@mmstack/resource 20.2.7 → 20.2.9
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 +8 -0
- package/fesm2022/mmstack-resource.mjs +295 -32
- package/fesm2022/mmstack-resource.mjs.map +1 -1
- package/index.d.ts +49 -3
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -37,6 +37,14 @@ export const appConfig: ApplicationConfig = {
|
|
|
37
37
|
providers: [
|
|
38
38
|
// ..other providers
|
|
39
39
|
provideQueryCache(),
|
|
40
|
+
|
|
41
|
+
// --- Example of a more advanced setup ---
|
|
42
|
+
// provideQueryCache({
|
|
43
|
+
// persist: true, // Enable IndexedDB persistence
|
|
44
|
+
// version: 1, // Version for the cache schema
|
|
45
|
+
// syncTabs: true // enable BroadcastChannel
|
|
46
|
+
// }),
|
|
47
|
+
|
|
40
48
|
provideHttpClient(withInterceptors([createCacheInterceptor(), createDedupeRequestsInterceptor()])),
|
|
41
49
|
],
|
|
42
50
|
};
|
|
@@ -1,11 +1,106 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { isDevMode, inject, PLATFORM_ID, untracked, computed, InjectionToken, signal, effect, Injector, linkedSignal, DestroyRef } from '@angular/core';
|
|
2
2
|
import { takeUntilDestroyed, toObservable } from '@angular/core/rxjs-interop';
|
|
3
3
|
import { of, tap, map, finalize, shareReplay, interval, firstValueFrom, catchError, combineLatestWith, filter } from 'rxjs';
|
|
4
|
-
import {
|
|
4
|
+
import { HttpHeaders, HttpResponse, HttpContextToken, HttpContext, HttpParams, httpResource, HttpClient } from '@angular/common/http';
|
|
5
5
|
import { mutable, toWritable } from '@mmstack/primitives';
|
|
6
6
|
import { v7 } from 'uuid';
|
|
7
|
+
import { isPlatformBrowser } from '@angular/common';
|
|
7
8
|
import { keys, hash, entries } from '@mmstack/object';
|
|
8
9
|
|
|
10
|
+
function createNoopDB() {
|
|
11
|
+
return {
|
|
12
|
+
getAll: async () => [],
|
|
13
|
+
store: async () => {
|
|
14
|
+
// noop
|
|
15
|
+
},
|
|
16
|
+
remove: async () => {
|
|
17
|
+
// noop
|
|
18
|
+
},
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
function toCacheDB(db, storeName) {
|
|
22
|
+
const getAll = async () => {
|
|
23
|
+
const now = Date.now();
|
|
24
|
+
return new Promise((res, rej) => {
|
|
25
|
+
const transaction = db.transaction(storeName, 'readonly');
|
|
26
|
+
const store = transaction.objectStore(storeName);
|
|
27
|
+
const request = store.getAll();
|
|
28
|
+
request.onsuccess = () => res(request.result);
|
|
29
|
+
request.onerror = () => rej(request.error);
|
|
30
|
+
})
|
|
31
|
+
.then((entries) => entries.filter((e) => e.expiresAt > now))
|
|
32
|
+
.catch((err) => {
|
|
33
|
+
if (isDevMode())
|
|
34
|
+
console.error('Error getting all items from cache DB:', err);
|
|
35
|
+
return [];
|
|
36
|
+
});
|
|
37
|
+
};
|
|
38
|
+
const store = (value) => {
|
|
39
|
+
return new Promise((res, rej) => {
|
|
40
|
+
const transaction = db.transaction(storeName, 'readwrite');
|
|
41
|
+
const store = transaction.objectStore(storeName);
|
|
42
|
+
store.put(value);
|
|
43
|
+
transaction.oncomplete = () => res();
|
|
44
|
+
transaction.onerror = () => rej(transaction.error);
|
|
45
|
+
}).catch((err) => {
|
|
46
|
+
if (isDevMode())
|
|
47
|
+
console.error('Error storing item in cache DB:', err);
|
|
48
|
+
});
|
|
49
|
+
};
|
|
50
|
+
const remove = (key) => {
|
|
51
|
+
return new Promise((res, rej) => {
|
|
52
|
+
const transaction = db.transaction(storeName, 'readwrite');
|
|
53
|
+
const store = transaction.objectStore(storeName);
|
|
54
|
+
store.delete(key);
|
|
55
|
+
transaction.oncomplete = () => res();
|
|
56
|
+
transaction.onerror = () => rej(transaction.error);
|
|
57
|
+
}).catch((err) => {
|
|
58
|
+
if (isDevMode())
|
|
59
|
+
console.error('Error removing item from cache DB:', err);
|
|
60
|
+
});
|
|
61
|
+
};
|
|
62
|
+
return {
|
|
63
|
+
getAll,
|
|
64
|
+
store,
|
|
65
|
+
remove,
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
function createSingleStoreDB(name, getStoreName, version = 1) {
|
|
69
|
+
const isBrowser = isPlatformBrowser(inject(PLATFORM_ID));
|
|
70
|
+
const storeName = getStoreName(version);
|
|
71
|
+
if (!isBrowser)
|
|
72
|
+
return Promise.resolve(createNoopDB());
|
|
73
|
+
return new Promise((res, rej) => {
|
|
74
|
+
if (version < 1)
|
|
75
|
+
rej(new Error('Version must be 1 or greater'));
|
|
76
|
+
const req = indexedDB.open(name, version);
|
|
77
|
+
req.onupgradeneeded = (event) => {
|
|
78
|
+
const db = req.result;
|
|
79
|
+
const oldVersion = event.oldVersion;
|
|
80
|
+
db.createObjectStore(storeName, { keyPath: 'key' });
|
|
81
|
+
if (oldVersion > 0) {
|
|
82
|
+
db.deleteObjectStore(getStoreName(oldVersion));
|
|
83
|
+
}
|
|
84
|
+
};
|
|
85
|
+
req.onerror = () => {
|
|
86
|
+
rej(req.error);
|
|
87
|
+
};
|
|
88
|
+
req.onsuccess = () => res(req.result);
|
|
89
|
+
})
|
|
90
|
+
.then((db) => toCacheDB(db, storeName))
|
|
91
|
+
.catch((err) => {
|
|
92
|
+
if (isDevMode())
|
|
93
|
+
console.error('Error creating query DB:', err);
|
|
94
|
+
return createNoopDB();
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function isSyncMessage(msg) {
|
|
99
|
+
return (typeof msg === 'object' &&
|
|
100
|
+
msg !== null &&
|
|
101
|
+
'type' in msg &&
|
|
102
|
+
msg.type === 'cache-sync-message');
|
|
103
|
+
}
|
|
9
104
|
const ONE_DAY = 1000 * 60 * 60 * 24;
|
|
10
105
|
const ONE_HOUR = 1000 * 60 * 60;
|
|
11
106
|
const DEFAULT_CLEANUP_OPT = {
|
|
@@ -21,8 +116,18 @@ const DEFAULT_CLEANUP_OPT = {
|
|
|
21
116
|
class Cache {
|
|
22
117
|
ttl;
|
|
23
118
|
staleTime;
|
|
119
|
+
db;
|
|
24
120
|
internal = mutable(new Map());
|
|
25
121
|
cleanupOpt;
|
|
122
|
+
id = v7();
|
|
123
|
+
/**
|
|
124
|
+
* Destroys the cache instance, cleaning up any resources used by the cache.
|
|
125
|
+
* This method is called automatically when the cache instance is garbage collected.
|
|
126
|
+
*/
|
|
127
|
+
destroy;
|
|
128
|
+
broadcast = () => {
|
|
129
|
+
// noop
|
|
130
|
+
};
|
|
26
131
|
/**
|
|
27
132
|
* Creates a new `Cache` instance.
|
|
28
133
|
*
|
|
@@ -31,14 +136,17 @@ class Cache {
|
|
|
31
136
|
* stale but can still be used while revalidation occurs in the background. Defaults to 1 hour.
|
|
32
137
|
* @param cleanupOpt - Options for configuring the cache cleanup strategy. Defaults to LRU with a
|
|
33
138
|
* `maxSize` of 200 and a `checkInterval` of one hour.
|
|
139
|
+
* @param syncTabs - If provided, the cache will use the options a BroadcastChannel to send updates between tabs.
|
|
140
|
+
* Defaults to `undefined`, meaning no synchronization across tabs.
|
|
34
141
|
*/
|
|
35
142
|
constructor(ttl = ONE_DAY, staleTime = ONE_HOUR, cleanupOpt = {
|
|
36
143
|
type: 'lru',
|
|
37
144
|
maxSize: 1000,
|
|
38
145
|
checkInterval: ONE_HOUR,
|
|
39
|
-
}) {
|
|
146
|
+
}, syncTabs, db = Promise.resolve(createNoopDB())) {
|
|
40
147
|
this.ttl = ttl;
|
|
41
148
|
this.staleTime = staleTime;
|
|
149
|
+
this.db = db;
|
|
42
150
|
this.cleanupOpt = {
|
|
43
151
|
...DEFAULT_CLEANUP_OPT,
|
|
44
152
|
...cleanupOpt,
|
|
@@ -49,14 +157,82 @@ class Cache {
|
|
|
49
157
|
const cleanupInterval = setInterval(() => {
|
|
50
158
|
this.cleanup();
|
|
51
159
|
}, cleanupOpt.checkInterval);
|
|
52
|
-
|
|
160
|
+
let destroySyncTabs = () => {
|
|
161
|
+
// noop
|
|
162
|
+
};
|
|
163
|
+
if (syncTabs) {
|
|
164
|
+
const channel = new BroadcastChannel(syncTabs.id);
|
|
165
|
+
this.broadcast = (msg) => {
|
|
166
|
+
if (msg.action === 'invalidate')
|
|
167
|
+
return channel.postMessage({
|
|
168
|
+
action: 'invalidate',
|
|
169
|
+
entry: { key: msg.entry.key },
|
|
170
|
+
cacheId: this.id,
|
|
171
|
+
type: 'cache-sync-message',
|
|
172
|
+
});
|
|
173
|
+
return channel.postMessage({
|
|
174
|
+
...msg,
|
|
175
|
+
entry: {
|
|
176
|
+
...msg.entry,
|
|
177
|
+
value: syncTabs.serialize(msg.entry.value),
|
|
178
|
+
},
|
|
179
|
+
cacheId: this.id,
|
|
180
|
+
type: 'cache-sync-message',
|
|
181
|
+
});
|
|
182
|
+
};
|
|
183
|
+
channel.onmessage = (event) => {
|
|
184
|
+
const msg = event.data;
|
|
185
|
+
if (!isSyncMessage(msg))
|
|
186
|
+
return;
|
|
187
|
+
if (msg.cacheId === this.id)
|
|
188
|
+
return; // ignore messages from this cache
|
|
189
|
+
if (msg.action === 'store') {
|
|
190
|
+
const value = syncTabs.deserialize(msg.entry.value);
|
|
191
|
+
if (value === null)
|
|
192
|
+
return;
|
|
193
|
+
this.storeInternal(msg.entry.key, value, msg.entry.stale - msg.entry.updated, msg.entry.expiresAt - msg.entry.updated, true, false);
|
|
194
|
+
}
|
|
195
|
+
else if (msg.action === 'invalidate') {
|
|
196
|
+
this.invalidateInternal(msg.entry.key, true);
|
|
197
|
+
}
|
|
198
|
+
};
|
|
199
|
+
destroySyncTabs = () => {
|
|
200
|
+
channel.close();
|
|
201
|
+
};
|
|
202
|
+
}
|
|
203
|
+
let destroyed = false;
|
|
204
|
+
const destroy = () => {
|
|
205
|
+
if (destroyed)
|
|
206
|
+
return;
|
|
207
|
+
destroyed = true;
|
|
208
|
+
clearInterval(cleanupInterval);
|
|
209
|
+
destroySyncTabs();
|
|
210
|
+
};
|
|
211
|
+
this.db
|
|
212
|
+
.then(async (db) => {
|
|
213
|
+
if (destroyed)
|
|
214
|
+
return [];
|
|
215
|
+
return db.getAll();
|
|
216
|
+
})
|
|
217
|
+
.then((entries) => {
|
|
218
|
+
if (destroyed)
|
|
219
|
+
return;
|
|
220
|
+
// load entries into the cache
|
|
221
|
+
const current = untracked(this.internal);
|
|
222
|
+
entries.forEach((entry) => {
|
|
223
|
+
if (current.has(entry.key))
|
|
224
|
+
return;
|
|
225
|
+
this.storeInternal(entry.key, entry.value, entry.stale - entry.updated, entry.expiresAt - entry.updated, true);
|
|
226
|
+
});
|
|
227
|
+
});
|
|
228
|
+
this.destroy = destroy;
|
|
53
229
|
// cleanup if object is garbage collected, this is because the cache can be quite large from a memory standpoint & we dont want all that floating garbage
|
|
54
230
|
const registry = new FinalizationRegistry((id) => {
|
|
55
|
-
if (id ===
|
|
56
|
-
|
|
231
|
+
if (id === this.id) {
|
|
232
|
+
destroy();
|
|
57
233
|
}
|
|
58
234
|
});
|
|
59
|
-
registry.register(this,
|
|
235
|
+
registry.register(this, this.id);
|
|
60
236
|
}
|
|
61
237
|
/** @internal */
|
|
62
238
|
getInternal(key) {
|
|
@@ -115,7 +291,10 @@ class Cache {
|
|
|
115
291
|
* @param staleTime - (Optional) The stale time for this entry, in milliseconds. Overrides the default `staleTime`.
|
|
116
292
|
* @param ttl - (Optional) The TTL for this entry, in milliseconds. Overrides the default `ttl`.
|
|
117
293
|
*/
|
|
118
|
-
store(key, value, staleTime = this.staleTime, ttl = this.ttl) {
|
|
294
|
+
store(key, value, staleTime = this.staleTime, ttl = this.ttl, persist = false) {
|
|
295
|
+
this.storeInternal(key, value, staleTime, ttl, false, persist);
|
|
296
|
+
}
|
|
297
|
+
storeInternal(key, value, staleTime = this.staleTime, ttl = this.ttl, fromSync = false, persist = false) {
|
|
119
298
|
const entry = this.getUntracked(key);
|
|
120
299
|
if (entry) {
|
|
121
300
|
clearTimeout(entry.timeout); // stop invalidation
|
|
@@ -125,18 +304,30 @@ class Cache {
|
|
|
125
304
|
if (ttl < staleTime)
|
|
126
305
|
staleTime = ttl;
|
|
127
306
|
const now = Date.now();
|
|
307
|
+
const next = {
|
|
308
|
+
value,
|
|
309
|
+
created: entry?.created ?? now,
|
|
310
|
+
updated: now,
|
|
311
|
+
useCount: prevCount + 1,
|
|
312
|
+
stale: now + staleTime,
|
|
313
|
+
expiresAt: now + ttl,
|
|
314
|
+
key,
|
|
315
|
+
};
|
|
128
316
|
this.internal.mutate((map) => {
|
|
129
317
|
map.set(key, {
|
|
130
|
-
|
|
131
|
-
created: entry?.created ?? now,
|
|
132
|
-
useCount: prevCount + 1,
|
|
133
|
-
stale: now + staleTime,
|
|
134
|
-
expiresAt: now + ttl,
|
|
318
|
+
...next,
|
|
135
319
|
timeout: setTimeout(() => this.invalidate(key), ttl),
|
|
136
|
-
key,
|
|
137
320
|
});
|
|
138
321
|
return map;
|
|
139
322
|
});
|
|
323
|
+
if (!fromSync) {
|
|
324
|
+
if (persist)
|
|
325
|
+
this.db.then((db) => db.store(next));
|
|
326
|
+
this.broadcast({
|
|
327
|
+
action: 'store',
|
|
328
|
+
entry: next,
|
|
329
|
+
});
|
|
330
|
+
}
|
|
140
331
|
}
|
|
141
332
|
/**
|
|
142
333
|
* Invalidates (removes) a cache entry.
|
|
@@ -144,6 +335,9 @@ class Cache {
|
|
|
144
335
|
* @param key - The key of the entry to invalidate.
|
|
145
336
|
*/
|
|
146
337
|
invalidate(key) {
|
|
338
|
+
this.invalidateInternal(key);
|
|
339
|
+
}
|
|
340
|
+
invalidateInternal(key, fromSync = false) {
|
|
147
341
|
const entry = this.getUntracked(key);
|
|
148
342
|
if (!entry)
|
|
149
343
|
return;
|
|
@@ -152,6 +346,10 @@ class Cache {
|
|
|
152
346
|
map.delete(key);
|
|
153
347
|
return map;
|
|
154
348
|
});
|
|
349
|
+
if (!fromSync) {
|
|
350
|
+
this.db.then((db) => db.remove(key));
|
|
351
|
+
this.broadcast({ action: 'invalidate', entry: { key } });
|
|
352
|
+
}
|
|
155
353
|
}
|
|
156
354
|
/** @internal */
|
|
157
355
|
cleanup() {
|
|
@@ -198,9 +396,80 @@ const CLIENT_CACHE_TOKEN = new InjectionToken('INTERNAL_CLIENT_CACHE');
|
|
|
198
396
|
* };
|
|
199
397
|
*/
|
|
200
398
|
function provideQueryCache(opt) {
|
|
399
|
+
const serialize = (value) => {
|
|
400
|
+
const headersRecord = {};
|
|
401
|
+
const headerKeys = value.headers.keys();
|
|
402
|
+
headerKeys.forEach((key) => {
|
|
403
|
+
const values = value.headers.getAll(key);
|
|
404
|
+
if (!values)
|
|
405
|
+
return;
|
|
406
|
+
headersRecord[key] = values;
|
|
407
|
+
});
|
|
408
|
+
return JSON.stringify({
|
|
409
|
+
body: value.body,
|
|
410
|
+
status: value.status,
|
|
411
|
+
statusText: value.statusText,
|
|
412
|
+
headers: headerKeys.length > 0 ? headersRecord : undefined,
|
|
413
|
+
url: value.url,
|
|
414
|
+
});
|
|
415
|
+
};
|
|
416
|
+
const deserialize = (value) => {
|
|
417
|
+
try {
|
|
418
|
+
const parsed = JSON.parse(value);
|
|
419
|
+
if (!parsed || typeof parsed !== 'object' || !('body' in parsed))
|
|
420
|
+
throw new Error('Invalid cache entry format');
|
|
421
|
+
const headers = parsed.headers
|
|
422
|
+
? new HttpHeaders(parsed.headers)
|
|
423
|
+
: undefined;
|
|
424
|
+
return new HttpResponse({
|
|
425
|
+
body: parsed.body,
|
|
426
|
+
status: parsed.status,
|
|
427
|
+
statusText: parsed.statusText,
|
|
428
|
+
headers: headers,
|
|
429
|
+
url: parsed.url,
|
|
430
|
+
});
|
|
431
|
+
}
|
|
432
|
+
catch (err) {
|
|
433
|
+
if (isDevMode())
|
|
434
|
+
console.error('Failed to deserialize cache entry:', err);
|
|
435
|
+
return null;
|
|
436
|
+
}
|
|
437
|
+
};
|
|
438
|
+
const syncTabsOpt = opt?.syncTabs
|
|
439
|
+
? {
|
|
440
|
+
id: 'mmstack-query-cache-sync',
|
|
441
|
+
serialize,
|
|
442
|
+
deserialize,
|
|
443
|
+
}
|
|
444
|
+
: undefined;
|
|
445
|
+
let db = opt?.persist === false
|
|
446
|
+
? undefined
|
|
447
|
+
: createSingleStoreDB('mmstack-query-cache-db', (version) => `query-store_v${version}`, opt?.version).then((db) => {
|
|
448
|
+
return {
|
|
449
|
+
getAll: () => {
|
|
450
|
+
return db.getAll().then((entries) => {
|
|
451
|
+
return entries
|
|
452
|
+
.map((entry) => {
|
|
453
|
+
const value = deserialize(entry.value);
|
|
454
|
+
if (value === null)
|
|
455
|
+
return null;
|
|
456
|
+
return {
|
|
457
|
+
...entry,
|
|
458
|
+
value,
|
|
459
|
+
};
|
|
460
|
+
})
|
|
461
|
+
.filter((e) => e !== null);
|
|
462
|
+
});
|
|
463
|
+
},
|
|
464
|
+
store: (entry) => {
|
|
465
|
+
return db.store({ ...entry, value: serialize(entry.value) });
|
|
466
|
+
},
|
|
467
|
+
remove: db.remove,
|
|
468
|
+
};
|
|
469
|
+
});
|
|
201
470
|
return {
|
|
202
471
|
provide: CLIENT_CACHE_TOKEN,
|
|
203
|
-
useValue: new Cache(opt?.ttl, opt?.staleTime, opt?.cleanup),
|
|
472
|
+
useValue: new Cache(opt?.ttl, opt?.staleTime, opt?.cleanup, syncTabsOpt, db),
|
|
204
473
|
};
|
|
205
474
|
}
|
|
206
475
|
class NoopCache extends Cache {
|
|
@@ -256,17 +525,7 @@ function setCacheContext(ctx = new HttpContext(), opt) {
|
|
|
256
525
|
function getCacheContext(ctx) {
|
|
257
526
|
return ctx.get(CACHE_CONTEXT);
|
|
258
527
|
}
|
|
259
|
-
function parseCacheControlHeader(req
|
|
260
|
-
if (ignoreCacheControl) {
|
|
261
|
-
return {
|
|
262
|
-
noStore: false,
|
|
263
|
-
noCache: false,
|
|
264
|
-
mustRevalidate: false,
|
|
265
|
-
immutable: false,
|
|
266
|
-
maxAge: null,
|
|
267
|
-
staleWhileRevalidate: null,
|
|
268
|
-
};
|
|
269
|
-
}
|
|
528
|
+
function parseCacheControlHeader(req) {
|
|
270
529
|
const header = req.headers.get('Cache-Control');
|
|
271
530
|
let sMaxAge = null;
|
|
272
531
|
const directives = {
|
|
@@ -421,10 +680,12 @@ function createCacheInterceptor(allowedMethods = ['GET', 'HEAD', 'OPTIONS']) {
|
|
|
421
680
|
}
|
|
422
681
|
return next(req).pipe(tap((event) => {
|
|
423
682
|
if (event instanceof HttpResponse && event.ok) {
|
|
424
|
-
const cacheControl = parseCacheControlHeader(event
|
|
425
|
-
if (cacheControl.noStore)
|
|
683
|
+
const cacheControl = parseCacheControlHeader(event);
|
|
684
|
+
if (cacheControl.noStore && !opt.ignoreCacheControl)
|
|
426
685
|
return;
|
|
427
|
-
const { staleTime, ttl } =
|
|
686
|
+
const { staleTime, ttl } = opt.ignoreCacheControl
|
|
687
|
+
? opt
|
|
688
|
+
: resolveTimings(cacheControl, opt.staleTime, opt.ttl);
|
|
428
689
|
if (opt.ttl === 0)
|
|
429
690
|
return; // no point
|
|
430
691
|
const parsedResponse = opt.parse
|
|
@@ -436,7 +697,7 @@ function createCacheInterceptor(allowedMethods = ['GET', 'HEAD', 'OPTIONS']) {
|
|
|
436
697
|
url: event.url ?? undefined,
|
|
437
698
|
})
|
|
438
699
|
: event;
|
|
439
|
-
cache.store(key, parsedResponse, staleTime, ttl);
|
|
700
|
+
cache.store(key, parsedResponse, staleTime, ttl, opt.persist);
|
|
440
701
|
}
|
|
441
702
|
}), map((event) => {
|
|
442
703
|
// handle 304 responses due to eTag/last-modified
|
|
@@ -989,7 +1250,7 @@ function queryResource(request, options) {
|
|
|
989
1250
|
options.cache.bustBrowserCache === true;
|
|
990
1251
|
const ignoreCacheControl = typeof options?.cache === 'object' &&
|
|
991
1252
|
options.cache.ignoreCacheControl === true;
|
|
992
|
-
const
|
|
1253
|
+
const persist = typeof options?.cache === 'object' && options.cache.persist === true;
|
|
993
1254
|
const cachedRequest = options?.cache
|
|
994
1255
|
? computed(() => {
|
|
995
1256
|
const r = stableRequest();
|
|
@@ -1003,6 +1264,7 @@ function queryResource(request, options) {
|
|
|
1003
1264
|
key: cacheKey() ?? hashFn(r),
|
|
1004
1265
|
bustBrowserCache,
|
|
1005
1266
|
ignoreCacheControl,
|
|
1267
|
+
persist,
|
|
1006
1268
|
}),
|
|
1007
1269
|
};
|
|
1008
1270
|
})
|
|
@@ -1074,7 +1336,7 @@ function queryResource(request, options) {
|
|
|
1074
1336
|
body: value,
|
|
1075
1337
|
status: 200,
|
|
1076
1338
|
statusText: 'OK',
|
|
1077
|
-
}));
|
|
1339
|
+
}), staleTime, ttl, persist);
|
|
1078
1340
|
};
|
|
1079
1341
|
const update = (updater) => {
|
|
1080
1342
|
set(updater(untracked(resource.value)));
|
|
@@ -1133,6 +1395,7 @@ function queryResource(request, options) {
|
|
|
1133
1395
|
}),
|
|
1134
1396
|
bustBrowserCache,
|
|
1135
1397
|
ignoreCacheControl,
|
|
1398
|
+
persist,
|
|
1136
1399
|
}),
|
|
1137
1400
|
headers: prefetchRequest.headers,
|
|
1138
1401
|
observe: 'response',
|