diff --git a/packages/database/src/crossAppContracts.ts b/packages/database/src/crossAppContracts.ts index 55633fa96..c996332c3 100644 --- a/packages/database/src/crossAppContracts.ts +++ b/packages/database/src/crossAppContracts.ts @@ -1,7 +1,7 @@ import type { ContentType } from "@repo/content-model"; import { Enums } from "./dbTypes"; -type LocalRef = { +export type LocalRef = { // This localId is expected to be unique within the current space localId: string; }; @@ -12,24 +12,24 @@ type DbRef = { }; // Generalized reference -type Ref = LocalRef | DbRef; +export type Ref = LocalRef | DbRef; // Common attributes for most types -type CrossAppBase = LocalRef & { +export type CrossAppBase = LocalRef & { createdAt: Date; modifiedAt?: Date; author: Ref; }; // An inline vector semantic embedding -type CrossAppEmbedding = { +export type CrossAppEmbedding = { value: number[]; embedding?: Enums<"EmbeddingName">; }; // A Content object. It can be put inline inside a concept. // Missing CrossAppBase attributes are inferred from enclosing object. -type InlineCrossAppContent = Partial & { +export type InlineCrossAppContent = Partial & { value: string; embedding?: CrossAppEmbedding; scale?: Enums<"Scale">; diff --git a/packages/database/src/lib/crossAppConverters.ts b/packages/database/src/lib/crossAppConverters.ts new file mode 100644 index 000000000..b6fa56494 --- /dev/null +++ b/packages/database/src/lib/crossAppConverters.ts @@ -0,0 +1,91 @@ +import { + LocalRef, + Ref, + CrossAppEmbedding, + InlineCrossAppContent, + CrossAppBase, + CrossAppNode, +} from "../crossAppContracts"; +import { LocalContentDataInput } from "../inputTypes"; +import { Enums, CompositeTypes } from "../dbTypes"; + +type InlineEmbeddingInput = CompositeTypes<"inline_embedding_input">; +type InlineAbstractBase = Partial; + +const decodeLocalRef = ( + ref: LocalRef | InlineAbstractBase | undefined, + localVarName: LocalVarName, +): Record | Record => { + if (ref === undefined) return {}; + if ("localId" in ref) { + return { + [localVarName]: ref.localId, + } as Record; + } + return {}; +}; + +const decodeRef = ( + ref: Ref | undefined, + dbVarName: DbVarName, + localVarName: LocalVarName, +): + | Record + | Record + | Record => { + if (ref === undefined) return {}; + if ("dbId" in ref) + return { [dbVarName]: ref.dbId } as Record; + return decodeLocalRef(ref, localVarName); +}; + +const crossAppEmbeddingToDbEmbedding = ( + embedding: CrossAppEmbedding | undefined, +): InlineEmbeddingInput | undefined => + embedding === undefined + ? undefined + : { + vector: embedding.value, + model: embedding.embedding || "openai_text_embedding_3_small_1536", + }; + +const filterUndefined = >(data: T): T => { + return Object.fromEntries( + Object.entries(data).filter(([, v]) => v !== undefined), + ) as T; +}; + +const inlineCrossAppContentToDbContent = ( + content: InlineCrossAppContent | undefined, + variant: Enums<"ContentVariant">, +): 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 || "text/plain", + variant, + created: content.createdAt?.toISOString(), + last_modified: content.modifiedAt?.toISOString(), + ...decodeRef(content.author, "author_id", "author_local_id"), + embedding_inline: crossAppEmbeddingToDbEmbedding(content.embedding), + }); +}; + +export const crossAppNodeToDbContent = ( + node: CrossAppNode | undefined, + variant: "full" | "direct", +): LocalContentDataInput | undefined => { + if (node === undefined) return undefined; + const content = node.content[variant]; + return inlineCrossAppContentToDbContent( + { + ...content, + createdAt: content.createdAt || node.createdAt, + modifiedAt: content.modifiedAt || node.modifiedAt, + author: content.author || node.author, + }, + variant, + ); +};