@emmett-community/emmett-expressjs-with-openapi 0.2.0 → 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/dist/index.d.cts CHANGED
@@ -6,6 +6,54 @@ import { Brand, Event, TestEventStream, EventStore } from '@event-driven-io/emme
6
6
  import supertest, { Test, Response as Response$1 } from 'supertest';
7
7
  import TestAgent from 'supertest/lib/agent';
8
8
 
9
+ /**
10
+ * Observability types for emmett-expressjs-with-openapi.
11
+ *
12
+ * This module provides optional logging integration
13
+ * following a "silent by default" philosophy.
14
+ */
15
+ /**
16
+ * Minimal logger interface compatible with Pino, Winston, console, and similar loggers.
17
+ * All methods are optional to support partial implementations.
18
+ */
19
+ interface Logger {
20
+ debug?(msg: string, data?: unknown): void;
21
+ info?(msg: string, data?: unknown): void;
22
+ warn?(msg: string, data?: unknown): void;
23
+ error?(msg: string, err?: unknown): void;
24
+ }
25
+ /**
26
+ * Observability configuration options.
27
+ */
28
+ type ObservabilityOptions = {
29
+ /**
30
+ * Optional logger instance for diagnostic output.
31
+ * When not provided, the library operates silently.
32
+ *
33
+ * @example
34
+ * ```typescript
35
+ * import pino from 'pino';
36
+ *
37
+ * const logger = pino();
38
+ *
39
+ * const app = await getApplication({
40
+ * observability: { logger },
41
+ * });
42
+ * ```
43
+ */
44
+ logger?: Logger;
45
+ };
46
+ /**
47
+ * Internal helper to safely call logger methods, handling partial implementations.
48
+ * Always use: safeLog.error(logger, msg, error) - never { error }
49
+ */
50
+ declare const safeLog: {
51
+ debug: (logger: Logger | undefined, msg: string, data?: unknown) => void | undefined;
52
+ info: (logger: Logger | undefined, msg: string, data?: unknown) => void | undefined;
53
+ warn: (logger: Logger | undefined, msg: string, data?: unknown) => void | undefined;
54
+ error: (logger: Logger | undefined, msg: string, error?: unknown) => void | undefined;
55
+ };
56
+
9
57
  type AuthClient = {
10
58
  verifyIdToken: (token: string) => Promise<unknown>;
11
59
  };
@@ -332,6 +380,12 @@ declare const send: (response: Response, statusCode: number, options?: HttpRespo
332
380
  declare const sendProblem: (response: Response, statusCode: number, options?: HttpProblemResponseOptions) => void;
333
381
 
334
382
  type WebApiSetup = (router: Router) => void;
383
+ /**
384
+ * Options forwarded to pino-http. Typed loosely to avoid hard dependency.
385
+ *
386
+ * @see https://github.com/pinojs/pino-http
387
+ */
388
+ type PinoHttpOptions = Record<string, unknown>;
335
389
  type ApplicationOptions = {
336
390
  apis?: WebApiSetup[];
337
391
  mapError?: ErrorToProblemDetailsMapping;
@@ -339,6 +393,25 @@ type ApplicationOptions = {
339
393
  disableJsonMiddleware?: boolean;
340
394
  disableUrlEncodingMiddleware?: boolean;
341
395
  disableProblemDetailsMiddleware?: boolean;
396
+ /**
397
+ * Optional Pino HTTP logger configuration.
398
+ * When true, enables pino-http with defaults.
399
+ * When an object, forwards the options to pino-http.
400
+ * Requires the 'pino-http' package to be installed.
401
+ *
402
+ * @see https://github.com/pinojs/pino-http
403
+ * @example
404
+ * ```typescript
405
+ * const app = await getApplication({
406
+ * pinoHttp: true,
407
+ * });
408
+ *
409
+ * const app = await getApplication({
410
+ * pinoHttp: { autoLogging: false },
411
+ * });
412
+ * ```
413
+ */
414
+ pinoHttp?: boolean | PinoHttpOptions;
342
415
  /**
343
416
  * Optional OpenAPI validator configuration.
344
417
  * When provided, enables request/response validation against an OpenAPI specification.
@@ -366,15 +439,60 @@ type ApplicationOptions = {
366
439
  * ```
367
440
  */
368
441
  openApiValidator?: OpenApiValidatorOptions;
442
+ /**
443
+ * Optional observability configuration for logging.
444
+ * When not provided, the library operates silently (no logs).
445
+ *
446
+ * @example
447
+ * ```typescript
448
+ * import pino from 'pino';
449
+ *
450
+ * const logger = pino();
451
+ *
452
+ * const app = await getApplication({
453
+ * observability: { logger },
454
+ * });
455
+ * ```
456
+ */
457
+ observability?: ObservabilityOptions;
369
458
  };
370
459
  declare const getApplication: (options: ApplicationOptions) => Promise<express.Application>;
371
460
  type StartApiOptions = {
372
461
  port?: number;
462
+ /**
463
+ * Optional logger for lifecycle events.
464
+ */
465
+ logger?: Logger;
373
466
  };
374
467
  declare const startAPI: (app: Application, options?: StartApiOptions) => http.Server<typeof http.IncomingMessage, typeof http.ServerResponse>;
375
468
 
376
469
  type HttpHandler<RequestType extends Request> = (request: RequestType) => Promise<HttpResponse> | HttpResponse;
377
470
  declare const on: <RequestType extends Request>(handle: HttpHandler<RequestType>) => (request: RequestType, response: Response, _next: NextFunction) => Promise<void>;
471
+ /**
472
+ * Options for the traced handler wrapper.
473
+ */
474
+ type TracedHandlerOptions = {
475
+ /**
476
+ * Custom span name. Defaults to 'emmett.http.handle_request'.
477
+ */
478
+ spanName?: string;
479
+ };
480
+ /**
481
+ * Wraps an HTTP handler with OpenTelemetry tracing.
482
+ * Creates a span that captures request method, route, and response status.
483
+ *
484
+ * If OpenTelemetry is not initialized by the application, spans are no-ops
485
+ * with zero overhead.
486
+ *
487
+ * @example
488
+ * ```typescript
489
+ * router.post('/carts', tracedOn(async (req) => {
490
+ * // Your handler logic
491
+ * return Created({ createdId: cartId });
492
+ * }));
493
+ * ```
494
+ */
495
+ declare const tracedOn: <RequestType extends Request>(handle: HttpHandler<RequestType>, options?: TracedHandlerOptions) => (request: RequestType, response: Response, _next: NextFunction) => Promise<void>;
378
496
  declare const OK: (options?: HttpResponseOptions) => HttpResponse;
379
497
  declare const Created: (options: CreatedHttpResponseOptions) => HttpResponse;
380
498
  declare const Accepted: (options: AcceptedHttpResponseOptions) => HttpResponse;
@@ -459,4 +577,4 @@ declare const ApiE2ESpecification: {
459
577
  */
460
578
  declare const registerHandlerModule: (modulePath: string, moduleExports: any) => void;
461
579
 
462
- export { Accepted, type AcceptedHttpResponseOptions, ApiE2ESpecification, type ApiE2ESpecificationAssert, ApiSpecification, type ApiSpecificationAssert, type ApplicationOptions, BadRequest, Conflict, Created, type CreatedHttpResponseOptions, DefaultHttpProblemResponseOptions, DefaultHttpResponseOptions, type E2EResponseAssert, type ETag, ETagErrors, type ErrorToProblemDetailsMapping, type FirebaseAuthSecurityOptions, Forbidden, HeaderNames, type HttpHandler, HttpProblem, type HttpProblemResponseOptions, HttpResponse, type HttpResponseOptions, type ImportedHandlerModules, NoContent, type NoContentHttpResponseOptions, NotFound, OK, type OpenAPIV3Document, type OpenApiValidatorOptions, PreconditionFailed, type ResponseAssert, type SecurityHandlers, type StartApiOptions, type TestRequest, type WeakETag, WeakETagRegex, type WebApiSetup, createFirebaseAuthSecurityHandlers, createOpenApiValidatorOptions, existingStream, expect, expectError, expectNewEvents, expectResponse, getApplication, getETagFromIfMatch, getETagFromIfNotMatch, getETagValueFromIfMatch, getWeakETagValue, isOpenApiValidatorAvailable, isWeakETag, on, registerHandlerModule, send, sendAccepted, sendCreated, sendProblem, setETag, startAPI, toWeakETag };
580
+ export { Accepted, type AcceptedHttpResponseOptions, ApiE2ESpecification, type ApiE2ESpecificationAssert, ApiSpecification, type ApiSpecificationAssert, type ApplicationOptions, BadRequest, Conflict, Created, type CreatedHttpResponseOptions, DefaultHttpProblemResponseOptions, DefaultHttpResponseOptions, type E2EResponseAssert, type ETag, ETagErrors, type ErrorToProblemDetailsMapping, type FirebaseAuthSecurityOptions, Forbidden, HeaderNames, type HttpHandler, HttpProblem, type HttpProblemResponseOptions, HttpResponse, type HttpResponseOptions, type ImportedHandlerModules, type Logger, NoContent, type NoContentHttpResponseOptions, NotFound, OK, type ObservabilityOptions, type OpenAPIV3Document, type OpenApiValidatorOptions, type PinoHttpOptions, PreconditionFailed, type ResponseAssert, type SecurityHandlers, type StartApiOptions, type TestRequest, type TracedHandlerOptions, type WeakETag, WeakETagRegex, type WebApiSetup, createFirebaseAuthSecurityHandlers, createOpenApiValidatorOptions, existingStream, expect, expectError, expectNewEvents, expectResponse, getApplication, getETagFromIfMatch, getETagFromIfNotMatch, getETagValueFromIfMatch, getWeakETagValue, isOpenApiValidatorAvailable, isWeakETag, on, registerHandlerModule, safeLog, send, sendAccepted, sendCreated, sendProblem, setETag, startAPI, toWeakETag, tracedOn };
package/dist/index.d.ts CHANGED
@@ -6,6 +6,54 @@ import { Brand, Event, TestEventStream, EventStore } from '@event-driven-io/emme
6
6
  import supertest, { Test, Response as Response$1 } from 'supertest';
7
7
  import TestAgent from 'supertest/lib/agent';
8
8
 
9
+ /**
10
+ * Observability types for emmett-expressjs-with-openapi.
11
+ *
12
+ * This module provides optional logging integration
13
+ * following a "silent by default" philosophy.
14
+ */
15
+ /**
16
+ * Minimal logger interface compatible with Pino, Winston, console, and similar loggers.
17
+ * All methods are optional to support partial implementations.
18
+ */
19
+ interface Logger {
20
+ debug?(msg: string, data?: unknown): void;
21
+ info?(msg: string, data?: unknown): void;
22
+ warn?(msg: string, data?: unknown): void;
23
+ error?(msg: string, err?: unknown): void;
24
+ }
25
+ /**
26
+ * Observability configuration options.
27
+ */
28
+ type ObservabilityOptions = {
29
+ /**
30
+ * Optional logger instance for diagnostic output.
31
+ * When not provided, the library operates silently.
32
+ *
33
+ * @example
34
+ * ```typescript
35
+ * import pino from 'pino';
36
+ *
37
+ * const logger = pino();
38
+ *
39
+ * const app = await getApplication({
40
+ * observability: { logger },
41
+ * });
42
+ * ```
43
+ */
44
+ logger?: Logger;
45
+ };
46
+ /**
47
+ * Internal helper to safely call logger methods, handling partial implementations.
48
+ * Always use: safeLog.error(logger, msg, error) - never { error }
49
+ */
50
+ declare const safeLog: {
51
+ debug: (logger: Logger | undefined, msg: string, data?: unknown) => void | undefined;
52
+ info: (logger: Logger | undefined, msg: string, data?: unknown) => void | undefined;
53
+ warn: (logger: Logger | undefined, msg: string, data?: unknown) => void | undefined;
54
+ error: (logger: Logger | undefined, msg: string, error?: unknown) => void | undefined;
55
+ };
56
+
9
57
  type AuthClient = {
10
58
  verifyIdToken: (token: string) => Promise<unknown>;
11
59
  };
@@ -332,6 +380,12 @@ declare const send: (response: Response, statusCode: number, options?: HttpRespo
332
380
  declare const sendProblem: (response: Response, statusCode: number, options?: HttpProblemResponseOptions) => void;
333
381
 
334
382
  type WebApiSetup = (router: Router) => void;
383
+ /**
384
+ * Options forwarded to pino-http. Typed loosely to avoid hard dependency.
385
+ *
386
+ * @see https://github.com/pinojs/pino-http
387
+ */
388
+ type PinoHttpOptions = Record<string, unknown>;
335
389
  type ApplicationOptions = {
336
390
  apis?: WebApiSetup[];
337
391
  mapError?: ErrorToProblemDetailsMapping;
@@ -339,6 +393,25 @@ type ApplicationOptions = {
339
393
  disableJsonMiddleware?: boolean;
340
394
  disableUrlEncodingMiddleware?: boolean;
341
395
  disableProblemDetailsMiddleware?: boolean;
396
+ /**
397
+ * Optional Pino HTTP logger configuration.
398
+ * When true, enables pino-http with defaults.
399
+ * When an object, forwards the options to pino-http.
400
+ * Requires the 'pino-http' package to be installed.
401
+ *
402
+ * @see https://github.com/pinojs/pino-http
403
+ * @example
404
+ * ```typescript
405
+ * const app = await getApplication({
406
+ * pinoHttp: true,
407
+ * });
408
+ *
409
+ * const app = await getApplication({
410
+ * pinoHttp: { autoLogging: false },
411
+ * });
412
+ * ```
413
+ */
414
+ pinoHttp?: boolean | PinoHttpOptions;
342
415
  /**
343
416
  * Optional OpenAPI validator configuration.
344
417
  * When provided, enables request/response validation against an OpenAPI specification.
@@ -366,15 +439,60 @@ type ApplicationOptions = {
366
439
  * ```
367
440
  */
368
441
  openApiValidator?: OpenApiValidatorOptions;
442
+ /**
443
+ * Optional observability configuration for logging.
444
+ * When not provided, the library operates silently (no logs).
445
+ *
446
+ * @example
447
+ * ```typescript
448
+ * import pino from 'pino';
449
+ *
450
+ * const logger = pino();
451
+ *
452
+ * const app = await getApplication({
453
+ * observability: { logger },
454
+ * });
455
+ * ```
456
+ */
457
+ observability?: ObservabilityOptions;
369
458
  };
370
459
  declare const getApplication: (options: ApplicationOptions) => Promise<express.Application>;
371
460
  type StartApiOptions = {
372
461
  port?: number;
462
+ /**
463
+ * Optional logger for lifecycle events.
464
+ */
465
+ logger?: Logger;
373
466
  };
374
467
  declare const startAPI: (app: Application, options?: StartApiOptions) => http.Server<typeof http.IncomingMessage, typeof http.ServerResponse>;
375
468
 
376
469
  type HttpHandler<RequestType extends Request> = (request: RequestType) => Promise<HttpResponse> | HttpResponse;
377
470
  declare const on: <RequestType extends Request>(handle: HttpHandler<RequestType>) => (request: RequestType, response: Response, _next: NextFunction) => Promise<void>;
471
+ /**
472
+ * Options for the traced handler wrapper.
473
+ */
474
+ type TracedHandlerOptions = {
475
+ /**
476
+ * Custom span name. Defaults to 'emmett.http.handle_request'.
477
+ */
478
+ spanName?: string;
479
+ };
480
+ /**
481
+ * Wraps an HTTP handler with OpenTelemetry tracing.
482
+ * Creates a span that captures request method, route, and response status.
483
+ *
484
+ * If OpenTelemetry is not initialized by the application, spans are no-ops
485
+ * with zero overhead.
486
+ *
487
+ * @example
488
+ * ```typescript
489
+ * router.post('/carts', tracedOn(async (req) => {
490
+ * // Your handler logic
491
+ * return Created({ createdId: cartId });
492
+ * }));
493
+ * ```
494
+ */
495
+ declare const tracedOn: <RequestType extends Request>(handle: HttpHandler<RequestType>, options?: TracedHandlerOptions) => (request: RequestType, response: Response, _next: NextFunction) => Promise<void>;
378
496
  declare const OK: (options?: HttpResponseOptions) => HttpResponse;
379
497
  declare const Created: (options: CreatedHttpResponseOptions) => HttpResponse;
380
498
  declare const Accepted: (options: AcceptedHttpResponseOptions) => HttpResponse;
@@ -459,4 +577,4 @@ declare const ApiE2ESpecification: {
459
577
  */
460
578
  declare const registerHandlerModule: (modulePath: string, moduleExports: any) => void;
461
579
 
462
- export { Accepted, type AcceptedHttpResponseOptions, ApiE2ESpecification, type ApiE2ESpecificationAssert, ApiSpecification, type ApiSpecificationAssert, type ApplicationOptions, BadRequest, Conflict, Created, type CreatedHttpResponseOptions, DefaultHttpProblemResponseOptions, DefaultHttpResponseOptions, type E2EResponseAssert, type ETag, ETagErrors, type ErrorToProblemDetailsMapping, type FirebaseAuthSecurityOptions, Forbidden, HeaderNames, type HttpHandler, HttpProblem, type HttpProblemResponseOptions, HttpResponse, type HttpResponseOptions, type ImportedHandlerModules, NoContent, type NoContentHttpResponseOptions, NotFound, OK, type OpenAPIV3Document, type OpenApiValidatorOptions, PreconditionFailed, type ResponseAssert, type SecurityHandlers, type StartApiOptions, type TestRequest, type WeakETag, WeakETagRegex, type WebApiSetup, createFirebaseAuthSecurityHandlers, createOpenApiValidatorOptions, existingStream, expect, expectError, expectNewEvents, expectResponse, getApplication, getETagFromIfMatch, getETagFromIfNotMatch, getETagValueFromIfMatch, getWeakETagValue, isOpenApiValidatorAvailable, isWeakETag, on, registerHandlerModule, send, sendAccepted, sendCreated, sendProblem, setETag, startAPI, toWeakETag };
580
+ export { Accepted, type AcceptedHttpResponseOptions, ApiE2ESpecification, type ApiE2ESpecificationAssert, ApiSpecification, type ApiSpecificationAssert, type ApplicationOptions, BadRequest, Conflict, Created, type CreatedHttpResponseOptions, DefaultHttpProblemResponseOptions, DefaultHttpResponseOptions, type E2EResponseAssert, type ETag, ETagErrors, type ErrorToProblemDetailsMapping, type FirebaseAuthSecurityOptions, Forbidden, HeaderNames, type HttpHandler, HttpProblem, type HttpProblemResponseOptions, HttpResponse, type HttpResponseOptions, type ImportedHandlerModules, type Logger, NoContent, type NoContentHttpResponseOptions, NotFound, OK, type ObservabilityOptions, type OpenAPIV3Document, type OpenApiValidatorOptions, type PinoHttpOptions, PreconditionFailed, type ResponseAssert, type SecurityHandlers, type StartApiOptions, type TestRequest, type TracedHandlerOptions, type WeakETag, WeakETagRegex, type WebApiSetup, createFirebaseAuthSecurityHandlers, createOpenApiValidatorOptions, existingStream, expect, expectError, expectNewEvents, expectResponse, getApplication, getETagFromIfMatch, getETagFromIfNotMatch, getETagValueFromIfMatch, getWeakETagValue, isOpenApiValidatorAvailable, isWeakETag, on, registerHandlerModule, safeLog, send, sendAccepted, sendCreated, sendProblem, setETag, startAPI, toWeakETag, tracedOn };
package/dist/index.js CHANGED
@@ -1,11 +1,15 @@
1
1
  import {
2
2
  registerHandlerModule
3
3
  } from "./chunk-5TC7YUZR.js";
4
+ import {
5
+ safeLog
6
+ } from "./chunk-I46UH36B.js";
4
7
 
5
8
  // src/index.ts
6
9
  import "express-async-errors";
7
10
 
8
11
  // src/application.ts
12
+ import { trace, SpanStatusCode } from "@opentelemetry/api";
9
13
  import express, {
10
14
  Router
11
15
  } from "express";
@@ -15,7 +19,8 @@ import { createRequire } from "module";
15
19
 
16
20
  // src/middlewares/problemDetailsMiddleware.ts
17
21
  import { ProblemDocument } from "http-problem-details";
18
- var problemDetailsMiddleware = (mapError) => (error, request, response, _next) => {
22
+ var problemDetailsMiddleware = (mapError, logger) => (error, request, response, _next) => {
23
+ safeLog.error(logger, "Request error", error);
19
24
  let problemDetails;
20
25
  if (mapError) problemDetails = mapError(error, request);
21
26
  problemDetails = problemDetails ?? defaultErrorToProblemDetailsMapping(error);
@@ -39,6 +44,7 @@ var defaultErrorToProblemDetailsMapping = (error) => {
39
44
  };
40
45
 
41
46
  // src/application.ts
47
+ var tracer = trace.getTracer("@emmett-community/emmett-expressjs-with-openapi");
42
48
  var getApplication = async (options) => {
43
49
  const app = express();
44
50
  const {
@@ -48,10 +54,39 @@ var getApplication = async (options) => {
48
54
  disableJsonMiddleware,
49
55
  disableUrlEncodingMiddleware,
50
56
  disableProblemDetailsMiddleware,
51
- openApiValidator
57
+ pinoHttp,
58
+ openApiValidator,
59
+ observability
52
60
  } = options;
61
+ const logger = observability?.logger;
62
+ safeLog.debug(logger, "Initializing Express application", {
63
+ hasApis: !!apis?.length,
64
+ hasOpenApiValidator: !!openApiValidator,
65
+ hasPinoHttp: !!pinoHttp
66
+ });
53
67
  const router = Router();
54
68
  app.set("etag", enableDefaultExpressEtag ?? false);
69
+ if (pinoHttp !== void 0 && pinoHttp !== false) {
70
+ try {
71
+ const require2 = createRequire(import.meta.url);
72
+ const mod = require2("pino-http");
73
+ const provider = mod.default ?? mod;
74
+ if (typeof provider !== "function") {
75
+ throw new Error("Invalid pino-http module: missing default export");
76
+ }
77
+ const options2 = pinoHttp === true ? void 0 : pinoHttp;
78
+ const middleware = provider(options2);
79
+ app.use(middleware);
80
+ } catch (error) {
81
+ safeLog.warn(
82
+ logger,
83
+ "Pino HTTP configuration provided but pino-http package is not installed. Install it with: npm install pino-http"
84
+ );
85
+ throw new Error(
86
+ "pino-http package is required when pinoHttp option is used"
87
+ );
88
+ }
89
+ }
55
90
  if (!disableJsonMiddleware) app.use(express.json());
56
91
  if (!disableUrlEncodingMiddleware)
57
92
  app.use(
@@ -65,20 +100,52 @@ var getApplication = async (options) => {
65
100
  activateESMResolver();
66
101
  const handlersBasePath = typeof openApiValidator.operationHandlers === "string" ? openApiValidator.operationHandlers : openApiValidator.operationHandlers.basePath;
67
102
  if (handlersBasePath) {
68
- const { extractHandlerModules } = await import("./openapi-parser-CCYU636U.js");
69
- const { importAndRegisterHandlers } = await import("./handler-importer-OJGFQON5.js");
70
- try {
71
- const modules = await extractHandlerModules(
72
- openApiValidator.apiSpec,
73
- handlersBasePath
74
- );
75
- const importedHandlers = await importAndRegisterHandlers(modules);
76
- if (openApiValidator.initializeHandlers) {
77
- await openApiValidator.initializeHandlers(importedHandlers);
103
+ const { extractHandlerModules } = await import("./openapi-parser-QYXNHNSZ.js");
104
+ const { importAndRegisterHandlers } = await import("./handler-importer-6A237UML.js");
105
+ const modules = await tracer.startActiveSpan(
106
+ "emmett.openapi.parse_spec",
107
+ async (span) => {
108
+ try {
109
+ const result = await extractHandlerModules(
110
+ openApiValidator.apiSpec,
111
+ handlersBasePath,
112
+ logger
113
+ );
114
+ span.setAttribute("emmett.handlers.count", result.length);
115
+ span.setStatus({ code: SpanStatusCode.OK });
116
+ return result;
117
+ } catch (error) {
118
+ span.recordException(error);
119
+ span.setStatus({ code: SpanStatusCode.ERROR });
120
+ throw error;
121
+ } finally {
122
+ span.end();
123
+ }
78
124
  }
79
- } catch (error) {
80
- console.error("Failed to auto-import handler modules:", error);
81
- throw error;
125
+ );
126
+ const importedHandlers = await tracer.startActiveSpan(
127
+ "emmett.http.import_handlers",
128
+ async (span) => {
129
+ try {
130
+ const result = await importAndRegisterHandlers(modules, logger);
131
+ span.setAttribute(
132
+ "emmett.handlers.count",
133
+ Object.keys(result).length
134
+ );
135
+ span.setStatus({ code: SpanStatusCode.OK });
136
+ return result;
137
+ } catch (error) {
138
+ safeLog.error(logger, "Failed to auto-import handler modules", error);
139
+ span.recordException(error);
140
+ span.setStatus({ code: SpanStatusCode.ERROR });
141
+ throw error;
142
+ } finally {
143
+ span.end();
144
+ }
145
+ }
146
+ );
147
+ if (openApiValidator.initializeHandlers) {
148
+ await openApiValidator.initializeHandlers(importedHandlers);
82
149
  }
83
150
  }
84
151
  } else {
@@ -114,8 +181,9 @@ var getApplication = async (options) => {
114
181
  } else {
115
182
  app.use(middleware);
116
183
  }
117
- } catch {
118
- console.warn(
184
+ } catch (error) {
185
+ safeLog.warn(
186
+ logger,
119
187
  "OpenAPI validator configuration provided but express-openapi-validator package is not installed. Install it with: npm install express-openapi-validator"
120
188
  );
121
189
  throw new Error(
@@ -130,14 +198,15 @@ var getApplication = async (options) => {
130
198
  app.use(router);
131
199
  }
132
200
  if (!disableProblemDetailsMiddleware)
133
- app.use(problemDetailsMiddleware(mapError));
201
+ app.use(problemDetailsMiddleware(mapError, logger));
202
+ safeLog.info(logger, "Express application initialized");
134
203
  return app;
135
204
  };
136
205
  var startAPI = (app, options = { port: 3e3 }) => {
137
- const { port } = options;
206
+ const { port, logger } = options;
138
207
  const server = http.createServer(app);
139
208
  server.on("listening", () => {
140
- console.info("server up listening");
209
+ safeLog.info(logger, "Server up listening", { port });
141
210
  });
142
211
  return server.listen(port);
143
212
  };
@@ -191,10 +260,34 @@ var getETagValueFromIfMatch = (request) => {
191
260
  };
192
261
 
193
262
  // src/handler.ts
263
+ import { trace as trace2, SpanStatusCode as SpanStatusCode2 } from "@opentelemetry/api";
264
+ var tracer2 = trace2.getTracer("@emmett-community/emmett-expressjs-with-openapi");
194
265
  var on = (handle) => async (request, response, _next) => {
195
266
  const setResponse = await Promise.resolve(handle(request));
196
267
  return setResponse(response);
197
268
  };
269
+ var tracedOn = (handle, options) => async (request, response, _next) => {
270
+ const spanName = options?.spanName ?? "emmett.http.handle_request";
271
+ return tracer2.startActiveSpan(spanName, async (span) => {
272
+ try {
273
+ span.setAttribute("http.method", request.method);
274
+ const route = (request.baseUrl ?? "") + (request.route?.path ?? request.path);
275
+ span.setAttribute("http.route", route);
276
+ const setResponse = await Promise.resolve(handle(request));
277
+ setResponse(response);
278
+ span.setAttribute("http.status_code", response.statusCode);
279
+ span.setStatus({
280
+ code: response.statusCode >= 400 ? SpanStatusCode2.ERROR : SpanStatusCode2.OK
281
+ });
282
+ } catch (error) {
283
+ span.recordException(error);
284
+ span.setStatus({ code: SpanStatusCode2.ERROR });
285
+ throw error;
286
+ } finally {
287
+ span.end();
288
+ }
289
+ });
290
+ };
198
291
  var OK = (options) => (response) => {
199
292
  send(response, 200, options);
200
293
  };
@@ -473,12 +566,14 @@ export {
473
566
  isWeakETag,
474
567
  on,
475
568
  registerHandlerModule,
569
+ safeLog,
476
570
  send,
477
571
  sendAccepted,
478
572
  sendCreated,
479
573
  sendProblem,
480
574
  setETag,
481
575
  startAPI,
482
- toWeakETag
576
+ toWeakETag,
577
+ tracedOn
483
578
  };
484
579
  //# sourceMappingURL=index.js.map