@tekir/cache 0.1.1 → 0.1.3

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.
@@ -0,0 +1,89 @@
1
+ /**
2
+ * HTTP response cache middleware.
3
+ *
4
+ * Caches the full Response (status + headers + body) under a key derived
5
+ * from the request. On hit: short-circuits the handler chain and returns
6
+ * the cached payload, also producing `304 Not Modified` when the client
7
+ * sends a matching `If-None-Match`.
8
+ *
9
+ * Storage is delegated to any `CacheStore` (memory, redis, database).
10
+ * Only safe methods (GET, HEAD) are cached by default.
11
+ *
12
+ * @example
13
+ * ```ts
14
+ * import { Cache, cache, MemoryCacheStore } from '@tekir/cache'
15
+ *
16
+ * const store = new Cache({ stores: { memory: new MemoryCacheStore() } })
17
+ *
18
+ * router.get(
19
+ * '/api/posts',
20
+ * cache({ store, ttl: 60 }),
21
+ * async () => Post.all(),
22
+ * )
23
+ * ```
24
+ */
25
+ import { Cache } from "./cache";
26
+ import type { CacheStore } from "./types";
27
+ export type HttpCacheOptions = {
28
+ /**
29
+ * Cache backend. Either a `Cache` manager (uses default store) or a raw
30
+ * `CacheStore`. If omitted you must register `@tekir/cache` in the app
31
+ * and the middleware will resolve `service('cache')` at request time.
32
+ */
33
+ store?: Cache | CacheStore;
34
+ /** Time-to-live in seconds. Default: 60. */
35
+ ttl?: number;
36
+ /**
37
+ * HTTP methods that are cacheable. Default: ['GET', 'HEAD'].
38
+ * Mutating methods (POST/PUT/PATCH/DELETE) skip the cache.
39
+ */
40
+ methods?: string[];
41
+ /**
42
+ * Custom key builder. Defaults to `${method} ${url}`. Pass a function
43
+ * to include user identity, query params, etc.
44
+ */
45
+ key?: (ctx: HttpCacheCtx) => string;
46
+ /**
47
+ * Optional list of request headers to include in the cache key,
48
+ * mirroring the HTTP `Vary` header. Default: [].
49
+ */
50
+ vary?: string[];
51
+ /**
52
+ * Skip predicate. Return `true` to bypass caching for this request.
53
+ */
54
+ skip?: (ctx: HttpCacheCtx) => boolean | Promise<boolean>;
55
+ /** Override the namespace prefix used in cache keys. Default: 'http:'. */
56
+ prefix?: string;
57
+ /**
58
+ * If true, sets `Cache-Control: public, max-age=<ttl>` on cached
59
+ * responses. Default: true.
60
+ */
61
+ setCacheControl?: boolean;
62
+ };
63
+ export type HttpCacheCtx = {
64
+ request: {
65
+ url: string;
66
+ method: string;
67
+ headers: Headers;
68
+ raw?: Request;
69
+ };
70
+ params?: Record<string, string>;
71
+ query?: Record<string, string | string[]>;
72
+ };
73
+ /**
74
+ * Register the default backing store for the `cache()` middleware. Called
75
+ * by `CacheProvider` after it builds the Cache manager from config.
76
+ *
77
+ * Users can also call this directly if they don't use providers:
78
+ *
79
+ * ```ts
80
+ * import { setDefaultCacheStore, Cache, MemoryCacheStore } from '@tekir/cache'
81
+ * setDefaultCacheStore(new Cache({ stores: { memory: new MemoryCacheStore() } }))
82
+ * ```
83
+ */
84
+ export declare function setDefaultCacheStore(s: Cache | CacheStore | null): void;
85
+ /**
86
+ * Returns the currently registered default store, or null if none.
87
+ */
88
+ export declare function getDefaultCacheStore(): Cache | CacheStore | null;
89
+ export declare function cache(opts?: HttpCacheOptions): (ctx: any, next: () => Promise<void>) => Promise<void>;
@@ -0,0 +1,175 @@
1
+ /**
2
+ * HTTP response cache middleware.
3
+ *
4
+ * Caches the full Response (status + headers + body) under a key derived
5
+ * from the request. On hit: short-circuits the handler chain and returns
6
+ * the cached payload, also producing `304 Not Modified` when the client
7
+ * sends a matching `If-None-Match`.
8
+ *
9
+ * Storage is delegated to any `CacheStore` (memory, redis, database).
10
+ * Only safe methods (GET, HEAD) are cached by default.
11
+ *
12
+ * @example
13
+ * ```ts
14
+ * import { Cache, cache, MemoryCacheStore } from '@tekir/cache'
15
+ *
16
+ * const store = new Cache({ stores: { memory: new MemoryCacheStore() } })
17
+ *
18
+ * router.get(
19
+ * '/api/posts',
20
+ * cache({ store, ttl: 60 }),
21
+ * async () => Post.all(),
22
+ * )
23
+ * ```
24
+ */
25
+ import { Cache } from "./cache";
26
+ const SAFE_METHODS = ["GET", "HEAD"];
27
+ const isStore = (s) => !!s &&
28
+ typeof s.get === "function" &&
29
+ typeof s.set === "function";
30
+ /**
31
+ * Module-level default store. CacheProvider sets this at register time so
32
+ * `cache({ ttl: 60 })` works without an explicit `store` option once the
33
+ * provider is wired into the app. Stays null if the provider isn't used,
34
+ * in which case the middleware no-ops (passes through).
35
+ */
36
+ let _defaultStore = null;
37
+ /**
38
+ * Register the default backing store for the `cache()` middleware. Called
39
+ * by `CacheProvider` after it builds the Cache manager from config.
40
+ *
41
+ * Users can also call this directly if they don't use providers:
42
+ *
43
+ * ```ts
44
+ * import { setDefaultCacheStore, Cache, MemoryCacheStore } from '@tekir/cache'
45
+ * setDefaultCacheStore(new Cache({ stores: { memory: new MemoryCacheStore() } }))
46
+ * ```
47
+ */
48
+ export function setDefaultCacheStore(s) {
49
+ _defaultStore = s;
50
+ }
51
+ /**
52
+ * Returns the currently registered default store, or null if none.
53
+ */
54
+ export function getDefaultCacheStore() {
55
+ return _defaultStore;
56
+ }
57
+ const hash = (s) => {
58
+ // FNV-1a 32-bit. Good enough for ETags; not crypto.
59
+ let h = 0x811c9dc5;
60
+ for (let i = 0; i < s.length; i++) {
61
+ h ^= s.charCodeAt(i);
62
+ h = (h + ((h << 1) + (h << 4) + (h << 7) + (h << 8) + (h << 24))) >>> 0;
63
+ }
64
+ return h.toString(16).padStart(8, "0");
65
+ };
66
+ const defaultKey = (ctx, vary) => {
67
+ const parts = [ctx.request.method, ctx.request.url];
68
+ for (const h of vary) {
69
+ const v = ctx.request.headers.get(h);
70
+ if (v)
71
+ parts.push(`${h}=${v}`);
72
+ }
73
+ return parts.join("|");
74
+ };
75
+ const resolveStore = (s) => {
76
+ if (!s)
77
+ return null;
78
+ if (s instanceof Cache)
79
+ return s.store();
80
+ if (isStore(s))
81
+ return s;
82
+ return null;
83
+ };
84
+ const responseToEntry = async (resp) => {
85
+ const body = await resp.clone().text();
86
+ const headers = {};
87
+ resp.headers.forEach((v, k) => {
88
+ // Skip hop-by-hop and connection-specific headers
89
+ if (k === "connection" || k === "keep-alive" || k === "transfer-encoding")
90
+ return;
91
+ headers[k] = v;
92
+ });
93
+ const etag = `W/"${hash(body)}"`;
94
+ return { status: resp.status, headers, body, etag, storedAt: Date.now() };
95
+ };
96
+ const entryToResponse = (e, opts) => {
97
+ const headers = { ...e.headers, etag: e.etag };
98
+ if (opts.setCacheControl !== false && !headers["cache-control"]) {
99
+ headers["cache-control"] = `public, max-age=${opts.ttl ?? 60}`;
100
+ }
101
+ headers["x-tekir-cache"] = "HIT";
102
+ return new Response(e.body, { status: e.status, headers });
103
+ };
104
+ export function cache(opts = {}) {
105
+ const ttl = opts.ttl ?? 60;
106
+ const methods = new Set((opts.methods ?? SAFE_METHODS).map((m) => m.toUpperCase()));
107
+ const vary = opts.vary ?? [];
108
+ const prefix = opts.prefix ?? "http:";
109
+ const buildKey = opts.key ?? ((ctx) => defaultKey(ctx, vary));
110
+ const directStore = resolveStore(opts.store);
111
+ return async function cacheMiddleware(ctx, next) {
112
+ const req = ctx.request;
113
+ if (!req || !methods.has(String(req.method ?? "GET").toUpperCase())) {
114
+ await next();
115
+ return;
116
+ }
117
+ if (opts.skip && (await opts.skip(ctx))) {
118
+ await next();
119
+ return;
120
+ }
121
+ const cacheControl = req.headers?.get?.("cache-control") ?? "";
122
+ if (cacheControl.includes("no-store")) {
123
+ await next();
124
+ return;
125
+ }
126
+ // Resolve store: option > module-level default (set by CacheProvider).
127
+ // No store anywhere → middleware acts as a transparent no-op so the
128
+ // route still works without a registered cache backend.
129
+ let store = directStore;
130
+ if (!store)
131
+ store = resolveStore(_defaultStore);
132
+ if (!store) {
133
+ await next();
134
+ return;
135
+ }
136
+ const key = prefix + buildKey(ctx);
137
+ const cached = await store.get(key);
138
+ // Conditional request: client sent If-None-Match
139
+ const ifNoneMatch = req.headers?.get?.("if-none-match") ?? "";
140
+ if (cached) {
141
+ if (ifNoneMatch && ifNoneMatch === cached.etag) {
142
+ ctx.$result = new Response(null, {
143
+ status: 304,
144
+ headers: { etag: cached.etag, "x-tekir-cache": "REVALIDATED" },
145
+ });
146
+ return;
147
+ }
148
+ ctx.$result = entryToResponse(cached, opts);
149
+ return;
150
+ }
151
+ // Miss: run the handler chain, capture, store.
152
+ await next();
153
+ const result = ctx.$result;
154
+ if (!(result instanceof Response))
155
+ return;
156
+ if (result.status >= 500 || result.status === 204)
157
+ return; // don't cache errors / empty
158
+ if (cacheControl.includes("no-cache"))
159
+ return;
160
+ const respCacheControl = result.headers.get("cache-control") ?? "";
161
+ if (respCacheControl.includes("private") || respCacheControl.includes("no-store"))
162
+ return;
163
+ const entry = await responseToEntry(result);
164
+ await store.set(key, entry, ttl);
165
+ // Re-emit with x-tekir-cache: MISS so the client can see it
166
+ const out = {};
167
+ result.headers.forEach((v, k) => (out[k] = v));
168
+ out["etag"] = entry.etag;
169
+ if (opts.setCacheControl !== false && !out["cache-control"]) {
170
+ out["cache-control"] = `public, max-age=${ttl}`;
171
+ }
172
+ out["x-tekir-cache"] = "MISS";
173
+ ctx.$result = new Response(entry.body, { status: result.status, headers: out });
174
+ };
175
+ }
package/dist/index.d.ts CHANGED
@@ -4,3 +4,5 @@ export { RedisCacheStore } from './stores/redis';
4
4
  export { DatabaseCacheStore } from './stores/database';
5
5
  export { Cache, createCache } from './cache';
6
6
  export { CacheProvider } from './provider';
7
+ export { cache, setDefaultCacheStore, getDefaultCacheStore } from './http-cache';
8
+ export type { HttpCacheOptions, HttpCacheCtx } from './http-cache';
package/dist/index.js CHANGED
@@ -3,3 +3,4 @@ export { RedisCacheStore } from './stores/redis';
3
3
  export { DatabaseCacheStore } from './stores/database';
4
4
  export { Cache, createCache } from './cache';
5
5
  export { CacheProvider } from './provider';
6
+ export { cache, setDefaultCacheStore, getDefaultCacheStore } from './http-cache';
package/dist/provider.js CHANGED
@@ -77,10 +77,16 @@ export class CacheProvider {
77
77
  if (Object.keys(stores).length === 0) {
78
78
  stores.memory = new MemoryCacheStore();
79
79
  }
80
- app.instance('cache', new Cache({
80
+ const cacheInstance = new Cache({
81
81
  stores,
82
82
  ttl: config('cache.ttl', 60),
83
83
  default: config('cache.default', Object.keys(stores)[0]),
84
- }));
84
+ });
85
+ app.instance('cache', cacheInstance);
86
+ // Wire the cache() HTTP middleware so route-level `cache({ ttl: 60 })`
87
+ // works without an explicit `store` option once this provider is
88
+ // registered.
89
+ const { setDefaultCacheStore } = await import('./http-cache');
90
+ setDefaultCacheStore(cacheInstance);
85
91
  }
86
92
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tekir/cache",
3
- "version": "0.1.1",
3
+ "version": "0.1.3",
4
4
  "description": "In-memory, Redis, and database caching abstraction",
5
5
  "author": "dev@tekir.io",
6
6
  "license": "MIT",
@@ -39,7 +39,7 @@
39
39
  }
40
40
  },
41
41
  "dependencies": {
42
- "@tekir/core": "^0.1.2"
42
+ "@tekir/core": "^0.1.3"
43
43
  },
44
44
  "peerDependencies": {
45
45
  "@tekir/redis": "^0.1.0"
@@ -0,0 +1,240 @@
1
+ /**
2
+ * HTTP response cache middleware.
3
+ *
4
+ * Caches the full Response (status + headers + body) under a key derived
5
+ * from the request. On hit: short-circuits the handler chain and returns
6
+ * the cached payload, also producing `304 Not Modified` when the client
7
+ * sends a matching `If-None-Match`.
8
+ *
9
+ * Storage is delegated to any `CacheStore` (memory, redis, database).
10
+ * Only safe methods (GET, HEAD) are cached by default.
11
+ *
12
+ * @example
13
+ * ```ts
14
+ * import { Cache, cache, MemoryCacheStore } from '@tekir/cache'
15
+ *
16
+ * const store = new Cache({ stores: { memory: new MemoryCacheStore() } })
17
+ *
18
+ * router.get(
19
+ * '/api/posts',
20
+ * cache({ store, ttl: 60 }),
21
+ * async () => Post.all(),
22
+ * )
23
+ * ```
24
+ */
25
+ import { Cache } from "./cache"
26
+ import type { CacheStore } from "./types"
27
+
28
+ export type HttpCacheOptions = {
29
+ /**
30
+ * Cache backend. Either a `Cache` manager (uses default store) or a raw
31
+ * `CacheStore`. If omitted you must register `@tekir/cache` in the app
32
+ * and the middleware will resolve `service('cache')` at request time.
33
+ */
34
+ store?: Cache | CacheStore
35
+ /** Time-to-live in seconds. Default: 60. */
36
+ ttl?: number
37
+ /**
38
+ * HTTP methods that are cacheable. Default: ['GET', 'HEAD'].
39
+ * Mutating methods (POST/PUT/PATCH/DELETE) skip the cache.
40
+ */
41
+ methods?: string[]
42
+ /**
43
+ * Custom key builder. Defaults to `${method} ${url}`. Pass a function
44
+ * to include user identity, query params, etc.
45
+ */
46
+ key?: (ctx: HttpCacheCtx) => string
47
+ /**
48
+ * Optional list of request headers to include in the cache key,
49
+ * mirroring the HTTP `Vary` header. Default: [].
50
+ */
51
+ vary?: string[]
52
+ /**
53
+ * Skip predicate. Return `true` to bypass caching for this request.
54
+ */
55
+ skip?: (ctx: HttpCacheCtx) => boolean | Promise<boolean>
56
+ /** Override the namespace prefix used in cache keys. Default: 'http:'. */
57
+ prefix?: string
58
+ /**
59
+ * If true, sets `Cache-Control: public, max-age=<ttl>` on cached
60
+ * responses. Default: true.
61
+ */
62
+ setCacheControl?: boolean
63
+ }
64
+
65
+ export type HttpCacheCtx = {
66
+ request: { url: string; method: string; headers: Headers; raw?: Request }
67
+ params?: Record<string, string>
68
+ query?: Record<string, string | string[]>
69
+ }
70
+
71
+ type CachedEntry = {
72
+ status: number
73
+ headers: Record<string, string>
74
+ body: string
75
+ etag: string
76
+ storedAt: number
77
+ }
78
+
79
+ const SAFE_METHODS = ["GET", "HEAD"]
80
+
81
+ const isStore = (s: unknown): s is CacheStore =>
82
+ !!s &&
83
+ typeof (s as CacheStore).get === "function" &&
84
+ typeof (s as CacheStore).set === "function"
85
+
86
+ /**
87
+ * Module-level default store. CacheProvider sets this at register time so
88
+ * `cache({ ttl: 60 })` works without an explicit `store` option once the
89
+ * provider is wired into the app. Stays null if the provider isn't used,
90
+ * in which case the middleware no-ops (passes through).
91
+ */
92
+ let _defaultStore: Cache | CacheStore | null = null
93
+
94
+ /**
95
+ * Register the default backing store for the `cache()` middleware. Called
96
+ * by `CacheProvider` after it builds the Cache manager from config.
97
+ *
98
+ * Users can also call this directly if they don't use providers:
99
+ *
100
+ * ```ts
101
+ * import { setDefaultCacheStore, Cache, MemoryCacheStore } from '@tekir/cache'
102
+ * setDefaultCacheStore(new Cache({ stores: { memory: new MemoryCacheStore() } }))
103
+ * ```
104
+ */
105
+ export function setDefaultCacheStore(s: Cache | CacheStore | null): void {
106
+ _defaultStore = s
107
+ }
108
+
109
+ /**
110
+ * Returns the currently registered default store, or null if none.
111
+ */
112
+ export function getDefaultCacheStore(): Cache | CacheStore | null {
113
+ return _defaultStore
114
+ }
115
+
116
+ const hash = (s: string): string => {
117
+ // FNV-1a 32-bit. Good enough for ETags; not crypto.
118
+ let h = 0x811c9dc5
119
+ for (let i = 0; i < s.length; i++) {
120
+ h ^= s.charCodeAt(i)
121
+ h = (h + ((h << 1) + (h << 4) + (h << 7) + (h << 8) + (h << 24))) >>> 0
122
+ }
123
+ return h.toString(16).padStart(8, "0")
124
+ }
125
+
126
+ const defaultKey = (ctx: HttpCacheCtx, vary: string[]): string => {
127
+ const parts = [ctx.request.method, ctx.request.url]
128
+ for (const h of vary) {
129
+ const v = ctx.request.headers.get(h)
130
+ if (v) parts.push(`${h}=${v}`)
131
+ }
132
+ return parts.join("|")
133
+ }
134
+
135
+ const resolveStore = (
136
+ s: HttpCacheOptions["store"] | null | undefined,
137
+ ): CacheStore | null => {
138
+ if (!s) return null
139
+ if (s instanceof Cache) return s.store()
140
+ if (isStore(s)) return s
141
+ return null
142
+ }
143
+
144
+ const responseToEntry = async (resp: Response): Promise<CachedEntry> => {
145
+ const body = await resp.clone().text()
146
+ const headers: Record<string, string> = {}
147
+ resp.headers.forEach((v, k) => {
148
+ // Skip hop-by-hop and connection-specific headers
149
+ if (k === "connection" || k === "keep-alive" || k === "transfer-encoding") return
150
+ headers[k] = v
151
+ })
152
+ const etag = `W/"${hash(body)}"`
153
+ return { status: resp.status, headers, body, etag, storedAt: Date.now() }
154
+ }
155
+
156
+ const entryToResponse = (e: CachedEntry, opts: HttpCacheOptions): Response => {
157
+ const headers: Record<string, string> = { ...e.headers, etag: e.etag }
158
+ if (opts.setCacheControl !== false && !headers["cache-control"]) {
159
+ headers["cache-control"] = `public, max-age=${opts.ttl ?? 60}`
160
+ }
161
+ headers["x-tekir-cache"] = "HIT"
162
+ return new Response(e.body, { status: e.status, headers })
163
+ }
164
+
165
+ export function cache(opts: HttpCacheOptions = {}) {
166
+ const ttl = opts.ttl ?? 60
167
+ const methods = new Set((opts.methods ?? SAFE_METHODS).map((m) => m.toUpperCase()))
168
+ const vary = opts.vary ?? []
169
+ const prefix = opts.prefix ?? "http:"
170
+ const buildKey = opts.key ?? ((ctx: HttpCacheCtx) => defaultKey(ctx, vary))
171
+ const directStore = resolveStore(opts.store)
172
+
173
+ return async function cacheMiddleware(ctx: any, next: () => Promise<void>) {
174
+ const req = ctx.request
175
+ if (!req || !methods.has(String(req.method ?? "GET").toUpperCase())) {
176
+ await next()
177
+ return
178
+ }
179
+
180
+ if (opts.skip && (await opts.skip(ctx))) {
181
+ await next()
182
+ return
183
+ }
184
+
185
+ const cacheControl = req.headers?.get?.("cache-control") ?? ""
186
+ if (cacheControl.includes("no-store")) {
187
+ await next()
188
+ return
189
+ }
190
+
191
+ // Resolve store: option > module-level default (set by CacheProvider).
192
+ // No store anywhere → middleware acts as a transparent no-op so the
193
+ // route still works without a registered cache backend.
194
+ let store: CacheStore | null = directStore
195
+ if (!store) store = resolveStore(_defaultStore)
196
+ if (!store) {
197
+ await next()
198
+ return
199
+ }
200
+
201
+ const key = prefix + buildKey(ctx)
202
+ const cached = await store.get<CachedEntry>(key)
203
+
204
+ // Conditional request: client sent If-None-Match
205
+ const ifNoneMatch = req.headers?.get?.("if-none-match") ?? ""
206
+ if (cached) {
207
+ if (ifNoneMatch && ifNoneMatch === cached.etag) {
208
+ ctx.$result = new Response(null, {
209
+ status: 304,
210
+ headers: { etag: cached.etag, "x-tekir-cache": "REVALIDATED" },
211
+ })
212
+ return
213
+ }
214
+ ctx.$result = entryToResponse(cached, opts)
215
+ return
216
+ }
217
+
218
+ // Miss: run the handler chain, capture, store.
219
+ await next()
220
+ const result = ctx.$result
221
+ if (!(result instanceof Response)) return
222
+ if (result.status >= 500 || result.status === 204) return // don't cache errors / empty
223
+ if (cacheControl.includes("no-cache")) return
224
+ const respCacheControl = result.headers.get("cache-control") ?? ""
225
+ if (respCacheControl.includes("private") || respCacheControl.includes("no-store")) return
226
+
227
+ const entry = await responseToEntry(result)
228
+ await store.set(key, entry, ttl)
229
+
230
+ // Re-emit with x-tekir-cache: MISS so the client can see it
231
+ const out: Record<string, string> = {}
232
+ result.headers.forEach((v, k) => (out[k] = v))
233
+ out["etag"] = entry.etag
234
+ if (opts.setCacheControl !== false && !out["cache-control"]) {
235
+ out["cache-control"] = `public, max-age=${ttl}`
236
+ }
237
+ out["x-tekir-cache"] = "MISS"
238
+ ctx.$result = new Response(entry.body, { status: result.status, headers: out })
239
+ }
240
+ }
package/src/index.ts CHANGED
@@ -4,3 +4,5 @@ export { RedisCacheStore } from './stores/redis'
4
4
  export { DatabaseCacheStore } from './stores/database'
5
5
  export { Cache, createCache } from './cache'
6
6
  export { CacheProvider } from './provider'
7
+ export { cache, setDefaultCacheStore, getDefaultCacheStore } from './http-cache'
8
+ export type { HttpCacheOptions, HttpCacheCtx } from './http-cache'
package/src/provider.ts CHANGED
@@ -84,10 +84,17 @@ export class CacheProvider {
84
84
  stores.memory = new MemoryCacheStore()
85
85
  }
86
86
 
87
- app.instance('cache', new Cache({
87
+ const cacheInstance = new Cache({
88
88
  stores,
89
89
  ttl: config('cache.ttl', 60) as number,
90
90
  default: config('cache.default', Object.keys(stores)[0]) as string,
91
- }))
91
+ })
92
+ app.instance('cache', cacheInstance)
93
+
94
+ // Wire the cache() HTTP middleware so route-level `cache({ ttl: 60 })`
95
+ // works without an explicit `store` option once this provider is
96
+ // registered.
97
+ const { setDefaultCacheStore } = await import('./http-cache')
98
+ setDefaultCacheStore(cacheInstance)
92
99
  }
93
100
  }