webhoster 0.1.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.eslintrc.json +74 -58
- package/.github/copilot-instructions.md +100 -0
- package/.github/workflows/test-matrix.yml +37 -0
- package/.test/benchmark.js +28 -0
- package/.test/constants.js +4 -0
- package/{test → .test}/http2server.js +1 -1
- package/{test → .test}/httpserver.js +1 -1
- package/{test → .test}/index.js +178 -192
- package/.test/multipromise.js +32 -0
- package/{test → .test}/tls.js +3 -3
- package/{test → .test}/urlencoded.js +3 -0
- package/.vscode/launch.json +24 -3
- package/README.md +116 -90
- package/data/CookieObject.js +14 -14
- package/errata/socketio.js +6 -11
- package/examples/starter.js +11 -0
- package/helpers/HeadersParser.js +7 -8
- package/helpers/HttpListener.js +387 -42
- package/helpers/RequestHeaders.js +43 -36
- package/helpers/RequestReader.js +27 -26
- package/helpers/ResponseHeaders.js +47 -36
- package/jsconfig.json +1 -1
- package/lib/HttpHandler.js +447 -277
- package/lib/HttpRequest.js +383 -39
- package/lib/HttpResponse.js +316 -52
- package/lib/HttpTransaction.js +146 -0
- package/middleware/AutoHeadersMiddleware.js +73 -0
- package/middleware/CORSMiddleware.js +45 -47
- package/middleware/CaseInsensitiveHeadersMiddleware.js +5 -11
- package/middleware/ContentDecoderMiddleware.js +81 -35
- package/middleware/ContentEncoderMiddleware.js +179 -132
- package/middleware/ContentLengthMiddleware.js +66 -43
- package/middleware/ContentWriterMiddleware.js +5 -11
- package/middleware/HashMiddleware.js +68 -40
- package/middleware/HeadMethodMiddleware.js +24 -22
- package/middleware/MethodMiddleware.js +29 -36
- package/middleware/PathMiddleware.js +49 -66
- package/middleware/ReadFormData.js +99 -0
- package/middleware/SendHeadersMiddleware.js +0 -2
- package/middleware/SendJsonMiddleware.js +131 -0
- package/middleware/SendStringMiddleware.js +87 -0
- package/package.json +38 -29
- package/polyfill/FormData.js +164 -0
- package/rollup.config.js +0 -1
- package/scripts/test-all-sync.sh +6 -0
- package/scripts/test-all.sh +7 -0
- package/templates/starter.js +53 -0
- package/test/fixtures/stream.js +68 -0
- package/test/helpers/HttpListener/construct.js +18 -0
- package/test/helpers/HttpListener/customOptions.js +22 -0
- package/test/helpers/HttpListener/doubleCreate.js +40 -0
- package/test/helpers/HttpListener/events.js +77 -0
- package/test/helpers/HttpListener/http.js +31 -0
- package/test/helpers/HttpListener/http2.js +41 -0
- package/test/helpers/HttpListener/https.js +38 -0
- package/test/helpers/HttpListener/startAll.js +31 -0
- package/test/helpers/HttpListener/stopNotStarted.js +23 -0
- package/test/lib/HttpHandler/class.js +8 -0
- package/test/lib/HttpHandler/handleRequest.js +11 -0
- package/test/lib/HttpHandler/middleware.js +941 -0
- package/test/lib/HttpHandler/parse.js +41 -0
- package/test/lib/HttpRequest/class.js +8 -0
- package/test/lib/HttpRequest/downstream.js +171 -0
- package/test/lib/HttpRequest/properties.js +101 -0
- package/test/lib/HttpRequest/read.js +518 -0
- package/test/lib/HttpResponse/class.js +8 -0
- package/test/lib/HttpResponse/properties.js +59 -0
- package/test/lib/HttpResponse/send.js +275 -0
- package/test/lib/HttpTransaction/class.js +8 -0
- package/test/lib/HttpTransaction/ping.js +50 -0
- package/test/lib/HttpTransaction/push.js +89 -0
- package/test/middleware/SendJsonMiddleware.js +222 -0
- package/test/sanity.js +10 -0
- package/test/templates/starter.js +93 -0
- package/tsconfig.json +12 -0
- package/types/index.js +61 -34
- package/types/typings.d.ts +8 -9
- package/utils/AsyncObject.js +6 -3
- package/utils/CaseInsensitiveObject.js +2 -3
- package/utils/function.js +1 -7
- package/utils/headers.js +42 -0
- package/utils/qualityValues.js +1 -1
- package/utils/stream.js +4 -20
- package/index.cjs +0 -3200
- package/test/constants.js +0 -4
- /package/{test → .test}/cookietester.js +0 -0
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import test from 'ava';
|
|
2
|
+
import http from 'node:http';
|
|
3
|
+
import HttpListener from '../../helpers/HttpListener.js';
|
|
4
|
+
import HttpHandler from '../../lib/HttpHandler.js';
|
|
5
|
+
import * as starter from '../../templates/starter.js';
|
|
6
|
+
|
|
7
|
+
test.serial('examples/starter starts singleton listener and responds', async (t) => {
|
|
8
|
+
// Import the example. It applies middleware and starts the singleton listener.
|
|
9
|
+
await import('../../examples/starter.js');
|
|
10
|
+
|
|
11
|
+
const listener = HttpListener.defaultInstance;
|
|
12
|
+
const server = listener.httpServer;
|
|
13
|
+
t.truthy(server, 'singleton server started');
|
|
14
|
+
|
|
15
|
+
const addr = server.address();
|
|
16
|
+
t.truthy(addr && addr.port, 'server bound to a port');
|
|
17
|
+
|
|
18
|
+
const result = await new Promise((resolve, reject) => {
|
|
19
|
+
const request = http.get({ port: addr.port, path: '/' }, (res) => {
|
|
20
|
+
let data = '';
|
|
21
|
+
res.setEncoding('utf8');
|
|
22
|
+
res.on('data', (chunk) => { data += chunk; });
|
|
23
|
+
res.on('end', () => resolve({ status: res.statusCode, body: data }));
|
|
24
|
+
});
|
|
25
|
+
request.on('error', reject);
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
t.is(result.status, 200);
|
|
29
|
+
t.true(typeof result.body === 'string');
|
|
30
|
+
t.true(result.body.length > 0, 'response body present');
|
|
31
|
+
|
|
32
|
+
await listener.stopHttpServer();
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
test.serial('starter.start applies default error handler and middleware when none provided', async (t) => {
|
|
36
|
+
const handler = HttpHandler.defaultInstance;
|
|
37
|
+
const listener = HttpListener.defaultInstance;
|
|
38
|
+
|
|
39
|
+
const mwLength = handler.middleware.length;
|
|
40
|
+
const ehLength = handler.errorHandlers.length;
|
|
41
|
+
|
|
42
|
+
// Start with empty options to exercise the default-error-handler branch
|
|
43
|
+
// Use port 0 and loopback host to avoid port collisions in CI.
|
|
44
|
+
await starter.start({ host: '127.0.0.1', port: 0 });
|
|
45
|
+
|
|
46
|
+
t.truthy(listener.httpServer, 'httpServer started');
|
|
47
|
+
|
|
48
|
+
t.true(handler.middleware.length >= mwLength + 8, 'starter middleware applied');
|
|
49
|
+
t.true(handler.errorHandlers.length > ehLength, 'default error handler added');
|
|
50
|
+
|
|
51
|
+
const lastError = handler.errorHandlers.at(-1);
|
|
52
|
+
t.is(typeof lastError.onError, 'function');
|
|
53
|
+
|
|
54
|
+
// Invoke the default onError to exercise its code path (logging + 500).
|
|
55
|
+
const result = lastError.onError({ request: { url: '/x' }, error: new Error('boom') });
|
|
56
|
+
t.is(result, 500);
|
|
57
|
+
|
|
58
|
+
await listener.stopHttpServer();
|
|
59
|
+
|
|
60
|
+
// restore handler arrays to previous lengths
|
|
61
|
+
handler.middleware.splice(mwLength);
|
|
62
|
+
handler.errorHandlers.splice(ehLength);
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
test.serial('starter.start respects provided errorHandlers and pushes middleware by reference', async (t) => {
|
|
66
|
+
const handler = HttpHandler.defaultInstance;
|
|
67
|
+
const listener = HttpListener.defaultInstance;
|
|
68
|
+
|
|
69
|
+
const mwLength = handler.middleware.length;
|
|
70
|
+
const ehLength = handler.errorHandlers.length;
|
|
71
|
+
|
|
72
|
+
const customHandlers = [{ onError: () => 418 }];
|
|
73
|
+
const customMiddleware = [() => 'custom'];
|
|
74
|
+
|
|
75
|
+
await starter.start({
|
|
76
|
+
middleware: customMiddleware, errorHandlers: customHandlers, host: '127.0.0.1', port: 0,
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
t.truthy(listener.httpServer, 'httpServer started');
|
|
80
|
+
|
|
81
|
+
// The supplied middleware array should be pushed as a single entry (by reference)
|
|
82
|
+
const lastMw = handler.middleware.at(-1);
|
|
83
|
+
t.is(lastMw, customMiddleware);
|
|
84
|
+
|
|
85
|
+
// The implementation does not automatically append provided `errorHandlers`;
|
|
86
|
+
// it only avoids adding the default when `options.errorHandlers` is supplied.
|
|
87
|
+
t.is(handler.errorHandlers.length, ehLength);
|
|
88
|
+
|
|
89
|
+
await listener.stopHttpServer();
|
|
90
|
+
|
|
91
|
+
handler.middleware.splice(mwLength);
|
|
92
|
+
handler.errorHandlers.splice(ehLength);
|
|
93
|
+
});
|
package/tsconfig.json
ADDED
package/types/index.js
CHANGED
|
@@ -1,55 +1,82 @@
|
|
|
1
|
-
/**
|
|
2
|
-
|
|
1
|
+
/**
|
|
2
|
+
* @template {any} [T=any]
|
|
3
|
+
* @typedef {import('../lib/HttpTransaction.js').default<T>} HttpTransaction<T>
|
|
4
|
+
*/
|
|
5
|
+
/** @typedef {import('../lib/HttpRequest.js').default} HttpRequest */
|
|
6
|
+
/** @typedef {import('../lib/HttpResponse.js').default} HttpResponse */
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* @typedef {Object} MediaType
|
|
10
|
+
* @prop {string} type
|
|
11
|
+
* @prop {string} tree
|
|
12
|
+
* @prop {string} subtype
|
|
13
|
+
* @prop {string} suffix
|
|
14
|
+
* @prop {Record<string, string>} parameters
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
/** @typedef {import('node:stream').Duplex} Duplex */
|
|
3
18
|
|
|
4
19
|
/** @typedef {import('./typings').Middleware} Middleware */
|
|
5
20
|
|
|
21
|
+
/** @typedef {true|false|0} MiddlewareFlowInstruction */
|
|
22
|
+
|
|
6
23
|
/** @typedef {'GET'|'HEAD'|'POST'|'PUT'|'DELETE'|'CONNECT'|'OPTIONS'|'TRACE'|'PATCH'} RequestMethod */
|
|
7
24
|
|
|
8
|
-
/** @typedef {
|
|
25
|
+
/** @typedef {0|true|false|null|undefined|void} MiddlewareFunctionResultType */
|
|
26
|
+
|
|
9
27
|
/** @typedef {Promise<Middleware>|Middleware} MiddlewareFunctionResult */
|
|
10
|
-
|
|
28
|
+
|
|
29
|
+
/** @typedef {Record<string,any>|number|import('stream').Readable|Buffer|string} MiddlewareContent */
|
|
11
30
|
|
|
12
31
|
/**
|
|
13
|
-
* @
|
|
14
|
-
* @
|
|
15
|
-
* @
|
|
32
|
+
* @template {any} [T=any]
|
|
33
|
+
* @callback MiddlewareFunction
|
|
34
|
+
* @param {!HttpTransaction<T>} transaction
|
|
35
|
+
* @return {MiddlewareFunctionResult}
|
|
16
36
|
*/
|
|
17
37
|
|
|
18
38
|
/**
|
|
19
|
-
* @
|
|
20
|
-
* @
|
|
39
|
+
* @template {any} [T=any]
|
|
40
|
+
* @callback MiddlewareResponseFunction
|
|
41
|
+
* @param {{ response: HttpTransaction<T>['response'] }} transaction
|
|
42
|
+
* @return {MiddlewareFunctionResult}
|
|
21
43
|
*/
|
|
22
44
|
|
|
23
45
|
/**
|
|
24
|
-
* @
|
|
25
|
-
* @
|
|
26
|
-
* @
|
|
27
|
-
* @
|
|
46
|
+
* @this {undefined}
|
|
47
|
+
* @callback StaticMiddlewareFunction
|
|
48
|
+
* @param {!HttpTransaction} transaction
|
|
49
|
+
* @return {MiddlewareFunctionResult}
|
|
28
50
|
*/
|
|
29
51
|
|
|
30
52
|
/**
|
|
31
|
-
* @
|
|
32
|
-
* @
|
|
33
|
-
* @
|
|
34
|
-
* @prop {HandlerState} state
|
|
35
|
-
* @prop {any} [err]
|
|
53
|
+
* @callback ResponseFinalizer
|
|
54
|
+
* @param {!HttpResponse} response
|
|
55
|
+
* @return {void|null|boolean|Promise<void|null|boolean>}
|
|
36
56
|
*/
|
|
37
57
|
|
|
38
58
|
/**
|
|
39
|
-
* @
|
|
40
|
-
* @
|
|
41
|
-
* @return {MiddlewareFunctionResult}
|
|
59
|
+
* @typedef MiddlewareExecutor
|
|
60
|
+
* @prop {MiddlewareFunction} execute
|
|
42
61
|
*/
|
|
43
62
|
|
|
44
63
|
/**
|
|
45
|
-
* @
|
|
46
|
-
* @
|
|
47
|
-
|
|
64
|
+
* @typedef {Object} MiddlewareStaticExecutor
|
|
65
|
+
* @prop {StaticMiddlewareFunction} Execute
|
|
66
|
+
*/
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* @typedef {Object} MiddlewareStaticErrorHandler
|
|
70
|
+
* @prop {StaticMiddlewareFunction} OnError
|
|
71
|
+
*/
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* @typedef MiddlewareErrorHandler
|
|
75
|
+
* @prop {MiddlewareFunction} onError
|
|
48
76
|
*/
|
|
49
77
|
|
|
50
78
|
/**
|
|
51
|
-
* @typedef {
|
|
52
|
-
* @prop {MiddlewareErrorHandlerFunction} onError
|
|
79
|
+
* @typedef {MiddlewareExecutor|MiddlewareStaticExecutor|MiddlewareErrorHandler|MiddlewareStaticErrorHandler|MiddlewareStaticExecutor} IMiddleware
|
|
53
80
|
*/
|
|
54
81
|
|
|
55
82
|
/**
|
|
@@ -57,8 +84,8 @@
|
|
|
57
84
|
* @prop {string} [name='']
|
|
58
85
|
* A cookie name can be any US-ASCII characters, except control characters, spaces, or tabs.
|
|
59
86
|
* It also must not contain a separator character like the following:( ) < > @ , ; : \ " / [ ] ? = { }.
|
|
60
|
-
*
|
|
61
|
-
*
|
|
87
|
+
* - __Secure- prefix: Cookies names starting with __Secure- (dash is part of the prefix) must be set with the secure flag from a secure page (HTTPS).
|
|
88
|
+
* - __Host- prefix: Cookies with names starting with __Host- must be set with the secure flag, must be from a secure page (HTTPS), must not have a domain specified (and therefore aren't sent to subdomains) and the path must be /.
|
|
62
89
|
* @prop {string} [value='']
|
|
63
90
|
* A cookie value can optionally be wrapped in double quotes and include any US-ASCII characters excluding
|
|
64
91
|
* control characters, Whitespace, double quotes, comma, semicolon, and backslash.
|
|
@@ -76,9 +103,9 @@
|
|
|
76
103
|
* If both Expires and Max-Age are set, Max-Age has precedence.
|
|
77
104
|
* @prop {string} [domain]
|
|
78
105
|
* Host to which the cookie will be sent.
|
|
79
|
-
*
|
|
80
|
-
*
|
|
81
|
-
*
|
|
106
|
+
* - If omitted, defaults to the host of the current document URL, not including subdomains.
|
|
107
|
+
* - Contrary to earlier specifications, leading dots in domain names (.example.com) are ignored.
|
|
108
|
+
* - Multiple host/domain values are not allowed, but if a domain is specified, then subdomains are always included.
|
|
82
109
|
* @prop {string} [path]
|
|
83
110
|
* A path that must exist in the requested URL, or the browser won't send the Cookie header.
|
|
84
111
|
*
|
|
@@ -96,13 +123,13 @@
|
|
|
96
123
|
* @prop {'Strict'|'Lax'|'None'} [sameSite]
|
|
97
124
|
* Asserts that a cookie must not be sent with cross-origin requests,
|
|
98
125
|
* providing some protection against cross-site request forgery attacks (CSRF).
|
|
99
|
-
*
|
|
126
|
+
* - Strict: The browser sends the cookie only for same-site requests
|
|
100
127
|
* (that is, requests originating from the same site that set the cookie).
|
|
101
128
|
* If the request originated from a different URL than the current one,
|
|
102
129
|
* no cookies with the SameSite=Strict attribute are sent.
|
|
103
|
-
*
|
|
130
|
+
* - Lax: The cookie is withheld on cross-site subrequests, such as calls to load images or frames,
|
|
104
131
|
* but is sent when a user navigates to the URL from an external site, such as by following a link.
|
|
105
|
-
*
|
|
132
|
+
* - None: The browser sends the cookie with both cross-site and same-site requests.
|
|
106
133
|
*/
|
|
107
134
|
|
|
108
135
|
export default {};
|
package/types/typings.d.ts
CHANGED
|
@@ -1,15 +1,14 @@
|
|
|
1
1
|
import {
|
|
2
2
|
IMiddleware,
|
|
3
|
+
MiddlewareContent,
|
|
3
4
|
MiddlewareFunction,
|
|
4
|
-
|
|
5
|
-
MiddlewareContinueBoolean,
|
|
5
|
+
MiddlewareFunctionResultType,
|
|
6
6
|
} from './index.js';
|
|
7
7
|
|
|
8
|
-
export type Middleware =
|
|
8
|
+
export type Middleware =
|
|
9
9
|
| IMiddleware
|
|
10
|
-
|
|
|
11
|
-
|
|
|
12
|
-
|
|
|
13
|
-
|
|
|
14
|
-
|
|
|
15
|
-
| 'end'|'break'|'continue'|null|undefined|void;
|
|
10
|
+
| MiddlewareFunction
|
|
11
|
+
| Middleware[]
|
|
12
|
+
| MiddlewareContent
|
|
13
|
+
| Set<Middleware>
|
|
14
|
+
| MiddlewareFunctionResultType
|
package/utils/AsyncObject.js
CHANGED
|
@@ -32,7 +32,7 @@ export default class AsyncObject {
|
|
|
32
32
|
|
|
33
33
|
/**
|
|
34
34
|
* @param {T} [value]
|
|
35
|
-
* @return {
|
|
35
|
+
* @return {T}
|
|
36
36
|
*/
|
|
37
37
|
set(value) {
|
|
38
38
|
this.value = value;
|
|
@@ -41,7 +41,7 @@ export default class AsyncObject {
|
|
|
41
41
|
while (this.pendingPromises.length) {
|
|
42
42
|
this.pendingPromises.shift().resolve(value);
|
|
43
43
|
}
|
|
44
|
-
return
|
|
44
|
+
return value;
|
|
45
45
|
}
|
|
46
46
|
|
|
47
47
|
/**
|
|
@@ -57,7 +57,10 @@ export default class AsyncObject {
|
|
|
57
57
|
}
|
|
58
58
|
}
|
|
59
59
|
|
|
60
|
-
/**
|
|
60
|
+
/**
|
|
61
|
+
* Clear value and mark busy
|
|
62
|
+
* @return {void}
|
|
63
|
+
*/
|
|
61
64
|
prepare() {
|
|
62
65
|
this.value = undefined;
|
|
63
66
|
this.busy = true;
|
|
@@ -7,10 +7,10 @@ export default class CaseInsensitiveObject {
|
|
|
7
7
|
// eslint-disable-next-line @typescript-eslint/no-this-alias
|
|
8
8
|
const instance = this;
|
|
9
9
|
const proxy = new Proxy(instance, CaseInsensitiveObject.defaultProxyHandler);
|
|
10
|
-
|
|
10
|
+
for (const [key, value] of Object.entries(object)) {
|
|
11
11
|
// @ts-ignore Coerce
|
|
12
12
|
this[key] = value;
|
|
13
|
-
}
|
|
13
|
+
}
|
|
14
14
|
return proxy;
|
|
15
15
|
}
|
|
16
16
|
}
|
|
@@ -30,4 +30,3 @@ CaseInsensitiveObject.defaultProxyHandler = {
|
|
|
30
30
|
return Reflect.deleteProperty(target, typeof p === 'string' ? p.toLowerCase() : p);
|
|
31
31
|
},
|
|
32
32
|
};
|
|
33
|
-
|
package/utils/function.js
CHANGED
|
@@ -1,7 +1 @@
|
|
|
1
|
-
|
|
2
|
-
/**
|
|
3
|
-
* @param {any} [args]
|
|
4
|
-
* @return {any}
|
|
5
|
-
*/
|
|
6
|
-
// eslint-disable-next-line @typescript-eslint/no-empty-function, @typescript-eslint/no-unused-vars
|
|
7
|
-
export function noop(...args) {}
|
|
1
|
+
export const noop = () => {};
|
package/utils/headers.js
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
*
|
|
3
|
+
* @param {?string} contentType
|
|
4
|
+
* @return {import('../types/index.js').MediaType}
|
|
5
|
+
*/
|
|
6
|
+
export function parseContentType(contentType) {
|
|
7
|
+
let type;
|
|
8
|
+
let tree;
|
|
9
|
+
let subtype;
|
|
10
|
+
let suffix;
|
|
11
|
+
/** @type {Record<string,string>} */
|
|
12
|
+
const parameters = {};
|
|
13
|
+
if (contentType) {
|
|
14
|
+
for (const directive of contentType.split(';')) {
|
|
15
|
+
let [key, value] = directive.split('=');
|
|
16
|
+
key = key.trim().toLowerCase();
|
|
17
|
+
if (value === undefined) {
|
|
18
|
+
let rest;
|
|
19
|
+
[type, rest] = key.split('/');
|
|
20
|
+
const treeEntries = rest.split('.');
|
|
21
|
+
const subtypeSuffix = treeEntries.pop();
|
|
22
|
+
tree = treeEntries.join('.');
|
|
23
|
+
[subtype, suffix] = subtypeSuffix.split('+');
|
|
24
|
+
continue;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
value = value.trim();
|
|
28
|
+
const firstQuote = value.indexOf('"');
|
|
29
|
+
const lastQuote = value.lastIndexOf('"');
|
|
30
|
+
if (firstQuote !== -1 && lastQuote !== -1) {
|
|
31
|
+
if (firstQuote === lastQuote) {
|
|
32
|
+
throw new Error('ERR_CONTENT_TYPE');
|
|
33
|
+
}
|
|
34
|
+
value = value.slice(firstQuote + 1, lastQuote);
|
|
35
|
+
}
|
|
36
|
+
parameters[key] = value;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
return {
|
|
40
|
+
type, subtype, suffix, tree, parameters,
|
|
41
|
+
};
|
|
42
|
+
}
|
package/utils/qualityValues.js
CHANGED
|
@@ -20,7 +20,7 @@ export function parseQualityValues(input) {
|
|
|
20
20
|
const trimmedSpec = specifier?.trim();
|
|
21
21
|
const trimmedSValue = sValue?.trim();
|
|
22
22
|
if (trimmedSpec === 'q') {
|
|
23
|
-
const parsedQ = parseFloat(trimmedSValue);
|
|
23
|
+
const parsedQ = Number.parseFloat(trimmedSValue);
|
|
24
24
|
return { q: Number.isNaN(parsedQ) ? 1 : parsedQ };
|
|
25
25
|
}
|
|
26
26
|
return { [trimmedSpec]: trimmedSValue };
|
package/utils/stream.js
CHANGED
|
@@ -1,23 +1,7 @@
|
|
|
1
|
-
/** @typedef {import('stream').Readable} Readable */
|
|
2
|
-
|
|
3
1
|
/**
|
|
4
|
-
* @param {
|
|
5
|
-
* @return {
|
|
2
|
+
* @param {import('node:stream').Writable|import('node:http').ServerResponse} writableLike
|
|
3
|
+
* @return {boolean}
|
|
6
4
|
*/
|
|
7
|
-
export
|
|
8
|
-
return
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
/**
|
|
12
|
-
* @param {Readable} readable
|
|
13
|
-
* @return {Promise<any[]>}
|
|
14
|
-
*/
|
|
15
|
-
export async function readAllChunks(readable) {
|
|
16
|
-
return new Promise((resolve, reject) => {
|
|
17
|
-
/** @type {any[]} */
|
|
18
|
-
const chunks = [];
|
|
19
|
-
readable.on('data', chunks.push);
|
|
20
|
-
readable.on('end', () => resolve(chunks));
|
|
21
|
-
readable.on('error', reject);
|
|
22
|
-
});
|
|
5
|
+
export function isWritable(writableLike) {
|
|
6
|
+
return (!writableLike.destroyed && !writableLike.writableEnded);
|
|
23
7
|
}
|