@forklaunch/core 0.12.3 → 0.13.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
@@ -57,6 +57,7 @@ __export(http_exports, {
57
57
  isForklaunchRequest: () => isForklaunchRequest,
58
58
  isForklaunchRouter: () => isForklaunchRouter,
59
59
  isInformational: () => isInformational,
60
+ isPortBound: () => isPortBound,
60
61
  isRedirection: () => isRedirection,
61
62
  isServerError: () => isServerError,
62
63
  isSuccessful: () => isSuccessful,
@@ -156,10 +157,10 @@ function isTypedHandler(maybeTypedHandler) {
156
157
 
157
158
  // src/http/middleware/request/auth.middleware.ts
158
159
  var import_common2 = require("@forklaunch/common");
159
- var import_jose = require("jose");
160
160
 
161
161
  // src/http/discriminateAuthMethod.ts
162
- function discriminateAuthMethod(auth) {
162
+ var import_jose = require("jose");
163
+ async function discriminateAuthMethod(auth) {
163
164
  if ("basic" in auth) {
164
165
  return {
165
166
  type: "basic",
@@ -168,18 +169,59 @@ function discriminateAuthMethod(auth) {
168
169
  login: auth.basic.login
169
170
  }
170
171
  };
171
- } else if ("jwt" in auth) {
172
+ } else if ("jwt" in auth && auth.jwt != null) {
173
+ const jwt = auth.jwt;
174
+ let verificationFunction;
175
+ if ("privateKey" in jwt) {
176
+ verificationFunction = async (token) => {
177
+ const { payload } = await (0, import_jose.jwtVerify)(token, Buffer.from(jwt.privateKey));
178
+ return payload;
179
+ };
180
+ } else {
181
+ let jwks;
182
+ if ("jwksPublicKeyUrl" in jwt) {
183
+ const jwksResponse = await fetch(jwt.jwksPublicKeyUrl);
184
+ jwks = (await jwksResponse.json()).keys;
185
+ } else {
186
+ jwks = [jwt.jwksPublicKey];
187
+ }
188
+ verificationFunction = async (token) => {
189
+ for (const key of jwks) {
190
+ try {
191
+ const { payload } = await (0, import_jose.jwtVerify)(token, key);
192
+ return payload;
193
+ } catch {
194
+ continue;
195
+ }
196
+ }
197
+ };
198
+ }
172
199
  return {
173
200
  type: "jwt",
174
201
  auth: {
175
- decodeResource: auth.decodeResource
202
+ decodeResource: auth.decodeResource,
203
+ verificationFunction
204
+ }
205
+ };
206
+ } else if ("secretKey" in auth) {
207
+ return {
208
+ type: "system",
209
+ auth: {
210
+ secretKey: auth.secretKey
176
211
  }
177
212
  };
178
213
  } else {
179
214
  return {
180
215
  type: "jwt",
181
216
  auth: {
182
- decodeResource: auth.decodeResource
217
+ decodeResource: auth.decodeResource,
218
+ verificationFunction: async (token) => {
219
+ const { payload } = await (0, import_jose.jwtVerify)(
220
+ token,
221
+ Buffer.from(process.env.JWT_SECRET_KEY)
222
+ );
223
+ return payload;
224
+ }
183
225
  }
184
226
  };
185
227
  }
@@ -187,12 +229,22 @@ function discriminateAuthMethod(auth) {
187
229
 
188
230
  // src/http/guards/hasPermissionChecks.ts
189
231
  function hasPermissionChecks(maybePermissionedAuth) {
190
- return typeof maybePermissionedAuth === "object" && maybePermissionedAuth !== null && "mapPermissions" in maybePermissionedAuth && ("allowedPermissions" in maybePermissionedAuth || "forbiddenPermissions" in maybePermissionedAuth);
232
+ return typeof maybePermissionedAuth === "object" && maybePermissionedAuth !== null && ("allowedPermissions" in maybePermissionedAuth || "forbiddenPermissions" in maybePermissionedAuth);
191
233
  }
192
234
 
193
235
  // src/http/guards/hasRoleChecks.ts
194
236
  function hasRoleChecks(maybeRoledAuth) {
195
- return typeof maybeRoledAuth === "object" && maybeRoledAuth !== null && "mapRoles" in maybeRoledAuth && ("allowedRoles" in maybeRoledAuth || "forbiddenRoles" in maybeRoledAuth);
237
+ return typeof maybeRoledAuth === "object" && maybeRoledAuth !== null && ("allowedRoles" in maybeRoledAuth || "forbiddenRoles" in maybeRoledAuth);
238
+ }
239
+
240
+ // src/http/guards/hasScopeChecks.ts
241
+ function hasScopeChecks(maybePermissionedAuth) {
242
+ return typeof maybePermissionedAuth === "object" && maybePermissionedAuth !== null && "requiredScope" in maybePermissionedAuth && maybePermissionedAuth.requiredScope != null;
243
+ }
244
+
245
+ // src/http/guards/isSystemAuthMethod.ts
246
+ function isSystemAuthMethod(maybeSystemAuthMethod) {
247
+ return typeof maybeSystemAuthMethod === "object" && maybeSystemAuthMethod !== null && "secretKey" in maybeSystemAuthMethod;
196
248
  }
197
249
 
198
250
  // src/http/middleware/request/auth.middleware.ts
@@ -220,23 +272,45 @@ var invalidAuthorizationLogin = [
220
272
  403,
221
273
  "Invalid Authorization login."
222
274
  ];
223
- async function checkAuthorizationToken(authorizationMethod, authorizationToken, req) {
275
+ var invalidScope = [403, "Invalid scope for operation."];
276
+ var invalidAuthorizationMethod = [
277
+ 401,
278
+ "Invalid Authorization method."
279
+ ];
280
+ var authorizationTokenRequired = [
281
+ 401,
282
+ "Authorization token required."
283
+ ];
284
+ async function checkAuthorizationToken(authorizationMethod, globalOptions, authorizationToken, req) {
285
+ if (authorizationMethod == null) {
286
+ return void 0;
287
+ }
288
+ const collapsedAuthorizationMethod = {
289
+ ...globalOptions,
290
+ ...authorizationMethod
291
+ };
224
292
  if (authorizationToken == null) {
225
- return [401, "No Authorization token provided."];
293
+ return authorizationTokenRequired;
226
294
  }
227
295
  const [tokenPrefix, token] = authorizationToken.split(" ");
228
296
  let resourceId;
229
- const { type, auth } = discriminateAuthMethod(authorizationMethod);
297
+ const { type, auth } = await discriminateAuthMethod(
298
+ collapsedAuthorizationMethod
299
+ );
230
300
  switch (type) {
301
+ case "system": {
302
+ if (token !== auth.secretKey || tokenPrefix !== collapsedAuthorizationMethod.tokenPrefix) {
303
+ return invalidAuthorizationToken;
304
+ }
305
+ resourceId = null;
306
+ break;
307
+ }
231
308
  case "jwt": {
232
- if (tokenPrefix !== (authorizationMethod.tokenPrefix ?? "Bearer")) {
309
+ if (tokenPrefix !== (collapsedAuthorizationMethod.tokenPrefix ?? "Bearer")) {
233
310
  return invalidAuthorizationTokenFormat;
234
311
  }
235
312
  try {
236
- const decodedJwt = await auth?.decodeResource?.(token) ?? (await (0, import_jose.jwtVerify)(
237
- token,
238
- new TextEncoder().encode(process.env.JWT_SECRET)
239
- )).payload;
313
+ const decodedJwt = "decodeResource" in auth && auth.decodeResource ? await auth.decodeResource(token) : "verificationFunction" in auth && auth.verificationFunction ? await auth.verificationFunction(token) : void 0;
240
314
  if (!decodedJwt) {
241
315
  return invalidAuthorizationSubject;
242
316
  }
@@ -248,7 +322,7 @@ async function checkAuthorizationToken(authorizationMethod, authorizationToken,
248
322
  break;
249
323
  }
250
324
  case "basic": {
251
- if (tokenPrefix !== (authorizationMethod.tokenPrefix ?? "Basic")) {
325
+ if (tokenPrefix !== (collapsedAuthorizationMethod.tokenPrefix ?? "Basic")) {
252
326
  return invalidAuthorizationTokenFormat;
253
327
  }
254
328
  if (auth.decodeResource) {
@@ -271,62 +345,82 @@ async function checkAuthorizationToken(authorizationMethod, authorizationToken,
271
345
  (0, import_common2.isNever)(type);
272
346
  return [401, "Invalid Authorization method."];
273
347
  }
274
- if (hasPermissionChecks(authorizationMethod)) {
275
- if (!authorizationMethod.mapPermissions) {
276
- return [500, "No permission mapping function provided."];
348
+ if (isSystemAuthMethod(collapsedAuthorizationMethod) || resourceId == null) {
349
+ return;
350
+ }
351
+ if (hasScopeChecks(collapsedAuthorizationMethod)) {
352
+ if (collapsedAuthorizationMethod.surfaceScopes) {
353
+ const resourceScopes = await collapsedAuthorizationMethod.surfaceScopes(
354
+ resourceId,
355
+ req
356
+ );
357
+ if (collapsedAuthorizationMethod.scopeHeirarchy) {
358
+ if (collapsedAuthorizationMethod.requiredScope) {
359
+ if (!resourceScopes.has(collapsedAuthorizationMethod.requiredScope) || Array.from(resourceScopes).every(
360
+ (scope) => collapsedAuthorizationMethod.scopeHeirarchy?.indexOf(scope) ?? -1 > -1
361
+ )) {
362
+ return invalidScope;
363
+ }
364
+ }
365
+ }
277
366
  }
278
- const resourcePermissions = await authorizationMethod.mapPermissions(
367
+ }
368
+ if (hasPermissionChecks(collapsedAuthorizationMethod)) {
369
+ if (!collapsedAuthorizationMethod.surfacePermissions) {
370
+ return [500, "No permission surfacing function provided."];
371
+ }
372
+ const resourcePermissions = await collapsedAuthorizationMethod.surfacePermissions(
279
373
  resourceId,
280
374
  req
281
375
  );
282
- if ("allowedPermissions" in authorizationMethod && authorizationMethod.allowedPermissions) {
283
- if (resourcePermissions.intersection(authorizationMethod.allowedPermissions).size === 0) {
376
+ if ("allowedPermissions" in collapsedAuthorizationMethod && collapsedAuthorizationMethod.allowedPermissions) {
377
+ if (resourcePermissions.intersection(
378
+ collapsedAuthorizationMethod.allowedPermissions
379
+ ).size === 0) {
284
380
  return invalidAuthorizationTokenPermissions;
285
381
  }
286
382
  }
287
- if ("forbiddenPermissions" in authorizationMethod && authorizationMethod.forbiddenPermissions) {
383
+ if ("forbiddenPermissions" in collapsedAuthorizationMethod && collapsedAuthorizationMethod.forbiddenPermissions) {
288
384
  if (resourcePermissions.intersection(
289
- authorizationMethod.forbiddenPermissions
385
+ collapsedAuthorizationMethod.forbiddenPermissions
290
386
  ).size !== 0) {
291
387
  return invalidAuthorizationTokenPermissions;
292
388
  }
293
389
  }
294
- } else if (hasRoleChecks(authorizationMethod)) {
295
- if (!authorizationMethod.mapRoles) {
296
- return [500, "No role mapping function provided."];
390
+ } else if (hasRoleChecks(collapsedAuthorizationMethod)) {
391
+ if (!collapsedAuthorizationMethod.surfaceRoles) {
392
+ return [500, "No role surfacing function provided."];
297
393
  }
298
- const resourceRoles = await authorizationMethod.mapRoles(
394
+ const resourceRoles = await collapsedAuthorizationMethod.surfaceRoles(
299
395
  resourceId,
300
396
  req
301
397
  );
302
- if ("allowedRoles" in authorizationMethod && authorizationMethod.allowedRoles) {
303
- if (resourceRoles.intersection(authorizationMethod.allowedRoles).size === 0) {
398
+ if ("allowedRoles" in collapsedAuthorizationMethod && collapsedAuthorizationMethod.allowedRoles) {
399
+ if (resourceRoles.intersection(collapsedAuthorizationMethod.allowedRoles).size === 0) {
304
400
  return invalidAuthorizationTokenRoles;
305
401
  }
306
402
  }
307
- if ("forbiddenRoles" in authorizationMethod && authorizationMethod.forbiddenRoles) {
308
- if (resourceRoles.intersection(authorizationMethod.forbiddenRoles).size !== 0) {
403
+ if ("forbiddenRoles" in collapsedAuthorizationMethod && collapsedAuthorizationMethod.forbiddenRoles) {
404
+ if (resourceRoles.intersection(collapsedAuthorizationMethod.forbiddenRoles).size !== 0) {
309
405
  return invalidAuthorizationTokenRoles;
310
406
  }
311
407
  }
312
408
  } else {
313
- return [401, "Invalid Authorization method."];
409
+ return invalidAuthorizationMethod;
314
410
  }
315
411
  }
316
412
  async function parseRequestAuth(req, res, next) {
317
413
  const auth = req.contractDetails.auth;
318
- if (auth) {
319
- const [error, message] = await checkAuthorizationToken(
320
- auth,
321
- req.headers[auth.headerName ?? "Authorization"] || req.headers[auth.headerName ?? "authorization"],
322
- // we can safely cast here because we know that the user will supply resolution for the request
323
- req
324
- ) ?? [];
325
- if (error != null) {
326
- res.type("text/plain");
327
- res.status(error).send(message);
328
- return;
329
- }
414
+ const [error, message] = await checkAuthorizationToken(
415
+ auth,
416
+ req._globalOptions?.auth,
417
+ req.headers[auth?.headerName ?? "Authorization"] || req.headers[auth?.headerName ?? "authorization"],
418
+ req
419
+ ) ?? [];
420
+ if (error != null) {
421
+ res.type("text/plain");
422
+ res.status(error).send(message);
423
+ return;
330
424
  }
331
425
  next?.();
332
426
  }
@@ -396,7 +490,7 @@ var import_common4 = require("@forklaunch/common");
396
490
  var import_api2 = require("@opentelemetry/api");
397
491
  var import_api_logs = require("@opentelemetry/api-logs");
398
492
  var import_pino = __toESM(require("pino"));
399
- var import_pino_pretty = __toESM(require("pino-pretty"));
493
+ var PinoPretty = __toESM(require("pino-pretty"));
400
494
 
401
495
  // src/http/guards/isLoggerMeta.ts
402
496
  function isLoggerMeta(arg) {
@@ -443,11 +537,35 @@ function normalizeLogArgs(args) {
443
537
  const metadata = Object.assign({}, ...metaObjects);
444
538
  return [metadata, message.trim()];
445
539
  }
540
+ function safePrettyFormat(level, args, timestamp) {
541
+ try {
542
+ const [metadata, message] = normalizeLogArgs(args);
543
+ const formattedTimestamp = timestamp || (/* @__PURE__ */ new Date()).toISOString();
544
+ return `[${formattedTimestamp}] ${level.toUpperCase()}: ${message}${Object.keys(metadata).length > 0 ? `
545
+ ${JSON.stringify(metadata, null, 2)}` : ""}`;
546
+ } catch (error) {
547
+ const fallbackMessage = args.map((arg) => {
548
+ try {
549
+ if (typeof arg === "string") return arg;
550
+ if (arg === null) return "null";
551
+ if (arg === void 0) return "undefined";
552
+ return JSON.stringify(arg);
553
+ } catch {
554
+ return "[Circular/Non-serializable Object]";
555
+ }
556
+ }).join(" ");
557
+ return `[${(/* @__PURE__ */ new Date()).toISOString()}] ${level.toUpperCase()}: ${fallbackMessage} [Pretty Print Error: ${error instanceof Error ? error.message : "Unknown error"}]`;
558
+ }
559
+ }
446
560
  var PinoLogger = class _PinoLogger {
447
561
  pinoLogger;
448
562
  meta;
449
- prettyPrinter = import_pino_pretty.default.prettyFactory({
450
- colorize: true
563
+ prettyPrinter = PinoPretty.prettyFactory({
564
+ colorize: true,
565
+ // Add error handling options
566
+ errorLikeObjectKeys: ["err", "error"],
567
+ ignore: "pid,hostname",
568
+ translateTime: "SYS:standard"
451
569
  });
452
570
  constructor(level, meta2 = {}) {
453
571
  this.pinoLogger = (0, import_pino.default)({
@@ -460,7 +578,12 @@ var PinoLogger = class _PinoLogger {
460
578
  timestamp: import_pino.default.stdTimeFunctions.isoTime,
461
579
  transport: {
462
580
  target: "pino-pretty",
463
- options: { colorize: true }
581
+ options: {
582
+ colorize: true,
583
+ errorLikeObjectKeys: ["err", "error"],
584
+ ignore: "pid,hostname",
585
+ translateTime: "SYS:standard"
586
+ }
464
587
  }
465
588
  });
466
589
  this.meta = meta2;
@@ -492,12 +615,21 @@ var PinoLogger = class _PinoLogger {
492
615
  ...meta2
493
616
  };
494
617
  this.pinoLogger[level](...normalizeLogArgs(filteredArgs));
495
- import_api_logs.logs.getLogger(process.env.OTEL_SERVICE_NAME ?? "unknown").emit({
496
- severityText: level,
497
- severityNumber: mapSeverity(level),
498
- body: this.prettyPrinter(filteredArgs),
499
- attributes: { ...this.meta, ...meta2 }
500
- });
618
+ const formattedBody = safePrettyFormat(level, filteredArgs);
619
+ try {
620
+ import_api_logs.logs.getLogger(process.env.OTEL_SERVICE_NAME ?? "unknown").emit({
621
+ severityText: level,
622
+ severityNumber: mapSeverity(level),
623
+ body: formattedBody,
624
+ attributes: { ...this.meta, ...meta2 }
625
+ });
626
+ } catch (error) {
627
+ console.error("Failed to emit OpenTelemetry log:", error);
628
+ console.log(
629
+ `[${(/* @__PURE__ */ new Date()).toISOString()}] ${level.toUpperCase()}:`,
630
+ ...filteredArgs
631
+ );
632
+ }
501
633
  }
502
634
  error = (msg, ...args) => this.log("error", msg, ...args);
503
635
  info = (msg, ...args) => this.log("info", msg, ...args);
@@ -626,13 +758,14 @@ var httpServerDurationHistogram = import_api3.metrics.getMeter((0, import_common
626
758
  });
627
759
 
628
760
  // src/http/middleware/request/enrichDetails.middleware.ts
629
- function enrichDetails(path, contractDetails, requestSchema, responseSchemas, openTelemetryCollector) {
761
+ function enrichDetails(path, contractDetails, requestSchema, responseSchemas, openTelemetryCollector, globalOptions) {
630
762
  return (req, res, next) => {
631
763
  req.originalPath = path;
632
764
  req.contractDetails = contractDetails;
633
765
  req.requestSchema = requestSchema;
634
766
  res.responseSchemas = responseSchemas;
635
767
  req.openTelemetryCollector = openTelemetryCollector;
768
+ req._globalOptions = globalOptions;
636
769
  req.context?.span?.setAttribute(ATTR_API_NAME, req.contractDetails?.name);
637
770
  const startTime = process.hrtime();
638
771
  res.on("finish", () => {
@@ -666,6 +799,7 @@ function isRequestShape(maybeResponseShape) {
666
799
 
667
800
  // src/http/middleware/request/parse.middleware.ts
668
801
  function parse(req, res, next) {
802
+ const collapsedOptions = req.contractDetails.options?.requestValidation ?? (req._globalOptions?.validation === false ? "none" : req._globalOptions?.validation?.request);
669
803
  const request = {
670
804
  params: req.params,
671
805
  query: req.query,
@@ -738,8 +872,9 @@ function parse(req, res, next) {
738
872
  );
739
873
  }
740
874
  if (!parsedRequest.ok) {
741
- switch (req.contractDetails.options?.requestValidation) {
875
+ switch (collapsedOptions) {
742
876
  default:
877
+ case void 0:
743
878
  case "error":
744
879
  res.type("application/json");
745
880
  res.status(400);
@@ -900,12 +1035,13 @@ function discriminateResponseBodies(schemaValidator, responses) {
900
1035
 
901
1036
  // src/http/router/expressLikeRouter.ts
902
1037
  var ForklaunchExpressLikeRouter = class _ForklaunchExpressLikeRouter {
903
- constructor(basePath, schemaValidator, internal, postEnrichMiddleware, openTelemetryCollector) {
1038
+ constructor(basePath, schemaValidator, internal, postEnrichMiddleware, openTelemetryCollector, routerOptions) {
904
1039
  this.basePath = basePath;
905
1040
  this.schemaValidator = schemaValidator;
906
1041
  this.internal = internal;
907
1042
  this.postEnrichMiddleware = postEnrichMiddleware;
908
1043
  this.openTelemetryCollector = openTelemetryCollector;
1044
+ this.routerOptions = routerOptions;
909
1045
  if (process.env.NODE_ENV !== "test" && !process.env.VITEST) {
910
1046
  process.on("uncaughtException", (err) => {
911
1047
  this.openTelemetryCollector.error(`Uncaught exception: ${err}`);
@@ -944,7 +1080,8 @@ var ForklaunchExpressLikeRouter = class _ForklaunchExpressLikeRouter {
944
1080
  contractDetails,
945
1081
  requestSchema,
946
1082
  responseSchemas,
947
- this.openTelemetryCollector
1083
+ this.openTelemetryCollector,
1084
+ this.routerOptions
948
1085
  ),
949
1086
  ...this.postEnrichMiddleware,
950
1087
  parse,
@@ -1637,7 +1774,8 @@ var ForklaunchExpressLikeRouter = class _ForklaunchExpressLikeRouter {
1637
1774
  this.schemaValidator,
1638
1775
  this.internal,
1639
1776
  this.postEnrichMiddleware,
1640
- this.openTelemetryCollector
1777
+ this.openTelemetryCollector,
1778
+ this.routerOptions
1641
1779
  );
1642
1780
  this.cloneInternals(clone);
1643
1781
  return clone;
@@ -1657,7 +1795,12 @@ var ForklaunchExpressLikeApplication = class extends ForklaunchExpressLikeRouter
1657
1795
  schemaValidator,
1658
1796
  internal,
1659
1797
  postEnrichMiddleware,
1660
- openTelemetryCollector
1798
+ openTelemetryCollector,
1799
+ {
1800
+ ...appOptions,
1801
+ openapi: appOptions?.openapi !== false,
1802
+ mcp: appOptions?.mcp !== false
1803
+ }
1661
1804
  );
1662
1805
  this.schemaValidator = schemaValidator;
1663
1806
  this.internal = internal;
@@ -1668,6 +1811,27 @@ var ForklaunchExpressLikeApplication = class extends ForklaunchExpressLikeRouter
1668
1811
  }
1669
1812
  };
1670
1813
 
1814
+ // src/http/cluster/isPortBound.ts
1815
+ var net = __toESM(require("net"));
1816
+ function isPortBound(port, host = "localhost") {
1817
+ return new Promise((resolve) => {
1818
+ const socket = new net.Socket();
1819
+ socket.setTimeout(1e3);
1820
+ socket.on("connect", () => {
1821
+ socket.destroy();
1822
+ resolve(true);
1823
+ });
1824
+ socket.on("timeout", () => {
1825
+ socket.destroy();
1826
+ resolve(false);
1827
+ });
1828
+ socket.on("error", () => {
1829
+ resolve(false);
1830
+ });
1831
+ socket.connect(port, host);
1832
+ });
1833
+ }
1834
+
1671
1835
  // src/http/guards/isPath.ts
1672
1836
  function isPath(path) {
1673
1837
  return path.startsWith("/");
@@ -2790,7 +2954,7 @@ function generateInputSchema(schemaValidator, body, params, query, requestHeader
2790
2954
  } : {}
2791
2955
  });
2792
2956
  }
2793
- function generateMcpServer(schemaValidator, protocol, host, port, version, application, options2, contentTypeMap) {
2957
+ function generateMcpServer(schemaValidator, protocol, host, port, version, application, appOptions, options2, contentTypeMap) {
2794
2958
  if (!(schemaValidator instanceof import_zod.ZodSchemaValidator)) {
2795
2959
  throw new Error(
2796
2960
  "Schema validator must be an instance of ZodSchemaValidator"
@@ -2811,6 +2975,9 @@ function generateMcpServer(schemaValidator, protocol, host, port, version, appli
2811
2975
  ])
2812
2976
  ].forEach(({ fullPath, router }) => {
2813
2977
  router.routes.forEach((route) => {
2978
+ if (!(route.contractDetails.options?.mcp ?? router.routerOptions?.mcp ?? appOptions !== false)) {
2979
+ return;
2980
+ }
2814
2981
  const inputSchemas = [];
2815
2982
  if (route.contractDetails.versions) {
2816
2983
  Object.values(route.contractDetails.versions).forEach((version2) => {
@@ -3012,6 +3179,11 @@ function isResponseCompiledSchema(schema) {
3012
3179
  function parse2(req, res, next) {
3013
3180
  let headers;
3014
3181
  let responses;
3182
+ const collapsedOptions = req.contractDetails.options?.responseValidation ?? (req._globalOptions?.validation === false ? "none" : req._globalOptions?.validation?.response);
3183
+ if (collapsedOptions === "none") {
3184
+ next?.();
3185
+ return;
3186
+ }
3015
3187
  const responseSchemas = res.responseSchemas;
3016
3188
  const schemaValidator = req.schemaValidator;
3017
3189
  if (!isResponseCompiledSchema(responseSchemas)) {
@@ -3073,6 +3245,7 @@ function parse2(req, res, next) {
3073
3245
  if (parseErrors.length > 0) {
3074
3246
  switch (req.contractDetails.options?.responseValidation) {
3075
3247
  default:
3248
+ case void 0:
3076
3249
  case "error":
3077
3250
  res.type("text/plain");
3078
3251
  res.status(500);
@@ -3289,13 +3462,16 @@ function transformBasePath(basePath) {
3289
3462
  }
3290
3463
  return `/${basePath}`;
3291
3464
  }
3292
- function generateOpenApiDocument(serverUrls, serverDescriptions, versionedTags, versionedPaths, versionedSecuritySchemes, otherServers) {
3465
+ function generateOpenApiDocument(serverUrls, serverDescriptions, versionedTags, versionedPaths, versionedSecuritySchemes, appOptions) {
3466
+ const { title, description, contact } = appOptions !== false ? appOptions ?? {} : {};
3293
3467
  return {
3294
3468
  [OPENAPI_DEFAULT_VERSION]: {
3295
3469
  openapi: "3.1.0",
3296
3470
  info: {
3297
- title: process.env.API_TITLE || "API Definition",
3298
- version: process.env.VERSION || "latest"
3471
+ title: title || process.env.API_TITLE || "API Definition",
3472
+ version: "latest",
3473
+ description,
3474
+ contact
3299
3475
  },
3300
3476
  components: {
3301
3477
  securitySchemes: versionedSecuritySchemes[OPENAPI_DEFAULT_VERSION]
@@ -3305,8 +3481,7 @@ function generateOpenApiDocument(serverUrls, serverDescriptions, versionedTags,
3305
3481
  ...serverUrls.map((url, index) => ({
3306
3482
  url,
3307
3483
  description: serverDescriptions?.[index]
3308
- })),
3309
- ...otherServers || []
3484
+ }))
3310
3485
  ],
3311
3486
  paths: versionedPaths[OPENAPI_DEFAULT_VERSION]
3312
3487
  },
@@ -3316,8 +3491,10 @@ function generateOpenApiDocument(serverUrls, serverDescriptions, versionedTags,
3316
3491
  {
3317
3492
  openapi: "3.1.0",
3318
3493
  info: {
3319
- title: process.env.API_TITLE || "API Definition",
3320
- version
3494
+ title: title || process.env.API_TITLE || "API Definition",
3495
+ version,
3496
+ description,
3497
+ contact
3321
3498
  },
3322
3499
  components: {
3323
3500
  securitySchemes: versionedSecuritySchemes[version]
@@ -3327,8 +3504,7 @@ function generateOpenApiDocument(serverUrls, serverDescriptions, versionedTags,
3327
3504
  ...serverUrls.map((url, index) => ({
3328
3505
  url,
3329
3506
  description: serverDescriptions?.[index]
3330
- })),
3331
- ...otherServers || []
3507
+ }))
3332
3508
  ],
3333
3509
  paths
3334
3510
  }
@@ -3432,11 +3608,11 @@ function generateOperationObject(schemaValidator, path, method, controllerName,
3432
3608
  }
3433
3609
  }
3434
3610
  if (auth) {
3435
- responses[401] = {
3611
+ coercedResponses[401] = {
3436
3612
  description: httpStatusCodes_default[401],
3437
3613
  content: contentResolver(schemaValidator, schemaValidator.string)
3438
3614
  };
3439
- responses[403] = {
3615
+ coercedResponses[403] = {
3440
3616
  description: httpStatusCodes_default[403],
3441
3617
  content: contentResolver(schemaValidator, schemaValidator.string)
3442
3618
  };
@@ -3477,7 +3653,7 @@ function generateOperationObject(schemaValidator, path, method, controllerName,
3477
3653
  }
3478
3654
  return operationObject;
3479
3655
  }
3480
- function generateOpenApiSpecs(schemaValidator, serverUrls, serverDescriptions, application, otherServers) {
3656
+ function generateOpenApiSpecs(schemaValidator, serverUrls, serverDescriptions, application, appOptions) {
3481
3657
  const versionedPaths = {
3482
3658
  [OPENAPI_DEFAULT_VERSION]: {}
3483
3659
  };
@@ -3501,7 +3677,10 @@ function generateOpenApiSpecs(schemaValidator, serverUrls, serverDescriptions, a
3501
3677
  const openApiPath = (0, import_common12.openApiCompliantPath)(
3502
3678
  `${fullPath}${route.path === "/" ? "" : route.path}`
3503
3679
  );
3504
- const { name, summary, params, versions, auth } = route.contractDetails;
3680
+ const { name, summary, params, versions, auth, options: options2 } = route.contractDetails;
3681
+ if (!(options2?.openapi ?? router.routerOptions?.openapi ?? appOptions !== false)) {
3682
+ return;
3683
+ }
3505
3684
  if (versions) {
3506
3685
  for (const version of Object.keys(versions)) {
3507
3686
  if (!versionedPaths[version]) {
@@ -3595,7 +3774,7 @@ function generateOpenApiSpecs(schemaValidator, serverUrls, serverDescriptions, a
3595
3774
  versionedTags,
3596
3775
  versionedPaths,
3597
3776
  versionedSecuritySchemes,
3598
- otherServers
3777
+ appOptions
3599
3778
  );
3600
3779
  }
3601
3780
 
@@ -3663,11 +3842,11 @@ function mapToFetch(schemaValidator, routerMap) {
3663
3842
  schemaValidator,
3664
3843
  routerMap
3665
3844
  );
3666
- return (path, ...reqInit) => {
3845
+ return ((path, ...reqInit) => {
3667
3846
  const method = reqInit[0]?.method;
3668
3847
  const version = reqInit[0] != null && "version" in reqInit[0] ? reqInit[0].version : void 0;
3669
3848
  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]);
3670
- };
3849
+ });
3671
3850
  }
3672
3851
  function sdkClient(schemaValidator, routerMap) {
3673
3852
  return {
@@ -3751,6 +3930,7 @@ function metricsDefinitions(metrics2) {
3751
3930
  isForklaunchRequest,
3752
3931
  isForklaunchRouter,
3753
3932
  isInformational,
3933
+ isPortBound,
3754
3934
  isRedirection,
3755
3935
  isServerError,
3756
3936
  isSuccessful,