pinia-plugin-subscription 0.1.6 → 0.1.7-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,6 +5,18 @@ 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.7]
9
+
10
+ ### Changed
11
+ - Hardened SSR hydration sequencing so `hydrate()` always runs before `afterHydration()` and lifecycle errors are caught without breaking store registration.
12
+ - Improved reset-state cloning to avoid JSON-only pitfalls during `$reset`, including structured values such as `Map` and `Set`.
13
+ - Ensured tracking metadata is cleared when a store is disposed to prevent stale subscriber state.
14
+ - Tightened internal store checks to avoid `hasOwnProperty` collisions.
15
+ - Reduced library bundle overhead by stripping the Vue plugin from the library build and treeshaking the published bundle more aggressively.
16
+ - Added additive sub-exports for helper-only imports and a strict type-only public surface to keep the core API stable while improving bundle clarity.
17
+ - Added a real bundle-size check command to measure the emitted output before deciding whether a heavier split is justified.
18
+ - Documented Nuxt / SSR best practices for `hydration: 'defer'`, `runtimeEnvironment`, and browser-only access in hydrated stores.
19
+
8
20
  ## [0.1.6]
9
21
 
10
22
  ### Added
package/README.md CHANGED
@@ -14,6 +14,26 @@
14
14
 
15
15
  Ensure Pinia is installed, then register the plugin in your `main.ts`:
16
16
 
17
+ For local development and runtime demos, use the dedicated demo app with `npm run dev`. The library build remains `npm run build` and is intentionally kept separate from the demo entry.
18
+
19
+ For helper-only imports, the package also exposes an additive subpath that is kept separate from the default library API:
20
+
21
+ ```typescript
22
+ import { PluginSubscriber, Store } from 'pinia-plugin-subscription/helpers'
23
+ ```
24
+
25
+ For type-only imports, prefer the dedicated public types entry:
26
+
27
+ ```typescript
28
+ import type { PluginSubscriptionOptions } from 'pinia-plugin-subscription/types'
29
+ ```
30
+
31
+ The project also includes a bundle check command to measure the real output size before introducing a heavier split strategy:
32
+
33
+ ```bash
34
+ npm run bundle:check
35
+ ```
36
+
17
37
  ```typescript
18
38
  import { createApp } from 'vue'
19
39
  import { createPinia } from 'pinia'
@@ -84,6 +104,7 @@ export const myStoreSubscriber: PluginSubscriberInterface = {
84
104
  - **Debug mode:** Specifies the name of the plugin(s) to debug as the second argument to `createPlugin` to enable detailed logging.
85
105
  - **Reset callbacks:** Define `resetStoreCallback` to run custom logic when a store is reset.
86
106
  - **SSR and hydration control:** Configure whether each subscribed plugin runs on the server, the client, or only after client hydration.
107
+ - **Lifecycle safety:** Internal hydration ordering is now guarded so `hydrate()` completes before `afterHydration()` and disposed stores do not retain stale subscriber metadata.
87
108
 
88
109
  ## API Reference
89
110
 
@@ -135,6 +156,61 @@ Available execution options:
135
156
  - `hydration: 'immediate' | 'defer'` — on the client, `defer` schedules execution after hydration.
136
157
  - `hydrationScheduler` can be provided either on a subscriber or via `createHydrationPlugin()` to override the execution timing when needed.
137
158
 
159
+ ### SSR / Nuxt best practices
160
+
161
+ `pinia-plugin-subscription` is SSR-safe by design, but the runtime policy still needs to match the actual app lifecycle.
162
+
163
+ Use `hydration: 'defer'` when the subscriber depends on browser-only APIs, client-side cookies, localStorage, DOM access, or a hydrated app state that must exist only after the app is mounted.
164
+
165
+ ```ts
166
+ const persistedStateSubscriber: PluginSubscriberInterface = {
167
+ name: 'persisted-state',
168
+ execution: {
169
+ environment: 'client',
170
+ hydration: 'defer',
171
+ },
172
+ hydrationScheduler: (run) => nextTick(() => run()),
173
+ invoke: () => true,
174
+ }
175
+ ```
176
+
177
+ Use `runtimeEnvironment: 'server'` or `execution.environment: 'server'` when a subscriber is only valid during server-side rendering or when it should not touch browser-only objects.
178
+
179
+ ```ts
180
+ pinia.use(createHydrationPlugin([serverOnlySubscriber], {
181
+ runtimeEnvironment: 'server'
182
+ }))
183
+ ```
184
+
185
+ Avoid browser-only code in stores during build-time SSR. In practice, that means:
186
+
187
+ - do not access `window`, `document`, `localStorage`, or `matchMedia` at module scope or during store setup when the code may run on the server,
188
+ - keep browser-specific bootstrap in `hydrate()` or `afterHydration()`,
189
+ - prefer environment-aware guards such as `typeof window !== 'undefined'`,
190
+ - avoid doing network access or hydration reads before the client is ready unless the code is explicitly intended to run on both environments.
191
+
192
+ A safe pattern is:
193
+
194
+ ```ts
195
+ const mySubscriber: PluginSubscriberInterface = {
196
+ name: 'safe-browser-access',
197
+ execution: {
198
+ environment: 'client',
199
+ hydration: 'defer',
200
+ },
201
+ hydrate: (context) => {
202
+ // safe browser access here after hydration is allowed
203
+ const saved = localStorage.getItem('demo')
204
+ if (saved) {
205
+ context.store.$patch({ value: JSON.parse(saved) })
206
+ }
207
+ },
208
+ invoke: () => true,
209
+ }
210
+ ```
211
+
212
+ This keeps the server render deterministic and prevents browser-only initialization from crashing Nuxt or SSR builds.
213
+
138
214
  ### `defineAStoreCtx(id, setup, options?)`
139
215
 
140
216
  Use actions or access the state, added by one or more plugins, when defining the store.
@@ -0,0 +1,338 @@
1
+ var f = Object.defineProperty;
2
+ var b = (s, t, e) => t in s ? f(s, t, { enumerable: !0, configurable: !0, writable: !0, value: e }) : s[t] = e;
3
+ var n = (s, t, e) => b(s, typeof t != "symbol" ? t + "" : t, e);
4
+ import { ref as S, toRef as y } from "vue";
5
+ import { defineStore as g } from "pinia";
6
+ class L {
7
+ constructor(t, e, r) {
8
+ n(this, "_console");
9
+ n(this, "_createInstance");
10
+ n(this, "_name");
11
+ n(this, "_pluginOptions");
12
+ n(this, "_storeInstance");
13
+ n(this, "_resetStoreCallback");
14
+ n(this, "_storeOnActionSubscription");
15
+ n(this, "_storeMutationSubscription");
16
+ n(this, "_subscriptions");
17
+ n(this, "pluginCreated");
18
+ n(this, "execution");
19
+ n(this, "hydrationScheduler");
20
+ this._name = t, this._createInstance = e, r && (this._console = r);
21
+ }
22
+ get console() {
23
+ return this._console ?? console;
24
+ }
25
+ get name() {
26
+ return this._name;
27
+ }
28
+ get storeInstance() {
29
+ return this._storeInstance;
30
+ }
31
+ get pluginOptions() {
32
+ return this._pluginOptions ?? {};
33
+ }
34
+ set pluginOptions(t) {
35
+ this._pluginOptions = t;
36
+ }
37
+ get resetStoreCallback() {
38
+ return this._resetStoreCallback;
39
+ }
40
+ get storeOnActionSubscription() {
41
+ return this._storeOnActionSubscription;
42
+ }
43
+ get storeMutationSubscription() {
44
+ return this._storeMutationSubscription;
45
+ }
46
+ get subscriptions() {
47
+ return this._subscriptions;
48
+ }
49
+ set subscriptions(t) {
50
+ this._subscriptions = t;
51
+ }
52
+ hydrate(t, e) {
53
+ }
54
+ afterHydration(t, e) {
55
+ }
56
+ invoke({ store: t, options: e }, r) {
57
+ return this._storeInstance = this._createInstance(t, { ...e, ...this.pluginOptions }, r, this.console), this._storeInstance ? (this._subscriptions = this._storeInstance.getSubscriptions(), this._storeMutationSubscription = this._storeInstance.storeSubscribe, this._storeOnActionSubscription = this._storeInstance.onAction, this.pluginCreated && this.pluginCreated(t), !0) : !1;
58
+ }
59
+ }
60
+ class C {
61
+ constructor(t, e) {
62
+ n(this, "_console");
63
+ n(this, "_debug", !1);
64
+ this._debug = t, e && (this._console = e);
65
+ }
66
+ get console() {
67
+ return this._console ?? console;
68
+ }
69
+ set console(t) {
70
+ this._console = t;
71
+ }
72
+ get debug() {
73
+ return this._debug;
74
+ }
75
+ set debug(t) {
76
+ this._debug = t;
77
+ }
78
+ debugLog(...t) {
79
+ this._debug && (Array.isArray(t) && (t[0] = `${this._className} - ${t[0]}`), this.console.log(...t));
80
+ }
81
+ logError(...t) {
82
+ this.console.error(...t);
83
+ }
84
+ }
85
+ const p = /* @__PURE__ */ new WeakMap(), c = /* @__PURE__ */ new Map(), l = /* @__PURE__ */ new WeakSet(), A = 50;
86
+ function O(s) {
87
+ return s?.storeOptions?.debug === !0;
88
+ }
89
+ function h(s, t, e) {
90
+ if (!O(t))
91
+ return;
92
+ const r = c.size;
93
+ console.debug(`[defineAStoreSetup] ${s} - ${e} (contextsById=${r})`), r >= A && console.warn(`[defineAStoreSetup] contextsById reached ${r} entries`);
94
+ }
95
+ function x(s, t, e) {
96
+ if (l.has(s) || typeof s.$dispose != "function")
97
+ return;
98
+ const r = s.$dispose;
99
+ s.$dispose = (...o) => (p.delete(s), c.delete(t), h(t, e, "dispose cleanup"), r.apply(s, o)), l.add(s);
100
+ }
101
+ function N(s, t, e) {
102
+ const r = e ? { storeOptions: e } : {};
103
+ return typeof t == "function" ? d(
104
+ s,
105
+ t,
106
+ r
107
+ ) : m(
108
+ s,
109
+ t,
110
+ r
111
+ );
112
+ }
113
+ function R(s, t, e) {
114
+ const r = e ? { storeOptions: { ...e, enhancedStore: !0 } } : { storeOptions: { enhancedStore: !0 } };
115
+ return d(
116
+ s,
117
+ t,
118
+ r
119
+ );
120
+ }
121
+ function d(s, t, e) {
122
+ const r = {
123
+ id: s,
124
+ extensions: {}
125
+ }, o = e?.storeOptions?.enhancedStore === !0, i = g(s, () => t(r), e);
126
+ return Object.assign(((...u) => {
127
+ o && (c.set(s, r), h(s, e, "registered context by id"));
128
+ const a = i(...u);
129
+ return o && (p.set(a, r), c.delete(s), h(s, e, "moved context to weak map"), x(a, s, e)), a;
130
+ }), i);
131
+ }
132
+ function m(s, t, e) {
133
+ return e && (t = { ...t, ...e ?? {} }), g(s, t);
134
+ }
135
+ const $ = /* @__PURE__ */ new Set(["_", "$"]);
136
+ function I(s) {
137
+ return $.has(s[0]);
138
+ }
139
+ function w(s) {
140
+ return p.get(s) ?? (typeof s?.$id == "string" ? c.get(s.$id) : void 0);
141
+ }
142
+ function k(s) {
143
+ const t = s.extensions.enhancedStore ?? s.extensions.extending;
144
+ if (!t)
145
+ throw new Error(`${s.id}Store - getEnhancedStore - Error: enhanced store is required`);
146
+ return t;
147
+ }
148
+ function M(s, t) {
149
+ s.extensions.enhancedStore = t, s.extensions.extending = t;
150
+ }
151
+ function T(s) {
152
+ return k(s);
153
+ }
154
+ function E(s) {
155
+ return s ? Array.isArray(s) ? s.length === 0 : typeof s == "object" ? Object.keys(s).length === 0 : typeof s == "string" ? s.trim() === "" : !1 : !(typeof s == "boolean" || typeof s == "number");
156
+ }
157
+ class P extends C {
158
+ constructor(e, r, o = !1, i) {
159
+ super(o, i);
160
+ n(this, "_className", "Store");
161
+ n(this, "_onAction");
162
+ n(this, "_options");
163
+ n(this, "_store");
164
+ n(this, "_subscriptions", {});
165
+ n(this, "_storeSubscribe");
166
+ if (this._options = r.storeOptions, this._store = e, this._options?.enhancedStore) {
167
+ const u = w(this.store);
168
+ u && M(u, this.store);
169
+ }
170
+ }
171
+ get onAction() {
172
+ if (this._onAction)
173
+ return () => ({
174
+ store: this.store,
175
+ callback: this._onAction
176
+ });
177
+ }
178
+ set onAction(e) {
179
+ this._onAction = e;
180
+ }
181
+ get options() {
182
+ return this._options;
183
+ }
184
+ get state() {
185
+ return this._store.$state;
186
+ }
187
+ set state(e) {
188
+ this._store.$state = e;
189
+ }
190
+ get store() {
191
+ return this._store;
192
+ }
193
+ hydrate() {
194
+ }
195
+ afterHydration() {
196
+ }
197
+ get storeSubscribe() {
198
+ if (this._storeSubscribe)
199
+ return () => ({
200
+ store: this.store,
201
+ callback: this._storeSubscribe
202
+ });
203
+ }
204
+ set storeSubscribe(e) {
205
+ this._storeSubscribe = e;
206
+ }
207
+ /**
208
+ * Add property to state
209
+ * @param name
210
+ * @param value
211
+ */
212
+ addToState(e, r) {
213
+ this.isOptionApi() || r?.value || (r = S(r)), this.state[e] = r, this.store[e] = y(this.state, e);
214
+ }
215
+ addSubscription(e, r, o) {
216
+ this._subscriptions[e] = { subscription: r, ...o ?? {} };
217
+ }
218
+ /**
219
+ * Create and return a class instance
220
+ * @param store
221
+ * @param options
222
+ * @param debug
223
+ * @returns
224
+ */
225
+ static customizeStore(e, r, o = !1, i) {
226
+ if (r.storeOptions && this.hasRequiredKeys(r.storeOptions))
227
+ return new this(e, r, o, i);
228
+ }
229
+ hasDeniedFirstChar(e) {
230
+ return I(e[0]);
231
+ }
232
+ static hasRequiredKeys(e) {
233
+ return this._requiredKeys !== void 0 && this._requiredKeys?.every((r) => !!e[r]);
234
+ }
235
+ getOption(e) {
236
+ return this.options && this.options[e];
237
+ }
238
+ getStatePropertyValue(e) {
239
+ return this.getValue(this.state[e]);
240
+ }
241
+ getSubscriptions() {
242
+ if (!E(this._subscriptions))
243
+ return this._subscriptions;
244
+ }
245
+ getValue(e) {
246
+ return e?.__v_isRef ? e.value : e;
247
+ }
248
+ isOptionApi() {
249
+ return this.store._isOptionsAPI;
250
+ }
251
+ stateHas(e) {
252
+ return Object.prototype.hasOwnProperty.call(this.state, e);
253
+ }
254
+ storeHas(e) {
255
+ return Object.prototype.hasOwnProperty.call(this.store, e);
256
+ }
257
+ }
258
+ n(P, "_requiredKeys");
259
+ const _ = "pinia-plugin-subscription", q = (s, t = "white") => `background-color: ${s}; color: ${t}; padding: 1px; margin-right: 5px; font-size: 12px`;
260
+ function B(s, t) {
261
+ if (!s)
262
+ throw new Error("Style instructions are required");
263
+ console.log(
264
+ "%c%s",
265
+ s,
266
+ t.shift(),
267
+ ...t
268
+ );
269
+ }
270
+ class D {
271
+ constructor() {
272
+ n(this, "_bgColor", "#ffec73");
273
+ n(this, "_color", "green");
274
+ n(this, "_icon", "🍍⚡");
275
+ n(this, "_errorBgColor", "#d24545");
276
+ n(this, "_errorColor", "white");
277
+ n(this, "_errorIcon", "🍍⚠️");
278
+ n(this, "_styles", {
279
+ error: { bgColor: this._errorBgColor, color: this._errorColor, icon: this._errorIcon },
280
+ info: { bgColor: this._bgColor, color: this._color, icon: this._icon }
281
+ });
282
+ }
283
+ formatMessage(t, e) {
284
+ return ` [${e} ${_}] - ${t} `;
285
+ }
286
+ generateStyle(t) {
287
+ return q(t.bgColor, t.color);
288
+ }
289
+ getStyleDefinition(t) {
290
+ return this._styles[t ?? "info"];
291
+ }
292
+ log(...t) {
293
+ this.useLog(
294
+ this.getStyleDefinition(),
295
+ t
296
+ );
297
+ }
298
+ error(...t) {
299
+ this.useLog(
300
+ this.getStyleDefinition("error"),
301
+ t
302
+ );
303
+ }
304
+ rebuildArgs(t, e) {
305
+ if (typeof t == "string")
306
+ return [this.formatMessage(t, e)];
307
+ if (Array.isArray(t))
308
+ return [this.formatMessage(t.shift(), e), ...t ?? []];
309
+ }
310
+ useLog(t, e) {
311
+ B(
312
+ this.generateStyle(t),
313
+ this.rebuildArgs(e, t.icon)
314
+ );
315
+ }
316
+ }
317
+ class j extends D {
318
+ constructor() {
319
+ super(...arguments);
320
+ n(this, "_pluginName", _);
321
+ }
322
+ }
323
+ const W = new j();
324
+ export {
325
+ D as C,
326
+ C as D,
327
+ W as P,
328
+ P as S,
329
+ L as a,
330
+ R as b,
331
+ k as c,
332
+ N as d,
333
+ T as e,
334
+ w as g,
335
+ E as i,
336
+ _ as p,
337
+ M as s
338
+ };
@@ -0,0 +1,53 @@
1
+ import Debug from "../system/Debug";
2
+ import type { PluginSubscriptions, StoreMutationSubscription, StoreMutationSubscriptionCallback, StoreOnActionSubscription, StoreOnActionSubscriptionCallback } from "../types/plugin";
3
+ import type { Store as PiniaStore, StateTree } from "pinia";
4
+ import type { Console } from "../types/log";
5
+ import type { AnyObject, PluginSubscriberInterface } from "../types";
6
+ import type { StoreOptions, StatePropertyValue } from "../types/store";
7
+ export default class Store extends Debug {
8
+ protected _className: string;
9
+ private _onAction?;
10
+ private _options;
11
+ private _store;
12
+ private _subscriptions;
13
+ private _storeSubscribe?;
14
+ protected static _requiredKeys?: string[];
15
+ get onAction(): StoreOnActionSubscription | undefined;
16
+ set onAction(onAction: StoreOnActionSubscriptionCallback);
17
+ get options(): StoreOptions;
18
+ get state(): StateTree;
19
+ set state(state: StateTree);
20
+ get store(): AnyObject;
21
+ hydrate(): void | Promise<void>;
22
+ afterHydration(): void | Promise<void>;
23
+ get storeSubscribe(): StoreMutationSubscription | undefined;
24
+ set storeSubscribe(storeSubscribe: StoreMutationSubscriptionCallback);
25
+ constructor(store: PiniaStore, options: AnyObject, debug?: boolean, customConsole?: Console);
26
+ /**
27
+ * Add property to state
28
+ * @param name
29
+ * @param value
30
+ */
31
+ addToState(name: string, value?: StatePropertyValue): void;
32
+ addSubscription(pluginName: string, subscription: PluginSubscriberInterface, options?: {
33
+ subscriptionOptions?: StoreOptions;
34
+ stores?: PiniaStore[];
35
+ }): void;
36
+ /**
37
+ * Create and return a class instance
38
+ * @param store
39
+ * @param options
40
+ * @param debug
41
+ * @returns
42
+ */
43
+ static customizeStore<Instance extends Store>(store: PiniaStore, options: AnyObject, debug?: boolean, customConsole?: Console): Instance | undefined;
44
+ hasDeniedFirstChar(property: string): boolean;
45
+ protected static hasRequiredKeys(options: AnyObject): boolean;
46
+ getOption(optionName: keyof StoreOptions): import("../types").StoreOptionsPropertyValue;
47
+ getStatePropertyValue(propertyName: string): any;
48
+ getSubscriptions(): PluginSubscriptions | undefined;
49
+ getValue(value: any): any;
50
+ isOptionApi(): boolean;
51
+ stateHas(property: string): boolean;
52
+ storeHas(property: string): boolean;
53
+ }
@@ -0,0 +1,4 @@
1
+ export { default as PluginSubscriber } from '../plugins/pluginSubscriber';
2
+ export { default as Store } from '../core/Store';
3
+ export { CustomConsole } from '../system/log';
4
+ export { isEmpty } from '../utils/validation';
@@ -0,0 +1,7 @@
1
+ import { C as a, a as r, S as e, i } from "./chunks/index-ByASkYCc.js";
2
+ export {
3
+ a as CustomConsole,
4
+ r as PluginSubscriber,
5
+ e as Store,
6
+ i as isEmpty
7
+ };
@@ -0,0 +1,14 @@
1
+ import PluginSubscriber from '../plugins/pluginSubscriber';
2
+ import PluginSubscription from '../plugins/pluginSubscription';
3
+ import Store from '../core/Store';
4
+ export { createPlugin } from "../plugins/createPlugin";
5
+ export { createHydrationPlugin } from "../plugins/createHydrationPlugin";
6
+ export type { AnyObject, Console, CustomStore, DefineAStoreSetupContext, DefineAStoreSetupExtensions, EmptyExtensions, PluginExecutionEnvironment, PluginExecutionOptions, PluginHydrationScheduler, PluginHydrationTiming, PluginRuntimeEnvironment, PluginStoreOptions, PluginSubscriptionOptions, PluginSubscriberInterface, PluginSubscriptionDefinition, PluginSubscriptions, StatePropertyValue, StoreOptions, StoreOptionsPropertyValue, StoreMutationSubscription, StoreMutationSubscriptionCallback, StoreOnActionCallbackParameters, StoreOnActionSubscription, StoreOnActionSubscriptionCallback, NativePiniaSubscription, NativePiniaSubscriptionReturn, SearchCollectionCriteria, LogType, StyleDefinition, StyleDefinitionKeys, StyleDefinitions, ConsoleStyleDefinition, ConsoleStyleDefinitionKeys, ConsoleStyleDefinitions, PartialList, ListTypes, ListTypesMap, List } from "../types";
7
+ export type { StoreMutationSubscriptionReturn, StoreOnActionSubscriptionReturn } from "../types/plugin";
8
+ export { defineAStore, defineAStoreCtx, getDefineAStoreSetupContext, getEnhancedStore, getExtendingStore, setEnhancedStore } from "../utils/store";
9
+ export { CustomConsole } from "../system/log";
10
+ export { isEmpty } from "../utils/validation";
11
+ export { pluginName as PLUGIN_NAME } from "../utils/constantes";
12
+ export { PluginSubscriber };
13
+ export { PluginSubscription };
14
+ export { Store };