webhoster 0.3.2 → 0.3.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (37) hide show
  1. package/.github/workflows/publish.yml +29 -0
  2. package/.test/index.js +1 -1
  3. package/README.md +4 -4
  4. package/data/CookieObject.js +1 -1
  5. package/{types/index.js → data/custom-types.js} +1 -1
  6. package/{types/typings.d.ts → data/middleware.d.ts} +1 -1
  7. package/helpers/HttpListener.js +20 -94
  8. package/helpers/RequestReader.js +1 -12
  9. package/helpers/ResponseHeaders.js +9 -10
  10. package/lib/HttpHandler.js +17 -7
  11. package/lib/HttpRequest.js +7 -4
  12. package/lib/HttpResponse.js +11 -5
  13. package/lib/HttpTransaction.js +2 -2
  14. package/middleware/AutoHeadersMiddleware.js +2 -2
  15. package/middleware/CORSMiddleware.js +3 -3
  16. package/middleware/CaseInsensitiveHeadersMiddleware.js +2 -2
  17. package/middleware/ContentDecoderMiddleware.js +12 -8
  18. package/middleware/ContentEncoderMiddleware.js +6 -5
  19. package/middleware/ContentLengthMiddleware.js +2 -2
  20. package/middleware/HashMiddleware.js +2 -2
  21. package/middleware/HeadMethodMiddleware.js +3 -3
  22. package/middleware/MethodMiddleware.js +3 -3
  23. package/middleware/PathMiddleware.js +4 -4
  24. package/middleware/ReadFormData.js +1 -1
  25. package/middleware/SendJsonMiddleware.js +4 -4
  26. package/middleware/SendStringMiddleware.js +3 -3
  27. package/package.json +20 -3
  28. package/templates/starter.js +2 -2
  29. package/tsconfig.json +18 -1
  30. package/utils/headers.js +1 -1
  31. package/errata/index.js +0 -1
  32. package/index.js +0 -4
  33. package/lib/index.js +0 -3
  34. package/middleware/ContentReaderMiddleware.js +0 -249
  35. package/middleware/ContentWriterMiddleware.js +0 -161
  36. package/middleware/SendHeadersMiddleware.js +0 -47
  37. package/middleware/index.js +0 -11
@@ -0,0 +1,29 @@
1
+ name: Publish Package
2
+
3
+ on:
4
+ push:
5
+ tags:
6
+ - 'v*'
7
+
8
+ permissions:
9
+ id-token: write # Required for OIDC
10
+ contents: read
11
+
12
+ jobs:
13
+ publish:
14
+ runs-on: ubuntu-latest
15
+ steps:
16
+ - uses: actions/checkout@v4
17
+
18
+ - uses: actions/setup-node@v4
19
+ with:
20
+ node-version: '20'
21
+ registry-url: 'https://registry.npmjs.org'
22
+
23
+ # Ensure npm 11.5.1 or later is installed
24
+ - name: Update npm
25
+ run: npm install -g npm@latest
26
+ - run: npm ci
27
+ - run: npm run build --if-present
28
+ - run: npm test
29
+ - run: npm publish
package/.test/index.js CHANGED
@@ -26,7 +26,7 @@ import {
26
26
  import * as tls from './tls.js';
27
27
  import { ServerResponse } from 'node:http';
28
28
 
29
- /** @typedef {import('../types').MiddlewareFunction} MiddlewareFunction */
29
+ /** @typedef {import('../data/custom-types.js').MiddlewareFunction} MiddlewareFunction */
30
30
 
31
31
  /**
32
32
  * Redirect to HTTPS/2
package/README.md CHANGED
@@ -39,7 +39,7 @@ For now, take a look at [/test/index.js](/test/index.js)
39
39
  * `.defaultInstance` - (`HttpHandler`) - Returns a instance of `HttpHandler` that can be accessed staticly.
40
40
  * `.middleware` - (`Middleware[]`) - An array of middleware operations to iterate through when handling a request. It is recommended to create isolated branches (eg: `/images/`; `/api/`; `/views/`; etc.).
41
41
  * `.errorHandlers` - (`MiddlewareErrorHandler[]`) - An array of `MiddlewareErrorHandler` that will handle errors and respond appropriately (eg: `res.status = 500`)
42
- * `.handleRequest` - (`function(MiddlewareFunctionParams):Promise<HttpResponse>`) - handles logic for calling middleware and error handlers. Unlikely to be used directly.
42
+ * `.handleRequest` - (`function(HttpTransaction):Promise<HttpResponse>`) - handles logic for calling middleware and error handlers. Unlikely to be used directly.
43
43
  * `.handleHttp1Request` - (`function(IncomingMessage, ServerResponse):Promise<HttpResponse>`) - constructs a new `HttpRequest` and `HttpResponse` based on the HTTP1 parameters and passes it to `handleRequest`
44
44
  * `.handleHttp2Stream` - (`function(ServerHttp2Stream, IncomingHttpHeaders, HttpResponseOptions):Promise<HttpResponse>`) - constructs a new `HttpRequest` and `HttpResponse` based on the HTTP2 parameters and passes it to `handleRequest`
45
45
 
@@ -141,7 +141,7 @@ async function onGetIndexPage(transaction) {
141
141
 
142
142
  Middleware logic flows in a tree structure, allowing for `continue`, `break`, or `end`.
143
143
 
144
- A `MiddlewareFunction` is a function that accepts a `MiddlewareFunctionParams` object structured as `{ res: HttpRequest, res: HttpResponse }`. The function can return a instruction with the step in the tree-based logic, status code, or content body to be handled. It maybe return any of these instructions with any of the values as a literal, a `Promise`, or `PromiseLike`:
144
+ A `MiddlewareFunction` is a function that accepts a `HttpTransaction` object structured as `{ res: HttpRequest, res: HttpResponse }`. The function can return a instruction with the step in the tree-based logic, status code, or content body to be handled. It maybe return any of these instructions with any of the values as a literal, a `Promise`, or `PromiseLike`:
145
145
 
146
146
  * `HttpHandler.CONTINUE`: Continues on the current branch to the next middleware, or moves to the next branch if there are no siblings left. `alias: true|void|null|undefined`
147
147
  * `HttpHandler.BREAK`: Breaks from the current middleware branch, and continues to the next branch. `alias: false`
@@ -151,7 +151,7 @@ A `MiddlewareFunction` is a function that accepts a `MiddlewareFunctionParams` o
151
151
  * `Array`: Explicitly passed to `HttpResponse.end()`. This is to support sending an `Array` object instead having it becoming an inline middleware branch.
152
152
  * `any`: Any other value returned would automatically be passed to `HttpResponse.end()` which, in turn, uses it's own content handlers (eg: `JSON`; `Readable`), and finally terminates the middleware tree.
153
153
 
154
- A `MiddlewareFilter` is a function that accepts a `MiddlewareFunctionParams` and returns a `boolean` or `Promise<boolean>` signaling whether to continue in the branch. `true` translates to `HttpHandler.CONTINUE`. `false` translates to `HttpHandler.BREAK`. There is no support for `HttpHandler.END` logic in a MiddlewareFilter by design.
154
+ A `MiddlewareFilter` is a function that accepts a `HttpTransaction` and returns a `boolean` or `Promise<boolean>` signaling whether to continue in the branch. `true` translates to `HttpHandler.CONTINUE`. `false` translates to `HttpHandler.BREAK`. There is no support for `HttpHandler.END` logic in a MiddlewareFilter by design.
155
155
 
156
156
  A `MiddlewareErrorHandler` is an `Object` with a `onError` property. `onError` is like a MiddlewareFunction, but includes an `err` item in its parameter object. When the handler is in an error state, it will bubble upwards while searching for the next `MiddlewareErrorHandler`.
157
157
 
@@ -163,7 +163,7 @@ To support branching, `Middleware` can also be a `Iterable<Middleware>` (eg: `Se
163
163
 
164
164
  ### Response Middleware
165
165
  * [AuthHeaders](./middleware/AutoHeadersMiddleware.js) - Automatically sends response headers before writing or ending a response stream
166
- * [ContentLength](./middleware/contentLengthMiddleware.js) - Sets `Content-Length` based on response stream content writes
166
+ * [ContentLength](./middleware/ContentLengthMiddleware.js) - Sets `Content-Length` based on response stream content writes
167
167
  * [Hash](./middleware/HashMiddleware.js) - Sets `ETag`, `Digest`, and `Content-MD5` response headers automatically
168
168
  * [ContentEncoder](./middleware/ContentEncoderMiddleware.js) - Applies `Content-Encoding` to response based on `Accept-Encoding` request header
169
169
  * [SendJson](./middleware/SendJsonMiddleware.js) - Adds response content processor that encodes objects and arrays to JSON string. Sets `application/json;charset=utf-8`, if content-type not set.
@@ -1,4 +1,4 @@
1
- /** @typedef {import('../types').CookieDetails} CookieDetails */
1
+ /** @typedef {import('./custom-types.js').CookieDetails} CookieDetails */
2
2
 
3
3
  /** @private */
4
4
  export default class CookieObject {
@@ -16,7 +16,7 @@
16
16
 
17
17
  /** @typedef {import('node:stream').Duplex} Duplex */
18
18
 
19
- /** @typedef {import('./typings').Middleware} Middleware */
19
+ /** @typedef {import('./middleware.js').Middleware} Middleware */
20
20
 
21
21
  /** @typedef {true|false|0} MiddlewareFlowInstruction */
22
22
 
@@ -3,7 +3,7 @@ import {
3
3
  MiddlewareContent,
4
4
  MiddlewareFunction,
5
5
  MiddlewareFunctionResultType,
6
- } from './index.js';
6
+ } from './custom-types.js';
7
7
 
8
8
  export type Middleware =
9
9
  | IMiddleware
@@ -148,6 +148,7 @@ export default class HttpListener {
148
148
 
149
149
  this.httpServer.keepAliveTimeout = 5000;
150
150
  this.httpServer.requestTimeout = 300_000;
151
+ // @ts-expect-error Missing types
151
152
  this.httpServer.setTimeout(120_000, (socket) => {
152
153
  if (!socket) {
153
154
  console.warn('HTTP socket (unknown) timed out.');
@@ -163,6 +164,7 @@ export default class HttpListener {
163
164
  // console.error('HTTP server error', err);
164
165
  });
165
166
  this.httpServer.addListener('clientError', this.#httpClientErrorListener = (error, socket) => {
167
+ // @ts-expect-error Missing types
166
168
  if (error?.code === 'ECONNRESET') {
167
169
  // console.warn('HTTP client connection reset.');
168
170
  return;
@@ -194,6 +196,7 @@ export default class HttpListener {
194
196
 
195
197
  this.httpsServer.keepAliveTimeout = 5000;
196
198
  this.httpsServer.requestTimeout = 300_000;
199
+ // @ts-expect-error Missing types
197
200
  this.httpsServer.setTimeout(120_000, (socket) => {
198
201
  if (!socket) {
199
202
  // console.warn('HTTPS socket (unknown) timed out.');
@@ -208,6 +211,7 @@ export default class HttpListener {
208
211
  // console.error('HTTPS server error', err);
209
212
  });
210
213
  this.httpsServer.addListener('clientError', this.#httpsClientErrorListener = (error, socket) => {
214
+ // @ts-expect-error Missing types
211
215
  if (error?.code === 'ECONNRESET') {
212
216
  console.warn('HTTPS client connection reset.');
213
217
  return;
@@ -237,77 +241,7 @@ export default class HttpListener {
237
241
  host: this.secureHost,
238
242
  }, () => {
239
243
  this.http2Server.removeListener('error', reject);
240
-
241
- /** @type {Set<WeakRef<Http2Session>>} */
242
- const sessions = new Set();
243
- /** @type {WeakMap<Http2Session, {timestamp:number, identity:string}>} */
244
- const sessionMetadata = new WeakMap();
245
- /** @type {Set<WeakRef<Http2Stream>>} */
246
- const streams = new Set();
247
- /** @type {WeakMap<Http2Stream, {timestamp:number, identity:string, path:string}>} */
248
- const streamMetadata = new WeakMap();
249
- /** @return {void} */
250
- function logUsage() {
251
- if (global.gc) {
252
- console.debug('Perfoming garbage collection.');
253
- global.gc();
254
- }
255
- for (const reference of sessions) {
256
- const session = reference.deref();
257
- if (!session) {
258
- sessions.delete(reference);
259
- continue;
260
- }
261
- const metadata = sessionMetadata.get(session);
262
- if (session.destroyed) {
263
- console.warn('SESSION destroyed from', metadata.identity, 'since', metadata.timestamp);
264
- } else {
265
- console.debug('SESSION alive from', metadata.identity, 'since', metadata.timestamp);
266
- }
267
- }
268
- for (const reference of streams) {
269
- const stream = reference.deref();
270
- if (!stream) {
271
- streams.delete(reference);
272
- continue;
273
- }
274
- const metadata = streamMetadata.get(stream);
275
- if (stream.destroyed) {
276
- console.warn('STREAM destroyed from', metadata.identity, 'since', metadata.timestamp, 'for', metadata.path);
277
- } else {
278
- console.debug('STREAM alive from', metadata.identity, 'since', metadata.timestamp, 'for', metadata.path);
279
- }
280
- }
281
- if (sessions.size) {
282
- console.debug('Active sessions:', sessions.size);
283
- }
284
- if (streams.size) {
285
- console.debug('Active streams:', streams.size);
286
- }
287
- if ('process' in globalThis) {
288
- if ('getActiveResourcesInfo' in globalThis.process) {
289
- console.dir('Active Resources', globalThis.process.getActiveResourcesInfo());
290
- }
291
- if ('_getActiveRequests' in globalThis.process) {
292
- console.dir('Active Requests', globalThis.process._getActiveRequests());
293
- }
294
- if ('_getActiveHandles' in globalThis.process) {
295
- const handles = globalThis.process._getActiveHandles();
296
- for (const handle of handles) {
297
- if (handle instanceof Socket) {
298
- console.debug('Active Handle (Socket)', `${handle.localAddress}:${handle.localPort} <=> ${handle.remoteAddress}:${handle.remotePort}`);
299
- } else if (handle instanceof Server) {
300
- console.debug('Active Handle (Server)', `${handle.address()?.port}`);
301
- } else {
302
- console.debug('Active Handle (unknown)', handle);
303
- }
304
- }
305
- }
306
- }
307
- }
308
- // setInterval(logUsage, 300_000);
309
- // logUsage();
310
-
244
+ // @ts-expect-error Missing types
311
245
  this.http2Server.setTimeout(120_000, (socket) => {
312
246
  if (!socket) {
313
247
  console.warn('HTTP2 socket (unknown) timed out.');
@@ -325,6 +259,7 @@ export default class HttpListener {
325
259
  this.http2Server.addListener('clientError', this.#http2ClientErrorListener = (error, socket) => {
326
260
  // No clear way to signal back to client why a socket has failed because
327
261
  // it's unsure if it's HTTP1 or HTTP2. Just destroy and log server-side.
262
+ // @ts-expect-error Missing types
328
263
  if (error?.code === 'ECONNRESET') {
329
264
  // console.warn('HTTP/2 client connection reset.');
330
265
  } else {
@@ -333,6 +268,7 @@ export default class HttpListener {
333
268
  if (!socket.destroyed) socket.destroy(error);
334
269
  });
335
270
  this.http2Server.addListener('sessionError', this.#http2SessionErrorListener = (error, session) => {
271
+ // @ts-expect-error Missing types
336
272
  if (error?.code === 'ECONNRESET') {
337
273
  // console.warn('HTTP/2 client connection reset.');
338
274
  } else if (error?.message === 'SOCKET_TIMEOUT') {
@@ -344,12 +280,7 @@ export default class HttpListener {
344
280
  });
345
281
 
346
282
  this.http2Server.addListener('session', this.#http2SessionListener = (session) => {
347
- sessions.add(new WeakRef(session));
348
283
  const identity = `${session.socket.remoteFamily}:${session.socket.remoteAddress}:${session.socket.remotePort}`;
349
- sessionMetadata.set(session, {
350
- timestamp: performance.now(),
351
- identity,
352
- });
353
284
  session.setTimeout(60_000, () => {
354
285
  // console.warn(`HTTP/2 session ${identity} timed out.`);
355
286
  session.destroy(new Error('SESSION_TIMEOUT'));
@@ -361,6 +292,7 @@ export default class HttpListener {
361
292
  session.ping((error) => {
362
293
  if (!error) return;
363
294
  if (session.destroyed) return;
295
+ // @ts-expect-error Missing types
364
296
  if (error.code === 'ERR_HTTP2_PING_CANCEL') return;
365
297
  console.error(`Ping to ${identity} failed.`, error);
366
298
  });
@@ -373,49 +305,42 @@ export default class HttpListener {
373
305
 
374
306
  // Logic handlers
375
307
  this.http2Server.addListener('stream', this.#http2StreamListener = (stream, headers) => {
376
- streams.add(new WeakRef(stream));
377
- streamMetadata.set(stream, {
378
- timestamp: performance.now(),
379
- identity: `${stream.session?.socket.remoteFamily}:${stream.session?.socket.remoteAddress}:${stream.session?.socket.remotePort}`,
380
- path: headers[':path'],
381
- });
382
-
383
308
  stream.setTimeout(300_000, () => {
384
309
  stream.destroy(new Error('SOCKET_TIMEOUT'));
385
310
  });
386
311
  stream.addListener('error', (error) => {
312
+ // @ts-expect-error Missing types
387
313
  if (error?.code === 'ECONNRESET') {
388
314
  // console.warn('HTTP/2 stream connection reset.', headers[':path']);
389
315
  } else {
390
316
  console.error('HTTP/2 stream error', headers, error);
391
317
  }
392
318
  });
319
+ // @ts-expect-error Missing types
393
320
  this.httpHandler.handleHttp2Stream(stream, headers).catch((error) => {
394
321
  console.error('HTTP2 handler failed.', error);
395
322
  });
396
323
  });
397
- this.http2Server.addListener('request', this.#http2RequestListener = (request, res) => {
324
+ this.http2Server.addListener('request', this.#http2RequestListener = (request, response) => {
398
325
  if (request.httpVersionMajor >= 2) return;
399
- // @ts-ignore Ignore typings
326
+ // @ts-expect-error Missing types
400
327
  request.setTimeout(300_000, (socket) => {
401
328
  if (!socket) {
402
329
  // console.warn('HTTP1 in HTTP2 request (unknown) timed out.');
403
330
  return;
404
331
  }
405
- // const identity = `${socket.remoteFamily}:${socket.remoteAddress}:${socket.remotePort}`;
406
- // console.warn(`HTTP1 in HTTP2 request ${identity} timed out.`);
407
332
  socket.destroy(new Error('SOCKET_TIMEOUT'));
408
333
  });
409
- res.setTimeout(300_000, (socket) => {
334
+ // @ts-expect-error Missing types
335
+ response.setTimeout(300_000, (socket) => {
410
336
  if (!socket) {
411
337
  // console.warn('HTTP1 in HTTP2 response (unknown) timed out.');
412
338
  return;
413
339
  }
414
- const identity = `${socket.remoteFamily}:${socket.remoteAddress}:${socket.remotePort}`;
415
- // console.warn(`HTTP1 in HTTP2 response ${identity} timed out.`);
416
340
  socket.destroy(new Error('SOCKET_TIMEOUT'));
417
341
  });
418
342
  request.addListener('error', (error) => {
343
+ // @ts-expect-error Missing types
419
344
  if (error?.code === 'ECONNRESET') {
420
345
  // console.warn('Request stream connection reset.', req.url);
421
346
  } else {
@@ -425,17 +350,18 @@ export default class HttpListener {
425
350
  request.destroy(error);
426
351
  }
427
352
  });
428
- res.addListener('error', (error) => {
353
+ response.addListener('error', (error) => {
354
+ // @ts-expect-error Missing types
429
355
  if (error?.code === 'ECONNRESET') {
430
356
  // console.warn('Response stream connection reset.', req.url);
431
357
  } else {
432
358
  console.error('Response stream error', request.url, request.headers, error);
433
359
  }
434
- if (!res.destroyed) {
435
- res.destroy(error);
360
+ if (!response.destroyed) {
361
+ response.destroy(error);
436
362
  }
437
363
  });
438
- this.httpHandler.handleHttp1Request(request, res).catch((error) => {
364
+ this.httpHandler.handleHttp1Request(request, response).catch((error) => {
439
365
  console.error('HTTP1 in HTTP2 handler failed.', error);
440
366
  });
441
367
  });
@@ -1,8 +1,7 @@
1
- /** @typedef {import('../types').HttpRequest} HttpRequest */
1
+ /** @typedef {import('../data/custom-types.js').HttpRequest} HttpRequest */
2
2
 
3
3
  import { TextDecoder } from 'node:util';
4
4
  import AsyncObject from '../utils/AsyncObject.js';
5
- import { noop } from '../utils/function.js';
6
5
  import RequestHeaders from './RequestHeaders.js';
7
6
 
8
7
  /**
@@ -11,7 +10,6 @@ import RequestHeaders from './RequestHeaders.js';
11
10
  */
12
11
 
13
12
  const BUFFER_SIZE = 4096;
14
- const STREAM_WAIT_MS = 0;
15
13
 
16
14
  /** @type {WeakMap<HttpRequest, RequestReader>} */
17
15
  const cache = new WeakMap();
@@ -45,8 +43,6 @@ export default class RequestReader {
45
43
  const hp = new RequestHeaders(this.request);
46
44
  let data = Buffer.allocUnsafe(Math.min(BUFFER_SIZE, hp.contentLength || BUFFER_SIZE));
47
45
  let bytesWritten = 0;
48
- /** @type {NodeJS.Timeout} */
49
- let sendPingTimeout = null;
50
46
  this.request.stream.on('readable', () => {
51
47
  let chunk;
52
48
  // eslint-disable-next-line no-cond-assign
@@ -70,15 +66,8 @@ export default class RequestReader {
70
66
  }
71
67
  bytesWritten += buffer.copy(data, bytesWritten);
72
68
  }
73
- clearTimeout(sendPingTimeout);
74
- if (this.request.canPing) {
75
- sendPingTimeout = setTimeout(() => {
76
- this.request.ping().catch(noop);
77
- }, STREAM_WAIT_MS);
78
- }
79
69
  });
80
70
  this.request.stream.on('end', () => {
81
- clearTimeout(sendPingTimeout);
82
71
  if (data.length > bytesWritten) {
83
72
  // Must partition to clear unsafe allocation
84
73
  data = data.subarray(0, bytesWritten);
@@ -3,7 +3,7 @@ import CookieObject from '../data/CookieObject.js';
3
3
  import HeadersHandler from './HeadersParser.js';
4
4
 
5
5
  /** @typedef {import('../lib/HttpResponse.js').default} HttpResponse */
6
- /** @typedef {import('../types').CookieDetails} CookieDetails */
6
+ /** @typedef {import('../data/custom-types.js').CookieDetails} CookieDetails */
7
7
 
8
8
  /** @type {(keyof CookieDetails)[]} */
9
9
  const COOKIE_DETAIL_KEYS = [
@@ -22,18 +22,17 @@ const COOKIE_DETAIL_KEYS = [
22
22
  const instanceCache = new WeakMap();
23
23
 
24
24
  export default class ResponseHeaders extends HeadersHandler {
25
- /** @param {HttpResponse} res */
26
- // @ts-ignore Cached constructor
27
- constructor(res) {
28
- const instance = instanceCache.get(res);
25
+ /** @param {HttpResponse} response */
26
+ constructor(response) {
27
+ const instance = instanceCache.get(response);
29
28
  if (instance) return instance;
30
- super(res.headers);
31
- instanceCache.set(res, this);
29
+ super(response.headers);
30
+ instanceCache.set(response, this);
32
31
  }
33
32
 
34
- /** @param {HttpResponse} res */
35
- static cookies(res) {
36
- const instance = new ResponseHeaders(res);
33
+ /** @param {HttpResponse} response */
34
+ static cookies(response) {
35
+ const instance = new ResponseHeaders(response);
37
36
  return instance.cookies;
38
37
  }
39
38
 
@@ -7,10 +7,10 @@ import HttpRequest from './HttpRequest.js';
7
7
  import HttpResponse from './HttpResponse.js';
8
8
  import HttpTransaction from './HttpTransaction.js';
9
9
 
10
- /** @typedef {import('../types').Middleware} Middleware */
11
- /** @typedef {import('../types').MiddlewareErrorHandler} MiddlewareErrorHandler */
12
- /** @typedef {import('../types').MiddlewareFlowInstruction} MiddlewareFlowInstruction */
13
- /** @typedef {import('../types').RequestMethod} RequestMethod */
10
+ /** @typedef {import('../data/custom-types.js').Middleware} Middleware */
11
+ /** @typedef {import('../data/custom-types.js').MiddlewareErrorHandler} MiddlewareErrorHandler */
12
+ /** @typedef {import('../data/custom-types.js').MiddlewareFlowInstruction} MiddlewareFlowInstruction */
13
+ /** @typedef {import('../data/custom-types.js').RequestMethod} RequestMethod */
14
14
 
15
15
  /** @type {HttpHandler} */
16
16
  let defaultInstance = null;
@@ -145,6 +145,7 @@ export default class HttpHandler {
145
145
 
146
146
  // Check if error handler
147
147
  const isErrorHandler = (typeof middleware === 'object'
148
+ && 'onError' in middleware
148
149
  && typeof middleware.onError === 'function');
149
150
 
150
151
  let value = middleware;
@@ -186,6 +187,7 @@ export default class HttpHandler {
186
187
  }
187
188
 
188
189
  // Sync operation returned Promise
190
+ // @ts-expect-error TS improperly thinks result can be null
189
191
  if (typeof result === 'object' && typeof result.then === 'function') {
190
192
  if (isErrorHandler) transaction.error = null;
191
193
  result = await result;
@@ -216,6 +218,7 @@ export default class HttpHandler {
216
218
  const { treeIndex } = transaction.state;
217
219
  treeIndex.push(-1);
218
220
  const { length } = value;
221
+ // eslint-disable-next-line no-plusplus
219
222
  for (let index = 0; index < length; index++) {
220
223
  const innerMiddleware = value[index];
221
224
  treeIndex[treeIndex.length - 1] += 1;
@@ -333,6 +336,7 @@ export default class HttpHandler {
333
336
 
334
337
  const request = new HttpRequest({
335
338
  headers,
339
+ // @ts-expect-error JSDoc syntax limitation
336
340
  method,
337
341
  stream: incomingMessage,
338
342
 
@@ -428,7 +432,7 @@ export default class HttpHandler {
428
432
  }
429
433
  }
430
434
 
431
- // eslint-disable-next-line @typescript-eslint/no-this-alias
435
+ // eslint-disable-next-line @typescript-eslint/no-this-alias, unicorn/no-this-assignment
432
436
  const context = this;
433
437
 
434
438
  /** @type {Promise<any>[]} */
@@ -436,6 +440,7 @@ export default class HttpHandler {
436
440
 
437
441
  const request = new HttpRequest({
438
442
  headers,
443
+ // @ts-expect-error JSDoc syntax limitation
439
444
  method,
440
445
  stream,
441
446
 
@@ -492,7 +497,7 @@ export default class HttpHandler {
492
497
  'cache-control',
493
498
  ]) {
494
499
  if (passedHeader in headers) {
495
- // @ts-ignore Coerce
500
+ // @ts-expect-error Coerce
496
501
  newHeaders[passedHeader] = headers[passedHeader];
497
502
  }
498
503
  }
@@ -500,10 +505,15 @@ export default class HttpHandler {
500
505
  // Build promise function
501
506
  const promiseFunction = async () => {
502
507
  try {
508
+ /** @type {import('node:http2').ServerHttp2Stream} */
503
509
  const pushStream = await new Promise((resolve, reject) => {
504
- stream.pushStream(newHeaders, ((error, newStream) => (error ? reject(error) : resolve(newStream))));
510
+ stream.pushStream(
511
+ newHeaders,
512
+ ((error, newStream) => (error ? reject(error) : resolve(newStream))),
513
+ );
505
514
  });
506
515
  pushStream.addListener('error', (error) => {
516
+ // @ts-expect-error Missing types
507
517
  if (error?.code === 'ECONNRESET') {
508
518
  console.warn('HTTP/2 stream connection reset.', headers[':path']);
509
519
  } else {
@@ -1,9 +1,9 @@
1
1
  import { Readable } from 'node:stream';
2
2
  import { URLSearchParams } from 'node:url';
3
3
 
4
- /** @typedef {import('../types').RequestMethod} RequestMethod */
4
+ /** @typedef {import('../data/custom-types.js').RequestMethod} RequestMethod */
5
5
  /** @typedef {import('http').IncomingHttpHeaders} IncomingHttpHeaders */
6
- /** @typedef {import('../types/index.js').MediaType} MediaType */
6
+ /** @typedef {import('../data/custom-types.js').MediaType} MediaType */
7
7
 
8
8
  /** @typedef {Partial<MediaType> & {parse:(this:HttpRequest)=>any|PromiseLike<any>, test?:(this:HttpRequest, mediaType: MediaType)=>boolean}} ContentReaderRegistration */
9
9
 
@@ -140,7 +140,9 @@ export default class HttpRequest {
140
140
  if (this.#readable === undefined) {
141
141
  if (this.method === 'GET' || this.method === 'HEAD') {
142
142
  this.#readable = null;
143
+ // @ts-expect-error Missing types
143
144
  } else if (Readable.toWeb) {
145
+ // @ts-expect-error Missing types
144
146
  this.#readable = Readable.toWeb(this.stream);
145
147
  this.#bodyUsed = true;
146
148
  } else {
@@ -226,7 +228,7 @@ export default class HttpRequest {
226
228
  }
227
229
 
228
230
  /**
229
- * @return {Promise<ArrayBuffer>}
231
+ * @return {Promise<ArrayBufferLike>}
230
232
  */
231
233
  async arrayBuffer() {
232
234
  try {
@@ -264,7 +266,7 @@ export default class HttpRequest {
264
266
  try {
265
267
  const module = await import('node:buffer');
266
268
  if ('Blob' in module === false) throw new Error('NOT_SUPPORTED');
267
- BlobClass = module.Blob;
269
+ BlobClass = /** @type {typeof Blob} */ (module.Blob);
268
270
  } catch {
269
271
  BlobClass = null;
270
272
  }
@@ -389,6 +391,7 @@ export default class HttpRequest {
389
391
  }
390
392
  }
391
393
 
394
+ // eslint-disable-next-line class-methods-use-this
392
395
  async formData() {
393
396
  throw new Error('UNSUPPORTED_MEDIA_TYPE');
394
397
  }
@@ -11,9 +11,9 @@ import { isWritable } from '../utils/stream.js';
11
11
 
12
12
  /** @typedef {import('http').OutgoingHttpHeaders} OutgoingHttpHeaders */
13
13
  /** @typedef {import('stream').Stream} Stream */
14
- /** @typedef {import('../types/index.js').Middleware} Middleware */
14
+ /** @typedef {import('../data/custom-types.js').Middleware} Middleware */
15
15
  /** @typedef {import('./HttpRequest.js').default} HttpRequest */
16
- /** @typedef {import('../types/index.js').ResponseFinalizer} ResponseFinalizer */
16
+ /** @typedef {import('../data/custom-types.js').ResponseFinalizer} ResponseFinalizer */
17
17
 
18
18
  /**
19
19
  * @typedef {Object} HttpResponseOptions
@@ -124,6 +124,7 @@ export default class HttpResponse {
124
124
  async sendRaw(body) {
125
125
  this.body = body;
126
126
  await new Promise((resolve, reject) => {
127
+ // @ts-expect-error Error is any
127
128
  this.stream.end(body, (error) => (error ? reject(error) : resolve()));
128
129
  });
129
130
  return 0;
@@ -135,7 +136,7 @@ export default class HttpResponse {
135
136
  }
136
137
 
137
138
  /**
138
- * @param {Transform} stream
139
+ * @param {import('stream').Transform} stream
139
140
  * @return {this}
140
141
  */
141
142
  addUpstream(stream) {
@@ -162,6 +163,7 @@ export default class HttpResponse {
162
163
  // Called directly by user and needs finalizer calls
163
164
 
164
165
  this.isStreaming = true;
166
+ // eslint-disable-next-line no-plusplus
165
167
  for (let index = 0; index < this.finalizers.length; index++) {
166
168
  const process = this.finalizers[index];
167
169
  const result = process(this);
@@ -183,11 +185,13 @@ export default class HttpResponse {
183
185
  ];
184
186
  }
185
187
 
186
- this.#pipeline = array[0];
187
- // @ts-ignore Bad typings
188
+ // eslint-disable-next-line prefer-destructuring
189
+ this.#pipeline = /** @type {Writable} */ (array[0]);
190
+ // @ts-expect-error Bad typings
188
191
  pipeline(array, (error) => {
189
192
  this.#pipelineComplete = true;
190
193
  let nextCallback;
194
+ // eslint-disable-next-line no-cond-assign
191
195
  while ((nextCallback = this.#pipelineCallbacks.shift()) != null) {
192
196
  nextCallback(error);
193
197
  }
@@ -282,6 +286,7 @@ export default class HttpResponse {
282
286
  /** @type {void|Promise<boolean|void>} */
283
287
  let pendingPromise;
284
288
  let index;
289
+ // eslint-disable-next-line no-plusplus
285
290
  for (index = 0; index < this.finalizers.length; index++) {
286
291
  const process = this.finalizers[index];
287
292
  if (needsAsync) {
@@ -303,6 +308,7 @@ export default class HttpResponse {
303
308
  if (pendingPromise) {
304
309
  pendingPromise.then(async (initialResult) => {
305
310
  if (initialResult !== false) {
311
+ // eslint-disable-next-line no-plusplus
306
312
  for (index = 0; index < pendingProcessors.length; index++) {
307
313
  const process = pendingProcessors[index];
308
314
  const result = process(this);
@@ -2,8 +2,8 @@
2
2
  /** @typedef {import('./HttpResponse.js').default} HttpResponse */
3
3
  /** @typedef {import('stream').Stream} Stream */
4
4
 
5
- /** @typedef {import('../types/index.js').MediaType} MediaType */
6
- /** @typedef {import('../types/index.js').Middleware} Middleware */
5
+ /** @typedef {import('../data/custom-types.js').MediaType} MediaType */
6
+ /** @typedef {import('../data/custom-types.js').Middleware} Middleware */
7
7
  /** @typedef {import('stream').Writable} Writable */
8
8
 
9
9
  /** @typedef {Partial<MediaType> & {parse:(this:HttpRequest)=>any|PromiseLike<any>, test?:(this:HttpRequest, mediaType: MediaType)=>boolean}} ContentReaderRegistration */
@@ -1,6 +1,6 @@
1
1
  /** @typedef {import('../lib/HttpResponse.js').default} HttpResponse */
2
- /** @typedef {import('../types').ResponseFinalizer} ResponseFinalizer */
3
- /** @typedef {import('../types').MiddlewareFunction} MiddlewareFunction */
2
+ /** @typedef {import('../data/custom-types.js').ResponseFinalizer} ResponseFinalizer */
3
+ /** @typedef {import('../data/custom-types.js').MiddlewareFunction} MiddlewareFunction */
4
4
 
5
5
  import { Transform } from 'node:stream';
6
6
 
@@ -1,6 +1,6 @@
1
- /** @typedef {import('../types').IMiddleware} IMiddleware */
2
- /** @typedef {import('../types').MiddlewareFunction} MiddlewareFunction */
3
- /** @typedef {import('../types').RequestMethod} RequestMethod */
1
+ /** @typedef {import('../data/custom-types.js').IMiddleware} IMiddleware */
2
+ /** @typedef {import('../data/custom-types.js').MiddlewareFunction} MiddlewareFunction */
3
+ /** @typedef {import('../data/custom-types.js').RequestMethod} RequestMethod */
4
4
 
5
5
  /**
6
6
  * @typedef CORSMiddlewareOptions
@@ -1,7 +1,7 @@
1
1
  import CaseInsensitiveObject from '../utils/CaseInsensitiveObject.js';
2
2
 
3
- /** @typedef {import('../types').IMiddleware} IMiddleware */
4
- /** @typedef {import('../types').MiddlewareFunction} MiddlewareFunction */
3
+ /** @typedef {import('../data/custom-types.js').IMiddleware} IMiddleware */
4
+ /** @typedef {import('../data/custom-types.js').MiddlewareFunction} MiddlewareFunction */
5
5
 
6
6
  /**
7
7
  * @typedef {Object} CaseInsensitiveHeadersMiddlewareOptions