@adalo/metrics 0.1.122 → 0.1.124

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 (63) hide show
  1. package/lib/health/healthCheckCache.d.ts +57 -0
  2. package/lib/health/healthCheckCache.d.ts.map +1 -0
  3. package/lib/health/healthCheckCache.js +200 -0
  4. package/lib/health/healthCheckCache.js.map +1 -0
  5. package/lib/{healthCheckClient.d.ts → health/healthCheckClient.d.ts} +59 -15
  6. package/lib/health/healthCheckClient.d.ts.map +1 -0
  7. package/lib/{healthCheckClient.js → health/healthCheckClient.js} +232 -49
  8. package/lib/health/healthCheckClient.js.map +1 -0
  9. package/lib/health/healthCheckUtils.d.ts +54 -0
  10. package/lib/health/healthCheckUtils.d.ts.map +1 -0
  11. package/lib/health/healthCheckUtils.js +142 -0
  12. package/lib/health/healthCheckUtils.js.map +1 -0
  13. package/lib/health/healthCheckWorker.d.ts +2 -0
  14. package/lib/health/healthCheckWorker.d.ts.map +1 -0
  15. package/lib/health/healthCheckWorker.js +64 -0
  16. package/lib/health/healthCheckWorker.js.map +1 -0
  17. package/lib/index.d.ts +8 -6
  18. package/lib/index.d.ts.map +1 -1
  19. package/lib/index.js +28 -6
  20. package/lib/index.js.map +1 -1
  21. package/lib/metrics/baseMetricsClient.d.ts.map +1 -0
  22. package/lib/metrics/baseMetricsClient.js.map +1 -0
  23. package/lib/metrics/metricsClient.d.ts.map +1 -0
  24. package/lib/metrics/metricsClient.js.map +1 -0
  25. package/lib/metrics/metricsDatabaseClient.d.ts.map +1 -0
  26. package/lib/metrics/metricsDatabaseClient.js.map +1 -0
  27. package/lib/metrics/metricsQueueRedisClient.d.ts.map +1 -0
  28. package/lib/{metricsQueueRedisClient.js → metrics/metricsQueueRedisClient.js} +2 -2
  29. package/lib/metrics/metricsQueueRedisClient.js.map +1 -0
  30. package/lib/metrics/metricsRedisClient.d.ts.map +1 -0
  31. package/lib/{metricsRedisClient.js → metrics/metricsRedisClient.js} +1 -1
  32. package/lib/metrics/metricsRedisClient.js.map +1 -0
  33. package/package.json +1 -1
  34. package/src/health/healthCheckCache.js +237 -0
  35. package/src/{healthCheckClient.js → health/healthCheckClient.js} +226 -49
  36. package/src/health/healthCheckUtils.js +143 -0
  37. package/src/health/healthCheckWorker.js +61 -0
  38. package/src/index.ts +8 -6
  39. package/src/{metricsQueueRedisClient.js → metrics/metricsQueueRedisClient.js} +2 -2
  40. package/src/{metricsRedisClient.js → metrics/metricsRedisClient.js} +1 -1
  41. package/lib/baseMetricsClient.d.ts.map +0 -1
  42. package/lib/baseMetricsClient.js.map +0 -1
  43. package/lib/healthCheckClient.d.ts.map +0 -1
  44. package/lib/healthCheckClient.js.map +0 -1
  45. package/lib/metricsClient.d.ts.map +0 -1
  46. package/lib/metricsClient.js.map +0 -1
  47. package/lib/metricsDatabaseClient.d.ts.map +0 -1
  48. package/lib/metricsDatabaseClient.js.map +0 -1
  49. package/lib/metricsQueueRedisClient.d.ts.map +0 -1
  50. package/lib/metricsQueueRedisClient.js.map +0 -1
  51. package/lib/metricsRedisClient.d.ts.map +0 -1
  52. package/lib/metricsRedisClient.js.map +0 -1
  53. /package/lib/{baseMetricsClient.d.ts → metrics/baseMetricsClient.d.ts} +0 -0
  54. /package/lib/{baseMetricsClient.js → metrics/baseMetricsClient.js} +0 -0
  55. /package/lib/{metricsClient.d.ts → metrics/metricsClient.d.ts} +0 -0
  56. /package/lib/{metricsClient.js → metrics/metricsClient.js} +0 -0
  57. /package/lib/{metricsDatabaseClient.d.ts → metrics/metricsDatabaseClient.d.ts} +0 -0
  58. /package/lib/{metricsDatabaseClient.js → metrics/metricsDatabaseClient.js} +0 -0
  59. /package/lib/{metricsQueueRedisClient.d.ts → metrics/metricsQueueRedisClient.d.ts} +0 -0
  60. /package/lib/{metricsRedisClient.d.ts → metrics/metricsRedisClient.d.ts} +0 -0
  61. /package/src/{baseMetricsClient.js → metrics/baseMetricsClient.js} +0 -0
  62. /package/src/{metricsClient.js → metrics/metricsClient.js} +0 -0
  63. /package/src/{metricsDatabaseClient.js → metrics/metricsDatabaseClient.js} +0 -0
@@ -0,0 +1 @@
1
+ {"version":3,"file":"metricsRedisClient.js","names":["BaseMetricsClient","require","getRedisClientType","REDIS_V4","IOREDIS","REDIS_V3","redisConnectionStableFields","redisConnectionFields","RedisMetricsClient","constructor","redisClient","metricsConfig","intervalSec","parseInt","process","env","METRICS_QUEUE_INTERVAL_SEC","processType","redisClientType","redisConnectionsGauge","createGauge","name","help","labelNames","withDefaultLabels","redisConnectionsMemoryGauge","redisMemoryGauge","redisStatsGauge","_setCleanupHandlers","getRedisConnections","Error","Promise","resolve","reject","send_command","err","result","message","sendCommand","client","getRedisInfo","section","info","parseRedisConnections","clientsStr","split","filter","line","trim","map","parts","forEach","p","k","v","includes","collectRedisMetrics","memoryInfoStr","statsInfoStr","connectionsInfoStr","all","labels","getDefaultLabels","connections","logValues","console","log","length","grouped","conn","flags","totMem","cmd","key","JSON","stringify","count","memory","Object","values","valueLabels","set","parseRedisInfo","infoStr","fromEntries","startsWith","stats","used_memory","memory_type","maxmemory","instantaneous_ops_per_sec","operation","error","warn","pushRedisMetrics","gatewayPush","clearAllCounters","metricsLogValues","metricObjects","registry","getMetricsAsJSON","startPush","_startPush","catch","cleanup","quit","disconnect","exit","on","module","exports"],"sources":["../../src/metrics/metricsRedisClient.js"],"sourcesContent":["const { BaseMetricsClient } = require('./baseMetricsClient')\nconst {\n getRedisClientType,\n REDIS_V4,\n IOREDIS,\n REDIS_V3,\n} = require('../redisUtils')\n\nconst redisConnectionStableFields = ['name', 'flags', 'cmd']\nconst redisConnectionFields = ['name', 'flags', 'tot-mem', 'cmd']\n\n/**\n * RedisMetricsClient extends BaseMetricsClient to collect\n * Redis metrics periodically and push them to Prometheus Pushgateway.\n *\n * @extends BaseMetricsClient\n */\nclass RedisMetricsClient extends BaseMetricsClient {\n /**\n * @param {Object} options\n * @param {any} options.redisClient - Redis client instance (required)\n * @param {string} [options.appName] - Application name (from BaseMetricsClient)\n * @param {string} [options.dynoId] - Dyno/instance ID (from BaseMetricsClient)\n * @param {string} [options.processType] - Process type (from BaseMetricsClient)\n * @param {boolean} [options.enabled] - Enable metrics collection (from BaseMetricsClient)\n * @param {boolean} [options.logValues] - Log metrics values (from BaseMetricsClient)\n * @param {string} [options.pushgatewayUrl] - PushGateway URL (from BaseMetricsClient)\n * @param {string} [options.pushgatewaySecret] - PushGateway secret token (from BaseMetricsClient)\n * @param {number} [options.intervalSec] - Interval in seconds for pushing metrics (from BaseMetricsClient)\n * @param {boolean} [options.removeOldMetrics] - Remove old metrics by service (from BaseMetricsClient)\n * @param {function} [options.startupValidation] - Function to validate startup (from BaseMetricsClient)\n */\n constructor({ redisClient, ...metricsConfig } = {}) {\n const intervalSec =\n metricsConfig.intervalSec ||\n parseInt(process.env.METRICS_QUEUE_INTERVAL_SEC || '', 10) ||\n 5\n\n super({\n ...metricsConfig,\n processType: metricsConfig.processType || 'queue-metrics',\n intervalSec,\n })\n\n /** Redis client used for metrics */\n this.redisClient = redisClient\n this.redisClientType = getRedisClientType(redisClient)\n\n /** Counter for Redis client connections */\n this.redisConnectionsGauge = this.createGauge({\n name: 'app_redis_connections_count',\n help: 'Redis client connections',\n labelNames: this.withDefaultLabels(redisConnectionStableFields),\n })\n\n this.redisConnectionsMemoryGauge = this.createGauge({\n name: 'app_redis_connections_memory_usage_count',\n help: 'Redis client connections',\n labelNames: this.withDefaultLabels(redisConnectionStableFields),\n })\n\n /** Gauge for Redis memory usage */\n this.redisMemoryGauge = this.createGauge({\n name: 'app_redis_memory_bytes',\n help: 'Redis memory usage in bytes',\n labelNames: this.withDefaultLabels(['memory_type']),\n })\n\n /** Gauge for Redis operation stats */\n this.redisStatsGauge = this.createGauge({\n name: 'app_redis_stats_total',\n help: 'Redis operation statistics',\n labelNames: this.withDefaultLabels(['operation']),\n })\n\n this._setCleanupHandlers()\n }\n\n getRedisConnections = async () => {\n if (!this.redisClient) throw new Error('Redis client not provided')\n\n // node-redis v3 (uses callback)\n if (this.redisClientType === REDIS_V3) {\n return new Promise((resolve, reject) => {\n this.redisClient.send_command('CLIENT', ['LIST'], (err, result) => {\n if (err) {\n reject(new Error(`Failed to get CLIENT LIST: ${err.message}`))\n } else resolve(result)\n })\n })\n }\n\n // node-redis v4\n if (this.redisClientType === REDIS_V4) {\n try {\n return this.redisClient.sendCommand(['CLIENT', 'LIST'])\n } catch (err) {\n throw new Error(`Failed to get CLIENT LIST: ${err.message}`)\n }\n }\n\n if (this.redisClientType === IOREDIS) {\n try {\n return this.redisClient.client('LIST')\n } catch (err) {\n throw new Error(`Failed to get CLIENT LIST: ${err.message}`)\n }\n }\n\n throw new Error('Unsupported Redis client type')\n }\n\n getRedisInfo = async section => {\n if (!this.redisClient) throw new Error('Redis client not provided')\n\n // node-redis v3 (uses callback)\n if (this.redisClientType === REDIS_V3) {\n return new Promise((resolve, reject) => {\n this.redisClient.info(section, (err, result) => {\n if (err) reject(err)\n else resolve(result)\n })\n })\n }\n\n // node-redis v4 or ioredis (info returns Promise)\n if (this.redisClientType === REDIS_V4 || this.redisClientType === IOREDIS) {\n try {\n return this.redisClient.info(section)\n } catch (err) {\n throw new Error(`Failed to get Redis INFO: ${err.message}`)\n }\n }\n\n throw new Error('Unsupported Redis client type')\n }\n\n parseRedisConnections = clientsStr => {\n return clientsStr\n .split('\\n')\n .filter(line => line.trim() !== '')\n .map(line => {\n const parts = line.split(' ')\n const client = {}\n parts.forEach(p => {\n const [k, v] = p.split('=')\n if (redisConnectionFields.includes(k)) {\n client[k] = v\n }\n })\n return client\n })\n }\n\n /**\n * Collect basic Redis INFO metrics: clients, memory, stats\n * @returns {Promise<void>}\n */\n collectRedisMetrics = async () => {\n try {\n const [memoryInfoStr, statsInfoStr, connectionsInfoStr] =\n await Promise.all([\n this.getRedisInfo('memory'),\n this.getRedisInfo('stats'),\n this.getRedisConnections(),\n ])\n\n const labels = this.getDefaultLabels()\n\n const connections = this.parseRedisConnections(connectionsInfoStr)\n\n if (this.logValues) {\n console.log('Redis connections: ', connectionsInfoStr)\n console.log('Redis connections count: ', connections.length)\n }\n\n const grouped = {}\n connections.forEach(conn => {\n const { name, flags, 'tot-mem': totMem, cmd } = conn\n\n // build grouping key (includes default gauge labels later)\n const key = JSON.stringify({ name, flags, cmd })\n\n if (!grouped[key]) {\n grouped[key] = {\n labels: { name, flags, cmd },\n count: 0,\n memory: 0,\n }\n }\n\n grouped[key].count += 1\n grouped[key].memory += parseInt(totMem, 10)\n })\n\n Object.values(grouped).forEach(\n ({ labels: valueLabels, count, memory }) => {\n this.redisConnectionsGauge.set({ ...labels, ...valueLabels }, count)\n this.redisConnectionsMemoryGauge.set(\n { ...labels, ...valueLabels },\n memory\n )\n }\n )\n\n const parseRedisInfo = infoStr =>\n Object.fromEntries(\n infoStr\n .split('\\r\\n')\n .filter(line => line && !line.startsWith('#'))\n .map(line => line.split(':', 2))\n .filter(parts => parts.length === 2 && parts[0] && parts[1])\n )\n\n const memory = parseRedisInfo(memoryInfoStr)\n const stats = parseRedisInfo(statsInfoStr)\n\n if (memory.used_memory) {\n this.redisMemoryGauge.set(\n { ...labels, memory_type: 'used' },\n parseInt(memory.used_memory, 10) || 0\n )\n }\n if (memory.maxmemory) {\n this.redisMemoryGauge.set(\n { ...labels, memory_type: 'max' },\n parseInt(memory.maxmemory, 10) || 0\n )\n }\n\n if (stats.instantaneous_ops_per_sec) {\n this.redisStatsGauge.set(\n { ...labels, operation: 'ops_per_sec' },\n parseInt(stats.instantaneous_ops_per_sec, 10) || 0\n )\n }\n } catch (error) {\n console.warn(\n `[queue-metrics] Failed to collect Redis metrics:`,\n error.message\n )\n }\n }\n\n /**\n * Collect metrics for all Redis and push to Prometheus Pushgateway\n * @returns {Promise<void>}\n */\n pushRedisMetrics = async () => {\n try {\n await this.collectRedisMetrics()\n await this.gatewayPush()\n this.clearAllCounters()\n\n if (this.metricsLogValues) {\n const metricObjects = await this.registry.getMetricsAsJSON()\n console.info(\n `[queue-metrics] Collected metrics for Redis`,\n JSON.stringify(metricObjects, null, 2)\n )\n }\n } catch (error) {\n console.error(\n `[queue-metrics] Failed to collect Redis metrics: ${error.message}`\n )\n throw error\n }\n }\n\n /**\n * Start periodic collection.\n * @param {number} [intervalSec=this.intervalSec] - Interval in seconds\n */\n startPush = (intervalSec = this.intervalSec) => {\n this._startPush(intervalSec, () => {\n this.pushRedisMetrics().catch(err => {\n console.error(`[queue-metrics] Failed to push Redis metrics:`, err)\n })\n })\n }\n\n /**\n * Cleanup Redis client and exit process.\n * @returns {Promise<void>}\n */\n cleanup = async () => {\n try {\n if (!this.redisClient) return\n\n if (\n this.redisClientType === REDIS_V3 ||\n this.redisClientType === REDIS_V4\n ) {\n await this.redisClient.quit()\n } else if (this.redisClientType === IOREDIS) {\n await this.redisClient.disconnect()\n }\n } catch (err) {\n console.error('[queue-metrics] Error closing Redis client:', err)\n }\n process.exit(0)\n }\n\n _setCleanupHandlers = () => {\n process.on('SIGINT', this.cleanup)\n process.on('SIGTERM', this.cleanup)\n }\n}\n\nmodule.exports = { RedisMetricsClient }\n"],"mappings":"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","ignoreList":[]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@adalo/metrics",
3
- "version": "0.1.122",
3
+ "version": "0.1.124",
4
4
  "description": "Reusable metrics utilities for Node.js and Laravel apps",
5
5
  "main": "lib/index.js",
6
6
  "types": "lib/index.d.ts",
@@ -0,0 +1,237 @@
1
+ const {
2
+ getRedisClientType,
3
+ REDIS_V4,
4
+ IOREDIS,
5
+ REDIS_V3,
6
+ } = require('../redisUtils')
7
+
8
+ /**
9
+ * HealthCheckCache provides a shared cache layer for health check results.
10
+ * It uses Redis if available for cross-process sharing, with graceful fallback
11
+ * to in-memory cache if Redis is not configured or unavailable.
12
+ */
13
+ class HealthCheckCache {
14
+ /**
15
+ * @param {Object} options
16
+ * @param {any} [options.redisClient] - Redis client instance (optional)
17
+ * @param {string} [options.appName] - Application name for cache key
18
+ * @param {number} [options.cacheTtlMs=60000] - Cache TTL in milliseconds
19
+ */
20
+ constructor(options = {}) {
21
+ this.redisClient = options.redisClient || null
22
+ this.appName = options.appName || process.env.BUILD_APP_NAME || 'unknown-app'
23
+ this.cacheTtlMs = options.cacheTtlMs ?? 60 * 1000
24
+ this.cacheKey = `healthcheck:${this.appName}`
25
+
26
+ /** In-memory fallback cache */
27
+ this._memoryCache = null
28
+ this._memoryCacheTimestamp = null
29
+
30
+ if (this.redisClient) {
31
+ this._redisClientType = getRedisClientType(this.redisClient)
32
+ this._redisAvailable = true
33
+ } else {
34
+ this._redisAvailable = false
35
+ console.warn(
36
+ `[HealthCheckCache] Redis not configured for ${this.appName}, using in-memory cache only (not shared across processes)`
37
+ )
38
+ }
39
+ }
40
+
41
+ /**
42
+ * Checks if Redis is available and working.
43
+ * @returns {Promise<boolean>}
44
+ * @private
45
+ */
46
+ async _checkRedisAvailable() {
47
+ if (!this.redisClient || !this._redisAvailable) {
48
+ return false
49
+ }
50
+
51
+ try {
52
+ let pong
53
+ if (this._redisClientType === REDIS_V3) {
54
+ pong = await new Promise((resolve, reject) => {
55
+ this.redisClient.ping((err, result) => {
56
+ if (err) reject(err)
57
+ else resolve(result)
58
+ })
59
+ })
60
+ } else if (
61
+ this._redisClientType === REDIS_V4 ||
62
+ this._redisClientType === IOREDIS
63
+ ) {
64
+ pong = await this.redisClient.ping()
65
+ } else {
66
+ return false
67
+ }
68
+ return pong === 'PONG'
69
+ } catch (err) {
70
+ // Redis not available
71
+ if (this._redisAvailable) {
72
+ console.warn(
73
+ `[HealthCheckCache] Redis became unavailable: ${err.message}`
74
+ )
75
+ this._redisAvailable = false
76
+ }
77
+ return false
78
+ }
79
+ }
80
+
81
+ /**
82
+ * Gets cached health check result from Redis (if available) or in-memory cache.
83
+ * Throws error if Redis is configured but read fails (so caller can return proper error format).
84
+ * @returns {Promise<Object | null>} Cached result or null
85
+ * @throws {Error} If Redis is configured but read fails
86
+ */
87
+ async get() {
88
+ // If Redis is configured, we MUST read from it (don't fall back to memory)
89
+ if (this.redisClient) {
90
+ try {
91
+ let cachedStr
92
+ if (this._redisClientType === REDIS_V3) {
93
+ cachedStr = await new Promise((resolve, reject) => {
94
+ this.redisClient.get(this.cacheKey, (err, result) => {
95
+ if (err) reject(err)
96
+ else resolve(result)
97
+ })
98
+ })
99
+ } else if (
100
+ this._redisClientType === REDIS_V4 ||
101
+ this._redisClientType === IOREDIS
102
+ ) {
103
+ cachedStr = await this.redisClient.get(this.cacheKey)
104
+ }
105
+
106
+ if (cachedStr) {
107
+ try {
108
+ const cached = JSON.parse(cachedStr)
109
+ if (cached.result && cached.timestamp) {
110
+ const age = Date.now() - cached.timestamp
111
+ if (age < this.cacheTtlMs) {
112
+ // Also update in-memory cache as backup
113
+ this._memoryCache = cached.result
114
+ this._memoryCacheTimestamp = cached.timestamp
115
+ return cached.result
116
+ }
117
+ }
118
+ } catch (parseErr) {
119
+ console.warn(
120
+ `[HealthCheckCache] Failed to parse Redis cache:`,
121
+ parseErr.message
122
+ )
123
+ }
124
+ }
125
+ // No cache in Redis - return null (worker may not have run yet)
126
+ return null
127
+ } catch (redisErr) {
128
+ // Redis read failed - throw error so caller can return proper error format
129
+ this._redisAvailable = false
130
+ throw new Error(`Redis cache read failed: ${redisErr.message}`)
131
+ }
132
+ }
133
+
134
+ // No Redis configured - fall back to in-memory cache
135
+ if (this._memoryCache && this._memoryCacheTimestamp) {
136
+ const age = Date.now() - this._memoryCacheTimestamp
137
+ if (age < this.cacheTtlMs) {
138
+ return this._memoryCache
139
+ }
140
+ }
141
+
142
+ return null
143
+ }
144
+
145
+ /**
146
+ * Sets cached health check result in Redis (if available) and in-memory.
147
+ * @param {Object} result - Health check result to cache
148
+ * @returns {Promise<void>}
149
+ */
150
+ async set(result) {
151
+ const cacheData = {
152
+ result,
153
+ timestamp: Date.now(),
154
+ }
155
+
156
+ // Update in-memory cache
157
+ this._memoryCache = result
158
+ this._memoryCacheTimestamp = cacheData.timestamp
159
+
160
+ // Try to update Redis if available
161
+ if (await this._checkRedisAvailable()) {
162
+ try {
163
+ const cacheStr = JSON.stringify(cacheData)
164
+ const ttlSeconds = Math.ceil(this.cacheTtlMs / 1000) + 10
165
+
166
+ if (this._redisClientType === REDIS_V3) {
167
+ await new Promise((resolve, reject) => {
168
+ this.redisClient.setex(
169
+ this.cacheKey,
170
+ ttlSeconds,
171
+ cacheStr,
172
+ (err) => {
173
+ if (err) reject(err)
174
+ else resolve()
175
+ }
176
+ )
177
+ })
178
+ } else if (
179
+ this._redisClientType === REDIS_V4 ||
180
+ this._redisClientType === IOREDIS
181
+ ) {
182
+ await this.redisClient.setex(this.cacheKey, ttlSeconds, cacheStr)
183
+ }
184
+ } catch (redisErr) {
185
+ // Redis write failed, but in-memory cache is updated
186
+ console.warn(
187
+ `[HealthCheckCache] Redis write failed (in-memory cache updated):`,
188
+ redisErr.message
189
+ )
190
+ this._redisAvailable = false
191
+ }
192
+ }
193
+ }
194
+
195
+ /**
196
+ * Clears the cache (both Redis and in-memory).
197
+ * @returns {Promise<void>}
198
+ */
199
+ async clear() {
200
+ this._memoryCache = null
201
+ this._memoryCacheTimestamp = null
202
+
203
+ if (await this._checkRedisAvailable()) {
204
+ try {
205
+ if (this._redisClientType === REDIS_V3) {
206
+ await new Promise((resolve, reject) => {
207
+ this.redisClient.del(this.cacheKey, (err) => {
208
+ if (err) reject(err)
209
+ else resolve()
210
+ })
211
+ })
212
+ } else if (
213
+ this._redisClientType === REDIS_V4 ||
214
+ this._redisClientType === IOREDIS
215
+ ) {
216
+ await this.redisClient.del(this.cacheKey)
217
+ }
218
+ } catch (redisErr) {
219
+ console.warn(
220
+ `[HealthCheckCache] Redis clear failed:`,
221
+ redisErr.message
222
+ )
223
+ }
224
+ }
225
+ }
226
+
227
+ /**
228
+ * Checks if Redis is configured and available.
229
+ * @returns {boolean}
230
+ */
231
+ isRedisAvailable() {
232
+ return this._redisAvailable && this.redisClient !== null
233
+ }
234
+ }
235
+
236
+ module.exports = { HealthCheckCache }
237
+