@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.
@@ -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 errorAndMessage = await checkAuthorizationToken(
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 (Array.isArray(errorAndMessage)) {
169
- res.status(errorAndMessage[0]).send(errorAndMessage[1]);
170
- next?.(new Error(errorAndMessage[1]));
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
- console.debug("[MIDDLEWARE] enrichRequestDetails started");
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(contractDetails, requestSchema, responseSchemas),
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?.(error);
307
- console.error(error);
308
- if (!res.headersSent) {
309
- res.status(500).send("Internal Server Error");
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 swaggerDocument(port, tags, paths) {
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 swaggerDocument(port, tags, paths);
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
  };