diff --git a/packages/cultmesh-ts/src/index.ts b/packages/cultmesh-ts/src/index.ts index 6481bcbb..d2d46991 100644 --- a/packages/cultmesh-ts/src/index.ts +++ b/packages/cultmesh-ts/src/index.ts @@ -5640,11 +5640,13 @@ function cultMeshSchemaFromDefinition( function normalizeCultMeshDocumentSchema( schema: CultMeshDocumentSchemaDescriptor, ): CultMeshDocumentSchemaDescriptor { + const schemaId = schema.schemaId?.trim() || undefined; + const inferredSchemaName = schema.schemaName?.trim() || inferCultMeshSchemaName(schemaId); const normalized = { type: schema.type?.trim() || undefined, - schemaId: schema.schemaId?.trim() || undefined, - schemaName: schema.schemaName?.trim() || undefined, - schemaVersion: schema.schemaVersion?.trim() || undefined, + schemaId, + schemaName: inferredSchemaName, + schemaVersion: schema.schemaVersion?.trim() || (inferredSchemaName ? schemaId : undefined), }; if (!normalized.type && !normalized.schemaId && !normalized.schemaName) { @@ -5682,6 +5684,18 @@ function cultMeshSchemasAreCompatible( return Boolean(left.type && right.type && left.type === right.type); } +function inferCultMeshSchemaName(schemaId: string | undefined): string | undefined { + if (!schemaId) { + return undefined; + } + const marker = schemaId.lastIndexOf(".v"); + if (marker <= 0 || marker + 2 >= schemaId.length) { + return undefined; + } + const version = schemaId.slice(marker + 2); + return /^\d+$/.test(version) ? schemaId.slice(0, marker) : undefined; +} + function resolveCultMeshStoreDocumentRecord( records: readonly CultCacheEnvelope[], schema: CultMeshDocumentSchemaDescriptor, diff --git a/packages/cultmesh-ts/test/cultmesh.test.ts b/packages/cultmesh-ts/test/cultmesh.test.ts index 86f58b77..91c346f0 100644 --- a/packages/cultmesh-ts/test/cultmesh.test.ts +++ b/packages/cultmesh-ts/test/cultmesh.test.ts @@ -679,6 +679,34 @@ test("CultMesh TS binds publication document catalogs from source resolvers", as assert.equal(catalog.document(noteDocument).routeHint.description, "publication catalog"); }); +test("CultMesh TS document catalogs resolve semantic schema versions passed as schema ids", async () => { + const current = { + noteId: "note:semantic", + body: "semantic alias", + }; + const document = CultMesh.document( + "daemon:semantic-note", + { + schemaId: "sha256:runtime-generated-note", + schemaName: "cultmesh.note", + schemaVersion: "cultmesh.note.v1", + }, + async () => current, + { + routeHint: CultMesh.routeHint("shared-memory", "semantic alias catalog"), + }, + ); + const catalog = CultMesh.documents(document); + + const alias = catalog.document({ + schemaId: "cultmesh.note.v1", + }); + + assert.equal(alias.documentId, "daemon:semantic-note"); + assert.equal(alias.routeHint.description, "semantic alias catalog"); + assert.deepEqual(await alias.latest(), current); +}); + test("CultMesh TS collection handles expose typed snapshots and reset watches", async () => { const filePath = join(await mkdtemp(join(tmpdir(), "cultmesh-ts-coll-")), "node.ccmp"); const node = await CultMesh.startNode(filePath, {