pinia-plugin-persist-state 0.1.2-beta.0 → 0.1.2-beta.1

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/CHANGELOG.md CHANGED
@@ -5,13 +5,14 @@ All notable changes to this project will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
- ## [0.1.2-beta.0]
8
+ ## [0.1.2]
9
9
 
10
10
  ### Added
11
11
  - Added an object-based `createPersistStatePlugin` configuration to explicitly select `storage`, `databaseName`, `objectStoreName`, and `cryptKey`.
12
12
  - Added `persistenceKey` to persist a store under a stable or application-specific record key instead of its Pinia `$id`.
13
13
  - Added opt-in cache metadata and restore policies through `cache`, including `maxAge`, schema `version`, and expiration/version-mismatch actions.
14
14
  - Added `debounceMs` for trailing, per-store automatic persistence; automatic writes use a 200 ms delay by default.
15
+ - Added `restoreOnHydrate` to restore state store on client hydration.
15
16
  - Added `flushPersistedState()` to immediately write the latest store state and cancel a pending debounced write.
16
17
  - Added `transformState` to convert a filtered snapshot into an application-defined persistent representation before it is stored.
17
18
  - Added `fake-indexeddb` integration tests for real IndexedDB updates, connection reuse, selective deletion, database isolation, and transaction errors.
package/README.md CHANGED
@@ -112,6 +112,7 @@ Fields available when setting `storeOptions`:
112
112
  - `transformState?: (state) => state` — Optional synchronous transformer applied to the filtered snapshot after selected properties are encrypted and before storage. Use it to convert application-specific values into a persistent representation.
113
113
  - `watchMutation?: boolean` — When `true`, plugin watches store mutations and automatically persists changes.
114
114
  - `debounceMs?: number` — Delay in milliseconds before an automatic persistence is written. Defaults to `200`; use `0` to persist each mutation immediately.
115
+ - `restoreOnHydrate?: boolean` - Set it to true, and the persisted state will be restored to the store.
115
116
 
116
117
  `CacheOptions` supports `maxAge?: number`, `version?: number`, `onExpired?: 'ignore' | 'remove' | 'restore'`, and `onVersionMismatch?: 'ignore' | 'remove' | 'restore'`. Cache metadata is written only when `cache` is configured.
117
118
 
@@ -1,6 +1,6 @@
1
1
  import { Store as f, isEmpty as u, CustomConsole as _, PluginSubscriber as w } from "pinia-plugin-subscription";
2
2
  import { toRaw as P, nextTick as v } from "vue";
3
- class T {
3
+ class E {
4
4
  _storage;
5
5
  constructor(t) {
6
6
  this._storage = t === "localStorage" ? localStorage : sessionStorage;
@@ -104,7 +104,7 @@ class m {
104
104
  }
105
105
  defineDb() {
106
106
  const { databaseName: t, keyPath: e, name: s, objectStoreName: r, storage: a } = this._db_options, o = a ?? (s === "localStorage" || s === "sessionStorage" ? s : "indexedDB");
107
- return o === "localStorage" || o === "sessionStorage" ? new T(o) : new l(t ?? s, r ?? "persistedStore", { keyPath: e });
107
+ return o === "localStorage" || o === "sessionStorage" ? new E(o) : new l(t ?? s, r ?? "persistedStore", { keyPath: e });
108
108
  }
109
109
  async getItem(t) {
110
110
  return this._db.getItem(t);
@@ -116,7 +116,7 @@ class m {
116
116
  this._db instanceof l ? await this._db.setItem({ storeName: t, ...e }) : await this._db.setItem(e, t);
117
117
  }
118
118
  }
119
- class E {
119
+ class T {
120
120
  _store = /* @__PURE__ */ new Map();
121
121
  async clear() {
122
122
  this._store.clear();
@@ -159,7 +159,7 @@ const N = {
159
159
  };
160
160
  class I extends m {
161
161
  defineDb() {
162
- return this.dbName === "localStorage" || this.dbName === "sessionStorage" ? N : new E();
162
+ return this.dbName === "localStorage" || this.dbName === "sessionStorage" ? N : new T();
163
163
  }
164
164
  }
165
165
  class h {
@@ -278,18 +278,12 @@ class g extends f {
278
278
  options: e
279
279
  }), this.toBeWatched() && (this._watchedStore.add(this.store.$id), this.storeSubscribe = this.storeSubscription.bind(this));
280
280
  }
281
- hydrate() {
282
- return typeof window > "u" ? Promise.resolve() : this.remember();
283
- }
284
281
  augmentStore() {
285
282
  const { isEncrypted: t, persistedPropertiesToEncrypt: e, watchMutation: s } = this.options;
286
283
  t === void 0 && (this.options.isEncrypted = !1), e === void 0 && (this.options.persistedPropertiesToEncrypt = []), s === void 0 && (this.options.watchMutation = !1), this.stateHas("isLoading") || this.addToState("isLoading", !1), this.store.persistState = async () => await this.persist(), this.store.flushPersistedState = async () => await this.flushPersistedState(), this.store.remember = async () => await this.remember(), this.store.removePersistedState = this.removePersistedState.bind(this), this.store.watch = this.watch.bind(this), this.store.stopWatch = this.stopWatch.bind(this), this.store.hydrate = this.hydrate.bind(this);
287
284
  }
288
- async decryptProperty(t, e) {
289
- return t.decrypt(e);
290
- }
291
- async encryptProperty(t, e) {
292
- return t.encrypt(e);
285
+ cancelScheduledPersist() {
286
+ this._persistTimer && (clearTimeout(this._persistTimer), this._persistTimer = void 0);
293
287
  }
294
288
  async cryptState(t, e = !1) {
295
289
  return await new Promise(async (s) => {
@@ -312,6 +306,9 @@ class g extends f {
312
306
  static customizeStore(t, e, s, r) {
313
307
  return !e.storeOptions?.persist && e.storeOptions?.watchMutation && (e.storeOptions.persist = !0), super.customizeStore(t, e, s, r);
314
308
  }
309
+ async decryptProperty(t, e) {
310
+ return t.decrypt(e);
311
+ }
315
312
  definePersister(t) {
316
313
  const e = this.getPersistedStoreOptions();
317
314
  if (!e.storage || e.storage === e.storageOptions?.storage) {
@@ -327,8 +324,11 @@ class g extends f {
327
324
  keyPath: "storeName"
328
325
  });
329
326
  }
330
- isEncrypted() {
331
- return this.options.isEncrypted;
327
+ async encryptProperty(t, e) {
328
+ return t.encrypt(e);
329
+ }
330
+ async flushPersistedState() {
331
+ this.cancelScheduledPersist(), await this.persist();
332
332
  }
333
333
  getPropertiesToEncrypt() {
334
334
  return this.options?.persistedPropertiesToEncrypt ?? [];
@@ -336,12 +336,12 @@ class g extends f {
336
336
  getPersistenceKey() {
337
337
  return this.getPersistedStoreOptions().persistenceKey ?? this.store.$id;
338
338
  }
339
+ getCachedState(t) {
340
+ return this.getCacheOptions() && t && "state" in t && "metadata" in t ? t : void 0;
341
+ }
339
342
  getCacheOptions() {
340
343
  return this.getPersistedStoreOptions().cache;
341
344
  }
342
- transformState(t) {
343
- return this.getPersistedStoreOptions().transformState?.(t) ?? t;
344
- }
345
345
  getPersistedStoreOptions() {
346
346
  return this.options;
347
347
  }
@@ -360,13 +360,6 @@ class g extends f {
360
360
  this.logError("getPersistedState()", { persistenceKey: e, e: s });
361
361
  }
362
362
  }
363
- getCachedState(t) {
364
- return this.getCacheOptions() && t && "state" in t && "metadata" in t ? t : void 0;
365
- }
366
- async shouldIgnoreCachedState(t, e) {
367
- const s = this.getCacheOptions(), r = s.maxAge !== void 0 && t.metadata.cachedAt + s.maxAge < Date.now(), a = s.version !== void 0 && s.version !== t.metadata.version, o = r ? s.onExpired : a ? s.onVersionMismatch : void 0;
368
- return !o || o === "restore" ? !1 : (o === "remove" && await this._persister.removeItem(e), !0);
369
- }
370
363
  async getStateToPersist() {
371
364
  const t = this._excludedKeys, e = this.state, s = this._propertiesToEncrypt.size > 0, r = this._crypt;
372
365
  s && await r.init();
@@ -387,12 +380,18 @@ class g extends f {
387
380
  static hasRequiredKeys(t) {
388
381
  return !!t?.persist || !!t?.watchMutation;
389
382
  }
383
+ hydrate() {
384
+ return typeof window > "u" || !this.shouldRestoreOnHydrate() ? Promise.resolve() : this.remember();
385
+ }
390
386
  initExcludedKeys() {
391
387
  return /* @__PURE__ */ new Set([
392
388
  ...O,
393
389
  ...this.options.excludedKeys ?? []
394
390
  ]);
395
391
  }
392
+ isEncrypted() {
393
+ return this.options.isEncrypted;
394
+ }
396
395
  async persist() {
397
396
  const t = ++this._persistenceVersion, e = await this.getStateToPersist();
398
397
  if (this.debugLog(`persist() ${this.store.$id} - stateToPersist:`, { state: e }), !u(e)) {
@@ -403,14 +402,16 @@ class g extends f {
403
402
  }), await a;
404
403
  }
405
404
  }
406
- propertyShouldBePersisted(t) {
407
- return !this._excludedKeys.has(t);
408
- }
409
405
  async remember() {
410
- return this.state.isLoading = !0, new Promise(async (t) => {
411
- let e = await this.getPersistedState();
412
- return e && !u(e) && this.store.$patch(e), this.state.isLoading = !1, t();
413
- });
406
+ this.state.isLoading = !0;
407
+ try {
408
+ let t = await this.getPersistedState();
409
+ t && !u(t) && this.store.$patch(t);
410
+ } catch (t) {
411
+ this.logError("remember()", { error: t, storeName: this.store.$id });
412
+ } finally {
413
+ this.state.isLoading = !1;
414
+ }
414
415
  }
415
416
  async removePersistedState() {
416
417
  this.cancelScheduledPersist(), ++this._persistenceVersion;
@@ -420,6 +421,18 @@ class g extends f {
420
421
  this._writeQueue = t.catch(() => {
421
422
  }), await t;
422
423
  }
424
+ schedulePersist(t) {
425
+ this.cancelScheduledPersist(), this._persistTimer = setTimeout(() => {
426
+ this._persistTimer = void 0, this.persist().then(() => this.store.mutationCallback?.(this.state, t)).catch((e) => this.logError("persist()", { error: e, storeName: this.store.$id }));
427
+ }, this.getDebounceMs());
428
+ }
429
+ async shouldIgnoreCachedState(t, e) {
430
+ const s = this.getCacheOptions(), r = s.maxAge !== void 0 && t.metadata.cachedAt + s.maxAge < Date.now(), a = s.version !== void 0 && s.version !== t.metadata.version, o = r ? s.onExpired : a ? s.onVersionMismatch : void 0;
431
+ return !o || o === "restore" ? !1 : (o === "remove" && await this._persister.removeItem(e), !0);
432
+ }
433
+ shouldRestoreOnHydrate() {
434
+ return this.options.restoreOnHydrate === !0;
435
+ }
423
436
  stopWatch() {
424
437
  this.options?.watchMutation && (this.options && (this.options.watchMutation = !1), this._watchedStore.delete(this.store.$id));
425
438
  }
@@ -437,33 +450,25 @@ class g extends f {
437
450
  this.store
438
451
  ]), t.type !== "patch object" && this.getWatchMutation() && this.schedulePersist(t);
439
452
  }
440
- cancelScheduledPersist() {
441
- this._persistTimer && (clearTimeout(this._persistTimer), this._persistTimer = void 0);
442
- }
443
- schedulePersist(t) {
444
- this.cancelScheduledPersist(), this._persistTimer = setTimeout(() => {
445
- this._persistTimer = void 0, this.persist().then(() => this.store.mutationCallback?.(this.state, t)).catch((e) => this.logError("persist()", { error: e, storeName: this.store.$id }));
446
- }, this.getDebounceMs());
447
- }
448
- async flushPersistedState() {
449
- this.cancelScheduledPersist(), await this.persist();
450
- }
451
453
  toBeCrypted() {
452
454
  return !!(this._crypt && this.getPropertiesToEncrypt());
453
455
  }
454
456
  toBeWatched() {
455
457
  return !this._watchedStore.has(this.store.$id);
456
458
  }
459
+ transformState(t) {
460
+ return this.getPersistedStoreOptions().transformState?.(t) ?? t;
461
+ }
457
462
  watch() {
458
463
  this.toBeWatched() && (this.options && (this.options.watchMutation = !0), this.storeSubscribe = this.storeSubscription.bind(this));
459
464
  }
460
465
  }
461
466
  const b = "pinia-plugin-persist-state";
462
- class x extends _ {
467
+ class C extends _ {
463
468
  _pluginName = b;
464
469
  }
465
- const S = new x();
466
- class C extends w {
470
+ const S = new C();
471
+ class x extends w {
467
472
  _resetStoreCallback = (t) => {
468
473
  const { removePersistedState: e } = t;
469
474
  typeof e == "function" && e();
@@ -487,13 +492,13 @@ class C extends w {
487
492
  });
488
493
  }
489
494
  }
490
- const y = new C(), K = ["localStorage", "sessionStorage"];
495
+ const y = new x(), A = ["localStorage", "sessionStorage"];
491
496
  function L(i, t) {
492
- const e = A(i, t);
497
+ const e = K(i, t);
493
498
  return y.pluginOptions = D(e), y;
494
499
  }
495
- function A(i, t) {
496
- return typeof i != "string" ? i : K.includes(i) ? { storage: i, cryptKey: t } : {
500
+ function K(i, t) {
501
+ return typeof i != "string" ? i : A.includes(i) ? { storage: i, cryptKey: t } : {
497
502
  storage: "indexedDB",
498
503
  databaseName: i,
499
504
  objectStoreName: "persistedStore",
@@ -35,6 +35,7 @@ export interface PersistedStoreOptions {
35
35
  persist?: boolean;
36
36
  persistenceKey?: string;
37
37
  persistedPropertiesToEncrypt?: string[];
38
+ restoreOnHydrate?: boolean;
38
39
  storage?: PersistStorage;
39
40
  transformState?: PersistedStateTransformer;
40
41
  watchMutation?: boolean;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "pinia-plugin-persist-state",
3
3
  "description": "Pinia plugin for persisting store state",
4
- "version": "0.1.2-beta.0",
4
+ "version": "0.1.2-beta.1",
5
5
  "type": "module",
6
6
  "author": "Alexandre Bidaud <alexbidaud85@gmail.com> (https://github.com/AlxBDo)",
7
7
  "license": "MIT",