@asaidimu/utils-store 2.3.2 → 4.0.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 CHANGED
@@ -1,8 +1,8 @@
1
1
  # `@asaidimu/utils-store`
2
2
 
3
- **A Reactive Data Store for TypeScript Applications**
3
+ **A Type-Safe Reactive Data Store for TypeScript Applications**
4
4
 
5
- A comprehensive, type-safe, and reactive state management library for TypeScript applications, featuring robust middleware, transactional updates, deep observability, and an optional persistence layer. It simplifies complex state interactions by promoting immutability, explicit updates, and a modular design.
5
+ A comprehensive, type-safe, and reactive state management library for TypeScript applications, featuring robust middleware, atomic transactions, dependency injection for services (artifacts), deep observability, and an optional persistence layer. It simplifies complex state interactions by promoting immutability, explicit updates, and a modular design.
6
6
 
7
7
  [![npm version](https://img.shields.io/npm/v/@asaidimu/utils-store?style=flat-square)](https://www.npmjs.com/package/@asaidimu/utils-store)
8
8
  [![License](https://img.shields.io/npm/l/@asaidimu/utils-store?style=flat-square)](https://github.com/asaidimu/erp-utils/blob/main/LICENSE)
@@ -16,9 +16,13 @@ A comprehensive, type-safe, and reactive state management library for TypeScript
16
16
  - [Installation & Setup](#installation--setup)
17
17
  - [Usage Documentation](#usage-documentation)
18
18
  - [Basic Usage](#basic-usage)
19
+ - [Actions & Dispatch](#actions--dispatch)
20
+ - [Reactive Selectors](#reactive-selectors)
19
21
  - [Persistence Integration](#persistence-integration)
20
22
  - [Middleware System](#middleware-system)
21
23
  - [Transaction Support](#transaction-support)
24
+ - [Artifacts (Dependency Injection)](#artifacts-dependency-injection)
25
+ - [React Integration](#react-integration)
22
26
  - [Store Observer (Debugging & Observability)](#store-observer-debugging--observability)
23
27
  - [Event System](#event-system)
24
28
  - [Project Architecture](#project-architecture)
@@ -31,25 +35,30 @@ A comprehensive, type-safe, and reactive state management library for TypeScript
31
35
 
32
36
  `@asaidimu/utils-store` is a powerful and flexible state management solution designed for modern TypeScript applications. It provides a highly performant and observable way to manage your application's data, ensuring type safety and predictability across complex state interactions. Built on principles of immutability and explicit updates, it makes state changes easy to track, debug, and extend.
33
37
 
34
- This library offers robust tools to handle your state with confidence, enabling features like atomic transactions, a pluggable middleware pipeline, and deep runtime introspection for unparalleled debugging capabilities. It emphasizes a component-based design internally, allowing for clear separation of concerns for core state management, middleware processing, persistence, and observability.
38
+ This library offers robust tools to handle your state with confidence, enabling features like atomic transactions, a pluggable middleware pipeline, a powerful dependency injection system for services (artifacts), and deep runtime introspection for unparalleled debugging capabilities. It emphasizes a component-based design internally, allowing for clear separation of concerns for core state management, middleware processing, persistence, and observability.
35
39
 
36
40
  ### Key Features
37
41
 
38
42
  * 📊 **Type-safe State Management**: Full TypeScript support for defining and interacting with your application state, leveraging `DeepPartial<T>` for precise, structural updates while maintaining strong type inference.
39
43
  * 🔄 **Reactive Updates & Granular Subscriptions**: Subscribe to granular changes at specific paths within your state or listen for any change, ensuring efficient re-renders or side effects. The internal `diff` algorithm optimizes notifications by identifying only truly changed paths.
44
+ * 🚀 **Action System**: Dispatch named actions to encapsulate state updates, improving code organization and enabling detailed logging and observability for each logical operation, including debouncing capabilities.
45
+ * 🔍 **Reactive Selectors with Memoization**: Efficiently derive computed state from your store using reactive selectors that re-evaluate and notify subscribers only when their accessed paths actually change, preventing unnecessary renders.
40
46
  * 🧠 **Composable Middleware System**:
41
47
  * **Transform Middleware**: Intercept, modify, normalize, or enrich state updates before they are applied. These can return a `DeepPartial<T>` to apply further changes or `void` for side effects.
42
48
  * **Blocking Middleware**: Implement custom validation, authorization, or other conditional logic to prevent invalid state changes from occurring. If a blocking middleware returns `false` or throws an error, the update is immediately halted and rolled back.
43
49
  * 📦 **Atomic Transaction Support**: Group multiple state updates into a single, atomic operation. If any update within the transaction fails or an error is thrown, the entire transaction is rolled back to the state before the transaction began, guaranteeing data integrity. Supports both synchronous and asynchronous operations.
44
- * 💾 **Optional Persistence Layer**: Seamlessly integrate with any `SimplePersistence<T>` implementation (e.g., for local storage, IndexedDB, or backend synchronization) to load an initial state and save subsequent changes. The store emits a `persistence:ready` event and listens for external updates.
45
- * 🔍 **Deep Observer & Debugging (`StoreObserver`)**: An optional but highly recommended class for unparalleled runtime introspection and debugging:
46
- * **Comprehensive Event History**: Captures a detailed log of all internal store events (`update:start`, `middleware:complete`, `transaction:error`, `persistence:ready`, `middleware:executed`, etc.).
50
+ * 💾 **Optional Persistence Layer**: Seamlessly integrate with any `SimplePersistence<T>` implementation (e.g., for local storage, IndexedDB, or backend synchronization) to load an initial state and save subsequent changes. The store emits a `persistence:ready` event and listens for external updates, handling persistence queueing and retries.
51
+ * 🧩 **Artifacts (Dependency Injection)**: A flexible dependency injection system to manage services, utilities, or complex objects that your actions and other artifacts depend on. Supports `Singleton` (re-evaluated on dependencies change) and `Transient` (new instance every time) scopes, and reactive resolution using `use(({select, resolve}) => ...)` context. Handles dependency graphs and circular dependency detection.
52
+ * ⚛️ **React Integration**: Provides a `createStore` factory function that returns a custom React hook (`useStore`). This hook offers `select` (memoized selector hook), `actions` (bound action dispatchers), and `resolve` (reactive artifact resolver) for seamless integration with React components. It leverages React's `useSyncExternalStore` for optimal performance.
53
+ * 👀 **Deep Observer & Debugging (`StoreObserver`)**: An optional but highly recommended class for unparalleled runtime introspection and debugging:
54
+ * **Comprehensive Event History**: Captures a detailed log of all internal store events (`update:start`, `middleware:complete`, `transaction:error`, `persistence:ready`, `middleware:executed`, `action:start`, `selector:accessed`, etc.).
47
55
  * **State Snapshots**: Maintains a configurable history of your application's state over time, allowing for easy inspection of changes between updates and post-mortem analysis.
48
56
  * **Time-Travel Debugging**: Leverage the recorded state history to `undo` and `redo` state changes, providing powerful capabilities for debugging complex asynchronous flows and state transitions.
49
57
  * **Performance Metrics**: Track real-time performance indicators like total update count, listener executions, average update times, largest update size, and slow operation warnings to identify bottlenecks.
50
58
  * **Configurable Console Logging**: Provides human-readable, color-coded logging of store events directly to the browser console for immediate feedback during development.
51
59
  * **Pre-built Debugging Middlewares**: Includes helper methods to easily create a generic logging middleware and a validation middleware for immediate use.
52
- * 🗑️ **Property Deletion**: Supports explicit property deletion within partial updates using the global `Symbol.for("delete")`or a custom marker.
60
+ * **Session Management**: Save and load observer sessions for offline analysis or sharing bug reproductions.
61
+ * 🗑️ **Property Deletion**: Supports explicit property deletion within partial updates using the global `Symbol.for("delete")` or a custom marker.
53
62
  * ⚡ **Concurrency Handling**: Automatically queues and processes `set` updates to prevent race conditions during concurrent calls, ensuring updates are applied in a predictable, sequential order.
54
63
 
55
64
  ---
@@ -72,7 +81,8 @@ yarn add @asaidimu/utils-store
72
81
  ### Prerequisites
73
82
 
74
83
  * **Node.js**: (LTS version recommended) for development and compilation.
75
- * **TypeScript**: (v4.0+ recommended) for full type-safety during development.
84
+ * **TypeScript**: (v4.0+ recommended) for full type-safety during development. Modern TS features (ES2017+ for `async/await`, ES2020+ for `Symbol.for()` and `structuredClone()`) are utilized. `moduleResolution`: `Node16` or `Bundler` is recommended in `tsconfig.json`.
85
+ * **React**: (v16.8+ for hooks) if using the `@asaidimu/utils-store/react` integration.
76
86
 
77
87
  ### Verification
78
88
 
@@ -90,12 +100,12 @@ interface MyState {
90
100
  const store = new ReactiveDataStore<MyState>({ count: 0, message: "Hello" });
91
101
 
92
102
  // Subscribing to "count" will only log when 'count' path changes
93
- store.subscribe("count", (state) => {
103
+ store.watch("count", (state) => {
94
104
  console.log(`Count changed to: ${state.count}`);
95
105
  });
96
106
 
97
107
  // Subscribing to "" (empty string) will log for any store update
98
- store.subscribe("", (state) => {
108
+ store.watch("", (state) => {
99
109
  console.log(`Store updated to: ${JSON.stringify(state)}`);
100
110
  });
101
111
 
@@ -133,14 +143,14 @@ This section provides practical examples and detailed explanations of how to use
133
143
  Learn how to create a store, read state, and update state with partial objects or functions.
134
144
 
135
145
  ```typescript
136
- import { ReactiveDataStore, type DeepPartial } from '@asaidimu/utils-store';
146
+ import { ReactiveDataStore, DELETE_SYMBOL, type DeepPartial } from '@asaidimu/utils-store';
137
147
 
138
148
  // 1. Define your state interface for type safety
139
149
  interface AppState {
140
150
  user: {
141
151
  id: string;
142
152
  name: string;
143
- email: string;
153
+ email?: string; // email is optional as we'll delete it
144
154
  isActive: boolean;
145
155
  };
146
156
  products: Array<{ id: string; name: string; price: number }>;
@@ -223,21 +233,21 @@ console.log('State after functional update, products count:', store.get().produc
223
233
 
224
234
  // 6. Subscribing to state changes
225
235
  // You can subscribe to the entire state (path: '') or specific paths (e.g., 'user.name', 'settings.notificationsEnabled').
226
- const unsubscribeUser = store.subscribe('user', (state) => {
236
+ const unsubscribeUser = store.watch('user', (state) => {
227
237
  console.log('User data changed:', state.user);
228
238
  });
229
239
 
230
- const unsubscribeNotifications = store.subscribe('settings.notificationsEnabled', (state) => {
240
+ const unsubscribeNotifications = store.watch('settings.notificationsEnabled', (state) => {
231
241
  console.log('Notifications setting changed:', state.settings.notificationsEnabled);
232
242
  });
233
243
 
234
244
  // Subscribe to multiple paths at once
235
- const unsubscribeMulti = store.subscribe(['user.name', 'products'], (state) => {
245
+ const unsubscribeMulti = store.watch(['user.name', 'products'], (state) => {
236
246
  console.log('User name or products changed:', state.user.name, state.products.length);
237
247
  });
238
248
 
239
249
  // Subscribe to any change in the store (root listener)
240
- const unsubscribeAll = store.subscribe('', (state) => {
250
+ const unsubscribeAll = store.watch('', (state) => {
241
251
  console.log('Store updated (any path changed). Current products count:', state.products.length);
242
252
  });
243
253
 
@@ -264,31 +274,238 @@ await store.set({ user: { isActive: true } });
264
274
  // No console output from the above listeners after unsubscribing.
265
275
 
266
276
  // 8. Deleting properties
267
- // Use `Symbol.for("delete")` to explicitly remove a property from the state.
268
- // Note: You might need a type cast (e.g., `as DeepPartial<string>`) for TypeScript if strict type checking is enabled.
269
- const DELETE_ME = Symbol.for("delete");
277
+ // Use `DELETE_SYMBOL` (exported from the library) to explicitly remove a property from the state.
270
278
  await store.set({
271
279
  user: {
272
- email: DELETE_ME as DeepPartial<string> // Cast is needed for type inference
280
+ email: DELETE_SYMBOL as DeepPartial<string> // Type cast is needed for strict TypeScript environments
273
281
  }
274
282
  });
275
283
  console.log('User email after deletion:', store.get().user.email);
276
284
  // Output: User email after deletion: undefined
277
285
  ```
278
286
 
287
+ ### Actions & Dispatch
288
+
289
+ The `dispatch` method allows you to encapsulate state updates into named actions. This improves code organization, provides clear semantic meaning to updates, and enables detailed logging and observability through `StoreObserver` and the event system. Actions can also be debounced to prevent excessive updates.
290
+
291
+ ```typescript
292
+ import { ReactiveDataStore } from '@asaidimu/utils-store';
293
+
294
+ interface CounterState {
295
+ value: number;
296
+ history: string[];
297
+ }
298
+
299
+ const store = new ReactiveDataStore<CounterState>({ value: 0, history: [] });
300
+
301
+ // 1. Register an action
302
+ store.register({
303
+ name: 'incrementCounter',
304
+ fn: (state, amount: number) => ({ // Action function receives state and dispatched parameters
305
+ value: state.value + amount,
306
+ history: [...state.history, `Incremented by ${amount} at ${new Date().toLocaleTimeString()}`],
307
+ }),
308
+ });
309
+
310
+ // 2. Register an action with debounce
311
+ store.register({
312
+ name: 'incrementCounterDebounced',
313
+ fn: (state, amount: number) => ({
314
+ value: state.value + amount,
315
+ history: [...state.history, `Debounced Increment by ${amount} at ${new Date().toLocaleTimeString()}`],
316
+ }),
317
+ debounce: {
318
+ delay: 100, // Debounce for 100ms
319
+ // Optional: condition to decide whether to debounce.
320
+ // Here, we always debounce if `amount` is different from previous.
321
+ condition: (previousArgs, currentArgs) => previousArgs?.[0] !== currentArgs[0],
322
+ }
323
+ });
324
+
325
+ // 3. Register an async action
326
+ store.register({
327
+ name: 'loadInitialValue',
328
+ fn: async (state, userId: string) => {
329
+ console.log(`Simulating loading initial value for user: ${userId}`);
330
+ await new Promise(resolve => setTimeout(resolve, 200)); // Simulate API call
331
+ return {
332
+ value: 100, // Fetched value
333
+ history: [...state.history, `Loaded initial value for ${userId}`],
334
+ };
335
+ },
336
+ });
337
+
338
+ // 4. Dispatch actions
339
+ console.log('Initial value:', store.get().value); // 0
340
+
341
+ await store.dispatch('incrementCounter', 5);
342
+ console.log('Value after incrementCounter(5):', store.get().value); // 5
343
+
344
+ await store.dispatch('incrementCounter', 10);
345
+ console.log('Value after incrementCounter(10):', store.get().value); // 15
346
+
347
+ // Dispatch debounced actions multiple times quickly
348
+ store.dispatch('incrementCounterDebounced', 1);
349
+ store.dispatch('incrementCounterDebounced', 2);
350
+ store.dispatch('incrementCounterDebounced', 3);
351
+ console.log('Value after immediate debounced dispatches (still 15, waiting for debounce):', store.get().value);
352
+
353
+ // Wait for the debounce to complete
354
+ await new Promise(resolve => setTimeout(150, resolve));
355
+ console.log('Value after debounced dispatches settled (only last one applied):', store.get().value); // 15 + 3 = 18
356
+
357
+ // Dispatch an async action
358
+ await store.dispatch('loadInitialValue', 'user-abc');
359
+ console.log('Value after loadInitialValue:', store.get().value); // 100 (overwritten by fetched value)
360
+ console.log('History:', store.get().history);
361
+
362
+ // 5. Deregister an action
363
+ const deregisterRisky = store.register({
364
+ name: 'riskyAction',
365
+ fn: () => { throw new Error('Risky action!'); }
366
+ });
367
+
368
+ deregisterRisky(); // Action is now removed
369
+
370
+ try {
371
+ await store.dispatch('riskyAction');
372
+ } catch (error: any) {
373
+ console.error('Expected error when dispatching deregistered action:', error.message);
374
+ }
375
+ ```
376
+
377
+ ### Reactive Selectors
378
+
379
+ Reactive selectors provide an efficient way to derive computed data from your store. They automatically track their dependencies and will only re-evaluate and notify subscribers if the underlying data they access changes. This prevents unnecessary re-renders in UI frameworks.
380
+
381
+ ```typescript
382
+ import { ReactiveDataStore } from '@asaidimu/utils-store';
383
+
384
+ interface UserProfile {
385
+ firstName: string;
386
+ lastName: string;
387
+ email: string;
388
+ address: {
389
+ city: string;
390
+ zipCode: string;
391
+ };
392
+ isActive: boolean;
393
+ friends: string[];
394
+ }
395
+
396
+ const store = new ReactiveDataStore<UserProfile>({
397
+ firstName: 'John',
398
+ lastName: 'Doe',
399
+ email: 'john.doe@example.com',
400
+ address: {
401
+ city: 'New York',
402
+ zipCode: '10001',
403
+ },
404
+ isActive: true,
405
+ friends: ['Alice', 'Bob'],
406
+ });
407
+
408
+ // Create a reactive selector for the full name
409
+ const selectFullName = store.select((state) => `${state.firstName} ${state.lastName}`);
410
+
411
+ // Create a reactive selector for user's location
412
+ const selectLocation = store.select((state) => `${state.address.city}, ${state.address.zipCode}`);
413
+
414
+ // Create a reactive selector for user's active status
415
+ const selectIsActive = store.select((state) => state.isActive);
416
+
417
+ // Create a reactive selector for the number of friends
418
+ const selectFriendCount = store.select((state) => state.friends.length);
419
+
420
+ let lastFullName = '';
421
+ let lastLocation = '';
422
+ let lastIsActive = false;
423
+ let lastFriendCount = 0;
424
+
425
+ // Subscribe to the full name selector
426
+ const unsubscribeFullName = selectFullName.subscribe((fullName) => {
427
+ lastFullName = fullName;
428
+ console.log('Full Name Changed:', lastFullName);
429
+ });
430
+
431
+ // Subscribe to the location selector
432
+ const unsubscribeLocation = selectLocation.subscribe((location) => {
433
+ lastLocation = location;
434
+ console.log('Location Changed:', lastLocation);
435
+ });
436
+
437
+ // Subscribe to the active status selector
438
+ const unsubscribeIsActive = selectIsActive.subscribe((isActive) => {
439
+ lastIsActive = isActive;
440
+ console.log('Is Active Changed:', lastIsActive);
441
+ });
442
+
443
+ // Subscribe to the friend count selector
444
+ const unsubscribeFriendCount = selectFriendCount.subscribe((count) => {
445
+ lastFriendCount = count;
446
+ console.log('Friend Count Changed:', lastFriendCount);
447
+ });
448
+
449
+
450
+ // Get initial values
451
+ lastFullName = selectFullName.get();
452
+ lastLocation = selectLocation.get();
453
+ lastIsActive = selectIsActive.get();
454
+ lastFriendCount = selectFriendCount.get();
455
+ console.log('Initial Full Name:', lastFullName);
456
+ console.log('Initial Location:', lastLocation);
457
+ console.log('Initial Is Active:', lastIsActive);
458
+ console.log('Initial Friend Count:', lastFriendCount);
459
+
460
+
461
+ console.log('\n--- Performing Updates ---');
462
+
463
+ // Update first name (should trigger selectFullName)
464
+ await store.set({ firstName: 'Jane' });
465
+ // Output: Full Name Changed: Jane Doe
466
+
467
+ await store.set({ lastName: 'Smith' }); // Should trigger selectFullName
468
+ // Output: Full Name Changed: Jane Smith
469
+
470
+ await store.set({ address: { city: 'Los Angeles' } }); // Should trigger selectLocation
471
+ // Output: Location Changed: Los Angeles, 10001
472
+
473
+ await store.set({ isActive: false }); // Should trigger selectIsActive
474
+ // Output: Is Active Changed: false
475
+
476
+ await store.set({ email: 'jane.smith@example.com' }); // Should NOT trigger any of the above selectors directly
477
+ // No output from subscribed selectors
478
+
479
+ await store.set((state) => ({ friends: [...state.friends, 'Charlie'] })); // Should trigger selectFriendCount
480
+ // Output: Friend Count Changed: 3
481
+
482
+ console.log('\n--- Current Values ---');
483
+ console.log('Current Full Name:', selectFullName.get());
484
+ console.log('Current Location:', selectLocation.get());
485
+ console.log('Current Is Active:', selectIsActive.get());
486
+ console.log('Current Friend Count:', selectFriendCount.get());
487
+
488
+ // Cleanup
489
+ unsubscribeFullName();
490
+ unsubscribeLocation();
491
+ unsubscribeIsActive();
492
+ unsubscribeFriendCount();
493
+ ```
494
+
279
495
  ### Persistence Integration
280
496
 
281
- The `ReactiveDataStore` can integrate with any persistence layer that implements the `SimplePersistence<T>` interface. This allows you to load an initial state and automatically save subsequent changes. The store emits a `persistence:ready` event once the persistence layer has loaded any initial state.
497
+ The `ReactiveDataStore` can integrate with any persistence layer that implements the `SimplePersistence<T>` interface. This allows you to load an initial state and automatically save subsequent changes. The store emits a `persistence:ready` event once the persistence layer has loaded any initial state. You can use `@asaidimu/utils-persistence` for concrete implementations or provide your own.
282
498
 
283
499
  ```typescript
284
500
  import { ReactiveDataStore, type StoreEvent } from '@asaidimu/utils-store';
501
+ import { v4 as uuidv4 } from 'uuid'; // For generating unique instance IDs
285
502
 
286
503
  // Define the SimplePersistence interface (from `@asaidimu/utils-persistence` or your own)
287
504
  interface SimplePersistence<T extends object> {
288
- get(): Promise<T | null>;
289
- set(instanceId: string, state: T): Promise<boolean>;
290
- subscribe(instanceId: string, listener: (state: T) => void): () => void;
291
- // An unsubscribe method for the persistence layer is recommended but not enforced by this interface
505
+ get(): T | null | Promise<T | null>; // Can be synchronous or asynchronous
506
+ set(instanceId: string, state: T): boolean | Promise<boolean>; // Can be synchronous or asynchronous
507
+ subscribe?(instanceId: string, listener: (state: T) => void): () => void; // Optional: for external change detection
508
+ clear?(): boolean | Promise<boolean>; // Optional: for clearing persisted data
292
509
  }
293
510
 
294
511
  // Example: A simple in-memory persistence for demonstration
@@ -297,56 +514,63 @@ class InMemoryPersistence<T extends object> implements SimplePersistence<T> {
297
514
  private data: T | null = null;
298
515
  // Using a map to simulate different instances subscribing to common data
299
516
  private subscribers: Map<string, (state: T) => void> = new Map();
517
+ private uniqueStoreId: string; // Acts as the storageKey/store identifier
300
518
 
301
- constructor(initialData: T | null = null) {
519
+ constructor(uniqueStoreId: string, initialData: T | null = null) {
520
+ this.uniqueStoreId = uniqueStoreId;
302
521
  this.data = initialData;
303
522
  }
304
523
 
305
- async get(): Promise<T | null> {
306
- console.log('Persistence: Loading state...');
524
+ get(): T | null { // Synchronous get for simplicity
525
+ console.log(`Persistence [${this.uniqueStoreId}]: Loading state...`);
307
526
  return this.data ? structuredClone(this.data) : null;
308
527
  }
309
528
 
310
529
  async set(instanceId: string, state: T): Promise<boolean> {
311
- console.log(`Persistence: Saving state for instance ${instanceId}...`);
530
+ console.log(`Persistence [${this.uniqueStoreId}]: Saving state for instance ${instanceId}...`);
312
531
  this.data = structuredClone(state); // Store a clone
313
532
  // Simulate external change notification for *other* instances
314
533
  this.subscribers.forEach((callback, subId) => {
315
534
  // Only notify other instances, not the one that just saved
316
535
  if (subId !== instanceId) {
317
- callback(structuredClone(this.data!)); // Pass a clone to prevent mutation
536
+ // Ensure to queue the microtask for async notification to prevent synchronous re-entry issues
537
+ queueMicrotask(() => callback(structuredClone(this.data!))); // Pass a clone to prevent mutation
318
538
  }
319
539
  });
320
540
  return true;
321
541
  }
322
542
 
323
543
  subscribe(instanceId: string, callback: (state: T) => void): () => void {
324
- console.log(`Persistence: Subscribing to external changes for instance ${instanceId}`);
544
+ console.log(`Persistence [${this.uniqueStoreId}]: Subscribing to external changes for instance ${instanceId}`);
325
545
  this.subscribers.set(instanceId, callback);
326
546
  return () => {
327
- console.log(`Persistence: Unsubscribing for instance ${instanceId}`);
547
+ console.log(`Persistence [${this.uniqueStoreId}]: Unsubscribing for instance ${instanceId}`);
328
548
  this.subscribers.delete(instanceId);
329
549
  };
330
550
  }
331
551
  }
332
552
 
333
553
  interface UserConfig {
334
- theme: 'light' | 'dark';
554
+ theme: 'light' | 'dark' | 'system';
335
555
  fontSize: number;
336
556
  }
337
557
 
338
558
  // Create a persistence instance, possibly with some pre-existing data
339
- const userConfigPersistence = new InMemoryPersistence<UserConfig>({ theme: 'dark', fontSize: 18 });
559
+ // The 'my-user-config' string acts as the unique identifier for this particular data set in persistence
560
+ const userConfigPersistence = new InMemoryPersistence<UserConfig>('my-user-config', { theme: 'dark', fontSize: 18 });
340
561
 
341
562
  // Initialize the store with persistence
342
563
  const store = new ReactiveDataStore<UserConfig>(
343
564
  { theme: 'light', fontSize: 16 }, // Initial state if no persisted data found (or if persistence is not used)
344
- userConfigPersistence // Pass your persistence implementation here
565
+ userConfigPersistence, // Pass your persistence implementation here
566
+ // You can also pass persistence options like retries and delay
567
+ undefined, // Use default DELETE_SYMBOL
568
+ { persistenceMaxRetries: 5, persistenceRetryDelay: 2000 }
345
569
  );
346
570
 
347
571
  // Optionally, listen for persistence readiness (important for UIs that depend on loaded state)
348
572
  const storeReadyPromise = new Promise<void>(resolve => {
349
- store.onStoreEvent('persistence:ready', (data) => {
573
+ store.on('persistence:ready', (data) => {
350
574
  console.log(`Store is ready and persistence is initialized! Timestamp: ${new Date(data.timestamp).toLocaleTimeString()}`);
351
575
  resolve();
352
576
  });
@@ -370,7 +594,7 @@ console.log('Current theme:', store.get().theme);
370
594
  // Simulate an external change (e.g., another tab or process updating the state)
371
595
  // Note: The `instanceId` here should be different from the store's `store.id()`
372
596
  // to simulate an external change and trigger the store's internal persistence subscription.
373
- await userConfigPersistence.set('another-instance-id-123', { theme: 'system', fontSize: 20 });
597
+ await userConfigPersistence.set(uuidv4(), { theme: 'system', fontSize: 20 });
374
598
  // The store will automatically update its state and notify its listeners due to the internal subscription.
375
599
  console.log('Current theme after external update:', store.get().theme);
376
600
  // Output: Current theme after external update: system
@@ -442,8 +666,8 @@ store.use({
442
666
  if (typeof update.counter === 'number') {
443
667
  return { counter: state.counter + update.counter };
444
668
  }
445
- // Return the original update or void if no transformation is needed for other paths
446
- return update;
669
+ // Return the original update or undefined if no transformation is needed for other paths
670
+ return undefined;
447
671
  },
448
672
  });
449
673
 
@@ -453,7 +677,7 @@ Middleware: Incoming update: { counter: 5 }
453
677
  */
454
678
  console.log('State after counter set:', store.get());
455
679
  /* Output will show:
456
- counter: 5 (initial) + 5 (update) = 10,
680
+ counter: 0 (initial) + 5 (update) = 5 (due to CounterIncrementMiddleware)
457
681
  lastAction updated by TimestampActionMiddleware,
458
682
  logs updated by TimestampActionMiddleware,
459
683
  version: 1 (incremented by VersionIncrementMiddleware)
@@ -474,7 +698,8 @@ console.log('State after manual action:', store.get());
474
698
  const temporaryLoggerId = store.use({ name: 'TemporaryLogger', action: (s, u) => console.log('Temporary logger saw:', u) });
475
699
  await store.set({ counter: 1 });
476
700
  // Output: Temporary logger saw: { counter: 1 }
477
- store.unuse(temporaryLoggerId); // Remove the temporary logger
701
+ const removed = store.use({ name: 'TemporaryLogger', action: (s, u) => console.log('Temporary logger saw:', u) })(); // Remove the temporary logger
702
+ console.log('Middleware removed:', removed);
478
703
  await store.set({ counter: 1 }); // TemporaryLogger will not be called now
479
704
  ```
480
705
 
@@ -643,6 +868,352 @@ try {
643
868
  }
644
869
  ```
645
870
 
871
+ ### Artifacts (Dependency Injection)
872
+
873
+ The Artifact system provides a powerful way to manage external dependencies, services, or complex objects that your actions or other artifacts might need. It supports `Singleton` (created once, reactive to dependencies) and `Transient` (new instance every time) scopes, and allows artifacts to depend on state changes and other artifacts.
874
+
875
+ This example uses the `ArtifactContainer` directly for clarity, but in React applications, you'd typically use the `createStore` factory, which automatically integrates with `ArtifactContainer` and exposes resolution via `useStore().resolve()`.
876
+
877
+ ```typescript
878
+ import { ArtifactContainer, ArtifactScope } from '@asaidimu/utils-store/artifacts';
879
+
880
+ interface AppState {
881
+ user: { id: string; name: string; };
882
+ config: { apiUrl: string; logLevel: string; };
883
+ }
884
+
885
+ // Mock DataStore interface for standalone ArtifactContainer usage
886
+ let currentState: AppState = {
887
+ user: { id: 'user-1', name: 'Alice' },
888
+ config: { apiUrl: '/api/v1', logLevel: 'info' }
889
+ };
890
+
891
+ const store = new ReactiveDataStore<AppState>(currentState);
892
+
893
+ const container = new ArtifactContainer(store);
894
+
895
+ // --- Artifact Definitions ---
896
+
897
+ // Artifact 1: Simple Logger (Transient) - new instance every time
898
+ container.register({
899
+ key: 'logger',
900
+ scope: ArtifactScope.Transient,
901
+ factory: () => {
902
+ console.log('Logger artifact created (Transient)');
903
+ return {
904
+ log: (message: string) => console.log(`[LOG] ${message}`)
905
+ };
906
+ }
907
+ });
908
+
909
+ // Artifact 2: API Client (Singleton) - depends on state.config.apiUrl
910
+ const apiClientCleanup = () => console.log('API Client connection closed.');
911
+ container.register({
912
+ key: 'apiClient',
913
+ scope: ArtifactScope.Singleton,
914
+ factory: async ({ use, current }) => {
915
+ const apiUrl = await use(({ select }) => select((state: AppState) => state.config.apiUrl));
916
+ console.log(`API Client created/re-created for URL: ${apiUrl}`);
917
+ if (current) {
918
+ console.log('Re-creating API client. Old instance:', current);
919
+ }
920
+ return [{
921
+ fetchUser: (id: string) => `Fetching ${id} from ${apiUrl}`,
922
+ sendData: (data: any) => `Sending ${JSON.stringify(data)} to ${apiUrl}`
923
+ }, apiClientCleanup];
924
+ }
925
+ });
926
+
927
+ // Artifact 3: User Service (Singleton) - depends on 'apiClient' and state.user.name
928
+ const userServiceCleanup = () => console.log('User Service resources released.');
929
+ container.register({
930
+ key: 'userService',
931
+ scope: ArtifactScope.Singleton,
932
+ factory: async ({ use, current }) => {
933
+ const apiClient = await use(({ resolve }) => resolve('apiClient'));
934
+ const userName = await use(({ select }) => select((state: AppState) => state.user.name));
935
+ console.log(`User Service created/re-created for user: ${userName}`);
936
+ if (current) {
937
+ console.log('Re-creating User Service. Old instance:', current);
938
+ }
939
+ return [{
940
+ getUserProfile: () => apiClient.fetchUser(mockGet().user.id),
941
+ updateUserName: (newName: string) => `Updating user name to ${newName} via API. Current: ${userName}`
942
+ }, userServiceCleanup];
943
+ }
944
+ });
945
+
946
+ // --- Usage ---
947
+
948
+ async function runDemo() {
949
+ console.log('\n--- Initial Artifact Resolution ---');
950
+ const logger1 = await container.resolve('logger');
951
+ logger1.log('Application started.');
952
+
953
+ const apiClient1 = await container.resolve('apiClient');
954
+ console.log(apiClient1.fetchUser('123'));
955
+
956
+ const userService1 = await container.resolve('userService');
957
+ console.log(userService1.getUserProfile());
958
+
959
+ // Transient artifact resolves a new instance
960
+ const logger2 = await container.resolve('logger');
961
+ expect(logger1).not.toBe(logger2); // Will be different instances
962
+
963
+ // Singleton artifacts resolve the same instance
964
+ const apiClient2 = await container.resolve('apiClient');
965
+ expect(apiClient1).toBe(apiClient2);
966
+
967
+ console.log('\n--- Simulate State Change (config.apiUrl) ---');
968
+ store.set({ config:{ apiUrl: '/api/v2'}})
969
+
970
+ // After config.apiUrl changes, apiClient (and userService) should be re-created
971
+ const apiClient3 = await container.resolve('apiClient'); // This will trigger re-creation
972
+ console.log(apiClient3.fetchUser('123'));
973
+ expect(apiClient3).not.toBe(apiClient1); // Should be a new instance
974
+
975
+ // userService should also be re-created because apiClient, its dependency, was re-created
976
+ const userService2 = await container.resolve('userService');
977
+ console.log(userService2.getUserProfile());
978
+ expect(userService2).not.toBe(userService1); // Should be a new instance
979
+
980
+ console.log('\n--- Simulate State Change (user.name) ---');
981
+ simulateStateUpdate(
982
+ { ...currentState, user: { ...currentState.user, name: 'Bob' } },
983
+ ['user.name']
984
+ );
985
+
986
+ // Only userService (which depends on user.name) should be re-created this time, not apiClient
987
+ const apiClient4 = await container.resolve('apiClient');
988
+ expect(apiClient4).toBe(apiClient3); // Still the same API client instance
989
+
990
+ const userService3 = await container.resolve('userService');
991
+ console.log(userService3.getUserProfile());
992
+ expect(userService3).not.toBe(userService2); // New user service instance
993
+
994
+ console.log('\n--- Unregistering Artifacts ---');
995
+ await container.unregister('userService');
996
+ // Output: User Service resources released. (cleanup called)
997
+ await container.unregister('apiClient');
998
+ // Output: API Client connection closed. (cleanup called)
999
+
1000
+ try {
1001
+ await container.resolve('userService');
1002
+ } catch (e: any) {
1003
+ console.error('Expected error:', e.message);
1004
+ }
1005
+ }
1006
+
1007
+ runDemo();
1008
+ ```
1009
+
1010
+ ### React Integration
1011
+
1012
+ The `@asaidimu/utils-store/react-store` module provides a `createStore` factory function that integrates the core `ReactiveDataStore` with React's context and `useSyncExternalStore` hook. This makes it easy to consume state, dispatch actions, and resolve artifacts in your React components.
1013
+
1014
+ ```typescript
1015
+ // store.ts (example for an E-commerce dashboard)
1016
+ import { ArtifactScope, createStore, ActionMap, ArtifactsMap } from '@asaidimu/utils-store';
1017
+
1018
+ export interface Product {
1019
+ id: number; name: string; price: number; stock: number; image: string;
1020
+ }
1021
+ export interface CartItem extends Product { quantity: number; }
1022
+ export interface Order { id: string; items: CartItem[]; total: number; date: Date; }
1023
+
1024
+ export interface ECommerceState extends Record<string, any> {
1025
+ products: Product[];
1026
+ cart: CartItem[];
1027
+ orders: Order[];
1028
+ topSellers: { id: number; name: string; sales: number }[];
1029
+ activeUsers: number;
1030
+ }
1031
+
1032
+ const initialState: ECommerceState = {
1033
+ products: [
1034
+ { id: 1, name: 'Wireless Mouse', price: 25.99, stock: 150, image: 'https://placehold.co/600x400/white/black?text=Mouse' },
1035
+ { id: 2, name: 'Mechanical Keyboard', price: 79.99, stock: 100, image: 'https://placehold.co/600x400/white/black?text=Keyboard' },
1036
+ ],
1037
+ cart: [], orders: [], topSellers: [], activeUsers: 1428,
1038
+ };
1039
+
1040
+ // 1. Define Artifacts
1041
+ export const artifacts = {
1042
+ logger: {
1043
+ scope: ArtifactScope.Singleton,
1044
+ factory: () => ((...args:any[]) => console.log('Artifact Logger:', ...args))
1045
+ },
1046
+ analytics: {
1047
+ scope: ArtifactScope.Transient,
1048
+ factory: () => ({
1049
+ trackEvent: (name: string, data: object) => console.log(`[Analytics] ${name}`, data)
1050
+ })
1051
+ }
1052
+ } as const satisfies ArtifactsMap<ECommerceState>
1053
+
1054
+ // 2. Define Actions
1055
+ export const actions = {
1056
+ addToCart: async ({ state, resolve }, product: Product) => {
1057
+ const log = await resolve("logger");
1058
+ const analytics = await resolve("analytics");
1059
+ analytics.trackEvent('add_to_cart', { product: product.name });
1060
+
1061
+ const existingItem = state.cart.find((item) => item.id === product.id);
1062
+ if (existingItem) {
1063
+ return {
1064
+ cart: state.cart.map((item) =>
1065
+ item.id === product.id ? { ...item, quantity: item.quantity + 1 } : item
1066
+ ),
1067
+ };
1068
+ }
1069
+ const result = { cart: [...state.cart, { ...product, quantity: 1 }] };
1070
+ log("Item added:", product.name);
1071
+ return result
1072
+ },
1073
+ removeFromCart: ({ state }, productId: number) => ({
1074
+ cart: state.cart.filter((item) => item.id !== productId),
1075
+ }),
1076
+ checkout: ({ state }) => {
1077
+ const total = state.cart.reduce((sum, item) => sum + item.price * item.quantity, 0);
1078
+ const newOrder: Order = {
1079
+ id: crypto.randomUUID(), items: state.cart, total, date: new Date(),
1080
+ };
1081
+ return {
1082
+ cart: [], orders: [newOrder, ...state.orders],
1083
+ };
1084
+ },
1085
+ // Simulated real-time updates for the dashboard example
1086
+ updateStock: ({ state }) => ({
1087
+ products: state.products.map(p => ({
1088
+ ...p,
1089
+ stock: Math.max(0, p.stock + Math.floor(Math.random() * 10) - 5)
1090
+ }))
1091
+ }),
1092
+ updateActiveUsers: ({ state }) => ({
1093
+ activeUsers: state.activeUsers + Math.floor(Math.random() * 20) - 10,
1094
+ }),
1095
+ addRandomOrder: ({ state }) => {
1096
+ const randomProduct = state.products[Math.floor(Math.random() * state.products.length)];
1097
+ const quantity = Math.floor(Math.random() * 3) + 1;
1098
+ const newOrder: Order = {
1099
+ id: crypto.randomUUID(),
1100
+ items: [{ ...randomProduct, quantity }],
1101
+ total: randomProduct.price * quantity,
1102
+ date: new Date(),
1103
+ };
1104
+ return {
1105
+ orders: [newOrder, ...state.orders],
1106
+ };
1107
+ },
1108
+ } as const satisfies ActionMap<ECommerceState, typeof artifacts>
1109
+
1110
+ // 3. Create the React Store Hook
1111
+ export const useStore = createStore(
1112
+ {
1113
+ state: initialState,
1114
+ artifacts,
1115
+ actions,
1116
+ },
1117
+ { enableMetrics: true, enableConsoleLogging: true }
1118
+ );
1119
+
1120
+ // App.tsx (React Component)
1121
+ import { useEffect, useMemo } from 'react';
1122
+ import { useStore } from './store'; // Import the custom hook
1123
+
1124
+ function ProductCatalog() {
1125
+ const { select, actions } = useStore();
1126
+ const products = select((state) => state.products); // Reactive selector for products
1127
+
1128
+ return (
1129
+ <div>
1130
+ <h2>Products</h2>
1131
+ {products.map((product) => (
1132
+ <div key={product.id}>
1133
+ {product.name} - ${product.price} ({product.stock} in stock)
1134
+ <button onClick={() => actions.addToCart(product)}>Add to Cart</button>
1135
+ </div>
1136
+ ))}
1137
+ </div>
1138
+ );
1139
+ }
1140
+
1141
+ function ShoppingCart() {
1142
+ const { select, actions } = useStore();
1143
+ const cart = select((state) => state.cart); // Reactive selector for cart
1144
+ const total = useMemo(() => cart.reduce((sum, item) => sum + item.price * item.quantity, 0), [cart]);
1145
+
1146
+ return (
1147
+ <div>
1148
+ <h2>Shopping Cart</h2>
1149
+ {cart.length === 0 ? (
1150
+ <p>Your cart is empty.</p>
1151
+ ) : (
1152
+ <ul>
1153
+ {cart.map((item) => (
1154
+ <li key={item.id}>
1155
+ {item.name} x {item.quantity} - ${item.price * item.quantity}
1156
+ <button onClick={() => actions.removeFromCart(item.id)}>Remove</button>
1157
+ </li>
1158
+ ))}
1159
+ </ul>
1160
+ )}
1161
+ <p>Total: ${total.toFixed(2)}</p>
1162
+ {cart.length > 0 && <button onClick={() => actions.checkout()}>Checkout</button>}
1163
+ </div>
1164
+ );
1165
+ }
1166
+
1167
+ function DebugPanel() {
1168
+ const { resolve, watch, state } = useStore();
1169
+ const [logger, loggerReady] = resolve('logger'); // Reactive artifact resolution
1170
+ const [analytics, analyticsReady] = resolve('analytics');
1171
+
1172
+ const isLoadingAddToCart = watch('addToCart'); // Watch specific action loading state
1173
+
1174
+ return (
1175
+ <div>
1176
+ <h3>Debug Panel</h3>
1177
+ <p>Add to Cart Loading: {isLoadingAddToCart ? 'Yes' : 'No'}</p>
1178
+ <p>Store is ready: {useStore().isReady ? 'Yes' : 'No'}</p>
1179
+ <button onClick={() => loggerReady && logger('Debug message from UI')}>Log via Artifact</button>
1180
+ <button onClick={() => analyticsReady && analytics.trackEvent('ui_event', { component: 'DebugPanel' })}>Track UI Event</button>
1181
+ <pre>Full State: {JSON.stringify(state(), null, 2)}</pre>
1182
+ </div>
1183
+ );
1184
+ }
1185
+
1186
+ function App() {
1187
+ const { actions } = useStore();
1188
+
1189
+ // Simulate real-time updates
1190
+ useEffect(() => {
1191
+ const stockInterval = setInterval(() => actions.updateStock(), 2000);
1192
+ const usersInterval = setInterval(() => actions.updateActiveUsers(), 3000);
1193
+ const ordersInterval = setInterval(() => actions.addRandomOrder(), 5000);
1194
+
1195
+ return () => {
1196
+ clearInterval(stockInterval);
1197
+ clearInterval(usersInterval);
1198
+ clearInterval(ordersInterval);
1199
+ };
1200
+ }, [actions]);
1201
+
1202
+ return (
1203
+ <div>
1204
+ <h1>E-Commerce App</h1>
1205
+ <ProductCatalog />
1206
+ <hr />
1207
+ <ShoppingCart />
1208
+ <hr />
1209
+ <DebugPanel />
1210
+ </div>
1211
+ );
1212
+ }
1213
+
1214
+ export default App;
1215
+ ```
1216
+
646
1217
  ### Store Observer (Debugging & Observability)
647
1218
 
648
1219
  The `StoreObserver` class provides advanced debugging and monitoring capabilities for any `ReactiveDataStore` instance. It allows you to inspect event history, state changes, and even time-travel through your application's state. It's an invaluable tool for understanding complex state flows.
@@ -674,6 +1245,8 @@ const observer = new StoreObserver(store, {
674
1245
  updates: true, // Log all update lifecycle events (start/complete)
675
1246
  middleware: true, // Log middleware start/complete/error/blocked/executed events
676
1247
  transactions: true, // Log transaction start/complete/error events
1248
+ actions: true, // Log action start/complete/error events
1249
+ selectors: true, // Log selector accessed/changed events
677
1250
  },
678
1251
  performanceThresholds: {
679
1252
  updateTime: 50, // Warn in console if an update takes > 50ms
@@ -693,8 +1266,8 @@ await store.set({ messages: ['Hello World!'] });
693
1266
  await store.set({ settings: { debugMode: false } });
694
1267
 
695
1268
  // Simulate a slow update to trigger performance warning
696
- await new Promise(resolve => setTimeout(resolve, 60)); // Artificially delay
697
- await store.set({ messages: ['Another message', 'And another'] });
1269
+ // await new Promise(resolve => setTimeout(resolve, 60)); // Artificially delay
1270
+ // await store.set({ messages: ['Another message', 'And another'] });
698
1271
  // This last set will cause a console warning for "Slow update detected" if enableConsoleLogging is true.
699
1272
 
700
1273
  // 1. Get Event History
@@ -706,7 +1279,7 @@ events.slice(0, 5).forEach(event => console.log(`Type: ${event.type}, Data: ${JS
706
1279
  // 2. Get State History
707
1280
  console.log('\n--- State History (Most Recent First) ---');
708
1281
  const stateSnapshots = observer.getStateHistory();
709
- stateSnapshots.forEach((snapshot, index) => console.log(`State #${index}: Messages: ${snapshot.messages.join(', ')}, User Status: ${snapshot.user.status}`));
1282
+ stateSnapshots.forEach((snapshot, index) => console.log(`State #${index}: Messages: ${snapshot.state.messages.join(', ')}, User Status: ${snapshot.state.user.status}`));
710
1283
 
711
1284
  // 3. Get Recent Changes (Diffs)
712
1285
  console.log('\n--- Recent State Changes (Diffs) ---');
@@ -745,7 +1318,7 @@ if (timeTravel.canRedo()) {
745
1318
  console.log('After redo 1:', store.get().messages); // Output: ['First message']
746
1319
  }
747
1320
 
748
- console.log('Time-Travel history length:', timeTravel.getHistoryLength()); // Reflects `maxStateHistory` + initial state
1321
+ console.log('Time-Travel history length:', timeTravel.length()); // Reflects `maxStateHistory` + initial state
749
1322
 
750
1323
  // 5. Custom Debugging Middleware (provided by StoreObserver for convenience)
751
1324
  // Example: A logging middleware that logs every update
@@ -769,7 +1342,7 @@ const validationMiddlewareId = store.use({ name: 'DebugValidation', block: true,
769
1342
 
770
1343
  try {
771
1344
  await store.set({ messages: ['m1','m2','m3','m4','m5','m6'] }); // This will be blocked
772
- } catch (e) {
1345
+ } catch (e: any) {
773
1346
  console.warn(`Caught expected error from validation middleware: ${e.message}`);
774
1347
  }
775
1348
  console.log('Current messages after failed validation:', store.get().messages); // Should be the state before this set.
@@ -788,7 +1361,7 @@ await store.set({ messages: ['Final message after disconnect'] });
788
1361
 
789
1362
  ### Event System
790
1363
 
791
- The store emits various events during its lifecycle, allowing for advanced monitoring, logging, and integration with external systems. You can subscribe to these events using `store.onStoreEvent(eventName, listener)`. The `StoreObserver` leverages this event system internally to provide its rich debugging capabilities.
1364
+ The store emits various events during its lifecycle, allowing for advanced monitoring, logging, and integration with external systems. You can subscribe to these events using `store.on(eventName, listener)`. The `StoreObserver` leverages this event system internally to provide its rich debugging capabilities.
792
1365
 
793
1366
  ```typescript
794
1367
  import { ReactiveDataStore, type StoreEvent } from '@asaidimu/utils-store';
@@ -801,58 +1374,93 @@ interface MyState {
801
1374
  const store = new ReactiveDataStore<MyState>({ value: 0, status: 'idle' });
802
1375
 
803
1376
  // Subscribe to 'update:start' event - triggered before an update begins processing.
804
- store.onStoreEvent('update:start', (data) => {
805
- console.log(`[${new Date(data.timestamp).toLocaleTimeString()}] ⚡ Update started.`);
1377
+ store.on('update:start', (data) => {
1378
+ console.log(`[${new Date(data.timestamp).toLocaleTimeString()}] ⚡ Update started. Action ID: ${data.actionId || 'N/A'}`);
806
1379
  });
807
1380
 
808
1381
  // Subscribe to 'update:complete' event - triggered after an update is fully applied or blocked.
809
- store.onStoreEvent('update:complete', (data) => {
1382
+ store.on('update:complete', (data) => {
810
1383
  if (data.blocked) {
811
1384
  console.warn(`[${new Date(data.timestamp).toLocaleTimeString()}] ✋ Update blocked. Error:`, data.error?.message);
812
1385
  } else {
813
- console.log(`[${new Date(data.timestamp).toLocaleTimeString()}] ✅ Update complete. Changed paths: ${data.changedPaths?.join(', ')} (took ${data.duration?.toFixed(2)}ms)`);
1386
+ console.log(`[${new Date(data.timestamp).toLocaleTimeString()}] ✅ Update complete. Changed paths: ${data.deltas?.map((d:any) => d.path).join(', ')} (took ${data.duration?.toFixed(2)}ms)`);
814
1387
  }
815
1388
  });
816
1389
 
817
1390
  // Subscribe to middleware lifecycle events
818
- store.onStoreEvent('middleware:start', (data) => {
819
- console.log(`[${new Date(data.timestamp).toLocaleTimeString()}] ▶ Middleware "${data.name}" (${data.type}) started.`);
1391
+ store.on('middleware:start', (data) => {
1392
+ console.log(`[${new Date(data.timestamp).toLocaleTimeString()}] ▶ Middleware "${data.name}" (${data.type || 'transform'}) started.`);
820
1393
  });
821
1394
 
822
- store.onStoreEvent('middleware:complete', (data) => {
823
- console.log(`[${new Date(data.timestamp).toLocaleTimeString()}] ◀ Middleware "${data.name}" (${data.type}) completed in ${data.duration?.toFixed(2)}ms.`);
1395
+ store.on('middleware:complete', (data) => {
1396
+ console.log(`[${new Date(data.timestamp).toLocaleTimeString()}] ◀ Middleware "${data.name}" (${data.type || 'transform'}) completed in ${data.duration?.toFixed(2)}ms.`);
824
1397
  });
825
1398
 
826
- store.onStoreEvent('middleware:error', (data) => {
1399
+ store.on('middleware:error', (data) => {
827
1400
  console.error(`[${new Date(data.timestamp).toLocaleTimeString()}] ❌ Middleware "${data.name}" failed:`, data.error);
828
1401
  });
829
1402
 
830
- store.onStoreEvent('middleware:blocked', (data) => {
1403
+ store.on('middleware:blocked', (data) => {
831
1404
  console.warn(`[${new Date(data.timestamp).toLocaleTimeString()}] 🛑 Middleware "${data.name}" blocked an update.`);
832
1405
  });
833
1406
 
834
- store.onStoreEvent('middleware:executed', (data) => {
1407
+ store.on('middleware:executed', (data) => {
835
1408
  // This event captures detailed execution info for all middlewares, useful for aggregate metrics.
836
1409
  console.debug(`[${new Date(data.timestamp).toLocaleTimeString()}] 📊 Middleware executed: "${data.name}" - Duration: ${data.duration?.toFixed(2)}ms, Blocked: ${data.blocked}`);
837
1410
  });
838
1411
 
839
1412
  // Subscribe to transaction lifecycle events
840
- store.onStoreEvent('transaction:start', (data) => {
1413
+ store.on('transaction:start', (data) => {
841
1414
  console.log(`[${new Date(data.timestamp).toLocaleTimeString()}] 📦 Transaction started.`);
842
1415
  });
843
1416
 
844
- store.onStoreEvent('transaction:complete', (data) => {
1417
+ store.on('transaction:complete', (data) => {
845
1418
  console.log(`[${new Date(data.timestamp).toLocaleTimeString()}] 📦 Transaction complete.`);
846
1419
  });
847
1420
 
848
- store.onStoreEvent('transaction:error', (data) => {
1421
+ store.on('transaction:error', (data) => {
849
1422
  console.error(`[${new Date(data.timestamp).toLocaleTimeString()}] 📦 Transaction failed:`, data.error);
850
1423
  });
851
1424
 
852
1425
  // Subscribe to persistence events
853
- store.onStoreEvent('persistence:ready', (data) => {
1426
+ store.on('persistence:ready', (data) => {
854
1427
  console.log(`[${new Date(data.timestamp).toLocaleTimeString()}] 💾 Persistence layer is ready.`);
855
1428
  });
1429
+ store.on('persistence:queued', (data) => {
1430
+ console.log(`[${new Date(data.timestamp).toLocaleTimeString()}] ⏳ Persistence task queued: ${data.taskId}, queue size: ${data.queueSize}`);
1431
+ });
1432
+ store.on('persistence:success', (data) => {
1433
+ console.log(`[${new Date(data.timestamp).toLocaleTimeString()}] ✅ Persistence success for task: ${data.taskId}, took ${data.duration?.toFixed(2)}ms`);
1434
+ });
1435
+ store.on('persistence:retry', (data) => {
1436
+ console.warn(`[${new Date(data.timestamp).toLocaleTimeString()}] 🔄 Persistence retry for task: ${data.taskId}, attempt ${data.attempt}/${data.maxRetries}`);
1437
+ });
1438
+ store.on('persistence:failed', (data) => {
1439
+ console.error(`[${new Date(data.timestamp).toLocaleTimeString()}] ❌ Persistence failed permanently for task: ${data.taskId}`, data.error);
1440
+ });
1441
+
1442
+
1443
+ // Subscribe to action events
1444
+ store.on('action:start', (data) => {
1445
+ console.log(`[${new Date(data.timestamp).toLocaleTimeString()}] 🚀 Action "${data.name}" (ID: ${data.actionId}) started with params:`, data.params);
1446
+ });
1447
+
1448
+ store.on('action:complete', (data) => {
1449
+ console.log(`[${new Date(data.timestamp).toLocaleTimeString()}] ✔️ Action "${data.name}" (ID: ${data.actionId}) completed in ${data.duration?.toFixed(2)}ms.`);
1450
+ });
1451
+
1452
+ store.on('action:error', (data) => {
1453
+ console.error(`[${new Date(data.timestamp).toLocaleTimeString()}] 🔥 Action "${data.name}" (ID: ${data.actionId}) failed:`, data.error);
1454
+ });
1455
+
1456
+ // Subscribe to selector events
1457
+ store.on('selector:accessed', (data) => {
1458
+ console.debug(`[${new Date(data.timestamp).toLocaleTimeString()}] 👀 Selector (ID: ${data.selectorId}) accessed paths: ${data.accessedPaths.join(', ')}`);
1459
+ });
1460
+
1461
+ store.on('selector:changed', (data) => {
1462
+ console.log(`[${new Date(data.timestamp).toLocaleTimeString()}] 📢 Selector (ID: ${data.selectorId}) changed. New result:`, data.newResult);
1463
+ });
856
1464
 
857
1465
 
858
1466
  // Add a transform middleware to demonstrate `middleware:start/complete/executed`
@@ -906,58 +1514,73 @@ src/store/
906
1514
  ├── index.ts # Main entry point (exports all public APIs)
907
1515
  ├── types.ts # TypeScript interfaces and types for the store
908
1516
  ├── store.ts # `ReactiveDataStore` - The main orchestrator
909
- ├── state.ts # `CoreStateManager` - Manages the immutable state and diffing
1517
+ ├── state.ts # `StateManager` - Manages the immutable state and diffing
910
1518
  ├── merge.ts # `createMerge` - Deep merging utility with deletion support
911
1519
  ├── diff.ts # `createDiff`, `createDerivePaths` - Change detection utilities
912
1520
  ├── middleware.ts # `MiddlewareEngine` - Manages and executes middleware pipeline
913
1521
  ├── transactions.ts # `TransactionManager` - Handles atomic state transactions
914
1522
  ├── persistence.ts # `PersistenceHandler` - Integrates with `SimplePersistence` layer
915
1523
  ├── metrics.ts # `MetricsCollector` - Gathers runtime performance metrics
916
- ├── observability.ts # `StoreObserver` - Debugging and introspection utilities
917
- └── ... # (Other test files like *.test.ts, internal helpers)
1524
+ ├── selector.ts # `SelectorManager` - Manages reactive selectors for derived state
1525
+ ├── observer.ts # `StoreObserver` - Debugging and introspection utilities
1526
+ ├── artifacts.ts # `ArtifactContainer` - Dependency Injection system for services
1527
+ ├── actions.ts # `ActionManager` - Manages registration and dispatch of named actions
1528
+ └── react-store/ # React integration module (hooks, context)
1529
+ ├── index.ts
1530
+ ├── types.ts
1531
+ ├── store.ts # `createStore` - Factory for React hooks
1532
+ └── execution.ts # `ActionTracker` - Tracks action execution history for React integration
918
1533
  ```
919
1534
 
920
1535
  ### Core Components
921
1536
 
922
- * **`ReactiveDataStore<T>` (`store.ts`)**: The public API and primary entry point. It orchestrates interactions between all other internal components. It manages the update queue, ensures sequential processing of `set` calls, and exposes public methods like `get`, `set`, `subscribe`, `transaction`, `use`, `unuse`, and `onStoreEvent`.
923
- * **`CoreStateManager<T>` (`state.ts`)**: Responsible for the direct management of the immutable state (`cache`). It applies incoming state changes, performs efficient object `diff`ing to identify modified paths, and notifies internal listeners (via an `updateBus`) about granular state changes.
924
- * **`MiddlewareEngine<T>` (`middleware.ts`)**: Manages the registration and execution of both `blocking` and `transform` middleware functions. It ensures middleware execution order, handles potential errors, and emits detailed lifecycle events for observability.
925
- * **`PersistenceHandler<T>` (`persistence.ts`)**: Handles integration with an external persistence layer via the `SimplePersistence` interface. It loads initial state, saves subsequent changes, and listens for external updates from the persistence layer to keep the in-memory state synchronized across multiple instances (e.g., browser tabs).
926
- * **`TransactionManager<T>` (`transactions.ts`)**: Provides atomic state operations. It creates a snapshot of the state before an `operation` begins and, if the operation fails, ensures the state is reverted to this snapshot, guaranteeing data integrity. It integrates closely with the store's event system for tracking transaction status.
927
- * **`MetricsCollector` (`metrics.ts`)**: Observes the internal `eventBus` to gather and expose real-time performance metrics of the store, such as update counts, listener executions, average update times, and the largest update size.
928
- * **`StoreObserver<T>` (`observability.ts`)**: An optional, yet highly valuable, debugging companion. It taps into the `ReactiveDataStore`'s extensive event stream and state changes to build a comprehensive history of events and state snapshots, enabling powerful features like time-travel debugging, detailed console logging, and performance monitoring.
929
- * **`createMerge` (`merge.ts`)**: A factory function that returns a configurable deep merging utility (`MergeFunction`). This utility is crucial for immutably applying partial updates and specifically handles `Symbol.for("delete")` for explicit property removal.
930
- * **`createDiff` / `createDerivePaths` (`diff.ts`)**: Factory functions returning utilities for efficient comparison between two objects (`diff`) to identify changed paths, and for deriving all parent paths from a set of changes (`derivePaths`). These are fundamental for optimizing listener notifications and internal change detection.
1537
+ * **`ReactiveDataStore<T>`**: The public API and primary entry point. It orchestrates interactions between all other internal components. It manages the update queue, ensures sequential processing of `set` calls, and exposes public methods like `get`, `dispatch`, `select`, `set`, `watch`, `transaction`, `use`, and `on`.
1538
+ * **`StateManager<T>`**: Responsible for the direct management of the immutable state (`cache`). It applies incoming state changes, performs efficient object `diff`ing to identify modified paths, and notifies internal listeners (via an `updateBus`) about granular state changes.
1539
+ * **`MiddlewareEngine<T>`**: Manages the registration and execution of both `blocking` and `transform` middleware functions. It ensures middleware execution order, handles potential errors, and emits detailed lifecycle events for observability.
1540
+ * **`PersistenceHandler<T>`**: Handles integration with an external persistence layer via the `SimplePersistence` interface. It loads initial state, saves subsequent changes, and listens for external updates from the persistence layer to keep the in-memory state synchronized across multiple instances (e.g., browser tabs). It also manages a background queue for persistence tasks with retries and exponential backoff.
1541
+ * **`TransactionManager<T>`**: Provides atomic state operations. It creates a snapshot of the state before an `operation` begins and, if the operation fails, ensures the state is reverted to this snapshot, guaranteeing data integrity. It integrates closely with the store's event system for tracking transaction status.
1542
+ * **`MetricsCollector`**: Observes the internal `eventBus` to gather and expose real-time performance metrics of the store, such as update counts, listener executions, average update times, largest update size, total events fired, and transaction/middleware execution counts.
1543
+ * **`SelectorManager<T>`**: Manages the creation, memoization, and reactivity of selectors. It tracks the paths accessed by each selector and re-evaluates them efficiently only when relevant parts of the state change, notifying their subscribers.
1544
+ * **`StoreObserver<T>`**: An optional, yet highly valuable, debugging companion. It taps into the `ReactiveDataStore`'s extensive event stream and state changes to build a comprehensive history of events and state snapshots, enabling powerful features like time-travel debugging, detailed console logging, and performance monitoring. It also supports saving/loading and exporting observer sessions.
1545
+ * **`ArtifactContainer<T>`**: Implements a dependency injection system for managing services (artifacts) with different lifecycles (Singleton, Transient) and complex dependencies on state paths and other artifacts. It handles lazy initialization, reactive re-evaluation, and resource cleanup.
1546
+ * **`ActionManager<T>`**: Manages the registration of named actions and their dispatch. It handles action lifecycle events, debouncing logic, and ensures actions interact correctly with the core `set` method.
1547
+ * **`createMerge`**: A factory function that returns a configurable deep merging utility (`MergeFunction`). This utility is crucial for immutably applying partial updates and specifically handles `Symbol.for("delete")` for explicit property removal.
1548
+ * **`createDiff` / `createDerivePaths`**: Factory functions returning utilities for efficient comparison between two objects (`createDiff`) to identify changed paths, and for deriving all parent paths from a set of changes (`createDerivePaths`). These are fundamental for optimizing listener notifications and internal change detection.
931
1549
 
932
1550
  ### Data Flow
933
1551
 
934
1552
  The `ReactiveDataStore` handles state updates in a robust, queued, and event-driven manner:
935
1553
 
936
- 1. **`store.set(update)` call**:
937
- * If another update is already in progress (`isUpdating`), the new `update` is queued in `pendingUpdates`. This ensures sequential processing and prevents race conditions, and the `store.state().pendingChanges` reflects the queue.
1554
+ 1. **`store.set(update)` or `store.dispatch(actionName, actionFn, ...)` call**:
1555
+ * If `dispatch` is used, an `action:start` event is emitted. The `actionFn` (which can be `async`) is executed to produce a `DeepPartial` update which is then passed to `store.set`. Actions can be debounced, delaying their `set` call and potentially cancelling previous in-flight actions.
1556
+ * All `set` calls are automatically queued to prevent race conditions during concurrent updates, ensuring sequential processing. The `store.state().pendingChanges` reflects the queue.
938
1557
  * An `update:start` event is immediately emitted.
939
1558
  2. **Middleware Execution**:
940
- * The `MiddlewareEngine` first executes all `blocking` middlewares (registered via `store.use({ block: true, ... })`). If any blocking middleware returns `false` or throws an error, the update is immediately halted. An `update:complete` event with `blocked: true` is emitted, and the process stops, with the state remaining unchanged.
1559
+ * The `MiddlewareEngine` first executes all `blocking` middlewares (registered via `store.use({ block: true, ... })`). If any blocking middleware returns `false` or throws an error, the update is immediately halted. An `update:complete` event with `blocked: true` and an `error` property is emitted, and the process stops, with the state remaining unchanged.
941
1560
  * If not blocked, `transform` middlewares (registered via `store.use({ action: ... })`) are executed sequentially. Each transform middleware receives the current state and the incoming partial update, and can return a new `DeepPartial<T>` that is then merged into the effective update payload.
942
1561
  * Detailed lifecycle events (`middleware:start`, `middleware:complete`, `middleware:error`, `middleware:blocked`, `middleware:executed`) are emitted during this phase, providing granular insight into middleware behavior.
943
1562
  3. **State Application**:
944
- * The `CoreStateManager` receives the (potentially transformed) final `DeepPartial` update.
1563
+ * The `StateManager` receives the (potentially transformed) final `DeepPartial` update.
945
1564
  * It internally uses the `createMerge` utility to immutably construct the new full state object.
946
- * It then performs a `createDiff` comparison between the *previous* state and the *new* state to precisely identify all `changedPaths` (an array of strings).
947
- * If changes are detected, the `CoreStateManager` updates its internal immutable `cache` to the `newState` and then emits an internal `update` event for each granular `changedPath` on its `updateBus`.
1565
+ * It then performs a `createDiff` comparison between the *previous* state and the *new* state to precisely identify all `changedPaths` (an array of `StateDelta` objects).
1566
+ * If changes are detected, the `StateManager` updates its internal immutable `cache` to the `newState` and then emits an internal `update` event for each granular `changedPath` on its `updateBus`.
948
1567
  4. **Listener Notification**:
949
- * Any external subscribers (registered with `store.subscribe()`) whose registered paths match or are parent paths of the `changedPaths` are efficiently notified with the latest state. The `MetricsCollector` tracks `listenerExecutions` during this phase.
1568
+ * Any external subscribers (registered with `store.watch()` or `store.subscribe()`) whose registered paths match or are parent paths of the `changedPaths` are efficiently notified with the latest state. The `MetricsCollector` tracks `listenerExecutions` during this phase.
1569
+ * The `SelectorManager` re-evaluates reactive selectors whose `accessedPaths` are affected by the `changedPaths`. If a selector's result changes, it notifies its own subscribers and emits a `selector:changed` event.
1570
+ * `ArtifactContainer` also receives change notifications for state paths its artifacts depend on, triggering re-evaluation for `Singleton` scoped artifacts.
950
1571
  5. **Persistence Handling**:
951
- * The `PersistenceHandler` receives the `changedPaths` and the new state. If a `SimplePersistence` implementation was configured during store initialization, it attempts to save the new state using `persistence.set()`.
1572
+ * The `PersistenceHandler` receives the `changedPaths` and the new state. If a `SimplePersistence` implementation was configured during store initialization, it attempts to save the new state using `persistence.set()`. This is done in the background via a queue, emitting `persistence:queued`, `persistence:success`, `persistence:retry`, and `persistence:failed` events.
952
1573
  * The `PersistenceHandler` also manages loading initial state and reacting to external state changes (e.g., from other browser tabs or processes) through `persistence.subscribe()`.
953
1574
  6. **Completion & Queue Processing**:
954
- * An `update:complete` event is emitted, containing crucial information about the update's duration, the `changedPaths`, and any blocking errors.
955
- * The `isUpdating` flag is reset. If there are any `pendingUpdates` in the queue, the next update is immediately pulled and processed, ensuring all queued updates are eventually applied in order.
1575
+ * An `update:complete` event is emitted, containing crucial information about the update's duration, the `StateDelta[]`, and any blocking errors.
1576
+ * If the update originated from a `dispatch` call, an `action:complete` or `action:error` event is emitted, correlating with the `action:start` event via a shared `actionId`.
1577
+ * The update queue automatically processes the next pending update.
956
1578
 
957
1579
  ### Extension Points
958
1580
 
959
1581
  * **Custom Middleware**: Developers can inject their own `Middleware` (for transformation) and `BlockingMiddleware` (for validation/prevention) functions using `store.use()`. This allows for highly customizable update logic, centralized logging, complex validation, authorization, or triggering specific side effects.
960
1582
  * **Custom Persistence**: The `SimplePersistence<T>` interface provides a clear contract for developers to integrate the store with any storage solution, whether it's local storage, IndexedDB, a backend API, or a WebSocket connection. This offers complete control over data durability and synchronization.
1583
+ * **Custom Artifacts**: The `ArtifactContainer` (directly or via React integration) allows defining and managing any custom services, utilities, or dependencies with defined scopes and lifecycle management.
961
1584
 
962
1585
  ---
963
1586
 
@@ -995,9 +1618,8 @@ From the `src/store` directory, the following `pnpm` scripts are available:
995
1618
 
996
1619
  * `pnpm test`: Runs all unit tests using [Vitest](https://vitest.dev/).
997
1620
  * `pnpm test:watch`: Runs tests in watch mode for continuous development.
1621
+ * `pnpm dev`: Starts the Vite development server for the UI example.
998
1622
  * `pnpm lint`: Lints the codebase using [ESLint](https://eslint.org/).
999
- * `pnpm format`: Formats the code using [Prettier](https://prettier.io/).
1000
- * `pnpm build`: Compiles TypeScript to JavaScript (CommonJS and ES Modules) and generates declaration files (`.d.ts`).
1001
1623
 
1002
1624
  ### Testing
1003
1625
 
@@ -1040,9 +1662,14 @@ If you find a bug or have a feature request, please open an issue on the [GitHub
1040
1662
  ### Troubleshooting
1041
1663
 
1042
1664
  * **"Update not triggering listeners"**:
1043
- * Ensure you are subscribing to the correct path. `store.subscribe('user.name', ...)` will not trigger if you update `user.email` (unless you also subscribe to `user` or the root `''` path).
1665
+ * Ensure you are subscribing to the correct path. `store.watch('user.name', ...)` will not trigger if you update `user.email` (unless you also subscribe to `user` or the root `''` path).
1044
1666
  * If the new value is strictly equal (`===`) to the old value, no change will be detected by the internal `diff` function, and listeners will not be notified.
1045
1667
  * Verify your `DeepPartial` update correctly targets the intended part of the state.
1668
+ * **"Reactive Selector not re-evaluating"**:
1669
+ * Ensure the state path(s) accessed by the selector are actually changing. The selector re-evaluates only when its dependencies change.
1670
+ * If using `select(() => state.someArray.length)`, it might not re-evaluate if array elements change but `length` remains the same.
1671
+ * Check for strict equality issues: if the new computed value is strictly equal to the old one, no re-evaluation notification will occur.
1672
+ * Avoid complex operations (array methods, conditionals, arithmetic) within selectors as they might bypass the static path analysis and lead to unexpected behavior or errors.
1046
1673
  * **"State not rolling back after transaction error"**:
1047
1674
  * Ensure the error is thrown *within* the `transaction` callback function. Errors caught and handled inside the callback, or thrown outside of it, will not trigger the rollback mechanism.
1048
1675
  * Promises within the transaction *must* be `await`ed so the `TransactionManager` can capture potential rejections and manage the atomic operation correctly.
@@ -1052,29 +1679,35 @@ If you find a bug or have a feature request, please open an issue on the [GitHub
1052
1679
  * Ensure your `transform` middleware returns a `DeepPartial<T>` or `void`/`Promise<void>`, and `blocking` middleware returns `boolean`/`Promise<boolean>`.
1053
1680
  * **"Performance warnings in console"**:
1054
1681
  * If `StoreObserver` is enabled with `enableConsoleLogging: true`, it will warn about updates or middlewares exceeding defined `performanceThresholds`. This is an informational warning, not an error, indicating a potentially slow operation that could be optimized in your application.
1682
+ * **"Artifact not resolving or re-evaluating"**:
1683
+ * Check for `Circular dependency` errors in the console during `resolve`.
1684
+ * For `Singleton` artifacts, ensure its `state` or `artifact` dependencies are actually changing to trigger re-evaluation. If a dependency changes, the artifact (and its downstream dependents) will be invalidated and re-created on next `resolve`.
1685
+ * For `Transient` artifacts, a new instance is created on every `resolve`.
1055
1686
 
1056
1687
  ### FAQ
1057
1688
 
1058
1689
  **Q: How are arrays handled during updates?**
1059
1690
  A: Arrays are treated as primitive values. When you provide an array in a `DeepPartial` update (e.g., `set({ items: [new Array] })`), the entire array at that path is replaced with the new array, not merged element-by-element. This ensures predictable behavior and avoids complex, potentially ambiguous partial array merging logic.
1060
1691
 
1061
- **Q: What is `Symbol.for("delete")`?**
1062
- A: `Symbol.for("delete")` is a special global symbol used to explicitly remove a property from the state during a `set` operation. If you pass `Symbol.for("delete")` as the value for a key in your `DeepPartial` update, that key will be removed from the resulting state object. This provides a clear semantic for deletion.
1692
+ **Q: What is `DELETE_SYMBOL`?**
1693
+ A: `DELETE_SYMBOL` (exported from `@asaidimu/utils-store`) is a special global symbol (`Symbol.for("delete")`) used to explicitly remove a property from the state during a `set` operation. If you pass `DELETE_SYMBOL` as the value for a key in your `DeepPartial` update, that key will be removed from the resulting state object. This provides a clear semantic for deletion.
1063
1694
 
1064
1695
  **Q: How do I debug my store's state changes?**
1065
1696
  A: The `StoreObserver` class is your primary tool. Instantiate it with your `ReactiveDataStore` instance. It provides methods to get event history, state snapshots, and even time-travel debugging capabilities. Enabling `enableConsoleLogging: true` in `StoreObserver` options provides immediate, formatted console output during development, showing update lifecycles, middleware execution, and transaction events.
1066
1697
 
1067
1698
  **Q: What is `SimplePersistence<T>`?**
1068
- A: It's a minimal interface that defines the contract for any persistence layer you wish to integrate. It requires `get()`, `set(instanceId, state)`, and `subscribe(instanceId, listener)` methods. You need to provide an implementation of this interface to enable state saving and loading. An example `InMemoryPersistence` implementation is provided in the [Persistence Integration](#persistence-integration) section.
1699
+ A: It's a minimal interface that defines the contract for any persistence layer you wish to integrate. It requires `get()`, `set(instanceId, state)`, and an optional `subscribe(instanceId, listener)` methods. You need to provide an implementation of this interface to enable state saving and loading. You can use concrete implementations from `@asaidimu/utils-persistence` or create your own.
1700
+
1701
+ **Q: How does the Artifact system work?**
1702
+ A: The Artifact system is a dependency injection container. You register `ArtifactFactory` functions with a `key` and a `scope` (`Singleton` or `Transient`). Factories receive a `context` with `state()` (current state snapshot) and `use()` (to declare dependencies on other artifacts or specific state paths). The `use()` method is crucial for building the dependency graph, allowing the system to automatically re-evaluate `Singleton` artifacts when their dependencies change. In React, you use `useStore().resolve()` to access artifacts reactively.
1069
1703
 
1070
- **Q: Can I use this with React/Vue/Angular or other UI frameworks?**
1071
- A: Yes, absolutely. This is a framework-agnostic state management library. You would typically use the `subscribe` method within your framework's lifecycle hooks (e.g., `useEffect` in React, `onMounted` in Vue) to react to state changes and update your UI components. The library's reactivity model is independent of any specific framework.
1704
+ **Q: What's the difference between `set` and `dispatch`?**
1705
+ A: `set` is the foundational method for updating the state, handling the core logic of applying changes, running middleware, and notifying subscribers. `dispatch` builds upon `set` by adding an "action" concept. It wraps `set` calls, providing a semantic name, optional parameters, and lifecycle callbacks (`action:start`, `action:complete`, `action:error`). This makes `dispatch` ideal for orchestrating complex, named operations and provides better observability, which can be correlated across the update lifecycle via a shared `actionId`. Use `set` for simple, direct updates; use `dispatch` for logical operations that should be tracked as distinct "actions."
1072
1706
 
1073
1707
  ### Changelog / Roadmap
1074
1708
 
1075
1709
  * **Changelog**: For detailed version history, including new features, bug fixes, and breaking changes, please refer to the project's [CHANGELOG.md](https://github.com/asaidimu/erp-utils/blob/main/src/store/CHANGELOG.md) file.
1076
1710
  * **Roadmap**: Future plans for `@asaidimu/utils-store` may include:
1077
- * Official framework-specific integrations (e.g., React hooks library for easier consumption).
1078
1711
  * More advanced query/selector capabilities with built-in memoization for derived state.
1079
1712
  * Built-in serialization/deserialization options for persistence, perhaps with schema validation.
1080
1713
  * Higher-order middlewares for common patterns (e.g., async data fetching, debouncing updates).
@@ -1089,3 +1722,5 @@ This project is licensed under the [MIT License](https://github.com/asaidimu/erp
1089
1722
  * Inspired by modern state management patterns such as Redux, Zustand, and Vuex, emphasizing immutability and explicit state changes.
1090
1723
  * Leverages the `@asaidimu/events` package for robust internal event bus capabilities.
1091
1724
  * Utilizes the `uuid` library for generating unique instance IDs.
1725
+
1726
+ ---