pi-mono/packages/durable/test/examples/36-durable-views.ts
Mario Zechner 14b4c56e57 feat(chord,durable): Chord v2 slice 6b, Durable and jobs on the store
StoreStorage (Durable's storage on Chord v2's store, @earendil-works/pi-durable/storage/store) passes
Durable's storage conformance suite and runs Durable's harness tests (npm run test:store). The store
gets owner names and claims, one id allocator, Durable's Storage semantics for refused steps, history
none/latest/rewindable with reads at a point, archive revisions and applyBatch. self.follow follows
Durable's synchronous observers as Chord sources. Persisted jobs: records and state in the owner's
store, restarts as Durable's tasks, abort stored, deferred state. Chord's ./next export returns for
the adapter surface.
2026-10-28 19:18:30 +00:00

91 lines
3.3 KiB
TypeScript

// Durable's views as Chord sources: `self.follow(attach)` turns Durable's synchronous observers (the conversation view,
// the task graph) into watchables, members and derive sources of the object that opened Durable. With Durable on the
// host's store, Durable adopts its commits inside the store's commit step, in the host's batch: a derive over two views
// of one commit computes once, and a watcher gets one frame per commit.
// Run from packages/durable:
// node --conditions=source --experimental-strip-types test/examples/36-durable-views.ts
import { BACKGROUND_CONTEXT } from "@earendil-works/chord/context";
import {
arr,
createHost,
defineFacet,
int,
local,
method,
obj,
openStore,
sqliteBackend,
str,
} from "@earendil-works/chord/next";
import { openNodeSqliteDatabase } from "@earendil-works/chord/sqlite/node";
import { createModels } from "@earendil-works/pi-ai";
import { createRegistry, Harness, ROOT_CONVERSATION_ID } from "../../src/index.ts";
import { StoreStorage } from "../../src/storage/store/index.ts";
const context = BACKGROUND_CONTEXT;
const Chat = local("example.chat", {
version: 1,
params: {},
members: {
// What a UI shows: the kinds of the transcript's entries and the number of tasks, from two views.
summary: obj({ kinds: arr(str()), tasks: int() }),
write: method(str()),
},
});
const facet = defineFacet((host) => {
host.serve(Chat, (_, self) => {
const harness = self.open(
async (open) => {
const opened = await Harness.open(
await StoreStorage.open(store, Chat.name, open),
{ models: createModels(), registry: createRegistry() },
open,
);
await opened.root(open);
return opened;
},
(h, _reason, close) => h.close(close),
);
// 1. Follow two of Durable's views; they attach once the natives opened.
const view = self.follow((observer, ctx) =>
harness.value.observeConversation(ROOT_CONVERSATION_ID, observer, ctx, { min: 50 }),
);
const graph = self.follow((observer, ctx) => harness.value.observeTaskGraph(observer, ctx));
// 2. A derive over both: one compute per Durable commit.
const summary = self.derive([view, graph], ([v, g]) => ({
kinds: v.entries.map((entry) => entry.kind),
tasks: Object.keys(g.tasks).length,
}));
return {
summary,
write: async (kind, ctx) => {
const root = await harness.value.conversation(ROOT_CONVERSATION_ID, ctx);
await root!.submit({ type: "write", entry: { kind } }, ctx);
return null;
},
};
});
});
const store = await openStore(sqliteBackend(await openNodeSqliteDatabase(":memory:")), context);
const host = createHost({ store, onError: (error) => console.log("onError", error.kind) });
await host.load([{ name: "@example/core", facet }], context);
const chat = await host.get(Chat, {}, context);
// 3. Watch the member: one frame per Durable commit, with its ops.
const watch = await chat.summary.watch(context);
watch.start(async (call) => {
if (call.kind === "value") console.log("frame:", call.value.kinds, JSON.stringify(call.ops));
});
await chat.write("example.first", context);
await chat.write("example.second", context);
await new Promise((resolve) => setTimeout(resolve, 10));
console.log("member value:", chat.summary.value);
// 4. Closing the host detaches the observers; the store is the app's.
await watch.stop();
await host.close(context);
await store.close(context);