@@ -106,13 +106,14 @@ type ClientEndpointCall<Parameter extends RecursiveRecord = Record<string, never
106
106
export interface ClientEndpoint < Parameter extends RecursiveRecord = Record < string , never > , Response = unknown > {
107
107
( param ?: Parameter , init ?: BaseInit ) : Promise < ResponseOrTypedResponse < Response > > ;
108
108
}
109
+
109
110
export type BaseCacheOption = { force ?: boolean ; retention ?: number ; evictOnError ?: boolean } ;
110
111
111
112
type ClientEndpointCache < Parameter extends RecursiveRecord = Record < string , never > , Response = unknown > = (
112
113
param ?: Parameter ,
113
114
init ?: BaseInit ,
114
115
cacheOptions ?: BaseCacheOption ,
115
- ) => Promise < ResponseOrTypedResponse < Response > > ;
116
+ ) => Promise < TypedResponse < Response > > ;
116
117
117
118
// eslint-disable-next-line @typescript-eslint/no-unsafe-declaration-merging
118
119
export class ClientEndpoint <
@@ -183,6 +184,52 @@ const cloneResponse = <T>(response: TypedResponse<T>, cache?: TypedResponse<T>['
183
184
return clone as TypedResponse < T > ;
184
185
} ;
185
186
187
+ export const getCachedFunction = <
188
+ Parameter extends RecursiveRecord = RecursiveRecord ,
189
+ ResponseBody = unknown ,
190
+ ResponseType extends Response = Response ,
191
+ > (
192
+ clientFn : ClientEndpointCall < Parameter , ResponseBody > ,
193
+ {
194
+ key,
195
+ cache,
196
+ retention,
197
+ } : {
198
+ key : string | ( ( param ?: Parameter , init ?: BaseInit ) => string ) ;
199
+ cache : CacheStore < ResponseType > ;
200
+ retention ?: BaseTemplateOptions [ 'cache' ] ;
201
+ } ,
202
+ ) : ClientEndpointCache < Parameter , ResponseBody > => {
203
+ return async ( param , init , cacheOptions ) => {
204
+ const _key = typeof key === 'function' ? key ( param , init ) : key ;
205
+ const cached = await cache . get ( _key ) ;
206
+ if ( cached && ! cacheOptions ?. force ) {
207
+ let templateRetention = typeof retention === 'number' ? retention : undefined ;
208
+ if ( typeof retention === 'object' ) templateRetention = retention . retention ;
209
+ const _retention = cacheOptions ?. retention ?? templateRetention ?? cache . retention ;
210
+ if ( ! _retention ) return cloneResponse < ResponseType > ( cached . value , { previous : cached , current : cached , isCache : true } ) ;
211
+ const expires = cached . cachedAt + _retention ;
212
+ if ( expires > Date . now ( ) ) return cloneResponse ( cached . value , { previous : cached , current : cached , isCache : true } ) ;
213
+ }
214
+
215
+ try {
216
+ const result : TypedResponse < ResponseBody > = await clientFn ( param , init ) ;
217
+ const cacheEntry : CacheStoreEntity < ResponseType > = {
218
+ cachedAt : Date . now ( ) ,
219
+ value : cloneResponse ( result ) as ResponseType ,
220
+ } ;
221
+ await cache . set ( _key , cacheEntry ) ;
222
+ result . cache = { previous : cached , current : cacheEntry , isCache : false } ;
223
+ return result ;
224
+ } catch ( error ) {
225
+ if ( cacheOptions ?. evictOnError ?? ( typeof retention === 'object' ? retention ?. evictOnError : undefined ) ?? cache . evictOnError ) {
226
+ cache . delete ( _key ) ;
227
+ }
228
+ throw error ;
229
+ }
230
+ } ;
231
+ } ;
232
+
186
233
/**
187
234
* Represents a client with common functionality.
188
235
*
@@ -285,39 +332,11 @@ export abstract class BaseClient<
285
332
if ( isApiTemplate ( template ) ) {
286
333
const fn : ClientEndpointCall = ( param , init ) => this . _call ( template , param , init ) ;
287
334
288
- const cachedFn : ClientEndpointCache = async ( param , init , cacheOptions ) => {
289
- const key = JSON . stringify ( { template : template . config , param, init } ) ;
290
-
291
- const cached = await this . _cache . get ( key ) ;
292
- if ( cached && ! cacheOptions ?. force ) {
293
- let templateRetention = typeof template . opts ?. cache === 'number' ? template . opts . cache : undefined ;
294
- if ( typeof template . opts ?. cache === 'object' ) templateRetention = template . opts . cache . retention ;
295
- const retention = cacheOptions ?. retention ?? templateRetention ?? this . _cache . retention ;
296
- if ( ! retention ) return cloneResponse ( cached . value , { previous : cached , current : cached , isCache : true } ) ;
297
- const expires = cached . cachedAt + retention ;
298
- if ( expires > Date . now ( ) ) return cloneResponse ( cached . value , { previous : cached , current : cached , isCache : true } ) ;
299
- }
300
-
301
- try {
302
- const result = await fn ( param , init ) ;
303
- const cacheEntry = {
304
- cachedAt : Date . now ( ) ,
305
- value : cloneResponse ( result ) as ResponseType ,
306
- } ;
307
- await this . _cache . set ( key , cacheEntry ) ;
308
- result . cache = { previous : cached , current : cacheEntry , isCache : false } ;
309
- return result ;
310
- } catch ( error ) {
311
- if (
312
- cacheOptions ?. evictOnError ??
313
- ( typeof template . opts ?. cache === 'object' ? template . opts ?. cache ?. evictOnError : undefined ) ??
314
- this . _cache . evictOnError
315
- ) {
316
- this . _cache . delete ( key ) ;
317
- }
318
- throw error ;
319
- }
320
- } ;
335
+ const cachedFn : ClientEndpointCache = getCachedFunction ( fn , {
336
+ key : ( param : unknown , init : unknown ) => JSON . stringify ( { template : template . config , param, init } ) ,
337
+ cache : this . _cache ,
338
+ retention : template . opts ?. cache ,
339
+ } ) ;
321
340
322
341
const parseUrl = ( param : Record < string , unknown > = { } ) => {
323
342
const _params = template . transform ?.( param ) ?? param ;
0 commit comments