// Basic pattern mechanics: defining patterns, passing inputs, returning outputs, // nesting patterns, default values, and map/iteration over collections. import { afterEach, beforeEach, describe, it } from "@std/testing/bdd"; import { expect } from "@std/expect"; import "@commonfabric/utils/equal-ignoring-symbols"; import { Identity } from "@commonfabric/identity"; import { StorageManager } from "@commonfabric/runner/storage/cache.deno"; import { type FactoryInput, type JSONSchema } from "../src/builder/types.ts"; import { createBuilder } from "../src/builder/factory.ts"; import { createTrustedBuilder } from "./support/trusted-builder.ts"; import { Runtime } from "../src/runtime.ts"; import { type IExtendedStorageTransaction } from "../src/storage/interface.ts"; const signer = await Identity.fromPassphrase("test operator"); const space = signer.did(); describe("Pattern Runner - Core", () => { let storageManager: ReturnType; let runtime: Runtime; let tx: IExtendedStorageTransaction; let lift: ReturnType["commonfabric"]["lift"]; let pattern: ReturnType["commonfabric"]["pattern"]; beforeEach(() => { storageManager = StorageManager.emulate({ as: signer }); runtime = new Runtime({ apiUrl: new URL(import.meta.url), storageManager, }); tx = runtime.edit(); const { commonfabric } = createTrustedBuilder(runtime); ({ lift, pattern, } = commonfabric); }); async function commitTx() { if (tx.status().status !== "ready") { return { ok: undefined, error: undefined }; } runtime.prepareTxForCommit(tx); return await tx.commit(); } afterEach(async () => { await commitTx(); await runtime?.dispose(); await storageManager?.close(); }); it("should run a simple pattern", async () => { const simplePattern = pattern<{ value: number }>( ({ value }) => { const doubled = lift((x: number) => x * 2)(value); return { result: doubled }; }, ); const resultCell = runtime.getCell<{ result: number }>( space, "should run a simple pattern", undefined, tx, ); const result = runtime.run(tx, simplePattern, { value: 5, }, resultCell); await commitTx(); const value = await result.pull(); expect(value).toMatchObject({ result: 10 }); }); it("should handle nested patterns", async () => { const innerPattern = pattern<{ x: number }>(({ x }) => { const squared = lift((n: number) => { return n * n; })(x); return { squared }; }); const outerPattern = pattern<{ value: number }>( ({ value }) => { const { squared } = innerPattern({ x: value }); const result = lift((n: number) => { return n + 1; })(squared); return { result }; }, ); const resultCell = runtime.getCell<{ result: number }>( space, "should handle nested patterns", undefined, tx, ); const result = runtime.run(tx, outerPattern, { value: 4, }, resultCell); await commitTx(); const value = await result.pull(); expect(value).toEqual({ result: 17 }); }); it("should handle patterns with default values", async () => { const patternWithDefaults = pattern( ({ a, b }) => { const { sum } = lift(({ x, y }) => ({ sum: x + y }))({ x: a, y: b }); return { sum }; }, { type: "object", properties: { a: { type: "number", default: 5 }, b: { type: "number", default: 10 }, }, }, { type: "object", properties: { sum: { type: "number" } } }, ); const resultCell1 = runtime.getCell<{ sum: number }>( space, "should handle patterns with defaults", undefined, tx, ); const result1 = runtime.run( tx, patternWithDefaults, {}, resultCell1, ); await commitTx(); tx = runtime.edit(); const value1 = await result1.pull(); expect(value1).toMatchObject({ sum: 15 }); const resultCell2 = runtime.getCell<{ sum: number }>( space, "should handle patterns with defaults (2)", undefined, tx, ); const result2 = runtime.run(tx, patternWithDefaults, { a: 20, }, resultCell2); await commitTx(); const value2 = await result2.pull(); expect(value2).toMatchObject({ sum: 30 }); }); it("should handle patterns with map nodes", async () => { const multiply = lift<{ x: number; index: number; array: { x: number }[] }>( ({ x, index, array }) => x * (index + 1) * array.length, ); const multipliedArray = pattern<{ values: { x: number }[] }>( ({ values }) => { const multiplied = (values as any).mapWithPattern( pattern(({ element, index, array }: FactoryInput) => ((({ x }: any, index: any, array: any) => { return { multiplied: multiply({ x, index, array }) }; }) as any)(element, index, array) ), {}, ); return { multiplied }; }, ); const resultCell = runtime.getCell( space, "should handle patterns with map nodes", { type: "object", properties: { multiplied: { type: "array", items: { type: "object", properties: { multiplied: { type: "number" } }, }, }, }, } as const satisfies JSONSchema, tx, ); const result = runtime.run(tx, multipliedArray, { values: [{ x: 1 }, { x: 2 }, { x: 3 }], }, resultCell); await commitTx(); const value = await result.pull(); expect(value).toMatchObjectIgnoringSymbols({ multiplied: [{ multiplied: 3 }, { multiplied: 12 }, { multiplied: 27 }], }); }); it("should handle map nodes with undefined input", async () => { const double = lift((x: number) => x * 2); const doubleArray = pattern<{ values?: number[] }>( ({ values }) => { const doubled = (values as any)?.mapWithPattern( pattern(({ element, index, array }: FactoryInput) => (((x: any) => double(x)) as any)(element, index, array) ), {}, ) ?? []; return { doubled }; }, ); const resultCell = runtime.getCell( space, "should handle map nodes with undefined input", { type: "object", properties: { doubled: { type: "array", items: { type: "number" } } }, } as const satisfies JSONSchema, tx, ); const result = runtime.run(tx, doubleArray, { values: undefined, }, resultCell); await commitTx(); const value = await result.pull(); expect(value).toMatchObjectIgnoringSymbols({ doubled: [] }); }); it("should preserve sparse array holes through map", async () => { const double = lift((x: number) => x * 2); const doubleArray = pattern<{ values: number[] }>( ({ values }) => { const doubled = (values as any).mapWithPattern( pattern(({ element, index, array }: FactoryInput) => (((x: any) => double(x)) as any)(element, index, array) ), {}, ); return { doubled }; }, ); // Create a sparse input array: [10, , 30] // deno-lint-ignore no-sparse-arrays const sparseInput = [10, , 30]; const resultCell = runtime.getCell( space, "should preserve sparse array holes through map", { type: "object", properties: { doubled: { type: "array", items: { type: "number" } }, }, } as const satisfies JSONSchema, tx, ); const result = runtime.run(tx, doubleArray, { values: sparseInput, }, resultCell); await commitTx(); const value = await result.pull(); const doubled = (value as any).doubled; expect(Array.isArray(doubled)).toBe(true); expect(doubled.length).toBe(3); expect(doubled[0]).toBe(20); expect(1 in doubled).toBe(false); // hole preserved expect(doubled[2]).toBe(60); }); });