@open-mercato/search 0.6.7 → 0.6.8-develop.6874.1.982d6097d8

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.
Files changed (66) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/AGENTS.md +7 -1
  3. package/dist/lib/presenter-enricher.js +12 -12
  4. package/dist/lib/presenter-enricher.js.map +2 -2
  5. package/dist/modules/search/ai-tools.js +5 -2
  6. package/dist/modules/search/ai-tools.js.map +2 -2
  7. package/dist/modules/search/api/openapi.js +2 -0
  8. package/dist/modules/search/api/openapi.js.map +2 -2
  9. package/dist/modules/search/api/settings/vector-store/route.js +34 -0
  10. package/dist/modules/search/api/settings/vector-store/route.js.map +2 -2
  11. package/dist/modules/search/events.js +13 -0
  12. package/dist/modules/search/events.js.map +7 -0
  13. package/dist/modules/search/frontend/components/GlobalSearchDialog.js +2 -9
  14. package/dist/modules/search/frontend/components/GlobalSearchDialog.js.map +2 -2
  15. package/dist/modules/search/frontend/components/HybridSearchTable.js +2 -11
  16. package/dist/modules/search/frontend/components/HybridSearchTable.js.map +2 -2
  17. package/dist/modules/search/frontend/components/SearchSettingsPageClient.js.map +2 -2
  18. package/dist/modules/search/frontend/components/TopbarSearchInline.js +2 -9
  19. package/dist/modules/search/frontend/components/TopbarSearchInline.js.map +2 -2
  20. package/dist/modules/search/frontend/components/sections/VectorSearchSection.js +15 -3
  21. package/dist/modules/search/frontend/components/sections/VectorSearchSection.js.map +2 -2
  22. package/dist/modules/search/frontend/lib/entityTypeLabel.js +19 -0
  23. package/dist/modules/search/frontend/lib/entityTypeLabel.js.map +7 -0
  24. package/dist/modules/search/i18n/de.json +49 -0
  25. package/dist/modules/search/i18n/en.json +49 -0
  26. package/dist/modules/search/i18n/es.json +49 -0
  27. package/dist/modules/search/i18n/ko.json +239 -0
  28. package/dist/modules/search/i18n/pl.json +49 -0
  29. package/dist/modules/search/lib/auto-indexing.js +7 -8
  30. package/dist/modules/search/lib/auto-indexing.js.map +2 -2
  31. package/dist/service.js +2 -5
  32. package/dist/service.js.map +2 -2
  33. package/dist/strategies/vector.strategy.js +3 -1
  34. package/dist/strategies/vector.strategy.js.map +2 -2
  35. package/dist/vector/drivers/pgvector/index.js +55 -1
  36. package/dist/vector/drivers/pgvector/index.js.map +2 -2
  37. package/dist/vector/types.js.map +2 -2
  38. package/package.json +11 -10
  39. package/src/__tests__/pgvector-extension-availability.test.ts +158 -0
  40. package/src/__tests__/presenter-enricher.test.ts +48 -0
  41. package/src/__tests__/presenter-locale.test.ts +76 -0
  42. package/src/lib/presenter-enricher.ts +14 -14
  43. package/src/modules/search/README.md +4 -1
  44. package/src/modules/search/ai-tools.ts +5 -2
  45. package/src/modules/search/api/__tests__/global-search.routes.test.ts +167 -0
  46. package/src/modules/search/api/openapi.ts +2 -0
  47. package/src/modules/search/api/settings/vector-store/route.ts +43 -0
  48. package/src/modules/search/events.ts +10 -0
  49. package/src/modules/search/frontend/components/GlobalSearchDialog.tsx +2 -15
  50. package/src/modules/search/frontend/components/HybridSearchTable.tsx +2 -17
  51. package/src/modules/search/frontend/components/SearchSettingsPageClient.tsx +2 -0
  52. package/src/modules/search/frontend/components/TopbarSearchInline.tsx +2 -15
  53. package/src/modules/search/frontend/components/sections/VectorSearchSection.tsx +32 -2
  54. package/src/modules/search/frontend/components/sections/__tests__/VectorSearchSection.test.tsx +36 -1
  55. package/src/modules/search/frontend/lib/__tests__/entityTypeLabel.test.ts +25 -0
  56. package/src/modules/search/frontend/lib/entityTypeLabel.ts +22 -0
  57. package/src/modules/search/i18n/de.json +49 -0
  58. package/src/modules/search/i18n/en.json +49 -0
  59. package/src/modules/search/i18n/es.json +49 -0
  60. package/src/modules/search/i18n/ko.json +239 -0
  61. package/src/modules/search/i18n/pl.json +49 -0
  62. package/src/modules/search/lib/auto-indexing.ts +7 -12
  63. package/src/service.ts +2 -7
  64. package/src/strategies/vector.strategy.ts +7 -1
  65. package/src/vector/drivers/pgvector/index.ts +77 -1
  66. package/src/vector/types.ts +14 -0
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../src/service.ts"],
4
- "sourcesContent": ["import type {\n SearchStrategy,\n SearchStrategyId,\n SearchOptions,\n SearchResult,\n SearchServiceOptions,\n ResultMergeConfig,\n IndexableRecord,\n PresenterEnricherFn,\n} from './types'\nimport { mergeAndRankResults } from './lib/merger'\nimport { searchError } from './lib/debug'\nimport { needsSearchResultEnrichment } from './lib/search-result-enrichment'\n\n/**\n * Default merge configuration.\n */\nconst DEFAULT_MERGE_CONFIG: ResultMergeConfig = {\n duplicateHandling: 'highest_score',\n}\n\n/**\n * Cache TTL for strategy availability checks.\n * Short window so connectivity changes (Meilisearch up/down) propagate quickly,\n * long enough to skip per-request RTT to remote backends on hot paths.\n */\nconst STRATEGY_AVAILABILITY_CACHE_TTL_MS = 2_000\n\nfunction normalizeOrganizationFilter(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\n const values = Array.from(new Set(\n options.organizationIds\n .map((value) => (typeof value === 'string' ? value.trim() : ''))\n .filter((value) => value.length > 0),\n ))\n return values\n}\n\nfunction filterResultsByOrganizationScope(results: SearchResult[], options: SearchOptions): SearchResult[] {\n const organizationIds = normalizeOrganizationFilter(options)\n if (!organizationIds) return results\n if (organizationIds.length === 0) return []\n\n const allowed = new Set(organizationIds)\n return results.filter((result) => {\n const organizationId = typeof result.organizationId === 'string' ? result.organizationId.trim() : ''\n return organizationId.length > 0 && allowed.has(organizationId)\n })\n}\n\n/**\n * SearchService orchestrates multiple search strategies, executing searches in parallel\n * and merging results using the RRF algorithm.\n *\n * Features:\n * - Parallel strategy execution for optimal performance\n * - Graceful degradation when strategies fail\n * - Result merging with configurable weights\n * - Strategy availability checking\n *\n * @example\n * ```typescript\n * const service = new SearchService({\n * strategies: [tokenStrategy, vectorStrategy, fulltextStrategy],\n * defaultStrategies: ['fulltext', 'vector', 'tokens'],\n * mergeConfig: {\n * duplicateHandling: 'highest_score',\n * strategyWeights: { fulltext: 1.2, vector: 1.0, tokens: 0.8 },\n * },\n * })\n *\n * const results = await service.search('john doe', { tenantId: 'tenant-123' })\n * ```\n */\nexport class SearchService {\n private readonly strategies: Map<SearchStrategyId, SearchStrategy>\n private readonly defaultStrategies: SearchStrategyId[]\n private readonly fallbackStrategy: SearchStrategyId | undefined\n private readonly mergeConfig: ResultMergeConfig\n private readonly presenterEnricher?: PresenterEnricherFn\n private readonly availabilityCache = new Map<SearchStrategyId, { value: boolean; expiresAt: number }>()\n private readonly availabilityInflight = new Map<SearchStrategyId, Promise<boolean>>()\n private readonly availabilityCacheTtlMs: number\n\n constructor(options: SearchServiceOptions = {}) {\n this.strategies = new Map()\n for (const strategy of options.strategies ?? []) {\n this.strategies.set(strategy.id, strategy)\n }\n this.defaultStrategies = options.defaultStrategies ?? ['tokens']\n this.fallbackStrategy = options.fallbackStrategy\n this.mergeConfig = options.mergeConfig ?? DEFAULT_MERGE_CONFIG\n this.presenterEnricher = options.presenterEnricher\n this.availabilityCacheTtlMs = options.availabilityCacheTtlMs ?? STRATEGY_AVAILABILITY_CACHE_TTL_MS\n }\n\n /**\n * Get all registered strategies.\n */\n getStrategies(): SearchStrategy[] {\n return Array.from(this.strategies.values())\n }\n\n /**\n * Execute a search query across configured strategies.\n *\n * @param query - Search query string\n * @param options - Search options with tenant, filters, etc.\n * @returns Merged and ranked search results\n */\n async search(query: string, options: SearchOptions): Promise<SearchResult[]> {\n const organizationIds = normalizeOrganizationFilter(options)\n if (organizationIds && organizationIds.length === 0) {\n return []\n }\n\n const strategyIds = options.strategies ?? this.defaultStrategies\n const activeStrategies = await this.getAvailableStrategies(strategyIds)\n\n if (activeStrategies.length === 0) {\n // Try fallback strategy if defined\n if (this.fallbackStrategy) {\n const fallback = await this.getAvailableStrategies([this.fallbackStrategy])\n if (fallback.length > 0) {\n activeStrategies.push(...fallback)\n }\n }\n }\n\n if (activeStrategies.length === 0) {\n return []\n }\n\n // Execute searches in parallel with graceful degradation\n const results = await Promise.allSettled(\n activeStrategies.map((strategy) => this.executeStrategySearch(strategy, query, options)),\n )\n\n // Collect successful results, log failures\n const allResults: SearchResult[] = []\n for (let i = 0; i < results.length; i++) {\n const result = results[i]\n if (result.status === 'fulfilled') {\n allResults.push(...result.value)\n } else {\n const strategy = activeStrategies[i]\n searchError('SearchService', 'Strategy search failed', {\n strategyId: strategy?.id,\n error: result.reason instanceof Error ? result.reason.message : result.reason,\n })\n }\n }\n\n // Merge and rank results\n const merged = mergeAndRankResults(allResults, this.mergeConfig)\n const scoped = filterResultsByOrganizationScope(merged, options)\n\n // Enrich results missing presenter or navigation metadata\n return this.enrichResultsWithPresenter(scoped, options.tenantId, options.organizationId)\n }\n\n /**\n * Enrich results that are missing presenter data using the configured enricher.\n * This ensures token-only results get proper titles/subtitles for display.\n */\n private async enrichResultsWithPresenter(\n results: SearchResult[],\n tenantId: string,\n organizationId?: string | null,\n ): Promise<SearchResult[]> {\n // If no enricher configured, return as-is\n if (!this.presenterEnricher) return results\n\n const hasMissing = results.some(needsSearchResultEnrichment)\n if (!hasMissing) return results\n\n // Use the configured presenter enricher\n try {\n return await this.presenterEnricher(results, tenantId, organizationId)\n } catch {\n // Enrichment failed, return results as-is\n return results\n }\n }\n\n /**\n * Index a record across all available strategies.\n *\n * @param record - Record to index\n */\n async index(record: IndexableRecord): Promise<void> {\n const strategies = await this.getAvailableStrategies()\n\n if (strategies.length === 0) {\n return\n }\n\n const results = await Promise.allSettled(\n strategies.map((strategy) => this.executeStrategyIndex(strategy, record)),\n )\n\n this.throwOnStrategyFailures('index', strategies, results, {\n entityId: record.entityId,\n recordId: record.recordId,\n })\n }\n\n /**\n * Delete a record from all strategies.\n *\n * @param entityId - Entity type identifier\n * @param recordId - Record primary key\n * @param tenantId - Tenant for isolation\n */\n async delete(entityId: string, recordId: string, tenantId: string): Promise<void> {\n const strategies = await this.getAvailableStrategies()\n\n const results = await Promise.allSettled(\n strategies.map((strategy) => this.executeStrategyDelete(strategy, entityId, recordId, tenantId)),\n )\n\n this.throwOnStrategyFailures('delete', strategies, results, { entityId, recordId })\n }\n\n /**\n * Bulk index multiple records.\n *\n * @param records - Records to index\n */\n async bulkIndex(records: IndexableRecord[]): Promise<void> {\n if (records.length === 0) return\n\n const strategies = await this.getAvailableStrategies()\n\n const results = await Promise.allSettled(\n strategies.map((strategy) => {\n if (strategy.bulkIndex) {\n return strategy.bulkIndex(records)\n }\n // Fallback to individual indexing\n return Promise.all(records.map((record) => this.executeStrategyIndex(strategy, record)))\n }),\n )\n\n // Collect failures and throw if any occurred\n const failures: Array<{ strategyId: string; error: string }> = []\n for (let i = 0; i < results.length; i++) {\n const result = results[i]\n if (result.status === 'rejected') {\n const strategy = strategies[i]\n const errorMessage = result.reason instanceof Error ? result.reason.message : result.reason\n failures.push({\n strategyId: strategy?.id || 'unknown',\n error: errorMessage,\n })\n searchError('SearchService', 'Strategy bulkIndex failed', {\n strategyId: strategy?.id,\n recordCount: records.length,\n error: errorMessage,\n })\n }\n }\n\n if (failures.length > 0) {\n throw new Error(\n `Bulk indexing failed for ${failures.length} strategy(ies): ${failures\n .map((f) => `${f.strategyId} (${f.error})`)\n .join(', ')}`\n )\n }\n }\n\n /**\n * Purge all records for an entity type.\n *\n * @param entityId - Entity type to purge\n * @param tenantId - Tenant for isolation\n */\n async purge(entityId: string, tenantId: string, organizationId?: string | null): Promise<void> {\n const strategies = await this.getAvailableStrategies()\n\n const results = await Promise.allSettled(\n strategies.map((strategy) => {\n if (strategy.purge) {\n return strategy.purge(entityId, tenantId, organizationId)\n }\n return Promise.resolve()\n }),\n )\n\n this.throwOnStrategyFailures('purge', strategies, results, { entityId })\n }\n\n /**\n * Inspect the settled results of a per-strategy write operation, log every\n * rejection, and re-throw an aggregated error when any strategy failed.\n *\n * Write operations (index/delete/purge) must surface failures to the caller\n * so the queue worker re-throws and the job is retried. Swallowing rejections\n * here causes silent, permanent index gaps on transient failures such as\n * Postgres connection-pool exhaustion (issue #3103). Successful strategies\n * still commit their work; only the aggregated failure propagates.\n */\n private throwOnStrategyFailures(\n operation: 'index' | 'delete' | 'purge',\n strategies: SearchStrategy[],\n results: PromiseSettledResult<unknown>[],\n context: { entityId: string; recordId?: string },\n ): void {\n const failures: Array<{ strategyId: string; reason: unknown }> = []\n\n for (let i = 0; i < results.length; i++) {\n const result = results[i]\n if (result.status === 'rejected') {\n const strategy = strategies[i]\n failures.push({ strategyId: strategy?.id || 'unknown', reason: result.reason })\n searchError('SearchService', `Strategy ${operation} failed`, {\n strategyId: strategy?.id,\n entityId: context.entityId,\n recordId: context.recordId,\n error: result.reason instanceof Error ? result.reason.message : result.reason,\n })\n }\n }\n\n if (failures.length === 0) return\n\n const summary = `Search ${operation} failed for ${failures.length} strategy(ies): ${failures\n .map((failure) => {\n const message = failure.reason instanceof Error ? failure.reason.message : String(failure.reason)\n return `${failure.strategyId} (${message})`\n })\n .join(', ')}`\n\n throw new AggregateError(\n failures.map((failure) => failure.reason),\n summary,\n )\n }\n\n /**\n * Register a new strategy at runtime.\n *\n * @param strategy - Strategy to register\n */\n registerStrategy(strategy: SearchStrategy): void {\n this.strategies.set(strategy.id, strategy)\n this.availabilityCache.delete(strategy.id)\n this.availabilityInflight.delete(strategy.id)\n }\n\n /**\n * Unregister a strategy.\n *\n * @param strategyId - Strategy ID to remove\n */\n unregisterStrategy(strategyId: SearchStrategyId): void {\n this.strategies.delete(strategyId)\n this.availabilityCache.delete(strategyId)\n this.availabilityInflight.delete(strategyId)\n }\n\n /**\n * Invalidate the strategy availability cache.\n * Call after manual reconnects or env changes when callers must observe the\n * current backend state immediately rather than waiting for TTL expiry.\n */\n invalidateAvailabilityCache(strategyId?: SearchStrategyId): void {\n if (strategyId) {\n this.availabilityCache.delete(strategyId)\n this.availabilityInflight.delete(strategyId)\n return\n }\n this.availabilityCache.clear()\n this.availabilityInflight.clear()\n }\n\n /**\n * Get all registered strategy IDs.\n */\n getRegisteredStrategies(): SearchStrategyId[] {\n return Array.from(this.strategies.keys())\n }\n\n /**\n * Get a specific strategy by ID.\n *\n * @param strategyId - Strategy ID to retrieve\n * @returns The strategy if registered, undefined otherwise\n */\n getStrategy(strategyId: SearchStrategyId): SearchStrategy | undefined {\n return this.strategies.get(strategyId)\n }\n\n /**\n * Get the default strategies list.\n */\n getDefaultStrategies(): SearchStrategyId[] {\n return [...this.defaultStrategies]\n }\n\n /**\n * Check if a specific strategy is available.\n *\n * @param strategyId - Strategy ID to check\n */\n async isStrategyAvailable(strategyId: SearchStrategyId): Promise<boolean> {\n const strategy = this.strategies.get(strategyId)\n if (!strategy) return false\n return this.checkStrategyAvailability(strategy)\n }\n\n /**\n * Resolve a strategy's availability via the short-lived TTL cache.\n * Coalesces concurrent callers onto a single in-flight probe to avoid\n * thundering-herd on remote backends.\n */\n private async checkStrategyAvailability(strategy: SearchStrategy): Promise<boolean> {\n const now = Date.now()\n const cached = this.availabilityCache.get(strategy.id)\n if (cached && cached.expiresAt > now) return cached.value\n\n const inflight = this.availabilityInflight.get(strategy.id)\n if (inflight) return inflight\n\n const probe = (async () => {\n try {\n const value = await strategy.isAvailable()\n this.availabilityCache.set(strategy.id, {\n value,\n expiresAt: Date.now() + this.availabilityCacheTtlMs,\n })\n return value\n } catch {\n this.availabilityCache.set(strategy.id, {\n value: false,\n expiresAt: Date.now() + this.availabilityCacheTtlMs,\n })\n return false\n } finally {\n this.availabilityInflight.delete(strategy.id)\n }\n })()\n this.availabilityInflight.set(strategy.id, probe)\n return probe\n }\n\n /**\n * Get available strategies from the requested list.\n * Filters out strategies that are not registered or not available.\n * Probes run in parallel and reuse a short-lived per-strategy availability\n * cache, so hot paths pay the max latency of the slowest probe (or zero\n * when cached) instead of the sum of all probes.\n */\n private async getAvailableStrategies(ids?: SearchStrategyId[]): Promise<SearchStrategy[]> {\n const targetIds = ids ?? Array.from(this.strategies.keys())\n const candidates: SearchStrategy[] = []\n for (const id of targetIds) {\n const strategy = this.strategies.get(id)\n if (strategy) candidates.push(strategy)\n }\n\n const probes = await Promise.allSettled(\n candidates.map((strategy) => this.checkStrategyAvailability(strategy)),\n )\n\n const available: SearchStrategy[] = []\n for (let i = 0; i < probes.length; i++) {\n const probe = probes[i]\n if (probe.status === 'fulfilled' && probe.value) {\n available.push(candidates[i])\n }\n }\n\n return available.sort((a, b) => b.priority - a.priority)\n }\n\n /**\n * Execute search on a single strategy with error handling.\n */\n private async executeStrategySearch(\n strategy: SearchStrategy,\n query: string,\n options: SearchOptions,\n ): Promise<SearchResult[]> {\n await strategy.ensureReady()\n return strategy.search(query, options)\n }\n\n /**\n * Execute index on a single strategy with error handling.\n */\n private async executeStrategyIndex(\n strategy: SearchStrategy,\n record: IndexableRecord,\n ): Promise<void> {\n await strategy.ensureReady()\n return strategy.index(record)\n }\n\n /**\n * Execute delete on a single strategy with error handling.\n */\n private async executeStrategyDelete(\n strategy: SearchStrategy,\n entityId: string,\n recordId: string,\n tenantId: string,\n ): Promise<void> {\n await strategy.ensureReady()\n return strategy.delete(entityId, recordId, tenantId)\n }\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["import type {\n SearchStrategy,\n SearchStrategyId,\n SearchOptions,\n SearchResult,\n SearchServiceOptions,\n ResultMergeConfig,\n IndexableRecord,\n PresenterEnricherFn,\n} from './types'\nimport { mergeAndRankResults } from './lib/merger'\nimport { searchError } from './lib/debug'\n\n/**\n * Default merge configuration.\n */\nconst DEFAULT_MERGE_CONFIG: ResultMergeConfig = {\n duplicateHandling: 'highest_score',\n}\n\n/**\n * Cache TTL for strategy availability checks.\n * Short window so connectivity changes (Meilisearch up/down) propagate quickly,\n * long enough to skip per-request RTT to remote backends on hot paths.\n */\nconst STRATEGY_AVAILABILITY_CACHE_TTL_MS = 2_000\n\nfunction normalizeOrganizationFilter(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\n const values = Array.from(new Set(\n options.organizationIds\n .map((value) => (typeof value === 'string' ? value.trim() : ''))\n .filter((value) => value.length > 0),\n ))\n return values\n}\n\nfunction filterResultsByOrganizationScope(results: SearchResult[], options: SearchOptions): SearchResult[] {\n const organizationIds = normalizeOrganizationFilter(options)\n if (!organizationIds) return results\n if (organizationIds.length === 0) return []\n\n const allowed = new Set(organizationIds)\n return results.filter((result) => {\n const organizationId = typeof result.organizationId === 'string' ? result.organizationId.trim() : ''\n return organizationId.length > 0 && allowed.has(organizationId)\n })\n}\n\n/**\n * SearchService orchestrates multiple search strategies, executing searches in parallel\n * and merging results using the RRF algorithm.\n *\n * Features:\n * - Parallel strategy execution for optimal performance\n * - Graceful degradation when strategies fail\n * - Result merging with configurable weights\n * - Strategy availability checking\n *\n * @example\n * ```typescript\n * const service = new SearchService({\n * strategies: [tokenStrategy, vectorStrategy, fulltextStrategy],\n * defaultStrategies: ['fulltext', 'vector', 'tokens'],\n * mergeConfig: {\n * duplicateHandling: 'highest_score',\n * strategyWeights: { fulltext: 1.2, vector: 1.0, tokens: 0.8 },\n * },\n * })\n *\n * const results = await service.search('john doe', { tenantId: 'tenant-123' })\n * ```\n */\nexport class SearchService {\n private readonly strategies: Map<SearchStrategyId, SearchStrategy>\n private readonly defaultStrategies: SearchStrategyId[]\n private readonly fallbackStrategy: SearchStrategyId | undefined\n private readonly mergeConfig: ResultMergeConfig\n private readonly presenterEnricher?: PresenterEnricherFn\n private readonly availabilityCache = new Map<SearchStrategyId, { value: boolean; expiresAt: number }>()\n private readonly availabilityInflight = new Map<SearchStrategyId, Promise<boolean>>()\n private readonly availabilityCacheTtlMs: number\n\n constructor(options: SearchServiceOptions = {}) {\n this.strategies = new Map()\n for (const strategy of options.strategies ?? []) {\n this.strategies.set(strategy.id, strategy)\n }\n this.defaultStrategies = options.defaultStrategies ?? ['tokens']\n this.fallbackStrategy = options.fallbackStrategy\n this.mergeConfig = options.mergeConfig ?? DEFAULT_MERGE_CONFIG\n this.presenterEnricher = options.presenterEnricher\n this.availabilityCacheTtlMs = options.availabilityCacheTtlMs ?? STRATEGY_AVAILABILITY_CACHE_TTL_MS\n }\n\n /**\n * Get all registered strategies.\n */\n getStrategies(): SearchStrategy[] {\n return Array.from(this.strategies.values())\n }\n\n /**\n * Execute a search query across configured strategies.\n *\n * @param query - Search query string\n * @param options - Search options with tenant, filters, etc.\n * @returns Merged and ranked search results\n */\n async search(query: string, options: SearchOptions): Promise<SearchResult[]> {\n const organizationIds = normalizeOrganizationFilter(options)\n if (organizationIds && organizationIds.length === 0) {\n return []\n }\n\n const strategyIds = options.strategies ?? this.defaultStrategies\n const activeStrategies = await this.getAvailableStrategies(strategyIds)\n\n if (activeStrategies.length === 0) {\n // Try fallback strategy if defined\n if (this.fallbackStrategy) {\n const fallback = await this.getAvailableStrategies([this.fallbackStrategy])\n if (fallback.length > 0) {\n activeStrategies.push(...fallback)\n }\n }\n }\n\n if (activeStrategies.length === 0) {\n return []\n }\n\n // Execute searches in parallel with graceful degradation\n const results = await Promise.allSettled(\n activeStrategies.map((strategy) => this.executeStrategySearch(strategy, query, options)),\n )\n\n // Collect successful results, log failures\n const allResults: SearchResult[] = []\n for (let i = 0; i < results.length; i++) {\n const result = results[i]\n if (result.status === 'fulfilled') {\n allResults.push(...result.value)\n } else {\n const strategy = activeStrategies[i]\n searchError('SearchService', 'Strategy search failed', {\n strategyId: strategy?.id,\n error: result.reason instanceof Error ? result.reason.message : result.reason,\n })\n }\n }\n\n // Merge and rank results\n const merged = mergeAndRankResults(allResults, this.mergeConfig)\n const scoped = filterResultsByOrganizationScope(merged, options)\n\n // Enrich results missing presenter or navigation metadata\n return this.enrichResultsWithPresenter(scoped, options.tenantId, options.organizationId)\n }\n\n /**\n * Recompute configured presenters at request time and fill missing presenter\n * or navigation data for unconfigured results.\n */\n private async enrichResultsWithPresenter(\n results: SearchResult[],\n tenantId: string,\n organizationId?: string | null,\n ): Promise<SearchResult[]> {\n // If no enricher configured, return as-is\n if (!this.presenterEnricher) return results\n\n try {\n return await this.presenterEnricher(results, tenantId, organizationId)\n } catch {\n // Enrichment failed, return results as-is\n return results\n }\n }\n\n /**\n * Index a record across all available strategies.\n *\n * @param record - Record to index\n */\n async index(record: IndexableRecord): Promise<void> {\n const strategies = await this.getAvailableStrategies()\n\n if (strategies.length === 0) {\n return\n }\n\n const results = await Promise.allSettled(\n strategies.map((strategy) => this.executeStrategyIndex(strategy, record)),\n )\n\n this.throwOnStrategyFailures('index', strategies, results, {\n entityId: record.entityId,\n recordId: record.recordId,\n })\n }\n\n /**\n * Delete a record from all strategies.\n *\n * @param entityId - Entity type identifier\n * @param recordId - Record primary key\n * @param tenantId - Tenant for isolation\n */\n async delete(entityId: string, recordId: string, tenantId: string): Promise<void> {\n const strategies = await this.getAvailableStrategies()\n\n const results = await Promise.allSettled(\n strategies.map((strategy) => this.executeStrategyDelete(strategy, entityId, recordId, tenantId)),\n )\n\n this.throwOnStrategyFailures('delete', strategies, results, { entityId, recordId })\n }\n\n /**\n * Bulk index multiple records.\n *\n * @param records - Records to index\n */\n async bulkIndex(records: IndexableRecord[]): Promise<void> {\n if (records.length === 0) return\n\n const strategies = await this.getAvailableStrategies()\n\n const results = await Promise.allSettled(\n strategies.map((strategy) => {\n if (strategy.bulkIndex) {\n return strategy.bulkIndex(records)\n }\n // Fallback to individual indexing\n return Promise.all(records.map((record) => this.executeStrategyIndex(strategy, record)))\n }),\n )\n\n // Collect failures and throw if any occurred\n const failures: Array<{ strategyId: string; error: string }> = []\n for (let i = 0; i < results.length; i++) {\n const result = results[i]\n if (result.status === 'rejected') {\n const strategy = strategies[i]\n const errorMessage = result.reason instanceof Error ? result.reason.message : result.reason\n failures.push({\n strategyId: strategy?.id || 'unknown',\n error: errorMessage,\n })\n searchError('SearchService', 'Strategy bulkIndex failed', {\n strategyId: strategy?.id,\n recordCount: records.length,\n error: errorMessage,\n })\n }\n }\n\n if (failures.length > 0) {\n throw new Error(\n `Bulk indexing failed for ${failures.length} strategy(ies): ${failures\n .map((f) => `${f.strategyId} (${f.error})`)\n .join(', ')}`\n )\n }\n }\n\n /**\n * Purge all records for an entity type.\n *\n * @param entityId - Entity type to purge\n * @param tenantId - Tenant for isolation\n */\n async purge(entityId: string, tenantId: string, organizationId?: string | null): Promise<void> {\n const strategies = await this.getAvailableStrategies()\n\n const results = await Promise.allSettled(\n strategies.map((strategy) => {\n if (strategy.purge) {\n return strategy.purge(entityId, tenantId, organizationId)\n }\n return Promise.resolve()\n }),\n )\n\n this.throwOnStrategyFailures('purge', strategies, results, { entityId })\n }\n\n /**\n * Inspect the settled results of a per-strategy write operation, log every\n * rejection, and re-throw an aggregated error when any strategy failed.\n *\n * Write operations (index/delete/purge) must surface failures to the caller\n * so the queue worker re-throws and the job is retried. Swallowing rejections\n * here causes silent, permanent index gaps on transient failures such as\n * Postgres connection-pool exhaustion (issue #3103). Successful strategies\n * still commit their work; only the aggregated failure propagates.\n */\n private throwOnStrategyFailures(\n operation: 'index' | 'delete' | 'purge',\n strategies: SearchStrategy[],\n results: PromiseSettledResult<unknown>[],\n context: { entityId: string; recordId?: string },\n ): void {\n const failures: Array<{ strategyId: string; reason: unknown }> = []\n\n for (let i = 0; i < results.length; i++) {\n const result = results[i]\n if (result.status === 'rejected') {\n const strategy = strategies[i]\n failures.push({ strategyId: strategy?.id || 'unknown', reason: result.reason })\n searchError('SearchService', `Strategy ${operation} failed`, {\n strategyId: strategy?.id,\n entityId: context.entityId,\n recordId: context.recordId,\n error: result.reason instanceof Error ? result.reason.message : result.reason,\n })\n }\n }\n\n if (failures.length === 0) return\n\n const summary = `Search ${operation} failed for ${failures.length} strategy(ies): ${failures\n .map((failure) => {\n const message = failure.reason instanceof Error ? failure.reason.message : String(failure.reason)\n return `${failure.strategyId} (${message})`\n })\n .join(', ')}`\n\n throw new AggregateError(\n failures.map((failure) => failure.reason),\n summary,\n )\n }\n\n /**\n * Register a new strategy at runtime.\n *\n * @param strategy - Strategy to register\n */\n registerStrategy(strategy: SearchStrategy): void {\n this.strategies.set(strategy.id, strategy)\n this.availabilityCache.delete(strategy.id)\n this.availabilityInflight.delete(strategy.id)\n }\n\n /**\n * Unregister a strategy.\n *\n * @param strategyId - Strategy ID to remove\n */\n unregisterStrategy(strategyId: SearchStrategyId): void {\n this.strategies.delete(strategyId)\n this.availabilityCache.delete(strategyId)\n this.availabilityInflight.delete(strategyId)\n }\n\n /**\n * Invalidate the strategy availability cache.\n * Call after manual reconnects or env changes when callers must observe the\n * current backend state immediately rather than waiting for TTL expiry.\n */\n invalidateAvailabilityCache(strategyId?: SearchStrategyId): void {\n if (strategyId) {\n this.availabilityCache.delete(strategyId)\n this.availabilityInflight.delete(strategyId)\n return\n }\n this.availabilityCache.clear()\n this.availabilityInflight.clear()\n }\n\n /**\n * Get all registered strategy IDs.\n */\n getRegisteredStrategies(): SearchStrategyId[] {\n return Array.from(this.strategies.keys())\n }\n\n /**\n * Get a specific strategy by ID.\n *\n * @param strategyId - Strategy ID to retrieve\n * @returns The strategy if registered, undefined otherwise\n */\n getStrategy(strategyId: SearchStrategyId): SearchStrategy | undefined {\n return this.strategies.get(strategyId)\n }\n\n /**\n * Get the default strategies list.\n */\n getDefaultStrategies(): SearchStrategyId[] {\n return [...this.defaultStrategies]\n }\n\n /**\n * Check if a specific strategy is available.\n *\n * @param strategyId - Strategy ID to check\n */\n async isStrategyAvailable(strategyId: SearchStrategyId): Promise<boolean> {\n const strategy = this.strategies.get(strategyId)\n if (!strategy) return false\n return this.checkStrategyAvailability(strategy)\n }\n\n /**\n * Resolve a strategy's availability via the short-lived TTL cache.\n * Coalesces concurrent callers onto a single in-flight probe to avoid\n * thundering-herd on remote backends.\n */\n private async checkStrategyAvailability(strategy: SearchStrategy): Promise<boolean> {\n const now = Date.now()\n const cached = this.availabilityCache.get(strategy.id)\n if (cached && cached.expiresAt > now) return cached.value\n\n const inflight = this.availabilityInflight.get(strategy.id)\n if (inflight) return inflight\n\n const probe = (async () => {\n try {\n const value = await strategy.isAvailable()\n this.availabilityCache.set(strategy.id, {\n value,\n expiresAt: Date.now() + this.availabilityCacheTtlMs,\n })\n return value\n } catch {\n this.availabilityCache.set(strategy.id, {\n value: false,\n expiresAt: Date.now() + this.availabilityCacheTtlMs,\n })\n return false\n } finally {\n this.availabilityInflight.delete(strategy.id)\n }\n })()\n this.availabilityInflight.set(strategy.id, probe)\n return probe\n }\n\n /**\n * Get available strategies from the requested list.\n * Filters out strategies that are not registered or not available.\n * Probes run in parallel and reuse a short-lived per-strategy availability\n * cache, so hot paths pay the max latency of the slowest probe (or zero\n * when cached) instead of the sum of all probes.\n */\n private async getAvailableStrategies(ids?: SearchStrategyId[]): Promise<SearchStrategy[]> {\n const targetIds = ids ?? Array.from(this.strategies.keys())\n const candidates: SearchStrategy[] = []\n for (const id of targetIds) {\n const strategy = this.strategies.get(id)\n if (strategy) candidates.push(strategy)\n }\n\n const probes = await Promise.allSettled(\n candidates.map((strategy) => this.checkStrategyAvailability(strategy)),\n )\n\n const available: SearchStrategy[] = []\n for (let i = 0; i < probes.length; i++) {\n const probe = probes[i]\n if (probe.status === 'fulfilled' && probe.value) {\n available.push(candidates[i])\n }\n }\n\n return available.sort((a, b) => b.priority - a.priority)\n }\n\n /**\n * Execute search on a single strategy with error handling.\n */\n private async executeStrategySearch(\n strategy: SearchStrategy,\n query: string,\n options: SearchOptions,\n ): Promise<SearchResult[]> {\n await strategy.ensureReady()\n return strategy.search(query, options)\n }\n\n /**\n * Execute index on a single strategy with error handling.\n */\n private async executeStrategyIndex(\n strategy: SearchStrategy,\n record: IndexableRecord,\n ): Promise<void> {\n await strategy.ensureReady()\n return strategy.index(record)\n }\n\n /**\n * Execute delete on a single strategy with error handling.\n */\n private async executeStrategyDelete(\n strategy: SearchStrategy,\n entityId: string,\n recordId: string,\n tenantId: string,\n ): Promise<void> {\n await strategy.ensureReady()\n return strategy.delete(entityId, recordId, tenantId)\n }\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -20,7 +20,9 @@ class VectorSearchStrategy {
20
20
  this.defaultLimit = config?.defaultLimit ?? 20;
21
21
  }
22
22
  async isAvailable() {
23
- return this.embeddingService.available;
23
+ if (!this.embeddingService.available) return false;
24
+ if (!this.vectorDriver.isHealthy) return true;
25
+ return this.vectorDriver.isHealthy();
24
26
  }
25
27
  async ensureReady() {
26
28
  if (this.ready) return;
@@ -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 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 return this.embeddingService.available\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\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 } = {\n tenantId: options.tenantId,\n entityIds: options.entityTypes as EntityId[],\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": "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",
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 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\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 } = {\n tenantId: options.tenantId,\n entityIds: options.entityTypes as EntityId[],\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": "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",
6
6
  "names": []
7
7
  }
@@ -1,9 +1,12 @@
1
1
  import { Pool } from "pg";
2
- import { searchDebugWarn } from "../../../lib/debug.js";
2
+ import { searchDebugWarn, searchWarn } from "../../../lib/debug.js";
3
3
  const DEFAULT_TABLE = "vector_search";
4
4
  const DEFAULT_MIGRATIONS_TABLE = "vector_search_migrations";
5
5
  const DEFAULT_DIMENSION = 1536;
6
6
  const DRIVER_ID = "pgvector";
7
+ const EXTENSION_MISSING_CODES = /* @__PURE__ */ new Set(["58P01", "0A000", "42704"]);
8
+ const EXTENSION_UNAVAILABLE_REASON = 'extension "vector" is not available on this PostgreSQL server';
9
+ const UNAVAILABLE_RECHECK_MS = 6e4;
7
10
  function assertIdentifier(name, defaultName) {
8
11
  const candidate = name ?? defaultName;
9
12
  if (!candidate || !/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(candidate)) return defaultName;
@@ -57,7 +60,47 @@ function createPgVectorDriver(opts = {}) {
57
60
  return new Pool({ connectionString: conn });
58
61
  })();
59
62
  let ready = null;
63
+ let status = null;
64
+ let reportedReason = null;
65
+ const cacheStatus = (value) => {
66
+ status = { value, at: Date.now() };
67
+ if (value.available) {
68
+ reportedReason = null;
69
+ } else if (value.reason && value.reason !== reportedReason) {
70
+ reportedReason = value.reason;
71
+ searchWarn("vector.pgvector", `vector store unavailable \u2014 skipping vector indexing and search (${value.reason})`);
72
+ }
73
+ return value;
74
+ };
75
+ const markExtensionMissing = () => {
76
+ cacheStatus({ available: false, reason: EXTENSION_UNAVAILABLE_REASON });
77
+ };
78
+ const readCachedStatus = () => {
79
+ if (!status) return null;
80
+ return Date.now() - status.at < UNAVAILABLE_RECHECK_MS ? status.value : null;
81
+ };
82
+ const getStatus = async () => {
83
+ const cached = readCachedStatus();
84
+ if (cached) return cached;
85
+ try {
86
+ const res = await pool.query(
87
+ `SELECT
88
+ EXISTS (SELECT 1 FROM pg_extension WHERE extname = 'vector') AS installed,
89
+ EXISTS (SELECT 1 FROM pg_available_extensions WHERE name = 'vector') AS installable`
90
+ );
91
+ const row = res.rows[0];
92
+ if (row?.installed || row?.installable) return cacheStatus({ available: true });
93
+ return cacheStatus({ available: false, reason: EXTENSION_UNAVAILABLE_REASON });
94
+ } catch {
95
+ return { available: true };
96
+ }
97
+ };
98
+ const isHealthy = async () => (await getStatus()).available;
60
99
  const ensureReady = async () => {
100
+ const cached = readCachedStatus();
101
+ if (cached && !cached.available) {
102
+ throw new Error(`[vector.pgvector] ${cached.reason ?? "vector store unavailable"}`);
103
+ }
61
104
  if (!ready) {
62
105
  ready = withClient(pool, async (client) => {
63
106
  const ensureExtension = async (extension) => {
@@ -70,6 +113,9 @@ function createPgVectorDriver(opts = {}) {
70
113
  searchDebugWarn("vector.pgvector", `skipping ${extension} extension creation; requires superuser${details}`);
71
114
  return;
72
115
  }
116
+ if (extension === "vector" && pgError?.code && EXTENSION_MISSING_CODES.has(pgError.code)) {
117
+ markExtensionMissing();
118
+ }
73
119
  throw error;
74
120
  }
75
121
  };
@@ -161,8 +207,14 @@ function createPgVectorDriver(opts = {}) {
161
207
  `INSERT INTO ${migrationsIdent} (id, applied_at) VALUES ($1, now()) ON CONFLICT (id) DO NOTHING`,
162
208
  ["0001_init"]
163
209
  );
210
+ }).then(() => {
211
+ cacheStatus({ available: true });
164
212
  }).catch((err) => {
165
213
  ready = null;
214
+ const pgError = err;
215
+ if (pgError?.code === "42704" && /\bvector\b/.test(pgError.message ?? "")) {
216
+ markExtensionMissing();
217
+ }
166
218
  throw err;
167
219
  });
168
220
  }
@@ -496,6 +548,8 @@ function createPgVectorDriver(opts = {}) {
496
548
  return {
497
549
  id: "pgvector",
498
550
  ensureReady,
551
+ isHealthy,
552
+ getStatus,
499
553
  upsert,
500
554
  delete: remove,
501
555
  getChecksum,
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../src/vector/drivers/pgvector/index.ts"],
4
- "sourcesContent": ["import { Pool } from 'pg'\nimport { searchDebugWarn } from '../../../lib/debug'\n\ntype PgPoolQueryResult<T> = { rows: T[]; rowCount?: number }\ntype PgPoolClient = {\n query<T = any>(text: string, params?: any[]): Promise<PgPoolQueryResult<T>>\n release(): void\n}\nexport type PgPool = {\n connect(): Promise<PgPoolClient>\n query<T = any>(text: string, params?: any[]): Promise<PgPoolQueryResult<T>>\n end(): Promise<void>\n}\nimport type {\n VectorDriver,\n VectorDriverDocument,\n VectorDriverQuery,\n VectorDriverQueryResult,\n VectorDriverListParams,\n VectorDriverCountParams,\n VectorIndexEntry,\n VectorDriverRemoveOrphansParams,\n VectorResultPresenter,\n VectorLinkDescriptor,\n} from '../../types'\n\ntype PgVectorDriverOptions = {\n pool?: PgPool\n connectionString?: string\n tableName?: string\n migrationsTable?: string\n dimension?: number\n distanceMetric?: 'cosine' | 'euclidean' | 'inner'\n}\n\nconst DEFAULT_TABLE = 'vector_search'\nconst DEFAULT_MIGRATIONS_TABLE = 'vector_search_migrations'\nconst DEFAULT_DIMENSION = 1536\nconst DRIVER_ID = 'pgvector' as const\n\nfunction assertIdentifier(name: string, defaultName: string): string {\n const candidate = name ?? defaultName\n if (!candidate || !/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(candidate)) return defaultName\n return candidate\n}\n\nfunction quoteIdent(name: string): string {\n return `\"${name}\"`\n}\n\nfunction toVectorLiteral(values: number[]): string {\n const formatted = values.map((n) => {\n if (!Number.isFinite(n)) return '0'\n const rounded = Math.fround(n)\n return Number.isInteger(rounded) ? `${rounded}.0` : `${rounded}`\n })\n return `[${formatted.join(',')}]`\n}\n\nfunction parseJsonColumn<T>(value: unknown): T | null {\n if (value === null || value === undefined) return null\n if (typeof value === 'string') {\n try {\n return JSON.parse(value) as T\n } catch {\n // When `jsonb` stores a JSON string, node-postgres parses it into a plain JS string.\n // In that case, there is nothing to JSON.parse \u2014 return the raw string value.\n return value as unknown as T\n }\n }\n if (typeof value === 'object') {\n return value as T\n }\n return null\n}\n\nasync function withClient<T>(pool: PgPool, fn: (client: PgPoolClient) => Promise<T>): Promise<T> {\n const client = await pool.connect()\n try {\n return await fn(client)\n } finally {\n client.release()\n }\n}\n\nexport function createPgVectorDriver(opts: PgVectorDriverOptions = {}): VectorDriver {\n const tableName = assertIdentifier(opts.tableName ?? DEFAULT_TABLE, DEFAULT_TABLE)\n const migrationsTable = assertIdentifier(opts.migrationsTable ?? DEFAULT_MIGRATIONS_TABLE, DEFAULT_MIGRATIONS_TABLE)\n let dimension = opts.dimension ?? DEFAULT_DIMENSION\n const distanceMetric = opts.distanceMetric ?? 'cosine'\n const tableIdent = quoteIdent(tableName)\n const migrationsIdent = quoteIdent(migrationsTable)\n\n const pool: PgPool =\n opts.pool ??\n (() => {\n const conn = opts.connectionString ?? process.env.DATABASE_URL\n if (!conn) {\n throw new Error('[vector.pgvector] DATABASE_URL is not configured')\n }\n return new Pool({ connectionString: conn }) as unknown as PgPool\n })()\n\n let ready: Promise<void> | null = null\n\n const ensureReady = async () => {\n if (!ready) {\n ready = withClient(pool, async (client) => {\n const ensureExtension = async (extension: 'pgcrypto' | 'vector') => {\n try {\n await client.query(`CREATE EXTENSION IF NOT EXISTS ${extension}`)\n } catch (error) {\n const pgError = error as { code?: string; message?: string }\n if (pgError?.code === '42501') {\n const details = pgError.message ? ` (${pgError.message})` : ''\n searchDebugWarn('vector.pgvector', `skipping ${extension} extension creation; requires superuser${details}`)\n return\n }\n throw error\n }\n }\n\n await ensureExtension('pgcrypto')\n await ensureExtension('vector')\n\n await client.query(\n `CREATE TABLE IF NOT EXISTS ${migrationsIdent} (\n id text primary key,\n applied_at timestamptz not null default now()\n )`,\n )\n\n await client.query(\n `CREATE TABLE IF NOT EXISTS ${tableIdent} (\n id uuid primary key default gen_random_uuid(),\n driver_id text not null,\n entity_id text not null,\n record_id text not null,\n tenant_id uuid not null,\n organization_id uuid null,\n checksum text not null,\n embedding vector(${dimension}) not null,\n url text null,\n presenter jsonb null,\n links jsonb null,\n payload jsonb null,\n result_title text null,\n result_subtitle text null,\n result_icon text null,\n result_badge text null,\n result_snapshot text null,\n primary_link_href text null,\n primary_link_label text null,\n created_at timestamptz not null default now(),\n updated_at timestamptz not null default now()\n )`,\n )\n\n await client.query(\n `CREATE UNIQUE INDEX IF NOT EXISTS ${tableName}_uniq ON ${tableIdent} (driver_id, entity_id, record_id, tenant_id)`,\n )\n await client.query(\n `CREATE INDEX IF NOT EXISTS ${tableName}_lookup ON ${tableIdent} (tenant_id, organization_id, entity_id)`,\n )\n // ivfflat index only supports up to 2000 dimensions\n // For higher dimensions, skip the index (uses sequential scan, slower but works)\n // Also check actual table dimension in case driver was initialized with different value\n let actualDimension = dimension\n try {\n const dimResult = await client.query<{ atttypmod: number }>(\n `SELECT a.atttypmod\n FROM pg_attribute a\n JOIN pg_class c ON a.attrelid = c.oid\n WHERE c.relname = $1\n AND a.attname = 'embedding'\n AND a.atttypmod > 0`,\n [tableName]\n )\n if (dimResult.rows.length > 0 && dimResult.rows[0].atttypmod > 0) {\n actualDimension = dimResult.rows[0].atttypmod\n }\n } catch {\n // Ignore errors reading dimension, use configured value\n }\n\n if (actualDimension <= 2000) {\n try {\n await client.query(\n `CREATE INDEX IF NOT EXISTS ${tableName}_embedding_idx ON ${tableIdent}\n USING ivfflat (embedding vector_${distanceMetric}_ops) WITH (lists = 100)`,\n )\n } catch (indexErr: unknown) {\n // Handle case where dimension exceeds ivfflat limit\n const errorMessage = indexErr instanceof Error ? indexErr.message : String(indexErr)\n if (errorMessage.includes('2000 dimensions')) {\n searchDebugWarn('pgvector', 'Skipping ivfflat index - dimension exceeds 2000 limit. Searches will use sequential scan.')\n } else {\n throw indexErr\n }\n }\n } else {\n searchDebugWarn('pgvector', `Skipping ivfflat index - dimension ${actualDimension} exceeds 2000 limit. Searches will use sequential scan.`)\n }\n\n const columnAlters = [\n `ALTER TABLE ${tableIdent} ADD COLUMN IF NOT EXISTS result_title text`,\n `ALTER TABLE ${tableIdent} ADD COLUMN IF NOT EXISTS result_subtitle text`,\n `ALTER TABLE ${tableIdent} ADD COLUMN IF NOT EXISTS result_icon text`,\n `ALTER TABLE ${tableIdent} ADD COLUMN IF NOT EXISTS result_badge text`,\n `ALTER TABLE ${tableIdent} ADD COLUMN IF NOT EXISTS result_snapshot text`,\n `ALTER TABLE ${tableIdent} ADD COLUMN IF NOT EXISTS primary_link_href text`,\n `ALTER TABLE ${tableIdent} ADD COLUMN IF NOT EXISTS primary_link_label text`,\n ]\n for (const statement of columnAlters) {\n await client.query(statement)\n }\n\n await client.query(\n `INSERT INTO ${migrationsIdent} (id, applied_at) VALUES ($1, now()) ON CONFLICT (id) DO NOTHING`,\n ['0001_init'],\n )\n }).catch((err) => {\n ready = null\n throw err\n })\n }\n return ready\n }\n\n const upsert = async (doc: VectorDriverDocument) => {\n await ensureReady()\n const vectorLiteral = toVectorLiteral(doc.embedding)\n await pool.query(\n `\n INSERT INTO ${tableIdent} (\n driver_id, entity_id, record_id, tenant_id, organization_id, checksum,\n embedding, url, presenter, links, payload,\n result_title, result_subtitle, result_icon, result_badge, result_snapshot,\n primary_link_href, primary_link_label,\n created_at, updated_at\n )\n VALUES (\n $1, $2, $3, $4::uuid, $5::uuid, $6, $7::vector, $8, $9::jsonb, $10::jsonb, $11::jsonb,\n $12, $13, $14, $15, $16, $17, $18,\n now(), now()\n )\n ON CONFLICT (driver_id, entity_id, record_id, tenant_id)\n DO UPDATE SET\n organization_id = EXCLUDED.organization_id,\n checksum = EXCLUDED.checksum,\n embedding = EXCLUDED.embedding,\n url = EXCLUDED.url,\n presenter = EXCLUDED.presenter,\n links = EXCLUDED.links,\n payload = EXCLUDED.payload,\n result_title = EXCLUDED.result_title,\n result_subtitle = EXCLUDED.result_subtitle,\n result_icon = EXCLUDED.result_icon,\n result_badge = EXCLUDED.result_badge,\n result_snapshot = EXCLUDED.result_snapshot,\n primary_link_href = EXCLUDED.primary_link_href,\n primary_link_label = EXCLUDED.primary_link_label,\n updated_at = now()\n `,\n [\n doc.driverId ?? DRIVER_ID,\n doc.entityId,\n doc.recordId,\n doc.tenantId,\n doc.organizationId ?? null,\n doc.checksum,\n vectorLiteral,\n doc.url ?? null,\n doc.presenter ? JSON.stringify(doc.presenter) : null,\n doc.links ? JSON.stringify(doc.links) : null,\n doc.payload ? JSON.stringify(doc.payload) : null,\n doc.resultTitle,\n doc.resultSubtitle ?? null,\n doc.resultIcon ?? null,\n doc.resultBadge ?? null,\n doc.resultSnapshot ?? null,\n doc.primaryLinkHref ?? null,\n doc.primaryLinkLabel ?? null,\n ],\n )\n }\n\n const remove = async (entityId: string, recordId: string, tenantId: string) => {\n await ensureReady()\n await pool.query(\n `DELETE FROM ${tableIdent} WHERE driver_id = $1 AND entity_id = $2 AND record_id = $3 AND tenant_id = $4::uuid`,\n [DRIVER_ID, entityId, recordId, tenantId],\n )\n }\n\n const getChecksum = async (entityId: string, recordId: string, tenantId: string): Promise<string | null> => {\n await ensureReady()\n const res = await pool.query<{ checksum: string }>(\n `SELECT checksum FROM ${tableIdent} WHERE driver_id = $1 AND entity_id = $2 AND record_id = $3 AND tenant_id = $4::uuid`,\n [DRIVER_ID, entityId, recordId, tenantId],\n )\n return res.rowCount ? res.rows[0].checksum : null\n }\n\n const purge = async (entityId: string, tenantId: string, organizationId?: string | null) => {\n await ensureReady()\n const normalizedOrganizationId =\n typeof organizationId === 'string' && organizationId.trim().length > 0 ? organizationId.trim() : null\n const conditions = ['driver_id = $1', 'entity_id = $2', 'tenant_id = $3::uuid']\n const values: any[] = [DRIVER_ID, entityId, tenantId]\n if (normalizedOrganizationId !== null) {\n conditions.push('organization_id = $4::uuid')\n values.push(normalizedOrganizationId)\n }\n await pool.query(\n `DELETE FROM ${tableIdent} WHERE ${conditions.join(' AND ')}`,\n values,\n )\n }\n\n const query = async (input: VectorDriverQuery): Promise<VectorDriverQueryResult[]> => {\n await ensureReady()\n const vectorLiteral = toVectorLiteral(input.vector)\n const filter = input.filter ?? { tenantId: '' }\n const normalizedOrganizationId =\n typeof filter.organizationId === 'string' && filter.organizationId.trim().length > 0\n ? filter.organizationId.trim()\n : null\n const normalizedOrganizationIds = normalizedOrganizationId\n ? [normalizedOrganizationId]\n : Array.isArray(filter.organizationIds)\n ? Array.from(new Set(\n filter.organizationIds\n .map((value) => (typeof value === 'string' ? value.trim() : ''))\n .filter((value) => value.length > 0),\n ))\n : null\n if (normalizedOrganizationIds && normalizedOrganizationIds.length === 0) {\n return []\n }\n const params: any[] = [\n vectorLiteral,\n DRIVER_ID,\n filter.tenantId,\n normalizedOrganizationIds,\n Array.isArray(filter.entityIds) && filter.entityIds.length ? filter.entityIds : null,\n input.limit ?? 20,\n ]\n const res = await pool.query<{\n entity_id: string\n record_id: string\n organization_id: string | null\n checksum: string\n url: string | null\n presenter: string | null\n links: string | null\n payload: string | null\n result_title: string | null\n result_subtitle: string | null\n result_icon: string | null\n result_badge: string | null\n result_snapshot: string | null\n primary_link_href: string | null\n primary_link_label: string | null\n distance: number\n }>(\n `\n SELECT\n entity_id,\n record_id,\n organization_id,\n checksum,\n url,\n presenter,\n links,\n payload,\n result_title,\n result_subtitle,\n result_icon,\n result_badge,\n result_snapshot,\n primary_link_href,\n primary_link_label,\n embedding <=> $1::vector AS distance\n FROM ${tableIdent}\n WHERE driver_id = $2\n AND tenant_id = $3::uuid\n AND ($4::uuid[] IS NULL OR organization_id = ANY($4::uuid[]))\n AND (\n $5::text[] IS NULL OR entity_id = ANY($5::text[])\n )\n ORDER BY embedding <=> $1::vector\n LIMIT $6\n `,\n params,\n )\n return res.rows.map<VectorDriverQueryResult>((row) => {\n const distance = typeof row.distance === 'number' ? row.distance : Number(row.distance || 1)\n const score = 1 - distance\n return {\n entityId: row.entity_id,\n recordId: row.record_id,\n organizationId: row.organization_id ?? null,\n checksum: row.checksum,\n url: row.url ?? null,\n presenter: parseJsonColumn<VectorResultPresenter>(row.presenter),\n links: parseJsonColumn<VectorLinkDescriptor[]>(row.links),\n payload: parseJsonColumn<Record<string, unknown>>(row.payload),\n resultTitle: row.result_title ?? '',\n resultSubtitle: row.result_subtitle ?? null,\n resultIcon: row.result_icon ?? null,\n resultBadge: row.result_badge ?? null,\n resultSnapshot: row.result_snapshot ?? null,\n primaryLinkHref: row.primary_link_href ?? null,\n primaryLinkLabel: row.primary_link_label ?? null,\n score,\n }\n })\n }\n\n const list = async (params: VectorDriverListParams): Promise<VectorIndexEntry[]> => {\n await ensureReady()\n const limit = Math.max(1, Math.min(params.limit ?? 50, 200))\n const offset = Math.max(0, params.offset ?? 0)\n const orderColumn = params.orderBy === 'created' ? 'created_at' : 'updated_at'\n const conditions: string[] = [\n 'driver_id = $1',\n 'tenant_id = $2::uuid',\n ]\n const values: any[] = [DRIVER_ID, params.tenantId]\n let nextParam = 3\n\n const normalizedOrganizationId =\n typeof params.organizationId === 'string' && params.organizationId.trim().length > 0\n ? params.organizationId.trim()\n : null\n if (normalizedOrganizationId !== null) {\n conditions.push(`organization_id = $${nextParam}::uuid`)\n values.push(normalizedOrganizationId)\n nextParam += 1\n }\n\n if (params.entityId) {\n conditions.push(`entity_id = $${nextParam}::text`)\n values.push(params.entityId)\n nextParam += 1\n }\n\n const limitParam = nextParam\n const offsetParam = nextParam + 1\n values.push(limit, offset)\n\n const sql = `\n SELECT\n entity_id,\n record_id,\n tenant_id,\n organization_id,\n checksum,\n url,\n presenter,\n links,\n payload,\n result_title,\n result_subtitle,\n result_icon,\n result_badge,\n result_snapshot,\n primary_link_href,\n primary_link_label,\n created_at,\n updated_at\n FROM ${tableIdent}\n WHERE ${conditions.join('\\n AND ')}\n ORDER BY ${orderColumn} DESC\n LIMIT $${limitParam} OFFSET $${offsetParam}\n `\n\n const res = await pool.query<{\n entity_id: string\n record_id: string\n tenant_id: string\n organization_id: string | null\n checksum: string\n url: string | null\n presenter: string | null\n links: string | null\n payload: string | null\n result_title: string | null\n result_subtitle: string | null\n result_icon: string | null\n result_badge: string | null\n result_snapshot: string | null\n primary_link_href: string | null\n primary_link_label: string | null\n created_at: Date | string\n updated_at: Date | string\n }>(sql, values)\n return res.rows.map<VectorIndexEntry>((row) => {\n const presenter = parseJsonColumn<VectorResultPresenter>(row.presenter)\n const links = parseJsonColumn<VectorLinkDescriptor[]>(row.links)\n const payload = parseJsonColumn<Record<string, unknown>>(row.payload)\n const createdAt =\n row.created_at instanceof Date\n ? row.created_at.toISOString()\n : new Date(row.created_at ?? Date.now()).toISOString()\n const updatedAt =\n row.updated_at instanceof Date\n ? row.updated_at.toISOString()\n : new Date(row.updated_at ?? Date.now()).toISOString()\n return {\n entityId: row.entity_id,\n recordId: row.record_id,\n driverId: DRIVER_ID,\n tenantId: row.tenant_id,\n organizationId: row.organization_id ?? null,\n checksum: row.checksum,\n url: row.url ?? null,\n presenter,\n links,\n payload,\n metadata: payload,\n resultTitle: row.result_title ?? '',\n resultSubtitle: row.result_subtitle ?? null,\n resultIcon: row.result_icon ?? null,\n resultBadge: row.result_badge ?? null,\n resultSnapshot: row.result_snapshot ?? null,\n primaryLinkHref: row.primary_link_href ?? null,\n primaryLinkLabel: row.primary_link_label ?? null,\n createdAt,\n updatedAt,\n score: null,\n }\n })\n }\n\n const count = async (params: VectorDriverCountParams): Promise<number> => {\n await ensureReady()\n const conditions: string[] = [\n 'driver_id = $1',\n 'tenant_id = $2::uuid',\n ]\n const values: any[] = [DRIVER_ID, params.tenantId]\n let nextParam = 3\n\n const normalizedOrganizationId =\n typeof params.organizationId === 'string' && params.organizationId.trim().length > 0\n ? params.organizationId.trim()\n : null\n if (normalizedOrganizationId !== null) {\n conditions.push(`organization_id = $${nextParam}::uuid`)\n values.push(normalizedOrganizationId)\n nextParam += 1\n }\n if (params.entityId) {\n conditions.push(`entity_id = $${nextParam}::text`)\n values.push(params.entityId)\n nextParam += 1\n }\n\n const sql = `\n SELECT count(*)::bigint AS total\n FROM ${tableIdent}\n WHERE ${conditions.join('\\n AND ')}\n `\n const res = await pool.query<{ total: string }>(sql, values)\n const raw = res.rows?.[0]?.total\n if (!raw) return 0\n const parsed = Number(raw)\n return Number.isFinite(parsed) ? parsed : 0\n }\n\n const removeOrphans = async (params: VectorDriverRemoveOrphansParams): Promise<number> => {\n await ensureReady()\n const conditions: string[] = [\n 'driver_id = $1',\n 'entity_id = $2',\n 'updated_at < $3::timestamptz',\n ]\n const values: any[] = [DRIVER_ID, params.entityId, (params.olderThan instanceof Date ? params.olderThan : new Date(params.olderThan)).toISOString()]\n let nextParam = 4\n\n if (params.tenantId !== undefined) {\n conditions.push(`tenant_id is not distinct from $${nextParam}::uuid`)\n values.push(params.tenantId)\n nextParam += 1\n }\n\n if (params.organizationId !== undefined) {\n conditions.push(`organization_id is not distinct from $${nextParam}::uuid`)\n values.push(params.organizationId)\n nextParam += 1\n }\n\n const sql = `\n DELETE FROM ${tableIdent}\n WHERE ${conditions.join('\\n AND ')}\n `\n const res = await pool.query(sql, values)\n return res.rowCount ?? 0\n }\n\n const getTableDimension = async (): Promise<number | null> => {\n try {\n const res = await pool.query<{ atttypmod: number }>(\n `SELECT a.atttypmod\n FROM pg_attribute a\n JOIN pg_class c ON a.attrelid = c.oid\n WHERE c.relname = $1\n AND a.attname = 'embedding'\n AND a.atttypmod > 0`,\n [tableName]\n )\n if (res.rows.length > 0 && res.rows[0].atttypmod > 0) {\n return res.rows[0].atttypmod\n }\n return null\n } catch {\n return null\n }\n }\n\n const recreateWithDimension = async (newDimension: number): Promise<void> => {\n await withClient(pool, async (client) => {\n await client.query(`DROP TABLE IF EXISTS ${tableIdent} CASCADE`)\n await client.query(`DROP TABLE IF EXISTS ${migrationsIdent} CASCADE`)\n })\n ready = null\n dimension = newDimension\n await ensureReady()\n }\n\n return {\n id: 'pgvector',\n ensureReady,\n upsert,\n delete: remove,\n getChecksum,\n purge,\n query,\n list,\n count,\n removeOrphans,\n getTableDimension,\n recreateWithDimension,\n }\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["import { Pool } from 'pg'\nimport { searchDebugWarn, searchWarn } from '../../../lib/debug'\n\ntype PgPoolQueryResult<T> = { rows: T[]; rowCount?: number }\ntype PgPoolClient = {\n query<T = any>(text: string, params?: any[]): Promise<PgPoolQueryResult<T>>\n release(): void\n}\nexport type PgPool = {\n connect(): Promise<PgPoolClient>\n query<T = any>(text: string, params?: any[]): Promise<PgPoolQueryResult<T>>\n end(): Promise<void>\n}\nimport type {\n VectorDriver,\n VectorDriverDocument,\n VectorDriverQuery,\n VectorDriverQueryResult,\n VectorDriverListParams,\n VectorDriverCountParams,\n VectorIndexEntry,\n VectorDriverRemoveOrphansParams,\n VectorResultPresenter,\n VectorLinkDescriptor,\n VectorDriverStatus,\n} from '../../types'\n\ntype PgVectorDriverOptions = {\n pool?: PgPool\n connectionString?: string\n tableName?: string\n migrationsTable?: string\n dimension?: number\n distanceMetric?: 'cosine' | 'euclidean' | 'inner'\n}\n\nconst DEFAULT_TABLE = 'vector_search'\nconst DEFAULT_MIGRATIONS_TABLE = 'vector_search_migrations'\nconst DEFAULT_DIMENSION = 1536\nconst DRIVER_ID = 'pgvector' as const\n\n// `undefined_file` is what Postgres raises for `extension \"vector\" is not available`\n// (the pgvector shared library is not installed on the server); `feature_not_supported`\n// covers managed providers that refuse the extension outright, and `undefined_object`\n// the case where the extension record is missing. None of these can be resolved by\n// retrying, so the driver reports itself unhealthy instead of failing every record.\nconst EXTENSION_MISSING_CODES = new Set(['58P01', '0A000', '42704'])\nconst EXTENSION_UNAVAILABLE_REASON = 'extension \"vector\" is not available on this PostgreSQL server'\n// Re-probe occasionally so installing pgvector recovers without restarting the process.\nconst UNAVAILABLE_RECHECK_MS = 60_000\n\nfunction assertIdentifier(name: string, defaultName: string): string {\n const candidate = name ?? defaultName\n if (!candidate || !/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(candidate)) return defaultName\n return candidate\n}\n\nfunction quoteIdent(name: string): string {\n return `\"${name}\"`\n}\n\nfunction toVectorLiteral(values: number[]): string {\n const formatted = values.map((n) => {\n if (!Number.isFinite(n)) return '0'\n const rounded = Math.fround(n)\n return Number.isInteger(rounded) ? `${rounded}.0` : `${rounded}`\n })\n return `[${formatted.join(',')}]`\n}\n\nfunction parseJsonColumn<T>(value: unknown): T | null {\n if (value === null || value === undefined) return null\n if (typeof value === 'string') {\n try {\n return JSON.parse(value) as T\n } catch {\n // When `jsonb` stores a JSON string, node-postgres parses it into a plain JS string.\n // In that case, there is nothing to JSON.parse \u2014 return the raw string value.\n return value as unknown as T\n }\n }\n if (typeof value === 'object') {\n return value as T\n }\n return null\n}\n\nasync function withClient<T>(pool: PgPool, fn: (client: PgPoolClient) => Promise<T>): Promise<T> {\n const client = await pool.connect()\n try {\n return await fn(client)\n } finally {\n client.release()\n }\n}\n\nexport function createPgVectorDriver(opts: PgVectorDriverOptions = {}): VectorDriver {\n const tableName = assertIdentifier(opts.tableName ?? DEFAULT_TABLE, DEFAULT_TABLE)\n const migrationsTable = assertIdentifier(opts.migrationsTable ?? DEFAULT_MIGRATIONS_TABLE, DEFAULT_MIGRATIONS_TABLE)\n let dimension = opts.dimension ?? DEFAULT_DIMENSION\n const distanceMetric = opts.distanceMetric ?? 'cosine'\n const tableIdent = quoteIdent(tableName)\n const migrationsIdent = quoteIdent(migrationsTable)\n\n const pool: PgPool =\n opts.pool ??\n (() => {\n const conn = opts.connectionString ?? process.env.DATABASE_URL\n if (!conn) {\n throw new Error('[vector.pgvector] DATABASE_URL is not configured')\n }\n return new Pool({ connectionString: conn }) as unknown as PgPool\n })()\n\n let ready: Promise<void> | null = null\n let status: { value: VectorDriverStatus; at: number } | null = null\n let reportedReason: string | null = null\n\n const cacheStatus = (value: VectorDriverStatus): VectorDriverStatus => {\n status = { value, at: Date.now() }\n if (value.available) {\n reportedReason = null\n } else if (value.reason && value.reason !== reportedReason) {\n reportedReason = value.reason\n // Always visible: replacing a per-record error storm with silence would hide the fact\n // that vector indexing has stopped until someone opens Settings > Search.\n searchWarn('vector.pgvector', `vector store unavailable \u2014 skipping vector indexing and search (${value.reason})`)\n }\n return value\n }\n\n // The operator-facing reason is curated rather than the raw Postgres message, which the\n // settings API surfaces to the browser.\n const markExtensionMissing = () => {\n cacheStatus({ available: false, reason: EXTENSION_UNAVAILABLE_REASON })\n }\n\n const readCachedStatus = (): VectorDriverStatus | null => {\n if (!status) return null\n return Date.now() - status.at < UNAVAILABLE_RECHECK_MS ? status.value : null\n }\n\n const getStatus = async (): Promise<VectorDriverStatus> => {\n const cached = readCachedStatus()\n if (cached) return cached\n try {\n const res = await pool.query<{ installed: boolean; installable: boolean }>(\n `SELECT\n EXISTS (SELECT 1 FROM pg_extension WHERE extname = 'vector') AS installed,\n EXISTS (SELECT 1 FROM pg_available_extensions WHERE name = 'vector') AS installable`,\n )\n const row = res.rows[0]\n if (row?.installed || row?.installable) return cacheStatus({ available: true })\n return cacheStatus({ available: false, reason: EXTENSION_UNAVAILABLE_REASON })\n } catch {\n // A probe that cannot run (connection refused, permissions on the catalogs)\n // says nothing about pgvector itself \u2014 stay optimistic so genuinely transient\n // database problems keep their existing retry semantics.\n return { available: true }\n }\n }\n\n const isHealthy = async (): Promise<boolean> => (await getStatus()).available\n\n const ensureReady = async () => {\n const cached = readCachedStatus()\n if (cached && !cached.available) {\n throw new Error(`[vector.pgvector] ${cached.reason ?? 'vector store unavailable'}`)\n }\n if (!ready) {\n ready = withClient(pool, async (client) => {\n const ensureExtension = async (extension: 'pgcrypto' | 'vector') => {\n try {\n await client.query(`CREATE EXTENSION IF NOT EXISTS ${extension}`)\n } catch (error) {\n const pgError = error as { code?: string; message?: string }\n if (pgError?.code === '42501') {\n const details = pgError.message ? ` (${pgError.message})` : ''\n searchDebugWarn('vector.pgvector', `skipping ${extension} extension creation; requires superuser${details}`)\n return\n }\n if (extension === 'vector' && pgError?.code && EXTENSION_MISSING_CODES.has(pgError.code)) {\n markExtensionMissing()\n }\n throw error\n }\n }\n\n await ensureExtension('pgcrypto')\n await ensureExtension('vector')\n\n await client.query(\n `CREATE TABLE IF NOT EXISTS ${migrationsIdent} (\n id text primary key,\n applied_at timestamptz not null default now()\n )`,\n )\n\n await client.query(\n `CREATE TABLE IF NOT EXISTS ${tableIdent} (\n id uuid primary key default gen_random_uuid(),\n driver_id text not null,\n entity_id text not null,\n record_id text not null,\n tenant_id uuid not null,\n organization_id uuid null,\n checksum text not null,\n embedding vector(${dimension}) not null,\n url text null,\n presenter jsonb null,\n links jsonb null,\n payload jsonb null,\n result_title text null,\n result_subtitle text null,\n result_icon text null,\n result_badge text null,\n result_snapshot text null,\n primary_link_href text null,\n primary_link_label text null,\n created_at timestamptz not null default now(),\n updated_at timestamptz not null default now()\n )`,\n )\n\n await client.query(\n `CREATE UNIQUE INDEX IF NOT EXISTS ${tableName}_uniq ON ${tableIdent} (driver_id, entity_id, record_id, tenant_id)`,\n )\n await client.query(\n `CREATE INDEX IF NOT EXISTS ${tableName}_lookup ON ${tableIdent} (tenant_id, organization_id, entity_id)`,\n )\n // ivfflat index only supports up to 2000 dimensions\n // For higher dimensions, skip the index (uses sequential scan, slower but works)\n // Also check actual table dimension in case driver was initialized with different value\n let actualDimension = dimension\n try {\n const dimResult = await client.query<{ atttypmod: number }>(\n `SELECT a.atttypmod\n FROM pg_attribute a\n JOIN pg_class c ON a.attrelid = c.oid\n WHERE c.relname = $1\n AND a.attname = 'embedding'\n AND a.atttypmod > 0`,\n [tableName]\n )\n if (dimResult.rows.length > 0 && dimResult.rows[0].atttypmod > 0) {\n actualDimension = dimResult.rows[0].atttypmod\n }\n } catch {\n // Ignore errors reading dimension, use configured value\n }\n\n if (actualDimension <= 2000) {\n try {\n await client.query(\n `CREATE INDEX IF NOT EXISTS ${tableName}_embedding_idx ON ${tableIdent}\n USING ivfflat (embedding vector_${distanceMetric}_ops) WITH (lists = 100)`,\n )\n } catch (indexErr: unknown) {\n // Handle case where dimension exceeds ivfflat limit\n const errorMessage = indexErr instanceof Error ? indexErr.message : String(indexErr)\n if (errorMessage.includes('2000 dimensions')) {\n searchDebugWarn('pgvector', 'Skipping ivfflat index - dimension exceeds 2000 limit. Searches will use sequential scan.')\n } else {\n throw indexErr\n }\n }\n } else {\n searchDebugWarn('pgvector', `Skipping ivfflat index - dimension ${actualDimension} exceeds 2000 limit. Searches will use sequential scan.`)\n }\n\n const columnAlters = [\n `ALTER TABLE ${tableIdent} ADD COLUMN IF NOT EXISTS result_title text`,\n `ALTER TABLE ${tableIdent} ADD COLUMN IF NOT EXISTS result_subtitle text`,\n `ALTER TABLE ${tableIdent} ADD COLUMN IF NOT EXISTS result_icon text`,\n `ALTER TABLE ${tableIdent} ADD COLUMN IF NOT EXISTS result_badge text`,\n `ALTER TABLE ${tableIdent} ADD COLUMN IF NOT EXISTS result_snapshot text`,\n `ALTER TABLE ${tableIdent} ADD COLUMN IF NOT EXISTS primary_link_href text`,\n `ALTER TABLE ${tableIdent} ADD COLUMN IF NOT EXISTS primary_link_label text`,\n ]\n for (const statement of columnAlters) {\n await client.query(statement)\n }\n\n await client.query(\n `INSERT INTO ${migrationsIdent} (id, applied_at) VALUES ($1, now()) ON CONFLICT (id) DO NOTHING`,\n ['0001_init'],\n )\n }).then(() => {\n cacheStatus({ available: true })\n }).catch((err) => {\n ready = null\n const pgError = err as { code?: string; message?: string }\n // Without the extension the `vector` column type does not exist either, so the\n // table DDL fails even when `CREATE EXTENSION` was skipped rather than raised.\n if (pgError?.code === '42704' && /\\bvector\\b/.test(pgError.message ?? '')) {\n markExtensionMissing()\n }\n throw err\n })\n }\n return ready\n }\n\n const upsert = async (doc: VectorDriverDocument) => {\n await ensureReady()\n const vectorLiteral = toVectorLiteral(doc.embedding)\n await pool.query(\n `\n INSERT INTO ${tableIdent} (\n driver_id, entity_id, record_id, tenant_id, organization_id, checksum,\n embedding, url, presenter, links, payload,\n result_title, result_subtitle, result_icon, result_badge, result_snapshot,\n primary_link_href, primary_link_label,\n created_at, updated_at\n )\n VALUES (\n $1, $2, $3, $4::uuid, $5::uuid, $6, $7::vector, $8, $9::jsonb, $10::jsonb, $11::jsonb,\n $12, $13, $14, $15, $16, $17, $18,\n now(), now()\n )\n ON CONFLICT (driver_id, entity_id, record_id, tenant_id)\n DO UPDATE SET\n organization_id = EXCLUDED.organization_id,\n checksum = EXCLUDED.checksum,\n embedding = EXCLUDED.embedding,\n url = EXCLUDED.url,\n presenter = EXCLUDED.presenter,\n links = EXCLUDED.links,\n payload = EXCLUDED.payload,\n result_title = EXCLUDED.result_title,\n result_subtitle = EXCLUDED.result_subtitle,\n result_icon = EXCLUDED.result_icon,\n result_badge = EXCLUDED.result_badge,\n result_snapshot = EXCLUDED.result_snapshot,\n primary_link_href = EXCLUDED.primary_link_href,\n primary_link_label = EXCLUDED.primary_link_label,\n updated_at = now()\n `,\n [\n doc.driverId ?? DRIVER_ID,\n doc.entityId,\n doc.recordId,\n doc.tenantId,\n doc.organizationId ?? null,\n doc.checksum,\n vectorLiteral,\n doc.url ?? null,\n doc.presenter ? JSON.stringify(doc.presenter) : null,\n doc.links ? JSON.stringify(doc.links) : null,\n doc.payload ? JSON.stringify(doc.payload) : null,\n doc.resultTitle,\n doc.resultSubtitle ?? null,\n doc.resultIcon ?? null,\n doc.resultBadge ?? null,\n doc.resultSnapshot ?? null,\n doc.primaryLinkHref ?? null,\n doc.primaryLinkLabel ?? null,\n ],\n )\n }\n\n const remove = async (entityId: string, recordId: string, tenantId: string) => {\n await ensureReady()\n await pool.query(\n `DELETE FROM ${tableIdent} WHERE driver_id = $1 AND entity_id = $2 AND record_id = $3 AND tenant_id = $4::uuid`,\n [DRIVER_ID, entityId, recordId, tenantId],\n )\n }\n\n const getChecksum = async (entityId: string, recordId: string, tenantId: string): Promise<string | null> => {\n await ensureReady()\n const res = await pool.query<{ checksum: string }>(\n `SELECT checksum FROM ${tableIdent} WHERE driver_id = $1 AND entity_id = $2 AND record_id = $3 AND tenant_id = $4::uuid`,\n [DRIVER_ID, entityId, recordId, tenantId],\n )\n return res.rowCount ? res.rows[0].checksum : null\n }\n\n const purge = async (entityId: string, tenantId: string, organizationId?: string | null) => {\n await ensureReady()\n const normalizedOrganizationId =\n typeof organizationId === 'string' && organizationId.trim().length > 0 ? organizationId.trim() : null\n const conditions = ['driver_id = $1', 'entity_id = $2', 'tenant_id = $3::uuid']\n const values: any[] = [DRIVER_ID, entityId, tenantId]\n if (normalizedOrganizationId !== null) {\n conditions.push('organization_id = $4::uuid')\n values.push(normalizedOrganizationId)\n }\n await pool.query(\n `DELETE FROM ${tableIdent} WHERE ${conditions.join(' AND ')}`,\n values,\n )\n }\n\n const query = async (input: VectorDriverQuery): Promise<VectorDriverQueryResult[]> => {\n await ensureReady()\n const vectorLiteral = toVectorLiteral(input.vector)\n const filter = input.filter ?? { tenantId: '' }\n const normalizedOrganizationId =\n typeof filter.organizationId === 'string' && filter.organizationId.trim().length > 0\n ? filter.organizationId.trim()\n : null\n const normalizedOrganizationIds = normalizedOrganizationId\n ? [normalizedOrganizationId]\n : Array.isArray(filter.organizationIds)\n ? Array.from(new Set(\n filter.organizationIds\n .map((value) => (typeof value === 'string' ? value.trim() : ''))\n .filter((value) => value.length > 0),\n ))\n : null\n if (normalizedOrganizationIds && normalizedOrganizationIds.length === 0) {\n return []\n }\n const params: any[] = [\n vectorLiteral,\n DRIVER_ID,\n filter.tenantId,\n normalizedOrganizationIds,\n Array.isArray(filter.entityIds) && filter.entityIds.length ? filter.entityIds : null,\n input.limit ?? 20,\n ]\n const res = await pool.query<{\n entity_id: string\n record_id: string\n organization_id: string | null\n checksum: string\n url: string | null\n presenter: string | null\n links: string | null\n payload: string | null\n result_title: string | null\n result_subtitle: string | null\n result_icon: string | null\n result_badge: string | null\n result_snapshot: string | null\n primary_link_href: string | null\n primary_link_label: string | null\n distance: number\n }>(\n `\n SELECT\n entity_id,\n record_id,\n organization_id,\n checksum,\n url,\n presenter,\n links,\n payload,\n result_title,\n result_subtitle,\n result_icon,\n result_badge,\n result_snapshot,\n primary_link_href,\n primary_link_label,\n embedding <=> $1::vector AS distance\n FROM ${tableIdent}\n WHERE driver_id = $2\n AND tenant_id = $3::uuid\n AND ($4::uuid[] IS NULL OR organization_id = ANY($4::uuid[]))\n AND (\n $5::text[] IS NULL OR entity_id = ANY($5::text[])\n )\n ORDER BY embedding <=> $1::vector\n LIMIT $6\n `,\n params,\n )\n return res.rows.map<VectorDriverQueryResult>((row) => {\n const distance = typeof row.distance === 'number' ? row.distance : Number(row.distance || 1)\n const score = 1 - distance\n return {\n entityId: row.entity_id,\n recordId: row.record_id,\n organizationId: row.organization_id ?? null,\n checksum: row.checksum,\n url: row.url ?? null,\n presenter: parseJsonColumn<VectorResultPresenter>(row.presenter),\n links: parseJsonColumn<VectorLinkDescriptor[]>(row.links),\n payload: parseJsonColumn<Record<string, unknown>>(row.payload),\n resultTitle: row.result_title ?? '',\n resultSubtitle: row.result_subtitle ?? null,\n resultIcon: row.result_icon ?? null,\n resultBadge: row.result_badge ?? null,\n resultSnapshot: row.result_snapshot ?? null,\n primaryLinkHref: row.primary_link_href ?? null,\n primaryLinkLabel: row.primary_link_label ?? null,\n score,\n }\n })\n }\n\n const list = async (params: VectorDriverListParams): Promise<VectorIndexEntry[]> => {\n await ensureReady()\n const limit = Math.max(1, Math.min(params.limit ?? 50, 200))\n const offset = Math.max(0, params.offset ?? 0)\n const orderColumn = params.orderBy === 'created' ? 'created_at' : 'updated_at'\n const conditions: string[] = [\n 'driver_id = $1',\n 'tenant_id = $2::uuid',\n ]\n const values: any[] = [DRIVER_ID, params.tenantId]\n let nextParam = 3\n\n const normalizedOrganizationId =\n typeof params.organizationId === 'string' && params.organizationId.trim().length > 0\n ? params.organizationId.trim()\n : null\n if (normalizedOrganizationId !== null) {\n conditions.push(`organization_id = $${nextParam}::uuid`)\n values.push(normalizedOrganizationId)\n nextParam += 1\n }\n\n if (params.entityId) {\n conditions.push(`entity_id = $${nextParam}::text`)\n values.push(params.entityId)\n nextParam += 1\n }\n\n const limitParam = nextParam\n const offsetParam = nextParam + 1\n values.push(limit, offset)\n\n const sql = `\n SELECT\n entity_id,\n record_id,\n tenant_id,\n organization_id,\n checksum,\n url,\n presenter,\n links,\n payload,\n result_title,\n result_subtitle,\n result_icon,\n result_badge,\n result_snapshot,\n primary_link_href,\n primary_link_label,\n created_at,\n updated_at\n FROM ${tableIdent}\n WHERE ${conditions.join('\\n AND ')}\n ORDER BY ${orderColumn} DESC\n LIMIT $${limitParam} OFFSET $${offsetParam}\n `\n\n const res = await pool.query<{\n entity_id: string\n record_id: string\n tenant_id: string\n organization_id: string | null\n checksum: string\n url: string | null\n presenter: string | null\n links: string | null\n payload: string | null\n result_title: string | null\n result_subtitle: string | null\n result_icon: string | null\n result_badge: string | null\n result_snapshot: string | null\n primary_link_href: string | null\n primary_link_label: string | null\n created_at: Date | string\n updated_at: Date | string\n }>(sql, values)\n return res.rows.map<VectorIndexEntry>((row) => {\n const presenter = parseJsonColumn<VectorResultPresenter>(row.presenter)\n const links = parseJsonColumn<VectorLinkDescriptor[]>(row.links)\n const payload = parseJsonColumn<Record<string, unknown>>(row.payload)\n const createdAt =\n row.created_at instanceof Date\n ? row.created_at.toISOString()\n : new Date(row.created_at ?? Date.now()).toISOString()\n const updatedAt =\n row.updated_at instanceof Date\n ? row.updated_at.toISOString()\n : new Date(row.updated_at ?? Date.now()).toISOString()\n return {\n entityId: row.entity_id,\n recordId: row.record_id,\n driverId: DRIVER_ID,\n tenantId: row.tenant_id,\n organizationId: row.organization_id ?? null,\n checksum: row.checksum,\n url: row.url ?? null,\n presenter,\n links,\n payload,\n metadata: payload,\n resultTitle: row.result_title ?? '',\n resultSubtitle: row.result_subtitle ?? null,\n resultIcon: row.result_icon ?? null,\n resultBadge: row.result_badge ?? null,\n resultSnapshot: row.result_snapshot ?? null,\n primaryLinkHref: row.primary_link_href ?? null,\n primaryLinkLabel: row.primary_link_label ?? null,\n createdAt,\n updatedAt,\n score: null,\n }\n })\n }\n\n const count = async (params: VectorDriverCountParams): Promise<number> => {\n await ensureReady()\n const conditions: string[] = [\n 'driver_id = $1',\n 'tenant_id = $2::uuid',\n ]\n const values: any[] = [DRIVER_ID, params.tenantId]\n let nextParam = 3\n\n const normalizedOrganizationId =\n typeof params.organizationId === 'string' && params.organizationId.trim().length > 0\n ? params.organizationId.trim()\n : null\n if (normalizedOrganizationId !== null) {\n conditions.push(`organization_id = $${nextParam}::uuid`)\n values.push(normalizedOrganizationId)\n nextParam += 1\n }\n if (params.entityId) {\n conditions.push(`entity_id = $${nextParam}::text`)\n values.push(params.entityId)\n nextParam += 1\n }\n\n const sql = `\n SELECT count(*)::bigint AS total\n FROM ${tableIdent}\n WHERE ${conditions.join('\\n AND ')}\n `\n const res = await pool.query<{ total: string }>(sql, values)\n const raw = res.rows?.[0]?.total\n if (!raw) return 0\n const parsed = Number(raw)\n return Number.isFinite(parsed) ? parsed : 0\n }\n\n const removeOrphans = async (params: VectorDriverRemoveOrphansParams): Promise<number> => {\n await ensureReady()\n const conditions: string[] = [\n 'driver_id = $1',\n 'entity_id = $2',\n 'updated_at < $3::timestamptz',\n ]\n const values: any[] = [DRIVER_ID, params.entityId, (params.olderThan instanceof Date ? params.olderThan : new Date(params.olderThan)).toISOString()]\n let nextParam = 4\n\n if (params.tenantId !== undefined) {\n conditions.push(`tenant_id is not distinct from $${nextParam}::uuid`)\n values.push(params.tenantId)\n nextParam += 1\n }\n\n if (params.organizationId !== undefined) {\n conditions.push(`organization_id is not distinct from $${nextParam}::uuid`)\n values.push(params.organizationId)\n nextParam += 1\n }\n\n const sql = `\n DELETE FROM ${tableIdent}\n WHERE ${conditions.join('\\n AND ')}\n `\n const res = await pool.query(sql, values)\n return res.rowCount ?? 0\n }\n\n const getTableDimension = async (): Promise<number | null> => {\n try {\n const res = await pool.query<{ atttypmod: number }>(\n `SELECT a.atttypmod\n FROM pg_attribute a\n JOIN pg_class c ON a.attrelid = c.oid\n WHERE c.relname = $1\n AND a.attname = 'embedding'\n AND a.atttypmod > 0`,\n [tableName]\n )\n if (res.rows.length > 0 && res.rows[0].atttypmod > 0) {\n return res.rows[0].atttypmod\n }\n return null\n } catch {\n return null\n }\n }\n\n const recreateWithDimension = async (newDimension: number): Promise<void> => {\n await withClient(pool, async (client) => {\n await client.query(`DROP TABLE IF EXISTS ${tableIdent} CASCADE`)\n await client.query(`DROP TABLE IF EXISTS ${migrationsIdent} CASCADE`)\n })\n ready = null\n dimension = newDimension\n await ensureReady()\n }\n\n return {\n id: 'pgvector',\n ensureReady,\n isHealthy,\n getStatus,\n upsert,\n delete: remove,\n getChecksum,\n purge,\n query,\n list,\n count,\n removeOrphans,\n getTableDimension,\n recreateWithDimension,\n }\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/vector/types.ts"],
4
- "sourcesContent": ["import type { EntityId } from '@open-mercato/shared/modules/entities'\nimport type {\n VectorDriverId,\n VectorIndexSource,\n VectorModuleConfig,\n VectorEntityConfig,\n VectorLinkDescriptor,\n VectorResultPresenter,\n VectorSearchHit,\n VectorQueryRequest,\n VectorIndexEntry,\n} from '@open-mercato/shared/modules/vector'\n\nexport type {\n VectorDriverId,\n VectorIndexSource,\n VectorModuleConfig,\n VectorEntityConfig,\n VectorLinkDescriptor,\n VectorResultPresenter,\n VectorSearchHit,\n VectorQueryRequest,\n VectorIndexEntry,\n}\n\nexport type VectorDriverDocument = {\n entityId: EntityId\n recordId: string\n tenantId: string\n organizationId?: string | null\n checksum: string\n embedding: number[]\n url?: string | null\n presenter?: VectorResultPresenter | null\n links?: VectorLinkDescriptor[] | null\n payload?: Record<string, unknown> | null\n driverId: VectorDriverId\n resultTitle: string\n resultSubtitle?: string | null\n resultIcon?: string | null\n resultBadge?: string | null\n resultSnapshot?: string | null\n primaryLinkHref?: string | null\n primaryLinkLabel?: string | null\n}\n\nexport type VectorDriverQuery = {\n vector: number[]\n limit?: number\n filter?: {\n entityIds?: EntityId[]\n organizationId?: string | null\n organizationIds?: string[] | null\n tenantId: string\n }\n}\n\nexport type VectorDriverQueryResult = {\n entityId: EntityId\n recordId: string\n organizationId?: string | null\n score: number\n checksum: string\n url?: string | null\n presenter?: VectorResultPresenter | null\n links?: VectorLinkDescriptor[] | null\n payload?: Record<string, unknown> | null\n resultTitle: string\n resultSubtitle?: string | null\n resultIcon?: string | null\n resultBadge?: string | null\n resultSnapshot?: string | null\n primaryLinkHref?: string | null\n primaryLinkLabel?: string | null\n}\n\nexport type VectorDriverListParams = {\n tenantId: string\n organizationId?: string | null\n entityId?: EntityId\n limit?: number\n offset?: number\n orderBy?: 'created' | 'updated'\n}\n\nexport type VectorDriverCountParams = {\n tenantId: string\n organizationId?: string | null\n entityId?: EntityId\n}\n\nexport type VectorDriverRemoveOrphansParams = {\n entityId: EntityId\n tenantId?: string | null\n organizationId?: string | null\n olderThan: Date\n}\n\nexport interface VectorDriver {\n readonly id: VectorDriverId\n ensureReady(): Promise<void>\n upsert(doc: VectorDriverDocument): Promise<void>\n delete(entityId: EntityId, recordId: string, tenantId: string): Promise<void>\n query(input: VectorDriverQuery): Promise<VectorDriverQueryResult[]>\n getChecksum(entityId: EntityId, recordId: string, tenantId: string): Promise<string | null>\n purge?(entityId: EntityId, tenantId: string, organizationId?: string | null): Promise<void>\n list?(params: VectorDriverListParams): Promise<VectorIndexEntry[]>\n count?(params: VectorDriverCountParams): Promise<number>\n removeOrphans?(params: VectorDriverRemoveOrphansParams): Promise<number | void>\n getTableDimension?(): Promise<number | null>\n recreateWithDimension?(newDimension: number): Promise<void>\n}\n\n// ============================================================================\n// Embedding Provider Configuration Types\n// ============================================================================\n\nexport type EmbeddingProviderId =\n | 'openai'\n | 'google'\n | 'mistral'\n | 'cohere'\n | 'bedrock'\n | 'ollama'\n\nexport type EmbeddingProviderConfig = {\n providerId: EmbeddingProviderId\n model: string\n dimension: number\n outputDimensionality?: number\n baseUrl?: string\n updatedAt: string\n}\n\nexport type EmbeddingModelInfo = {\n id: string\n name: string\n dimension: number\n configurableDimension?: boolean\n minDimension?: number\n maxDimension?: number\n}\n\nexport type EmbeddingProviderInfo = {\n name: string\n envKeyRequired: string\n defaultModel: string\n models: EmbeddingModelInfo[]\n}\n\nexport const EMBEDDING_PROVIDERS: Record<EmbeddingProviderId, EmbeddingProviderInfo> = {\n openai: {\n name: 'OpenAI',\n envKeyRequired: 'OPENAI_API_KEY',\n defaultModel: 'text-embedding-3-small',\n models: [\n { id: 'text-embedding-3-small', name: 'text-embedding-3-small', dimension: 1536 },\n { id: 'text-embedding-3-large', name: 'text-embedding-3-large', dimension: 3072, configurableDimension: true, minDimension: 256, maxDimension: 3072 },\n { id: 'text-embedding-ada-002', name: 'text-embedding-ada-002', dimension: 1536 },\n ],\n },\n google: {\n name: 'Google Generative AI',\n envKeyRequired: 'GOOGLE_GENERATIVE_AI_API_KEY',\n defaultModel: 'text-embedding-004',\n models: [\n { id: 'text-embedding-004', name: 'text-embedding-004', dimension: 768, configurableDimension: true, minDimension: 1, maxDimension: 768 },\n { id: 'embedding-001', name: 'embedding-001', dimension: 768 },\n ],\n },\n mistral: {\n name: 'Mistral',\n envKeyRequired: 'MISTRAL_API_KEY',\n defaultModel: 'mistral-embed',\n models: [\n { id: 'mistral-embed', name: 'mistral-embed', dimension: 1024 },\n ],\n },\n cohere: {\n name: 'Cohere',\n envKeyRequired: 'COHERE_API_KEY',\n defaultModel: 'embed-english-v3.0',\n models: [\n { id: 'embed-english-v3.0', name: 'embed-english-v3.0', dimension: 1024 },\n { id: 'embed-multilingual-v3.0', name: 'embed-multilingual-v3.0', dimension: 1024 },\n { id: 'embed-english-light-v3.0', name: 'embed-english-light-v3.0', dimension: 384 },\n { id: 'embed-multilingual-light-v3.0', name: 'embed-multilingual-light-v3.0', dimension: 384 },\n ],\n },\n bedrock: {\n name: 'Amazon Bedrock',\n envKeyRequired: 'AWS_ACCESS_KEY_ID',\n defaultModel: 'amazon.titan-embed-text-v2:0',\n models: [\n { id: 'amazon.titan-embed-text-v2:0', name: 'Titan Embed Text v2', dimension: 1024, configurableDimension: true, minDimension: 256, maxDimension: 1024 },\n { id: 'amazon.titan-embed-text-v1', name: 'Titan Embed Text v1', dimension: 1536 },\n { id: 'cohere.embed-english-v3', name: 'Cohere Embed English v3', dimension: 1024 },\n { id: 'cohere.embed-multilingual-v3', name: 'Cohere Embed Multilingual v3', dimension: 1024 },\n ],\n },\n ollama: {\n name: 'Ollama (Local)',\n envKeyRequired: 'OLLAMA_BASE_URL',\n defaultModel: 'nomic-embed-text',\n models: [\n { id: 'nomic-embed-text', name: 'nomic-embed-text', dimension: 768 },\n { id: 'mxbai-embed-large', name: 'mxbai-embed-large', dimension: 1024 },\n { id: 'all-minilm', name: 'all-minilm', dimension: 384 },\n { id: 'snowflake-arctic-embed', name: 'snowflake-arctic-embed', dimension: 1024 },\n ],\n },\n}\n\nexport const EMBEDDING_CONFIG_KEY = 'embedding_provider'\n\nexport const DEFAULT_EMBEDDING_CONFIG: EmbeddingProviderConfig = {\n providerId: 'openai',\n model: 'text-embedding-3-small',\n dimension: 1536,\n updatedAt: new Date().toISOString(),\n}\n"],
5
- "mappings": "AAsJO,MAAM,sBAA0E;AAAA,EACrF,QAAQ;AAAA,IACN,MAAM;AAAA,IACN,gBAAgB;AAAA,IAChB,cAAc;AAAA,IACd,QAAQ;AAAA,MACN,EAAE,IAAI,0BAA0B,MAAM,0BAA0B,WAAW,KAAK;AAAA,MAChF,EAAE,IAAI,0BAA0B,MAAM,0BAA0B,WAAW,MAAM,uBAAuB,MAAM,cAAc,KAAK,cAAc,KAAK;AAAA,MACpJ,EAAE,IAAI,0BAA0B,MAAM,0BAA0B,WAAW,KAAK;AAAA,IAClF;AAAA,EACF;AAAA,EACA,QAAQ;AAAA,IACN,MAAM;AAAA,IACN,gBAAgB;AAAA,IAChB,cAAc;AAAA,IACd,QAAQ;AAAA,MACN,EAAE,IAAI,sBAAsB,MAAM,sBAAsB,WAAW,KAAK,uBAAuB,MAAM,cAAc,GAAG,cAAc,IAAI;AAAA,MACxI,EAAE,IAAI,iBAAiB,MAAM,iBAAiB,WAAW,IAAI;AAAA,IAC/D;AAAA,EACF;AAAA,EACA,SAAS;AAAA,IACP,MAAM;AAAA,IACN,gBAAgB;AAAA,IAChB,cAAc;AAAA,IACd,QAAQ;AAAA,MACN,EAAE,IAAI,iBAAiB,MAAM,iBAAiB,WAAW,KAAK;AAAA,IAChE;AAAA,EACF;AAAA,EACA,QAAQ;AAAA,IACN,MAAM;AAAA,IACN,gBAAgB;AAAA,IAChB,cAAc;AAAA,IACd,QAAQ;AAAA,MACN,EAAE,IAAI,sBAAsB,MAAM,sBAAsB,WAAW,KAAK;AAAA,MACxE,EAAE,IAAI,2BAA2B,MAAM,2BAA2B,WAAW,KAAK;AAAA,MAClF,EAAE,IAAI,4BAA4B,MAAM,4BAA4B,WAAW,IAAI;AAAA,MACnF,EAAE,IAAI,iCAAiC,MAAM,iCAAiC,WAAW,IAAI;AAAA,IAC/F;AAAA,EACF;AAAA,EACA,SAAS;AAAA,IACP,MAAM;AAAA,IACN,gBAAgB;AAAA,IAChB,cAAc;AAAA,IACd,QAAQ;AAAA,MACN,EAAE,IAAI,gCAAgC,MAAM,uBAAuB,WAAW,MAAM,uBAAuB,MAAM,cAAc,KAAK,cAAc,KAAK;AAAA,MACvJ,EAAE,IAAI,8BAA8B,MAAM,uBAAuB,WAAW,KAAK;AAAA,MACjF,EAAE,IAAI,2BAA2B,MAAM,2BAA2B,WAAW,KAAK;AAAA,MAClF,EAAE,IAAI,gCAAgC,MAAM,gCAAgC,WAAW,KAAK;AAAA,IAC9F;AAAA,EACF;AAAA,EACA,QAAQ;AAAA,IACN,MAAM;AAAA,IACN,gBAAgB;AAAA,IAChB,cAAc;AAAA,IACd,QAAQ;AAAA,MACN,EAAE,IAAI,oBAAoB,MAAM,oBAAoB,WAAW,IAAI;AAAA,MACnE,EAAE,IAAI,qBAAqB,MAAM,qBAAqB,WAAW,KAAK;AAAA,MACtE,EAAE,IAAI,cAAc,MAAM,cAAc,WAAW,IAAI;AAAA,MACvD,EAAE,IAAI,0BAA0B,MAAM,0BAA0B,WAAW,KAAK;AAAA,IAClF;AAAA,EACF;AACF;AAEO,MAAM,uBAAuB;AAE7B,MAAM,2BAAoD;AAAA,EAC/D,YAAY;AAAA,EACZ,OAAO;AAAA,EACP,WAAW;AAAA,EACX,YAAW,oBAAI,KAAK,GAAE,YAAY;AACpC;",
4
+ "sourcesContent": ["import type { EntityId } from '@open-mercato/shared/modules/entities'\nimport type {\n VectorDriverId,\n VectorIndexSource,\n VectorModuleConfig,\n VectorEntityConfig,\n VectorLinkDescriptor,\n VectorResultPresenter,\n VectorSearchHit,\n VectorQueryRequest,\n VectorIndexEntry,\n} from '@open-mercato/shared/modules/vector'\n\nexport type {\n VectorDriverId,\n VectorIndexSource,\n VectorModuleConfig,\n VectorEntityConfig,\n VectorLinkDescriptor,\n VectorResultPresenter,\n VectorSearchHit,\n VectorQueryRequest,\n VectorIndexEntry,\n}\n\nexport type VectorDriverDocument = {\n entityId: EntityId\n recordId: string\n tenantId: string\n organizationId?: string | null\n checksum: string\n embedding: number[]\n url?: string | null\n presenter?: VectorResultPresenter | null\n links?: VectorLinkDescriptor[] | null\n payload?: Record<string, unknown> | null\n driverId: VectorDriverId\n resultTitle: string\n resultSubtitle?: string | null\n resultIcon?: string | null\n resultBadge?: string | null\n resultSnapshot?: string | null\n primaryLinkHref?: string | null\n primaryLinkLabel?: string | null\n}\n\nexport type VectorDriverQuery = {\n vector: number[]\n limit?: number\n filter?: {\n entityIds?: EntityId[]\n organizationId?: string | null\n organizationIds?: string[] | null\n tenantId: string\n }\n}\n\nexport type VectorDriverQueryResult = {\n entityId: EntityId\n recordId: string\n organizationId?: string | null\n score: number\n checksum: string\n url?: string | null\n presenter?: VectorResultPresenter | null\n links?: VectorLinkDescriptor[] | null\n payload?: Record<string, unknown> | null\n resultTitle: string\n resultSubtitle?: string | null\n resultIcon?: string | null\n resultBadge?: string | null\n resultSnapshot?: string | null\n primaryLinkHref?: string | null\n primaryLinkLabel?: string | null\n}\n\nexport type VectorDriverListParams = {\n tenantId: string\n organizationId?: string | null\n entityId?: EntityId\n limit?: number\n offset?: number\n orderBy?: 'created' | 'updated'\n}\n\nexport type VectorDriverCountParams = {\n tenantId: string\n organizationId?: string | null\n entityId?: EntityId\n}\n\nexport type VectorDriverRemoveOrphansParams = {\n entityId: EntityId\n tenantId?: string | null\n organizationId?: string | null\n olderThan: Date\n}\n\nexport type VectorDriverStatus = {\n available: boolean\n reason?: string\n}\n\nexport interface VectorDriver {\n readonly id: VectorDriverId\n ensureReady(): Promise<void>\n /**\n * Cheap, cached probe answering whether the backing store can serve reads and\n * writes at all. Drivers that cannot satisfy their hard requirements (for\n * pgvector: the `vector` extension) MUST report `false` here so callers skip\n * the strategy instead of failing every record. Optional for backward\n * compatibility \u2014 an absent implementation is treated as available.\n */\n isHealthy?(): Promise<boolean>\n getStatus?(): Promise<VectorDriverStatus>\n upsert(doc: VectorDriverDocument): Promise<void>\n delete(entityId: EntityId, recordId: string, tenantId: string): Promise<void>\n query(input: VectorDriverQuery): Promise<VectorDriverQueryResult[]>\n getChecksum(entityId: EntityId, recordId: string, tenantId: string): Promise<string | null>\n purge?(entityId: EntityId, tenantId: string, organizationId?: string | null): Promise<void>\n list?(params: VectorDriverListParams): Promise<VectorIndexEntry[]>\n count?(params: VectorDriverCountParams): Promise<number>\n removeOrphans?(params: VectorDriverRemoveOrphansParams): Promise<number | void>\n getTableDimension?(): Promise<number | null>\n recreateWithDimension?(newDimension: number): Promise<void>\n}\n\n// ============================================================================\n// Embedding Provider Configuration Types\n// ============================================================================\n\nexport type EmbeddingProviderId =\n | 'openai'\n | 'google'\n | 'mistral'\n | 'cohere'\n | 'bedrock'\n | 'ollama'\n\nexport type EmbeddingProviderConfig = {\n providerId: EmbeddingProviderId\n model: string\n dimension: number\n outputDimensionality?: number\n baseUrl?: string\n updatedAt: string\n}\n\nexport type EmbeddingModelInfo = {\n id: string\n name: string\n dimension: number\n configurableDimension?: boolean\n minDimension?: number\n maxDimension?: number\n}\n\nexport type EmbeddingProviderInfo = {\n name: string\n envKeyRequired: string\n defaultModel: string\n models: EmbeddingModelInfo[]\n}\n\nexport const EMBEDDING_PROVIDERS: Record<EmbeddingProviderId, EmbeddingProviderInfo> = {\n openai: {\n name: 'OpenAI',\n envKeyRequired: 'OPENAI_API_KEY',\n defaultModel: 'text-embedding-3-small',\n models: [\n { id: 'text-embedding-3-small', name: 'text-embedding-3-small', dimension: 1536 },\n { id: 'text-embedding-3-large', name: 'text-embedding-3-large', dimension: 3072, configurableDimension: true, minDimension: 256, maxDimension: 3072 },\n { id: 'text-embedding-ada-002', name: 'text-embedding-ada-002', dimension: 1536 },\n ],\n },\n google: {\n name: 'Google Generative AI',\n envKeyRequired: 'GOOGLE_GENERATIVE_AI_API_KEY',\n defaultModel: 'text-embedding-004',\n models: [\n { id: 'text-embedding-004', name: 'text-embedding-004', dimension: 768, configurableDimension: true, minDimension: 1, maxDimension: 768 },\n { id: 'embedding-001', name: 'embedding-001', dimension: 768 },\n ],\n },\n mistral: {\n name: 'Mistral',\n envKeyRequired: 'MISTRAL_API_KEY',\n defaultModel: 'mistral-embed',\n models: [\n { id: 'mistral-embed', name: 'mistral-embed', dimension: 1024 },\n ],\n },\n cohere: {\n name: 'Cohere',\n envKeyRequired: 'COHERE_API_KEY',\n defaultModel: 'embed-english-v3.0',\n models: [\n { id: 'embed-english-v3.0', name: 'embed-english-v3.0', dimension: 1024 },\n { id: 'embed-multilingual-v3.0', name: 'embed-multilingual-v3.0', dimension: 1024 },\n { id: 'embed-english-light-v3.0', name: 'embed-english-light-v3.0', dimension: 384 },\n { id: 'embed-multilingual-light-v3.0', name: 'embed-multilingual-light-v3.0', dimension: 384 },\n ],\n },\n bedrock: {\n name: 'Amazon Bedrock',\n envKeyRequired: 'AWS_ACCESS_KEY_ID',\n defaultModel: 'amazon.titan-embed-text-v2:0',\n models: [\n { id: 'amazon.titan-embed-text-v2:0', name: 'Titan Embed Text v2', dimension: 1024, configurableDimension: true, minDimension: 256, maxDimension: 1024 },\n { id: 'amazon.titan-embed-text-v1', name: 'Titan Embed Text v1', dimension: 1536 },\n { id: 'cohere.embed-english-v3', name: 'Cohere Embed English v3', dimension: 1024 },\n { id: 'cohere.embed-multilingual-v3', name: 'Cohere Embed Multilingual v3', dimension: 1024 },\n ],\n },\n ollama: {\n name: 'Ollama (Local)',\n envKeyRequired: 'OLLAMA_BASE_URL',\n defaultModel: 'nomic-embed-text',\n models: [\n { id: 'nomic-embed-text', name: 'nomic-embed-text', dimension: 768 },\n { id: 'mxbai-embed-large', name: 'mxbai-embed-large', dimension: 1024 },\n { id: 'all-minilm', name: 'all-minilm', dimension: 384 },\n { id: 'snowflake-arctic-embed', name: 'snowflake-arctic-embed', dimension: 1024 },\n ],\n },\n}\n\nexport const EMBEDDING_CONFIG_KEY = 'embedding_provider'\n\nexport const DEFAULT_EMBEDDING_CONFIG: EmbeddingProviderConfig = {\n providerId: 'openai',\n model: 'text-embedding-3-small',\n dimension: 1536,\n updatedAt: new Date().toISOString(),\n}\n"],
5
+ "mappings": "AAoKO,MAAM,sBAA0E;AAAA,EACrF,QAAQ;AAAA,IACN,MAAM;AAAA,IACN,gBAAgB;AAAA,IAChB,cAAc;AAAA,IACd,QAAQ;AAAA,MACN,EAAE,IAAI,0BAA0B,MAAM,0BAA0B,WAAW,KAAK;AAAA,MAChF,EAAE,IAAI,0BAA0B,MAAM,0BAA0B,WAAW,MAAM,uBAAuB,MAAM,cAAc,KAAK,cAAc,KAAK;AAAA,MACpJ,EAAE,IAAI,0BAA0B,MAAM,0BAA0B,WAAW,KAAK;AAAA,IAClF;AAAA,EACF;AAAA,EACA,QAAQ;AAAA,IACN,MAAM;AAAA,IACN,gBAAgB;AAAA,IAChB,cAAc;AAAA,IACd,QAAQ;AAAA,MACN,EAAE,IAAI,sBAAsB,MAAM,sBAAsB,WAAW,KAAK,uBAAuB,MAAM,cAAc,GAAG,cAAc,IAAI;AAAA,MACxI,EAAE,IAAI,iBAAiB,MAAM,iBAAiB,WAAW,IAAI;AAAA,IAC/D;AAAA,EACF;AAAA,EACA,SAAS;AAAA,IACP,MAAM;AAAA,IACN,gBAAgB;AAAA,IAChB,cAAc;AAAA,IACd,QAAQ;AAAA,MACN,EAAE,IAAI,iBAAiB,MAAM,iBAAiB,WAAW,KAAK;AAAA,IAChE;AAAA,EACF;AAAA,EACA,QAAQ;AAAA,IACN,MAAM;AAAA,IACN,gBAAgB;AAAA,IAChB,cAAc;AAAA,IACd,QAAQ;AAAA,MACN,EAAE,IAAI,sBAAsB,MAAM,sBAAsB,WAAW,KAAK;AAAA,MACxE,EAAE,IAAI,2BAA2B,MAAM,2BAA2B,WAAW,KAAK;AAAA,MAClF,EAAE,IAAI,4BAA4B,MAAM,4BAA4B,WAAW,IAAI;AAAA,MACnF,EAAE,IAAI,iCAAiC,MAAM,iCAAiC,WAAW,IAAI;AAAA,IAC/F;AAAA,EACF;AAAA,EACA,SAAS;AAAA,IACP,MAAM;AAAA,IACN,gBAAgB;AAAA,IAChB,cAAc;AAAA,IACd,QAAQ;AAAA,MACN,EAAE,IAAI,gCAAgC,MAAM,uBAAuB,WAAW,MAAM,uBAAuB,MAAM,cAAc,KAAK,cAAc,KAAK;AAAA,MACvJ,EAAE,IAAI,8BAA8B,MAAM,uBAAuB,WAAW,KAAK;AAAA,MACjF,EAAE,IAAI,2BAA2B,MAAM,2BAA2B,WAAW,KAAK;AAAA,MAClF,EAAE,IAAI,gCAAgC,MAAM,gCAAgC,WAAW,KAAK;AAAA,IAC9F;AAAA,EACF;AAAA,EACA,QAAQ;AAAA,IACN,MAAM;AAAA,IACN,gBAAgB;AAAA,IAChB,cAAc;AAAA,IACd,QAAQ;AAAA,MACN,EAAE,IAAI,oBAAoB,MAAM,oBAAoB,WAAW,IAAI;AAAA,MACnE,EAAE,IAAI,qBAAqB,MAAM,qBAAqB,WAAW,KAAK;AAAA,MACtE,EAAE,IAAI,cAAc,MAAM,cAAc,WAAW,IAAI;AAAA,MACvD,EAAE,IAAI,0BAA0B,MAAM,0BAA0B,WAAW,KAAK;AAAA,IAClF;AAAA,EACF;AACF;AAEO,MAAM,uBAAuB;AAE7B,MAAM,2BAAoD;AAAA,EAC/D,YAAY;AAAA,EACZ,OAAO;AAAA,EACP,WAAW;AAAA,EACX,YAAW,oBAAI,KAAK,GAAE,YAAY;AACpC;",
6
6
  "names": []
7
7
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@open-mercato/search",
3
- "version": "0.6.7",
3
+ "version": "0.6.8-develop.6874.1.982d6097d8",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -116,25 +116,25 @@
116
116
  },
117
117
  "dependencies": {
118
118
  "@ai-sdk/amazon-bedrock": "^5.0.2",
119
- "@ai-sdk/cohere": "^4.0.1",
119
+ "@ai-sdk/cohere": "^4.0.14",
120
120
  "@ai-sdk/google": "^4.0.2",
121
- "@ai-sdk/mistral": "^4.0.1",
121
+ "@ai-sdk/mistral": "^4.0.16",
122
122
  "@ai-sdk/openai": "^4.0.2",
123
123
  "ai": "^7.0.4",
124
- "ai-sdk-ollama": "4.0.0",
125
- "meilisearch": "^0.59.0",
124
+ "ai-sdk-ollama": "4.1.0",
125
+ "meilisearch": "^0.60.0",
126
126
  "pg": "8.22.0",
127
127
  "zod": "^4.4.3"
128
128
  },
129
129
  "peerDependencies": {
130
- "@open-mercato/core": "0.6.7",
131
- "@open-mercato/queue": "0.6.7",
132
- "@open-mercato/shared": "0.6.7"
130
+ "@open-mercato/core": "0.6.8-develop.6874.1.982d6097d8",
131
+ "@open-mercato/queue": "0.6.8-develop.6874.1.982d6097d8",
132
+ "@open-mercato/shared": "0.6.8-develop.6874.1.982d6097d8"
133
133
  },
134
134
  "devDependencies": {
135
135
  "@types/jest": "^30.0.0",
136
136
  "jest": "^30.4.2",
137
- "ts-jest": "^29.4.11",
137
+ "ts-jest": "^29.4.12",
138
138
  "typescript": "7.0.2"
139
139
  },
140
140
  "publishConfig": {
@@ -144,5 +144,6 @@
144
144
  "type": "git",
145
145
  "url": "https://github.com/open-mercato/open-mercato",
146
146
  "directory": "packages/search"
147
- }
147
+ },
148
+ "stableVersion": "0.6.7"
148
149
  }