@open-mercato/search 0.4.2-canary-c02407ff85

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 (237) hide show
  1. package/AGENTS.md +678 -0
  2. package/build.mjs +92 -0
  3. package/dist/di.js +157 -0
  4. package/dist/di.js.map +7 -0
  5. package/dist/fulltext/drivers/index.js +21 -0
  6. package/dist/fulltext/drivers/index.js.map +7 -0
  7. package/dist/fulltext/drivers/meilisearch/index.js +320 -0
  8. package/dist/fulltext/drivers/meilisearch/index.js.map +7 -0
  9. package/dist/fulltext/index.js +7 -0
  10. package/dist/fulltext/index.js.map +7 -0
  11. package/dist/fulltext/types.js +1 -0
  12. package/dist/fulltext/types.js.map +7 -0
  13. package/dist/index.js +12 -0
  14. package/dist/index.js.map +7 -0
  15. package/dist/indexer/index.js +8 -0
  16. package/dist/indexer/index.js.map +7 -0
  17. package/dist/indexer/search-indexer.js +848 -0
  18. package/dist/indexer/search-indexer.js.map +7 -0
  19. package/dist/indexer/subscribers/delete.js +41 -0
  20. package/dist/indexer/subscribers/delete.js.map +7 -0
  21. package/dist/lib/debug.js +34 -0
  22. package/dist/lib/debug.js.map +7 -0
  23. package/dist/lib/fallback-presenter.js +107 -0
  24. package/dist/lib/fallback-presenter.js.map +7 -0
  25. package/dist/lib/field-policy.js +75 -0
  26. package/dist/lib/field-policy.js.map +7 -0
  27. package/dist/lib/index.js +19 -0
  28. package/dist/lib/index.js.map +7 -0
  29. package/dist/lib/merger.js +93 -0
  30. package/dist/lib/merger.js.map +7 -0
  31. package/dist/lib/presenter-enricher.js +192 -0
  32. package/dist/lib/presenter-enricher.js.map +7 -0
  33. package/dist/modules/search/acl.js +14 -0
  34. package/dist/modules/search/acl.js.map +7 -0
  35. package/dist/modules/search/ai-tools.js +284 -0
  36. package/dist/modules/search/ai-tools.js.map +7 -0
  37. package/dist/modules/search/api/embeddings/reindex/cancel/route.js +65 -0
  38. package/dist/modules/search/api/embeddings/reindex/cancel/route.js.map +7 -0
  39. package/dist/modules/search/api/embeddings/reindex/route.js +165 -0
  40. package/dist/modules/search/api/embeddings/reindex/route.js.map +7 -0
  41. package/dist/modules/search/api/embeddings/route.js +246 -0
  42. package/dist/modules/search/api/embeddings/route.js.map +7 -0
  43. package/dist/modules/search/api/index/route.js +245 -0
  44. package/dist/modules/search/api/index/route.js.map +7 -0
  45. package/dist/modules/search/api/reindex/cancel/route.js +65 -0
  46. package/dist/modules/search/api/reindex/cancel/route.js.map +7 -0
  47. package/dist/modules/search/api/reindex/route.js +332 -0
  48. package/dist/modules/search/api/reindex/route.js.map +7 -0
  49. package/dist/modules/search/api/search/global/route.js +100 -0
  50. package/dist/modules/search/api/search/global/route.js.map +7 -0
  51. package/dist/modules/search/api/search/route.js +101 -0
  52. package/dist/modules/search/api/search/route.js.map +7 -0
  53. package/dist/modules/search/api/settings/fulltext/route.js +55 -0
  54. package/dist/modules/search/api/settings/fulltext/route.js.map +7 -0
  55. package/dist/modules/search/api/settings/global-search/route.js +80 -0
  56. package/dist/modules/search/api/settings/global-search/route.js.map +7 -0
  57. package/dist/modules/search/api/settings/route.js +118 -0
  58. package/dist/modules/search/api/settings/route.js.map +7 -0
  59. package/dist/modules/search/api/settings/vector-store/route.js +77 -0
  60. package/dist/modules/search/api/settings/vector-store/route.js.map +7 -0
  61. package/dist/modules/search/backend/config/search/page.js +10 -0
  62. package/dist/modules/search/backend/config/search/page.js.map +7 -0
  63. package/dist/modules/search/backend/config/search/page.meta.js +24 -0
  64. package/dist/modules/search/backend/config/search/page.meta.js.map +7 -0
  65. package/dist/modules/search/cli.js +698 -0
  66. package/dist/modules/search/cli.js.map +7 -0
  67. package/dist/modules/search/di.js +32 -0
  68. package/dist/modules/search/di.js.map +7 -0
  69. package/dist/modules/search/frontend/components/GlobalSearchDialog.js +357 -0
  70. package/dist/modules/search/frontend/components/GlobalSearchDialog.js.map +7 -0
  71. package/dist/modules/search/frontend/components/HybridSearchTable.js +343 -0
  72. package/dist/modules/search/frontend/components/HybridSearchTable.js.map +7 -0
  73. package/dist/modules/search/frontend/components/SearchSettingsPageClient.js +303 -0
  74. package/dist/modules/search/frontend/components/SearchSettingsPageClient.js.map +7 -0
  75. package/dist/modules/search/frontend/components/sections/FulltextSearchSection.js +360 -0
  76. package/dist/modules/search/frontend/components/sections/FulltextSearchSection.js.map +7 -0
  77. package/dist/modules/search/frontend/components/sections/GlobalSearchSection.js +101 -0
  78. package/dist/modules/search/frontend/components/sections/GlobalSearchSection.js.map +7 -0
  79. package/dist/modules/search/frontend/components/sections/VectorSearchSection.js +608 -0
  80. package/dist/modules/search/frontend/components/sections/VectorSearchSection.js.map +7 -0
  81. package/dist/modules/search/frontend/index.js +9 -0
  82. package/dist/modules/search/frontend/index.js.map +7 -0
  83. package/dist/modules/search/frontend/utils.js +41 -0
  84. package/dist/modules/search/frontend/utils.js.map +7 -0
  85. package/dist/modules/search/i18n/de.json +61 -0
  86. package/dist/modules/search/i18n/en.json +72 -0
  87. package/dist/modules/search/i18n/es.json +61 -0
  88. package/dist/modules/search/i18n/pl.json +61 -0
  89. package/dist/modules/search/index.js +11 -0
  90. package/dist/modules/search/index.js.map +7 -0
  91. package/dist/modules/search/lib/auto-indexing.js +29 -0
  92. package/dist/modules/search/lib/auto-indexing.js.map +7 -0
  93. package/dist/modules/search/lib/embedding-config.js +131 -0
  94. package/dist/modules/search/lib/embedding-config.js.map +7 -0
  95. package/dist/modules/search/lib/global-search-config.js +45 -0
  96. package/dist/modules/search/lib/global-search-config.js.map +7 -0
  97. package/dist/modules/search/lib/reindex-lock.js +99 -0
  98. package/dist/modules/search/lib/reindex-lock.js.map +7 -0
  99. package/dist/modules/search/subscribers/fulltext_upsert.js +64 -0
  100. package/dist/modules/search/subscribers/fulltext_upsert.js.map +7 -0
  101. package/dist/modules/search/subscribers/vector_delete.js +58 -0
  102. package/dist/modules/search/subscribers/vector_delete.js.map +7 -0
  103. package/dist/modules/search/subscribers/vector_purge.js +142 -0
  104. package/dist/modules/search/subscribers/vector_purge.js.map +7 -0
  105. package/dist/modules/search/subscribers/vector_upsert.js +58 -0
  106. package/dist/modules/search/subscribers/vector_upsert.js.map +7 -0
  107. package/dist/modules/search/workers/fulltext-index.worker.js +240 -0
  108. package/dist/modules/search/workers/fulltext-index.worker.js.map +7 -0
  109. package/dist/modules/search/workers/vector-index.worker.js +234 -0
  110. package/dist/modules/search/workers/vector-index.worker.js.map +7 -0
  111. package/dist/queue/fulltext-indexing.js +15 -0
  112. package/dist/queue/fulltext-indexing.js.map +7 -0
  113. package/dist/queue/index.js +3 -0
  114. package/dist/queue/index.js.map +7 -0
  115. package/dist/queue/vector-indexing.js +15 -0
  116. package/dist/queue/vector-indexing.js.map +7 -0
  117. package/dist/service.js +286 -0
  118. package/dist/service.js.map +7 -0
  119. package/dist/strategies/fulltext.strategy.js +116 -0
  120. package/dist/strategies/fulltext.strategy.js.map +7 -0
  121. package/dist/strategies/index.js +12 -0
  122. package/dist/strategies/index.js.map +7 -0
  123. package/dist/strategies/token.strategy.js +80 -0
  124. package/dist/strategies/token.strategy.js.map +7 -0
  125. package/dist/strategies/vector.strategy.js +137 -0
  126. package/dist/strategies/vector.strategy.js.map +7 -0
  127. package/dist/types.js +1 -0
  128. package/dist/types.js.map +7 -0
  129. package/dist/vector/drivers/chromadb/index.js +44 -0
  130. package/dist/vector/drivers/chromadb/index.js.map +7 -0
  131. package/dist/vector/drivers/index.js +9 -0
  132. package/dist/vector/drivers/index.js.map +7 -0
  133. package/dist/vector/drivers/pgvector/index.js +509 -0
  134. package/dist/vector/drivers/pgvector/index.js.map +7 -0
  135. package/dist/vector/drivers/qdrant/index.js +44 -0
  136. package/dist/vector/drivers/qdrant/index.js.map +7 -0
  137. package/dist/vector/index.js +4 -0
  138. package/dist/vector/index.js.map +7 -0
  139. package/dist/vector/lib/vector-logs.js +33 -0
  140. package/dist/vector/lib/vector-logs.js.map +7 -0
  141. package/dist/vector/services/checksum.js +20 -0
  142. package/dist/vector/services/checksum.js.map +7 -0
  143. package/dist/vector/services/embedding.js +222 -0
  144. package/dist/vector/services/embedding.js.map +7 -0
  145. package/dist/vector/services/index.js +4 -0
  146. package/dist/vector/services/index.js.map +7 -0
  147. package/dist/vector/services/vector-index.service.js +960 -0
  148. package/dist/vector/services/vector-index.service.js.map +7 -0
  149. package/dist/vector/types/pg.d.js +1 -0
  150. package/dist/vector/types/pg.d.js.map +7 -0
  151. package/dist/vector/types.js +75 -0
  152. package/dist/vector/types.js.map +7 -0
  153. package/jest.config.cjs +19 -0
  154. package/package.json +142 -0
  155. package/src/__tests__/queue.test.ts +148 -0
  156. package/src/__tests__/service.test.ts +345 -0
  157. package/src/__tests__/workers.test.ts +319 -0
  158. package/src/di.ts +291 -0
  159. package/src/fulltext/drivers/index.ts +41 -0
  160. package/src/fulltext/drivers/meilisearch/index.ts +410 -0
  161. package/src/fulltext/index.ts +13 -0
  162. package/src/fulltext/types.ts +115 -0
  163. package/src/index.ts +36 -0
  164. package/src/indexer/index.ts +13 -0
  165. package/src/indexer/search-indexer.ts +1141 -0
  166. package/src/indexer/subscribers/delete.ts +49 -0
  167. package/src/lib/debug.ts +46 -0
  168. package/src/lib/fallback-presenter.ts +106 -0
  169. package/src/lib/field-policy.ts +169 -0
  170. package/src/lib/index.ts +13 -0
  171. package/src/lib/merger.ts +159 -0
  172. package/src/lib/presenter-enricher.ts +323 -0
  173. package/src/modules/search/README.md +694 -0
  174. package/src/modules/search/acl.ts +10 -0
  175. package/src/modules/search/ai-tools.ts +467 -0
  176. package/src/modules/search/api/embeddings/reindex/cancel/route.ts +77 -0
  177. package/src/modules/search/api/embeddings/reindex/route.ts +197 -0
  178. package/src/modules/search/api/embeddings/route.ts +304 -0
  179. package/src/modules/search/api/index/route.ts +297 -0
  180. package/src/modules/search/api/reindex/cancel/route.ts +77 -0
  181. package/src/modules/search/api/reindex/route.ts +419 -0
  182. package/src/modules/search/api/search/global/route.ts +120 -0
  183. package/src/modules/search/api/search/route.ts +121 -0
  184. package/src/modules/search/api/settings/fulltext/route.ts +82 -0
  185. package/src/modules/search/api/settings/global-search/route.ts +91 -0
  186. package/src/modules/search/api/settings/route.ts +187 -0
  187. package/src/modules/search/api/settings/vector-store/route.ts +105 -0
  188. package/src/modules/search/backend/config/search/page.meta.ts +22 -0
  189. package/src/modules/search/backend/config/search/page.tsx +12 -0
  190. package/src/modules/search/cli.ts +818 -0
  191. package/src/modules/search/di.ts +50 -0
  192. package/src/modules/search/frontend/components/GlobalSearchDialog.tsx +436 -0
  193. package/src/modules/search/frontend/components/HybridSearchTable.tsx +418 -0
  194. package/src/modules/search/frontend/components/SearchSettingsPageClient.tsx +476 -0
  195. package/src/modules/search/frontend/components/sections/FulltextSearchSection.tsx +624 -0
  196. package/src/modules/search/frontend/components/sections/GlobalSearchSection.tsx +124 -0
  197. package/src/modules/search/frontend/components/sections/VectorSearchSection.tsx +943 -0
  198. package/src/modules/search/frontend/index.ts +3 -0
  199. package/src/modules/search/frontend/utils.ts +82 -0
  200. package/src/modules/search/i18n/de.json +61 -0
  201. package/src/modules/search/i18n/en.json +72 -0
  202. package/src/modules/search/i18n/es.json +61 -0
  203. package/src/modules/search/i18n/pl.json +61 -0
  204. package/src/modules/search/index.ts +9 -0
  205. package/src/modules/search/lib/auto-indexing.ts +35 -0
  206. package/src/modules/search/lib/embedding-config.ts +161 -0
  207. package/src/modules/search/lib/global-search-config.ts +69 -0
  208. package/src/modules/search/lib/reindex-lock.ts +201 -0
  209. package/src/modules/search/subscribers/fulltext_upsert.ts +83 -0
  210. package/src/modules/search/subscribers/vector_delete.ts +75 -0
  211. package/src/modules/search/subscribers/vector_purge.ts +161 -0
  212. package/src/modules/search/subscribers/vector_upsert.ts +75 -0
  213. package/src/modules/search/workers/fulltext-index.worker.ts +318 -0
  214. package/src/modules/search/workers/vector-index.worker.ts +292 -0
  215. package/src/queue/fulltext-indexing.ts +87 -0
  216. package/src/queue/index.ts +2 -0
  217. package/src/queue/vector-indexing.ts +66 -0
  218. package/src/service.ts +397 -0
  219. package/src/strategies/fulltext.strategy.ts +155 -0
  220. package/src/strategies/index.ts +17 -0
  221. package/src/strategies/token.strategy.ts +153 -0
  222. package/src/strategies/vector.strategy.ts +234 -0
  223. package/src/types.ts +38 -0
  224. package/src/vector/drivers/chromadb/index.ts +49 -0
  225. package/src/vector/drivers/index.ts +4 -0
  226. package/src/vector/drivers/pgvector/index.ts +627 -0
  227. package/src/vector/drivers/qdrant/index.ts +49 -0
  228. package/src/vector/index.ts +3 -0
  229. package/src/vector/lib/vector-logs.ts +46 -0
  230. package/src/vector/services/checksum.ts +18 -0
  231. package/src/vector/services/embedding.ts +275 -0
  232. package/src/vector/services/index.ts +3 -0
  233. package/src/vector/services/vector-index.service.ts +1234 -0
  234. package/src/vector/types/pg.d.ts +1 -0
  235. package/src/vector/types.ts +220 -0
  236. package/tsconfig.json +9 -0
  237. package/watch.mjs +6 -0
@@ -0,0 +1,960 @@
1
+ import { computeChecksum } from "./checksum.js";
2
+ import { logVectorOperation } from "../lib/vector-logs.js";
3
+ import { searchDebug, searchDebugWarn } from "../../lib/debug.js";
4
+ const VECTOR_ENTRY_ENCRYPTION_ENTITY_ID = "vector:vector_search";
5
+ const ENRICHMENT_FIELD_HINTS = {
6
+ "customers:customer_entity": [
7
+ "id",
8
+ "organization_id",
9
+ "tenant_id",
10
+ "display_name",
11
+ "description",
12
+ "status",
13
+ "lifecycle_stage",
14
+ "primary_email",
15
+ "primary_phone",
16
+ "kind",
17
+ "customer_kind"
18
+ ],
19
+ "customers:customer_comment": ["id", "organization_id", "tenant_id", "entity_id", "body", "appearance_icon", "appearance_color"],
20
+ "customers:customer_activity": ["id", "organization_id", "tenant_id", "entity_id", "activity_type", "subject", "body", "deal_id"],
21
+ "customers:customer_deal": ["id", "organization_id", "tenant_id", "title", "pipeline_stage", "status", "value_amount", "value_currency"],
22
+ "customers:customer_todo_link": ["id", "organization_id", "tenant_id", "entity_id", "todo_id", "todo_source"],
23
+ "customers:customer_person_profile": [
24
+ "id",
25
+ "organization_id",
26
+ "tenant_id",
27
+ "entity_id",
28
+ "first_name",
29
+ "last_name",
30
+ "preferred_name",
31
+ "job_title",
32
+ "department"
33
+ ],
34
+ "customers:customer_company_profile": [
35
+ "id",
36
+ "organization_id",
37
+ "tenant_id",
38
+ "entity_id",
39
+ "brand_name",
40
+ "legal_name",
41
+ "domain",
42
+ "industry",
43
+ "size_bucket"
44
+ ]
45
+ };
46
+ class VectorIndexService {
47
+ constructor(opts) {
48
+ this.opts = opts;
49
+ this.driverMap = /* @__PURE__ */ new Map();
50
+ this.entityConfig = /* @__PURE__ */ new Map();
51
+ for (const driver of opts.drivers) {
52
+ this.driverMap.set(driver.id, driver);
53
+ }
54
+ this.defaultDriverId = opts.defaultDriverId ?? "pgvector";
55
+ for (const moduleConfig of opts.moduleConfigs) {
56
+ const driverId = moduleConfig.defaultDriverId ?? this.defaultDriverId;
57
+ for (const entity of moduleConfig.entities ?? []) {
58
+ if (!entity?.entityId) continue;
59
+ if (entity.enabled === false) continue;
60
+ const targetDriver = entity.driverId ?? driverId;
61
+ this.entityConfig.set(entity.entityId, { config: entity, driverId: targetDriver });
62
+ }
63
+ }
64
+ }
65
+ resolveEncryptionService() {
66
+ if (!this.opts.containerResolver) return null;
67
+ try {
68
+ const container = this.opts.containerResolver();
69
+ if (!container || typeof container.resolve !== "function") return null;
70
+ return container.resolve("tenantEncryptionService");
71
+ } catch {
72
+ return null;
73
+ }
74
+ }
75
+ async encryptResultFields(args) {
76
+ const service = this.resolveEncryptionService();
77
+ if (!service || !service.isEnabled?.()) {
78
+ return {
79
+ resultTitle: args.resultTitle,
80
+ resultSubtitle: args.resultSubtitle,
81
+ resultIcon: args.resultIcon,
82
+ resultSnapshot: args.resultSnapshot,
83
+ primaryLinkHref: args.primaryLinkHref,
84
+ primaryLinkLabel: args.primaryLinkLabel,
85
+ links: args.links,
86
+ payload: args.payload
87
+ };
88
+ }
89
+ try {
90
+ const encrypted = await service.encryptEntityPayload(
91
+ VECTOR_ENTRY_ENCRYPTION_ENTITY_ID,
92
+ {
93
+ resultTitle: args.resultTitle,
94
+ resultSubtitle: args.resultSubtitle,
95
+ resultIcon: args.resultIcon,
96
+ resultSnapshot: args.resultSnapshot,
97
+ primaryLinkHref: args.primaryLinkHref,
98
+ primaryLinkLabel: args.primaryLinkLabel,
99
+ links: args.links,
100
+ payload: args.payload
101
+ },
102
+ args.tenantId,
103
+ args.organizationId
104
+ );
105
+ return {
106
+ resultTitle: String(encrypted.resultTitle ?? args.resultTitle),
107
+ resultSubtitle: encrypted.resultSubtitle ?? args.resultSubtitle,
108
+ resultIcon: encrypted.resultIcon ?? args.resultIcon,
109
+ resultSnapshot: encrypted.resultSnapshot ?? args.resultSnapshot,
110
+ primaryLinkHref: encrypted.primaryLinkHref ?? args.primaryLinkHref,
111
+ primaryLinkLabel: encrypted.primaryLinkLabel ?? args.primaryLinkLabel,
112
+ links: encrypted.links ?? args.links,
113
+ payload: encrypted.payload ?? args.payload
114
+ };
115
+ } catch {
116
+ return {
117
+ resultTitle: args.resultTitle,
118
+ resultSubtitle: args.resultSubtitle,
119
+ resultIcon: args.resultIcon,
120
+ resultSnapshot: args.resultSnapshot,
121
+ primaryLinkHref: args.primaryLinkHref,
122
+ primaryLinkLabel: args.primaryLinkLabel,
123
+ links: args.links,
124
+ payload: args.payload
125
+ };
126
+ }
127
+ }
128
+ async decryptResultFields(args) {
129
+ const service = this.resolveEncryptionService();
130
+ if (!service || !service.isEnabled?.() || typeof service.decryptEntityPayload !== "function") {
131
+ return {
132
+ resultTitle: args.resultTitle,
133
+ resultSubtitle: args.resultSubtitle,
134
+ resultIcon: args.resultIcon,
135
+ resultSnapshot: args.resultSnapshot,
136
+ primaryLinkHref: args.primaryLinkHref,
137
+ primaryLinkLabel: args.primaryLinkLabel,
138
+ links: args.links,
139
+ payload: args.payload
140
+ };
141
+ }
142
+ try {
143
+ const decrypted = await service.decryptEntityPayload(
144
+ VECTOR_ENTRY_ENCRYPTION_ENTITY_ID,
145
+ {
146
+ resultTitle: args.resultTitle,
147
+ resultSubtitle: args.resultSubtitle,
148
+ resultIcon: args.resultIcon,
149
+ resultSnapshot: args.resultSnapshot,
150
+ primaryLinkHref: args.primaryLinkHref,
151
+ primaryLinkLabel: args.primaryLinkLabel,
152
+ links: args.links,
153
+ payload: args.payload
154
+ },
155
+ args.tenantId,
156
+ args.organizationId
157
+ );
158
+ return {
159
+ resultTitle: String(decrypted.resultTitle ?? args.resultTitle),
160
+ resultSubtitle: decrypted.resultSubtitle ?? args.resultSubtitle,
161
+ resultIcon: decrypted.resultIcon ?? args.resultIcon,
162
+ resultSnapshot: decrypted.resultSnapshot ?? args.resultSnapshot,
163
+ primaryLinkHref: decrypted.primaryLinkHref ?? args.primaryLinkHref,
164
+ primaryLinkLabel: decrypted.primaryLinkLabel ?? args.primaryLinkLabel,
165
+ links: decrypted.links ?? args.links,
166
+ payload: decrypted.payload ?? args.payload
167
+ };
168
+ } catch {
169
+ return {
170
+ resultTitle: args.resultTitle,
171
+ resultSubtitle: args.resultSubtitle,
172
+ resultIcon: args.resultIcon,
173
+ resultSnapshot: args.resultSnapshot,
174
+ primaryLinkHref: args.primaryLinkHref,
175
+ primaryLinkLabel: args.primaryLinkLabel,
176
+ links: args.links,
177
+ payload: args.payload
178
+ };
179
+ }
180
+ }
181
+ listEnabledEntities() {
182
+ return Array.from(this.entityConfig.keys());
183
+ }
184
+ async ensureDriverReady(entityId) {
185
+ if (entityId) {
186
+ const entry = this.entityConfig.get(entityId);
187
+ if (!entry) return;
188
+ const driver = this.getDriver(entry.driverId);
189
+ await driver.ensureReady();
190
+ return;
191
+ }
192
+ const uniqueDrivers = /* @__PURE__ */ new Set();
193
+ for (const entry of this.entityConfig.values()) {
194
+ uniqueDrivers.add(entry.driverId);
195
+ }
196
+ if (!uniqueDrivers.size) uniqueDrivers.add(this.defaultDriverId);
197
+ await Promise.all(Array.from(uniqueDrivers).map(async (driverId) => {
198
+ try {
199
+ const driver = this.getDriver(driverId);
200
+ await driver.ensureReady();
201
+ } catch (err) {
202
+ searchDebugWarn("vector", "Failed to ensure driver readiness", { driverId, error: err instanceof Error ? err.message : err });
203
+ }
204
+ }));
205
+ }
206
+ getDriver(driverId) {
207
+ const driver = this.driverMap.get(driverId);
208
+ if (!driver) {
209
+ throw new Error(`[vector] Driver ${driverId} is not registered`);
210
+ }
211
+ return driver;
212
+ }
213
+ getEnrichmentFields(entityId) {
214
+ const hints = ENRICHMENT_FIELD_HINTS[entityId];
215
+ if (!hints) return void 0;
216
+ const unique = /* @__PURE__ */ new Set(["id", ...hints]);
217
+ return Array.from(unique);
218
+ }
219
+ async fetchRecord(entityId, recordIds, tenantId, organizationId) {
220
+ const filters = { id: { $in: recordIds } };
221
+ const result = await this.opts.queryEngine.query(entityId, {
222
+ tenantId,
223
+ organizationId: organizationId ?? void 0,
224
+ filters,
225
+ includeCustomFields: true,
226
+ fields: this.getEnrichmentFields(entityId)
227
+ });
228
+ const byId = /* @__PURE__ */ new Map();
229
+ for (const item of result.items) {
230
+ const key = String(item.id ?? "");
231
+ if (!key) continue;
232
+ byId.set(key, item);
233
+ }
234
+ return byId;
235
+ }
236
+ extractRecordPayload(entityId, raw) {
237
+ const record = {};
238
+ const customFields = {};
239
+ const multiMap = /* @__PURE__ */ new Map();
240
+ for (const [key, value] of Object.entries(raw)) {
241
+ if (key.startsWith("cf:") && key.endsWith("__is_multi")) {
242
+ const base = key.replace(/__is_multi$/, "");
243
+ multiMap.set(base, Boolean(value));
244
+ continue;
245
+ }
246
+ if (key.startsWith("cf:")) {
247
+ customFields[key.slice(3)] = value;
248
+ continue;
249
+ }
250
+ record[key] = value;
251
+ }
252
+ for (const [key, isMulti] of multiMap.entries()) {
253
+ const bare = key.slice(3);
254
+ if (bare && customFields[bare] != null && !Array.isArray(customFields[bare]) && isMulti) {
255
+ customFields[bare] = [customFields[bare]];
256
+ }
257
+ }
258
+ if (record.entity_id == null && record.entityId == null && entityId.endsWith("_company_profile")) {
259
+ searchDebugWarn("vector.index", "company profile missing entity id in payload", {
260
+ id: record.id,
261
+ keys: Object.keys(record)
262
+ });
263
+ }
264
+ return { record, customFields };
265
+ }
266
+ async indexExisting(entry, driver, args, raw, opts = {}) {
267
+ const scopeOrg = args.organizationId ?? null;
268
+ const { record, customFields } = this.extractRecordPayload(args.entityId, raw);
269
+ const resolvedOrgId = scopeOrg ?? (record.organization_id ?? record.organizationId ?? null);
270
+ const source = await this.resolveSource(args.entityId, entry.config, {
271
+ record,
272
+ customFields,
273
+ tenantId: args.tenantId,
274
+ organizationId: resolvedOrgId
275
+ });
276
+ if (!source) {
277
+ const existing = await driver.getChecksum(args.entityId, args.recordId, args.tenantId);
278
+ if (!opts.skipDelete && existing) {
279
+ await driver.delete(args.entityId, args.recordId, args.tenantId);
280
+ return {
281
+ action: "deleted",
282
+ existed: true,
283
+ tenantId: args.tenantId,
284
+ organizationId: resolvedOrgId
285
+ };
286
+ }
287
+ return {
288
+ action: "skipped",
289
+ existed: Boolean(existing),
290
+ tenantId: args.tenantId,
291
+ organizationId: resolvedOrgId,
292
+ reason: "missing_record"
293
+ };
294
+ }
295
+ const checksumSource = source.checksumSource ?? { record, customFields };
296
+ const checksum = computeChecksum(checksumSource);
297
+ const current = await driver.getChecksum(args.entityId, args.recordId, args.tenantId);
298
+ if (current && current === checksum) {
299
+ return {
300
+ action: "skipped",
301
+ existed: true,
302
+ tenantId: args.tenantId,
303
+ organizationId: scopeOrg,
304
+ reason: "checksum_match"
305
+ };
306
+ }
307
+ if (!this.opts.embeddingService.available) {
308
+ throw new Error("[vector] Embedding service unavailable (missing OPENAI_API_KEY)");
309
+ }
310
+ const embedding = await this.opts.embeddingService.createEmbedding(source.input);
311
+ const presenter = await this.resolvePresenter(entry.config, {
312
+ record,
313
+ customFields,
314
+ tenantId: args.tenantId,
315
+ organizationId: resolvedOrgId
316
+ }, source.presenter ?? null);
317
+ if (!presenter?.title) {
318
+ searchDebugWarn("vector.index", "missing presenter title", {
319
+ entityId: args.entityId,
320
+ recordId: args.recordId,
321
+ recordSample: {
322
+ display_name: record.display_name,
323
+ displayName: record.displayName,
324
+ name: record.name,
325
+ title: record.title,
326
+ subject: record.subject,
327
+ kind: record.kind
328
+ }
329
+ });
330
+ }
331
+ const links = await this.resolveLinks(entry.config, {
332
+ record,
333
+ customFields,
334
+ tenantId: args.tenantId,
335
+ organizationId: resolvedOrgId
336
+ }, source.links ?? null);
337
+ const url = await this.resolveUrl(entry.config, {
338
+ record,
339
+ customFields,
340
+ tenantId: args.tenantId,
341
+ organizationId: resolvedOrgId
342
+ });
343
+ const normalizedPresenter = this.ensurePresenter(presenter, record, customFields, args.recordId, args.entityId);
344
+ const normalizedLinks = Array.isArray(links) && links.length ? links : null;
345
+ const snapshot = this.deriveSnapshot(record, customFields);
346
+ const rawResultTitle = this.resolveResultTitle(normalizedPresenter, record, customFields, args.recordId);
347
+ const rawResultSubtitle = normalizedPresenter.subtitle ?? snapshot ?? null;
348
+ const rawResultIcon = normalizedPresenter.icon ?? this.mapDefaultIcon(args.entityId);
349
+ const resultBadge = normalizedPresenter.badge ?? null;
350
+ const primaryLink = this.resolvePrimaryLink(normalizedLinks, url, rawResultTitle);
351
+ const encryptedResult = await this.encryptResultFields({
352
+ tenantId: args.tenantId,
353
+ organizationId: resolvedOrgId,
354
+ resultTitle: rawResultTitle,
355
+ resultSubtitle: rawResultSubtitle,
356
+ resultIcon: rawResultIcon ?? null,
357
+ resultSnapshot: snapshot ?? null,
358
+ primaryLinkHref: primaryLink?.href ?? null,
359
+ primaryLinkLabel: primaryLink?.label ?? null,
360
+ links: normalizedLinks,
361
+ payload: source.payload ?? null
362
+ });
363
+ const presenterForStorage = {
364
+ title: encryptedResult.resultTitle,
365
+ subtitle: encryptedResult.resultSubtitle ?? void 0,
366
+ icon: encryptedResult.resultIcon ?? void 0,
367
+ badge: resultBadge ?? void 0
368
+ };
369
+ searchDebug("VectorIndexService", "Storing vector index entry", {
370
+ entityId: args.entityId,
371
+ recordId: args.recordId,
372
+ tenantId: args.tenantId,
373
+ organizationId: resolvedOrgId
374
+ });
375
+ await driver.upsert({
376
+ driverId: entry.driverId,
377
+ entityId: args.entityId,
378
+ recordId: args.recordId,
379
+ tenantId: args.tenantId,
380
+ organizationId: resolvedOrgId,
381
+ checksum,
382
+ embedding,
383
+ url: url ?? null,
384
+ presenter: presenterForStorage,
385
+ links: encryptedResult.links ?? normalizedLinks,
386
+ payload: encryptedResult.payload ?? source.payload ?? null,
387
+ resultTitle: encryptedResult.resultTitle,
388
+ resultSubtitle: encryptedResult.resultSubtitle,
389
+ resultIcon: encryptedResult.resultIcon ?? null,
390
+ resultBadge,
391
+ resultSnapshot: encryptedResult.resultSnapshot ?? snapshot,
392
+ primaryLinkHref: encryptedResult.primaryLinkHref ?? primaryLink?.href ?? null,
393
+ primaryLinkLabel: encryptedResult.primaryLinkLabel ?? null
394
+ });
395
+ return {
396
+ action: "indexed",
397
+ created: !current,
398
+ existed: true,
399
+ tenantId: args.tenantId,
400
+ organizationId: resolvedOrgId
401
+ };
402
+ }
403
+ ensurePresenter(presenter, record, customFields, recordId, entityId) {
404
+ if (presenter?.title) return presenter;
405
+ const fallback = this.buildFallbackPresenter(record, customFields, recordId, entityId);
406
+ return fallback;
407
+ }
408
+ resolveResultTitle(presenter, record, customFields, recordId) {
409
+ const candidate = presenter.title ?? record.display_name ?? record.displayName ?? record.name ?? record.title ?? record.subject ?? customFields.title ?? customFields.name ?? recordId;
410
+ const text = String(candidate).trim();
411
+ return text.length ? text : recordId;
412
+ }
413
+ buildFallbackPresenter(record, customFields, recordId, entityId) {
414
+ const titleCandidate = record.display_name ?? record.displayName ?? record.name ?? record.title ?? record.subject ?? recordId;
415
+ const subtitleCandidate = record.description ?? record.summary ?? record.body ?? customFields.summary ?? customFields.description ?? null;
416
+ const icon = typeof record.kind === "string" ? this.mapEntityIcon(record.kind) : this.mapDefaultIcon(entityId);
417
+ return {
418
+ title: String(titleCandidate),
419
+ subtitle: subtitleCandidate ? String(subtitleCandidate) : void 0,
420
+ icon: icon ?? void 0
421
+ };
422
+ }
423
+ mapEntityIcon(kind) {
424
+ if (!kind) return null;
425
+ const normalized = kind.toLowerCase();
426
+ if (normalized === "person") return "user";
427
+ if (normalized === "company" || normalized === "organization") return "building";
428
+ return null;
429
+ }
430
+ mapDefaultIcon(entityId) {
431
+ if (entityId.startsWith("customers:customer_deal")) return "briefcase";
432
+ if (entityId.startsWith("customers:customer_comment")) return "sticky-note";
433
+ if (entityId.startsWith("customers:customer_activity")) return "bolt";
434
+ if (entityId.startsWith("customers:customer_todo")) return "check-square";
435
+ return null;
436
+ }
437
+ resolvePrimaryLink(links, url, fallbackLabel) {
438
+ if (links?.length) {
439
+ const primary = links.find((link) => link.kind === "primary") ?? links[0];
440
+ if (primary?.href) {
441
+ return { href: primary.href, label: primary.label ?? fallbackLabel };
442
+ }
443
+ }
444
+ if (url) {
445
+ return { href: url, label: fallbackLabel };
446
+ }
447
+ return null;
448
+ }
449
+ deriveSnapshot(record, customFields) {
450
+ const candidates = [
451
+ record.summary,
452
+ record.description,
453
+ record.body,
454
+ customFields.summary,
455
+ customFields.description,
456
+ customFields.body
457
+ ];
458
+ for (const value of candidates) {
459
+ if (typeof value === "string") {
460
+ const trimmed = value.trim();
461
+ if (trimmed.length) return trimmed;
462
+ }
463
+ }
464
+ return null;
465
+ }
466
+ buildDefaultSource(entityId, payload) {
467
+ const { record, customFields } = payload;
468
+ const lines = [];
469
+ const pushEntry = (label, value) => {
470
+ if (value === null || value === void 0) return;
471
+ if (typeof value === "string" && value.trim().length === 0) return;
472
+ if (typeof value === "object") {
473
+ lines.push(`${label}: ${JSON.stringify(value)}`);
474
+ } else {
475
+ lines.push(`${label}: ${value}`);
476
+ }
477
+ };
478
+ const preferredFields = ["title", "name", "displayName", "summary", "subject"];
479
+ for (const field of preferredFields) {
480
+ if (record[field] != null) pushEntry(field, record[field]);
481
+ }
482
+ for (const [key, value] of Object.entries(record)) {
483
+ if (preferredFields.includes(key)) continue;
484
+ if (key === "id" || key === "tenantId" || key === "organizationId" || key === "createdAt" || key === "updatedAt") continue;
485
+ pushEntry(key, value);
486
+ }
487
+ for (const [key, value] of Object.entries(customFields)) {
488
+ pushEntry(`custom.${key}`, value);
489
+ }
490
+ if (lines.length === 0) {
491
+ lines.push(`${entityId}#${record.id ?? ""}`);
492
+ }
493
+ return {
494
+ input: lines,
495
+ payload: null,
496
+ checksumSource: { record, customFields }
497
+ };
498
+ }
499
+ async resolveSource(entityId, config, ctx) {
500
+ const baseCtx = {
501
+ record: ctx.record,
502
+ customFields: ctx.customFields,
503
+ organizationId: ctx.organizationId ?? null,
504
+ tenantId: ctx.tenantId,
505
+ queryEngine: this.opts.queryEngine,
506
+ container: this.opts.containerResolver ? this.opts.containerResolver() : void 0
507
+ };
508
+ if (config.buildSource) {
509
+ const built = await config.buildSource(baseCtx);
510
+ if (built) return built;
511
+ return null;
512
+ }
513
+ return this.buildDefaultSource(entityId, { record: ctx.record, customFields: ctx.customFields });
514
+ }
515
+ async resolvePresenter(config, ctx, fallback) {
516
+ const baseCtx = {
517
+ record: ctx.record,
518
+ customFields: ctx.customFields,
519
+ organizationId: ctx.organizationId ?? null,
520
+ tenantId: ctx.tenantId,
521
+ queryEngine: this.opts.queryEngine,
522
+ container: this.opts.containerResolver ? this.opts.containerResolver() : void 0
523
+ };
524
+ if (config.formatResult) {
525
+ const formatted = await config.formatResult(baseCtx);
526
+ if (formatted) return formatted;
527
+ }
528
+ if (fallback) return fallback;
529
+ const nameLike = ctx.record.displayName || ctx.record.title || ctx.record.name;
530
+ if (typeof nameLike === "string" && nameLike.trim().length > 0) {
531
+ const subtitle = ctx.record.description || ctx.record.summary;
532
+ return {
533
+ title: nameLike,
534
+ subtitle: typeof subtitle === "string" ? subtitle : void 0
535
+ };
536
+ }
537
+ return null;
538
+ }
539
+ async resolveLinks(config, ctx, fallback) {
540
+ const baseCtx = {
541
+ record: ctx.record,
542
+ customFields: ctx.customFields,
543
+ organizationId: ctx.organizationId ?? null,
544
+ tenantId: ctx.tenantId,
545
+ queryEngine: this.opts.queryEngine,
546
+ container: this.opts.containerResolver ? this.opts.containerResolver() : void 0
547
+ };
548
+ if (config.resolveLinks) {
549
+ const resolved = await config.resolveLinks(baseCtx);
550
+ if (resolved?.length) return resolved;
551
+ }
552
+ return fallback ?? null;
553
+ }
554
+ async resolveUrl(config, ctx, fallback) {
555
+ if (config.resolveUrl) {
556
+ const candidate = await config.resolveUrl({
557
+ record: ctx.record,
558
+ customFields: ctx.customFields,
559
+ organizationId: ctx.organizationId ?? null,
560
+ tenantId: ctx.tenantId,
561
+ queryEngine: this.opts.queryEngine,
562
+ container: this.opts.containerResolver ? this.opts.containerResolver() : void 0
563
+ });
564
+ if (candidate) return candidate;
565
+ }
566
+ return fallback ?? null;
567
+ }
568
+ async indexRecord(args) {
569
+ const entry = this.entityConfig.get(args.entityId);
570
+ if (!entry) {
571
+ return {
572
+ action: "skipped",
573
+ existed: false,
574
+ tenantId: args.tenantId,
575
+ organizationId: args.organizationId ?? null,
576
+ reason: "unsupported"
577
+ };
578
+ }
579
+ const driver = this.getDriver(entry.driverId);
580
+ await driver.ensureReady();
581
+ const records = await this.fetchRecord(args.entityId, [args.recordId], args.tenantId, args.organizationId);
582
+ const raw = records.get(args.recordId);
583
+ if (!raw) {
584
+ const existing = await driver.getChecksum(args.entityId, args.recordId, args.tenantId);
585
+ if (existing) {
586
+ await driver.delete(args.entityId, args.recordId, args.tenantId);
587
+ return {
588
+ action: "deleted",
589
+ existed: true,
590
+ tenantId: args.tenantId,
591
+ organizationId: args.organizationId ?? null
592
+ };
593
+ }
594
+ return {
595
+ action: "skipped",
596
+ existed: false,
597
+ tenantId: args.tenantId,
598
+ organizationId: args.organizationId ?? null,
599
+ reason: "missing_record"
600
+ };
601
+ }
602
+ return this.indexExisting(entry, driver, args, raw);
603
+ }
604
+ async deleteRecord(args) {
605
+ const entry = this.entityConfig.get(args.entityId);
606
+ if (!entry) {
607
+ return {
608
+ action: "skipped",
609
+ existed: false,
610
+ tenantId: args.tenantId,
611
+ organizationId: args.organizationId ?? null,
612
+ reason: "unsupported"
613
+ };
614
+ }
615
+ const driver = this.getDriver(entry.driverId);
616
+ await driver.ensureReady();
617
+ const scopeOrg = args.organizationId ?? null;
618
+ const existing = await driver.getChecksum(args.entityId, args.recordId, args.tenantId);
619
+ if (existing) {
620
+ await driver.delete(args.entityId, args.recordId, args.tenantId);
621
+ return {
622
+ action: "deleted",
623
+ existed: true,
624
+ tenantId: args.tenantId,
625
+ organizationId: scopeOrg
626
+ };
627
+ }
628
+ return {
629
+ action: "skipped",
630
+ existed: false,
631
+ tenantId: args.tenantId,
632
+ organizationId: scopeOrg,
633
+ reason: "missing_record"
634
+ };
635
+ }
636
+ async reindexEntity(args) {
637
+ const entry = this.entityConfig.get(args.entityId);
638
+ if (!entry) return;
639
+ const driver = this.getDriver(entry.driverId);
640
+ await driver.ensureReady();
641
+ const shouldPurge = args.purgeFirst === true;
642
+ const reindexStartedAt = /* @__PURE__ */ new Date();
643
+ if (this.opts.eventBus) {
644
+ if (shouldPurge && driver.purge && args.tenantId) {
645
+ await driver.purge(args.entityId, args.tenantId);
646
+ } else if (shouldPurge && !args.tenantId) {
647
+ searchDebugWarn("vector", "Skipping purge for multi-tenant reindex (tenant not provided)");
648
+ }
649
+ const payload = {
650
+ entityType: args.entityId
651
+ };
652
+ if (shouldPurge) {
653
+ payload.force = true;
654
+ payload.resetCoverage = true;
655
+ }
656
+ if (args.tenantId !== void 0) payload.tenantId = args.tenantId;
657
+ if (args.organizationId !== void 0) payload.organizationId = args.organizationId;
658
+ await this.opts.eventBus.emitEvent("query_index.reindex", payload);
659
+ return;
660
+ }
661
+ if (!args.tenantId) {
662
+ throw new Error("[vector] Reindex without tenantId requires event bus integration");
663
+ }
664
+ if (shouldPurge && driver.purge) {
665
+ await driver.purge(args.entityId, args.tenantId);
666
+ }
667
+ const pageSize = 50;
668
+ let page = 1;
669
+ const loggingEm = this.resolveEntityManager();
670
+ for (; ; ) {
671
+ const result = await this.opts.queryEngine.query(args.entityId, {
672
+ tenantId: args.tenantId,
673
+ organizationId: args.organizationId ?? void 0,
674
+ page: { page, pageSize },
675
+ includeCustomFields: true,
676
+ fields: this.getEnrichmentFields(args.entityId)
677
+ });
678
+ if (!result.items.length) break;
679
+ for (const raw of result.items) {
680
+ const recordId = String(raw.id ?? "");
681
+ if (!recordId) continue;
682
+ const opResult = await this.indexExisting(
683
+ entry,
684
+ driver,
685
+ {
686
+ entityId: args.entityId,
687
+ recordId,
688
+ tenantId: args.tenantId,
689
+ organizationId: args.organizationId ?? null
690
+ },
691
+ raw,
692
+ { skipDelete: true }
693
+ );
694
+ await logVectorOperation({
695
+ em: loggingEm,
696
+ handler: "service:vector.reindex",
697
+ entityType: args.entityId,
698
+ recordId,
699
+ result: opResult
700
+ });
701
+ }
702
+ if (result.items.length < pageSize) break;
703
+ page += 1;
704
+ }
705
+ if (shouldPurge) {
706
+ await this.removeOrphans({
707
+ entityId: args.entityId,
708
+ tenantId: args.tenantId,
709
+ organizationId: args.organizationId,
710
+ olderThan: reindexStartedAt
711
+ });
712
+ }
713
+ }
714
+ async reindexAll(args) {
715
+ for (const entityId of this.listEnabledEntities()) {
716
+ await this.reindexEntity({ entityId, tenantId: args.tenantId, organizationId: args.organizationId ?? null, purgeFirst: args.purgeFirst });
717
+ }
718
+ }
719
+ resolveEntityManager() {
720
+ if (!this.opts.containerResolver) return null;
721
+ try {
722
+ const container = this.opts.containerResolver();
723
+ if (!container || typeof container.resolve !== "function") return null;
724
+ const resolver = container;
725
+ const em = resolver.resolve("em");
726
+ return em ?? null;
727
+ } catch {
728
+ return null;
729
+ }
730
+ }
731
+ async purgeIndex(args) {
732
+ const targets = args.entityId ? [args.entityId] : this.listEnabledEntities();
733
+ if (!targets.length) return;
734
+ const grouped = /* @__PURE__ */ new Map();
735
+ for (const entityId of targets) {
736
+ const cfg = this.entityConfig.get(entityId);
737
+ if (!cfg) continue;
738
+ const driver = this.getDriver(cfg.driverId);
739
+ if (typeof driver.purge !== "function") {
740
+ throw new Error(`[vector] Driver ${cfg.driverId} does not support purging entities`);
741
+ }
742
+ if (!grouped.has(cfg.driverId)) grouped.set(cfg.driverId, []);
743
+ grouped.get(cfg.driverId).push(entityId);
744
+ }
745
+ for (const [driverId, entityIds] of grouped.entries()) {
746
+ const driver = this.getDriver(driverId);
747
+ await driver.ensureReady();
748
+ for (const entityId of entityIds) {
749
+ await driver.purge(entityId, args.tenantId);
750
+ }
751
+ }
752
+ }
753
+ async removeOrphans(args) {
754
+ const entry = this.entityConfig.get(args.entityId);
755
+ if (!entry) return 0;
756
+ const driver = this.getDriver(entry.driverId);
757
+ await driver.ensureReady();
758
+ const driverAny = driver;
759
+ if (typeof driverAny.removeOrphans === "function") {
760
+ const deleted = await driverAny.removeOrphans({
761
+ entityId: args.entityId,
762
+ tenantId: args.tenantId,
763
+ organizationId: args.organizationId,
764
+ olderThan: args.olderThan
765
+ });
766
+ return typeof deleted === "number" ? deleted : 0;
767
+ }
768
+ searchDebugWarn("vector", "Driver does not support orphan cleanup", { driverId: entry.driverId });
769
+ return 0;
770
+ }
771
+ async countIndexEntries(args) {
772
+ if (!args.tenantId) return 0;
773
+ const targetEntity = args.entityId ? this.entityConfig.get(args.entityId) : null;
774
+ if (args.entityId && !targetEntity) {
775
+ return 0;
776
+ }
777
+ const driverId = args.driverId ?? (targetEntity ? targetEntity.driverId : this.defaultDriverId);
778
+ const driver = this.getDriver(driverId);
779
+ await driver.ensureReady();
780
+ const countParams = {
781
+ tenantId: args.tenantId,
782
+ organizationId: args.organizationId,
783
+ entityId: args.entityId
784
+ };
785
+ if (typeof driver.count === "function") {
786
+ try {
787
+ return await driver.count(countParams);
788
+ } catch (err) {
789
+ searchDebugWarn("vector", "Driver count failed, falling back to list", {
790
+ driverId,
791
+ error: err instanceof Error ? err.message : err
792
+ });
793
+ }
794
+ }
795
+ if (typeof driver.list === "function") {
796
+ const limit = 1e3;
797
+ let offset = 0;
798
+ let total = 0;
799
+ for (; ; ) {
800
+ const batch = await driver.list({
801
+ tenantId: countParams.tenantId,
802
+ organizationId: countParams.organizationId,
803
+ entityId: countParams.entityId,
804
+ limit,
805
+ offset,
806
+ orderBy: "created"
807
+ });
808
+ const size = batch.length;
809
+ total += size;
810
+ if (size < limit) break;
811
+ offset += limit;
812
+ }
813
+ return total;
814
+ }
815
+ searchDebugWarn("vector", "Driver does not support counting or listing index entries", { driverId });
816
+ return 0;
817
+ }
818
+ async listIndexEntries(args) {
819
+ const targetEntity = args.entityId ? this.entityConfig.get(args.entityId) : null;
820
+ if (args.entityId && !targetEntity) {
821
+ return [];
822
+ }
823
+ const driverId = args.driverId ?? (targetEntity ? targetEntity.driverId : this.defaultDriverId);
824
+ const driver = this.getDriver(driverId);
825
+ if (typeof driver.list !== "function") {
826
+ throw new Error(`[vector] Driver ${driverId} does not support listing index entries`);
827
+ }
828
+ await driver.ensureReady();
829
+ const list = await driver.list({
830
+ tenantId: args.tenantId,
831
+ organizationId: args.organizationId,
832
+ entityId: args.entityId,
833
+ limit: args.limit,
834
+ offset: args.offset,
835
+ orderBy: "updated"
836
+ });
837
+ if (!list.length) {
838
+ return [];
839
+ }
840
+ const decrypted = await Promise.all(
841
+ list.map(async (entry) => {
842
+ const decryptedResult = await this.decryptResultFields({
843
+ tenantId: args.tenantId,
844
+ organizationId: entry.organizationId ?? null,
845
+ resultTitle: entry.resultTitle,
846
+ resultSubtitle: entry.resultSubtitle ?? null,
847
+ resultIcon: entry.resultIcon ?? null,
848
+ resultSnapshot: entry.resultSnapshot ?? null,
849
+ primaryLinkHref: entry.primaryLinkHref ?? null,
850
+ primaryLinkLabel: entry.primaryLinkLabel ?? null,
851
+ links: entry.links ?? null,
852
+ payload: entry.payload ?? null
853
+ });
854
+ return {
855
+ ...entry,
856
+ resultTitle: decryptedResult.resultTitle,
857
+ resultSubtitle: decryptedResult.resultSubtitle,
858
+ resultIcon: decryptedResult.resultIcon,
859
+ resultSnapshot: decryptedResult.resultSnapshot,
860
+ primaryLinkHref: decryptedResult.primaryLinkHref,
861
+ primaryLinkLabel: decryptedResult.primaryLinkLabel,
862
+ links: decryptedResult.links,
863
+ payload: decryptedResult.payload,
864
+ metadata: decryptedResult.payload ?? null
865
+ };
866
+ })
867
+ );
868
+ return decrypted.map((entry) => {
869
+ const presenter = {
870
+ title: entry.resultTitle,
871
+ subtitle: entry.resultSubtitle ?? void 0,
872
+ icon: entry.resultIcon ?? void 0,
873
+ badge: entry.presenter?.badge ?? entry.resultBadge ?? void 0
874
+ };
875
+ const links = entry.links ?? (entry.primaryLinkHref ? [{ href: entry.primaryLinkHref, label: entry.primaryLinkLabel ?? entry.resultTitle, kind: "primary" }] : null);
876
+ const url = entry.url ?? entry.primaryLinkHref ?? null;
877
+ const metadata = entry.metadata ?? (entry.resultSnapshot ? { snapshot: entry.resultSnapshot } : null);
878
+ return {
879
+ ...entry,
880
+ driverId,
881
+ presenter,
882
+ links,
883
+ url,
884
+ metadata,
885
+ score: entry.score ?? null
886
+ };
887
+ });
888
+ }
889
+ async search(request) {
890
+ const driverId = request.driverId ?? this.defaultDriverId;
891
+ const driver = this.getDriver(driverId);
892
+ await driver.ensureReady();
893
+ if (!this.opts.embeddingService.available) {
894
+ throw new Error("[vector] Embedding service unavailable (missing OPENAI_API_KEY)");
895
+ }
896
+ const embedding = await this.opts.embeddingService.createEmbedding(request.query);
897
+ const hits = await driver.query({
898
+ vector: embedding,
899
+ limit: request.limit ?? 10,
900
+ filter: {
901
+ tenantId: request.tenantId,
902
+ organizationId: request.organizationId ?? null,
903
+ entityIds: void 0
904
+ }
905
+ });
906
+ if (!hits.length) return [];
907
+ const decrypted = await Promise.all(
908
+ hits.map(async (hit) => {
909
+ const decryptedResult = await this.decryptResultFields({
910
+ tenantId: request.tenantId,
911
+ organizationId: hit.organizationId ?? request.organizationId ?? null,
912
+ resultTitle: hit.resultTitle,
913
+ resultSubtitle: hit.resultSubtitle ?? null,
914
+ resultIcon: hit.resultIcon ?? null,
915
+ resultSnapshot: hit.resultSnapshot ?? null,
916
+ primaryLinkHref: hit.primaryLinkHref ?? null,
917
+ primaryLinkLabel: hit.primaryLinkLabel ?? null,
918
+ links: hit.links ?? null,
919
+ payload: hit.payload ?? null
920
+ });
921
+ return {
922
+ ...hit,
923
+ resultTitle: decryptedResult.resultTitle,
924
+ resultSubtitle: decryptedResult.resultSubtitle,
925
+ resultIcon: decryptedResult.resultIcon,
926
+ resultSnapshot: decryptedResult.resultSnapshot,
927
+ primaryLinkHref: decryptedResult.primaryLinkHref,
928
+ primaryLinkLabel: decryptedResult.primaryLinkLabel,
929
+ links: decryptedResult.links,
930
+ payload: decryptedResult.payload
931
+ };
932
+ })
933
+ );
934
+ return decrypted.map((hit) => {
935
+ const presenter = {
936
+ title: hit.resultTitle,
937
+ subtitle: hit.resultSubtitle ?? void 0,
938
+ icon: hit.resultIcon ?? void 0,
939
+ badge: hit.presenter?.badge ?? hit.resultBadge ?? void 0
940
+ };
941
+ const links = hit.links ?? (hit.primaryLinkHref ? [{ href: hit.primaryLinkHref, label: hit.primaryLinkLabel ?? hit.resultTitle, kind: "primary" }] : null);
942
+ const url = hit.url ?? hit.primaryLinkHref ?? null;
943
+ const metadata = hit.payload ?? (hit.resultSnapshot ? { snapshot: hit.resultSnapshot } : null);
944
+ return {
945
+ entityId: hit.entityId,
946
+ recordId: hit.recordId,
947
+ score: hit.score,
948
+ url,
949
+ presenter,
950
+ links,
951
+ driverId,
952
+ metadata
953
+ };
954
+ });
955
+ }
956
+ }
957
+ export {
958
+ VectorIndexService
959
+ };
960
+ //# sourceMappingURL=vector-index.service.js.map