import { afterEach, beforeEach, describe, it } from "@std/testing/bdd";
import { expect } from "@std/expect";
import { createSession, Identity } from "@commonfabric/identity";
import { entityIdFrom, Runtime } from "@commonfabric/runner";
import { StorageManager } from "@commonfabric/runner/storage/cache.deno";
import type { RuntimeProgram } from "../../runner/src/harness/types.ts";
import { createBuilder } from "../../runner/src/builder/factory.ts";
import type { Cell } from "../../runner/src/builder/types.ts";
import { pieceId, PieceManager } from "../src/manager.ts";
import { PiecesController } from "../src/ops/pieces-controller.ts";
const signer = await Identity.fromPassphrase(
"test default pattern persistence",
);
const defaultPatternProgram: RuntimeProgram = {
main: "/main.tsx",
files: [
{
name: "/main.tsx",
contents: [
"/// ",
"import { handler, pattern } from 'commonfabric';",
"const addPiece = handler<{ piece: unknown }, { allPieces: unknown[] }>(",
" ({ piece }, { allPieces }) => {",
" allPieces.push(piece);",
" },",
" { proxy: true },",
");",
"export default pattern<{ allPieces: unknown[] }>(({ allPieces }) => ({",
" allPieces,",
" addPiece: addPiece({ allPieces }),",
"}));",
].join("\n"),
},
],
};
const persistedPieceProgram: RuntimeProgram = {
main: "/main.tsx",
files: [
{
name: "/main.tsx",
contents: [
"import { pattern } from 'commonfabric';",
"export default pattern<{ value: number }>(({ value }) => ({ value }));",
].join("\n"),
},
],
};
async function createDefaultPatternPieceWithResult(manager: PieceManager) {
const { commonfabric } = createBuilder();
const { handler, pattern } = commonfabric;
const addPiece = handler<
{ piece: Cell },
{ allPieces: Cell[] }
>(
({ piece }, { allPieces }) => {
allPieces.push(piece);
},
{ proxy: true },
);
const defaultPattern = pattern<{ allPieces: Cell[] }>(
({ allPieces }) => ({
allPieces,
addPiece: addPiece({ allPieces }),
}),
);
const defaultPatternPiece = await manager.runPersistent(
defaultPattern,
{ allPieces: [] },
"default-pattern-persistence",
);
await manager.linkDefaultPattern(defaultPatternPiece);
await manager.runtime.idle();
await manager.synced();
const persistedPattern = pattern<{ value: number }>(({ value }) => ({
value,
nested: { value },
}));
const persistedPiece = await manager.runPersistent(
persistedPattern,
{ value: 42 },
"persisted-piece",
);
await manager.add([persistedPiece]);
await manager.runtime.idle();
await manager.synced();
return persistedPiece;
}
describe("PieceManager default pattern persistence", () => {
let storageManager: ReturnType;
let runtime: Runtime;
let manager: PieceManager;
beforeEach(async () => {
storageManager = StorageManager.emulate({ as: signer });
runtime = new Runtime({
apiUrl: new URL(import.meta.url),
storageManager,
});
const session = await createSession({
identity: signer,
spaceName: "default-pattern-persistence-" + crypto.randomUUID(),
});
manager = new PieceManager(session, runtime);
await manager.synced();
});
afterEach(async () => {
await runtime?.dispose();
await storageManager?.close();
});
it("reads persisted allPieces without restarting the default pattern", async () => {
const { commonfabric } = createBuilder();
const { handler, pattern } = commonfabric;
const addPiece = handler<
{ piece: Cell },
{ allPieces: Cell[] }
>(
({ piece }, { allPieces }) => {
allPieces.push(piece);
},
{ proxy: true },
);
const defaultPattern = pattern<{ allPieces: Cell[] }>(
({ allPieces }) => ({
allPieces,
addPiece: addPiece({ allPieces }),
}),
);
const defaultPatternPiece = await manager.runPersistent(
defaultPattern,
{ allPieces: [] },
"default-pattern-persistence",
);
await manager.linkDefaultPattern(defaultPatternPiece);
await manager.runtime.idle();
await manager.synced();
const persistedPattern = pattern<{ value: number }>(({ value }) => ({
value,
}));
const persistedPiece = await manager.runPersistent(
persistedPattern,
{ value: 1 },
"persisted-piece",
);
await manager.add([persistedPiece]);
await manager.stopPiece(defaultPatternPiece);
const piecesCell = await manager.getPieces();
const ids = piecesCell.get().map((piece) => pieceId(piece)).filter(Boolean);
expect(ids).toContain(pieceId(persistedPiece));
});
it("adds a persisted piece from a fresh runtime", async () => {
const compiledDefaultPattern = await runtime.patternManager.compilePattern(
defaultPatternProgram,
{ space: manager.getSpace() },
);
const defaultPatternPiece = await manager.runPersistent(
compiledDefaultPattern,
{ allPieces: [] },
"default-pattern-persistence-fresh",
);
await manager.linkDefaultPattern(defaultPatternPiece);
await manager.runtime.idle();
await manager.synced();
// Compile INTO the space so the content-addressed source + compiled docs
// persist — a fresh runtime recovers the pattern by its `{ identity, symbol }`
// pointer (there is no longer a meta cell holding the program).
const compiledPiecePattern = await runtime.patternManager.compilePattern(
persistedPieceProgram,
{ space: manager.getSpace() },
);
const persistedPiece = await manager.runPersistent(
compiledPiecePattern,
{ value: 2 },
"persisted-piece-fresh",
);
await manager.runtime.idle();
await manager.synced();
const session = await createSession({
identity: signer,
spaceName: manager.getSpaceName()!,
});
const freshRuntime = new Runtime({
apiUrl: new URL(import.meta.url),
storageManager,
});
const freshManager = new PieceManager(session, freshRuntime);
try {
await freshManager.synced();
const freshPiece = freshRuntime.getCellFromEntityId(
freshManager.getSpace(),
entityIdFrom(pieceId(persistedPiece)!),
);
await freshManager.add([freshPiece]);
await freshManager.stopPiece(defaultPatternPiece);
const piecesCell = await freshManager.getPieces();
const ids = piecesCell.get().map((piece) => pieceId(piece)).filter(
Boolean,
);
const pieces = new PiecesController(freshManager);
const listedPiece = (await pieces.getAllPieces()).find((piece) =>
piece.id === pieceId(persistedPiece)
);
const directPiece = await pieces.get(pieceId(persistedPiece)!, false);
expect(ids.filter((id) => id === pieceId(persistedPiece))).toHaveLength(
1,
);
expect(listedPiece).toBeDefined();
expect(await listedPiece!.result.get()).toEqual(
await directPiece.result.get(),
);
} finally {
await freshRuntime.dispose();
}
});
it("getAllPieces controllers expose the same result root as direct cold lookup", async () => {
const persistedPiece = await createDefaultPatternPieceWithResult(manager);
const id = pieceId(persistedPiece)!;
const pieces = new PiecesController(manager);
const listedPiece = (await pieces.getAllPieces()).find((piece) =>
piece.id === id
);
expect(listedPiece).toBeDefined();
const directPiece = await pieces.get(id, false);
expect(await listedPiece!.result.get()).toEqual(
await directPiece.result.get(),
);
});
it("getAllPieces controllers resolve result paths like direct cold lookup", async () => {
const persistedPiece = await createDefaultPatternPieceWithResult(manager);
const id = pieceId(persistedPiece)!;
const pieces = new PiecesController(manager);
const listedPiece = (await pieces.getAllPieces()).find((piece) =>
piece.id === id
);
expect(listedPiece).toBeDefined();
const directPiece = await pieces.get(id, false);
expect(await listedPiece!.result.get(["value"])).toEqual(
await directPiece.result.get(["value"]),
);
expect(await listedPiece!.result.get(["nested", "value"])).toEqual(
await directPiece.result.get(["nested", "value"]),
);
});
});