@forklaunch/core 0.14.8 → 0.14.11-beta.1

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
@@ -102,7 +102,7 @@ function cors(corsOptions) {
102
102
  }
103
103
 
104
104
  // src/http/router/expressLikeRouter.ts
105
- var import_common8 = require("@forklaunch/common");
105
+ var import_common9 = require("@forklaunch/common");
106
106
 
107
107
  // src/http/guards/hasVersionedSchema.ts
108
108
  function hasVersionedSchema(contractDetails) {
@@ -157,12 +157,14 @@ function isTypedHandler(maybeTypedHandler) {
157
157
  }
158
158
 
159
159
  // src/http/middleware/request/auth.middleware.ts
160
- var import_common2 = require("@forklaunch/common");
160
+ var import_common3 = require("@forklaunch/common");
161
+ var import_validator = require("@forklaunch/validator");
161
162
 
162
163
  // src/http/discriminateAuthMethod.ts
163
164
  var import_jose = require("jose");
164
165
 
165
166
  // src/http/createHmacToken.ts
167
+ var import_common2 = require("@forklaunch/common");
166
168
  var import_crypto = require("crypto");
167
169
  function createHmacToken({
168
170
  method,
@@ -173,11 +175,14 @@ function createHmacToken({
173
175
  secretKey
174
176
  }) {
175
177
  const hmac = (0, import_crypto.createHmac)("sha256", secretKey);
176
- hmac.update(`${method}
178
+ const bodyString = body ? `${(0, import_common2.safeStringify)(body)}
179
+ ` : void 0;
180
+ hmac.update(
181
+ `${method}
177
182
  ${path}
178
- ${body}
179
- ${timestamp}
180
- ${nonce}`);
183
+ ${bodyString}${timestamp.toISOString()}
184
+ ${nonce}`
185
+ );
181
186
  return hmac.digest("base64");
182
187
  }
183
188
 
@@ -197,6 +202,12 @@ function isJwtAuthMethod(maybeJwtAuthMethod) {
197
202
  }
198
203
 
199
204
  // src/http/discriminateAuthMethod.ts
205
+ var DEFAULT_TTL = 60 * 1e3 * 5;
206
+ var memoizedJwks = {
207
+ value: null,
208
+ lastUpdated: null,
209
+ ttl: DEFAULT_TTL
210
+ };
200
211
  async function discriminateAuthMethod(auth) {
201
212
  let authMethod;
202
213
  if (isBasicAuthMethod(auth)) {
@@ -221,8 +232,17 @@ async function discriminateAuthMethod(auth) {
221
232
  } else {
222
233
  let jwks;
223
234
  if ("jwksPublicKeyUrl" in jwt) {
224
- const jwksResponse = await fetch(jwt.jwksPublicKeyUrl);
225
- jwks = (await jwksResponse.json()).keys;
235
+ if (memoizedJwks.value && memoizedJwks.lastUpdated && Date.now() - memoizedJwks.lastUpdated.getTime() < memoizedJwks.ttl) {
236
+ jwks = memoizedJwks.value;
237
+ } else {
238
+ const jwksResponse = await fetch(jwt.jwksPublicKeyUrl);
239
+ jwks = (await jwksResponse.json()).keys;
240
+ memoizedJwks.value = jwks;
241
+ memoizedJwks.lastUpdated = /* @__PURE__ */ new Date();
242
+ memoizedJwks.ttl = parseInt(
243
+ jwksResponse.headers.get("cache-control")?.split("=")[1] ?? `${DEFAULT_TTL / 1e3}`
244
+ ) * 1e3;
245
+ }
226
246
  } else if ("jwksPublicKey" in jwt) {
227
247
  jwks = [jwt.jwksPublicKey];
228
248
  }
@@ -232,6 +252,9 @@ async function discriminateAuthMethod(auth) {
232
252
  const { payload } = await (0, import_jose.jwtVerify)(token, key);
233
253
  return payload;
234
254
  } catch {
255
+ memoizedJwks.value = null;
256
+ memoizedJwks.lastUpdated = null;
257
+ memoizedJwks.ttl = DEFAULT_TTL;
235
258
  continue;
236
259
  }
237
260
  }
@@ -249,7 +272,15 @@ async function discriminateAuthMethod(auth) {
249
272
  type: "hmac",
250
273
  auth: {
251
274
  secretKeys: auth.hmac.secretKeys,
252
- verificationFunction: async (method, path, body, timestamp, nonce, signature, secretKey) => {
275
+ verificationFunction: async ({
276
+ method,
277
+ path,
278
+ body,
279
+ timestamp,
280
+ nonce,
281
+ signature,
282
+ secretKey
283
+ }) => {
253
284
  return createHmacToken({
254
285
  method,
255
286
  path,
@@ -327,7 +358,7 @@ function parseHmacTokenPart(part, expectedKey) {
327
358
  if (key !== expectedKey || rest.length === 0) return void 0;
328
359
  return rest.join("=");
329
360
  }
330
- async function checkAuthorizationToken(authorizationMethod, globalOptions, authorizationToken, req) {
361
+ async function checkAuthorizationToken(req, authorizationMethod, authorizationToken, globalOptions) {
331
362
  if (authorizationMethod == null) {
332
363
  return void 0;
333
364
  }
@@ -342,7 +373,7 @@ async function checkAuthorizationToken(authorizationMethod, globalOptions, autho
342
373
  if (!tokenParts.length || !tokenPrefix) {
343
374
  return invalidAuthorizationTokenFormat;
344
375
  }
345
- let resourceId;
376
+ let sessionPayload;
346
377
  const { type, auth } = await discriminateAuthMethod(
347
378
  collapsedAuthorizationMethod
348
379
  );
@@ -362,19 +393,19 @@ async function checkAuthorizationToken(authorizationMethod, globalOptions, autho
362
393
  if (!parsedKeyId || !parsedTimestamp || !parsedNonce || !parsedSignature) {
363
394
  return invalidAuthorizationTokenFormat;
364
395
  }
365
- const verificationResult = await auth.verificationFunction(
366
- req?.method ?? "",
367
- req?.path ?? "",
368
- JSON.stringify(req?.body ?? ""),
369
- parsedTimestamp,
370
- parsedNonce,
371
- parsedSignature,
372
- collapsedAuthorizationMethod.hmac.secretKeys[parsedKeyId]
373
- );
396
+ const verificationResult = await auth.verificationFunction({
397
+ method: req?.method ?? "",
398
+ path: req?.path ?? "",
399
+ body: req?.body,
400
+ timestamp: new Date(parsedTimestamp),
401
+ nonce: parsedNonce,
402
+ signature: parsedSignature,
403
+ secretKey: collapsedAuthorizationMethod.hmac.secretKeys[parsedKeyId]
404
+ });
374
405
  if (!verificationResult) {
375
406
  return invalidAuthorizationSignature;
376
407
  }
377
- resourceId = null;
408
+ sessionPayload = null;
378
409
  break;
379
410
  }
380
411
  case "jwt": {
@@ -387,7 +418,7 @@ async function checkAuthorizationToken(authorizationMethod, globalOptions, autho
387
418
  if (!decodedJwt) {
388
419
  return invalidAuthorizationToken;
389
420
  }
390
- resourceId = decodedJwt;
421
+ sessionPayload = decodedJwt;
391
422
  } catch (error) {
392
423
  req?.openTelemetryCollector.error(error);
393
424
  return invalidAuthorizationToken;
@@ -400,7 +431,7 @@ async function checkAuthorizationToken(authorizationMethod, globalOptions, autho
400
431
  return invalidAuthorizationTokenFormat;
401
432
  }
402
433
  if (auth.decodeResource) {
403
- resourceId = await auth.decodeResource(token);
434
+ sessionPayload = await auth.decodeResource(token);
404
435
  } else {
405
436
  const [username, password] = Buffer.from(token, "base64").toString("utf-8").split(":");
406
437
  if (!username || !password) {
@@ -409,26 +440,39 @@ async function checkAuthorizationToken(authorizationMethod, globalOptions, autho
409
440
  if (!auth.login(username, password)) {
410
441
  return invalidAuthorizationLogin;
411
442
  }
412
- resourceId = {
443
+ sessionPayload = {
413
444
  sub: username
414
445
  };
415
446
  }
416
447
  break;
417
448
  }
418
449
  default:
419
- (0, import_common2.isNever)(type);
450
+ (0, import_common3.isNever)(type);
420
451
  return [401, "Invalid Authorization method."];
421
452
  }
422
- if (isHmacMethod(collapsedAuthorizationMethod) && resourceId == null) {
453
+ if (isHmacMethod(collapsedAuthorizationMethod) && sessionPayload == null) {
423
454
  return;
424
455
  }
425
- if (resourceId == null) {
456
+ if (sessionPayload == null) {
426
457
  return invalidAuthorizationToken;
427
458
  }
459
+ req.session = sessionPayload;
460
+ if (collapsedAuthorizationMethod.sessionSchema) {
461
+ const parsedSession = req.schemaValidator.parse(
462
+ collapsedAuthorizationMethod.sessionSchema,
463
+ sessionPayload
464
+ );
465
+ if (!parsedSession.ok) {
466
+ return [
467
+ 400,
468
+ `Invalid session: ${(0, import_validator.prettyPrintParseErrors)(parsedSession.errors, "Session")}`
469
+ ];
470
+ }
471
+ }
428
472
  if (hasScopeChecks(collapsedAuthorizationMethod)) {
429
473
  if (collapsedAuthorizationMethod.surfaceScopes) {
430
474
  const resourceScopes = await collapsedAuthorizationMethod.surfaceScopes(
431
- resourceId,
475
+ sessionPayload,
432
476
  req
433
477
  );
434
478
  if (collapsedAuthorizationMethod.scopeHeirarchy) {
@@ -447,7 +491,7 @@ async function checkAuthorizationToken(authorizationMethod, globalOptions, autho
447
491
  return [500, "No permission surfacing function provided."];
448
492
  }
449
493
  const resourcePermissions = await collapsedAuthorizationMethod.surfacePermissions(
450
- resourceId,
494
+ sessionPayload,
451
495
  req
452
496
  );
453
497
  if ("allowedPermissions" in collapsedAuthorizationMethod && collapsedAuthorizationMethod.allowedPermissions) {
@@ -469,7 +513,7 @@ async function checkAuthorizationToken(authorizationMethod, globalOptions, autho
469
513
  return [500, "No role surfacing function provided."];
470
514
  }
471
515
  const resourceRoles = await collapsedAuthorizationMethod.surfaceRoles(
472
- resourceId,
516
+ sessionPayload,
473
517
  req
474
518
  );
475
519
  if ("allowedRoles" in collapsedAuthorizationMethod && collapsedAuthorizationMethod.allowedRoles) {
@@ -489,10 +533,10 @@ async function checkAuthorizationToken(authorizationMethod, globalOptions, autho
489
533
  async function parseRequestAuth(req, res, next) {
490
534
  const auth = req.contractDetails.auth;
491
535
  const [error, message] = await checkAuthorizationToken(
536
+ req,
492
537
  auth,
493
- req._globalOptions?.()?.auth,
494
538
  req.headers[auth?.headerName ?? "Authorization"] || req.headers[auth?.headerName ?? "authorization"],
495
- req
539
+ req._globalOptions?.()?.auth
496
540
  ) ?? [];
497
541
  if (error != null) {
498
542
  res.type("text/plain");
@@ -503,7 +547,7 @@ async function parseRequestAuth(req, res, next) {
503
547
  }
504
548
 
505
549
  // src/http/middleware/request/createContext.middleware.ts
506
- var import_common3 = require("@forklaunch/common");
550
+ var import_common4 = require("@forklaunch/common");
507
551
  var import_api = require("@opentelemetry/api");
508
552
  var import_uuid = require("uuid");
509
553
 
@@ -530,7 +574,7 @@ function createContext(schemaValidator) {
530
574
  req.context.span?.setAttribute(ATTR_CORRELATION_ID, correlationId);
531
575
  req.context.span?.setAttribute(
532
576
  import_semantic_conventions.ATTR_SERVICE_NAME,
533
- (0, import_common3.getEnvVar)("OTEL_SERVICE_NAME")
577
+ (0, import_common4.getEnvVar)("OTEL_SERVICE_NAME")
534
578
  );
535
579
  }
536
580
  next?.();
@@ -538,10 +582,10 @@ function createContext(schemaValidator) {
538
582
  }
539
583
 
540
584
  // src/http/middleware/request/enrichDetails.middleware.ts
541
- var import_common6 = require("@forklaunch/common");
585
+ var import_common7 = require("@forklaunch/common");
542
586
 
543
587
  // src/http/telemetry/openTelemetryCollector.ts
544
- var import_common5 = require("@forklaunch/common");
588
+ var import_common6 = require("@forklaunch/common");
545
589
  var import_opentelemetry_instrumentation_hyper_express = require("@forklaunch/opentelemetry-instrumentation-hyper-express");
546
590
  var import_api3 = require("@opentelemetry/api");
547
591
  var import_exporter_logs_otlp_http = require("@opentelemetry/exporter-logs-otlp-http");
@@ -562,7 +606,7 @@ function isForklaunchRequest(request) {
562
606
  }
563
607
 
564
608
  // src/http/telemetry/pinoLogger.ts
565
- var import_common4 = require("@forklaunch/common");
609
+ var import_common5 = require("@forklaunch/common");
566
610
  var import_api2 = require("@opentelemetry/api");
567
611
  var import_api_logs = require("@opentelemetry/api-logs");
568
612
  var import_pino = __toESM(require("pino"));
@@ -594,7 +638,7 @@ function mapSeverity(level) {
594
638
  case "fatal":
595
639
  return 21;
596
640
  default:
597
- (0, import_common4.isNever)(level);
641
+ (0, import_common5.isNever)(level);
598
642
  return 0;
599
643
  }
600
644
  }
@@ -787,24 +831,24 @@ var OpenTelemetryCollector = class {
787
831
  return this.metrics[metricId];
788
832
  }
789
833
  };
790
- import_dotenv.default.config({ path: (0, import_common5.getEnvVar)("DOTENV_FILE_PATH") });
834
+ import_dotenv.default.config({ path: (0, import_common6.getEnvVar)("DOTENV_FILE_PATH") });
791
835
  new import_sdk_node.NodeSDK({
792
836
  resource: (0, import_resources.resourceFromAttributes)({
793
- [import_semantic_conventions2.ATTR_SERVICE_NAME]: (0, import_common5.getEnvVar)("OTEL_SERVICE_NAME")
837
+ [import_semantic_conventions2.ATTR_SERVICE_NAME]: (0, import_common6.getEnvVar)("OTEL_SERVICE_NAME")
794
838
  }),
795
839
  traceExporter: new import_exporter_trace_otlp_http.OTLPTraceExporter({
796
- url: `${(0, import_common5.getEnvVar)("OTEL_EXPORTER_OTLP_ENDPOINT") ?? "http://localhost:4318"}/v1/traces`
840
+ url: `${(0, import_common6.getEnvVar)("OTEL_EXPORTER_OTLP_ENDPOINT") ?? "http://localhost:4318"}/v1/traces`
797
841
  }),
798
842
  metricReader: new import_sdk_metrics.PeriodicExportingMetricReader({
799
843
  exporter: new import_exporter_metrics_otlp_http.OTLPMetricExporter({
800
- url: `${(0, import_common5.getEnvVar)("OTEL_EXPORTER_OTLP_ENDPOINT") ?? "http://localhost:4318"}/v1/metrics`
844
+ url: `${(0, import_common6.getEnvVar)("OTEL_EXPORTER_OTLP_ENDPOINT") ?? "http://localhost:4318"}/v1/metrics`
801
845
  }),
802
846
  exportIntervalMillis: 5e3
803
847
  }),
804
848
  logRecordProcessors: [
805
849
  new import_sdk_logs.BatchLogRecordProcessor(
806
850
  new import_exporter_logs_otlp_http.OTLPLogExporter({
807
- url: `${(0, import_common5.getEnvVar)("OTEL_EXPORTER_OTLP_ENDPOINT") ?? "http://localhost:4318"}/v1/logs`
851
+ url: `${(0, import_common6.getEnvVar)("OTEL_EXPORTER_OTLP_ENDPOINT") ?? "http://localhost:4318"}/v1/logs`
808
852
  })
809
853
  )
810
854
  ],
@@ -813,7 +857,7 @@ new import_sdk_node.NodeSDK({
813
857
  applyCustomAttributesOnSpan: (span, request) => {
814
858
  span.setAttribute(
815
859
  import_semantic_conventions2.ATTR_SERVICE_NAME,
816
- (0, import_common5.getEnvVar)("OTEL_SERVICE_NAME") ?? "unknown"
860
+ (0, import_common6.getEnvVar)("OTEL_SERVICE_NAME") ?? "unknown"
817
861
  );
818
862
  if (isForklaunchRequest(request)) {
819
863
  span.setAttribute(ATTR_API_NAME, request.contractDetails?.name);
@@ -825,10 +869,10 @@ new import_sdk_node.NodeSDK({
825
869
  new import_opentelemetry_instrumentation_hyper_express.HyperExpressInstrumentation()
826
870
  ]
827
871
  }).start();
828
- var httpRequestsTotalCounter = import_api3.metrics.getMeter((0, import_common5.getEnvVar)("OTEL_SERVICE_NAME") || "unknown").createCounter("http_requests_total", {
872
+ var httpRequestsTotalCounter = import_api3.metrics.getMeter((0, import_common6.getEnvVar)("OTEL_SERVICE_NAME") || "unknown").createCounter("http_requests_total", {
829
873
  description: "Number of HTTP requests"
830
874
  });
831
- var httpServerDurationHistogram = import_api3.metrics.getMeter((0, import_common5.getEnvVar)("OTEL_SERVICE_NAME") || "unknown").createHistogram("http_server_duration", {
875
+ var httpServerDurationHistogram = import_api3.metrics.getMeter((0, import_common6.getEnvVar)("OTEL_SERVICE_NAME") || "unknown").createHistogram("http_server_duration", {
832
876
  description: "Duration of HTTP server requests",
833
877
  unit: "s"
834
878
  });
@@ -848,7 +892,7 @@ function enrichDetails(path, contractDetails, requestSchema, responseSchemas, op
848
892
  const [seconds, nanoseconds] = process.hrtime(startTime);
849
893
  const durationMs = seconds + nanoseconds / 1e9;
850
894
  httpServerDurationHistogram.record(durationMs, {
851
- [import_semantic_conventions.ATTR_SERVICE_NAME]: (0, import_common6.getEnvVar)("OTEL_SERVICE_NAME") || "unknown",
895
+ [import_semantic_conventions.ATTR_SERVICE_NAME]: (0, import_common7.getEnvVar)("OTEL_SERVICE_NAME") || "unknown",
852
896
  [ATTR_API_NAME]: req.contractDetails?.name || "unknown",
853
897
  [import_semantic_conventions.ATTR_HTTP_REQUEST_METHOD]: req.method,
854
898
  [import_semantic_conventions.ATTR_HTTP_ROUTE]: req.originalPath || "unknown",
@@ -860,8 +904,8 @@ function enrichDetails(path, contractDetails, requestSchema, responseSchemas, op
860
904
  }
861
905
 
862
906
  // src/http/middleware/request/parse.middleware.ts
863
- var import_common7 = require("@forklaunch/common");
864
- var import_validator = require("@forklaunch/validator");
907
+ var import_common8 = require("@forklaunch/common");
908
+ var import_validator2 = require("@forklaunch/validator");
865
909
 
866
910
  // src/http/guards/hasSend.ts
867
911
  function hasSend(res) {
@@ -888,7 +932,7 @@ function parse(req, res, next) {
888
932
  let parsedRequest;
889
933
  let collectedParseErrors;
890
934
  if (req.contractDetails.versions) {
891
- if ((0, import_common7.isRecord)(req.requestSchema)) {
935
+ if ((0, import_common8.isRecord)(req.requestSchema)) {
892
936
  let runningParseErrors = "";
893
937
  matchedVersions = [];
894
938
  Object.entries(req.requestSchema).forEach(([version, schema]) => {
@@ -899,7 +943,7 @@ function parse(req, res, next) {
899
943
  req.version = version;
900
944
  res.version = req.version;
901
945
  } else {
902
- runningParseErrors += (0, import_validator.prettyPrintParseErrors)(
946
+ runningParseErrors += (0, import_validator2.prettyPrintParseErrors)(
903
947
  parsingResult.errors,
904
948
  `Version ${version} request`
905
949
  );
@@ -957,7 +1001,7 @@ function parse(req, res, next) {
957
1001
  res.status(400);
958
1002
  if (hasSend(res)) {
959
1003
  res.send(
960
- `${collectedParseErrors ?? (0, import_validator.prettyPrintParseErrors)(parsedRequest.errors, "Request")}
1004
+ `${collectedParseErrors ?? (0, import_validator2.prettyPrintParseErrors)(parsedRequest.errors, "Request")}
961
1005
 
962
1006
  Correlation id: ${req.context.correlationId ?? "No correlation ID"}`
963
1007
  );
@@ -967,7 +1011,7 @@ Correlation id: ${req.context.correlationId ?? "No correlation ID"}`
967
1011
  return;
968
1012
  case "warning":
969
1013
  req.openTelemetryCollector.warn(
970
- collectedParseErrors ?? (0, import_validator.prettyPrintParseErrors)(parsedRequest.errors, "Request")
1014
+ collectedParseErrors ?? (0, import_validator2.prettyPrintParseErrors)(parsedRequest.errors, "Request")
971
1015
  );
972
1016
  break;
973
1017
  case "none":
@@ -1280,13 +1324,13 @@ var ForklaunchExpressLikeRouter = class _ForklaunchExpressLikeRouter {
1280
1324
  versionedContractDetails,
1281
1325
  contractDetails.params
1282
1326
  );
1283
- if ((0, import_common8.isRecord)(requestSchema)) {
1327
+ if ((0, import_common9.isRecord)(requestSchema)) {
1284
1328
  requestSchema = {
1285
1329
  ...requestSchema,
1286
1330
  [version]: versionedRequestSchema
1287
1331
  };
1288
1332
  }
1289
- if ((0, import_common8.isRecord)(responseSchemas)) {
1333
+ if ((0, import_common9.isRecord)(responseSchemas)) {
1290
1334
  responseSchemas = {
1291
1335
  ...responseSchemas,
1292
1336
  [version]: versionedResponseSchemas
@@ -1475,8 +1519,8 @@ var ForklaunchExpressLikeRouter = class _ForklaunchExpressLikeRouter {
1475
1519
  ...resolvedMiddlewares,
1476
1520
  this.#parseAndRunControllerHandler(controllerHandler)
1477
1521
  );
1478
- (0, import_common8.toRecord)(this._fetchMap)[(0, import_common8.sanitizePathSlashes)(`${this.basePath}${path}`)] = {
1479
- ...this._fetchMap[(0, import_common8.sanitizePathSlashes)(`${this.basePath}${path}`)] ?? {},
1522
+ (0, import_common9.toRecord)(this._fetchMap)[(0, import_common9.sanitizePathSlashes)(`${this.basePath}${path}`)] = {
1523
+ ...this._fetchMap[(0, import_common9.sanitizePathSlashes)(`${this.basePath}${path}`)] ?? {},
1480
1524
  [method.toUpperCase()]: contractDetails.versions ? Object.fromEntries(
1481
1525
  Object.keys(contractDetails.versions).map((version) => [
1482
1526
  version,
@@ -1484,7 +1528,7 @@ var ForklaunchExpressLikeRouter = class _ForklaunchExpressLikeRouter {
1484
1528
  ])
1485
1529
  ) : this.#localParamRequest(handlers, controllerHandler)
1486
1530
  };
1487
- (0, import_common8.toRecord)(this.sdk)[(0, import_common8.toPrettyCamelCase)(contractDetails.name)] = contractDetails.versions ? Object.fromEntries(
1531
+ (0, import_common9.toRecord)(this.sdk)[(0, import_common9.toPrettyCamelCase)(contractDetails.name)] = contractDetails.versions ? Object.fromEntries(
1488
1532
  Object.keys(contractDetails.versions).map((version) => [
1489
1533
  version,
1490
1534
  (req) => this.#localParamRequest(
@@ -1583,10 +1627,10 @@ var ForklaunchExpressLikeRouter = class _ForklaunchExpressLikeRouter {
1583
1627
  }
1584
1628
  addRouterToSdk(router) {
1585
1629
  Object.entries(router._fetchMap).map(
1586
- ([key, value]) => (0, import_common8.toRecord)(this._fetchMap)[(0, import_common8.sanitizePathSlashes)(`${this.basePath}${key}`)] = value
1630
+ ([key, value]) => (0, import_common9.toRecord)(this._fetchMap)[(0, import_common9.sanitizePathSlashes)(`${this.basePath}${key}`)] = value
1587
1631
  );
1588
- const existingSdk = this.sdk[router.sdkName ?? (0, import_common8.toPrettyCamelCase)(router.basePath)];
1589
- (0, import_common8.toRecord)(this.sdk)[router.sdkName ?? (0, import_common8.toPrettyCamelCase)(router.basePath)] = {
1632
+ const existingSdk = this.sdk[router.sdkName ?? (0, import_common9.toPrettyCamelCase)(router.basePath)];
1633
+ (0, import_common9.toRecord)(this.sdk)[router.sdkName ?? (0, import_common9.toPrettyCamelCase)(router.basePath)] = {
1590
1634
  ...typeof existingSdk === "object" ? existingSdk : {},
1591
1635
  ...router.sdk
1592
1636
  };
@@ -2985,7 +3029,7 @@ var getCodeForStatus = (status) => {
2985
3029
  var httpStatusCodes_default = HTTPStatuses;
2986
3030
 
2987
3031
  // src/http/mcpGenerator/mcpGenerator.ts
2988
- var import_common9 = require("@forklaunch/common");
3032
+ var import_common10 = require("@forklaunch/common");
2989
3033
  var import_fastmcp_fork = require("@forklaunch/fastmcp-fork");
2990
3034
  var import_zod = require("@forklaunch/validator/zod");
2991
3035
 
@@ -3122,7 +3166,7 @@ function generateMcpServer(schemaValidator, protocol, host, port, version, appli
3122
3166
  if (discriminatedBody) {
3123
3167
  switch (discriminatedBody.parserType) {
3124
3168
  case "json": {
3125
- parsedBody = (0, import_common9.safeStringify)(body);
3169
+ parsedBody = (0, import_common10.safeStringify)(body);
3126
3170
  break;
3127
3171
  }
3128
3172
  case "text": {
@@ -3135,7 +3179,7 @@ function generateMcpServer(schemaValidator, protocol, host, port, version, appli
3135
3179
  }
3136
3180
  case "multipart": {
3137
3181
  const formData = new FormData();
3138
- if ((0, import_common9.isRecord)(body)) {
3182
+ if ((0, import_common10.isRecord)(body)) {
3139
3183
  for (const key in body) {
3140
3184
  if (typeof body[key] === "string" || body[key] instanceof Blob) {
3141
3185
  formData.append(key, body[key]);
@@ -3150,11 +3194,11 @@ function generateMcpServer(schemaValidator, protocol, host, port, version, appli
3150
3194
  break;
3151
3195
  }
3152
3196
  case "urlEncoded": {
3153
- if ((0, import_common9.isRecord)(body)) {
3197
+ if ((0, import_common10.isRecord)(body)) {
3154
3198
  parsedBody = new URLSearchParams(
3155
3199
  Object.entries(body).map(([key, value]) => [
3156
3200
  key,
3157
- (0, import_common9.safeStringify)(value)
3201
+ (0, import_common10.safeStringify)(value)
3158
3202
  ])
3159
3203
  );
3160
3204
  } else {
@@ -3163,8 +3207,8 @@ function generateMcpServer(schemaValidator, protocol, host, port, version, appli
3163
3207
  break;
3164
3208
  }
3165
3209
  default: {
3166
- (0, import_common9.isNever)(discriminatedBody.parserType);
3167
- parsedBody = (0, import_common9.safeStringify)(body);
3210
+ (0, import_common10.isNever)(discriminatedBody.parserType);
3211
+ parsedBody = (0, import_common10.safeStringify)(body);
3168
3212
  break;
3169
3213
  }
3170
3214
  }
@@ -3173,7 +3217,7 @@ function generateMcpServer(schemaValidator, protocol, host, port, version, appli
3173
3217
  const queryString = new URLSearchParams(
3174
3218
  Object.entries(query).map(([key, value]) => [
3175
3219
  key,
3176
- (0, import_common9.safeStringify)(value)
3220
+ (0, import_common10.safeStringify)(value)
3177
3221
  ])
3178
3222
  ).toString();
3179
3223
  url += queryString ? `?${queryString}` : "";
@@ -3206,7 +3250,7 @@ function generateMcpServer(schemaValidator, protocol, host, port, version, appli
3206
3250
  content: [
3207
3251
  {
3208
3252
  type: "text",
3209
- text: (0, import_common9.safeStringify)(await response.json())
3253
+ text: (0, import_common10.safeStringify)(await response.json())
3210
3254
  }
3211
3255
  ]
3212
3256
  };
@@ -3251,7 +3295,7 @@ function generateMcpServer(schemaValidator, protocol, host, port, version, appli
3251
3295
  }
3252
3296
 
3253
3297
  // src/http/middleware/response/parse.middleware.ts
3254
- var import_validator2 = require("@forklaunch/validator");
3298
+ var import_validator3 = require("@forklaunch/validator");
3255
3299
 
3256
3300
  // src/http/guards/isResponseCompiledSchema.ts
3257
3301
  function isResponseCompiledSchema(schema) {
@@ -3312,13 +3356,13 @@ function parse2(req, res, next) {
3312
3356
  );
3313
3357
  const parseErrors = [];
3314
3358
  if (!parsedHeaders.ok) {
3315
- const headerErrors = (0, import_validator2.prettyPrintParseErrors)(parsedHeaders.errors, "Header");
3359
+ const headerErrors = (0, import_validator3.prettyPrintParseErrors)(parsedHeaders.errors, "Header");
3316
3360
  if (headerErrors) {
3317
3361
  parseErrors.push(headerErrors);
3318
3362
  }
3319
3363
  }
3320
3364
  if (!parsedResponse.ok) {
3321
- const responseErrors = (0, import_validator2.prettyPrintParseErrors)(
3365
+ const responseErrors = (0, import_validator3.prettyPrintParseErrors)(
3322
3366
  parsedResponse.errors,
3323
3367
  "Response"
3324
3368
  );
@@ -3360,18 +3404,18 @@ ${parseErrors.join("\n\n")}`
3360
3404
  }
3361
3405
 
3362
3406
  // src/http/middleware/response/enrichExpressLikeSend.middleware.ts
3363
- var import_common11 = require("@forklaunch/common");
3407
+ var import_common12 = require("@forklaunch/common");
3364
3408
  var import_stream = require("stream");
3365
3409
 
3366
3410
  // src/http/telemetry/recordMetric.ts
3367
- var import_common10 = require("@forklaunch/common");
3411
+ var import_common11 = require("@forklaunch/common");
3368
3412
  var import_semantic_conventions3 = require("@opentelemetry/semantic-conventions");
3369
3413
  function recordMetric(req, res) {
3370
3414
  if (res.metricRecorded) {
3371
3415
  return;
3372
3416
  }
3373
3417
  httpRequestsTotalCounter.add(1, {
3374
- [import_semantic_conventions3.ATTR_SERVICE_NAME]: (0, import_common10.getEnvVar)("OTEL_SERVICE_NAME"),
3418
+ [import_semantic_conventions3.ATTR_SERVICE_NAME]: (0, import_common11.getEnvVar)("OTEL_SERVICE_NAME"),
3375
3419
  [ATTR_API_NAME]: req.contractDetails?.name,
3376
3420
  [import_semantic_conventions3.ATTR_HTTP_REQUEST_METHOD]: req.method,
3377
3421
  [import_semantic_conventions3.ATTR_HTTP_ROUTE]: req.originalPath,
@@ -3420,8 +3464,8 @@ function enrichExpressLikeSend(instance, req, res, originalOperation, originalSe
3420
3464
  `attachment; filename="${data.name}"`
3421
3465
  );
3422
3466
  }
3423
- if ((0, import_common11.isNodeJsWriteableStream)(res)) {
3424
- import_stream.Readable.from((0, import_common11.readableStreamToAsyncIterable)(data.stream())).pipe(
3467
+ if ((0, import_common12.isNodeJsWriteableStream)(res)) {
3468
+ import_stream.Readable.from((0, import_common12.readableStreamToAsyncIterable)(data.stream())).pipe(
3425
3469
  res
3426
3470
  );
3427
3471
  } else {
@@ -3430,7 +3474,7 @@ function enrichExpressLikeSend(instance, req, res, originalOperation, originalSe
3430
3474
  originalSend.call(instance, "Not a NodeJS WritableStream");
3431
3475
  errorSent = true;
3432
3476
  }
3433
- } else if ((0, import_common11.isAsyncGenerator)(data)) {
3477
+ } else if ((0, import_common12.isAsyncGenerator)(data)) {
3434
3478
  let firstPass = true;
3435
3479
  const transformer = new import_stream.Transform({
3436
3480
  objectMode: true,
@@ -3458,7 +3502,7 @@ ${res.locals.errorMessage}`;
3458
3502
  if (!errorSent) {
3459
3503
  let data2 = "";
3460
3504
  for (const [key, value] of Object.entries(chunk)) {
3461
- data2 += `${key}: ${typeof value === "string" ? value : (0, import_common11.safeStringify)(value)}
3505
+ data2 += `${key}: ${typeof value === "string" ? value : (0, import_common12.safeStringify)(value)}
3462
3506
  `;
3463
3507
  }
3464
3508
  data2 += "\n";
@@ -3466,7 +3510,7 @@ ${res.locals.errorMessage}`;
3466
3510
  }
3467
3511
  }
3468
3512
  });
3469
- if ((0, import_common11.isNodeJsWriteableStream)(res)) {
3513
+ if ((0, import_common12.isNodeJsWriteableStream)(res)) {
3470
3514
  import_stream.Readable.from(data).pipe(transformer).pipe(res);
3471
3515
  } else {
3472
3516
  res.type("text/plain");
@@ -3477,7 +3521,7 @@ ${res.locals.errorMessage}`;
3477
3521
  } else {
3478
3522
  const parserType = responseBodies?.[Number(res.statusCode)]?.parserType;
3479
3523
  res.bodyData = data;
3480
- if ((0, import_common11.isRecord)(data)) {
3524
+ if ((0, import_common12.isRecord)(data)) {
3481
3525
  switch (parserType) {
3482
3526
  case "json":
3483
3527
  res.bodyData = "json" in data ? data.json : data;
@@ -3498,7 +3542,7 @@ ${res.locals.errorMessage}`;
3498
3542
  res.bodyData = data;
3499
3543
  break;
3500
3544
  default:
3501
- (0, import_common11.isNever)(parserType);
3545
+ (0, import_common12.isNever)(parserType);
3502
3546
  res.bodyData = data;
3503
3547
  break;
3504
3548
  }
@@ -3534,7 +3578,7 @@ ${res.locals.errorMessage}`;
3534
3578
  }
3535
3579
 
3536
3580
  // src/http/openApiV3Generator/openApiV3Generator.ts
3537
- var import_common12 = require("@forklaunch/common");
3581
+ var import_common13 = require("@forklaunch/common");
3538
3582
  var OPENAPI_DEFAULT_VERSION = Symbol("default");
3539
3583
  function toUpperCase(str) {
3540
3584
  return str.charAt(0).toUpperCase() + str.slice(1);
@@ -3757,7 +3801,7 @@ function generateOpenApiSpecs(schemaValidator, serverUrls, serverDescriptions, a
3757
3801
  ].forEach(({ fullPath, router }) => {
3758
3802
  const controllerName = transformBasePath(fullPath);
3759
3803
  router.routes.forEach((route) => {
3760
- const openApiPath = (0, import_common12.openApiCompliantPath)(
3804
+ const openApiPath = (0, import_common13.openApiCompliantPath)(
3761
3805
  `${fullPath}${route.path === "/" ? "" : route.path}`
3762
3806
  );
3763
3807
  const { name, summary, params, versions, auth, options: options2 } = route.contractDetails;
@@ -3862,7 +3906,7 @@ function generateOpenApiSpecs(schemaValidator, serverUrls, serverDescriptions, a
3862
3906
  }
3863
3907
 
3864
3908
  // src/http/sdk/sdkClient.ts
3865
- var import_common13 = require("@forklaunch/common");
3909
+ var import_common14 = require("@forklaunch/common");
3866
3910
 
3867
3911
  // src/http/guards/isSdkRouter.ts
3868
3912
  function isSdkRouter(value) {
@@ -3874,12 +3918,12 @@ function mapToSdk(schemaValidator, routerMap, runningPath = void 0) {
3874
3918
  const routerUniquenessCache = /* @__PURE__ */ new Set();
3875
3919
  return Object.fromEntries(
3876
3920
  Object.entries(routerMap).map(([key, value]) => {
3877
- if (routerUniquenessCache.has((0, import_common13.hashString)((0, import_common13.safeStringify)(value)))) {
3921
+ if (routerUniquenessCache.has((0, import_common14.hashString)((0, import_common14.safeStringify)(value)))) {
3878
3922
  throw new Error(
3879
3923
  `SdkClient: Cannot use the same router pointer twice. Please clone the duplicate router with .clone() or only use the router once.`
3880
3924
  );
3881
3925
  }
3882
- routerUniquenessCache.add((0, import_common13.hashString)((0, import_common13.safeStringify)(value)));
3926
+ routerUniquenessCache.add((0, import_common14.hashString)((0, import_common14.safeStringify)(value)));
3883
3927
  const currentPath = runningPath ? [runningPath, key].join(".") : key;
3884
3928
  if (isSdkRouter(value)) {
3885
3929
  Object.entries(value.sdkPaths).forEach(([routePath, sdkKey]) => {
@@ -3928,19 +3972,43 @@ function mapToFetch(schemaValidator, routerMap) {
3928
3972
  return ((path, ...reqInit) => {
3929
3973
  const method = reqInit[0]?.method;
3930
3974
  const version = reqInit[0] != null && "version" in reqInit[0] ? reqInit[0].version : void 0;
3931
- return (version ? (0, import_common13.toRecord)((0, import_common13.toRecord)(flattenedFetchMap[path])[method ?? "GET"])[version] : (0, import_common13.toRecord)(flattenedFetchMap[path])[method ?? "GET"])(path, reqInit[0]);
3975
+ return (version ? (0, import_common14.toRecord)((0, import_common14.toRecord)(flattenedFetchMap[path])[method ?? "GET"])[version] : (0, import_common14.toRecord)(flattenedFetchMap[path])[method ?? "GET"])(path, reqInit[0]);
3932
3976
  });
3933
3977
  }
3934
- function sdkClient(schemaValidator, routerMap) {
3935
- return {
3978
+ function sdkClient(schemaValidator, routerMap, options2) {
3979
+ if (options2?.lazyEvaluation) {
3980
+ let _sdk;
3981
+ let _fetch;
3982
+ const lazyClient = {
3983
+ _finalizedSdk: true,
3984
+ get sdk() {
3985
+ if (!_sdk) {
3986
+ _sdk = mapToSdk(schemaValidator, routerMap);
3987
+ }
3988
+ return _sdk;
3989
+ },
3990
+ get fetch() {
3991
+ if (!_fetch) {
3992
+ _fetch = mapToFetch(schemaValidator, routerMap);
3993
+ }
3994
+ return _fetch;
3995
+ }
3996
+ };
3997
+ return lazyClient;
3998
+ }
3999
+ const client = {
3936
4000
  _finalizedSdk: true,
3937
4001
  sdk: mapToSdk(schemaValidator, routerMap),
3938
4002
  fetch: mapToFetch(schemaValidator, routerMap)
3939
4003
  };
4004
+ if (options2?.optimizePerformance) {
4005
+ return client;
4006
+ }
4007
+ return client;
3940
4008
  }
3941
4009
 
3942
4010
  // src/http/sdk/sdkRouter.ts
3943
- var import_common14 = require("@forklaunch/common");
4011
+ var import_common15 = require("@forklaunch/common");
3944
4012
  function sdkRouter(schemaValidator, controller, router) {
3945
4013
  const controllerSdkPaths = [];
3946
4014
  const mappedSdk = Object.fromEntries(
@@ -3950,7 +4018,7 @@ function sdkRouter(schemaValidator, controller, router) {
3950
4018
  router.sdkPaths[sdkPath] = key;
3951
4019
  return [
3952
4020
  key,
3953
- router.sdk[(0, import_common14.toPrettyCamelCase)(value.contractDetails.name)]
4021
+ router.sdk[(0, import_common15.toPrettyCamelCase)(value.contractDetails.name)]
3954
4022
  ];
3955
4023
  })
3956
4024
  );