dx-server 0.0.1-pre

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 (51) hide show
  1. package/README.md +113 -0
  2. package/cjs/body.d.ts +29 -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.d.ts +5 -0
  9. package/cjs/error.js +42 -0
  10. package/cjs/etag.d.ts +7 -0
  11. package/cjs/etag.js +97 -0
  12. package/cjs/express.d.ts +39 -0
  13. package/cjs/express.js +143 -0
  14. package/cjs/expressApp.d.ts +2 -0
  15. package/cjs/expressApp.js +14 -0
  16. package/cjs/file.d.ts +4 -0
  17. package/cjs/file.js +12 -0
  18. package/cjs/index.d.ts +6 -0
  19. package/cjs/index.js +27 -0
  20. package/cjs/route.d.ts +33 -0
  21. package/cjs/route.js +87 -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 +97 -0
  26. package/esm/body.d.ts +28 -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.d.ts +5 -0
  33. package/esm/error.js +37 -0
  34. package/esm/etag.d.ts +7 -0
  35. package/esm/etag.js +90 -0
  36. package/esm/express.d.ts +21 -0
  37. package/esm/express.js +135 -0
  38. package/esm/expressApp.d.ts +2 -0
  39. package/esm/expressApp.js +10 -0
  40. package/esm/file.d.ts +3 -0
  41. package/esm/file.js +8 -0
  42. package/esm/index.d.ts +6 -0
  43. package/esm/index.js +7 -0
  44. package/esm/package.json +3 -0
  45. package/esm/route.d.ts +33 -0
  46. package/esm/route.js +83 -0
  47. package/esm/static.d.ts +4 -0
  48. package/esm/static.js +56 -0
  49. package/esm/stream.d.ts +10 -0
  50. package/esm/stream.js +92 -0
  51. package/package.json +35 -0
package/README.md ADDED
@@ -0,0 +1,113 @@
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
+ ```javascript
13
+ import {Server} from 'http'
14
+ import {promisify} from 'util'
15
+ import {
16
+ requestContext, responseContext,
17
+
18
+ expressContext, setHtml, setJson,
19
+
20
+ bufferBodyContext,
21
+ jsonBodyContext,
22
+ queryContext,
23
+ rawBodyContext,
24
+ textBodyContext,
25
+ urlencodedBodyContext,
26
+
27
+ router,
28
+ catchApiError, catchError, notFound, notFoundApi,
29
+ expressApp,
30
+ } from 'dx-server'
31
+
32
+ // https://nodejs.org/en/docs/guides/anatomy-of-an-http-transaction
33
+ const tcpServer = new Server()
34
+ .on('request', async (req, res) => {
35
+ try {
36
+ await chain(
37
+ requestContext.chain(req), // required for most middlewares
38
+ responseContext.chain(res), // required for most middlewares
39
+ expressContext.chain({jsonBeautify: true}), // allows to use setHtml, setJson, setRaw, setBuffer, setFile, setRedirect, etc.
40
+ bufferBodyContext.chain(), // use raw buffer body as Buffer use bufferBodyContext.value. This is required for jsonBodyContext, urlencodedBodyContext, textBodyContext, rawBodyContext
41
+ jsonBodyContext.chain(), // to get body parsed as json use jsonBodyContext.value. Only available if content-type is application/json
42
+ urlencodedBodyContext.chain(), // to get body parsed as urlencoded use urlencodedBodyContext.value. Only available if content-type is application/x-www-form-urlencoded
43
+ textBodyContext.chain(), // to get body parsed as text use textBodyContext.value. Only available if content-type is text/plain
44
+ rawBodyContext.chain(), // to get body as raw use rawBodyContext.value. Only available if content-type is application/octet-stream
45
+ queryContext.chain(), // to get query params use queryContext.value. Query is parsed via 'qs' package. If no query, return empty object {}
46
+ next => {
47
+ // this is the difference between express and dx-server
48
+ // req, res can be accessed from anywhere via context which uses NodeJS's AsyncLocalStorage under the hood
49
+ responseContext.value.setHeader('cache-control', 'no-cache')
50
+ next()
51
+ },
52
+ async next => {
53
+ // global error catching for all following middlewares
54
+ try {
55
+ await next()
56
+ } catch (e) {
57
+ console.error(e)
58
+ setHtml('internal server error', {status: 500})
59
+ }
60
+ },
61
+ expressApp(app => {
62
+ // any express feature can be used
63
+ // required express installed, with for e.g., `yarn add express`
64
+ app.use('/photos', express.static('photos'))
65
+ }),
66
+ // example of catching error for all /api/* routes
67
+ router.post({
68
+ async '/api'({next}) {
69
+ try {
70
+ await next()
71
+ } catch (e) {
72
+ console.error(e)
73
+ setJson({
74
+ message: 'internal server error',
75
+ code: 'internal_server_error'
76
+ }, {status: 500})
77
+ }
78
+ }
79
+ }, {end: false}), // note: {end: false} is required to match all /api/* routes. This option is passed directly to path-to-regexp
80
+ router.post({
81
+ '/api/me'() { // sample POST router
82
+ setJson({name: 'joe'})
83
+ }
84
+ }),
85
+ router.get({ // sample GET router
86
+ '/'() {
87
+ setHtml('ok')
88
+ },
89
+ '/health'() {
90
+ setHtml('ok')
91
+ }
92
+ }),
93
+ router.post({ // api not found router
94
+ '/api'() {
95
+ setJson({
96
+ message: 'not found',
97
+ code: 'not_found'
98
+ }, {status: 404})
99
+ }
100
+ }, {end: false}),
101
+ () => { // not found router
102
+ setHtml('not found', {status: 404})
103
+ },
104
+ )()
105
+ } catch (e) {
106
+ console.error(e)
107
+ }
108
+ })
109
+
110
+ const port = +(process.env.PORT ?? 3000)
111
+ await promisify(tcpServer.listen.bind(tcpServer))(port)
112
+ console.log(`server is listening at ${port}`)
113
+ ```
package/cjs/body.d.ts ADDED
@@ -0,0 +1,29 @@
1
+ /// <reference types="node" />
2
+ export declare const bufferBodyContext: {
3
+ readonly value: Buffer | undefined;
4
+ chain(params_0?: {
5
+ limit?: number | undefined;
6
+ } | undefined): <V>(next: () => V) => Promise<any>;
7
+ };
8
+ export declare const jsonBodyContext: {
9
+ readonly value: any;
10
+ chain(): <V>(next: () => V) => Promise<any>;
11
+ };
12
+ export declare const rawBodyContext: {
13
+ readonly value: Buffer | undefined;
14
+ chain(): <V>(next: () => V) => Promise<any>;
15
+ };
16
+ export declare const textBodyContext: {
17
+ readonly value: string | undefined;
18
+ chain(): <V>(next: () => V) => Promise<any>;
19
+ };
20
+ export declare const urlencodedBodyContext: {
21
+ readonly value: any;
22
+ chain(params_0?: {
23
+ simplify?: boolean | undefined;
24
+ } | undefined): <V>(next: () => V) => Promise<any>;
25
+ };
26
+ export declare const queryContext: {
27
+ readonly value: any;
28
+ chain(): <V>(next: () => V) => Promise<any>;
29
+ };
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
+ : qs_1.default.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 ? qs_1.default.parse(query) : {};
90
+ });
91
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiYm9keS5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uL3NyYy9ib2R5LnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiI7OztBQUFBLDJDQUF3RDtBQUN4RCwyQkFBbUI7QUFDbkIscURBQWlEO0FBQ2pELDZDQUF3RDtBQUUzQyxRQUFBLGlCQUFpQixHQUFHLElBQUEsd0JBQVcsRUFBQyxLQUFLLEVBQ2pELEVBQ0MsS0FBSyxHQUFHLEdBQUcsSUFBSSxFQUFFLEVBQUUsUUFBUTtLQUd4QixFQUFFLEVBQ0wsRUFBRTtJQUNILE1BQU0sR0FBRyxHQUFHLDJCQUFjLENBQUMsS0FBSyxDQUFBO0lBRWhDOzs7OztPQUtHO0lBQ0YsK0ZBQStGO0lBQ2hHLE1BQU0sbUJBQW1CLEdBQUcsUUFBUSxDQUFDLEdBQUcsQ0FBQyxPQUFPLENBQUMsZ0JBQWdCLENBQUMsSUFBSSxFQUFFLEVBQUUsRUFBRSxDQUFDLENBQUE7SUFDN0UsSUFDQyxHQUFHLENBQUMsT0FBTyxDQUFDLG1CQUFtQixDQUFDLEtBQUssU0FBUztXQUMzQyxLQUFLLENBQUMsbUJBQW1CLENBQUM7UUFDNUIsT0FBTTtJQUNSLE1BQU0sYUFBYSxHQUFHLEtBQUssQ0FBQyxtQkFBbUIsQ0FBQyxDQUFDLENBQUMsQ0FBQyxTQUFTLENBQUMsQ0FBQyxDQUFDLG1CQUFtQixDQUFBO0lBRWxGLE9BQU87SUFDUCxNQUFNLFFBQVEsR0FBRyxDQUFDLEdBQUcsQ0FBQyxPQUFPLENBQUMsa0JBQWtCLENBQUMsSUFBSSxVQUFVLENBQUMsQ0FBQyxXQUFXLEVBQUUsQ0FBQTtJQUM5RSxNQUFNLE1BQU0sR0FBRyxJQUFBLDRCQUFnQixFQUFDLEdBQUcsRUFBRSxRQUFRLENBQUMsQ0FBQTtJQUM5QyxPQUFPLE1BQU0sSUFBQSxzQkFBVSxFQUN0QixNQUFNLEVBQ047UUFDQyxNQUFNLEVBQUUsUUFBUSxLQUFLLFVBQVUsQ0FBQyxDQUFDLENBQUMsYUFBYSxDQUFDLENBQUMsQ0FBQyxTQUFTO1FBQzNELEtBQUs7S0FD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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" />
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.d.ts ADDED
@@ -0,0 +1,5 @@
1
+ import { IChainable } from 'jchain';
2
+ export declare const catchError: IChainable;
3
+ export declare const catchApiError: IChainable<any, any>;
4
+ export declare const notFound: IChainable;
5
+ export declare const notFoundApi: IChainable<any, any>;
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" />
2
+ /// <reference types="node" />
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,97 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.isFreshModifiedSince = exports.isFreshETag = exports.statTag = exports.entityTag = void 0;
4
+ // etag: https://github.com/jshttp/etag/blob/b9f0642256e63654287299d205bc6ced71b1a228/index.js#L39
5
+ const node_crypto_1 = require("node:crypto");
6
+ function entityTag(buf, weak) {
7
+ // pre-computed empty
8
+ return buf.length
9
+ ? `${buf.length.toString(16)}-${node_crypto_1.default
10
+ .createHash('sha1')
11
+ .update(buf)
12
+ .digest('base64')
13
+ .substring(0, 27)}"`
14
+ : `${weak ? 'W/' : ''}"0-2jmj7l5rSw0yVb/vlWAYkK/YBwk"`;
15
+ // https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/ETag#directives
16
+ // weak W/ vs strong eTag
17
+ // same weak eTag: 2 resources might be semantically equivalent, but not byte-for-byte identical
18
+ }
19
+ exports.entityTag = entityTag;
20
+ const CACHE_CONTROL_NO_CACHE_REGEXP = /(?:^|,)\s*?no-cache\s*?(?:,|$)/;
21
+ function statTag(stat) {
22
+ const mtime = stat.mtime.getTime().toString(16);
23
+ const size = stat.size.toString(16);
24
+ return `"${size}-${mtime}"`;
25
+ }
26
+ exports.statTag = statTag;
27
+ // https://github.com/jshttp/fresh/blob/05254186fd7428915224db46144fc94293a7df7d/index.js#L33
28
+ function isFreshETag(req, etag) {
29
+ const noneMatch = req.headers['if-none-match'];
30
+ if (!noneMatch)
31
+ return;
32
+ // Always return stale when Cache-Control: no-cache
33
+ // to support end-to-end reload requests
34
+ // https://tools.ietf.org/html/rfc2616#section-14.9.4
35
+ const cacheControl = req.headers['cache-control'];
36
+ if (cacheControl && CACHE_CONTROL_NO_CACHE_REGEXP.test(cacheControl))
37
+ return;
38
+ if (noneMatch && noneMatch !== '*') {
39
+ if (!etag)
40
+ return;
41
+ let etagStale = true;
42
+ for (const match of parseTokenList(noneMatch)) {
43
+ if (match === etag || match === `W/${etag}` || `W/${match}` === etag) {
44
+ etagStale = false;
45
+ break;
46
+ }
47
+ }
48
+ if (etagStale)
49
+ return;
50
+ }
51
+ return true;
52
+ }
53
+ exports.isFreshETag = isFreshETag;
54
+ function isFreshModifiedSince(req, lastModified) {
55
+ const modifiedSince = req.headers['if-modified-since'];
56
+ if (!modifiedSince)
57
+ return;
58
+ // Always return stale when Cache-Control: no-cache
59
+ // to support end-to-end reload requests
60
+ // https://tools.ietf.org/html/rfc2616#section-14.9.4
61
+ const cacheControl = req.headers['cache-control'];
62
+ if (cacheControl && CACHE_CONTROL_NO_CACHE_REGEXP.test(cacheControl))
63
+ return;
64
+ if (modifiedSince && lastModified) {
65
+ const lastModifiedDate = Date.parse(lastModified);
66
+ const modifiedSinceDate = Date.parse(modifiedSince);
67
+ return !isNaN(lastModifiedDate) && !isNaN(modifiedSinceDate) && lastModifiedDate <= modifiedSinceDate;
68
+ }
69
+ return true;
70
+ }
71
+ exports.isFreshModifiedSince = isFreshModifiedSince;
72
+ function parseTokenList(str) {
73
+ let end = 0;
74
+ const list = [];
75
+ let start = 0;
76
+ // gather tokens
77
+ for (let i = 0, len = str.length; i < len; i++) {
78
+ switch (str.charCodeAt(i)) {
79
+ case 0x20: /* */
80
+ if (start === end) {
81
+ start = end = i + 1;
82
+ }
83
+ break;
84
+ case 0x2c: /* , */
85
+ list.push(str.substring(start, end));
86
+ start = end = i + 1;
87
+ break;
88
+ default:
89
+ end = i + 1;
90
+ break;
91
+ }
92
+ }
93
+ // final token
94
+ list.push(str.substring(start, end));
95
+ return list;
96
+ }
97
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiZXRhZy5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uL3NyYy9ldGFnLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiI7OztBQUFBLGtHQUFrRztBQUNsRyw2Q0FBZ0M7QUFJaEMsU0FBZ0IsU0FBUyxDQUFDLEdBQVcsRUFBRSxJQUFjO0lBQ3BELHFCQUFxQjtJQUNyQixPQUFPLEdBQUcsQ0FBQyxNQUFNO1FBQ2hCLENBQUMsQ0FBQyxHQUFHLEdBQUcsQ0FBQyxNQUFNLENBQUMsUUFBUSxDQUFDLEVBQUUsQ0FBQyxJQUFJLHFCQUFNO2FBQ3BDLFVBQVUsQ0FBQyxNQUFNLENBQUM7YUFDbEIsTUFBTSxDQUFDLEdBQUcsQ0FBQzthQUNYLE1BQU0sQ0FBQyxRQUFRLENBQUM7YUFDaEIsU0FBUyxDQUFDLENBQUMsRUFBRSxFQUFFLENBQUMsR0FBRztRQUNyQixDQUFDLENBQUMsR0FBRyxJQUFJLENBQUMsQ0FBQyxDQUFDLElBQUksQ0FBQyxDQUFDLENBQUMsRUFBRSxpQ0FBaUMsQ0FBQTtJQUN2RCw0RUFBNEU7SUFDNUUseUJBQXlCO0lBQ3pCLGdHQUFnRztBQUNqRyxDQUFDO0FBWkQsOEJBWUM7QUFFRCxNQUFNLDZCQUE2QixHQUFHLGdDQUFnQyxDQUFBO0FBQ3RFLFNBQWdCLE9BQU8sQ0FBQyxJQUFJO0lBQzNCLE1BQU0sS0FBSyxHQUFHLElBQUksQ0FBQyxLQUFLLENBQUMsT0FBTyxFQUFFLENBQUMsUUFBUSxDQUFDLEVBQUUsQ0FBQyxDQUFBO0lBQy9DLE1BQU0sSUFBSSxHQUFHLElBQUksQ0FBQyxJQUFJLENBQUMsUUFBUSxDQUFDLEVBQUUsQ0FBQyxDQUFBO0lBRW5DLE9BQU8sSUFBSSxJQUFJLElBQUksS0FBSyxHQUFHLENBQUE7QUFDNUIsQ0FBQztBQUxELDBCQUtDO0FBQ0QsNkZBQTZGO0FBQzdGLFNBQWdCLFdBQVcsQ0FBQyxHQUFvQixFQUFFLElBQVk7SUFDN0QsTUFBTSxTQUFTLEdBQUcsR0FBRyxDQUFDLE9BQU8sQ0FBQyxlQUFlLENB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
@@ -0,0 +1,39 @@
1
+ /// <reference types="node" />
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;