@whatwg-node/server 0.10.0-alpha-20240726141316-c6ce93b3598457ebe73b3b725986723af8f5e609 → 0.10.0-alpha-20241125124208-52a1220ca8b2a3b86a8338c8f96b80dde432e1bb
Sign up to get free protection for your applications and to get access to all the features.
- package/cjs/createServerAdapter.js +60 -23
- package/cjs/plugins/useContentEncoding.js +44 -18
- package/cjs/plugins/useErrorHandling.js +5 -1
- package/cjs/utils.js +117 -104
- package/cjs/uwebsockets.js +192 -54
- package/esm/createServerAdapter.js +62 -25
- package/esm/plugins/useContentEncoding.js +45 -19
- package/esm/plugins/useErrorHandling.js +5 -1
- package/esm/utils.js +115 -103
- package/esm/uwebsockets.js +190 -54
- package/package.json +4 -3
- package/typings/plugins/useCors.d.cts +1 -1
- package/typings/plugins/useCors.d.ts +1 -1
- package/typings/plugins/useErrorHandling.d.cts +2 -2
- package/typings/plugins/useErrorHandling.d.ts +2 -2
- package/typings/types.d.cts +10 -2
- package/typings/types.d.ts +10 -2
- package/typings/utils.d.cts +4 -2
- package/typings/utils.d.ts +4 -2
- package/typings/uwebsockets.d.cts +3 -1
- package/typings/uwebsockets.d.ts +3 -1
@@ -3,12 +3,10 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.createServerAdapter = createServerAdapter;
|
4
4
|
const tslib_1 = require("tslib");
|
5
5
|
/* eslint-disable @typescript-eslint/ban-types */
|
6
|
+
const disposablestack_1 = require("@whatwg-node/disposablestack");
|
6
7
|
const DefaultFetchAPI = tslib_1.__importStar(require("@whatwg-node/fetch"));
|
7
8
|
const utils_js_1 = require("./utils.js");
|
8
9
|
const uwebsockets_js_1 = require("./uwebsockets.js");
|
9
|
-
async function handleWaitUntils(waitUntilPromises) {
|
10
|
-
await Promise.allSettled(waitUntilPromises);
|
11
|
-
}
|
12
10
|
// Required for envs like nextjs edge runtime
|
13
11
|
function isRequestAccessible(serverContext) {
|
14
12
|
try {
|
@@ -29,6 +27,37 @@ function createServerAdapter(serverAdapterBaseObject, options) {
|
|
29
27
|
: serverAdapterBaseObject.handle;
|
30
28
|
const onRequestHooks = [];
|
31
29
|
const onResponseHooks = [];
|
30
|
+
const waitUntilPromises = new Set();
|
31
|
+
const disposableStack = new disposablestack_1.AsyncDisposableStack();
|
32
|
+
const signals = new Set();
|
33
|
+
function registerSignal(signal) {
|
34
|
+
signals.add(signal);
|
35
|
+
signal.addEventListener('abort', () => {
|
36
|
+
signals.delete(signal);
|
37
|
+
});
|
38
|
+
}
|
39
|
+
disposableStack.defer(() => {
|
40
|
+
for (const signal of signals) {
|
41
|
+
signal.sendAbort();
|
42
|
+
}
|
43
|
+
});
|
44
|
+
disposableStack.defer(() => {
|
45
|
+
if (waitUntilPromises.size > 0) {
|
46
|
+
return Promise.allSettled(waitUntilPromises).then(() => { }, () => { });
|
47
|
+
}
|
48
|
+
});
|
49
|
+
function waitUntil(promiseLike) {
|
50
|
+
// If it is a Node.js environment, we should register the disposable stack to handle process termination events
|
51
|
+
if (globalThis.process) {
|
52
|
+
(0, utils_js_1.ensureDisposableStackRegisteredForTerminateEvents)(disposableStack);
|
53
|
+
}
|
54
|
+
waitUntilPromises.add(promiseLike.then(() => {
|
55
|
+
waitUntilPromises.delete(promiseLike);
|
56
|
+
}, err => {
|
57
|
+
console.error(`Unexpected error while waiting: ${err.message || err}`);
|
58
|
+
waitUntilPromises.delete(promiseLike);
|
59
|
+
}));
|
60
|
+
}
|
32
61
|
if (options?.plugins != null) {
|
33
62
|
for (const plugin of options.plugins) {
|
34
63
|
if (plugin.onRequest) {
|
@@ -107,20 +136,21 @@ function createServerAdapter(serverAdapterBaseObject, options) {
|
|
107
136
|
}
|
108
137
|
: givenHandleRequest;
|
109
138
|
// TODO: Remove this on the next major version
|
110
|
-
function
|
111
|
-
const nodeResponse = nodeResponseOrContainer.raw || nodeResponseOrContainer;
|
139
|
+
function handleNodeRequest(nodeRequest, ...ctx) {
|
112
140
|
const serverContext = ctx.length > 1 ? (0, utils_js_1.completeAssign)(...ctx) : ctx[0] || {};
|
113
|
-
const request = (0, utils_js_1.normalizeNodeRequest)(nodeRequest,
|
141
|
+
const request = (0, utils_js_1.normalizeNodeRequest)(nodeRequest, fetchAPI, registerSignal);
|
114
142
|
return handleRequest(request, serverContext);
|
115
143
|
}
|
144
|
+
function handleNodeRequestAndResponse(nodeRequest, nodeResponseOrContainer, ...ctx) {
|
145
|
+
const nodeResponse = nodeResponseOrContainer.raw || nodeResponseOrContainer;
|
146
|
+
utils_js_1.nodeRequestResponseMap.set(nodeRequest, nodeResponse);
|
147
|
+
return handleNodeRequest(nodeRequest, ...ctx);
|
148
|
+
}
|
116
149
|
function requestListener(nodeRequest, nodeResponse, ...ctx) {
|
117
|
-
const waitUntilPromises = [];
|
118
150
|
const defaultServerContext = {
|
119
151
|
req: nodeRequest,
|
120
152
|
res: nodeResponse,
|
121
|
-
waitUntil
|
122
|
-
waitUntilPromises.push(cb.catch(err => console.error(err)));
|
123
|
-
},
|
153
|
+
waitUntil,
|
124
154
|
};
|
125
155
|
let response$;
|
126
156
|
try {
|
@@ -145,19 +175,17 @@ function createServerAdapter(serverAdapterBaseObject, options) {
|
|
145
175
|
}
|
146
176
|
}
|
147
177
|
function handleUWS(res, req, ...ctx) {
|
148
|
-
const waitUntilPromises = [];
|
149
178
|
const defaultServerContext = {
|
150
179
|
res,
|
151
180
|
req,
|
152
|
-
waitUntil
|
153
|
-
waitUntilPromises.push(cb.catch(err => console.error(err)));
|
154
|
-
},
|
181
|
+
waitUntil,
|
155
182
|
};
|
156
183
|
const filteredCtxParts = ctx.filter(partCtx => partCtx != null);
|
157
184
|
const serverContext = filteredCtxParts.length > 0
|
158
185
|
? (0, utils_js_1.completeAssign)(defaultServerContext, ...ctx)
|
159
186
|
: defaultServerContext;
|
160
187
|
const signal = new utils_js_1.ServerAdapterRequestAbortSignal();
|
188
|
+
registerSignal(signal);
|
161
189
|
const originalResEnd = res.end.bind(res);
|
162
190
|
let resEnded = false;
|
163
191
|
res.end = function (data) {
|
@@ -189,7 +217,7 @@ function createServerAdapter(serverAdapterBaseObject, options) {
|
|
189
217
|
.catch((e) => (0, utils_js_1.handleErrorFromRequestHandler)(e, fetchAPI.Response))
|
190
218
|
.then(response => {
|
191
219
|
if (!signal.aborted && !resEnded) {
|
192
|
-
return (0, uwebsockets_js_1.sendResponseToUwsOpts)(res, response, signal);
|
220
|
+
return (0, uwebsockets_js_1.sendResponseToUwsOpts)(res, response, signal, fetchAPI);
|
193
221
|
}
|
194
222
|
})
|
195
223
|
.catch(err => {
|
@@ -198,7 +226,7 @@ function createServerAdapter(serverAdapterBaseObject, options) {
|
|
198
226
|
}
|
199
227
|
try {
|
200
228
|
if (!signal.aborted && !resEnded) {
|
201
|
-
return (0, uwebsockets_js_1.sendResponseToUwsOpts)(res, response$, signal);
|
229
|
+
return (0, uwebsockets_js_1.sendResponseToUwsOpts)(res, response$, signal, fetchAPI);
|
202
230
|
}
|
203
231
|
}
|
204
232
|
catch (err) {
|
@@ -218,17 +246,12 @@ function createServerAdapter(serverAdapterBaseObject, options) {
|
|
218
246
|
}
|
219
247
|
function handleRequestWithWaitUntil(request, ...ctx) {
|
220
248
|
const filteredCtxParts = ctx.filter(partCtx => partCtx != null);
|
221
|
-
let waitUntilPromises;
|
222
249
|
const serverContext = filteredCtxParts.length > 1
|
223
250
|
? (0, utils_js_1.completeAssign)({}, ...filteredCtxParts)
|
224
251
|
: (0, utils_js_1.isolateObject)(filteredCtxParts[0], filteredCtxParts[0] == null || filteredCtxParts[0].waitUntil == null
|
225
|
-
?
|
252
|
+
? waitUntil
|
226
253
|
: undefined);
|
227
|
-
|
228
|
-
if (waitUntilPromises?.length) {
|
229
|
-
return handleWaitUntils(waitUntilPromises).then(() => response$);
|
230
|
-
}
|
231
|
-
return response$;
|
254
|
+
return handleRequest(request, serverContext);
|
232
255
|
}
|
233
256
|
const fetchFn = (input, ...maybeCtx) => {
|
234
257
|
if (typeof input === 'string' || 'href' in input) {
|
@@ -275,11 +298,25 @@ function createServerAdapter(serverAdapterBaseObject, options) {
|
|
275
298
|
const adapterObj = {
|
276
299
|
handleRequest: handleRequestWithWaitUntil,
|
277
300
|
fetch: fetchFn,
|
301
|
+
handleNodeRequest,
|
278
302
|
handleNodeRequestAndResponse,
|
279
303
|
requestListener,
|
280
304
|
handleEvent,
|
281
305
|
handleUWS,
|
282
306
|
handle: genericRequestHandler,
|
307
|
+
disposableStack,
|
308
|
+
[disposablestack_1.DisposableSymbols.asyncDispose]() {
|
309
|
+
if (!disposableStack.disposed) {
|
310
|
+
return disposableStack.disposeAsync();
|
311
|
+
}
|
312
|
+
return (0, uwebsockets_js_1.fakePromise)(undefined);
|
313
|
+
},
|
314
|
+
dispose() {
|
315
|
+
if (!disposableStack.disposed) {
|
316
|
+
return disposableStack.disposeAsync();
|
317
|
+
}
|
318
|
+
return (0, uwebsockets_js_1.fakePromise)(undefined);
|
319
|
+
},
|
283
320
|
};
|
284
321
|
const serverAdapter = new Proxy(genericRequestHandler, {
|
285
322
|
// It should have all the attributes of the handler function and the server instance
|
@@ -21,7 +21,7 @@ function useContentEncoding() {
|
|
21
21
|
for (const contentEncoding of contentEncodings) {
|
22
22
|
newBody = newBody.pipeThrough(new fetchAPI.DecompressionStream(contentEncoding));
|
23
23
|
}
|
24
|
-
|
24
|
+
request = new fetchAPI.Request(request.url, {
|
25
25
|
body: newBody,
|
26
26
|
cache: request.cache,
|
27
27
|
credentials: request.credentials,
|
@@ -38,7 +38,7 @@ function useContentEncoding() {
|
|
38
38
|
// @ts-ignore - not in the TS types yet
|
39
39
|
duplex: 'half',
|
40
40
|
});
|
41
|
-
setRequest(
|
41
|
+
setRequest(request);
|
42
42
|
}
|
43
43
|
}
|
44
44
|
const acceptEncoding = request.headers.get('accept-encoding');
|
@@ -46,7 +46,8 @@ function useContentEncoding() {
|
|
46
46
|
encodingMap.set(request, acceptEncoding.split(','));
|
47
47
|
}
|
48
48
|
},
|
49
|
-
onResponse({ request, response, setResponse, fetchAPI }) {
|
49
|
+
onResponse({ request, response, setResponse, fetchAPI, serverContext }) {
|
50
|
+
const waitUntil = serverContext.waitUntil?.bind(serverContext) || (() => { });
|
50
51
|
// Hack for avoiding to create whatwg-node to create a readable stream until it's needed
|
51
52
|
if (response['bodyInit'] || response.body) {
|
52
53
|
const encodings = encodingMap.get(request);
|
@@ -60,21 +61,15 @@ function useContentEncoding() {
|
|
60
61
|
const bufOfRes = response._buffer;
|
61
62
|
if (bufOfRes) {
|
62
63
|
const writer = compressionStream.writable.getWriter();
|
63
|
-
writer.write(bufOfRes);
|
64
|
-
writer.close();
|
65
|
-
const
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
break;
|
73
|
-
}
|
74
|
-
else if (value) {
|
75
|
-
chunks.push(...value);
|
76
|
-
}
|
77
|
-
}
|
64
|
+
waitUntil(writer.write(bufOfRes));
|
65
|
+
waitUntil(writer.close());
|
66
|
+
const uint8Arrays$ = (0, utils_js_1.isReadable)(compressionStream.readable['readable'])
|
67
|
+
? collectReadableValues(compressionStream.readable['readable'])
|
68
|
+
: (0, utils_js_1.isAsyncIterable)(compressionStream.readable)
|
69
|
+
? collectAsyncIterableValues(compressionStream.readable)
|
70
|
+
: collectReadableStreamValues(compressionStream.readable);
|
71
|
+
return uint8Arrays$.then(uint8Arrays => {
|
72
|
+
const chunks = uint8Arrays.flatMap(uint8Array => [...uint8Array]);
|
78
73
|
const uint8Array = new Uint8Array(chunks);
|
79
74
|
const newHeaders = new fetchAPI.Headers(response.headers);
|
80
75
|
newHeaders.set('content-encoding', supportedEncoding);
|
@@ -85,6 +80,7 @@ function useContentEncoding() {
|
|
85
80
|
});
|
86
81
|
utils_js_1.decompressedResponseMap.set(compressedResponse, response);
|
87
82
|
setResponse(compressedResponse);
|
83
|
+
waitUntil(compressionStream.writable.close());
|
88
84
|
});
|
89
85
|
}
|
90
86
|
}
|
@@ -105,3 +101,33 @@ function useContentEncoding() {
|
|
105
101
|
},
|
106
102
|
};
|
107
103
|
}
|
104
|
+
function collectReadableValues(readable) {
|
105
|
+
const values = [];
|
106
|
+
readable.on('data', value => values.push(value));
|
107
|
+
return new Promise((resolve, reject) => {
|
108
|
+
readable.once('end', () => resolve(values));
|
109
|
+
readable.once('error', reject);
|
110
|
+
});
|
111
|
+
}
|
112
|
+
async function collectAsyncIterableValues(asyncIterable) {
|
113
|
+
const values = [];
|
114
|
+
for await (const value of asyncIterable) {
|
115
|
+
values.push(value);
|
116
|
+
}
|
117
|
+
return values;
|
118
|
+
}
|
119
|
+
async function collectReadableStreamValues(readableStream) {
|
120
|
+
const reader = readableStream.getReader();
|
121
|
+
const values = [];
|
122
|
+
while (true) {
|
123
|
+
const { done, value } = await reader.read();
|
124
|
+
if (done) {
|
125
|
+
reader.releaseLock();
|
126
|
+
break;
|
127
|
+
}
|
128
|
+
else if (value) {
|
129
|
+
values.push(value);
|
130
|
+
}
|
131
|
+
}
|
132
|
+
return values;
|
133
|
+
}
|
@@ -24,13 +24,17 @@ function createDefaultErrorResponse(ResponseCtor) {
|
|
24
24
|
return new ResponseCtor(null, { status: 500 });
|
25
25
|
}
|
26
26
|
class HTTPError extends Error {
|
27
|
+
status;
|
28
|
+
message;
|
29
|
+
headers;
|
30
|
+
details;
|
31
|
+
name = 'HTTPError';
|
27
32
|
constructor(status = 500, message, headers = {}, details) {
|
28
33
|
super(message);
|
29
34
|
this.status = status;
|
30
35
|
this.message = message;
|
31
36
|
this.headers = headers;
|
32
37
|
this.details = details;
|
33
|
-
this.name = 'HTTPError';
|
34
38
|
Error.captureStackTrace(this, HTTPError);
|
35
39
|
}
|
36
40
|
}
|
package/cjs/utils.js
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
"use strict";
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
3
|
-
exports.decompressedResponseMap = exports.ServerAdapterRequestAbortSignal = void 0;
|
3
|
+
exports.decompressedResponseMap = exports.nodeRequestResponseMap = exports.ServerAdapterRequestAbortSignal = void 0;
|
4
4
|
exports.isAsyncIterable = isAsyncIterable;
|
5
5
|
exports.normalizeNodeRequest = normalizeNodeRequest;
|
6
6
|
exports.isReadable = isReadable;
|
@@ -19,7 +19,7 @@ exports.createDeferredPromise = createDeferredPromise;
|
|
19
19
|
exports.handleAbortSignalAndPromiseResponse = handleAbortSignalAndPromiseResponse;
|
20
20
|
exports.getSupportedEncodings = getSupportedEncodings;
|
21
21
|
exports.handleResponseDecompression = handleResponseDecompression;
|
22
|
-
|
22
|
+
exports.ensureDisposableStackRegisteredForTerminateEvents = ensureDisposableStackRegisteredForTerminateEvents;
|
23
23
|
function isAsyncIterable(body) {
|
24
24
|
return (body != null && typeof body === 'object' && typeof body[Symbol.asyncIterator] === 'function');
|
25
25
|
}
|
@@ -70,11 +70,9 @@ function isRequestBody(body) {
|
|
70
70
|
return false;
|
71
71
|
}
|
72
72
|
class ServerAdapterRequestAbortSignal extends EventTarget {
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
this._onabort = null;
|
77
|
-
}
|
73
|
+
aborted = false;
|
74
|
+
_onabort = null;
|
75
|
+
reason;
|
78
76
|
throwIfAborted() {
|
79
77
|
if (this.aborted) {
|
80
78
|
throw this.reason;
|
@@ -103,17 +101,20 @@ class ServerAdapterRequestAbortSignal extends EventTarget {
|
|
103
101
|
}
|
104
102
|
exports.ServerAdapterRequestAbortSignal = ServerAdapterRequestAbortSignal;
|
105
103
|
let bunNodeCompatModeWarned = false;
|
106
|
-
|
104
|
+
exports.nodeRequestResponseMap = new WeakMap();
|
105
|
+
function normalizeNodeRequest(nodeRequest, fetchAPI, registerSignal) {
|
107
106
|
const rawRequest = nodeRequest.raw || nodeRequest.req || nodeRequest;
|
108
107
|
let fullUrl = buildFullUrl(rawRequest);
|
109
108
|
if (nodeRequest.query) {
|
110
|
-
const url = new
|
109
|
+
const url = new fetchAPI.URL(fullUrl);
|
111
110
|
for (const key in nodeRequest.query) {
|
112
111
|
url.searchParams.set(key, nodeRequest.query[key]);
|
113
112
|
}
|
114
113
|
fullUrl = url.toString();
|
115
114
|
}
|
116
115
|
let signal;
|
116
|
+
const nodeResponse = exports.nodeRequestResponseMap.get(nodeRequest);
|
117
|
+
exports.nodeRequestResponseMap.delete(nodeRequest);
|
117
118
|
let normalizedHeaders = nodeRequest.headers;
|
118
119
|
if (nodeRequest.headers?.[':method']) {
|
119
120
|
normalizedHeaders = {};
|
@@ -126,8 +127,10 @@ function normalizeNodeRequest(nodeRequest, nodeResponse, RequestCtor) {
|
|
126
127
|
if (nodeResponse?.once) {
|
127
128
|
let sendAbortSignal;
|
128
129
|
// If ponyfilled
|
129
|
-
if (
|
130
|
-
|
130
|
+
if (fetchAPI.Request !== globalThis.Request) {
|
131
|
+
const newSignal = new ServerAdapterRequestAbortSignal();
|
132
|
+
registerSignal?.(newSignal);
|
133
|
+
signal = newSignal;
|
131
134
|
sendAbortSignal = () => signal.sendAbort();
|
132
135
|
}
|
133
136
|
else {
|
@@ -148,7 +151,7 @@ function normalizeNodeRequest(nodeRequest, nodeResponse, RequestCtor) {
|
|
148
151
|
});
|
149
152
|
}
|
150
153
|
if (nodeRequest.method === 'GET' || nodeRequest.method === 'HEAD') {
|
151
|
-
return new
|
154
|
+
return new fetchAPI.Request(fullUrl, {
|
152
155
|
method: nodeRequest.method,
|
153
156
|
headers: normalizedHeaders,
|
154
157
|
signal,
|
@@ -163,14 +166,14 @@ function normalizeNodeRequest(nodeRequest, nodeResponse, RequestCtor) {
|
|
163
166
|
const maybeParsedBody = nodeRequest.body;
|
164
167
|
if (maybeParsedBody != null && Object.keys(maybeParsedBody).length > 0) {
|
165
168
|
if (isRequestBody(maybeParsedBody)) {
|
166
|
-
return new
|
169
|
+
return new fetchAPI.Request(fullUrl, {
|
167
170
|
method: nodeRequest.method,
|
168
171
|
headers: normalizedHeaders,
|
169
172
|
body: maybeParsedBody,
|
170
173
|
signal,
|
171
174
|
});
|
172
175
|
}
|
173
|
-
const request = new
|
176
|
+
const request = new fetchAPI.Request(fullUrl, {
|
174
177
|
method: nodeRequest.method,
|
175
178
|
headers: normalizedHeaders,
|
176
179
|
signal,
|
@@ -198,7 +201,7 @@ function normalizeNodeRequest(nodeRequest, nodeResponse, RequestCtor) {
|
|
198
201
|
console.warn(`You use Bun Node compatibility mode, which is not recommended!
|
199
202
|
It will affect your performance. Please check our Bun integration recipe, and avoid using 'http' for your server implementation.`);
|
200
203
|
}
|
201
|
-
return new
|
204
|
+
return new fetchAPI.Request(fullUrl, {
|
202
205
|
method: nodeRequest.method,
|
203
206
|
headers: normalizedHeaders,
|
204
207
|
duplex: 'half',
|
@@ -222,7 +225,7 @@ It will affect your performance. Please check our Bun integration recipe, and av
|
|
222
225
|
});
|
223
226
|
}
|
224
227
|
// perf: instead of spreading the object, we can just pass it as is and it performs better
|
225
|
-
return new
|
228
|
+
return new fetchAPI.Request(fullUrl, {
|
226
229
|
method: nodeRequest.method,
|
227
230
|
headers: normalizedHeaders,
|
228
231
|
body: rawRequest,
|
@@ -260,11 +263,26 @@ function endResponse(serverResponse) {
|
|
260
263
|
serverResponse.end(null, null, null);
|
261
264
|
}
|
262
265
|
async function sendAsyncIterable(serverResponse, asyncIterable) {
|
266
|
+
let closed = false;
|
267
|
+
const closeEventListener = () => {
|
268
|
+
closed = true;
|
269
|
+
};
|
270
|
+
serverResponse.once('error', closeEventListener);
|
271
|
+
serverResponse.once('close', closeEventListener);
|
272
|
+
serverResponse.once('finish', () => {
|
273
|
+
serverResponse.removeListener('close', closeEventListener);
|
274
|
+
});
|
263
275
|
for await (const chunk of asyncIterable) {
|
276
|
+
if (closed) {
|
277
|
+
break;
|
278
|
+
}
|
264
279
|
if (!serverResponse
|
265
280
|
// @ts-expect-error http and http2 writes are actually compatible
|
266
281
|
.write(chunk)) {
|
267
|
-
|
282
|
+
if (closed) {
|
283
|
+
break;
|
284
|
+
}
|
285
|
+
await new Promise(resolve => serverResponse.once('drain', resolve));
|
268
286
|
}
|
269
287
|
}
|
270
288
|
endResponse(serverResponse);
|
@@ -275,7 +293,7 @@ function sendNodeResponse(fetchResponse, serverResponse, nodeRequest) {
|
|
275
293
|
}
|
276
294
|
if (!fetchResponse) {
|
277
295
|
serverResponse.statusCode = 404;
|
278
|
-
serverResponse
|
296
|
+
endResponse(serverResponse);
|
279
297
|
return;
|
280
298
|
}
|
281
299
|
serverResponse.statusCode = fetchResponse.status;
|
@@ -324,10 +342,31 @@ function sendNodeResponse(fetchResponse, serverResponse, nodeRequest) {
|
|
324
342
|
fetchBody.pipe(serverResponse);
|
325
343
|
return;
|
326
344
|
}
|
345
|
+
if (isReadableStream(fetchBody)) {
|
346
|
+
return sendReadableStream(serverResponse, fetchBody);
|
347
|
+
}
|
327
348
|
if (isAsyncIterable(fetchBody)) {
|
328
349
|
return sendAsyncIterable(serverResponse, fetchBody);
|
329
350
|
}
|
330
351
|
}
|
352
|
+
async function sendReadableStream(serverResponse, readableStream) {
|
353
|
+
const reader = readableStream.getReader();
|
354
|
+
serverResponse.req.once('error', err => {
|
355
|
+
reader.cancel(err);
|
356
|
+
});
|
357
|
+
while (true) {
|
358
|
+
const { done, value } = await reader.read();
|
359
|
+
if (done) {
|
360
|
+
break;
|
361
|
+
}
|
362
|
+
if (!serverResponse
|
363
|
+
// @ts-expect-error http and http2 writes are actually compatible
|
364
|
+
.write(value)) {
|
365
|
+
await new Promise(resolve => serverResponse.once('drain', resolve));
|
366
|
+
}
|
367
|
+
}
|
368
|
+
endResponse(serverResponse);
|
369
|
+
}
|
331
370
|
function isRequestInit(val) {
|
332
371
|
return (val != null &&
|
333
372
|
typeof val === 'object' &&
|
@@ -404,77 +443,18 @@ function handleErrorFromRequestHandler(error, ResponseCtor) {
|
|
404
443
|
status: error.status || 500,
|
405
444
|
});
|
406
445
|
}
|
407
|
-
function isolateObject(originalCtx,
|
446
|
+
function isolateObject(originalCtx, waitUntilFn) {
|
408
447
|
if (originalCtx == null) {
|
409
|
-
|
410
|
-
|
411
|
-
|
412
|
-
|
413
|
-
|
414
|
-
|
415
|
-
|
416
|
-
|
417
|
-
|
418
|
-
|
419
|
-
}
|
420
|
-
const extraPropVal = extraProps[prop];
|
421
|
-
if (extraPropVal != null) {
|
422
|
-
if (typeof extraPropVal === 'function') {
|
423
|
-
return extraPropVal.bind(extraProps);
|
424
|
-
}
|
425
|
-
return extraPropVal;
|
426
|
-
}
|
427
|
-
if (deletedProps.has(prop)) {
|
428
|
-
return undefined;
|
429
|
-
}
|
430
|
-
return originalCtx[prop];
|
431
|
-
},
|
432
|
-
set(_originalCtx, prop, value) {
|
433
|
-
extraProps[prop] = value;
|
434
|
-
return true;
|
435
|
-
},
|
436
|
-
has(originalCtx, prop) {
|
437
|
-
if (waitUntilPromises != null && prop === 'waitUntil') {
|
438
|
-
return true;
|
439
|
-
}
|
440
|
-
if (deletedProps.has(prop)) {
|
441
|
-
return false;
|
442
|
-
}
|
443
|
-
if (prop in extraProps) {
|
444
|
-
return true;
|
445
|
-
}
|
446
|
-
return prop in originalCtx;
|
447
|
-
},
|
448
|
-
defineProperty(_originalCtx, prop, descriptor) {
|
449
|
-
return Reflect.defineProperty(extraProps, prop, descriptor);
|
450
|
-
},
|
451
|
-
deleteProperty(_originalCtx, prop) {
|
452
|
-
if (prop in extraProps) {
|
453
|
-
return Reflect.deleteProperty(extraProps, prop);
|
454
|
-
}
|
455
|
-
deletedProps.add(prop);
|
456
|
-
return true;
|
457
|
-
},
|
458
|
-
ownKeys(originalCtx) {
|
459
|
-
const extraKeys = Reflect.ownKeys(extraProps);
|
460
|
-
const originalKeys = Reflect.ownKeys(originalCtx);
|
461
|
-
const deletedKeys = Array.from(deletedProps);
|
462
|
-
const allKeys = new Set(extraKeys.concat(originalKeys.filter(keys => !deletedKeys.includes(keys))));
|
463
|
-
if (waitUntilPromises != null) {
|
464
|
-
allKeys.add('waitUntil');
|
465
|
-
}
|
466
|
-
return Array.from(allKeys);
|
467
|
-
},
|
468
|
-
getOwnPropertyDescriptor(originalCtx, prop) {
|
469
|
-
if (prop in extraProps) {
|
470
|
-
return Reflect.getOwnPropertyDescriptor(extraProps, prop);
|
471
|
-
}
|
472
|
-
if (deletedProps.has(prop)) {
|
473
|
-
return undefined;
|
474
|
-
}
|
475
|
-
return Reflect.getOwnPropertyDescriptor(originalCtx, prop);
|
476
|
-
},
|
477
|
-
});
|
448
|
+
if (waitUntilFn == null) {
|
449
|
+
return {};
|
450
|
+
}
|
451
|
+
return {
|
452
|
+
waitUntil: waitUntilFn,
|
453
|
+
};
|
454
|
+
}
|
455
|
+
return completeAssign(Object.create(originalCtx), {
|
456
|
+
waitUntil: waitUntilFn,
|
457
|
+
}, originalCtx);
|
478
458
|
}
|
479
459
|
function createDeferredPromise() {
|
480
460
|
let resolveFn;
|
@@ -516,22 +496,27 @@ function getSupportedEncodings(fetchAPI) {
|
|
516
496
|
let supportedEncodings = supportedEncodingsByFetchAPI.get(fetchAPI);
|
517
497
|
if (!supportedEncodings) {
|
518
498
|
const possibleEncodings = ['deflate', 'gzip', 'deflate-raw', 'br'];
|
519
|
-
|
520
|
-
|
521
|
-
|
522
|
-
|
523
|
-
|
524
|
-
|
525
|
-
|
526
|
-
|
527
|
-
|
528
|
-
|
529
|
-
|
530
|
-
|
531
|
-
|
532
|
-
|
533
|
-
|
534
|
-
|
499
|
+
if (fetchAPI.DecompressionStream?.['supportedFormats']) {
|
500
|
+
supportedEncodings = fetchAPI.DecompressionStream['supportedFormats'];
|
501
|
+
}
|
502
|
+
else {
|
503
|
+
supportedEncodings = possibleEncodings.filter(encoding => {
|
504
|
+
// deflate-raw is not supported in Node.js >v20
|
505
|
+
if (globalThis.process?.version?.startsWith('v2') &&
|
506
|
+
fetchAPI.DecompressionStream === globalThis.DecompressionStream &&
|
507
|
+
encoding === 'deflate-raw') {
|
508
|
+
return false;
|
509
|
+
}
|
510
|
+
try {
|
511
|
+
// eslint-disable-next-line no-new
|
512
|
+
new fetchAPI.DecompressionStream(encoding);
|
513
|
+
return true;
|
514
|
+
}
|
515
|
+
catch {
|
516
|
+
return false;
|
517
|
+
}
|
518
|
+
});
|
519
|
+
}
|
535
520
|
supportedEncodingsByFetchAPI.set(fetchAPI, supportedEncodings);
|
536
521
|
}
|
537
522
|
return supportedEncodings;
|
@@ -562,3 +547,31 @@ function handleResponseDecompression(response, fetchAPI) {
|
|
562
547
|
}
|
563
548
|
return decompressedResponse;
|
564
549
|
}
|
550
|
+
const terminateEvents = ['SIGINT', 'SIGTERM', 'exit'];
|
551
|
+
const disposableStacks = new Set();
|
552
|
+
let eventListenerRegistered = false;
|
553
|
+
function ensureEventListenerForDisposableStacks() {
|
554
|
+
if (eventListenerRegistered) {
|
555
|
+
return;
|
556
|
+
}
|
557
|
+
eventListenerRegistered = true;
|
558
|
+
for (const event of terminateEvents) {
|
559
|
+
globalThis.process.once(event, function terminateHandler() {
|
560
|
+
return Promise.allSettled([...disposableStacks].map(stack => !stack.disposed &&
|
561
|
+
stack.disposeAsync().catch(e => {
|
562
|
+
console.error('Error while disposing:', e);
|
563
|
+
})));
|
564
|
+
});
|
565
|
+
}
|
566
|
+
}
|
567
|
+
function ensureDisposableStackRegisteredForTerminateEvents(disposableStack) {
|
568
|
+
if (globalThis.process) {
|
569
|
+
ensureEventListenerForDisposableStacks();
|
570
|
+
if (!disposableStacks.has(disposableStack)) {
|
571
|
+
disposableStacks.add(disposableStack);
|
572
|
+
disposableStack.defer(() => {
|
573
|
+
disposableStacks.delete(disposableStack);
|
574
|
+
});
|
575
|
+
}
|
576
|
+
}
|
577
|
+
}
|