ng-simple-state 21.1.4 → 21.1.5

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,4 +1,4 @@
1
- # NgSimpleState [![Build Status](https://app.travis-ci.com/nigrosimone/ng-simple-state.svg?branch=main)](https://app.travis-ci.com/nigrosimone/ng-simple-state) [![Coverage Status](https://coveralls.io/repos/github/nigrosimone/ng-simple-state/badge.svg?branch=main)](https://coveralls.io/github/nigrosimone/ng-simple-state?branch=main) [![NPM version](https://img.shields.io/npm/v/ng-simple-state.svg)](https://www.npmjs.com/package/ng-simple-state) [![Maintainability](https://api.codeclimate.com/v1/badges/1bfc363a95053ecc3429/maintainability)](https://codeclimate.com/github/nigrosimone/ng-simple-state/maintainability)
1
+ # NgSimpleState [![NPM version](https://img.shields.io/npm/v/ng-simple-state.svg)](https://www.npmjs.com/package/ng-simple-state) [![Maintainability](https://qlty.sh/gh/nigrosimone/projects/ng-simple-state/maintainability.svg)](https://qlty.sh/gh/nigrosimone/projects/ng-simple-state)
2
2
 
3
3
  Simple state management in Angular with only Services and RxJS or Signal.
4
4
 
@@ -31,7 +31,6 @@ npm i ng-simple-state
31
31
  | *serializeState* | A function used to serialize the state to a string. | `JSON.stringify` |
32
32
  | *deserializeState* | A function used to deserialize the state from a string. | `JSON.parse` |
33
33
  | *plugins* | Array of plugins to extend store functionality. | `[]` |
34
- | *enableImmer* | Enable Immer-style immutable updates. | `false` |
35
34
  | *immerProduce* | Custom Immer produce function for immutable updates. | undefined |
36
35
 
37
36
  _Side note: each store can be override the global configuration implementing `storeConfig()` method (see "Override global config")._
@@ -1154,7 +1153,6 @@ Usage in component:
1154
1153
  selector: 'app-profile',
1155
1154
  template: `
1156
1155
  <p>Full Name: {{ store.fullName() }}</p>
1157
- <p>Is Adult: {{ store.isAdult() ? 'Yes' : 'No' }}</p>
1158
1156
  <input [value]="firstName()" (input)="updateFirstName($event)" placeholder="First name" />
1159
1157
  <input [value]="lastName()" (input)="updateLastName($event)" placeholder="Last name" />
1160
1158
  `
@@ -1283,89 +1281,83 @@ const myCustomPlugin: NgSimpleStatePlugin = {
1283
1281
  };
1284
1282
  ```
1285
1283
 
1286
- ### Batch Updates
1284
+ ### Transactions
1287
1285
 
1288
- Group multiple state updates into a single emission to improve performance:
1286
+ Execute operations with automatic rollback on error using `withTransaction`:
1289
1287
 
1290
1288
  ```ts
1291
- import { Injectable, Signal } from '@angular/core';
1292
- import { NgSimpleStateBaseSignalStore, NgSimpleStateStoreConfig, batchState } from 'ng-simple-state';
1293
-
1294
- export interface FormState {
1295
- name: string;
1296
- email: string;
1297
- phone: string;
1298
- isValid: boolean;
1299
- }
1300
-
1301
- @Injectable({ providedIn: 'root' })
1302
- export class FormStore extends NgSimpleStateBaseSignalStore<FormState> {
1289
+ import { withTransaction } from 'ng-simple-state';
1290
+
1291
+ // Transaction with automatic rollback on error
1292
+ await withTransaction(store, async (tx) => {
1293
+ store.setState({ step: 1 });
1294
+ await apiCall(); // If this fails, state rolls back automatically
1295
+ store.setState({ step: 2 });
1296
+ tx.commit(); // Explicit commit (optional - auto-commits if not called)
1297
+ });
1303
1298
 
1304
- storeConfig(): NgSimpleStateStoreConfig<FormState> {
1305
- return { storeName: 'FormStore' };
1299
+ // Manual rollback example
1300
+ await withTransaction(store, async (tx) => {
1301
+ store.setState({ processing: true });
1302
+ const result = await riskyOperation();
1303
+
1304
+ if (!result.success) {
1305
+ tx.rollback(); // Manually rollback to initial state
1306
+ return;
1306
1307
  }
1308
+
1309
+ store.setState({ data: result.data });
1310
+ tx.commit();
1311
+ });
1312
+ ```
1307
1313
 
1308
- initialState(): FormState {
1309
- return { name: '', email: '', phone: '', isValid: false };
1310
- }
1314
+ ### Debounced Updates
1311
1315
 
1312
- selectForm(): Signal<FormState> {
1313
- return this.selectState();
1314
- }
1316
+ Rate-limit state updates with `createDebouncedUpdater`. Only the last update within the time window is applied:
1315
1317
 
1316
- // Batch multiple updates - only one emission at the end
1317
- updateAllFields(name: string, email: string, phone: string): void {
1318
- batchState(() => {
1319
- this.setState({ name });
1320
- this.setState({ email });
1321
- this.setState({ phone });
1322
- this.setState({ isValid: this.validateForm(name, email, phone) });
1323
- }); // Single emission with all changes
1324
- }
1318
+ ```ts
1319
+ import { createDebouncedUpdater } from 'ng-simple-state';
1325
1320
 
1326
- // Regular updates (each triggers an emission)
1327
- setName(name: string): void {
1328
- this.setState({ name });
1329
- }
1321
+ // Create a debounced updater with 300ms delay
1322
+ const { update, flush, cancel } = createDebouncedUpdater<MyState>(
1323
+ (state) => store.setState(state),
1324
+ 300
1325
+ );
1330
1326
 
1331
- setEmail(email: string): void {
1332
- this.setState({ email });
1333
- }
1327
+ // Rapid calls - only the last one is applied after 300ms
1328
+ update({ searchQuery: 'a' });
1329
+ update({ searchQuery: 'ab' });
1330
+ update({ searchQuery: 'abc' }); // Only this is applied after 300ms
1334
1331
 
1335
- private validateForm(name: string, email: string, phone: string): boolean {
1336
- return name.length > 0 && email.includes('@') && phone.length >= 10;
1337
- }
1338
- }
1332
+ // Force immediate update
1333
+ flush();
1334
+
1335
+ // Cancel any pending update
1336
+ cancel();
1339
1337
  ```
1340
1338
 
1341
- Usage in component:
1339
+ ### Throttled Updates
1340
+
1341
+ Limit update frequency with `createThrottledUpdater`. At most one update per time window:
1342
1342
 
1343
1343
  ```ts
1344
- @Component({
1345
- selector: 'app-form',
1346
- template: `
1347
- <form (ngSubmit)="submitForm()">
1348
- <input [(ngModel)]="name" name="name" placeholder="Name" />
1349
- <input [(ngModel)]="email" name="email" placeholder="Email" />
1350
- <input [(ngModel)]="phone" name="phone" placeholder="Phone" />
1351
- <button type="submit">Save All (Batched)</button>
1352
- </form>
1353
- <p>Form Valid: {{ form().isValid ? 'Yes' : 'No' }}</p>
1354
- `
1355
- })
1356
- export class FormComponent {
1357
- store = inject(FormStore);
1358
- form = this.store.selectForm();
1359
-
1360
- name = '';
1361
- email = '';
1362
- phone = '';
1344
+ import { createThrottledUpdater } from 'ng-simple-state';
1363
1345
 
1364
- submitForm(): void {
1365
- // All updates batched into single emission
1366
- this.store.updateAllFields(this.name, this.email, this.phone);
1367
- }
1368
- }
1346
+ // Create a throttled updater with 100ms delay
1347
+ const { update, cancel } = createThrottledUpdater<MyState>(
1348
+ (state) => store.setState(state),
1349
+ 100
1350
+ );
1351
+
1352
+ // First call executes immediately, subsequent calls are throttled
1353
+ update({ scrollPosition: 100 }); // Executes immediately
1354
+ update({ scrollPosition: 150 }); // Queued
1355
+ update({ scrollPosition: 200 }); // Replaces queued update
1356
+
1357
+ // After 100ms, { scrollPosition: 200 } is applied
1358
+
1359
+ // Cancel pending throttled update
1360
+ cancel();
1369
1361
  ```
1370
1362
 
1371
1363
  ### Immer-style Updates
@@ -1508,6 +1500,43 @@ Features available in Redux DevTools:
1508
1500
 
1509
1501
  ![Redux DevTools](https://github.com/nigrosimone/ng-simple-state/blob/main/projects/ng-simple-state-demo/src/assets/dev-tool.gif?raw=true)
1510
1502
 
1503
+ ### Integration with ng-http-caching
1504
+
1505
+ `ng-simple-state` can be used as a reactive storage backend for [ng-http-caching](https://www.npmjs.com/package/ng-http-caching). This integration allows you to manage the HTTP cache through `ng-simple-state` stores, providing full visibility and control over the cache state within your DevTools or application state.
1506
+
1507
+ 1. Install `ng-http-caching`:
1508
+
1509
+ ```bash
1510
+ npm i ng-http-caching
1511
+ ```
1512
+
1513
+ 2. Configure `ng-http-caching` to use the `ng-simple-state` adapter in your `bootstrapApplication`:
1514
+
1515
+ ```ts
1516
+ import { provideNgHttpCaching } from 'ng-http-caching';
1517
+ import { withNgHttpCachingNgSimpleState } from 'ng-http-caching/ng-simple-state';
1518
+
1519
+ bootstrapApplication(AppComponent, {
1520
+ providers: [
1521
+ provideNgHttpCaching({
1522
+ store: withNgHttpCachingNgSimpleState(),
1523
+ }),
1524
+ // ... other providers
1525
+ ]
1526
+ });
1527
+ ```
1528
+
1529
+ You can customize the store configuration (e.g., enable persistence or change the store name) by passing a configuration object to the adapter:
1530
+
1531
+ ```ts
1532
+ provideNgHttpCaching({
1533
+ store: withNgHttpCachingNgSimpleState({
1534
+ storeName: 'MyCacheStore',
1535
+ // ... other NgSimpleStateStoreConfig options
1536
+ }),
1537
+ })
1538
+ ```
1539
+
1511
1540
  ## Alternatives
1512
1541
 
1513
1542
  Aren't you satisfied? there are some valid alternatives:
@@ -21,11 +21,11 @@ class NgSimpleStateModule {
21
21
  ],
22
22
  };
23
23
  }
24
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.0", ngImport: i0, type: NgSimpleStateModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule }); }
25
- static { this.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "21.0.0", ngImport: i0, type: NgSimpleStateModule }); }
26
- static { this.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "21.0.0", ngImport: i0, type: NgSimpleStateModule }); }
24
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: NgSimpleStateModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule }); }
25
+ static { this.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "21.2.5", ngImport: i0, type: NgSimpleStateModule }); }
26
+ static { this.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: NgSimpleStateModule }); }
27
27
  }
28
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.0", ngImport: i0, type: NgSimpleStateModule, decorators: [{
28
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: NgSimpleStateModule, decorators: [{
29
29
  type: NgModule
30
30
  }] });
31
31
 
@@ -50,9 +50,9 @@ class NgSimpleStateDevTool {
50
50
  this.historyIdCounter = 0;
51
51
  this.maxHistorySize = 100;
52
52
  /** Current history position for time-travel */
53
- this.currentPositionSig = signal(-1, ...(ngDevMode ? [{ debugName: "currentPositionSig" }] : []));
53
+ this.currentPositionSig = signal(-1, ...(ngDevMode ? [{ debugName: "currentPositionSig" }] : /* istanbul ignore next */ []));
54
54
  /** Paused state - when true, state changes are not recorded */
55
- this.isPausedSig = signal(false, ...(ngDevMode ? [{ debugName: "isPausedSig" }] : []));
55
+ this.isPausedSig = signal(false, ...(ngDevMode ? [{ debugName: "isPausedSig" }] : /* istanbul ignore next */ []));
56
56
  if (this.globalDevtools) {
57
57
  inject(NgZone).runOutsideAngular(() => {
58
58
  this.localDevTool = this.globalDevtools.connect({
@@ -404,10 +404,10 @@ class NgSimpleStateDevTool {
404
404
  }
405
405
  return NgSimpleStateDevTool.computeDiff(last.prevState, last.state);
406
406
  }
407
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.0", ngImport: i0, type: NgSimpleStateDevTool, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
408
- static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.0.0", ngImport: i0, type: NgSimpleStateDevTool, providedIn: 'root' }); }
407
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: NgSimpleStateDevTool, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
408
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: NgSimpleStateDevTool, providedIn: 'root' }); }
409
409
  }
410
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.0", ngImport: i0, type: NgSimpleStateDevTool, decorators: [{
410
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: NgSimpleStateDevTool, decorators: [{
411
411
  type: Injectable,
412
412
  args: [{ providedIn: 'root' }]
413
413
  }], ctorParameters: () => [] });
@@ -822,7 +822,9 @@ class NgSimpleStateBaseCommonStore {
822
822
  return undefined;
823
823
  }
824
824
  // avoid function call if not necessary
825
+ // eslint-disable-next-line @typescript-eslint/no-unused-expressions
825
826
  this.devTool && this.devToolSend(state, resolvedActionName);
827
+ // eslint-disable-next-line @typescript-eslint/no-unused-expressions
826
828
  this.storage && this.statePersist(state);
827
829
  // Notify plugins after change
828
830
  this.notifyPluginsAfterChange(currState, state, resolvedActionName);
@@ -869,7 +871,9 @@ class NgSimpleStateBaseCommonStore {
869
871
  return undefined;
870
872
  }
871
873
  // avoid function call if not necessary
874
+ // eslint-disable-next-line @typescript-eslint/no-unused-expressions
872
875
  this.devTool && this.devToolSend(newState, resolvedActionName);
876
+ // eslint-disable-next-line @typescript-eslint/no-unused-expressions
873
877
  this.storage && this.statePersist(newState);
874
878
  // Notify plugins after change
875
879
  this.notifyPluginsAfterChange(currState, newState, resolvedActionName);
@@ -925,11 +929,11 @@ class NgSimpleStateBaseCommonStore {
925
929
  this.storage.setItem(this.storeName, state);
926
930
  }
927
931
  }
928
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.0", ngImport: i0, type: NgSimpleStateBaseCommonStore, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
929
- static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "21.0.0", type: NgSimpleStateBaseCommonStore, isStandalone: true, ngImport: i0 }); }
930
- static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.0.0", ngImport: i0, type: NgSimpleStateBaseCommonStore }); }
932
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: NgSimpleStateBaseCommonStore, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
933
+ static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "21.2.5", type: NgSimpleStateBaseCommonStore, isStandalone: true, ngImport: i0 }); }
934
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: NgSimpleStateBaseCommonStore }); }
931
935
  }
932
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.0", ngImport: i0, type: NgSimpleStateBaseCommonStore, decorators: [{
936
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: NgSimpleStateBaseCommonStore, decorators: [{
933
937
  type: Injectable
934
938
  }, {
935
939
  type: Directive
@@ -982,18 +986,29 @@ class NgSimpleStateBaseRxjsStore extends NgSimpleStateBaseCommonStore {
982
986
  return this.state$.pipe(map(state => selectFn(state)), distinctUntilChanged(comparator ?? this.comparator), observeOn(asyncScheduler));
983
987
  }
984
988
  /**
985
- * Create an effect that reacts to state changes
989
+ * Create an effect from an observable
986
990
  * @param name Unique effect name
987
- * @param effectFn Effect function that receives current state
991
+ * @param source$ Observable source
992
+ * @param effectFn Effect function that receives the observable value
988
993
  */
989
- createEffect(name, effectFn) {
994
+ createEffectFromObservable(name, source$, effectFn) {
990
995
  // Cleanup existing effect with same name
991
996
  this.destroyEffect(name);
992
- const subscription = this.state$.pipe(takeUntil(this.destroy$)).subscribe(state => {
993
- effectFn(state);
997
+ const subscription = source$
998
+ .pipe(takeUntil(this.destroy$))
999
+ .subscribe(value => {
1000
+ effectFn(value);
994
1001
  });
995
1002
  this.registeredEffects.set(name, subscription);
996
1003
  }
1004
+ /**
1005
+ * Create an effect that reacts to state changes
1006
+ * @param name Unique effect name
1007
+ * @param effectFn Effect function that receives current state
1008
+ */
1009
+ createEffect(name, effectFn) {
1010
+ this.createEffectFromObservable(name, this.state$, effectFn);
1011
+ }
997
1012
  /**
998
1013
  * Create an effect that reacts to selected state changes
999
1014
  * @param name Unique effect name
@@ -1001,11 +1016,7 @@ class NgSimpleStateBaseRxjsStore extends NgSimpleStateBaseCommonStore {
1001
1016
  * @param effectFn Effect function that receives selected value
1002
1017
  */
1003
1018
  createSelectorEffect(name, selector, effectFn) {
1004
- this.destroyEffect(name);
1005
- const subscription = this.selectState(selector).pipe(takeUntil(this.destroy$)).subscribe(value => {
1006
- effectFn(value);
1007
- });
1008
- this.registeredEffects.set(name, subscription);
1019
+ this.createEffectFromObservable(name, this.selectState(selector), effectFn);
1009
1020
  }
1010
1021
  /**
1011
1022
  * Destroy a specific effect by name
@@ -1091,11 +1102,11 @@ class NgSimpleStateBaseRxjsStore extends NgSimpleStateBaseCommonStore {
1091
1102
  }
1092
1103
  return false;
1093
1104
  }
1094
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.0", ngImport: i0, type: NgSimpleStateBaseRxjsStore, deps: null, target: i0.ɵɵFactoryTarget.Directive }); }
1095
- static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "21.0.0", type: NgSimpleStateBaseRxjsStore, isStandalone: true, usesInheritance: true, ngImport: i0 }); }
1096
- static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.0.0", ngImport: i0, type: NgSimpleStateBaseRxjsStore }); }
1105
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: NgSimpleStateBaseRxjsStore, deps: null, target: i0.ɵɵFactoryTarget.Directive }); }
1106
+ static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "21.2.5", type: NgSimpleStateBaseRxjsStore, isStandalone: true, usesInheritance: true, ngImport: i0 }); }
1107
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: NgSimpleStateBaseRxjsStore }); }
1097
1108
  }
1098
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.0", ngImport: i0, type: NgSimpleStateBaseRxjsStore, decorators: [{
1109
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: NgSimpleStateBaseRxjsStore, decorators: [{
1099
1110
  type: Injectable
1100
1111
  }, {
1101
1112
  type: Directive
@@ -1106,7 +1117,7 @@ class NgSimpleStateBaseSignalStore extends NgSimpleStateBaseCommonStore {
1106
1117
  constructor() {
1107
1118
  super(...arguments);
1108
1119
  this.stackPoint = 4;
1109
- this.stateSig = signal(this.firstState, ...(ngDevMode ? [{ debugName: "stateSig" }] : []));
1120
+ this.stateSig = signal(this.firstState, ...(ngDevMode ? [{ debugName: "stateSig" }] : /* istanbul ignore next */ []));
1110
1121
  this.stateSigRo = this.stateSig.asReadonly();
1111
1122
  this.registeredEffects = new Map();
1112
1123
  this.injector = inject(Injector);
@@ -1151,7 +1162,7 @@ class NgSimpleStateBaseSignalStore extends NgSimpleStateBaseCommonStore {
1151
1162
  * @returns WritableSignal that is linked to the store state
1152
1163
  */
1153
1164
  linkedState(options) {
1154
- const sourceSignal = computed(() => options.source(this.stateSig()), ...(ngDevMode ? [{ debugName: "sourceSignal" }] : []));
1165
+ const sourceSignal = computed(() => options.source(this.stateSig()), ...(ngDevMode ? [{ debugName: "sourceSignal" }] : /* istanbul ignore next */ []));
1155
1166
  return linkedSignal({
1156
1167
  source: sourceSignal,
1157
1168
  computation: (source, previous) => {
@@ -1163,26 +1174,36 @@ class NgSimpleStateBaseSignalStore extends NgSimpleStateBaseCommonStore {
1163
1174
  });
1164
1175
  }
1165
1176
  /**
1166
- * Create an effect that reacts to state changes
1177
+ * Create an effect from a signal
1167
1178
  * @param name Unique effect name
1168
- * @param effectFn Effect function that receives current state
1179
+ * @param sourceSignal Signal source
1180
+ * @param effectFn Effect function that receives the signal value
1169
1181
  * @returns EffectRef for cleanup
1170
1182
  */
1171
- createEffect(name, effectFn) {
1183
+ createEffectFromSignal(name, sourceSignal, effectFn) {
1172
1184
  // Cleanup existing effect with same name
1173
1185
  this.destroyEffect(name);
1174
1186
  let cleanup;
1175
1187
  const effectRef = runInInjectionContext(this.injector, () => effect(() => {
1176
- const state = this.stateSig();
1188
+ const value = sourceSignal();
1177
1189
  // Run previous cleanup
1178
1190
  if (cleanup) {
1179
1191
  cleanup();
1180
1192
  }
1181
- cleanup = effectFn(state);
1193
+ cleanup = effectFn(value);
1182
1194
  }));
1183
1195
  this.registeredEffects.set(name, effectRef);
1184
1196
  return effectRef;
1185
1197
  }
1198
+ /**
1199
+ * Create an effect that reacts to state changes
1200
+ * @param name Unique effect name
1201
+ * @param effectFn Effect function that receives current state
1202
+ * @returns EffectRef for cleanup
1203
+ */
1204
+ createEffect(name, effectFn) {
1205
+ return this.createEffectFromSignal(name, this.stateSig, effectFn);
1206
+ }
1186
1207
  /**
1187
1208
  * Create an effect that reacts to selected state changes
1188
1209
  * @param name Unique effect name
@@ -1191,18 +1212,8 @@ class NgSimpleStateBaseSignalStore extends NgSimpleStateBaseCommonStore {
1191
1212
  * @returns EffectRef for cleanup
1192
1213
  */
1193
1214
  createSelectorEffect(name, selector, effectFn) {
1194
- this.destroyEffect(name);
1195
- const selectedSignal = computed(() => selector(this.stateSig()), ...(ngDevMode ? [{ debugName: "selectedSignal" }] : []));
1196
- let cleanup;
1197
- const effectRef = runInInjectionContext(this.injector, () => effect(() => {
1198
- const value = selectedSignal();
1199
- if (cleanup) {
1200
- cleanup();
1201
- }
1202
- cleanup = effectFn(value);
1203
- }));
1204
- this.registeredEffects.set(name, effectRef);
1205
- return effectRef;
1215
+ const selectedSignal = computed(() => selector(this.stateSig()), ...(ngDevMode ? [{ debugName: "selectedSignal" }] : /* istanbul ignore next */ []));
1216
+ return this.createEffectFromSignal(name, selectedSignal, effectFn);
1206
1217
  }
1207
1218
  /**
1208
1219
  * Destroy a specific effect by name
@@ -1288,11 +1299,11 @@ class NgSimpleStateBaseSignalStore extends NgSimpleStateBaseCommonStore {
1288
1299
  }
1289
1300
  return false;
1290
1301
  }
1291
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.0", ngImport: i0, type: NgSimpleStateBaseSignalStore, deps: null, target: i0.ɵɵFactoryTarget.Directive }); }
1292
- static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "21.0.0", type: NgSimpleStateBaseSignalStore, isStandalone: true, usesInheritance: true, ngImport: i0 }); }
1293
- static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.0.0", ngImport: i0, type: NgSimpleStateBaseSignalStore }); }
1302
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: NgSimpleStateBaseSignalStore, deps: null, target: i0.ɵɵFactoryTarget.Directive }); }
1303
+ static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "21.2.5", type: NgSimpleStateBaseSignalStore, isStandalone: true, usesInheritance: true, ngImport: i0 }); }
1304
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: NgSimpleStateBaseSignalStore }); }
1294
1305
  }
1295
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.0", ngImport: i0, type: NgSimpleStateBaseSignalStore, decorators: [{
1306
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: NgSimpleStateBaseSignalStore, decorators: [{
1296
1307
  type: Injectable
1297
1308
  }, {
1298
1309
  type: Directive
@@ -1340,121 +1351,6 @@ function provideNgSimpleStatePlugins(plugins) {
1340
1351
  }]);
1341
1352
  }
1342
1353
 
1343
- /**
1344
- * Batch updates manager for grouping multiple state changes
1345
- * into a single emission
1346
- */
1347
- class NgSimpleStateBatchManager {
1348
- constructor() {
1349
- this.batching = false;
1350
- this.pendingUpdates = new Map();
1351
- this.isBatchingSig = signal(false, ...(ngDevMode ? [{ debugName: "isBatchingSig" }] : []));
1352
- }
1353
- /**
1354
- * Get singleton instance
1355
- */
1356
- static getInstance() {
1357
- if (!NgSimpleStateBatchManager.instance) {
1358
- NgSimpleStateBatchManager.instance = new NgSimpleStateBatchManager();
1359
- }
1360
- return NgSimpleStateBatchManager.instance;
1361
- }
1362
- /**
1363
- * Check if currently batching
1364
- */
1365
- get isBatching() {
1366
- return this.isBatchingSig.asReadonly();
1367
- }
1368
- /**
1369
- * Start a batch operation
1370
- */
1371
- startBatch() {
1372
- this.batching = true;
1373
- this.isBatchingSig.set(true);
1374
- }
1375
- /**
1376
- * End batch operation and flush all pending updates
1377
- */
1378
- endBatch() {
1379
- this.batching = false;
1380
- this.isBatchingSig.set(false);
1381
- this.flush();
1382
- }
1383
- /**
1384
- * Check if currently in batch mode
1385
- */
1386
- isInBatch() {
1387
- return this.batching;
1388
- }
1389
- /**
1390
- * Queue an update for batching
1391
- * @param storeId Unique store identifier
1392
- * @param updateFn Update function to execute
1393
- */
1394
- queueUpdate(storeId, updateFn) {
1395
- if (!this.pendingUpdates.has(storeId)) {
1396
- this.pendingUpdates.set(storeId, []);
1397
- }
1398
- this.pendingUpdates.get(storeId).push(updateFn);
1399
- }
1400
- /**
1401
- * Flush all pending updates
1402
- */
1403
- flush() {
1404
- const updates = new Map(this.pendingUpdates);
1405
- this.pendingUpdates.clear();
1406
- updates.forEach((fns, storeId) => {
1407
- // Execute only the last update for each store
1408
- // since intermediate states are not needed
1409
- const lastUpdate = fns[fns.length - 1];
1410
- if (lastUpdate) {
1411
- lastUpdate();
1412
- }
1413
- });
1414
- }
1415
- /**
1416
- * Clear pending updates without executing
1417
- */
1418
- clearPending() {
1419
- this.pendingUpdates.clear();
1420
- }
1421
- }
1422
- /**
1423
- * Execute multiple state updates in a batch
1424
- * Only the final state is emitted
1425
- *
1426
- * @example
1427
- * ```ts
1428
- * batchState(() => {
1429
- * store.setState({ count: 1 });
1430
- * store.setState({ count: 2 });
1431
- * store.setState({ count: 3 });
1432
- * }); // Only emits once with count: 3
1433
- * ```
1434
- */
1435
- function batchState(updateFn) {
1436
- const manager = NgSimpleStateBatchManager.getInstance();
1437
- manager.startBatch();
1438
- try {
1439
- return updateFn();
1440
- }
1441
- finally {
1442
- manager.endBatch();
1443
- }
1444
- }
1445
- /**
1446
- * Execute multiple state updates in a batch asynchronously
1447
- */
1448
- async function batchStateAsync(updateFn) {
1449
- const manager = NgSimpleStateBatchManager.getInstance();
1450
- manager.startBatch();
1451
- try {
1452
- return await updateFn();
1453
- }
1454
- finally {
1455
- manager.endBatch();
1456
- }
1457
- }
1458
1354
  /**
1459
1355
  * Transaction wrapper for atomic state updates
1460
1356
  * Rolls back on error
@@ -1504,6 +1400,8 @@ class StateTransaction {
1504
1400
  *
1505
1401
  * @example
1506
1402
  * ```ts
1403
+ * import { withTransaction } from 'ng-simple-state';
1404
+ *
1507
1405
  * await withTransaction(store, async (tx) => {
1508
1406
  * store.setState({ step: 1 });
1509
1407
  * await apiCall(); // If this fails, state rolls back
@@ -1531,6 +1429,27 @@ async function withTransaction(store, fn) {
1531
1429
  /**
1532
1430
  * Debounce state updates
1533
1431
  * Only the last update within the time window is applied
1432
+ *
1433
+ * @example
1434
+ * ```ts
1435
+ * import { createDebouncedUpdater } from 'ng-simple-state';
1436
+ *
1437
+ * // Create a debounced updater with 300ms delay
1438
+ * const { update, flush, cancel } = createDebouncedUpdater<MyState>(
1439
+ * (state) => store.setState(state),
1440
+ * 300
1441
+ * );
1442
+ *
1443
+ * // Rapid calls - only the last one is applied after 300ms
1444
+ * update({ searchQuery: 'a' });
1445
+ * update({ searchQuery: 'ab' });
1446
+ * update({ searchQuery: 'abc' }); // Only this is applied after 300ms
1447
+ *
1448
+ * // Force immediate update
1449
+ * flush();
1450
+ *
1451
+ * // Cancel any pending update
1452
+ * cancel();
1534
1453
  */
1535
1454
  function createDebouncedUpdater(updateFn, delay = 100) {
1536
1455
  let timeoutId = null;
@@ -1564,6 +1483,27 @@ function createDebouncedUpdater(updateFn, delay = 100) {
1564
1483
  /**
1565
1484
  * Throttle state updates
1566
1485
  * At most one update per time window
1486
+ *
1487
+ * @example
1488
+ * ```ts
1489
+ * import { createThrottledUpdater } from 'ng-simple-state';
1490
+ *
1491
+ * // Create a throttled updater with 100ms delay
1492
+ * const { update, cancel } = createThrottledUpdater<MyState>(
1493
+ * (state) => store.setState(state),
1494
+ * 100
1495
+ * );
1496
+ *
1497
+ * // First call executes immediately, subsequent calls are throttled
1498
+ * update({ scrollPosition: 100 }); // Executes immediately
1499
+ * update({ scrollPosition: 150 }); // Queued
1500
+ * update({ scrollPosition: 200 }); // Replaces queued update
1501
+ *
1502
+ * // After 100ms, { scrollPosition: 200 } is applied
1503
+ *
1504
+ * // Cancel pending throttled update
1505
+ * cancel();
1506
+ ```
1567
1507
  */
1568
1508
  function createThrottledUpdater(updateFn, delay = 100) {
1569
1509
  let lastCall = 0;
@@ -1608,5 +1548,5 @@ function createThrottledUpdater(updateFn, delay = 100) {
1608
1548
  * Generated bundle index. Do not edit.
1609
1549
  */
1610
1550
 
1611
- export { NG_SIMPLE_STATE_PLUGINS, NG_SIMPLE_STATE_UNDO_REDO, NG_SIMPLE_STORE_CONFIG, NgSimpleStateBaseCommonStore, NgSimpleStateBaseRxjsStore, NgSimpleStateBaseSignalStore, NgSimpleStateBatchManager, NgSimpleStateDevTool, NgSimpleStateLocalStorage, NgSimpleStateModule, NgSimpleStateSessionStorage, StateTransaction, batchState, batchStateAsync, createDebouncedUpdater, createThrottledUpdater, persistPlugin, provideNgSimpleState, provideNgSimpleStatePlugins, undoRedoPlugin, withTransaction };
1551
+ export { NG_SIMPLE_STATE_PLUGINS, NG_SIMPLE_STATE_UNDO_REDO, NG_SIMPLE_STORE_CONFIG, NgSimpleStateBaseCommonStore, NgSimpleStateBaseRxjsStore, NgSimpleStateBaseSignalStore, NgSimpleStateDevTool, NgSimpleStateLocalStorage, NgSimpleStateModule, NgSimpleStateSessionStorage, StateTransaction, createDebouncedUpdater, createThrottledUpdater, persistPlugin, provideNgSimpleState, provideNgSimpleStatePlugins, undoRedoPlugin, withTransaction };
1612
1552
  //# sourceMappingURL=ng-simple-state.mjs.map