Skip to content

Commit 39cbc4f

Browse files
feat: move devtools constraint to runtime check
1 parent b2a8f1f commit 39cbc4f

6 files changed

Lines changed: 65 additions & 81 deletions

File tree

libs/ngrx-toolkit/src/lib/devtools/features/with-glitch-tracking.ts

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { createDevtoolsFeature } from '../internal/devtools-feature';
22
import { GlitchTrackerService } from '../internal/glitch-tracker.service';
33

4-
export const GLITCH_TRACKING_FEATURE = 'GLITCH_TRACKING_FEATURE' as const;
4+
export const GLITCH_TRACKING_FEATURE = 'GLITCH_TRACKING_FEATURE';
55

66
/**
77
* It tracks all state changes of the State, including intermediary updates
@@ -33,10 +33,8 @@ export const GLITCH_TRACKING_FEATURE = 'GLITCH_TRACKING_FEATURE' as const;
3333
* Without `withGlitchTracking`, the DevTools would only show the final value of 3.
3434
*/
3535
export function withGlitchTracking() {
36-
return createDevtoolsFeature(
37-
{
38-
tracker: GlitchTrackerService,
39-
},
40-
GLITCH_TRACKING_FEATURE,
41-
);
36+
return createDevtoolsFeature({
37+
name: GLITCH_TRACKING_FEATURE,
38+
tracker: GlitchTrackerService,
39+
});
4240
}

libs/ngrx-toolkit/src/lib/devtools/internal/devtools-feature.ts

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ export const DEVTOOLS_FEATURE = Symbol('DEVTOOLS_FEATURE');
55
export type Mapper = (state: object) => object;
66

77
export type DevtoolsOptions = {
8+
name?: string; // defines the name of the feature
89
indexNames?: boolean; // defines if names should be indexed.
910
map?: Mapper; // defines a mapper for the state.
1011
tracker?: new () => Tracker; // defines a tracker for the state
@@ -23,18 +24,15 @@ export type DevtoolsInnerOptions = {
2324
* We use them (function calls) instead of a config object,
2425
* because of tree-shaking.
2526
*/
26-
export type DevtoolsFeature<Name extends string> = {
27+
export type DevtoolsFeature = {
2728
[DEVTOOLS_FEATURE]: true;
28-
name: Name;
2929
} & Partial<DevtoolsOptions>;
3030

31-
export function createDevtoolsFeature<Name extends string = ''>(
31+
export function createDevtoolsFeature(
3232
options: DevtoolsOptions,
33-
name: Name = '' as Name,
34-
): DevtoolsFeature<Name> {
33+
): DevtoolsFeature {
3534
return {
3635
[DEVTOOLS_FEATURE]: true,
3736
...options,
38-
name,
3937
};
4038
}

libs/ngrx-toolkit/src/lib/devtools/tests/with-tracked-reducer.spec.ts

Lines changed: 25 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -202,26 +202,46 @@ describe('withTrackedReducer', () => {
202202
);
203203
});
204204

205-
describe('types', () => {
205+
describe('devtools checks', () => {
206206
it('should fail if `withDevtools` is not used', () => {
207-
signalStore(withState({ count: 0 }), withTrackedReducer());
207+
const Store = signalStore(
208+
{ providedIn: 'root' },
209+
withState({ count: 0 }),
210+
withTrackedReducer(),
211+
);
212+
expect(() => TestBed.inject(Store)).toThrow(
213+
`In order to use withTrackedReducer, you must first enable the devtools feature via withDevtools('[your store name]', withGlitchTracking())`,
214+
);
208215
});
216+
209217
it('should fail during runtime if `withDevtools` is missing glitched tracking', () => {
210-
signalStore(withDevtools('store'), withTrackedReducer());
218+
const Store = signalStore(
219+
{ providedIn: 'root' },
220+
withDevtools('store'),
221+
withTrackedReducer(),
222+
);
223+
expect(() => TestBed.inject(Store)).toThrow(
224+
`In order to use withTrackedReducer, you must first enable the glitch tracking devtools feature via withDevtools('[your store name]', withGlitchTracking())`,
225+
);
211226
});
227+
212228
it('should succeed if `withDevtools` is used with glitched tracking', () => {
213229
// In order to have a type-safe test
214-
signalStore(
230+
const Store = signalStore(
231+
{ providedIn: 'root' },
215232
withDevtools('store', withGlitchTracking()),
216233
withTrackedReducer(),
217234
);
235+
expect(() => TestBed.inject(Store)).not.toThrow();
218236
});
219237

220238
it('should also work with multiple devtools features', () => {
221-
signalStore(
239+
const Store = signalStore(
240+
{ providedIn: 'root' },
222241
withDevtools('store', withGlitchTracking(), withDisabledNameIndices()),
223242
withTrackedReducer(),
224243
);
244+
expect(() => TestBed.inject(Store)).not.toThrow();
225245
});
226246
});
227247
});
Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,11 @@
11
import { signalStoreFeature, withProps } from '@ngrx/signals';
2-
import { DEVTOOL_PROP, withDevtools } from './with-devtools';
3-
2+
import { DEVTOOL_FEATURE_NAMES, withDevtools } from './with-devtools';
43
/**
54
* Stub for DevTools integration. Can be used to disable DevTools in production.
65
*/
76
export const withDevToolsStub: typeof withDevtools = () =>
8-
signalStoreFeature(withProps(() => ({ [DEVTOOL_PROP]: [] as [] })));
7+
signalStoreFeature(
8+
withProps(() => ({
9+
[DEVTOOL_FEATURE_NAMES]: [],
10+
})),
11+
);

libs/ngrx-toolkit/src/lib/devtools/with-devtools.ts

Lines changed: 4 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,7 @@ import { DevtoolsSyncer } from './internal/devtools-syncer.service';
1616
import { ReduxDevtoolsExtension } from './internal/models';
1717

1818
// Users requested that we export this type: https://github.com/angular-architects/ngrx-toolkit/issues/178
19-
export type DevtoolsFeature<Name extends string = string> =
20-
DevtoolsFeatureInternal<Name>;
19+
export type DevtoolsFeature = DevtoolsFeatureInternal;
2120

2221
declare global {
2322
interface Window {
@@ -28,7 +27,7 @@ declare global {
2827
export const renameDevtoolsMethodName = '___renameDevtoolsName';
2928
export const uniqueDevtoolsId = '___uniqueDevtoolsId';
3029
// Used to declare the existence of the devtools extension
31-
export const DEVTOOL_PROP = Symbol('DEVTOOL_PROP');
30+
export const DEVTOOL_FEATURE_NAMES = Symbol('DEVTOOL_PROP');
3231

3332
/**
3433
* Adds this store as a feature state to the Redux DevTools.
@@ -43,51 +42,6 @@ export const DEVTOOL_PROP = Symbol('DEVTOOL_PROP');
4342
* @param name name of the store as it should appear in the DevTools
4443
* @param features features to extend or modify the behavior of the Devtools
4544
*/
46-
export function withDevtools(
47-
name: string,
48-
): SignalStoreFeature<
49-
EmptyFeatureResult,
50-
EmptyFeatureResult & { props: { [DEVTOOL_PROP]: [] } }
51-
>;
52-
53-
export function withDevtools<DV1 extends DevtoolsFeature>(
54-
name: string,
55-
feature: DV1,
56-
): SignalStoreFeature<
57-
EmptyFeatureResult,
58-
EmptyFeatureResult & { props: { [DEVTOOL_PROP]: DV1['name'] } }
59-
>;
60-
61-
export function withDevtools<
62-
DV1 extends DevtoolsFeature,
63-
DV2 extends DevtoolsFeature,
64-
>(
65-
name: string,
66-
feature1: DV1,
67-
feature2: DV2,
68-
): SignalStoreFeature<
69-
EmptyFeatureResult,
70-
EmptyFeatureResult & {
71-
props: { [DEVTOOL_PROP]: DV1['name'] | DV2['name'] };
72-
}
73-
>;
74-
75-
export function withDevtools<
76-
DV1 extends DevtoolsFeature,
77-
DV2 extends DevtoolsFeature,
78-
DV3 extends DevtoolsFeature,
79-
>(
80-
name: string,
81-
feature1: DV1,
82-
feature2: DV2,
83-
feature3: DV3,
84-
): SignalStoreFeature<
85-
EmptyFeatureResult,
86-
EmptyFeatureResult & {
87-
props: { [DEVTOOL_PROP]: DV1['name'] | DV2['name'] | DV3['name'] };
88-
}
89-
>;
90-
9145
export function withDevtools(name: string, ...features: DevtoolsFeature[]) {
9246
return signalStoreFeature(
9347
withMethods(() => {
@@ -104,7 +58,7 @@ export function withDevtools(name: string, ...features: DevtoolsFeature[]) {
10458
} as Record<string, (newName?: unknown) => unknown>;
10559
}),
10660
withProps(() => ({
107-
[DEVTOOL_PROP]: features.filter((f) => f.name).map((f) => f.name),
61+
[DEVTOOL_FEATURE_NAMES]: features.filter(Boolean).map((f) => f.name),
10862
})),
10963
withHooks((store) => {
11064
const syncer = inject(DevtoolsSyncer);
@@ -128,6 +82,6 @@ export function withDevtools(name: string, ...features: DevtoolsFeature[]) {
12882
}),
12983
) as SignalStoreFeature<
13084
EmptyFeatureResult,
131-
EmptyFeatureResult & { props: { [DEVTOOL_PROP]: unknown } }
85+
EmptyFeatureResult & { props: { [DEVTOOL_FEATURE_NAMES]: string[] } }
13286
>;
13387
}

libs/ngrx-toolkit/src/lib/devtools/with-tracked-reducer.ts

Lines changed: 21 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import {
66
SignalStoreFeature,
77
signalStoreFeature,
88
type,
9+
withHooks,
910
} from '@ngrx/signals';
1011
import {
1112
EventCreator,
@@ -15,22 +16,14 @@ import {
1516
import { tap } from 'rxjs/operators';
1617
import { GLITCH_TRACKING_FEATURE } from './features/with-glitch-tracking';
1718
import { updateState } from './update-state';
18-
import { DEVTOOL_PROP } from './with-devtools';
19+
import { DEVTOOL_FEATURE_NAMES } from './with-devtools';
1920

20-
export function withTrackedReducer<
21-
State extends object,
22-
DevtoolsFeatureName extends string,
23-
>(
21+
export function withTrackedReducer<State extends object>(
2422
// eslint-disable-next-line @typescript-eslint/no-explicit-any
2523
...caseReducers: CaseReducerResult<State, any>[]
2624
): SignalStoreFeature<
2725
EmptyFeatureResult & {
2826
state: State;
29-
props: {
30-
[DEVTOOL_PROP]: typeof GLITCH_TRACKING_FEATURE extends DevtoolsFeatureName
31-
? DevtoolsFeatureName
32-
: 'NO GLITCHED TRACKING ACTIVATED';
33-
};
3427
},
3528
EmptyFeatureResult
3629
> {
@@ -51,6 +44,24 @@ export function withTrackedReducer<
5144
),
5245
),
5346
),
47+
withHooks((store) => ({
48+
onInit() {
49+
if (!(DEVTOOL_FEATURE_NAMES in store)) {
50+
throw new Error(
51+
`In order to use withTrackedReducer, you must first enable the devtools feature via withDevtools('[your store name]', withGlitchTracking())`,
52+
);
53+
}
54+
if (
55+
!(store[DEVTOOL_FEATURE_NAMES] as string[]).includes(
56+
GLITCH_TRACKING_FEATURE,
57+
)
58+
) {
59+
throw new Error(
60+
`In order to use withTrackedReducer, you must first enable the glitch tracking devtools feature via withDevtools('[your store name]', withGlitchTracking())`,
61+
);
62+
}
63+
},
64+
})),
5465
);
5566
}
5667

0 commit comments

Comments
 (0)