@forklaunch/core 0.16.1 → 0.17.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/{apiDefinition.types-BYizofKE.d.mts → apiDefinition.types--RSi8f6C.d.mts} +9 -5
- package/lib/{apiDefinition.types-BYizofKE.d.ts → apiDefinition.types--RSi8f6C.d.ts} +9 -5
- package/lib/http/index.d.mts +112 -7
- package/lib/http/index.d.ts +112 -7
- package/lib/http/index.js +787 -647
- package/lib/http/index.js.map +1 -1
- package/lib/http/index.mjs +759 -624
- package/lib/http/index.mjs.map +1 -1
- package/lib/mappers/index.d.mts +14 -4
- package/lib/mappers/index.d.ts +14 -4
- package/lib/mappers/index.js +20 -8
- package/lib/mappers/index.js.map +1 -1
- package/lib/mappers/index.mjs +20 -8
- package/lib/mappers/index.mjs.map +1 -1
- package/lib/ws/index.d.mts +1 -1
- package/lib/ws/index.d.ts +1 -1
- package/package.json +32 -32
package/lib/http/index.mjs
CHANGED
|
@@ -22,18 +22,12 @@ function cors(corsOptions) {
|
|
|
22
22
|
// src/http/router/expressLikeRouter.ts
|
|
23
23
|
import {
|
|
24
24
|
hashString,
|
|
25
|
-
|
|
26
|
-
safeStringify as safeStringify2,
|
|
25
|
+
safeStringify as safeStringify3,
|
|
27
26
|
sanitizePathSlashes,
|
|
28
27
|
toPrettyCamelCase,
|
|
29
28
|
toRecord
|
|
30
29
|
} from "@forklaunch/common";
|
|
31
30
|
|
|
32
|
-
// src/http/guards/hasVersionedSchema.ts
|
|
33
|
-
function hasVersionedSchema(contractDetails) {
|
|
34
|
-
return typeof contractDetails === "object" && contractDetails !== null && "versions" in contractDetails && contractDetails.versions !== null;
|
|
35
|
-
}
|
|
36
|
-
|
|
37
31
|
// src/http/guards/isForklaunchRouter.ts
|
|
38
32
|
function isForklaunchRouter(maybeForklaunchRouter) {
|
|
39
33
|
return maybeForklaunchRouter != null && typeof maybeForklaunchRouter === "object" && "basePath" in maybeForklaunchRouter && "routes" in maybeForklaunchRouter && Array.isArray(maybeForklaunchRouter.routes);
|
|
@@ -62,6 +56,197 @@ function isForklaunchExpressLikeRouter(maybeForklaunchExpressLikeRouter) {
|
|
|
62
56
|
) && "basePath" in maybeForklaunchExpressLikeRouter && "internal" in maybeForklaunchExpressLikeRouter;
|
|
63
57
|
}
|
|
64
58
|
|
|
59
|
+
// src/http/guards/isSdkHandler.ts
|
|
60
|
+
function isSdkHandler(handler) {
|
|
61
|
+
return typeof handler === "object" && handler !== null && "_path" in handler && "_method" in handler && "contractDetails" in handler;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// src/http/guards/isTypedHandler.ts
|
|
65
|
+
function isTypedHandler(maybeTypedHandler) {
|
|
66
|
+
return maybeTypedHandler != null && typeof maybeTypedHandler === "object" && "_typedHandler" in maybeTypedHandler && maybeTypedHandler._typedHandler === true;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// src/http/middleware/request/createContext.middleware.ts
|
|
70
|
+
import { getEnvVar } from "@forklaunch/common";
|
|
71
|
+
import { context, trace } from "@opentelemetry/api";
|
|
72
|
+
import { v4 } from "uuid";
|
|
73
|
+
|
|
74
|
+
// src/http/telemetry/constants.ts
|
|
75
|
+
import {
|
|
76
|
+
ATTR_HTTP_REQUEST_METHOD,
|
|
77
|
+
ATTR_HTTP_RESPONSE_STATUS_CODE,
|
|
78
|
+
ATTR_HTTP_ROUTE,
|
|
79
|
+
ATTR_SERVICE_NAME
|
|
80
|
+
} from "@opentelemetry/semantic-conventions";
|
|
81
|
+
var ATTR_API_NAME = "api.name";
|
|
82
|
+
var ATTR_CORRELATION_ID = "correlation.id";
|
|
83
|
+
var ATTR_APPLICATION_ID = "application_id";
|
|
84
|
+
|
|
85
|
+
// src/http/middleware/request/createContext.middleware.ts
|
|
86
|
+
function createContext(schemaValidator) {
|
|
87
|
+
return function setContext(req, res, next) {
|
|
88
|
+
req.schemaValidator = schemaValidator;
|
|
89
|
+
let correlationId = v4();
|
|
90
|
+
if (req.headers["x-correlation-id"]) {
|
|
91
|
+
correlationId = req.headers["x-correlation-id"];
|
|
92
|
+
}
|
|
93
|
+
res.setHeader("x-correlation-id", correlationId);
|
|
94
|
+
req.context = {
|
|
95
|
+
correlationId
|
|
96
|
+
};
|
|
97
|
+
const span = trace.getSpan(context.active());
|
|
98
|
+
if (span != null) {
|
|
99
|
+
req.context.span = span;
|
|
100
|
+
req.context.span?.setAttribute(ATTR_CORRELATION_ID, correlationId);
|
|
101
|
+
req.context.span?.setAttribute(
|
|
102
|
+
ATTR_SERVICE_NAME,
|
|
103
|
+
getEnvVar("OTEL_SERVICE_NAME")
|
|
104
|
+
);
|
|
105
|
+
}
|
|
106
|
+
next?.();
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// src/http/router/discriminateBody.ts
|
|
111
|
+
function discriminateBody(schemaValidator, body) {
|
|
112
|
+
if (body == null) {
|
|
113
|
+
return void 0;
|
|
114
|
+
}
|
|
115
|
+
const maybeTypedBody = body;
|
|
116
|
+
if ("text" in maybeTypedBody && maybeTypedBody.text != null) {
|
|
117
|
+
return {
|
|
118
|
+
contentType: maybeTypedBody.contentType ?? "text/plain",
|
|
119
|
+
parserType: "text",
|
|
120
|
+
schema: maybeTypedBody.text
|
|
121
|
+
};
|
|
122
|
+
} else if ("json" in maybeTypedBody && maybeTypedBody.json != null) {
|
|
123
|
+
return {
|
|
124
|
+
contentType: maybeTypedBody.contentType ?? "application/json",
|
|
125
|
+
parserType: "json",
|
|
126
|
+
schema: maybeTypedBody.json
|
|
127
|
+
};
|
|
128
|
+
} else if ("file" in maybeTypedBody && maybeTypedBody.file != null) {
|
|
129
|
+
return {
|
|
130
|
+
contentType: maybeTypedBody.contentType ?? "application/octet-stream",
|
|
131
|
+
parserType: "file",
|
|
132
|
+
schema: maybeTypedBody.file
|
|
133
|
+
};
|
|
134
|
+
} else if ("multipartForm" in maybeTypedBody && maybeTypedBody.multipartForm != null) {
|
|
135
|
+
return {
|
|
136
|
+
contentType: maybeTypedBody.contentType ?? "multipart/form-data",
|
|
137
|
+
parserType: "multipart",
|
|
138
|
+
schema: maybeTypedBody.multipartForm
|
|
139
|
+
};
|
|
140
|
+
} else if ("urlEncodedForm" in maybeTypedBody && maybeTypedBody.urlEncodedForm != null) {
|
|
141
|
+
return {
|
|
142
|
+
contentType: maybeTypedBody.contentType ?? "application/x-www-form-urlencoded",
|
|
143
|
+
parserType: "urlEncoded",
|
|
144
|
+
schema: maybeTypedBody.urlEncodedForm
|
|
145
|
+
};
|
|
146
|
+
} else if ("schema" in maybeTypedBody && maybeTypedBody.schema != null) {
|
|
147
|
+
return {
|
|
148
|
+
contentType: maybeTypedBody.contentType ?? "application/json",
|
|
149
|
+
parserType: "text",
|
|
150
|
+
schema: maybeTypedBody.schema
|
|
151
|
+
};
|
|
152
|
+
} else if (schemaValidator.isInstanceOf(
|
|
153
|
+
maybeTypedBody,
|
|
154
|
+
schemaValidator.string
|
|
155
|
+
)) {
|
|
156
|
+
return {
|
|
157
|
+
contentType: "text/plain",
|
|
158
|
+
parserType: "text",
|
|
159
|
+
schema: maybeTypedBody
|
|
160
|
+
};
|
|
161
|
+
} else if (schemaValidator.openapi(maybeTypedBody).format === "binary") {
|
|
162
|
+
return {
|
|
163
|
+
contentType: "application/octet-stream",
|
|
164
|
+
parserType: "file",
|
|
165
|
+
schema: maybeTypedBody
|
|
166
|
+
};
|
|
167
|
+
} else {
|
|
168
|
+
return {
|
|
169
|
+
contentType: "application/json",
|
|
170
|
+
parserType: "json",
|
|
171
|
+
schema: maybeTypedBody
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
function discriminateResponseBodies(schemaValidator, responses) {
|
|
176
|
+
const discriminatedResponses = {};
|
|
177
|
+
for (const [statusCode, response] of Object.entries(responses)) {
|
|
178
|
+
if (response != null && typeof response === "object") {
|
|
179
|
+
if ("json" in response && response.json != null) {
|
|
180
|
+
discriminatedResponses[Number(statusCode)] = {
|
|
181
|
+
contentType: ("contentType" in response && typeof response.contentType === "string" ? response.contentType : "application/json") ?? "application/json",
|
|
182
|
+
parserType: "json",
|
|
183
|
+
schema: response.json
|
|
184
|
+
};
|
|
185
|
+
} else if ("schema" in response && response.schema != null) {
|
|
186
|
+
discriminatedResponses[Number(statusCode)] = {
|
|
187
|
+
contentType: ("contentType" in response && typeof response.contentType === "string" ? response.contentType : "application/json") ?? "application/json",
|
|
188
|
+
parserType: "text",
|
|
189
|
+
schema: response.schema
|
|
190
|
+
};
|
|
191
|
+
} else if ("text" in response && response.text != null) {
|
|
192
|
+
discriminatedResponses[Number(statusCode)] = {
|
|
193
|
+
contentType: ("contentType" in response && typeof response.contentType === "string" ? response.contentType : "text/plain") ?? "text/plain",
|
|
194
|
+
parserType: "text",
|
|
195
|
+
schema: response.text
|
|
196
|
+
};
|
|
197
|
+
} else if ("file" in response && response.file != null) {
|
|
198
|
+
discriminatedResponses[Number(statusCode)] = {
|
|
199
|
+
contentType: ("contentType" in response && typeof response.contentType === "string" ? response.contentType : "application/octet-stream") ?? "application/octet-stream",
|
|
200
|
+
parserType: "file",
|
|
201
|
+
schema: response.file
|
|
202
|
+
};
|
|
203
|
+
} else if ("event" in response && response.event != null) {
|
|
204
|
+
discriminatedResponses[Number(statusCode)] = {
|
|
205
|
+
contentType: ("contentType" in response && typeof response.contentType === "string" ? response.contentType : "text/event-stream") ?? "text/event-stream",
|
|
206
|
+
parserType: "serverSentEvent",
|
|
207
|
+
schema: response.event
|
|
208
|
+
};
|
|
209
|
+
} else if (schemaValidator.isInstanceOf(
|
|
210
|
+
response,
|
|
211
|
+
schemaValidator.string
|
|
212
|
+
)) {
|
|
213
|
+
discriminatedResponses[Number(statusCode)] = {
|
|
214
|
+
contentType: "text/plain",
|
|
215
|
+
parserType: "text",
|
|
216
|
+
schema: response
|
|
217
|
+
};
|
|
218
|
+
} else if (schemaValidator.openapi(response).format === "binary") {
|
|
219
|
+
discriminatedResponses[Number(statusCode)] = {
|
|
220
|
+
contentType: "application/octet-stream",
|
|
221
|
+
parserType: "file",
|
|
222
|
+
schema: response
|
|
223
|
+
};
|
|
224
|
+
} else {
|
|
225
|
+
discriminatedResponses[Number(statusCode)] = {
|
|
226
|
+
contentType: "application/json",
|
|
227
|
+
parserType: "json",
|
|
228
|
+
schema: response
|
|
229
|
+
};
|
|
230
|
+
}
|
|
231
|
+
} else {
|
|
232
|
+
discriminatedResponses[Number(statusCode)] = {
|
|
233
|
+
contentType: "application/json",
|
|
234
|
+
parserType: "json",
|
|
235
|
+
schema: response
|
|
236
|
+
};
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
return discriminatedResponses;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
// src/http/router/routerSharedLogic.ts
|
|
243
|
+
import { isRecord as isRecord2 } from "@forklaunch/common";
|
|
244
|
+
|
|
245
|
+
// src/http/guards/hasVersionedSchema.ts
|
|
246
|
+
function hasVersionedSchema(contractDetails) {
|
|
247
|
+
return typeof contractDetails === "object" && contractDetails !== null && "versions" in contractDetails && contractDetails.versions !== null;
|
|
248
|
+
}
|
|
249
|
+
|
|
65
250
|
// src/http/guards/isPathParamContractDetails.ts
|
|
66
251
|
function isPathParamHttpContractDetails(maybePathParamHttpContractDetails) {
|
|
67
252
|
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(
|
|
@@ -76,23 +261,14 @@ function isHttpContractDetails(maybeContractDetails) {
|
|
|
76
261
|
));
|
|
77
262
|
}
|
|
78
263
|
|
|
79
|
-
// src/http/guards/isSdkHandler.ts
|
|
80
|
-
function isSdkHandler(handler) {
|
|
81
|
-
return typeof handler === "object" && handler !== null && "_path" in handler && "_method" in handler && "contractDetails" in handler;
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
// src/http/guards/isTypedHandler.ts
|
|
85
|
-
function isTypedHandler(maybeTypedHandler) {
|
|
86
|
-
return maybeTypedHandler != null && typeof maybeTypedHandler === "object" && "_typedHandler" in maybeTypedHandler && maybeTypedHandler._typedHandler === true;
|
|
87
|
-
}
|
|
88
|
-
|
|
89
264
|
// src/http/middleware/request/auth.middleware.ts
|
|
90
|
-
import { isNever } from "@forklaunch/common";
|
|
265
|
+
import { isNever as isNever2 } from "@forklaunch/common";
|
|
91
266
|
import {
|
|
92
267
|
prettyPrintParseErrors
|
|
93
268
|
} from "@forklaunch/validator";
|
|
94
269
|
|
|
95
270
|
// src/http/discriminateAuthMethod.ts
|
|
271
|
+
import { safeStringify as safeStringify2 } from "@forklaunch/common";
|
|
96
272
|
import { jwtVerify } from "jose";
|
|
97
273
|
|
|
98
274
|
// src/http/createHmacToken.ts
|
|
@@ -154,7 +330,7 @@ async function getCachedJwks(jwksPublicKeyUrl) {
|
|
|
154
330
|
return jwks;
|
|
155
331
|
}
|
|
156
332
|
}
|
|
157
|
-
async function discriminateAuthMethod(auth) {
|
|
333
|
+
async function discriminateAuthMethod(auth, openTelemetryCollector) {
|
|
158
334
|
let authMethod;
|
|
159
335
|
if (isBasicAuthMethod(auth)) {
|
|
160
336
|
authMethod = {
|
|
@@ -217,37 +393,219 @@ async function discriminateAuthMethod(auth) {
|
|
|
217
393
|
signature,
|
|
218
394
|
secretKey
|
|
219
395
|
}) => {
|
|
220
|
-
|
|
396
|
+
const computedSignature = createHmacToken({
|
|
221
397
|
method,
|
|
222
398
|
path,
|
|
223
399
|
body,
|
|
224
400
|
timestamp,
|
|
225
401
|
nonce,
|
|
226
402
|
secretKey
|
|
227
|
-
})
|
|
403
|
+
});
|
|
404
|
+
const isValid = computedSignature === signature;
|
|
405
|
+
if (!isValid) {
|
|
406
|
+
const errorInfo = {
|
|
407
|
+
method,
|
|
408
|
+
path,
|
|
409
|
+
timestamp: timestamp.toISOString(),
|
|
410
|
+
nonce,
|
|
411
|
+
receivedSignature: signature,
|
|
412
|
+
computedSignature
|
|
413
|
+
};
|
|
414
|
+
openTelemetryCollector?.debug("[HMAC Verification Failed]", {
|
|
415
|
+
...errorInfo,
|
|
416
|
+
bodyType: body ? typeof body : "undefined",
|
|
417
|
+
body: body ? safeStringify2(body) : "undefined"
|
|
418
|
+
});
|
|
419
|
+
}
|
|
420
|
+
return isValid;
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
};
|
|
424
|
+
}
|
|
425
|
+
if (authMethod == null) {
|
|
426
|
+
throw new Error("Invalid auth method");
|
|
427
|
+
}
|
|
428
|
+
return authMethod;
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
// src/http/guards/hasPermissionChecks.ts
|
|
432
|
+
function hasPermissionChecks(maybePermissionedAuth) {
|
|
433
|
+
return typeof maybePermissionedAuth === "object" && maybePermissionedAuth !== null && ("allowedPermissions" in maybePermissionedAuth || "forbiddenPermissions" in maybePermissionedAuth);
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
// src/http/guards/hasRoleChecks.ts
|
|
437
|
+
function hasRoleChecks(maybeRoledAuth) {
|
|
438
|
+
return typeof maybeRoledAuth === "object" && maybeRoledAuth !== null && ("allowedRoles" in maybeRoledAuth || "forbiddenRoles" in maybeRoledAuth);
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
// src/http/guards/hasScopeChecks.ts
|
|
442
|
+
function hasScopeChecks(maybePermissionedAuth) {
|
|
443
|
+
return typeof maybePermissionedAuth === "object" && maybePermissionedAuth !== null && "requiredScope" in maybePermissionedAuth && maybePermissionedAuth.requiredScope != null;
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
// src/http/telemetry/pinoLogger.ts
|
|
447
|
+
import { isNever } from "@forklaunch/common";
|
|
448
|
+
import { trace as trace2 } from "@opentelemetry/api";
|
|
449
|
+
import { logs } from "@opentelemetry/api-logs";
|
|
450
|
+
import pino from "pino";
|
|
451
|
+
|
|
452
|
+
// src/http/guards/isLoggerMeta.ts
|
|
453
|
+
function isLoggerMeta(arg) {
|
|
454
|
+
return typeof arg === "object" && arg !== null && "_meta" in arg;
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
// src/http/telemetry/pinoLogger.ts
|
|
458
|
+
function meta(meta2) {
|
|
459
|
+
return meta2;
|
|
460
|
+
}
|
|
461
|
+
function mapSeverity(level) {
|
|
462
|
+
switch (level) {
|
|
463
|
+
case "silent":
|
|
464
|
+
return 0;
|
|
465
|
+
case "trace":
|
|
466
|
+
return 1;
|
|
467
|
+
case "debug":
|
|
468
|
+
return 5;
|
|
469
|
+
case "info":
|
|
470
|
+
return 9;
|
|
471
|
+
case "warn":
|
|
472
|
+
return 13;
|
|
473
|
+
case "error":
|
|
474
|
+
return 17;
|
|
475
|
+
case "fatal":
|
|
476
|
+
return 21;
|
|
477
|
+
default:
|
|
478
|
+
isNever(level);
|
|
479
|
+
return 0;
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
function normalizeLogArgs(args) {
|
|
483
|
+
let message = "";
|
|
484
|
+
const metaObjects = [];
|
|
485
|
+
for (const arg of args) {
|
|
486
|
+
if (typeof arg === "string" && message === "") {
|
|
487
|
+
message = arg;
|
|
488
|
+
} else if (arg instanceof Error) {
|
|
489
|
+
metaObjects.push({
|
|
490
|
+
err: {
|
|
491
|
+
message: arg.message,
|
|
492
|
+
name: arg.name,
|
|
493
|
+
stack: arg.stack,
|
|
494
|
+
...Object.keys(arg).length > 0 ? arg : {}
|
|
228
495
|
}
|
|
496
|
+
});
|
|
497
|
+
} else if (arg && typeof arg === "object" && !Array.isArray(arg)) {
|
|
498
|
+
metaObjects.push(arg);
|
|
499
|
+
} else {
|
|
500
|
+
message += ` ${String(arg)}`;
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
const metadata = Object.assign({}, ...metaObjects);
|
|
504
|
+
return [metadata, message.trim()];
|
|
505
|
+
}
|
|
506
|
+
function safePrettyFormat(level, args, timestamp) {
|
|
507
|
+
try {
|
|
508
|
+
const [metadata, message] = normalizeLogArgs(args);
|
|
509
|
+
const formattedTimestamp = timestamp || (/* @__PURE__ */ new Date()).toISOString();
|
|
510
|
+
return `[${formattedTimestamp}] ${level.toUpperCase()}: ${message}${Object.keys(metadata).length > 0 ? `
|
|
511
|
+
${JSON.stringify(metadata, null, 2)}` : ""}`;
|
|
512
|
+
} catch (error) {
|
|
513
|
+
const fallbackMessage = args.map((arg) => {
|
|
514
|
+
try {
|
|
515
|
+
if (typeof arg === "string") return arg;
|
|
516
|
+
if (arg === null) return "null";
|
|
517
|
+
if (arg === void 0) return "undefined";
|
|
518
|
+
return JSON.stringify(arg);
|
|
519
|
+
} catch {
|
|
520
|
+
return "[Circular/Non-serializable Object]";
|
|
521
|
+
}
|
|
522
|
+
}).join(" ");
|
|
523
|
+
return `[${(/* @__PURE__ */ new Date()).toISOString()}] ${level.toUpperCase()}: ${fallbackMessage} [Pretty Print Error: ${error instanceof Error ? error.message : "Unknown error"}]`;
|
|
524
|
+
}
|
|
525
|
+
}
|
|
526
|
+
var PinoLogger = class _PinoLogger {
|
|
527
|
+
pinoLogger;
|
|
528
|
+
meta;
|
|
529
|
+
constructor(level, meta2 = {}) {
|
|
530
|
+
this.pinoLogger = pino({
|
|
531
|
+
level: level || "info",
|
|
532
|
+
formatters: {
|
|
533
|
+
level(label) {
|
|
534
|
+
return { level: label };
|
|
535
|
+
}
|
|
536
|
+
},
|
|
537
|
+
timestamp: pino.stdTimeFunctions.isoTime,
|
|
538
|
+
transport: {
|
|
539
|
+
target: "pino-pretty",
|
|
540
|
+
options: {
|
|
541
|
+
colorize: true,
|
|
542
|
+
errorLikeObjectKeys: ["err", "error"],
|
|
543
|
+
ignore: "pid,hostname",
|
|
544
|
+
translateTime: "SYS:standard"
|
|
545
|
+
}
|
|
546
|
+
}
|
|
547
|
+
});
|
|
548
|
+
this.meta = meta2;
|
|
549
|
+
}
|
|
550
|
+
log(level, ...args) {
|
|
551
|
+
let meta2 = {};
|
|
552
|
+
const filteredArgs = args.filter((arg) => {
|
|
553
|
+
if (isLoggerMeta(arg)) {
|
|
554
|
+
Object.assign(meta2, arg);
|
|
555
|
+
return false;
|
|
229
556
|
}
|
|
557
|
+
return true;
|
|
558
|
+
});
|
|
559
|
+
const [errorMetadata] = normalizeLogArgs(filteredArgs);
|
|
560
|
+
Object.assign(meta2, errorMetadata);
|
|
561
|
+
const activeSpan = trace2.getActiveSpan();
|
|
562
|
+
if (activeSpan) {
|
|
563
|
+
const activeSpanContext = activeSpan.spanContext();
|
|
564
|
+
meta2.trace_id = activeSpanContext.traceId;
|
|
565
|
+
meta2.span_id = activeSpanContext.spanId;
|
|
566
|
+
meta2.trace_flags = activeSpanContext.traceFlags;
|
|
567
|
+
meta2 = {
|
|
568
|
+
// @ts-expect-error accessing private property
|
|
569
|
+
...activeSpan.attributes,
|
|
570
|
+
...meta2
|
|
571
|
+
};
|
|
572
|
+
}
|
|
573
|
+
meta2 = {
|
|
574
|
+
"api.name": "none",
|
|
575
|
+
"correlation.id": "none",
|
|
576
|
+
...meta2
|
|
230
577
|
};
|
|
578
|
+
this.pinoLogger[level](...normalizeLogArgs(filteredArgs));
|
|
579
|
+
const formattedBody = safePrettyFormat(level, filteredArgs);
|
|
580
|
+
try {
|
|
581
|
+
logs.getLogger(process.env.OTEL_SERVICE_NAME ?? "unknown").emit({
|
|
582
|
+
severityText: level,
|
|
583
|
+
severityNumber: mapSeverity(level),
|
|
584
|
+
body: formattedBody,
|
|
585
|
+
attributes: { ...this.meta, ...meta2 }
|
|
586
|
+
});
|
|
587
|
+
} catch (error) {
|
|
588
|
+
console.error("Failed to emit OpenTelemetry log:", error);
|
|
589
|
+
console.log(
|
|
590
|
+
`[${(/* @__PURE__ */ new Date()).toISOString()}] ${level.toUpperCase()}:`,
|
|
591
|
+
...filteredArgs
|
|
592
|
+
);
|
|
593
|
+
}
|
|
231
594
|
}
|
|
232
|
-
|
|
233
|
-
|
|
595
|
+
error = (msg, ...args) => this.log("error", msg, ...args);
|
|
596
|
+
info = (msg, ...args) => this.log("info", msg, ...args);
|
|
597
|
+
debug = (msg, ...args) => this.log("debug", msg, ...args);
|
|
598
|
+
warn = (msg, ...args) => this.log("warn", msg, ...args);
|
|
599
|
+
trace = (msg, ...args) => this.log("trace", msg, ...args);
|
|
600
|
+
child(meta2 = {}) {
|
|
601
|
+
return new _PinoLogger(this.pinoLogger.level, { ...this.meta, ...meta2 });
|
|
234
602
|
}
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
function
|
|
240
|
-
return
|
|
241
|
-
}
|
|
242
|
-
|
|
243
|
-
// src/http/guards/hasRoleChecks.ts
|
|
244
|
-
function hasRoleChecks(maybeRoledAuth) {
|
|
245
|
-
return typeof maybeRoledAuth === "object" && maybeRoledAuth !== null && ("allowedRoles" in maybeRoledAuth || "forbiddenRoles" in maybeRoledAuth);
|
|
246
|
-
}
|
|
247
|
-
|
|
248
|
-
// src/http/guards/hasScopeChecks.ts
|
|
249
|
-
function hasScopeChecks(maybePermissionedAuth) {
|
|
250
|
-
return typeof maybePermissionedAuth === "object" && maybePermissionedAuth !== null && "requiredScope" in maybePermissionedAuth && maybePermissionedAuth.requiredScope != null;
|
|
603
|
+
getBaseLogger() {
|
|
604
|
+
return this.pinoLogger;
|
|
605
|
+
}
|
|
606
|
+
};
|
|
607
|
+
function logger(level, meta2 = {}) {
|
|
608
|
+
return new PinoLogger(level, meta2);
|
|
251
609
|
}
|
|
252
610
|
|
|
253
611
|
// src/http/middleware/request/auth.middleware.ts
|
|
@@ -311,7 +669,8 @@ async function checkAuthorizationToken(req, authorizationMethod, authorizationTo
|
|
|
311
669
|
}
|
|
312
670
|
let sessionPayload;
|
|
313
671
|
const { type, auth } = await discriminateAuthMethod(
|
|
314
|
-
collapsedAuthorizationMethod
|
|
672
|
+
collapsedAuthorizationMethod,
|
|
673
|
+
req.openTelemetryCollector
|
|
315
674
|
);
|
|
316
675
|
switch (type) {
|
|
317
676
|
case "hmac": {
|
|
@@ -332,7 +691,7 @@ async function checkAuthorizationToken(req, authorizationMethod, authorizationTo
|
|
|
332
691
|
const verificationResult = await auth.verificationFunction({
|
|
333
692
|
method: req?.method ?? "",
|
|
334
693
|
path: req?.path ?? "",
|
|
335
|
-
body: req?.body,
|
|
694
|
+
body: req?._rawBody ?? req?.body,
|
|
336
695
|
timestamp: new Date(parsedTimestamp),
|
|
337
696
|
nonce: parsedNonce,
|
|
338
697
|
signature: parsedSignature,
|
|
@@ -356,7 +715,15 @@ async function checkAuthorizationToken(req, authorizationMethod, authorizationTo
|
|
|
356
715
|
}
|
|
357
716
|
sessionPayload = decodedJwt;
|
|
358
717
|
} catch (error) {
|
|
359
|
-
req?.openTelemetryCollector
|
|
718
|
+
req?.openTelemetryCollector?.error(
|
|
719
|
+
"JWT Verification Failed",
|
|
720
|
+
meta({
|
|
721
|
+
error,
|
|
722
|
+
method: req.method,
|
|
723
|
+
path: req.path,
|
|
724
|
+
token
|
|
725
|
+
})
|
|
726
|
+
);
|
|
360
727
|
return invalidAuthorizationToken;
|
|
361
728
|
}
|
|
362
729
|
break;
|
|
@@ -383,7 +750,7 @@ async function checkAuthorizationToken(req, authorizationMethod, authorizationTo
|
|
|
383
750
|
break;
|
|
384
751
|
}
|
|
385
752
|
default:
|
|
386
|
-
|
|
753
|
+
isNever2(type);
|
|
387
754
|
return [401, "Invalid Authorization method."];
|
|
388
755
|
}
|
|
389
756
|
if (isHmacMethod(collapsedAuthorizationMethod) && sessionPayload == null) {
|
|
@@ -468,14 +835,18 @@ async function checkAuthorizationToken(req, authorizationMethod, authorizationTo
|
|
|
468
835
|
}
|
|
469
836
|
async function parseRequestAuth(req, res, next) {
|
|
470
837
|
const auth = req.contractDetails.auth;
|
|
471
|
-
const [
|
|
472
|
-
|
|
473
|
-
auth,
|
|
474
|
-
req.headers[auth?.headerName ?? "Authorization"] || req.headers[auth?.headerName ?? "authorization"],
|
|
475
|
-
req._globalOptions?.()?.auth
|
|
476
|
-
) ?? [];
|
|
838
|
+
const token = req.headers[auth?.headerName ?? "Authorization"] || req.headers[auth?.headerName ?? "authorization"];
|
|
839
|
+
const [error, message] = await checkAuthorizationToken(req, auth, token, req._globalOptions?.()?.auth) ?? [];
|
|
477
840
|
if (error != null) {
|
|
478
|
-
req.openTelemetryCollector
|
|
841
|
+
req.openTelemetryCollector?.error(
|
|
842
|
+
message || "Authorization Failed",
|
|
843
|
+
meta({
|
|
844
|
+
statusCode: error,
|
|
845
|
+
method: req.method,
|
|
846
|
+
path: req.path,
|
|
847
|
+
token
|
|
848
|
+
})
|
|
849
|
+
);
|
|
479
850
|
res.type("text/plain");
|
|
480
851
|
res.status(error).send(message);
|
|
481
852
|
return;
|
|
@@ -483,46 +854,6 @@ async function parseRequestAuth(req, res, next) {
|
|
|
483
854
|
next?.();
|
|
484
855
|
}
|
|
485
856
|
|
|
486
|
-
// src/http/middleware/request/createContext.middleware.ts
|
|
487
|
-
import { getEnvVar } from "@forklaunch/common";
|
|
488
|
-
import { context, trace } from "@opentelemetry/api";
|
|
489
|
-
import { v4 } from "uuid";
|
|
490
|
-
|
|
491
|
-
// src/http/telemetry/constants.ts
|
|
492
|
-
import {
|
|
493
|
-
ATTR_HTTP_REQUEST_METHOD,
|
|
494
|
-
ATTR_HTTP_RESPONSE_STATUS_CODE,
|
|
495
|
-
ATTR_HTTP_ROUTE,
|
|
496
|
-
ATTR_SERVICE_NAME
|
|
497
|
-
} from "@opentelemetry/semantic-conventions";
|
|
498
|
-
var ATTR_API_NAME = "api.name";
|
|
499
|
-
var ATTR_CORRELATION_ID = "correlation.id";
|
|
500
|
-
|
|
501
|
-
// src/http/middleware/request/createContext.middleware.ts
|
|
502
|
-
function createContext(schemaValidator) {
|
|
503
|
-
return function setContext(req, res, next) {
|
|
504
|
-
req.schemaValidator = schemaValidator;
|
|
505
|
-
let correlationId = v4();
|
|
506
|
-
if (req.headers["x-correlation-id"]) {
|
|
507
|
-
correlationId = req.headers["x-correlation-id"];
|
|
508
|
-
}
|
|
509
|
-
res.setHeader("x-correlation-id", correlationId);
|
|
510
|
-
req.context = {
|
|
511
|
-
correlationId
|
|
512
|
-
};
|
|
513
|
-
const span = trace.getSpan(context.active());
|
|
514
|
-
if (span != null) {
|
|
515
|
-
req.context.span = span;
|
|
516
|
-
req.context.span?.setAttribute(ATTR_CORRELATION_ID, correlationId);
|
|
517
|
-
req.context.span?.setAttribute(
|
|
518
|
-
ATTR_SERVICE_NAME,
|
|
519
|
-
getEnvVar("OTEL_SERVICE_NAME")
|
|
520
|
-
);
|
|
521
|
-
}
|
|
522
|
-
next?.();
|
|
523
|
-
};
|
|
524
|
-
}
|
|
525
|
-
|
|
526
857
|
// src/http/middleware/request/enrichDetails.middleware.ts
|
|
527
858
|
import { getEnvVar as getEnvVar3 } from "@forklaunch/common";
|
|
528
859
|
|
|
@@ -551,171 +882,6 @@ function isForklaunchRequest(request) {
|
|
|
551
882
|
return request != null && typeof request === "object" && "contractDetails" in request;
|
|
552
883
|
}
|
|
553
884
|
|
|
554
|
-
// src/http/telemetry/pinoLogger.ts
|
|
555
|
-
import { isNever as isNever2 } from "@forklaunch/common";
|
|
556
|
-
import { trace as trace2 } from "@opentelemetry/api";
|
|
557
|
-
import { logs } from "@opentelemetry/api-logs";
|
|
558
|
-
import pino from "pino";
|
|
559
|
-
|
|
560
|
-
// src/http/guards/isLoggerMeta.ts
|
|
561
|
-
function isLoggerMeta(arg) {
|
|
562
|
-
return typeof arg === "object" && arg !== null && "_meta" in arg;
|
|
563
|
-
}
|
|
564
|
-
|
|
565
|
-
// src/http/telemetry/pinoLogger.ts
|
|
566
|
-
function meta(meta2) {
|
|
567
|
-
return meta2;
|
|
568
|
-
}
|
|
569
|
-
function mapSeverity(level) {
|
|
570
|
-
switch (level) {
|
|
571
|
-
case "silent":
|
|
572
|
-
return 0;
|
|
573
|
-
case "trace":
|
|
574
|
-
return 1;
|
|
575
|
-
case "debug":
|
|
576
|
-
return 5;
|
|
577
|
-
case "info":
|
|
578
|
-
return 9;
|
|
579
|
-
case "warn":
|
|
580
|
-
return 13;
|
|
581
|
-
case "error":
|
|
582
|
-
return 17;
|
|
583
|
-
case "fatal":
|
|
584
|
-
return 21;
|
|
585
|
-
default:
|
|
586
|
-
isNever2(level);
|
|
587
|
-
return 0;
|
|
588
|
-
}
|
|
589
|
-
}
|
|
590
|
-
function normalizeLogArgs(args) {
|
|
591
|
-
let message = "";
|
|
592
|
-
const metaObjects = [];
|
|
593
|
-
for (const arg of args) {
|
|
594
|
-
if (typeof arg === "string" && message === "") {
|
|
595
|
-
message = arg;
|
|
596
|
-
} else if (arg instanceof Error) {
|
|
597
|
-
metaObjects.push({
|
|
598
|
-
err: {
|
|
599
|
-
message: arg.message,
|
|
600
|
-
name: arg.name,
|
|
601
|
-
stack: arg.stack,
|
|
602
|
-
...Object.keys(arg).length > 0 ? arg : {}
|
|
603
|
-
}
|
|
604
|
-
});
|
|
605
|
-
} else if (arg && typeof arg === "object" && !Array.isArray(arg)) {
|
|
606
|
-
metaObjects.push(arg);
|
|
607
|
-
} else {
|
|
608
|
-
message += ` ${String(arg)}`;
|
|
609
|
-
}
|
|
610
|
-
}
|
|
611
|
-
const metadata = Object.assign({}, ...metaObjects);
|
|
612
|
-
return [metadata, message.trim()];
|
|
613
|
-
}
|
|
614
|
-
function safePrettyFormat(level, args, timestamp) {
|
|
615
|
-
try {
|
|
616
|
-
const [metadata, message] = normalizeLogArgs(args);
|
|
617
|
-
const formattedTimestamp = timestamp || (/* @__PURE__ */ new Date()).toISOString();
|
|
618
|
-
return `[${formattedTimestamp}] ${level.toUpperCase()}: ${message}${Object.keys(metadata).length > 0 ? `
|
|
619
|
-
${JSON.stringify(metadata, null, 2)}` : ""}`;
|
|
620
|
-
} catch (error) {
|
|
621
|
-
const fallbackMessage = args.map((arg) => {
|
|
622
|
-
try {
|
|
623
|
-
if (typeof arg === "string") return arg;
|
|
624
|
-
if (arg === null) return "null";
|
|
625
|
-
if (arg === void 0) return "undefined";
|
|
626
|
-
return JSON.stringify(arg);
|
|
627
|
-
} catch {
|
|
628
|
-
return "[Circular/Non-serializable Object]";
|
|
629
|
-
}
|
|
630
|
-
}).join(" ");
|
|
631
|
-
return `[${(/* @__PURE__ */ new Date()).toISOString()}] ${level.toUpperCase()}: ${fallbackMessage} [Pretty Print Error: ${error instanceof Error ? error.message : "Unknown error"}]`;
|
|
632
|
-
}
|
|
633
|
-
}
|
|
634
|
-
var PinoLogger = class _PinoLogger {
|
|
635
|
-
pinoLogger;
|
|
636
|
-
meta;
|
|
637
|
-
constructor(level, meta2 = {}) {
|
|
638
|
-
this.pinoLogger = pino({
|
|
639
|
-
level: level || "info",
|
|
640
|
-
formatters: {
|
|
641
|
-
level(label) {
|
|
642
|
-
return { level: label };
|
|
643
|
-
}
|
|
644
|
-
},
|
|
645
|
-
timestamp: pino.stdTimeFunctions.isoTime,
|
|
646
|
-
transport: {
|
|
647
|
-
target: "pino-pretty",
|
|
648
|
-
options: {
|
|
649
|
-
colorize: true,
|
|
650
|
-
errorLikeObjectKeys: ["err", "error"],
|
|
651
|
-
ignore: "pid,hostname",
|
|
652
|
-
translateTime: "SYS:standard"
|
|
653
|
-
}
|
|
654
|
-
}
|
|
655
|
-
});
|
|
656
|
-
this.meta = meta2;
|
|
657
|
-
}
|
|
658
|
-
log(level, ...args) {
|
|
659
|
-
let meta2 = {};
|
|
660
|
-
const filteredArgs = args.filter((arg) => {
|
|
661
|
-
if (isLoggerMeta(arg)) {
|
|
662
|
-
Object.assign(meta2, arg);
|
|
663
|
-
return false;
|
|
664
|
-
}
|
|
665
|
-
return true;
|
|
666
|
-
});
|
|
667
|
-
const [errorMetadata] = normalizeLogArgs(filteredArgs);
|
|
668
|
-
Object.assign(meta2, errorMetadata);
|
|
669
|
-
const activeSpan = trace2.getActiveSpan();
|
|
670
|
-
if (activeSpan) {
|
|
671
|
-
const activeSpanContext = activeSpan.spanContext();
|
|
672
|
-
meta2.trace_id = activeSpanContext.traceId;
|
|
673
|
-
meta2.span_id = activeSpanContext.spanId;
|
|
674
|
-
meta2.trace_flags = activeSpanContext.traceFlags;
|
|
675
|
-
meta2 = {
|
|
676
|
-
// @ts-expect-error accessing private property
|
|
677
|
-
...activeSpan.attributes,
|
|
678
|
-
...meta2
|
|
679
|
-
};
|
|
680
|
-
}
|
|
681
|
-
meta2 = {
|
|
682
|
-
"api.name": "none",
|
|
683
|
-
"correlation.id": "none",
|
|
684
|
-
...meta2
|
|
685
|
-
};
|
|
686
|
-
this.pinoLogger[level](...normalizeLogArgs(filteredArgs));
|
|
687
|
-
const formattedBody = safePrettyFormat(level, filteredArgs);
|
|
688
|
-
try {
|
|
689
|
-
logs.getLogger(process.env.OTEL_SERVICE_NAME ?? "unknown").emit({
|
|
690
|
-
severityText: level,
|
|
691
|
-
severityNumber: mapSeverity(level),
|
|
692
|
-
body: formattedBody,
|
|
693
|
-
attributes: { ...this.meta, ...meta2 }
|
|
694
|
-
});
|
|
695
|
-
} catch (error) {
|
|
696
|
-
console.error("Failed to emit OpenTelemetry log:", error);
|
|
697
|
-
console.log(
|
|
698
|
-
`[${(/* @__PURE__ */ new Date()).toISOString()}] ${level.toUpperCase()}:`,
|
|
699
|
-
...filteredArgs
|
|
700
|
-
);
|
|
701
|
-
}
|
|
702
|
-
}
|
|
703
|
-
error = (msg, ...args) => this.log("error", msg, ...args);
|
|
704
|
-
info = (msg, ...args) => this.log("info", msg, ...args);
|
|
705
|
-
debug = (msg, ...args) => this.log("debug", msg, ...args);
|
|
706
|
-
warn = (msg, ...args) => this.log("warn", msg, ...args);
|
|
707
|
-
trace = (msg, ...args) => this.log("trace", msg, ...args);
|
|
708
|
-
child(meta2 = {}) {
|
|
709
|
-
return new _PinoLogger(this.pinoLogger.level, { ...this.meta, ...meta2 });
|
|
710
|
-
}
|
|
711
|
-
getBaseLogger() {
|
|
712
|
-
return this.pinoLogger;
|
|
713
|
-
}
|
|
714
|
-
};
|
|
715
|
-
function logger(level, meta2 = {}) {
|
|
716
|
-
return new PinoLogger(level, meta2);
|
|
717
|
-
}
|
|
718
|
-
|
|
719
885
|
// src/http/telemetry/openTelemetryCollector.ts
|
|
720
886
|
var OpenTelemetryCollector = class {
|
|
721
887
|
#logger;
|
|
@@ -781,23 +947,39 @@ var OpenTelemetryCollector = class {
|
|
|
781
947
|
}
|
|
782
948
|
};
|
|
783
949
|
dotenv.config({ path: getEnvVar2("DOTENV_FILE_PATH") });
|
|
950
|
+
function parseOtelHeaders() {
|
|
951
|
+
const headersEnv = getEnvVar2("OTEL_EXPORTER_OTLP_HEADERS");
|
|
952
|
+
if (!headersEnv) return void 0;
|
|
953
|
+
const headers = {};
|
|
954
|
+
for (const pair of headersEnv.split(",")) {
|
|
955
|
+
const [key, ...valueParts] = pair.split("=");
|
|
956
|
+
if (key && valueParts.length > 0) {
|
|
957
|
+
headers[key.trim()] = valueParts.join("=").trim();
|
|
958
|
+
}
|
|
959
|
+
}
|
|
960
|
+
return Object.keys(headers).length > 0 ? headers : void 0;
|
|
961
|
+
}
|
|
962
|
+
var otelHeaders = parseOtelHeaders();
|
|
784
963
|
new NodeSDK({
|
|
785
964
|
resource: resourceFromAttributes({
|
|
786
965
|
[ATTR_SERVICE_NAME2]: getEnvVar2("OTEL_SERVICE_NAME")
|
|
787
966
|
}),
|
|
788
967
|
traceExporter: new OTLPTraceExporter({
|
|
789
|
-
url: `${getEnvVar2("OTEL_EXPORTER_OTLP_ENDPOINT") ?? "http://localhost:4318"}/v1/traces
|
|
968
|
+
url: `${getEnvVar2("OTEL_EXPORTER_OTLP_ENDPOINT") ?? "http://localhost:4318"}/v1/traces`,
|
|
969
|
+
headers: otelHeaders
|
|
790
970
|
}),
|
|
791
971
|
metricReader: new PeriodicExportingMetricReader({
|
|
792
972
|
exporter: new OTLPMetricExporter({
|
|
793
|
-
url: `${getEnvVar2("OTEL_EXPORTER_OTLP_ENDPOINT") ?? "http://localhost:4318"}/v1/metrics
|
|
973
|
+
url: `${getEnvVar2("OTEL_EXPORTER_OTLP_ENDPOINT") ?? "http://localhost:4318"}/v1/metrics`,
|
|
974
|
+
headers: otelHeaders
|
|
794
975
|
}),
|
|
795
976
|
exportIntervalMillis: 5e3
|
|
796
977
|
}),
|
|
797
978
|
logRecordProcessors: [
|
|
798
979
|
new BatchLogRecordProcessor(
|
|
799
980
|
new OTLPLogExporter({
|
|
800
|
-
url: `${getEnvVar2("OTEL_EXPORTER_OTLP_ENDPOINT") ?? "http://localhost:4318"}/v1/logs
|
|
981
|
+
url: `${getEnvVar2("OTEL_EXPORTER_OTLP_ENDPOINT") ?? "http://localhost:4318"}/v1/logs`,
|
|
982
|
+
headers: otelHeaders
|
|
801
983
|
})
|
|
802
984
|
)
|
|
803
985
|
],
|
|
@@ -834,7 +1016,7 @@ function enrichDetails(path, contractDetails, requestSchema, responseSchemas, op
|
|
|
834
1016
|
req.requestSchema = requestSchema;
|
|
835
1017
|
res.responseSchemas = responseSchemas;
|
|
836
1018
|
req.openTelemetryCollector = openTelemetryCollector;
|
|
837
|
-
req._globalOptions = globalOptions;
|
|
1019
|
+
req._globalOptions = globalOptions ?? (() => void 0);
|
|
838
1020
|
req.context?.span?.setAttribute(ATTR_API_NAME, req.contractDetails?.name);
|
|
839
1021
|
const startTime = process.hrtime();
|
|
840
1022
|
res.on("finish", () => {
|
|
@@ -842,6 +1024,7 @@ function enrichDetails(path, contractDetails, requestSchema, responseSchemas, op
|
|
|
842
1024
|
const durationMs = seconds + nanoseconds / 1e9;
|
|
843
1025
|
httpServerDurationHistogram.record(durationMs, {
|
|
844
1026
|
[ATTR_SERVICE_NAME]: getEnvVar3("OTEL_SERVICE_NAME") || "unknown",
|
|
1027
|
+
[ATTR_APPLICATION_ID]: getEnvVar3("OTEL_APPLICATION_ID"),
|
|
845
1028
|
[ATTR_API_NAME]: req.contractDetails?.name || "unknown",
|
|
846
1029
|
[ATTR_HTTP_REQUEST_METHOD]: req.method,
|
|
847
1030
|
[ATTR_HTTP_ROUTE]: req.originalPath || "unknown",
|
|
@@ -872,6 +1055,7 @@ function isRequestShape(maybeResponseShape) {
|
|
|
872
1055
|
function parse(req, res, next) {
|
|
873
1056
|
const globalOptions = req._globalOptions?.();
|
|
874
1057
|
const collapsedOptions = req.contractDetails.options?.requestValidation ?? (globalOptions?.validation === false ? "none" : globalOptions?.validation?.request);
|
|
1058
|
+
req._rawBody = req.body;
|
|
875
1059
|
const request = {
|
|
876
1060
|
params: req.params,
|
|
877
1061
|
query: req.query,
|
|
@@ -961,7 +1145,7 @@ Correlation id: ${req.context.correlationId ?? "No correlation ID"}`
|
|
|
961
1145
|
}
|
|
962
1146
|
return;
|
|
963
1147
|
case "warning":
|
|
964
|
-
req.openTelemetryCollector
|
|
1148
|
+
req.openTelemetryCollector?.warn(
|
|
965
1149
|
collectedParseErrors ?? prettyPrintParseErrors2(parsedRequest.errors, "Request")
|
|
966
1150
|
);
|
|
967
1151
|
break;
|
|
@@ -969,143 +1153,221 @@ Correlation id: ${req.context.correlationId ?? "No correlation ID"}`
|
|
|
969
1153
|
break;
|
|
970
1154
|
}
|
|
971
1155
|
}
|
|
972
|
-
req._parsedVersions = matchedVersions;
|
|
973
|
-
next?.();
|
|
1156
|
+
req._parsedVersions = matchedVersions;
|
|
1157
|
+
next?.();
|
|
1158
|
+
}
|
|
1159
|
+
|
|
1160
|
+
// src/http/router/routerSharedLogic.ts
|
|
1161
|
+
function resolveContractDetailsAndHandlers(contractDetailsOrMiddlewareOrTypedHandler, middlewareOrMiddlewareAndTypedHandler) {
|
|
1162
|
+
let contractDetails;
|
|
1163
|
+
let handlers = [];
|
|
1164
|
+
if (isTypedHandler(contractDetailsOrMiddlewareOrTypedHandler)) {
|
|
1165
|
+
contractDetails = contractDetailsOrMiddlewareOrTypedHandler.contractDetails;
|
|
1166
|
+
handlers = contractDetailsOrMiddlewareOrTypedHandler.handlers;
|
|
1167
|
+
} else {
|
|
1168
|
+
const maybeTypedHandler = middlewareOrMiddlewareAndTypedHandler[middlewareOrMiddlewareAndTypedHandler.length - 1];
|
|
1169
|
+
if (isTypedHandler(maybeTypedHandler)) {
|
|
1170
|
+
contractDetails = maybeTypedHandler.contractDetails;
|
|
1171
|
+
const typedHandlerHandlers = maybeTypedHandler.handlers;
|
|
1172
|
+
const finalHandlers = [];
|
|
1173
|
+
if (isExpressLikeSchemaHandler(contractDetailsOrMiddlewareOrTypedHandler)) {
|
|
1174
|
+
finalHandlers.push(contractDetailsOrMiddlewareOrTypedHandler);
|
|
1175
|
+
}
|
|
1176
|
+
finalHandlers.push(...middlewareOrMiddlewareAndTypedHandler.slice(0, -1));
|
|
1177
|
+
finalHandlers.push(
|
|
1178
|
+
...typedHandlerHandlers
|
|
1179
|
+
);
|
|
1180
|
+
handlers = finalHandlers.filter(
|
|
1181
|
+
(handler) => isExpressLikeSchemaHandler(handler)
|
|
1182
|
+
);
|
|
1183
|
+
} else {
|
|
1184
|
+
if (isExpressLikeSchemaHandler(contractDetailsOrMiddlewareOrTypedHandler) || isTypedHandler(contractDetailsOrMiddlewareOrTypedHandler)) {
|
|
1185
|
+
throw new Error("Contract details are not defined");
|
|
1186
|
+
}
|
|
1187
|
+
contractDetails = contractDetailsOrMiddlewareOrTypedHandler;
|
|
1188
|
+
handlers = middlewareOrMiddlewareAndTypedHandler.filter(
|
|
1189
|
+
(handler) => isExpressLikeSchemaHandler(handler)
|
|
1190
|
+
);
|
|
1191
|
+
}
|
|
1192
|
+
}
|
|
1193
|
+
return { contractDetails, handlers };
|
|
974
1194
|
}
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
if (body == null) {
|
|
979
|
-
return void 0;
|
|
1195
|
+
function validateContractDetails(contractDetails, schemaValidator) {
|
|
1196
|
+
if (!isHttpContractDetails(contractDetails) && !isPathParamHttpContractDetails(contractDetails)) {
|
|
1197
|
+
throw new Error("Contract details are malformed for route definition");
|
|
980
1198
|
}
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
parserType: "json",
|
|
992
|
-
schema: maybeTypedBody.json
|
|
993
|
-
};
|
|
994
|
-
} else if ("file" in maybeTypedBody && maybeTypedBody.file != null) {
|
|
995
|
-
return {
|
|
996
|
-
contentType: maybeTypedBody.contentType ?? "application/octet-stream",
|
|
997
|
-
parserType: "file",
|
|
998
|
-
schema: maybeTypedBody.file
|
|
999
|
-
};
|
|
1000
|
-
} else if ("multipartForm" in maybeTypedBody && maybeTypedBody.multipartForm != null) {
|
|
1001
|
-
return {
|
|
1002
|
-
contentType: maybeTypedBody.contentType ?? "multipart/form-data",
|
|
1003
|
-
parserType: "multipart",
|
|
1004
|
-
schema: maybeTypedBody.multipartForm
|
|
1005
|
-
};
|
|
1006
|
-
} else if ("urlEncodedForm" in maybeTypedBody && maybeTypedBody.urlEncodedForm != null) {
|
|
1007
|
-
return {
|
|
1008
|
-
contentType: maybeTypedBody.contentType ?? "application/x-www-form-urlencoded",
|
|
1009
|
-
parserType: "urlEncoded",
|
|
1010
|
-
schema: maybeTypedBody.urlEncodedForm
|
|
1011
|
-
};
|
|
1012
|
-
} else if ("schema" in maybeTypedBody && maybeTypedBody.schema != null) {
|
|
1013
|
-
return {
|
|
1014
|
-
contentType: maybeTypedBody.contentType ?? "application/json",
|
|
1015
|
-
parserType: "text",
|
|
1016
|
-
schema: maybeTypedBody.schema
|
|
1017
|
-
};
|
|
1018
|
-
} else if (schemaValidator.isInstanceOf(
|
|
1019
|
-
maybeTypedBody,
|
|
1020
|
-
schemaValidator.string
|
|
1021
|
-
)) {
|
|
1022
|
-
return {
|
|
1023
|
-
contentType: "text/plain",
|
|
1024
|
-
parserType: "text",
|
|
1025
|
-
schema: maybeTypedBody
|
|
1026
|
-
};
|
|
1027
|
-
} else if (schemaValidator.openapi(maybeTypedBody).format === "binary") {
|
|
1028
|
-
return {
|
|
1029
|
-
contentType: "application/octet-stream",
|
|
1030
|
-
parserType: "file",
|
|
1031
|
-
schema: maybeTypedBody
|
|
1032
|
-
};
|
|
1033
|
-
} else {
|
|
1034
|
-
return {
|
|
1035
|
-
contentType: "application/json",
|
|
1036
|
-
parserType: "json",
|
|
1037
|
-
schema: maybeTypedBody
|
|
1038
|
-
};
|
|
1199
|
+
if (contractDetails.versions) {
|
|
1200
|
+
const parserTypes = Object.values(contractDetails.versions).map(
|
|
1201
|
+
(version) => discriminateBody(schemaValidator, version.body)?.parserType
|
|
1202
|
+
);
|
|
1203
|
+
const allParserTypesSame = parserTypes.length === 0 || parserTypes.every((pt) => pt === parserTypes[0]);
|
|
1204
|
+
if (!allParserTypesSame) {
|
|
1205
|
+
throw new Error(
|
|
1206
|
+
"All versioned contractDetails must have the same parsing type for body."
|
|
1207
|
+
);
|
|
1208
|
+
}
|
|
1039
1209
|
}
|
|
1040
1210
|
}
|
|
1041
|
-
function
|
|
1042
|
-
const
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
}
|
|
1063
|
-
}
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
schemaValidator.string
|
|
1078
|
-
)) {
|
|
1079
|
-
discriminatedResponses[Number(statusCode)] = {
|
|
1080
|
-
contentType: "text/plain",
|
|
1081
|
-
parserType: "text",
|
|
1082
|
-
schema: response
|
|
1083
|
-
};
|
|
1084
|
-
} else if (schemaValidator.openapi(response).format === "binary") {
|
|
1085
|
-
discriminatedResponses[Number(statusCode)] = {
|
|
1086
|
-
contentType: "application/octet-stream",
|
|
1087
|
-
parserType: "file",
|
|
1088
|
-
schema: response
|
|
1089
|
-
};
|
|
1090
|
-
} else {
|
|
1091
|
-
discriminatedResponses[Number(statusCode)] = {
|
|
1092
|
-
contentType: "application/json",
|
|
1093
|
-
parserType: "json",
|
|
1094
|
-
schema: response
|
|
1095
|
-
};
|
|
1096
|
-
}
|
|
1097
|
-
} else {
|
|
1098
|
-
discriminatedResponses[Number(statusCode)] = {
|
|
1099
|
-
contentType: "application/json",
|
|
1100
|
-
parserType: "json",
|
|
1101
|
-
schema: response
|
|
1102
|
-
};
|
|
1211
|
+
function processContractDetailsIO(schemaValidator, contractDetailsIO, routeParams) {
|
|
1212
|
+
const responseSchemas = {
|
|
1213
|
+
400: schemaValidator.string,
|
|
1214
|
+
401: schemaValidator.string,
|
|
1215
|
+
403: schemaValidator.string,
|
|
1216
|
+
404: schemaValidator.string,
|
|
1217
|
+
500: schemaValidator.string,
|
|
1218
|
+
...Object.fromEntries(
|
|
1219
|
+
Object.entries(
|
|
1220
|
+
discriminateResponseBodies(schemaValidator, contractDetailsIO.responses)
|
|
1221
|
+
).map(([key, value]) => [Number(key), value.schema])
|
|
1222
|
+
)
|
|
1223
|
+
};
|
|
1224
|
+
return {
|
|
1225
|
+
requestSchema: schemaValidator.compile(
|
|
1226
|
+
schemaValidator.schemify({
|
|
1227
|
+
...routeParams != null ? { params: routeParams } : { params: schemaValidator.unknown },
|
|
1228
|
+
...contractDetailsIO.requestHeaders != null ? { headers: contractDetailsIO.requestHeaders } : { headers: schemaValidator.unknown },
|
|
1229
|
+
...contractDetailsIO.query != null ? { query: contractDetailsIO.query } : { query: schemaValidator.unknown },
|
|
1230
|
+
...contractDetailsIO.body != null ? {
|
|
1231
|
+
body: discriminateBody(schemaValidator, contractDetailsIO.body)?.schema
|
|
1232
|
+
} : { body: schemaValidator.unknown }
|
|
1233
|
+
})
|
|
1234
|
+
),
|
|
1235
|
+
responseSchemas: {
|
|
1236
|
+
...contractDetailsIO.responseHeaders != null ? {
|
|
1237
|
+
headers: schemaValidator.compile(
|
|
1238
|
+
schemaValidator.schemify(contractDetailsIO.responseHeaders)
|
|
1239
|
+
)
|
|
1240
|
+
} : { headers: schemaValidator.unknown },
|
|
1241
|
+
responses: Object.fromEntries(
|
|
1242
|
+
Object.entries(responseSchemas).map(([key, value]) => [
|
|
1243
|
+
key,
|
|
1244
|
+
schemaValidator.compile(schemaValidator.schemify(value))
|
|
1245
|
+
])
|
|
1246
|
+
)
|
|
1103
1247
|
}
|
|
1248
|
+
};
|
|
1249
|
+
}
|
|
1250
|
+
function compileRouteSchemas(contractDetails, schemaValidator) {
|
|
1251
|
+
const validator = schemaValidator;
|
|
1252
|
+
let requestSchema;
|
|
1253
|
+
let responseSchemas;
|
|
1254
|
+
if (hasVersionedSchema(contractDetails)) {
|
|
1255
|
+
requestSchema = {};
|
|
1256
|
+
responseSchemas = {};
|
|
1257
|
+
Object.entries(contractDetails.versions ?? {}).forEach(
|
|
1258
|
+
([version, versionedContractDetails]) => {
|
|
1259
|
+
const {
|
|
1260
|
+
requestSchema: versionedRequestSchema,
|
|
1261
|
+
responseSchemas: versionedResponseSchemas
|
|
1262
|
+
} = processContractDetailsIO(
|
|
1263
|
+
validator,
|
|
1264
|
+
versionedContractDetails,
|
|
1265
|
+
contractDetails.params
|
|
1266
|
+
);
|
|
1267
|
+
if (isRecord2(requestSchema)) {
|
|
1268
|
+
requestSchema = {
|
|
1269
|
+
...requestSchema,
|
|
1270
|
+
[version]: versionedRequestSchema
|
|
1271
|
+
};
|
|
1272
|
+
}
|
|
1273
|
+
if (isRecord2(responseSchemas)) {
|
|
1274
|
+
responseSchemas = {
|
|
1275
|
+
...responseSchemas,
|
|
1276
|
+
[version]: versionedResponseSchemas
|
|
1277
|
+
};
|
|
1278
|
+
}
|
|
1279
|
+
}
|
|
1280
|
+
);
|
|
1281
|
+
} else {
|
|
1282
|
+
const {
|
|
1283
|
+
requestSchema: unversionedRequestSchema,
|
|
1284
|
+
responseSchemas: unversionedResponseSchemas
|
|
1285
|
+
} = processContractDetailsIO(
|
|
1286
|
+
validator,
|
|
1287
|
+
{
|
|
1288
|
+
..."params" in contractDetails && contractDetails.params != null ? { params: contractDetails.params } : { params: validator.unknown },
|
|
1289
|
+
..."requestHeaders" in contractDetails && contractDetails.requestHeaders != null ? { requestHeaders: contractDetails.requestHeaders } : {
|
|
1290
|
+
requestHeaders: validator.unknown
|
|
1291
|
+
},
|
|
1292
|
+
..."responseHeaders" in contractDetails && contractDetails.responseHeaders != null ? { responseHeaders: contractDetails.responseHeaders } : {
|
|
1293
|
+
responseHeaders: validator.unknown
|
|
1294
|
+
},
|
|
1295
|
+
..."query" in contractDetails && contractDetails.query != null ? { query: contractDetails.query } : {
|
|
1296
|
+
query: validator.unknown
|
|
1297
|
+
},
|
|
1298
|
+
..."body" in contractDetails && contractDetails.body != null ? { body: contractDetails.body } : {
|
|
1299
|
+
body: validator.unknown
|
|
1300
|
+
},
|
|
1301
|
+
responses: "responses" in contractDetails && contractDetails.responses != null ? contractDetails.responses : validator.unknown
|
|
1302
|
+
},
|
|
1303
|
+
contractDetails.params
|
|
1304
|
+
);
|
|
1305
|
+
requestSchema = unversionedRequestSchema;
|
|
1306
|
+
responseSchemas = unversionedResponseSchemas;
|
|
1104
1307
|
}
|
|
1105
|
-
return
|
|
1308
|
+
return { requestSchema, responseSchemas };
|
|
1309
|
+
}
|
|
1310
|
+
function resolveRouteMiddlewares(params) {
|
|
1311
|
+
const handlersCopy = [...params.handlers];
|
|
1312
|
+
const controllerHandler = handlersCopy.pop();
|
|
1313
|
+
if (typeof controllerHandler !== "function") {
|
|
1314
|
+
throw new Error(
|
|
1315
|
+
`Last argument must be a handler, received: ${controllerHandler}`
|
|
1316
|
+
);
|
|
1317
|
+
}
|
|
1318
|
+
const middlewares = [
|
|
1319
|
+
...params.includeCreateContext !== false ? [createContext] : [],
|
|
1320
|
+
enrichDetails(
|
|
1321
|
+
`${params.basePath}${params.path}`,
|
|
1322
|
+
params.contractDetails,
|
|
1323
|
+
params.requestSchema,
|
|
1324
|
+
params.responseSchemas,
|
|
1325
|
+
params.openTelemetryCollector,
|
|
1326
|
+
() => params.routerOptions
|
|
1327
|
+
),
|
|
1328
|
+
...params.postEnrichMiddleware,
|
|
1329
|
+
parse,
|
|
1330
|
+
parseRequestAuth,
|
|
1331
|
+
...handlersCopy
|
|
1332
|
+
];
|
|
1333
|
+
return {
|
|
1334
|
+
middlewares,
|
|
1335
|
+
controllerHandler
|
|
1336
|
+
};
|
|
1106
1337
|
}
|
|
1107
1338
|
|
|
1108
1339
|
// src/http/router/expressLikeRouter.ts
|
|
1340
|
+
function extractRouteHandlers(params) {
|
|
1341
|
+
const schemaValidator = params.schemaValidator;
|
|
1342
|
+
const { contractDetails, handlers } = resolveContractDetailsAndHandlers(
|
|
1343
|
+
params.contractDetailsOrMiddlewareOrTypedHandler,
|
|
1344
|
+
params.middlewareOrMiddlewareAndTypedHandler
|
|
1345
|
+
);
|
|
1346
|
+
validateContractDetails(contractDetails, schemaValidator);
|
|
1347
|
+
const { requestSchema, responseSchemas } = compileRouteSchemas(
|
|
1348
|
+
contractDetails,
|
|
1349
|
+
schemaValidator
|
|
1350
|
+
);
|
|
1351
|
+
const { middlewares, controllerHandler } = resolveRouteMiddlewares({
|
|
1352
|
+
basePath: params.basePath,
|
|
1353
|
+
path: params.path,
|
|
1354
|
+
contractDetails,
|
|
1355
|
+
requestSchema,
|
|
1356
|
+
responseSchemas,
|
|
1357
|
+
openTelemetryCollector: params.openTelemetryCollector,
|
|
1358
|
+
routerOptions: params.routerOptions,
|
|
1359
|
+
postEnrichMiddleware: params.postEnrichMiddleware ?? [],
|
|
1360
|
+
handlers,
|
|
1361
|
+
includeCreateContext: false
|
|
1362
|
+
});
|
|
1363
|
+
return {
|
|
1364
|
+
middlewares: [
|
|
1365
|
+
createContext(schemaValidator),
|
|
1366
|
+
...middlewares
|
|
1367
|
+
],
|
|
1368
|
+
controllerHandler
|
|
1369
|
+
};
|
|
1370
|
+
}
|
|
1109
1371
|
var ForklaunchExpressLikeRouter = class _ForklaunchExpressLikeRouter {
|
|
1110
1372
|
constructor(basePath, schemaValidator, internal, postEnrichMiddleware, openTelemetryCollector, routerOptions) {
|
|
1111
1373
|
this.basePath = basePath;
|
|
@@ -1145,21 +1407,6 @@ var ForklaunchExpressLikeRouter = class _ForklaunchExpressLikeRouter {
|
|
|
1145
1407
|
* @param {PathParamHttpContractDetails<SV> | HttpContractDetails<SV>} contractDetails - The contract details.
|
|
1146
1408
|
* @returns {MiddlewareHandler<SV>[]} - The resolved middlewares.
|
|
1147
1409
|
*/
|
|
1148
|
-
#resolveMiddlewares(path, contractDetails, requestSchema, responseSchemas) {
|
|
1149
|
-
return [
|
|
1150
|
-
enrichDetails(
|
|
1151
|
-
`${this.basePath}${path}`,
|
|
1152
|
-
contractDetails,
|
|
1153
|
-
requestSchema,
|
|
1154
|
-
responseSchemas,
|
|
1155
|
-
this.openTelemetryCollector,
|
|
1156
|
-
() => this.routerOptions
|
|
1157
|
-
),
|
|
1158
|
-
...this.postEnrichMiddleware,
|
|
1159
|
-
parse,
|
|
1160
|
-
parseRequestAuth
|
|
1161
|
-
];
|
|
1162
|
-
}
|
|
1163
1410
|
/**
|
|
1164
1411
|
* Parses and runs the controller handler with error handling.
|
|
1165
1412
|
*
|
|
@@ -1200,126 +1447,6 @@ var ForklaunchExpressLikeRouter = class _ForklaunchExpressLikeRouter {
|
|
|
1200
1447
|
* @returns {MiddlewareHandler<SV, P, ResBodyMap, ReqBody, ReqQuery, LocalsObj>} - The extracted controller handler.
|
|
1201
1448
|
* @throws {Error} - Throws an error if the last argument is not a handler.
|
|
1202
1449
|
*/
|
|
1203
|
-
#extractControllerHandler(handlers) {
|
|
1204
|
-
const controllerHandler = handlers.pop();
|
|
1205
|
-
if (typeof controllerHandler !== "function") {
|
|
1206
|
-
throw new Error(
|
|
1207
|
-
`Last argument must be a handler, received: ${controllerHandler}`
|
|
1208
|
-
);
|
|
1209
|
-
}
|
|
1210
|
-
return controllerHandler;
|
|
1211
|
-
}
|
|
1212
|
-
#processContractDetailsIO(contractDetailsIO, params) {
|
|
1213
|
-
const schemaValidator = this.schemaValidator;
|
|
1214
|
-
const responseSchemas = {
|
|
1215
|
-
400: schemaValidator.string,
|
|
1216
|
-
401: schemaValidator.string,
|
|
1217
|
-
403: schemaValidator.string,
|
|
1218
|
-
404: schemaValidator.string,
|
|
1219
|
-
500: schemaValidator.string,
|
|
1220
|
-
...Object.fromEntries(
|
|
1221
|
-
Object.entries(
|
|
1222
|
-
discriminateResponseBodies(
|
|
1223
|
-
this.schemaValidator,
|
|
1224
|
-
contractDetailsIO.responses
|
|
1225
|
-
)
|
|
1226
|
-
).map(([key, value]) => {
|
|
1227
|
-
return [Number(key), value.schema];
|
|
1228
|
-
})
|
|
1229
|
-
)
|
|
1230
|
-
};
|
|
1231
|
-
return {
|
|
1232
|
-
requestSchema: schemaValidator.compile(
|
|
1233
|
-
schemaValidator.schemify({
|
|
1234
|
-
...params != null ? { params } : { params: schemaValidator.unknown },
|
|
1235
|
-
...contractDetailsIO.requestHeaders != null ? { headers: contractDetailsIO.requestHeaders } : { headers: schemaValidator.unknown },
|
|
1236
|
-
...contractDetailsIO.query != null ? { query: contractDetailsIO.query } : { query: schemaValidator.unknown },
|
|
1237
|
-
...contractDetailsIO.body != null ? {
|
|
1238
|
-
body: discriminateBody(
|
|
1239
|
-
this.schemaValidator,
|
|
1240
|
-
contractDetailsIO.body
|
|
1241
|
-
)?.schema
|
|
1242
|
-
} : { body: schemaValidator.unknown }
|
|
1243
|
-
})
|
|
1244
|
-
),
|
|
1245
|
-
responseSchemas: {
|
|
1246
|
-
...contractDetailsIO.responseHeaders != null ? {
|
|
1247
|
-
headers: schemaValidator.compile(
|
|
1248
|
-
schemaValidator.schemify(contractDetailsIO.responseHeaders)
|
|
1249
|
-
)
|
|
1250
|
-
} : { headers: schemaValidator.unknown },
|
|
1251
|
-
responses: Object.fromEntries(
|
|
1252
|
-
Object.entries(responseSchemas).map(([key, value]) => {
|
|
1253
|
-
return [
|
|
1254
|
-
key,
|
|
1255
|
-
schemaValidator.compile(schemaValidator.schemify(value))
|
|
1256
|
-
];
|
|
1257
|
-
})
|
|
1258
|
-
)
|
|
1259
|
-
}
|
|
1260
|
-
};
|
|
1261
|
-
}
|
|
1262
|
-
#compile(contractDetails) {
|
|
1263
|
-
const schemaValidator = this.schemaValidator;
|
|
1264
|
-
let requestSchema;
|
|
1265
|
-
let responseSchemas;
|
|
1266
|
-
if (hasVersionedSchema(contractDetails)) {
|
|
1267
|
-
requestSchema = {};
|
|
1268
|
-
responseSchemas = {};
|
|
1269
|
-
Object.entries(contractDetails.versions ?? {}).forEach(
|
|
1270
|
-
([version, versionedContractDetails]) => {
|
|
1271
|
-
const {
|
|
1272
|
-
requestSchema: versionedRequestSchema,
|
|
1273
|
-
responseSchemas: versionedResponseSchemas
|
|
1274
|
-
} = this.#processContractDetailsIO(
|
|
1275
|
-
versionedContractDetails,
|
|
1276
|
-
contractDetails.params
|
|
1277
|
-
);
|
|
1278
|
-
if (isRecord2(requestSchema)) {
|
|
1279
|
-
requestSchema = {
|
|
1280
|
-
...requestSchema,
|
|
1281
|
-
[version]: versionedRequestSchema
|
|
1282
|
-
};
|
|
1283
|
-
}
|
|
1284
|
-
if (isRecord2(responseSchemas)) {
|
|
1285
|
-
responseSchemas = {
|
|
1286
|
-
...responseSchemas,
|
|
1287
|
-
[version]: versionedResponseSchemas
|
|
1288
|
-
};
|
|
1289
|
-
}
|
|
1290
|
-
}
|
|
1291
|
-
);
|
|
1292
|
-
} else {
|
|
1293
|
-
const {
|
|
1294
|
-
requestSchema: unversionedRequestSchema,
|
|
1295
|
-
responseSchemas: unversionedResponseSchemas
|
|
1296
|
-
} = this.#processContractDetailsIO(
|
|
1297
|
-
{
|
|
1298
|
-
..."params" in contractDetails && contractDetails.params != null ? { params: contractDetails.params } : { params: schemaValidator.unknown },
|
|
1299
|
-
..."requestHeaders" in contractDetails && contractDetails.requestHeaders != null ? { requestHeaders: contractDetails.requestHeaders } : {
|
|
1300
|
-
requestHeaders: schemaValidator.unknown
|
|
1301
|
-
},
|
|
1302
|
-
..."responseHeaders" in contractDetails && contractDetails.responseHeaders != null ? { responseHeaders: contractDetails.responseHeaders } : {
|
|
1303
|
-
responseHeaders: schemaValidator.unknown
|
|
1304
|
-
},
|
|
1305
|
-
..."query" in contractDetails && contractDetails.query != null ? { query: contractDetails.query } : {
|
|
1306
|
-
query: schemaValidator.unknown
|
|
1307
|
-
},
|
|
1308
|
-
..."body" in contractDetails && contractDetails.body != null ? { body: contractDetails.body } : {
|
|
1309
|
-
body: schemaValidator.unknown
|
|
1310
|
-
},
|
|
1311
|
-
responses: "responses" in contractDetails && contractDetails.responses != null ? contractDetails.responses : schemaValidator.unknown
|
|
1312
|
-
},
|
|
1313
|
-
contractDetails.params
|
|
1314
|
-
);
|
|
1315
|
-
requestSchema = unversionedRequestSchema;
|
|
1316
|
-
responseSchemas = unversionedResponseSchemas;
|
|
1317
|
-
}
|
|
1318
|
-
return {
|
|
1319
|
-
requestSchema,
|
|
1320
|
-
responseSchemas
|
|
1321
|
-
};
|
|
1322
|
-
}
|
|
1323
1450
|
/**
|
|
1324
1451
|
* Fetches a route from the route map and executes it with the given parameters.
|
|
1325
1452
|
*
|
|
@@ -1409,92 +1536,71 @@ var ForklaunchExpressLikeRouter = class _ForklaunchExpressLikeRouter {
|
|
|
1409
1536
|
};
|
|
1410
1537
|
}
|
|
1411
1538
|
registerRoute(method, path, registrationMethod, contractDetailsOrMiddlewareOrTypedHandler, ...middlewareOrMiddlewareAndTypedHandler) {
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
|
|
1468
|
-
|
|
1469
|
-
path
|
|
1470
|
-
|
|
1471
|
-
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
[method.toUpperCase()]: contractDetails.versions ? Object.fromEntries(
|
|
1476
|
-
Object.keys(contractDetails.versions).map((version) => [
|
|
1477
|
-
version,
|
|
1478
|
-
this.#localParamRequest(handlers, controllerHandler, version)
|
|
1479
|
-
])
|
|
1480
|
-
) : this.#localParamRequest(handlers, controllerHandler)
|
|
1481
|
-
};
|
|
1482
|
-
toRecord(this.sdk)[toPrettyCamelCase(contractDetails.name)] = contractDetails.versions ? Object.fromEntries(
|
|
1483
|
-
Object.keys(contractDetails.versions).map((version) => [
|
|
1484
|
-
version,
|
|
1485
|
-
(req) => this.#localParamRequest(
|
|
1486
|
-
handlers,
|
|
1487
|
-
controllerHandler,
|
|
1488
|
-
version
|
|
1489
|
-
)(`${this.basePath}${path}`, req)
|
|
1490
|
-
])
|
|
1491
|
-
) : (req) => this.#localParamRequest(handlers, controllerHandler)(
|
|
1492
|
-
`${this.basePath}${path}`,
|
|
1493
|
-
req
|
|
1494
|
-
);
|
|
1495
|
-
return this;
|
|
1496
|
-
}
|
|
1539
|
+
const { contractDetails, handlers } = resolveContractDetailsAndHandlers(
|
|
1540
|
+
contractDetailsOrMiddlewareOrTypedHandler,
|
|
1541
|
+
middlewareOrMiddlewareAndTypedHandler
|
|
1542
|
+
);
|
|
1543
|
+
validateContractDetails(contractDetails, this.schemaValidator);
|
|
1544
|
+
this.routes.push({
|
|
1545
|
+
basePath: this.basePath,
|
|
1546
|
+
path,
|
|
1547
|
+
method,
|
|
1548
|
+
contractDetails
|
|
1549
|
+
});
|
|
1550
|
+
const { requestSchema, responseSchemas } = compileRouteSchemas(
|
|
1551
|
+
contractDetails,
|
|
1552
|
+
this.schemaValidator
|
|
1553
|
+
);
|
|
1554
|
+
const { middlewares, controllerHandler } = resolveRouteMiddlewares({
|
|
1555
|
+
basePath: this.basePath,
|
|
1556
|
+
path,
|
|
1557
|
+
contractDetails,
|
|
1558
|
+
requestSchema,
|
|
1559
|
+
responseSchemas,
|
|
1560
|
+
openTelemetryCollector: this.openTelemetryCollector,
|
|
1561
|
+
routerOptions: this.routerOptions,
|
|
1562
|
+
postEnrichMiddleware: this.postEnrichMiddleware,
|
|
1563
|
+
includeCreateContext: false,
|
|
1564
|
+
handlers
|
|
1565
|
+
});
|
|
1566
|
+
registrationMethod.bind(this.internal)(
|
|
1567
|
+
path,
|
|
1568
|
+
...middlewares,
|
|
1569
|
+
this.#parseAndRunControllerHandler(controllerHandler)
|
|
1570
|
+
);
|
|
1571
|
+
toRecord(this._fetchMap)[sanitizePathSlashes(`${this.basePath}${path}`)] = {
|
|
1572
|
+
...this._fetchMap[sanitizePathSlashes(`${this.basePath}${path}`)] ?? {},
|
|
1573
|
+
[method.toUpperCase()]: contractDetails.versions ? Object.fromEntries(
|
|
1574
|
+
Object.keys(contractDetails.versions).map((version) => [
|
|
1575
|
+
version,
|
|
1576
|
+
this.#localParamRequest(
|
|
1577
|
+
middlewares,
|
|
1578
|
+
controllerHandler,
|
|
1579
|
+
version
|
|
1580
|
+
)
|
|
1581
|
+
])
|
|
1582
|
+
) : this.#localParamRequest(
|
|
1583
|
+
middlewares,
|
|
1584
|
+
controllerHandler
|
|
1585
|
+
)
|
|
1586
|
+
};
|
|
1587
|
+
const contractDetailsName = contractDetails.name;
|
|
1588
|
+
if (contractDetailsName) {
|
|
1589
|
+
toRecord(this.sdk)[toPrettyCamelCase(contractDetailsName)] = contractDetails.versions ? Object.fromEntries(
|
|
1590
|
+
Object.keys(contractDetails.versions).map((version) => [
|
|
1591
|
+
version,
|
|
1592
|
+
(req) => this.#localParamRequest(
|
|
1593
|
+
middlewares,
|
|
1594
|
+
controllerHandler,
|
|
1595
|
+
version
|
|
1596
|
+
)(`${this.basePath}${path}`, req)
|
|
1597
|
+
])
|
|
1598
|
+
) : (req) => this.#localParamRequest(
|
|
1599
|
+
middlewares,
|
|
1600
|
+
controllerHandler
|
|
1601
|
+
)(`${this.basePath}${path}`, req);
|
|
1497
1602
|
}
|
|
1603
|
+
return this;
|
|
1498
1604
|
}
|
|
1499
1605
|
#extractHandlers(handlers, processMiddleware) {
|
|
1500
1606
|
const last = handlers.pop();
|
|
@@ -1873,7 +1979,7 @@ var ForklaunchExpressLikeRouter = class _ForklaunchExpressLikeRouter {
|
|
|
1873
1979
|
unpackSdks(sdks, path, routerUniquenessCache) {
|
|
1874
1980
|
Object.entries(sdks).forEach(([key, maybeHandler]) => {
|
|
1875
1981
|
if (isSdkHandler(maybeHandler)) {
|
|
1876
|
-
const cacheKey = hashString(
|
|
1982
|
+
const cacheKey = hashString(safeStringify3(maybeHandler));
|
|
1877
1983
|
if (routerUniquenessCache.has(cacheKey)) {
|
|
1878
1984
|
throw new Error(`SDK handler ${key} is already registered`);
|
|
1879
1985
|
}
|
|
@@ -1967,6 +2073,30 @@ function isPortBound(port, host = "localhost") {
|
|
|
1967
2073
|
});
|
|
1968
2074
|
}
|
|
1969
2075
|
|
|
2076
|
+
// src/http/generateHmacAuthHeaders.ts
|
|
2077
|
+
import { randomUUID } from "crypto";
|
|
2078
|
+
function generateHmacAuthHeaders({
|
|
2079
|
+
secretKey,
|
|
2080
|
+
method,
|
|
2081
|
+
path,
|
|
2082
|
+
body,
|
|
2083
|
+
keyId = "default"
|
|
2084
|
+
}) {
|
|
2085
|
+
const timestamp = /* @__PURE__ */ new Date();
|
|
2086
|
+
const nonce = randomUUID();
|
|
2087
|
+
const signature = createHmacToken({
|
|
2088
|
+
secretKey,
|
|
2089
|
+
method,
|
|
2090
|
+
path,
|
|
2091
|
+
timestamp,
|
|
2092
|
+
nonce,
|
|
2093
|
+
body
|
|
2094
|
+
});
|
|
2095
|
+
return {
|
|
2096
|
+
authorization: `HMAC keyId=${keyId} ts=${timestamp.toISOString()} nonce=${nonce} signature=${signature}`
|
|
2097
|
+
};
|
|
2098
|
+
}
|
|
2099
|
+
|
|
1970
2100
|
// src/http/guards/isPath.ts
|
|
1971
2101
|
function isPath(path) {
|
|
1972
2102
|
return path.startsWith("/");
|
|
@@ -3037,7 +3167,7 @@ var getCodeForStatus = (status) => {
|
|
|
3037
3167
|
var httpStatusCodes_default = HTTPStatuses;
|
|
3038
3168
|
|
|
3039
3169
|
// src/http/mcpGenerator/mcpGenerator.ts
|
|
3040
|
-
import { isNever as isNever3, isRecord as isRecord3, safeStringify as
|
|
3170
|
+
import { isNever as isNever3, isRecord as isRecord3, safeStringify as safeStringify4 } from "@forklaunch/common";
|
|
3041
3171
|
import { FastMCP } from "@forklaunch/fastmcp-fork";
|
|
3042
3172
|
import { string, ZodSchemaValidator } from "@forklaunch/validator/zod";
|
|
3043
3173
|
|
|
@@ -3172,7 +3302,7 @@ function generateMcpServer(schemaValidator, protocol, host, port, version, appli
|
|
|
3172
3302
|
if (discriminatedBody) {
|
|
3173
3303
|
switch (discriminatedBody.parserType) {
|
|
3174
3304
|
case "json": {
|
|
3175
|
-
parsedBody =
|
|
3305
|
+
parsedBody = safeStringify4(body);
|
|
3176
3306
|
break;
|
|
3177
3307
|
}
|
|
3178
3308
|
case "text": {
|
|
@@ -3180,7 +3310,7 @@ function generateMcpServer(schemaValidator, protocol, host, port, version, appli
|
|
|
3180
3310
|
break;
|
|
3181
3311
|
}
|
|
3182
3312
|
case "file": {
|
|
3183
|
-
parsedBody = Buffer.from(
|
|
3313
|
+
parsedBody = Buffer.from(safeStringify4(body));
|
|
3184
3314
|
break;
|
|
3185
3315
|
}
|
|
3186
3316
|
case "multipart": {
|
|
@@ -3214,7 +3344,7 @@ function generateMcpServer(schemaValidator, protocol, host, port, version, appli
|
|
|
3214
3344
|
parsedBody = new URLSearchParams(
|
|
3215
3345
|
Object.entries(body).map(([key, value]) => [
|
|
3216
3346
|
key,
|
|
3217
|
-
|
|
3347
|
+
safeStringify4(value)
|
|
3218
3348
|
])
|
|
3219
3349
|
);
|
|
3220
3350
|
} else {
|
|
@@ -3224,7 +3354,7 @@ function generateMcpServer(schemaValidator, protocol, host, port, version, appli
|
|
|
3224
3354
|
}
|
|
3225
3355
|
default: {
|
|
3226
3356
|
isNever3(discriminatedBody.parserType);
|
|
3227
|
-
parsedBody =
|
|
3357
|
+
parsedBody = safeStringify4(body);
|
|
3228
3358
|
break;
|
|
3229
3359
|
}
|
|
3230
3360
|
}
|
|
@@ -3233,7 +3363,7 @@ function generateMcpServer(schemaValidator, protocol, host, port, version, appli
|
|
|
3233
3363
|
const queryString = new URLSearchParams(
|
|
3234
3364
|
Object.entries(query).map(([key, value]) => [
|
|
3235
3365
|
key,
|
|
3236
|
-
|
|
3366
|
+
safeStringify4(value)
|
|
3237
3367
|
])
|
|
3238
3368
|
).toString();
|
|
3239
3369
|
url += queryString ? `?${queryString}` : "";
|
|
@@ -3266,7 +3396,7 @@ function generateMcpServer(schemaValidator, protocol, host, port, version, appli
|
|
|
3266
3396
|
content: [
|
|
3267
3397
|
{
|
|
3268
3398
|
type: "text",
|
|
3269
|
-
text:
|
|
3399
|
+
text: safeStringify4(await response.json())
|
|
3270
3400
|
}
|
|
3271
3401
|
]
|
|
3272
3402
|
};
|
|
@@ -3410,7 +3540,7 @@ Correlation id: ${req.context.correlationId ?? "No correlation ID"}`
|
|
|
3410
3540
|
}
|
|
3411
3541
|
return;
|
|
3412
3542
|
case "warning":
|
|
3413
|
-
req.openTelemetryCollector
|
|
3543
|
+
req.openTelemetryCollector?.warn(
|
|
3414
3544
|
`Invalid response:
|
|
3415
3545
|
${parseErrors.join("\n\n")}`
|
|
3416
3546
|
);
|
|
@@ -3429,7 +3559,7 @@ import {
|
|
|
3429
3559
|
isNodeJsWriteableStream,
|
|
3430
3560
|
isRecord as isRecord4,
|
|
3431
3561
|
readableStreamToAsyncIterable,
|
|
3432
|
-
safeStringify as
|
|
3562
|
+
safeStringify as safeStringify5
|
|
3433
3563
|
} from "@forklaunch/common";
|
|
3434
3564
|
import { Readable, Transform } from "stream";
|
|
3435
3565
|
|
|
@@ -3447,6 +3577,7 @@ function recordMetric(req, res) {
|
|
|
3447
3577
|
}
|
|
3448
3578
|
httpRequestsTotalCounter.add(1, {
|
|
3449
3579
|
[ATTR_SERVICE_NAME3]: getEnvVar4("OTEL_SERVICE_NAME"),
|
|
3580
|
+
[ATTR_APPLICATION_ID]: getEnvVar4("OTEL_APPLICATION_ID"),
|
|
3450
3581
|
[ATTR_API_NAME]: req.contractDetails?.name,
|
|
3451
3582
|
[ATTR_HTTP_REQUEST_METHOD3]: req.method,
|
|
3452
3583
|
[ATTR_HTTP_ROUTE3]: req.originalPath,
|
|
@@ -3465,7 +3596,7 @@ function enrichExpressLikeSend(instance, req, res, originalOperation, originalSe
|
|
|
3465
3596
|
if (res.statusCode === 404) {
|
|
3466
3597
|
res.type("text/plain");
|
|
3467
3598
|
res.status(404);
|
|
3468
|
-
req.openTelemetryCollector
|
|
3599
|
+
req.openTelemetryCollector?.error("Not Found");
|
|
3469
3600
|
originalSend.call(instance, "Not Found");
|
|
3470
3601
|
errorSent = true;
|
|
3471
3602
|
}
|
|
@@ -3520,7 +3651,7 @@ function enrichExpressLikeSend(instance, req, res, originalOperation, originalSe
|
|
|
3520
3651
|
------------------
|
|
3521
3652
|
${res.locals.errorMessage}`;
|
|
3522
3653
|
}
|
|
3523
|
-
req.openTelemetryCollector
|
|
3654
|
+
req.openTelemetryCollector?.error(errorString);
|
|
3524
3655
|
res.type("text/plain");
|
|
3525
3656
|
res.status(500);
|
|
3526
3657
|
originalSend.call(instance, errorString);
|
|
@@ -3529,7 +3660,7 @@ ${res.locals.errorMessage}`;
|
|
|
3529
3660
|
} else {
|
|
3530
3661
|
let data2 = "";
|
|
3531
3662
|
for (const [key, value] of Object.entries(chunk)) {
|
|
3532
|
-
data2 += `${key}: ${typeof value === "string" ? value :
|
|
3663
|
+
data2 += `${key}: ${typeof value === "string" ? value : safeStringify5(value)}
|
|
3533
3664
|
`;
|
|
3534
3665
|
}
|
|
3535
3666
|
data2 += "\n";
|
|
@@ -3540,7 +3671,7 @@ ${res.locals.errorMessage}`;
|
|
|
3540
3671
|
} else if (!errorSent) {
|
|
3541
3672
|
let data2 = "";
|
|
3542
3673
|
for (const [key, value] of Object.entries(chunk)) {
|
|
3543
|
-
data2 += `${key}: ${typeof value === "string" ? value :
|
|
3674
|
+
data2 += `${key}: ${typeof value === "string" ? value : safeStringify5(value)}
|
|
3544
3675
|
`;
|
|
3545
3676
|
}
|
|
3546
3677
|
data2 += "\n";
|
|
@@ -3593,7 +3724,7 @@ ${res.locals.errorMessage}`;
|
|
|
3593
3724
|
------------------
|
|
3594
3725
|
${res.locals.errorMessage}`;
|
|
3595
3726
|
}
|
|
3596
|
-
req.openTelemetryCollector
|
|
3727
|
+
req.openTelemetryCollector?.error(errorString);
|
|
3597
3728
|
res.type("text/plain");
|
|
3598
3729
|
res.status(500);
|
|
3599
3730
|
originalSend.call(instance, errorString);
|
|
@@ -3616,7 +3747,7 @@ ${res.locals.errorMessage}`;
|
|
|
3616
3747
|
|
|
3617
3748
|
// src/http/openApiV3Generator/openApiV3Generator.ts
|
|
3618
3749
|
import { openApiCompliantPath } from "@forklaunch/common";
|
|
3619
|
-
var OPENAPI_DEFAULT_VERSION = Symbol("default");
|
|
3750
|
+
var OPENAPI_DEFAULT_VERSION = /* @__PURE__ */ Symbol("default");
|
|
3620
3751
|
function toUpperCase(str) {
|
|
3621
3752
|
return str.charAt(0).toUpperCase() + str.slice(1);
|
|
3622
3753
|
}
|
|
@@ -3964,6 +4095,7 @@ function metricsDefinitions(metrics2) {
|
|
|
3964
4095
|
}
|
|
3965
4096
|
export {
|
|
3966
4097
|
ATTR_API_NAME,
|
|
4098
|
+
ATTR_APPLICATION_ID,
|
|
3967
4099
|
ATTR_CORRELATION_ID,
|
|
3968
4100
|
ATTR_HTTP_REQUEST_METHOD,
|
|
3969
4101
|
ATTR_HTTP_RESPONSE_STATUS_CODE,
|
|
@@ -3975,6 +4107,7 @@ export {
|
|
|
3975
4107
|
OPENAPI_DEFAULT_VERSION,
|
|
3976
4108
|
OpenTelemetryCollector,
|
|
3977
4109
|
PinoLogger,
|
|
4110
|
+
createContext,
|
|
3978
4111
|
createHmacToken,
|
|
3979
4112
|
delete_,
|
|
3980
4113
|
discriminateAuthMethod,
|
|
@@ -3982,6 +4115,8 @@ export {
|
|
|
3982
4115
|
discriminateResponseBodies,
|
|
3983
4116
|
enrichExpressLikeSend,
|
|
3984
4117
|
evaluateTelemetryOptions,
|
|
4118
|
+
extractRouteHandlers,
|
|
4119
|
+
generateHmacAuthHeaders,
|
|
3985
4120
|
generateMcpServer,
|
|
3986
4121
|
generateOpenApiSpecs,
|
|
3987
4122
|
get,
|