pinia-plugin-subscription 0.2.0-beta.0 → 0.2.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 +20 -1
- package/README.md +47 -341
- package/dist/chunks/{index-D-rnwSJk.js → index-VL_PgzVK.js} +27 -33
- package/dist/chunks/index-VL_PgzVK.js.map +1 -0
- package/dist/core/RollbackStateSnapshotsHandler.d.ts +1 -0
- package/dist/helpers.js +2 -1
- package/dist/helpers.js.map +1 -0
- package/dist/lib/main.d.ts +1 -1
- package/dist/pinia-plugin-subscription.js +4 -69
- package/dist/pinia-plugin-subscription.js.map +1 -0
- package/dist/plugins/createPlugin.d.ts +4 -1
- package/dist/tests/defineAStore.test-d.d.ts +1 -0
- package/dist/tests/storeOptionsExtensions.test-d.d.ts +12 -0
- package/dist/types/index.d.ts +1 -1
- package/dist/types/store.d.ts +32 -2
- package/dist/utils/deep-clone.d.ts +6 -0
- package/dist/utils/store.d.ts +77 -8
- package/docs/authoring-plugins.md +408 -0
- package/docs/using-plugins.md +374 -0
- package/package.json +2 -1
|
@@ -0,0 +1,374 @@
|
|
|
1
|
+
# Using compatible plugins
|
|
2
|
+
|
|
3
|
+
Guide for **application developers** who consume plugins built on
|
|
4
|
+
`pinia-plugin-subscription` (for example `pinia-plugin-extending-store` or
|
|
5
|
+
`pinia-plugin-persist-state`), or who use this package's built-in store
|
|
6
|
+
features directly.
|
|
7
|
+
|
|
8
|
+
If you want to **build your own pinia plugin** on top of this package, read
|
|
9
|
+
the [plugin authoring guide](authoring-plugins.md) instead.
|
|
10
|
+
|
|
11
|
+
> **Documentation convention:** JavaScript examples come first and are
|
|
12
|
+
> complete and runnable as-is. TypeScript versions follow, adding type
|
|
13
|
+
> annotations and generics. Types are always optional at runtime — they only
|
|
14
|
+
> add autocompletion and compile-time checks.
|
|
15
|
+
|
|
16
|
+
## What every compatible plugin guarantees
|
|
17
|
+
|
|
18
|
+
Plugins built on `pinia-plugin-subscription` share a common contract:
|
|
19
|
+
|
|
20
|
+
- **Single registration point** — all subscribers are registered through one
|
|
21
|
+
`createPlugin()` call.
|
|
22
|
+
- **`$reset` on registered stores** — every store registered by the plugin
|
|
23
|
+
gains a `$reset()` method that restores its initial state, including
|
|
24
|
+
setup-style stores where pinia does not provide one natively.
|
|
25
|
+
- **Per-plugin debug mode** — pass plugin names to `createPlugin()` to
|
|
26
|
+
enable detailed logging for those plugins only.
|
|
27
|
+
- **SSR-safe execution** — each plugin declares where (`client`, `server`
|
|
28
|
+
or `both`) and when (`immediate` or deferred after hydration) it runs.
|
|
29
|
+
- **State rollback on action failure** — actions listed in the
|
|
30
|
+
`rollbackAfterFailure` store option automatically restore their state
|
|
31
|
+
snapshot when they throw.
|
|
32
|
+
|
|
33
|
+
## Installation
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
npm install pinia pinia-plugin-subscription
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
## Registering compatible plugins
|
|
40
|
+
|
|
41
|
+
Compatible plugins expose **subscribers** that are passed to
|
|
42
|
+
`createPlugin()`.
|
|
43
|
+
|
|
44
|
+
### JavaScript
|
|
45
|
+
|
|
46
|
+
```js
|
|
47
|
+
import { createApp } from 'vue'
|
|
48
|
+
import { createPinia } from 'pinia'
|
|
49
|
+
import { createPlugin, PLUGIN_NAME } from 'pinia-plugin-subscription'
|
|
50
|
+
import { myStoreSubscriber } from './plugins/my-store'
|
|
51
|
+
import App from './App.vue'
|
|
52
|
+
|
|
53
|
+
const app = createApp(App)
|
|
54
|
+
const pinia = createPinia()
|
|
55
|
+
|
|
56
|
+
// Second argument (optional): names of the plugins to debug.
|
|
57
|
+
pinia.use(createPlugin([myStoreSubscriber], [PLUGIN_NAME]))
|
|
58
|
+
|
|
59
|
+
app.use(pinia)
|
|
60
|
+
app.mount('#app')
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
### TypeScript
|
|
64
|
+
|
|
65
|
+
The TypeScript version is identical — `createPlugin()` is fully typed and
|
|
66
|
+
requires no generics:
|
|
67
|
+
|
|
68
|
+
```ts
|
|
69
|
+
import { createPlugin, PLUGIN_NAME } from 'pinia-plugin-subscription'
|
|
70
|
+
import type { PluginSubscriberInterface } from 'pinia-plugin-subscription'
|
|
71
|
+
|
|
72
|
+
const subscribers: PluginSubscriberInterface[] = [myStoreSubscriber]
|
|
73
|
+
|
|
74
|
+
pinia.use(createPlugin(subscribers, [PLUGIN_NAME]))
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
## Defining stores: `defineAStore`
|
|
78
|
+
|
|
79
|
+
`defineAStore()` is a drop-in replacement for pinia's `defineStore()` that
|
|
80
|
+
wires the store to receive plugin options while preserving the full
|
|
81
|
+
TypeScript typing of the store and its state.
|
|
82
|
+
|
|
83
|
+
### Signature
|
|
84
|
+
|
|
85
|
+
```ts
|
|
86
|
+
defineAStore(id, storeDefinition, options?)
|
|
87
|
+
defineAStore<Sto, Sta>(id, storeDefinition, options?) // explicit typings
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
| Parameter | Type | Description |
|
|
91
|
+
| --- | --- | --- |
|
|
92
|
+
| `id` | `string` | Unique store identifier — same as pinia's `defineStore()`. |
|
|
93
|
+
| `storeDefinition` | options object \| setup function | Same shape as pinia's `defineStore()`: an options object (`state`, `getters`, `actions`) or a setup function returning state and actions. |
|
|
94
|
+
| `options` | `StoreOptions` (optional) | Per-store plugin options: the built-in `rollbackAfterFailure` key, plus any key contributed by installed plugins through `StoreOptionsExtensions`. |
|
|
95
|
+
|
|
96
|
+
### Type parameters (TypeScript only)
|
|
97
|
+
|
|
98
|
+
The type parameters are **optional**. When they are omitted, the state,
|
|
99
|
+
getters and actions are inferred from the store definition (the setup
|
|
100
|
+
function return or the options object), exactly like pinia's `defineStore()`
|
|
101
|
+
— IDEs list the store members and display action prototypes at every call
|
|
102
|
+
site.
|
|
103
|
+
|
|
104
|
+
| Generic | Role |
|
|
105
|
+
| --- | --- |
|
|
106
|
+
| `Sto` | Interface describing the store's exposed behavior (actions and getters). |
|
|
107
|
+
| `Sta` | Interface describing the store's state. |
|
|
108
|
+
|
|
109
|
+
Provide `Sto` and `Sta` explicitly when plugins augment the store with
|
|
110
|
+
members not returned by the setup function: the returned definition is then
|
|
111
|
+
typed as `DefineAStoreDefinition<Sto, Sta>`, whose call signature exposes
|
|
112
|
+
`Sto` and `Sta` on the store. `DefineAStoreDefinition` extends pinia's
|
|
113
|
+
`StoreDefinition`, so the returned `useStore` stays assignable wherever a
|
|
114
|
+
standard store definition is expected.
|
|
115
|
+
|
|
116
|
+
### JavaScript example
|
|
117
|
+
|
|
118
|
+
No generics, no annotations — the function behaves like `defineStore()`
|
|
119
|
+
plus plugin options (in TypeScript, the store members are inferred from the
|
|
120
|
+
returned object):
|
|
121
|
+
|
|
122
|
+
```js
|
|
123
|
+
import { ref } from 'vue'
|
|
124
|
+
import { defineAStore } from 'pinia-plugin-subscription'
|
|
125
|
+
|
|
126
|
+
export const useCounterStore = defineAStore('counter', () => {
|
|
127
|
+
const count = ref(0)
|
|
128
|
+
|
|
129
|
+
function increment() {
|
|
130
|
+
count.value++
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
return { count, increment }
|
|
134
|
+
}, {
|
|
135
|
+
rollbackAfterFailure: { increment: ['count'] }
|
|
136
|
+
})
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
### TypeScript example (setup style)
|
|
140
|
+
|
|
141
|
+
```ts
|
|
142
|
+
import { ref } from 'vue'
|
|
143
|
+
import { defineAStore } from 'pinia-plugin-subscription'
|
|
144
|
+
|
|
145
|
+
export interface CounterState {
|
|
146
|
+
count: number
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
export interface CounterStore {
|
|
150
|
+
increment: () => void
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
export const useCounterStore = defineAStore<CounterStore, CounterState>(
|
|
154
|
+
'counter',
|
|
155
|
+
() => {
|
|
156
|
+
const count = ref(0)
|
|
157
|
+
|
|
158
|
+
function increment() {
|
|
159
|
+
count.value++
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
return { count, increment }
|
|
163
|
+
}
|
|
164
|
+
)
|
|
165
|
+
|
|
166
|
+
// Consumption is fully typed:
|
|
167
|
+
// const store = useCounterStore()
|
|
168
|
+
// store.count → number
|
|
169
|
+
// store.increment() → void
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
### TypeScript example (options style)
|
|
173
|
+
|
|
174
|
+
```ts
|
|
175
|
+
import { defineAStore } from 'pinia-plugin-subscription'
|
|
176
|
+
|
|
177
|
+
export interface ResourceId {
|
|
178
|
+
'@id'?: string
|
|
179
|
+
id?: string
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
export const useResourceIdStore = defineAStore('resourceId', {
|
|
183
|
+
state: (): ResourceId => ({
|
|
184
|
+
'@id': undefined,
|
|
185
|
+
id: undefined
|
|
186
|
+
}),
|
|
187
|
+
|
|
188
|
+
actions: {
|
|
189
|
+
setData(data: Partial<ResourceId>) {
|
|
190
|
+
if (data['@id']) { this['@id'] = data['@id'] }
|
|
191
|
+
if (data.id) { this.id = data.id }
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
})
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
## Using plugin-added features at definition time: `defineAStoreCtx`
|
|
198
|
+
|
|
199
|
+
When a plugin adds state or actions to your stores (an "enhanced store"),
|
|
200
|
+
`defineAStoreCtx()` lets your setup function use those additions while the
|
|
201
|
+
store is being defined.
|
|
202
|
+
|
|
203
|
+
### Signature
|
|
204
|
+
|
|
205
|
+
```ts
|
|
206
|
+
defineAStoreCtx(id, setup, options?)
|
|
207
|
+
defineAStoreCtx<Sto, Sta, TExtraExtensions>(id, setup, options?) // explicit typings
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
| Generic | Role |
|
|
211
|
+
| --- | --- |
|
|
212
|
+
| `Sto` | Store behavior interface (as in `defineAStore`, optional — inferred when omitted). |
|
|
213
|
+
| `Sta` | Store state interface (as in `defineAStore`, optional — inferred when omitted). |
|
|
214
|
+
| `TExtraExtensions` | Extra extension keys available on `ctx.extensions` (optional). |
|
|
215
|
+
|
|
216
|
+
The setup function receives a `ctx` object with `{ id, extensions }`. Use
|
|
217
|
+
`getEnhancedStore<T>()` to retrieve the plugin-added store API:
|
|
218
|
+
|
|
219
|
+
```ts
|
|
220
|
+
import { defineAStoreCtx, getEnhancedStore } from 'pinia-plugin-subscription'
|
|
221
|
+
|
|
222
|
+
type EnhancedStore = { addItem: (value: unknown) => void }
|
|
223
|
+
|
|
224
|
+
export const useMyStore = defineAStoreCtx('myStore', (ctx) => {
|
|
225
|
+
const enhanced = getEnhancedStore<EnhancedStore>(ctx)
|
|
226
|
+
|
|
227
|
+
return {
|
|
228
|
+
addViaEnhanced: (value: unknown) => enhanced.addItem(value)
|
|
229
|
+
}
|
|
230
|
+
})
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
`defineAStoreCtx()` enables the `enhancedStore` store option automatically.
|
|
234
|
+
The internal setup-context lookup is cleaned up as soon as the store is
|
|
235
|
+
associated (and on store dispose), which limits map growth over time. When
|
|
236
|
+
`storeOptions.debug` is `true`, setup-context map size transitions are
|
|
237
|
+
logged for observability.
|
|
238
|
+
|
|
239
|
+
## `$reset` on registered stores
|
|
240
|
+
|
|
241
|
+
Every store registered by the plugin exposes `$reset()`, which restores the
|
|
242
|
+
initial state captured at registration — including for setup-style stores:
|
|
243
|
+
|
|
244
|
+
```js
|
|
245
|
+
const store = useCounterStore()
|
|
246
|
+
store.increment()
|
|
247
|
+
store.$reset() // state is back to its initial value
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
Each compatible plugin may also run custom logic on reset — see the plugin's
|
|
251
|
+
own documentation.
|
|
252
|
+
|
|
253
|
+
## State rollback on action failure
|
|
254
|
+
|
|
255
|
+
The plugin can automatically restore the previous state when an action
|
|
256
|
+
throws. Declare the state keys to snapshot per action via the
|
|
257
|
+
`rollbackAfterFailure` store option:
|
|
258
|
+
|
|
259
|
+
```js
|
|
260
|
+
import { ref } from 'vue'
|
|
261
|
+
import { defineAStore } from 'pinia-plugin-subscription'
|
|
262
|
+
|
|
263
|
+
export const useMyStore = defineAStore('myStore', () => {
|
|
264
|
+
const count = ref(0)
|
|
265
|
+
|
|
266
|
+
function riskyAction() {
|
|
267
|
+
count.value += 1
|
|
268
|
+
throw new Error('Something went wrong')
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
return { count, riskyAction }
|
|
272
|
+
}, {
|
|
273
|
+
rollbackAfterFailure: {
|
|
274
|
+
// only the listed keys are snapshotted / restored (deep-cloned)
|
|
275
|
+
riskyAction: ['count']
|
|
276
|
+
}
|
|
277
|
+
})
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
Before a configured action runs, the declared state keys are snapshotted.
|
|
281
|
+
If the action throws (synchronously or by rejecting its returned promise),
|
|
282
|
+
the snapshot is restored through `$patch`, so partial mutations are rolled
|
|
283
|
+
back. The snapshot is always discarded once the action completes or fails.
|
|
284
|
+
|
|
285
|
+
Only the actions listed in `rollbackAfterFailure` are snapshotted, so
|
|
286
|
+
unrelated actions pay no cloning cost. Use `'all'` instead of a key list to
|
|
287
|
+
snapshot the full state — prefer explicit keys for large states.
|
|
288
|
+
|
|
289
|
+
Action names starting with `_` or `$` are rejected to protect internal store
|
|
290
|
+
methods.
|
|
291
|
+
|
|
292
|
+
### Manual rollback with `$rollbackAfterFailure`
|
|
293
|
+
|
|
294
|
+
Every store registered by the plugin also exposes a `$rollbackAfterFailure`
|
|
295
|
+
method to run any action with an explicit snapshot/rollback wrapper, without
|
|
296
|
+
declaring it in the store options:
|
|
297
|
+
|
|
298
|
+
```js
|
|
299
|
+
const store = useMyStore()
|
|
300
|
+
|
|
301
|
+
try {
|
|
302
|
+
await store.$rollbackAfterFailure(
|
|
303
|
+
{ action: 'riskyAction', stateKeys: ['count'] },
|
|
304
|
+
['firstArgument', 'secondArgument'] // action arguments, as a single array
|
|
305
|
+
)
|
|
306
|
+
} catch (error) {
|
|
307
|
+
// the action failed and the state was restored
|
|
308
|
+
}
|
|
309
|
+
```
|
|
310
|
+
|
|
311
|
+
- `params.action` — the name of the action to execute.
|
|
312
|
+
- `params.stateKeys` — optional; the state keys to snapshot, or `'all'`
|
|
313
|
+
(default when omitted or empty).
|
|
314
|
+
- `args` — optional; the action arguments, provided as a single array
|
|
315
|
+
(forwarded to the action via `apply()`).
|
|
316
|
+
- The original error is rethrown after the state is restored.
|
|
317
|
+
|
|
318
|
+
## Debug mode
|
|
319
|
+
|
|
320
|
+
Pass the names of the plugins you want to inspect as the second argument of
|
|
321
|
+
`createPlugin()`:
|
|
322
|
+
|
|
323
|
+
```js
|
|
324
|
+
pinia.use(createPlugin([myStoreSubscriber], [PLUGIN_NAME, 'my-plugin']))
|
|
325
|
+
```
|
|
326
|
+
|
|
327
|
+
Matching plugins then emit detailed logs (registration, execution policy,
|
|
328
|
+
hydration lifecycle).
|
|
329
|
+
|
|
330
|
+
## SSR / Nuxt
|
|
331
|
+
|
|
332
|
+
The package is SSR-safe by design. Each compatible plugin declares its own
|
|
333
|
+
execution policy, so nothing browser-only runs on the server unless the
|
|
334
|
+
plugin explicitly allows it.
|
|
335
|
+
|
|
336
|
+
When a plugin needs a runtime-specific scheduler or environment override —
|
|
337
|
+
without forcing every app to re-declare the plugin's execution policy —
|
|
338
|
+
register it through the dedicated hydration helper:
|
|
339
|
+
|
|
340
|
+
```js
|
|
341
|
+
import { nextTick } from 'vue'
|
|
342
|
+
import { createHydrationPlugin, PLUGIN_NAME } from 'pinia-plugin-subscription'
|
|
343
|
+
|
|
344
|
+
pinia.use(createHydrationPlugin([persistedStateSubscriber], {
|
|
345
|
+
debug: [PLUGIN_NAME],
|
|
346
|
+
hydrationScheduler: (run) => {
|
|
347
|
+
nextTick(() => run())
|
|
348
|
+
}
|
|
349
|
+
}))
|
|
350
|
+
```
|
|
351
|
+
|
|
352
|
+
`createHydrationPlugin(subscribers, options?)` accepts
|
|
353
|
+
`PluginSubscriptionOptions`:
|
|
354
|
+
|
|
355
|
+
| Option | Type | Description |
|
|
356
|
+
| --- | --- | --- |
|
|
357
|
+
| `debug` | `string[]` | Plugin names to debug. |
|
|
358
|
+
| `execution` | `PluginExecutionOptions` | Global execution policy override. |
|
|
359
|
+
| `hydrationScheduler` | `(run: () => void) => void` | Scheduler used for deferred hydration. |
|
|
360
|
+
| `runtimeEnvironment` | `'client' \| 'server'` | Forces the runtime environment. |
|
|
361
|
+
| `subscriberExecution` | `Record<string, PluginExecutionOptions>` | Per-subscriber execution overrides, keyed by subscriber name. |
|
|
362
|
+
|
|
363
|
+
If you are authoring a plugin and need to declare its SSR policy, see the
|
|
364
|
+
[execution policy & SSR section](authoring-plugins.md#execution-policy--ssr)
|
|
365
|
+
of the authoring guide.
|
|
366
|
+
|
|
367
|
+
## Ecosystem
|
|
368
|
+
|
|
369
|
+
Plugins built on `pinia-plugin-subscription`:
|
|
370
|
+
|
|
371
|
+
- `pinia-plugin-extending-store`
|
|
372
|
+
- `pinia-plugin-persist-state`
|
|
373
|
+
|
|
374
|
+
Check each plugin's own README for its specific store options and behavior.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pinia-plugin-subscription",
|
|
3
|
-
"version": "0.2.0
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "Pinia plugin for Vue.js that helps building Pinia plugins by centralizing subscriber registration and providing a Store base class for store helpers.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"sideEffects": false,
|
|
@@ -51,6 +51,7 @@
|
|
|
51
51
|
},
|
|
52
52
|
"files": [
|
|
53
53
|
"dist",
|
|
54
|
+
"docs",
|
|
54
55
|
"CHANGELOG.md",
|
|
55
56
|
"CONTRIBUTING.md",
|
|
56
57
|
"SECURITY.md"
|