From 026cf608b72b5d3fba3c2bac5aae70cf531d35e4 Mon Sep 17 00:00:00 2001 From: Marc-Antoine Parent Date: Mon, 6 Jul 2026 15:26:49 -0400 Subject: [PATCH] eng-1985 full converter suite --- .../database/src/lib/crossAppConverters.ts | 355 +++++++++++++++++- 1 file changed, 351 insertions(+), 4 deletions(-) diff --git a/packages/database/src/lib/crossAppConverters.ts b/packages/database/src/lib/crossAppConverters.ts index d5c9dfc9f..3d1124a59 100644 --- a/packages/database/src/lib/crossAppConverters.ts +++ b/packages/database/src/lib/crossAppConverters.ts @@ -1,17 +1,39 @@ import { LocalRef, Ref, + CrossAppBase, + // InlineAbstractBase, + InlineCrossAppDocument, + CrossAppNodeSchema, + CrossAppRelationTypeSchema, + CrossAppRelationTripleSchema, CrossAppEmbedding, InlineCrossAppContent, - CrossAppBase, + CrossAppAccount, CrossAppNode, + CrossAppRelation, + LocalOrRemoteRef, } from "../crossAppContracts"; -import { LocalContentDataInput, LocalConceptDataInput } from "../inputTypes"; +import { spaceUriAndLocalIdToRid, ridToSpaceUriAndLocalId } from "./rid"; +import { + LocalAccountDataInput, + LocalDocumentDataInput, + LocalContentDataInput, + LocalConceptDataInput, +} from "../inputTypes"; import { Enums, CompositeTypes } from "../dbTypes"; type InlineEmbeddingInput = CompositeTypes<"inline_embedding_input">; type InlineAbstractBase = Partial; +export const crossAppAccountToDbAccount = ( + node: CrossAppAccount, +): LocalAccountDataInput => ({ + name: node.name, + account_local_id: node.accountLocalId, + email: node.email, +}); + const decodeLocalRef = ( ref: LocalRef | InlineAbstractBase | undefined, localVarName: LocalVarName, @@ -39,7 +61,125 @@ const decodeRef = ( return decodeLocalRef(ref, localVarName); }; -const crossAppEmbeddingToDbEmbedding = ( +const decodeLocalOrRemoteRef = < + LocalVarName extends string, + DbVarName extends string, +>({ + ref, + dbVarName, + localVarName, + currentSpaceUri, +}: { + ref: LocalOrRemoteRef | InlineAbstractBase | undefined; + dbVarName: DbVarName; + localVarName: LocalVarName; + currentSpaceUri: string; +}): + | Record + | Record + | Record => { + if (ref === undefined) return {}; + if ("dbId" in ref) + return { [dbVarName]: ref.dbId } as Record; + if ("rid" in ref) { + const { sourceLocalId, spaceUri } = ridToSpaceUriAndLocalId(ref.rid); + if (spaceUri === currentSpaceUri) + return { + [localVarName]: sourceLocalId, + } as Record; + return { + [localVarName]: ref.rid, + } as Record; + } + if ("space" in ref && ref.space !== undefined) { + if ("dbId" in ref.space) { + // not sure how to handle this + return { + [localVarName]: ref.localId, + } as Record; + } + if ("url" in ref.space) { + if (ref.space.url === currentSpaceUri) { + return { + [localVarName]: ref.localId, + } as Record; + } else + return { + [localVarName]: spaceUriAndLocalIdToRid(ref.space.url, ref.localId), + } as Record; + } + } + if ("localId" in ref) { + return { + [localVarName]: ref.localId, + } as Record; + } + return {}; +}; + +const decodeRefOrInline = < + InlineType extends object, + DbType extends object, + DbVarName extends string, + LocalVarName extends string, + InlineVarName extends string, +>({ + data, + dbVarName, + localVarName, + inlineVarName, + encoder, +}: { + data: InlineType | Ref | undefined; + dbVarName: DbVarName; + localVarName: LocalVarName; + inlineVarName: InlineVarName; + encoder: (inlineData: InlineType) => DbType; +}): + | Record + | Record + | Record + | Record => { + if (data === undefined) return {}; + if (Object.keys(data).length === 0) return {}; + const ref = data as Ref; + if ("dbId" in ref) { + // eslint-disable-next-line @typescript-eslint/no-unused-vars + const { dbId, ...extraData } = ref; + if (Object.keys(extraData).length === 0) + return decodeRef(ref, dbVarName, localVarName); + } + if ("localId" in ref) { + // eslint-disable-next-line @typescript-eslint/no-unused-vars + const { localId, ...extraData } = ref; + if (Object.keys(extraData).length === ("space" in ref ? 1 : 0)) + return decodeRef(ref, dbVarName, localVarName); + } + // assume not a simple ref + return { + [inlineVarName]: encoder(data as InlineType), + } as Record; +}; + +export const crossAppDocumentToDbDocument = ( + document: InlineCrossAppDocument, +): LocalDocumentDataInput => { + return { + ...decodeLocalRef(document, "source_local_id"), + ...decodeRefOrInline({ + data: document.author, + dbVarName: "author_id", + localVarName: "author_local_id", + inlineVarName: "author_inline", + encoder: crossAppAccountToDbAccount, + }), + created: document.createdAt?.toISOString(), + last_modified: document.modifiedAt?.toISOString(), + content_type: document.contentType, + }; +}; + +export const crossAppEmbeddingToDbEmbedding = ( embedding: CrossAppEmbedding | undefined, ): InlineEmbeddingInput | undefined => embedding === undefined @@ -49,6 +189,51 @@ const crossAppEmbeddingToDbEmbedding = ( model: embedding.embedding || "openai_text_embedding_3_small_1536", }; +export const crossAppContentToDbContent = ( + content: InlineCrossAppContent | undefined, + variant: Enums<"ContentVariant">, + node?: CrossAppNode, +): LocalContentDataInput | undefined => { + if (content === undefined) return undefined; + return filterUndefined({ + ...decodeLocalRef(content, "source_local_id"), + text: content.value, + scale: content.scale || "document", + content_type: + content.contentType || + (variant === "full" ? document?.contentType : undefined) || + "text/plain", + variant: variant, + created: (content.createdAt || node?.createdAt)?.toISOString(), + last_modified: (content.modifiedAt || node?.modifiedAt)?.toISOString(), + ...decodeRefOrInline({ + data: content.author || node?.author, + dbVarName: "author_id", + localVarName: "author_local_id", + inlineVarName: "author_inline", + encoder: crossAppAccountToDbAccount, + }), + ...decodeRef(content.partOf, "part_of_id", "part_of_local_id"), + ...decodeRefOrInline({ + data: content.document || node?.document, + dbVarName: "document_id", + localVarName: "document_local_id", + inlineVarName: "document_inline", + encoder: crossAppDocumentToDbDocument, + }), + embedding_inline: crossAppEmbeddingToDbEmbedding(content.embedding), + }); +}; + +export const crossAppInlineContentToDbContent = ( + node: CrossAppNode | undefined, + variant: "full" | "direct", +): LocalContentDataInput | undefined => { + if (node === undefined) return undefined; + const content = node.content[variant]; + return crossAppContentToDbContent(content, variant, node); +}; + const filterUndefined = >(data: T): T => { return Object.fromEntries( Object.entries(data).filter(([, v]) => v !== undefined), @@ -100,11 +285,173 @@ export const crossAppNodeToDbConcept = ( ...decodeLocalRef(node, "source_local_id"), name: node.content.direct.value, ...decodeRef(node.author, "author_id", "author_local_id"), - ...decodeRef(node.nodeType, "schema_id", "schema_represented_by_local_id"), + ...decodeRefOrInline({ + data: node.author, + dbVarName: "author_id", + localVarName: "author_local_id", + inlineVarName: "author_inline", + encoder: crossAppAccountToDbAccount, + }), contents_inline: filterUndefinedArray([ crossAppNodeToDbContent(node, "direct"), crossAppNodeToDbContent(node, "full"), ]), + ...decodeRef(node.nodeType, "schema_id", "schema_represented_by_local_id"), + created: node.createdAt?.toISOString(), + last_modified: node.modifiedAt?.toISOString(), + }); +}; + +export const crossAppNodeSchemaToDbConcept = ( + node: CrossAppNodeSchema, +): LocalConceptDataInput => { + const literalInfo = filterUndefined({ + template: node.templateTitle, + templateContent: node.template, + }); + return filterUndefined({ + ...decodeLocalRef(node, "source_local_id"), + name: node.label, + ...decodeRefOrInline({ + data: node.author, + dbVarName: "author_id", + localVarName: "author_local_id", + inlineVarName: "author_inline", + encoder: crossAppAccountToDbAccount, + }), + is_schema: true, + literal_content: + Object.keys(literalInfo).length > 0 ? literalInfo : undefined, + created: node.createdAt?.toISOString(), + last_modified: node.modifiedAt?.toISOString(), + }); +}; + +export const crossAppRelationTypeSchemaToDbConcept = ( + node: CrossAppRelationTypeSchema, +): LocalConceptDataInput => { + return filterUndefined({ + ...decodeLocalRef(node, "source_local_id"), + name: node.label, + ...decodeRefOrInline({ + data: node.author, + dbVarName: "author_id", + localVarName: "author_local_id", + inlineVarName: "author_inline", + encoder: crossAppAccountToDbAccount, + }), + is_schema: true, + literal_content: { + roles: ["source", "destination"], + label: node.label, + complement: node.complement, + }, + created: node.createdAt?.toISOString(), + last_modified: node.modifiedAt?.toISOString(), + }); +}; + +export const crossAppRelationTripleSchemaToDbConcept = ( + node: CrossAppRelationTripleSchema, +): LocalConceptDataInput => { + const relDataRaw = { + ...decodeRefOrInline({ + data: node.relation, + dbVarName: "relation", + localVarName: "relation_local_id", + inlineVarName: "relation_inline", + encoder: crossAppRelationTypeSchemaToDbConcept, + }), + ...decodeRef(node.sourceType, "source", "source_local_id"), + ...decodeRef(node.destinationType, "destination", "destination_local_id"), + }; + let relData: Omit & { + relationInline?: LocalConceptDataInput; + } = relDataRaw; + // We don't allow inline data in upload + let relationType: CrossAppRelationTypeSchema | undefined; + if ("relation_inline" in relData) { + relationType = node.relation as CrossAppRelationTypeSchema; + delete relData["relation_inline"]; + relData = { + ...relData, + ...decodeLocalRef(relationType, "relation_local_id"), + }; + } + const refLocalIds = Object.fromEntries( + Object.entries(relData) + .filter(([k]) => k.endsWith("_local_id")) + .map(([k, v]) => [k.substring(0, k.length - 9), v]), + ) as Record; + const refIds = Object.fromEntries( + Object.entries(relData).filter( + ([k]) => k.endsWith("_id") && !k.endsWith("_local_id"), + ), + ) as Record; + return filterUndefined({ + ...decodeLocalRef(node, "source_local_id"), + name: node.label, + ...decodeRefOrInline({ + data: node.author, + dbVarName: "author_id", + localVarName: "author_local_id", + inlineVarName: "author_inline", + encoder: crossAppAccountToDbAccount, + }), + is_schema: true, + literal_content: filterUndefined({ + roles: ["source", "destination"], + label: node.label || relationType?.label, + complement: node.complement || relationType?.complement, + }), + local_reference_content: + Object.keys(refLocalIds).length > 0 ? refLocalIds : undefined, + reference_content: Object.keys(refIds).length > 0 ? refIds : undefined, + created: node.createdAt?.toISOString(), + last_modified: node.modifiedAt?.toISOString(), + }); +}; + +export const crossAppRelationToDbConcept = ( + node: CrossAppRelation, + currentSpaceUri: string, +): LocalConceptDataInput => { + const relData = { + ...decodeLocalOrRemoteRef({ + ref: node.source, + dbVarName: "source", + localVarName: "source_local_id", + currentSpaceUri, + }), + ...decodeLocalOrRemoteRef({ + ref: node.destination, + dbVarName: "destination", + localVarName: "destination_local_id", + currentSpaceUri, + }), + }; + const refLocalIds = Object.fromEntries( + Object.entries(relData) + .filter(([k]) => k.endsWith("_local_id")) + .map(([k, v]) => [k.substring(0, k.length - 9), v]), + ) as Record; + const refIds = Object.fromEntries( + Object.entries(relData).filter( + ([k]) => k.endsWith("_id") && !k.endsWith("_local_id"), + ), + ) as Record; + + return filterUndefined({ + ...decodeLocalRef(node, "source_local_id"), + ...decodeRefOrInline({ + data: node.author, + dbVarName: "author_id", + localVarName: "author_local_id", + inlineVarName: "author_inline", + encoder: crossAppAccountToDbAccount, + }), + local_reference_content: refLocalIds, + reference_content: refIds, created: node.createdAt?.toISOString(), last_modified: node.modifiedAt?.toISOString(), });