This commit is contained in:
2023-06-01 00:07:50 -04:00
parent c7786b3b21
commit 7be9f4aa43
13 changed files with 231 additions and 149 deletions

View File

@@ -1,2 +1,2 @@
# @mifi/auth
# @mifi/breakerbox-db

View File

@@ -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"
}

View File

@@ -1,4 +0,0 @@
import { JSONFile } from 'lowdb/node';
import { Fuses, Panel } from '../types/Panel';
export const jsonAdapter = <T = Fuses>(file: string) => new JSONFile<Panel<T>>(file);

View File

@@ -1,26 +0,0 @@
import { TextFile } from 'lowdb/node';
import { parse, stringify } from 'yaml';
import { Fuses, Panel } from '../types/Panel';
class YAMLFile<T = Fuses> {
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<T>;
}
}
write(obj: Panel<T>) {
return this.adapter.write(stringify(obj));
}
}
export const yamlAdapter = <T = Fuses>(filename: string) => new YAMLFile<Panel<T>>(filename);

8
src/db/index.ts Normal file
View File

@@ -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<string, Fuse>(dbPath, { valueEncoding: 'json' });
export { db };

View File

@@ -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<string, Fuse>();
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<Fuses>(file) : jsonAdapter<Fuses>(file);
this.db = new Low<Panel<Fuses>>(<Adapter<Panel<Fuses>>>adapter, defaultStore);
this.db.read();
private static readonly KEY_PREFIX = 'fuse:';
constructor(db: Level<string, Fuse>) {
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<T = ValidValues>(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 = <T = ValidValues>(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);

7
src/types/Fuse.ts Normal file
View File

@@ -0,0 +1,7 @@
import { FuseMeta } from './FuseMeta.js';
import { ValidValues } from './ValidValues.js';
export interface Fuse {
meta: FuseMeta;
value: ValidValues;
}

View File

@@ -1,4 +1,4 @@
export interface SettingsMeta {
export interface FuseMeta {
type: 'boolean' | 'number' | 'object' | 'string';
category?: string;
}

View File

@@ -1,15 +0,0 @@
import { SettingsMeta } from './SettingsMeta';
import { ValidValues } from './ValidValues';
export interface Panel<T = Fuses> {
fuses: T;
}
export interface Setting {
meta: SettingsMeta;
value: ValidValues;
}
export type Fuses = {
[key: string]: Setting;
};

View File

@@ -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 };

View File

@@ -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/"],

View File

@@ -5,8 +5,6 @@
"declaration": true,
"experimentalDecorators": true,
"emitDecoratorMetadata": true,
"module": "es2022",
"moduleResolution": "Node",
"noImplicitAny": true,
"outDir": "lib/",
"rootDirs": ["./", "src/"],

160
yarn.lock
View File

@@ -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"