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.
Files changed (97) hide show
  1. package/README.md +21 -10
  2. package/dist/{eleva-plugins.cjs.js → eleva-plugins.cjs} +1002 -292
  3. package/dist/eleva-plugins.cjs.map +1 -0
  4. package/dist/eleva-plugins.d.cts +1352 -0
  5. package/dist/eleva-plugins.d.cts.map +1 -0
  6. package/dist/eleva-plugins.d.ts +1352 -0
  7. package/dist/eleva-plugins.d.ts.map +1 -0
  8. package/dist/{eleva-plugins.esm.js → eleva-plugins.js} +1002 -292
  9. package/dist/eleva-plugins.js.map +1 -0
  10. package/dist/eleva-plugins.umd.js +1001 -291
  11. package/dist/eleva-plugins.umd.js.map +1 -1
  12. package/dist/eleva-plugins.umd.min.js +1 -1
  13. package/dist/eleva-plugins.umd.min.js.map +1 -1
  14. package/dist/{eleva.cjs.js → eleva.cjs} +421 -191
  15. package/dist/eleva.cjs.map +1 -0
  16. package/dist/eleva.d.cts +1329 -0
  17. package/dist/eleva.d.cts.map +1 -0
  18. package/dist/eleva.d.ts +473 -226
  19. package/dist/eleva.d.ts.map +1 -0
  20. package/dist/{eleva.esm.js → eleva.js} +422 -192
  21. package/dist/eleva.js.map +1 -0
  22. package/dist/eleva.umd.js +420 -190
  23. package/dist/eleva.umd.js.map +1 -1
  24. package/dist/eleva.umd.min.js +1 -1
  25. package/dist/eleva.umd.min.js.map +1 -1
  26. package/dist/plugins/attr.cjs +279 -0
  27. package/dist/plugins/attr.cjs.map +1 -0
  28. package/dist/plugins/attr.d.cts +101 -0
  29. package/dist/plugins/attr.d.cts.map +1 -0
  30. package/dist/plugins/attr.d.ts +101 -0
  31. package/dist/plugins/attr.d.ts.map +1 -0
  32. package/dist/plugins/attr.js +276 -0
  33. package/dist/plugins/attr.js.map +1 -0
  34. package/dist/plugins/attr.umd.js +111 -22
  35. package/dist/plugins/attr.umd.js.map +1 -1
  36. package/dist/plugins/attr.umd.min.js +1 -1
  37. package/dist/plugins/attr.umd.min.js.map +1 -1
  38. package/dist/plugins/router.cjs +1873 -0
  39. package/dist/plugins/router.cjs.map +1 -0
  40. package/dist/plugins/router.d.cts +1296 -0
  41. package/dist/plugins/router.d.cts.map +1 -0
  42. package/dist/plugins/router.d.ts +1296 -0
  43. package/dist/plugins/router.d.ts.map +1 -0
  44. package/dist/plugins/router.js +1870 -0
  45. package/dist/plugins/router.js.map +1 -0
  46. package/dist/plugins/router.umd.js +482 -186
  47. package/dist/plugins/router.umd.js.map +1 -1
  48. package/dist/plugins/router.umd.min.js +1 -1
  49. package/dist/plugins/router.umd.min.js.map +1 -1
  50. package/dist/plugins/store.cjs +920 -0
  51. package/dist/plugins/store.cjs.map +1 -0
  52. package/dist/plugins/store.d.cts +266 -0
  53. package/dist/plugins/store.d.cts.map +1 -0
  54. package/dist/plugins/store.d.ts +266 -0
  55. package/dist/plugins/store.d.ts.map +1 -0
  56. package/dist/plugins/store.js +917 -0
  57. package/dist/plugins/store.js.map +1 -0
  58. package/dist/plugins/store.umd.js +410 -85
  59. package/dist/plugins/store.umd.js.map +1 -1
  60. package/dist/plugins/store.umd.min.js +1 -1
  61. package/dist/plugins/store.umd.min.js.map +1 -1
  62. package/package.json +112 -68
  63. package/src/core/Eleva.js +195 -115
  64. package/src/index.cjs +10 -0
  65. package/src/index.js +11 -0
  66. package/src/modules/Emitter.js +68 -20
  67. package/src/modules/Renderer.js +82 -20
  68. package/src/modules/Signal.js +43 -15
  69. package/src/modules/TemplateEngine.js +50 -9
  70. package/src/plugins/Attr.js +121 -19
  71. package/src/plugins/Router.js +526 -181
  72. package/src/plugins/Store.js +448 -69
  73. package/src/plugins/index.js +1 -0
  74. package/types/core/Eleva.d.ts +263 -169
  75. package/types/core/Eleva.d.ts.map +1 -1
  76. package/types/index.d.cts +3 -0
  77. package/types/index.d.cts.map +1 -0
  78. package/types/index.d.ts +5 -0
  79. package/types/index.d.ts.map +1 -1
  80. package/types/modules/Emitter.d.ts +73 -30
  81. package/types/modules/Emitter.d.ts.map +1 -1
  82. package/types/modules/Renderer.d.ts +48 -18
  83. package/types/modules/Renderer.d.ts.map +1 -1
  84. package/types/modules/Signal.d.ts +44 -16
  85. package/types/modules/Signal.d.ts.map +1 -1
  86. package/types/modules/TemplateEngine.d.ts +46 -11
  87. package/types/modules/TemplateEngine.d.ts.map +1 -1
  88. package/types/plugins/Attr.d.ts +83 -16
  89. package/types/plugins/Attr.d.ts.map +1 -1
  90. package/types/plugins/Router.d.ts +498 -207
  91. package/types/plugins/Router.d.ts.map +1 -1
  92. package/types/plugins/Store.d.ts +211 -37
  93. package/types/plugins/Store.d.ts.map +1 -1
  94. package/dist/eleva-plugins.cjs.js.map +0 -1
  95. package/dist/eleva-plugins.esm.js.map +0 -1
  96. package/dist/eleva.cjs.js.map +0 -1
  97. package/dist/eleva.esm.js.map +0 -1
@@ -1,4 +1,4 @@
1
- /*! Eleva Store Plugin v1.0.0 | MIT License | https://elevajs.com */
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.push(todo),
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 onclick="ctx.increment()">+</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.0.0",
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
- * @param {Object} eleva - The Eleva instance
79
- * @param {Object} options - Plugin configuration options
80
- * @param {Object} [options.state={}] - Initial state object
81
- * @param {Object} [options.actions={}] - Action functions for state mutations
82
- * @param {Object} [options.namespaces={}] - Namespaced modules for organizing store
83
- * @param {Object} [options.persistence] - Persistence configuration
84
- * @param {boolean} [options.persistence.enabled=false] - Enable state persistence
85
- * @param {string} [options.persistence.key="eleva-store"] - Storage key
86
- * @param {"localStorage" | "sessionStorage"} [options.persistence.storage="localStorage"] - Storage type
87
- * @param {Array<string>} [options.persistence.include] - State keys to persist (if not provided, all state is persisted)
88
- * @param {Array<string>} [options.persistence.exclude] - State keys to exclude from persistence
89
- * @param {boolean} [options.devTools=false] - Enable development tools integration
90
- * @param {Function} [options.onError=null] - Error handler function
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 instance that manages all state and provides the API
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
- * @param {string} actionName - The name of the action to dispatch (supports namespaced actions like "auth.login")
272
- * @param {any} payload - The payload to pass to the action
273
- * @returns {Promise<any>} The result of the action
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
- * @param {Function} callback - Callback function to call on mutations
337
- * @returns {Function} Unsubscribe function
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 a deep copy of the current state values (not signals)
350
- * @returns {Object} The current state values
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 the entire state (useful for testing or state hydration)
356
- * @param {Object} newState - The new state object
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
- * @param {string} namespace - The namespace for the module
379
- * @param {Object} module - The module definition
380
- * @param {Object} module.state - The module's initial state
381
- * @param {Object} module.actions - The module's actions
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
- * @param {string} namespace - The namespace to unregister
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
- * @param {string} key - The state key
411
- * @param {*} initialValue - The initial value
412
- * @returns {Object} The created signal
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
- * @param {string} name - The action name
424
- * @param {Function} actionFn - The action function
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
- this.actions[name] = actionFn;
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
- constructor(){
432
- this.state = {};
433
- this.actions = {};
434
- this.subscribers = new Set();
435
- this.mutations = [];
436
- this.persistence = {
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
- * Override the mount method to inject store context into components
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
- // Inject store into the context with enhanced API
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
- eleva._mountComponents = async (container, children, childInstances)=>{
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
- // Inject store into the context with enhanced API
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
- * @namespace eleva.store
538
- */ eleva.createAction = (name, actionFn)=>{
539
- store.actions[name] = actionFn;
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