@emmett-community/emmett-expressjs-with-openapi 0.3.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/README.md CHANGED
@@ -31,16 +31,19 @@ npm install @my-f-startup/firebase-auth-express # optional: Firebase Auth handl
31
31
  ### Peer Dependencies
32
32
 
33
33
  Required:
34
+
34
35
  - `@event-driven-io/emmett` ^0.39.1
35
36
  - `express` ^4.19.2
36
37
  - `express-async-errors` ^3.1.1
37
38
  - `http-problem-details` ^0.1.5
38
39
 
39
40
  TypeScript types:
41
+
40
42
  - `@types/express` ^4.17.21
41
43
  - `@types/supertest` ^6.0.2 (if using testing helpers)
42
44
 
43
45
  Optional (feature-gated):
46
+
44
47
  - `express-openapi-validator` ^5.3.7 (OpenAPI validation)
45
48
  - `pino-http` ^9.0.0 (HTTP logging)
46
49
  - `@my-f-startup/firebase-auth-express` 0.1.0 (Firebase Auth handlers)
@@ -103,6 +106,7 @@ const app = await getApplication({
103
106
  ```
104
107
 
105
108
  Highlights:
109
+
106
110
  - Enable/disable request or response validation, including per-field tweaks (coercion, unknown query params, removing additional fields, etc.).
107
111
  - Register custom security handlers with `validateSecurity.handlers`.
108
112
  - Serve the parsed spec via `serveSpec`, configure file upload limits, ignore health-check routes, or validate the spec itself.
@@ -156,25 +160,67 @@ const app = await getApplication({
156
160
 
157
161
  `@my-f-startup/firebase-auth-express` relies on `firebase-admin`. Make sure the Admin SDK is installed, initialized, and configured for your environment (credentials or emulator).
158
162
 
163
+ ## Observability
164
+
165
+ This package supports optional logging and tracing.
166
+
167
+ ### Logging
168
+
169
+ Inject a Pino-compatible logger:
170
+
171
+ ```typescript
172
+ import pino from 'pino';
173
+
174
+ const logger = pino();
175
+
176
+ const app = await getApplication({
177
+ observability: { logger },
178
+ });
179
+
180
+ startAPI(app, { port: 3000, logger });
181
+ ```
182
+
183
+ No logs are emitted when logger is not provided.
184
+
185
+ ### Tracing
186
+
187
+ If your application initializes OpenTelemetry, this package will emit spans automatically. Tracing is passive - spans are no-ops when OpenTelemetry is not configured.
188
+
189
+ ```typescript
190
+ import { tracedOn, OK } from '@emmett-community/emmett-expressjs-with-openapi';
191
+
192
+ router.post('/carts', tracedOn(async (req) => {
193
+ // Your handler logic
194
+ return OK({ body: { success: true } });
195
+ }));
196
+ ```
197
+
198
+ This package never initializes OpenTelemetry - configure your tracer provider in your application.
199
+
159
200
  ## API Reference
160
201
 
161
202
  ### Application
203
+
162
204
  - `getApplication(options)` - Creates and configures the Express app.
163
205
  - `startAPI(app, options)` - Starts the HTTP server.
164
206
 
165
207
  ### OpenAPI
208
+
166
209
  - `createOpenApiValidatorOptions(apiSpec, options)` - Helper to assemble OpenAPI validator config.
167
210
  - `isOpenApiValidatorAvailable()` - Type guard for optional validator dependency.
168
211
  - `createFirebaseAuthSecurityHandlers(options)` - Firebase Auth security handlers.
169
212
  - `registerHandlerModule(handlersPath, module)` - Manual registration for operation handlers.
170
213
 
171
214
  ### HTTP helpers
215
+
172
216
  - `send`, `sendCreated`, `sendAccepted`, `sendProblem` - Standard HTTP responses.
173
217
 
174
218
  ### ETag helpers
219
+
175
220
  - `toWeakETag`, `getETagFromIfMatch`, `getETagFromIfNotMatch`, `getETagValueFromIfMatch`, `setETag`.
176
221
 
177
222
  ### Testing helpers
223
+
178
224
  - `ApiSpecification`, `ApiE2ESpecification`
179
225
  - `expect`, `expectNewEvents`, `expectResponse`, `expectError`
180
226
 
@@ -0,0 +1,12 @@
1
+ "use strict";Object.defineProperty(exports, "__esModule", {value: true}); function _optionalChain(ops) { let lastAccessLHS = undefined; let value = ops[0]; let i = 1; while (i < ops.length) { const op = ops[i]; const fn = ops[i + 1]; i += 2; if ((op === 'optionalAccess' || op === 'optionalCall') && value == null) { return undefined; } if (op === 'access' || op === 'optionalAccess') { lastAccessLHS = value; value = fn(value); } else if (op === 'call' || op === 'optionalCall') { value = fn((...args) => value.call(lastAccessLHS, ...args)); lastAccessLHS = undefined; } } return value; }// src/observability.ts
2
+ var safeLog = {
3
+ debug: (logger, msg, data) => _optionalChain([logger, 'optionalAccess', _ => _.debug, 'optionalCall', _2 => _2(msg, data)]),
4
+ info: (logger, msg, data) => _optionalChain([logger, 'optionalAccess', _3 => _3.info, 'optionalCall', _4 => _4(msg, data)]),
5
+ warn: (logger, msg, data) => _optionalChain([logger, 'optionalAccess', _5 => _5.warn, 'optionalCall', _6 => _6(msg, data)]),
6
+ error: (logger, msg, error) => _optionalChain([logger, 'optionalAccess', _7 => _7.error, 'optionalCall', _8 => _8(msg, error)])
7
+ };
8
+
9
+
10
+
11
+ exports.safeLog = safeLog;
12
+ //# sourceMappingURL=chunk-BY65ZALY.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["/home/runner/work/emmett-expressjs-with-openapi/emmett-expressjs-with-openapi/dist/chunk-BY65ZALY.cjs","../src/observability.ts"],"names":[],"mappings":"AAAA;AC4CO,IAAM,QAAA,EAAU;AAAA,EACrB,KAAA,EAAO,CAAC,MAAA,EAA4B,GAAA,EAAa,IAAA,EAAA,mBAC/C,MAAA,2BAAQ,KAAA,0BAAA,CAAQ,GAAA,EAAK,IAAI,GAAA;AAAA,EAC3B,IAAA,EAAM,CAAC,MAAA,EAA4B,GAAA,EAAa,IAAA,EAAA,mBAC9C,MAAA,6BAAQ,IAAA,0BAAA,CAAO,GAAA,EAAK,IAAI,GAAA;AAAA,EAC1B,IAAA,EAAM,CAAC,MAAA,EAA4B,GAAA,EAAa,IAAA,EAAA,mBAC9C,MAAA,6BAAQ,IAAA,0BAAA,CAAO,GAAA,EAAK,IAAI,GAAA;AAAA,EAC1B,KAAA,EAAO,CAAC,MAAA,EAA4B,GAAA,EAAa,KAAA,EAAA,mBAC/C,MAAA,6BAAQ,KAAA,0BAAA,CAAQ,GAAA,EAAK,KAAK;AAC9B,CAAA;AD9CA;AACA;AACE;AACF,0BAAC","file":"/home/runner/work/emmett-expressjs-with-openapi/emmett-expressjs-with-openapi/dist/chunk-BY65ZALY.cjs","sourcesContent":[null,"/**\n * Observability types for emmett-expressjs-with-openapi.\n *\n * This module provides optional logging integration\n * following a \"silent by default\" philosophy.\n */\n\n/**\n * Minimal logger interface compatible with Pino, Winston, console, and similar loggers.\n * All methods are optional to support partial implementations.\n */\nexport interface Logger {\n debug?(msg: string, data?: unknown): void;\n info?(msg: string, data?: unknown): void;\n warn?(msg: string, data?: unknown): void;\n error?(msg: string, err?: unknown): void;\n}\n\n/**\n * Observability configuration options.\n */\nexport type ObservabilityOptions = {\n /**\n * Optional logger instance for diagnostic output.\n * When not provided, the library operates silently.\n *\n * @example\n * ```typescript\n * import pino from 'pino';\n *\n * const logger = pino();\n *\n * const app = await getApplication({\n * observability: { logger },\n * });\n * ```\n */\n logger?: Logger;\n};\n\n/**\n * Internal helper to safely call logger methods, handling partial implementations.\n * Always use: safeLog.error(logger, msg, error) - never { error }\n */\nexport const safeLog = {\n debug: (logger: Logger | undefined, msg: string, data?: unknown) =>\n logger?.debug?.(msg, data),\n info: (logger: Logger | undefined, msg: string, data?: unknown) =>\n logger?.info?.(msg, data),\n warn: (logger: Logger | undefined, msg: string, data?: unknown) =>\n logger?.warn?.(msg, data),\n error: (logger: Logger | undefined, msg: string, error?: unknown) =>\n logger?.error?.(msg, error),\n};\n"]}
@@ -0,0 +1,12 @@
1
+ // src/observability.ts
2
+ var safeLog = {
3
+ debug: (logger, msg, data) => logger?.debug?.(msg, data),
4
+ info: (logger, msg, data) => logger?.info?.(msg, data),
5
+ warn: (logger, msg, data) => logger?.warn?.(msg, data),
6
+ error: (logger, msg, error) => logger?.error?.(msg, error)
7
+ };
8
+
9
+ export {
10
+ safeLog
11
+ };
12
+ //# sourceMappingURL=chunk-I46UH36B.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/observability.ts"],"sourcesContent":["/**\n * Observability types for emmett-expressjs-with-openapi.\n *\n * This module provides optional logging integration\n * following a \"silent by default\" philosophy.\n */\n\n/**\n * Minimal logger interface compatible with Pino, Winston, console, and similar loggers.\n * All methods are optional to support partial implementations.\n */\nexport interface Logger {\n debug?(msg: string, data?: unknown): void;\n info?(msg: string, data?: unknown): void;\n warn?(msg: string, data?: unknown): void;\n error?(msg: string, err?: unknown): void;\n}\n\n/**\n * Observability configuration options.\n */\nexport type ObservabilityOptions = {\n /**\n * Optional logger instance for diagnostic output.\n * When not provided, the library operates silently.\n *\n * @example\n * ```typescript\n * import pino from 'pino';\n *\n * const logger = pino();\n *\n * const app = await getApplication({\n * observability: { logger },\n * });\n * ```\n */\n logger?: Logger;\n};\n\n/**\n * Internal helper to safely call logger methods, handling partial implementations.\n * Always use: safeLog.error(logger, msg, error) - never { error }\n */\nexport const safeLog = {\n debug: (logger: Logger | undefined, msg: string, data?: unknown) =>\n logger?.debug?.(msg, data),\n info: (logger: Logger | undefined, msg: string, data?: unknown) =>\n logger?.info?.(msg, data),\n warn: (logger: Logger | undefined, msg: string, data?: unknown) =>\n logger?.warn?.(msg, data),\n error: (logger: Logger | undefined, msg: string, error?: unknown) =>\n logger?.error?.(msg, error),\n};\n"],"mappings":";AA4CO,IAAM,UAAU;AAAA,EACrB,OAAO,CAAC,QAA4B,KAAa,SAC/C,QAAQ,QAAQ,KAAK,IAAI;AAAA,EAC3B,MAAM,CAAC,QAA4B,KAAa,SAC9C,QAAQ,OAAO,KAAK,IAAI;AAAA,EAC1B,MAAM,CAAC,QAA4B,KAAa,SAC9C,QAAQ,OAAO,KAAK,IAAI;AAAA,EAC1B,OAAO,CAAC,QAA4B,KAAa,UAC/C,QAAQ,QAAQ,KAAK,KAAK;AAC9B;","names":[]}
@@ -1,18 +1,37 @@
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/internal/handler-importer.ts
6
9
  import { pathToFileURL } from "url";
7
- async function importAndRegisterHandlers(modules) {
10
+ async function importAndRegisterHandlers(modules, logger) {
11
+ safeLog.debug(logger, "Importing handler modules", {
12
+ count: modules.length,
13
+ modules: modules.map((m) => m.moduleName)
14
+ });
8
15
  const importedHandlers = {};
9
16
  for (const module of modules) {
10
17
  try {
18
+ safeLog.debug(logger, "Importing handler module", {
19
+ moduleName: module.moduleName,
20
+ absolutePath: module.absolutePath
21
+ });
11
22
  const fileUrl = pathToFileURL(module.absolutePath).href;
12
23
  const importedModule = await import(fileUrl);
13
24
  registerHandlerModule(module.absolutePath, importedModule);
14
25
  importedHandlers[module.moduleName] = importedModule;
26
+ safeLog.debug(logger, "Handler module imported successfully", {
27
+ moduleName: module.moduleName
28
+ });
15
29
  } catch (error) {
30
+ safeLog.error(
31
+ logger,
32
+ `Failed to import handler module "${module.moduleName}"`,
33
+ error
34
+ );
16
35
  throw new Error(
17
36
  `Failed to import handler module "${module.moduleName}" from ${module.absolutePath}: ${error.message}`
18
37
  );
@@ -23,4 +42,4 @@ async function importAndRegisterHandlers(modules) {
23
42
  export {
24
43
  importAndRegisterHandlers
25
44
  };
26
- //# sourceMappingURL=handler-importer-OJGFQON5.js.map
45
+ //# sourceMappingURL=handler-importer-6A237UML.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/internal/handler-importer.ts"],"sourcesContent":["/**\n * Handler module importer.\n *\n * INTERNAL MODULE - Not part of public API.\n *\n * Dynamically imports handler modules and registers them in the ESM resolver cache,\n * enabling automatic handler discovery without manual registration.\n */\n\nimport { pathToFileURL } from 'node:url';\nimport { type Logger, safeLog } from '../observability';\nimport { registerHandlerModule } from './esm-resolver.js';\nimport type { HandlerModuleInfo } from './openapi-parser.js';\n\nexport type ImportedHandlerModules = Record<string, any>;\n\n/**\n * Dynamically import and register all handler modules.\n *\n * @param modules - Handler module information from OpenAPI parser\n * @param logger - Optional logger for debug output\n * @returns Object containing all imported modules, keyed by module name\n */\nexport async function importAndRegisterHandlers(\n modules: HandlerModuleInfo[],\n logger?: Logger,\n): Promise<ImportedHandlerModules> {\n safeLog.debug(logger, 'Importing handler modules', {\n count: modules.length,\n modules: modules.map((m) => m.moduleName),\n });\n\n const importedHandlers: ImportedHandlerModules = {};\n\n for (const module of modules) {\n try {\n safeLog.debug(logger, 'Importing handler module', {\n moduleName: module.moduleName,\n absolutePath: module.absolutePath,\n });\n\n // Convert to file:// URL for dynamic import\n const fileUrl = pathToFileURL(module.absolutePath).href;\n\n // Dynamically import the handler module\n const importedModule = await import(fileUrl);\n\n // Register in ESM resolver cache\n registerHandlerModule(module.absolutePath, importedModule);\n\n // Store in result object keyed by module name\n importedHandlers[module.moduleName] = importedModule;\n\n safeLog.debug(logger, 'Handler module imported successfully', {\n moduleName: module.moduleName,\n });\n } catch (error) {\n safeLog.error(\n logger,\n `Failed to import handler module \"${module.moduleName}\"`,\n error,\n );\n throw new Error(\n `Failed to import handler module \"${module.moduleName}\" from ${module.absolutePath}: ${\n (error as Error).message\n }`,\n );\n }\n }\n\n return importedHandlers;\n}\n"],"mappings":";;;;;;;;AASA,SAAS,qBAAqB;AAc9B,eAAsB,0BACpB,SACA,QACiC;AACjC,UAAQ,MAAM,QAAQ,6BAA6B;AAAA,IACjD,OAAO,QAAQ;AAAA,IACf,SAAS,QAAQ,IAAI,CAAC,MAAM,EAAE,UAAU;AAAA,EAC1C,CAAC;AAED,QAAM,mBAA2C,CAAC;AAElD,aAAW,UAAU,SAAS;AAC5B,QAAI;AACF,cAAQ,MAAM,QAAQ,4BAA4B;AAAA,QAChD,YAAY,OAAO;AAAA,QACnB,cAAc,OAAO;AAAA,MACvB,CAAC;AAGD,YAAM,UAAU,cAAc,OAAO,YAAY,EAAE;AAGnD,YAAM,iBAAiB,MAAM,OAAO;AAGpC,4BAAsB,OAAO,cAAc,cAAc;AAGzD,uBAAiB,OAAO,UAAU,IAAI;AAEtC,cAAQ,MAAM,QAAQ,wCAAwC;AAAA,QAC5D,YAAY,OAAO;AAAA,MACrB,CAAC;AAAA,IACH,SAAS,OAAO;AACd,cAAQ;AAAA,QACN;AAAA,QACA,oCAAoC,OAAO,UAAU;AAAA,QACrD;AAAA,MACF;AACA,YAAM,IAAI;AAAA,QACR,oCAAoC,OAAO,UAAU,UAAU,OAAO,YAAY,KAC/E,MAAgB,OACnB;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAEA,SAAO;AACT;","names":[]}
@@ -1,19 +1,38 @@
1
1
  "use strict";Object.defineProperty(exports, "__esModule", {value: true}); function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) { newObj[key] = obj[key]; } } } newObj.default = obj; return newObj; } }
2
2
 
3
3
  var _chunkMRSGTROWcjs = require('./chunk-MRSGTROW.cjs');
4
+
5
+
6
+ var _chunkBY65ZALYcjs = require('./chunk-BY65ZALY.cjs');
4
7
  require('./chunk-GS7T56RP.cjs');
5
8
 
6
9
  // src/internal/handler-importer.ts
7
10
  var _url = require('url');
8
- async function importAndRegisterHandlers(modules) {
11
+ async function importAndRegisterHandlers(modules, logger) {
12
+ _chunkBY65ZALYcjs.safeLog.debug(logger, "Importing handler modules", {
13
+ count: modules.length,
14
+ modules: modules.map((m) => m.moduleName)
15
+ });
9
16
  const importedHandlers = {};
10
17
  for (const module of modules) {
11
18
  try {
19
+ _chunkBY65ZALYcjs.safeLog.debug(logger, "Importing handler module", {
20
+ moduleName: module.moduleName,
21
+ absolutePath: module.absolutePath
22
+ });
12
23
  const fileUrl = _url.pathToFileURL.call(void 0, module.absolutePath).href;
13
24
  const importedModule = await Promise.resolve().then(() => _interopRequireWildcard(require(fileUrl)));
14
25
  _chunkMRSGTROWcjs.registerHandlerModule.call(void 0, module.absolutePath, importedModule);
15
26
  importedHandlers[module.moduleName] = importedModule;
27
+ _chunkBY65ZALYcjs.safeLog.debug(logger, "Handler module imported successfully", {
28
+ moduleName: module.moduleName
29
+ });
16
30
  } catch (error) {
31
+ _chunkBY65ZALYcjs.safeLog.error(
32
+ logger,
33
+ `Failed to import handler module "${module.moduleName}"`,
34
+ error
35
+ );
17
36
  throw new Error(
18
37
  `Failed to import handler module "${module.moduleName}" from ${module.absolutePath}: ${error.message}`
19
38
  );
@@ -24,4 +43,4 @@ async function importAndRegisterHandlers(modules) {
24
43
 
25
44
 
26
45
  exports.importAndRegisterHandlers = importAndRegisterHandlers;
27
- //# sourceMappingURL=handler-importer-4BVBIZX3.cjs.map
46
+ //# sourceMappingURL=handler-importer-UFV7TKBN.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["/home/runner/work/emmett-expressjs-with-openapi/emmett-expressjs-with-openapi/dist/handler-importer-UFV7TKBN.cjs","../src/internal/handler-importer.ts"],"names":[],"mappings":"AAAA;AACE;AACF,wDAA6B;AAC7B;AACE;AACF,wDAA6B;AAC7B,gCAA6B;AAC7B;AACA;ACCA,0BAA8B;AAc9B,MAAA,SAAsB,yBAAA,CACpB,OAAA,EACA,MAAA,EACiC;AACjC,EAAA,yBAAA,CAAQ,KAAA,CAAM,MAAA,EAAQ,2BAAA,EAA6B;AAAA,IACjD,KAAA,EAAO,OAAA,CAAQ,MAAA;AAAA,IACf,OAAA,EAAS,OAAA,CAAQ,GAAA,CAAI,CAAC,CAAA,EAAA,GAAM,CAAA,CAAE,UAAU;AAAA,EAC1C,CAAC,CAAA;AAED,EAAA,MAAM,iBAAA,EAA2C,CAAC,CAAA;AAElD,EAAA,IAAA,CAAA,MAAW,OAAA,GAAU,OAAA,EAAS;AAC5B,IAAA,IAAI;AACF,MAAA,yBAAA,CAAQ,KAAA,CAAM,MAAA,EAAQ,0BAAA,EAA4B;AAAA,QAChD,UAAA,EAAY,MAAA,CAAO,UAAA;AAAA,QACnB,YAAA,EAAc,MAAA,CAAO;AAAA,MACvB,CAAC,CAAA;AAGD,MAAA,MAAM,QAAA,EAAU,gCAAA,MAAc,CAAO,YAAY,CAAA,CAAE,IAAA;AAGnD,MAAA,MAAM,eAAA,EAAiB,MAAM,4DAAA,CAAO,OAAA,GAAA;AAGpC,MAAA,qDAAA,MAAsB,CAAO,YAAA,EAAc,cAAc,CAAA;AAGzD,MAAA,gBAAA,CAAiB,MAAA,CAAO,UAAU,EAAA,EAAI,cAAA;AAEtC,MAAA,yBAAA,CAAQ,KAAA,CAAM,MAAA,EAAQ,sCAAA,EAAwC;AAAA,QAC5D,UAAA,EAAY,MAAA,CAAO;AAAA,MACrB,CAAC,CAAA;AAAA,IACH,EAAA,MAAA,CAAS,KAAA,EAAO;AACd,MAAA,yBAAA,CAAQ,KAAA;AAAA,QACN,MAAA;AAAA,QACA,CAAA,iCAAA,EAAoC,MAAA,CAAO,UAAU,CAAA,CAAA,CAAA;AAAA,QACrD;AAAA,MACF,CAAA;AACA,MAAA,MAAM,IAAI,KAAA;AAAA,QACR,CAAA,iCAAA,EAAoC,MAAA,CAAO,UAAU,CAAA,OAAA,EAAU,MAAA,CAAO,YAAY,CAAA,EAAA,EAC/E,KAAA,CAAgB,OACnB,CAAA;AAAA,MAAA;AACF,IAAA;AACF,EAAA;AAGF,EAAA;AACF;AD7BA;AACA;AACA","file":"/home/runner/work/emmett-expressjs-with-openapi/emmett-expressjs-with-openapi/dist/handler-importer-UFV7TKBN.cjs","sourcesContent":[null,"/**\n * Handler module importer.\n *\n * INTERNAL MODULE - Not part of public API.\n *\n * Dynamically imports handler modules and registers them in the ESM resolver cache,\n * enabling automatic handler discovery without manual registration.\n */\n\nimport { pathToFileURL } from 'node:url';\nimport { type Logger, safeLog } from '../observability';\nimport { registerHandlerModule } from './esm-resolver.js';\nimport type { HandlerModuleInfo } from './openapi-parser.js';\n\nexport type ImportedHandlerModules = Record<string, any>;\n\n/**\n * Dynamically import and register all handler modules.\n *\n * @param modules - Handler module information from OpenAPI parser\n * @param logger - Optional logger for debug output\n * @returns Object containing all imported modules, keyed by module name\n */\nexport async function importAndRegisterHandlers(\n modules: HandlerModuleInfo[],\n logger?: Logger,\n): Promise<ImportedHandlerModules> {\n safeLog.debug(logger, 'Importing handler modules', {\n count: modules.length,\n modules: modules.map((m) => m.moduleName),\n });\n\n const importedHandlers: ImportedHandlerModules = {};\n\n for (const module of modules) {\n try {\n safeLog.debug(logger, 'Importing handler module', {\n moduleName: module.moduleName,\n absolutePath: module.absolutePath,\n });\n\n // Convert to file:// URL for dynamic import\n const fileUrl = pathToFileURL(module.absolutePath).href;\n\n // Dynamically import the handler module\n const importedModule = await import(fileUrl);\n\n // Register in ESM resolver cache\n registerHandlerModule(module.absolutePath, importedModule);\n\n // Store in result object keyed by module name\n importedHandlers[module.moduleName] = importedModule;\n\n safeLog.debug(logger, 'Handler module imported successfully', {\n moduleName: module.moduleName,\n });\n } catch (error) {\n safeLog.error(\n logger,\n `Failed to import handler module \"${module.moduleName}\"`,\n error,\n );\n throw new Error(\n `Failed to import handler module \"${module.moduleName}\" from ${module.absolutePath}: ${\n (error as Error).message\n }`,\n );\n }\n }\n\n return importedHandlers;\n}\n"]}
package/dist/index.cjs CHANGED
@@ -3,12 +3,16 @@
3
3
  var _chunkMRSGTROWcjs = require('./chunk-MRSGTROW.cjs');
4
4
 
5
5
 
6
+ var _chunkBY65ZALYcjs = require('./chunk-BY65ZALY.cjs');
7
+
8
+
6
9
  var _chunkGS7T56RPcjs = require('./chunk-GS7T56RP.cjs');
7
10
 
8
11
  // src/index.ts
9
12
  require('express-async-errors');
10
13
 
11
14
  // src/application.ts
15
+ var _api = require('@opentelemetry/api');
12
16
 
13
17
 
14
18
  var _express = require('express'); var _express2 = _interopRequireDefault(_express);
@@ -18,7 +22,8 @@ var _module = require('module');
18
22
 
19
23
  // src/middlewares/problemDetailsMiddleware.ts
20
24
  var _httpproblemdetails = require('http-problem-details');
21
- var problemDetailsMiddleware = (mapError) => (error, request, response, _next) => {
25
+ var problemDetailsMiddleware = (mapError, logger) => (error, request, response, _next) => {
26
+ _chunkBY65ZALYcjs.safeLog.error(logger, "Request error", error);
22
27
  let problemDetails;
23
28
  if (mapError) problemDetails = mapError(error, request);
24
29
  problemDetails = _nullishCoalesce(problemDetails, () => ( defaultErrorToProblemDetailsMapping(error)));
@@ -42,6 +47,7 @@ var defaultErrorToProblemDetailsMapping = (error) => {
42
47
  };
43
48
 
44
49
  // src/application.ts
50
+ var tracer = _api.trace.getTracer("@emmett-community/emmett-expressjs-with-openapi");
45
51
  var getApplication = async (options) => {
46
52
  const app = _express2.default.call(void 0, );
47
53
  const {
@@ -52,8 +58,15 @@ var getApplication = async (options) => {
52
58
  disableUrlEncodingMiddleware,
53
59
  disableProblemDetailsMiddleware,
54
60
  pinoHttp,
55
- openApiValidator
61
+ openApiValidator,
62
+ observability
56
63
  } = options;
64
+ const logger = _optionalChain([observability, 'optionalAccess', _ => _.logger]);
65
+ _chunkBY65ZALYcjs.safeLog.debug(logger, "Initializing Express application", {
66
+ hasApis: !!_optionalChain([apis, 'optionalAccess', _2 => _2.length]),
67
+ hasOpenApiValidator: !!openApiValidator,
68
+ hasPinoHttp: !!pinoHttp
69
+ });
57
70
  const router = _express.Router.call(void 0, );
58
71
  app.set("etag", _nullishCoalesce(enableDefaultExpressEtag, () => ( false)));
59
72
  if (pinoHttp !== void 0 && pinoHttp !== false) {
@@ -67,8 +80,9 @@ var getApplication = async (options) => {
67
80
  const options2 = pinoHttp === true ? void 0 : pinoHttp;
68
81
  const middleware = provider(options2);
69
82
  app.use(middleware);
70
- } catch (e) {
71
- console.warn(
83
+ } catch (error) {
84
+ _chunkBY65ZALYcjs.safeLog.warn(
85
+ logger,
72
86
  "Pino HTTP configuration provided but pino-http package is not installed. Install it with: npm install pino-http"
73
87
  );
74
88
  throw new Error(
@@ -89,20 +103,52 @@ var getApplication = async (options) => {
89
103
  activateESMResolver();
90
104
  const handlersBasePath = typeof openApiValidator.operationHandlers === "string" ? openApiValidator.operationHandlers : openApiValidator.operationHandlers.basePath;
91
105
  if (handlersBasePath) {
92
- const { extractHandlerModules } = await Promise.resolve().then(() => _interopRequireWildcard(require("./openapi-parser-NFUD7ZGZ.cjs")));
93
- const { importAndRegisterHandlers } = await Promise.resolve().then(() => _interopRequireWildcard(require("./handler-importer-4BVBIZX3.cjs")));
94
- try {
95
- const modules = await extractHandlerModules(
96
- openApiValidator.apiSpec,
97
- handlersBasePath
98
- );
99
- const importedHandlers = await importAndRegisterHandlers(modules);
100
- if (openApiValidator.initializeHandlers) {
101
- await openApiValidator.initializeHandlers(importedHandlers);
106
+ const { extractHandlerModules } = await Promise.resolve().then(() => _interopRequireWildcard(require("./openapi-parser-PW5USNZ4.cjs")));
107
+ const { importAndRegisterHandlers } = await Promise.resolve().then(() => _interopRequireWildcard(require("./handler-importer-UFV7TKBN.cjs")));
108
+ const modules = await tracer.startActiveSpan(
109
+ "emmett.openapi.parse_spec",
110
+ async (span) => {
111
+ try {
112
+ const result = await extractHandlerModules(
113
+ openApiValidator.apiSpec,
114
+ handlersBasePath,
115
+ logger
116
+ );
117
+ span.setAttribute("emmett.handlers.count", result.length);
118
+ span.setStatus({ code: _api.SpanStatusCode.OK });
119
+ return result;
120
+ } catch (error) {
121
+ span.recordException(error);
122
+ span.setStatus({ code: _api.SpanStatusCode.ERROR });
123
+ throw error;
124
+ } finally {
125
+ span.end();
126
+ }
102
127
  }
103
- } catch (error) {
104
- console.error("Failed to auto-import handler modules:", error);
105
- throw error;
128
+ );
129
+ const importedHandlers = await tracer.startActiveSpan(
130
+ "emmett.http.import_handlers",
131
+ async (span) => {
132
+ try {
133
+ const result = await importAndRegisterHandlers(modules, logger);
134
+ span.setAttribute(
135
+ "emmett.handlers.count",
136
+ Object.keys(result).length
137
+ );
138
+ span.setStatus({ code: _api.SpanStatusCode.OK });
139
+ return result;
140
+ } catch (error) {
141
+ _chunkBY65ZALYcjs.safeLog.error(logger, "Failed to auto-import handler modules", error);
142
+ span.recordException(error);
143
+ span.setStatus({ code: _api.SpanStatusCode.ERROR });
144
+ throw error;
145
+ } finally {
146
+ span.end();
147
+ }
148
+ }
149
+ );
150
+ if (openApiValidator.initializeHandlers) {
151
+ await openApiValidator.initializeHandlers(importedHandlers);
106
152
  }
107
153
  }
108
154
  } else {
@@ -138,8 +184,9 @@ var getApplication = async (options) => {
138
184
  } else {
139
185
  app.use(middleware);
140
186
  }
141
- } catch (e2) {
142
- console.warn(
187
+ } catch (error) {
188
+ _chunkBY65ZALYcjs.safeLog.warn(
189
+ logger,
143
190
  "OpenAPI validator configuration provided but express-openapi-validator package is not installed. Install it with: npm install express-openapi-validator"
144
191
  );
145
192
  throw new Error(
@@ -154,14 +201,15 @@ var getApplication = async (options) => {
154
201
  app.use(router);
155
202
  }
156
203
  if (!disableProblemDetailsMiddleware)
157
- app.use(problemDetailsMiddleware(mapError));
204
+ app.use(problemDetailsMiddleware(mapError, logger));
205
+ _chunkBY65ZALYcjs.safeLog.info(logger, "Express application initialized");
158
206
  return app;
159
207
  };
160
208
  var startAPI = (app, options = { port: 3e3 }) => {
161
- const { port } = options;
209
+ const { port, logger } = options;
162
210
  const server = _http2.default.createServer(app);
163
211
  server.on("listening", () => {
164
- console.info("server up listening");
212
+ _chunkBY65ZALYcjs.safeLog.info(logger, "Server up listening", { port });
165
213
  });
166
214
  return server.listen(port);
167
215
  };
@@ -215,10 +263,34 @@ var getETagValueFromIfMatch = (request) => {
215
263
  };
216
264
 
217
265
  // src/handler.ts
266
+
267
+ var tracer2 = _api.trace.getTracer("@emmett-community/emmett-expressjs-with-openapi");
218
268
  var on = (handle) => async (request, response, _next) => {
219
269
  const setResponse = await Promise.resolve(handle(request));
220
270
  return setResponse(response);
221
271
  };
272
+ var tracedOn = (handle, options) => async (request, response, _next) => {
273
+ const spanName = _nullishCoalesce(_optionalChain([options, 'optionalAccess', _3 => _3.spanName]), () => ( "emmett.http.handle_request"));
274
+ return tracer2.startActiveSpan(spanName, async (span) => {
275
+ try {
276
+ span.setAttribute("http.method", request.method);
277
+ const route = (_nullishCoalesce(request.baseUrl, () => ( ""))) + (_nullishCoalesce(_optionalChain([request, 'access', _4 => _4.route, 'optionalAccess', _5 => _5.path]), () => ( request.path)));
278
+ span.setAttribute("http.route", route);
279
+ const setResponse = await Promise.resolve(handle(request));
280
+ setResponse(response);
281
+ span.setAttribute("http.status_code", response.statusCode);
282
+ span.setStatus({
283
+ code: response.statusCode >= 400 ? _api.SpanStatusCode.ERROR : _api.SpanStatusCode.OK
284
+ });
285
+ } catch (error) {
286
+ span.recordException(error);
287
+ span.setStatus({ code: _api.SpanStatusCode.ERROR });
288
+ throw error;
289
+ } finally {
290
+ span.end();
291
+ }
292
+ });
293
+ };
222
294
  var OK = (options) => (response) => {
223
295
  send(response, 200, options);
224
296
  };
@@ -292,7 +364,7 @@ var createFirebaseAuthSecurityHandlers = (options = {}) => {
292
364
  const isAuthenticated = await runMiddleware(middleware, req);
293
365
  if (!isAuthenticated) return false;
294
366
  if (!scopes.length) return true;
295
- const roles = _optionalChain([req, 'optionalAccess', _ => _.auth, 'optionalAccess', _2 => _2.token, 'optionalAccess', _3 => _3[roleClaim]]);
367
+ const roles = _optionalChain([req, 'optionalAccess', _6 => _6.auth, 'optionalAccess', _7 => _7.token, 'optionalAccess', _8 => _8[roleClaim]]);
296
368
  if (!Array.isArray(roles)) return false;
297
369
  return scopes.every((scope) => roles.includes(scope));
298
370
  }
@@ -303,24 +375,24 @@ var createFirebaseAuthSecurityHandlers = (options = {}) => {
303
375
  var createOpenApiValidatorOptions = (apiSpec, options) => {
304
376
  return {
305
377
  apiSpec,
306
- validateRequests: _nullishCoalesce(_optionalChain([options, 'optionalAccess', _4 => _4.validateRequests]), () => ( true)),
307
- validateResponses: _nullishCoalesce(_optionalChain([options, 'optionalAccess', _5 => _5.validateResponses]), () => ( false)),
308
- validateSecurity: _nullishCoalesce(_optionalChain([options, 'optionalAccess', _6 => _6.validateSecurity]), () => ( true)),
309
- validateFormats: _nullishCoalesce(_optionalChain([options, 'optionalAccess', _7 => _7.validateFormats]), () => ( true)),
310
- operationHandlers: _optionalChain([options, 'optionalAccess', _8 => _8.operationHandlers]),
311
- ignorePaths: _optionalChain([options, 'optionalAccess', _9 => _9.ignorePaths]),
312
- validateApiSpec: _nullishCoalesce(_optionalChain([options, 'optionalAccess', _10 => _10.validateApiSpec]), () => ( true)),
313
- $refParser: _optionalChain([options, 'optionalAccess', _11 => _11.$refParser]),
314
- serveSpec: _nullishCoalesce(_optionalChain([options, 'optionalAccess', _12 => _12.serveSpec]), () => ( false)),
315
- fileUploader: _optionalChain([options, 'optionalAccess', _13 => _13.fileUploader]),
316
- initializeHandlers: _optionalChain([options, 'optionalAccess', _14 => _14.initializeHandlers])
378
+ validateRequests: _nullishCoalesce(_optionalChain([options, 'optionalAccess', _9 => _9.validateRequests]), () => ( true)),
379
+ validateResponses: _nullishCoalesce(_optionalChain([options, 'optionalAccess', _10 => _10.validateResponses]), () => ( false)),
380
+ validateSecurity: _nullishCoalesce(_optionalChain([options, 'optionalAccess', _11 => _11.validateSecurity]), () => ( true)),
381
+ validateFormats: _nullishCoalesce(_optionalChain([options, 'optionalAccess', _12 => _12.validateFormats]), () => ( true)),
382
+ operationHandlers: _optionalChain([options, 'optionalAccess', _13 => _13.operationHandlers]),
383
+ ignorePaths: _optionalChain([options, 'optionalAccess', _14 => _14.ignorePaths]),
384
+ validateApiSpec: _nullishCoalesce(_optionalChain([options, 'optionalAccess', _15 => _15.validateApiSpec]), () => ( true)),
385
+ $refParser: _optionalChain([options, 'optionalAccess', _16 => _16.$refParser]),
386
+ serveSpec: _nullishCoalesce(_optionalChain([options, 'optionalAccess', _17 => _17.serveSpec]), () => ( false)),
387
+ fileUploader: _optionalChain([options, 'optionalAccess', _18 => _18.fileUploader]),
388
+ initializeHandlers: _optionalChain([options, 'optionalAccess', _19 => _19.initializeHandlers])
317
389
  };
318
390
  };
319
391
  var isOpenApiValidatorAvailable = async () => {
320
392
  try {
321
393
  await Promise.resolve().then(() => _interopRequireWildcard(require("express-openapi-validator")));
322
394
  return true;
323
- } catch (e3) {
395
+ } catch (e) {
324
396
  return false;
325
397
  }
326
398
  };
@@ -504,5 +576,7 @@ var ApiSpecification = {
504
576
 
505
577
 
506
578
 
507
- exports.Accepted = Accepted; exports.ApiE2ESpecification = ApiE2ESpecification; exports.ApiSpecification = ApiSpecification; exports.BadRequest = BadRequest; exports.Conflict = Conflict; exports.Created = Created; exports.DefaultHttpProblemResponseOptions = DefaultHttpProblemResponseOptions; exports.DefaultHttpResponseOptions = DefaultHttpResponseOptions; exports.ETagErrors = ETagErrors; exports.Forbidden = Forbidden; exports.HeaderNames = HeaderNames; exports.HttpProblem = HttpProblem; exports.HttpResponse = HttpResponse; exports.NoContent = NoContent; exports.NotFound = NotFound; exports.OK = OK; exports.PreconditionFailed = PreconditionFailed; exports.WeakETagRegex = WeakETagRegex; exports.createFirebaseAuthSecurityHandlers = createFirebaseAuthSecurityHandlers; exports.createOpenApiValidatorOptions = createOpenApiValidatorOptions; exports.existingStream = existingStream; exports.expect = expect; exports.expectError = expectError; exports.expectNewEvents = expectNewEvents; exports.expectResponse = expectResponse; exports.getApplication = getApplication; exports.getETagFromIfMatch = getETagFromIfMatch; exports.getETagFromIfNotMatch = getETagFromIfNotMatch; exports.getETagValueFromIfMatch = getETagValueFromIfMatch; exports.getWeakETagValue = getWeakETagValue; exports.isOpenApiValidatorAvailable = isOpenApiValidatorAvailable; exports.isWeakETag = isWeakETag; exports.on = on; exports.registerHandlerModule = _chunkMRSGTROWcjs.registerHandlerModule; exports.send = send; exports.sendAccepted = sendAccepted; exports.sendCreated = sendCreated; exports.sendProblem = sendProblem; exports.setETag = setETag; exports.startAPI = startAPI; exports.toWeakETag = toWeakETag;
579
+
580
+
581
+ exports.Accepted = Accepted; exports.ApiE2ESpecification = ApiE2ESpecification; exports.ApiSpecification = ApiSpecification; exports.BadRequest = BadRequest; exports.Conflict = Conflict; exports.Created = Created; exports.DefaultHttpProblemResponseOptions = DefaultHttpProblemResponseOptions; exports.DefaultHttpResponseOptions = DefaultHttpResponseOptions; exports.ETagErrors = ETagErrors; exports.Forbidden = Forbidden; exports.HeaderNames = HeaderNames; exports.HttpProblem = HttpProblem; exports.HttpResponse = HttpResponse; exports.NoContent = NoContent; exports.NotFound = NotFound; exports.OK = OK; exports.PreconditionFailed = PreconditionFailed; exports.WeakETagRegex = WeakETagRegex; exports.createFirebaseAuthSecurityHandlers = createFirebaseAuthSecurityHandlers; exports.createOpenApiValidatorOptions = createOpenApiValidatorOptions; exports.existingStream = existingStream; exports.expect = expect; exports.expectError = expectError; exports.expectNewEvents = expectNewEvents; exports.expectResponse = expectResponse; exports.getApplication = getApplication; exports.getETagFromIfMatch = getETagFromIfMatch; exports.getETagFromIfNotMatch = getETagFromIfNotMatch; exports.getETagValueFromIfMatch = getETagValueFromIfMatch; exports.getWeakETagValue = getWeakETagValue; exports.isOpenApiValidatorAvailable = isOpenApiValidatorAvailable; exports.isWeakETag = isWeakETag; exports.on = on; exports.registerHandlerModule = _chunkMRSGTROWcjs.registerHandlerModule; exports.safeLog = _chunkBY65ZALYcjs.safeLog; exports.send = send; exports.sendAccepted = sendAccepted; exports.sendCreated = sendCreated; exports.sendProblem = sendProblem; exports.setETag = setETag; exports.startAPI = startAPI; exports.toWeakETag = toWeakETag; exports.tracedOn = tracedOn;
508
582
  //# sourceMappingURL=index.cjs.map