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
package/src/plugins/Store.js
CHANGED
|
@@ -1,5 +1,222 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
|
|
3
|
+
/**
|
|
4
|
+
* @module eleva/plugins/store
|
|
5
|
+
* @fileoverview Reactive state management plugin with namespaced modules,
|
|
6
|
+
* persistence, and subscription system.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
// ============================================================================
|
|
10
|
+
// TYPE DEFINITIONS
|
|
11
|
+
// ============================================================================
|
|
12
|
+
|
|
13
|
+
// -----------------------------------------------------------------------------
|
|
14
|
+
// External Type Imports
|
|
15
|
+
// -----------------------------------------------------------------------------
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Type imports from the Eleva core library.
|
|
19
|
+
* @typedef {import('eleva').Eleva} Eleva
|
|
20
|
+
* @typedef {import('eleva').ComponentDefinition} ComponentDefinition
|
|
21
|
+
* @typedef {import('eleva').ComponentContext} ComponentContext
|
|
22
|
+
* @typedef {import('eleva').SetupResult} SetupResult
|
|
23
|
+
* @typedef {import('eleva').ComponentProps} ComponentProps
|
|
24
|
+
* @typedef {import('eleva').ChildrenMap} ChildrenMap
|
|
25
|
+
* @typedef {import('eleva').MountResult} MountResult
|
|
26
|
+
*/
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Generic type import.
|
|
30
|
+
* @template T
|
|
31
|
+
* @typedef {import('eleva').Signal<T>} Signal
|
|
32
|
+
*/
|
|
33
|
+
|
|
34
|
+
// -----------------------------------------------------------------------------
|
|
35
|
+
// Store Type Definitions
|
|
36
|
+
// -----------------------------------------------------------------------------
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Mutation record emitted to subscribers.
|
|
40
|
+
* @typedef {Object} StoreMutation
|
|
41
|
+
* @property {string} type
|
|
42
|
+
* The action name that was dispatched.
|
|
43
|
+
* @property {unknown} payload
|
|
44
|
+
* The payload passed to the action.
|
|
45
|
+
* @property {number} timestamp
|
|
46
|
+
* Unix timestamp of when the mutation occurred.
|
|
47
|
+
* @description Record passed to subscribers when state changes via dispatch.
|
|
48
|
+
* @example
|
|
49
|
+
* store.subscribe((mutation, state) => {
|
|
50
|
+
* console.log(`Action: ${mutation.type}`);
|
|
51
|
+
* console.log(`Payload: ${mutation.payload}`);
|
|
52
|
+
* console.log(`Time: ${new Date(mutation.timestamp)}`);
|
|
53
|
+
* });
|
|
54
|
+
*/
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Store configuration options.
|
|
58
|
+
* @typedef {Object} StoreOptions
|
|
59
|
+
* @property {Record<string, unknown>} [state]
|
|
60
|
+
* Initial state object.
|
|
61
|
+
* @property {Record<string, ActionFunction>} [actions]
|
|
62
|
+
* Action functions for state mutations.
|
|
63
|
+
* @property {Record<string, StoreModule>} [namespaces]
|
|
64
|
+
* Namespaced modules for organizing store.
|
|
65
|
+
* @property {StorePersistenceOptions} [persistence]
|
|
66
|
+
* Persistence configuration.
|
|
67
|
+
* @property {boolean} [devTools]
|
|
68
|
+
* Enable development tools integration.
|
|
69
|
+
* @property {StoreErrorHandler} [onError]
|
|
70
|
+
* Error handler function.
|
|
71
|
+
* @description Configuration options passed to StorePlugin.install().
|
|
72
|
+
* @example
|
|
73
|
+
* app.use(StorePlugin, {
|
|
74
|
+
* state: { count: 0, user: null },
|
|
75
|
+
* actions: {
|
|
76
|
+
* increment: (state) => state.count.value++,
|
|
77
|
+
* setUser: (state, user) => state.user.value = user
|
|
78
|
+
* },
|
|
79
|
+
* persistence: { enabled: true, key: 'my-app' }
|
|
80
|
+
* });
|
|
81
|
+
*/
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Namespaced store module definition.
|
|
85
|
+
* @typedef {Object} StoreModule
|
|
86
|
+
* @property {Record<string, unknown>} state
|
|
87
|
+
* Module state.
|
|
88
|
+
* @property {Record<string, ActionFunction>} [actions]
|
|
89
|
+
* Module actions.
|
|
90
|
+
* @description Defines a namespaced module for organizing related state and actions.
|
|
91
|
+
* @example
|
|
92
|
+
* // Define a module
|
|
93
|
+
* const authModule = {
|
|
94
|
+
* state: { user: null, token: null },
|
|
95
|
+
* actions: {
|
|
96
|
+
* login: (state, { user, token }) => {
|
|
97
|
+
* state.auth.user.value = user;
|
|
98
|
+
* state.auth.token.value = token;
|
|
99
|
+
* }
|
|
100
|
+
* }
|
|
101
|
+
* };
|
|
102
|
+
*
|
|
103
|
+
* // Register dynamically
|
|
104
|
+
* store.registerModule('auth', authModule);
|
|
105
|
+
*/
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Store persistence configuration.
|
|
109
|
+
* @typedef {Object} StorePersistenceOptions
|
|
110
|
+
* @property {boolean} [enabled]
|
|
111
|
+
* Enable state persistence.
|
|
112
|
+
* @property {string} [key]
|
|
113
|
+
* Storage key (default: "eleva-store").
|
|
114
|
+
* @property {'localStorage' | 'sessionStorage'} [storage]
|
|
115
|
+
* Storage type.
|
|
116
|
+
* @property {string[]} [include]
|
|
117
|
+
* Dot-path prefixes to persist (e.g., "auth.user").
|
|
118
|
+
* @property {string[]} [exclude]
|
|
119
|
+
* Dot-path prefixes to exclude.
|
|
120
|
+
* @description Configuration for persisting store state to localStorage or sessionStorage.
|
|
121
|
+
* @example
|
|
122
|
+
* // Persist only specific state paths
|
|
123
|
+
* persistence: {
|
|
124
|
+
* enabled: true,
|
|
125
|
+
* key: 'my-app-store',
|
|
126
|
+
* storage: 'localStorage',
|
|
127
|
+
* include: ['user', 'settings.theme']
|
|
128
|
+
* }
|
|
129
|
+
*
|
|
130
|
+
* @example
|
|
131
|
+
* // Exclude sensitive data
|
|
132
|
+
* persistence: {
|
|
133
|
+
* enabled: true,
|
|
134
|
+
* exclude: ['auth.token', 'temp']
|
|
135
|
+
* }
|
|
136
|
+
*/
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Store error handler callback.
|
|
140
|
+
* @typedef {(error: Error, context: string) => void} StoreErrorHandler
|
|
141
|
+
* @description Custom error handler for store operations.
|
|
142
|
+
* @example
|
|
143
|
+
* app.use(StorePlugin, {
|
|
144
|
+
* onError: (error, context) => {
|
|
145
|
+
* console.error(`Store error in ${context}:`, error);
|
|
146
|
+
* // Send to error tracking service
|
|
147
|
+
* errorTracker.capture(error, { context });
|
|
148
|
+
* }
|
|
149
|
+
* });
|
|
150
|
+
*/
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Reactive state tree containing signals and nested namespaces.
|
|
154
|
+
* @typedef {Record<string, Signal<unknown> | Record<string, unknown>>} StoreState
|
|
155
|
+
* @description Represents the store's reactive state structure with support for nested modules.
|
|
156
|
+
*/
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* Action function signature for store actions.
|
|
160
|
+
* @typedef {(state: StoreState, payload?: unknown) => unknown} ActionFunction
|
|
161
|
+
* @description Function that receives state and optional payload, returns action result.
|
|
162
|
+
*/
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* Dispatch function signature for triggering actions.
|
|
166
|
+
* @typedef {(actionName: string, payload?: unknown) => Promise<unknown>} DispatchFunction
|
|
167
|
+
* @description Dispatches an action by name with optional payload, returns action result.
|
|
168
|
+
*/
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* Subscribe callback signature for mutation listeners.
|
|
172
|
+
* @typedef {(mutation: StoreMutation, state: StoreState) => void} SubscribeCallback
|
|
173
|
+
* @description Called after each successful action dispatch with mutation details and current state.
|
|
174
|
+
*/
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* Store API exposed to components via ctx.store.
|
|
178
|
+
* @typedef {Object} StoreApi
|
|
179
|
+
* @property {StoreState} state
|
|
180
|
+
* Reactive state signals (supports nested modules).
|
|
181
|
+
* @property {DispatchFunction} dispatch
|
|
182
|
+
* Dispatch an action by name with optional payload.
|
|
183
|
+
* @property {(callback: SubscribeCallback) => () => void} subscribe
|
|
184
|
+
* Subscribe to state mutations. Returns unsubscribe function.
|
|
185
|
+
* @property {() => Record<string, unknown>} getState
|
|
186
|
+
* Get a snapshot of current state values.
|
|
187
|
+
* @property {(namespace: string, module: StoreModule) => void} registerModule
|
|
188
|
+
* Register a namespaced module dynamically.
|
|
189
|
+
* @property {(namespace: string) => void} unregisterModule
|
|
190
|
+
* Unregister a namespaced module.
|
|
191
|
+
* @property {(key: string, initialValue: unknown) => Signal<unknown>} createState
|
|
192
|
+
* Create a new state signal dynamically.
|
|
193
|
+
* @property {(name: string, actionFn: ActionFunction) => void} createAction
|
|
194
|
+
* Register a new action dynamically.
|
|
195
|
+
* @property {new <T>(value: T) => Signal<T>} signal
|
|
196
|
+
* Signal class constructor for manual state creation.
|
|
197
|
+
* @description The store API injected into component setup as `ctx.store`.
|
|
198
|
+
* @example
|
|
199
|
+
* app.component('Counter', {
|
|
200
|
+
* setup({ store }) {
|
|
201
|
+
* // Access reactive state
|
|
202
|
+
* const count = store.state.count;
|
|
203
|
+
*
|
|
204
|
+
* // Dispatch actions
|
|
205
|
+
* const increment = () => store.dispatch('increment');
|
|
206
|
+
*
|
|
207
|
+
* // Subscribe to changes
|
|
208
|
+
* const unsub = store.subscribe((mutation) => {
|
|
209
|
+
* console.log('State changed:', mutation.type);
|
|
210
|
+
* });
|
|
211
|
+
*
|
|
212
|
+
* return { count, increment, onUnmount: () => unsub() };
|
|
213
|
+
* },
|
|
214
|
+
* template: (ctx) => `<button @click="increment">${ctx.count.value}</button>`
|
|
215
|
+
* });
|
|
216
|
+
* @see StoreMutation - Mutation record structure.
|
|
217
|
+
* @see StoreModule - Module definition for namespaces.
|
|
218
|
+
*/
|
|
219
|
+
|
|
3
220
|
/**
|
|
4
221
|
* @class 🏪 StorePlugin
|
|
5
222
|
* @classdesc A powerful reactive state management plugin for Eleva that enables sharing
|
|
@@ -27,7 +244,7 @@
|
|
|
27
244
|
* },
|
|
28
245
|
* actions: {
|
|
29
246
|
* increment: (state) => state.counter.value++,
|
|
30
|
-
* addTodo: (state, todo) => state.todos.value.
|
|
247
|
+
* addTodo: (state, todo) => state.todos.value = [...state.todos.value, todo],
|
|
31
248
|
* setUser: (state, user) => state.user.value = user
|
|
32
249
|
* },
|
|
33
250
|
* persistence: {
|
|
@@ -50,7 +267,7 @@
|
|
|
50
267
|
* <div>
|
|
51
268
|
* <p>Hello ${ctx.user.value.name}!</p>
|
|
52
269
|
* <p>Count: ${ctx.count.value}</p>
|
|
53
|
-
* <button
|
|
270
|
+
* <button @click="increment">+</button>
|
|
54
271
|
* </div>
|
|
55
272
|
* `
|
|
56
273
|
* });
|
|
@@ -66,7 +283,7 @@ export const StorePlugin = {
|
|
|
66
283
|
* Plugin version
|
|
67
284
|
* @type {string}
|
|
68
285
|
*/
|
|
69
|
-
version: "1.
|
|
286
|
+
version: "1.1.0",
|
|
70
287
|
|
|
71
288
|
/**
|
|
72
289
|
* Plugin description
|
|
@@ -76,21 +293,28 @@ export const StorePlugin = {
|
|
|
76
293
|
"Reactive state management for sharing data across the entire Eleva application",
|
|
77
294
|
|
|
78
295
|
/**
|
|
79
|
-
* Installs the plugin into the Eleva instance
|
|
296
|
+
* Installs the plugin into the Eleva instance.
|
|
80
297
|
*
|
|
81
|
-
* @
|
|
82
|
-
* @param {
|
|
83
|
-
* @param {
|
|
84
|
-
* @param {
|
|
85
|
-
* @param {
|
|
86
|
-
* @param {
|
|
87
|
-
* @param {
|
|
88
|
-
* @param {
|
|
89
|
-
* @param {
|
|
90
|
-
* @param {
|
|
91
|
-
* @param {
|
|
92
|
-
* @param {
|
|
93
|
-
* @param {
|
|
298
|
+
* @public
|
|
299
|
+
* @param {Eleva} eleva - The Eleva instance.
|
|
300
|
+
* @param {StoreOptions} options - Plugin configuration options.
|
|
301
|
+
* @param {Record<string, unknown>} [options.state={}] - Initial state object.
|
|
302
|
+
* @param {Record<string, ActionFunction>} [options.actions={}] - Action functions for state mutations.
|
|
303
|
+
* @param {Record<string, StoreModule>} [options.namespaces={}] - Namespaced modules for organizing store.
|
|
304
|
+
* @param {StorePersistenceOptions} [options.persistence] - Persistence configuration.
|
|
305
|
+
* @param {boolean} [options.persistence.enabled=false] - Enable state persistence.
|
|
306
|
+
* @param {string} [options.persistence.key="eleva-store"] - Storage key.
|
|
307
|
+
* @param {'localStorage' | 'sessionStorage'} [options.persistence.storage="localStorage"] - Storage type.
|
|
308
|
+
* @param {string[]} [options.persistence.include] - Dot-path prefixes to persist (e.g., "auth.user")
|
|
309
|
+
* @param {string[]} [options.persistence.exclude] - Dot-path prefixes to exclude (applies when include is empty).
|
|
310
|
+
* @param {boolean} [options.devTools=false] - Enable development tools integration.
|
|
311
|
+
* @param {(error: Error, context: string) => void} [options.onError=null] - Error handler function.
|
|
312
|
+
* @returns {void}
|
|
313
|
+
* @description
|
|
314
|
+
* Installs the store and injects `store` into component setup context by wrapping
|
|
315
|
+
* `eleva.mount` and `eleva._mountComponents`. Also exposes `eleva.store` and
|
|
316
|
+
* helper methods (`eleva.dispatch`, `eleva.getState`, `eleva.subscribe`, `eleva.createAction`).
|
|
317
|
+
* Uninstall restores the originals.
|
|
94
318
|
*
|
|
95
319
|
* @example
|
|
96
320
|
* // Basic installation
|
|
@@ -110,12 +334,12 @@ export const StorePlugin = {
|
|
|
110
334
|
* state: { user: null, token: null },
|
|
111
335
|
* actions: {
|
|
112
336
|
* login: (state, { user, token }) => {
|
|
113
|
-
* state.user.value = user;
|
|
114
|
-
* state.token.value = token;
|
|
337
|
+
* state.auth.user.value = user;
|
|
338
|
+
* state.auth.token.value = token;
|
|
115
339
|
* },
|
|
116
340
|
* logout: (state) => {
|
|
117
|
-
* state.user.value = null;
|
|
118
|
-
* state.token.value = null;
|
|
341
|
+
* state.auth.user.value = null;
|
|
342
|
+
* state.auth.token.value = null;
|
|
119
343
|
* }
|
|
120
344
|
* }
|
|
121
345
|
* }
|
|
@@ -137,15 +361,27 @@ export const StorePlugin = {
|
|
|
137
361
|
} = options;
|
|
138
362
|
|
|
139
363
|
/**
|
|
140
|
-
* Store
|
|
364
|
+
* @class Store
|
|
365
|
+
* @classdesc Store instance that manages all state and provides the API.
|
|
141
366
|
* @private
|
|
142
367
|
*/
|
|
143
368
|
class Store {
|
|
369
|
+
/**
|
|
370
|
+
* Creates a new Store instance.
|
|
371
|
+
* Initializes state signals, actions, persistence, and devtools integration.
|
|
372
|
+
*
|
|
373
|
+
* @constructor
|
|
374
|
+
*/
|
|
144
375
|
constructor() {
|
|
376
|
+
/** @type {Record<string, Signal | Record<string, unknown>>} */
|
|
145
377
|
this.state = {};
|
|
378
|
+
/** @type {Record<string, ActionFunction | Record<string, ActionFunction>>} */
|
|
146
379
|
this.actions = {};
|
|
380
|
+
/** @type {Set<SubscribeCallback>} */
|
|
147
381
|
this.subscribers = new Set();
|
|
382
|
+
/** @type {StoreMutation[]} */
|
|
148
383
|
this.mutations = [];
|
|
384
|
+
/** @type {{enabled: boolean, key: string, storage: string, include: string[]|null, exclude: string[]|null}} */
|
|
149
385
|
this.persistence = {
|
|
150
386
|
enabled: false,
|
|
151
387
|
key: "eleva-store",
|
|
@@ -154,7 +390,9 @@ export const StorePlugin = {
|
|
|
154
390
|
exclude: null,
|
|
155
391
|
...persistence,
|
|
156
392
|
};
|
|
393
|
+
/** @type {boolean} */
|
|
157
394
|
this.devTools = devTools;
|
|
395
|
+
/** @type {((error: Error, context: string) => void)|null} */
|
|
158
396
|
this.onError = onError;
|
|
159
397
|
|
|
160
398
|
this._initializeState(state, actions);
|
|
@@ -164,8 +402,13 @@ export const StorePlugin = {
|
|
|
164
402
|
}
|
|
165
403
|
|
|
166
404
|
/**
|
|
167
|
-
* Initializes the root state and actions
|
|
405
|
+
* Initializes the root state and actions.
|
|
406
|
+
* Creates reactive signals for each state property and copies actions.
|
|
407
|
+
*
|
|
168
408
|
* @private
|
|
409
|
+
* @param {Record<string, unknown>} initialState - The initial state key-value pairs.
|
|
410
|
+
* @param {Record<string, ActionFunction>} initialActions - The action functions to register.
|
|
411
|
+
* @returns {void}
|
|
169
412
|
*/
|
|
170
413
|
_initializeState(initialState, initialActions) {
|
|
171
414
|
// Create reactive signals for each state property
|
|
@@ -178,8 +421,12 @@ export const StorePlugin = {
|
|
|
178
421
|
}
|
|
179
422
|
|
|
180
423
|
/**
|
|
181
|
-
* Initializes namespaced modules
|
|
424
|
+
* Initializes namespaced modules.
|
|
425
|
+
* Creates namespace objects and populates them with state signals and actions.
|
|
426
|
+
*
|
|
182
427
|
* @private
|
|
428
|
+
* @param {Record<string, StoreModule>} namespaces - Map of namespace names to module definitions.
|
|
429
|
+
* @returns {void}
|
|
183
430
|
*/
|
|
184
431
|
_initializeNamespaces(namespaces) {
|
|
185
432
|
Object.entries(namespaces).forEach(([namespace, module]) => {
|
|
@@ -205,8 +452,12 @@ export const StorePlugin = {
|
|
|
205
452
|
}
|
|
206
453
|
|
|
207
454
|
/**
|
|
208
|
-
* Loads persisted state from storage
|
|
455
|
+
* Loads persisted state from storage.
|
|
456
|
+
* Reads from localStorage/sessionStorage and applies values to state signals.
|
|
457
|
+
* Does nothing if persistence is disabled or running in SSR environment.
|
|
458
|
+
*
|
|
209
459
|
* @private
|
|
460
|
+
* @returns {void}
|
|
210
461
|
*/
|
|
211
462
|
_loadPersistedState() {
|
|
212
463
|
if (!this.persistence.enabled || typeof window === "undefined") {
|
|
@@ -234,8 +485,14 @@ export const StorePlugin = {
|
|
|
234
485
|
}
|
|
235
486
|
|
|
236
487
|
/**
|
|
237
|
-
* Applies persisted data to the current state
|
|
488
|
+
* Applies persisted data to the current state.
|
|
489
|
+
* Recursively updates signal values for paths that should be persisted.
|
|
490
|
+
*
|
|
238
491
|
* @private
|
|
492
|
+
* @param {Record<string, unknown>} data - The persisted data object to apply.
|
|
493
|
+
* @param {Record<string, unknown>} [currentState=this.state] - The current state object to update.
|
|
494
|
+
* @param {string} [path=""] - The current dot-notation path (for include/exclude filtering).
|
|
495
|
+
* @returns {void}
|
|
239
496
|
*/
|
|
240
497
|
_applyPersistedData(data, currentState = this.state, path = "") {
|
|
241
498
|
Object.entries(data).forEach(([key, value]) => {
|
|
@@ -262,8 +519,12 @@ export const StorePlugin = {
|
|
|
262
519
|
}
|
|
263
520
|
|
|
264
521
|
/**
|
|
265
|
-
* Determines if a state path should be persisted
|
|
522
|
+
* Determines if a state path should be persisted.
|
|
523
|
+
* Checks against include/exclude filters configured in persistence options.
|
|
524
|
+
*
|
|
266
525
|
* @private
|
|
526
|
+
* @param {string} path - The dot-notation path to check (e.g., "auth.user").
|
|
527
|
+
* @returns {boolean} True if the path should be persisted, false otherwise.
|
|
267
528
|
*/
|
|
268
529
|
_shouldPersist(path) {
|
|
269
530
|
const { include, exclude } = this.persistence;
|
|
@@ -280,8 +541,12 @@ export const StorePlugin = {
|
|
|
280
541
|
}
|
|
281
542
|
|
|
282
543
|
/**
|
|
283
|
-
* Saves current state to storage
|
|
544
|
+
* Saves current state to storage.
|
|
545
|
+
* Extracts persistable data and writes to localStorage/sessionStorage.
|
|
546
|
+
* Does nothing if persistence is disabled or running in SSR environment.
|
|
547
|
+
*
|
|
284
548
|
* @private
|
|
549
|
+
* @returns {void}
|
|
285
550
|
*/
|
|
286
551
|
_saveState() {
|
|
287
552
|
if (!this.persistence.enabled || typeof window === "undefined") {
|
|
@@ -302,8 +567,13 @@ export const StorePlugin = {
|
|
|
302
567
|
}
|
|
303
568
|
|
|
304
569
|
/**
|
|
305
|
-
* Extracts data that should be persisted
|
|
570
|
+
* Extracts data that should be persisted.
|
|
571
|
+
* Recursively extracts signal values for paths that pass persistence filters.
|
|
572
|
+
*
|
|
306
573
|
* @private
|
|
574
|
+
* @param {Record<string, unknown>} [currentState=this.state] - The state object to extract from.
|
|
575
|
+
* @param {string} [path=""] - The current dot-notation path (for include/exclude filtering).
|
|
576
|
+
* @returns {Record<string, unknown>} The extracted data object with raw values (not signals).
|
|
307
577
|
*/
|
|
308
578
|
_extractPersistedData(currentState = this.state, path = "") {
|
|
309
579
|
const result = {};
|
|
@@ -329,8 +599,12 @@ export const StorePlugin = {
|
|
|
329
599
|
}
|
|
330
600
|
|
|
331
601
|
/**
|
|
332
|
-
* Sets up development tools integration
|
|
602
|
+
* Sets up development tools integration.
|
|
603
|
+
* Registers the store with Eleva DevTools if available and enabled.
|
|
604
|
+
* Does nothing if devTools is disabled, running in SSR, or DevTools not installed.
|
|
605
|
+
*
|
|
333
606
|
* @private
|
|
607
|
+
* @returns {void}
|
|
334
608
|
*/
|
|
335
609
|
_setupDevTools() {
|
|
336
610
|
if (
|
|
@@ -345,10 +619,26 @@ export const StorePlugin = {
|
|
|
345
619
|
}
|
|
346
620
|
|
|
347
621
|
/**
|
|
348
|
-
* Dispatches an action to mutate the state
|
|
349
|
-
*
|
|
350
|
-
*
|
|
351
|
-
*
|
|
622
|
+
* Dispatches an action to mutate the state.
|
|
623
|
+
*
|
|
624
|
+
* Execution flow:
|
|
625
|
+
* 1. Retrieves the action function (supports namespaced actions like "auth.login")
|
|
626
|
+
* 2. Records mutation for devtools/history (keeps last 100 mutations)
|
|
627
|
+
* 3. Executes action with await (actions can be sync or async)
|
|
628
|
+
* 4. Saves state if persistence is enabled
|
|
629
|
+
* 5. Notifies all subscribers with (mutation, state)
|
|
630
|
+
* 6. Notifies devtools if enabled
|
|
631
|
+
*
|
|
632
|
+
* @note Always returns a Promise regardless of whether the action is sync or async.
|
|
633
|
+
* Subscriber callbacks that throw are caught and passed to onError handler.
|
|
634
|
+
*
|
|
635
|
+
* @async
|
|
636
|
+
* @param {string} actionName - The name of the action to dispatch (supports dot notation for namespaces).
|
|
637
|
+
* @param {unknown} payload - The payload to pass to the action.
|
|
638
|
+
* @returns {Promise<unknown>} The result of the action (undefined if action returns nothing).
|
|
639
|
+
* @throws {Error} If action is not found or action function throws.
|
|
640
|
+
* @see subscribe - Listen for mutations triggered by dispatch.
|
|
641
|
+
* @see getState - Get current state values.
|
|
352
642
|
*/
|
|
353
643
|
async dispatch(actionName, payload) {
|
|
354
644
|
try {
|
|
@@ -410,8 +700,12 @@ export const StorePlugin = {
|
|
|
410
700
|
}
|
|
411
701
|
|
|
412
702
|
/**
|
|
413
|
-
* Gets an action by name (supports namespaced actions)
|
|
703
|
+
* Gets an action by name (supports namespaced actions).
|
|
704
|
+
* Traverses the actions object using dot-notation path segments.
|
|
705
|
+
*
|
|
414
706
|
* @private
|
|
707
|
+
* @param {string} actionName - The action name, supports dot notation for namespaces (e.g., "auth.login").
|
|
708
|
+
* @returns {ActionFunction | null} The action function if found and is a function, null otherwise.
|
|
415
709
|
*/
|
|
416
710
|
_getAction(actionName) {
|
|
417
711
|
const parts = actionName.split(".");
|
|
@@ -428,9 +722,18 @@ export const StorePlugin = {
|
|
|
428
722
|
}
|
|
429
723
|
|
|
430
724
|
/**
|
|
431
|
-
* Subscribes to store mutations
|
|
432
|
-
*
|
|
433
|
-
*
|
|
725
|
+
* Subscribes to store mutations.
|
|
726
|
+
* Callback is invoked after every successful action dispatch.
|
|
727
|
+
*
|
|
728
|
+
* @param {SubscribeCallback} callback
|
|
729
|
+
* Called after each mutation with:
|
|
730
|
+
* - mutation.type: The action name that was dispatched
|
|
731
|
+
* - mutation.payload: The payload passed to the action
|
|
732
|
+
* - mutation.timestamp: When the mutation occurred (Date.now())
|
|
733
|
+
* - state: The current state object (contains Signals)
|
|
734
|
+
* @returns {() => void} Unsubscribe function. Call to stop receiving notifications.
|
|
735
|
+
* @throws {Error} If callback is not a function.
|
|
736
|
+
* @see dispatch - Triggers mutations that notify subscribers.
|
|
434
737
|
*/
|
|
435
738
|
subscribe(callback) {
|
|
436
739
|
if (typeof callback !== "function") {
|
|
@@ -446,16 +749,26 @@ export const StorePlugin = {
|
|
|
446
749
|
}
|
|
447
750
|
|
|
448
751
|
/**
|
|
449
|
-
* Gets
|
|
450
|
-
*
|
|
752
|
+
* Gets current state values (not signals).
|
|
753
|
+
*
|
|
754
|
+
* @note When persistence include/exclude filters are configured,
|
|
755
|
+
* this returns only the filtered subset of state.
|
|
756
|
+
* @returns {Record<string, unknown>} The current state values (filtered by persistence config if set).
|
|
757
|
+
* @see replaceState - Set state values.
|
|
758
|
+
* @see subscribe - Listen for state changes.
|
|
451
759
|
*/
|
|
452
760
|
getState() {
|
|
453
761
|
return this._extractPersistedData();
|
|
454
762
|
}
|
|
455
763
|
|
|
456
764
|
/**
|
|
457
|
-
* Replaces
|
|
458
|
-
*
|
|
765
|
+
* Replaces state values (useful for testing or state hydration).
|
|
766
|
+
*
|
|
767
|
+
* @note When persistence include/exclude filters are configured,
|
|
768
|
+
* this only updates the filtered subset of state.
|
|
769
|
+
* @param {Record<string, unknown>} newState - The new state object.
|
|
770
|
+
* @returns {void}
|
|
771
|
+
* @see getState - Get current state values.
|
|
459
772
|
*/
|
|
460
773
|
replaceState(newState) {
|
|
461
774
|
this._applyPersistedData(newState);
|
|
@@ -463,7 +776,9 @@ export const StorePlugin = {
|
|
|
463
776
|
}
|
|
464
777
|
|
|
465
778
|
/**
|
|
466
|
-
* Clears persisted state from storage
|
|
779
|
+
* Clears persisted state from storage.
|
|
780
|
+
* Does nothing if persistence is disabled or running in SSR.
|
|
781
|
+
* @returns {void}
|
|
467
782
|
*/
|
|
468
783
|
clearPersistedState() {
|
|
469
784
|
if (!this.persistence.enabled || typeof window === "undefined") {
|
|
@@ -481,11 +796,14 @@ export const StorePlugin = {
|
|
|
481
796
|
}
|
|
482
797
|
|
|
483
798
|
/**
|
|
484
|
-
* Registers a new namespaced module at runtime
|
|
485
|
-
*
|
|
486
|
-
*
|
|
487
|
-
* @param {
|
|
488
|
-
* @param {
|
|
799
|
+
* Registers a new namespaced module at runtime.
|
|
800
|
+
* Logs a warning if the namespace already exists.
|
|
801
|
+
* Module state is nested under `state[namespace]` and actions under `actions[namespace]`.
|
|
802
|
+
* @param {string} namespace - The namespace for the module.
|
|
803
|
+
* @param {StoreModule} module - The module definition.
|
|
804
|
+
* @param {Record<string, unknown>} module.state - The module's initial state.
|
|
805
|
+
* @param {Record<string, ActionFunction>} module.actions - The module's actions.
|
|
806
|
+
* @returns {void}
|
|
489
807
|
*/
|
|
490
808
|
registerModule(namespace, module) {
|
|
491
809
|
if (this.state[namespace] || this.actions[namespace]) {
|
|
@@ -504,8 +822,11 @@ export const StorePlugin = {
|
|
|
504
822
|
}
|
|
505
823
|
|
|
506
824
|
/**
|
|
507
|
-
* Unregisters a namespaced module
|
|
508
|
-
*
|
|
825
|
+
* Unregisters a namespaced module.
|
|
826
|
+
* Logs a warning if the namespace doesn't exist.
|
|
827
|
+
* Removes both state and actions under the namespace.
|
|
828
|
+
* @param {string} namespace - The namespace to unregister.
|
|
829
|
+
* @returns {void}
|
|
509
830
|
*/
|
|
510
831
|
unregisterModule(namespace) {
|
|
511
832
|
if (!this.state[namespace] && !this.actions[namespace]) {
|
|
@@ -519,10 +840,11 @@ export const StorePlugin = {
|
|
|
519
840
|
}
|
|
520
841
|
|
|
521
842
|
/**
|
|
522
|
-
* Creates a new reactive state property at runtime
|
|
523
|
-
*
|
|
524
|
-
* @param {
|
|
525
|
-
* @
|
|
843
|
+
* Creates a new reactive state property at runtime.
|
|
844
|
+
*
|
|
845
|
+
* @param {string} key - The state key.
|
|
846
|
+
* @param {*} initialValue - The initial value.
|
|
847
|
+
* @returns {Signal} The created signal, or existing signal if key exists.
|
|
526
848
|
*/
|
|
527
849
|
createState(key, initialValue) {
|
|
528
850
|
if (this.state[key]) {
|
|
@@ -535,16 +857,42 @@ export const StorePlugin = {
|
|
|
535
857
|
}
|
|
536
858
|
|
|
537
859
|
/**
|
|
538
|
-
* Creates a new action at runtime
|
|
539
|
-
*
|
|
540
|
-
*
|
|
860
|
+
* Creates a new action at runtime.
|
|
861
|
+
* Overwrites existing action if name already exists.
|
|
862
|
+
* Supports dot-notation for namespaced actions (e.g., "auth.login").
|
|
863
|
+
* @param {string} name - The action name (supports dot notation for namespaces).
|
|
864
|
+
* @param {ActionFunction} actionFn - The action function (receives state and payload).
|
|
865
|
+
* @returns {void}
|
|
866
|
+
* @throws {Error} If actionFn is not a function.
|
|
867
|
+
* @example
|
|
868
|
+
* // Root-level action
|
|
869
|
+
* store.createAction("increment", (state) => state.count.value++);
|
|
870
|
+
*
|
|
871
|
+
* // Namespaced action
|
|
872
|
+
* store.createAction("auth.login", async (state, credentials) => {
|
|
873
|
+
* // ... login logic
|
|
874
|
+
* });
|
|
541
875
|
*/
|
|
542
876
|
createAction(name, actionFn) {
|
|
543
877
|
if (typeof actionFn !== "function") {
|
|
544
878
|
throw new Error("Action must be a function");
|
|
545
879
|
}
|
|
546
880
|
|
|
547
|
-
|
|
881
|
+
// Fast path: no dot means simple action (avoids array allocation)
|
|
882
|
+
if (name.indexOf(".") === -1) {
|
|
883
|
+
this.actions[name] = actionFn;
|
|
884
|
+
return;
|
|
885
|
+
}
|
|
886
|
+
|
|
887
|
+
// Namespaced action, traverse/create nested structure
|
|
888
|
+
const parts = name.split(".");
|
|
889
|
+
const lastIndex = parts.length - 1;
|
|
890
|
+
let current = this.actions;
|
|
891
|
+
|
|
892
|
+
for (let i = 0; i < lastIndex; i++) {
|
|
893
|
+
current = current[parts[i]] || (current[parts[i]] = {});
|
|
894
|
+
}
|
|
895
|
+
current[parts[lastIndex]] = actionFn;
|
|
548
896
|
}
|
|
549
897
|
}
|
|
550
898
|
|
|
@@ -555,7 +903,13 @@ export const StorePlugin = {
|
|
|
555
903
|
const originalMount = eleva.mount;
|
|
556
904
|
|
|
557
905
|
/**
|
|
558
|
-
*
|
|
906
|
+
* Overridden mount method that injects store context into components.
|
|
907
|
+
* Wraps the original mount to add `ctx.store` to the component's setup context.
|
|
908
|
+
*
|
|
909
|
+
* @param {HTMLElement} container - The DOM element where the component will be mounted.
|
|
910
|
+
* @param {string | ComponentDefinition} compName - Component name or definition.
|
|
911
|
+
* @param {ComponentProps} [props={}] - Optional properties to pass to the component.
|
|
912
|
+
* @returns {Promise<MountResult>} The mount result.
|
|
559
913
|
*/
|
|
560
914
|
eleva.mount = async (container, compName, props = {}) => {
|
|
561
915
|
// Get the component definition
|
|
@@ -572,7 +926,7 @@ export const StorePlugin = {
|
|
|
572
926
|
const wrappedComponent = {
|
|
573
927
|
...componentDef,
|
|
574
928
|
async setup(ctx) {
|
|
575
|
-
|
|
929
|
+
/** @type {StoreApi} */
|
|
576
930
|
ctx.store = {
|
|
577
931
|
// Core store functionality
|
|
578
932
|
state: store.state,
|
|
@@ -611,7 +965,23 @@ export const StorePlugin = {
|
|
|
611
965
|
|
|
612
966
|
// Override _mountComponents to ensure child components also get store context
|
|
613
967
|
const originalMountComponents = eleva._mountComponents;
|
|
614
|
-
|
|
968
|
+
|
|
969
|
+
/**
|
|
970
|
+
* Overridden _mountComponents method that injects store context into child components.
|
|
971
|
+
* Wraps each child component's setup function to add `ctx.store` before mounting.
|
|
972
|
+
*
|
|
973
|
+
* @param {HTMLElement} container - The parent container element.
|
|
974
|
+
* @param {ChildrenMap} children - Map of selectors to component definitions.
|
|
975
|
+
* @param {MountResult[]} childInstances - Array to store mounted instances.
|
|
976
|
+
* @param {ComponentContext & SetupResult} context - Parent component context.
|
|
977
|
+
* @returns {Promise<void>}
|
|
978
|
+
*/
|
|
979
|
+
eleva._mountComponents = async (
|
|
980
|
+
container,
|
|
981
|
+
children,
|
|
982
|
+
childInstances,
|
|
983
|
+
context
|
|
984
|
+
) => {
|
|
615
985
|
// Create wrapped children with store injection
|
|
616
986
|
const wrappedChildren = {};
|
|
617
987
|
|
|
@@ -625,7 +995,7 @@ export const StorePlugin = {
|
|
|
625
995
|
wrappedChildren[selector] = {
|
|
626
996
|
...componentDef,
|
|
627
997
|
async setup(ctx) {
|
|
628
|
-
|
|
998
|
+
/** @type {StoreApi} */
|
|
629
999
|
ctx.store = {
|
|
630
1000
|
// Core store functionality
|
|
631
1001
|
state: store.state,
|
|
@@ -662,29 +1032,35 @@ export const StorePlugin = {
|
|
|
662
1032
|
eleva,
|
|
663
1033
|
container,
|
|
664
1034
|
wrappedChildren,
|
|
665
|
-
childInstances
|
|
1035
|
+
childInstances,
|
|
1036
|
+
context
|
|
666
1037
|
);
|
|
667
1038
|
};
|
|
668
1039
|
|
|
669
1040
|
// Expose store instance and utilities on the Eleva instance
|
|
1041
|
+
/** @type {StoreApi} */
|
|
670
1042
|
eleva.store = store;
|
|
671
1043
|
|
|
672
1044
|
/**
|
|
673
|
-
* Expose utility methods on the Eleva instance
|
|
674
|
-
*
|
|
1045
|
+
* Expose utility methods on the Eleva instance.
|
|
1046
|
+
* These are top-level helpers (e.g., `eleva.dispatch`) in addition to `eleva.store`.
|
|
675
1047
|
*/
|
|
1048
|
+
/** @type {(name: string, actionFn: ActionFunction) => void} */
|
|
676
1049
|
eleva.createAction = (name, actionFn) => {
|
|
677
|
-
store.
|
|
1050
|
+
store.createAction(name, actionFn);
|
|
678
1051
|
};
|
|
679
1052
|
|
|
1053
|
+
/** @type {DispatchFunction} */
|
|
680
1054
|
eleva.dispatch = (actionName, payload) => {
|
|
681
1055
|
return store.dispatch(actionName, payload);
|
|
682
1056
|
};
|
|
683
1057
|
|
|
1058
|
+
/** @type {() => Record<string, unknown>} */
|
|
684
1059
|
eleva.getState = () => {
|
|
685
1060
|
return store.getState();
|
|
686
1061
|
};
|
|
687
1062
|
|
|
1063
|
+
/** @type {(callback: SubscribeCallback) => () => void} */
|
|
688
1064
|
eleva.subscribe = (callback) => {
|
|
689
1065
|
return store.subscribe(callback);
|
|
690
1066
|
};
|
|
@@ -695,14 +1071,17 @@ export const StorePlugin = {
|
|
|
695
1071
|
},
|
|
696
1072
|
|
|
697
1073
|
/**
|
|
698
|
-
* Uninstalls the plugin from the Eleva instance
|
|
699
|
-
*
|
|
700
|
-
* @param {Object} eleva - The Eleva instance
|
|
1074
|
+
* Uninstalls the plugin from the Eleva instance.
|
|
701
1075
|
*
|
|
1076
|
+
* @public
|
|
1077
|
+
* @param {Eleva} eleva - The Eleva instance.
|
|
1078
|
+
* @returns {void}
|
|
702
1079
|
* @description
|
|
703
1080
|
* Restores the original Eleva methods and removes all plugin-specific
|
|
704
1081
|
* functionality. This method should be called when the plugin is no
|
|
705
1082
|
* longer needed.
|
|
1083
|
+
* Also removes `eleva.store` and top-level helpers (`eleva.dispatch`,
|
|
1084
|
+
* `eleva.getState`, `eleva.subscribe`, `eleva.createAction`).
|
|
706
1085
|
*
|
|
707
1086
|
* @example
|
|
708
1087
|
* // Uninstall the plugin
|