UNPKG

@constl/bohr-db

Version:

Type-safe databases for orbit-db.

360 lines (299 loc) 9.6 kB
import { type HeliaLibp2p } from "helia"; import { rimraf } from "rimraf"; import { createTestHelia } from "./config.js"; import { Identities, Identity, KeyStore, KeyStoreType } from "@orbitdb/core"; import { OrderedKeyValue, OrderedKeyValueDatabaseType, } from "@orbitdb/ordered-keyvalue-db"; import { TypedOrderedKeyValue, typedOrderedKeyValue, } from "@/ordered-keyvalue.js"; import { chai, chaiAsPromised, expect } from "aegir/chai"; import { JSONSchemaType } from "ajv"; chai.use(chaiAsPromised); const keysPath = "./testkeys"; const removeHash = <T>( data: { key: string; value: T; hash: string }[], ): { key: string; value: T }[] => data.map((x) => ({ key: x.key, value: x.value })); describe("Typed OrderedKeyValue", () => { let ipfs: HeliaLibp2p; let identities; let keystore: KeyStoreType; let testIdentity1: Identity; let db: OrderedKeyValueDatabaseType; const databaseId = "ordered-keyvalue-AAA"; before(async () => { ipfs = await createTestHelia(); keystore = await KeyStore({ path: keysPath }); identities = await Identities({ keystore }); testIdentity1 = await identities.createIdentity({ id: "userA" }); }); after(async () => { if (ipfs) { await ipfs.stop(); } if (keystore) { await keystore.close(); } await rimraf(keysPath); await rimraf("./orbitdb"); }); describe("Creating a Typed OrderedKeyValue database", () => { type structure = { a: number; b: string }; const schema: JSONSchemaType<Partial<structure>> = { type: "object", properties: { a: { type: "number", nullable: true }, b: { type: "string", nullable: true }, }, required: [], }; let typedDB: TypedOrderedKeyValue<structure>; beforeEach(async () => { db = await OrderedKeyValue()({ ipfs, identity: testIdentity1, address: databaseId, }); typedDB = typedOrderedKeyValue({ db, schema, }); }); afterEach(async () => { if (typedDB) { await typedDB.drop(); await typedDB.close(); } }); it("wraps an OrderedKeyValue store", async () => { expect(typedDB.type).to.equal("ordered-keyvalue"); }); it("returns 0 items when it's a fresh database", async () => { const all = await typedDB.all(); expect(all).to.be.an.empty("object"); }); }); describe("Typed OrderedKeyValue database - fixed properties", () => { type structure = { a: number; b: string }; const schema: JSONSchemaType<Partial<structure>> = { type: "object", properties: { a: { type: "number", nullable: true }, b: { type: "string", nullable: true }, }, additionalProperties: false, required: [], }; let typedDB: TypedOrderedKeyValue<structure>; beforeEach(async () => { db = await OrderedKeyValue()({ ipfs, identity: testIdentity1, address: databaseId, }); typedDB = typedOrderedKeyValue({ db, schema, }); }); afterEach(async () => { if (typedDB) { await typedDB.drop(); await typedDB.close(); } }); it("put valid key/value", async () => { await typedDB.put("a", 1); await typedDB.put("b", "c"); const actual = await typedDB.all(); expect(removeHash(actual)).to.deep.equal([ { key: "a", value: 1 }, { key: "b", value: "c" }, ]); }); it("error on put invalid key", async () => { // @ts-expect-error Deliberately adding invalid key await expect(typedDB.put("c", 3)).to.be.rejectedWith( "Unsupported key c.", ); }); it("error on move invalid key", async () => { // @ts-expect-error Deliberately adding invalid key await expect(typedDB.put("c", 3)).to.be.rejectedWith( "Unsupported key c.", ); }); it("error on put invalid value", async () => { await expect( // @ts-expect-error Deliberately adding invalid value typedDB.put("a", "text"), ).to.be.rejectedWith("must be number"); }); it("get valid key", async () => { await typedDB.put("a", 1); const actual = await typedDB.get("a"); expect(actual).to.deep.equal({ value: 1, position: undefined }); }); it("move valid key", async () => { await typedDB.put("a", 1); await typedDB.put("b", "c"); await typedDB.move("a", 1); const actual = await typedDB.all(); expect(removeHash(actual)).to.deep.equal([ { key: "b", value: "c" }, { key: "a", value: 1 }, ]); }); it("error on get invalid key", async () => { await expect( // @ts-expect-error Deliberately getting invalid key typedDB.get("d"), ).to.be.rejectedWith("Unsupported key d."); }); it("error on move invalid key", async () => { await typedDB.put("a", 1); await typedDB.put("b", "c"); // @ts-expect-error Deliberately moving invalid key await expect(typedDB.move("d", 1)).to.be.rejectedWith( "Unsupported key d.", ); }); it("invalid keys in log do not appear on `db.all()`", async () => { // Force write invalid key to underlying orbit-db database await db.put("d", 4); await typedDB.put("a", 1); await typedDB.set("b", "c"); const actual = await typedDB.all(); expect(removeHash(actual)).to.deep.equal([ { key: "a", value: 1 }, { key: "b", value: "c" }, ]); }); it("invalid keys moved in log do not appear on `db.all()`", async () => { await typedDB.put("a", 1); await typedDB.set("b", "c"); // Force write invalid key to underlying orbit-db database await db.move("d", 2); const actual = await typedDB.all(); expect(removeHash(actual)).to.deep.equal([ { key: "a", value: 1 }, { key: "b", value: "c" }, ]); }); it("invalid values in log lead do not appear on `db.all()`", async () => { // Force write invalid value to underlying orbit-db database await db.put("a", "text"); await typedDB.set("b", "c"); const actual = await typedDB.all(); expect(removeHash(actual)).to.deep.equal([{ key: "b", value: "c" }]); }); }); describe("Typed OrderedKeyValue database - additional properties", () => { type structure = { [key: string]: number }; const schema: JSONSchemaType<Partial<structure>> = { type: "object", additionalProperties: { type: "number", }, required: [], }; let typedDB: TypedOrderedKeyValue<structure>; beforeEach(async () => { db = await OrderedKeyValue()({ ipfs, identity: testIdentity1, address: databaseId, }); typedDB = typedOrderedKeyValue({ db, schema, }); }); afterEach(async () => { if (typedDB) { await typedDB.drop(); await typedDB.close(); } }); it("add additional property key", async () => { const hash = await typedDB.put("d", 1); expect(hash).to.be.a("string"); }); it("get additional property key/value", async () => { await typedDB.put("d", 1); const actual = await typedDB.all(); expect(removeHash(actual)).to.deep.equal([{ key: "d", value: 1 }]); }); it("error on add invalid additional property value", async () => { await expect( // @ts-expect-error Deliberately adding invalid value typedDB.put("d", "text"), ).to.be.rejectedWith("must be number"); }); it("invalid values in log do not appear on `db.all()`", async () => { // Force write invalid value to underlying orbit-db database await db.put("d", "text"); await typedDB.put("a", 1); await typedDB.set("b", 2); const actual = await typedDB.all(); expect(removeHash(actual)).to.deep.equal([ { key: "a", value: 1 }, { key: "b", value: 2 }, ]); }); }); describe("Typed OrderedKeyValue database - undefined properties", () => { type structure = { a: { b?: number; c: number } }; const schema: JSONSchemaType<Partial<structure>> = { type: "object", properties: { a: { type: "object", properties: { b: { type: "number", nullable: true }, c: { type: "number" }, }, required: ["c"], nullable: true, }, }, additionalProperties: false, required: [], }; let typedDB: TypedOrderedKeyValue<structure>; beforeEach(async () => { db = await OrderedKeyValue()({ ipfs, identity: testIdentity1, address: databaseId, }); typedDB = typedOrderedKeyValue({ db, schema, }); }); afterEach(async () => { if (typedDB) { await typedDB.drop(); await typedDB.close(); } }); it("put valid key/value", async () => { await typedDB.put("a", { b: 1, c: 2 }); const actual = await typedDB.all(); expect(removeHash(actual)).to.deep.equal([ { key: "a", value: { b: 1, c: 2 } }, ]); }); it("delete key with undefined value", async () => { // @ts-expect-error Deliberately adding explicit undefined value await typedDB.put("a", { b: undefined, c: 2 }); const actual = await typedDB.all(); expect(removeHash(actual)).to.deep.equal([{ key: "a", value: { c: 2 } }]); }); }); });