UNPKG

@constl/bohr-db

Version:

Type-safe databases for OrbitDB.

574 lines (462 loc) 16.4 kB
import { type Helia } from "helia"; import { rimraf } from "rimraf"; import { createTestHelia } from "./config.ts"; import { Identities, KeyStore } from "@orbitdb/core"; import type { Identity, KeyStoreType } from "@orbitdb/core"; import { chai, chaiAsPromised, expect } from "aegir/chai"; import { type JSONSchemaType } from "ajv"; import { Nested, type NestedDatabaseType } from "@orbitdb/nested-db"; import { type TypedNested, typedNested } from "../src/nested.ts"; import { type RecursivePartial } from "../src/types.ts"; chai.use(chaiAsPromised); const keysPath = "./testkeys-nested"; describe("Typed Nested", () => { let ipfs: Helia; let identities; let keystore: KeyStoreType; let testIdentity1: Identity; let db: NestedDatabaseType; const databaseId = "nested-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(); } await rimraf(keysPath); await rimraf("./orbitdb"); }); describe("Creating a Typed Nested database", () => { type Structure = { a: number; b: { c: string } }; const schema: JSONSchemaType<RecursivePartial<Structure>> = { type: "object", properties: { a: { type: "number", nullable: true }, b: { type: "object", properties: { c: { type: "string", nullable: true }, }, required: [], nullable: true, }, }, required: [], }; let typedDB: TypedNested<Structure>; beforeEach(async () => { db = await Nested()({ ipfs, identity: testIdentity1, address: databaseId, }); typedDB = typedNested<Structure>({ db, schema, }); }); afterEach(async () => { if (typedDB) { await typedDB.drop(); await typedDB.close(); } }); it("wraps a nested store", async () => { expect(typedDB.type).to.equal("nested"); }); 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 Nested database - fixed properties", () => { type Structure = { a: number; b: { c: string } }; const schema: JSONSchemaType<RecursivePartial<Structure>> = { type: "object", properties: { a: { type: "number", nullable: true }, b: { type: "object", properties: { c: { type: "string", nullable: true }, }, required: [], nullable: true, additionalProperties: false, }, }, additionalProperties: false, required: [], }; let typedDB: TypedNested<Structure>; beforeEach(async () => { db = await Nested()({ ipfs, identity: testIdentity1, address: databaseId, }); typedDB = typedNested<Structure>({ db, schema, }); }); afterEach(async () => { if (typedDB) { await typedDB.drop(); await typedDB.close(); } }); it("put valid key/value", async () => { await typedDB.put("a", 1); const actual = await typedDB.all(); expect(actual).to.deep.equal({ a: 1 }); }); it("get valid key/value", async () => { await typedDB.put("a", 1); const actual = await typedDB.get("a"); expect(actual).to.equal(1); }); it("delete key", async () => { await typedDB.put("a", 1); await typedDB.del("a"); const actual = await typedDB.get("a"); expect(actual).to.be.undefined(); }); 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 put invalid value", async () => { await expect( // @ts-expect-error Deliberately adding invalid value typedDB.put("a", "text"), ).to.be.rejectedWith("must be number"); }); 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("undefined on get invalid value", async () => { // Force write invalid key to underlying orbit-db database await db.put("a", "wrong type"); const val = await typedDB.get("a"); expect(val).to.be.undefined(); }); it("error on delete invalid key", async () => { // @ts-expect-error Deliberately deleting invalid key await expect(typedDB.del("c")).to.be.rejectedWith("Unsupported key c."); }); it("put valid nested key/value", async () => { await typedDB.put("a", 1); await typedDB.put("b/c", "test"); const actual = await typedDB.all(); expect(actual).to.deep.equal({ a: 1, b: { c: "test" } }); }); it("get valid nested key/value", async () => { await typedDB.put("b/c", "test"); const actual = await typedDB.get("b"); expect(actual).to.deep.equal({ c: "test" }); }); it("delete nested key", async () => { await typedDB.put("b/c", "test"); await typedDB.del("b/c"); const actual = await typedDB.get("b/c"); expect(actual).to.be.undefined(); }); it("error on put invalid nested key", async () => { // @ts-expect-error Deliberately adding invalid key await expect(typedDB.put("b/d", 3)).to.be.rejectedWith( "Unsupported key b/d.", ); }); it("error on put invalid value in nested key", async () => { await expect( // @ts-expect-error Deliberately adding invalid value typedDB.put("b/c", 1), ).to.be.rejectedWith("must be string"); }); it("error on get invalid nested key", async () => { await expect( // @ts-expect-error Deliberately getting invalid key typedDB.get("b/c/d"), ).to.be.rejectedWith("Unsupported key b/c/d."); }); it("error on delete invalid nested key", async () => { // @ts-expect-error Deliberately deleting invalid key await expect(typedDB.del("a/b")).to.be.rejectedWith( "Unsupported key a/b.", ); }); it("insert nested valid", async () => { await typedDB.insert({ b: { c: "test" } }); const actual = await typedDB.all(); expect(actual).to.deep.equal({ b: { c: "test" } }); }); it("error on insert nested invalid key", async () => { // @ts-expect-error Deliberately adding invalid key await expect(typedDB.insert({ b: { d: 3 } })).to.be.rejectedWith( "Unsupported key b/d.", ); }); it("error on insert nested invalid value", async () => { await expect( // @ts-expect-error Deliberately adding invalid value typedDB.insert({ b: { c: 1 } }), ).to.be.rejectedWith("must be string"); }); it("insert valid with key", async () => { await typedDB.insert("b", { c: "test" }); const actual = await typedDB.all(); expect(actual).to.deep.equal({ b: { c: "test" } }); }); it("insert nested key/value - list key", async () => { await typedDB.insert(["b"], { c: "test" }); const actual = await typedDB.all(); expect(actual).to.deep.equal({ b: { c: "test" } }); }); it("get valid nested key/value - list key", async () => { await typedDB.put(["b", "c"], "test"); const actual = await typedDB.get(["b", "c"]); expect(actual).to.deep.equal("test"); }); it("delete nested list key", async () => { await typedDB.put(["b", "c"], "test"); await typedDB.del(["b", "c"]); const actual = await typedDB.get(["b", "c"]); expect(actual).to.be.undefined(); }); it("error on delete invalid nested list key", async () => { // @ts-expect-error Deliberately deleting invalid key await expect(typedDB.del(["a", "b"])).to.be.rejectedWith( "Unsupported key a/b.", ); }); it("error on insert with invalid key", async () => { // @ts-expect-error Deliberately adding invalid value await expect(typedDB.insert("b/d", 2)).to.be.rejectedWith( "Unsupported key b/d.", ); }); it("error on put invalid nested key - list key", async () => { // @ts-expect-error Deliberately adding invalid key await expect(typedDB.put(["b", "d"], 3)).to.be.rejectedWith( "Unsupported key b/d.", ); }); it("error on insert with key and invalid value", async () => { // @ts-expect-error Deliberately adding invalid value await expect(typedDB.insert("b", { c: 1 })).to.be.rejectedWith( "must be string", ); }); it("error on put invalid value in nested key - list key", async () => { await expect( // @ts-expect-error Deliberately adding invalid value typedDB.put(["b", "c"], 1), ).to.be.rejectedWith("must be string"); }); it("error on get invalid nested key - list key", async () => { await expect( // @ts-expect-error Deliberately getting invalid key typedDB.get(["b", "c", "d"]), ).to.be.rejectedWith("Unsupported key b/c/d."); }); it("invalid keys in log lead to error 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", "text"); await expect(typedDB.all()).to.be.rejectedWith( "must NOT have additional properties", ); }); it("invalid values in log lead to error on `db.all()`", async () => { // Force write invalid value to underlying orbit-db database await db.put("a", "text"); await typedDB.set("b/c", "text 2"); await expect(typedDB.all()).to.be.rejectedWith("must be number"); }); }); describe("Typed Nested database - additional properties", () => { type Structure = { a: string; b: { [key: string]: number } }; const schema: JSONSchemaType<RecursivePartial<Structure>> = { type: "object", properties: { a: { type: "string", nullable: true }, b: { type: "object", additionalProperties: { type: "number", }, required: [], nullable: true, }, }, required: [], }; let typedDB: TypedNested<Structure>; beforeEach(async () => { db = await Nested()({ ipfs, identity: testIdentity1, address: databaseId, }); typedDB = typedNested<Structure>({ db, schema, }); }); afterEach(async () => { if (typedDB) { await typedDB.drop(); await typedDB.close(); } }); it("add additional property key", async () => { const hash = await typedDB.put("b/d", 1); expect(hash).to.be.a("string"); }); it("get additional property key/value", async () => { await typedDB.put("b/d", 1); const actual = await typedDB.all(); expect(actual).to.deep.equal({ b: { d: 1 } }); }); it("error on add invalid additional property value", async () => { await expect( // @ts-expect-error Deliberately adding invalid value typedDB.put("b/d", "text"), ).to.be.rejectedWith("must be number"); }); it("add additional property key - list", async () => { const hash = await typedDB.put(["b", "d"], 1); expect(hash).to.be.a("string"); }); it("get additional property key/value - list", async () => { await typedDB.put(["b", "d"], 1); const actual = await typedDB.all(); expect(actual).to.deep.equal({ b: { d: 1 } }); }); it("error on add invalid additional property value - list", async () => { await expect( // @ts-expect-error Deliberately adding invalid value typedDB.put(["b", "d"], "text"), ).to.be.rejectedWith("must be number"); }); it("invalid values in log lead to error on `db.all()`", async () => { // Force write invalid value to underlying orbit-db database await db.put(["b", "d"], "text"); await typedDB.set("a", "text 2"); await expect(typedDB.all()).to.be.rejectedWith("must be number"); }); }); describe("Typed Nested database - additional structured properties", () => { type Structure = { a: string; b: { [key: string]: { c: number } } }; const schema: JSONSchemaType<RecursivePartial<Structure>> = { type: "object", properties: { a: { type: "string", nullable: true }, b: { type: "object", additionalProperties: { type: "object", properties: { c: { type: "number", nullable: true }, }, }, required: [], nullable: true, }, }, required: [], }; let typedDB: TypedNested<Structure>; beforeEach(async () => { db = await Nested()({ ipfs, identity: testIdentity1, address: databaseId, }); typedDB = typedNested<Structure>({ db, schema, }); }); afterEach(async () => { if (typedDB) { await typedDB.drop(); await typedDB.close(); } }); it("add additional property key", async () => { const hash = await typedDB.put("b/d", { c: 1 }); expect(hash).to.be.a("string"); }); it("get additional property key/value", async () => { await typedDB.put("b/d", { c: 1 }); const actual = await typedDB.all(); expect(actual).to.deep.equal({ b: { d: { c: 1 } } }); }); it("add additional property nested key/raw value", async () => { const hash = await typedDB.put("b/d/c", 1); expect(hash).to.be.a("string"); }); it("get additional property nested key/raw value", async () => { await typedDB.put("b/d", { c: 1 }); const actual = await typedDB.get("b/d/c"); expect(actual).to.equal(1); }); it("error on add invalid additional property value", async () => { await expect( // @ts-expect-error Deliberately adding invalid value typedDB.put("b/d", "text"), ).to.be.rejectedWith("must be object"); }); it("error on add invalid additional nested property value", async () => { await expect( // @ts-expect-error Deliberately adding invalid value typedDB.put("b/d", { c: "text" }), ).to.be.rejectedWith("must be number"); }); it("add additional property key - list", async () => { const hash = await typedDB.put(["b", "d"], { c: 1 }); expect(hash).to.be.a("string"); }); it("get additional property key/value - list", async () => { await typedDB.put(["b", "d"], { c: 1 }); const actual = await typedDB.all(); expect(actual).to.deep.equal({ b: { d: { c: 1 } } }); }); it("error on add invalid additional property value - list", async () => { await expect( // @ts-expect-error Deliberately adding invalid value typedDB.put(["b", "d"], "text"), ).to.be.rejectedWith("must be object"); }); it("error on add invalid additional nested property value - list", async () => { await expect( // @ts-expect-error Deliberately adding invalid value typedDB.put(["b", "d"], { c: "text" }), ).to.be.rejectedWith("must be number"); }); it("invalid values in log lead to error on `db.all()`", async () => { // Force write invalid value to underlying orbit-db database await db.put(["b", "d"], "text"); await typedDB.set("a", "text 2"); await expect(typedDB.all()).to.be.rejectedWith("must be object"); }); it("invalid nested values in log lead to error on `db.all()`", async () => { // Force write invalid value to underlying orbit-db database await db.put(["b", "d"], { c: "text" }); await typedDB.set("a", "text 2"); await expect(typedDB.all()).to.be.rejectedWith("must be number"); }); }); });