@open-mercato/search 0.10.1-develop.7768.1.90fa052c11 → 0.10.1-develop.7770.1.970b396ebb

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/lib/presenter-enricher.ts"],
4
- "sourcesContent": ["import type { Kysely } from 'kysely'\nimport type {\n SearchBuildContext,\n SearchResult,\n SearchResultPresenter,\n SearchResultLink,\n SearchEntityConfig,\n PresenterEnricherFn,\n} from '../types'\nimport type { QueryEngine } from '@open-mercato/shared/lib/query/types'\nimport type { EntityId } from '@open-mercato/shared/modules/entities'\nimport type { TenantDataEncryptionService } from '@open-mercato/shared/lib/encryption/tenantDataEncryptionService'\nimport { decryptIndexDocForSearch } from '@open-mercato/shared/lib/encryption/indexDoc'\nimport {\n buildCustomFieldKindIndex,\n createCustomFieldKindMapResolver,\n type CustomFieldKindMapResolver,\n} from '@open-mercato/shared/lib/custom-fields/kinds'\nimport { createLogger } from '@open-mercato/shared/lib/logger'\nimport { extractFallbackPresenter } from './fallback-presenter'\nimport { needsSearchResultEnrichment } from './search-result-enrichment'\n\n/** Maximum number of record IDs per batch query to avoid hitting DB parameter limits */\nconst BATCH_SIZE = 500\n\n/** Diagnostic logger - surfaces issues without breaking flow, gated by OM_LOG_LEVEL=debug */\nconst enricherLogger = createLogger('search').child({ component: 'presenter-enricher' })\nconst logWarning = (message: string, context?: Record<string, unknown>) => {\n enricherLogger.debug(message, context)\n}\n\n/**\n * Split an array into chunks of specified size.\n */\nfunction chunk<T>(array: T[], size: number): T[][] {\n const chunks: T[][] = []\n for (let i = 0; i < array.length; i += size) {\n chunks.push(array.slice(i, i + size))\n }\n return chunks\n}\n\n/**\n * Load the `kind` of every active custom field on the entity types in this batch, as a\n * per-entity-type resolver. Without it `decryptIndexDocForSearch` runs `JSON.parse` over\n * decrypted string-typed values and returns `123` where `\"123\"` was stored (issue #5968).\n *\n * The definitions' scope columns come along so each result row can be typed by the\n * definition that applies to *its* organization: global search spans organizations, and one\n * of them may override a key with a different `kind` than another.\n *\n * Fails open: a lookup error yields an empty map, which keeps the previous behavior rather\n * than breaking search.\n */\nasync function fetchCustomFieldKindResolversByEntityType(\n db: Kysely<any>,\n entityTypes: string[],\n tenantId: string,\n): Promise<Map<string, CustomFieldKindMapResolver>> {\n const byEntityType = new Map<string, CustomFieldKindMapResolver>()\n if (!entityTypes.length) return byEntityType\n try {\n const rows = await db\n .selectFrom('custom_field_defs')\n .select(['entity_id', 'key', 'kind', 'organization_id', 'tenant_id', 'config_json', 'updated_at'])\n .where('entity_id', 'in', entityTypes)\n .where('is_active', '=', true)\n .where((eb: any) => eb.or([\n eb('tenant_id', '=', tenantId),\n eb('tenant_id', 'is', null),\n ]))\n .execute() as Array<Record<string, unknown>>\n const grouped = new Map<string, Array<Record<string, unknown>>>()\n for (const row of rows) {\n const entityType = typeof row.entity_id === 'string' ? row.entity_id : String(row.entity_id ?? '')\n if (!entityType) continue\n const group = grouped.get(entityType) ?? []\n group.push(row)\n grouped.set(entityType, group)\n }\n for (const [entityType, group] of grouped) {\n const index = buildCustomFieldKindIndex(group.map((row) => ({\n key: row.key,\n kind: row.kind,\n organizationId: row.organization_id,\n tenantId: row.tenant_id,\n configJson: row.config_json,\n updatedAt: row.updated_at,\n })))\n byEntityType.set(entityType, createCustomFieldKindMapResolver(index))\n }\n } catch (err) {\n logWarning('Failed to resolve custom field kinds', { err })\n }\n return byEntityType\n}\n\n/**\n * Build a single batch query for multiple entity types and their record IDs.\n * Uses OR conditions to fetch all needed docs in one round trip.\n */\nasync function fetchDocsBatch(\n db: Kysely<any>,\n byEntityType: Map<string, SearchResult[]>,\n tenantId: string,\n organizationId?: string | null,\n): Promise<Array<{ entity_type: string; entity_id: string; doc: Record<string, unknown> }>> {\n const allDocs: Array<{ entity_type: string; entity_id: string; doc: Record<string, unknown> }> = []\n\n // Collect all entity type + record ID pairs\n const allPairs: Array<{ entityType: string; recordId: string }> = []\n for (const [entityType, results] of byEntityType) {\n for (const result of results) {\n allPairs.push({ entityType, recordId: result.recordId })\n }\n }\n\n if (allPairs.length === 0) return allDocs\n\n // Process in chunks to avoid hitting DB parameter limits\n const chunks = chunk(allPairs, BATCH_SIZE)\n\n for (const pairChunk of chunks) {\n // Group by entity type within this chunk for efficient OR query\n const chunkByType = new Map<string, string[]>()\n for (const { entityType, recordId } of pairChunk) {\n const ids = chunkByType.get(entityType) ?? []\n ids.push(recordId)\n chunkByType.set(entityType, ids)\n }\n\n // Build query with OR conditions per entity type\n let query = db\n .selectFrom('entity_indexes' as any)\n .select(['entity_type' as any, 'entity_id' as any, 'doc' as any])\n .where('tenant_id' as any, '=', tenantId)\n .where('deleted_at' as any, 'is', null)\n .where((eb: any) => eb.or(\n Array.from(chunkByType.entries()).map(([entityType, recordIds]) => eb.and([\n eb('entity_type' as any, '=', entityType),\n eb('entity_id' as any, 'in', recordIds),\n ])),\n ))\n\n // Add organization filter if provided\n if (organizationId) {\n query = query.where((eb: any) => eb.or([\n eb('organization_id' as any, '=', organizationId),\n eb('organization_id' as any, 'is', null),\n ]))\n }\n\n const rows = await query.execute() as Array<{ entity_type: string; entity_id: string; doc: Record<string, unknown> }>\n allDocs.push(...rows)\n }\n\n return allDocs\n}\n\n/** Result type for presenter and links computation */\ntype EnrichmentResult = {\n presenter: SearchResultPresenter | null\n url?: string\n links?: SearchResultLink[]\n}\n\nfunction primaryNavigationHref(result: SearchResult): string | null {\n if (typeof result.url === 'string' && result.url.trim().length > 0) {\n return result.url.trim()\n }\n const primaryLink = result.links?.find((link) => link.kind === 'primary' && link.href.trim().length > 0)\n return primaryLink?.href.trim() ?? null\n}\n\nfunction directNavigationRecordId(href: string): string | null {\n try {\n const url = new URL(href, 'http://search.local')\n if (url.search || url.hash) return null\n const segments = url.pathname.split('/').filter(Boolean)\n const lastSegment = segments.at(-1)\n return lastSegment ? decodeURIComponent(lastSegment) : null\n } catch {\n return null\n }\n}\n\nfunction presenterTitle(result: SearchResult): string | null {\n const title = result.presenter?.title?.trim()\n return title?.length ? title : null\n}\n\nfunction resultScopeKey(result: SearchResult, recordId: string): string {\n return `${result.organizationId ?? ''}:${recordId}`\n}\n\nfunction mergeResultMetadata(\n targetMetadata: SearchResult['metadata'],\n linkedMetadata: SearchResult['metadata'],\n): SearchResult['metadata'] {\n if (!targetMetadata && !linkedMetadata) return undefined\n return {\n ...targetMetadata,\n ...linkedMetadata,\n }\n}\n\nfunction mergeLinkedDuplicateResults(results: SearchResult[]): SearchResult[] {\n const indexesByRecord = new Map<string, number[]>()\n for (let index = 0; index < results.length; index += 1) {\n const result = results[index]\n const key = resultScopeKey(result, result.recordId)\n const indexes = indexesByRecord.get(key) ?? []\n indexes.push(index)\n indexesByRecord.set(key, indexes)\n }\n\n const replacements = new Map<number, SearchResult>()\n const removedIndexes = new Set<number>()\n\n for (let index = 0; index < results.length; index += 1) {\n const linkedResult = results[index]\n const href = primaryNavigationHref(linkedResult)\n const targetRecordId = href ? directNavigationRecordId(href) : null\n if (!targetRecordId || targetRecordId === linkedResult.recordId) continue\n\n const targetIndexes = (indexesByRecord.get(resultScopeKey(linkedResult, targetRecordId)) ?? [])\n .filter((candidateIndex) => candidateIndex !== index && !removedIndexes.has(candidateIndex))\n if (targetIndexes.length !== 1) continue\n\n const targetIndex = targetIndexes[0]\n const targetResult = replacements.get(targetIndex) ?? results[targetIndex]\n if (primaryNavigationHref(targetResult)) continue\n\n const linkedTitle = presenterTitle(linkedResult)\n const targetTitle = presenterTitle(targetResult)\n if (!linkedTitle || linkedTitle !== targetTitle) continue\n\n replacements.set(targetIndex, {\n ...targetResult,\n score: Math.max(targetResult.score, linkedResult.score),\n source: linkedResult.score > targetResult.score ? linkedResult.source : targetResult.source,\n presenter: linkedResult.presenter ?? targetResult.presenter,\n url: linkedResult.url ?? targetResult.url,\n links: linkedResult.links ?? targetResult.links,\n metadata: mergeResultMetadata(targetResult.metadata, linkedResult.metadata),\n })\n removedIndexes.add(index)\n }\n\n return results\n .map((result, index) => replacements.get(index) ?? result)\n .filter((_, index) => !removedIndexes.has(index))\n .sort((left, right) => right.score - left.score)\n}\n\n/**\n * Compute presenter, URL, and links for a single doc using config or fallback.\n * Returns presenter (null if cannot be computed), and optionally URL/links from config.\n */\nasync function computePresenterAndLinks(\n doc: Record<string, unknown>,\n entityId: string,\n recordId: string,\n config: SearchEntityConfig | undefined,\n tenantId: string,\n organizationId: string | null | undefined,\n queryEngine: QueryEngine | undefined,\n): Promise<EnrichmentResult> {\n let presenter: SearchResultPresenter | null = null\n let url: string | undefined\n let links: SearchResultLink[] | undefined\n\n // Build context for config functions\n const customFields: Record<string, unknown> = {}\n for (const [key, value] of Object.entries(doc)) {\n if (key.startsWith('cf:') || key.startsWith('cf_')) {\n customFields[key.slice(3)] = value\n }\n }\n\n const buildContext: SearchBuildContext = {\n record: doc,\n customFields,\n organizationId,\n tenantId,\n queryEngine,\n }\n\n if (config?.formatResult || config?.buildSource) {\n if (config.formatResult) {\n try {\n presenter = (await config.formatResult(buildContext)) ?? null\n } catch (err) {\n logWarning('formatResult failed', { entityId, recordId, err })\n }\n }\n\n if (!presenter && config.buildSource) {\n try {\n const source = await config.buildSource(buildContext)\n if (source?.presenter) presenter = source.presenter\n if (source?.links) links = source.links\n } catch (err) {\n logWarning('buildSource failed', { entityId, recordId, err })\n }\n }\n }\n\n // Fallback presenter: extract from doc fields directly\n if (!presenter) {\n presenter = extractFallbackPresenter(doc, entityId, recordId)\n }\n\n // Resolve URL from config\n if (config?.resolveUrl) {\n try {\n url = (await config.resolveUrl(buildContext)) ?? undefined\n } catch {\n // Skip URL resolution errors\n }\n }\n\n // Resolve links from config (if not already set from buildSource)\n if (!links && config?.resolveLinks) {\n try {\n links = (await config.resolveLinks(buildContext)) ?? undefined\n } catch {\n // Skip link resolution errors\n }\n }\n\n return { presenter, url, links }\n}\n\n/**\n * Create a presenter enricher that loads data from entity_indexes and computes presenter.\n * Uses formatResult from search.ts configs when available, otherwise falls back to extracting\n * common fields like display_name, name, title from the doc.\n *\n * Optimizations:\n * - Single batch DB query for all entity types (instead of one per type)\n * - Parallel Promise.all for formatResult/buildSource calls\n * - Tenant/organization scoping for security\n * - Chunked queries to avoid DB parameter limits\n * - Automatic decryption of encrypted fields when encryption service is provided\n */\nexport function createPresenterEnricher(\n db: Kysely<any>,\n entityConfigMap: Map<EntityId, SearchEntityConfig>,\n queryEngine?: QueryEngine,\n encryptionService?: TenantDataEncryptionService | null,\n): PresenterEnricherFn {\n return async (results, tenantId, organizationId) => {\n const shouldEnrich = (result: SearchResult): boolean =>\n needsSearchResultEnrichment(result) || entityConfigMap.has(result.entityId as EntityId)\n\n const missingResults = results.filter(shouldEnrich)\n if (missingResults.length === 0) return results\n\n // Group by entity type for config lookup\n const byEntityType = new Map<string, SearchResult[]>()\n for (const result of missingResults) {\n const group = byEntityType.get(result.entityId) ?? []\n group.push(result)\n byEntityType.set(result.entityId, group)\n }\n\n // The kind lookup only matters to decryption, so skip the round trip entirely when no\n // encryption service will run \u2014 search is a hot path and the map would be dead weight.\n const decryptionActive = Boolean(encryptionService) && encryptionService?.isEnabled?.() !== false\n\n // Single batch query for all docs across all entity types\n const [rawDocs, kindResolversByEntityType] = await Promise.all([\n fetchDocsBatch(db, byEntityType, tenantId, organizationId),\n decryptionActive\n ? fetchCustomFieldKindResolversByEntityType(db, Array.from(byEntityType.keys()), tenantId)\n : Promise.resolve(new Map<string, CustomFieldKindMapResolver>()),\n ])\n\n // Decrypt docs in parallel using DEK cache for efficiency\n const dekCache = new Map<string | null, string | null>()\n\n const decryptedDocs = await Promise.all(\n rawDocs.map(async (row) => {\n try {\n // Use organization_id from the doc itself for proper encryption map lookup\n // This is critical for global search where organizationId param is null\n const docData = row.doc as Record<string, unknown>\n const docOrgId = (docData.organization_id as string | null | undefined) ?? organizationId\n const scope = { tenantId, organizationId: docOrgId }\n\n // Same scope for the kind as for the decryption itself \u2014 a result from another\n // organization must not be typed by this organization's definition.\n const resolveKinds = kindResolversByEntityType.get(row.entity_type)\n\n const decryptedDoc = await decryptIndexDocForSearch(\n row.entity_type,\n row.doc,\n scope,\n encryptionService ?? null,\n dekCache,\n resolveKinds ? resolveKinds(docOrgId, tenantId) : null,\n )\n return { ...row, doc: decryptedDoc }\n } catch (err) {\n logWarning('Failed to decrypt doc', { entityId: row.entity_type, recordId: row.entity_id, err })\n return row // Return original doc if decryption fails\n }\n }),\n )\n\n // Build doc lookup map for fast access\n const docMap = new Map<string, Record<string, unknown>>()\n for (const row of decryptedDocs) {\n docMap.set(`${row.entity_type}:${row.entity_id}`, row.doc)\n }\n\n // Compute presenters and links in parallel\n const enrichmentPromises = missingResults.map(async (result) => {\n const key = `${result.entityId}:${result.recordId}`\n const doc = docMap.get(key)\n\n if (!doc) {\n logWarning('Doc not found in entity_indexes', { entityId: result.entityId, recordId: result.recordId })\n return { key, presenter: null, url: undefined, links: undefined }\n }\n\n const config = entityConfigMap.get(result.entityId as EntityId)\n const enrichment = await computePresenterAndLinks(\n doc,\n result.entityId,\n result.recordId,\n config,\n tenantId,\n organizationId,\n queryEngine,\n )\n\n return { key, ...enrichment }\n })\n\n const computed = await Promise.all(enrichmentPromises)\n\n // Build enrichment map from parallel results\n const enrichmentMap = new Map<string, EnrichmentResult>()\n for (const { key, presenter, url, links } of computed) {\n enrichmentMap.set(key, { presenter, url, links })\n }\n\n // Enrich results with computed presenter, URL, and links\n const enrichedResults = results.map((result) => {\n if (!shouldEnrich(result)) return result\n const key = `${result.entityId}:${result.recordId}`\n const enriched = enrichmentMap.get(key)\n if (!enriched) return result\n return {\n ...result,\n presenter: enriched.presenter ?? result.presenter,\n url: result.url ?? enriched.url,\n links: enriched.links ?? result.links,\n }\n })\n\n return mergeLinkedDuplicateResults(enrichedResults)\n }\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["import type { Kysely } from 'kysely'\nimport type {\n SearchBuildContext,\n SearchResult,\n SearchResultPresenter,\n SearchResultLink,\n SearchEntityConfig,\n PresenterEnricherFn,\n} from '../types'\nimport type { QueryEngine } from '@open-mercato/shared/lib/query/types'\nimport type { EntityId } from '@open-mercato/shared/modules/entities'\nimport type { TenantDataEncryptionService } from '@open-mercato/shared/lib/encryption/tenantDataEncryptionService'\nimport { decryptIndexDocForSearch } from '@open-mercato/shared/lib/encryption/indexDoc'\nimport {\n buildCustomFieldKindIndex,\n createCustomFieldKindMapResolver,\n type CustomFieldKindMapResolver,\n} from '@open-mercato/shared/lib/custom-fields/kinds'\nimport { createLogger } from '@open-mercato/shared/lib/logger'\nimport { extractFallbackPresenter } from './fallback-presenter'\nimport { extractSearchableFields } from './field-policy'\nimport { needsSearchResultEnrichment } from './search-result-enrichment'\n\n/** Maximum number of record IDs per batch query to avoid hitting DB parameter limits */\nconst BATCH_SIZE = 500\n\n/** Diagnostic logger - surfaces issues without breaking flow, gated by OM_LOG_LEVEL=debug */\nconst enricherLogger = createLogger('search').child({ component: 'presenter-enricher' })\nconst logWarning = (message: string, context?: Record<string, unknown>) => {\n enricherLogger.debug(message, context)\n}\n\n/**\n * Split an array into chunks of specified size.\n */\nfunction chunk<T>(array: T[], size: number): T[][] {\n const chunks: T[][] = []\n for (let i = 0; i < array.length; i += size) {\n chunks.push(array.slice(i, i + size))\n }\n return chunks\n}\n\n/**\n * Load the `kind` of every active custom field on the entity types in this batch, as a\n * per-entity-type resolver. Without it `decryptIndexDocForSearch` runs `JSON.parse` over\n * decrypted string-typed values and returns `123` where `\"123\"` was stored (issue #5968).\n *\n * The definitions' scope columns come along so each result row can be typed by the\n * definition that applies to *its* organization: global search spans organizations, and one\n * of them may override a key with a different `kind` than another.\n *\n * Fails open: a lookup error yields an empty map, which keeps the previous behavior rather\n * than breaking search.\n */\nasync function fetchCustomFieldKindResolversByEntityType(\n db: Kysely<any>,\n entityTypes: string[],\n tenantId: string,\n): Promise<Map<string, CustomFieldKindMapResolver>> {\n const byEntityType = new Map<string, CustomFieldKindMapResolver>()\n if (!entityTypes.length) return byEntityType\n try {\n const rows = await db\n .selectFrom('custom_field_defs')\n .select(['entity_id', 'key', 'kind', 'organization_id', 'tenant_id', 'config_json', 'updated_at'])\n .where('entity_id', 'in', entityTypes)\n .where('is_active', '=', true)\n .where((eb: any) => eb.or([\n eb('tenant_id', '=', tenantId),\n eb('tenant_id', 'is', null),\n ]))\n .execute() as Array<Record<string, unknown>>\n const grouped = new Map<string, Array<Record<string, unknown>>>()\n for (const row of rows) {\n const entityType = typeof row.entity_id === 'string' ? row.entity_id : String(row.entity_id ?? '')\n if (!entityType) continue\n const group = grouped.get(entityType) ?? []\n group.push(row)\n grouped.set(entityType, group)\n }\n for (const [entityType, group] of grouped) {\n const index = buildCustomFieldKindIndex(group.map((row) => ({\n key: row.key,\n kind: row.kind,\n organizationId: row.organization_id,\n tenantId: row.tenant_id,\n configJson: row.config_json,\n updatedAt: row.updated_at,\n })))\n byEntityType.set(entityType, createCustomFieldKindMapResolver(index))\n }\n } catch (err) {\n logWarning('Failed to resolve custom field kinds', { err })\n }\n return byEntityType\n}\n\n/**\n * Build a single batch query for multiple entity types and their record IDs.\n * Uses OR conditions to fetch all needed docs in one round trip.\n */\nasync function fetchDocsBatch(\n db: Kysely<any>,\n byEntityType: Map<string, SearchResult[]>,\n tenantId: string,\n organizationId?: string | null,\n): Promise<Array<{ entity_type: string; entity_id: string; doc: Record<string, unknown> }>> {\n const allDocs: Array<{ entity_type: string; entity_id: string; doc: Record<string, unknown> }> = []\n\n // Collect all entity type + record ID pairs\n const allPairs: Array<{ entityType: string; recordId: string }> = []\n for (const [entityType, results] of byEntityType) {\n for (const result of results) {\n allPairs.push({ entityType, recordId: result.recordId })\n }\n }\n\n if (allPairs.length === 0) return allDocs\n\n // Process in chunks to avoid hitting DB parameter limits\n const chunks = chunk(allPairs, BATCH_SIZE)\n\n for (const pairChunk of chunks) {\n // Group by entity type within this chunk for efficient OR query\n const chunkByType = new Map<string, string[]>()\n for (const { entityType, recordId } of pairChunk) {\n const ids = chunkByType.get(entityType) ?? []\n ids.push(recordId)\n chunkByType.set(entityType, ids)\n }\n\n // Build query with OR conditions per entity type\n let query = db\n .selectFrom('entity_indexes' as any)\n .select(['entity_type' as any, 'entity_id' as any, 'doc' as any])\n .where('tenant_id' as any, '=', tenantId)\n .where('deleted_at' as any, 'is', null)\n .where((eb: any) => eb.or(\n Array.from(chunkByType.entries()).map(([entityType, recordIds]) => eb.and([\n eb('entity_type' as any, '=', entityType),\n eb('entity_id' as any, 'in', recordIds),\n ])),\n ))\n\n // Add organization filter if provided\n if (organizationId) {\n query = query.where((eb: any) => eb.or([\n eb('organization_id' as any, '=', organizationId),\n eb('organization_id' as any, 'is', null),\n ]))\n }\n\n const rows = await query.execute() as Array<{ entity_type: string; entity_id: string; doc: Record<string, unknown> }>\n allDocs.push(...rows)\n }\n\n return allDocs\n}\n\n/** Result type for presenter and links computation */\ntype EnrichmentResult = {\n presenter: SearchResultPresenter | null\n url?: string\n links?: SearchResultLink[]\n}\n\nfunction primaryNavigationHref(result: SearchResult): string | null {\n if (typeof result.url === 'string' && result.url.trim().length > 0) {\n return result.url.trim()\n }\n const primaryLink = result.links?.find((link) => link.kind === 'primary' && link.href.trim().length > 0)\n return primaryLink?.href.trim() ?? null\n}\n\nfunction directNavigationRecordId(href: string): string | null {\n try {\n const url = new URL(href, 'http://search.local')\n if (url.search || url.hash) return null\n const segments = url.pathname.split('/').filter(Boolean)\n const lastSegment = segments.at(-1)\n return lastSegment ? decodeURIComponent(lastSegment) : null\n } catch {\n return null\n }\n}\n\nfunction presenterTitle(result: SearchResult): string | null {\n const title = result.presenter?.title?.trim()\n return title?.length ? title : null\n}\n\nfunction resultScopeKey(result: SearchResult, recordId: string): string {\n return `${result.organizationId ?? ''}:${recordId}`\n}\n\nfunction mergeResultMetadata(\n targetMetadata: SearchResult['metadata'],\n linkedMetadata: SearchResult['metadata'],\n): SearchResult['metadata'] {\n if (!targetMetadata && !linkedMetadata) return undefined\n return {\n ...targetMetadata,\n ...linkedMetadata,\n }\n}\n\nfunction mergeLinkedDuplicateResults(results: SearchResult[]): SearchResult[] {\n const indexesByRecord = new Map<string, number[]>()\n for (let index = 0; index < results.length; index += 1) {\n const result = results[index]\n const key = resultScopeKey(result, result.recordId)\n const indexes = indexesByRecord.get(key) ?? []\n indexes.push(index)\n indexesByRecord.set(key, indexes)\n }\n\n const replacements = new Map<number, SearchResult>()\n const removedIndexes = new Set<number>()\n\n for (let index = 0; index < results.length; index += 1) {\n const linkedResult = results[index]\n const href = primaryNavigationHref(linkedResult)\n const targetRecordId = href ? directNavigationRecordId(href) : null\n if (!targetRecordId || targetRecordId === linkedResult.recordId) continue\n\n const targetIndexes = (indexesByRecord.get(resultScopeKey(linkedResult, targetRecordId)) ?? [])\n .filter((candidateIndex) => candidateIndex !== index && !removedIndexes.has(candidateIndex))\n if (targetIndexes.length !== 1) continue\n\n const targetIndex = targetIndexes[0]\n const targetResult = replacements.get(targetIndex) ?? results[targetIndex]\n if (primaryNavigationHref(targetResult)) continue\n\n const linkedTitle = presenterTitle(linkedResult)\n const targetTitle = presenterTitle(targetResult)\n if (!linkedTitle || linkedTitle !== targetTitle) continue\n\n replacements.set(targetIndex, {\n ...targetResult,\n score: Math.max(targetResult.score, linkedResult.score),\n source: linkedResult.score > targetResult.score ? linkedResult.source : targetResult.source,\n presenter: linkedResult.presenter ?? targetResult.presenter,\n url: linkedResult.url ?? targetResult.url,\n links: linkedResult.links ?? targetResult.links,\n metadata: mergeResultMetadata(targetResult.metadata, linkedResult.metadata),\n })\n removedIndexes.add(index)\n }\n\n return results\n .map((result, index) => replacements.get(index) ?? result)\n .filter((_, index) => !removedIndexes.has(index))\n .sort((left, right) => right.score - left.score)\n}\n\n/**\n * Compute presenter, URL, and links for a single doc using config or fallback.\n * Returns presenter (null if cannot be computed), and optionally URL/links from config.\n */\nasync function computePresenterAndLinks(\n doc: Record<string, unknown>,\n entityId: string,\n recordId: string,\n config: SearchEntityConfig | undefined,\n tenantId: string,\n organizationId: string | null | undefined,\n queryEngine: QueryEngine | undefined,\n): Promise<EnrichmentResult> {\n let presenter: SearchResultPresenter | null = null\n let url: string | undefined\n let links: SearchResultLink[] | undefined\n\n // Build context for config functions\n const customFields: Record<string, unknown> = {}\n for (const [key, value] of Object.entries(doc)) {\n if (key.startsWith('cf:') || key.startsWith('cf_')) {\n customFields[key.slice(3)] = value\n }\n }\n\n const buildContext: SearchBuildContext = {\n record: doc,\n customFields,\n organizationId,\n tenantId,\n queryEngine,\n }\n\n if (config?.formatResult || config?.buildSource) {\n if (config.formatResult) {\n try {\n presenter = (await config.formatResult(buildContext)) ?? null\n } catch (err) {\n logWarning('formatResult failed', { entityId, recordId, err })\n }\n }\n\n if (!presenter && config.buildSource) {\n try {\n const source = await config.buildSource(buildContext)\n if (source?.presenter) presenter = source.presenter\n if (source?.links) links = source.links\n } catch (err) {\n logWarning('buildSource failed', { entityId, recordId, err })\n }\n }\n }\n\n // Fallback presenter: extract from doc fields directly\n if (!presenter) {\n presenter = extractFallbackPresenter(extractSearchableFields(doc, { fieldPolicy: config?.fieldPolicy }), entityId, recordId)\n }\n\n // Resolve URL from config\n if (config?.resolveUrl) {\n try {\n url = (await config.resolveUrl(buildContext)) ?? undefined\n } catch {\n // Skip URL resolution errors\n }\n }\n\n // Resolve links from config (if not already set from buildSource)\n if (!links && config?.resolveLinks) {\n try {\n links = (await config.resolveLinks(buildContext)) ?? undefined\n } catch {\n // Skip link resolution errors\n }\n }\n\n return { presenter, url, links }\n}\n\n/**\n * Create a presenter enricher that loads data from entity_indexes and computes presenter.\n * Uses formatResult from search.ts configs when available, otherwise falls back to extracting\n * common fields like display_name, name, title from the doc.\n *\n * Optimizations:\n * - Single batch DB query for all entity types (instead of one per type)\n * - Parallel Promise.all for formatResult/buildSource calls\n * - Tenant/organization scoping for security\n * - Chunked queries to avoid DB parameter limits\n * - Automatic decryption of encrypted fields when encryption service is provided\n */\nexport function createPresenterEnricher(\n db: Kysely<any>,\n entityConfigMap: Map<EntityId, SearchEntityConfig>,\n queryEngine?: QueryEngine,\n encryptionService?: TenantDataEncryptionService | null,\n): PresenterEnricherFn {\n return async (results, tenantId, organizationId) => {\n const shouldEnrich = (result: SearchResult): boolean =>\n needsSearchResultEnrichment(result) || entityConfigMap.has(result.entityId as EntityId)\n\n const missingResults = results.filter(shouldEnrich)\n if (missingResults.length === 0) return results\n\n // Group by entity type for config lookup\n const byEntityType = new Map<string, SearchResult[]>()\n for (const result of missingResults) {\n const group = byEntityType.get(result.entityId) ?? []\n group.push(result)\n byEntityType.set(result.entityId, group)\n }\n\n // The kind lookup only matters to decryption, so skip the round trip entirely when no\n // encryption service will run \u2014 search is a hot path and the map would be dead weight.\n const decryptionActive = Boolean(encryptionService) && encryptionService?.isEnabled?.() !== false\n\n // Single batch query for all docs across all entity types\n const [rawDocs, kindResolversByEntityType] = await Promise.all([\n fetchDocsBatch(db, byEntityType, tenantId, organizationId),\n decryptionActive\n ? fetchCustomFieldKindResolversByEntityType(db, Array.from(byEntityType.keys()), tenantId)\n : Promise.resolve(new Map<string, CustomFieldKindMapResolver>()),\n ])\n\n // Decrypt docs in parallel using DEK cache for efficiency\n const dekCache = new Map<string | null, string | null>()\n\n const decryptedDocs = await Promise.all(\n rawDocs.map(async (row) => {\n try {\n // Use organization_id from the doc itself for proper encryption map lookup\n // This is critical for global search where organizationId param is null\n const docData = row.doc as Record<string, unknown>\n const docOrgId = (docData.organization_id as string | null | undefined) ?? organizationId\n const scope = { tenantId, organizationId: docOrgId }\n\n // Same scope for the kind as for the decryption itself \u2014 a result from another\n // organization must not be typed by this organization's definition.\n const resolveKinds = kindResolversByEntityType.get(row.entity_type)\n\n const decryptedDoc = await decryptIndexDocForSearch(\n row.entity_type,\n row.doc,\n scope,\n encryptionService ?? null,\n dekCache,\n resolveKinds ? resolveKinds(docOrgId, tenantId) : null,\n )\n return { ...row, doc: decryptedDoc }\n } catch (err) {\n logWarning('Failed to decrypt doc', { entityId: row.entity_type, recordId: row.entity_id, err })\n return row // Return original doc if decryption fails\n }\n }),\n )\n\n // Build doc lookup map for fast access\n const docMap = new Map<string, Record<string, unknown>>()\n for (const row of decryptedDocs) {\n docMap.set(`${row.entity_type}:${row.entity_id}`, row.doc)\n }\n\n // Compute presenters and links in parallel\n const enrichmentPromises = missingResults.map(async (result) => {\n const key = `${result.entityId}:${result.recordId}`\n const doc = docMap.get(key)\n\n if (!doc) {\n logWarning('Doc not found in entity_indexes', { entityId: result.entityId, recordId: result.recordId })\n return { key, presenter: null, url: undefined, links: undefined }\n }\n\n const config = entityConfigMap.get(result.entityId as EntityId)\n const enrichment = await computePresenterAndLinks(\n doc,\n result.entityId,\n result.recordId,\n config,\n tenantId,\n organizationId,\n queryEngine,\n )\n\n return { key, ...enrichment }\n })\n\n const computed = await Promise.all(enrichmentPromises)\n\n // Build enrichment map from parallel results\n const enrichmentMap = new Map<string, EnrichmentResult>()\n for (const { key, presenter, url, links } of computed) {\n enrichmentMap.set(key, { presenter, url, links })\n }\n\n // Enrich results with computed presenter, URL, and links\n const enrichedResults = results.map((result) => {\n if (!shouldEnrich(result)) return result\n const key = `${result.entityId}:${result.recordId}`\n const enriched = enrichmentMap.get(key)\n if (!enriched) return result\n return {\n ...result,\n presenter: enriched.presenter ?? result.presenter,\n url: result.url ?? enriched.url,\n links: enriched.links ?? result.links,\n }\n })\n\n return mergeLinkedDuplicateResults(enrichedResults)\n }\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -77,8 +77,8 @@ class VectorSearchStrategy {
77
77
  }
78
78
  async index(record) {
79
79
  await this.ensureReady();
80
- const textContent = record.text ? Array.isArray(record.text) ? record.text.join("\n") : record.text : this.buildTextContent(record);
81
- if (!textContent) return;
80
+ const textContent = record.text !== void 0 ? Array.isArray(record.text) ? record.text.join("\n") : record.text : this.buildTextContent(record);
81
+ if (!textContent.trim()) return;
82
82
  const embedding = await this.embeddingService.createEmbedding(textContent);
83
83
  const doc = {
84
84
  entityId: record.entityId,
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/strategies/vector.strategy.ts"],
4
- "sourcesContent": ["import { createHash } from 'crypto'\nimport type {\n SearchStrategy,\n SearchStrategyId,\n SearchOptions,\n SearchResult,\n IndexableRecord,\n} from '../types'\nimport type { EntityId } from '@open-mercato/shared/modules/entities'\nimport type { VectorDriver, VectorDriverDocument } from '../vector/types'\nimport { searchDebugWarn } from '../lib/debug'\n\n/**\n * Embedding service interface - minimal subset needed by VectorSearchStrategy.\n */\nexport interface EmbeddingService {\n createEmbedding(text: string): Promise<number[]>\n available: boolean\n}\n\n/**\n * Configuration for VectorSearchStrategy.\n */\nexport type VectorStrategyConfig = {\n /** Default limit for search results */\n defaultLimit?: number\n}\n\nfunction normalizeOrganizationIds(options: SearchOptions): string[] | null {\n const single = typeof options.organizationId === 'string' ? options.organizationId.trim() : ''\n if (single) return [single]\n if (!Array.isArray(options.organizationIds)) return null\n return Array.from(new Set(\n options.organizationIds\n .map((value) => (typeof value === 'string' ? value.trim() : ''))\n .filter((value) => value.length > 0),\n ))\n}\n\n/**\n * VectorSearchStrategy provides semantic search using embeddings.\n * It wraps the existing vector module infrastructure.\n */\nexport class VectorSearchStrategy implements SearchStrategy {\n readonly id: SearchStrategyId = 'vector'\n readonly name = 'Vector Search'\n readonly priority = 20 // Medium priority\n\n get supportsIndexDocFilter(): boolean {\n return this.vectorDriver.supportsIndexDocFilter === true\n }\n\n private readonly defaultLimit: number\n private ready = false\n private readyPromise: Promise<void> | null = null\n\n constructor(\n private readonly embeddingService: EmbeddingService,\n private readonly vectorDriver: VectorDriver,\n config?: VectorStrategyConfig,\n ) {\n this.defaultLimit = config?.defaultLimit ?? 20\n }\n\n async isAvailable(): Promise<boolean> {\n if (!this.embeddingService.available) return false\n // A configured embedding provider says nothing about the vector store. When the\n // store cannot serve writes (pgvector extension missing), reporting availability\n // here makes SearchService call ensureReady() for every record and fail the whole\n // index/delete operation. Drivers without a probe keep the previous behavior.\n if (!this.vectorDriver.isHealthy) return true\n return this.vectorDriver.isHealthy()\n }\n\n async ensureReady(): Promise<void> {\n if (this.ready) return\n if (!this.readyPromise) {\n this.readyPromise = this.vectorDriver.ensureReady().then(() => {\n this.ready = true\n })\n }\n return this.readyPromise\n }\n\n async search(query: string, options: SearchOptions): Promise<SearchResult[]> {\n const organizationIds = normalizeOrganizationIds(options)\n if (organizationIds && organizationIds.length === 0) return []\n if (options.indexDocFilter && !this.supportsIndexDocFilter) return []\n\n await this.ensureReady()\n const embedding = await this.embeddingService.createEmbedding(query)\n\n // Build filter - only include organizationId if it's a real value\n // The pgvector driver treats null as \"only records with null org_id\",\n // but we want null/undefined to mean \"no organization filter\"\n const filter: {\n tenantId: string\n organizationId?: string | null\n organizationIds?: string[] | null\n entityIds?: EntityId[]\n indexDocFilter?: SearchOptions['indexDocFilter']\n } = {\n tenantId: options.tenantId,\n entityIds: options.entityTypes as EntityId[],\n ...(options.indexDocFilter ? { indexDocFilter: options.indexDocFilter } : {}),\n }\n\n if (organizationIds) {\n filter.organizationIds = organizationIds\n if (organizationIds.length === 1) {\n filter.organizationId = organizationIds[0]\n }\n }\n\n const results = await this.vectorDriver.query({\n vector: embedding,\n limit: options.limit ?? this.defaultLimit,\n filter,\n })\n\n return results.map((hit) => ({\n entityId: hit.entityId,\n recordId: hit.recordId,\n score: hit.score,\n source: this.id,\n organizationId: hit.organizationId ?? null,\n presenter: hit.presenter ?? undefined,\n url: hit.primaryLinkHref ?? hit.url ?? undefined,\n links: hit.links?.map((link) => ({\n href: link.href,\n label: link.label ?? '',\n kind: link.kind,\n })),\n metadata: hit.payload ?? undefined,\n }))\n }\n\n async index(record: IndexableRecord): Promise<void> {\n await this.ensureReady()\n // Use text from buildSource if available, otherwise fall back to generic extraction\n const textContent = record.text\n ? (Array.isArray(record.text) ? record.text.join('\\n') : record.text)\n : this.buildTextContent(record)\n if (!textContent) return\n\n const embedding = await this.embeddingService.createEmbedding(textContent)\n\n const doc: VectorDriverDocument = {\n entityId: record.entityId as EntityId,\n recordId: record.recordId,\n tenantId: record.tenantId,\n organizationId: record.organizationId,\n checksum: this.computeChecksum(record),\n embedding,\n url: record.url,\n presenter: record.presenter,\n links: record.links,\n driverId: this.vectorDriver.id,\n resultTitle: record.presenter?.title ?? record.recordId,\n resultSubtitle: record.presenter?.subtitle,\n resultIcon: record.presenter?.icon,\n resultBadge: record.presenter?.badge,\n }\n\n await this.vectorDriver.upsert(doc)\n }\n\n async delete(entityId: EntityId, recordId: string, tenantId: string): Promise<void> {\n await this.ensureReady()\n await this.vectorDriver.delete(entityId, recordId, tenantId)\n }\n\n async purge(entityId: EntityId, tenantId: string, organizationId?: string | null): Promise<void> {\n await this.ensureReady()\n if (this.vectorDriver.purge) {\n await this.vectorDriver.purge(entityId, tenantId, organizationId)\n }\n }\n\n /**\n * Build text content from record fields for embedding.\n */\n private buildTextContent(record: IndexableRecord): string {\n const parts: string[] = []\n\n // Add presenter info\n if (record.presenter?.title) {\n parts.push(record.presenter.title)\n }\n if (record.presenter?.subtitle) {\n parts.push(record.presenter.subtitle)\n }\n\n // Add string fields from record\n for (const [, value] of Object.entries(record.fields)) {\n if (typeof value === 'string' && value.trim()) {\n parts.push(value)\n }\n }\n\n return parts.join(' ').trim()\n }\n\n /**\n * Compute a checksum for change detection using SHA-256.\n * Uses checksumSource from buildSource if available, otherwise uses fields/presenter/url.\n */\n private computeChecksum(record: IndexableRecord): string {\n const source = record.checksumSource !== undefined\n ? record.checksumSource\n : {\n fields: record.fields,\n presenter: record.presenter,\n url: record.url,\n }\n const content = JSON.stringify(source)\n return createHash('sha256').update(content).digest('hex').slice(0, 16)\n }\n\n /**\n * List entries in the vector index (for admin/debugging).\n */\n async listEntries(options: {\n tenantId: string\n organizationId?: string | null\n entityId?: string\n limit?: number\n offset?: number\n }): Promise<Array<{\n entityId: string\n recordId: string\n tenantId: string\n organizationId: string | null\n presenter?: unknown\n url?: string\n }>> {\n await this.ensureReady()\n // Delegate to vector driver's list method if available\n const listMethod = (this.vectorDriver as unknown as {\n list?: (options: {\n tenantId: string\n organizationId?: string | null\n entityId?: string\n limit?: number\n offset?: number\n }) => Promise<unknown[]>\n }).list\n\n if (typeof listMethod === 'function') {\n const entries = await listMethod.call(this.vectorDriver, options)\n return entries as Array<{\n entityId: string\n recordId: string\n tenantId: string\n organizationId: string | null\n presenter?: unknown\n url?: string\n }>\n }\n\n // Fallback: return empty array if driver doesn't support listing\n searchDebugWarn('VectorSearchStrategy', 'Vector driver does not support listing entries')\n return []\n }\n}\n"],
5
- "mappings": "AAAA,SAAS,kBAAkB;AAU3B,SAAS,uBAAuB;AAkBhC,SAAS,yBAAyB,SAAyC;AACzE,QAAM,SAAS,OAAO,QAAQ,mBAAmB,WAAW,QAAQ,eAAe,KAAK,IAAI;AAC5F,MAAI,OAAQ,QAAO,CAAC,MAAM;AAC1B,MAAI,CAAC,MAAM,QAAQ,QAAQ,eAAe,EAAG,QAAO;AACpD,SAAO,MAAM,KAAK,IAAI;AAAA,IACpB,QAAQ,gBACL,IAAI,CAAC,UAAW,OAAO,UAAU,WAAW,MAAM,KAAK,IAAI,EAAG,EAC9D,OAAO,CAAC,UAAU,MAAM,SAAS,CAAC;AAAA,EACvC,CAAC;AACH;AAMO,MAAM,qBAA+C;AAAA,EAa1D,YACmB,kBACA,cACjB,QACA;AAHiB;AACA;AAdnB,SAAS,KAAuB;AAChC,SAAS,OAAO;AAChB,SAAS,WAAW;AAOpB,SAAQ,QAAQ;AAChB,SAAQ,eAAqC;AAO3C,SAAK,eAAe,QAAQ,gBAAgB;AAAA,EAC9C;AAAA;AAAA,EAdA,IAAI,yBAAkC;AACpC,WAAO,KAAK,aAAa,2BAA2B;AAAA,EACtD;AAAA,EAcA,MAAM,cAAgC;AACpC,QAAI,CAAC,KAAK,iBAAiB,UAAW,QAAO;AAK7C,QAAI,CAAC,KAAK,aAAa,UAAW,QAAO;AACzC,WAAO,KAAK,aAAa,UAAU;AAAA,EACrC;AAAA,EAEA,MAAM,cAA6B;AACjC,QAAI,KAAK,MAAO;AAChB,QAAI,CAAC,KAAK,cAAc;AACtB,WAAK,eAAe,KAAK,aAAa,YAAY,EAAE,KAAK,MAAM;AAC7D,aAAK,QAAQ;AAAA,MACf,CAAC;AAAA,IACH;AACA,WAAO,KAAK;AAAA,EACd;AAAA,EAEA,MAAM,OAAO,OAAe,SAAiD;AAC3E,UAAM,kBAAkB,yBAAyB,OAAO;AACxD,QAAI,mBAAmB,gBAAgB,WAAW,EAAG,QAAO,CAAC;AAC7D,QAAI,QAAQ,kBAAkB,CAAC,KAAK,uBAAwB,QAAO,CAAC;AAEpE,UAAM,KAAK,YAAY;AACvB,UAAM,YAAY,MAAM,KAAK,iBAAiB,gBAAgB,KAAK;AAKnE,UAAM,SAMF;AAAA,MACF,UAAU,QAAQ;AAAA,MAClB,WAAW,QAAQ;AAAA,MACnB,GAAI,QAAQ,iBAAiB,EAAE,gBAAgB,QAAQ,eAAe,IAAI,CAAC;AAAA,IAC7E;AAEA,QAAI,iBAAiB;AACnB,aAAO,kBAAkB;AACzB,UAAI,gBAAgB,WAAW,GAAG;AAChC,eAAO,iBAAiB,gBAAgB,CAAC;AAAA,MAC3C;AAAA,IACF;AAEA,UAAM,UAAU,MAAM,KAAK,aAAa,MAAM;AAAA,MAC5C,QAAQ;AAAA,MACR,OAAO,QAAQ,SAAS,KAAK;AAAA,MAC7B;AAAA,IACF,CAAC;AAED,WAAO,QAAQ,IAAI,CAAC,SAAS;AAAA,MAC3B,UAAU,IAAI;AAAA,MACd,UAAU,IAAI;AAAA,MACd,OAAO,IAAI;AAAA,MACX,QAAQ,KAAK;AAAA,MACb,gBAAgB,IAAI,kBAAkB;AAAA,MACtC,WAAW,IAAI,aAAa;AAAA,MAC5B,KAAK,IAAI,mBAAmB,IAAI,OAAO;AAAA,MACvC,OAAO,IAAI,OAAO,IAAI,CAAC,UAAU;AAAA,QAC/B,MAAM,KAAK;AAAA,QACX,OAAO,KAAK,SAAS;AAAA,QACrB,MAAM,KAAK;AAAA,MACb,EAAE;AAAA,MACF,UAAU,IAAI,WAAW;AAAA,IAC3B,EAAE;AAAA,EACJ;AAAA,EAEA,MAAM,MAAM,QAAwC;AAClD,UAAM,KAAK,YAAY;AAEvB,UAAM,cAAc,OAAO,OACtB,MAAM,QAAQ,OAAO,IAAI,IAAI,OAAO,KAAK,KAAK,IAAI,IAAI,OAAO,OAC9D,KAAK,iBAAiB,MAAM;AAChC,QAAI,CAAC,YAAa;AAElB,UAAM,YAAY,MAAM,KAAK,iBAAiB,gBAAgB,WAAW;AAEzE,UAAM,MAA4B;AAAA,MAChC,UAAU,OAAO;AAAA,MACjB,UAAU,OAAO;AAAA,MACjB,UAAU,OAAO;AAAA,MACjB,gBAAgB,OAAO;AAAA,MACvB,UAAU,KAAK,gBAAgB,MAAM;AAAA,MACrC;AAAA,MACA,KAAK,OAAO;AAAA,MACZ,WAAW,OAAO;AAAA,MAClB,OAAO,OAAO;AAAA,MACd,UAAU,KAAK,aAAa;AAAA,MAC5B,aAAa,OAAO,WAAW,SAAS,OAAO;AAAA,MAC/C,gBAAgB,OAAO,WAAW;AAAA,MAClC,YAAY,OAAO,WAAW;AAAA,MAC9B,aAAa,OAAO,WAAW;AAAA,IACjC;AAEA,UAAM,KAAK,aAAa,OAAO,GAAG;AAAA,EACpC;AAAA,EAEA,MAAM,OAAO,UAAoB,UAAkB,UAAiC;AAClF,UAAM,KAAK,YAAY;AACvB,UAAM,KAAK,aAAa,OAAO,UAAU,UAAU,QAAQ;AAAA,EAC7D;AAAA,EAEA,MAAM,MAAM,UAAoB,UAAkB,gBAA+C;AAC/F,UAAM,KAAK,YAAY;AACvB,QAAI,KAAK,aAAa,OAAO;AAC3B,YAAM,KAAK,aAAa,MAAM,UAAU,UAAU,cAAc;AAAA,IAClE;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKQ,iBAAiB,QAAiC;AACxD,UAAM,QAAkB,CAAC;AAGzB,QAAI,OAAO,WAAW,OAAO;AAC3B,YAAM,KAAK,OAAO,UAAU,KAAK;AAAA,IACnC;AACA,QAAI,OAAO,WAAW,UAAU;AAC9B,YAAM,KAAK,OAAO,UAAU,QAAQ;AAAA,IACtC;AAGA,eAAW,CAAC,EAAE,KAAK,KAAK,OAAO,QAAQ,OAAO,MAAM,GAAG;AACrD,UAAI,OAAO,UAAU,YAAY,MAAM,KAAK,GAAG;AAC7C,cAAM,KAAK,KAAK;AAAA,MAClB;AAAA,IACF;AAEA,WAAO,MAAM,KAAK,GAAG,EAAE,KAAK;AAAA,EAC9B;AAAA;AAAA;AAAA;AAAA;AAAA,EAMQ,gBAAgB,QAAiC;AACvD,UAAM,SAAS,OAAO,mBAAmB,SACrC,OAAO,iBACP;AAAA,MACE,QAAQ,OAAO;AAAA,MACf,WAAW,OAAO;AAAA,MAClB,KAAK,OAAO;AAAA,IACd;AACJ,UAAM,UAAU,KAAK,UAAU,MAAM;AACrC,WAAO,WAAW,QAAQ,EAAE,OAAO,OAAO,EAAE,OAAO,KAAK,EAAE,MAAM,GAAG,EAAE;AAAA,EACvE;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,YAAY,SAad;AACF,UAAM,KAAK,YAAY;AAEvB,UAAM,aAAc,KAAK,aAQtB;AAEH,QAAI,OAAO,eAAe,YAAY;AACpC,YAAM,UAAU,MAAM,WAAW,KAAK,KAAK,cAAc,OAAO;AAChE,aAAO;AAAA,IAQT;AAGA,oBAAgB,wBAAwB,gDAAgD;AACxF,WAAO,CAAC;AAAA,EACV;AACF;",
4
+ "sourcesContent": ["import { createHash } from 'crypto'\nimport type {\n SearchStrategy,\n SearchStrategyId,\n SearchOptions,\n SearchResult,\n IndexableRecord,\n} from '../types'\nimport type { EntityId } from '@open-mercato/shared/modules/entities'\nimport type { VectorDriver, VectorDriverDocument } from '../vector/types'\nimport { searchDebugWarn } from '../lib/debug'\n\n/**\n * Embedding service interface - minimal subset needed by VectorSearchStrategy.\n */\nexport interface EmbeddingService {\n createEmbedding(text: string): Promise<number[]>\n available: boolean\n}\n\n/**\n * Configuration for VectorSearchStrategy.\n */\nexport type VectorStrategyConfig = {\n /** Default limit for search results */\n defaultLimit?: number\n}\n\nfunction normalizeOrganizationIds(options: SearchOptions): string[] | null {\n const single = typeof options.organizationId === 'string' ? options.organizationId.trim() : ''\n if (single) return [single]\n if (!Array.isArray(options.organizationIds)) return null\n return Array.from(new Set(\n options.organizationIds\n .map((value) => (typeof value === 'string' ? value.trim() : ''))\n .filter((value) => value.length > 0),\n ))\n}\n\n/**\n * VectorSearchStrategy provides semantic search using embeddings.\n * It wraps the existing vector module infrastructure.\n */\nexport class VectorSearchStrategy implements SearchStrategy {\n readonly id: SearchStrategyId = 'vector'\n readonly name = 'Vector Search'\n readonly priority = 20 // Medium priority\n\n get supportsIndexDocFilter(): boolean {\n return this.vectorDriver.supportsIndexDocFilter === true\n }\n\n private readonly defaultLimit: number\n private ready = false\n private readyPromise: Promise<void> | null = null\n\n constructor(\n private readonly embeddingService: EmbeddingService,\n private readonly vectorDriver: VectorDriver,\n config?: VectorStrategyConfig,\n ) {\n this.defaultLimit = config?.defaultLimit ?? 20\n }\n\n async isAvailable(): Promise<boolean> {\n if (!this.embeddingService.available) return false\n // A configured embedding provider says nothing about the vector store. When the\n // store cannot serve writes (pgvector extension missing), reporting availability\n // here makes SearchService call ensureReady() for every record and fail the whole\n // index/delete operation. Drivers without a probe keep the previous behavior.\n if (!this.vectorDriver.isHealthy) return true\n return this.vectorDriver.isHealthy()\n }\n\n async ensureReady(): Promise<void> {\n if (this.ready) return\n if (!this.readyPromise) {\n this.readyPromise = this.vectorDriver.ensureReady().then(() => {\n this.ready = true\n })\n }\n return this.readyPromise\n }\n\n async search(query: string, options: SearchOptions): Promise<SearchResult[]> {\n const organizationIds = normalizeOrganizationIds(options)\n if (organizationIds && organizationIds.length === 0) return []\n if (options.indexDocFilter && !this.supportsIndexDocFilter) return []\n\n await this.ensureReady()\n const embedding = await this.embeddingService.createEmbedding(query)\n\n // Build filter - only include organizationId if it's a real value\n // The pgvector driver treats null as \"only records with null org_id\",\n // but we want null/undefined to mean \"no organization filter\"\n const filter: {\n tenantId: string\n organizationId?: string | null\n organizationIds?: string[] | null\n entityIds?: EntityId[]\n indexDocFilter?: SearchOptions['indexDocFilter']\n } = {\n tenantId: options.tenantId,\n entityIds: options.entityTypes as EntityId[],\n ...(options.indexDocFilter ? { indexDocFilter: options.indexDocFilter } : {}),\n }\n\n if (organizationIds) {\n filter.organizationIds = organizationIds\n if (organizationIds.length === 1) {\n filter.organizationId = organizationIds[0]\n }\n }\n\n const results = await this.vectorDriver.query({\n vector: embedding,\n limit: options.limit ?? this.defaultLimit,\n filter,\n })\n\n return results.map((hit) => ({\n entityId: hit.entityId,\n recordId: hit.recordId,\n score: hit.score,\n source: this.id,\n organizationId: hit.organizationId ?? null,\n presenter: hit.presenter ?? undefined,\n url: hit.primaryLinkHref ?? hit.url ?? undefined,\n links: hit.links?.map((link) => ({\n href: link.href,\n label: link.label ?? '',\n kind: link.kind,\n })),\n metadata: hit.payload ?? undefined,\n }))\n }\n\n async index(record: IndexableRecord): Promise<void> {\n await this.ensureReady()\n // Use text from buildSource if available, otherwise fall back to generic extraction\n const textContent = record.text !== undefined\n ? (Array.isArray(record.text) ? record.text.join('\\n') : record.text)\n : this.buildTextContent(record)\n if (!textContent.trim()) return\n\n const embedding = await this.embeddingService.createEmbedding(textContent)\n\n const doc: VectorDriverDocument = {\n entityId: record.entityId as EntityId,\n recordId: record.recordId,\n tenantId: record.tenantId,\n organizationId: record.organizationId,\n checksum: this.computeChecksum(record),\n embedding,\n url: record.url,\n presenter: record.presenter,\n links: record.links,\n driverId: this.vectorDriver.id,\n resultTitle: record.presenter?.title ?? record.recordId,\n resultSubtitle: record.presenter?.subtitle,\n resultIcon: record.presenter?.icon,\n resultBadge: record.presenter?.badge,\n }\n\n await this.vectorDriver.upsert(doc)\n }\n\n async delete(entityId: EntityId, recordId: string, tenantId: string): Promise<void> {\n await this.ensureReady()\n await this.vectorDriver.delete(entityId, recordId, tenantId)\n }\n\n async purge(entityId: EntityId, tenantId: string, organizationId?: string | null): Promise<void> {\n await this.ensureReady()\n if (this.vectorDriver.purge) {\n await this.vectorDriver.purge(entityId, tenantId, organizationId)\n }\n }\n\n /**\n * Build text content from record fields for embedding.\n */\n private buildTextContent(record: IndexableRecord): string {\n const parts: string[] = []\n\n // Add presenter info\n if (record.presenter?.title) {\n parts.push(record.presenter.title)\n }\n if (record.presenter?.subtitle) {\n parts.push(record.presenter.subtitle)\n }\n\n // Add string fields from record\n for (const [, value] of Object.entries(record.fields)) {\n if (typeof value === 'string' && value.trim()) {\n parts.push(value)\n }\n }\n\n return parts.join(' ').trim()\n }\n\n /**\n * Compute a checksum for change detection using SHA-256.\n * Uses checksumSource from buildSource if available, otherwise uses fields/presenter/url.\n */\n private computeChecksum(record: IndexableRecord): string {\n const source = record.checksumSource !== undefined\n ? record.checksumSource\n : {\n fields: record.fields,\n presenter: record.presenter,\n url: record.url,\n }\n const content = JSON.stringify(source)\n return createHash('sha256').update(content).digest('hex').slice(0, 16)\n }\n\n /**\n * List entries in the vector index (for admin/debugging).\n */\n async listEntries(options: {\n tenantId: string\n organizationId?: string | null\n entityId?: string\n limit?: number\n offset?: number\n }): Promise<Array<{\n entityId: string\n recordId: string\n tenantId: string\n organizationId: string | null\n presenter?: unknown\n url?: string\n }>> {\n await this.ensureReady()\n // Delegate to vector driver's list method if available\n const listMethod = (this.vectorDriver as unknown as {\n list?: (options: {\n tenantId: string\n organizationId?: string | null\n entityId?: string\n limit?: number\n offset?: number\n }) => Promise<unknown[]>\n }).list\n\n if (typeof listMethod === 'function') {\n const entries = await listMethod.call(this.vectorDriver, options)\n return entries as Array<{\n entityId: string\n recordId: string\n tenantId: string\n organizationId: string | null\n presenter?: unknown\n url?: string\n }>\n }\n\n // Fallback: return empty array if driver doesn't support listing\n searchDebugWarn('VectorSearchStrategy', 'Vector driver does not support listing entries')\n return []\n }\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -2,6 +2,7 @@ import { parseDecryptedFieldValue } from "@open-mercato/shared/lib/encryption/te
2
2
  import { computeChecksum } from "./checksum.js";
3
3
  import { logVectorOperation } from "../lib/vector-logs.js";
4
4
  import { searchDebug, searchDebugWarn } from "../../lib/debug.js";
5
+ import { extractSearchableFields } from "../../lib/field-policy.js";
5
6
  const VECTOR_ENTRY_ENCRYPTION_ENTITY_ID = "vector:vector_search";
6
7
  const DEFAULT_SOURCE_SCOPE_FIELDS = /* @__PURE__ */ new Set([
7
8
  "id",
@@ -351,10 +352,12 @@ class VectorIndexService {
351
352
  tenantId: args.tenantId,
352
353
  organizationId: resolvedOrgId
353
354
  });
354
- const normalizedPresenter = this.ensurePresenter(presenter, record, customFields, args.recordId, args.entityId);
355
+ const metadataRecord = extractSearchableFields(record, { fieldPolicy: entry.config.fieldPolicy });
356
+ const metadataCustomFields = extractSearchableFields(customFields, { fieldPolicy: entry.config.fieldPolicy });
357
+ const normalizedPresenter = this.ensurePresenter(presenter, metadataRecord, metadataCustomFields, args.recordId, args.entityId);
355
358
  const normalizedLinks = Array.isArray(links) && links.length ? links : null;
356
- const snapshot = this.deriveSnapshot(record, customFields);
357
- const rawResultTitle = this.resolveResultTitle(normalizedPresenter, record, customFields, args.recordId);
359
+ const snapshot = this.deriveSnapshot(metadataRecord, metadataCustomFields);
360
+ const rawResultTitle = this.resolveResultTitle(normalizedPresenter, metadataRecord, metadataCustomFields, args.recordId);
358
361
  const rawResultSubtitle = normalizedPresenter.subtitle ?? snapshot ?? null;
359
362
  const rawResultIcon = normalizedPresenter.icon ?? this.mapDefaultIcon(args.entityId);
360
363
  const resultBadge = normalizedPresenter.badge ?? null;
@@ -550,9 +553,10 @@ class VectorIndexService {
550
553
  if (formatted) return formatted;
551
554
  }
552
555
  if (fallback) return fallback;
553
- const nameLike = ctx.record.displayName || ctx.record.title || ctx.record.name;
556
+ const fields = extractSearchableFields(ctx.record, { fieldPolicy: config.fieldPolicy });
557
+ const nameLike = fields.displayName || fields.title || fields.name;
554
558
  if (typeof nameLike === "string" && nameLike.trim().length > 0) {
555
- const subtitle = ctx.record.description || ctx.record.summary;
559
+ const subtitle = fields.description || fields.summary;
556
560
  return {
557
561
  title: nameLike,
558
562
  subtitle: typeof subtitle === "string" ? subtitle : void 0
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../src/vector/services/vector-index.service.ts"],
4
- "sourcesContent": ["import type { QueryEngine } from '@open-mercato/shared/lib/query/types'\nimport type { EntityManager } from '@mikro-orm/postgresql'\nimport type { EntityId } from '@open-mercato/shared/modules/entities'\nimport type { TenantDataEncryptionService } from '@open-mercato/shared/lib/encryption/tenantDataEncryptionService'\nimport { parseDecryptedFieldValue } from '@open-mercato/shared/lib/encryption/tenantDataEncryptionService'\nimport {\n type VectorModuleConfig,\n type VectorEntityConfig,\n type VectorQueryRequest,\n type VectorSearchHit,\n type VectorIndexSource,\n type VectorDriverId,\n type VectorLinkDescriptor,\n type VectorResultPresenter,\n type VectorIndexEntry,\n} from '../types'\nimport type { VectorDriver } from '../types'\nimport type { SearchFieldPolicy } from '@open-mercato/shared/modules/search'\nimport { computeChecksum } from './checksum'\nimport { EmbeddingService } from './embedding'\nimport { logVectorOperation } from '../lib/vector-logs'\nimport { searchDebug, searchDebugWarn } from '../../lib/debug'\n\ntype ContainerResolver = () => unknown\nconst VECTOR_ENTRY_ENCRYPTION_ENTITY_ID = 'vector:vector_search'\n\nconst DEFAULT_SOURCE_SCOPE_FIELDS = new Set<string>([\n 'id',\n 'tenant_id',\n 'tenantId',\n 'organization_id',\n 'organizationId',\n 'created_at',\n 'createdAt',\n 'updated_at',\n 'updatedAt',\n 'deleted_at',\n 'deletedAt',\n])\n\nconst ENRICHMENT_FIELD_HINTS: Record<EntityId, string[]> = {\n 'customers:customer_entity': [\n 'id',\n 'organization_id',\n 'tenant_id',\n 'display_name',\n 'description',\n 'status',\n 'lifecycle_stage',\n 'primary_email',\n 'primary_phone',\n 'kind',\n 'customer_kind',\n ],\n 'customers:customer_comment': ['id', 'organization_id', 'tenant_id', 'entity_id', 'body', 'appearance_icon', 'appearance_color'],\n 'customers:customer_activity': ['id', 'organization_id', 'tenant_id', 'entity_id', 'activity_type', 'subject', 'body', 'deal_id'],\n 'customers:customer_deal': ['id', 'organization_id', 'tenant_id', 'title', 'pipeline_stage', 'status', 'value_amount', 'value_currency'],\n 'customers:customer_todo_link': ['id', 'organization_id', 'tenant_id', 'entity_id', 'todo_id', 'todo_source'],\n 'customers:customer_person_profile': [\n 'id',\n 'organization_id',\n 'tenant_id',\n 'entity_id',\n 'first_name',\n 'last_name',\n 'preferred_name',\n 'job_title',\n 'department',\n ],\n 'customers:customer_company_profile': [\n 'id',\n 'organization_id',\n 'tenant_id',\n 'entity_id',\n 'brand_name',\n 'legal_name',\n 'domain',\n 'industry',\n 'size_bucket',\n ],\n}\n\nexport type VectorIndexServiceOptions = {\n drivers: VectorDriver[]\n embeddingService: EmbeddingService\n queryEngine: QueryEngine\n moduleConfigs: VectorModuleConfig[]\n defaultDriverId?: VectorDriverId\n containerResolver?: ContainerResolver\n eventBus?: {\n emitEvent(event: string, payload: any, options?: any): Promise<void>\n }\n}\n\ntype IndexRecordArgs = {\n entityId: EntityId\n recordId: string\n tenantId: string\n organizationId?: string | null\n}\n\ntype DeleteRecordArgs = {\n entityId: EntityId\n recordId: string\n tenantId: string\n organizationId?: string | null\n}\n\nexport type VectorIndexOperationResult = {\n action: 'indexed' | 'deleted' | 'skipped'\n created?: boolean\n existed?: boolean\n tenantId: string\n organizationId: string | null\n reason?: 'unsupported' | 'missing_record' | 'checksum_match'\n}\n\nexport class VectorIndexService {\n private readonly driverMap = new Map<VectorDriverId, VectorDriver>()\n private readonly entityConfig = new Map<EntityId, { config: VectorEntityConfig; driverId: VectorDriverId }>()\n private readonly defaultDriverId: VectorDriverId\n\n constructor(private readonly opts: VectorIndexServiceOptions) {\n for (const driver of opts.drivers) {\n this.driverMap.set(driver.id, driver)\n }\n this.defaultDriverId = opts.defaultDriverId ?? 'pgvector'\n for (const moduleConfig of opts.moduleConfigs) {\n const driverId = moduleConfig.defaultDriverId ?? this.defaultDriverId\n for (const entity of moduleConfig.entities ?? []) {\n if (!entity?.entityId) continue\n if (entity.enabled === false) continue\n const targetDriver = entity.driverId ?? driverId\n this.entityConfig.set(entity.entityId, { config: entity, driverId: targetDriver })\n }\n }\n }\n\n private resolveEncryptionService(): TenantDataEncryptionService | null {\n if (!this.opts.containerResolver) return null\n try {\n const container = this.opts.containerResolver() as any\n if (!container || typeof container.resolve !== 'function') return null\n return container.resolve('tenantEncryptionService') as TenantDataEncryptionService\n } catch {\n return null\n }\n }\n\n private async encryptResultFields(args: {\n tenantId: string\n organizationId: string | null\n resultTitle: string\n resultSubtitle: string | null\n resultIcon: string | null\n resultSnapshot: string | null\n primaryLinkHref: string | null\n primaryLinkLabel: string | null\n links: VectorLinkDescriptor[] | string | null\n payload: Record<string, unknown> | string | null\n }): Promise<{\n resultTitle: string\n resultSubtitle: string | null\n resultIcon: string | null\n resultSnapshot: string | null\n primaryLinkHref: string | null\n primaryLinkLabel: string | null\n links: VectorLinkDescriptor[] | string | null\n payload: Record<string, unknown> | string | null\n }> {\n const service = this.resolveEncryptionService()\n if (!service || !service.isEnabled?.()) {\n return {\n resultTitle: args.resultTitle,\n resultSubtitle: args.resultSubtitle,\n resultIcon: args.resultIcon,\n resultSnapshot: args.resultSnapshot,\n primaryLinkHref: args.primaryLinkHref,\n primaryLinkLabel: args.primaryLinkLabel,\n links: args.links,\n payload: args.payload,\n }\n }\n try {\n const encrypted = await service.encryptEntityPayload(\n VECTOR_ENTRY_ENCRYPTION_ENTITY_ID,\n {\n resultTitle: args.resultTitle,\n resultSubtitle: args.resultSubtitle,\n resultIcon: args.resultIcon,\n resultSnapshot: args.resultSnapshot,\n primaryLinkHref: args.primaryLinkHref,\n primaryLinkLabel: args.primaryLinkLabel,\n links: args.links,\n payload: args.payload,\n },\n args.tenantId,\n args.organizationId,\n )\n return {\n resultTitle: String((encrypted as any).resultTitle ?? args.resultTitle),\n resultSubtitle: ((encrypted as any).resultSubtitle ?? args.resultSubtitle) as any,\n resultIcon: ((encrypted as any).resultIcon ?? args.resultIcon) as any,\n resultSnapshot: ((encrypted as any).resultSnapshot ?? args.resultSnapshot) as any,\n primaryLinkHref: ((encrypted as any).primaryLinkHref ?? args.primaryLinkHref) as any,\n primaryLinkLabel: ((encrypted as any).primaryLinkLabel ?? args.primaryLinkLabel) as any,\n links: ((encrypted as any).links ?? args.links) as any,\n payload: ((encrypted as any).payload ?? args.payload) as any,\n }\n } catch (err) {\n searchDebugWarn('vector.index', 'Vector entry encryption failed; refusing to persist plaintext', {\n entityId: VECTOR_ENTRY_ENCRYPTION_ENTITY_ID,\n tenantId: args.tenantId,\n organizationId: args.organizationId,\n error: err instanceof Error ? err.message : String(err),\n })\n throw err instanceof Error\n ? err\n : new Error('[internal] Vector entry encryption failed')\n }\n }\n\n private async decryptResultFields(args: {\n tenantId: string\n organizationId: string | null\n resultTitle: string\n resultSubtitle: string | null\n resultIcon: string | null\n resultSnapshot: string | null\n primaryLinkHref: string | null\n primaryLinkLabel: string | null\n links: VectorLinkDescriptor[] | string | null\n payload: Record<string, unknown> | string | null\n }): Promise<{\n resultTitle: string\n resultSubtitle: string | null\n resultIcon: string | null\n resultSnapshot: string | null\n primaryLinkHref: string | null\n primaryLinkLabel: string | null\n links: VectorLinkDescriptor[] | string | null\n payload: Record<string, unknown> | string | null\n }> {\n const service = this.resolveEncryptionService()\n if (!service || !service.isEnabled?.() || typeof service.decryptEntityPayload !== 'function') {\n return {\n resultTitle: args.resultTitle,\n resultSubtitle: args.resultSubtitle,\n resultIcon: args.resultIcon,\n resultSnapshot: args.resultSnapshot,\n primaryLinkHref: args.primaryLinkHref,\n primaryLinkLabel: args.primaryLinkLabel,\n links: args.links,\n payload: args.payload,\n }\n }\n try {\n const decrypted = await service.decryptEntityPayload(\n VECTOR_ENTRY_ENCRYPTION_ENTITY_ID,\n {\n resultTitle: args.resultTitle,\n resultSubtitle: args.resultSubtitle,\n resultIcon: args.resultIcon,\n resultSnapshot: args.resultSnapshot,\n primaryLinkHref: args.primaryLinkHref,\n primaryLinkLabel: args.primaryLinkLabel,\n links: args.links,\n payload: args.payload,\n },\n args.tenantId,\n args.organizationId,\n )\n const decryptedLinks = (decrypted as any).links ?? args.links\n const decryptedPayload = (decrypted as any).payload ?? args.payload\n return {\n resultTitle: String((decrypted as any).resultTitle ?? args.resultTitle),\n resultSubtitle: ((decrypted as any).resultSubtitle ?? args.resultSubtitle) as any,\n resultIcon: ((decrypted as any).resultIcon ?? args.resultIcon) as any,\n resultSnapshot: ((decrypted as any).resultSnapshot ?? args.resultSnapshot) as any,\n primaryLinkHref: ((decrypted as any).primaryLinkHref ?? args.primaryLinkHref) as any,\n primaryLinkLabel: ((decrypted as any).primaryLinkLabel ?? args.primaryLinkLabel) as any,\n links: (typeof decryptedLinks === 'string' ? parseDecryptedFieldValue(decryptedLinks) : decryptedLinks) as any,\n payload: (typeof decryptedPayload === 'string' ? parseDecryptedFieldValue(decryptedPayload) : decryptedPayload) as any,\n }\n } catch (err) {\n searchDebugWarn('vector.index', 'Vector entry decryption failed; refusing to return ciphertext as plaintext', {\n entityId: VECTOR_ENTRY_ENCRYPTION_ENTITY_ID,\n tenantId: args.tenantId,\n organizationId: args.organizationId,\n error: err instanceof Error ? err.message : String(err),\n })\n throw err instanceof Error\n ? err\n : new Error('[internal] Vector entry decryption failed')\n }\n }\n\n listEnabledEntities(): EntityId[] {\n return Array.from(this.entityConfig.keys())\n }\n\n async ensureDriverReady(entityId?: EntityId): Promise<void> {\n if (entityId) {\n const entry = this.entityConfig.get(entityId)\n if (!entry) return\n const driver = this.getDriver(entry.driverId)\n await driver.ensureReady()\n return\n }\n const uniqueDrivers = new Set<VectorDriverId>()\n for (const entry of this.entityConfig.values()) {\n uniqueDrivers.add(entry.driverId)\n }\n if (!uniqueDrivers.size) uniqueDrivers.add(this.defaultDriverId)\n await Promise.all(Array.from(uniqueDrivers).map(async (driverId) => {\n try {\n const driver = this.getDriver(driverId)\n await driver.ensureReady()\n } catch (err) {\n searchDebugWarn('vector', 'Failed to ensure driver readiness', { driverId, error: err instanceof Error ? err.message : err })\n }\n }))\n }\n\n private getDriver(driverId: VectorDriverId): VectorDriver {\n const driver = this.driverMap.get(driverId)\n if (!driver) {\n throw new Error(`[vector] Driver ${driverId} is not registered`)\n }\n return driver\n }\n\n private getEnrichmentFields(entityId: EntityId): string[] | undefined {\n const hints = ENRICHMENT_FIELD_HINTS[entityId]\n if (!hints) return undefined\n const unique = new Set<string>(['id', ...hints])\n return Array.from(unique)\n }\n\n private async fetchRecord(entityId: EntityId, recordIds: string[], tenantId: string, organizationId?: string | null) {\n const filters: Record<string, any> = { id: { $in: recordIds } }\n const result = await this.opts.queryEngine.query(entityId, {\n tenantId,\n organizationId: organizationId ?? undefined,\n filters,\n includeCustomFields: true,\n fields: this.getEnrichmentFields(entityId),\n skipAutoReindex: true,\n })\n const byId = new Map<string, Record<string, any>>()\n for (const item of result.items) {\n const key = String((item as any).id ?? '')\n if (!key) continue\n byId.set(key, item as Record<string, any>)\n }\n return byId\n }\n\n private extractRecordPayload(entityId: EntityId, raw: Record<string, any>) {\n const record: Record<string, any> = {}\n const customFields: Record<string, any> = {}\n const multiMap = new Map<string, boolean>()\n\n for (const [key, value] of Object.entries(raw)) {\n if (key.startsWith('cf:') && key.endsWith('__is_multi')) {\n const base = key.replace(/__is_multi$/, '')\n multiMap.set(base, Boolean(value))\n continue\n }\n if (key.startsWith('cf:')) {\n customFields[key.slice(3)] = value\n continue\n }\n record[key] = value\n }\n\n for (const [key, isMulti] of multiMap.entries()) {\n const bare = key.slice(3)\n if (bare && customFields[bare] != null && !Array.isArray(customFields[bare]) && isMulti) {\n customFields[bare] = [customFields[bare]]\n }\n }\n\n if (record.entity_id == null && record.entityId == null && entityId.endsWith('_company_profile')) {\n searchDebugWarn('vector.index', 'company profile missing entity id in payload', {\n id: record.id,\n keys: Object.keys(record),\n })\n }\n\n return { record, customFields }\n }\n\n private async indexExisting(\n entry: { config: VectorEntityConfig; driverId: VectorDriverId },\n driver: VectorDriver,\n args: IndexRecordArgs,\n raw: Record<string, any>,\n opts: { skipDelete?: boolean } = {},\n ): Promise<VectorIndexOperationResult> {\n const scopeOrg = args.organizationId ?? null\n const { record, customFields } = this.extractRecordPayload(args.entityId, raw)\n const resolvedOrgId = scopeOrg ?? (record.organization_id ?? record.organizationId ?? null)\n const source = await this.resolveSource(args.entityId, entry.config, {\n record,\n customFields,\n tenantId: args.tenantId,\n organizationId: resolvedOrgId,\n })\n if (!source) {\n const existing = await driver.getChecksum(args.entityId, args.recordId, args.tenantId)\n if (!opts.skipDelete && existing) {\n await driver.delete(args.entityId, args.recordId, args.tenantId)\n return {\n action: 'deleted',\n existed: true,\n tenantId: args.tenantId,\n organizationId: resolvedOrgId,\n }\n }\n return {\n action: 'skipped',\n existed: Boolean(existing),\n tenantId: args.tenantId,\n organizationId: resolvedOrgId,\n reason: 'missing_record',\n }\n }\n\n const checksumSource = source.checksumSource ?? { record, customFields }\n const checksum = computeChecksum(checksumSource)\n const current = await driver.getChecksum(args.entityId, args.recordId, args.tenantId)\n if (current && current === checksum) {\n return {\n action: 'skipped',\n existed: true,\n tenantId: args.tenantId,\n organizationId: scopeOrg,\n reason: 'checksum_match',\n }\n }\n if (!this.opts.embeddingService.available) {\n throw new Error('[vector] Embedding service unavailable (missing OPENAI_API_KEY)')\n }\n const embedding = await this.opts.embeddingService.createEmbedding(source.input)\n const presenter = await this.resolvePresenter(entry.config, {\n record,\n customFields,\n tenantId: args.tenantId,\n organizationId: resolvedOrgId,\n }, source.presenter ?? null)\n if (!presenter?.title) {\n searchDebugWarn('vector.index', 'missing presenter title', {\n entityId: args.entityId,\n recordId: args.recordId,\n recordSample: {\n display_name: record.display_name,\n displayName: record.displayName,\n name: record.name,\n title: record.title,\n subject: record.subject,\n kind: record.kind,\n },\n })\n }\n const links = await this.resolveLinks(entry.config, {\n record,\n customFields,\n tenantId: args.tenantId,\n organizationId: resolvedOrgId,\n }, source.links ?? null)\n const url = await this.resolveUrl(entry.config, {\n record,\n customFields,\n tenantId: args.tenantId,\n organizationId: resolvedOrgId,\n })\n\n const normalizedPresenter = this.ensurePresenter(presenter, record, customFields, args.recordId, args.entityId)\n const normalizedLinks = Array.isArray(links) && links.length ? links : null\n const snapshot = this.deriveSnapshot(record, customFields)\n const rawResultTitle = this.resolveResultTitle(normalizedPresenter, record, customFields, args.recordId)\n const rawResultSubtitle = normalizedPresenter.subtitle ?? snapshot ?? null\n const rawResultIcon = normalizedPresenter.icon ?? this.mapDefaultIcon(args.entityId)\n const resultBadge = normalizedPresenter.badge ?? null\n const primaryLink = this.resolvePrimaryLink(normalizedLinks, url, rawResultTitle)\n\n const encryptedResult = await this.encryptResultFields({\n tenantId: args.tenantId,\n organizationId: resolvedOrgId,\n resultTitle: rawResultTitle,\n resultSubtitle: rawResultSubtitle,\n resultIcon: rawResultIcon ?? null,\n resultSnapshot: snapshot ?? null,\n primaryLinkHref: primaryLink?.href ?? null,\n primaryLinkLabel: primaryLink?.label ?? null,\n links: normalizedLinks,\n payload: source.payload ?? null,\n })\n\n const presenterForStorage: VectorResultPresenter = {\n title: encryptedResult.resultTitle,\n subtitle: encryptedResult.resultSubtitle ?? undefined,\n icon: encryptedResult.resultIcon ?? undefined,\n badge: resultBadge ?? undefined,\n }\n\n searchDebug('VectorIndexService', 'Storing vector index entry', {\n entityId: args.entityId,\n recordId: args.recordId,\n tenantId: args.tenantId,\n organizationId: resolvedOrgId,\n })\n await driver.upsert({\n driverId: entry.driverId,\n entityId: args.entityId,\n recordId: args.recordId,\n tenantId: args.tenantId,\n organizationId: resolvedOrgId,\n checksum,\n embedding,\n url: url ?? null,\n presenter: presenterForStorage,\n links: (encryptedResult.links as any) ?? normalizedLinks,\n payload: (encryptedResult.payload as any) ?? source.payload ?? null,\n resultTitle: encryptedResult.resultTitle,\n resultSubtitle: encryptedResult.resultSubtitle,\n resultIcon: encryptedResult.resultIcon ?? null,\n resultBadge,\n resultSnapshot: encryptedResult.resultSnapshot ?? snapshot,\n primaryLinkHref: encryptedResult.primaryLinkHref ?? primaryLink?.href ?? null,\n primaryLinkLabel: encryptedResult.primaryLinkLabel ?? null,\n })\n\n return {\n action: 'indexed',\n created: !current,\n existed: true,\n tenantId: args.tenantId,\n organizationId: resolvedOrgId,\n }\n }\n\n private ensurePresenter(\n presenter: VectorResultPresenter | null,\n record: Record<string, any>,\n customFields: Record<string, any>,\n recordId: string,\n entityId: EntityId,\n ): VectorResultPresenter {\n if (presenter?.title) return presenter\n const fallback = this.buildFallbackPresenter(record, customFields, recordId, entityId)\n return fallback\n }\n\n private resolveResultTitle(\n presenter: VectorResultPresenter,\n record: Record<string, any>,\n customFields: Record<string, any>,\n recordId: string,\n ): string {\n const candidate =\n presenter.title ??\n record.display_name ??\n record.displayName ??\n record.name ??\n record.title ??\n record.subject ??\n customFields.title ??\n customFields.name ??\n recordId\n const text = String(candidate).trim()\n return text.length ? text : recordId\n }\n\n private buildFallbackPresenter(\n record: Record<string, any>,\n customFields: Record<string, any>,\n recordId: string,\n entityId: EntityId,\n ): VectorResultPresenter {\n const titleCandidate =\n record.display_name ??\n record.displayName ??\n record.name ??\n record.title ??\n record.subject ??\n recordId\n const subtitleCandidate =\n record.description ??\n record.summary ??\n record.body ??\n customFields.summary ??\n customFields.description ??\n null\n const icon = typeof record.kind === 'string'\n ? this.mapEntityIcon(record.kind)\n : this.mapDefaultIcon(entityId)\n return {\n title: String(titleCandidate),\n subtitle: subtitleCandidate ? String(subtitleCandidate) : undefined,\n icon: icon ?? undefined,\n }\n }\n\n private mapEntityIcon(kind?: string | null): string | null {\n if (!kind) return null\n const normalized = kind.toLowerCase()\n if (normalized === 'person') return 'user'\n if (normalized === 'company' || normalized === 'organization') return 'building'\n return null\n }\n\n private mapDefaultIcon(entityId: EntityId): string | null {\n if (entityId.startsWith('customers:customer_deal')) return 'briefcase'\n if (entityId.startsWith('customers:customer_comment')) return 'sticky-note'\n if (entityId.startsWith('customers:customer_activity')) return 'bolt'\n if (entityId.startsWith('customers:customer_todo')) return 'check-square'\n return null\n }\n\n private resolvePrimaryLink(\n links: VectorLinkDescriptor[] | null,\n url: string | null,\n fallbackLabel: string,\n ): { href: string; label: string } | null {\n if (links?.length) {\n const primary = links.find((link) => link.kind === 'primary') ?? links[0]\n if (primary?.href) {\n return { href: primary.href, label: primary.label ?? fallbackLabel }\n }\n }\n if (url) {\n return { href: url, label: fallbackLabel }\n }\n return null\n }\n\n private deriveSnapshot(\n record: Record<string, any>,\n customFields: Record<string, any>,\n ): string | null {\n const candidates = [\n record.summary,\n record.description,\n record.body,\n customFields.summary,\n customFields.description,\n customFields.body,\n ]\n for (const value of candidates) {\n if (typeof value === 'string') {\n const trimmed = value.trim()\n if (trimmed.length) return trimmed\n }\n }\n return null\n }\n\n private buildDefaultSource(\n entityId: EntityId,\n payload: { record: Record<string, any>; customFields: Record<string, any> },\n fieldPolicy?: SearchFieldPolicy,\n ): VectorIndexSource {\n const { record, customFields } = payload\n const lines: string[] = []\n\n const searchableWhitelist = fieldPolicy?.searchable ? new Set(fieldPolicy.searchable) : null\n const excludedFields = new Set<string>([\n ...(fieldPolicy?.excluded ?? []),\n ...(fieldPolicy?.hashOnly ?? []),\n ])\n\n const isEligibleField = (field: string): boolean => {\n if (DEFAULT_SOURCE_SCOPE_FIELDS.has(field)) return false\n if (excludedFields.has(field)) return false\n if (searchableWhitelist && !searchableWhitelist.has(field)) return false\n return true\n }\n\n const pushEntry = (label: string, value: unknown) => {\n if (value === null || value === undefined) return\n if (typeof value === 'string' && value.trim().length === 0) return\n if (typeof value === 'object') {\n lines.push(`${label}: ${JSON.stringify(value)}`)\n } else {\n lines.push(`${label}: ${value}`)\n }\n }\n\n const preferredFields = ['title', 'name', 'displayName', 'summary', 'subject']\n for (const field of preferredFields) {\n if (!isEligibleField(field)) continue\n if (record[field] != null) pushEntry(field, record[field])\n }\n\n for (const [key, value] of Object.entries(record)) {\n if (preferredFields.includes(key)) continue\n if (!isEligibleField(key)) continue\n pushEntry(key, value)\n }\n\n for (const [key, value] of Object.entries(customFields)) {\n if (!isEligibleField(key)) continue\n pushEntry(`custom.${key}`, value)\n }\n\n if (lines.length === 0) {\n lines.push(`${entityId}#${record.id ?? ''}`)\n }\n\n return {\n input: lines,\n payload: null,\n checksumSource: { record, customFields },\n }\n }\n\n private async resolveSource(entityId: EntityId, config: VectorEntityConfig, ctx: {\n record: Record<string, any>\n customFields: Record<string, any>\n organizationId?: string | null\n tenantId: string\n }): Promise<VectorIndexSource | null> {\n const baseCtx = {\n record: ctx.record,\n customFields: ctx.customFields,\n organizationId: ctx.organizationId ?? null,\n tenantId: ctx.tenantId,\n queryEngine: this.opts.queryEngine,\n container: this.opts.containerResolver ? this.opts.containerResolver() : undefined,\n }\n if (config.buildSource) {\n const built = await config.buildSource(baseCtx)\n if (built) return built\n return null\n }\n return this.buildDefaultSource(entityId, { record: ctx.record, customFields: ctx.customFields }, config.fieldPolicy)\n }\n\n private async resolvePresenter(\n config: VectorEntityConfig,\n ctx: {\n record: Record<string, any>\n customFields: Record<string, any>\n organizationId?: string | null\n tenantId: string\n },\n fallback?: VectorResultPresenter | null,\n ): Promise<VectorResultPresenter | null> {\n const baseCtx = {\n record: ctx.record,\n customFields: ctx.customFields,\n organizationId: ctx.organizationId ?? null,\n tenantId: ctx.tenantId,\n queryEngine: this.opts.queryEngine,\n container: this.opts.containerResolver ? this.opts.containerResolver() : undefined,\n }\n if (config.formatResult) {\n const formatted = await config.formatResult(baseCtx)\n if (formatted) return formatted\n }\n if (fallback) return fallback\n const nameLike = ctx.record.displayName || ctx.record.title || ctx.record.name\n if (typeof nameLike === 'string' && nameLike.trim().length > 0) {\n const subtitle = ctx.record.description || ctx.record.summary\n return {\n title: nameLike,\n subtitle: typeof subtitle === 'string' ? subtitle : undefined,\n }\n }\n return null\n }\n\n private async resolveLinks(\n config: VectorEntityConfig,\n ctx: {\n record: Record<string, any>\n customFields: Record<string, any>\n organizationId?: string | null\n tenantId: string\n },\n fallback?: VectorLinkDescriptor[] | null,\n ): Promise<VectorLinkDescriptor[] | null> {\n const baseCtx = {\n record: ctx.record,\n customFields: ctx.customFields,\n organizationId: ctx.organizationId ?? null,\n tenantId: ctx.tenantId,\n queryEngine: this.opts.queryEngine,\n container: this.opts.containerResolver ? this.opts.containerResolver() : undefined,\n }\n if (config.resolveLinks) {\n const resolved = await config.resolveLinks(baseCtx)\n if (resolved?.length) return resolved\n }\n return fallback ?? null\n }\n\n private async resolveUrl(\n config: VectorEntityConfig,\n ctx: {\n record: Record<string, any>\n customFields: Record<string, any>\n organizationId?: string | null\n tenantId: string\n },\n fallback?: string | null,\n ): Promise<string | null> {\n if (config.resolveUrl) {\n const candidate = await config.resolveUrl({\n record: ctx.record,\n customFields: ctx.customFields,\n organizationId: ctx.organizationId ?? null,\n tenantId: ctx.tenantId,\n queryEngine: this.opts.queryEngine,\n container: this.opts.containerResolver ? this.opts.containerResolver() : undefined,\n })\n if (candidate) return candidate\n }\n return fallback ?? null\n }\n\n async indexRecord(args: IndexRecordArgs): Promise<VectorIndexOperationResult> {\n const entry = this.entityConfig.get(args.entityId)\n if (!entry) {\n return {\n action: 'skipped',\n existed: false,\n tenantId: args.tenantId,\n organizationId: args.organizationId ?? null,\n reason: 'unsupported',\n }\n }\n const driver = this.getDriver(entry.driverId)\n await driver.ensureReady()\n\n const records = await this.fetchRecord(args.entityId, [args.recordId], args.tenantId, args.organizationId)\n const raw = records.get(args.recordId)\n if (!raw) {\n const existing = await driver.getChecksum(args.entityId, args.recordId, args.tenantId)\n if (existing) {\n await driver.delete(args.entityId, args.recordId, args.tenantId)\n return {\n action: 'deleted',\n existed: true,\n tenantId: args.tenantId,\n organizationId: args.organizationId ?? null,\n }\n }\n return {\n action: 'skipped',\n existed: false,\n tenantId: args.tenantId,\n organizationId: args.organizationId ?? null,\n reason: 'missing_record',\n }\n }\n return this.indexExisting(entry, driver, args, raw as Record<string, any>)\n }\n\n async deleteRecord(args: DeleteRecordArgs): Promise<VectorIndexOperationResult> {\n const entry = this.entityConfig.get(args.entityId)\n if (!entry) {\n return {\n action: 'skipped',\n existed: false,\n tenantId: args.tenantId,\n organizationId: args.organizationId ?? null,\n reason: 'unsupported',\n }\n }\n const driver = this.getDriver(entry.driverId)\n await driver.ensureReady()\n const scopeOrg = args.organizationId ?? null\n const existing = await driver.getChecksum(args.entityId, args.recordId, args.tenantId)\n if (existing) {\n await driver.delete(args.entityId, args.recordId, args.tenantId)\n return {\n action: 'deleted',\n existed: true,\n tenantId: args.tenantId,\n organizationId: scopeOrg,\n }\n }\n return {\n action: 'skipped',\n existed: false,\n tenantId: args.tenantId,\n organizationId: scopeOrg,\n reason: 'missing_record',\n }\n }\n\n async reindexEntity(args: { entityId: EntityId; tenantId?: string | null; organizationId?: string | null; purgeFirst?: boolean }): Promise<void> {\n const entry = this.entityConfig.get(args.entityId)\n if (!entry) return\n const driver = this.getDriver(entry.driverId)\n await driver.ensureReady()\n\n const shouldPurge = args.purgeFirst === true\n const reindexStartedAt = new Date()\n\n if (this.opts.eventBus) {\n if (shouldPurge && driver.purge && args.tenantId) {\n await driver.purge(args.entityId, args.tenantId, args.organizationId)\n } else if (shouldPurge && !args.tenantId) {\n searchDebugWarn('vector', 'Skipping purge for multi-tenant reindex (tenant not provided)')\n }\n const payload: Record<string, unknown> = {\n entityType: args.entityId,\n }\n if (shouldPurge) {\n payload.force = true\n payload.resetCoverage = true\n }\n if (args.tenantId !== undefined) payload.tenantId = args.tenantId\n else payload.allowAllTenants = true\n if (args.organizationId !== undefined) payload.organizationId = args.organizationId\n await this.opts.eventBus.emitEvent('query_index.reindex', payload)\n return\n }\n\n if (!args.tenantId) {\n throw new Error('[vector] Reindex without tenantId requires event bus integration')\n }\n\n if (shouldPurge && driver.purge) {\n await driver.purge(args.entityId, args.tenantId, args.organizationId)\n }\n\n const pageSize = 50\n let page = 1\n const loggingEm = this.resolveEntityManager()\n for (;;) {\n const result = await this.opts.queryEngine.query(args.entityId, {\n tenantId: args.tenantId,\n organizationId: args.organizationId ?? undefined,\n page: { page, pageSize },\n includeCustomFields: true,\n fields: this.getEnrichmentFields(args.entityId),\n skipAutoReindex: true,\n })\n if (!result.items.length) break\n for (const raw of result.items) {\n const recordId = String((raw as any).id ?? '')\n if (!recordId) continue\n const opResult = await this.indexExisting(\n entry,\n driver,\n {\n entityId: args.entityId,\n recordId,\n tenantId: args.tenantId,\n organizationId: args.organizationId ?? null,\n },\n raw as Record<string, any>,\n { skipDelete: true },\n )\n await logVectorOperation({\n em: loggingEm,\n handler: 'service:vector.reindex',\n entityType: args.entityId,\n recordId,\n result: opResult,\n })\n }\n if (result.items.length < pageSize) break\n page += 1\n }\n\n if (shouldPurge) {\n await this.removeOrphans({\n entityId: args.entityId,\n tenantId: args.tenantId,\n organizationId: args.organizationId,\n olderThan: reindexStartedAt,\n })\n }\n }\n\n async reindexAll(args: { tenantId?: string | null; organizationId?: string | null; purgeFirst?: boolean }): Promise<void> {\n for (const entityId of this.listEnabledEntities()) {\n await this.reindexEntity({ entityId, tenantId: args.tenantId, organizationId: args.organizationId ?? null, purgeFirst: args.purgeFirst })\n }\n }\n\n private resolveEntityManager(): EntityManager | null {\n if (!this.opts.containerResolver) return null\n try {\n const container = this.opts.containerResolver()\n if (!container || typeof (container as any).resolve !== 'function') return null\n const resolver = container as { resolve(name: string): unknown }\n const em = resolver.resolve('em')\n return (em as EntityManager | null) ?? null\n } catch {\n return null\n }\n }\n\n async purgeIndex(args: { tenantId: string; organizationId?: string | null; entityId?: EntityId | null }): Promise<void> {\n const targets = args.entityId ? [args.entityId] : this.listEnabledEntities()\n if (!targets.length) return\n\n const grouped = new Map<VectorDriverId, EntityId[]>()\n for (const entityId of targets) {\n const cfg = this.entityConfig.get(entityId)\n if (!cfg) continue\n const driver = this.getDriver(cfg.driverId)\n if (typeof driver.purge !== 'function') {\n throw new Error(`[vector] Driver ${cfg.driverId} does not support purging entities`)\n }\n if (!grouped.has(cfg.driverId)) grouped.set(cfg.driverId, [])\n grouped.get(cfg.driverId)!.push(entityId)\n }\n\n for (const [driverId, entityIds] of grouped.entries()) {\n const driver = this.getDriver(driverId)\n await driver.ensureReady()\n for (const entityId of entityIds) {\n await driver.purge!(entityId, args.tenantId)\n }\n }\n }\n\n async removeOrphans(args: {\n entityId: EntityId\n tenantId?: string | null\n organizationId?: string | null\n olderThan: Date\n }): Promise<number> {\n const entry = this.entityConfig.get(args.entityId)\n if (!entry) return 0\n const driver = this.getDriver(entry.driverId)\n await driver.ensureReady()\n\n const driverAny = driver as VectorDriver & {\n removeOrphans?: (params: {\n entityId: EntityId\n tenantId?: string | null\n organizationId?: string | null\n olderThan: Date\n }) => Promise<number | void>\n }\n\n if (typeof driverAny.removeOrphans === 'function') {\n const deleted = await driverAny.removeOrphans({\n entityId: args.entityId,\n tenantId: args.tenantId,\n organizationId: args.organizationId,\n olderThan: args.olderThan,\n })\n return typeof deleted === 'number' ? deleted : 0\n }\n\n searchDebugWarn('vector', 'Driver does not support orphan cleanup', { driverId: entry.driverId })\n return 0\n }\n\n async countIndexEntries(args: {\n tenantId: string\n organizationId?: string | null\n entityId?: EntityId\n driverId?: VectorDriverId\n }): Promise<number> {\n if (!args.tenantId) return 0\n const targetEntity = args.entityId ? this.entityConfig.get(args.entityId) : null\n if (args.entityId && !targetEntity) {\n return 0\n }\n const driverId =\n args.driverId ??\n (targetEntity ? targetEntity.driverId : this.defaultDriverId)\n const driver = this.getDriver(driverId)\n await driver.ensureReady()\n const countParams = {\n tenantId: args.tenantId,\n organizationId: args.organizationId,\n entityId: args.entityId,\n }\n if (typeof driver.count === 'function') {\n try {\n return await driver.count(countParams)\n } catch (err) {\n searchDebugWarn('vector', 'Driver count failed, falling back to list', {\n driverId,\n error: err instanceof Error ? err.message : err,\n })\n }\n }\n if (typeof driver.list === 'function') {\n const limit = 1000\n let offset = 0\n let total = 0\n for (;;) {\n const batch = await driver.list({\n tenantId: countParams.tenantId,\n organizationId: countParams.organizationId,\n entityId: countParams.entityId,\n limit,\n offset,\n orderBy: 'created',\n })\n const size = batch.length\n total += size\n if (size < limit) break\n offset += limit\n }\n return total\n }\n searchDebugWarn('vector', 'Driver does not support counting or listing index entries', { driverId })\n return 0\n }\n\n async listIndexEntries(args: {\n tenantId: string\n organizationId?: string | null\n entityId?: EntityId\n limit?: number\n offset?: number\n driverId?: VectorDriverId\n }): Promise<VectorIndexEntry[]> {\n const targetEntity = args.entityId ? this.entityConfig.get(args.entityId) : null\n if (args.entityId && !targetEntity) {\n return []\n }\n const driverId =\n args.driverId ??\n (targetEntity ? targetEntity.driverId : this.defaultDriverId)\n const driver = this.getDriver(driverId)\n if (typeof driver.list !== 'function') {\n throw new Error(`[vector] Driver ${driverId} does not support listing index entries`)\n }\n await driver.ensureReady()\n const list = await driver.list({\n tenantId: args.tenantId,\n organizationId: args.organizationId,\n entityId: args.entityId,\n limit: args.limit,\n offset: args.offset,\n orderBy: 'updated',\n })\n if (!list.length) {\n return []\n }\n\n const decrypted = await Promise.all(\n list.map(async (entry) => {\n const decryptedResult = await this.decryptResultFields({\n tenantId: args.tenantId,\n organizationId: entry.organizationId ?? null,\n resultTitle: entry.resultTitle,\n resultSubtitle: entry.resultSubtitle ?? null,\n resultIcon: entry.resultIcon ?? null,\n resultSnapshot: entry.resultSnapshot ?? null,\n primaryLinkHref: entry.primaryLinkHref ?? null,\n primaryLinkLabel: entry.primaryLinkLabel ?? null,\n links: (entry.links as any) ?? null,\n payload: (entry.payload as any) ?? null,\n })\n return {\n ...entry,\n resultTitle: decryptedResult.resultTitle,\n resultSubtitle: decryptedResult.resultSubtitle,\n resultIcon: decryptedResult.resultIcon,\n resultSnapshot: decryptedResult.resultSnapshot,\n primaryLinkHref: decryptedResult.primaryLinkHref,\n primaryLinkLabel: decryptedResult.primaryLinkLabel,\n links: decryptedResult.links as any,\n payload: decryptedResult.payload as any,\n metadata: (decryptedResult.payload as any) ?? null,\n }\n }),\n )\n\n return decrypted.map((entry) => {\n const presenter = {\n title: entry.resultTitle,\n subtitle: entry.resultSubtitle ?? undefined,\n icon: entry.resultIcon ?? undefined,\n badge: entry.presenter?.badge ?? entry.resultBadge ?? undefined,\n }\n const links = entry.links ?? (entry.primaryLinkHref\n ? [{ href: entry.primaryLinkHref, label: entry.primaryLinkLabel ?? entry.resultTitle, kind: 'primary' as const }]\n : null)\n const url = entry.url ?? entry.primaryLinkHref ?? null\n const metadata = entry.metadata ?? (entry.resultSnapshot ? { snapshot: entry.resultSnapshot } : null)\n return {\n ...entry,\n driverId,\n presenter,\n links,\n url,\n metadata,\n score: entry.score ?? null,\n }\n })\n }\n\n async search(request: VectorQueryRequest): Promise<VectorSearchHit[]> {\n const driverId = request.driverId ?? this.defaultDriverId\n const driver = this.getDriver(driverId)\n await driver.ensureReady()\n if (!this.opts.embeddingService.available) {\n throw new Error('[vector] Embedding service unavailable (missing OPENAI_API_KEY)')\n }\n const embedding = await this.opts.embeddingService.createEmbedding(request.query)\n const hits = await driver.query({\n vector: embedding,\n limit: request.limit ?? 10,\n filter: {\n tenantId: request.tenantId,\n organizationId: request.organizationId ?? null,\n entityIds: undefined,\n },\n })\n\n if (!hits.length) return []\n\n const decrypted = await Promise.all(\n hits.map(async (hit) => {\n const decryptedResult = await this.decryptResultFields({\n tenantId: request.tenantId,\n organizationId: hit.organizationId ?? request.organizationId ?? null,\n resultTitle: hit.resultTitle,\n resultSubtitle: hit.resultSubtitle ?? null,\n resultIcon: hit.resultIcon ?? null,\n resultSnapshot: hit.resultSnapshot ?? null,\n primaryLinkHref: hit.primaryLinkHref ?? null,\n primaryLinkLabel: hit.primaryLinkLabel ?? null,\n links: (hit.links as any) ?? null,\n payload: (hit.payload as any) ?? null,\n })\n return {\n ...hit,\n resultTitle: decryptedResult.resultTitle,\n resultSubtitle: decryptedResult.resultSubtitle,\n resultIcon: decryptedResult.resultIcon,\n resultSnapshot: decryptedResult.resultSnapshot,\n primaryLinkHref: decryptedResult.primaryLinkHref,\n primaryLinkLabel: decryptedResult.primaryLinkLabel,\n links: decryptedResult.links as any,\n payload: decryptedResult.payload as any,\n }\n }),\n )\n\n return decrypted.map((hit) => {\n const presenter = {\n title: hit.resultTitle,\n subtitle: hit.resultSubtitle ?? undefined,\n icon: hit.resultIcon ?? undefined,\n badge: hit.presenter?.badge ?? hit.resultBadge ?? undefined,\n }\n const links = hit.links ?? (hit.primaryLinkHref\n ? [{ href: hit.primaryLinkHref, label: hit.primaryLinkLabel ?? hit.resultTitle, kind: 'primary' as const }]\n : null)\n const url = hit.url ?? hit.primaryLinkHref ?? null\n const metadata = hit.payload ?? (hit.resultSnapshot ? { snapshot: hit.resultSnapshot } : null)\n return {\n entityId: hit.entityId,\n recordId: hit.recordId,\n score: hit.score,\n url,\n presenter,\n links,\n driverId,\n metadata,\n }\n })\n }\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["import type { QueryEngine } from '@open-mercato/shared/lib/query/types'\nimport type { EntityManager } from '@mikro-orm/postgresql'\nimport type { EntityId } from '@open-mercato/shared/modules/entities'\nimport type { TenantDataEncryptionService } from '@open-mercato/shared/lib/encryption/tenantDataEncryptionService'\nimport { parseDecryptedFieldValue } from '@open-mercato/shared/lib/encryption/tenantDataEncryptionService'\nimport {\n type VectorModuleConfig,\n type VectorEntityConfig,\n type VectorQueryRequest,\n type VectorSearchHit,\n type VectorIndexSource,\n type VectorDriverId,\n type VectorLinkDescriptor,\n type VectorResultPresenter,\n type VectorIndexEntry,\n} from '../types'\nimport type { VectorDriver } from '../types'\nimport type { SearchFieldPolicy } from '@open-mercato/shared/modules/search'\nimport { computeChecksum } from './checksum'\nimport { EmbeddingService } from './embedding'\nimport { logVectorOperation } from '../lib/vector-logs'\nimport { searchDebug, searchDebugWarn } from '../../lib/debug'\nimport { extractSearchableFields } from '../../lib/field-policy'\n\ntype ContainerResolver = () => unknown\nconst VECTOR_ENTRY_ENCRYPTION_ENTITY_ID = 'vector:vector_search'\n\nconst DEFAULT_SOURCE_SCOPE_FIELDS = new Set<string>([\n 'id',\n 'tenant_id',\n 'tenantId',\n 'organization_id',\n 'organizationId',\n 'created_at',\n 'createdAt',\n 'updated_at',\n 'updatedAt',\n 'deleted_at',\n 'deletedAt',\n])\n\nconst ENRICHMENT_FIELD_HINTS: Record<EntityId, string[]> = {\n 'customers:customer_entity': [\n 'id',\n 'organization_id',\n 'tenant_id',\n 'display_name',\n 'description',\n 'status',\n 'lifecycle_stage',\n 'primary_email',\n 'primary_phone',\n 'kind',\n 'customer_kind',\n ],\n 'customers:customer_comment': ['id', 'organization_id', 'tenant_id', 'entity_id', 'body', 'appearance_icon', 'appearance_color'],\n 'customers:customer_activity': ['id', 'organization_id', 'tenant_id', 'entity_id', 'activity_type', 'subject', 'body', 'deal_id'],\n 'customers:customer_deal': ['id', 'organization_id', 'tenant_id', 'title', 'pipeline_stage', 'status', 'value_amount', 'value_currency'],\n 'customers:customer_todo_link': ['id', 'organization_id', 'tenant_id', 'entity_id', 'todo_id', 'todo_source'],\n 'customers:customer_person_profile': [\n 'id',\n 'organization_id',\n 'tenant_id',\n 'entity_id',\n 'first_name',\n 'last_name',\n 'preferred_name',\n 'job_title',\n 'department',\n ],\n 'customers:customer_company_profile': [\n 'id',\n 'organization_id',\n 'tenant_id',\n 'entity_id',\n 'brand_name',\n 'legal_name',\n 'domain',\n 'industry',\n 'size_bucket',\n ],\n}\n\nexport type VectorIndexServiceOptions = {\n drivers: VectorDriver[]\n embeddingService: EmbeddingService\n queryEngine: QueryEngine\n moduleConfigs: VectorModuleConfig[]\n defaultDriverId?: VectorDriverId\n containerResolver?: ContainerResolver\n eventBus?: {\n emitEvent(event: string, payload: any, options?: any): Promise<void>\n }\n}\n\ntype IndexRecordArgs = {\n entityId: EntityId\n recordId: string\n tenantId: string\n organizationId?: string | null\n}\n\ntype DeleteRecordArgs = {\n entityId: EntityId\n recordId: string\n tenantId: string\n organizationId?: string | null\n}\n\nexport type VectorIndexOperationResult = {\n action: 'indexed' | 'deleted' | 'skipped'\n created?: boolean\n existed?: boolean\n tenantId: string\n organizationId: string | null\n reason?: 'unsupported' | 'missing_record' | 'checksum_match'\n}\n\nexport class VectorIndexService {\n private readonly driverMap = new Map<VectorDriverId, VectorDriver>()\n private readonly entityConfig = new Map<EntityId, { config: VectorEntityConfig; driverId: VectorDriverId }>()\n private readonly defaultDriverId: VectorDriverId\n\n constructor(private readonly opts: VectorIndexServiceOptions) {\n for (const driver of opts.drivers) {\n this.driverMap.set(driver.id, driver)\n }\n this.defaultDriverId = opts.defaultDriverId ?? 'pgvector'\n for (const moduleConfig of opts.moduleConfigs) {\n const driverId = moduleConfig.defaultDriverId ?? this.defaultDriverId\n for (const entity of moduleConfig.entities ?? []) {\n if (!entity?.entityId) continue\n if (entity.enabled === false) continue\n const targetDriver = entity.driverId ?? driverId\n this.entityConfig.set(entity.entityId, { config: entity, driverId: targetDriver })\n }\n }\n }\n\n private resolveEncryptionService(): TenantDataEncryptionService | null {\n if (!this.opts.containerResolver) return null\n try {\n const container = this.opts.containerResolver() as any\n if (!container || typeof container.resolve !== 'function') return null\n return container.resolve('tenantEncryptionService') as TenantDataEncryptionService\n } catch {\n return null\n }\n }\n\n private async encryptResultFields(args: {\n tenantId: string\n organizationId: string | null\n resultTitle: string\n resultSubtitle: string | null\n resultIcon: string | null\n resultSnapshot: string | null\n primaryLinkHref: string | null\n primaryLinkLabel: string | null\n links: VectorLinkDescriptor[] | string | null\n payload: Record<string, unknown> | string | null\n }): Promise<{\n resultTitle: string\n resultSubtitle: string | null\n resultIcon: string | null\n resultSnapshot: string | null\n primaryLinkHref: string | null\n primaryLinkLabel: string | null\n links: VectorLinkDescriptor[] | string | null\n payload: Record<string, unknown> | string | null\n }> {\n const service = this.resolveEncryptionService()\n if (!service || !service.isEnabled?.()) {\n return {\n resultTitle: args.resultTitle,\n resultSubtitle: args.resultSubtitle,\n resultIcon: args.resultIcon,\n resultSnapshot: args.resultSnapshot,\n primaryLinkHref: args.primaryLinkHref,\n primaryLinkLabel: args.primaryLinkLabel,\n links: args.links,\n payload: args.payload,\n }\n }\n try {\n const encrypted = await service.encryptEntityPayload(\n VECTOR_ENTRY_ENCRYPTION_ENTITY_ID,\n {\n resultTitle: args.resultTitle,\n resultSubtitle: args.resultSubtitle,\n resultIcon: args.resultIcon,\n resultSnapshot: args.resultSnapshot,\n primaryLinkHref: args.primaryLinkHref,\n primaryLinkLabel: args.primaryLinkLabel,\n links: args.links,\n payload: args.payload,\n },\n args.tenantId,\n args.organizationId,\n )\n return {\n resultTitle: String((encrypted as any).resultTitle ?? args.resultTitle),\n resultSubtitle: ((encrypted as any).resultSubtitle ?? args.resultSubtitle) as any,\n resultIcon: ((encrypted as any).resultIcon ?? args.resultIcon) as any,\n resultSnapshot: ((encrypted as any).resultSnapshot ?? args.resultSnapshot) as any,\n primaryLinkHref: ((encrypted as any).primaryLinkHref ?? args.primaryLinkHref) as any,\n primaryLinkLabel: ((encrypted as any).primaryLinkLabel ?? args.primaryLinkLabel) as any,\n links: ((encrypted as any).links ?? args.links) as any,\n payload: ((encrypted as any).payload ?? args.payload) as any,\n }\n } catch (err) {\n searchDebugWarn('vector.index', 'Vector entry encryption failed; refusing to persist plaintext', {\n entityId: VECTOR_ENTRY_ENCRYPTION_ENTITY_ID,\n tenantId: args.tenantId,\n organizationId: args.organizationId,\n error: err instanceof Error ? err.message : String(err),\n })\n throw err instanceof Error\n ? err\n : new Error('[internal] Vector entry encryption failed')\n }\n }\n\n private async decryptResultFields(args: {\n tenantId: string\n organizationId: string | null\n resultTitle: string\n resultSubtitle: string | null\n resultIcon: string | null\n resultSnapshot: string | null\n primaryLinkHref: string | null\n primaryLinkLabel: string | null\n links: VectorLinkDescriptor[] | string | null\n payload: Record<string, unknown> | string | null\n }): Promise<{\n resultTitle: string\n resultSubtitle: string | null\n resultIcon: string | null\n resultSnapshot: string | null\n primaryLinkHref: string | null\n primaryLinkLabel: string | null\n links: VectorLinkDescriptor[] | string | null\n payload: Record<string, unknown> | string | null\n }> {\n const service = this.resolveEncryptionService()\n if (!service || !service.isEnabled?.() || typeof service.decryptEntityPayload !== 'function') {\n return {\n resultTitle: args.resultTitle,\n resultSubtitle: args.resultSubtitle,\n resultIcon: args.resultIcon,\n resultSnapshot: args.resultSnapshot,\n primaryLinkHref: args.primaryLinkHref,\n primaryLinkLabel: args.primaryLinkLabel,\n links: args.links,\n payload: args.payload,\n }\n }\n try {\n const decrypted = await service.decryptEntityPayload(\n VECTOR_ENTRY_ENCRYPTION_ENTITY_ID,\n {\n resultTitle: args.resultTitle,\n resultSubtitle: args.resultSubtitle,\n resultIcon: args.resultIcon,\n resultSnapshot: args.resultSnapshot,\n primaryLinkHref: args.primaryLinkHref,\n primaryLinkLabel: args.primaryLinkLabel,\n links: args.links,\n payload: args.payload,\n },\n args.tenantId,\n args.organizationId,\n )\n const decryptedLinks = (decrypted as any).links ?? args.links\n const decryptedPayload = (decrypted as any).payload ?? args.payload\n return {\n resultTitle: String((decrypted as any).resultTitle ?? args.resultTitle),\n resultSubtitle: ((decrypted as any).resultSubtitle ?? args.resultSubtitle) as any,\n resultIcon: ((decrypted as any).resultIcon ?? args.resultIcon) as any,\n resultSnapshot: ((decrypted as any).resultSnapshot ?? args.resultSnapshot) as any,\n primaryLinkHref: ((decrypted as any).primaryLinkHref ?? args.primaryLinkHref) as any,\n primaryLinkLabel: ((decrypted as any).primaryLinkLabel ?? args.primaryLinkLabel) as any,\n links: (typeof decryptedLinks === 'string' ? parseDecryptedFieldValue(decryptedLinks) : decryptedLinks) as any,\n payload: (typeof decryptedPayload === 'string' ? parseDecryptedFieldValue(decryptedPayload) : decryptedPayload) as any,\n }\n } catch (err) {\n searchDebugWarn('vector.index', 'Vector entry decryption failed; refusing to return ciphertext as plaintext', {\n entityId: VECTOR_ENTRY_ENCRYPTION_ENTITY_ID,\n tenantId: args.tenantId,\n organizationId: args.organizationId,\n error: err instanceof Error ? err.message : String(err),\n })\n throw err instanceof Error\n ? err\n : new Error('[internal] Vector entry decryption failed')\n }\n }\n\n listEnabledEntities(): EntityId[] {\n return Array.from(this.entityConfig.keys())\n }\n\n async ensureDriverReady(entityId?: EntityId): Promise<void> {\n if (entityId) {\n const entry = this.entityConfig.get(entityId)\n if (!entry) return\n const driver = this.getDriver(entry.driverId)\n await driver.ensureReady()\n return\n }\n const uniqueDrivers = new Set<VectorDriverId>()\n for (const entry of this.entityConfig.values()) {\n uniqueDrivers.add(entry.driverId)\n }\n if (!uniqueDrivers.size) uniqueDrivers.add(this.defaultDriverId)\n await Promise.all(Array.from(uniqueDrivers).map(async (driverId) => {\n try {\n const driver = this.getDriver(driverId)\n await driver.ensureReady()\n } catch (err) {\n searchDebugWarn('vector', 'Failed to ensure driver readiness', { driverId, error: err instanceof Error ? err.message : err })\n }\n }))\n }\n\n private getDriver(driverId: VectorDriverId): VectorDriver {\n const driver = this.driverMap.get(driverId)\n if (!driver) {\n throw new Error(`[vector] Driver ${driverId} is not registered`)\n }\n return driver\n }\n\n private getEnrichmentFields(entityId: EntityId): string[] | undefined {\n const hints = ENRICHMENT_FIELD_HINTS[entityId]\n if (!hints) return undefined\n const unique = new Set<string>(['id', ...hints])\n return Array.from(unique)\n }\n\n private async fetchRecord(entityId: EntityId, recordIds: string[], tenantId: string, organizationId?: string | null) {\n const filters: Record<string, any> = { id: { $in: recordIds } }\n const result = await this.opts.queryEngine.query(entityId, {\n tenantId,\n organizationId: organizationId ?? undefined,\n filters,\n includeCustomFields: true,\n fields: this.getEnrichmentFields(entityId),\n skipAutoReindex: true,\n })\n const byId = new Map<string, Record<string, any>>()\n for (const item of result.items) {\n const key = String((item as any).id ?? '')\n if (!key) continue\n byId.set(key, item as Record<string, any>)\n }\n return byId\n }\n\n private extractRecordPayload(entityId: EntityId, raw: Record<string, any>) {\n const record: Record<string, any> = {}\n const customFields: Record<string, any> = {}\n const multiMap = new Map<string, boolean>()\n\n for (const [key, value] of Object.entries(raw)) {\n if (key.startsWith('cf:') && key.endsWith('__is_multi')) {\n const base = key.replace(/__is_multi$/, '')\n multiMap.set(base, Boolean(value))\n continue\n }\n if (key.startsWith('cf:')) {\n customFields[key.slice(3)] = value\n continue\n }\n record[key] = value\n }\n\n for (const [key, isMulti] of multiMap.entries()) {\n const bare = key.slice(3)\n if (bare && customFields[bare] != null && !Array.isArray(customFields[bare]) && isMulti) {\n customFields[bare] = [customFields[bare]]\n }\n }\n\n if (record.entity_id == null && record.entityId == null && entityId.endsWith('_company_profile')) {\n searchDebugWarn('vector.index', 'company profile missing entity id in payload', {\n id: record.id,\n keys: Object.keys(record),\n })\n }\n\n return { record, customFields }\n }\n\n private async indexExisting(\n entry: { config: VectorEntityConfig; driverId: VectorDriverId },\n driver: VectorDriver,\n args: IndexRecordArgs,\n raw: Record<string, any>,\n opts: { skipDelete?: boolean } = {},\n ): Promise<VectorIndexOperationResult> {\n const scopeOrg = args.organizationId ?? null\n const { record, customFields } = this.extractRecordPayload(args.entityId, raw)\n const resolvedOrgId = scopeOrg ?? (record.organization_id ?? record.organizationId ?? null)\n const source = await this.resolveSource(args.entityId, entry.config, {\n record,\n customFields,\n tenantId: args.tenantId,\n organizationId: resolvedOrgId,\n })\n if (!source) {\n const existing = await driver.getChecksum(args.entityId, args.recordId, args.tenantId)\n if (!opts.skipDelete && existing) {\n await driver.delete(args.entityId, args.recordId, args.tenantId)\n return {\n action: 'deleted',\n existed: true,\n tenantId: args.tenantId,\n organizationId: resolvedOrgId,\n }\n }\n return {\n action: 'skipped',\n existed: Boolean(existing),\n tenantId: args.tenantId,\n organizationId: resolvedOrgId,\n reason: 'missing_record',\n }\n }\n\n const checksumSource = source.checksumSource ?? { record, customFields }\n const checksum = computeChecksum(checksumSource)\n const current = await driver.getChecksum(args.entityId, args.recordId, args.tenantId)\n if (current && current === checksum) {\n return {\n action: 'skipped',\n existed: true,\n tenantId: args.tenantId,\n organizationId: scopeOrg,\n reason: 'checksum_match',\n }\n }\n if (!this.opts.embeddingService.available) {\n throw new Error('[vector] Embedding service unavailable (missing OPENAI_API_KEY)')\n }\n const embedding = await this.opts.embeddingService.createEmbedding(source.input)\n const presenter = await this.resolvePresenter(entry.config, {\n record,\n customFields,\n tenantId: args.tenantId,\n organizationId: resolvedOrgId,\n }, source.presenter ?? null)\n if (!presenter?.title) {\n searchDebugWarn('vector.index', 'missing presenter title', {\n entityId: args.entityId,\n recordId: args.recordId,\n recordSample: {\n display_name: record.display_name,\n displayName: record.displayName,\n name: record.name,\n title: record.title,\n subject: record.subject,\n kind: record.kind,\n },\n })\n }\n const links = await this.resolveLinks(entry.config, {\n record,\n customFields,\n tenantId: args.tenantId,\n organizationId: resolvedOrgId,\n }, source.links ?? null)\n const url = await this.resolveUrl(entry.config, {\n record,\n customFields,\n tenantId: args.tenantId,\n organizationId: resolvedOrgId,\n })\n\n const metadataRecord = extractSearchableFields(record, { fieldPolicy: entry.config.fieldPolicy })\n const metadataCustomFields = extractSearchableFields(customFields, { fieldPolicy: entry.config.fieldPolicy })\n const normalizedPresenter = this.ensurePresenter(presenter, metadataRecord, metadataCustomFields, args.recordId, args.entityId)\n const normalizedLinks = Array.isArray(links) && links.length ? links : null\n const snapshot = this.deriveSnapshot(metadataRecord, metadataCustomFields)\n const rawResultTitle = this.resolveResultTitle(normalizedPresenter, metadataRecord, metadataCustomFields, args.recordId)\n const rawResultSubtitle = normalizedPresenter.subtitle ?? snapshot ?? null\n const rawResultIcon = normalizedPresenter.icon ?? this.mapDefaultIcon(args.entityId)\n const resultBadge = normalizedPresenter.badge ?? null\n const primaryLink = this.resolvePrimaryLink(normalizedLinks, url, rawResultTitle)\n\n const encryptedResult = await this.encryptResultFields({\n tenantId: args.tenantId,\n organizationId: resolvedOrgId,\n resultTitle: rawResultTitle,\n resultSubtitle: rawResultSubtitle,\n resultIcon: rawResultIcon ?? null,\n resultSnapshot: snapshot ?? null,\n primaryLinkHref: primaryLink?.href ?? null,\n primaryLinkLabel: primaryLink?.label ?? null,\n links: normalizedLinks,\n payload: source.payload ?? null,\n })\n\n const presenterForStorage: VectorResultPresenter = {\n title: encryptedResult.resultTitle,\n subtitle: encryptedResult.resultSubtitle ?? undefined,\n icon: encryptedResult.resultIcon ?? undefined,\n badge: resultBadge ?? undefined,\n }\n\n searchDebug('VectorIndexService', 'Storing vector index entry', {\n entityId: args.entityId,\n recordId: args.recordId,\n tenantId: args.tenantId,\n organizationId: resolvedOrgId,\n })\n await driver.upsert({\n driverId: entry.driverId,\n entityId: args.entityId,\n recordId: args.recordId,\n tenantId: args.tenantId,\n organizationId: resolvedOrgId,\n checksum,\n embedding,\n url: url ?? null,\n presenter: presenterForStorage,\n links: (encryptedResult.links as any) ?? normalizedLinks,\n payload: (encryptedResult.payload as any) ?? source.payload ?? null,\n resultTitle: encryptedResult.resultTitle,\n resultSubtitle: encryptedResult.resultSubtitle,\n resultIcon: encryptedResult.resultIcon ?? null,\n resultBadge,\n resultSnapshot: encryptedResult.resultSnapshot ?? snapshot,\n primaryLinkHref: encryptedResult.primaryLinkHref ?? primaryLink?.href ?? null,\n primaryLinkLabel: encryptedResult.primaryLinkLabel ?? null,\n })\n\n return {\n action: 'indexed',\n created: !current,\n existed: true,\n tenantId: args.tenantId,\n organizationId: resolvedOrgId,\n }\n }\n\n private ensurePresenter(\n presenter: VectorResultPresenter | null,\n record: Record<string, any>,\n customFields: Record<string, any>,\n recordId: string,\n entityId: EntityId,\n ): VectorResultPresenter {\n if (presenter?.title) return presenter\n const fallback = this.buildFallbackPresenter(record, customFields, recordId, entityId)\n return fallback\n }\n\n private resolveResultTitle(\n presenter: VectorResultPresenter,\n record: Record<string, any>,\n customFields: Record<string, any>,\n recordId: string,\n ): string {\n const candidate =\n presenter.title ??\n record.display_name ??\n record.displayName ??\n record.name ??\n record.title ??\n record.subject ??\n customFields.title ??\n customFields.name ??\n recordId\n const text = String(candidate).trim()\n return text.length ? text : recordId\n }\n\n private buildFallbackPresenter(\n record: Record<string, any>,\n customFields: Record<string, any>,\n recordId: string,\n entityId: EntityId,\n ): VectorResultPresenter {\n const titleCandidate =\n record.display_name ??\n record.displayName ??\n record.name ??\n record.title ??\n record.subject ??\n recordId\n const subtitleCandidate =\n record.description ??\n record.summary ??\n record.body ??\n customFields.summary ??\n customFields.description ??\n null\n const icon = typeof record.kind === 'string'\n ? this.mapEntityIcon(record.kind)\n : this.mapDefaultIcon(entityId)\n return {\n title: String(titleCandidate),\n subtitle: subtitleCandidate ? String(subtitleCandidate) : undefined,\n icon: icon ?? undefined,\n }\n }\n\n private mapEntityIcon(kind?: string | null): string | null {\n if (!kind) return null\n const normalized = kind.toLowerCase()\n if (normalized === 'person') return 'user'\n if (normalized === 'company' || normalized === 'organization') return 'building'\n return null\n }\n\n private mapDefaultIcon(entityId: EntityId): string | null {\n if (entityId.startsWith('customers:customer_deal')) return 'briefcase'\n if (entityId.startsWith('customers:customer_comment')) return 'sticky-note'\n if (entityId.startsWith('customers:customer_activity')) return 'bolt'\n if (entityId.startsWith('customers:customer_todo')) return 'check-square'\n return null\n }\n\n private resolvePrimaryLink(\n links: VectorLinkDescriptor[] | null,\n url: string | null,\n fallbackLabel: string,\n ): { href: string; label: string } | null {\n if (links?.length) {\n const primary = links.find((link) => link.kind === 'primary') ?? links[0]\n if (primary?.href) {\n return { href: primary.href, label: primary.label ?? fallbackLabel }\n }\n }\n if (url) {\n return { href: url, label: fallbackLabel }\n }\n return null\n }\n\n private deriveSnapshot(\n record: Record<string, any>,\n customFields: Record<string, any>,\n ): string | null {\n const candidates = [\n record.summary,\n record.description,\n record.body,\n customFields.summary,\n customFields.description,\n customFields.body,\n ]\n for (const value of candidates) {\n if (typeof value === 'string') {\n const trimmed = value.trim()\n if (trimmed.length) return trimmed\n }\n }\n return null\n }\n\n private buildDefaultSource(\n entityId: EntityId,\n payload: { record: Record<string, any>; customFields: Record<string, any> },\n fieldPolicy?: SearchFieldPolicy,\n ): VectorIndexSource {\n const { record, customFields } = payload\n const lines: string[] = []\n\n const searchableWhitelist = fieldPolicy?.searchable ? new Set(fieldPolicy.searchable) : null\n const excludedFields = new Set<string>([\n ...(fieldPolicy?.excluded ?? []),\n ...(fieldPolicy?.hashOnly ?? []),\n ])\n\n const isEligibleField = (field: string): boolean => {\n if (DEFAULT_SOURCE_SCOPE_FIELDS.has(field)) return false\n if (excludedFields.has(field)) return false\n if (searchableWhitelist && !searchableWhitelist.has(field)) return false\n return true\n }\n\n const pushEntry = (label: string, value: unknown) => {\n if (value === null || value === undefined) return\n if (typeof value === 'string' && value.trim().length === 0) return\n if (typeof value === 'object') {\n lines.push(`${label}: ${JSON.stringify(value)}`)\n } else {\n lines.push(`${label}: ${value}`)\n }\n }\n\n const preferredFields = ['title', 'name', 'displayName', 'summary', 'subject']\n for (const field of preferredFields) {\n if (!isEligibleField(field)) continue\n if (record[field] != null) pushEntry(field, record[field])\n }\n\n for (const [key, value] of Object.entries(record)) {\n if (preferredFields.includes(key)) continue\n if (!isEligibleField(key)) continue\n pushEntry(key, value)\n }\n\n for (const [key, value] of Object.entries(customFields)) {\n if (!isEligibleField(key)) continue\n pushEntry(`custom.${key}`, value)\n }\n\n if (lines.length === 0) {\n lines.push(`${entityId}#${record.id ?? ''}`)\n }\n\n return {\n input: lines,\n payload: null,\n checksumSource: { record, customFields },\n }\n }\n\n private async resolveSource(entityId: EntityId, config: VectorEntityConfig, ctx: {\n record: Record<string, any>\n customFields: Record<string, any>\n organizationId?: string | null\n tenantId: string\n }): Promise<VectorIndexSource | null> {\n const baseCtx = {\n record: ctx.record,\n customFields: ctx.customFields,\n organizationId: ctx.organizationId ?? null,\n tenantId: ctx.tenantId,\n queryEngine: this.opts.queryEngine,\n container: this.opts.containerResolver ? this.opts.containerResolver() : undefined,\n }\n if (config.buildSource) {\n const built = await config.buildSource(baseCtx)\n if (built) return built\n return null\n }\n return this.buildDefaultSource(entityId, { record: ctx.record, customFields: ctx.customFields }, config.fieldPolicy)\n }\n\n private async resolvePresenter(\n config: VectorEntityConfig,\n ctx: {\n record: Record<string, any>\n customFields: Record<string, any>\n organizationId?: string | null\n tenantId: string\n },\n fallback?: VectorResultPresenter | null,\n ): Promise<VectorResultPresenter | null> {\n const baseCtx = {\n record: ctx.record,\n customFields: ctx.customFields,\n organizationId: ctx.organizationId ?? null,\n tenantId: ctx.tenantId,\n queryEngine: this.opts.queryEngine,\n container: this.opts.containerResolver ? this.opts.containerResolver() : undefined,\n }\n if (config.formatResult) {\n const formatted = await config.formatResult(baseCtx)\n if (formatted) return formatted\n }\n if (fallback) return fallback\n const fields = extractSearchableFields(ctx.record, { fieldPolicy: config.fieldPolicy })\n const nameLike = fields.displayName || fields.title || fields.name\n if (typeof nameLike === 'string' && nameLike.trim().length > 0) {\n const subtitle = fields.description || fields.summary\n return {\n title: nameLike,\n subtitle: typeof subtitle === 'string' ? subtitle : undefined,\n }\n }\n return null\n }\n\n private async resolveLinks(\n config: VectorEntityConfig,\n ctx: {\n record: Record<string, any>\n customFields: Record<string, any>\n organizationId?: string | null\n tenantId: string\n },\n fallback?: VectorLinkDescriptor[] | null,\n ): Promise<VectorLinkDescriptor[] | null> {\n const baseCtx = {\n record: ctx.record,\n customFields: ctx.customFields,\n organizationId: ctx.organizationId ?? null,\n tenantId: ctx.tenantId,\n queryEngine: this.opts.queryEngine,\n container: this.opts.containerResolver ? this.opts.containerResolver() : undefined,\n }\n if (config.resolveLinks) {\n const resolved = await config.resolveLinks(baseCtx)\n if (resolved?.length) return resolved\n }\n return fallback ?? null\n }\n\n private async resolveUrl(\n config: VectorEntityConfig,\n ctx: {\n record: Record<string, any>\n customFields: Record<string, any>\n organizationId?: string | null\n tenantId: string\n },\n fallback?: string | null,\n ): Promise<string | null> {\n if (config.resolveUrl) {\n const candidate = await config.resolveUrl({\n record: ctx.record,\n customFields: ctx.customFields,\n organizationId: ctx.organizationId ?? null,\n tenantId: ctx.tenantId,\n queryEngine: this.opts.queryEngine,\n container: this.opts.containerResolver ? this.opts.containerResolver() : undefined,\n })\n if (candidate) return candidate\n }\n return fallback ?? null\n }\n\n async indexRecord(args: IndexRecordArgs): Promise<VectorIndexOperationResult> {\n const entry = this.entityConfig.get(args.entityId)\n if (!entry) {\n return {\n action: 'skipped',\n existed: false,\n tenantId: args.tenantId,\n organizationId: args.organizationId ?? null,\n reason: 'unsupported',\n }\n }\n const driver = this.getDriver(entry.driverId)\n await driver.ensureReady()\n\n const records = await this.fetchRecord(args.entityId, [args.recordId], args.tenantId, args.organizationId)\n const raw = records.get(args.recordId)\n if (!raw) {\n const existing = await driver.getChecksum(args.entityId, args.recordId, args.tenantId)\n if (existing) {\n await driver.delete(args.entityId, args.recordId, args.tenantId)\n return {\n action: 'deleted',\n existed: true,\n tenantId: args.tenantId,\n organizationId: args.organizationId ?? null,\n }\n }\n return {\n action: 'skipped',\n existed: false,\n tenantId: args.tenantId,\n organizationId: args.organizationId ?? null,\n reason: 'missing_record',\n }\n }\n return this.indexExisting(entry, driver, args, raw as Record<string, any>)\n }\n\n async deleteRecord(args: DeleteRecordArgs): Promise<VectorIndexOperationResult> {\n const entry = this.entityConfig.get(args.entityId)\n if (!entry) {\n return {\n action: 'skipped',\n existed: false,\n tenantId: args.tenantId,\n organizationId: args.organizationId ?? null,\n reason: 'unsupported',\n }\n }\n const driver = this.getDriver(entry.driverId)\n await driver.ensureReady()\n const scopeOrg = args.organizationId ?? null\n const existing = await driver.getChecksum(args.entityId, args.recordId, args.tenantId)\n if (existing) {\n await driver.delete(args.entityId, args.recordId, args.tenantId)\n return {\n action: 'deleted',\n existed: true,\n tenantId: args.tenantId,\n organizationId: scopeOrg,\n }\n }\n return {\n action: 'skipped',\n existed: false,\n tenantId: args.tenantId,\n organizationId: scopeOrg,\n reason: 'missing_record',\n }\n }\n\n async reindexEntity(args: { entityId: EntityId; tenantId?: string | null; organizationId?: string | null; purgeFirst?: boolean }): Promise<void> {\n const entry = this.entityConfig.get(args.entityId)\n if (!entry) return\n const driver = this.getDriver(entry.driverId)\n await driver.ensureReady()\n\n const shouldPurge = args.purgeFirst === true\n const reindexStartedAt = new Date()\n\n if (this.opts.eventBus) {\n if (shouldPurge && driver.purge && args.tenantId) {\n await driver.purge(args.entityId, args.tenantId, args.organizationId)\n } else if (shouldPurge && !args.tenantId) {\n searchDebugWarn('vector', 'Skipping purge for multi-tenant reindex (tenant not provided)')\n }\n const payload: Record<string, unknown> = {\n entityType: args.entityId,\n }\n if (shouldPurge) {\n payload.force = true\n payload.resetCoverage = true\n }\n if (args.tenantId !== undefined) payload.tenantId = args.tenantId\n else payload.allowAllTenants = true\n if (args.organizationId !== undefined) payload.organizationId = args.organizationId\n await this.opts.eventBus.emitEvent('query_index.reindex', payload)\n return\n }\n\n if (!args.tenantId) {\n throw new Error('[vector] Reindex without tenantId requires event bus integration')\n }\n\n if (shouldPurge && driver.purge) {\n await driver.purge(args.entityId, args.tenantId, args.organizationId)\n }\n\n const pageSize = 50\n let page = 1\n const loggingEm = this.resolveEntityManager()\n for (;;) {\n const result = await this.opts.queryEngine.query(args.entityId, {\n tenantId: args.tenantId,\n organizationId: args.organizationId ?? undefined,\n page: { page, pageSize },\n includeCustomFields: true,\n fields: this.getEnrichmentFields(args.entityId),\n skipAutoReindex: true,\n })\n if (!result.items.length) break\n for (const raw of result.items) {\n const recordId = String((raw as any).id ?? '')\n if (!recordId) continue\n const opResult = await this.indexExisting(\n entry,\n driver,\n {\n entityId: args.entityId,\n recordId,\n tenantId: args.tenantId,\n organizationId: args.organizationId ?? null,\n },\n raw as Record<string, any>,\n { skipDelete: true },\n )\n await logVectorOperation({\n em: loggingEm,\n handler: 'service:vector.reindex',\n entityType: args.entityId,\n recordId,\n result: opResult,\n })\n }\n if (result.items.length < pageSize) break\n page += 1\n }\n\n if (shouldPurge) {\n await this.removeOrphans({\n entityId: args.entityId,\n tenantId: args.tenantId,\n organizationId: args.organizationId,\n olderThan: reindexStartedAt,\n })\n }\n }\n\n async reindexAll(args: { tenantId?: string | null; organizationId?: string | null; purgeFirst?: boolean }): Promise<void> {\n for (const entityId of this.listEnabledEntities()) {\n await this.reindexEntity({ entityId, tenantId: args.tenantId, organizationId: args.organizationId ?? null, purgeFirst: args.purgeFirst })\n }\n }\n\n private resolveEntityManager(): EntityManager | null {\n if (!this.opts.containerResolver) return null\n try {\n const container = this.opts.containerResolver()\n if (!container || typeof (container as any).resolve !== 'function') return null\n const resolver = container as { resolve(name: string): unknown }\n const em = resolver.resolve('em')\n return (em as EntityManager | null) ?? null\n } catch {\n return null\n }\n }\n\n async purgeIndex(args: { tenantId: string; organizationId?: string | null; entityId?: EntityId | null }): Promise<void> {\n const targets = args.entityId ? [args.entityId] : this.listEnabledEntities()\n if (!targets.length) return\n\n const grouped = new Map<VectorDriverId, EntityId[]>()\n for (const entityId of targets) {\n const cfg = this.entityConfig.get(entityId)\n if (!cfg) continue\n const driver = this.getDriver(cfg.driverId)\n if (typeof driver.purge !== 'function') {\n throw new Error(`[vector] Driver ${cfg.driverId} does not support purging entities`)\n }\n if (!grouped.has(cfg.driverId)) grouped.set(cfg.driverId, [])\n grouped.get(cfg.driverId)!.push(entityId)\n }\n\n for (const [driverId, entityIds] of grouped.entries()) {\n const driver = this.getDriver(driverId)\n await driver.ensureReady()\n for (const entityId of entityIds) {\n await driver.purge!(entityId, args.tenantId)\n }\n }\n }\n\n async removeOrphans(args: {\n entityId: EntityId\n tenantId?: string | null\n organizationId?: string | null\n olderThan: Date\n }): Promise<number> {\n const entry = this.entityConfig.get(args.entityId)\n if (!entry) return 0\n const driver = this.getDriver(entry.driverId)\n await driver.ensureReady()\n\n const driverAny = driver as VectorDriver & {\n removeOrphans?: (params: {\n entityId: EntityId\n tenantId?: string | null\n organizationId?: string | null\n olderThan: Date\n }) => Promise<number | void>\n }\n\n if (typeof driverAny.removeOrphans === 'function') {\n const deleted = await driverAny.removeOrphans({\n entityId: args.entityId,\n tenantId: args.tenantId,\n organizationId: args.organizationId,\n olderThan: args.olderThan,\n })\n return typeof deleted === 'number' ? deleted : 0\n }\n\n searchDebugWarn('vector', 'Driver does not support orphan cleanup', { driverId: entry.driverId })\n return 0\n }\n\n async countIndexEntries(args: {\n tenantId: string\n organizationId?: string | null\n entityId?: EntityId\n driverId?: VectorDriverId\n }): Promise<number> {\n if (!args.tenantId) return 0\n const targetEntity = args.entityId ? this.entityConfig.get(args.entityId) : null\n if (args.entityId && !targetEntity) {\n return 0\n }\n const driverId =\n args.driverId ??\n (targetEntity ? targetEntity.driverId : this.defaultDriverId)\n const driver = this.getDriver(driverId)\n await driver.ensureReady()\n const countParams = {\n tenantId: args.tenantId,\n organizationId: args.organizationId,\n entityId: args.entityId,\n }\n if (typeof driver.count === 'function') {\n try {\n return await driver.count(countParams)\n } catch (err) {\n searchDebugWarn('vector', 'Driver count failed, falling back to list', {\n driverId,\n error: err instanceof Error ? err.message : err,\n })\n }\n }\n if (typeof driver.list === 'function') {\n const limit = 1000\n let offset = 0\n let total = 0\n for (;;) {\n const batch = await driver.list({\n tenantId: countParams.tenantId,\n organizationId: countParams.organizationId,\n entityId: countParams.entityId,\n limit,\n offset,\n orderBy: 'created',\n })\n const size = batch.length\n total += size\n if (size < limit) break\n offset += limit\n }\n return total\n }\n searchDebugWarn('vector', 'Driver does not support counting or listing index entries', { driverId })\n return 0\n }\n\n async listIndexEntries(args: {\n tenantId: string\n organizationId?: string | null\n entityId?: EntityId\n limit?: number\n offset?: number\n driverId?: VectorDriverId\n }): Promise<VectorIndexEntry[]> {\n const targetEntity = args.entityId ? this.entityConfig.get(args.entityId) : null\n if (args.entityId && !targetEntity) {\n return []\n }\n const driverId =\n args.driverId ??\n (targetEntity ? targetEntity.driverId : this.defaultDriverId)\n const driver = this.getDriver(driverId)\n if (typeof driver.list !== 'function') {\n throw new Error(`[vector] Driver ${driverId} does not support listing index entries`)\n }\n await driver.ensureReady()\n const list = await driver.list({\n tenantId: args.tenantId,\n organizationId: args.organizationId,\n entityId: args.entityId,\n limit: args.limit,\n offset: args.offset,\n orderBy: 'updated',\n })\n if (!list.length) {\n return []\n }\n\n const decrypted = await Promise.all(\n list.map(async (entry) => {\n const decryptedResult = await this.decryptResultFields({\n tenantId: args.tenantId,\n organizationId: entry.organizationId ?? null,\n resultTitle: entry.resultTitle,\n resultSubtitle: entry.resultSubtitle ?? null,\n resultIcon: entry.resultIcon ?? null,\n resultSnapshot: entry.resultSnapshot ?? null,\n primaryLinkHref: entry.primaryLinkHref ?? null,\n primaryLinkLabel: entry.primaryLinkLabel ?? null,\n links: (entry.links as any) ?? null,\n payload: (entry.payload as any) ?? null,\n })\n return {\n ...entry,\n resultTitle: decryptedResult.resultTitle,\n resultSubtitle: decryptedResult.resultSubtitle,\n resultIcon: decryptedResult.resultIcon,\n resultSnapshot: decryptedResult.resultSnapshot,\n primaryLinkHref: decryptedResult.primaryLinkHref,\n primaryLinkLabel: decryptedResult.primaryLinkLabel,\n links: decryptedResult.links as any,\n payload: decryptedResult.payload as any,\n metadata: (decryptedResult.payload as any) ?? null,\n }\n }),\n )\n\n return decrypted.map((entry) => {\n const presenter = {\n title: entry.resultTitle,\n subtitle: entry.resultSubtitle ?? undefined,\n icon: entry.resultIcon ?? undefined,\n badge: entry.presenter?.badge ?? entry.resultBadge ?? undefined,\n }\n const links = entry.links ?? (entry.primaryLinkHref\n ? [{ href: entry.primaryLinkHref, label: entry.primaryLinkLabel ?? entry.resultTitle, kind: 'primary' as const }]\n : null)\n const url = entry.url ?? entry.primaryLinkHref ?? null\n const metadata = entry.metadata ?? (entry.resultSnapshot ? { snapshot: entry.resultSnapshot } : null)\n return {\n ...entry,\n driverId,\n presenter,\n links,\n url,\n metadata,\n score: entry.score ?? null,\n }\n })\n }\n\n async search(request: VectorQueryRequest): Promise<VectorSearchHit[]> {\n const driverId = request.driverId ?? this.defaultDriverId\n const driver = this.getDriver(driverId)\n await driver.ensureReady()\n if (!this.opts.embeddingService.available) {\n throw new Error('[vector] Embedding service unavailable (missing OPENAI_API_KEY)')\n }\n const embedding = await this.opts.embeddingService.createEmbedding(request.query)\n const hits = await driver.query({\n vector: embedding,\n limit: request.limit ?? 10,\n filter: {\n tenantId: request.tenantId,\n organizationId: request.organizationId ?? null,\n entityIds: undefined,\n },\n })\n\n if (!hits.length) return []\n\n const decrypted = await Promise.all(\n hits.map(async (hit) => {\n const decryptedResult = await this.decryptResultFields({\n tenantId: request.tenantId,\n organizationId: hit.organizationId ?? request.organizationId ?? null,\n resultTitle: hit.resultTitle,\n resultSubtitle: hit.resultSubtitle ?? null,\n resultIcon: hit.resultIcon ?? null,\n resultSnapshot: hit.resultSnapshot ?? null,\n primaryLinkHref: hit.primaryLinkHref ?? null,\n primaryLinkLabel: hit.primaryLinkLabel ?? null,\n links: (hit.links as any) ?? null,\n payload: (hit.payload as any) ?? null,\n })\n return {\n ...hit,\n resultTitle: decryptedResult.resultTitle,\n resultSubtitle: decryptedResult.resultSubtitle,\n resultIcon: decryptedResult.resultIcon,\n resultSnapshot: decryptedResult.resultSnapshot,\n primaryLinkHref: decryptedResult.primaryLinkHref,\n primaryLinkLabel: decryptedResult.primaryLinkLabel,\n links: decryptedResult.links as any,\n payload: decryptedResult.payload as any,\n }\n }),\n )\n\n return decrypted.map((hit) => {\n const presenter = {\n title: hit.resultTitle,\n subtitle: hit.resultSubtitle ?? undefined,\n icon: hit.resultIcon ?? undefined,\n badge: hit.presenter?.badge ?? hit.resultBadge ?? undefined,\n }\n const links = hit.links ?? (hit.primaryLinkHref\n ? [{ href: hit.primaryLinkHref, label: hit.primaryLinkLabel ?? hit.resultTitle, kind: 'primary' as const }]\n : null)\n const url = hit.url ?? hit.primaryLinkHref ?? null\n const metadata = hit.payload ?? (hit.resultSnapshot ? { snapshot: hit.resultSnapshot } : null)\n return {\n entityId: hit.entityId,\n recordId: hit.recordId,\n score: hit.score,\n url,\n presenter,\n links,\n driverId,\n metadata,\n }\n })\n }\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }