dx-server 0.0.1 → 0.0.3
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 +183 -0
- package/cjs/body.d.ts +30 -0
- package/cjs/body.js +91 -0
- package/cjs/contentType.d.ts +4 -0
- package/cjs/contentType.js +92 -0
- package/cjs/context.d.ts +15 -0
- package/cjs/context.js +27 -0
- package/cjs/error.js +42 -0
- package/cjs/etag.d.ts +7 -0
- package/cjs/etag.js +100 -0
- package/cjs/express.d.ts +49 -0
- package/cjs/express.js +168 -0
- package/cjs/expressApp.d.ts +4 -0
- package/cjs/expressApp.js +44 -0
- package/cjs/file.d.ts +3 -0
- package/cjs/file.js +12 -0
- package/cjs/index.d.ts +6 -0
- package/cjs/index.js +33 -0
- package/cjs/package.json +3 -0
- package/cjs/route.d.ts +38 -0
- package/cjs/route.js +86 -0
- package/cjs/static.d.ts +4 -0
- package/cjs/static.js +60 -0
- package/cjs/stream.d.ts +11 -0
- package/cjs/stream.js +100 -0
- package/esm/body.d.ts +30 -0
- package/esm/body.js +88 -0
- package/esm/contentType.d.ts +4 -0
- package/esm/contentType.js +88 -0
- package/esm/context.d.ts +15 -0
- package/esm/context.js +23 -0
- package/esm/error.js +37 -0
- package/esm/etag.d.ts +7 -0
- package/esm/etag.js +90 -0
- package/esm/express.d.ts +49 -0
- package/esm/express.js +156 -0
- package/esm/expressApp.d.ts +4 -0
- package/esm/expressApp.js +35 -0
- package/esm/file.d.ts +3 -0
- package/esm/file.js +8 -0
- package/esm/index.d.ts +6 -0
- package/esm/index.js +7 -0
- package/esm/route.d.ts +38 -0
- package/esm/route.js +82 -0
- package/esm/static.d.ts +4 -0
- package/esm/static.js +56 -0
- package/esm/stream.d.ts +11 -0
- package/esm/stream.js +92 -0
- package/package.json +32 -5
- package/index.js +0 -1
package/README.md
ADDED
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
# dx-server - modern, unopinionated, and satisfactory server
|
|
2
|
+
|
|
3
|
+
## Install
|
|
4
|
+
```bash
|
|
5
|
+
yarn add dx-server jchain
|
|
6
|
+
```
|
|
7
|
+
|
|
8
|
+
## Usage
|
|
9
|
+
|
|
10
|
+
Check below sample with comment for more details.
|
|
11
|
+
|
|
12
|
+
Sample additionally requires: `yarn install express morgan`
|
|
13
|
+
|
|
14
|
+
```javascript
|
|
15
|
+
import {Server} from 'http'
|
|
16
|
+
import {promisify} from 'util'
|
|
17
|
+
import chain from 'jchain'
|
|
18
|
+
import {
|
|
19
|
+
makeContext, requestContext, responseContext,
|
|
20
|
+
|
|
21
|
+
expressContext, setHtml, setJson,
|
|
22
|
+
|
|
23
|
+
bufferBodyContext,
|
|
24
|
+
jsonBodyContext,
|
|
25
|
+
queryContext,
|
|
26
|
+
rawBodyContext,
|
|
27
|
+
textBodyContext,
|
|
28
|
+
urlencodedBodyContext,
|
|
29
|
+
|
|
30
|
+
router,
|
|
31
|
+
expressApp, expressRouter, chainExpressMiddlewares,
|
|
32
|
+
} from 'dx-server'
|
|
33
|
+
import express from 'express'
|
|
34
|
+
import morgan from 'morgan'
|
|
35
|
+
|
|
36
|
+
// it is best practice to create custom error class for non-system error
|
|
37
|
+
class ServerError extends Error {
|
|
38
|
+
name = 'ServerError'
|
|
39
|
+
|
|
40
|
+
constructor(message, status = 400, code = 'unknown') {
|
|
41
|
+
super(message)
|
|
42
|
+
this.status = status
|
|
43
|
+
this.code = code
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// makeContext is a convenient way to create context
|
|
48
|
+
const authContext = makeContext(() => {
|
|
49
|
+
const req = requestContext.value
|
|
50
|
+
// determine if user is authenticated
|
|
51
|
+
// for e.g.
|
|
52
|
+
if (req.headers.authorization) {
|
|
53
|
+
return {id: 1, name: 'joe'}
|
|
54
|
+
}
|
|
55
|
+
})
|
|
56
|
+
|
|
57
|
+
const requireAuth = () => {
|
|
58
|
+
if (!authContext.value) throw new ServerError('unauthorized', 401, 'unauthorized')
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const serverChain = chain(
|
|
62
|
+
expressContext.chain({jsonBeautify: true}), // allows to use setHtml, setJson, setRaw, setBuffer, setFile, setRedirect, etc.
|
|
63
|
+
bufferBodyContext.chain(), // use raw buffer body as Buffer use bufferBodyContext.value. This is required for jsonBodyContext, urlencodedBodyContext, textBodyContext, rawBodyContext
|
|
64
|
+
jsonBodyContext.chain(), // to get body parsed as json use jsonBodyContext.value. Only available if content-type is application/json
|
|
65
|
+
urlencodedBodyContext.chain(), // to get body parsed as urlencoded use urlencodedBodyContext.value. Only available if content-type is application/x-www-form-urlencoded
|
|
66
|
+
textBodyContext.chain(), // to get body parsed as text use textBodyContext.value. Only available if content-type is text/plain
|
|
67
|
+
rawBodyContext.chain(), // to get body as raw use rawBodyContext.value. Only available if content-type is application/octet-stream
|
|
68
|
+
queryContext.chain(), // to get query params use queryContext.value. Query is parsed via 'qs' package. If no query, return empty object {}
|
|
69
|
+
next => {
|
|
70
|
+
// this is the difference between express and dx-server
|
|
71
|
+
// req, res can be accessed from anywhere via context which uses NodeJS's AsyncLocalStorage under the hood
|
|
72
|
+
responseContext.value.setHeader('Cache-Control', 'no-cache')
|
|
73
|
+
next()
|
|
74
|
+
},
|
|
75
|
+
async next => {
|
|
76
|
+
// global error catching for all following middlewares
|
|
77
|
+
try {
|
|
78
|
+
await next()
|
|
79
|
+
} catch (e) {
|
|
80
|
+
// only app error message should be shown to user
|
|
81
|
+
if (e instanceof ServerError) setHtml(`${e.message} (code: ${e.code})`, {status: e.status})
|
|
82
|
+
else {
|
|
83
|
+
// report system error
|
|
84
|
+
console.error(e)
|
|
85
|
+
setHtml('internal server error (code: internal)', {status: 500})
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
},
|
|
89
|
+
await expressApp(app => {
|
|
90
|
+
// any express feature can be used
|
|
91
|
+
// required express installed, with for e.g., `yarn add express`
|
|
92
|
+
app.set('trust proxy', true)
|
|
93
|
+
if (process.env.NODE_ENV !== 'production') app.set('json spaces', 2)
|
|
94
|
+
}),
|
|
95
|
+
chainExpressMiddlewares(
|
|
96
|
+
morgan('common'), // in future, we will provide native implementation of express middlewares
|
|
97
|
+
// cookies, session, etc.
|
|
98
|
+
// session({
|
|
99
|
+
// secret: '123',
|
|
100
|
+
// resave: false,
|
|
101
|
+
// store: redisStore,
|
|
102
|
+
// saveUninitialized: true,
|
|
103
|
+
// // cookie: { secure: true }
|
|
104
|
+
// }),
|
|
105
|
+
),
|
|
106
|
+
await expressRouter(router => {
|
|
107
|
+
// setup express router
|
|
108
|
+
router.use('/public', express.static('public'))
|
|
109
|
+
}),
|
|
110
|
+
authContext.chain(),
|
|
111
|
+
// example of catching error for all /api/* routes
|
|
112
|
+
router.post({
|
|
113
|
+
async '/api'({next}) {
|
|
114
|
+
try {
|
|
115
|
+
await next()
|
|
116
|
+
} catch (e) {
|
|
117
|
+
// only app error message should be shown to user
|
|
118
|
+
if (e instanceof ServerError) setJson({
|
|
119
|
+
error: e.message,
|
|
120
|
+
code: e.code,
|
|
121
|
+
}, {status: e.status})
|
|
122
|
+
else {
|
|
123
|
+
// report system error
|
|
124
|
+
console.error(e)
|
|
125
|
+
setJson({
|
|
126
|
+
message: 'internal server error',
|
|
127
|
+
code: 'internal'
|
|
128
|
+
}, {status: 500})
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
}, {end: false}), // note: {end: false} is required to match all /api/* routes. This option is passed directly to path-to-regexp
|
|
133
|
+
router.post({
|
|
134
|
+
'/api/sample-public-api'() { // sample POST router
|
|
135
|
+
setJson({name: 'joe'})
|
|
136
|
+
},
|
|
137
|
+
'/api/me'() { // sample private router
|
|
138
|
+
requireAuth()
|
|
139
|
+
setJson({name: authContext.value.name})
|
|
140
|
+
},
|
|
141
|
+
}),
|
|
142
|
+
router.get({ // sample GET router
|
|
143
|
+
'/'() {
|
|
144
|
+
setHtml('ok')
|
|
145
|
+
},
|
|
146
|
+
'/health'() {
|
|
147
|
+
setHtml('ok')
|
|
148
|
+
}
|
|
149
|
+
}),
|
|
150
|
+
router.post({ // api not found router
|
|
151
|
+
'/api'() {
|
|
152
|
+
throw new ServerError('not found', 404, 'not_found')
|
|
153
|
+
}
|
|
154
|
+
}, {end: false}),
|
|
155
|
+
() => { // not found router
|
|
156
|
+
throw new ServerError('not found', 404, 'not_found')
|
|
157
|
+
},
|
|
158
|
+
)
|
|
159
|
+
|
|
160
|
+
const tcpServer = new Server()
|
|
161
|
+
.on('request', async (req, res) => {
|
|
162
|
+
try {
|
|
163
|
+
await chain(
|
|
164
|
+
requestContext.chain(req), // required for most middlewares
|
|
165
|
+
responseContext.chain(res), // required for most middlewares
|
|
166
|
+
serverChain,
|
|
167
|
+
)()
|
|
168
|
+
} catch (e) {
|
|
169
|
+
console.error(e)
|
|
170
|
+
}
|
|
171
|
+
})
|
|
172
|
+
|
|
173
|
+
const port = +(process.env.PORT ?? 3000)
|
|
174
|
+
await promisify(tcpServer.listen.bind(tcpServer))(port)
|
|
175
|
+
console.log(`server is listening at ${port}`)
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
## TODO
|
|
179
|
+
Until these middlewares are available as native dx-server middlewares, express middlewares can be used with `expressApp()`
|
|
180
|
+
- [ ] native static file serve, like 'static-serve'
|
|
181
|
+
- [ ] logger like morgan
|
|
182
|
+
- [ ] cookie, session
|
|
183
|
+
- [ ] cors
|
package/cjs/body.d.ts
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/// <reference types="node" resolution-mode="require"/>
|
|
2
|
+
/// <reference types="qs" />
|
|
3
|
+
export declare const bufferBodyContext: {
|
|
4
|
+
readonly value: Buffer | undefined;
|
|
5
|
+
chain(params_0?: {
|
|
6
|
+
limit?: number | undefined;
|
|
7
|
+
} | undefined): <V>(next: () => V) => Promise<any>;
|
|
8
|
+
};
|
|
9
|
+
export declare const jsonBodyContext: {
|
|
10
|
+
readonly value: any;
|
|
11
|
+
chain(): <V>(next: () => V) => Promise<any>;
|
|
12
|
+
};
|
|
13
|
+
export declare const rawBodyContext: {
|
|
14
|
+
readonly value: Buffer | undefined;
|
|
15
|
+
chain(): <V>(next: () => V) => Promise<any>;
|
|
16
|
+
};
|
|
17
|
+
export declare const textBodyContext: {
|
|
18
|
+
readonly value: string | undefined;
|
|
19
|
+
chain(): <V>(next: () => V) => Promise<any>;
|
|
20
|
+
};
|
|
21
|
+
export declare const urlencodedBodyContext: {
|
|
22
|
+
readonly value: import("qs").ParsedQs | undefined;
|
|
23
|
+
chain(params_0?: {
|
|
24
|
+
simplify?: boolean | undefined;
|
|
25
|
+
} | undefined): <V>(next: () => V) => Promise<any>;
|
|
26
|
+
};
|
|
27
|
+
export declare const queryContext: {
|
|
28
|
+
readonly value: import("qs").ParsedQs;
|
|
29
|
+
chain(): <V>(next: () => V) => Promise<any>;
|
|
30
|
+
};
|
package/cjs/body.js
ADDED
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.queryContext = exports.urlencodedBodyContext = exports.textBodyContext = exports.rawBodyContext = exports.jsonBodyContext = exports.bufferBodyContext = void 0;
|
|
4
|
+
const stream_js_1 = require("./stream.js");
|
|
5
|
+
const qs_1 = require("qs");
|
|
6
|
+
const contentType_js_1 = require("./contentType.js");
|
|
7
|
+
const context_js_1 = require("./context.js");
|
|
8
|
+
exports.bufferBodyContext = (0, context_js_1.makeContext)(async ({ limit = 100 << 10, // 100kb
|
|
9
|
+
} = {}) => {
|
|
10
|
+
const req = context_js_1.requestContext.value;
|
|
11
|
+
/**
|
|
12
|
+
* Check if a request has a request body.
|
|
13
|
+
* A request with a body __must__ either have `transfer-encoding`
|
|
14
|
+
* or `content-length` headers set.
|
|
15
|
+
* http://www.w3.org/Protocols/rfc2616/rfc2616-sec4.html#sec4.3
|
|
16
|
+
*/
|
|
17
|
+
// https://github.com/jshttp/type-is/blob/cdcfe23e9833872e425b0aaf71ca0311373b6116/index.js#L92
|
|
18
|
+
const contentLengthParsed = parseInt(req.headers['content-length'] ?? '', 10);
|
|
19
|
+
if (req.headers['transfer-encoding'] === undefined
|
|
20
|
+
&& isNaN(contentLengthParsed))
|
|
21
|
+
return;
|
|
22
|
+
const contentLength = isNaN(contentLengthParsed) ? undefined : contentLengthParsed;
|
|
23
|
+
// read
|
|
24
|
+
const encoding = (req.headers['content-encoding'] ?? 'identity').toLowerCase();
|
|
25
|
+
const stream = (0, stream_js_1.getContentStream)(req, encoding);
|
|
26
|
+
return await (0, stream_js_1.readStream)(stream, {
|
|
27
|
+
length: encoding === 'identity' ? contentLength : undefined,
|
|
28
|
+
limit,
|
|
29
|
+
});
|
|
30
|
+
});
|
|
31
|
+
const forceGetContentTypeParams = (expected) => {
|
|
32
|
+
const req = context_js_1.requestContext.value;
|
|
33
|
+
const contentTypeRaw = req.headers['content-type'];
|
|
34
|
+
if (!contentTypeRaw)
|
|
35
|
+
return;
|
|
36
|
+
const { mediaType, parameters } = (0, contentType_js_1.parseContentType)(contentTypeRaw);
|
|
37
|
+
if (mediaType !== expected)
|
|
38
|
+
return;
|
|
39
|
+
return parameters;
|
|
40
|
+
};
|
|
41
|
+
const forceGetCharset = (expected) => {
|
|
42
|
+
const parameters = forceGetContentTypeParams(expected);
|
|
43
|
+
if (!parameters)
|
|
44
|
+
return;
|
|
45
|
+
// assert charset per RFC 7159 sec 8.1
|
|
46
|
+
const charset = parameters.charset?.toLowerCase() || 'utf-8';
|
|
47
|
+
if (!charset.startsWith('utf-'))
|
|
48
|
+
throw new Error(`unsupported charset "${charset.toUpperCase()}"`);
|
|
49
|
+
return charset;
|
|
50
|
+
};
|
|
51
|
+
exports.jsonBodyContext = (0, context_js_1.makeContext)(async () => {
|
|
52
|
+
const charset = forceGetCharset('application/json');
|
|
53
|
+
if (!charset)
|
|
54
|
+
return;
|
|
55
|
+
const buffer = exports.bufferBodyContext.value;
|
|
56
|
+
if (buffer) {
|
|
57
|
+
const str = buffer.toString(charset);
|
|
58
|
+
return str ? JSON.parse(str) : undefined;
|
|
59
|
+
}
|
|
60
|
+
});
|
|
61
|
+
exports.rawBodyContext = (0, context_js_1.makeContext)(async () => {
|
|
62
|
+
if (!forceGetContentTypeParams('application/octet-stream'))
|
|
63
|
+
return;
|
|
64
|
+
return exports.bufferBodyContext.value;
|
|
65
|
+
});
|
|
66
|
+
exports.textBodyContext = (0, context_js_1.makeContext)(async () => {
|
|
67
|
+
const charset = forceGetCharset('text/plain');
|
|
68
|
+
if (!charset)
|
|
69
|
+
return;
|
|
70
|
+
const buffer = exports.bufferBodyContext.value;
|
|
71
|
+
if (buffer)
|
|
72
|
+
return buffer.toString(charset);
|
|
73
|
+
});
|
|
74
|
+
exports.urlencodedBodyContext = (0, context_js_1.makeContext)(async ({ simplify } = {}) => {
|
|
75
|
+
const charset = forceGetCharset('application/x-www-form-urlencoded');
|
|
76
|
+
if (!charset)
|
|
77
|
+
return;
|
|
78
|
+
const buffer = exports.bufferBodyContext.value;
|
|
79
|
+
if (buffer) {
|
|
80
|
+
const str = buffer.toString(charset);
|
|
81
|
+
return simplify
|
|
82
|
+
? Object.fromEntries(new URLSearchParams(str))
|
|
83
|
+
: (0, qs_1.parse)(str);
|
|
84
|
+
}
|
|
85
|
+
});
|
|
86
|
+
exports.queryContext = (0, context_js_1.makeContext)(() => {
|
|
87
|
+
const req = context_js_1.requestContext.value;
|
|
88
|
+
const query = req.url?.split('?', 2)?.[1];
|
|
89
|
+
return query ? (0, qs_1.parse)(query) : {};
|
|
90
|
+
});
|
|
91
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiYm9keS5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uL3NyYy9ib2R5LnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiI7OztBQUFBLDJDQUF3RDtBQUN4RCwyQkFBd0I7QUFDeEIscURBQWlEO0FBQ2pELDZDQUF3RDtBQUUzQyxRQUFBLGlCQUFpQixHQUFHLElBQUEsd0JBQVcsRUFBQyxLQUFLLEVBQ2pELEVBQ0MsS0FBSyxHQUFHLEdBQUcsSUFBSSxFQUFFLEVBQUUsUUFBUTtLQUd4QixFQUFFLEVBQ0wsRUFBRTtJQUNILE1BQU0sR0FBRyxHQUFHLDJCQUFjLENBQUMsS0FBSyxDQUFBO0lBRWhDOzs7OztPQUtHO0lBQ0YsK0ZBQStGO0lBQ2hHLE1BQU0sbUJBQW1CLEdBQUcsUUFBUSxDQUFDLEdBQUcsQ0FBQyxPQUFPLENBQUMsZ0JBQWdCLENBQUMsSUFBSSxFQUFFLEVBQUUsRUFBRSxDQUFDLENBQUE7SUFDN0UsSUFDQyxHQUFHLENBQUMsT0FBTyxDQUFDLG1CQUFtQixDQUFDLEtBQUssU0FBUztXQUMzQyxLQUFLLENBQUMsbUJBQW1CLENBQUM7UUFDNUIsT0FBTTtJQUNSLE1BQU0sYUFBYSxHQUFHLEtBQUssQ0FBQyxtQkFBbUIsQ0FBQyxDQUFDLENBQUMsQ0FBQyxTQUFTLENBQUMsQ0FBQyxDQUFDLG1CQUFtQixDQUFBO0lBRWxGLE9BQU87SUFDUCxNQUFNLFFBQVEsR0FBRyxDQUFDLEdBQUcsQ0FBQyxPQUFPLENBQUMsa0JBQWtCLENBQUMsSUFBSSxVQUFVLENBQUMsQ0FBQyxXQUFXLEVBQUUsQ0FBQTtJQUM5RSxNQUFNLE1BQU0sR0FBRyxJQUFBLDRCQUFnQixFQUFDLEdBQUcsRUFBRSxRQUFRLENBQUMsQ0FBQTtJQUM5QyxPQUFPLE1BQU0sSUFBQSxzQkFBVSxFQUN0QixNQUFNLEVBQ047UUFDQyxNQUFNLEVBQUUsUUFBUSxLQUFLLFVBQVUsQ0FBQyxDQUFDLENBQUMsYUFBYSxDQUFDLENBQUMsQ0FBQyxTQUFTO1FBQzNELEtBQUs7S0FD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
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.parseContentType = void 0;
|
|
4
|
+
// https://github.com/jshttp/content-type/blob/d02574e9640bd4370f148c767b1b877b5a300070/index.js#L106
|
|
5
|
+
/**
|
|
6
|
+
* RegExp to match type in RFC 7231 sec 3.1.1.1
|
|
7
|
+
*
|
|
8
|
+
* media-type = type "/" subtype
|
|
9
|
+
* type = token
|
|
10
|
+
* subtype = token
|
|
11
|
+
*/
|
|
12
|
+
const TYPE_REGEXP = /^[!#$%&'*+.^_`|~0-9A-Za-z-]+\/[!#$%&'*+.^_`|~0-9A-Za-z-]+$/;
|
|
13
|
+
/**
|
|
14
|
+
* RegExp to match *( ";" parameter ) in RFC 7231 sec 3.1.1.1
|
|
15
|
+
*
|
|
16
|
+
* parameter = token "=" ( token / quoted-string )
|
|
17
|
+
* token = 1*tchar
|
|
18
|
+
* tchar = "!" / "#" / "$" / "%" / "&" / "'" / "*"
|
|
19
|
+
* / "+" / "-" / "." / "^" / "_" / "`" / "|" / "~"
|
|
20
|
+
* / DIGIT / ALPHA
|
|
21
|
+
* ; any VCHAR, except delimiters
|
|
22
|
+
* quoted-string = DQUOTE *( qdtext / quoted-pair ) DQUOTE
|
|
23
|
+
* qdtext = HTAB / SP / %x21 / %x23-5B / %x5D-7E / obs-text
|
|
24
|
+
* obs-text = %x80-FF
|
|
25
|
+
* quoted-pair = "\" ( HTAB / SP / VCHAR / obs-text )
|
|
26
|
+
*/
|
|
27
|
+
const PARAM_REGEXP = /; *([!#$%&'*+.^_`|~0-9A-Za-z-]+) *= *("(?:[\u000b\u0020\u0021\u0023-\u005b\u005d-\u007e\u0080-\u00ff]|\\[\u000b\u0020-\u00ff])*"|[!#$%&'*+.^_`|~0-9A-Za-z-]+) */g; // eslint-disable-line no-control-regex
|
|
28
|
+
const TEXT_REGEXP = /^[\u000b\u0020-\u007e\u0080-\u00ff]+$/; // eslint-disable-line no-control-regex
|
|
29
|
+
const TOKEN_REGEXP = /^[!#$%&'*+.^_`|~0-9A-Za-z-]+$/;
|
|
30
|
+
/**
|
|
31
|
+
* RegExp to match quoted-pair in RFC 7230 sec 3.2.6
|
|
32
|
+
*
|
|
33
|
+
* quoted-pair = "\" ( HTAB / SP / VCHAR / obs-text )
|
|
34
|
+
* obs-text = %x80-FF
|
|
35
|
+
*/
|
|
36
|
+
const QESC_REGEXP = /\\([\u000b\u0020-\u00ff])/g; // eslint-disable-line no-control-regex
|
|
37
|
+
function parseContentType(header) {
|
|
38
|
+
let index = header.indexOf(';');
|
|
39
|
+
const mediaType = index !== -1
|
|
40
|
+
? header.slice(0, index).trim()
|
|
41
|
+
: header.trim();
|
|
42
|
+
if (!TYPE_REGEXP.test(mediaType))
|
|
43
|
+
throw new TypeError(`invalid media type: ${mediaType}`);
|
|
44
|
+
const parameters = {};
|
|
45
|
+
// parse parameters
|
|
46
|
+
if (index !== -1) {
|
|
47
|
+
let key;
|
|
48
|
+
let match;
|
|
49
|
+
let value;
|
|
50
|
+
const regexp = new RegExp(PARAM_REGEXP);
|
|
51
|
+
regexp.lastIndex = index;
|
|
52
|
+
while ((match = regexp.exec(header))) {
|
|
53
|
+
if (match.index !== index)
|
|
54
|
+
throw new TypeError('invalid parameter format');
|
|
55
|
+
index += match[0].length;
|
|
56
|
+
key = match[1].toLowerCase();
|
|
57
|
+
value = match[2];
|
|
58
|
+
if (value.charCodeAt(0) === 0x22 /* " */) {
|
|
59
|
+
// remove quotes
|
|
60
|
+
value = value.slice(1, -1);
|
|
61
|
+
// remove escapes
|
|
62
|
+
if (value.indexOf('\\') !== -1)
|
|
63
|
+
value = value.replace(QESC_REGEXP, '$1');
|
|
64
|
+
}
|
|
65
|
+
parameters[key] = value;
|
|
66
|
+
}
|
|
67
|
+
if (index !== header.length)
|
|
68
|
+
throw new TypeError('invalid parameter format');
|
|
69
|
+
}
|
|
70
|
+
return { mediaType, parameters };
|
|
71
|
+
}
|
|
72
|
+
exports.parseContentType = parseContentType;
|
|
73
|
+
/**
|
|
74
|
+
* RegExp to match chars that must be quoted-pair in RFC 7230 sec 3.2.6
|
|
75
|
+
*/
|
|
76
|
+
const QUOTE_REGEXP = /([\\"])/g;
|
|
77
|
+
function qstring(str) {
|
|
78
|
+
// no need to quote tokens
|
|
79
|
+
if (TOKEN_REGEXP.test(str))
|
|
80
|
+
return str;
|
|
81
|
+
if (str.length > 0 && !TEXT_REGEXP.test(str))
|
|
82
|
+
throw new TypeError(`invalid parameter value: ${str}`);
|
|
83
|
+
return `"${str.replace(QUOTE_REGEXP, '\\$1')}"`;
|
|
84
|
+
}
|
|
85
|
+
function formatContentType({ mediaType, parameters }) {
|
|
86
|
+
if (!mediaType || !TYPE_REGEXP.test(mediaType))
|
|
87
|
+
throw new TypeError(`invalid type: ${mediaType}`);
|
|
88
|
+
return `${mediaType}${parameters
|
|
89
|
+
? Object.keys(parameters).sort().map(key => `; ${key}=${qstring(parameters[key])}`).join('')
|
|
90
|
+
: ''}`;
|
|
91
|
+
}
|
|
92
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY29udGVudFR5cGUuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi9zcmMvY29udGVudFR5cGUudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6Ijs7O0FBQ0EscUdBQXFHO0FBQ3JHOzs7Ozs7R0FNRztBQUNILE1BQU0sV0FBVyxHQUFHLDREQUE0RCxDQUFBO0FBQ2hGOzs7Ozs7Ozs7Ozs7O0dBYUc7QUFDSCxNQUFNLFlBQVksR0FBRyxrS0FBa0ssQ0FBQSxDQUFDLHVDQUF1QztBQUMvTixNQUFNLFdBQVcsR0FBRyx1Q0FBdUMsQ0FBQSxDQUFDLHVDQUF1QztBQUNuRyxNQUFNLFlBQVksR0FBRywrQkFBK0IsQ0FBQTtBQUNwRDs7Ozs7R0FLRztBQUNILE1BQU0sV0FBVyxHQUFHLDRCQUE0QixDQUFBLENBQUMsdUNBQXVDO0FBRXhGLFNBQWdCLGdCQUFnQixDQUFFLE1BQWM7SUFDL0MsSUFBSSxLQUFLLEdBQUcsTUFBTSxDQUFDLE9BQU8sQ0FBQyxHQUFHLENBQUMsQ0FBQTtJQUMvQixNQUFNLFNBQVMsR0FBRyxLQUFLLEtBQUssQ0FBQyxDQUFDO1FBQzdCLENBQUMsQ0FBQyxNQUFNLENBQUMsS0FBSyxDQUFDLENBQUMsRUFBRSxLQUFLLENBQUMsQ0FBQyxJQUFJLEVBQUU7UUFDL0IsQ0FBQyxDQUFDLE1BQU0sQ0FBQyxJQUFJLEVBQUUsQ0FBQTtJQUVoQixJQUFJLENBQUMsV0FBVyxDQUFDLElBQUksQ0FBQyxTQUFTLENBQUM7UUFBRSxNQUFNLElBQUksU0FBUyxDQUFDLHVCQUF1QixTQUFTLEVBQUUsQ0FBQyxDQUFBO0lBQ3pGLE1BQU0sVUFBVSxHQUEyQixFQUFFLENBQUE7SUFFN0MsbUJBQW1CO0lBQ25CLElBQUksS0FBSyxLQUFLLENBQUMsQ0FBQyxFQUFFO1FBQ2pCLElBQUksR0FBRyxDQUFBO1FBQ1AsSUFBSSxLQUFLLENBQUE7UUFDVCxJQUFJLEtBQUssQ0FBQTtRQUVULE1BQU0sTUFBTSxHQUFHLElBQUksTUFBTSxDQUFDLFlBQVksQ0FB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
|
package/cjs/context.d.ts
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/// <reference types="node" resolution-mode="require"/>
|
|
2
|
+
import { Promisable } from 'type-fest';
|
|
3
|
+
import { IncomingMessage, ServerResponse } from 'node:http';
|
|
4
|
+
export declare const makeContext: <T, Params extends any[] = unknown[]>(maker: (...params: Params) => Promisable<T>, end?: (result: any, value: T) => any) => {
|
|
5
|
+
readonly value: T;
|
|
6
|
+
chain(...params: Params): <V>(next: () => V) => Promise<any>;
|
|
7
|
+
};
|
|
8
|
+
export declare const requestContext: {
|
|
9
|
+
readonly value: IncomingMessage;
|
|
10
|
+
chain(params_0: IncomingMessage): <V>(next: () => V) => Promise<any>;
|
|
11
|
+
};
|
|
12
|
+
export declare const responseContext: {
|
|
13
|
+
readonly value: ServerResponse<IncomingMessage>;
|
|
14
|
+
chain(params_0: ServerResponse<IncomingMessage>): <V>(next: () => V) => Promise<any>;
|
|
15
|
+
};
|
package/cjs/context.js
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.responseContext = exports.requestContext = exports.makeContext = void 0;
|
|
4
|
+
const node_async_hooks_1 = require("node:async_hooks");
|
|
5
|
+
const jmisc_1 = require("jmisc");
|
|
6
|
+
const makeContext = (maker, end = jmisc_1.identity) => {
|
|
7
|
+
const asyncLocalStorage = new node_async_hooks_1.AsyncLocalStorage();
|
|
8
|
+
return {
|
|
9
|
+
get value() {
|
|
10
|
+
return asyncLocalStorage.getStore();
|
|
11
|
+
},
|
|
12
|
+
chain(...params) {
|
|
13
|
+
return async (next) => {
|
|
14
|
+
const value = await maker(...params);
|
|
15
|
+
return end(await asyncLocalStorage.run(value, next), value);
|
|
16
|
+
};
|
|
17
|
+
},
|
|
18
|
+
};
|
|
19
|
+
};
|
|
20
|
+
exports.makeContext = makeContext;
|
|
21
|
+
// method: verb
|
|
22
|
+
// url: full url without server, protocol, port.
|
|
23
|
+
// headers: if headers are repeated, they are joined by comma. Header names are lowercased.
|
|
24
|
+
// rawHeaders: list of header name and value in a flat array. Case is preserved.
|
|
25
|
+
exports.requestContext = (0, exports.makeContext)(jmisc_1.identity);
|
|
26
|
+
exports.responseContext = (0, exports.makeContext)(jmisc_1.identity);
|
|
27
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY29udGV4dC5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uL3NyYy9jb250ZXh0LnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiI7OztBQUFBLHVEQUFrRDtBQUdsRCxpQ0FBOEI7QUFFdkIsTUFBTSxXQUFXLEdBQUcsQ0FDMUIsS0FBMkMsRUFDM0MsTUFBc0MsZ0JBQVEsRUFDN0MsRUFBRTtJQUNILE1BQU0saUJBQWlCLEdBQUcsSUFBSSxvQ0FBaUIsRUFBSyxDQUFBO0lBQ3BELE9BQU87UUFDTixJQUFJLEtBQUs7WUFDUixPQUFPLGlCQUFpQixDQUFDLFFBQVEsRUFBRyxDQUFBO1FBQ3JDLENBQUM7UUFDRCxLQUFLLENBQUMsR0FBRyxNQUFjO1lBQ3RCLE9BQU8sS0FBSyxFQUFLLElBQWEsRUFBRSxFQUFFO2dCQUNqQyxNQUFNLEtBQUssR0FBRyxNQUFNLEtBQUssQ0FBQyxHQUFHLE1BQU0sQ0FBQyxDQUFBO2dCQUNwQyxPQUFPLEdBQUcsQ0FBQyxNQUFNLGlCQUFpQixDQUFDLEdBQUcsQ0FBQyxLQUFLLEVBQUUsSUFBSSxDQUFDLEVBQUUsS0FBSyxDQUFDLENBQUE7WUFDNUQsQ0FBQyxDQUFBO1FBQ0YsQ0FBQztLQUNELENBQUE7QUFDRixDQUFDLENBQUE7QUFoQlksUUFBQSxXQUFXLGVBZ0J2QjtBQUVELGVBQWU7QUFDZixnREFBZ0Q7QUFDaEQsMkZBQTJGO0FBQzNGLGdGQUFnRjtBQUNuRSxRQUFBLGNBQWMsR0FBRyxJQUFBLG1CQUFXLEVBQXFDLGdCQUFRLENBQUMsQ0FBQTtBQUMxRSxRQUFBLGVBQWUsR0FBRyxJQUFBLG1CQUFXLEVBQW1DLGdCQUFRLENBQUMsQ0FBQSJ9
|
package/cjs/error.js
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.notFoundApi = exports.notFound = exports.catchApiError = exports.catchError = void 0;
|
|
4
|
+
const express_js_1 = require("./express.js");
|
|
5
|
+
const route_js_1 = require("./route.js");
|
|
6
|
+
const catchError = async (next) => {
|
|
7
|
+
try {
|
|
8
|
+
await next();
|
|
9
|
+
}
|
|
10
|
+
catch (e) {
|
|
11
|
+
console.error(e);
|
|
12
|
+
(0, express_js_1.setHtml)('internal server error', { status: 500 });
|
|
13
|
+
}
|
|
14
|
+
};
|
|
15
|
+
exports.catchError = catchError;
|
|
16
|
+
exports.catchApiError = route_js_1.router.post({
|
|
17
|
+
async '/api'({ next }) {
|
|
18
|
+
try {
|
|
19
|
+
await next();
|
|
20
|
+
}
|
|
21
|
+
catch (e) {
|
|
22
|
+
console.error(e);
|
|
23
|
+
(0, express_js_1.setJson)({
|
|
24
|
+
message: 'internal server error',
|
|
25
|
+
code: 'internal_server_error'
|
|
26
|
+
}, { status: 500 });
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
}, { end: false });
|
|
30
|
+
const notFound = () => {
|
|
31
|
+
(0, express_js_1.setHtml)('not found', { status: 404 });
|
|
32
|
+
};
|
|
33
|
+
exports.notFound = notFound;
|
|
34
|
+
exports.notFoundApi = route_js_1.router.post({
|
|
35
|
+
'/api'() {
|
|
36
|
+
(0, express_js_1.setJson)({
|
|
37
|
+
message: 'not found',
|
|
38
|
+
code: 'not_found'
|
|
39
|
+
}, { status: 404 });
|
|
40
|
+
}
|
|
41
|
+
}, { end: false });
|
|
42
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiZXJyb3IuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi9zcmMvZXJyb3IudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6Ijs7O0FBQ0EsNkNBQTZDO0FBQzdDLHlDQUFpQztBQUUxQixNQUFNLFVBQVUsR0FBZSxLQUFLLEVBQUMsSUFBSSxFQUFDLEVBQUU7SUFDbEQsSUFBSTtRQUNILE1BQU0sSUFBSSxFQUFFLENBQUE7S0FDWjtJQUFDLE9BQU8sQ0FBQyxFQUFFO1FBQ1gsT0FBTyxDQUFDLEtBQUssQ0FBQyxDQUFDLENBQUMsQ0FBQTtRQUNoQixJQUFBLG9CQUFPLEVBQUMsdUJBQXVCLEVBQUUsRUFBQyxNQUFNLEVBQUUsR0FBRyxFQUFDLENBQUMsQ0FBQTtLQUMvQztBQUNGLENBQUMsQ0FBQTtBQVBZLFFBQUEsVUFBVSxjQU90QjtBQUVZLFFBQUEsYUFBYSxHQUFHLGlCQUFNLENBQUMsSUFBSSxDQUFDO0lBQ3hDLEtBQUssQ0FBQyxNQUFNLENBQUMsRUFBQyxJQUFJLEVBQUM7UUFDbEIsSUFBSTtZQUNILE1BQU0sSUFBSSxFQUFFLENBQUE7U0FDWjtRQUFDLE9BQU8sQ0FBQyxFQUFFO1lBQ1gsT0FBTyxDQUFDLEtBQUssQ0FBQyxDQUFDLENBQUMsQ0FBQTtZQUNoQixJQUFBLG9CQUFPLEVBQUM7Z0JBQ1AsT0FBTyxFQUFFLHVCQUF1QjtnQkFDaEMsSUFBSSxFQUFFLHVCQUF1QjthQUM3QixFQUFFLEVBQUMsTUFBTSxFQUFFLEdBQUcsRUFBQyxDQUFDLENBQUE7U0FDakI7SUFDRixDQUFDO0NBQ0QsRUFBRSxFQUFDLEdBQUcsRUFBRSxLQUFLLEVBQUMsQ0FBQyxDQUFBO0FBRVQsTUFBTSxRQUFRLEdBQWUsR0FBRyxFQUFFO0lBQ3hDLElBQUEsb0JBQU8sRUFBQyxXQUFXLEVBQUUsRUFBQyxNQUFNLEVBQUUsR0FBRyxFQUFDLENBQUMsQ0FBQTtBQUNwQyxDQUFDLENBQUE7QUFGWSxRQUFBLFFBQVEsWUFFcEI7QUFDWSxRQUFBLFdBQVcsR0FBRyxpQkFBTSxDQUFDLElBQUksQ0FBQztJQUN0QyxNQUFNO1FBQ0wsSUFBQSxvQkFBTyxFQUFDO1lBQ1AsT0FBTyxFQUFFLFdBQVc7WUFDcEIsSUFBSSxFQUFFLFdBQVc7U0FDakIsRUFBRSxFQUFDLE1BQU0sRUFBRSxHQUFHLEVBQUMsQ0FBQyxDQUFBO0lBQ2xCLENBQUM7Q0FDRCxFQUFFLEVBQUMsR0FBRyxFQUFFLEtBQUssRUFBQyxDQUFDLENBQUEifQ==
|
package/cjs/etag.d.ts
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/// <reference types="node" resolution-mode="require"/>
|
|
2
|
+
/// <reference types="node" resolution-mode="require"/>
|
|
3
|
+
import type { IncomingMessage } from 'node:http';
|
|
4
|
+
export declare function entityTag(buf: Buffer, weak?: boolean): string;
|
|
5
|
+
export declare function statTag(stat: any): string;
|
|
6
|
+
export declare function isFreshETag(req: IncomingMessage, etag: string): true | undefined;
|
|
7
|
+
export declare function isFreshModifiedSince(req: IncomingMessage, lastModified: string): boolean | undefined;
|
package/cjs/etag.js
ADDED
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.isFreshModifiedSince = exports.isFreshETag = exports.statTag = exports.entityTag = void 0;
|
|
7
|
+
// etag: https://github.com/jshttp/etag/blob/b9f0642256e63654287299d205bc6ced71b1a228/index.js#L39
|
|
8
|
+
const node_crypto_1 = __importDefault(require("node:crypto"));
|
|
9
|
+
function entityTag(buf, weak) {
|
|
10
|
+
// pre-computed empty
|
|
11
|
+
return buf.length
|
|
12
|
+
? `${buf.length.toString(16)}-${node_crypto_1.default
|
|
13
|
+
.createHash('sha1')
|
|
14
|
+
.update(buf)
|
|
15
|
+
.digest('base64')
|
|
16
|
+
.substring(0, 27)}"`
|
|
17
|
+
: `${weak ? 'W/' : ''}"0-2jmj7l5rSw0yVb/vlWAYkK/YBwk"`;
|
|
18
|
+
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/ETag#directives
|
|
19
|
+
// weak W/ vs strong eTag
|
|
20
|
+
// same weak eTag: 2 resources might be semantically equivalent, but not byte-for-byte identical
|
|
21
|
+
}
|
|
22
|
+
exports.entityTag = entityTag;
|
|
23
|
+
const CACHE_CONTROL_NO_CACHE_REGEXP = /(?:^|,)\s*?no-cache\s*?(?:,|$)/;
|
|
24
|
+
function statTag(stat) {
|
|
25
|
+
const mtime = stat.mtime.getTime().toString(16);
|
|
26
|
+
const size = stat.size.toString(16);
|
|
27
|
+
return `"${size}-${mtime}"`;
|
|
28
|
+
}
|
|
29
|
+
exports.statTag = statTag;
|
|
30
|
+
// https://github.com/jshttp/fresh/blob/05254186fd7428915224db46144fc94293a7df7d/index.js#L33
|
|
31
|
+
function isFreshETag(req, etag) {
|
|
32
|
+
const noneMatch = req.headers['if-none-match'];
|
|
33
|
+
if (!noneMatch)
|
|
34
|
+
return;
|
|
35
|
+
// Always return stale when Cache-Control: no-cache
|
|
36
|
+
// to support end-to-end reload requests
|
|
37
|
+
// https://tools.ietf.org/html/rfc2616#section-14.9.4
|
|
38
|
+
const cacheControl = req.headers['cache-control'];
|
|
39
|
+
if (cacheControl && CACHE_CONTROL_NO_CACHE_REGEXP.test(cacheControl))
|
|
40
|
+
return;
|
|
41
|
+
if (noneMatch && noneMatch !== '*') {
|
|
42
|
+
if (!etag)
|
|
43
|
+
return;
|
|
44
|
+
let etagStale = true;
|
|
45
|
+
for (const match of parseTokenList(noneMatch)) {
|
|
46
|
+
if (match === etag || match === `W/${etag}` || `W/${match}` === etag) {
|
|
47
|
+
etagStale = false;
|
|
48
|
+
break;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
if (etagStale)
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
return true;
|
|
55
|
+
}
|
|
56
|
+
exports.isFreshETag = isFreshETag;
|
|
57
|
+
function isFreshModifiedSince(req, lastModified) {
|
|
58
|
+
const modifiedSince = req.headers['if-modified-since'];
|
|
59
|
+
if (!modifiedSince)
|
|
60
|
+
return;
|
|
61
|
+
// Always return stale when Cache-Control: no-cache
|
|
62
|
+
// to support end-to-end reload requests
|
|
63
|
+
// https://tools.ietf.org/html/rfc2616#section-14.9.4
|
|
64
|
+
const cacheControl = req.headers['cache-control'];
|
|
65
|
+
if (cacheControl && CACHE_CONTROL_NO_CACHE_REGEXP.test(cacheControl))
|
|
66
|
+
return;
|
|
67
|
+
if (modifiedSince && lastModified) {
|
|
68
|
+
const lastModifiedDate = Date.parse(lastModified);
|
|
69
|
+
const modifiedSinceDate = Date.parse(modifiedSince);
|
|
70
|
+
return !isNaN(lastModifiedDate) && !isNaN(modifiedSinceDate) && lastModifiedDate <= modifiedSinceDate;
|
|
71
|
+
}
|
|
72
|
+
return true;
|
|
73
|
+
}
|
|
74
|
+
exports.isFreshModifiedSince = isFreshModifiedSince;
|
|
75
|
+
function parseTokenList(str) {
|
|
76
|
+
let end = 0;
|
|
77
|
+
const list = [];
|
|
78
|
+
let start = 0;
|
|
79
|
+
// gather tokens
|
|
80
|
+
for (let i = 0, len = str.length; i < len; i++) {
|
|
81
|
+
switch (str.charCodeAt(i)) {
|
|
82
|
+
case 0x20: /* */
|
|
83
|
+
if (start === end) {
|
|
84
|
+
start = end = i + 1;
|
|
85
|
+
}
|
|
86
|
+
break;
|
|
87
|
+
case 0x2c: /* , */
|
|
88
|
+
list.push(str.substring(start, end));
|
|
89
|
+
start = end = i + 1;
|
|
90
|
+
break;
|
|
91
|
+
default:
|
|
92
|
+
end = i + 1;
|
|
93
|
+
break;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
// final token
|
|
97
|
+
list.push(str.substring(start, end));
|
|
98
|
+
return list;
|
|
99
|
+
}
|
|
100
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiZXRhZy5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uL3NyYy9ldGFnLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiI7Ozs7OztBQUFBLGtHQUFrRztBQUNsRyw4REFBZ0M7QUFJaEMsU0FBZ0IsU0FBUyxDQUFDLEdBQVcsRUFBRSxJQUFjO0lBQ3BELHFCQUFxQjtJQUNyQixPQUFPLEdBQUcsQ0FBQyxNQUFNO1FBQ2hCLENBQUMsQ0FBQyxHQUFHLEdBQUcsQ0FBQyxNQUFNLENBQUMsUUFBUSxDQUFDLEVBQUUsQ0FBQyxJQUFJLHFCQUFNO2FBQ3BDLFVBQVUsQ0FBQyxNQUFNLENBQUM7YUFDbEIsTUFBTSxDQUFDLEdBQUcsQ0FBQzthQUNYLE1BQU0sQ0FBQyxRQUFRLENBQUM7YUFDaEIsU0FBUyxDQUFDLENBQUMsRUFBRSxFQUFFLENBQUMsR0FBRztRQUNyQixDQUFDLENBQUMsR0FBRyxJQUFJLENBQUMsQ0FBQyxDQUFDLElBQUksQ0FBQyxDQUFDLENBQUMsRUFBRSxpQ0FBaUMsQ0FBQTtJQUN2RCw0RUFBNEU7SUFDNUUseUJBQXlCO0lBQ3pCLGdHQUFnRztBQUNqRyxDQUFDO0FBWkQsOEJBWUM7QUFFRCxNQUFNLDZCQUE2QixHQUFHLGdDQUFnQyxDQUFBO0FBQ3RFLFNBQWdCLE9BQU8sQ0FBQyxJQUFJO0lBQzNCLE1BQU0sS0FBSyxHQUFHLElBQUksQ0FBQyxLQUFLLENBQUMsT0FBTyxFQUFFLENBQUMsUUFBUSxDQUFDLEVBQUUsQ0FBQyxDQUFBO0lBQy9DLE1BQU0sSUFBSSxHQUFHLElBQUksQ0FBQyxJQUFJLENBQUMsUUFBUSxDQUFDLEVBQUUsQ0FBQyxDQUFBO0lBRW5DLE9BQU8sSUFBSSxJQUFJLElBQUksS0FBSyxHQUFHLENBQUE7QUFDNUIsQ0FBQztBQUxELDBCQUtDO0FBQ0QsNkZBQTZGO0FBQzdGLFNBQWdCLFdBQVcsQ0FBQyxHQUFvQixFQUFFLElBQVk7SUFDN0QsTUFBTSxTQUFTLEdBQUcsR0FBRyxDQUFDLE9BQU8sQ0FBQyxlQUFl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
|
package/cjs/express.d.ts
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/// <reference types="node" resolution-mode="require"/>
|
|
2
|
+
/// <reference types="node" resolution-mode="require"/>
|
|
3
|
+
import { Writable } from 'node:stream';
|
|
4
|
+
export declare const expressContext: {
|
|
5
|
+
readonly value: {
|
|
6
|
+
charset?: BufferEncoding | undefined;
|
|
7
|
+
jsonBeautify?: boolean | undefined;
|
|
8
|
+
disableEtag?: boolean | undefined;
|
|
9
|
+
} & ({
|
|
10
|
+
type: 'text';
|
|
11
|
+
data: string;
|
|
12
|
+
} | {
|
|
13
|
+
type: 'html';
|
|
14
|
+
data: string;
|
|
15
|
+
} | {
|
|
16
|
+
type: 'buffer';
|
|
17
|
+
data: Buffer;
|
|
18
|
+
} | {
|
|
19
|
+
type: 'json';
|
|
20
|
+
data: any;
|
|
21
|
+
} | {
|
|
22
|
+
type: 'redirect';
|
|
23
|
+
data: string;
|
|
24
|
+
} | {
|
|
25
|
+
type: 'stream';
|
|
26
|
+
data: Writable;
|
|
27
|
+
});
|
|
28
|
+
chain(params_0?: {
|
|
29
|
+
jsonBeautify?: boolean | undefined;
|
|
30
|
+
disableEtag?: boolean | undefined;
|
|
31
|
+
} | undefined): <V>(next: () => V) => Promise<any>;
|
|
32
|
+
};
|
|
33
|
+
export declare function setText(text: string, { status }?: {
|
|
34
|
+
status?: number;
|
|
35
|
+
}): void;
|
|
36
|
+
export declare function setHtml(html: string, opts?: {
|
|
37
|
+
status?: number;
|
|
38
|
+
}): void;
|
|
39
|
+
export declare function setBuffer(buffer: Buffer, { status }?: {
|
|
40
|
+
status?: number;
|
|
41
|
+
}): void;
|
|
42
|
+
export declare function setStream(stream: Writable, { status }?: {
|
|
43
|
+
status?: number;
|
|
44
|
+
}): void;
|
|
45
|
+
export declare function setJson(json: any, { status, beautify }?: {
|
|
46
|
+
status?: number;
|
|
47
|
+
beautify?: boolean;
|
|
48
|
+
}): void;
|
|
49
|
+
export declare function setRedirect(url: string, status: 301 | 302): void;
|