dx-server 0.12.1 → 0.13.0-alpha.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 +47 -55
- package/{cjs → lib}/body.d.ts +2 -3
- package/lib/body.js +10 -0
- package/{esm → lib}/bodyHelpers.d.ts +2 -4
- package/lib/bodyHelpers.js +89 -0
- package/{esm → lib}/dx.d.ts +5 -8
- package/lib/dx.js +127 -0
- package/{cjs → lib}/dxHelpers.d.ts +0 -4
- package/{esm → lib}/dxHelpers.js +0 -2
- package/{cjs → lib}/index.d.ts +0 -1
- package/{esm/index.d.ts → lib/index.js} +0 -1
- package/lib/logger.js +56 -0
- package/lib/router.d.ts +42 -0
- package/lib/router.js +43 -0
- package/lib/static.js +22 -0
- package/{cjs → lib}/staticHelpers.d.ts +0 -2
- package/lib/staticHelpers.js +186 -0
- package/{cjs → lib}/stream.d.ts +2 -7
- package/lib/stream.js +90 -0
- package/{esm → lib}/vendors/contentType.js +0 -1
- package/{cjs → lib}/vendors/etag.d.ts +0 -3
- package/lib/vendors/etag.js +104 -0
- package/{cjs → lib}/vendors/fresh.d.ts +2 -2
- package/lib/vendors/fresh.js +95 -0
- package/lib/vendors/mime.d.ts +1 -0
- package/lib/vendors/mime.js +35 -0
- package/{esm → lib}/vendors/mimeDb.js +0 -1
- package/{cjs → lib}/vendors/mimeScore.d.ts +1 -1
- package/lib/vendors/mimeScore.js +45 -0
- package/{cjs → lib}/vendors/onFinished.d.ts +1 -1
- package/lib/vendors/onFinished.js +231 -0
- package/lib/vendors/rangeParser.d.ts +20 -0
- package/lib/vendors/rangeParser.js +121 -0
- package/package.json +10 -19
- package/cjs/body.js +0 -14
- package/cjs/bodyHelpers.d.ts +0 -16
- package/cjs/bodyHelpers.js +0 -101
- package/cjs/connect.d.ts +0 -5
- package/cjs/connect.js +0 -44
- package/cjs/dx.d.ts +0 -46
- package/cjs/dx.js +0 -144
- package/cjs/dxHelpers.js +0 -123
- package/cjs/express.d.ts +0 -4
- package/cjs/express.js +0 -43
- package/cjs/helpers.js +0 -14
- package/cjs/index.js +0 -38
- package/cjs/logger.js +0 -61
- package/cjs/package.json +0 -3
- package/cjs/polyfillWithResolvers.d.ts +0 -1
- package/cjs/polyfillWithResolvers.js +0 -17
- package/cjs/router.js +0 -47
- package/cjs/static.js +0 -27
- package/cjs/staticHelpers.js +0 -195
- package/cjs/stream.js +0 -97
- package/cjs/vendors/contentType.js +0 -92
- package/cjs/vendors/etag.js +0 -136
- package/cjs/vendors/fresh.js +0 -102
- package/cjs/vendors/mime.d.ts +0 -1
- package/cjs/vendors/mime.js +0 -42
- package/cjs/vendors/mimeDb.js +0 -9417
- package/cjs/vendors/mimeScore.js +0 -50
- package/cjs/vendors/onFinished.js +0 -245
- package/cjs/vendors/rangeParser.d.ts +0 -10
- package/cjs/vendors/rangeParser.js +0 -125
- package/esm/body.d.ts +0 -8
- package/esm/body.js +0 -11
- package/esm/bodyHelpers.js +0 -90
- package/esm/connect.d.ts +0 -5
- package/esm/connect.js +0 -40
- package/esm/dx.js +0 -128
- package/esm/dxHelpers.d.ts +0 -49
- package/esm/express.d.ts +0 -4
- package/esm/express.js +0 -35
- package/esm/helpers.js +0 -3
- package/esm/index.js +0 -9
- package/esm/logger.d.ts +0 -3
- package/esm/logger.js +0 -57
- package/esm/polyfillWithResolvers.d.ts +0 -1
- package/esm/polyfillWithResolvers.js +0 -16
- package/esm/router.js +0 -44
- package/esm/static.d.ts +0 -5
- package/esm/static.js +0 -23
- package/esm/staticHelpers.d.ts +0 -18
- package/esm/staticHelpers.js +0 -188
- package/esm/stream.d.ts +0 -12
- package/esm/stream.js +0 -92
- package/esm/vendors/contentType.d.ts +0 -4
- package/esm/vendors/etag.d.ts +0 -10
- package/esm/vendors/etag.js +0 -105
- package/esm/vendors/fresh.d.ts +0 -23
- package/esm/vendors/fresh.js +0 -96
- package/esm/vendors/mime.d.ts +0 -1
- package/esm/vendors/mime.js +0 -35
- package/esm/vendors/mimeDb.d.ts +0 -9413
- package/esm/vendors/mimeScore.d.ts +0 -5
- package/esm/vendors/mimeScore.js +0 -46
- package/esm/vendors/onFinished.d.ts +0 -14
- package/esm/vendors/onFinished.js +0 -241
- package/esm/vendors/rangeParser.d.ts +0 -10
- package/esm/vendors/rangeParser.js +0 -121
- /package/{cjs → lib}/helpers.d.ts +0 -0
- /package/{esm/helpers.d.ts → lib/helpers.js} +0 -0
- /package/{cjs → lib}/logger.d.ts +0 -0
- /package/{cjs → lib}/static.d.ts +0 -0
- /package/{cjs → lib}/vendors/contentType.d.ts +0 -0
- /package/{cjs → lib}/vendors/mimeDb.d.ts +0 -0
package/README.md
CHANGED
|
@@ -10,7 +10,7 @@ A modern, unopinionated, and performant Node.js server framework built on AsyncL
|
|
|
10
10
|
- 🎯 **Elegant API interface** - No need to pass req/res objects through middleware chains
|
|
11
11
|
- 🔗 **Chainable middleware** - Elegant middleware composition with [jchain](https://www.npmjs.com/package/jchain)
|
|
12
12
|
- 🚀 **Context-based architecture** - Access request/response from anywhere using AsyncLocalStorage
|
|
13
|
-
- 🔄 **Express compatible** -
|
|
13
|
+
- 🔄 **Express compatible** - Bridge Express/Connect middleware with a small adapter
|
|
14
14
|
- 📦 **Zero dependencies** - No runtime dependencies, all functionality built-in
|
|
15
15
|
- 🛡️ **Built-in body parsing** - JSON, text, URL-encoded, and raw body parsing with size limits
|
|
16
16
|
- 🗂️ **Static file serving** - Efficient static file handling with ETag, Range, and Last-Modified support
|
|
@@ -127,9 +127,7 @@ new Server().on('request', (req, res) => chain(
|
|
|
127
127
|
)()).listen(3000)
|
|
128
128
|
```
|
|
129
129
|
|
|
130
|
-
### Production-Ready Server
|
|
131
|
-
|
|
132
|
-
This example requires: `npm install express morgan helmet cors`
|
|
130
|
+
### Production-Ready Server
|
|
133
131
|
|
|
134
132
|
```javascript
|
|
135
133
|
import {Server} from 'node:http'
|
|
@@ -139,11 +137,9 @@ import dxServer, {
|
|
|
139
137
|
getReq, getRes,
|
|
140
138
|
getBuffer, getJson, getRaw, getText, getUrlEncoded, getQuery,
|
|
141
139
|
setHtml, setJson, setText, setEmpty, setBuffer, setRedirect, setNodeStream, setWebStream, setFile,
|
|
142
|
-
router,
|
|
140
|
+
router, chainStatic, makeDxContext
|
|
143
141
|
} from 'dx-server'
|
|
144
|
-
import {
|
|
145
|
-
import express from 'express'
|
|
146
|
-
import morgan from 'morgan'
|
|
142
|
+
import {resolve} from 'node:path'
|
|
147
143
|
|
|
148
144
|
// it is best practice to create custom error class for non-system error
|
|
149
145
|
class ServerError extends Error {
|
|
@@ -166,7 +162,6 @@ function requireAuth() {
|
|
|
166
162
|
|
|
167
163
|
const serverChain = chain(
|
|
168
164
|
next => {
|
|
169
|
-
// this is the difference between express and dx-server
|
|
170
165
|
// req, res can be accessed from anywhere via context which uses NodeJS's AsyncLocalStorage under the hood
|
|
171
166
|
getRes().setHeader('Cache-Control', 'no-cache')
|
|
172
167
|
return next() // must return or await
|
|
@@ -182,15 +177,7 @@ const serverChain = chain(
|
|
|
182
177
|
}
|
|
183
178
|
}
|
|
184
179
|
},
|
|
185
|
-
|
|
186
|
-
morgan('common'),
|
|
187
|
-
// cors(),
|
|
188
|
-
),
|
|
189
|
-
await expressApp(app => {// any express feature can be used. This requires express installed, with for e.g., `yarn add express`
|
|
190
|
-
app.set('trust proxy', true)
|
|
191
|
-
if (process.env.NODE_ENV !== 'production') app.set('json spaces', 2)
|
|
192
|
-
app.use('/public', express.static('public'))
|
|
193
|
-
}),
|
|
180
|
+
chainStatic('/public/*', {root: resolve(import.meta.dirname, 'public')}),
|
|
194
181
|
authContext.chain(), // chain context will set the context value to authContext.value in every request
|
|
195
182
|
router.post('/api/*', async ({next}) => {// example of catching error for all /api/* routes
|
|
196
183
|
try {
|
|
@@ -318,12 +305,12 @@ import dxServer, {
|
|
|
318
305
|
setNodeStream, setWebStream, setFile,
|
|
319
306
|
|
|
320
307
|
// Utilities
|
|
321
|
-
router,
|
|
308
|
+
router, chainStatic, makeDxContext,
|
|
309
|
+
|
|
310
|
+
// Logging
|
|
311
|
+
logger, logJson,
|
|
322
312
|
} from 'dx-server'
|
|
323
313
|
|
|
324
|
-
// Express integration (requires express installed)
|
|
325
|
-
import {expressApp, expressRouter} from 'dx-server/express'
|
|
326
|
-
|
|
327
314
|
// Low-level helpers
|
|
328
315
|
import {
|
|
329
316
|
setBufferBodyDefaultOptions,
|
|
@@ -384,7 +371,6 @@ Options:
|
|
|
384
371
|
```
|
|
385
372
|
|
|
386
373
|
#### Middleware Utilities
|
|
387
|
-
- **`connectMiddlewares(...middlewares)`** - Use Connect/Express middleware
|
|
388
374
|
- **`chainStatic(pattern, options)`** - Serve static files
|
|
389
375
|
```javascript
|
|
390
376
|
chainStatic('/public/*', {
|
|
@@ -453,30 +439,38 @@ router.get({
|
|
|
453
439
|
|
|
454
440
|
### Express Integration
|
|
455
441
|
|
|
456
|
-
dx-server
|
|
442
|
+
dx-server does not ship a built-in Express adapter, but Express apps and Connect-style middleware slot into a chain with a one-line adapter. A Connect/Express middleware has the signature `(req, res, next) => void`; jchain expects `next => void | Promise`. Bridge them inline:
|
|
457
443
|
|
|
458
444
|
```javascript
|
|
459
|
-
import
|
|
445
|
+
import chain from 'jchain'
|
|
446
|
+
import {getReq, getRes} from 'dx-server'
|
|
460
447
|
import express from 'express'
|
|
461
|
-
import
|
|
448
|
+
import morgan from 'morgan'
|
|
462
449
|
import helmet from 'helmet'
|
|
450
|
+
import cors from 'cors'
|
|
463
451
|
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
}),
|
|
472
|
-
|
|
473
|
-
// Or use Express router
|
|
474
|
-
expressRouter(router => {
|
|
475
|
-
router.use(cors())
|
|
476
|
-
router.get('/legacy', (req, res) => {
|
|
477
|
-
res.json({message: 'Express route'})
|
|
478
|
-
})
|
|
452
|
+
// Adapt one Connect/Express-style middleware (or a full Express app, which has the
|
|
453
|
+
// same (req, res, next) shape) into a jchain step. Always calls next() so the rest of
|
|
454
|
+
// the chain runs — dx-server handles the case where the response is already ended.
|
|
455
|
+
const fromConnect = mw => next => new Promise((resolve, reject) => {
|
|
456
|
+
mw(getReq(), getRes(), err => {
|
|
457
|
+
if (err) return reject(err)
|
|
458
|
+
next().then(resolve, reject)
|
|
479
459
|
})
|
|
460
|
+
})
|
|
461
|
+
|
|
462
|
+
const app = express()
|
|
463
|
+
app.set('trust proxy', true)
|
|
464
|
+
app.use('/public', express.static('public'))
|
|
465
|
+
app.get('/legacy', (req, res) => res.json({message: 'Express route'}))
|
|
466
|
+
|
|
467
|
+
chain(
|
|
468
|
+
fromConnect(app), // mount the entire Express app first
|
|
469
|
+
fromConnect(morgan('common')),
|
|
470
|
+
fromConnect(helmet()),
|
|
471
|
+
fromConnect(cors()),
|
|
472
|
+
// dx-server routes continue here
|
|
473
|
+
router.get('/', () => setHtml('ok')),
|
|
480
474
|
)
|
|
481
475
|
```
|
|
482
476
|
|
|
@@ -560,20 +554,18 @@ router.post('/api/users', async () => {
|
|
|
560
554
|
```
|
|
561
555
|
|
|
562
556
|
### Security Headers
|
|
563
|
-
Use security middleware:
|
|
557
|
+
Use security middleware via the `fromConnect` adapter shown in [Express Integration](#express-integration):
|
|
564
558
|
|
|
565
559
|
```javascript
|
|
566
560
|
import helmet from 'helmet'
|
|
567
561
|
import cors from 'cors'
|
|
568
562
|
|
|
569
563
|
chain(
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
})
|
|
576
|
-
)
|
|
564
|
+
fromConnect(helmet()),
|
|
565
|
+
fromConnect(cors({
|
|
566
|
+
origin: process.env.ALLOWED_ORIGINS?.split(','),
|
|
567
|
+
credentials: true
|
|
568
|
+
})),
|
|
577
569
|
)
|
|
578
570
|
```
|
|
579
571
|
|
|
@@ -611,16 +603,16 @@ server.on('upgrade', (request, socket, head) => {
|
|
|
611
603
|
```
|
|
612
604
|
|
|
613
605
|
### Rate Limiting
|
|
606
|
+
Using the `fromConnect` adapter from [Express Integration](#express-integration):
|
|
607
|
+
|
|
614
608
|
```javascript
|
|
615
609
|
import rateLimit from 'express-rate-limit'
|
|
616
610
|
|
|
617
611
|
chain(
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
})
|
|
623
|
-
)
|
|
612
|
+
fromConnect(rateLimit({
|
|
613
|
+
windowMs: 15 * 60 * 1000, // 15 minutes
|
|
614
|
+
max: 100 // limit each IP to 100 requests per windowMs
|
|
615
|
+
})),
|
|
624
616
|
)
|
|
625
617
|
```
|
|
626
618
|
|
package/{cjs → lib}/body.d.ts
RENAMED
|
@@ -1,8 +1,7 @@
|
|
|
1
|
-
/// <reference types="node" resolution-mode="require"/>
|
|
2
1
|
import { type BufferBodyOptions } from './bodyHelpers.js';
|
|
3
|
-
export declare const getBuffer: import("./dx.js").Context<Buffer | undefined, [options?: Partial<BufferBodyOptions> | undefined], any, (...np: any[]) => any>;
|
|
2
|
+
export declare const getBuffer: import("./dx.js").Context<Buffer<ArrayBufferLike> | undefined, [options?: Partial<BufferBodyOptions> | undefined], any, (...np: any[]) => any>;
|
|
4
3
|
export declare const getJson: import("./dx.js").Context<any, [options?: Partial<BufferBodyOptions> | undefined], any, (...np: any[]) => any>;
|
|
5
|
-
export declare const getRaw: import("./dx.js").Context<Buffer | undefined, [options?: Partial<BufferBodyOptions> | undefined], any, (...np: any[]) => any>;
|
|
4
|
+
export declare const getRaw: import("./dx.js").Context<Buffer<ArrayBufferLike> | undefined, [options?: Partial<BufferBodyOptions> | undefined], any, (...np: any[]) => any>;
|
|
6
5
|
export declare const getText: import("./dx.js").Context<string | undefined, [options?: Partial<BufferBodyOptions> | undefined], any, (...np: any[]) => any>;
|
|
7
6
|
export declare const getUrlEncoded: import("./dx.js").Context<any, [options?: Partial<BufferBodyOptions> | undefined], any, (...np: any[]) => any>;
|
|
8
7
|
export declare const getQuery: import("./dx.js").Context<any, [options?: Partial<BufferBodyOptions> | undefined], any, (...np: any[]) => any>;
|
package/lib/body.js
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { getReq, makeDxContext } from './dx.js';
|
|
2
|
+
import { bufferFromReq, jsonFromReq, queryFromReq, rawFromReq, textFromReq, urlEncodedFromReq } from './bodyHelpers.js';
|
|
3
|
+
export const getBuffer = makeDxContext((options) => bufferFromReq(getReq(), options));
|
|
4
|
+
export const getJson = makeDxContext((options) => jsonFromReq(getReq(), options));
|
|
5
|
+
export const getRaw = makeDxContext((options) => rawFromReq(getReq(), options));
|
|
6
|
+
export const getText = makeDxContext((options) => textFromReq(getReq(), options));
|
|
7
|
+
export const getUrlEncoded = makeDxContext((options) => urlEncodedFromReq(getReq(), options));
|
|
8
|
+
export const getQuery = makeDxContext((options) => queryFromReq(getReq(), options));
|
|
9
|
+
// to getFile use busboy
|
|
10
|
+
// https://github.com/mscdex/busboy
|
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
/// <reference types="node" resolution-mode="require"/>
|
|
2
|
-
/// <reference types="node" resolution-mode="require"/>
|
|
3
1
|
import { IncomingMessage } from 'node:http';
|
|
4
2
|
export interface BufferBodyOptions {
|
|
5
3
|
bodyLimit: number;
|
|
@@ -7,9 +5,9 @@ export interface BufferBodyOptions {
|
|
|
7
5
|
queryParser?(search: string): any;
|
|
8
6
|
}
|
|
9
7
|
export declare function setBufferBodyDefaultOptions(options: Partial<BufferBodyOptions>): void;
|
|
10
|
-
export declare function bufferFromReq(req: IncomingMessage, options?: Partial<BufferBodyOptions>): Promise<Buffer | undefined>;
|
|
8
|
+
export declare function bufferFromReq(req: IncomingMessage, options?: Partial<BufferBodyOptions>): Promise<Buffer<ArrayBufferLike> | undefined>;
|
|
11
9
|
export declare function jsonFromReq(req: IncomingMessage, options?: Partial<BufferBodyOptions>): Promise<any>;
|
|
12
|
-
export declare function rawFromReq(req: IncomingMessage, options?: Partial<BufferBodyOptions>): Promise<Buffer | undefined>;
|
|
10
|
+
export declare function rawFromReq(req: IncomingMessage, options?: Partial<BufferBodyOptions>): Promise<Buffer<ArrayBufferLike> | undefined>;
|
|
13
11
|
export declare function textFromReq(req: IncomingMessage, options?: Partial<BufferBodyOptions>): Promise<string | undefined>;
|
|
14
12
|
export declare function urlEncodedFromReq(req: IncomingMessage, options?: Partial<BufferBodyOptions>): Promise<any>;
|
|
15
13
|
export declare function urlFromReq(req: IncomingMessage): URL;
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { getContentStream, readStream } from './stream.js';
|
|
2
|
+
import { parseContentType } from './vendors/contentType.js';
|
|
3
|
+
function defaultQueryParser(search) {
|
|
4
|
+
return Object.fromEntries(new URLSearchParams(search)); // support both leading ? and not
|
|
5
|
+
}
|
|
6
|
+
let bodyDefaultOptions = { bodyLimit: 100 << 10 }; // 100kb
|
|
7
|
+
export function setBufferBodyDefaultOptions(options) {
|
|
8
|
+
bodyDefaultOptions = { ...bodyDefaultOptions, ...options };
|
|
9
|
+
}
|
|
10
|
+
export async function bufferFromReq(req, options) {
|
|
11
|
+
const { bodyLimit } = { ...bodyDefaultOptions, ...options };
|
|
12
|
+
/**
|
|
13
|
+
* Check if a request has a request body.
|
|
14
|
+
* A request with a body __must__ either have `transfer-encoding`
|
|
15
|
+
* or `content-length` headers set.
|
|
16
|
+
* http://www.w3.org/Protocols/rfc2616/rfc2616-sec4.html#sec4.3
|
|
17
|
+
*/
|
|
18
|
+
// https://github.com/jshttp/type-is/blob/cdcfe23e9833872e425b0aaf71ca0311373b6116/index.js#L92
|
|
19
|
+
const contentLengthParsed = parseInt(req.headers['content-length'] ?? '', 10);
|
|
20
|
+
if (req.headers['transfer-encoding'] === undefined
|
|
21
|
+
&& isNaN(contentLengthParsed))
|
|
22
|
+
return;
|
|
23
|
+
const contentLength = isNaN(contentLengthParsed) ? undefined : contentLengthParsed;
|
|
24
|
+
// read
|
|
25
|
+
const encoding = (req.headers['content-encoding'] ?? 'identity').toLowerCase();
|
|
26
|
+
const stream = getContentStream(req, encoding);
|
|
27
|
+
return await readStream(stream, {
|
|
28
|
+
length: encoding === 'identity' ? contentLength : undefined,
|
|
29
|
+
limit: bodyLimit,
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
// if content-type is not as expected, return undefined
|
|
33
|
+
function forceGetContentTypeParams(req, expected) {
|
|
34
|
+
const contentTypeRaw = req.headers['content-type'];
|
|
35
|
+
if (!contentTypeRaw)
|
|
36
|
+
return;
|
|
37
|
+
const { mediaType, parameters } = parseContentType(contentTypeRaw);
|
|
38
|
+
if (mediaType !== expected)
|
|
39
|
+
return;
|
|
40
|
+
return parameters;
|
|
41
|
+
}
|
|
42
|
+
function forceGetCharset(req, expected) {
|
|
43
|
+
const parameters = forceGetContentTypeParams(req, expected);
|
|
44
|
+
if (!parameters)
|
|
45
|
+
return;
|
|
46
|
+
// assert charset per RFC 7159 sec 8.1
|
|
47
|
+
const charset = parameters.charset?.toLowerCase() || 'utf-8';
|
|
48
|
+
if (!charset.startsWith('utf-'))
|
|
49
|
+
throw new Error(`unsupported charset "${charset.toUpperCase()}"`);
|
|
50
|
+
return charset;
|
|
51
|
+
}
|
|
52
|
+
export async function jsonFromReq(req, options) {
|
|
53
|
+
const charset = forceGetCharset(req, 'application/json');
|
|
54
|
+
if (!charset)
|
|
55
|
+
return;
|
|
56
|
+
const buffer = await bufferFromReq(req, options);
|
|
57
|
+
if (buffer) {
|
|
58
|
+
const str = buffer.toString(charset);
|
|
59
|
+
return str ? JSON.parse(str) : undefined;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
export async function rawFromReq(req, options) {
|
|
63
|
+
if (!forceGetContentTypeParams(req, 'application/octet-stream'))
|
|
64
|
+
return;
|
|
65
|
+
return await bufferFromReq(req, options);
|
|
66
|
+
}
|
|
67
|
+
export async function textFromReq(req, options) {
|
|
68
|
+
const charset = forceGetCharset(req, 'text/plain');
|
|
69
|
+
if (!charset)
|
|
70
|
+
return;
|
|
71
|
+
const buffer = await bufferFromReq(req, options);
|
|
72
|
+
if (buffer)
|
|
73
|
+
return buffer.toString(charset);
|
|
74
|
+
}
|
|
75
|
+
export async function urlEncodedFromReq(req, options) {
|
|
76
|
+
const charset = forceGetCharset(req, 'application/x-www-form-urlencoded');
|
|
77
|
+
if (!charset)
|
|
78
|
+
return;
|
|
79
|
+
const buffer = await bufferFromReq(req, options);
|
|
80
|
+
if (buffer) {
|
|
81
|
+
return (bodyDefaultOptions.urlEncodedParser ?? options?.urlEncodedParser ?? defaultQueryParser)(buffer.toString(charset));
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
export function urlFromReq(req) {
|
|
85
|
+
return new URL(req.url ?? '', 'https://example.com');
|
|
86
|
+
}
|
|
87
|
+
export function queryFromReq(req, options) {
|
|
88
|
+
return (bodyDefaultOptions.queryParser ?? options?.queryParser ?? defaultQueryParser)(urlFromReq(req).searchParams.toString());
|
|
89
|
+
}
|
package/{esm → lib}/dx.d.ts
RENAMED
|
@@ -1,20 +1,17 @@
|
|
|
1
|
-
/// <reference types="node" resolution-mode="require"/>
|
|
2
|
-
/// <reference types="node" resolution-mode="require"/>
|
|
3
|
-
/// <reference types="node" resolution-mode="require"/>
|
|
4
1
|
import { Readable } from 'node:stream';
|
|
5
2
|
import type { IncomingMessage, ServerResponse } from 'node:http';
|
|
6
3
|
import type { SendFileOptions } from './staticHelpers.js';
|
|
7
|
-
export interface Chainable<
|
|
8
|
-
(next: Next
|
|
4
|
+
export interface Chainable<R = any, Next = (...np: any[]) => any> {
|
|
5
|
+
(next: Next): R;
|
|
9
6
|
}
|
|
10
|
-
export interface Context<T, Params extends any[], R = any, Next = (...np: any[]) => any> {
|
|
7
|
+
export interface Context<T, Params extends any[] = any[], R = any, Next = (...np: any[]) => any> {
|
|
11
8
|
value: Awaited<T>;
|
|
12
9
|
get(req: IncomingMessage): T;
|
|
13
10
|
set(req: IncomingMessage, value: T): void;
|
|
14
11
|
(...params: Params): Promise<T>;
|
|
15
|
-
chain(...params: Params): Chainable<
|
|
12
|
+
chain(...params: Params): Chainable<R, Next>;
|
|
16
13
|
}
|
|
17
|
-
export declare function makeDxContext<T, Params extends any[], R = any, Next = (...np: any[]) => any>(maker: (...params: Params) => T | Promise<T>): Context<T, Params, R, Next>;
|
|
14
|
+
export declare function makeDxContext<T, Params extends any[] = any[], R = any, Next = (...np: any[]) => any>(maker: (...params: Params) => T | Promise<T>): Context<T, Params, R, Next>;
|
|
18
15
|
export declare function dxServer(req: IncomingMessage, res: ServerResponse, options?: {
|
|
19
16
|
jsonBeautify?: boolean;
|
|
20
17
|
disableEtag?: boolean;
|
package/lib/dx.js
ADDED
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import { AsyncLocalStorage } from 'node:async_hooks';
|
|
2
|
+
import { writeRes } from './dxHelpers.js';
|
|
3
|
+
export function makeDxContext(maker) {
|
|
4
|
+
const promiseMap = new WeakMap();
|
|
5
|
+
const valueMap = new WeakMap();
|
|
6
|
+
const context = ((...params) => {
|
|
7
|
+
const req = getReq();
|
|
8
|
+
if (!promiseMap.has(req))
|
|
9
|
+
promiseMap.set(req, (async () => {
|
|
10
|
+
const value = await maker(...params);
|
|
11
|
+
valueMap.set(req, value);
|
|
12
|
+
return value;
|
|
13
|
+
})());
|
|
14
|
+
return promiseMap.get(req);
|
|
15
|
+
});
|
|
16
|
+
Object.defineProperty(context, 'value', {
|
|
17
|
+
get() { return valueMap.get(getReq()); },
|
|
18
|
+
set(value) {
|
|
19
|
+
const req = getReq();
|
|
20
|
+
promiseMap.set(req, Promise.resolve(value));
|
|
21
|
+
valueMap.set(req, value);
|
|
22
|
+
}
|
|
23
|
+
});
|
|
24
|
+
context.chain = ((...params) => (async (next) => {
|
|
25
|
+
await context(...params);
|
|
26
|
+
return next();
|
|
27
|
+
}));
|
|
28
|
+
context.set = (req, value) => {
|
|
29
|
+
promiseMap.set(req, Promise.resolve(value));
|
|
30
|
+
valueMap.set(req, value);
|
|
31
|
+
};
|
|
32
|
+
context.get = req => valueMap.get(req);
|
|
33
|
+
return context;
|
|
34
|
+
}
|
|
35
|
+
const requestStorage = new AsyncLocalStorage();
|
|
36
|
+
const dxContext = makeDxContext(options => ({ ...options }));
|
|
37
|
+
export function dxServer(req, res, options = {}) {
|
|
38
|
+
return async (next) => {
|
|
39
|
+
dxContext.set(req, { ...options });
|
|
40
|
+
const result = await requestStorage.run({ req, res }, next);
|
|
41
|
+
await writeRes(req, res, dxContext.get(req));
|
|
42
|
+
return result;
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
// method: verb
|
|
46
|
+
// url: full url without server, protocol, port.
|
|
47
|
+
// headers: if headers are repeated, they are joined by comma. Header names are lowercased.
|
|
48
|
+
// rawHeaders: list of header name and value in a flat array. Case is preserved.
|
|
49
|
+
export function getReq() { return requestStorage.getStore().req; }
|
|
50
|
+
export function getRes() { return requestStorage.getStore().res; }
|
|
51
|
+
export function setText(text, { status } = {}) {
|
|
52
|
+
const res = getRes();
|
|
53
|
+
const dx = dxContext.value;
|
|
54
|
+
if (status)
|
|
55
|
+
res.statusCode = status;
|
|
56
|
+
dx.data = text;
|
|
57
|
+
dx.type = 'text';
|
|
58
|
+
}
|
|
59
|
+
export function setEmpty({ status } = {}) {
|
|
60
|
+
const res = getRes();
|
|
61
|
+
const dx = dxContext.value;
|
|
62
|
+
if (status)
|
|
63
|
+
res.statusCode = status;
|
|
64
|
+
dx.data = undefined;
|
|
65
|
+
dx.type = 'empty';
|
|
66
|
+
}
|
|
67
|
+
export function setHtml(html, opts = {}) {
|
|
68
|
+
setText(html, opts);
|
|
69
|
+
const dx = dxContext.value;
|
|
70
|
+
dx.type = 'html';
|
|
71
|
+
}
|
|
72
|
+
export function setFile(filePath, options) {
|
|
73
|
+
const dx = dxContext.value;
|
|
74
|
+
dx.data = filePath;
|
|
75
|
+
dx.type = 'file';
|
|
76
|
+
dx.options = options;
|
|
77
|
+
}
|
|
78
|
+
export function setBuffer(buffer, { status } = {}) {
|
|
79
|
+
const res = getRes();
|
|
80
|
+
const dx = dxContext.value;
|
|
81
|
+
if (status)
|
|
82
|
+
res.statusCode = status;
|
|
83
|
+
dx.data = buffer;
|
|
84
|
+
dx.type = 'buffer';
|
|
85
|
+
}
|
|
86
|
+
export function setNodeStream(stream, { status } = {}) {
|
|
87
|
+
const res = getRes();
|
|
88
|
+
const dx = dxContext.value;
|
|
89
|
+
if (status)
|
|
90
|
+
res.statusCode = status;
|
|
91
|
+
dx.data = stream;
|
|
92
|
+
dx.type = 'nodeStream';
|
|
93
|
+
}
|
|
94
|
+
export function setWebStream(stream, { status } = {}) {
|
|
95
|
+
const res = getRes();
|
|
96
|
+
const dx = dxContext.value;
|
|
97
|
+
if (status)
|
|
98
|
+
res.statusCode = status;
|
|
99
|
+
dx.data = stream;
|
|
100
|
+
dx.type = 'webStream';
|
|
101
|
+
}
|
|
102
|
+
export function setJson(json, { status } = {}) {
|
|
103
|
+
const res = getRes();
|
|
104
|
+
if (status)
|
|
105
|
+
res.statusCode = status;
|
|
106
|
+
const dx = dxContext.value;
|
|
107
|
+
dx.data = json;
|
|
108
|
+
dx.type = 'json';
|
|
109
|
+
}
|
|
110
|
+
export function setRedirect(url, status) {
|
|
111
|
+
const res = getRes();
|
|
112
|
+
const dx = dxContext.value;
|
|
113
|
+
res.statusCode = status;
|
|
114
|
+
dx.data = url;
|
|
115
|
+
dx.type = 'redirect';
|
|
116
|
+
}
|
|
117
|
+
// for download, set content-disposition header
|
|
118
|
+
// res.setHeader('Content-disposition', 'attachment; filename=my-movie.MOV')
|
|
119
|
+
// res.setHeader('Content-type', 'video/quicktime')
|
|
120
|
+
// fileStream.pipe(res)
|
|
121
|
+
// or
|
|
122
|
+
// send(req, filePath, options).pipe(res) // which will set content-type, content-length, and other cache related headers like staticHelpers.sendFile
|
|
123
|
+
// implementing this require a strict validation for the type (attachment) and filename.
|
|
124
|
+
// For example: express relies on this
|
|
125
|
+
// https://github.com/jshttp/content-disposition/blob/1037e24e4790273da96645ad250061f39e77968c/index.js#L186
|
|
126
|
+
// because in most applications, users can specify a simple filename which usually doesn't need to be validated.
|
|
127
|
+
// we leave setDownload() implementation for users, for now.
|
|
@@ -1,10 +1,6 @@
|
|
|
1
|
-
/// <reference types="node" resolution-mode="require"/>
|
|
2
|
-
/// <reference types="node" resolution-mode="require"/>
|
|
3
|
-
/// <reference types="node" resolution-mode="require"/>
|
|
4
1
|
import type { IncomingMessage, ServerResponse } from 'node:http';
|
|
5
2
|
import { Readable } from 'node:stream';
|
|
6
3
|
import { type SendFileOptions } from './staticHelpers.js';
|
|
7
|
-
import './polyfillWithResolvers.js';
|
|
8
4
|
export type DxContext = {
|
|
9
5
|
charset?: BufferEncoding;
|
|
10
6
|
jsonBeautify?: boolean;
|
package/{esm → lib}/dxHelpers.js
RENAMED
|
@@ -2,7 +2,6 @@ import { Readable } from 'node:stream';
|
|
|
2
2
|
import { promisify } from 'node:util';
|
|
3
3
|
import { entityTag, isFreshETag } from './vendors/etag.js';
|
|
4
4
|
import { sendFileTrusted } from './staticHelpers.js';
|
|
5
|
-
import './polyfillWithResolvers.js';
|
|
6
5
|
export async function writeRes(req, res, { type, data, charset, jsonBeautify, disableEtag, options }) {
|
|
7
6
|
const setContentType = (contentType) => {
|
|
8
7
|
if (res.headersSent || res.getHeader('content-type'))
|
|
@@ -116,4 +115,3 @@ export async function writeRes(req, res, { type, data, charset, jsonBeautify, di
|
|
|
116
115
|
await promisify(res.end.bind(res))();
|
|
117
116
|
}
|
|
118
117
|
}
|
|
119
|
-
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiZHhIZWxwZXJzLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vc3JjL2R4SGVscGVycy50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFDQSxPQUFPLEVBQUMsUUFBUSxFQUFDLE1BQU0sYUFBYSxDQUFBO0FBQ3BDLE9BQU8sRUFBQyxTQUFTLEVBQUMsTUFBTSxXQUFXLENBQUE7QUFDbkMsT0FBTyxFQUFDLFNBQVMsRUFBRSxXQUFXLEVBQUMsTUFBTSxtQkFBbUIsQ0FBQTtBQUN4RCxPQUFPLEVBQUMsZUFBZSxFQUF1QixNQUFNLG9CQUFvQixDQUFBO0FBRXhFLE9BQU8sNEJBQTRCLENBQUE7QUFvRG5DLE1BQU0sQ0FBQyxLQUFLLFVBQVUsUUFBUSxDQUFDLEdBQW9CLEVBQUUsR0FBbUIsRUFBRSxFQUFDLElBQUksRUFBRSxJQUFJLEVBQUUsT0FBTyxFQUFFLFlBQVksRUFBRSxXQUFXLEVBQUUsT0FBTyxFQUFZO0lBQzdJLE1BQU0sY0FBYyxHQUFHLENBQUMsV0FBbUIsRUFBRSxFQUFFO1FBQzlDLElBQUksR0FBRyxDQUFDLFdBQVcsSUFBSSxHQUFHLENBQUMsU0FBUyxDQUFDLGNBQWMsQ0FBQztZQUFFLE9BQU07UUFDNUQsR0FBRyxDQUFDLFNBQVMsQ0FBQyxjQUFjLEVBQUUsR0FBRyxXQUFXLEdBQUcsT0FBTyxDQUFDLENBQUMsQ0FBQyxhQUFhLE9BQU8sRUFBRSxDQUFDLENBQUMsQ0FBQyxFQUFFLEVBQUUsQ0FBQyxDQUFBO0lBQ3hGLENBQUMsQ0FBQTtJQUNELElBQUksY0FBYyxDQUFBO0lBRWxCLFFBQVEsSUFBSSxFQUFFLENBQUM7UUFDZCxLQUFLLE1BQU07WUFDVixjQUFjLENBQUMsWUFBWSxDQUFDLENBQUE7UUFDN0IsS0FBSyxNQUFNO1lBQ1YsY0FBYyxDQUFDLFdBQVcsQ0FBQyxDQUFBO1lBQzNCLG1CQUFtQjtZQUNuQixjQUFjLEdBQUcsTUFBTSxDQUFDLElBQUksQ0FBQyxJQUFJLElBQUksRUFBRSxFQUFFLE9BQU8sQ0FBQyxD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
|
package/{cjs → lib}/index.d.ts
RENAMED
|
@@ -2,7 +2,6 @@ export { getReq, getRes, setHtml, setNodeStream, setWebStream, setJson, setBuffe
|
|
|
2
2
|
import { dxServer } from './dx.js';
|
|
3
3
|
export { getBuffer, getJson, getRaw, getText, getUrlEncoded, getQuery, } from './body.js';
|
|
4
4
|
export { router } from './router.js';
|
|
5
|
-
export { connectMiddlewares } from './connect.js';
|
|
6
5
|
export { chainStatic } from './static.js';
|
|
7
6
|
export { logJson, default as logger } from './logger.js';
|
|
8
7
|
export default dxServer;
|
|
@@ -2,7 +2,6 @@ export { getReq, getRes, setHtml, setNodeStream, setWebStream, setJson, setBuffe
|
|
|
2
2
|
import { dxServer } from './dx.js';
|
|
3
3
|
export { getBuffer, getJson, getRaw, getText, getUrlEncoded, getQuery, } from './body.js';
|
|
4
4
|
export { router } from './router.js';
|
|
5
|
-
export { connectMiddlewares } from './connect.js';
|
|
6
5
|
export { chainStatic } from './static.js';
|
|
7
6
|
export { logJson, default as logger } from './logger.js';
|
|
8
7
|
export default dxServer;
|
package/lib/logger.js
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { getReq, getRes } from './dx.js';
|
|
2
|
+
import { hrtime } from 'node:process';
|
|
3
|
+
export function logJson(json) {
|
|
4
|
+
console.log(JSON.stringify(json));
|
|
5
|
+
}
|
|
6
|
+
let requestCount = 0;
|
|
7
|
+
export default (log = logJson) => function logger(next) {
|
|
8
|
+
const res = getRes();
|
|
9
|
+
const req = getReq();
|
|
10
|
+
const logId = requestCount++;
|
|
11
|
+
const start = hrtime.bigint();
|
|
12
|
+
const now = new Date(Date.now() + 9 * 60 * 60 * 1000); // jst
|
|
13
|
+
log({
|
|
14
|
+
level: 'info',
|
|
15
|
+
id: logId,
|
|
16
|
+
timestamp: [
|
|
17
|
+
[
|
|
18
|
+
now.getUTCFullYear(),
|
|
19
|
+
String(now.getUTCMonth() + 1).padStart(2, '0'),
|
|
20
|
+
String(now.getUTCDate()).padStart(2, '0'),
|
|
21
|
+
].join('-'),
|
|
22
|
+
[
|
|
23
|
+
String(now.getUTCHours()).padStart(2, '0'),
|
|
24
|
+
String(now.getUTCMinutes()).padStart(2, '0'),
|
|
25
|
+
[String(now.getUTCSeconds()).padStart(2, '0'), String(now.getUTCMilliseconds()).padStart(3, '0')].join('.'),
|
|
26
|
+
].join(':'),
|
|
27
|
+
].join('T'),
|
|
28
|
+
remoteAddress: req.socket.remoteAddress,
|
|
29
|
+
method: req.method,
|
|
30
|
+
url: req.url,
|
|
31
|
+
httpVersion: `HTTP/${req.httpVersion}`,
|
|
32
|
+
headers: process.env.NODE_ENV === 'production'
|
|
33
|
+
? req.headers
|
|
34
|
+
: Object.fromEntries(Object.entries(req.headers).filter(([k]) => [
|
|
35
|
+
'host',
|
|
36
|
+
'referer',
|
|
37
|
+
'referrer',
|
|
38
|
+
'user-agent',
|
|
39
|
+
'x-forwarded-proto',
|
|
40
|
+
'x-forwarded-host',
|
|
41
|
+
'x-forwarded-for',
|
|
42
|
+
].includes(k))),
|
|
43
|
+
});
|
|
44
|
+
res.once('finish', end).once('close', end).once('error', end);
|
|
45
|
+
return next();
|
|
46
|
+
function end() {
|
|
47
|
+
res.off('finish', end).off('close', end).off('error', end);
|
|
48
|
+
const durationNs = hrtime.bigint() - start;
|
|
49
|
+
log({
|
|
50
|
+
level: 'info',
|
|
51
|
+
id: logId,
|
|
52
|
+
duration: Number(durationNs) / 1e6, // ms
|
|
53
|
+
headers: res.getHeaders(),
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
};
|
package/lib/router.d.ts
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { type Chainable } from './dx.js';
|
|
2
|
+
interface URLPatternOptions {
|
|
3
|
+
}
|
|
4
|
+
interface RouteContext {
|
|
5
|
+
matched: URLPatternResult;
|
|
6
|
+
next(): any;
|
|
7
|
+
}
|
|
8
|
+
interface Route {
|
|
9
|
+
(context: RouteContext): any;
|
|
10
|
+
}
|
|
11
|
+
interface Routes {
|
|
12
|
+
[k: string]: Route;
|
|
13
|
+
}
|
|
14
|
+
interface RouterOptions extends URLPatternOptions {
|
|
15
|
+
prefix?: string;
|
|
16
|
+
}
|
|
17
|
+
type Router = {
|
|
18
|
+
patch(routes: Routes, options?: RouterOptions): Chainable;
|
|
19
|
+
patch(pattern: string, route: Route, options?: RouterOptions): Chainable;
|
|
20
|
+
trace(routes: Routes, options?: RouterOptions): Chainable;
|
|
21
|
+
trace(pattern: string, route: Route, options?: RouterOptions): Chainable;
|
|
22
|
+
options(routes: Routes, options?: RouterOptions): Chainable;
|
|
23
|
+
options(pattern: string, route: Route, options?: RouterOptions): Chainable;
|
|
24
|
+
connect(routes: Routes, options?: RouterOptions): Chainable;
|
|
25
|
+
connect(pattern: string, route: Route, options?: RouterOptions): Chainable;
|
|
26
|
+
delete(routes: Routes, options?: RouterOptions): Chainable;
|
|
27
|
+
delete(pattern: string, route: Route, options?: RouterOptions): Chainable;
|
|
28
|
+
put(routes: Routes, options?: RouterOptions): Chainable;
|
|
29
|
+
put(pattern: string, route: Route, options?: RouterOptions): Chainable;
|
|
30
|
+
post(routes: Routes, options?: RouterOptions): Chainable;
|
|
31
|
+
post(pattern: string, route: Route, options?: RouterOptions): Chainable;
|
|
32
|
+
head(routes: Routes, options?: RouterOptions): Chainable;
|
|
33
|
+
head(pattern: string, route: Route, options?: RouterOptions): Chainable;
|
|
34
|
+
get(routes: Routes, options?: RouterOptions): Chainable;
|
|
35
|
+
get(pattern: string, route: Route, options?: RouterOptions): Chainable;
|
|
36
|
+
all(routes: Routes, options?: RouterOptions): Chainable;
|
|
37
|
+
all(pattern: string, route: Route, options?: RouterOptions): Chainable;
|
|
38
|
+
method(method: string, routes: Routes, options?: RouterOptions): Chainable;
|
|
39
|
+
method(method: string, pattern: string, route: Route, options?: RouterOptions): Chainable;
|
|
40
|
+
};
|
|
41
|
+
export declare const router: Router;
|
|
42
|
+
export {};
|