@forklaunch/core 0.8.2 → 0.8.4

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