/** * Runs one projection against a persistent synthetic store. Takes three * positional arguments: mode (`seed`, `read`, or `change`), store directory, * and frame-log path. Writes one JSON line to stdout containing the selected * values, runtime errors, and storage effects for the parent test to inspect. */ import { toFileUrl } from "@std/path"; import { Identity } from "@commonfabric/identity"; import { type URI } from "@commonfabric/memory/interface"; import { Runtime } from "@commonfabric/runner"; import { EmulatedStorageManager, newLoopbackServer, } from "@commonfabric/runner/storage/cache.deno"; import { deriveSelectedValue, parseSelectProjection, } from "../../lib/cell-selection.ts"; /** The frame fields needed to observe projection writes and storage verdicts. */ interface Frame { /** Direction relative to the client. */ dir: string; /** Protocol message type. */ type: string; /** Identity joining a request to its response. */ requestId?: string; /** Storage rejection carried by a response. */ error?: { name: string; message: string }; /** Write operations in a transaction request. */ commit?: { operations: { id: URI; scope: string }[] }; } const [mode, directory, framesPath] = Deno.args; if (Deno.args.length !== 3 || !directory || !framesPath) { throw new Error("Expected replay mode, store directory, and frame-log path"); } if (mode !== "seed" && mode !== "read" && mode !== "change") { throw new Error(`Unknown replay mode: \`${mode}\``); } // The logger appends frames; an empty observation window is a valid log. await Deno.writeTextFile(framesPath, ""); const signer = await Identity.fromPassphrase("cli-projection-replay"); const space = signer.did(); const server = newLoopbackServer({ store: toFileUrl(`${directory}/`), subscriptionRefreshDelayMs: 0, }); const storageManager = EmulatedStorageManager.connectTo(server, { as: signer }); const errors: string[] = []; const runtime = new Runtime({ apiUrl: new URL("https://example.com"), storageManager, experimental: { serverExecution: false }, errorHandlers: [(error) => errors.push(error.message)], }); const itemSchema = { type: "object", properties: { title: { type: "string" }, createdAt: { type: "number" }, lastActivityAt: { type: "number" }, commentCount: { type: "number" }, }, required: ["title", "createdAt", "lastActivityAt", "commentCount"], } as const; const schema = { type: "array", items: itemSchema } as const; /** Reads the structural frame log captured by this process. */ async function readFrames(): Promise { return (await Deno.readTextFile(framesPath)).split("\n") .filter((line) => line.trim().length > 0) .map((line) => JSON.parse(line) as Frame); } /** Counts mutable shared writes in an observed frame window. */ function countMutableSharedWrites(frames: Frame[]): number { return frames.filter((frame) => frame.dir === "out" && frame.type === "transact" ).flatMap((frame) => frame.commit?.operations ?? []).filter((operation) => operation.scope === "space" && !operation.id.startsWith("cid:") ).length; } try { if (mode === "seed") { const tx = runtime.edit(); const rows = Array.from({ length: 8 }, (_, index) => { const row = runtime.getCell(space, { row: index }, itemSchema, tx); row.set({ title: `Row ${index}`, createdAt: index, lastActivityAt: index, commentCount: index, }); return row.getAsLink(); }); runtime.getCell(space, "survey", schema, tx).setRaw(rows); const committed = await tx.commit().settled; if (committed.error) throw committed.error; await storageManager.synced(); } if (mode === "change") { const row = runtime.getCell(space, { row: 3 }, itemSchema); await row.pull(); const tx = runtime.edit(); row.withTx(tx).key("title").set("Updated row"); row.withTx(tx).key("commentCount").set(99); const committed = await tx.commit().settled; if (committed.error) throw committed.error; await storageManager.synced(); } const source = runtime.getCell(space, "survey", schema); await source.pull(); const setupFrames = await readFrames(); const before = setupFrames.length; const value = await deriveSelectedValue(runtime, space, source, { projection: parseSelectProjection( "@,title,createdAt,lastActivityAt,commentCount", ), }); await runtime.idle(); await storageManager.synced(); const frames = (await readFrames()).slice(before); const requests = frames.filter((frame) => frame.dir === "out" && frame.type === "transact" ); const responses = new Map( frames.filter((frame) => frame.dir === "in" && frame.requestId) .map((frame) => [frame.requestId, frame]), ); console.log(JSON.stringify({ value, errors, rejected: requests.flatMap((request) => { const error = responses.get(request.requestId)?.error; return error ? [error] : []; }), unanswered: requests.filter((request) => !responses.has(request.requestId) ).length, setupMutableSharedWrites: countMutableSharedWrites(setupFrames), mutableSharedWrites: countMutableSharedWrites(frames), })); } finally { await runtime.dispose(); await storageManager.close(); await server.close(); }