dx-server 0.0.1 → 0.0.2

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.
Files changed (50) hide show
  1. package/README.md +183 -0
  2. package/cjs/body.d.ts +32 -0
  3. package/cjs/body.js +91 -0
  4. package/cjs/contentType.d.ts +4 -0
  5. package/cjs/contentType.js +92 -0
  6. package/cjs/context.d.ts +15 -0
  7. package/cjs/context.js +27 -0
  8. package/cjs/error.js +42 -0
  9. package/cjs/etag.d.ts +7 -0
  10. package/cjs/etag.js +100 -0
  11. package/cjs/express.d.ts +39 -0
  12. package/cjs/express.js +144 -0
  13. package/cjs/expressApp.d.ts +4 -0
  14. package/cjs/expressApp.js +44 -0
  15. package/cjs/file.d.ts +3 -0
  16. package/cjs/file.js +12 -0
  17. package/cjs/index.d.ts +6 -0
  18. package/cjs/index.js +33 -0
  19. package/cjs/package.json +3 -0
  20. package/cjs/route.d.ts +38 -0
  21. package/cjs/route.js +86 -0
  22. package/cjs/static.d.ts +4 -0
  23. package/cjs/static.js +60 -0
  24. package/cjs/stream.d.ts +11 -0
  25. package/cjs/stream.js +100 -0
  26. package/esm/body.d.ts +32 -0
  27. package/esm/body.js +88 -0
  28. package/esm/contentType.d.ts +4 -0
  29. package/esm/contentType.js +88 -0
  30. package/esm/context.d.ts +15 -0
  31. package/esm/context.js +23 -0
  32. package/esm/error.js +37 -0
  33. package/esm/etag.d.ts +7 -0
  34. package/esm/etag.js +90 -0
  35. package/esm/express.d.ts +39 -0
  36. package/esm/express.js +133 -0
  37. package/esm/expressApp.d.ts +4 -0
  38. package/esm/expressApp.js +35 -0
  39. package/esm/file.d.ts +3 -0
  40. package/esm/file.js +8 -0
  41. package/esm/index.d.ts +6 -0
  42. package/esm/index.js +7 -0
  43. package/esm/route.d.ts +38 -0
  44. package/esm/route.js +82 -0
  45. package/esm/static.d.ts +4 -0
  46. package/esm/static.js +56 -0
  47. package/esm/stream.d.ts +11 -0
  48. package/esm/stream.js +92 -0
  49. package/package.json +31 -4
  50. 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,32 @@
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: {
23
+ [x: string]: any;
24
+ } | undefined;
25
+ chain(params_0?: {
26
+ simplify?: boolean | undefined;
27
+ } | undefined): <V>(next: () => V) => Promise<any>;
28
+ };
29
+ export declare const queryContext: {
30
+ readonly value: import("qs").ParsedQs;
31
+ chain(): <V>(next: () => V) => Promise<any>;
32
+ };
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,4 @@
1
+ export declare function parseContentType(header: string): {
2
+ mediaType: string;
3
+ parameters: Record<string, string>;
4
+ };
@@ -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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
@@ -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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
@@ -0,0 +1,39 @@
1
+ /// <reference types="node" resolution-mode="require"/>
2
+ export declare const expressContext: {
3
+ readonly value: {
4
+ charset?: BufferEncoding | undefined;
5
+ beautify?: boolean | undefined;
6
+ } & ({
7
+ type: 'text';
8
+ data: string;
9
+ } | {
10
+ type: 'html';
11
+ data: string;
12
+ } | {
13
+ type: 'buffer';
14
+ data: Buffer;
15
+ } | {
16
+ type: 'json';
17
+ data: any;
18
+ } | {
19
+ type: 'redirect';
20
+ data: string;
21
+ });
22
+ chain(params_0?: {
23
+ jsonBeautify?: boolean | undefined;
24
+ } | undefined): <V>(next: () => V) => Promise<any>;
25
+ };
26
+ export declare function setText(text: string, { status }?: {
27
+ status?: number;
28
+ }): void;
29
+ export declare function setHtml(html: string, opts?: {
30
+ status?: number;
31
+ }): void;
32
+ export declare function setBuffer(buffer: Buffer, { status }?: {
33
+ status?: number;
34
+ }): void;
35
+ export declare function setJson(json: any, { status, beautify }?: {
36
+ status?: number;
37
+ beautify?: boolean;
38
+ }): void;
39
+ export declare function setRedirect(url: string, status: 301 | 302): void;