@forklaunch/core 0.11.6 → 0.12.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
@@ -39,6 +39,7 @@ __export(http_exports, {
39
39
  ForklaunchExpressLikeApplication: () => ForklaunchExpressLikeApplication,
40
40
  ForklaunchExpressLikeRouter: () => ForklaunchExpressLikeRouter,
41
41
  HTTPStatuses: () => HTTPStatuses,
42
+ OPENAPI_DEFAULT_VERSION: () => OPENAPI_DEFAULT_VERSION,
42
43
  OpenTelemetryCollector: () => OpenTelemetryCollector,
43
44
  delete_: () => delete_,
44
45
  discriminateBody: () => discriminateBody,
@@ -46,7 +47,7 @@ __export(http_exports, {
46
47
  enrichExpressLikeSend: () => enrichExpressLikeSend,
47
48
  evaluateTelemetryOptions: () => evaluateTelemetryOptions,
48
49
  generateMcpServer: () => generateMcpServer,
49
- generateSwaggerDocument: () => generateSwaggerDocument,
50
+ generateOpenApiSpecs: () => generateOpenApiSpecs,
50
51
  get: () => get,
51
52
  getCodeForStatus: () => getCodeForStatus,
52
53
  head: () => head,
@@ -69,6 +70,8 @@ __export(http_exports, {
69
70
  post: () => post,
70
71
  put: () => put,
71
72
  recordMetric: () => recordMetric,
73
+ sdkClient: () => sdkClient,
74
+ sdkRouter: () => sdkRouter,
72
75
  trace: () => trace3,
73
76
  typedAuthHandler: () => typedAuthHandler,
74
77
  typedHandler: () => typedHandler
@@ -87,13 +90,22 @@ function cors(corsOptions) {
87
90
  return res.getHeaders()[key];
88
91
  };
89
92
  }
90
- (0, import_cors.default)(corsOptions)(req, res, next ?? (() => {
91
- }));
93
+ (0, import_cors.default)(corsOptions)(
94
+ req,
95
+ res,
96
+ next ?? (() => {
97
+ })
98
+ );
92
99
  };
93
100
  }
94
101
 
95
102
  // src/http/router/expressLikeRouter.ts
96
- var import_common6 = require("@forklaunch/common");
103
+ var import_common7 = require("@forklaunch/common");
104
+
105
+ // src/http/guards/hasVersionedSchema.ts
106
+ function hasVersionedSchema(contractDetails) {
107
+ return typeof contractDetails === "object" && contractDetails !== null && "versions" in contractDetails && contractDetails.versions !== null;
108
+ }
97
109
 
98
110
  // src/http/guards/isForklaunchRouter.ts
99
111
  function isForklaunchRouter(maybeForklaunchRouter) {
@@ -125,12 +137,16 @@ function isForklaunchExpressLikeRouter(maybeForklaunchExpressLikeRouter) {
125
137
 
126
138
  // src/http/guards/isPathParamContractDetails.ts
127
139
  function isPathParamHttpContractDetails(maybePathParamHttpContractDetails) {
128
- return maybePathParamHttpContractDetails != null && typeof maybePathParamHttpContractDetails === "object" && "name" in maybePathParamHttpContractDetails && "summary" in maybePathParamHttpContractDetails && "responses" in maybePathParamHttpContractDetails && maybePathParamHttpContractDetails.name != null && maybePathParamHttpContractDetails.summary != null && maybePathParamHttpContractDetails.responses != null;
140
+ 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(
141
+ (version) => "responses" in version && version.responses != null
142
+ ));
129
143
  }
130
144
 
131
145
  // src/http/guards/isHttpContractDetails.ts
132
146
  function isHttpContractDetails(maybeContractDetails) {
133
- return isPathParamHttpContractDetails(maybeContractDetails) && "body" in maybeContractDetails && maybeContractDetails.body != null;
147
+ return isPathParamHttpContractDetails(maybeContractDetails) && ("body" in maybeContractDetails && maybeContractDetails.body != null || "versions" in maybeContractDetails && typeof maybeContractDetails.versions === "object" && maybeContractDetails.versions != null && Object.values(maybeContractDetails.versions).every(
148
+ (version) => "body" in version && version.body != null
149
+ ));
134
150
  }
135
151
 
136
152
  // src/http/guards/isTypedHandler.ts
@@ -279,7 +295,10 @@ async function checkAuthorizationToken(authorizationMethod, authorizationToken,
279
295
  if (!authorizationMethod.mapRoles) {
280
296
  return [500, "No role mapping function provided."];
281
297
  }
282
- const resourceRoles = await authorizationMethod.mapRoles(resourceId, req);
298
+ const resourceRoles = await authorizationMethod.mapRoles(
299
+ resourceId,
300
+ req
301
+ );
283
302
  if ("allowedRoles" in authorizationMethod && authorizationMethod.allowedRoles) {
284
303
  if (resourceRoles.intersection(authorizationMethod.allowedRoles).size === 0) {
285
304
  return invalidAuthorizationTokenRoles;
@@ -300,6 +319,7 @@ async function parseRequestAuth(req, res, next) {
300
319
  const [error, message] = await checkAuthorizationToken(
301
320
  auth,
302
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
303
323
  req
304
324
  ) ?? [];
305
325
  if (error != null) {
@@ -610,6 +630,7 @@ function enrichDetails(path, contractDetails, requestSchema, responseSchemas, op
610
630
  }
611
631
 
612
632
  // src/http/middleware/request/parse.middleware.ts
633
+ var import_common6 = require("@forklaunch/common");
613
634
  var import_validator = require("@forklaunch/validator");
614
635
 
615
636
  // src/http/guards/hasSend.ts
@@ -630,10 +651,49 @@ function parse(req, res, next) {
630
651
  headers: req.headers,
631
652
  body: req.body
632
653
  };
633
- const parsedRequest = req.schemaValidator.parse(
634
- req.requestSchema,
635
- request
636
- );
654
+ const schemaValidator = req.schemaValidator;
655
+ let matchedVersions;
656
+ let parsedRequest;
657
+ let collectedParseErrors;
658
+ if (req.contractDetails.versions) {
659
+ if ((0, import_common6.isRecord)(req.requestSchema)) {
660
+ let runningParseErrors = "";
661
+ matchedVersions = [];
662
+ Object.entries(req.requestSchema).forEach(([version, schema]) => {
663
+ const parsingResult = schemaValidator.parse(schema, request);
664
+ if (parsingResult.ok) {
665
+ parsedRequest = parsingResult;
666
+ matchedVersions.push(version);
667
+ req.version = version;
668
+ res.version = req.version;
669
+ } else {
670
+ runningParseErrors += (0, import_validator.prettyPrintParseErrors)(
671
+ parsingResult.errors,
672
+ `Version ${version} request`
673
+ );
674
+ }
675
+ });
676
+ if (!parsedRequest) {
677
+ parsedRequest = {
678
+ ok: false,
679
+ errors: []
680
+ };
681
+ collectedParseErrors = runningParseErrors;
682
+ }
683
+ } else {
684
+ req.version = Object.keys(req.contractDetails.versions).pop();
685
+ res.version = req.version;
686
+ parsedRequest = {
687
+ ok: true,
688
+ value: request
689
+ };
690
+ matchedVersions = Object.keys(req.contractDetails.versions);
691
+ }
692
+ } else {
693
+ const parsingResult = schemaValidator.parse(req.requestSchema, request);
694
+ parsedRequest = parsingResult;
695
+ matchedVersions = 0;
696
+ }
637
697
  if (parsedRequest.ok && isRequestShape(parsedRequest.value)) {
638
698
  req.body = parsedRequest.value.body;
639
699
  req.params = parsedRequest.value.params;
@@ -664,10 +724,7 @@ function parse(req, res, next) {
664
724
  res.status(400);
665
725
  if (hasSend(res)) {
666
726
  res.send(
667
- `${(0, import_validator.prettyPrintParseErrors)(
668
- parsedRequest.errors,
669
- "Request"
670
- )}
727
+ `${collectedParseErrors ?? (0, import_validator.prettyPrintParseErrors)(parsedRequest.errors, "Request")}
671
728
 
672
729
  Correlation id: ${req.context.correlationId ?? "No correlation ID"}`
673
730
  );
@@ -677,13 +734,14 @@ Correlation id: ${req.context.correlationId ?? "No correlation ID"}`
677
734
  return;
678
735
  case "warning":
679
736
  req.openTelemetryCollector.warn(
680
- (0, import_validator.prettyPrintParseErrors)(parsedRequest.errors, "Request")
737
+ collectedParseErrors ?? (0, import_validator.prettyPrintParseErrors)(parsedRequest.errors, "Request")
681
738
  );
682
739
  break;
683
740
  case "none":
684
741
  break;
685
742
  }
686
743
  }
744
+ req._parsedVersions = matchedVersions;
687
745
  next?.();
688
746
  }
689
747
 
@@ -821,13 +879,12 @@ function discriminateResponseBodies(schemaValidator, responses) {
821
879
 
822
880
  // src/http/router/expressLikeRouter.ts
823
881
  var ForklaunchExpressLikeRouter = class _ForklaunchExpressLikeRouter {
824
- constructor(basePath, schemaValidator, internal, postEnrichMiddleware, openTelemetryCollector, sdkName) {
882
+ constructor(basePath, schemaValidator, internal, postEnrichMiddleware, openTelemetryCollector) {
825
883
  this.basePath = basePath;
826
884
  this.schemaValidator = schemaValidator;
827
885
  this.internal = internal;
828
886
  this.postEnrichMiddleware = postEnrichMiddleware;
829
887
  this.openTelemetryCollector = openTelemetryCollector;
830
- this.sdkName = sdkName;
831
888
  if (process.env.NODE_ENV !== "test" && !process.env.VITEST) {
832
889
  process.on("uncaughtException", (err) => {
833
890
  this.openTelemetryCollector.error(`Uncaught exception: ${err}`);
@@ -850,8 +907,9 @@ var ForklaunchExpressLikeRouter = class _ForklaunchExpressLikeRouter {
850
907
  requestHandler;
851
908
  routers = [];
852
909
  routes = [];
853
- fetchMap = {};
910
+ _fetchMap = {};
854
911
  sdk = {};
912
+ sdkPaths = {};
855
913
  /**
856
914
  * Resolves middlewares based on the contract details.
857
915
  *
@@ -921,50 +979,112 @@ var ForklaunchExpressLikeRouter = class _ForklaunchExpressLikeRouter {
921
979
  }
922
980
  return controllerHandler;
923
981
  }
924
- #compile(contractDetails) {
982
+ #processContractDetailsIO(contractDetailsIO, params) {
925
983
  const schemaValidator = this.schemaValidator;
926
- let body = null;
927
- if (isHttpContractDetails(contractDetails)) {
928
- body = discriminateBody(this.schemaValidator, contractDetails.body);
929
- }
930
- const requestSchema = schemaValidator.compile(
931
- schemaValidator.schemify({
932
- ...contractDetails.params ? { params: contractDetails.params } : {},
933
- ...contractDetails.requestHeaders ? { headers: contractDetails.requestHeaders } : {},
934
- ...contractDetails.query ? { query: contractDetails.query } : {},
935
- ...body != null ? { body: body.schema } : {}
936
- })
937
- );
938
- const responseEntries = {
984
+ const responseSchemas = {
939
985
  400: schemaValidator.string,
940
986
  401: schemaValidator.string,
941
987
  403: schemaValidator.string,
942
988
  404: schemaValidator.string,
943
989
  500: schemaValidator.string,
944
- ...isPathParamHttpContractDetails(contractDetails) || isHttpContractDetails(contractDetails) ? Object.fromEntries(
990
+ ...Object.fromEntries(
945
991
  Object.entries(
946
992
  discriminateResponseBodies(
947
993
  this.schemaValidator,
948
- contractDetails.responses
994
+ contractDetailsIO.responses
949
995
  )
950
996
  ).map(([key, value]) => {
951
- return [key, value.schema];
997
+ return [Number(key), value.schema];
952
998
  })
953
- ) : {}
999
+ )
954
1000
  };
955
- const responseSchemas = {
956
- responses: {},
957
- ...contractDetails.responseHeaders ? {
958
- headers: schemaValidator.compile(
959
- schemaValidator.schemify(contractDetails.responseHeaders)
1001
+ return {
1002
+ requestSchema: schemaValidator.compile(
1003
+ schemaValidator.schemify({
1004
+ ...params != null ? { params } : { params: schemaValidator.unknown },
1005
+ ...contractDetailsIO.requestHeaders != null ? { headers: contractDetailsIO.requestHeaders } : { headers: schemaValidator.unknown },
1006
+ ...contractDetailsIO.query != null ? { query: contractDetailsIO.query } : { query: schemaValidator.unknown },
1007
+ ...contractDetailsIO.body != null ? {
1008
+ body: discriminateBody(
1009
+ this.schemaValidator,
1010
+ contractDetailsIO.body
1011
+ )?.schema
1012
+ } : { body: schemaValidator.unknown }
1013
+ })
1014
+ ),
1015
+ responseSchemas: {
1016
+ ...contractDetailsIO.responseHeaders != null ? {
1017
+ headers: schemaValidator.compile(
1018
+ schemaValidator.schemify(contractDetailsIO.responseHeaders)
1019
+ )
1020
+ } : { headers: schemaValidator.unknown },
1021
+ responses: Object.fromEntries(
1022
+ Object.entries(responseSchemas).map(([key, value]) => {
1023
+ return [
1024
+ key,
1025
+ schemaValidator.compile(schemaValidator.schemify(value))
1026
+ ];
1027
+ })
960
1028
  )
961
- } : {}
1029
+ }
962
1030
  };
963
- Object.entries(responseEntries).forEach(([code, responseShape]) => {
964
- responseSchemas.responses[Number(code)] = schemaValidator.compile(
965
- schemaValidator.schemify(responseShape)
1031
+ }
1032
+ #compile(contractDetails) {
1033
+ const schemaValidator = this.schemaValidator;
1034
+ let requestSchema;
1035
+ let responseSchemas;
1036
+ if (hasVersionedSchema(contractDetails)) {
1037
+ requestSchema = {};
1038
+ responseSchemas = {};
1039
+ Object.entries(contractDetails.versions ?? {}).forEach(
1040
+ ([version, versionedContractDetails]) => {
1041
+ const {
1042
+ requestSchema: versionedRequestSchema,
1043
+ responseSchemas: versionedResponseSchemas
1044
+ } = this.#processContractDetailsIO(
1045
+ versionedContractDetails,
1046
+ contractDetails.params
1047
+ );
1048
+ if ((0, import_common7.isRecord)(requestSchema)) {
1049
+ requestSchema = {
1050
+ ...requestSchema,
1051
+ [version]: versionedRequestSchema
1052
+ };
1053
+ }
1054
+ if ((0, import_common7.isRecord)(responseSchemas)) {
1055
+ responseSchemas = {
1056
+ ...responseSchemas,
1057
+ [version]: versionedResponseSchemas
1058
+ };
1059
+ }
1060
+ }
966
1061
  );
967
- });
1062
+ } else {
1063
+ const {
1064
+ requestSchema: unversionedRequestSchema,
1065
+ responseSchemas: unversionedResponseSchemas
1066
+ } = this.#processContractDetailsIO(
1067
+ {
1068
+ ..."params" in contractDetails && contractDetails.params != null ? { params: contractDetails.params } : { params: schemaValidator.unknown },
1069
+ ..."requestHeaders" in contractDetails && contractDetails.requestHeaders != null ? { requestHeaders: contractDetails.requestHeaders } : {
1070
+ requestHeaders: schemaValidator.unknown
1071
+ },
1072
+ ..."responseHeaders" in contractDetails && contractDetails.responseHeaders != null ? { responseHeaders: contractDetails.responseHeaders } : {
1073
+ responseHeaders: schemaValidator.unknown
1074
+ },
1075
+ ..."query" in contractDetails && contractDetails.query != null ? { query: contractDetails.query } : {
1076
+ query: schemaValidator.unknown
1077
+ },
1078
+ ..."body" in contractDetails && contractDetails.body != null ? { body: contractDetails.body } : {
1079
+ body: schemaValidator.unknown
1080
+ },
1081
+ responses: "responses" in contractDetails && contractDetails.responses != null ? contractDetails.responses : schemaValidator.unknown
1082
+ },
1083
+ contractDetails.params
1084
+ );
1085
+ requestSchema = unversionedRequestSchema;
1086
+ responseSchemas = unversionedResponseSchemas;
1087
+ }
968
1088
  return {
969
1089
  requestSchema,
970
1090
  responseSchemas
@@ -973,15 +1093,18 @@ var ForklaunchExpressLikeRouter = class _ForklaunchExpressLikeRouter {
973
1093
  /**
974
1094
  * Fetches a route from the route map and executes it with the given parameters.
975
1095
  *
976
- * @template Path - The path type that extends keyof fetchMap and string.
1096
+ * @template Path - The path type that extends keyof _fetchMap and string.
977
1097
  * @param {Path} path - The route path
978
- * @param {Parameters<fetchMap[Path]>[1]} [requestInit] - Optional request initialization parameters.
979
- * @returns {Promise<ReturnType<fetchMap[Path]>>} - The result of executing the route handler.
1098
+ * @param {Parameters<_fetchMap[Path]>[1]} [requestInit] - Optional request initialization parameters.
1099
+ * @returns {Promise<ReturnType<_fetchMap[Path]>>} - The result of executing the route handler.
980
1100
  */
981
1101
  fetch = async (path, ...reqInit) => {
982
- return this.fetchMap[path](
1102
+ const method = reqInit[0]?.method;
1103
+ const version = reqInit[0] != null && "version" in reqInit[0] ? reqInit[0].version : void 0;
1104
+ return (version ? this._fetchMap[path][method ?? "GET"][version] : this._fetchMap[path][method ?? "GET"])(
983
1105
  path,
984
1106
  reqInit[0]
1107
+ // reqInit
985
1108
  );
986
1109
  };
987
1110
  /**
@@ -991,7 +1114,7 @@ var ForklaunchExpressLikeRouter = class _ForklaunchExpressLikeRouter {
991
1114
  * @param controllerHandler
992
1115
  * @returns
993
1116
  */
994
- #localParamRequest(handlers, controllerHandler) {
1117
+ #localParamRequest(handlers, controllerHandler, version) {
995
1118
  return async (route, request) => {
996
1119
  let statusCode;
997
1120
  let responseMessage;
@@ -1001,7 +1124,8 @@ var ForklaunchExpressLikeRouter = class _ForklaunchExpressLikeRouter {
1001
1124
  query: request?.query ?? {},
1002
1125
  headers: request?.headers ?? {},
1003
1126
  body: discriminateBody(this.schemaValidator, request?.body)?.schema ?? {},
1004
- path: route
1127
+ path: route,
1128
+ version
1005
1129
  };
1006
1130
  const res = {
1007
1131
  status: (code) => {
@@ -1022,7 +1146,8 @@ var ForklaunchExpressLikeRouter = class _ForklaunchExpressLikeRouter {
1022
1146
  },
1023
1147
  sseEmitter: (generator) => {
1024
1148
  responseMessage = generator();
1025
- }
1149
+ },
1150
+ version
1026
1151
  };
1027
1152
  let cursor = handlers.shift();
1028
1153
  if (cursor) {
@@ -1056,20 +1181,20 @@ var ForklaunchExpressLikeRouter = class _ForklaunchExpressLikeRouter {
1056
1181
  registerRoute(method, path, registrationMethod, contractDetailsOrMiddlewareOrTypedHandler, ...middlewareOrMiddlewareAndTypedHandler) {
1057
1182
  if (isTypedHandler(contractDetailsOrMiddlewareOrTypedHandler)) {
1058
1183
  const { contractDetails, handlers } = contractDetailsOrMiddlewareOrTypedHandler;
1059
- this.registerRoute(method, path, registrationMethod, contractDetails, ...handlers);
1060
- return this;
1184
+ const router = this.registerRoute(method, path, registrationMethod, contractDetails, ...handlers);
1185
+ return router;
1061
1186
  } else {
1062
1187
  const maybeTypedHandler = middlewareOrMiddlewareAndTypedHandler[middlewareOrMiddlewareAndTypedHandler.length - 1];
1063
1188
  if (isTypedHandler(maybeTypedHandler)) {
1064
1189
  const { contractDetails, handlers } = maybeTypedHandler;
1065
- this.registerRoute(
1190
+ const router = this.registerRoute(
1066
1191
  method,
1067
1192
  path,
1068
1193
  registrationMethod,
1069
1194
  contractDetails,
1070
1195
  ...middlewareOrMiddlewareAndTypedHandler.concat(handlers)
1071
1196
  );
1072
- return this;
1197
+ return router;
1073
1198
  } else {
1074
1199
  if (isExpressLikeSchemaHandler(contractDetailsOrMiddlewareOrTypedHandler) || isTypedHandler(contractDetailsOrMiddlewareOrTypedHandler)) {
1075
1200
  throw new Error("Contract details are not defined");
@@ -1083,6 +1208,17 @@ var ForklaunchExpressLikeRouter = class _ForklaunchExpressLikeRouter {
1083
1208
  "Contract details are malformed for route definition"
1084
1209
  );
1085
1210
  }
1211
+ if (contractDetails.versions) {
1212
+ const parserTypes = Object.values(contractDetails.versions).map(
1213
+ (version) => discriminateBody(this.schemaValidator, version.body)?.parserType
1214
+ );
1215
+ const allParserTypesSame = parserTypes.length === 0 || parserTypes.every((pt) => pt === parserTypes[0]);
1216
+ if (!allParserTypesSame) {
1217
+ throw new Error(
1218
+ "All versioned contractDetails must have the same parsing type for body."
1219
+ );
1220
+ }
1221
+ }
1086
1222
  this.routes.push({
1087
1223
  basePath: this.basePath,
1088
1224
  path,
@@ -1091,22 +1227,39 @@ var ForklaunchExpressLikeRouter = class _ForklaunchExpressLikeRouter {
1091
1227
  });
1092
1228
  const { requestSchema, responseSchemas } = this.#compile(contractDetails);
1093
1229
  const controllerHandler = this.#extractControllerHandler(handlers);
1230
+ const resolvedMiddlewares = this.#resolveMiddlewares(
1231
+ path,
1232
+ contractDetails,
1233
+ requestSchema,
1234
+ responseSchemas
1235
+ ).concat(handlers);
1094
1236
  registrationMethod.bind(this.internal)(
1095
1237
  path,
1096
- ...this.#resolveMiddlewares(
1097
- path,
1098
- contractDetails,
1099
- requestSchema,
1100
- responseSchemas
1101
- ).concat(handlers),
1238
+ ...resolvedMiddlewares,
1102
1239
  this.#parseAndRunControllerHandler(controllerHandler)
1103
1240
  );
1104
- const localParamRequest = this.#localParamRequest(
1105
- handlers,
1106
- controllerHandler
1241
+ (0, import_common7.toRecord)(this._fetchMap)[(0, import_common7.sanitizePathSlashes)(`${this.basePath}${path}`)] = {
1242
+ ...this._fetchMap[(0, import_common7.sanitizePathSlashes)(`${this.basePath}${path}`)] ?? {},
1243
+ [method.toUpperCase()]: contractDetails.versions ? Object.fromEntries(
1244
+ Object.keys(contractDetails.versions).map((version) => [
1245
+ version,
1246
+ this.#localParamRequest(handlers, controllerHandler, version)
1247
+ ])
1248
+ ) : this.#localParamRequest(handlers, controllerHandler)
1249
+ };
1250
+ (0, import_common7.toRecord)(this.sdk)[(0, import_common7.toPrettyCamelCase)(contractDetails.name)] = contractDetails.versions ? Object.fromEntries(
1251
+ Object.keys(contractDetails.versions).map((version) => [
1252
+ version,
1253
+ (req) => this.#localParamRequest(
1254
+ handlers,
1255
+ controllerHandler,
1256
+ version
1257
+ )(`${this.basePath}${path}`, req)
1258
+ ])
1259
+ ) : (req) => this.#localParamRequest(handlers, controllerHandler)(
1260
+ `${this.basePath}${path}`,
1261
+ req
1107
1262
  );
1108
- (0, import_common6.toRecord)(this.fetchMap)[(0, import_common6.sanitizePathSlashes)(`${this.basePath}${path}`)] = localParamRequest;
1109
- (0, import_common6.toRecord)(this.sdk)[(0, import_common6.toPrettyCamelCase)(contractDetails.name ?? this.basePath)] = (req) => localParamRequest(`${this.basePath}${path}`, req);
1110
1263
  return this;
1111
1264
  }
1112
1265
  }
@@ -1192,11 +1345,11 @@ var ForklaunchExpressLikeRouter = class _ForklaunchExpressLikeRouter {
1192
1345
  return this;
1193
1346
  }
1194
1347
  addRouterToSdk(router) {
1195
- Object.entries(router.fetchMap).map(
1196
- ([key, value]) => (0, import_common6.toRecord)(this.fetchMap)[(0, import_common6.sanitizePathSlashes)(`${this.basePath}${key}`)] = value
1348
+ Object.entries(router._fetchMap).map(
1349
+ ([key, value]) => (0, import_common7.toRecord)(this._fetchMap)[(0, import_common7.sanitizePathSlashes)(`${this.basePath}${key}`)] = value
1197
1350
  );
1198
- const existingSdk = this.sdk[router.sdkName ?? (0, import_common6.toPrettyCamelCase)(router.basePath)];
1199
- (0, import_common6.toRecord)(this.sdk)[router.sdkName ?? (0, import_common6.toPrettyCamelCase)(router.basePath)] = {
1351
+ const existingSdk = this.sdk[router.sdkName ?? (0, import_common7.toPrettyCamelCase)(router.basePath)];
1352
+ (0, import_common7.toRecord)(this.sdk)[router.sdkName ?? (0, import_common7.toPrettyCamelCase)(router.basePath)] = {
1200
1353
  ...typeof existingSdk === "object" ? existingSdk : {},
1201
1354
  ...router.sdk
1202
1355
  };
@@ -1453,8 +1606,9 @@ var ForklaunchExpressLikeRouter = class _ForklaunchExpressLikeRouter {
1453
1606
  cloneInternals(clone) {
1454
1607
  clone.routers = [...this.routers];
1455
1608
  clone.routes = [...this.routes];
1456
- clone.fetchMap = { ...this.fetchMap };
1609
+ clone._fetchMap = { ...this._fetchMap };
1457
1610
  clone.sdk = { ...this.sdk };
1611
+ clone.sdkPaths = { ...this.sdkPaths };
1458
1612
  }
1459
1613
  clone() {
1460
1614
  const clone = new _ForklaunchExpressLikeRouter(
@@ -1462,8 +1616,7 @@ var ForklaunchExpressLikeRouter = class _ForklaunchExpressLikeRouter {
1462
1616
  this.schemaValidator,
1463
1617
  this.internal,
1464
1618
  this.postEnrichMiddleware,
1465
- this.openTelemetryCollector,
1466
- this.sdkName
1619
+ this.openTelemetryCollector
1467
1620
  );
1468
1621
  this.cloneInternals(clone);
1469
1622
  return clone;
@@ -1518,9 +1671,13 @@ function typedHandler(_schemaValidator, pathOrContractMethod, contractMethodOrCo
1518
1671
  throw new Error("Invalid definition for handler");
1519
1672
  }
1520
1673
  }
1674
+ if (isPath(pathOrContractMethod) && typeof contractMethodOrContractDetails !== "string") {
1675
+ throw new Error("Contract method not supplied, bailing");
1676
+ }
1521
1677
  return {
1522
1678
  _typedHandler: true,
1523
1679
  _path: isPath(pathOrContractMethod) ? pathOrContractMethod : void 0,
1680
+ _method: isPath(pathOrContractMethod) ? contractMethodOrContractDetails : pathOrContractMethod,
1524
1681
  contractDetails,
1525
1682
  handlers
1526
1683
  };
@@ -2564,33 +2721,53 @@ var import_common8 = require("@forklaunch/common");
2564
2721
  var import_zod = require("@forklaunch/validator/zod");
2565
2722
  var import_fastmcp = require("fastmcp");
2566
2723
 
2724
+ // src/http/guards/isVersionedInputSchema.ts
2725
+ function isUnionable(schema) {
2726
+ return schema.length > 1;
2727
+ }
2728
+
2567
2729
  // src/http/router/unpackRouters.ts
2568
- var import_common7 = require("@forklaunch/common");
2569
- function unpackRouters(routers, recursiveBasePath = [], recursiveSdkPath = []) {
2730
+ function unpackRouters(routers, recursiveBasePath = []) {
2570
2731
  return routers.reduce((acc, router) => {
2571
2732
  acc.push({
2572
2733
  fullPath: [...recursiveBasePath, router.basePath].join(""),
2573
- sdkPath: [
2574
- ...recursiveSdkPath,
2575
- (0, import_common7.toPrettyCamelCase)(router.sdkName ?? router.basePath)
2576
- ].join("."),
2577
2734
  router
2578
2735
  });
2579
2736
  acc.push(
2580
- ...unpackRouters(
2581
- router.routers,
2582
- [...recursiveBasePath, router.basePath],
2583
- [
2584
- ...recursiveSdkPath,
2585
- (0, import_common7.toPrettyCamelCase)(router.sdkName ?? router.basePath)
2586
- ]
2587
- )
2737
+ ...unpackRouters(router.routers, [
2738
+ ...recursiveBasePath,
2739
+ router.basePath
2740
+ ])
2588
2741
  );
2589
2742
  return acc;
2590
2743
  }, []);
2591
2744
  }
2592
2745
 
2593
2746
  // src/http/mcpGenerator/mcpGenerator.ts
2747
+ function generateInputSchema(schemaValidator, body, params, query, requestHeaders, auth) {
2748
+ let discriminatedBody;
2749
+ if (body) {
2750
+ discriminatedBody = discriminateBody(schemaValidator, body);
2751
+ }
2752
+ return schemaValidator.schemify({
2753
+ ...discriminatedBody && body ? {
2754
+ ..."contentType" in body ? { contentType: body.contentType } : {},
2755
+ body: schemaValidator.schemify(discriminatedBody.schema)
2756
+ } : {},
2757
+ ...params ? { params: schemaValidator.schemify(params) } : {},
2758
+ ...query ? { query: schemaValidator.schemify(query) } : {},
2759
+ ...requestHeaders ? {
2760
+ headers: schemaValidator.schemify({
2761
+ ...requestHeaders,
2762
+ ...auth ? {
2763
+ [auth.headerName ?? "authorization"]: import_zod.string.startsWith(
2764
+ auth.tokenPrefix ?? ("basic" in auth ? "Basic " : "Bearer ")
2765
+ )
2766
+ } : {}
2767
+ })
2768
+ } : {}
2769
+ });
2770
+ }
2594
2771
  function generateMcpServer(schemaValidator, protocol, host, port, version, routers, options2, contentTypeMap) {
2595
2772
  if (!(schemaValidator instanceof import_zod.ZodSchemaValidator)) {
2596
2773
  throw new Error(
@@ -2604,35 +2781,36 @@ function generateMcpServer(schemaValidator, protocol, host, port, version, route
2604
2781
  });
2605
2782
  unpackRouters(routers).forEach(({ fullPath, router }) => {
2606
2783
  router.routes.forEach((route) => {
2607
- let discriminatedBody;
2608
- if ("body" in route.contractDetails) {
2609
- discriminatedBody = discriminateBody(
2610
- schemaValidator,
2611
- route.contractDetails.body
2784
+ const inputSchemas = [];
2785
+ if (route.contractDetails.versions) {
2786
+ Object.values(route.contractDetails.versions).forEach((version2) => {
2787
+ inputSchemas.push(
2788
+ generateInputSchema(
2789
+ schemaValidator,
2790
+ version2.body,
2791
+ route.contractDetails.params,
2792
+ version2.query,
2793
+ version2.requestHeaders,
2794
+ route.contractDetails.auth
2795
+ )
2796
+ );
2797
+ });
2798
+ } else {
2799
+ inputSchemas.push(
2800
+ generateInputSchema(
2801
+ schemaValidator,
2802
+ route.contractDetails.body,
2803
+ route.contractDetails.params,
2804
+ route.contractDetails.query,
2805
+ route.contractDetails.requestHeaders,
2806
+ route.contractDetails.auth
2807
+ )
2612
2808
  );
2613
2809
  }
2614
- const inputSchema = schemaValidator.schemify({
2615
- ...discriminatedBody && "body" in route.contractDetails ? {
2616
- ..."contentType" in route.contractDetails.body ? { contentType: route.contractDetails.body.contentType } : {},
2617
- body: schemaValidator.schemify(discriminatedBody.schema)
2618
- } : {},
2619
- ...route.contractDetails.params ? { params: schemaValidator.schemify(route.contractDetails.params) } : {},
2620
- ...route.contractDetails.query ? { query: schemaValidator.schemify(route.contractDetails.query) } : {},
2621
- ...route.contractDetails.requestHeaders ? {
2622
- headers: schemaValidator.schemify({
2623
- ...route.contractDetails.requestHeaders,
2624
- ...route.contractDetails.auth ? {
2625
- [route.contractDetails.auth.headerName ?? "authorization"]: import_zod.string.startsWith(
2626
- route.contractDetails.auth.tokenPrefix ?? ("basic" in route.contractDetails.auth ? "Basic " : "Bearer ")
2627
- )
2628
- } : {}
2629
- })
2630
- } : {}
2631
- });
2632
2810
  mcpServer.addTool({
2633
2811
  name: route.contractDetails.name,
2634
2812
  description: route.contractDetails.summary,
2635
- parameters: inputSchema,
2813
+ parameters: isUnionable(inputSchemas) ? schemaValidator.union(inputSchemas) : inputSchemas[0],
2636
2814
  execute: async (args) => {
2637
2815
  const { contentType, body, params, query, headers } = args;
2638
2816
  let url = `${protocol}://${host}:${port}${fullPath}${route.path}`;
@@ -2644,6 +2822,22 @@ function generateMcpServer(schemaValidator, protocol, host, port, version, route
2644
2822
  );
2645
2823
  }
2646
2824
  }
2825
+ let bodySchema;
2826
+ let responsesSchemas;
2827
+ if (route.contractDetails.versions) {
2828
+ Object.values(route.contractDetails.versions).forEach(
2829
+ (version2, index) => {
2830
+ if (version2.body && schemaValidator.parse(inputSchemas[index], args).ok) {
2831
+ bodySchema = version2.body;
2832
+ responsesSchemas = version2.responses;
2833
+ }
2834
+ }
2835
+ );
2836
+ } else {
2837
+ bodySchema = route.contractDetails.body;
2838
+ responsesSchemas = route.contractDetails.responses;
2839
+ }
2840
+ const discriminatedBody = bodySchema ? discriminateBody(schemaValidator, bodySchema) : void 0;
2647
2841
  let parsedBody;
2648
2842
  if (discriminatedBody) {
2649
2843
  switch (discriminatedBody.parserType) {
@@ -2719,9 +2913,12 @@ function generateMcpServer(schemaValidator, protocol, host, port, version, route
2719
2913
  `Error received while proxying request to ${url}: ${await response.text()}`
2720
2914
  );
2721
2915
  }
2916
+ if (!responsesSchemas) {
2917
+ throw new Error("No responses schemas found");
2918
+ }
2722
2919
  const contractContentType = discriminateResponseBodies(
2723
2920
  schemaValidator,
2724
- route.contractDetails.responses
2921
+ responsesSchemas
2725
2922
  )[response.status].contentType;
2726
2923
  switch (contentTypeMap && contentTypeMap[contractContentType] ? contentTypeMap[contractContentType] : contractContentType) {
2727
2924
  case "application/json":
@@ -2775,15 +2972,56 @@ function generateMcpServer(schemaValidator, protocol, host, port, version, route
2775
2972
 
2776
2973
  // src/http/middleware/response/parse.middleware.ts
2777
2974
  var import_validator2 = require("@forklaunch/validator");
2975
+
2976
+ // src/http/guards/isResponseCompiledSchema.ts
2977
+ function isResponseCompiledSchema(schema) {
2978
+ return typeof schema === "object" && schema !== null && "responses" in schema;
2979
+ }
2980
+
2981
+ // src/http/middleware/response/parse.middleware.ts
2778
2982
  function parse2(req, res, next) {
2779
- const { headers, responses } = res.responseSchemas;
2983
+ let headers;
2984
+ let responses;
2985
+ const responseSchemas = res.responseSchemas;
2986
+ const schemaValidator = req.schemaValidator;
2987
+ if (!isResponseCompiledSchema(responseSchemas)) {
2988
+ const parsedVersions = req._parsedVersions;
2989
+ if (typeof parsedVersions === "number") {
2990
+ throw new Error("Request failed to parse given version map");
2991
+ }
2992
+ const mappedHeaderSchemas = parsedVersions.map(
2993
+ (version) => responseSchemas[version].headers
2994
+ );
2995
+ const mappedResponseSchemas = parsedVersions.map(
2996
+ (version) => responseSchemas[version].responses
2997
+ );
2998
+ const collapsedResponseSchemas = mappedResponseSchemas.reduce((acc, responseSchema) => {
2999
+ Object.entries(responseSchema).forEach(([status, schema]) => {
3000
+ if (!acc[Number(status)]) {
3001
+ acc[Number(status)] = [];
3002
+ }
3003
+ acc[Number(status)].push(schema);
3004
+ });
3005
+ return acc;
3006
+ }, {});
3007
+ headers = schemaValidator.union(mappedHeaderSchemas);
3008
+ responses = Object.fromEntries(
3009
+ Object.entries(collapsedResponseSchemas).map(([status, schemas]) => [
3010
+ status,
3011
+ schemaValidator.union(schemas)
3012
+ ])
3013
+ );
3014
+ } else {
3015
+ headers = responseSchemas.headers;
3016
+ responses = responseSchemas.responses;
3017
+ }
2780
3018
  const statusCode = Number(res.statusCode);
2781
- const parsedResponse = req.schemaValidator.parse(
2782
- responses?.[statusCode],
3019
+ const parsedResponse = schemaValidator.parse(
3020
+ [400, 401, 404, 403, 500].includes(statusCode) ? schemaValidator.union([schemaValidator.string, responses?.[statusCode]]) : responses?.[statusCode],
2783
3021
  res.bodyData
2784
3022
  );
2785
- const parsedHeaders = req.schemaValidator.parse(
2786
- headers ?? req.schemaValidator.unknown,
3023
+ const parsedHeaders = schemaValidator.parse(
3024
+ headers ?? schemaValidator.unknown,
2787
3025
  res.getHeaders()
2788
3026
  );
2789
3027
  const parseErrors = [];
@@ -2870,9 +3108,21 @@ function enrichExpressLikeSend(instance, req, res, originalOperation, originalSe
2870
3108
  originalSend.call(instance, "Not Found");
2871
3109
  errorSent = true;
2872
3110
  }
3111
+ let responses;
3112
+ if (req.contractDetails.responses == null && (req.contractDetails.versions == null || Object.values(req.contractDetails.versions).some(
3113
+ (version) => version?.responses == null
3114
+ ))) {
3115
+ throw new Error("Responses schema definitions are required");
3116
+ } else {
3117
+ if (req.contractDetails.responses != null) {
3118
+ responses = req.contractDetails.responses;
3119
+ } else {
3120
+ responses = req.contractDetails.versions[req.version].responses;
3121
+ }
3122
+ }
2873
3123
  const responseBodies = discriminateResponseBodies(
2874
3124
  req.schemaValidator,
2875
- req.contractDetails.responses
3125
+ responses
2876
3126
  );
2877
3127
  if (responseBodies != null && responseBodies[Number(res.statusCode)] != null) {
2878
3128
  res.type(responseBodies[Number(res.statusCode)].contentType);
@@ -2999,6 +3249,7 @@ ${res.locals.errorMessage}`;
2999
3249
 
3000
3250
  // src/http/openApiV3Generator/openApiV3Generator.ts
3001
3251
  var import_common11 = require("@forklaunch/common");
3252
+ var OPENAPI_DEFAULT_VERSION = Symbol("default");
3002
3253
  function toUpperCase(str) {
3003
3254
  return str.charAt(0).toUpperCase() + str.slice(1);
3004
3255
  }
@@ -3008,25 +3259,50 @@ function transformBasePath(basePath) {
3008
3259
  }
3009
3260
  return `/${basePath}`;
3010
3261
  }
3011
- function generateOpenApiDocument(protocol, host, port, tags, paths, securitySchemes, otherServers) {
3262
+ function generateOpenApiDocument(protocol, host, port, versionedTags, versionedPaths, versionedSecuritySchemes, otherServers) {
3012
3263
  return {
3013
- openapi: "3.1.0",
3014
- info: {
3015
- title: process.env.API_TITLE || "",
3016
- version: process.env.VERSION || "1.0.0"
3017
- },
3018
- components: {
3019
- securitySchemes
3020
- },
3021
- tags,
3022
- servers: [
3023
- {
3024
- url: `${protocol}://${host}:${port}`,
3025
- description: "Main Server"
3264
+ [OPENAPI_DEFAULT_VERSION]: {
3265
+ openapi: "3.1.0",
3266
+ info: {
3267
+ title: process.env.API_TITLE || "API Definition",
3268
+ version: process.env.VERSION || "latest"
3026
3269
  },
3027
- ...otherServers || []
3028
- ],
3029
- paths
3270
+ components: {
3271
+ securitySchemes: versionedSecuritySchemes[OPENAPI_DEFAULT_VERSION]
3272
+ },
3273
+ tags: versionedTags[OPENAPI_DEFAULT_VERSION],
3274
+ servers: [
3275
+ {
3276
+ url: `${protocol}://${host}:${port}`,
3277
+ description: "Main Server"
3278
+ },
3279
+ ...otherServers || []
3280
+ ],
3281
+ paths: versionedPaths[OPENAPI_DEFAULT_VERSION]
3282
+ },
3283
+ ...Object.fromEntries(
3284
+ Object.entries(versionedPaths).map(([version, paths]) => [
3285
+ version,
3286
+ {
3287
+ openapi: "3.1.0",
3288
+ info: {
3289
+ title: process.env.API_TITLE || "API Definition",
3290
+ version
3291
+ },
3292
+ components: {
3293
+ securitySchemes: versionedSecuritySchemes[version]
3294
+ },
3295
+ tags: versionedTags[version],
3296
+ servers: [
3297
+ {
3298
+ url: `${protocol}://${host}:${port}`,
3299
+ description: "Main Server"
3300
+ }
3301
+ ],
3302
+ paths
3303
+ }
3304
+ ])
3305
+ )
3030
3306
  };
3031
3307
  }
3032
3308
  function contentResolver(schemaValidator, body, contentType) {
@@ -3045,143 +3321,232 @@ function contentResolver(schemaValidator, body, contentType) {
3045
3321
  }
3046
3322
  };
3047
3323
  }
3048
- function generateSwaggerDocument(schemaValidator, protocol, host, port, routers, otherServers) {
3049
- const tags = [];
3050
- const paths = {};
3051
- const securitySchemes = {};
3052
- unpackRouters(routers).forEach(({ fullPath, router, sdkPath }) => {
3324
+ function generateOperationObject(schemaValidator, path, method, controllerName, sdkPaths, securitySchemes, name, summary, responses, params, responseHeaders, requestHeaders, query, body, auth) {
3325
+ const typedSchemaValidator = schemaValidator;
3326
+ const coercedResponses = {};
3327
+ const discriminatedResponseBodiesResult = discriminateResponseBodies(
3328
+ schemaValidator,
3329
+ responses
3330
+ );
3331
+ for (const key in discriminatedResponseBodiesResult) {
3332
+ coercedResponses[key] = {
3333
+ description: httpStatusCodes_default[key],
3334
+ content: contentResolver(
3335
+ schemaValidator,
3336
+ discriminatedResponseBodiesResult[key].schema,
3337
+ discriminatedResponseBodiesResult[key].contentType
3338
+ ),
3339
+ headers: responseHeaders ? Object.fromEntries(
3340
+ Object.entries(responseHeaders).map(([key2, value]) => [
3341
+ key2,
3342
+ {
3343
+ schema: typedSchemaValidator.openapi(value)
3344
+ }
3345
+ ])
3346
+ ) : void 0
3347
+ };
3348
+ }
3349
+ const commonErrors = [400, 404, 500];
3350
+ for (const error of commonErrors) {
3351
+ if (!(error in responses)) {
3352
+ coercedResponses[error] = {
3353
+ description: httpStatusCodes_default[error],
3354
+ content: contentResolver(schemaValidator, schemaValidator.string)
3355
+ };
3356
+ }
3357
+ }
3358
+ const operationObject = {
3359
+ tags: [controllerName],
3360
+ summary: `${name}: ${summary}`,
3361
+ parameters: [],
3362
+ responses: coercedResponses,
3363
+ operationId: sdkPaths[[method, path].join(".")]
3364
+ };
3365
+ if (params) {
3366
+ for (const key in params) {
3367
+ operationObject.parameters?.push({
3368
+ name: key,
3369
+ in: "path",
3370
+ schema: typedSchemaValidator.openapi(params[key])
3371
+ });
3372
+ }
3373
+ }
3374
+ const discriminatedBodyResult = body ? discriminateBody(schemaValidator, body) : null;
3375
+ if (discriminatedBodyResult) {
3376
+ operationObject.requestBody = {
3377
+ required: true,
3378
+ content: contentResolver(
3379
+ schemaValidator,
3380
+ discriminatedBodyResult.schema,
3381
+ discriminatedBodyResult.contentType
3382
+ )
3383
+ };
3384
+ }
3385
+ if (requestHeaders) {
3386
+ for (const key in requestHeaders) {
3387
+ operationObject.parameters?.push({
3388
+ name: key,
3389
+ in: "header",
3390
+ schema: typedSchemaValidator.openapi(requestHeaders[key])
3391
+ });
3392
+ }
3393
+ }
3394
+ if (query) {
3395
+ for (const key in query) {
3396
+ operationObject.parameters?.push({
3397
+ name: key,
3398
+ in: "query",
3399
+ schema: typedSchemaValidator.openapi(query[key])
3400
+ });
3401
+ }
3402
+ }
3403
+ if (auth) {
3404
+ responses[401] = {
3405
+ description: httpStatusCodes_default[401],
3406
+ content: contentResolver(schemaValidator, schemaValidator.string)
3407
+ };
3408
+ responses[403] = {
3409
+ description: httpStatusCodes_default[403],
3410
+ content: contentResolver(schemaValidator, schemaValidator.string)
3411
+ };
3412
+ if ("basic" in auth) {
3413
+ operationObject.security = [
3414
+ {
3415
+ basic: Array.from(
3416
+ "allowedPermissions" in auth ? auth.allowedPermissions?.values() || [] : []
3417
+ )
3418
+ }
3419
+ ];
3420
+ securitySchemes["basic"] = {
3421
+ type: "http",
3422
+ scheme: "basic"
3423
+ };
3424
+ } else if (auth) {
3425
+ operationObject.security = [
3426
+ {
3427
+ [auth.headerName !== "Authorization" ? "bearer" : "apiKey"]: Array.from(
3428
+ "allowedPermissions" in auth ? auth.allowedPermissions?.values() || [] : []
3429
+ )
3430
+ }
3431
+ ];
3432
+ if (auth.headerName && auth.headerName !== "Authorization") {
3433
+ securitySchemes[auth.headerName] = {
3434
+ type: "apiKey",
3435
+ in: "header",
3436
+ name: auth.headerName
3437
+ };
3438
+ } else {
3439
+ securitySchemes["Authorization"] = {
3440
+ type: "http",
3441
+ scheme: "bearer",
3442
+ bearerFormat: "JWT"
3443
+ };
3444
+ }
3445
+ }
3446
+ }
3447
+ return operationObject;
3448
+ }
3449
+ function generateOpenApiSpecs(schemaValidator, protocol, host, port, routers, otherServers) {
3450
+ const versionedPaths = {
3451
+ [OPENAPI_DEFAULT_VERSION]: {}
3452
+ };
3453
+ const versionedTags = {
3454
+ [OPENAPI_DEFAULT_VERSION]: []
3455
+ };
3456
+ const versionedSecuritySchemes = {
3457
+ [OPENAPI_DEFAULT_VERSION]: {}
3458
+ };
3459
+ unpackRouters(routers).forEach(({ fullPath, router }) => {
3053
3460
  const controllerName = transformBasePath(fullPath);
3054
- tags.push({
3055
- name: controllerName,
3056
- description: `${toUpperCase(controllerName)} Operations`
3057
- });
3058
3461
  router.routes.forEach((route) => {
3059
3462
  const openApiPath = (0, import_common11.openApiCompliantPath)(
3060
3463
  `${fullPath}${route.path === "/" ? "" : route.path}`
3061
3464
  );
3062
- if (!paths[openApiPath]) {
3063
- paths[openApiPath] = {};
3064
- }
3065
- const { name, summary, query, requestHeaders } = route.contractDetails;
3066
- const responses = {};
3067
- const discriminatedResponseBodiesResult = discriminateResponseBodies(
3068
- schemaValidator,
3069
- route.contractDetails.responses
3070
- );
3071
- for (const key in discriminatedResponseBodiesResult) {
3072
- responses[key] = {
3073
- description: httpStatusCodes_default[key],
3074
- content: contentResolver(
3465
+ const { name, summary, params, versions, auth } = route.contractDetails;
3466
+ if (versions) {
3467
+ for (const version of Object.keys(versions)) {
3468
+ if (!versionedPaths[version]) {
3469
+ versionedPaths[version] = {};
3470
+ }
3471
+ if (!versionedPaths[version][openApiPath]) {
3472
+ versionedPaths[version][openApiPath] = {};
3473
+ }
3474
+ if (!versionedTags[version]) {
3475
+ versionedTags[version] = [];
3476
+ }
3477
+ if (!versionedTags[version].find((tag) => tag.name === controllerName)) {
3478
+ versionedTags[version].push({
3479
+ name: controllerName,
3480
+ description: `${toUpperCase(controllerName)} Operations`
3481
+ });
3482
+ }
3483
+ if (!versionedSecuritySchemes[version]) {
3484
+ versionedSecuritySchemes[version] = {};
3485
+ }
3486
+ const { query, requestHeaders, body, responses, responseHeaders } = versions[version];
3487
+ const operationObject = generateOperationObject(
3075
3488
  schemaValidator,
3076
- discriminatedResponseBodiesResult[key].schema,
3077
- discriminatedResponseBodiesResult[key].contentType
3078
- )
3079
- };
3080
- }
3081
- const commonErrors = [400, 404, 500];
3082
- for (const error of commonErrors) {
3083
- if (!(error in responses)) {
3084
- responses[error] = {
3085
- description: httpStatusCodes_default[error],
3086
- content: contentResolver(schemaValidator, schemaValidator.string)
3087
- };
3489
+ route.path,
3490
+ route.method,
3491
+ controllerName,
3492
+ router.sdkPaths,
3493
+ versionedSecuritySchemes[version],
3494
+ name,
3495
+ summary,
3496
+ responses,
3497
+ params,
3498
+ responseHeaders,
3499
+ requestHeaders,
3500
+ query,
3501
+ body,
3502
+ auth
3503
+ );
3504
+ if (route.method !== "middleware") {
3505
+ versionedPaths[version][openApiPath][route.method] = operationObject;
3506
+ }
3088
3507
  }
3089
- }
3090
- const operationObject = {
3091
- tags: [controllerName],
3092
- summary: `${name}: ${summary}`,
3093
- parameters: [],
3094
- responses,
3095
- operationId: `${sdkPath}.${(0, import_common11.toPrettyCamelCase)(name)}`
3096
- };
3097
- if (route.contractDetails.params) {
3098
- for (const key in route.contractDetails.params) {
3099
- operationObject.parameters?.push({
3100
- name: key,
3101
- in: "path",
3102
- schema: schemaValidator.openapi(
3103
- route.contractDetails.params[key]
3104
- )
3105
- });
3508
+ } else {
3509
+ if (!versionedPaths[OPENAPI_DEFAULT_VERSION]) {
3510
+ versionedPaths[OPENAPI_DEFAULT_VERSION] = {};
3106
3511
  }
3107
- }
3108
- const discriminatedBodyResult = "body" in route.contractDetails ? discriminateBody(schemaValidator, route.contractDetails.body) : null;
3109
- if (discriminatedBodyResult) {
3110
- operationObject.requestBody = {
3111
- required: true,
3112
- content: contentResolver(
3113
- schemaValidator,
3114
- discriminatedBodyResult.schema,
3115
- discriminatedBodyResult.contentType
3116
- )
3117
- };
3118
- }
3119
- if (requestHeaders) {
3120
- for (const key in requestHeaders) {
3121
- operationObject.parameters?.push({
3122
- name: key,
3123
- in: "header",
3124
- schema: schemaValidator.openapi(
3125
- requestHeaders[key]
3126
- )
3127
- });
3512
+ if (!versionedPaths[OPENAPI_DEFAULT_VERSION][openApiPath]) {
3513
+ versionedPaths[OPENAPI_DEFAULT_VERSION][openApiPath] = {};
3128
3514
  }
3129
- }
3130
- if (query) {
3131
- for (const key in query) {
3132
- operationObject.parameters?.push({
3133
- name: key,
3134
- in: "query",
3135
- schema: schemaValidator.openapi(query[key])
3515
+ if (!versionedTags[OPENAPI_DEFAULT_VERSION]) {
3516
+ versionedTags[OPENAPI_DEFAULT_VERSION] = [];
3517
+ }
3518
+ if (!versionedTags[OPENAPI_DEFAULT_VERSION].find(
3519
+ (tag) => tag.name === controllerName
3520
+ )) {
3521
+ versionedTags[OPENAPI_DEFAULT_VERSION].push({
3522
+ name: controllerName,
3523
+ description: `${toUpperCase(controllerName)} Operations`
3136
3524
  });
3137
3525
  }
3138
- }
3139
- if (route.contractDetails.auth) {
3140
- responses[401] = {
3141
- description: httpStatusCodes_default[401],
3142
- content: contentResolver(schemaValidator, schemaValidator.string)
3143
- };
3144
- responses[403] = {
3145
- description: httpStatusCodes_default[403],
3146
- content: contentResolver(schemaValidator, schemaValidator.string)
3147
- };
3148
- if ("basic" in route.contractDetails.auth) {
3149
- operationObject.security = [
3150
- {
3151
- basic: Array.from(
3152
- "allowedPermissions" in route.contractDetails.auth ? route.contractDetails.auth.allowedPermissions?.values() || [] : []
3153
- )
3154
- }
3155
- ];
3156
- securitySchemes["basic"] = {
3157
- type: "http",
3158
- scheme: "basic"
3159
- };
3160
- } else if (route.contractDetails.auth) {
3161
- operationObject.security = [
3162
- {
3163
- [route.contractDetails.auth.headerName !== "Authorization" ? "bearer" : "apiKey"]: Array.from(
3164
- "allowedPermissions" in route.contractDetails.auth ? route.contractDetails.auth.allowedPermissions?.values() || [] : []
3165
- )
3166
- }
3167
- ];
3168
- if (route.contractDetails.auth.headerName && route.contractDetails.auth.headerName !== "Authorization") {
3169
- securitySchemes[route.contractDetails.auth.headerName] = {
3170
- type: "apiKey",
3171
- in: "header",
3172
- name: route.contractDetails.auth.headerName
3173
- };
3174
- } else {
3175
- securitySchemes["Authorization"] = {
3176
- type: "http",
3177
- scheme: "bearer",
3178
- bearerFormat: "JWT"
3179
- };
3180
- }
3526
+ if (!versionedSecuritySchemes[OPENAPI_DEFAULT_VERSION]) {
3527
+ versionedSecuritySchemes[OPENAPI_DEFAULT_VERSION] = {};
3528
+ }
3529
+ const { query, requestHeaders, body, responses, responseHeaders } = route.contractDetails;
3530
+ const operationObject = generateOperationObject(
3531
+ schemaValidator,
3532
+ route.path,
3533
+ route.method,
3534
+ controllerName,
3535
+ router.sdkPaths,
3536
+ versionedSecuritySchemes[OPENAPI_DEFAULT_VERSION],
3537
+ name,
3538
+ summary,
3539
+ responses,
3540
+ params,
3541
+ responseHeaders,
3542
+ requestHeaders,
3543
+ query,
3544
+ body,
3545
+ auth
3546
+ );
3547
+ if (route.method !== "middleware") {
3548
+ versionedPaths[OPENAPI_DEFAULT_VERSION][openApiPath][route.method] = operationObject;
3181
3549
  }
3182
- }
3183
- if (route.method !== "middleware") {
3184
- paths[openApiPath][route.method] = operationObject;
3185
3550
  }
3186
3551
  });
3187
3552
  });
@@ -3189,13 +3554,116 @@ function generateSwaggerDocument(schemaValidator, protocol, host, port, routers,
3189
3554
  protocol,
3190
3555
  host,
3191
3556
  port,
3192
- tags,
3193
- paths,
3194
- securitySchemes,
3557
+ versionedTags,
3558
+ versionedPaths,
3559
+ versionedSecuritySchemes,
3195
3560
  otherServers
3196
3561
  );
3197
3562
  }
3198
3563
 
3564
+ // src/http/sdk/sdkClient.ts
3565
+ var import_common12 = require("@forklaunch/common");
3566
+
3567
+ // src/http/guards/isSdkRouter.ts
3568
+ function isSdkRouter(value) {
3569
+ return typeof value === "object" && value !== null && "sdk" in value && "_fetchMap" in value && "sdkPaths" in value;
3570
+ }
3571
+
3572
+ // src/http/sdk/sdkClient.ts
3573
+ function mapToSdk(schemaValidator, routerMap, runningPath = void 0) {
3574
+ const routerUniquenessCache = /* @__PURE__ */ new Set();
3575
+ return Object.fromEntries(
3576
+ Object.entries(routerMap).map(([key, value]) => {
3577
+ if (routerUniquenessCache.has((0, import_common12.hashString)((0, import_common12.safeStringify)(value)))) {
3578
+ throw new Error(
3579
+ `SdkClient: Cannot use the same router pointer twice. Please clone the duplicate router with .clone() or only use the router once.`
3580
+ );
3581
+ }
3582
+ routerUniquenessCache.add((0, import_common12.hashString)((0, import_common12.safeStringify)(value)));
3583
+ const currentPath = runningPath ? [runningPath, key].join(".") : key;
3584
+ if (isSdkRouter(value)) {
3585
+ Object.entries(value.sdkPaths).forEach(([routePath, sdkKey]) => {
3586
+ if ("controllerSdkPaths" in value && Array.isArray(value.controllerSdkPaths) && value.controllerSdkPaths.includes(routePath)) {
3587
+ value.sdkPaths[routePath] = [currentPath, sdkKey].join(".");
3588
+ }
3589
+ });
3590
+ return [key, value.sdk];
3591
+ } else {
3592
+ return [
3593
+ key,
3594
+ mapToSdk(
3595
+ schemaValidator,
3596
+ value,
3597
+ runningPath ? [runningPath, key].join(".") : key
3598
+ )
3599
+ ];
3600
+ }
3601
+ })
3602
+ );
3603
+ }
3604
+ function flattenFetchMap(schemaValidator, routerMap) {
3605
+ const _fetchMap = Object.entries(routerMap).reduce(
3606
+ (acc, [, value]) => {
3607
+ if ("_fetchMap" in value) {
3608
+ return {
3609
+ ...acc,
3610
+ ...value._fetchMap
3611
+ };
3612
+ } else {
3613
+ return {
3614
+ ...acc,
3615
+ ...flattenFetchMap(schemaValidator, value)
3616
+ };
3617
+ }
3618
+ },
3619
+ {}
3620
+ );
3621
+ return _fetchMap;
3622
+ }
3623
+ function mapToFetch(schemaValidator, routerMap) {
3624
+ const flattenedFetchMap = flattenFetchMap(
3625
+ schemaValidator,
3626
+ routerMap
3627
+ );
3628
+ return (path, ...reqInit) => {
3629
+ const method = reqInit[0]?.method;
3630
+ const version = reqInit[0] != null && "version" in reqInit[0] ? reqInit[0].version : void 0;
3631
+ return (version ? (0, import_common12.toRecord)((0, import_common12.toRecord)(flattenedFetchMap[path])[method ?? "GET"])[version] : (0, import_common12.toRecord)(flattenedFetchMap[path])[method ?? "GET"])(path, reqInit[0]);
3632
+ };
3633
+ }
3634
+ function sdkClient(schemaValidator, routerMap) {
3635
+ return {
3636
+ _finalizedSdk: true,
3637
+ sdk: mapToSdk(schemaValidator, routerMap),
3638
+ fetch: mapToFetch(schemaValidator, routerMap)
3639
+ };
3640
+ }
3641
+
3642
+ // src/http/sdk/sdkRouter.ts
3643
+ var import_common13 = require("@forklaunch/common");
3644
+ function sdkRouter(schemaValidator, controller, router) {
3645
+ const controllerSdkPaths = [];
3646
+ const mappedSdk = Object.fromEntries(
3647
+ Object.entries(controller).map(([key, value]) => {
3648
+ const sdkPath = [value._method, value._path].join(".");
3649
+ controllerSdkPaths.push(sdkPath);
3650
+ router.sdkPaths[sdkPath] = key;
3651
+ return [
3652
+ key,
3653
+ router.sdk[(0, import_common13.toPrettyCamelCase)(value.contractDetails.name)]
3654
+ ];
3655
+ })
3656
+ );
3657
+ const _fetchMap = router._fetchMap;
3658
+ return {
3659
+ sdk: mappedSdk,
3660
+ fetch: router.fetch,
3661
+ _fetchMap,
3662
+ sdkPaths: router.sdkPaths,
3663
+ controllerSdkPaths
3664
+ };
3665
+ }
3666
+
3199
3667
  // src/http/telemetry/evaluateTelemetryOptions.ts
3200
3668
  function evaluateTelemetryOptions(telemetryOptions) {
3201
3669
  return {
@@ -3227,6 +3695,7 @@ function metricsDefinitions(metrics2) {
3227
3695
  ForklaunchExpressLikeApplication,
3228
3696
  ForklaunchExpressLikeRouter,
3229
3697
  HTTPStatuses,
3698
+ OPENAPI_DEFAULT_VERSION,
3230
3699
  OpenTelemetryCollector,
3231
3700
  delete_,
3232
3701
  discriminateBody,
@@ -3234,7 +3703,7 @@ function metricsDefinitions(metrics2) {
3234
3703
  enrichExpressLikeSend,
3235
3704
  evaluateTelemetryOptions,
3236
3705
  generateMcpServer,
3237
- generateSwaggerDocument,
3706
+ generateOpenApiSpecs,
3238
3707
  get,
3239
3708
  getCodeForStatus,
3240
3709
  head,
@@ -3257,6 +3726,8 @@ function metricsDefinitions(metrics2) {
3257
3726
  post,
3258
3727
  put,
3259
3728
  recordMetric,
3729
+ sdkClient,
3730
+ sdkRouter,
3260
3731
  trace,
3261
3732
  typedAuthHandler,
3262
3733
  typedHandler