undici 7.0.0-alpha.1 → 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 (113) hide show
  1. package/README.md +24 -38
  2. package/docs/docs/api/Agent.md +14 -14
  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 -12
  6. package/docs/docs/api/Debug.md +1 -1
  7. package/docs/docs/api/Dispatcher.md +98 -193
  8. package/docs/docs/api/EnvHttpProxyAgent.md +12 -12
  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 -15
  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 -3
  24. package/lib/api/abort-signal.js +2 -0
  25. package/lib/api/api-pipeline.js +4 -2
  26. package/lib/api/api-request.js +6 -4
  27. package/lib/api/api-stream.js +3 -1
  28. package/lib/api/api-upgrade.js +2 -2
  29. package/lib/api/readable.js +200 -47
  30. package/lib/api/util.js +2 -0
  31. package/lib/cache/memory-cache-store.js +177 -0
  32. package/lib/cache/sqlite-cache-store.js +446 -0
  33. package/lib/core/connect.js +54 -22
  34. package/lib/core/constants.js +35 -10
  35. package/lib/core/diagnostics.js +122 -128
  36. package/lib/core/errors.js +2 -2
  37. package/lib/core/request.js +6 -6
  38. package/lib/core/symbols.js +2 -0
  39. package/lib/core/tree.js +4 -2
  40. package/lib/core/util.js +238 -40
  41. package/lib/dispatcher/client-h1.js +405 -142
  42. package/lib/dispatcher/client-h2.js +212 -109
  43. package/lib/dispatcher/client.js +24 -7
  44. package/lib/dispatcher/dispatcher-base.js +4 -1
  45. package/lib/dispatcher/dispatcher.js +4 -0
  46. package/lib/dispatcher/fixed-queue.js +91 -49
  47. package/lib/dispatcher/pool-base.js +3 -3
  48. package/lib/dispatcher/pool-stats.js +2 -0
  49. package/lib/dispatcher/proxy-agent.js +3 -1
  50. package/lib/handler/cache-handler.js +393 -0
  51. package/lib/handler/cache-revalidation-handler.js +124 -0
  52. package/lib/handler/decorator-handler.js +3 -0
  53. package/lib/handler/redirect-handler.js +45 -59
  54. package/lib/handler/retry-handler.js +68 -109
  55. package/lib/handler/unwrap-handler.js +96 -0
  56. package/lib/handler/wrap-handler.js +98 -0
  57. package/lib/interceptor/cache.js +350 -0
  58. package/lib/interceptor/dns.js +375 -0
  59. package/lib/interceptor/response-error.js +15 -7
  60. package/lib/mock/mock-agent.js +5 -8
  61. package/lib/mock/mock-client.js +7 -2
  62. package/lib/mock/mock-errors.js +3 -1
  63. package/lib/mock/mock-interceptor.js +8 -6
  64. package/lib/mock/mock-pool.js +7 -2
  65. package/lib/mock/mock-symbols.js +2 -1
  66. package/lib/mock/mock-utils.js +33 -5
  67. package/lib/util/cache.js +360 -0
  68. package/lib/util/timers.js +50 -6
  69. package/lib/web/cache/cache.js +25 -21
  70. package/lib/web/cache/cachestorage.js +3 -1
  71. package/lib/web/cookies/index.js +18 -5
  72. package/lib/web/cookies/parse.js +6 -1
  73. package/lib/web/eventsource/eventsource.js +2 -0
  74. package/lib/web/fetch/body.js +43 -39
  75. package/lib/web/fetch/constants.js +45 -29
  76. package/lib/web/fetch/data-url.js +2 -2
  77. package/lib/web/fetch/formdata-parser.js +84 -46
  78. package/lib/web/fetch/formdata.js +42 -20
  79. package/lib/web/fetch/headers.js +119 -85
  80. package/lib/web/fetch/index.js +69 -65
  81. package/lib/web/fetch/request.js +132 -55
  82. package/lib/web/fetch/response.js +81 -36
  83. package/lib/web/fetch/util.js +274 -103
  84. package/lib/web/fetch/webidl.js +54 -18
  85. package/lib/web/websocket/connection.js +92 -15
  86. package/lib/web/websocket/constants.js +69 -9
  87. package/lib/web/websocket/events.js +8 -2
  88. package/lib/web/websocket/receiver.js +20 -26
  89. package/lib/web/websocket/stream/websocketerror.js +83 -0
  90. package/lib/web/websocket/stream/websocketstream.js +485 -0
  91. package/lib/web/websocket/util.js +115 -10
  92. package/lib/web/websocket/websocket.js +47 -170
  93. package/package.json +15 -11
  94. package/types/agent.d.ts +1 -1
  95. package/types/cache-interceptor.d.ts +172 -0
  96. package/types/cookies.d.ts +2 -0
  97. package/types/dispatcher.d.ts +29 -4
  98. package/types/env-http-proxy-agent.d.ts +1 -1
  99. package/types/fetch.d.ts +9 -8
  100. package/types/handlers.d.ts +4 -4
  101. package/types/index.d.ts +3 -1
  102. package/types/interceptors.d.ts +18 -1
  103. package/types/mock-agent.d.ts +4 -1
  104. package/types/mock-client.d.ts +1 -1
  105. package/types/mock-pool.d.ts +1 -1
  106. package/types/proxy-agent.d.ts +1 -1
  107. package/types/readable.d.ts +10 -7
  108. package/types/retry-handler.d.ts +3 -3
  109. package/types/webidl.d.ts +30 -4
  110. package/types/websocket.d.ts +33 -0
  111. package/lib/mock/pluralizer.js +0 -29
  112. package/lib/web/cache/symbols.js +0 -5
  113. package/lib/web/fetch/symbols.js +0 -8
@@ -0,0 +1,96 @@
1
+ 'use strict'
2
+
3
+ const { parseHeaders } = require('../core/util')
4
+ const { InvalidArgumentError } = require('../core/errors')
5
+
6
+ const kResume = Symbol('resume')
7
+
8
+ class UnwrapController {
9
+ #paused = false
10
+ #reason = null
11
+ #aborted = false
12
+ #abort
13
+
14
+ [kResume] = null
15
+
16
+ constructor (abort) {
17
+ this.#abort = abort
18
+ }
19
+
20
+ pause () {
21
+ this.#paused = true
22
+ }
23
+
24
+ resume () {
25
+ if (this.#paused) {
26
+ this.#paused = false
27
+ this[kResume]?.()
28
+ }
29
+ }
30
+
31
+ abort (reason) {
32
+ if (!this.#aborted) {
33
+ this.#aborted = true
34
+ this.#reason = reason
35
+ this.#abort(reason)
36
+ }
37
+ }
38
+
39
+ get aborted () {
40
+ return this.#aborted
41
+ }
42
+
43
+ get reason () {
44
+ return this.#reason
45
+ }
46
+
47
+ get paused () {
48
+ return this.#paused
49
+ }
50
+ }
51
+
52
+ module.exports = class UnwrapHandler {
53
+ #handler
54
+ #controller
55
+
56
+ constructor (handler) {
57
+ this.#handler = handler
58
+ }
59
+
60
+ static unwrap (handler) {
61
+ // TODO (fix): More checks...
62
+ return !handler.onRequestStart ? handler : new UnwrapHandler(handler)
63
+ }
64
+
65
+ onConnect (abort, context) {
66
+ this.#controller = new UnwrapController(abort)
67
+ this.#handler.onRequestStart?.(this.#controller, context)
68
+ }
69
+
70
+ onUpgrade (statusCode, rawHeaders, socket) {
71
+ this.#handler.onRequestUpgrade?.(this.#controller, statusCode, parseHeaders(rawHeaders), socket)
72
+ }
73
+
74
+ onHeaders (statusCode, rawHeaders, resume, statusMessage) {
75
+ this.#controller[kResume] = resume
76
+ this.#handler.onResponseStart?.(this.#controller, statusCode, parseHeaders(rawHeaders), statusMessage)
77
+ return !this.#controller.paused
78
+ }
79
+
80
+ onData (data) {
81
+ this.#handler.onResponseData?.(this.#controller, data)
82
+ return !this.#controller.paused
83
+ }
84
+
85
+ onComplete (rawTrailers) {
86
+ this.#handler.onResponseEnd?.(this.#controller, parseHeaders(rawTrailers))
87
+ }
88
+
89
+ onError (err) {
90
+ if (!this.#handler.onResponseError) {
91
+ throw new InvalidArgumentError('invalid onError method')
92
+ }
93
+
94
+ this.#handler.onResponseError?.(this.#controller, err)
95
+ }
96
+ }
@@ -0,0 +1,98 @@
1
+ 'use strict'
2
+
3
+ const { InvalidArgumentError } = require('../core/errors')
4
+
5
+ module.exports = class WrapHandler {
6
+ #handler
7
+
8
+ constructor (handler) {
9
+ this.#handler = handler
10
+ }
11
+
12
+ static wrap (handler) {
13
+ // TODO (fix): More checks...
14
+ return handler.onRequestStart ? handler : new WrapHandler(handler)
15
+ }
16
+
17
+ // Unwrap Interface
18
+
19
+ onConnect (abort, context) {
20
+ return this.#handler.onConnect?.(abort, context)
21
+ }
22
+
23
+ onHeaders (statusCode, rawHeaders, resume, statusMessage) {
24
+ return this.#handler.onHeaders?.(statusCode, rawHeaders, resume, statusMessage)
25
+ }
26
+
27
+ onUpgrade (statusCode, rawHeaders, socket) {
28
+ return this.#handler.onUpgrade?.(statusCode, rawHeaders, socket)
29
+ }
30
+
31
+ onData (data) {
32
+ return this.#handler.onData?.(data)
33
+ }
34
+
35
+ onComplete (trailers) {
36
+ return this.#handler.onComplete?.(trailers)
37
+ }
38
+
39
+ onError (err) {
40
+ if (!this.#handler.onError) {
41
+ throw err
42
+ }
43
+
44
+ return this.#handler.onError?.(err)
45
+ }
46
+
47
+ // Wrap Interface
48
+
49
+ onRequestStart (controller, context) {
50
+ this.#handler.onConnect?.((reason) => controller.abort(reason), context)
51
+ }
52
+
53
+ onRequestUpgrade (controller, statusCode, headers, socket) {
54
+ const rawHeaders = []
55
+ for (const [key, val] of Object.entries(headers)) {
56
+ // TODO (fix): What if val is Array
57
+ rawHeaders.push(Buffer.from(key), Buffer.from(val))
58
+ }
59
+
60
+ this.#handler.onUpgrade?.(statusCode, rawHeaders, socket)
61
+ }
62
+
63
+ onResponseStart (controller, statusCode, headers, statusMessage) {
64
+ const rawHeaders = []
65
+ for (const [key, val] of Object.entries(headers)) {
66
+ // TODO (fix): What if val is Array
67
+ rawHeaders.push(Buffer.from(key), Buffer.from(val))
68
+ }
69
+
70
+ if (this.#handler.onHeaders?.(statusCode, rawHeaders, () => controller.resume(), statusMessage) === false) {
71
+ controller.pause()
72
+ }
73
+ }
74
+
75
+ onResponseData (controller, data) {
76
+ if (this.#handler.onData?.(data) === false) {
77
+ controller.pause()
78
+ }
79
+ }
80
+
81
+ onResponseEnd (controller, trailers) {
82
+ const rawTrailers = []
83
+ for (const [key, val] of Object.entries(trailers)) {
84
+ // TODO (fix): What if val is Array
85
+ rawTrailers.push(Buffer.from(key), Buffer.from(val))
86
+ }
87
+
88
+ this.#handler.onComplete?.(rawTrailers)
89
+ }
90
+
91
+ onResponseError (controller, err) {
92
+ if (!this.#handler.onError) {
93
+ throw new InvalidArgumentError('invalid onError method')
94
+ }
95
+
96
+ this.#handler.onError?.(err)
97
+ }
98
+ }
@@ -0,0 +1,350 @@
1
+ 'use strict'
2
+
3
+ const assert = require('node:assert')
4
+ const { Readable } = require('node:stream')
5
+ const util = require('../core/util')
6
+ const CacheHandler = require('../handler/cache-handler')
7
+ const MemoryCacheStore = require('../cache/memory-cache-store')
8
+ const CacheRevalidationHandler = require('../handler/cache-revalidation-handler')
9
+ const { assertCacheStore, assertCacheMethods, makeCacheKey, parseCacheControlHeader } = require('../util/cache.js')
10
+ const { AbortError } = require('../core/errors.js')
11
+
12
+ /**
13
+ * @typedef {(options: import('../../types/dispatcher.d.ts').default.DispatchOptions, handler: import('../../types/dispatcher.d.ts').default.DispatchHandler) => void} DispatchFn
14
+ */
15
+
16
+ /**
17
+ * @param {import('../../types/cache-interceptor.d.ts').default.GetResult} result
18
+ * @param {import('../../types/cache-interceptor.d.ts').default.CacheControlDirectives | undefined} cacheControlDirectives
19
+ * @returns {boolean}
20
+ */
21
+ function needsRevalidation (result, cacheControlDirectives) {
22
+ if (cacheControlDirectives?.['no-cache']) {
23
+ // Always revalidate requests with the no-cache directive
24
+ return true
25
+ }
26
+
27
+ const now = Date.now()
28
+ if (now > result.staleAt) {
29
+ // Response is stale
30
+ if (cacheControlDirectives?.['max-stale']) {
31
+ // There's a threshold where we can serve stale responses, let's see if
32
+ // we're in it
33
+ // https://www.rfc-editor.org/rfc/rfc9111.html#name-max-stale
34
+ const gracePeriod = result.staleAt + (cacheControlDirectives['max-stale'] * 1000)
35
+ return now > gracePeriod
36
+ }
37
+
38
+ return true
39
+ }
40
+
41
+ if (cacheControlDirectives?.['min-fresh']) {
42
+ // https://www.rfc-editor.org/rfc/rfc9111.html#section-5.2.1.3
43
+
44
+ // At this point, staleAt is always > now
45
+ const timeLeftTillStale = result.staleAt - now
46
+ const threshold = cacheControlDirectives['min-fresh'] * 1000
47
+
48
+ return timeLeftTillStale <= threshold
49
+ }
50
+
51
+ return false
52
+ }
53
+
54
+ /**
55
+ * @param {DispatchFn} dispatch
56
+ * @param {import('../../types/cache-interceptor.d.ts').default.CacheHandlerOptions} globalOpts
57
+ * @param {import('../../types/cache-interceptor.d.ts').default.CacheKey} cacheKey
58
+ * @param {import('../../types/dispatcher.d.ts').default.DispatchHandler} handler
59
+ * @param {import('../../types/dispatcher.d.ts').default.RequestOptions} opts
60
+ * @param {import('../../types/cache-interceptor.d.ts').default.CacheControlDirectives | undefined} reqCacheControl
61
+ */
62
+ function handleUncachedResponse (
63
+ dispatch,
64
+ globalOpts,
65
+ cacheKey,
66
+ handler,
67
+ opts,
68
+ reqCacheControl
69
+ ) {
70
+ if (reqCacheControl?.['only-if-cached']) {
71
+ let aborted = false
72
+ try {
73
+ if (typeof handler.onConnect === 'function') {
74
+ handler.onConnect(() => {
75
+ aborted = true
76
+ })
77
+
78
+ if (aborted) {
79
+ return
80
+ }
81
+ }
82
+
83
+ if (typeof handler.onHeaders === 'function') {
84
+ handler.onHeaders(504, [], () => {}, 'Gateway Timeout')
85
+ if (aborted) {
86
+ return
87
+ }
88
+ }
89
+
90
+ if (typeof handler.onComplete === 'function') {
91
+ handler.onComplete([])
92
+ }
93
+ } catch (err) {
94
+ if (typeof handler.onError === 'function') {
95
+ handler.onError(err)
96
+ }
97
+ }
98
+
99
+ return true
100
+ }
101
+
102
+ return dispatch(opts, new CacheHandler(globalOpts, cacheKey, handler))
103
+ }
104
+
105
+ /**
106
+ * @param {import('../../types/cache-interceptor.d.ts').default.GetResult} result
107
+ * @param {number} age
108
+ */
109
+ function sendCachedValue (handler, opts, result, age, context) {
110
+ // TODO (perf): Readable.from path can be optimized...
111
+ const stream = util.isStream(result.body)
112
+ ? result.body
113
+ : Readable.from(result.body ?? [])
114
+
115
+ assert(!stream.destroyed, 'stream should not be destroyed')
116
+ assert(!stream.readableDidRead, 'stream should not be readableDidRead')
117
+
118
+ const controller = {
119
+ resume () {
120
+ stream.resume()
121
+ },
122
+ pause () {
123
+ stream.pause()
124
+ },
125
+ get paused () {
126
+ return stream.isPaused()
127
+ },
128
+ get aborted () {
129
+ return stream.destroyed
130
+ },
131
+ get reason () {
132
+ return stream.errored
133
+ },
134
+ abort (reason) {
135
+ stream.destroy(reason ?? new AbortError())
136
+ }
137
+ }
138
+
139
+ stream
140
+ .on('error', function (err) {
141
+ if (!this.readableEnded) {
142
+ if (typeof handler.onResponseError === 'function') {
143
+ handler.onResponseError(controller, err)
144
+ } else {
145
+ throw err
146
+ }
147
+ }
148
+ })
149
+ .on('close', function () {
150
+ if (!this.errored) {
151
+ handler.onResponseEnd?.(controller, {})
152
+ }
153
+ })
154
+
155
+ handler.onRequestStart?.(controller, context)
156
+
157
+ if (stream.destroyed) {
158
+ return
159
+ }
160
+
161
+ // Add the age header
162
+ // https://www.rfc-editor.org/rfc/rfc9111.html#name-age
163
+ // TODO (fix): What if headers.age already exists?
164
+ const headers = age != null ? { ...result.headers, age: String(age) } : result.headers
165
+
166
+ handler.onResponseStart?.(controller, result.statusCode, headers, result.statusMessage)
167
+
168
+ if (opts.method === 'HEAD') {
169
+ stream.destroy()
170
+ } else {
171
+ stream.on('data', function (chunk) {
172
+ handler.onResponseData?.(controller, chunk)
173
+ })
174
+ }
175
+ }
176
+
177
+ /**
178
+ * @param {DispatchFn} dispatch
179
+ * @param {import('../../types/cache-interceptor.d.ts').default.CacheHandlerOptions} globalOpts
180
+ * @param {import('../../types/cache-interceptor.d.ts').default.CacheKey} cacheKey
181
+ * @param {import('../../types/dispatcher.d.ts').default.DispatchHandler} handler
182
+ * @param {import('../../types/dispatcher.d.ts').default.RequestOptions} opts
183
+ * @param {import('../../types/cache-interceptor.d.ts').default.CacheControlDirectives | undefined} reqCacheControl
184
+ * @param {import('../../types/cache-interceptor.d.ts').default.GetResult | undefined} result
185
+ */
186
+ function handleResult (
187
+ dispatch,
188
+ globalOpts,
189
+ cacheKey,
190
+ handler,
191
+ opts,
192
+ reqCacheControl,
193
+ result
194
+ ) {
195
+ if (!result) {
196
+ return handleUncachedResponse(dispatch, globalOpts, cacheKey, handler, opts, reqCacheControl)
197
+ }
198
+
199
+ const now = Date.now()
200
+ if (now > result.deleteAt) {
201
+ // Response is expired, cache store shouldn't have given this to us
202
+ return dispatch(opts, new CacheHandler(globalOpts, cacheKey, handler))
203
+ }
204
+
205
+ const age = Math.round((now - result.cachedAt) / 1000)
206
+ if (reqCacheControl?.['max-age'] && age >= reqCacheControl['max-age']) {
207
+ // Response is considered expired for this specific request
208
+ // https://www.rfc-editor.org/rfc/rfc9111.html#section-5.2.1.1
209
+ return dispatch(opts, handler)
210
+ }
211
+
212
+ // Check if the response is stale
213
+ if (needsRevalidation(result, reqCacheControl)) {
214
+ if (util.isStream(opts.body) && util.bodyLength(opts.body) !== 0) {
215
+ // If body is is stream we can't revalidate...
216
+ // TODO (fix): This could be less strict...
217
+ return dispatch(opts, new CacheHandler(globalOpts, cacheKey, handler))
218
+ }
219
+
220
+ let withinStaleIfErrorThreshold = false
221
+ const staleIfErrorExpiry = result.cacheControlDirectives['stale-if-error'] ?? reqCacheControl?.['stale-if-error']
222
+ if (staleIfErrorExpiry) {
223
+ withinStaleIfErrorThreshold = now < (result.staleAt + (staleIfErrorExpiry * 1000))
224
+ }
225
+
226
+ let headers = {
227
+ ...opts.headers,
228
+ 'if-modified-since': new Date(result.cachedAt).toUTCString(),
229
+ 'if-none-match': result.etag
230
+ }
231
+
232
+ if (result.vary) {
233
+ headers = {
234
+ ...headers,
235
+ ...result.vary
236
+ }
237
+ }
238
+
239
+ // We need to revalidate the response
240
+ return dispatch(
241
+ {
242
+ ...opts,
243
+ headers
244
+ },
245
+ new CacheRevalidationHandler(
246
+ (success, context) => {
247
+ if (success) {
248
+ sendCachedValue(handler, opts, result, age, context)
249
+ } else if (util.isStream(result.body)) {
250
+ result.body.on('error', () => {}).destroy()
251
+ }
252
+ },
253
+ new CacheHandler(globalOpts, cacheKey, handler),
254
+ withinStaleIfErrorThreshold
255
+ )
256
+ )
257
+ }
258
+
259
+ // Dump request body.
260
+ if (util.isStream(opts.body)) {
261
+ opts.body.on('error', () => {}).destroy()
262
+ }
263
+
264
+ sendCachedValue(handler, opts, result, age, null)
265
+ }
266
+
267
+ /**
268
+ * @param {import('../../types/cache-interceptor.d.ts').default.CacheOptions} [opts]
269
+ * @returns {import('../../types/dispatcher.d.ts').default.DispatcherComposeInterceptor}
270
+ */
271
+ module.exports = (opts = {}) => {
272
+ const {
273
+ store = new MemoryCacheStore(),
274
+ methods = ['GET'],
275
+ cacheByDefault = undefined,
276
+ type = 'shared'
277
+ } = opts
278
+
279
+ if (typeof opts !== 'object' || opts === null) {
280
+ throw new TypeError(`expected type of opts to be an Object, got ${opts === null ? 'null' : typeof opts}`)
281
+ }
282
+
283
+ assertCacheStore(store, 'opts.store')
284
+ assertCacheMethods(methods, 'opts.methods')
285
+
286
+ if (typeof cacheByDefault !== 'undefined' && typeof cacheByDefault !== 'number') {
287
+ throw new TypeError(`exepcted opts.cacheByDefault to be number or undefined, got ${typeof cacheByDefault}`)
288
+ }
289
+
290
+ if (typeof type !== 'undefined' && type !== 'shared' && type !== 'private') {
291
+ throw new TypeError(`exepcted opts.type to be shared, private, or undefined, got ${typeof type}`)
292
+ }
293
+
294
+ const globalOpts = {
295
+ store,
296
+ methods,
297
+ cacheByDefault,
298
+ type
299
+ }
300
+
301
+ const safeMethodsToNotCache = util.safeHTTPMethods.filter(method => methods.includes(method) === false)
302
+
303
+ return dispatch => {
304
+ return (opts, handler) => {
305
+ if (!opts.origin || safeMethodsToNotCache.includes(opts.method)) {
306
+ // Not a method we want to cache or we don't have the origin, skip
307
+ return dispatch(opts, handler)
308
+ }
309
+
310
+ const reqCacheControl = opts.headers?.['cache-control']
311
+ ? parseCacheControlHeader(opts.headers['cache-control'])
312
+ : undefined
313
+
314
+ if (reqCacheControl?.['no-store']) {
315
+ return dispatch(opts, handler)
316
+ }
317
+
318
+ /**
319
+ * @type {import('../../types/cache-interceptor.d.ts').default.CacheKey}
320
+ */
321
+ const cacheKey = makeCacheKey(opts)
322
+ const result = store.get(cacheKey)
323
+
324
+ if (result && typeof result.then === 'function') {
325
+ result.then(result => {
326
+ handleResult(dispatch,
327
+ globalOpts,
328
+ cacheKey,
329
+ handler,
330
+ opts,
331
+ reqCacheControl,
332
+ result
333
+ )
334
+ })
335
+ } else {
336
+ handleResult(
337
+ dispatch,
338
+ globalOpts,
339
+ cacheKey,
340
+ handler,
341
+ opts,
342
+ reqCacheControl,
343
+ result
344
+ )
345
+ }
346
+
347
+ return true
348
+ }
349
+ }
350
+ }