pi-mono/packages/chord/test/examples/02-documents.ts
Mario Zechner e9c0ff4d84 feat(chord): Chord v2 is the package's root
src/next is src (the main export), src/next-node is src/node with short subpaths (/http, /workers, /plugins,
/plugins/runtime, /app, /surfaces, /web, /react, /tui, /tui/launcher); the foundations get /json and /watch;
Chord v1 moves to src/old (/old, /old/node) until p2 runs on v2; tests and docs move likewise (test/, docs/,
test/old, docs/old). Durable and env import the foundations from their subpaths.
2026-11-02 22:36:55 +00:00

79 lines
2.9 KiB
TypeScript

// Documents and transactions: an object's in-memory JSON state, written atomically.
// Run from packages/chord:
// node --conditions=source --experimental-strip-types test/examples/02-documents.ts
import { BACKGROUND_CONTEXT } from "../../src/context/index.ts";
import { arr, int, local, method, obj, opt, str } from "../../src/contracts/index.ts";
import { defineState } from "../../src/documents/index.ts";
import { createHost, defineFacet } from "../../src/host/index.ts";
const context = BACKGROUND_CONTEXT;
// A singleton document: one per object, `initial()` until written.
const Todos = defineState("todos", {
schema: obj({ items: arr(obj({ id: int(), text: str() })), next: int() }),
version: 1,
initial: () => ({ items: [], next: 1 }),
});
// A keyed document: many per object, one per string key, absent until written.
const Notes = defineState("notes", {
schema: obj({ body: str() }),
version: 1,
initial: () => ({ body: "" }),
keyed: true,
});
const Store = local("example.todos", {
version: 1,
params: {},
members: {
todos: obj({ items: arr(obj({ id: int(), text: str() })), next: int() }),
add: method(obj({ text: str(), note: opt(str()) }), int()),
note: method(obj({ id: int() }), str()),
clear: method(),
},
});
const facet = defineFacet((host) => {
host.serve(Store, (_, self) => {
const todos = self.state(Todos);
const notes = self.state(Notes);
return {
// A singleton handle is a watchable, so it can be a state member as it is.
todos,
add: async ({ text, note }, ctx) => {
// One transaction over two documents: both change together, or neither does.
const id = await self.tx(async (tx) => {
const draft = await tx.draft(todos);
const id = draft.next++;
draft.items.push({ id, text });
if (note !== undefined) (await tx.draft(notes, String(id))).body = note;
// Return plain values: drafts are revoked once the transaction settles.
return id;
}, ctx);
return id;
},
// Read keyed documents outside transactions; an absent one is `undefined`.
note: async ({ id }, ctx) => (await notes.get(String(id), ctx))?.body ?? "(none)",
clear: async (ctx) => {
// Shorthands for one document: retire resets a singleton to `initial()`.
await todos.retire(ctx);
for await (const key of notes.keys(ctx)) await notes.retire(key, ctx);
return null;
},
};
});
});
const host = createHost({ onError: (error) => console.error("background error:", error) });
await host.load([{ name: "@example/todos", facet }], context);
const store = await host.get(Store, {}, context);
const first = await store.add({ text: "buy milk", note: "the oat one" }, context);
await store.add({ text: "call Anna" }, context);
console.log("todos:", store.todos.value.items);
console.log("note of", first, "->", await store.note({ id: first }, context));
await store.clear(context);
console.log("after clear:", store.todos.value, await store.note({ id: first }, context));
await host.close(context);