@forklaunch/core 0.3.6 → 0.5.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/controllers/index.d.mts +6 -1
- package/lib/controllers/index.d.ts +6 -1
- package/lib/http/index.d.mts +206 -163
- package/lib/http/index.d.ts +206 -163
- package/lib/http/index.js +435 -136
- package/lib/http/index.js.map +1 -1
- package/lib/http/index.mjs +441 -138
- package/lib/http/index.mjs.map +1 -1
- package/package.json +32 -13
package/lib/http/index.mjs
CHANGED
@@ -1,3 +1,48 @@
|
|
1
|
+
// src/http/middleware/request/cors.middleware.ts
|
2
|
+
import corsMiddleware from "cors";
|
3
|
+
function cors(req, res, next) {
|
4
|
+
if (req.method === "OPTIONS") {
|
5
|
+
res.cors = true;
|
6
|
+
}
|
7
|
+
corsMiddleware()(req, res, next ?? (() => {
|
8
|
+
}));
|
9
|
+
}
|
10
|
+
|
11
|
+
// src/http/middleware/request/createContext.middleware.ts
|
12
|
+
import { trace } from "@opentelemetry/api";
|
13
|
+
import { v4 } from "uuid";
|
14
|
+
|
15
|
+
// src/http/telemetry/constants.ts
|
16
|
+
import {
|
17
|
+
ATTR_HTTP_REQUEST_METHOD,
|
18
|
+
ATTR_HTTP_RESPONSE_STATUS_CODE,
|
19
|
+
ATTR_HTTP_ROUTE,
|
20
|
+
ATTR_SERVICE_NAME
|
21
|
+
} from "@opentelemetry/semantic-conventions";
|
22
|
+
var ATTR_API_NAME = "api.name";
|
23
|
+
var ATTR_CORRELATION_ID = "correlation.id";
|
24
|
+
|
25
|
+
// src/http/middleware/request/createContext.middleware.ts
|
26
|
+
function createContext(schemaValidator) {
|
27
|
+
return function setContext(req, res, next) {
|
28
|
+
req.schemaValidator = schemaValidator;
|
29
|
+
let correlationId = v4();
|
30
|
+
if (req.headers["x-correlation-id"]) {
|
31
|
+
correlationId = req.headers["x-correlation-id"];
|
32
|
+
}
|
33
|
+
res.setHeader("x-correlation-id", correlationId);
|
34
|
+
req.context = {
|
35
|
+
correlationId
|
36
|
+
};
|
37
|
+
const span = trace.getActiveSpan();
|
38
|
+
if (span != null) {
|
39
|
+
req.context.span = span;
|
40
|
+
req.context.span?.setAttribute(ATTR_CORRELATION_ID, correlationId);
|
41
|
+
}
|
42
|
+
next?.();
|
43
|
+
};
|
44
|
+
}
|
45
|
+
|
1
46
|
// src/http/guards/isForklaunchRouter.ts
|
2
47
|
function isForklaunchRouter(maybeForklaunchRouter) {
|
3
48
|
return maybeForklaunchRouter != null && typeof maybeForklaunchRouter === "object" && "basePath" in maybeForklaunchRouter && "routes" in maybeForklaunchRouter && Array.isArray(maybeForklaunchRouter.routes);
|
@@ -157,58 +202,29 @@ async function checkAuthorizationToken(authorizationMethod, authorizationToken,
|
|
157
202
|
return [401, "Invalid Authorization method."];
|
158
203
|
}
|
159
204
|
async function parseRequestAuth(req, res, next) {
|
160
|
-
console.debug("[MIDDLEWARE] parseRequest started");
|
161
205
|
const auth = req.contractDetails.auth;
|
162
206
|
if (auth) {
|
163
|
-
const
|
207
|
+
const [error, message] = await checkAuthorizationToken(
|
164
208
|
auth,
|
165
209
|
req.headers[(auth.method === "other" ? auth.headerName : void 0) ?? "Authorization"],
|
166
210
|
req
|
167
|
-
);
|
168
|
-
if (
|
169
|
-
res.status(
|
170
|
-
next?.(new Error(
|
211
|
+
) ?? [];
|
212
|
+
if (error != null) {
|
213
|
+
res.status(error).send(message);
|
214
|
+
next?.(new Error(message));
|
171
215
|
}
|
172
216
|
}
|
173
217
|
next?.();
|
174
218
|
}
|
175
219
|
|
176
|
-
// src/http/middleware/request/cors.middleware.ts
|
177
|
-
import corsMiddleware from "cors";
|
178
|
-
function cors(req, res, next) {
|
179
|
-
console.debug("[MIDDLEWARE] cors started");
|
180
|
-
if (req.method === "OPTIONS") {
|
181
|
-
res.cors = true;
|
182
|
-
}
|
183
|
-
corsMiddleware()(req, res, next ?? (() => {
|
184
|
-
}));
|
185
|
-
}
|
186
|
-
|
187
|
-
// src/http/middleware/request/createContext.middleware.ts
|
188
|
-
import { v4 } from "uuid";
|
189
|
-
function createContext(schemaValidator) {
|
190
|
-
return (req, res, next) => {
|
191
|
-
console.debug("[MIDDLEWARE] createRequestContext started");
|
192
|
-
req.schemaValidator = schemaValidator;
|
193
|
-
let correlationId = v4();
|
194
|
-
if (req.headers["x-correlation-id"]) {
|
195
|
-
correlationId = req.headers["x-correlation-id"];
|
196
|
-
}
|
197
|
-
res.setHeader("x-correlation-id", correlationId);
|
198
|
-
req.context = {
|
199
|
-
correlationId
|
200
|
-
};
|
201
|
-
next?.();
|
202
|
-
};
|
203
|
-
}
|
204
|
-
|
205
220
|
// src/http/middleware/request/enrichDetails.middleware.ts
|
206
|
-
function enrichDetails(contractDetails, requestSchema, responseSchemas) {
|
221
|
+
function enrichDetails(path, contractDetails, requestSchema, responseSchemas) {
|
207
222
|
return (req, res, next) => {
|
208
|
-
|
223
|
+
req.originalPath = path;
|
209
224
|
req.contractDetails = contractDetails;
|
210
225
|
req.requestSchema = requestSchema;
|
211
226
|
res.responseSchemas = responseSchemas;
|
227
|
+
req.context.span?.setAttribute(ATTR_API_NAME, req.contractDetails?.name);
|
212
228
|
next?.();
|
213
229
|
};
|
214
230
|
}
|
@@ -225,14 +241,16 @@ function isResponseShape(maybeResponseShape) {
|
|
225
241
|
|
226
242
|
// src/http/middleware/request/parse.middleware.ts
|
227
243
|
function parse(req, _res, next) {
|
228
|
-
console.debug("[MIDDLEWARE] parseRequest started");
|
229
244
|
const request = {
|
230
245
|
params: req.params,
|
231
246
|
query: req.query,
|
232
247
|
headers: req.headers,
|
233
248
|
body: req.body
|
234
249
|
};
|
235
|
-
const parsedRequest = req.schemaValidator.parse(
|
250
|
+
const parsedRequest = req.schemaValidator.parse(
|
251
|
+
req.requestSchema,
|
252
|
+
request
|
253
|
+
);
|
236
254
|
if (parsedRequest.ok && isResponseShape(parsedRequest.value)) {
|
237
255
|
req.body = parsedRequest.value.body;
|
238
256
|
req.params = parsedRequest.value.params;
|
@@ -263,8 +281,6 @@ var ForklaunchExpressLikeRouter = class {
|
|
263
281
|
this.schemaValidator = schemaValidator;
|
264
282
|
this.internal = internal;
|
265
283
|
this.basePath = basePath;
|
266
|
-
this.internal.use(createContext(this.schemaValidator));
|
267
|
-
this.internal.use(cors);
|
268
284
|
}
|
269
285
|
requestHandler;
|
270
286
|
routers = [];
|
@@ -276,9 +292,14 @@ var ForklaunchExpressLikeRouter = class {
|
|
276
292
|
* @param {PathParamHttpContractDetails<SV> | HttpContractDetails<SV>} contractDetails - The contract details.
|
277
293
|
* @returns {MiddlewareHandler<SV>[]} - The resolved middlewares.
|
278
294
|
*/
|
279
|
-
#resolveMiddlewares(contractDetails, requestSchema, responseSchemas) {
|
295
|
+
#resolveMiddlewares(path, contractDetails, requestSchema, responseSchemas) {
|
280
296
|
return [
|
281
|
-
enrichDetails(
|
297
|
+
enrichDetails(
|
298
|
+
`${this.basePath}${path}`,
|
299
|
+
contractDetails,
|
300
|
+
requestSchema,
|
301
|
+
responseSchemas
|
302
|
+
),
|
282
303
|
parse,
|
283
304
|
parseRequestAuth
|
284
305
|
];
|
@@ -303,10 +324,10 @@ var ForklaunchExpressLikeRouter = class {
|
|
303
324
|
try {
|
304
325
|
await requestHandler(req, res, next);
|
305
326
|
} catch (error) {
|
306
|
-
next
|
307
|
-
|
308
|
-
|
309
|
-
|
327
|
+
if (next && typeof next === "function") {
|
328
|
+
next(error);
|
329
|
+
} else {
|
330
|
+
throw error;
|
310
331
|
}
|
311
332
|
}
|
312
333
|
};
|
@@ -339,7 +360,7 @@ var ForklaunchExpressLikeRouter = class {
|
|
339
360
|
...contractDetails.params ? { params: contractDetails.params } : {},
|
340
361
|
...contractDetails.requestHeaders ? { headers: contractDetails.requestHeaders } : {},
|
341
362
|
...contractDetails.query ? { query: contractDetails.query } : {},
|
342
|
-
...isHttpContractDetails(contractDetails) && contractDetails.body ? { body: contractDetails.body } : {}
|
363
|
+
...isHttpContractDetails(contractDetails) && contractDetails.body != null ? { body: contractDetails.body } : {}
|
343
364
|
})
|
344
365
|
);
|
345
366
|
const responseEntries = {
|
@@ -348,7 +369,9 @@ var ForklaunchExpressLikeRouter = class {
|
|
348
369
|
403: schemaValidator.string,
|
349
370
|
404: schemaValidator.string,
|
350
371
|
500: schemaValidator.string,
|
351
|
-
...isPathParamHttpContractDetails(contractDetails) || isHttpContractDetails(contractDetails) ? {
|
372
|
+
...isPathParamHttpContractDetails(contractDetails) || isHttpContractDetails(contractDetails) ? {
|
373
|
+
...contractDetails.responses
|
374
|
+
} : {}
|
352
375
|
};
|
353
376
|
const responseSchemas = {
|
354
377
|
responses: {},
|
@@ -478,9 +501,12 @@ var ForklaunchExpressLikeRouter = class {
|
|
478
501
|
const controllerHandler = this.#extractControllerHandler(handlers);
|
479
502
|
registrationMethod.bind(this.internal)(
|
480
503
|
path,
|
481
|
-
...this.#resolveMiddlewares(
|
482
|
-
|
483
|
-
|
504
|
+
...this.#resolveMiddlewares(
|
505
|
+
path,
|
506
|
+
contractDetails,
|
507
|
+
requestSchema,
|
508
|
+
responseSchemas
|
509
|
+
).concat(handlers),
|
484
510
|
this.#parseAndRunControllerHandler(controllerHandler)
|
485
511
|
);
|
486
512
|
return this.#localParamRequest(
|
@@ -536,9 +562,7 @@ var ForklaunchExpressLikeRouter = class {
|
|
536
562
|
contractDetailsOrMiddlewareOrTypedHandler,
|
537
563
|
middleware2
|
538
564
|
);
|
539
|
-
if (isForklaunchExpressLikeRouter(
|
540
|
-
contractDetailsOrMiddlewareOrTypedHandler
|
541
|
-
)) {
|
565
|
+
if (isForklaunchExpressLikeRouter(contractDetailsOrMiddlewareOrTypedHandler)) {
|
542
566
|
middleware2.push(contractDetailsOrMiddlewareOrTypedHandler.internal);
|
543
567
|
}
|
544
568
|
middleware2.push(
|
@@ -839,11 +863,23 @@ var ForklaunchExpressLikeApplication = class extends ForklaunchExpressLikeRouter
|
|
839
863
|
super("/", schemaValidator, internal);
|
840
864
|
this.schemaValidator = schemaValidator;
|
841
865
|
this.internal = internal;
|
866
|
+
this.internal.use(createContext(this.schemaValidator));
|
867
|
+
this.internal.use(cors);
|
842
868
|
}
|
843
869
|
};
|
844
870
|
|
871
|
+
// src/http/guards/isForklaunchRequest.ts
|
872
|
+
function isForklaunchRequest(request) {
|
873
|
+
return request != null && typeof request === "object" && "contractDetails" in request;
|
874
|
+
}
|
875
|
+
|
876
|
+
// src/http/guards/isPath.ts
|
877
|
+
function isPath(path) {
|
878
|
+
return path.startsWith("/");
|
879
|
+
}
|
880
|
+
|
845
881
|
// src/http/handlers/typedHandler.ts
|
846
|
-
function typedHandler(_schemaValidator,
|
882
|
+
function typedHandler(_schemaValidator, pathOrContractMethod, contractMethodOrContractDetails, contractDetailsOrHandler, ...handlerArray) {
|
847
883
|
let contractDetails;
|
848
884
|
let handlers;
|
849
885
|
if (typeof contractMethodOrContractDetails === "string") {
|
@@ -863,6 +899,7 @@ function typedHandler(_schemaValidator, _pathOrContractDetails, contractMethodOr
|
|
863
899
|
}
|
864
900
|
return {
|
865
901
|
_typedHandler: true,
|
902
|
+
_path: isPath(pathOrContractMethod) ? pathOrContractMethod : void 0,
|
866
903
|
contractDetails,
|
867
904
|
handlers
|
868
905
|
};
|
@@ -909,11 +946,11 @@ var put = (_schemaValidator, path, contractDetails, ...handlers) => {
|
|
909
946
|
};
|
910
947
|
|
911
948
|
// src/http/handlers/trace.ts
|
912
|
-
var
|
949
|
+
var trace2 = (_schemaValidator, path, contractDetails, ...handlers) => {
|
913
950
|
return typedHandler(_schemaValidator, path, "trace", contractDetails, ...handlers);
|
914
951
|
};
|
915
952
|
|
916
|
-
// src/http/
|
953
|
+
// src/http/httpStatusCodes.ts
|
917
954
|
var HTTPStatuses = {
|
918
955
|
/**
|
919
956
|
* The initial part of a request has been received and has not yet been
|
@@ -1896,6 +1933,324 @@ var getCodeForStatus = (status) => {
|
|
1896
1933
|
};
|
1897
1934
|
var httpStatusCodes_default = HTTPStatuses;
|
1898
1935
|
|
1936
|
+
// src/http/middleware/response/parse.middleware.ts
|
1937
|
+
import {
|
1938
|
+
prettyPrintParseErrors as prettyPrintParseErrors2
|
1939
|
+
} from "@forklaunch/validator";
|
1940
|
+
function parse2(req, res, next) {
|
1941
|
+
const { headers, responses } = res.responseSchemas;
|
1942
|
+
const parsedResponse = req.schemaValidator.parse(
|
1943
|
+
responses?.[res.statusCode],
|
1944
|
+
res.bodyData
|
1945
|
+
);
|
1946
|
+
const parsedHeaders = req.schemaValidator.parse(
|
1947
|
+
headers ?? req.schemaValidator.unknown,
|
1948
|
+
res.getHeaders()
|
1949
|
+
);
|
1950
|
+
const parseErrors = [];
|
1951
|
+
if (!parsedHeaders.ok) {
|
1952
|
+
const headerErrors = prettyPrintParseErrors2(parsedHeaders.errors, "Header");
|
1953
|
+
if (headerErrors) {
|
1954
|
+
parseErrors.push(headerErrors);
|
1955
|
+
}
|
1956
|
+
}
|
1957
|
+
if (!parsedResponse.ok) {
|
1958
|
+
const responseErrors = prettyPrintParseErrors2(
|
1959
|
+
parsedResponse.errors,
|
1960
|
+
"Response"
|
1961
|
+
);
|
1962
|
+
if (responseErrors) {
|
1963
|
+
parseErrors.push(responseErrors);
|
1964
|
+
}
|
1965
|
+
}
|
1966
|
+
if (parseErrors.length > 0) {
|
1967
|
+
switch (req.contractDetails.options?.responseValidation) {
|
1968
|
+
default:
|
1969
|
+
case "error":
|
1970
|
+
next?.(new Error(`Invalid response:
|
1971
|
+
${parseErrors.join("\n\n")}`));
|
1972
|
+
break;
|
1973
|
+
case "warning":
|
1974
|
+
console.warn(`Invalid response:
|
1975
|
+
${parseErrors.join("\n\n")}`);
|
1976
|
+
break;
|
1977
|
+
case "none":
|
1978
|
+
break;
|
1979
|
+
}
|
1980
|
+
}
|
1981
|
+
next?.();
|
1982
|
+
}
|
1983
|
+
|
1984
|
+
// src/http/telemetry/pinoLogger.ts
|
1985
|
+
import { isNever } from "@forklaunch/common";
|
1986
|
+
import { trace as trace3 } from "@opentelemetry/api";
|
1987
|
+
import { logs } from "@opentelemetry/api-logs";
|
1988
|
+
import pino from "pino";
|
1989
|
+
function mapSeverity(level) {
|
1990
|
+
switch (level) {
|
1991
|
+
case "silent":
|
1992
|
+
return 0;
|
1993
|
+
case "trace":
|
1994
|
+
return 1;
|
1995
|
+
case "debug":
|
1996
|
+
return 5;
|
1997
|
+
case "info":
|
1998
|
+
return 9;
|
1999
|
+
case "warn":
|
2000
|
+
return 13;
|
2001
|
+
case "error":
|
2002
|
+
return 17;
|
2003
|
+
case "fatal":
|
2004
|
+
return 21;
|
2005
|
+
default:
|
2006
|
+
isNever(level);
|
2007
|
+
return 0;
|
2008
|
+
}
|
2009
|
+
}
|
2010
|
+
var PinoLogger = class _PinoLogger {
|
2011
|
+
pinoLogger;
|
2012
|
+
meta;
|
2013
|
+
constructor(level, meta = {}) {
|
2014
|
+
this.pinoLogger = pino({
|
2015
|
+
level: level || "info",
|
2016
|
+
formatters: {
|
2017
|
+
level(label) {
|
2018
|
+
return { level: label };
|
2019
|
+
}
|
2020
|
+
},
|
2021
|
+
timestamp: pino.stdTimeFunctions.isoTime,
|
2022
|
+
transport: {
|
2023
|
+
target: "pino-pretty",
|
2024
|
+
options: { colorize: true }
|
2025
|
+
}
|
2026
|
+
});
|
2027
|
+
this.meta = meta;
|
2028
|
+
}
|
2029
|
+
log(level, msg, meta = {}) {
|
2030
|
+
const activeSpan = trace3.getActiveSpan();
|
2031
|
+
if (activeSpan) {
|
2032
|
+
const activeSpanContext = activeSpan.spanContext();
|
2033
|
+
meta.trace_id = activeSpanContext.traceId;
|
2034
|
+
meta.span_id = activeSpanContext.spanId;
|
2035
|
+
meta.trace_flags = activeSpanContext.traceFlags;
|
2036
|
+
if (!meta.api_name) {
|
2037
|
+
meta = { ...meta, ...activeSpan?.attributes };
|
2038
|
+
}
|
2039
|
+
}
|
2040
|
+
this.pinoLogger[level](msg);
|
2041
|
+
logs.getLogger(process.env.OTEL_SERVICE_NAME ?? "unknown").emit({
|
2042
|
+
severityText: level,
|
2043
|
+
severityNumber: mapSeverity(level),
|
2044
|
+
body: msg,
|
2045
|
+
attributes: { ...this.meta, ...meta }
|
2046
|
+
});
|
2047
|
+
}
|
2048
|
+
error(msg, meta = {}) {
|
2049
|
+
this.log("error", msg, meta);
|
2050
|
+
}
|
2051
|
+
info(msg, meta = {}) {
|
2052
|
+
this.log("info", msg, meta);
|
2053
|
+
}
|
2054
|
+
debug(msg, meta = {}) {
|
2055
|
+
this.log("debug", msg, meta);
|
2056
|
+
}
|
2057
|
+
warn(msg, meta = {}) {
|
2058
|
+
this.log("warn", msg, meta);
|
2059
|
+
}
|
2060
|
+
trace(msg, meta = {}) {
|
2061
|
+
this.log("trace", msg, meta);
|
2062
|
+
}
|
2063
|
+
child(meta = {}) {
|
2064
|
+
return new _PinoLogger(this.pinoLogger.level, { ...this.meta, ...meta });
|
2065
|
+
}
|
2066
|
+
getBaseLogger() {
|
2067
|
+
return this.pinoLogger;
|
2068
|
+
}
|
2069
|
+
};
|
2070
|
+
function logger(level, meta = {}) {
|
2071
|
+
return new PinoLogger(level, meta);
|
2072
|
+
}
|
2073
|
+
|
2074
|
+
// src/http/telemetry/recordMetric.ts
|
2075
|
+
import {
|
2076
|
+
ATTR_HTTP_REQUEST_METHOD as ATTR_HTTP_REQUEST_METHOD3,
|
2077
|
+
ATTR_HTTP_RESPONSE_STATUS_CODE as ATTR_HTTP_RESPONSE_STATUS_CODE3,
|
2078
|
+
ATTR_HTTP_ROUTE as ATTR_HTTP_ROUTE3,
|
2079
|
+
ATTR_SERVICE_NAME as ATTR_SERVICE_NAME3
|
2080
|
+
} from "@opentelemetry/semantic-conventions";
|
2081
|
+
|
2082
|
+
// src/services/configInjector.ts
|
2083
|
+
import { extractArgumentNames as extractArgumentNames2, isNever as isNever2 } from "@forklaunch/common";
|
2084
|
+
import {
|
2085
|
+
prettyPrintParseErrors as prettyPrintParseErrors3
|
2086
|
+
} from "@forklaunch/validator";
|
2087
|
+
|
2088
|
+
// src/services/getEnvVar.ts
|
2089
|
+
function getEnvVar(name) {
|
2090
|
+
const value = process.env[name];
|
2091
|
+
return value;
|
2092
|
+
}
|
2093
|
+
|
2094
|
+
// src/http/telemetry/openTelemetryCollector.ts
|
2095
|
+
import { HyperExpressInstrumentation } from "@forklaunch/opentelemetry-instrumentation-hyper-express";
|
2096
|
+
import {
|
2097
|
+
metrics
|
2098
|
+
} from "@opentelemetry/api";
|
2099
|
+
import { OTLPLogExporter } from "@opentelemetry/exporter-logs-otlp-http";
|
2100
|
+
import { OTLPMetricExporter } from "@opentelemetry/exporter-metrics-otlp-http";
|
2101
|
+
import { OTLPTraceExporter } from "@opentelemetry/exporter-trace-otlp-http";
|
2102
|
+
import { ExpressInstrumentation } from "@opentelemetry/instrumentation-express";
|
2103
|
+
import { HttpInstrumentation } from "@opentelemetry/instrumentation-http";
|
2104
|
+
import { Resource } from "@opentelemetry/resources";
|
2105
|
+
import { BatchLogRecordProcessor } from "@opentelemetry/sdk-logs";
|
2106
|
+
import { PeriodicExportingMetricReader } from "@opentelemetry/sdk-metrics";
|
2107
|
+
import { NodeSDK } from "@opentelemetry/sdk-node";
|
2108
|
+
import {
|
2109
|
+
ATTR_SERVICE_NAME as ATTR_SERVICE_NAME2
|
2110
|
+
} from "@opentelemetry/semantic-conventions";
|
2111
|
+
import dotenv from "dotenv";
|
2112
|
+
var OpenTelemetryCollector = class {
|
2113
|
+
// scoped creation and create this in middleware when api execute. Also add correlation id
|
2114
|
+
constructor(serviceName, level, metricDefinitions) {
|
2115
|
+
this.serviceName = serviceName;
|
2116
|
+
this.logger = logger(level ?? "info");
|
2117
|
+
this.metrics = {};
|
2118
|
+
for (const [metricId, metricType] of Object.entries(
|
2119
|
+
metricDefinitions ?? {}
|
2120
|
+
)) {
|
2121
|
+
switch (metricType) {
|
2122
|
+
case "counter":
|
2123
|
+
this.metrics[metricId] = metrics.getMeter(this.serviceName).createCounter(metricId);
|
2124
|
+
break;
|
2125
|
+
case "gauge":
|
2126
|
+
this.metrics[metricId] = metrics.getMeter(this.serviceName).createGauge(metricId);
|
2127
|
+
break;
|
2128
|
+
case "histogram":
|
2129
|
+
this.metrics[metricId] = metrics.getMeter(this.serviceName).createHistogram(metricId);
|
2130
|
+
break;
|
2131
|
+
case "upDownCounter":
|
2132
|
+
this.metrics[metricId] = metrics.getMeter(this.serviceName).createUpDownCounter(metricId);
|
2133
|
+
break;
|
2134
|
+
case "observableCounter":
|
2135
|
+
this.metrics[metricId] = metrics.getMeter(this.serviceName).createObservableCounter(metricId);
|
2136
|
+
break;
|
2137
|
+
case "observableGauge":
|
2138
|
+
this.metrics[metricId] = metrics.getMeter(this.serviceName).createObservableGauge(metricId);
|
2139
|
+
break;
|
2140
|
+
case "observableUpDownCounter":
|
2141
|
+
this.metrics[metricId] = metrics.getMeter(this.serviceName).createObservableUpDownCounter(metricId);
|
2142
|
+
break;
|
2143
|
+
}
|
2144
|
+
}
|
2145
|
+
this.log("info", "OpenTelemetry (Traces + Logs + Metrics) started");
|
2146
|
+
}
|
2147
|
+
logger;
|
2148
|
+
metrics;
|
2149
|
+
log(level, msg, meta = {}) {
|
2150
|
+
this.logger.log(level, msg, meta);
|
2151
|
+
}
|
2152
|
+
info(msg, meta = {}) {
|
2153
|
+
this.logger.info(msg, meta);
|
2154
|
+
}
|
2155
|
+
error(msg, meta = {}) {
|
2156
|
+
this.logger.error(msg, meta);
|
2157
|
+
}
|
2158
|
+
warn(msg, meta = {}) {
|
2159
|
+
this.logger.warn(msg, meta);
|
2160
|
+
}
|
2161
|
+
debug(msg, meta = {}) {
|
2162
|
+
this.logger.debug(msg, meta);
|
2163
|
+
}
|
2164
|
+
trace(msg, meta = {}) {
|
2165
|
+
this.logger.trace(msg, meta);
|
2166
|
+
}
|
2167
|
+
getMetric(metricId) {
|
2168
|
+
return this.metrics[metricId];
|
2169
|
+
}
|
2170
|
+
};
|
2171
|
+
dotenv.config({ path: getEnvVar("ENV_FILE_PATH") });
|
2172
|
+
new NodeSDK({
|
2173
|
+
resource: new Resource({
|
2174
|
+
[ATTR_SERVICE_NAME2]: getEnvVar("OTEL_SERVICE_NAME")
|
2175
|
+
}),
|
2176
|
+
traceExporter: new OTLPTraceExporter({
|
2177
|
+
url: `${getEnvVar("OTEL_EXPORTER_OTLP_ENDPOINT") ?? "http://localhost:4318"}/v1/traces`
|
2178
|
+
}),
|
2179
|
+
metricReader: new PeriodicExportingMetricReader({
|
2180
|
+
exporter: new OTLPMetricExporter({
|
2181
|
+
url: `${getEnvVar("OTEL_EXPORTER_OTLP_ENDPOINT") ?? "http://localhost:4318"}/v1/metrics`
|
2182
|
+
}),
|
2183
|
+
exportIntervalMillis: 5e3
|
2184
|
+
}),
|
2185
|
+
logRecordProcessors: [
|
2186
|
+
new BatchLogRecordProcessor(
|
2187
|
+
new OTLPLogExporter({
|
2188
|
+
url: `${getEnvVar("OTEL_EXPORTER_OTLP_ENDPOINT") ?? "http://localhost:4318"}/v1/logs`
|
2189
|
+
})
|
2190
|
+
)
|
2191
|
+
],
|
2192
|
+
instrumentations: [
|
2193
|
+
new HttpInstrumentation({
|
2194
|
+
applyCustomAttributesOnSpan: (span, request) => {
|
2195
|
+
span.setAttribute(
|
2196
|
+
"service.name",
|
2197
|
+
getEnvVar("OTEL_SERVICE_NAME") ?? "unknown"
|
2198
|
+
);
|
2199
|
+
if (isForklaunchRequest(request)) {
|
2200
|
+
span.setAttribute("api.name", request.contractDetails?.name);
|
2201
|
+
}
|
2202
|
+
}
|
2203
|
+
}),
|
2204
|
+
new ExpressInstrumentation(),
|
2205
|
+
new HyperExpressInstrumentation()
|
2206
|
+
]
|
2207
|
+
}).start();
|
2208
|
+
var httpRequestsTotalCounter = metrics.getMeter(getEnvVar("OTEL_SERVICE_NAME") || "unknown").createCounter("http_requests_total", {
|
2209
|
+
description: "Number of HTTP requests"
|
2210
|
+
});
|
2211
|
+
|
2212
|
+
// src/http/telemetry/recordMetric.ts
|
2213
|
+
function recordMetric(req, res) {
|
2214
|
+
httpRequestsTotalCounter.add(1, {
|
2215
|
+
[ATTR_SERVICE_NAME3]: getEnvVar("OTEL_SERVICE_NAME"),
|
2216
|
+
[ATTR_API_NAME]: req.contractDetails?.name,
|
2217
|
+
[ATTR_CORRELATION_ID]: req.context.correlationId,
|
2218
|
+
[ATTR_HTTP_REQUEST_METHOD3]: req.method,
|
2219
|
+
[ATTR_HTTP_ROUTE3]: req.originalPath,
|
2220
|
+
[ATTR_HTTP_RESPONSE_STATUS_CODE3]: res.statusCode || 0
|
2221
|
+
});
|
2222
|
+
}
|
2223
|
+
|
2224
|
+
// src/http/middleware/response/enrichExpressLikeSend.middleware.ts
|
2225
|
+
function enrichExpressLikeSend(instance, req, res, originalSend, data, shouldEnrich) {
|
2226
|
+
let parseErrorSent;
|
2227
|
+
if (shouldEnrich) {
|
2228
|
+
recordMetric(req, res);
|
2229
|
+
if (res.statusCode === 404) {
|
2230
|
+
res.status(404);
|
2231
|
+
logger("error").error("Not Found");
|
2232
|
+
originalSend.call(instance, "Not Found");
|
2233
|
+
}
|
2234
|
+
parse2(req, res, (err) => {
|
2235
|
+
if (err) {
|
2236
|
+
let errorString = err.message;
|
2237
|
+
if (res.locals.errorMessage) {
|
2238
|
+
errorString += `
|
2239
|
+
------------------
|
2240
|
+
${res.locals.errorMessage}`;
|
2241
|
+
}
|
2242
|
+
logger("error").error(errorString);
|
2243
|
+
res.status(500);
|
2244
|
+
originalSend.call(instance, errorString);
|
2245
|
+
parseErrorSent = true;
|
2246
|
+
}
|
2247
|
+
});
|
2248
|
+
}
|
2249
|
+
if (!parseErrorSent) {
|
2250
|
+
originalSend.call(instance, data);
|
2251
|
+
}
|
2252
|
+
}
|
2253
|
+
|
1899
2254
|
// src/http/openApiV3Generator/openApiV3Generator.ts
|
1900
2255
|
function toUpperCase(str) {
|
1901
2256
|
return str.charAt(0).toUpperCase() + str.slice(1);
|
@@ -1906,7 +2261,7 @@ function transformBasePath(basePath) {
|
|
1906
2261
|
}
|
1907
2262
|
return `/${basePath}`;
|
1908
2263
|
}
|
1909
|
-
function
|
2264
|
+
function generateOpenApiDocument(port, tags, paths) {
|
1910
2265
|
return {
|
1911
2266
|
openapi: "3.1.0",
|
1912
2267
|
info: {
|
@@ -1978,7 +2333,10 @@ function generateSwaggerDocument(schemaValidator, port, routers) {
|
|
1978
2333
|
for (const key in route.contractDetails.params) {
|
1979
2334
|
pathItemObject.parameters?.push({
|
1980
2335
|
name: key,
|
1981
|
-
in: "path"
|
2336
|
+
in: "path",
|
2337
|
+
schema: schemaValidator.openapi(
|
2338
|
+
route.contractDetails.params[key]
|
2339
|
+
)
|
1982
2340
|
});
|
1983
2341
|
}
|
1984
2342
|
}
|
@@ -1993,7 +2351,10 @@ function generateSwaggerDocument(schemaValidator, port, routers) {
|
|
1993
2351
|
for (const key in requestHeaders) {
|
1994
2352
|
pathItemObject.parameters?.push({
|
1995
2353
|
name: key,
|
1996
|
-
in: "header"
|
2354
|
+
in: "header",
|
2355
|
+
schema: schemaValidator.openapi(
|
2356
|
+
requestHeaders[key]
|
2357
|
+
)
|
1997
2358
|
});
|
1998
2359
|
}
|
1999
2360
|
}
|
@@ -2001,7 +2362,8 @@ function generateSwaggerDocument(schemaValidator, port, routers) {
|
|
2001
2362
|
for (const key in query) {
|
2002
2363
|
pathItemObject.parameters?.push({
|
2003
2364
|
name: key,
|
2004
|
-
in: "query"
|
2365
|
+
in: "query",
|
2366
|
+
schema: schemaValidator.openapi(query[key])
|
2005
2367
|
});
|
2006
2368
|
}
|
2007
2369
|
}
|
@@ -2029,107 +2391,48 @@ function generateSwaggerDocument(schemaValidator, port, routers) {
|
|
2029
2391
|
}
|
2030
2392
|
});
|
2031
2393
|
});
|
2032
|
-
return
|
2033
|
-
}
|
2034
|
-
|
2035
|
-
// src/http/middleware/response/parse.middleware.ts
|
2036
|
-
import {
|
2037
|
-
prettyPrintParseErrors as prettyPrintParseErrors2
|
2038
|
-
} from "@forklaunch/validator";
|
2039
|
-
function parse2(req, res, next) {
|
2040
|
-
console.debug("[MIDDLEWARE] parseResponse started");
|
2041
|
-
const { headers, responses } = res.responseSchemas;
|
2042
|
-
const parsedResponse = req.schemaValidator.parse(
|
2043
|
-
responses?.[res.statusCode],
|
2044
|
-
res.bodyData
|
2045
|
-
);
|
2046
|
-
const parsedHeaders = req.schemaValidator.parse(
|
2047
|
-
headers ?? req.schemaValidator.unknown,
|
2048
|
-
res.getHeaders()
|
2049
|
-
);
|
2050
|
-
const parseErrors = [];
|
2051
|
-
if (!parsedHeaders.ok) {
|
2052
|
-
const headerErrors = prettyPrintParseErrors2(parsedHeaders.errors, "Header");
|
2053
|
-
if (headerErrors) {
|
2054
|
-
parseErrors.push(headerErrors);
|
2055
|
-
}
|
2056
|
-
}
|
2057
|
-
if (!parsedResponse.ok) {
|
2058
|
-
const responseErrors = prettyPrintParseErrors2(
|
2059
|
-
parsedResponse.errors,
|
2060
|
-
"Response"
|
2061
|
-
);
|
2062
|
-
if (responseErrors) {
|
2063
|
-
parseErrors.push(responseErrors);
|
2064
|
-
}
|
2065
|
-
}
|
2066
|
-
if (parseErrors.length > 0) {
|
2067
|
-
switch (req.contractDetails.options?.responseValidation) {
|
2068
|
-
default:
|
2069
|
-
case "error":
|
2070
|
-
next?.(new Error(`Invalid response:
|
2071
|
-
${parseErrors.join("\n\n")}`));
|
2072
|
-
break;
|
2073
|
-
case "warning":
|
2074
|
-
console.warn(`Invalid response:
|
2075
|
-
${parseErrors.join("\n\n")}`);
|
2076
|
-
break;
|
2077
|
-
case "none":
|
2078
|
-
break;
|
2079
|
-
}
|
2080
|
-
}
|
2081
|
-
next?.();
|
2394
|
+
return generateOpenApiDocument(port, tags, paths);
|
2082
2395
|
}
|
2083
2396
|
|
2084
|
-
// src/http/
|
2085
|
-
function
|
2086
|
-
|
2087
|
-
if (shouldEnrich) {
|
2088
|
-
if (res.statusCode === 404) {
|
2089
|
-
res.status(404);
|
2090
|
-
originalSend.call(instance, "Not Found");
|
2091
|
-
}
|
2092
|
-
parse2(req, res, (err) => {
|
2093
|
-
if (err) {
|
2094
|
-
let errorString = err.message;
|
2095
|
-
if (res.locals.errorMessage) {
|
2096
|
-
errorString += `
|
2097
|
-
------------------
|
2098
|
-
${res.locals.errorMessage}`;
|
2099
|
-
}
|
2100
|
-
res.status(500);
|
2101
|
-
originalSend.call(instance, errorString);
|
2102
|
-
parseErrorSent = true;
|
2103
|
-
}
|
2104
|
-
});
|
2105
|
-
}
|
2106
|
-
if (!parseErrorSent) {
|
2107
|
-
originalSend.call(instance, data);
|
2108
|
-
}
|
2397
|
+
// src/http/telemetry/metricsDefinition.ts
|
2398
|
+
function metricsDefinitions(metrics2) {
|
2399
|
+
return metrics2;
|
2109
2400
|
}
|
2110
2401
|
export {
|
2402
|
+
ATTR_API_NAME,
|
2403
|
+
ATTR_CORRELATION_ID,
|
2404
|
+
ATTR_HTTP_REQUEST_METHOD,
|
2405
|
+
ATTR_HTTP_RESPONSE_STATUS_CODE,
|
2406
|
+
ATTR_HTTP_ROUTE,
|
2407
|
+
ATTR_SERVICE_NAME,
|
2111
2408
|
ForklaunchExpressLikeApplication,
|
2112
2409
|
ForklaunchExpressLikeRouter,
|
2113
2410
|
HTTPStatuses,
|
2411
|
+
OpenTelemetryCollector,
|
2114
2412
|
delete_,
|
2115
2413
|
enrichExpressLikeSend,
|
2116
2414
|
generateSwaggerDocument,
|
2117
2415
|
get,
|
2118
2416
|
getCodeForStatus,
|
2119
2417
|
head,
|
2418
|
+
httpRequestsTotalCounter,
|
2120
2419
|
isClientError,
|
2420
|
+
isForklaunchRequest,
|
2121
2421
|
isForklaunchRouter,
|
2122
2422
|
isInformational,
|
2123
2423
|
isRedirection,
|
2124
2424
|
isServerError,
|
2125
2425
|
isSuccessful,
|
2126
2426
|
isValidStatusCode,
|
2427
|
+
logger,
|
2428
|
+
metricsDefinitions,
|
2127
2429
|
middleware,
|
2128
2430
|
options,
|
2129
2431
|
patch,
|
2130
2432
|
post,
|
2131
2433
|
put,
|
2132
|
-
|
2434
|
+
recordMetric,
|
2435
|
+
trace2 as trace,
|
2133
2436
|
typedHandler
|
2134
2437
|
};
|
2135
2438
|
//# sourceMappingURL=index.mjs.map
|