1
- import type { CacheStore } from '~/utils/cache.utils' ;
1
+ import type { CacheStore , CacheStoreEntity } from '~/utils/cache.utils' ;
2
2
3
3
import type { RecursiveRecord } from '~/utils/typescript.utils' ;
4
4
@@ -56,7 +56,7 @@ export type BaseTemplateOptions<
56
56
* Enables caching of requests (defaults to true).
57
57
* If a number is provided, it will be used as the retention time in milliseconds.
58
58
*/
59
- cache ?: boolean | number ;
59
+ cache ?: boolean | number | { retention ?: number ; evictOnError ?: boolean } ;
60
60
/** Boolean record or required (truthy) or optional parameters (falsy) */
61
61
parameters ?: {
62
62
/** Boolean record or required (truthy) or optional path parameters (falsy) */
@@ -87,7 +87,14 @@ export type BaseTemplate<P extends RecursiveRecord = RecursiveRecord, O extends
87
87
transform ?: ( param : P ) => P ;
88
88
} ;
89
89
90
- export type TypedResponse < T > = Omit < Response , 'json' > & { json ( ) : Promise < T > } ;
90
+ export type TypedResponse < T > = Omit < Response , 'json' > & {
91
+ json ( ) : Promise < T > ;
92
+ cache ?: {
93
+ previous ?: CacheStoreEntity < TypedResponse < T > > ;
94
+ current ?: CacheStoreEntity < TypedResponse < T > > ;
95
+ isCache ?: boolean ;
96
+ } ;
97
+ } ;
91
98
92
99
export type ResponseOrTypedResponse < T = unknown > = T extends never ? Response : TypedResponse < T > ;
93
100
@@ -99,7 +106,7 @@ type ClientEndpointCall<Parameter extends Record<string, never> = Record<string,
99
106
export interface ClientEndpoint < Parameter extends RecursiveRecord = Record < string , never > , Response = unknown > {
100
107
( param ?: Parameter , init ?: BaseInit ) : Promise < ResponseOrTypedResponse < Response > > ;
101
108
}
102
- type BaseCacheOption = { force ?: boolean ; retention ?: number } ;
109
+ type BaseCacheOption = { force ?: boolean ; retention ?: number ; evictOnError ?: boolean } ;
103
110
104
111
type ClientEndpointCache < Parameter extends RecursiveRecord = Record < string , never > , Response = unknown > = (
105
112
param ?: Parameter ,
@@ -125,6 +132,16 @@ export class ClientEndpoint<
125
132
cached : Cache extends true ? Omit < this, 'cached' > & ClientEndpointCache < Parameter , Response > : never ;
126
133
resolve : ( param ?: Parameter ) => URL ;
127
134
135
+ get config ( ) {
136
+ return {
137
+ method : this . method ,
138
+ url : this . url ,
139
+ opts : this . opts ,
140
+ init : this . init ,
141
+ body : this . body ,
142
+ } ;
143
+ }
144
+
128
145
constructor ( template : BaseTemplate < Parameter , Option > ) {
129
146
this . method = template . method ;
130
147
this . url = template . url ;
@@ -155,12 +172,14 @@ const isApiTemplate = <T extends RecursiveRecord = RecursiveRecord>(template: Cl
155
172
/**
156
173
* Clones a response object
157
174
* @param response - The response to clone
175
+ * @param cache - Optional cache data to attach to the clone
158
176
*/
159
- const cloneResponse = < T > ( response : TypedResponse < T > ) : TypedResponse < T > => {
160
- const clone : Record < keyof TypedResponse < T > , unknown > = response . clone ( ) ;
177
+ const cloneResponse = < T > ( response : TypedResponse < T > , cache ?: TypedResponse < T > [ 'cache' ] ) : TypedResponse < T > => {
178
+ const clone : { - readonly [ K in keyof TypedResponse < T > ] : unknown } = response . clone ( ) ;
161
179
Object . entries ( response ) . forEach ( ( [ key , value ] ) => {
162
180
if ( typeof value !== 'function' ) clone [ key as keyof TypedResponse < T > ] = value ;
163
181
} ) ;
182
+ clone . cache = cache ;
164
183
return clone as TypedResponse < T > ;
165
184
} ;
166
185
@@ -262,30 +281,39 @@ export abstract class BaseClient<
262
281
protected bindToEndpoint ( api : IApi ) {
263
282
const client = { ...api } ;
264
283
Object . entries ( client ) . forEach ( ( [ endpoint , template ] ) => {
265
- if ( isApiTemplate ( template ) && isApiTemplate ( client [ endpoint ] ) ) {
284
+ if ( isApiTemplate ( template ) ) {
266
285
const fn : ClientEndpointCall = ( param , init ) => this . _call ( template , param , init ) ;
267
286
268
287
const cachedFn : ClientEndpointCache = async ( param , init , cacheOptions ) => {
269
- const key = JSON . stringify ( { endpoint , param, init } ) ;
270
- if ( ! cacheOptions ?. force ) {
271
- const cached = await this . _cache . get ( key ) ;
272
- if ( cached ) {
273
- const templateRetention = typeof template . opts ?. cache === 'number' ? template . opts . cache : undefined ;
274
- const retention = cacheOptions ?. retention ?? templateRetention ?? this . _cache . retention ;
275
- if ( ! retention ) return cloneResponse ( cached . value ) ;
276
- const expires = cached . cachedAt + retention ;
277
- if ( expires > Date . now ( ) ) return cloneResponse ( cached . value ) ;
278
- }
288
+ const key = JSON . stringify ( { template : template . config , param, init } ) ;
289
+
290
+ const cached = await this . _cache . get ( key ) ;
291
+ if ( cached && ! cacheOptions ?. force ) {
292
+ let templateRetention = typeof template . opts ?. cache === 'number' ? template . opts . cache : undefined ;
293
+ if ( typeof template . opts ?. cache === 'object' ) templateRetention = template . opts . cache . retention ;
294
+ const retention = cacheOptions ?. retention ?? templateRetention ?? this . _cache . retention ;
295
+ if ( ! retention ) return cloneResponse ( cached . value , { previous : cached , current : cached , isCache : true } ) ;
296
+ const expires = cached . cachedAt + retention ;
297
+ if ( expires > Date . now ( ) ) return cloneResponse ( cached . value , { previous : cached , current : cached , isCache : true } ) ;
279
298
}
299
+
280
300
try {
281
301
const result = await fn ( param , init ) ;
282
- await this . _cache . set ( key , {
302
+ const cacheEntry = {
283
303
cachedAt : Date . now ( ) ,
284
304
value : cloneResponse ( result ) as ResponseType ,
285
- } ) ;
305
+ } ;
306
+ await this . _cache . set ( key , cacheEntry ) ;
307
+ result . cache = { previous : cached , current : cacheEntry , isCache : false } ;
286
308
return result ;
287
309
} catch ( error ) {
288
- this . _cache . delete ( key ) ;
310
+ if (
311
+ cacheOptions ?. evictOnError ??
312
+ ( typeof template . opts ?. cache === 'object' ? template . opts ?. cache ?. evictOnError : undefined ) ??
313
+ this . _cache . evictOnError
314
+ ) {
315
+ this . _cache . delete ( key ) ;
316
+ }
289
317
throw error ;
290
318
}
291
319
} ;
@@ -296,7 +324,7 @@ export abstract class BaseClient<
296
324
return this . _parseUrl ( template , _params ) ;
297
325
} ;
298
326
299
- Object . entries ( client [ endpoint ] ) . forEach ( ( [ key , value ] ) => {
327
+ Object . entries ( template ) . forEach ( ( [ key , value ] ) => {
300
328
if ( key === 'cached' ) {
301
329
if ( template . opts ?. cache ) Object . defineProperty ( fn , 'cached' , { value : cachedFn } ) ;
302
330
} else if ( key === 'resolve' ) {
@@ -310,7 +338,7 @@ export abstract class BaseClient<
310
338
311
339
client [ endpoint ] = fn as ( typeof client ) [ typeof endpoint ] ;
312
340
} else {
313
- client [ endpoint ] = this . bindToEndpoint ( client [ endpoint ] as IApi ) ;
341
+ client [ endpoint ] = this . bindToEndpoint ( template as IApi ) ;
314
342
}
315
343
} ) ;
316
344
return client ;
0 commit comments