@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,131 @@
1
+ import { EMBEDDING_CONFIG_KEY, EMBEDDING_PROVIDERS, DEFAULT_EMBEDDING_CONFIG } from "../../../vector/index.js";
2
+ function detectConfigChange(previousConfig, newConfig, indexedDimension) {
3
+ const newDimension = newConfig.outputDimensionality ?? newConfig.dimension;
4
+ if (!previousConfig) {
5
+ if (indexedDimension !== null && indexedDimension !== void 0 && indexedDimension !== newDimension) {
6
+ return {
7
+ previousConfig,
8
+ newConfig,
9
+ requiresReindex: true,
10
+ reason: `Indexed dimension (${indexedDimension}) differs from new config (${newDimension})`
11
+ };
12
+ }
13
+ return {
14
+ previousConfig,
15
+ newConfig,
16
+ requiresReindex: false,
17
+ reason: null
18
+ };
19
+ }
20
+ const previousDimension = previousConfig.outputDimensionality ?? previousConfig.dimension;
21
+ if (previousConfig.providerId !== newConfig.providerId) {
22
+ return {
23
+ previousConfig,
24
+ newConfig,
25
+ requiresReindex: true,
26
+ reason: `Provider changed from ${EMBEDDING_PROVIDERS[previousConfig.providerId].name} to ${EMBEDDING_PROVIDERS[newConfig.providerId].name}`
27
+ };
28
+ }
29
+ if (previousConfig.model !== newConfig.model) {
30
+ return {
31
+ previousConfig,
32
+ newConfig,
33
+ requiresReindex: true,
34
+ reason: `Model changed from ${previousConfig.model} to ${newConfig.model}`
35
+ };
36
+ }
37
+ if (previousDimension !== newDimension) {
38
+ return {
39
+ previousConfig,
40
+ newConfig,
41
+ requiresReindex: true,
42
+ reason: `Dimension changed from ${previousDimension} to ${newDimension}`
43
+ };
44
+ }
45
+ if (indexedDimension !== null && indexedDimension !== void 0 && indexedDimension !== newDimension) {
46
+ return {
47
+ previousConfig,
48
+ newConfig,
49
+ requiresReindex: true,
50
+ reason: `Indexed dimension (${indexedDimension}) differs from config (${newDimension})`
51
+ };
52
+ }
53
+ return {
54
+ previousConfig,
55
+ newConfig,
56
+ requiresReindex: false,
57
+ reason: null
58
+ };
59
+ }
60
+ function isProviderConfigured(providerId) {
61
+ switch (providerId) {
62
+ case "openai":
63
+ return Boolean(process.env.OPENAI_API_KEY?.trim());
64
+ case "google":
65
+ return Boolean(process.env.GOOGLE_GENERATIVE_AI_API_KEY?.trim());
66
+ case "mistral":
67
+ return Boolean(process.env.MISTRAL_API_KEY?.trim());
68
+ case "cohere":
69
+ return Boolean(process.env.COHERE_API_KEY?.trim());
70
+ case "bedrock":
71
+ return Boolean(process.env.AWS_ACCESS_KEY_ID?.trim() && process.env.AWS_SECRET_ACCESS_KEY?.trim());
72
+ case "ollama":
73
+ return true;
74
+ default:
75
+ return false;
76
+ }
77
+ }
78
+ function getConfiguredProviders() {
79
+ const providers = [];
80
+ const allProviders = ["openai", "google", "mistral", "cohere", "bedrock", "ollama"];
81
+ for (const providerId of allProviders) {
82
+ if (isProviderConfigured(providerId)) {
83
+ providers.push(providerId);
84
+ }
85
+ }
86
+ return providers;
87
+ }
88
+ function getEffectiveDimension(config) {
89
+ return config.outputDimensionality ?? config.dimension;
90
+ }
91
+ async function resolveEmbeddingConfig(resolver, options) {
92
+ const fallback = options?.defaultValue ?? null;
93
+ let service;
94
+ try {
95
+ service = resolver.resolve("moduleConfigService");
96
+ } catch {
97
+ return fallback;
98
+ }
99
+ try {
100
+ const value = await service.getValue("vector", EMBEDDING_CONFIG_KEY, { defaultValue: fallback });
101
+ return value;
102
+ } catch {
103
+ return fallback;
104
+ }
105
+ }
106
+ async function saveEmbeddingConfig(resolver, config) {
107
+ let service;
108
+ try {
109
+ service = resolver.resolve("moduleConfigService");
110
+ } catch {
111
+ throw new Error("Configuration service unavailable");
112
+ }
113
+ await service.setValue("vector", EMBEDDING_CONFIG_KEY, {
114
+ ...config,
115
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString()
116
+ });
117
+ }
118
+ function createDefaultConfig() {
119
+ return { ...DEFAULT_EMBEDDING_CONFIG, updatedAt: (/* @__PURE__ */ new Date()).toISOString() };
120
+ }
121
+ export {
122
+ EMBEDDING_CONFIG_KEY,
123
+ createDefaultConfig,
124
+ detectConfigChange,
125
+ getConfiguredProviders,
126
+ getEffectiveDimension,
127
+ isProviderConfigured,
128
+ resolveEmbeddingConfig,
129
+ saveEmbeddingConfig
130
+ };
131
+ //# sourceMappingURL=embedding-config.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../../src/modules/search/lib/embedding-config.ts"],
4
+ "sourcesContent": ["import type { ModuleConfigService } from '@open-mercato/core/modules/configs/lib/module-config-service'\nimport type { EmbeddingProviderConfig, EmbeddingProviderId } from '../../../vector'\nimport { EMBEDDING_CONFIG_KEY, EMBEDDING_PROVIDERS, DEFAULT_EMBEDDING_CONFIG } from '../../../vector'\n\nexport { EMBEDDING_CONFIG_KEY }\n\nexport type EmbeddingConfigChange = {\n previousConfig: EmbeddingProviderConfig | null\n newConfig: EmbeddingProviderConfig\n requiresReindex: boolean\n reason: string | null\n}\n\nexport function detectConfigChange(\n previousConfig: EmbeddingProviderConfig | null,\n newConfig: EmbeddingProviderConfig,\n indexedDimension?: number | null\n): EmbeddingConfigChange {\n const newDimension = newConfig.outputDimensionality ?? newConfig.dimension\n\n if (!previousConfig) {\n // First time setup - check if indexed dimension differs from new config\n if (indexedDimension !== null && indexedDimension !== undefined && indexedDimension !== newDimension) {\n return {\n previousConfig,\n newConfig,\n requiresReindex: true,\n reason: `Indexed dimension (${indexedDimension}) differs from new config (${newDimension})`,\n }\n }\n return {\n previousConfig,\n newConfig,\n requiresReindex: false,\n reason: null,\n }\n }\n\n const previousDimension = previousConfig.outputDimensionality ?? previousConfig.dimension\n\n if (previousConfig.providerId !== newConfig.providerId) {\n return {\n previousConfig,\n newConfig,\n requiresReindex: true,\n reason: `Provider changed from ${EMBEDDING_PROVIDERS[previousConfig.providerId].name} to ${EMBEDDING_PROVIDERS[newConfig.providerId].name}`,\n }\n }\n\n if (previousConfig.model !== newConfig.model) {\n return {\n previousConfig,\n newConfig,\n requiresReindex: true,\n reason: `Model changed from ${previousConfig.model} to ${newConfig.model}`,\n }\n }\n\n if (previousDimension !== newDimension) {\n return {\n previousConfig,\n newConfig,\n requiresReindex: true,\n reason: `Dimension changed from ${previousDimension} to ${newDimension}`,\n }\n }\n\n // Also check if indexed dimension differs from config dimension\n if (indexedDimension !== null && indexedDimension !== undefined && indexedDimension !== newDimension) {\n return {\n previousConfig,\n newConfig,\n requiresReindex: true,\n reason: `Indexed dimension (${indexedDimension}) differs from config (${newDimension})`,\n }\n }\n\n return {\n previousConfig,\n newConfig,\n requiresReindex: false,\n reason: null,\n }\n}\n\nexport function isProviderConfigured(providerId: EmbeddingProviderId): boolean {\n switch (providerId) {\n case 'openai':\n return Boolean(process.env.OPENAI_API_KEY?.trim())\n case 'google':\n return Boolean(process.env.GOOGLE_GENERATIVE_AI_API_KEY?.trim())\n case 'mistral':\n return Boolean(process.env.MISTRAL_API_KEY?.trim())\n case 'cohere':\n return Boolean(process.env.COHERE_API_KEY?.trim())\n case 'bedrock':\n return Boolean(process.env.AWS_ACCESS_KEY_ID?.trim() && process.env.AWS_SECRET_ACCESS_KEY?.trim())\n case 'ollama':\n return true\n default:\n return false\n }\n}\n\nexport function getConfiguredProviders(): EmbeddingProviderId[] {\n const providers: EmbeddingProviderId[] = []\n const allProviders: EmbeddingProviderId[] = ['openai', 'google', 'mistral', 'cohere', 'bedrock', 'ollama']\n for (const providerId of allProviders) {\n if (isProviderConfigured(providerId)) {\n providers.push(providerId)\n }\n }\n return providers\n}\n\nexport function getEffectiveDimension(config: EmbeddingProviderConfig): number {\n return config.outputDimensionality ?? config.dimension\n}\n\ntype Resolver = {\n resolve: <T = unknown>(name: string) => T\n}\n\nexport async function resolveEmbeddingConfig(\n resolver: Resolver,\n options?: { defaultValue?: EmbeddingProviderConfig | null }\n): Promise<EmbeddingProviderConfig | null> {\n const fallback = options?.defaultValue ?? null\n let service: ModuleConfigService\n try {\n service = resolver.resolve<ModuleConfigService>('moduleConfigService')\n } catch {\n return fallback\n }\n try {\n const value = await service.getValue<EmbeddingProviderConfig>('vector', EMBEDDING_CONFIG_KEY, { defaultValue: fallback })\n return value\n } catch {\n return fallback\n }\n}\n\nexport async function saveEmbeddingConfig(\n resolver: Resolver,\n config: EmbeddingProviderConfig\n): Promise<void> {\n let service: ModuleConfigService\n try {\n service = resolver.resolve<ModuleConfigService>('moduleConfigService')\n } catch {\n throw new Error('Configuration service unavailable')\n }\n await service.setValue('vector', EMBEDDING_CONFIG_KEY, {\n ...config,\n updatedAt: new Date().toISOString(),\n })\n}\n\nexport function createDefaultConfig(): EmbeddingProviderConfig {\n return { ...DEFAULT_EMBEDDING_CONFIG, updatedAt: new Date().toISOString() }\n}\n"],
5
+ "mappings": "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",
6
+ "names": []
7
+ }
@@ -0,0 +1,45 @@
1
+ const GLOBAL_SEARCH_STRATEGIES_KEY = "global_search_strategies";
2
+ const DEFAULT_GLOBAL_SEARCH_STRATEGIES = ["fulltext", "vector", "tokens"];
3
+ async function resolveGlobalSearchStrategies(resolver, options) {
4
+ const fallback = options?.defaultValue ?? DEFAULT_GLOBAL_SEARCH_STRATEGIES;
5
+ let service;
6
+ try {
7
+ service = resolver.resolve("moduleConfigService");
8
+ } catch {
9
+ return fallback;
10
+ }
11
+ try {
12
+ const value = await service.getValue("search", GLOBAL_SEARCH_STRATEGIES_KEY, { defaultValue: fallback });
13
+ if (!Array.isArray(value) || value.length === 0) {
14
+ return fallback;
15
+ }
16
+ return value;
17
+ } catch {
18
+ return fallback;
19
+ }
20
+ }
21
+ async function saveGlobalSearchStrategies(resolver, strategies) {
22
+ let service;
23
+ try {
24
+ service = resolver.resolve("moduleConfigService");
25
+ } catch {
26
+ throw new Error("Module config service not available");
27
+ }
28
+ if (!Array.isArray(strategies) || strategies.length === 0) {
29
+ throw new Error("At least one search strategy must be enabled");
30
+ }
31
+ const validStrategies = strategies.filter(
32
+ (s) => ["fulltext", "vector", "tokens"].includes(s)
33
+ );
34
+ if (validStrategies.length === 0) {
35
+ throw new Error("At least one valid search strategy must be enabled");
36
+ }
37
+ await service.setValue("search", GLOBAL_SEARCH_STRATEGIES_KEY, validStrategies);
38
+ }
39
+ export {
40
+ DEFAULT_GLOBAL_SEARCH_STRATEGIES,
41
+ GLOBAL_SEARCH_STRATEGIES_KEY,
42
+ resolveGlobalSearchStrategies,
43
+ saveGlobalSearchStrategies
44
+ };
45
+ //# sourceMappingURL=global-search-config.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../../src/modules/search/lib/global-search-config.ts"],
4
+ "sourcesContent": ["import type { ModuleConfigService } from '@open-mercato/core/modules/configs/lib/module-config-service'\nimport type { SearchStrategyId } from '@open-mercato/shared/modules/search'\n\nexport const GLOBAL_SEARCH_STRATEGIES_KEY = 'global_search_strategies'\n\n/** Default strategies when none are configured */\nexport const DEFAULT_GLOBAL_SEARCH_STRATEGIES: SearchStrategyId[] = ['fulltext', 'vector', 'tokens']\n\ntype Resolver = {\n resolve: <T = unknown>(name: string) => T\n}\n\n/**\n * Get the enabled strategies for global search (Cmd+K).\n * Falls back to all strategies if not configured.\n */\nexport async function resolveGlobalSearchStrategies(\n resolver: Resolver,\n options?: { defaultValue?: SearchStrategyId[] },\n): Promise<SearchStrategyId[]> {\n const fallback = options?.defaultValue ?? DEFAULT_GLOBAL_SEARCH_STRATEGIES\n let service: ModuleConfigService\n try {\n service = resolver.resolve<ModuleConfigService>('moduleConfigService')\n } catch {\n return fallback\n }\n try {\n const value = await service.getValue<SearchStrategyId[]>('search', GLOBAL_SEARCH_STRATEGIES_KEY, { defaultValue: fallback })\n // Ensure we always return a non-empty array\n if (!Array.isArray(value) || value.length === 0) {\n return fallback\n }\n return value\n } catch {\n return fallback\n }\n}\n\n/**\n * Save the enabled strategies for global search.\n */\nexport async function saveGlobalSearchStrategies(\n resolver: Resolver,\n strategies: SearchStrategyId[],\n): Promise<void> {\n let service: ModuleConfigService\n try {\n service = resolver.resolve<ModuleConfigService>('moduleConfigService')\n } catch {\n throw new Error('Module config service not available')\n }\n\n // Validate that at least one strategy is enabled\n if (!Array.isArray(strategies) || strategies.length === 0) {\n throw new Error('At least one search strategy must be enabled')\n }\n\n // Filter to only valid strategy IDs\n const validStrategies = strategies.filter(\n (s) => ['fulltext', 'vector', 'tokens'].includes(s)\n ) as SearchStrategyId[]\n\n if (validStrategies.length === 0) {\n throw new Error('At least one valid search strategy must be enabled')\n }\n\n await service.setValue('search', GLOBAL_SEARCH_STRATEGIES_KEY, validStrategies)\n}\n"],
5
+ "mappings": "AAGO,MAAM,+BAA+B;AAGrC,MAAM,mCAAuD,CAAC,YAAY,UAAU,QAAQ;AAUnG,eAAsB,8BACpB,UACA,SAC6B;AAC7B,QAAM,WAAW,SAAS,gBAAgB;AAC1C,MAAI;AACJ,MAAI;AACF,cAAU,SAAS,QAA6B,qBAAqB;AAAA,EACvE,QAAQ;AACN,WAAO;AAAA,EACT;AACA,MAAI;AACF,UAAM,QAAQ,MAAM,QAAQ,SAA6B,UAAU,8BAA8B,EAAE,cAAc,SAAS,CAAC;AAE3H,QAAI,CAAC,MAAM,QAAQ,KAAK,KAAK,MAAM,WAAW,GAAG;AAC/C,aAAO;AAAA,IACT;AACA,WAAO;AAAA,EACT,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAKA,eAAsB,2BACpB,UACA,YACe;AACf,MAAI;AACJ,MAAI;AACF,cAAU,SAAS,QAA6B,qBAAqB;AAAA,EACvE,QAAQ;AACN,UAAM,IAAI,MAAM,qCAAqC;AAAA,EACvD;AAGA,MAAI,CAAC,MAAM,QAAQ,UAAU,KAAK,WAAW,WAAW,GAAG;AACzD,UAAM,IAAI,MAAM,8CAA8C;AAAA,EAChE;AAGA,QAAM,kBAAkB,WAAW;AAAA,IACjC,CAAC,MAAM,CAAC,YAAY,UAAU,QAAQ,EAAE,SAAS,CAAC;AAAA,EACpD;AAEA,MAAI,gBAAgB,WAAW,GAAG;AAChC,UAAM,IAAI,MAAM,oDAAoD;AAAA,EACtE;AAEA,QAAM,QAAQ,SAAS,UAAU,8BAA8B,eAAe;AAChF;",
6
+ "names": []
7
+ }
@@ -0,0 +1,99 @@
1
+ import {
2
+ prepareJob,
3
+ finalizeJob
4
+ } from "@open-mercato/core/modules/query_index/lib/jobs";
5
+ const REINDEX_LOCK_KEY = "reindex_lock";
6
+ const LOCK_ENTITY_TYPES = {
7
+ fulltext: "search:reindex:fulltext",
8
+ vector: "search:reindex:vector"
9
+ };
10
+ const HEARTBEAT_STALE_MS = 30 * 1e3;
11
+ function buildScope(type, tenantId, organizationId) {
12
+ return {
13
+ entityType: LOCK_ENTITY_TYPES[type],
14
+ tenantId,
15
+ organizationId: organizationId ?? null,
16
+ partitionIndex: null,
17
+ partitionCount: null
18
+ };
19
+ }
20
+ async function getReindexLockStatus(knex, tenantId, options) {
21
+ const typesToCheck = options?.type ? [options.type] : ["fulltext", "vector"];
22
+ for (const lockType of typesToCheck) {
23
+ const entityType = LOCK_ENTITY_TYPES[lockType];
24
+ try {
25
+ const job = await knex("entity_index_jobs").where("entity_type", entityType).whereRaw("tenant_id is not distinct from ?", [tenantId]).whereNull("finished_at").first();
26
+ if (!job) continue;
27
+ const heartbeatAt = job.heartbeat_at ? new Date(job.heartbeat_at).getTime() : 0;
28
+ const elapsed = Date.now() - heartbeatAt;
29
+ if (elapsed > HEARTBEAT_STALE_MS) {
30
+ await knex("entity_index_jobs").where("id", job.id).update({ finished_at: knex.fn.now() });
31
+ continue;
32
+ }
33
+ const startedAtStr = job.started_at ? typeof job.started_at === "string" ? job.started_at : new Date(job.started_at).toISOString() : (/* @__PURE__ */ new Date()).toISOString();
34
+ const result = {
35
+ type: lockType,
36
+ action: job.status || "reindexing",
37
+ startedAt: startedAtStr,
38
+ tenantId,
39
+ organizationId: job.organization_id,
40
+ processedCount: job.processed_count,
41
+ totalCount: job.total_count
42
+ };
43
+ return result;
44
+ } catch {
45
+ continue;
46
+ }
47
+ }
48
+ return null;
49
+ }
50
+ async function acquireReindexLock(knex, options) {
51
+ const existing = await getReindexLockStatus(knex, options.tenantId, {
52
+ type: options.type
53
+ });
54
+ if (existing) {
55
+ return { acquired: false };
56
+ }
57
+ try {
58
+ const scope = buildScope(
59
+ options.type,
60
+ options.tenantId,
61
+ options.organizationId
62
+ );
63
+ const jobId = await prepareJob(knex, scope, "reindexing", {
64
+ totalCount: options.totalCount
65
+ });
66
+ return { acquired: true, jobId: jobId ?? void 0 };
67
+ } catch {
68
+ return { acquired: false };
69
+ }
70
+ }
71
+ async function clearReindexLock(knex, tenantId, type, organizationId) {
72
+ try {
73
+ const scope = buildScope(type, tenantId, organizationId);
74
+ await finalizeJob(knex, scope);
75
+ } catch {
76
+ }
77
+ }
78
+ async function updateReindexProgress(knex, tenantId, type, processedDelta, organizationId) {
79
+ try {
80
+ const scope = buildScope(type, tenantId, organizationId);
81
+ const entityType = LOCK_ENTITY_TYPES[type];
82
+ const updated = await knex("entity_index_jobs").where("entity_type", entityType).whereRaw("tenant_id is not distinct from ?", [tenantId]).whereRaw("organization_id is not distinct from ?", [organizationId ?? null]).whereNull("finished_at").update({
83
+ processed_count: knex.raw("coalesce(processed_count, 0) + ?", [Math.max(0, processedDelta)]),
84
+ heartbeat_at: knex.fn.now()
85
+ });
86
+ if (updated === 0) {
87
+ await prepareJob(knex, scope, "reindexing");
88
+ }
89
+ } catch {
90
+ }
91
+ }
92
+ export {
93
+ REINDEX_LOCK_KEY,
94
+ acquireReindexLock,
95
+ clearReindexLock,
96
+ getReindexLockStatus,
97
+ updateReindexProgress
98
+ };
99
+ //# sourceMappingURL=reindex-lock.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../../src/modules/search/lib/reindex-lock.ts"],
4
+ "sourcesContent": ["import type { Knex } from 'knex'\nimport {\n prepareJob,\n updateJobProgress,\n finalizeJob,\n type JobScope,\n} from '@open-mercato/core/modules/query_index/lib/jobs'\n\nexport const REINDEX_LOCK_KEY = 'reindex_lock'\n\nexport type ReindexLockType = 'fulltext' | 'vector'\n\n// Entity type mapping for search reindex jobs\nconst LOCK_ENTITY_TYPES: Record<ReindexLockType, string> = {\n fulltext: 'search:reindex:fulltext',\n vector: 'search:reindex:vector',\n}\n\n// Heartbeat staleness threshold (30 seconds)\nconst HEARTBEAT_STALE_MS = 30 * 1000\n\nexport type ReindexLockStatus = {\n type: ReindexLockType\n action: string\n startedAt: string\n tenantId: string\n organizationId?: string | null\n processedCount?: number | null\n totalCount?: number | null\n}\n\nfunction buildScope(\n type: ReindexLockType,\n tenantId: string,\n organizationId?: string | null,\n): JobScope {\n return {\n entityType: LOCK_ENTITY_TYPES[type],\n tenantId,\n organizationId: organizationId ?? null,\n partitionIndex: null,\n partitionCount: null,\n }\n}\n\n/**\n * Check if a reindex operation is currently in progress for a specific type.\n * Returns the lock status if active, null if no lock or lock is stale.\n *\n * Automatically cleans up stale locks (heartbeat older than 60 seconds).\n */\nexport async function getReindexLockStatus(\n knex: Knex,\n tenantId: string,\n options?: { type?: ReindexLockType },\n): Promise<ReindexLockStatus | null> {\n const typesToCheck: ReindexLockType[] = options?.type\n ? [options.type]\n : ['fulltext', 'vector']\n\n for (const lockType of typesToCheck) {\n const entityType = LOCK_ENTITY_TYPES[lockType]\n\n try {\n const job = await knex('entity_index_jobs')\n .where('entity_type', entityType)\n .whereRaw('tenant_id is not distinct from ?', [tenantId])\n .whereNull('finished_at')\n .first()\n\n if (!job) continue\n\n // Check heartbeat staleness\n const heartbeatAt = job.heartbeat_at\n ? new Date(job.heartbeat_at).getTime()\n : 0\n const elapsed = Date.now() - heartbeatAt\n\n if (elapsed > HEARTBEAT_STALE_MS) {\n // Auto-cleanup stale lock\n await knex('entity_index_jobs')\n .where('id', job.id)\n .update({ finished_at: knex.fn.now() })\n continue\n }\n\n // started_at comes as string from knex, convert if needed\n const startedAtStr = job.started_at\n ? (typeof job.started_at === 'string' ? job.started_at : new Date(job.started_at).toISOString())\n : new Date().toISOString()\n\n const result = {\n type: lockType,\n action: job.status || 'reindexing',\n startedAt: startedAtStr,\n tenantId,\n organizationId: job.organization_id,\n processedCount: job.processed_count,\n totalCount: job.total_count,\n }\n return result\n } catch {\n continue\n }\n }\n\n return null\n}\n\n/**\n * Acquire a reindex lock for a specific type. Returns whether lock was acquired.\n * Fulltext and vector locks are independent - they don't block each other.\n */\nexport async function acquireReindexLock(\n knex: Knex,\n options: {\n type: ReindexLockType\n action: string\n tenantId: string\n organizationId?: string | null\n totalCount?: number | null\n },\n): Promise<{ acquired: boolean; jobId?: string }> {\n // Check existing active lock\n const existing = await getReindexLockStatus(knex, options.tenantId, {\n type: options.type,\n })\n if (existing) {\n return { acquired: false }\n }\n\n try {\n const scope = buildScope(\n options.type,\n options.tenantId,\n options.organizationId,\n )\n const jobId = await prepareJob(knex, scope, 'reindexing', {\n totalCount: options.totalCount,\n })\n\n return { acquired: true, jobId: jobId ?? undefined }\n } catch {\n return { acquired: false }\n }\n}\n\n/**\n * Release the reindex lock for a specific type.\n */\nexport async function clearReindexLock(\n knex: Knex,\n tenantId: string,\n type: ReindexLockType,\n organizationId?: string | null,\n): Promise<void> {\n try {\n const scope = buildScope(type, tenantId, organizationId)\n await finalizeJob(knex, scope)\n } catch {\n // Ignore errors when clearing lock\n }\n}\n\n/**\n * Update the reindex progress and refresh the heartbeat.\n * Call this periodically during batch processing to prevent stale lock detection.\n *\n * If no active lock exists (e.g., it expired after queue restart), this will\n * recreate the lock so the reindex button stays disabled while processing.\n */\nexport async function updateReindexProgress(\n knex: Knex,\n tenantId: string,\n type: ReindexLockType,\n processedDelta: number,\n organizationId?: string | null,\n): Promise<void> {\n try {\n const scope = buildScope(type, tenantId, organizationId)\n const entityType = LOCK_ENTITY_TYPES[type]\n\n // Try to update existing active job first\n const updated = await knex('entity_index_jobs')\n .where('entity_type', entityType)\n .whereRaw('tenant_id is not distinct from ?', [tenantId])\n .whereRaw('organization_id is not distinct from ?', [organizationId ?? null])\n .whereNull('finished_at')\n .update({\n processed_count: knex.raw('coalesce(processed_count, 0) + ?', [Math.max(0, processedDelta)]),\n heartbeat_at: knex.fn.now(),\n })\n\n // If no active lock exists, recreate it\n if (updated === 0) {\n await prepareJob(knex, scope, 'reindexing')\n }\n } catch {\n // Ignore errors when updating progress\n }\n}\n"],
5
+ "mappings": "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",
6
+ "names": []
7
+ }
@@ -0,0 +1,64 @@
1
+ import { resolveEntityTableName } from "@open-mercato/shared/lib/query/engine";
2
+ import { resolveAutoIndexingEnabled } from "../lib/auto-indexing.js";
3
+ import { searchDebugWarn, searchError } from "../../../lib/debug.js";
4
+ const metadata = { event: "search.index_record", persistent: false };
5
+ async function handle(payload, ctx) {
6
+ const entityType = String(payload?.entityId ?? "");
7
+ const recordId = String(payload?.recordId ?? "");
8
+ if (!entityType || !recordId) {
9
+ return;
10
+ }
11
+ let organizationId = payload?.organizationId ?? null;
12
+ let tenantId = payload?.tenantId ?? null;
13
+ let em = null;
14
+ try {
15
+ em = ctx.resolve("em");
16
+ } catch {
17
+ em = null;
18
+ }
19
+ if ((organizationId == null || tenantId == null) && em) {
20
+ try {
21
+ const knex = em.getConnection().getKnex();
22
+ const table = resolveEntityTableName(em, entityType);
23
+ const row = await knex(table).select(["organization_id", "tenant_id"]).where({ id: recordId }).first();
24
+ if (organizationId == null) organizationId = row?.organization_id ?? organizationId;
25
+ if (tenantId == null) tenantId = row?.tenant_id ?? tenantId;
26
+ } catch {
27
+ }
28
+ }
29
+ if (!tenantId) {
30
+ return;
31
+ }
32
+ const autoIndexingEnabled = await resolveAutoIndexingEnabled(ctx, { defaultValue: true });
33
+ if (!autoIndexingEnabled) {
34
+ return;
35
+ }
36
+ let queue;
37
+ try {
38
+ queue = ctx.resolve("fulltextIndexQueue");
39
+ } catch {
40
+ searchDebugWarn("search.fulltext", "fulltextIndexQueue not available, skipping fulltext indexing");
41
+ return;
42
+ }
43
+ try {
44
+ await queue.enqueue({
45
+ jobType: "index",
46
+ entityType,
47
+ recordId,
48
+ tenantId: String(tenantId),
49
+ organizationId: organizationId ? String(organizationId) : null
50
+ });
51
+ } catch (error) {
52
+ searchError("search.fulltext", "Failed to enqueue fulltext index job", {
53
+ entityType,
54
+ recordId,
55
+ error: error instanceof Error ? error.message : error
56
+ });
57
+ throw error;
58
+ }
59
+ }
60
+ export {
61
+ handle as default,
62
+ metadata
63
+ };
64
+ //# sourceMappingURL=fulltext_upsert.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../../src/modules/search/subscribers/fulltext_upsert.ts"],
4
+ "sourcesContent": ["import { resolveEntityTableName } from '@open-mercato/shared/lib/query/engine'\nimport type { Queue } from '@open-mercato/queue'\nimport type { FulltextIndexJobPayload } from '../../../queue/fulltext-indexing'\nimport { resolveAutoIndexingEnabled } from '../lib/auto-indexing'\nimport { searchDebugWarn, searchError } from '../../../lib/debug'\n\nexport const metadata = { event: 'search.index_record', persistent: false }\n\ntype Payload = {\n entityId?: string\n recordId?: string\n organizationId?: string | null\n tenantId?: string | null\n}\n\ntype HandlerContext = { resolve: <T = unknown>(name: string) => T }\n\nexport default async function handle(payload: Payload, ctx: HandlerContext) {\n const entityType = String(payload?.entityId ?? '')\n const recordId = String(payload?.recordId ?? '')\n\n if (!entityType || !recordId) {\n return\n }\n\n let organizationId = payload?.organizationId ?? null\n let tenantId = payload?.tenantId ?? null\n\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n let em: any | null = null\n try {\n em = ctx.resolve('em')\n } catch {\n em = null\n }\n\n // Resolve missing scope from DB if needed (same pattern as vector_upsert.ts)\n if ((organizationId == null || tenantId == null) && em) {\n try {\n const knex = em.getConnection().getKnex()\n const table = resolveEntityTableName(em, entityType)\n const row = await knex(table).select(['organization_id', 'tenant_id']).where({ id: recordId }).first()\n if (organizationId == null) organizationId = row?.organization_id ?? organizationId\n if (tenantId == null) tenantId = row?.tenant_id ?? tenantId\n } catch {\n // Ignore lookup errors\n }\n }\n\n if (!tenantId) {\n return\n }\n\n const autoIndexingEnabled = await resolveAutoIndexingEnabled(ctx, { defaultValue: true })\n if (!autoIndexingEnabled) {\n return\n }\n\n let queue: Queue<FulltextIndexJobPayload>\n try {\n queue = ctx.resolve<Queue<FulltextIndexJobPayload>>('fulltextIndexQueue')\n } catch {\n searchDebugWarn('search.fulltext', 'fulltextIndexQueue not available, skipping fulltext indexing')\n return\n }\n\n try {\n await queue.enqueue({\n jobType: 'index',\n entityType,\n recordId,\n tenantId: String(tenantId),\n organizationId: organizationId ? String(organizationId) : null,\n })\n } catch (error) {\n searchError('search.fulltext', 'Failed to enqueue fulltext index job', {\n entityType,\n recordId,\n error: error instanceof Error ? error.message : error,\n })\n throw error\n }\n}\n"],
5
+ "mappings": "AAAA,SAAS,8BAA8B;AAGvC,SAAS,kCAAkC;AAC3C,SAAS,iBAAiB,mBAAmB;AAEtC,MAAM,WAAW,EAAE,OAAO,uBAAuB,YAAY,MAAM;AAW1E,eAAO,OAA8B,SAAkB,KAAqB;AAC1E,QAAM,aAAa,OAAO,SAAS,YAAY,EAAE;AACjD,QAAM,WAAW,OAAO,SAAS,YAAY,EAAE;AAE/C,MAAI,CAAC,cAAc,CAAC,UAAU;AAC5B;AAAA,EACF;AAEA,MAAI,iBAAiB,SAAS,kBAAkB;AAChD,MAAI,WAAW,SAAS,YAAY;AAGpC,MAAI,KAAiB;AACrB,MAAI;AACF,SAAK,IAAI,QAAQ,IAAI;AAAA,EACvB,QAAQ;AACN,SAAK;AAAA,EACP;AAGA,OAAK,kBAAkB,QAAQ,YAAY,SAAS,IAAI;AACtD,QAAI;AACF,YAAM,OAAO,GAAG,cAAc,EAAE,QAAQ;AACxC,YAAM,QAAQ,uBAAuB,IAAI,UAAU;AACnD,YAAM,MAAM,MAAM,KAAK,KAAK,EAAE,OAAO,CAAC,mBAAmB,WAAW,CAAC,EAAE,MAAM,EAAE,IAAI,SAAS,CAAC,EAAE,MAAM;AACrG,UAAI,kBAAkB,KAAM,kBAAiB,KAAK,mBAAmB;AACrE,UAAI,YAAY,KAAM,YAAW,KAAK,aAAa;AAAA,IACrD,QAAQ;AAAA,IAER;AAAA,EACF;AAEA,MAAI,CAAC,UAAU;AACb;AAAA,EACF;AAEA,QAAM,sBAAsB,MAAM,2BAA2B,KAAK,EAAE,cAAc,KAAK,CAAC;AACxF,MAAI,CAAC,qBAAqB;AACxB;AAAA,EACF;AAEA,MAAI;AACJ,MAAI;AACF,YAAQ,IAAI,QAAwC,oBAAoB;AAAA,EAC1E,QAAQ;AACN,oBAAgB,mBAAmB,8DAA8D;AACjG;AAAA,EACF;AAEA,MAAI;AACF,UAAM,MAAM,QAAQ;AAAA,MAClB,SAAS;AAAA,MACT;AAAA,MACA;AAAA,MACA,UAAU,OAAO,QAAQ;AAAA,MACzB,gBAAgB,iBAAiB,OAAO,cAAc,IAAI;AAAA,IAC5D,CAAC;AAAA,EACH,SAAS,OAAO;AACd,gBAAY,mBAAmB,wCAAwC;AAAA,MACrE;AAAA,MACA;AAAA,MACA,OAAO,iBAAiB,QAAQ,MAAM,UAAU;AAAA,IAClD,CAAC;AACD,UAAM;AAAA,EACR;AACF;",
6
+ "names": []
7
+ }
@@ -0,0 +1,58 @@
1
+ import { resolveEntityTableName } from "@open-mercato/shared/lib/query/engine";
2
+ import { resolveAutoIndexingEnabled } from "../lib/auto-indexing.js";
3
+ import { searchDebugWarn, searchError } from "../../../lib/debug.js";
4
+ const metadata = { event: "query_index.delete_one", persistent: false };
5
+ async function handle(payload, ctx) {
6
+ const entityType = String(payload?.entityType ?? "");
7
+ const recordId = String(payload?.recordId ?? "");
8
+ if (!entityType || !recordId) return;
9
+ let organizationId = payload?.organizationId ?? null;
10
+ let tenantId = payload?.tenantId ?? null;
11
+ let em = null;
12
+ try {
13
+ em = ctx.resolve("em");
14
+ } catch {
15
+ em = null;
16
+ }
17
+ if ((organizationId == null || tenantId == null) && em) {
18
+ try {
19
+ const knex = em.getConnection().getKnex();
20
+ const table = resolveEntityTableName(em, entityType);
21
+ const row = await knex(table).select(["organization_id", "tenant_id"]).where({ id: recordId }).first();
22
+ if (organizationId == null) organizationId = row?.organization_id ?? organizationId;
23
+ if (tenantId == null) tenantId = row?.tenant_id ?? tenantId;
24
+ } catch {
25
+ }
26
+ }
27
+ if (!tenantId) return;
28
+ const autoIndexingEnabled = await resolveAutoIndexingEnabled(ctx, { defaultValue: true });
29
+ if (!autoIndexingEnabled) return;
30
+ let queue;
31
+ try {
32
+ queue = ctx.resolve("vectorIndexQueue");
33
+ } catch {
34
+ searchDebugWarn("search.vector", "vectorIndexQueue not available, skipping vector delete");
35
+ return;
36
+ }
37
+ try {
38
+ await queue.enqueue({
39
+ jobType: "delete",
40
+ entityType,
41
+ recordId,
42
+ tenantId: String(tenantId),
43
+ organizationId: organizationId ? String(organizationId) : null
44
+ });
45
+ } catch (error) {
46
+ searchError("search.vector", "Failed to enqueue vector delete job", {
47
+ entityType,
48
+ recordId,
49
+ error: error instanceof Error ? error.message : error
50
+ });
51
+ throw error;
52
+ }
53
+ }
54
+ export {
55
+ handle as default,
56
+ metadata
57
+ };
58
+ //# sourceMappingURL=vector_delete.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../../src/modules/search/subscribers/vector_delete.ts"],
4
+ "sourcesContent": ["import { resolveEntityTableName } from '@open-mercato/shared/lib/query/engine'\nimport type { Queue } from '@open-mercato/queue'\nimport type { VectorIndexJobPayload } from '../../../queue/vector-indexing'\nimport { resolveAutoIndexingEnabled } from '../lib/auto-indexing'\nimport { searchDebugWarn, searchError } from '../../../lib/debug'\n\nexport const metadata = { event: 'query_index.delete_one', persistent: false }\n\ntype Payload = {\n entityType?: string\n recordId?: string\n organizationId?: string | null\n tenantId?: string | null\n}\n\ntype HandlerContext = { resolve: <T = unknown>(name: string) => T }\n\nexport default async function handle(payload: Payload, ctx: HandlerContext) {\n const entityType = String(payload?.entityType ?? '')\n const recordId = String(payload?.recordId ?? '')\n if (!entityType || !recordId) return\n\n let organizationId = payload?.organizationId ?? null\n let tenantId = payload?.tenantId ?? null\n\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n let em: any | null = null\n try {\n em = ctx.resolve('em')\n } catch {\n em = null\n }\n\n if ((organizationId == null || tenantId == null) && em) {\n try {\n const knex = em.getConnection().getKnex()\n const table = resolveEntityTableName(em, entityType)\n const row = await knex(table).select(['organization_id', 'tenant_id']).where({ id: recordId }).first()\n if (organizationId == null) organizationId = row?.organization_id ?? organizationId\n if (tenantId == null) tenantId = row?.tenant_id ?? tenantId\n } catch {\n // Ignore lookup errors\n }\n }\n\n if (!tenantId) return\n\n const autoIndexingEnabled = await resolveAutoIndexingEnabled(ctx, { defaultValue: true })\n if (!autoIndexingEnabled) return\n\n let queue: Queue<VectorIndexJobPayload>\n try {\n queue = ctx.resolve<Queue<VectorIndexJobPayload>>('vectorIndexQueue')\n } catch {\n searchDebugWarn('search.vector', 'vectorIndexQueue not available, skipping vector delete')\n return\n }\n\n try {\n await queue.enqueue({\n jobType: 'delete',\n entityType,\n recordId,\n tenantId: String(tenantId),\n organizationId: organizationId ? String(organizationId) : null,\n })\n } catch (error) {\n searchError('search.vector', 'Failed to enqueue vector delete job', {\n entityType,\n recordId,\n error: error instanceof Error ? error.message : error,\n })\n throw error // Propagate to caller so failure is visible\n }\n}\n"],
5
+ "mappings": "AAAA,SAAS,8BAA8B;AAGvC,SAAS,kCAAkC;AAC3C,SAAS,iBAAiB,mBAAmB;AAEtC,MAAM,WAAW,EAAE,OAAO,0BAA0B,YAAY,MAAM;AAW7E,eAAO,OAA8B,SAAkB,KAAqB;AAC1E,QAAM,aAAa,OAAO,SAAS,cAAc,EAAE;AACnD,QAAM,WAAW,OAAO,SAAS,YAAY,EAAE;AAC/C,MAAI,CAAC,cAAc,CAAC,SAAU;AAE9B,MAAI,iBAAiB,SAAS,kBAAkB;AAChD,MAAI,WAAW,SAAS,YAAY;AAGpC,MAAI,KAAiB;AACrB,MAAI;AACF,SAAK,IAAI,QAAQ,IAAI;AAAA,EACvB,QAAQ;AACN,SAAK;AAAA,EACP;AAEA,OAAK,kBAAkB,QAAQ,YAAY,SAAS,IAAI;AACtD,QAAI;AACF,YAAM,OAAO,GAAG,cAAc,EAAE,QAAQ;AACxC,YAAM,QAAQ,uBAAuB,IAAI,UAAU;AACnD,YAAM,MAAM,MAAM,KAAK,KAAK,EAAE,OAAO,CAAC,mBAAmB,WAAW,CAAC,EAAE,MAAM,EAAE,IAAI,SAAS,CAAC,EAAE,MAAM;AACrG,UAAI,kBAAkB,KAAM,kBAAiB,KAAK,mBAAmB;AACrE,UAAI,YAAY,KAAM,YAAW,KAAK,aAAa;AAAA,IACrD,QAAQ;AAAA,IAER;AAAA,EACF;AAEA,MAAI,CAAC,SAAU;AAEf,QAAM,sBAAsB,MAAM,2BAA2B,KAAK,EAAE,cAAc,KAAK,CAAC;AACxF,MAAI,CAAC,oBAAqB;AAE1B,MAAI;AACJ,MAAI;AACF,YAAQ,IAAI,QAAsC,kBAAkB;AAAA,EACtE,QAAQ;AACN,oBAAgB,iBAAiB,wDAAwD;AACzF;AAAA,EACF;AAEA,MAAI;AACF,UAAM,MAAM,QAAQ;AAAA,MAClB,SAAS;AAAA,MACT;AAAA,MACA;AAAA,MACA,UAAU,OAAO,QAAQ;AAAA,MACzB,gBAAgB,iBAAiB,OAAO,cAAc,IAAI;AAAA,IAC5D,CAAC;AAAA,EACH,SAAS,OAAO;AACd,gBAAY,iBAAiB,uCAAuC;AAAA,MAClE;AAAA,MACA;AAAA,MACA,OAAO,iBAAiB,QAAQ,MAAM,UAAU;AAAA,IAClD,CAAC;AACD,UAAM;AAAA,EACR;AACF;",
6
+ "names": []
7
+ }
@@ -0,0 +1,142 @@
1
+ import { recordIndexerError } from "@open-mercato/shared/lib/indexers/error-log";
2
+ import { recordIndexerLog } from "@open-mercato/shared/lib/indexers/status-log";
3
+ import { writeCoverageCounts } from "@open-mercato/core/modules/query_index/lib/coverage";
4
+ import { resolveEmbeddingConfig } from "../lib/embedding-config.js";
5
+ import { searchDebugWarn } from "../../../lib/debug.js";
6
+ const metadata = { event: "query_index.vectorize_purge", persistent: false };
7
+ async function handle(payload, ctx) {
8
+ const entityType = String(payload?.entityType ?? "");
9
+ if (!entityType) return;
10
+ const tenantIdRaw = payload?.tenantId;
11
+ if (tenantIdRaw == null || tenantIdRaw === "") {
12
+ searchDebugWarn("search.vector", "Skipping vector purge for reindex without tenant scope", { entityType });
13
+ return;
14
+ }
15
+ const tenantId = String(tenantIdRaw);
16
+ const organizationId = payload?.organizationId == null ? null : String(payload.organizationId);
17
+ let searchIndexer;
18
+ try {
19
+ searchIndexer = ctx.resolve("searchIndexer");
20
+ } catch {
21
+ return;
22
+ }
23
+ try {
24
+ const embeddingConfig = await resolveEmbeddingConfig(ctx, { defaultValue: null });
25
+ if (embeddingConfig) {
26
+ const embeddingService = ctx.resolve("vectorEmbeddingService");
27
+ embeddingService.updateConfig(embeddingConfig);
28
+ }
29
+ } catch {
30
+ }
31
+ let em = null;
32
+ try {
33
+ em = ctx.resolve("em");
34
+ } catch {
35
+ em = null;
36
+ }
37
+ let eventBus = null;
38
+ try {
39
+ eventBus = ctx.resolve("eventBus");
40
+ } catch {
41
+ eventBus = null;
42
+ }
43
+ const scopes = /* @__PURE__ */ new Set();
44
+ const registerScope = (org) => {
45
+ const key = org ?? "__null__";
46
+ if (!scopes.has(key)) scopes.add(key);
47
+ };
48
+ registerScope(null);
49
+ if (organizationId != null) registerScope(organizationId);
50
+ try {
51
+ await searchIndexer.purgeEntity({
52
+ entityId: entityType,
53
+ tenantId
54
+ });
55
+ if (em) {
56
+ try {
57
+ for (const scope of scopes) {
58
+ const orgValue = scope === "__null__" ? null : scope;
59
+ await writeCoverageCounts(
60
+ em,
61
+ {
62
+ entityType,
63
+ tenantId,
64
+ organizationId: orgValue,
65
+ withDeleted: false
66
+ },
67
+ { vectorCount: 0 }
68
+ );
69
+ }
70
+ } catch (coverageError) {
71
+ searchDebugWarn("search.vector", "Failed to reset vector coverage after purge", {
72
+ error: coverageError instanceof Error ? coverageError.message : coverageError
73
+ });
74
+ }
75
+ }
76
+ if (eventBus) {
77
+ await Promise.all(
78
+ Array.from(scopes).map((scope) => {
79
+ const orgValue = scope === "__null__" ? null : scope;
80
+ return eventBus.emitEvent(
81
+ "query_index.coverage.refresh",
82
+ {
83
+ entityType,
84
+ tenantId,
85
+ organizationId: orgValue,
86
+ delayMs: 0
87
+ }
88
+ ).catch(() => void 0);
89
+ })
90
+ );
91
+ }
92
+ await recordIndexerLog(
93
+ { em: em ?? void 0 },
94
+ {
95
+ source: "vector",
96
+ handler: "event:query_index.vectorize_purge",
97
+ message: `Vector purge completed for ${entityType}`,
98
+ entityType,
99
+ tenantId,
100
+ organizationId,
101
+ details: payload
102
+ }
103
+ ).catch(() => void 0);
104
+ } catch (error) {
105
+ searchDebugWarn("search.vector", "Failed to purge vector index scope", {
106
+ entityType,
107
+ tenantId,
108
+ organizationId,
109
+ error: error instanceof Error ? error.message : error
110
+ });
111
+ await recordIndexerLog(
112
+ { em: em ?? void 0 },
113
+ {
114
+ source: "vector",
115
+ handler: "event:query_index.vectorize_purge",
116
+ level: "warn",
117
+ message: `Vector purge failed for ${entityType}`,
118
+ entityType,
119
+ tenantId,
120
+ organizationId,
121
+ details: { error: error instanceof Error ? error.message : String(error), payload }
122
+ }
123
+ ).catch(() => void 0);
124
+ await recordIndexerError(
125
+ { em: em ?? void 0 },
126
+ {
127
+ source: "vector",
128
+ handler: "event:query_index.vectorize_purge",
129
+ error,
130
+ entityType,
131
+ tenantId,
132
+ organizationId,
133
+ payload
134
+ }
135
+ );
136
+ }
137
+ }
138
+ export {
139
+ handle as default,
140
+ metadata
141
+ };
142
+ //# sourceMappingURL=vector_purge.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../../src/modules/search/subscribers/vector_purge.ts"],
4
+ "sourcesContent": ["import { recordIndexerError } from '@open-mercato/shared/lib/indexers/error-log'\nimport { recordIndexerLog } from '@open-mercato/shared/lib/indexers/status-log'\nimport type { SearchIndexer } from '../../../indexer/search-indexer'\nimport type { EmbeddingService } from '../../../vector'\nimport { writeCoverageCounts } from '@open-mercato/core/modules/query_index/lib/coverage'\nimport { resolveEmbeddingConfig } from '../lib/embedding-config'\nimport type { EntityId } from '@open-mercato/shared/modules/entities'\nimport { searchDebugWarn } from '../../../lib/debug'\n\nexport const metadata = { event: 'query_index.vectorize_purge', persistent: false }\n\ntype Payload = {\n entityType?: string\n tenantId?: string | null\n organizationId?: string | null\n}\n\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\ntype HandlerContext = { resolve: <T = any>(name: string) => T }\n\nexport default async function handle(payload: Payload, ctx: HandlerContext) {\n const entityType = String(payload?.entityType ?? '')\n if (!entityType) return\n const tenantIdRaw = payload?.tenantId\n if (tenantIdRaw == null || tenantIdRaw === '') {\n searchDebugWarn('search.vector', 'Skipping vector purge for reindex without tenant scope', { entityType })\n return\n }\n const tenantId = String(tenantIdRaw)\n const organizationId = payload?.organizationId == null ? null : String(payload.organizationId)\n\n let searchIndexer: SearchIndexer\n try {\n searchIndexer = ctx.resolve<SearchIndexer>('searchIndexer')\n } catch {\n return\n }\n\n // Load saved embedding config for consistency (dimension info may be needed for table recreation)\n try {\n const embeddingConfig = await resolveEmbeddingConfig(ctx, { defaultValue: null })\n if (embeddingConfig) {\n const embeddingService = ctx.resolve<EmbeddingService>('vectorEmbeddingService')\n embeddingService.updateConfig(embeddingConfig)\n }\n } catch {\n // Purge operations don't require embedding, ignore config errors\n }\n\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n let em: any = null\n try {\n em = ctx.resolve('em')\n } catch {\n em = null\n }\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n let eventBus: { emitEvent(event: string, payload: any, options?: any): Promise<void> } | null = null\n try {\n eventBus = ctx.resolve('eventBus')\n } catch {\n eventBus = null\n }\n const scopes = new Set<string>()\n const registerScope = (org: string | null) => {\n const key = org ?? '__null__'\n if (!scopes.has(key)) scopes.add(key)\n }\n registerScope(null)\n if (organizationId != null) registerScope(organizationId)\n\n try {\n await searchIndexer.purgeEntity({\n entityId: entityType as EntityId,\n tenantId,\n })\n if (em) {\n try {\n for (const scope of scopes) {\n const orgValue = scope === '__null__' ? null : scope\n await writeCoverageCounts(\n em,\n {\n entityType,\n tenantId,\n organizationId: orgValue,\n withDeleted: false,\n },\n { vectorCount: 0 },\n )\n }\n } catch (coverageError) {\n searchDebugWarn('search.vector', 'Failed to reset vector coverage after purge', {\n error: coverageError instanceof Error ? coverageError.message : coverageError,\n })\n }\n }\n if (eventBus) {\n await Promise.all(\n Array.from(scopes).map((scope) => {\n const orgValue = scope === '__null__' ? null : scope\n return eventBus!\n .emitEvent(\n 'query_index.coverage.refresh',\n {\n entityType,\n tenantId,\n organizationId: orgValue,\n delayMs: 0,\n },\n )\n .catch(() => undefined)\n }),\n )\n }\n await recordIndexerLog(\n { em: em ?? undefined },\n {\n source: 'vector',\n handler: 'event:query_index.vectorize_purge',\n message: `Vector purge completed for ${entityType}`,\n entityType,\n tenantId,\n organizationId,\n details: payload,\n },\n ).catch(() => undefined)\n } catch (error) {\n searchDebugWarn('search.vector', 'Failed to purge vector index scope', {\n entityType,\n tenantId,\n organizationId,\n error: error instanceof Error ? error.message : error,\n })\n await recordIndexerLog(\n { em: em ?? undefined },\n {\n source: 'vector',\n handler: 'event:query_index.vectorize_purge',\n level: 'warn',\n message: `Vector purge failed for ${entityType}`,\n entityType,\n tenantId,\n organizationId,\n details: { error: error instanceof Error ? error.message : String(error), payload },\n },\n ).catch(() => undefined)\n await recordIndexerError(\n { em: em ?? undefined },\n {\n source: 'vector',\n handler: 'event:query_index.vectorize_purge',\n error,\n entityType,\n tenantId,\n organizationId,\n payload,\n },\n )\n }\n}\n"],
5
+ "mappings": "AAAA,SAAS,0BAA0B;AACnC,SAAS,wBAAwB;AAGjC,SAAS,2BAA2B;AACpC,SAAS,8BAA8B;AAEvC,SAAS,uBAAuB;AAEzB,MAAM,WAAW,EAAE,OAAO,+BAA+B,YAAY,MAAM;AAWlF,eAAO,OAA8B,SAAkB,KAAqB;AAC1E,QAAM,aAAa,OAAO,SAAS,cAAc,EAAE;AACnD,MAAI,CAAC,WAAY;AACjB,QAAM,cAAc,SAAS;AAC7B,MAAI,eAAe,QAAQ,gBAAgB,IAAI;AAC7C,oBAAgB,iBAAiB,0DAA0D,EAAE,WAAW,CAAC;AACzG;AAAA,EACF;AACA,QAAM,WAAW,OAAO,WAAW;AACnC,QAAM,iBAAiB,SAAS,kBAAkB,OAAO,OAAO,OAAO,QAAQ,cAAc;AAE7F,MAAI;AACJ,MAAI;AACF,oBAAgB,IAAI,QAAuB,eAAe;AAAA,EAC5D,QAAQ;AACN;AAAA,EACF;AAGA,MAAI;AACF,UAAM,kBAAkB,MAAM,uBAAuB,KAAK,EAAE,cAAc,KAAK,CAAC;AAChF,QAAI,iBAAiB;AACnB,YAAM,mBAAmB,IAAI,QAA0B,wBAAwB;AAC/E,uBAAiB,aAAa,eAAe;AAAA,IAC/C;AAAA,EACF,QAAQ;AAAA,EAER;AAGA,MAAI,KAAU;AACd,MAAI;AACF,SAAK,IAAI,QAAQ,IAAI;AAAA,EACvB,QAAQ;AACN,SAAK;AAAA,EACP;AAEA,MAAI,WAA4F;AAChG,MAAI;AACF,eAAW,IAAI,QAAQ,UAAU;AAAA,EACnC,QAAQ;AACN,eAAW;AAAA,EACb;AACA,QAAM,SAAS,oBAAI,IAAY;AAC/B,QAAM,gBAAgB,CAAC,QAAuB;AAC5C,UAAM,MAAM,OAAO;AACnB,QAAI,CAAC,OAAO,IAAI,GAAG,EAAG,QAAO,IAAI,GAAG;AAAA,EACtC;AACA,gBAAc,IAAI;AAClB,MAAI,kBAAkB,KAAM,eAAc,cAAc;AAExD,MAAI;AACF,UAAM,cAAc,YAAY;AAAA,MAC9B,UAAU;AAAA,MACV;AAAA,IACF,CAAC;AACD,QAAI,IAAI;AACN,UAAI;AACF,mBAAW,SAAS,QAAQ;AAC1B,gBAAM,WAAW,UAAU,aAAa,OAAO;AAC/C,gBAAM;AAAA,YACJ;AAAA,YACA;AAAA,cACE;AAAA,cACA;AAAA,cACA,gBAAgB;AAAA,cAChB,aAAa;AAAA,YACf;AAAA,YACA,EAAE,aAAa,EAAE;AAAA,UACnB;AAAA,QACF;AAAA,MACF,SAAS,eAAe;AACtB,wBAAgB,iBAAiB,+CAA+C;AAAA,UAC9E,OAAO,yBAAyB,QAAQ,cAAc,UAAU;AAAA,QAClE,CAAC;AAAA,MACH;AAAA,IACF;AACA,QAAI,UAAU;AACZ,YAAM,QAAQ;AAAA,QACZ,MAAM,KAAK,MAAM,EAAE,IAAI,CAAC,UAAU;AAChC,gBAAM,WAAW,UAAU,aAAa,OAAO;AAC/C,iBAAO,SACJ;AAAA,YACC;AAAA,YACA;AAAA,cACE;AAAA,cACA;AAAA,cACA,gBAAgB;AAAA,cAChB,SAAS;AAAA,YACX;AAAA,UACF,EACC,MAAM,MAAM,MAAS;AAAA,QAC1B,CAAC;AAAA,MACH;AAAA,IACF;AACA,UAAM;AAAA,MACJ,EAAE,IAAI,MAAM,OAAU;AAAA,MACtB;AAAA,QACE,QAAQ;AAAA,QACR,SAAS;AAAA,QACT,SAAS,8BAA8B,UAAU;AAAA,QACjD;AAAA,QACA;AAAA,QACA;AAAA,QACA,SAAS;AAAA,MACX;AAAA,IACF,EAAE,MAAM,MAAM,MAAS;AAAA,EACzB,SAAS,OAAO;AACd,oBAAgB,iBAAiB,sCAAsC;AAAA,MACrE;AAAA,MACA;AAAA,MACA;AAAA,MACA,OAAO,iBAAiB,QAAQ,MAAM,UAAU;AAAA,IAClD,CAAC;AACD,UAAM;AAAA,MACJ,EAAE,IAAI,MAAM,OAAU;AAAA,MACtB;AAAA,QACE,QAAQ;AAAA,QACR,SAAS;AAAA,QACT,OAAO;AAAA,QACP,SAAS,2BAA2B,UAAU;AAAA,QAC9C;AAAA,QACA;AAAA,QACA;AAAA,QACA,SAAS,EAAE,OAAO,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK,GAAG,QAAQ;AAAA,MACpF;AAAA,IACF,EAAE,MAAM,MAAM,MAAS;AACvB,UAAM;AAAA,MACJ,EAAE,IAAI,MAAM,OAAU;AAAA,MACtB;AAAA,QACE,QAAQ;AAAA,QACR,SAAS;AAAA,QACT;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,MACF;AAAA,IACF;AAAA,EACF;AACF;",
6
+ "names": []
7
+ }