UNPKG

@constl/bohr-db

Version:

Type-safe databases for orbit-db.

105 lines (91 loc) 3.08 kB
import Ajv, { JSONSchemaType, ValidateFunction } from "ajv"; import type { DBElements, ExtractKeys, ExtractKeysAsList } from "./types"; import { DagCborEncodable } from "@orbitdb/core"; import { CID } from "multiformats/cid"; import { joinKey } from "@orbitdb/nested-db"; type NestedValueObject = { [key: string]: DagCborEncodable | NestedValueObject; }; const ajv = new Ajv({ allowUnionTypes: true }); export const generateListValidator = <T extends DBElements>( schema: JSONSchemaType<T>, ): { validate: ValidateFunction<T> } => { const validate = ajv.compile(schema); return { validate, }; }; export const generateDictValidator = <T extends { [clef: string]: DBElements }>( schema: JSONSchemaType<Partial<T>>, ): { validateRoot: ValidateFunction<Partial<T>>; getKeyValidator: <K extends keyof T>(key: K) => ValidateFunction<T[K]>; validateKey: <K extends keyof T>(v: unknown, key: K) => v is T[K]; supportedKey: <K extends string>(key: K) => boolean; } => { const validateRoot = ajv.compile<Partial<T>>(schema); const compileKeySchema = ( s: JSONSchemaType<T[keyof T]> | JSONSchemaType<T[keyof T]>["properties"], ) => { // Apparently necessary to avoid AJV error if `nullable: true` and value is `undefined` if (s === true) { return () => true; } return ajv.compile(s); }; const validators = Object.fromEntries( ( Object.entries(schema.properties || {}) as [ keyof T, JSONSchemaType<T[keyof T]>, ][] ).map(([c, p]) => [c, compileKeySchema(p)]), ) as { [clef in keyof T]: ValidateFunction<T[clef]> }; const validateAdditionalProperties = schema.additionalProperties ? compileKeySchema(schema.additionalProperties) : () => false; const validateKey = <K extends keyof T>(v: unknown, key: K): v is T[K] => { const vld = getKeyValidator(key); return vld(v); }; const getKeyValidator = <K extends keyof T>(key: K) => { return validators[key] || validateAdditionalProperties; }; const supportedKey = (key: string): boolean => { return !!validators[key] || !!schema.additionalProperties; }; return { validateRoot, validateKey, getKeyValidator, supportedKey, }; }; export type NoUndefinedField<T> = { [P in keyof T]-?: T[P] extends undefined ? never : T[P]; }; export const removeUndefinedProperties = < T extends { [clef: string]: DagCborEncodable | undefined }, >( objet: T, ): NoUndefinedField<T> => { return Object.fromEntries( Object.entries(objet) .filter(([_clef, val]) => val !== undefined) .map(([clef, val]): [string, DBElements] => { return [ clef, typeof val === "object" && !Array.isArray(val) && !(val instanceof CID) ? removeUndefinedProperties(val as NestedValueObject) : val!, ] as [string, DBElements]; }), ) as NoUndefinedField<T>; }; export const getJoinedKey = <T extends NestedValueObject>( key: ExtractKeysAsList<T>, ): ExtractKeys<T> => { return joinKey(key) as ExtractKeys<T>; };