undici 6.21.0 → 7.0.0-alpha.10

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 (156) hide show
  1. package/README.md +27 -46
  2. package/docs/docs/api/Agent.md +14 -17
  3. package/docs/docs/api/BalancedPool.md +16 -16
  4. package/docs/docs/api/CacheStore.md +131 -0
  5. package/docs/docs/api/Client.md +12 -14
  6. package/docs/docs/api/Debug.md +1 -1
  7. package/docs/docs/api/Dispatcher.md +98 -194
  8. package/docs/docs/api/EnvHttpProxyAgent.md +12 -13
  9. package/docs/docs/api/MockAgent.md +5 -3
  10. package/docs/docs/api/MockClient.md +5 -5
  11. package/docs/docs/api/MockPool.md +4 -3
  12. package/docs/docs/api/Pool.md +15 -16
  13. package/docs/docs/api/PoolStats.md +1 -1
  14. package/docs/docs/api/ProxyAgent.md +3 -3
  15. package/docs/docs/api/RedirectHandler.md +1 -1
  16. package/docs/docs/api/RetryAgent.md +1 -1
  17. package/docs/docs/api/RetryHandler.md +4 -4
  18. package/docs/docs/api/WebSocket.md +46 -4
  19. package/docs/docs/api/api-lifecycle.md +11 -11
  20. package/docs/docs/best-practices/mocking-request.md +2 -2
  21. package/docs/docs/best-practices/proxy.md +1 -1
  22. package/index.d.ts +1 -1
  23. package/index.js +23 -7
  24. package/lib/api/abort-signal.js +2 -0
  25. package/lib/api/api-connect.js +3 -1
  26. package/lib/api/api-pipeline.js +7 -6
  27. package/lib/api/api-request.js +33 -48
  28. package/lib/api/api-stream.js +39 -50
  29. package/lib/api/api-upgrade.js +5 -3
  30. package/lib/api/readable.js +235 -62
  31. package/lib/api/util.js +2 -0
  32. package/lib/cache/memory-cache-store.js +177 -0
  33. package/lib/cache/sqlite-cache-store.js +446 -0
  34. package/lib/core/constants.js +35 -10
  35. package/lib/core/diagnostics.js +122 -128
  36. package/lib/core/errors.js +6 -6
  37. package/lib/core/request.js +13 -11
  38. package/lib/core/symbols.js +2 -1
  39. package/lib/core/tree.js +9 -1
  40. package/lib/core/util.js +237 -49
  41. package/lib/dispatcher/agent.js +3 -17
  42. package/lib/dispatcher/balanced-pool.js +5 -8
  43. package/lib/dispatcher/client-h1.js +379 -134
  44. package/lib/dispatcher/client-h2.js +173 -107
  45. package/lib/dispatcher/client.js +19 -32
  46. package/lib/dispatcher/dispatcher-base.js +6 -35
  47. package/lib/dispatcher/dispatcher.js +7 -24
  48. package/lib/dispatcher/fixed-queue.js +91 -49
  49. package/lib/dispatcher/pool-stats.js +2 -0
  50. package/lib/dispatcher/pool.js +3 -6
  51. package/lib/dispatcher/proxy-agent.js +3 -6
  52. package/lib/handler/cache-handler.js +393 -0
  53. package/lib/handler/cache-revalidation-handler.js +124 -0
  54. package/lib/handler/decorator-handler.js +27 -0
  55. package/lib/handler/redirect-handler.js +54 -59
  56. package/lib/handler/retry-handler.js +77 -109
  57. package/lib/handler/unwrap-handler.js +96 -0
  58. package/lib/handler/wrap-handler.js +98 -0
  59. package/lib/interceptor/cache.js +350 -0
  60. package/lib/interceptor/dns.js +375 -0
  61. package/lib/interceptor/dump.js +2 -2
  62. package/lib/interceptor/redirect.js +11 -14
  63. package/lib/interceptor/response-error.js +18 -7
  64. package/lib/llhttp/constants.d.ts +97 -0
  65. package/lib/llhttp/constants.js +412 -192
  66. package/lib/llhttp/constants.js.map +1 -0
  67. package/lib/llhttp/llhttp-wasm.js +11 -1
  68. package/lib/llhttp/llhttp_simd-wasm.js +11 -1
  69. package/lib/llhttp/utils.d.ts +2 -0
  70. package/lib/llhttp/utils.js +9 -9
  71. package/lib/llhttp/utils.js.map +1 -0
  72. package/lib/mock/mock-agent.js +5 -8
  73. package/lib/mock/mock-client.js +9 -4
  74. package/lib/mock/mock-errors.js +3 -1
  75. package/lib/mock/mock-interceptor.js +8 -6
  76. package/lib/mock/mock-pool.js +9 -4
  77. package/lib/mock/mock-symbols.js +3 -1
  78. package/lib/mock/mock-utils.js +29 -5
  79. package/lib/util/cache.js +360 -0
  80. package/lib/web/cache/cache.js +24 -21
  81. package/lib/web/cache/cachestorage.js +1 -1
  82. package/lib/web/cookies/index.js +29 -14
  83. package/lib/web/cookies/parse.js +8 -3
  84. package/lib/web/eventsource/eventsource-stream.js +9 -8
  85. package/lib/web/eventsource/eventsource.js +10 -6
  86. package/lib/web/fetch/body.js +43 -41
  87. package/lib/web/fetch/constants.js +12 -5
  88. package/lib/web/fetch/data-url.js +3 -3
  89. package/lib/web/fetch/formdata-parser.js +72 -45
  90. package/lib/web/fetch/formdata.js +65 -54
  91. package/lib/web/fetch/headers.js +118 -86
  92. package/lib/web/fetch/index.js +58 -67
  93. package/lib/web/fetch/request.js +136 -77
  94. package/lib/web/fetch/response.js +87 -56
  95. package/lib/web/fetch/util.js +259 -109
  96. package/lib/web/fetch/webidl.js +113 -68
  97. package/lib/web/websocket/connection.js +76 -147
  98. package/lib/web/websocket/constants.js +70 -10
  99. package/lib/web/websocket/events.js +4 -2
  100. package/lib/web/websocket/frame.js +45 -3
  101. package/lib/web/websocket/receiver.js +29 -33
  102. package/lib/web/websocket/sender.js +18 -13
  103. package/lib/web/websocket/stream/websocketerror.js +83 -0
  104. package/lib/web/websocket/stream/websocketstream.js +485 -0
  105. package/lib/web/websocket/util.js +128 -77
  106. package/lib/web/websocket/websocket.js +234 -135
  107. package/package.json +24 -36
  108. package/scripts/strip-comments.js +3 -1
  109. package/types/agent.d.ts +7 -7
  110. package/types/api.d.ts +24 -24
  111. package/types/balanced-pool.d.ts +11 -11
  112. package/types/cache-interceptor.d.ts +172 -0
  113. package/types/client.d.ts +11 -12
  114. package/types/cookies.d.ts +2 -0
  115. package/types/diagnostics-channel.d.ts +10 -10
  116. package/types/dispatcher.d.ts +113 -90
  117. package/types/env-http-proxy-agent.d.ts +2 -2
  118. package/types/errors.d.ts +53 -47
  119. package/types/fetch.d.ts +17 -16
  120. package/types/formdata.d.ts +7 -7
  121. package/types/global-dispatcher.d.ts +4 -4
  122. package/types/global-origin.d.ts +5 -5
  123. package/types/handlers.d.ts +7 -7
  124. package/types/header.d.ts +157 -1
  125. package/types/index.d.ts +44 -46
  126. package/types/interceptors.d.ts +25 -8
  127. package/types/mock-agent.d.ts +21 -18
  128. package/types/mock-client.d.ts +4 -4
  129. package/types/mock-errors.d.ts +3 -3
  130. package/types/mock-interceptor.d.ts +19 -19
  131. package/types/mock-pool.d.ts +4 -4
  132. package/types/patch.d.ts +0 -4
  133. package/types/pool-stats.d.ts +8 -8
  134. package/types/pool.d.ts +12 -12
  135. package/types/proxy-agent.d.ts +4 -4
  136. package/types/readable.d.ts +18 -15
  137. package/types/retry-agent.d.ts +1 -1
  138. package/types/retry-handler.d.ts +10 -10
  139. package/types/util.d.ts +3 -3
  140. package/types/utility.d.ts +7 -0
  141. package/types/webidl.d.ts +44 -6
  142. package/types/websocket.d.ts +34 -1
  143. package/docs/docs/api/DispatchInterceptor.md +0 -60
  144. package/lib/interceptor/redirect-interceptor.js +0 -21
  145. package/lib/mock/pluralizer.js +0 -29
  146. package/lib/web/cache/symbols.js +0 -5
  147. package/lib/web/fetch/file.js +0 -126
  148. package/lib/web/fetch/symbols.js +0 -9
  149. package/lib/web/fileapi/encoding.js +0 -290
  150. package/lib/web/fileapi/filereader.js +0 -344
  151. package/lib/web/fileapi/progressevent.js +0 -78
  152. package/lib/web/fileapi/symbols.js +0 -10
  153. package/lib/web/fileapi/util.js +0 -391
  154. package/lib/web/websocket/symbols.js +0 -12
  155. package/types/file.d.ts +0 -39
  156. package/types/filereader.d.ts +0 -54
@@ -0,0 +1,393 @@
1
+ 'use strict'
2
+
3
+ const util = require('../core/util')
4
+ const {
5
+ parseCacheControlHeader,
6
+ parseVaryHeader,
7
+ isEtagUsable
8
+ } = require('../util/cache')
9
+
10
+ function noop () {}
11
+
12
+ /**
13
+ * @typedef {import('../../types/dispatcher.d.ts').default.DispatchHandler} DispatchHandler
14
+ *
15
+ * @implements {DispatchHandler}
16
+ */
17
+ class CacheHandler {
18
+ /**
19
+ * @type {import('../../types/cache-interceptor.d.ts').default.CacheKey}
20
+ */
21
+ #cacheKey
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
+
33
+ /**
34
+ * @type {import('../../types/cache-interceptor.d.ts').default.CacheStore}
35
+ */
36
+ #store
37
+
38
+ /**
39
+ * @type {import('../../types/dispatcher.d.ts').default.DispatchHandler}
40
+ */
41
+ #handler
42
+
43
+ /**
44
+ * @type {import('node:stream').Writable | undefined}
45
+ */
46
+ #writeStream
47
+
48
+ /**
49
+ * @param {import('../../types/cache-interceptor.d.ts').default.CacheHandlerOptions} opts
50
+ * @param {import('../../types/cache-interceptor.d.ts').default.CacheKey} cacheKey
51
+ * @param {import('../../types/dispatcher.d.ts').default.DispatchHandler} handler
52
+ */
53
+ constructor ({ store, type, cacheByDefault }, cacheKey, handler) {
54
+ this.#store = store
55
+ this.#cacheType = type
56
+ this.#cacheByDefault = cacheByDefault
57
+ this.#cacheKey = cacheKey
58
+ this.#handler = handler
59
+ }
60
+
61
+ onRequestStart (controller, context) {
62
+ this.#writeStream?.destroy()
63
+ this.#writeStream = undefined
64
+ this.#handler.onRequestStart?.(controller, context)
65
+ }
66
+
67
+ onRequestUpgrade (controller, statusCode, headers, socket) {
68
+ this.#handler.onRequestUpgrade?.(controller, statusCode, headers, socket)
69
+ }
70
+
71
+ onResponseStart (
72
+ controller,
73
+ statusCode,
74
+ headers,
75
+ statusMessage
76
+ ) {
77
+ const downstreamOnHeaders = () =>
78
+ this.#handler.onResponseStart?.(
79
+ controller,
80
+ statusCode,
81
+ headers,
82
+ statusMessage
83
+ )
84
+
85
+ if (
86
+ !util.safeHTTPMethods.includes(this.#cacheKey.method) &&
87
+ statusCode >= 200 &&
88
+ statusCode <= 399
89
+ ) {
90
+ // https://www.rfc-editor.org/rfc/rfc9111.html#name-invalidating-stored-response
91
+ try {
92
+ this.#store.delete(this.#cacheKey).catch?.(noop)
93
+ } catch {
94
+ // Fail silently
95
+ }
96
+ return downstreamOnHeaders()
97
+ }
98
+
99
+ const cacheControlHeader = headers['cache-control']
100
+ if (!cacheControlHeader && !headers['expires'] && !this.#cacheByDefault) {
101
+ // Don't have the cache control header or the cache is full
102
+ return downstreamOnHeaders()
103
+ }
104
+
105
+ const cacheControlDirectives = cacheControlHeader ? parseCacheControlHeader(cacheControlHeader) : {}
106
+ if (!canCacheResponse(this.#cacheType, statusCode, headers, cacheControlDirectives)) {
107
+ return downstreamOnHeaders()
108
+ }
109
+
110
+ const age = getAge(headers)
111
+
112
+ const now = Date.now()
113
+ const staleAt = determineStaleAt(this.#cacheType, now, headers, cacheControlDirectives) ?? this.#cacheByDefault
114
+ if (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
+ }
139
+
140
+ const deleteAt = determineDeleteAt(cacheControlDirectives, absoluteStaleAt)
141
+ const strippedHeaders = stripNecessaryHeaders(headers, cacheControlDirectives)
142
+
143
+ /**
144
+ * @type {import('../../types/cache-interceptor.d.ts').default.CacheValue}
145
+ */
146
+ const value = {
147
+ statusCode,
148
+ statusMessage,
149
+ headers: strippedHeaders,
150
+ vary: varyDirectives,
151
+ cacheControlDirectives,
152
+ cachedAt: age ? now - (age * 1000) : now,
153
+ staleAt: absoluteStaleAt,
154
+ deleteAt
155
+ }
156
+
157
+ if (typeof headers.etag === 'string' && isEtagUsable(headers.etag)) {
158
+ value.etag = headers.etag
159
+ }
160
+
161
+ this.#writeStream = this.#store.createWriteStream(this.#cacheKey, value)
162
+
163
+ if (this.#writeStream) {
164
+ const handler = this
165
+ this.#writeStream
166
+ .on('drain', () => controller.resume())
167
+ .on('error', function () {
168
+ // TODO (fix): Make error somehow observable?
169
+ handler.#writeStream = undefined
170
+ })
171
+ .on('close', function () {
172
+ if (handler.#writeStream === this) {
173
+ handler.#writeStream = undefined
174
+ }
175
+
176
+ // TODO (fix): Should we resume even if was paused downstream?
177
+ controller.resume()
178
+ })
179
+ }
180
+ }
181
+
182
+ return downstreamOnHeaders()
183
+ }
184
+
185
+ onResponseData (controller, chunk) {
186
+ if (this.#writeStream?.write(chunk) === false) {
187
+ controller.pause()
188
+ }
189
+
190
+ this.#handler.onResponseData?.(controller, chunk)
191
+ }
192
+
193
+ onResponseEnd (controller, trailers) {
194
+ this.#writeStream?.end()
195
+ this.#handler.onResponseEnd?.(controller, trailers)
196
+ }
197
+
198
+ onResponseError (controller, err) {
199
+ this.#writeStream?.destroy(err)
200
+ this.#writeStream = undefined
201
+ this.#handler.onResponseError?.(controller, err)
202
+ }
203
+ }
204
+
205
+ /**
206
+ * @see https://www.rfc-editor.org/rfc/rfc9111.html#name-storing-responses-to-authen
207
+ *
208
+ * @param {import('../../types/cache-interceptor.d.ts').default.CacheOptions['type']} cacheType
209
+ * @param {number} statusCode
210
+ * @param {Record<string, string | string[]>} headers
211
+ * @param {import('../../types/cache-interceptor.d.ts').default.CacheControlDirectives} cacheControlDirectives
212
+ */
213
+ function canCacheResponse (cacheType, statusCode, headers, cacheControlDirectives) {
214
+ if (statusCode !== 200 && statusCode !== 307) {
215
+ return false
216
+ }
217
+
218
+ if (
219
+ cacheControlDirectives['no-cache'] === true ||
220
+ cacheControlDirectives['no-store']
221
+ ) {
222
+ return false
223
+ }
224
+
225
+ if (cacheType === 'shared' && cacheControlDirectives.private === true) {
226
+ return false
227
+ }
228
+
229
+ // https://www.rfc-editor.org/rfc/rfc9111.html#section-4.1-5
230
+ if (headers.vary?.includes('*')) {
231
+ return false
232
+ }
233
+
234
+ // https://www.rfc-editor.org/rfc/rfc9111.html#name-storing-responses-to-authen
235
+ if (headers.authorization) {
236
+ if (!cacheControlDirectives.public || typeof headers.authorization !== 'string') {
237
+ return false
238
+ }
239
+
240
+ if (
241
+ Array.isArray(cacheControlDirectives['no-cache']) &&
242
+ cacheControlDirectives['no-cache'].includes('authorization')
243
+ ) {
244
+ return false
245
+ }
246
+
247
+ if (
248
+ Array.isArray(cacheControlDirectives['private']) &&
249
+ cacheControlDirectives['private'].includes('authorization')
250
+ ) {
251
+ return false
252
+ }
253
+ }
254
+
255
+ return true
256
+ }
257
+
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
277
+ * @param {number} now
278
+ * @param {Record<string, string | string[]>} headers
279
+ * @param {import('../../types/cache-interceptor.d.ts').default.CacheControlDirectives} cacheControlDirectives
280
+ *
281
+ * @returns {number | undefined} time that the value is stale at or undefined if it shouldn't be cached
282
+ */
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
+ }
292
+ }
293
+
294
+ const maxAge = cacheControlDirectives['max-age']
295
+ if (maxAge) {
296
+ return maxAge * 1000
297
+ }
298
+
299
+ if (headers.expires && typeof headers.expires === 'string') {
300
+ // https://www.rfc-editor.org/rfc/rfc9111.html#section-5.3
301
+ const expiresDate = new Date(headers.expires)
302
+ if (expiresDate instanceof Date && Number.isFinite(expiresDate.valueOf())) {
303
+ if (now >= expiresDate.getTime()) {
304
+ return undefined
305
+ }
306
+
307
+ return expiresDate.getTime() - now
308
+ }
309
+ }
310
+
311
+ if (cacheControlDirectives.immutable) {
312
+ // https://www.rfc-editor.org/rfc/rfc8246.html#section-2.2
313
+ return 31536000
314
+ }
315
+
316
+ return undefined
317
+ }
318
+
319
+ /**
320
+ * @param {import('../../types/cache-interceptor.d.ts').default.CacheControlDirectives} cacheControlDirectives
321
+ * @param {number} staleAt
322
+ */
323
+ function determineDeleteAt (cacheControlDirectives, staleAt) {
324
+ let staleWhileRevalidate = -Infinity
325
+ let staleIfError = -Infinity
326
+ let immutable = -Infinity
327
+
328
+ if (cacheControlDirectives['stale-while-revalidate']) {
329
+ staleWhileRevalidate = staleAt + (cacheControlDirectives['stale-while-revalidate'] * 1000)
330
+ }
331
+
332
+ if (cacheControlDirectives['stale-if-error']) {
333
+ staleIfError = staleAt + (cacheControlDirectives['stale-if-error'] * 1000)
334
+ }
335
+
336
+ if (staleWhileRevalidate === -Infinity && staleIfError === -Infinity) {
337
+ immutable = 31536000
338
+ }
339
+
340
+ return Math.max(staleAt, staleWhileRevalidate, staleIfError, immutable)
341
+ }
342
+
343
+ /**
344
+ * Strips headers required to be removed in cached responses
345
+ * @param {Record<string, string | string[]>} headers
346
+ * @param {import('../../types/cache-interceptor.d.ts').default.CacheControlDirectives} cacheControlDirectives
347
+ * @returns {Record<string, string | string []>}
348
+ */
349
+ function stripNecessaryHeaders (headers, cacheControlDirectives) {
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
+ }
373
+
374
+ if (Array.isArray(cacheControlDirectives['no-cache'])) {
375
+ headersToRemove.push(...cacheControlDirectives['no-cache'])
376
+ }
377
+
378
+ if (Array.isArray(cacheControlDirectives['private'])) {
379
+ headersToRemove.push(...cacheControlDirectives['private'])
380
+ }
381
+
382
+ let strippedHeaders
383
+ for (const headerName of headersToRemove) {
384
+ if (headers[headerName]) {
385
+ strippedHeaders ??= { ...headers }
386
+ delete strippedHeaders[headerName]
387
+ }
388
+ }
389
+
390
+ return strippedHeaders ?? headers
391
+ }
392
+
393
+ module.exports = CacheHandler
@@ -0,0 +1,124 @@
1
+ 'use strict'
2
+
3
+ const assert = require('node:assert')
4
+
5
+ /**
6
+ * This takes care of revalidation requests we send to the origin. If we get
7
+ * a response indicating that what we have is cached (via a HTTP 304), we can
8
+ * continue using the cached value. Otherwise, we'll receive the new response
9
+ * here, which we then just pass on to the next handler (most likely a
10
+ * CacheHandler). Note that this assumes the proper headers were already
11
+ * included in the request to tell the origin that we want to revalidate the
12
+ * response (i.e. if-modified-since).
13
+ *
14
+ * @see https://www.rfc-editor.org/rfc/rfc9111.html#name-validation
15
+ *
16
+ * @implements {import('../../types/dispatcher.d.ts').default.DispatchHandler}
17
+ */
18
+ class CacheRevalidationHandler {
19
+ #successful = false
20
+
21
+ /**
22
+ * @type {((boolean, any) => void) | null}
23
+ */
24
+ #callback
25
+
26
+ /**
27
+ * @type {(import('../../types/dispatcher.d.ts').default.DispatchHandler)}
28
+ */
29
+ #handler
30
+
31
+ #context
32
+
33
+ /**
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
42
+ */
43
+ constructor (callback, handler, allowErrorStatusCodes) {
44
+ if (typeof callback !== 'function') {
45
+ throw new TypeError('callback must be a function')
46
+ }
47
+
48
+ this.#callback = callback
49
+ this.#handler = handler
50
+ this.#allowErrorStatusCodes = allowErrorStatusCodes
51
+ }
52
+
53
+ onRequestStart (_, context) {
54
+ this.#successful = false
55
+ this.#context = context
56
+ }
57
+
58
+ onRequestUpgrade (controller, statusCode, headers, socket) {
59
+ this.#handler.onRequestUpgrade?.(controller, statusCode, headers, socket)
60
+ }
61
+
62
+ onResponseStart (
63
+ controller,
64
+ statusCode,
65
+ headers,
66
+ statusMessage
67
+ ) {
68
+ assert(this.#callback != null)
69
+
70
+ // https://www.rfc-editor.org/rfc/rfc9111.html#name-handling-a-validation-respo
71
+ // https://datatracker.ietf.org/doc/html/rfc5861#section-4
72
+ this.#successful = statusCode === 304 ||
73
+ (this.#allowErrorStatusCodes && statusCode >= 500 && statusCode <= 504)
74
+ this.#callback(this.#successful, this.#context)
75
+ this.#callback = null
76
+
77
+ if (this.#successful) {
78
+ return true
79
+ }
80
+
81
+ this.#handler.onRequestStart?.(controller, this.#context)
82
+ this.#handler.onResponseStart?.(
83
+ controller,
84
+ statusCode,
85
+ headers,
86
+ statusMessage
87
+ )
88
+ }
89
+
90
+ onResponseData (controller, chunk) {
91
+ if (this.#successful) {
92
+ return
93
+ }
94
+
95
+ return this.#handler.onResponseData?.(controller, chunk)
96
+ }
97
+
98
+ onResponseEnd (controller, trailers) {
99
+ if (this.#successful) {
100
+ return
101
+ }
102
+
103
+ this.#handler.onResponseEnd?.(controller, trailers)
104
+ }
105
+
106
+ onResponseError (controller, err) {
107
+ if (this.#successful) {
108
+ return
109
+ }
110
+
111
+ if (this.#callback) {
112
+ this.#callback(false)
113
+ this.#callback = null
114
+ }
115
+
116
+ if (typeof this.#handler.onResponseError === 'function') {
117
+ this.#handler.onResponseError(controller, err)
118
+ } else {
119
+ throw err
120
+ }
121
+ }
122
+ }
123
+
124
+ module.exports = CacheRevalidationHandler
@@ -1,7 +1,14 @@
1
1
  'use strict'
2
2
 
3
+ const assert = require('node:assert')
4
+
5
+ /**
6
+ * @deprecated
7
+ */
3
8
  module.exports = class DecoratorHandler {
4
9
  #handler
10
+ #onCompleteCalled = false
11
+ #onErrorCalled = false
5
12
 
6
13
  constructor (handler) {
7
14
  if (typeof handler !== 'object' || handler === null) {
@@ -15,30 +22,50 @@ module.exports = class DecoratorHandler {
15
22
  }
16
23
 
17
24
  onError (...args) {
25
+ this.#onErrorCalled = true
18
26
  return this.#handler.onError?.(...args)
19
27
  }
20
28
 
21
29
  onUpgrade (...args) {
30
+ assert(!this.#onCompleteCalled)
31
+ assert(!this.#onErrorCalled)
32
+
22
33
  return this.#handler.onUpgrade?.(...args)
23
34
  }
24
35
 
25
36
  onResponseStarted (...args) {
37
+ assert(!this.#onCompleteCalled)
38
+ assert(!this.#onErrorCalled)
39
+
26
40
  return this.#handler.onResponseStarted?.(...args)
27
41
  }
28
42
 
29
43
  onHeaders (...args) {
44
+ assert(!this.#onCompleteCalled)
45
+ assert(!this.#onErrorCalled)
46
+
30
47
  return this.#handler.onHeaders?.(...args)
31
48
  }
32
49
 
33
50
  onData (...args) {
51
+ assert(!this.#onCompleteCalled)
52
+ assert(!this.#onErrorCalled)
53
+
34
54
  return this.#handler.onData?.(...args)
35
55
  }
36
56
 
37
57
  onComplete (...args) {
58
+ assert(!this.#onCompleteCalled)
59
+ assert(!this.#onErrorCalled)
60
+
61
+ this.#onCompleteCalled = true
38
62
  return this.#handler.onComplete?.(...args)
39
63
  }
40
64
 
41
65
  onBodySent (...args) {
66
+ assert(!this.#onCompleteCalled)
67
+ assert(!this.#onErrorCalled)
68
+
42
69
  return this.#handler.onBodySent?.(...args)
43
70
  }
44
71
  }