diff --git a/packages/cli/src/index.ts b/packages/cli/src/index.ts index 2f0eb39..9e1dbcd 100644 --- a/packages/cli/src/index.ts +++ b/packages/cli/src/index.ts @@ -3,7 +3,7 @@ import { existsSync, readFileSync } from "node:fs"; import { homedir } from "node:os"; import { join, resolve } from "node:path"; -import type { Hash, ListOptions, OcasStore } from "@ocas/core"; +import type { Hash, ListOptions, Store } from "@ocas/core"; import { CasNodeNotFoundError, computeHash, @@ -108,7 +108,7 @@ const compact = flags.json === true; const inlineRender = flags.render === true || flags.r === true; -async function out(data: unknown, store?: OcasStore): Promise { +async function out(data: unknown, store?: Store): Promise { if ( inlineRender && typeof data === "object" && @@ -156,10 +156,10 @@ async function readStdinJson(): Promise { } /** - * Open the filesystem-backed OcasStore. Automatically creates directory and + * Open the filesystem-backed Store. Automatically creates directory and * bootstraps if needed. */ -async function openStore(): Promise { +async function openStore(): Promise { const fullPath = resolve(storePath); return await openFsStore(fullPath); } @@ -176,7 +176,7 @@ function isHash(input: string): boolean { * Otherwise, query the store's var sub-store for a variable with that exact * name and return the first match's value. */ -function resolveHash(input: string, store: OcasStore): Hash { +function resolveHash(input: string, store: Store): Hash { if (isHash(input)) { return input as Hash; } @@ -297,7 +297,7 @@ async function cmdPut(args: string[]): Promise { const metaHash = resolveHash("@ocas/schema", store); if (typeHash === metaHash) { try { - const hash = await putSchema(store, payload as Record); + const hash = putSchema(store, payload as Record); await out(await wrapEnvelope(store, "@ocas/output/put", hash), store); } catch (_e) { console.error( diff --git a/packages/cli/src/prompts/usage-doc.test.ts b/packages/cli/src/prompts/usage-doc.test.ts index 1c70ce1..3420b24 100644 --- a/packages/cli/src/prompts/usage-doc.test.ts +++ b/packages/cli/src/prompts/usage-doc.test.ts @@ -11,7 +11,7 @@ describe("usage.md doc cleanup (D)", () => { expect(content).not.toContain("createVariableStore"); }); - test("D1. usage.md references openStore returning OcasStore", () => { + test("D1. usage.md references openStore returning Store", () => { const content = readFileSync(usagePath, "utf8"); expect(content).toContain("openStore"); expect(content).toMatch(/store\.cas|store\.var|store\.tag/); diff --git a/packages/cli/tests/edge-cases.test.ts b/packages/cli/tests/edge-cases.test.ts index d41496a..b2c8beb 100644 --- a/packages/cli/tests/edge-cases.test.ts +++ b/packages/cli/tests/edge-cases.test.ts @@ -70,10 +70,7 @@ describe("Phase 7: Edge Cases", () => { const { openStore: openFsStore } = await import("@ocas/fs"); const { putSchema } = await import("@ocas/core"); const store = await openFsStore(tmpStore); - typeHash = await putSchema( - store, - JSON.parse(readFileSync(schemaFile, "utf-8")), - ); + typeHash = putSchema(store, JSON.parse(readFileSync(schemaFile, "utf-8"))); const nodeFile = join(tmpStore, "test-node.json"); writeFileSync(nodeFile, JSON.stringify({ name: "Alice", age: 30 })); @@ -178,10 +175,7 @@ describe("Phase 3: Variable System", () => { const { openStore: openFsStore } = await import("@ocas/fs"); const { putSchema } = await import("@ocas/core"); const store = await openFsStore(tmpStore); - typeHash = await putSchema( - store, - JSON.parse(readFileSync(schemaFile, "utf-8")), - ); + typeHash = putSchema(store, JSON.parse(readFileSync(schemaFile, "utf-8"))); const nodeFile = join(tmpStore, "test-node.json"); writeFileSync(nodeFile, JSON.stringify({ name: "Alice", age: 30 })); @@ -369,10 +363,7 @@ describe("Phase 4: Template System", () => { const { openStore: openFsStore } = await import("@ocas/fs"); const { putSchema } = await import("@ocas/core"); const store = await openFsStore(tmpStore); - typeHash = await putSchema( - store, - JSON.parse(readFileSync(schemaFile, "utf-8")), - ); + typeHash = putSchema(store, JSON.parse(readFileSync(schemaFile, "utf-8"))); }); afterAll(() => { diff --git a/packages/cli/tests/gc.test.ts b/packages/cli/tests/gc.test.ts index ec96ec3..cabaf5e 100644 --- a/packages/cli/tests/gc.test.ts +++ b/packages/cli/tests/gc.test.ts @@ -26,10 +26,7 @@ beforeAll(async () => { const { openStore: openFsStore } = await import("@ocas/fs"); const { putSchema } = await import("@ocas/core"); const store = await openFsStore(tmpStore); - typeHash = await putSchema( - store, - JSON.parse(readFileSync(schemaFile, "utf-8")), - ); + typeHash = putSchema(store, JSON.parse(readFileSync(schemaFile, "utf-8"))); const nodeFile = join(tmpStore, "test-node.json"); writeFileSync(nodeFile, JSON.stringify({ name: "Alice", age: 30 })); diff --git a/packages/cli/tests/helpers.ts b/packages/cli/tests/helpers.ts index b3c4bbc..b6fde67 100644 --- a/packages/cli/tests/helpers.ts +++ b/packages/cli/tests/helpers.ts @@ -42,7 +42,7 @@ export async function putSchemaFile( const schema = JSON.parse( readFileSync(schemaFilePath, "utf-8"), ) as JSONSchema; - const hash = await putSchema(store, schema); + const hash = putSchema(store, schema); return hash; } diff --git a/packages/cli/tests/pipe.test.ts b/packages/cli/tests/pipe.test.ts index ff7a263..ce2ef69 100644 --- a/packages/cli/tests/pipe.test.ts +++ b/packages/cli/tests/pipe.test.ts @@ -26,10 +26,7 @@ beforeAll(async () => { const { openStore: openFsStore } = await import("@ocas/fs"); const { putSchema } = await import("@ocas/core"); const store = await openFsStore(tmpStore); - typeHash = await putSchema( - store, - JSON.parse(readFileSync(schemaFile, "utf-8")), - ); + typeHash = putSchema(store, JSON.parse(readFileSync(schemaFile, "utf-8"))); const nodeFile = join(tmpStore, "test-node.json"); writeFileSync(nodeFile, JSON.stringify({ name: "Alice", age: 30 })); diff --git a/packages/cli/tests/put-get-has.test.ts b/packages/cli/tests/put-get-has.test.ts index 69db91c..fa3bd78 100644 --- a/packages/cli/tests/put-get-has.test.ts +++ b/packages/cli/tests/put-get-has.test.ts @@ -29,10 +29,7 @@ beforeAll(async () => { const { openStore: openFsStore } = await import("@ocas/fs"); const { putSchema } = await import("@ocas/core"); const store = await openFsStore(tmpStore); - typeHash = await putSchema( - store, - JSON.parse(readFileSync(schemaFile, "utf-8")), - ); + typeHash = putSchema(store, JSON.parse(readFileSync(schemaFile, "utf-8"))); const nodeFile = join(tmpStore, "test-node.json"); writeFileSync(nodeFile, JSON.stringify({ name: "Alice", age: 30 })); diff --git a/packages/cli/tests/render.test.ts b/packages/cli/tests/render.test.ts index bb1966a..8463980 100644 --- a/packages/cli/tests/render.test.ts +++ b/packages/cli/tests/render.test.ts @@ -103,10 +103,7 @@ describe("Phase 5: Render", () => { const { openStore: openFsStore } = await import("@ocas/fs"); const { putSchema } = await import("@ocas/core"); const store = await openFsStore(tmpStore); - typeHash = await putSchema( - store, - JSON.parse(readFileSync(schemaFile, "utf-8")), - ); + typeHash = putSchema(store, JSON.parse(readFileSync(schemaFile, "utf-8"))); const nodeFile = join(tmpStore, "test-node.json"); writeFileSync(nodeFile, JSON.stringify({ name: "Alice", age: 30 })); @@ -422,7 +419,7 @@ describe("Suite 6: CLI Integration with Templates", () => { // Get @ocas/string type hash via bootstrap const store = await openFsStore(tmpStore); - const types = await bootstrap(store); + const types = bootstrap(store); const stringType = types["@ocas/string"]; // Create and store a simple string node @@ -457,7 +454,7 @@ describe("Suite 6: CLI Integration with Templates", () => { // Get @ocas/string type hash via bootstrap const store = await openFsStore(tmpStore); - const types = await bootstrap(store); + const types = bootstrap(store); const stringType = types["@ocas/string"]; // Create envelope and pipe to render diff --git a/packages/cli/tests/schema-validation.test.ts b/packages/cli/tests/schema-validation.test.ts index ac11e8e..caa2e3a 100644 --- a/packages/cli/tests/schema-validation.test.ts +++ b/packages/cli/tests/schema-validation.test.ts @@ -577,10 +577,7 @@ describe("Phase 2: Schema Validation", () => { const { openStore: openFsStore } = await import("@ocas/fs"); const { putSchema } = await import("@ocas/core"); const store = await openFsStore(tmpStore); - typeHash = await putSchema( - store, - JSON.parse(readFileSync(schemaFile, "utf-8")), - ); + typeHash = putSchema(store, JSON.parse(readFileSync(schemaFile, "utf-8"))); const nodeFile = join(tmpStore, "test-node.json"); writeFileSync(nodeFile, JSON.stringify({ name: "Alice", age: 30 })); diff --git a/packages/cli/tests/template.test.ts b/packages/cli/tests/template.test.ts index 310306e..d0f0be3 100644 --- a/packages/cli/tests/template.test.ts +++ b/packages/cli/tests/template.test.ts @@ -2,7 +2,7 @@ import { afterEach, beforeEach, describe, expect, test } from "bun:test"; import { mkdirSync, rmSync, writeFileSync } from "node:fs"; import { tmpdir } from "node:os"; import { join } from "node:path"; -import type { Hash, OcasStore } from "@ocas/core"; +import type { Hash, Store } from "@ocas/core"; import { bootstrap } from "@ocas/core"; import { openStore as openFsStore } from "@ocas/fs"; @@ -67,8 +67,8 @@ async function runCli(...args: string[]): Promise<{ /** * Get bootstrap @ocas/string type hash */ -async function getStringHash(store: OcasStore): Promise { - const builtinSchemas = await bootstrap(store); +async function getStringHash(store: Store): Promise { + const builtinSchemas = bootstrap(store); return builtinSchemas["@ocas/string"] ?? ""; } diff --git a/packages/cli/tests/variable-history.test.ts b/packages/cli/tests/variable-history.test.ts index bb5661a..771a430 100644 --- a/packages/cli/tests/variable-history.test.ts +++ b/packages/cli/tests/variable-history.test.ts @@ -2,7 +2,7 @@ import { afterEach, beforeEach, describe, expect, test } from "bun:test"; import { mkdirSync, rmSync } from "node:fs"; import { tmpdir } from "node:os"; import { join } from "node:path"; -import type { Hash, OcasStore } from "@ocas/core"; +import type { Hash, Store } from "@ocas/core"; import { bootstrap } from "@ocas/core"; import { openStore as openFsStore } from "@ocas/fs"; @@ -61,8 +61,8 @@ async function setupSchemaAndValues(): Promise<{ schema: Hash; values: Hash[]; }> { - const store: OcasStore = await openFsStore(storePath); - const aliases = await bootstrap(store); + const store: Store = await openFsStore(storePath); + const aliases = bootstrap(store); const numberHash = aliases["@ocas/number"] as Hash; const values: Hash[] = []; for (let i = 0; i < 4; i++) { diff --git a/packages/cli/tests/variable.test.ts b/packages/cli/tests/variable.test.ts index 8cc8137..4692ed9 100644 --- a/packages/cli/tests/variable.test.ts +++ b/packages/cli/tests/variable.test.ts @@ -2,7 +2,7 @@ import { afterEach, beforeEach, describe, expect, test } from "bun:test"; import { mkdirSync, rmSync } from "node:fs"; import { tmpdir } from "node:os"; import { join } from "node:path"; -import type { Hash, OcasStore } from "@ocas/core"; +import type { Hash, Store } from "@ocas/core"; import { bootstrap, putSchema } from "@ocas/core"; import { openStore as openFsStore } from "@ocas/fs"; @@ -68,7 +68,7 @@ async function runCli(...args: string[]): Promise<{ * Create a test CAS node and return its hash */ async function createTestNode( - store: OcasStore, + store: Store, typeHash: Hash, payload: unknown, ): Promise { @@ -78,8 +78,8 @@ async function createTestNode( /** * Get bootstrap type hash */ -async function getBootstrapHash(store: OcasStore): Promise { - const builtinSchemas = await bootstrap(store); +async function getBootstrapHash(store: Store): Promise { + const builtinSchemas = bootstrap(store); return builtinSchemas["@ocas/schema"] ?? ""; } @@ -189,7 +189,7 @@ describe("var set", () => { test("create variant with different schema", async () => { const store = await openFsStore(storePath); const typeHash1 = await getBootstrapHash(store); - const typeHash2 = await putSchema(store, { title: "Test", type: "object" }); + const typeHash2 = putSchema(store, { title: "Test", type: "object" }); const hash1 = await createTestNode(store, typeHash1, { test: "data1" }); const hash2 = await createTestNode(store, typeHash2, { test: "data2" }); @@ -354,7 +354,7 @@ describe("var get", () => { test("distinguish variants by schema", async () => { const store = await openFsStore(storePath); const typeHash1 = await getBootstrapHash(store); - const typeHash2 = await putSchema(store, { title: "Test", type: "object" }); + const typeHash2 = putSchema(store, { title: "Test", type: "object" }); const hash1 = await createTestNode(store, typeHash1, { test: "data1" }); const hash2 = await createTestNode(store, typeHash2, { test: "data2" }); @@ -392,7 +392,7 @@ describe("var delete", () => { test("remove all schema variants", async () => { const store = await openFsStore(storePath); const typeHash1 = await getBootstrapHash(store); - const typeHash2 = await putSchema(store, { title: "Test", type: "object" }); + const typeHash2 = putSchema(store, { title: "Test", type: "object" }); const hash1 = await createTestNode(store, typeHash1, { test: "data1" }); const hash2 = await createTestNode(store, typeHash2, { test: "data2" }); @@ -432,7 +432,7 @@ describe("var delete", () => { test("remove specific variant by schema", async () => { const store = await openFsStore(storePath); const typeHash1 = await getBootstrapHash(store); - const typeHash2 = await putSchema(store, { title: "Test", type: "object" }); + const typeHash2 = putSchema(store, { title: "Test", type: "object" }); const hash1 = await createTestNode(store, typeHash1, { test: "data1" }); const hash2 = await createTestNode(store, typeHash2, { test: "data2" }); @@ -579,11 +579,11 @@ describe("var list", () => { test("filter by schema", async () => { const store = await openFsStore(storePath); const bootstrapHash = await getBootstrapHash(store); - const typeHash1 = await putSchema(store, { + const typeHash1 = putSchema(store, { title: "TypeA", type: "object", }); - const typeHash2 = await putSchema(store, { + const typeHash2 = putSchema(store, { title: "TypeB", type: "object", }); diff --git a/packages/cli/tests/verify-refs-walk.test.ts b/packages/cli/tests/verify-refs-walk.test.ts index bda285f..388f447 100644 --- a/packages/cli/tests/verify-refs-walk.test.ts +++ b/packages/cli/tests/verify-refs-walk.test.ts @@ -26,10 +26,7 @@ beforeAll(async () => { const { openStore: openFsStore } = await import("@ocas/fs"); const { putSchema } = await import("@ocas/core"); const store = await openFsStore(tmpStore); - typeHash = await putSchema( - store, - JSON.parse(readFileSync(schemaFile, "utf-8")), - ); + typeHash = putSchema(store, JSON.parse(readFileSync(schemaFile, "utf-8"))); const nodeFile = join(tmpStore, "test-node.json"); writeFileSync(nodeFile, JSON.stringify({ name: "Alice", age: 30 })); diff --git a/packages/core/src/bootstrap-capable.ts b/packages/core/src/bootstrap-capable.ts index 0b09e02..70ca500 100644 --- a/packages/core/src/bootstrap-capable.ts +++ b/packages/core/src/bootstrap-capable.ts @@ -1,14 +1,14 @@ -import type { Hash, Store } from "./types.js"; +import type { CasStore, Hash } from "./types.js"; /** @internal Store implementations attach this for bootstrap() only. */ export const BOOTSTRAP_STORE = Symbol.for("@ocas/core/bootstrap-store"); -export type BootstrapCapableStore = Store & { - [BOOTSTRAP_STORE](payload: unknown): Hash | Promise; +export type BootstrapCapableStore = CasStore & { + [BOOTSTRAP_STORE](payload: unknown): Hash; }; export function isBootstrapCapableStore( - store: Store, + store: CasStore, ): store is BootstrapCapableStore { return ( typeof (store as BootstrapCapableStore)[BOOTSTRAP_STORE] === "function" diff --git a/packages/core/src/bootstrap.test.ts b/packages/core/src/bootstrap.test.ts index 5232450..8ed7cf0 100644 --- a/packages/core/src/bootstrap.test.ts +++ b/packages/core/src/bootstrap.test.ts @@ -35,7 +35,7 @@ const OUTPUT_ALIASES = [ describe("bootstrap - Built-in Schemas", () => { test("should return map of 30 built-in schema aliases to hashes", async () => { const store = createMemoryStore(); - const builtinSchemas = await bootstrap(store); + const builtinSchemas = bootstrap(store); // Should return object with 9 primitive + 21 output aliases = 30 expect(builtinSchemas).toHaveProperty("@ocas/schema"); @@ -63,7 +63,7 @@ describe("bootstrap - Built-in Schemas", () => { test("should register @ocas/schema as meta-schema alias", async () => { const store = createMemoryStore(); - const builtinSchemas = await bootstrap(store); + const builtinSchemas = bootstrap(store); const metaHash = builtinSchemas["@ocas/schema"]; if (!metaHash) throw new Error("@ocas/schema not found"); @@ -76,7 +76,7 @@ describe("bootstrap - Built-in Schemas", () => { test("should register @ocas/string schema correctly", async () => { const store = createMemoryStore(); - const builtinSchemas = await bootstrap(store); + const builtinSchemas = bootstrap(store); const stringHash = builtinSchemas["@ocas/string"]; if (!stringHash) throw new Error("@ocas/string not found"); @@ -87,7 +87,7 @@ describe("bootstrap - Built-in Schemas", () => { test("should register @ocas/number schema correctly", async () => { const store = createMemoryStore(); - const builtinSchemas = await bootstrap(store); + const builtinSchemas = bootstrap(store); const numberHash = builtinSchemas["@ocas/number"]; if (!numberHash) throw new Error("@ocas/number not found"); @@ -98,7 +98,7 @@ describe("bootstrap - Built-in Schemas", () => { test("should register @ocas/object schema correctly", async () => { const store = createMemoryStore(); - const builtinSchemas = await bootstrap(store); + const builtinSchemas = bootstrap(store); const objectHash = builtinSchemas["@ocas/object"]; if (!objectHash) throw new Error("@ocas/object not found"); @@ -109,7 +109,7 @@ describe("bootstrap - Built-in Schemas", () => { test("should register @ocas/array schema correctly", async () => { const store = createMemoryStore(); - const builtinSchemas = await bootstrap(store); + const builtinSchemas = bootstrap(store); const arrayHash = builtinSchemas["@ocas/array"]; if (!arrayHash) throw new Error("@ocas/array not found"); @@ -120,7 +120,7 @@ describe("bootstrap - Built-in Schemas", () => { test("should register @ocas/bool schema correctly", async () => { const store = createMemoryStore(); - const builtinSchemas = await bootstrap(store); + const builtinSchemas = bootstrap(store); const boolHash = builtinSchemas["@ocas/bool"]; if (!boolHash) throw new Error("@ocas/bool not found"); @@ -131,8 +131,8 @@ describe("bootstrap - Built-in Schemas", () => { test("should return same hashes on repeated bootstrap calls", async () => { const store = createMemoryStore(); - const first = await bootstrap(store); - const second = await bootstrap(store); + const first = bootstrap(store); + const second = bootstrap(store); expect(first).toEqual(second); @@ -147,7 +147,7 @@ describe("bootstrap - Built-in Schemas", () => { test("all built-in schemas should be typed by meta-schema", async () => { const store = createMemoryStore(); - const builtinSchemas = await bootstrap(store); + const builtinSchemas = bootstrap(store); const metaHash = builtinSchemas["@ocas/schema"]; if (!metaHash) throw new Error("@ocas/schema not found"); @@ -169,7 +169,7 @@ describe("bootstrap - Built-in Schemas", () => { describe("bootstrap - @ocas/output/* Schemas", () => { test("each @ocas/output/* schema has a title", async () => { const store = createMemoryStore(); - const aliases = await bootstrap(store); + const aliases = bootstrap(store); for (const alias of OUTPUT_ALIASES) { const hash = aliases[alias]; @@ -184,7 +184,7 @@ describe("bootstrap - @ocas/output/* Schemas", () => { test("@ocas/output/put schema describes a ocas_ref string", async () => { const store = createMemoryStore(); - const aliases = await bootstrap(store); + const aliases = bootstrap(store); const hash = aliases["@ocas/output/put"]; if (!hash) throw new Error("@ocas/output/put not found"); @@ -198,7 +198,7 @@ describe("bootstrap - @ocas/output/* Schemas", () => { test("@ocas/output/get schema describes object with type, payload, timestamp", async () => { const store = createMemoryStore(); - const aliases = await bootstrap(store); + const aliases = bootstrap(store); const hash = aliases["@ocas/output/get"]; if (!hash) throw new Error("@ocas/output/get not found"); @@ -214,7 +214,7 @@ describe("bootstrap - @ocas/output/* Schemas", () => { test("@ocas/output/has schema describes a boolean", async () => { const store = createMemoryStore(); - const aliases = await bootstrap(store); + const aliases = bootstrap(store); const hash = aliases["@ocas/output/has"]; if (!hash) throw new Error("@ocas/output/has not found"); @@ -226,7 +226,7 @@ describe("bootstrap - @ocas/output/* Schemas", () => { test("@ocas/output/verify schema describes enum of ok|corrupted|invalid", async () => { const store = createMemoryStore(); - const aliases = await bootstrap(store); + const aliases = bootstrap(store); const hash = aliases["@ocas/output/verify"]; if (!hash) throw new Error("@ocas/output/verify not found"); @@ -240,7 +240,7 @@ describe("bootstrap - @ocas/output/* Schemas", () => { test("@ocas/output/refs schema describes array of ocas_ref strings", async () => { const store = createMemoryStore(); - const aliases = await bootstrap(store); + const aliases = bootstrap(store); const hash = aliases["@ocas/output/refs"]; if (!hash) throw new Error("@ocas/output/refs not found"); @@ -253,7 +253,7 @@ describe("bootstrap - @ocas/output/* Schemas", () => { test("@ocas/output/gc schema describes object with gc stats fields", async () => { const store = createMemoryStore(); - const aliases = await bootstrap(store); + const aliases = bootstrap(store); const hash = aliases["@ocas/output/gc"]; if (!hash) throw new Error("@ocas/output/gc not found"); @@ -270,7 +270,7 @@ describe("bootstrap - @ocas/output/* Schemas", () => { test("@ocas/output/var-set schema describes a Variable object", async () => { const store = createMemoryStore(); - const aliases = await bootstrap(store); + const aliases = bootstrap(store); const hash = aliases["@ocas/output/var-set"]; if (!hash) throw new Error("@ocas/output/var-set not found"); @@ -286,7 +286,7 @@ describe("bootstrap - @ocas/output/* Schemas", () => { test("@ocas/output/var-list schema describes array of Variable objects", async () => { const store = createMemoryStore(); - const aliases = await bootstrap(store); + const aliases = bootstrap(store); const hash = aliases["@ocas/output/var-list"]; if (!hash) throw new Error("@ocas/output/var-list not found"); @@ -302,7 +302,7 @@ describe("bootstrap - @ocas/output/* Schemas", () => { test("@ocas/output/template-delete schema describes object with deleted boolean", async () => { const store = createMemoryStore(); - const aliases = await bootstrap(store); + const aliases = bootstrap(store); const hash = aliases["@ocas/output/template-delete"]; if (!hash) throw new Error("@ocas/output/template-delete not found"); @@ -315,7 +315,7 @@ describe("bootstrap - @ocas/output/* Schemas", () => { test("all @ocas/output/* schemas are distinct hashes", async () => { const store = createMemoryStore(); - const aliases = await bootstrap(store); + const aliases = bootstrap(store); const outputHashes = OUTPUT_ALIASES.map((alias) => aliases[alias]); const uniqueHashes = new Set(outputHashes); @@ -326,7 +326,7 @@ describe("bootstrap - @ocas/output/* Schemas", () => { describe("bootstrap - meta and schemas indexes (D1)", () => { test("listMeta contains the bootstrap meta-schema hash", async () => { const store = createMemoryStore(); - const aliases = await bootstrap(store); + const aliases = bootstrap(store); const metaHash = aliases["@ocas/schema"]; expect(store.cas.listMeta().map((e) => e.hash)).toContain( metaHash as string, @@ -335,7 +335,7 @@ describe("bootstrap - meta and schemas indexes (D1)", () => { test("listSchemas contains meta-schema and all built-in schemas", async () => { const store = createMemoryStore(); - const aliases = await bootstrap(store); + const aliases = bootstrap(store); const schemas = store.cas.listSchemas().map((e) => e.hash); for (const [, hash] of Object.entries(aliases)) { diff --git a/packages/core/src/bootstrap.ts b/packages/core/src/bootstrap.ts index 8332468..e396e3e 100644 --- a/packages/core/src/bootstrap.ts +++ b/packages/core/src/bootstrap.ts @@ -2,7 +2,7 @@ import { BOOTSTRAP_STORE, isBootstrapCapableStore, } from "./bootstrap-capable.js"; -import type { Hash, OcasStore } from "./types.js"; +import type { Hash, Store } from "./types.js"; const JSON_SCHEMA_TYPES = [ "string", @@ -328,25 +328,23 @@ const OUTPUT_SCHEMAS: ReadonlyArray< * All aliases are written to `store.var` via `var.set(name, hash)`, bypassing * @ocas/ namespace protection (protection is enforced only at the CLI layer). */ -export async function bootstrap( - store: OcasStore, -): Promise> { +export function bootstrap(store: Store): Record { const cas = store.cas; if (!isBootstrapCapableStore(cas)) { throw new Error("Store does not support bootstrap"); } // 1. Bootstrap the meta-schema (self-referential) - const metaHash = await cas[BOOTSTRAP_STORE](BOOTSTRAP_PAYLOAD); + const metaHash = cas[BOOTSTRAP_STORE](BOOTSTRAP_PAYLOAD); // 2. Register built-in primitive schemas directly (without putSchema to avoid recursion) - const stringHash = await cas.put(metaHash, { type: "string" }); - const numberHash = await cas.put(metaHash, { type: "number" }); - const integerHash = await cas.put(metaHash, { type: "integer" }); - const boolHash = await cas.put(metaHash, { type: "boolean" }); - const objectHash = await cas.put(metaHash, { type: "object" }); - const arrayHash = await cas.put(metaHash, { type: "array" }); - const nullHash = await cas.put(metaHash, { type: "null" }); + const stringHash = cas.put(metaHash, { type: "string" }); + const numberHash = cas.put(metaHash, { type: "number" }); + const integerHash = cas.put(metaHash, { type: "integer" }); + const boolHash = cas.put(metaHash, { type: "boolean" }); + const objectHash = cas.put(metaHash, { type: "object" }); + const arrayHash = cas.put(metaHash, { type: "array" }); + const nullHash = cas.put(metaHash, { type: "null" }); // 3. Register @ocas/output/* schemas const aliases: Record = { @@ -362,7 +360,7 @@ export async function bootstrap( }; for (const [alias, schema] of OUTPUT_SCHEMAS) { - aliases[alias] = await cas.put(metaHash, schema); + aliases[alias] = cas.put(metaHash, schema); } // 4. Write all aliases to the var store. Idempotent: VarStore.set is an diff --git a/packages/core/src/gc.test.ts b/packages/core/src/gc.test.ts index 848db0e..71174ef 100644 --- a/packages/core/src/gc.test.ts +++ b/packages/core/src/gc.test.ts @@ -7,9 +7,9 @@ import { createMemoryStore } from "./store.js"; describe("GC - Variable Model Refactoring", () => { test("GC preserves variable-referenced nodes", async () => { const store = createMemoryStore(); - await bootstrap(store); + bootstrap(store); const schema = { type: "object", properties: { name: { type: "string" } } }; - const schemaHash = await putSchema(store, schema); + const schemaHash = putSchema(store, schema); const hashRef = store.cas.put(schemaHash, { name: "referenced" }); const hashOrphan = store.cas.put(schemaHash, { name: "orphan" }); @@ -26,11 +26,11 @@ describe("GC - Variable Model Refactoring", () => { test("GC preserves nodes from variables with same name, different schemas", async () => { const store = createMemoryStore(); - await bootstrap(store); + bootstrap(store); const schemaA = { type: "object", properties: { x: { type: "number" } } }; const schemaB = { type: "object", properties: { y: { type: "string" } } }; - const schemaAHash = await putSchema(store, schemaA); - const schemaBHash = await putSchema(store, schemaB); + const schemaAHash = putSchema(store, schemaA); + const schemaBHash = putSchema(store, schemaB); const hashA = store.cas.put(schemaAHash, { x: 42 }); const hashB = store.cas.put(schemaBHash, { y: "hello" }); @@ -48,9 +48,9 @@ describe("GC - Variable Model Refactoring", () => { test("GC removes nodes after variable deletion", async () => { const store = createMemoryStore(); - await bootstrap(store); + bootstrap(store); const schema = { type: "object", properties: { name: { type: "string" } } }; - const schemaHash = await putSchema(store, schema); + const schemaHash = putSchema(store, schema); const hashRef = store.cas.put(schemaHash, { name: "referenced" }); @@ -64,11 +64,11 @@ describe("GC - Variable Model Refactoring", () => { test("GC is global across all variables", async () => { const store = createMemoryStore(); - await bootstrap(store); + bootstrap(store); const schemaA = { type: "object", properties: { x: { type: "number" } } }; const schemaB = { type: "object", properties: { y: { type: "string" } } }; - const schemaAHash = await putSchema(store, schemaA); - const schemaBHash = await putSchema(store, schemaB); + const schemaAHash = putSchema(store, schemaA); + const schemaBHash = putSchema(store, schemaB); const hash1 = store.cas.put(schemaAHash, { x: 1 }); const hash2 = store.cas.put(schemaAHash, { x: 2 }); @@ -89,12 +89,12 @@ describe("GC - Variable Model Refactoring", () => { test("GC integration with refactored variable store", async () => { const store = createMemoryStore(); - await bootstrap(store); + bootstrap(store); const schemaA = { type: "object", properties: { x: { type: "number" } } }; const schemaB = { type: "object", properties: { y: { type: "string" } } }; - const schemaAHash = await putSchema(store, schemaA); - const schemaBHash = await putSchema(store, schemaB); + const schemaAHash = putSchema(store, schemaA); + const schemaBHash = putSchema(store, schemaB); const hashA1 = store.cas.put(schemaAHash, { x: 1 }); const hashA2 = store.cas.put(schemaAHash, { x: 2 }); diff --git a/packages/core/src/gc.ts b/packages/core/src/gc.ts index 5f74185..18e6abe 100644 --- a/packages/core/src/gc.ts +++ b/packages/core/src/gc.ts @@ -1,5 +1,5 @@ import { walk } from "./schema.js"; -import type { Hash, OcasStore } from "./types.js"; +import type { Hash, Store } from "./types.js"; export interface GcStats { total: number; // Total CAS nodes before GC @@ -15,7 +15,7 @@ export interface GcStats { * - Sweep: delete unmarked nodes * - Schema preservation: schemas of reachable nodes are also marked */ -export function gc(store: OcasStore): GcStats { +export function gc(store: Store): GcStats { // Get all variables (no filters → global). Omit `limit` so the full // variable set is returned for use as gc roots. const variables = store.var.list(); diff --git a/packages/core/src/index.test.ts b/packages/core/src/index.test.ts index 373bc98..b2b116c 100644 --- a/packages/core/src/index.test.ts +++ b/packages/core/src/index.test.ts @@ -4,7 +4,7 @@ import { bootstrap } from "./bootstrap.js"; import { cborEncode } from "./cbor.js"; import { computeHash, computeSelfHash } from "./hash.js"; import { createMemoryStore } from "./store.js"; -import type { CasNode, Store } from "./types.js"; +import type { CasNode, CasStore } from "./types.js"; import { verify } from "./verify.js"; // ────────────────────────────────────────────────────────────────────────────── @@ -197,7 +197,7 @@ describe("createMemoryStore – listByType", () => { test("bootstrap node is listed under its self type", async () => { const store = createMemoryStore(); - const builtinSchemas = await bootstrap(store); + const builtinSchemas = bootstrap(store); const hash = builtinSchemas["@ocas/schema"] ?? ""; // All built-in schemas should be typed by the meta-schema @@ -253,25 +253,25 @@ describe("verify", () => { // ────────────────────────────────────────────────────────────────────────────── describe("bootstrap", () => { test("throws when store lacks internal bootstrap path", async () => { - const cas: Store = { - put: async () => "0000000000000", + const cas = { + put: () => "0000000000000", get: () => null, has: () => false, listByType: () => [], - }; + } as unknown as CasStore; const fakeStore = { cas, var: { set: () => null } as never, tag: {} as never, } as never; - await expect(bootstrap(fakeStore)).rejects.toThrow( + expect(() => bootstrap(fakeStore)).toThrow( "Store does not support bootstrap", ); }); test("returns a map with 30 built-in schema aliases", async () => { const store = createMemoryStore(); - const builtinSchemas = await bootstrap(store); + const builtinSchemas = bootstrap(store); expect(builtinSchemas).toHaveProperty("@ocas/schema"); expect(builtinSchemas).toHaveProperty("@ocas/string"); @@ -294,7 +294,7 @@ describe("bootstrap", () => { test("meta-schema node is stored and retrievable", async () => { const store = createMemoryStore(); - const builtinSchemas = await bootstrap(store); + const builtinSchemas = bootstrap(store); const metaHash = builtinSchemas["@ocas/schema"] ?? ""; expect(store.cas.has(metaHash)).toBe(true); @@ -304,7 +304,7 @@ describe("bootstrap", () => { test("meta-schema node is self-referencing: type === hash", async () => { const store = createMemoryStore(); - const builtinSchemas = await bootstrap(store); + const builtinSchemas = bootstrap(store); const metaHash = builtinSchemas["@ocas/schema"] ?? ""; const node = store.cas.get(metaHash) as CasNode; @@ -313,7 +313,7 @@ describe("bootstrap", () => { test("bootstrap node passes verify()", async () => { const store = createMemoryStore(); - const builtinSchemas = await bootstrap(store); + const builtinSchemas = bootstrap(store); const metaHash = builtinSchemas["@ocas/schema"] ?? ""; const node = store.cas.get(metaHash) as CasNode; @@ -322,8 +322,8 @@ describe("bootstrap", () => { test("bootstrap is idempotent: same hashes on repeated calls", async () => { const store = createMemoryStore(); - const h1 = await bootstrap(store); - const h2 = await bootstrap(store); + const h1 = bootstrap(store); + const h2 = bootstrap(store); expect(h1).toEqual(h2); // All built-in schemas typed by the meta-schema (1 self + 7 unique primitives + 21 outputs) diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index 72c406c..e5b9693 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -50,7 +50,6 @@ export type { ListEntry, ListOptions, ListSort, - OcasStore, Store, Tag, TagOp, @@ -60,6 +59,16 @@ export type { VarStore, } from "./types.js"; export { validateName } from "./validation.js"; +export { + addNameIndex, + checkTagLabelConflict, + cloneVarRecord, + extractSchema, + pushHistory, + removeNameIndex, + type VarRecord, + varKey, +} from "./var-store-helpers.js"; export type { Variable } from "./variable.js"; export { verify } from "./verify.js"; export { wrapEnvelope } from "./wrap-envelope.js"; diff --git a/packages/core/src/liquid-render.test.ts b/packages/core/src/liquid-render.test.ts index 06927fa..3eba60f 100644 --- a/packages/core/src/liquid-render.test.ts +++ b/packages/core/src/liquid-render.test.ts @@ -5,10 +5,10 @@ import { putSchema } from "./schema.js"; import { createMemoryStore } from "./store.js"; import type { Hash } from "./types.js"; -// Helper to create an in-memory OcasStore with bootstrap +// Helper to create an in-memory Store with bootstrap async function createTempVarStore() { const store = createMemoryStore(); - await bootstrap(store); + bootstrap(store); return { store, cleanup: async () => {}, @@ -55,7 +55,7 @@ describe("Suite 2: Custom {% render %} Tag Implementation", () => { const { store, cleanup } = await createTempVarStore(); try { - const childSchema = await putSchema(store, { + const childSchema = putSchema(store, { type: "object", properties: { value: { type: "string" }, @@ -65,7 +65,7 @@ describe("Suite 2: Custom {% render %} Tag Implementation", () => { value: "child content", }); - const parentSchema = await putSchema(store, { + const parentSchema = putSchema(store, { type: "object", properties: { name: { type: "string" }, @@ -78,7 +78,7 @@ describe("Suite 2: Custom {% render %} Tag Implementation", () => { }); // Register template for parent - const templateSchema = await putSchema(store, { type: "string" }); + const templateSchema = putSchema(store, { type: "string" }); const templateHash = store.cas.put( templateSchema, "Parent: {{ payload.name }}\n{% render payload.child %}", @@ -109,7 +109,7 @@ describe("Suite 2: Custom {% render %} Tag Implementation", () => { const { store, cleanup } = await createTempVarStore(); try { - const nodeSchema = await putSchema(store, { + const nodeSchema = putSchema(store, { type: "object", properties: { level: { type: "number" }, @@ -130,7 +130,7 @@ describe("Suite 2: Custom {% render %} Tag Implementation", () => { child: level1Hash, }); - const templateSchema = await putSchema(store, { type: "string" }); + const templateSchema = putSchema(store, { type: "string" }); // Template that shows the level and renders child with explicit decay const template = store.cas.put( @@ -158,7 +158,7 @@ describe("Suite 2: Custom {% render %} Tag Implementation", () => { const { store, cleanup } = await createTempVarStore(); try { - const childSchema = await putSchema(store, { + const childSchema = putSchema(store, { type: "object", properties: { value: { type: "string" }, @@ -167,7 +167,7 @@ describe("Suite 2: Custom {% render %} Tag Implementation", () => { const leftHash = store.cas.put(childSchema, { value: "left" }); const rightHash = store.cas.put(childSchema, { value: "right" }); - const parentSchema = await putSchema(store, { + const parentSchema = putSchema(store, { type: "object", properties: { left: { type: "string", format: "ocas_ref" }, @@ -179,7 +179,7 @@ describe("Suite 2: Custom {% render %} Tag Implementation", () => { right: rightHash, }); - const templateSchema = await putSchema(store, { type: "string" }); + const templateSchema = putSchema(store, { type: "string" }); const parentTemplate = store.cas.put( templateSchema, "Left:\n{% render payload.left %}\nRight:\n{% render payload.right %}", @@ -211,7 +211,7 @@ describe("Suite 2: Custom {% render %} Tag Implementation", () => { const { store, cleanup } = await createTempVarStore(); try { - const nodeSchema = await putSchema(store, { + const nodeSchema = putSchema(store, { type: "object", properties: { name: { type: "string" }, @@ -225,7 +225,7 @@ describe("Suite 2: Custom {% render %} Tag Implementation", () => { child: null, }); - const templateSchema = await putSchema(store, { type: "string" }); + const templateSchema = putSchema(store, { type: "string" }); const template = store.cas.put( templateSchema, "Before\n{% render payload.child %}\nAfter", @@ -251,7 +251,7 @@ describe("Suite 2: Custom {% render %} Tag Implementation", () => { try { const fakeHash = "ZZZZZZZZZZZZZ" as Hash; - const nodeSchema = await putSchema(store, { + const nodeSchema = putSchema(store, { type: "object", properties: { name: { type: "string" }, @@ -263,7 +263,7 @@ describe("Suite 2: Custom {% render %} Tag Implementation", () => { child: fakeHash, }); - const templateSchema = await putSchema(store, { type: "string" }); + const templateSchema = putSchema(store, { type: "string" }); const template = store.cas.put( templateSchema, "{% render payload.child %}", @@ -286,7 +286,7 @@ describe("Suite 2: Custom {% render %} Tag Implementation", () => { const { store, cleanup } = await createTempVarStore(); try { - const childSchema = await putSchema(store, { + const childSchema = putSchema(store, { type: "object", properties: { value: { type: "string" }, @@ -294,7 +294,7 @@ describe("Suite 2: Custom {% render %} Tag Implementation", () => { }); const childHash = store.cas.put(childSchema, { value: "child" }); - const parentSchema = await putSchema(store, { + const parentSchema = putSchema(store, { type: "object", properties: { child: { type: "string", format: "ocas_ref" }, @@ -302,7 +302,7 @@ describe("Suite 2: Custom {% render %} Tag Implementation", () => { }); const parentHash = store.cas.put(parentSchema, { child: childHash }); - const templateSchema = await putSchema(store, { type: "string" }); + const templateSchema = putSchema(store, { type: "string" }); const parentTemplate = store.cas.put( templateSchema, "{% render payload.child %}", @@ -328,7 +328,7 @@ describe("Suite 3: Template Context Variables", () => { const { store, cleanup } = await createTempVarStore(); try { - const nodeSchema = await putSchema(store, { + const nodeSchema = putSchema(store, { type: "object", properties: { name: { type: "string" }, @@ -336,7 +336,7 @@ describe("Suite 3: Template Context Variables", () => { }); const nodeHash = store.cas.put(nodeSchema, { name: "test" }); - const templateSchema = await putSchema(store, { type: "string" }); + const templateSchema = putSchema(store, { type: "string" }); const template = store.cas.put( templateSchema, "Resolution: {{ resolution }}", @@ -359,7 +359,7 @@ describe("Suite 3: Template Context Variables", () => { const { store, cleanup } = await createTempVarStore(); try { - const nodeSchema = await putSchema(store, { + const nodeSchema = putSchema(store, { type: "object", properties: { name: { type: "string" }, @@ -367,7 +367,7 @@ describe("Suite 3: Template Context Variables", () => { }); const nodeHash = store.cas.put(nodeSchema, { name: "test" }); - const templateSchema = await putSchema(store, { type: "string" }); + const templateSchema = putSchema(store, { type: "string" }); const template = store.cas.put(templateSchema, "Epsilon: {{ epsilon }}"); store.var.set(`@ocas/template/text/${nodeSchema}`, template); @@ -387,7 +387,7 @@ describe("Suite 3: Template Context Variables", () => { const { store, cleanup } = await createTempVarStore(); try { - const nodeSchema = await putSchema(store, { + const nodeSchema = putSchema(store, { type: "object", properties: { name: { type: "string" }, @@ -395,7 +395,7 @@ describe("Suite 3: Template Context Variables", () => { }); const nodeHash = store.cas.put(nodeSchema, { name: "test" }); - const templateSchema = await putSchema(store, { type: "string" }); + const templateSchema = putSchema(store, { type: "string" }); const template = store.cas.put(templateSchema, "Hash: {{ hash }}"); store.var.set(`@ocas/template/text/${nodeSchema}`, template); @@ -415,7 +415,7 @@ describe("Suite 3: Template Context Variables", () => { const { store, cleanup } = await createTempVarStore(); try { - const nodeSchema = await putSchema(store, { + const nodeSchema = putSchema(store, { type: "object", properties: { name: { type: "string" }, @@ -424,7 +424,7 @@ describe("Suite 3: Template Context Variables", () => { }); const nodeHash = store.cas.put(nodeSchema, { name: "test", count: 42 }); - const templateSchema = await putSchema(store, { type: "string" }); + const templateSchema = putSchema(store, { type: "string" }); const template = store.cas.put( templateSchema, "Name: {{ payload.name }}, Count: {{ payload.count }}", @@ -447,7 +447,7 @@ describe("Suite 3: Template Context Variables", () => { const { store, cleanup } = await createTempVarStore(); try { - const nodeSchema = await putSchema(store, { + const nodeSchema = putSchema(store, { type: "object", properties: { name: { type: "string" }, @@ -455,7 +455,7 @@ describe("Suite 3: Template Context Variables", () => { }); const nodeHash = store.cas.put(nodeSchema, { name: "test" }); - const templateSchema = await putSchema(store, { type: "string" }); + const templateSchema = putSchema(store, { type: "string" }); const template = store.cas.put(templateSchema, "Type: {{ type }}"); store.var.set(`@ocas/template/text/${nodeSchema}`, template); @@ -475,7 +475,7 @@ describe("Suite 3: Template Context Variables", () => { const { store, cleanup } = await createTempVarStore(); try { - const nodeSchema = await putSchema(store, { + const nodeSchema = putSchema(store, { type: "object", properties: { name: { type: "string" }, @@ -483,7 +483,7 @@ describe("Suite 3: Template Context Variables", () => { }); const nodeHash = store.cas.put(nodeSchema, { name: "test" }); - const templateSchema = await putSchema(store, { type: "string" }); + const templateSchema = putSchema(store, { type: "string" }); const template = store.cas.put( templateSchema, "Timestamp: {{ timestamp }}", @@ -506,7 +506,7 @@ describe("Suite 3: Template Context Variables", () => { const { store, cleanup } = await createTempVarStore(); try { - const nodeSchema = await putSchema(store, { + const nodeSchema = putSchema(store, { type: "object", properties: { name: { type: "string" }, @@ -514,7 +514,7 @@ describe("Suite 3: Template Context Variables", () => { }); const nodeHash = store.cas.put(nodeSchema, { name: "test" }); - const templateSchema = await putSchema(store, { type: "string" }); + const templateSchema = putSchema(store, { type: "string" }); const template = store.cas.put( templateSchema, `Hash: {{ hash }} @@ -549,7 +549,7 @@ describe("Suite 4: Render Flow Integration", () => { const { store, cleanup } = await createTempVarStore(); try { - const nodeSchema = await putSchema(store, { + const nodeSchema = putSchema(store, { type: "object", properties: { name: { type: "string" }, @@ -557,7 +557,7 @@ describe("Suite 4: Render Flow Integration", () => { }); const nodeHash = store.cas.put(nodeSchema, { name: "test" }); - const templateSchema = await putSchema(store, { type: "string" }); + const templateSchema = putSchema(store, { type: "string" }); const template = store.cas.put( templateSchema, "Custom template: {{ payload.name }}", @@ -580,7 +580,7 @@ describe("Suite 4: Render Flow Integration", () => { const { store, cleanup } = await createTempVarStore(); try { - const nodeSchema = await putSchema(store, { + const nodeSchema = putSchema(store, { type: "object", properties: { name: { type: "string" }, @@ -588,7 +588,7 @@ describe("Suite 4: Render Flow Integration", () => { }); const nodeHash = store.cas.put(nodeSchema, { name: "test" }); - const templateSchema = await putSchema(store, { type: "string" }); + const templateSchema = putSchema(store, { type: "string" }); const template = store.cas.put(templateSchema, ""); store.var.set(`@ocas/template/text/${nodeSchema}`, template); @@ -608,7 +608,7 @@ describe("Suite 4: Render Flow Integration", () => { const { store, cleanup } = await createTempVarStore(); try { - const nodeSchema = await putSchema(store, { + const nodeSchema = putSchema(store, { type: "object", properties: { name: { type: "string" }, @@ -616,7 +616,7 @@ describe("Suite 4: Render Flow Integration", () => { }); const nodeHash = store.cas.put(nodeSchema, { name: "test" }); - const templateSchema = await putSchema(store, { type: "string" }); + const templateSchema = putSchema(store, { type: "string" }); const template = store.cas.put( templateSchema, "{% render %}", // Invalid: no variable @@ -641,7 +641,7 @@ describe("Suite 5: Decay Priority Chain", () => { const { store, cleanup } = await createTempVarStore(); try { - const childSchema = await putSchema(store, { + const childSchema = putSchema(store, { type: "object", properties: { value: { type: "string" }, @@ -649,7 +649,7 @@ describe("Suite 5: Decay Priority Chain", () => { }); const childHash = store.cas.put(childSchema, { value: "child" }); - const parentSchema = await putSchema(store, { + const parentSchema = putSchema(store, { type: "object", properties: { child: { type: "string", format: "ocas_ref" }, @@ -657,7 +657,7 @@ describe("Suite 5: Decay Priority Chain", () => { }); const parentHash = store.cas.put(parentSchema, { child: childHash }); - const templateSchema = await putSchema(store, { type: "string" }); + const templateSchema = putSchema(store, { type: "string" }); const parentTemplate = store.cas.put( templateSchema, "{% render payload.child, decay: 0.7 %}", @@ -687,7 +687,7 @@ describe("Suite 5: Decay Priority Chain", () => { const { store, cleanup } = await createTempVarStore(); try { - const childSchema = await putSchema(store, { + const childSchema = putSchema(store, { type: "object", properties: { value: { type: "string" }, @@ -695,7 +695,7 @@ describe("Suite 5: Decay Priority Chain", () => { }); const childHash = store.cas.put(childSchema, { value: "child" }); - const parentSchema = await putSchema(store, { + const parentSchema = putSchema(store, { type: "object", properties: { child: { type: "string", format: "ocas_ref" }, @@ -703,7 +703,7 @@ describe("Suite 5: Decay Priority Chain", () => { }); const parentHash = store.cas.put(parentSchema, { child: childHash }); - const templateSchema = await putSchema(store, { type: "string" }); + const templateSchema = putSchema(store, { type: "string" }); const parentTemplate = store.cas.put( templateSchema, "{% render payload.child %}", // No explicit decay @@ -733,7 +733,7 @@ describe("Suite 5: Decay Priority Chain", () => { const { store, cleanup } = await createTempVarStore(); try { - const childSchema = await putSchema(store, { + const childSchema = putSchema(store, { type: "object", properties: { value: { type: "string" }, @@ -741,7 +741,7 @@ describe("Suite 5: Decay Priority Chain", () => { }); const childHash = store.cas.put(childSchema, { value: "child" }); - const parentSchema = await putSchema(store, { + const parentSchema = putSchema(store, { type: "object", properties: { child: { type: "string", format: "ocas_ref" }, @@ -749,7 +749,7 @@ describe("Suite 5: Decay Priority Chain", () => { }); const parentHash = store.cas.put(parentSchema, { child: childHash }); - const templateSchema = await putSchema(store, { type: "string" }); + const templateSchema = putSchema(store, { type: "string" }); const parentTemplate = store.cas.put( templateSchema, "{% render payload.child %}", @@ -781,7 +781,7 @@ describe("Suite 6: Recursive Rendering Edge Cases", () => { const { store, cleanup } = await createTempVarStore(); try { - const nodeSchema = await putSchema(store, { + const nodeSchema = putSchema(store, { type: "object", properties: { level: { type: "number" }, @@ -800,7 +800,7 @@ describe("Suite 6: Recursive Rendering Edge Cases", () => { }); } - const templateSchema = await putSchema(store, { type: "string" }); + const templateSchema = putSchema(store, { type: "string" }); const template = store.cas.put( templateSchema, "Level {{ payload.level }}\n{% render payload.next %}", @@ -826,7 +826,7 @@ describe("Suite 6: Recursive Rendering Edge Cases", () => { const { store, cleanup } = await createTempVarStore(); try { - const nodeSchema = await putSchema(store, { + const nodeSchema = putSchema(store, { type: "object", properties: { name: { type: "string" }, @@ -839,7 +839,7 @@ describe("Suite 6: Recursive Rendering Edge Cases", () => { // Create simple node first const nodeAHash = store.cas.put(nodeSchema, { name: "A", ref: null }); - const templateSchema = await putSchema(store, { type: "string" }); + const templateSchema = putSchema(store, { type: "string" }); const template = store.cas.put( templateSchema, "Node {{ payload.name }}\n{% render payload.ref %}", @@ -862,7 +862,7 @@ describe("Suite 6: Recursive Rendering Edge Cases", () => { const { store, cleanup } = await createTempVarStore(); try { - const itemSchema = await putSchema(store, { + const itemSchema = putSchema(store, { type: "object", properties: { name: { type: "string" }, @@ -872,7 +872,7 @@ describe("Suite 6: Recursive Rendering Edge Cases", () => { const item2 = store.cas.put(itemSchema, { name: "item2" }); const item3 = store.cas.put(itemSchema, { name: "item3" }); - const parentSchema = await putSchema(store, { + const parentSchema = putSchema(store, { type: "object", properties: { items: { @@ -885,7 +885,7 @@ describe("Suite 6: Recursive Rendering Edge Cases", () => { items: [item1, item2, item3], }); - const templateSchema = await putSchema(store, { type: "string" }); + const templateSchema = putSchema(store, { type: "string" }); const parentTemplate = store.cas.put( templateSchema, "{% for item in payload.items %}{% render item %}\n{% endfor %}", @@ -918,7 +918,7 @@ describe("Suite 7: Error Handling & Edge Cases", () => { const { store, cleanup } = await createTempVarStore(); try { - const nodeSchema = await putSchema(store, { + const nodeSchema = putSchema(store, { type: "object", properties: { name: { type: "string" }, @@ -926,7 +926,7 @@ describe("Suite 7: Error Handling & Edge Cases", () => { }); const nodeHash = store.cas.put(nodeSchema, { name: "test" }); - const templateSchema = await putSchema(store, { type: "string" }); + const templateSchema = putSchema(store, { type: "string" }); const template = store.cas.put(templateSchema, "{% render missingVar %}"); store.var.set(`@ocas/template/text/${nodeSchema}`, template); @@ -947,7 +947,7 @@ describe("Suite 7: Error Handling & Edge Cases", () => { const { store, cleanup } = await createTempVarStore(); try { - const childSchema = await putSchema(store, { + const childSchema = putSchema(store, { type: "object", properties: { value: { type: "string" }, @@ -955,7 +955,7 @@ describe("Suite 7: Error Handling & Edge Cases", () => { }); const childHash = store.cas.put(childSchema, { value: "child" }); - const parentSchema = await putSchema(store, { + const parentSchema = putSchema(store, { type: "object", properties: { child: { type: "string", format: "ocas_ref" }, @@ -963,7 +963,7 @@ describe("Suite 7: Error Handling & Edge Cases", () => { }); const parentHash = store.cas.put(parentSchema, { child: childHash }); - const templateSchema = await putSchema(store, { type: "string" }); + const templateSchema = putSchema(store, { type: "string" }); const template = store.cas.put( templateSchema, "{% render payload.child, decay: 1.5 %}", @@ -986,7 +986,7 @@ describe("Suite 7: Error Handling & Edge Cases", () => { const { store, cleanup } = await createTempVarStore(); try { - const childSchema = await putSchema(store, { + const childSchema = putSchema(store, { type: "object", properties: { value: { type: "string" }, @@ -994,7 +994,7 @@ describe("Suite 7: Error Handling & Edge Cases", () => { }); const childHash = store.cas.put(childSchema, { value: "child" }); - const parentSchema = await putSchema(store, { + const parentSchema = putSchema(store, { type: "object", properties: { child: { type: "string", format: "ocas_ref" }, @@ -1002,7 +1002,7 @@ describe("Suite 7: Error Handling & Edge Cases", () => { }); const parentHash = store.cas.put(parentSchema, { child: childHash }); - const templateSchema = await putSchema(store, { type: "string" }); + const templateSchema = putSchema(store, { type: "string" }); const template = store.cas.put( templateSchema, "{% render payload.child, decay: -0.5 %}", @@ -1025,7 +1025,7 @@ describe("Suite 7: Error Handling & Edge Cases", () => { const { store, cleanup } = await createTempVarStore(); try { - const childSchema = await putSchema(store, { + const childSchema = putSchema(store, { type: "object", properties: { value: { type: "string" }, @@ -1033,7 +1033,7 @@ describe("Suite 7: Error Handling & Edge Cases", () => { }); const childHash = store.cas.put(childSchema, { value: "child" }); - const parentSchema = await putSchema(store, { + const parentSchema = putSchema(store, { type: "object", properties: { child: { type: "string", format: "ocas_ref" }, @@ -1041,7 +1041,7 @@ describe("Suite 7: Error Handling & Edge Cases", () => { }); const parentHash = store.cas.put(parentSchema, { child: childHash }); - const templateSchema = await putSchema(store, { type: "string" }); + const templateSchema = putSchema(store, { type: "string" }); const template = store.cas.put( templateSchema, "{% render payload.child, decay: 0 %}", @@ -1064,7 +1064,7 @@ describe("Suite 7: Error Handling & Edge Cases", () => { const { store, cleanup } = await createTempVarStore(); try { - const childSchema = await putSchema(store, { + const childSchema = putSchema(store, { type: "object", properties: { value: { type: "string" }, @@ -1072,7 +1072,7 @@ describe("Suite 7: Error Handling & Edge Cases", () => { }); const childHash = store.cas.put(childSchema, { value: "child" }); - const parentSchema = await putSchema(store, { + const parentSchema = putSchema(store, { type: "object", properties: { child: { type: "string", format: "ocas_ref" }, @@ -1080,7 +1080,7 @@ describe("Suite 7: Error Handling & Edge Cases", () => { }); const parentHash = store.cas.put(parentSchema, { child: childHash }); - const templateSchema = await putSchema(store, { type: "string" }); + const templateSchema = putSchema(store, { type: "string" }); const parentTemplate = store.cas.put( templateSchema, "{% render payload.child, decay: 1 %}", @@ -1110,7 +1110,7 @@ describe("Suite 7: Error Handling & Edge Cases", () => { const { store, cleanup } = await createTempVarStore(); try { - const nodeSchema = await putSchema(store, { + const nodeSchema = putSchema(store, { type: "object", properties: { name: { type: "string" }, @@ -1118,7 +1118,7 @@ describe("Suite 7: Error Handling & Edge Cases", () => { }); const nodeHash = store.cas.put(nodeSchema, { name: "世界" }); - const templateSchema = await putSchema(store, { type: "string" }); + const templateSchema = putSchema(store, { type: "string" }); const template = store.cas.put( templateSchema, "你好: {{ payload.name }} 🌍", @@ -1143,7 +1143,7 @@ describe("Suite 8: Performance & Scalability", () => { const { store, cleanup } = await createTempVarStore(); try { - const itemSchema = await putSchema(store, { + const itemSchema = putSchema(store, { type: "object", properties: { value: { type: "number" }, @@ -1156,7 +1156,7 @@ describe("Suite 8: Performance & Scalability", () => { children.push(hash); } - const parentSchema = await putSchema(store, { + const parentSchema = putSchema(store, { type: "object", properties: { items: { @@ -1167,7 +1167,7 @@ describe("Suite 8: Performance & Scalability", () => { }); const parentHash = store.cas.put(parentSchema, { items: children }); - const templateSchema = await putSchema(store, { type: "string" }); + const templateSchema = putSchema(store, { type: "string" }); const parentTemplate = store.cas.put( templateSchema, "{% for child in payload.items %}{% render child %}{% endfor %}", @@ -1199,7 +1199,7 @@ describe("Suite 9: E2E Template Variable Rendering (Issue #52)", () => { try { // Create schema for person object - const personSchema = await putSchema(store, { + const personSchema = putSchema(store, { type: "object", properties: { name: { type: "string" }, @@ -1214,7 +1214,7 @@ describe("Suite 9: E2E Template Variable Rendering (Issue #52)", () => { }); // Register template using direct property access (incorrect syntax) - const templateSchema = await putSchema(store, { type: "string" }); + const templateSchema = putSchema(store, { type: "string" }); const templateHash = store.cas.put( templateSchema, "Name: {{ name }}, Age: {{ age }}", @@ -1239,7 +1239,7 @@ describe("Suite 9: E2E Template Variable Rendering (Issue #52)", () => { try { // Create schema for person object - const personSchema = await putSchema(store, { + const personSchema = putSchema(store, { type: "object", properties: { name: { type: "string" }, @@ -1254,7 +1254,7 @@ describe("Suite 9: E2E Template Variable Rendering (Issue #52)", () => { }); // Register template using correct payload. prefix - const templateSchema = await putSchema(store, { type: "string" }); + const templateSchema = putSchema(store, { type: "string" }); const templateHash = store.cas.put( templateSchema, "Name: {{ payload.name }}, Age: {{ payload.age }}", @@ -1279,7 +1279,7 @@ describe("Suite 9: E2E Template Variable Rendering (Issue #52)", () => { try { // Create schema and node - const personSchema = await putSchema(store, { + const personSchema = putSchema(store, { type: "object", properties: { name: { type: "string" }, @@ -1293,7 +1293,7 @@ describe("Suite 9: E2E Template Variable Rendering (Issue #52)", () => { }); // Register template - const templateSchema = await putSchema(store, { type: "string" }); + const templateSchema = putSchema(store, { type: "string" }); const templateHash = store.cas.put( templateSchema, "User: {{ payload.name }}, Age: {{ payload.age }}", @@ -1319,13 +1319,13 @@ describe("Suite 9: E2E Template Variable Rendering (Issue #52)", () => { try { // Create schema for simple string - const stringSchema = await putSchema(store, { type: "string" }); + const stringSchema = putSchema(store, { type: "string" }); // Create node with string payload const stringHash = store.cas.put(stringSchema, "Hello World"); // Register template - const templateSchema = await putSchema(store, { type: "string" }); + const templateSchema = putSchema(store, { type: "string" }); const templateHash = store.cas.put( templateSchema, "Value is: {{ payload }}", @@ -1350,13 +1350,13 @@ describe("Suite 9: E2E Template Variable Rendering (Issue #52)", () => { try { // Create schema for number - const numberSchema = await putSchema(store, { type: "number" }); + const numberSchema = putSchema(store, { type: "number" }); // Create node with number payload const numberHash = store.cas.put(numberSchema, 42); // Register template - const templateSchema = await putSchema(store, { type: "string" }); + const templateSchema = putSchema(store, { type: "string" }); const templateHash = store.cas.put( templateSchema, "The answer is {{ payload }}", @@ -1381,7 +1381,7 @@ describe("Suite 9: E2E Template Variable Rendering (Issue #52)", () => { try { // Create schema for nested object - const userSchema = await putSchema(store, { + const userSchema = putSchema(store, { type: "object", properties: { user: { @@ -1410,7 +1410,7 @@ describe("Suite 9: E2E Template Variable Rendering (Issue #52)", () => { }); // Register template with deep property access - const templateSchema = await putSchema(store, { type: "string" }); + const templateSchema = putSchema(store, { type: "string" }); const templateHash = store.cas.put( templateSchema, "User {{ payload.user.name }} lives in {{ payload.user.address.city }}", @@ -1435,7 +1435,7 @@ describe("Suite 9: E2E Template Variable Rendering (Issue #52)", () => { try { // Create schema with array - const tagsSchema = await putSchema(store, { + const tagsSchema = putSchema(store, { type: "object", properties: { tags: { @@ -1451,7 +1451,7 @@ describe("Suite 9: E2E Template Variable Rendering (Issue #52)", () => { }); // Register template with array iteration - const templateSchema = await putSchema(store, { type: "string" }); + const templateSchema = putSchema(store, { type: "string" }); const templateHash = store.cas.put( templateSchema, "Tags: {% for tag in payload.tags %}{{ tag }}{% unless forloop.last %}, {% endunless %}{% endfor %}", @@ -1476,7 +1476,7 @@ describe("Suite 9: E2E Template Variable Rendering (Issue #52)", () => { try { // Create schema - const personSchema = await putSchema(store, { + const personSchema = putSchema(store, { type: "object", properties: { name: { type: "string" }, @@ -1489,7 +1489,7 @@ describe("Suite 9: E2E Template Variable Rendering (Issue #52)", () => { }); // Register template that references missing property - const templateSchema = await putSchema(store, { type: "string" }); + const templateSchema = putSchema(store, { type: "string" }); const templateHash = store.cas.put( templateSchema, "Name: {{ payload.name }}, Age: {{ payload.age }}", @@ -1514,7 +1514,7 @@ describe("Suite 9: E2E Template Variable Rendering (Issue #52)", () => { try { // Create schema allowing null - const personSchema = await putSchema(store, { + const personSchema = putSchema(store, { type: "object", properties: { name: { type: "string" }, @@ -1529,7 +1529,7 @@ describe("Suite 9: E2E Template Variable Rendering (Issue #52)", () => { }); // Register template - const templateSchema = await putSchema(store, { type: "string" }); + const templateSchema = putSchema(store, { type: "string" }); const templateHash = store.cas.put( templateSchema, "Name: {{ payload.name }}, Email: {{ payload.email }}", @@ -1554,7 +1554,7 @@ describe("Suite 9: E2E Template Variable Rendering (Issue #52)", () => { try { // Create schema with boolean - const userSchema = await putSchema(store, { + const userSchema = putSchema(store, { type: "object", properties: { name: { type: "string" }, @@ -1569,7 +1569,7 @@ describe("Suite 9: E2E Template Variable Rendering (Issue #52)", () => { }); // Register template with conditional - const templateSchema = await putSchema(store, { type: "string" }); + const templateSchema = putSchema(store, { type: "string" }); const templateHash = store.cas.put( templateSchema, "User {{ payload.name }} is {% if payload.active %}active{% else %}inactive{% endif %}", @@ -1594,7 +1594,7 @@ describe("Suite 9: E2E Template Variable Rendering (Issue #52)", () => { try { // Create schema - const dataSchema = await putSchema(store, { + const dataSchema = putSchema(store, { type: "object", properties: { name: { type: "string" }, @@ -1609,7 +1609,7 @@ describe("Suite 9: E2E Template Variable Rendering (Issue #52)", () => { }); // Register template - const templateSchema = await putSchema(store, { type: "string" }); + const templateSchema = putSchema(store, { type: "string" }); const templateHash = store.cas.put( templateSchema, "Name: '{{ payload.name }}', Count: {{ payload.count }}", @@ -1634,7 +1634,7 @@ describe("Suite 9: E2E Template Variable Rendering (Issue #52)", () => { try { // Create schema - const textSchema = await putSchema(store, { + const textSchema = putSchema(store, { type: "object", properties: { text: { type: "string" }, @@ -1647,7 +1647,7 @@ describe("Suite 9: E2E Template Variable Rendering (Issue #52)", () => { }); // Register template - const templateSchema = await putSchema(store, { type: "string" }); + const templateSchema = putSchema(store, { type: "string" }); const templateHash = store.cas.put( templateSchema, "Text: {{ payload.text }}", @@ -1674,7 +1674,7 @@ describe("Suite 10: Context Variable Completeness", () => { try { // Create child schema and node - const childSchema = await putSchema(store, { + const childSchema = putSchema(store, { type: "object", properties: { name: { type: "string" }, @@ -1683,7 +1683,7 @@ describe("Suite 10: Context Variable Completeness", () => { const childHash = store.cas.put(childSchema, { name: "child" }); // Create parent schema and node - const parentSchema = await putSchema(store, { + const parentSchema = putSchema(store, { type: "object", properties: { name: { type: "string" }, @@ -1696,7 +1696,7 @@ describe("Suite 10: Context Variable Completeness", () => { }); // Register parent template - const templateSchema = await putSchema(store, { type: "string" }); + const templateSchema = putSchema(store, { type: "string" }); const parentTemplateHash = store.cas.put( templateSchema, "Parent: {{ payload.name }}\n{% render payload.child %}", @@ -1731,7 +1731,7 @@ describe("Suite 10: Context Variable Completeness", () => { try { // Create child schema and node - const childSchema = await putSchema(store, { + const childSchema = putSchema(store, { type: "object", properties: { custom: { type: "string" }, @@ -1742,7 +1742,7 @@ describe("Suite 10: Context Variable Completeness", () => { }); // Create parent schema and node - const parentSchema = await putSchema(store, { + const parentSchema = putSchema(store, { type: "object", properties: { custom: { type: "string" }, @@ -1755,7 +1755,7 @@ describe("Suite 10: Context Variable Completeness", () => { }); // Register parent template - const templateSchema = await putSchema(store, { type: "string" }); + const templateSchema = putSchema(store, { type: "string" }); const parentTemplateHash = store.cas.put( templateSchema, "Parent custom: {{ payload.custom }}\n{% render payload.child %}", diff --git a/packages/core/src/liquid-render.ts b/packages/core/src/liquid-render.ts index 29b7834..575362e 100644 --- a/packages/core/src/liquid-render.ts +++ b/packages/core/src/liquid-render.ts @@ -1,7 +1,7 @@ import { type Context, Liquid, type TagToken } from "liquidjs"; import type { RenderOptions } from "./render.js"; import { putSchema } from "./schema.js"; -import type { Hash, OcasStore } from "./types.js"; +import type { Hash, Store } from "./types.js"; const DEFAULT_RESOLUTION = 1.0; const DEFAULT_DECAY = 0.5; @@ -13,7 +13,7 @@ const FLOAT_TOLERANCE = 1e-10; * Templates are discovered via variables: @ocas/template/text/ */ export async function renderWithTemplate( - store: OcasStore, + store: Store, hash: Hash, options?: RenderOptions, ): Promise { @@ -43,7 +43,7 @@ export async function renderWithTemplate( /** * Create a Liquid engine instance with custom render tag */ -function createLiquidEngine(store: OcasStore, globalDecay: number): Liquid { +function createLiquidEngine(store: Store, globalDecay: number): Liquid { const engine = new Liquid({ strictFilters: false, strictVariables: false, @@ -141,7 +141,7 @@ function createLiquidEngine(store: OcasStore, globalDecay: number): Liquid { */ async function renderNode( engine: Liquid, - store: OcasStore, + store: Store, hash: Hash, currentResolution: number, epsilon: number, @@ -200,14 +200,14 @@ async function renderNode( * Find a template for a given type hash */ async function findTemplate( - store: OcasStore, + store: Store, typeHash: Hash, ): Promise { const varName = `@ocas/template/text/${typeHash}`; try { // Find the string schema hash (we need this to query variables) - const stringSchema = await putSchema(store, { type: "string" }); + const stringSchema = putSchema(store, { type: "string" }); const variable = store.var.get(varName, stringSchema); if (variable === null) { diff --git a/packages/core/src/mem-store.ts b/packages/core/src/mem-store.ts index a744e06..14e4341 100644 --- a/packages/core/src/mem-store.ts +++ b/packages/core/src/mem-store.ts @@ -1,13 +1,13 @@ import { createMemoryStore } from "./store.js"; -import type { CasStore, OcasStore, TagStore, VarStore } from "./types.js"; +import type { CasStore, Store, TagStore, VarStore } from "./types.js"; /** - * In-memory `OcasStore` used by schema validation tests. It exposes the - * `cas`, `var`, and `tag` sub-stores of an `OcasStore` plus a few legacy + * In-memory `Store` used by schema validation tests. It exposes the + * `cas`, `var`, and `tag` sub-stores of an `Store` plus a few legacy * pass-through helpers (`get`, `put`, `has`, …) that some older tests still * use directly. */ -export class MemStore implements OcasStore { +export class MemStore implements Store { readonly cas: CasStore; readonly var: VarStore; readonly tag: TagStore; diff --git a/packages/core/src/output-templates.test.ts b/packages/core/src/output-templates.test.ts index 468af3d..fdc5f36 100644 --- a/packages/core/src/output-templates.test.ts +++ b/packages/core/src/output-templates.test.ts @@ -28,7 +28,7 @@ const OUTPUT_ALIASES = [ describe("registerOutputTemplates", () => { test("registers a template for every @ocas/output/* schema", async () => { const store = createMemoryStore(); - await bootstrap(store); + bootstrap(store); const registered = await registerOutputTemplates(store); @@ -41,7 +41,7 @@ describe("registerOutputTemplates", () => { test("each template is retrievable via @ocas/template/text/", async () => { const store = createMemoryStore(); - const aliases = await bootstrap(store); + const aliases = bootstrap(store); await registerOutputTemplates(store); @@ -65,7 +65,7 @@ describe("registerOutputTemplates", () => { test("is idempotent — safe to call multiple times", async () => { const store = createMemoryStore(); - await bootstrap(store); + bootstrap(store); const first = await registerOutputTemplates(store); const second = await registerOutputTemplates(store); @@ -75,7 +75,7 @@ describe("registerOutputTemplates", () => { test("@ocas/output/put template contains payload reference", async () => { const store = createMemoryStore(); - const aliases = await bootstrap(store); + const aliases = bootstrap(store); await registerOutputTemplates(store); diff --git a/packages/core/src/output-templates.ts b/packages/core/src/output-templates.ts index d2eb458..a9393bb 100644 --- a/packages/core/src/output-templates.ts +++ b/packages/core/src/output-templates.ts @@ -1,5 +1,5 @@ import { bootstrap } from "./bootstrap.js"; -import type { Hash, OcasStore } from "./types.js"; +import type { Hash, Store } from "./types.js"; const DEFAULT_TEMPLATES: ReadonlyArray< readonly [alias: string, template: string] @@ -63,9 +63,9 @@ const DEFAULT_TEMPLATES: ReadonlyArray< * Idempotent: safe to call multiple times. */ export async function registerOutputTemplates( - store: OcasStore, + store: Store, ): Promise> { - const aliases = await bootstrap(store); + const aliases = bootstrap(store); const stringHash = aliases["@ocas/string"]; if (stringHash === undefined) { throw new Error("@ocas/string schema not found in bootstrap result"); diff --git a/packages/core/src/render.test.ts b/packages/core/src/render.test.ts index 6a58aae..fa0ae59 100644 --- a/packages/core/src/render.test.ts +++ b/packages/core/src/render.test.ts @@ -9,8 +9,8 @@ import type { Hash } from "./types.js"; describe("Suite 1: Basic Rendering (No Nesting)", () => { test("1.1 Render Simple Primitives", async () => { const store = createMemoryStore(); - await bootstrap(store); - const textSchema = await putSchema(store, { type: "string" }); + bootstrap(store); + const textSchema = putSchema(store, { type: "string" }); const hash = store.cas.put(textSchema, "hello"); const output = render(store, hash, { resolution: 1.0 }); @@ -21,8 +21,8 @@ describe("Suite 1: Basic Rendering (No Nesting)", () => { test("1.2 Render Object Node (Flat)", async () => { const store = createMemoryStore(); - await bootstrap(store); - const objSchema = await putSchema(store, { + bootstrap(store); + const objSchema = putSchema(store, { type: "object", properties: { name: { type: "string" }, @@ -41,8 +41,8 @@ describe("Suite 1: Basic Rendering (No Nesting)", () => { test("1.3 Render Array Node (Flat)", async () => { const store = createMemoryStore(); - await bootstrap(store); - const arraySchema = await putSchema(store, { + bootstrap(store); + const arraySchema = putSchema(store, { type: "array", items: { type: "number" }, }); @@ -57,8 +57,8 @@ describe("Suite 1: Basic Rendering (No Nesting)", () => { test("1.4 Render with resolution=0 (Force Reference)", async () => { const store = createMemoryStore(); - await bootstrap(store); - const textSchema = await putSchema(store, { type: "string" }); + bootstrap(store); + const textSchema = putSchema(store, { type: "string" }); const hash = store.cas.put(textSchema, "hello"); const output = render(store, hash, { resolution: 0 }); @@ -80,9 +80,9 @@ describe("Suite 1: Basic Rendering (No Nesting)", () => { describe("Suite 2: Resolution Decay Model", () => { test("2.1 Single-level Nesting with Default Decay", async () => { const store = createMemoryStore(); - await bootstrap(store); + bootstrap(store); - const childSchema = await putSchema(store, { + const childSchema = putSchema(store, { type: "object", properties: { content: { type: "string" }, @@ -90,7 +90,7 @@ describe("Suite 2: Resolution Decay Model", () => { }); const childHash = store.cas.put(childSchema, { content: "leaf" }); - const parentSchema = await putSchema(store, { + const parentSchema = putSchema(store, { type: "object", properties: { title: { type: "string" }, @@ -116,9 +116,9 @@ describe("Suite 2: Resolution Decay Model", () => { test("2.2 Multi-level Nesting Reaches Epsilon", async () => { const store = createMemoryStore(); - await bootstrap(store); + bootstrap(store); - const leafSchema = await putSchema(store, { + const leafSchema = putSchema(store, { type: "object", properties: { value: { type: "number" }, @@ -152,9 +152,9 @@ describe("Suite 2: Resolution Decay Model", () => { test("2.3 High Decay (Quick Cutoff)", async () => { const store = createMemoryStore(); - await bootstrap(store); + bootstrap(store); - const nodeSchema = await putSchema(store, { + const nodeSchema = putSchema(store, { type: "object", properties: { level: { type: "number" }, @@ -190,9 +190,9 @@ describe("Suite 2: Resolution Decay Model", () => { test("2.4 Low Decay (Deep Expansion)", async () => { const store = createMemoryStore(); - await bootstrap(store); + bootstrap(store); - const nodeSchema = await putSchema(store, { + const nodeSchema = putSchema(store, { type: "object", properties: { level: { type: "number" }, @@ -225,9 +225,9 @@ describe("Suite 2: Resolution Decay Model", () => { test("2.5 Starting Resolution Below 1.0", async () => { const store = createMemoryStore(); - await bootstrap(store); + bootstrap(store); - const nodeSchema = await putSchema(store, { + const nodeSchema = putSchema(store, { type: "object", properties: { level: { type: "number" }, @@ -263,9 +263,9 @@ describe("Suite 2: Resolution Decay Model", () => { describe("Suite 3: Complex Graph Structures", () => { test("3.1 Multiple Child References", async () => { const store = createMemoryStore(); - await bootstrap(store); + bootstrap(store); - const itemSchema = await putSchema(store, { + const itemSchema = putSchema(store, { type: "object", properties: { name: { type: "string" }, @@ -276,7 +276,7 @@ describe("Suite 3: Complex Graph Structures", () => { const item2 = store.cas.put(itemSchema, { name: "item2" }); const item3 = store.cas.put(itemSchema, { name: "item3" }); - const parentSchema = await putSchema(store, { + const parentSchema = putSchema(store, { type: "object", properties: { items: { @@ -302,9 +302,9 @@ describe("Suite 3: Complex Graph Structures", () => { test("3.2 Object with Multiple ocas_ref Fields", async () => { const store = createMemoryStore(); - await bootstrap(store); + bootstrap(store); - const childSchema = await putSchema(store, { + const childSchema = putSchema(store, { type: "object", properties: { value: { type: "string" }, @@ -314,7 +314,7 @@ describe("Suite 3: Complex Graph Structures", () => { const leftHash = store.cas.put(childSchema, { value: "left" }); const rightHash = store.cas.put(childSchema, { value: "right" }); - const parentSchema = await putSchema(store, { + const parentSchema = putSchema(store, { type: "object", properties: { left: { type: "string", format: "ocas_ref" }, @@ -341,9 +341,9 @@ describe("Suite 3: Complex Graph Structures", () => { test("3.3 Cycle Detection", async () => { const store = createMemoryStore(); - await bootstrap(store); + bootstrap(store); - const nodeSchema = await putSchema(store, { + const nodeSchema = putSchema(store, { type: "object", properties: { name: { type: "string" }, @@ -373,9 +373,9 @@ describe("Suite 3: Complex Graph Structures", () => { test("3.4 DAG (Shared Descendant)", async () => { const store = createMemoryStore(); - await bootstrap(store); + bootstrap(store); - const leafSchema = await putSchema(store, { + const leafSchema = putSchema(store, { type: "object", properties: { value: { type: "string" }, @@ -383,7 +383,7 @@ describe("Suite 3: Complex Graph Structures", () => { }); const sharedLeaf = store.cas.put(leafSchema, { value: "shared" }); - const branchSchema = await putSchema(store, { + const branchSchema = putSchema(store, { type: "object", properties: { name: { type: "string" }, @@ -399,7 +399,7 @@ describe("Suite 3: Complex Graph Structures", () => { child: sharedLeaf, }); - const rootSchema = await putSchema(store, { + const rootSchema = putSchema(store, { type: "object", properties: { left: { type: "string", format: "ocas_ref" }, @@ -424,9 +424,9 @@ describe("Suite 3: Complex Graph Structures", () => { test("3.5 Deep Tree", async () => { const store = createMemoryStore(); - await bootstrap(store); + bootstrap(store); - const nodeSchema = await putSchema(store, { + const nodeSchema = putSchema(store, { type: "object", properties: { value: { type: "number" }, @@ -465,8 +465,8 @@ describe("Suite 3: Complex Graph Structures", () => { describe("Suite 4: Epsilon Boundary Cases", () => { test("4.1 Resolution Exactly at Epsilon", async () => { const store = createMemoryStore(); - await bootstrap(store); - const textSchema = await putSchema(store, { type: "string" }); + bootstrap(store); + const textSchema = putSchema(store, { type: "string" }); const hash = store.cas.put(textSchema, "test"); const output = render(store, hash, { @@ -480,8 +480,8 @@ describe("Suite 4: Epsilon Boundary Cases", () => { test("4.2 Resolution Just Above Epsilon", async () => { const store = createMemoryStore(); - await bootstrap(store); - const textSchema = await putSchema(store, { type: "string" }); + bootstrap(store); + const textSchema = putSchema(store, { type: "string" }); const hash = store.cas.put(textSchema, "test"); const output = render(store, hash, { @@ -495,9 +495,9 @@ describe("Suite 4: Epsilon Boundary Cases", () => { test("4.3 Very Small Epsilon (Deep Expansion)", async () => { const store = createMemoryStore(); - await bootstrap(store); + bootstrap(store); - const nodeSchema = await putSchema(store, { + const nodeSchema = putSchema(store, { type: "object", properties: { level: { type: "number" }, @@ -530,9 +530,9 @@ describe("Suite 4: Epsilon Boundary Cases", () => { test("4.4 Zero Epsilon (Never Prune)", async () => { const store = createMemoryStore(); - await bootstrap(store); + bootstrap(store); - const nodeSchema = await putSchema(store, { + const nodeSchema = putSchema(store, { type: "object", properties: { level: { type: "number" }, @@ -567,8 +567,8 @@ describe("Suite 4: Epsilon Boundary Cases", () => { describe("Suite 5: YAML Output Format", () => { test("5.1 Valid YAML Syntax", async () => { const store = createMemoryStore(); - await bootstrap(store); - const objSchema = await putSchema(store, { + bootstrap(store); + const objSchema = putSchema(store, { type: "object", properties: { name: { type: "string" }, @@ -585,8 +585,8 @@ describe("Suite 5: YAML Output Format", () => { test("5.2 Nested Object Indentation", async () => { const store = createMemoryStore(); - await bootstrap(store); - const nestedSchema = await putSchema(store, { + bootstrap(store); + const nestedSchema = putSchema(store, { type: "object", properties: { outer: { @@ -611,8 +611,8 @@ describe("Suite 5: YAML Output Format", () => { test("5.3 Array Rendering", async () => { const store = createMemoryStore(); - await bootstrap(store); - const arraySchema = await putSchema(store, { + bootstrap(store); + const arraySchema = putSchema(store, { type: "array", items: { type: "number" }, }); @@ -626,9 +626,9 @@ describe("Suite 5: YAML Output Format", () => { test("5.4 CAS Reference in YAML", async () => { const store = createMemoryStore(); - await bootstrap(store); + bootstrap(store); - const childSchema = await putSchema(store, { + const childSchema = putSchema(store, { type: "object", properties: { value: { type: "string" }, @@ -636,7 +636,7 @@ describe("Suite 5: YAML Output Format", () => { }); const childHash = store.cas.put(childSchema, { value: "child" }); - const parentSchema = await putSchema(store, { + const parentSchema = putSchema(store, { type: "object", properties: { child: { type: "string", format: "ocas_ref" }, @@ -656,8 +656,8 @@ describe("Suite 5: YAML Output Format", () => { test("5.5 Special Characters Escaping", async () => { const store = createMemoryStore(); - await bootstrap(store); - const textSchema = await putSchema(store, { type: "string" }); + bootstrap(store); + const textSchema = putSchema(store, { type: "string" }); const hash = store.cas.put(textSchema, "line1\nline2: value"); const output = render(store, hash); @@ -668,8 +668,8 @@ describe("Suite 5: YAML Output Format", () => { test("5.6 Null Handling", async () => { const store = createMemoryStore(); - await bootstrap(store); - const nullableSchema = await putSchema(store, { + bootstrap(store); + const nullableSchema = putSchema(store, { type: "object", properties: { ref: { @@ -688,9 +688,9 @@ describe("Suite 5: YAML Output Format", () => { describe("Suite 6: Schema Integration", () => { test("6.1 Detect ocas_ref Fields via Schema", async () => { const store = createMemoryStore(); - await bootstrap(store); + bootstrap(store); - const childSchema = await putSchema(store, { + const childSchema = putSchema(store, { type: "object", properties: { value: { type: "string" }, @@ -698,7 +698,7 @@ describe("Suite 6: Schema Integration", () => { }); const childHash = store.cas.put(childSchema, { value: "child" }); - const parentSchema = await putSchema(store, { + const parentSchema = putSchema(store, { type: "object", properties: { link: { type: "string", format: "ocas_ref" }, @@ -717,8 +717,8 @@ describe("Suite 6: Schema Integration", () => { test("6.2 Non-ocas_ref String Not Expanded", async () => { const store = createMemoryStore(); - await bootstrap(store); - const objSchema = await putSchema(store, { + bootstrap(store); + const objSchema = putSchema(store, { type: "object", properties: { name: { type: "string" }, @@ -735,9 +735,9 @@ describe("Suite 6: Schema Integration", () => { test("6.3 Array of ocas_ref", async () => { const store = createMemoryStore(); - await bootstrap(store); + bootstrap(store); - const itemSchema = await putSchema(store, { + const itemSchema = putSchema(store, { type: "object", properties: { name: { type: "string" }, @@ -746,7 +746,7 @@ describe("Suite 6: Schema Integration", () => { const item1 = store.cas.put(itemSchema, { name: "item1" }); const item2 = store.cas.put(itemSchema, { name: "item2" }); - const arraySchema = await putSchema(store, { + const arraySchema = putSchema(store, { type: "array", items: { type: "string", format: "ocas_ref" }, }); @@ -764,9 +764,9 @@ describe("Suite 6: Schema Integration", () => { test("6.4 anyOf with ocas_ref (Nullable Reference)", async () => { const store = createMemoryStore(); - await bootstrap(store); + bootstrap(store); - const childSchema = await putSchema(store, { + const childSchema = putSchema(store, { type: "object", properties: { value: { type: "string" }, @@ -774,7 +774,7 @@ describe("Suite 6: Schema Integration", () => { }); const childHash = store.cas.put(childSchema, { value: "child" }); - const parentSchema = await putSchema(store, { + const parentSchema = putSchema(store, { type: "object", properties: { ref: { @@ -795,7 +795,7 @@ describe("Suite 6: Schema Integration", () => { test("6.5 Schema-less Node (Bootstrap Node)", async () => { const store = createMemoryStore(); - const types = await bootstrap(store); + const types = bootstrap(store); const schemaHash = types["@ocas/schema"]; const output = render(store, schemaHash); @@ -808,9 +808,9 @@ describe("Suite 6: Schema Integration", () => { describe("Suite 7: Error Handling", () => { test("7.1 Missing Referenced Node", async () => { const store = createMemoryStore(); - await bootstrap(store); + bootstrap(store); - const parentSchema = await putSchema(store, { + const parentSchema = putSchema(store, { type: "object", properties: { child: { type: "string", format: "ocas_ref" }, @@ -850,8 +850,8 @@ describe("Suite 7: Error Handling", () => { describe("Suite 8: Performance & Edge Cases", () => { test("8.1 Large Payload", async () => { const store = createMemoryStore(); - await bootstrap(store); - const arraySchema = await putSchema(store, { + bootstrap(store); + const arraySchema = putSchema(store, { type: "array", items: { type: "object", @@ -878,9 +878,9 @@ describe("Suite 8: Performance & Edge Cases", () => { test("8.2 Wide Fan-out", async () => { const store = createMemoryStore(); - await bootstrap(store); + bootstrap(store); - const itemSchema = await putSchema(store, { + const itemSchema = putSchema(store, { type: "object", properties: { value: { type: "number" }, @@ -893,7 +893,7 @@ describe("Suite 8: Performance & Edge Cases", () => { children.push(hash); } - const parentSchema = await putSchema(store, { + const parentSchema = putSchema(store, { type: "array", items: { type: "string", format: "ocas_ref" }, }); @@ -910,8 +910,8 @@ describe("Suite 8: Performance & Edge Cases", () => { test("8.3 Empty Payload", async () => { const store = createMemoryStore(); - await bootstrap(store); - const emptySchema = await putSchema(store, { type: "object" }); + bootstrap(store); + const emptySchema = putSchema(store, { type: "object" }); const hash = store.cas.put(emptySchema, {}); const output = render(store, hash); @@ -921,8 +921,8 @@ describe("Suite 8: Performance & Edge Cases", () => { test("8.4 Unicode in Payload", async () => { const store = createMemoryStore(); - await bootstrap(store); - const textSchema = await putSchema(store, { + bootstrap(store); + const textSchema = putSchema(store, { type: "object", properties: { text: { type: "string" }, @@ -986,17 +986,17 @@ describe("Suite 9: renderDirect (in-memory rendering)", () => { test("9.5 Render with store expands ocas_ref fields", async () => { const store = createMemoryStore(); - await bootstrap(store); + bootstrap(store); // Create a child node - const childSchema = await putSchema(store, { + const childSchema = putSchema(store, { type: "object", properties: { msg: { type: "string" } }, }); const childHash = store.cas.put(childSchema, { msg: "inner" }); // Parent schema with ocas_ref - const parentSchema = await putSchema(store, { + const parentSchema = putSchema(store, { type: "object", properties: { child: { type: "string", format: "ocas_ref" }, @@ -1052,7 +1052,7 @@ describe("Suite 9: renderDirect (in-memory rendering)", () => { test("9.10 store present but schema missing — renders without ref expansion", async () => { const store = createMemoryStore(); - await bootstrap(store); + bootstrap(store); const unknownType = "ZZZZZZZZZZZZ0" as Hash; const output = renderDirect(unknownType, { key: "val" }, store, null); expect(output).toContain("key: val"); @@ -1062,7 +1062,7 @@ describe("Suite 9: renderDirect (in-memory rendering)", () => { describe("Suite 10: Missing Root Hash Error Handling (Issue #53)", () => { test("10.1 renderAsync() throws CasNodeNotFoundError for missing root hash", async () => { const store = createMemoryStore(); - await bootstrap(store); + bootstrap(store); const fakeHash = "AAAAAAAAAAAAA" as Hash; await expect(renderAsync(store, fakeHash)).rejects.toThrow( @@ -1093,9 +1093,9 @@ describe("Suite 10: Missing Root Hash Error Handling (Issue #53)", () => { test("10.4 Missing nested node renders as cas: reference (no error)", async () => { const store = createMemoryStore(); - await bootstrap(store); + bootstrap(store); - const parentSchema = await putSchema(store, { + const parentSchema = putSchema(store, { type: "object", properties: { title: { type: "string" }, @@ -1117,9 +1117,9 @@ describe("Suite 10: Missing Root Hash Error Handling (Issue #53)", () => { test("10.5 Resolution below epsilon renders as cas: reference (no error)", async () => { const store = createMemoryStore(); - await bootstrap(store); + bootstrap(store); - const nodeSchema = await putSchema(store, { + const nodeSchema = putSchema(store, { type: "object", properties: { level: { type: "number" }, diff --git a/packages/core/src/render.ts b/packages/core/src/render.ts index e71513c..c319d5d 100644 --- a/packages/core/src/render.ts +++ b/packages/core/src/render.ts @@ -1,7 +1,7 @@ import { CasNodeNotFoundError } from "./errors.js"; import { renderWithTemplate } from "./liquid-render.js"; import { collectRefs, getSchema, putSchema, refs } from "./schema.js"; -import type { Hash, OcasStore } from "./types.js"; +import type { Hash, Store } from "./types.js"; export type RenderOptions = { resolution?: number; // (0, 1], default 1.0 @@ -47,7 +47,7 @@ function validateAndExtractOptions(options: RenderOptions | null | undefined): { * For template support, use renderAsync(). */ export function render( - store: OcasStore, + store: Store, hash: Hash, options?: RenderOptions, ): string { @@ -68,7 +68,7 @@ export function render( * Attempts to use LiquidJS templates first, falling back to YAML. */ export async function renderAsync( - store: OcasStore, + store: Store, hash: Hash, options?: RenderOptions, ): Promise { @@ -111,7 +111,7 @@ export async function renderAsync( export function renderDirect( typeHash: Hash, value: unknown, - store: OcasStore | null, + store: Store | null, options: RenderOptions | null, ): string { const { resolution, decay, epsilon } = validateAndExtractOptions(options); @@ -142,10 +142,10 @@ export function renderDirect( /** * Check if a template exists for a given type */ -async function hasTemplate(store: OcasStore, typeHash: Hash): Promise { +async function hasTemplate(store: Store, typeHash: Hash): Promise { const varName = `@ocas/template/text/${typeHash}`; try { - const stringSchema = await putSchema(store, { type: "string" }); + const stringSchema = putSchema(store, { type: "string" }); const variable = store.var.get(varName, stringSchema); return variable !== null; } catch { @@ -154,7 +154,7 @@ async function hasTemplate(store: OcasStore, typeHash: Hash): Promise { } function renderNode( - store: OcasStore | null, + store: Store | null, hash: Hash, currentResolution: number, decay: number, @@ -203,7 +203,7 @@ function renderNode( } function renderValue( - store: OcasStore | null, + store: Store | null, value: unknown, refHashes: Set, childResolution: number, diff --git a/packages/core/src/schema.test.ts b/packages/core/src/schema.test.ts index 893ef83..b9fbe58 100644 --- a/packages/core/src/schema.test.ts +++ b/packages/core/src/schema.test.ts @@ -11,7 +11,7 @@ import type { CasNode } from "./types.js"; describe("putSchema", () => { test("returns a valid 13-char hash", async () => { const store = createMemoryStore(); - const hash = await putSchema(store, { type: "object", properties: {} }); + const hash = putSchema(store, { type: "object", properties: {} }); expect(hash).toHaveLength(13); expect(hash).toMatch(/^[0-9A-HJKMNP-TV-Z]{13}$/); }); @@ -19,7 +19,7 @@ describe("putSchema", () => { test("schema node is stored in the store", async () => { const store = createMemoryStore(); const schema = { type: "object", properties: { name: { type: "string" } } }; - const hash = await putSchema(store, schema); + const hash = putSchema(store, schema); expect(store.cas.has(hash)).toBe(true); const node = store.cas.get(hash); @@ -29,9 +29,9 @@ describe("putSchema", () => { test("schema node type equals the meta-schema hash", async () => { const store = createMemoryStore(); - const builtinSchemas = await bootstrap(store); + const builtinSchemas = bootstrap(store); const metaHash = builtinSchemas["@ocas/schema"] ?? ""; - const schemaHash = await putSchema(store, { type: "string" }); + const schemaHash = putSchema(store, { type: "string" }); const node = store.cas.get(schemaHash) as CasNode; expect(node.type).toBe(metaHash); @@ -40,16 +40,16 @@ describe("putSchema", () => { test("putSchema is idempotent: same schema → same hash", async () => { const store = createMemoryStore(); const schema = { type: "number" }; - const h1 = await putSchema(store, schema); - const h2 = await putSchema(store, schema); + const h1 = putSchema(store, schema); + const h2 = putSchema(store, schema); expect(h1).toBe(h2); }); test("different schemas produce different hashes", async () => { const store = createMemoryStore(); - const h1 = await putSchema(store, { type: "string" }); - const h2 = await putSchema(store, { type: "number" }); + const h1 = putSchema(store, { type: "string" }); + const h2 = putSchema(store, { type: "number" }); expect(h1).not.toBe(h2); }); @@ -62,7 +62,7 @@ describe("getSchema", () => { test("returns the original schema object", async () => { const store = createMemoryStore(); const schema = { type: "object", properties: { age: { type: "number" } } }; - const hash = await putSchema(store, schema); + const hash = putSchema(store, schema); expect(getSchema(store, hash)).toEqual(schema); }); @@ -82,7 +82,7 @@ describe("getSchema", () => { label: { type: "string" }, }, }; - const hash = await putSchema(store, schema); + const hash = putSchema(store, schema); expect(getSchema(store, hash)).toEqual(schema); }); }); @@ -93,7 +93,7 @@ describe("getSchema", () => { describe("validate", () => { test("returns true when payload matches the schema", async () => { const store = createMemoryStore(); - const schemaHash = await putSchema(store, { + const schemaHash = putSchema(store, { type: "object", properties: { name: { type: "string" }, age: { type: "number" } }, required: ["name"], @@ -106,7 +106,7 @@ describe("validate", () => { test("returns false when payload violates the schema", async () => { const store = createMemoryStore(); - const schemaHash = await putSchema(store, { + const schemaHash = putSchema(store, { type: "object", properties: { count: { type: "number" } }, required: ["count"], @@ -119,7 +119,7 @@ describe("validate", () => { test("returns false when required field is missing", async () => { const store = createMemoryStore(); - const schemaHash = await putSchema(store, { + const schemaHash = putSchema(store, { type: "object", required: ["title"], properties: { title: { type: "string" } }, @@ -148,7 +148,7 @@ describe("validate", () => { describe("refs", () => { test("returns empty array when schema has no ocas_ref fields", async () => { const store = createMemoryStore(); - const schemaHash = await putSchema(store, { + const schemaHash = putSchema(store, { type: "object", properties: { title: { type: "string" } }, }); @@ -160,7 +160,7 @@ describe("refs", () => { test("returns the ocas_ref hash values from payload", async () => { const store = createMemoryStore(); - const schemaHash = await putSchema(store, { + const schemaHash = putSchema(store, { type: "object", properties: { parentHash: { type: "string", format: "ocas_ref" }, @@ -180,7 +180,7 @@ describe("refs", () => { test("collects multiple ocas_ref fields", async () => { const store = createMemoryStore(); - const schemaHash = await putSchema(store, { + const schemaHash = putSchema(store, { type: "object", properties: { leftHash: { type: "string", format: "ocas_ref" }, @@ -204,7 +204,7 @@ describe("refs", () => { test("skips null/undefined ocas_ref values", async () => { const store = createMemoryStore(); - const schemaHash = await putSchema(store, { + const schemaHash = putSchema(store, { type: "object", properties: { optionalRef: { type: "string", format: "ocas_ref" }, @@ -236,7 +236,7 @@ describe("refs", () => { describe("walk", () => { test("visits a single node with no refs", async () => { const store = createMemoryStore(); - const schemaHash = await putSchema(store, { + const schemaHash = putSchema(store, { type: "object", properties: { val: { type: "number" } }, }); @@ -250,7 +250,7 @@ describe("walk", () => { test("visits all reachable nodes in a chain A → B → C", async () => { const store = createMemoryStore(); - const schemaHash = await putSchema(store, { + const schemaHash = putSchema(store, { type: "object", properties: { nextHash: { type: "string", format: "ocas_ref" }, @@ -274,7 +274,7 @@ describe("walk", () => { test("handles cycles without infinite loop", async () => { const store = createMemoryStore(); - const schemaHash = await putSchema(store, { + const schemaHash = putSchema(store, { type: "object", properties: { peerHash: { type: "string", format: "ocas_ref" }, @@ -292,7 +292,7 @@ describe("walk", () => { const hashD = store.cas.put(schemaHash, { peerHash: hashC, val: 4 }); const hashE = store.cas.put(schemaHash, { peerHash: hashC, val: 5 }); - const schemaHash2 = await putSchema(store, { + const schemaHash2 = putSchema(store, { type: "object", properties: { leftHash: { type: "string", format: "ocas_ref" }, @@ -317,7 +317,7 @@ describe("walk", () => { test("skips missing hashes gracefully", async () => { const store = createMemoryStore(); - const schemaHash = await putSchema(store, { + const schemaHash = putSchema(store, { type: "object", properties: { ref: { type: "string", format: "ocas_ref" } }, }); @@ -332,7 +332,7 @@ describe("walk", () => { test("visitor receives both hash and node", async () => { const store = createMemoryStore(); - const schemaHash = await putSchema(store, { + const schemaHash = putSchema(store, { type: "object", properties: { x: { type: "number" } }, }); @@ -356,7 +356,7 @@ describe("walk", () => { describe("bootstrap meta-schema self-reference", () => { test("metaNode.type === metaHash (self-referencing)", async () => { const store = createMemoryStore(); - const builtinSchemas = await bootstrap(store); + const builtinSchemas = bootstrap(store); const metaHash = builtinSchemas["@ocas/schema"] ?? ""; const metaNode = store.cas.get(metaHash) as CasNode; @@ -365,9 +365,9 @@ describe("bootstrap meta-schema self-reference", () => { test("schema nodes have type === metaHash", async () => { const store = createMemoryStore(); - const builtinSchemas = await bootstrap(store); + const builtinSchemas = bootstrap(store); const metaHash = builtinSchemas["@ocas/schema"] ?? ""; - const schemaHash = await putSchema(store, { type: "string" }); + const schemaHash = putSchema(store, { type: "string" }); const schemaNode = store.cas.get(schemaHash) as CasNode; expect(schemaNode.type).toBe(metaHash); @@ -375,9 +375,9 @@ describe("bootstrap meta-schema self-reference", () => { test("data nodes have type === schemaHash (not metaHash)", async () => { const store = createMemoryStore(); - const builtinSchemas = await bootstrap(store); + const builtinSchemas = bootstrap(store); const metaHash = builtinSchemas["@ocas/schema"] ?? ""; - const schemaHash = await putSchema(store, { + const schemaHash = putSchema(store, { type: "object", properties: { val: { type: "number" } }, }); @@ -392,7 +392,7 @@ describe("bootstrap meta-schema self-reference", () => { test("accepts schema with numeric constraints (minimum/maximum)", async () => { const store = createMemoryStore(); - const hash = await putSchema(store, { + const hash = putSchema(store, { type: "number", minimum: 0, maximum: 100, @@ -414,7 +414,7 @@ describe("bootstrap meta-schema self-reference", () => { test("accepts schema with string constraints (minLength/maxLength/pattern)", async () => { const store = createMemoryStore(); - const hash = await putSchema(store, { + const hash = putSchema(store, { type: "string", minLength: 1, maxLength: 10, @@ -431,7 +431,7 @@ describe("bootstrap meta-schema self-reference", () => { test("accepts schema with array constraints (minItems/maxItems/uniqueItems)", async () => { const store = createMemoryStore(); - const hash = await putSchema(store, { + const hash = putSchema(store, { type: "array", items: { type: "number" }, minItems: 1, @@ -452,20 +452,20 @@ describe("bootstrap meta-schema self-reference", () => { test("rejects schema with wrong constraint types", async () => { const store = createMemoryStore(); - await expect( + expect(() => putSchema(store, { type: "number", minimum: "zero" } as never), - ).rejects.toThrow(); - await expect( + ).toThrow(); + expect(() => putSchema(store, { type: "string", maxLength: true } as never), - ).rejects.toThrow(); - await expect( + ).toThrow(); + expect(() => putSchema(store, { type: "array", uniqueItems: 1 } as never), - ).rejects.toThrow(); + ).toThrow(); }); test("accepts schema with nested property constraints", async () => { const store = createMemoryStore(); - const hash = await putSchema(store, { + const hash = putSchema(store, { type: "object", properties: { name: { type: "string", minLength: 1, maxLength: 50 }, @@ -491,11 +491,11 @@ describe("bootstrap meta-schema self-reference", () => { test("bootstrap is idempotent across putSchema calls", async () => { const store = createMemoryStore(); - const builtinSchemas = await bootstrap(store); + const builtinSchemas = bootstrap(store); const metaHash = builtinSchemas["@ocas/schema"] ?? ""; - await putSchema(store, { type: "string" }); - await putSchema(store, { type: "number" }); + putSchema(store, { type: "string" }); + putSchema(store, { type: "number" }); // bootstrap node should still be there and unchanged const metaNode = store.cas.get(metaHash) as CasNode; @@ -506,7 +506,7 @@ describe("bootstrap meta-schema self-reference", () => { test("accepts schema with allOf", async () => { const store = createMemoryStore(); - const hash = await putSchema(store, { + const hash = putSchema(store, { allOf: [ { type: "object", properties: { name: { type: "string" } } }, { required: ["name"] }, @@ -523,7 +523,7 @@ describe("bootstrap meta-schema self-reference", () => { test("accepts schema with if/then/else", async () => { const store = createMemoryStore(); - const hash = await putSchema(store, { + const hash = putSchema(store, { type: "object", properties: { kind: { type: "string" }, @@ -539,7 +539,7 @@ describe("bootstrap meta-schema self-reference", () => { test("accepts schema with patternProperties", async () => { const store = createMemoryStore(); - const hash = await putSchema(store, { + const hash = putSchema(store, { type: "object", patternProperties: { "^x-": { type: "string" }, @@ -553,7 +553,7 @@ describe("bootstrap meta-schema self-reference", () => { test("accepts schema with prefixItems (tuple)", async () => { const store = createMemoryStore(); - const hash = await putSchema(store, { + const hash = putSchema(store, { type: "array", prefixItems: [{ type: "string" }, { type: "number" }], }); @@ -562,7 +562,7 @@ describe("bootstrap meta-schema self-reference", () => { test("accepts schema with multipleOf", async () => { const store = createMemoryStore(); - const hash = await putSchema(store, { + const hash = putSchema(store, { type: "number", multipleOf: 5, }); @@ -577,7 +577,7 @@ describe("bootstrap meta-schema self-reference", () => { test("accepts schema with minProperties/maxProperties", async () => { const store = createMemoryStore(); - const hash = await putSchema(store, { + const hash = putSchema(store, { type: "object", minProperties: 1, maxProperties: 3, @@ -593,7 +593,7 @@ describe("bootstrap meta-schema self-reference", () => { test("accepts schema with default value", async () => { const store = createMemoryStore(); - const hash = await putSchema(store, { + const hash = putSchema(store, { type: "string", default: "hello", }); @@ -602,21 +602,17 @@ describe("bootstrap meta-schema self-reference", () => { test("rejects invalid P2 keyword types", async () => { const store = createMemoryStore(); - await expect( - putSchema(store, { allOf: "not-array" } as never), - ).rejects.toThrow(); - await expect( - putSchema(store, { multipleOf: "five" } as never), - ).rejects.toThrow(); - await expect( + expect(() => putSchema(store, { allOf: "not-array" } as never)).toThrow(); + expect(() => putSchema(store, { multipleOf: "five" } as never)).toThrow(); + expect(() => putSchema(store, { patternProperties: [1, 2] } as never), - ).rejects.toThrow(); + ).toThrow(); }); test("collectRefs traverses allOf sub-schemas", async () => { const store = createMemoryStore(); - const innerSchema = await putSchema(store, { type: "string" }); - const schema = await putSchema(store, { + const innerSchema = putSchema(store, { type: "string" }); + const schema = putSchema(store, { allOf: [ { type: "object", @@ -634,8 +630,8 @@ describe("bootstrap meta-schema self-reference", () => { test("collectRefs traverses patternProperties", async () => { const store = createMemoryStore(); - const innerSchema = await putSchema(store, { type: "string" }); - const schema = await putSchema(store, { + const innerSchema = putSchema(store, { type: "string" }); + const schema = putSchema(store, { type: "object", patternProperties: { "^ref_": { type: "string", format: "ocas_ref" }, @@ -651,8 +647,8 @@ describe("bootstrap meta-schema self-reference", () => { test("collectRefs traverses prefixItems", async () => { const store = createMemoryStore(); - const innerSchema = await putSchema(store, { type: "string" }); - const schema = await putSchema(store, { + const innerSchema = putSchema(store, { type: "string" }); + const schema = putSchema(store, { type: "array", prefixItems: [{ type: "string", format: "ocas_ref" }, { type: "number" }], }); @@ -668,7 +664,7 @@ describe("bootstrap meta-schema self-reference", () => { test("accepts schema with not", async () => { const store = createMemoryStore(); - const hash = await putSchema(store, { + const hash = putSchema(store, { not: { type: "string" }, }); expect(hash).toHaveLength(13); @@ -682,7 +678,7 @@ describe("bootstrap meta-schema self-reference", () => { test("accepts schema with contains", async () => { const store = createMemoryStore(); - const hash = await putSchema(store, { + const hash = putSchema(store, { type: "array", contains: { type: "number", minimum: 10 }, }); @@ -697,7 +693,7 @@ describe("bootstrap meta-schema self-reference", () => { test("accepts schema with propertyNames", async () => { const store = createMemoryStore(); - const hash = await putSchema(store, { + const hash = putSchema(store, { type: "object", propertyNames: { pattern: "^[a-z]+$" }, }); @@ -712,7 +708,7 @@ describe("bootstrap meta-schema self-reference", () => { test("accepts schema with metadata keywords", async () => { const store = createMemoryStore(); - const hash = await putSchema(store, { + const hash = putSchema(store, { type: "string", examples: ["hello", "world"], readOnly: true, @@ -724,22 +720,18 @@ describe("bootstrap meta-schema self-reference", () => { test("rejects invalid P3 keyword types", async () => { const store = createMemoryStore(); - await expect( - putSchema(store, { not: "not-object" } as never), - ).rejects.toThrow(); - await expect( + expect(() => putSchema(store, { not: "not-object" } as never)).toThrow(); + expect(() => putSchema(store, { examples: "not-array" } as never), - ).rejects.toThrow(); - await expect( - putSchema(store, { readOnly: "yes" } as never), - ).rejects.toThrow(); - await expect(putSchema(store, { $comment: 42 } as never)).rejects.toThrow(); + ).toThrow(); + expect(() => putSchema(store, { readOnly: "yes" } as never)).toThrow(); + expect(() => putSchema(store, { $comment: 42 } as never)).toThrow(); }); test("collectRefs traverses contains", async () => { const store = createMemoryStore(); - const innerSchema = await putSchema(store, { type: "string" }); - const schema = await putSchema(store, { + const innerSchema = putSchema(store, { type: "string" }); + const schema = putSchema(store, { type: "array", contains: { type: "string", format: "ocas_ref" }, }); diff --git a/packages/core/src/schema.ts b/packages/core/src/schema.ts index 32aca2b..63d69d3 100644 --- a/packages/core/src/schema.ts +++ b/packages/core/src/schema.ts @@ -11,7 +11,7 @@ const Ajv = ((AjvModule as any).default ?? AjvModule) as { }; import { bootstrap } from "./bootstrap.js"; -import type { CasNode, Hash, OcasStore } from "./types.js"; +import type { CasNode, Hash, Store } from "./types.js"; export type JSONSchema = Record; @@ -239,7 +239,7 @@ function isValidSchema(value: unknown): boolean { return true; } -function isMetaSchemaNode(store: OcasStore, node: CasNode): boolean { +function isMetaSchemaNode(store: Store, node: CasNode): boolean { const schema = getSchema(store, node.type); return schema !== null && schema === node.payload; } @@ -248,11 +248,8 @@ function isMetaSchemaNode(store: OcasStore, node: CasNode): boolean { * Store a JSON Schema as a CAS node typed by the meta-schema hash. * The returned hash becomes the typeHash for nodes that conform to this schema. */ -export async function putSchema( - store: OcasStore, - jsonSchema: JSONSchema, -): Promise { - const builtinSchemas = await bootstrap(store); +export function putSchema(store: Store, jsonSchema: JSONSchema): Hash { + const builtinSchemas = bootstrap(store); const metaHash = builtinSchemas["@ocas/schema"]; if (!metaHash) { throw new Error("Meta-schema not found in bootstrap result"); @@ -262,14 +259,14 @@ export async function putSchema( "Invalid schema: input does not conform to the ocas JSON Schema meta-schema", ); } - return Promise.resolve(store.cas.put(metaHash, jsonSchema)); + return store.cas.put(metaHash, jsonSchema); } /** * Retrieve the JSON Schema payload for a given type hash. * Returns null if no node exists at that hash. */ -export function getSchema(store: OcasStore, typeHash: Hash): JSONSchema | null { +export function getSchema(store: Store, typeHash: Hash): JSONSchema | null { const node = store.cas.get(typeHash); if (node === null) return null; return node.payload as JSONSchema; @@ -279,7 +276,7 @@ export function getSchema(store: OcasStore, typeHash: Hash): JSONSchema | null { * Validate a node's payload against the schema identified by node.type. * Returns false if the schema cannot be found or validation fails. */ -export function validate(store: OcasStore, node: CasNode): boolean { +export function validate(store: Store, node: CasNode): boolean { const schema = getSchema(store, node.type); if (schema === null) return false; if (isMetaSchemaNode(store, node)) { @@ -416,7 +413,7 @@ export function collectRefs(schema: JSONSchema, value: unknown): Hash[] { * Return all hashes referenced by this node via ocas_ref fields in its schema. * Null/undefined values are skipped. */ -export function refs(store: OcasStore, node: CasNode): Hash[] { +export function refs(store: Store, node: CasNode): Hash[] { const schema = getSchema(store, node.type); if (schema === null) return []; return collectRefs(schema, node.payload); @@ -428,7 +425,7 @@ export function refs(store: OcasStore, node: CasNode): Hash[] { * Handles cycles via a visited set. */ export function walk( - store: OcasStore, + store: Store, rootHash: Hash, visitor: (hash: Hash, node: CasNode) => void, ): void { diff --git a/packages/core/src/store.ts b/packages/core/src/store.ts index bb8cad9..98f6738 100644 --- a/packages/core/src/store.ts +++ b/packages/core/src/store.ts @@ -2,13 +2,7 @@ import { BOOTSTRAP_STORE, type BootstrapCapableStore, } from "./bootstrap-capable.js"; -import { - CasNodeNotFoundError, - MAX_HISTORY, - SchemaMismatchError, - TagLabelConflictError, - VariableNotFoundError, -} from "./errors.js"; +import { SchemaMismatchError, VariableNotFoundError } from "./errors.js"; import { computeHashSync, computeSelfHashSync, initHasher } from "./hash.js"; import { applyListOptions, casListEntry } from "./list-utils.js"; import type { @@ -18,7 +12,7 @@ import type { HistoryEntry, ListEntry, ListOptions, - OcasStore, + Store, Tag, TagOp, TagStore, @@ -27,6 +21,16 @@ import type { VarStore, } from "./types.js"; import { validateName } from "./validation.js"; +import { + addNameIndex, + checkTagLabelConflict, + cloneVarRecord, + extractSchema, + pushHistory, + removeNameIndex, + type VarRecord, + varKey, +} from "./var-store-helpers.js"; import type { Variable } from "./variable.js"; // Initialise the xxhash WASM instance once at module load. This allows the @@ -34,7 +38,7 @@ import type { Variable } from "./variable.js"; await initHasher(); /** - * The cas sub-store of an in-memory `OcasStore` — also satisfies the legacy + * The cas sub-store of an in-memory `Store` — also satisfies the legacy * `BootstrapCapableStore` interface so that helpers that have not yet been * refactored (e.g. bootstrap, gc, render) continue to work against * `store.cas`. @@ -142,32 +146,9 @@ function createCasStore(): MemoryCasStore { return store; } -type VarRecord = { - name: string; - schema: Hash; - value: Hash; - created: number; - updated: number; - tags: Record; - labels: string[]; - history: HistoryEntry[]; -}; - -function cloneVar(rec: VarRecord): Variable { - return { - name: rec.name, - schema: rec.schema, - value: rec.value, - created: rec.created, - updated: rec.updated, - tags: { ...rec.tags }, - labels: [...rec.labels], - }; -} - /** * Build an in-memory `VarStore` backed by the supplied CAS store. Exposed so - * non-Memory CAS stores (e.g. the FS store) can compose a full `OcasStore` + * non-Memory CAS stores (e.g. the FS store) can compose a full `Store` * without re-implementing variable storage. */ export function createMemoryVarStoreFor(cas: CasStore): VarStore { @@ -175,73 +156,18 @@ export function createMemoryVarStoreFor(cas: CasStore): VarStore { const records = new Map(); const byName = new Map>(); // name -> set of composite keys - function key(name: string, schema: Hash): string { - return `${name}\u0000${schema}`; - } - - function addIndex(name: string, k: string): void { - let set = byName.get(name); - if (!set) { - set = new Set(); - byName.set(name, set); - } - set.add(k); - } - - function removeIndex(name: string, k: string): void { - const set = byName.get(name); - if (!set) return; - set.delete(k); - if (set.size === 0) byName.delete(name); - } - - function extractSchema(hash: Hash): Hash { - const node = cas.get(hash); - if (node === null) { - throw new CasNodeNotFoundError(hash); - } - return node.type; - } - - function checkConflict(tags: Record, labels: string[]): void { - for (const tk of Object.keys(tags)) { - if (labels.includes(tk)) { - throw new TagLabelConflictError(tk, "label", "tag"); - } - } - } - - function pushHistory(rec: VarRecord, value: Hash, now: number): boolean { - if (rec.history.length > 0 && rec.history[0]?.value === value) { - return false; - } - const existingIdx = rec.history.findIndex((e) => e.value === value); - if (existingIdx > 0) { - rec.history.splice(existingIdx, 1); - } - rec.history.unshift({ value, position: 0, setAt: now }); - if (rec.history.length > MAX_HISTORY) { - rec.history.length = MAX_HISTORY; - } - for (let i = 0; i < rec.history.length; i++) { - const entry = rec.history[i]; - if (entry !== undefined) entry.position = i; - } - return true; - } - const varStore: VarStore = { set(name: string, hash: Hash, options?: VarSetOptions): Variable { validateName(name); - const schema = extractSchema(hash); - const k = key(name, schema); + const schema = extractSchema(cas, hash); + const k = varKey(name, schema); const existing = records.get(k); const now = Date.now(); if (existing) { const tags = options?.tags ?? existing.tags; const labels = options?.labels ?? existing.labels; - if (options !== undefined) checkConflict(tags, labels); + if (options !== undefined) checkTagLabelConflict(tags, labels); const changed = pushHistory(existing, hash, now); if (changed) { existing.value = hash; @@ -251,12 +177,12 @@ export function createMemoryVarStoreFor(cas: CasStore): VarStore { existing.tags = { ...tags }; existing.labels = [...labels]; } - return cloneVar(existing); + return cloneVarRecord(existing); } const tags = options?.tags ?? {}; const labels = options?.labels ?? []; - checkConflict(tags, labels); + checkTagLabelConflict(tags, labels); const rec: VarRecord = { name, schema, @@ -268,14 +194,14 @@ export function createMemoryVarStoreFor(cas: CasStore): VarStore { history: [{ value: hash, position: 0, setAt: now }], }; records.set(k, rec); - addIndex(name, k); - return cloneVar(rec); + addNameIndex(byName, name, k); + return cloneVarRecord(rec); }, get(name: string, schema?: Hash): Variable | null { if (schema !== undefined) { - const rec = records.get(key(name, schema)); - return rec ? cloneVar(rec) : null; + const rec = records.get(varKey(name, schema)); + return rec ? cloneVarRecord(rec) : null; } // No schema: if exactly one variant, return it; otherwise null const set = byName.get(name); @@ -283,17 +209,17 @@ export function createMemoryVarStoreFor(cas: CasStore): VarStore { const onlyKey = set.values().next().value; if (onlyKey === undefined) return null; const rec = records.get(onlyKey); - return rec ? cloneVar(rec) : null; + return rec ? cloneVarRecord(rec) : null; }, remove(name: string, schema?: Hash): Variable[] { if (schema !== undefined) { - const k = key(name, schema); + const k = varKey(name, schema); const rec = records.get(k); if (!rec) return []; records.delete(k); - removeIndex(name, k); - return [cloneVar(rec)]; + removeNameIndex(byName, name, k); + return [cloneVarRecord(rec)]; } const set = byName.get(name); if (!set) return []; @@ -301,7 +227,7 @@ export function createMemoryVarStoreFor(cas: CasStore): VarStore { for (const k of [...set]) { const rec = records.get(k); if (rec) { - removed.push(cloneVar(rec)); + removed.push(cloneVarRecord(rec)); records.delete(k); } } @@ -311,14 +237,14 @@ export function createMemoryVarStoreFor(cas: CasStore): VarStore { update(name: string, hash: Hash, options?: VarSetOptions): Variable { validateName(name); - const newSchema = extractSchema(hash); + const newSchema = extractSchema(cas, hash); // Find existing record by name; require existing schema match new schema const set = byName.get(name); if (!set || set.size === 0) { throw new VariableNotFoundError(name, newSchema); } // find a record matching newSchema - const k = key(name, newSchema); + const k = varKey(name, newSchema); const existing = records.get(k); if (!existing) { // Find any existing — schema mismatch @@ -333,7 +259,7 @@ export function createMemoryVarStoreFor(cas: CasStore): VarStore { const now = Date.now(); const tags = options?.tags ?? existing.tags; const labels = options?.labels ?? existing.labels; - if (options !== undefined) checkConflict(tags, labels); + if (options !== undefined) checkTagLabelConflict(tags, labels); const changed = pushHistory(existing, hash, now); if (changed) { existing.value = hash; @@ -343,7 +269,7 @@ export function createMemoryVarStoreFor(cas: CasStore): VarStore { existing.tags = { ...tags }; existing.labels = [...labels]; } - return cloneVar(existing); + return cloneVarRecord(existing); }, list(options?: VarListOptions): Variable[] { @@ -400,12 +326,12 @@ export function createMemoryVarStoreFor(cas: CasStore): VarStore { if (offset > 0) results = results.slice(offset); if (limit !== undefined) results = results.slice(0, limit); - return results.map(cloneVar); + return results.map(cloneVarRecord); }, history(name: string, schema?: Hash): HistoryEntry[] { if (schema !== undefined) { - const rec = records.get(key(name, schema)); + const rec = records.get(varKey(name, schema)); return rec ? rec.history.map((e) => ({ ...e })) : []; } const set = byName.get(name); @@ -535,14 +461,14 @@ export function createMemoryTagStoreImpl(): TagStore { } /** - * Create an in-memory `OcasStore` with three sub-stores: `cas`, `var`, `tag`. + * Create an in-memory `Store` with three sub-stores: `cas`, `var`, `tag`. * * The `cas` sub-store also satisfies the legacy `BootstrapCapableStore` * contract — it carries a `[BOOTSTRAP_STORE]` callable and a `listAll()` * helper — so existing helpers (`bootstrap`, `gc`, `render`, …) can be * called with `store.cas` until they are migrated to the unified surface. */ -export function createMemoryStore(): OcasStore & { +export function createMemoryStore(): Store & { cas: MemoryCasStore; } { const cas = createCasStore(); diff --git a/packages/core/src/types-store.test.ts b/packages/core/src/types-store.test.ts index 51dfe2d..12ebe16 100644 --- a/packages/core/src/types-store.test.ts +++ b/packages/core/src/types-store.test.ts @@ -8,7 +8,7 @@ import type { HistoryEntry, ListEntry, ListOptions, - OcasStore, + Store, Tag, TagOp, TagStore, @@ -120,11 +120,11 @@ describe("TagStore type", () => { }); }); -describe("Aggregate OcasStore type", () => { +describe("Aggregate Store type", () => { test("has cas, var, tag fields", () => { - type _AssertCas = OcasStore["cas"] extends CasStore ? true : false; - type _AssertVar = OcasStore["var"] extends VarStore ? true : false; - type _AssertTag = OcasStore["tag"] extends TagStore ? true : false; + type _AssertCas = Store["cas"] extends CasStore ? true : false; + type _AssertVar = Store["var"] extends VarStore ? true : false; + type _AssertTag = Store["tag"] extends TagStore ? true : false; const a: _AssertCas = true; const b: _AssertVar = true; const c: _AssertTag = true; @@ -147,7 +147,7 @@ describe("Exports surface", () => { type _C = CasStore extends object ? true : never; type _V = VarStore extends object ? true : never; type _T = TagStore extends object ? true : never; - type _O = OcasStore extends object ? true : never; + type _O = Store extends object ? true : never; const _checks: [_C, _V, _T, _O] = [true, true, true, true]; expect(_checks.length).toBe(4); }); diff --git a/packages/core/src/types.ts b/packages/core/src/types.ts index a752ff4..a9d3ef3 100644 --- a/packages/core/src/types.ts +++ b/packages/core/src/types.ts @@ -47,23 +47,7 @@ export type ListEntry = { }; /** - * Content-addressable store interface. - * Self-referencing nodes are created only via bootstrap(). - */ -export type Store = { - put(typeHash: Hash, payload: unknown): Hash | Promise; - get(hash: Hash): CasNode | null; - has(hash: Hash): boolean; - listByType(typeHash: Hash, options?: ListOptions): ListEntry[]; - listAll(): Hash[]; - listMeta(options?: ListOptions): ListEntry[]; - listSchemas(options?: ListOptions): ListEntry[]; - delete(hash: Hash): void; -}; - -/** - * Synchronous content-addressable store interface (new unified design). - * Unlike legacy `Store`, `put` returns the hash synchronously. + * Synchronous content-addressable store interface. */ export type CasStore = { get(hash: Hash): CasNode | null; @@ -149,9 +133,8 @@ export type TagStore = { /** * Aggregate OCAS store: bundles CAS, variable, and tag stores. - * Named `OcasStore` to avoid colliding with the legacy `Store` export. */ -export type OcasStore = { +export type Store = { cas: CasStore; var: VarStore; tag: TagStore; diff --git a/packages/core/src/var-store-helpers.ts b/packages/core/src/var-store-helpers.ts new file mode 100644 index 0000000..65cfb06 --- /dev/null +++ b/packages/core/src/var-store-helpers.ts @@ -0,0 +1,103 @@ +import { + CasNodeNotFoundError, + MAX_HISTORY, + TagLabelConflictError, +} from "./errors.js"; +import type { CasStore, Hash, HistoryEntry } from "./types.js"; +import type { Variable } from "./variable.js"; + +/** + * Internal record shape used by both Memory and Fs VarStore implementations. + * Persistence-specific code lives in each factory; pure operations on this + * shape live here so they aren't duplicated. + */ +export type VarRecord = { + name: string; + schema: Hash; + value: Hash; + created: number; + updated: number; + tags: Record; + labels: string[]; + history: HistoryEntry[]; +}; + +/** Build the composite map key from `(name, schema)`. */ +export function varKey(name: string, schema: Hash): string { + return `${name}\u0000${schema}`; +} + +/** Add `k` to the `byName[name]` index, creating the set if needed. */ +export function addNameIndex( + byName: Map>, + name: string, + k: string, +): void { + let set = byName.get(name); + if (!set) { + set = new Set(); + byName.set(name, set); + } + set.add(k); +} + +/** Remove `k` from the `byName[name]` index, dropping empty sets. */ +export function removeNameIndex( + byName: Map>, + name: string, + k: string, +): void { + const set = byName.get(name); + if (!set) return; + set.delete(k); + if (set.size === 0) byName.delete(name); +} + +/** Resolve `hash → schema` (the type field of the stored CAS node). */ +export function extractSchema(cas: CasStore, hash: Hash): Hash { + const node = cas.get(hash); + if (node === null) throw new CasNodeNotFoundError(hash); + return node.type; +} + +/** Reject `(tags, labels)` combinations where the same key appears in both. */ +export function checkTagLabelConflict( + tags: Record, + labels: string[], +): void { + for (const tk of Object.keys(tags)) { + if (labels.includes(tk)) { + throw new TagLabelConflictError(tk, "label", "tag"); + } + } +} + +/** + * Push a new value onto the variable's history. Returns true if the head + * changed (i.e. a new value was set), false when the head was already `value`. + */ +export function pushHistory(rec: VarRecord, value: Hash, now: number): boolean { + if (rec.history.length > 0 && rec.history[0]?.value === value) return false; + const existingIdx = rec.history.findIndex((e) => e.value === value); + if (existingIdx > 0) rec.history.splice(existingIdx, 1); + rec.history.unshift({ value, position: 0, setAt: now }); + if (rec.history.length > MAX_HISTORY) rec.history.length = MAX_HISTORY; + for (let i = 0; i < rec.history.length; i++) { + const entry = rec.history[i]; + if (entry !== undefined) entry.position = i; + } + return true; +} + +/** Convert a stored `VarRecord` to the public `Variable` shape (deep copy). */ +export function cloneVarRecord(rec: VarRecord): Variable { + return { + name: rec.name, + schema: rec.schema, + value: rec.value, + created: rec.created, + updated: rec.updated, + tags: { ...rec.tags }, + labels: [...rec.labels], + }; +} diff --git a/packages/core/src/wrap-envelope.test.ts b/packages/core/src/wrap-envelope.test.ts index 5f60348..cdf1a55 100644 --- a/packages/core/src/wrap-envelope.test.ts +++ b/packages/core/src/wrap-envelope.test.ts @@ -6,7 +6,7 @@ import { wrapEnvelope } from "./wrap-envelope.js"; describe("wrapEnvelope", () => { test("resolves @ocas/output/put alias and returns envelope", async () => { const store = createMemoryStore(); - const aliases = await bootstrap(store); + const aliases = bootstrap(store); const envelope = await wrapEnvelope( store, @@ -20,7 +20,7 @@ describe("wrapEnvelope", () => { test("resolves @ocas/output/has alias with boolean value", async () => { const store = createMemoryStore(); - const aliases = await bootstrap(store); + const aliases = bootstrap(store); const envelope = await wrapEnvelope(store, "@ocas/output/has", true); @@ -30,7 +30,7 @@ describe("wrapEnvelope", () => { test("resolves @ocas/output/gc alias with object value", async () => { const store = createMemoryStore(); - const aliases = await bootstrap(store); + const aliases = bootstrap(store); const gcStats = { total: 100, reachable: 80, collected: 20, scanned: 5 }; const envelope = await wrapEnvelope(store, "@ocas/output/gc", gcStats); @@ -41,7 +41,7 @@ describe("wrapEnvelope", () => { test("resolves primitive alias @ocas/string", async () => { const store = createMemoryStore(); - const aliases = await bootstrap(store); + const aliases = bootstrap(store); const envelope = await wrapEnvelope(store, "@ocas/string", "hello"); @@ -51,7 +51,7 @@ describe("wrapEnvelope", () => { test("throws for unknown alias", async () => { const store = createMemoryStore(); - await bootstrap(store); + bootstrap(store); await expect( wrapEnvelope(store, "@ocas/output/nonexistent", "value"), @@ -75,7 +75,7 @@ describe("wrapEnvelope", () => { test("preserves complex object values without mutation", async () => { const store = createMemoryStore(); - await bootstrap(store); + bootstrap(store); const original = { name: "test", diff --git a/packages/core/src/wrap-envelope.ts b/packages/core/src/wrap-envelope.ts index 072b6da..eaf1dcb 100644 --- a/packages/core/src/wrap-envelope.ts +++ b/packages/core/src/wrap-envelope.ts @@ -1,16 +1,16 @@ import { bootstrap } from "./bootstrap.js"; -import type { Hash, OcasStore } from "./types.js"; +import type { Hash, Store } from "./types.js"; /** * Resolve a schema alias (e.g. "@ocas/output/put") to its hash via bootstrap, * then return a typed envelope ready for store.cas.put() or direct rendering. */ export async function wrapEnvelope( - store: OcasStore, + store: Store, schemaAlias: string, value: unknown, ): Promise<{ type: Hash; value: unknown }> { - const aliases = await bootstrap(store); + const aliases = bootstrap(store); const typeHash = aliases[schemaAlias]; if (typeHash === undefined) { throw new Error(`Unknown schema alias: ${schemaAlias}`); diff --git a/packages/core/tests/schema-validation.test.ts b/packages/core/tests/schema-validation.test.ts index 8c69700..0a126a1 100644 --- a/packages/core/tests/schema-validation.test.ts +++ b/packages/core/tests/schema-validation.test.ts @@ -15,7 +15,7 @@ import type { CasNode } from "../src/types.js"; describe("Test Suite 1: Meta-Schema Structure and Self-Validation", () => { test("1.1: Meta-schema is a valid JSON Schema", async () => { const store = new MemStore(); - const builtinSchemas = await bootstrap(store); + const builtinSchemas = bootstrap(store); const metaHash = builtinSchemas["@ocas/schema"] ?? ""; const metaNode = store.get(metaHash); @@ -26,7 +26,7 @@ describe("Test Suite 1: Meta-Schema Structure and Self-Validation", () => { test("1.2: Meta-schema self-validates", async () => { const store = new MemStore(); - const builtinSchemas = await bootstrap(store); + const builtinSchemas = bootstrap(store); const metaHash = builtinSchemas["@ocas/schema"] ?? ""; const metaNode = store.get(metaHash); @@ -36,7 +36,7 @@ describe("Test Suite 1: Meta-Schema Structure and Self-Validation", () => { test("1.3: Meta-schema defines all supported keywords", async () => { const store = new MemStore(); - const builtinSchemas = await bootstrap(store); + const builtinSchemas = bootstrap(store); const metaHash = builtinSchemas["@ocas/schema"] ?? ""; const metaSchema = getSchema(store, metaHash); @@ -60,7 +60,7 @@ describe("Test Suite 1: Meta-Schema Structure and Self-Validation", () => { test("1.4: Meta-schema does not include unsupported keywords", async () => { const store = new MemStore(); - const builtinSchemas = await bootstrap(store); + const builtinSchemas = bootstrap(store); const metaHash = builtinSchemas["@ocas/schema"] ?? ""; const metaSchema = getSchema(store, metaHash); @@ -98,7 +98,7 @@ describe("Test Suite 1: Meta-Schema Structure and Self-Validation", () => { test("1.5: Meta-schema node type equals its own hash", async () => { const store = new MemStore(); - const builtinSchemas = await bootstrap(store); + const builtinSchemas = bootstrap(store); const metaHash = builtinSchemas["@ocas/schema"] ?? ""; const metaNode = store.get(metaHash); @@ -110,22 +110,22 @@ describe("Test Suite 1: Meta-Schema Structure and Self-Validation", () => { describe("Test Suite 2: putSchema Validation - Valid Schemas", () => { test("2.1: Accept minimal valid schema (empty object)", async () => { const store = new MemStore(); - await bootstrap(store); - const hash = await putSchema(store, {}); + bootstrap(store); + const hash = putSchema(store, {}); expect(hash).toBeTruthy(); }); test("2.2: Accept schema with type constraint", async () => { const store = new MemStore(); - await bootstrap(store); - const hash = await putSchema(store, { type: "string" }); + bootstrap(store); + const hash = putSchema(store, { type: "string" }); expect(hash).toBeTruthy(); }); test("2.3: Accept schema with properties", async () => { const store = new MemStore(); - await bootstrap(store); - const hash = await putSchema(store, { + bootstrap(store); + const hash = putSchema(store, { type: "object", properties: { name: { type: "string" } }, }); @@ -134,8 +134,8 @@ describe("Test Suite 2: putSchema Validation - Valid Schemas", () => { test("2.4: Accept schema with required fields", async () => { const store = new MemStore(); - await bootstrap(store); - const hash = await putSchema(store, { + bootstrap(store); + const hash = putSchema(store, { type: "object", required: ["id"], properties: { id: { type: "string" } }, @@ -145,8 +145,8 @@ describe("Test Suite 2: putSchema Validation - Valid Schemas", () => { test("2.5: Accept schema with additionalProperties = false", async () => { const store = new MemStore(); - await bootstrap(store); - const hash = await putSchema(store, { + bootstrap(store); + const hash = putSchema(store, { type: "object", additionalProperties: false, }); @@ -155,8 +155,8 @@ describe("Test Suite 2: putSchema Validation - Valid Schemas", () => { test("2.6: Accept schema with additionalProperties = schema", async () => { const store = new MemStore(); - await bootstrap(store); - const hash = await putSchema(store, { + bootstrap(store); + const hash = putSchema(store, { type: "object", additionalProperties: { type: "string" }, }); @@ -165,8 +165,8 @@ describe("Test Suite 2: putSchema Validation - Valid Schemas", () => { test("2.7: Accept schema with anyOf", async () => { const store = new MemStore(); - await bootstrap(store); - const hash = await putSchema(store, { + bootstrap(store); + const hash = putSchema(store, { anyOf: [{ type: "string" }, { type: "null" }], }); expect(hash).toBeTruthy(); @@ -174,8 +174,8 @@ describe("Test Suite 2: putSchema Validation - Valid Schemas", () => { test("2.7b: Accept schema with oneOf", async () => { const store = new MemStore(); - await bootstrap(store); - const hash = await putSchema(store, { + bootstrap(store); + const hash = putSchema(store, { oneOf: [ { properties: { status: { const: "ready" } }, required: ["status"] }, { properties: { status: { const: "failed" } }, required: ["status"] }, @@ -186,8 +186,8 @@ describe("Test Suite 2: putSchema Validation - Valid Schemas", () => { test("2.8: Accept schema with array items", async () => { const store = new MemStore(); - await bootstrap(store); - const hash = await putSchema(store, { + bootstrap(store); + const hash = putSchema(store, { type: "array", items: { type: "number" }, }); @@ -196,8 +196,8 @@ describe("Test Suite 2: putSchema Validation - Valid Schemas", () => { test("2.9: Accept schema with format constraint", async () => { const store = new MemStore(); - await bootstrap(store); - const hash = await putSchema(store, { + bootstrap(store); + const hash = putSchema(store, { type: "string", format: "ocas_ref", }); @@ -206,8 +206,8 @@ describe("Test Suite 2: putSchema Validation - Valid Schemas", () => { test("2.10: Accept schema with enum", async () => { const store = new MemStore(); - await bootstrap(store); - const hash = await putSchema(store, { + bootstrap(store); + const hash = putSchema(store, { type: "string", enum: ["red", "green", "blue"], }); @@ -216,15 +216,15 @@ describe("Test Suite 2: putSchema Validation - Valid Schemas", () => { test("2.11: Accept schema with const", async () => { const store = new MemStore(); - await bootstrap(store); - const hash = await putSchema(store, { const: "FIXED_VALUE" }); + bootstrap(store); + const hash = putSchema(store, { const: "FIXED_VALUE" }); expect(hash).toBeTruthy(); }); test("2.12: Accept schema with title and description", async () => { const store = new MemStore(); - await bootstrap(store); - const hash = await putSchema(store, { + bootstrap(store); + const hash = putSchema(store, { type: "string", title: "User Name", description: "The user's full name", @@ -234,8 +234,8 @@ describe("Test Suite 2: putSchema Validation - Valid Schemas", () => { test("2.13: Accept complex nested schema", async () => { const store = new MemStore(); - await bootstrap(store); - const hash = await putSchema(store, { + bootstrap(store); + const hash = putSchema(store, { type: "object", required: ["type", "payload"], properties: { @@ -257,183 +257,169 @@ describe("Test Suite 2: putSchema Validation - Valid Schemas", () => { describe("Test Suite 3: putSchema Validation - Invalid Schemas", () => { test("3.1: Reject schema with invalid type value", async () => { const store = new MemStore(); - await bootstrap(store); - expect(async () => await putSchema(store, { type: "garbage" })).toThrow(); + bootstrap(store); + expect(async () => putSchema(store, { type: "garbage" })).toThrow(); }); test("3.2: Reject schema with type as number", async () => { const store = new MemStore(); - await bootstrap(store); - expect( - async () => - await putSchema(store, { type: 123 } as unknown as JSONSchema), + bootstrap(store); + expect(async () => + putSchema(store, { type: 123 } as unknown as JSONSchema), ).toThrow(); }); test("3.3: Reject schema with properties not an object", async () => { const store = new MemStore(); - await bootstrap(store); - expect( - async () => - await putSchema(store, { - type: "object", - properties: "not-an-object", - } as unknown as JSONSchema), + bootstrap(store); + expect(async () => + putSchema(store, { + type: "object", + properties: "not-an-object", + } as unknown as JSONSchema), ).toThrow(); }); test("3.4: Reject schema with required not an array", async () => { const store = new MemStore(); - await bootstrap(store); - expect( - async () => - await putSchema(store, { - type: "object", - required: "name", - } as unknown as JSONSchema), + bootstrap(store); + expect(async () => + putSchema(store, { + type: "object", + required: "name", + } as unknown as JSONSchema), ).toThrow(); }); test("3.5: Reject schema with required containing non-strings", async () => { const store = new MemStore(); - await bootstrap(store); - expect( - async () => - await putSchema(store, { - type: "object", - required: ["name", 123, true], - } as unknown as JSONSchema), + bootstrap(store); + expect(async () => + putSchema(store, { + type: "object", + required: ["name", 123, true], + } as unknown as JSONSchema), ).toThrow(); }); test("3.6: Reject schema with additionalProperties as string", async () => { const store = new MemStore(); - await bootstrap(store); - expect( - async () => - await putSchema(store, { - type: "object", - additionalProperties: "yes", - } as unknown as JSONSchema), + bootstrap(store); + expect(async () => + putSchema(store, { + type: "object", + additionalProperties: "yes", + } as unknown as JSONSchema), ).toThrow(); }); test("3.7: Reject schema with anyOf not an array", async () => { const store = new MemStore(); - await bootstrap(store); - expect( - async () => - await putSchema(store, { - anyOf: { type: "string" }, - } as unknown as JSONSchema), + bootstrap(store); + expect(async () => + putSchema(store, { + anyOf: { type: "string" }, + } as unknown as JSONSchema), ).toThrow(); }); test("3.8: Reject schema with empty anyOf array", async () => { const store = new MemStore(); - await bootstrap(store); - expect(async () => await putSchema(store, { anyOf: [] })).toThrow(); + bootstrap(store); + expect(async () => putSchema(store, { anyOf: [] })).toThrow(); }); test("3.9: Reject schema with items not an object", async () => { const store = new MemStore(); - await bootstrap(store); - expect( - async () => - await putSchema(store, { - type: "array", - items: "string", - } as unknown as JSONSchema), + bootstrap(store); + expect(async () => + putSchema(store, { + type: "array", + items: "string", + } as unknown as JSONSchema), ).toThrow(); }); test("3.10: Reject schema with format not a string", async () => { const store = new MemStore(); - await bootstrap(store); - expect( - async () => - await putSchema(store, { - type: "string", - format: 123, - } as unknown as JSONSchema), + bootstrap(store); + expect(async () => + putSchema(store, { + type: "string", + format: 123, + } as unknown as JSONSchema), ).toThrow(); }); test("3.11: Reject schema with enum not an array", async () => { const store = new MemStore(); - await bootstrap(store); - expect( - async () => - await putSchema(store, { - type: "string", - enum: "red", - } as unknown as JSONSchema), + bootstrap(store); + expect(async () => + putSchema(store, { + type: "string", + enum: "red", + } as unknown as JSONSchema), ).toThrow(); }); test("3.12: Reject schema with empty enum array", async () => { const store = new MemStore(); - await bootstrap(store); - expect( - async () => await putSchema(store, { type: "string", enum: [] }), + bootstrap(store); + expect(async () => + putSchema(store, { type: "string", enum: [] }), ).toThrow(); }); test("3.13: Reject schema with title not a string", async () => { const store = new MemStore(); - await bootstrap(store); - expect( - async () => - await putSchema(store, { - type: "string", - title: 123, - } as unknown as JSONSchema), + bootstrap(store); + expect(async () => + putSchema(store, { + type: "string", + title: 123, + } as unknown as JSONSchema), ).toThrow(); }); test("3.14: Reject schema with description not a string", async () => { const store = new MemStore(); - await bootstrap(store); - expect( - async () => - await putSchema(store, { - type: "string", - description: ["not a string"], - } as unknown as JSONSchema), + bootstrap(store); + expect(async () => + putSchema(store, { + type: "string", + description: ["not a string"], + } as unknown as JSONSchema), ).toThrow(); }); test("3.15: Reject schema with unsupported $ref keyword", async () => { const store = new MemStore(); - await bootstrap(store); - expect( - async () => - await putSchema(store, { - $ref: "#/definitions/user", - } as unknown as JSONSchema), + bootstrap(store); + expect(async () => + putSchema(store, { + $ref: "#/definitions/user", + } as unknown as JSONSchema), ).toThrow(); }); test("3.16: Reject completely invalid data (non-object)", async () => { const store = new MemStore(); - await bootstrap(store); - expect( - async () => - await putSchema(store, "not-a-schema" as unknown as JSONSchema), + bootstrap(store); + expect(async () => + putSchema(store, "not-a-schema" as unknown as JSONSchema), ).toThrow(); }); test("3.17: Reject nested invalid schema in properties", async () => { const store = new MemStore(); - await bootstrap(store); - expect( - async () => - await putSchema(store, { - type: "object", - properties: { - name: { type: "invalid-type" }, - }, - } as unknown as JSONSchema), + bootstrap(store); + expect(async () => + putSchema(store, { + type: "object", + properties: { + name: { type: "invalid-type" }, + }, + } as unknown as JSONSchema), ).toThrow(); }); }); @@ -441,9 +427,9 @@ describe("Test Suite 3: putSchema Validation - Invalid Schemas", () => { describe("Test Suite 4: Error Messages and Debugging", () => { test("4.1: Error includes schema validation details", async () => { const store = new MemStore(); - await bootstrap(store); + bootstrap(store); try { - await putSchema(store, { type: 123 } as unknown as JSONSchema); + putSchema(store, { type: 123 } as unknown as JSONSchema); expect(true).toBe(false); // Should not reach here } catch (error) { expect(error).toBeInstanceOf(SchemaValidationError); @@ -453,9 +439,9 @@ describe("Test Suite 4: Error Messages and Debugging", () => { test("4.2: Error distinguishes schema validation from data validation", async () => { const store = new MemStore(); - await bootstrap(store); + bootstrap(store); try { - await putSchema(store, { type: "invalid-type" } as unknown as JSONSchema); + putSchema(store, { type: "invalid-type" } as unknown as JSONSchema); expect(true).toBe(false); // Should not reach here } catch (error) { expect(error).toBeInstanceOf(SchemaValidationError); @@ -468,7 +454,7 @@ describe("Test Suite 5: Backward Compatibility and Migration", () => { test("5.1: Bootstrap hash changes (breaking change)", async () => { // This is a documentation test - the old hash was different const store = new MemStore(); - const builtinSchemas = await bootstrap(store); + const builtinSchemas = bootstrap(store); const newMetaHash = builtinSchemas["@ocas/schema"] ?? ""; // The new hash should be different from the old system metadata hash @@ -479,10 +465,10 @@ describe("Test Suite 5: Backward Compatibility and Migration", () => { test("5.2: Existing tests compatibility", async () => { // This test ensures our changes don't break existing valid schema usage const store = new MemStore(); - await bootstrap(store); + bootstrap(store); // This is the kind of schema that existed before - const schemaHash = await putSchema(store, { + const schemaHash = putSchema(store, { type: "object", properties: { name: { type: "string" }, @@ -494,9 +480,9 @@ describe("Test Suite 5: Backward Compatibility and Migration", () => { test("5.3: Data nodes with valid schemas still validate", async () => { const store = new MemStore(); - await bootstrap(store); + bootstrap(store); - const schemaHash = await putSchema(store, { + const schemaHash = putSchema(store, { type: "object", required: ["name"], properties: { @@ -512,9 +498,9 @@ describe("Test Suite 5: Backward Compatibility and Migration", () => { test("5.4: Invalid data still fails validation", async () => { const store = new MemStore(); - await bootstrap(store); + bootstrap(store); - const schemaHash = await putSchema(store, { + const schemaHash = putSchema(store, { type: "object", required: ["name"], properties: { @@ -534,10 +520,10 @@ describe("Test Suite 5: Backward Compatibility and Migration", () => { describe("Test Suite 6: Integration with Existing Functionality", () => { test("6.1: getSchema works with validated schemas", async () => { const store = new MemStore(); - await bootstrap(store); + bootstrap(store); const originalSchema = { type: "string", title: "Test" }; - const schemaHash = await putSchema(store, originalSchema); + const schemaHash = putSchema(store, originalSchema); const retrieved = getSchema(store, schemaHash); expect(retrieved).toEqual(originalSchema); @@ -545,9 +531,9 @@ describe("Test Suite 6: Integration with Existing Functionality", () => { test("6.2: validate() works with schemas validated by meta-schema", async () => { const store = new MemStore(); - await bootstrap(store); + bootstrap(store); - const schemaHash = await putSchema(store, { type: "number" }); + const schemaHash = putSchema(store, { type: "number" }); const validNode = store.get(await store.put(schemaHash, 42)); const invalidNode = store.get(await store.put(schemaHash, "not a number")); @@ -557,9 +543,9 @@ describe("Test Suite 6: Integration with Existing Functionality", () => { test("6.3: refs() works with validated schemas containing ocas_ref", async () => { const store = new MemStore(); - await bootstrap(store); + bootstrap(store); - const schemaHash = await putSchema(store, { + const schemaHash = putSchema(store, { type: "object", properties: { ref: { type: "string", format: "ocas_ref" }, @@ -575,9 +561,9 @@ describe("Test Suite 6: Integration with Existing Functionality", () => { test("6.4: walk() works with graphs using validated schemas", async () => { const store = new MemStore(); - await bootstrap(store); + bootstrap(store); - const schemaHash = await putSchema(store, { + const schemaHash = putSchema(store, { type: "object", properties: { next: { @@ -598,11 +584,11 @@ describe("Test Suite 6: Integration with Existing Functionality", () => { test("6.5: Idempotency preserved for putSchema", async () => { const store = new MemStore(); - await bootstrap(store); + bootstrap(store); const schema = { type: "string", title: "Test" }; - const hash1 = await putSchema(store, schema); - const hash2 = await putSchema(store, schema); + const hash1 = putSchema(store, schema); + const hash2 = putSchema(store, schema); expect(hash1).toBe(hash2); }); @@ -611,7 +597,7 @@ describe("Test Suite 6: Integration with Existing Functionality", () => { describe("Test Suite 7: Meta-Schema Content Validation", () => { test("7.1: Meta-schema allows recursive schema definitions", async () => { const store = new MemStore(); - const builtinSchemas = await bootstrap(store); + const builtinSchemas = bootstrap(store); const metaHash = builtinSchemas["@ocas/schema"] ?? ""; const metaSchema = getSchema(store, metaHash); @@ -624,11 +610,11 @@ describe("Test Suite 7: Meta-Schema Content Validation", () => { test("7.2: Meta-schema restricts additionalProperties", async () => { const store = new MemStore(); - await bootstrap(store); + bootstrap(store); // Schema with unknown keyword should be rejected if meta-schema is strict try { - await putSchema(store, { + putSchema(store, { type: "string", unknownKeyword: "value", } as unknown as JSONSchema); @@ -642,22 +628,21 @@ describe("Test Suite 7: Meta-Schema Content Validation", () => { test("7.3: Meta-schema validates type as string OR array", async () => { const store = new MemStore(); - await bootstrap(store); + bootstrap(store); // Single string type - const hash1 = await putSchema(store, { type: "string" }); + const hash1 = putSchema(store, { type: "string" }); expect(hash1).toBeTruthy(); // Array of types - const hash2 = await putSchema(store, { + const hash2 = putSchema(store, { type: ["string", "null"], } as unknown as JSONSchema); expect(hash2).toBeTruthy(); // Invalid type (number) - expect( - async () => - await putSchema(store, { type: 123 } as unknown as JSONSchema), + expect(async () => + putSchema(store, { type: 123 } as unknown as JSONSchema), ).toThrow(); }); }); @@ -665,7 +650,7 @@ describe("Test Suite 7: Meta-Schema Content Validation", () => { describe("Test Suite 8: Performance and Edge Cases", () => { test("8.1: Validation performance is acceptable", async () => { const store = new MemStore(); - await bootstrap(store); + bootstrap(store); const complexSchema = { type: "object", @@ -686,7 +671,7 @@ describe("Test Suite 8: Performance and Edge Cases", () => { const start = performance.now(); for (let i = 0; i < 100; i++) { - await putSchema(store, complexSchema); + putSchema(store, complexSchema); } const duration = performance.now() - start; @@ -696,7 +681,7 @@ describe("Test Suite 8: Performance and Edge Cases", () => { test("8.2: Large schemas are handled correctly", async () => { const store = new MemStore(); - await bootstrap(store); + bootstrap(store); const largeSchema: Record = { type: "object", @@ -709,13 +694,13 @@ describe("Test Suite 8: Performance and Edge Cases", () => { props[`prop${i}`] = { type: "string" }; } - const hash = await putSchema(store, largeSchema); + const hash = putSchema(store, largeSchema); expect(hash).toBeTruthy(); }); test("8.3: Deeply nested schemas validate correctly", async () => { const store = new MemStore(); - await bootstrap(store); + bootstrap(store); // Build a 5-level deep schema let schema: Record = { type: "string" }; @@ -726,13 +711,13 @@ describe("Test Suite 8: Performance and Edge Cases", () => { }; } - const hash = await putSchema(store, schema); + const hash = putSchema(store, schema); expect(hash).toBeTruthy(); }); test("8.4: Circular-like schemas don't cause infinite loops", async () => { const store = new MemStore(); - await bootstrap(store); + bootstrap(store); // Schema where additionalProperties has same structure as parent const schema = { @@ -748,7 +733,7 @@ describe("Test Suite 8: Performance and Edge Cases", () => { }, }; - const hash = await putSchema(store, schema); + const hash = putSchema(store, schema); expect(hash).toBeTruthy(); }); }); diff --git a/packages/fs/src/store.test.ts b/packages/fs/src/store.test.ts index 7149a1b..2133313 100644 --- a/packages/fs/src/store.test.ts +++ b/packages/fs/src/store.test.ts @@ -51,7 +51,7 @@ describe("createFsStore – init and bootstrap", () => { test("bootstrap returns a valid 13-char self-referencing hash", async () => { const store = await openStore(dir); - const builtinSchemas = await bootstrap(store); + const builtinSchemas = bootstrap(store); const hash = builtinSchemas["@ocas/schema"] ?? ""; expect(hash).toHaveLength(13); @@ -63,8 +63,8 @@ describe("createFsStore – init and bootstrap", () => { test("bootstrap is idempotent across calls", async () => { const store = await openStore(dir); - const h1 = await bootstrap(store); - const h2 = await bootstrap(store); + const h1 = bootstrap(store); + const h2 = bootstrap(store); expect(h1).toEqual(h2); expect(store.cas.listByType(h1["@ocas/schema"] ?? "")).toHaveLength(29); @@ -113,7 +113,7 @@ describe("createFsStore – persistence round-trip", () => { test("bootstrap survives round-trip: self-referencing node reloads correctly", async () => { const store1 = await openStore(dir); - const builtinSchemas = await bootstrap(store1); + const builtinSchemas = bootstrap(store1); const hash = builtinSchemas["@ocas/schema"] ?? ""; const store2 = createFsStore(dir); @@ -261,7 +261,7 @@ describe("createFsStore – listByType", () => { test("bootstrap node is listed under its self type after reload", async () => { const store1 = await openStore(dir); - const builtinSchemas = await bootstrap(store1); + const builtinSchemas = bootstrap(store1); const hash = builtinSchemas["@ocas/schema"] ?? ""; const store2 = createFsStore(dir); @@ -295,7 +295,7 @@ describe("createFsStore – verify on disk-loaded nodes", () => { test("verify passes on a disk-loaded bootstrap node", async () => { const store1 = await openStore(dir); - const builtinSchemas = await bootstrap(store1); + const builtinSchemas = bootstrap(store1); const hash = builtinSchemas["@ocas/schema"] ?? ""; const store2 = createFsStore(dir); @@ -375,7 +375,7 @@ describe("openStore – async with auto-bootstrap", () => { const store = await openStore(dir); // Check that bootstrap schemas exist - const builtinSchemas = await bootstrap(store); + const builtinSchemas = bootstrap(store); const metaHash = builtinSchemas["@ocas/schema"]; expect(metaHash).toBeDefined(); @@ -392,11 +392,11 @@ describe("openStore – async with auto-bootstrap", () => { test("openStore bootstrap is idempotent on subsequent opens", async () => { const store1 = await openStore(dir); - const schemas1 = await bootstrap(store1); + const schemas1 = bootstrap(store1); const count1 = store1.cas.listAll().length; const store2 = await openStore(dir); - const schemas2 = await bootstrap(store2); + const schemas2 = bootstrap(store2); const count2 = store2.cas.listAll().length; // Same schemas, same count @@ -407,11 +407,11 @@ describe("openStore – async with auto-bootstrap", () => { test("openStore works on already-bootstrapped store", async () => { // Open + bootstrap const store1 = await openStore(dir); - const schemas1 = await bootstrap(store1); + const schemas1 = bootstrap(store1); // Open again const store2 = await openStore(dir); - const schemas2 = await bootstrap(store2); + const schemas2 = bootstrap(store2); expect(schemas1).toEqual(schemas2); }); @@ -424,7 +424,7 @@ describe("openStore – async with auto-bootstrap", () => { // Open with openStore - should auto-bootstrap const store2 = await openStore(dir); - const schemas = await bootstrap(store2); + const schemas = bootstrap(store2); expect(store2.cas.has(schemas["@ocas/schema"] as string)).toBe(true); // Old data still exists @@ -560,9 +560,9 @@ describe("createFsStore – listMeta and listSchemas", () => { }); // ────────────────────────────────────────────────────────────────────────────── -// E2. OcasStore shape from openStore +// E2. Store shape from openStore // ────────────────────────────────────────────────────────────────────────────── -describe("openStore – OcasStore shape", () => { +describe("openStore – Store shape", () => { let dir: string; beforeEach(() => { dir = makeTmpDir(); diff --git a/packages/fs/src/store.ts b/packages/fs/src/store.ts index d6a194a..9d4b701 100644 --- a/packages/fs/src/store.ts +++ b/packages/fs/src/store.ts @@ -24,7 +24,7 @@ import { initHasher, type ListEntry, type ListOptions, - type OcasStore, + type Store, } from "@ocas/core"; import { decode } from "cborg"; import { createFsTagStore, createFsVarStoreFor } from "./var-store.js"; @@ -194,7 +194,7 @@ function hashesToEntries( } /** - * The CAS sub-store of an FS-backed `OcasStore` — also satisfies the legacy + * The CAS sub-store of an FS-backed `Store` — also satisfies the legacy * `BootstrapCapableStore` interface so `bootstrap()` can run against it. */ export type FsCasStore = BootstrapCapableStore & { @@ -383,21 +383,21 @@ export async function prepareStore(dir: string): Promise { } /** - * Open a filesystem-backed `OcasStore` with automatic directory creation and + * Open a filesystem-backed `Store` with automatic directory creation and * bootstrap. The CAS sub-store is FS-backed; the variable and tag sub-stores * are in-memory (provided by `@ocas/core`). * * @param dir - The directory path for the CAS store - * @returns A Promise resolving to the OcasStore + * @returns A Promise resolving to the Store * @throws Error if the path exists but is not a directory */ -export async function openStore(dir: string): Promise { +export async function openStore(dir: string): Promise { const cas = await prepareStore(dir); - const ocas: OcasStore = { + const ocas: Store = { cas, var: createFsVarStoreFor(dir, cas), tag: createFsTagStore(dir), }; - await bootstrap(ocas); + bootstrap(ocas); return ocas; } diff --git a/packages/fs/src/var-store.test.ts b/packages/fs/src/var-store.test.ts index 7345598..ac91564 100644 --- a/packages/fs/src/var-store.test.ts +++ b/packages/fs/src/var-store.test.ts @@ -2,7 +2,7 @@ import { afterEach, beforeEach, describe, expect, test } from "bun:test"; import { existsSync, mkdtempSync, readFileSync, rmSync } from "node:fs"; import { tmpdir } from "node:os"; import { join } from "node:path"; -import type { Hash, OcasStore } from "@ocas/core"; +import type { Hash, Store } from "@ocas/core"; import { CasNodeNotFoundError, InvalidVariableNameError, @@ -16,7 +16,7 @@ import { openStore } from "./store.js"; const META_TYPE_KEY = Symbol.for("@ocas/core/bootstrap-store"); async function setupStore(dir: string): Promise<{ - store: OcasStore; + store: Store; schema: Hash; put: (payload: unknown) => Hash; }> { diff --git a/packages/fs/src/var-store.ts b/packages/fs/src/var-store.ts index 187c5fa..770d046 100644 --- a/packages/fs/src/var-store.ts +++ b/packages/fs/src/var-store.ts @@ -8,7 +8,6 @@ import { join } from "node:path"; import type { CasStore, Hash, - HistoryEntry, ListEntry, Tag, TagStore, @@ -17,65 +16,29 @@ import type { VarStore, } from "@ocas/core"; import { + addNameIndex, applyListOptions, - CasNodeNotFoundError, casListEntry, - MAX_HISTORY, + checkTagLabelConflict, + cloneVarRecord, + extractSchema, + pushHistory, + removeNameIndex, SchemaMismatchError, - TagLabelConflictError, VariableNotFoundError, + type VarRecord, validateName, + varKey, } from "@ocas/core"; const VARS_FILE = "_vars.jsonl"; const TAGS_FILE = "_tags.jsonl"; -type VarRecord = { - name: string; - schema: Hash; - value: Hash; - created: number; - updated: number; - tags: Record; - labels: string[]; - history: HistoryEntry[]; -}; - -function cloneVar(rec: VarRecord): Variable { - return { - name: rec.name, - schema: rec.schema, - value: rec.value, - created: rec.created, - updated: rec.updated, - tags: { ...rec.tags }, - labels: [...rec.labels], - }; -} - export function createFsVarStoreFor(dir: string, cas: CasStore): VarStore { const records = new Map(); const byName = new Map>(); const path = join(dir, VARS_FILE); - function key(name: string, schema: Hash): string { - return `${name}\u0000${schema}`; - } - function addIndex(name: string, k: string): void { - let set = byName.get(name); - if (!set) { - set = new Set(); - byName.set(name, set); - } - set.add(k); - } - function removeIndex(name: string, k: string): void { - const set = byName.get(name); - if (!set) return; - set.delete(k); - if (set.size === 0) byName.delete(name); - } - // Load existing records (last record per key wins) try { const content = readFileSync(path, "utf8"); @@ -84,13 +47,13 @@ export function createFsVarStoreFor(dir: string, cas: CasStore): VarStore { try { const rec = JSON.parse(line) as VarRecord & { __op?: string }; if (rec.__op === "remove") { - const k = key(rec.name, rec.schema); + const k = varKey(rec.name, rec.schema); records.delete(k); - removeIndex(rec.name, k); + removeNameIndex(byName, rec.name, k); } else { - const k = key(rec.name, rec.schema); + const k = varKey(rec.name, rec.schema); records.set(k, rec); - addIndex(rec.name, k); + addNameIndex(byName, rec.name, k); } } catch { // skip malformed @@ -123,43 +86,17 @@ export function createFsVarStoreFor(dir: string, cas: CasStore): VarStore { ); } - function extractSchema(hash: Hash): Hash { - const node = cas.get(hash); - if (node === null) throw new CasNodeNotFoundError(hash); - return node.type; - } - - function checkConflict(tags: Record, labels: string[]): void { - for (const tk of Object.keys(tags)) { - if (labels.includes(tk)) - throw new TagLabelConflictError(tk, "label", "tag"); - } - } - - function pushHistory(rec: VarRecord, value: Hash, now: number): boolean { - if (rec.history.length > 0 && rec.history[0]?.value === value) return false; - const existingIdx = rec.history.findIndex((e) => e.value === value); - if (existingIdx > 0) rec.history.splice(existingIdx, 1); - rec.history.unshift({ value, position: 0, setAt: now }); - if (rec.history.length > MAX_HISTORY) rec.history.length = MAX_HISTORY; - for (let i = 0; i < rec.history.length; i++) { - const entry = rec.history[i]; - if (entry !== undefined) entry.position = i; - } - return true; - } - return { set(name, hash, options) { validateName(name); - const schema = extractSchema(hash); - const k = key(name, schema); + const schema = extractSchema(cas, hash); + const k = varKey(name, schema); const existing = records.get(k); const now = Date.now(); if (existing) { const tags = options?.tags ?? existing.tags; const labels = options?.labels ?? existing.labels; - if (options !== undefined) checkConflict(tags, labels); + if (options !== undefined) checkTagLabelConflict(tags, labels); const changed = pushHistory(existing, hash, now); if (changed) { existing.value = hash; @@ -170,11 +107,11 @@ export function createFsVarStoreFor(dir: string, cas: CasStore): VarStore { existing.labels = [...labels]; } persistFull(); - return cloneVar(existing); + return cloneVarRecord(existing); } const tags = options?.tags ?? {}; const labels = options?.labels ?? []; - checkConflict(tags, labels); + checkTagLabelConflict(tags, labels); const rec: VarRecord = { name, schema, @@ -186,33 +123,33 @@ export function createFsVarStoreFor(dir: string, cas: CasStore): VarStore { history: [{ value: hash, position: 0, setAt: now }], }; records.set(k, rec); - addIndex(name, k); + addNameIndex(byName, name, k); appendRecord(rec); - return cloneVar(rec); + return cloneVarRecord(rec); }, get(name, schema) { if (schema !== undefined) { - const rec = records.get(key(name, schema)); - return rec ? cloneVar(rec) : null; + const rec = records.get(varKey(name, schema)); + return rec ? cloneVarRecord(rec) : null; } const set = byName.get(name); if (!set || set.size !== 1) return null; const onlyKey = set.values().next().value; if (onlyKey === undefined) return null; const rec = records.get(onlyKey); - return rec ? cloneVar(rec) : null; + return rec ? cloneVarRecord(rec) : null; }, remove(name, schema) { if (schema !== undefined) { - const k = key(name, schema); + const k = varKey(name, schema); const rec = records.get(k); if (!rec) return []; records.delete(k); - removeIndex(name, k); + removeNameIndex(byName, name, k); appendRemoval(name, schema); - return [cloneVar(rec)]; + return [cloneVarRecord(rec)]; } const set = byName.get(name); if (!set) return []; @@ -220,7 +157,7 @@ export function createFsVarStoreFor(dir: string, cas: CasStore): VarStore { for (const k of [...set]) { const rec = records.get(k); if (rec) { - removed.push(cloneVar(rec)); + removed.push(cloneVarRecord(rec)); records.delete(k); appendRemoval(rec.name, rec.schema); } @@ -231,11 +168,11 @@ export function createFsVarStoreFor(dir: string, cas: CasStore): VarStore { update(name, hash, options) { validateName(name); - const newSchema = extractSchema(hash); + const newSchema = extractSchema(cas, hash); const set = byName.get(name); if (!set || set.size === 0) throw new VariableNotFoundError(name, newSchema); - const k = key(name, newSchema); + const k = varKey(name, newSchema); const existing = records.get(k); if (!existing) { for (const ek of set) { @@ -247,7 +184,7 @@ export function createFsVarStoreFor(dir: string, cas: CasStore): VarStore { const now = Date.now(); const tags = options?.tags ?? existing.tags; const labels = options?.labels ?? existing.labels; - if (options !== undefined) checkConflict(tags, labels); + if (options !== undefined) checkTagLabelConflict(tags, labels); const changed = pushHistory(existing, hash, now); if (changed) { existing.value = hash; @@ -258,7 +195,7 @@ export function createFsVarStoreFor(dir: string, cas: CasStore): VarStore { existing.labels = [...labels]; } persistFull(); - return cloneVar(existing); + return cloneVarRecord(existing); }, list(options?: VarListOptions) { @@ -312,12 +249,12 @@ export function createFsVarStoreFor(dir: string, cas: CasStore): VarStore { }); if (offset > 0) results = results.slice(offset); if (limit !== undefined) results = results.slice(0, limit); - return results.map(cloneVar); + return results.map(cloneVarRecord); }, history(name, schema) { if (schema !== undefined) { - const rec = records.get(key(name, schema)); + const rec = records.get(varKey(name, schema)); return rec ? rec.history.map((e) => ({ ...e })) : []; } const set = byName.get(name);