@angular-architects/ngrx-toolkit 19.0.2 → 19.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (31) hide show
  1. package/fesm2022/angular-architects-ngrx-toolkit-redux-connector.mjs +8 -8
  2. package/fesm2022/angular-architects-ngrx-toolkit-redux-connector.mjs.map +1 -1
  3. package/fesm2022/angular-architects-ngrx-toolkit.mjs +951 -504
  4. package/fesm2022/angular-architects-ngrx-toolkit.mjs.map +1 -1
  5. package/index.d.ts +18 -8
  6. package/lib/devtools/internal/glitch-tracker.service.d.ts +1 -1
  7. package/lib/devtools/internal/models.d.ts +2 -3
  8. package/lib/devtools/provide-devtools-config.d.ts +20 -0
  9. package/lib/devtools/update-state.d.ts +1 -2
  10. package/lib/devtools/with-devtools.d.ts +2 -8
  11. package/lib/immutable-state/deep-freeze.d.ts +11 -0
  12. package/lib/immutable-state/is-dev-mode.d.ts +1 -0
  13. package/lib/immutable-state/with-immutable-state.d.ts +60 -0
  14. package/lib/storage-sync/features/with-indexed-db.d.ts +2 -0
  15. package/lib/storage-sync/features/with-local-storage.d.ts +3 -0
  16. package/lib/storage-sync/internal/indexeddb.service.d.ts +29 -0
  17. package/lib/storage-sync/internal/local-storage.service.d.ts +8 -0
  18. package/lib/storage-sync/internal/models.d.ts +34 -0
  19. package/lib/storage-sync/internal/session-storage.service.d.ts +8 -0
  20. package/lib/storage-sync/with-storage-sync.d.ts +45 -0
  21. package/lib/with-call-state.d.ts +19 -2
  22. package/lib/with-conditional.d.ts +50 -0
  23. package/lib/with-data-service.d.ts +5 -5
  24. package/lib/with-feature-factory.d.ts +28 -0
  25. package/lib/with-pagination.d.ts +6 -32
  26. package/lib/with-redux.d.ts +3 -1
  27. package/lib/with-undo-redo.d.ts +1 -1
  28. package/package.json +3 -3
  29. package/redux-connector/src/lib/rxjs-interop/redux-method.d.ts +2 -2
  30. package/redux-connector/src/lib/signal-redux-store.d.ts +4 -16
  31. package/lib/with-storage-sync.d.ts +0 -52
@@ -1,16 +1,47 @@
1
- import { getState, signalStoreFeature, withMethods, withHooks, watchState, patchState as patchState$1, withState, withComputed, withProps } from '@ngrx/signals';
2
1
  import * as i0 from '@angular/core';
3
- import { inject, PLATFORM_ID, Injectable, signal, effect, computed, isSignal, untracked } from '@angular/core';
2
+ import { Injectable, InjectionToken, signal, effect, inject, PLATFORM_ID, computed, isSignal, untracked, isDevMode as isDevMode$1 } from '@angular/core';
3
+ import { watchState, getState, signalStoreFeature, withMethods, withHooks, patchState as patchState$1, withState, withComputed, withProps } from '@ngrx/signals';
4
4
  import { isPlatformBrowser, isPlatformServer } from '@angular/common';
5
5
  import { Subject } from 'rxjs';
6
- import { setAllEntities, addEntity, updateEntity, removeEntity } from '@ngrx/signals/entities';
6
+ import { removeEntity, setAllEntities, updateEntity, addEntity } from '@ngrx/signals/entities';
7
+
8
+ const DEVTOOLS_FEATURE = Symbol('DEVTOOLS_FEATURE');
9
+ function createDevtoolsFeature(options) {
10
+ return {
11
+ [DEVTOOLS_FEATURE]: true,
12
+ ...options,
13
+ };
14
+ }
7
15
 
8
16
  /**
9
- * Stub for DevTools integration. Can be used to disable DevTools in production.
17
+ * If multiple instances of the same SignalStore class
18
+ * exist, their devtool names are indexed.
19
+ *
20
+ * For example:
21
+ *
22
+ * ```typescript
23
+ * const Store = signalStore(
24
+ * withDevtools('flights')
25
+ * )
26
+ *
27
+ * const store1 = new Store(); // will show up as 'flights'
28
+ * const store2 = new Store(); // will show up as 'flights-1'
29
+ * ```
30
+ *
31
+ * With adding `withDisabledNameIndices` to the store:
32
+ * ```typescript
33
+ * const Store = signalStore(
34
+ * withDevtools('flights', withDisabledNameIndices())
35
+ * )
36
+ *
37
+ * const store1 = new Store(); // will show up as 'flights'
38
+ * const store2 = new Store(); //💥 throws an error
39
+ * ```
40
+ *
10
41
  */
11
- const withDevToolsStub = () => (store) => store;
12
-
13
- const currentActionNames = new Set();
42
+ function withDisabledNameIndices() {
43
+ return createDevtoolsFeature({ indexNames: false });
44
+ }
14
45
 
15
46
  function throwIfNull(obj) {
16
47
  if (obj === null || obj === undefined) {
@@ -19,6 +50,182 @@ function throwIfNull(obj) {
19
50
  return obj;
20
51
  }
21
52
 
53
+ /**
54
+ * Internal Service used by {@link withGlitchTracking}. It does not rely
55
+ * on `effect` as {@link DefaultTracker} does but uses the NgRx function
56
+ * `watchState` to track all state changes.
57
+ */
58
+ class GlitchTrackerService {
59
+ #stores = {};
60
+ #callback;
61
+ get stores() {
62
+ return Object.entries(this.#stores).reduce((acc, [id, { store }]) => {
63
+ acc[id] = store;
64
+ return acc;
65
+ }, {});
66
+ }
67
+ onChange(callback) {
68
+ this.#callback = callback;
69
+ }
70
+ removeStore(id) {
71
+ this.#stores = Object.entries(this.#stores).reduce((newStore, [storeId, value]) => {
72
+ if (storeId !== id) {
73
+ newStore[storeId] = value;
74
+ }
75
+ else {
76
+ value.destroyWatcher();
77
+ }
78
+ return newStore;
79
+ }, {});
80
+ throwIfNull(this.#callback)({});
81
+ }
82
+ track(id, store) {
83
+ const watcher = watchState(store, (state) => {
84
+ throwIfNull(this.#callback)({ [id]: state });
85
+ });
86
+ this.#stores[id] = { destroyWatcher: watcher.destroy, store };
87
+ }
88
+ notifyRenamedStore(id) {
89
+ if (Object.keys(this.#stores).includes(id) && this.#callback) {
90
+ this.#callback({ [id]: getState(this.#stores[id].store) });
91
+ }
92
+ }
93
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: GlitchTrackerService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
94
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: GlitchTrackerService, providedIn: 'root' }); }
95
+ }
96
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: GlitchTrackerService, decorators: [{
97
+ type: Injectable,
98
+ args: [{ providedIn: 'root' }]
99
+ }] });
100
+
101
+ /**
102
+ * It tracks all state changes of the State, including intermediary updates
103
+ * that are typically suppressed by Angular's glitch-free mechanism.
104
+ *
105
+ * This feature is especially useful for debugging.
106
+ *
107
+ * Example:
108
+ *
109
+ * ```typescript
110
+ * const Store = signalStore(
111
+ * { providedIn: 'root' },
112
+ * withState({ count: 0 }),
113
+ * withDevtools('counter', withGlitchTracking()),
114
+ * withMethods((store) => ({
115
+ * increase: () =>
116
+ * patchState(store, (value) => ({ count: value.count + 1 })),
117
+ * }))
118
+ * );
119
+ *
120
+ * // would show up in the DevTools with value 0
121
+ * const store = inject(Store);
122
+ *
123
+ * store.increase(); // would show up in the DevTools with value 1
124
+ * store.increase(); // would show up in the DevTools with value 2
125
+ * store.increase(); // would show up in the DevTools with value 3
126
+ * ```
127
+ *
128
+ * Without `withGlitchTracking`, the DevTools would only show the final value of 3.
129
+ */
130
+ function withGlitchTracking() {
131
+ return createDevtoolsFeature({ tracker: GlitchTrackerService });
132
+ }
133
+
134
+ /**
135
+ * Allows you to define a function to map the state.
136
+ *
137
+ * It is needed for huge states, that slows down the Devtools and where
138
+ * you don't need to see the whole state or other reasons.
139
+ *
140
+ * Example:
141
+ *
142
+ * ```typescript
143
+ * const initialState = {
144
+ * id: 1,
145
+ * email: 'john.list@host.com',
146
+ * name: 'John List',
147
+ * enteredPassword: ''
148
+ * }
149
+ *
150
+ * const Store = signalStore(
151
+ * withState(initialState),
152
+ * withDevtools(
153
+ * 'user',
154
+ * withMapper(state => ({...state, enteredPassword: '***' }))
155
+ * )
156
+ * )
157
+ * ```
158
+ *
159
+ * @param map function which maps the state
160
+ */
161
+ function withMapper(map) {
162
+ return createDevtoolsFeature({ map: map });
163
+ }
164
+
165
+ /**
166
+ * Provides the configuration options for connecting to the Redux DevTools Extension.
167
+ */
168
+ function provideDevtoolsConfig(config) {
169
+ return {
170
+ provide: REDUX_DEVTOOLS_CONFIG,
171
+ useValue: config,
172
+ };
173
+ }
174
+ /**
175
+ * Injection token for the configuration options for connecting to the Redux DevTools Extension.
176
+ */
177
+ const REDUX_DEVTOOLS_CONFIG = new InjectionToken('ReduxDevtoolsConfig');
178
+
179
+ class DefaultTracker {
180
+ #stores = signal({});
181
+ get stores() {
182
+ return this.#stores();
183
+ }
184
+ #trackCallback;
185
+ #trackingEffect = effect(() => {
186
+ if (this.#trackCallback === undefined) {
187
+ throw new Error('no callback function defined');
188
+ }
189
+ const stores = this.#stores();
190
+ const fullState = Object.entries(stores).reduce((acc, [id, store]) => {
191
+ return { ...acc, [id]: getState(store) };
192
+ }, {});
193
+ this.#trackCallback(fullState);
194
+ });
195
+ track(id, store) {
196
+ this.#stores.update((value) => ({
197
+ ...value,
198
+ [id]: store,
199
+ }));
200
+ }
201
+ onChange(callback) {
202
+ this.#trackCallback = callback;
203
+ }
204
+ removeStore(id) {
205
+ this.#stores.update((stores) => Object.entries(stores).reduce((newStore, [storeId, state]) => {
206
+ if (storeId !== id) {
207
+ newStore[storeId] = state;
208
+ }
209
+ return newStore;
210
+ }, {}));
211
+ }
212
+ notifyRenamedStore(id) {
213
+ if (this.#stores()[id]) {
214
+ this.#stores.update((stores) => {
215
+ return { ...stores };
216
+ });
217
+ }
218
+ }
219
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: DefaultTracker, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
220
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: DefaultTracker, providedIn: 'root' }); }
221
+ }
222
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: DefaultTracker, decorators: [{
223
+ type: Injectable,
224
+ args: [{ providedIn: 'root' }]
225
+ }] });
226
+
227
+ const currentActionNames = new Set();
228
+
22
229
  const dummyConnection = {
23
230
  send: () => void true,
24
231
  };
@@ -41,6 +248,10 @@ class DevtoolsSyncer {
41
248
  #stores = {};
42
249
  #isBrowser = isPlatformBrowser(inject(PLATFORM_ID));
43
250
  #trackers = [];
251
+ #devtoolsConfig = {
252
+ name: 'NgRx SignalStore',
253
+ ...inject(REDUX_DEVTOOLS_CONFIG, { optional: true }),
254
+ };
44
255
  /**
45
256
  * Maintains the current states of all stores to avoid conflicts
46
257
  * between glitch-free and glitched trackers when used simultaneously.
@@ -59,19 +270,13 @@ class DevtoolsSyncer {
59
270
  #currentId = 1;
60
271
  #connection = this.#isBrowser
61
272
  ? window.__REDUX_DEVTOOLS_EXTENSION__
62
- ? window.__REDUX_DEVTOOLS_EXTENSION__.connect({
63
- name: 'NgRx SignalStore',
64
- })
273
+ ? window.__REDUX_DEVTOOLS_EXTENSION__.connect(this.#devtoolsConfig)
65
274
  : dummyConnection
66
275
  : dummyConnection;
67
276
  constructor() {
68
277
  if (!this.#isBrowser) {
69
278
  return;
70
279
  }
71
- const isToolkitAvailable = Boolean(window.__REDUX_DEVTOOLS_EXTENSION__);
72
- if (!isToolkitAvailable) {
73
- console.info('NgRx Toolkit/DevTools: Redux DevTools Extension is not available.');
74
- }
75
280
  }
76
281
  ngOnDestroy() {
77
282
  currentActionNames.clear();
@@ -106,7 +311,9 @@ class DevtoolsSyncer {
106
311
  let storeName = name;
107
312
  const names = Object.values(this.#stores).map((store) => store.name);
108
313
  if (names.includes(storeName)) {
109
- const { options } = throwIfNull(Object.values(this.#stores).find((store) => store.name === storeName));
314
+ // const { options } = throwIfNull(
315
+ // Object.values(this.#stores).find((store) => store.name === storeName)
316
+ // );
110
317
  if (!options.indexNames) {
111
318
  throw new Error(`An instance of the store ${storeName} already exists. \
112
319
  Enable automatic indexing via withDevTools('${storeName}', { indexNames: true }), or rename it upon instantiation.`);
@@ -166,65 +373,17 @@ Enable automatic indexing via withDevTools('${storeName}', { indexNames: true })
166
373
  }, {});
167
374
  this.#trackers.forEach((tracker) => tracker.notifyRenamedStore(id));
168
375
  }
169
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.0.5", ngImport: i0, type: DevtoolsSyncer, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
170
- static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.0.5", ngImport: i0, type: DevtoolsSyncer, providedIn: 'root' }); }
376
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: DevtoolsSyncer, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
377
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: DevtoolsSyncer, providedIn: 'root' }); }
171
378
  }
172
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.0.5", ngImport: i0, type: DevtoolsSyncer, decorators: [{
379
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: DevtoolsSyncer, decorators: [{
173
380
  type: Injectable,
174
381
  args: [{ providedIn: 'root' }]
175
382
  }], ctorParameters: () => [] });
176
383
 
177
- class DefaultTracker {
178
- #stores = signal({});
179
- get stores() {
180
- return this.#stores();
181
- }
182
- #trackCallback;
183
- #trackingEffect = effect(() => {
184
- if (this.#trackCallback === undefined) {
185
- throw new Error('no callback function defined');
186
- }
187
- const stores = this.#stores();
188
- const fullState = Object.entries(stores).reduce((acc, [id, store]) => {
189
- return { ...acc, [id]: getState(store) };
190
- }, {});
191
- this.#trackCallback(fullState);
192
- });
193
- track(id, store) {
194
- this.#stores.update((value) => ({
195
- ...value,
196
- [id]: store,
197
- }));
198
- }
199
- onChange(callback) {
200
- this.#trackCallback = callback;
201
- }
202
- removeStore(id) {
203
- this.#stores.update((stores) => Object.entries(stores).reduce((newStore, [storeId, state]) => {
204
- if (storeId !== id) {
205
- newStore[storeId] = state;
206
- }
207
- return newStore;
208
- }, {}));
209
- }
210
- notifyRenamedStore(id) {
211
- if (this.#stores()[id]) {
212
- this.#stores.update((stores) => {
213
- return { ...stores };
214
- });
215
- }
216
- }
217
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.0.5", ngImport: i0, type: DefaultTracker, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
218
- static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.0.5", ngImport: i0, type: DefaultTracker, providedIn: 'root' }); }
219
- }
220
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.0.5", ngImport: i0, type: DefaultTracker, decorators: [{
221
- type: Injectable,
222
- args: [{ providedIn: 'root' }]
223
- }] });
224
-
225
- const existingNames = new Map();
226
384
  const renameDevtoolsMethodName = '___renameDevtoolsName';
227
385
  const uniqueDevtoolsId = '___uniqueDevtoolsId';
386
+ const EXISTING_NAMES = new InjectionToken('Array contain existing names for the signal stores', { factory: () => [], providedIn: 'root' });
228
387
  /**
229
388
  * Adds this store as a feature state to the Redux DevTools.
230
389
  *
@@ -239,10 +398,6 @@ const uniqueDevtoolsId = '___uniqueDevtoolsId';
239
398
  * @param features features to extend or modify the behavior of the Devtools
240
399
  */
241
400
  function withDevtools(name, ...features) {
242
- if (existingNames.has(name)) {
243
- throw new Error(`The store "${name}" has already been registered in the DevTools. Duplicate registration is not allowed.`);
244
- }
245
- existingNames.set(name, true);
246
401
  return signalStoreFeature(withMethods(() => {
247
402
  const syncer = inject(DevtoolsSyncer);
248
403
  const id = syncer.getNextId();
@@ -273,154 +428,17 @@ function withDevtools(name, ...features) {
273
428
  }));
274
429
  }
275
430
 
276
- const DEVTOOLS_FEATURE = Symbol('DEVTOOLS_FEATURE');
277
- function createDevtoolsFeature(options) {
278
- return {
279
- [DEVTOOLS_FEATURE]: true,
280
- ...options,
281
- };
282
- }
283
-
284
- /**
285
- * If multiple instances of the same SignalStore class
286
- * exist, their devtool names are indexed.
287
- *
288
- * For example:
289
- *
290
- * ```typescript
291
- * const Store = signalStore(
292
- * withDevtools('flights')
293
- * )
294
- *
295
- * const store1 = new Store(); // will show up as 'flights'
296
- * const store2 = new Store(); // will show up as 'flights-1'
297
- * ```
298
- *
299
- * With adding `withDisabledNameIndices` to the store:
300
- * ```typescript
301
- * const Store = signalStore(
302
- * withDevtools('flights', withDisabledNameIndices())
303
- * )
304
- *
305
- * const store1 = new Store(); // will show up as 'flights'
306
- * const store2 = new Store(); //💥 throws an error
307
- * ```
308
- *
309
- */
310
- function withDisabledNameIndices() {
311
- return createDevtoolsFeature({ indexNames: false });
312
- }
313
-
314
- /**
315
- * Allows you to define a function to map the state.
316
- *
317
- * It is needed for huge states, that slows down the Devtools and where
318
- * you don't need to see the whole state or other reasons.
319
- *
320
- * Example:
321
- *
322
- * ```typescript
323
- * const initialState = {
324
- * id: 1,
325
- * email: 'john.list@host.com',
326
- * name: 'John List',
327
- * enteredPassword: ''
328
- * }
329
- *
330
- * const Store = signalStore(
331
- * withState(initialState),
332
- * withDevtools(
333
- * 'user',
334
- * withMapper(state => ({...state, enteredPassword: '***' }))
335
- * )
336
- * )
337
- * ```
338
- *
339
- * @param map function which maps the state
340
- */
341
- function withMapper(map) {
342
- return createDevtoolsFeature({ map: map });
343
- }
344
-
345
431
  /**
346
- * Internal Service used by {@link withGlitchTracking}. It does not rely
347
- * on `effect` as {@link DefaultTracker} does but uses the NgRx function
348
- * `watchState` to track all state changes.
432
+ * Renames the name of a store how it appears in the Devtools.
433
+ * @param store instance of the SignalStore
434
+ * @param newName new name for the Devtools
349
435
  */
350
- class GlitchTrackerService {
351
- #stores = {};
352
- #callback;
353
- get stores() {
354
- return Object.entries(this.#stores).reduce((acc, [id, { store }]) => {
355
- acc[id] = store;
356
- return acc;
357
- }, {});
358
- }
359
- onChange(callback) {
360
- this.#callback = callback;
361
- }
362
- removeStore(id) {
363
- this.#stores = Object.entries(this.#stores).reduce((newStore, [storeId, value]) => {
364
- if (storeId !== id) {
365
- newStore[storeId] = value;
366
- }
367
- else {
368
- value.destroyWatcher();
369
- }
370
- return newStore;
371
- }, {});
372
- throwIfNull(this.#callback)({});
373
- }
374
- track(id, store) {
375
- const watcher = watchState(store, (state) => {
376
- throwIfNull(this.#callback)({ [id]: state });
377
- });
378
- this.#stores[id] = { destroyWatcher: watcher.destroy, store };
379
- }
380
- notifyRenamedStore(id) {
381
- if (Object.keys(this.#stores).includes(id) && this.#callback) {
382
- this.#callback({ [id]: getState(this.#stores[id].store) });
383
- }
436
+ function renameDevtoolsName(store, newName) {
437
+ const renameMethod = store[renameDevtoolsMethodName];
438
+ if (!renameMethod) {
439
+ throw new Error("Devtools extensions haven't been added to this store.");
384
440
  }
385
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.0.5", ngImport: i0, type: GlitchTrackerService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
386
- static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.0.5", ngImport: i0, type: GlitchTrackerService, providedIn: 'root' }); }
387
- }
388
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.0.5", ngImport: i0, type: GlitchTrackerService, decorators: [{
389
- type: Injectable,
390
- args: [{ providedIn: 'root' }]
391
- }] });
392
-
393
- /**
394
- * It tracks all state changes of the State, including intermediary updates
395
- * that are typically suppressed by Angular's glitch-free mechanism.
396
- *
397
- * This feature is especially useful for debugging.
398
- *
399
- * Example:
400
- *
401
- * ```typescript
402
- * const Store = signalStore(
403
- * { providedIn: 'root' },
404
- * withState({ count: 0 }),
405
- * withDevtools('counter', withGlitchTracking()),
406
- * withMethods((store) => ({
407
- * increase: () =>
408
- * patchState(store, (value) => ({ count: value.count + 1 })),
409
- * }))
410
- * );
411
- *
412
- * // would show up in the DevTools with value 0
413
- * const store = inject(Store);
414
- *
415
- * store.increase(); // would show up in the DevTools with value 1
416
- * store.increase(); // would show up in the DevTools with value 2
417
- * store.increase(); // would show up in the DevTools with value 3
418
- * ```
419
- *
420
- * Without `withGlitchTracking`, the DevTools would only show the final value of 3.
421
- */
422
- function withGlitchTracking() {
423
- return createDevtoolsFeature({ tracker: GlitchTrackerService });
441
+ renameMethod(newName);
424
442
  }
425
443
 
426
444
  /**
@@ -442,17 +460,9 @@ function updateState(stateSource, action, ...updaters) {
442
460
  }
443
461
 
444
462
  /**
445
- * Renames the name of a store how it appears in the Devtools.
446
- * @param store instance of the SignalStore
447
- * @param newName new name for the Devtools
463
+ * Stub for DevTools integration. Can be used to disable DevTools in production.
448
464
  */
449
- function renameDevtoolsName(store, newName) {
450
- const renameMethod = store[renameDevtoolsMethodName];
451
- if (!renameMethod) {
452
- throw new Error("Devtools extensions haven't been added to this store.");
453
- }
454
- renameMethod(newName);
455
- }
465
+ const withDevToolsStub = () => (store) => store;
456
466
 
457
467
  function assertActionFnSpecs(obj) {
458
468
  if (!obj || typeof obj !== 'object') {
@@ -510,6 +520,8 @@ function createReducer(reducerFactory) {
510
520
  return reducerFactory;
511
521
  }
512
522
  /**
523
+ * @deprecated Use NgRx's `@ngrx/signals/events` starting in 19.2
524
+ *
513
525
  * Creates the effects function to separate the effects logic into another file.
514
526
  *
515
527
  * ```typescript
@@ -645,23 +657,63 @@ function withRedux(redux) {
645
657
  const { methods } = processRedux(redux.actions, redux.reducer, redux.effects, store);
646
658
  return {
647
659
  ...store,
648
- methods,
660
+ methods: { ...store.methods, ...methods },
649
661
  };
650
662
  };
651
663
  }
652
664
 
653
- function getCallStateKeys(config) {
654
- const prop = config?.collection;
665
+ function deriveCallStateKeys(collection) {
655
666
  return {
656
- callStateKey: prop ? `${config.collection}CallState` : 'callState',
657
- loadingKey: prop ? `${config.collection}Loading` : 'loading',
658
- loadedKey: prop ? `${config.collection}Loaded` : 'loaded',
659
- errorKey: prop ? `${config.collection}Error` : 'error',
667
+ callStateKey: collection ? `${collection}CallState` : 'callState',
668
+ loadingKey: collection ? `${collection}Loading` : 'loading',
669
+ loadedKey: collection ? `${collection}Loaded` : 'loaded',
670
+ errorKey: collection ? `${collection}Error` : 'error',
660
671
  };
661
672
  }
673
+ function getCallStateKeys(config) {
674
+ const prop = config?.collection;
675
+ return deriveCallStateKeys(prop);
676
+ }
677
+ function getCollectionArray(config) {
678
+ return 'collections' in config
679
+ ? config.collections
680
+ : 'collection' in config && config.collection
681
+ ? [config.collection]
682
+ : undefined;
683
+ }
662
684
  function withCallState(config) {
663
- const { callStateKey, errorKey, loadedKey, loadingKey } = getCallStateKeys(config);
664
- return signalStoreFeature(withState({ [callStateKey]: 'init' }), withComputed((state) => {
685
+ return signalStoreFeature(withState(() => {
686
+ if (!config) {
687
+ return { callState: 'init' };
688
+ }
689
+ const collections = getCollectionArray(config);
690
+ if (collections) {
691
+ return collections.reduce((acc, cur) => ({
692
+ ...acc,
693
+ ...{ [cur ? `${cur}CallState` : 'callState']: 'init' },
694
+ }), {});
695
+ }
696
+ return { callState: 'init' };
697
+ }), withComputed((state) => {
698
+ if (config) {
699
+ const collections = getCollectionArray(config);
700
+ if (collections) {
701
+ return collections.reduce((acc, cur) => {
702
+ const { callStateKey, errorKey, loadedKey, loadingKey } = deriveCallStateKeys(cur);
703
+ const callState = state[callStateKey];
704
+ return {
705
+ ...acc,
706
+ [loadingKey]: computed(() => callState() === 'loading'),
707
+ [loadedKey]: computed(() => callState() === 'loaded'),
708
+ [errorKey]: computed(() => {
709
+ const v = callState();
710
+ return typeof v === 'object' ? v.error : null;
711
+ }),
712
+ };
713
+ }, {});
714
+ }
715
+ }
716
+ const { callStateKey, errorKey, loadedKey, loadingKey } = deriveCallStateKeys();
665
717
  const callState = state[callStateKey];
666
718
  return {
667
719
  [loadingKey]: computed(() => callState() === 'loading'),
@@ -911,252 +963,60 @@ function withDataService(options) {
911
963
  }));
912
964
  }
913
965
 
914
- const defaultOptions = {
915
- maxStackSize: 100,
916
- keys: [],
917
- skip: 0,
918
- };
919
- function getUndoRedoKeys(collections) {
920
- if (collections) {
921
- return collections.flatMap((c) => [
922
- `${c}EntityMap`,
923
- `${c}Ids`,
924
- `selected${capitalize(c)}Ids`,
925
- `${c}Filter`,
926
- ]);
927
- }
928
- return ['entityMap', 'ids', 'selectedIds', 'filter'];
966
+ /** With pagination comes in two flavors the first one is local pagination or in memory pagination. For example we have 2000 items which we want
967
+ * to display in a table and the response payload is small enough to be stored in the memory. But we can not display all 2000 items at once
968
+ * so we need to paginate the data. The second flavor is server side pagination where the response payload is too large to be stored in the memory
969
+ * and we need to fetch the data from the server in chunks. In the second case we 'could' also cache the data in the memory but that could lead to
970
+ * other problems like memory leaks and stale data. So we will not cache the data in the memory in the second case.
971
+ * This feature implements the local pagination.
972
+ */
973
+ function withPagination(options) {
974
+ const { pageKey, pageSizeKey, entitiesKey, selectedPageEntitiesKey, totalCountKey, pageCountKey, pageNavigationArrayMaxKey, pageNavigationArrayKey, hasNextPageKey, hasPreviousPageKey, } = createPaginationKeys(options);
975
+ return signalStoreFeature(withState({
976
+ [pageKey]: 0,
977
+ [pageSizeKey]: 10,
978
+ [pageNavigationArrayMaxKey]: 7,
979
+ }), withComputed((store) => {
980
+ const entities = store[entitiesKey];
981
+ const page = store[pageKey];
982
+ const pageSize = store[pageSizeKey];
983
+ const pageNavigationArrayMax = store[pageNavigationArrayMaxKey];
984
+ return {
985
+ // The derived enitites which are displayed on the current page
986
+ [selectedPageEntitiesKey]: computed(() => {
987
+ const pageSizeValue = pageSize();
988
+ const pageValue = page();
989
+ return entities().slice(pageValue * pageSizeValue, (pageValue + 1) * pageSizeValue);
990
+ }),
991
+ [totalCountKey]: computed(() => entities().length),
992
+ [pageCountKey]: computed(() => {
993
+ const totalCountValue = entities().length;
994
+ const pageSizeValue = pageSize();
995
+ if (totalCountValue === 0) {
996
+ return 0;
997
+ }
998
+ return Math.ceil(totalCountValue / pageSizeValue);
999
+ }),
1000
+ [pageNavigationArrayKey]: computed(() => createPageArray(page(), pageSize(), entities().length, pageNavigationArrayMax())),
1001
+ [hasNextPageKey]: computed(() => {
1002
+ return page() < pageSize();
1003
+ }),
1004
+ [hasPreviousPageKey]: computed(() => {
1005
+ return page() > 1;
1006
+ }),
1007
+ };
1008
+ }));
929
1009
  }
930
- function withUndoRedo(options) {
931
- let previous = null;
932
- let skipOnce = false;
933
- const normalized = {
934
- ...defaultOptions,
935
- ...options,
1010
+ function gotoPage(page, options) {
1011
+ const { pageKey } = createPaginationKeys(options);
1012
+ return {
1013
+ [pageKey]: page,
936
1014
  };
937
- //
938
- // Design Decision: This feature has its own
939
- // internal state.
940
- //
941
- const undoStack = [];
942
- const redoStack = [];
943
- const canUndo = signal(false);
944
- const canRedo = signal(false);
945
- const updateInternal = () => {
946
- canUndo.set(undoStack.length !== 0);
947
- canRedo.set(redoStack.length !== 0);
948
- };
949
- const keys = [...getUndoRedoKeys(normalized.collections), ...normalized.keys];
950
- return signalStoreFeature(withComputed(() => ({
951
- canUndo: canUndo.asReadonly(),
952
- canRedo: canRedo.asReadonly(),
953
- })), withMethods((store) => ({
954
- undo() {
955
- const item = undoStack.pop();
956
- if (item && previous) {
957
- redoStack.push(previous);
958
- }
959
- if (item) {
960
- skipOnce = true;
961
- patchState$1(store, item);
962
- previous = item;
963
- }
964
- updateInternal();
965
- },
966
- redo() {
967
- const item = redoStack.pop();
968
- if (item && previous) {
969
- undoStack.push(previous);
970
- }
971
- if (item) {
972
- skipOnce = true;
973
- patchState$1(store, item);
974
- previous = item;
975
- }
976
- updateInternal();
977
- },
978
- clearStack() {
979
- undoStack.splice(0);
980
- redoStack.splice(0);
981
- updateInternal();
982
- },
983
- })), withHooks({
984
- onInit(store) {
985
- effect(() => {
986
- const cand = keys.reduce((acc, key) => {
987
- const s = store[key];
988
- if (s && isSignal(s)) {
989
- return {
990
- ...acc,
991
- [key]: s(),
992
- };
993
- }
994
- return acc;
995
- }, {});
996
- if (normalized.skip > 0) {
997
- normalized.skip--;
998
- return;
999
- }
1000
- if (skipOnce) {
1001
- skipOnce = false;
1002
- return;
1003
- }
1004
- //
1005
- // Deep Comparison to prevent duplicated entries
1006
- // on the stack. This can e.g. happen after an undo
1007
- // if the component sends back the undone filter
1008
- // to the store.
1009
- //
1010
- if (JSON.stringify(cand) === JSON.stringify(previous)) {
1011
- return;
1012
- }
1013
- // Clear redoStack after recorded action
1014
- redoStack.splice(0);
1015
- if (previous) {
1016
- undoStack.push(previous);
1017
- }
1018
- if (redoStack.length > normalized.maxStackSize) {
1019
- undoStack.unshift();
1020
- }
1021
- previous = cand;
1022
- // Don't propogate current reactive context
1023
- untracked(() => updateInternal());
1024
- });
1025
- },
1026
- }));
1027
- }
1028
-
1029
- const NOOP = () => void true;
1030
- const StorageSyncStub = {
1031
- clearStorage: NOOP,
1032
- readFromStorage: NOOP,
1033
- writeToStorage: NOOP,
1034
- };
1035
- function withStorageSync(configOrKey) {
1036
- const { key, autoSync = true, select = (state) => state, parse = JSON.parse, stringify = JSON.stringify, storage: storageFactory = () => localStorage, } = typeof configOrKey === 'string' ? { key: configOrKey } : configOrKey;
1037
- return signalStoreFeature(withMethods((store, platformId = inject(PLATFORM_ID)) => {
1038
- if (isPlatformServer(platformId)) {
1039
- console.warn(`'withStorageSync' provides non-functional implementation due to server-side execution`);
1040
- return StorageSyncStub;
1041
- }
1042
- const storage = storageFactory();
1043
- return {
1044
- /**
1045
- * Removes the item stored in storage.
1046
- */
1047
- clearStorage() {
1048
- storage.removeItem(key);
1049
- },
1050
- /**
1051
- * Reads item from storage and patches the state.
1052
- */
1053
- readFromStorage() {
1054
- const stateString = storage.getItem(key);
1055
- if (stateString) {
1056
- patchState$1(store, parse(stateString));
1057
- }
1058
- },
1059
- /**
1060
- * Writes selected portion to storage.
1061
- */
1062
- writeToStorage() {
1063
- const slicedState = select(getState(store));
1064
- storage.setItem(key, stringify(slicedState));
1065
- },
1066
- };
1067
- }), withHooks({
1068
- onInit(store, platformId = inject(PLATFORM_ID)) {
1069
- if (isPlatformServer(platformId)) {
1070
- return;
1071
- }
1072
- if (autoSync) {
1073
- store.readFromStorage();
1074
- effect(() => {
1075
- store.writeToStorage();
1076
- });
1077
- }
1078
- },
1079
- }));
1080
- }
1081
-
1082
- /** With pagination comes in two flavors the first one is local pagination or in memory pagination. For example we have 2000 items which we want
1083
- * to display in a table and the response payload is small enough to be stored in the memory. But we can not display all 2000 items at once
1084
- * so we need to paginate the data. The second flavor is server side pagination where the response payload is too large to be stored in the memory
1085
- * and we need to fetch the data from the server in chunks. In the second case we 'could' also cache the data in the memory but that could lead to
1086
- * other problems like memory leaks and stale data. So we will not cache the data in the memory in the second case.
1087
- * This feature implements the local pagination.
1088
- */
1089
- function withPagination(options) {
1090
- const { pageKey, pageSizeKey, entitiesKey, selectedPageEntitiesKey, totalCountKey, pageCountKey, pageNavigationArrayMaxKey, pageNavigationArrayKey, setPageSizeKey, nextPageKey, previousPageKey, lastPageKey, firstPageKey, gotoPageKey, hasNextPageKey, hasPreviousPageKey, } = createPaginationKeys(options);
1091
- return signalStoreFeature(withState({
1092
- [pageKey]: 0,
1093
- [pageSizeKey]: 10,
1094
- [pageNavigationArrayMaxKey]: 7,
1095
- }), withComputed((store) => {
1096
- const entities = store[entitiesKey];
1097
- const page = store[pageKey];
1098
- const pageSize = store[pageSizeKey];
1099
- const pageNavigationArrayMax = store[pageNavigationArrayMaxKey];
1100
- return {
1101
- // The derived enitites which are displayed on the current page
1102
- [selectedPageEntitiesKey]: computed(() => {
1103
- const pageSizeValue = pageSize();
1104
- const pageValue = page();
1105
- return entities().slice(pageValue * pageSizeValue, (pageValue + 1) * pageSizeValue);
1106
- }),
1107
- [totalCountKey]: computed(() => entities().length),
1108
- [pageCountKey]: computed(() => {
1109
- const totalCountValue = entities().length;
1110
- const pageSizeValue = pageSize();
1111
- if (totalCountValue === 0) {
1112
- return 0;
1113
- }
1114
- return Math.ceil(totalCountValue / pageSizeValue);
1115
- }),
1116
- [pageNavigationArrayKey]: computed(() => createPageArray(page(), pageSize(), entities().length, pageNavigationArrayMax())),
1117
- [hasNextPageKey]: computed(() => {
1118
- return page() < pageSize();
1119
- }),
1120
- [hasPreviousPageKey]: computed(() => {
1121
- return page() > 1;
1122
- }),
1123
- };
1124
- }), withMethods((store) => {
1125
- return {
1126
- [setPageSizeKey]: (size) => {
1127
- patchState$1(store, setPageSize(size, options));
1128
- },
1129
- [nextPageKey]: () => {
1130
- patchState$1(store, nextPage(options));
1131
- },
1132
- [previousPageKey]: () => {
1133
- patchState$1(store, previousPage(options));
1134
- },
1135
- [lastPageKey]: () => {
1136
- const lastPage = store[pageCountKey]();
1137
- if (lastPage === 0)
1138
- return;
1139
- patchState$1(store, gotoPage(lastPage - 1, options));
1140
- },
1141
- [firstPageKey]: () => {
1142
- patchState$1(store, firstPage());
1143
- },
1144
- [gotoPageKey]: (page) => {
1145
- patchState$1(store, gotoPage(page, options));
1146
- },
1147
- };
1148
- }));
1149
- }
1150
- function gotoPage(page, options) {
1151
- const { pageKey } = createPaginationKeys(options);
1152
- return {
1153
- [pageKey]: page,
1154
- };
1155
- }
1156
- function setPageSize(pageSize, options) {
1157
- const { pageSizeKey } = createPaginationKeys(options);
1158
- return {
1159
- [pageSizeKey]: pageSize,
1015
+ }
1016
+ function setPageSize(pageSize, options) {
1017
+ const { pageSizeKey } = createPaginationKeys(options);
1018
+ return {
1019
+ [pageSizeKey]: pageSize,
1160
1020
  };
1161
1021
  }
1162
1022
  function nextPage(options) {
@@ -1208,24 +1068,6 @@ function createPaginationKeys(options) {
1208
1068
  const pageNavigationArrayKey = options?.collection
1209
1069
  ? `${options.collection}PageNavigationArray`
1210
1070
  : 'pageNavigationArray';
1211
- const setPageSizeKey = options?.collection
1212
- ? `set${capitalize(options.collection)}PageSize`
1213
- : 'setPageSize';
1214
- const nextPageKey = options?.collection
1215
- ? `next${capitalize(options.collection)}Page`
1216
- : 'nextPage';
1217
- const previousPageKey = options?.collection
1218
- ? `previous${capitalize(options.collection)}Page`
1219
- : 'previousPage';
1220
- const lastPageKey = options?.collection
1221
- ? `last${capitalize(options.collection)}Page`
1222
- : 'lastPage';
1223
- const firstPageKey = options?.collection
1224
- ? `first${capitalize(options.collection)}Page`
1225
- : 'firstPage';
1226
- const gotoPageKey = options?.collection
1227
- ? `goto${capitalize(options.collection)}Page`
1228
- : 'gotoPage';
1229
1071
  const hasNextPageKey = options?.collection
1230
1072
  ? `hasNext${capitalize(options.collection)}Page`
1231
1073
  : 'hasNextPage';
@@ -1241,12 +1083,6 @@ function createPaginationKeys(options) {
1241
1083
  pageCountKey,
1242
1084
  pageNavigationArrayKey,
1243
1085
  pageNavigationArrayMaxKey,
1244
- setPageSizeKey,
1245
- nextPageKey,
1246
- previousPageKey,
1247
- lastPageKey,
1248
- firstPageKey,
1249
- gotoPageKey,
1250
1086
  hasNextPageKey,
1251
1087
  hasPreviousPageKey,
1252
1088
  };
@@ -1323,9 +1159,620 @@ function setResetState(store, state) {
1323
1159
  store.__setResetState__(state);
1324
1160
  }
1325
1161
 
1162
+ const defaultOptions = {
1163
+ maxStackSize: 100,
1164
+ keys: [],
1165
+ skip: 0,
1166
+ };
1167
+ function getUndoRedoKeys(collections) {
1168
+ if (collections) {
1169
+ return collections.flatMap((c) => [
1170
+ `${c}EntityMap`,
1171
+ `${c}Ids`,
1172
+ `selected${capitalize(c)}Ids`,
1173
+ `${c}Filter`,
1174
+ ]);
1175
+ }
1176
+ return ['entityMap', 'ids', 'selectedIds', 'filter'];
1177
+ }
1178
+ function withUndoRedo(options) {
1179
+ let previous = null;
1180
+ let skipOnce = false;
1181
+ const normalized = {
1182
+ ...defaultOptions,
1183
+ ...options,
1184
+ };
1185
+ //
1186
+ // Design Decision: This feature has its own
1187
+ // internal state.
1188
+ //
1189
+ const undoStack = [];
1190
+ const redoStack = [];
1191
+ const canUndo = signal(false);
1192
+ const canRedo = signal(false);
1193
+ const updateInternal = () => {
1194
+ canUndo.set(undoStack.length !== 0);
1195
+ canRedo.set(redoStack.length !== 0);
1196
+ };
1197
+ const keys = [...getUndoRedoKeys(normalized.collections), ...normalized.keys];
1198
+ return signalStoreFeature(withComputed(() => ({
1199
+ canUndo: canUndo.asReadonly(),
1200
+ canRedo: canRedo.asReadonly(),
1201
+ })), withMethods((store) => ({
1202
+ undo() {
1203
+ const item = undoStack.pop();
1204
+ if (item && previous) {
1205
+ redoStack.push(previous);
1206
+ }
1207
+ if (item) {
1208
+ skipOnce = true;
1209
+ patchState$1(store, item);
1210
+ previous = item;
1211
+ }
1212
+ updateInternal();
1213
+ },
1214
+ redo() {
1215
+ const item = redoStack.pop();
1216
+ if (item && previous) {
1217
+ undoStack.push(previous);
1218
+ }
1219
+ if (item) {
1220
+ skipOnce = true;
1221
+ patchState$1(store, item);
1222
+ previous = item;
1223
+ }
1224
+ updateInternal();
1225
+ },
1226
+ clearStack() {
1227
+ undoStack.splice(0);
1228
+ redoStack.splice(0);
1229
+ previous = null;
1230
+ updateInternal();
1231
+ },
1232
+ })), withHooks({
1233
+ onInit(store) {
1234
+ effect(() => {
1235
+ const cand = keys.reduce((acc, key) => {
1236
+ const s = store[key];
1237
+ if (s && isSignal(s)) {
1238
+ return {
1239
+ ...acc,
1240
+ [key]: s(),
1241
+ };
1242
+ }
1243
+ return acc;
1244
+ }, {});
1245
+ if (normalized.skip > 0) {
1246
+ normalized.skip--;
1247
+ return;
1248
+ }
1249
+ if (skipOnce) {
1250
+ skipOnce = false;
1251
+ return;
1252
+ }
1253
+ //
1254
+ // Deep Comparison to prevent duplicated entries
1255
+ // on the stack. This can e.g. happen after an undo
1256
+ // if the component sends back the undone filter
1257
+ // to the store.
1258
+ //
1259
+ if (JSON.stringify(cand) === JSON.stringify(previous)) {
1260
+ return;
1261
+ }
1262
+ // Clear redoStack after recorded action
1263
+ redoStack.splice(0);
1264
+ if (previous) {
1265
+ undoStack.push(previous);
1266
+ }
1267
+ if (redoStack.length > normalized.maxStackSize) {
1268
+ undoStack.unshift();
1269
+ }
1270
+ previous = cand;
1271
+ // Don't propogate current reactive context
1272
+ untracked(() => updateInternal());
1273
+ });
1274
+ },
1275
+ }));
1276
+ }
1277
+
1278
+ /**
1279
+ * Deep freezes a state object along its properties with primitive values
1280
+ * on the first level.
1281
+ *
1282
+ * The reason for this is that the final state is a merge of all
1283
+ * root properties of all states, i.e. `withState`,....
1284
+ *
1285
+ * Since the root object will not be part of the state (shadow clone),
1286
+ * we are not freezing it.
1287
+ */
1288
+ function deepFreeze(target,
1289
+ // if empty all properties will be frozen
1290
+ propertyNamesToBeFrozen,
1291
+ // also means that we are on the first level
1292
+ isRoot = true) {
1293
+ const runPropertyNameCheck = propertyNamesToBeFrozen.length > 0;
1294
+ for (const key of Reflect.ownKeys(target)) {
1295
+ if (runPropertyNameCheck && !propertyNamesToBeFrozen.includes(key)) {
1296
+ continue;
1297
+ }
1298
+ const propValue = target[key];
1299
+ if (isRecordLike(propValue) && !Object.isFrozen(propValue)) {
1300
+ Object.freeze(propValue);
1301
+ deepFreeze(propValue, [], false);
1302
+ }
1303
+ else if (isRoot) {
1304
+ Object.defineProperty(target, key, {
1305
+ value: propValue,
1306
+ writable: false,
1307
+ configurable: false,
1308
+ });
1309
+ }
1310
+ }
1311
+ }
1312
+ function isRecordLike(target) {
1313
+ return typeof target === 'object' && target !== null;
1314
+ }
1315
+
1316
+ // necessary wrapper function to test prod mode
1317
+ function isDevMode() {
1318
+ return isDevMode$1();
1319
+ }
1320
+
1321
+ function withImmutableState(stateOrFactory, options) {
1322
+ const immutableState = typeof stateOrFactory === 'function' ? stateOrFactory() : stateOrFactory;
1323
+ const stateKeys = Reflect.ownKeys(immutableState);
1324
+ const applyFreezing = isDevMode() || options?.enableInProduction === true;
1325
+ return signalStoreFeature(withState(immutableState), withHooks((store) => ({
1326
+ onInit() {
1327
+ if (!applyFreezing) {
1328
+ return;
1329
+ }
1330
+ /**
1331
+ * `immutableState` will be initially frozen. That is because
1332
+ * of potential mutations outside the SignalStore
1333
+ *
1334
+ * ```ts
1335
+ * const initialState = {id: 1};
1336
+ * signalStore(withImmutableState(initialState));
1337
+ *
1338
+ * initialState.id = 2; // must throw immutability
1339
+ * ```
1340
+ */
1341
+ Object.freeze(immutableState);
1342
+ watchState(store, (state) => {
1343
+ deepFreeze(state, stateKeys);
1344
+ });
1345
+ },
1346
+ })));
1347
+ }
1348
+
1349
+ const keyPath = 'ngrxToolkitKeyPath';
1350
+ const dbName = 'ngrxToolkitDb';
1351
+ const storeName = 'ngrxToolkitStore';
1352
+ const VERSION = 1;
1353
+ class IndexedDBService {
1354
+ /**
1355
+ * write to indexedDB
1356
+ * @param key
1357
+ * @param data
1358
+ */
1359
+ async setItem(key, data) {
1360
+ const db = await this.openDB();
1361
+ const tx = db.transaction(storeName, 'readwrite');
1362
+ const store = tx.objectStore(storeName);
1363
+ store.put({
1364
+ [keyPath]: key,
1365
+ value: data,
1366
+ });
1367
+ return new Promise((resolve, reject) => {
1368
+ tx.oncomplete = () => {
1369
+ db.close();
1370
+ resolve();
1371
+ };
1372
+ tx.onerror = () => {
1373
+ db.close();
1374
+ reject();
1375
+ };
1376
+ });
1377
+ }
1378
+ /**
1379
+ * read from indexedDB
1380
+ * @param key
1381
+ */
1382
+ async getItem(key) {
1383
+ const db = await this.openDB();
1384
+ const tx = db.transaction(storeName, 'readonly');
1385
+ const store = tx.objectStore(storeName);
1386
+ const request = store.get(key);
1387
+ return new Promise((resolve, reject) => {
1388
+ request.onsuccess = () => {
1389
+ db.close();
1390
+ // localStorage(sessionStorage) returns null if the key does not exist
1391
+ // Similarly, indexedDB should return null
1392
+ if (request.result === undefined) {
1393
+ resolve(null);
1394
+ }
1395
+ resolve(request.result?.['value']);
1396
+ };
1397
+ request.onerror = () => {
1398
+ db.close();
1399
+ reject();
1400
+ };
1401
+ });
1402
+ }
1403
+ /**
1404
+ * delete indexedDB
1405
+ * @param key
1406
+ */
1407
+ async clear(key) {
1408
+ const db = await this.openDB();
1409
+ const tx = db.transaction(storeName, 'readwrite');
1410
+ const store = tx.objectStore(storeName);
1411
+ const request = store.delete(key);
1412
+ return new Promise((resolve, reject) => {
1413
+ request.onsuccess = () => {
1414
+ db.close();
1415
+ resolve();
1416
+ };
1417
+ request.onerror = () => {
1418
+ db.close();
1419
+ reject();
1420
+ };
1421
+ });
1422
+ }
1423
+ /**
1424
+ * open indexedDB
1425
+ */
1426
+ async openDB() {
1427
+ return new Promise((resolve, reject) => {
1428
+ const request = indexedDB.open(dbName, VERSION);
1429
+ request.onupgradeneeded = () => {
1430
+ const db = request.result;
1431
+ if (!db.objectStoreNames.contains(storeName)) {
1432
+ db.createObjectStore(storeName, { keyPath });
1433
+ }
1434
+ };
1435
+ request.onsuccess = () => {
1436
+ resolve(request.result);
1437
+ };
1438
+ request.onerror = () => {
1439
+ reject(request.error);
1440
+ };
1441
+ });
1442
+ }
1443
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: IndexedDBService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
1444
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: IndexedDBService, providedIn: 'root' }); }
1445
+ }
1446
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: IndexedDBService, decorators: [{
1447
+ type: Injectable,
1448
+ args: [{ providedIn: 'root' }]
1449
+ }] });
1450
+
1451
+ const SYNC_STATUS = Symbol('SYNC_STATUS');
1452
+
1453
+ function withIndexedDB() {
1454
+ function factory({ key, parse, select, stringify }, store, useStubs) {
1455
+ if (useStubs) {
1456
+ return {
1457
+ clearStorage: () => Promise.resolve(),
1458
+ readFromStorage: () => Promise.resolve(),
1459
+ writeToStorage: () => Promise.resolve(),
1460
+ };
1461
+ }
1462
+ const indexeddbService = inject(IndexedDBService);
1463
+ function warnOnSyncing(mode) {
1464
+ if (store[SYNC_STATUS]() === 'syncing') {
1465
+ const prettyMode = mode === 'read' ? 'Reading' : 'Writing';
1466
+ console.warn(`${prettyMode} to Store (${key}) happened during an ongoing synchronization process.`, 'Please ensure that the store is not in syncing state via `store.whenSynced()`.', 'Alternatively, you can disable the autoSync by passing `autoSync: false` in the config.');
1467
+ }
1468
+ }
1469
+ return {
1470
+ /**
1471
+ * Removes the item stored in storage.
1472
+ */
1473
+ async clearStorage() {
1474
+ warnOnSyncing('write');
1475
+ store[SYNC_STATUS].set('syncing');
1476
+ patchState$1(store, {});
1477
+ await indexeddbService.clear(key);
1478
+ store[SYNC_STATUS].set('synced');
1479
+ },
1480
+ /**
1481
+ * Reads item from storage and patches the state.
1482
+ */
1483
+ async readFromStorage() {
1484
+ warnOnSyncing('read');
1485
+ store[SYNC_STATUS].set('syncing');
1486
+ const stateString = await indexeddbService.getItem(key);
1487
+ if (stateString) {
1488
+ patchState$1(store, parse(stateString));
1489
+ }
1490
+ store[SYNC_STATUS].set('synced');
1491
+ },
1492
+ /**
1493
+ * Writes selected portion to storage.
1494
+ */
1495
+ async writeToStorage() {
1496
+ warnOnSyncing('write');
1497
+ store[SYNC_STATUS].set('syncing');
1498
+ const slicedState = select(getState(store));
1499
+ await indexeddbService.setItem(key, stringify(slicedState));
1500
+ store[SYNC_STATUS].set('synced');
1501
+ },
1502
+ };
1503
+ }
1504
+ factory.type = 'async';
1505
+ return factory;
1506
+ }
1507
+
1508
+ class LocalStorageService {
1509
+ getItem(key) {
1510
+ return localStorage.getItem(key);
1511
+ }
1512
+ setItem(key, data) {
1513
+ return localStorage.setItem(key, data);
1514
+ }
1515
+ clear(key) {
1516
+ return localStorage.removeItem(key);
1517
+ }
1518
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: LocalStorageService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
1519
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: LocalStorageService, providedIn: 'root' }); }
1520
+ }
1521
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: LocalStorageService, decorators: [{
1522
+ type: Injectable,
1523
+ args: [{
1524
+ providedIn: 'root',
1525
+ }]
1526
+ }] });
1527
+
1528
+ class SessionStorageService {
1529
+ getItem(key) {
1530
+ return sessionStorage.getItem(key);
1531
+ }
1532
+ setItem(key, data) {
1533
+ return sessionStorage.setItem(key, data);
1534
+ }
1535
+ clear(key) {
1536
+ return sessionStorage.removeItem(key);
1537
+ }
1538
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: SessionStorageService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
1539
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: SessionStorageService, providedIn: 'root' }); }
1540
+ }
1541
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: SessionStorageService, decorators: [{
1542
+ type: Injectable,
1543
+ args: [{
1544
+ providedIn: 'root',
1545
+ }]
1546
+ }] });
1547
+
1548
+ function withLocalStorage() {
1549
+ return createSyncMethods(LocalStorageService);
1550
+ }
1551
+ function withSessionStorage() {
1552
+ return createSyncMethods(SessionStorageService);
1553
+ }
1554
+ function createSyncMethods(Storage) {
1555
+ function factory({ key, parse, select, stringify }, store, useStubs) {
1556
+ if (useStubs) {
1557
+ return {
1558
+ clearStorage: () => undefined,
1559
+ readFromStorage: () => undefined,
1560
+ writeToStorage: () => undefined,
1561
+ };
1562
+ }
1563
+ const storage = inject(Storage);
1564
+ return {
1565
+ clearStorage() {
1566
+ storage.clear(key);
1567
+ },
1568
+ readFromStorage() {
1569
+ const stateString = storage.getItem(key);
1570
+ if (stateString) {
1571
+ patchState$1(store, parse(stateString));
1572
+ }
1573
+ },
1574
+ writeToStorage() {
1575
+ const slicedState = select(getState(store));
1576
+ storage.setItem(key, stringify(slicedState));
1577
+ },
1578
+ };
1579
+ }
1580
+ factory.type = 'sync';
1581
+ return factory;
1582
+ }
1583
+
1584
+ function withStorageSync(configOrKey, storageStrategy) {
1585
+ if (typeof configOrKey !== 'string' &&
1586
+ configOrKey.storage &&
1587
+ storageStrategy) {
1588
+ throw new Error('You can either pass a storage strategy or a config with storage, but not both.');
1589
+ }
1590
+ const config = {
1591
+ autoSync: true,
1592
+ select: (state) => state,
1593
+ parse: JSON.parse,
1594
+ stringify: JSON.stringify,
1595
+ storage: () => localStorage,
1596
+ ...(typeof configOrKey === 'string' ? { key: configOrKey } : configOrKey),
1597
+ };
1598
+ const factory = storageStrategy ??
1599
+ (config.storage() === localStorage
1600
+ ? withLocalStorage()
1601
+ : withSessionStorage());
1602
+ if (factory.type === 'sync') {
1603
+ return createSyncStorageSync(factory, config);
1604
+ }
1605
+ else {
1606
+ return createAsyncStorageSync(factory, config);
1607
+ }
1608
+ }
1609
+ function createSyncStorageSync(factory, config) {
1610
+ return signalStoreFeature(withMethods((store, platformId = inject(PLATFORM_ID)) => {
1611
+ return factory(config, store, isPlatformServer(platformId));
1612
+ }), withHooks({
1613
+ onInit(store, platformId = inject(PLATFORM_ID)) {
1614
+ if (isPlatformServer(platformId)) {
1615
+ return;
1616
+ }
1617
+ if (config.autoSync) {
1618
+ store.readFromStorage();
1619
+ watchState(store, () => store.writeToStorage());
1620
+ }
1621
+ },
1622
+ }));
1623
+ }
1624
+ function createAsyncStorageSync(factory, config) {
1625
+ return signalStoreFeature(withProps(() => {
1626
+ const props = {
1627
+ /*
1628
+ // we need to have that as property (and not state)
1629
+ // Otherwise the state watcher fires when updating the sync status
1630
+ */
1631
+ [SYNC_STATUS]: signal('idle'),
1632
+ };
1633
+ const resolves = [];
1634
+ effect(() => {
1635
+ const syncStatus = props[SYNC_STATUS]();
1636
+ if (syncStatus === 'synced') {
1637
+ resolves.forEach((resolve) => resolve());
1638
+ resolves.splice(0, resolves.length);
1639
+ }
1640
+ });
1641
+ return {
1642
+ ...props,
1643
+ isSynced: computed(() => props[SYNC_STATUS]() === 'synced'),
1644
+ whenSynced: () => new Promise((resolve) => {
1645
+ if (props[SYNC_STATUS]() === 'synced') {
1646
+ resolve();
1647
+ }
1648
+ else {
1649
+ resolves.push(resolve);
1650
+ }
1651
+ }),
1652
+ };
1653
+ }), withMethods((store, platformId = inject(PLATFORM_ID)) => {
1654
+ return factory(config, store, isPlatformServer(platformId));
1655
+ }), withHooks({
1656
+ async onInit(store, platformId = inject(PLATFORM_ID)) {
1657
+ if (isPlatformServer(platformId)) {
1658
+ return;
1659
+ }
1660
+ const initialState = getState(store);
1661
+ if (config.autoSync) {
1662
+ let startWatching = false;
1663
+ watchState(store, () => {
1664
+ if (!startWatching) {
1665
+ if (getState(store) === initialState) {
1666
+ return;
1667
+ }
1668
+ console.warn(`Writing to Store (${config.key}) happened before the state was initially read from storage.`, 'Please ensure that the store is not in syncing state via `store.whenSynced()` before writing to the state.', 'Alternatively, you can disable autoSync by passing `autoSync: false` in the config.');
1669
+ return;
1670
+ }
1671
+ return store.writeToStorage();
1672
+ });
1673
+ await store.readFromStorage();
1674
+ startWatching = true;
1675
+ }
1676
+ },
1677
+ }));
1678
+ }
1679
+
1680
+ /**
1681
+ * `withConditional` activates a feature based on a given condition.
1682
+ *
1683
+ * **Use Cases**
1684
+ * - Conditionally activate features based on the **store state** or other criteria.
1685
+ * - Choose between **two different implementations** of a feature.
1686
+ *
1687
+ * **Type Constraints**
1688
+ * Both features must have **exactly the same state, props, and methods**.
1689
+ * Otherwise, a type error will occur.
1690
+ *
1691
+ *
1692
+ * **Usage**
1693
+ *
1694
+ * ```typescript
1695
+ * const withUser = signalStoreFeature(
1696
+ * withState({ id: 1, name: 'Konrad' }),
1697
+ * withHooks(store => ({
1698
+ * onInit() {
1699
+ * // user loading logic
1700
+ * }
1701
+ * }))
1702
+ * );
1703
+ *
1704
+ * function withFakeUser() {
1705
+ * return signalStoreFeature(
1706
+ * withState({ id: 0, name: 'anonymous' })
1707
+ * );
1708
+ * }
1709
+ *
1710
+ * signalStore(
1711
+ * withMethods(() => ({
1712
+ * useRealUser: () => true
1713
+ * })),
1714
+ * withConditional((store) => store.useRealUser(), withUser, withFakeUser)
1715
+ * )
1716
+ * ```
1717
+ *
1718
+ * @param condition - A function that determines which feature to activate based on the store state.
1719
+ * @param featureIfTrue - The feature to activate if the condition evaluates to `true`.
1720
+ * @param featureIfFalse - The feature to activate if the condition evaluates to `false`.
1721
+ * @returns A `SignalStoreFeature` that applies the selected feature based on the condition.
1722
+ */
1723
+ function withConditional(condition, featureIfTrue, featureIfFalse) {
1724
+ return (store) => {
1725
+ const conditionStore = {
1726
+ ...store['stateSignals'],
1727
+ ...store['props'],
1728
+ ...store['methods'],
1729
+ };
1730
+ return condition(conditionStore)
1731
+ ? featureIfTrue(store)
1732
+ : featureIfFalse(store);
1733
+ };
1734
+ }
1735
+ const emptyFeature = signalStoreFeature(withState({}));
1736
+
1737
+ /**
1738
+ * @deprecated Use `import { withFeature } from '@ngrx/signals'` instead, starting with `ngrx/signals` 19.1: https://ngrx.io/guide/signals/signal-store/custom-store-features#connecting-a-custom-feature-with-the-store
1739
+ *
1740
+ * Allows to pass properties, methods, or signals from a SignalStore
1741
+ * to a feature.
1742
+ *
1743
+ * Typically, a `signalStoreFeature` can have input constraints on
1744
+ *
1745
+ * ```typescript
1746
+ * function withSum(a: Signal<number>, b: Signal<number>) {
1747
+ * return signalStoreFeature(
1748
+ * withComputed(() => ({
1749
+ * sum: computed(() => a() + b())
1750
+ * }))
1751
+ * );
1752
+ * }
1753
+ *
1754
+ * signalStore(
1755
+ * withState({ a: 1, b: 2 }),
1756
+ * withFeatureFactory((store) => withSum(store.a, store.b))
1757
+ * );
1758
+ * ```
1759
+ * @param factoryFn
1760
+ */
1761
+ function withFeatureFactory(factoryFn) {
1762
+ return (store) => {
1763
+ const storeForFactory = {
1764
+ ...store['stateSignals'],
1765
+ ...store['props'],
1766
+ ...store['methods'],
1767
+ };
1768
+ const feature = factoryFn(storeForFactory);
1769
+ return feature(store);
1770
+ };
1771
+ }
1772
+
1326
1773
  /**
1327
1774
  * Generated bundle index. Do not edit.
1328
1775
  */
1329
1776
 
1330
- export { capitalize, createEffects, createPageArray, createReducer, firstPage, getCallStateKeys, getDataServiceKeys, getUndoRedoKeys, gotoPage, nextPage, noPayload, patchState, payload, previousPage, renameDevtoolsName, setError, setLoaded, setLoading, setMaxPageNavigationArrayItems, setPageSize, setResetState, updateState, withCallState, withDataService, withDevToolsStub, withDevtools, withDisabledNameIndices, withGlitchTracking, withMapper, withPagination, withRedux, withReset, withStorageSync, withUndoRedo };
1777
+ export { capitalize, createEffects, createPageArray, createReducer, deriveCallStateKeys, emptyFeature, firstPage, getCallStateKeys, getCollectionArray, getDataServiceKeys, getUndoRedoKeys, gotoPage, nextPage, noPayload, patchState, payload, previousPage, provideDevtoolsConfig, renameDevtoolsName, setError, setLoaded, setLoading, setMaxPageNavigationArrayItems, setPageSize, setResetState, updateState, withCallState, withConditional, withDataService, withDevToolsStub, withDevtools, withDisabledNameIndices, withFeatureFactory, withGlitchTracking, withImmutableState, withIndexedDB, withIndexedDB as withIndexeddb, withLocalStorage, withMapper, withPagination, withRedux, withReset, withSessionStorage, withStorageSync, withUndoRedo };
1331
1778
  //# sourceMappingURL=angular-architects-ngrx-toolkit.mjs.map