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.
@@ -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-beta.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"