pulse-js-framework 1.4.4 → 1.4.6

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/runtime/store.js CHANGED
@@ -1,13 +1,71 @@
1
1
  /**
2
2
  * Pulse Store - Global state management
3
+ * @module pulse-js-framework/runtime/store
3
4
  *
4
- * A simple but powerful store that integrates with Pulse reactivity
5
+ * A simple but powerful store that integrates with Pulse reactivity.
6
+ *
7
+ * @example
8
+ * import { createStore, createActions, createGetters } from './store.js';
9
+ *
10
+ * const store = createStore({ count: 0, name: 'App' });
11
+ * const actions = createActions(store, {
12
+ * increment: (store) => store.count.update(n => n + 1)
13
+ * });
14
+ *
15
+ * actions.increment();
5
16
  */
6
17
 
7
18
  import { pulse, computed, effect, batch } from './pulse.js';
19
+ import { loggers, createLogger } from './logger.js';
20
+
21
+ const log = loggers.store;
22
+
23
+ /**
24
+ * @typedef {Object} StoreOptions
25
+ * @property {boolean} [persist=false] - Persist state to localStorage
26
+ * @property {string} [storageKey='pulse-store'] - Key for localStorage persistence
27
+ */
28
+
29
+ /**
30
+ * @typedef {Object} Store
31
+ * @property {function(): Object} $getState - Get current state snapshot
32
+ * @property {function(Object): void} $setState - Update multiple values at once
33
+ * @property {function(): void} $reset - Reset to initial state
34
+ * @property {function(function): function} $subscribe - Subscribe to state changes
35
+ * @property {Object.<string, Pulse>} $pulses - Access underlying pulse objects
36
+ */
37
+
38
+ /**
39
+ * @typedef {Object} ModuleDef
40
+ * @property {Object} [state] - Initial module state
41
+ * @property {Object.<string, function>} [actions] - Module actions
42
+ * @property {Object.<string, function>} [getters] - Module getters
43
+ */
8
44
 
9
45
  /**
10
- * Create a global store
46
+ * @typedef {function(Store): Store} StorePlugin
47
+ */
48
+
49
+ /**
50
+ * Create a global store with reactive state properties.
51
+ * @template T
52
+ * @param {T} [initialState={}] - Initial state object
53
+ * @param {StoreOptions} [options={}] - Store configuration
54
+ * @returns {T & Store} Store with reactive properties and helper methods
55
+ * @example
56
+ * // Basic store
57
+ * const store = createStore({ count: 0, user: null });
58
+ * store.count.get(); // 0
59
+ * store.count.set(5);
60
+ *
61
+ * // With persistence
62
+ * const store = createStore(
63
+ * { theme: 'dark', lang: 'en' },
64
+ * { persist: true, storageKey: 'app-settings' }
65
+ * );
66
+ *
67
+ * // Batch updates
68
+ * store.$setState({ theme: 'light', lang: 'fr' });
11
69
  */
12
70
  export function createStore(initialState = {}, options = {}) {
13
71
  const { persist = false, storageKey = 'pulse-store' } = options;
@@ -21,14 +79,22 @@ export function createStore(initialState = {}, options = {}) {
21
79
  state = { ...initialState, ...JSON.parse(saved) };
22
80
  }
23
81
  } catch (e) {
24
- console.warn('Failed to load persisted state:', e);
82
+ log.warn('Failed to load persisted state:', e);
25
83
  }
26
84
  }
27
85
 
28
86
  // Create pulses for each state property
87
+ /** @type {Object.<string, Pulse>} */
29
88
  const pulses = {};
30
89
  const store = {};
31
90
 
91
+ /**
92
+ * Create a pulse for a state value, handling nested objects
93
+ * @private
94
+ * @param {string} key - State key
95
+ * @param {*} value - Initial value
96
+ * @returns {Pulse|Object} Pulse or nested object of pulses
97
+ */
32
98
  function createPulse(key, value) {
33
99
  if (typeof value === 'object' && value !== null && !Array.isArray(value)) {
34
100
  // Nested object - create nested store
@@ -59,13 +125,14 @@ export function createStore(initialState = {}, options = {}) {
59
125
  try {
60
126
  localStorage.setItem(storageKey, JSON.stringify(snapshot));
61
127
  } catch (e) {
62
- console.warn('Failed to persist state:', e);
128
+ log.warn('Failed to persist state:', e);
63
129
  }
64
130
  });
65
131
  }
66
132
 
67
133
  /**
68
- * Get a snapshot of the current state
134
+ * Get a snapshot of the current state (without tracking)
135
+ * @returns {Object} Plain object with current values
69
136
  */
70
137
  function getState() {
71
138
  const snapshot = {};
@@ -76,7 +143,9 @@ export function createStore(initialState = {}, options = {}) {
76
143
  }
77
144
 
78
145
  /**
79
- * Set multiple values at once
146
+ * Set multiple values at once (batched)
147
+ * @param {Object} updates - Key-value pairs to update
148
+ * @returns {void}
80
149
  */
81
150
  function setState(updates) {
82
151
  batch(() => {
@@ -90,6 +159,7 @@ export function createStore(initialState = {}, options = {}) {
90
159
 
91
160
  /**
92
161
  * Reset state to initial values
162
+ * @returns {void}
93
163
  */
94
164
  function reset() {
95
165
  batch(() => {
@@ -103,6 +173,8 @@ export function createStore(initialState = {}, options = {}) {
103
173
 
104
174
  /**
105
175
  * Subscribe to all state changes
176
+ * @param {function(Object): void} callback - Called with current state on each change
177
+ * @returns {function(): void} Unsubscribe function
106
178
  */
107
179
  function subscribe(callback) {
108
180
  return effect(() => {
@@ -122,7 +194,23 @@ export function createStore(initialState = {}, options = {}) {
122
194
  }
123
195
 
124
196
  /**
125
- * Create actions that can modify the store
197
+ * Create bound actions that can modify the store.
198
+ * Actions receive the store as their first argument.
199
+ * @template T
200
+ * @param {Store} store - The store to bind actions to
201
+ * @param {Object.<string, function(Store, ...any): any>} actions - Action definitions
202
+ * @returns {Object.<string, function(...any): any>} Bound action functions
203
+ * @example
204
+ * const store = createStore({ count: 0 });
205
+ *
206
+ * const actions = createActions(store, {
207
+ * increment: (store) => store.count.update(n => n + 1),
208
+ * decrement: (store) => store.count.update(n => n - 1),
209
+ * add: (store, amount) => store.count.update(n => n + amount)
210
+ * });
211
+ *
212
+ * actions.increment();
213
+ * actions.add(10);
126
214
  */
127
215
  export function createActions(store, actions) {
128
216
  const boundActions = {};
@@ -137,7 +225,24 @@ export function createActions(store, actions) {
137
225
  }
138
226
 
139
227
  /**
140
- * Create getters (computed values) for the store
228
+ * Create computed getters for the store.
229
+ * Getters are memoized and update automatically when dependencies change.
230
+ * @param {Store} store - The store to create getters for
231
+ * @param {Object.<string, function(Store): any>} getters - Getter definitions
232
+ * @returns {Object.<string, Pulse>} Object of computed pulses
233
+ * @example
234
+ * const store = createStore({ items: [], filter: '' });
235
+ *
236
+ * const getters = createGetters(store, {
237
+ * filteredItems: (store) => {
238
+ * const items = store.items.get();
239
+ * const filter = store.filter.get();
240
+ * return items.filter(i => i.includes(filter));
241
+ * },
242
+ * itemCount: (store) => store.items.get().length
243
+ * });
244
+ *
245
+ * getters.filteredItems.get(); // Computed value
141
246
  */
142
247
  export function createGetters(store, getters) {
143
248
  const boundGetters = {};
@@ -150,7 +255,21 @@ export function createGetters(store, getters) {
150
255
  }
151
256
 
152
257
  /**
153
- * Combine multiple stores
258
+ * Combine multiple stores into a single object.
259
+ * Each store is namespaced under its key.
260
+ * @param {Object.<string, Store>} stores - Stores to combine
261
+ * @returns {Object.<string, Store>} Combined stores object
262
+ * @example
263
+ * const userStore = createStore({ name: '', email: '' });
264
+ * const settingsStore = createStore({ theme: 'dark' });
265
+ *
266
+ * const rootStore = combineStores({
267
+ * user: userStore,
268
+ * settings: settingsStore
269
+ * });
270
+ *
271
+ * rootStore.user.name.get();
272
+ * rootStore.settings.theme.set('light');
154
273
  */
155
274
  export function combineStores(stores) {
156
275
  const combined = {};
@@ -163,7 +282,35 @@ export function combineStores(stores) {
163
282
  }
164
283
 
165
284
  /**
166
- * Create a module-based store (like Vuex modules)
285
+ * Create a module-based store similar to Vuex modules.
286
+ * Each module has its own state, actions, and getters.
287
+ * @param {Object.<string, ModuleDef>} modules - Module definitions
288
+ * @returns {Object} Root store with namespaced modules
289
+ * @example
290
+ * const store = createModuleStore({
291
+ * user: {
292
+ * state: { name: '', loggedIn: false },
293
+ * actions: {
294
+ * login: (store, name) => {
295
+ * store.name.set(name);
296
+ * store.loggedIn.set(true);
297
+ * }
298
+ * },
299
+ * getters: {
300
+ * displayName: (store) => store.loggedIn.get() ? store.name.get() : 'Guest'
301
+ * }
302
+ * },
303
+ * cart: {
304
+ * state: { items: [] },
305
+ * actions: {
306
+ * addItem: (store, item) => store.items.update(arr => [...arr, item])
307
+ * }
308
+ * }
309
+ * });
310
+ *
311
+ * store.user.login('John');
312
+ * store.cart.addItem({ id: 1, name: 'Product' });
313
+ * store.$getState(); // { user: {...}, cart: {...} }
167
314
  */
168
315
  export function createModuleStore(modules) {
169
316
  const stores = {};
@@ -186,7 +333,10 @@ export function createModuleStore(modules) {
186
333
  rootStore[name] = stores[name];
187
334
  }
188
335
 
189
- // Root methods
336
+ /**
337
+ * Get combined state from all modules
338
+ * @returns {Object} State from all modules
339
+ */
190
340
  rootStore.$getState = () => {
191
341
  const state = {};
192
342
  for (const [name, store] of Object.entries(stores)) {
@@ -195,6 +345,10 @@ export function createModuleStore(modules) {
195
345
  return state;
196
346
  };
197
347
 
348
+ /**
349
+ * Reset all modules to initial state
350
+ * @returns {void}
351
+ */
198
352
  rootStore.$reset = () => {
199
353
  for (const store of Object.values(stores)) {
200
354
  store.$reset();
@@ -205,35 +359,72 @@ export function createModuleStore(modules) {
205
359
  }
206
360
 
207
361
  /**
208
- * Plugin system for store
362
+ * Apply a plugin to a store.
363
+ * Plugins can extend store functionality.
364
+ * @param {Store} store - The store to enhance
365
+ * @param {StorePlugin} plugin - Plugin function
366
+ * @returns {Store} Enhanced store
367
+ * @example
368
+ * const store = createStore({ count: 0 });
369
+ * usePlugin(store, loggerPlugin);
370
+ * usePlugin(store, historyPlugin);
209
371
  */
210
372
  export function usePlugin(store, plugin) {
211
373
  return plugin(store);
212
374
  }
213
375
 
214
376
  /**
215
- * Logger plugin - logs all state changes
377
+ * Logger plugin - logs all state changes to console.
378
+ * Useful for debugging store updates.
379
+ * @param {Store} store - The store to add logging to
380
+ * @returns {Store} Store with logging enabled
381
+ * @example
382
+ * const store = createStore({ count: 0 });
383
+ * usePlugin(store, loggerPlugin);
384
+ *
385
+ * store.$setState({ count: 5 });
386
+ * // Console: [Store:Update] State Change
387
+ * // Previous: { count: 0 }
388
+ * // Updates: { count: 5 }
389
+ * // Next: { count: 5 }
216
390
  */
217
391
  export function loggerPlugin(store) {
218
392
  const originalSetState = store.$setState;
393
+ const pluginLog = createLogger('Store:Update');
219
394
 
220
395
  store.$setState = (updates) => {
221
- console.group('Store Update');
222
- console.log('Previous:', store.$getState());
223
- console.log('Updates:', updates);
396
+ pluginLog.group('State Change');
397
+ pluginLog.debug('Previous:', store.$getState());
398
+ pluginLog.debug('Updates:', updates);
224
399
  originalSetState(updates);
225
- console.log('Next:', store.$getState());
226
- console.groupEnd();
400
+ pluginLog.debug('Next:', store.$getState());
401
+ pluginLog.groupEnd();
227
402
  };
228
403
 
229
404
  return store;
230
405
  }
231
406
 
232
407
  /**
233
- * History plugin - enables undo/redo
408
+ * History plugin - enables undo/redo functionality.
409
+ * Tracks state changes and allows reverting to previous states.
410
+ * @param {Store} store - The store to add history to
411
+ * @param {number} [maxHistory=50] - Maximum number of history states to keep
412
+ * @returns {Store & {$undo: function, $redo: function, $canUndo: function, $canRedo: function}} Store with history methods
413
+ * @example
414
+ * const store = createStore({ count: 0 });
415
+ * usePlugin(store, (s) => historyPlugin(s, 100));
416
+ *
417
+ * store.$setState({ count: 1 });
418
+ * store.$setState({ count: 2 });
419
+ *
420
+ * store.$canUndo(); // true
421
+ * store.$undo(); // count = 1
422
+ * store.$redo(); // count = 2
234
423
  */
235
424
  export function historyPlugin(store, maxHistory = 50) {
425
+ /** @type {Array<Object>} */
236
426
  const history = [store.$getState()];
427
+ /** @type {number} */
237
428
  let currentIndex = 0;
238
429
 
239
430
  const originalSetState = store.$setState;
@@ -255,6 +446,10 @@ export function historyPlugin(store, maxHistory = 50) {
255
446
  }
256
447
  };
257
448
 
449
+ /**
450
+ * Undo the last state change
451
+ * @returns {void}
452
+ */
258
453
  store.$undo = () => {
259
454
  if (currentIndex > 0) {
260
455
  currentIndex--;
@@ -269,6 +464,10 @@ export function historyPlugin(store, maxHistory = 50) {
269
464
  }
270
465
  };
271
466
 
467
+ /**
468
+ * Redo a previously undone state change
469
+ * @returns {void}
470
+ */
272
471
  store.$redo = () => {
273
472
  if (currentIndex < history.length - 1) {
274
473
  currentIndex++;
@@ -283,7 +482,16 @@ export function historyPlugin(store, maxHistory = 50) {
283
482
  }
284
483
  };
285
484
 
485
+ /**
486
+ * Check if undo is available
487
+ * @returns {boolean} True if there are states to undo
488
+ */
286
489
  store.$canUndo = () => currentIndex > 0;
490
+
491
+ /**
492
+ * Check if redo is available
493
+ * @returns {boolean} True if there are states to redo
494
+ */
287
495
  store.$canRedo = () => currentIndex < history.length - 1;
288
496
 
289
497
  return store;
package/types/index.d.ts CHANGED
@@ -13,6 +13,8 @@ export {
13
13
  EqualsFn,
14
14
  ReactiveState,
15
15
  PromiseState,
16
+ EffectFn,
17
+ ReactiveContext,
16
18
  pulse,
17
19
  computed,
18
20
  effect,
@@ -23,7 +25,9 @@ export {
23
25
  memoComputed,
24
26
  fromPromise,
25
27
  untrack,
26
- onCleanup
28
+ onCleanup,
29
+ context,
30
+ resetContext
27
31
  } from './pulse';
28
32
 
29
33
  // DOM Helpers
@@ -118,3 +122,18 @@ export {
118
122
  loggerPlugin,
119
123
  historyPlugin
120
124
  } from './store';
125
+
126
+ // Logger
127
+ export {
128
+ LogLevel,
129
+ LogLevelValue,
130
+ LogFormatter,
131
+ LoggerOptions,
132
+ Logger,
133
+ setLogLevel,
134
+ getLogLevel,
135
+ setFormatter,
136
+ createLogger,
137
+ logger,
138
+ loggers
139
+ } from './logger';
@@ -0,0 +1,122 @@
1
+ /**
2
+ * Pulse Logger - Type Definitions
3
+ * @module pulse-js-framework/runtime/logger
4
+ */
5
+
6
+ /**
7
+ * Log level constants
8
+ */
9
+ export declare const LogLevel: {
10
+ readonly SILENT: 0;
11
+ readonly ERROR: 1;
12
+ readonly WARN: 2;
13
+ readonly INFO: 3;
14
+ readonly DEBUG: 4;
15
+ };
16
+
17
+ export type LogLevelValue = 0 | 1 | 2 | 3 | 4;
18
+
19
+ /**
20
+ * Custom formatter function type
21
+ */
22
+ export type LogFormatter = (
23
+ level: 'error' | 'warn' | 'info' | 'debug',
24
+ namespace: string | null,
25
+ args: unknown[]
26
+ ) => string;
27
+
28
+ /**
29
+ * Logger options
30
+ */
31
+ export interface LoggerOptions {
32
+ /** Override global level for this logger */
33
+ level?: LogLevelValue;
34
+ }
35
+
36
+ /**
37
+ * Logger instance interface
38
+ */
39
+ export interface Logger {
40
+ /**
41
+ * Log error message (always shown unless SILENT)
42
+ */
43
+ error(...args: unknown[]): void;
44
+
45
+ /**
46
+ * Log warning message
47
+ */
48
+ warn(...args: unknown[]): void;
49
+
50
+ /**
51
+ * Log info message (general output)
52
+ */
53
+ info(...args: unknown[]): void;
54
+
55
+ /**
56
+ * Log debug message (verbose output)
57
+ */
58
+ debug(...args: unknown[]): void;
59
+
60
+ /**
61
+ * Create a log group (for debug mode)
62
+ */
63
+ group(label: string): void;
64
+
65
+ /**
66
+ * End a log group
67
+ */
68
+ groupEnd(): void;
69
+
70
+ /**
71
+ * Log with custom level
72
+ */
73
+ log(level: LogLevelValue, ...args: unknown[]): void;
74
+
75
+ /**
76
+ * Create a child logger with additional namespace
77
+ */
78
+ child(childNamespace: string): Logger;
79
+ }
80
+
81
+ /**
82
+ * Set global log level
83
+ */
84
+ export declare function setLogLevel(level: LogLevelValue): void;
85
+
86
+ /**
87
+ * Get current global log level
88
+ */
89
+ export declare function getLogLevel(): LogLevelValue;
90
+
91
+ /**
92
+ * Set custom formatter for all loggers
93
+ */
94
+ export declare function setFormatter(formatter: LogFormatter | null): void;
95
+
96
+ /**
97
+ * Create a logger instance with optional namespace
98
+ */
99
+ export declare function createLogger(
100
+ namespace?: string | null,
101
+ options?: LoggerOptions
102
+ ): Logger;
103
+
104
+ /**
105
+ * Default logger instance (no namespace)
106
+ */
107
+ export declare const logger: Logger;
108
+
109
+ /**
110
+ * Pre-configured loggers for common subsystems
111
+ */
112
+ export declare const loggers: {
113
+ readonly pulse: Logger;
114
+ readonly dom: Logger;
115
+ readonly router: Logger;
116
+ readonly store: Logger;
117
+ readonly native: Logger;
118
+ readonly hmr: Logger;
119
+ readonly cli: Logger;
120
+ };
121
+
122
+ export default logger;
package/types/pulse.d.ts CHANGED
@@ -147,3 +147,36 @@ export declare function untrack<T>(fn: () => T): T;
147
147
  * Register cleanup function for current effect
148
148
  */
149
149
  export declare function onCleanup(fn: () => void): void;
150
+
151
+ /** Effect function with dependency tracking */
152
+ export interface EffectFn {
153
+ run: () => void;
154
+ dependencies: Set<Pulse>;
155
+ cleanups: (() => void)[];
156
+ }
157
+
158
+ /**
159
+ * Reactive context - holds global tracking state.
160
+ * Exposed for testing and advanced use cases.
161
+ */
162
+ export interface ReactiveContext {
163
+ /** Currently executing effect for dependency tracking */
164
+ currentEffect: EffectFn | null;
165
+ /** Nesting depth of batch() calls */
166
+ batchDepth: number;
167
+ /** Effects queued during batch */
168
+ pendingEffects: Set<EffectFn>;
169
+ /** Flag to prevent recursive effect flushing */
170
+ isRunningEffects: boolean;
171
+ }
172
+
173
+ /**
174
+ * Global reactive context
175
+ */
176
+ export declare const context: ReactiveContext;
177
+
178
+ /**
179
+ * Reset the reactive context to initial state.
180
+ * Use this in tests to ensure isolation between test cases.
181
+ */
182
+ export declare function resetContext(): void;