@angular-architects/ngrx-toolkit 20.0.0 → 20.0.2

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 (84) hide show
  1. package/eslint.config.cjs +43 -0
  2. package/jest.config.ts +22 -0
  3. package/ng-package.json +7 -0
  4. package/package.json +4 -21
  5. package/project.json +37 -0
  6. package/redux-connector/docs/README.md +131 -0
  7. package/redux-connector/index.ts +6 -0
  8. package/redux-connector/ng-package.json +5 -0
  9. package/redux-connector/src/lib/create-redux.ts +102 -0
  10. package/redux-connector/src/lib/model.ts +89 -0
  11. package/redux-connector/src/lib/rxjs-interop/redux-method.ts +66 -0
  12. package/redux-connector/src/lib/signal-redux-store.ts +59 -0
  13. package/redux-connector/src/lib/util.ts +22 -0
  14. package/src/index.ts +43 -0
  15. package/src/lib/assertions/assertions.ts +9 -0
  16. package/src/lib/devtools/features/with-disabled-name-indicies.ts +31 -0
  17. package/src/lib/devtools/features/with-glitch-tracking.ts +35 -0
  18. package/src/lib/devtools/features/with-mapper.ts +34 -0
  19. package/src/lib/devtools/internal/current-action-names.ts +1 -0
  20. package/src/lib/devtools/internal/default-tracker.ts +60 -0
  21. package/src/lib/devtools/internal/devtools-feature.ts +37 -0
  22. package/src/lib/devtools/internal/devtools-syncer.service.ts +202 -0
  23. package/src/lib/devtools/internal/glitch-tracker.service.ts +61 -0
  24. package/src/lib/devtools/internal/models.ts +29 -0
  25. package/src/lib/devtools/provide-devtools-config.ts +32 -0
  26. package/src/lib/devtools/rename-devtools-name.ts +21 -0
  27. package/src/lib/devtools/tests/action-name.spec.ts +48 -0
  28. package/src/lib/devtools/tests/basic.spec.ts +111 -0
  29. package/src/lib/devtools/tests/connecting.spec.ts +37 -0
  30. package/src/lib/devtools/tests/helpers.spec.ts +43 -0
  31. package/src/lib/devtools/tests/naming.spec.ts +216 -0
  32. package/src/lib/devtools/tests/provide-devtools-config.spec.ts +25 -0
  33. package/src/lib/devtools/tests/types.spec.ts +19 -0
  34. package/src/lib/devtools/tests/update-state.spec.ts +29 -0
  35. package/src/lib/devtools/tests/with-devtools.spec.ts +5 -0
  36. package/src/lib/devtools/tests/with-glitch-tracking.spec.ts +272 -0
  37. package/src/lib/devtools/tests/with-mapper.spec.ts +69 -0
  38. package/src/lib/devtools/update-state.ts +38 -0
  39. package/src/lib/devtools/with-dev-tools-stub.ts +6 -0
  40. package/src/lib/devtools/with-devtools.ts +81 -0
  41. package/src/lib/immutable-state/deep-freeze.ts +43 -0
  42. package/src/lib/immutable-state/is-dev-mode.ts +6 -0
  43. package/src/lib/immutable-state/tests/with-immutable-state.spec.ts +278 -0
  44. package/src/lib/immutable-state/with-immutable-state.ts +150 -0
  45. package/src/lib/shared/prettify.ts +3 -0
  46. package/src/lib/shared/signal-store-models.ts +30 -0
  47. package/src/lib/shared/throw-if-null.ts +7 -0
  48. package/src/lib/storage-sync/features/with-indexed-db.ts +81 -0
  49. package/src/lib/storage-sync/features/with-local-storage.ts +58 -0
  50. package/src/lib/storage-sync/internal/indexeddb.service.ts +124 -0
  51. package/src/lib/storage-sync/internal/local-storage.service.ts +19 -0
  52. package/src/lib/storage-sync/internal/models.ts +62 -0
  53. package/src/lib/storage-sync/internal/session-storage.service.ts +18 -0
  54. package/src/lib/storage-sync/tests/indexeddb.service.spec.ts +99 -0
  55. package/src/lib/storage-sync/tests/with-storage-async.spec.ts +305 -0
  56. package/src/lib/storage-sync/tests/with-storage-sync.spec.ts +273 -0
  57. package/src/lib/storage-sync/with-storage-sync.ts +236 -0
  58. package/src/lib/with-call-state.spec.ts +42 -0
  59. package/src/lib/with-call-state.ts +195 -0
  60. package/src/lib/with-conditional.spec.ts +125 -0
  61. package/src/lib/with-conditional.ts +74 -0
  62. package/src/lib/with-data-service.spec.ts +564 -0
  63. package/src/lib/with-data-service.ts +433 -0
  64. package/src/lib/with-feature-factory.spec.ts +69 -0
  65. package/src/lib/with-feature-factory.ts +56 -0
  66. package/src/lib/with-pagination.spec.ts +135 -0
  67. package/src/lib/with-pagination.ts +373 -0
  68. package/src/lib/with-redux.spec.ts +258 -0
  69. package/src/lib/with-redux.ts +387 -0
  70. package/src/lib/with-reset.spec.ts +112 -0
  71. package/src/lib/with-reset.ts +62 -0
  72. package/src/lib/with-undo-redo.spec.ts +274 -0
  73. package/src/lib/with-undo-redo.ts +200 -0
  74. package/src/test-setup.ts +6 -0
  75. package/tsconfig.json +29 -0
  76. package/tsconfig.lib.json +17 -0
  77. package/tsconfig.lib.prod.json +9 -0
  78. package/tsconfig.spec.json +17 -0
  79. package/fesm2022/angular-architects-ngrx-toolkit-redux-connector.mjs +0 -119
  80. package/fesm2022/angular-architects-ngrx-toolkit-redux-connector.mjs.map +0 -1
  81. package/fesm2022/angular-architects-ngrx-toolkit.mjs +0 -1780
  82. package/fesm2022/angular-architects-ngrx-toolkit.mjs.map +0 -1
  83. package/index.d.ts +0 -938
  84. package/redux-connector/index.d.ts +0 -59
@@ -0,0 +1,278 @@
1
+ import { TestBed } from '@angular/core/testing';
2
+ import { getState, patchState, signalStore, withState } from '@ngrx/signals';
3
+ import * as devMode from '../is-dev-mode';
4
+ import { withImmutableState } from '../with-immutable-state';
5
+
6
+ describe('withImmutableState', () => {
7
+ const SECRET = Symbol('secret');
8
+
9
+ const createInitialState = () => ({
10
+ user: {
11
+ firstName: 'John',
12
+ lastName: 'Smith',
13
+ },
14
+ foo: 'bar',
15
+ numbers: [1, 2, 3],
16
+ ngrx: 'signals',
17
+ nestedSymbol: {
18
+ [SECRET]: 'another secret',
19
+ },
20
+ [SECRET]: {
21
+ code: 'secret',
22
+ value: '123',
23
+ },
24
+ });
25
+
26
+ const createStore = (enableInProduction: boolean | undefined) => {
27
+ const initialState = createInitialState();
28
+ const Store = signalStore(
29
+ { protectedState: false },
30
+ enableInProduction === undefined
31
+ ? withImmutableState(initialState)
32
+ : withImmutableState(initialState, { enableInProduction }),
33
+ );
34
+ return TestBed.configureTestingModule({ providers: [Store] }).inject(Store);
35
+ };
36
+
37
+ for (const isDevMode of [true, false]) {
38
+ describe(isDevMode ? 'dev mode' : 'production mode', () => {
39
+ beforeEach(() => {
40
+ jest.spyOn(devMode, 'isDevMode').mockReturnValue(isDevMode);
41
+ });
42
+ for (const { stateFactory, enableInProduction, name, protectionOn } of [
43
+ {
44
+ name: 'dev-only protection',
45
+ stateFactory: () => createStore(false),
46
+ enableInProduction: false,
47
+ protectionOn: isDevMode,
48
+ },
49
+ {
50
+ name: 'production protection',
51
+ enableInProduction: true,
52
+ stateFactory: () => createStore(true),
53
+ protectionOn: true,
54
+ },
55
+ {
56
+ name: 'default settings',
57
+ enableInProduction: undefined,
58
+ stateFactory: () => createStore(undefined),
59
+ protectionOn: isDevMode,
60
+ },
61
+ ]) {
62
+ describe(name, () => {
63
+ it(`throws not on a mutable change (primitive type)`, () => {
64
+ const state = stateFactory();
65
+
66
+ const patch = () =>
67
+ patchState(state, (state) => {
68
+ state.ngrx = 'mutable change';
69
+ return state;
70
+ });
71
+
72
+ expect(patch).not.toThrow();
73
+ });
74
+
75
+ it(`throws ${protectionOn ? '' : 'not '}on a mutable change (object type)`, () => {
76
+ const state = stateFactory();
77
+
78
+ const patch = () =>
79
+ patchState(state, (state) => {
80
+ state.user.firstName = 'Siegfried';
81
+ return state;
82
+ });
83
+
84
+ if (protectionOn) {
85
+ expect(patch).toThrow(
86
+ "Cannot assign to read only property 'firstName' of object",
87
+ );
88
+ } else {
89
+ expect(patch).not.toThrow();
90
+ }
91
+ });
92
+
93
+ it(`does ${
94
+ protectionOn ? '' : 'not '
95
+ }on a nested mutable change`, () => {
96
+ const state = stateFactory();
97
+
98
+ const patch = () =>
99
+ patchState(state, (state) => {
100
+ state.user.firstName = 'mutable change';
101
+ return state;
102
+ });
103
+
104
+ if (protectionOn) {
105
+ expect(patch).toThrow(
106
+ "Cannot assign to read only property 'firstName' of object",
107
+ );
108
+ } else {
109
+ expect(patch).not.toThrow();
110
+ }
111
+ });
112
+
113
+ describe('mutable changes outside of patchState', () => {
114
+ it(`throws${
115
+ protectionOn ? '' : ' not'
116
+ } on reassigned a property of the exposed state`, () => {
117
+ const state = stateFactory();
118
+ const patch = () => {
119
+ state.user().firstName = 'mutable change 1';
120
+ };
121
+ if (protectionOn) {
122
+ expect(patch).toThrow(
123
+ "Cannot assign to read only property 'firstName' of object",
124
+ );
125
+ } else {
126
+ expect(patch).not.toThrow();
127
+ }
128
+ });
129
+
130
+ it(`throws not when exposed state (primitive type) via getState is mutated`, () => {
131
+ const state = stateFactory();
132
+ const s = getState(state);
133
+
134
+ const patch = () => (s.ngrx = 'mutable change 2');
135
+ expect(patch).not.toThrow();
136
+ });
137
+
138
+ it(`throws ${protectionOn ? '' : 'not '} when exposed state (object type) via getState is mutated`, () => {
139
+ const state = stateFactory();
140
+ const s = getState(state);
141
+
142
+ const patch = () => (s.user.firstName = 'Hans');
143
+
144
+ if (protectionOn) {
145
+ expect(patch).toThrow(
146
+ "Cannot assign to read only property 'firstName' of object",
147
+ );
148
+ } else {
149
+ expect(patch).not.toThrow();
150
+ }
151
+ });
152
+
153
+ it(`throws ${
154
+ protectionOn ? '' : 'not '
155
+ }when mutable change happens`, () => {
156
+ const state = stateFactory();
157
+ const s = { user: { firstName: 'M', lastName: 'S' } };
158
+ patchState(state, s);
159
+ const patch = () => {
160
+ s.user.firstName = 'mutable change 3';
161
+ };
162
+ if (protectionOn) {
163
+ expect(patch).toThrow(
164
+ "Cannot assign to read only property 'firstName' of object",
165
+ );
166
+ } else {
167
+ expect(patch).not.toThrow();
168
+ }
169
+ });
170
+
171
+ it(`throws ${
172
+ protectionOn ? '' : 'not '
173
+ }when mutable change of root symbol property happens`, () => {
174
+ const state = stateFactory();
175
+ const s = getState(state);
176
+
177
+ const patch = () => {
178
+ s[SECRET].code = 'mutable change';
179
+ };
180
+
181
+ if (protectionOn) {
182
+ expect(patch).toThrow(
183
+ "Cannot assign to read only property 'code' of object",
184
+ );
185
+ } else {
186
+ expect(patch).not.toThrow();
187
+ }
188
+ });
189
+
190
+ it(`throws ${
191
+ protectionOn ? '' : 'not '
192
+ }when mutable change of nested symbol property happens`, () => {
193
+ const state = stateFactory();
194
+ const s = getState(state);
195
+
196
+ const patch = () => {
197
+ s.nestedSymbol[SECRET] = 'mutable change';
198
+ };
199
+
200
+ if (protectionOn) {
201
+ expect(patch).toThrow(
202
+ "Cannot assign to read only property 'Symbol(secret)' of object",
203
+ );
204
+ } else {
205
+ expect(patch).not.toThrow();
206
+ }
207
+ });
208
+ });
209
+
210
+ describe('special tests', () => {
211
+ for (const { name, mutationFn } of [
212
+ {
213
+ name: 'location',
214
+ mutationFn: (state: { location: { city: string } }) =>
215
+ (state.location.city = 'Paris'),
216
+ },
217
+ {
218
+ name: 'user',
219
+ mutationFn: (state: { user: { firstName: string } }) =>
220
+ (state.user.firstName = 'Jane'),
221
+ },
222
+ ]) {
223
+ it(`throws ${
224
+ protectionOn ? '' : 'not '
225
+ }on concatenated state (${name})`, () => {
226
+ const UserStore = signalStore(
227
+ { providedIn: 'root' },
228
+ withImmutableState(createInitialState(), {
229
+ enableInProduction,
230
+ }),
231
+ withImmutableState(
232
+ { location: { city: 'London' } },
233
+ { enableInProduction },
234
+ ),
235
+ );
236
+ const store = TestBed.inject(UserStore);
237
+ const state = getState(store);
238
+
239
+ if (protectionOn) {
240
+ expect(() => mutationFn(state)).toThrow();
241
+ } else {
242
+ expect(() => mutationFn(state)).not.toThrow();
243
+ }
244
+ });
245
+ }
246
+ });
247
+
248
+ it('should be possible to mix both mutable and immutable state', () => {
249
+ const immutableState = {
250
+ user: {
251
+ id: 1,
252
+ name: 'John',
253
+ },
254
+ };
255
+
256
+ const mutableState = {
257
+ address: 'London',
258
+ };
259
+ const TestStore = signalStore(
260
+ { providedIn: 'root', protectedState: false },
261
+ withImmutableState(immutableState, { enableInProduction }),
262
+ withState(mutableState),
263
+ );
264
+
265
+ TestBed.inject(TestStore);
266
+
267
+ if (protectionOn) {
268
+ expect(() => (immutableState.user.name = 'Jane')).toThrow();
269
+ } else {
270
+ expect(() => (immutableState.user.name = 'Jane')).not.toThrow();
271
+ }
272
+ expect(() => (mutableState.address = 'Glastonbury')).not.toThrow();
273
+ });
274
+ });
275
+ }
276
+ });
277
+ }
278
+ });
@@ -0,0 +1,150 @@
1
+ import {
2
+ EmptyFeatureResult,
3
+ getState,
4
+ signalStoreFeature,
5
+ SignalStoreFeature,
6
+ SignalStoreFeatureResult,
7
+ watchState,
8
+ withHooks,
9
+ withState,
10
+ } from '@ngrx/signals';
11
+ import { deepFreeze } from './deep-freeze';
12
+ import { isDevMode } from './is-dev-mode';
13
+
14
+ /**
15
+ * Starting from v20, the root properties of the state object
16
+ * are all of type `WritableSignal`.
17
+ *
18
+ * That means, we cannot freeze root properties, which are of a
19
+ * primitive data type.
20
+ *
21
+ * This is a breaking change, because in v19 the state was a Signal.
22
+ * We had the possibility to freeze that Signal's
23
+ * object and could therefore provide immutability for
24
+ * root properties of primitive data types.
25
+ *
26
+ * For example:
27
+ *
28
+ * ```ts
29
+ * const state = {
30
+ * id: 1, // was frozen in v19, but not in >= v20
31
+ * address: {
32
+ * street: 'Main St',
33
+ * city: 'Springfield',
34
+ * }
35
+ * }
36
+ * ```
37
+ */
38
+
39
+ /**
40
+ * Prevents mutation of the state.
41
+ *
42
+ * This is done by applying `Object.freeze` to each root
43
+ * property of the state. Any mutable change within
44
+ * or outside the `SignalStore` will throw an error.
45
+ *
46
+ * Root properties of the state having a primitive data type
47
+ * are not supported.
48
+ *
49
+ * * For example:
50
+ *
51
+ * ```ts
52
+ * const state = {
53
+ * // ⛔️ are not frozen -> mutable changes possible
54
+ * id: 1,
55
+ *
56
+ * // ✅ are frozen -> mutable changes throw
57
+ * address: {
58
+ * street: 'Main St',
59
+ * city: 'Springfield',
60
+ * }
61
+ * }
62
+ * ```
63
+ *
64
+ * @param state the state object
65
+ * @param options enable protection in production (default: false)
66
+ */
67
+ export function withImmutableState<State extends object>(
68
+ state: State,
69
+ options?: { enableInProduction?: boolean },
70
+ ): SignalStoreFeature<
71
+ SignalStoreFeatureResult,
72
+ EmptyFeatureResult & { state: State }
73
+ >;
74
+ /**
75
+ * Prevents mutation of the state.
76
+ *
77
+ * This is done by applying `Object.freeze` to each root
78
+ * property of the state. Any mutable change within
79
+ * or outside the `SignalStore` will throw an error.
80
+ *
81
+ * Root properties of the state having a primitive data type
82
+ * are not supported.
83
+ *
84
+ * * For example:
85
+ *
86
+ * ```ts
87
+ * const state = {
88
+ * // ⛔️ are not frozen -> mutable changes possible
89
+ * id: 1,
90
+ *
91
+ * // ✅ are frozen -> mutable changes throw
92
+ * address: {
93
+ * street: 'Main St',
94
+ * city: 'Springfield',
95
+ * }
96
+ * }
97
+ * ```
98
+ *
99
+ * @param stateFactory a function returning the state object
100
+ * @param options enable protection in production (default: false)
101
+ */
102
+ export function withImmutableState<State extends object>(
103
+ stateFactory: () => State,
104
+ options?: { enableInProduction?: boolean },
105
+ ): SignalStoreFeature<
106
+ SignalStoreFeatureResult,
107
+ EmptyFeatureResult & { state: State }
108
+ >;
109
+ export function withImmutableState<State extends object>(
110
+ stateOrFactory: State | (() => State),
111
+ options?: { enableInProduction?: boolean },
112
+ ): SignalStoreFeature<
113
+ SignalStoreFeatureResult,
114
+ EmptyFeatureResult & { state: State }
115
+ > {
116
+ const immutableState =
117
+ typeof stateOrFactory === 'function' ? stateOrFactory() : stateOrFactory;
118
+ const stateKeys = Reflect.ownKeys(immutableState);
119
+
120
+ const applyFreezing = isDevMode() || options?.enableInProduction === true;
121
+ return signalStoreFeature(
122
+ withState(immutableState),
123
+ withHooks((store) => ({
124
+ onInit() {
125
+ if (!applyFreezing) {
126
+ return;
127
+ }
128
+ /**
129
+ * `immutableState` will be initially frozen. That is because
130
+ * of potential mutations outside the SignalStore
131
+ *
132
+ * ```ts
133
+ * const initialState = {user: {id: 1}};
134
+ * signalStore(withImmutableState(initialState));
135
+ *
136
+ * initialState.user.id = 2; // must throw immutability
137
+ * ```
138
+ */
139
+ deepFreeze(
140
+ getState(store) as Record<string | symbol, unknown>,
141
+ stateKeys,
142
+ );
143
+
144
+ watchState(store, (state) => {
145
+ deepFreeze(state as Record<string | symbol, unknown>, stateKeys);
146
+ });
147
+ },
148
+ })),
149
+ );
150
+ }
@@ -0,0 +1,3 @@
1
+ export type Prettify<Type extends object> = {
2
+ [Key in keyof Type]: Type[Key];
3
+ };
@@ -0,0 +1,30 @@
1
+ /**
2
+ * This file contains copies of types of the Signal Store which are not public.
3
+ *
4
+ * Since certain features depend on them, if we don't want to break
5
+ * the encapsulation of @ngrx/signals, we decided to copy them.
6
+ *
7
+ * Since TypeScript is based on structural typing, we can get away with it.
8
+ *
9
+ * If @ngrx/signals changes its internal types, we catch them via integration
10
+ * tests.
11
+ *
12
+ * Because of the "tight coupling", the toolkit doesn't have version range
13
+ * to @ngrx/signal, but is very precise.
14
+ */
15
+ import { Signal } from '@angular/core';
16
+ import { EntityId } from '@ngrx/signals/entities';
17
+
18
+ // withEntites models
19
+ export type EntityState<Entity> = {
20
+ entityMap: Record<EntityId, Entity>;
21
+ ids: EntityId[];
22
+ };
23
+
24
+ export type EntityComputed<Entity> = {
25
+ entities: Signal<Entity[]>;
26
+ };
27
+
28
+ export type NamedEntityComputed<Entity, Collection extends string> = {
29
+ [K in keyof EntityComputed<Entity> as `${Collection}${Capitalize<K>}`]: EntityComputed<Entity>[K];
30
+ };
@@ -0,0 +1,7 @@
1
+ export function throwIfNull<T>(obj: T): NonNullable<T> {
2
+ if (obj === null || obj === undefined) {
3
+ throw new Error('');
4
+ }
5
+
6
+ return obj;
7
+ }
@@ -0,0 +1,81 @@
1
+ import { inject } from '@angular/core';
2
+ import { getState, patchState } from '@ngrx/signals';
3
+ import { IndexedDBService } from '../internal/indexeddb.service';
4
+ import {
5
+ AsyncMethods,
6
+ AsyncStorageStrategy,
7
+ AsyncStoreForFactory,
8
+ SYNC_STATUS,
9
+ } from '../internal/models';
10
+ import { SyncConfig } from '../with-storage-sync';
11
+
12
+ export function withIndexedDB<
13
+ State extends object,
14
+ >(): AsyncStorageStrategy<State> {
15
+ function factory(
16
+ { key, parse, select, stringify }: Required<SyncConfig<State>>,
17
+ store: AsyncStoreForFactory<State>,
18
+ useStubs: boolean,
19
+ ): AsyncMethods {
20
+ if (useStubs) {
21
+ return {
22
+ clearStorage: () => Promise.resolve(),
23
+ readFromStorage: () => Promise.resolve(),
24
+ writeToStorage: () => Promise.resolve(),
25
+ };
26
+ }
27
+
28
+ const indexeddbService = inject(IndexedDBService);
29
+
30
+ function warnOnSyncing(mode: 'read' | 'write'): void {
31
+ if (store[SYNC_STATUS]() === 'syncing') {
32
+ const prettyMode = mode === 'read' ? 'Reading' : 'Writing';
33
+ console.warn(
34
+ `${prettyMode} to Store (${key}) happened during an ongoing synchronization process.`,
35
+ 'Please ensure that the store is not in syncing state via `store.whenSynced()`.',
36
+ 'Alternatively, you can disable the autoSync by passing `autoSync: false` in the config.',
37
+ );
38
+ }
39
+ }
40
+
41
+ return {
42
+ /**
43
+ * Removes the item stored in storage.
44
+ */
45
+ async clearStorage(): Promise<void> {
46
+ warnOnSyncing('write');
47
+ store[SYNC_STATUS].set('syncing');
48
+ patchState(store, {});
49
+ await indexeddbService.clear(key);
50
+ store[SYNC_STATUS].set('synced');
51
+ },
52
+
53
+ /**
54
+ * Reads item from storage and patches the state.
55
+ */
56
+ async readFromStorage(): Promise<void> {
57
+ warnOnSyncing('read');
58
+ store[SYNC_STATUS].set('syncing');
59
+ const stateString = await indexeddbService.getItem(key);
60
+ if (stateString) {
61
+ patchState(store, parse(stateString));
62
+ }
63
+ store[SYNC_STATUS].set('synced');
64
+ },
65
+
66
+ /**
67
+ * Writes selected portion to storage.
68
+ */
69
+ async writeToStorage(): Promise<void> {
70
+ warnOnSyncing('write');
71
+ store[SYNC_STATUS].set('syncing');
72
+ const slicedState = select(getState(store)) as State;
73
+ await indexeddbService.setItem(key, stringify(slicedState));
74
+ store[SYNC_STATUS].set('synced');
75
+ },
76
+ };
77
+ }
78
+ factory.type = 'async' as const;
79
+
80
+ return factory;
81
+ }
@@ -0,0 +1,58 @@
1
+ import { inject, Type } from '@angular/core';
2
+ import { getState, patchState } from '@ngrx/signals';
3
+ import { LocalStorageService } from '../internal/local-storage.service';
4
+ import { SyncStorageStrategy, SyncStoreForFactory } from '../internal/models';
5
+ import { SessionStorageService } from '../internal/session-storage.service';
6
+ import { SyncConfig } from '../with-storage-sync';
7
+
8
+ export function withLocalStorage<
9
+ State extends object,
10
+ >(): SyncStorageStrategy<State> {
11
+ return createSyncMethods<State>(LocalStorageService);
12
+ }
13
+
14
+ export function withSessionStorage<State extends object>() {
15
+ return createSyncMethods<State>(SessionStorageService);
16
+ }
17
+
18
+ function createSyncMethods<State extends object>(
19
+ Storage: Type<LocalStorageService | SessionStorageService>,
20
+ ): SyncStorageStrategy<State> {
21
+ function factory(
22
+ { key, parse, select, stringify }: Required<SyncConfig<State>>,
23
+ store: SyncStoreForFactory<State>,
24
+ useStubs: boolean,
25
+ ) {
26
+ if (useStubs) {
27
+ return {
28
+ clearStorage: () => undefined,
29
+ readFromStorage: () => undefined,
30
+ writeToStorage: () => undefined,
31
+ };
32
+ }
33
+
34
+ const storage = inject(Storage);
35
+
36
+ return {
37
+ clearStorage(): void {
38
+ storage.clear(key);
39
+ },
40
+
41
+ readFromStorage(): void {
42
+ const stateString = storage.getItem(key);
43
+
44
+ if (stateString) {
45
+ patchState(store, parse(stateString));
46
+ }
47
+ },
48
+
49
+ writeToStorage() {
50
+ const slicedState = select(getState(store)) as State;
51
+ storage.setItem(key, stringify(slicedState));
52
+ },
53
+ };
54
+ }
55
+ factory.type = 'sync' as const;
56
+
57
+ return factory;
58
+ }