@forklaunch/core 0.11.7 → 0.12.1

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.
@@ -257,7 +257,8 @@ async function parseRequestAuth(req, res, next) {
257
257
  }
258
258
 
259
259
  // src/http/middleware/request/createContext.middleware.ts
260
- import { trace } from "@opentelemetry/api";
260
+ import { getEnvVar } from "@forklaunch/common";
261
+ import { context, trace } from "@opentelemetry/api";
261
262
  import { v4 } from "uuid";
262
263
 
263
264
  // src/http/telemetry/constants.ts
@@ -282,20 +283,24 @@ function createContext(schemaValidator) {
282
283
  req.context = {
283
284
  correlationId
284
285
  };
285
- const span = trace.getActiveSpan();
286
+ const span = trace.getSpan(context.active());
286
287
  if (span != null) {
287
288
  req.context.span = span;
288
289
  req.context.span?.setAttribute(ATTR_CORRELATION_ID, correlationId);
290
+ req.context.span?.setAttribute(
291
+ ATTR_SERVICE_NAME,
292
+ getEnvVar("OTEL_SERVICE_NAME")
293
+ );
289
294
  }
290
295
  next?.();
291
296
  };
292
297
  }
293
298
 
294
299
  // src/http/middleware/request/enrichDetails.middleware.ts
295
- import { getEnvVar as getEnvVar2 } from "@forklaunch/common";
300
+ import { getEnvVar as getEnvVar3 } from "@forklaunch/common";
296
301
 
297
302
  // src/http/telemetry/openTelemetryCollector.ts
298
- import { getEnvVar } from "@forklaunch/common";
303
+ import { getEnvVar as getEnvVar2 } from "@forklaunch/common";
299
304
  import { HyperExpressInstrumentation } from "@forklaunch/opentelemetry-instrumentation-hyper-express";
300
305
  import {
301
306
  metrics
@@ -321,7 +326,7 @@ function isForklaunchRequest(request) {
321
326
  }
322
327
 
323
328
  // src/http/telemetry/pinoLogger.ts
324
- import { isNever as isNever2, safeStringify } from "@forklaunch/common";
329
+ import { isNever as isNever2 } from "@forklaunch/common";
325
330
  import { trace as trace2 } from "@opentelemetry/api";
326
331
  import { logs } from "@opentelemetry/api-logs";
327
332
  import pino from "pino";
@@ -357,6 +362,21 @@ function mapSeverity(level) {
357
362
  return 0;
358
363
  }
359
364
  }
365
+ function normalizeLogArgs(args) {
366
+ let message = "";
367
+ const metaObjects = [];
368
+ for (const arg of args) {
369
+ if (typeof arg === "string" && message === "") {
370
+ message = arg;
371
+ } else if (arg && typeof arg === "object" && !Array.isArray(arg)) {
372
+ metaObjects.push(arg);
373
+ } else {
374
+ message += ` ${String(arg)}`;
375
+ }
376
+ }
377
+ const metadata = Object.assign({}, ...metaObjects);
378
+ return [metadata, message.trim()];
379
+ }
360
380
  var PinoLogger = class _PinoLogger {
361
381
  pinoLogger;
362
382
  meta;
@@ -387,7 +407,7 @@ var PinoLogger = class _PinoLogger {
387
407
  return false;
388
408
  }
389
409
  return true;
390
- }).map(safeStringify);
410
+ });
391
411
  const activeSpan = trace2.getActiveSpan();
392
412
  if (activeSpan) {
393
413
  const activeSpanContext = activeSpan.spanContext();
@@ -405,7 +425,7 @@ var PinoLogger = class _PinoLogger {
405
425
  "correlation.id": "none",
406
426
  ...meta2
407
427
  };
408
- this.pinoLogger[level](...filteredArgs);
428
+ this.pinoLogger[level](...normalizeLogArgs(filteredArgs));
409
429
  logs.getLogger(process.env.OTEL_SERVICE_NAME ?? "unknown").emit({
410
430
  severityText: level,
411
431
  severityNumber: mapSeverity(level),
@@ -493,24 +513,24 @@ var OpenTelemetryCollector = class {
493
513
  return this.metrics[metricId];
494
514
  }
495
515
  };
496
- dotenv.config({ path: getEnvVar("DOTENV_FILE_PATH") });
516
+ dotenv.config({ path: getEnvVar2("DOTENV_FILE_PATH") });
497
517
  new NodeSDK({
498
518
  resource: resourceFromAttributes({
499
- [ATTR_SERVICE_NAME2]: getEnvVar("OTEL_SERVICE_NAME")
519
+ [ATTR_SERVICE_NAME2]: getEnvVar2("OTEL_SERVICE_NAME")
500
520
  }),
501
521
  traceExporter: new OTLPTraceExporter({
502
- url: `${getEnvVar("OTEL_EXPORTER_OTLP_ENDPOINT") ?? "http://localhost:4318"}/v1/traces`
522
+ url: `${getEnvVar2("OTEL_EXPORTER_OTLP_ENDPOINT") ?? "http://localhost:4318"}/v1/traces`
503
523
  }),
504
524
  metricReader: new PeriodicExportingMetricReader({
505
525
  exporter: new OTLPMetricExporter({
506
- url: `${getEnvVar("OTEL_EXPORTER_OTLP_ENDPOINT") ?? "http://localhost:4318"}/v1/metrics`
526
+ url: `${getEnvVar2("OTEL_EXPORTER_OTLP_ENDPOINT") ?? "http://localhost:4318"}/v1/metrics`
507
527
  }),
508
528
  exportIntervalMillis: 5e3
509
529
  }),
510
530
  logRecordProcessors: [
511
531
  new BatchLogRecordProcessor(
512
532
  new OTLPLogExporter({
513
- url: `${getEnvVar("OTEL_EXPORTER_OTLP_ENDPOINT") ?? "http://localhost:4318"}/v1/logs`
533
+ url: `${getEnvVar2("OTEL_EXPORTER_OTLP_ENDPOINT") ?? "http://localhost:4318"}/v1/logs`
514
534
  })
515
535
  )
516
536
  ],
@@ -518,11 +538,12 @@ new NodeSDK({
518
538
  new HttpInstrumentation({
519
539
  applyCustomAttributesOnSpan: (span, request) => {
520
540
  span.setAttribute(
521
- "service.name",
522
- getEnvVar("OTEL_SERVICE_NAME") ?? "unknown"
541
+ ATTR_SERVICE_NAME2,
542
+ getEnvVar2("OTEL_SERVICE_NAME") ?? "unknown"
523
543
  );
524
544
  if (isForklaunchRequest(request)) {
525
- span.setAttribute("api.name", request.contractDetails?.name);
545
+ span.setAttribute(ATTR_API_NAME, request.contractDetails?.name);
546
+ span.setAttribute(ATTR_CORRELATION_ID, request.context.correlationId);
526
547
  }
527
548
  }
528
549
  }),
@@ -530,10 +551,10 @@ new NodeSDK({
530
551
  new HyperExpressInstrumentation()
531
552
  ]
532
553
  }).start();
533
- var httpRequestsTotalCounter = metrics.getMeter(getEnvVar("OTEL_SERVICE_NAME") || "unknown").createCounter("http_requests_total", {
554
+ var httpRequestsTotalCounter = metrics.getMeter(getEnvVar2("OTEL_SERVICE_NAME") || "unknown").createCounter("http_requests_total", {
534
555
  description: "Number of HTTP requests"
535
556
  });
536
- var httpServerDurationHistogram = metrics.getMeter(getEnvVar("OTEL_SERVICE_NAME") || "unknown").createHistogram("http_server_duration", {
557
+ var httpServerDurationHistogram = metrics.getMeter(getEnvVar2("OTEL_SERVICE_NAME") || "unknown").createHistogram("http_server_duration", {
537
558
  description: "Duration of HTTP server requests",
538
559
  unit: "s"
539
560
  });
@@ -552,7 +573,7 @@ function enrichDetails(path, contractDetails, requestSchema, responseSchemas, op
552
573
  const [seconds, nanoseconds] = process.hrtime(startTime);
553
574
  const durationMs = seconds + nanoseconds / 1e9;
554
575
  httpServerDurationHistogram.record(durationMs, {
555
- [ATTR_SERVICE_NAME]: getEnvVar2("OTEL_SERVICE_NAME") || "unknown",
576
+ [ATTR_SERVICE_NAME]: getEnvVar3("OTEL_SERVICE_NAME") || "unknown",
556
577
  [ATTR_API_NAME]: req.contractDetails?.name || "unknown",
557
578
  [ATTR_HTTP_REQUEST_METHOD]: req.method,
558
579
  [ATTR_HTTP_ROUTE]: req.originalPath || "unknown",
@@ -2653,7 +2674,7 @@ var getCodeForStatus = (status) => {
2653
2674
  var httpStatusCodes_default = HTTPStatuses;
2654
2675
 
2655
2676
  // src/http/mcpGenerator/mcpGenerator.ts
2656
- import { isNever as isNever3, isRecord as isRecord3, safeStringify as safeStringify2 } from "@forklaunch/common";
2677
+ import { isNever as isNever3, isRecord as isRecord3, safeStringify } from "@forklaunch/common";
2657
2678
  import { string, ZodSchemaValidator } from "@forklaunch/validator/zod";
2658
2679
  import { FastMCP } from "fastmcp";
2659
2680
 
@@ -2665,8 +2686,9 @@ function isUnionable(schema) {
2665
2686
  // src/http/router/unpackRouters.ts
2666
2687
  function unpackRouters(routers, recursiveBasePath = []) {
2667
2688
  return routers.reduce((acc, router) => {
2689
+ const fullPath = [...recursiveBasePath, router.basePath].join("");
2668
2690
  acc.push({
2669
- fullPath: [...recursiveBasePath, router.basePath].join(""),
2691
+ fullPath,
2670
2692
  router
2671
2693
  });
2672
2694
  acc.push(
@@ -2704,7 +2726,7 @@ function generateInputSchema(schemaValidator, body, params, query, requestHeader
2704
2726
  } : {}
2705
2727
  });
2706
2728
  }
2707
- function generateMcpServer(schemaValidator, protocol, host, port, version, routers, options2, contentTypeMap) {
2729
+ function generateMcpServer(schemaValidator, protocol, host, port, version, application, options2, contentTypeMap) {
2708
2730
  if (!(schemaValidator instanceof ZodSchemaValidator)) {
2709
2731
  throw new Error(
2710
2732
  "Schema validator must be an instance of ZodSchemaValidator"
@@ -2715,7 +2737,15 @@ function generateMcpServer(schemaValidator, protocol, host, port, version, route
2715
2737
  name: options2?.name ?? "mcp-server",
2716
2738
  version
2717
2739
  });
2718
- unpackRouters(routers).forEach(({ fullPath, router }) => {
2740
+ [
2741
+ {
2742
+ fullPath: application.basePath === "/" ? "" : application.basePath,
2743
+ router: application
2744
+ },
2745
+ ...unpackRouters(application.routers, [
2746
+ application.basePath === "/" ? "" : application.basePath
2747
+ ])
2748
+ ].forEach(({ fullPath, router }) => {
2719
2749
  router.routes.forEach((route) => {
2720
2750
  const inputSchemas = [];
2721
2751
  if (route.contractDetails.versions) {
@@ -2778,7 +2808,7 @@ function generateMcpServer(schemaValidator, protocol, host, port, version, route
2778
2808
  if (discriminatedBody) {
2779
2809
  switch (discriminatedBody.parserType) {
2780
2810
  case "json": {
2781
- parsedBody = safeStringify2(body);
2811
+ parsedBody = safeStringify(body);
2782
2812
  break;
2783
2813
  }
2784
2814
  case "text": {
@@ -2810,7 +2840,7 @@ function generateMcpServer(schemaValidator, protocol, host, port, version, route
2810
2840
  parsedBody = new URLSearchParams(
2811
2841
  Object.entries(body).map(([key, value]) => [
2812
2842
  key,
2813
- safeStringify2(value)
2843
+ safeStringify(value)
2814
2844
  ])
2815
2845
  );
2816
2846
  } else {
@@ -2820,7 +2850,7 @@ function generateMcpServer(schemaValidator, protocol, host, port, version, route
2820
2850
  }
2821
2851
  default: {
2822
2852
  isNever3(discriminatedBody.parserType);
2823
- parsedBody = safeStringify2(body);
2853
+ parsedBody = safeStringify(body);
2824
2854
  break;
2825
2855
  }
2826
2856
  }
@@ -2829,7 +2859,7 @@ function generateMcpServer(schemaValidator, protocol, host, port, version, route
2829
2859
  const queryString = new URLSearchParams(
2830
2860
  Object.entries(query).map(([key, value]) => [
2831
2861
  key,
2832
- safeStringify2(value)
2862
+ safeStringify(value)
2833
2863
  ])
2834
2864
  ).toString();
2835
2865
  url += queryString ? `?${queryString}` : "";
@@ -2862,7 +2892,7 @@ function generateMcpServer(schemaValidator, protocol, host, port, version, route
2862
2892
  content: [
2863
2893
  {
2864
2894
  type: "text",
2865
- text: safeStringify2(await response.json())
2895
+ text: safeStringify(await response.json())
2866
2896
  }
2867
2897
  ]
2868
2898
  };
@@ -2955,7 +2985,7 @@ function parse2(req, res, next) {
2955
2985
  }
2956
2986
  const statusCode = Number(res.statusCode);
2957
2987
  const parsedResponse = schemaValidator.parse(
2958
- responses?.[statusCode],
2988
+ [400, 401, 404, 403, 500].includes(statusCode) ? schemaValidator.union([schemaValidator.string, responses?.[statusCode]]) : responses?.[statusCode],
2959
2989
  res.bodyData
2960
2990
  );
2961
2991
  const parsedHeaders = schemaValidator.parse(
@@ -3017,12 +3047,12 @@ import {
3017
3047
  isNodeJsWriteableStream,
3018
3048
  isRecord as isRecord4,
3019
3049
  readableStreamToAsyncIterable,
3020
- safeStringify as safeStringify3
3050
+ safeStringify as safeStringify2
3021
3051
  } from "@forklaunch/common";
3022
3052
  import { Readable, Transform } from "stream";
3023
3053
 
3024
3054
  // src/http/telemetry/recordMetric.ts
3025
- import { getEnvVar as getEnvVar3 } from "@forklaunch/common";
3055
+ import { getEnvVar as getEnvVar4 } from "@forklaunch/common";
3026
3056
  import {
3027
3057
  ATTR_HTTP_REQUEST_METHOD as ATTR_HTTP_REQUEST_METHOD3,
3028
3058
  ATTR_HTTP_RESPONSE_STATUS_CODE as ATTR_HTTP_RESPONSE_STATUS_CODE3,
@@ -3034,7 +3064,7 @@ function recordMetric(req, res) {
3034
3064
  return;
3035
3065
  }
3036
3066
  httpRequestsTotalCounter.add(1, {
3037
- [ATTR_SERVICE_NAME3]: getEnvVar3("OTEL_SERVICE_NAME"),
3067
+ [ATTR_SERVICE_NAME3]: getEnvVar4("OTEL_SERVICE_NAME"),
3038
3068
  [ATTR_API_NAME]: req.contractDetails?.name,
3039
3069
  [ATTR_CORRELATION_ID]: req.context.correlationId,
3040
3070
  [ATTR_HTTP_REQUEST_METHOD3]: req.method,
@@ -3122,7 +3152,7 @@ ${res.locals.errorMessage}`;
3122
3152
  if (!errorSent) {
3123
3153
  let data2 = "";
3124
3154
  for (const [key, value] of Object.entries(chunk)) {
3125
- data2 += `${key}: ${typeof value === "string" ? value : safeStringify3(value)}
3155
+ data2 += `${key}: ${typeof value === "string" ? value : safeStringify2(value)}
3126
3156
  `;
3127
3157
  }
3128
3158
  data2 += "\n";
@@ -3209,7 +3239,7 @@ function transformBasePath(basePath) {
3209
3239
  }
3210
3240
  return `/${basePath}`;
3211
3241
  }
3212
- function generateOpenApiDocument(protocol, host, port, versionedTags, versionedPaths, versionedSecuritySchemes, otherServers) {
3242
+ function generateOpenApiDocument(serverUrls, serverDescriptions, versionedTags, versionedPaths, versionedSecuritySchemes, otherServers) {
3213
3243
  return {
3214
3244
  [OPENAPI_DEFAULT_VERSION]: {
3215
3245
  openapi: "3.1.0",
@@ -3222,10 +3252,10 @@ function generateOpenApiDocument(protocol, host, port, versionedTags, versionedP
3222
3252
  },
3223
3253
  tags: versionedTags[OPENAPI_DEFAULT_VERSION],
3224
3254
  servers: [
3225
- {
3226
- url: `${protocol}://${host}:${port}`,
3227
- description: "Main Server"
3228
- },
3255
+ ...serverUrls.map((url, index) => ({
3256
+ url,
3257
+ description: serverDescriptions?.[index]
3258
+ })),
3229
3259
  ...otherServers || []
3230
3260
  ],
3231
3261
  paths: versionedPaths[OPENAPI_DEFAULT_VERSION]
@@ -3244,10 +3274,11 @@ function generateOpenApiDocument(protocol, host, port, versionedTags, versionedP
3244
3274
  },
3245
3275
  tags: versionedTags[version],
3246
3276
  servers: [
3247
- {
3248
- url: `${protocol}://${host}:${port}`,
3249
- description: "Main Server"
3250
- }
3277
+ ...serverUrls.map((url, index) => ({
3278
+ url,
3279
+ description: serverDescriptions?.[index]
3280
+ })),
3281
+ ...otherServers || []
3251
3282
  ],
3252
3283
  paths
3253
3284
  }
@@ -3396,7 +3427,7 @@ function generateOperationObject(schemaValidator, path, method, controllerName,
3396
3427
  }
3397
3428
  return operationObject;
3398
3429
  }
3399
- function generateOpenApiSpecs(schemaValidator, protocol, host, port, routers, otherServers) {
3430
+ function generateOpenApiSpecs(schemaValidator, serverUrls, serverDescriptions, application, otherServers) {
3400
3431
  const versionedPaths = {
3401
3432
  [OPENAPI_DEFAULT_VERSION]: {}
3402
3433
  };
@@ -3406,7 +3437,15 @@ function generateOpenApiSpecs(schemaValidator, protocol, host, port, routers, ot
3406
3437
  const versionedSecuritySchemes = {
3407
3438
  [OPENAPI_DEFAULT_VERSION]: {}
3408
3439
  };
3409
- unpackRouters(routers).forEach(({ fullPath, router }) => {
3440
+ [
3441
+ {
3442
+ fullPath: application.basePath === "/" ? "" : application.basePath,
3443
+ router: application
3444
+ },
3445
+ ...unpackRouters(application.routers, [
3446
+ application.basePath === "/" ? "" : application.basePath
3447
+ ])
3448
+ ].forEach(({ fullPath, router }) => {
3410
3449
  const controllerName = transformBasePath(fullPath);
3411
3450
  router.routes.forEach((route) => {
3412
3451
  const openApiPath = openApiCompliantPath(
@@ -3501,9 +3540,8 @@ function generateOpenApiSpecs(schemaValidator, protocol, host, port, routers, ot
3501
3540
  });
3502
3541
  });
3503
3542
  return generateOpenApiDocument(
3504
- protocol,
3505
- host,
3506
- port,
3543
+ serverUrls,
3544
+ serverDescriptions,
3507
3545
  versionedTags,
3508
3546
  versionedPaths,
3509
3547
  versionedSecuritySchemes,
@@ -3512,7 +3550,7 @@ function generateOpenApiSpecs(schemaValidator, protocol, host, port, routers, ot
3512
3550
  }
3513
3551
 
3514
3552
  // src/http/sdk/sdkClient.ts
3515
- import { hashString, safeStringify as safeStringify4, toRecord as toRecord2 } from "@forklaunch/common";
3553
+ import { hashString, safeStringify as safeStringify3, toRecord as toRecord2 } from "@forklaunch/common";
3516
3554
 
3517
3555
  // src/http/guards/isSdkRouter.ts
3518
3556
  function isSdkRouter(value) {
@@ -3524,12 +3562,12 @@ function mapToSdk(schemaValidator, routerMap, runningPath = void 0) {
3524
3562
  const routerUniquenessCache = /* @__PURE__ */ new Set();
3525
3563
  return Object.fromEntries(
3526
3564
  Object.entries(routerMap).map(([key, value]) => {
3527
- if (routerUniquenessCache.has(hashString(safeStringify4(value)))) {
3565
+ if (routerUniquenessCache.has(hashString(safeStringify3(value)))) {
3528
3566
  throw new Error(
3529
3567
  `SdkClient: Cannot use the same router pointer twice. Please clone the duplicate router with .clone() or only use the router once.`
3530
3568
  );
3531
3569
  }
3532
- routerUniquenessCache.add(hashString(safeStringify4(value)));
3570
+ routerUniquenessCache.add(hashString(safeStringify3(value)));
3533
3571
  const currentPath = runningPath ? [runningPath, key].join(".") : key;
3534
3572
  if (isSdkRouter(value)) {
3535
3573
  Object.entries(value.sdkPaths).forEach(([routePath, sdkKey]) => {