@path58/n8n-mcp 0.1.0

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 (319) hide show
  1. package/AGENT_INSTALL.md +223 -0
  2. package/CHANGELOG.md +38 -0
  3. package/LICENSE +21 -0
  4. package/README.md +187 -0
  5. package/dist/autofix/suggestion-fixers/deprecated-node-fixer.js +465 -0
  6. package/dist/autofix/suggestion-fixers/deprecated-node-fixer.js.map +1 -0
  7. package/dist/autofix/suggestion-fixers/fixer-registry.js +495 -0
  8. package/dist/autofix/suggestion-fixers/fixer-registry.js.map +1 -0
  9. package/dist/autofix/suggestion-fixers/l1-structure-fixer.js +639 -0
  10. package/dist/autofix/suggestion-fixers/l1-structure-fixer.js.map +1 -0
  11. package/dist/autofix/suggestion-fixers/l4-connection-fixer.js +449 -0
  12. package/dist/autofix/suggestion-fixers/l4-connection-fixer.js.map +1 -0
  13. package/dist/autofix/suggestion-fixers/l5-parameter-fixer.js +575 -0
  14. package/dist/autofix/suggestion-fixers/l5-parameter-fixer.js.map +1 -0
  15. package/dist/autofix/suggestion-fixers/l5-typeversion-fixer.js +431 -0
  16. package/dist/autofix/suggestion-fixers/l5-typeversion-fixer.js.map +1 -0
  17. package/dist/autofix/suggestion-fixers/l5-webhook-path-fixer.js +356 -0
  18. package/dist/autofix/suggestion-fixers/l5-webhook-path-fixer.js.map +1 -0
  19. package/dist/autofix/suggestion-fixers/l6-ai-tool-variant-fixer.js +618 -0
  20. package/dist/autofix/suggestion-fixers/l6-ai-tool-variant-fixer.js.map +1 -0
  21. package/dist/autofix/suggestion-fixers/l6-pattern-fixer.js +1475 -0
  22. package/dist/autofix/suggestion-fixers/l6-pattern-fixer.js.map +1 -0
  23. package/dist/autofix/suggestion-fixers/llm-fixer.js +716 -0
  24. package/dist/autofix/suggestion-fixers/llm-fixer.js.map +1 -0
  25. package/dist/autofix/suggestion-fixers/missing-credential-fixer.js +336 -0
  26. package/dist/autofix/suggestion-fixers/missing-credential-fixer.js.map +1 -0
  27. package/dist/autofix/suggestion-fixers/types.js +29 -0
  28. package/dist/autofix/suggestion-fixers/types.js.map +1 -0
  29. package/dist/autofix/suggestion-fixers/typo-fixer.js +197 -0
  30. package/dist/autofix/suggestion-fixers/typo-fixer.js.map +1 -0
  31. package/dist/classification/certification-engine.js +208 -0
  32. package/dist/classification/certification-engine.js.map +1 -0
  33. package/dist/classification/feedback-collector.js +516 -0
  34. package/dist/classification/feedback-collector.js.map +1 -0
  35. package/dist/classification/l5-parameter-analyzer.js +670 -0
  36. package/dist/classification/l5-parameter-analyzer.js.map +1 -0
  37. package/dist/classification/l6-graph-analyzer.js +613 -0
  38. package/dist/classification/l6-graph-analyzer.js.map +1 -0
  39. package/dist/classification/severity-classifier.js +237 -0
  40. package/dist/classification/severity-classifier.js.map +1 -0
  41. package/dist/config/env.js +280 -0
  42. package/dist/config/env.js.map +1 -0
  43. package/dist/config/env.schema.js +234 -0
  44. package/dist/config/env.schema.js.map +1 -0
  45. package/dist/config/scraperEnv.js +55 -0
  46. package/dist/config/scraperEnv.js.map +1 -0
  47. package/dist/db/postgresClient.js +38 -0
  48. package/dist/db/postgresClient.js.map +1 -0
  49. package/dist/db/scraperDb.js +6 -0
  50. package/dist/db/scraperDb.js.map +1 -0
  51. package/dist/db/scraperPostgresClient.js +118 -0
  52. package/dist/db/scraperPostgresClient.js.map +1 -0
  53. package/dist/db/validationRepository.js +55 -0
  54. package/dist/db/validationRepository.js.map +1 -0
  55. package/dist/db/validatorPostgresClient.js +248 -0
  56. package/dist/db/validatorPostgresClient.js.map +1 -0
  57. package/dist/db/workflowInstanceMappingRepository.js +128 -0
  58. package/dist/db/workflowInstanceMappingRepository.js.map +1 -0
  59. package/dist/errors/AppError.js +156 -0
  60. package/dist/errors/AppError.js.map +1 -0
  61. package/dist/errors/index.js +7 -0
  62. package/dist/errors/index.js.map +1 -0
  63. package/dist/factory/error-to-problem-mappers.js +385 -0
  64. package/dist/factory/error-to-problem-mappers.js.map +1 -0
  65. package/dist/factory/gap-recorder.js +260 -0
  66. package/dist/factory/gap-recorder.js.map +1 -0
  67. package/dist/factory/problem-recorder.js +94 -0
  68. package/dist/factory/problem-recorder.js.map +1 -0
  69. package/dist/factory/warning-to-gap-mappers.js +493 -0
  70. package/dist/factory/warning-to-gap-mappers.js.map +1 -0
  71. package/dist/factory/workflow-normalizer.js +247 -0
  72. package/dist/factory/workflow-normalizer.js.map +1 -0
  73. package/dist/mcp/adapters/catalog.js +13 -0
  74. package/dist/mcp/adapters/catalog.js.map +1 -0
  75. package/dist/mcp/adapters/index.js +36 -0
  76. package/dist/mcp/adapters/index.js.map +1 -0
  77. package/dist/mcp/adapters/supabase-catalog.js +467 -0
  78. package/dist/mcp/adapters/supabase-catalog.js.map +1 -0
  79. package/dist/mcp/adapters/test-catalog-adapter.js +100 -0
  80. package/dist/mcp/adapters/test-catalog-adapter.js.map +1 -0
  81. package/dist/mcp/adapters/validation.js +258 -0
  82. package/dist/mcp/adapters/validation.js.map +1 -0
  83. package/dist/mcp/build-email-workflow.js +113 -0
  84. package/dist/mcp/build-email-workflow.js.map +1 -0
  85. package/dist/mcp/config.js +22 -0
  86. package/dist/mcp/config.js.map +1 -0
  87. package/dist/mcp/formatters/errors.js +217 -0
  88. package/dist/mcp/formatters/errors.js.map +1 -0
  89. package/dist/mcp/formatters/index.js +12 -0
  90. package/dist/mcp/formatters/index.js.map +1 -0
  91. package/dist/mcp/formatters/response.js +141 -0
  92. package/dist/mcp/formatters/response.js.map +1 -0
  93. package/dist/mcp/quick-test.js +33 -0
  94. package/dist/mcp/quick-test.js.map +1 -0
  95. package/dist/mcp/server.js +70 -0
  96. package/dist/mcp/server.js.map +1 -0
  97. package/dist/mcp/test-mcp-error.js +81 -0
  98. package/dist/mcp/test-mcp-error.js.map +1 -0
  99. package/dist/mcp/test-mcp.js +80 -0
  100. package/dist/mcp/test-mcp.js.map +1 -0
  101. package/dist/mcp/tools/fixes/expression-fixes.js +166 -0
  102. package/dist/mcp/tools/fixes/expression-fixes.js.map +1 -0
  103. package/dist/mcp/tools/fixes/flow-fixes.js +155 -0
  104. package/dist/mcp/tools/fixes/flow-fixes.js.map +1 -0
  105. package/dist/mcp/tools/fixes/index.js +91 -0
  106. package/dist/mcp/tools/fixes/index.js.map +1 -0
  107. package/dist/mcp/tools/fixes/node-fixes.js +233 -0
  108. package/dist/mcp/tools/fixes/node-fixes.js.map +1 -0
  109. package/dist/mcp/tools/fixes/parameter-fixes.js +277 -0
  110. package/dist/mcp/tools/fixes/parameter-fixes.js.map +1 -0
  111. package/dist/mcp/tools/fixes/types.js +10 -0
  112. package/dist/mcp/tools/fixes/types.js.map +1 -0
  113. package/dist/mcp/tools/handlers/check-parameter.js +300 -0
  114. package/dist/mcp/tools/handlers/check-parameter.js.map +1 -0
  115. package/dist/mcp/tools/handlers/find-similar-pattern.js +121 -0
  116. package/dist/mcp/tools/handlers/find-similar-pattern.js.map +1 -0
  117. package/dist/mcp/tools/handlers/get-node-info.js +131 -0
  118. package/dist/mcp/tools/handlers/get-node-info.js.map +1 -0
  119. package/dist/mcp/tools/handlers/get-operation-schema.js +141 -0
  120. package/dist/mcp/tools/handlers/get-operation-schema.js.map +1 -0
  121. package/dist/mcp/tools/handlers/list-nodes.js +126 -0
  122. package/dist/mcp/tools/handlers/list-nodes.js.map +1 -0
  123. package/dist/mcp/tools/handlers/list-operations.js +138 -0
  124. package/dist/mcp/tools/handlers/list-operations.js.map +1 -0
  125. package/dist/mcp/tools/handlers/suggest-fix.js +120 -0
  126. package/dist/mcp/tools/handlers/suggest-fix.js.map +1 -0
  127. package/dist/mcp/tools/handlers/validate-workflow.js +92 -0
  128. package/dist/mcp/tools/handlers/validate-workflow.js.map +1 -0
  129. package/dist/mcp/tools/index.js +190 -0
  130. package/dist/mcp/tools/index.js.map +1 -0
  131. package/dist/mcp/tools/schemas.js +195 -0
  132. package/dist/mcp/tools/schemas.js.map +1 -0
  133. package/dist/mcp/tools/validate.js +95 -0
  134. package/dist/mcp/tools/validate.js.map +1 -0
  135. package/dist/mcp/types/mcp.js +7 -0
  136. package/dist/mcp/types/mcp.js.map +1 -0
  137. package/dist/mcp/utils/timeout.js +78 -0
  138. package/dist/mcp/utils/timeout.js.map +1 -0
  139. package/dist/services/BatchProcessor.js +433 -0
  140. package/dist/services/BatchProcessor.js.map +1 -0
  141. package/dist/services/CheckpointManager.js +281 -0
  142. package/dist/services/CheckpointManager.js.map +1 -0
  143. package/dist/services/CostCalculator.js +211 -0
  144. package/dist/services/CostCalculator.js.map +1 -0
  145. package/dist/services/EmbeddingCache.js +68 -0
  146. package/dist/services/EmbeddingCache.js.map +1 -0
  147. package/dist/services/EmbeddingService.js +143 -0
  148. package/dist/services/EmbeddingService.js.map +1 -0
  149. package/dist/services/RankingService.js +81 -0
  150. package/dist/services/RankingService.js.map +1 -0
  151. package/dist/services/RedisCache.js +376 -0
  152. package/dist/services/RedisCache.js.map +1 -0
  153. package/dist/services/RedisCatalogCache.js +680 -0
  154. package/dist/services/RedisCatalogCache.js.map +1 -0
  155. package/dist/services/ResumeManager.js +252 -0
  156. package/dist/services/ResumeManager.js.map +1 -0
  157. package/dist/services/SearchService.js +282 -0
  158. package/dist/services/SearchService.js.map +1 -0
  159. package/dist/services/SemanticCatalogSearch.js +405 -0
  160. package/dist/services/SemanticCatalogSearch.js.map +1 -0
  161. package/dist/services/ValidationCache.js +157 -0
  162. package/dist/services/ValidationCache.js.map +1 -0
  163. package/dist/services/WorkflowPipelineService.js +1997 -0
  164. package/dist/services/WorkflowPipelineService.js.map +1 -0
  165. package/dist/services/catalog/index.js +34 -0
  166. package/dist/services/catalog/index.js.map +1 -0
  167. package/dist/services/catalog/interfaces.js +17 -0
  168. package/dist/services/catalog/interfaces.js.map +1 -0
  169. package/dist/services/catalog/loaders.js +169 -0
  170. package/dist/services/catalog/loaders.js.map +1 -0
  171. package/dist/services/catalog/types.js +138 -0
  172. package/dist/services/catalog/types.js.map +1 -0
  173. package/dist/services/documentation-normalization/docUrlUtils.js +88 -0
  174. package/dist/services/documentation-normalization/docUrlUtils.js.map +1 -0
  175. package/dist/services/error-quality/ErrorQualityService.js +262 -0
  176. package/dist/services/error-quality/ErrorQualityService.js.map +1 -0
  177. package/dist/services/error-quality/analyzers/CredentialAnalyzer.js +260 -0
  178. package/dist/services/error-quality/analyzers/CredentialAnalyzer.js.map +1 -0
  179. package/dist/services/error-quality/analyzers/IssuePredictor.js +380 -0
  180. package/dist/services/error-quality/analyzers/IssuePredictor.js.map +1 -0
  181. package/dist/services/error-quality/analyzers/MockCoverageAnalyzer.js +267 -0
  182. package/dist/services/error-quality/analyzers/MockCoverageAnalyzer.js.map +1 -0
  183. package/dist/services/error-quality/data/ErrorPatternSeeder.js +963 -0
  184. package/dist/services/error-quality/data/ErrorPatternSeeder.js.map +1 -0
  185. package/dist/services/error-quality/index.js +25 -0
  186. package/dist/services/error-quality/index.js.map +1 -0
  187. package/dist/services/error-quality/reports/ReportGenerator.js +343 -0
  188. package/dist/services/error-quality/reports/ReportGenerator.js.map +1 -0
  189. package/dist/services/error-quality/taxonomy/ErrorTaxonomy.js +698 -0
  190. package/dist/services/error-quality/taxonomy/ErrorTaxonomy.js.map +1 -0
  191. package/dist/services/error-quality/types.js +11 -0
  192. package/dist/services/error-quality/types.js.map +1 -0
  193. package/dist/services/progress/ProgressTracker.js +288 -0
  194. package/dist/services/progress/ProgressTracker.js.map +1 -0
  195. package/dist/services/progress/formatters.js +122 -0
  196. package/dist/services/progress/formatters.js.map +1 -0
  197. package/dist/services/progress/index.js +36 -0
  198. package/dist/services/progress/index.js.map +1 -0
  199. package/dist/services/progress/types.js +7 -0
  200. package/dist/services/progress/types.js.map +1 -0
  201. package/dist/services/search/embeddingGenerator.js +112 -0
  202. package/dist/services/search/embeddingGenerator.js.map +1 -0
  203. package/dist/types/aiCapabilities.js +7 -0
  204. package/dist/types/aiCapabilities.js.map +1 -0
  205. package/dist/types/aiConfigSchema.js +7 -0
  206. package/dist/types/aiConfigSchema.js.map +1 -0
  207. package/dist/utils/bannerLogger.js +186 -0
  208. package/dist/utils/bannerLogger.js.map +1 -0
  209. package/dist/utils/bannerService.js +23 -0
  210. package/dist/utils/bannerService.js.map +1 -0
  211. package/dist/utils/bannerServiceAdapter.js +54 -0
  212. package/dist/utils/bannerServiceAdapter.js.map +1 -0
  213. package/dist/utils/batchLogger.js +171 -0
  214. package/dist/utils/batchLogger.js.map +1 -0
  215. package/dist/utils/bottomStickyBanner.js +239 -0
  216. package/dist/utils/bottomStickyBanner.js.map +1 -0
  217. package/dist/utils/credentialMatcher.js +206 -0
  218. package/dist/utils/credentialMatcher.js.map +1 -0
  219. package/dist/utils/credentialNormalizer.js +442 -0
  220. package/dist/utils/credentialNormalizer.js.map +1 -0
  221. package/dist/utils/integratedBannerLogger.js +59 -0
  222. package/dist/utils/integratedBannerLogger.js.map +1 -0
  223. package/dist/utils/n8nSourceGit.js +195 -0
  224. package/dist/utils/n8nSourceGit.js.map +1 -0
  225. package/dist/utils/nodeTypeNormalizer.js +131 -0
  226. package/dist/utils/nodeTypeNormalizer.js.map +1 -0
  227. package/dist/utils/openaiClient.js +397 -0
  228. package/dist/utils/openaiClient.js.map +1 -0
  229. package/dist/utils/productionLogger.js +16 -0
  230. package/dist/utils/productionLogger.js.map +1 -0
  231. package/dist/utils/progressBarBanner.js +132 -0
  232. package/dist/utils/progressBarBanner.js.map +1 -0
  233. package/dist/utils/scriptHeartbeat.js +117 -0
  234. package/dist/utils/scriptHeartbeat.js.map +1 -0
  235. package/dist/utils/scriptLogger.js +125 -0
  236. package/dist/utils/scriptLogger.js.map +1 -0
  237. package/dist/utils/scriptRunner.js +95 -0
  238. package/dist/utils/scriptRunner.js.map +1 -0
  239. package/dist/utils/scriptTimeout.js +128 -0
  240. package/dist/utils/scriptTimeout.js.map +1 -0
  241. package/dist/utils/scriptWrapper.js +219 -0
  242. package/dist/utils/scriptWrapper.js.map +1 -0
  243. package/dist/utils/stickyBanner.js +226 -0
  244. package/dist/utils/stickyBanner.js.map +1 -0
  245. package/dist/utils/terminalSpinner.js +97 -0
  246. package/dist/utils/terminalSpinner.js.map +1 -0
  247. package/dist/utils/threeLineBanner.js +427 -0
  248. package/dist/utils/threeLineBanner.js.map +1 -0
  249. package/dist/utils/validatorCheckpointManager.js +170 -0
  250. package/dist/utils/validatorCheckpointManager.js.map +1 -0
  251. package/dist/utils/validatorConnectionManager.js +124 -0
  252. package/dist/utils/validatorConnectionManager.js.map +1 -0
  253. package/dist/validation/catalog.js +56 -0
  254. package/dist/validation/catalog.js.map +1 -0
  255. package/dist/validation/config/deprecated-nodes.js +234 -0
  256. package/dist/validation/config/deprecated-nodes.js.map +1 -0
  257. package/dist/validation/config/l6-severity.js +227 -0
  258. package/dist/validation/config/l6-severity.js.map +1 -0
  259. package/dist/validation/config/terminal-nodes.js +132 -0
  260. package/dist/validation/config/terminal-nodes.js.map +1 -0
  261. package/dist/validation/config/unreachable-nodes.js +67 -0
  262. package/dist/validation/config/unreachable-nodes.js.map +1 -0
  263. package/dist/validation/core.js +47 -0
  264. package/dist/validation/core.js.map +1 -0
  265. package/dist/validation/docExtraction.js +12 -0
  266. package/dist/validation/docExtraction.js.map +1 -0
  267. package/dist/validation/dryRunMockRunner.js +128 -0
  268. package/dist/validation/dryRunMockRunner.js.map +1 -0
  269. package/dist/validation/fixtureEngine.js +61 -0
  270. package/dist/validation/fixtureEngine.js.map +1 -0
  271. package/dist/validation/index.js +15 -0
  272. package/dist/validation/index.js.map +1 -0
  273. package/dist/validation/k-levels/k2-blockers.js +222 -0
  274. package/dist/validation/k-levels/k2-blockers.js.map +1 -0
  275. package/dist/validation/l1-structure.js +296 -0
  276. package/dist/validation/l1-structure.js.map +1 -0
  277. package/dist/validation/l2-nodes.js +282 -0
  278. package/dist/validation/l2-nodes.js.map +1 -0
  279. package/dist/validation/l3-credentials.js +322 -0
  280. package/dist/validation/l3-credentials.js.map +1 -0
  281. package/dist/validation/l4-connections.js +698 -0
  282. package/dist/validation/l4-connections.js.map +1 -0
  283. package/dist/validation/l5-parameters.js +803 -0
  284. package/dist/validation/l5-parameters.js.map +1 -0
  285. package/dist/validation/l6-checks/ai-tool-variants.js +407 -0
  286. package/dist/validation/l6-checks/ai-tool-variants.js.map +1 -0
  287. package/dist/validation/l6-checks/catalog-checks.js +260 -0
  288. package/dist/validation/l6-checks/catalog-checks.js.map +1 -0
  289. package/dist/validation/l6-checks/data-contracts.js +197 -0
  290. package/dist/validation/l6-checks/data-contracts.js.map +1 -0
  291. package/dist/validation/l6-checks/deprecation.js +133 -0
  292. package/dist/validation/l6-checks/deprecation.js.map +1 -0
  293. package/dist/validation/l6-checks/error-handling.js +193 -0
  294. package/dist/validation/l6-checks/error-handling.js.map +1 -0
  295. package/dist/validation/l6-checks/expression-syntax.js +387 -0
  296. package/dist/validation/l6-checks/expression-syntax.js.map +1 -0
  297. package/dist/validation/l6-checks/flow-integrity.js +504 -0
  298. package/dist/validation/l6-checks/flow-integrity.js.map +1 -0
  299. package/dist/validation/l6-checks/index.js +106 -0
  300. package/dist/validation/l6-checks/index.js.map +1 -0
  301. package/dist/validation/l6-checks/loops.js +370 -0
  302. package/dist/validation/l6-checks/loops.js.map +1 -0
  303. package/dist/validation/l6-checks/performance.js +182 -0
  304. package/dist/validation/l6-checks/performance.js.map +1 -0
  305. package/dist/validation/l6-checks/security.js +273 -0
  306. package/dist/validation/l6-checks/security.js.map +1 -0
  307. package/dist/validation/l6-patterns.js +472 -0
  308. package/dist/validation/l6-patterns.js.map +1 -0
  309. package/dist/validation/mockLevelResolver.js +95 -0
  310. package/dist/validation/mockLevelResolver.js.map +1 -0
  311. package/dist/validation/n8nApiClient.js +21 -0
  312. package/dist/validation/n8nApiClient.js.map +1 -0
  313. package/dist/validation/n8nCli.js +87 -0
  314. package/dist/validation/n8nCli.js.map +1 -0
  315. package/dist/validation/types.js +8 -0
  316. package/dist/validation/types.js.map +1 -0
  317. package/dist/validation/usageStats.js +82 -0
  318. package/dist/validation/usageStats.js.map +1 -0
  319. package/package.json +274 -0
@@ -0,0 +1,680 @@
1
+ /**
2
+ * Redis Catalog Cache Service
3
+ *
4
+ * Implements ICatalogCache using Redis for distributed caching.
5
+ * Caches all bluelime catalog data (nodes, operations, parameters,
6
+ * credentials, connection_rules) with structured key namespaces.
7
+ *
8
+ * Features:
9
+ * - Sub-millisecond lookups for cached data
10
+ * - Graceful fallback to DB if Redis unavailable
11
+ * - Configurable TTL (default: 24 hours)
12
+ * - Hit/miss tracking for monitoring
13
+ *
14
+ * @module services/RedisCatalogCache
15
+ */
16
+ import Redis from 'ioredis';
17
+ import { logger } from '@tsvika58/shared-utilities/logging';
18
+ import { loadFullCatalog } from './catalog/loaders.js';
19
+ import { validatorQuery } from '../db/validatorPostgresClient.js';
20
+ /** Default TTL in seconds (24 hours) */
21
+ const DEFAULT_TTL = 86400;
22
+ /** Redis key prefix for all catalog data */
23
+ const PREFIX = 'catalog:';
24
+ /**
25
+ * Normalize node type to lowercase for consistent lookups.
26
+ */
27
+ function normalizeNodeType(nodeType) {
28
+ return nodeType.toLowerCase().trim();
29
+ }
30
+ /**
31
+ * RedisCatalogCache implements ICatalogCache using Redis as the backing store.
32
+ * Falls back to database queries when Redis is unavailable.
33
+ */
34
+ export class RedisCatalogCache {
35
+ redis = null;
36
+ isConnected = false;
37
+ ready = false;
38
+ config;
39
+ stats;
40
+ constructor(config) {
41
+ this.config = {
42
+ ttlSeconds: config?.ttlSeconds ?? DEFAULT_TTL,
43
+ autoRefresh: config?.autoRefresh ?? false,
44
+ refreshIntervalSeconds: config?.refreshIntervalSeconds ?? 3600,
45
+ preloadOnInit: config?.preloadOnInit ?? true,
46
+ redisUrl: config?.redisUrl ?? process.env.REDIS_URL ?? '',
47
+ fallbackToMemory: config?.fallbackToMemory ?? true,
48
+ };
49
+ this.stats = {
50
+ nodes: 0,
51
+ operations: 0,
52
+ parameters: 0,
53
+ credentials: 0,
54
+ connections: 0,
55
+ loadedAt: null,
56
+ loadDurationMs: 0,
57
+ hits: 0,
58
+ misses: 0,
59
+ };
60
+ }
61
+ // ============================================================
62
+ // Lifecycle Methods
63
+ // ============================================================
64
+ async initialize() {
65
+ await this.connectRedis();
66
+ if (this.config.preloadOnInit && this.isConnected && this.redis) {
67
+ // Check if Redis already has cached data (skip redundant Supabase loads)
68
+ // RAG-2.2.93.13: Check nodes, operations, AND parameters to ensure cache is complete
69
+ try {
70
+ const cachedNodeCount = await this.redis.scard(`${PREFIX}nodes:_all`);
71
+ // Check if operations are cached
72
+ const opKeys = await this.redis.keys(`${PREFIX}operations:*`);
73
+ const hasOperations = opKeys.length > 100; // Expect many operation keys
74
+ // Check if parameters are cached
75
+ const paramKeys = await this.redis.keys(`${PREFIX}parameters:*`);
76
+ const hasParameters = paramKeys.length > 100; // Expect many parameter keys
77
+ if (cachedNodeCount > 0 && hasOperations && hasParameters) {
78
+ // Cache already populated - restore stats from Redis
79
+ this.stats.nodes = cachedNodeCount;
80
+ this.stats.operations = opKeys.length;
81
+ this.stats.parameters = paramKeys.length;
82
+ this.stats.credentials = await this.redis.scard(`${PREFIX}credentials:_all`);
83
+ logger.info('Using existing Redis cache', {
84
+ node_count: cachedNodeCount,
85
+ op_keys: opKeys.length,
86
+ param_keys: paramKeys.length,
87
+ });
88
+ }
89
+ else {
90
+ // Cache incomplete - need to load from Supabase
91
+ logger.info('Redis cache incomplete, refreshing...', {
92
+ nodes: cachedNodeCount,
93
+ op_keys: opKeys.length,
94
+ param_keys: paramKeys.length,
95
+ });
96
+ await this.refresh();
97
+ }
98
+ }
99
+ catch {
100
+ // Fallback to refresh if cache check fails (e.g., mocked Redis in tests)
101
+ await this.refresh();
102
+ }
103
+ }
104
+ this.ready = true;
105
+ }
106
+ async refresh() {
107
+ const start = performance.now();
108
+ try {
109
+ const catalogData = await loadFullCatalog();
110
+ await this.populateCache(catalogData);
111
+ this.stats.loadedAt = new Date().toISOString();
112
+ this.stats.loadDurationMs = performance.now() - start;
113
+ return {
114
+ success: true,
115
+ stats: await this.getStats(),
116
+ };
117
+ }
118
+ catch (error) {
119
+ const errorMsg = error instanceof Error ? error.message : String(error);
120
+ return {
121
+ success: false,
122
+ stats: await this.getStats(),
123
+ error: errorMsg,
124
+ };
125
+ }
126
+ }
127
+ async getStats() {
128
+ return {
129
+ nodes: this.stats.nodes,
130
+ operations: this.stats.operations,
131
+ parameters: this.stats.parameters,
132
+ credentials: this.stats.credentials,
133
+ connections: this.stats.connections,
134
+ loaded_at: this.stats.loadedAt,
135
+ load_duration_ms: this.stats.loadDurationMs,
136
+ hits: this.stats.hits,
137
+ misses: this.stats.misses,
138
+ backend: this.getBackend(),
139
+ };
140
+ }
141
+ isReady() {
142
+ return this.ready;
143
+ }
144
+ async dispose() {
145
+ if (this.redis) {
146
+ await this.redis.quit();
147
+ this.redis = null;
148
+ this.isConnected = false;
149
+ }
150
+ this.ready = false;
151
+ }
152
+ // ============================================================
153
+ // Node Operations
154
+ // ============================================================
155
+ async getNode(nodeType) {
156
+ const key = `${PREFIX}nodes:${normalizeNodeType(nodeType)}`;
157
+ return this.getCachedOrFallback(key, async () => {
158
+ const result = await validatorQuery('SELECT * FROM bluelime.nodes WHERE LOWER(node_type) = $1', [normalizeNodeType(nodeType)]);
159
+ return result.rows[0] ?? null;
160
+ });
161
+ }
162
+ async isTrigger(nodeType) {
163
+ const node = await this.getNode(nodeType);
164
+ return node?.is_trigger ?? false;
165
+ }
166
+ async isDeprecated(nodeType) {
167
+ const node = await this.getNode(nodeType);
168
+ return node?.is_deprecated ?? false;
169
+ }
170
+ async getNodesByRole(role) {
171
+ const key = `${PREFIX}nodes:_roles:${role}`;
172
+ return this.getSetMembers(key);
173
+ }
174
+ async getAllNodeTypes() {
175
+ // Try Redis first
176
+ const cachedTypes = await this.getSetMembers(`${PREFIX}nodes:_all`);
177
+ if (cachedTypes.length > 0) {
178
+ return cachedTypes;
179
+ }
180
+ // Fallback to database query if Redis set is empty
181
+ try {
182
+ const result = await validatorQuery('SELECT LOWER(node_type) as node_type FROM bluelime.nodes');
183
+ return result.rows.map((row) => row.node_type);
184
+ }
185
+ catch {
186
+ return [];
187
+ }
188
+ }
189
+ async hasNode(nodeType) {
190
+ const key = `${PREFIX}nodes:${normalizeNodeType(nodeType)}`;
191
+ if (this.isConnected && this.redis) {
192
+ const exists = await this.redis.exists(key);
193
+ return exists === 1;
194
+ }
195
+ const node = await this.getNode(nodeType);
196
+ return node !== null;
197
+ }
198
+ // ============================================================
199
+ // Operation and Parameter Methods
200
+ // ============================================================
201
+ async getOperations(nodeType) {
202
+ const key = `${PREFIX}operations:${normalizeNodeType(nodeType)}`;
203
+ const result = await this.getCachedOrFallback(key, async () => {
204
+ const dbResult = await validatorQuery(`SELECT o.id, n.node_type, o.display_name AS operation_name,
205
+ o.operation AS operation_value, o.description, o.required_parameters
206
+ FROM bluelime.operations o
207
+ JOIN bluelime.nodes n ON o.node_id = n.id
208
+ WHERE LOWER(n.node_type) = $1`, [normalizeNodeType(nodeType)]);
209
+ return dbResult.rows;
210
+ });
211
+ return result ?? [];
212
+ }
213
+ async getOperation(nodeType, operationValue) {
214
+ const operations = await this.getOperations(nodeType);
215
+ return (operations.find((op) => op.operation_value.toLowerCase() === operationValue.toLowerCase()) ?? null);
216
+ }
217
+ /**
218
+ * RAG-2.2.108.5: Batch fetch operations for multiple node types.
219
+ * Reduces Redis round-trips from O(n) to O(1) for L5 validation.
220
+ */
221
+ async getOperationsBatch(nodeTypes) {
222
+ const result = new Map();
223
+ if (nodeTypes.length === 0)
224
+ return result;
225
+ // Try batch fetch from Redis first
226
+ if (this.isConnected && this.redis) {
227
+ try {
228
+ const keys = nodeTypes.map((nt) => `${PREFIX}operations:${normalizeNodeType(nt)}`);
229
+ const values = await this.redis.mget(...keys);
230
+ const missingTypes = [];
231
+ for (let i = 0; i < nodeTypes.length; i++) {
232
+ const value = values[i];
233
+ const normalizedType = normalizeNodeType(nodeTypes[i]);
234
+ if (value) {
235
+ this.stats.hits++;
236
+ result.set(normalizedType, JSON.parse(value));
237
+ }
238
+ else {
239
+ missingTypes.push(nodeTypes[i]);
240
+ }
241
+ }
242
+ // Fetch missing from DB in single batch query
243
+ if (missingTypes.length > 0) {
244
+ this.stats.misses += missingTypes.length;
245
+ logger.debug(`[CACHE] Operations cache miss: ${missingTypes.length} node types`);
246
+ // Batch query for all missing operations
247
+ const placeholders = missingTypes.map((_, i) => `$${i + 1}`).join(', ');
248
+ const normalizedTypes = missingTypes.map((nt) => normalizeNodeType(nt));
249
+ const dbResult = await validatorQuery(`SELECT o.id, n.node_type, o.display_name AS operation_name,
250
+ o.operation AS operation_value, o.description, o.required_parameters
251
+ FROM bluelime.operations o
252
+ JOIN bluelime.nodes n ON o.node_id = n.id
253
+ WHERE LOWER(n.node_type) IN (${placeholders})`, normalizedTypes);
254
+ // Group results by node_type
255
+ const byNodeType = new Map();
256
+ for (const op of dbResult.rows) {
257
+ const normalized = normalizeNodeType(op.node_type);
258
+ if (!byNodeType.has(normalized)) {
259
+ byNodeType.set(normalized, []);
260
+ }
261
+ byNodeType.get(normalized).push(op);
262
+ }
263
+ // Cache and add to result
264
+ const pipeline = this.redis.pipeline();
265
+ for (const nt of missingTypes) {
266
+ const normalized = normalizeNodeType(nt);
267
+ const ops = byNodeType.get(normalized) || [];
268
+ result.set(normalized, ops);
269
+ pipeline.setex(`${PREFIX}operations:${normalized}`, this.config.ttlSeconds, JSON.stringify(ops));
270
+ }
271
+ await pipeline.exec();
272
+ }
273
+ return result;
274
+ }
275
+ catch {
276
+ // Fall through to sequential fallback
277
+ }
278
+ }
279
+ // Fallback: sequential fetches
280
+ for (const nodeType of nodeTypes) {
281
+ const ops = await this.getOperations(nodeType);
282
+ result.set(normalizeNodeType(nodeType), ops);
283
+ }
284
+ return result;
285
+ }
286
+ async getParameters(operationId) {
287
+ const key = `${PREFIX}parameters:${operationId}`;
288
+ const result = await this.getCachedOrFallback(key, async () => {
289
+ const dbResult = await validatorQuery('SELECT * FROM bluelime.parameters WHERE operation_id = $1', [operationId]);
290
+ return dbResult.rows;
291
+ });
292
+ return result ?? [];
293
+ }
294
+ async getParametersBatch(operationIds) {
295
+ const result = new Map();
296
+ if (operationIds.length === 0)
297
+ return result;
298
+ // Try batch fetch from Redis first
299
+ if (this.isConnected && this.redis) {
300
+ try {
301
+ const keys = operationIds.map((id) => `${PREFIX}parameters:${id}`);
302
+ const values = await this.redis.mget(...keys);
303
+ const missingIds = [];
304
+ for (let i = 0; i < operationIds.length; i++) {
305
+ const value = values[i];
306
+ if (value) {
307
+ this.stats.hits++;
308
+ result.set(operationIds[i], JSON.parse(value));
309
+ }
310
+ else {
311
+ missingIds.push(operationIds[i]);
312
+ }
313
+ }
314
+ // RAG-2.2.93.13: Fetch missing from DB in single batch query (not parallel)
315
+ // This prevents overwhelming the connection pool with many parallel queries
316
+ if (missingIds.length > 0) {
317
+ this.stats.misses += missingIds.length;
318
+ logger.debug(`[CACHE] Parameter cache miss: ${missingIds.length} operation IDs`);
319
+ // Single batch query for all missing parameters
320
+ const placeholders = missingIds.map((_, i) => `$${i + 1}`).join(', ');
321
+ const dbResult = await validatorQuery(`SELECT * FROM bluelime.parameters WHERE operation_id IN (${placeholders})`, missingIds);
322
+ // Group results by operation_id
323
+ const byOperationId = new Map();
324
+ for (const param of dbResult.rows) {
325
+ if (!byOperationId.has(param.operation_id)) {
326
+ byOperationId.set(param.operation_id, []);
327
+ }
328
+ byOperationId.get(param.operation_id).push(param);
329
+ }
330
+ // Cache and add to result
331
+ const pipeline = this.redis.pipeline();
332
+ for (const id of missingIds) {
333
+ const params = byOperationId.get(id) || [];
334
+ result.set(id, params);
335
+ pipeline.setex(`${PREFIX}parameters:${id}`, this.config.ttlSeconds, JSON.stringify(params));
336
+ }
337
+ await pipeline.exec();
338
+ }
339
+ return result;
340
+ }
341
+ catch {
342
+ // Fall through to sequential fallback
343
+ }
344
+ }
345
+ // Fallback: sequential fetches
346
+ for (const id of operationIds) {
347
+ const params = await this.getParameters(id);
348
+ result.set(id, params);
349
+ }
350
+ return result;
351
+ }
352
+ async getParametersByOperation(nodeType, operationValue) {
353
+ const operation = await this.getOperation(nodeType, operationValue);
354
+ if (!operation)
355
+ return [];
356
+ return this.getParameters(operation.id);
357
+ }
358
+ // ============================================================
359
+ // Credential Methods
360
+ // ============================================================
361
+ async getCredential(credType) {
362
+ const key = `${PREFIX}credentials:${credType.toLowerCase()}`;
363
+ return this.getCachedOrFallback(key, async () => {
364
+ const result = await validatorQuery('SELECT * FROM bluelime.credentials WHERE LOWER(credential_type) = $1', [credType.toLowerCase()]);
365
+ return result.rows[0] ?? null;
366
+ });
367
+ }
368
+ async hasCredential(credType) {
369
+ const cred = await this.getCredential(credType);
370
+ return cred !== null;
371
+ }
372
+ async getAllCredentialTypes() {
373
+ return this.getSetMembers(`${PREFIX}credentials:_all`);
374
+ }
375
+ async getCredentialsForNode(nodeType) {
376
+ const key = `${PREFIX}node_credentials:${normalizeNodeType(nodeType)}`;
377
+ return this.getSetMembers(key);
378
+ }
379
+ // ============================================================
380
+ // Connection Methods
381
+ // ============================================================
382
+ async getConnection(source, target) {
383
+ const key = this.getConnectionKey(source, target);
384
+ return this.getCachedOrFallback(key, async () => {
385
+ const result = await validatorQuery(`SELECT source_node_type AS source_type, target_node_type AS target_type,
386
+ occurrence_count, 1.0 AS confidence
387
+ FROM bluelime.connection_rules
388
+ WHERE LOWER(source_node_type) = $1 AND LOWER(target_node_type) = $2`, [normalizeNodeType(source), normalizeNodeType(target)]);
389
+ return result.rows[0] ?? null;
390
+ });
391
+ }
392
+ async hasConnection(source, target) {
393
+ const conn = await this.getConnection(source, target);
394
+ return conn !== null;
395
+ }
396
+ async getOutgoingConnections(source) {
397
+ const key = `${PREFIX}connections:out:${normalizeNodeType(source)}`;
398
+ const result = await this.getCachedOrFallback(key, async () => {
399
+ const dbResult = await validatorQuery(`SELECT source_node_type AS source_type, target_node_type AS target_type,
400
+ occurrence_count, 1.0 AS confidence
401
+ FROM bluelime.connection_rules WHERE LOWER(source_node_type) = $1`, [normalizeNodeType(source)]);
402
+ return dbResult.rows;
403
+ });
404
+ return result ?? [];
405
+ }
406
+ async getIncomingConnections(target) {
407
+ const key = `${PREFIX}connections:in:${normalizeNodeType(target)}`;
408
+ const result = await this.getCachedOrFallback(key, async () => {
409
+ const dbResult = await validatorQuery(`SELECT source_node_type AS source_type, target_node_type AS target_type,
410
+ occurrence_count, 1.0 AS confidence
411
+ FROM bluelime.connection_rules WHERE LOWER(target_node_type) = $1`, [normalizeNodeType(target)]);
412
+ return dbResult.rows;
413
+ });
414
+ return result ?? [];
415
+ }
416
+ // ============================================================
417
+ // Private: Redis Connection
418
+ // ============================================================
419
+ async connectRedis() {
420
+ const redisUrl = this.config.redisUrl ||
421
+ process.env.REDIS_URL ||
422
+ 'redis://localhost:6379';
423
+ try {
424
+ this.redis = new Redis(redisUrl, {
425
+ enableAutoPipelining: true,
426
+ maxRetriesPerRequest: 1,
427
+ connectTimeout: 2000,
428
+ commandTimeout: 5000,
429
+ retryStrategy: (times) => {
430
+ if (times > 2)
431
+ return null;
432
+ return Math.min(times * 100, 1000);
433
+ },
434
+ enableReadyCheck: true,
435
+ });
436
+ await this.waitForConnection();
437
+ await this.redis.ping();
438
+ this.isConnected = true;
439
+ }
440
+ catch (error) {
441
+ this.handleConnectionError(error);
442
+ }
443
+ }
444
+ async waitForConnection() {
445
+ return new Promise((resolve, reject) => {
446
+ const timeout = setTimeout(() => {
447
+ reject(new Error('Redis connection timeout'));
448
+ }, 5000);
449
+ this.redis.on('ready', () => {
450
+ clearTimeout(timeout);
451
+ resolve();
452
+ });
453
+ this.redis.on('error', (err) => {
454
+ clearTimeout(timeout);
455
+ reject(err);
456
+ });
457
+ });
458
+ }
459
+ handleConnectionError(error) {
460
+ logger.warn('Failed to connect to Redis', {
461
+ error: error instanceof Error ? error.message : String(error),
462
+ });
463
+ if (this.redis) {
464
+ this.redis.quit().catch((err) => {
465
+ logger.debug('Redis quit failed during connection error cleanup', { error: err.message });
466
+ });
467
+ }
468
+ this.redis = null;
469
+ this.isConnected = false;
470
+ }
471
+ // ============================================================
472
+ // Private: Cache Population
473
+ // ============================================================
474
+ async populateCache(data) {
475
+ if (!this.isConnected || !this.redis)
476
+ return;
477
+ const pipeline = this.redis.pipeline();
478
+ const ttl = this.config.ttlSeconds;
479
+ this.populateNodes(pipeline, data.nodes.data, ttl);
480
+ this.populateOperations(pipeline, data.operations.data, ttl);
481
+ this.populateParameters(pipeline, data.parameters.data, ttl);
482
+ this.populateCredentials(pipeline, data.credentials.data, ttl);
483
+ this.populateConnections(pipeline, data.connections.data, ttl);
484
+ await pipeline.exec();
485
+ this.stats.nodes = data.nodes.count;
486
+ this.stats.operations = data.operations.count;
487
+ this.stats.parameters = data.parameters.count;
488
+ this.stats.credentials = data.credentials.count;
489
+ this.stats.connections = data.connections.count;
490
+ }
491
+ populateNodes(pipeline, nodes, ttl) {
492
+ const triggers = [];
493
+ const deprecated = [];
494
+ const roleMap = new Map();
495
+ const allNodes = [];
496
+ for (const node of nodes) {
497
+ const normalizedType = normalizeNodeType(node.node_type);
498
+ const key = `${PREFIX}nodes:${normalizedType}`;
499
+ pipeline.setex(key, ttl, JSON.stringify(node));
500
+ allNodes.push(normalizedType);
501
+ if (node.is_trigger)
502
+ triggers.push(normalizedType);
503
+ if (node.is_deprecated)
504
+ deprecated.push(normalizedType);
505
+ const role = node.node_role || 'unknown';
506
+ if (!roleMap.has(role))
507
+ roleMap.set(role, []);
508
+ roleMap.get(role).push(normalizedType);
509
+ }
510
+ if (allNodes.length > 0) {
511
+ pipeline.del(`${PREFIX}nodes:_all`);
512
+ pipeline.sadd(`${PREFIX}nodes:_all`, ...allNodes);
513
+ }
514
+ if (triggers.length > 0) {
515
+ pipeline.del(`${PREFIX}nodes:_triggers`);
516
+ pipeline.sadd(`${PREFIX}nodes:_triggers`, ...triggers);
517
+ }
518
+ if (deprecated.length > 0) {
519
+ pipeline.del(`${PREFIX}nodes:_deprecated`);
520
+ pipeline.sadd(`${PREFIX}nodes:_deprecated`, ...deprecated);
521
+ }
522
+ Array.from(roleMap.entries()).forEach(([role, types]) => {
523
+ const roleKey = `${PREFIX}nodes:_roles:${role}`;
524
+ pipeline.del(roleKey);
525
+ pipeline.sadd(roleKey, ...types);
526
+ });
527
+ }
528
+ populateOperations(pipeline, operations, ttl) {
529
+ const byNode = new Map();
530
+ for (const op of operations) {
531
+ const normalized = normalizeNodeType(op.node_type);
532
+ if (!byNode.has(normalized))
533
+ byNode.set(normalized, []);
534
+ byNode.get(normalized).push(op);
535
+ }
536
+ Array.from(byNode.entries()).forEach(([nodeType, ops]) => {
537
+ const key = `${PREFIX}operations:${nodeType}`;
538
+ pipeline.setex(key, ttl, JSON.stringify(ops));
539
+ });
540
+ }
541
+ populateParameters(pipeline, parameters, ttl) {
542
+ const byOperation = new Map();
543
+ for (const param of parameters) {
544
+ if (!byOperation.has(param.operation_id)) {
545
+ byOperation.set(param.operation_id, []);
546
+ }
547
+ byOperation.get(param.operation_id).push(param);
548
+ }
549
+ Array.from(byOperation.entries()).forEach(([opId, params]) => {
550
+ const key = `${PREFIX}parameters:${opId}`;
551
+ pipeline.setex(key, ttl, JSON.stringify(params));
552
+ });
553
+ }
554
+ populateCredentials(pipeline, credentials, ttl) {
555
+ const allCreds = [];
556
+ for (const cred of credentials) {
557
+ const normalized = cred.credential_type.toLowerCase();
558
+ const key = `${PREFIX}credentials:${normalized}`;
559
+ pipeline.setex(key, ttl, JSON.stringify(cred));
560
+ allCreds.push(normalized);
561
+ }
562
+ if (allCreds.length > 0) {
563
+ pipeline.del(`${PREFIX}credentials:_all`);
564
+ pipeline.sadd(`${PREFIX}credentials:_all`, ...allCreds);
565
+ }
566
+ }
567
+ populateConnections(pipeline, connections, ttl) {
568
+ const outgoing = new Map();
569
+ const incoming = new Map();
570
+ for (const conn of connections) {
571
+ const key = this.getConnectionKey(conn.source_type, conn.target_type);
572
+ pipeline.setex(key, ttl, JSON.stringify(conn));
573
+ const srcNorm = normalizeNodeType(conn.source_type);
574
+ const tgtNorm = normalizeNodeType(conn.target_type);
575
+ if (!outgoing.has(srcNorm))
576
+ outgoing.set(srcNorm, []);
577
+ outgoing.get(srcNorm).push(conn);
578
+ if (!incoming.has(tgtNorm))
579
+ incoming.set(tgtNorm, []);
580
+ incoming.get(tgtNorm).push(conn);
581
+ }
582
+ Array.from(outgoing.entries()).forEach(([src, conns]) => {
583
+ const key = `${PREFIX}connections:out:${src}`;
584
+ pipeline.setex(key, ttl, JSON.stringify(conns));
585
+ });
586
+ Array.from(incoming.entries()).forEach(([tgt, conns]) => {
587
+ const key = `${PREFIX}connections:in:${tgt}`;
588
+ pipeline.setex(key, ttl, JSON.stringify(conns));
589
+ });
590
+ }
591
+ // ============================================================
592
+ // Private: Cache Helpers
593
+ // ============================================================
594
+ async getCachedOrFallback(key, fallback) {
595
+ if (this.isConnected && this.redis) {
596
+ try {
597
+ const cached = await this.redis.get(key);
598
+ if (cached) {
599
+ this.stats.hits++;
600
+ return JSON.parse(cached);
601
+ }
602
+ }
603
+ catch {
604
+ // Fall through to DB
605
+ }
606
+ }
607
+ this.stats.misses++;
608
+ const result = await fallback();
609
+ if (result && this.isConnected && this.redis) {
610
+ await this.redis
611
+ .setex(key, this.config.ttlSeconds, JSON.stringify(result))
612
+ .catch((err) => {
613
+ logger.debug('Redis setex failed (non-critical)', { key, error: err.message });
614
+ });
615
+ }
616
+ return result;
617
+ }
618
+ async getSetMembers(key) {
619
+ if (this.isConnected && this.redis) {
620
+ try {
621
+ return await this.redis.smembers(key);
622
+ }
623
+ catch {
624
+ return [];
625
+ }
626
+ }
627
+ return [];
628
+ }
629
+ getConnectionKey(source, target) {
630
+ return `${PREFIX}connections:${normalizeNodeType(source)}:${normalizeNodeType(target)}`;
631
+ }
632
+ getBackend() {
633
+ if (this.isConnected && this.redis)
634
+ return 'redis';
635
+ return 'none';
636
+ }
637
+ }
638
+ /** Singleton instance */
639
+ let instance = null;
640
+ /** Promise singleton for initialization (prevents race conditions) */
641
+ let initPromise = null;
642
+ /**
643
+ * Get or create the singleton RedisCatalogCache instance.
644
+ */
645
+ export function getRedisCatalogCache(config) {
646
+ if (!instance) {
647
+ instance = new RedisCatalogCache(config);
648
+ }
649
+ return instance;
650
+ }
651
+ /**
652
+ * Initialize and return the singleton cache.
653
+ * Convenience function that creates, initializes, and returns the cache.
654
+ * Uses promise singleton pattern to prevent race conditions when called concurrently.
655
+ */
656
+ export function initRedisCatalogCache(config) {
657
+ if (!initPromise) {
658
+ initPromise = (async () => {
659
+ const cache = getRedisCatalogCache(config);
660
+ if (!cache.isReady()) {
661
+ await cache.initialize();
662
+ }
663
+ return cache;
664
+ })();
665
+ }
666
+ return initPromise;
667
+ }
668
+ /**
669
+ * Reset the singleton instance (for testing).
670
+ */
671
+ export function resetRedisCatalogCache() {
672
+ if (instance) {
673
+ instance.dispose().catch((err) => {
674
+ logger.debug('Dispose failed during reset', { error: err.message });
675
+ });
676
+ instance = null;
677
+ }
678
+ initPromise = null;
679
+ }
680
+ //# sourceMappingURL=RedisCatalogCache.js.map