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 +56 -359
- package/dist/cli.cjs +3 -1
- package/dist/woodland.cjs +337 -167
- package/dist/woodland.js +338 -168
- package/package.json +8 -8
- package/types/woodland.d.ts +35 -54
package/README.md
CHANGED
|
@@ -3,12 +3,11 @@
|
|
|
3
3
|
|
|
4
4
|
# Woodland
|
|
5
5
|
|
|
6
|
-
|
|
6
|
+
Secure HTTP framework for Node.js. Express-compatible with built-in security, no performance tradeoff.
|
|
7
7
|
|
|
8
8
|
[](https://badge.fury.io/js/woodland)
|
|
9
9
|
[](https://nodejs.org/)
|
|
10
10
|
[](https://opensource.org/licenses/BSD-3-Clause)
|
|
11
|
-
[](https://github.com/avoidwork/woodland/actions)
|
|
12
11
|
[](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
|
-
###
|
|
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
|
-
**
|
|
51
|
+
**Security-First Design:**
|
|
53
52
|
|
|
54
|
-
- **Zero learning curve** - Express-compatible
|
|
55
|
-
- **
|
|
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
|
-
- **
|
|
58
|
-
- **Lightweight** - Minimal dependencies
|
|
59
|
-
- **Dual module support** -
|
|
60
|
-
|
|
61
|
-
**Built-in Features
|
|
62
|
-
|
|
63
|
-
- **
|
|
64
|
-
- **
|
|
65
|
-
- **
|
|
66
|
-
- **
|
|
67
|
-
- **
|
|
68
|
-
- **
|
|
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",
|
|
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
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
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
|
-
|
|
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/
|
|
464
|
-
- [Technical Documentation](https://github.com/avoidwork/woodland/
|
|
465
|
-
- [Code Style Guide](https://github.com/avoidwork/woodland/
|
|
466
|
-
- [Benchmarks](https://github.com/avoidwork/woodland/
|
|
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
|
-
|
|
206
|
+
## Performance
|
|
471
207
|
|
|
472
|
-
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
493
|
-
const app = woodland(); // No origins = deny all CORS
|
|
218
|
+
Woodland implements multiple layers of protection:
|
|
494
219
|
|
|
495
|
-
|
|
496
|
-
|
|
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
|
|
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
|
}
|