@forklaunch/core 0.3.6 → 0.4.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/http/index.d.mts +44 -2
- package/lib/http/index.d.ts +44 -2
- package/lib/http/index.js +234 -53
- package/lib/http/index.js.map +1 -1
- package/lib/http/index.mjs +244 -53
- package/lib/http/index.mjs.map +1 -1
- package/package.json +31 -13
package/lib/http/index.mjs
CHANGED
@@ -1,3 +1,30 @@
|
|
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 { v4 } from "uuid";
|
13
|
+
function createContext(schemaValidator) {
|
14
|
+
return function setContext(req, res, next) {
|
15
|
+
req.schemaValidator = schemaValidator;
|
16
|
+
let correlationId = v4();
|
17
|
+
if (req.headers["x-correlation-id"]) {
|
18
|
+
correlationId = req.headers["x-correlation-id"];
|
19
|
+
}
|
20
|
+
res.setHeader("x-correlation-id", correlationId);
|
21
|
+
req.context = {
|
22
|
+
correlationId
|
23
|
+
};
|
24
|
+
next?.();
|
25
|
+
};
|
26
|
+
}
|
27
|
+
|
1
28
|
// src/http/guards/isForklaunchRouter.ts
|
2
29
|
function isForklaunchRouter(maybeForklaunchRouter) {
|
3
30
|
return maybeForklaunchRouter != null && typeof maybeForklaunchRouter === "object" && "basePath" in maybeForklaunchRouter && "routes" in maybeForklaunchRouter && Array.isArray(maybeForklaunchRouter.routes);
|
@@ -157,55 +184,25 @@ async function checkAuthorizationToken(authorizationMethod, authorizationToken,
|
|
157
184
|
return [401, "Invalid Authorization method."];
|
158
185
|
}
|
159
186
|
async function parseRequestAuth(req, res, next) {
|
160
|
-
console.debug("[MIDDLEWARE] parseRequest started");
|
161
187
|
const auth = req.contractDetails.auth;
|
162
188
|
if (auth) {
|
163
|
-
const
|
189
|
+
const [error, message] = await checkAuthorizationToken(
|
164
190
|
auth,
|
165
191
|
req.headers[(auth.method === "other" ? auth.headerName : void 0) ?? "Authorization"],
|
166
192
|
req
|
167
|
-
);
|
168
|
-
if (
|
169
|
-
res.status(
|
170
|
-
next?.(new Error(
|
193
|
+
) ?? [];
|
194
|
+
if (error != null) {
|
195
|
+
res.status(error).send(message);
|
196
|
+
next?.(new Error(message));
|
171
197
|
}
|
172
198
|
}
|
173
199
|
next?.();
|
174
200
|
}
|
175
201
|
|
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
202
|
// src/http/middleware/request/enrichDetails.middleware.ts
|
206
|
-
function enrichDetails(contractDetails, requestSchema, responseSchemas) {
|
203
|
+
function enrichDetails(path, contractDetails, requestSchema, responseSchemas) {
|
207
204
|
return (req, res, next) => {
|
208
|
-
|
205
|
+
req.originalPath = path;
|
209
206
|
req.contractDetails = contractDetails;
|
210
207
|
req.requestSchema = requestSchema;
|
211
208
|
res.responseSchemas = responseSchemas;
|
@@ -225,7 +222,6 @@ function isResponseShape(maybeResponseShape) {
|
|
225
222
|
|
226
223
|
// src/http/middleware/request/parse.middleware.ts
|
227
224
|
function parse(req, _res, next) {
|
228
|
-
console.debug("[MIDDLEWARE] parseRequest started");
|
229
225
|
const request = {
|
230
226
|
params: req.params,
|
231
227
|
query: req.query,
|
@@ -263,8 +259,6 @@ var ForklaunchExpressLikeRouter = class {
|
|
263
259
|
this.schemaValidator = schemaValidator;
|
264
260
|
this.internal = internal;
|
265
261
|
this.basePath = basePath;
|
266
|
-
this.internal.use(createContext(this.schemaValidator));
|
267
|
-
this.internal.use(cors);
|
268
262
|
}
|
269
263
|
requestHandler;
|
270
264
|
routers = [];
|
@@ -276,9 +270,14 @@ var ForklaunchExpressLikeRouter = class {
|
|
276
270
|
* @param {PathParamHttpContractDetails<SV> | HttpContractDetails<SV>} contractDetails - The contract details.
|
277
271
|
* @returns {MiddlewareHandler<SV>[]} - The resolved middlewares.
|
278
272
|
*/
|
279
|
-
#resolveMiddlewares(contractDetails, requestSchema, responseSchemas) {
|
273
|
+
#resolveMiddlewares(path, contractDetails, requestSchema, responseSchemas) {
|
280
274
|
return [
|
281
|
-
enrichDetails(
|
275
|
+
enrichDetails(
|
276
|
+
`${this.basePath}${path}`,
|
277
|
+
contractDetails,
|
278
|
+
requestSchema,
|
279
|
+
responseSchemas
|
280
|
+
),
|
282
281
|
parse,
|
283
282
|
parseRequestAuth
|
284
283
|
];
|
@@ -303,10 +302,10 @@ var ForklaunchExpressLikeRouter = class {
|
|
303
302
|
try {
|
304
303
|
await requestHandler(req, res, next);
|
305
304
|
} catch (error) {
|
306
|
-
next
|
307
|
-
|
308
|
-
|
309
|
-
|
305
|
+
if (next) {
|
306
|
+
next(error);
|
307
|
+
} else {
|
308
|
+
throw error;
|
310
309
|
}
|
311
310
|
}
|
312
311
|
};
|
@@ -478,7 +477,7 @@ var ForklaunchExpressLikeRouter = class {
|
|
478
477
|
const controllerHandler = this.#extractControllerHandler(handlers);
|
479
478
|
registrationMethod.bind(this.internal)(
|
480
479
|
path,
|
481
|
-
...this.#resolveMiddlewares(contractDetails, requestSchema, responseSchemas).concat(
|
480
|
+
...this.#resolveMiddlewares(path, contractDetails, requestSchema, responseSchemas).concat(
|
482
481
|
handlers
|
483
482
|
),
|
484
483
|
this.#parseAndRunControllerHandler(controllerHandler)
|
@@ -839,6 +838,8 @@ var ForklaunchExpressLikeApplication = class extends ForklaunchExpressLikeRouter
|
|
839
838
|
super("/", schemaValidator, internal);
|
840
839
|
this.schemaValidator = schemaValidator;
|
841
840
|
this.internal = internal;
|
841
|
+
this.internal.use(createContext(this.schemaValidator));
|
842
|
+
this.internal.use(cors);
|
842
843
|
}
|
843
844
|
};
|
844
845
|
|
@@ -1906,7 +1907,7 @@ function transformBasePath(basePath) {
|
|
1906
1907
|
}
|
1907
1908
|
return `/${basePath}`;
|
1908
1909
|
}
|
1909
|
-
function
|
1910
|
+
function generateOpenApiDocument(port, tags, paths) {
|
1910
1911
|
return {
|
1911
1912
|
openapi: "3.1.0",
|
1912
1913
|
info: {
|
@@ -1978,7 +1979,10 @@ function generateSwaggerDocument(schemaValidator, port, routers) {
|
|
1978
1979
|
for (const key in route.contractDetails.params) {
|
1979
1980
|
pathItemObject.parameters?.push({
|
1980
1981
|
name: key,
|
1981
|
-
in: "path"
|
1982
|
+
in: "path",
|
1983
|
+
schema: schemaValidator.openapi(
|
1984
|
+
route.contractDetails.params[key]
|
1985
|
+
)
|
1982
1986
|
});
|
1983
1987
|
}
|
1984
1988
|
}
|
@@ -1993,7 +1997,10 @@ function generateSwaggerDocument(schemaValidator, port, routers) {
|
|
1993
1997
|
for (const key in requestHeaders) {
|
1994
1998
|
pathItemObject.parameters?.push({
|
1995
1999
|
name: key,
|
1996
|
-
in: "header"
|
2000
|
+
in: "header",
|
2001
|
+
schema: schemaValidator.openapi(
|
2002
|
+
requestHeaders[key]
|
2003
|
+
)
|
1997
2004
|
});
|
1998
2005
|
}
|
1999
2006
|
}
|
@@ -2001,7 +2008,8 @@ function generateSwaggerDocument(schemaValidator, port, routers) {
|
|
2001
2008
|
for (const key in query) {
|
2002
2009
|
pathItemObject.parameters?.push({
|
2003
2010
|
name: key,
|
2004
|
-
in: "query"
|
2011
|
+
in: "query",
|
2012
|
+
schema: schemaValidator.openapi(query[key])
|
2005
2013
|
});
|
2006
2014
|
}
|
2007
2015
|
}
|
@@ -2029,15 +2037,176 @@ function generateSwaggerDocument(schemaValidator, port, routers) {
|
|
2029
2037
|
}
|
2030
2038
|
});
|
2031
2039
|
});
|
2032
|
-
return
|
2040
|
+
return generateOpenApiDocument(port, tags, paths);
|
2033
2041
|
}
|
2034
2042
|
|
2043
|
+
// src/http/tracing/emitLoggerError.ts
|
2044
|
+
import { logs } from "@opentelemetry/api-logs";
|
2045
|
+
import {
|
2046
|
+
SEMATTRS_HTTP_METHOD,
|
2047
|
+
SEMATTRS_HTTP_ROUTE,
|
2048
|
+
SEMATTRS_HTTP_STATUS_CODE,
|
2049
|
+
SEMATTRS_HTTP_TARGET
|
2050
|
+
} from "@opentelemetry/semantic-conventions";
|
2051
|
+
|
2052
|
+
// src/http/tracing/pinoLogger.ts
|
2053
|
+
import pino from "pino";
|
2054
|
+
function pinoLogger(level) {
|
2055
|
+
return pino({
|
2056
|
+
level: level || "info",
|
2057
|
+
formatters: {
|
2058
|
+
level(label) {
|
2059
|
+
return { level: label };
|
2060
|
+
}
|
2061
|
+
},
|
2062
|
+
timestamp: pino.stdTimeFunctions.isoTime,
|
2063
|
+
transport: {
|
2064
|
+
target: "pino-pretty",
|
2065
|
+
options: { colorize: true }
|
2066
|
+
}
|
2067
|
+
});
|
2068
|
+
}
|
2069
|
+
|
2070
|
+
// src/http/tracing/emitLoggerError.ts
|
2071
|
+
var logger = pinoLogger("error");
|
2072
|
+
var emitLoggerError = (req, res, errorString) => {
|
2073
|
+
logs.getLogger(process.env.SERVICE_NAME ?? "unknown").emit({
|
2074
|
+
severityText: "ERROR",
|
2075
|
+
severityNumber: 17,
|
2076
|
+
body: errorString,
|
2077
|
+
attributes: {
|
2078
|
+
"service.name": process.env.SERVICE_NAME,
|
2079
|
+
"api.name": req.contractDetails?.name,
|
2080
|
+
[SEMATTRS_HTTP_TARGET]: req.path,
|
2081
|
+
[SEMATTRS_HTTP_ROUTE]: req.originalPath,
|
2082
|
+
[SEMATTRS_HTTP_METHOD]: req.method,
|
2083
|
+
[SEMATTRS_HTTP_STATUS_CODE]: res.statusCode
|
2084
|
+
}
|
2085
|
+
});
|
2086
|
+
logger.error(errorString);
|
2087
|
+
};
|
2088
|
+
|
2089
|
+
// src/http/tracing/openTelemetryCollector.ts
|
2090
|
+
import { HyperExpressInstrumentation } from "@forklaunch/opentelemetry-instrumentation-hyper-express";
|
2091
|
+
import {
|
2092
|
+
metrics,
|
2093
|
+
trace as trace2
|
2094
|
+
} from "@opentelemetry/api";
|
2095
|
+
import { logs as logs2 } from "@opentelemetry/api-logs";
|
2096
|
+
import { OTLPLogExporter } from "@opentelemetry/exporter-logs-otlp-http";
|
2097
|
+
import { OTLPMetricExporter } from "@opentelemetry/exporter-metrics-otlp-http";
|
2098
|
+
import { OTLPTraceExporter } from "@opentelemetry/exporter-trace-otlp-http";
|
2099
|
+
import { ExpressInstrumentation } from "@opentelemetry/instrumentation-express";
|
2100
|
+
import { HttpInstrumentation } from "@opentelemetry/instrumentation-http";
|
2101
|
+
import { Resource } from "@opentelemetry/resources";
|
2102
|
+
import { BatchLogRecordProcessor } from "@opentelemetry/sdk-logs";
|
2103
|
+
import { PeriodicExportingMetricReader } from "@opentelemetry/sdk-metrics";
|
2104
|
+
import { NodeSDK } from "@opentelemetry/sdk-node";
|
2105
|
+
import {
|
2106
|
+
ATTR_SERVICE_NAME
|
2107
|
+
} from "@opentelemetry/semantic-conventions";
|
2108
|
+
var OpenTelemetryCollector = class {
|
2109
|
+
// scoped creation and create this in middleware when api execute. Also add correlation id
|
2110
|
+
constructor(serviceName, level, metricDefinitions) {
|
2111
|
+
this.serviceName = serviceName;
|
2112
|
+
this.logger = pinoLogger(level ?? "info");
|
2113
|
+
this.metrics = {};
|
2114
|
+
for (const [metricId, metricType] of Object.entries(
|
2115
|
+
metricDefinitions ?? {}
|
2116
|
+
)) {
|
2117
|
+
switch (metricType) {
|
2118
|
+
case "counter":
|
2119
|
+
this.metrics[metricId] = metrics.getMeter(this.serviceName).createCounter(metricId);
|
2120
|
+
break;
|
2121
|
+
case "gauge":
|
2122
|
+
this.metrics[metricId] = metrics.getMeter(this.serviceName).createGauge(metricId);
|
2123
|
+
break;
|
2124
|
+
case "histogram":
|
2125
|
+
this.metrics[metricId] = metrics.getMeter(this.serviceName).createHistogram(metricId);
|
2126
|
+
break;
|
2127
|
+
case "upDownCounter":
|
2128
|
+
this.metrics[metricId] = metrics.getMeter(this.serviceName).createUpDownCounter(metricId);
|
2129
|
+
break;
|
2130
|
+
case "observableCounter":
|
2131
|
+
this.metrics[metricId] = metrics.getMeter(this.serviceName).createObservableCounter(metricId);
|
2132
|
+
break;
|
2133
|
+
case "observableGauge":
|
2134
|
+
this.metrics[metricId] = metrics.getMeter(this.serviceName).createObservableGauge(metricId);
|
2135
|
+
break;
|
2136
|
+
case "observableUpDownCounter":
|
2137
|
+
this.metrics[metricId] = metrics.getMeter(this.serviceName).createObservableUpDownCounter(metricId);
|
2138
|
+
break;
|
2139
|
+
}
|
2140
|
+
}
|
2141
|
+
this.log("info", "OpenTelemetry (Traces + Logs + Metrics) started");
|
2142
|
+
}
|
2143
|
+
logger;
|
2144
|
+
metrics;
|
2145
|
+
log(level, msg, meta = {}) {
|
2146
|
+
const activeSpan = trace2.getActiveSpan();
|
2147
|
+
if (activeSpan) {
|
2148
|
+
const activeSpanContext = activeSpan.spanContext();
|
2149
|
+
meta.trace_id = activeSpanContext.traceId;
|
2150
|
+
meta.span_id = activeSpanContext.spanId;
|
2151
|
+
meta.trace_flags = activeSpanContext.traceFlags;
|
2152
|
+
}
|
2153
|
+
if (!meta.api_name) {
|
2154
|
+
meta.api_name = activeSpan?.attributes["api.name"] ?? "undefined";
|
2155
|
+
}
|
2156
|
+
this.logger[level](msg);
|
2157
|
+
logs2.getLogger(this.serviceName).emit({
|
2158
|
+
severityText: level,
|
2159
|
+
body: msg,
|
2160
|
+
attributes: meta
|
2161
|
+
});
|
2162
|
+
}
|
2163
|
+
getMetric(metricId) {
|
2164
|
+
return this.metrics[metricId];
|
2165
|
+
}
|
2166
|
+
};
|
2167
|
+
new NodeSDK({
|
2168
|
+
resource: new Resource({
|
2169
|
+
[ATTR_SERVICE_NAME]: process.env.SERVICE_NAME
|
2170
|
+
}),
|
2171
|
+
traceExporter: new OTLPTraceExporter({
|
2172
|
+
url: `${process.env.OTEL_EXPORTER_OTLP_ENDPOINT ?? "http://localhost:4318"}/v1/traces`
|
2173
|
+
}),
|
2174
|
+
metricReader: new PeriodicExportingMetricReader({
|
2175
|
+
exporter: new OTLPMetricExporter({
|
2176
|
+
url: `${process.env.OTEL_EXPORTER_OTLP_ENDPOINT ?? "http://localhost:4318"}/v1/metrics`
|
2177
|
+
}),
|
2178
|
+
exportIntervalMillis: 5e3
|
2179
|
+
}),
|
2180
|
+
logRecordProcessors: [
|
2181
|
+
new BatchLogRecordProcessor(
|
2182
|
+
new OTLPLogExporter({
|
2183
|
+
url: `${process.env.OTEL_EXPORTER_OTLP_ENDPOINT ?? "http://localhost:4318"}/v1/logs`
|
2184
|
+
})
|
2185
|
+
)
|
2186
|
+
],
|
2187
|
+
instrumentations: [
|
2188
|
+
new HttpInstrumentation(),
|
2189
|
+
new ExpressInstrumentation({
|
2190
|
+
requestHook: (span, info) => {
|
2191
|
+
span.setAttribute(
|
2192
|
+
"service.name",
|
2193
|
+
process.env.SERVICE_NAME ?? "unknown"
|
2194
|
+
);
|
2195
|
+
span.setAttribute("api.name", info.request.contractDetails?.name);
|
2196
|
+
}
|
2197
|
+
}),
|
2198
|
+
new HyperExpressInstrumentation()
|
2199
|
+
]
|
2200
|
+
}).start();
|
2201
|
+
var httpRequestsTotalCounter = metrics.getMeter(process.env.SERVICE_NAME || "unknown").createCounter("http_requests_total", {
|
2202
|
+
description: "Number of HTTP requests"
|
2203
|
+
});
|
2204
|
+
|
2035
2205
|
// src/http/middleware/response/parse.middleware.ts
|
2036
2206
|
import {
|
2037
2207
|
prettyPrintParseErrors as prettyPrintParseErrors2
|
2038
2208
|
} from "@forklaunch/validator";
|
2039
2209
|
function parse2(req, res, next) {
|
2040
|
-
console.debug("[MIDDLEWARE] parseResponse started");
|
2041
2210
|
const { headers, responses } = res.responseSchemas;
|
2042
2211
|
const parsedResponse = req.schemaValidator.parse(
|
2043
2212
|
responses?.[res.statusCode],
|
@@ -2087,6 +2256,7 @@ function enrichExpressLikeSend(instance, req, res, originalSend, data, shouldEnr
|
|
2087
2256
|
if (shouldEnrich) {
|
2088
2257
|
if (res.statusCode === 404) {
|
2089
2258
|
res.status(404);
|
2259
|
+
emitLoggerError(req, res, "Not Found");
|
2090
2260
|
originalSend.call(instance, "Not Found");
|
2091
2261
|
}
|
2092
2262
|
parse2(req, res, (err) => {
|
@@ -2097,6 +2267,7 @@ function enrichExpressLikeSend(instance, req, res, originalSend, data, shouldEnr
|
|
2097
2267
|
------------------
|
2098
2268
|
${res.locals.errorMessage}`;
|
2099
2269
|
}
|
2270
|
+
emitLoggerError(req, res, errorString);
|
2100
2271
|
res.status(500);
|
2101
2272
|
originalSend.call(instance, errorString);
|
2102
2273
|
parseErrorSent = true;
|
@@ -2107,16 +2278,35 @@ ${res.locals.errorMessage}`;
|
|
2107
2278
|
originalSend.call(instance, data);
|
2108
2279
|
}
|
2109
2280
|
}
|
2281
|
+
|
2282
|
+
// src/http/utils/recordMetric.ts
|
2283
|
+
import {
|
2284
|
+
SEMATTRS_HTTP_METHOD as SEMATTRS_HTTP_METHOD3,
|
2285
|
+
SEMATTRS_HTTP_ROUTE as SEMATTRS_HTTP_ROUTE3,
|
2286
|
+
SEMATTRS_HTTP_STATUS_CODE as SEMATTRS_HTTP_STATUS_CODE3
|
2287
|
+
} from "@opentelemetry/semantic-conventions";
|
2288
|
+
function recordMetric(req, res) {
|
2289
|
+
httpRequestsTotalCounter.add(1, {
|
2290
|
+
"service.name": process.env.SERVICE_NAME ?? "unknown",
|
2291
|
+
"api.name": req.contractDetails?.name,
|
2292
|
+
[SEMATTRS_HTTP_METHOD3]: req.method,
|
2293
|
+
[SEMATTRS_HTTP_ROUTE3]: req.originalPath,
|
2294
|
+
[SEMATTRS_HTTP_STATUS_CODE3]: res.statusCode || 0
|
2295
|
+
});
|
2296
|
+
}
|
2110
2297
|
export {
|
2111
2298
|
ForklaunchExpressLikeApplication,
|
2112
2299
|
ForklaunchExpressLikeRouter,
|
2113
2300
|
HTTPStatuses,
|
2301
|
+
OpenTelemetryCollector,
|
2114
2302
|
delete_,
|
2303
|
+
emitLoggerError,
|
2115
2304
|
enrichExpressLikeSend,
|
2116
2305
|
generateSwaggerDocument,
|
2117
2306
|
get,
|
2118
2307
|
getCodeForStatus,
|
2119
2308
|
head,
|
2309
|
+
httpRequestsTotalCounter,
|
2120
2310
|
isClientError,
|
2121
2311
|
isForklaunchRouter,
|
2122
2312
|
isInformational,
|
@@ -2129,6 +2319,7 @@ export {
|
|
2129
2319
|
patch,
|
2130
2320
|
post,
|
2131
2321
|
put,
|
2322
|
+
recordMetric,
|
2132
2323
|
trace,
|
2133
2324
|
typedHandler
|
2134
2325
|
};
|