@forklaunch/core 0.11.5 → 0.11.7
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 +585 -292
- package/lib/http/index.d.ts +585 -292
- package/lib/http/index.js +737 -266
- package/lib/http/index.js.map +1 -1
- package/lib/http/index.mjs +737 -268
- package/lib/http/index.mjs.map +1 -1
- package/package.json +16 -16
package/lib/http/index.mjs
CHANGED
@@ -10,18 +10,28 @@ function cors(corsOptions) {
|
|
10
10
|
return res.getHeaders()[key];
|
11
11
|
};
|
12
12
|
}
|
13
|
-
corsMiddleware(corsOptions)(
|
14
|
-
|
13
|
+
corsMiddleware(corsOptions)(
|
14
|
+
req,
|
15
|
+
res,
|
16
|
+
next ?? (() => {
|
17
|
+
})
|
18
|
+
);
|
15
19
|
};
|
16
20
|
}
|
17
21
|
|
18
22
|
// src/http/router/expressLikeRouter.ts
|
19
23
|
import {
|
24
|
+
isRecord as isRecord2,
|
20
25
|
sanitizePathSlashes,
|
21
26
|
toPrettyCamelCase,
|
22
27
|
toRecord
|
23
28
|
} from "@forklaunch/common";
|
24
29
|
|
30
|
+
// src/http/guards/hasVersionedSchema.ts
|
31
|
+
function hasVersionedSchema(contractDetails) {
|
32
|
+
return typeof contractDetails === "object" && contractDetails !== null && "versions" in contractDetails && contractDetails.versions !== null;
|
33
|
+
}
|
34
|
+
|
25
35
|
// src/http/guards/isForklaunchRouter.ts
|
26
36
|
function isForklaunchRouter(maybeForklaunchRouter) {
|
27
37
|
return maybeForklaunchRouter != null && typeof maybeForklaunchRouter === "object" && "basePath" in maybeForklaunchRouter && "routes" in maybeForklaunchRouter && Array.isArray(maybeForklaunchRouter.routes);
|
@@ -52,12 +62,16 @@ function isForklaunchExpressLikeRouter(maybeForklaunchExpressLikeRouter) {
|
|
52
62
|
|
53
63
|
// src/http/guards/isPathParamContractDetails.ts
|
54
64
|
function isPathParamHttpContractDetails(maybePathParamHttpContractDetails) {
|
55
|
-
return maybePathParamHttpContractDetails != null && typeof maybePathParamHttpContractDetails === "object" && "name" in maybePathParamHttpContractDetails && "summary" in maybePathParamHttpContractDetails && "responses" in maybePathParamHttpContractDetails && maybePathParamHttpContractDetails.
|
65
|
+
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(
|
66
|
+
(version) => "responses" in version && version.responses != null
|
67
|
+
));
|
56
68
|
}
|
57
69
|
|
58
70
|
// src/http/guards/isHttpContractDetails.ts
|
59
71
|
function isHttpContractDetails(maybeContractDetails) {
|
60
|
-
return isPathParamHttpContractDetails(maybeContractDetails) && "body" in maybeContractDetails && maybeContractDetails.body != null
|
72
|
+
return isPathParamHttpContractDetails(maybeContractDetails) && ("body" in maybeContractDetails && maybeContractDetails.body != null || "versions" in maybeContractDetails && typeof maybeContractDetails.versions === "object" && maybeContractDetails.versions != null && Object.values(maybeContractDetails.versions).every(
|
73
|
+
(version) => "body" in version && version.body != null
|
74
|
+
));
|
61
75
|
}
|
62
76
|
|
63
77
|
// src/http/guards/isTypedHandler.ts
|
@@ -206,7 +220,10 @@ async function checkAuthorizationToken(authorizationMethod, authorizationToken,
|
|
206
220
|
if (!authorizationMethod.mapRoles) {
|
207
221
|
return [500, "No role mapping function provided."];
|
208
222
|
}
|
209
|
-
const resourceRoles = await authorizationMethod.mapRoles(
|
223
|
+
const resourceRoles = await authorizationMethod.mapRoles(
|
224
|
+
resourceId,
|
225
|
+
req
|
226
|
+
);
|
210
227
|
if ("allowedRoles" in authorizationMethod && authorizationMethod.allowedRoles) {
|
211
228
|
if (resourceRoles.intersection(authorizationMethod.allowedRoles).size === 0) {
|
212
229
|
return invalidAuthorizationTokenRoles;
|
@@ -227,6 +244,7 @@ async function parseRequestAuth(req, res, next) {
|
|
227
244
|
const [error, message] = await checkAuthorizationToken(
|
228
245
|
auth,
|
229
246
|
req.headers[auth.headerName ?? "Authorization"] || req.headers[auth.headerName ?? "authorization"],
|
247
|
+
// we can safely cast here because we know that the user will supply resolution for the request
|
230
248
|
req
|
231
249
|
) ?? [];
|
232
250
|
if (error != null) {
|
@@ -546,6 +564,7 @@ function enrichDetails(path, contractDetails, requestSchema, responseSchemas, op
|
|
546
564
|
}
|
547
565
|
|
548
566
|
// src/http/middleware/request/parse.middleware.ts
|
567
|
+
import { isRecord } from "@forklaunch/common";
|
549
568
|
import {
|
550
569
|
prettyPrintParseErrors
|
551
570
|
} from "@forklaunch/validator";
|
@@ -568,10 +587,49 @@ function parse(req, res, next) {
|
|
568
587
|
headers: req.headers,
|
569
588
|
body: req.body
|
570
589
|
};
|
571
|
-
const
|
572
|
-
|
573
|
-
|
574
|
-
|
590
|
+
const schemaValidator = req.schemaValidator;
|
591
|
+
let matchedVersions;
|
592
|
+
let parsedRequest;
|
593
|
+
let collectedParseErrors;
|
594
|
+
if (req.contractDetails.versions) {
|
595
|
+
if (isRecord(req.requestSchema)) {
|
596
|
+
let runningParseErrors = "";
|
597
|
+
matchedVersions = [];
|
598
|
+
Object.entries(req.requestSchema).forEach(([version, schema]) => {
|
599
|
+
const parsingResult = schemaValidator.parse(schema, request);
|
600
|
+
if (parsingResult.ok) {
|
601
|
+
parsedRequest = parsingResult;
|
602
|
+
matchedVersions.push(version);
|
603
|
+
req.version = version;
|
604
|
+
res.version = req.version;
|
605
|
+
} else {
|
606
|
+
runningParseErrors += prettyPrintParseErrors(
|
607
|
+
parsingResult.errors,
|
608
|
+
`Version ${version} request`
|
609
|
+
);
|
610
|
+
}
|
611
|
+
});
|
612
|
+
if (!parsedRequest) {
|
613
|
+
parsedRequest = {
|
614
|
+
ok: false,
|
615
|
+
errors: []
|
616
|
+
};
|
617
|
+
collectedParseErrors = runningParseErrors;
|
618
|
+
}
|
619
|
+
} else {
|
620
|
+
req.version = Object.keys(req.contractDetails.versions).pop();
|
621
|
+
res.version = req.version;
|
622
|
+
parsedRequest = {
|
623
|
+
ok: true,
|
624
|
+
value: request
|
625
|
+
};
|
626
|
+
matchedVersions = Object.keys(req.contractDetails.versions);
|
627
|
+
}
|
628
|
+
} else {
|
629
|
+
const parsingResult = schemaValidator.parse(req.requestSchema, request);
|
630
|
+
parsedRequest = parsingResult;
|
631
|
+
matchedVersions = 0;
|
632
|
+
}
|
575
633
|
if (parsedRequest.ok && isRequestShape(parsedRequest.value)) {
|
576
634
|
req.body = parsedRequest.value.body;
|
577
635
|
req.params = parsedRequest.value.params;
|
@@ -602,10 +660,7 @@ function parse(req, res, next) {
|
|
602
660
|
res.status(400);
|
603
661
|
if (hasSend(res)) {
|
604
662
|
res.send(
|
605
|
-
`${prettyPrintParseErrors(
|
606
|
-
parsedRequest.errors,
|
607
|
-
"Request"
|
608
|
-
)}
|
663
|
+
`${collectedParseErrors ?? prettyPrintParseErrors(parsedRequest.errors, "Request")}
|
609
664
|
|
610
665
|
Correlation id: ${req.context.correlationId ?? "No correlation ID"}`
|
611
666
|
);
|
@@ -615,13 +670,14 @@ Correlation id: ${req.context.correlationId ?? "No correlation ID"}`
|
|
615
670
|
return;
|
616
671
|
case "warning":
|
617
672
|
req.openTelemetryCollector.warn(
|
618
|
-
prettyPrintParseErrors(parsedRequest.errors, "Request")
|
673
|
+
collectedParseErrors ?? prettyPrintParseErrors(parsedRequest.errors, "Request")
|
619
674
|
);
|
620
675
|
break;
|
621
676
|
case "none":
|
622
677
|
break;
|
623
678
|
}
|
624
679
|
}
|
680
|
+
req._parsedVersions = matchedVersions;
|
625
681
|
next?.();
|
626
682
|
}
|
627
683
|
|
@@ -759,13 +815,12 @@ function discriminateResponseBodies(schemaValidator, responses) {
|
|
759
815
|
|
760
816
|
// src/http/router/expressLikeRouter.ts
|
761
817
|
var ForklaunchExpressLikeRouter = class _ForklaunchExpressLikeRouter {
|
762
|
-
constructor(basePath, schemaValidator, internal, postEnrichMiddleware, openTelemetryCollector
|
818
|
+
constructor(basePath, schemaValidator, internal, postEnrichMiddleware, openTelemetryCollector) {
|
763
819
|
this.basePath = basePath;
|
764
820
|
this.schemaValidator = schemaValidator;
|
765
821
|
this.internal = internal;
|
766
822
|
this.postEnrichMiddleware = postEnrichMiddleware;
|
767
823
|
this.openTelemetryCollector = openTelemetryCollector;
|
768
|
-
this.sdkName = sdkName;
|
769
824
|
if (process.env.NODE_ENV !== "test" && !process.env.VITEST) {
|
770
825
|
process.on("uncaughtException", (err) => {
|
771
826
|
this.openTelemetryCollector.error(`Uncaught exception: ${err}`);
|
@@ -788,8 +843,9 @@ var ForklaunchExpressLikeRouter = class _ForklaunchExpressLikeRouter {
|
|
788
843
|
requestHandler;
|
789
844
|
routers = [];
|
790
845
|
routes = [];
|
791
|
-
|
846
|
+
_fetchMap = {};
|
792
847
|
sdk = {};
|
848
|
+
sdkPaths = {};
|
793
849
|
/**
|
794
850
|
* Resolves middlewares based on the contract details.
|
795
851
|
*
|
@@ -859,50 +915,112 @@ var ForklaunchExpressLikeRouter = class _ForklaunchExpressLikeRouter {
|
|
859
915
|
}
|
860
916
|
return controllerHandler;
|
861
917
|
}
|
862
|
-
#
|
918
|
+
#processContractDetailsIO(contractDetailsIO, params) {
|
863
919
|
const schemaValidator = this.schemaValidator;
|
864
|
-
|
865
|
-
if (isHttpContractDetails(contractDetails)) {
|
866
|
-
body = discriminateBody(this.schemaValidator, contractDetails.body);
|
867
|
-
}
|
868
|
-
const requestSchema = schemaValidator.compile(
|
869
|
-
schemaValidator.schemify({
|
870
|
-
...contractDetails.params ? { params: contractDetails.params } : {},
|
871
|
-
...contractDetails.requestHeaders ? { headers: contractDetails.requestHeaders } : {},
|
872
|
-
...contractDetails.query ? { query: contractDetails.query } : {},
|
873
|
-
...body != null ? { body: body.schema } : {}
|
874
|
-
})
|
875
|
-
);
|
876
|
-
const responseEntries = {
|
920
|
+
const responseSchemas = {
|
877
921
|
400: schemaValidator.string,
|
878
922
|
401: schemaValidator.string,
|
879
923
|
403: schemaValidator.string,
|
880
924
|
404: schemaValidator.string,
|
881
925
|
500: schemaValidator.string,
|
882
|
-
...
|
926
|
+
...Object.fromEntries(
|
883
927
|
Object.entries(
|
884
928
|
discriminateResponseBodies(
|
885
929
|
this.schemaValidator,
|
886
|
-
|
930
|
+
contractDetailsIO.responses
|
887
931
|
)
|
888
932
|
).map(([key, value]) => {
|
889
|
-
return [key, value.schema];
|
933
|
+
return [Number(key), value.schema];
|
890
934
|
})
|
891
|
-
)
|
935
|
+
)
|
892
936
|
};
|
893
|
-
|
894
|
-
|
895
|
-
|
896
|
-
|
897
|
-
schemaValidator.
|
937
|
+
return {
|
938
|
+
requestSchema: schemaValidator.compile(
|
939
|
+
schemaValidator.schemify({
|
940
|
+
...params != null ? { params } : { params: schemaValidator.unknown },
|
941
|
+
...contractDetailsIO.requestHeaders != null ? { headers: contractDetailsIO.requestHeaders } : { headers: schemaValidator.unknown },
|
942
|
+
...contractDetailsIO.query != null ? { query: contractDetailsIO.query } : { query: schemaValidator.unknown },
|
943
|
+
...contractDetailsIO.body != null ? {
|
944
|
+
body: discriminateBody(
|
945
|
+
this.schemaValidator,
|
946
|
+
contractDetailsIO.body
|
947
|
+
)?.schema
|
948
|
+
} : { body: schemaValidator.unknown }
|
949
|
+
})
|
950
|
+
),
|
951
|
+
responseSchemas: {
|
952
|
+
...contractDetailsIO.responseHeaders != null ? {
|
953
|
+
headers: schemaValidator.compile(
|
954
|
+
schemaValidator.schemify(contractDetailsIO.responseHeaders)
|
955
|
+
)
|
956
|
+
} : { headers: schemaValidator.unknown },
|
957
|
+
responses: Object.fromEntries(
|
958
|
+
Object.entries(responseSchemas).map(([key, value]) => {
|
959
|
+
return [
|
960
|
+
key,
|
961
|
+
schemaValidator.compile(schemaValidator.schemify(value))
|
962
|
+
];
|
963
|
+
})
|
898
964
|
)
|
899
|
-
}
|
965
|
+
}
|
900
966
|
};
|
901
|
-
|
902
|
-
|
903
|
-
|
967
|
+
}
|
968
|
+
#compile(contractDetails) {
|
969
|
+
const schemaValidator = this.schemaValidator;
|
970
|
+
let requestSchema;
|
971
|
+
let responseSchemas;
|
972
|
+
if (hasVersionedSchema(contractDetails)) {
|
973
|
+
requestSchema = {};
|
974
|
+
responseSchemas = {};
|
975
|
+
Object.entries(contractDetails.versions ?? {}).forEach(
|
976
|
+
([version, versionedContractDetails]) => {
|
977
|
+
const {
|
978
|
+
requestSchema: versionedRequestSchema,
|
979
|
+
responseSchemas: versionedResponseSchemas
|
980
|
+
} = this.#processContractDetailsIO(
|
981
|
+
versionedContractDetails,
|
982
|
+
contractDetails.params
|
983
|
+
);
|
984
|
+
if (isRecord2(requestSchema)) {
|
985
|
+
requestSchema = {
|
986
|
+
...requestSchema,
|
987
|
+
[version]: versionedRequestSchema
|
988
|
+
};
|
989
|
+
}
|
990
|
+
if (isRecord2(responseSchemas)) {
|
991
|
+
responseSchemas = {
|
992
|
+
...responseSchemas,
|
993
|
+
[version]: versionedResponseSchemas
|
994
|
+
};
|
995
|
+
}
|
996
|
+
}
|
904
997
|
);
|
905
|
-
}
|
998
|
+
} else {
|
999
|
+
const {
|
1000
|
+
requestSchema: unversionedRequestSchema,
|
1001
|
+
responseSchemas: unversionedResponseSchemas
|
1002
|
+
} = this.#processContractDetailsIO(
|
1003
|
+
{
|
1004
|
+
..."params" in contractDetails && contractDetails.params != null ? { params: contractDetails.params } : { params: schemaValidator.unknown },
|
1005
|
+
..."requestHeaders" in contractDetails && contractDetails.requestHeaders != null ? { requestHeaders: contractDetails.requestHeaders } : {
|
1006
|
+
requestHeaders: schemaValidator.unknown
|
1007
|
+
},
|
1008
|
+
..."responseHeaders" in contractDetails && contractDetails.responseHeaders != null ? { responseHeaders: contractDetails.responseHeaders } : {
|
1009
|
+
responseHeaders: schemaValidator.unknown
|
1010
|
+
},
|
1011
|
+
..."query" in contractDetails && contractDetails.query != null ? { query: contractDetails.query } : {
|
1012
|
+
query: schemaValidator.unknown
|
1013
|
+
},
|
1014
|
+
..."body" in contractDetails && contractDetails.body != null ? { body: contractDetails.body } : {
|
1015
|
+
body: schemaValidator.unknown
|
1016
|
+
},
|
1017
|
+
responses: "responses" in contractDetails && contractDetails.responses != null ? contractDetails.responses : schemaValidator.unknown
|
1018
|
+
},
|
1019
|
+
contractDetails.params
|
1020
|
+
);
|
1021
|
+
requestSchema = unversionedRequestSchema;
|
1022
|
+
responseSchemas = unversionedResponseSchemas;
|
1023
|
+
}
|
906
1024
|
return {
|
907
1025
|
requestSchema,
|
908
1026
|
responseSchemas
|
@@ -911,15 +1029,18 @@ var ForklaunchExpressLikeRouter = class _ForklaunchExpressLikeRouter {
|
|
911
1029
|
/**
|
912
1030
|
* Fetches a route from the route map and executes it with the given parameters.
|
913
1031
|
*
|
914
|
-
* @template Path - The path type that extends keyof
|
1032
|
+
* @template Path - The path type that extends keyof _fetchMap and string.
|
915
1033
|
* @param {Path} path - The route path
|
916
|
-
* @param {Parameters<
|
917
|
-
* @returns {Promise<ReturnType<
|
1034
|
+
* @param {Parameters<_fetchMap[Path]>[1]} [requestInit] - Optional request initialization parameters.
|
1035
|
+
* @returns {Promise<ReturnType<_fetchMap[Path]>>} - The result of executing the route handler.
|
918
1036
|
*/
|
919
1037
|
fetch = async (path, ...reqInit) => {
|
920
|
-
|
1038
|
+
const method = reqInit[0]?.method;
|
1039
|
+
const version = reqInit[0] != null && "version" in reqInit[0] ? reqInit[0].version : void 0;
|
1040
|
+
return (version ? this._fetchMap[path][method ?? "GET"][version] : this._fetchMap[path][method ?? "GET"])(
|
921
1041
|
path,
|
922
1042
|
reqInit[0]
|
1043
|
+
// reqInit
|
923
1044
|
);
|
924
1045
|
};
|
925
1046
|
/**
|
@@ -929,7 +1050,7 @@ var ForklaunchExpressLikeRouter = class _ForklaunchExpressLikeRouter {
|
|
929
1050
|
* @param controllerHandler
|
930
1051
|
* @returns
|
931
1052
|
*/
|
932
|
-
#localParamRequest(handlers, controllerHandler) {
|
1053
|
+
#localParamRequest(handlers, controllerHandler, version) {
|
933
1054
|
return async (route, request) => {
|
934
1055
|
let statusCode;
|
935
1056
|
let responseMessage;
|
@@ -939,7 +1060,8 @@ var ForklaunchExpressLikeRouter = class _ForklaunchExpressLikeRouter {
|
|
939
1060
|
query: request?.query ?? {},
|
940
1061
|
headers: request?.headers ?? {},
|
941
1062
|
body: discriminateBody(this.schemaValidator, request?.body)?.schema ?? {},
|
942
|
-
path: route
|
1063
|
+
path: route,
|
1064
|
+
version
|
943
1065
|
};
|
944
1066
|
const res = {
|
945
1067
|
status: (code) => {
|
@@ -960,7 +1082,8 @@ var ForklaunchExpressLikeRouter = class _ForklaunchExpressLikeRouter {
|
|
960
1082
|
},
|
961
1083
|
sseEmitter: (generator) => {
|
962
1084
|
responseMessage = generator();
|
963
|
-
}
|
1085
|
+
},
|
1086
|
+
version
|
964
1087
|
};
|
965
1088
|
let cursor = handlers.shift();
|
966
1089
|
if (cursor) {
|
@@ -994,20 +1117,20 @@ var ForklaunchExpressLikeRouter = class _ForklaunchExpressLikeRouter {
|
|
994
1117
|
registerRoute(method, path, registrationMethod, contractDetailsOrMiddlewareOrTypedHandler, ...middlewareOrMiddlewareAndTypedHandler) {
|
995
1118
|
if (isTypedHandler(contractDetailsOrMiddlewareOrTypedHandler)) {
|
996
1119
|
const { contractDetails, handlers } = contractDetailsOrMiddlewareOrTypedHandler;
|
997
|
-
this.registerRoute(method, path, registrationMethod, contractDetails, ...handlers);
|
998
|
-
return
|
1120
|
+
const router = this.registerRoute(method, path, registrationMethod, contractDetails, ...handlers);
|
1121
|
+
return router;
|
999
1122
|
} else {
|
1000
1123
|
const maybeTypedHandler = middlewareOrMiddlewareAndTypedHandler[middlewareOrMiddlewareAndTypedHandler.length - 1];
|
1001
1124
|
if (isTypedHandler(maybeTypedHandler)) {
|
1002
1125
|
const { contractDetails, handlers } = maybeTypedHandler;
|
1003
|
-
this.registerRoute(
|
1126
|
+
const router = this.registerRoute(
|
1004
1127
|
method,
|
1005
1128
|
path,
|
1006
1129
|
registrationMethod,
|
1007
1130
|
contractDetails,
|
1008
1131
|
...middlewareOrMiddlewareAndTypedHandler.concat(handlers)
|
1009
1132
|
);
|
1010
|
-
return
|
1133
|
+
return router;
|
1011
1134
|
} else {
|
1012
1135
|
if (isExpressLikeSchemaHandler(contractDetailsOrMiddlewareOrTypedHandler) || isTypedHandler(contractDetailsOrMiddlewareOrTypedHandler)) {
|
1013
1136
|
throw new Error("Contract details are not defined");
|
@@ -1021,6 +1144,17 @@ var ForklaunchExpressLikeRouter = class _ForklaunchExpressLikeRouter {
|
|
1021
1144
|
"Contract details are malformed for route definition"
|
1022
1145
|
);
|
1023
1146
|
}
|
1147
|
+
if (contractDetails.versions) {
|
1148
|
+
const parserTypes = Object.values(contractDetails.versions).map(
|
1149
|
+
(version) => discriminateBody(this.schemaValidator, version.body)?.parserType
|
1150
|
+
);
|
1151
|
+
const allParserTypesSame = parserTypes.length === 0 || parserTypes.every((pt) => pt === parserTypes[0]);
|
1152
|
+
if (!allParserTypesSame) {
|
1153
|
+
throw new Error(
|
1154
|
+
"All versioned contractDetails must have the same parsing type for body."
|
1155
|
+
);
|
1156
|
+
}
|
1157
|
+
}
|
1024
1158
|
this.routes.push({
|
1025
1159
|
basePath: this.basePath,
|
1026
1160
|
path,
|
@@ -1029,22 +1163,39 @@ var ForklaunchExpressLikeRouter = class _ForklaunchExpressLikeRouter {
|
|
1029
1163
|
});
|
1030
1164
|
const { requestSchema, responseSchemas } = this.#compile(contractDetails);
|
1031
1165
|
const controllerHandler = this.#extractControllerHandler(handlers);
|
1166
|
+
const resolvedMiddlewares = this.#resolveMiddlewares(
|
1167
|
+
path,
|
1168
|
+
contractDetails,
|
1169
|
+
requestSchema,
|
1170
|
+
responseSchemas
|
1171
|
+
).concat(handlers);
|
1032
1172
|
registrationMethod.bind(this.internal)(
|
1033
1173
|
path,
|
1034
|
-
...
|
1035
|
-
path,
|
1036
|
-
contractDetails,
|
1037
|
-
requestSchema,
|
1038
|
-
responseSchemas
|
1039
|
-
).concat(handlers),
|
1174
|
+
...resolvedMiddlewares,
|
1040
1175
|
this.#parseAndRunControllerHandler(controllerHandler)
|
1041
1176
|
);
|
1042
|
-
|
1043
|
-
|
1044
|
-
|
1177
|
+
toRecord(this._fetchMap)[sanitizePathSlashes(`${this.basePath}${path}`)] = {
|
1178
|
+
...this._fetchMap[sanitizePathSlashes(`${this.basePath}${path}`)] ?? {},
|
1179
|
+
[method.toUpperCase()]: contractDetails.versions ? Object.fromEntries(
|
1180
|
+
Object.keys(contractDetails.versions).map((version) => [
|
1181
|
+
version,
|
1182
|
+
this.#localParamRequest(handlers, controllerHandler, version)
|
1183
|
+
])
|
1184
|
+
) : this.#localParamRequest(handlers, controllerHandler)
|
1185
|
+
};
|
1186
|
+
toRecord(this.sdk)[toPrettyCamelCase(contractDetails.name)] = contractDetails.versions ? Object.fromEntries(
|
1187
|
+
Object.keys(contractDetails.versions).map((version) => [
|
1188
|
+
version,
|
1189
|
+
(req) => this.#localParamRequest(
|
1190
|
+
handlers,
|
1191
|
+
controllerHandler,
|
1192
|
+
version
|
1193
|
+
)(`${this.basePath}${path}`, req)
|
1194
|
+
])
|
1195
|
+
) : (req) => this.#localParamRequest(handlers, controllerHandler)(
|
1196
|
+
`${this.basePath}${path}`,
|
1197
|
+
req
|
1045
1198
|
);
|
1046
|
-
toRecord(this.fetchMap)[sanitizePathSlashes(`${this.basePath}${path}`)] = localParamRequest;
|
1047
|
-
toRecord(this.sdk)[toPrettyCamelCase(contractDetails.name ?? this.basePath)] = (req) => localParamRequest(`${this.basePath}${path}`, req);
|
1048
1199
|
return this;
|
1049
1200
|
}
|
1050
1201
|
}
|
@@ -1130,8 +1281,8 @@ var ForklaunchExpressLikeRouter = class _ForklaunchExpressLikeRouter {
|
|
1130
1281
|
return this;
|
1131
1282
|
}
|
1132
1283
|
addRouterToSdk(router) {
|
1133
|
-
Object.entries(router.
|
1134
|
-
([key, value]) => toRecord(this.
|
1284
|
+
Object.entries(router._fetchMap).map(
|
1285
|
+
([key, value]) => toRecord(this._fetchMap)[sanitizePathSlashes(`${this.basePath}${key}`)] = value
|
1135
1286
|
);
|
1136
1287
|
const existingSdk = this.sdk[router.sdkName ?? toPrettyCamelCase(router.basePath)];
|
1137
1288
|
toRecord(this.sdk)[router.sdkName ?? toPrettyCamelCase(router.basePath)] = {
|
@@ -1391,8 +1542,9 @@ var ForklaunchExpressLikeRouter = class _ForklaunchExpressLikeRouter {
|
|
1391
1542
|
cloneInternals(clone) {
|
1392
1543
|
clone.routers = [...this.routers];
|
1393
1544
|
clone.routes = [...this.routes];
|
1394
|
-
clone.
|
1545
|
+
clone._fetchMap = { ...this._fetchMap };
|
1395
1546
|
clone.sdk = { ...this.sdk };
|
1547
|
+
clone.sdkPaths = { ...this.sdkPaths };
|
1396
1548
|
}
|
1397
1549
|
clone() {
|
1398
1550
|
const clone = new _ForklaunchExpressLikeRouter(
|
@@ -1400,8 +1552,7 @@ var ForklaunchExpressLikeRouter = class _ForklaunchExpressLikeRouter {
|
|
1400
1552
|
this.schemaValidator,
|
1401
1553
|
this.internal,
|
1402
1554
|
this.postEnrichMiddleware,
|
1403
|
-
this.openTelemetryCollector
|
1404
|
-
this.sdkName
|
1555
|
+
this.openTelemetryCollector
|
1405
1556
|
);
|
1406
1557
|
this.cloneInternals(clone);
|
1407
1558
|
return clone;
|
@@ -1456,9 +1607,13 @@ function typedHandler(_schemaValidator, pathOrContractMethod, contractMethodOrCo
|
|
1456
1607
|
throw new Error("Invalid definition for handler");
|
1457
1608
|
}
|
1458
1609
|
}
|
1610
|
+
if (isPath(pathOrContractMethod) && typeof contractMethodOrContractDetails !== "string") {
|
1611
|
+
throw new Error("Contract method not supplied, bailing");
|
1612
|
+
}
|
1459
1613
|
return {
|
1460
1614
|
_typedHandler: true,
|
1461
1615
|
_path: isPath(pathOrContractMethod) ? pathOrContractMethod : void 0,
|
1616
|
+
_method: isPath(pathOrContractMethod) ? contractMethodOrContractDetails : pathOrContractMethod,
|
1462
1617
|
contractDetails,
|
1463
1618
|
handlers
|
1464
1619
|
};
|
@@ -2498,37 +2653,57 @@ var getCodeForStatus = (status) => {
|
|
2498
2653
|
var httpStatusCodes_default = HTTPStatuses;
|
2499
2654
|
|
2500
2655
|
// src/http/mcpGenerator/mcpGenerator.ts
|
2501
|
-
import { isNever as isNever3, isRecord, safeStringify as safeStringify2 } from "@forklaunch/common";
|
2656
|
+
import { isNever as isNever3, isRecord as isRecord3, safeStringify as safeStringify2 } from "@forklaunch/common";
|
2502
2657
|
import { string, ZodSchemaValidator } from "@forklaunch/validator/zod";
|
2503
2658
|
import { FastMCP } from "fastmcp";
|
2504
2659
|
|
2660
|
+
// src/http/guards/isVersionedInputSchema.ts
|
2661
|
+
function isUnionable(schema) {
|
2662
|
+
return schema.length > 1;
|
2663
|
+
}
|
2664
|
+
|
2505
2665
|
// src/http/router/unpackRouters.ts
|
2506
|
-
|
2507
|
-
function unpackRouters(routers, recursiveBasePath = [], recursiveSdkPath = []) {
|
2666
|
+
function unpackRouters(routers, recursiveBasePath = []) {
|
2508
2667
|
return routers.reduce((acc, router) => {
|
2509
2668
|
acc.push({
|
2510
2669
|
fullPath: [...recursiveBasePath, router.basePath].join(""),
|
2511
|
-
sdkPath: [
|
2512
|
-
...recursiveSdkPath,
|
2513
|
-
toPrettyCamelCase2(router.sdkName ?? router.basePath)
|
2514
|
-
].join("."),
|
2515
2670
|
router
|
2516
2671
|
});
|
2517
2672
|
acc.push(
|
2518
|
-
...unpackRouters(
|
2519
|
-
|
2520
|
-
|
2521
|
-
|
2522
|
-
...recursiveSdkPath,
|
2523
|
-
toPrettyCamelCase2(router.sdkName ?? router.basePath)
|
2524
|
-
]
|
2525
|
-
)
|
2673
|
+
...unpackRouters(router.routers, [
|
2674
|
+
...recursiveBasePath,
|
2675
|
+
router.basePath
|
2676
|
+
])
|
2526
2677
|
);
|
2527
2678
|
return acc;
|
2528
2679
|
}, []);
|
2529
2680
|
}
|
2530
2681
|
|
2531
2682
|
// src/http/mcpGenerator/mcpGenerator.ts
|
2683
|
+
function generateInputSchema(schemaValidator, body, params, query, requestHeaders, auth) {
|
2684
|
+
let discriminatedBody;
|
2685
|
+
if (body) {
|
2686
|
+
discriminatedBody = discriminateBody(schemaValidator, body);
|
2687
|
+
}
|
2688
|
+
return schemaValidator.schemify({
|
2689
|
+
...discriminatedBody && body ? {
|
2690
|
+
..."contentType" in body ? { contentType: body.contentType } : {},
|
2691
|
+
body: schemaValidator.schemify(discriminatedBody.schema)
|
2692
|
+
} : {},
|
2693
|
+
...params ? { params: schemaValidator.schemify(params) } : {},
|
2694
|
+
...query ? { query: schemaValidator.schemify(query) } : {},
|
2695
|
+
...requestHeaders ? {
|
2696
|
+
headers: schemaValidator.schemify({
|
2697
|
+
...requestHeaders,
|
2698
|
+
...auth ? {
|
2699
|
+
[auth.headerName ?? "authorization"]: string.startsWith(
|
2700
|
+
auth.tokenPrefix ?? ("basic" in auth ? "Basic " : "Bearer ")
|
2701
|
+
)
|
2702
|
+
} : {}
|
2703
|
+
})
|
2704
|
+
} : {}
|
2705
|
+
});
|
2706
|
+
}
|
2532
2707
|
function generateMcpServer(schemaValidator, protocol, host, port, version, routers, options2, contentTypeMap) {
|
2533
2708
|
if (!(schemaValidator instanceof ZodSchemaValidator)) {
|
2534
2709
|
throw new Error(
|
@@ -2542,35 +2717,36 @@ function generateMcpServer(schemaValidator, protocol, host, port, version, route
|
|
2542
2717
|
});
|
2543
2718
|
unpackRouters(routers).forEach(({ fullPath, router }) => {
|
2544
2719
|
router.routes.forEach((route) => {
|
2545
|
-
|
2546
|
-
if (
|
2547
|
-
|
2548
|
-
|
2549
|
-
|
2720
|
+
const inputSchemas = [];
|
2721
|
+
if (route.contractDetails.versions) {
|
2722
|
+
Object.values(route.contractDetails.versions).forEach((version2) => {
|
2723
|
+
inputSchemas.push(
|
2724
|
+
generateInputSchema(
|
2725
|
+
schemaValidator,
|
2726
|
+
version2.body,
|
2727
|
+
route.contractDetails.params,
|
2728
|
+
version2.query,
|
2729
|
+
version2.requestHeaders,
|
2730
|
+
route.contractDetails.auth
|
2731
|
+
)
|
2732
|
+
);
|
2733
|
+
});
|
2734
|
+
} else {
|
2735
|
+
inputSchemas.push(
|
2736
|
+
generateInputSchema(
|
2737
|
+
schemaValidator,
|
2738
|
+
route.contractDetails.body,
|
2739
|
+
route.contractDetails.params,
|
2740
|
+
route.contractDetails.query,
|
2741
|
+
route.contractDetails.requestHeaders,
|
2742
|
+
route.contractDetails.auth
|
2743
|
+
)
|
2550
2744
|
);
|
2551
2745
|
}
|
2552
|
-
const inputSchema = schemaValidator.schemify({
|
2553
|
-
...discriminatedBody && "body" in route.contractDetails ? {
|
2554
|
-
..."contentType" in route.contractDetails.body ? { contentType: route.contractDetails.body.contentType } : {},
|
2555
|
-
body: schemaValidator.schemify(discriminatedBody.schema)
|
2556
|
-
} : {},
|
2557
|
-
...route.contractDetails.params ? { params: schemaValidator.schemify(route.contractDetails.params) } : {},
|
2558
|
-
...route.contractDetails.query ? { query: schemaValidator.schemify(route.contractDetails.query) } : {},
|
2559
|
-
...route.contractDetails.requestHeaders ? {
|
2560
|
-
headers: schemaValidator.schemify({
|
2561
|
-
...route.contractDetails.requestHeaders,
|
2562
|
-
...route.contractDetails.auth ? {
|
2563
|
-
[route.contractDetails.auth.headerName ?? "authorization"]: string.startsWith(
|
2564
|
-
route.contractDetails.auth.tokenPrefix ?? ("basic" in route.contractDetails.auth ? "Basic " : "Bearer ")
|
2565
|
-
)
|
2566
|
-
} : {}
|
2567
|
-
})
|
2568
|
-
} : {}
|
2569
|
-
});
|
2570
2746
|
mcpServer.addTool({
|
2571
2747
|
name: route.contractDetails.name,
|
2572
2748
|
description: route.contractDetails.summary,
|
2573
|
-
parameters:
|
2749
|
+
parameters: isUnionable(inputSchemas) ? schemaValidator.union(inputSchemas) : inputSchemas[0],
|
2574
2750
|
execute: async (args) => {
|
2575
2751
|
const { contentType, body, params, query, headers } = args;
|
2576
2752
|
let url = `${protocol}://${host}:${port}${fullPath}${route.path}`;
|
@@ -2582,6 +2758,22 @@ function generateMcpServer(schemaValidator, protocol, host, port, version, route
|
|
2582
2758
|
);
|
2583
2759
|
}
|
2584
2760
|
}
|
2761
|
+
let bodySchema;
|
2762
|
+
let responsesSchemas;
|
2763
|
+
if (route.contractDetails.versions) {
|
2764
|
+
Object.values(route.contractDetails.versions).forEach(
|
2765
|
+
(version2, index) => {
|
2766
|
+
if (version2.body && schemaValidator.parse(inputSchemas[index], args).ok) {
|
2767
|
+
bodySchema = version2.body;
|
2768
|
+
responsesSchemas = version2.responses;
|
2769
|
+
}
|
2770
|
+
}
|
2771
|
+
);
|
2772
|
+
} else {
|
2773
|
+
bodySchema = route.contractDetails.body;
|
2774
|
+
responsesSchemas = route.contractDetails.responses;
|
2775
|
+
}
|
2776
|
+
const discriminatedBody = bodySchema ? discriminateBody(schemaValidator, bodySchema) : void 0;
|
2585
2777
|
let parsedBody;
|
2586
2778
|
if (discriminatedBody) {
|
2587
2779
|
switch (discriminatedBody.parserType) {
|
@@ -2599,7 +2791,7 @@ function generateMcpServer(schemaValidator, protocol, host, port, version, route
|
|
2599
2791
|
}
|
2600
2792
|
case "multipart": {
|
2601
2793
|
const formData = new FormData();
|
2602
|
-
if (
|
2794
|
+
if (isRecord3(body)) {
|
2603
2795
|
for (const key in body) {
|
2604
2796
|
if (typeof body[key] === "string" || body[key] instanceof Blob) {
|
2605
2797
|
formData.append(key, body[key]);
|
@@ -2614,7 +2806,7 @@ function generateMcpServer(schemaValidator, protocol, host, port, version, route
|
|
2614
2806
|
break;
|
2615
2807
|
}
|
2616
2808
|
case "urlEncoded": {
|
2617
|
-
if (
|
2809
|
+
if (isRecord3(body)) {
|
2618
2810
|
parsedBody = new URLSearchParams(
|
2619
2811
|
Object.entries(body).map(([key, value]) => [
|
2620
2812
|
key,
|
@@ -2657,9 +2849,12 @@ function generateMcpServer(schemaValidator, protocol, host, port, version, route
|
|
2657
2849
|
`Error received while proxying request to ${url}: ${await response.text()}`
|
2658
2850
|
);
|
2659
2851
|
}
|
2852
|
+
if (!responsesSchemas) {
|
2853
|
+
throw new Error("No responses schemas found");
|
2854
|
+
}
|
2660
2855
|
const contractContentType = discriminateResponseBodies(
|
2661
2856
|
schemaValidator,
|
2662
|
-
|
2857
|
+
responsesSchemas
|
2663
2858
|
)[response.status].contentType;
|
2664
2859
|
switch (contentTypeMap && contentTypeMap[contractContentType] ? contentTypeMap[contractContentType] : contractContentType) {
|
2665
2860
|
case "application/json":
|
@@ -2715,15 +2910,56 @@ function generateMcpServer(schemaValidator, protocol, host, port, version, route
|
|
2715
2910
|
import {
|
2716
2911
|
prettyPrintParseErrors as prettyPrintParseErrors2
|
2717
2912
|
} from "@forklaunch/validator";
|
2913
|
+
|
2914
|
+
// src/http/guards/isResponseCompiledSchema.ts
|
2915
|
+
function isResponseCompiledSchema(schema) {
|
2916
|
+
return typeof schema === "object" && schema !== null && "responses" in schema;
|
2917
|
+
}
|
2918
|
+
|
2919
|
+
// src/http/middleware/response/parse.middleware.ts
|
2718
2920
|
function parse2(req, res, next) {
|
2719
|
-
|
2921
|
+
let headers;
|
2922
|
+
let responses;
|
2923
|
+
const responseSchemas = res.responseSchemas;
|
2924
|
+
const schemaValidator = req.schemaValidator;
|
2925
|
+
if (!isResponseCompiledSchema(responseSchemas)) {
|
2926
|
+
const parsedVersions = req._parsedVersions;
|
2927
|
+
if (typeof parsedVersions === "number") {
|
2928
|
+
throw new Error("Request failed to parse given version map");
|
2929
|
+
}
|
2930
|
+
const mappedHeaderSchemas = parsedVersions.map(
|
2931
|
+
(version) => responseSchemas[version].headers
|
2932
|
+
);
|
2933
|
+
const mappedResponseSchemas = parsedVersions.map(
|
2934
|
+
(version) => responseSchemas[version].responses
|
2935
|
+
);
|
2936
|
+
const collapsedResponseSchemas = mappedResponseSchemas.reduce((acc, responseSchema) => {
|
2937
|
+
Object.entries(responseSchema).forEach(([status, schema]) => {
|
2938
|
+
if (!acc[Number(status)]) {
|
2939
|
+
acc[Number(status)] = [];
|
2940
|
+
}
|
2941
|
+
acc[Number(status)].push(schema);
|
2942
|
+
});
|
2943
|
+
return acc;
|
2944
|
+
}, {});
|
2945
|
+
headers = schemaValidator.union(mappedHeaderSchemas);
|
2946
|
+
responses = Object.fromEntries(
|
2947
|
+
Object.entries(collapsedResponseSchemas).map(([status, schemas]) => [
|
2948
|
+
status,
|
2949
|
+
schemaValidator.union(schemas)
|
2950
|
+
])
|
2951
|
+
);
|
2952
|
+
} else {
|
2953
|
+
headers = responseSchemas.headers;
|
2954
|
+
responses = responseSchemas.responses;
|
2955
|
+
}
|
2720
2956
|
const statusCode = Number(res.statusCode);
|
2721
|
-
const parsedResponse =
|
2957
|
+
const parsedResponse = schemaValidator.parse(
|
2722
2958
|
responses?.[statusCode],
|
2723
2959
|
res.bodyData
|
2724
2960
|
);
|
2725
|
-
const parsedHeaders =
|
2726
|
-
headers ??
|
2961
|
+
const parsedHeaders = schemaValidator.parse(
|
2962
|
+
headers ?? schemaValidator.unknown,
|
2727
2963
|
res.getHeaders()
|
2728
2964
|
);
|
2729
2965
|
const parseErrors = [];
|
@@ -2779,7 +3015,7 @@ import {
|
|
2779
3015
|
isAsyncGenerator,
|
2780
3016
|
isNever as isNever4,
|
2781
3017
|
isNodeJsWriteableStream,
|
2782
|
-
isRecord as
|
3018
|
+
isRecord as isRecord4,
|
2783
3019
|
readableStreamToAsyncIterable,
|
2784
3020
|
safeStringify as safeStringify3
|
2785
3021
|
} from "@forklaunch/common";
|
@@ -2822,9 +3058,21 @@ function enrichExpressLikeSend(instance, req, res, originalOperation, originalSe
|
|
2822
3058
|
originalSend.call(instance, "Not Found");
|
2823
3059
|
errorSent = true;
|
2824
3060
|
}
|
3061
|
+
let responses;
|
3062
|
+
if (req.contractDetails.responses == null && (req.contractDetails.versions == null || Object.values(req.contractDetails.versions).some(
|
3063
|
+
(version) => version?.responses == null
|
3064
|
+
))) {
|
3065
|
+
throw new Error("Responses schema definitions are required");
|
3066
|
+
} else {
|
3067
|
+
if (req.contractDetails.responses != null) {
|
3068
|
+
responses = req.contractDetails.responses;
|
3069
|
+
} else {
|
3070
|
+
responses = req.contractDetails.versions[req.version].responses;
|
3071
|
+
}
|
3072
|
+
}
|
2825
3073
|
const responseBodies = discriminateResponseBodies(
|
2826
3074
|
req.schemaValidator,
|
2827
|
-
|
3075
|
+
responses
|
2828
3076
|
);
|
2829
3077
|
if (responseBodies != null && responseBodies[Number(res.statusCode)] != null) {
|
2830
3078
|
res.type(responseBodies[Number(res.statusCode)].contentType);
|
@@ -2893,7 +3141,7 @@ ${res.locals.errorMessage}`;
|
|
2893
3141
|
} else {
|
2894
3142
|
const parserType = responseBodies?.[Number(res.statusCode)]?.parserType;
|
2895
3143
|
res.bodyData = data;
|
2896
|
-
if (
|
3144
|
+
if (isRecord4(data)) {
|
2897
3145
|
switch (parserType) {
|
2898
3146
|
case "json":
|
2899
3147
|
res.bodyData = "json" in data ? data.json : data;
|
@@ -2950,7 +3198,8 @@ ${res.locals.errorMessage}`;
|
|
2950
3198
|
}
|
2951
3199
|
|
2952
3200
|
// src/http/openApiV3Generator/openApiV3Generator.ts
|
2953
|
-
import { openApiCompliantPath
|
3201
|
+
import { openApiCompliantPath } from "@forklaunch/common";
|
3202
|
+
var OPENAPI_DEFAULT_VERSION = Symbol("default");
|
2954
3203
|
function toUpperCase(str) {
|
2955
3204
|
return str.charAt(0).toUpperCase() + str.slice(1);
|
2956
3205
|
}
|
@@ -2960,25 +3209,50 @@ function transformBasePath(basePath) {
|
|
2960
3209
|
}
|
2961
3210
|
return `/${basePath}`;
|
2962
3211
|
}
|
2963
|
-
function generateOpenApiDocument(protocol, host, port,
|
3212
|
+
function generateOpenApiDocument(protocol, host, port, versionedTags, versionedPaths, versionedSecuritySchemes, otherServers) {
|
2964
3213
|
return {
|
2965
|
-
|
2966
|
-
|
2967
|
-
|
2968
|
-
|
2969
|
-
|
2970
|
-
|
2971
|
-
|
2972
|
-
|
2973
|
-
tags,
|
2974
|
-
servers: [
|
2975
|
-
{
|
2976
|
-
url: `${protocol}://${host}:${port}`,
|
2977
|
-
description: "Main Server"
|
3214
|
+
[OPENAPI_DEFAULT_VERSION]: {
|
3215
|
+
openapi: "3.1.0",
|
3216
|
+
info: {
|
3217
|
+
title: process.env.API_TITLE || "API Definition",
|
3218
|
+
version: process.env.VERSION || "latest"
|
3219
|
+
},
|
3220
|
+
components: {
|
3221
|
+
securitySchemes: versionedSecuritySchemes[OPENAPI_DEFAULT_VERSION]
|
2978
3222
|
},
|
2979
|
-
|
2980
|
-
|
2981
|
-
|
3223
|
+
tags: versionedTags[OPENAPI_DEFAULT_VERSION],
|
3224
|
+
servers: [
|
3225
|
+
{
|
3226
|
+
url: `${protocol}://${host}:${port}`,
|
3227
|
+
description: "Main Server"
|
3228
|
+
},
|
3229
|
+
...otherServers || []
|
3230
|
+
],
|
3231
|
+
paths: versionedPaths[OPENAPI_DEFAULT_VERSION]
|
3232
|
+
},
|
3233
|
+
...Object.fromEntries(
|
3234
|
+
Object.entries(versionedPaths).map(([version, paths]) => [
|
3235
|
+
version,
|
3236
|
+
{
|
3237
|
+
openapi: "3.1.0",
|
3238
|
+
info: {
|
3239
|
+
title: process.env.API_TITLE || "API Definition",
|
3240
|
+
version
|
3241
|
+
},
|
3242
|
+
components: {
|
3243
|
+
securitySchemes: versionedSecuritySchemes[version]
|
3244
|
+
},
|
3245
|
+
tags: versionedTags[version],
|
3246
|
+
servers: [
|
3247
|
+
{
|
3248
|
+
url: `${protocol}://${host}:${port}`,
|
3249
|
+
description: "Main Server"
|
3250
|
+
}
|
3251
|
+
],
|
3252
|
+
paths
|
3253
|
+
}
|
3254
|
+
])
|
3255
|
+
)
|
2982
3256
|
};
|
2983
3257
|
}
|
2984
3258
|
function contentResolver(schemaValidator, body, contentType) {
|
@@ -2997,143 +3271,232 @@ function contentResolver(schemaValidator, body, contentType) {
|
|
2997
3271
|
}
|
2998
3272
|
};
|
2999
3273
|
}
|
3000
|
-
function
|
3001
|
-
const
|
3002
|
-
const
|
3003
|
-
const
|
3004
|
-
|
3274
|
+
function generateOperationObject(schemaValidator, path, method, controllerName, sdkPaths, securitySchemes, name, summary, responses, params, responseHeaders, requestHeaders, query, body, auth) {
|
3275
|
+
const typedSchemaValidator = schemaValidator;
|
3276
|
+
const coercedResponses = {};
|
3277
|
+
const discriminatedResponseBodiesResult = discriminateResponseBodies(
|
3278
|
+
schemaValidator,
|
3279
|
+
responses
|
3280
|
+
);
|
3281
|
+
for (const key in discriminatedResponseBodiesResult) {
|
3282
|
+
coercedResponses[key] = {
|
3283
|
+
description: httpStatusCodes_default[key],
|
3284
|
+
content: contentResolver(
|
3285
|
+
schemaValidator,
|
3286
|
+
discriminatedResponseBodiesResult[key].schema,
|
3287
|
+
discriminatedResponseBodiesResult[key].contentType
|
3288
|
+
),
|
3289
|
+
headers: responseHeaders ? Object.fromEntries(
|
3290
|
+
Object.entries(responseHeaders).map(([key2, value]) => [
|
3291
|
+
key2,
|
3292
|
+
{
|
3293
|
+
schema: typedSchemaValidator.openapi(value)
|
3294
|
+
}
|
3295
|
+
])
|
3296
|
+
) : void 0
|
3297
|
+
};
|
3298
|
+
}
|
3299
|
+
const commonErrors = [400, 404, 500];
|
3300
|
+
for (const error of commonErrors) {
|
3301
|
+
if (!(error in responses)) {
|
3302
|
+
coercedResponses[error] = {
|
3303
|
+
description: httpStatusCodes_default[error],
|
3304
|
+
content: contentResolver(schemaValidator, schemaValidator.string)
|
3305
|
+
};
|
3306
|
+
}
|
3307
|
+
}
|
3308
|
+
const operationObject = {
|
3309
|
+
tags: [controllerName],
|
3310
|
+
summary: `${name}: ${summary}`,
|
3311
|
+
parameters: [],
|
3312
|
+
responses: coercedResponses,
|
3313
|
+
operationId: sdkPaths[[method, path].join(".")]
|
3314
|
+
};
|
3315
|
+
if (params) {
|
3316
|
+
for (const key in params) {
|
3317
|
+
operationObject.parameters?.push({
|
3318
|
+
name: key,
|
3319
|
+
in: "path",
|
3320
|
+
schema: typedSchemaValidator.openapi(params[key])
|
3321
|
+
});
|
3322
|
+
}
|
3323
|
+
}
|
3324
|
+
const discriminatedBodyResult = body ? discriminateBody(schemaValidator, body) : null;
|
3325
|
+
if (discriminatedBodyResult) {
|
3326
|
+
operationObject.requestBody = {
|
3327
|
+
required: true,
|
3328
|
+
content: contentResolver(
|
3329
|
+
schemaValidator,
|
3330
|
+
discriminatedBodyResult.schema,
|
3331
|
+
discriminatedBodyResult.contentType
|
3332
|
+
)
|
3333
|
+
};
|
3334
|
+
}
|
3335
|
+
if (requestHeaders) {
|
3336
|
+
for (const key in requestHeaders) {
|
3337
|
+
operationObject.parameters?.push({
|
3338
|
+
name: key,
|
3339
|
+
in: "header",
|
3340
|
+
schema: typedSchemaValidator.openapi(requestHeaders[key])
|
3341
|
+
});
|
3342
|
+
}
|
3343
|
+
}
|
3344
|
+
if (query) {
|
3345
|
+
for (const key in query) {
|
3346
|
+
operationObject.parameters?.push({
|
3347
|
+
name: key,
|
3348
|
+
in: "query",
|
3349
|
+
schema: typedSchemaValidator.openapi(query[key])
|
3350
|
+
});
|
3351
|
+
}
|
3352
|
+
}
|
3353
|
+
if (auth) {
|
3354
|
+
responses[401] = {
|
3355
|
+
description: httpStatusCodes_default[401],
|
3356
|
+
content: contentResolver(schemaValidator, schemaValidator.string)
|
3357
|
+
};
|
3358
|
+
responses[403] = {
|
3359
|
+
description: httpStatusCodes_default[403],
|
3360
|
+
content: contentResolver(schemaValidator, schemaValidator.string)
|
3361
|
+
};
|
3362
|
+
if ("basic" in auth) {
|
3363
|
+
operationObject.security = [
|
3364
|
+
{
|
3365
|
+
basic: Array.from(
|
3366
|
+
"allowedPermissions" in auth ? auth.allowedPermissions?.values() || [] : []
|
3367
|
+
)
|
3368
|
+
}
|
3369
|
+
];
|
3370
|
+
securitySchemes["basic"] = {
|
3371
|
+
type: "http",
|
3372
|
+
scheme: "basic"
|
3373
|
+
};
|
3374
|
+
} else if (auth) {
|
3375
|
+
operationObject.security = [
|
3376
|
+
{
|
3377
|
+
[auth.headerName !== "Authorization" ? "bearer" : "apiKey"]: Array.from(
|
3378
|
+
"allowedPermissions" in auth ? auth.allowedPermissions?.values() || [] : []
|
3379
|
+
)
|
3380
|
+
}
|
3381
|
+
];
|
3382
|
+
if (auth.headerName && auth.headerName !== "Authorization") {
|
3383
|
+
securitySchemes[auth.headerName] = {
|
3384
|
+
type: "apiKey",
|
3385
|
+
in: "header",
|
3386
|
+
name: auth.headerName
|
3387
|
+
};
|
3388
|
+
} else {
|
3389
|
+
securitySchemes["Authorization"] = {
|
3390
|
+
type: "http",
|
3391
|
+
scheme: "bearer",
|
3392
|
+
bearerFormat: "JWT"
|
3393
|
+
};
|
3394
|
+
}
|
3395
|
+
}
|
3396
|
+
}
|
3397
|
+
return operationObject;
|
3398
|
+
}
|
3399
|
+
function generateOpenApiSpecs(schemaValidator, protocol, host, port, routers, otherServers) {
|
3400
|
+
const versionedPaths = {
|
3401
|
+
[OPENAPI_DEFAULT_VERSION]: {}
|
3402
|
+
};
|
3403
|
+
const versionedTags = {
|
3404
|
+
[OPENAPI_DEFAULT_VERSION]: []
|
3405
|
+
};
|
3406
|
+
const versionedSecuritySchemes = {
|
3407
|
+
[OPENAPI_DEFAULT_VERSION]: {}
|
3408
|
+
};
|
3409
|
+
unpackRouters(routers).forEach(({ fullPath, router }) => {
|
3005
3410
|
const controllerName = transformBasePath(fullPath);
|
3006
|
-
tags.push({
|
3007
|
-
name: controllerName,
|
3008
|
-
description: `${toUpperCase(controllerName)} Operations`
|
3009
|
-
});
|
3010
3411
|
router.routes.forEach((route) => {
|
3011
3412
|
const openApiPath = openApiCompliantPath(
|
3012
3413
|
`${fullPath}${route.path === "/" ? "" : route.path}`
|
3013
3414
|
);
|
3014
|
-
|
3015
|
-
|
3016
|
-
|
3017
|
-
|
3018
|
-
|
3019
|
-
|
3020
|
-
|
3021
|
-
|
3022
|
-
|
3023
|
-
|
3024
|
-
|
3025
|
-
|
3026
|
-
|
3415
|
+
const { name, summary, params, versions, auth } = route.contractDetails;
|
3416
|
+
if (versions) {
|
3417
|
+
for (const version of Object.keys(versions)) {
|
3418
|
+
if (!versionedPaths[version]) {
|
3419
|
+
versionedPaths[version] = {};
|
3420
|
+
}
|
3421
|
+
if (!versionedPaths[version][openApiPath]) {
|
3422
|
+
versionedPaths[version][openApiPath] = {};
|
3423
|
+
}
|
3424
|
+
if (!versionedTags[version]) {
|
3425
|
+
versionedTags[version] = [];
|
3426
|
+
}
|
3427
|
+
if (!versionedTags[version].find((tag) => tag.name === controllerName)) {
|
3428
|
+
versionedTags[version].push({
|
3429
|
+
name: controllerName,
|
3430
|
+
description: `${toUpperCase(controllerName)} Operations`
|
3431
|
+
});
|
3432
|
+
}
|
3433
|
+
if (!versionedSecuritySchemes[version]) {
|
3434
|
+
versionedSecuritySchemes[version] = {};
|
3435
|
+
}
|
3436
|
+
const { query, requestHeaders, body, responses, responseHeaders } = versions[version];
|
3437
|
+
const operationObject = generateOperationObject(
|
3027
3438
|
schemaValidator,
|
3028
|
-
|
3029
|
-
|
3030
|
-
|
3031
|
-
|
3032
|
-
|
3033
|
-
|
3034
|
-
|
3035
|
-
|
3036
|
-
|
3037
|
-
|
3038
|
-
|
3039
|
-
|
3439
|
+
route.path,
|
3440
|
+
route.method,
|
3441
|
+
controllerName,
|
3442
|
+
router.sdkPaths,
|
3443
|
+
versionedSecuritySchemes[version],
|
3444
|
+
name,
|
3445
|
+
summary,
|
3446
|
+
responses,
|
3447
|
+
params,
|
3448
|
+
responseHeaders,
|
3449
|
+
requestHeaders,
|
3450
|
+
query,
|
3451
|
+
body,
|
3452
|
+
auth
|
3453
|
+
);
|
3454
|
+
if (route.method !== "middleware") {
|
3455
|
+
versionedPaths[version][openApiPath][route.method] = operationObject;
|
3456
|
+
}
|
3040
3457
|
}
|
3041
|
-
}
|
3042
|
-
|
3043
|
-
|
3044
|
-
summary: `${name}: ${summary}`,
|
3045
|
-
parameters: [],
|
3046
|
-
responses,
|
3047
|
-
operationId: `${sdkPath}.${toPrettyCamelCase3(name)}`
|
3048
|
-
};
|
3049
|
-
if (route.contractDetails.params) {
|
3050
|
-
for (const key in route.contractDetails.params) {
|
3051
|
-
operationObject.parameters?.push({
|
3052
|
-
name: key,
|
3053
|
-
in: "path",
|
3054
|
-
schema: schemaValidator.openapi(
|
3055
|
-
route.contractDetails.params[key]
|
3056
|
-
)
|
3057
|
-
});
|
3458
|
+
} else {
|
3459
|
+
if (!versionedPaths[OPENAPI_DEFAULT_VERSION]) {
|
3460
|
+
versionedPaths[OPENAPI_DEFAULT_VERSION] = {};
|
3058
3461
|
}
|
3059
|
-
|
3060
|
-
|
3061
|
-
if (discriminatedBodyResult) {
|
3062
|
-
operationObject.requestBody = {
|
3063
|
-
required: true,
|
3064
|
-
content: contentResolver(
|
3065
|
-
schemaValidator,
|
3066
|
-
discriminatedBodyResult.schema,
|
3067
|
-
discriminatedBodyResult.contentType
|
3068
|
-
)
|
3069
|
-
};
|
3070
|
-
}
|
3071
|
-
if (requestHeaders) {
|
3072
|
-
for (const key in requestHeaders) {
|
3073
|
-
operationObject.parameters?.push({
|
3074
|
-
name: key,
|
3075
|
-
in: "header",
|
3076
|
-
schema: schemaValidator.openapi(
|
3077
|
-
requestHeaders[key]
|
3078
|
-
)
|
3079
|
-
});
|
3462
|
+
if (!versionedPaths[OPENAPI_DEFAULT_VERSION][openApiPath]) {
|
3463
|
+
versionedPaths[OPENAPI_DEFAULT_VERSION][openApiPath] = {};
|
3080
3464
|
}
|
3081
|
-
|
3082
|
-
|
3083
|
-
|
3084
|
-
|
3085
|
-
|
3086
|
-
|
3087
|
-
|
3465
|
+
if (!versionedTags[OPENAPI_DEFAULT_VERSION]) {
|
3466
|
+
versionedTags[OPENAPI_DEFAULT_VERSION] = [];
|
3467
|
+
}
|
3468
|
+
if (!versionedTags[OPENAPI_DEFAULT_VERSION].find(
|
3469
|
+
(tag) => tag.name === controllerName
|
3470
|
+
)) {
|
3471
|
+
versionedTags[OPENAPI_DEFAULT_VERSION].push({
|
3472
|
+
name: controllerName,
|
3473
|
+
description: `${toUpperCase(controllerName)} Operations`
|
3088
3474
|
});
|
3089
3475
|
}
|
3090
|
-
|
3091
|
-
|
3092
|
-
|
3093
|
-
|
3094
|
-
|
3095
|
-
|
3096
|
-
|
3097
|
-
|
3098
|
-
|
3099
|
-
|
3100
|
-
|
3101
|
-
|
3102
|
-
|
3103
|
-
|
3104
|
-
|
3105
|
-
|
3106
|
-
|
3107
|
-
|
3108
|
-
|
3109
|
-
|
3110
|
-
|
3111
|
-
|
3112
|
-
|
3113
|
-
operationObject.security = [
|
3114
|
-
{
|
3115
|
-
[route.contractDetails.auth.headerName !== "Authorization" ? "bearer" : "apiKey"]: Array.from(
|
3116
|
-
"allowedPermissions" in route.contractDetails.auth ? route.contractDetails.auth.allowedPermissions?.values() || [] : []
|
3117
|
-
)
|
3118
|
-
}
|
3119
|
-
];
|
3120
|
-
if (route.contractDetails.auth.headerName && route.contractDetails.auth.headerName !== "Authorization") {
|
3121
|
-
securitySchemes[route.contractDetails.auth.headerName] = {
|
3122
|
-
type: "apiKey",
|
3123
|
-
in: "header",
|
3124
|
-
name: route.contractDetails.auth.headerName
|
3125
|
-
};
|
3126
|
-
} else {
|
3127
|
-
securitySchemes["Authorization"] = {
|
3128
|
-
type: "http",
|
3129
|
-
scheme: "bearer",
|
3130
|
-
bearerFormat: "JWT"
|
3131
|
-
};
|
3132
|
-
}
|
3476
|
+
if (!versionedSecuritySchemes[OPENAPI_DEFAULT_VERSION]) {
|
3477
|
+
versionedSecuritySchemes[OPENAPI_DEFAULT_VERSION] = {};
|
3478
|
+
}
|
3479
|
+
const { query, requestHeaders, body, responses, responseHeaders } = route.contractDetails;
|
3480
|
+
const operationObject = generateOperationObject(
|
3481
|
+
schemaValidator,
|
3482
|
+
route.path,
|
3483
|
+
route.method,
|
3484
|
+
controllerName,
|
3485
|
+
router.sdkPaths,
|
3486
|
+
versionedSecuritySchemes[OPENAPI_DEFAULT_VERSION],
|
3487
|
+
name,
|
3488
|
+
summary,
|
3489
|
+
responses,
|
3490
|
+
params,
|
3491
|
+
responseHeaders,
|
3492
|
+
requestHeaders,
|
3493
|
+
query,
|
3494
|
+
body,
|
3495
|
+
auth
|
3496
|
+
);
|
3497
|
+
if (route.method !== "middleware") {
|
3498
|
+
versionedPaths[OPENAPI_DEFAULT_VERSION][openApiPath][route.method] = operationObject;
|
3133
3499
|
}
|
3134
|
-
}
|
3135
|
-
if (route.method !== "middleware") {
|
3136
|
-
paths[openApiPath][route.method] = operationObject;
|
3137
3500
|
}
|
3138
3501
|
});
|
3139
3502
|
});
|
@@ -3141,13 +3504,116 @@ function generateSwaggerDocument(schemaValidator, protocol, host, port, routers,
|
|
3141
3504
|
protocol,
|
3142
3505
|
host,
|
3143
3506
|
port,
|
3144
|
-
|
3145
|
-
|
3146
|
-
|
3507
|
+
versionedTags,
|
3508
|
+
versionedPaths,
|
3509
|
+
versionedSecuritySchemes,
|
3147
3510
|
otherServers
|
3148
3511
|
);
|
3149
3512
|
}
|
3150
3513
|
|
3514
|
+
// src/http/sdk/sdkClient.ts
|
3515
|
+
import { hashString, safeStringify as safeStringify4, toRecord as toRecord2 } from "@forklaunch/common";
|
3516
|
+
|
3517
|
+
// src/http/guards/isSdkRouter.ts
|
3518
|
+
function isSdkRouter(value) {
|
3519
|
+
return typeof value === "object" && value !== null && "sdk" in value && "_fetchMap" in value && "sdkPaths" in value;
|
3520
|
+
}
|
3521
|
+
|
3522
|
+
// src/http/sdk/sdkClient.ts
|
3523
|
+
function mapToSdk(schemaValidator, routerMap, runningPath = void 0) {
|
3524
|
+
const routerUniquenessCache = /* @__PURE__ */ new Set();
|
3525
|
+
return Object.fromEntries(
|
3526
|
+
Object.entries(routerMap).map(([key, value]) => {
|
3527
|
+
if (routerUniquenessCache.has(hashString(safeStringify4(value)))) {
|
3528
|
+
throw new Error(
|
3529
|
+
`SdkClient: Cannot use the same router pointer twice. Please clone the duplicate router with .clone() or only use the router once.`
|
3530
|
+
);
|
3531
|
+
}
|
3532
|
+
routerUniquenessCache.add(hashString(safeStringify4(value)));
|
3533
|
+
const currentPath = runningPath ? [runningPath, key].join(".") : key;
|
3534
|
+
if (isSdkRouter(value)) {
|
3535
|
+
Object.entries(value.sdkPaths).forEach(([routePath, sdkKey]) => {
|
3536
|
+
if ("controllerSdkPaths" in value && Array.isArray(value.controllerSdkPaths) && value.controllerSdkPaths.includes(routePath)) {
|
3537
|
+
value.sdkPaths[routePath] = [currentPath, sdkKey].join(".");
|
3538
|
+
}
|
3539
|
+
});
|
3540
|
+
return [key, value.sdk];
|
3541
|
+
} else {
|
3542
|
+
return [
|
3543
|
+
key,
|
3544
|
+
mapToSdk(
|
3545
|
+
schemaValidator,
|
3546
|
+
value,
|
3547
|
+
runningPath ? [runningPath, key].join(".") : key
|
3548
|
+
)
|
3549
|
+
];
|
3550
|
+
}
|
3551
|
+
})
|
3552
|
+
);
|
3553
|
+
}
|
3554
|
+
function flattenFetchMap(schemaValidator, routerMap) {
|
3555
|
+
const _fetchMap = Object.entries(routerMap).reduce(
|
3556
|
+
(acc, [, value]) => {
|
3557
|
+
if ("_fetchMap" in value) {
|
3558
|
+
return {
|
3559
|
+
...acc,
|
3560
|
+
...value._fetchMap
|
3561
|
+
};
|
3562
|
+
} else {
|
3563
|
+
return {
|
3564
|
+
...acc,
|
3565
|
+
...flattenFetchMap(schemaValidator, value)
|
3566
|
+
};
|
3567
|
+
}
|
3568
|
+
},
|
3569
|
+
{}
|
3570
|
+
);
|
3571
|
+
return _fetchMap;
|
3572
|
+
}
|
3573
|
+
function mapToFetch(schemaValidator, routerMap) {
|
3574
|
+
const flattenedFetchMap = flattenFetchMap(
|
3575
|
+
schemaValidator,
|
3576
|
+
routerMap
|
3577
|
+
);
|
3578
|
+
return (path, ...reqInit) => {
|
3579
|
+
const method = reqInit[0]?.method;
|
3580
|
+
const version = reqInit[0] != null && "version" in reqInit[0] ? reqInit[0].version : void 0;
|
3581
|
+
return (version ? toRecord2(toRecord2(flattenedFetchMap[path])[method ?? "GET"])[version] : toRecord2(flattenedFetchMap[path])[method ?? "GET"])(path, reqInit[0]);
|
3582
|
+
};
|
3583
|
+
}
|
3584
|
+
function sdkClient(schemaValidator, routerMap) {
|
3585
|
+
return {
|
3586
|
+
_finalizedSdk: true,
|
3587
|
+
sdk: mapToSdk(schemaValidator, routerMap),
|
3588
|
+
fetch: mapToFetch(schemaValidator, routerMap)
|
3589
|
+
};
|
3590
|
+
}
|
3591
|
+
|
3592
|
+
// src/http/sdk/sdkRouter.ts
|
3593
|
+
import { toPrettyCamelCase as toPrettyCamelCase2 } from "@forklaunch/common";
|
3594
|
+
function sdkRouter(schemaValidator, controller, router) {
|
3595
|
+
const controllerSdkPaths = [];
|
3596
|
+
const mappedSdk = Object.fromEntries(
|
3597
|
+
Object.entries(controller).map(([key, value]) => {
|
3598
|
+
const sdkPath = [value._method, value._path].join(".");
|
3599
|
+
controllerSdkPaths.push(sdkPath);
|
3600
|
+
router.sdkPaths[sdkPath] = key;
|
3601
|
+
return [
|
3602
|
+
key,
|
3603
|
+
router.sdk[toPrettyCamelCase2(value.contractDetails.name)]
|
3604
|
+
];
|
3605
|
+
})
|
3606
|
+
);
|
3607
|
+
const _fetchMap = router._fetchMap;
|
3608
|
+
return {
|
3609
|
+
sdk: mappedSdk,
|
3610
|
+
fetch: router.fetch,
|
3611
|
+
_fetchMap,
|
3612
|
+
sdkPaths: router.sdkPaths,
|
3613
|
+
controllerSdkPaths
|
3614
|
+
};
|
3615
|
+
}
|
3616
|
+
|
3151
3617
|
// src/http/telemetry/evaluateTelemetryOptions.ts
|
3152
3618
|
function evaluateTelemetryOptions(telemetryOptions) {
|
3153
3619
|
return {
|
@@ -3178,6 +3644,7 @@ export {
|
|
3178
3644
|
ForklaunchExpressLikeApplication,
|
3179
3645
|
ForklaunchExpressLikeRouter,
|
3180
3646
|
HTTPStatuses,
|
3647
|
+
OPENAPI_DEFAULT_VERSION,
|
3181
3648
|
OpenTelemetryCollector,
|
3182
3649
|
delete_,
|
3183
3650
|
discriminateBody,
|
@@ -3185,7 +3652,7 @@ export {
|
|
3185
3652
|
enrichExpressLikeSend,
|
3186
3653
|
evaluateTelemetryOptions,
|
3187
3654
|
generateMcpServer,
|
3188
|
-
|
3655
|
+
generateOpenApiSpecs,
|
3189
3656
|
get,
|
3190
3657
|
getCodeForStatus,
|
3191
3658
|
head,
|
@@ -3208,6 +3675,8 @@ export {
|
|
3208
3675
|
post,
|
3209
3676
|
put,
|
3210
3677
|
recordMetric,
|
3678
|
+
sdkClient,
|
3679
|
+
sdkRouter,
|
3211
3680
|
trace3 as trace,
|
3212
3681
|
typedAuthHandler,
|
3213
3682
|
typedHandler
|