woodland 22.1.1 → 22.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -155,6 +155,22 @@ app.use((error, req, res, next) => {
155
155
  });
156
156
  ```
157
157
 
158
+ ### Global Error Handling
159
+
160
+ Set `app.error` to intercept all unhandled errors before the error middleware chain:
161
+
162
+ ```javascript
163
+ const app = woodland();
164
+
165
+ app.error = (err, _req, res) => {
166
+ console.error("Unhandled error:", err);
167
+ res.status(500).send("Internal server error");
168
+ };
169
+ ```
170
+
171
+ The handler receives 3 arguments `(err, req, res)` and must terminate the request itself. When set, the error middleware chain is skipped.
172
+
173
+
158
174
  ## Configuration
159
175
 
160
176
  ```javascript
@@ -196,6 +212,7 @@ req.cors; // CORS enabled
196
212
  req.body; // Request body
197
213
  req.host; // Hostname
198
214
  req.valid; // Request validity
215
+ req.app; // Woodland application instance (provides access to app.error)
199
216
  ```
200
217
 
201
218
  ## Event Handlers
@@ -262,7 +279,7 @@ npm run lint # Check linting
262
279
  ## Documentation
263
280
 
264
281
  - [API Reference](docs/API.md) - Complete method documentation
265
- - [Technical Documentation](docs/TECHNICAL_DOCUMENTATION.md) - Architecture, OWASP security, internals
282
+ - [Framework Overview](docs/OVERVIEW.md) - Architecture, OWASP security, internals
266
283
  - [Code Style Guide](docs/CODE_STYLE_GUIDE.md) - Conventions and best practices
267
284
  - [Benchmarks](docs/BENCHMARKS.md) - Performance testing results
268
285
 
package/dist/cli.cjs CHANGED
@@ -4,7 +4,7 @@
4
4
  *
5
5
  * @copyright 2026 Jason Mulligan <jason.mulligan@avoidwork.com>
6
6
  * @license BSD-3-Clause
7
- * @version 22.1.1
7
+ * @version 22.2.1
8
8
  */
9
9
  'use strict';
10
10
 
package/dist/woodland.cjs CHANGED
@@ -3,7 +3,7 @@
3
3
  *
4
4
  * @copyright 2026 Jason Mulligan <jason.mulligan@avoidwork.com>
5
5
  * @license BSD-3-Clause
6
- * @version 22.1.1
6
+ * @version 22.2.1
7
7
  */
8
8
  'use strict';
9
9
 
@@ -165,6 +165,7 @@ const NEWLINE = "\n";
165
165
  const ROUTE_PATTERN = "(/.*)?";
166
166
  const MSG_USE_MIDDLEWARE_REQUIRED =
167
167
  "useMiddleware is required or config.use must be a function";
168
+ const MSG_MIDDLEWARE_REQUIRED = "Expected a function in the parameters";
168
169
  const EXTRACT_PATH_REPLACE = "(?<$1>[^/]+)";
169
170
  const TPL_DIR = "tpl";
170
171
  const INDEX_HTML_FILE = "index.html";
@@ -1163,6 +1164,10 @@ function next(req, res, middleware, immediate = false) {
1163
1164
  */
1164
1165
  const execute = (err) => {
1165
1166
  if (err !== void 0) {
1167
+ if (typeof req.app?.error === FUNCTION) {
1168
+ req.app.error(err, req, res);
1169
+ return;
1170
+ }
1166
1171
  handleError(err, execute);
1167
1172
  } else {
1168
1173
  handleMiddleware(execute);
@@ -1471,10 +1476,15 @@ function validateLogging(logging = {}) {
1471
1476
  const envLogFormat = process.env.WOODLAND_LOG_FORMAT;
1472
1477
  const envLogLevel = process.env.WOODLAND_LOG_LEVEL;
1473
1478
 
1474
- const enabled = logging.enabled ?? (envLogEnabled ?? TRUE) !== FALSE;
1479
+ const enabled =
1480
+ envLogEnabled !== undefined
1481
+ ? envLogEnabled !== FALSE
1482
+ : logging.enabled !== undefined
1483
+ ? logging.enabled !== false
1484
+ : TRUE !== FALSE;
1475
1485
 
1476
- const format = resolveLoggingValue(logging.format, envLogFormat, LOG_FORMAT);
1477
- const level = resolveLoggingValue(logging.level, envLogLevel, INFO);
1486
+ const format = resolveLoggingValue(envLogFormat, logging.format, LOG_FORMAT);
1487
+ const level = resolveLoggingValue(envLogLevel, logging.level, INFO);
1478
1488
 
1479
1489
  if (!VALID_LOG_LEVELS.has(level)) {
1480
1490
  return Object.freeze({ enabled, format, level: INFO });
@@ -1884,6 +1894,7 @@ class Woodland extends node_events.EventEmitter {
1884
1894
  #logger;
1885
1895
  #fileServer;
1886
1896
  #middleware;
1897
+ #error;
1887
1898
 
1888
1899
  /**
1889
1900
  * Creates a new Woodland instance
@@ -1930,6 +1941,7 @@ class Woodland extends node_events.EventEmitter {
1930
1941
  this.#logger = this.#createLogger();
1931
1942
  this.#fileServer = this.#createFileServer();
1932
1943
  this.#middleware = createMiddlewareRegistry(this.#methods, this.#cache);
1944
+ this.#error = null;
1933
1945
 
1934
1946
  this.#setupMiddleware();
1935
1947
  this.#setupErrorHandling();
@@ -2103,6 +2115,10 @@ class Woodland extends node_events.EventEmitter {
2103
2115
  * @returns {Woodland} Returns self for chaining
2104
2116
  */
2105
2117
  #registerMethod(method, ...args) {
2118
+ if (args.length === INT_1 && typeof args[INT_0] === STRING) {
2119
+ throw new TypeError(MSG_MIDDLEWARE_REQUIRED);
2120
+ }
2121
+
2106
2122
  return this.use(...args, method);
2107
2123
  }
2108
2124
 
@@ -2133,7 +2149,7 @@ class Woodland extends node_events.EventEmitter {
2133
2149
  req.host = parsed.hostname;
2134
2150
  req.params = {};
2135
2151
  req.valid = true;
2136
- req.app = { get: (key) => (key === "trust proxy" ? false : undefined) };
2152
+ req.app = this;
2137
2153
 
2138
2154
  const allowString = this.#allows(parsed.pathname);
2139
2155
  const headersBatch = Object.create(null);
@@ -2550,6 +2566,19 @@ class Woodland extends node_events.EventEmitter {
2550
2566
  get logger() {
2551
2567
  return this.#logger;
2552
2568
  }
2569
+
2570
+ /**
2571
+ * Global error handler property
2572
+ * @param {Function} [fn] - Error handler function
2573
+ * @returns {Function|null} Current error handler or null
2574
+ */
2575
+ get error() {
2576
+ return this.#error;
2577
+ }
2578
+
2579
+ set error(fn) {
2580
+ this.#error = typeof fn === FUNCTION ? fn : null;
2581
+ }
2553
2582
  }
2554
2583
 
2555
2584
  /**
package/dist/woodland.js CHANGED
@@ -3,7 +3,7 @@
3
3
  *
4
4
  * @copyright 2026 Jason Mulligan <jason.mulligan@avoidwork.com>
5
5
  * @license BSD-3-Clause
6
- * @version 22.1.1
6
+ * @version 22.2.1
7
7
  */
8
8
  import {STATUS_CODES}from'node:http';import {EventEmitter}from'node:events';import {readFileSync,createReadStream}from'node:fs';import {etag}from'tiny-etag';import {lru}from'tiny-lru';import {precise}from'precise';import {createRequire}from'node:module';import {join,extname,resolve,sep}from'node:path';import {fileURLToPath,URL as URL$1}from'node:url';import mimeDb from'mime-db';import {coerce}from'tiny-coerce';import {Validator}from'jsonschema';import {realpath,stat,readdir}from'node:fs/promises';const __dirname$2 = fileURLToPath(new URL$1(".", import.meta.url));
9
9
  const require$1 = createRequire(import.meta.url);
@@ -148,6 +148,7 @@ const NEWLINE = "\n";
148
148
  const ROUTE_PATTERN = "(/.*)?";
149
149
  const MSG_USE_MIDDLEWARE_REQUIRED =
150
150
  "useMiddleware is required or config.use must be a function";
151
+ const MSG_MIDDLEWARE_REQUIRED = "Expected a function in the parameters";
151
152
  const EXTRACT_PATH_REPLACE = "(?<$1>[^/]+)";
152
153
  const TPL_DIR = "tpl";
153
154
  const INDEX_HTML_FILE = "index.html";
@@ -1140,6 +1141,10 @@ function next(req, res, middleware, immediate = false) {
1140
1141
  */
1141
1142
  const execute = (err) => {
1142
1143
  if (err !== void 0) {
1144
+ if (typeof req.app?.error === FUNCTION) {
1145
+ req.app.error(err, req, res);
1146
+ return;
1147
+ }
1143
1148
  handleError(err, execute);
1144
1149
  } else {
1145
1150
  handleMiddleware(execute);
@@ -1446,10 +1451,15 @@ function validateLogging(logging = {}) {
1446
1451
  const envLogFormat = process.env.WOODLAND_LOG_FORMAT;
1447
1452
  const envLogLevel = process.env.WOODLAND_LOG_LEVEL;
1448
1453
 
1449
- const enabled = logging.enabled ?? (envLogEnabled ?? TRUE) !== FALSE;
1454
+ const enabled =
1455
+ envLogEnabled !== undefined
1456
+ ? envLogEnabled !== FALSE
1457
+ : logging.enabled !== undefined
1458
+ ? logging.enabled !== false
1459
+ : TRUE !== FALSE;
1450
1460
 
1451
- const format = resolveLoggingValue(logging.format, envLogFormat, LOG_FORMAT);
1452
- const level = resolveLoggingValue(logging.level, envLogLevel, INFO);
1461
+ const format = resolveLoggingValue(envLogFormat, logging.format, LOG_FORMAT);
1462
+ const level = resolveLoggingValue(envLogLevel, logging.level, INFO);
1453
1463
 
1454
1464
  if (!VALID_LOG_LEVELS.has(level)) {
1455
1465
  return Object.freeze({ enabled, format, level: INFO });
@@ -1853,6 +1863,7 @@ class Woodland extends EventEmitter {
1853
1863
  #logger;
1854
1864
  #fileServer;
1855
1865
  #middleware;
1866
+ #error;
1856
1867
 
1857
1868
  /**
1858
1869
  * Creates a new Woodland instance
@@ -1899,6 +1910,7 @@ class Woodland extends EventEmitter {
1899
1910
  this.#logger = this.#createLogger();
1900
1911
  this.#fileServer = this.#createFileServer();
1901
1912
  this.#middleware = createMiddlewareRegistry(this.#methods, this.#cache);
1913
+ this.#error = null;
1902
1914
 
1903
1915
  this.#setupMiddleware();
1904
1916
  this.#setupErrorHandling();
@@ -2072,6 +2084,10 @@ class Woodland extends EventEmitter {
2072
2084
  * @returns {Woodland} Returns self for chaining
2073
2085
  */
2074
2086
  #registerMethod(method, ...args) {
2087
+ if (args.length === INT_1 && typeof args[INT_0] === STRING) {
2088
+ throw new TypeError(MSG_MIDDLEWARE_REQUIRED);
2089
+ }
2090
+
2075
2091
  return this.use(...args, method);
2076
2092
  }
2077
2093
 
@@ -2102,7 +2118,7 @@ class Woodland extends EventEmitter {
2102
2118
  req.host = parsed.hostname;
2103
2119
  req.params = {};
2104
2120
  req.valid = true;
2105
- req.app = { get: (key) => (key === "trust proxy" ? false : undefined) };
2121
+ req.app = this;
2106
2122
 
2107
2123
  const allowString = this.#allows(parsed.pathname);
2108
2124
  const headersBatch = Object.create(null);
@@ -2519,6 +2535,19 @@ class Woodland extends EventEmitter {
2519
2535
  get logger() {
2520
2536
  return this.#logger;
2521
2537
  }
2538
+
2539
+ /**
2540
+ * Global error handler property
2541
+ * @param {Function} [fn] - Error handler function
2542
+ * @returns {Function|null} Current error handler or null
2543
+ */
2544
+ get error() {
2545
+ return this.#error;
2546
+ }
2547
+
2548
+ set error(fn) {
2549
+ this.#error = typeof fn === FUNCTION ? fn : null;
2550
+ }
2522
2551
  }
2523
2552
 
2524
2553
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "woodland",
3
- "version": "22.1.1",
3
+ "version": "22.2.1",
4
4
  "description": "Secure HTTP framework for Node.js. Express-compatible with built-in security, no performance tradeoff.",
5
5
  "keywords": [
6
6
  "api",
@@ -58,7 +58,7 @@
58
58
  "prebuild": "npm run clean",
59
59
  "rollup": "rollup --config",
60
60
  "sample": "node sample.js",
61
- "test": "npm run lint && node --test tests/**/*.test.js",
61
+ "test": "npm run lint && WOODLAND_LOG_ENABLED=false node --test tests/**/*.test.js",
62
62
  "test:cli": "node --test tests/unit/cli.test.js",
63
63
  "test:watch": "node --test --watch tests/**/*.test.js"
64
64
  },
@@ -73,7 +73,7 @@
73
73
  "devDependencies": {
74
74
  "auto-changelog": "^2.5.0",
75
75
  "husky": "^9.1.7",
76
- "oxfmt": "^0.49.0",
76
+ "oxfmt": "^0.51.0",
77
77
  "oxlint": "^1.57.0",
78
78
  "rimraf": "^6.1.3",
79
79
  "rollup": "^4.60.0"
@@ -49,6 +49,9 @@ export class Woodland extends EventEmitter {
49
49
  clf: (...args: any[]) => string;
50
50
  }>;
51
51
 
52
+ // Public error handler property (getter/setter)
53
+ error: ((err: Error, req: any, res: any) => void) | null;
54
+
52
55
  constructor(config?: WoodlandConfig);
53
56
 
54
57
  // Public routing methods - with path