pi-mono/packages/durable/test/examples/02-forks.ts
Mario Zechner 837d8a16bd feat(durable): first runnable chat turn (Package 15)
Submissions with request-ID deduplication and handles, the pi.live run
control and presentation document, the built-in pi.generation task
(system prompt preparation, streamed durable partials, retries, deferred
polling, abort), typed entry tokens, stream options and retry policy,
TaskRuntime reads, Tx.settleSubmission, registry conversation setups,
Storage.scanSubmissions, Harness.inspect, and byTaskId attribution.
Waiting calls enable scheduling. The scratch tour is split into
test/examples.
2026-09-29 14:08:28 +02:00

55 lines
2.2 KiB
TypeScript

// Fork a conversation.
// Run from packages/durable:
// node --conditions=source --experimental-strip-types test/examples/02-forks.ts
import { BACKGROUND_CONTEXT } from "@earendil-works/chord/context";
import { createSession, defineDoc, MemoryStorage } from "../../src/index.ts";
const context = BACKGROUND_CONTEXT;
const session = createSession(new MemoryStorage());
const Notes = defineDoc<{ text: string }>({
kind: "example.notes",
version: 1,
scope: "conversation",
history: "rewindable",
fork: "asOf",
initial: () => ({ text: "" }),
});
const chat = await session.commit((tx) => tx.createConversation({ ownership: { kind: "ownerless" } }), context);
const firstEntry = await session.commit(async (tx) => {
const entry = await tx.appendEntry(chat.id, { kind: "note", data: "hello" });
(await tx.doc(Notes, chat.id)).text = "after hello";
return entry;
}, context);
await session.commit(async (tx) => {
await tx.appendEntry(chat.id, { kind: "note", data: "goodbye" });
(await tx.doc(Notes, chat.id)).text = "after goodbye";
}, context);
// A fork is a new conversation that continues from one entry of another. It
// sees the parent's transcript up to that entry, and each document follows its
// own `fork` setting. Notes uses "asOf", so the fork starts with the notes
// value from the fork entry.
const branch = await session.commit(
(tx) => tx.forkConversation(chat.id, firstEntry.id, { ownership: { kind: "ownerless" } }),
context,
);
// scanEntries() pages through visible entries, newest first. The fork sees
// "hello" (inherited from the parent) but not "goodbye", which came later.
const branchEntries = await session.commit((tx) => tx.scanEntries({ conversationId: branch.id }, 10), context);
console.log(
"fork transcript:",
branchEntries.items.map((entry) => entry.data),
);
console.log("fork notes:", await session.snapshot(Notes, branch.id, context));
// The fork's copy is independent: editing it leaves the parent unchanged.
await session.commit(async (tx) => {
(await tx.doc(Notes, branch.id)).text = "changed only in the fork";
}, context);
console.log("fork notes after edit:", await session.snapshot(Notes, branch.id, context));
console.log("parent notes after edit:", await session.snapshot(Notes, chat.id, context));
await session.close(context);