@atoapayments/pinia-store-lifecycle-manager 0.0.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/README.md ADDED
@@ -0,0 +1,320 @@
1
+ # Pinia Store Lifecycle Manager
2
+
3
+ A robust Pinia plugin designed to manage store lifecycles seamlessly, including state resets and refreshes. Enhance your Vue applications with streamlined state management tailored for dynamic development needs.
4
+
5
+ ## Table of Contents
6
+ - [Installation](#installation)
7
+ - [Usage](#usage)
8
+ - [Basic Setup](#basic-setup)
9
+ - [Advanced Configuration](#advanced-configuration)
10
+ - [Features](#features)
11
+ - [Use Cases](#use-cases)
12
+ - [API Reference](#api-reference)
13
+ - [Why Choose Pinia Store Lifecycle Manager](#why-choose-pinia-store-lifecycle-manager)
14
+ - [License](#license)
15
+
16
+ ## Installation
17
+
18
+ You can install the Pinia Store Lifecycle Manager using npm or Yarn:
19
+
20
+ ```bash
21
+ npm install @atoapayments/pinia-store-lifecycle-manager
22
+ # or
23
+ yarn add @atoapayments/pinia-store-lifecycle-manager
24
+ ```
25
+
26
+ ## Usage
27
+
28
+ ### Basic Setup
29
+
30
+ ```typescript
31
+ //vue
32
+ import { createPinia } from "pinia";
33
+ import { PiniaStoreLifecycleManager } from "pinia-store-lifecycle-manager";
34
+
35
+ const pinia = createPinia();
36
+ pinia.use((context) => {
37
+ PiniaStoreLifecycleManager(context, (lifecycleEvents) => {
38
+ // Register your lifecycle event handlers here
39
+ // For example:
40
+ // lifecycleEvents.reset();
41
+ // lifecycleEvents.reconfigure();
42
+ // lifecycleEvents.refresh({ mode: 'full' });
43
+ });
44
+ });
45
+ ```
46
+
47
+ #### Nuxt
48
+
49
+ ```typescript
50
+ // \plugin\pinia-store-lifecycle-manager.ts
51
+ import type { Pinia } from "pinia";
52
+ import { PiniaStoreLifecycleManager } from "pinia-store-lifecycle-manager";
53
+
54
+ export default defineNuxtPlugin(({ $pinia }) => {
55
+ ($pinia as Pinia).use((pluginContext) => {
56
+ return PiniaStoreLifecycleManager(
57
+ pluginContext,
58
+ (lifecycleEvents) => {
59
+ // Use event listeners of your choice
60
+ const { $listen } = useNuxtApp();
61
+
62
+ // Register event listeners
63
+ $listen("user:logOut", () => {
64
+ lifecycleEvents.reset();
65
+ });
66
+ $listen("user:switchProfile", () => {
67
+ // Reset the store and perform a full refresh when switching profile
68
+ lifecycleEvents.reset();
69
+ lifecycleEvents.refresh({
70
+ mode: "full",
71
+ includeResetOnlyActions: true,
72
+ });
73
+ });
74
+ $listen("user:anyEvent", () => {
75
+ // Reconfigure the store and perform a partial refresh
76
+ lifecycleEvents.reconfigure();
77
+ lifecycleEvents.refresh({ mode: "partial" });
78
+ });
79
+ },
80
+ {
81
+ enableDebugLogs: __DEV__,
82
+ }
83
+ );
84
+ });
85
+ });
86
+
87
+ ```
88
+
89
+ ### Advanced Configuration
90
+
91
+ ```typescript
92
+ import { createPinia } from "pinia";
93
+ import { PiniaStoreLifecycleManager } from "pinia-store-lifecycle-manager";
94
+
95
+ const pinia = createPinia();
96
+ pinia.use((context) => {
97
+ PiniaStoreLifecycleManager(
98
+ context,
99
+ (lifecycleEvents) => {
100
+ // Define custom lifecycle event handlers
101
+ },
102
+ {
103
+ enableDebugLogs: true,
104
+ enableSSR: false,
105
+ disableAutoRegister: false,
106
+ }
107
+ );
108
+ });
109
+ ```
110
+
111
+ ## Features
112
+
113
+ - **State Reset:** Easily reset the store to its initial state.
114
+ - **Reconfiguration:** Update multiple store properties simultaneously.
115
+ - **Data Refresh:** Refresh store data by invoking specified actions.
116
+ - **Debug Logging:** Enable detailed logging for lifecycle events and actions.
117
+ - **SSR Support:** Compatible with server-side rendering setups.
118
+
119
+ ## Use Cases
120
+
121
+ - **User Authentication:** Reset store states upon user logout.
122
+ - **Dynamic Configuration:** Reconfigure store settings based on different environments.
123
+ - **Real-time Data Management:** Refresh store data in response to real-time events.
124
+ - **State Management Optimization:** Streamline complex state transitions.
125
+
126
+ ## API Reference
127
+
128
+ ### Lifecycle Options
129
+
130
+ ```typescript
131
+ interface DefineStoreOptionsBase<S extends StateTree, Store> {
132
+ lifecycleOptions?: {
133
+ clean?: CleanOptions<Store> | CleanFunction<Store> | (() => CleanOptions<Store> | void);
134
+ reConfigure?: ReconfigureOptions<Store> | ReconfigureFunction<Store> | (() => ReconfigureOptions<Store> | void);
135
+ refresh?: RefreshOptions<Store> | RefreshFunction<Store> | (() => RefreshOptions<Store> | void);
136
+ disableListener?: boolean;
137
+ };
138
+ }
139
+ ```
140
+
141
+ #### Clean and Reconfigure
142
+
143
+ Both `clean` and `reConfigure` can be defined as an object, a function that takes the store as an argument, or a function that takes no arguments:
144
+
145
+ ```typescript
146
+ // As an object
147
+ clean: {
148
+ user: null,
149
+ token: '',
150
+ isAuthenticated: false
151
+ }
152
+
153
+ // As a function with store argument
154
+ clean: (store) => ({
155
+ user: null,
156
+ token: '',
157
+ isAuthenticated: false
158
+ })
159
+
160
+ // As a function without arguments
161
+ clean: () => ({
162
+ user: null,
163
+ token: '',
164
+ isAuthenticated: false
165
+ })
166
+
167
+ // Reconfigure examples (similar pattern)
168
+ reConfigure: {
169
+ theme: 'dark',
170
+ language: 'en'
171
+ }
172
+
173
+ reConfigure: (store) => ({
174
+ theme: 'dark',
175
+ language: 'en'
176
+ })
177
+
178
+ reConfigure: () => ({
179
+ theme: 'dark',
180
+ language: 'en'
181
+ })
182
+ ```
183
+
184
+ #### Refresh
185
+
186
+ The `refresh` option can be defined as an object, a function that takes the store as an argument, or a function that takes no arguments:
187
+
188
+ ```typescript
189
+ // As an object
190
+ refresh: {
191
+ fetchUserProfile: { params: [], resetOnly: false },
192
+ fetchUserSettings: { params: [], resetOnly: true }
193
+ }
194
+
195
+ // As a function with store argument
196
+ refresh: (store) => ({
197
+ fetchUserProfile: { params: [] },
198
+ fetchUserSettings: { params: [], resetOnly: true }
199
+ })
200
+
201
+ // As a function without arguments
202
+ refresh: () => ({
203
+ fetchUserProfile: { params: [] },
204
+ fetchUserSettings: { params: [], resetOnly: true }
205
+ })
206
+ ```
207
+
208
+ ### Refresh Options
209
+
210
+ When calling the `refresh` function, you can specify options:
211
+
212
+ ```typescript
213
+ type PiniaStoreLifecycleManagerRefreshOptions = {
214
+ mode: "full" | "partial";
215
+ includeResetOnlyActions?: boolean;
216
+ };
217
+
218
+ /**
219
+ * Use 'full' mode when you need to refresh all data, including reset-only actions.
220
+ * This is useful for major state changes, like switching user accounts.
221
+ *
222
+ * Use 'partial' mode for routine updates or when switching to a related context
223
+ * that doesn't require a full data refresh.
224
+ */
225
+ lifecycleEvents.refresh({ mode: 'full', includeResetOnlyActions: true });
226
+ ```
227
+
228
+ #### Property Exclusions
229
+
230
+ The `clean`, `reConfigure`, and `refresh` options automatically exclude certain properties to ensure safe state management:
231
+
232
+ - Pinia's internal properties (starting with `$`)
233
+ - Conventionally private properties (starting with `_`)
234
+ - Non-writable computed properties
235
+ - State properties defined in the store
236
+
237
+ This ensures that only appropriate properties are modified during lifecycle operations.
238
+
239
+ #### Clean and Reconfigure
240
+
241
+ Both `clean` and `reConfigure` can be defined as either an object or a function:
242
+
243
+ ```typescript
244
+ // As an object
245
+ clean: {
246
+ user: null,
247
+ token: '',
248
+ isAuthenticated: false
249
+ }
250
+
251
+ // As a function
252
+ clean: (store) => ({
253
+ previousUserId: store.user?.id,
254
+ user: null,
255
+ token: '',
256
+ isAuthenticated: false
257
+ })
258
+
259
+ // Reconfigure example
260
+ reConfigure: () => ({
261
+ theme: 'dark',
262
+ language: 'en',
263
+ })
264
+ ```
265
+
266
+ #### Refresh
267
+
268
+ The `refresh` option can be defined as either an object or a function:
269
+
270
+ ```typescript
271
+ // As an object
272
+ refresh: {
273
+ fetchUserProfile: { params: ['userId'] },
274
+ fetchUserSettings: { params: [], resetOnly: true }
275
+ }
276
+
277
+ // As a function
278
+ refresh: (store) => ({
279
+ fetchUserProfile: { params: [store.userId] },
280
+ fetchUserSettings: { params: [], resetOnly: true }
281
+ })
282
+ ```
283
+
284
+ #### Property Inclusions and Exclusions
285
+
286
+ The `clean` and `reConfigure` options include:
287
+ - Data variables (including list types)
288
+ - Writable computed properties
289
+
290
+ They automatically exclude:
291
+ - Pinia's internal properties (starting with `$`)
292
+ - Conventionally private properties (starting with `_`)
293
+ - Non-writable computed properties
294
+ - Function properties
295
+
296
+ This ensures that only appropriate properties are modified during lifecycle operations while including all relevant data types.
297
+
298
+ #### Readonly Properties
299
+
300
+ The `clean` and `reConfigure` operations automatically skip readonly properties to prevent errors:
301
+
302
+ - If a property is readonly or a non-writable computed property, it will not be modified during reset or reconfigure operations.
303
+ - When debug logs are enabled, a warning with a 🚫 emoji will be issued for each skipped non-writable property, reminding developers to avoid including such properties in clean or reConfigure options.
304
+
305
+ This ensures that only modifiable properties are affected by lifecycle operations, maintaining the integrity of readonly state and computed properties.
306
+
307
+ > **Open Issue / Upcoming Enhancement:**
308
+ > We are aware that non-writable properties can currently be included in `clean` and `reConfigure` options, which is not ideal. We plan to enhance the type system to prevent this in a future update. For now, we have a runtime safety check that skips these properties and issues a warning when encountered. Please avoid including non-writable properties in your `clean` and `reConfigure` options to ensure smooth operation and prepare for future updates.
309
+
310
+ ## Why Choose Pinia Store Lifecycle Manager
311
+
312
+ - **Seamless Integration:** Easily integrates with existing Pinia stores.
313
+ - **Highly Customizable:** Offers flexible configuration options.
314
+ - **Performance Optimized:** Ensures minimal overhead in state management operations.
315
+ - **Comprehensive Logging:** Facilitates debugging through optional debug logs.
316
+ - **Active Maintenance:** Regular updates and community support.
317
+
318
+ ## License
319
+
320
+ MIT
package/dist/index.cjs ADDED
@@ -0,0 +1,242 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/index.ts
21
+ var src_exports = {};
22
+ __export(src_exports, {
23
+ PiniaStoreLifecycleManager: () => PiniaStoreLifecycleManager
24
+ });
25
+ module.exports = __toCommonJS(src_exports);
26
+
27
+ // src/utils.ts
28
+ function debugLog(storeId, message, data) {
29
+ console.log(`\u{1F504} [${storeId}] ${message}`, data ? data : "");
30
+ }
31
+ function devWarnNonWritable(storeId, propertyName) {
32
+ console.warn(
33
+ `\u{1F504} \u{1F6AB} [${storeId}] Skipped non-writable property '${propertyName}'. Avoid including non-writable computed properties in clean or reConfigure options.`
34
+ );
35
+ }
36
+ function devError(storeId, message, error) {
37
+ console.error(
38
+ `\u{1F504} [PiniaStoreLifecycleManager] (${storeId}) ${message}`,
39
+ error
40
+ );
41
+ }
42
+
43
+ // src/plugin.ts
44
+ var import_vue = require("vue");
45
+ var PiniaStoreLifecycleManager = ({ options, store }, lifecycleEventHandler, pluginOptions) => {
46
+ if ((typeof window === "undefined" || typeof document === "undefined") && !pluginOptions?.enableSSR) {
47
+ if (pluginOptions?.enableDebugLogs) {
48
+ debugLog(
49
+ store.$id,
50
+ "PiniaStoreLifecycleManager is not running on the client side."
51
+ );
52
+ }
53
+ return;
54
+ }
55
+ if (!store.$state.hasOwnProperty("hasStoreLifecycleManagerListener") && (!pluginOptions?.disableAutoRegister && options.lifecycleOptions?.disableListener !== true || pluginOptions?.disableAutoRegister && options.lifecycleOptions?.disableListener === false)) {
56
+ const _hasStoreLifecycleManagerListener = (0, import_vue.ref)(true);
57
+ store.$state._hasStoreLifecycleManagerListener = _hasStoreLifecycleManagerListener;
58
+ if (pluginOptions?.enableDebugLogs) {
59
+ debugLog(store.$id, "Attaching PiniaStoreLifecycleManager listener");
60
+ }
61
+ lifecycleEventHandler({
62
+ reset: resetState,
63
+ reconfigure: reconfigureState,
64
+ refresh: refreshState
65
+ });
66
+ store._hasStoreLifecycleManagerListener = (0, import_vue.toRef)(
67
+ store.$state,
68
+ "_hasStoreLifecycleManagerListener"
69
+ );
70
+ if (pluginOptions?.enableDebugLogs) {
71
+ debugLog(
72
+ store.$id,
73
+ "PiniaStoreLifecycleManager listener attached successfully"
74
+ );
75
+ }
76
+ } else {
77
+ if (pluginOptions?.enableDebugLogs) {
78
+ debugLog(
79
+ store.$id,
80
+ "Skipping PiniaStoreLifecycleManager listener attachment",
81
+ {
82
+ reason: store.$state.hasStoreLifecycleManagerListener ? "Already attached" : "Disabled by configuration"
83
+ }
84
+ );
85
+ }
86
+ }
87
+ function resetState() {
88
+ if (pluginOptions?.enableDebugLogs) {
89
+ debugLog(
90
+ store.$id,
91
+ "Initiating reset state operation",
92
+ options.lifecycleOptions?.clean
93
+ );
94
+ }
95
+ if (!options.lifecycleOptions?.clean) {
96
+ if (pluginOptions?.enableDebugLogs) {
97
+ debugLog(
98
+ store.$id,
99
+ "Reset state operation skipped: No clean options defined"
100
+ );
101
+ }
102
+ return;
103
+ }
104
+ const cleanOptions = typeof options.lifecycleOptions.clean === "function" ? options.lifecycleOptions.clean.length > 0 ? options.lifecycleOptions.clean(store) : options.lifecycleOptions.clean() : options.lifecycleOptions.clean;
105
+ if (cleanOptions) {
106
+ Object.keys(cleanOptions).forEach((key) => {
107
+ if ((0, import_vue.isReadonly)(store[key])) {
108
+ if (pluginOptions?.enableDebugLogs) {
109
+ devWarnNonWritable(store.$id, key);
110
+ }
111
+ return;
112
+ }
113
+ const oldValue = store[key];
114
+ try {
115
+ store[key] = cleanOptions[key];
116
+ if (pluginOptions?.enableDebugLogs) {
117
+ debugLog(store.$id, `Reset state: Updated '${key}'`, {
118
+ oldValue,
119
+ newValue: store[key]
120
+ });
121
+ }
122
+ } catch (error) {
123
+ devError(store.$id, `Failed to reset state for key '${key}'`, error);
124
+ }
125
+ });
126
+ }
127
+ if (pluginOptions?.enableDebugLogs) {
128
+ debugLog(store.$id, "Reset state operation completed");
129
+ }
130
+ }
131
+ function reconfigureState() {
132
+ if (pluginOptions?.enableDebugLogs) {
133
+ debugLog(
134
+ store.$id,
135
+ "Initiating reconfigure state operation",
136
+ options.lifecycleOptions?.reConfigure
137
+ );
138
+ }
139
+ if (!options.lifecycleOptions?.reConfigure) {
140
+ if (pluginOptions?.enableDebugLogs) {
141
+ debugLog(
142
+ store.$id,
143
+ "Reconfigure state operation skipped: No reconfigure options defined"
144
+ );
145
+ }
146
+ return;
147
+ }
148
+ const reconfigureOptions = typeof options.lifecycleOptions.reConfigure === "function" ? options.lifecycleOptions.reConfigure.length > 0 ? options.lifecycleOptions.reConfigure(store) : options.lifecycleOptions.reConfigure() : options.lifecycleOptions.reConfigure;
149
+ if (reconfigureOptions) {
150
+ Object.keys(reconfigureOptions).forEach((key) => {
151
+ if ((0, import_vue.isReadonly)(store[key])) {
152
+ if (pluginOptions?.enableDebugLogs) {
153
+ devWarnNonWritable(store.$id, key);
154
+ }
155
+ return;
156
+ }
157
+ const oldValue = store[key];
158
+ try {
159
+ store[key] = reconfigureOptions[key];
160
+ if (pluginOptions?.enableDebugLogs) {
161
+ debugLog(store.$id, `Reconfigure state: Updated '${key}'`, {
162
+ oldValue,
163
+ newValue: store[key]
164
+ });
165
+ }
166
+ } catch (error) {
167
+ devError(
168
+ store.$id,
169
+ `Failed to reconfigure state for key '${key}'`,
170
+ error
171
+ );
172
+ }
173
+ });
174
+ }
175
+ if (pluginOptions?.enableDebugLogs) {
176
+ debugLog(store.$id, "Reconfigure state operation completed");
177
+ }
178
+ }
179
+ function refreshState(refreshOptions) {
180
+ const { mode, includeResetOnlyActions = false } = refreshOptions;
181
+ if (pluginOptions?.enableDebugLogs) {
182
+ debugLog(store.$id, "Initiating refresh action", {
183
+ mode,
184
+ includeResetOnlyActions,
185
+ refreshOptions: options.lifecycleOptions?.refresh
186
+ });
187
+ }
188
+ if (!options.lifecycleOptions?.refresh) {
189
+ if (pluginOptions?.enableDebugLogs) {
190
+ debugLog(
191
+ store.$id,
192
+ "Refresh action skipped: No refresh options defined"
193
+ );
194
+ }
195
+ return;
196
+ }
197
+ const refreshResult = typeof options.lifecycleOptions.refresh === "function" ? options.lifecycleOptions.refresh.length > 0 ? options.lifecycleOptions.refresh(store) : options.lifecycleOptions.refresh() : options.lifecycleOptions.refresh;
198
+ if (refreshResult && typeof refreshResult === "object") {
199
+ const filteredResult = Object.fromEntries(
200
+ Object.entries(refreshResult).filter(([key, value]) => {
201
+ const actionResetOnly = value?.resetOnly ?? false;
202
+ const shouldInclude = mode === "full" ? !includeResetOnlyActions || includeResetOnlyActions && actionResetOnly : !actionResetOnly;
203
+ if (pluginOptions?.enableDebugLogs) {
204
+ debugLog(store.$id, `Filtering refresh action: ${key}`, {
205
+ shouldInclude,
206
+ mode,
207
+ includeResetOnlyActions,
208
+ actionResetOnly
209
+ });
210
+ }
211
+ return shouldInclude;
212
+ })
213
+ );
214
+ Object.entries(filteredResult).forEach(([key, value]) => {
215
+ const actionKey = key;
216
+ const action = store[actionKey];
217
+ if (action) {
218
+ const params = value?.params ?? [];
219
+ if (pluginOptions?.enableDebugLogs) {
220
+ debugLog(store.$id, `Executing refresh action: ${key}`, { params });
221
+ }
222
+ action(...params);
223
+ } else {
224
+ if (pluginOptions?.enableDebugLogs) {
225
+ debugLog(store.$id, `Skipped non-existent refresh action: ${key}`);
226
+ }
227
+ }
228
+ });
229
+ } else {
230
+ if (pluginOptions?.enableDebugLogs) {
231
+ debugLog(store.$id, "Refresh action skipped: Invalid refresh result");
232
+ }
233
+ }
234
+ if (pluginOptions?.enableDebugLogs) {
235
+ debugLog(store.$id, "Refresh action completed");
236
+ }
237
+ }
238
+ };
239
+ // Annotate the CommonJS export names for ESM import in node:
240
+ 0 && (module.exports = {
241
+ PiniaStoreLifecycleManager
242
+ });
@@ -0,0 +1,130 @@
1
+ import { StoreState, StoreGetters, StoreActions, PiniaPluginContext } from 'pinia';
2
+ import { Ref } from 'vue';
3
+
4
+ /**
5
+ * Represents a map of action parameters for a store.
6
+ * Each key is an action name, and the value contains the action's parameters and a flag indicating if it's reset-only.
7
+ */
8
+ type ActionParamsMap<T> = {
9
+ [K in keyof T]: {
10
+ params: T[K] extends (...args: infer P) => unknown ? P : [];
11
+ resetOnly?: boolean;
12
+ };
13
+ };
14
+ /**
15
+ * Options for refreshing a Pinia store using the lifecycle manager.
16
+ */
17
+ type PiniaStoreLifecycleManagerRefreshOptions = {
18
+ /** The refresh mode: 'full' refreshes all actions, 'partial' skips reset-only actions */
19
+ mode: "full" | "partial";
20
+ /** Whether to include reset-only actions in 'full' mode */
21
+ includeResetOnlyActions?: boolean;
22
+ };
23
+ /**
24
+ * Represents the options for cleaning a store's state.
25
+ * Omits the internal '_hasStoreLifecycleManagerListener' property.
26
+ */
27
+ type CleanOptions<Store> = Partial<Omit<StoreState<Store> & StoreGetters<Store>, "_hasStoreLifecycleManagerListener">>;
28
+ /**
29
+ * Represents the options for reconfiguring a store's state.
30
+ * Omits the internal '_hasStoreLifecycleManagerListener' property.
31
+ */
32
+ type ReconfigureOptions<Store> = Partial<Omit<StoreState<Store> & StoreGetters<Store>, "_hasStoreLifecycleManagerListener">>;
33
+ /**
34
+ * A function that takes a store and returns clean options or void.
35
+ */
36
+ type CleanFunction<Store> = (store: Omit<Store, `$${string}` | `_customProperties` | "_hasStoreLifecycleManagerListener">) => CleanOptions<Store> | void;
37
+ /**
38
+ * A function that takes a store and returns reconfigure options or void.
39
+ */
40
+ type ReconfigureFunction<Store> = (store: Omit<Store, `$${string}` | `_customProperties` | "_hasStoreLifecycleManagerListener">) => ReconfigureOptions<Store> | void;
41
+ /**
42
+ * Represents the options for refreshing a store's actions.
43
+ */
44
+ type RefreshOptions<Store> = Partial<ActionParamsMap<StoreActions<Store>>>;
45
+ /**
46
+ * A function that takes a store and returns refresh options or void.
47
+ */
48
+ type RefreshFunction<Store> = (store: Omit<Store, `$${string}` | `_customProperties` | "_hasStoreLifecycleManagerListener">) => RefreshOptions<Store> | void;
49
+ declare module "pinia" {
50
+ interface PiniaCustomProperties {
51
+ _hasStoreLifecycleManagerListener?: Ref<boolean>;
52
+ }
53
+ interface DefineStoreOptionsBase<S extends StateTree, Store> {
54
+ lifecycleOptions?: {
55
+ /**
56
+ * Defines properties to be reset during a hard-reset operation.
57
+ */
58
+ clean?: CleanOptions<Store> | CleanFunction<Store> | (() => CleanOptions<Store> | void);
59
+ /**
60
+ * Defines properties to be reset during a soft-reset (reconfigure) operation.
61
+ */
62
+ reConfigure?: ReconfigureOptions<Store> | ReconfigureFunction<Store> | (() => ReconfigureOptions<Store> | void);
63
+ /**
64
+ * Defines actions to be called during a refresh operation.
65
+ * @param v - The store instance with certain properties omitted.
66
+ * @returns Partial map of action parameters.
67
+ */
68
+ refresh?: RefreshOptions<Store> | RefreshFunction<Store> | (() => RefreshOptions<Store> | void);
69
+ /**
70
+ * Controls the automatic registration of the listener.
71
+ * - For automatic registration: set to true to disable listener registration.
72
+ * - For manual registration: set to false to enable listener registration.
73
+ */
74
+ disableListener?: boolean;
75
+ };
76
+ }
77
+ }
78
+
79
+ /**
80
+ * PiniaStoreLifecycleManager: A Pinia plugin for managing store lifecycle, including state resets and refreshes.
81
+ *
82
+ * @param context - The Pinia plugin context.
83
+ * @param lifecycleEventHandler - The listener function to be called for various lifecycle events.
84
+ * @param pluginOptions - Configuration options for the plugin.
85
+ */
86
+ declare const PiniaStoreLifecycleManager: ({ options, store }: PiniaPluginContext, lifecycleEventHandler: (events: {
87
+ /**
88
+ * Resets the store to its initial state.
89
+ * Use this when you need to completely clear the store, e.g., on user logout.
90
+ */
91
+ reset: () => void;
92
+ /**
93
+ * Reconfigures the store with new values.
94
+ * Use this when you need to update multiple store properties at once,
95
+ * e.g., when switching to a different configuration or context.
96
+ */
97
+ reconfigure: () => void;
98
+ /**
99
+ * Refreshes the store data by calling specified actions.
100
+ * @param options RefreshOptions to control the refresh behavior.
101
+ *
102
+ * Use 'full' mode when you need to refresh all data, including reset-only actions.
103
+ * This is useful for major state changes, like switching user accounts.
104
+ *
105
+ * Use 'partial' mode for routine updates or when switching to a related context
106
+ * that doesn't require a full data refresh.
107
+ */
108
+ refresh: (options: PiniaStoreLifecycleManagerRefreshOptions) => void;
109
+ }) => void, pluginOptions?: {
110
+ /**
111
+ * Disables the automatic registration of lifecycle listeners.
112
+ * Set to true to manually manage lifecycle listener registration.
113
+ */
114
+ disableAutoRegister?: boolean;
115
+ /**
116
+ * Enables debug logging for the PiniaStoreLifecycleManager.
117
+ * Set to true to output debug information to the console.
118
+ */
119
+ enableDebugLogs?: boolean;
120
+ /**
121
+ * Enables server-side rendering support for the PiniaStoreLifecycleManager.
122
+ * Set to true if the application uses SSR to ensure proper lifecycle management.
123
+ *
124
+ * **Note:** Only enable if you are sure that your listener works perfectly in SSR,
125
+ * as this plugin only assigns the listener once either on the server or on the client.
126
+ */
127
+ enableSSR?: boolean;
128
+ }) => void;
129
+
130
+ export { type ActionParamsMap, type CleanFunction, type CleanOptions, PiniaStoreLifecycleManager, type PiniaStoreLifecycleManagerRefreshOptions, type ReconfigureFunction, type ReconfigureOptions, type RefreshFunction, type RefreshOptions };
@@ -0,0 +1,130 @@
1
+ import { StoreState, StoreGetters, StoreActions, PiniaPluginContext } from 'pinia';
2
+ import { Ref } from 'vue';
3
+
4
+ /**
5
+ * Represents a map of action parameters for a store.
6
+ * Each key is an action name, and the value contains the action's parameters and a flag indicating if it's reset-only.
7
+ */
8
+ type ActionParamsMap<T> = {
9
+ [K in keyof T]: {
10
+ params: T[K] extends (...args: infer P) => unknown ? P : [];
11
+ resetOnly?: boolean;
12
+ };
13
+ };
14
+ /**
15
+ * Options for refreshing a Pinia store using the lifecycle manager.
16
+ */
17
+ type PiniaStoreLifecycleManagerRefreshOptions = {
18
+ /** The refresh mode: 'full' refreshes all actions, 'partial' skips reset-only actions */
19
+ mode: "full" | "partial";
20
+ /** Whether to include reset-only actions in 'full' mode */
21
+ includeResetOnlyActions?: boolean;
22
+ };
23
+ /**
24
+ * Represents the options for cleaning a store's state.
25
+ * Omits the internal '_hasStoreLifecycleManagerListener' property.
26
+ */
27
+ type CleanOptions<Store> = Partial<Omit<StoreState<Store> & StoreGetters<Store>, "_hasStoreLifecycleManagerListener">>;
28
+ /**
29
+ * Represents the options for reconfiguring a store's state.
30
+ * Omits the internal '_hasStoreLifecycleManagerListener' property.
31
+ */
32
+ type ReconfigureOptions<Store> = Partial<Omit<StoreState<Store> & StoreGetters<Store>, "_hasStoreLifecycleManagerListener">>;
33
+ /**
34
+ * A function that takes a store and returns clean options or void.
35
+ */
36
+ type CleanFunction<Store> = (store: Omit<Store, `$${string}` | `_customProperties` | "_hasStoreLifecycleManagerListener">) => CleanOptions<Store> | void;
37
+ /**
38
+ * A function that takes a store and returns reconfigure options or void.
39
+ */
40
+ type ReconfigureFunction<Store> = (store: Omit<Store, `$${string}` | `_customProperties` | "_hasStoreLifecycleManagerListener">) => ReconfigureOptions<Store> | void;
41
+ /**
42
+ * Represents the options for refreshing a store's actions.
43
+ */
44
+ type RefreshOptions<Store> = Partial<ActionParamsMap<StoreActions<Store>>>;
45
+ /**
46
+ * A function that takes a store and returns refresh options or void.
47
+ */
48
+ type RefreshFunction<Store> = (store: Omit<Store, `$${string}` | `_customProperties` | "_hasStoreLifecycleManagerListener">) => RefreshOptions<Store> | void;
49
+ declare module "pinia" {
50
+ interface PiniaCustomProperties {
51
+ _hasStoreLifecycleManagerListener?: Ref<boolean>;
52
+ }
53
+ interface DefineStoreOptionsBase<S extends StateTree, Store> {
54
+ lifecycleOptions?: {
55
+ /**
56
+ * Defines properties to be reset during a hard-reset operation.
57
+ */
58
+ clean?: CleanOptions<Store> | CleanFunction<Store> | (() => CleanOptions<Store> | void);
59
+ /**
60
+ * Defines properties to be reset during a soft-reset (reconfigure) operation.
61
+ */
62
+ reConfigure?: ReconfigureOptions<Store> | ReconfigureFunction<Store> | (() => ReconfigureOptions<Store> | void);
63
+ /**
64
+ * Defines actions to be called during a refresh operation.
65
+ * @param v - The store instance with certain properties omitted.
66
+ * @returns Partial map of action parameters.
67
+ */
68
+ refresh?: RefreshOptions<Store> | RefreshFunction<Store> | (() => RefreshOptions<Store> | void);
69
+ /**
70
+ * Controls the automatic registration of the listener.
71
+ * - For automatic registration: set to true to disable listener registration.
72
+ * - For manual registration: set to false to enable listener registration.
73
+ */
74
+ disableListener?: boolean;
75
+ };
76
+ }
77
+ }
78
+
79
+ /**
80
+ * PiniaStoreLifecycleManager: A Pinia plugin for managing store lifecycle, including state resets and refreshes.
81
+ *
82
+ * @param context - The Pinia plugin context.
83
+ * @param lifecycleEventHandler - The listener function to be called for various lifecycle events.
84
+ * @param pluginOptions - Configuration options for the plugin.
85
+ */
86
+ declare const PiniaStoreLifecycleManager: ({ options, store }: PiniaPluginContext, lifecycleEventHandler: (events: {
87
+ /**
88
+ * Resets the store to its initial state.
89
+ * Use this when you need to completely clear the store, e.g., on user logout.
90
+ */
91
+ reset: () => void;
92
+ /**
93
+ * Reconfigures the store with new values.
94
+ * Use this when you need to update multiple store properties at once,
95
+ * e.g., when switching to a different configuration or context.
96
+ */
97
+ reconfigure: () => void;
98
+ /**
99
+ * Refreshes the store data by calling specified actions.
100
+ * @param options RefreshOptions to control the refresh behavior.
101
+ *
102
+ * Use 'full' mode when you need to refresh all data, including reset-only actions.
103
+ * This is useful for major state changes, like switching user accounts.
104
+ *
105
+ * Use 'partial' mode for routine updates or when switching to a related context
106
+ * that doesn't require a full data refresh.
107
+ */
108
+ refresh: (options: PiniaStoreLifecycleManagerRefreshOptions) => void;
109
+ }) => void, pluginOptions?: {
110
+ /**
111
+ * Disables the automatic registration of lifecycle listeners.
112
+ * Set to true to manually manage lifecycle listener registration.
113
+ */
114
+ disableAutoRegister?: boolean;
115
+ /**
116
+ * Enables debug logging for the PiniaStoreLifecycleManager.
117
+ * Set to true to output debug information to the console.
118
+ */
119
+ enableDebugLogs?: boolean;
120
+ /**
121
+ * Enables server-side rendering support for the PiniaStoreLifecycleManager.
122
+ * Set to true if the application uses SSR to ensure proper lifecycle management.
123
+ *
124
+ * **Note:** Only enable if you are sure that your listener works perfectly in SSR,
125
+ * as this plugin only assigns the listener once either on the server or on the client.
126
+ */
127
+ enableSSR?: boolean;
128
+ }) => void;
129
+
130
+ export { type ActionParamsMap, type CleanFunction, type CleanOptions, PiniaStoreLifecycleManager, type PiniaStoreLifecycleManagerRefreshOptions, type ReconfigureFunction, type ReconfigureOptions, type RefreshFunction, type RefreshOptions };
package/dist/index.js ADDED
@@ -0,0 +1,215 @@
1
+ // src/utils.ts
2
+ function debugLog(storeId, message, data) {
3
+ console.log(`\u{1F504} [${storeId}] ${message}`, data ? data : "");
4
+ }
5
+ function devWarnNonWritable(storeId, propertyName) {
6
+ console.warn(
7
+ `\u{1F504} \u{1F6AB} [${storeId}] Skipped non-writable property '${propertyName}'. Avoid including non-writable computed properties in clean or reConfigure options.`
8
+ );
9
+ }
10
+ function devError(storeId, message, error) {
11
+ console.error(
12
+ `\u{1F504} [PiniaStoreLifecycleManager] (${storeId}) ${message}`,
13
+ error
14
+ );
15
+ }
16
+
17
+ // src/plugin.ts
18
+ import { ref, toRef, isReadonly } from "vue";
19
+ var PiniaStoreLifecycleManager = ({ options, store }, lifecycleEventHandler, pluginOptions) => {
20
+ if ((typeof window === "undefined" || typeof document === "undefined") && !pluginOptions?.enableSSR) {
21
+ if (pluginOptions?.enableDebugLogs) {
22
+ debugLog(
23
+ store.$id,
24
+ "PiniaStoreLifecycleManager is not running on the client side."
25
+ );
26
+ }
27
+ return;
28
+ }
29
+ if (!store.$state.hasOwnProperty("hasStoreLifecycleManagerListener") && (!pluginOptions?.disableAutoRegister && options.lifecycleOptions?.disableListener !== true || pluginOptions?.disableAutoRegister && options.lifecycleOptions?.disableListener === false)) {
30
+ const _hasStoreLifecycleManagerListener = ref(true);
31
+ store.$state._hasStoreLifecycleManagerListener = _hasStoreLifecycleManagerListener;
32
+ if (pluginOptions?.enableDebugLogs) {
33
+ debugLog(store.$id, "Attaching PiniaStoreLifecycleManager listener");
34
+ }
35
+ lifecycleEventHandler({
36
+ reset: resetState,
37
+ reconfigure: reconfigureState,
38
+ refresh: refreshState
39
+ });
40
+ store._hasStoreLifecycleManagerListener = toRef(
41
+ store.$state,
42
+ "_hasStoreLifecycleManagerListener"
43
+ );
44
+ if (pluginOptions?.enableDebugLogs) {
45
+ debugLog(
46
+ store.$id,
47
+ "PiniaStoreLifecycleManager listener attached successfully"
48
+ );
49
+ }
50
+ } else {
51
+ if (pluginOptions?.enableDebugLogs) {
52
+ debugLog(
53
+ store.$id,
54
+ "Skipping PiniaStoreLifecycleManager listener attachment",
55
+ {
56
+ reason: store.$state.hasStoreLifecycleManagerListener ? "Already attached" : "Disabled by configuration"
57
+ }
58
+ );
59
+ }
60
+ }
61
+ function resetState() {
62
+ if (pluginOptions?.enableDebugLogs) {
63
+ debugLog(
64
+ store.$id,
65
+ "Initiating reset state operation",
66
+ options.lifecycleOptions?.clean
67
+ );
68
+ }
69
+ if (!options.lifecycleOptions?.clean) {
70
+ if (pluginOptions?.enableDebugLogs) {
71
+ debugLog(
72
+ store.$id,
73
+ "Reset state operation skipped: No clean options defined"
74
+ );
75
+ }
76
+ return;
77
+ }
78
+ const cleanOptions = typeof options.lifecycleOptions.clean === "function" ? options.lifecycleOptions.clean.length > 0 ? options.lifecycleOptions.clean(store) : options.lifecycleOptions.clean() : options.lifecycleOptions.clean;
79
+ if (cleanOptions) {
80
+ Object.keys(cleanOptions).forEach((key) => {
81
+ if (isReadonly(store[key])) {
82
+ if (pluginOptions?.enableDebugLogs) {
83
+ devWarnNonWritable(store.$id, key);
84
+ }
85
+ return;
86
+ }
87
+ const oldValue = store[key];
88
+ try {
89
+ store[key] = cleanOptions[key];
90
+ if (pluginOptions?.enableDebugLogs) {
91
+ debugLog(store.$id, `Reset state: Updated '${key}'`, {
92
+ oldValue,
93
+ newValue: store[key]
94
+ });
95
+ }
96
+ } catch (error) {
97
+ devError(store.$id, `Failed to reset state for key '${key}'`, error);
98
+ }
99
+ });
100
+ }
101
+ if (pluginOptions?.enableDebugLogs) {
102
+ debugLog(store.$id, "Reset state operation completed");
103
+ }
104
+ }
105
+ function reconfigureState() {
106
+ if (pluginOptions?.enableDebugLogs) {
107
+ debugLog(
108
+ store.$id,
109
+ "Initiating reconfigure state operation",
110
+ options.lifecycleOptions?.reConfigure
111
+ );
112
+ }
113
+ if (!options.lifecycleOptions?.reConfigure) {
114
+ if (pluginOptions?.enableDebugLogs) {
115
+ debugLog(
116
+ store.$id,
117
+ "Reconfigure state operation skipped: No reconfigure options defined"
118
+ );
119
+ }
120
+ return;
121
+ }
122
+ const reconfigureOptions = typeof options.lifecycleOptions.reConfigure === "function" ? options.lifecycleOptions.reConfigure.length > 0 ? options.lifecycleOptions.reConfigure(store) : options.lifecycleOptions.reConfigure() : options.lifecycleOptions.reConfigure;
123
+ if (reconfigureOptions) {
124
+ Object.keys(reconfigureOptions).forEach((key) => {
125
+ if (isReadonly(store[key])) {
126
+ if (pluginOptions?.enableDebugLogs) {
127
+ devWarnNonWritable(store.$id, key);
128
+ }
129
+ return;
130
+ }
131
+ const oldValue = store[key];
132
+ try {
133
+ store[key] = reconfigureOptions[key];
134
+ if (pluginOptions?.enableDebugLogs) {
135
+ debugLog(store.$id, `Reconfigure state: Updated '${key}'`, {
136
+ oldValue,
137
+ newValue: store[key]
138
+ });
139
+ }
140
+ } catch (error) {
141
+ devError(
142
+ store.$id,
143
+ `Failed to reconfigure state for key '${key}'`,
144
+ error
145
+ );
146
+ }
147
+ });
148
+ }
149
+ if (pluginOptions?.enableDebugLogs) {
150
+ debugLog(store.$id, "Reconfigure state operation completed");
151
+ }
152
+ }
153
+ function refreshState(refreshOptions) {
154
+ const { mode, includeResetOnlyActions = false } = refreshOptions;
155
+ if (pluginOptions?.enableDebugLogs) {
156
+ debugLog(store.$id, "Initiating refresh action", {
157
+ mode,
158
+ includeResetOnlyActions,
159
+ refreshOptions: options.lifecycleOptions?.refresh
160
+ });
161
+ }
162
+ if (!options.lifecycleOptions?.refresh) {
163
+ if (pluginOptions?.enableDebugLogs) {
164
+ debugLog(
165
+ store.$id,
166
+ "Refresh action skipped: No refresh options defined"
167
+ );
168
+ }
169
+ return;
170
+ }
171
+ const refreshResult = typeof options.lifecycleOptions.refresh === "function" ? options.lifecycleOptions.refresh.length > 0 ? options.lifecycleOptions.refresh(store) : options.lifecycleOptions.refresh() : options.lifecycleOptions.refresh;
172
+ if (refreshResult && typeof refreshResult === "object") {
173
+ const filteredResult = Object.fromEntries(
174
+ Object.entries(refreshResult).filter(([key, value]) => {
175
+ const actionResetOnly = value?.resetOnly ?? false;
176
+ const shouldInclude = mode === "full" ? !includeResetOnlyActions || includeResetOnlyActions && actionResetOnly : !actionResetOnly;
177
+ if (pluginOptions?.enableDebugLogs) {
178
+ debugLog(store.$id, `Filtering refresh action: ${key}`, {
179
+ shouldInclude,
180
+ mode,
181
+ includeResetOnlyActions,
182
+ actionResetOnly
183
+ });
184
+ }
185
+ return shouldInclude;
186
+ })
187
+ );
188
+ Object.entries(filteredResult).forEach(([key, value]) => {
189
+ const actionKey = key;
190
+ const action = store[actionKey];
191
+ if (action) {
192
+ const params = value?.params ?? [];
193
+ if (pluginOptions?.enableDebugLogs) {
194
+ debugLog(store.$id, `Executing refresh action: ${key}`, { params });
195
+ }
196
+ action(...params);
197
+ } else {
198
+ if (pluginOptions?.enableDebugLogs) {
199
+ debugLog(store.$id, `Skipped non-existent refresh action: ${key}`);
200
+ }
201
+ }
202
+ });
203
+ } else {
204
+ if (pluginOptions?.enableDebugLogs) {
205
+ debugLog(store.$id, "Refresh action skipped: Invalid refresh result");
206
+ }
207
+ }
208
+ if (pluginOptions?.enableDebugLogs) {
209
+ debugLog(store.$id, "Refresh action completed");
210
+ }
211
+ }
212
+ };
213
+ export {
214
+ PiniaStoreLifecycleManager
215
+ };
package/package.json ADDED
@@ -0,0 +1,49 @@
1
+ {
2
+ "name": "@atoapayments/pinia-store-lifecycle-manager",
3
+ "version": "0.0.1",
4
+ "description": "A Pinia plugin for managing store lifecycle, including state resets and refreshes",
5
+ "type": "module",
6
+ "main": "dist/index.js",
7
+ "types": "dist/index.d.ts",
8
+ "files": [
9
+ "dist"
10
+ ],
11
+ "scripts": {
12
+ "build": "tsup src/index.ts --format cjs,esm --dts",
13
+ "dev": "tsup src/index.ts --format cjs,esm --dts --watch",
14
+ "lint": "eslint src/**/*.ts",
15
+ "clean": "rm -rf dist",
16
+ "prepublishOnly": "npm run clean && npm run build"
17
+ },
18
+ "keywords": [
19
+ "pinia",
20
+ "vue",
21
+ "store",
22
+ "lifecycle",
23
+ "management"
24
+ ],
25
+ "author": "Tushar gupta",
26
+ "license": "MIT",
27
+ "devDependencies": {
28
+ "@types/node": "^22.7.4",
29
+ "@typescript-eslint/eslint-plugin": "^8.7.0",
30
+ "@typescript-eslint/parser": "^8.7.0",
31
+ "eslint": "^9.11.1",
32
+ "pinia": "^2.2.2",
33
+ "tsup": "^8.3.0",
34
+ "typescript": "^5.6.2",
35
+ "vue": "^3.5.9"
36
+ },
37
+ "peerDependencies": {
38
+ "pinia": "^2.0.0",
39
+ "vue": "^3.0.0"
40
+ },
41
+ "peerDependenciesMeta": {
42
+ "pinia": {
43
+ "optional": false
44
+ },
45
+ "vue": {
46
+ "optional": false
47
+ }
48
+ }
49
+ }