@forklaunch/core 0.9.22 → 0.10.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.d.mts +296 -102
- package/lib/http/index.d.ts +296 -102
- package/lib/http/index.js +421 -160
- package/lib/http/index.js.map +1 -1
- package/lib/http/index.mjs +420 -156
- package/lib/http/index.mjs.map +1 -1
- package/package.json +14 -13
package/lib/http/index.mjs
CHANGED
@@ -15,6 +15,13 @@ function cors(corsOptions) {
|
|
15
15
|
};
|
16
16
|
}
|
17
17
|
|
18
|
+
// src/http/router/expressLikeRouter.ts
|
19
|
+
import {
|
20
|
+
sanitizePathSlashes,
|
21
|
+
toPrettyCamelCase,
|
22
|
+
toRecord
|
23
|
+
} from "@forklaunch/common";
|
24
|
+
|
18
25
|
// src/http/guards/isForklaunchRouter.ts
|
19
26
|
function isForklaunchRouter(maybeForklaunchRouter) {
|
20
27
|
return maybeForklaunchRouter != null && typeof maybeForklaunchRouter === "object" && "basePath" in maybeForklaunchRouter && "routes" in maybeForklaunchRouter && Array.isArray(maybeForklaunchRouter.routes);
|
@@ -33,7 +40,7 @@ function isExpressLikeSchemaHandler(middleware2) {
|
|
33
40
|
(argumentName) => argumentName.toLowerCase()
|
34
41
|
)
|
35
42
|
);
|
36
|
-
return extractedArgumentNames && extractedArgumentNames.size <=
|
43
|
+
return extractedArgumentNames && extractedArgumentNames.size <= 4;
|
37
44
|
}
|
38
45
|
|
39
46
|
// src/http/guards/isForklaunchExpressLikeRouter.ts
|
@@ -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,37 +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.
|
697
|
-
process.
|
698
|
-
|
699
|
-
|
700
|
-
|
701
|
-
|
702
|
-
|
703
|
-
|
704
|
-
|
705
|
-
);
|
706
|
-
process.exit(
|
707
|
-
|
708
|
-
|
709
|
-
|
710
|
-
|
711
|
-
|
712
|
-
|
713
|
-
|
714
|
-
});
|
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
|
+
}
|
715
734
|
this.internal.use(createContext(this.schemaValidator));
|
716
735
|
}
|
717
736
|
requestHandler;
|
718
737
|
routers = [];
|
719
738
|
routes = [];
|
720
|
-
|
739
|
+
fetchMap = {};
|
740
|
+
sdk = {};
|
721
741
|
/**
|
722
742
|
* Resolves middlewares based on the contract details.
|
723
743
|
*
|
@@ -836,6 +856,20 @@ var ForklaunchExpressLikeRouter = class {
|
|
836
856
|
responseSchemas
|
837
857
|
};
|
838
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
|
+
};
|
839
873
|
/**
|
840
874
|
* Executes request locally, applying parameters
|
841
875
|
*
|
@@ -855,24 +889,6 @@ var ForklaunchExpressLikeRouter = class {
|
|
855
889
|
body: discriminateBody(this.schemaValidator, request?.body)?.schema ?? {},
|
856
890
|
path: route
|
857
891
|
};
|
858
|
-
function remapFileBody(body) {
|
859
|
-
if (body instanceof File) {
|
860
|
-
return (name, contentType) => {
|
861
|
-
return new File([body], name, { type: contentType });
|
862
|
-
};
|
863
|
-
}
|
864
|
-
Object.entries(body).forEach(([key, value]) => {
|
865
|
-
if (value instanceof File) {
|
866
|
-
body[key] = (name, contentType) => {
|
867
|
-
return new File([value], name, { type: contentType });
|
868
|
-
};
|
869
|
-
} else if (typeof value === "object") {
|
870
|
-
body[key] = remapFileBody(value);
|
871
|
-
}
|
872
|
-
});
|
873
|
-
return body;
|
874
|
-
}
|
875
|
-
req.body = remapFileBody(req.body);
|
876
892
|
const res = {
|
877
893
|
status: (code) => {
|
878
894
|
statusCode = code;
|
@@ -926,24 +942,20 @@ var ForklaunchExpressLikeRouter = class {
|
|
926
942
|
registerRoute(method, path, registrationMethod, contractDetailsOrMiddlewareOrTypedHandler, ...middlewareOrMiddlewareAndTypedHandler) {
|
927
943
|
if (isTypedHandler(contractDetailsOrMiddlewareOrTypedHandler)) {
|
928
944
|
const { contractDetails, handlers } = contractDetailsOrMiddlewareOrTypedHandler;
|
929
|
-
|
930
|
-
|
931
|
-
path,
|
932
|
-
registrationMethod,
|
933
|
-
contractDetails,
|
934
|
-
...handlers
|
935
|
-
);
|
945
|
+
this.registerRoute(method, path, registrationMethod, contractDetails, ...handlers);
|
946
|
+
return this;
|
936
947
|
} else {
|
937
948
|
const maybeTypedHandler = middlewareOrMiddlewareAndTypedHandler[middlewareOrMiddlewareAndTypedHandler.length - 1];
|
938
949
|
if (isTypedHandler(maybeTypedHandler)) {
|
939
950
|
const { contractDetails, handlers } = maybeTypedHandler;
|
940
|
-
|
951
|
+
this.registerRoute(
|
941
952
|
method,
|
942
953
|
path,
|
943
954
|
registrationMethod,
|
944
955
|
contractDetails,
|
945
956
|
...middlewareOrMiddlewareAndTypedHandler.concat(handlers)
|
946
957
|
);
|
958
|
+
return this;
|
947
959
|
} else {
|
948
960
|
if (isExpressLikeSchemaHandler(contractDetailsOrMiddlewareOrTypedHandler) || isTypedHandler(contractDetailsOrMiddlewareOrTypedHandler)) {
|
949
961
|
throw new Error("Contract details are not defined");
|
@@ -975,10 +987,13 @@ var ForklaunchExpressLikeRouter = class {
|
|
975
987
|
).concat(handlers),
|
976
988
|
this.#parseAndRunControllerHandler(controllerHandler)
|
977
989
|
);
|
978
|
-
|
990
|
+
const localParamRequest = this.#localParamRequest(
|
979
991
|
handlers,
|
980
992
|
controllerHandler
|
981
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;
|
982
997
|
}
|
983
998
|
}
|
984
999
|
}
|
@@ -1002,10 +1017,13 @@ var ForklaunchExpressLikeRouter = class {
|
|
1002
1017
|
return this.#extractHandlers(handlers);
|
1003
1018
|
}
|
1004
1019
|
#extractNestableMiddlewareFromEnrichedTypedHandlerArray(handlers) {
|
1005
|
-
return this.#extractHandlers(
|
1006
|
-
|
1007
|
-
|
1008
|
-
|
1020
|
+
return this.#extractHandlers(handlers, (handler) => {
|
1021
|
+
if (isForklaunchExpressLikeRouter(handler)) {
|
1022
|
+
this.addRouterToSdk(handler);
|
1023
|
+
return handler.internal;
|
1024
|
+
}
|
1025
|
+
return handler;
|
1026
|
+
});
|
1009
1027
|
}
|
1010
1028
|
#processTypedHandlerOrMiddleware(handler, middleware2) {
|
1011
1029
|
if (isTypedHandler(handler)) {
|
@@ -1029,6 +1047,7 @@ var ForklaunchExpressLikeRouter = class {
|
|
1029
1047
|
middleware2
|
1030
1048
|
);
|
1031
1049
|
if (isForklaunchExpressLikeRouter(contractDetailsOrMiddlewareOrTypedHandler)) {
|
1050
|
+
this.addRouterToSdk(contractDetailsOrMiddlewareOrTypedHandler);
|
1032
1051
|
middleware2.push(contractDetailsOrMiddlewareOrTypedHandler.internal);
|
1033
1052
|
}
|
1034
1053
|
middleware2.push(
|
@@ -1058,6 +1077,16 @@ var ForklaunchExpressLikeRouter = class {
|
|
1058
1077
|
}
|
1059
1078
|
return this;
|
1060
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
|
+
}
|
1061
1090
|
registerNestableMiddlewareHandler(registrationMethod, pathOrContractDetailsOrMiddlewareOrTypedHandler, contractDetailsOrMiddlewareOrTypedHandler, ...middlewareOrMiddlewareWithTypedHandler) {
|
1062
1091
|
const middleware2 = [];
|
1063
1092
|
let path;
|
@@ -1073,7 +1102,9 @@ var ForklaunchExpressLikeRouter = class {
|
|
1073
1102
|
if (isConstrainedForklaunchRouter(
|
1074
1103
|
pathOrContractDetailsOrMiddlewareOrTypedHandler
|
1075
1104
|
)) {
|
1076
|
-
|
1105
|
+
const router = pathOrContractDetailsOrMiddlewareOrTypedHandler;
|
1106
|
+
this.addRouterToSdk(router);
|
1107
|
+
path = router.basePath;
|
1077
1108
|
}
|
1078
1109
|
middleware2.push(
|
1079
1110
|
...this.#extractNestableMiddlewareAsRouterHandlers(
|
@@ -1084,6 +1115,11 @@ var ForklaunchExpressLikeRouter = class {
|
|
1084
1115
|
)
|
1085
1116
|
);
|
1086
1117
|
}
|
1118
|
+
if (!path) {
|
1119
|
+
path = middleware2.filter(
|
1120
|
+
(m) => isForklaunchExpressLikeRouter(m)
|
1121
|
+
)[0]?.basePath;
|
1122
|
+
}
|
1087
1123
|
if (path) {
|
1088
1124
|
registrationMethod.bind(this.internal)(path, ...middleware2);
|
1089
1125
|
} else {
|
@@ -1138,15 +1174,13 @@ var ForklaunchExpressLikeRouter = class {
|
|
1138
1174
|
* @returns {ExpressRouter} - The Express router.
|
1139
1175
|
*/
|
1140
1176
|
get = (path, contractDetailsOrMiddlewareOrTypedHandler, ...middlewareOrMiddlewareWithTypedHandler) => {
|
1141
|
-
return
|
1142
|
-
get
|
1143
|
-
|
1144
|
-
|
1145
|
-
|
1146
|
-
|
1147
|
-
|
1148
|
-
)
|
1149
|
-
};
|
1177
|
+
return this.registerRoute(
|
1178
|
+
"get",
|
1179
|
+
path,
|
1180
|
+
this.internal.get,
|
1181
|
+
contractDetailsOrMiddlewareOrTypedHandler,
|
1182
|
+
...middlewareOrMiddlewareWithTypedHandler
|
1183
|
+
);
|
1150
1184
|
};
|
1151
1185
|
/**
|
1152
1186
|
* Registers a POST route with the specified contract details and handler handlers.
|
@@ -1162,15 +1196,13 @@ var ForklaunchExpressLikeRouter = class {
|
|
1162
1196
|
* @returns {ExpressRouter} - The Expxwress router.
|
1163
1197
|
*/
|
1164
1198
|
post = (path, contractDetailsOrMiddlewareOrTypedHandler, ...middlewareOrMiddlewareWithTypedHandler) => {
|
1165
|
-
return
|
1166
|
-
post
|
1167
|
-
|
1168
|
-
|
1169
|
-
|
1170
|
-
|
1171
|
-
|
1172
|
-
)
|
1173
|
-
};
|
1199
|
+
return this.registerRoute(
|
1200
|
+
"post",
|
1201
|
+
path,
|
1202
|
+
this.internal.post,
|
1203
|
+
contractDetailsOrMiddlewareOrTypedHandler,
|
1204
|
+
...middlewareOrMiddlewareWithTypedHandler
|
1205
|
+
);
|
1174
1206
|
};
|
1175
1207
|
/**
|
1176
1208
|
* Registers a PUT route with the specified contract details and handler handlers.
|
@@ -1186,15 +1218,13 @@ var ForklaunchExpressLikeRouter = class {
|
|
1186
1218
|
* @returns {ExpressRouter} - The Express router.
|
1187
1219
|
*/
|
1188
1220
|
put = (path, contractDetailsOrMiddlewareOrTypedHandler, ...middlewareOrMiddlewareWithTypedHandler) => {
|
1189
|
-
return
|
1190
|
-
put
|
1191
|
-
|
1192
|
-
|
1193
|
-
|
1194
|
-
|
1195
|
-
|
1196
|
-
)
|
1197
|
-
};
|
1221
|
+
return this.registerRoute(
|
1222
|
+
"put",
|
1223
|
+
path,
|
1224
|
+
this.internal.put,
|
1225
|
+
contractDetailsOrMiddlewareOrTypedHandler,
|
1226
|
+
...middlewareOrMiddlewareWithTypedHandler
|
1227
|
+
);
|
1198
1228
|
};
|
1199
1229
|
/**
|
1200
1230
|
* Registers a PATCH route with the specified contract details and handler handlers.
|
@@ -1210,15 +1240,13 @@ var ForklaunchExpressLikeRouter = class {
|
|
1210
1240
|
* @returns {ExpressRouter} - The Express router.
|
1211
1241
|
*/
|
1212
1242
|
patch = (path, contractDetailsOrMiddlewareOrTypedHandler, ...middlewareOrMiddlewareWithTypedHandler) => {
|
1213
|
-
return
|
1214
|
-
patch
|
1215
|
-
|
1216
|
-
|
1217
|
-
|
1218
|
-
|
1219
|
-
|
1220
|
-
)
|
1221
|
-
};
|
1243
|
+
return this.registerRoute(
|
1244
|
+
"patch",
|
1245
|
+
path,
|
1246
|
+
this.internal.patch,
|
1247
|
+
contractDetailsOrMiddlewareOrTypedHandler,
|
1248
|
+
...middlewareOrMiddlewareWithTypedHandler
|
1249
|
+
);
|
1222
1250
|
};
|
1223
1251
|
/**
|
1224
1252
|
* Registers a DELETE route with the specified contract details and handler handlers.
|
@@ -1234,15 +1262,13 @@ var ForklaunchExpressLikeRouter = class {
|
|
1234
1262
|
* @returns {ExpressRouter} - The Express router.
|
1235
1263
|
*/
|
1236
1264
|
delete = (path, contractDetailsOrMiddlewareOrTypedHandler, ...middlewareOrMiddlewareWithTypedHandler) => {
|
1237
|
-
return
|
1238
|
-
delete
|
1239
|
-
|
1240
|
-
|
1241
|
-
|
1242
|
-
|
1243
|
-
|
1244
|
-
)
|
1245
|
-
};
|
1265
|
+
return this.registerRoute(
|
1266
|
+
"delete",
|
1267
|
+
path,
|
1268
|
+
this.internal.delete,
|
1269
|
+
contractDetailsOrMiddlewareOrTypedHandler,
|
1270
|
+
...middlewareOrMiddlewareWithTypedHandler
|
1271
|
+
);
|
1246
1272
|
};
|
1247
1273
|
/**
|
1248
1274
|
* Registers a OPTIONS route with the specified contract details and handler handlers.
|
@@ -1258,15 +1284,13 @@ var ForklaunchExpressLikeRouter = class {
|
|
1258
1284
|
* @returns {ExpressRouter} - The Express router.
|
1259
1285
|
*/
|
1260
1286
|
options = (path, contractDetailsOrMiddlewareOrTypedHandler, ...middlewareOrMiddlewareWithTypedHandler) => {
|
1261
|
-
return
|
1262
|
-
options
|
1263
|
-
|
1264
|
-
|
1265
|
-
|
1266
|
-
|
1267
|
-
|
1268
|
-
)
|
1269
|
-
};
|
1287
|
+
return this.registerRoute(
|
1288
|
+
"options",
|
1289
|
+
path,
|
1290
|
+
this.internal.options,
|
1291
|
+
contractDetailsOrMiddlewareOrTypedHandler,
|
1292
|
+
...middlewareOrMiddlewareWithTypedHandler
|
1293
|
+
);
|
1270
1294
|
};
|
1271
1295
|
/**
|
1272
1296
|
* Registers a HEAD route with the specified contract details and handler handlers.
|
@@ -1282,15 +1306,13 @@ var ForklaunchExpressLikeRouter = class {
|
|
1282
1306
|
* @returns {ExpressRouter} - The Express router.
|
1283
1307
|
*/
|
1284
1308
|
head = (path, contractDetailsOrMiddlewareOrTypedHandler, ...middlewareOrMiddlewareWithTypedHandler) => {
|
1285
|
-
return
|
1286
|
-
head
|
1287
|
-
|
1288
|
-
|
1289
|
-
|
1290
|
-
|
1291
|
-
|
1292
|
-
)
|
1293
|
-
};
|
1309
|
+
return this.registerRoute(
|
1310
|
+
"head",
|
1311
|
+
path,
|
1312
|
+
this.internal.head,
|
1313
|
+
contractDetailsOrMiddlewareOrTypedHandler,
|
1314
|
+
...middlewareOrMiddlewareWithTypedHandler
|
1315
|
+
);
|
1294
1316
|
};
|
1295
1317
|
/**
|
1296
1318
|
* Registers a TRACE route with the specified contract details and handler handlers.
|
@@ -1306,16 +1328,32 @@ var ForklaunchExpressLikeRouter = class {
|
|
1306
1328
|
* @returns {ExpressRouter} - The Express router.
|
1307
1329
|
*/
|
1308
1330
|
trace = (path, contractDetailsOrMiddlewareOrTypedHandler, ...middlewareOrMiddlewareWithTypedHandler) => {
|
1309
|
-
return
|
1310
|
-
trace
|
1311
|
-
|
1312
|
-
|
1313
|
-
|
1314
|
-
|
1315
|
-
|
1316
|
-
)
|
1317
|
-
};
|
1331
|
+
return this.registerRoute(
|
1332
|
+
"trace",
|
1333
|
+
path,
|
1334
|
+
this.internal.trace,
|
1335
|
+
contractDetailsOrMiddlewareOrTypedHandler,
|
1336
|
+
...middlewareOrMiddlewareWithTypedHandler
|
1337
|
+
);
|
1318
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
|
+
}
|
1319
1357
|
};
|
1320
1358
|
|
1321
1359
|
// src/http/application/expressLikeApplication.ts
|
@@ -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
|
2721
|
+
isNever as isNever3,
|
2472
2722
|
isNodeJsWriteableStream,
|
2473
|
-
isRecord,
|
2723
|
+
isRecord as isRecord2,
|
2474
2724
|
readableStreamToAsyncIterable,
|
2475
|
-
safeStringify as
|
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
|
-
|
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
|
-
|
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 :
|
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 (
|
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
|
-
|
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
|
-
|
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
|
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:
|
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
|
2698
|
-
const controllerName = transformBasePath(
|
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
|
2705
|
-
|
2706
|
-
|
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
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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[
|
3057
|
+
paths[openApiPath][route.method] = operationObject;
|
2802
3058
|
}
|
2803
3059
|
});
|
2804
3060
|
});
|
2805
|
-
return generateOpenApiDocument(
|
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,
|