@angular-architects/ngrx-toolkit 20.0.1 → 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 -1789
  82. package/fesm2022/angular-architects-ngrx-toolkit.mjs.map +0 -1
  83. package/index.d.ts +0 -949
  84. package/redux-connector/index.d.ts +0 -59
@@ -0,0 +1,272 @@
1
+ import {
2
+ createEnvironmentInjector,
3
+ EnvironmentInjector,
4
+ inject,
5
+ runInInjectionContext,
6
+ } from '@angular/core';
7
+ import { TestBed } from '@angular/core/testing';
8
+ import { patchState, signalStore, withMethods, withState } from '@ngrx/signals';
9
+ import { withGlitchTracking } from '../features/with-glitch-tracking';
10
+ import { renameDevtoolsName } from '../rename-devtools-name';
11
+ import { withDevtools } from '../with-devtools';
12
+ import { setupExtensions } from './helpers.spec';
13
+
14
+ describe('withGlitchTracking', () => {
15
+ it('should sync immediately upon instantiation', () => {
16
+ const { sendSpy } = setupExtensions();
17
+
18
+ const Store = signalStore(
19
+ { providedIn: 'root' },
20
+ withDevtools('counter', withGlitchTracking()),
21
+ withState({ count: 0 }),
22
+ );
23
+
24
+ expect(sendSpy).not.toHaveBeenCalled();
25
+ TestBed.inject(Store);
26
+
27
+ expect(sendSpy).toHaveBeenCalledWith(
28
+ { type: 'Store Update' },
29
+ { counter: { count: 0 } },
30
+ );
31
+ });
32
+
33
+ it('should sync synchronous state changes', () => {
34
+ const { sendSpy } = setupExtensions();
35
+
36
+ const Store = signalStore(
37
+ { providedIn: 'root' },
38
+ withState({ count: 0 }),
39
+ withDevtools('counter', withGlitchTracking()),
40
+ withMethods((store) => ({
41
+ increase: () =>
42
+ patchState(store, (value) => ({ count: value.count + 1 })),
43
+ })),
44
+ );
45
+
46
+ const store = TestBed.inject(Store);
47
+
48
+ store.increase();
49
+ store.increase();
50
+ store.increase();
51
+ TestBed.flushEffects();
52
+
53
+ expect(sendSpy.mock.calls).toEqual([
54
+ [{ type: 'Store Update' }, { counter: { count: 0 } }],
55
+ [{ type: 'Store Update' }, { counter: { count: 1 } }],
56
+ [{ type: 'Store Update' }, { counter: { count: 2 } }],
57
+ [{ type: 'Store Update' }, { counter: { count: 3 } }],
58
+ ]);
59
+ });
60
+
61
+ it('should support a mixed approach', () => {
62
+ const { sendSpy } = setupExtensions();
63
+
64
+ const GlitchFreeStore = signalStore(
65
+ { providedIn: 'root' },
66
+ withState({ count: 0 }),
67
+ withDevtools('glitch-free counter'),
68
+ withMethods((store) => ({
69
+ increase: () =>
70
+ patchState(store, (value) => ({ count: value.count + 1 })),
71
+ })),
72
+ );
73
+
74
+ const GlitchStore = signalStore(
75
+ { providedIn: 'root' },
76
+ withState({ count: 0 }),
77
+ withDevtools('glitch counter', withGlitchTracking()),
78
+ withMethods((store) => ({
79
+ increase: () =>
80
+ patchState(store, (value) => ({ count: value.count + 1 })),
81
+ })),
82
+ );
83
+
84
+ const glitchFreeStore = TestBed.inject(GlitchFreeStore);
85
+ const glitchStore = TestBed.inject(GlitchStore);
86
+
87
+ TestBed.flushEffects();
88
+ for (let i = 0; i < 2; i++) {
89
+ glitchFreeStore.increase();
90
+ glitchStore.increase();
91
+ }
92
+ TestBed.flushEffects();
93
+
94
+ expect(sendSpy.mock.calls).toEqual([
95
+ [{ type: 'Store Update' }, { 'glitch counter': { count: 0 } }],
96
+ [
97
+ { type: 'Store Update' },
98
+ { 'glitch-free counter': { count: 0 }, 'glitch counter': { count: 0 } },
99
+ ],
100
+ [
101
+ { type: 'Store Update' },
102
+ { 'glitch-free counter': { count: 0 }, 'glitch counter': { count: 1 } },
103
+ ],
104
+ [
105
+ { type: 'Store Update' },
106
+ { 'glitch-free counter': { count: 0 }, 'glitch counter': { count: 2 } },
107
+ ],
108
+ [
109
+ { type: 'Store Update' },
110
+ { 'glitch-free counter': { count: 2 }, 'glitch counter': { count: 2 } },
111
+ ],
112
+ ]);
113
+ });
114
+
115
+ it('two glitch stores should sync per change', () => {
116
+ const { sendSpy } = setupExtensions();
117
+
118
+ const GlitchStore1 = signalStore(
119
+ { providedIn: 'root' },
120
+ withState({ count: 0 }),
121
+ withDevtools('glitch counter 1', withGlitchTracking()),
122
+ withMethods((store) => ({
123
+ increase: () =>
124
+ patchState(store, (value) => ({ count: value.count + 1 })),
125
+ })),
126
+ );
127
+
128
+ const GlitchStore2 = signalStore(
129
+ { providedIn: 'root' },
130
+ withState({ count: 0 }),
131
+ withDevtools('glitch counter 2', withGlitchTracking()),
132
+ withMethods((store) => ({
133
+ increase: () =>
134
+ patchState(store, (value) => ({ count: value.count + 1 })),
135
+ })),
136
+ );
137
+
138
+ const glitchStore1 = TestBed.inject(GlitchStore1);
139
+ const glitchStore2 = TestBed.inject(GlitchStore2);
140
+
141
+ for (let i = 0; i < 2; i++) {
142
+ glitchStore1.increase();
143
+ glitchStore2.increase();
144
+ }
145
+ TestBed.flushEffects();
146
+
147
+ expect(sendSpy.mock.calls).toEqual([
148
+ [{ type: 'Store Update' }, { 'glitch counter 1': { count: 0 } }],
149
+ [
150
+ { type: 'Store Update' },
151
+ { 'glitch counter 1': { count: 0 }, 'glitch counter 2': { count: 0 } },
152
+ ],
153
+ [
154
+ { type: 'Store Update' },
155
+ { 'glitch counter 1': { count: 1 }, 'glitch counter 2': { count: 0 } },
156
+ ],
157
+ [
158
+ { type: 'Store Update' },
159
+ { 'glitch counter 1': { count: 1 }, 'glitch counter 2': { count: 1 } },
160
+ ],
161
+ [
162
+ { type: 'Store Update' },
163
+ { 'glitch counter 1': { count: 2 }, 'glitch counter 2': { count: 1 } },
164
+ ],
165
+ [
166
+ { type: 'Store Update' },
167
+ { 'glitch counter 1': { count: 2 }, 'glitch counter 2': { count: 2 } },
168
+ ],
169
+ ]);
170
+ });
171
+
172
+ it('should not sync glitch-free if glitched is renamed', () => {
173
+ const { sendSpy } = setupExtensions();
174
+
175
+ const GlitchFreeStore = signalStore(
176
+ { providedIn: 'root' },
177
+ withState({ name: 'Product', price: 10.5 }),
178
+ withDevtools('flight1'),
179
+ );
180
+
181
+ const GlitchStore = signalStore(
182
+ { providedIn: 'root' },
183
+ withState({ name: 'Product', price: 10.5 }),
184
+ withDevtools('flight2', withGlitchTracking()),
185
+ );
186
+
187
+ TestBed.inject(GlitchFreeStore);
188
+ const glitchStore = TestBed.inject(GlitchStore);
189
+
190
+ TestBed.flushEffects();
191
+
192
+ expect(sendSpy.mock.calls).toEqual([
193
+ [{ type: 'Store Update' }, { flight2: { name: 'Product', price: 10.5 } }],
194
+ [
195
+ { type: 'Store Update' },
196
+ {
197
+ flight1: { name: 'Product', price: 10.5 },
198
+ flight2: { name: 'Product', price: 10.5 },
199
+ },
200
+ ],
201
+ ]);
202
+ sendSpy.mockClear();
203
+
204
+ renameDevtoolsName(glitchStore, 'flights2');
205
+ TestBed.flushEffects();
206
+
207
+ expect(sendSpy.mock.calls).toEqual([
208
+ [
209
+ { type: 'Store Update' },
210
+ {
211
+ flight1: { name: 'Product', price: 10.5 },
212
+ flights2: { name: 'Product', price: 10.5 },
213
+ },
214
+ ],
215
+ ]);
216
+ });
217
+
218
+ it('should not sync glitch tracker if glitch-free store is renamed', () => {
219
+ const { sendSpy } = setupExtensions();
220
+
221
+ const GlitchFreeStore = signalStore(
222
+ { providedIn: 'root' },
223
+ withState({ name: 'Product', price: 10.5 }),
224
+ withDevtools('flight1'),
225
+ );
226
+
227
+ const GlitchStore = signalStore(
228
+ { providedIn: 'root' },
229
+ withState({ name: 'Product', price: 10.5 }),
230
+ withDevtools('glitched Flights', withGlitchTracking()),
231
+ );
232
+
233
+ const glitchFreeStore = TestBed.inject(GlitchFreeStore);
234
+ TestBed.inject(GlitchStore);
235
+ TestBed.flushEffects();
236
+
237
+ sendSpy.mockClear();
238
+ renameDevtoolsName(glitchFreeStore, 'glitch-free Flights');
239
+ expect(sendSpy).not.toHaveBeenCalled();
240
+ TestBed.flushEffects();
241
+
242
+ expect(sendSpy.mock.calls).toEqual([
243
+ [
244
+ { type: 'Store Update' },
245
+ {
246
+ 'glitch-free Flights': { name: 'Product', price: 10.5 },
247
+ 'glitched Flights': { name: 'Product', price: 10.5 },
248
+ },
249
+ ],
250
+ ]);
251
+ });
252
+
253
+ it('should destroy watcher if store is destroyed', () => {
254
+ const { sendSpy } = setupExtensions();
255
+
256
+ const GlitchStore = signalStore(
257
+ withState({ name: 'Product', price: 10.5 }),
258
+ withDevtools('Glitched Store', withGlitchTracking()),
259
+ );
260
+
261
+ const childContext = createEnvironmentInjector(
262
+ [GlitchStore],
263
+ TestBed.inject(EnvironmentInjector),
264
+ );
265
+ runInInjectionContext(childContext, () => inject(GlitchStore));
266
+
267
+ expect(sendSpy).toHaveBeenCalled();
268
+ sendSpy.mockClear();
269
+ childContext.destroy();
270
+ expect(sendSpy).toHaveBeenCalledWith({ type: 'Store Update' }, {});
271
+ });
272
+ });
@@ -0,0 +1,69 @@
1
+ import { TestBed } from '@angular/core/testing';
2
+ import { signalStore, withState } from '@ngrx/signals';
3
+ import { withMapper } from '../features/with-mapper';
4
+ import { withDevtools } from '../with-devtools';
5
+ import { setupExtensions } from './helpers.spec';
6
+
7
+ function domRemover(state: Record<string, unknown>) {
8
+ return Object.keys(state).reduce((acc, key) => {
9
+ const value = state[key];
10
+
11
+ if (value instanceof HTMLElement) {
12
+ return acc;
13
+ } else {
14
+ return { ...acc, [key]: value };
15
+ }
16
+ }, {});
17
+ }
18
+
19
+ describe('with-mapper', () => {
20
+ it('should remove DOM Nodes', () => {
21
+ const { sendSpy } = setupExtensions();
22
+
23
+ const Store = signalStore(
24
+ { providedIn: 'root' },
25
+ withState({
26
+ name: 'Car',
27
+ carElement: document.createElement('div'),
28
+ }),
29
+ withDevtools('shop', withMapper(domRemover)),
30
+ );
31
+
32
+ TestBed.inject(Store);
33
+ TestBed.flushEffects();
34
+ expect(sendSpy).toHaveBeenCalledWith(
35
+ { type: 'Store Update' },
36
+ { shop: { name: 'Car' } },
37
+ );
38
+ });
39
+
40
+ it('should every property ending with *Key', () => {
41
+ const { sendSpy } = setupExtensions();
42
+ const Store = signalStore(
43
+ { providedIn: 'root' },
44
+ withState({
45
+ name: 'Car',
46
+ unlockKey: '1234',
47
+ }),
48
+ withDevtools(
49
+ 'shop',
50
+ withMapper((state: Record<string, unknown>) =>
51
+ Object.keys(state).reduce((acc, key) => {
52
+ if (key.endsWith('Key')) {
53
+ return acc;
54
+ } else {
55
+ return { ...acc, [key]: state[key] };
56
+ }
57
+ }, {}),
58
+ ),
59
+ ),
60
+ );
61
+
62
+ TestBed.inject(Store);
63
+ TestBed.flushEffects();
64
+ expect(sendSpy).toHaveBeenCalledWith(
65
+ { type: 'Store Update' },
66
+ { shop: { name: 'Car' } },
67
+ );
68
+ });
69
+ });
@@ -0,0 +1,38 @@
1
+ import {
2
+ patchState as originalPatchState,
3
+ PartialStateUpdater,
4
+ WritableStateSource,
5
+ } from '@ngrx/signals';
6
+ import { currentActionNames } from './internal/current-action-names';
7
+
8
+ type PatchFn = typeof originalPatchState extends (
9
+ arg1: infer First,
10
+ ...args: infer Rest
11
+ ) => infer Returner
12
+ ? (state: First, action: string, ...rest: Rest) => Returner
13
+ : never;
14
+
15
+ /**
16
+ * @deprecated Has been renamed to `updateState`
17
+ */
18
+ export const patchState: PatchFn = (state, action, ...rest) => {
19
+ updateState(state, action, ...rest);
20
+ };
21
+
22
+ /**
23
+ * Wrapper of `patchState` for DevTools integration. Next to updating the state,
24
+ * it also sends the action to the DevTools.
25
+ * @param stateSource state of Signal Store
26
+ * @param action name of action how it will show in DevTools
27
+ * @param updaters updater functions or objects
28
+ */
29
+ export function updateState<State extends object>(
30
+ stateSource: WritableStateSource<State>,
31
+ action: string,
32
+ ...updaters: Array<
33
+ Partial<NoInfer<State>> | PartialStateUpdater<NoInfer<State>>
34
+ >
35
+ ): void {
36
+ currentActionNames.add(action);
37
+ return originalPatchState(stateSource, ...updaters);
38
+ }
@@ -0,0 +1,6 @@
1
+ import { withDevtools } from './with-devtools';
2
+
3
+ /**
4
+ * Stub for DevTools integration. Can be used to disable DevTools in production.
5
+ */
6
+ export const withDevToolsStub: typeof withDevtools = () => (store) => store;
@@ -0,0 +1,81 @@
1
+ import { inject, InjectionToken } from '@angular/core';
2
+ import {
3
+ EmptyFeatureResult,
4
+ SignalStoreFeature,
5
+ signalStoreFeature,
6
+ withHooks,
7
+ withMethods,
8
+ } from '@ngrx/signals';
9
+ import { DefaultTracker } from './internal/default-tracker';
10
+ import {
11
+ DevtoolsFeature,
12
+ DevtoolsInnerOptions,
13
+ } from './internal/devtools-feature';
14
+ import { DevtoolsSyncer } from './internal/devtools-syncer.service';
15
+ import { ReduxDevtoolsExtension } from './internal/models';
16
+
17
+ declare global {
18
+ interface Window {
19
+ __REDUX_DEVTOOLS_EXTENSION__: ReduxDevtoolsExtension | undefined;
20
+ }
21
+ }
22
+
23
+ export const renameDevtoolsMethodName = '___renameDevtoolsName';
24
+ export const uniqueDevtoolsId = '___uniqueDevtoolsId';
25
+
26
+ const EXISTING_NAMES = new InjectionToken(
27
+ 'Array contain existing names for the signal stores',
28
+ { factory: () => [] as string[], providedIn: 'root' },
29
+ );
30
+
31
+ /**
32
+ * Adds this store as a feature state to the Redux DevTools.
33
+ *
34
+ * By default, the action name is 'Store Update'. You can
35
+ * change that via the {@link updateState} method, which has as second
36
+ * parameter the action name.
37
+ *
38
+ * The standalone function {@link renameDevtoolsName} can rename
39
+ * the store name.
40
+ *
41
+ * @param name name of the store as it should appear in the DevTools
42
+ * @param features features to extend or modify the behavior of the Devtools
43
+ */
44
+ export function withDevtools(name: string, ...features: DevtoolsFeature[]) {
45
+ return signalStoreFeature(
46
+ withMethods(() => {
47
+ const syncer = inject(DevtoolsSyncer);
48
+
49
+ const id = syncer.getNextId();
50
+
51
+ // TODO: use withProps and symbols
52
+ return {
53
+ [renameDevtoolsMethodName]: (newName: string) => {
54
+ syncer.renameStore(name, newName);
55
+ },
56
+ [uniqueDevtoolsId]: () => id,
57
+ } as Record<string, (newName?: unknown) => unknown>;
58
+ }),
59
+ withHooks((store) => {
60
+ const syncer = inject(DevtoolsSyncer);
61
+ const id = String(store[uniqueDevtoolsId]());
62
+ return {
63
+ onInit() {
64
+ const id = String(store[uniqueDevtoolsId]());
65
+ const finalOptions: DevtoolsInnerOptions = {
66
+ indexNames: !features.some((f) => f.indexNames === false),
67
+ map: features.find((f) => f.map)?.map ?? ((state) => state),
68
+ tracker: inject(
69
+ features.find((f) => f.tracker)?.tracker || DefaultTracker,
70
+ ),
71
+ };
72
+
73
+ syncer.addStore(id, name, store, finalOptions);
74
+ },
75
+ onDestroy() {
76
+ syncer.removeStore(id);
77
+ },
78
+ };
79
+ }),
80
+ ) as SignalStoreFeature<EmptyFeatureResult, EmptyFeatureResult>;
81
+ }
@@ -0,0 +1,43 @@
1
+ /**
2
+ * Deep freezes a state object along its properties with primitive values
3
+ * on the first level.
4
+ *
5
+ * The reason for this is that the final state is a merge of all
6
+ * root properties of all states, i.e. `withState`,....
7
+ *
8
+ * Since the root object will not be part of the state (shadow clone),
9
+ * we are not freezing it.
10
+ */
11
+
12
+ export function deepFreeze<T extends Record<string | symbol, unknown>>(
13
+ target: T,
14
+ // if empty all properties will be frozen
15
+ propertyNamesToBeFrozen: (string | symbol)[],
16
+ // also means that we are on the first level
17
+ isRoot = true,
18
+ ): void {
19
+ const runPropertyNameCheck = propertyNamesToBeFrozen.length > 0;
20
+ for (const key of Reflect.ownKeys(target)) {
21
+ if (runPropertyNameCheck && !propertyNamesToBeFrozen.includes(key)) {
22
+ continue;
23
+ }
24
+
25
+ const propValue = target[key];
26
+ if (isRecordLike(propValue) && !Object.isFrozen(propValue)) {
27
+ Object.freeze(propValue);
28
+ deepFreeze(propValue, [], false);
29
+ } else if (isRoot) {
30
+ Object.defineProperty(target, key, {
31
+ value: propValue,
32
+ writable: false,
33
+ configurable: false,
34
+ });
35
+ }
36
+ }
37
+ }
38
+
39
+ function isRecordLike(
40
+ target: unknown,
41
+ ): target is Record<string | symbol, unknown> {
42
+ return typeof target === 'object' && target !== null;
43
+ }
@@ -0,0 +1,6 @@
1
+ import { isDevMode as ngIsInDevMode } from '@angular/core';
2
+
3
+ // necessary wrapper function to test prod mode
4
+ export function isDevMode() {
5
+ return ngIsInDevMode();
6
+ }