@open-mercato/search 0.6.7 → 0.6.8-develop.6875.1.871a4afc94

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (66) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/AGENTS.md +7 -1
  3. package/dist/lib/presenter-enricher.js +12 -12
  4. package/dist/lib/presenter-enricher.js.map +2 -2
  5. package/dist/modules/search/ai-tools.js +5 -2
  6. package/dist/modules/search/ai-tools.js.map +2 -2
  7. package/dist/modules/search/api/openapi.js +2 -0
  8. package/dist/modules/search/api/openapi.js.map +2 -2
  9. package/dist/modules/search/api/settings/vector-store/route.js +34 -0
  10. package/dist/modules/search/api/settings/vector-store/route.js.map +2 -2
  11. package/dist/modules/search/events.js +13 -0
  12. package/dist/modules/search/events.js.map +7 -0
  13. package/dist/modules/search/frontend/components/GlobalSearchDialog.js +2 -9
  14. package/dist/modules/search/frontend/components/GlobalSearchDialog.js.map +2 -2
  15. package/dist/modules/search/frontend/components/HybridSearchTable.js +2 -11
  16. package/dist/modules/search/frontend/components/HybridSearchTable.js.map +2 -2
  17. package/dist/modules/search/frontend/components/SearchSettingsPageClient.js.map +2 -2
  18. package/dist/modules/search/frontend/components/TopbarSearchInline.js +2 -9
  19. package/dist/modules/search/frontend/components/TopbarSearchInline.js.map +2 -2
  20. package/dist/modules/search/frontend/components/sections/VectorSearchSection.js +15 -3
  21. package/dist/modules/search/frontend/components/sections/VectorSearchSection.js.map +2 -2
  22. package/dist/modules/search/frontend/lib/entityTypeLabel.js +19 -0
  23. package/dist/modules/search/frontend/lib/entityTypeLabel.js.map +7 -0
  24. package/dist/modules/search/i18n/de.json +49 -0
  25. package/dist/modules/search/i18n/en.json +49 -0
  26. package/dist/modules/search/i18n/es.json +49 -0
  27. package/dist/modules/search/i18n/ko.json +239 -0
  28. package/dist/modules/search/i18n/pl.json +49 -0
  29. package/dist/modules/search/lib/auto-indexing.js +7 -8
  30. package/dist/modules/search/lib/auto-indexing.js.map +2 -2
  31. package/dist/service.js +2 -5
  32. package/dist/service.js.map +2 -2
  33. package/dist/strategies/vector.strategy.js +3 -1
  34. package/dist/strategies/vector.strategy.js.map +2 -2
  35. package/dist/vector/drivers/pgvector/index.js +55 -1
  36. package/dist/vector/drivers/pgvector/index.js.map +2 -2
  37. package/dist/vector/types.js.map +2 -2
  38. package/package.json +11 -10
  39. package/src/__tests__/pgvector-extension-availability.test.ts +158 -0
  40. package/src/__tests__/presenter-enricher.test.ts +48 -0
  41. package/src/__tests__/presenter-locale.test.ts +76 -0
  42. package/src/lib/presenter-enricher.ts +14 -14
  43. package/src/modules/search/README.md +4 -1
  44. package/src/modules/search/ai-tools.ts +5 -2
  45. package/src/modules/search/api/__tests__/global-search.routes.test.ts +167 -0
  46. package/src/modules/search/api/openapi.ts +2 -0
  47. package/src/modules/search/api/settings/vector-store/route.ts +43 -0
  48. package/src/modules/search/events.ts +10 -0
  49. package/src/modules/search/frontend/components/GlobalSearchDialog.tsx +2 -15
  50. package/src/modules/search/frontend/components/HybridSearchTable.tsx +2 -17
  51. package/src/modules/search/frontend/components/SearchSettingsPageClient.tsx +2 -0
  52. package/src/modules/search/frontend/components/TopbarSearchInline.tsx +2 -15
  53. package/src/modules/search/frontend/components/sections/VectorSearchSection.tsx +32 -2
  54. package/src/modules/search/frontend/components/sections/__tests__/VectorSearchSection.test.tsx +36 -1
  55. package/src/modules/search/frontend/lib/__tests__/entityTypeLabel.test.ts +25 -0
  56. package/src/modules/search/frontend/lib/entityTypeLabel.ts +22 -0
  57. package/src/modules/search/i18n/de.json +49 -0
  58. package/src/modules/search/i18n/en.json +49 -0
  59. package/src/modules/search/i18n/es.json +49 -0
  60. package/src/modules/search/i18n/ko.json +239 -0
  61. package/src/modules/search/i18n/pl.json +49 -0
  62. package/src/modules/search/lib/auto-indexing.ts +7 -12
  63. package/src/service.ts +2 -7
  64. package/src/strategies/vector.strategy.ts +7 -1
  65. package/src/vector/drivers/pgvector/index.ts +77 -1
  66. package/src/vector/types.ts +14 -0
@@ -1,2 +1,2 @@
1
- [build:search] found 95 entry points
1
+ [build:search] found 97 entry points
2
2
  [build:search] built successfully
package/AGENTS.md CHANGED
@@ -42,6 +42,8 @@ Choose strategies based on what users need:
42
42
 
43
43
  Strategies automatically become unavailable if their backend is not configured (e.g., no `MEILISEARCH_HOST` means fulltext is unavailable).
44
44
 
45
+ Availability covers the **store**, not just its credentials. `VectorDriver` exposes optional `isHealthy()` / `getStatus()`; the pgvector driver reports `false` when the `vector` extension is neither installed nor installable, and `VectorSearchStrategy.isAvailable()` consumes it. Writes (`index`/`delete`/`purge`) stay fail-loud for available strategies — a driver that cannot serve its backend MUST report itself unhealthy rather than throwing per record. Settings > Search shows the reason when the store is unavailable.
46
+
45
47
  ## Configure Global Search (Cmd+K)
46
48
 
47
49
  Search settings (Cmd+K strategies, embedding provider/model, auto-index flag) are
@@ -541,7 +543,11 @@ curl "https://your-app.com/api/search?q=john%20doe&limit=20" \
541
543
  | `OM_SEARCH_ENABLE_PARTIAL` | When trading `search_tokens` size for prefix matching | Default: `true`. Prefix/partial expansion for tokens (Meilisearch unaffected); increases `search_tokens` size ~5–6×. **Token strategy only** |
542
544
  | `OM_SEARCH_HASH_ALGO` | When choosing the token hash algorithm | Default: `sha256` (accepts `sha1`, `md5`). **Token strategy only** |
543
545
  | `OM_SEARCH_STORE_RAW_TOKENS` | Almost never — debugging tokenization only | Default: `false`. Stores plaintext token alongside the hash — **security-sensitive**, retains plaintext of otherwise-hashed values. **Token strategy only** |
544
- | `OM_SEARCH_FIELD_BLOCKLIST` | When extra fields must never be tokenized | Comma-separated field names, merged with built-in `password,token,secret,hash`. **Token strategy only** |
546
+ | `OM_SEARCH_FIELD_BLOCKLIST` | When extra fields must never be tokenized | Comma-separated field-name substrings, merged with built-in `password,token,secret,hash`; prefix an entry with `entityType@` to scope it to one entity. Applies to per-field tokens and aggregate `search_text`; reindex affected entities after changes. **Token strategy only** |
547
+ | `OM_SEARCH_MAX_FIELD_CHARS` | When bounding large searchable values | Default: `20000`. Maximum input characters considered per field value before splitting or prefix expansion; `0` disables the limit. **Token strategy only** |
548
+ | `OM_SEARCH_MAX_TOKENS_PER_FIELD` | When bounding token fan-out for one field | Default: `5000`. Maximum distinct token rows across all values of one field; `0` disables the limit. **Token strategy only** |
549
+ | `OM_SEARCH_MAX_TOKENS_PER_RECORD` | When bounding token fan-out for one record | Default: `20000`. Maximum token rows across all fields in one indexed record; `0` disables the limit. **Token strategy only** |
550
+ | `OM_SEARCH_TOKEN_PRESENCE_CACHE_MS` | When tuning the query engines' "does this entity have search tokens?" probe | Default: `30000` (ms); `0` disables. Process-level TTL cache in `@open-mercato/shared/lib/search/availability` — bounds how long a token purge or first-index is invisible to like/ilike routing. **Token strategy only** |
545
551
  | `SEARCH_EXCLUDE_ENCRYPTED_FIELDS` | When you need to keep encrypted fields out of fulltext | Set to `true` to exclude encrypted fields from fulltext index |
546
552
  | `OM_LOG_LEVEL` | When debugging presenter enrichment | Set to `debug` to surface presenter enricher diagnostics (replaces the former `DEBUG_SEARCH_ENRICHER` flag) |
547
553
 
@@ -66,20 +66,20 @@ async function computePresenterAndLinks(doc, entityId, recordId, config, tenantI
66
66
  queryEngine
67
67
  };
68
68
  if (config?.formatResult || config?.buildSource) {
69
- if (config.buildSource) {
69
+ if (config.formatResult) {
70
70
  try {
71
- const source = await config.buildSource(buildContext);
72
- if (source?.presenter) presenter = source.presenter;
73
- if (source?.links) links = source.links;
71
+ presenter = await config.formatResult(buildContext) ?? null;
74
72
  } catch (err) {
75
- logWarning("buildSource failed", { entityId, recordId, err });
73
+ logWarning("formatResult failed", { entityId, recordId, err });
76
74
  }
77
75
  }
78
- if (!presenter && config.formatResult) {
76
+ if (!presenter && config.buildSource) {
79
77
  try {
80
- presenter = await config.formatResult(buildContext) ?? null;
78
+ const source = await config.buildSource(buildContext);
79
+ if (source?.presenter) presenter = source.presenter;
80
+ if (source?.links) links = source.links;
81
81
  } catch (err) {
82
- logWarning("formatResult failed", { entityId, recordId, err });
82
+ logWarning("buildSource failed", { entityId, recordId, err });
83
83
  }
84
84
  }
85
85
  }
@@ -102,7 +102,8 @@ async function computePresenterAndLinks(doc, entityId, recordId, config, tenantI
102
102
  }
103
103
  function createPresenterEnricher(db, entityConfigMap, queryEngine, encryptionService) {
104
104
  return async (results, tenantId, organizationId) => {
105
- const missingResults = results.filter(needsSearchResultEnrichment);
105
+ const shouldEnrich = (result) => needsSearchResultEnrichment(result) || entityConfigMap.has(result.entityId);
106
+ const missingResults = results.filter(shouldEnrich);
106
107
  if (missingResults.length === 0) return results;
107
108
  const byEntityType = /* @__PURE__ */ new Map();
108
109
  for (const result of missingResults) {
@@ -161,16 +162,15 @@ function createPresenterEnricher(db, entityConfigMap, queryEngine, encryptionSer
161
162
  enrichmentMap.set(key, { presenter, url, links });
162
163
  }
163
164
  return results.map((result) => {
164
- if (!needsSearchResultEnrichment(result)) return result;
165
+ if (!shouldEnrich(result)) return result;
165
166
  const key = `${result.entityId}:${result.recordId}`;
166
167
  const enriched = enrichmentMap.get(key);
167
168
  if (!enriched) return result;
168
- const hasExistingLinks = Array.isArray(result.links) && result.links.length > 0;
169
169
  return {
170
170
  ...result,
171
171
  presenter: enriched.presenter ?? result.presenter,
172
172
  url: result.url ?? enriched.url,
173
- links: hasExistingLinks ? result.links : enriched.links ?? result.links
173
+ links: enriched.links ?? result.links
174
174
  };
175
175
  });
176
176
  };
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/lib/presenter-enricher.ts"],
4
- "sourcesContent": ["import type { Kysely } from 'kysely'\nimport type {\n SearchBuildContext,\n SearchResult,\n SearchResultPresenter,\n SearchResultLink,\n SearchEntityConfig,\n PresenterEnricherFn,\n} from '../types'\nimport type { QueryEngine } from '@open-mercato/shared/lib/query/types'\nimport type { EntityId } from '@open-mercato/shared/modules/entities'\nimport type { TenantDataEncryptionService } from '@open-mercato/shared/lib/encryption/tenantDataEncryptionService'\nimport { decryptIndexDocForSearch } from '@open-mercato/shared/lib/encryption/indexDoc'\nimport { createLogger } from '@open-mercato/shared/lib/logger'\nimport { extractFallbackPresenter } from './fallback-presenter'\nimport { needsSearchResultEnrichment } from './search-result-enrichment'\n\n/** Maximum number of record IDs per batch query to avoid hitting DB parameter limits */\nconst BATCH_SIZE = 500\n\n/** Diagnostic logger - surfaces issues without breaking flow, gated by OM_LOG_LEVEL=debug */\nconst enricherLogger = createLogger('search').child({ component: 'presenter-enricher' })\nconst logWarning = (message: string, context?: Record<string, unknown>) => {\n enricherLogger.debug(message, context)\n}\n\n/**\n * Split an array into chunks of specified size.\n */\nfunction chunk<T>(array: T[], size: number): T[][] {\n const chunks: T[][] = []\n for (let i = 0; i < array.length; i += size) {\n chunks.push(array.slice(i, i + size))\n }\n return chunks\n}\n\n/**\n * Build a single batch query for multiple entity types and their record IDs.\n * Uses OR conditions to fetch all needed docs in one round trip.\n */\nasync function fetchDocsBatch(\n db: Kysely<any>,\n byEntityType: Map<string, SearchResult[]>,\n tenantId: string,\n organizationId?: string | null,\n): Promise<Array<{ entity_type: string; entity_id: string; doc: Record<string, unknown> }>> {\n const allDocs: Array<{ entity_type: string; entity_id: string; doc: Record<string, unknown> }> = []\n\n // Collect all entity type + record ID pairs\n const allPairs: Array<{ entityType: string; recordId: string }> = []\n for (const [entityType, results] of byEntityType) {\n for (const result of results) {\n allPairs.push({ entityType, recordId: result.recordId })\n }\n }\n\n if (allPairs.length === 0) return allDocs\n\n // Process in chunks to avoid hitting DB parameter limits\n const chunks = chunk(allPairs, BATCH_SIZE)\n\n for (const pairChunk of chunks) {\n // Group by entity type within this chunk for efficient OR query\n const chunkByType = new Map<string, string[]>()\n for (const { entityType, recordId } of pairChunk) {\n const ids = chunkByType.get(entityType) ?? []\n ids.push(recordId)\n chunkByType.set(entityType, ids)\n }\n\n // Build query with OR conditions per entity type\n let query = db\n .selectFrom('entity_indexes' as any)\n .select(['entity_type' as any, 'entity_id' as any, 'doc' as any])\n .where('tenant_id' as any, '=', tenantId)\n .where('deleted_at' as any, 'is', null)\n .where((eb: any) => eb.or(\n Array.from(chunkByType.entries()).map(([entityType, recordIds]) => eb.and([\n eb('entity_type' as any, '=', entityType),\n eb('entity_id' as any, 'in', recordIds),\n ])),\n ))\n\n // Add organization filter if provided\n if (organizationId) {\n query = query.where((eb: any) => eb.or([\n eb('organization_id' as any, '=', organizationId),\n eb('organization_id' as any, 'is', null),\n ]))\n }\n\n const rows = await query.execute() as Array<{ entity_type: string; entity_id: string; doc: Record<string, unknown> }>\n allDocs.push(...rows)\n }\n\n return allDocs\n}\n\n/** Result type for presenter and links computation */\ntype EnrichmentResult = {\n presenter: SearchResultPresenter | null\n url?: string\n links?: SearchResultLink[]\n}\n\n/**\n * Compute presenter, URL, and links for a single doc using config or fallback.\n * Returns presenter (null if cannot be computed), and optionally URL/links from config.\n */\nasync function computePresenterAndLinks(\n doc: Record<string, unknown>,\n entityId: string,\n recordId: string,\n config: SearchEntityConfig | undefined,\n tenantId: string,\n organizationId: string | null | undefined,\n queryEngine: QueryEngine | undefined,\n): Promise<EnrichmentResult> {\n let presenter: SearchResultPresenter | null = null\n let url: string | undefined\n let links: SearchResultLink[] | undefined\n\n // Build context for config functions\n const customFields: Record<string, unknown> = {}\n for (const [key, value] of Object.entries(doc)) {\n if (key.startsWith('cf:') || key.startsWith('cf_')) {\n customFields[key.slice(3)] = value\n }\n }\n\n const buildContext: SearchBuildContext = {\n record: doc,\n customFields,\n organizationId,\n tenantId,\n queryEngine,\n }\n\n // If search.ts config exists, use formatResult/buildSource for presenter\n if (config?.formatResult || config?.buildSource) {\n if (config.buildSource) {\n try {\n const source = await config.buildSource(buildContext)\n if (source?.presenter) presenter = source.presenter\n if (source?.links) links = source.links\n } catch (err) {\n logWarning('buildSource failed', { entityId, recordId, err })\n }\n }\n\n if (!presenter && config.formatResult) {\n try {\n presenter = (await config.formatResult(buildContext)) ?? null\n } catch (err) {\n logWarning('formatResult failed', { entityId, recordId, err })\n }\n }\n }\n\n // Fallback presenter: extract from doc fields directly\n if (!presenter) {\n presenter = extractFallbackPresenter(doc, entityId, recordId)\n }\n\n // Resolve URL from config\n if (config?.resolveUrl) {\n try {\n url = (await config.resolveUrl(buildContext)) ?? undefined\n } catch {\n // Skip URL resolution errors\n }\n }\n\n // Resolve links from config (if not already set from buildSource)\n if (!links && config?.resolveLinks) {\n try {\n links = (await config.resolveLinks(buildContext)) ?? undefined\n } catch {\n // Skip link resolution errors\n }\n }\n\n return { presenter, url, links }\n}\n\n/**\n * Create a presenter enricher that loads data from entity_indexes and computes presenter.\n * Uses formatResult from search.ts configs when available, otherwise falls back to extracting\n * common fields like display_name, name, title from the doc.\n *\n * Optimizations:\n * - Single batch DB query for all entity types (instead of one per type)\n * - Parallel Promise.all for formatResult/buildSource calls\n * - Tenant/organization scoping for security\n * - Chunked queries to avoid DB parameter limits\n * - Automatic decryption of encrypted fields when encryption service is provided\n */\nexport function createPresenterEnricher(\n db: Kysely<any>,\n entityConfigMap: Map<EntityId, SearchEntityConfig>,\n queryEngine?: QueryEngine,\n encryptionService?: TenantDataEncryptionService | null,\n): PresenterEnricherFn {\n return async (results, tenantId, organizationId) => {\n // Find results missing presenter OR with encrypted presenter\n const missingResults = results.filter(needsSearchResultEnrichment)\n if (missingResults.length === 0) return results\n\n // Group by entity type for config lookup\n const byEntityType = new Map<string, SearchResult[]>()\n for (const result of missingResults) {\n const group = byEntityType.get(result.entityId) ?? []\n group.push(result)\n byEntityType.set(result.entityId, group)\n }\n\n // Single batch query for all docs across all entity types\n const rawDocs = await fetchDocsBatch(db, byEntityType, tenantId, organizationId)\n\n // Decrypt docs in parallel using DEK cache for efficiency\n const dekCache = new Map<string | null, string | null>()\n\n const decryptedDocs = await Promise.all(\n rawDocs.map(async (row) => {\n try {\n // Use organization_id from the doc itself for proper encryption map lookup\n // This is critical for global search where organizationId param is null\n const docData = row.doc as Record<string, unknown>\n const docOrgId = (docData.organization_id as string | null | undefined) ?? organizationId\n const scope = { tenantId, organizationId: docOrgId }\n\n const decryptedDoc = await decryptIndexDocForSearch(\n row.entity_type,\n row.doc,\n scope,\n encryptionService ?? null,\n dekCache,\n )\n return { ...row, doc: decryptedDoc }\n } catch (err) {\n logWarning('Failed to decrypt doc', { entityId: row.entity_type, recordId: row.entity_id, err })\n return row // Return original doc if decryption fails\n }\n }),\n )\n\n // Build doc lookup map for fast access\n const docMap = new Map<string, Record<string, unknown>>()\n for (const row of decryptedDocs) {\n docMap.set(`${row.entity_type}:${row.entity_id}`, row.doc)\n }\n\n // Compute presenters and links in parallel\n const enrichmentPromises = missingResults.map(async (result) => {\n const key = `${result.entityId}:${result.recordId}`\n const doc = docMap.get(key)\n\n if (!doc) {\n logWarning('Doc not found in entity_indexes', { entityId: result.entityId, recordId: result.recordId })\n return { key, presenter: null, url: undefined, links: undefined }\n }\n\n const config = entityConfigMap.get(result.entityId as EntityId)\n const enrichment = await computePresenterAndLinks(\n doc,\n result.entityId,\n result.recordId,\n config,\n tenantId,\n organizationId,\n queryEngine,\n )\n\n return { key, ...enrichment }\n })\n\n const computed = await Promise.all(enrichmentPromises)\n\n // Build enrichment map from parallel results\n const enrichmentMap = new Map<string, EnrichmentResult>()\n for (const { key, presenter, url, links } of computed) {\n enrichmentMap.set(key, { presenter, url, links })\n }\n\n // Enrich results with computed presenter, URL, and links\n return results.map((result) => {\n if (!needsSearchResultEnrichment(result)) return result\n const key = `${result.entityId}:${result.recordId}`\n const enriched = enrichmentMap.get(key)\n if (!enriched) return result\n const hasExistingLinks = Array.isArray(result.links) && result.links.length > 0\n return {\n ...result,\n presenter: enriched.presenter ?? result.presenter,\n url: result.url ?? enriched.url,\n links: hasExistingLinks ? result.links : (enriched.links ?? result.links),\n }\n })\n }\n}\n"],
5
- "mappings": "AAYA,SAAS,gCAAgC;AACzC,SAAS,oBAAoB;AAC7B,SAAS,gCAAgC;AACzC,SAAS,mCAAmC;AAG5C,MAAM,aAAa;AAGnB,MAAM,iBAAiB,aAAa,QAAQ,EAAE,MAAM,EAAE,WAAW,qBAAqB,CAAC;AACvF,MAAM,aAAa,CAAC,SAAiB,YAAsC;AACzE,iBAAe,MAAM,SAAS,OAAO;AACvC;AAKA,SAAS,MAAS,OAAY,MAAqB;AACjD,QAAM,SAAgB,CAAC;AACvB,WAAS,IAAI,GAAG,IAAI,MAAM,QAAQ,KAAK,MAAM;AAC3C,WAAO,KAAK,MAAM,MAAM,GAAG,IAAI,IAAI,CAAC;AAAA,EACtC;AACA,SAAO;AACT;AAMA,eAAe,eACb,IACA,cACA,UACA,gBAC0F;AAC1F,QAAM,UAA2F,CAAC;AAGlG,QAAM,WAA4D,CAAC;AACnE,aAAW,CAAC,YAAY,OAAO,KAAK,cAAc;AAChD,eAAW,UAAU,SAAS;AAC5B,eAAS,KAAK,EAAE,YAAY,UAAU,OAAO,SAAS,CAAC;AAAA,IACzD;AAAA,EACF;AAEA,MAAI,SAAS,WAAW,EAAG,QAAO;AAGlC,QAAM,SAAS,MAAM,UAAU,UAAU;AAEzC,aAAW,aAAa,QAAQ;AAE9B,UAAM,cAAc,oBAAI,IAAsB;AAC9C,eAAW,EAAE,YAAY,SAAS,KAAK,WAAW;AAChD,YAAM,MAAM,YAAY,IAAI,UAAU,KAAK,CAAC;AAC5C,UAAI,KAAK,QAAQ;AACjB,kBAAY,IAAI,YAAY,GAAG;AAAA,IACjC;AAGA,QAAI,QAAQ,GACT,WAAW,gBAAuB,EAClC,OAAO,CAAC,eAAsB,aAAoB,KAAY,CAAC,EAC/D,MAAM,aAAoB,KAAK,QAAQ,EACvC,MAAM,cAAqB,MAAM,IAAI,EACrC,MAAM,CAAC,OAAY,GAAG;AAAA,MACrB,MAAM,KAAK,YAAY,QAAQ,CAAC,EAAE,IAAI,CAAC,CAAC,YAAY,SAAS,MAAM,GAAG,IAAI;AAAA,QACxE,GAAG,eAAsB,KAAK,UAAU;AAAA,QACxC,GAAG,aAAoB,MAAM,SAAS;AAAA,MACxC,CAAC,CAAC;AAAA,IACJ,CAAC;AAGH,QAAI,gBAAgB;AAClB,cAAQ,MAAM,MAAM,CAAC,OAAY,GAAG,GAAG;AAAA,QACrC,GAAG,mBAA0B,KAAK,cAAc;AAAA,QAChD,GAAG,mBAA0B,MAAM,IAAI;AAAA,MACzC,CAAC,CAAC;AAAA,IACJ;AAEA,UAAM,OAAO,MAAM,MAAM,QAAQ;AACjC,YAAQ,KAAK,GAAG,IAAI;AAAA,EACtB;AAEA,SAAO;AACT;AAaA,eAAe,yBACb,KACA,UACA,UACA,QACA,UACA,gBACA,aAC2B;AAC3B,MAAI,YAA0C;AAC9C,MAAI;AACJ,MAAI;AAGJ,QAAM,eAAwC,CAAC;AAC/C,aAAW,CAAC,KAAK,KAAK,KAAK,OAAO,QAAQ,GAAG,GAAG;AAC9C,QAAI,IAAI,WAAW,KAAK,KAAK,IAAI,WAAW,KAAK,GAAG;AAClD,mBAAa,IAAI,MAAM,CAAC,CAAC,IAAI;AAAA,IAC/B;AAAA,EACF;AAEA,QAAM,eAAmC;AAAA,IACvC,QAAQ;AAAA,IACR;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF;AAGA,MAAI,QAAQ,gBAAgB,QAAQ,aAAa;AAC/C,QAAI,OAAO,aAAa;AACtB,UAAI;AACF,cAAM,SAAS,MAAM,OAAO,YAAY,YAAY;AACpD,YAAI,QAAQ,UAAW,aAAY,OAAO;AAC1C,YAAI,QAAQ,MAAO,SAAQ,OAAO;AAAA,MACpC,SAAS,KAAK;AACZ,mBAAW,sBAAsB,EAAE,UAAU,UAAU,IAAI,CAAC;AAAA,MAC9D;AAAA,IACF;AAEA,QAAI,CAAC,aAAa,OAAO,cAAc;AACrC,UAAI;AACF,oBAAa,MAAM,OAAO,aAAa,YAAY,KAAM;AAAA,MAC3D,SAAS,KAAK;AACZ,mBAAW,uBAAuB,EAAE,UAAU,UAAU,IAAI,CAAC;AAAA,MAC/D;AAAA,IACF;AAAA,EACF;AAGA,MAAI,CAAC,WAAW;AACd,gBAAY,yBAAyB,KAAK,UAAU,QAAQ;AAAA,EAC9D;AAGA,MAAI,QAAQ,YAAY;AACtB,QAAI;AACF,YAAO,MAAM,OAAO,WAAW,YAAY,KAAM;AAAA,IACnD,QAAQ;AAAA,IAER;AAAA,EACF;AAGA,MAAI,CAAC,SAAS,QAAQ,cAAc;AAClC,QAAI;AACF,cAAS,MAAM,OAAO,aAAa,YAAY,KAAM;AAAA,IACvD,QAAQ;AAAA,IAER;AAAA,EACF;AAEA,SAAO,EAAE,WAAW,KAAK,MAAM;AACjC;AAcO,SAAS,wBACd,IACA,iBACA,aACA,mBACqB;AACrB,SAAO,OAAO,SAAS,UAAU,mBAAmB;AAElD,UAAM,iBAAiB,QAAQ,OAAO,2BAA2B;AACjE,QAAI,eAAe,WAAW,EAAG,QAAO;AAGxC,UAAM,eAAe,oBAAI,IAA4B;AACrD,eAAW,UAAU,gBAAgB;AACnC,YAAM,QAAQ,aAAa,IAAI,OAAO,QAAQ,KAAK,CAAC;AACpD,YAAM,KAAK,MAAM;AACjB,mBAAa,IAAI,OAAO,UAAU,KAAK;AAAA,IACzC;AAGA,UAAM,UAAU,MAAM,eAAe,IAAI,cAAc,UAAU,cAAc;AAG/E,UAAM,WAAW,oBAAI,IAAkC;AAEvD,UAAM,gBAAgB,MAAM,QAAQ;AAAA,MAClC,QAAQ,IAAI,OAAO,QAAQ;AACzB,YAAI;AAGF,gBAAM,UAAU,IAAI;AACpB,gBAAM,WAAY,QAAQ,mBAAiD;AAC3E,gBAAM,QAAQ,EAAE,UAAU,gBAAgB,SAAS;AAEnD,gBAAM,eAAe,MAAM;AAAA,YACzB,IAAI;AAAA,YACJ,IAAI;AAAA,YACJ;AAAA,YACA,qBAAqB;AAAA,YACrB;AAAA,UACF;AACA,iBAAO,EAAE,GAAG,KAAK,KAAK,aAAa;AAAA,QACrC,SAAS,KAAK;AACZ,qBAAW,yBAAyB,EAAE,UAAU,IAAI,aAAa,UAAU,IAAI,WAAW,IAAI,CAAC;AAC/F,iBAAO;AAAA,QACT;AAAA,MACF,CAAC;AAAA,IACH;AAGA,UAAM,SAAS,oBAAI,IAAqC;AACxD,eAAW,OAAO,eAAe;AAC/B,aAAO,IAAI,GAAG,IAAI,WAAW,IAAI,IAAI,SAAS,IAAI,IAAI,GAAG;AAAA,IAC3D;AAGA,UAAM,qBAAqB,eAAe,IAAI,OAAO,WAAW;AAC9D,YAAM,MAAM,GAAG,OAAO,QAAQ,IAAI,OAAO,QAAQ;AACjD,YAAM,MAAM,OAAO,IAAI,GAAG;AAE1B,UAAI,CAAC,KAAK;AACR,mBAAW,mCAAmC,EAAE,UAAU,OAAO,UAAU,UAAU,OAAO,SAAS,CAAC;AACtG,eAAO,EAAE,KAAK,WAAW,MAAM,KAAK,QAAW,OAAO,OAAU;AAAA,MAClE;AAEA,YAAM,SAAS,gBAAgB,IAAI,OAAO,QAAoB;AAC9D,YAAM,aAAa,MAAM;AAAA,QACvB;AAAA,QACA,OAAO;AAAA,QACP,OAAO;AAAA,QACP;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,MACF;AAEA,aAAO,EAAE,KAAK,GAAG,WAAW;AAAA,IAC9B,CAAC;AAED,UAAM,WAAW,MAAM,QAAQ,IAAI,kBAAkB;AAGrD,UAAM,gBAAgB,oBAAI,IAA8B;AACxD,eAAW,EAAE,KAAK,WAAW,KAAK,MAAM,KAAK,UAAU;AACrD,oBAAc,IAAI,KAAK,EAAE,WAAW,KAAK,MAAM,CAAC;AAAA,IAClD;AAGA,WAAO,QAAQ,IAAI,CAAC,WAAW;AAC7B,UAAI,CAAC,4BAA4B,MAAM,EAAG,QAAO;AACjD,YAAM,MAAM,GAAG,OAAO,QAAQ,IAAI,OAAO,QAAQ;AACjD,YAAM,WAAW,cAAc,IAAI,GAAG;AACtC,UAAI,CAAC,SAAU,QAAO;AACtB,YAAM,mBAAmB,MAAM,QAAQ,OAAO,KAAK,KAAK,OAAO,MAAM,SAAS;AAC9E,aAAO;AAAA,QACL,GAAG;AAAA,QACH,WAAW,SAAS,aAAa,OAAO;AAAA,QACxC,KAAK,OAAO,OAAO,SAAS;AAAA,QAC5B,OAAO,mBAAmB,OAAO,QAAS,SAAS,SAAS,OAAO;AAAA,MACrE;AAAA,IACF,CAAC;AAAA,EACH;AACF;",
4
+ "sourcesContent": ["import type { Kysely } from 'kysely'\nimport type {\n SearchBuildContext,\n SearchResult,\n SearchResultPresenter,\n SearchResultLink,\n SearchEntityConfig,\n PresenterEnricherFn,\n} from '../types'\nimport type { QueryEngine } from '@open-mercato/shared/lib/query/types'\nimport type { EntityId } from '@open-mercato/shared/modules/entities'\nimport type { TenantDataEncryptionService } from '@open-mercato/shared/lib/encryption/tenantDataEncryptionService'\nimport { decryptIndexDocForSearch } from '@open-mercato/shared/lib/encryption/indexDoc'\nimport { createLogger } from '@open-mercato/shared/lib/logger'\nimport { extractFallbackPresenter } from './fallback-presenter'\nimport { needsSearchResultEnrichment } from './search-result-enrichment'\n\n/** Maximum number of record IDs per batch query to avoid hitting DB parameter limits */\nconst BATCH_SIZE = 500\n\n/** Diagnostic logger - surfaces issues without breaking flow, gated by OM_LOG_LEVEL=debug */\nconst enricherLogger = createLogger('search').child({ component: 'presenter-enricher' })\nconst logWarning = (message: string, context?: Record<string, unknown>) => {\n enricherLogger.debug(message, context)\n}\n\n/**\n * Split an array into chunks of specified size.\n */\nfunction chunk<T>(array: T[], size: number): T[][] {\n const chunks: T[][] = []\n for (let i = 0; i < array.length; i += size) {\n chunks.push(array.slice(i, i + size))\n }\n return chunks\n}\n\n/**\n * Build a single batch query for multiple entity types and their record IDs.\n * Uses OR conditions to fetch all needed docs in one round trip.\n */\nasync function fetchDocsBatch(\n db: Kysely<any>,\n byEntityType: Map<string, SearchResult[]>,\n tenantId: string,\n organizationId?: string | null,\n): Promise<Array<{ entity_type: string; entity_id: string; doc: Record<string, unknown> }>> {\n const allDocs: Array<{ entity_type: string; entity_id: string; doc: Record<string, unknown> }> = []\n\n // Collect all entity type + record ID pairs\n const allPairs: Array<{ entityType: string; recordId: string }> = []\n for (const [entityType, results] of byEntityType) {\n for (const result of results) {\n allPairs.push({ entityType, recordId: result.recordId })\n }\n }\n\n if (allPairs.length === 0) return allDocs\n\n // Process in chunks to avoid hitting DB parameter limits\n const chunks = chunk(allPairs, BATCH_SIZE)\n\n for (const pairChunk of chunks) {\n // Group by entity type within this chunk for efficient OR query\n const chunkByType = new Map<string, string[]>()\n for (const { entityType, recordId } of pairChunk) {\n const ids = chunkByType.get(entityType) ?? []\n ids.push(recordId)\n chunkByType.set(entityType, ids)\n }\n\n // Build query with OR conditions per entity type\n let query = db\n .selectFrom('entity_indexes' as any)\n .select(['entity_type' as any, 'entity_id' as any, 'doc' as any])\n .where('tenant_id' as any, '=', tenantId)\n .where('deleted_at' as any, 'is', null)\n .where((eb: any) => eb.or(\n Array.from(chunkByType.entries()).map(([entityType, recordIds]) => eb.and([\n eb('entity_type' as any, '=', entityType),\n eb('entity_id' as any, 'in', recordIds),\n ])),\n ))\n\n // Add organization filter if provided\n if (organizationId) {\n query = query.where((eb: any) => eb.or([\n eb('organization_id' as any, '=', organizationId),\n eb('organization_id' as any, 'is', null),\n ]))\n }\n\n const rows = await query.execute() as Array<{ entity_type: string; entity_id: string; doc: Record<string, unknown> }>\n allDocs.push(...rows)\n }\n\n return allDocs\n}\n\n/** Result type for presenter and links computation */\ntype EnrichmentResult = {\n presenter: SearchResultPresenter | null\n url?: string\n links?: SearchResultLink[]\n}\n\n/**\n * Compute presenter, URL, and links for a single doc using config or fallback.\n * Returns presenter (null if cannot be computed), and optionally URL/links from config.\n */\nasync function computePresenterAndLinks(\n doc: Record<string, unknown>,\n entityId: string,\n recordId: string,\n config: SearchEntityConfig | undefined,\n tenantId: string,\n organizationId: string | null | undefined,\n queryEngine: QueryEngine | undefined,\n): Promise<EnrichmentResult> {\n let presenter: SearchResultPresenter | null = null\n let url: string | undefined\n let links: SearchResultLink[] | undefined\n\n // Build context for config functions\n const customFields: Record<string, unknown> = {}\n for (const [key, value] of Object.entries(doc)) {\n if (key.startsWith('cf:') || key.startsWith('cf_')) {\n customFields[key.slice(3)] = value\n }\n }\n\n const buildContext: SearchBuildContext = {\n record: doc,\n customFields,\n organizationId,\n tenantId,\n queryEngine,\n }\n\n if (config?.formatResult || config?.buildSource) {\n if (config.formatResult) {\n try {\n presenter = (await config.formatResult(buildContext)) ?? null\n } catch (err) {\n logWarning('formatResult failed', { entityId, recordId, err })\n }\n }\n\n if (!presenter && config.buildSource) {\n try {\n const source = await config.buildSource(buildContext)\n if (source?.presenter) presenter = source.presenter\n if (source?.links) links = source.links\n } catch (err) {\n logWarning('buildSource failed', { entityId, recordId, err })\n }\n }\n }\n\n // Fallback presenter: extract from doc fields directly\n if (!presenter) {\n presenter = extractFallbackPresenter(doc, entityId, recordId)\n }\n\n // Resolve URL from config\n if (config?.resolveUrl) {\n try {\n url = (await config.resolveUrl(buildContext)) ?? undefined\n } catch {\n // Skip URL resolution errors\n }\n }\n\n // Resolve links from config (if not already set from buildSource)\n if (!links && config?.resolveLinks) {\n try {\n links = (await config.resolveLinks(buildContext)) ?? undefined\n } catch {\n // Skip link resolution errors\n }\n }\n\n return { presenter, url, links }\n}\n\n/**\n * Create a presenter enricher that loads data from entity_indexes and computes presenter.\n * Uses formatResult from search.ts configs when available, otherwise falls back to extracting\n * common fields like display_name, name, title from the doc.\n *\n * Optimizations:\n * - Single batch DB query for all entity types (instead of one per type)\n * - Parallel Promise.all for formatResult/buildSource calls\n * - Tenant/organization scoping for security\n * - Chunked queries to avoid DB parameter limits\n * - Automatic decryption of encrypted fields when encryption service is provided\n */\nexport function createPresenterEnricher(\n db: Kysely<any>,\n entityConfigMap: Map<EntityId, SearchEntityConfig>,\n queryEngine?: QueryEngine,\n encryptionService?: TenantDataEncryptionService | null,\n): PresenterEnricherFn {\n return async (results, tenantId, organizationId) => {\n const shouldEnrich = (result: SearchResult): boolean =>\n needsSearchResultEnrichment(result) || entityConfigMap.has(result.entityId as EntityId)\n\n const missingResults = results.filter(shouldEnrich)\n if (missingResults.length === 0) return results\n\n // Group by entity type for config lookup\n const byEntityType = new Map<string, SearchResult[]>()\n for (const result of missingResults) {\n const group = byEntityType.get(result.entityId) ?? []\n group.push(result)\n byEntityType.set(result.entityId, group)\n }\n\n // Single batch query for all docs across all entity types\n const rawDocs = await fetchDocsBatch(db, byEntityType, tenantId, organizationId)\n\n // Decrypt docs in parallel using DEK cache for efficiency\n const dekCache = new Map<string | null, string | null>()\n\n const decryptedDocs = await Promise.all(\n rawDocs.map(async (row) => {\n try {\n // Use organization_id from the doc itself for proper encryption map lookup\n // This is critical for global search where organizationId param is null\n const docData = row.doc as Record<string, unknown>\n const docOrgId = (docData.organization_id as string | null | undefined) ?? organizationId\n const scope = { tenantId, organizationId: docOrgId }\n\n const decryptedDoc = await decryptIndexDocForSearch(\n row.entity_type,\n row.doc,\n scope,\n encryptionService ?? null,\n dekCache,\n )\n return { ...row, doc: decryptedDoc }\n } catch (err) {\n logWarning('Failed to decrypt doc', { entityId: row.entity_type, recordId: row.entity_id, err })\n return row // Return original doc if decryption fails\n }\n }),\n )\n\n // Build doc lookup map for fast access\n const docMap = new Map<string, Record<string, unknown>>()\n for (const row of decryptedDocs) {\n docMap.set(`${row.entity_type}:${row.entity_id}`, row.doc)\n }\n\n // Compute presenters and links in parallel\n const enrichmentPromises = missingResults.map(async (result) => {\n const key = `${result.entityId}:${result.recordId}`\n const doc = docMap.get(key)\n\n if (!doc) {\n logWarning('Doc not found in entity_indexes', { entityId: result.entityId, recordId: result.recordId })\n return { key, presenter: null, url: undefined, links: undefined }\n }\n\n const config = entityConfigMap.get(result.entityId as EntityId)\n const enrichment = await computePresenterAndLinks(\n doc,\n result.entityId,\n result.recordId,\n config,\n tenantId,\n organizationId,\n queryEngine,\n )\n\n return { key, ...enrichment }\n })\n\n const computed = await Promise.all(enrichmentPromises)\n\n // Build enrichment map from parallel results\n const enrichmentMap = new Map<string, EnrichmentResult>()\n for (const { key, presenter, url, links } of computed) {\n enrichmentMap.set(key, { presenter, url, links })\n }\n\n // Enrich results with computed presenter, URL, and links\n return results.map((result) => {\n if (!shouldEnrich(result)) return result\n const key = `${result.entityId}:${result.recordId}`\n const enriched = enrichmentMap.get(key)\n if (!enriched) return result\n return {\n ...result,\n presenter: enriched.presenter ?? result.presenter,\n url: result.url ?? enriched.url,\n links: enriched.links ?? result.links,\n }\n })\n }\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -1,5 +1,5 @@
1
1
  import { z } from "zod";
2
- import { hasAllFeatures } from "@open-mercato/shared/security/features";
2
+ import { authorizeFeatures } from "@open-mercato/shared/security/featurePolicy";
3
3
  class SearchToolAuthorizationError extends Error {
4
4
  constructor(message) {
5
5
  super(message);
@@ -21,7 +21,10 @@ function authorizeEntityAccess(entityType, ctx) {
21
21
  `[internal] Entity type "${entityType}" does not declare aclFeatures; access denied`
22
22
  );
23
23
  }
24
- if (!hasAllFeatures(ctx.userFeatures, required)) {
24
+ if (!authorizeFeatures(required, {
25
+ grantedFeatures: ctx.userFeatures,
26
+ unrestricted: ctx.isSuperAdmin
27
+ })) {
25
28
  throw new SearchToolAuthorizationError(
26
29
  `[internal] Insufficient permissions for entity "${entityType}". Required: ${required.join(", ")}`
27
30
  );
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../src/modules/search/ai-tools.ts"],
4
- "sourcesContent": ["import { z } from 'zod'\nimport type {\n SearchEntityConfig,\n SearchResult,\n SearchStrategyId,\n} from '@open-mercato/shared/modules/search'\nimport { hasAllFeatures } from '@open-mercato/shared/security/features'\n\n/**\n * AI Tools definitions for the Search module.\n *\n * These tool definitions are discovered by the ai-assistant module's generator\n * and registered as MCP tools. The search module does not depend on ai-assistant.\n *\n * Tool Definition Format:\n * - name: Unique tool identifier (module_action format, no dots allowed)\n * - description: Human-readable description for AI clients\n * - inputSchema: Zod schema for input validation\n * - requiredFeatures: ACL features required to execute\n * - handler: Async function that executes the tool\n */\n\n/**\n * Tool context provided by the MCP server at execution time.\n */\ntype ToolContext = {\n tenantId: string | null\n organizationId: string | null\n userId: string | null\n container: {\n resolve: <T = unknown>(name: string) => T\n }\n userFeatures: string[]\n isSuperAdmin: boolean\n}\n\n/**\n * Tool definition structure.\n */\ntype AiToolDefinition = {\n name: string\n description: string\n inputSchema: z.ZodType<any>\n requiredFeatures?: string[]\n /**\n * Optional flag \u2014 when true, the tool is treated as a write by the\n * agent runtime and routed through the pending-action approval card.\n * Mirrors the public `AiToolDefinition.isMutation` flag without taking\n * a hard dependency on `@open-mercato/ai-assistant` here.\n */\n isMutation?: boolean\n handler: (input: any, ctx: ToolContext) => Promise<unknown>\n}\n\n/**\n * Minimal shape of the `searchIndexer` DI service consumed by the per-entity\n * ACL / field-policy resolution below. Kept local to avoid importing the full\n * `SearchIndexer` class into the tool module.\n */\ntype SearchIndexerLike = {\n getEntityConfig: (entityId: string) => SearchEntityConfig | undefined\n}\n\nclass SearchToolAuthorizationError extends Error {\n constructor(message: string) {\n super(message)\n this.name = 'SearchToolAuthorizationError'\n }\n}\n\n/**\n * Resolve the search config for an entity type and enforce the per-entity view\n * feature(s). Data-returning tools (`search_get` / `search_aggregate`) MUST NOT\n * rely on the search-administration `search.view` feature to gate record reads.\n *\n * Fails closed: when the entity is not configured for search, or has no\n * `aclFeatures` declared, access is denied so records are never exposed without\n * an explicit owning-module grant.\n */\nfunction authorizeEntityAccess(entityType: string, ctx: ToolContext): SearchEntityConfig {\n const searchIndexer = ctx.container.resolve<SearchIndexerLike>('searchIndexer')\n const config = searchIndexer.getEntityConfig(entityType)\n\n if (!config) {\n throw new SearchToolAuthorizationError(\n `[internal] Entity type \"${entityType}\" is not configured for search`\n )\n }\n\n if (ctx.isSuperAdmin) return config\n\n const required = config.aclFeatures\n if (!required || required.length === 0) {\n throw new SearchToolAuthorizationError(\n `[internal] Entity type \"${entityType}\" does not declare aclFeatures; access denied`\n )\n }\n\n if (!hasAllFeatures(ctx.userFeatures, required)) {\n throw new SearchToolAuthorizationError(\n `[internal] Insufficient permissions for entity \"${entityType}\". Required: ${required.join(', ')}`\n )\n }\n\n return config\n}\n\n/**\n * Strip fields that the entity's field policy marks as sensitive\n * (`hashOnly` exact-match PII or fully `excluded`) from a query-engine record.\n * Defense-in-depth so even an authorized caller never receives decrypted PII or\n * fields a `fieldPolicy` keeps out of search.\n */\nfunction stripSensitiveFields(\n record: Record<string, unknown>,\n config: SearchEntityConfig\n): Record<string, unknown> {\n const sensitive = new Set<string>([\n ...(config.fieldPolicy?.hashOnly ?? []),\n ...(config.fieldPolicy?.excluded ?? []),\n ])\n if (sensitive.size === 0) return record\n const safe: Record<string, unknown> = {}\n for (const [key, value] of Object.entries(record)) {\n if (sensitive.has(key)) continue\n safe[key] = value\n }\n return safe\n}\n\n/**\n * Determine whether a field is a safe aggregation grouping key for an entity.\n * Only fields explicitly declared `searchable` are allowed; `hashOnly` and\n * `excluded` (PII / sensitive) fields can never be enumerated via group-by.\n */\nfunction isGroupByAllowed(field: string, config: SearchEntityConfig): boolean {\n const sensitive = new Set<string>([\n ...(config.fieldPolicy?.hashOnly ?? []),\n ...(config.fieldPolicy?.excluded ?? []),\n ])\n if (sensitive.has(field)) return false\n const searchable = config.fieldPolicy?.searchable ?? []\n return searchable.includes(field)\n}\n\n// =============================================================================\n// Tool Definitions\n// =============================================================================\n\nconst searchQueryTool: AiToolDefinition = {\n name: 'search_query',\n description: `Search across all data using hybrid search. Use this FIRST for finding records.\n\nReturns: title, subtitle, entityType, recordId, url for each match.\nSearches customers, products, orders, deals, and more in one call.`,\n inputSchema: z.object({\n query: z.string().min(1).describe('The search query text'),\n limit: z\n .number()\n .int()\n .min(1)\n .max(100)\n .optional()\n .default(20)\n .describe('Maximum number of results to return (default: 20)'),\n entityTypes: z\n .array(z.string())\n .optional()\n .describe(\n 'Filter to specific entity types (e.g., [\"customers:customer_person_profile\", \"catalog:product\"])'\n ),\n strategies: z\n .array(z.enum(['fulltext', 'vector', 'tokens']))\n .optional()\n .describe('Specific search strategies to use (default: all available)'),\n }),\n requiredFeatures: ['search.global'],\n handler: async (input, ctx) => {\n if (!ctx.tenantId) {\n throw new Error('Tenant context is required for search')\n }\n\n const searchService = ctx.container.resolve<{\n search: (query: string, options: any) => Promise<SearchResult[]>\n }>('searchService')\n\n const results = await searchService.search(input.query, {\n tenantId: ctx.tenantId,\n organizationId: ctx.organizationId,\n entityTypes: input.entityTypes,\n strategies: input.strategies as SearchStrategyId[],\n limit: input.limit,\n })\n\n return {\n query: input.query,\n totalResults: results.length,\n results: results.map((result) => ({\n entityType: result.entityId,\n recordId: result.recordId,\n score: Math.round(result.score * 100) / 100,\n source: result.source,\n title: result.presenter?.title ?? result.recordId,\n subtitle: result.presenter?.subtitle,\n url: result.url,\n })),\n }\n },\n}\n\nconst searchStatusTool: AiToolDefinition = {\n name: 'search_status',\n description:\n 'Get the current status of the search module, including available search strategies and their availability.',\n inputSchema: z.object({}),\n requiredFeatures: ['search.view'],\n handler: async (_input, ctx) => {\n const searchService = ctx.container.resolve<{\n getStrategies: () => Array<{\n id: string\n name: string\n priority: number\n isAvailable: () => Promise<boolean>\n }>\n getDefaultStrategies: () => string[]\n }>('searchService')\n\n const strategies = searchService.getStrategies()\n const defaultStrategies = searchService.getDefaultStrategies()\n\n const strategyStatus = await Promise.all(\n strategies.map(async (strategy) => ({\n id: strategy.id,\n name: strategy.name,\n priority: strategy.priority,\n isAvailable: await strategy.isAvailable(),\n isDefault: defaultStrategies.includes(strategy.id),\n }))\n )\n\n return {\n strategiesRegistered: strategies.length,\n defaultStrategies,\n strategies: strategyStatus,\n }\n },\n}\n\n// =============================================================================\n// search.get - Retrieve full record details by entity type and ID\n// =============================================================================\n\nconst searchGetTool: AiToolDefinition = {\n name: 'search_get',\n description: `Get full record details by entityType and recordId from search_query results.`,\n inputSchema: z.object({\n entityType: z\n .string()\n .describe('The entity type (e.g., \"customers:customer_company_profile\", \"customers:customer_deal\")'),\n recordId: z.string().describe('The record ID (UUID)'),\n }),\n requiredFeatures: ['search.view'],\n handler: async (input, ctx) => {\n if (!ctx.tenantId) {\n throw new Error('Tenant context is required')\n }\n\n const entityConfig = authorizeEntityAccess(input.entityType, ctx)\n\n const queryEngine = ctx.container.resolve<{\n query: (entityId: string, options: any) => Promise<{ items: unknown[]; total: number }>\n }>('queryEngine')\n\n const result = await queryEngine.query(input.entityType, {\n tenantId: ctx.tenantId,\n organizationId: ctx.organizationId,\n filters: { id: input.recordId },\n includeCustomFields: true,\n page: { page: 1, pageSize: 1 },\n })\n\n const rawRecord = result.items[0] as Record<string, unknown> | undefined\n if (!rawRecord) {\n return {\n found: false,\n entityType: input.entityType,\n recordId: input.recordId,\n error: 'Record not found',\n }\n }\n\n const record = stripSensitiveFields(rawRecord, entityConfig)\n\n // Extract custom fields\n const customFields: Record<string, unknown> = {}\n const standardFields: Record<string, unknown> = {}\n for (const [key, value] of Object.entries(record)) {\n if (key.startsWith('cf:') || key.startsWith('cf_')) {\n customFields[key.replace(/^cf[:_]/, '')] = value\n } else {\n standardFields[key] = value\n }\n }\n\n // Build URL based on entity type\n let url: string | null = null\n const id = record.id ?? record.entity_id ?? input.recordId\n if (input.entityType.includes('person')) {\n url = `/backend/customers/people/${id}`\n } else if (input.entityType.includes('company')) {\n url = `/backend/customers/companies/${id}`\n } else if (input.entityType.includes('deal')) {\n url = `/backend/customers/deals/${id}`\n } else if (input.entityType.includes('activity')) {\n const entityId = record.entity_id ?? record.entityId\n url = entityId ? `/backend/customers/companies/${entityId}#activity-${id}` : null\n }\n\n return {\n found: true,\n entityType: input.entityType,\n recordId: input.recordId,\n record: standardFields,\n customFields: Object.keys(customFields).length > 0 ? customFields : undefined,\n url,\n }\n },\n}\n\n// =============================================================================\n// search.schema - Discover searchable entities and their fields\n// =============================================================================\n\nconst searchSchemaTool: AiToolDefinition = {\n name: 'search_schema',\n description:\n 'Discover searchable entities and their fields. Use this to learn what data can be searched and what fields are available for filtering.',\n inputSchema: z.object({\n entityType: z\n .string()\n .optional()\n .describe('Optional: Get schema for a specific entity type only'),\n }),\n requiredFeatures: ['search.view'],\n handler: async (input, ctx) => {\n const searchIndexer = ctx.container.resolve<{\n getAllEntityConfigs: () => Array<{\n entityId: string\n enabled?: boolean\n priority?: number\n strategies?: string[]\n fieldPolicy?: {\n searchable?: string[]\n hashOnly?: string[]\n excluded?: string[]\n }\n }>\n }>('searchIndexer')\n\n const allConfigs = searchIndexer.getAllEntityConfigs()\n const entities: Array<{\n entityId: string\n enabled: boolean\n priority: number\n strategies?: string[]\n searchableFields?: string[]\n hashOnlyFields?: string[]\n excludedFields?: string[]\n }> = []\n\n for (const entityConfig of allConfigs) {\n if (input.entityType && entityConfig.entityId !== input.entityType) {\n continue\n }\n\n entities.push({\n entityId: entityConfig.entityId,\n enabled: entityConfig.enabled !== false,\n priority: entityConfig.priority ?? 5,\n strategies: entityConfig.strategies,\n searchableFields: entityConfig.fieldPolicy?.searchable,\n hashOnlyFields: entityConfig.fieldPolicy?.hashOnly,\n excludedFields: entityConfig.fieldPolicy?.excluded,\n })\n }\n\n if (input.entityType && entities.length === 0) {\n return {\n found: false,\n entityType: input.entityType,\n error: 'Entity type not configured for search',\n }\n }\n\n return {\n totalEntities: entities.length,\n entities: entities.sort((a, b) => (b.priority ?? 0) - (a.priority ?? 0)),\n }\n },\n}\n\n// =============================================================================\n// search.aggregate - Get counts grouped by field values\n// =============================================================================\n\nconst searchAggregateTool: AiToolDefinition = {\n name: 'search_aggregate',\n description:\n 'Get record counts grouped by a field value. Useful for analytics like \"how many deals by stage?\" or \"customers by status\". Samples up to 100 records \u2014 percentages may not reflect the full dataset for large entity sets.',\n inputSchema: z.object({\n entityType: z\n .string()\n .describe('The entity type to aggregate (e.g., \"customers:customer_deal\")'),\n groupBy: z\n .string()\n .describe('The field to group by (e.g., \"status\", \"industry\", \"pipeline_stage\")'),\n limit: z\n .number()\n .int()\n .min(1)\n .max(100)\n .optional()\n .default(20)\n .describe('Maximum number of buckets to return (default: 20)'),\n }),\n requiredFeatures: ['search.view'],\n handler: async (input, ctx) => {\n if (!ctx.tenantId) {\n throw new Error('Tenant context is required')\n }\n\n const entityConfig = authorizeEntityAccess(input.entityType, ctx)\n\n if (!isGroupByAllowed(input.groupBy, entityConfig)) {\n throw new SearchToolAuthorizationError(\n `[internal] Field \"${input.groupBy}\" is not an allowed grouping key for \"${input.entityType}\"`\n )\n }\n\n const queryEngine = ctx.container.resolve<{\n query: (entityId: string, options: any) => Promise<{ items: unknown[]; total: number }>\n }>('queryEngine')\n\n // Fetch records and aggregate in memory\n // Note: For large datasets, this should use database GROUP BY\n const result = await queryEngine.query(input.entityType, {\n tenantId: ctx.tenantId,\n organizationId: ctx.organizationId,\n page: { page: 1, pageSize: 100 },\n })\n\n const counts = new Map<string | null, number>()\n for (const item of result.items as Record<string, unknown>[]) {\n const value = item[input.groupBy]\n const key = value === null || value === undefined ? null : String(value)\n counts.set(key, (counts.get(key) ?? 0) + 1)\n }\n\n const total = result.items.length\n const buckets = Array.from(counts.entries())\n .map(([value, count]) => ({\n value,\n count,\n percentage: Math.round((count / total) * 100 * 100) / 100,\n }))\n .sort((a, b) => b.count - a.count)\n .slice(0, input.limit)\n\n return {\n entityType: input.entityType,\n groupBy: input.groupBy,\n total,\n buckets,\n }\n },\n}\n\nconst searchReindexTool: AiToolDefinition = {\n name: 'search_reindex',\n description:\n 'Trigger a reindex operation for search data. This rebuilds the search index for the specified entity type or all entities.',\n inputSchema: z.object({\n entityType: z\n .string()\n .optional()\n .describe(\n 'Specific entity type to reindex (e.g., \"customers:customer_person_profile\"). If not provided, reindexes all entities.'\n ),\n strategy: z\n .enum(['fulltext', 'vector'])\n .optional()\n .default('fulltext')\n .describe('Which search strategy to reindex (default: fulltext)'),\n recreateIndex: z\n .boolean()\n .optional()\n .default(false)\n .describe('Whether to recreate the index from scratch (default: false)'),\n }),\n requiredFeatures: ['search.reindex'],\n // Reindex changes server-side index state \u2014 must surface as a write so\n // any agent that whitelists it routes through the approval card.\n isMutation: true,\n handler: async (input, ctx) => {\n if (!ctx.tenantId) {\n throw new Error('Tenant context is required for reindex')\n }\n\n const searchIndexer = ctx.container.resolve<{\n reindexEntityToFulltext: (params: any) => Promise<any>\n reindexAllToFulltext: (params: any) => Promise<any>\n reindexEntityToVector: (params: any) => Promise<void>\n reindexAllToVector: (params: any) => Promise<void>\n }>('searchIndexer')\n\n const baseParams = {\n tenantId: ctx.tenantId,\n organizationId: ctx.organizationId,\n recreateIndex: input.recreateIndex,\n useQueue: true,\n }\n\n if (input.strategy === 'vector') {\n if (input.entityType) {\n await searchIndexer.reindexEntityToVector({\n ...baseParams,\n entityId: input.entityType,\n })\n return {\n status: 'started',\n strategy: 'vector',\n entityType: input.entityType,\n message: `Vector reindex started for ${input.entityType}`,\n }\n } else {\n await searchIndexer.reindexAllToVector(baseParams)\n return {\n status: 'started',\n strategy: 'vector',\n entityType: 'all',\n message: 'Vector reindex started for all entities',\n }\n }\n } else {\n if (input.entityType) {\n const result = await searchIndexer.reindexEntityToFulltext({\n ...baseParams,\n entityId: input.entityType,\n })\n return {\n status: 'completed',\n strategy: 'fulltext',\n entityType: input.entityType,\n ...result,\n }\n } else {\n const result = await searchIndexer.reindexAllToFulltext(baseParams)\n return {\n status: 'completed',\n strategy: 'fulltext',\n entityType: 'all',\n ...result,\n }\n }\n }\n },\n}\n\n// =============================================================================\n// Export\n// =============================================================================\n\n/**\n * All AI tools exported by the search module.\n * Discovered by ai-assistant module's generator.\n */\nexport const aiTools = [\n searchQueryTool,\n searchStatusTool,\n searchGetTool,\n searchSchemaTool,\n searchAggregateTool,\n searchReindexTool,\n]\n\nexport default aiTools\n"],
5
- "mappings": "AAAA,SAAS,SAAS;AAMlB,SAAS,sBAAsB;AAyD/B,MAAM,qCAAqC,MAAM;AAAA,EAC/C,YAAY,SAAiB;AAC3B,UAAM,OAAO;AACb,SAAK,OAAO;AAAA,EACd;AACF;AAWA,SAAS,sBAAsB,YAAoB,KAAsC;AACvF,QAAM,gBAAgB,IAAI,UAAU,QAA2B,eAAe;AAC9E,QAAM,SAAS,cAAc,gBAAgB,UAAU;AAEvD,MAAI,CAAC,QAAQ;AACX,UAAM,IAAI;AAAA,MACR,2BAA2B,UAAU;AAAA,IACvC;AAAA,EACF;AAEA,MAAI,IAAI,aAAc,QAAO;AAE7B,QAAM,WAAW,OAAO;AACxB,MAAI,CAAC,YAAY,SAAS,WAAW,GAAG;AACtC,UAAM,IAAI;AAAA,MACR,2BAA2B,UAAU;AAAA,IACvC;AAAA,EACF;AAEA,MAAI,CAAC,eAAe,IAAI,cAAc,QAAQ,GAAG;AAC/C,UAAM,IAAI;AAAA,MACR,mDAAmD,UAAU,gBAAgB,SAAS,KAAK,IAAI,CAAC;AAAA,IAClG;AAAA,EACF;AAEA,SAAO;AACT;AAQA,SAAS,qBACP,QACA,QACyB;AACzB,QAAM,YAAY,oBAAI,IAAY;AAAA,IAChC,GAAI,OAAO,aAAa,YAAY,CAAC;AAAA,IACrC,GAAI,OAAO,aAAa,YAAY,CAAC;AAAA,EACvC,CAAC;AACD,MAAI,UAAU,SAAS,EAAG,QAAO;AACjC,QAAM,OAAgC,CAAC;AACvC,aAAW,CAAC,KAAK,KAAK,KAAK,OAAO,QAAQ,MAAM,GAAG;AACjD,QAAI,UAAU,IAAI,GAAG,EAAG;AACxB,SAAK,GAAG,IAAI;AAAA,EACd;AACA,SAAO;AACT;AAOA,SAAS,iBAAiB,OAAe,QAAqC;AAC5E,QAAM,YAAY,oBAAI,IAAY;AAAA,IAChC,GAAI,OAAO,aAAa,YAAY,CAAC;AAAA,IACrC,GAAI,OAAO,aAAa,YAAY,CAAC;AAAA,EACvC,CAAC;AACD,MAAI,UAAU,IAAI,KAAK,EAAG,QAAO;AACjC,QAAM,aAAa,OAAO,aAAa,cAAc,CAAC;AACtD,SAAO,WAAW,SAAS,KAAK;AAClC;AAMA,MAAM,kBAAoC;AAAA,EACxC,MAAM;AAAA,EACN,aAAa;AAAA;AAAA;AAAA;AAAA,EAIb,aAAa,EAAE,OAAO;AAAA,IACpB,OAAO,EAAE,OAAO,EAAE,IAAI,CAAC,EAAE,SAAS,uBAAuB;AAAA,IACzD,OAAO,EACJ,OAAO,EACP,IAAI,EACJ,IAAI,CAAC,EACL,IAAI,GAAG,EACP,SAAS,EACT,QAAQ,EAAE,EACV,SAAS,mDAAmD;AAAA,IAC/D,aAAa,EACV,MAAM,EAAE,OAAO,CAAC,EAChB,SAAS,EACT;AAAA,MACC;AAAA,IACF;AAAA,IACF,YAAY,EACT,MAAM,EAAE,KAAK,CAAC,YAAY,UAAU,QAAQ,CAAC,CAAC,EAC9C,SAAS,EACT,SAAS,4DAA4D;AAAA,EAC1E,CAAC;AAAA,EACD,kBAAkB,CAAC,eAAe;AAAA,EAClC,SAAS,OAAO,OAAO,QAAQ;AAC7B,QAAI,CAAC,IAAI,UAAU;AACjB,YAAM,IAAI,MAAM,uCAAuC;AAAA,IACzD;AAEA,UAAM,gBAAgB,IAAI,UAAU,QAEjC,eAAe;AAElB,UAAM,UAAU,MAAM,cAAc,OAAO,MAAM,OAAO;AAAA,MACtD,UAAU,IAAI;AAAA,MACd,gBAAgB,IAAI;AAAA,MACpB,aAAa,MAAM;AAAA,MACnB,YAAY,MAAM;AAAA,MAClB,OAAO,MAAM;AAAA,IACf,CAAC;AAED,WAAO;AAAA,MACL,OAAO,MAAM;AAAA,MACb,cAAc,QAAQ;AAAA,MACtB,SAAS,QAAQ,IAAI,CAAC,YAAY;AAAA,QAChC,YAAY,OAAO;AAAA,QACnB,UAAU,OAAO;AAAA,QACjB,OAAO,KAAK,MAAM,OAAO,QAAQ,GAAG,IAAI;AAAA,QACxC,QAAQ,OAAO;AAAA,QACf,OAAO,OAAO,WAAW,SAAS,OAAO;AAAA,QACzC,UAAU,OAAO,WAAW;AAAA,QAC5B,KAAK,OAAO;AAAA,MACd,EAAE;AAAA,IACJ;AAAA,EACF;AACF;AAEA,MAAM,mBAAqC;AAAA,EACzC,MAAM;AAAA,EACN,aACE;AAAA,EACF,aAAa,EAAE,OAAO,CAAC,CAAC;AAAA,EACxB,kBAAkB,CAAC,aAAa;AAAA,EAChC,SAAS,OAAO,QAAQ,QAAQ;AAC9B,UAAM,gBAAgB,IAAI,UAAU,QAQjC,eAAe;AAElB,UAAM,aAAa,cAAc,cAAc;AAC/C,UAAM,oBAAoB,cAAc,qBAAqB;AAE7D,UAAM,iBAAiB,MAAM,QAAQ;AAAA,MACnC,WAAW,IAAI,OAAO,cAAc;AAAA,QAClC,IAAI,SAAS;AAAA,QACb,MAAM,SAAS;AAAA,QACf,UAAU,SAAS;AAAA,QACnB,aAAa,MAAM,SAAS,YAAY;AAAA,QACxC,WAAW,kBAAkB,SAAS,SAAS,EAAE;AAAA,MACnD,EAAE;AAAA,IACJ;AAEA,WAAO;AAAA,MACL,sBAAsB,WAAW;AAAA,MACjC;AAAA,MACA,YAAY;AAAA,IACd;AAAA,EACF;AACF;AAMA,MAAM,gBAAkC;AAAA,EACtC,MAAM;AAAA,EACN,aAAa;AAAA,EACb,aAAa,EAAE,OAAO;AAAA,IACpB,YAAY,EACT,OAAO,EACP,SAAS,yFAAyF;AAAA,IACrG,UAAU,EAAE,OAAO,EAAE,SAAS,sBAAsB;AAAA,EACtD,CAAC;AAAA,EACD,kBAAkB,CAAC,aAAa;AAAA,EAChC,SAAS,OAAO,OAAO,QAAQ;AAC7B,QAAI,CAAC,IAAI,UAAU;AACjB,YAAM,IAAI,MAAM,4BAA4B;AAAA,IAC9C;AAEA,UAAM,eAAe,sBAAsB,MAAM,YAAY,GAAG;AAEhE,UAAM,cAAc,IAAI,UAAU,QAE/B,aAAa;AAEhB,UAAM,SAAS,MAAM,YAAY,MAAM,MAAM,YAAY;AAAA,MACvD,UAAU,IAAI;AAAA,MACd,gBAAgB,IAAI;AAAA,MACpB,SAAS,EAAE,IAAI,MAAM,SAAS;AAAA,MAC9B,qBAAqB;AAAA,MACrB,MAAM,EAAE,MAAM,GAAG,UAAU,EAAE;AAAA,IAC/B,CAAC;AAED,UAAM,YAAY,OAAO,MAAM,CAAC;AAChC,QAAI,CAAC,WAAW;AACd,aAAO;AAAA,QACL,OAAO;AAAA,QACP,YAAY,MAAM;AAAA,QAClB,UAAU,MAAM;AAAA,QAChB,OAAO;AAAA,MACT;AAAA,IACF;AAEA,UAAM,SAAS,qBAAqB,WAAW,YAAY;AAG3D,UAAM,eAAwC,CAAC;AAC/C,UAAM,iBAA0C,CAAC;AACjD,eAAW,CAAC,KAAK,KAAK,KAAK,OAAO,QAAQ,MAAM,GAAG;AACjD,UAAI,IAAI,WAAW,KAAK,KAAK,IAAI,WAAW,KAAK,GAAG;AAClD,qBAAa,IAAI,QAAQ,WAAW,EAAE,CAAC,IAAI;AAAA,MAC7C,OAAO;AACL,uBAAe,GAAG,IAAI;AAAA,MACxB;AAAA,IACF;AAGA,QAAI,MAAqB;AACzB,UAAM,KAAK,OAAO,MAAM,OAAO,aAAa,MAAM;AAClD,QAAI,MAAM,WAAW,SAAS,QAAQ,GAAG;AACvC,YAAM,6BAA6B,EAAE;AAAA,IACvC,WAAW,MAAM,WAAW,SAAS,SAAS,GAAG;AAC/C,YAAM,gCAAgC,EAAE;AAAA,IAC1C,WAAW,MAAM,WAAW,SAAS,MAAM,GAAG;AAC5C,YAAM,4BAA4B,EAAE;AAAA,IACtC,WAAW,MAAM,WAAW,SAAS,UAAU,GAAG;AAChD,YAAM,WAAW,OAAO,aAAa,OAAO;AAC5C,YAAM,WAAW,gCAAgC,QAAQ,aAAa,EAAE,KAAK;AAAA,IAC/E;AAEA,WAAO;AAAA,MACL,OAAO;AAAA,MACP,YAAY,MAAM;AAAA,MAClB,UAAU,MAAM;AAAA,MAChB,QAAQ;AAAA,MACR,cAAc,OAAO,KAAK,YAAY,EAAE,SAAS,IAAI,eAAe;AAAA,MACpE;AAAA,IACF;AAAA,EACF;AACF;AAMA,MAAM,mBAAqC;AAAA,EACzC,MAAM;AAAA,EACN,aACE;AAAA,EACF,aAAa,EAAE,OAAO;AAAA,IACpB,YAAY,EACT,OAAO,EACP,SAAS,EACT,SAAS,sDAAsD;AAAA,EACpE,CAAC;AAAA,EACD,kBAAkB,CAAC,aAAa;AAAA,EAChC,SAAS,OAAO,OAAO,QAAQ;AAC7B,UAAM,gBAAgB,IAAI,UAAU,QAYjC,eAAe;AAElB,UAAM,aAAa,cAAc,oBAAoB;AACrD,UAAM,WAQD,CAAC;AAEN,eAAW,gBAAgB,YAAY;AACrC,UAAI,MAAM,cAAc,aAAa,aAAa,MAAM,YAAY;AAClE;AAAA,MACF;AAEA,eAAS,KAAK;AAAA,QACZ,UAAU,aAAa;AAAA,QACvB,SAAS,aAAa,YAAY;AAAA,QAClC,UAAU,aAAa,YAAY;AAAA,QACnC,YAAY,aAAa;AAAA,QACzB,kBAAkB,aAAa,aAAa;AAAA,QAC5C,gBAAgB,aAAa,aAAa;AAAA,QAC1C,gBAAgB,aAAa,aAAa;AAAA,MAC5C,CAAC;AAAA,IACH;AAEA,QAAI,MAAM,cAAc,SAAS,WAAW,GAAG;AAC7C,aAAO;AAAA,QACL,OAAO;AAAA,QACP,YAAY,MAAM;AAAA,QAClB,OAAO;AAAA,MACT;AAAA,IACF;AAEA,WAAO;AAAA,MACL,eAAe,SAAS;AAAA,MACxB,UAAU,SAAS,KAAK,CAAC,GAAG,OAAO,EAAE,YAAY,MAAM,EAAE,YAAY,EAAE;AAAA,IACzE;AAAA,EACF;AACF;AAMA,MAAM,sBAAwC;AAAA,EAC5C,MAAM;AAAA,EACN,aACE;AAAA,EACF,aAAa,EAAE,OAAO;AAAA,IACpB,YAAY,EACT,OAAO,EACP,SAAS,gEAAgE;AAAA,IAC5E,SAAS,EACN,OAAO,EACP,SAAS,sEAAsE;AAAA,IAClF,OAAO,EACJ,OAAO,EACP,IAAI,EACJ,IAAI,CAAC,EACL,IAAI,GAAG,EACP,SAAS,EACT,QAAQ,EAAE,EACV,SAAS,mDAAmD;AAAA,EACjE,CAAC;AAAA,EACD,kBAAkB,CAAC,aAAa;AAAA,EAChC,SAAS,OAAO,OAAO,QAAQ;AAC7B,QAAI,CAAC,IAAI,UAAU;AACjB,YAAM,IAAI,MAAM,4BAA4B;AAAA,IAC9C;AAEA,UAAM,eAAe,sBAAsB,MAAM,YAAY,GAAG;AAEhE,QAAI,CAAC,iBAAiB,MAAM,SAAS,YAAY,GAAG;AAClD,YAAM,IAAI;AAAA,QACR,qBAAqB,MAAM,OAAO,yCAAyC,MAAM,UAAU;AAAA,MAC7F;AAAA,IACF;AAEA,UAAM,cAAc,IAAI,UAAU,QAE/B,aAAa;AAIhB,UAAM,SAAS,MAAM,YAAY,MAAM,MAAM,YAAY;AAAA,MACvD,UAAU,IAAI;AAAA,MACd,gBAAgB,IAAI;AAAA,MACpB,MAAM,EAAE,MAAM,GAAG,UAAU,IAAI;AAAA,IACjC,CAAC;AAED,UAAM,SAAS,oBAAI,IAA2B;AAC9C,eAAW,QAAQ,OAAO,OAAoC;AAC5D,YAAM,QAAQ,KAAK,MAAM,OAAO;AAChC,YAAM,MAAM,UAAU,QAAQ,UAAU,SAAY,OAAO,OAAO,KAAK;AACvE,aAAO,IAAI,MAAM,OAAO,IAAI,GAAG,KAAK,KAAK,CAAC;AAAA,IAC5C;AAEA,UAAM,QAAQ,OAAO,MAAM;AAC3B,UAAM,UAAU,MAAM,KAAK,OAAO,QAAQ,CAAC,EACxC,IAAI,CAAC,CAAC,OAAO,KAAK,OAAO;AAAA,MACxB;AAAA,MACA;AAAA,MACA,YAAY,KAAK,MAAO,QAAQ,QAAS,MAAM,GAAG,IAAI;AAAA,IACxD,EAAE,EACD,KAAK,CAAC,GAAG,MAAM,EAAE,QAAQ,EAAE,KAAK,EAChC,MAAM,GAAG,MAAM,KAAK;AAEvB,WAAO;AAAA,MACL,YAAY,MAAM;AAAA,MAClB,SAAS,MAAM;AAAA,MACf;AAAA,MACA;AAAA,IACF;AAAA,EACF;AACF;AAEA,MAAM,oBAAsC;AAAA,EAC1C,MAAM;AAAA,EACN,aACE;AAAA,EACF,aAAa,EAAE,OAAO;AAAA,IACpB,YAAY,EACT,OAAO,EACP,SAAS,EACT;AAAA,MACC;AAAA,IACF;AAAA,IACF,UAAU,EACP,KAAK,CAAC,YAAY,QAAQ,CAAC,EAC3B,SAAS,EACT,QAAQ,UAAU,EAClB,SAAS,sDAAsD;AAAA,IAClE,eAAe,EACZ,QAAQ,EACR,SAAS,EACT,QAAQ,KAAK,EACb,SAAS,6DAA6D;AAAA,EAC3E,CAAC;AAAA,EACD,kBAAkB,CAAC,gBAAgB;AAAA;AAAA;AAAA,EAGnC,YAAY;AAAA,EACZ,SAAS,OAAO,OAAO,QAAQ;AAC7B,QAAI,CAAC,IAAI,UAAU;AACjB,YAAM,IAAI,MAAM,wCAAwC;AAAA,IAC1D;AAEA,UAAM,gBAAgB,IAAI,UAAU,QAKjC,eAAe;AAElB,UAAM,aAAa;AAAA,MACjB,UAAU,IAAI;AAAA,MACd,gBAAgB,IAAI;AAAA,MACpB,eAAe,MAAM;AAAA,MACrB,UAAU;AAAA,IACZ;AAEA,QAAI,MAAM,aAAa,UAAU;AAC/B,UAAI,MAAM,YAAY;AACpB,cAAM,cAAc,sBAAsB;AAAA,UACxC,GAAG;AAAA,UACH,UAAU,MAAM;AAAA,QAClB,CAAC;AACD,eAAO;AAAA,UACL,QAAQ;AAAA,UACR,UAAU;AAAA,UACV,YAAY,MAAM;AAAA,UAClB,SAAS,8BAA8B,MAAM,UAAU;AAAA,QACzD;AAAA,MACF,OAAO;AACL,cAAM,cAAc,mBAAmB,UAAU;AACjD,eAAO;AAAA,UACL,QAAQ;AAAA,UACR,UAAU;AAAA,UACV,YAAY;AAAA,UACZ,SAAS;AAAA,QACX;AAAA,MACF;AAAA,IACF,OAAO;AACL,UAAI,MAAM,YAAY;AACpB,cAAM,SAAS,MAAM,cAAc,wBAAwB;AAAA,UACzD,GAAG;AAAA,UACH,UAAU,MAAM;AAAA,QAClB,CAAC;AACD,eAAO;AAAA,UACL,QAAQ;AAAA,UACR,UAAU;AAAA,UACV,YAAY,MAAM;AAAA,UAClB,GAAG;AAAA,QACL;AAAA,MACF,OAAO;AACL,cAAM,SAAS,MAAM,cAAc,qBAAqB,UAAU;AAClE,eAAO;AAAA,UACL,QAAQ;AAAA,UACR,UAAU;AAAA,UACV,YAAY;AAAA,UACZ,GAAG;AAAA,QACL;AAAA,MACF;AAAA,IACF;AAAA,EACF;AACF;AAUO,MAAM,UAAU;AAAA,EACrB;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;AAEA,IAAO,mBAAQ;",
4
+ "sourcesContent": ["import { z } from 'zod'\nimport type {\n SearchEntityConfig,\n SearchResult,\n SearchStrategyId,\n} from '@open-mercato/shared/modules/search'\nimport { authorizeFeatures } from '@open-mercato/shared/security/featurePolicy'\n\n/**\n * AI Tools definitions for the Search module.\n *\n * These tool definitions are discovered by the ai-assistant module's generator\n * and registered as MCP tools. The search module does not depend on ai-assistant.\n *\n * Tool Definition Format:\n * - name: Unique tool identifier (module_action format, no dots allowed)\n * - description: Human-readable description for AI clients\n * - inputSchema: Zod schema for input validation\n * - requiredFeatures: ACL features required to execute\n * - handler: Async function that executes the tool\n */\n\n/**\n * Tool context provided by the MCP server at execution time.\n */\ntype ToolContext = {\n tenantId: string | null\n organizationId: string | null\n userId: string | null\n container: {\n resolve: <T = unknown>(name: string) => T\n }\n userFeatures: string[]\n isSuperAdmin: boolean\n}\n\n/**\n * Tool definition structure.\n */\ntype AiToolDefinition = {\n name: string\n description: string\n inputSchema: z.ZodType<any>\n requiredFeatures?: string[]\n /**\n * Optional flag \u2014 when true, the tool is treated as a write by the\n * agent runtime and routed through the pending-action approval card.\n * Mirrors the public `AiToolDefinition.isMutation` flag without taking\n * a hard dependency on `@open-mercato/ai-assistant` here.\n */\n isMutation?: boolean\n handler: (input: any, ctx: ToolContext) => Promise<unknown>\n}\n\n/**\n * Minimal shape of the `searchIndexer` DI service consumed by the per-entity\n * ACL / field-policy resolution below. Kept local to avoid importing the full\n * `SearchIndexer` class into the tool module.\n */\ntype SearchIndexerLike = {\n getEntityConfig: (entityId: string) => SearchEntityConfig | undefined\n}\n\nclass SearchToolAuthorizationError extends Error {\n constructor(message: string) {\n super(message)\n this.name = 'SearchToolAuthorizationError'\n }\n}\n\n/**\n * Resolve the search config for an entity type and enforce the per-entity view\n * feature(s). Data-returning tools (`search_get` / `search_aggregate`) MUST NOT\n * rely on the search-administration `search.view` feature to gate record reads.\n *\n * Fails closed: when the entity is not configured for search, or has no\n * `aclFeatures` declared, access is denied so records are never exposed without\n * an explicit owning-module grant.\n */\nfunction authorizeEntityAccess(entityType: string, ctx: ToolContext): SearchEntityConfig {\n const searchIndexer = ctx.container.resolve<SearchIndexerLike>('searchIndexer')\n const config = searchIndexer.getEntityConfig(entityType)\n\n if (!config) {\n throw new SearchToolAuthorizationError(\n `[internal] Entity type \"${entityType}\" is not configured for search`\n )\n }\n\n if (ctx.isSuperAdmin) return config\n\n const required = config.aclFeatures\n if (!required || required.length === 0) {\n throw new SearchToolAuthorizationError(\n `[internal] Entity type \"${entityType}\" does not declare aclFeatures; access denied`\n )\n }\n\n if (!authorizeFeatures(required, {\n grantedFeatures: ctx.userFeatures,\n unrestricted: ctx.isSuperAdmin,\n })) {\n throw new SearchToolAuthorizationError(\n `[internal] Insufficient permissions for entity \"${entityType}\". Required: ${required.join(', ')}`\n )\n }\n\n return config\n}\n\n/**\n * Strip fields that the entity's field policy marks as sensitive\n * (`hashOnly` exact-match PII or fully `excluded`) from a query-engine record.\n * Defense-in-depth so even an authorized caller never receives decrypted PII or\n * fields a `fieldPolicy` keeps out of search.\n */\nfunction stripSensitiveFields(\n record: Record<string, unknown>,\n config: SearchEntityConfig\n): Record<string, unknown> {\n const sensitive = new Set<string>([\n ...(config.fieldPolicy?.hashOnly ?? []),\n ...(config.fieldPolicy?.excluded ?? []),\n ])\n if (sensitive.size === 0) return record\n const safe: Record<string, unknown> = {}\n for (const [key, value] of Object.entries(record)) {\n if (sensitive.has(key)) continue\n safe[key] = value\n }\n return safe\n}\n\n/**\n * Determine whether a field is a safe aggregation grouping key for an entity.\n * Only fields explicitly declared `searchable` are allowed; `hashOnly` and\n * `excluded` (PII / sensitive) fields can never be enumerated via group-by.\n */\nfunction isGroupByAllowed(field: string, config: SearchEntityConfig): boolean {\n const sensitive = new Set<string>([\n ...(config.fieldPolicy?.hashOnly ?? []),\n ...(config.fieldPolicy?.excluded ?? []),\n ])\n if (sensitive.has(field)) return false\n const searchable = config.fieldPolicy?.searchable ?? []\n return searchable.includes(field)\n}\n\n// =============================================================================\n// Tool Definitions\n// =============================================================================\n\nconst searchQueryTool: AiToolDefinition = {\n name: 'search_query',\n description: `Search across all data using hybrid search. Use this FIRST for finding records.\n\nReturns: title, subtitle, entityType, recordId, url for each match.\nSearches customers, products, orders, deals, and more in one call.`,\n inputSchema: z.object({\n query: z.string().min(1).describe('The search query text'),\n limit: z\n .number()\n .int()\n .min(1)\n .max(100)\n .optional()\n .default(20)\n .describe('Maximum number of results to return (default: 20)'),\n entityTypes: z\n .array(z.string())\n .optional()\n .describe(\n 'Filter to specific entity types (e.g., [\"customers:customer_person_profile\", \"catalog:product\"])'\n ),\n strategies: z\n .array(z.enum(['fulltext', 'vector', 'tokens']))\n .optional()\n .describe('Specific search strategies to use (default: all available)'),\n }),\n requiredFeatures: ['search.global'],\n handler: async (input, ctx) => {\n if (!ctx.tenantId) {\n throw new Error('Tenant context is required for search')\n }\n\n const searchService = ctx.container.resolve<{\n search: (query: string, options: any) => Promise<SearchResult[]>\n }>('searchService')\n\n const results = await searchService.search(input.query, {\n tenantId: ctx.tenantId,\n organizationId: ctx.organizationId,\n entityTypes: input.entityTypes,\n strategies: input.strategies as SearchStrategyId[],\n limit: input.limit,\n })\n\n return {\n query: input.query,\n totalResults: results.length,\n results: results.map((result) => ({\n entityType: result.entityId,\n recordId: result.recordId,\n score: Math.round(result.score * 100) / 100,\n source: result.source,\n title: result.presenter?.title ?? result.recordId,\n subtitle: result.presenter?.subtitle,\n url: result.url,\n })),\n }\n },\n}\n\nconst searchStatusTool: AiToolDefinition = {\n name: 'search_status',\n description:\n 'Get the current status of the search module, including available search strategies and their availability.',\n inputSchema: z.object({}),\n requiredFeatures: ['search.view'],\n handler: async (_input, ctx) => {\n const searchService = ctx.container.resolve<{\n getStrategies: () => Array<{\n id: string\n name: string\n priority: number\n isAvailable: () => Promise<boolean>\n }>\n getDefaultStrategies: () => string[]\n }>('searchService')\n\n const strategies = searchService.getStrategies()\n const defaultStrategies = searchService.getDefaultStrategies()\n\n const strategyStatus = await Promise.all(\n strategies.map(async (strategy) => ({\n id: strategy.id,\n name: strategy.name,\n priority: strategy.priority,\n isAvailable: await strategy.isAvailable(),\n isDefault: defaultStrategies.includes(strategy.id),\n }))\n )\n\n return {\n strategiesRegistered: strategies.length,\n defaultStrategies,\n strategies: strategyStatus,\n }\n },\n}\n\n// =============================================================================\n// search.get - Retrieve full record details by entity type and ID\n// =============================================================================\n\nconst searchGetTool: AiToolDefinition = {\n name: 'search_get',\n description: `Get full record details by entityType and recordId from search_query results.`,\n inputSchema: z.object({\n entityType: z\n .string()\n .describe('The entity type (e.g., \"customers:customer_company_profile\", \"customers:customer_deal\")'),\n recordId: z.string().describe('The record ID (UUID)'),\n }),\n requiredFeatures: ['search.view'],\n handler: async (input, ctx) => {\n if (!ctx.tenantId) {\n throw new Error('Tenant context is required')\n }\n\n const entityConfig = authorizeEntityAccess(input.entityType, ctx)\n\n const queryEngine = ctx.container.resolve<{\n query: (entityId: string, options: any) => Promise<{ items: unknown[]; total: number }>\n }>('queryEngine')\n\n const result = await queryEngine.query(input.entityType, {\n tenantId: ctx.tenantId,\n organizationId: ctx.organizationId,\n filters: { id: input.recordId },\n includeCustomFields: true,\n page: { page: 1, pageSize: 1 },\n })\n\n const rawRecord = result.items[0] as Record<string, unknown> | undefined\n if (!rawRecord) {\n return {\n found: false,\n entityType: input.entityType,\n recordId: input.recordId,\n error: 'Record not found',\n }\n }\n\n const record = stripSensitiveFields(rawRecord, entityConfig)\n\n // Extract custom fields\n const customFields: Record<string, unknown> = {}\n const standardFields: Record<string, unknown> = {}\n for (const [key, value] of Object.entries(record)) {\n if (key.startsWith('cf:') || key.startsWith('cf_')) {\n customFields[key.replace(/^cf[:_]/, '')] = value\n } else {\n standardFields[key] = value\n }\n }\n\n // Build URL based on entity type\n let url: string | null = null\n const id = record.id ?? record.entity_id ?? input.recordId\n if (input.entityType.includes('person')) {\n url = `/backend/customers/people/${id}`\n } else if (input.entityType.includes('company')) {\n url = `/backend/customers/companies/${id}`\n } else if (input.entityType.includes('deal')) {\n url = `/backend/customers/deals/${id}`\n } else if (input.entityType.includes('activity')) {\n const entityId = record.entity_id ?? record.entityId\n url = entityId ? `/backend/customers/companies/${entityId}#activity-${id}` : null\n }\n\n return {\n found: true,\n entityType: input.entityType,\n recordId: input.recordId,\n record: standardFields,\n customFields: Object.keys(customFields).length > 0 ? customFields : undefined,\n url,\n }\n },\n}\n\n// =============================================================================\n// search.schema - Discover searchable entities and their fields\n// =============================================================================\n\nconst searchSchemaTool: AiToolDefinition = {\n name: 'search_schema',\n description:\n 'Discover searchable entities and their fields. Use this to learn what data can be searched and what fields are available for filtering.',\n inputSchema: z.object({\n entityType: z\n .string()\n .optional()\n .describe('Optional: Get schema for a specific entity type only'),\n }),\n requiredFeatures: ['search.view'],\n handler: async (input, ctx) => {\n const searchIndexer = ctx.container.resolve<{\n getAllEntityConfigs: () => Array<{\n entityId: string\n enabled?: boolean\n priority?: number\n strategies?: string[]\n fieldPolicy?: {\n searchable?: string[]\n hashOnly?: string[]\n excluded?: string[]\n }\n }>\n }>('searchIndexer')\n\n const allConfigs = searchIndexer.getAllEntityConfigs()\n const entities: Array<{\n entityId: string\n enabled: boolean\n priority: number\n strategies?: string[]\n searchableFields?: string[]\n hashOnlyFields?: string[]\n excludedFields?: string[]\n }> = []\n\n for (const entityConfig of allConfigs) {\n if (input.entityType && entityConfig.entityId !== input.entityType) {\n continue\n }\n\n entities.push({\n entityId: entityConfig.entityId,\n enabled: entityConfig.enabled !== false,\n priority: entityConfig.priority ?? 5,\n strategies: entityConfig.strategies,\n searchableFields: entityConfig.fieldPolicy?.searchable,\n hashOnlyFields: entityConfig.fieldPolicy?.hashOnly,\n excludedFields: entityConfig.fieldPolicy?.excluded,\n })\n }\n\n if (input.entityType && entities.length === 0) {\n return {\n found: false,\n entityType: input.entityType,\n error: 'Entity type not configured for search',\n }\n }\n\n return {\n totalEntities: entities.length,\n entities: entities.sort((a, b) => (b.priority ?? 0) - (a.priority ?? 0)),\n }\n },\n}\n\n// =============================================================================\n// search.aggregate - Get counts grouped by field values\n// =============================================================================\n\nconst searchAggregateTool: AiToolDefinition = {\n name: 'search_aggregate',\n description:\n 'Get record counts grouped by a field value. Useful for analytics like \"how many deals by stage?\" or \"customers by status\". Samples up to 100 records \u2014 percentages may not reflect the full dataset for large entity sets.',\n inputSchema: z.object({\n entityType: z\n .string()\n .describe('The entity type to aggregate (e.g., \"customers:customer_deal\")'),\n groupBy: z\n .string()\n .describe('The field to group by (e.g., \"status\", \"industry\", \"pipeline_stage\")'),\n limit: z\n .number()\n .int()\n .min(1)\n .max(100)\n .optional()\n .default(20)\n .describe('Maximum number of buckets to return (default: 20)'),\n }),\n requiredFeatures: ['search.view'],\n handler: async (input, ctx) => {\n if (!ctx.tenantId) {\n throw new Error('Tenant context is required')\n }\n\n const entityConfig = authorizeEntityAccess(input.entityType, ctx)\n\n if (!isGroupByAllowed(input.groupBy, entityConfig)) {\n throw new SearchToolAuthorizationError(\n `[internal] Field \"${input.groupBy}\" is not an allowed grouping key for \"${input.entityType}\"`\n )\n }\n\n const queryEngine = ctx.container.resolve<{\n query: (entityId: string, options: any) => Promise<{ items: unknown[]; total: number }>\n }>('queryEngine')\n\n // Fetch records and aggregate in memory\n // Note: For large datasets, this should use database GROUP BY\n const result = await queryEngine.query(input.entityType, {\n tenantId: ctx.tenantId,\n organizationId: ctx.organizationId,\n page: { page: 1, pageSize: 100 },\n })\n\n const counts = new Map<string | null, number>()\n for (const item of result.items as Record<string, unknown>[]) {\n const value = item[input.groupBy]\n const key = value === null || value === undefined ? null : String(value)\n counts.set(key, (counts.get(key) ?? 0) + 1)\n }\n\n const total = result.items.length\n const buckets = Array.from(counts.entries())\n .map(([value, count]) => ({\n value,\n count,\n percentage: Math.round((count / total) * 100 * 100) / 100,\n }))\n .sort((a, b) => b.count - a.count)\n .slice(0, input.limit)\n\n return {\n entityType: input.entityType,\n groupBy: input.groupBy,\n total,\n buckets,\n }\n },\n}\n\nconst searchReindexTool: AiToolDefinition = {\n name: 'search_reindex',\n description:\n 'Trigger a reindex operation for search data. This rebuilds the search index for the specified entity type or all entities.',\n inputSchema: z.object({\n entityType: z\n .string()\n .optional()\n .describe(\n 'Specific entity type to reindex (e.g., \"customers:customer_person_profile\"). If not provided, reindexes all entities.'\n ),\n strategy: z\n .enum(['fulltext', 'vector'])\n .optional()\n .default('fulltext')\n .describe('Which search strategy to reindex (default: fulltext)'),\n recreateIndex: z\n .boolean()\n .optional()\n .default(false)\n .describe('Whether to recreate the index from scratch (default: false)'),\n }),\n requiredFeatures: ['search.reindex'],\n // Reindex changes server-side index state \u2014 must surface as a write so\n // any agent that whitelists it routes through the approval card.\n isMutation: true,\n handler: async (input, ctx) => {\n if (!ctx.tenantId) {\n throw new Error('Tenant context is required for reindex')\n }\n\n const searchIndexer = ctx.container.resolve<{\n reindexEntityToFulltext: (params: any) => Promise<any>\n reindexAllToFulltext: (params: any) => Promise<any>\n reindexEntityToVector: (params: any) => Promise<void>\n reindexAllToVector: (params: any) => Promise<void>\n }>('searchIndexer')\n\n const baseParams = {\n tenantId: ctx.tenantId,\n organizationId: ctx.organizationId,\n recreateIndex: input.recreateIndex,\n useQueue: true,\n }\n\n if (input.strategy === 'vector') {\n if (input.entityType) {\n await searchIndexer.reindexEntityToVector({\n ...baseParams,\n entityId: input.entityType,\n })\n return {\n status: 'started',\n strategy: 'vector',\n entityType: input.entityType,\n message: `Vector reindex started for ${input.entityType}`,\n }\n } else {\n await searchIndexer.reindexAllToVector(baseParams)\n return {\n status: 'started',\n strategy: 'vector',\n entityType: 'all',\n message: 'Vector reindex started for all entities',\n }\n }\n } else {\n if (input.entityType) {\n const result = await searchIndexer.reindexEntityToFulltext({\n ...baseParams,\n entityId: input.entityType,\n })\n return {\n status: 'completed',\n strategy: 'fulltext',\n entityType: input.entityType,\n ...result,\n }\n } else {\n const result = await searchIndexer.reindexAllToFulltext(baseParams)\n return {\n status: 'completed',\n strategy: 'fulltext',\n entityType: 'all',\n ...result,\n }\n }\n }\n },\n}\n\n// =============================================================================\n// Export\n// =============================================================================\n\n/**\n * All AI tools exported by the search module.\n * Discovered by ai-assistant module's generator.\n */\nexport const aiTools = [\n searchQueryTool,\n searchStatusTool,\n searchGetTool,\n searchSchemaTool,\n searchAggregateTool,\n searchReindexTool,\n]\n\nexport default aiTools\n"],
5
+ "mappings": "AAAA,SAAS,SAAS;AAMlB,SAAS,yBAAyB;AAyDlC,MAAM,qCAAqC,MAAM;AAAA,EAC/C,YAAY,SAAiB;AAC3B,UAAM,OAAO;AACb,SAAK,OAAO;AAAA,EACd;AACF;AAWA,SAAS,sBAAsB,YAAoB,KAAsC;AACvF,QAAM,gBAAgB,IAAI,UAAU,QAA2B,eAAe;AAC9E,QAAM,SAAS,cAAc,gBAAgB,UAAU;AAEvD,MAAI,CAAC,QAAQ;AACX,UAAM,IAAI;AAAA,MACR,2BAA2B,UAAU;AAAA,IACvC;AAAA,EACF;AAEA,MAAI,IAAI,aAAc,QAAO;AAE7B,QAAM,WAAW,OAAO;AACxB,MAAI,CAAC,YAAY,SAAS,WAAW,GAAG;AACtC,UAAM,IAAI;AAAA,MACR,2BAA2B,UAAU;AAAA,IACvC;AAAA,EACF;AAEA,MAAI,CAAC,kBAAkB,UAAU;AAAA,IAC/B,iBAAiB,IAAI;AAAA,IACrB,cAAc,IAAI;AAAA,EACpB,CAAC,GAAG;AACF,UAAM,IAAI;AAAA,MACR,mDAAmD,UAAU,gBAAgB,SAAS,KAAK,IAAI,CAAC;AAAA,IAClG;AAAA,EACF;AAEA,SAAO;AACT;AAQA,SAAS,qBACP,QACA,QACyB;AACzB,QAAM,YAAY,oBAAI,IAAY;AAAA,IAChC,GAAI,OAAO,aAAa,YAAY,CAAC;AAAA,IACrC,GAAI,OAAO,aAAa,YAAY,CAAC;AAAA,EACvC,CAAC;AACD,MAAI,UAAU,SAAS,EAAG,QAAO;AACjC,QAAM,OAAgC,CAAC;AACvC,aAAW,CAAC,KAAK,KAAK,KAAK,OAAO,QAAQ,MAAM,GAAG;AACjD,QAAI,UAAU,IAAI,GAAG,EAAG;AACxB,SAAK,GAAG,IAAI;AAAA,EACd;AACA,SAAO;AACT;AAOA,SAAS,iBAAiB,OAAe,QAAqC;AAC5E,QAAM,YAAY,oBAAI,IAAY;AAAA,IAChC,GAAI,OAAO,aAAa,YAAY,CAAC;AAAA,IACrC,GAAI,OAAO,aAAa,YAAY,CAAC;AAAA,EACvC,CAAC;AACD,MAAI,UAAU,IAAI,KAAK,EAAG,QAAO;AACjC,QAAM,aAAa,OAAO,aAAa,cAAc,CAAC;AACtD,SAAO,WAAW,SAAS,KAAK;AAClC;AAMA,MAAM,kBAAoC;AAAA,EACxC,MAAM;AAAA,EACN,aAAa;AAAA;AAAA;AAAA;AAAA,EAIb,aAAa,EAAE,OAAO;AAAA,IACpB,OAAO,EAAE,OAAO,EAAE,IAAI,CAAC,EAAE,SAAS,uBAAuB;AAAA,IACzD,OAAO,EACJ,OAAO,EACP,IAAI,EACJ,IAAI,CAAC,EACL,IAAI,GAAG,EACP,SAAS,EACT,QAAQ,EAAE,EACV,SAAS,mDAAmD;AAAA,IAC/D,aAAa,EACV,MAAM,EAAE,OAAO,CAAC,EAChB,SAAS,EACT;AAAA,MACC;AAAA,IACF;AAAA,IACF,YAAY,EACT,MAAM,EAAE,KAAK,CAAC,YAAY,UAAU,QAAQ,CAAC,CAAC,EAC9C,SAAS,EACT,SAAS,4DAA4D;AAAA,EAC1E,CAAC;AAAA,EACD,kBAAkB,CAAC,eAAe;AAAA,EAClC,SAAS,OAAO,OAAO,QAAQ;AAC7B,QAAI,CAAC,IAAI,UAAU;AACjB,YAAM,IAAI,MAAM,uCAAuC;AAAA,IACzD;AAEA,UAAM,gBAAgB,IAAI,UAAU,QAEjC,eAAe;AAElB,UAAM,UAAU,MAAM,cAAc,OAAO,MAAM,OAAO;AAAA,MACtD,UAAU,IAAI;AAAA,MACd,gBAAgB,IAAI;AAAA,MACpB,aAAa,MAAM;AAAA,MACnB,YAAY,MAAM;AAAA,MAClB,OAAO,MAAM;AAAA,IACf,CAAC;AAED,WAAO;AAAA,MACL,OAAO,MAAM;AAAA,MACb,cAAc,QAAQ;AAAA,MACtB,SAAS,QAAQ,IAAI,CAAC,YAAY;AAAA,QAChC,YAAY,OAAO;AAAA,QACnB,UAAU,OAAO;AAAA,QACjB,OAAO,KAAK,MAAM,OAAO,QAAQ,GAAG,IAAI;AAAA,QACxC,QAAQ,OAAO;AAAA,QACf,OAAO,OAAO,WAAW,SAAS,OAAO;AAAA,QACzC,UAAU,OAAO,WAAW;AAAA,QAC5B,KAAK,OAAO;AAAA,MACd,EAAE;AAAA,IACJ;AAAA,EACF;AACF;AAEA,MAAM,mBAAqC;AAAA,EACzC,MAAM;AAAA,EACN,aACE;AAAA,EACF,aAAa,EAAE,OAAO,CAAC,CAAC;AAAA,EACxB,kBAAkB,CAAC,aAAa;AAAA,EAChC,SAAS,OAAO,QAAQ,QAAQ;AAC9B,UAAM,gBAAgB,IAAI,UAAU,QAQjC,eAAe;AAElB,UAAM,aAAa,cAAc,cAAc;AAC/C,UAAM,oBAAoB,cAAc,qBAAqB;AAE7D,UAAM,iBAAiB,MAAM,QAAQ;AAAA,MACnC,WAAW,IAAI,OAAO,cAAc;AAAA,QAClC,IAAI,SAAS;AAAA,QACb,MAAM,SAAS;AAAA,QACf,UAAU,SAAS;AAAA,QACnB,aAAa,MAAM,SAAS,YAAY;AAAA,QACxC,WAAW,kBAAkB,SAAS,SAAS,EAAE;AAAA,MACnD,EAAE;AAAA,IACJ;AAEA,WAAO;AAAA,MACL,sBAAsB,WAAW;AAAA,MACjC;AAAA,MACA,YAAY;AAAA,IACd;AAAA,EACF;AACF;AAMA,MAAM,gBAAkC;AAAA,EACtC,MAAM;AAAA,EACN,aAAa;AAAA,EACb,aAAa,EAAE,OAAO;AAAA,IACpB,YAAY,EACT,OAAO,EACP,SAAS,yFAAyF;AAAA,IACrG,UAAU,EAAE,OAAO,EAAE,SAAS,sBAAsB;AAAA,EACtD,CAAC;AAAA,EACD,kBAAkB,CAAC,aAAa;AAAA,EAChC,SAAS,OAAO,OAAO,QAAQ;AAC7B,QAAI,CAAC,IAAI,UAAU;AACjB,YAAM,IAAI,MAAM,4BAA4B;AAAA,IAC9C;AAEA,UAAM,eAAe,sBAAsB,MAAM,YAAY,GAAG;AAEhE,UAAM,cAAc,IAAI,UAAU,QAE/B,aAAa;AAEhB,UAAM,SAAS,MAAM,YAAY,MAAM,MAAM,YAAY;AAAA,MACvD,UAAU,IAAI;AAAA,MACd,gBAAgB,IAAI;AAAA,MACpB,SAAS,EAAE,IAAI,MAAM,SAAS;AAAA,MAC9B,qBAAqB;AAAA,MACrB,MAAM,EAAE,MAAM,GAAG,UAAU,EAAE;AAAA,IAC/B,CAAC;AAED,UAAM,YAAY,OAAO,MAAM,CAAC;AAChC,QAAI,CAAC,WAAW;AACd,aAAO;AAAA,QACL,OAAO;AAAA,QACP,YAAY,MAAM;AAAA,QAClB,UAAU,MAAM;AAAA,QAChB,OAAO;AAAA,MACT;AAAA,IACF;AAEA,UAAM,SAAS,qBAAqB,WAAW,YAAY;AAG3D,UAAM,eAAwC,CAAC;AAC/C,UAAM,iBAA0C,CAAC;AACjD,eAAW,CAAC,KAAK,KAAK,KAAK,OAAO,QAAQ,MAAM,GAAG;AACjD,UAAI,IAAI,WAAW,KAAK,KAAK,IAAI,WAAW,KAAK,GAAG;AAClD,qBAAa,IAAI,QAAQ,WAAW,EAAE,CAAC,IAAI;AAAA,MAC7C,OAAO;AACL,uBAAe,GAAG,IAAI;AAAA,MACxB;AAAA,IACF;AAGA,QAAI,MAAqB;AACzB,UAAM,KAAK,OAAO,MAAM,OAAO,aAAa,MAAM;AAClD,QAAI,MAAM,WAAW,SAAS,QAAQ,GAAG;AACvC,YAAM,6BAA6B,EAAE;AAAA,IACvC,WAAW,MAAM,WAAW,SAAS,SAAS,GAAG;AAC/C,YAAM,gCAAgC,EAAE;AAAA,IAC1C,WAAW,MAAM,WAAW,SAAS,MAAM,GAAG;AAC5C,YAAM,4BAA4B,EAAE;AAAA,IACtC,WAAW,MAAM,WAAW,SAAS,UAAU,GAAG;AAChD,YAAM,WAAW,OAAO,aAAa,OAAO;AAC5C,YAAM,WAAW,gCAAgC,QAAQ,aAAa,EAAE,KAAK;AAAA,IAC/E;AAEA,WAAO;AAAA,MACL,OAAO;AAAA,MACP,YAAY,MAAM;AAAA,MAClB,UAAU,MAAM;AAAA,MAChB,QAAQ;AAAA,MACR,cAAc,OAAO,KAAK,YAAY,EAAE,SAAS,IAAI,eAAe;AAAA,MACpE;AAAA,IACF;AAAA,EACF;AACF;AAMA,MAAM,mBAAqC;AAAA,EACzC,MAAM;AAAA,EACN,aACE;AAAA,EACF,aAAa,EAAE,OAAO;AAAA,IACpB,YAAY,EACT,OAAO,EACP,SAAS,EACT,SAAS,sDAAsD;AAAA,EACpE,CAAC;AAAA,EACD,kBAAkB,CAAC,aAAa;AAAA,EAChC,SAAS,OAAO,OAAO,QAAQ;AAC7B,UAAM,gBAAgB,IAAI,UAAU,QAYjC,eAAe;AAElB,UAAM,aAAa,cAAc,oBAAoB;AACrD,UAAM,WAQD,CAAC;AAEN,eAAW,gBAAgB,YAAY;AACrC,UAAI,MAAM,cAAc,aAAa,aAAa,MAAM,YAAY;AAClE;AAAA,MACF;AAEA,eAAS,KAAK;AAAA,QACZ,UAAU,aAAa;AAAA,QACvB,SAAS,aAAa,YAAY;AAAA,QAClC,UAAU,aAAa,YAAY;AAAA,QACnC,YAAY,aAAa;AAAA,QACzB,kBAAkB,aAAa,aAAa;AAAA,QAC5C,gBAAgB,aAAa,aAAa;AAAA,QAC1C,gBAAgB,aAAa,aAAa;AAAA,MAC5C,CAAC;AAAA,IACH;AAEA,QAAI,MAAM,cAAc,SAAS,WAAW,GAAG;AAC7C,aAAO;AAAA,QACL,OAAO;AAAA,QACP,YAAY,MAAM;AAAA,QAClB,OAAO;AAAA,MACT;AAAA,IACF;AAEA,WAAO;AAAA,MACL,eAAe,SAAS;AAAA,MACxB,UAAU,SAAS,KAAK,CAAC,GAAG,OAAO,EAAE,YAAY,MAAM,EAAE,YAAY,EAAE;AAAA,IACzE;AAAA,EACF;AACF;AAMA,MAAM,sBAAwC;AAAA,EAC5C,MAAM;AAAA,EACN,aACE;AAAA,EACF,aAAa,EAAE,OAAO;AAAA,IACpB,YAAY,EACT,OAAO,EACP,SAAS,gEAAgE;AAAA,IAC5E,SAAS,EACN,OAAO,EACP,SAAS,sEAAsE;AAAA,IAClF,OAAO,EACJ,OAAO,EACP,IAAI,EACJ,IAAI,CAAC,EACL,IAAI,GAAG,EACP,SAAS,EACT,QAAQ,EAAE,EACV,SAAS,mDAAmD;AAAA,EACjE,CAAC;AAAA,EACD,kBAAkB,CAAC,aAAa;AAAA,EAChC,SAAS,OAAO,OAAO,QAAQ;AAC7B,QAAI,CAAC,IAAI,UAAU;AACjB,YAAM,IAAI,MAAM,4BAA4B;AAAA,IAC9C;AAEA,UAAM,eAAe,sBAAsB,MAAM,YAAY,GAAG;AAEhE,QAAI,CAAC,iBAAiB,MAAM,SAAS,YAAY,GAAG;AAClD,YAAM,IAAI;AAAA,QACR,qBAAqB,MAAM,OAAO,yCAAyC,MAAM,UAAU;AAAA,MAC7F;AAAA,IACF;AAEA,UAAM,cAAc,IAAI,UAAU,QAE/B,aAAa;AAIhB,UAAM,SAAS,MAAM,YAAY,MAAM,MAAM,YAAY;AAAA,MACvD,UAAU,IAAI;AAAA,MACd,gBAAgB,IAAI;AAAA,MACpB,MAAM,EAAE,MAAM,GAAG,UAAU,IAAI;AAAA,IACjC,CAAC;AAED,UAAM,SAAS,oBAAI,IAA2B;AAC9C,eAAW,QAAQ,OAAO,OAAoC;AAC5D,YAAM,QAAQ,KAAK,MAAM,OAAO;AAChC,YAAM,MAAM,UAAU,QAAQ,UAAU,SAAY,OAAO,OAAO,KAAK;AACvE,aAAO,IAAI,MAAM,OAAO,IAAI,GAAG,KAAK,KAAK,CAAC;AAAA,IAC5C;AAEA,UAAM,QAAQ,OAAO,MAAM;AAC3B,UAAM,UAAU,MAAM,KAAK,OAAO,QAAQ,CAAC,EACxC,IAAI,CAAC,CAAC,OAAO,KAAK,OAAO;AAAA,MACxB;AAAA,MACA;AAAA,MACA,YAAY,KAAK,MAAO,QAAQ,QAAS,MAAM,GAAG,IAAI;AAAA,IACxD,EAAE,EACD,KAAK,CAAC,GAAG,MAAM,EAAE,QAAQ,EAAE,KAAK,EAChC,MAAM,GAAG,MAAM,KAAK;AAEvB,WAAO;AAAA,MACL,YAAY,MAAM;AAAA,MAClB,SAAS,MAAM;AAAA,MACf;AAAA,MACA;AAAA,IACF;AAAA,EACF;AACF;AAEA,MAAM,oBAAsC;AAAA,EAC1C,MAAM;AAAA,EACN,aACE;AAAA,EACF,aAAa,EAAE,OAAO;AAAA,IACpB,YAAY,EACT,OAAO,EACP,SAAS,EACT;AAAA,MACC;AAAA,IACF;AAAA,IACF,UAAU,EACP,KAAK,CAAC,YAAY,QAAQ,CAAC,EAC3B,SAAS,EACT,QAAQ,UAAU,EAClB,SAAS,sDAAsD;AAAA,IAClE,eAAe,EACZ,QAAQ,EACR,SAAS,EACT,QAAQ,KAAK,EACb,SAAS,6DAA6D;AAAA,EAC3E,CAAC;AAAA,EACD,kBAAkB,CAAC,gBAAgB;AAAA;AAAA;AAAA,EAGnC,YAAY;AAAA,EACZ,SAAS,OAAO,OAAO,QAAQ;AAC7B,QAAI,CAAC,IAAI,UAAU;AACjB,YAAM,IAAI,MAAM,wCAAwC;AAAA,IAC1D;AAEA,UAAM,gBAAgB,IAAI,UAAU,QAKjC,eAAe;AAElB,UAAM,aAAa;AAAA,MACjB,UAAU,IAAI;AAAA,MACd,gBAAgB,IAAI;AAAA,MACpB,eAAe,MAAM;AAAA,MACrB,UAAU;AAAA,IACZ;AAEA,QAAI,MAAM,aAAa,UAAU;AAC/B,UAAI,MAAM,YAAY;AACpB,cAAM,cAAc,sBAAsB;AAAA,UACxC,GAAG;AAAA,UACH,UAAU,MAAM;AAAA,QAClB,CAAC;AACD,eAAO;AAAA,UACL,QAAQ;AAAA,UACR,UAAU;AAAA,UACV,YAAY,MAAM;AAAA,UAClB,SAAS,8BAA8B,MAAM,UAAU;AAAA,QACzD;AAAA,MACF,OAAO;AACL,cAAM,cAAc,mBAAmB,UAAU;AACjD,eAAO;AAAA,UACL,QAAQ;AAAA,UACR,UAAU;AAAA,UACV,YAAY;AAAA,UACZ,SAAS;AAAA,QACX;AAAA,MACF;AAAA,IACF,OAAO;AACL,UAAI,MAAM,YAAY;AACpB,cAAM,SAAS,MAAM,cAAc,wBAAwB;AAAA,UACzD,GAAG;AAAA,UACH,UAAU,MAAM;AAAA,QAClB,CAAC;AACD,eAAO;AAAA,UACL,QAAQ;AAAA,UACR,UAAU;AAAA,UACV,YAAY,MAAM;AAAA,UAClB,GAAG;AAAA,QACL;AAAA,MACF,OAAO;AACL,cAAM,SAAS,MAAM,cAAc,qBAAqB,UAAU;AAClE,eAAO;AAAA,UACL,QAAQ;AAAA,UACR,UAAU;AAAA,UACV,YAAY;AAAA,UACZ,GAAG;AAAA,QACL;AAAA,MACF;AAAA,IACF;AAAA,EACF;AACF;AAUO,MAAM,UAAU;AAAA,EACrB;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;AAEA,IAAO,mBAAQ;",
6
6
  "names": []
7
7
  }
@@ -178,6 +178,8 @@ const vectorDriverStatusSchema = z.object({
178
178
  name: z.string(),
179
179
  configured: z.boolean(),
180
180
  implemented: z.boolean(),
181
+ available: z.boolean().nullable(),
182
+ unavailableReason: z.string().nullable(),
181
183
  envVars: z.array(vectorDriverEnvVarSchema)
182
184
  });
183
185
  const vectorStoreSettingsResponseSchema = z.object({
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../src/modules/search/api/openapi.ts"],
4
- "sourcesContent": ["import { z } from 'zod'\nimport type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'\n\n// ============================================================================\n// Common Schemas\n// ============================================================================\n\nexport const searchStrategyIdSchema = z.enum(['fulltext', 'vector', 'tokens'])\n\nexport const searchResultPresenterSchema = z.object({\n title: z.string(),\n subtitle: z.string().optional(),\n icon: z.string().optional(),\n badge: z.string().optional(),\n})\n\nexport const searchResultLinkSchema = z.object({\n href: z.string(),\n label: z.string(),\n kind: z.enum(['primary', 'secondary']),\n})\n\nexport const searchResultSchema = z.object({\n entityId: z.string().describe('Entity identifier (e.g., \"customers:customer_person_profile\")'),\n recordId: z.string().describe('Primary key of the record'),\n score: z.number().describe('Relevance score (0-1)'),\n source: searchStrategyIdSchema.describe('Which strategy returned this result'),\n presenter: searchResultPresenterSchema.optional(),\n url: z.string().optional(),\n links: z.array(searchResultLinkSchema).optional(),\n metadata: z.record(z.string(), z.unknown()).optional(),\n})\n\nexport const errorResponseSchema = z.object({\n error: z.string(),\n})\n\n// ============================================================================\n// Search Endpoint Schemas (/api/search)\n// ============================================================================\n\nexport const searchQueryParamsSchema = z.object({\n q: z.string().describe('Search query (required)'),\n limit: z.coerce.number().min(1).max(100).optional().describe('Maximum results to return (default: 50, max: 100)'),\n strategies: z.string().optional().describe('Comma-separated strategies to use: fulltext, vector, tokens (e.g., \"fulltext,vector\")'),\n entityTypes: z.string().optional().describe('Comma-separated entity types to filter results (e.g., \"customers:customer_person_profile,catalog:catalog_product,sales:sales_order\")'),\n})\n\nexport const searchResponseSchema = z.object({\n results: z.array(searchResultSchema),\n strategiesUsed: z.array(searchStrategyIdSchema),\n timing: z.number().describe('Search duration in milliseconds'),\n query: z.string(),\n limit: z.number(),\n})\n\n// ============================================================================\n// Global Search Endpoint Schemas (/api/search/global)\n// ============================================================================\n\nexport const globalSearchQueryParamsSchema = z.object({\n q: z.string().describe('Search query (required)'),\n limit: z.coerce.number().min(1).max(100).optional().describe('Maximum results to return (default: 50, max: 100)'),\n entityTypes: z.string().optional().describe('Comma-separated entity types to filter results (e.g., \"customers:customer_person_profile,catalog:catalog_product,sales:sales_order\")'),\n})\n\nexport const globalSearchResponseSchema = z.object({\n results: z.array(searchResultSchema),\n strategiesUsed: z.array(searchStrategyIdSchema),\n strategiesEnabled: z.array(searchStrategyIdSchema),\n timing: z.number().describe('Search duration in milliseconds'),\n query: z.string(),\n limit: z.number(),\n})\n\n// ============================================================================\n// Settings Endpoint Schemas (/api/search/settings)\n// ============================================================================\n\nexport const strategyStatusSchema = z.object({\n id: z.string(),\n name: z.string(),\n priority: z.number(),\n available: z.boolean(),\n})\n\nexport const fulltextStatsSchema = z.object({\n numberOfDocuments: z.number(),\n isIndexing: z.boolean(),\n fieldDistribution: z.record(z.string(), z.number()),\n})\n\nexport const reindexLockSchema = z.object({\n type: z.enum(['fulltext', 'vector']),\n action: z.string(),\n startedAt: z.string(),\n elapsedMinutes: z.number(),\n processedCount: z.number().nullable().optional(),\n totalCount: z.number().nullable().optional(),\n})\n\nexport const searchSettingsSchema = z.object({\n strategies: z.array(strategyStatusSchema),\n fulltextConfigured: z.boolean(),\n fulltextStats: fulltextStatsSchema.nullable(),\n vectorConfigured: z.boolean(),\n tokensEnabled: z.boolean(),\n defaultStrategies: z.array(z.string()),\n reindexLock: reindexLockSchema.nullable().describe('Deprecated: Use fulltextReindexLock or vectorReindexLock'),\n fulltextReindexLock: reindexLockSchema.nullable(),\n vectorReindexLock: reindexLockSchema.nullable(),\n})\n\nexport const settingsResponseSchema = z.object({\n settings: searchSettingsSchema,\n})\n\n// ============================================================================\n// Global Search Settings Schemas (/api/search/settings/global-search)\n// ============================================================================\n\nexport const globalSearchSettingsResponseSchema = z.object({\n enabledStrategies: z.array(searchStrategyIdSchema),\n})\n\nexport const globalSearchSettingsUpdateSchema = z.object({\n enabledStrategies: z.array(searchStrategyIdSchema).min(1),\n})\n\nexport const globalSearchSettingsUpdateResponseSchema = z.object({\n ok: z.boolean(),\n enabledStrategies: z.array(searchStrategyIdSchema),\n})\n\n// ============================================================================\n// Reindex Endpoint Schemas (/api/search/reindex)\n// ============================================================================\n\nexport const reindexRequestSchema = z.object({\n action: z.enum(['clear', 'recreate', 'reindex']).optional().describe('Action to perform (default: reindex)'),\n entityId: z.string().optional().describe('Specific entity ID to reindex (e.g., \"customers:customer_person_profile\", \"catalog:catalog_product\")'),\n useQueue: z.boolean().optional().describe('Whether to use queue (default: true)'),\n})\n\nexport const reindexResultSchema = z.object({\n entitiesProcessed: z.number().optional(),\n recordsIndexed: z.number().optional(),\n jobsEnqueued: z.number().optional(),\n errors: z.array(z.object({\n entityId: z.string(),\n error: z.string(),\n })).optional(),\n})\n\nexport const reindexResponseSchema = z.object({\n ok: z.boolean(),\n action: z.enum(['clear', 'recreate', 'reindex']),\n entityId: z.string().nullable(),\n useQueue: z.boolean().optional(),\n result: reindexResultSchema.optional(),\n stats: z.record(z.string(), z.record(z.string(), z.unknown()).nullable()).optional(),\n})\n\nexport const reindexConflictResponseSchema = z.object({\n error: z.string(),\n lock: reindexLockSchema,\n})\n\n// ============================================================================\n// Index Endpoint Schemas (/api/search/index)\n// ============================================================================\n\nexport const indexEntrySchema = z.object({\n id: z.string(),\n entityId: z.string(),\n recordId: z.string(),\n tenantId: z.string(),\n organizationId: z.string().nullable().optional(),\n title: z.string().optional(),\n subtitle: z.string().optional(),\n createdAt: z.string().optional(),\n})\n\nexport const indexListQueryParamsSchema = z.object({\n entityId: z.string().optional().describe('Filter by entity ID (e.g., \"customers:customer_person_profile\", \"catalog:catalog_product\")'),\n limit: z.coerce.number().min(1).max(200).optional().describe('Maximum entries to return (default: 50, max: 200)'),\n offset: z.coerce.number().min(0).optional().describe('Offset for pagination (default: 0)'),\n})\n\nexport const indexListResponseSchema = z.object({\n entries: z.array(indexEntrySchema),\n limit: z.number(),\n offset: z.number(),\n})\n\nexport const indexPurgeQueryParamsSchema = z.object({\n entityId: z.string().optional().describe('Specific entity ID to purge (e.g., \"customers:customer_person_profile\", \"catalog:catalog_product\")'),\n confirmAll: z.enum(['true']).optional().describe('Required when purging all entities'),\n})\n\nexport const indexPurgeResponseSchema = z.object({\n ok: z.boolean(),\n})\n\n// ============================================================================\n// Fulltext Settings Schemas (/api/search/settings/fulltext)\n// ============================================================================\n\nexport const fulltextEnvVarStatusSchema = z.object({\n set: z.boolean(),\n hint: z.string(),\n})\n\nexport const fulltextOptionalEnvVarStatusSchema = z.object({\n set: z.boolean(),\n value: z.union([z.string(), z.boolean()]).optional(),\n default: z.union([z.string(), z.boolean()]).optional(),\n hint: z.string(),\n})\n\nexport const fulltextSettingsResponseSchema = z.object({\n driver: z.enum(['meilisearch']).nullable(),\n configured: z.boolean(),\n envVars: z.object({\n MEILISEARCH_HOST: fulltextEnvVarStatusSchema,\n MEILISEARCH_API_KEY: fulltextEnvVarStatusSchema,\n }),\n optionalEnvVars: z.object({\n MEILISEARCH_INDEX_PREFIX: fulltextOptionalEnvVarStatusSchema,\n SEARCH_EXCLUDE_ENCRYPTED_FIELDS: fulltextOptionalEnvVarStatusSchema,\n }),\n})\n\n// ============================================================================\n// Vector Store Settings Schemas (/api/search/settings/vector-store)\n// ============================================================================\n\nexport const vectorDriverEnvVarSchema = z.object({\n name: z.string(),\n set: z.boolean(),\n hint: z.string(),\n})\n\nexport const vectorDriverStatusSchema = z.object({\n id: z.enum(['pgvector', 'qdrant', 'chromadb']),\n name: z.string(),\n configured: z.boolean(),\n implemented: z.boolean(),\n envVars: z.array(vectorDriverEnvVarSchema),\n})\n\nexport const vectorStoreSettingsResponseSchema = z.object({\n currentDriver: z.enum(['pgvector', 'qdrant', 'chromadb']),\n configured: z.boolean(),\n drivers: z.array(vectorDriverStatusSchema),\n})\n\n// ============================================================================\n// Embeddings Endpoint Schemas (/api/search/embeddings)\n// ============================================================================\n\nexport const embeddingProviderIdSchema = z.enum(['openai', 'google', 'mistral', 'cohere', 'bedrock', 'ollama'])\n\nexport const embeddingConfigSchema = z.object({\n providerId: embeddingProviderIdSchema,\n model: z.string(),\n dimension: z.number(),\n outputDimensionality: z.number().optional(),\n baseUrl: z.string().optional(),\n updatedAt: z.string().optional(),\n})\n\nexport const embeddingsSettingsSchema = z.object({\n openaiConfigured: z.boolean(),\n autoIndexingEnabled: z.boolean(),\n autoIndexingLocked: z.boolean(),\n lockReason: z.string().nullable(),\n embeddingConfig: embeddingConfigSchema.nullable(),\n configuredProviders: z.array(embeddingProviderIdSchema),\n indexedDimension: z.number().nullable(),\n reindexRequired: z.boolean(),\n documentCount: z.number().nullable(),\n})\n\nexport const embeddingsSettingsResponseSchema = z.object({\n settings: embeddingsSettingsSchema,\n})\n\nexport const embeddingsSettingsUpdateSchema = z.object({\n autoIndexingEnabled: z.boolean().optional(),\n embeddingConfig: z.object({\n providerId: embeddingProviderIdSchema,\n model: z.string(),\n dimension: z.number(),\n outputDimensionality: z.number().optional(),\n baseUrl: z.string().optional(),\n }).optional(),\n})\n\n// ============================================================================\n// Reindex Cancel Schemas (/api/search/reindex/cancel)\n// ============================================================================\n\nexport const reindexCancelResponseSchema = z.object({\n ok: z.boolean(),\n jobsRemoved: z.number(),\n})\n\n// ============================================================================\n// Vector Reindex Schemas (/api/search/embeddings/reindex)\n// ============================================================================\n\nexport const vectorReindexRequestSchema = z.object({\n entityId: z.string().optional().describe('Specific entity ID to reindex (e.g., \"customers:customer_person_profile\", \"catalog:catalog_product\")'),\n purgeFirst: z.boolean().optional().describe('Purge existing entries before reindexing'),\n})\n\nexport const vectorReindexResponseSchema = z.object({\n ok: z.boolean(),\n recordsIndexed: z.number().optional(),\n jobsEnqueued: z.number().optional(),\n entitiesProcessed: z.number().optional(),\n errors: z.array(z.object({\n entityId: z.string(),\n error: z.string(),\n })).optional(),\n})\n\n// ============================================================================\n// OpenAPI Route Docs\n// ============================================================================\n\nexport const searchOpenApi: OpenApiRouteDoc = {\n tag: 'Search',\n summary: 'Search across all indexed entities',\n description: 'Performs a search using configured strategies (fulltext, vector, tokens). Use for search playground.',\n methods: {\n GET: {\n summary: 'Search across all indexed entities',\n description: 'Performs a search using configured strategies (fulltext, vector, tokens). Use for search playground.',\n tags: ['Search'],\n query: searchQueryParamsSchema,\n responses: [\n { status: 200, description: 'Search results', schema: searchResponseSchema },\n { status: 400, description: 'Missing query parameter', schema: errorResponseSchema },\n { status: 401, description: 'Unauthorized', schema: errorResponseSchema },\n { status: 500, description: 'Search failed', schema: errorResponseSchema },\n { status: 503, description: 'Search service unavailable', schema: errorResponseSchema },\n ],\n },\n },\n}\n\nexport const globalSearchOpenApi: OpenApiRouteDoc = {\n tag: 'Search',\n summary: 'Global search (Cmd+K)',\n description: 'Performs a global search using saved tenant strategies. Does NOT accept strategies from URL.',\n methods: {\n GET: {\n summary: 'Global search (Cmd+K)',\n description: 'Performs a global search using saved tenant strategies. Does NOT accept strategies from URL.',\n tags: ['Search'],\n query: globalSearchQueryParamsSchema,\n responses: [\n { status: 200, description: 'Search results', schema: globalSearchResponseSchema },\n { status: 400, description: 'Missing query parameter', schema: errorResponseSchema },\n { status: 401, description: 'Unauthorized', schema: errorResponseSchema },\n { status: 500, description: 'Search failed', schema: errorResponseSchema },\n { status: 503, description: 'Search service unavailable', schema: errorResponseSchema },\n ],\n },\n },\n}\n\nexport const settingsOpenApi: OpenApiRouteDoc = {\n tag: 'Search',\n summary: 'Get search settings and status',\n description: 'Returns search module configuration, available strategies, and reindex lock status.',\n methods: {\n GET: {\n summary: 'Get search settings and status',\n description: 'Returns search module configuration, available strategies, and reindex lock status.',\n tags: ['Search'],\n responses: [\n { status: 200, description: 'Search settings', schema: settingsResponseSchema },\n { status: 401, description: 'Unauthorized', schema: errorResponseSchema },\n ],\n },\n },\n}\n\nexport const globalSearchSettingsOpenApi: OpenApiRouteDoc = {\n tag: 'Search',\n summary: 'Global search strategy settings',\n description: 'Manage enabled strategies for Cmd+K global search.',\n methods: {\n GET: {\n summary: 'Get global search strategies',\n description: 'Returns the enabled strategies for Cmd+K global search.',\n tags: ['Search'],\n responses: [\n { status: 200, description: 'Global search settings', schema: globalSearchSettingsResponseSchema },\n { status: 401, description: 'Unauthorized', schema: errorResponseSchema },\n ],\n },\n POST: {\n summary: 'Update global search strategies',\n description: 'Sets which strategies are enabled for Cmd+K global search.',\n tags: ['Search'],\n requestBody: { schema: globalSearchSettingsUpdateSchema },\n responses: [\n { status: 200, description: 'Updated settings', schema: globalSearchSettingsUpdateResponseSchema },\n { status: 400, description: 'Invalid request', schema: errorResponseSchema },\n { status: 401, description: 'Unauthorized', schema: errorResponseSchema },\n { status: 500, description: 'Internal error', schema: errorResponseSchema },\n ],\n },\n },\n}\n\nexport const reindexOpenApi: OpenApiRouteDoc = {\n tag: 'Search',\n summary: 'Trigger fulltext reindex',\n description: 'Starts a fulltext (Meilisearch) reindex operation. Can clear, recreate, or fully reindex.',\n methods: {\n POST: {\n summary: 'Trigger fulltext reindex',\n description: 'Starts a fulltext (Meilisearch) reindex operation. Can clear, recreate, or fully reindex.',\n tags: ['Search'],\n requestBody: { schema: reindexRequestSchema },\n responses: [\n { status: 200, description: 'Reindex result', schema: reindexResponseSchema },\n { status: 401, description: 'Unauthorized', schema: errorResponseSchema },\n { status: 409, description: 'Reindex already in progress', schema: reindexConflictResponseSchema },\n { status: 500, description: 'Reindex failed', schema: errorResponseSchema },\n { status: 503, description: 'Search service unavailable', schema: errorResponseSchema },\n ],\n },\n },\n}\n\nexport const reindexCancelOpenApi: OpenApiRouteDoc = {\n tag: 'Search',\n summary: 'Cancel fulltext reindex',\n description: 'Cancels an in-progress fulltext reindex operation.',\n methods: {\n POST: {\n summary: 'Cancel fulltext reindex',\n description: 'Cancels an in-progress fulltext reindex operation.',\n tags: ['Search'],\n responses: [\n { status: 200, description: 'Cancel result', schema: reindexCancelResponseSchema },\n { status: 401, description: 'Unauthorized', schema: errorResponseSchema },\n ],\n },\n },\n}\n\nexport const indexOpenApi: OpenApiRouteDoc = {\n tag: 'Search',\n summary: 'Vector index management',\n description: 'List and purge vector search index entries.',\n methods: {\n GET: {\n summary: 'List vector index entries',\n description: 'Returns paginated list of entries in the vector search index.',\n tags: ['Search'],\n query: indexListQueryParamsSchema,\n responses: [\n { status: 200, description: 'Index entries', schema: indexListResponseSchema },\n { status: 401, description: 'Unauthorized', schema: errorResponseSchema },\n { status: 500, description: 'Failed to fetch index', schema: errorResponseSchema },\n { status: 503, description: 'Vector strategy unavailable', schema: errorResponseSchema },\n ],\n },\n DELETE: {\n summary: 'Purge vector index',\n description: 'Purges entries from the vector search index. Requires confirmAll=true when purging all entities.',\n tags: ['Search'],\n query: indexPurgeQueryParamsSchema,\n responses: [\n { status: 200, description: 'Purge result', schema: indexPurgeResponseSchema },\n { status: 400, description: 'Missing confirmAll parameter', schema: errorResponseSchema },\n { status: 401, description: 'Unauthorized', schema: errorResponseSchema },\n { status: 500, description: 'Purge failed', schema: errorResponseSchema },\n { status: 503, description: 'Search indexer unavailable', schema: errorResponseSchema },\n ],\n },\n },\n}\n\nexport const fulltextSettingsOpenApi: OpenApiRouteDoc = {\n tag: 'Search',\n summary: 'Get fulltext search configuration',\n description: 'Returns Meilisearch configuration status and index statistics.',\n methods: {\n GET: {\n summary: 'Get fulltext search configuration',\n description: 'Returns Meilisearch configuration status and index statistics.',\n tags: ['Search'],\n responses: [\n { status: 200, description: 'Fulltext settings', schema: fulltextSettingsResponseSchema },\n { status: 401, description: 'Unauthorized', schema: errorResponseSchema },\n ],\n },\n },\n}\n\nexport const vectorStoreSettingsOpenApi: OpenApiRouteDoc = {\n tag: 'Search',\n summary: 'Get vector store configuration',\n description: 'Returns vector store configuration status.',\n methods: {\n GET: {\n summary: 'Get vector store configuration',\n description: 'Returns vector store configuration status.',\n tags: ['Search'],\n responses: [\n { status: 200, description: 'Vector store settings', schema: vectorStoreSettingsResponseSchema },\n { status: 401, description: 'Unauthorized', schema: errorResponseSchema },\n ],\n },\n },\n}\n\nexport const embeddingsOpenApi: OpenApiRouteDoc = {\n tag: 'Search',\n summary: 'Embeddings configuration',\n description: 'Manage embedding provider and model configuration.',\n methods: {\n GET: {\n summary: 'Get embeddings configuration',\n description: 'Returns current embedding provider and model configuration.',\n tags: ['Search'],\n responses: [\n { status: 200, description: 'Embeddings settings', schema: embeddingsSettingsResponseSchema },\n { status: 401, description: 'Unauthorized', schema: errorResponseSchema },\n ],\n },\n POST: {\n summary: 'Update embeddings configuration',\n description: 'Updates the embedding provider and model settings.',\n tags: ['Search'],\n requestBody: { schema: embeddingsSettingsUpdateSchema },\n responses: [\n { status: 200, description: 'Updated settings', schema: embeddingsSettingsResponseSchema },\n { status: 400, description: 'Invalid request', schema: errorResponseSchema },\n { status: 401, description: 'Unauthorized', schema: errorResponseSchema },\n { status: 409, description: 'Auto-indexing disabled via environment', schema: errorResponseSchema },\n { status: 500, description: 'Update failed', schema: errorResponseSchema },\n { status: 503, description: 'Configuration service unavailable', schema: errorResponseSchema },\n ],\n },\n },\n}\n\nexport const embeddingsReindexOpenApi: OpenApiRouteDoc = {\n tag: 'Search',\n summary: 'Trigger vector reindex',\n description: 'Starts a vector embedding reindex operation.',\n methods: {\n POST: {\n summary: 'Trigger vector reindex',\n description: 'Starts a vector embedding reindex operation.',\n tags: ['Search'],\n requestBody: { schema: vectorReindexRequestSchema },\n responses: [\n { status: 200, description: 'Reindex result', schema: vectorReindexResponseSchema },\n { status: 401, description: 'Unauthorized', schema: errorResponseSchema },\n { status: 409, description: 'Reindex already in progress', schema: reindexConflictResponseSchema },\n { status: 500, description: 'Reindex failed', schema: errorResponseSchema },\n { status: 503, description: 'Search indexer unavailable', schema: errorResponseSchema },\n ],\n },\n },\n}\n\nexport const embeddingsReindexCancelOpenApi: OpenApiRouteDoc = {\n tag: 'Search',\n summary: 'Cancel vector reindex',\n description: 'Cancels an in-progress vector reindex operation.',\n methods: {\n POST: {\n summary: 'Cancel vector reindex',\n description: 'Cancels an in-progress vector reindex operation.',\n tags: ['Search'],\n responses: [\n { status: 200, description: 'Cancel result', schema: reindexCancelResponseSchema },\n { status: 401, description: 'Unauthorized', schema: errorResponseSchema },\n ],\n },\n },\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["import { z } from 'zod'\nimport type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'\n\n// ============================================================================\n// Common Schemas\n// ============================================================================\n\nexport const searchStrategyIdSchema = z.enum(['fulltext', 'vector', 'tokens'])\n\nexport const searchResultPresenterSchema = z.object({\n title: z.string(),\n subtitle: z.string().optional(),\n icon: z.string().optional(),\n badge: z.string().optional(),\n})\n\nexport const searchResultLinkSchema = z.object({\n href: z.string(),\n label: z.string(),\n kind: z.enum(['primary', 'secondary']),\n})\n\nexport const searchResultSchema = z.object({\n entityId: z.string().describe('Entity identifier (e.g., \"customers:customer_person_profile\")'),\n recordId: z.string().describe('Primary key of the record'),\n score: z.number().describe('Relevance score (0-1)'),\n source: searchStrategyIdSchema.describe('Which strategy returned this result'),\n presenter: searchResultPresenterSchema.optional(),\n url: z.string().optional(),\n links: z.array(searchResultLinkSchema).optional(),\n metadata: z.record(z.string(), z.unknown()).optional(),\n})\n\nexport const errorResponseSchema = z.object({\n error: z.string(),\n})\n\n// ============================================================================\n// Search Endpoint Schemas (/api/search)\n// ============================================================================\n\nexport const searchQueryParamsSchema = z.object({\n q: z.string().describe('Search query (required)'),\n limit: z.coerce.number().min(1).max(100).optional().describe('Maximum results to return (default: 50, max: 100)'),\n strategies: z.string().optional().describe('Comma-separated strategies to use: fulltext, vector, tokens (e.g., \"fulltext,vector\")'),\n entityTypes: z.string().optional().describe('Comma-separated entity types to filter results (e.g., \"customers:customer_person_profile,catalog:catalog_product,sales:sales_order\")'),\n})\n\nexport const searchResponseSchema = z.object({\n results: z.array(searchResultSchema),\n strategiesUsed: z.array(searchStrategyIdSchema),\n timing: z.number().describe('Search duration in milliseconds'),\n query: z.string(),\n limit: z.number(),\n})\n\n// ============================================================================\n// Global Search Endpoint Schemas (/api/search/global)\n// ============================================================================\n\nexport const globalSearchQueryParamsSchema = z.object({\n q: z.string().describe('Search query (required)'),\n limit: z.coerce.number().min(1).max(100).optional().describe('Maximum results to return (default: 50, max: 100)'),\n entityTypes: z.string().optional().describe('Comma-separated entity types to filter results (e.g., \"customers:customer_person_profile,catalog:catalog_product,sales:sales_order\")'),\n})\n\nexport const globalSearchResponseSchema = z.object({\n results: z.array(searchResultSchema),\n strategiesUsed: z.array(searchStrategyIdSchema),\n strategiesEnabled: z.array(searchStrategyIdSchema),\n timing: z.number().describe('Search duration in milliseconds'),\n query: z.string(),\n limit: z.number(),\n})\n\n// ============================================================================\n// Settings Endpoint Schemas (/api/search/settings)\n// ============================================================================\n\nexport const strategyStatusSchema = z.object({\n id: z.string(),\n name: z.string(),\n priority: z.number(),\n available: z.boolean(),\n})\n\nexport const fulltextStatsSchema = z.object({\n numberOfDocuments: z.number(),\n isIndexing: z.boolean(),\n fieldDistribution: z.record(z.string(), z.number()),\n})\n\nexport const reindexLockSchema = z.object({\n type: z.enum(['fulltext', 'vector']),\n action: z.string(),\n startedAt: z.string(),\n elapsedMinutes: z.number(),\n processedCount: z.number().nullable().optional(),\n totalCount: z.number().nullable().optional(),\n})\n\nexport const searchSettingsSchema = z.object({\n strategies: z.array(strategyStatusSchema),\n fulltextConfigured: z.boolean(),\n fulltextStats: fulltextStatsSchema.nullable(),\n vectorConfigured: z.boolean(),\n tokensEnabled: z.boolean(),\n defaultStrategies: z.array(z.string()),\n reindexLock: reindexLockSchema.nullable().describe('Deprecated: Use fulltextReindexLock or vectorReindexLock'),\n fulltextReindexLock: reindexLockSchema.nullable(),\n vectorReindexLock: reindexLockSchema.nullable(),\n})\n\nexport const settingsResponseSchema = z.object({\n settings: searchSettingsSchema,\n})\n\n// ============================================================================\n// Global Search Settings Schemas (/api/search/settings/global-search)\n// ============================================================================\n\nexport const globalSearchSettingsResponseSchema = z.object({\n enabledStrategies: z.array(searchStrategyIdSchema),\n})\n\nexport const globalSearchSettingsUpdateSchema = z.object({\n enabledStrategies: z.array(searchStrategyIdSchema).min(1),\n})\n\nexport const globalSearchSettingsUpdateResponseSchema = z.object({\n ok: z.boolean(),\n enabledStrategies: z.array(searchStrategyIdSchema),\n})\n\n// ============================================================================\n// Reindex Endpoint Schemas (/api/search/reindex)\n// ============================================================================\n\nexport const reindexRequestSchema = z.object({\n action: z.enum(['clear', 'recreate', 'reindex']).optional().describe('Action to perform (default: reindex)'),\n entityId: z.string().optional().describe('Specific entity ID to reindex (e.g., \"customers:customer_person_profile\", \"catalog:catalog_product\")'),\n useQueue: z.boolean().optional().describe('Whether to use queue (default: true)'),\n})\n\nexport const reindexResultSchema = z.object({\n entitiesProcessed: z.number().optional(),\n recordsIndexed: z.number().optional(),\n jobsEnqueued: z.number().optional(),\n errors: z.array(z.object({\n entityId: z.string(),\n error: z.string(),\n })).optional(),\n})\n\nexport const reindexResponseSchema = z.object({\n ok: z.boolean(),\n action: z.enum(['clear', 'recreate', 'reindex']),\n entityId: z.string().nullable(),\n useQueue: z.boolean().optional(),\n result: reindexResultSchema.optional(),\n stats: z.record(z.string(), z.record(z.string(), z.unknown()).nullable()).optional(),\n})\n\nexport const reindexConflictResponseSchema = z.object({\n error: z.string(),\n lock: reindexLockSchema,\n})\n\n// ============================================================================\n// Index Endpoint Schemas (/api/search/index)\n// ============================================================================\n\nexport const indexEntrySchema = z.object({\n id: z.string(),\n entityId: z.string(),\n recordId: z.string(),\n tenantId: z.string(),\n organizationId: z.string().nullable().optional(),\n title: z.string().optional(),\n subtitle: z.string().optional(),\n createdAt: z.string().optional(),\n})\n\nexport const indexListQueryParamsSchema = z.object({\n entityId: z.string().optional().describe('Filter by entity ID (e.g., \"customers:customer_person_profile\", \"catalog:catalog_product\")'),\n limit: z.coerce.number().min(1).max(200).optional().describe('Maximum entries to return (default: 50, max: 200)'),\n offset: z.coerce.number().min(0).optional().describe('Offset for pagination (default: 0)'),\n})\n\nexport const indexListResponseSchema = z.object({\n entries: z.array(indexEntrySchema),\n limit: z.number(),\n offset: z.number(),\n})\n\nexport const indexPurgeQueryParamsSchema = z.object({\n entityId: z.string().optional().describe('Specific entity ID to purge (e.g., \"customers:customer_person_profile\", \"catalog:catalog_product\")'),\n confirmAll: z.enum(['true']).optional().describe('Required when purging all entities'),\n})\n\nexport const indexPurgeResponseSchema = z.object({\n ok: z.boolean(),\n})\n\n// ============================================================================\n// Fulltext Settings Schemas (/api/search/settings/fulltext)\n// ============================================================================\n\nexport const fulltextEnvVarStatusSchema = z.object({\n set: z.boolean(),\n hint: z.string(),\n})\n\nexport const fulltextOptionalEnvVarStatusSchema = z.object({\n set: z.boolean(),\n value: z.union([z.string(), z.boolean()]).optional(),\n default: z.union([z.string(), z.boolean()]).optional(),\n hint: z.string(),\n})\n\nexport const fulltextSettingsResponseSchema = z.object({\n driver: z.enum(['meilisearch']).nullable(),\n configured: z.boolean(),\n envVars: z.object({\n MEILISEARCH_HOST: fulltextEnvVarStatusSchema,\n MEILISEARCH_API_KEY: fulltextEnvVarStatusSchema,\n }),\n optionalEnvVars: z.object({\n MEILISEARCH_INDEX_PREFIX: fulltextOptionalEnvVarStatusSchema,\n SEARCH_EXCLUDE_ENCRYPTED_FIELDS: fulltextOptionalEnvVarStatusSchema,\n }),\n})\n\n// ============================================================================\n// Vector Store Settings Schemas (/api/search/settings/vector-store)\n// ============================================================================\n\nexport const vectorDriverEnvVarSchema = z.object({\n name: z.string(),\n set: z.boolean(),\n hint: z.string(),\n})\n\nexport const vectorDriverStatusSchema = z.object({\n id: z.enum(['pgvector', 'qdrant', 'chromadb']),\n name: z.string(),\n configured: z.boolean(),\n implemented: z.boolean(),\n available: z.boolean().nullable(),\n unavailableReason: z.string().nullable(),\n envVars: z.array(vectorDriverEnvVarSchema),\n})\n\nexport const vectorStoreSettingsResponseSchema = z.object({\n currentDriver: z.enum(['pgvector', 'qdrant', 'chromadb']),\n configured: z.boolean(),\n drivers: z.array(vectorDriverStatusSchema),\n})\n\n// ============================================================================\n// Embeddings Endpoint Schemas (/api/search/embeddings)\n// ============================================================================\n\nexport const embeddingProviderIdSchema = z.enum(['openai', 'google', 'mistral', 'cohere', 'bedrock', 'ollama'])\n\nexport const embeddingConfigSchema = z.object({\n providerId: embeddingProviderIdSchema,\n model: z.string(),\n dimension: z.number(),\n outputDimensionality: z.number().optional(),\n baseUrl: z.string().optional(),\n updatedAt: z.string().optional(),\n})\n\nexport const embeddingsSettingsSchema = z.object({\n openaiConfigured: z.boolean(),\n autoIndexingEnabled: z.boolean(),\n autoIndexingLocked: z.boolean(),\n lockReason: z.string().nullable(),\n embeddingConfig: embeddingConfigSchema.nullable(),\n configuredProviders: z.array(embeddingProviderIdSchema),\n indexedDimension: z.number().nullable(),\n reindexRequired: z.boolean(),\n documentCount: z.number().nullable(),\n})\n\nexport const embeddingsSettingsResponseSchema = z.object({\n settings: embeddingsSettingsSchema,\n})\n\nexport const embeddingsSettingsUpdateSchema = z.object({\n autoIndexingEnabled: z.boolean().optional(),\n embeddingConfig: z.object({\n providerId: embeddingProviderIdSchema,\n model: z.string(),\n dimension: z.number(),\n outputDimensionality: z.number().optional(),\n baseUrl: z.string().optional(),\n }).optional(),\n})\n\n// ============================================================================\n// Reindex Cancel Schemas (/api/search/reindex/cancel)\n// ============================================================================\n\nexport const reindexCancelResponseSchema = z.object({\n ok: z.boolean(),\n jobsRemoved: z.number(),\n})\n\n// ============================================================================\n// Vector Reindex Schemas (/api/search/embeddings/reindex)\n// ============================================================================\n\nexport const vectorReindexRequestSchema = z.object({\n entityId: z.string().optional().describe('Specific entity ID to reindex (e.g., \"customers:customer_person_profile\", \"catalog:catalog_product\")'),\n purgeFirst: z.boolean().optional().describe('Purge existing entries before reindexing'),\n})\n\nexport const vectorReindexResponseSchema = z.object({\n ok: z.boolean(),\n recordsIndexed: z.number().optional(),\n jobsEnqueued: z.number().optional(),\n entitiesProcessed: z.number().optional(),\n errors: z.array(z.object({\n entityId: z.string(),\n error: z.string(),\n })).optional(),\n})\n\n// ============================================================================\n// OpenAPI Route Docs\n// ============================================================================\n\nexport const searchOpenApi: OpenApiRouteDoc = {\n tag: 'Search',\n summary: 'Search across all indexed entities',\n description: 'Performs a search using configured strategies (fulltext, vector, tokens). Use for search playground.',\n methods: {\n GET: {\n summary: 'Search across all indexed entities',\n description: 'Performs a search using configured strategies (fulltext, vector, tokens). Use for search playground.',\n tags: ['Search'],\n query: searchQueryParamsSchema,\n responses: [\n { status: 200, description: 'Search results', schema: searchResponseSchema },\n { status: 400, description: 'Missing query parameter', schema: errorResponseSchema },\n { status: 401, description: 'Unauthorized', schema: errorResponseSchema },\n { status: 500, description: 'Search failed', schema: errorResponseSchema },\n { status: 503, description: 'Search service unavailable', schema: errorResponseSchema },\n ],\n },\n },\n}\n\nexport const globalSearchOpenApi: OpenApiRouteDoc = {\n tag: 'Search',\n summary: 'Global search (Cmd+K)',\n description: 'Performs a global search using saved tenant strategies. Does NOT accept strategies from URL.',\n methods: {\n GET: {\n summary: 'Global search (Cmd+K)',\n description: 'Performs a global search using saved tenant strategies. Does NOT accept strategies from URL.',\n tags: ['Search'],\n query: globalSearchQueryParamsSchema,\n responses: [\n { status: 200, description: 'Search results', schema: globalSearchResponseSchema },\n { status: 400, description: 'Missing query parameter', schema: errorResponseSchema },\n { status: 401, description: 'Unauthorized', schema: errorResponseSchema },\n { status: 500, description: 'Search failed', schema: errorResponseSchema },\n { status: 503, description: 'Search service unavailable', schema: errorResponseSchema },\n ],\n },\n },\n}\n\nexport const settingsOpenApi: OpenApiRouteDoc = {\n tag: 'Search',\n summary: 'Get search settings and status',\n description: 'Returns search module configuration, available strategies, and reindex lock status.',\n methods: {\n GET: {\n summary: 'Get search settings and status',\n description: 'Returns search module configuration, available strategies, and reindex lock status.',\n tags: ['Search'],\n responses: [\n { status: 200, description: 'Search settings', schema: settingsResponseSchema },\n { status: 401, description: 'Unauthorized', schema: errorResponseSchema },\n ],\n },\n },\n}\n\nexport const globalSearchSettingsOpenApi: OpenApiRouteDoc = {\n tag: 'Search',\n summary: 'Global search strategy settings',\n description: 'Manage enabled strategies for Cmd+K global search.',\n methods: {\n GET: {\n summary: 'Get global search strategies',\n description: 'Returns the enabled strategies for Cmd+K global search.',\n tags: ['Search'],\n responses: [\n { status: 200, description: 'Global search settings', schema: globalSearchSettingsResponseSchema },\n { status: 401, description: 'Unauthorized', schema: errorResponseSchema },\n ],\n },\n POST: {\n summary: 'Update global search strategies',\n description: 'Sets which strategies are enabled for Cmd+K global search.',\n tags: ['Search'],\n requestBody: { schema: globalSearchSettingsUpdateSchema },\n responses: [\n { status: 200, description: 'Updated settings', schema: globalSearchSettingsUpdateResponseSchema },\n { status: 400, description: 'Invalid request', schema: errorResponseSchema },\n { status: 401, description: 'Unauthorized', schema: errorResponseSchema },\n { status: 500, description: 'Internal error', schema: errorResponseSchema },\n ],\n },\n },\n}\n\nexport const reindexOpenApi: OpenApiRouteDoc = {\n tag: 'Search',\n summary: 'Trigger fulltext reindex',\n description: 'Starts a fulltext (Meilisearch) reindex operation. Can clear, recreate, or fully reindex.',\n methods: {\n POST: {\n summary: 'Trigger fulltext reindex',\n description: 'Starts a fulltext (Meilisearch) reindex operation. Can clear, recreate, or fully reindex.',\n tags: ['Search'],\n requestBody: { schema: reindexRequestSchema },\n responses: [\n { status: 200, description: 'Reindex result', schema: reindexResponseSchema },\n { status: 401, description: 'Unauthorized', schema: errorResponseSchema },\n { status: 409, description: 'Reindex already in progress', schema: reindexConflictResponseSchema },\n { status: 500, description: 'Reindex failed', schema: errorResponseSchema },\n { status: 503, description: 'Search service unavailable', schema: errorResponseSchema },\n ],\n },\n },\n}\n\nexport const reindexCancelOpenApi: OpenApiRouteDoc = {\n tag: 'Search',\n summary: 'Cancel fulltext reindex',\n description: 'Cancels an in-progress fulltext reindex operation.',\n methods: {\n POST: {\n summary: 'Cancel fulltext reindex',\n description: 'Cancels an in-progress fulltext reindex operation.',\n tags: ['Search'],\n responses: [\n { status: 200, description: 'Cancel result', schema: reindexCancelResponseSchema },\n { status: 401, description: 'Unauthorized', schema: errorResponseSchema },\n ],\n },\n },\n}\n\nexport const indexOpenApi: OpenApiRouteDoc = {\n tag: 'Search',\n summary: 'Vector index management',\n description: 'List and purge vector search index entries.',\n methods: {\n GET: {\n summary: 'List vector index entries',\n description: 'Returns paginated list of entries in the vector search index.',\n tags: ['Search'],\n query: indexListQueryParamsSchema,\n responses: [\n { status: 200, description: 'Index entries', schema: indexListResponseSchema },\n { status: 401, description: 'Unauthorized', schema: errorResponseSchema },\n { status: 500, description: 'Failed to fetch index', schema: errorResponseSchema },\n { status: 503, description: 'Vector strategy unavailable', schema: errorResponseSchema },\n ],\n },\n DELETE: {\n summary: 'Purge vector index',\n description: 'Purges entries from the vector search index. Requires confirmAll=true when purging all entities.',\n tags: ['Search'],\n query: indexPurgeQueryParamsSchema,\n responses: [\n { status: 200, description: 'Purge result', schema: indexPurgeResponseSchema },\n { status: 400, description: 'Missing confirmAll parameter', schema: errorResponseSchema },\n { status: 401, description: 'Unauthorized', schema: errorResponseSchema },\n { status: 500, description: 'Purge failed', schema: errorResponseSchema },\n { status: 503, description: 'Search indexer unavailable', schema: errorResponseSchema },\n ],\n },\n },\n}\n\nexport const fulltextSettingsOpenApi: OpenApiRouteDoc = {\n tag: 'Search',\n summary: 'Get fulltext search configuration',\n description: 'Returns Meilisearch configuration status and index statistics.',\n methods: {\n GET: {\n summary: 'Get fulltext search configuration',\n description: 'Returns Meilisearch configuration status and index statistics.',\n tags: ['Search'],\n responses: [\n { status: 200, description: 'Fulltext settings', schema: fulltextSettingsResponseSchema },\n { status: 401, description: 'Unauthorized', schema: errorResponseSchema },\n ],\n },\n },\n}\n\nexport const vectorStoreSettingsOpenApi: OpenApiRouteDoc = {\n tag: 'Search',\n summary: 'Get vector store configuration',\n description: 'Returns vector store configuration status.',\n methods: {\n GET: {\n summary: 'Get vector store configuration',\n description: 'Returns vector store configuration status.',\n tags: ['Search'],\n responses: [\n { status: 200, description: 'Vector store settings', schema: vectorStoreSettingsResponseSchema },\n { status: 401, description: 'Unauthorized', schema: errorResponseSchema },\n ],\n },\n },\n}\n\nexport const embeddingsOpenApi: OpenApiRouteDoc = {\n tag: 'Search',\n summary: 'Embeddings configuration',\n description: 'Manage embedding provider and model configuration.',\n methods: {\n GET: {\n summary: 'Get embeddings configuration',\n description: 'Returns current embedding provider and model configuration.',\n tags: ['Search'],\n responses: [\n { status: 200, description: 'Embeddings settings', schema: embeddingsSettingsResponseSchema },\n { status: 401, description: 'Unauthorized', schema: errorResponseSchema },\n ],\n },\n POST: {\n summary: 'Update embeddings configuration',\n description: 'Updates the embedding provider and model settings.',\n tags: ['Search'],\n requestBody: { schema: embeddingsSettingsUpdateSchema },\n responses: [\n { status: 200, description: 'Updated settings', schema: embeddingsSettingsResponseSchema },\n { status: 400, description: 'Invalid request', schema: errorResponseSchema },\n { status: 401, description: 'Unauthorized', schema: errorResponseSchema },\n { status: 409, description: 'Auto-indexing disabled via environment', schema: errorResponseSchema },\n { status: 500, description: 'Update failed', schema: errorResponseSchema },\n { status: 503, description: 'Configuration service unavailable', schema: errorResponseSchema },\n ],\n },\n },\n}\n\nexport const embeddingsReindexOpenApi: OpenApiRouteDoc = {\n tag: 'Search',\n summary: 'Trigger vector reindex',\n description: 'Starts a vector embedding reindex operation.',\n methods: {\n POST: {\n summary: 'Trigger vector reindex',\n description: 'Starts a vector embedding reindex operation.',\n tags: ['Search'],\n requestBody: { schema: vectorReindexRequestSchema },\n responses: [\n { status: 200, description: 'Reindex result', schema: vectorReindexResponseSchema },\n { status: 401, description: 'Unauthorized', schema: errorResponseSchema },\n { status: 409, description: 'Reindex already in progress', schema: reindexConflictResponseSchema },\n { status: 500, description: 'Reindex failed', schema: errorResponseSchema },\n { status: 503, description: 'Search indexer unavailable', schema: errorResponseSchema },\n ],\n },\n },\n}\n\nexport const embeddingsReindexCancelOpenApi: OpenApiRouteDoc = {\n tag: 'Search',\n summary: 'Cancel vector reindex',\n description: 'Cancels an in-progress vector reindex operation.',\n methods: {\n POST: {\n summary: 'Cancel vector reindex',\n description: 'Cancels an in-progress vector reindex operation.',\n tags: ['Search'],\n responses: [\n { status: 200, description: 'Cancel result', schema: reindexCancelResponseSchema },\n { status: 401, description: 'Unauthorized', schema: errorResponseSchema },\n ],\n },\n },\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -1,10 +1,25 @@
1
1
  import { NextResponse } from "next/server";
2
+ import { createRequestContainer } from "@open-mercato/shared/lib/di/container";
2
3
  import { getAuthFromRequest } from "@open-mercato/shared/lib/auth/server";
3
4
  import { resolveTranslations } from "@open-mercato/shared/lib/i18n/server";
4
5
  import { vectorStoreSettingsOpenApi } from "../../openapi.js";
5
6
  const metadata = {
6
7
  GET: { requireAuth: true, requireFeatures: ["search.view"] }
7
8
  };
9
+ async function probeDriverStatus(container, driverId) {
10
+ try {
11
+ const drivers = container.resolve("vectorDrivers");
12
+ const driver = drivers.find((entry) => entry.id === driverId);
13
+ if (!driver?.getStatus) return { available: null, unavailableReason: null };
14
+ const status = await driver.getStatus();
15
+ return {
16
+ available: status.available,
17
+ unavailableReason: status.available ? null : status.reason ?? null
18
+ };
19
+ } catch {
20
+ return { available: null, unavailableReason: null };
21
+ }
22
+ }
8
23
  const unauthorized = async () => {
9
24
  const { t } = await resolveTranslations();
10
25
  return NextResponse.json({ error: t("api.errors.unauthorized", "Unauthorized") }, { status: 401 });
@@ -16,12 +31,27 @@ async function GET(req) {
16
31
  const qdrantUrlSet = Boolean(process.env.QDRANT_URL?.trim());
17
32
  const qdrantApiKeySet = Boolean(process.env.QDRANT_API_KEY?.trim());
18
33
  const chromaUrlSet = Boolean(process.env.CHROMA_URL?.trim());
34
+ let pgvectorStatus = {
35
+ available: null,
36
+ unavailableReason: null
37
+ };
38
+ if (databaseUrlSet) {
39
+ const container = await createRequestContainer();
40
+ try {
41
+ pgvectorStatus = await probeDriverStatus(container, "pgvector");
42
+ } finally {
43
+ const disposable = container;
44
+ if (typeof disposable.dispose === "function") await disposable.dispose();
45
+ }
46
+ }
19
47
  const drivers = [
20
48
  {
21
49
  id: "pgvector",
22
50
  name: "PostgreSQL (pgvector)",
23
51
  configured: databaseUrlSet,
24
52
  implemented: true,
53
+ available: pgvectorStatus.available,
54
+ unavailableReason: pgvectorStatus.unavailableReason,
25
55
  envVars: [
26
56
  {
27
57
  name: "DATABASE_URL",
@@ -35,6 +65,8 @@ async function GET(req) {
35
65
  name: "Qdrant",
36
66
  configured: qdrantUrlSet,
37
67
  implemented: false,
68
+ available: null,
69
+ unavailableReason: null,
38
70
  envVars: [
39
71
  {
40
72
  name: "QDRANT_URL",
@@ -53,6 +85,8 @@ async function GET(req) {
53
85
  name: "ChromaDB",
54
86
  configured: chromaUrlSet,
55
87
  implemented: false,
88
+ available: null,
89
+ unavailableReason: null,
56
90
  envVars: [
57
91
  {
58
92
  name: "CHROMA_URL",