@constl/bohr-db
Version:
Type-safe databases for OrbitDB.
139 lines • 5.94 kB
JavaScript
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