@open-mercato/search 0.4.11-develop.2384.32517eccbc → 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.
- package/dist/fulltext/drivers/meilisearch/index.js +10 -1
- package/dist/fulltext/drivers/meilisearch/index.js.map +2 -2
- package/dist/vector/services/embedding.js +9 -1
- package/dist/vector/services/embedding.js.map +2 -2
- package/package.json +4 -4
- package/src/fulltext/drivers/meilisearch/index.ts +13 -1
- package/src/vector/services/embedding.ts +9 -1
|
@@ -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": "AAAA,SAAS,mBAAmB;
|
|
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": "AAAA,SAAS,mBAAmB;AAG5B,SAAS,wBAAwB;AASjC,SAAS,+BAAwD;AAajE,MAAM,yCAAyC;AAE/C,SAAS,4BAA4B,UAA2B;AAC9D,MAAI,OAAO,aAAa,SAAU,QAAO,iBAAiB,UAAU,sCAAsC;AAC1G,QAAM,MAAM,QAAQ,IAAI;AACxB,QAAM,SAAS,MAAM,OAAO,SAAS,KAAK,EAAE,IAAI;AAChD,SAAO,iBAAiB,QAAQ,sCAAsC;AACxE;AAEO,SAAS,wBACd,SACsB;AACtB,QAAM,OAAO,SAAS,QAAQ,QAAQ,IAAI,oBAAoB;AAC9D,QAAM,SAAS,SAAS,UAAU,QAAQ,IAAI,uBAAuB;AACrE,QAAM,cAAc,SAAS,eAAe,QAAQ,IAAI,4BAA4B;AACpF,QAAM,eAAe,SAAS,gBAAgB;AAC9C,QAAM,mBAAmB,4BAA4B,SAAS,SAAS;AACvE,QAAM,wBAAwB,SAAS;AACvC,QAAM,sBAAsB,SAAS;AAErC,MAAI,SAA6B;AACjC,QAAM,qBAAqB,oBAAI,IAAY;AAC3C,QAAM,sBAAsB,oBAAI,IAA2B;AAE3D,WAAS,YAAyB;AAChC,QAAI,CAAC,QAAQ;AACX,eAAS,IAAI,YAAY,EAAE,MAAM,QAAQ,SAAS,iBAAiB,CAAC;AAAA,IACtE;AACA,WAAO;AAAA,EACT;AAEA,WAAS,eAAe,UAA0B;AAChD,UAAM,YAAY,SAAS,QAAQ,mBAAmB,GAAG;AACzD,WAAO,GAAG,WAAW,IAAI,SAAS;AAAA,EACpC;AAEA,WAAS,kBAAkB,OAAuB;AAChD,WAAO,MAAM,QAAQ,UAAU,MAAM;AAAA,EACvC;AAEA,WAAS,aAAaA,UAAwC;AAC5D,UAAM,UAAoB,CAAC;AAE3B,QAAIA,SAAQ,gBAAgB;AAC1B,cAAQ,KAAK,sBAAsB,kBAAkBA,SAAQ,cAAc,CAAC,GAAG;AAAA,IACjF;AAEA,QAAIA,SAAQ,aAAa,QAAQ;AAC/B,YAAM,eAAeA,SAAQ,YAAY,IAAI,CAAC,MAAM,IAAI,kBAAkB,CAAC,CAAC,GAAG,EAAE,KAAK,IAAI;AAC1F,cAAQ,KAAK,iBAAiB,YAAY,GAAG;AAAA,IAC/C;AAEA,WAAO;AAAA,EACT;AAEA,iBAAe,cAAc,WAAkC;AAC7D,UAAM,cAAc,UAAU;AAE9B,QAAI;AACF,YAAM,YAAY,YAAY,WAAW,EAAE,YAAY,MAAM,CAAC;AAAA,IAChE,SAAS,OAAgB;AACvB,YAAM,mBAAmB;AACzB,UAAI,iBAAiB,SAAS,wBAAwB;AACpD,cAAM;AAAA,MACR;AAAA,IACF;AAEA,UAAM,QAAQ,YAAY,MAAM,SAAS;AACzC,UAAM,MAAM,eAAe;AAAA,MACzB,sBAAsB,CAAC,GAAG;AAAA,MAC1B,sBAAsB,CAAC,aAAa,iBAAiB;AAAA,MACrD,oBAAoB,CAAC,YAAY;AAAA,MACjC,eAAe;AAAA,QACb,SAAS;AAAA,QACT,qBAAqB;AAAA,UACnB,SAAS;AAAA,UACT,UAAU;AAAA,QACZ;AAAA,MACF;AAAA,IACF,CAAC;AAED,uBAAmB,IAAI,SAAS;AAAA,EAClC;AAEA,iBAAe,YAAY,WAAkC;AAC3D,QAAI,mBAAmB,IAAI,SAAS,GAAG;AACrC;AAAA,IACF;AAEA,UAAM,kBAAkB,oBAAoB,IAAI,SAAS;AACzD,QAAI,iBAAiB;AACnB,aAAO;AAAA,IACT;AAEA,UAAM,cAAc,cAAc,SAAS;AAC3C,wBAAoB,IAAI,WAAW,WAAW;AAE9C,QAAI;AACF,YAAM;AAAA,IACR,UAAE;AACA,0BAAoB,OAAO,SAAS;AAAA,IACtC;AAAA,EACF;AAEA,iBAAe,gBAAgB,KAA+D;AAE5F,UAAM,mBAAmB,QAAQ,qBAAqB;AACtD,UAAM,kBAAkB,wBACpB,MAAM,sBAAsB,IAAI,QAAQ,IACxC,CAAC;AACL,UAAM,cAAc,sBAAsB,IAAI,QAAQ;AAEtD,UAAM,mBAAmB,wBAAwB,IAAI,QAAQ;AAAA,MAC3D;AAAA,MACA;AAAA,IACF,CAAC;AAOD,QAAI,YAAY,IAAI;AACpB,QAAI,QAAQ,IAAI;AAChB,QAAI,kBAAkB;AACpB,UAAI,WAAW;AACb,oBAAY;AAAA,UACV,GAAG;AAAA,UACH,OAAO;AAAA;AAAA,UACP,UAAU;AAAA;AAAA,QACZ;AAAA,MACF;AAEA,UAAI,SAAS,MAAM,SAAS,GAAG;AAC7B,gBAAQ,MAAM,IAAI,CAAC,UAAU;AAAA,UAC3B,GAAG;AAAA,UACH,OAAO,KAAK,SAAS,YAAY,SAAS;AAAA;AAAA,QAC5C,EAAE;AAAA,MACJ;AAAA,IACF;AAEA,WAAO;AAAA,MACL,KAAK,IAAI;AAAA,MACT,WAAW,IAAI;AAAA,MACf,iBAAiB,IAAI;AAAA,MACrB,YAAY;AAAA,MACZ,MAAM,IAAI;AAAA,MACV,QAAQ;AAAA,MACR,aAAY,oBAAI,KAAK,GAAE,YAAY;AAAA,MACnC,GAAG;AAAA,IACL;AAAA,EACF;AAEA,QAAM,SAA+B;AAAA,IACnC,IAAI;AAAA,IAEJ,MAAM,cAA6B;AAAA,IAEnC;AAAA,IAEA,MAAM,YAA8B;AAClC,UAAI,CAAC,MAAM;AACT,eAAO;AAAA,MACT;AAEA,UAAI;AACF,cAAM,cAAc,UAAU;AAC9B,cAAM,YAAY,OAAO;AACzB,eAAO;AAAA,MACT,QAAQ;AACN,eAAO;AAAA,MACT;AAAA,IACF;AAAA,IAEA,MAAM,OAAO,OAAeA,UAA4D;AACtF,YAAM,cAAc,UAAU;AAC9B,YAAM,YAAY,eAAeA,SAAQ,QAAQ;AAEjD,UAAI;AACF,cAAM,QAAQ,YAAY,MAAM,SAAS;AACzC,cAAM,UAAU,aAAaA,QAAO;AAEpC,cAAM,WAAW,MAAM,MAAM,OAAO,OAAO;AAAA,UACzC,OAAOA,SAAQ,SAAS;AAAA,UACxB,QAAQA,SAAQ;AAAA,UAChB,QAAQ,QAAQ,SAAS,IAAI,QAAQ,KAAK,OAAO,IAAI;AAAA,UACrD,kBAAkB;AAAA,QACpB,CAAC;AAED,eAAO,SAAS,KAAK,IAAI,CAAC,SAAkC;AAAA,UAC1D,UAAU,IAAI;AAAA,UACd,UAAU,IAAI;AAAA,UACd,OAAQ,IAAI,iBAA4B;AAAA,UACxC,WAAW,IAAI;AAAA,UACf,KAAK,IAAI;AAAA,UACT,OAAO,IAAI;AAAA,UACX,UAAU,IAAI;AAAA,QAChB,EAAE;AAAA,MACJ,SAAS,OAAgB;AACvB,cAAM,mBAAmB;AACzB,YAAI,iBAAiB,SAAS,mBAAmB;AAC/C,iBAAO,CAAC;AAAA,QACV;AACA,cAAM;AAAA,MACR;AAAA,IACF;AAAA,IAEA,MAAM,MAAM,KAA4C;AACtD,YAAM,cAAc,UAAU;AAC9B,YAAM,YAAY,eAAe,IAAI,QAAQ;AAE7C,YAAM,YAAY,SAAS;AAE3B,YAAM,WAAW,MAAM,gBAAgB,GAAG;AAE1C,YAAM,QAAQ,YAAY,MAAM,SAAS;AACzC,YAAM,MAAM,aAAa,CAAC,QAAQ,GAAG,EAAE,YAAY,MAAM,CAAC;AAAA,IAC5D;AAAA,IAEA,MAAM,OAAO,UAAkB,UAAiC;AAC9D,YAAM,cAAc,UAAU;AAC9B,YAAM,YAAY,eAAe,QAAQ;AAEzC,UAAI;AACF,cAAM,QAAQ,YAAY,MAAM,SAAS;AACzC,cAAM,MAAM,eAAe,QAAQ;AAAA,MACrC,SAAS,OAAgB;AACvB,cAAM,mBAAmB;AACzB,YAAI,iBAAiB,SAAS,mBAAmB;AAC/C;AAAA,QACF;AACA,cAAM;AAAA,MACR;AAAA,IACF;AAAA,IAEA,MAAM,UAAU,MAA+C;AAC7D,UAAI,KAAK,WAAW,EAAG;AAGvB,YAAM,WAAW,oBAAI,IAAsC;AAC3D,iBAAW,OAAO,MAAM;AACtB,cAAM,OAAO,SAAS,IAAI,IAAI,QAAQ,KAAK,CAAC;AAC5C,aAAK,KAAK,GAAG;AACb,iBAAS,IAAI,IAAI,UAAU,IAAI;AAAA,MACjC;AAEA,YAAM,cAAc,UAAU;AAE9B,iBAAW,CAAC,UAAU,UAAU,KAAK,UAAU;AAC7C,cAAM,YAAY,eAAe,QAAQ;AACzC,cAAM,YAAY,SAAS;AAE3B,cAAM,YAAY,MAAM,QAAQ,IAAI,WAAW,IAAI,eAAe,CAAC;AAEnE,cAAM,QAAQ,YAAY,MAAM,SAAS;AACzC,cAAM,MAAM,aAAa,WAAW,EAAE,YAAY,MAAM,CAAC;AAAA,MAC3D;AAAA,IACF;AAAA,IAEA,MAAM,MAAM,UAAoB,UAAiC;AAC/D,YAAM,cAAc,UAAU;AAC9B,YAAM,YAAY,eAAe,QAAQ;AAEzC,UAAI;AACF,cAAM,QAAQ,YAAY,MAAM,SAAS;AACzC,cAAM,MAAM,gBAAgB;AAAA,UAC1B,QAAQ,gBAAgB,QAAQ;AAAA,QAClC,CAAC;AAAA,MACH,SAAS,OAAgB;AACvB,cAAM,mBAAmB;AACzB,YAAI,iBAAiB,SAAS,mBAAmB;AAC/C;AAAA,QACF;AACA,cAAM;AAAA,MACR;AAAA,IACF;AAAA,IAEA,MAAM,WAAW,UAAiC;AAChD,YAAM,cAAc,UAAU;AAC9B,YAAM,YAAY,eAAe,QAAQ;AAEzC,UAAI;AACF,cAAM,QAAQ,YAAY,MAAM,SAAS;AACzC,cAAM,MAAM,mBAAmB;AAAA,MACjC,SAAS,OAAgB;AACvB,cAAM,mBAAmB;AACzB,YAAI,iBAAiB,SAAS,mBAAmB;AAC/C;AAAA,QACF;AACA,cAAM;AAAA,MACR;AAAA,IACF;AAAA,IAEA,MAAM,cAAc,UAAiC;AACnD,YAAM,cAAc,UAAU;AAC9B,YAAM,YAAY,eAAe,QAAQ;AAEzC,yBAAmB,OAAO,SAAS;AAEnC,UAAI;AACF,cAAM,YAAY,YAAY,SAAS;AAAA,MACzC,SAAS,OAAgB;AACvB,cAAM,mBAAmB;AACzB,YAAI,iBAAiB,SAAS,mBAAmB;AAC/C,gBAAM;AAAA,QACR;AAAA,MACF;AAEA,YAAM,YAAY,SAAS;AAAA,IAC7B;AAAA,IAEA,MAAM,aACJ,KACA,UACyC;AACzC,YAAM,SAAS,oBAAI,IAA+B;AAClD,UAAI,IAAI,WAAW,EAAG,QAAO;AAE7B,YAAM,cAAc,UAAU;AAC9B,YAAM,YAAY,eAAe,QAAQ;AAEzC,UAAI;AACF,cAAM,QAAQ,YAAY,MAAM,SAAS;AAEzC,cAAM,YAAY,IAAI,IAAI,CAAC,OAAO,GAAG,QAAQ;AAC7C,cAAM,YAAY,MAAM,MAAM,aAAa;AAAA,UACzC,QAAQ,WAAW,UAAU,IAAI,CAAC,OAAO,IAAI,EAAE,GAAG,EAAE,KAAK,IAAI,CAAC;AAAA,UAC9D,OAAO,UAAU;AAAA,QACnB,CAAC;AAED,mBAAW,OAAO,UAAU,SAAS;AACnC,gBAAM,MAAM;AACZ,gBAAM,MAAM,GAAG,IAAI,SAAS,IAAI,IAAI,GAAG;AACvC,iBAAO,IAAI,KAAK;AAAA,YACd,UAAU,IAAI;AAAA,YACd,UAAU,IAAI;AAAA,YACd,OAAO;AAAA,YACP,WAAW,IAAI;AAAA,YACf,KAAK,IAAI;AAAA,YACT,OAAO,IAAI;AAAA,UACb,CAAC;AAAA,QACH;AAAA,MACF,QAAQ;AAAA,MAER;AAEA,aAAO;AAAA,IACT;AAAA,IAEA,MAAM,cAAc,UAA8C;AAChE,YAAM,cAAc,UAAU;AAC9B,YAAM,YAAY,eAAe,QAAQ;AAEzC,UAAI;AACF,cAAM,QAAQ,YAAY,MAAM,SAAS;AACzC,cAAM,QAAQ,MAAM,MAAM,SAAS;AACnC,eAAO;AAAA,UACL,mBAAmB,MAAM;AAAA,UACzB,YAAY,MAAM;AAAA,UAClB,mBAAmB,MAAM;AAAA,QAC3B;AAAA,MACF,SAAS,OAAgB;AACvB,cAAM,mBAAmB;AACzB,YAAI,iBAAiB,SAAS,mBAAmB;AAC/C,iBAAO;AAAA,QACT;AACA,cAAM;AAAA,MACR;AAAA,IACF;AAAA,IAEA,MAAM,gBAAgB,UAA0D;AAC9E,YAAM,cAAc,UAAU;AAC9B,YAAM,YAAY,eAAe,QAAQ;AAEzC,UAAI;AACF,cAAM,QAAQ,YAAY,MAAM,SAAS;AACzC,cAAM,eAAe,MAAM,MAAM,OAAO,IAAI;AAAA,UAC1C,OAAO;AAAA,UACP,QAAQ,CAAC,WAAW;AAAA,QACtB,CAAC;AACD,cAAM,oBAAoB,aAAa,mBAAmB;AAC1D,YAAI,CAAC,mBAAmB;AACtB,iBAAO,CAAC;AAAA,QACV;AACA,eAAO;AAAA,MACT,SAAS,OAAgB;AACvB,cAAM,mBAAmB;AACzB,YAAI,iBAAiB,SAAS,mBAAmB;AAC/C,iBAAO;AAAA,QACT;AACA,cAAM;AAAA,MACR;AAAA,IACF;AAAA,EACF;AAEA,SAAO;AACT;",
|
|
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
|
-
|
|
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.
|
|
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.
|
|
130
|
-
"@open-mercato/queue": "0.4.11-develop.
|
|
131
|
-
"@open-mercato/shared": "0.4.11-develop.
|
|
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
|
-
|
|
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
|
}
|