import "fake-indexeddb/auto"; import { IndexedDBKvPrimitives } from "../data/indexeddb_kv_primitives.ts"; import { DenoKvPrimitives } from "../data/deno_kv_primitives.ts"; import type { KvPrimitives } from "../data/kv_primitives.ts"; import { assertEquals, assertThrows } from "@std/assert"; import { PrefixedKvPrimitives } from "../data/prefixed_kv_primitives.ts"; import type { Query } from "../../plug-api/types.ts"; import { DataStore } from "$lib/data/datastore.ts"; async function test(db: KvPrimitives) { const datastore = new DataStore(new PrefixedKvPrimitives(db, ["ds"]), { count: (arr: any[]) => arr.length, $query: (_query: Query) => { return Promise.resolve([]); }, }); await datastore.set(["user", "peter"], { name: "Peter" }); await datastore.set(["user", "hank"], { name: "Hank" }); let results = await datastore.query({ prefix: ["user"], filter: ["=", ["attr", "name"], ["string", "Peter"]], }); assertEquals(results, [{ key: ["user", "peter"], value: { name: "Peter" } }]); assertEquals( [{ key: ["user", "hank"], value: { name: "Hank" } }, { key: ["user", "peter"], value: { name: "Peter" }, }], await datastore.query({ prefix: ["user"], orderBy: [{ expr: ["attr", "name"], desc: false }], }), ); assertEquals( [{ key: ["user", "peter"], value: { name: "Peter" } }, { key: ["user", "hank"], value: { name: "Hank" }, }], await datastore.query({ prefix: ["user"], orderBy: [{ expr: ["attr", "name"], desc: true }], }), ); await datastore.batchSet([ { key: ["kv", "name"], value: "Zef" }, { key: ["kv", "data"], value: new Uint8Array([1, 2, 3]) }, { key: ["kv", "complicated"], value: { name: "Frank", parents: ["John", "Jane"], address: { street: "123 Main St", city: "San Francisco", }, }, }, ]); assertEquals(await datastore.get(["kv", "name"]), "Zef"); assertEquals(await datastore.get(["kv", "data"]), new Uint8Array([1, 2, 3])); results = await datastore.query({ prefix: ["kv"], filter: ["=~", ["attr", ""], ["regexp", "Z.f", "i"]], }); assertEquals(results, [{ key: ["kv", "name"], value: "Zef" }]); results = await datastore.query({ prefix: ["kv"], filter: ["and", ["=", ["attr", "parents"], ["string", "John"]], [ "=", ["attr", ["attr", "address"], "city"], ["string", "San Francisco"], ]], select: [ { name: "parents" }, { name: "name", expr: ["+", ["attr", "name"], ["string", "!"]], }, { name: "parentCount", expr: ["call", "count", [["attr", "parents"]]], }, ], }); assertEquals(results.length, 1); assertEquals(results[0], { key: ["kv", "complicated"], value: { name: "Frank!", parentCount: 2, parents: ["John", "Jane"] }, }); assertEquals( await datastore.query({ prefix: ["kv"], limit: ["number", 1], select: [ { name: "random", expr: ["query", { querySource: "bla" }], }, ], }), [{ key: ["kv", "complicated"], value: { random: [] } }], ); // Test object enrichment datastore.objectDecorators = [{ where: ["boolean", true], attributes: { pageDecoration: { prefix: ["string", "🧑 "], }, list: ["array", [["number", 2]]], }, }]; const simplePage = { name: "My Page", pageDecoration: { hide: true, }, list: [1], }; const enrichedPage = datastore.enrichObject(simplePage); assertEquals(enrichedPage.name, "My Page"); assertEquals(enrichedPage.pageDecoration.hide, true); assertEquals(enrichedPage.pageDecoration.prefix, "🧑 "); assertEquals(enrichedPage.list, [1, 2]); // console.log("Enriched page", enrichedPage); // More complicated case datastore.objectDecorators = [ { // fullName where: ["=", ["attr", "tags"], ["string", "person"]], attributes: { fullName: ["+", ["+", ["attr", "firstName"], ["string", " "]], [ "attr", "lastName", ]], }, }, { where: ["=", ["attr", "tags"], ["string", "person"]], attributes: { pageDecoration: { prefix: { bla: { doh: ["+", ["string", "🧑 "], ["attr", "fullName"]], }, }, }, }, }, // Test extending existing array attributes { where: ["boolean", true], attributes: { listAttribute: ["array", [["string", "newValue1"]]], nested: { listAttribute1: ["array", [["string", "newValue 1"]]], }, }, }, { where: ["boolean", true], attributes: { listAttribute: ["array", [["string", "newValue2"]]], nested: { listAttribute1: ["array", [["string", "newValue 2"]]], }, }, }, { where: ["boolean", true], attributes: { nested: { listAttribute1: ["array", [["string", "newValue 3"]]], }, }, }, { where: ["boolean", true], attributes: { nested: { deeper: { listAttribute2: ["array", [["string", "newValue 1"]]], }, }, }, }, { where: ["boolean", true], attributes: { nested: { deeper: { listAttribute2: ["array", [["string", "newValue 2"]]], }, }, }, }, // Test not being able to override existing attributes { where: ["boolean", true], attributes: { lastName: ["string", "Shouldn't be set"], }, }, { where: ["=", ["attr", "tags"], ["string", "person"]], attributes: { existingObjAttribute: { another: ["string", "value"], }, }, }, ]; let obj: Record = { firstName: "Pete", lastName: "Smith", pageDecoration: {}, existingObjAttribute: { something: true, }, tags: ["person"], }; const pristineCopy = JSON.parse(JSON.stringify(obj)); obj = datastore.enrichObject(obj); // console.log("Enrhiched", obj); assertEquals(obj.fullName, "Pete Smith"); assertEquals(obj.lastName, "Smith"); assertEquals(obj.pageDecoration.prefix.bla.doh, "🧑 Pete Smith"); assertEquals(obj.existingObjAttribute.something, true); assertEquals(obj.existingObjAttribute.another, "value"); assertEquals(obj.listAttribute, ["newValue1", "newValue2"]); assertEquals(obj.nested.listAttribute1, [ "newValue 1", "newValue 2", "newValue 3", ]); // And now let's clean it again datastore.cleanEnrichedObject(obj); assertEquals(obj, pristineCopy); // Validate no async functions are called in the object enrichment datastore.objectDecorators = [ { where: ["call", "$query", []], attributes: {}, }, ]; assertThrows(() => { datastore.enrichObject({}); }); } Deno.test("Test Deno KV DataStore", async () => { const tmpFile = await Deno.makeTempFile(); const db = new DenoKvPrimitives(await Deno.openKv(tmpFile)); await test(db); db.close(); await Deno.remove(tmpFile); }); Deno.test("Test IndexDB DataStore", { sanitizeResources: false, sanitizeOps: false, }, async () => { const db = new IndexedDBKvPrimitives("test"); await db.init(); await test(db); db.close(); });