zova-module-a-model 5.1.37 → 5.1.39

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.
Files changed (65) hide show
  1. package/dist/.metadata/index.d.ts +7 -7
  2. package/dist/.metadata/index.d.ts.map +1 -1
  3. package/dist/.metadata/this.d.ts +1 -1
  4. package/dist/bean/bean.model/bean.model.cookie.d.ts +2 -2
  5. package/dist/bean/bean.model/bean.model.first.d.ts +1 -1
  6. package/dist/bean/bean.model/bean.model.last.d.ts +3 -3
  7. package/dist/bean/bean.model/bean.model.local.d.ts +1 -1
  8. package/dist/bean/bean.model/bean.model.persister.d.ts +15 -6
  9. package/dist/bean/bean.model/bean.model.persister.d.ts.map +1 -1
  10. package/dist/bean/bean.model/bean.model.query.d.ts +2 -2
  11. package/dist/bean/bean.model/bean.model.query.d.ts.map +1 -1
  12. package/dist/bean/bean.model/bean.model.useMutation.d.ts +2 -2
  13. package/dist/bean/bean.model/bean.model.useQuery.d.ts +3 -3
  14. package/dist/bean/bean.model/bean.model.useQuery.d.ts.map +1 -1
  15. package/dist/bean/bean.model/bean.model.useState.d.ts +3 -3
  16. package/dist/bean/bean.model/bean.model.useState.d.ts.map +1 -1
  17. package/dist/bean/bean.model/bean.model.useStateGeneral.d.ts +3 -3
  18. package/dist/bean/bean.modelBase.d.ts +1 -1
  19. package/dist/common/cookieWrapper.d.ts +1 -1
  20. package/dist/config/config.d.ts +1 -1
  21. package/dist/index.d.ts +3 -3
  22. package/dist/index.js +501 -68
  23. package/dist/index.js.map +1 -1
  24. package/dist/lib/index.d.ts +3 -2
  25. package/dist/lib/index.d.ts.map +1 -1
  26. package/dist/lib/model.d.ts +1 -1
  27. package/dist/lib/modelPersistence.d.ts +39 -0
  28. package/dist/lib/modelPersistence.d.ts.map +1 -0
  29. package/dist/lib/persistenceQueue.d.ts +14 -0
  30. package/dist/lib/persistenceQueue.d.ts.map +1 -0
  31. package/dist/lib/queryRefetch.d.ts +1 -1
  32. package/dist/lib/utils.d.ts +1 -1
  33. package/dist/monkey.d.ts.map +1 -1
  34. package/dist/service/storage.d.ts +15 -0
  35. package/dist/service/storage.d.ts.map +1 -1
  36. package/dist/types/index.d.ts +2 -2
  37. package/dist/types/query.d.ts +2 -0
  38. package/dist/types/query.d.ts.map +1 -1
  39. package/package.json +2 -2
  40. package/src/.metadata/index.ts +7 -7
  41. package/src/.metadata/this.ts +1 -1
  42. package/src/bean/bean.model/bean.model.cookie.ts +2 -2
  43. package/src/bean/bean.model/bean.model.first.ts +1 -1
  44. package/src/bean/bean.model/bean.model.last.ts +4 -4
  45. package/src/bean/bean.model/bean.model.local.ts +1 -1
  46. package/src/bean/bean.model/bean.model.persister.ts +336 -92
  47. package/src/bean/bean.model/bean.model.query.ts +13 -8
  48. package/src/bean/bean.model/bean.model.useMutation.ts +2 -2
  49. package/src/bean/bean.model/bean.model.useQuery.ts +8 -6
  50. package/src/bean/bean.model/bean.model.useState.ts +20 -4
  51. package/src/bean/bean.model/bean.model.useStateGeneral.ts +4 -4
  52. package/src/bean/bean.modelBase.ts +1 -1
  53. package/src/common/cookieWrapper.ts +3 -3
  54. package/src/config/config.ts +1 -1
  55. package/src/index.ts +3 -3
  56. package/src/lib/index.ts +3 -2
  57. package/src/lib/model.ts +1 -1
  58. package/src/lib/modelPersistence.ts +204 -0
  59. package/src/lib/persistenceQueue.ts +50 -0
  60. package/src/lib/queryRefetch.ts +1 -1
  61. package/src/lib/utils.ts +1 -1
  62. package/src/monkey.ts +6 -1
  63. package/src/service/storage.ts +47 -2
  64. package/src/types/index.ts +2 -2
  65. package/src/types/query.ts +3 -0
package/dist/index.js CHANGED
@@ -2,8 +2,9 @@ import { BeanBase, BeanInfo, BeanScopeBase, BeanSimple, SymbolBeanFullName, Virt
2
2
  import { Bean, Scope, Service } from "zova-module-a-bean";
3
3
  import { QueryCache, QueryClient, VueQueryPlugin, defaultShouldDehydrateQuery, dehydrate, hashKey, hydrate, useMutation, useQuery, useQueryClient } from "@tanstack/vue-query";
4
4
  import localforage from "localforage";
5
+ import { CancelledError, notifyManager } from "@tanstack/query-core";
5
6
  import { experimental_createQueryPersister } from "@tanstack/query-persist-client-core";
6
- import { markRaw } from "vue";
7
+ import { markRaw, shallowRef } from "vue";
7
8
  //#region src/lib/queryRefetch.ts
8
9
  function createQueryRefetch(refetch, getQuery) {
9
10
  return async (refetchOptions) => {
@@ -26,6 +27,56 @@ function resolveMaxAgeTime(maxAge, query) {
26
27
  return typeof maxAge === "function" ? maxAge(query) : maxAge;
27
28
  }
28
29
  //#endregion
30
+ //#region src/lib/persistenceQueue.ts
31
+ var PersistenceQueue = class {
32
+ constructor() {
33
+ this._tail = Promise.resolve();
34
+ this._sequence = 0;
35
+ this._failure = void 0;
36
+ this._generation = 0;
37
+ this._keyGenerations = /* @__PURE__ */ new Map();
38
+ }
39
+ generation(key) {
40
+ return `${this._generation}:${this._keyGenerations.get(key) ?? 0}`;
41
+ }
42
+ invalidate(key) {
43
+ this._keyGenerations.set(key, (this._keyGenerations.get(key) ?? 0) + 1);
44
+ }
45
+ invalidateAll() {
46
+ this._generation++;
47
+ this._keyGenerations.clear();
48
+ }
49
+ enqueue(operation) {
50
+ const sequence = ++this._sequence;
51
+ const result = this._tail.then(operation);
52
+ this._tail = result.catch((error) => {
53
+ this._failure ??= {
54
+ sequence,
55
+ error
56
+ };
57
+ });
58
+ return result;
59
+ }
60
+ async flush() {
61
+ const sequence = this._sequence;
62
+ await this._tail;
63
+ if (this._failure && this._failure.sequence <= sequence) {
64
+ const { error } = this._failure;
65
+ this._failure = void 0;
66
+ throw error;
67
+ }
68
+ }
69
+ };
70
+ var queues = /* @__PURE__ */ new WeakMap();
71
+ function getPersistenceQueue(storage) {
72
+ let queue = queues.get(storage);
73
+ if (!queue) {
74
+ queue = new PersistenceQueue();
75
+ queues.set(storage, queue);
76
+ }
77
+ return queue;
78
+ }
79
+ //#endregion
29
80
  //#region src/.metadata/this.ts
30
81
  var __ThisModule__ = "a-model";
31
82
  //#endregion
@@ -57,6 +108,149 @@ var CookieWrapper = class extends BeanSimple {
57
108
  }
58
109
  };
59
110
  //#endregion
111
+ //#region src/lib/modelPersistence.ts
112
+ var normalize = (value) => value == null ? null : String(value);
113
+ function getModelPersistenceStorageKey(appName, scope, logicalKey) {
114
+ return JSON.stringify([
115
+ "zova-model-db",
116
+ 1,
117
+ appName,
118
+ scope,
119
+ logicalKey
120
+ ]);
121
+ }
122
+ function isModelPersistenceDb(options) {
123
+ if (options === false) return false;
124
+ const persister = options && typeof options === "object" ? options : {};
125
+ return (persister.storage ?? (persister.sync ? "local" : "db")) === "db";
126
+ }
127
+ function emptyQueryState() {
128
+ return {
129
+ data: void 0,
130
+ dataUpdateCount: 0,
131
+ dataUpdatedAt: 0,
132
+ error: null,
133
+ errorUpdateCount: 0,
134
+ errorUpdatedAt: 0,
135
+ fetchFailureCount: 0,
136
+ fetchFailureReason: null,
137
+ fetchMeta: null,
138
+ isInvalidated: false,
139
+ status: "pending",
140
+ fetchStatus: "idle"
141
+ };
142
+ }
143
+ /** App-owned identity; independent of the module providing the current Passport. */
144
+ var ModelPersistenceContext = class {
145
+ constructor(_queryClient, siteId) {
146
+ this._queryClient = _queryClient;
147
+ this._epoch = 0;
148
+ this._version = shallowRef(0);
149
+ this._scope = void 0;
150
+ this._registered = false;
151
+ this._hydrated = /* @__PURE__ */ new Set();
152
+ this._listeners = /* @__PURE__ */ new Set();
153
+ markRaw(this);
154
+ this._scope = Object.freeze([
155
+ null,
156
+ normalize(siteId),
157
+ null
158
+ ]);
159
+ }
160
+ get epoch() {
161
+ this._version.value;
162
+ return this._epoch;
163
+ }
164
+ capture() {
165
+ return Object.freeze({
166
+ scope: this._scope,
167
+ epoch: this.epoch
168
+ });
169
+ }
170
+ current(token) {
171
+ return token.epoch === this.epoch && token.scope === this._scope;
172
+ }
173
+ markHydrated(queries) {
174
+ if (!this._registered) for (const query of queries) this._hydrated.add(query);
175
+ }
176
+ touch(query) {
177
+ if (query) this._hydrated.delete(query);
178
+ }
179
+ subscribe(listener) {
180
+ this._listeners.add(listener);
181
+ return () => this._listeners.delete(listener);
182
+ }
183
+ update(values, options = {}) {
184
+ const scope = Object.freeze([
185
+ normalize(values.instanceId),
186
+ normalize(values.siteId),
187
+ normalize(values.userId)
188
+ ]);
189
+ const adopt = !this._registered && options.initial === true;
190
+ this._registered = true;
191
+ if (scope.every((value, index) => value === this._scope[index])) {
192
+ this._hydrated.clear();
193
+ return;
194
+ }
195
+ const affected = this._queryClient.getQueryCache().getAll().filter((query) => isModelPersistenceDb(query.meta?.persister) && !(adopt && this._hydrated.has(query)));
196
+ this._scope = scope;
197
+ this._hydrated.clear();
198
+ this._invalidate(affected);
199
+ if (options.refetchActive === false) return;
200
+ const token = this.capture();
201
+ Promise.resolve().then(() => {
202
+ if (this.current(token)) return this._queryClient.refetchQueries({
203
+ type: "active",
204
+ predicate: (query) => affected.includes(query)
205
+ });
206
+ });
207
+ }
208
+ clear() {
209
+ this._hydrated.clear();
210
+ this._invalidate(this._queryClient.getQueryCache().getAll());
211
+ }
212
+ _invalidate(queries) {
213
+ this._epoch++;
214
+ for (const query of queries) query.cancel({
215
+ silent: true,
216
+ revert: false
217
+ });
218
+ notifyManager.batch(() => {
219
+ for (const query of queries) {
220
+ query.setOptions({
221
+ ...query.options,
222
+ initialData: void 0,
223
+ initialDataUpdatedAt: void 0
224
+ });
225
+ for (const observer of query.observers) observer.setOptions({
226
+ ...observer.options,
227
+ initialData: void 0,
228
+ initialDataUpdatedAt: void 0,
229
+ placeholderData: void 0
230
+ });
231
+ query.setState(emptyQueryState());
232
+ }
233
+ });
234
+ this._version.value++;
235
+ const token = this.capture();
236
+ for (const listener of this._listeners) listener(token);
237
+ }
238
+ /** Keep later observer option reapplication from reinstalling an old identity. */
239
+ guardOptions(options) {
240
+ if (!isModelPersistenceDb(options.meta?.persister)) return options;
241
+ const token = this.capture();
242
+ const initialData = options.initialData;
243
+ const initialDataUpdatedAt = options.initialDataUpdatedAt;
244
+ const placeholderData = options.placeholderData;
245
+ return {
246
+ ...options,
247
+ initialData: () => this.current(token) ? typeof initialData === "function" ? initialData() : initialData : void 0,
248
+ initialDataUpdatedAt: () => this.current(token) ? typeof initialDataUpdatedAt === "function" ? initialDataUpdatedAt() : initialDataUpdatedAt : void 0,
249
+ placeholderData: (...args) => this.current(token) ? typeof placeholderData === "function" ? placeholderData(...args) : placeholderData : void 0
250
+ };
251
+ }
252
+ };
253
+ //#endregion
60
254
  //#region src/lib/queryPersister.ts
61
255
  function createQueryPersister(persisterFn) {
62
256
  return (queryFn, context, query) => {
@@ -86,50 +280,102 @@ var BeanModelLast = class extends BeanBase {
86
280
  //#endregion
87
281
  //#region src/bean/bean.model/bean.model.persister.ts
88
282
  var BeanModelPersister = class extends BeanModelLast {
89
- _persisterLoad_inner(storage, storageKey, storedData, query, options) {
90
- if (!storedData) return void 0;
283
+ constructor(...args) {
284
+ super(...args);
285
+ this._persisterRestoreErrors = /* @__PURE__ */ new Set();
286
+ }
287
+ $persisterError(queryKey) {
288
+ const query = this.self.$queryFind({ queryKey });
289
+ const options = this._adjustPersisterOptions(query?.meta?.persister);
290
+ return !!options && this._persisterRestoreErrors.has(this._getPersisterStorageKey(options, query, queryKey));
291
+ }
292
+ _persisterLoad_inner(storedData, query, options) {
293
+ if (!storedData) return { remove: false };
91
294
  const persistedQuery = options.deserialize ? options.deserialize(storedData, options.deserializeDefault) : options.deserializeDefault(storedData);
92
295
  if (persistedQuery.state.dataUpdatedAt) {
93
296
  const expired = Date.now() - persistedQuery.state.dataUpdatedAt > (resolveMaxAgeTime(options.maxAge, query) ?? Infinity);
94
297
  const busted = persistedQuery.buster !== options.buster;
95
- if (expired || busted) storage.removeItem(storageKey);
96
- else {
97
- query.setState({
298
+ if (!expired && !busted) return {
299
+ data: persistedQuery.state.data,
300
+ remove: false,
301
+ timestamps: {
98
302
  dataUpdatedAt: persistedQuery.state.dataUpdatedAt,
99
303
  errorUpdatedAt: persistedQuery.state.errorUpdatedAt
100
- });
101
- return persistedQuery.state.data;
102
- }
103
- } else storage.removeItem(storageKey);
304
+ }
305
+ };
306
+ }
307
+ return { remove: true };
104
308
  }
105
309
  $persisterLoad(queryKey) {
310
+ const ctx = this.ctx;
311
+ if (ctx?.disposed) return void 0;
312
+ const persistence = this.$modelPersistence;
106
313
  const query = this.self.$queryFind({ queryKey });
107
314
  if (!query) return void 0;
108
315
  const options = this._adjustPersisterOptions(query.meta?.persister);
109
316
  if (!options) return void 0;
110
317
  const storage = this._getPersisterStorage(options, query);
111
318
  if (!storage) return void 0;
112
- const storageKey = this._getPersisterStorageKey(options, query);
319
+ const token = persistence.capture();
320
+ if (options.storage === "db") persistence.touch(query);
321
+ const storageKey = this._getPersisterStorageKey(options, query, void 0, token);
322
+ this._persisterRestoreErrors.delete(storageKey);
323
+ const queue = getPersistenceQueue(storage);
324
+ const generation = queue.generation(storageKey);
325
+ const current = () => !ctx?.disposed && generation === queue.generation(storageKey) && (options.storage !== "db" || persistence.current(token));
326
+ if (!options.sync) return queue.enqueue(async () => {
327
+ if (!current()) return void 0;
328
+ const storedData = await storage.getItem(storageKey);
329
+ if (!current()) return void 0;
330
+ try {
331
+ const result = this._persisterLoad_inner(storedData, query, options);
332
+ if (!current()) return void 0;
333
+ if (result.timestamps) query.setState(result.timestamps);
334
+ if (result.remove) await storage.removeItem(storageKey);
335
+ return current() ? result.data : void 0;
336
+ } catch {
337
+ if (!current()) return void 0;
338
+ this._persisterRestoreErrors.add(storageKey);
339
+ await storage.removeItem(storageKey);
340
+ this._reportPersisterError("restore");
341
+ return;
342
+ }
343
+ }).catch(() => {
344
+ if (current()) {
345
+ this._persisterRestoreErrors.add(storageKey);
346
+ this._reportPersisterError("restore");
347
+ }
348
+ });
113
349
  try {
114
- const storedData = storage.getItem(storageKey);
115
- if (options.sync) return this._persisterLoad_inner(storage, storageKey, storedData, query, options);
116
- else return storedData.then((storedData) => {
117
- return this._persisterLoad_inner(storage, storageKey, storedData, query, options);
118
- });
119
- } catch (err) {
120
- if (process.env.DEV) {
121
- console.error(err);
122
- console.warn("Encountered an error attempting to restore query cache from persisted location.");
350
+ const result = this._persisterLoad_inner(storage.getItem(storageKey), query, options);
351
+ if (result.timestamps) query.setState(result.timestamps);
352
+ if (result.remove) storage.removeItem(storageKey);
353
+ return result.data;
354
+ } catch {
355
+ this._persisterRestoreErrors.add(storageKey);
356
+ this._reportPersisterError("restore");
357
+ try {
358
+ storage.removeItem(storageKey);
359
+ } catch {
360
+ this._reportPersisterError("remove");
123
361
  }
124
- storage.removeItem(storageKey);
125
362
  }
126
363
  }
364
+ $persisterGeneration(queryKey) {
365
+ const query = this.self.$queryFind({ queryKey });
366
+ const options = this._adjustPersisterOptions(query?.meta?.persister);
367
+ if (!options) return "";
368
+ const storage = this._getPersisterStorage(options, query);
369
+ if (!storage) return "";
370
+ const storageKey = this._getPersisterStorageKey(options, query, queryKey);
371
+ return `${options.storage === "db" ? this.$modelPersistence.epoch : ""}:${storageKey}:${getPersistenceQueue(storage).generation(storageKey)}`;
372
+ }
127
373
  $persisterSave(queryKey) {
128
374
  const query = this.self.$queryFind({ queryKey });
129
- if (!query) return;
375
+ if (!query) return Promise.resolve();
130
376
  const options = this._adjustPersisterOptions(query.meta?.persister);
131
- if (!options) return;
132
- this._persisterSave(query, options);
377
+ if (!options) return Promise.resolve();
378
+ return this._persisterSave(query, options);
133
379
  }
134
380
  $persisterSaveTo(queryKey, storageTarget, value) {
135
381
  const query = this.self.$queryFind({ queryKey });
@@ -144,13 +390,9 @@ var BeanModelPersister = class extends BeanModelLast {
144
390
  sync: true,
145
391
  serializeDefault: (obj) => storageTarget === "cookie" ? String(obj?.state?.data ?? "") : JSON.stringify(obj?.state?.data)
146
392
  });
147
- if (!options) return;
148
- this._persisterSave(query, options, value);
393
+ if (options) this._persisterSave(query, options, value);
149
394
  }
150
- _persisterSave(query, options, value) {
151
- const storage = this._getPersisterStorage(options, query);
152
- if (!storage) return;
153
- const storageKey = this._getPersisterStorageKey(options, query);
395
+ _serializePersister(query, options, value) {
154
396
  const params = {
155
397
  state: value === void 0 ? query.state : {
156
398
  ...query.state,
@@ -160,42 +402,183 @@ var BeanModelPersister = class extends BeanModelLast {
160
402
  queryHash: query.queryHash,
161
403
  buster: options.buster
162
404
  };
163
- const data = options.serialize ? options.serialize(params, options.serializeDefault) : options.serializeDefault(params);
164
- if (options.sync === true) storage.setItem(storageKey, data);
165
- else setTimeout(() => {
405
+ return options.serialize ? options.serialize(params, options.serializeDefault) : options.serializeDefault(params);
406
+ }
407
+ _persisterSave(query, options, value) {
408
+ const storage = this._getPersisterStorage(options, query);
409
+ if (!storage) return Promise.resolve();
410
+ if (options.storage === "db") this.$modelPersistence.touch(query);
411
+ const storageKey = this._getPersisterStorageKey(options, query);
412
+ const queue = getPersistenceQueue(storage);
413
+ queue.invalidate(storageKey);
414
+ let data;
415
+ try {
416
+ data = this._serializePersister(query, options, value);
417
+ } catch (error) {
418
+ return this._handlePersisterOperation(queue.enqueue(() => {
419
+ throw error;
420
+ }), "save");
421
+ }
422
+ if (options.sync === true && options.storage !== "db") try {
166
423
  storage.setItem(storageKey, data);
167
- }, 0);
424
+ return Promise.resolve();
425
+ } catch (error) {
426
+ return this._handlePersisterOperation(Promise.reject(error), "save");
427
+ }
428
+ return this._handlePersisterOperation(queue.enqueue(async () => {
429
+ await storage.setItem(storageKey, data);
430
+ }), "save");
168
431
  }
169
- $persisterRemove(queryKey) {
432
+ $persisterRemove(queryKey, persister) {
170
433
  const query = this.self.$queryFind({ queryKey });
171
- if (!query) return;
172
- const options = this._adjustPersisterOptions(query.meta?.persister);
173
- if (!options) return;
434
+ if (!query && !persister) return Promise.resolve();
435
+ const options = this._adjustPersisterOptions(persister ?? query?.meta?.persister);
436
+ if (!options) return Promise.resolve();
174
437
  const storage = this._getPersisterStorage(options, query);
175
- if (!storage) return;
176
- const storageKey = this._getPersisterStorageKey(options, query);
177
- if (options.sync === true) storage.removeItem(storageKey);
178
- else setTimeout(() => {
438
+ if (!storage) return Promise.resolve();
439
+ if (options.storage === "db") this.$modelPersistence.touch(query);
440
+ const storageKey = this._getPersisterStorageKey(options, query, queryKey);
441
+ const queue = getPersistenceQueue(storage);
442
+ queue.invalidate(storageKey);
443
+ if (options.sync === true && options.storage !== "db") try {
179
444
  storage.removeItem(storageKey);
180
- }, 0);
445
+ return Promise.resolve();
446
+ } catch (error) {
447
+ return this._handlePersisterOperation(Promise.reject(error), "remove");
448
+ }
449
+ return this._handlePersisterOperation(queue.enqueue(async () => {
450
+ await storage.removeItem(storageKey);
451
+ }), "remove");
452
+ }
453
+ async $persisterFlush() {
454
+ if (process.env.SERVER) return;
455
+ await getPersistenceQueue(localforage).flush();
456
+ }
457
+ _handlePersisterOperation(operation, action) {
458
+ operation.catch(() => this._reportPersisterError(action));
459
+ return operation;
460
+ }
461
+ _reportPersisterError(action) {
462
+ if (process.env.DEV) console.warn(`Encountered an error attempting to ${action} persisted query cache.`);
181
463
  }
182
464
  _createPersister(options) {
183
465
  options = this._adjustPersisterOptions(options);
184
466
  if (!options) return void 0;
185
- const persisterFn = experimental_createQueryPersister({
186
- storage: this._getPersisterStorage(options),
187
- maxAge: options.maxAge,
188
- refetchOnRestore: options.refetchOnRestore,
189
- prefix: options.prefix,
190
- buster: options.buster
191
- }).persisterFn;
192
- return createQueryPersister(persisterFn);
467
+ if (options.storage === "db") return this._createDbPersister(options);
468
+ const storage = this._getPersisterStorage(options);
469
+ const persisterOptions = options;
470
+ return createQueryPersister((queryFn, context, query) => {
471
+ const queue = storage ? getPersistenceQueue(storage) : void 0;
472
+ const storageKey = `${persisterOptions.prefix}-${query.queryHash}`;
473
+ const generation = queue?.generation(storageKey);
474
+ const current = () => generation === queue?.generation(storageKey);
475
+ const orderedStorage = storage && queue && !persisterOptions.sync ? {
476
+ getItem: (key) => queue.enqueue(() => current() ? storage.getItem(key) : void 0),
477
+ setItem: (key, data) => this._handlePersisterOperation(queue.enqueue(async () => {
478
+ if (current()) await storage.setItem(key, data);
479
+ }), "save"),
480
+ removeItem: (key) => this._handlePersisterOperation(queue.enqueue(async () => {
481
+ if (current()) await storage.removeItem(key);
482
+ }), "remove")
483
+ } : storage;
484
+ return experimental_createQueryPersister({
485
+ storage: orderedStorage,
486
+ maxAge: persisterOptions.maxAge,
487
+ refetchOnRestore: persisterOptions.refetchOnRestore,
488
+ prefix: persisterOptions.prefix,
489
+ buster: persisterOptions.buster
490
+ }).persisterFn(queryFn, context, query);
491
+ });
492
+ }
493
+ _createDbPersister(options) {
494
+ const ctx = this.ctx;
495
+ const persistence = this.$modelPersistence;
496
+ return async (queryFn, context, query) => {
497
+ if (ctx?.disposed) throw new CancelledError({
498
+ silent: true,
499
+ revert: false
500
+ });
501
+ persistence.touch(query);
502
+ const token = persistence.capture();
503
+ const storage = this._getPersisterStorage(options, query);
504
+ const queue = storage && getPersistenceQueue(storage);
505
+ const storageKey = this._getPersisterStorageKey(options, query, void 0, token);
506
+ const generation = queue?.generation(storageKey);
507
+ const current = () => !ctx?.disposed && persistence.current(token) && generation === queue?.generation(storageKey);
508
+ const assertCurrent = () => {
509
+ if (!current()) throw new CancelledError({
510
+ silent: true,
511
+ revert: false
512
+ });
513
+ };
514
+ const bypass = query.state.fetchMeta?.bypassPersister === true;
515
+ if (!bypass && storage && queue && query.state.data === void 0) {
516
+ this._persisterRestoreErrors.delete(storageKey);
517
+ const restored = await queue.enqueue(async () => {
518
+ if (!current()) return void 0;
519
+ try {
520
+ const storedData = await storage.getItem(storageKey);
521
+ if (!current()) return void 0;
522
+ const result = this._persisterLoad_inner(storedData, query, options);
523
+ if (!current()) return void 0;
524
+ if (result.remove) await storage.removeItem(storageKey);
525
+ return current() ? result : void 0;
526
+ } catch {
527
+ if (current()) {
528
+ this._persisterRestoreErrors.add(storageKey);
529
+ await storage.removeItem(storageKey);
530
+ this._reportPersisterError("restore");
531
+ }
532
+ return;
533
+ }
534
+ }).catch(() => {
535
+ if (current()) {
536
+ this._persisterRestoreErrors.add(storageKey);
537
+ this._reportPersisterError("restore");
538
+ }
539
+ });
540
+ assertCurrent();
541
+ if (restored?.data !== void 0) {
542
+ notifyManager.schedule(() => {
543
+ if (!current()) return;
544
+ if (restored.timestamps) query.setState(restored.timestamps);
545
+ if (options.refetchOnRestore === "always" || options.refetchOnRestore === true && query.isStale()) query.fetch().catch(() => void 0);
546
+ });
547
+ return restored.data;
548
+ }
549
+ }
550
+ assertCurrent();
551
+ let result;
552
+ try {
553
+ result = await queryFn(context);
554
+ } catch (error) {
555
+ assertCurrent();
556
+ throw error;
557
+ }
558
+ assertCurrent();
559
+ if (!bypass && storage && queue) notifyManager.schedule(() => {
560
+ if (!current()) return;
561
+ let data;
562
+ try {
563
+ data = this._serializePersister(query, options, result);
564
+ } catch (error) {
565
+ this._handlePersisterOperation(queue.enqueue(() => {
566
+ throw error;
567
+ }), "save");
568
+ return;
569
+ }
570
+ this._handlePersisterOperation(queue.enqueue(async () => {
571
+ if (current()) await storage.setItem(storageKey, data);
572
+ }), "save");
573
+ });
574
+ return result;
575
+ };
193
576
  }
194
577
  _adjustPersisterOptions(options) {
195
578
  if (options === false) return void 0;
196
- if (options === void 0 || options === true) options = {};
197
- else options = { ...options };
579
+ options = options === void 0 || options === true ? {} : { ...options };
198
580
  options.storage = options.storage ?? (options.sync ? "local" : "db");
581
+ if (options.storage === "db") options.sync = false;
199
582
  options.maxAge = options.maxAge ?? this.scopeSelf.config.persister.maxAge[options.storage];
200
583
  options.refetchOnRestore = options.refetchOnRestore ?? this.scopeSelf.config.persister.refetchOnRestore;
201
584
  options.prefix = options.prefix ?? this._getPersisterPrefix();
@@ -204,9 +587,10 @@ var BeanModelPersister = class extends BeanModelLast {
204
587
  options.deserializeDefault = options.deserializeDefault ?? JSON.parse;
205
588
  return options;
206
589
  }
207
- _getPersisterStorageKey(options, query) {
208
- if (options.storageKeySimplify) return String(query.queryKey[query.queryKey.length - 1]);
209
- return `${options.prefix}-${query.queryHash}`;
590
+ _getPersisterStorageKey(options, query, queryKey, token) {
591
+ const key = query?.queryKey ?? this._forceQueryKeyPrefix(queryKey);
592
+ const logicalKey = options.storageKeySimplify ? String(key[key.length - 1]) : `${options.prefix}-${query?.queryHash ?? hashKey(key)}`;
593
+ return options.storage === "db" ? getModelPersistenceStorageKey(this.sys.env.APP_NAME ?? "", (token ?? this.$modelPersistence.capture()).scope, logicalKey) : logicalKey;
210
594
  }
211
595
  _getPersisterStorage(options, query) {
212
596
  options = this._adjustPersisterOptions(options);
@@ -331,9 +715,13 @@ var BeanModelQuery = class extends BeanModelCookie {
331
715
  })?.setData(value);
332
716
  }
333
717
  async $clear() {
334
- const queries = this.$queryClient.getQueryCache().getAll();
335
- for (const query of queries) query?.setData(void 0);
336
- await localforage.clear();
718
+ const queue = getPersistenceQueue(localforage);
719
+ queue.invalidateAll();
720
+ const clear = process.env.SERVER ? Promise.resolve() : queue.enqueue(async () => {
721
+ await localforage.clear();
722
+ });
723
+ this.$modelPersistence.clear();
724
+ await clear;
337
725
  }
338
726
  $normalizeFilters(filters) {
339
727
  if (!filters) filters = {};
@@ -350,10 +738,10 @@ var BeanModelUseQuery = class extends BeanModelQuery {
350
738
  $useQuery(options, queryClient) {
351
739
  const queryKey = this.self._forceQueryKeyPrefix(options.queryKey);
352
740
  const persister = this._createPersister(options.meta?.persister);
353
- options = Object.assign({}, options, {
741
+ options = this.$modelPersistence.guardOptions(Object.assign({}, options, {
354
742
  queryKey,
355
743
  persister
356
- });
744
+ }));
357
745
  if ((typeof options.meta?.persister === "object" && options.meta?.persister?.sync) !== true) {
358
746
  const staleTime = options.staleTime ?? this.scopeSelf.config.query.staleTime.async;
359
747
  const queryCacheExists = this.$queryFind({ queryKey })?.state.data !== void 0;
@@ -501,16 +889,24 @@ var BeanModelUseState = class extends BeanModelUseQuery {
501
889
  return this[SymbolUseQueries][queryHash];
502
890
  }
503
891
  _handleAsyncDataGet(options, queryClient, persister) {
892
+ if (this.ctx?.disposed) return void 0;
893
+ this.$modelPersistence.epoch;
504
894
  const query = this.$useStateData(options, queryClient);
505
895
  if (query.data !== void 0) return query.data;
506
896
  return this._handleAsyncDataGet_inner(options, queryClient, persister);
507
897
  }
508
898
  async _handleAsyncDataGet_inner(options, queryClient, persister) {
899
+ const ctx = this.ctx;
900
+ if (ctx?.disposed) return void 0;
901
+ const persistence = this.$modelPersistence;
509
902
  const queryKey = options.queryKey;
903
+ const token = persistence.capture();
510
904
  const query = this.$useStateData(options, queryClient);
511
905
  if (persister) {
906
+ const generation = this.$persisterGeneration(queryKey);
512
907
  const data = await this.$persisterLoad(queryKey);
513
- if (data !== void 0) this.$setQueryData(queryKey, data, false);
908
+ if (ctx?.disposed || !persistence.current(token) || generation !== this.$persisterGeneration(queryKey)) return;
909
+ if (data !== void 0 && query.data === void 0) this.$setQueryData(queryKey, data, false);
514
910
  }
515
911
  if (query.data === void 0) this._handleSyncDataGet_defaultData(queryKey, options);
516
912
  return query.data;
@@ -595,6 +991,31 @@ var ServiceStorage = (_dec$1 = Service(), _dec2$1 = BeanInfo({ module: "a-model"
595
991
  constructor(...args) {
596
992
  super(...args);
597
993
  this._queryClient = void 0;
994
+ this._persistence = void 0;
995
+ }
996
+ get epoch() {
997
+ return this._persistence.epoch;
998
+ }
999
+ capture() {
1000
+ return this._persistence.capture();
1001
+ }
1002
+ current(token) {
1003
+ return this._persistence.current(token);
1004
+ }
1005
+ update(values, options) {
1006
+ this._persistence.update(values, options);
1007
+ }
1008
+ subscribe(listener) {
1009
+ return this._persistence.subscribe(listener);
1010
+ }
1011
+ clear() {
1012
+ this._persistence.clear();
1013
+ }
1014
+ touch(query) {
1015
+ this._persistence.touch(query);
1016
+ }
1017
+ guardOptions(options) {
1018
+ return this._persistence.guardOptions(options);
598
1019
  }
599
1020
  async moduleLoaded() {
600
1021
  const options = deepExtend({}, this.scope.config.queryClientConfig.defaultOptions);
@@ -603,10 +1024,12 @@ var ServiceStorage = (_dec$1 = Service(), _dec2$1 = BeanInfo({ module: "a-model"
603
1024
  options.queries.gcTime = Infinity;
604
1025
  }
605
1026
  const queryCache = new QueryCache();
606
- const vueQueryPluginOptions = { queryClient: this._queryClient = new QueryClient({
1027
+ const queryClient = this._queryClient = new QueryClient({
607
1028
  defaultOptions: options,
608
1029
  queryCache
609
- }) };
1030
+ });
1031
+ this._persistence = new ModelPersistenceContext(queryClient, this.sys.env.SITE_ID);
1032
+ const vueQueryPluginOptions = { queryClient };
610
1033
  this.app.vue.use(VueQueryPlugin, vueQueryPluginOptions);
611
1034
  }
612
1035
  async appInitialize() {
@@ -616,7 +1039,14 @@ var ServiceStorage = (_dec$1 = Service(), _dec2$1 = BeanInfo({ module: "a-model"
616
1039
  } });
617
1040
  this._queryClient?.clear();
618
1041
  });
619
- if (process.env.CLIENT && this.ctx.meta.$ssr.isRuntimeSsrPreHydration) hydrate(this._queryClient, this.ctx.meta.$ssr.stateDefer.query);
1042
+ if (process.env.CLIENT && this.ctx.meta.$ssr.isRuntimeSsrPreHydration) {
1043
+ const state = this.ctx.meta.$ssr.stateDefer.query;
1044
+ hydrate(this._queryClient, state);
1045
+ this._persistence.markHydrated((state?.queries ?? []).flatMap((item) => {
1046
+ const query = this._queryClient.getQueryCache().get(item.queryHash);
1047
+ return query ? [query] : [];
1048
+ }));
1049
+ }
620
1050
  }
621
1051
  }) || _class$1) || _class$1);
622
1052
  //#endregion
@@ -679,6 +1109,11 @@ var Monkey = class extends BeanSimple {
679
1109
  if (this._moduleSelf === module) await (await this.getStorage()).moduleLoaded();
680
1110
  }
681
1111
  async beanInit(bean, beanInstance) {
1112
+ bean.defineProperty(beanInstance, "$modelPersistence", {
1113
+ enumerable: false,
1114
+ configurable: true,
1115
+ get: () => this._storage
1116
+ });
682
1117
  bean.defineProperty(beanInstance, "$queryClient", {
683
1118
  enumerable: false,
684
1119
  configurable: true,
@@ -690,8 +1125,6 @@ var Monkey = class extends BeanSimple {
690
1125
  };
691
1126
  //#endregion
692
1127
  //#region src/.metadata/index.ts
693
- /** monkey: end */
694
- /** scope: begin */
695
1128
  var _dec;
696
1129
  var _dec2;
697
1130
  var _class;
@@ -742,6 +1175,6 @@ async function _QueryEnsureLoadedInner(fn) {
742
1175
  return query;
743
1176
  }
744
1177
  //#endregion
745
- export { $QueriesEnsureLoaded, $QueryEnsureFresh, $QueryEnsureLoaded, $QueryGetFresh, BeanModelBase, Model, Monkey, ScopeModuleAModel, ServiceStorage, config, resolveMaxAgeTime, resolveStaleTime };
1178
+ export { $QueriesEnsureLoaded, $QueryEnsureFresh, $QueryEnsureLoaded, $QueryGetFresh, BeanModelBase, Model, ModelPersistenceContext, Monkey, ScopeModuleAModel, ServiceStorage, config, getModelPersistenceStorageKey, isModelPersistenceDb, resolveMaxAgeTime, resolveStaleTime };
746
1179
 
747
1180
  //# sourceMappingURL=index.js.map