@angular-architects/ngrx-toolkit 19.4.0 → 19.4.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.
- package/fesm2022/angular-architects-ngrx-toolkit-redux-connector.mjs +119 -0
- package/fesm2022/angular-architects-ngrx-toolkit-redux-connector.mjs.map +1 -0
- package/fesm2022/angular-architects-ngrx-toolkit.mjs +2108 -0
- package/fesm2022/angular-architects-ngrx-toolkit.mjs.map +1 -0
- package/{src/index.ts → index.d.ts} +5 -32
- package/lib/assertions/assertions.d.ts +2 -0
- package/{src/lib/devtools/features/with-disabled-name-indicies.ts → lib/devtools/features/with-disabled-name-indicies.d.ts} +1 -5
- package/{src/lib/devtools/features/with-glitch-tracking.ts → lib/devtools/features/with-glitch-tracking.d.ts} +1 -6
- package/{src/lib/devtools/features/with-mapper.ts → lib/devtools/features/with-mapper.d.ts} +1 -7
- package/lib/devtools/internal/current-action-names.d.ts +1 -0
- package/lib/devtools/internal/default-tracker.d.ts +13 -0
- package/lib/devtools/internal/devtools-feature.d.ts +24 -0
- package/lib/devtools/internal/devtools-syncer.service.d.ts +39 -0
- package/lib/devtools/internal/glitch-tracker.service.d.ts +18 -0
- package/lib/devtools/internal/models.d.ts +24 -0
- package/{src/lib/devtools/provide-devtools-config.ts → lib/devtools/provide-devtools-config.d.ts} +4 -16
- package/lib/devtools/rename-devtools-name.d.ts +7 -0
- package/lib/devtools/update-state.d.ts +15 -0
- package/{src/lib/devtools/with-dev-tools-stub.ts → lib/devtools/with-dev-tools-stub.d.ts} +1 -2
- package/lib/devtools/with-devtools.d.ts +24 -0
- package/lib/flattening-operator.d.ts +14 -0
- package/lib/immutable-state/deep-freeze.d.ts +11 -0
- package/lib/immutable-state/is-dev-mode.d.ts +1 -0
- package/lib/immutable-state/with-immutable-state.d.ts +60 -0
- package/lib/mutation/http-mutation.d.ts +86 -0
- package/lib/mutation/mutation.d.ts +20 -0
- package/lib/mutation/rx-mutation.d.ts +76 -0
- package/{src/lib/shared/signal-store-models.ts → lib/shared/signal-store-models.d.ts} +4 -8
- package/lib/shared/throw-if-null.d.ts +1 -0
- package/lib/storage-sync/features/with-indexed-db.d.ts +2 -0
- package/lib/storage-sync/features/with-local-storage.d.ts +3 -0
- package/lib/storage-sync/internal/indexeddb.service.d.ts +29 -0
- package/lib/storage-sync/internal/local-storage.service.d.ts +8 -0
- package/lib/storage-sync/internal/models.d.ts +45 -0
- package/lib/storage-sync/internal/session-storage.service.d.ts +8 -0
- package/lib/storage-sync/with-storage-sync.d.ts +45 -0
- package/lib/with-call-state.d.ts +58 -0
- package/{src/lib/with-conditional.ts → lib/with-conditional.d.ts} +7 -31
- package/lib/with-data-service.d.ts +109 -0
- package/{src/lib/with-feature-factory.ts → lib/with-feature-factory.d.ts} +4 -32
- package/lib/with-mutations.d.ts +51 -0
- package/lib/with-pagination.d.ts +98 -0
- package/lib/with-redux.d.ts +147 -0
- package/lib/with-reset.d.ts +29 -0
- package/lib/with-undo-redo.d.ts +31 -0
- package/package.json +21 -4
- package/redux-connector/index.d.ts +2 -0
- package/redux-connector/src/lib/create-redux.d.ts +13 -0
- package/redux-connector/src/lib/model.d.ts +40 -0
- package/redux-connector/src/lib/rxjs-interop/redux-method.d.ts +14 -0
- package/redux-connector/src/lib/signal-redux-store.d.ts +11 -0
- package/redux-connector/src/lib/util.d.ts +5 -0
- package/eslint.config.cjs +0 -43
- package/jest.config.ts +0 -22
- package/ng-package.json +0 -7
- package/project.json +0 -37
- package/redux-connector/docs/README.md +0 -131
- package/redux-connector/index.ts +0 -6
- package/redux-connector/ng-package.json +0 -5
- package/redux-connector/src/lib/create-redux.ts +0 -102
- package/redux-connector/src/lib/model.ts +0 -89
- package/redux-connector/src/lib/rxjs-interop/redux-method.ts +0 -66
- package/redux-connector/src/lib/signal-redux-store.ts +0 -59
- package/redux-connector/src/lib/util.ts +0 -22
- package/src/lib/assertions/assertions.ts +0 -9
- package/src/lib/devtools/internal/current-action-names.ts +0 -1
- package/src/lib/devtools/internal/default-tracker.ts +0 -60
- package/src/lib/devtools/internal/devtools-feature.ts +0 -37
- package/src/lib/devtools/internal/devtools-syncer.service.ts +0 -202
- package/src/lib/devtools/internal/glitch-tracker.service.ts +0 -61
- package/src/lib/devtools/internal/models.ts +0 -29
- package/src/lib/devtools/rename-devtools-name.ts +0 -21
- package/src/lib/devtools/tests/action-name.spec.ts +0 -48
- package/src/lib/devtools/tests/basic.spec.ts +0 -111
- package/src/lib/devtools/tests/connecting.spec.ts +0 -37
- package/src/lib/devtools/tests/helpers.spec.ts +0 -43
- package/src/lib/devtools/tests/naming.spec.ts +0 -216
- package/src/lib/devtools/tests/provide-devtools-config.spec.ts +0 -25
- package/src/lib/devtools/tests/types.spec.ts +0 -19
- package/src/lib/devtools/tests/update-state.spec.ts +0 -29
- package/src/lib/devtools/tests/with-devtools.spec.ts +0 -5
- package/src/lib/devtools/tests/with-glitch-tracking.spec.ts +0 -272
- package/src/lib/devtools/tests/with-mapper.spec.ts +0 -69
- package/src/lib/devtools/update-state.ts +0 -38
- package/src/lib/devtools/with-devtools.ts +0 -81
- package/src/lib/flattening-operator.ts +0 -42
- package/src/lib/immutable-state/deep-freeze.ts +0 -43
- package/src/lib/immutable-state/is-dev-mode.ts +0 -6
- package/src/lib/immutable-state/tests/with-immutable-state.spec.ts +0 -260
- package/src/lib/immutable-state/with-immutable-state.ts +0 -115
- package/src/lib/mutation/http-mutation.spec.ts +0 -473
- package/src/lib/mutation/http-mutation.ts +0 -172
- package/src/lib/mutation/mutation.ts +0 -26
- package/src/lib/mutation/rx-mutation.spec.ts +0 -594
- package/src/lib/mutation/rx-mutation.ts +0 -208
- package/src/lib/shared/prettify.ts +0 -3
- package/src/lib/shared/throw-if-null.ts +0 -7
- package/src/lib/storage-sync/features/with-indexed-db.ts +0 -81
- package/src/lib/storage-sync/features/with-local-storage.ts +0 -58
- package/src/lib/storage-sync/internal/indexeddb.service.ts +0 -124
- package/src/lib/storage-sync/internal/local-storage.service.ts +0 -19
- package/src/lib/storage-sync/internal/models.ts +0 -62
- package/src/lib/storage-sync/internal/session-storage.service.ts +0 -18
- package/src/lib/storage-sync/tests/indexeddb.service.spec.ts +0 -99
- package/src/lib/storage-sync/tests/with-storage-async.spec.ts +0 -308
- package/src/lib/storage-sync/tests/with-storage-sync.spec.ts +0 -268
- package/src/lib/storage-sync/with-storage-sync.ts +0 -233
- package/src/lib/with-call-state.spec.ts +0 -42
- package/src/lib/with-call-state.ts +0 -195
- package/src/lib/with-conditional.spec.ts +0 -125
- package/src/lib/with-data-service.spec.ts +0 -564
- package/src/lib/with-data-service.ts +0 -433
- package/src/lib/with-feature-factory.spec.ts +0 -69
- package/src/lib/with-mutations.spec.ts +0 -537
- package/src/lib/with-mutations.ts +0 -146
- package/src/lib/with-pagination.spec.ts +0 -90
- package/src/lib/with-pagination.ts +0 -353
- package/src/lib/with-redux.spec.ts +0 -258
- package/src/lib/with-redux.ts +0 -387
- package/src/lib/with-reset.spec.ts +0 -112
- package/src/lib/with-reset.ts +0 -62
- package/src/lib/with-undo-redo.spec.ts +0 -287
- package/src/lib/with-undo-redo.ts +0 -199
- package/src/test-setup.ts +0 -8
- package/tsconfig.json +0 -29
- package/tsconfig.lib.json +0 -17
- package/tsconfig.lib.prod.json +0 -9
- package/tsconfig.spec.json +0 -17
|
@@ -1,81 +0,0 @@
|
|
|
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
|
-
}
|
|
@@ -1,42 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
concatMap,
|
|
3
|
-
exhaustMap,
|
|
4
|
-
mergeMap,
|
|
5
|
-
ObservableInput,
|
|
6
|
-
ObservedValueOf,
|
|
7
|
-
OperatorFunction,
|
|
8
|
-
switchMap,
|
|
9
|
-
} from 'rxjs';
|
|
10
|
-
|
|
11
|
-
export type RxJsFlatteningOperator = <T, O extends ObservableInput<unknown>>(
|
|
12
|
-
project: (value: T, index: number) => O,
|
|
13
|
-
) => OperatorFunction<T, ObservedValueOf<O>>;
|
|
14
|
-
|
|
15
|
-
/**
|
|
16
|
-
* A wrapper for an RxJS flattening operator.
|
|
17
|
-
* This wrapper informs about whether the operator has exhaust semantics or not.
|
|
18
|
-
*/
|
|
19
|
-
export type FlatteningOperator = {
|
|
20
|
-
rxJsOperator: RxJsFlatteningOperator;
|
|
21
|
-
exhaustSemantics: boolean;
|
|
22
|
-
};
|
|
23
|
-
|
|
24
|
-
export const switchOp: FlatteningOperator = {
|
|
25
|
-
rxJsOperator: switchMap,
|
|
26
|
-
exhaustSemantics: false,
|
|
27
|
-
};
|
|
28
|
-
|
|
29
|
-
export const mergeOp: FlatteningOperator = {
|
|
30
|
-
rxJsOperator: mergeMap,
|
|
31
|
-
exhaustSemantics: false,
|
|
32
|
-
};
|
|
33
|
-
|
|
34
|
-
export const concatOp: FlatteningOperator = {
|
|
35
|
-
rxJsOperator: concatMap,
|
|
36
|
-
exhaustSemantics: false,
|
|
37
|
-
};
|
|
38
|
-
|
|
39
|
-
export const exhaustOp: FlatteningOperator = {
|
|
40
|
-
rxJsOperator: exhaustMap,
|
|
41
|
-
exhaustSemantics: true,
|
|
42
|
-
};
|
|
@@ -1,43 +0,0 @@
|
|
|
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
|
-
}
|
|
@@ -1,260 +0,0 @@
|
|
|
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 ${protectionOn ? '' : 'not '}on a mutable change`, () => {
|
|
64
|
-
const state = stateFactory();
|
|
65
|
-
|
|
66
|
-
const patch = () =>
|
|
67
|
-
patchState(state, (state) => {
|
|
68
|
-
state.ngrx = 'mutable change';
|
|
69
|
-
return state;
|
|
70
|
-
});
|
|
71
|
-
|
|
72
|
-
if (protectionOn) {
|
|
73
|
-
expect(patch).toThrowError(
|
|
74
|
-
"Cannot assign to read only property 'ngrx' of object",
|
|
75
|
-
);
|
|
76
|
-
} else {
|
|
77
|
-
expect(patch).not.toThrowError();
|
|
78
|
-
}
|
|
79
|
-
});
|
|
80
|
-
|
|
81
|
-
it(`throws ${
|
|
82
|
-
protectionOn ? '' : 'not '
|
|
83
|
-
}on a nested mutable change`, () => {
|
|
84
|
-
const state = stateFactory();
|
|
85
|
-
|
|
86
|
-
const patch = () =>
|
|
87
|
-
patchState(state, (state) => {
|
|
88
|
-
state.user.firstName = 'mutable change';
|
|
89
|
-
return state;
|
|
90
|
-
});
|
|
91
|
-
|
|
92
|
-
if (protectionOn) {
|
|
93
|
-
expect(patch).toThrowError(
|
|
94
|
-
"Cannot assign to read only property 'firstName' of object",
|
|
95
|
-
);
|
|
96
|
-
} else {
|
|
97
|
-
expect(patch).not.toThrowError();
|
|
98
|
-
}
|
|
99
|
-
});
|
|
100
|
-
|
|
101
|
-
describe('mutable changes outside of patchState', () => {
|
|
102
|
-
it(`throws${
|
|
103
|
-
protectionOn ? '' : ' not'
|
|
104
|
-
} on reassigned a property of the exposed state`, () => {
|
|
105
|
-
const state = stateFactory();
|
|
106
|
-
const patch = () => {
|
|
107
|
-
state.user().firstName = 'mutable change 1';
|
|
108
|
-
};
|
|
109
|
-
if (protectionOn) {
|
|
110
|
-
expect(patch).toThrowError(
|
|
111
|
-
"Cannot assign to read only property 'firstName' of object",
|
|
112
|
-
);
|
|
113
|
-
} else {
|
|
114
|
-
expect(patch).not.toThrowError();
|
|
115
|
-
}
|
|
116
|
-
});
|
|
117
|
-
|
|
118
|
-
it(`throws ${
|
|
119
|
-
protectionOn ? '' : 'not '
|
|
120
|
-
}when exposed state via getState is mutated`, () => {
|
|
121
|
-
const state = stateFactory();
|
|
122
|
-
const s = getState(state);
|
|
123
|
-
|
|
124
|
-
const patch = () => (s.ngrx = 'mutable change 2');
|
|
125
|
-
|
|
126
|
-
if (protectionOn) {
|
|
127
|
-
expect(patch).toThrowError(
|
|
128
|
-
"Cannot assign to read only property 'ngrx' of object",
|
|
129
|
-
);
|
|
130
|
-
} else {
|
|
131
|
-
expect(patch).not.toThrowError();
|
|
132
|
-
}
|
|
133
|
-
});
|
|
134
|
-
|
|
135
|
-
it(`throws ${
|
|
136
|
-
protectionOn ? '' : 'not '
|
|
137
|
-
}when mutable change happens`, () => {
|
|
138
|
-
const state = stateFactory();
|
|
139
|
-
const s = { user: { firstName: 'M', lastName: 'S' } };
|
|
140
|
-
patchState(state, s);
|
|
141
|
-
const patch = () => {
|
|
142
|
-
s.user.firstName = 'mutable change 3';
|
|
143
|
-
};
|
|
144
|
-
if (protectionOn) {
|
|
145
|
-
expect(patch).toThrowError(
|
|
146
|
-
"Cannot assign to read only property 'firstName' of object",
|
|
147
|
-
);
|
|
148
|
-
} else {
|
|
149
|
-
expect(patch).not.toThrowError();
|
|
150
|
-
}
|
|
151
|
-
});
|
|
152
|
-
|
|
153
|
-
it(`throws ${
|
|
154
|
-
protectionOn ? '' : 'not '
|
|
155
|
-
}when mutable change of root symbol property happens`, () => {
|
|
156
|
-
const state = stateFactory();
|
|
157
|
-
const s = getState(state);
|
|
158
|
-
|
|
159
|
-
const patch = () => {
|
|
160
|
-
s[SECRET].code = 'mutable change';
|
|
161
|
-
};
|
|
162
|
-
|
|
163
|
-
if (protectionOn) {
|
|
164
|
-
expect(patch).toThrowError(
|
|
165
|
-
"Cannot assign to read only property 'code' of object",
|
|
166
|
-
);
|
|
167
|
-
} else {
|
|
168
|
-
expect(patch).not.toThrowError();
|
|
169
|
-
}
|
|
170
|
-
});
|
|
171
|
-
|
|
172
|
-
it(`throws ${
|
|
173
|
-
protectionOn ? '' : 'not '
|
|
174
|
-
}when mutable change of nested symbol property happens`, () => {
|
|
175
|
-
const state = stateFactory();
|
|
176
|
-
const s = getState(state);
|
|
177
|
-
|
|
178
|
-
const patch = () => {
|
|
179
|
-
s.nestedSymbol[SECRET] = 'mutable change';
|
|
180
|
-
};
|
|
181
|
-
|
|
182
|
-
if (protectionOn) {
|
|
183
|
-
expect(patch).toThrowError(
|
|
184
|
-
"Cannot assign to read only property 'Symbol(secret)' of object",
|
|
185
|
-
);
|
|
186
|
-
} else {
|
|
187
|
-
expect(patch).not.toThrowError();
|
|
188
|
-
}
|
|
189
|
-
});
|
|
190
|
-
});
|
|
191
|
-
|
|
192
|
-
describe('special tests', () => {
|
|
193
|
-
for (const { name, mutationFn } of [
|
|
194
|
-
{
|
|
195
|
-
name: 'location',
|
|
196
|
-
mutationFn: (state: { location: { city: string } }) =>
|
|
197
|
-
(state.location.city = 'Paris'),
|
|
198
|
-
},
|
|
199
|
-
{
|
|
200
|
-
name: 'user',
|
|
201
|
-
mutationFn: (state: { user: { firstName: string } }) =>
|
|
202
|
-
(state.user.firstName = 'Jane'),
|
|
203
|
-
},
|
|
204
|
-
]) {
|
|
205
|
-
it(`throws ${
|
|
206
|
-
protectionOn ? '' : 'not '
|
|
207
|
-
}on concatenated state (${name})`, () => {
|
|
208
|
-
const UserStore = signalStore(
|
|
209
|
-
{ providedIn: 'root' },
|
|
210
|
-
withImmutableState(createInitialState(), {
|
|
211
|
-
enableInProduction,
|
|
212
|
-
}),
|
|
213
|
-
withImmutableState(
|
|
214
|
-
{ location: { city: 'London' } },
|
|
215
|
-
{ enableInProduction },
|
|
216
|
-
),
|
|
217
|
-
);
|
|
218
|
-
const store = TestBed.inject(UserStore);
|
|
219
|
-
const state = getState(store);
|
|
220
|
-
|
|
221
|
-
if (protectionOn) {
|
|
222
|
-
expect(() => mutationFn(state)).toThrowError();
|
|
223
|
-
} else {
|
|
224
|
-
expect(() => mutationFn(state)).not.toThrowError();
|
|
225
|
-
}
|
|
226
|
-
});
|
|
227
|
-
}
|
|
228
|
-
});
|
|
229
|
-
|
|
230
|
-
it('should be possible to mix both mutable and immutable state', () => {
|
|
231
|
-
const immutableState = {
|
|
232
|
-
id: 1,
|
|
233
|
-
name: 'John',
|
|
234
|
-
};
|
|
235
|
-
|
|
236
|
-
const mutableState = {
|
|
237
|
-
address: 'London',
|
|
238
|
-
};
|
|
239
|
-
const TestStore = signalStore(
|
|
240
|
-
{ providedIn: 'root', protectedState: false },
|
|
241
|
-
withImmutableState(immutableState, { enableInProduction }),
|
|
242
|
-
withState(mutableState),
|
|
243
|
-
);
|
|
244
|
-
|
|
245
|
-
TestBed.inject(TestStore);
|
|
246
|
-
|
|
247
|
-
if (protectionOn) {
|
|
248
|
-
expect(() => (immutableState.name = 'Jane')).toThrow();
|
|
249
|
-
} else {
|
|
250
|
-
expect(() => (immutableState.name = 'Jane')).not.toThrow();
|
|
251
|
-
}
|
|
252
|
-
expect(
|
|
253
|
-
() => (mutableState.address = 'Glastonbury'),
|
|
254
|
-
).not.toThrowError();
|
|
255
|
-
});
|
|
256
|
-
});
|
|
257
|
-
}
|
|
258
|
-
});
|
|
259
|
-
}
|
|
260
|
-
});
|
|
@@ -1,115 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
EmptyFeatureResult,
|
|
3
|
-
signalStoreFeature,
|
|
4
|
-
SignalStoreFeature,
|
|
5
|
-
SignalStoreFeatureResult,
|
|
6
|
-
watchState,
|
|
7
|
-
withHooks,
|
|
8
|
-
withState,
|
|
9
|
-
} from '@ngrx/signals';
|
|
10
|
-
import { deepFreeze } from './deep-freeze';
|
|
11
|
-
import { isDevMode } from './is-dev-mode';
|
|
12
|
-
|
|
13
|
-
/**
|
|
14
|
-
* The implementation of this feature is a little bit tricky.
|
|
15
|
-
*
|
|
16
|
-
* `signalStore` does a shallow clone in the initial phase, in order to
|
|
17
|
-
* merge all different states together.
|
|
18
|
-
*
|
|
19
|
-
* Shallow cloning also happens in `patchState`.
|
|
20
|
-
*
|
|
21
|
-
* With shallow cloning, the root state object is replaced, which means,
|
|
22
|
-
* the freezing only stays for its nested properties but not for
|
|
23
|
-
* the primitive and immediate properties.
|
|
24
|
-
*
|
|
25
|
-
* For example:
|
|
26
|
-
*
|
|
27
|
-
* ```ts
|
|
28
|
-
* const state = {
|
|
29
|
-
* id: 1,
|
|
30
|
-
* address: {
|
|
31
|
-
* street: 'Main St',
|
|
32
|
-
* city: 'Springfield',
|
|
33
|
-
* }
|
|
34
|
-
* }
|
|
35
|
-
* ```
|
|
36
|
-
*
|
|
37
|
-
* Running `Object.freeze` on `state` will freeze the `address` object, and
|
|
38
|
-
* the `id`. But since `state` is shallow cloned, the "frozing" state of the
|
|
39
|
-
* `id` is lost. `address`, being an object, is still frozen.
|
|
40
|
-
*
|
|
41
|
-
* To overcome that, we run `watchState` and run `deepFreeze`
|
|
42
|
-
* on every change.
|
|
43
|
-
*/
|
|
44
|
-
|
|
45
|
-
/**
|
|
46
|
-
* Prevents mutation of the state.
|
|
47
|
-
*
|
|
48
|
-
* This is done by deeply applying `Object.freeze`. Any mutable change within
|
|
49
|
-
* or outside the `SignalStore` will throw an error.
|
|
50
|
-
*
|
|
51
|
-
* @param state the state object
|
|
52
|
-
* @param options enable protection in production (default: false)
|
|
53
|
-
*/
|
|
54
|
-
export function withImmutableState<State extends object>(
|
|
55
|
-
state: State,
|
|
56
|
-
options?: { enableInProduction?: boolean },
|
|
57
|
-
): SignalStoreFeature<
|
|
58
|
-
SignalStoreFeatureResult,
|
|
59
|
-
EmptyFeatureResult & { state: State }
|
|
60
|
-
>;
|
|
61
|
-
/**
|
|
62
|
-
* Prevents mutation of the state.
|
|
63
|
-
*
|
|
64
|
-
* This is done by deeply applying `Object.freeze`. Any mutable change within
|
|
65
|
-
* or outside the `SignalStore` will throw an error.
|
|
66
|
-
*
|
|
67
|
-
* @param stateFactory a function returning the state object
|
|
68
|
-
* @param options enable protection in production (default: false)
|
|
69
|
-
*/
|
|
70
|
-
export function withImmutableState<State extends object>(
|
|
71
|
-
stateFactory: () => State,
|
|
72
|
-
options?: { enableInProduction?: boolean },
|
|
73
|
-
): SignalStoreFeature<
|
|
74
|
-
SignalStoreFeatureResult,
|
|
75
|
-
EmptyFeatureResult & { state: State }
|
|
76
|
-
>;
|
|
77
|
-
export function withImmutableState<State extends object>(
|
|
78
|
-
stateOrFactory: State | (() => State),
|
|
79
|
-
options?: { enableInProduction?: boolean },
|
|
80
|
-
): SignalStoreFeature<
|
|
81
|
-
SignalStoreFeatureResult,
|
|
82
|
-
EmptyFeatureResult & { state: State }
|
|
83
|
-
> {
|
|
84
|
-
const immutableState =
|
|
85
|
-
typeof stateOrFactory === 'function' ? stateOrFactory() : stateOrFactory;
|
|
86
|
-
const stateKeys = Reflect.ownKeys(immutableState);
|
|
87
|
-
|
|
88
|
-
const applyFreezing = isDevMode() || options?.enableInProduction === true;
|
|
89
|
-
return signalStoreFeature(
|
|
90
|
-
withState(immutableState),
|
|
91
|
-
withHooks((store) => ({
|
|
92
|
-
onInit() {
|
|
93
|
-
if (!applyFreezing) {
|
|
94
|
-
return;
|
|
95
|
-
}
|
|
96
|
-
/**
|
|
97
|
-
* `immutableState` will be initially frozen. That is because
|
|
98
|
-
* of potential mutations outside the SignalStore
|
|
99
|
-
*
|
|
100
|
-
* ```ts
|
|
101
|
-
* const initialState = {id: 1};
|
|
102
|
-
* signalStore(withImmutableState(initialState));
|
|
103
|
-
*
|
|
104
|
-
* initialState.id = 2; // must throw immutability
|
|
105
|
-
* ```
|
|
106
|
-
*/
|
|
107
|
-
|
|
108
|
-
Object.freeze(immutableState);
|
|
109
|
-
watchState(store, (state) => {
|
|
110
|
-
deepFreeze(state, stateKeys);
|
|
111
|
-
});
|
|
112
|
-
},
|
|
113
|
-
})),
|
|
114
|
-
);
|
|
115
|
-
}
|