Resolve TS node document aliases transparently

This commit is contained in:
Meta Vens 2026-06-27 20:46:39 +01:00
commit d62f35fbae
2 changed files with 101 additions and 20 deletions

View file

@ -3636,14 +3636,23 @@ export class CultMeshNode {
definition: TDefinition,
key: string,
): CultCacheDocumentValue<TDefinition> | undefined {
return this.cache.get(definition, key);
const registered = this.resolveCacheDefinition(definition);
const value = this.cache.get(registered, key);
return value === undefined
? undefined
: this.parseDocumentValue(definition, value);
}
public getRequired<TDefinition extends AnyCultCacheDocumentDefinition>(
definition: TDefinition,
key: string,
): CultCacheDocumentValue<TDefinition> {
return this.cache.getRequired(definition, key);
const value = this.get(definition, key);
if (value === undefined) {
throw new Error(`CultMesh node has no "${definition.type}" document at key "${key}".`);
}
return value;
}
public put<TDefinition extends AnyCultCacheDocumentDefinition>(
@ -3651,14 +3660,17 @@ export class CultMeshNode {
key: string,
value: CultCacheDocumentValue<TDefinition>,
): Promise<CultCacheDocumentValue<TDefinition>> {
return this.cache.put(definition, key, value);
const registered = this.resolveCacheDefinition(definition);
return this.cache
.put(registered, key, this.parseDocumentValue(definition, value) as never)
.then(stored => this.parseDocumentValue(definition, stored));
}
public delete<TDefinition extends AnyCultCacheDocumentDefinition>(
definition: TDefinition,
key: string,
): Promise<boolean> {
return this.cache.delete(definition, key);
return this.cache.delete(this.resolveCacheDefinition(definition), key);
}
public document<TDefinition extends AnyCultCacheDocumentDefinition>(
@ -3670,7 +3682,16 @@ export class CultMeshNode {
pollMs?: number;
} = {},
): CultMeshDocumentHandle<CultCacheDocumentValue<TDefinition>> {
return cultMeshDocumentFromCache(this.cache, definition, key, options);
const documentId = options.documentId ?? `${definition.type}:${key}`;
return this.asRequestedDocument(
cultMeshDocumentFromCache(
this.cache,
this.resolveCacheDefinition(definition),
key,
{ ...options, documentId },
),
definition,
);
}
public reactiveDocument<TDefinition extends AnyCultCacheDocumentDefinition>(
@ -3692,18 +3713,8 @@ export class CultMeshNode {
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);
return this.document(definition, key, { documentId, routeHint, pollMs })
.reactive<CultCacheDocumentValue<TDefinition> & object>(reactiveOptions);
}
public globalDocument<TDefinition extends AnyCultCacheDocumentDefinition>(
@ -3714,7 +3725,15 @@ export class CultMeshNode {
pollMs?: number;
} = {},
): CultMeshDocumentHandle<CultCacheDocumentValue<TDefinition>> {
return cultMeshGlobalDocumentFromCache(this.cache, definition, options);
const documentId = options.documentId ?? `${definition.type}:global`;
return this.asRequestedDocument(
cultMeshGlobalDocumentFromCache(
this.cache,
this.resolveCacheDefinition(definition),
{ ...options, documentId },
),
definition,
);
}
public collection<TDefinition extends AnyCultCacheDocumentDefinition>(
@ -3725,12 +3744,47 @@ export class CultMeshNode {
pollMs?: number;
} = {},
): CultMeshCollectionHandle<CultCacheDocumentValue<TDefinition>> {
return cultMeshCollectionFromCache(this.cache, definition, options);
const collectionId = options.collectionId ?? definition.type;
return cultMeshCollectionFromCache(
this.cache,
this.resolveCacheDefinition(definition),
{ ...options, collectionId },
).asSchemaAlias<CultCacheDocumentValue<TDefinition>>(
cultMeshSchemaFromDefinition(definition),
{ parse: value => this.parseDocumentValue(definition, value) },
);
}
public async flush(soft = false): Promise<void> {
await this.store.pushAll?.(this.cache.snapshot(), { soft });
}
private resolveCacheDefinition<TDefinition extends AnyCultCacheDocumentDefinition>(
definition: TDefinition,
): AnyCultCacheDocumentDefinition {
const schema = cultMeshSchemaFromDefinition(definition);
return (
this.documents.get(definition.type) ??
(schema.schemaId ? this.documents.getBySchemaId(schema.schemaId) : undefined)
)?.definition ?? definition;
}
private asRequestedDocument<TDefinition extends AnyCultCacheDocumentDefinition>(
document: CultMeshDocumentHandle<unknown>,
definition: TDefinition,
): CultMeshDocumentHandle<CultCacheDocumentValue<TDefinition>> {
return document.asSchemaAlias<CultCacheDocumentValue<TDefinition>>(
cultMeshSchemaFromDefinition(definition),
{ parse: value => this.parseDocumentValue(definition, value) },
);
}
private parseDocumentValue<TDefinition extends AnyCultCacheDocumentDefinition>(
definition: TDefinition,
value: unknown,
): CultCacheDocumentValue<TDefinition> {
return definition.schema.parse(value) as CultCacheDocumentValue<TDefinition>;
}
}
export class CultMeshVerseCatalog<TVerse = unknown> {

View file

@ -240,7 +240,7 @@ 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 () => {
test("CultMesh TS node opens 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],
@ -250,6 +250,33 @@ test("CultMesh TS node opens reactive same-schema aliases with one call", async
body: "initial",
});
const aliasHandle = node.document(noteAliasDocument, "note:node-reactive", {
pollMs: 5,
});
assert.equal(aliasHandle.documentId, "cultmesh.note.ui:note:node-reactive");
assert.equal((await aliasHandle.latest()).body, "initial");
await aliasHandle.set({
noteId: "note:node-reactive",
body: "edited-through-alias-handle",
});
assert.equal(
node.getRequired(noteAliasDocument, "note:node-reactive").body,
"edited-through-alias-handle",
);
await node.put(noteAliasDocument, "note:node-reactive", {
noteId: "note:node-reactive",
body: "edited-through-alias-put",
});
assert.equal(
node.getRequired(noteDocument, "note:node-reactive").body,
"edited-through-alias-put",
);
assert.deepEqual(
(await node.collection(noteAliasDocument).latest()).map(note => note.body),
["edited-through-alias-put"],
);
const reactive = node.reactiveDocument(noteAliasDocument, "note:node-reactive", {
context: CultMesh.queryContext("browser-client"),
watch: false,