@forklaunch/core 0.9.21 → 0.10.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.
@@ -15,40 +15,12 @@ function cors(corsOptions) {
15
15
  };
16
16
  }
17
17
 
18
- // src/http/middleware/request/createContext.middleware.ts
19
- import { trace } from "@opentelemetry/api";
20
- import { v4 } from "uuid";
21
-
22
- // src/http/telemetry/constants.ts
18
+ // src/http/router/expressLikeRouter.ts
23
19
  import {
24
- ATTR_HTTP_REQUEST_METHOD,
25
- ATTR_HTTP_RESPONSE_STATUS_CODE,
26
- ATTR_HTTP_ROUTE,
27
- ATTR_SERVICE_NAME
28
- } from "@opentelemetry/semantic-conventions";
29
- var ATTR_API_NAME = "api.name";
30
- var ATTR_CORRELATION_ID = "correlation.id";
31
-
32
- // src/http/middleware/request/createContext.middleware.ts
33
- function createContext(schemaValidator) {
34
- return function setContext(req, res, next) {
35
- req.schemaValidator = schemaValidator;
36
- let correlationId = v4();
37
- if (req.headers["x-correlation-id"]) {
38
- correlationId = req.headers["x-correlation-id"];
39
- }
40
- res.setHeader("x-correlation-id", correlationId);
41
- req.context = {
42
- correlationId
43
- };
44
- const span = trace.getActiveSpan();
45
- if (span != null) {
46
- req.context.span = span;
47
- req.context.span?.setAttribute(ATTR_CORRELATION_ID, correlationId);
48
- }
49
- next?.();
50
- };
51
- }
20
+ sanitizePathSlashes,
21
+ toPrettyCamelCase,
22
+ toRecord
23
+ } from "@forklaunch/common";
52
24
 
53
25
  // src/http/guards/isForklaunchRouter.ts
54
26
  function isForklaunchRouter(maybeForklaunchRouter) {
@@ -68,7 +40,7 @@ function isExpressLikeSchemaHandler(middleware2) {
68
40
  (argumentName) => argumentName.toLowerCase()
69
41
  )
70
42
  );
71
- return extractedArgumentNames && extractedArgumentNames.size <= 3;
43
+ return extractedArgumentNames && extractedArgumentNames.size <= 4;
72
44
  }
73
45
 
74
46
  // src/http/guards/isForklaunchExpressLikeRouter.ts
@@ -225,6 +197,41 @@ async function parseRequestAuth(req, res, next) {
225
197
  next?.();
226
198
  }
227
199
 
200
+ // src/http/middleware/request/createContext.middleware.ts
201
+ import { trace } from "@opentelemetry/api";
202
+ import { v4 } from "uuid";
203
+
204
+ // src/http/telemetry/constants.ts
205
+ import {
206
+ ATTR_HTTP_REQUEST_METHOD,
207
+ ATTR_HTTP_RESPONSE_STATUS_CODE,
208
+ ATTR_HTTP_ROUTE,
209
+ ATTR_SERVICE_NAME
210
+ } from "@opentelemetry/semantic-conventions";
211
+ var ATTR_API_NAME = "api.name";
212
+ var ATTR_CORRELATION_ID = "correlation.id";
213
+
214
+ // src/http/middleware/request/createContext.middleware.ts
215
+ function createContext(schemaValidator) {
216
+ return function setContext(req, res, next) {
217
+ req.schemaValidator = schemaValidator;
218
+ let correlationId = v4();
219
+ if (req.headers["x-correlation-id"]) {
220
+ correlationId = req.headers["x-correlation-id"];
221
+ }
222
+ res.setHeader("x-correlation-id", correlationId);
223
+ req.context = {
224
+ correlationId
225
+ };
226
+ const span = trace.getActiveSpan();
227
+ if (span != null) {
228
+ req.context.span = span;
229
+ req.context.span?.setAttribute(ATTR_CORRELATION_ID, correlationId);
230
+ }
231
+ next?.();
232
+ };
233
+ }
234
+
228
235
  // src/http/middleware/request/enrichDetails.middleware.ts
229
236
  import { getEnvVar as getEnvVar2 } from "@forklaunch/common";
230
237
 
@@ -480,7 +487,7 @@ function enrichDetails(path, contractDetails, requestSchema, responseSchemas, op
480
487
  req.requestSchema = requestSchema;
481
488
  res.responseSchemas = responseSchemas;
482
489
  req.openTelemetryCollector = openTelemetryCollector;
483
- req.context.span?.setAttribute(ATTR_API_NAME, req.contractDetails?.name);
490
+ req.context?.span?.setAttribute(ATTR_API_NAME, req.contractDetails?.name);
484
491
  const startTime = process.hrtime();
485
492
  res.on("finish", () => {
486
493
  const [seconds, nanoseconds] = process.hrtime(startTime);
@@ -617,6 +624,12 @@ function discriminateBody(schemaValidator, body) {
617
624
  parserType: "text",
618
625
  schema: maybeTypedBody
619
626
  };
627
+ } else if (schemaValidator.openapi(maybeTypedBody).format === "binary") {
628
+ return {
629
+ contentType: "application/octet-stream",
630
+ parserType: "file",
631
+ schema: maybeTypedBody
632
+ };
620
633
  } else {
621
634
  return {
622
635
  contentType: "application/json",
@@ -668,6 +681,12 @@ function discriminateResponseBodies(schemaValidator, responses) {
668
681
  parserType: "text",
669
682
  schema: response
670
683
  };
684
+ } else if (schemaValidator.openapi(response).format === "binary") {
685
+ discriminatedResponses[Number(statusCode)] = {
686
+ contentType: "application/octet-stream",
687
+ parserType: "file",
688
+ schema: response
689
+ };
671
690
  } else {
672
691
  discriminatedResponses[Number(statusCode)] = {
673
692
  contentType: "application/json",
@@ -687,18 +706,38 @@ function discriminateResponseBodies(schemaValidator, responses) {
687
706
  }
688
707
 
689
708
  // src/http/router/expressLikeRouter.ts
690
- var ForklaunchExpressLikeRouter = class {
691
- constructor(basePath, schemaValidator, internal, postEnrichMiddleware, openTelemetryCollector) {
709
+ var ForklaunchExpressLikeRouter = class _ForklaunchExpressLikeRouter {
710
+ constructor(basePath, schemaValidator, internal, postEnrichMiddleware, openTelemetryCollector, sdkName) {
711
+ this.basePath = basePath;
692
712
  this.schemaValidator = schemaValidator;
693
713
  this.internal = internal;
694
714
  this.postEnrichMiddleware = postEnrichMiddleware;
695
715
  this.openTelemetryCollector = openTelemetryCollector;
696
- this.basePath = basePath;
716
+ this.sdkName = sdkName;
717
+ if (process.env.NODE_ENV !== "test" && !process.env.VITEST) {
718
+ process.on("uncaughtException", (err) => {
719
+ this.openTelemetryCollector.error(`Uncaught exception: ${err}`);
720
+ process.exit(1);
721
+ });
722
+ process.on("unhandledRejection", (reason) => {
723
+ this.openTelemetryCollector.error(`Unhandled rejection: ${reason}`);
724
+ process.exit(1);
725
+ });
726
+ process.on("exit", () => {
727
+ this.openTelemetryCollector.info("Shutting down application");
728
+ });
729
+ process.on("SIGINT", () => {
730
+ this.openTelemetryCollector.info("Shutting down application");
731
+ process.exit(0);
732
+ });
733
+ }
734
+ this.internal.use(createContext(this.schemaValidator));
697
735
  }
698
736
  requestHandler;
699
737
  routers = [];
700
738
  routes = [];
701
- basePath;
739
+ fetchMap = {};
740
+ sdk = {};
702
741
  /**
703
742
  * Resolves middlewares based on the contract details.
704
743
  *
@@ -817,6 +856,20 @@ var ForklaunchExpressLikeRouter = class {
817
856
  responseSchemas
818
857
  };
819
858
  }
859
+ /**
860
+ * Fetches a route from the route map and executes it with the given parameters.
861
+ *
862
+ * @template Path - The path type that extends keyof fetchMap and string.
863
+ * @param {Path} path - The route path
864
+ * @param {Parameters<fetchMap[Path]>[1]} [requestInit] - Optional request initialization parameters.
865
+ * @returns {Promise<ReturnType<fetchMap[Path]>>} - The result of executing the route handler.
866
+ */
867
+ fetch = async (path, ...reqInit) => {
868
+ return this.fetchMap[path](
869
+ path,
870
+ reqInit[0]
871
+ );
872
+ };
820
873
  /**
821
874
  * Executes request locally, applying parameters
822
875
  *
@@ -836,24 +889,6 @@ var ForklaunchExpressLikeRouter = class {
836
889
  body: discriminateBody(this.schemaValidator, request?.body)?.schema ?? {},
837
890
  path: route
838
891
  };
839
- function remapFileBody(body) {
840
- if (body instanceof File) {
841
- return (name, contentType) => {
842
- return new File([body], name, { type: contentType });
843
- };
844
- }
845
- Object.entries(body).forEach(([key, value]) => {
846
- if (value instanceof File) {
847
- body[key] = (name, contentType) => {
848
- return new File([value], name, { type: contentType });
849
- };
850
- } else if (typeof value === "object") {
851
- body[key] = remapFileBody(value);
852
- }
853
- });
854
- return body;
855
- }
856
- req.body = remapFileBody(req.body);
857
892
  const res = {
858
893
  status: (code) => {
859
894
  statusCode = code;
@@ -907,24 +942,20 @@ var ForklaunchExpressLikeRouter = class {
907
942
  registerRoute(method, path, registrationMethod, contractDetailsOrMiddlewareOrTypedHandler, ...middlewareOrMiddlewareAndTypedHandler) {
908
943
  if (isTypedHandler(contractDetailsOrMiddlewareOrTypedHandler)) {
909
944
  const { contractDetails, handlers } = contractDetailsOrMiddlewareOrTypedHandler;
910
- return this.registerRoute(
911
- method,
912
- path,
913
- registrationMethod,
914
- contractDetails,
915
- ...handlers
916
- );
945
+ this.registerRoute(method, path, registrationMethod, contractDetails, ...handlers);
946
+ return this;
917
947
  } else {
918
948
  const maybeTypedHandler = middlewareOrMiddlewareAndTypedHandler[middlewareOrMiddlewareAndTypedHandler.length - 1];
919
949
  if (isTypedHandler(maybeTypedHandler)) {
920
950
  const { contractDetails, handlers } = maybeTypedHandler;
921
- return this.registerRoute(
951
+ this.registerRoute(
922
952
  method,
923
953
  path,
924
954
  registrationMethod,
925
955
  contractDetails,
926
956
  ...middlewareOrMiddlewareAndTypedHandler.concat(handlers)
927
957
  );
958
+ return this;
928
959
  } else {
929
960
  if (isExpressLikeSchemaHandler(contractDetailsOrMiddlewareOrTypedHandler) || isTypedHandler(contractDetailsOrMiddlewareOrTypedHandler)) {
930
961
  throw new Error("Contract details are not defined");
@@ -956,10 +987,13 @@ var ForklaunchExpressLikeRouter = class {
956
987
  ).concat(handlers),
957
988
  this.#parseAndRunControllerHandler(controllerHandler)
958
989
  );
959
- return this.#localParamRequest(
990
+ const localParamRequest = this.#localParamRequest(
960
991
  handlers,
961
992
  controllerHandler
962
993
  );
994
+ toRecord(this.fetchMap)[sanitizePathSlashes(`${this.basePath}${path}`)] = localParamRequest;
995
+ toRecord(this.sdk)[toPrettyCamelCase(contractDetails.name ?? this.basePath)] = (req) => localParamRequest(`${this.basePath}${path}`, req);
996
+ return this;
963
997
  }
964
998
  }
965
999
  }
@@ -983,10 +1017,13 @@ var ForklaunchExpressLikeRouter = class {
983
1017
  return this.#extractHandlers(handlers);
984
1018
  }
985
1019
  #extractNestableMiddlewareFromEnrichedTypedHandlerArray(handlers) {
986
- return this.#extractHandlers(
987
- handlers,
988
- (handler) => isForklaunchExpressLikeRouter(handler) ? handler.internal : handler
989
- );
1020
+ return this.#extractHandlers(handlers, (handler) => {
1021
+ if (isForklaunchExpressLikeRouter(handler)) {
1022
+ this.addRouterToSdk(handler);
1023
+ return handler.internal;
1024
+ }
1025
+ return handler;
1026
+ });
990
1027
  }
991
1028
  #processTypedHandlerOrMiddleware(handler, middleware2) {
992
1029
  if (isTypedHandler(handler)) {
@@ -1010,6 +1047,7 @@ var ForklaunchExpressLikeRouter = class {
1010
1047
  middleware2
1011
1048
  );
1012
1049
  if (isForklaunchExpressLikeRouter(contractDetailsOrMiddlewareOrTypedHandler)) {
1050
+ this.addRouterToSdk(contractDetailsOrMiddlewareOrTypedHandler);
1013
1051
  middleware2.push(contractDetailsOrMiddlewareOrTypedHandler.internal);
1014
1052
  }
1015
1053
  middleware2.push(
@@ -1039,6 +1077,16 @@ var ForklaunchExpressLikeRouter = class {
1039
1077
  }
1040
1078
  return this;
1041
1079
  }
1080
+ addRouterToSdk(router) {
1081
+ Object.entries(router.fetchMap).map(
1082
+ ([key, value]) => toRecord(this.fetchMap)[sanitizePathSlashes(`${this.basePath}${key}`)] = value
1083
+ );
1084
+ const existingSdk = this.sdk[router.sdkName ?? toPrettyCamelCase(router.basePath)];
1085
+ toRecord(this.sdk)[router.sdkName ?? toPrettyCamelCase(router.basePath)] = {
1086
+ ...typeof existingSdk === "object" ? existingSdk : {},
1087
+ ...router.sdk
1088
+ };
1089
+ }
1042
1090
  registerNestableMiddlewareHandler(registrationMethod, pathOrContractDetailsOrMiddlewareOrTypedHandler, contractDetailsOrMiddlewareOrTypedHandler, ...middlewareOrMiddlewareWithTypedHandler) {
1043
1091
  const middleware2 = [];
1044
1092
  let path;
@@ -1054,7 +1102,9 @@ var ForklaunchExpressLikeRouter = class {
1054
1102
  if (isConstrainedForklaunchRouter(
1055
1103
  pathOrContractDetailsOrMiddlewareOrTypedHandler
1056
1104
  )) {
1057
- path = pathOrContractDetailsOrMiddlewareOrTypedHandler.basePath;
1105
+ const router = pathOrContractDetailsOrMiddlewareOrTypedHandler;
1106
+ this.addRouterToSdk(router);
1107
+ path = router.basePath;
1058
1108
  }
1059
1109
  middleware2.push(
1060
1110
  ...this.#extractNestableMiddlewareAsRouterHandlers(
@@ -1065,6 +1115,11 @@ var ForklaunchExpressLikeRouter = class {
1065
1115
  )
1066
1116
  );
1067
1117
  }
1118
+ if (!path) {
1119
+ path = middleware2.filter(
1120
+ (m) => isForklaunchExpressLikeRouter(m)
1121
+ )[0]?.basePath;
1122
+ }
1068
1123
  if (path) {
1069
1124
  registrationMethod.bind(this.internal)(path, ...middleware2);
1070
1125
  } else {
@@ -1119,15 +1174,13 @@ var ForklaunchExpressLikeRouter = class {
1119
1174
  * @returns {ExpressRouter} - The Express router.
1120
1175
  */
1121
1176
  get = (path, contractDetailsOrMiddlewareOrTypedHandler, ...middlewareOrMiddlewareWithTypedHandler) => {
1122
- return {
1123
- get: this.registerRoute(
1124
- "get",
1125
- path,
1126
- this.internal.get,
1127
- contractDetailsOrMiddlewareOrTypedHandler,
1128
- ...middlewareOrMiddlewareWithTypedHandler
1129
- )
1130
- };
1177
+ return this.registerRoute(
1178
+ "get",
1179
+ path,
1180
+ this.internal.get,
1181
+ contractDetailsOrMiddlewareOrTypedHandler,
1182
+ ...middlewareOrMiddlewareWithTypedHandler
1183
+ );
1131
1184
  };
1132
1185
  /**
1133
1186
  * Registers a POST route with the specified contract details and handler handlers.
@@ -1143,15 +1196,13 @@ var ForklaunchExpressLikeRouter = class {
1143
1196
  * @returns {ExpressRouter} - The Expxwress router.
1144
1197
  */
1145
1198
  post = (path, contractDetailsOrMiddlewareOrTypedHandler, ...middlewareOrMiddlewareWithTypedHandler) => {
1146
- return {
1147
- post: this.registerRoute(
1148
- "post",
1149
- path,
1150
- this.internal.post,
1151
- contractDetailsOrMiddlewareOrTypedHandler,
1152
- ...middlewareOrMiddlewareWithTypedHandler
1153
- )
1154
- };
1199
+ return this.registerRoute(
1200
+ "post",
1201
+ path,
1202
+ this.internal.post,
1203
+ contractDetailsOrMiddlewareOrTypedHandler,
1204
+ ...middlewareOrMiddlewareWithTypedHandler
1205
+ );
1155
1206
  };
1156
1207
  /**
1157
1208
  * Registers a PUT route with the specified contract details and handler handlers.
@@ -1167,15 +1218,13 @@ var ForklaunchExpressLikeRouter = class {
1167
1218
  * @returns {ExpressRouter} - The Express router.
1168
1219
  */
1169
1220
  put = (path, contractDetailsOrMiddlewareOrTypedHandler, ...middlewareOrMiddlewareWithTypedHandler) => {
1170
- return {
1171
- put: this.registerRoute(
1172
- "put",
1173
- path,
1174
- this.internal.put,
1175
- contractDetailsOrMiddlewareOrTypedHandler,
1176
- ...middlewareOrMiddlewareWithTypedHandler
1177
- )
1178
- };
1221
+ return this.registerRoute(
1222
+ "put",
1223
+ path,
1224
+ this.internal.put,
1225
+ contractDetailsOrMiddlewareOrTypedHandler,
1226
+ ...middlewareOrMiddlewareWithTypedHandler
1227
+ );
1179
1228
  };
1180
1229
  /**
1181
1230
  * Registers a PATCH route with the specified contract details and handler handlers.
@@ -1191,15 +1240,13 @@ var ForklaunchExpressLikeRouter = class {
1191
1240
  * @returns {ExpressRouter} - The Express router.
1192
1241
  */
1193
1242
  patch = (path, contractDetailsOrMiddlewareOrTypedHandler, ...middlewareOrMiddlewareWithTypedHandler) => {
1194
- return {
1195
- patch: this.registerRoute(
1196
- "patch",
1197
- path,
1198
- this.internal.patch,
1199
- contractDetailsOrMiddlewareOrTypedHandler,
1200
- ...middlewareOrMiddlewareWithTypedHandler
1201
- )
1202
- };
1243
+ return this.registerRoute(
1244
+ "patch",
1245
+ path,
1246
+ this.internal.patch,
1247
+ contractDetailsOrMiddlewareOrTypedHandler,
1248
+ ...middlewareOrMiddlewareWithTypedHandler
1249
+ );
1203
1250
  };
1204
1251
  /**
1205
1252
  * Registers a DELETE route with the specified contract details and handler handlers.
@@ -1215,15 +1262,13 @@ var ForklaunchExpressLikeRouter = class {
1215
1262
  * @returns {ExpressRouter} - The Express router.
1216
1263
  */
1217
1264
  delete = (path, contractDetailsOrMiddlewareOrTypedHandler, ...middlewareOrMiddlewareWithTypedHandler) => {
1218
- return {
1219
- delete: this.registerRoute(
1220
- "delete",
1221
- path,
1222
- this.internal.delete,
1223
- contractDetailsOrMiddlewareOrTypedHandler,
1224
- ...middlewareOrMiddlewareWithTypedHandler
1225
- )
1226
- };
1265
+ return this.registerRoute(
1266
+ "delete",
1267
+ path,
1268
+ this.internal.delete,
1269
+ contractDetailsOrMiddlewareOrTypedHandler,
1270
+ ...middlewareOrMiddlewareWithTypedHandler
1271
+ );
1227
1272
  };
1228
1273
  /**
1229
1274
  * Registers a OPTIONS route with the specified contract details and handler handlers.
@@ -1239,15 +1284,13 @@ var ForklaunchExpressLikeRouter = class {
1239
1284
  * @returns {ExpressRouter} - The Express router.
1240
1285
  */
1241
1286
  options = (path, contractDetailsOrMiddlewareOrTypedHandler, ...middlewareOrMiddlewareWithTypedHandler) => {
1242
- return {
1243
- options: this.registerRoute(
1244
- "options",
1245
- path,
1246
- this.internal.options,
1247
- contractDetailsOrMiddlewareOrTypedHandler,
1248
- ...middlewareOrMiddlewareWithTypedHandler
1249
- )
1250
- };
1287
+ return this.registerRoute(
1288
+ "options",
1289
+ path,
1290
+ this.internal.options,
1291
+ contractDetailsOrMiddlewareOrTypedHandler,
1292
+ ...middlewareOrMiddlewareWithTypedHandler
1293
+ );
1251
1294
  };
1252
1295
  /**
1253
1296
  * Registers a HEAD route with the specified contract details and handler handlers.
@@ -1263,15 +1306,13 @@ var ForklaunchExpressLikeRouter = class {
1263
1306
  * @returns {ExpressRouter} - The Express router.
1264
1307
  */
1265
1308
  head = (path, contractDetailsOrMiddlewareOrTypedHandler, ...middlewareOrMiddlewareWithTypedHandler) => {
1266
- return {
1267
- head: this.registerRoute(
1268
- "head",
1269
- path,
1270
- this.internal.head,
1271
- contractDetailsOrMiddlewareOrTypedHandler,
1272
- ...middlewareOrMiddlewareWithTypedHandler
1273
- )
1274
- };
1309
+ return this.registerRoute(
1310
+ "head",
1311
+ path,
1312
+ this.internal.head,
1313
+ contractDetailsOrMiddlewareOrTypedHandler,
1314
+ ...middlewareOrMiddlewareWithTypedHandler
1315
+ );
1275
1316
  };
1276
1317
  /**
1277
1318
  * Registers a TRACE route with the specified contract details and handler handlers.
@@ -1287,16 +1328,32 @@ var ForklaunchExpressLikeRouter = class {
1287
1328
  * @returns {ExpressRouter} - The Express router.
1288
1329
  */
1289
1330
  trace = (path, contractDetailsOrMiddlewareOrTypedHandler, ...middlewareOrMiddlewareWithTypedHandler) => {
1290
- return {
1291
- trace: this.registerRoute(
1292
- "trace",
1293
- path,
1294
- this.internal.trace,
1295
- contractDetailsOrMiddlewareOrTypedHandler,
1296
- ...middlewareOrMiddlewareWithTypedHandler
1297
- )
1298
- };
1331
+ return this.registerRoute(
1332
+ "trace",
1333
+ path,
1334
+ this.internal.trace,
1335
+ contractDetailsOrMiddlewareOrTypedHandler,
1336
+ ...middlewareOrMiddlewareWithTypedHandler
1337
+ );
1299
1338
  };
1339
+ cloneInternals(clone) {
1340
+ clone.routers = [...this.routers];
1341
+ clone.routes = [...this.routes];
1342
+ clone.fetchMap = { ...this.fetchMap };
1343
+ clone.sdk = { ...this.sdk };
1344
+ }
1345
+ clone() {
1346
+ const clone = new _ForklaunchExpressLikeRouter(
1347
+ this.basePath,
1348
+ this.schemaValidator,
1349
+ this.internal,
1350
+ this.postEnrichMiddleware,
1351
+ this.openTelemetryCollector,
1352
+ this.sdkName
1353
+ );
1354
+ this.cloneInternals(clone);
1355
+ return clone;
1356
+ }
1300
1357
  };
1301
1358
 
1302
1359
  // src/http/application/expressLikeApplication.ts
@@ -1319,25 +1376,6 @@ var ForklaunchExpressLikeApplication = class extends ForklaunchExpressLikeRouter
1319
1376
  this.postEnrichMiddleware = postEnrichMiddleware;
1320
1377
  this.openTelemetryCollector = openTelemetryCollector;
1321
1378
  this.appOptions = appOptions;
1322
- process.on("uncaughtException", (err) => {
1323
- this.openTelemetryCollector.log("error", `Uncaught exception: ${err}`);
1324
- process.exit(1);
1325
- });
1326
- process.on("unhandledRejection", (reason) => {
1327
- this.openTelemetryCollector.log(
1328
- "error",
1329
- `Unhandled rejection: ${reason}`
1330
- );
1331
- process.exit(1);
1332
- });
1333
- process.on("exit", () => {
1334
- this.openTelemetryCollector.log("info", "Shutting down application");
1335
- });
1336
- process.on("SIGINT", () => {
1337
- this.openTelemetryCollector.log("info", "Shutting down application");
1338
- process.exit(0);
1339
- });
1340
- this.internal.use(createContext(this.schemaValidator));
1341
1379
  this.internal.use(cors(this.appOptions?.cors ?? {}));
1342
1380
  }
1343
1381
  };
@@ -2402,6 +2440,218 @@ var getCodeForStatus = (status) => {
2402
2440
  };
2403
2441
  var httpStatusCodes_default = HTTPStatuses;
2404
2442
 
2443
+ // src/http/mcpGenerator/mcpGenerator.ts
2444
+ import { isNever as isNever2, isRecord, safeStringify as safeStringify2 } from "@forklaunch/common";
2445
+ import { ZodSchemaValidator } from "@forklaunch/validator/zod";
2446
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2447
+
2448
+ // src/http/router/unpackRouters.ts
2449
+ import { toPrettyCamelCase as toPrettyCamelCase2 } from "@forklaunch/common";
2450
+ function unpackRouters(routers, recursiveBasePath = [], recursiveSdkPath = []) {
2451
+ return routers.reduce((acc, router) => {
2452
+ acc.push({
2453
+ fullPath: [...recursiveBasePath, router.basePath].join(""),
2454
+ sdkPath: [
2455
+ ...recursiveSdkPath,
2456
+ toPrettyCamelCase2(router.sdkName ?? router.basePath)
2457
+ ].join("."),
2458
+ router
2459
+ });
2460
+ acc.push(
2461
+ ...unpackRouters(
2462
+ router.routers,
2463
+ [...recursiveBasePath, router.basePath],
2464
+ [
2465
+ ...recursiveSdkPath,
2466
+ toPrettyCamelCase2(router.sdkName ?? router.basePath)
2467
+ ]
2468
+ )
2469
+ );
2470
+ return acc;
2471
+ }, []);
2472
+ }
2473
+
2474
+ // src/http/mcpGenerator/mcpGenerator.ts
2475
+ function generateMcpServer(schemaValidator, protocol, host, port, version, routers, contentTypeMap) {
2476
+ if (!(schemaValidator instanceof ZodSchemaValidator)) {
2477
+ throw new Error(
2478
+ "Schema validator must be an instance of ZodSchemaValidator"
2479
+ );
2480
+ }
2481
+ const mcpServer = new McpServer({
2482
+ name: "example-server",
2483
+ version
2484
+ });
2485
+ unpackRouters(routers).forEach(({ fullPath, router }) => {
2486
+ router.routes.forEach((route) => {
2487
+ let discriminatedBody;
2488
+ if ("body" in route.contractDetails) {
2489
+ discriminatedBody = discriminateBody(
2490
+ schemaValidator,
2491
+ route.contractDetails.body
2492
+ );
2493
+ }
2494
+ const inputSchema = {
2495
+ ...discriminatedBody && "body" in route.contractDetails ? {
2496
+ ..."contentType" in route.contractDetails.body ? { contentType: route.contractDetails.body.contentType } : {},
2497
+ body: schemaValidator.schemify(discriminatedBody.schema)
2498
+ } : {},
2499
+ ...route.contractDetails.params ? { params: schemaValidator.schemify(route.contractDetails.params) } : {},
2500
+ ...route.contractDetails.query ? { query: schemaValidator.schemify(route.contractDetails.query) } : {},
2501
+ ...route.contractDetails.requestHeaders ? {
2502
+ headers: schemaValidator.schemify(
2503
+ route.contractDetails.requestHeaders
2504
+ )
2505
+ } : {}
2506
+ // TODO: support auth
2507
+ // ...(route.contractDetails.auth
2508
+ // ? { auth: route.contractDetails.auth }
2509
+ // : {})
2510
+ };
2511
+ mcpServer.tool(
2512
+ route.contractDetails.name,
2513
+ route.contractDetails.summary,
2514
+ inputSchema,
2515
+ async (args) => {
2516
+ const { contentType, body, params, query, headers } = args;
2517
+ let url = `${protocol}://${host}:${port}${fullPath}${route.path}`;
2518
+ if (params) {
2519
+ for (const key in params) {
2520
+ url = url.replace(
2521
+ `:${key}`,
2522
+ encodeURIComponent(params[key])
2523
+ );
2524
+ }
2525
+ }
2526
+ let parsedBody;
2527
+ if (discriminatedBody) {
2528
+ switch (discriminatedBody.parserType) {
2529
+ case "json": {
2530
+ parsedBody = safeStringify2(body);
2531
+ break;
2532
+ }
2533
+ case "text": {
2534
+ parsedBody = body;
2535
+ break;
2536
+ }
2537
+ case "file": {
2538
+ parsedBody = body;
2539
+ break;
2540
+ }
2541
+ case "multipart": {
2542
+ const formData = new FormData();
2543
+ if (isRecord(body)) {
2544
+ for (const key in body) {
2545
+ if (typeof body[key] === "string" || body[key] instanceof Blob) {
2546
+ formData.append(key, body[key]);
2547
+ } else {
2548
+ throw new Error("Body is not a valid multipart object");
2549
+ }
2550
+ }
2551
+ } else {
2552
+ throw new Error("Body is not a valid multipart object");
2553
+ }
2554
+ parsedBody = formData;
2555
+ break;
2556
+ }
2557
+ case "urlEncoded": {
2558
+ if (isRecord(body)) {
2559
+ parsedBody = new URLSearchParams(
2560
+ Object.entries(body).map(([key, value]) => [
2561
+ key,
2562
+ safeStringify2(value)
2563
+ ])
2564
+ );
2565
+ } else {
2566
+ throw new Error("Body is not a valid url encoded object");
2567
+ }
2568
+ break;
2569
+ }
2570
+ default: {
2571
+ isNever2(discriminatedBody.parserType);
2572
+ parsedBody = safeStringify2(body);
2573
+ break;
2574
+ }
2575
+ }
2576
+ }
2577
+ if (query) {
2578
+ const queryString = new URLSearchParams(
2579
+ Object.entries(query).map(([key, value]) => [
2580
+ key,
2581
+ safeStringify2(value)
2582
+ ])
2583
+ ).toString();
2584
+ url += queryString ? `?${queryString}` : "";
2585
+ }
2586
+ const response = await fetch(encodeURI(url), {
2587
+ method: route.method.toUpperCase(),
2588
+ headers: {
2589
+ ...headers,
2590
+ ...discriminatedBody?.contentType != "multipart/form-data" ? {
2591
+ "Content-Type": contentType ?? discriminatedBody?.contentType
2592
+ } : {}
2593
+ },
2594
+ body: parsedBody
2595
+ });
2596
+ if (response.status >= 300) {
2597
+ throw new Error(
2598
+ `Error received while proxying request to ${url}: ${await response.text()}`
2599
+ );
2600
+ }
2601
+ const contractContentType = discriminateResponseBodies(
2602
+ schemaValidator,
2603
+ route.contractDetails.responses
2604
+ )[response.status].contentType;
2605
+ switch (contentTypeMap && contentTypeMap[contractContentType] ? contentTypeMap[contractContentType] : contractContentType) {
2606
+ case "application/json":
2607
+ return {
2608
+ content: [
2609
+ {
2610
+ type: "text",
2611
+ text: safeStringify2(await response.json())
2612
+ }
2613
+ ]
2614
+ };
2615
+ case "text/plain":
2616
+ return {
2617
+ content: [
2618
+ { type: "text", text: await response.text() }
2619
+ ]
2620
+ };
2621
+ case "application/octet-stream":
2622
+ return {
2623
+ content: [
2624
+ {
2625
+ type: "resource",
2626
+ resource: {
2627
+ uri: response.url,
2628
+ blob: Buffer.from(
2629
+ await (await response.blob()).arrayBuffer()
2630
+ ).toString("base64")
2631
+ }
2632
+ }
2633
+ ]
2634
+ };
2635
+ case "text/event-stream":
2636
+ return {
2637
+ content: [
2638
+ { type: "text", text: await response.text() }
2639
+ ]
2640
+ };
2641
+ default:
2642
+ return {
2643
+ content: [
2644
+ { type: "text", text: await response.text() }
2645
+ ]
2646
+ };
2647
+ }
2648
+ }
2649
+ );
2650
+ });
2651
+ });
2652
+ return mcpServer;
2653
+ }
2654
+
2405
2655
  // src/http/middleware/response/parse.middleware.ts
2406
2656
  import {
2407
2657
  prettyPrintParseErrors as prettyPrintParseErrors2
@@ -2468,11 +2718,11 @@ ${parseErrors.join("\n\n")}`
2468
2718
  // src/http/middleware/response/enrichExpressLikeSend.middleware.ts
2469
2719
  import {
2470
2720
  isAsyncGenerator,
2471
- isNever as isNever2,
2721
+ isNever as isNever3,
2472
2722
  isNodeJsWriteableStream,
2473
- isRecord,
2723
+ isRecord as isRecord2,
2474
2724
  readableStreamToAsyncIterable,
2475
- safeStringify as safeStringify2
2725
+ safeStringify as safeStringify3
2476
2726
  } from "@forklaunch/common";
2477
2727
  import { Readable, Transform } from "stream";
2478
2728
 
@@ -2509,7 +2759,7 @@ function enrichExpressLikeSend(instance, req, res, originalOperation, originalSe
2509
2759
  if (res.statusCode === 404) {
2510
2760
  res.type("text/plain");
2511
2761
  res.status(404);
2512
- logger("error").error("Not Found");
2762
+ req.openTelemetryCollector.error("Not Found");
2513
2763
  originalSend.call(instance, "Not Found");
2514
2764
  errorSent = true;
2515
2765
  }
@@ -2552,7 +2802,7 @@ function enrichExpressLikeSend(instance, req, res, originalOperation, originalSe
2552
2802
  ------------------
2553
2803
  ${res.locals.errorMessage}`;
2554
2804
  }
2555
- logger("error").error(errorString);
2805
+ req.openTelemetryCollector.error(errorString);
2556
2806
  res.type("text/plain");
2557
2807
  res.status(500);
2558
2808
  originalSend.call(instance, errorString);
@@ -2565,7 +2815,7 @@ ${res.locals.errorMessage}`;
2565
2815
  if (!errorSent) {
2566
2816
  let data2 = "";
2567
2817
  for (const [key, value] of Object.entries(chunk)) {
2568
- data2 += `${key}: ${typeof value === "string" ? value : safeStringify2(value)}
2818
+ data2 += `${key}: ${typeof value === "string" ? value : safeStringify3(value)}
2569
2819
  `;
2570
2820
  }
2571
2821
  data2 += "\n";
@@ -2584,7 +2834,7 @@ ${res.locals.errorMessage}`;
2584
2834
  } else {
2585
2835
  const parserType = responseBodies?.[Number(res.statusCode)]?.parserType;
2586
2836
  res.bodyData = data;
2587
- if (isRecord(data)) {
2837
+ if (isRecord2(data)) {
2588
2838
  switch (parserType) {
2589
2839
  case "json":
2590
2840
  res.bodyData = "json" in data ? data.json : data;
@@ -2605,7 +2855,7 @@ ${res.locals.errorMessage}`;
2605
2855
  res.bodyData = data;
2606
2856
  break;
2607
2857
  default:
2608
- isNever2(parserType);
2858
+ isNever3(parserType);
2609
2859
  res.bodyData = data;
2610
2860
  break;
2611
2861
  }
@@ -2618,7 +2868,7 @@ ${res.locals.errorMessage}`;
2618
2868
  ------------------
2619
2869
  ${res.locals.errorMessage}`;
2620
2870
  }
2621
- logger("error").error(errorString);
2871
+ req.openTelemetryCollector.error(errorString);
2622
2872
  res.type("text/plain");
2623
2873
  res.status(500);
2624
2874
  originalSend.call(instance, errorString);
@@ -2641,16 +2891,17 @@ ${res.locals.errorMessage}`;
2641
2891
  }
2642
2892
 
2643
2893
  // src/http/openApiV3Generator/openApiV3Generator.ts
2894
+ import { openApiCompliantPath, toPrettyCamelCase as toPrettyCamelCase3 } from "@forklaunch/common";
2644
2895
  function toUpperCase(str) {
2645
2896
  return str.charAt(0).toUpperCase() + str.slice(1);
2646
2897
  }
2647
2898
  function transformBasePath(basePath) {
2648
2899
  if (basePath.startsWith("/")) {
2649
- return toUpperCase(basePath.slice(1));
2900
+ return basePath.slice(1);
2650
2901
  }
2651
2902
  return `/${basePath}`;
2652
2903
  }
2653
- function generateOpenApiDocument(port, tags, paths) {
2904
+ function generateOpenApiDocument(protocol, host, port, tags, paths, otherServers) {
2654
2905
  return {
2655
2906
  openapi: "3.1.0",
2656
2907
  info: {
@@ -2669,8 +2920,10 @@ function generateOpenApiDocument(port, tags, paths) {
2669
2920
  tags,
2670
2921
  servers: [
2671
2922
  {
2672
- url: `http://localhost:${port}`
2673
- }
2923
+ url: `${protocol}://${host}:${port}`,
2924
+ description: "Main Server"
2925
+ },
2926
+ ...otherServers || []
2674
2927
  ],
2675
2928
  paths
2676
2929
  };
@@ -2691,19 +2944,21 @@ function contentResolver(schemaValidator, body, contentType) {
2691
2944
  }
2692
2945
  };
2693
2946
  }
2694
- function generateSwaggerDocument(schemaValidator, port, routers) {
2947
+ function generateSwaggerDocument(schemaValidator, protocol, host, port, routers, otherServers) {
2695
2948
  const tags = [];
2696
2949
  const paths = {};
2697
- routers.flat(Infinity).forEach((router) => {
2698
- const controllerName = transformBasePath(router.basePath);
2950
+ unpackRouters(routers).forEach(({ fullPath, router, sdkPath }) => {
2951
+ const controllerName = transformBasePath(fullPath);
2699
2952
  tags.push({
2700
2953
  name: controllerName,
2701
- description: `${controllerName} Operations`
2954
+ description: `${toUpperCase(controllerName)} Operations`
2702
2955
  });
2703
2956
  router.routes.forEach((route) => {
2704
- const fullPath = `${router.basePath}${route.path === "/" ? "" : route.path}`.replace(/:(\w+)/g, "{$1}");
2705
- if (!paths[fullPath]) {
2706
- paths[fullPath] = {};
2957
+ const openApiPath = openApiCompliantPath(
2958
+ `${fullPath}${route.path === "/" ? "" : route.path}`
2959
+ );
2960
+ if (!paths[openApiPath]) {
2961
+ paths[openApiPath] = {};
2707
2962
  }
2708
2963
  const { name, summary, query, requestHeaders } = route.contractDetails;
2709
2964
  const responses = {};
@@ -2730,15 +2985,16 @@ function generateSwaggerDocument(schemaValidator, port, routers) {
2730
2985
  };
2731
2986
  }
2732
2987
  }
2733
- const pathItemObject = {
2988
+ const operationObject = {
2734
2989
  tags: [controllerName],
2735
2990
  summary: `${name}: ${summary}`,
2736
2991
  parameters: [],
2737
- responses
2992
+ responses,
2993
+ operationId: `${sdkPath}.${toPrettyCamelCase3(name)}`
2738
2994
  };
2739
2995
  if (route.contractDetails.params) {
2740
2996
  for (const key in route.contractDetails.params) {
2741
- pathItemObject.parameters?.push({
2997
+ operationObject.parameters?.push({
2742
2998
  name: key,
2743
2999
  in: "path",
2744
3000
  schema: schemaValidator.openapi(
@@ -2749,7 +3005,7 @@ function generateSwaggerDocument(schemaValidator, port, routers) {
2749
3005
  }
2750
3006
  const discriminatedBodyResult = "body" in route.contractDetails ? discriminateBody(schemaValidator, route.contractDetails.body) : null;
2751
3007
  if (discriminatedBodyResult) {
2752
- pathItemObject.requestBody = {
3008
+ operationObject.requestBody = {
2753
3009
  required: true,
2754
3010
  content: contentResolver(
2755
3011
  schemaValidator,
@@ -2760,7 +3016,7 @@ function generateSwaggerDocument(schemaValidator, port, routers) {
2760
3016
  }
2761
3017
  if (requestHeaders) {
2762
3018
  for (const key in requestHeaders) {
2763
- pathItemObject.parameters?.push({
3019
+ operationObject.parameters?.push({
2764
3020
  name: key,
2765
3021
  in: "header",
2766
3022
  schema: schemaValidator.openapi(
@@ -2771,7 +3027,7 @@ function generateSwaggerDocument(schemaValidator, port, routers) {
2771
3027
  }
2772
3028
  if (query) {
2773
3029
  for (const key in query) {
2774
- pathItemObject.parameters?.push({
3030
+ operationObject.parameters?.push({
2775
3031
  name: key,
2776
3032
  in: "query",
2777
3033
  schema: schemaValidator.openapi(query[key])
@@ -2788,7 +3044,7 @@ function generateSwaggerDocument(schemaValidator, port, routers) {
2788
3044
  content: contentResolver(schemaValidator, schemaValidator.string)
2789
3045
  };
2790
3046
  if (route.contractDetails.auth.method === "jwt") {
2791
- pathItemObject.security = [
3047
+ operationObject.security = [
2792
3048
  {
2793
3049
  bearer: Array.from(
2794
3050
  route.contractDetails.auth.allowedPermissions?.values() || []
@@ -2798,11 +3054,18 @@ function generateSwaggerDocument(schemaValidator, port, routers) {
2798
3054
  }
2799
3055
  }
2800
3056
  if (route.method !== "middleware") {
2801
- paths[fullPath][route.method] = pathItemObject;
3057
+ paths[openApiPath][route.method] = operationObject;
2802
3058
  }
2803
3059
  });
2804
3060
  });
2805
- return generateOpenApiDocument(port, tags, paths);
3061
+ return generateOpenApiDocument(
3062
+ protocol,
3063
+ host,
3064
+ port,
3065
+ tags,
3066
+ paths,
3067
+ otherServers
3068
+ );
2806
3069
  }
2807
3070
 
2808
3071
  // src/http/telemetry/evaluateTelemetryOptions.ts
@@ -2841,6 +3104,7 @@ export {
2841
3104
  discriminateResponseBodies,
2842
3105
  enrichExpressLikeSend,
2843
3106
  evaluateTelemetryOptions,
3107
+ generateMcpServer,
2844
3108
  generateSwaggerDocument,
2845
3109
  get,
2846
3110
  getCodeForStatus,