UNPKG

@constl/bohr-db

Version:

Type-safe databases for orbit-db.

123 lines (117 loc) 3.75 kB
import type { JSONSchemaType } from "ajv"; import { splitKey, type NestedDatabaseType, type NestedValue, toNested, } from "@orbitdb/nested-db"; import { DBElements, ExtractKeys, ExtractKeysAsList, GetValueFromKey, GetValueFromKeyList, RecursivePartial, } from "./types.js"; import { removeUndefinedProperties } from "./utils.js"; export type TypedNested<T extends NestedValue> = Omit< NestedDatabaseType, "put" | "set" | "putNested" | "del" | "get" | "all" > & { put<K extends ExtractKeys<T>>( key: K, value: GetValueFromKey<T, K>, ): Promise<string>; put<K extends ExtractKeysAsList<T>>( key: K, value: GetValueFromKeyList<T, K>, ): Promise<string>; set: TypedNested<T>["put"]; putNested(value: RecursivePartial<T>): Promise<string[]>; putNested<K extends ExtractKeys<T>>( key: K, value: GetValueFromKey<T, K>, ): Promise<string[]>; setNested: TypedNested<T>["putNested"]; del<K extends ExtractKeys<T>>(key: K): Promise<string>; get<K extends ExtractKeys<T>>( key: K, ): Promise<GetValueFromKey<T, K> | undefined>; all: () => Promise< { key: ExtractKeys<T>; value: GetValueFromKey<T, ExtractKeys<T>>; hash: string; }[] >; allAsJSON(): Promise<T>; }; export const typedNested = <T extends NestedValue>({ db, schema, }: { db: NestedDatabaseType; schema: JSONSchemaType<RecursivePartial<T>>; }): TypedNested<T> => { const supportedKey = (key: string | string[]) => { const keyComponents = typeof key === "string" ? splitKey(key) : key; let schemaBranch = schema; for (const k of keyComponents) { if (schemaBranch.additionalProperties) return true; if (schemaBranch.properties[k] === undefined) return false; schemaBranch = schemaBranch.properties[k]; } return true; }; // const compiledValidators: {[key in ExtractKeys<T>]: (x: unknown)=>x is GetValueFromKey<T, key>} = {}; /*const validateKeyValue = <K extends ExtractKeys<T>>( val: unknown, key: K ): val is GetValueFromKey<T, K> => { return compiledValidators[key](val) }*/ return new Proxy(db, { get(target, prop) { if (prop === "allAsJSON") { // Todo: type check const typedAllAsJSON: TypedNested<T>["allAsJSON"] = async () => toNested(await db.all()) as T; return typedAllAsJSON; } else if (prop === "setNested" || prop === "putNested") { const typedSetNested: TypedNested<T>["setNested"] = async < K extends ExtractKeys<T>, >( keyOrValue: K | RecursivePartial<T>, value?: GetValueFromKey<T, K>, ): Promise<string[]> => { if (typeof keyOrValue === "string") { // @ts-expect-error types in progress const data = removeUndefinedProperties(value); // Todo: type check return await db.putNested(keyOrValue, data); } else { // @ts-expect-error types in progress const data = removeUndefinedProperties(keyOrValue); // Todo: type check return await db.putNested(data); } }; return typedSetNested; } /*if (prop === "get") { return async <K extends ExtractKeys<T>>(key: K): Promise<GetValueFromKey<T, K> | undefined> => { if (!supportedKey(key)) throw new Error(`Unsupported key ${key}.`); const val = await target.get(key); if (val === undefined) return val; if (validateKeyValue(val, key)) { return val } else { return undefined; }; }; } else { };*/ return target[prop as keyof typeof target]; }, }) as unknown as TypedNested<T>; };