Skip to content

Commit 7bf8b69

Browse files
committed
Fix mask specifier priorities
1 parent 0cd6c22 commit 7bf8b69

2 files changed

Lines changed: 63 additions & 53 deletions

File tree

package.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -164,6 +164,9 @@
164164
"use-sync-external-store": ">=1.2.0"
165165
},
166166
"peerDependenciesMeta": {
167+
"@redux-devtools/utils": {
168+
"optional": true
169+
},
167170
"@types/react": {
168171
"optional": true
169172
},

src/middleware/devtools.ts

Lines changed: 60 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,7 @@ type FunctionPaths<T, Prefix extends string = ""> = {
9696
: never;
9797
}[keyof T & string];
9898

99-
type ActionCreatorsMask<T extends Record<string, any> = Record<string, any>> = {
99+
type ActionCreatorsMask<T extends Record<string, any>> = {
100100
[K in FunctionPaths<T> | '*' | '**' | (string & {})]?: boolean | (
101101
// Allow overriding leafs
102102
K extends '*' | '**' | `${string}.*` | `${string}.**`
@@ -294,41 +294,21 @@ const devtoolsImpl: DevtoolsImpl =
294294
}
295295
return new Function('return ' + action)();
296296
}
297-
if (options.actionCreators) {
297+
298+
// Not using the redux plugin
299+
if (!(api as any).dispatch && options.actionCreators) {
298300
try {
299301
evalAction = require('@redux-devtools/utils').evalAction;
302+
actionCreators = getActionsArray(
303+
initialState as Record<string, unknown>,
304+
options.actionCreators as ActionCreatorsMask<{}>
305+
);
306+
// override to pass it to the extension connector, any is ok, we dont care about the type anymore
307+
options.actionCreators = actionCreators as any;
300308
} catch {
301309
console.warn('[zustand devtools middleware] Please install @redux-devtools/utils to use actionCreators in devtools middleware');
302310
}
303-
actionCreators = getActionsArray(
304-
initialState as Record<string, unknown>,
305-
options.actionCreators as ActionCreatorsMask<{}>
306-
);
307-
// override to pass it to the extension connector, any is ok, we dont care about the type anymore
308-
options.actionCreators = actionCreators as any;
309-
}
310311

311-
// We connect after extracting action creators from the initial state
312-
const { connection, ...connectionInformation } = extractConnectionInformation(store, extensionConnector, options);
313-
314-
if (connectionInformation.type === 'untracked') {
315-
connection?.init(initialState)
316-
} else {
317-
connectionInformation.stores[connectionInformation.store] = api
318-
connection?.init(
319-
Object.fromEntries(
320-
Object.entries(connectionInformation.stores).map(([key, store]) => [
321-
key,
322-
key === connectionInformation.store
323-
? initialState
324-
: store.getState(),
325-
]),
326-
),
327-
)
328-
}
329-
330-
// Not using the redux plugin
331-
if (!(api as any).dispatchFromDevtools && options.actionCreators) {
332312
(api as any).dispatchFromDevtools = true;
333313
(api as any).dispatch = (payload: {
334314
type: string,
@@ -353,6 +333,25 @@ const devtoolsImpl: DevtoolsImpl =
353333
}
354334
}
355335

336+
// We connect after extracting action creators from the initial state
337+
const { connection, ...connectionInformation } = extractConnectionInformation(store, extensionConnector, options);
338+
339+
if (connectionInformation.type === 'untracked') {
340+
connection?.init(initialState)
341+
} else {
342+
connectionInformation.stores[connectionInformation.store] = api
343+
connection?.init(
344+
Object.fromEntries(
345+
Object.entries(connectionInformation.stores).map(([key, store]) => [
346+
key,
347+
key === connectionInformation.store
348+
? initialState
349+
: store.getState(),
350+
]),
351+
),
352+
)
353+
}
354+
356355
if (
357356
(api as any).dispatchFromDevtools &&
358357
typeof (api as any).dispatch === 'function'
@@ -528,40 +527,44 @@ function maskToRegex(mask: string): RegExp {
528527
}
529528

530529
function getActionsArray<T extends Record<string, any>>(actionCreators: T, mask: ActionCreatorsMask<T>): ActionCreatorObject[] {
531-
if (Array.isArray(actionCreators)) return actionCreators;
532-
if (typeof actionCreators !== 'object') return [];
533-
534-
const getActionsArray = require('@redux-devtools/utils').getActionsArray;
535-
const flat = getActionsArray(actionCreators);
530+
const getActions = require('@redux-devtools/utils').getActionsArray;
531+
const flat = getActions(actionCreators);
536532
const actions = new Map<string, ActionCreatorObject>();
537533
const customActions = new Map<string, ActionCreatorObject>(
538-
getActionsArray(mask).map((action: ActionCreatorObject) => [action.name, action])
534+
getActions(mask).map((action: ActionCreatorObject) => [action.name, action])
539535
);
536+
537+
// Sort matchers by specificity
540538
const matchers = Object.entries(mask ?? {})
541539
.map(([key, picked]) => ({
542540
matcher: maskToRegex(key),
543541
picked,
544-
wildcard: key.includes('*'),
545-
}));
542+
key,
543+
})).toSorted((a, b) => {
544+
const aIsDoubleWildcard = a.key.includes('**');
545+
const bIsDoubleWildcard = b.key.includes('**');
546+
if (aIsDoubleWildcard !== bIsDoubleWildcard) {
547+
return aIsDoubleWildcard ? -1 : 1;
548+
}
549+
550+
const aIsSingleWildcard = a.key.includes('*') && !aIsDoubleWildcard;
551+
const bIsSingleWildcard = b.key.includes('*') && !bIsDoubleWildcard;
552+
if (aIsSingleWildcard !== bIsSingleWildcard) {
553+
return aIsSingleWildcard ? -1 : 1;
554+
}
555+
556+
return a.key.length - b.key.length;
557+
});
546558

547-
next_action: for (const action of flat) {
548-
for (const { matcher, picked, wildcard } of matchers) {
559+
for (const action of flat) {
560+
for (const { matcher, picked } of matchers) {
549561
if (matcher.test(action.name)) {
550-
if (!wildcard) {
551-
if (picked) {
552-
actions.set(action.name,
553-
customActions.get(action.name) ?? {
554-
name: action.name,
555-
func: action.func,
556-
args: action.args,
557-
});
558-
} else {
559-
actions.delete(action.name);
560-
}
561-
continue next_action;
562+
if (picked === false) {
563+
actions.delete(action.name);
564+
continue;
562565
}
563566

564-
actions.set(action.name, {
567+
actions.set(action.name, customActions.get(action.name) ?? {
565568
name: action.name,
566569
func: typeof picked === 'function' ? picked : action.func,
567570
args: action.args,
@@ -570,5 +573,9 @@ function getActionsArray<T extends Record<string, any>>(actionCreators: T, mask:
570573
}
571574
}
572575

576+
for (const [name, action] of customActions.entries()) {
577+
actions.set(name, action);
578+
}
579+
573580
return Array.from(actions.values());
574581
}

0 commit comments

Comments
 (0)