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.
- package/README.md +24 -38
- package/docs/docs/api/Agent.md +14 -14
- package/docs/docs/api/BalancedPool.md +16 -16
- package/docs/docs/api/CacheStore.md +131 -0
- package/docs/docs/api/Client.md +12 -12
- package/docs/docs/api/Debug.md +1 -1
- package/docs/docs/api/Dispatcher.md +98 -193
- package/docs/docs/api/EnvHttpProxyAgent.md +12 -12
- package/docs/docs/api/MockAgent.md +5 -3
- package/docs/docs/api/MockClient.md +5 -5
- package/docs/docs/api/MockPool.md +4 -3
- package/docs/docs/api/Pool.md +15 -15
- package/docs/docs/api/PoolStats.md +1 -1
- package/docs/docs/api/ProxyAgent.md +3 -3
- package/docs/docs/api/RedirectHandler.md +1 -1
- package/docs/docs/api/RetryAgent.md +1 -1
- package/docs/docs/api/RetryHandler.md +4 -4
- package/docs/docs/api/WebSocket.md +46 -4
- package/docs/docs/api/api-lifecycle.md +11 -11
- package/docs/docs/best-practices/mocking-request.md +2 -2
- package/docs/docs/best-practices/proxy.md +1 -1
- package/index.d.ts +1 -1
- package/index.js +23 -3
- package/lib/api/abort-signal.js +2 -0
- package/lib/api/api-pipeline.js +4 -2
- package/lib/api/api-request.js +6 -4
- package/lib/api/api-stream.js +3 -1
- package/lib/api/api-upgrade.js +2 -2
- package/lib/api/readable.js +200 -47
- package/lib/api/util.js +2 -0
- package/lib/cache/memory-cache-store.js +177 -0
- package/lib/cache/sqlite-cache-store.js +446 -0
- package/lib/core/connect.js +54 -22
- package/lib/core/constants.js +35 -10
- package/lib/core/diagnostics.js +122 -128
- package/lib/core/errors.js +2 -2
- package/lib/core/request.js +6 -6
- package/lib/core/symbols.js +2 -0
- package/lib/core/tree.js +4 -2
- package/lib/core/util.js +238 -40
- package/lib/dispatcher/client-h1.js +405 -142
- package/lib/dispatcher/client-h2.js +212 -109
- package/lib/dispatcher/client.js +24 -7
- package/lib/dispatcher/dispatcher-base.js +4 -1
- package/lib/dispatcher/dispatcher.js +4 -0
- package/lib/dispatcher/fixed-queue.js +91 -49
- package/lib/dispatcher/pool-base.js +3 -3
- package/lib/dispatcher/pool-stats.js +2 -0
- package/lib/dispatcher/proxy-agent.js +3 -1
- package/lib/handler/cache-handler.js +393 -0
- package/lib/handler/cache-revalidation-handler.js +124 -0
- package/lib/handler/decorator-handler.js +3 -0
- package/lib/handler/redirect-handler.js +45 -59
- package/lib/handler/retry-handler.js +68 -109
- package/lib/handler/unwrap-handler.js +96 -0
- package/lib/handler/wrap-handler.js +98 -0
- package/lib/interceptor/cache.js +350 -0
- package/lib/interceptor/dns.js +375 -0
- package/lib/interceptor/response-error.js +15 -7
- package/lib/mock/mock-agent.js +5 -8
- package/lib/mock/mock-client.js +7 -2
- package/lib/mock/mock-errors.js +3 -1
- package/lib/mock/mock-interceptor.js +8 -6
- package/lib/mock/mock-pool.js +7 -2
- package/lib/mock/mock-symbols.js +2 -1
- package/lib/mock/mock-utils.js +33 -5
- package/lib/util/cache.js +360 -0
- package/lib/util/timers.js +50 -6
- package/lib/web/cache/cache.js +25 -21
- package/lib/web/cache/cachestorage.js +3 -1
- package/lib/web/cookies/index.js +18 -5
- package/lib/web/cookies/parse.js +6 -1
- package/lib/web/eventsource/eventsource.js +2 -0
- package/lib/web/fetch/body.js +43 -39
- package/lib/web/fetch/constants.js +45 -29
- package/lib/web/fetch/data-url.js +2 -2
- package/lib/web/fetch/formdata-parser.js +84 -46
- package/lib/web/fetch/formdata.js +42 -20
- package/lib/web/fetch/headers.js +119 -85
- package/lib/web/fetch/index.js +69 -65
- package/lib/web/fetch/request.js +132 -55
- package/lib/web/fetch/response.js +81 -36
- package/lib/web/fetch/util.js +274 -103
- package/lib/web/fetch/webidl.js +54 -18
- package/lib/web/websocket/connection.js +92 -15
- package/lib/web/websocket/constants.js +69 -9
- package/lib/web/websocket/events.js +8 -2
- package/lib/web/websocket/receiver.js +20 -26
- package/lib/web/websocket/stream/websocketerror.js +83 -0
- package/lib/web/websocket/stream/websocketstream.js +485 -0
- package/lib/web/websocket/util.js +115 -10
- package/lib/web/websocket/websocket.js +47 -170
- package/package.json +15 -11
- package/types/agent.d.ts +1 -1
- package/types/cache-interceptor.d.ts +172 -0
- package/types/cookies.d.ts +2 -0
- package/types/dispatcher.d.ts +29 -4
- package/types/env-http-proxy-agent.d.ts +1 -1
- package/types/fetch.d.ts +9 -8
- package/types/handlers.d.ts +4 -4
- package/types/index.d.ts +3 -1
- package/types/interceptors.d.ts +18 -1
- package/types/mock-agent.d.ts +4 -1
- package/types/mock-client.d.ts +1 -1
- package/types/mock-pool.d.ts +1 -1
- package/types/proxy-agent.d.ts +1 -1
- package/types/readable.d.ts +10 -7
- package/types/retry-handler.d.ts +3 -3
- package/types/webidl.d.ts +30 -4
- package/types/websocket.d.ts +33 -0
- package/lib/mock/pluralizer.js +0 -29
- package/lib/web/cache/symbols.js +0 -5
- package/lib/web/fetch/symbols.js +0 -8
package/lib/api/readable.js
CHANGED
|
@@ -19,7 +19,20 @@ const kBytesRead = Symbol('kBytesRead')
|
|
|
19
19
|
|
|
20
20
|
const noop = () => {}
|
|
21
21
|
|
|
22
|
+
/**
|
|
23
|
+
* @class
|
|
24
|
+
* @extends {Readable}
|
|
25
|
+
* @see https://fetch.spec.whatwg.org/#body
|
|
26
|
+
*/
|
|
22
27
|
class BodyReadable extends Readable {
|
|
28
|
+
/**
|
|
29
|
+
* @param {object} opts
|
|
30
|
+
* @param {(this: Readable, size: number) => void} opts.resume
|
|
31
|
+
* @param {() => (void | null)} opts.abort
|
|
32
|
+
* @param {string} [opts.contentType = '']
|
|
33
|
+
* @param {number} [opts.contentLength]
|
|
34
|
+
* @param {number} [opts.highWaterMark = 64 * 1024]
|
|
35
|
+
*/
|
|
23
36
|
constructor ({
|
|
24
37
|
resume,
|
|
25
38
|
abort,
|
|
@@ -36,8 +49,15 @@ class BodyReadable extends Readable {
|
|
|
36
49
|
this._readableState.dataEmitted = false
|
|
37
50
|
|
|
38
51
|
this[kAbort] = abort
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* @type {Consume | null}
|
|
55
|
+
*/
|
|
39
56
|
this[kConsume] = null
|
|
40
57
|
this[kBytesRead] = 0
|
|
58
|
+
/**
|
|
59
|
+
* @type {ReadableStream|null}
|
|
60
|
+
*/
|
|
41
61
|
this[kBody] = null
|
|
42
62
|
this[kUsed] = false
|
|
43
63
|
this[kContentType] = contentType
|
|
@@ -50,6 +70,11 @@ class BodyReadable extends Readable {
|
|
|
50
70
|
this[kReading] = false
|
|
51
71
|
}
|
|
52
72
|
|
|
73
|
+
/**
|
|
74
|
+
* @param {Error|null} err
|
|
75
|
+
* @param {(error:(Error|null)) => void} callback
|
|
76
|
+
* @returns {void}
|
|
77
|
+
*/
|
|
53
78
|
_destroy (err, callback) {
|
|
54
79
|
if (!err && !this._readableState.endEmitted) {
|
|
55
80
|
err = new RequestAbortedError()
|
|
@@ -61,7 +86,7 @@ class BodyReadable extends Readable {
|
|
|
61
86
|
|
|
62
87
|
// Workaround for Node "bug". If the stream is destroyed in same
|
|
63
88
|
// tick as it is created, then a user who is waiting for a
|
|
64
|
-
// promise (i.e micro tick) for installing
|
|
89
|
+
// promise (i.e micro tick) for installing an 'error' listener will
|
|
65
90
|
// never get a chance and will always encounter an unhandled exception.
|
|
66
91
|
if (!this[kUsed]) {
|
|
67
92
|
setImmediate(() => {
|
|
@@ -72,21 +97,36 @@ class BodyReadable extends Readable {
|
|
|
72
97
|
}
|
|
73
98
|
}
|
|
74
99
|
|
|
75
|
-
|
|
76
|
-
|
|
100
|
+
/**
|
|
101
|
+
* @param {string} event
|
|
102
|
+
* @param {(...args: any[]) => void} listener
|
|
103
|
+
* @returns {this}
|
|
104
|
+
*/
|
|
105
|
+
on (event, listener) {
|
|
106
|
+
if (event === 'data' || event === 'readable') {
|
|
77
107
|
this[kReading] = true
|
|
78
108
|
this[kUsed] = true
|
|
79
109
|
}
|
|
80
|
-
return super.on(
|
|
110
|
+
return super.on(event, listener)
|
|
81
111
|
}
|
|
82
112
|
|
|
83
|
-
|
|
84
|
-
|
|
113
|
+
/**
|
|
114
|
+
* @param {string} event
|
|
115
|
+
* @param {(...args: any[]) => void} listener
|
|
116
|
+
* @returns {this}
|
|
117
|
+
*/
|
|
118
|
+
addListener (event, listener) {
|
|
119
|
+
return this.on(event, listener)
|
|
85
120
|
}
|
|
86
121
|
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
122
|
+
/**
|
|
123
|
+
* @param {string|symbol} event
|
|
124
|
+
* @param {(...args: any[]) => void} listener
|
|
125
|
+
* @returns {this}
|
|
126
|
+
*/
|
|
127
|
+
off (event, listener) {
|
|
128
|
+
const ret = super.off(event, listener)
|
|
129
|
+
if (event === 'data' || event === 'readable') {
|
|
90
130
|
this[kReading] = (
|
|
91
131
|
this.listenerCount('data') > 0 ||
|
|
92
132
|
this.listenerCount('readable') > 0
|
|
@@ -95,10 +135,19 @@ class BodyReadable extends Readable {
|
|
|
95
135
|
return ret
|
|
96
136
|
}
|
|
97
137
|
|
|
98
|
-
|
|
99
|
-
|
|
138
|
+
/**
|
|
139
|
+
* @param {string|symbol} event
|
|
140
|
+
* @param {(...args: any[]) => void} listener
|
|
141
|
+
* @returns {this}
|
|
142
|
+
*/
|
|
143
|
+
removeListener (event, listener) {
|
|
144
|
+
return this.off(event, listener)
|
|
100
145
|
}
|
|
101
146
|
|
|
147
|
+
/**
|
|
148
|
+
* @param {Buffer|null} chunk
|
|
149
|
+
* @returns {boolean}
|
|
150
|
+
*/
|
|
102
151
|
push (chunk) {
|
|
103
152
|
this[kBytesRead] += chunk ? chunk.length : 0
|
|
104
153
|
|
|
@@ -109,43 +158,84 @@ class BodyReadable extends Readable {
|
|
|
109
158
|
return super.push(chunk)
|
|
110
159
|
}
|
|
111
160
|
|
|
112
|
-
|
|
113
|
-
|
|
161
|
+
/**
|
|
162
|
+
* Consumes and returns the body as a string.
|
|
163
|
+
*
|
|
164
|
+
* @see https://fetch.spec.whatwg.org/#dom-body-text
|
|
165
|
+
* @returns {Promise<string>}
|
|
166
|
+
*/
|
|
167
|
+
text () {
|
|
114
168
|
return consume(this, 'text')
|
|
115
169
|
}
|
|
116
170
|
|
|
117
|
-
|
|
118
|
-
|
|
171
|
+
/**
|
|
172
|
+
* Consumes and returns the body as a JavaScript Object.
|
|
173
|
+
*
|
|
174
|
+
* @see https://fetch.spec.whatwg.org/#dom-body-json
|
|
175
|
+
* @returns {Promise<unknown>}
|
|
176
|
+
*/
|
|
177
|
+
json () {
|
|
119
178
|
return consume(this, 'json')
|
|
120
179
|
}
|
|
121
180
|
|
|
122
|
-
|
|
123
|
-
|
|
181
|
+
/**
|
|
182
|
+
* Consumes and returns the body as a Blob
|
|
183
|
+
*
|
|
184
|
+
* @see https://fetch.spec.whatwg.org/#dom-body-blob
|
|
185
|
+
* @returns {Promise<Blob>}
|
|
186
|
+
*/
|
|
187
|
+
blob () {
|
|
124
188
|
return consume(this, 'blob')
|
|
125
189
|
}
|
|
126
190
|
|
|
127
|
-
|
|
128
|
-
|
|
191
|
+
/**
|
|
192
|
+
* Consumes and returns the body as an Uint8Array.
|
|
193
|
+
*
|
|
194
|
+
* @see https://fetch.spec.whatwg.org/#dom-body-bytes
|
|
195
|
+
* @returns {Promise<Uint8Array>}
|
|
196
|
+
*/
|
|
197
|
+
bytes () {
|
|
129
198
|
return consume(this, 'bytes')
|
|
130
199
|
}
|
|
131
200
|
|
|
132
|
-
|
|
133
|
-
|
|
201
|
+
/**
|
|
202
|
+
* Consumes and returns the body as an ArrayBuffer.
|
|
203
|
+
*
|
|
204
|
+
* @see https://fetch.spec.whatwg.org/#dom-body-arraybuffer
|
|
205
|
+
* @returns {Promise<ArrayBuffer>}
|
|
206
|
+
*/
|
|
207
|
+
arrayBuffer () {
|
|
134
208
|
return consume(this, 'arrayBuffer')
|
|
135
209
|
}
|
|
136
210
|
|
|
137
|
-
|
|
211
|
+
/**
|
|
212
|
+
* Not implemented
|
|
213
|
+
*
|
|
214
|
+
* @see https://fetch.spec.whatwg.org/#dom-body-formdata
|
|
215
|
+
* @throws {NotSupportedError}
|
|
216
|
+
*/
|
|
138
217
|
async formData () {
|
|
139
218
|
// TODO: Implement.
|
|
140
219
|
throw new NotSupportedError()
|
|
141
220
|
}
|
|
142
221
|
|
|
143
|
-
|
|
222
|
+
/**
|
|
223
|
+
* Returns true if the body is not null and the body has been consumed.
|
|
224
|
+
* Otherwise, returns false.
|
|
225
|
+
*
|
|
226
|
+
* @see https://fetch.spec.whatwg.org/#dom-body-bodyused
|
|
227
|
+
* @readonly
|
|
228
|
+
* @returns {boolean}
|
|
229
|
+
*/
|
|
144
230
|
get bodyUsed () {
|
|
145
231
|
return util.isDisturbed(this)
|
|
146
232
|
}
|
|
147
233
|
|
|
148
|
-
|
|
234
|
+
/**
|
|
235
|
+
* @see https://fetch.spec.whatwg.org/#dom-body-body
|
|
236
|
+
* @readonly
|
|
237
|
+
* @returns {ReadableStream}
|
|
238
|
+
*/
|
|
149
239
|
get body () {
|
|
150
240
|
if (!this[kBody]) {
|
|
151
241
|
this[kBody] = ReadableStreamFrom(this)
|
|
@@ -158,6 +248,13 @@ class BodyReadable extends Readable {
|
|
|
158
248
|
return this[kBody]
|
|
159
249
|
}
|
|
160
250
|
|
|
251
|
+
/**
|
|
252
|
+
* Dumps the response body by reading `limit` number of bytes.
|
|
253
|
+
* @param {object} opts
|
|
254
|
+
* @param {number} [opts.limit = 131072] Number of bytes to read.
|
|
255
|
+
* @param {AbortSignal} [opts.signal] An AbortSignal to cancel the dump.
|
|
256
|
+
* @returns {Promise<null>}
|
|
257
|
+
*/
|
|
161
258
|
async dump (opts) {
|
|
162
259
|
const signal = opts?.signal
|
|
163
260
|
|
|
@@ -165,7 +262,9 @@ class BodyReadable extends Readable {
|
|
|
165
262
|
throw new InvalidArgumentError('signal must be an AbortSignal')
|
|
166
263
|
}
|
|
167
264
|
|
|
168
|
-
const limit =
|
|
265
|
+
const limit = opts?.limit && Number.isFinite(opts.limit)
|
|
266
|
+
? opts.limit
|
|
267
|
+
: 128 * 1024
|
|
169
268
|
|
|
170
269
|
signal?.throwIfAborted()
|
|
171
270
|
|
|
@@ -174,26 +273,34 @@ class BodyReadable extends Readable {
|
|
|
174
273
|
}
|
|
175
274
|
|
|
176
275
|
return await new Promise((resolve, reject) => {
|
|
177
|
-
if (
|
|
276
|
+
if (
|
|
277
|
+
(this[kContentLength] && (this[kContentLength] > limit)) ||
|
|
278
|
+
this[kBytesRead] > limit
|
|
279
|
+
) {
|
|
178
280
|
this.destroy(new AbortError())
|
|
179
281
|
}
|
|
180
282
|
|
|
181
|
-
|
|
182
|
-
|
|
283
|
+
if (signal) {
|
|
284
|
+
const onAbort = () => {
|
|
285
|
+
this.destroy(signal.reason ?? new AbortError())
|
|
286
|
+
}
|
|
287
|
+
signal.addEventListener('abort', onAbort)
|
|
288
|
+
this
|
|
289
|
+
.on('close', function () {
|
|
290
|
+
signal.removeEventListener('abort', onAbort)
|
|
291
|
+
if (signal.aborted) {
|
|
292
|
+
reject(signal.reason ?? new AbortError())
|
|
293
|
+
} else {
|
|
294
|
+
resolve(null)
|
|
295
|
+
}
|
|
296
|
+
})
|
|
297
|
+
} else {
|
|
298
|
+
this.on('close', resolve)
|
|
183
299
|
}
|
|
184
|
-
signal?.addEventListener('abort', onAbort)
|
|
185
300
|
|
|
186
301
|
this
|
|
187
|
-
.on('close', function () {
|
|
188
|
-
signal?.removeEventListener('abort', onAbort)
|
|
189
|
-
if (signal?.aborted) {
|
|
190
|
-
reject(signal.reason ?? new AbortError())
|
|
191
|
-
} else {
|
|
192
|
-
resolve(null)
|
|
193
|
-
}
|
|
194
|
-
})
|
|
195
302
|
.on('error', noop)
|
|
196
|
-
.on('data',
|
|
303
|
+
.on('data', () => {
|
|
197
304
|
if (this[kBytesRead] > limit) {
|
|
198
305
|
this.destroy()
|
|
199
306
|
}
|
|
@@ -204,7 +311,7 @@ class BodyReadable extends Readable {
|
|
|
204
311
|
|
|
205
312
|
/**
|
|
206
313
|
* @param {BufferEncoding} encoding
|
|
207
|
-
* @returns {
|
|
314
|
+
* @returns {this}
|
|
208
315
|
*/
|
|
209
316
|
setEncoding (encoding) {
|
|
210
317
|
if (Buffer.isEncoding(encoding)) {
|
|
@@ -214,18 +321,41 @@ class BodyReadable extends Readable {
|
|
|
214
321
|
}
|
|
215
322
|
}
|
|
216
323
|
|
|
217
|
-
|
|
218
|
-
|
|
324
|
+
/**
|
|
325
|
+
* @see https://streams.spec.whatwg.org/#readablestream-locked
|
|
326
|
+
* @param {BodyReadable} bodyReadable
|
|
327
|
+
* @returns {boolean}
|
|
328
|
+
*/
|
|
329
|
+
function isLocked (bodyReadable) {
|
|
219
330
|
// Consume is an implicit lock.
|
|
220
|
-
return
|
|
331
|
+
return bodyReadable[kBody]?.locked === true || bodyReadable[kConsume] !== null
|
|
221
332
|
}
|
|
222
333
|
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
334
|
+
/**
|
|
335
|
+
* @see https://fetch.spec.whatwg.org/#body-unusable
|
|
336
|
+
* @param {BodyReadable} bodyReadable
|
|
337
|
+
* @returns {boolean}
|
|
338
|
+
*/
|
|
339
|
+
function isUnusable (bodyReadable) {
|
|
340
|
+
return util.isDisturbed(bodyReadable) || isLocked(bodyReadable)
|
|
226
341
|
}
|
|
227
342
|
|
|
228
|
-
|
|
343
|
+
/**
|
|
344
|
+
* @typedef {object} Consume
|
|
345
|
+
* @property {string} type
|
|
346
|
+
* @property {BodyReadable} stream
|
|
347
|
+
* @property {((value?: any) => void)} resolve
|
|
348
|
+
* @property {((err: Error) => void)} reject
|
|
349
|
+
* @property {number} length
|
|
350
|
+
* @property {Buffer[]} body
|
|
351
|
+
*/
|
|
352
|
+
|
|
353
|
+
/**
|
|
354
|
+
* @param {BodyReadable} stream
|
|
355
|
+
* @param {string} type
|
|
356
|
+
* @returns {Promise<any>}
|
|
357
|
+
*/
|
|
358
|
+
function consume (stream, type) {
|
|
229
359
|
assert(!stream[kConsume])
|
|
230
360
|
|
|
231
361
|
return new Promise((resolve, reject) => {
|
|
@@ -269,6 +399,10 @@ async function consume (stream, type) {
|
|
|
269
399
|
})
|
|
270
400
|
}
|
|
271
401
|
|
|
402
|
+
/**
|
|
403
|
+
* @param {Consume} consume
|
|
404
|
+
* @returns {void}
|
|
405
|
+
*/
|
|
272
406
|
function consumeStart (consume) {
|
|
273
407
|
if (consume.body === null) {
|
|
274
408
|
return
|
|
@@ -356,6 +490,11 @@ function chunksConcat (chunks, length) {
|
|
|
356
490
|
return buffer
|
|
357
491
|
}
|
|
358
492
|
|
|
493
|
+
/**
|
|
494
|
+
* @param {Consume} consume
|
|
495
|
+
* @param {BufferEncoding} encoding
|
|
496
|
+
* @returns {void}
|
|
497
|
+
*/
|
|
359
498
|
function consumeEnd (consume, encoding) {
|
|
360
499
|
const { type, body, resolve, stream, length } = consume
|
|
361
500
|
|
|
@@ -378,11 +517,21 @@ function consumeEnd (consume, encoding) {
|
|
|
378
517
|
}
|
|
379
518
|
}
|
|
380
519
|
|
|
520
|
+
/**
|
|
521
|
+
* @param {Consume} consume
|
|
522
|
+
* @param {Buffer} chunk
|
|
523
|
+
* @returns {void}
|
|
524
|
+
*/
|
|
381
525
|
function consumePush (consume, chunk) {
|
|
382
526
|
consume.length += chunk.length
|
|
383
527
|
consume.body.push(chunk)
|
|
384
528
|
}
|
|
385
529
|
|
|
530
|
+
/**
|
|
531
|
+
* @param {Consume} consume
|
|
532
|
+
* @param {Error} [err]
|
|
533
|
+
* @returns {void}
|
|
534
|
+
*/
|
|
386
535
|
function consumeFinish (consume, err) {
|
|
387
536
|
if (consume.body === null) {
|
|
388
537
|
return
|
|
@@ -394,6 +543,7 @@ function consumeFinish (consume, err) {
|
|
|
394
543
|
consume.resolve()
|
|
395
544
|
}
|
|
396
545
|
|
|
546
|
+
// Reset the consume object to allow for garbage collection.
|
|
397
547
|
consume.type = null
|
|
398
548
|
consume.stream = null
|
|
399
549
|
consume.resolve = null
|
|
@@ -402,4 +552,7 @@ function consumeFinish (consume, err) {
|
|
|
402
552
|
consume.body = null
|
|
403
553
|
}
|
|
404
554
|
|
|
405
|
-
module.exports = {
|
|
555
|
+
module.exports = {
|
|
556
|
+
Readable: BodyReadable,
|
|
557
|
+
chunksDecode
|
|
558
|
+
}
|
package/lib/api/util.js
CHANGED
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const { Writable } = require('node:stream')
|
|
4
|
+
const { assertCacheKey, assertCacheValue } = require('../util/cache.js')
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* @typedef {import('../../types/cache-interceptor.d.ts').default.CacheKey} CacheKey
|
|
8
|
+
* @typedef {import('../../types/cache-interceptor.d.ts').default.CacheValue} CacheValue
|
|
9
|
+
* @typedef {import('../../types/cache-interceptor.d.ts').default.CacheStore} CacheStore
|
|
10
|
+
* @typedef {import('../../types/cache-interceptor.d.ts').default.GetResult} GetResult
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* @implements {CacheStore}
|
|
15
|
+
*/
|
|
16
|
+
class MemoryCacheStore {
|
|
17
|
+
#maxCount = Infinity
|
|
18
|
+
#maxSize = Infinity
|
|
19
|
+
#maxEntrySize = Infinity
|
|
20
|
+
|
|
21
|
+
#size = 0
|
|
22
|
+
#count = 0
|
|
23
|
+
#entries = new Map()
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* @param {import('../../types/cache-interceptor.d.ts').default.MemoryCacheStoreOpts | undefined} [opts]
|
|
27
|
+
*/
|
|
28
|
+
constructor (opts) {
|
|
29
|
+
if (opts) {
|
|
30
|
+
if (typeof opts !== 'object') {
|
|
31
|
+
throw new TypeError('MemoryCacheStore options must be an object')
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
if (opts.maxCount !== undefined) {
|
|
35
|
+
if (
|
|
36
|
+
typeof opts.maxCount !== 'number' ||
|
|
37
|
+
!Number.isInteger(opts.maxCount) ||
|
|
38
|
+
opts.maxCount < 0
|
|
39
|
+
) {
|
|
40
|
+
throw new TypeError('MemoryCacheStore options.maxCount must be a non-negative integer')
|
|
41
|
+
}
|
|
42
|
+
this.#maxCount = opts.maxCount
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
if (opts.maxSize !== undefined) {
|
|
46
|
+
if (
|
|
47
|
+
typeof opts.maxSize !== 'number' ||
|
|
48
|
+
!Number.isInteger(opts.maxSize) ||
|
|
49
|
+
opts.maxSize < 0
|
|
50
|
+
) {
|
|
51
|
+
throw new TypeError('MemoryCacheStore options.maxSize must be a non-negative integer')
|
|
52
|
+
}
|
|
53
|
+
this.#maxSize = opts.maxSize
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
if (opts.maxEntrySize !== undefined) {
|
|
57
|
+
if (
|
|
58
|
+
typeof opts.maxEntrySize !== 'number' ||
|
|
59
|
+
!Number.isInteger(opts.maxEntrySize) ||
|
|
60
|
+
opts.maxEntrySize < 0
|
|
61
|
+
) {
|
|
62
|
+
throw new TypeError('MemoryCacheStore options.maxEntrySize must be a non-negative integer')
|
|
63
|
+
}
|
|
64
|
+
this.#maxEntrySize = opts.maxEntrySize
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* @param {import('../../types/cache-interceptor.d.ts').default.CacheKey} req
|
|
71
|
+
* @returns {import('../../types/cache-interceptor.d.ts').default.GetResult | undefined}
|
|
72
|
+
*/
|
|
73
|
+
get (key) {
|
|
74
|
+
assertCacheKey(key)
|
|
75
|
+
|
|
76
|
+
const topLevelKey = `${key.origin}:${key.path}`
|
|
77
|
+
|
|
78
|
+
const now = Date.now()
|
|
79
|
+
const entry = this.#entries.get(topLevelKey)?.find((entry) => (
|
|
80
|
+
entry.deleteAt > now &&
|
|
81
|
+
entry.method === key.method &&
|
|
82
|
+
(entry.vary == null || Object.keys(entry.vary).every(headerName => entry.vary[headerName] === key.headers?.[headerName]))
|
|
83
|
+
))
|
|
84
|
+
|
|
85
|
+
return entry == null
|
|
86
|
+
? undefined
|
|
87
|
+
: {
|
|
88
|
+
statusMessage: entry.statusMessage,
|
|
89
|
+
statusCode: entry.statusCode,
|
|
90
|
+
headers: entry.headers,
|
|
91
|
+
body: entry.body,
|
|
92
|
+
vary: entry.vary ? entry.vary : undefined,
|
|
93
|
+
etag: entry.etag,
|
|
94
|
+
cacheControlDirectives: entry.cacheControlDirectives,
|
|
95
|
+
cachedAt: entry.cachedAt,
|
|
96
|
+
staleAt: entry.staleAt,
|
|
97
|
+
deleteAt: entry.deleteAt
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* @param {import('../../types/cache-interceptor.d.ts').default.CacheKey} key
|
|
103
|
+
* @param {import('../../types/cache-interceptor.d.ts').default.CacheValue} val
|
|
104
|
+
* @returns {Writable | undefined}
|
|
105
|
+
*/
|
|
106
|
+
createWriteStream (key, val) {
|
|
107
|
+
assertCacheKey(key)
|
|
108
|
+
assertCacheValue(val)
|
|
109
|
+
|
|
110
|
+
const topLevelKey = `${key.origin}:${key.path}`
|
|
111
|
+
|
|
112
|
+
const store = this
|
|
113
|
+
const entry = { ...key, ...val, body: [], size: 0 }
|
|
114
|
+
|
|
115
|
+
return new Writable({
|
|
116
|
+
write (chunk, encoding, callback) {
|
|
117
|
+
if (typeof chunk === 'string') {
|
|
118
|
+
chunk = Buffer.from(chunk, encoding)
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
entry.size += chunk.byteLength
|
|
122
|
+
|
|
123
|
+
if (entry.size >= store.#maxEntrySize) {
|
|
124
|
+
this.destroy()
|
|
125
|
+
} else {
|
|
126
|
+
entry.body.push(chunk)
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
callback(null)
|
|
130
|
+
},
|
|
131
|
+
final (callback) {
|
|
132
|
+
let entries = store.#entries.get(topLevelKey)
|
|
133
|
+
if (!entries) {
|
|
134
|
+
entries = []
|
|
135
|
+
store.#entries.set(topLevelKey, entries)
|
|
136
|
+
}
|
|
137
|
+
entries.push(entry)
|
|
138
|
+
|
|
139
|
+
store.#size += entry.size
|
|
140
|
+
store.#count += 1
|
|
141
|
+
|
|
142
|
+
if (store.#size > store.#maxSize || store.#count > store.#maxCount) {
|
|
143
|
+
for (const [key, entries] of store.#entries) {
|
|
144
|
+
for (const entry of entries.splice(0, entries.length / 2)) {
|
|
145
|
+
store.#size -= entry.size
|
|
146
|
+
store.#count -= 1
|
|
147
|
+
}
|
|
148
|
+
if (entries.length === 0) {
|
|
149
|
+
store.#entries.delete(key)
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
callback(null)
|
|
155
|
+
}
|
|
156
|
+
})
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* @param {CacheKey} key
|
|
161
|
+
*/
|
|
162
|
+
delete (key) {
|
|
163
|
+
if (typeof key !== 'object') {
|
|
164
|
+
throw new TypeError(`expected key to be object, got ${typeof key}`)
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
const topLevelKey = `${key.origin}:${key.path}`
|
|
168
|
+
|
|
169
|
+
for (const entry of this.#entries.get(topLevelKey) ?? []) {
|
|
170
|
+
this.#size -= entry.size
|
|
171
|
+
this.#count -= 1
|
|
172
|
+
}
|
|
173
|
+
this.#entries.delete(topLevelKey)
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
module.exports = MemoryCacheStore
|