@forklaunch/core 0.16.1 → 0.17.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.
package/lib/http/index.js CHANGED
@@ -31,6 +31,7 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
31
31
  var http_exports = {};
32
32
  __export(http_exports, {
33
33
  ATTR_API_NAME: () => ATTR_API_NAME,
34
+ ATTR_APPLICATION_ID: () => ATTR_APPLICATION_ID,
34
35
  ATTR_CORRELATION_ID: () => ATTR_CORRELATION_ID,
35
36
  ATTR_HTTP_REQUEST_METHOD: () => import_semantic_conventions.ATTR_HTTP_REQUEST_METHOD,
36
37
  ATTR_HTTP_RESPONSE_STATUS_CODE: () => import_semantic_conventions.ATTR_HTTP_RESPONSE_STATUS_CODE,
@@ -42,6 +43,7 @@ __export(http_exports, {
42
43
  OPENAPI_DEFAULT_VERSION: () => OPENAPI_DEFAULT_VERSION,
43
44
  OpenTelemetryCollector: () => OpenTelemetryCollector,
44
45
  PinoLogger: () => PinoLogger,
46
+ createContext: () => createContext,
45
47
  createHmacToken: () => createHmacToken,
46
48
  delete_: () => delete_,
47
49
  discriminateAuthMethod: () => discriminateAuthMethod,
@@ -49,6 +51,8 @@ __export(http_exports, {
49
51
  discriminateResponseBodies: () => discriminateResponseBodies,
50
52
  enrichExpressLikeSend: () => enrichExpressLikeSend,
51
53
  evaluateTelemetryOptions: () => evaluateTelemetryOptions,
54
+ extractRouteHandlers: () => extractRouteHandlers,
55
+ generateHmacAuthHeaders: () => generateHmacAuthHeaders,
52
56
  generateMcpServer: () => generateMcpServer,
53
57
  generateOpenApiSpecs: () => generateOpenApiSpecs,
54
58
  get: () => get,
@@ -103,12 +107,7 @@ function cors(corsOptions) {
103
107
  }
104
108
 
105
109
  // src/http/router/expressLikeRouter.ts
106
- var import_common9 = require("@forklaunch/common");
107
-
108
- // src/http/guards/hasVersionedSchema.ts
109
- function hasVersionedSchema(contractDetails) {
110
- return typeof contractDetails === "object" && contractDetails !== null && "versions" in contractDetails && contractDetails.versions !== null;
111
- }
110
+ var import_common11 = require("@forklaunch/common");
112
111
 
113
112
  // src/http/guards/isForklaunchRouter.ts
114
113
  function isForklaunchRouter(maybeForklaunchRouter) {
@@ -138,6 +137,192 @@ function isForklaunchExpressLikeRouter(maybeForklaunchExpressLikeRouter) {
138
137
  ) && "basePath" in maybeForklaunchExpressLikeRouter && "internal" in maybeForklaunchExpressLikeRouter;
139
138
  }
140
139
 
140
+ // src/http/guards/isSdkHandler.ts
141
+ function isSdkHandler(handler) {
142
+ return typeof handler === "object" && handler !== null && "_path" in handler && "_method" in handler && "contractDetails" in handler;
143
+ }
144
+
145
+ // src/http/guards/isTypedHandler.ts
146
+ function isTypedHandler(maybeTypedHandler) {
147
+ return maybeTypedHandler != null && typeof maybeTypedHandler === "object" && "_typedHandler" in maybeTypedHandler && maybeTypedHandler._typedHandler === true;
148
+ }
149
+
150
+ // src/http/middleware/request/createContext.middleware.ts
151
+ var import_common2 = require("@forklaunch/common");
152
+ var import_api = require("@opentelemetry/api");
153
+ var import_uuid = require("uuid");
154
+
155
+ // src/http/telemetry/constants.ts
156
+ var import_semantic_conventions = require("@opentelemetry/semantic-conventions");
157
+ var ATTR_API_NAME = "api.name";
158
+ var ATTR_CORRELATION_ID = "correlation.id";
159
+ var ATTR_APPLICATION_ID = "application_id";
160
+
161
+ // src/http/middleware/request/createContext.middleware.ts
162
+ function createContext(schemaValidator) {
163
+ return function setContext(req, res, next) {
164
+ req.schemaValidator = schemaValidator;
165
+ let correlationId = (0, import_uuid.v4)();
166
+ if (req.headers["x-correlation-id"]) {
167
+ correlationId = req.headers["x-correlation-id"];
168
+ }
169
+ res.setHeader("x-correlation-id", correlationId);
170
+ req.context = {
171
+ correlationId
172
+ };
173
+ const span = import_api.trace.getSpan(import_api.context.active());
174
+ if (span != null) {
175
+ req.context.span = span;
176
+ req.context.span?.setAttribute(ATTR_CORRELATION_ID, correlationId);
177
+ req.context.span?.setAttribute(
178
+ import_semantic_conventions.ATTR_SERVICE_NAME,
179
+ (0, import_common2.getEnvVar)("OTEL_SERVICE_NAME")
180
+ );
181
+ }
182
+ next?.();
183
+ };
184
+ }
185
+
186
+ // src/http/router/discriminateBody.ts
187
+ function discriminateBody(schemaValidator, body) {
188
+ if (body == null) {
189
+ return void 0;
190
+ }
191
+ const maybeTypedBody = body;
192
+ if ("text" in maybeTypedBody && maybeTypedBody.text != null) {
193
+ return {
194
+ contentType: maybeTypedBody.contentType ?? "text/plain",
195
+ parserType: "text",
196
+ schema: maybeTypedBody.text
197
+ };
198
+ } else if ("json" in maybeTypedBody && maybeTypedBody.json != null) {
199
+ return {
200
+ contentType: maybeTypedBody.contentType ?? "application/json",
201
+ parserType: "json",
202
+ schema: maybeTypedBody.json
203
+ };
204
+ } else if ("file" in maybeTypedBody && maybeTypedBody.file != null) {
205
+ return {
206
+ contentType: maybeTypedBody.contentType ?? "application/octet-stream",
207
+ parserType: "file",
208
+ schema: maybeTypedBody.file
209
+ };
210
+ } else if ("multipartForm" in maybeTypedBody && maybeTypedBody.multipartForm != null) {
211
+ return {
212
+ contentType: maybeTypedBody.contentType ?? "multipart/form-data",
213
+ parserType: "multipart",
214
+ schema: maybeTypedBody.multipartForm
215
+ };
216
+ } else if ("urlEncodedForm" in maybeTypedBody && maybeTypedBody.urlEncodedForm != null) {
217
+ return {
218
+ contentType: maybeTypedBody.contentType ?? "application/x-www-form-urlencoded",
219
+ parserType: "urlEncoded",
220
+ schema: maybeTypedBody.urlEncodedForm
221
+ };
222
+ } else if ("schema" in maybeTypedBody && maybeTypedBody.schema != null) {
223
+ return {
224
+ contentType: maybeTypedBody.contentType ?? "application/json",
225
+ parserType: "text",
226
+ schema: maybeTypedBody.schema
227
+ };
228
+ } else if (schemaValidator.isInstanceOf(
229
+ maybeTypedBody,
230
+ schemaValidator.string
231
+ )) {
232
+ return {
233
+ contentType: "text/plain",
234
+ parserType: "text",
235
+ schema: maybeTypedBody
236
+ };
237
+ } else if (schemaValidator.openapi(maybeTypedBody).format === "binary") {
238
+ return {
239
+ contentType: "application/octet-stream",
240
+ parserType: "file",
241
+ schema: maybeTypedBody
242
+ };
243
+ } else {
244
+ return {
245
+ contentType: "application/json",
246
+ parserType: "json",
247
+ schema: maybeTypedBody
248
+ };
249
+ }
250
+ }
251
+ function discriminateResponseBodies(schemaValidator, responses) {
252
+ const discriminatedResponses = {};
253
+ for (const [statusCode, response] of Object.entries(responses)) {
254
+ if (response != null && typeof response === "object") {
255
+ if ("json" in response && response.json != null) {
256
+ discriminatedResponses[Number(statusCode)] = {
257
+ contentType: ("contentType" in response && typeof response.contentType === "string" ? response.contentType : "application/json") ?? "application/json",
258
+ parserType: "json",
259
+ schema: response.json
260
+ };
261
+ } else if ("schema" in response && response.schema != null) {
262
+ discriminatedResponses[Number(statusCode)] = {
263
+ contentType: ("contentType" in response && typeof response.contentType === "string" ? response.contentType : "application/json") ?? "application/json",
264
+ parserType: "text",
265
+ schema: response.schema
266
+ };
267
+ } else if ("text" in response && response.text != null) {
268
+ discriminatedResponses[Number(statusCode)] = {
269
+ contentType: ("contentType" in response && typeof response.contentType === "string" ? response.contentType : "text/plain") ?? "text/plain",
270
+ parserType: "text",
271
+ schema: response.text
272
+ };
273
+ } else if ("file" in response && response.file != null) {
274
+ discriminatedResponses[Number(statusCode)] = {
275
+ contentType: ("contentType" in response && typeof response.contentType === "string" ? response.contentType : "application/octet-stream") ?? "application/octet-stream",
276
+ parserType: "file",
277
+ schema: response.file
278
+ };
279
+ } else if ("event" in response && response.event != null) {
280
+ discriminatedResponses[Number(statusCode)] = {
281
+ contentType: ("contentType" in response && typeof response.contentType === "string" ? response.contentType : "text/event-stream") ?? "text/event-stream",
282
+ parserType: "serverSentEvent",
283
+ schema: response.event
284
+ };
285
+ } else if (schemaValidator.isInstanceOf(
286
+ response,
287
+ schemaValidator.string
288
+ )) {
289
+ discriminatedResponses[Number(statusCode)] = {
290
+ contentType: "text/plain",
291
+ parserType: "text",
292
+ schema: response
293
+ };
294
+ } else if (schemaValidator.openapi(response).format === "binary") {
295
+ discriminatedResponses[Number(statusCode)] = {
296
+ contentType: "application/octet-stream",
297
+ parserType: "file",
298
+ schema: response
299
+ };
300
+ } else {
301
+ discriminatedResponses[Number(statusCode)] = {
302
+ contentType: "application/json",
303
+ parserType: "json",
304
+ schema: response
305
+ };
306
+ }
307
+ } else {
308
+ discriminatedResponses[Number(statusCode)] = {
309
+ contentType: "application/json",
310
+ parserType: "json",
311
+ schema: response
312
+ };
313
+ }
314
+ }
315
+ return discriminatedResponses;
316
+ }
317
+
318
+ // src/http/router/routerSharedLogic.ts
319
+ var import_common10 = require("@forklaunch/common");
320
+
321
+ // src/http/guards/hasVersionedSchema.ts
322
+ function hasVersionedSchema(contractDetails) {
323
+ return typeof contractDetails === "object" && contractDetails !== null && "versions" in contractDetails && contractDetails.versions !== null;
324
+ }
325
+
141
326
  // src/http/guards/isPathParamContractDetails.ts
142
327
  function isPathParamHttpContractDetails(maybePathParamHttpContractDetails) {
143
328
  return maybePathParamHttpContractDetails != null && typeof maybePathParamHttpContractDetails === "object" && "name" in maybePathParamHttpContractDetails && "summary" in maybePathParamHttpContractDetails && maybePathParamHttpContractDetails.name != null && maybePathParamHttpContractDetails.summary != null && ("responses" in maybePathParamHttpContractDetails && maybePathParamHttpContractDetails.responses != null || "versions" in maybePathParamHttpContractDetails && typeof maybePathParamHttpContractDetails.versions === "object" && maybePathParamHttpContractDetails.versions != null && Object.values(maybePathParamHttpContractDetails.versions).every(
@@ -152,25 +337,16 @@ function isHttpContractDetails(maybeContractDetails) {
152
337
  ));
153
338
  }
154
339
 
155
- // src/http/guards/isSdkHandler.ts
156
- function isSdkHandler(handler) {
157
- return typeof handler === "object" && handler !== null && "_path" in handler && "_method" in handler && "contractDetails" in handler;
158
- }
159
-
160
- // src/http/guards/isTypedHandler.ts
161
- function isTypedHandler(maybeTypedHandler) {
162
- return maybeTypedHandler != null && typeof maybeTypedHandler === "object" && "_typedHandler" in maybeTypedHandler && maybeTypedHandler._typedHandler === true;
163
- }
164
-
165
340
  // src/http/middleware/request/auth.middleware.ts
166
- var import_common3 = require("@forklaunch/common");
341
+ var import_common6 = require("@forklaunch/common");
167
342
  var import_validator = require("@forklaunch/validator");
168
343
 
169
344
  // src/http/discriminateAuthMethod.ts
345
+ var import_common4 = require("@forklaunch/common");
170
346
  var import_jose = require("jose");
171
347
 
172
348
  // src/http/createHmacToken.ts
173
- var import_common2 = require("@forklaunch/common");
349
+ var import_common3 = require("@forklaunch/common");
174
350
  var import_crypto = require("crypto");
175
351
  function createHmacToken({
176
352
  method,
@@ -181,7 +357,7 @@ function createHmacToken({
181
357
  secretKey
182
358
  }) {
183
359
  const hmac = (0, import_crypto.createHmac)("sha256", secretKey);
184
- const bodyString = body ? `${(0, import_common2.safeStringify)(body)}
360
+ const bodyString = body ? `${(0, import_common3.safeStringify)(body)}
185
361
  ` : void 0;
186
362
  hmac.update(
187
363
  `${method}
@@ -228,7 +404,7 @@ async function getCachedJwks(jwksPublicKeyUrl) {
228
404
  return jwks;
229
405
  }
230
406
  }
231
- async function discriminateAuthMethod(auth) {
407
+ async function discriminateAuthMethod(auth, openTelemetryCollector) {
232
408
  let authMethod;
233
409
  if (isBasicAuthMethod(auth)) {
234
410
  authMethod = {
@@ -291,37 +467,219 @@ async function discriminateAuthMethod(auth) {
291
467
  signature,
292
468
  secretKey
293
469
  }) => {
294
- return createHmacToken({
470
+ const computedSignature = createHmacToken({
295
471
  method,
296
472
  path,
297
473
  body,
298
474
  timestamp,
299
475
  nonce,
300
476
  secretKey
301
- }) === signature;
477
+ });
478
+ const isValid = computedSignature === signature;
479
+ if (!isValid) {
480
+ const errorInfo = {
481
+ method,
482
+ path,
483
+ timestamp: timestamp.toISOString(),
484
+ nonce,
485
+ receivedSignature: signature,
486
+ computedSignature
487
+ };
488
+ openTelemetryCollector?.debug("[HMAC Verification Failed]", {
489
+ ...errorInfo,
490
+ bodyType: body ? typeof body : "undefined",
491
+ body: body ? (0, import_common4.safeStringify)(body) : "undefined"
492
+ });
493
+ }
494
+ return isValid;
302
495
  }
303
496
  }
304
497
  };
305
498
  }
306
- if (authMethod == null) {
307
- throw new Error("Invalid auth method");
499
+ if (authMethod == null) {
500
+ throw new Error("Invalid auth method");
501
+ }
502
+ return authMethod;
503
+ }
504
+
505
+ // src/http/guards/hasPermissionChecks.ts
506
+ function hasPermissionChecks(maybePermissionedAuth) {
507
+ return typeof maybePermissionedAuth === "object" && maybePermissionedAuth !== null && ("allowedPermissions" in maybePermissionedAuth || "forbiddenPermissions" in maybePermissionedAuth);
508
+ }
509
+
510
+ // src/http/guards/hasRoleChecks.ts
511
+ function hasRoleChecks(maybeRoledAuth) {
512
+ return typeof maybeRoledAuth === "object" && maybeRoledAuth !== null && ("allowedRoles" in maybeRoledAuth || "forbiddenRoles" in maybeRoledAuth);
513
+ }
514
+
515
+ // src/http/guards/hasScopeChecks.ts
516
+ function hasScopeChecks(maybePermissionedAuth) {
517
+ return typeof maybePermissionedAuth === "object" && maybePermissionedAuth !== null && "requiredScope" in maybePermissionedAuth && maybePermissionedAuth.requiredScope != null;
518
+ }
519
+
520
+ // src/http/telemetry/pinoLogger.ts
521
+ var import_common5 = require("@forklaunch/common");
522
+ var import_api2 = require("@opentelemetry/api");
523
+ var import_api_logs = require("@opentelemetry/api-logs");
524
+ var import_pino = __toESM(require("pino"));
525
+
526
+ // src/http/guards/isLoggerMeta.ts
527
+ function isLoggerMeta(arg) {
528
+ return typeof arg === "object" && arg !== null && "_meta" in arg;
529
+ }
530
+
531
+ // src/http/telemetry/pinoLogger.ts
532
+ function meta(meta2) {
533
+ return meta2;
534
+ }
535
+ function mapSeverity(level) {
536
+ switch (level) {
537
+ case "silent":
538
+ return 0;
539
+ case "trace":
540
+ return 1;
541
+ case "debug":
542
+ return 5;
543
+ case "info":
544
+ return 9;
545
+ case "warn":
546
+ return 13;
547
+ case "error":
548
+ return 17;
549
+ case "fatal":
550
+ return 21;
551
+ default:
552
+ (0, import_common5.isNever)(level);
553
+ return 0;
554
+ }
555
+ }
556
+ function normalizeLogArgs(args) {
557
+ let message = "";
558
+ const metaObjects = [];
559
+ for (const arg of args) {
560
+ if (typeof arg === "string" && message === "") {
561
+ message = arg;
562
+ } else if (arg instanceof Error) {
563
+ metaObjects.push({
564
+ err: {
565
+ message: arg.message,
566
+ name: arg.name,
567
+ stack: arg.stack,
568
+ ...Object.keys(arg).length > 0 ? arg : {}
569
+ }
570
+ });
571
+ } else if (arg && typeof arg === "object" && !Array.isArray(arg)) {
572
+ metaObjects.push(arg);
573
+ } else {
574
+ message += ` ${String(arg)}`;
575
+ }
576
+ }
577
+ const metadata = Object.assign({}, ...metaObjects);
578
+ return [metadata, message.trim()];
579
+ }
580
+ function safePrettyFormat(level, args, timestamp) {
581
+ try {
582
+ const [metadata, message] = normalizeLogArgs(args);
583
+ const formattedTimestamp = timestamp || (/* @__PURE__ */ new Date()).toISOString();
584
+ return `[${formattedTimestamp}] ${level.toUpperCase()}: ${message}${Object.keys(metadata).length > 0 ? `
585
+ ${JSON.stringify(metadata, null, 2)}` : ""}`;
586
+ } catch (error) {
587
+ const fallbackMessage = args.map((arg) => {
588
+ try {
589
+ if (typeof arg === "string") return arg;
590
+ if (arg === null) return "null";
591
+ if (arg === void 0) return "undefined";
592
+ return JSON.stringify(arg);
593
+ } catch {
594
+ return "[Circular/Non-serializable Object]";
595
+ }
596
+ }).join(" ");
597
+ return `[${(/* @__PURE__ */ new Date()).toISOString()}] ${level.toUpperCase()}: ${fallbackMessage} [Pretty Print Error: ${error instanceof Error ? error.message : "Unknown error"}]`;
598
+ }
599
+ }
600
+ var PinoLogger = class _PinoLogger {
601
+ pinoLogger;
602
+ meta;
603
+ constructor(level, meta2 = {}) {
604
+ this.pinoLogger = (0, import_pino.default)({
605
+ level: level || "info",
606
+ formatters: {
607
+ level(label) {
608
+ return { level: label };
609
+ }
610
+ },
611
+ timestamp: import_pino.default.stdTimeFunctions.isoTime,
612
+ transport: {
613
+ target: "pino-pretty",
614
+ options: {
615
+ colorize: true,
616
+ errorLikeObjectKeys: ["err", "error"],
617
+ ignore: "pid,hostname",
618
+ translateTime: "SYS:standard"
619
+ }
620
+ }
621
+ });
622
+ this.meta = meta2;
623
+ }
624
+ log(level, ...args) {
625
+ let meta2 = {};
626
+ const filteredArgs = args.filter((arg) => {
627
+ if (isLoggerMeta(arg)) {
628
+ Object.assign(meta2, arg);
629
+ return false;
630
+ }
631
+ return true;
632
+ });
633
+ const [errorMetadata] = normalizeLogArgs(filteredArgs);
634
+ Object.assign(meta2, errorMetadata);
635
+ const activeSpan = import_api2.trace.getActiveSpan();
636
+ if (activeSpan) {
637
+ const activeSpanContext = activeSpan.spanContext();
638
+ meta2.trace_id = activeSpanContext.traceId;
639
+ meta2.span_id = activeSpanContext.spanId;
640
+ meta2.trace_flags = activeSpanContext.traceFlags;
641
+ meta2 = {
642
+ // @ts-expect-error accessing private property
643
+ ...activeSpan.attributes,
644
+ ...meta2
645
+ };
646
+ }
647
+ meta2 = {
648
+ "api.name": "none",
649
+ "correlation.id": "none",
650
+ ...meta2
651
+ };
652
+ this.pinoLogger[level](...normalizeLogArgs(filteredArgs));
653
+ const formattedBody = safePrettyFormat(level, filteredArgs);
654
+ try {
655
+ import_api_logs.logs.getLogger(process.env.OTEL_SERVICE_NAME ?? "unknown").emit({
656
+ severityText: level,
657
+ severityNumber: mapSeverity(level),
658
+ body: formattedBody,
659
+ attributes: { ...this.meta, ...meta2 }
660
+ });
661
+ } catch (error) {
662
+ console.error("Failed to emit OpenTelemetry log:", error);
663
+ console.log(
664
+ `[${(/* @__PURE__ */ new Date()).toISOString()}] ${level.toUpperCase()}:`,
665
+ ...filteredArgs
666
+ );
667
+ }
668
+ }
669
+ error = (msg, ...args) => this.log("error", msg, ...args);
670
+ info = (msg, ...args) => this.log("info", msg, ...args);
671
+ debug = (msg, ...args) => this.log("debug", msg, ...args);
672
+ warn = (msg, ...args) => this.log("warn", msg, ...args);
673
+ trace = (msg, ...args) => this.log("trace", msg, ...args);
674
+ child(meta2 = {}) {
675
+ return new _PinoLogger(this.pinoLogger.level, { ...this.meta, ...meta2 });
676
+ }
677
+ getBaseLogger() {
678
+ return this.pinoLogger;
308
679
  }
309
- return authMethod;
310
- }
311
-
312
- // src/http/guards/hasPermissionChecks.ts
313
- function hasPermissionChecks(maybePermissionedAuth) {
314
- return typeof maybePermissionedAuth === "object" && maybePermissionedAuth !== null && ("allowedPermissions" in maybePermissionedAuth || "forbiddenPermissions" in maybePermissionedAuth);
315
- }
316
-
317
- // src/http/guards/hasRoleChecks.ts
318
- function hasRoleChecks(maybeRoledAuth) {
319
- return typeof maybeRoledAuth === "object" && maybeRoledAuth !== null && ("allowedRoles" in maybeRoledAuth || "forbiddenRoles" in maybeRoledAuth);
320
- }
321
-
322
- // src/http/guards/hasScopeChecks.ts
323
- function hasScopeChecks(maybePermissionedAuth) {
324
- return typeof maybePermissionedAuth === "object" && maybePermissionedAuth !== null && "requiredScope" in maybePermissionedAuth && maybePermissionedAuth.requiredScope != null;
680
+ };
681
+ function logger(level, meta2 = {}) {
682
+ return new PinoLogger(level, meta2);
325
683
  }
326
684
 
327
685
  // src/http/middleware/request/auth.middleware.ts
@@ -385,7 +743,8 @@ async function checkAuthorizationToken(req, authorizationMethod, authorizationTo
385
743
  }
386
744
  let sessionPayload;
387
745
  const { type, auth } = await discriminateAuthMethod(
388
- collapsedAuthorizationMethod
746
+ collapsedAuthorizationMethod,
747
+ req.openTelemetryCollector
389
748
  );
390
749
  switch (type) {
391
750
  case "hmac": {
@@ -406,7 +765,7 @@ async function checkAuthorizationToken(req, authorizationMethod, authorizationTo
406
765
  const verificationResult = await auth.verificationFunction({
407
766
  method: req?.method ?? "",
408
767
  path: req?.path ?? "",
409
- body: req?.body,
768
+ body: req?._rawBody ?? req?.body,
410
769
  timestamp: new Date(parsedTimestamp),
411
770
  nonce: parsedNonce,
412
771
  signature: parsedSignature,
@@ -430,7 +789,15 @@ async function checkAuthorizationToken(req, authorizationMethod, authorizationTo
430
789
  }
431
790
  sessionPayload = decodedJwt;
432
791
  } catch (error) {
433
- req?.openTelemetryCollector.error(error);
792
+ req?.openTelemetryCollector?.error(
793
+ "JWT Verification Failed",
794
+ meta({
795
+ error,
796
+ method: req.method,
797
+ path: req.path,
798
+ token
799
+ })
800
+ );
434
801
  return invalidAuthorizationToken;
435
802
  }
436
803
  break;
@@ -457,7 +824,7 @@ async function checkAuthorizationToken(req, authorizationMethod, authorizationTo
457
824
  break;
458
825
  }
459
826
  default:
460
- (0, import_common3.isNever)(type);
827
+ (0, import_common6.isNever)(type);
461
828
  return [401, "Invalid Authorization method."];
462
829
  }
463
830
  if (isHmacMethod(collapsedAuthorizationMethod) && sessionPayload == null) {
@@ -542,14 +909,18 @@ async function checkAuthorizationToken(req, authorizationMethod, authorizationTo
542
909
  }
543
910
  async function parseRequestAuth(req, res, next) {
544
911
  const auth = req.contractDetails.auth;
545
- const [error, message] = await checkAuthorizationToken(
546
- req,
547
- auth,
548
- req.headers[auth?.headerName ?? "Authorization"] || req.headers[auth?.headerName ?? "authorization"],
549
- req._globalOptions?.()?.auth
550
- ) ?? [];
912
+ const token = req.headers[auth?.headerName ?? "Authorization"] || req.headers[auth?.headerName ?? "authorization"];
913
+ const [error, message] = await checkAuthorizationToken(req, auth, token, req._globalOptions?.()?.auth) ?? [];
551
914
  if (error != null) {
552
- req.openTelemetryCollector.error(error, message);
915
+ req.openTelemetryCollector?.error(
916
+ message || "Authorization Failed",
917
+ meta({
918
+ statusCode: error,
919
+ method: req.method,
920
+ path: req.path,
921
+ token
922
+ })
923
+ );
553
924
  res.type("text/plain");
554
925
  res.status(error).send(message);
555
926
  return;
@@ -557,46 +928,11 @@ async function parseRequestAuth(req, res, next) {
557
928
  next?.();
558
929
  }
559
930
 
560
- // src/http/middleware/request/createContext.middleware.ts
561
- var import_common4 = require("@forklaunch/common");
562
- var import_api = require("@opentelemetry/api");
563
- var import_uuid = require("uuid");
564
-
565
- // src/http/telemetry/constants.ts
566
- var import_semantic_conventions = require("@opentelemetry/semantic-conventions");
567
- var ATTR_API_NAME = "api.name";
568
- var ATTR_CORRELATION_ID = "correlation.id";
569
-
570
- // src/http/middleware/request/createContext.middleware.ts
571
- function createContext(schemaValidator) {
572
- return function setContext(req, res, next) {
573
- req.schemaValidator = schemaValidator;
574
- let correlationId = (0, import_uuid.v4)();
575
- if (req.headers["x-correlation-id"]) {
576
- correlationId = req.headers["x-correlation-id"];
577
- }
578
- res.setHeader("x-correlation-id", correlationId);
579
- req.context = {
580
- correlationId
581
- };
582
- const span = import_api.trace.getSpan(import_api.context.active());
583
- if (span != null) {
584
- req.context.span = span;
585
- req.context.span?.setAttribute(ATTR_CORRELATION_ID, correlationId);
586
- req.context.span?.setAttribute(
587
- import_semantic_conventions.ATTR_SERVICE_NAME,
588
- (0, import_common4.getEnvVar)("OTEL_SERVICE_NAME")
589
- );
590
- }
591
- next?.();
592
- };
593
- }
594
-
595
931
  // src/http/middleware/request/enrichDetails.middleware.ts
596
- var import_common7 = require("@forklaunch/common");
932
+ var import_common8 = require("@forklaunch/common");
597
933
 
598
934
  // src/http/telemetry/openTelemetryCollector.ts
599
- var import_common6 = require("@forklaunch/common");
935
+ var import_common7 = require("@forklaunch/common");
600
936
  var import_opentelemetry_instrumentation_hyper_express = require("@forklaunch/opentelemetry-instrumentation-hyper-express");
601
937
  var import_api3 = require("@opentelemetry/api");
602
938
  var import_exporter_logs_otlp_http = require("@opentelemetry/exporter-logs-otlp-http");
@@ -616,171 +952,6 @@ function isForklaunchRequest(request) {
616
952
  return request != null && typeof request === "object" && "contractDetails" in request;
617
953
  }
618
954
 
619
- // src/http/telemetry/pinoLogger.ts
620
- var import_common5 = require("@forklaunch/common");
621
- var import_api2 = require("@opentelemetry/api");
622
- var import_api_logs = require("@opentelemetry/api-logs");
623
- var import_pino = __toESM(require("pino"));
624
-
625
- // src/http/guards/isLoggerMeta.ts
626
- function isLoggerMeta(arg) {
627
- return typeof arg === "object" && arg !== null && "_meta" in arg;
628
- }
629
-
630
- // src/http/telemetry/pinoLogger.ts
631
- function meta(meta2) {
632
- return meta2;
633
- }
634
- function mapSeverity(level) {
635
- switch (level) {
636
- case "silent":
637
- return 0;
638
- case "trace":
639
- return 1;
640
- case "debug":
641
- return 5;
642
- case "info":
643
- return 9;
644
- case "warn":
645
- return 13;
646
- case "error":
647
- return 17;
648
- case "fatal":
649
- return 21;
650
- default:
651
- (0, import_common5.isNever)(level);
652
- return 0;
653
- }
654
- }
655
- function normalizeLogArgs(args) {
656
- let message = "";
657
- const metaObjects = [];
658
- for (const arg of args) {
659
- if (typeof arg === "string" && message === "") {
660
- message = arg;
661
- } else if (arg instanceof Error) {
662
- metaObjects.push({
663
- err: {
664
- message: arg.message,
665
- name: arg.name,
666
- stack: arg.stack,
667
- ...Object.keys(arg).length > 0 ? arg : {}
668
- }
669
- });
670
- } else if (arg && typeof arg === "object" && !Array.isArray(arg)) {
671
- metaObjects.push(arg);
672
- } else {
673
- message += ` ${String(arg)}`;
674
- }
675
- }
676
- const metadata = Object.assign({}, ...metaObjects);
677
- return [metadata, message.trim()];
678
- }
679
- function safePrettyFormat(level, args, timestamp) {
680
- try {
681
- const [metadata, message] = normalizeLogArgs(args);
682
- const formattedTimestamp = timestamp || (/* @__PURE__ */ new Date()).toISOString();
683
- return `[${formattedTimestamp}] ${level.toUpperCase()}: ${message}${Object.keys(metadata).length > 0 ? `
684
- ${JSON.stringify(metadata, null, 2)}` : ""}`;
685
- } catch (error) {
686
- const fallbackMessage = args.map((arg) => {
687
- try {
688
- if (typeof arg === "string") return arg;
689
- if (arg === null) return "null";
690
- if (arg === void 0) return "undefined";
691
- return JSON.stringify(arg);
692
- } catch {
693
- return "[Circular/Non-serializable Object]";
694
- }
695
- }).join(" ");
696
- return `[${(/* @__PURE__ */ new Date()).toISOString()}] ${level.toUpperCase()}: ${fallbackMessage} [Pretty Print Error: ${error instanceof Error ? error.message : "Unknown error"}]`;
697
- }
698
- }
699
- var PinoLogger = class _PinoLogger {
700
- pinoLogger;
701
- meta;
702
- constructor(level, meta2 = {}) {
703
- this.pinoLogger = (0, import_pino.default)({
704
- level: level || "info",
705
- formatters: {
706
- level(label) {
707
- return { level: label };
708
- }
709
- },
710
- timestamp: import_pino.default.stdTimeFunctions.isoTime,
711
- transport: {
712
- target: "pino-pretty",
713
- options: {
714
- colorize: true,
715
- errorLikeObjectKeys: ["err", "error"],
716
- ignore: "pid,hostname",
717
- translateTime: "SYS:standard"
718
- }
719
- }
720
- });
721
- this.meta = meta2;
722
- }
723
- log(level, ...args) {
724
- let meta2 = {};
725
- const filteredArgs = args.filter((arg) => {
726
- if (isLoggerMeta(arg)) {
727
- Object.assign(meta2, arg);
728
- return false;
729
- }
730
- return true;
731
- });
732
- const [errorMetadata] = normalizeLogArgs(filteredArgs);
733
- Object.assign(meta2, errorMetadata);
734
- const activeSpan = import_api2.trace.getActiveSpan();
735
- if (activeSpan) {
736
- const activeSpanContext = activeSpan.spanContext();
737
- meta2.trace_id = activeSpanContext.traceId;
738
- meta2.span_id = activeSpanContext.spanId;
739
- meta2.trace_flags = activeSpanContext.traceFlags;
740
- meta2 = {
741
- // @ts-expect-error accessing private property
742
- ...activeSpan.attributes,
743
- ...meta2
744
- };
745
- }
746
- meta2 = {
747
- "api.name": "none",
748
- "correlation.id": "none",
749
- ...meta2
750
- };
751
- this.pinoLogger[level](...normalizeLogArgs(filteredArgs));
752
- const formattedBody = safePrettyFormat(level, filteredArgs);
753
- try {
754
- import_api_logs.logs.getLogger(process.env.OTEL_SERVICE_NAME ?? "unknown").emit({
755
- severityText: level,
756
- severityNumber: mapSeverity(level),
757
- body: formattedBody,
758
- attributes: { ...this.meta, ...meta2 }
759
- });
760
- } catch (error) {
761
- console.error("Failed to emit OpenTelemetry log:", error);
762
- console.log(
763
- `[${(/* @__PURE__ */ new Date()).toISOString()}] ${level.toUpperCase()}:`,
764
- ...filteredArgs
765
- );
766
- }
767
- }
768
- error = (msg, ...args) => this.log("error", msg, ...args);
769
- info = (msg, ...args) => this.log("info", msg, ...args);
770
- debug = (msg, ...args) => this.log("debug", msg, ...args);
771
- warn = (msg, ...args) => this.log("warn", msg, ...args);
772
- trace = (msg, ...args) => this.log("trace", msg, ...args);
773
- child(meta2 = {}) {
774
- return new _PinoLogger(this.pinoLogger.level, { ...this.meta, ...meta2 });
775
- }
776
- getBaseLogger() {
777
- return this.pinoLogger;
778
- }
779
- };
780
- function logger(level, meta2 = {}) {
781
- return new PinoLogger(level, meta2);
782
- }
783
-
784
955
  // src/http/telemetry/openTelemetryCollector.ts
785
956
  var OpenTelemetryCollector = class {
786
957
  #logger;
@@ -845,24 +1016,40 @@ var OpenTelemetryCollector = class {
845
1016
  return this.#metrics[metricId];
846
1017
  }
847
1018
  };
848
- import_dotenv.default.config({ path: (0, import_common6.getEnvVar)("DOTENV_FILE_PATH") });
1019
+ import_dotenv.default.config({ path: (0, import_common7.getEnvVar)("DOTENV_FILE_PATH") });
1020
+ function parseOtelHeaders() {
1021
+ const headersEnv = (0, import_common7.getEnvVar)("OTEL_EXPORTER_OTLP_HEADERS");
1022
+ if (!headersEnv) return void 0;
1023
+ const headers = {};
1024
+ for (const pair of headersEnv.split(",")) {
1025
+ const [key, ...valueParts] = pair.split("=");
1026
+ if (key && valueParts.length > 0) {
1027
+ headers[key.trim()] = valueParts.join("=").trim();
1028
+ }
1029
+ }
1030
+ return Object.keys(headers).length > 0 ? headers : void 0;
1031
+ }
1032
+ var otelHeaders = parseOtelHeaders();
849
1033
  new import_sdk_node.NodeSDK({
850
1034
  resource: (0, import_resources.resourceFromAttributes)({
851
- [import_semantic_conventions2.ATTR_SERVICE_NAME]: (0, import_common6.getEnvVar)("OTEL_SERVICE_NAME")
1035
+ [import_semantic_conventions2.ATTR_SERVICE_NAME]: (0, import_common7.getEnvVar)("OTEL_SERVICE_NAME")
852
1036
  }),
853
1037
  traceExporter: new import_exporter_trace_otlp_http.OTLPTraceExporter({
854
- url: `${(0, import_common6.getEnvVar)("OTEL_EXPORTER_OTLP_ENDPOINT") ?? "http://localhost:4318"}/v1/traces`
1038
+ url: `${(0, import_common7.getEnvVar)("OTEL_EXPORTER_OTLP_ENDPOINT") ?? "http://localhost:4318"}/v1/traces`,
1039
+ headers: otelHeaders
855
1040
  }),
856
1041
  metricReader: new import_sdk_metrics.PeriodicExportingMetricReader({
857
1042
  exporter: new import_exporter_metrics_otlp_http.OTLPMetricExporter({
858
- url: `${(0, import_common6.getEnvVar)("OTEL_EXPORTER_OTLP_ENDPOINT") ?? "http://localhost:4318"}/v1/metrics`
1043
+ url: `${(0, import_common7.getEnvVar)("OTEL_EXPORTER_OTLP_ENDPOINT") ?? "http://localhost:4318"}/v1/metrics`,
1044
+ headers: otelHeaders
859
1045
  }),
860
1046
  exportIntervalMillis: 5e3
861
1047
  }),
862
1048
  logRecordProcessors: [
863
1049
  new import_sdk_logs.BatchLogRecordProcessor(
864
1050
  new import_exporter_logs_otlp_http.OTLPLogExporter({
865
- url: `${(0, import_common6.getEnvVar)("OTEL_EXPORTER_OTLP_ENDPOINT") ?? "http://localhost:4318"}/v1/logs`
1051
+ url: `${(0, import_common7.getEnvVar)("OTEL_EXPORTER_OTLP_ENDPOINT") ?? "http://localhost:4318"}/v1/logs`,
1052
+ headers: otelHeaders
866
1053
  })
867
1054
  )
868
1055
  ],
@@ -871,7 +1058,7 @@ new import_sdk_node.NodeSDK({
871
1058
  applyCustomAttributesOnSpan: (span, request) => {
872
1059
  span.setAttribute(
873
1060
  import_semantic_conventions2.ATTR_SERVICE_NAME,
874
- (0, import_common6.getEnvVar)("OTEL_SERVICE_NAME") ?? "unknown"
1061
+ (0, import_common7.getEnvVar)("OTEL_SERVICE_NAME") ?? "unknown"
875
1062
  );
876
1063
  if (isForklaunchRequest(request)) {
877
1064
  span.setAttribute(ATTR_API_NAME, request.contractDetails?.name);
@@ -883,10 +1070,10 @@ new import_sdk_node.NodeSDK({
883
1070
  new import_opentelemetry_instrumentation_hyper_express.HyperExpressInstrumentation()
884
1071
  ]
885
1072
  }).start();
886
- var httpRequestsTotalCounter = import_api3.metrics.getMeter((0, import_common6.getEnvVar)("OTEL_SERVICE_NAME") || "unknown").createCounter("http_requests_total", {
1073
+ var httpRequestsTotalCounter = import_api3.metrics.getMeter((0, import_common7.getEnvVar)("OTEL_SERVICE_NAME") || "unknown").createCounter("http_requests_total", {
887
1074
  description: "Number of HTTP requests"
888
1075
  });
889
- var httpServerDurationHistogram = import_api3.metrics.getMeter((0, import_common6.getEnvVar)("OTEL_SERVICE_NAME") || "unknown").createHistogram("http_server_duration", {
1076
+ var httpServerDurationHistogram = import_api3.metrics.getMeter((0, import_common7.getEnvVar)("OTEL_SERVICE_NAME") || "unknown").createHistogram("http_server_duration", {
890
1077
  description: "Duration of HTTP server requests",
891
1078
  unit: "s"
892
1079
  });
@@ -899,14 +1086,15 @@ function enrichDetails(path, contractDetails, requestSchema, responseSchemas, op
899
1086
  req.requestSchema = requestSchema;
900
1087
  res.responseSchemas = responseSchemas;
901
1088
  req.openTelemetryCollector = openTelemetryCollector;
902
- req._globalOptions = globalOptions;
1089
+ req._globalOptions = globalOptions ?? (() => void 0);
903
1090
  req.context?.span?.setAttribute(ATTR_API_NAME, req.contractDetails?.name);
904
1091
  const startTime = process.hrtime();
905
1092
  res.on("finish", () => {
906
1093
  const [seconds, nanoseconds] = process.hrtime(startTime);
907
1094
  const durationMs = seconds + nanoseconds / 1e9;
908
1095
  httpServerDurationHistogram.record(durationMs, {
909
- [import_semantic_conventions.ATTR_SERVICE_NAME]: (0, import_common7.getEnvVar)("OTEL_SERVICE_NAME") || "unknown",
1096
+ [import_semantic_conventions.ATTR_SERVICE_NAME]: (0, import_common8.getEnvVar)("OTEL_SERVICE_NAME") || "unknown",
1097
+ [ATTR_APPLICATION_ID]: (0, import_common8.getEnvVar)("OTEL_APPLICATION_ID"),
910
1098
  [ATTR_API_NAME]: req.contractDetails?.name || "unknown",
911
1099
  [import_semantic_conventions.ATTR_HTTP_REQUEST_METHOD]: req.method,
912
1100
  [import_semantic_conventions.ATTR_HTTP_ROUTE]: req.originalPath || "unknown",
@@ -918,7 +1106,7 @@ function enrichDetails(path, contractDetails, requestSchema, responseSchemas, op
918
1106
  }
919
1107
 
920
1108
  // src/http/middleware/request/parse.middleware.ts
921
- var import_common8 = require("@forklaunch/common");
1109
+ var import_common9 = require("@forklaunch/common");
922
1110
  var import_validator2 = require("@forklaunch/validator");
923
1111
 
924
1112
  // src/http/guards/hasSend.ts
@@ -935,6 +1123,7 @@ function isRequestShape(maybeResponseShape) {
935
1123
  function parse(req, res, next) {
936
1124
  const globalOptions = req._globalOptions?.();
937
1125
  const collapsedOptions = req.contractDetails.options?.requestValidation ?? (globalOptions?.validation === false ? "none" : globalOptions?.validation?.request);
1126
+ req._rawBody = req.body;
938
1127
  const request = {
939
1128
  params: req.params,
940
1129
  query: req.query,
@@ -946,7 +1135,7 @@ function parse(req, res, next) {
946
1135
  let parsedRequest;
947
1136
  let collectedParseErrors;
948
1137
  if (req.contractDetails.versions) {
949
- if ((0, import_common8.isRecord)(req.requestSchema)) {
1138
+ if ((0, import_common9.isRecord)(req.requestSchema)) {
950
1139
  let runningParseErrors = "";
951
1140
  matchedVersions = [];
952
1141
  Object.entries(req.requestSchema).forEach(([version, schema]) => {
@@ -1024,7 +1213,7 @@ Correlation id: ${req.context.correlationId ?? "No correlation ID"}`
1024
1213
  }
1025
1214
  return;
1026
1215
  case "warning":
1027
- req.openTelemetryCollector.warn(
1216
+ req.openTelemetryCollector?.warn(
1028
1217
  collectedParseErrors ?? (0, import_validator2.prettyPrintParseErrors)(parsedRequest.errors, "Request")
1029
1218
  );
1030
1219
  break;
@@ -1032,143 +1221,221 @@ Correlation id: ${req.context.correlationId ?? "No correlation ID"}`
1032
1221
  break;
1033
1222
  }
1034
1223
  }
1035
- req._parsedVersions = matchedVersions;
1036
- next?.();
1224
+ req._parsedVersions = matchedVersions;
1225
+ next?.();
1226
+ }
1227
+
1228
+ // src/http/router/routerSharedLogic.ts
1229
+ function resolveContractDetailsAndHandlers(contractDetailsOrMiddlewareOrTypedHandler, middlewareOrMiddlewareAndTypedHandler) {
1230
+ let contractDetails;
1231
+ let handlers = [];
1232
+ if (isTypedHandler(contractDetailsOrMiddlewareOrTypedHandler)) {
1233
+ contractDetails = contractDetailsOrMiddlewareOrTypedHandler.contractDetails;
1234
+ handlers = contractDetailsOrMiddlewareOrTypedHandler.handlers;
1235
+ } else {
1236
+ const maybeTypedHandler = middlewareOrMiddlewareAndTypedHandler[middlewareOrMiddlewareAndTypedHandler.length - 1];
1237
+ if (isTypedHandler(maybeTypedHandler)) {
1238
+ contractDetails = maybeTypedHandler.contractDetails;
1239
+ const typedHandlerHandlers = maybeTypedHandler.handlers;
1240
+ const finalHandlers = [];
1241
+ if (isExpressLikeSchemaHandler(contractDetailsOrMiddlewareOrTypedHandler)) {
1242
+ finalHandlers.push(contractDetailsOrMiddlewareOrTypedHandler);
1243
+ }
1244
+ finalHandlers.push(...middlewareOrMiddlewareAndTypedHandler.slice(0, -1));
1245
+ finalHandlers.push(
1246
+ ...typedHandlerHandlers
1247
+ );
1248
+ handlers = finalHandlers.filter(
1249
+ (handler) => isExpressLikeSchemaHandler(handler)
1250
+ );
1251
+ } else {
1252
+ if (isExpressLikeSchemaHandler(contractDetailsOrMiddlewareOrTypedHandler) || isTypedHandler(contractDetailsOrMiddlewareOrTypedHandler)) {
1253
+ throw new Error("Contract details are not defined");
1254
+ }
1255
+ contractDetails = contractDetailsOrMiddlewareOrTypedHandler;
1256
+ handlers = middlewareOrMiddlewareAndTypedHandler.filter(
1257
+ (handler) => isExpressLikeSchemaHandler(handler)
1258
+ );
1259
+ }
1260
+ }
1261
+ return { contractDetails, handlers };
1262
+ }
1263
+ function validateContractDetails(contractDetails, schemaValidator) {
1264
+ if (!isHttpContractDetails(contractDetails) && !isPathParamHttpContractDetails(contractDetails)) {
1265
+ throw new Error("Contract details are malformed for route definition");
1266
+ }
1267
+ if (contractDetails.versions) {
1268
+ const parserTypes = Object.values(contractDetails.versions).map(
1269
+ (version) => discriminateBody(schemaValidator, version.body)?.parserType
1270
+ );
1271
+ const allParserTypesSame = parserTypes.length === 0 || parserTypes.every((pt) => pt === parserTypes[0]);
1272
+ if (!allParserTypesSame) {
1273
+ throw new Error(
1274
+ "All versioned contractDetails must have the same parsing type for body."
1275
+ );
1276
+ }
1277
+ }
1037
1278
  }
1038
-
1039
- // src/http/router/discriminateBody.ts
1040
- function discriminateBody(schemaValidator, body) {
1041
- if (body == null) {
1042
- return void 0;
1043
- }
1044
- const maybeTypedBody = body;
1045
- if ("text" in maybeTypedBody && maybeTypedBody.text != null) {
1046
- return {
1047
- contentType: maybeTypedBody.contentType ?? "text/plain",
1048
- parserType: "text",
1049
- schema: maybeTypedBody.text
1050
- };
1051
- } else if ("json" in maybeTypedBody && maybeTypedBody.json != null) {
1052
- return {
1053
- contentType: maybeTypedBody.contentType ?? "application/json",
1054
- parserType: "json",
1055
- schema: maybeTypedBody.json
1056
- };
1057
- } else if ("file" in maybeTypedBody && maybeTypedBody.file != null) {
1058
- return {
1059
- contentType: maybeTypedBody.contentType ?? "application/octet-stream",
1060
- parserType: "file",
1061
- schema: maybeTypedBody.file
1062
- };
1063
- } else if ("multipartForm" in maybeTypedBody && maybeTypedBody.multipartForm != null) {
1064
- return {
1065
- contentType: maybeTypedBody.contentType ?? "multipart/form-data",
1066
- parserType: "multipart",
1067
- schema: maybeTypedBody.multipartForm
1068
- };
1069
- } else if ("urlEncodedForm" in maybeTypedBody && maybeTypedBody.urlEncodedForm != null) {
1070
- return {
1071
- contentType: maybeTypedBody.contentType ?? "application/x-www-form-urlencoded",
1072
- parserType: "urlEncoded",
1073
- schema: maybeTypedBody.urlEncodedForm
1074
- };
1075
- } else if ("schema" in maybeTypedBody && maybeTypedBody.schema != null) {
1076
- return {
1077
- contentType: maybeTypedBody.contentType ?? "application/json",
1078
- parserType: "text",
1079
- schema: maybeTypedBody.schema
1080
- };
1081
- } else if (schemaValidator.isInstanceOf(
1082
- maybeTypedBody,
1083
- schemaValidator.string
1084
- )) {
1085
- return {
1086
- contentType: "text/plain",
1087
- parserType: "text",
1088
- schema: maybeTypedBody
1089
- };
1090
- } else if (schemaValidator.openapi(maybeTypedBody).format === "binary") {
1091
- return {
1092
- contentType: "application/octet-stream",
1093
- parserType: "file",
1094
- schema: maybeTypedBody
1095
- };
1279
+ function processContractDetailsIO(schemaValidator, contractDetailsIO, routeParams) {
1280
+ const responseSchemas = {
1281
+ 400: schemaValidator.string,
1282
+ 401: schemaValidator.string,
1283
+ 403: schemaValidator.string,
1284
+ 404: schemaValidator.string,
1285
+ 500: schemaValidator.string,
1286
+ ...Object.fromEntries(
1287
+ Object.entries(
1288
+ discriminateResponseBodies(schemaValidator, contractDetailsIO.responses)
1289
+ ).map(([key, value]) => [Number(key), value.schema])
1290
+ )
1291
+ };
1292
+ return {
1293
+ requestSchema: schemaValidator.compile(
1294
+ schemaValidator.schemify({
1295
+ ...routeParams != null ? { params: routeParams } : { params: schemaValidator.unknown },
1296
+ ...contractDetailsIO.requestHeaders != null ? { headers: contractDetailsIO.requestHeaders } : { headers: schemaValidator.unknown },
1297
+ ...contractDetailsIO.query != null ? { query: contractDetailsIO.query } : { query: schemaValidator.unknown },
1298
+ ...contractDetailsIO.body != null ? {
1299
+ body: discriminateBody(schemaValidator, contractDetailsIO.body)?.schema
1300
+ } : { body: schemaValidator.unknown }
1301
+ })
1302
+ ),
1303
+ responseSchemas: {
1304
+ ...contractDetailsIO.responseHeaders != null ? {
1305
+ headers: schemaValidator.compile(
1306
+ schemaValidator.schemify(contractDetailsIO.responseHeaders)
1307
+ )
1308
+ } : { headers: schemaValidator.unknown },
1309
+ responses: Object.fromEntries(
1310
+ Object.entries(responseSchemas).map(([key, value]) => [
1311
+ key,
1312
+ schemaValidator.compile(schemaValidator.schemify(value))
1313
+ ])
1314
+ )
1315
+ }
1316
+ };
1317
+ }
1318
+ function compileRouteSchemas(contractDetails, schemaValidator) {
1319
+ const validator = schemaValidator;
1320
+ let requestSchema;
1321
+ let responseSchemas;
1322
+ if (hasVersionedSchema(contractDetails)) {
1323
+ requestSchema = {};
1324
+ responseSchemas = {};
1325
+ Object.entries(contractDetails.versions ?? {}).forEach(
1326
+ ([version, versionedContractDetails]) => {
1327
+ const {
1328
+ requestSchema: versionedRequestSchema,
1329
+ responseSchemas: versionedResponseSchemas
1330
+ } = processContractDetailsIO(
1331
+ validator,
1332
+ versionedContractDetails,
1333
+ contractDetails.params
1334
+ );
1335
+ if ((0, import_common10.isRecord)(requestSchema)) {
1336
+ requestSchema = {
1337
+ ...requestSchema,
1338
+ [version]: versionedRequestSchema
1339
+ };
1340
+ }
1341
+ if ((0, import_common10.isRecord)(responseSchemas)) {
1342
+ responseSchemas = {
1343
+ ...responseSchemas,
1344
+ [version]: versionedResponseSchemas
1345
+ };
1346
+ }
1347
+ }
1348
+ );
1096
1349
  } else {
1097
- return {
1098
- contentType: "application/json",
1099
- parserType: "json",
1100
- schema: maybeTypedBody
1101
- };
1350
+ const {
1351
+ requestSchema: unversionedRequestSchema,
1352
+ responseSchemas: unversionedResponseSchemas
1353
+ } = processContractDetailsIO(
1354
+ validator,
1355
+ {
1356
+ ..."params" in contractDetails && contractDetails.params != null ? { params: contractDetails.params } : { params: validator.unknown },
1357
+ ..."requestHeaders" in contractDetails && contractDetails.requestHeaders != null ? { requestHeaders: contractDetails.requestHeaders } : {
1358
+ requestHeaders: validator.unknown
1359
+ },
1360
+ ..."responseHeaders" in contractDetails && contractDetails.responseHeaders != null ? { responseHeaders: contractDetails.responseHeaders } : {
1361
+ responseHeaders: validator.unknown
1362
+ },
1363
+ ..."query" in contractDetails && contractDetails.query != null ? { query: contractDetails.query } : {
1364
+ query: validator.unknown
1365
+ },
1366
+ ..."body" in contractDetails && contractDetails.body != null ? { body: contractDetails.body } : {
1367
+ body: validator.unknown
1368
+ },
1369
+ responses: "responses" in contractDetails && contractDetails.responses != null ? contractDetails.responses : validator.unknown
1370
+ },
1371
+ contractDetails.params
1372
+ );
1373
+ requestSchema = unversionedRequestSchema;
1374
+ responseSchemas = unversionedResponseSchemas;
1102
1375
  }
1376
+ return { requestSchema, responseSchemas };
1103
1377
  }
1104
- function discriminateResponseBodies(schemaValidator, responses) {
1105
- const discriminatedResponses = {};
1106
- for (const [statusCode, response] of Object.entries(responses)) {
1107
- if (response != null && typeof response === "object") {
1108
- if ("json" in response && response.json != null) {
1109
- discriminatedResponses[Number(statusCode)] = {
1110
- contentType: ("contentType" in response && typeof response.contentType === "string" ? response.contentType : "application/json") ?? "application/json",
1111
- parserType: "json",
1112
- schema: response.json
1113
- };
1114
- } else if ("schema" in response && response.schema != null) {
1115
- discriminatedResponses[Number(statusCode)] = {
1116
- contentType: ("contentType" in response && typeof response.contentType === "string" ? response.contentType : "application/json") ?? "application/json",
1117
- parserType: "text",
1118
- schema: response.schema
1119
- };
1120
- } else if ("text" in response && response.text != null) {
1121
- discriminatedResponses[Number(statusCode)] = {
1122
- contentType: ("contentType" in response && typeof response.contentType === "string" ? response.contentType : "text/plain") ?? "text/plain",
1123
- parserType: "text",
1124
- schema: response.text
1125
- };
1126
- } else if ("file" in response && response.file != null) {
1127
- discriminatedResponses[Number(statusCode)] = {
1128
- contentType: ("contentType" in response && typeof response.contentType === "string" ? response.contentType : "application/octet-stream") ?? "application/octet-stream",
1129
- parserType: "file",
1130
- schema: response.file
1131
- };
1132
- } else if ("event" in response && response.event != null) {
1133
- discriminatedResponses[Number(statusCode)] = {
1134
- contentType: ("contentType" in response && typeof response.contentType === "string" ? response.contentType : "text/event-stream") ?? "text/event-stream",
1135
- parserType: "serverSentEvent",
1136
- schema: response.event
1137
- };
1138
- } else if (schemaValidator.isInstanceOf(
1139
- response,
1140
- schemaValidator.string
1141
- )) {
1142
- discriminatedResponses[Number(statusCode)] = {
1143
- contentType: "text/plain",
1144
- parserType: "text",
1145
- schema: response
1146
- };
1147
- } else if (schemaValidator.openapi(response).format === "binary") {
1148
- discriminatedResponses[Number(statusCode)] = {
1149
- contentType: "application/octet-stream",
1150
- parserType: "file",
1151
- schema: response
1152
- };
1153
- } else {
1154
- discriminatedResponses[Number(statusCode)] = {
1155
- contentType: "application/json",
1156
- parserType: "json",
1157
- schema: response
1158
- };
1159
- }
1160
- } else {
1161
- discriminatedResponses[Number(statusCode)] = {
1162
- contentType: "application/json",
1163
- parserType: "json",
1164
- schema: response
1165
- };
1166
- }
1378
+ function resolveRouteMiddlewares(params) {
1379
+ const handlersCopy = [...params.handlers];
1380
+ const controllerHandler = handlersCopy.pop();
1381
+ if (typeof controllerHandler !== "function") {
1382
+ throw new Error(
1383
+ `Last argument must be a handler, received: ${controllerHandler}`
1384
+ );
1167
1385
  }
1168
- return discriminatedResponses;
1386
+ const middlewares = [
1387
+ ...params.includeCreateContext !== false ? [createContext] : [],
1388
+ enrichDetails(
1389
+ `${params.basePath}${params.path}`,
1390
+ params.contractDetails,
1391
+ params.requestSchema,
1392
+ params.responseSchemas,
1393
+ params.openTelemetryCollector,
1394
+ () => params.routerOptions
1395
+ ),
1396
+ ...params.postEnrichMiddleware,
1397
+ parse,
1398
+ parseRequestAuth,
1399
+ ...handlersCopy
1400
+ ];
1401
+ return {
1402
+ middlewares,
1403
+ controllerHandler
1404
+ };
1169
1405
  }
1170
1406
 
1171
1407
  // src/http/router/expressLikeRouter.ts
1408
+ function extractRouteHandlers(params) {
1409
+ const schemaValidator = params.schemaValidator;
1410
+ const { contractDetails, handlers } = resolveContractDetailsAndHandlers(
1411
+ params.contractDetailsOrMiddlewareOrTypedHandler,
1412
+ params.middlewareOrMiddlewareAndTypedHandler
1413
+ );
1414
+ validateContractDetails(contractDetails, schemaValidator);
1415
+ const { requestSchema, responseSchemas } = compileRouteSchemas(
1416
+ contractDetails,
1417
+ schemaValidator
1418
+ );
1419
+ const { middlewares, controllerHandler } = resolveRouteMiddlewares({
1420
+ basePath: params.basePath,
1421
+ path: params.path,
1422
+ contractDetails,
1423
+ requestSchema,
1424
+ responseSchemas,
1425
+ openTelemetryCollector: params.openTelemetryCollector,
1426
+ routerOptions: params.routerOptions,
1427
+ postEnrichMiddleware: params.postEnrichMiddleware ?? [],
1428
+ handlers,
1429
+ includeCreateContext: false
1430
+ });
1431
+ return {
1432
+ middlewares: [
1433
+ createContext(schemaValidator),
1434
+ ...middlewares
1435
+ ],
1436
+ controllerHandler
1437
+ };
1438
+ }
1172
1439
  var ForklaunchExpressLikeRouter = class _ForklaunchExpressLikeRouter {
1173
1440
  constructor(basePath, schemaValidator, internal, postEnrichMiddleware, openTelemetryCollector, routerOptions) {
1174
1441
  this.basePath = basePath;
@@ -1208,21 +1475,6 @@ var ForklaunchExpressLikeRouter = class _ForklaunchExpressLikeRouter {
1208
1475
  * @param {PathParamHttpContractDetails<SV> | HttpContractDetails<SV>} contractDetails - The contract details.
1209
1476
  * @returns {MiddlewareHandler<SV>[]} - The resolved middlewares.
1210
1477
  */
1211
- #resolveMiddlewares(path, contractDetails, requestSchema, responseSchemas) {
1212
- return [
1213
- enrichDetails(
1214
- `${this.basePath}${path}`,
1215
- contractDetails,
1216
- requestSchema,
1217
- responseSchemas,
1218
- this.openTelemetryCollector,
1219
- () => this.routerOptions
1220
- ),
1221
- ...this.postEnrichMiddleware,
1222
- parse,
1223
- parseRequestAuth
1224
- ];
1225
- }
1226
1478
  /**
1227
1479
  * Parses and runs the controller handler with error handling.
1228
1480
  *
@@ -1263,126 +1515,6 @@ var ForklaunchExpressLikeRouter = class _ForklaunchExpressLikeRouter {
1263
1515
  * @returns {MiddlewareHandler<SV, P, ResBodyMap, ReqBody, ReqQuery, LocalsObj>} - The extracted controller handler.
1264
1516
  * @throws {Error} - Throws an error if the last argument is not a handler.
1265
1517
  */
1266
- #extractControllerHandler(handlers) {
1267
- const controllerHandler = handlers.pop();
1268
- if (typeof controllerHandler !== "function") {
1269
- throw new Error(
1270
- `Last argument must be a handler, received: ${controllerHandler}`
1271
- );
1272
- }
1273
- return controllerHandler;
1274
- }
1275
- #processContractDetailsIO(contractDetailsIO, params) {
1276
- const schemaValidator = this.schemaValidator;
1277
- const responseSchemas = {
1278
- 400: schemaValidator.string,
1279
- 401: schemaValidator.string,
1280
- 403: schemaValidator.string,
1281
- 404: schemaValidator.string,
1282
- 500: schemaValidator.string,
1283
- ...Object.fromEntries(
1284
- Object.entries(
1285
- discriminateResponseBodies(
1286
- this.schemaValidator,
1287
- contractDetailsIO.responses
1288
- )
1289
- ).map(([key, value]) => {
1290
- return [Number(key), value.schema];
1291
- })
1292
- )
1293
- };
1294
- return {
1295
- requestSchema: schemaValidator.compile(
1296
- schemaValidator.schemify({
1297
- ...params != null ? { params } : { params: schemaValidator.unknown },
1298
- ...contractDetailsIO.requestHeaders != null ? { headers: contractDetailsIO.requestHeaders } : { headers: schemaValidator.unknown },
1299
- ...contractDetailsIO.query != null ? { query: contractDetailsIO.query } : { query: schemaValidator.unknown },
1300
- ...contractDetailsIO.body != null ? {
1301
- body: discriminateBody(
1302
- this.schemaValidator,
1303
- contractDetailsIO.body
1304
- )?.schema
1305
- } : { body: schemaValidator.unknown }
1306
- })
1307
- ),
1308
- responseSchemas: {
1309
- ...contractDetailsIO.responseHeaders != null ? {
1310
- headers: schemaValidator.compile(
1311
- schemaValidator.schemify(contractDetailsIO.responseHeaders)
1312
- )
1313
- } : { headers: schemaValidator.unknown },
1314
- responses: Object.fromEntries(
1315
- Object.entries(responseSchemas).map(([key, value]) => {
1316
- return [
1317
- key,
1318
- schemaValidator.compile(schemaValidator.schemify(value))
1319
- ];
1320
- })
1321
- )
1322
- }
1323
- };
1324
- }
1325
- #compile(contractDetails) {
1326
- const schemaValidator = this.schemaValidator;
1327
- let requestSchema;
1328
- let responseSchemas;
1329
- if (hasVersionedSchema(contractDetails)) {
1330
- requestSchema = {};
1331
- responseSchemas = {};
1332
- Object.entries(contractDetails.versions ?? {}).forEach(
1333
- ([version, versionedContractDetails]) => {
1334
- const {
1335
- requestSchema: versionedRequestSchema,
1336
- responseSchemas: versionedResponseSchemas
1337
- } = this.#processContractDetailsIO(
1338
- versionedContractDetails,
1339
- contractDetails.params
1340
- );
1341
- if ((0, import_common9.isRecord)(requestSchema)) {
1342
- requestSchema = {
1343
- ...requestSchema,
1344
- [version]: versionedRequestSchema
1345
- };
1346
- }
1347
- if ((0, import_common9.isRecord)(responseSchemas)) {
1348
- responseSchemas = {
1349
- ...responseSchemas,
1350
- [version]: versionedResponseSchemas
1351
- };
1352
- }
1353
- }
1354
- );
1355
- } else {
1356
- const {
1357
- requestSchema: unversionedRequestSchema,
1358
- responseSchemas: unversionedResponseSchemas
1359
- } = this.#processContractDetailsIO(
1360
- {
1361
- ..."params" in contractDetails && contractDetails.params != null ? { params: contractDetails.params } : { params: schemaValidator.unknown },
1362
- ..."requestHeaders" in contractDetails && contractDetails.requestHeaders != null ? { requestHeaders: contractDetails.requestHeaders } : {
1363
- requestHeaders: schemaValidator.unknown
1364
- },
1365
- ..."responseHeaders" in contractDetails && contractDetails.responseHeaders != null ? { responseHeaders: contractDetails.responseHeaders } : {
1366
- responseHeaders: schemaValidator.unknown
1367
- },
1368
- ..."query" in contractDetails && contractDetails.query != null ? { query: contractDetails.query } : {
1369
- query: schemaValidator.unknown
1370
- },
1371
- ..."body" in contractDetails && contractDetails.body != null ? { body: contractDetails.body } : {
1372
- body: schemaValidator.unknown
1373
- },
1374
- responses: "responses" in contractDetails && contractDetails.responses != null ? contractDetails.responses : schemaValidator.unknown
1375
- },
1376
- contractDetails.params
1377
- );
1378
- requestSchema = unversionedRequestSchema;
1379
- responseSchemas = unversionedResponseSchemas;
1380
- }
1381
- return {
1382
- requestSchema,
1383
- responseSchemas
1384
- };
1385
- }
1386
1518
  /**
1387
1519
  * Fetches a route from the route map and executes it with the given parameters.
1388
1520
  *
@@ -1472,92 +1604,71 @@ var ForklaunchExpressLikeRouter = class _ForklaunchExpressLikeRouter {
1472
1604
  };
1473
1605
  }
1474
1606
  registerRoute(method, path, registrationMethod, contractDetailsOrMiddlewareOrTypedHandler, ...middlewareOrMiddlewareAndTypedHandler) {
1475
- if (isTypedHandler(contractDetailsOrMiddlewareOrTypedHandler)) {
1476
- const { contractDetails, handlers } = contractDetailsOrMiddlewareOrTypedHandler;
1477
- const router = this.registerRoute(method, path, registrationMethod, contractDetails, ...handlers);
1478
- return router;
1479
- } else {
1480
- const maybeTypedHandler = middlewareOrMiddlewareAndTypedHandler[middlewareOrMiddlewareAndTypedHandler.length - 1];
1481
- if (isTypedHandler(maybeTypedHandler)) {
1482
- const { contractDetails, handlers } = maybeTypedHandler;
1483
- const finalHandlers = [];
1484
- if (isExpressLikeSchemaHandler(contractDetailsOrMiddlewareOrTypedHandler)) {
1485
- finalHandlers.push(
1486
- contractDetailsOrMiddlewareOrTypedHandler
1487
- );
1488
- }
1489
- finalHandlers.push(...middlewareOrMiddlewareAndTypedHandler);
1490
- finalHandlers.push(...handlers);
1491
- const router = this.registerRoute(method, path, registrationMethod, contractDetails, ...finalHandlers);
1492
- return router;
1493
- } else {
1494
- if (isExpressLikeSchemaHandler(contractDetailsOrMiddlewareOrTypedHandler) || isTypedHandler(contractDetailsOrMiddlewareOrTypedHandler)) {
1495
- throw new Error("Contract details are not defined");
1496
- }
1497
- const contractDetails = contractDetailsOrMiddlewareOrTypedHandler;
1498
- const handlers = middlewareOrMiddlewareAndTypedHandler.filter(
1499
- (handler) => isExpressLikeSchemaHandler(handler)
1500
- );
1501
- if (!isHttpContractDetails(contractDetails) && !isPathParamHttpContractDetails(contractDetails)) {
1502
- throw new Error(
1503
- "Contract details are malformed for route definition"
1504
- );
1505
- }
1506
- if (contractDetails.versions) {
1507
- const parserTypes = Object.values(contractDetails.versions).map(
1508
- (version) => discriminateBody(this.schemaValidator, version.body)?.parserType
1509
- );
1510
- const allParserTypesSame = parserTypes.length === 0 || parserTypes.every((pt) => pt === parserTypes[0]);
1511
- if (!allParserTypesSame) {
1512
- throw new Error(
1513
- "All versioned contractDetails must have the same parsing type for body."
1514
- );
1515
- }
1516
- }
1517
- this.routes.push({
1518
- basePath: this.basePath,
1519
- path,
1520
- method,
1521
- contractDetails
1522
- });
1523
- const { requestSchema, responseSchemas } = this.#compile(contractDetails);
1524
- const controllerHandler = this.#extractControllerHandler(handlers);
1525
- const resolvedMiddlewares = this.#resolveMiddlewares(
1526
- path,
1527
- contractDetails,
1528
- requestSchema,
1529
- responseSchemas
1530
- ).concat(handlers);
1531
- registrationMethod.bind(this.internal)(
1532
- path,
1533
- ...resolvedMiddlewares,
1534
- this.#parseAndRunControllerHandler(controllerHandler)
1535
- );
1536
- (0, import_common9.toRecord)(this._fetchMap)[(0, import_common9.sanitizePathSlashes)(`${this.basePath}${path}`)] = {
1537
- ...this._fetchMap[(0, import_common9.sanitizePathSlashes)(`${this.basePath}${path}`)] ?? {},
1538
- [method.toUpperCase()]: contractDetails.versions ? Object.fromEntries(
1539
- Object.keys(contractDetails.versions).map((version) => [
1540
- version,
1541
- this.#localParamRequest(handlers, controllerHandler, version)
1542
- ])
1543
- ) : this.#localParamRequest(handlers, controllerHandler)
1544
- };
1545
- (0, import_common9.toRecord)(this.sdk)[(0, import_common9.toPrettyCamelCase)(contractDetails.name)] = contractDetails.versions ? Object.fromEntries(
1546
- Object.keys(contractDetails.versions).map((version) => [
1547
- version,
1548
- (req) => this.#localParamRequest(
1549
- handlers,
1550
- controllerHandler,
1551
- version
1552
- )(`${this.basePath}${path}`, req)
1553
- ])
1554
- ) : (req) => this.#localParamRequest(handlers, controllerHandler)(
1555
- `${this.basePath}${path}`,
1556
- req
1557
- );
1558
- return this;
1559
- }
1607
+ const { contractDetails, handlers } = resolveContractDetailsAndHandlers(
1608
+ contractDetailsOrMiddlewareOrTypedHandler,
1609
+ middlewareOrMiddlewareAndTypedHandler
1610
+ );
1611
+ validateContractDetails(contractDetails, this.schemaValidator);
1612
+ this.routes.push({
1613
+ basePath: this.basePath,
1614
+ path,
1615
+ method,
1616
+ contractDetails
1617
+ });
1618
+ const { requestSchema, responseSchemas } = compileRouteSchemas(
1619
+ contractDetails,
1620
+ this.schemaValidator
1621
+ );
1622
+ const { middlewares, controllerHandler } = resolveRouteMiddlewares({
1623
+ basePath: this.basePath,
1624
+ path,
1625
+ contractDetails,
1626
+ requestSchema,
1627
+ responseSchemas,
1628
+ openTelemetryCollector: this.openTelemetryCollector,
1629
+ routerOptions: this.routerOptions,
1630
+ postEnrichMiddleware: this.postEnrichMiddleware,
1631
+ includeCreateContext: false,
1632
+ handlers
1633
+ });
1634
+ registrationMethod.bind(this.internal)(
1635
+ path,
1636
+ ...middlewares,
1637
+ this.#parseAndRunControllerHandler(controllerHandler)
1638
+ );
1639
+ (0, import_common11.toRecord)(this._fetchMap)[(0, import_common11.sanitizePathSlashes)(`${this.basePath}${path}`)] = {
1640
+ ...this._fetchMap[(0, import_common11.sanitizePathSlashes)(`${this.basePath}${path}`)] ?? {},
1641
+ [method.toUpperCase()]: contractDetails.versions ? Object.fromEntries(
1642
+ Object.keys(contractDetails.versions).map((version) => [
1643
+ version,
1644
+ this.#localParamRequest(
1645
+ middlewares,
1646
+ controllerHandler,
1647
+ version
1648
+ )
1649
+ ])
1650
+ ) : this.#localParamRequest(
1651
+ middlewares,
1652
+ controllerHandler
1653
+ )
1654
+ };
1655
+ const contractDetailsName = contractDetails.name;
1656
+ if (contractDetailsName) {
1657
+ (0, import_common11.toRecord)(this.sdk)[(0, import_common11.toPrettyCamelCase)(contractDetailsName)] = contractDetails.versions ? Object.fromEntries(
1658
+ Object.keys(contractDetails.versions).map((version) => [
1659
+ version,
1660
+ (req) => this.#localParamRequest(
1661
+ middlewares,
1662
+ controllerHandler,
1663
+ version
1664
+ )(`${this.basePath}${path}`, req)
1665
+ ])
1666
+ ) : (req) => this.#localParamRequest(
1667
+ middlewares,
1668
+ controllerHandler
1669
+ )(`${this.basePath}${path}`, req);
1560
1670
  }
1671
+ return this;
1561
1672
  }
1562
1673
  #extractHandlers(handlers, processMiddleware) {
1563
1674
  const last = handlers.pop();
@@ -1641,10 +1752,10 @@ var ForklaunchExpressLikeRouter = class _ForklaunchExpressLikeRouter {
1641
1752
  }
1642
1753
  addRouterToSdk(router) {
1643
1754
  Object.entries(router._fetchMap).map(
1644
- ([key, value]) => (0, import_common9.toRecord)(this._fetchMap)[(0, import_common9.sanitizePathSlashes)(`${this.basePath}${key}`)] = value
1755
+ ([key, value]) => (0, import_common11.toRecord)(this._fetchMap)[(0, import_common11.sanitizePathSlashes)(`${this.basePath}${key}`)] = value
1645
1756
  );
1646
- const existingSdk = this.sdk[router.sdkName ?? (0, import_common9.toPrettyCamelCase)(router.basePath)];
1647
- (0, import_common9.toRecord)(this.sdk)[router.sdkName ?? (0, import_common9.toPrettyCamelCase)(router.basePath)] = {
1757
+ const existingSdk = this.sdk[router.sdkName ?? (0, import_common11.toPrettyCamelCase)(router.basePath)];
1758
+ (0, import_common11.toRecord)(this.sdk)[router.sdkName ?? (0, import_common11.toPrettyCamelCase)(router.basePath)] = {
1648
1759
  ...typeof existingSdk === "object" ? existingSdk : {},
1649
1760
  ...router.sdk
1650
1761
  };
@@ -1936,7 +2047,7 @@ var ForklaunchExpressLikeRouter = class _ForklaunchExpressLikeRouter {
1936
2047
  unpackSdks(sdks, path, routerUniquenessCache) {
1937
2048
  Object.entries(sdks).forEach(([key, maybeHandler]) => {
1938
2049
  if (isSdkHandler(maybeHandler)) {
1939
- const cacheKey = (0, import_common9.hashString)((0, import_common9.safeStringify)(maybeHandler));
2050
+ const cacheKey = (0, import_common11.hashString)((0, import_common11.safeStringify)(maybeHandler));
1940
2051
  if (routerUniquenessCache.has(cacheKey)) {
1941
2052
  throw new Error(`SDK handler ${key} is already registered`);
1942
2053
  }
@@ -2030,6 +2141,30 @@ function isPortBound(port, host = "localhost") {
2030
2141
  });
2031
2142
  }
2032
2143
 
2144
+ // src/http/generateHmacAuthHeaders.ts
2145
+ var import_crypto2 = require("crypto");
2146
+ function generateHmacAuthHeaders({
2147
+ secretKey,
2148
+ method,
2149
+ path,
2150
+ body,
2151
+ keyId = "default"
2152
+ }) {
2153
+ const timestamp = /* @__PURE__ */ new Date();
2154
+ const nonce = (0, import_crypto2.randomUUID)();
2155
+ const signature = createHmacToken({
2156
+ secretKey,
2157
+ method,
2158
+ path,
2159
+ timestamp,
2160
+ nonce,
2161
+ body
2162
+ });
2163
+ return {
2164
+ authorization: `HMAC keyId=${keyId} ts=${timestamp.toISOString()} nonce=${nonce} signature=${signature}`
2165
+ };
2166
+ }
2167
+
2033
2168
  // src/http/guards/isPath.ts
2034
2169
  function isPath(path) {
2035
2170
  return path.startsWith("/");
@@ -3100,7 +3235,7 @@ var getCodeForStatus = (status) => {
3100
3235
  var httpStatusCodes_default = HTTPStatuses;
3101
3236
 
3102
3237
  // src/http/mcpGenerator/mcpGenerator.ts
3103
- var import_common10 = require("@forklaunch/common");
3238
+ var import_common12 = require("@forklaunch/common");
3104
3239
  var import_fastmcp_fork = require("@forklaunch/fastmcp-fork");
3105
3240
  var import_zod = require("@forklaunch/validator/zod");
3106
3241
 
@@ -3235,7 +3370,7 @@ function generateMcpServer(schemaValidator, protocol, host, port, version, appli
3235
3370
  if (discriminatedBody) {
3236
3371
  switch (discriminatedBody.parserType) {
3237
3372
  case "json": {
3238
- parsedBody = (0, import_common10.safeStringify)(body);
3373
+ parsedBody = (0, import_common12.safeStringify)(body);
3239
3374
  break;
3240
3375
  }
3241
3376
  case "text": {
@@ -3243,12 +3378,12 @@ function generateMcpServer(schemaValidator, protocol, host, port, version, appli
3243
3378
  break;
3244
3379
  }
3245
3380
  case "file": {
3246
- parsedBody = Buffer.from((0, import_common10.safeStringify)(body));
3381
+ parsedBody = Buffer.from((0, import_common12.safeStringify)(body));
3247
3382
  break;
3248
3383
  }
3249
3384
  case "multipart": {
3250
3385
  const formData = new FormData();
3251
- if ((0, import_common10.isRecord)(body)) {
3386
+ if ((0, import_common12.isRecord)(body)) {
3252
3387
  for (const key in body) {
3253
3388
  if (typeof body[key] === "string") {
3254
3389
  if (schemaValidator.isInstanceOf(
@@ -3273,11 +3408,11 @@ function generateMcpServer(schemaValidator, protocol, host, port, version, appli
3273
3408
  break;
3274
3409
  }
3275
3410
  case "urlEncoded": {
3276
- if ((0, import_common10.isRecord)(body)) {
3411
+ if ((0, import_common12.isRecord)(body)) {
3277
3412
  parsedBody = new URLSearchParams(
3278
3413
  Object.entries(body).map(([key, value]) => [
3279
3414
  key,
3280
- (0, import_common10.safeStringify)(value)
3415
+ (0, import_common12.safeStringify)(value)
3281
3416
  ])
3282
3417
  );
3283
3418
  } else {
@@ -3286,8 +3421,8 @@ function generateMcpServer(schemaValidator, protocol, host, port, version, appli
3286
3421
  break;
3287
3422
  }
3288
3423
  default: {
3289
- (0, import_common10.isNever)(discriminatedBody.parserType);
3290
- parsedBody = (0, import_common10.safeStringify)(body);
3424
+ (0, import_common12.isNever)(discriminatedBody.parserType);
3425
+ parsedBody = (0, import_common12.safeStringify)(body);
3291
3426
  break;
3292
3427
  }
3293
3428
  }
@@ -3296,7 +3431,7 @@ function generateMcpServer(schemaValidator, protocol, host, port, version, appli
3296
3431
  const queryString = new URLSearchParams(
3297
3432
  Object.entries(query).map(([key, value]) => [
3298
3433
  key,
3299
- (0, import_common10.safeStringify)(value)
3434
+ (0, import_common12.safeStringify)(value)
3300
3435
  ])
3301
3436
  ).toString();
3302
3437
  url += queryString ? `?${queryString}` : "";
@@ -3329,7 +3464,7 @@ function generateMcpServer(schemaValidator, protocol, host, port, version, appli
3329
3464
  content: [
3330
3465
  {
3331
3466
  type: "text",
3332
- text: (0, import_common10.safeStringify)(await response.json())
3467
+ text: (0, import_common12.safeStringify)(await response.json())
3333
3468
  }
3334
3469
  ]
3335
3470
  };
@@ -3471,7 +3606,7 @@ Correlation id: ${req.context.correlationId ?? "No correlation ID"}`
3471
3606
  }
3472
3607
  return;
3473
3608
  case "warning":
3474
- req.openTelemetryCollector.warn(
3609
+ req.openTelemetryCollector?.warn(
3475
3610
  `Invalid response:
3476
3611
  ${parseErrors.join("\n\n")}`
3477
3612
  );
@@ -3484,18 +3619,19 @@ ${parseErrors.join("\n\n")}`
3484
3619
  }
3485
3620
 
3486
3621
  // src/http/middleware/response/enrichExpressLikeSend.middleware.ts
3487
- var import_common12 = require("@forklaunch/common");
3622
+ var import_common14 = require("@forklaunch/common");
3488
3623
  var import_stream = require("stream");
3489
3624
 
3490
3625
  // src/http/telemetry/recordMetric.ts
3491
- var import_common11 = require("@forklaunch/common");
3626
+ var import_common13 = require("@forklaunch/common");
3492
3627
  var import_semantic_conventions3 = require("@opentelemetry/semantic-conventions");
3493
3628
  function recordMetric(req, res) {
3494
3629
  if (res.metricRecorded) {
3495
3630
  return;
3496
3631
  }
3497
3632
  httpRequestsTotalCounter.add(1, {
3498
- [import_semantic_conventions3.ATTR_SERVICE_NAME]: (0, import_common11.getEnvVar)("OTEL_SERVICE_NAME"),
3633
+ [import_semantic_conventions3.ATTR_SERVICE_NAME]: (0, import_common13.getEnvVar)("OTEL_SERVICE_NAME"),
3634
+ [ATTR_APPLICATION_ID]: (0, import_common13.getEnvVar)("OTEL_APPLICATION_ID"),
3499
3635
  [ATTR_API_NAME]: req.contractDetails?.name,
3500
3636
  [import_semantic_conventions3.ATTR_HTTP_REQUEST_METHOD]: req.method,
3501
3637
  [import_semantic_conventions3.ATTR_HTTP_ROUTE]: req.originalPath,
@@ -3514,7 +3650,7 @@ function enrichExpressLikeSend(instance, req, res, originalOperation, originalSe
3514
3650
  if (res.statusCode === 404) {
3515
3651
  res.type("text/plain");
3516
3652
  res.status(404);
3517
- req.openTelemetryCollector.error("Not Found");
3653
+ req.openTelemetryCollector?.error("Not Found");
3518
3654
  originalSend.call(instance, "Not Found");
3519
3655
  errorSent = true;
3520
3656
  }
@@ -3544,8 +3680,8 @@ function enrichExpressLikeSend(instance, req, res, originalOperation, originalSe
3544
3680
  `attachment; filename="${data.name}"`
3545
3681
  );
3546
3682
  }
3547
- if ((0, import_common12.isNodeJsWriteableStream)(res)) {
3548
- import_stream.Readable.from((0, import_common12.readableStreamToAsyncIterable)(data.stream())).pipe(
3683
+ if ((0, import_common14.isNodeJsWriteableStream)(res)) {
3684
+ import_stream.Readable.from((0, import_common14.readableStreamToAsyncIterable)(data.stream())).pipe(
3549
3685
  res
3550
3686
  );
3551
3687
  } else {
@@ -3554,7 +3690,7 @@ function enrichExpressLikeSend(instance, req, res, originalOperation, originalSe
3554
3690
  originalSend.call(instance, "Not a NodeJS WritableStream");
3555
3691
  errorSent = true;
3556
3692
  }
3557
- } else if ((0, import_common12.isAsyncGenerator)(data)) {
3693
+ } else if ((0, import_common14.isAsyncGenerator)(data)) {
3558
3694
  let firstPass = true;
3559
3695
  const transformer = new import_stream.Transform({
3560
3696
  objectMode: true,
@@ -3569,7 +3705,7 @@ function enrichExpressLikeSend(instance, req, res, originalOperation, originalSe
3569
3705
  ------------------
3570
3706
  ${res.locals.errorMessage}`;
3571
3707
  }
3572
- req.openTelemetryCollector.error(errorString);
3708
+ req.openTelemetryCollector?.error(errorString);
3573
3709
  res.type("text/plain");
3574
3710
  res.status(500);
3575
3711
  originalSend.call(instance, errorString);
@@ -3578,7 +3714,7 @@ ${res.locals.errorMessage}`;
3578
3714
  } else {
3579
3715
  let data2 = "";
3580
3716
  for (const [key, value] of Object.entries(chunk)) {
3581
- data2 += `${key}: ${typeof value === "string" ? value : (0, import_common12.safeStringify)(value)}
3717
+ data2 += `${key}: ${typeof value === "string" ? value : (0, import_common14.safeStringify)(value)}
3582
3718
  `;
3583
3719
  }
3584
3720
  data2 += "\n";
@@ -3589,7 +3725,7 @@ ${res.locals.errorMessage}`;
3589
3725
  } else if (!errorSent) {
3590
3726
  let data2 = "";
3591
3727
  for (const [key, value] of Object.entries(chunk)) {
3592
- data2 += `${key}: ${typeof value === "string" ? value : (0, import_common12.safeStringify)(value)}
3728
+ data2 += `${key}: ${typeof value === "string" ? value : (0, import_common14.safeStringify)(value)}
3593
3729
  `;
3594
3730
  }
3595
3731
  data2 += "\n";
@@ -3597,7 +3733,7 @@ ${res.locals.errorMessage}`;
3597
3733
  }
3598
3734
  }
3599
3735
  });
3600
- if ((0, import_common12.isNodeJsWriteableStream)(res)) {
3736
+ if ((0, import_common14.isNodeJsWriteableStream)(res)) {
3601
3737
  import_stream.Readable.from(data).pipe(transformer).pipe(res);
3602
3738
  } else {
3603
3739
  res.type("text/plain");
@@ -3608,7 +3744,7 @@ ${res.locals.errorMessage}`;
3608
3744
  } else {
3609
3745
  const parserType = responseBodies?.[Number(res.statusCode)]?.parserType;
3610
3746
  res.bodyData = data;
3611
- if ((0, import_common12.isRecord)(data)) {
3747
+ if ((0, import_common14.isRecord)(data)) {
3612
3748
  switch (parserType) {
3613
3749
  case "json":
3614
3750
  res.bodyData = "json" in data ? data.json : data;
@@ -3629,7 +3765,7 @@ ${res.locals.errorMessage}`;
3629
3765
  res.bodyData = data;
3630
3766
  break;
3631
3767
  default:
3632
- (0, import_common12.isNever)(parserType);
3768
+ (0, import_common14.isNever)(parserType);
3633
3769
  res.bodyData = data;
3634
3770
  break;
3635
3771
  }
@@ -3642,7 +3778,7 @@ ${res.locals.errorMessage}`;
3642
3778
  ------------------
3643
3779
  ${res.locals.errorMessage}`;
3644
3780
  }
3645
- req.openTelemetryCollector.error(errorString);
3781
+ req.openTelemetryCollector?.error(errorString);
3646
3782
  res.type("text/plain");
3647
3783
  res.status(500);
3648
3784
  originalSend.call(instance, errorString);
@@ -3664,8 +3800,8 @@ ${res.locals.errorMessage}`;
3664
3800
  }
3665
3801
 
3666
3802
  // src/http/openApiV3Generator/openApiV3Generator.ts
3667
- var import_common13 = require("@forklaunch/common");
3668
- var OPENAPI_DEFAULT_VERSION = Symbol("default");
3803
+ var import_common15 = require("@forklaunch/common");
3804
+ var OPENAPI_DEFAULT_VERSION = /* @__PURE__ */ Symbol("default");
3669
3805
  function toUpperCase(str) {
3670
3806
  return str.charAt(0).toUpperCase() + str.slice(1);
3671
3807
  }
@@ -3887,7 +4023,7 @@ function generateOpenApiSpecs(schemaValidator, serverUrls, serverDescriptions, a
3887
4023
  ].forEach(({ fullPath, router }) => {
3888
4024
  const controllerName = transformBasePath(fullPath);
3889
4025
  router.routes.forEach((route) => {
3890
- const openApiPath = (0, import_common13.openApiCompliantPath)(
4026
+ const openApiPath = (0, import_common15.openApiCompliantPath)(
3891
4027
  `${fullPath}${route.path === "/" ? "" : route.path}`
3892
4028
  );
3893
4029
  const { name, summary, params, versions, auth, options: options2 } = route.contractDetails;
@@ -4014,6 +4150,7 @@ function metricsDefinitions(metrics2) {
4014
4150
  // Annotate the CommonJS export names for ESM import in node:
4015
4151
  0 && (module.exports = {
4016
4152
  ATTR_API_NAME,
4153
+ ATTR_APPLICATION_ID,
4017
4154
  ATTR_CORRELATION_ID,
4018
4155
  ATTR_HTTP_REQUEST_METHOD,
4019
4156
  ATTR_HTTP_RESPONSE_STATUS_CODE,
@@ -4025,6 +4162,7 @@ function metricsDefinitions(metrics2) {
4025
4162
  OPENAPI_DEFAULT_VERSION,
4026
4163
  OpenTelemetryCollector,
4027
4164
  PinoLogger,
4165
+ createContext,
4028
4166
  createHmacToken,
4029
4167
  delete_,
4030
4168
  discriminateAuthMethod,
@@ -4032,6 +4170,8 @@ function metricsDefinitions(metrics2) {
4032
4170
  discriminateResponseBodies,
4033
4171
  enrichExpressLikeSend,
4034
4172
  evaluateTelemetryOptions,
4173
+ extractRouteHandlers,
4174
+ generateHmacAuthHeaders,
4035
4175
  generateMcpServer,
4036
4176
  generateOpenApiSpecs,
4037
4177
  get,