woodland 21.0.9 → 22.0.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
@@ -3,12 +3,11 @@
3
3
 
4
4
  # Woodland
5
5
 
6
- High-performance HTTP framework for Node.js. Express-compatible, optimized for speed.
6
+ Secure HTTP framework for Node.js. Express-compatible with built-in security, no performance tradeoff.
7
7
 
8
8
  [![npm version](https://badge.fury.io/js/woodland.svg)](https://badge.fury.io/js/woodland)
9
9
  [![Node.js Version](https://img.shields.io/node/v/woodland.svg)](https://nodejs.org/)
10
10
  [![License](https://img.shields.io/badge/License-BSD%203--Clause-blue.svg)](https://opensource.org/licenses/BSD-3-Clause)
11
- [![Build Status](https://github.com/avoidwork/woodland/actions/workflows/ci.yml/badge.svg)](https://github.com/avoidwork/woodland/actions)
12
11
  [![Test Coverage](https://img.shields.io/badge/coverage-100%25-brightgreen.svg)](https://github.com/avoidwork/woodland)
13
12
 
14
13
  </div>
@@ -19,7 +18,7 @@ High-performance HTTP framework for Node.js. Express-compatible, optimized for s
19
18
  npm install woodland
20
19
  ```
21
20
 
22
- ### ECMAScript Modules (ESM)
21
+ ### ESM
23
22
 
24
23
  ```javascript
25
24
  import { createServer } from "node:http";
@@ -49,29 +48,24 @@ createServer(app.route).listen(3000, () => console.log("Server running at http:/
49
48
 
50
49
  ## Why Woodland?
51
50
 
52
- **Benefits:**
51
+ **Security-First Design:**
53
52
 
54
- - **Zero learning curve** - Express-compatible middleware and routing patterns
55
- - **Production-ready** - 100% test coverage, battle-tested security
53
+ - **Zero learning curve** - Express-compatible patterns
54
+ - **Built-in security** - CORS, path traversal, XSS prevention (no additional packages)
55
+ - **Secure by default** - CORS deny-all, path traversal blocked, HTML escaping automatic
56
56
  - **TypeScript first** - Full type definitions included
57
- - **Zero config** - Works out of the box, tune when you need to
58
- - **Lightweight** - Minimal dependencies, no bloat
59
- - **Dual module support** - Works with CommonJS and ECMAScript Modules (ESM)
60
-
61
- **Built-in Features (No Additional Packages Needed):**
62
-
63
- - **Automatic `Allow` header** - Every response includes `Allow: GET, POST, OPTIONS` (or relevant methods) for REST compliance
64
- - **CORS support** - Configure allowed origins with automatic preflight handling (OPTIONS)
65
- - **X-Content-Type-Options** - Automatic `nosniff` header on all responses
66
- - **ETag generation** - Built-in ETag support via `tiny-etag` for caching
67
- - **Response timing** - Optional `X-Response-Time` header for monitoring
68
- - **Directory indexing** - Auto-generated HTML directory listings (optional)
69
- - **Range request support** - Automatic handling of `Range` headers for file streaming
70
- - **HEAD request handling** - GET routes automatically support HEAD
71
- - **Error event emission** - Built-in error logging and event hooks
72
- - **Request decoration** - IP extraction, URL parsing, parameter extraction built-in
73
- - **Logger with CLF** - Common Log Format logging with configurable levels
74
- - **Cache control** - LRU caching for routes (configurable size and TTL)
57
+ - **No performance tradeoff** - Security features add ~0.02ms overhead per request
58
+ - **Lightweight** - Minimal dependencies (6 packages)
59
+ - **Dual module support** - CommonJS and ESM
60
+
61
+ **Built-in Security Features:**
62
+
63
+ - **CORS enforcement** - Default deny-all, explicit allowlist required
64
+ - **Path traversal protection** - Resolved paths validated against allowed directories
65
+ - **XSS prevention** - Automatic HTML escaping for user output
66
+ - **IP validation** - Protects against header spoofing
67
+ - **X-Content-Type-Options** - Automatic `nosniff` header
68
+ - **Secure error handling** - No sensitive data exposure
75
69
 
76
70
  ## Common Patterns
77
71
 
@@ -121,27 +115,13 @@ app.always((req, res, next) => {
121
115
  // Route-specific middleware
122
116
  app.get("/protected", authenticate, handler);
123
117
 
124
- // Error handler (register last)
118
+ // Error handler (register last, 4 params)
125
119
  app.use("/(.*)", (error, req, res, next) => {
126
120
  console.error(error);
127
121
  res.error(500);
128
122
  });
129
123
  ```
130
124
 
131
- ### Routing
132
-
133
- ```javascript
134
- // Parameters
135
- app.get("/users/:id", handler);
136
- app.get("/users/:userId/posts/:postId", handler);
137
-
138
- // RegExp patterns
139
- app.get("/files/:path(.*)", handler);
140
-
141
- // Wildcards
142
- app.get("/api/*", apiMiddleware);
143
- ```
144
-
145
125
  ## Configuration
146
126
 
147
127
  ```javascript
@@ -152,349 +132,96 @@ const app = woodland({
152
132
  cacheSize: 1000, // Route cache size
153
133
  cacheTTL: 10000, // Cache TTL in ms
154
134
  charset: "utf-8", // Default charset
155
- corsExpose: "", // CORS expose headers
156
- defaultHeaders: {}, // Additional default headers
157
- digit: 3, // Timing digit precision
158
- indexes: ["index.htm", "index.html"], // Index files
159
135
  logging: {
160
136
  enabled: true,
161
- level: "info", // debug, info, warn, error, crit, alert, emerg, notice
162
- format: "%h %l %u %t \"%r\" %>s %b",
137
+ level: "info",
163
138
  },
164
- silent: false, // Disable server headers
165
139
  time: false, // X-Response-Time header
140
+ silent: false, // Disable server headers
166
141
  });
167
142
  ```
168
143
 
169
- **Environment Variables:**
170
-
171
- ```bash
172
- export WOODLAND_LOG_LEVEL=debug
173
- export WOODLAND_LOG_FORMAT="%h %t \"%r\" %>s %b"
174
- export WOODLAND_LOG_ENABLED=true
175
- ```
176
-
177
144
  ## Response Helpers
178
145
 
179
146
  ```javascript
180
- // JSON
181
147
  res.json({ data: "value" });
182
- res.json({ data: "value" }, 201); // Custom status
183
-
184
- // Text
185
148
  res.send("Hello World");
186
-
187
- // Redirect
188
149
  res.redirect("/new-url");
189
- res.redirect("/new-url", false); // Temporary (307)
190
-
191
- // Headers
192
150
  res.header("x-custom", "value");
193
- res.set({ "x-one": "1", "x-two": "2" });
194
-
195
- // Error
196
- res.error(404);
197
- res.error(500, "Server Error");
198
-
199
- // Status
200
151
  res.status(201);
152
+ res.error(404);
201
153
  ```
202
154
 
203
155
  ## Request Properties
204
156
 
205
157
  ```javascript
206
- app.get("/users/:id", (req, res) => {
207
- req.ip; // Client IP address (from X-Forwarded-For or connection)
208
- req.method; // HTTP method
209
- req.parsed; // URL object with pathname, search, hostname, etc.
210
- req.params; // URL parameters { id: "123" }
211
- req.allow; // Allowed methods string (e.g., "GET, OPTIONS")
212
- req.cors; // CORS enabled for this request
213
- req.corsHost; // Origin host differs from request host
214
- req.body; // Request body (parsed by middleware)
215
- req.headers; // Request headers
216
- req.host; // Hostname
217
- req.valid; // Request validation status
218
- req.exit; // Exit iterator for middleware chain
219
- req.precise; // Precise timer instance (when timing enabled)
220
- });
158
+ req.ip; // Client IP address
159
+ req.params; // URL parameters { id: "123" }
160
+ req.parsed; // URL object
161
+ req.allow; // Allowed methods
162
+ req.cors; // CORS enabled
163
+ req.body; // Request body
164
+ req.host; // Hostname
165
+ req.valid; // Request validity
221
166
  ```
222
167
 
223
168
  ## Event Handlers
224
169
 
225
170
  ```javascript
226
- app.on("connect", (req, res) => {
227
- console.log(`Connection from ${req.ip}`);
228
- });
229
-
230
- app.on("finish", (req, res) => {
231
- analytics.track({ method: req.method, status: res.statusCode });
232
- });
233
-
234
- app.on("error", (req, res, err) => {
235
- console.error(`Error ${res.statusCode}:`, err);
236
- });
237
-
238
- app.on("stream", (req, res) => {
239
- console.log(`Streaming file`);
240
- });
241
- ```
242
-
243
- ## HTTP Methods
244
-
245
- ```javascript
246
- app.get(path, ...handlers); // GET
247
- app.post(path, ...handlers); // POST
248
- app.put(path, ...handlers); // PUT
249
- app.delete(path, ...handlers); // DELETE
250
- app.patch(path, ...handlers); // PATCH
251
- app.options(path, ...handlers); // OPTIONS
252
- app.connect(path, ...handlers); // CONNECT
253
- app.trace(path, ...handlers); // TRACE
254
- app.always(...handlers); // Wildcard (all methods)
255
- ```
256
-
257
- ## Class API (for larger apps)
258
-
259
- ```javascript
260
- import { Woodland } from "woodland";
261
-
262
- class API extends Woodland {
263
- constructor() {
264
- super({ origins: ["https://myapp.com"] });
265
- this.setupRoutes();
266
- }
267
-
268
- setupRoutes() {
269
- this.get("/health", this.healthCheck);
270
- this.get("/users", this.getUsers);
271
- }
272
-
273
- healthCheck(req, res) {
274
- res.json({ status: "ok" });
275
- }
276
-
277
- getUsers(req, res) {
278
- res.json([]);
279
- }
280
- }
281
-
282
- const api = new API();
283
- ```
284
-
285
- ## Factory Pattern
286
-
287
- Woodland uses factories everywhere for testability:
288
-
289
- ```javascript
290
- // Factories return objects with bound methods
291
- import { woodland, createLogger } from "woodland";
292
-
293
- const app = woodland(); // Factory creates Woodland instance
294
- const logger = createLogger({ enabled: true, level: "debug" });
295
-
296
- // Closures provide private state instead of class fields
297
- ```
298
-
299
- ## Middleware Registration
300
-
301
- ```javascript
302
- // Middleware registration pattern: middleware.register(path, ...fn, method)
303
- app.get("/users", getUsers); // Delegates to use()
304
- app.post("/users", createUser); // Delegates to use()
305
- app.always(globalMiddleware); // Wildcard for all methods
306
-
307
- // HEAD routes cannot be registered directly
308
- // GET routes implicitly allow HEAD
309
- app.get("/resource", handler); // Handles both GET and HEAD
310
- ```
311
-
312
- ## Middleware Registry
313
-
314
- ```javascript
315
- // Check if a method is allowed for a URI
316
- const allowed = app.allowed("GET", "/users"); // true
317
-
318
- // Get allowed methods for a URI
319
- const methods = app.allows("/users"); // "GET, OPTIONS"
320
-
321
- // List routes for a method
322
- const routes = app.list("get", "array"); // ["/", "/users", ...]
323
- const routesObj = app.list("get", "object"); // { "/": [...], "/users": [...] }
324
-
325
- // Get route information
326
- const info = app.routes("/users/:id", "GET");
327
- ```
328
-
329
- ## Error Handler Middleware
330
-
331
- Error handlers are automatically detected by their 4 parameters:
332
-
333
- ```javascript
334
- // Error handler (register last)
335
- app.use("/(.*)", (err, req, res, next) => {
336
- console.error(err);
337
- res.error(500, err.message);
338
- });
339
-
340
- // Regular middleware has 3 parameters
341
- app.use((req, res, next) => {
342
- next(); // Continue chain
343
- });
344
- ```
345
-
346
- ## Performance Patterns
347
-
348
- ```javascript
349
- // Use for loops instead of for..of in hot paths
350
- for (let i = 0; i < files.length; i++) {
351
- const file = files[i];
352
- // Process file
353
- }
354
-
355
- // Use Set for O(1) lookups
356
- const ignored = new Set();
357
- if (!ignored.has(fn)) {
358
- // Process
359
- }
360
-
361
- // Use Object.create(null) for null-prototype objects
362
- const headers = Object.create(null);
363
- ```
364
-
365
- ## File Server
366
-
367
- ```javascript
368
- // Serve files from a directory
369
- app.files("/static", "./public");
370
-
371
- // Serve a specific file
372
- await app.serve(req, res, "/path/to/file.txt", "./public");
373
-
374
- // Stream a file
375
- app.stream(req, res, {
376
- path: "./public/file.txt",
377
- etag: "abc123",
378
- charset: "utf-8",
379
- stats: { size: 1024, mtime: new Date() }
380
- });
381
- ```
382
-
383
- ## Logger
384
-
385
- ```javascript
386
- // Access logger
387
- app.logger.log("Custom log message");
388
- app.logger.logError("/path", "GET", "127.0.0.1");
389
- app.logger.logRoute("/path", "GET", "127.0.0.1");
390
- app.logger.logMiddleware("/path", handler);
391
- app.logger.logDecoration("/path", "GET", "127.0.0.1");
392
- app.logger.logServe(req, "Serving file");
393
-
394
- // Common Log Format
395
- app.logger.clf(req, res); // "127.0.0.1 - - [date] \"GET / HTTP/1.1\" 200 1234"
396
-
397
- // Time formatting
398
- app.logger.ms(1234567); // "1.234 ms"
399
- app.logger.timeOffset(300); // "-0500" (timezone offset)
171
+ app.on("connect", (req, res) => console.log(`Connection from ${req.ip}`));
172
+ app.on("finish", (req, res) => analytics.track({ method: req.method, status: res.statusCode }));
173
+ app.on("error", (req, res, err) => console.error(`Error ${res.statusCode}:`, err));
174
+ app.on("stream", (req, res) => console.log(`Streaming file`));
400
175
  ```
401
176
 
402
177
  ## CLI
403
178
 
404
179
  ```bash
405
- # Install globally
406
- npm install -g woodland
407
-
408
180
  # Serve directory (default: http://127.0.0.1:8000)
409
- woodland
181
+ npx woodland
410
182
 
411
183
  # Custom port
412
- woodland --port=3000
184
+ npx woodland --port=3000
413
185
 
414
186
  # Custom IP
415
- woodland --ip=0.0.0.0
416
-
417
- # Verbose logging
418
- woodland --verbose
187
+ npx woodland --ip=0.0.0.0
419
188
  ```
420
189
 
421
190
  ## Testing
422
191
 
423
192
  ```bash
424
- npm test # Run tests (100% coverage)
193
+ npm test # Run tests (100% line coverage)
425
194
  npm run coverage # Generate coverage report
426
195
  npm run benchmark # Performance benchmarks
427
196
  npm run lint # Check linting
428
- npm run fix # Fix linting issues
429
197
  ```
430
198
 
431
- ## Benchmarks
432
-
433
- **Performance comparison (mean of 5 runs):**
434
-
435
- | Framework | Mean (ms) | Ops/sec |
436
- |-----------|-----------|---------|
437
- | Fastify | 0.0863ms | 11,698 |
438
- | **Woodland** | **0.0929ms** | **10,860** |
439
- | Node.js HTTP | 0.1092ms | 9,180 |
440
- | Express | 0.1043ms | 9,591 |
441
-
442
- Woodland is **11.5% faster than Express** and **14.8% faster than raw Node.js HTTP**.
443
-
444
- **Additional Performance Metrics:**
445
-
446
- | Category | Metric | Result |
447
- |----------|--------|--------|
448
- | **Routing** | Method validation (`allows()`) | 4.9M ops/sec |
449
- | | Route resolution (`routes()`) | 1.8M ops/sec |
450
- | **Middleware** | Registration | 43K+ ops/sec |
451
- | | Simple execution | 31K ops/sec |
452
- | **HTTP Server** | JSON responses | 13,225 ops/sec |
453
- | | CRUD operations | 10-14K ops/sec |
454
- | **File Serving** | Small files (< 1KB) | 44K ops/sec |
455
- | | Streaming with ETags | 370K ops/sec |
456
- | **Capacity** | Single instance (JSON API) | 5,000-7,000 RPS |
457
- | | Cluster (10 instances) | 50,000-70,000 RPS |
458
-
459
- [Full benchmark details](https://github.com/avoidwork/woodland/blob/main/docs/BENCHMARKS.md)
460
-
461
199
  ## Documentation
462
200
 
463
- - [API Reference](https://github.com/avoidwork/woodland/blob/main/docs/API.md) - Complete method documentation
464
- - [Technical Documentation](https://github.com/avoidwork/woodland/blob/main/docs/TECHNICAL_DOCUMENTATION.md) - Architecture, OWASP security, internals
465
- - [Code Style Guide](https://github.com/avoidwork/woodland/blob/main/docs/CODE_STYLE_GUIDE.md) - Conventions and best practices
466
- - [Benchmarks](https://github.com/avoidwork/woodland/blob/main/docs/BENCHMARKS.md) - Performance testing results
467
-
468
- ## Security
201
+ - [API Reference](https://github.com/avoidwork/woodland/tree/master/docs/API.md) - Complete method documentation
202
+ - [Technical Documentation](https://github.com/avoidwork/woodland/tree/master/docs/TECHNICAL_DOCUMENTATION.md) - Architecture, OWASP security, internals
203
+ - [Code Style Guide](https://github.com/avoidwork/woodland/tree/master/docs/CODE_STYLE_GUIDE.md) - Conventions and best practices
204
+ - [Benchmarks](https://github.com/avoidwork/woodland/tree/master/docs/BENCHMARKS.md) - Performance testing results
469
205
 
470
- **Automatic Protection:**
206
+ ## Performance
471
207
 
472
- - Injection prevention - Input validation, HTML escaping
473
- - Path traversal protection - Secure file access
474
- - CORS enforcement - Origin validation
475
- - Secure defaults - Safe error handling
208
+ Woodland delivers **enterprise-grade security without sacrificing performance**. Security features add minimal overhead (~0.02ms per request).
476
209
 
477
- **Security Best Practices:**
210
+ | Framework | Security Approach | Mean Response Time |
211
+ |-----------|------------------|-------------------|
212
+ | Fastify | Requires plugins | 0.1491ms |
213
+ | **Woodland** | **Built-in** | **0.1866ms** |
214
+ | Express | Requires middleware | 0.1956ms |
478
215
 
479
- ```javascript
480
- // Always validate IP addresses before use
481
- import { isValidIP } from "woodland";
482
-
483
- if (isValidIP(req.ip)) {
484
- // Safe to use
485
- }
486
-
487
- // Escape HTML in dynamic content
488
- import { escapeHtml } from "woodland";
489
-
490
- const safeOutput = escapeHtml(userInput);
216
+ ## Security
491
217
 
492
- // Deny CORS by default (empty origins array)
493
- const app = woodland(); // No origins = deny all CORS
218
+ Woodland implements multiple layers of protection:
494
219
 
495
- // Block path traversal in file serving
496
- app.files("/static", "./public"); // Automatically blocks ../ attempts
497
- ```
220
+ 1. **CORS Validation** - Default deny-all policy
221
+ 2. **Path Traversal Protection** - Resolved paths validated
222
+ 3. **Input Validation** - IP addresses validated, URLs parsed securely
223
+ 4. **Output Encoding** - HTML escaping automatic
224
+ 5. **Secure Error Handling** - No internal paths exposed
498
225
 
499
226
  **Production Setup:**
500
227
 
@@ -506,43 +233,13 @@ app.always(helmet());
506
233
  app.always(rateLimit({ windowMs: 15 * 60 * 1000, max: 100 }));
507
234
  ```
508
235
 
509
- ## Troubleshooting
510
-
511
- **CORS blocked?**
512
-
513
- ```javascript
514
- const app = woodland({ origins: ["https://myapp.com"] });
515
- ```
516
-
517
- **Route not matching?**
518
-
519
- ```javascript
520
- // Check trailing slashes - be consistent
521
- app.get("/users/:id", handler); // ✅
522
- ```
523
-
524
- **High memory?**
525
-
526
- ```javascript
527
- const app = woodland({ cacheSize: 100, cacheTTL: 60000 });
528
- ```
529
-
530
236
  ## License
531
237
 
532
238
  Copyright (c) 2026 Jason Mulligan
533
239
 
534
240
  Licensed under the **BSD-3-Clause** license.
535
241
 
536
- ## Contributing
537
-
538
- 1. Fork the repository
539
- 2. Create a feature branch
540
- 3. Add tests for new functionality
541
- 4. Ensure all tests pass (`npm test`)
542
- 5. Submit a pull request
543
-
544
242
  ## Support
545
243
 
546
244
  - **Issues**: [GitHub Issues](https://github.com/avoidwork/woodland/issues)
547
- - **Documentation**: [GitHub Wiki](https://github.com/avoidwork/woodland/tree/master/docs)
548
245
  - **Discussions**: [GitHub Discussions](https://github.com/avoidwork/woodland/discussions)
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 21.0.9
7
+ * @version 22.0.0
8
8
  */
9
9
  'use strict';
10
10
 
@@ -268,6 +268,7 @@ function main(args = process.argv) {
268
268
  app.files();
269
269
  const server = node_http.createServer(app.route);
270
270
  server.listen(portValidation.port, ip);
271
+ /* node:coverage ignore next 6 */
271
272
  server.on("listening", () => {
272
273
  const actualPort = server.address().port;
273
274
  app.logger.log(
@@ -281,6 +282,7 @@ function main(args = process.argv) {
281
282
 
282
283
  // CLI entry point - only run when executed directly
283
284
  const __filename$1 = node_url.fileURLToPath((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('cli.cjs', document.baseURI).href)));
285
+ /* node:coverage ignore next 3 */
284
286
  if (process.argv[1] && process.argv[1] === __filename$1) {
285
287
  main();
286
288
  }