undici 7.0.0-alpha.7 → 7.0.0-alpha.8

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.
@@ -1260,6 +1260,8 @@ The `cache` interceptor implements client-side response caching as described in
1260
1260
 
1261
1261
  - `store` - The [`CacheStore`](/docs/docs/api/CacheStore.md) to store and retrieve responses from. Default is [`MemoryCacheStore`](/docs/docs/api/CacheStore.md#memorycachestore).
1262
1262
  - `methods` - The [**safe** HTTP methods](https://www.rfc-editor.org/rfc/rfc9110#section-9.2.1) to cache the response of.
1263
+ - `cacheByDefault` - The default expiration time to cache responses by if they don't have an explicit expiration. If this isn't present, responses without explicit expiration will not be cached. Default `undefined`.
1264
+ - `type` - The type of cache for Undici to act as. Can be `shared` or `private`. Default `shared`.
1263
1265
 
1264
1266
  ## Instance Events
1265
1267
 
@@ -89,7 +89,9 @@ class MemoryCacheStore {
89
89
  statusCode: entry.statusCode,
90
90
  headers: entry.headers,
91
91
  body: entry.body,
92
+ vary: entry.vary ? entry.vary : undefined,
92
93
  etag: entry.etag,
94
+ cacheControlDirectives: entry.cacheControlDirectives,
93
95
  cachedAt: entry.cachedAt,
94
96
  staleAt: entry.staleAt,
95
97
  deleteAt: entry.deleteAt
@@ -4,7 +4,10 @@ const { DatabaseSync } = require('node:sqlite')
4
4
  const { Writable } = require('stream')
5
5
  const { assertCacheKey, assertCacheValue } = require('../util/cache.js')
6
6
 
7
- const VERSION = 2
7
+ const VERSION = 3
8
+
9
+ // 2gb
10
+ const MAX_ENTRY_SIZE = 2 * 1000 * 1000 * 1000
8
11
 
9
12
  /**
10
13
  * @typedef {import('../../types/cache-interceptor.d.ts').default.CacheStore} CacheStore
@@ -17,8 +20,8 @@ const VERSION = 2
17
20
  * body: string
18
21
  * } & import('../../types/cache-interceptor.d.ts').default.CacheValue} SqliteStoreValue
19
22
  */
20
- class SqliteCacheStore {
21
- #maxEntrySize = Infinity
23
+ module.exports = class SqliteCacheStore {
24
+ #maxEntrySize = MAX_ENTRY_SIZE
22
25
  #maxCount = Infinity
23
26
 
24
27
  /**
@@ -78,6 +81,11 @@ class SqliteCacheStore {
78
81
  ) {
79
82
  throw new TypeError('SqliteCacheStore options.maxEntrySize must be a non-negative integer')
80
83
  }
84
+
85
+ if (opts.maxEntrySize > MAX_ENTRY_SIZE) {
86
+ throw new TypeError('SqliteCacheStore options.maxEntrySize must be less than 2gb')
87
+ }
88
+
81
89
  this.#maxEntrySize = opts.maxEntrySize
82
90
  }
83
91
 
@@ -103,11 +111,12 @@ class SqliteCacheStore {
103
111
  method TEXT NOT NULL,
104
112
 
105
113
  -- Data returned to the interceptor
106
- body TEXT NULL,
114
+ body BUF NULL,
107
115
  deleteAt INTEGER NOT NULL,
108
116
  statusCode INTEGER NOT NULL,
109
117
  statusMessage TEXT NOT NULL,
110
118
  headers TEXT NULL,
119
+ cacheControlDirectives TEXT NULL,
111
120
  etag TEXT NULL,
112
121
  vary TEXT NULL,
113
122
  cachedAt INTEGER NOT NULL,
@@ -128,6 +137,7 @@ class SqliteCacheStore {
128
137
  statusMessage,
129
138
  headers,
130
139
  etag,
140
+ cacheControlDirectives,
131
141
  vary,
132
142
  cachedAt,
133
143
  staleAt
@@ -147,6 +157,7 @@ class SqliteCacheStore {
147
157
  statusMessage = ?,
148
158
  headers = ?,
149
159
  etag = ?,
160
+ cacheControlDirectives = ?,
150
161
  cachedAt = ?,
151
162
  staleAt = ?,
152
163
  deleteAt = ?
@@ -164,11 +175,12 @@ class SqliteCacheStore {
164
175
  statusMessage,
165
176
  headers,
166
177
  etag,
178
+ cacheControlDirectives,
167
179
  vary,
168
180
  cachedAt,
169
181
  staleAt,
170
182
  deleteAt
171
- ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
183
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
172
184
  `)
173
185
 
174
186
  this.#deleteByUrlQuery = this.#db.prepare(
@@ -218,11 +230,15 @@ class SqliteCacheStore {
218
230
  * @type {import('../../types/cache-interceptor.d.ts').default.GetResult}
219
231
  */
220
232
  const result = {
221
- body: value.body ? parseBufferArray(JSON.parse(value.body)) : null,
233
+ body: Buffer.from(value.body),
222
234
  statusCode: value.statusCode,
223
235
  statusMessage: value.statusMessage,
224
236
  headers: value.headers ? JSON.parse(value.headers) : undefined,
225
237
  etag: value.etag ? value.etag : undefined,
238
+ vary: value.vary ?? undefined,
239
+ cacheControlDirectives: value.cacheControlDirectives
240
+ ? JSON.parse(value.cacheControlDirectives)
241
+ : undefined,
226
242
  cachedAt: value.cachedAt,
227
243
  staleAt: value.staleAt,
228
244
  deleteAt: value.deleteAt
@@ -269,12 +285,13 @@ class SqliteCacheStore {
269
285
  if (existingValue) {
270
286
  // Updating an existing response, let's overwrite it
271
287
  store.#updateValueQuery.run(
272
- JSON.stringify(stringifyBufferArray(body)),
288
+ Buffer.concat(body),
273
289
  value.deleteAt,
274
290
  value.statusCode,
275
291
  value.statusMessage,
276
292
  value.headers ? JSON.stringify(value.headers) : null,
277
- value.etag,
293
+ value.etag ? value.etag : null,
294
+ value.cacheControlDirectives ? JSON.stringify(value.cacheControlDirectives) : null,
278
295
  value.cachedAt,
279
296
  value.staleAt,
280
297
  value.deleteAt,
@@ -286,12 +303,13 @@ class SqliteCacheStore {
286
303
  store.#insertValueQuery.run(
287
304
  url,
288
305
  key.method,
289
- JSON.stringify(stringifyBufferArray(body)),
306
+ Buffer.concat(body),
290
307
  value.deleteAt,
291
308
  value.statusCode,
292
309
  value.statusMessage,
293
310
  value.headers ? JSON.stringify(value.headers) : null,
294
311
  value.etag ? value.etag : null,
312
+ value.cacheControlDirectives ? JSON.stringify(value.cacheControlDirectives) : null,
295
313
  value.vary ? JSON.stringify(value.vary) : null,
296
314
  value.cachedAt,
297
315
  value.staleAt,
@@ -316,7 +334,7 @@ class SqliteCacheStore {
316
334
  }
317
335
 
318
336
  #prune () {
319
- if (this.#size <= this.#maxCount) {
337
+ if (this.size <= this.#maxCount) {
320
338
  return 0
321
339
  }
322
340
 
@@ -341,7 +359,7 @@ class SqliteCacheStore {
341
359
  * Counts the number of rows in the cache
342
360
  * @returns {Number}
343
361
  */
344
- get #size () {
362
+ get size () {
345
363
  const { total } = this.#countEntriesQuery.get()
346
364
  return total
347
365
  }
@@ -385,10 +403,10 @@ class SqliteCacheStore {
385
403
  return undefined
386
404
  }
387
405
 
388
- const vary = JSON.parse(value.vary)
406
+ value.vary = JSON.parse(value.vary)
389
407
 
390
- for (const header in vary) {
391
- if (headerValueEquals(headers[header], vary[header])) {
408
+ for (const header in value.vary) {
409
+ if (!headerValueEquals(headers[header], value.vary[header])) {
392
410
  matches = false
393
411
  break
394
412
  }
@@ -426,32 +444,3 @@ function headerValueEquals (lhs, rhs) {
426
444
 
427
445
  return lhs === rhs
428
446
  }
429
-
430
- /**
431
- * @param {Buffer[]} buffers
432
- * @returns {string[]}
433
- */
434
- function stringifyBufferArray (buffers) {
435
- const output = new Array(buffers.length)
436
- for (let i = 0; i < buffers.length; i++) {
437
- output[i] = buffers[i].toString()
438
- }
439
-
440
- return output
441
- }
442
-
443
- /**
444
- * @param {string[]} strings
445
- * @returns {Buffer[]}
446
- */
447
- function parseBufferArray (strings) {
448
- const output = new Array(strings.length)
449
-
450
- for (let i = 0; i < strings.length; i++) {
451
- output[i] = Buffer.from(strings[i])
452
- }
453
-
454
- return output
455
- }
456
-
457
- module.exports = SqliteCacheStore
@@ -10,7 +10,9 @@ const {
10
10
  function noop () {}
11
11
 
12
12
  /**
13
- * @implements {import('../../types/dispatcher.d.ts').default.DispatchHandler}
13
+ * @typedef {import('../../types/dispatcher.d.ts').default.DispatchHandler} DispatchHandler
14
+ *
15
+ * @implements {DispatchHandler}
14
16
  */
15
17
  class CacheHandler {
16
18
  /**
@@ -18,6 +20,16 @@ class CacheHandler {
18
20
  */
19
21
  #cacheKey
20
22
 
23
+ /**
24
+ * @type {import('../../types/cache-interceptor.d.ts').default.CacheHandlerOptions['type']}
25
+ */
26
+ #cacheType
27
+
28
+ /**
29
+ * @type {number | undefined}
30
+ */
31
+ #cacheByDefault
32
+
21
33
  /**
22
34
  * @type {import('../../types/cache-interceptor.d.ts').default.CacheStore}
23
35
  */
@@ -38,10 +50,10 @@ class CacheHandler {
38
50
  * @param {import('../../types/cache-interceptor.d.ts').default.CacheKey} cacheKey
39
51
  * @param {import('../../types/dispatcher.d.ts').default.DispatchHandler} handler
40
52
  */
41
- constructor (opts, cacheKey, handler) {
42
- const { store } = opts
43
-
53
+ constructor ({ store, type, cacheByDefault }, cacheKey, handler) {
44
54
  this.#store = store
55
+ this.#cacheType = type
56
+ this.#cacheByDefault = cacheByDefault
45
57
  this.#cacheKey = cacheKey
46
58
  this.#handler = handler
47
59
  }
@@ -85,24 +97,47 @@ class CacheHandler {
85
97
  }
86
98
 
87
99
  const cacheControlHeader = headers['cache-control']
88
- if (!cacheControlHeader) {
100
+ if (!cacheControlHeader && !headers['expires'] && !this.#cacheByDefault) {
89
101
  // Don't have the cache control header or the cache is full
90
102
  return downstreamOnHeaders()
91
103
  }
92
104
 
93
- const cacheControlDirectives = parseCacheControlHeader(cacheControlHeader)
94
- if (!canCacheResponse(statusCode, headers, cacheControlDirectives)) {
105
+ const cacheControlDirectives = cacheControlHeader ? parseCacheControlHeader(cacheControlHeader) : {}
106
+ if (!canCacheResponse(this.#cacheType, statusCode, headers, cacheControlDirectives)) {
95
107
  return downstreamOnHeaders()
96
108
  }
97
109
 
110
+ const age = getAge(headers)
111
+
98
112
  const now = Date.now()
99
- const staleAt = determineStaleAt(now, headers, cacheControlDirectives)
113
+ const staleAt = determineStaleAt(this.#cacheType, now, headers, cacheControlDirectives) ?? this.#cacheByDefault
100
114
  if (staleAt) {
101
- const varyDirectives = this.#cacheKey.headers && headers.vary
102
- ? parseVaryHeader(headers.vary, this.#cacheKey.headers)
103
- : undefined
104
- const deleteAt = determineDeleteAt(now, cacheControlDirectives, staleAt)
115
+ let baseTime = now
116
+ if (headers['date']) {
117
+ const parsedDate = parseInt(headers['date'])
118
+ const date = new Date(isNaN(parsedDate) ? headers['date'] : parsedDate)
119
+ if (date instanceof Date && !isNaN(date)) {
120
+ baseTime = date.getTime()
121
+ }
122
+ }
123
+
124
+ const absoluteStaleAt = staleAt + baseTime
125
+
126
+ if (now >= absoluteStaleAt || (age && age >= staleAt)) {
127
+ // Response is already stale
128
+ return downstreamOnHeaders()
129
+ }
130
+
131
+ let varyDirectives
132
+ if (this.#cacheKey.headers && headers.vary) {
133
+ varyDirectives = parseVaryHeader(headers.vary, this.#cacheKey.headers)
134
+ if (!varyDirectives) {
135
+ // Parse error
136
+ return downstreamOnHeaders()
137
+ }
138
+ }
105
139
 
140
+ const deleteAt = determineDeleteAt(cacheControlDirectives, absoluteStaleAt)
106
141
  const strippedHeaders = stripNecessaryHeaders(headers, cacheControlDirectives)
107
142
 
108
143
  /**
@@ -113,8 +148,9 @@ class CacheHandler {
113
148
  statusMessage,
114
149
  headers: strippedHeaders,
115
150
  vary: varyDirectives,
116
- cachedAt: now,
117
- staleAt,
151
+ cacheControlDirectives,
152
+ cachedAt: age ? now - (age * 1000) : now,
153
+ staleAt: absoluteStaleAt,
118
154
  deleteAt
119
155
  }
120
156
 
@@ -130,6 +166,7 @@ class CacheHandler {
130
166
  .on('drain', () => controller.resume())
131
167
  .on('error', function () {
132
168
  // TODO (fix): Make error somehow observable?
169
+ handler.#writeStream = undefined
133
170
  })
134
171
  .on('close', function () {
135
172
  if (handler.#writeStream === this) {
@@ -168,25 +205,29 @@ class CacheHandler {
168
205
  /**
169
206
  * @see https://www.rfc-editor.org/rfc/rfc9111.html#name-storing-responses-to-authen
170
207
  *
208
+ * @param {import('../../types/cache-interceptor.d.ts').default.CacheOptions['type']} cacheType
171
209
  * @param {number} statusCode
172
210
  * @param {Record<string, string | string[]>} headers
173
- * @param {import('../util/cache.js').CacheControlDirectives} cacheControlDirectives
211
+ * @param {import('../../types/cache-interceptor.d.ts').default.CacheControlDirectives} cacheControlDirectives
174
212
  */
175
- function canCacheResponse (statusCode, headers, cacheControlDirectives) {
213
+ function canCacheResponse (cacheType, statusCode, headers, cacheControlDirectives) {
176
214
  if (statusCode !== 200 && statusCode !== 307) {
177
215
  return false
178
216
  }
179
217
 
180
218
  if (
181
- cacheControlDirectives.private === true ||
182
219
  cacheControlDirectives['no-cache'] === true ||
183
220
  cacheControlDirectives['no-store']
184
221
  ) {
185
222
  return false
186
223
  }
187
224
 
225
+ if (cacheType === 'shared' && cacheControlDirectives.private === true) {
226
+ return false
227
+ }
228
+
188
229
  // https://www.rfc-editor.org/rfc/rfc9111.html#section-4.1-5
189
- if (headers.vary === '*') {
230
+ if (headers.vary?.includes('*')) {
190
231
  return false
191
232
  }
192
233
 
@@ -215,63 +256,120 @@ function canCacheResponse (statusCode, headers, cacheControlDirectives) {
215
256
  }
216
257
 
217
258
  /**
259
+ * @param {Record<string, string | string[]>} headers
260
+ * @returns {number | undefined}
261
+ */
262
+ function getAge (headers) {
263
+ if (!headers.age) {
264
+ return undefined
265
+ }
266
+
267
+ const age = parseInt(Array.isArray(headers.age) ? headers.age[0] : headers.age)
268
+ if (isNaN(age) || age >= 2147483647) {
269
+ return undefined
270
+ }
271
+
272
+ return age
273
+ }
274
+
275
+ /**
276
+ * @param {import('../../types/cache-interceptor.d.ts').default.CacheOptions['type']} cacheType
218
277
  * @param {number} now
219
278
  * @param {Record<string, string | string[]>} headers
220
- * @param {import('../util/cache.js').CacheControlDirectives} cacheControlDirectives
279
+ * @param {import('../../types/cache-interceptor.d.ts').default.CacheControlDirectives} cacheControlDirectives
221
280
  *
222
281
  * @returns {number | undefined} time that the value is stale at or undefined if it shouldn't be cached
223
282
  */
224
- function determineStaleAt (now, headers, cacheControlDirectives) {
225
- // Prioritize s-maxage since we're a shared cache
226
- // s-maxage > max-age > Expire
227
- // https://www.rfc-editor.org/rfc/rfc9111.html#section-5.2.2.10-3
228
- const sMaxAge = cacheControlDirectives['s-maxage']
229
- if (sMaxAge) {
230
- return now + (sMaxAge * 1000)
231
- }
232
-
233
- if (cacheControlDirectives.immutable) {
234
- // https://www.rfc-editor.org/rfc/rfc8246.html#section-2.2
235
- return now + 31536000
283
+ function determineStaleAt (cacheType, now, headers, cacheControlDirectives) {
284
+ if (cacheType === 'shared') {
285
+ // Prioritize s-maxage since we're a shared cache
286
+ // s-maxage > max-age > Expire
287
+ // https://www.rfc-editor.org/rfc/rfc9111.html#section-5.2.2.10-3
288
+ const sMaxAge = cacheControlDirectives['s-maxage']
289
+ if (sMaxAge) {
290
+ return sMaxAge * 1000
291
+ }
236
292
  }
237
293
 
238
294
  const maxAge = cacheControlDirectives['max-age']
239
295
  if (maxAge) {
240
- return now + (maxAge * 1000)
296
+ return maxAge * 1000
241
297
  }
242
298
 
243
- if (headers.expire && typeof headers.expire === 'string') {
299
+ if (headers.expires && typeof headers.expires === 'string') {
244
300
  // https://www.rfc-editor.org/rfc/rfc9111.html#section-5.3
245
- const expiresDate = new Date(headers.expire)
301
+ const expiresDate = new Date(headers.expires)
246
302
  if (expiresDate instanceof Date && Number.isFinite(expiresDate.valueOf())) {
247
- return now + (Date.now() - expiresDate.getTime())
303
+ if (now >= expiresDate.getTime()) {
304
+ return undefined
305
+ }
306
+
307
+ return expiresDate.getTime() - now
248
308
  }
249
309
  }
250
310
 
311
+ if (cacheControlDirectives.immutable) {
312
+ // https://www.rfc-editor.org/rfc/rfc8246.html#section-2.2
313
+ return 31536000
314
+ }
315
+
251
316
  return undefined
252
317
  }
253
318
 
254
319
  /**
255
- * @param {number} now
256
- * @param {import('../util/cache.js').CacheControlDirectives} cacheControlDirectives
320
+ * @param {import('../../types/cache-interceptor.d.ts').default.CacheControlDirectives} cacheControlDirectives
257
321
  * @param {number} staleAt
258
322
  */
259
- function determineDeleteAt (now, cacheControlDirectives, staleAt) {
323
+ function determineDeleteAt (cacheControlDirectives, staleAt) {
324
+ let staleWhileRevalidate = -Infinity
325
+ let staleIfError = -Infinity
326
+ let immutable = -Infinity
327
+
260
328
  if (cacheControlDirectives['stale-while-revalidate']) {
261
- return now + (cacheControlDirectives['stale-while-revalidate'] * 1000)
329
+ staleWhileRevalidate = staleAt + (cacheControlDirectives['stale-while-revalidate'] * 1000)
330
+ }
331
+
332
+ if (cacheControlDirectives['stale-if-error']) {
333
+ staleIfError = staleAt + (cacheControlDirectives['stale-if-error'] * 1000)
262
334
  }
263
335
 
264
- return staleAt
336
+ if (staleWhileRevalidate === -Infinity && staleIfError === -Infinity) {
337
+ immutable = 31536000
338
+ }
339
+
340
+ return Math.max(staleAt, staleWhileRevalidate, staleIfError, immutable)
265
341
  }
266
342
 
267
343
  /**
268
344
  * Strips headers required to be removed in cached responses
269
345
  * @param {Record<string, string | string[]>} headers
270
- * @param {import('../util/cache.js').CacheControlDirectives} cacheControlDirectives
346
+ * @param {import('../../types/cache-interceptor.d.ts').default.CacheControlDirectives} cacheControlDirectives
271
347
  * @returns {Record<string, string | string []>}
272
348
  */
273
349
  function stripNecessaryHeaders (headers, cacheControlDirectives) {
274
- const headersToRemove = ['connection']
350
+ const headersToRemove = [
351
+ 'connection',
352
+ 'proxy-authenticate',
353
+ 'proxy-authentication-info',
354
+ 'proxy-authorization',
355
+ 'proxy-connection',
356
+ 'te',
357
+ 'transfer-encoding',
358
+ 'upgrade',
359
+ // We'll add age back when serving it
360
+ 'age'
361
+ ]
362
+
363
+ if (headers['connection']) {
364
+ if (Array.isArray(headers['connection'])) {
365
+ // connection: a
366
+ // connection: b
367
+ headersToRemove.push(...headers['connection'].map(header => header.trim()))
368
+ } else {
369
+ // connection: a, b
370
+ headersToRemove.push(...headers['connection'].split(',').map(header => header.trim()))
371
+ }
372
+ }
275
373
 
276
374
  if (Array.isArray(cacheControlDirectives['no-cache'])) {
277
375
  headersToRemove.push(...cacheControlDirectives['no-cache'])
@@ -282,12 +380,13 @@ function stripNecessaryHeaders (headers, cacheControlDirectives) {
282
380
  }
283
381
 
284
382
  let strippedHeaders
285
- for (const headerName of Object.keys(headers)) {
286
- if (headersToRemove.includes(headerName)) {
383
+ for (const headerName of headersToRemove) {
384
+ if (headers[headerName]) {
287
385
  strippedHeaders ??= { ...headers }
288
- delete headers[headerName]
386
+ delete strippedHeaders[headerName]
289
387
  }
290
388
  }
389
+
291
390
  return strippedHeaders ?? headers
292
391
  }
293
392
 
@@ -17,10 +17,12 @@ const assert = require('node:assert')
17
17
  */
18
18
  class CacheRevalidationHandler {
19
19
  #successful = false
20
+
20
21
  /**
21
22
  * @type {((boolean, any) => void) | null}
22
23
  */
23
24
  #callback
25
+
24
26
  /**
25
27
  * @type {(import('../../types/dispatcher.d.ts').default.DispatchHandler)}
26
28
  */
@@ -29,19 +31,26 @@ class CacheRevalidationHandler {
29
31
  #context
30
32
 
31
33
  /**
32
- * @param {(boolean, any) => void} callback Function to call if the cached value is valid
33
- * @param {import('../../types/dispatcher.d.ts').default.DispatchHandler} handler
34
+ * @type {boolean}
35
+ */
36
+ #allowErrorStatusCodes
37
+
38
+ /**
39
+ * @param {(boolean) => void} callback Function to call if the cached value is valid
40
+ * @param {import('../../types/dispatcher.d.ts').default.DispatchHandlers} handler
41
+ * @param {boolean} allowErrorStatusCodes
34
42
  */
35
- constructor (callback, handler) {
43
+ constructor (callback, handler, allowErrorStatusCodes) {
36
44
  if (typeof callback !== 'function') {
37
45
  throw new TypeError('callback must be a function')
38
46
  }
39
47
 
40
48
  this.#callback = callback
41
49
  this.#handler = handler
50
+ this.#allowErrorStatusCodes = allowErrorStatusCodes
42
51
  }
43
52
 
44
- onRequestStart (controller, context) {
53
+ onRequestStart (_, context) {
45
54
  this.#successful = false
46
55
  this.#context = context
47
56
  }
@@ -59,7 +68,9 @@ class CacheRevalidationHandler {
59
68
  assert(this.#callback != null)
60
69
 
61
70
  // https://www.rfc-editor.org/rfc/rfc9111.html#name-handling-a-validation-respo
62
- this.#successful = statusCode === 304
71
+ // https://datatracker.ietf.org/doc/html/rfc5861#section-4
72
+ this.#successful = statusCode === 304 ||
73
+ (this.#allowErrorStatusCodes && statusCode >= 500 && statusCode <= 504)
63
74
  this.#callback(this.#successful, this.#context)
64
75
  this.#callback = null
65
76
 
@@ -2,6 +2,9 @@
2
2
 
3
3
  const assert = require('node:assert')
4
4
 
5
+ /**
6
+ * @deprecated
7
+ */
5
8
  module.exports = class DecoratorHandler {
6
9
  #handler
7
10
  #onCompleteCalled = false