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

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,6 +5,38 @@ 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]
9
+
10
+ ### Added
11
+ - Added an object-based `createPersistStatePlugin` configuration to explicitly select `storage`, `databaseName`, `objectStoreName`, and `cryptKey`.
12
+ - Added `persistenceKey` to persist a store under a stable or application-specific record key instead of its Pinia `$id`.
13
+ - Added opt-in cache metadata and restore policies through `cache`, including `maxAge`, schema `version`, and expiration/version-mismatch actions.
14
+ - Added `debounceMs` for trailing, per-store automatic persistence; automatic writes use a 200 ms delay by default.
15
+ - Added `flushPersistedState()` to immediately write the latest store state and cancel a pending debounced write.
16
+ - Added `transformState` to convert a filtered snapshot into an application-defined persistent representation before it is stored.
17
+ - Added `fake-indexeddb` integration tests for real IndexedDB updates, connection reuse, selective deletion, database isolation, and transaction errors.
18
+
19
+ ### Changed
20
+ - Retained the legacy positional `createPersistStatePlugin(storageOrDatabaseName?, cryptKey?)` signature for compatibility. Non-window-storage names are interpreted as IndexedDB database names using the `persistedStore` object store.
21
+ - Replaced per-store `dbName` with `storage` to express a storage backend override without conflating the backend, IndexedDB database, and persisted record key.
22
+ - IndexedDB now reuses open connections and waits for transaction completion before resolving storage operations.
23
+ - The default IndexedDB model uses one application database, a stable object store, and one record per persisted Pinia store.
24
+ - `removePersistedState()` is asynchronous and waits for prior writes to complete.
25
+ - Window Storage remains responsible for serializing complete records; IndexedDB retains native values.
26
+ - `null`, `undefined`, and values considered empty by `pinia-plugin-subscription` are intentionally omitted from persisted snapshots before `transformState` runs.
27
+
28
+ ### Fixed
29
+ - Replaced IndexedDB read-then-update writes with atomic `put()` writes to prevent stale records from being rewritten.
30
+ - Serialized persistence requests and discard superseded snapshots before writing, preventing older asynchronous writes from overwriting newer state.
31
+ - Aligned package ESM metadata with the generated bundle by removing the reference to a nonexistent CommonJS artifact.
32
+ - Aligned storage operations on asynchronous `Promise<void>` contracts so write and removal failures can propagate to callers.
33
+
34
+ ### Security
35
+ - New encrypted values use AES-GCM with PBKDF2 and an independent random 16-byte salt plus 12-byte IV for every encrypted value.
36
+ - Introduced the versioned encrypted payload format `v1:salt:iv:ciphertext`.
37
+ - Preserved decryption support for legacy `iv:ciphertext` payloads generated with the historical salt.
38
+ - Encrypted properties preserve JSON-compatible types including strings, numbers, booleans, arrays, and objects; unsupported values are rejected.
39
+
8
40
  ## [0.1.1]
9
41
 
10
42
  ### Fixed
package/README.md CHANGED
@@ -4,23 +4,23 @@ A small Pinia plugin that adds persistence and optional encryption to your Pinia
4
4
 
5
5
  ---
6
6
 
7
- ## 🔧 Features
7
+ ## Features
8
8
 
9
9
  - Persist store state to LocalStorage, SessionStorage or IndexedDB
10
10
  - Selective encryption for specific properties using Web Crypto (AES-GCM)
11
11
  - Per-store options via `storeOptions` when defining a store
12
12
  - Augmented store API: `persistState`, `remember`, `removePersistedState`, `watch`, `stopWatch`
13
- - Simple initialization through `createPersistStatePlugin(dbName?, cryptKey?)`
13
+ - Explicit configuration of the persistence backend and IndexedDB storage
14
14
  - SSR-safe hydration flow: methods are injected immediately on client, persistence restore is executed during hydration lifecycle
15
15
 
16
16
  ---
17
17
 
18
- ## ⚙️ Installation
18
+ ## Installation
19
19
 
20
20
  Install the package (example):
21
21
 
22
22
  ```bash
23
- npm install --save persist-pinia-state
23
+ npm install --save pinia-plugin-persist-state
24
24
  ```
25
25
 
26
26
  Then register the plugin with Pinia in your app entry (see `src/main.ts`):
@@ -29,15 +29,18 @@ Then register the plugin with Pinia in your app entry (see `src/main.ts`):
29
29
  import { createApp } from 'vue'
30
30
  import { createPinia } from 'pinia'
31
31
  import { createPlugin } from 'pinia-plugin-subscription'
32
- import { createPersistStatePlugin, PLUGIN_NAME } from 'persist-pinia-state'
32
+ import { createPersistStatePlugin, PLUGIN_NAME } from 'pinia-plugin-persist-state'
33
33
 
34
34
  const app = createApp(App)
35
35
  const pinia = createPinia()
36
36
 
37
- // Pass `dbName` (e.g. 'localStorage', 'sessionStorage' or a DB name for IndexedDB)
38
- // and an optional `cryptKey` to enable encryption support
39
37
  pinia.use(createPlugin([
40
- createPersistStatePlugin('localStorage', 'my-secret-key')
38
+ createPersistStatePlugin({
39
+ storage: 'indexedDB',
40
+ databaseName: 'my-application',
41
+ objectStoreName: 'persistedStore',
42
+ cryptKey: 'my-secret-key'
43
+ })
41
44
  ]))
42
45
 
43
46
  app.use(pinia)
@@ -47,10 +50,10 @@ For Nuxt/SSR projects, prefer `createHydrationPlugin` from `pinia-plugin-subscri
47
50
 
48
51
  ```ts
49
52
  import { createHydrationPlugin } from 'pinia-plugin-subscription'
50
- import { createPersistStatePlugin } from 'persist-pinia-state'
53
+ import { createPersistStatePlugin } from 'pinia-plugin-persist-state'
51
54
 
52
55
  pinia.use(createHydrationPlugin([
53
- createPersistStatePlugin('localStorage', 'my-secret-key')
56
+ createPersistStatePlugin({ storage: 'localStorage', cryptKey: 'my-secret-key' })
54
57
  ], {
55
58
  runtimeEnvironment: import.meta.client ? 'client' : 'server'
56
59
  }))
@@ -58,7 +61,7 @@ pinia.use(createHydrationPlugin([
58
61
 
59
62
  ---
60
63
 
61
- ## 📚 Usage
64
+ ## Usage
62
65
 
63
66
  When defining a store you can pass `storeOptions` (type: `PersistedStoreOptions`) as part of the `defineStore` options. Example in `src/stores/test.ts`:
64
67
 
@@ -92,27 +95,38 @@ export const useTestStore = defineStore('testStore', () => {
92
95
 
93
96
  If `persist` or `watchMutation` are `true` the plugin will attempt to persist the store state using the configured persister: localStorage, sessionStorage or IndexedDB.
94
97
 
98
+ `createPersistStatePlugin(storageOrDatabaseName?, cryptKey?)` remains supported for compatibility. A value of `'localStorage'` or `'sessionStorage'` selects that storage; any other string is interpreted as an IndexedDB database name and uses the default `persistedStore` object store.
99
+
95
100
  ---
96
101
 
97
- ## 🔣 PersistedStoreOptions
102
+ ## PersistedStoreOptions
98
103
 
99
104
  Fields available when setting `storeOptions`:
100
105
 
101
- - `dbName?: string` — (Optional) Name of the database/storage to use to persist the store state (use only if different from the one defined in the plugin). Use `'localStorage'` or `'sessionStorage'` for window storage, or any other name to use IndexedDB.
106
+ - `storage?: 'localStorage' | 'sessionStorage' | 'indexedDB'` — Optional backend override for this store. IndexedDB settings remain configured at plugin level.
107
+ - `persistenceKey?: string` — Record key used for this store. Defaults to the Pinia `$id` and is useful for stable, readable or parameterized keys.
108
+ - `cache?: CacheOptions` — Opt-in cache metadata and restore policy. Without it, the state is persisted in the historical raw format.
102
109
  - `excludedKeys?: string[]` — List of state properties that should NOT be persisted.
103
110
  - `persist?: boolean` — Enable or disable persistence for the store (default: `false`).
104
111
  - `persistedPropertiesToEncrypt?: string[]` — List of property names to be encrypted when persisted.
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.
105
113
  - `watchMutation?: boolean` — When `true`, plugin watches store mutations and automatically persists changes.
114
+ - `debounceMs?: number` — Delay in milliseconds before an automatic persistence is written. Defaults to `200`; use `0` to persist each mutation immediately.
115
+
116
+ `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
+ `null` and `undefined` state values are intentionally omitted from persisted snapshots. Empty values handled by `pinia-plugin-subscription` are also omitted. `transformState` receives the resulting snapshot and must return the representation to persist; it must not mutate the store state. A transformer only changes the written representation, so use values that can be restored directly by the store or handle reconstruction in the store hydration flow.
106
119
 
107
120
  ---
108
121
 
109
- ## 🧰 Augmented Store API
122
+ ## Augmented Store API
110
123
 
111
124
  When the plugin is active stores gain the following methods (see `PersistedStore` interface):
112
125
 
113
126
  - `persistState(): Promise<void>` — Immediately persist the current store state (ignores empty values and excluded keys).
127
+ - `flushPersistedState(): Promise<void>` — Cancels a pending automatic persistence and immediately persists the current store state.
114
128
  - `remember(): Promise<void>` — Load persisted state and apply it to the store (used on plugin init).
115
- - `removePersistedState(): void` — Delete the persisted entry for this store.
129
+ - `removePersistedState(): Promise<void>` — Delete the persisted entry for this store after any running write completes.
116
130
  - `watch(): void` — Start watching for mutations (sets `watchMutation = true`).
117
131
  - `stopWatch(): void` — Stop auto-persisting on mutations (sets `watchMutation = false`).
118
132
 
@@ -120,21 +134,23 @@ Note: encrypted properties are automatically decrypted when remembered (if a cry
120
134
 
121
135
  ---
122
136
 
123
- ## 🔐 Encryption
137
+ ## Encryption
138
+
139
+ Optionally supply a `cryptKey` when creating the plugin, e.g. `createPersistStatePlugin({ storage: 'localStorage', cryptKey: 'my-secret' })`.
140
+ The plugin uses the Web Crypto API (PBKDF2 + AES-GCM) to encrypt properties listed in `persistedPropertiesToEncrypt` on each store. Only the specified properties will be encrypted. New encrypted values use a versioned `v1:salt:iv:ciphertext` format with a random salt and IV for every value; records in the previous `iv:ciphertext` format remain readable.
124
141
 
125
- Optionally supply a `cryptKey` when creating the plugin, e.g. `createPersistStatePlugin(undefined, 'my-secret')`.
126
- The plugin uses the Web Crypto API (PBKDF2 + AES-GCM) to encrypt properties listed in `persistedPropertiesToEncrypt` on each store. Only the specified properties will be encrypted.
142
+ The storage adapter remains responsible for serializing the persisted record: Window Storage uses JSON and IndexedDB stores native values. Encryption encodes only the selected value and restores its JSON type (string, number, boolean, array, or object). Encrypted values must be JSON-serializable; functions, symbols, `undefined`, cyclic objects, and `BigInt` are not supported.
127
143
 
128
144
  ---
129
145
 
130
- ## 💡 Notes
146
+ ## Notes
131
147
 
132
148
  - The plugin augments Pinia store definitions using the `pinia-plugin-subscription` helper. It adds `storeOptions` to Pinia's `DefineStoreOptionsBase` type through declaration merging.
133
149
  - The $reset method is available for stores augmented by the plugin (also setup store 😁).
134
- - When using IndexedDB, the persister stores objects with a `storeName` key path.
150
+ - With IndexedDB, `databaseName` identifies the application database, `objectStoreName` identifies the persisted record collection, and `persistenceKey` (or `$id`) identifies each record.
135
151
 
136
152
  ---
137
153
 
138
- ## 📜 License
154
+ ## License
139
155
 
140
156
  MIT
package/SECURITY.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  ## Reporting Security Vulnerabilities
4
4
 
5
- If you discover a security vulnerability in pinia-plugin-subscription, please do **NOT** open a public GitHub issue. Instead, please email the maintainers privately.
5
+ If you discover a security vulnerability in pinia-plugin-persist-state, please do **NOT** open a public GitHub issue. Instead, please email the maintainers privately.
6
6
 
7
7
  **Email:** [Security Contact - reach out via GitHub profile]
8
8
 
@@ -30,7 +30,7 @@ We will:
30
30
 
31
31
  ## Security Best Practices
32
32
 
33
- When using pinia-plugin-subscription:
33
+ When using pinia-plugin-persist-state:
34
34
 
35
35
  1. **Keep Dependencies Updated:** Regularly update Pinia, Vue, and other dependencies to the latest versions
36
36
  2. **Review Plugin Code:** Always review subscriber code before using in production
@@ -58,7 +58,7 @@ We monitor these dependencies for vulnerabilities and recommend keeping them upd
58
58
 
59
59
  This policy covers:
60
60
 
61
- - ✅ Code in the pinia-plugin-subscription repository
61
+ - ✅ Code in the pinia-plugin-persist-state repository
62
62
  - ✅ Published npm packages
63
63
  - ✅ Security of the plugin API
64
64
 
@@ -72,4 +72,4 @@ This policy does NOT cover:
72
72
 
73
73
  For general security questions or concerns, open a private security advisory on GitHub.
74
74
 
75
- Thank you for helping keep pinia-plugin-subscription secure! 🔒
75
+ Thank you for helping keep pinia-plugin-persist-state secure! 🔒