@whatwg-node/server 0.10.0-alpha-20240717150008-1474b9d9b679a0e8f6225f44f11b95a6f4bf24ea → 0.10.0-alpha-20241123133536-975c9068dde45574fcfa26567e4bab96f45d1f85
Sign up to get free protection for your applications and to get access to all the features.
- package/cjs/createServerAdapter.js +62 -24
- package/cjs/index.js +1 -0
- package/cjs/plugins/useContentEncoding.js +133 -0
- package/cjs/plugins/useErrorHandling.js +5 -1
- package/cjs/utils.js +174 -96
- package/cjs/uwebsockets.js +192 -27
- package/esm/createServerAdapter.js +63 -25
- package/esm/index.js +1 -0
- package/esm/plugins/useContentEncoding.js +130 -0
- package/esm/plugins/useErrorHandling.js +5 -1
- package/esm/utils.js +170 -95
- package/esm/uwebsockets.js +190 -27
- package/package.json +4 -3
- package/typings/index.d.cts +1 -0
- package/typings/index.d.ts +1 -0
- package/typings/plugins/types.d.cts +3 -0
- package/typings/plugins/types.d.ts +3 -0
- package/typings/plugins/useContentEncoding.d.cts +2 -0
- package/typings/plugins/useContentEncoding.d.ts +2 -0
- package/typings/plugins/useCors.d.cts +1 -1
- package/typings/plugins/useCors.d.ts +1 -1
- package/typings/plugins/useErrorHandling.d.cts +3 -3
- package/typings/plugins/useErrorHandling.d.ts +3 -3
- package/typings/types.d.cts +10 -2
- package/typings/types.d.ts +10 -2
- package/typings/utils.d.cts +8 -3
- package/typings/utils.d.ts +8 -3
- package/typings/uwebsockets.d.cts +3 -1
- package/typings/uwebsockets.d.ts +3 -1
package/esm/utils.js
CHANGED
@@ -1,4 +1,3 @@
|
|
1
|
-
import { URL } from '@whatwg-node/fetch';
|
2
1
|
export function isAsyncIterable(body) {
|
3
2
|
return (body != null && typeof body === 'object' && typeof body[Symbol.asyncIterator] === 'function');
|
4
3
|
}
|
@@ -49,11 +48,9 @@ function isRequestBody(body) {
|
|
49
48
|
return false;
|
50
49
|
}
|
51
50
|
export class ServerAdapterRequestAbortSignal extends EventTarget {
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
this._onabort = null;
|
56
|
-
}
|
51
|
+
aborted = false;
|
52
|
+
_onabort = null;
|
53
|
+
reason;
|
57
54
|
throwIfAborted() {
|
58
55
|
if (this.aborted) {
|
59
56
|
throw this.reason;
|
@@ -81,22 +78,36 @@ export class ServerAdapterRequestAbortSignal extends EventTarget {
|
|
81
78
|
}
|
82
79
|
}
|
83
80
|
let bunNodeCompatModeWarned = false;
|
84
|
-
export
|
81
|
+
export const nodeRequestResponseMap = new WeakMap();
|
82
|
+
export function normalizeNodeRequest(nodeRequest, fetchAPI, registerSignal) {
|
85
83
|
const rawRequest = nodeRequest.raw || nodeRequest.req || nodeRequest;
|
86
84
|
let fullUrl = buildFullUrl(rawRequest);
|
87
85
|
if (nodeRequest.query) {
|
88
|
-
const url = new URL(fullUrl);
|
86
|
+
const url = new fetchAPI.URL(fullUrl);
|
89
87
|
for (const key in nodeRequest.query) {
|
90
88
|
url.searchParams.set(key, nodeRequest.query[key]);
|
91
89
|
}
|
92
90
|
fullUrl = url.toString();
|
93
91
|
}
|
94
92
|
let signal;
|
95
|
-
|
93
|
+
const nodeResponse = nodeRequestResponseMap.get(nodeRequest);
|
94
|
+
nodeRequestResponseMap.delete(nodeRequest);
|
95
|
+
let normalizedHeaders = nodeRequest.headers;
|
96
|
+
if (nodeRequest.headers?.[':method']) {
|
97
|
+
normalizedHeaders = {};
|
98
|
+
for (const key in nodeRequest.headers) {
|
99
|
+
if (!key.startsWith(':')) {
|
100
|
+
normalizedHeaders[key] = nodeRequest.headers[key];
|
101
|
+
}
|
102
|
+
}
|
103
|
+
}
|
104
|
+
if (nodeResponse?.once) {
|
96
105
|
let sendAbortSignal;
|
97
106
|
// If ponyfilled
|
98
|
-
if (
|
99
|
-
|
107
|
+
if (fetchAPI.Request !== globalThis.Request) {
|
108
|
+
const newSignal = new ServerAdapterRequestAbortSignal();
|
109
|
+
registerSignal?.(newSignal);
|
110
|
+
signal = newSignal;
|
100
111
|
sendAbortSignal = () => signal.sendAbort();
|
101
112
|
}
|
102
113
|
else {
|
@@ -117,9 +128,9 @@ export function normalizeNodeRequest(nodeRequest, nodeResponse, RequestCtor) {
|
|
117
128
|
});
|
118
129
|
}
|
119
130
|
if (nodeRequest.method === 'GET' || nodeRequest.method === 'HEAD') {
|
120
|
-
return new
|
131
|
+
return new fetchAPI.Request(fullUrl, {
|
121
132
|
method: nodeRequest.method,
|
122
|
-
headers:
|
133
|
+
headers: normalizedHeaders,
|
123
134
|
signal,
|
124
135
|
});
|
125
136
|
}
|
@@ -132,16 +143,16 @@ export function normalizeNodeRequest(nodeRequest, nodeResponse, RequestCtor) {
|
|
132
143
|
const maybeParsedBody = nodeRequest.body;
|
133
144
|
if (maybeParsedBody != null && Object.keys(maybeParsedBody).length > 0) {
|
134
145
|
if (isRequestBody(maybeParsedBody)) {
|
135
|
-
return new
|
146
|
+
return new fetchAPI.Request(fullUrl, {
|
136
147
|
method: nodeRequest.method,
|
137
|
-
headers:
|
148
|
+
headers: normalizedHeaders,
|
138
149
|
body: maybeParsedBody,
|
139
150
|
signal,
|
140
151
|
});
|
141
152
|
}
|
142
|
-
const request = new
|
153
|
+
const request = new fetchAPI.Request(fullUrl, {
|
143
154
|
method: nodeRequest.method,
|
144
|
-
headers:
|
155
|
+
headers: normalizedHeaders,
|
145
156
|
signal,
|
146
157
|
});
|
147
158
|
if (!request.headers.get('content-type')?.includes('json')) {
|
@@ -167,9 +178,9 @@ export function normalizeNodeRequest(nodeRequest, nodeResponse, RequestCtor) {
|
|
167
178
|
console.warn(`You use Bun Node compatibility mode, which is not recommended!
|
168
179
|
It will affect your performance. Please check our Bun integration recipe, and avoid using 'http' for your server implementation.`);
|
169
180
|
}
|
170
|
-
return new
|
181
|
+
return new fetchAPI.Request(fullUrl, {
|
171
182
|
method: nodeRequest.method,
|
172
|
-
headers:
|
183
|
+
headers: normalizedHeaders,
|
173
184
|
duplex: 'half',
|
174
185
|
body: new ReadableStream({
|
175
186
|
start(controller) {
|
@@ -191,9 +202,9 @@ It will affect your performance. Please check our Bun integration recipe, and av
|
|
191
202
|
});
|
192
203
|
}
|
193
204
|
// perf: instead of spreading the object, we can just pass it as is and it performs better
|
194
|
-
return new
|
205
|
+
return new fetchAPI.Request(fullUrl, {
|
195
206
|
method: nodeRequest.method,
|
196
|
-
headers:
|
207
|
+
headers: normalizedHeaders,
|
197
208
|
body: rawRequest,
|
198
209
|
duplex: 'half',
|
199
210
|
signal,
|
@@ -229,11 +240,26 @@ function endResponse(serverResponse) {
|
|
229
240
|
serverResponse.end(null, null, null);
|
230
241
|
}
|
231
242
|
async function sendAsyncIterable(serverResponse, asyncIterable) {
|
243
|
+
let closed = false;
|
244
|
+
const closeEventListener = () => {
|
245
|
+
closed = true;
|
246
|
+
};
|
247
|
+
serverResponse.once('error', closeEventListener);
|
248
|
+
serverResponse.once('close', closeEventListener);
|
249
|
+
serverResponse.once('finish', () => {
|
250
|
+
serverResponse.removeListener('close', closeEventListener);
|
251
|
+
});
|
232
252
|
for await (const chunk of asyncIterable) {
|
253
|
+
if (closed) {
|
254
|
+
break;
|
255
|
+
}
|
233
256
|
if (!serverResponse
|
234
257
|
// @ts-expect-error http and http2 writes are actually compatible
|
235
258
|
.write(chunk)) {
|
236
|
-
|
259
|
+
if (closed) {
|
260
|
+
break;
|
261
|
+
}
|
262
|
+
await new Promise(resolve => serverResponse.once('drain', resolve));
|
237
263
|
}
|
238
264
|
}
|
239
265
|
endResponse(serverResponse);
|
@@ -244,7 +270,7 @@ export function sendNodeResponse(fetchResponse, serverResponse, nodeRequest) {
|
|
244
270
|
}
|
245
271
|
if (!fetchResponse) {
|
246
272
|
serverResponse.statusCode = 404;
|
247
|
-
serverResponse
|
273
|
+
endResponse(serverResponse);
|
248
274
|
return;
|
249
275
|
}
|
250
276
|
serverResponse.statusCode = fetchResponse.status;
|
@@ -293,10 +319,31 @@ export function sendNodeResponse(fetchResponse, serverResponse, nodeRequest) {
|
|
293
319
|
fetchBody.pipe(serverResponse);
|
294
320
|
return;
|
295
321
|
}
|
322
|
+
if (isReadableStream(fetchBody)) {
|
323
|
+
return sendReadableStream(serverResponse, fetchBody);
|
324
|
+
}
|
296
325
|
if (isAsyncIterable(fetchBody)) {
|
297
326
|
return sendAsyncIterable(serverResponse, fetchBody);
|
298
327
|
}
|
299
328
|
}
|
329
|
+
async function sendReadableStream(serverResponse, readableStream) {
|
330
|
+
const reader = readableStream.getReader();
|
331
|
+
serverResponse.req.once('error', err => {
|
332
|
+
reader.cancel(err);
|
333
|
+
});
|
334
|
+
while (true) {
|
335
|
+
const { done, value } = await reader.read();
|
336
|
+
if (done) {
|
337
|
+
break;
|
338
|
+
}
|
339
|
+
if (!serverResponse
|
340
|
+
// @ts-expect-error http and http2 writes are actually compatible
|
341
|
+
.write(value)) {
|
342
|
+
await new Promise(resolve => serverResponse.once('drain', resolve));
|
343
|
+
}
|
344
|
+
}
|
345
|
+
endResponse(serverResponse);
|
346
|
+
}
|
300
347
|
export function isRequestInit(val) {
|
301
348
|
return (val != null &&
|
302
349
|
typeof val === 'object' &&
|
@@ -321,13 +368,16 @@ export function completeAssign(...args) {
|
|
321
368
|
// modified Object.keys to Object.getOwnPropertyNames
|
322
369
|
// because Object.keys only returns enumerable properties
|
323
370
|
const descriptors = Object.getOwnPropertyNames(source).reduce((descriptors, key) => {
|
324
|
-
|
371
|
+
const descriptor = Object.getOwnPropertyDescriptor(source, key);
|
372
|
+
if (descriptor) {
|
373
|
+
descriptors[key] = Object.getOwnPropertyDescriptor(source, key);
|
374
|
+
}
|
325
375
|
return descriptors;
|
326
376
|
}, {});
|
327
377
|
// By default, Object.assign copies enumerable Symbols, too
|
328
378
|
Object.getOwnPropertySymbols(source).forEach(sym => {
|
329
379
|
const descriptor = Object.getOwnPropertyDescriptor(source, sym);
|
330
|
-
if (descriptor
|
380
|
+
if (descriptor?.enumerable) {
|
331
381
|
descriptors[sym] = descriptor;
|
332
382
|
}
|
333
383
|
});
|
@@ -370,77 +420,18 @@ export function handleErrorFromRequestHandler(error, ResponseCtor) {
|
|
370
420
|
status: error.status || 500,
|
371
421
|
});
|
372
422
|
}
|
373
|
-
export function isolateObject(originalCtx,
|
423
|
+
export function isolateObject(originalCtx, waitUntilFn) {
|
374
424
|
if (originalCtx == null) {
|
375
|
-
|
376
|
-
|
377
|
-
|
378
|
-
|
379
|
-
|
380
|
-
|
381
|
-
|
382
|
-
|
383
|
-
|
384
|
-
|
385
|
-
}
|
386
|
-
const extraPropVal = extraProps[prop];
|
387
|
-
if (extraPropVal != null) {
|
388
|
-
if (typeof extraPropVal === 'function') {
|
389
|
-
return extraPropVal.bind(extraProps);
|
390
|
-
}
|
391
|
-
return extraPropVal;
|
392
|
-
}
|
393
|
-
if (deletedProps.has(prop)) {
|
394
|
-
return undefined;
|
395
|
-
}
|
396
|
-
return originalCtx[prop];
|
397
|
-
},
|
398
|
-
set(_originalCtx, prop, value) {
|
399
|
-
extraProps[prop] = value;
|
400
|
-
return true;
|
401
|
-
},
|
402
|
-
has(originalCtx, prop) {
|
403
|
-
if (waitUntilPromises != null && prop === 'waitUntil') {
|
404
|
-
return true;
|
405
|
-
}
|
406
|
-
if (deletedProps.has(prop)) {
|
407
|
-
return false;
|
408
|
-
}
|
409
|
-
if (prop in extraProps) {
|
410
|
-
return true;
|
411
|
-
}
|
412
|
-
return prop in originalCtx;
|
413
|
-
},
|
414
|
-
defineProperty(_originalCtx, prop, descriptor) {
|
415
|
-
return Reflect.defineProperty(extraProps, prop, descriptor);
|
416
|
-
},
|
417
|
-
deleteProperty(_originalCtx, prop) {
|
418
|
-
if (prop in extraProps) {
|
419
|
-
return Reflect.deleteProperty(extraProps, prop);
|
420
|
-
}
|
421
|
-
deletedProps.add(prop);
|
422
|
-
return true;
|
423
|
-
},
|
424
|
-
ownKeys(originalCtx) {
|
425
|
-
const extraKeys = Reflect.ownKeys(extraProps);
|
426
|
-
const originalKeys = Reflect.ownKeys(originalCtx);
|
427
|
-
const deletedKeys = Array.from(deletedProps);
|
428
|
-
const allKeys = new Set(extraKeys.concat(originalKeys.filter(keys => !deletedKeys.includes(keys))));
|
429
|
-
if (waitUntilPromises != null) {
|
430
|
-
allKeys.add('waitUntil');
|
431
|
-
}
|
432
|
-
return Array.from(allKeys);
|
433
|
-
},
|
434
|
-
getOwnPropertyDescriptor(originalCtx, prop) {
|
435
|
-
if (prop in extraProps) {
|
436
|
-
return Reflect.getOwnPropertyDescriptor(extraProps, prop);
|
437
|
-
}
|
438
|
-
if (deletedProps.has(prop)) {
|
439
|
-
return undefined;
|
440
|
-
}
|
441
|
-
return Reflect.getOwnPropertyDescriptor(originalCtx, prop);
|
442
|
-
},
|
443
|
-
});
|
425
|
+
if (waitUntilFn == null) {
|
426
|
+
return {};
|
427
|
+
}
|
428
|
+
return {
|
429
|
+
waitUntil: waitUntilFn,
|
430
|
+
};
|
431
|
+
}
|
432
|
+
return completeAssign(Object.create(originalCtx), {
|
433
|
+
waitUntil: waitUntilFn,
|
434
|
+
}, originalCtx);
|
444
435
|
}
|
445
436
|
export function createDeferredPromise() {
|
446
437
|
let resolveFn;
|
@@ -476,3 +467,87 @@ export function handleAbortSignalAndPromiseResponse(response$, abortSignal) {
|
|
476
467
|
}
|
477
468
|
return response$;
|
478
469
|
}
|
470
|
+
export const decompressedResponseMap = new WeakMap();
|
471
|
+
const supportedEncodingsByFetchAPI = new WeakMap();
|
472
|
+
export function getSupportedEncodings(fetchAPI) {
|
473
|
+
let supportedEncodings = supportedEncodingsByFetchAPI.get(fetchAPI);
|
474
|
+
if (!supportedEncodings) {
|
475
|
+
const possibleEncodings = ['deflate', 'gzip', 'deflate-raw', 'br'];
|
476
|
+
if (fetchAPI.DecompressionStream?.['supportedFormats']) {
|
477
|
+
supportedEncodings = fetchAPI.DecompressionStream['supportedFormats'];
|
478
|
+
}
|
479
|
+
else {
|
480
|
+
supportedEncodings = possibleEncodings.filter(encoding => {
|
481
|
+
// deflate-raw is not supported in Node.js >v20
|
482
|
+
if (globalThis.process?.version?.startsWith('v2') &&
|
483
|
+
fetchAPI.DecompressionStream === globalThis.DecompressionStream &&
|
484
|
+
encoding === 'deflate-raw') {
|
485
|
+
return false;
|
486
|
+
}
|
487
|
+
try {
|
488
|
+
// eslint-disable-next-line no-new
|
489
|
+
new fetchAPI.DecompressionStream(encoding);
|
490
|
+
return true;
|
491
|
+
}
|
492
|
+
catch {
|
493
|
+
return false;
|
494
|
+
}
|
495
|
+
});
|
496
|
+
}
|
497
|
+
supportedEncodingsByFetchAPI.set(fetchAPI, supportedEncodings);
|
498
|
+
}
|
499
|
+
return supportedEncodings;
|
500
|
+
}
|
501
|
+
export function handleResponseDecompression(response, fetchAPI) {
|
502
|
+
const contentEncodingHeader = response?.headers.get('content-encoding');
|
503
|
+
if (!contentEncodingHeader || contentEncodingHeader === 'none') {
|
504
|
+
return response;
|
505
|
+
}
|
506
|
+
if (!response?.body) {
|
507
|
+
return response;
|
508
|
+
}
|
509
|
+
let decompressedResponse = decompressedResponseMap.get(response);
|
510
|
+
if (!decompressedResponse || decompressedResponse.bodyUsed) {
|
511
|
+
let decompressedBody = response.body;
|
512
|
+
const contentEncodings = contentEncodingHeader.split(',');
|
513
|
+
if (!contentEncodings.every(encoding => getSupportedEncodings(fetchAPI).includes(encoding))) {
|
514
|
+
return new fetchAPI.Response(`Unsupported 'Content-Encoding': ${contentEncodingHeader}`, {
|
515
|
+
status: 415,
|
516
|
+
statusText: 'Unsupported Media Type',
|
517
|
+
});
|
518
|
+
}
|
519
|
+
for (const contentEncoding of contentEncodings) {
|
520
|
+
decompressedBody = decompressedBody.pipeThrough(new fetchAPI.DecompressionStream(contentEncoding));
|
521
|
+
}
|
522
|
+
decompressedResponse = new fetchAPI.Response(decompressedBody, response);
|
523
|
+
decompressedResponseMap.set(response, decompressedResponse);
|
524
|
+
}
|
525
|
+
return decompressedResponse;
|
526
|
+
}
|
527
|
+
const terminateEvents = ['SIGINT', 'SIGTERM', 'exit'];
|
528
|
+
const disposableStacks = new Set();
|
529
|
+
let eventListenerRegistered = false;
|
530
|
+
function ensureEventListenerForDisposableStacks() {
|
531
|
+
if (eventListenerRegistered) {
|
532
|
+
return;
|
533
|
+
}
|
534
|
+
eventListenerRegistered = true;
|
535
|
+
for (const event of terminateEvents) {
|
536
|
+
globalThis.process.once(event, function terminateHandler() {
|
537
|
+
return Promise.allSettled([...disposableStacks].map(stack => stack.disposeAsync().catch(e => {
|
538
|
+
console.error('Error while disposing:', e);
|
539
|
+
})));
|
540
|
+
});
|
541
|
+
}
|
542
|
+
}
|
543
|
+
export function ensureDisposableStackRegisteredForTerminateEvents(disposableStack) {
|
544
|
+
if (globalThis.process) {
|
545
|
+
ensureEventListenerForDisposableStacks();
|
546
|
+
if (!disposableStacks.has(disposableStack)) {
|
547
|
+
disposableStacks.add(disposableStack);
|
548
|
+
disposableStack.defer(() => {
|
549
|
+
disposableStacks.delete(disposableStack);
|
550
|
+
});
|
551
|
+
}
|
552
|
+
}
|
553
|
+
}
|
package/esm/uwebsockets.js
CHANGED
@@ -1,22 +1,69 @@
|
|
1
|
+
import { isPromise } from './utils.js';
|
1
2
|
export function isUWSResponse(res) {
|
2
3
|
return !!res.onData;
|
3
4
|
}
|
4
5
|
export function getRequestFromUWSRequest({ req, res, fetchAPI, signal }) {
|
5
|
-
let body;
|
6
6
|
const method = req.getMethod();
|
7
|
+
let duplex;
|
8
|
+
const chunks = [];
|
9
|
+
const pushFns = [
|
10
|
+
(chunk) => {
|
11
|
+
chunks.push(chunk);
|
12
|
+
},
|
13
|
+
];
|
14
|
+
const push = (chunk) => {
|
15
|
+
for (const pushFn of pushFns) {
|
16
|
+
pushFn(chunk);
|
17
|
+
}
|
18
|
+
};
|
19
|
+
let stopped = false;
|
20
|
+
const stopFns = [
|
21
|
+
() => {
|
22
|
+
stopped = true;
|
23
|
+
},
|
24
|
+
];
|
25
|
+
const stop = () => {
|
26
|
+
for (const stopFn of stopFns) {
|
27
|
+
stopFn();
|
28
|
+
}
|
29
|
+
};
|
30
|
+
res.onData(function (ab, isLast) {
|
31
|
+
push(Buffer.from(Buffer.from(ab, 0, ab.byteLength)));
|
32
|
+
if (isLast) {
|
33
|
+
stop();
|
34
|
+
}
|
35
|
+
});
|
36
|
+
let getReadableStream;
|
7
37
|
if (method !== 'get' && method !== 'head') {
|
8
|
-
|
9
|
-
const readable = body.readable;
|
38
|
+
duplex = 'half';
|
10
39
|
signal.addEventListener('abort', () => {
|
11
|
-
|
40
|
+
stop();
|
12
41
|
});
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
42
|
+
let readableStream;
|
43
|
+
getReadableStream = () => {
|
44
|
+
if (!readableStream) {
|
45
|
+
readableStream = new fetchAPI.ReadableStream({
|
46
|
+
start(controller) {
|
47
|
+
for (const chunk of chunks) {
|
48
|
+
controller.enqueue(chunk);
|
49
|
+
}
|
50
|
+
if (stopped) {
|
51
|
+
controller.close();
|
52
|
+
return;
|
53
|
+
}
|
54
|
+
pushFns.push((chunk) => {
|
55
|
+
controller.enqueue(chunk);
|
56
|
+
});
|
57
|
+
stopFns.push(() => {
|
58
|
+
if (controller.desiredSize) {
|
59
|
+
controller.close();
|
60
|
+
}
|
61
|
+
});
|
62
|
+
},
|
63
|
+
});
|
18
64
|
}
|
19
|
-
|
65
|
+
return readableStream;
|
66
|
+
};
|
20
67
|
}
|
21
68
|
const headers = new fetchAPI.Headers();
|
22
69
|
req.forEach((key, value) => {
|
@@ -27,27 +74,97 @@ export function getRequestFromUWSRequest({ req, res, fetchAPI, signal }) {
|
|
27
74
|
if (query) {
|
28
75
|
url += `?${query}`;
|
29
76
|
}
|
30
|
-
|
77
|
+
let buffer;
|
78
|
+
function getBody() {
|
79
|
+
if (!getReadableStream) {
|
80
|
+
return null;
|
81
|
+
}
|
82
|
+
if (stopped) {
|
83
|
+
return getBufferFromChunks();
|
84
|
+
}
|
85
|
+
return getReadableStream();
|
86
|
+
}
|
87
|
+
const request = new fetchAPI.Request(url, {
|
31
88
|
method,
|
32
89
|
headers,
|
33
|
-
body
|
90
|
+
get body() {
|
91
|
+
return getBody();
|
92
|
+
},
|
34
93
|
signal,
|
94
|
+
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
95
|
+
// @ts-ignore - not in the TS types yet
|
96
|
+
duplex,
|
35
97
|
});
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
if (signal.aborted) {
|
40
|
-
return;
|
98
|
+
function getBufferFromChunks() {
|
99
|
+
if (!buffer) {
|
100
|
+
buffer = chunks.length === 1 ? chunks[0] : Buffer.concat(chunks);
|
41
101
|
}
|
42
|
-
|
43
|
-
|
102
|
+
return buffer;
|
103
|
+
}
|
104
|
+
function collectBuffer() {
|
105
|
+
if (stopped) {
|
106
|
+
return fakePromise(getBufferFromChunks());
|
107
|
+
}
|
108
|
+
return new Promise((resolve, reject) => {
|
109
|
+
try {
|
110
|
+
stopFns.push(() => {
|
111
|
+
resolve(getBufferFromChunks());
|
112
|
+
});
|
113
|
+
}
|
114
|
+
catch (e) {
|
115
|
+
reject(e);
|
116
|
+
}
|
44
117
|
});
|
45
118
|
}
|
46
|
-
|
47
|
-
|
119
|
+
Object.defineProperties(request, {
|
120
|
+
body: {
|
121
|
+
get() {
|
122
|
+
return getBody();
|
123
|
+
},
|
124
|
+
configurable: true,
|
125
|
+
enumerable: true,
|
126
|
+
},
|
127
|
+
json: {
|
128
|
+
value() {
|
129
|
+
return collectBuffer()
|
130
|
+
.then(b => b.toString('utf8'))
|
131
|
+
.then(t => JSON.parse(t));
|
132
|
+
},
|
133
|
+
configurable: true,
|
134
|
+
enumerable: true,
|
135
|
+
},
|
136
|
+
text: {
|
137
|
+
value() {
|
138
|
+
return collectBuffer().then(b => b.toString('utf8'));
|
139
|
+
},
|
140
|
+
configurable: true,
|
141
|
+
enumerable: true,
|
142
|
+
},
|
143
|
+
arrayBuffer: {
|
144
|
+
value() {
|
145
|
+
return collectBuffer();
|
146
|
+
},
|
147
|
+
configurable: true,
|
148
|
+
enumerable: true,
|
149
|
+
},
|
150
|
+
});
|
151
|
+
return request;
|
152
|
+
}
|
153
|
+
export function createWritableFromUWS(uwsResponse, fetchAPI) {
|
154
|
+
return new fetchAPI.WritableStream({
|
155
|
+
write(chunk) {
|
156
|
+
uwsResponse.cork(() => {
|
157
|
+
uwsResponse.write(chunk);
|
158
|
+
});
|
159
|
+
},
|
160
|
+
close() {
|
161
|
+
uwsResponse.cork(() => {
|
162
|
+
uwsResponse.end();
|
163
|
+
});
|
164
|
+
},
|
48
165
|
});
|
49
166
|
}
|
50
|
-
export function sendResponseToUwsOpts(uwsResponse, fetchResponse, signal) {
|
167
|
+
export function sendResponseToUwsOpts(uwsResponse, fetchResponse, signal, fetchAPI) {
|
51
168
|
if (!fetchResponse) {
|
52
169
|
uwsResponse.writeStatus('404 Not Found');
|
53
170
|
uwsResponse.end();
|
@@ -77,13 +194,59 @@ export function sendResponseToUwsOpts(uwsResponse, fetchResponse, signal) {
|
|
77
194
|
if (bufferOfRes) {
|
78
195
|
uwsResponse.end(bufferOfRes);
|
79
196
|
}
|
197
|
+
else if (!fetchResponse.body) {
|
198
|
+
uwsResponse.end();
|
199
|
+
}
|
80
200
|
});
|
81
|
-
if (bufferOfRes) {
|
201
|
+
if (bufferOfRes || !fetchResponse.body) {
|
82
202
|
return;
|
83
203
|
}
|
84
|
-
|
85
|
-
|
86
|
-
|
204
|
+
signal.addEventListener('abort', () => {
|
205
|
+
if (!fetchResponse.body?.locked) {
|
206
|
+
fetchResponse.body?.cancel(signal.reason);
|
207
|
+
}
|
208
|
+
});
|
209
|
+
return fetchResponse.body
|
210
|
+
.pipeTo(createWritableFromUWS(uwsResponse, fetchAPI), {
|
211
|
+
signal,
|
212
|
+
})
|
213
|
+
.catch(err => {
|
214
|
+
if (signal.aborted) {
|
215
|
+
return;
|
216
|
+
}
|
217
|
+
throw err;
|
218
|
+
});
|
219
|
+
}
|
220
|
+
export function fakePromise(value) {
|
221
|
+
if (isPromise(value)) {
|
222
|
+
return value;
|
87
223
|
}
|
88
|
-
|
224
|
+
// Write a fake promise to avoid the promise constructor
|
225
|
+
// being called with `new Promise` in the browser.
|
226
|
+
return {
|
227
|
+
then(resolve) {
|
228
|
+
if (resolve) {
|
229
|
+
const callbackResult = resolve(value);
|
230
|
+
if (isPromise(callbackResult)) {
|
231
|
+
return callbackResult;
|
232
|
+
}
|
233
|
+
return fakePromise(callbackResult);
|
234
|
+
}
|
235
|
+
return this;
|
236
|
+
},
|
237
|
+
catch() {
|
238
|
+
return this;
|
239
|
+
},
|
240
|
+
finally(cb) {
|
241
|
+
if (cb) {
|
242
|
+
const callbackResult = cb();
|
243
|
+
if (isPromise(callbackResult)) {
|
244
|
+
return callbackResult.then(() => value);
|
245
|
+
}
|
246
|
+
return fakePromise(value);
|
247
|
+
}
|
248
|
+
return this;
|
249
|
+
},
|
250
|
+
[Symbol.toStringTag]: 'Promise',
|
251
|
+
};
|
89
252
|
}
|
package/package.json
CHANGED
@@ -1,10 +1,11 @@
|
|
1
1
|
{
|
2
2
|
"name": "@whatwg-node/server",
|
3
|
-
"version": "0.10.0-alpha-
|
3
|
+
"version": "0.10.0-alpha-20241123133536-975c9068dde45574fcfa26567e4bab96f45d1f85",
|
4
4
|
"description": "Fetch API compliant HTTP Server adapter",
|
5
5
|
"sideEffects": false,
|
6
6
|
"dependencies": {
|
7
|
-
"@whatwg-node/
|
7
|
+
"@whatwg-node/disposablestack": "^0.0.5",
|
8
|
+
"@whatwg-node/fetch": "^0.10.0",
|
8
9
|
"tslib": "^2.6.3"
|
9
10
|
},
|
10
11
|
"repository": {
|
@@ -15,7 +16,7 @@
|
|
15
16
|
"author": "Arda TANRIKULU <ardatanrikulu@gmail.com>",
|
16
17
|
"license": "MIT",
|
17
18
|
"engines": {
|
18
|
-
"node": ">=
|
19
|
+
"node": ">=18.0.0"
|
19
20
|
},
|
20
21
|
"main": "cjs/index.js",
|
21
22
|
"module": "esm/index.js",
|
package/typings/index.d.cts
CHANGED
@@ -4,5 +4,6 @@ export * from './utils.cjs';
|
|
4
4
|
export * from './plugins/types.cjs';
|
5
5
|
export * from './plugins/useCors.cjs';
|
6
6
|
export * from './plugins/useErrorHandling.cjs';
|
7
|
+
export * from './plugins/useContentEncoding.cjs';
|
7
8
|
export * from './uwebsockets.cjs';
|
8
9
|
export { Response } from '@whatwg-node/fetch';
|
package/typings/index.d.ts
CHANGED
@@ -4,5 +4,6 @@ export * from './utils.js';
|
|
4
4
|
export * from './plugins/types.js';
|
5
5
|
export * from './plugins/useCors.js';
|
6
6
|
export * from './plugins/useErrorHandling.js';
|
7
|
+
export * from './plugins/useContentEncoding.js';
|
7
8
|
export * from './uwebsockets.js';
|
8
9
|
export { Response } from '@whatwg-node/fetch';
|
@@ -6,6 +6,7 @@ export interface ServerAdapterPlugin<TServerContext = {}> {
|
|
6
6
|
export type OnRequestHook<TServerContext> = (payload: OnRequestEventPayload<TServerContext>) => Promise<void> | void;
|
7
7
|
export interface OnRequestEventPayload<TServerContext> {
|
8
8
|
request: Request;
|
9
|
+
setRequest(newRequest: Request): void;
|
9
10
|
serverContext: TServerContext;
|
10
11
|
fetchAPI: FetchAPI;
|
11
12
|
requestHandler: ServerAdapterRequestHandler<TServerContext>;
|
@@ -18,4 +19,6 @@ export interface OnResponseEventPayload<TServerContext> {
|
|
18
19
|
request: Request;
|
19
20
|
serverContext: TServerContext;
|
20
21
|
response: Response;
|
22
|
+
setResponse(newResponse: Response): void;
|
23
|
+
fetchAPI: FetchAPI;
|
21
24
|
}
|