diff --git a/README.md b/README.md index 1a251b3..13c4e2e 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,2 @@ -# @mifi/auth +# @mifi/breakerbox-db diff --git a/package.json b/package.json index fa27bd4..a52f5f4 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@mifi/breakerbox-db", - "version": "1.0.3", + "version": "1.0.13", "author": "mifi (Mike Fitzpatrick)", "license": "MIT", "scripts": { @@ -13,6 +13,10 @@ "prettier:fix": "prettier --write 'src/**/*.ts'", "test": "jest --passWithNoTests" }, + "dependencies": { + "level": "^8.0.0", + "yaml": "^2.3.1" + }, "devDependencies": { "@babel/core": "^7.21.8", "@babel/preset-env": "^7.21.5", @@ -38,14 +42,10 @@ "ts-node": "^10.9.1", "typescript": "^4.9.5" }, - "description": "", + "description": "A Level-based key/value store for application settings", "repository": { "type": "git", "url": "https://git.mifi.dev/mifi/breakerbox-db.git" }, - "packageManager": "yarn@3.5.1", - "dependencies": { - "lowdb": "^6.0.1", - "yaml": "^2.3.1" - } + "packageManager": "yarn@3.5.1" } diff --git a/src/adapters/json.ts b/src/adapters/json.ts deleted file mode 100644 index 6c8b9ed..0000000 --- a/src/adapters/json.ts +++ /dev/null @@ -1,4 +0,0 @@ -import { JSONFile } from 'lowdb/node'; -import { Fuses, Panel } from '../types/Panel'; - -export const jsonAdapter = (file: string) => new JSONFile>(file); diff --git a/src/adapters/yaml.ts b/src/adapters/yaml.ts deleted file mode 100644 index f184d9c..0000000 --- a/src/adapters/yaml.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { TextFile } from 'lowdb/node'; -import { parse, stringify } from 'yaml'; -import { Fuses, Panel } from '../types/Panel'; - -class YAMLFile { - private adapter: TextFile; - - constructor(filename: string) { - this.adapter = new TextFile(filename); - } - - async read() { - const data = await this.adapter.read(); - if (data === null) { - return null; - } else { - return parse(data) as Panel; - } - } - - write(obj: Panel) { - return this.adapter.write(stringify(obj)); - } -} - -export const yamlAdapter = (filename: string) => new YAMLFile>(filename); diff --git a/src/db/index.ts b/src/db/index.ts new file mode 100644 index 0000000..daad8cc --- /dev/null +++ b/src/db/index.ts @@ -0,0 +1,8 @@ +import { Level } from 'level'; +import path from 'path'; +import { Fuse } from '../types/Fuse'; + +const dbPath = process.env.SETTINGS_DB_PATH || path.join(__dirname, 'breakers'); +const db = new Level(dbPath, { valueEncoding: 'json' }); + +export { db }; diff --git a/src/index.ts b/src/index.ts index 549b122..d93fd5a 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,99 +1,91 @@ -import { Adapter, Low } from 'lowdb'; -import { join, dirname } from 'node:path'; -import { fileURLToPath } from 'node:url'; +import { Level } from 'level'; -import { jsonAdapter } from './adapters/json'; -import { yamlAdapter } from './adapters/yaml'; -import { Fuses, Panel } from './types/Panel'; -import { ValidValues } from './types/ValidValues'; -import { SettingsMeta } from './types/SettingsMeta'; +import { Fuse } from './types/Fuse.js'; +import { FuseMeta } from './types/FuseMeta.js'; +import { ValidValues } from './types/ValidValues.js'; +import { db } from './db'; -interface BreakerboxProps { - filename?: string; - path?: string; - defaultStore?: Panel; - storageType: 'json' | 'yaml'; -} -export class Breakerbox { - private static instance: Breakerbox; - protected db; - public static defaultStore = { fuses: {} as Fuses } as Panel; +class Breakerbox { + private db; + private cachedSettings = new Map(); - constructor( - filename: BreakerboxProps['filename'] = 'settings.json', - path: BreakerboxProps['path'] = fileURLToPath(import.meta.url), - defaultStore: BreakerboxProps['defaultStore'] = Breakerbox.defaultStore, - storageType: BreakerboxProps['storageType'] = 'json', - ) { - const __dirname = dirname(path); - const file = join(__dirname, filename); - const adapter = storageType === 'yaml' ? yamlAdapter(file) : jsonAdapter(file); - this.db = new Low>(>>adapter, defaultStore); - this.db.read(); + private static readonly KEY_PREFIX = 'fuse:'; + + constructor(db: Level) { + this.db = db; + this.updateCache(); } - public async delete(key: string) { - delete this.db.data.fuses[key]; - await this.db.write(); - return true; - } + private getKey = (key: string) => `${Breakerbox.KEY_PREFIX}${key}`; + private stripKey = (key: string) => key.replace(Breakerbox.KEY_PREFIX, ''); - public get(key: string) { - return this.getAll()[key]; - } - - public getAll() { - return this.db.data.fuses; - } - - public static getInstance(props = {} as BreakerboxProps) { - if (!Breakerbox.instance) { - Breakerbox.instance = new Breakerbox(props.filename, props.path, props.defaultStore, props.storageType); + private updateCache = async () => { + for await (const [key, value] of this.db.iterator({ + gte: Breakerbox.KEY_PREFIX, + lte: String.fromCharCode(Breakerbox.KEY_PREFIX.charCodeAt(0) + 1), + })) { + this.cachedSettings.set(this.stripKey(key), value); } - return Breakerbox.instance; - } + }; - public getValue(key: string, defaultValue?: T, insert = false) { - const fuse = this.get(key); + public delete = async (key: string) => { + if (await this.db.del(this.getKey(key)).then(() => true)) { + return true; + } + return false; + }; - if (fuse) { - return fuse.value as T; + public get = (key: string) => { + return this.cachedSettings.get(key); + }; + + public getAll = () => { + return this.cachedSettings; + }; + + public getOrSetValue = (key: string, defaultValue: T, insert = false) => { + const cached = this.cachedSettings.get(key); + if (cached) { + return cached.value as T; } if (insert && defaultValue) { - this.set(key, defaultValue); + this.set(key, defaultValue, { type: typeof defaultValue as FuseMeta['type'] }); } return defaultValue as T; - } + }; - public async set(key: string, value: ValidValues, meta = { type: typeof value }) { + public set = async (key: string, value: ValidValues, meta = { type: typeof value }) => { if (['boolean', 'number', 'object', 'string'].includes(meta.type)) { - this.db.data.fuses[key] = { value, meta: { ...meta, type: meta.type as SettingsMeta['type'] } }; - await this.db.write(); - return true; + try { + const fuse = { value, meta: { ...meta, type: meta.type as FuseMeta['type'] } }; + await this.db.put(this.getKey(key), fuse); + this.cachedSettings.set(key, await this.db.get(this.getKey(key))); + return true; + } catch (err) { + console.error('Error: Database Error', { key, meta, value }); + return false; + } } else { console.error('Error: Invalid type', { key, meta, value }); throw new Error('Invalid type'); } - } + }; - public async update(key: string, value: ValidValues, meta = {} as SettingsMeta) { - const fuse = this.db.data.fuses[key]; + public update = async (key: string, value: ValidValues, meta = {} as FuseMeta) => { + const fuse = this.get(key); - if (!fuse) { - return this.set(key, value, meta); + if (!fuse && (await this.set(key, value, meta))) { + return true; } - if (fuse) { - this.db.data.fuses[key] = { - value: value ? value : fuse.value, - meta: { ...fuse.meta, ...meta }, - }; - await this.db.write(); + if (fuse && (await this.set(key, value ? value : fuse.value, { ...fuse.meta, ...meta }))) { return true; } return false; - } + }; } + +export const breakerbox = new Breakerbox(db); diff --git a/src/types/Fuse.ts b/src/types/Fuse.ts new file mode 100644 index 0000000..e53d05e --- /dev/null +++ b/src/types/Fuse.ts @@ -0,0 +1,7 @@ +import { FuseMeta } from './FuseMeta.js'; +import { ValidValues } from './ValidValues.js'; + +export interface Fuse { + meta: FuseMeta; + value: ValidValues; +} diff --git a/src/types/SettingsMeta.ts b/src/types/FuseMeta.ts similarity index 71% rename from src/types/SettingsMeta.ts rename to src/types/FuseMeta.ts index 022c3c2..dc280cc 100644 --- a/src/types/SettingsMeta.ts +++ b/src/types/FuseMeta.ts @@ -1,4 +1,4 @@ -export interface SettingsMeta { +export interface FuseMeta { type: 'boolean' | 'number' | 'object' | 'string'; category?: string; } diff --git a/src/types/Panel.ts b/src/types/Panel.ts deleted file mode 100644 index e21aa54..0000000 --- a/src/types/Panel.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { SettingsMeta } from './SettingsMeta'; -import { ValidValues } from './ValidValues'; - -export interface Panel { - fuses: T; -} - -export interface Setting { - meta: SettingsMeta; - value: ValidValues; -} - -export type Fuses = { - [key: string]: Setting; -}; diff --git a/src/types/ValidValues.ts b/src/types/ValidValues.ts index 230cc58..b6ad57d 100644 --- a/src/types/ValidValues.ts +++ b/src/types/ValidValues.ts @@ -1,4 +1,4 @@ -type Primitives = boolean | null | number | string; -type Arrays = (boolean | null | number | string)[] | KeyValue[]; -export type KeyValue = { [key: string]: ValidValues }; +export type Primitives = boolean | null | number | string; +export type Arrays = (boolean | null | number | string)[] | KeyValue[]; export type ValidValues = Arrays | KeyValue | Primitives; +export type KeyValue = { [key: string]: ValidValues }; diff --git a/tsconfig.json b/tsconfig.json index dc2a0f5..422e4f8 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,12 +1,10 @@ { - "extends": "@tsconfig/node20/tsconfig.json", + "extends": "@tsconfig/node16/tsconfig.json", "compilerOptions": { "allowSyntheticDefaultImports": true, "declaration": true, "experimentalDecorators": true, "emitDecoratorMetadata": true, - "module": "es2022", - "moduleResolution": "Node", "noImplicitAny": true, "outDir": "lib/", "rootDirs": ["./", "src/"], diff --git a/tsconfig.production.json b/tsconfig.production.json index 6a1a296..422e4f8 100644 --- a/tsconfig.production.json +++ b/tsconfig.production.json @@ -5,8 +5,6 @@ "declaration": true, "experimentalDecorators": true, "emitDecoratorMetadata": true, - "module": "es2022", - "moduleResolution": "Node", "noImplicitAny": true, "outDir": "lib/", "rootDirs": ["./", "src/"], diff --git a/yarn.lock b/yarn.lock index 607923d..a4b7b23 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1867,7 +1867,7 @@ __metadata: eslint-plugin-prettier: ^4.2.1 eslint-plugin-promise: ^6.0.0 jest: ^29.5.0 - lowdb: ^6.0.1 + level: ^8.0.0 prettier: ^2.8.4 prettier-eslint: ^15.0.1 prettier-eslint-cli: ^7.1.0 @@ -2322,6 +2322,21 @@ __metadata: languageName: node linkType: hard +"abstract-level@npm:^1.0.2": + version: 1.0.3 + resolution: "abstract-level@npm:1.0.3" + dependencies: + buffer: ^6.0.3 + catering: ^2.1.0 + is-buffer: ^2.0.5 + level-supports: ^4.0.0 + level-transcoder: ^1.0.1 + module-error: ^1.0.1 + queue-microtask: ^1.2.3 + checksum: 70d61a3924526ebc257b138992052f9ff571a6cee5a7660836e37a1cc7081273c3acf465dd2f5e1897b38dc743a6fd9dba14a5d8a2a9d39e5787cd3da99f301d + languageName: node + linkType: hard + "acorn-jsx@npm:^5.3.2": version: 5.3.2 resolution: "acorn-jsx@npm:5.3.2" @@ -2695,6 +2710,13 @@ __metadata: languageName: node linkType: hard +"base64-js@npm:^1.3.1": + version: 1.5.1 + resolution: "base64-js@npm:1.5.1" + checksum: 669632eb3745404c2f822a18fc3a0122d2f9a7a13f7fb8b5823ee19d1d2ff9ee5b52c53367176ea4ad093c332fd5ab4bd0ebae5a8e27917a4105a4cfc86b1005 + languageName: node + linkType: hard + "big-integer@npm:^1.6.44": version: 1.6.51 resolution: "big-integer@npm:1.6.51" @@ -2746,6 +2768,18 @@ __metadata: languageName: node linkType: hard +"browser-level@npm:^1.0.1": + version: 1.0.1 + resolution: "browser-level@npm:1.0.1" + dependencies: + abstract-level: ^1.0.2 + catering: ^2.1.1 + module-error: ^1.0.2 + run-parallel-limit: ^1.1.0 + checksum: 67fbc77ce832940bfa25073eccff279f512ad56f545deb996a5b23b02316f5e76f4a79d381acc27eda983f5c9a2566aaf9c97e4fdd0748288c4407307537a29b + languageName: node + linkType: hard + "browserslist@npm:^4.21.3, browserslist@npm:^4.21.5": version: 4.21.5 resolution: "browserslist@npm:4.21.5" @@ -2776,6 +2810,16 @@ __metadata: languageName: node linkType: hard +"buffer@npm:^6.0.3": + version: 6.0.3 + resolution: "buffer@npm:6.0.3" + dependencies: + base64-js: ^1.3.1 + ieee754: ^1.2.1 + checksum: 5ad23293d9a731e4318e420025800b42bf0d264004c0286c8cc010af7a270c7a0f6522e84f54b9ad65cbd6db20b8badbfd8d2ebf4f80fa03dab093b89e68c3f9 + languageName: node + linkType: hard + "builtins@npm:^5.0.1": version: 5.0.1 resolution: "builtins@npm:5.0.1" @@ -2870,6 +2914,13 @@ __metadata: languageName: node linkType: hard +"catering@npm:^2.1.0, catering@npm:^2.1.1": + version: 2.1.1 + resolution: "catering@npm:2.1.1" + checksum: 205daefa69c935b0c19f3d8f2e0a520dd69aebe9bda55902958003f7c9cff8f967dfb90071b421bd6eb618576f657a89d2bc0986872c9bc04bbd66655e9d4bd6 + languageName: node + linkType: hard + "chalk@npm:^1.1.3": version: 1.1.3 resolution: "chalk@npm:1.1.3" @@ -2932,6 +2983,20 @@ __metadata: languageName: node linkType: hard +"classic-level@npm:^1.2.0": + version: 1.3.0 + resolution: "classic-level@npm:1.3.0" + dependencies: + abstract-level: ^1.0.2 + catering: ^2.1.0 + module-error: ^1.0.1 + napi-macros: ^2.2.2 + node-gyp: latest + node-gyp-build: ^4.3.0 + checksum: 773da48aef52a041115d413fee8340b357a4da2eb505764f327183b155edd7cc9d24819eb4f707c83dbdae8588024f5dddeb322125567c59d5d1f6f16334cdb9 + languageName: node + linkType: hard + "clean-stack@npm:^2.0.0": version: 2.2.0 resolution: "clean-stack@npm:2.2.0" @@ -4314,6 +4379,13 @@ __metadata: languageName: node linkType: hard +"ieee754@npm:^1.2.1": + version: 1.2.1 + resolution: "ieee754@npm:1.2.1" + checksum: 5144c0c9815e54ada181d80a0b810221a253562422e7c6c3a60b1901154184f49326ec239d618c416c1c5945a2e197107aee8d986a3dd836b53dffefd99b5e7e + languageName: node + linkType: hard + "ignore@npm:^5.1.1, ignore@npm:^5.2.0": version: 5.2.4 resolution: "ignore@npm:5.2.4" @@ -4436,6 +4508,13 @@ __metadata: languageName: node linkType: hard +"is-buffer@npm:^2.0.5": + version: 2.0.5 + resolution: "is-buffer@npm:2.0.5" + checksum: 764c9ad8b523a9f5a32af29bdf772b08eb48c04d2ad0a7240916ac2688c983bf5f8504bf25b35e66240edeb9d9085461f9b5dae1f3d2861c6b06a65fe983de42 + languageName: node + linkType: hard + "is-callable@npm:^1.1.3, is-callable@npm:^1.1.4, is-callable@npm:^1.2.7": version: 1.2.7 resolution: "is-callable@npm:1.2.7" @@ -5243,6 +5322,33 @@ __metadata: languageName: node linkType: hard +"level-supports@npm:^4.0.0": + version: 4.0.1 + resolution: "level-supports@npm:4.0.1" + checksum: d4552b42bb8cdeada07b0f6356c7a90fefe76279147331f291aceae26e3e56d5f927b09ce921647c0230bfe03ddfbdcef332be921e5c2194421ae2bfa3cf6368 + languageName: node + linkType: hard + +"level-transcoder@npm:^1.0.1": + version: 1.0.1 + resolution: "level-transcoder@npm:1.0.1" + dependencies: + buffer: ^6.0.3 + module-error: ^1.0.1 + checksum: 304f08d802faf3491a533b6d87ad8be3cabfd27f2713bbe9d4c633bf50fcb9460eab5a6776bf015e101ead7ba1c1853e05e7f341112f17a9d0cb37ee5a421a25 + languageName: node + linkType: hard + +"level@npm:^8.0.0": + version: 8.0.0 + resolution: "level@npm:8.0.0" + dependencies: + browser-level: ^1.0.1 + classic-level: ^1.2.0 + checksum: 13eb25bd71bfdca6cd714d1233adf9da97de9a8a4bf9f28d62a390b5c96d0250abaf983eb90eb8c4e89c7a985bb330750683d106f12670e5ea8fba1d7e608a1f + languageName: node + linkType: hard + "leven@npm:^3.1.0": version: 3.1.0 resolution: "leven@npm:3.1.0" @@ -5340,15 +5446,6 @@ __metadata: languageName: node linkType: hard -"lowdb@npm:^6.0.1": - version: 6.0.1 - resolution: "lowdb@npm:6.0.1" - dependencies: - steno: ^3.0.0 - checksum: d555a5bcc2e4a963fae89209b693a6f2b7b69bae915ff67355537b7a14a4f6e44bc273467bc3d4d7e81660c1313587ee3bfebf044d50d3213a5e95ea7f07ded4 - languageName: node - linkType: hard - "lru-cache@npm:^5.1.1": version: 5.1.1 resolution: "lru-cache@npm:5.1.1" @@ -5586,6 +5683,13 @@ __metadata: languageName: node linkType: hard +"module-error@npm:^1.0.1, module-error@npm:^1.0.2": + version: 1.0.2 + resolution: "module-error@npm:1.0.2" + checksum: 5d653e35bd55b3e95f8aee2cdac108082ea892e71b8f651be92cde43e4ee86abee4fa8bd7fc3fe5e68b63926d42f63c54cd17b87a560c31f18739295575a3962 + languageName: node + linkType: hard + "moo@npm:^0.5.1": version: 0.5.2 resolution: "moo@npm:0.5.2" @@ -5607,6 +5711,13 @@ __metadata: languageName: node linkType: hard +"napi-macros@npm:^2.2.2": + version: 2.2.2 + resolution: "napi-macros@npm:2.2.2" + checksum: c6f9bd71cdbbc37ddc3535aa5be481238641d89585b8a3f4d301cb89abf459e2d294810432bb7d12056d1f9350b1a0899a5afcf460237a3da6c398cf0fec7629 + languageName: node + linkType: hard + "natural-compare-lite@npm:^1.4.0": version: 1.4.0 resolution: "natural-compare-lite@npm:1.4.0" @@ -5628,6 +5739,17 @@ __metadata: languageName: node linkType: hard +"node-gyp-build@npm:^4.3.0": + version: 4.6.0 + resolution: "node-gyp-build@npm:4.6.0" + bin: + node-gyp-build: bin.js + node-gyp-build-optional: optional.js + node-gyp-build-test: build-test.js + checksum: 25d78c5ef1f8c24291f4a370c47ba52fcea14f39272041a90a7894cd50d766f7c8cb8fb06c0f42bf6f69b204b49d9be3c8fc344aac09714d5bdb95965499eb15 + languageName: node + linkType: hard + "node-gyp@npm:latest": version: 9.3.1 resolution: "node-gyp@npm:9.3.1" @@ -6081,7 +6203,7 @@ __metadata: languageName: node linkType: hard -"queue-microtask@npm:^1.2.2": +"queue-microtask@npm:^1.2.2, queue-microtask@npm:^1.2.3": version: 1.2.3 resolution: "queue-microtask@npm:1.2.3" checksum: b676f8c040cdc5b12723ad2f91414d267605b26419d5c821ff03befa817ddd10e238d22b25d604920340fd73efd8ba795465a0377c4adf45a4a41e4234e42dc4 @@ -6306,6 +6428,15 @@ __metadata: languageName: node linkType: hard +"run-parallel-limit@npm:^1.1.0": + version: 1.1.0 + resolution: "run-parallel-limit@npm:1.1.0" + dependencies: + queue-microtask: ^1.2.2 + checksum: 672c3b87e7f939c684b9965222b361421db0930223ed1e43ebf0e7e48ccc1a022ea4de080bef4d5468434e2577c33b7681e3f03b7593fdc49ad250a55381123c + languageName: node + linkType: hard + "run-parallel@npm:^1.1.9": version: 1.2.0 resolution: "run-parallel@npm:1.2.0" @@ -6508,13 +6639,6 @@ __metadata: languageName: node linkType: hard -"steno@npm:^3.0.0": - version: 3.0.0 - resolution: "steno@npm:3.0.0" - checksum: fb928451a4f96342b496b71147fbca0a20a5daf7bfd23a4a1cec8640d3c6c67176809169e9a5801ea44490d448b5b7ecb151b9fba434872c2d65549847f39460 - languageName: node - linkType: hard - "string-length@npm:^4.0.1": version: 4.0.2 resolution: "string-length@npm:4.0.2"