@fjell/express-router 4.4.83 → 4.4.84

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/CHANGELOG.md ADDED
@@ -0,0 +1,20 @@
1
+ # Changelog
2
+
3
+ All notable changes to `@fjell/express-router` will be documented in this file.
4
+
5
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
+ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
+
8
+ ## [Unreleased]
9
+
10
+ ### Security
11
+ - Redact sensitive request headers (`authorization`, `cookie`, etc.) in error logs by default
12
+ - Do not log request bodies by default (`logRequestBody` opt-in); redact common secret fields in query/params/body when logged
13
+ - Avoid echoing full request body into error response `operation.params` unless `logRequestBody` is enabled
14
+
15
+ ### Changed
16
+ - Bump express to 5.2.x and vitest/eslint toolchain within current majors
17
+ - Exclude `examples/**` from coverage; set src-only thresholds (lines/functions/statements 85, branches 72)
18
+
19
+ ### Added
20
+ - CHANGELOG.md and explicit `package.json` `files` whitelist
@@ -3,6 +3,10 @@ import { ErrorInfo } from '@fjell/core';
3
3
  export interface ErrorHandlerOptions {
4
4
  includeStackTrace?: boolean;
5
5
  logErrors?: boolean;
6
+ /** When true, include request body in error logs (default: false). */
7
+ logRequestBody?: boolean;
8
+ /** When true, include raw (unredacted) headers in error logs (default: false). */
9
+ logRawHeaders?: boolean;
6
10
  customErrorMapper?: (error: any) => ErrorInfo | null;
7
11
  }
8
12
  export declare class FjellErrorHandler {
@@ -12,6 +16,7 @@ export declare class FjellErrorHandler {
12
16
  * Get appropriate HTTP status code for error
13
17
  */
14
18
  private getHttpStatusForError;
19
+ private getSafeOperationParams;
15
20
  /**
16
21
  * Convert generic errors to ErrorInfo structure
17
22
  */
@@ -1 +1 @@
1
- {"version":3,"file":"errorHandler.d.ts","sourceRoot":"","sources":["../src/errorHandler.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AAC1D,OAAO,EAAE,SAAS,EAAiB,MAAM,aAAa,CAAC;AAKvD,MAAM,WAAW,mBAAmB;IAClC,iBAAiB,CAAC,EAAE,OAAO,CAAC;IAC5B,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,iBAAiB,CAAC,EAAE,CAAC,KAAK,EAAE,GAAG,KAAK,SAAS,GAAG,IAAI,CAAC;CACtD;AAED,qBAAa,iBAAiB;IAChB,OAAO,CAAC,OAAO;gBAAP,OAAO,GAAE,mBAAwB;IAQrD;;OAEG;IACH,OAAO,CAAC,qBAAqB;IAgB7B;;OAEG;IACH,OAAO,CAAC,kBAAkB;IAiE1B;;OAEG;IACH,OAAO,CAAC,gBAAgB;IAoBxB;;OAEG;IACH,OAAO,CAAC,2BAA2B;IA4BnC;;OAEG;IACH,OAAO,CAAC,eAAe;IAKvB;;OAEG;IACH,OAAO,CAAC,YAAY;IAMpB;;OAEG;IACH,OAAO,CAAC,qBAAqB;IAc7B;;OAEG;IACH,OAAO,CAAC,0BAA0B;IAclC;;OAEG;IACH,MAAM,GAAI,KAAK,GAAG,EAAE,KAAK,OAAO,EAAE,KAAK,QAAQ,EAAE,MAAM,YAAY,KAAG,IAAI,CAqDxE;CACH;AAED;;GAEG;AACH,wBAAgB,kBAAkB,CAAC,OAAO,CAAC,EAAE,mBAAmB,SA3D/C,GAAG,OAAO,OAAO,OAAO,QAAQ,QAAQ,YAAY,KAAG,IAAI,CA8D3E"}
1
+ {"version":3,"file":"errorHandler.d.ts","sourceRoot":"","sources":["../src/errorHandler.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AAC1D,OAAO,EAAE,SAAS,EAAiB,MAAM,aAAa,CAAC;AA2BvD,MAAM,WAAW,mBAAmB;IAClC,iBAAiB,CAAC,EAAE,OAAO,CAAC;IAC5B,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,sEAAsE;IACtE,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,kFAAkF;IAClF,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,iBAAiB,CAAC,EAAE,CAAC,KAAK,EAAE,GAAG,KAAK,SAAS,GAAG,IAAI,CAAC;CACtD;AA+BD,qBAAa,iBAAiB;IAChB,OAAO,CAAC,OAAO;gBAAP,OAAO,GAAE,mBAAwB;IAUrD;;OAEG;IACH,OAAO,CAAC,qBAAqB;IAgB7B,OAAO,CAAC,sBAAsB;IAQ9B;;OAEG;IACH,OAAO,CAAC,kBAAkB;IAiE1B;;OAEG;IACH,OAAO,CAAC,gBAAgB;IAoBxB;;OAEG;IACH,OAAO,CAAC,2BAA2B;IA4BnC;;OAEG;IACH,OAAO,CAAC,eAAe;IAKvB;;OAEG;IACH,OAAO,CAAC,YAAY;IAMpB;;OAEG;IACH,OAAO,CAAC,qBAAqB;IAc7B;;OAEG;IACH,OAAO,CAAC,0BAA0B;IAclC;;OAEG;IACH,MAAM,GAAI,KAAK,GAAG,EAAE,KAAK,OAAO,EAAE,KAAK,QAAQ,EAAE,MAAM,YAAY,KAAG,IAAI,CAyDxE;CACH;AAED;;GAEG;AACH,wBAAgB,kBAAkB,CAAC,OAAO,CAAC,EAAE,mBAAmB,SA/D/C,GAAG,OAAO,OAAO,OAAO,QAAQ,QAAQ,YAAY,KAAG,IAAI,CAkE3E"}
@@ -1,15 +1,65 @@
1
1
  import { isActionError } from "@fjell/core";
2
2
  import LibLogger from "./logger.js";
3
3
  const logger = LibLogger.get("express-router", "errorHandler");
4
+ const SENSITIVE_HEADER_NAMES = /* @__PURE__ */ new Set([
5
+ "authorization",
6
+ "cookie",
7
+ "set-cookie",
8
+ "x-api-key",
9
+ "x-auth-token",
10
+ "proxy-authorization"
11
+ ]);
12
+ const SENSITIVE_PARAM_KEYS = /* @__PURE__ */ new Set([
13
+ "password",
14
+ "passwd",
15
+ "secret",
16
+ "token",
17
+ "access_token",
18
+ "refresh_token",
19
+ "api_key",
20
+ "apikey",
21
+ "authorization",
22
+ "cookie"
23
+ ]);
24
+ function redactHeaders(headers) {
25
+ const redacted = {};
26
+ for (const [key, value] of Object.entries(headers || {})) {
27
+ if (SENSITIVE_HEADER_NAMES.has(key.toLowerCase())) {
28
+ redacted[key] = "[REDACTED]";
29
+ } else {
30
+ redacted[key] = value;
31
+ }
32
+ }
33
+ return redacted;
34
+ }
35
+ function redactParams(params) {
36
+ if (params == null || typeof params !== "object" || Array.isArray(params)) {
37
+ return params;
38
+ }
39
+ const redacted = {};
40
+ for (const [key, value] of Object.entries(params)) {
41
+ if (SENSITIVE_PARAM_KEYS.has(key.toLowerCase())) {
42
+ redacted[key] = "[REDACTED]";
43
+ } else if (value && typeof value === "object" && !Array.isArray(value)) {
44
+ redacted[key] = redactParams(value);
45
+ } else {
46
+ redacted[key] = value;
47
+ }
48
+ }
49
+ return redacted;
50
+ }
4
51
  class FjellErrorHandler {
5
52
  constructor(options = {}) {
6
53
  this.options = options;
7
54
  this.options = {
8
55
  includeStackTrace: process.env.NODE_ENV === "development",
9
56
  logErrors: true,
57
+ logRequestBody: false,
58
+ logRawHeaders: false,
10
59
  ...options
11
60
  };
12
61
  }
62
+ options;
13
63
  /**
14
64
  * Get appropriate HTTP status code for error
15
65
  */
@@ -27,6 +77,13 @@ class FjellErrorHandler {
27
77
  };
28
78
  return statusMap[code] || 500;
29
79
  }
80
+ getSafeOperationParams(req) {
81
+ return redactParams({
82
+ ...req.query,
83
+ ...req.params,
84
+ ...this.options.logRequestBody ? req.body : {}
85
+ });
86
+ }
30
87
  /**
31
88
  * Convert generic errors to ErrorInfo structure
32
89
  */
@@ -52,7 +109,7 @@ class FjellErrorHandler {
52
109
  operation: {
53
110
  type: this.getOperationTypeFromRequest(req),
54
111
  name: req.path,
55
- params: { ...req.body, ...req.query, ...req.params }
112
+ params: this.getSafeOperationParams(req)
56
113
  },
57
114
  context: {
58
115
  itemType: this.extractItemType(req.path)
@@ -76,7 +133,7 @@ class FjellErrorHandler {
76
133
  operation: {
77
134
  type: this.getOperationTypeFromRequest(req),
78
135
  name: req.path,
79
- params: { ...req.body, ...req.query, ...req.params }
136
+ params: this.getSafeOperationParams(req)
80
137
  },
81
138
  context: {
82
139
  itemType: this.extractItemType(req.path)
@@ -191,6 +248,16 @@ class FjellErrorHandler {
191
248
  statusCode = this.getHttpStatusForError(errorInfo.code);
192
249
  }
193
250
  if (this.options.logErrors) {
251
+ const requestLog = {
252
+ method: req.method,
253
+ path: req.path,
254
+ headers: this.options.logRawHeaders ? req.headers : redactHeaders(req.headers),
255
+ query: redactParams(req.query),
256
+ params: redactParams(req.params)
257
+ };
258
+ if (this.options.logRequestBody) {
259
+ requestLog.body = redactParams(req.body);
260
+ }
194
261
  logger.error("HTTP request error", {
195
262
  component: "FjellErrorHandler",
196
263
  operation: "handle",
@@ -206,14 +273,7 @@ class FjellErrorHandler {
206
273
  validOptions: errorInfo.details?.validOptions,
207
274
  suggestedAction: errorInfo.details?.suggestedAction,
208
275
  retryable: errorInfo.details?.retryable,
209
- request: {
210
- method: req.method,
211
- path: req.path,
212
- headers: req.headers,
213
- body: req.body,
214
- query: req.query,
215
- params: req.params
216
- },
276
+ request: requestLog,
217
277
  suggestion: errorInfo.details?.suggestedAction || "Check request parameters, authentication, and data validation",
218
278
  errorInfo
219
279
  });
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../src/errorHandler.ts"],
4
- "sourcesContent": ["import { NextFunction, Request, Response } from 'express';\nimport { ErrorInfo, isActionError } from '@fjell/core';\nimport LibLogger from './logger.js';\n\nconst logger = LibLogger.get('express-router', 'errorHandler');\n\nexport interface ErrorHandlerOptions {\n includeStackTrace?: boolean;\n logErrors?: boolean;\n customErrorMapper?: (error: any) => ErrorInfo | null;\n}\n\nexport class FjellErrorHandler {\n constructor(private options: ErrorHandlerOptions = {}) {\n this.options = {\n includeStackTrace: process.env.NODE_ENV === 'development',\n logErrors: true,\n ...options\n };\n }\n\n /**\n * Get appropriate HTTP status code for error\n */\n private getHttpStatusForError(code: string): number {\n const statusMap: Record<string, number> = {\n 'VALIDATION_ERROR': 400,\n 'NOT_FOUND': 404,\n 'PERMISSION_ERROR': 403,\n 'UNAUTHORIZED': 401,\n 'BUSINESS_LOGIC_ERROR': 422,\n 'ACTION_NOT_FOUND': 404,\n 'DUPLICATE_ERROR': 409,\n 'RATE_LIMIT_EXCEEDED': 429,\n 'INTERNAL_ERROR': 500,\n };\n\n return statusMap[code] || 500;\n }\n\n /**\n * Convert generic errors to ErrorInfo structure\n */\n private convertToErrorInfo(error: any, req: Request): ErrorInfo {\n // Check if custom mapper can handle this error\n if (this.options.customErrorMapper) {\n const mapped = this.options.customErrorMapper(error);\n if (mapped) {\n return this.enhanceErrorInfo(mapped, req);\n }\n }\n\n // Handle common HTTP errors\n if (error.status || error.statusCode) {\n const status = error.status || error.statusCode;\n const technical: ErrorInfo['technical'] = {\n timestamp: new Date().toISOString(),\n requestId: this.getRequestId(req)\n };\n \n if (this.options.includeStackTrace && error.stack) {\n technical.stackTrace = error.stack;\n }\n\n return {\n code: this.getErrorCodeForStatus(status),\n message: error.message || this.getDefaultMessageForStatus(status),\n operation: {\n type: this.getOperationTypeFromRequest(req),\n name: req.path,\n params: { ...req.body, ...req.query, ...req.params }\n },\n context: {\n itemType: this.extractItemType(req.path)\n },\n technical\n };\n }\n\n // Default error structure\n const technical: ErrorInfo['technical'] = {\n timestamp: new Date().toISOString(),\n requestId: this.getRequestId(req)\n };\n \n if (this.options.includeStackTrace && error.stack) {\n technical.stackTrace = error.stack;\n }\n \n if (error.cause) {\n technical.cause = error.cause;\n }\n\n return {\n code: 'INTERNAL_ERROR',\n message: error.message || 'An unexpected error occurred',\n operation: {\n type: this.getOperationTypeFromRequest(req),\n name: req.path,\n params: { ...req.body, ...req.query, ...req.params }\n },\n context: {\n itemType: this.extractItemType(req.path)\n },\n technical\n };\n }\n\n /**\n * Enhance error info with request context\n */\n private enhanceErrorInfo(errorInfo: ErrorInfo, req: Request): ErrorInfo {\n const technical: ErrorInfo['technical'] = {\n timestamp: errorInfo.technical?.timestamp || new Date().toISOString(),\n requestId: this.getRequestId(req)\n };\n\n if (this.options.includeStackTrace && errorInfo.technical?.stackTrace) {\n technical.stackTrace = errorInfo.technical.stackTrace;\n }\n\n if (errorInfo.technical?.cause) {\n technical.cause = errorInfo.technical.cause;\n }\n\n return {\n ...errorInfo,\n technical\n };\n }\n\n /**\n * Extract operation type from request\n */\n private getOperationTypeFromRequest(req: Request): ErrorInfo['operation']['type'] {\n const path = req.path.toLowerCase();\n const method = req.method.toLowerCase();\n\n // Pattern matching for operation types\n if (path.includes('/action/')) return 'action';\n if (path.includes('/allaction/')) return 'allAction';\n if (path.includes('/facet/')) return 'facet';\n if (path.includes('/allfacet/')) return 'allFacet';\n if (path.includes('/find/')) return method === 'get' ? 'findOne' : 'find';\n if (path.includes('/upsert')) return 'upsert';\n \n // Method-based detection\n switch (method) {\n case 'get':\n return path.match(/\\/\\d+$/) ? 'get' : 'all';\n case 'post':\n return 'create';\n case 'put':\n case 'patch':\n return 'update';\n case 'delete':\n return 'remove';\n default:\n return 'get';\n }\n }\n\n /**\n * Extract item type from path\n */\n private extractItemType(path: string): string {\n const segments = path.split('/').filter(s => s && !s.match(/^\\d+$/));\n return segments[segments.length - 1] || 'unknown';\n }\n\n /**\n * Get request ID from headers or generate one\n */\n private getRequestId(req: Request): string {\n return (req.headers['x-request-id'] ||\n req.headers['x-trace-id'] ||\n `req-${Date.now()}`) as string;\n }\n\n /**\n * Get error code for HTTP status\n */\n private getErrorCodeForStatus(status: number): string {\n const codeMap: Record<number, string> = {\n 400: 'VALIDATION_ERROR',\n 401: 'UNAUTHORIZED',\n 403: 'PERMISSION_ERROR',\n 404: 'NOT_FOUND',\n 409: 'DUPLICATE_ERROR',\n 422: 'BUSINESS_LOGIC_ERROR',\n 429: 'RATE_LIMIT_EXCEEDED',\n 500: 'INTERNAL_ERROR',\n };\n return codeMap[status] || 'INTERNAL_ERROR';\n }\n\n /**\n * Get default message for HTTP status\n */\n private getDefaultMessageForStatus(status: number): string {\n const messageMap: Record<number, string> = {\n 400: 'Bad Request',\n 401: 'Unauthorized',\n 403: 'Forbidden',\n 404: 'Not Found',\n 409: 'Conflict',\n 422: 'Unprocessable Entity',\n 429: 'Too Many Requests',\n 500: 'Internal Server Error',\n };\n return messageMap[status] || 'Error';\n }\n\n /**\n * Express error handler middleware\n */\n handle = (err: any, req: Request, res: Response, next: NextFunction): void => {\n if (res.headersSent) {\n return next(err);\n }\n\n let errorInfo: ErrorInfo;\n let statusCode: number;\n\n if (isActionError(err)) {\n // This is already a structured Fjell error\n errorInfo = this.enhanceErrorInfo(err.errorInfo, req);\n statusCode = this.getHttpStatusForError(errorInfo.code);\n } else {\n // Convert to ErrorInfo structure\n errorInfo = this.convertToErrorInfo(err, req);\n statusCode = this.getHttpStatusForError(errorInfo.code);\n }\n\n // Log the error if configured - structured for agentic debugging\n if (this.options.logErrors) {\n logger.error('HTTP request error', {\n component: 'FjellErrorHandler',\n operation: 'handle',\n endpoint: req.path,\n method: req.method,\n statusCode,\n errorCode: errorInfo.code,\n errorMessage: errorInfo.message,\n operationType: errorInfo.operation?.type,\n operationName: errorInfo.operation?.name,\n itemType: errorInfo.context?.itemType,\n requestId: this.getRequestId(req),\n validOptions: errorInfo.details?.validOptions,\n suggestedAction: errorInfo.details?.suggestedAction,\n retryable: errorInfo.details?.retryable,\n request: {\n method: req.method,\n path: req.path,\n headers: req.headers,\n body: req.body,\n query: req.query,\n params: req.params\n },\n suggestion: errorInfo.details?.suggestedAction || 'Check request parameters, authentication, and data validation',\n errorInfo\n });\n }\n\n // Send structured error response\n res.status(statusCode).json({\n success: false,\n error: errorInfo\n });\n };\n}\n\n/**\n * Export convenience function\n */\nexport function createErrorHandler(options?: ErrorHandlerOptions) {\n const handler = new FjellErrorHandler(options);\n return handler.handle;\n}\n\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["import { NextFunction, Request, Response } from 'express';\nimport { ErrorInfo, isActionError } from '@fjell/core';\nimport LibLogger from './logger.js';\n\nconst logger = LibLogger.get('express-router', 'errorHandler');\n\nconst SENSITIVE_HEADER_NAMES = new Set([\n 'authorization',\n 'cookie',\n 'set-cookie',\n 'x-api-key',\n 'x-auth-token',\n 'proxy-authorization',\n]);\n\nconst SENSITIVE_PARAM_KEYS = new Set([\n 'password',\n 'passwd',\n 'secret',\n 'token',\n 'access_token',\n 'refresh_token',\n 'api_key',\n 'apikey',\n 'authorization',\n 'cookie',\n]);\n\nexport interface ErrorHandlerOptions {\n includeStackTrace?: boolean;\n logErrors?: boolean;\n /** When true, include request body in error logs (default: false). */\n logRequestBody?: boolean;\n /** When true, include raw (unredacted) headers in error logs (default: false). */\n logRawHeaders?: boolean;\n customErrorMapper?: (error: any) => ErrorInfo | null;\n}\n\nfunction redactHeaders(headers: Request['headers']): Record<string, unknown> {\n const redacted: Record<string, unknown> = {};\n for (const [key, value] of Object.entries(headers || {})) {\n if (SENSITIVE_HEADER_NAMES.has(key.toLowerCase())) {\n redacted[key] = '[REDACTED]';\n } else {\n redacted[key] = value;\n }\n }\n return redacted;\n}\n\nfunction redactParams(params: unknown): unknown {\n if (params == null || typeof params !== 'object' || Array.isArray(params)) {\n return params;\n }\n const redacted: Record<string, unknown> = {};\n for (const [key, value] of Object.entries(params as Record<string, unknown>)) {\n if (SENSITIVE_PARAM_KEYS.has(key.toLowerCase())) {\n redacted[key] = '[REDACTED]';\n } else if (value && typeof value === 'object' && !Array.isArray(value)) {\n redacted[key] = redactParams(value);\n } else {\n redacted[key] = value;\n }\n }\n return redacted;\n}\n\nexport class FjellErrorHandler {\n constructor(private options: ErrorHandlerOptions = {}) {\n this.options = {\n includeStackTrace: process.env.NODE_ENV === 'development',\n logErrors: true,\n logRequestBody: false,\n logRawHeaders: false,\n ...options\n };\n }\n\n /**\n * Get appropriate HTTP status code for error\n */\n private getHttpStatusForError(code: string): number {\n const statusMap: Record<string, number> = {\n 'VALIDATION_ERROR': 400,\n 'NOT_FOUND': 404,\n 'PERMISSION_ERROR': 403,\n 'UNAUTHORIZED': 401,\n 'BUSINESS_LOGIC_ERROR': 422,\n 'ACTION_NOT_FOUND': 404,\n 'DUPLICATE_ERROR': 409,\n 'RATE_LIMIT_EXCEEDED': 429,\n 'INTERNAL_ERROR': 500,\n };\n\n return statusMap[code] || 500;\n }\n\n private getSafeOperationParams(req: Request): Record<string, unknown> {\n return redactParams({\n ...req.query,\n ...req.params,\n ...(this.options.logRequestBody ? req.body : {}),\n }) as Record<string, unknown>;\n }\n\n /**\n * Convert generic errors to ErrorInfo structure\n */\n private convertToErrorInfo(error: any, req: Request): ErrorInfo {\n // Check if custom mapper can handle this error\n if (this.options.customErrorMapper) {\n const mapped = this.options.customErrorMapper(error);\n if (mapped) {\n return this.enhanceErrorInfo(mapped, req);\n }\n }\n\n // Handle common HTTP errors\n if (error.status || error.statusCode) {\n const status = error.status || error.statusCode;\n const technical: ErrorInfo['technical'] = {\n timestamp: new Date().toISOString(),\n requestId: this.getRequestId(req)\n };\n \n if (this.options.includeStackTrace && error.stack) {\n technical.stackTrace = error.stack;\n }\n\n return {\n code: this.getErrorCodeForStatus(status),\n message: error.message || this.getDefaultMessageForStatus(status),\n operation: {\n type: this.getOperationTypeFromRequest(req),\n name: req.path,\n params: this.getSafeOperationParams(req)\n },\n context: {\n itemType: this.extractItemType(req.path)\n },\n technical\n };\n }\n\n // Default error structure\n const technical: ErrorInfo['technical'] = {\n timestamp: new Date().toISOString(),\n requestId: this.getRequestId(req)\n };\n \n if (this.options.includeStackTrace && error.stack) {\n technical.stackTrace = error.stack;\n }\n \n if (error.cause) {\n technical.cause = error.cause;\n }\n\n return {\n code: 'INTERNAL_ERROR',\n message: error.message || 'An unexpected error occurred',\n operation: {\n type: this.getOperationTypeFromRequest(req),\n name: req.path,\n params: this.getSafeOperationParams(req)\n },\n context: {\n itemType: this.extractItemType(req.path)\n },\n technical\n };\n }\n\n /**\n * Enhance error info with request context\n */\n private enhanceErrorInfo(errorInfo: ErrorInfo, req: Request): ErrorInfo {\n const technical: ErrorInfo['technical'] = {\n timestamp: errorInfo.technical?.timestamp || new Date().toISOString(),\n requestId: this.getRequestId(req)\n };\n\n if (this.options.includeStackTrace && errorInfo.technical?.stackTrace) {\n technical.stackTrace = errorInfo.technical.stackTrace;\n }\n\n if (errorInfo.technical?.cause) {\n technical.cause = errorInfo.technical.cause;\n }\n\n return {\n ...errorInfo,\n technical\n };\n }\n\n /**\n * Extract operation type from request\n */\n private getOperationTypeFromRequest(req: Request): ErrorInfo['operation']['type'] {\n const path = req.path.toLowerCase();\n const method = req.method.toLowerCase();\n\n // Pattern matching for operation types\n if (path.includes('/action/')) return 'action';\n if (path.includes('/allaction/')) return 'allAction';\n if (path.includes('/facet/')) return 'facet';\n if (path.includes('/allfacet/')) return 'allFacet';\n if (path.includes('/find/')) return method === 'get' ? 'findOne' : 'find';\n if (path.includes('/upsert')) return 'upsert';\n \n // Method-based detection\n switch (method) {\n case 'get':\n return path.match(/\\/\\d+$/) ? 'get' : 'all';\n case 'post':\n return 'create';\n case 'put':\n case 'patch':\n return 'update';\n case 'delete':\n return 'remove';\n default:\n return 'get';\n }\n }\n\n /**\n * Extract item type from path\n */\n private extractItemType(path: string): string {\n const segments = path.split('/').filter(s => s && !s.match(/^\\d+$/));\n return segments[segments.length - 1] || 'unknown';\n }\n\n /**\n * Get request ID from headers or generate one\n */\n private getRequestId(req: Request): string {\n return (req.headers['x-request-id'] ||\n req.headers['x-trace-id'] ||\n `req-${Date.now()}`) as string;\n }\n\n /**\n * Get error code for HTTP status\n */\n private getErrorCodeForStatus(status: number): string {\n const codeMap: Record<number, string> = {\n 400: 'VALIDATION_ERROR',\n 401: 'UNAUTHORIZED',\n 403: 'PERMISSION_ERROR',\n 404: 'NOT_FOUND',\n 409: 'DUPLICATE_ERROR',\n 422: 'BUSINESS_LOGIC_ERROR',\n 429: 'RATE_LIMIT_EXCEEDED',\n 500: 'INTERNAL_ERROR',\n };\n return codeMap[status] || 'INTERNAL_ERROR';\n }\n\n /**\n * Get default message for HTTP status\n */\n private getDefaultMessageForStatus(status: number): string {\n const messageMap: Record<number, string> = {\n 400: 'Bad Request',\n 401: 'Unauthorized',\n 403: 'Forbidden',\n 404: 'Not Found',\n 409: 'Conflict',\n 422: 'Unprocessable Entity',\n 429: 'Too Many Requests',\n 500: 'Internal Server Error',\n };\n return messageMap[status] || 'Error';\n }\n\n /**\n * Express error handler middleware\n */\n handle = (err: any, req: Request, res: Response, next: NextFunction): void => {\n if (res.headersSent) {\n return next(err);\n }\n\n let errorInfo: ErrorInfo;\n let statusCode: number;\n\n if (isActionError(err)) {\n // This is already a structured Fjell error\n errorInfo = this.enhanceErrorInfo(err.errorInfo, req);\n statusCode = this.getHttpStatusForError(errorInfo.code);\n } else {\n // Convert to ErrorInfo structure\n errorInfo = this.convertToErrorInfo(err, req);\n statusCode = this.getHttpStatusForError(errorInfo.code);\n }\n\n // Log the error if configured - structured for agentic debugging\n if (this.options.logErrors) {\n const requestLog: Record<string, unknown> = {\n method: req.method,\n path: req.path,\n headers: this.options.logRawHeaders ? req.headers : redactHeaders(req.headers),\n query: redactParams(req.query),\n params: redactParams(req.params),\n };\n if (this.options.logRequestBody) {\n requestLog.body = redactParams(req.body);\n }\n\n logger.error('HTTP request error', {\n component: 'FjellErrorHandler',\n operation: 'handle',\n endpoint: req.path,\n method: req.method,\n statusCode,\n errorCode: errorInfo.code,\n errorMessage: errorInfo.message,\n operationType: errorInfo.operation?.type,\n operationName: errorInfo.operation?.name,\n itemType: errorInfo.context?.itemType,\n requestId: this.getRequestId(req),\n validOptions: errorInfo.details?.validOptions,\n suggestedAction: errorInfo.details?.suggestedAction,\n retryable: errorInfo.details?.retryable,\n request: requestLog,\n suggestion: errorInfo.details?.suggestedAction || 'Check request parameters, authentication, and data validation',\n errorInfo\n });\n }\n\n // Send structured error response\n res.status(statusCode).json({\n success: false,\n error: errorInfo\n });\n };\n}\n\n/**\n * Export convenience function\n */\nexport function createErrorHandler(options?: ErrorHandlerOptions) {\n const handler = new FjellErrorHandler(options);\n return handler.handle;\n}\n"],
5
+ "mappings": "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",
6
6
  "names": ["technical"]
7
7
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@fjell/express-router",
3
3
  "description": "Express Router for Fjell",
4
- "version": "4.4.83",
4
+ "version": "4.4.84",
5
5
  "keywords": [
6
6
  "express",
7
7
  "router",
@@ -34,33 +34,45 @@
34
34
  "docs:test": "cd docs && npm run test"
35
35
  },
36
36
  "dependencies": {
37
- "@fjell/core": "^4.4.79",
38
- "@fjell/lib": "^4.4.89",
39
- "@fjell/logging": "^4.4.72",
40
- "@fjell/registry": "^4.4.86",
41
- "@fjell/validation": "^4.4.6",
37
+ "@fjell/core": "^4.4.80",
38
+ "@fjell/lib": "^4.4.90",
39
+ "@fjell/logging": "^4.4.73",
40
+ "@fjell/registry": "^4.4.87",
41
+ "@fjell/validation": "^4.4.7",
42
42
  "deepmerge": "^4.3.1",
43
- "express": "^5.1.0"
43
+ "express": "^5.2.1"
44
44
  },
45
45
  "devDependencies": {
46
46
  "@eslint/eslintrc": "^3.3.1",
47
- "@eslint/js": "^9.38.0",
48
- "@fjell/common-config": "^1.1.38",
47
+ "@eslint/js": "^9.39.4",
48
+ "@fjell/common-config": "^1.1.41",
49
49
  "@tsconfig/recommended": "^1.0.10",
50
- "@types/express": "^5.0.3",
51
- "@types/node": "^24.9.1",
52
- "@typescript-eslint/eslint-plugin": "^8.46.2",
53
- "@typescript-eslint/parser": "^8.46.2",
54
- "@vitest/coverage-v8": "^4.0.2",
55
- "esbuild": "0.25.11",
56
- "eslint": "^9.38.0",
50
+ "@types/express": "^5.0.6",
51
+ "@types/node": "^24.13.3",
52
+ "@typescript-eslint/eslint-plugin": "^8.63.0",
53
+ "@typescript-eslint/parser": "^8.63.0",
54
+ "@vitest/coverage-v8": "^4.1.10",
55
+ "esbuild": "0.28.1",
56
+ "eslint": "^9.39.4",
57
57
  "nodemon": "^3.1.10",
58
58
  "supertest": "^7.1.4",
59
59
  "typescript": "^5.9.3",
60
- "vitest": "^4.0.2"
60
+ "vitest": "^4.1.10"
61
61
  },
62
62
  "repository": {
63
63
  "type": "git",
64
64
  "url": "git+https://github.com/fjellgrunn/express-router.git"
65
- }
65
+ },
66
+ "bugs": {
67
+ "url": "https://github.com/fjellgrunn/express-router/issues"
68
+ },
69
+ "homepage": "https://github.com/fjellgrunn/express-router#readme",
70
+ "author": "Fjell Team",
71
+ "files": [
72
+ "dist",
73
+ "README.md",
74
+ "LICENSE",
75
+ "CHANGELOG.md",
76
+ "guide"
77
+ ]
66
78
  }