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 +18 -1
- package/dist/cli.cjs +1 -1
- package/dist/woodland.cjs +34 -5
- package/dist/woodland.js +34 -5
- package/package.json +3 -3
- package/types/woodland.d.ts +3 -0
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
|
-
- [
|
|
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
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.
|
|
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 =
|
|
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,
|
|
1477
|
-
const level = resolveLoggingValue(logging.level,
|
|
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 =
|
|
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.
|
|
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 =
|
|
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,
|
|
1452
|
-
const level = resolveLoggingValue(logging.level,
|
|
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 =
|
|
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.
|
|
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.
|
|
76
|
+
"oxfmt": "^0.51.0",
|
|
77
77
|
"oxlint": "^1.57.0",
|
|
78
78
|
"rimraf": "^6.1.3",
|
|
79
79
|
"rollup": "^4.60.0"
|
package/types/woodland.d.ts
CHANGED
|
@@ -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
|