@whatwg-node/server 0.10.0-alpha-20241125144944-df106a17746126e3c8089afa16af852a4483c8b3 → 0.10.0-alpha-20250205010145-5ab5edf75d82f8174c999dc970ad9a042ea5547d

Sign up to get free protection for your applications and to get access to all the features.
package/README.md CHANGED
@@ -40,7 +40,7 @@ nodeServer.listen(4000)
40
40
  ### AWS Lambda
41
41
 
42
42
  AWS Lambda is a serverless computing platform that makes it easy to build applications that run on
43
- the AWS cloud. Our adaoter is platform agnostic so they can fit together easily. In order to reduce
43
+ the AWS cloud. Our adapter is platform agnostic so they can fit together easily. In order to reduce
44
44
  the boilerplate we prefer to use
45
45
  [Serverless Express from Vendia](https://github.com/vendia/serverless-express).
46
46
 
@@ -167,10 +167,15 @@ app.route({
167
167
  url: '/mypath',
168
168
  method: ['GET', 'POST', 'OPTIONS'],
169
169
  handler: async (req, reply) => {
170
- const response = await myServerAdapter.handleNodeRequestAndResponse(req, reply, {
170
+ const response: Response = await myServerAdapter.handleNodeRequestAndResponse(req, reply, {
171
171
  req,
172
172
  reply
173
173
  })
174
+
175
+ if (!response) {
176
+ return reply.status(404).send('Not Found')
177
+ }
178
+
174
179
  response.headers.forEach((value, key) => {
175
180
  reply.header(key, value)
176
181
  })
@@ -200,7 +205,7 @@ import myServerAdapter from './myServerAdapter'
200
205
  const app = new Koa()
201
206
 
202
207
  app.use(async ctx => {
203
- const response = await myServerAdapter.handleNodeRequest(ctx.req)
208
+ const response = await myServerAdapter.handleNodeRequestAndResponse(ctx.request, ctx.res, ctx)
204
209
 
205
210
  // Set status code
206
211
  ctx.status = response.status
@@ -258,9 +263,7 @@ as a first class citizen. So the configuration is really simple like any other J
258
263
  ```ts
259
264
  import myServerAdapter from './myServerAdapter'
260
265
 
261
- Bun.serve(myServerAdapter)
262
-
263
- const server = Bun.serve(yoga)
266
+ const server = Bun.serve(myServerAdapter)
264
267
 
265
268
  console.info(`Server is running on ${server.hostname}`)
266
269
  ```
@@ -299,3 +302,305 @@ We'd recommend to use `fets` to handle routing and middleware approach. It uses
299
302
  `@whatwg-node/server` under the hood.
300
303
 
301
304
  > Learn more about `fets` [here](https://github.com/ardatan/fets)
305
+
306
+ ## Plugin System
307
+
308
+ You can create your own plugins to extend the functionality of your server adapter.
309
+
310
+ ### `onRequest`
311
+
312
+ This hook is invoked for ANY incoming HTTP request. Here you can manipulate the request or create a
313
+ short circuit before the server adapter handles the request.
314
+
315
+ For example, you can shortcut the manually handle an HTTP request, short-circuiting the HTTP
316
+ handler:
317
+
318
+ ```ts
319
+ import { createServerAdapter, type ServerAdapterPlugin } from '@whatwg-node/server'
320
+
321
+ const myPlugin: ServerAdapterPlugin = {
322
+ onRequest({ request, endResponse, fetchAPI }) {
323
+ if (!request.headers.get('authorization')) {
324
+ endResponse(
325
+ new fetchAPI.Response(null, {
326
+ status: 401,
327
+ headers: {
328
+ 'Content-Type': 'application/json'
329
+ }
330
+ })
331
+ )
332
+ }
333
+ }
334
+ }
335
+
336
+ const myServerAdapter = createServerAdapter(
337
+ async request => {
338
+ return new Response(`Hello World!`, { status: 200 })
339
+ },
340
+ {
341
+ plugins: [myPlugin]
342
+ }
343
+ )
344
+ ```
345
+
346
+ Possible usage examples of this hook are:
347
+
348
+ - Manipulate the request
349
+ - Short circuit before the adapter handles the request
350
+
351
+ | Payload field | Description |
352
+ | --------------- | ----------------------------------------------------------------------------------------------------------------------------------------------- |
353
+ | `request` | The incoming HTTP request as WHATWG `Request` object. [Learn more about the request](https://developer.mozilla.org/en-US/docs/Web/API/Request). |
354
+ | `serverContext` | The early context object that is shared between all hooks and the entire execution. [Learn more about the context](/docs/features/context). |
355
+ | `fetchAPI` | WHATWG Fetch API implementation. [Learn more about the fetch API](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API). |
356
+ | `url` | WHATWG URL object of the incoming request. [Learn more about the URL object](https://developer.mozilla.org/en-US/docs/Web/API/URL). |
357
+ | `endResponse` | A function that allows you to end the request early and send a response to the client. |
358
+
359
+ ### `onResponse`
360
+
361
+ This hook is invoked after a HTTP request has been processed and after the response has been
362
+ forwarded to the client. Here you can perform any cleanup or logging operations, or you can
363
+ manipulate the outgoing response object.
364
+
365
+ ```ts
366
+ import { createServerAdapter, type ServerAdapterPlugin } from '@whatwg-node/server'
367
+
368
+ const requestTimeMap = new WeakMap<Request, number>()
369
+
370
+ const myPlugin: ServerAdapterPlugin = {
371
+ onRequest({ request }) {
372
+ requestTimeMap.set(request, Date.now())
373
+ },
374
+ onResponse({ request, serverContext, response }) {
375
+ console.log(`Request to ${request.url} has been processed with status ${response.status}`)
376
+ // Add some headers
377
+ response.headers.set('X-Server-Name', 'My Server')
378
+ console.log(`Request to ${request.url} took ${Date.now() - requestTimeMap.get(request)}ms`)
379
+ }
380
+ }
381
+ ```
382
+
383
+ **Example actions in this hook:**
384
+
385
+ - Specify custom response format
386
+ - Logging/Metrics
387
+
388
+ | Field Name | Description |
389
+ | --------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------- |
390
+ | `request` | The incoming HTTP request as WHATWG `Request` object. [Learn more about the request](https://developer.mozilla.org/en-US/docs/Web/API/Request). |
391
+ | `serverContext` | The final context object that is shared between all hooks and the execution. [Learn more about the context](/docs/features/context). |
392
+ | `response` | The outgoing HTTP response as WHATWG `Response` object. [Learn more about the response interface](https://developer.mozilla.org/en-US/docs/Web/API/Response). |
393
+
394
+ ### `onDispose`
395
+
396
+ In order to clean up resources when the server is shut down, you can use `onDispose`,
397
+ `Symbol.asyncDispose` or `Symbol.syncDispose` to clean up resources.
398
+
399
+ ```ts
400
+ export const useMyPlugin = () => {
401
+ return {
402
+ async onDispose() {
403
+ // Clean up resources
404
+ await stopConnection()
405
+ }
406
+ }
407
+ }
408
+ ```
409
+
410
+ [You can learn more about Explicit Resource Management below](#explicit-resource-management)
411
+
412
+ ## `Request.signal` for awareness of client disconnection
413
+
414
+ In the real world, a lot of HTTP requests are dropped or canceled. This can happen due to a flakey
415
+ internet connection, navigation to a new view or page within a web or native app or the user simply
416
+ closing the app. In this case, the server can stop processing the request and save resources.
417
+
418
+ You can utilize `request.signal` to cancel pending asynchronous operations when the client
419
+ disconnects.
420
+
421
+ ```ts
422
+ import { createServerAdapter } from '@whatwg-node/server'
423
+
424
+ const myServerAdapter = createServerAdapter(async request => {
425
+ const upstreamRes = await fetch('https://api.example.com/data', {
426
+ // When the client disconnects, the fetch request will be canceled
427
+ signal: request.signal
428
+ })
429
+ return Response.json({
430
+ data: await upstreamRes.json()
431
+ })
432
+ })
433
+ ```
434
+
435
+ The execution cancelation API is built on top of the AbortController and AbortSignal APIs.
436
+
437
+ [Learn more about AbortController and AbortSignal](https://developer.mozilla.org/en-US/docs/Web/API/AbortController)
438
+
439
+ ## Explicit Resource Management
440
+
441
+ While implementing your server with `@whatwg-node/server`, you need to control over the lifecycle of
442
+ your resources. This is especially important when you are dealing with resources that need to be
443
+ cleaned up when they are no longer needed, or clean up the operations in a queue when the server is
444
+ shutting down.
445
+
446
+ ### Dispose the Server Adapter
447
+
448
+ The server adapter supports
449
+ [Explicit Resource Management](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-5-2.html#using-declarations-and-explicit-resource-management)
450
+ approach that allows you to dispose of resources when they are no longer needed. This can be done in
451
+ two ways shown below;
452
+
453
+ #### `await using` syntax
454
+
455
+ We use the `await using` syntax to create a new instance of `adapter` and dispose of it when the
456
+ block is exited. Notice that we are using a block to limit the scope of `adapter` within `{ }`. So
457
+ resources will be disposed of when the block is exited.
458
+
459
+ ```ts
460
+ console.log('Adapter is starting')
461
+ {
462
+ await using adapter = createServerAdapter(/* ... */)
463
+ }
464
+ console.log('Adapter is disposed')
465
+ ```
466
+
467
+ #### `dispose` method
468
+
469
+ We create a new instance of `adapter` and dispose of it using the `dispose` method.
470
+
471
+ ```ts
472
+ console.log('Adapter is starting')
473
+ const adapter = createServerAdapter(/* ... */)
474
+ await adapter.dispose()
475
+ console.log('Adapter is disposed')
476
+ ```
477
+
478
+ In the first example, we use the `await using` syntax to create a new instance of `adapter` and
479
+ dispose of it when the block is exited. In the second example,
480
+
481
+ #### Dispose on Node.js
482
+
483
+ When running your adapter on Node.js, you can use process event listeners or server's `close` event
484
+ to trigger the adapter's disposal. Or you can configure the adapter to handle this automatically by
485
+ listening `process` exit signals.
486
+
487
+ ##### Explicit disposal
488
+
489
+ We can dispose of the adapter instance when the server is closed like below.
490
+
491
+ ```ts
492
+ import { createServer } from 'http'
493
+ import { createServerAdapter } from '@whatwg-node/server'
494
+
495
+ const adapter = createServerAdapter(/* ... */)
496
+
497
+ const server = createServer(adapter)
498
+ server.listen(4000, () => {
499
+ console.info('Server is running on http://localhost:4000')
500
+ })
501
+ server.once('close', async () => {
502
+ await adapter.dispose()
503
+ console.info('Server is disposed so is adapter')
504
+ })
505
+ ```
506
+
507
+ ##### Automatic disposal
508
+
509
+ `disposeOnProcessTerminate` option will register an event listener for `process` termination in
510
+ Node.js
511
+
512
+ ```ts
513
+ import { createServer } from 'http'
514
+ import { createServerAdapter } from '@whatwg-node/server'
515
+
516
+ createServer(
517
+ createServerAdapter(/* ... */, {
518
+ disposeOnProcessTerminate: true,
519
+ plugins: [/* ... */]
520
+ })
521
+ ).listen(4000, () => {
522
+ console.info('Server is running on http://localhost:4000')
523
+ })
524
+ ```
525
+
526
+ ### Plugin Disposal
527
+
528
+ If you have plugins that need some internal resources to be disposed of, you can use the `onDispose`
529
+ hook to dispose of them. This hook will be invoked when the adapter instance is disposed like above.
530
+
531
+ ```ts
532
+ let dbConnection: Connection
533
+ const plugin = {
534
+ onPluginInit: async () => {
535
+ dbConnection = await createConnection()
536
+ },
537
+ onDispose: async () => {
538
+ // Dispose of resources
539
+ await dbConnection.close()
540
+ }
541
+ }
542
+ ```
543
+
544
+ Or you can flush a queue of operations when the server is shutting down.
545
+
546
+ ```ts
547
+ const backgroundJobs: Promise<void>[] = []
548
+
549
+ const plugin = {
550
+ onRequest() {
551
+ backgroundJobs.push(
552
+ sendAnalytics({
553
+ /* ... */
554
+ })
555
+ )
556
+ },
557
+ onDispose: async () => {
558
+ // Flush the queue of background jobs
559
+ await Promise.all(backgroundJobs)
560
+ }
561
+ }
562
+ ```
563
+
564
+ But for this kind of purposes, `waitUntil` can be a better choice.
565
+
566
+ ### Background jobs
567
+
568
+ If you have background jobs that need to be completed before the environment is shut down.
569
+ `waitUntil` is better choice than `onDispose`. In this case, those jobs will keep running in the
570
+ background but in case of disposal, they will be awaited. `waitUntil` works so similar to
571
+ [Cloudflare Workers' `waitUntil` function](https://developers.cloudflare.com/workers/runtime-apis/handlers/fetch/#parameters).
572
+
573
+ But the adapter handles `waitUntil` even if it is not provided by the environment.
574
+
575
+ ```ts
576
+ const adapter = createServerAdapter(async (request, context) => {
577
+ const args = await request.json()
578
+ if (!args.name) {
579
+ return Response.json({ error: 'Name is required' }, { status: 400 })
580
+ }
581
+ // This does not block the response
582
+ context.waitUntil(
583
+ fetch('http://my-analytics.com/analytics', {
584
+ method: 'POST',
585
+ body: JSON.stringify({
586
+ name: args.name,
587
+ userAgent: request.headers.get('User-Agent')
588
+ })
589
+ })
590
+ )
591
+ return Response.json({ greetings: `Hello, ${args.name}` })
592
+ })
593
+
594
+ const res = await adapter.fetch('http://localhost:4000', {
595
+ method: 'POST',
596
+ headers: {
597
+ 'Content-Type': 'application/json'
598
+ },
599
+ body: JSON.stringify({ name: 'John' })
600
+ })
601
+
602
+ console.log(await res.json()) // { greetings: "Hello, John" }
603
+
604
+ await adapter.dispose()
605
+ // The fetch request for `analytics` will be awaited here
606
+ ```
@@ -2,7 +2,6 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.createServerAdapter = createServerAdapter;
4
4
  const tslib_1 = require("tslib");
5
- /* eslint-disable @typescript-eslint/ban-types */
6
5
  const disposablestack_1 = require("@whatwg-node/disposablestack");
7
6
  const DefaultFetchAPI = tslib_1.__importStar(require("@whatwg-node/fetch"));
8
7
  const utils_js_1 = require("./utils.js");
@@ -28,54 +27,56 @@ function createServerAdapter(serverAdapterBaseObject, options) {
28
27
  const onRequestHooks = [];
29
28
  const onResponseHooks = [];
30
29
  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
- waitUntilPromises.clear();
48
- }, () => {
49
- waitUntilPromises.clear();
30
+ let _disposableStack;
31
+ function ensureDisposableStack() {
32
+ if (!_disposableStack) {
33
+ _disposableStack = new disposablestack_1.AsyncDisposableStack();
34
+ if (options?.disposeOnProcessTerminate) {
35
+ (0, utils_js_1.ensureDisposableStackRegisteredForTerminateEvents)(_disposableStack);
36
+ }
37
+ _disposableStack.defer(() => {
38
+ if (waitUntilPromises.size > 0) {
39
+ return Promise.allSettled(waitUntilPromises).then(() => {
40
+ waitUntilPromises.clear();
41
+ }, () => {
42
+ waitUntilPromises.clear();
43
+ });
44
+ }
50
45
  });
51
46
  }
52
- });
47
+ return _disposableStack;
48
+ }
53
49
  function waitUntil(promiseLike) {
54
- // If it is a Node.js environment, we should register the disposable stack to handle process termination events
55
- if (globalThis.process) {
56
- (0, utils_js_1.ensureDisposableStackRegisteredForTerminateEvents)(disposableStack);
50
+ // Ensure that the disposable stack is created
51
+ if ((0, utils_js_1.isPromise)(promiseLike)) {
52
+ ensureDisposableStack();
53
+ waitUntilPromises.add(promiseLike);
54
+ promiseLike.then(() => {
55
+ waitUntilPromises.delete(promiseLike);
56
+ }, err => {
57
+ console.error(`Unexpected error while waiting: ${err.message || err}`);
58
+ waitUntilPromises.delete(promiseLike);
59
+ });
57
60
  }
58
- waitUntilPromises.add(promiseLike);
59
- promiseLike.then(() => {
60
- waitUntilPromises.delete(promiseLike);
61
- }, err => {
62
- console.error(`Unexpected error while waiting: ${err.message || err}`);
63
- waitUntilPromises.delete(promiseLike);
64
- });
65
61
  }
66
62
  if (options?.plugins != null) {
67
63
  for (const plugin of options.plugins) {
68
- if (plugin != null) {
69
- if (plugin.onRequest) {
70
- onRequestHooks.push(plugin.onRequest);
71
- }
72
- if (plugin.onResponse) {
73
- onResponseHooks.push(plugin.onResponse);
74
- }
75
- const disposeFn = plugin[disposablestack_1.DisposableSymbols.asyncDispose] || plugin[disposablestack_1.DisposableSymbols.dispose];
76
- if (disposeFn != null) {
77
- disposableStack.defer(disposeFn);
78
- }
64
+ if (plugin.onRequest) {
65
+ onRequestHooks.push(plugin.onRequest);
66
+ }
67
+ if (plugin.onResponse) {
68
+ onResponseHooks.push(plugin.onResponse);
69
+ }
70
+ const disposeFn = plugin[disposablestack_1.DisposableSymbols.dispose];
71
+ if (disposeFn) {
72
+ ensureDisposableStack().defer(disposeFn);
73
+ }
74
+ const asyncDisposeFn = plugin[disposablestack_1.DisposableSymbols.asyncDispose];
75
+ if (asyncDisposeFn) {
76
+ ensureDisposableStack().defer(asyncDisposeFn);
77
+ }
78
+ if (plugin.onDispose) {
79
+ ensureDisposableStack().defer(plugin.onDispose);
79
80
  }
80
81
  }
81
82
  }
@@ -150,7 +151,11 @@ function createServerAdapter(serverAdapterBaseObject, options) {
150
151
  // TODO: Remove this on the next major version
151
152
  function handleNodeRequest(nodeRequest, ...ctx) {
152
153
  const serverContext = ctx.length > 1 ? (0, utils_js_1.completeAssign)(...ctx) : ctx[0] || {};
153
- const request = (0, utils_js_1.normalizeNodeRequest)(nodeRequest, fetchAPI, registerSignal);
154
+ // Ensure `waitUntil` is available in the server context
155
+ if (!serverContext.waitUntil) {
156
+ serverContext.waitUntil = waitUntil;
157
+ }
158
+ const request = (0, utils_js_1.normalizeNodeRequest)(nodeRequest, fetchAPI);
154
159
  return handleRequest(request, serverContext);
155
160
  }
156
161
  function handleNodeRequestAndResponse(nodeRequest, nodeResponseOrContainer, ...ctx) {
@@ -196,8 +201,7 @@ function createServerAdapter(serverAdapterBaseObject, options) {
196
201
  const serverContext = filteredCtxParts.length > 0
197
202
  ? (0, utils_js_1.completeAssign)(defaultServerContext, ...ctx)
198
203
  : defaultServerContext;
199
- const signal = new utils_js_1.ServerAdapterRequestAbortSignal();
200
- registerSignal(signal);
204
+ const controller = new AbortController();
201
205
  const originalResEnd = res.end.bind(res);
202
206
  let resEnded = false;
203
207
  res.end = function (data) {
@@ -206,16 +210,16 @@ function createServerAdapter(serverAdapterBaseObject, options) {
206
210
  };
207
211
  const originalOnAborted = res.onAborted.bind(res);
208
212
  originalOnAborted(function () {
209
- signal.sendAbort();
213
+ controller.abort();
210
214
  });
211
215
  res.onAborted = function (cb) {
212
- signal.addEventListener('abort', cb);
216
+ controller.signal.addEventListener('abort', cb, { once: true });
213
217
  };
214
218
  const request = (0, uwebsockets_js_1.getRequestFromUWSRequest)({
215
219
  req,
216
220
  res,
217
221
  fetchAPI,
218
- signal,
222
+ controller,
219
223
  });
220
224
  let response$;
221
225
  try {
@@ -228,8 +232,8 @@ function createServerAdapter(serverAdapterBaseObject, options) {
228
232
  return response$
229
233
  .catch((e) => (0, utils_js_1.handleErrorFromRequestHandler)(e, fetchAPI.Response))
230
234
  .then(response => {
231
- if (!signal.aborted && !resEnded) {
232
- return (0, uwebsockets_js_1.sendResponseToUwsOpts)(res, response, signal, fetchAPI);
235
+ if (!controller.signal.aborted && !resEnded) {
236
+ return (0, uwebsockets_js_1.sendResponseToUwsOpts)(res, response, controller, fetchAPI);
233
237
  }
234
238
  })
235
239
  .catch(err => {
@@ -237,8 +241,8 @@ function createServerAdapter(serverAdapterBaseObject, options) {
237
241
  });
238
242
  }
239
243
  try {
240
- if (!signal.aborted && !resEnded) {
241
- return (0, uwebsockets_js_1.sendResponseToUwsOpts)(res, response$, signal, fetchAPI);
244
+ if (!controller.signal.aborted && !resEnded) {
245
+ return (0, uwebsockets_js_1.sendResponseToUwsOpts)(res, response$, controller, fetchAPI);
242
246
  }
243
247
  }
244
248
  catch (err) {
@@ -271,13 +275,17 @@ function createServerAdapter(serverAdapterBaseObject, options) {
271
275
  if ((0, utils_js_1.isRequestInit)(initOrCtx)) {
272
276
  const request = new fetchAPI.Request(input, initOrCtx);
273
277
  const res$ = handleRequestWithWaitUntil(request, ...restOfCtx);
274
- return (0, utils_js_1.handleAbortSignalAndPromiseResponse)(res$, initOrCtx?.signal);
278
+ const signal = initOrCtx.signal;
279
+ if (signal) {
280
+ return (0, utils_js_1.handleAbortSignalAndPromiseResponse)(res$, signal);
281
+ }
282
+ return res$;
275
283
  }
276
284
  const request = new fetchAPI.Request(input);
277
285
  return handleRequestWithWaitUntil(request, ...maybeCtx);
278
286
  }
279
287
  const res$ = handleRequestWithWaitUntil(input, ...maybeCtx);
280
- return (0, utils_js_1.handleAbortSignalAndPromiseResponse)(res$, input._signal);
288
+ return (0, utils_js_1.handleAbortSignalAndPromiseResponse)(res$, input.signal);
281
289
  };
282
290
  const genericRequestHandler = (input, ...maybeCtx) => {
283
291
  // If it is a Node request
@@ -316,16 +324,18 @@ function createServerAdapter(serverAdapterBaseObject, options) {
316
324
  handleEvent,
317
325
  handleUWS,
318
326
  handle: genericRequestHandler,
319
- disposableStack,
327
+ get disposableStack() {
328
+ return ensureDisposableStack();
329
+ },
320
330
  [disposablestack_1.DisposableSymbols.asyncDispose]() {
321
- if (!disposableStack.disposed) {
322
- return disposableStack.disposeAsync();
331
+ if (_disposableStack && !_disposableStack.disposed) {
332
+ return _disposableStack.disposeAsync();
323
333
  }
324
334
  return (0, uwebsockets_js_1.fakePromise)(undefined);
325
335
  },
326
336
  dispose() {
327
- if (!disposableStack.disposed) {
328
- return disposableStack.disposeAsync();
337
+ if (_disposableStack && !_disposableStack.disposed) {
338
+ return _disposableStack.disposeAsync();
329
339
  }
330
340
  return (0, uwebsockets_js_1.fakePromise)(undefined);
331
341
  },
package/cjs/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.Response = void 0;
3
+ exports.DisposableSymbols = exports.Response = void 0;
4
4
  const tslib_1 = require("tslib");
5
5
  tslib_1.__exportStar(require("./createServerAdapter.js"), exports);
6
6
  tslib_1.__exportStar(require("./types.js"), exports);
@@ -12,3 +12,5 @@ tslib_1.__exportStar(require("./plugins/useContentEncoding.js"), exports);
12
12
  tslib_1.__exportStar(require("./uwebsockets.js"), exports);
13
13
  var fetch_1 = require("@whatwg-node/fetch");
14
14
  Object.defineProperty(exports, "Response", { enumerable: true, get: function () { return fetch_1.Response; } });
15
+ var disposablestack_1 = require("@whatwg-node/disposablestack");
16
+ Object.defineProperty(exports, "DisposableSymbols", { enumerable: true, get: function () { return disposablestack_1.DisposableSymbols; } });
@@ -46,50 +46,19 @@ function useContentEncoding() {
46
46
  encodingMap.set(request, acceptEncoding.split(','));
47
47
  }
48
48
  },
49
- onResponse({ request, response, setResponse, fetchAPI, serverContext }) {
50
- // Hack for avoiding to create whatwg-node to create a readable stream until it's needed
51
- if (response['bodyInit'] || response.body) {
49
+ onResponse({ request, response, setResponse, fetchAPI }) {
50
+ if (response.body) {
52
51
  const encodings = encodingMap.get(request);
53
52
  if (encodings) {
54
53
  const supportedEncoding = encodings.find(encoding => (0, utils_js_1.getSupportedEncodings)(fetchAPI).includes(encoding));
55
54
  if (supportedEncoding) {
56
55
  const compressionStream = new fetchAPI.CompressionStream(supportedEncoding);
57
- // To calculate final content-length
58
- const contentLength = response.headers.get('content-length');
59
- if (contentLength) {
60
- const bufOfRes = response._buffer;
61
- if (bufOfRes) {
62
- const writer = compressionStream.writable.getWriter();
63
- const write$ = writer.write(bufOfRes);
64
- serverContext.waitUntil?.(write$);
65
- const close$ = writer.close();
66
- serverContext.waitUntil?.(close$);
67
- const uint8Arrays$ = (0, utils_js_1.isReadable)(compressionStream.readable['readable'])
68
- ? collectReadableValues(compressionStream.readable['readable'])
69
- : (0, utils_js_1.isAsyncIterable)(compressionStream.readable)
70
- ? collectAsyncIterableValues(compressionStream.readable)
71
- : collectReadableStreamValues(compressionStream.readable);
72
- return uint8Arrays$.then(uint8Arrays => {
73
- const chunks = uint8Arrays.flatMap(uint8Array => [...uint8Array]);
74
- const uint8Array = new Uint8Array(chunks);
75
- const newHeaders = new fetchAPI.Headers(response.headers);
76
- newHeaders.set('content-encoding', supportedEncoding);
77
- newHeaders.set('content-length', uint8Array.byteLength.toString());
78
- const compressedResponse = new fetchAPI.Response(uint8Array, {
79
- ...response,
80
- headers: newHeaders,
81
- });
82
- utils_js_1.decompressedResponseMap.set(compressedResponse, response);
83
- setResponse(compressedResponse);
84
- const close$ = compressionStream.writable.close();
85
- serverContext.waitUntil?.(close$);
86
- });
87
- }
88
- }
89
56
  const newHeaders = new fetchAPI.Headers(response.headers);
90
57
  newHeaders.set('content-encoding', supportedEncoding);
91
58
  newHeaders.delete('content-length');
92
- const compressedBody = response.body.pipeThrough(compressionStream);
59
+ const compressedBody = response.body.pipeThrough(compressionStream, {
60
+ signal: request.signal,
61
+ });
93
62
  const compressedResponse = new fetchAPI.Response(compressedBody, {
94
63
  status: response.status,
95
64
  statusText: response.statusText,
@@ -103,33 +72,3 @@ function useContentEncoding() {
103
72
  },
104
73
  };
105
74
  }
106
- function collectReadableValues(readable) {
107
- const values = [];
108
- readable.on('data', value => values.push(value));
109
- return new Promise((resolve, reject) => {
110
- readable.once('end', () => resolve(values));
111
- readable.once('error', reject);
112
- });
113
- }
114
- async function collectAsyncIterableValues(asyncIterable) {
115
- const values = [];
116
- for await (const value of asyncIterable) {
117
- values.push(value);
118
- }
119
- return values;
120
- }
121
- async function collectReadableStreamValues(readableStream) {
122
- const reader = readableStream.getReader();
123
- const values = [];
124
- while (true) {
125
- const { done, value } = await reader.read();
126
- if (done) {
127
- reader.releaseLock();
128
- break;
129
- }
130
- else if (value) {
131
- values.push(value);
132
- }
133
- }
134
- return values;
135
- }