56e3253829
Rewrite createMemoryStore() to return an OcasStore with three sub-stores: cas, var, tag. The cas sub-store keeps the existing Map-based logic, but its put is now synchronous; the in-memory VarStore and TagStore are new Map-based implementations of the types defined in #39. The legacy Store.put signature is widened to Hash | Promise<Hash> so that the new sync cas can still be passed to helpers (bootstrap, gc, render, schema, …) that have not yet been migrated to the unified surface. Tests in packages/core were mechanically updated from store.* to store.cas.* and new test suites for VarStore (var-store.test.ts) and TagStore (tag-store.test.ts) cover the spec from issue #40. Closes #40 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
156 lines
6.3 KiB
TypeScript
156 lines
6.3 KiB
TypeScript
import { describe, expect, test } from "bun:test";
|
|
import { BOOTSTRAP_STORE } from "./bootstrap-capable.js";
|
|
import { createMemoryStore } from "./store.js";
|
|
|
|
describe("A. createMemoryStore – shape", () => {
|
|
test("A1. returns an object with cas, var, tag sub-stores", () => {
|
|
const store = createMemoryStore();
|
|
expect(typeof store.cas).toBe("object");
|
|
expect(store.cas).not.toBeNull();
|
|
expect(typeof store.var).toBe("object");
|
|
expect(store.var).not.toBeNull();
|
|
expect(typeof store.tag).toBe("object");
|
|
expect(store.tag).not.toBeNull();
|
|
});
|
|
|
|
test("A2. cas/var/tag are independent objects", () => {
|
|
const store = createMemoryStore();
|
|
expect(store.cas).not.toBe(store.var as unknown as typeof store.cas);
|
|
expect(store.cas).not.toBe(store.tag as unknown as typeof store.cas);
|
|
expect(store.var).not.toBe(store.tag as unknown as typeof store.var);
|
|
});
|
|
});
|
|
|
|
describe("B. cas sub-store – meta and schema indexes", () => {
|
|
test("B1. listMeta and listSchemas are empty on a fresh store", () => {
|
|
const store = createMemoryStore();
|
|
expect(store.cas.listMeta()).toEqual([]);
|
|
expect(store.cas.listSchemas()).toEqual([]);
|
|
});
|
|
|
|
test("B2. self-referencing put adds hash to metaSet and listSchemas", () => {
|
|
const store = createMemoryStore();
|
|
const hash = store.cas[BOOTSTRAP_STORE]({ type: "object" }) as string;
|
|
expect(store.cas.listMeta().map((e) => e.hash)).toContain(hash);
|
|
expect(store.cas.listSchemas().map((e) => e.hash)).toContain(hash);
|
|
});
|
|
|
|
test("B3. regular put does not add hash to metaSet", () => {
|
|
const store = createMemoryStore();
|
|
const metaHash = store.cas[BOOTSTRAP_STORE]({ type: "object" }) as string;
|
|
const schemaHash = store.cas.put(metaHash, { type: "string" });
|
|
expect(store.cas.listMeta().map((e) => e.hash)).not.toContain(schemaHash);
|
|
expect(store.cas.listMeta().map((e) => e.hash)).toContain(metaHash);
|
|
});
|
|
|
|
test("B4. schema typed by meta-schema appears in listSchemas", () => {
|
|
const store = createMemoryStore();
|
|
const m = store.cas[BOOTSTRAP_STORE]({ type: "object" }) as string;
|
|
const s = store.cas.put(m, { type: "string" });
|
|
const schemas = store.cas.listSchemas().map((e) => e.hash);
|
|
expect(schemas).toContain(m);
|
|
expect(schemas).toContain(s);
|
|
const meta = store.cas.listMeta().map((e) => e.hash);
|
|
expect(meta).toContain(m);
|
|
expect(meta).not.toContain(s);
|
|
});
|
|
|
|
test("B5. multiple meta-schemas (versioning)", () => {
|
|
const store = createMemoryStore();
|
|
const m1 = store.cas[BOOTSTRAP_STORE]({
|
|
type: "object",
|
|
title: "v1",
|
|
}) as string;
|
|
const m2 = store.cas[BOOTSTRAP_STORE]({
|
|
type: "object",
|
|
title: "v2",
|
|
}) as string;
|
|
const s1 = store.cas.put(m1, { type: "string" });
|
|
const s2 = store.cas.put(m2, { type: "number" });
|
|
const meta = store.cas.listMeta().map((e) => e.hash);
|
|
expect(meta).toContain(m1);
|
|
expect(meta).toContain(m2);
|
|
expect(meta).toHaveLength(2);
|
|
const schemas = store.cas.listSchemas().map((e) => e.hash);
|
|
expect(schemas).toContain(m1);
|
|
expect(schemas).toContain(m2);
|
|
expect(schemas).toContain(s1);
|
|
expect(schemas).toContain(s2);
|
|
});
|
|
|
|
test("B6. idempotent self-referencing put does not duplicate", () => {
|
|
const store = createMemoryStore();
|
|
const payload = { type: "object", title: "dup" };
|
|
const h1 = store.cas[BOOTSTRAP_STORE](payload) as string;
|
|
const h2 = store.cas[BOOTSTRAP_STORE](payload) as string;
|
|
expect(h1).toBe(h2);
|
|
const meta = store.cas.listMeta().map((e) => e.hash);
|
|
expect(meta.filter((h) => h === h1)).toHaveLength(1);
|
|
});
|
|
|
|
test("B7. delete removes hash from metaSet and listSchemas", () => {
|
|
const store = createMemoryStore();
|
|
const m = store.cas[BOOTSTRAP_STORE]({ type: "object" }) as string;
|
|
const s = store.cas.put(m, { type: "string" });
|
|
expect(store.cas.listMeta().map((e) => e.hash)).toContain(m);
|
|
expect(store.cas.listSchemas().map((e) => e.hash)).toContain(s);
|
|
store.cas.delete(m);
|
|
expect(store.cas.listMeta().map((e) => e.hash)).not.toContain(m);
|
|
expect(store.cas.listSchemas().map((e) => e.hash)).not.toContain(s);
|
|
expect(store.cas.listSchemas().map((e) => e.hash)).not.toContain(m);
|
|
});
|
|
|
|
test("B8. cas.put returns Hash synchronously (not a Promise)", () => {
|
|
const store = createMemoryStore();
|
|
const m = store.cas[BOOTSTRAP_STORE]({ type: "object" }) as string;
|
|
const result = store.cas.put(m, { type: "string" });
|
|
expect(typeof result).toBe("string");
|
|
expect(result).toHaveLength(13);
|
|
});
|
|
|
|
test("B9. has/get/delete reflect put state", () => {
|
|
const store = createMemoryStore();
|
|
const m = store.cas[BOOTSTRAP_STORE]({ type: "object" }) as string;
|
|
const h = store.cas.put(m, { type: "string" });
|
|
expect(store.cas.has(h)).toBe(true);
|
|
expect(store.cas.get(h)?.payload).toEqual({ type: "string" });
|
|
expect(store.cas.has("0000000000000")).toBe(false);
|
|
expect(store.cas.get("0000000000000")).toBeNull();
|
|
expect(store.cas.delete(h)).toBe(true);
|
|
expect(store.cas.delete(h)).toBe(false);
|
|
expect(store.cas.has(h)).toBe(false);
|
|
});
|
|
});
|
|
|
|
describe("E. cross-store independence", () => {
|
|
test("E1. tagging a hash does not surface it in cas.listByType", () => {
|
|
const store = createMemoryStore();
|
|
const fakeTarget = "0000000000000";
|
|
store.tag.tag(fakeTarget, [{ op: "set", key: "env", value: "prod" }]);
|
|
expect(store.cas.listByType(fakeTarget)).toEqual([]);
|
|
});
|
|
|
|
test("E2. setting a variable does not mutate the CAS node", () => {
|
|
const store = createMemoryStore();
|
|
const m = store.cas[BOOTSTRAP_STORE]({ type: "object" }) as string;
|
|
const s = store.cas.put(m, { type: "string" });
|
|
const value = store.cas.put(s, "hello");
|
|
const before = store.cas.get(value);
|
|
store.var.set("@app/x", value);
|
|
const after = store.cas.get(value);
|
|
expect(after).toEqual(before);
|
|
});
|
|
|
|
test("E3. cas.delete does not cascade-delete a variable referencing it", () => {
|
|
const store = createMemoryStore();
|
|
const m = store.cas[BOOTSTRAP_STORE]({ type: "object" }) as string;
|
|
const s = store.cas.put(m, { type: "string" });
|
|
const value = store.cas.put(s, "hello");
|
|
store.var.set("@app/x", value);
|
|
store.cas.delete(value);
|
|
const got = store.var.get("@app/x", s);
|
|
expect(got).not.toBeNull();
|
|
expect(got?.value).toBe(value);
|
|
});
|
|
});
|