3
3
const { Writable } = require ( 'node:stream' )
4
4
5
5
/**
6
+ * @typedef {import('../../types/cache-interceptor.d.ts').default.CacheKey } CacheKey
6
7
* @typedef {import('../../types/cache-interceptor.d.ts').default.CacheStore } CacheStore
8
+ * @typedef {import('../../types/cache-interceptor.d.ts').default.CachedResponse } CachedResponse
9
+ * @typedef {import('../../types/cache-interceptor.d.ts').default.GetResult } GetResult
7
10
* @implements {CacheStore}
8
- *
9
- * @typedef {{
10
- * locked: boolean
11
- * opts: import('../../types/cache-interceptor.d.ts').default.CachedResponse
12
- * body?: Buffer[]
13
- * }} MemoryStoreValue
14
11
*/
15
12
class MemoryCacheStore {
16
13
#maxCount = Infinity
@@ -20,7 +17,7 @@ class MemoryCacheStore {
20
17
#entryCount = 0
21
18
22
19
/**
23
- * @type {Map<string, Map<string, MemoryStoreValue []>> }
20
+ * @type {Map<string, Map<string, GetResult []>> }
24
21
*/
25
22
#data = new Map ( )
26
23
@@ -66,22 +63,7 @@ class MemoryCacheStore {
66
63
* @returns {import('../../types/cache-interceptor.d.ts').default.GetResult | undefined }
67
64
*/
68
65
get ( key ) {
69
- if ( typeof key !== 'object' ) {
70
- throw new TypeError ( `expected key to be object, got ${ typeof key } ` )
71
- }
72
-
73
- const values = this . #getValuesForRequest( key , false )
74
- if ( ! values ) {
75
- return undefined
76
- }
77
-
78
- const value = this . #findValue( key , values )
79
-
80
- if ( ! value || value . locked ) {
81
- return undefined
82
- }
83
-
84
- return { ...value . opts , body : value . body }
66
+ return this . #findValue( key , this . #getValuesForRequest( key ) )
85
67
}
86
68
87
69
/**
@@ -97,87 +79,53 @@ class MemoryCacheStore {
97
79
throw new TypeError ( `expected value to be object, got ${ typeof opts } ` )
98
80
}
99
81
82
+ if ( this . isFull ) {
83
+ this . #prune( )
84
+ }
85
+
100
86
if ( this . isFull ) {
101
87
return undefined
102
88
}
103
89
104
- const values = this . #getValuesForRequest( key , true )
90
+ const values = this . #getValuesForRequest( key )
105
91
106
92
let value = this . #findValue( key , values )
107
- if ( ! value ) {
108
- // The value doesn't already exist, meaning we haven't cached this
109
- // response before. Let's assign it a value and insert it into our data
110
- // property.
111
-
112
- if ( this . isFull ) {
113
- // Or not, we don't have space to add another response
114
- return undefined
115
- }
116
93
117
- if ( this . #entryCount++ > this . #maxEntries) {
118
- this . #prune( )
119
- }
120
-
121
- value = { locked : true , opts }
94
+ if ( ! value ) {
95
+ value = { ...opts , body : null }
122
96
values . push ( value )
123
- } else {
124
- // Check if there's already another request writing to the value or
125
- // a request reading from it
126
- if ( value . locked ) {
127
- return undefined
128
- }
129
-
130
- // Empty it so we can overwrite it
131
- value . body = [ ]
132
97
}
133
98
134
99
let currentSize = 0
135
- /**
136
- * @type {Buffer[] | null }
137
- */
138
- let body = [ ]
100
+
101
+ const body = [ ]
139
102
const maxEntrySize = this . #maxEntrySize
140
103
141
- const writable = new Writable ( {
104
+ return new Writable ( {
142
105
write ( chunk , encoding , callback ) {
143
- if ( key . method === 'HEAD' ) {
144
- throw new Error ( 'HEAD request shouldn\'t have a body' )
145
- }
146
-
147
- if ( ! body ) {
148
- return callback ( )
149
- }
150
-
151
106
if ( typeof chunk === 'string' ) {
152
107
chunk = Buffer . from ( chunk , encoding )
153
108
}
154
109
155
110
currentSize += chunk . byteLength
156
111
157
112
if ( currentSize >= maxEntrySize ) {
158
- body = null
159
- this . end ( )
160
- return callback ( )
113
+ this . destroy ( )
114
+ } else {
115
+ body . push ( chunk )
161
116
}
162
117
163
- body . push ( chunk )
164
118
callback ( )
165
119
} ,
166
120
final ( callback ) {
167
- value . locked = false
168
- if ( body !== null ) {
169
- value . body = body
170
- }
171
-
121
+ Object . assign ( value , opts , { body } )
172
122
callback ( )
173
123
}
174
124
} )
175
-
176
- return writable
177
125
}
178
126
179
127
/**
180
- * @param {import('../../types/cache-interceptor.d.ts').default. CacheKey } key
128
+ * @param {CacheKey } key
181
129
*/
182
130
delete ( key ) {
183
131
this . #data. delete ( `${ key . origin } :${ key . path } ` )
@@ -186,25 +134,24 @@ class MemoryCacheStore {
186
134
/**
187
135
* Gets all of the requests of the same origin, path, and method. Does not
188
136
* take the `vary` property into account.
189
- * @param {import('../../types/cache-interceptor.d.ts').default.CacheKey } key
190
- * @param {boolean } [makeIfDoesntExist=false]
191
- * @returns {MemoryStoreValue[] | undefined }
137
+ * @param {CacheKey } key
138
+ * @returns {GetResult[] }
192
139
*/
193
- #getValuesForRequest ( key , makeIfDoesntExist ) {
140
+ #getValuesForRequest ( key ) {
141
+ if ( typeof key !== 'object' ) {
142
+ throw new TypeError ( `expected key to be object, got ${ typeof key } ` )
143
+ }
144
+
194
145
// https://www.rfc-editor.org/rfc/rfc9111.html#section-2-3
195
146
const topLevelKey = `${ key . origin } :${ key . path } `
196
147
let cachedPaths = this . #data. get ( topLevelKey )
197
148
if ( ! cachedPaths ) {
198
- if ( ! makeIfDoesntExist ) {
199
- return undefined
200
- }
201
-
202
149
cachedPaths = new Map ( )
203
150
this . #data. set ( topLevelKey , cachedPaths )
204
151
}
205
152
206
153
let value = cachedPaths . get ( key . method )
207
- if ( ! value && makeIfDoesntExist ) {
154
+ if ( ! value ) {
208
155
value = [ ]
209
156
cachedPaths . set ( key . method , value )
210
157
}
@@ -214,25 +161,29 @@ class MemoryCacheStore {
214
161
215
162
/**
216
163
* Given a list of values of a certain request, this decides the best value
217
- * to respond with.
218
- * @param {import('../../types/cache-interceptor.d.ts').default. CacheKey } req
219
- * @param {MemoryStoreValue[] } values
220
- * @returns {(MemoryStoreValue ) | undefined }
164
+ * to respond with.
165
+ * @param {CacheKey } key
166
+ * @param {GetResult[] | undefined } values
167
+ * @returns {(GetResult ) | undefined }
221
168
*/
222
- #findValue ( req , values ) {
169
+ #findValue ( key , values ) {
170
+ if ( typeof key !== 'object' ) {
171
+ throw new TypeError ( `expected key to be object, got ${ typeof key } ` )
172
+ }
173
+
223
174
const now = Date . now ( )
224
- return values . find ( ( { opts : { deleteAt, vary } , body } ) => (
175
+ return values ? .find ( ( { deleteAt, vary, body } ) => (
225
176
body != null &&
226
177
deleteAt > now &&
227
- ( ! vary || Object . keys ( vary ) . every ( key => vary [ key ] === req . headers ?. [ key ] ) )
178
+ ( ! vary || Object . keys ( vary ) . every ( headerName => vary [ headerName ] === key . headers ?. [ headerName ] ) )
228
179
) )
229
180
}
230
181
231
182
#prune ( ) {
232
183
const now = Date . now ( )
233
184
for ( const [ key , cachedPaths ] of this . #data) {
234
185
for ( const [ method , prev ] of cachedPaths ) {
235
- const next = prev . filter ( ( { opts , body } ) => body == null || opts . deleteAt > now )
186
+ const next = prev . filter ( ( { deleteAt } ) => deleteAt > now )
236
187
if ( next . length === 0 ) {
237
188
cachedPaths . delete ( method )
238
189
if ( cachedPaths . size === 0 ) {
0 commit comments