@emmett-community/emmett-expressjs-with-openapi 0.4.0 → 0.5.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
@@ -182,6 +182,51 @@ startAPI(app, { port: 3000, logger });
182
182
 
183
183
  No logs are emitted when logger is not provided.
184
184
 
185
+ #### Logger Contract
186
+
187
+ This package defines the canonical Logger interface for the Emmett ecosystem. Implementations (Pino, Winston, etc.) MUST adapt to this contract.
188
+
189
+ ```typescript
190
+ interface Logger {
191
+ debug(context: Record<string, unknown>, message?: string): void;
192
+ info(context: Record<string, unknown>, message?: string): void;
193
+ warn(context: Record<string, unknown>, message?: string): void;
194
+ error(context: Record<string, unknown>, message?: string): void;
195
+ }
196
+ ```
197
+
198
+ **Semantic Rules:**
199
+
200
+ - `context` (first parameter): Structured data for the log entry
201
+ - `message` (second parameter): Human-readable message
202
+ - The order is never inverted
203
+
204
+ Pino implements this contract natively. For other loggers like Winston, you need to create an adapter.
205
+
206
+ > **Note**: The adapter example below is for demonstration purposes only. It is NOT part of the public API and NOT an official recommendation. Users must implement their own adapters for non-Pino loggers.
207
+
208
+ ```typescript
209
+ import winston from 'winston';
210
+ import type { Logger } from '@emmett-community/emmett-expressjs-with-openapi';
211
+
212
+ const winstonLogger = winston.createLogger({ /* config */ });
213
+
214
+ const logger: Logger = {
215
+ debug(context, message) {
216
+ winstonLogger.debug(message ?? '', context);
217
+ },
218
+ info(context, message) {
219
+ winstonLogger.info(message ?? '', context);
220
+ },
221
+ warn(context, message) {
222
+ winstonLogger.warn(message ?? '', context);
223
+ },
224
+ error(context, message) {
225
+ winstonLogger.error(message ?? '', context);
226
+ },
227
+ };
228
+ ```
229
+
185
230
  ### Tracing
186
231
 
187
232
  If your application initializes OpenTelemetry, this package will emit spans automatically. Tracing is passive - spans are no-ops when OpenTelemetry is not configured.
@@ -0,0 +1,45 @@
1
+ "use strict";Object.defineProperty(exports, "__esModule", {value: true});// src/observability.ts
2
+ var safeLog = {
3
+ debug: (logger, msg, data) => {
4
+ if (!logger) return;
5
+ logger.debug(normalizeContext(data), msg);
6
+ },
7
+ info: (logger, msg, data) => {
8
+ if (!logger) return;
9
+ logger.info(normalizeContext(data), msg);
10
+ },
11
+ warn: (logger, msg, data) => {
12
+ if (!logger) return;
13
+ logger.warn(normalizeContext(data), msg);
14
+ },
15
+ error: (logger, msg, error) => {
16
+ if (!logger) return;
17
+ logger.error(normalizeErrorContext(error), msg);
18
+ }
19
+ };
20
+ function normalizeContext(data) {
21
+ if (data === void 0 || data === null) {
22
+ return {};
23
+ }
24
+ if (typeof data === "object" && !Array.isArray(data)) {
25
+ return { ...data };
26
+ }
27
+ return { data };
28
+ }
29
+ function normalizeErrorContext(error) {
30
+ if (error === void 0 || error === null) {
31
+ return {};
32
+ }
33
+ if (error instanceof Error) {
34
+ return { err: error };
35
+ }
36
+ if (typeof error === "object" && !Array.isArray(error)) {
37
+ return { ...error };
38
+ }
39
+ return { err: error };
40
+ }
41
+
42
+
43
+
44
+ exports.safeLog = safeLog;
45
+ //# sourceMappingURL=chunk-FSPH5TRT.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["/home/runner/work/emmett-expressjs-with-openapi/emmett-expressjs-with-openapi/dist/chunk-FSPH5TRT.cjs","../src/observability.ts"],"names":[],"mappings":"AAAA;AC+FO,IAAM,QAAA,EAAU;AAAA,EACrB,KAAA,EAAO,CAAC,MAAA,EAA4B,GAAA,EAAa,IAAA,EAAA,GAAmB;AAClE,IAAA,GAAA,CAAI,CAAC,MAAA,EAAQ,MAAA;AACb,IAAA,MAAA,CAAO,KAAA,CAAM,gBAAA,CAAiB,IAAI,CAAA,EAAG,GAAG,CAAA;AAAA,EAC1C,CAAA;AAAA,EACA,IAAA,EAAM,CAAC,MAAA,EAA4B,GAAA,EAAa,IAAA,EAAA,GAAmB;AACjE,IAAA,GAAA,CAAI,CAAC,MAAA,EAAQ,MAAA;AACb,IAAA,MAAA,CAAO,IAAA,CAAK,gBAAA,CAAiB,IAAI,CAAA,EAAG,GAAG,CAAA;AAAA,EACzC,CAAA;AAAA,EACA,IAAA,EAAM,CAAC,MAAA,EAA4B,GAAA,EAAa,IAAA,EAAA,GAAmB;AACjE,IAAA,GAAA,CAAI,CAAC,MAAA,EAAQ,MAAA;AACb,IAAA,MAAA,CAAO,IAAA,CAAK,gBAAA,CAAiB,IAAI,CAAA,EAAG,GAAG,CAAA;AAAA,EACzC,CAAA;AAAA,EACA,KAAA,EAAO,CAAC,MAAA,EAA4B,GAAA,EAAa,KAAA,EAAA,GAAoB;AACnE,IAAA,GAAA,CAAI,CAAC,MAAA,EAAQ,MAAA;AACb,IAAA,MAAA,CAAO,KAAA,CAAM,qBAAA,CAAsB,KAAK,CAAA,EAAG,GAAG,CAAA;AAAA,EAChD;AACF,CAAA;AAMA,SAAS,gBAAA,CAAiB,IAAA,EAAwC;AAChE,EAAA,GAAA,CAAI,KAAA,IAAS,KAAA,EAAA,GAAa,KAAA,IAAS,IAAA,EAAM;AACvC,IAAA,OAAO,CAAC,CAAA;AAAA,EACV;AACA,EAAA,GAAA,CAAI,OAAO,KAAA,IAAS,SAAA,GAAY,CAAC,KAAA,CAAM,OAAA,CAAQ,IAAI,CAAA,EAAG;AACpD,IAAA,OAAO,EAAE,GAAI,KAAiC,CAAA;AAAA,EAChD;AACA,EAAA,OAAO,EAAE,KAAK,CAAA;AAChB;AAOA,SAAS,qBAAA,CAAsB,KAAA,EAAyC;AACtE,EAAA,GAAA,CAAI,MAAA,IAAU,KAAA,EAAA,GAAa,MAAA,IAAU,IAAA,EAAM;AACzC,IAAA,OAAO,CAAC,CAAA;AAAA,EACV;AACA,EAAA,GAAA,CAAI,MAAA,WAAiB,KAAA,EAAO;AAC1B,IAAA,OAAO,EAAE,GAAA,EAAK,MAAM,CAAA;AAAA,EACtB;AACA,EAAA,GAAA,CAAI,OAAO,MAAA,IAAU,SAAA,GAAY,CAAC,KAAA,CAAM,OAAA,CAAQ,KAAK,CAAA,EAAG;AACtD,IAAA,OAAO,EAAE,GAAI,MAAkC,CAAA;AAAA,EACjD;AACA,EAAA,OAAO,EAAE,GAAA,EAAK,MAAM,CAAA;AACtB;ADxGA;AACA;AACE;AACF,0BAAC","file":"/home/runner/work/emmett-expressjs-with-openapi/emmett-expressjs-with-openapi/dist/chunk-FSPH5TRT.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 * Canonical Logger contract for the Emmett ecosystem.\n *\n * This package defines the canonical Logger interface.\n * Implementations (Pino, Winston, etc.) MUST adapt to this contract.\n * This contract MUST NOT adapt to any specific implementation.\n *\n * Semantic Rules:\n * - context (first parameter): ALWAYS structured data as Record<string, unknown>\n * - message (second parameter): ALWAYS the human-readable log message\n * - The order is NEVER inverted\n * - The (message, data) form is NOT valid for this contract\n *\n * @example\n * ```typescript\n * // Pino - native compatibility\n * import pino from 'pino';\n * const logger = pino();\n * // logger.info({ orderId }, 'Order created') matches our contract\n * ```\n */\nexport interface Logger {\n /**\n * Log debug-level message with structured context.\n * @param context - Structured data to include in the log entry\n * @param message - Optional human-readable message\n */\n debug(context: Record<string, unknown>, message?: string): void;\n\n /**\n * Log info-level message with structured context.\n * @param context - Structured data to include in the log entry\n * @param message - Optional human-readable message\n */\n info(context: Record<string, unknown>, message?: string): void;\n\n /**\n * Log warn-level message with structured context.\n * @param context - Structured data to include in the log entry\n * @param message - Optional human-readable message\n */\n warn(context: Record<string, unknown>, message?: string): void;\n\n /**\n * Log error-level message with structured context.\n * @param context - Structured data to include in the log entry (may include 'err' key)\n * @param message - Optional human-readable message\n */\n error(context: Record<string, unknown>, message?: string): 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.\n *\n * IMPORTANT: safeLog is the ONLY translation point between internal usage\n * and the Logger contract.\n *\n * Internal Usage Pattern:\n * safeLog.info(logger, 'Order created', { orderId: 123 })\n *\n * Translation to Logger Contract:\n * logger.info({ orderId: 123 }, 'Order created')\n *\n * This allows ergonomic internal usage while ensuring all injected loggers\n * receive calls in the canonical (context, message) format.\n */\nexport const safeLog = {\n debug: (logger: Logger | undefined, msg: string, data?: unknown) => {\n if (!logger) return;\n logger.debug(normalizeContext(data), msg);\n },\n info: (logger: Logger | undefined, msg: string, data?: unknown) => {\n if (!logger) return;\n logger.info(normalizeContext(data), msg);\n },\n warn: (logger: Logger | undefined, msg: string, data?: unknown) => {\n if (!logger) return;\n logger.warn(normalizeContext(data), msg);\n },\n error: (logger: Logger | undefined, msg: string, error?: unknown) => {\n if (!logger) return;\n logger.error(normalizeErrorContext(error), msg);\n },\n};\n\n/**\n * Normalize data to context object for regular log methods.\n * Creates a shallow copy to avoid accidental mutation of the original object.\n */\nfunction normalizeContext(data: unknown): Record<string, unknown> {\n if (data === undefined || data === null) {\n return {};\n }\n if (typeof data === 'object' && !Array.isArray(data)) {\n return { ...(data as Record<string, unknown>) };\n }\n return { data };\n}\n\n/**\n * Normalize error to context object for error log method.\n * Uses 'err' key for Pino compatibility (Pino serializes Error objects on 'err' key).\n * Creates a shallow copy to avoid accidental mutation of the original object.\n */\nfunction normalizeErrorContext(error: unknown): Record<string, unknown> {\n if (error === undefined || error === null) {\n return {};\n }\n if (error instanceof Error) {\n return { err: error };\n }\n if (typeof error === 'object' && !Array.isArray(error)) {\n return { ...(error as Record<string, unknown>) };\n }\n return { err: error };\n}\n"]}
@@ -0,0 +1,45 @@
1
+ // src/observability.ts
2
+ var safeLog = {
3
+ debug: (logger, msg, data) => {
4
+ if (!logger) return;
5
+ logger.debug(normalizeContext(data), msg);
6
+ },
7
+ info: (logger, msg, data) => {
8
+ if (!logger) return;
9
+ logger.info(normalizeContext(data), msg);
10
+ },
11
+ warn: (logger, msg, data) => {
12
+ if (!logger) return;
13
+ logger.warn(normalizeContext(data), msg);
14
+ },
15
+ error: (logger, msg, error) => {
16
+ if (!logger) return;
17
+ logger.error(normalizeErrorContext(error), msg);
18
+ }
19
+ };
20
+ function normalizeContext(data) {
21
+ if (data === void 0 || data === null) {
22
+ return {};
23
+ }
24
+ if (typeof data === "object" && !Array.isArray(data)) {
25
+ return { ...data };
26
+ }
27
+ return { data };
28
+ }
29
+ function normalizeErrorContext(error) {
30
+ if (error === void 0 || error === null) {
31
+ return {};
32
+ }
33
+ if (error instanceof Error) {
34
+ return { err: error };
35
+ }
36
+ if (typeof error === "object" && !Array.isArray(error)) {
37
+ return { ...error };
38
+ }
39
+ return { err: error };
40
+ }
41
+
42
+ export {
43
+ safeLog
44
+ };
45
+ //# sourceMappingURL=chunk-PZBLPYJH.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 * Canonical Logger contract for the Emmett ecosystem.\n *\n * This package defines the canonical Logger interface.\n * Implementations (Pino, Winston, etc.) MUST adapt to this contract.\n * This contract MUST NOT adapt to any specific implementation.\n *\n * Semantic Rules:\n * - context (first parameter): ALWAYS structured data as Record<string, unknown>\n * - message (second parameter): ALWAYS the human-readable log message\n * - The order is NEVER inverted\n * - The (message, data) form is NOT valid for this contract\n *\n * @example\n * ```typescript\n * // Pino - native compatibility\n * import pino from 'pino';\n * const logger = pino();\n * // logger.info({ orderId }, 'Order created') matches our contract\n * ```\n */\nexport interface Logger {\n /**\n * Log debug-level message with structured context.\n * @param context - Structured data to include in the log entry\n * @param message - Optional human-readable message\n */\n debug(context: Record<string, unknown>, message?: string): void;\n\n /**\n * Log info-level message with structured context.\n * @param context - Structured data to include in the log entry\n * @param message - Optional human-readable message\n */\n info(context: Record<string, unknown>, message?: string): void;\n\n /**\n * Log warn-level message with structured context.\n * @param context - Structured data to include in the log entry\n * @param message - Optional human-readable message\n */\n warn(context: Record<string, unknown>, message?: string): void;\n\n /**\n * Log error-level message with structured context.\n * @param context - Structured data to include in the log entry (may include 'err' key)\n * @param message - Optional human-readable message\n */\n error(context: Record<string, unknown>, message?: string): 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.\n *\n * IMPORTANT: safeLog is the ONLY translation point between internal usage\n * and the Logger contract.\n *\n * Internal Usage Pattern:\n * safeLog.info(logger, 'Order created', { orderId: 123 })\n *\n * Translation to Logger Contract:\n * logger.info({ orderId: 123 }, 'Order created')\n *\n * This allows ergonomic internal usage while ensuring all injected loggers\n * receive calls in the canonical (context, message) format.\n */\nexport const safeLog = {\n debug: (logger: Logger | undefined, msg: string, data?: unknown) => {\n if (!logger) return;\n logger.debug(normalizeContext(data), msg);\n },\n info: (logger: Logger | undefined, msg: string, data?: unknown) => {\n if (!logger) return;\n logger.info(normalizeContext(data), msg);\n },\n warn: (logger: Logger | undefined, msg: string, data?: unknown) => {\n if (!logger) return;\n logger.warn(normalizeContext(data), msg);\n },\n error: (logger: Logger | undefined, msg: string, error?: unknown) => {\n if (!logger) return;\n logger.error(normalizeErrorContext(error), msg);\n },\n};\n\n/**\n * Normalize data to context object for regular log methods.\n * Creates a shallow copy to avoid accidental mutation of the original object.\n */\nfunction normalizeContext(data: unknown): Record<string, unknown> {\n if (data === undefined || data === null) {\n return {};\n }\n if (typeof data === 'object' && !Array.isArray(data)) {\n return { ...(data as Record<string, unknown>) };\n }\n return { data };\n}\n\n/**\n * Normalize error to context object for error log method.\n * Uses 'err' key for Pino compatibility (Pino serializes Error objects on 'err' key).\n * Creates a shallow copy to avoid accidental mutation of the original object.\n */\nfunction normalizeErrorContext(error: unknown): Record<string, unknown> {\n if (error === undefined || error === null) {\n return {};\n }\n if (error instanceof Error) {\n return { err: error };\n }\n if (typeof error === 'object' && !Array.isArray(error)) {\n return { ...(error as Record<string, unknown>) };\n }\n return { err: error };\n}\n"],"mappings":";AA+FO,IAAM,UAAU;AAAA,EACrB,OAAO,CAAC,QAA4B,KAAa,SAAmB;AAClE,QAAI,CAAC,OAAQ;AACb,WAAO,MAAM,iBAAiB,IAAI,GAAG,GAAG;AAAA,EAC1C;AAAA,EACA,MAAM,CAAC,QAA4B,KAAa,SAAmB;AACjE,QAAI,CAAC,OAAQ;AACb,WAAO,KAAK,iBAAiB,IAAI,GAAG,GAAG;AAAA,EACzC;AAAA,EACA,MAAM,CAAC,QAA4B,KAAa,SAAmB;AACjE,QAAI,CAAC,OAAQ;AACb,WAAO,KAAK,iBAAiB,IAAI,GAAG,GAAG;AAAA,EACzC;AAAA,EACA,OAAO,CAAC,QAA4B,KAAa,UAAoB;AACnE,QAAI,CAAC,OAAQ;AACb,WAAO,MAAM,sBAAsB,KAAK,GAAG,GAAG;AAAA,EAChD;AACF;AAMA,SAAS,iBAAiB,MAAwC;AAChE,MAAI,SAAS,UAAa,SAAS,MAAM;AACvC,WAAO,CAAC;AAAA,EACV;AACA,MAAI,OAAO,SAAS,YAAY,CAAC,MAAM,QAAQ,IAAI,GAAG;AACpD,WAAO,EAAE,GAAI,KAAiC;AAAA,EAChD;AACA,SAAO,EAAE,KAAK;AAChB;AAOA,SAAS,sBAAsB,OAAyC;AACtE,MAAI,UAAU,UAAa,UAAU,MAAM;AACzC,WAAO,CAAC;AAAA,EACV;AACA,MAAI,iBAAiB,OAAO;AAC1B,WAAO,EAAE,KAAK,MAAM;AAAA,EACtB;AACA,MAAI,OAAO,UAAU,YAAY,CAAC,MAAM,QAAQ,KAAK,GAAG;AACtD,WAAO,EAAE,GAAI,MAAkC;AAAA,EACjD;AACA,SAAO,EAAE,KAAK,MAAM;AACtB;","names":[]}
@@ -3,20 +3,20 @@
3
3
  var _chunkMRSGTROWcjs = require('./chunk-MRSGTROW.cjs');
4
4
 
5
5
 
6
- var _chunkBY65ZALYcjs = require('./chunk-BY65ZALY.cjs');
6
+ var _chunkFSPH5TRTcjs = require('./chunk-FSPH5TRT.cjs');
7
7
  require('./chunk-GS7T56RP.cjs');
8
8
 
9
9
  // src/internal/handler-importer.ts
10
10
  var _url = require('url');
11
11
  async function importAndRegisterHandlers(modules, logger) {
12
- _chunkBY65ZALYcjs.safeLog.debug(logger, "Importing handler modules", {
12
+ _chunkFSPH5TRTcjs.safeLog.debug(logger, "Importing handler modules", {
13
13
  count: modules.length,
14
14
  modules: modules.map((m) => m.moduleName)
15
15
  });
16
16
  const importedHandlers = {};
17
17
  for (const module of modules) {
18
18
  try {
19
- _chunkBY65ZALYcjs.safeLog.debug(logger, "Importing handler module", {
19
+ _chunkFSPH5TRTcjs.safeLog.debug(logger, "Importing handler module", {
20
20
  moduleName: module.moduleName,
21
21
  absolutePath: module.absolutePath
22
22
  });
@@ -24,11 +24,11 @@ async function importAndRegisterHandlers(modules, logger) {
24
24
  const importedModule = await Promise.resolve().then(() => _interopRequireWildcard(require(fileUrl)));
25
25
  _chunkMRSGTROWcjs.registerHandlerModule.call(void 0, module.absolutePath, importedModule);
26
26
  importedHandlers[module.moduleName] = importedModule;
27
- _chunkBY65ZALYcjs.safeLog.debug(logger, "Handler module imported successfully", {
27
+ _chunkFSPH5TRTcjs.safeLog.debug(logger, "Handler module imported successfully", {
28
28
  moduleName: module.moduleName
29
29
  });
30
30
  } catch (error) {
31
- _chunkBY65ZALYcjs.safeLog.error(
31
+ _chunkFSPH5TRTcjs.safeLog.error(
32
32
  logger,
33
33
  `Failed to import handler module "${module.moduleName}"`,
34
34
  error
@@ -43,4 +43,4 @@ async function importAndRegisterHandlers(modules, logger) {
43
43
 
44
44
 
45
45
  exports.importAndRegisterHandlers = importAndRegisterHandlers;
46
- //# sourceMappingURL=handler-importer-UFV7TKBN.cjs.map
46
+ //# sourceMappingURL=handler-importer-BRJVJNST.cjs.map
@@ -1 +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"]}
1
+ {"version":3,"sources":["/home/runner/work/emmett-expressjs-with-openapi/emmett-expressjs-with-openapi/dist/handler-importer-BRJVJNST.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-BRJVJNST.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"]}
@@ -3,7 +3,7 @@ import {
3
3
  } from "./chunk-5TC7YUZR.js";
4
4
  import {
5
5
  safeLog
6
- } from "./chunk-I46UH36B.js";
6
+ } from "./chunk-PZBLPYJH.js";
7
7
 
8
8
  // src/internal/handler-importer.ts
9
9
  import { pathToFileURL } from "url";
@@ -42,4 +42,4 @@ async function importAndRegisterHandlers(modules, logger) {
42
42
  export {
43
43
  importAndRegisterHandlers
44
44
  };
45
- //# sourceMappingURL=handler-importer-6A237UML.js.map
45
+ //# sourceMappingURL=handler-importer-V4EJZ2DG.js.map
package/dist/index.cjs CHANGED
@@ -3,7 +3,7 @@
3
3
  var _chunkMRSGTROWcjs = require('./chunk-MRSGTROW.cjs');
4
4
 
5
5
 
6
- var _chunkBY65ZALYcjs = require('./chunk-BY65ZALY.cjs');
6
+ var _chunkFSPH5TRTcjs = require('./chunk-FSPH5TRT.cjs');
7
7
 
8
8
 
9
9
  var _chunkGS7T56RPcjs = require('./chunk-GS7T56RP.cjs');
@@ -23,7 +23,7 @@ var _module = require('module');
23
23
  // src/middlewares/problemDetailsMiddleware.ts
24
24
  var _httpproblemdetails = require('http-problem-details');
25
25
  var problemDetailsMiddleware = (mapError, logger) => (error, request, response, _next) => {
26
- _chunkBY65ZALYcjs.safeLog.error(logger, "Request error", error);
26
+ _chunkFSPH5TRTcjs.safeLog.error(logger, "Request error", error);
27
27
  let problemDetails;
28
28
  if (mapError) problemDetails = mapError(error, request);
29
29
  problemDetails = _nullishCoalesce(problemDetails, () => ( defaultErrorToProblemDetailsMapping(error)));
@@ -62,7 +62,7 @@ var getApplication = async (options) => {
62
62
  observability
63
63
  } = options;
64
64
  const logger = _optionalChain([observability, 'optionalAccess', _ => _.logger]);
65
- _chunkBY65ZALYcjs.safeLog.debug(logger, "Initializing Express application", {
65
+ _chunkFSPH5TRTcjs.safeLog.debug(logger, "Initializing Express application", {
66
66
  hasApis: !!_optionalChain([apis, 'optionalAccess', _2 => _2.length]),
67
67
  hasOpenApiValidator: !!openApiValidator,
68
68
  hasPinoHttp: !!pinoHttp
@@ -81,7 +81,7 @@ var getApplication = async (options) => {
81
81
  const middleware = provider(options2);
82
82
  app.use(middleware);
83
83
  } catch (error) {
84
- _chunkBY65ZALYcjs.safeLog.warn(
84
+ _chunkFSPH5TRTcjs.safeLog.warn(
85
85
  logger,
86
86
  "Pino HTTP configuration provided but pino-http package is not installed. Install it with: npm install pino-http"
87
87
  );
@@ -103,8 +103,8 @@ var getApplication = async (options) => {
103
103
  activateESMResolver();
104
104
  const handlersBasePath = typeof openApiValidator.operationHandlers === "string" ? openApiValidator.operationHandlers : openApiValidator.operationHandlers.basePath;
105
105
  if (handlersBasePath) {
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")));
106
+ const { extractHandlerModules } = await Promise.resolve().then(() => _interopRequireWildcard(require("./openapi-parser-HMWO3IPV.cjs")));
107
+ const { importAndRegisterHandlers } = await Promise.resolve().then(() => _interopRequireWildcard(require("./handler-importer-BRJVJNST.cjs")));
108
108
  const modules = await tracer.startActiveSpan(
109
109
  "emmett.openapi.parse_spec",
110
110
  async (span) => {
@@ -138,7 +138,7 @@ var getApplication = async (options) => {
138
138
  span.setStatus({ code: _api.SpanStatusCode.OK });
139
139
  return result;
140
140
  } catch (error) {
141
- _chunkBY65ZALYcjs.safeLog.error(logger, "Failed to auto-import handler modules", error);
141
+ _chunkFSPH5TRTcjs.safeLog.error(logger, "Failed to auto-import handler modules", error);
142
142
  span.recordException(error);
143
143
  span.setStatus({ code: _api.SpanStatusCode.ERROR });
144
144
  throw error;
@@ -185,7 +185,7 @@ var getApplication = async (options) => {
185
185
  app.use(middleware);
186
186
  }
187
187
  } catch (error) {
188
- _chunkBY65ZALYcjs.safeLog.warn(
188
+ _chunkFSPH5TRTcjs.safeLog.warn(
189
189
  logger,
190
190
  "OpenAPI validator configuration provided but express-openapi-validator package is not installed. Install it with: npm install express-openapi-validator"
191
191
  );
@@ -202,14 +202,14 @@ var getApplication = async (options) => {
202
202
  }
203
203
  if (!disableProblemDetailsMiddleware)
204
204
  app.use(problemDetailsMiddleware(mapError, logger));
205
- _chunkBY65ZALYcjs.safeLog.info(logger, "Express application initialized");
205
+ _chunkFSPH5TRTcjs.safeLog.info(logger, "Express application initialized");
206
206
  return app;
207
207
  };
208
208
  var startAPI = (app, options = { port: 3e3 }) => {
209
209
  const { port, logger } = options;
210
210
  const server = _http2.default.createServer(app);
211
211
  server.on("listening", () => {
212
- _chunkBY65ZALYcjs.safeLog.info(logger, "Server up listening", { port });
212
+ _chunkFSPH5TRTcjs.safeLog.info(logger, "Server up listening", { port });
213
213
  });
214
214
  return server.listen(port);
215
215
  };
@@ -578,5 +578,5 @@ var ApiSpecification = {
578
578
 
579
579
 
580
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;
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 = _chunkFSPH5TRTcjs.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;
582
582
  //# sourceMappingURL=index.cjs.map
package/dist/index.d.cts CHANGED
@@ -13,14 +13,51 @@ import TestAgent from 'supertest/lib/agent';
13
13
  * following a "silent by default" philosophy.
14
14
  */
15
15
  /**
16
- * Minimal logger interface compatible with Pino, Winston, console, and similar loggers.
17
- * All methods are optional to support partial implementations.
16
+ * Canonical Logger contract for the Emmett ecosystem.
17
+ *
18
+ * This package defines the canonical Logger interface.
19
+ * Implementations (Pino, Winston, etc.) MUST adapt to this contract.
20
+ * This contract MUST NOT adapt to any specific implementation.
21
+ *
22
+ * Semantic Rules:
23
+ * - context (first parameter): ALWAYS structured data as Record<string, unknown>
24
+ * - message (second parameter): ALWAYS the human-readable log message
25
+ * - The order is NEVER inverted
26
+ * - The (message, data) form is NOT valid for this contract
27
+ *
28
+ * @example
29
+ * ```typescript
30
+ * // Pino - native compatibility
31
+ * import pino from 'pino';
32
+ * const logger = pino();
33
+ * // logger.info({ orderId }, 'Order created') matches our contract
34
+ * ```
18
35
  */
19
36
  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;
37
+ /**
38
+ * Log debug-level message with structured context.
39
+ * @param context - Structured data to include in the log entry
40
+ * @param message - Optional human-readable message
41
+ */
42
+ debug(context: Record<string, unknown>, message?: string): void;
43
+ /**
44
+ * Log info-level message with structured context.
45
+ * @param context - Structured data to include in the log entry
46
+ * @param message - Optional human-readable message
47
+ */
48
+ info(context: Record<string, unknown>, message?: string): void;
49
+ /**
50
+ * Log warn-level message with structured context.
51
+ * @param context - Structured data to include in the log entry
52
+ * @param message - Optional human-readable message
53
+ */
54
+ warn(context: Record<string, unknown>, message?: string): void;
55
+ /**
56
+ * Log error-level message with structured context.
57
+ * @param context - Structured data to include in the log entry (may include 'err' key)
58
+ * @param message - Optional human-readable message
59
+ */
60
+ error(context: Record<string, unknown>, message?: string): void;
24
61
  }
25
62
  /**
26
63
  * Observability configuration options.
@@ -44,14 +81,25 @@ type ObservabilityOptions = {
44
81
  logger?: Logger;
45
82
  };
46
83
  /**
47
- * Internal helper to safely call logger methods, handling partial implementations.
48
- * Always use: safeLog.error(logger, msg, error) - never { error }
84
+ * Internal helper to safely call logger methods.
85
+ *
86
+ * IMPORTANT: safeLog is the ONLY translation point between internal usage
87
+ * and the Logger contract.
88
+ *
89
+ * Internal Usage Pattern:
90
+ * safeLog.info(logger, 'Order created', { orderId: 123 })
91
+ *
92
+ * Translation to Logger Contract:
93
+ * logger.info({ orderId: 123 }, 'Order created')
94
+ *
95
+ * This allows ergonomic internal usage while ensuring all injected loggers
96
+ * receive calls in the canonical (context, message) format.
49
97
  */
50
98
  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;
99
+ debug: (logger: Logger | undefined, msg: string, data?: unknown) => void;
100
+ info: (logger: Logger | undefined, msg: string, data?: unknown) => void;
101
+ warn: (logger: Logger | undefined, msg: string, data?: unknown) => void;
102
+ error: (logger: Logger | undefined, msg: string, error?: unknown) => void;
55
103
  };
56
104
 
57
105
  type AuthClient = {
package/dist/index.d.ts CHANGED
@@ -13,14 +13,51 @@ import TestAgent from 'supertest/lib/agent';
13
13
  * following a "silent by default" philosophy.
14
14
  */
15
15
  /**
16
- * Minimal logger interface compatible with Pino, Winston, console, and similar loggers.
17
- * All methods are optional to support partial implementations.
16
+ * Canonical Logger contract for the Emmett ecosystem.
17
+ *
18
+ * This package defines the canonical Logger interface.
19
+ * Implementations (Pino, Winston, etc.) MUST adapt to this contract.
20
+ * This contract MUST NOT adapt to any specific implementation.
21
+ *
22
+ * Semantic Rules:
23
+ * - context (first parameter): ALWAYS structured data as Record<string, unknown>
24
+ * - message (second parameter): ALWAYS the human-readable log message
25
+ * - The order is NEVER inverted
26
+ * - The (message, data) form is NOT valid for this contract
27
+ *
28
+ * @example
29
+ * ```typescript
30
+ * // Pino - native compatibility
31
+ * import pino from 'pino';
32
+ * const logger = pino();
33
+ * // logger.info({ orderId }, 'Order created') matches our contract
34
+ * ```
18
35
  */
19
36
  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;
37
+ /**
38
+ * Log debug-level message with structured context.
39
+ * @param context - Structured data to include in the log entry
40
+ * @param message - Optional human-readable message
41
+ */
42
+ debug(context: Record<string, unknown>, message?: string): void;
43
+ /**
44
+ * Log info-level message with structured context.
45
+ * @param context - Structured data to include in the log entry
46
+ * @param message - Optional human-readable message
47
+ */
48
+ info(context: Record<string, unknown>, message?: string): void;
49
+ /**
50
+ * Log warn-level message with structured context.
51
+ * @param context - Structured data to include in the log entry
52
+ * @param message - Optional human-readable message
53
+ */
54
+ warn(context: Record<string, unknown>, message?: string): void;
55
+ /**
56
+ * Log error-level message with structured context.
57
+ * @param context - Structured data to include in the log entry (may include 'err' key)
58
+ * @param message - Optional human-readable message
59
+ */
60
+ error(context: Record<string, unknown>, message?: string): void;
24
61
  }
25
62
  /**
26
63
  * Observability configuration options.
@@ -44,14 +81,25 @@ type ObservabilityOptions = {
44
81
  logger?: Logger;
45
82
  };
46
83
  /**
47
- * Internal helper to safely call logger methods, handling partial implementations.
48
- * Always use: safeLog.error(logger, msg, error) - never { error }
84
+ * Internal helper to safely call logger methods.
85
+ *
86
+ * IMPORTANT: safeLog is the ONLY translation point between internal usage
87
+ * and the Logger contract.
88
+ *
89
+ * Internal Usage Pattern:
90
+ * safeLog.info(logger, 'Order created', { orderId: 123 })
91
+ *
92
+ * Translation to Logger Contract:
93
+ * logger.info({ orderId: 123 }, 'Order created')
94
+ *
95
+ * This allows ergonomic internal usage while ensuring all injected loggers
96
+ * receive calls in the canonical (context, message) format.
49
97
  */
50
98
  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;
99
+ debug: (logger: Logger | undefined, msg: string, data?: unknown) => void;
100
+ info: (logger: Logger | undefined, msg: string, data?: unknown) => void;
101
+ warn: (logger: Logger | undefined, msg: string, data?: unknown) => void;
102
+ error: (logger: Logger | undefined, msg: string, error?: unknown) => void;
55
103
  };
56
104
 
57
105
  type AuthClient = {
package/dist/index.js CHANGED
@@ -3,7 +3,7 @@ import {
3
3
  } from "./chunk-5TC7YUZR.js";
4
4
  import {
5
5
  safeLog
6
- } from "./chunk-I46UH36B.js";
6
+ } from "./chunk-PZBLPYJH.js";
7
7
 
8
8
  // src/index.ts
9
9
  import "express-async-errors";
@@ -100,8 +100,8 @@ var getApplication = async (options) => {
100
100
  activateESMResolver();
101
101
  const handlersBasePath = typeof openApiValidator.operationHandlers === "string" ? openApiValidator.operationHandlers : openApiValidator.operationHandlers.basePath;
102
102
  if (handlersBasePath) {
103
- const { extractHandlerModules } = await import("./openapi-parser-QYXNHNSZ.js");
104
- const { importAndRegisterHandlers } = await import("./handler-importer-6A237UML.js");
103
+ const { extractHandlerModules } = await import("./openapi-parser-SFYDJEIN.js");
104
+ const { importAndRegisterHandlers } = await import("./handler-importer-V4EJZ2DG.js");
105
105
  const modules = await tracer.startActiveSpan(
106
106
  "emmett.openapi.parse_spec",
107
107
  async (span) => {
@@ -1,13 +1,13 @@
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; } } function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
2
2
 
3
- var _chunkBY65ZALYcjs = require('./chunk-BY65ZALY.cjs');
3
+ var _chunkFSPH5TRTcjs = require('./chunk-FSPH5TRT.cjs');
4
4
  require('./chunk-GS7T56RP.cjs');
5
5
 
6
6
  // src/internal/openapi-parser.ts
7
7
  var _promises = require('fs/promises');
8
8
  var _path = require('path'); var _path2 = _interopRequireDefault(_path);
9
9
  async function extractHandlerModules(apiSpec, handlersBasePath, logger) {
10
- _chunkBY65ZALYcjs.safeLog.debug(logger, "Extracting handler modules from OpenAPI spec", {
10
+ _chunkFSPH5TRTcjs.safeLog.debug(logger, "Extracting handler modules from OpenAPI spec", {
11
11
  apiSpec: typeof apiSpec === "string" ? apiSpec : "<object>",
12
12
  handlersBasePath
13
13
  });
@@ -43,14 +43,14 @@ async function extractHandlerModules(apiSpec, handlersBasePath, logger) {
43
43
  }
44
44
  }
45
45
  const modules = Array.from(handlersMap.values());
46
- _chunkBY65ZALYcjs.safeLog.debug(logger, "Extracted handler modules", {
46
+ _chunkFSPH5TRTcjs.safeLog.debug(logger, "Extracted handler modules", {
47
47
  count: modules.length,
48
48
  modules: modules.map((m) => m.moduleName)
49
49
  });
50
50
  return modules;
51
51
  }
52
52
  async function loadOpenApiSpec(filePath, logger) {
53
- _chunkBY65ZALYcjs.safeLog.debug(logger, "Loading OpenAPI spec file", { filePath });
53
+ _chunkFSPH5TRTcjs.safeLog.debug(logger, "Loading OpenAPI spec file", { filePath });
54
54
  try {
55
55
  const content = await _promises.readFile.call(void 0, filePath, "utf-8");
56
56
  const ext = _path2.default.extname(filePath).toLowerCase();
@@ -65,7 +65,7 @@ async function loadOpenApiSpec(filePath, logger) {
65
65
  `Unsupported OpenAPI file format: ${ext}. Use .json, .yaml, or .yml`
66
66
  );
67
67
  }
68
- _chunkBY65ZALYcjs.safeLog.debug(logger, "OpenAPI spec loaded successfully", {
68
+ _chunkFSPH5TRTcjs.safeLog.debug(logger, "OpenAPI spec loaded successfully", {
69
69
  filePath,
70
70
  format: ext
71
71
  });
@@ -91,4 +91,4 @@ function resolveHandlerPath(basePath, relativePath) {
91
91
 
92
92
 
93
93
  exports.extractHandlerModules = extractHandlerModules;
94
- //# sourceMappingURL=openapi-parser-PW5USNZ4.cjs.map
94
+ //# sourceMappingURL=openapi-parser-HMWO3IPV.cjs.map
@@ -1 +1 @@
1
- {"version":3,"sources":["/home/runner/work/emmett-expressjs-with-openapi/emmett-expressjs-with-openapi/dist/openapi-parser-PW5USNZ4.cjs","../src/internal/openapi-parser.ts"],"names":[],"mappings":"AAAA;AACE;AACF,wDAA6B;AAC7B,gCAA6B;AAC7B;AACA;ACKA,uCAAyB;AACzB,wEAAiB;AAqBjB,MAAA,SAAsB,qBAAA,CACpB,OAAA,EACA,gBAAA,EACA,MAAA,EAC8B;AAC9B,EAAA,yBAAA,CAAQ,KAAA,CAAM,MAAA,EAAQ,8CAAA,EAAgD;AAAA,IACpE,OAAA,EAAS,OAAO,QAAA,IAAY,SAAA,EAAW,QAAA,EAAU,UAAA;AAAA,IACjD;AAAA,EACF,CAAC,CAAA;AAGD,EAAA,MAAM,KAAA,EACJ,OAAO,QAAA,IAAY,SAAA,EAAW,MAAM,eAAA,CAAgB,OAAA,EAAS,MAAM,EAAA,EAAI,OAAA;AAGzE,EAAA,GAAA,CAAI,CAAC,IAAA,CAAK,MAAA,GAAS,OAAO,IAAA,CAAK,MAAA,IAAU,QAAA,EAAU;AACjD,IAAA,MAAM,IAAI,KAAA,CAAM,iEAAiE,CAAA;AAAA,EACnF;AAGA,EAAA,MAAM,YAAA,kBAAc,IAAI,GAAA,CAA+B,CAAA;AAEvD,EAAA,IAAA,CAAA,MAAW,CAAC,OAAA,EAAS,QAAQ,EAAA,GAAK,MAAA,CAAO,OAAA,CAAQ,IAAA,CAAK,KAAK,CAAA,EAAG;AAC5D,IAAA,GAAA,CAAI,CAAC,SAAA,GAAY,OAAO,SAAA,IAAa,QAAA,EAAU,QAAA;AAE/C,IAAA,IAAA,CAAA,MAAW,CAAC,MAAA,EAAQ,SAAS,EAAA,GAAK,MAAA,CAAO,OAAA,CAAQ,QAAQ,CAAA,EAAG;AAC1D,MAAA,GAAA,CAAI,OAAA,IAAW,aAAA,GAAgB,OAAA,IAAW,SAAA,EAAW,QAAA;AACrD,MAAA,GAAA,CAAI,CAAC,UAAA,GAAa,OAAO,UAAA,IAAc,QAAA,EAAU,QAAA;AAEjD,MAAA,MAAM,YAAA,EAAe,SAAA,CAAkB,yBAAyB,CAAA;AAChE,MAAA,MAAM,YAAA,EACH,SAAA,CAAkB,oBAAoB,EAAA,GACtC,SAAA,CAAkB,WAAA;AAErB,MAAA,GAAA,CAAI,YAAA,GAAe,OAAO,YAAA,IAAgB,QAAA,EAAU;AAClD,QAAA,MAAM,aAAA,EAAe,kBAAA;AAAA,UACnB,gBAAA;AAAA,UACA;AAAA,QACF,CAAA;AAEA,QAAA,GAAA,CAAI,CAAC,WAAA,CAAY,GAAA,CAAI,WAAW,CAAA,EAAG;AACjC,UAAA,WAAA,CAAY,GAAA,CAAI,WAAA,EAAa;AAAA,YAC3B,UAAA,EAAY,WAAA;AAAA,YACZ,YAAA,EAAc,WAAA;AAAA,YACd,YAAA;AAAA,YACA,YAAA,EAAc,CAAC;AAAA,UACjB,CAAC,CAAA;AAAA,QACH;AAEA,QAAA,GAAA,CAAI,WAAA,EAAa;AACf,UAAA,WAAA,CAAY,GAAA,CAAI,WAAW,CAAA,CAAG,YAAA,CAAa,IAAA,CAAK,WAAW,CAAA;AAAA,QAC7D;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAEA,EAAA,MAAM,QAAA,EAAU,KAAA,CAAM,IAAA,CAAK,WAAA,CAAY,MAAA,CAAO,CAAC,CAAA;AAE/C,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,OAAO,OAAA;AACT;AAKA,MAAA,SAAe,eAAA,CACb,QAAA,EACA,MAAA,EAC0B;AAC1B,EAAA,yBAAA,CAAQ,KAAA,CAAM,MAAA,EAAQ,2BAAA,EAA6B,EAAE,SAAS,CAAC,CAAA;AAC/D,EAAA,IAAI;AACF,IAAA,MAAM,QAAA,EAAU,MAAM,gCAAA,QAAS,EAAU,OAAO,CAAA;AAChD,IAAA,MAAM,IAAA,EAAM,cAAA,CAAK,OAAA,CAAQ,QAAQ,CAAA,CAAE,WAAA,CAAY,CAAA;AAE/C,IAAA,IAAI,MAAA;AACJ,IAAA,GAAA,CAAI,IAAA,IAAQ,OAAA,EAAS;AACnB,MAAA,OAAA,EAAS,IAAA,CAAK,KAAA,CAAM,OAAO,CAAA;AAAA,IAC7B,EAAA,KAAA,GAAA,CAAW,IAAA,IAAQ,QAAA,GAAW,IAAA,IAAQ,MAAA,EAAQ;AAE5C,MAAA,MAAM,KAAA,EAAO,MAAM,4DAAA,CAAO,MAAM,GAAA;AAChC,MAAA,OAAA,EAAS,IAAA,CAAK,KAAA,CAAM,OAAO,CAAA;AAAA,IAC7B,EAAA,KAAO;AACL,MAAA,MAAM,IAAI,KAAA;AAAA,QACR,CAAA,iCAAA,EAAoC,GAAG,CAAA,2BAAA;AAAA,MACzC,CAAA;AAAA,IACF;AAEA,IAAA,yBAAA,CAAQ,KAAA,CAAM,MAAA,EAAQ,kCAAA,EAAoC;AAAA,MACxD,QAAA;AAAA,MACA,MAAA,EAAQ;AAAA,IACV,CAAC,CAAA;AAED,IAAA,OAAO,MAAA;AAAA,EACT,EAAA,MAAA,CAAS,KAAA,EAAO;AACd,IAAA,GAAA,CAAK,KAAA,CAAgC,KAAA,IAAS,QAAA,EAAU;AACtD,MAAA,MAAM,IAAI,KAAA,CAAM,CAAA,sCAAA,EAAyC,QAAQ,CAAA,CAAA;AACnE,IAAA;AACM,IAAA;AACR,EAAA;AACF;AAQU;AAEsC,EAAA;AAGQ,EAAA;AAGZ,EAAA;AACE,EAAA;AAChC,IAAA;AAC8B,MAAA;AACxC,IAAA;AACF,EAAA;AAEO,EAAA;AACT;ADrEwE;AACA;AACA","file":"/home/runner/work/emmett-expressjs-with-openapi/emmett-expressjs-with-openapi/dist/openapi-parser-PW5USNZ4.cjs","sourcesContent":[null,"/**\n * OpenAPI Parser for handler module discovery.\n *\n * INTERNAL MODULE - Not part of public API.\n *\n * Parses OpenAPI specifications to extract handler module paths from\n * x-eov-operation-handler fields, enabling automatic handler discovery\n * and registration.\n */\n\nimport { readFile } from 'node:fs/promises';\nimport path from 'node:path';\nimport type { OpenAPIV3 } from 'express-openapi-validator/dist/framework/types';\nimport { type Logger, safeLog } from '../observability';\n\ntype OpenApiDocument = OpenAPIV3.DocumentV3 | OpenAPIV3.DocumentV3_1;\n\nexport type HandlerModuleInfo = {\n moduleName: string; // e.g., \"shoppingCarts\"\n relativePath: string; // from x-eov-operation-handler\n absolutePath: string; // resolved full path\n operationIds: string[]; // operations using this handler\n};\n\n/**\n * Extract handler modules from OpenAPI specification.\n *\n * @param apiSpec - OpenAPI spec (file path or object)\n * @param handlersBasePath - Base path for handler modules\n * @param logger - Optional logger for debug output\n * @returns Array of handler module information\n */\nexport async function extractHandlerModules(\n apiSpec: string | OpenApiDocument,\n handlersBasePath: string,\n logger?: Logger,\n): Promise<HandlerModuleInfo[]> {\n safeLog.debug(logger, 'Extracting handler modules from OpenAPI spec', {\n apiSpec: typeof apiSpec === 'string' ? apiSpec : '<object>',\n handlersBasePath,\n });\n\n // Load spec if it's a file path\n const spec =\n typeof apiSpec === 'string' ? await loadOpenApiSpec(apiSpec, logger) : apiSpec;\n\n // Validate spec structure\n if (!spec.paths || typeof spec.paths !== 'object') {\n throw new Error('Invalid OpenAPI specification: missing or invalid \"paths\" field');\n }\n\n // Extract handler modules from spec\n const handlersMap = new Map<string, HandlerModuleInfo>();\n\n for (const [pathKey, pathItem] of Object.entries(spec.paths)) {\n if (!pathItem || typeof pathItem !== 'object') continue;\n\n for (const [method, operation] of Object.entries(pathItem)) {\n if (method === 'parameters' || method === 'servers') continue;\n if (!operation || typeof operation !== 'object') continue;\n\n const handlerName = (operation as any)['x-eov-operation-handler'];\n const operationId =\n (operation as any)['x-eov-operation-id'] ||\n (operation as any).operationId;\n\n if (handlerName && typeof handlerName === 'string') {\n const absolutePath = resolveHandlerPath(\n handlersBasePath,\n handlerName,\n );\n\n if (!handlersMap.has(handlerName)) {\n handlersMap.set(handlerName, {\n moduleName: handlerName,\n relativePath: handlerName,\n absolutePath,\n operationIds: [],\n });\n }\n\n if (operationId) {\n handlersMap.get(handlerName)!.operationIds.push(operationId);\n }\n }\n }\n }\n\n const modules = Array.from(handlersMap.values());\n\n safeLog.debug(logger, 'Extracted handler modules', {\n count: modules.length,\n modules: modules.map((m) => m.moduleName),\n });\n\n return modules;\n}\n\n/**\n * Load OpenAPI specification from file.\n */\nasync function loadOpenApiSpec(\n filePath: string,\n logger?: Logger,\n): Promise<OpenApiDocument> {\n safeLog.debug(logger, 'Loading OpenAPI spec file', { filePath });\n try {\n const content = await readFile(filePath, 'utf-8');\n const ext = path.extname(filePath).toLowerCase();\n\n let parsed: OpenApiDocument;\n if (ext === '.json') {\n parsed = JSON.parse(content);\n } else if (ext === '.yaml' || ext === '.yml') {\n // Dynamic import to avoid bundling yaml if not needed\n const yaml = await import('yaml');\n parsed = yaml.parse(content);\n } else {\n throw new Error(\n `Unsupported OpenAPI file format: ${ext}. Use .json, .yaml, or .yml`,\n );\n }\n\n safeLog.debug(logger, 'OpenAPI spec loaded successfully', {\n filePath,\n format: ext,\n });\n\n return parsed;\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code === 'ENOENT') {\n throw new Error(`OpenAPI specification file not found: ${filePath}`);\n }\n throw error;\n }\n}\n\n/**\n * Resolve handler module path, preventing path traversal attacks.\n */\nfunction resolveHandlerPath(\n basePath: string,\n relativePath: string,\n): string {\n // Normalize to prevent path traversal\n const normalized = path.normalize(relativePath);\n\n // Resolve absolute path\n const absolutePath = path.resolve(basePath, normalized);\n\n // Ensure path is within basePath (no escape via ../)\n const resolvedBase = path.resolve(basePath);\n if (!absolutePath.startsWith(resolvedBase)) {\n throw new Error(\n `Invalid handler path: \"${relativePath}\" escapes base directory`,\n );\n }\n\n return absolutePath;\n}\n"]}
1
+ {"version":3,"sources":["/home/runner/work/emmett-expressjs-with-openapi/emmett-expressjs-with-openapi/dist/openapi-parser-HMWO3IPV.cjs","../src/internal/openapi-parser.ts"],"names":[],"mappings":"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","file":"/home/runner/work/emmett-expressjs-with-openapi/emmett-expressjs-with-openapi/dist/openapi-parser-HMWO3IPV.cjs","sourcesContent":[null,"/**\n * OpenAPI Parser for handler module discovery.\n *\n * INTERNAL MODULE - Not part of public API.\n *\n * Parses OpenAPI specifications to extract handler module paths from\n * x-eov-operation-handler fields, enabling automatic handler discovery\n * and registration.\n */\n\nimport { readFile } from 'node:fs/promises';\nimport path from 'node:path';\nimport type { OpenAPIV3 } from 'express-openapi-validator/dist/framework/types';\nimport { type Logger, safeLog } from '../observability';\n\ntype OpenApiDocument = OpenAPIV3.DocumentV3 | OpenAPIV3.DocumentV3_1;\n\nexport type HandlerModuleInfo = {\n moduleName: string; // e.g., \"shoppingCarts\"\n relativePath: string; // from x-eov-operation-handler\n absolutePath: string; // resolved full path\n operationIds: string[]; // operations using this handler\n};\n\n/**\n * Extract handler modules from OpenAPI specification.\n *\n * @param apiSpec - OpenAPI spec (file path or object)\n * @param handlersBasePath - Base path for handler modules\n * @param logger - Optional logger for debug output\n * @returns Array of handler module information\n */\nexport async function extractHandlerModules(\n apiSpec: string | OpenApiDocument,\n handlersBasePath: string,\n logger?: Logger,\n): Promise<HandlerModuleInfo[]> {\n safeLog.debug(logger, 'Extracting handler modules from OpenAPI spec', {\n apiSpec: typeof apiSpec === 'string' ? apiSpec : '<object>',\n handlersBasePath,\n });\n\n // Load spec if it's a file path\n const spec =\n typeof apiSpec === 'string' ? await loadOpenApiSpec(apiSpec, logger) : apiSpec;\n\n // Validate spec structure\n if (!spec.paths || typeof spec.paths !== 'object') {\n throw new Error('Invalid OpenAPI specification: missing or invalid \"paths\" field');\n }\n\n // Extract handler modules from spec\n const handlersMap = new Map<string, HandlerModuleInfo>();\n\n for (const [pathKey, pathItem] of Object.entries(spec.paths)) {\n if (!pathItem || typeof pathItem !== 'object') continue;\n\n for (const [method, operation] of Object.entries(pathItem)) {\n if (method === 'parameters' || method === 'servers') continue;\n if (!operation || typeof operation !== 'object') continue;\n\n const handlerName = (operation as any)['x-eov-operation-handler'];\n const operationId =\n (operation as any)['x-eov-operation-id'] ||\n (operation as any).operationId;\n\n if (handlerName && typeof handlerName === 'string') {\n const absolutePath = resolveHandlerPath(\n handlersBasePath,\n handlerName,\n );\n\n if (!handlersMap.has(handlerName)) {\n handlersMap.set(handlerName, {\n moduleName: handlerName,\n relativePath: handlerName,\n absolutePath,\n operationIds: [],\n });\n }\n\n if (operationId) {\n handlersMap.get(handlerName)!.operationIds.push(operationId);\n }\n }\n }\n }\n\n const modules = Array.from(handlersMap.values());\n\n safeLog.debug(logger, 'Extracted handler modules', {\n count: modules.length,\n modules: modules.map((m) => m.moduleName),\n });\n\n return modules;\n}\n\n/**\n * Load OpenAPI specification from file.\n */\nasync function loadOpenApiSpec(\n filePath: string,\n logger?: Logger,\n): Promise<OpenApiDocument> {\n safeLog.debug(logger, 'Loading OpenAPI spec file', { filePath });\n try {\n const content = await readFile(filePath, 'utf-8');\n const ext = path.extname(filePath).toLowerCase();\n\n let parsed: OpenApiDocument;\n if (ext === '.json') {\n parsed = JSON.parse(content);\n } else if (ext === '.yaml' || ext === '.yml') {\n // Dynamic import to avoid bundling yaml if not needed\n const yaml = await import('yaml');\n parsed = yaml.parse(content);\n } else {\n throw new Error(\n `Unsupported OpenAPI file format: ${ext}. Use .json, .yaml, or .yml`,\n );\n }\n\n safeLog.debug(logger, 'OpenAPI spec loaded successfully', {\n filePath,\n format: ext,\n });\n\n return parsed;\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code === 'ENOENT') {\n throw new Error(`OpenAPI specification file not found: ${filePath}`);\n }\n throw error;\n }\n}\n\n/**\n * Resolve handler module path, preventing path traversal attacks.\n */\nfunction resolveHandlerPath(\n basePath: string,\n relativePath: string,\n): string {\n // Normalize to prevent path traversal\n const normalized = path.normalize(relativePath);\n\n // Resolve absolute path\n const absolutePath = path.resolve(basePath, normalized);\n\n // Ensure path is within basePath (no escape via ../)\n const resolvedBase = path.resolve(basePath);\n if (!absolutePath.startsWith(resolvedBase)) {\n throw new Error(\n `Invalid handler path: \"${relativePath}\" escapes base directory`,\n );\n }\n\n return absolutePath;\n}\n"]}
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  safeLog
3
- } from "./chunk-I46UH36B.js";
3
+ } from "./chunk-PZBLPYJH.js";
4
4
 
5
5
  // src/internal/openapi-parser.ts
6
6
  import { readFile } from "fs/promises";
@@ -90,4 +90,4 @@ function resolveHandlerPath(basePath, relativePath) {
90
90
  export {
91
91
  extractHandlerModules
92
92
  };
93
- //# sourceMappingURL=openapi-parser-QYXNHNSZ.js.map
93
+ //# sourceMappingURL=openapi-parser-SFYDJEIN.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@emmett-community/emmett-expressjs-with-openapi",
3
- "version": "0.4.0",
3
+ "version": "0.5.0",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "description": "Express.js utilities for Emmett applications that want OpenAPI 3.x validation without pulling the whole Emmett core",
@@ -1,12 +0,0 @@
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
@@ -1 +0,0 @@
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"]}
@@ -1,12 +0,0 @@
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
@@ -1 +0,0 @@
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":[]}