Add TS node reactive document shortcut

This commit is contained in:
Meta Vens 2026-06-27 20:42:09 +01:00
commit a2b2f8ea8c
2 changed files with 87 additions and 0 deletions

View file

@ -3673,6 +3673,39 @@ export class CultMeshNode {
return cultMeshDocumentFromCache(this.cache, definition, key, options);
}
public reactiveDocument<TDefinition extends AnyCultCacheDocumentDefinition>(
definition: TDefinition,
key: string,
options: {
context?: CultMeshQueryContext | string;
watch?: boolean;
debounceMs?: number;
onError?: (error: unknown) => void;
documentId?: string;
routeHint?: CultMeshRouteHint;
pollMs?: number;
} = {},
): CultMeshReactiveDocument<CultCacheDocumentValue<TDefinition> & object> {
const {
documentId,
routeHint,
pollMs,
...reactiveOptions
} = options;
const schema = cultMeshSchemaFromDefinition(definition);
const binding =
this.documents.get(definition.type) ??
(schema.schemaId ? this.documents.getBySchemaId(schema.schemaId) : undefined);
const document = binding
? this.document(binding.definition, key, { documentId, routeHint, pollMs })
.asSchemaAlias<CultCacheDocumentValue<TDefinition>>(schema, {
parse: value => definition.schema.parse(value),
})
: this.document(definition, key, { documentId, routeHint, pollMs });
return document.reactive<CultCacheDocumentValue<TDefinition> & object>(reactiveOptions);
}
public globalDocument<TDefinition extends AnyCultCacheDocumentDefinition>(
definition: TDefinition,
options: {
@ -4590,6 +4623,23 @@ export class CultMesh {
return cultMeshGlobalDocumentFromCache(cache, definition, options);
}
public static reactiveDocument<TDefinition extends AnyCultCacheDocumentDefinition>(
node: CultMeshNode,
definition: TDefinition,
key: string,
options: {
context?: CultMeshQueryContext | string;
watch?: boolean;
debounceMs?: number;
onError?: (error: unknown) => void;
documentId?: string;
routeHint?: CultMeshRouteHint;
pollMs?: number;
} = {},
): CultMeshReactiveDocument<CultCacheDocumentValue<TDefinition> & object> {
return node.reactiveDocument(definition, key, options);
}
public static documents(
...documents: readonly CultMeshDocumentHandle<any>[]
): CultMeshDocumentCatalog {

View file

@ -240,6 +240,43 @@ test("CultMesh TS reactive documents submit predictions from member writes", asy
assert.deepEqual(predictions, ["member-write-prediction-final"]);
});
test("CultMesh TS node opens reactive same-schema aliases with one call", async () => {
const filePath = join(await mkdtemp(join(tmpdir(), "cultmesh-ts-node-reactive-")), "node.ccmp");
const node = await CultMesh.startNode(filePath, {
documents: [noteDocument],
});
await node.put(noteDocument, "note:node-reactive", {
noteId: "note:node-reactive",
body: "initial",
});
const reactive = node.reactiveDocument(noteAliasDocument, "note:node-reactive", {
context: CultMesh.queryContext("browser-client"),
watch: false,
});
await reactive.ready;
reactive.current.body = "edited-through-node-helper";
await waitFor(
() => node.getRequired(noteDocument, "note:node-reactive").body === "edited-through-node-helper",
"node-level reactive alias write",
);
assert.equal(reactive.current.noteId, "note:node-reactive");
assert.equal(
node.document(noteDocument, "note:node-reactive").asSchemaAlias(noteAliasDocument, {
parse: value => noteAliasDocument.schema.parse(value),
}).canSet,
true,
);
assert.equal(
(await CultMesh.reactiveDocument(node, noteAliasDocument, "note:node-reactive", {
watch: false,
}).ready).body,
"edited-through-node-helper",
);
reactive.dispose();
});
test("CultMesh TS reactive documents store reconciliation deltas after misprediction", async () => {
let current = {
noteId: "note:reconcile",