eleva 1.0.1 → 1.1.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/README.md +21 -10
- package/dist/{eleva-plugins.cjs.js → eleva-plugins.cjs} +1002 -292
- package/dist/eleva-plugins.cjs.map +1 -0
- package/dist/eleva-plugins.d.cts +1352 -0
- package/dist/eleva-plugins.d.cts.map +1 -0
- package/dist/eleva-plugins.d.ts +1352 -0
- package/dist/eleva-plugins.d.ts.map +1 -0
- package/dist/{eleva-plugins.esm.js → eleva-plugins.js} +1002 -292
- package/dist/eleva-plugins.js.map +1 -0
- package/dist/eleva-plugins.umd.js +1001 -291
- package/dist/eleva-plugins.umd.js.map +1 -1
- package/dist/eleva-plugins.umd.min.js +1 -1
- package/dist/eleva-plugins.umd.min.js.map +1 -1
- package/dist/{eleva.cjs.js → eleva.cjs} +421 -191
- package/dist/eleva.cjs.map +1 -0
- package/dist/eleva.d.cts +1329 -0
- package/dist/eleva.d.cts.map +1 -0
- package/dist/eleva.d.ts +473 -226
- package/dist/eleva.d.ts.map +1 -0
- package/dist/{eleva.esm.js → eleva.js} +422 -192
- package/dist/eleva.js.map +1 -0
- package/dist/eleva.umd.js +420 -190
- package/dist/eleva.umd.js.map +1 -1
- package/dist/eleva.umd.min.js +1 -1
- package/dist/eleva.umd.min.js.map +1 -1
- package/dist/plugins/attr.cjs +279 -0
- package/dist/plugins/attr.cjs.map +1 -0
- package/dist/plugins/attr.d.cts +101 -0
- package/dist/plugins/attr.d.cts.map +1 -0
- package/dist/plugins/attr.d.ts +101 -0
- package/dist/plugins/attr.d.ts.map +1 -0
- package/dist/plugins/attr.js +276 -0
- package/dist/plugins/attr.js.map +1 -0
- package/dist/plugins/attr.umd.js +111 -22
- package/dist/plugins/attr.umd.js.map +1 -1
- package/dist/plugins/attr.umd.min.js +1 -1
- package/dist/plugins/attr.umd.min.js.map +1 -1
- package/dist/plugins/router.cjs +1873 -0
- package/dist/plugins/router.cjs.map +1 -0
- package/dist/plugins/router.d.cts +1296 -0
- package/dist/plugins/router.d.cts.map +1 -0
- package/dist/plugins/router.d.ts +1296 -0
- package/dist/plugins/router.d.ts.map +1 -0
- package/dist/plugins/router.js +1870 -0
- package/dist/plugins/router.js.map +1 -0
- package/dist/plugins/router.umd.js +482 -186
- package/dist/plugins/router.umd.js.map +1 -1
- package/dist/plugins/router.umd.min.js +1 -1
- package/dist/plugins/router.umd.min.js.map +1 -1
- package/dist/plugins/store.cjs +920 -0
- package/dist/plugins/store.cjs.map +1 -0
- package/dist/plugins/store.d.cts +266 -0
- package/dist/plugins/store.d.cts.map +1 -0
- package/dist/plugins/store.d.ts +266 -0
- package/dist/plugins/store.d.ts.map +1 -0
- package/dist/plugins/store.js +917 -0
- package/dist/plugins/store.js.map +1 -0
- package/dist/plugins/store.umd.js +410 -85
- package/dist/plugins/store.umd.js.map +1 -1
- package/dist/plugins/store.umd.min.js +1 -1
- package/dist/plugins/store.umd.min.js.map +1 -1
- package/package.json +112 -68
- package/src/core/Eleva.js +195 -115
- package/src/index.cjs +10 -0
- package/src/index.js +11 -0
- package/src/modules/Emitter.js +68 -20
- package/src/modules/Renderer.js +82 -20
- package/src/modules/Signal.js +43 -15
- package/src/modules/TemplateEngine.js +50 -9
- package/src/plugins/Attr.js +121 -19
- package/src/plugins/Router.js +526 -181
- package/src/plugins/Store.js +448 -69
- package/src/plugins/index.js +1 -0
- package/types/core/Eleva.d.ts +263 -169
- package/types/core/Eleva.d.ts.map +1 -1
- package/types/index.d.cts +3 -0
- package/types/index.d.cts.map +1 -0
- package/types/index.d.ts +5 -0
- package/types/index.d.ts.map +1 -1
- package/types/modules/Emitter.d.ts +73 -30
- package/types/modules/Emitter.d.ts.map +1 -1
- package/types/modules/Renderer.d.ts +48 -18
- package/types/modules/Renderer.d.ts.map +1 -1
- package/types/modules/Signal.d.ts +44 -16
- package/types/modules/Signal.d.ts.map +1 -1
- package/types/modules/TemplateEngine.d.ts +46 -11
- package/types/modules/TemplateEngine.d.ts.map +1 -1
- package/types/plugins/Attr.d.ts +83 -16
- package/types/plugins/Attr.d.ts.map +1 -1
- package/types/plugins/Router.d.ts +498 -207
- package/types/plugins/Router.d.ts.map +1 -1
- package/types/plugins/Store.d.ts +211 -37
- package/types/plugins/Store.d.ts.map +1 -1
- package/dist/eleva-plugins.cjs.js.map +0 -1
- package/dist/eleva-plugins.esm.js.map +0 -1
- package/dist/eleva.cjs.js.map +0 -1
- package/dist/eleva.esm.js.map +0 -1
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/*! Eleva Store Plugin v1.
|
|
1
|
+
/*! Eleva Store Plugin v1.1.0 | MIT License | https://elevajs.com */
|
|
2
2
|
(function (global, factory) {
|
|
3
3
|
typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) :
|
|
4
4
|
typeof define === 'function' && define.amd ? define(['exports'], factory) :
|
|
@@ -6,6 +6,194 @@
|
|
|
6
6
|
})(this, (function (exports) { 'use strict';
|
|
7
7
|
|
|
8
8
|
/**
|
|
9
|
+
* @module eleva/plugins/store
|
|
10
|
+
* @fileoverview Reactive state management plugin with namespaced modules,
|
|
11
|
+
* persistence, and subscription system.
|
|
12
|
+
*/ // ============================================================================
|
|
13
|
+
// TYPE DEFINITIONS
|
|
14
|
+
// ============================================================================
|
|
15
|
+
// -----------------------------------------------------------------------------
|
|
16
|
+
// External Type Imports
|
|
17
|
+
// -----------------------------------------------------------------------------
|
|
18
|
+
/**
|
|
19
|
+
* Type imports from the Eleva core library.
|
|
20
|
+
* @typedef {import('eleva').Eleva} Eleva
|
|
21
|
+
* @typedef {import('eleva').ComponentDefinition} ComponentDefinition
|
|
22
|
+
* @typedef {import('eleva').ComponentContext} ComponentContext
|
|
23
|
+
* @typedef {import('eleva').SetupResult} SetupResult
|
|
24
|
+
* @typedef {import('eleva').ComponentProps} ComponentProps
|
|
25
|
+
* @typedef {import('eleva').ChildrenMap} ChildrenMap
|
|
26
|
+
* @typedef {import('eleva').MountResult} MountResult
|
|
27
|
+
*/ /**
|
|
28
|
+
* Generic type import.
|
|
29
|
+
* @template T
|
|
30
|
+
* @typedef {import('eleva').Signal<T>} Signal
|
|
31
|
+
*/ // -----------------------------------------------------------------------------
|
|
32
|
+
// Store Type Definitions
|
|
33
|
+
// -----------------------------------------------------------------------------
|
|
34
|
+
/**
|
|
35
|
+
* Mutation record emitted to subscribers.
|
|
36
|
+
* @typedef {Object} StoreMutation
|
|
37
|
+
* @property {string} type
|
|
38
|
+
* The action name that was dispatched.
|
|
39
|
+
* @property {unknown} payload
|
|
40
|
+
* The payload passed to the action.
|
|
41
|
+
* @property {number} timestamp
|
|
42
|
+
* Unix timestamp of when the mutation occurred.
|
|
43
|
+
* @description Record passed to subscribers when state changes via dispatch.
|
|
44
|
+
* @example
|
|
45
|
+
* store.subscribe((mutation, state) => {
|
|
46
|
+
* console.log(`Action: ${mutation.type}`);
|
|
47
|
+
* console.log(`Payload: ${mutation.payload}`);
|
|
48
|
+
* console.log(`Time: ${new Date(mutation.timestamp)}`);
|
|
49
|
+
* });
|
|
50
|
+
*/ /**
|
|
51
|
+
* Store configuration options.
|
|
52
|
+
* @typedef {Object} StoreOptions
|
|
53
|
+
* @property {Record<string, unknown>} [state]
|
|
54
|
+
* Initial state object.
|
|
55
|
+
* @property {Record<string, ActionFunction>} [actions]
|
|
56
|
+
* Action functions for state mutations.
|
|
57
|
+
* @property {Record<string, StoreModule>} [namespaces]
|
|
58
|
+
* Namespaced modules for organizing store.
|
|
59
|
+
* @property {StorePersistenceOptions} [persistence]
|
|
60
|
+
* Persistence configuration.
|
|
61
|
+
* @property {boolean} [devTools]
|
|
62
|
+
* Enable development tools integration.
|
|
63
|
+
* @property {StoreErrorHandler} [onError]
|
|
64
|
+
* Error handler function.
|
|
65
|
+
* @description Configuration options passed to StorePlugin.install().
|
|
66
|
+
* @example
|
|
67
|
+
* app.use(StorePlugin, {
|
|
68
|
+
* state: { count: 0, user: null },
|
|
69
|
+
* actions: {
|
|
70
|
+
* increment: (state) => state.count.value++,
|
|
71
|
+
* setUser: (state, user) => state.user.value = user
|
|
72
|
+
* },
|
|
73
|
+
* persistence: { enabled: true, key: 'my-app' }
|
|
74
|
+
* });
|
|
75
|
+
*/ /**
|
|
76
|
+
* Namespaced store module definition.
|
|
77
|
+
* @typedef {Object} StoreModule
|
|
78
|
+
* @property {Record<string, unknown>} state
|
|
79
|
+
* Module state.
|
|
80
|
+
* @property {Record<string, ActionFunction>} [actions]
|
|
81
|
+
* Module actions.
|
|
82
|
+
* @description Defines a namespaced module for organizing related state and actions.
|
|
83
|
+
* @example
|
|
84
|
+
* // Define a module
|
|
85
|
+
* const authModule = {
|
|
86
|
+
* state: { user: null, token: null },
|
|
87
|
+
* actions: {
|
|
88
|
+
* login: (state, { user, token }) => {
|
|
89
|
+
* state.auth.user.value = user;
|
|
90
|
+
* state.auth.token.value = token;
|
|
91
|
+
* }
|
|
92
|
+
* }
|
|
93
|
+
* };
|
|
94
|
+
*
|
|
95
|
+
* // Register dynamically
|
|
96
|
+
* store.registerModule('auth', authModule);
|
|
97
|
+
*/ /**
|
|
98
|
+
* Store persistence configuration.
|
|
99
|
+
* @typedef {Object} StorePersistenceOptions
|
|
100
|
+
* @property {boolean} [enabled]
|
|
101
|
+
* Enable state persistence.
|
|
102
|
+
* @property {string} [key]
|
|
103
|
+
* Storage key (default: "eleva-store").
|
|
104
|
+
* @property {'localStorage' | 'sessionStorage'} [storage]
|
|
105
|
+
* Storage type.
|
|
106
|
+
* @property {string[]} [include]
|
|
107
|
+
* Dot-path prefixes to persist (e.g., "auth.user").
|
|
108
|
+
* @property {string[]} [exclude]
|
|
109
|
+
* Dot-path prefixes to exclude.
|
|
110
|
+
* @description Configuration for persisting store state to localStorage or sessionStorage.
|
|
111
|
+
* @example
|
|
112
|
+
* // Persist only specific state paths
|
|
113
|
+
* persistence: {
|
|
114
|
+
* enabled: true,
|
|
115
|
+
* key: 'my-app-store',
|
|
116
|
+
* storage: 'localStorage',
|
|
117
|
+
* include: ['user', 'settings.theme']
|
|
118
|
+
* }
|
|
119
|
+
*
|
|
120
|
+
* @example
|
|
121
|
+
* // Exclude sensitive data
|
|
122
|
+
* persistence: {
|
|
123
|
+
* enabled: true,
|
|
124
|
+
* exclude: ['auth.token', 'temp']
|
|
125
|
+
* }
|
|
126
|
+
*/ /**
|
|
127
|
+
* Store error handler callback.
|
|
128
|
+
* @typedef {(error: Error, context: string) => void} StoreErrorHandler
|
|
129
|
+
* @description Custom error handler for store operations.
|
|
130
|
+
* @example
|
|
131
|
+
* app.use(StorePlugin, {
|
|
132
|
+
* onError: (error, context) => {
|
|
133
|
+
* console.error(`Store error in ${context}:`, error);
|
|
134
|
+
* // Send to error tracking service
|
|
135
|
+
* errorTracker.capture(error, { context });
|
|
136
|
+
* }
|
|
137
|
+
* });
|
|
138
|
+
*/ /**
|
|
139
|
+
* Reactive state tree containing signals and nested namespaces.
|
|
140
|
+
* @typedef {Record<string, Signal<unknown> | Record<string, unknown>>} StoreState
|
|
141
|
+
* @description Represents the store's reactive state structure with support for nested modules.
|
|
142
|
+
*/ /**
|
|
143
|
+
* Action function signature for store actions.
|
|
144
|
+
* @typedef {(state: StoreState, payload?: unknown) => unknown} ActionFunction
|
|
145
|
+
* @description Function that receives state and optional payload, returns action result.
|
|
146
|
+
*/ /**
|
|
147
|
+
* Dispatch function signature for triggering actions.
|
|
148
|
+
* @typedef {(actionName: string, payload?: unknown) => Promise<unknown>} DispatchFunction
|
|
149
|
+
* @description Dispatches an action by name with optional payload, returns action result.
|
|
150
|
+
*/ /**
|
|
151
|
+
* Subscribe callback signature for mutation listeners.
|
|
152
|
+
* @typedef {(mutation: StoreMutation, state: StoreState) => void} SubscribeCallback
|
|
153
|
+
* @description Called after each successful action dispatch with mutation details and current state.
|
|
154
|
+
*/ /**
|
|
155
|
+
* Store API exposed to components via ctx.store.
|
|
156
|
+
* @typedef {Object} StoreApi
|
|
157
|
+
* @property {StoreState} state
|
|
158
|
+
* Reactive state signals (supports nested modules).
|
|
159
|
+
* @property {DispatchFunction} dispatch
|
|
160
|
+
* Dispatch an action by name with optional payload.
|
|
161
|
+
* @property {(callback: SubscribeCallback) => () => void} subscribe
|
|
162
|
+
* Subscribe to state mutations. Returns unsubscribe function.
|
|
163
|
+
* @property {() => Record<string, unknown>} getState
|
|
164
|
+
* Get a snapshot of current state values.
|
|
165
|
+
* @property {(namespace: string, module: StoreModule) => void} registerModule
|
|
166
|
+
* Register a namespaced module dynamically.
|
|
167
|
+
* @property {(namespace: string) => void} unregisterModule
|
|
168
|
+
* Unregister a namespaced module.
|
|
169
|
+
* @property {(key: string, initialValue: unknown) => Signal<unknown>} createState
|
|
170
|
+
* Create a new state signal dynamically.
|
|
171
|
+
* @property {(name: string, actionFn: ActionFunction) => void} createAction
|
|
172
|
+
* Register a new action dynamically.
|
|
173
|
+
* @property {new <T>(value: T) => Signal<T>} signal
|
|
174
|
+
* Signal class constructor for manual state creation.
|
|
175
|
+
* @description The store API injected into component setup as `ctx.store`.
|
|
176
|
+
* @example
|
|
177
|
+
* app.component('Counter', {
|
|
178
|
+
* setup({ store }) {
|
|
179
|
+
* // Access reactive state
|
|
180
|
+
* const count = store.state.count;
|
|
181
|
+
*
|
|
182
|
+
* // Dispatch actions
|
|
183
|
+
* const increment = () => store.dispatch('increment');
|
|
184
|
+
*
|
|
185
|
+
* // Subscribe to changes
|
|
186
|
+
* const unsub = store.subscribe((mutation) => {
|
|
187
|
+
* console.log('State changed:', mutation.type);
|
|
188
|
+
* });
|
|
189
|
+
*
|
|
190
|
+
* return { count, increment, onUnmount: () => unsub() };
|
|
191
|
+
* },
|
|
192
|
+
* template: (ctx) => `<button @click="increment">${ctx.count.value}</button>`
|
|
193
|
+
* });
|
|
194
|
+
* @see StoreMutation - Mutation record structure.
|
|
195
|
+
* @see StoreModule - Module definition for namespaces.
|
|
196
|
+
*/ /**
|
|
9
197
|
* @class 🏪 StorePlugin
|
|
10
198
|
* @classdesc A powerful reactive state management plugin for Eleva that enables sharing
|
|
11
199
|
* reactive data across the entire application. The Store plugin provides a centralized,
|
|
@@ -32,7 +220,7 @@
|
|
|
32
220
|
* },
|
|
33
221
|
* actions: {
|
|
34
222
|
* increment: (state) => state.counter.value++,
|
|
35
|
-
* addTodo: (state, todo) => state.todos.value.
|
|
223
|
+
* addTodo: (state, todo) => state.todos.value = [...state.todos.value, todo],
|
|
36
224
|
* setUser: (state, user) => state.user.value = user
|
|
37
225
|
* },
|
|
38
226
|
* persistence: {
|
|
@@ -55,7 +243,7 @@
|
|
|
55
243
|
* <div>
|
|
56
244
|
* <p>Hello ${ctx.user.value.name}!</p>
|
|
57
245
|
* <p>Count: ${ctx.count.value}</p>
|
|
58
|
-
* <button
|
|
246
|
+
* <button @click="increment">+</button>
|
|
59
247
|
* </div>
|
|
60
248
|
* `
|
|
61
249
|
* });
|
|
@@ -67,27 +255,34 @@
|
|
|
67
255
|
/**
|
|
68
256
|
* Plugin version
|
|
69
257
|
* @type {string}
|
|
70
|
-
*/ version: "1.
|
|
258
|
+
*/ version: "1.1.0",
|
|
71
259
|
/**
|
|
72
260
|
* Plugin description
|
|
73
261
|
* @type {string}
|
|
74
262
|
*/ description: "Reactive state management for sharing data across the entire Eleva application",
|
|
75
263
|
/**
|
|
76
|
-
* Installs the plugin into the Eleva instance
|
|
264
|
+
* Installs the plugin into the Eleva instance.
|
|
77
265
|
*
|
|
78
|
-
* @
|
|
79
|
-
* @param {
|
|
80
|
-
* @param {
|
|
81
|
-
* @param {
|
|
82
|
-
* @param {
|
|
83
|
-
* @param {
|
|
84
|
-
* @param {
|
|
85
|
-
* @param {
|
|
86
|
-
* @param {
|
|
87
|
-
* @param {
|
|
88
|
-
* @param {
|
|
89
|
-
* @param {
|
|
90
|
-
* @param {
|
|
266
|
+
* @public
|
|
267
|
+
* @param {Eleva} eleva - The Eleva instance.
|
|
268
|
+
* @param {StoreOptions} options - Plugin configuration options.
|
|
269
|
+
* @param {Record<string, unknown>} [options.state={}] - Initial state object.
|
|
270
|
+
* @param {Record<string, ActionFunction>} [options.actions={}] - Action functions for state mutations.
|
|
271
|
+
* @param {Record<string, StoreModule>} [options.namespaces={}] - Namespaced modules for organizing store.
|
|
272
|
+
* @param {StorePersistenceOptions} [options.persistence] - Persistence configuration.
|
|
273
|
+
* @param {boolean} [options.persistence.enabled=false] - Enable state persistence.
|
|
274
|
+
* @param {string} [options.persistence.key="eleva-store"] - Storage key.
|
|
275
|
+
* @param {'localStorage' | 'sessionStorage'} [options.persistence.storage="localStorage"] - Storage type.
|
|
276
|
+
* @param {string[]} [options.persistence.include] - Dot-path prefixes to persist (e.g., "auth.user")
|
|
277
|
+
* @param {string[]} [options.persistence.exclude] - Dot-path prefixes to exclude (applies when include is empty).
|
|
278
|
+
* @param {boolean} [options.devTools=false] - Enable development tools integration.
|
|
279
|
+
* @param {(error: Error, context: string) => void} [options.onError=null] - Error handler function.
|
|
280
|
+
* @returns {void}
|
|
281
|
+
* @description
|
|
282
|
+
* Installs the store and injects `store` into component setup context by wrapping
|
|
283
|
+
* `eleva.mount` and `eleva._mountComponents`. Also exposes `eleva.store` and
|
|
284
|
+
* helper methods (`eleva.dispatch`, `eleva.getState`, `eleva.subscribe`, `eleva.createAction`).
|
|
285
|
+
* Uninstall restores the originals.
|
|
91
286
|
*
|
|
92
287
|
* @example
|
|
93
288
|
* // Basic installation
|
|
@@ -107,12 +302,12 @@
|
|
|
107
302
|
* state: { user: null, token: null },
|
|
108
303
|
* actions: {
|
|
109
304
|
* login: (state, { user, token }) => {
|
|
110
|
-
* state.user.value = user;
|
|
111
|
-
* state.token.value = token;
|
|
305
|
+
* state.auth.user.value = user;
|
|
306
|
+
* state.auth.token.value = token;
|
|
112
307
|
* },
|
|
113
308
|
* logout: (state) => {
|
|
114
|
-
* state.user.value = null;
|
|
115
|
-
* state.token.value = null;
|
|
309
|
+
* state.auth.user.value = null;
|
|
310
|
+
* state.auth.token.value = null;
|
|
116
311
|
* }
|
|
117
312
|
* }
|
|
118
313
|
* }
|
|
@@ -125,12 +320,18 @@
|
|
|
125
320
|
*/ install (eleva, options = {}) {
|
|
126
321
|
const { state = {}, actions = {}, namespaces = {}, persistence = {}, devTools = false, onError = null } = options;
|
|
127
322
|
/**
|
|
128
|
-
* Store
|
|
323
|
+
* @class Store
|
|
324
|
+
* @classdesc Store instance that manages all state and provides the API.
|
|
129
325
|
* @private
|
|
130
326
|
*/ class Store {
|
|
131
327
|
/**
|
|
132
|
-
* Initializes the root state and actions
|
|
328
|
+
* Initializes the root state and actions.
|
|
329
|
+
* Creates reactive signals for each state property and copies actions.
|
|
330
|
+
*
|
|
133
331
|
* @private
|
|
332
|
+
* @param {Record<string, unknown>} initialState - The initial state key-value pairs.
|
|
333
|
+
* @param {Record<string, ActionFunction>} initialActions - The action functions to register.
|
|
334
|
+
* @returns {void}
|
|
134
335
|
*/ _initializeState(initialState, initialActions) {
|
|
135
336
|
// Create reactive signals for each state property
|
|
136
337
|
Object.entries(initialState).forEach(([key, value])=>{
|
|
@@ -142,8 +343,12 @@
|
|
|
142
343
|
};
|
|
143
344
|
}
|
|
144
345
|
/**
|
|
145
|
-
* Initializes namespaced modules
|
|
346
|
+
* Initializes namespaced modules.
|
|
347
|
+
* Creates namespace objects and populates them with state signals and actions.
|
|
348
|
+
*
|
|
146
349
|
* @private
|
|
350
|
+
* @param {Record<string, StoreModule>} namespaces - Map of namespace names to module definitions.
|
|
351
|
+
* @returns {void}
|
|
147
352
|
*/ _initializeNamespaces(namespaces) {
|
|
148
353
|
Object.entries(namespaces).forEach(([namespace, module])=>{
|
|
149
354
|
const { state: moduleState = {}, actions: moduleActions = {} } = module;
|
|
@@ -165,8 +370,12 @@
|
|
|
165
370
|
});
|
|
166
371
|
}
|
|
167
372
|
/**
|
|
168
|
-
* Loads persisted state from storage
|
|
373
|
+
* Loads persisted state from storage.
|
|
374
|
+
* Reads from localStorage/sessionStorage and applies values to state signals.
|
|
375
|
+
* Does nothing if persistence is disabled or running in SSR environment.
|
|
376
|
+
*
|
|
169
377
|
* @private
|
|
378
|
+
* @returns {void}
|
|
170
379
|
*/ _loadPersistedState() {
|
|
171
380
|
if (!this.persistence.enabled || typeof window === "undefined") {
|
|
172
381
|
return;
|
|
@@ -187,8 +396,14 @@
|
|
|
187
396
|
}
|
|
188
397
|
}
|
|
189
398
|
/**
|
|
190
|
-
* Applies persisted data to the current state
|
|
399
|
+
* Applies persisted data to the current state.
|
|
400
|
+
* Recursively updates signal values for paths that should be persisted.
|
|
401
|
+
*
|
|
191
402
|
* @private
|
|
403
|
+
* @param {Record<string, unknown>} data - The persisted data object to apply.
|
|
404
|
+
* @param {Record<string, unknown>} [currentState=this.state] - The current state object to update.
|
|
405
|
+
* @param {string} [path=""] - The current dot-notation path (for include/exclude filtering).
|
|
406
|
+
* @returns {void}
|
|
192
407
|
*/ _applyPersistedData(data, currentState = this.state, path = "") {
|
|
193
408
|
Object.entries(data).forEach(([key, value])=>{
|
|
194
409
|
const fullPath = path ? `${path}.${key}` : key;
|
|
@@ -204,8 +419,12 @@
|
|
|
204
419
|
});
|
|
205
420
|
}
|
|
206
421
|
/**
|
|
207
|
-
* Determines if a state path should be persisted
|
|
422
|
+
* Determines if a state path should be persisted.
|
|
423
|
+
* Checks against include/exclude filters configured in persistence options.
|
|
424
|
+
*
|
|
208
425
|
* @private
|
|
426
|
+
* @param {string} path - The dot-notation path to check (e.g., "auth.user").
|
|
427
|
+
* @returns {boolean} True if the path should be persisted, false otherwise.
|
|
209
428
|
*/ _shouldPersist(path) {
|
|
210
429
|
const { include, exclude } = this.persistence;
|
|
211
430
|
if (include && include.length > 0) {
|
|
@@ -217,8 +436,12 @@
|
|
|
217
436
|
return true;
|
|
218
437
|
}
|
|
219
438
|
/**
|
|
220
|
-
* Saves current state to storage
|
|
439
|
+
* Saves current state to storage.
|
|
440
|
+
* Extracts persistable data and writes to localStorage/sessionStorage.
|
|
441
|
+
* Does nothing if persistence is disabled or running in SSR environment.
|
|
442
|
+
*
|
|
221
443
|
* @private
|
|
444
|
+
* @returns {void}
|
|
222
445
|
*/ _saveState() {
|
|
223
446
|
if (!this.persistence.enabled || typeof window === "undefined") {
|
|
224
447
|
return;
|
|
@@ -236,8 +459,13 @@
|
|
|
236
459
|
}
|
|
237
460
|
}
|
|
238
461
|
/**
|
|
239
|
-
* Extracts data that should be persisted
|
|
462
|
+
* Extracts data that should be persisted.
|
|
463
|
+
* Recursively extracts signal values for paths that pass persistence filters.
|
|
464
|
+
*
|
|
240
465
|
* @private
|
|
466
|
+
* @param {Record<string, unknown>} [currentState=this.state] - The state object to extract from.
|
|
467
|
+
* @param {string} [path=""] - The current dot-notation path (for include/exclude filtering).
|
|
468
|
+
* @returns {Record<string, unknown>} The extracted data object with raw values (not signals).
|
|
241
469
|
*/ _extractPersistedData(currentState = this.state, path = "") {
|
|
242
470
|
const result = {};
|
|
243
471
|
Object.entries(currentState).forEach(([key, value])=>{
|
|
@@ -258,8 +486,12 @@
|
|
|
258
486
|
return result;
|
|
259
487
|
}
|
|
260
488
|
/**
|
|
261
|
-
* Sets up development tools integration
|
|
489
|
+
* Sets up development tools integration.
|
|
490
|
+
* Registers the store with Eleva DevTools if available and enabled.
|
|
491
|
+
* Does nothing if devTools is disabled, running in SSR, or DevTools not installed.
|
|
492
|
+
*
|
|
262
493
|
* @private
|
|
494
|
+
* @returns {void}
|
|
263
495
|
*/ _setupDevTools() {
|
|
264
496
|
if (!this.devTools || typeof window === "undefined" || !window.__ELEVA_DEVTOOLS__) {
|
|
265
497
|
return;
|
|
@@ -267,10 +499,26 @@
|
|
|
267
499
|
window.__ELEVA_DEVTOOLS__.registerStore(this);
|
|
268
500
|
}
|
|
269
501
|
/**
|
|
270
|
-
* Dispatches an action to mutate the state
|
|
271
|
-
*
|
|
272
|
-
*
|
|
273
|
-
*
|
|
502
|
+
* Dispatches an action to mutate the state.
|
|
503
|
+
*
|
|
504
|
+
* Execution flow:
|
|
505
|
+
* 1. Retrieves the action function (supports namespaced actions like "auth.login")
|
|
506
|
+
* 2. Records mutation for devtools/history (keeps last 100 mutations)
|
|
507
|
+
* 3. Executes action with await (actions can be sync or async)
|
|
508
|
+
* 4. Saves state if persistence is enabled
|
|
509
|
+
* 5. Notifies all subscribers with (mutation, state)
|
|
510
|
+
* 6. Notifies devtools if enabled
|
|
511
|
+
*
|
|
512
|
+
* @note Always returns a Promise regardless of whether the action is sync or async.
|
|
513
|
+
* Subscriber callbacks that throw are caught and passed to onError handler.
|
|
514
|
+
*
|
|
515
|
+
* @async
|
|
516
|
+
* @param {string} actionName - The name of the action to dispatch (supports dot notation for namespaces).
|
|
517
|
+
* @param {unknown} payload - The payload to pass to the action.
|
|
518
|
+
* @returns {Promise<unknown>} The result of the action (undefined if action returns nothing).
|
|
519
|
+
* @throws {Error} If action is not found or action function throws.
|
|
520
|
+
* @see subscribe - Listen for mutations triggered by dispatch.
|
|
521
|
+
* @see getState - Get current state values.
|
|
274
522
|
*/ async dispatch(actionName, payload) {
|
|
275
523
|
try {
|
|
276
524
|
const action = this._getAction(actionName);
|
|
@@ -318,8 +566,12 @@
|
|
|
318
566
|
}
|
|
319
567
|
}
|
|
320
568
|
/**
|
|
321
|
-
* Gets an action by name (supports namespaced actions)
|
|
569
|
+
* Gets an action by name (supports namespaced actions).
|
|
570
|
+
* Traverses the actions object using dot-notation path segments.
|
|
571
|
+
*
|
|
322
572
|
* @private
|
|
573
|
+
* @param {string} actionName - The action name, supports dot notation for namespaces (e.g., "auth.login").
|
|
574
|
+
* @returns {ActionFunction | null} The action function if found and is a function, null otherwise.
|
|
323
575
|
*/ _getAction(actionName) {
|
|
324
576
|
const parts = actionName.split(".");
|
|
325
577
|
let current = this.actions;
|
|
@@ -332,9 +584,18 @@
|
|
|
332
584
|
return typeof current === "function" ? current : null;
|
|
333
585
|
}
|
|
334
586
|
/**
|
|
335
|
-
* Subscribes to store mutations
|
|
336
|
-
*
|
|
337
|
-
*
|
|
587
|
+
* Subscribes to store mutations.
|
|
588
|
+
* Callback is invoked after every successful action dispatch.
|
|
589
|
+
*
|
|
590
|
+
* @param {SubscribeCallback} callback
|
|
591
|
+
* Called after each mutation with:
|
|
592
|
+
* - mutation.type: The action name that was dispatched
|
|
593
|
+
* - mutation.payload: The payload passed to the action
|
|
594
|
+
* - mutation.timestamp: When the mutation occurred (Date.now())
|
|
595
|
+
* - state: The current state object (contains Signals)
|
|
596
|
+
* @returns {() => void} Unsubscribe function. Call to stop receiving notifications.
|
|
597
|
+
* @throws {Error} If callback is not a function.
|
|
598
|
+
* @see dispatch - Triggers mutations that notify subscribers.
|
|
338
599
|
*/ subscribe(callback) {
|
|
339
600
|
if (typeof callback !== "function") {
|
|
340
601
|
throw new Error("Subscribe callback must be a function");
|
|
@@ -346,20 +607,32 @@
|
|
|
346
607
|
};
|
|
347
608
|
}
|
|
348
609
|
/**
|
|
349
|
-
* Gets
|
|
350
|
-
*
|
|
610
|
+
* Gets current state values (not signals).
|
|
611
|
+
*
|
|
612
|
+
* @note When persistence include/exclude filters are configured,
|
|
613
|
+
* this returns only the filtered subset of state.
|
|
614
|
+
* @returns {Record<string, unknown>} The current state values (filtered by persistence config if set).
|
|
615
|
+
* @see replaceState - Set state values.
|
|
616
|
+
* @see subscribe - Listen for state changes.
|
|
351
617
|
*/ getState() {
|
|
352
618
|
return this._extractPersistedData();
|
|
353
619
|
}
|
|
354
620
|
/**
|
|
355
|
-
* Replaces
|
|
356
|
-
*
|
|
621
|
+
* Replaces state values (useful for testing or state hydration).
|
|
622
|
+
*
|
|
623
|
+
* @note When persistence include/exclude filters are configured,
|
|
624
|
+
* this only updates the filtered subset of state.
|
|
625
|
+
* @param {Record<string, unknown>} newState - The new state object.
|
|
626
|
+
* @returns {void}
|
|
627
|
+
* @see getState - Get current state values.
|
|
357
628
|
*/ replaceState(newState) {
|
|
358
629
|
this._applyPersistedData(newState);
|
|
359
630
|
this._saveState();
|
|
360
631
|
}
|
|
361
632
|
/**
|
|
362
|
-
* Clears persisted state from storage
|
|
633
|
+
* Clears persisted state from storage.
|
|
634
|
+
* Does nothing if persistence is disabled or running in SSR.
|
|
635
|
+
* @returns {void}
|
|
363
636
|
*/ clearPersistedState() {
|
|
364
637
|
if (!this.persistence.enabled || typeof window === "undefined") {
|
|
365
638
|
return;
|
|
@@ -374,11 +647,14 @@
|
|
|
374
647
|
}
|
|
375
648
|
}
|
|
376
649
|
/**
|
|
377
|
-
* Registers a new namespaced module at runtime
|
|
378
|
-
*
|
|
379
|
-
*
|
|
380
|
-
* @param {
|
|
381
|
-
* @param {
|
|
650
|
+
* Registers a new namespaced module at runtime.
|
|
651
|
+
* Logs a warning if the namespace already exists.
|
|
652
|
+
* Module state is nested under `state[namespace]` and actions under `actions[namespace]`.
|
|
653
|
+
* @param {string} namespace - The namespace for the module.
|
|
654
|
+
* @param {StoreModule} module - The module definition.
|
|
655
|
+
* @param {Record<string, unknown>} module.state - The module's initial state.
|
|
656
|
+
* @param {Record<string, ActionFunction>} module.actions - The module's actions.
|
|
657
|
+
* @returns {void}
|
|
382
658
|
*/ registerModule(namespace, module) {
|
|
383
659
|
if (this.state[namespace] || this.actions[namespace]) {
|
|
384
660
|
console.warn(`[StorePlugin] Module "${namespace}" already exists`);
|
|
@@ -394,8 +670,11 @@
|
|
|
394
670
|
this._saveState();
|
|
395
671
|
}
|
|
396
672
|
/**
|
|
397
|
-
* Unregisters a namespaced module
|
|
398
|
-
*
|
|
673
|
+
* Unregisters a namespaced module.
|
|
674
|
+
* Logs a warning if the namespace doesn't exist.
|
|
675
|
+
* Removes both state and actions under the namespace.
|
|
676
|
+
* @param {string} namespace - The namespace to unregister.
|
|
677
|
+
* @returns {void}
|
|
399
678
|
*/ unregisterModule(namespace) {
|
|
400
679
|
if (!this.state[namespace] && !this.actions[namespace]) {
|
|
401
680
|
console.warn(`[StorePlugin] Module "${namespace}" does not exist`);
|
|
@@ -406,10 +685,11 @@
|
|
|
406
685
|
this._saveState();
|
|
407
686
|
}
|
|
408
687
|
/**
|
|
409
|
-
* Creates a new reactive state property at runtime
|
|
410
|
-
*
|
|
411
|
-
* @param {
|
|
412
|
-
* @
|
|
688
|
+
* Creates a new reactive state property at runtime.
|
|
689
|
+
*
|
|
690
|
+
* @param {string} key - The state key.
|
|
691
|
+
* @param {*} initialValue - The initial value.
|
|
692
|
+
* @returns {Signal} The created signal, or existing signal if key exists.
|
|
413
693
|
*/ createState(key, initialValue) {
|
|
414
694
|
if (this.state[key]) {
|
|
415
695
|
return this.state[key]; // Return existing state
|
|
@@ -419,21 +699,50 @@
|
|
|
419
699
|
return this.state[key];
|
|
420
700
|
}
|
|
421
701
|
/**
|
|
422
|
-
* Creates a new action at runtime
|
|
423
|
-
*
|
|
424
|
-
*
|
|
702
|
+
* Creates a new action at runtime.
|
|
703
|
+
* Overwrites existing action if name already exists.
|
|
704
|
+
* Supports dot-notation for namespaced actions (e.g., "auth.login").
|
|
705
|
+
* @param {string} name - The action name (supports dot notation for namespaces).
|
|
706
|
+
* @param {ActionFunction} actionFn - The action function (receives state and payload).
|
|
707
|
+
* @returns {void}
|
|
708
|
+
* @throws {Error} If actionFn is not a function.
|
|
709
|
+
* @example
|
|
710
|
+
* // Root-level action
|
|
711
|
+
* store.createAction("increment", (state) => state.count.value++);
|
|
712
|
+
*
|
|
713
|
+
* // Namespaced action
|
|
714
|
+
* store.createAction("auth.login", async (state, credentials) => {
|
|
715
|
+
* // ... login logic
|
|
716
|
+
* });
|
|
425
717
|
*/ createAction(name, actionFn) {
|
|
426
718
|
if (typeof actionFn !== "function") {
|
|
427
719
|
throw new Error("Action must be a function");
|
|
428
720
|
}
|
|
429
|
-
|
|
721
|
+
// Fast path: no dot means simple action (avoids array allocation)
|
|
722
|
+
if (name.indexOf(".") === -1) {
|
|
723
|
+
this.actions[name] = actionFn;
|
|
724
|
+
return;
|
|
725
|
+
}
|
|
726
|
+
// Namespaced action, traverse/create nested structure
|
|
727
|
+
const parts = name.split(".");
|
|
728
|
+
const lastIndex = parts.length - 1;
|
|
729
|
+
let current = this.actions;
|
|
730
|
+
for(let i = 0; i < lastIndex; i++){
|
|
731
|
+
current = current[parts[i]] || (current[parts[i]] = {});
|
|
732
|
+
}
|
|
733
|
+
current[parts[lastIndex]] = actionFn;
|
|
430
734
|
}
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
735
|
+
/**
|
|
736
|
+
* Creates a new Store instance.
|
|
737
|
+
* Initializes state signals, actions, persistence, and devtools integration.
|
|
738
|
+
*
|
|
739
|
+
* @constructor
|
|
740
|
+
*/ constructor(){
|
|
741
|
+
/** @type {Record<string, Signal | Record<string, unknown>>} */ this.state = {};
|
|
742
|
+
/** @type {Record<string, ActionFunction | Record<string, ActionFunction>>} */ this.actions = {};
|
|
743
|
+
/** @type {Set<SubscribeCallback>} */ this.subscribers = new Set();
|
|
744
|
+
/** @type {StoreMutation[]} */ this.mutations = [];
|
|
745
|
+
/** @type {{enabled: boolean, key: string, storage: string, include: string[]|null, exclude: string[]|null}} */ this.persistence = {
|
|
437
746
|
enabled: false,
|
|
438
747
|
key: "eleva-store",
|
|
439
748
|
storage: "localStorage",
|
|
@@ -441,8 +750,8 @@
|
|
|
441
750
|
exclude: null,
|
|
442
751
|
...persistence
|
|
443
752
|
};
|
|
444
|
-
this.devTools = devTools;
|
|
445
|
-
this.onError = onError;
|
|
753
|
+
/** @type {boolean} */ this.devTools = devTools;
|
|
754
|
+
/** @type {((error: Error, context: string) => void)|null} */ this.onError = onError;
|
|
446
755
|
this._initializeState(state, actions);
|
|
447
756
|
this._initializeNamespaces(namespaces);
|
|
448
757
|
this._loadPersistedState();
|
|
@@ -454,7 +763,13 @@
|
|
|
454
763
|
// Store the original mount method to override it
|
|
455
764
|
const originalMount = eleva.mount;
|
|
456
765
|
/**
|
|
457
|
-
*
|
|
766
|
+
* Overridden mount method that injects store context into components.
|
|
767
|
+
* Wraps the original mount to add `ctx.store` to the component's setup context.
|
|
768
|
+
*
|
|
769
|
+
* @param {HTMLElement} container - The DOM element where the component will be mounted.
|
|
770
|
+
* @param {string | ComponentDefinition} compName - Component name or definition.
|
|
771
|
+
* @param {ComponentProps} [props={}] - Optional properties to pass to the component.
|
|
772
|
+
* @returns {Promise<MountResult>} The mount result.
|
|
458
773
|
*/ eleva.mount = async (container, compName, props = {})=>{
|
|
459
774
|
// Get the component definition
|
|
460
775
|
const componentDef = typeof compName === "string" ? eleva._components.get(compName) || compName : compName;
|
|
@@ -465,8 +780,7 @@
|
|
|
465
780
|
const wrappedComponent = {
|
|
466
781
|
...componentDef,
|
|
467
782
|
async setup (ctx) {
|
|
468
|
-
|
|
469
|
-
ctx.store = {
|
|
783
|
+
/** @type {StoreApi} */ ctx.store = {
|
|
470
784
|
// Core store functionality
|
|
471
785
|
state: store.state,
|
|
472
786
|
dispatch: store.dispatch.bind(store),
|
|
@@ -492,7 +806,16 @@
|
|
|
492
806
|
};
|
|
493
807
|
// Override _mountComponents to ensure child components also get store context
|
|
494
808
|
const originalMountComponents = eleva._mountComponents;
|
|
495
|
-
|
|
809
|
+
/**
|
|
810
|
+
* Overridden _mountComponents method that injects store context into child components.
|
|
811
|
+
* Wraps each child component's setup function to add `ctx.store` before mounting.
|
|
812
|
+
*
|
|
813
|
+
* @param {HTMLElement} container - The parent container element.
|
|
814
|
+
* @param {ChildrenMap} children - Map of selectors to component definitions.
|
|
815
|
+
* @param {MountResult[]} childInstances - Array to store mounted instances.
|
|
816
|
+
* @param {ComponentContext & SetupResult} context - Parent component context.
|
|
817
|
+
* @returns {Promise<void>}
|
|
818
|
+
*/ eleva._mountComponents = async (container, children, childInstances, context)=>{
|
|
496
819
|
// Create wrapped children with store injection
|
|
497
820
|
const wrappedChildren = {};
|
|
498
821
|
for (const [selector, childComponent] of Object.entries(children)){
|
|
@@ -501,8 +824,7 @@
|
|
|
501
824
|
wrappedChildren[selector] = {
|
|
502
825
|
...componentDef,
|
|
503
826
|
async setup (ctx) {
|
|
504
|
-
|
|
505
|
-
ctx.store = {
|
|
827
|
+
/** @type {StoreApi} */ ctx.store = {
|
|
506
828
|
// Core store functionality
|
|
507
829
|
state: store.state,
|
|
508
830
|
dispatch: store.dispatch.bind(store),
|
|
@@ -528,23 +850,23 @@
|
|
|
528
850
|
}
|
|
529
851
|
}
|
|
530
852
|
// Call original _mountComponents with wrapped children
|
|
531
|
-
return await originalMountComponents.call(eleva, container, wrappedChildren, childInstances);
|
|
853
|
+
return await originalMountComponents.call(eleva, container, wrappedChildren, childInstances, context);
|
|
532
854
|
};
|
|
533
855
|
// Expose store instance and utilities on the Eleva instance
|
|
534
|
-
eleva.store = store;
|
|
856
|
+
/** @type {StoreApi} */ eleva.store = store;
|
|
535
857
|
/**
|
|
536
|
-
* Expose utility methods on the Eleva instance
|
|
537
|
-
*
|
|
538
|
-
*/ eleva.createAction = (name, actionFn)=>{
|
|
539
|
-
store.
|
|
858
|
+
* Expose utility methods on the Eleva instance.
|
|
859
|
+
* These are top-level helpers (e.g., `eleva.dispatch`) in addition to `eleva.store`.
|
|
860
|
+
*/ /** @type {(name: string, actionFn: ActionFunction) => void} */ eleva.createAction = (name, actionFn)=>{
|
|
861
|
+
store.createAction(name, actionFn);
|
|
540
862
|
};
|
|
541
|
-
eleva.dispatch = (actionName, payload)=>{
|
|
863
|
+
/** @type {DispatchFunction} */ eleva.dispatch = (actionName, payload)=>{
|
|
542
864
|
return store.dispatch(actionName, payload);
|
|
543
865
|
};
|
|
544
|
-
eleva.getState = ()=>{
|
|
866
|
+
/** @type {() => Record<string, unknown>} */ eleva.getState = ()=>{
|
|
545
867
|
return store.getState();
|
|
546
868
|
};
|
|
547
|
-
eleva.subscribe = (callback)=>{
|
|
869
|
+
/** @type {(callback: SubscribeCallback) => () => void} */ eleva.subscribe = (callback)=>{
|
|
548
870
|
return store.subscribe(callback);
|
|
549
871
|
};
|
|
550
872
|
// Store original methods for cleanup
|
|
@@ -552,14 +874,17 @@
|
|
|
552
874
|
eleva._originalMountComponents = originalMountComponents;
|
|
553
875
|
},
|
|
554
876
|
/**
|
|
555
|
-
* Uninstalls the plugin from the Eleva instance
|
|
556
|
-
*
|
|
557
|
-
* @param {Object} eleva - The Eleva instance
|
|
877
|
+
* Uninstalls the plugin from the Eleva instance.
|
|
558
878
|
*
|
|
879
|
+
* @public
|
|
880
|
+
* @param {Eleva} eleva - The Eleva instance.
|
|
881
|
+
* @returns {void}
|
|
559
882
|
* @description
|
|
560
883
|
* Restores the original Eleva methods and removes all plugin-specific
|
|
561
884
|
* functionality. This method should be called when the plugin is no
|
|
562
885
|
* longer needed.
|
|
886
|
+
* Also removes `eleva.store` and top-level helpers (`eleva.dispatch`,
|
|
887
|
+
* `eleva.getState`, `eleva.subscribe`, `eleva.createAction`).
|
|
563
888
|
*
|
|
564
889
|
* @example
|
|
565
890
|
* // Uninstall the plugin
|