@open-mercato/search 0.4.11-develop.2502.29119c6047 → 0.4.11-develop.2516.30653cfe18

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,10 +1,19 @@
1
1
  import { MeiliSearch } from "meilisearch";
2
+ import { resolveTimeoutMs } from "@open-mercato/shared/lib/http/fetchWithTimeout";
2
3
  import { extractSearchableFields } from "../../../lib/field-policy.js";
4
+ const DEFAULT_MEILISEARCH_REQUEST_TIMEOUT_MS = 3e4;
5
+ function resolveMeilisearchTimeoutMs(explicit) {
6
+ if (typeof explicit === "number") return resolveTimeoutMs(explicit, DEFAULT_MEILISEARCH_REQUEST_TIMEOUT_MS);
7
+ const raw = process.env.MEILISEARCH_REQUEST_TIMEOUT_MS;
8
+ const parsed = raw ? Number.parseInt(raw, 10) : void 0;
9
+ return resolveTimeoutMs(parsed, DEFAULT_MEILISEARCH_REQUEST_TIMEOUT_MS);
10
+ }
3
11
  function createMeilisearchDriver(options) {
4
12
  const host = options?.host ?? process.env.MEILISEARCH_HOST ?? "";
5
13
  const apiKey = options?.apiKey ?? process.env.MEILISEARCH_API_KEY ?? "";
6
14
  const indexPrefix = options?.indexPrefix ?? process.env.MEILISEARCH_INDEX_PREFIX ?? "om";
7
15
  const defaultLimit = options?.defaultLimit ?? 20;
16
+ const requestTimeoutMs = resolveMeilisearchTimeoutMs(options?.timeoutMs);
8
17
  const encryptionMapResolver = options?.encryptionMapResolver;
9
18
  const fieldPolicyResolver = options?.fieldPolicyResolver;
10
19
  let client = null;
@@ -12,7 +21,7 @@ function createMeilisearchDriver(options) {
12
21
  const initializingIndexes = /* @__PURE__ */ new Map();
13
22
  function getClient() {
14
23
  if (!client) {
15
- client = new MeiliSearch({ host, apiKey });
24
+ client = new MeiliSearch({ host, apiKey, timeout: requestTimeoutMs });
16
25
  }
17
26
  return client;
18
27
  }
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../src/fulltext/drivers/meilisearch/index.ts"],
4
- "sourcesContent": ["import { MeiliSearch } from 'meilisearch'\nimport type { EntityId } from '@open-mercato/shared/modules/entities'\nimport type { SearchFieldPolicy } from '@open-mercato/shared/modules/search'\nimport type {\n FullTextSearchDriver,\n FullTextSearchDocument,\n FullTextSearchQuery,\n FullTextSearchHit,\n DocumentLookupKey,\n IndexStats,\n} from '../../types'\nimport { extractSearchableFields, type EncryptionMapEntry } from '../../../lib/field-policy'\n\n\nexport type MeilisearchDriverOptions = {\n host?: string\n apiKey?: string\n indexPrefix?: string\n defaultLimit?: number\n encryptionMapResolver?: (entityId: EntityId) => Promise<EncryptionMapEntry[]>\n fieldPolicyResolver?: (entityId: EntityId) => SearchFieldPolicy | undefined\n}\n\nexport function createMeilisearchDriver(\n options?: MeilisearchDriverOptions\n): FullTextSearchDriver {\n const host = options?.host ?? process.env.MEILISEARCH_HOST ?? ''\n const apiKey = options?.apiKey ?? process.env.MEILISEARCH_API_KEY ?? ''\n const indexPrefix = options?.indexPrefix ?? process.env.MEILISEARCH_INDEX_PREFIX ?? 'om'\n const defaultLimit = options?.defaultLimit ?? 20\n const encryptionMapResolver = options?.encryptionMapResolver\n const fieldPolicyResolver = options?.fieldPolicyResolver\n\n let client: MeiliSearch | null = null\n const initializedIndexes = new Set<string>()\n const initializingIndexes = new Map<string, Promise<void>>()\n\n function getClient(): MeiliSearch {\n if (!client) {\n client = new MeiliSearch({ host, apiKey })\n }\n return client\n }\n\n function buildIndexName(tenantId: string): string {\n const sanitized = tenantId.replace(/[^a-zA-Z0-9_-]/g, '_')\n return `${indexPrefix}_${sanitized}`\n }\n\n function escapeFilterValue(value: string): string {\n return value.replace(/[\"\\\\]/g, '\\\\$&')\n }\n\n function buildFilters(options: FullTextSearchQuery): string[] {\n const filters: string[] = []\n\n if (options.organizationId) {\n filters.push(`_organizationId = \"${escapeFilterValue(options.organizationId)}\"`)\n }\n\n if (options.entityTypes?.length) {\n const entityFilter = options.entityTypes.map((t) => `\"${escapeFilterValue(t)}\"`).join(', ')\n filters.push(`_entityId IN [${entityFilter}]`)\n }\n\n return filters\n }\n\n async function doEnsureIndex(indexName: string): Promise<void> {\n const meiliClient = getClient()\n\n try {\n await meiliClient.createIndex(indexName, { primaryKey: '_id' })\n } catch (error: unknown) {\n const meilisearchError = error as { code?: string }\n if (meilisearchError.code !== 'index_already_exists') {\n throw error\n }\n }\n\n const index = meiliClient.index(indexName)\n await index.updateSettings({\n searchableAttributes: ['*'],\n filterableAttributes: ['_entityId', '_organizationId'],\n sortableAttributes: ['_indexedAt'],\n typoTolerance: {\n enabled: true,\n minWordSizeForTypos: {\n oneTypo: 4,\n twoTypos: 8,\n },\n },\n })\n\n initializedIndexes.add(indexName)\n }\n\n async function ensureIndex(indexName: string): Promise<void> {\n if (initializedIndexes.has(indexName)) {\n return\n }\n\n const existingPromise = initializingIndexes.get(indexName)\n if (existingPromise) {\n return existingPromise\n }\n\n const initPromise = doEnsureIndex(indexName)\n initializingIndexes.set(indexName, initPromise)\n\n try {\n await initPromise\n } finally {\n initializingIndexes.delete(indexName)\n }\n }\n\n async function prepareDocument(doc: FullTextSearchDocument): Promise<Record<string, unknown>> {\n // When encryptionMapResolver is provided, SEARCH_EXCLUDE_ENCRYPTED_FIELDS is enabled\n const excludeEncrypted = Boolean(encryptionMapResolver)\n const encryptedFields = encryptionMapResolver\n ? await encryptionMapResolver(doc.entityId)\n : []\n const fieldPolicy = fieldPolicyResolver?.(doc.entityId)\n\n const searchableFields = extractSearchableFields(doc.fields, {\n encryptedFields,\n fieldPolicy,\n })\n\n // When SEARCH_EXCLUDE_ENCRYPTED_FIELDS is enabled:\n // - Exclude sensitive parts of presenter (title, subtitle) - these are derived from encrypted fields\n // - Keep non-sensitive parts (icon, badge)\n // - Sanitize link labels (they often contain names derived from encrypted fields)\n // - Title/subtitle/link labels will be enriched at search time from the database\n let presenter = doc.presenter\n let links = doc.links\n if (excludeEncrypted) {\n if (presenter) {\n presenter = {\n ...presenter,\n title: '', // Will be enriched at search time\n subtitle: undefined, // Will be enriched at search time\n }\n }\n // Sanitize link labels - they often contain sensitive data (names, etc.)\n if (links && links.length > 0) {\n links = links.map((link) => ({\n ...link,\n label: link.kind === 'primary' ? 'Open' : 'View', // Generic labels\n }))\n }\n }\n\n return {\n _id: doc.recordId,\n _entityId: doc.entityId,\n _organizationId: doc.organizationId,\n _presenter: presenter,\n _url: doc.url,\n _links: links,\n _indexedAt: new Date().toISOString(),\n ...searchableFields,\n }\n }\n\n const driver: FullTextSearchDriver = {\n id: 'meilisearch',\n\n async ensureReady(): Promise<void> {\n // Client is lazily initialized\n },\n\n async isHealthy(): Promise<boolean> {\n if (!host) {\n return false\n }\n\n try {\n const meiliClient = getClient()\n await meiliClient.health()\n return true\n } catch {\n return false\n }\n },\n\n async search(query: string, options: FullTextSearchQuery): Promise<FullTextSearchHit[]> {\n const meiliClient = getClient()\n const indexName = buildIndexName(options.tenantId)\n\n try {\n const index = meiliClient.index(indexName)\n const filters = buildFilters(options)\n\n const response = await index.search(query, {\n limit: options.limit ?? defaultLimit,\n offset: options.offset,\n filter: filters.length > 0 ? filters.join(' AND ') : undefined,\n showRankingScore: true,\n })\n\n return response.hits.map((hit: Record<string, unknown>) => ({\n recordId: hit._id as string,\n entityId: hit._entityId as EntityId,\n score: (hit._rankingScore as number) ?? 0.5,\n presenter: hit._presenter as FullTextSearchHit['presenter'],\n url: hit._url as string | undefined,\n links: hit._links as FullTextSearchHit['links'],\n metadata: hit._metadata as Record<string, unknown> | undefined,\n }))\n } catch (error: unknown) {\n const meilisearchError = error as { code?: string }\n if (meilisearchError.code === 'index_not_found') {\n return []\n }\n throw error\n }\n },\n\n async index(doc: FullTextSearchDocument): Promise<void> {\n const meiliClient = getClient()\n const indexName = buildIndexName(doc.tenantId)\n\n await ensureIndex(indexName)\n\n const document = await prepareDocument(doc)\n\n const index = meiliClient.index(indexName)\n await index.addDocuments([document], { primaryKey: '_id' })\n },\n\n async delete(recordId: string, tenantId: string): Promise<void> {\n const meiliClient = getClient()\n const indexName = buildIndexName(tenantId)\n\n try {\n const index = meiliClient.index(indexName)\n await index.deleteDocument(recordId)\n } catch (error: unknown) {\n const meilisearchError = error as { code?: string }\n if (meilisearchError.code === 'index_not_found') {\n return\n }\n throw error\n }\n },\n\n async bulkIndex(docs: FullTextSearchDocument[]): Promise<void> {\n if (docs.length === 0) return\n\n // Group documents by tenant\n const byTenant = new Map<string, FullTextSearchDocument[]>()\n for (const doc of docs) {\n const list = byTenant.get(doc.tenantId) ?? []\n list.push(doc)\n byTenant.set(doc.tenantId, list)\n }\n\n const meiliClient = getClient()\n\n for (const [tenantId, tenantDocs] of byTenant) {\n const indexName = buildIndexName(tenantId)\n await ensureIndex(indexName)\n\n const documents = await Promise.all(tenantDocs.map(prepareDocument))\n\n const index = meiliClient.index(indexName)\n await index.addDocuments(documents, { primaryKey: '_id' })\n }\n },\n\n async purge(entityId: EntityId, tenantId: string): Promise<void> {\n const meiliClient = getClient()\n const indexName = buildIndexName(tenantId)\n\n try {\n const index = meiliClient.index(indexName)\n await index.deleteDocuments({\n filter: `_entityId = \"${entityId}\"`,\n })\n } catch (error: unknown) {\n const meilisearchError = error as { code?: string }\n if (meilisearchError.code === 'index_not_found') {\n return\n }\n throw error\n }\n },\n\n async clearIndex(tenantId: string): Promise<void> {\n const meiliClient = getClient()\n const indexName = buildIndexName(tenantId)\n\n try {\n const index = meiliClient.index(indexName)\n await index.deleteAllDocuments()\n } catch (error: unknown) {\n const meilisearchError = error as { code?: string }\n if (meilisearchError.code === 'index_not_found') {\n return\n }\n throw error\n }\n },\n\n async recreateIndex(tenantId: string): Promise<void> {\n const meiliClient = getClient()\n const indexName = buildIndexName(tenantId)\n\n initializedIndexes.delete(indexName)\n\n try {\n await meiliClient.deleteIndex(indexName)\n } catch (error: unknown) {\n const meilisearchError = error as { code?: string }\n if (meilisearchError.code !== 'index_not_found') {\n throw error\n }\n }\n\n await ensureIndex(indexName)\n },\n\n async getDocuments(\n ids: DocumentLookupKey[],\n tenantId: string\n ): Promise<Map<string, FullTextSearchHit>> {\n const result = new Map<string, FullTextSearchHit>()\n if (ids.length === 0) return result\n\n const meiliClient = getClient()\n const indexName = buildIndexName(tenantId)\n\n try {\n const index = meiliClient.index(indexName)\n\n const recordIds = ids.map((id) => id.recordId)\n const documents = await index.getDocuments({\n filter: `_id IN [${recordIds.map((id) => `\"${id}\"`).join(', ')}]`,\n limit: recordIds.length,\n })\n\n for (const doc of documents.results) {\n const hit = doc as Record<string, unknown>\n const key = `${hit._entityId}:${hit._id}`\n result.set(key, {\n recordId: hit._id as string,\n entityId: hit._entityId as EntityId,\n score: 0,\n presenter: hit._presenter as FullTextSearchHit['presenter'],\n url: hit._url as string | undefined,\n links: hit._links as FullTextSearchHit['links'],\n })\n }\n } catch {\n // Index not found or error, return empty map\n }\n\n return result\n },\n\n async getIndexStats(tenantId: string): Promise<IndexStats | null> {\n const meiliClient = getClient()\n const indexName = buildIndexName(tenantId)\n\n try {\n const index = meiliClient.index(indexName)\n const stats = await index.getStats()\n return {\n numberOfDocuments: stats.numberOfDocuments,\n isIndexing: stats.isIndexing,\n fieldDistribution: stats.fieldDistribution,\n }\n } catch (error: unknown) {\n const meilisearchError = error as { code?: string }\n if (meilisearchError.code === 'index_not_found') {\n return null\n }\n throw error\n }\n },\n\n async getEntityCounts(tenantId: string): Promise<Record<string, number> | null> {\n const meiliClient = getClient()\n const indexName = buildIndexName(tenantId)\n\n try {\n const index = meiliClient.index(indexName)\n const searchResult = await index.search('', {\n limit: 0,\n facets: ['_entityId'],\n })\n const facetDistribution = searchResult.facetDistribution?._entityId\n if (!facetDistribution) {\n return {}\n }\n return facetDistribution\n } catch (error: unknown) {\n const meilisearchError = error as { code?: string }\n if (meilisearchError.code === 'index_not_found') {\n return null\n }\n throw error\n }\n },\n }\n\n return driver\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["import { MeiliSearch } from 'meilisearch'\nimport type { EntityId } from '@open-mercato/shared/modules/entities'\nimport type { SearchFieldPolicy } from '@open-mercato/shared/modules/search'\nimport { resolveTimeoutMs } from '@open-mercato/shared/lib/http/fetchWithTimeout'\nimport type {\n FullTextSearchDriver,\n FullTextSearchDocument,\n FullTextSearchQuery,\n FullTextSearchHit,\n DocumentLookupKey,\n IndexStats,\n} from '../../types'\nimport { extractSearchableFields, type EncryptionMapEntry } from '../../../lib/field-policy'\n\n\nexport type MeilisearchDriverOptions = {\n host?: string\n apiKey?: string\n indexPrefix?: string\n defaultLimit?: number\n timeoutMs?: number\n encryptionMapResolver?: (entityId: EntityId) => Promise<EncryptionMapEntry[]>\n fieldPolicyResolver?: (entityId: EntityId) => SearchFieldPolicy | undefined\n}\n\nconst DEFAULT_MEILISEARCH_REQUEST_TIMEOUT_MS = 30_000\n\nfunction resolveMeilisearchTimeoutMs(explicit?: number): number {\n if (typeof explicit === 'number') return resolveTimeoutMs(explicit, DEFAULT_MEILISEARCH_REQUEST_TIMEOUT_MS)\n const raw = process.env.MEILISEARCH_REQUEST_TIMEOUT_MS\n const parsed = raw ? Number.parseInt(raw, 10) : undefined\n return resolveTimeoutMs(parsed, DEFAULT_MEILISEARCH_REQUEST_TIMEOUT_MS)\n}\n\nexport function createMeilisearchDriver(\n options?: MeilisearchDriverOptions\n): FullTextSearchDriver {\n const host = options?.host ?? process.env.MEILISEARCH_HOST ?? ''\n const apiKey = options?.apiKey ?? process.env.MEILISEARCH_API_KEY ?? ''\n const indexPrefix = options?.indexPrefix ?? process.env.MEILISEARCH_INDEX_PREFIX ?? 'om'\n const defaultLimit = options?.defaultLimit ?? 20\n const requestTimeoutMs = resolveMeilisearchTimeoutMs(options?.timeoutMs)\n const encryptionMapResolver = options?.encryptionMapResolver\n const fieldPolicyResolver = options?.fieldPolicyResolver\n\n let client: MeiliSearch | null = null\n const initializedIndexes = new Set<string>()\n const initializingIndexes = new Map<string, Promise<void>>()\n\n function getClient(): MeiliSearch {\n if (!client) {\n client = new MeiliSearch({ host, apiKey, timeout: requestTimeoutMs })\n }\n return client\n }\n\n function buildIndexName(tenantId: string): string {\n const sanitized = tenantId.replace(/[^a-zA-Z0-9_-]/g, '_')\n return `${indexPrefix}_${sanitized}`\n }\n\n function escapeFilterValue(value: string): string {\n return value.replace(/[\"\\\\]/g, '\\\\$&')\n }\n\n function buildFilters(options: FullTextSearchQuery): string[] {\n const filters: string[] = []\n\n if (options.organizationId) {\n filters.push(`_organizationId = \"${escapeFilterValue(options.organizationId)}\"`)\n }\n\n if (options.entityTypes?.length) {\n const entityFilter = options.entityTypes.map((t) => `\"${escapeFilterValue(t)}\"`).join(', ')\n filters.push(`_entityId IN [${entityFilter}]`)\n }\n\n return filters\n }\n\n async function doEnsureIndex(indexName: string): Promise<void> {\n const meiliClient = getClient()\n\n try {\n await meiliClient.createIndex(indexName, { primaryKey: '_id' })\n } catch (error: unknown) {\n const meilisearchError = error as { code?: string }\n if (meilisearchError.code !== 'index_already_exists') {\n throw error\n }\n }\n\n const index = meiliClient.index(indexName)\n await index.updateSettings({\n searchableAttributes: ['*'],\n filterableAttributes: ['_entityId', '_organizationId'],\n sortableAttributes: ['_indexedAt'],\n typoTolerance: {\n enabled: true,\n minWordSizeForTypos: {\n oneTypo: 4,\n twoTypos: 8,\n },\n },\n })\n\n initializedIndexes.add(indexName)\n }\n\n async function ensureIndex(indexName: string): Promise<void> {\n if (initializedIndexes.has(indexName)) {\n return\n }\n\n const existingPromise = initializingIndexes.get(indexName)\n if (existingPromise) {\n return existingPromise\n }\n\n const initPromise = doEnsureIndex(indexName)\n initializingIndexes.set(indexName, initPromise)\n\n try {\n await initPromise\n } finally {\n initializingIndexes.delete(indexName)\n }\n }\n\n async function prepareDocument(doc: FullTextSearchDocument): Promise<Record<string, unknown>> {\n // When encryptionMapResolver is provided, SEARCH_EXCLUDE_ENCRYPTED_FIELDS is enabled\n const excludeEncrypted = Boolean(encryptionMapResolver)\n const encryptedFields = encryptionMapResolver\n ? await encryptionMapResolver(doc.entityId)\n : []\n const fieldPolicy = fieldPolicyResolver?.(doc.entityId)\n\n const searchableFields = extractSearchableFields(doc.fields, {\n encryptedFields,\n fieldPolicy,\n })\n\n // When SEARCH_EXCLUDE_ENCRYPTED_FIELDS is enabled:\n // - Exclude sensitive parts of presenter (title, subtitle) - these are derived from encrypted fields\n // - Keep non-sensitive parts (icon, badge)\n // - Sanitize link labels (they often contain names derived from encrypted fields)\n // - Title/subtitle/link labels will be enriched at search time from the database\n let presenter = doc.presenter\n let links = doc.links\n if (excludeEncrypted) {\n if (presenter) {\n presenter = {\n ...presenter,\n title: '', // Will be enriched at search time\n subtitle: undefined, // Will be enriched at search time\n }\n }\n // Sanitize link labels - they often contain sensitive data (names, etc.)\n if (links && links.length > 0) {\n links = links.map((link) => ({\n ...link,\n label: link.kind === 'primary' ? 'Open' : 'View', // Generic labels\n }))\n }\n }\n\n return {\n _id: doc.recordId,\n _entityId: doc.entityId,\n _organizationId: doc.organizationId,\n _presenter: presenter,\n _url: doc.url,\n _links: links,\n _indexedAt: new Date().toISOString(),\n ...searchableFields,\n }\n }\n\n const driver: FullTextSearchDriver = {\n id: 'meilisearch',\n\n async ensureReady(): Promise<void> {\n // Client is lazily initialized\n },\n\n async isHealthy(): Promise<boolean> {\n if (!host) {\n return false\n }\n\n try {\n const meiliClient = getClient()\n await meiliClient.health()\n return true\n } catch {\n return false\n }\n },\n\n async search(query: string, options: FullTextSearchQuery): Promise<FullTextSearchHit[]> {\n const meiliClient = getClient()\n const indexName = buildIndexName(options.tenantId)\n\n try {\n const index = meiliClient.index(indexName)\n const filters = buildFilters(options)\n\n const response = await index.search(query, {\n limit: options.limit ?? defaultLimit,\n offset: options.offset,\n filter: filters.length > 0 ? filters.join(' AND ') : undefined,\n showRankingScore: true,\n })\n\n return response.hits.map((hit: Record<string, unknown>) => ({\n recordId: hit._id as string,\n entityId: hit._entityId as EntityId,\n score: (hit._rankingScore as number) ?? 0.5,\n presenter: hit._presenter as FullTextSearchHit['presenter'],\n url: hit._url as string | undefined,\n links: hit._links as FullTextSearchHit['links'],\n metadata: hit._metadata as Record<string, unknown> | undefined,\n }))\n } catch (error: unknown) {\n const meilisearchError = error as { code?: string }\n if (meilisearchError.code === 'index_not_found') {\n return []\n }\n throw error\n }\n },\n\n async index(doc: FullTextSearchDocument): Promise<void> {\n const meiliClient = getClient()\n const indexName = buildIndexName(doc.tenantId)\n\n await ensureIndex(indexName)\n\n const document = await prepareDocument(doc)\n\n const index = meiliClient.index(indexName)\n await index.addDocuments([document], { primaryKey: '_id' })\n },\n\n async delete(recordId: string, tenantId: string): Promise<void> {\n const meiliClient = getClient()\n const indexName = buildIndexName(tenantId)\n\n try {\n const index = meiliClient.index(indexName)\n await index.deleteDocument(recordId)\n } catch (error: unknown) {\n const meilisearchError = error as { code?: string }\n if (meilisearchError.code === 'index_not_found') {\n return\n }\n throw error\n }\n },\n\n async bulkIndex(docs: FullTextSearchDocument[]): Promise<void> {\n if (docs.length === 0) return\n\n // Group documents by tenant\n const byTenant = new Map<string, FullTextSearchDocument[]>()\n for (const doc of docs) {\n const list = byTenant.get(doc.tenantId) ?? []\n list.push(doc)\n byTenant.set(doc.tenantId, list)\n }\n\n const meiliClient = getClient()\n\n for (const [tenantId, tenantDocs] of byTenant) {\n const indexName = buildIndexName(tenantId)\n await ensureIndex(indexName)\n\n const documents = await Promise.all(tenantDocs.map(prepareDocument))\n\n const index = meiliClient.index(indexName)\n await index.addDocuments(documents, { primaryKey: '_id' })\n }\n },\n\n async purge(entityId: EntityId, tenantId: string): Promise<void> {\n const meiliClient = getClient()\n const indexName = buildIndexName(tenantId)\n\n try {\n const index = meiliClient.index(indexName)\n await index.deleteDocuments({\n filter: `_entityId = \"${entityId}\"`,\n })\n } catch (error: unknown) {\n const meilisearchError = error as { code?: string }\n if (meilisearchError.code === 'index_not_found') {\n return\n }\n throw error\n }\n },\n\n async clearIndex(tenantId: string): Promise<void> {\n const meiliClient = getClient()\n const indexName = buildIndexName(tenantId)\n\n try {\n const index = meiliClient.index(indexName)\n await index.deleteAllDocuments()\n } catch (error: unknown) {\n const meilisearchError = error as { code?: string }\n if (meilisearchError.code === 'index_not_found') {\n return\n }\n throw error\n }\n },\n\n async recreateIndex(tenantId: string): Promise<void> {\n const meiliClient = getClient()\n const indexName = buildIndexName(tenantId)\n\n initializedIndexes.delete(indexName)\n\n try {\n await meiliClient.deleteIndex(indexName)\n } catch (error: unknown) {\n const meilisearchError = error as { code?: string }\n if (meilisearchError.code !== 'index_not_found') {\n throw error\n }\n }\n\n await ensureIndex(indexName)\n },\n\n async getDocuments(\n ids: DocumentLookupKey[],\n tenantId: string\n ): Promise<Map<string, FullTextSearchHit>> {\n const result = new Map<string, FullTextSearchHit>()\n if (ids.length === 0) return result\n\n const meiliClient = getClient()\n const indexName = buildIndexName(tenantId)\n\n try {\n const index = meiliClient.index(indexName)\n\n const recordIds = ids.map((id) => id.recordId)\n const documents = await index.getDocuments({\n filter: `_id IN [${recordIds.map((id) => `\"${id}\"`).join(', ')}]`,\n limit: recordIds.length,\n })\n\n for (const doc of documents.results) {\n const hit = doc as Record<string, unknown>\n const key = `${hit._entityId}:${hit._id}`\n result.set(key, {\n recordId: hit._id as string,\n entityId: hit._entityId as EntityId,\n score: 0,\n presenter: hit._presenter as FullTextSearchHit['presenter'],\n url: hit._url as string | undefined,\n links: hit._links as FullTextSearchHit['links'],\n })\n }\n } catch {\n // Index not found or error, return empty map\n }\n\n return result\n },\n\n async getIndexStats(tenantId: string): Promise<IndexStats | null> {\n const meiliClient = getClient()\n const indexName = buildIndexName(tenantId)\n\n try {\n const index = meiliClient.index(indexName)\n const stats = await index.getStats()\n return {\n numberOfDocuments: stats.numberOfDocuments,\n isIndexing: stats.isIndexing,\n fieldDistribution: stats.fieldDistribution,\n }\n } catch (error: unknown) {\n const meilisearchError = error as { code?: string }\n if (meilisearchError.code === 'index_not_found') {\n return null\n }\n throw error\n }\n },\n\n async getEntityCounts(tenantId: string): Promise<Record<string, number> | null> {\n const meiliClient = getClient()\n const indexName = buildIndexName(tenantId)\n\n try {\n const index = meiliClient.index(indexName)\n const searchResult = await index.search('', {\n limit: 0,\n facets: ['_entityId'],\n })\n const facetDistribution = searchResult.facetDistribution?._entityId\n if (!facetDistribution) {\n return {}\n }\n return facetDistribution\n } catch (error: unknown) {\n const meilisearchError = error as { code?: string }\n if (meilisearchError.code === 'index_not_found') {\n return null\n }\n throw error\n }\n },\n }\n\n return driver\n}\n"],
5
+ "mappings": "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",
6
6
  "names": ["options"]
7
7
  }
@@ -187,6 +187,7 @@ class EmbeddingService {
187
187
  const model = this.getEmbeddingModel();
188
188
  const providerOptions = this.getProviderOptions();
189
189
  const timeoutMs = resolveEmbeddingTimeoutMs();
190
+ let timeoutHandle = null;
190
191
  try {
191
192
  const result = await Promise.race([
192
193
  embed({
@@ -195,7 +196,10 @@ class EmbeddingService {
195
196
  ...providerOptions && { providerOptions }
196
197
  }),
197
198
  new Promise((_, reject) => {
198
- setTimeout(() => reject(timeoutError(this.config.providerId, timeoutMs)), timeoutMs);
199
+ timeoutHandle = setTimeout(
200
+ () => reject(timeoutError(this.config.providerId, timeoutMs)),
201
+ timeoutMs
202
+ );
199
203
  })
200
204
  ]);
201
205
  const emb = Array.isArray(result.embedding) ? result.embedding : Array.from(result.embedding);
@@ -235,6 +239,10 @@ class EmbeddingService {
235
239
  }
236
240
  wrapped.cause = err;
237
241
  throw wrapped;
242
+ } finally {
243
+ if (timeoutHandle !== null) {
244
+ clearTimeout(timeoutHandle);
245
+ }
238
246
  }
239
247
  }
240
248
  }
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../src/vector/services/embedding.ts"],
4
- "sourcesContent": ["import { embed } from 'ai'\nimport type { EmbeddingModel } from 'ai'\nimport { createOpenAI } from '@ai-sdk/openai'\n\n// Local type definition to avoid @ai-sdk/provider version conflicts\n// Matches SharedV3ProviderOptions = Record<string, JSONObject>\ntype JSONValue = string | number | boolean | null | JSONValue[] | { [key: string]: JSONValue }\ntype JSONObject = { [key: string]: JSONValue }\ntype ProviderOptions = Record<string, JSONObject>\nimport { createGoogleGenerativeAI } from '@ai-sdk/google'\nimport { createMistral } from '@ai-sdk/mistral'\nimport { createCohere } from '@ai-sdk/cohere'\nimport { createAmazonBedrock } from '@ai-sdk/amazon-bedrock'\nimport { createOllama } from 'ai-sdk-ollama'\nimport type { EmbeddingProviderId, EmbeddingProviderConfig } from '../types'\nimport { EMBEDDING_PROVIDERS, DEFAULT_EMBEDDING_CONFIG } from '../types'\n\nexport type EmbeddingServiceOptions = {\n apiKey?: string\n model?: string\n config?: EmbeddingProviderConfig\n}\n\ntype OllamaClient = ReturnType<typeof createOllama>\n\ntype ProviderClient = ReturnType<typeof createOpenAI>\n | ReturnType<typeof createGoogleGenerativeAI>\n | ReturnType<typeof createMistral>\n | ReturnType<typeof createCohere>\n | ReturnType<typeof createAmazonBedrock>\n | OllamaClient\n\nconst DEFAULT_EMBEDDING_TIMEOUT_MS = 3_000\n\nfunction resolveEmbeddingTimeoutMs(): number {\n const rawValue = process.env.VECTOR_EMBEDDING_TIMEOUT_MS\n if (!rawValue) return DEFAULT_EMBEDDING_TIMEOUT_MS\n const parsed = Number.parseInt(rawValue, 10)\n if (!Number.isFinite(parsed) || parsed <= 0) {\n return DEFAULT_EMBEDDING_TIMEOUT_MS\n }\n return parsed\n}\n\nfunction timeoutError(providerId: EmbeddingProviderId, timeoutMs: number): Error {\n const providerInfo = EMBEDDING_PROVIDERS[providerId]\n return new Error(\n `${providerInfo.name} request timed out after ${timeoutMs}ms. Check ${providerInfo.envKeyRequired}.`,\n )\n}\n\nexport class EmbeddingService {\n private config: EmbeddingProviderConfig\n private clientCache: Map<EmbeddingProviderId, ProviderClient> = new Map()\n\n constructor(private readonly opts: EmbeddingServiceOptions = {}) {\n if (opts.config) {\n this.config = opts.config\n } else {\n this.config = {\n providerId: 'openai',\n model: opts.model ?? DEFAULT_EMBEDDING_CONFIG.model,\n dimension: DEFAULT_EMBEDDING_CONFIG.dimension,\n updatedAt: new Date().toISOString(),\n }\n }\n }\n\n updateConfig(config: EmbeddingProviderConfig): void {\n this.config = config\n this.clientCache.clear()\n }\n\n get currentConfig(): EmbeddingProviderConfig {\n return { ...this.config }\n }\n\n get dimension(): number {\n return this.config.outputDimensionality ?? this.config.dimension\n }\n\n get available(): boolean {\n return this.isProviderConfigured(this.config.providerId)\n }\n\n private isProviderConfigured(providerId: EmbeddingProviderId): boolean {\n switch (providerId) {\n case 'openai':\n return Boolean(this.opts.apiKey ?? process.env.OPENAI_API_KEY)\n case 'google':\n return Boolean(process.env.GOOGLE_GENERATIVE_AI_API_KEY)\n case 'mistral':\n return Boolean(process.env.MISTRAL_API_KEY)\n case 'cohere':\n return Boolean(process.env.COHERE_API_KEY)\n case 'bedrock':\n return Boolean(process.env.AWS_ACCESS_KEY_ID && process.env.AWS_SECRET_ACCESS_KEY)\n case 'ollama':\n return true\n default:\n return false\n }\n }\n\n private getClient(providerId: EmbeddingProviderId): ProviderClient {\n const cached = this.clientCache.get(providerId)\n if (cached) {\n return cached\n }\n\n let client: ProviderClient\n switch (providerId) {\n case 'openai': {\n const apiKey = this.opts.apiKey ?? process.env.OPENAI_API_KEY\n if (!apiKey) {\n throw new Error('[vector.embedding] Missing OPENAI_API_KEY environment variable')\n }\n client = createOpenAI({ apiKey })\n break\n }\n case 'google': {\n const apiKey = process.env.GOOGLE_GENERATIVE_AI_API_KEY\n if (!apiKey) {\n throw new Error('[vector.embedding] Missing GOOGLE_GENERATIVE_AI_API_KEY environment variable')\n }\n client = createGoogleGenerativeAI({ apiKey })\n break\n }\n case 'mistral': {\n const apiKey = process.env.MISTRAL_API_KEY\n if (!apiKey) {\n throw new Error('[vector.embedding] Missing MISTRAL_API_KEY environment variable')\n }\n client = createMistral({ apiKey })\n break\n }\n case 'cohere': {\n const apiKey = process.env.COHERE_API_KEY\n if (!apiKey) {\n throw new Error('[vector.embedding] Missing COHERE_API_KEY environment variable')\n }\n client = createCohere({ apiKey })\n break\n }\n case 'bedrock': {\n const accessKeyId = process.env.AWS_ACCESS_KEY_ID\n const secretAccessKey = process.env.AWS_SECRET_ACCESS_KEY\n if (!accessKeyId || !secretAccessKey) {\n throw new Error('[vector.embedding] Missing AWS_ACCESS_KEY_ID or AWS_SECRET_ACCESS_KEY environment variables')\n }\n client = createAmazonBedrock({\n accessKeyId,\n secretAccessKey,\n region: process.env.AWS_REGION ?? 'us-east-1',\n })\n break\n }\n case 'ollama': {\n const baseURL = this.config.baseUrl ?? process.env.OLLAMA_BASE_URL ?? 'http://localhost:11434'\n client = createOllama({ baseURL })\n break\n }\n default:\n throw new Error(`[vector.embedding] Unknown provider: ${providerId}`)\n }\n\n this.clientCache.set(providerId, client)\n return client\n }\n\n private getEmbeddingModel() {\n const client = this.getClient(this.config.providerId)\n const { providerId, model, outputDimensionality } = this.config\n\n switch (providerId) {\n case 'openai':\n return (client as ReturnType<typeof createOpenAI>).embedding(model)\n case 'google':\n return (client as ReturnType<typeof createGoogleGenerativeAI>).textEmbeddingModel(model)\n case 'mistral':\n return (client as ReturnType<typeof createMistral>).textEmbeddingModel(model)\n case 'cohere':\n return (client as ReturnType<typeof createCohere>).textEmbeddingModel(model)\n case 'bedrock':\n return (client as ReturnType<typeof createAmazonBedrock>).embedding(model)\n case 'ollama':\n return (client as OllamaClient).embedding(model)\n default:\n throw new Error(`[vector.embedding] Unknown provider: ${providerId}`)\n }\n }\n\n private getProviderOptions(): ProviderOptions | undefined {\n const { providerId, outputDimensionality, model } = this.config\n\n if (!outputDimensionality) {\n if (providerId === 'cohere') {\n return { cohere: { inputType: 'search_document' } }\n }\n return undefined\n }\n\n switch (providerId) {\n case 'openai':\n if (model === 'text-embedding-3-large' || model === 'text-embedding-3-small') {\n return { openai: { dimensions: outputDimensionality } }\n }\n return undefined\n case 'google':\n return { google: { outputDimensionality } }\n case 'bedrock':\n return { bedrock: { dimensions: outputDimensionality } }\n case 'cohere':\n return { cohere: { inputType: 'search_document' } }\n default:\n return undefined\n }\n }\n\n async createEmbedding(input: string | string[]): Promise<number[]> {\n const merged = Array.isArray(input)\n ? input.map((part) => String(part ?? '')).filter((part) => part.length > 0).join('\\n\\n')\n : String(input ?? '')\n if (!merged.length) {\n throw new Error('[vector.embedding] Refusing to embed empty payload')\n }\n\n if (!this.available) {\n const providerInfo = EMBEDDING_PROVIDERS[this.config.providerId]\n throw new Error(`[vector.embedding] Provider ${providerInfo.name} is not configured. Set ${providerInfo.envKeyRequired} environment variable.`)\n }\n\n const model = this.getEmbeddingModel() as EmbeddingModel\n const providerOptions = this.getProviderOptions()\n const timeoutMs = resolveEmbeddingTimeoutMs()\n\n try {\n const result = await Promise.race([\n embed({\n model,\n value: merged,\n ...(providerOptions && { providerOptions }),\n }),\n new Promise<never>((_, reject) => {\n setTimeout(() => reject(timeoutError(this.config.providerId, timeoutMs)), timeoutMs)\n }),\n ])\n const emb = Array.isArray(result.embedding)\n ? result.embedding\n : Array.from(result.embedding as ArrayLike<number>)\n return emb.map((n) => Number.isFinite(n) ? Number(n) : 0)\n } catch (err: unknown) {\n const error = err as { statusCode?: number; status?: number; response?: { status?: number; statusCode?: number; data?: { error?: { message?: string; code?: string }; message?: string } }; data?: { error?: { message?: string; code?: string } }; body?: { error?: { message?: string; code?: string } }; message?: string }\n const statusCandidate =\n error?.statusCode ?? error?.status ?? error?.response?.status ?? error?.response?.statusCode\n const status =\n typeof statusCandidate === 'number'\n ? Number.isFinite(statusCandidate) ? statusCandidate : undefined\n : typeof statusCandidate === 'string'\n ? Number.parseInt(statusCandidate, 10)\n : undefined\n const apiError = error?.data?.error ?? error?.body?.error ?? error?.response?.data?.error\n const apiMessage = apiError?.message ?? error?.response?.data?.message\n const apiCode = typeof apiError?.code === 'string' ? apiError.code : undefined\n const rawMessage = typeof apiMessage === 'string'\n ? apiMessage\n : (typeof error?.message === 'string' ? error.message : 'Embedding request failed')\n\n const providerInfo = EMBEDDING_PROVIDERS[this.config.providerId]\n let guidance: string\n switch (apiCode) {\n case 'insufficient_quota':\n guidance = `${providerInfo.name} usage quota exceeded. Please review your plan and billing.`\n break\n case 'invalid_api_key':\n guidance = `Invalid ${providerInfo.name} API key. Update the key and retry.`\n break\n case 'account_deactivated':\n guidance = `${providerInfo.name} account is disabled. Contact support or provide a different key.`\n break\n default:\n guidance = rawMessage.startsWith('[vector.embedding] ')\n ? rawMessage.slice('[vector.embedding] '.length)\n : rawMessage.includes('https://')\n ? rawMessage\n : rawMessage.includes(providerInfo.envKeyRequired)\n ? rawMessage\n : `${rawMessage}. Check ${providerInfo.envKeyRequired}.`\n }\n const wrapped = new Error(`[vector.embedding] ${guidance}`) as Error & { status?: number; code?: string; cause?: unknown }\n if (typeof status === 'number' && Number.isFinite(status)) {\n const normalizedStatus = status === 401 || status === 403 ? 502 : status\n if (normalizedStatus >= 400 && normalizedStatus < 600) {\n wrapped.status = normalizedStatus\n }\n }\n if (apiCode) {\n wrapped.code = apiCode\n }\n wrapped.cause = err\n throw wrapped\n }\n }\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["import { embed } from 'ai'\nimport type { EmbeddingModel } from 'ai'\nimport { createOpenAI } from '@ai-sdk/openai'\n\n// Local type definition to avoid @ai-sdk/provider version conflicts\n// Matches SharedV3ProviderOptions = Record<string, JSONObject>\ntype JSONValue = string | number | boolean | null | JSONValue[] | { [key: string]: JSONValue }\ntype JSONObject = { [key: string]: JSONValue }\ntype ProviderOptions = Record<string, JSONObject>\nimport { createGoogleGenerativeAI } from '@ai-sdk/google'\nimport { createMistral } from '@ai-sdk/mistral'\nimport { createCohere } from '@ai-sdk/cohere'\nimport { createAmazonBedrock } from '@ai-sdk/amazon-bedrock'\nimport { createOllama } from 'ai-sdk-ollama'\nimport type { EmbeddingProviderId, EmbeddingProviderConfig } from '../types'\nimport { EMBEDDING_PROVIDERS, DEFAULT_EMBEDDING_CONFIG } from '../types'\n\nexport type EmbeddingServiceOptions = {\n apiKey?: string\n model?: string\n config?: EmbeddingProviderConfig\n}\n\ntype OllamaClient = ReturnType<typeof createOllama>\n\ntype ProviderClient = ReturnType<typeof createOpenAI>\n | ReturnType<typeof createGoogleGenerativeAI>\n | ReturnType<typeof createMistral>\n | ReturnType<typeof createCohere>\n | ReturnType<typeof createAmazonBedrock>\n | OllamaClient\n\nconst DEFAULT_EMBEDDING_TIMEOUT_MS = 3_000\n\nfunction resolveEmbeddingTimeoutMs(): number {\n const rawValue = process.env.VECTOR_EMBEDDING_TIMEOUT_MS\n if (!rawValue) return DEFAULT_EMBEDDING_TIMEOUT_MS\n const parsed = Number.parseInt(rawValue, 10)\n if (!Number.isFinite(parsed) || parsed <= 0) {\n return DEFAULT_EMBEDDING_TIMEOUT_MS\n }\n return parsed\n}\n\nfunction timeoutError(providerId: EmbeddingProviderId, timeoutMs: number): Error {\n const providerInfo = EMBEDDING_PROVIDERS[providerId]\n return new Error(\n `${providerInfo.name} request timed out after ${timeoutMs}ms. Check ${providerInfo.envKeyRequired}.`,\n )\n}\n\nexport class EmbeddingService {\n private config: EmbeddingProviderConfig\n private clientCache: Map<EmbeddingProviderId, ProviderClient> = new Map()\n\n constructor(private readonly opts: EmbeddingServiceOptions = {}) {\n if (opts.config) {\n this.config = opts.config\n } else {\n this.config = {\n providerId: 'openai',\n model: opts.model ?? DEFAULT_EMBEDDING_CONFIG.model,\n dimension: DEFAULT_EMBEDDING_CONFIG.dimension,\n updatedAt: new Date().toISOString(),\n }\n }\n }\n\n updateConfig(config: EmbeddingProviderConfig): void {\n this.config = config\n this.clientCache.clear()\n }\n\n get currentConfig(): EmbeddingProviderConfig {\n return { ...this.config }\n }\n\n get dimension(): number {\n return this.config.outputDimensionality ?? this.config.dimension\n }\n\n get available(): boolean {\n return this.isProviderConfigured(this.config.providerId)\n }\n\n private isProviderConfigured(providerId: EmbeddingProviderId): boolean {\n switch (providerId) {\n case 'openai':\n return Boolean(this.opts.apiKey ?? process.env.OPENAI_API_KEY)\n case 'google':\n return Boolean(process.env.GOOGLE_GENERATIVE_AI_API_KEY)\n case 'mistral':\n return Boolean(process.env.MISTRAL_API_KEY)\n case 'cohere':\n return Boolean(process.env.COHERE_API_KEY)\n case 'bedrock':\n return Boolean(process.env.AWS_ACCESS_KEY_ID && process.env.AWS_SECRET_ACCESS_KEY)\n case 'ollama':\n return true\n default:\n return false\n }\n }\n\n private getClient(providerId: EmbeddingProviderId): ProviderClient {\n const cached = this.clientCache.get(providerId)\n if (cached) {\n return cached\n }\n\n let client: ProviderClient\n switch (providerId) {\n case 'openai': {\n const apiKey = this.opts.apiKey ?? process.env.OPENAI_API_KEY\n if (!apiKey) {\n throw new Error('[vector.embedding] Missing OPENAI_API_KEY environment variable')\n }\n client = createOpenAI({ apiKey })\n break\n }\n case 'google': {\n const apiKey = process.env.GOOGLE_GENERATIVE_AI_API_KEY\n if (!apiKey) {\n throw new Error('[vector.embedding] Missing GOOGLE_GENERATIVE_AI_API_KEY environment variable')\n }\n client = createGoogleGenerativeAI({ apiKey })\n break\n }\n case 'mistral': {\n const apiKey = process.env.MISTRAL_API_KEY\n if (!apiKey) {\n throw new Error('[vector.embedding] Missing MISTRAL_API_KEY environment variable')\n }\n client = createMistral({ apiKey })\n break\n }\n case 'cohere': {\n const apiKey = process.env.COHERE_API_KEY\n if (!apiKey) {\n throw new Error('[vector.embedding] Missing COHERE_API_KEY environment variable')\n }\n client = createCohere({ apiKey })\n break\n }\n case 'bedrock': {\n const accessKeyId = process.env.AWS_ACCESS_KEY_ID\n const secretAccessKey = process.env.AWS_SECRET_ACCESS_KEY\n if (!accessKeyId || !secretAccessKey) {\n throw new Error('[vector.embedding] Missing AWS_ACCESS_KEY_ID or AWS_SECRET_ACCESS_KEY environment variables')\n }\n client = createAmazonBedrock({\n accessKeyId,\n secretAccessKey,\n region: process.env.AWS_REGION ?? 'us-east-1',\n })\n break\n }\n case 'ollama': {\n const baseURL = this.config.baseUrl ?? process.env.OLLAMA_BASE_URL ?? 'http://localhost:11434'\n client = createOllama({ baseURL })\n break\n }\n default:\n throw new Error(`[vector.embedding] Unknown provider: ${providerId}`)\n }\n\n this.clientCache.set(providerId, client)\n return client\n }\n\n private getEmbeddingModel() {\n const client = this.getClient(this.config.providerId)\n const { providerId, model, outputDimensionality } = this.config\n\n switch (providerId) {\n case 'openai':\n return (client as ReturnType<typeof createOpenAI>).embedding(model)\n case 'google':\n return (client as ReturnType<typeof createGoogleGenerativeAI>).textEmbeddingModel(model)\n case 'mistral':\n return (client as ReturnType<typeof createMistral>).textEmbeddingModel(model)\n case 'cohere':\n return (client as ReturnType<typeof createCohere>).textEmbeddingModel(model)\n case 'bedrock':\n return (client as ReturnType<typeof createAmazonBedrock>).embedding(model)\n case 'ollama':\n return (client as OllamaClient).embedding(model)\n default:\n throw new Error(`[vector.embedding] Unknown provider: ${providerId}`)\n }\n }\n\n private getProviderOptions(): ProviderOptions | undefined {\n const { providerId, outputDimensionality, model } = this.config\n\n if (!outputDimensionality) {\n if (providerId === 'cohere') {\n return { cohere: { inputType: 'search_document' } }\n }\n return undefined\n }\n\n switch (providerId) {\n case 'openai':\n if (model === 'text-embedding-3-large' || model === 'text-embedding-3-small') {\n return { openai: { dimensions: outputDimensionality } }\n }\n return undefined\n case 'google':\n return { google: { outputDimensionality } }\n case 'bedrock':\n return { bedrock: { dimensions: outputDimensionality } }\n case 'cohere':\n return { cohere: { inputType: 'search_document' } }\n default:\n return undefined\n }\n }\n\n async createEmbedding(input: string | string[]): Promise<number[]> {\n const merged = Array.isArray(input)\n ? input.map((part) => String(part ?? '')).filter((part) => part.length > 0).join('\\n\\n')\n : String(input ?? '')\n if (!merged.length) {\n throw new Error('[vector.embedding] Refusing to embed empty payload')\n }\n\n if (!this.available) {\n const providerInfo = EMBEDDING_PROVIDERS[this.config.providerId]\n throw new Error(`[vector.embedding] Provider ${providerInfo.name} is not configured. Set ${providerInfo.envKeyRequired} environment variable.`)\n }\n\n const model = this.getEmbeddingModel() as EmbeddingModel\n const providerOptions = this.getProviderOptions()\n const timeoutMs = resolveEmbeddingTimeoutMs()\n\n let timeoutHandle: ReturnType<typeof setTimeout> | null = null\n try {\n const result = await Promise.race([\n embed({\n model,\n value: merged,\n ...(providerOptions && { providerOptions }),\n }),\n new Promise<never>((_, reject) => {\n timeoutHandle = setTimeout(\n () => reject(timeoutError(this.config.providerId, timeoutMs)),\n timeoutMs,\n )\n }),\n ])\n const emb = Array.isArray(result.embedding)\n ? result.embedding\n : Array.from(result.embedding as ArrayLike<number>)\n return emb.map((n) => Number.isFinite(n) ? Number(n) : 0)\n } catch (err: unknown) {\n const error = err as { statusCode?: number; status?: number; response?: { status?: number; statusCode?: number; data?: { error?: { message?: string; code?: string }; message?: string } }; data?: { error?: { message?: string; code?: string } }; body?: { error?: { message?: string; code?: string } }; message?: string }\n const statusCandidate =\n error?.statusCode ?? error?.status ?? error?.response?.status ?? error?.response?.statusCode\n const status =\n typeof statusCandidate === 'number'\n ? Number.isFinite(statusCandidate) ? statusCandidate : undefined\n : typeof statusCandidate === 'string'\n ? Number.parseInt(statusCandidate, 10)\n : undefined\n const apiError = error?.data?.error ?? error?.body?.error ?? error?.response?.data?.error\n const apiMessage = apiError?.message ?? error?.response?.data?.message\n const apiCode = typeof apiError?.code === 'string' ? apiError.code : undefined\n const rawMessage = typeof apiMessage === 'string'\n ? apiMessage\n : (typeof error?.message === 'string' ? error.message : 'Embedding request failed')\n\n const providerInfo = EMBEDDING_PROVIDERS[this.config.providerId]\n let guidance: string\n switch (apiCode) {\n case 'insufficient_quota':\n guidance = `${providerInfo.name} usage quota exceeded. Please review your plan and billing.`\n break\n case 'invalid_api_key':\n guidance = `Invalid ${providerInfo.name} API key. Update the key and retry.`\n break\n case 'account_deactivated':\n guidance = `${providerInfo.name} account is disabled. Contact support or provide a different key.`\n break\n default:\n guidance = rawMessage.startsWith('[vector.embedding] ')\n ? rawMessage.slice('[vector.embedding] '.length)\n : rawMessage.includes('https://')\n ? rawMessage\n : rawMessage.includes(providerInfo.envKeyRequired)\n ? rawMessage\n : `${rawMessage}. Check ${providerInfo.envKeyRequired}.`\n }\n const wrapped = new Error(`[vector.embedding] ${guidance}`) as Error & { status?: number; code?: string; cause?: unknown }\n if (typeof status === 'number' && Number.isFinite(status)) {\n const normalizedStatus = status === 401 || status === 403 ? 502 : status\n if (normalizedStatus >= 400 && normalizedStatus < 600) {\n wrapped.status = normalizedStatus\n }\n }\n if (apiCode) {\n wrapped.code = apiCode\n }\n wrapped.cause = err\n throw wrapped\n } finally {\n if (timeoutHandle !== null) {\n clearTimeout(timeoutHandle)\n }\n }\n }\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@open-mercato/search",
3
- "version": "0.4.11-develop.2502.29119c6047",
3
+ "version": "0.4.11-develop.2516.30653cfe18",
4
4
  "type": "module",
5
5
  "main": "./dist/index.js",
6
6
  "exports": {
@@ -126,9 +126,9 @@
126
126
  "zod": "^4.0.0"
127
127
  },
128
128
  "peerDependencies": {
129
- "@open-mercato/core": "0.4.11-develop.2502.29119c6047",
130
- "@open-mercato/queue": "0.4.11-develop.2502.29119c6047",
131
- "@open-mercato/shared": "0.4.11-develop.2502.29119c6047"
129
+ "@open-mercato/core": "0.4.11-develop.2516.30653cfe18",
130
+ "@open-mercato/queue": "0.4.11-develop.2516.30653cfe18",
131
+ "@open-mercato/shared": "0.4.11-develop.2516.30653cfe18"
132
132
  },
133
133
  "devDependencies": {
134
134
  "@types/jest": "^30.0.0",
@@ -1,6 +1,7 @@
1
1
  import { MeiliSearch } from 'meilisearch'
2
2
  import type { EntityId } from '@open-mercato/shared/modules/entities'
3
3
  import type { SearchFieldPolicy } from '@open-mercato/shared/modules/search'
4
+ import { resolveTimeoutMs } from '@open-mercato/shared/lib/http/fetchWithTimeout'
4
5
  import type {
5
6
  FullTextSearchDriver,
6
7
  FullTextSearchDocument,
@@ -17,10 +18,20 @@ export type MeilisearchDriverOptions = {
17
18
  apiKey?: string
18
19
  indexPrefix?: string
19
20
  defaultLimit?: number
21
+ timeoutMs?: number
20
22
  encryptionMapResolver?: (entityId: EntityId) => Promise<EncryptionMapEntry[]>
21
23
  fieldPolicyResolver?: (entityId: EntityId) => SearchFieldPolicy | undefined
22
24
  }
23
25
 
26
+ const DEFAULT_MEILISEARCH_REQUEST_TIMEOUT_MS = 30_000
27
+
28
+ function resolveMeilisearchTimeoutMs(explicit?: number): number {
29
+ if (typeof explicit === 'number') return resolveTimeoutMs(explicit, DEFAULT_MEILISEARCH_REQUEST_TIMEOUT_MS)
30
+ const raw = process.env.MEILISEARCH_REQUEST_TIMEOUT_MS
31
+ const parsed = raw ? Number.parseInt(raw, 10) : undefined
32
+ return resolveTimeoutMs(parsed, DEFAULT_MEILISEARCH_REQUEST_TIMEOUT_MS)
33
+ }
34
+
24
35
  export function createMeilisearchDriver(
25
36
  options?: MeilisearchDriverOptions
26
37
  ): FullTextSearchDriver {
@@ -28,6 +39,7 @@ export function createMeilisearchDriver(
28
39
  const apiKey = options?.apiKey ?? process.env.MEILISEARCH_API_KEY ?? ''
29
40
  const indexPrefix = options?.indexPrefix ?? process.env.MEILISEARCH_INDEX_PREFIX ?? 'om'
30
41
  const defaultLimit = options?.defaultLimit ?? 20
42
+ const requestTimeoutMs = resolveMeilisearchTimeoutMs(options?.timeoutMs)
31
43
  const encryptionMapResolver = options?.encryptionMapResolver
32
44
  const fieldPolicyResolver = options?.fieldPolicyResolver
33
45
 
@@ -37,7 +49,7 @@ export function createMeilisearchDriver(
37
49
 
38
50
  function getClient(): MeiliSearch {
39
51
  if (!client) {
40
- client = new MeiliSearch({ host, apiKey })
52
+ client = new MeiliSearch({ host, apiKey, timeout: requestTimeoutMs })
41
53
  }
42
54
  return client
43
55
  }
@@ -234,6 +234,7 @@ export class EmbeddingService {
234
234
  const providerOptions = this.getProviderOptions()
235
235
  const timeoutMs = resolveEmbeddingTimeoutMs()
236
236
 
237
+ let timeoutHandle: ReturnType<typeof setTimeout> | null = null
237
238
  try {
238
239
  const result = await Promise.race([
239
240
  embed({
@@ -242,7 +243,10 @@ export class EmbeddingService {
242
243
  ...(providerOptions && { providerOptions }),
243
244
  }),
244
245
  new Promise<never>((_, reject) => {
245
- setTimeout(() => reject(timeoutError(this.config.providerId, timeoutMs)), timeoutMs)
246
+ timeoutHandle = setTimeout(
247
+ () => reject(timeoutError(this.config.providerId, timeoutMs)),
248
+ timeoutMs,
249
+ )
246
250
  }),
247
251
  ])
248
252
  const emb = Array.isArray(result.embedding)
@@ -299,6 +303,10 @@ export class EmbeddingService {
299
303
  }
300
304
  wrapped.cause = err
301
305
  throw wrapped
306
+ } finally {
307
+ if (timeoutHandle !== null) {
308
+ clearTimeout(timeoutHandle)
309
+ }
302
310
  }
303
311
  }
304
312
  }