UNPKG

@constl/bohr-db

Version:

Type-safe databases for OrbitDB.

139 lines 5.94 kB
import { Ajv } from "ajv"; import { joinKey, splitKey, asJoinedKey, } from "@orbitdb/nested-db"; import { getJoinedKey, removeUndefinedProperties, } from "./utils.js"; const nestedCache = new WeakMap(); export const typedNested = ({ db, schema, }) => { let dbCache = nestedCache.get(db); if (dbCache) { const cached = dbCache.get(schema); if (cached) return cached; } else { nestedCache.set(db, new WeakMap()); dbCache = nestedCache.get(db); } const ajv = new Ajv({ allowUnionTypes: true }); const rootValidator = ajv.compile(schema); const validators = {}; const getValidator = (key) => { let branchSchema = schema; for (const k of splitKey(key)) { if (branchSchema.additionalProperties === true) { validators[key] = (() => true); break; } branchSchema = branchSchema.properties?.[k] || branchSchema.additionalProperties; } if (!validators[key]) { validators[key] = ajv.compile(branchSchema); } return validators[key]; }; const supportedKey = (key) => { const keyComponents = typeof key === "string" ? splitKey(key) : key; let schemaBranch = schema; for (const k of keyComponents) { if (schemaBranch.additionalProperties) return true; // .? is necessary if schemaBranch does not have `properties` if (schemaBranch.properties?.[k] === undefined) return false; schemaBranch = schemaBranch.properties[k]; } return true; }; const proxy = new Proxy(db, { get(target, prop) { if (prop === "get") { const typedGet = async (key) => { const joinedKey = (typeof key === "string" ? key : getJoinedKey(key)); if (!supportedKey(key)) throw new Error(`Unsupported key ${joinedKey}.`); const value = await target.get(key); const valueValidator = getValidator(joinedKey); if (valueValidator(value)) return value; return undefined; }; return typedGet; } else if (prop === "del") { const typedDel = async (...args) => { const [key] = args; const joinedKey = typeof key === "string" ? key : joinKey(key); if (!supportedKey(key)) throw new Error(`Unsupported key ${joinedKey}.`); return target.del(key); }; return typedDel; } else if (prop === "all") { const typedAll = async () => { const jsonValue = await db.all(); if (rootValidator(jsonValue)) { return jsonValue; } throw new Error(JSON.stringify(rootValidator.errors, undefined, 2)); }; return typedAll; } else if (prop === "insert") { const typedInsert = async (keyOrValue, value) => { if (typeof keyOrValue === "string" || Array.isArray(keyOrValue)) { const joinedKey = asJoinedKey(keyOrValue); if (!supportedKey(joinedKey)) throw new Error(`Unsupported key ${joinedKey}.`); const valueValidator = getValidator(joinedKey); if (valueValidator(value)) { return await target.insert(joinedKey, value); } else { throw new Error(JSON.stringify(valueValidator.errors, undefined, 2)); } } else { // @ts-expect-error types in progress const data = removeUndefinedProperties(keyOrValue); if (rootValidator(data)) { return await db.insert(data); } else { const firstError = rootValidator.errors?.[0]; // Provide better error message if (firstError?.message?.includes("must NOT have additional properties")) { throw new Error(`Unsupported key ${firstError.instancePath.replace(/^\//, "")}/${firstError.params.additionalProperty}.`); } throw new Error(JSON.stringify(rootValidator.errors, undefined, 2)); } } }; return typedInsert; } else if (prop === "set" || prop === "put") { const typedPut = async (key, value) => { const joinedKey = (typeof key === "string" ? key : getJoinedKey(key)); if (!supportedKey(key)) throw new Error(`Unsupported key ${joinedKey}.`); const valueValidator = getValidator(joinedKey); if (valueValidator(value)) { return await target.put(joinedKey, value); } else { throw new Error(JSON.stringify(valueValidator.errors, undefined, 2)); } }; return typedPut; } return target[prop]; }, }); dbCache.set(schema, proxy); return proxy; }; //# sourceMappingURL=nested.js.map