@forklaunch/core 0.6.5 → 0.7.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 (35) hide show
  1. package/lib/{openTelemetryCollector-CWrfzmmW.d.mts → openTelemetryCollector-CmZ3T_2T.d.mts} +9 -1
  2. package/lib/{openTelemetryCollector-CWrfzmmW.d.ts → openTelemetryCollector-CmZ3T_2T.d.ts} +9 -1
  3. package/lib/src/cache/index.d.mts +210 -24
  4. package/lib/src/cache/index.d.ts +210 -24
  5. package/lib/src/cache/index.js +364 -31
  6. package/lib/src/cache/index.js.map +1 -1
  7. package/lib/src/cache/index.mjs +374 -31
  8. package/lib/src/cache/index.mjs.map +1 -1
  9. package/lib/src/controllers/index.d.mts +10 -0
  10. package/lib/src/controllers/index.d.ts +10 -0
  11. package/lib/src/http/index.d.mts +51 -4
  12. package/lib/src/http/index.d.ts +51 -4
  13. package/lib/src/http/index.js +24 -1
  14. package/lib/src/http/index.js.map +1 -1
  15. package/lib/src/http/index.mjs +23 -1
  16. package/lib/src/http/index.mjs.map +1 -1
  17. package/lib/src/{dtoMapper → mappers}/index.d.mts +3 -3
  18. package/lib/src/{dtoMapper → mappers}/index.d.ts +3 -3
  19. package/lib/src/{dtoMapper → mappers}/index.js +12 -12
  20. package/lib/src/mappers/index.js.map +1 -0
  21. package/lib/src/{dtoMapper → mappers}/index.mjs +8 -8
  22. package/lib/src/mappers/index.mjs.map +1 -0
  23. package/lib/src/persistence/index.d.mts +120 -0
  24. package/lib/src/persistence/index.d.ts +120 -0
  25. package/lib/src/persistence/index.js +152 -0
  26. package/lib/src/persistence/index.js.map +1 -0
  27. package/lib/src/persistence/index.mjs +127 -0
  28. package/lib/src/persistence/index.mjs.map +1 -0
  29. package/lib/src/services/index.d.mts +3 -2
  30. package/lib/src/services/index.d.ts +3 -2
  31. package/lib/src/services/index.js.map +1 -1
  32. package/lib/src/services/index.mjs.map +1 -1
  33. package/package.json +15 -9
  34. package/lib/src/dtoMapper/index.js.map +0 -1
  35. package/lib/src/dtoMapper/index.mjs.map +0 -1
@@ -1,104 +1,447 @@
1
+ // src/cache/cacheKey.ts
2
+ var createCacheKey = (cacheKeyPrefix) => (id) => {
3
+ return `${cacheKeyPrefix}:${id}`;
4
+ };
5
+
1
6
  // src/cache/redisTtlCache.ts
2
7
  import { createClient } from "redis";
8
+
9
+ // src/http/middleware/request/cors.middleware.ts
10
+ import corsMiddleware from "cors";
11
+
12
+ // src/http/middleware/request/createContext.middleware.ts
13
+ import { trace } from "@opentelemetry/api";
14
+ import { v4 } from "uuid";
15
+
16
+ // src/http/telemetry/constants.ts
17
+ import {
18
+ ATTR_HTTP_REQUEST_METHOD,
19
+ ATTR_HTTP_RESPONSE_STATUS_CODE,
20
+ ATTR_HTTP_ROUTE,
21
+ ATTR_SERVICE_NAME
22
+ } from "@opentelemetry/semantic-conventions";
23
+
24
+ // src/http/guards/isExpressLikeSchemaHandler.ts
25
+ import { extractArgumentNames } from "@forklaunch/common";
26
+
27
+ // src/http/middleware/request/auth.middleware.ts
28
+ import { jwtVerify } from "jose";
29
+
30
+ // src/services/getEnvVar.ts
31
+ function getEnvVar(name) {
32
+ const value = process.env[name];
33
+ return value;
34
+ }
35
+
36
+ // src/http/telemetry/openTelemetryCollector.ts
37
+ import { HyperExpressInstrumentation } from "@forklaunch/opentelemetry-instrumentation-hyper-express";
38
+ import {
39
+ metrics
40
+ } from "@opentelemetry/api";
41
+ import { OTLPLogExporter } from "@opentelemetry/exporter-logs-otlp-http";
42
+ import { OTLPMetricExporter } from "@opentelemetry/exporter-metrics-otlp-http";
43
+ import { OTLPTraceExporter } from "@opentelemetry/exporter-trace-otlp-http";
44
+ import { ExpressInstrumentation } from "@opentelemetry/instrumentation-express";
45
+ import { HttpInstrumentation } from "@opentelemetry/instrumentation-http";
46
+ import { resourceFromAttributes } from "@opentelemetry/resources";
47
+ import { BatchLogRecordProcessor } from "@opentelemetry/sdk-logs";
48
+ import { PeriodicExportingMetricReader } from "@opentelemetry/sdk-metrics";
49
+ import { NodeSDK } from "@opentelemetry/sdk-node";
50
+ import {
51
+ ATTR_SERVICE_NAME as ATTR_SERVICE_NAME2
52
+ } from "@opentelemetry/semantic-conventions";
53
+ import dotenv from "dotenv";
54
+
55
+ // src/http/guards/isForklaunchRequest.ts
56
+ function isForklaunchRequest(request) {
57
+ return request != null && typeof request === "object" && "contractDetails" in request;
58
+ }
59
+
60
+ // src/http/telemetry/pinoLogger.ts
61
+ import { isNever } from "@forklaunch/common";
62
+ import { trace as trace2 } from "@opentelemetry/api";
63
+ import { logs } from "@opentelemetry/api-logs";
64
+ import pino from "pino";
65
+ import PinoPretty from "pino-pretty";
66
+
67
+ // src/http/telemetry/openTelemetryCollector.ts
68
+ dotenv.config({ path: getEnvVar("ENV_FILE_PATH") });
69
+ new NodeSDK({
70
+ resource: resourceFromAttributes({
71
+ [ATTR_SERVICE_NAME2]: getEnvVar("OTEL_SERVICE_NAME")
72
+ }),
73
+ traceExporter: new OTLPTraceExporter({
74
+ url: `${getEnvVar("OTEL_EXPORTER_OTLP_ENDPOINT") ?? "http://localhost:4318"}/v1/traces`
75
+ }),
76
+ metricReader: new PeriodicExportingMetricReader({
77
+ exporter: new OTLPMetricExporter({
78
+ url: `${getEnvVar("OTEL_EXPORTER_OTLP_ENDPOINT") ?? "http://localhost:4318"}/v1/metrics`
79
+ }),
80
+ exportIntervalMillis: 5e3
81
+ }),
82
+ logRecordProcessors: [
83
+ new BatchLogRecordProcessor(
84
+ new OTLPLogExporter({
85
+ url: `${getEnvVar("OTEL_EXPORTER_OTLP_ENDPOINT") ?? "http://localhost:4318"}/v1/logs`
86
+ })
87
+ )
88
+ ],
89
+ instrumentations: [
90
+ new HttpInstrumentation({
91
+ applyCustomAttributesOnSpan: (span, request) => {
92
+ span.setAttribute(
93
+ "service.name",
94
+ getEnvVar("OTEL_SERVICE_NAME") ?? "unknown"
95
+ );
96
+ if (isForklaunchRequest(request)) {
97
+ span.setAttribute("api.name", request.contractDetails?.name);
98
+ }
99
+ }
100
+ }),
101
+ new ExpressInstrumentation(),
102
+ new HyperExpressInstrumentation()
103
+ ]
104
+ }).start();
105
+ var httpRequestsTotalCounter = metrics.getMeter(getEnvVar("OTEL_SERVICE_NAME") || "unknown").createCounter("http_requests_total", {
106
+ description: "Number of HTTP requests"
107
+ });
108
+ var httpServerDurationHistogram = metrics.getMeter(getEnvVar("OTEL_SERVICE_NAME") || "unknown").createHistogram("http_server_duration", {
109
+ description: "Duration of HTTP server requests",
110
+ unit: "s"
111
+ });
112
+
113
+ // src/http/middleware/request/parse.middleware.ts
114
+ import {
115
+ prettyPrintParseErrors
116
+ } from "@forklaunch/validator";
117
+
118
+ // src/http/middleware/response/parse.middleware.ts
119
+ import {
120
+ prettyPrintParseErrors as prettyPrintParseErrors2
121
+ } from "@forklaunch/validator";
122
+
123
+ // src/http/telemetry/recordMetric.ts
124
+ import {
125
+ ATTR_HTTP_REQUEST_METHOD as ATTR_HTTP_REQUEST_METHOD3,
126
+ ATTR_HTTP_RESPONSE_STATUS_CODE as ATTR_HTTP_RESPONSE_STATUS_CODE3,
127
+ ATTR_HTTP_ROUTE as ATTR_HTTP_ROUTE3,
128
+ ATTR_SERVICE_NAME as ATTR_SERVICE_NAME3
129
+ } from "@opentelemetry/semantic-conventions";
130
+
131
+ // src/services/configInjector.ts
132
+ import { extractArgumentNames as extractArgumentNames2, isNever as isNever2 } from "@forklaunch/common";
133
+ import {
134
+ prettyPrintParseErrors as prettyPrintParseErrors3
135
+ } from "@forklaunch/validator";
136
+
137
+ // src/http/telemetry/evaluateTelemetryOptions.ts
138
+ function evaluateTelemetryOptions(telemetryOptions) {
139
+ return {
140
+ enabled: typeof telemetryOptions.enabled === "boolean" ? {
141
+ metrics: telemetryOptions.enabled,
142
+ tracing: telemetryOptions.enabled,
143
+ logging: telemetryOptions.enabled
144
+ } : {
145
+ metrics: telemetryOptions.enabled.metrics,
146
+ tracing: telemetryOptions.enabled.tracing,
147
+ logging: telemetryOptions.enabled.logging
148
+ },
149
+ level: telemetryOptions.level
150
+ };
151
+ }
152
+
153
+ // src/cache/redisTtlCache.ts
3
154
  var RedisTtlCache = class {
4
155
  /**
5
156
  * Creates an instance of RedisTtlCache.
6
157
  *
7
- * @param {number} ttlMilliseconds - The default TTL in milliseconds.
8
- * @param {RedisClientOptions} [hostingOptions] - The Redis client options.
158
+ * @param {number} ttlMilliseconds - The default Time-To-Live in milliseconds for cache entries
159
+ * @param {OpenTelemetryCollector<MetricsDefinition>} openTelemetryCollector - Collector for OpenTelemetry metrics
160
+ * @param {RedisClientOptions} hostingOptions - Configuration options for the Redis client
161
+ * @param {TelemetryOptions} telemetryOptions - Configuration options for telemetry
9
162
  */
10
- constructor(ttlMilliseconds, openTelemetryCollector, hostingOptions) {
163
+ constructor(ttlMilliseconds, openTelemetryCollector, hostingOptions, telemetryOptions) {
11
164
  this.ttlMilliseconds = ttlMilliseconds;
12
165
  this.openTelemetryCollector = openTelemetryCollector;
166
+ this.telemetryOptions = evaluateTelemetryOptions(telemetryOptions);
13
167
  this.client = createClient(hostingOptions);
14
- this.client.on("error", (err) => openTelemetryCollector.error(err));
15
- this.client.connect().catch(openTelemetryCollector.error);
168
+ if (this.telemetryOptions.enabled.logging) {
169
+ this.client.on("error", (err) => this.openTelemetryCollector.error(err));
170
+ this.client.connect().catch(this.openTelemetryCollector.error);
171
+ }
16
172
  }
17
173
  client;
174
+ telemetryOptions;
175
+ /**
176
+ * Parses a raw Redis reply into the expected type.
177
+ * Handles null values, arrays, buffers, and JSON strings.
178
+ *
179
+ * @template T - The expected type of the parsed value
180
+ * @param {RedisCommandRawReply} value - The raw value from Redis to parse
181
+ * @returns {T} The parsed value cast to type T
182
+ */
183
+ parseValue(value) {
184
+ if (value == null) {
185
+ return null;
186
+ }
187
+ if (Array.isArray(value)) {
188
+ return value.map((v) => this.parseValue(v));
189
+ }
190
+ if (Buffer.isBuffer(value)) {
191
+ return value.toJSON();
192
+ }
193
+ switch (typeof value) {
194
+ case "object":
195
+ case "string":
196
+ return JSON.parse(value);
197
+ case "number":
198
+ return value;
199
+ }
200
+ }
18
201
  /**
19
202
  * Puts a record into the Redis cache.
20
203
  *
21
- * @param {TtlCacheRecord} param0 - The cache record to put into the cache.
22
- * @returns {Promise<void>} - A promise that resolves when the record is put into the cache.
204
+ * @template T - The type of value being cached
205
+ * @param {TtlCacheRecord<T>} param0 - The cache record containing key, value and optional TTL
206
+ * @param {string} param0.key - The key to store the value under
207
+ * @param {T} param0.value - The value to cache
208
+ * @param {number} [param0.ttlMilliseconds] - Optional TTL in milliseconds, defaults to constructor value
209
+ * @returns {Promise<void>} A promise that resolves when the value is cached
23
210
  */
24
211
  async putRecord({
25
212
  key,
26
213
  value,
27
214
  ttlMilliseconds = this.ttlMilliseconds
28
215
  }) {
216
+ if (this.telemetryOptions.enabled.logging) {
217
+ this.openTelemetryCollector.info(`Putting record into cache: ${key}`);
218
+ }
29
219
  await this.client.set(key, JSON.stringify(value), {
30
220
  PX: ttlMilliseconds
31
221
  });
32
222
  }
223
+ /**
224
+ * Puts multiple records into the Redis cache in a single transaction.
225
+ *
226
+ * @template T - The type of values being cached
227
+ * @param {TtlCacheRecord<T>[]} cacheRecords - Array of cache records to store
228
+ * @returns {Promise<void>} A promise that resolves when all values are cached
229
+ */
230
+ async putBatchRecords(cacheRecords) {
231
+ let multiCommand = this.client.multi();
232
+ for (const { key, value, ttlMilliseconds } of cacheRecords) {
233
+ multiCommand = multiCommand.set(key, JSON.stringify(value), {
234
+ PX: ttlMilliseconds || this.ttlMilliseconds
235
+ });
236
+ }
237
+ await multiCommand.exec();
238
+ }
239
+ /**
240
+ * Adds a value to the left end of a Redis list.
241
+ *
242
+ * @template T - The type of value being enqueued
243
+ * @param {string} queueName - The name of the Redis list
244
+ * @param {T} value - The value to add to the list
245
+ * @returns {Promise<void>} A promise that resolves when the value is enqueued
246
+ */
247
+ async enqueueRecord(queueName, value) {
248
+ await this.client.lPush(queueName, JSON.stringify(value));
249
+ }
250
+ /**
251
+ * Adds multiple values to the left end of a Redis list in a single transaction.
252
+ *
253
+ * @template T - The type of values being enqueued
254
+ * @param {string} queueName - The name of the Redis list
255
+ * @param {T[]} values - Array of values to add to the list
256
+ * @returns {Promise<void>} A promise that resolves when all values are enqueued
257
+ */
258
+ async enqueueBatchRecords(queueName, values) {
259
+ let multiCommand = this.client.multi();
260
+ for (const value of values) {
261
+ multiCommand = multiCommand.lPush(queueName, JSON.stringify(value));
262
+ }
263
+ await multiCommand.exec();
264
+ }
33
265
  /**
34
266
  * Deletes a record from the Redis cache.
35
267
  *
36
- * @param {string} cacheRecordKey - The key of the cache record to delete.
37
- * @returns {Promise<void>} - A promise that resolves when the record is deleted from the cache.
268
+ * @param {string} cacheRecordKey - The key of the record to delete
269
+ * @returns {Promise<void>} A promise that resolves when the record is deleted
38
270
  */
39
271
  async deleteRecord(cacheRecordKey) {
40
272
  await this.client.del(cacheRecordKey);
41
273
  }
274
+ /**
275
+ * Deletes multiple records from the Redis cache in a single transaction.
276
+ *
277
+ * @param {string[]} cacheRecordKeys - Array of keys to delete
278
+ * @returns {Promise<void>} A promise that resolves when all records are deleted
279
+ */
280
+ async deleteBatchRecords(cacheRecordKeys) {
281
+ let multiCommand = this.client.multi();
282
+ for (const key of cacheRecordKeys) {
283
+ multiCommand = multiCommand.del(key);
284
+ }
285
+ await multiCommand.exec();
286
+ }
287
+ /**
288
+ * Removes and returns the rightmost element from a Redis list.
289
+ *
290
+ * @template T - The type of value being dequeued
291
+ * @param {string} queueName - The name of the Redis list
292
+ * @returns {Promise<T>} A promise that resolves with the dequeued value
293
+ * @throws {Error} If the queue is empty
294
+ */
295
+ async dequeueRecord(queueName) {
296
+ const value = await this.client.rPop(queueName);
297
+ if (value === null) {
298
+ throw new Error(`Queue is empty: ${queueName}`);
299
+ }
300
+ return JSON.parse(value);
301
+ }
302
+ /**
303
+ * Removes and returns multiple elements from the right end of a Redis list.
304
+ *
305
+ * @template T - The type of values being dequeued
306
+ * @param {string} queueName - The name of the Redis list
307
+ * @param {number} pageSize - Maximum number of elements to dequeue
308
+ * @returns {Promise<T[]>} A promise that resolves with an array of dequeued values
309
+ */
310
+ async dequeueBatchRecords(queueName, pageSize) {
311
+ let multiCommand = this.client.multi();
312
+ for (let i = 0; i < pageSize; i++) {
313
+ multiCommand = multiCommand.rPop(queueName);
314
+ }
315
+ const values = await multiCommand.exec();
316
+ return values.map((value) => this.parseValue(value));
317
+ }
42
318
  /**
43
319
  * Reads a record from the Redis cache.
44
320
  *
45
- * @param {string} cacheRecordKey - The key of the cache record to read.
46
- * @returns {Promise<TtlCacheRecord>} - A promise that resolves with the cache record.
47
- * @throws {Error} - Throws an error if the record is not found.
321
+ * @template T - The type of value being read
322
+ * @param {string} cacheRecordKey - The key of the record to read
323
+ * @returns {Promise<TtlCacheRecord<T>>} A promise that resolves with the cache record
324
+ * @throws {Error} If the record is not found
48
325
  */
49
326
  async readRecord(cacheRecordKey) {
50
- const value = await this.client.get(cacheRecordKey);
327
+ const [value, ttl] = await this.client.multi().get(cacheRecordKey).ttl(cacheRecordKey).exec();
51
328
  if (value === null) {
52
329
  throw new Error(`Record not found for key: ${cacheRecordKey}`);
53
330
  }
54
- const ttl = await this.client.ttl(cacheRecordKey);
55
331
  return {
56
332
  key: cacheRecordKey,
57
- value: JSON.parse(value),
58
- ttlMilliseconds: ttl * 1e3
333
+ value: this.parseValue(value),
334
+ ttlMilliseconds: this.parseValue(ttl) * 1e3
59
335
  };
60
336
  }
61
337
  /**
62
- * Lists the keys in the Redis cache that match a pattern prefix.
338
+ * Reads multiple records from the Redis cache.
339
+ *
340
+ * @template T - The type of values being read
341
+ * @param {string[] | string} cacheRecordKeysOrPrefix - Array of keys to read, or a prefix pattern
342
+ * @returns {Promise<TtlCacheRecord<T>[]>} A promise that resolves with an array of cache records
343
+ */
344
+ async readBatchRecords(cacheRecordKeysOrPrefix) {
345
+ const keys = Array.isArray(cacheRecordKeysOrPrefix) ? cacheRecordKeysOrPrefix : await this.client.keys(cacheRecordKeysOrPrefix + "*");
346
+ let multiCommand = this.client.multi();
347
+ for (const key of keys) {
348
+ multiCommand = multiCommand.get(key);
349
+ multiCommand = multiCommand.ttl(key);
350
+ }
351
+ const values = await multiCommand.exec();
352
+ return values.reduce((acc, value, index) => {
353
+ if (index % 2 === 0) {
354
+ acc.push({
355
+ key: keys[index / 2],
356
+ value: this.parseValue(value),
357
+ ttlMilliseconds: this.parseValue(values[index + 1]) * 1e3
358
+ });
359
+ }
360
+ return acc;
361
+ }, []);
362
+ }
363
+ /**
364
+ * Lists all keys in the Redis cache that match a pattern prefix.
63
365
  *
64
- * @param {string} pattern_prefix - The pattern prefix to match.
65
- * @returns {Promise<string[]>} - A promise that resolves with an array of keys matching the pattern prefix.
366
+ * @param {string} pattern_prefix - The prefix pattern to match keys against
367
+ * @returns {Promise<string[]>} A promise that resolves with an array of matching keys
66
368
  */
67
369
  async listKeys(pattern_prefix) {
68
370
  const keys = await this.client.keys(pattern_prefix + "*");
69
371
  return keys;
70
372
  }
71
373
  /**
72
- * Peeks at a record in the Redis cache to check if it exists.
374
+ * Checks if a record exists in the Redis cache.
73
375
  *
74
- * @param {string} cacheRecordKey - The key of the cache record to peek at.
75
- * @returns {Promise<boolean>} - A promise that resolves with a boolean indicating if the record exists.
376
+ * @param {string} cacheRecordKey - The key to check
377
+ * @returns {Promise<boolean>} A promise that resolves with true if the record exists, false otherwise
76
378
  */
77
379
  async peekRecord(cacheRecordKey) {
78
380
  const result = await this.client.exists(cacheRecordKey);
79
381
  return result === 1;
80
382
  }
81
383
  /**
82
- * Disconnects the Redis client.
384
+ * Checks if multiple records exist in the Redis cache.
83
385
  *
84
- * @returns {Promise<void>} - A promise that resolves when the client is disconnected.
386
+ * @param {string[] | string} cacheRecordKeysOrPrefix - Array of keys to check, or a prefix pattern
387
+ * @returns {Promise<boolean[]>} A promise that resolves with an array of existence booleans
388
+ */
389
+ async peekBatchRecords(cacheRecordKeysOrPrefix) {
390
+ const keys = Array.isArray(cacheRecordKeysOrPrefix) ? cacheRecordKeysOrPrefix : await this.client.keys(cacheRecordKeysOrPrefix + "*");
391
+ let multiCommand = this.client.multi();
392
+ for (const key of keys) {
393
+ multiCommand = multiCommand.exists(key);
394
+ }
395
+ const results = await multiCommand.exec();
396
+ return results.map((result) => result === 1);
397
+ }
398
+ /**
399
+ * Peeks at a record in the Redis cache.
400
+ *
401
+ * @template T - The type of value being peeked at
402
+ * @param {string} queueName - The name of the Redis queue
403
+ * @returns {Promise<T>} A promise that resolves with the peeked value
404
+ */
405
+ async peekQueueRecord(queueName) {
406
+ const value = await this.client.lRange(queueName, 0, 0);
407
+ return this.parseValue(value[0]);
408
+ }
409
+ /**
410
+ * Peeks at multiple records in the Redis cache.
411
+ *
412
+ * @template T - The type of values being peeked at
413
+ * @param {string} queueName - The name of the Redis queue
414
+ * @param {number} pageSize - The number of records to peek at
415
+ * @returns {Promise<T[]>} A promise that resolves with an array of peeked values
416
+ */
417
+ async peekQueueRecords(queueName, pageSize) {
418
+ const values = await this.client.lRange(queueName, 0, pageSize - 1);
419
+ return values.map((value) => this.parseValue(value));
420
+ }
421
+ /**
422
+ * Gracefully disconnects from the Redis server.
423
+ *
424
+ * @returns {Promise<void>} A promise that resolves when the connection is closed
85
425
  */
86
426
  async disconnect() {
87
427
  await this.client.quit();
88
428
  }
89
429
  /**
90
- * Gets the default TTL (Time-To-Live) in milliseconds.
430
+ * Gets the default Time-To-Live value in milliseconds.
91
431
  *
92
- * @returns {number} - The TTL in milliseconds.
432
+ * @returns {number} The default TTL in milliseconds
93
433
  */
94
434
  getTtlMilliseconds() {
95
435
  return this.ttlMilliseconds;
96
436
  }
97
- };
98
-
99
- // src/cache/utils/cacheKey.ts
100
- var createCacheKey = (cacheKeyPrefix) => (id) => {
101
- return `${cacheKeyPrefix}:${id}`;
437
+ /**
438
+ * Gets the underlying Redis client instance.
439
+ *
440
+ * @returns {typeof this.client} The Redis client instance
441
+ */
442
+ getClient() {
443
+ return this.client;
444
+ }
102
445
  };
103
446
  export {
104
447
  RedisTtlCache,