From d13658dbeebe1b2b50b2f20185aaf60f2822ae62 Mon Sep 17 00:00:00 2001 From: Mark Erikson Date: Sat, 19 Nov 2022 14:53:12 +0000 Subject: [PATCH 001/412] Remove ES5 builds and target modern browsers --- packages/toolkit/.size-limit.js | 24 +++++++++++------------ packages/toolkit/package.json | 2 +- packages/toolkit/query/package.json | 2 +- packages/toolkit/query/react/package.json | 2 +- packages/toolkit/scripts/build.ts | 20 ++++++++----------- packages/toolkit/scripts/types.ts | 2 +- 6 files changed, 24 insertions(+), 28 deletions(-) diff --git a/packages/toolkit/.size-limit.js b/packages/toolkit/.size-limit.js index 458676495e..70f8386b54 100644 --- a/packages/toolkit/.size-limit.js +++ b/packages/toolkit/.size-limit.js @@ -19,30 +19,30 @@ function withRtkPath(suffix) { join(__dirname) ), new webpack.NormalModuleReplacementPlugin( - /rtk-query-react.esm.js/, + /rtk-query-react.modern.js/, (r) => { const old = r.request r.request = r.request.replace( - /rtk-query-react.esm.js$/, + /rtk-query-react.modern.js$/, `rtk-query-react.${suffix}` ) // console.log(old, '=>', r.request) } ), - new webpack.NormalModuleReplacementPlugin(/rtk-query.esm.js/, (r) => { + new webpack.NormalModuleReplacementPlugin(/rtk-query.modern.js/, (r) => { const old = r.request r.request = r.request.replace( - /rtk-query.esm.js$/, + /rtk-query.modern.js$/, `rtk-query.${suffix}` ) // console.log(old, '=>', r.request) }), new webpack.NormalModuleReplacementPlugin( - /redux-toolkit.esm.js$/, + /redux-toolkit.modern.js$/, (r) => { const old = r.request r.request = r.request.replace( - /redux-toolkit.esm.js$/, + /redux-toolkit.modern.js$/, `redux-toolkit.${suffix}` ) // console.log(old, '=>', r.request) @@ -69,29 +69,29 @@ const ignoreAll = [ module.exports = [ { name: `1. entry point: @reduxjs/toolkit`, - path: 'dist/redux-toolkit.esm.js', + path: 'dist/redux-toolkit.modern.js', }, { name: `1. entry point: @reduxjs/toolkit/query`, - path: 'dist/query/rtk-query.esm.js', + path: 'dist/query/rtk-query.modern.js', }, { name: `1. entry point: @reduxjs/toolkit/query/react`, - path: 'dist/query/react/rtk-query-react.esm.js', + path: 'dist/query/react/rtk-query-react.modern.js', }, { name: `2. entry point: @reduxjs/toolkit (without dependencies)`, - path: 'dist/redux-toolkit.esm.js', + path: 'dist/redux-toolkit.modern.js', ignore: ignoreAll, }, { name: `2. entry point: @reduxjs/toolkit/query (without dependencies)`, - path: 'dist/query/rtk-query.esm.js', + path: 'dist/query/rtk-query.modern.js', ignore: ignoreAll, }, { name: `2. entry point: @reduxjs/toolkit/query/react (without dependencies)`, - path: 'dist/query/react/rtk-query-react.esm.js', + path: 'dist/query/react/rtk-query-react.modern.js', ignore: ignoreAll, }, ] diff --git a/packages/toolkit/package.json b/packages/toolkit/package.json index 2d1e48e666..683570c207 100644 --- a/packages/toolkit/package.json +++ b/packages/toolkit/package.json @@ -23,7 +23,7 @@ "access": "public" }, "main": "dist/index.js", - "module": "dist/redux-toolkit.esm.js", + "module": "dist/redux-toolkit.modern.js", "unpkg": "dist/redux-toolkit.umd.min.js", "types": "dist/index.d.ts", "devDependencies": { diff --git a/packages/toolkit/query/package.json b/packages/toolkit/query/package.json index 0abc176e05..f093468b90 100644 --- a/packages/toolkit/query/package.json +++ b/packages/toolkit/query/package.json @@ -3,7 +3,7 @@ "version": "1.0.0", "description": "", "main": "../dist/query/index.js", - "module": "../dist/query/rtk-query.esm.js", + "module": "../dist/query/rtk-query.modern.js", "unpkg": "../dist/query/rtk-query.umd.min.js", "types": "../dist/query/index.d.ts", "author": "Mark Erikson ", diff --git a/packages/toolkit/query/react/package.json b/packages/toolkit/query/react/package.json index 664a7e3e95..69f96cec3b 100644 --- a/packages/toolkit/query/react/package.json +++ b/packages/toolkit/query/react/package.json @@ -3,7 +3,7 @@ "version": "1.0.0", "description": "", "main": "../../dist/query/react/index.js", - "module": "../../dist/query/react/rtk-query-react.esm.js", + "module": "../../dist/query/react/rtk-query-react.modern.js", "unpkg": "../../dist/query/react/rtk-query-react.umd.min.js", "author": "Mark Erikson ", "license": "MIT", diff --git a/packages/toolkit/scripts/build.ts b/packages/toolkit/scripts/build.ts index c77391d79a..6a2effdf8d 100644 --- a/packages/toolkit/scripts/build.ts +++ b/packages/toolkit/scripts/build.ts @@ -36,28 +36,22 @@ const buildTargets: BuildOptions[] = [ { format: 'cjs', name: 'cjs.development', + target: 'es2018', minify: false, env: 'development', }, - { format: 'cjs', name: 'cjs.production.min', + target: 'es2018', minify: true, env: 'production', }, - // ESM, embedded `process`, ES5 syntax: typical Webpack dev - { - format: 'esm', - name: 'esm', - minify: false, - env: '', - }, // ESM, embedded `process`, ES2017 syntax: modern Webpack dev { format: 'esm', name: 'modern', - target: 'es2017', + target: 'es2018', minify: false, env: '', }, @@ -65,7 +59,7 @@ const buildTargets: BuildOptions[] = [ { format: 'esm', name: 'modern.development', - target: 'es2017', + target: 'es2018', minify: false, env: 'development', }, @@ -73,19 +67,21 @@ const buildTargets: BuildOptions[] = [ { format: 'esm', name: 'modern.production.min', - target: 'es2017', + target: 'es2018', minify: true, env: 'production', }, { format: 'umd', name: 'umd', + target: 'es2018', minify: false, env: 'development', }, { format: 'umd', name: 'umd.min', + target: 'es2018', minify: true, env: 'production', }, @@ -197,7 +193,7 @@ async function bundle(options: BuildOptions & EntryPointOptions) { const esVersion = target in esVersionMappings ? esVersionMappings[target] - : ts.ScriptTarget.ES5 + : ts.ScriptTarget.ES2017 const origin = chunk.text const sourcemap = extractInlineSourcemap(origin) diff --git a/packages/toolkit/scripts/types.ts b/packages/toolkit/scripts/types.ts index 31f2fbe9b9..cf0d8f889c 100644 --- a/packages/toolkit/scripts/types.ts +++ b/packages/toolkit/scripts/types.ts @@ -11,7 +11,7 @@ export interface BuildOptions { | 'umd.min' minify: boolean env: 'development' | 'production' | '' - target?: 'es2017' + target?: 'es2017' | 'es2018' | 'es2019' | 'es2020' } export interface EntryPointOptions { From 68c0ff91ce29fd1b05e97372774931fe47468971 Mon Sep 17 00:00:00 2001 From: Mark Erikson Date: Sat, 19 Nov 2022 14:53:47 +0000 Subject: [PATCH 002/412] Remove Immer ES5 legacy compat --- packages/toolkit/src/index.ts | 7 ------- 1 file changed, 7 deletions(-) diff --git a/packages/toolkit/src/index.ts b/packages/toolkit/src/index.ts index 24501035d2..61b9af0b5e 100644 --- a/packages/toolkit/src/index.ts +++ b/packages/toolkit/src/index.ts @@ -1,4 +1,3 @@ -import { enableES5 } from 'immer' export * from 'redux' export { default as createNextState, @@ -18,12 +17,6 @@ export type { export { createDraftSafeSelector } from './createDraftSafeSelector' export type { ThunkAction, ThunkDispatch, ThunkMiddleware } from 'redux-thunk' -// We deliberately enable Immer's ES5 support, on the grounds that -// we assume RTK will be used with React Native and other Proxy-less -// environments. In addition, that's how Immer 4 behaved, and since -// we want to ship this in an RTK minor, we should keep the same behavior. -enableES5() - export { // js configureStore, From 18345f9225b8c0d93e5df88b871e3602b2ec4477 Mon Sep 17 00:00:00 2001 From: Mark Erikson Date: Fri, 30 Dec 2022 15:11:14 -0500 Subject: [PATCH 003/412] Remove legacy object syntax for reducers --- packages/toolkit/src/createReducer.ts | 81 +------- packages/toolkit/src/createSlice.ts | 13 +- .../toolkit/src/tests/createReducer.test.ts | 183 ++++-------------- .../src/tests/createReducer.typetest.ts | 58 +++--- .../toolkit/src/tests/createSlice.test.ts | 45 +++-- 5 files changed, 110 insertions(+), 270 deletions(-) diff --git a/packages/toolkit/src/createReducer.ts b/packages/toolkit/src/createReducer.ts index 2ccb730864..dd749f398e 100644 --- a/packages/toolkit/src/createReducer.ts +++ b/packages/toolkit/src/createReducer.ts @@ -151,91 +151,20 @@ export function createReducer>( builderCallback: (builder: ActionReducerMapBuilder) => void ): ReducerWithInitialState -/** - * A utility function that allows defining a reducer as a mapping from action - * type to *case reducer* functions that handle these action types. The - * reducer's initial state is passed as the first argument. - * - * The body of every case reducer is implicitly wrapped with a call to - * `produce()` from the [immer](https://github.com/mweststrate/immer) library. - * This means that rather than returning a new state object, you can also - * mutate the passed-in state object directly; these mutations will then be - * automatically and efficiently translated into copies, giving you both - * convenience and immutability. - * - * @overloadSummary - * This overload accepts an object where the keys are string action types, and the values - * are case reducer functions to handle those action types. - * - * @param initialState - `State | (() => State)`: The initial state that should be used when the reducer is called the first time. This may also be a "lazy initializer" function, which should return an initial state value when called. This will be used whenever the reducer is called with `undefined` as its state value, and is primarily useful for cases like reading initial state from `localStorage`. - * @param actionsMap - An object mapping from action types to _case reducers_, each of which handles one specific action type. - * @param actionMatchers - An array of matcher definitions in the form `{matcher, reducer}`. - * All matching reducers will be executed in order, independently if a case reducer matched or not. - * @param defaultCaseReducer - A "default case" reducer that is executed if no case reducer and no matcher - * reducer was executed for this action. - * - * @example -```js -const counterReducer = createReducer(0, { - increment: (state, action) => state + action.payload, - decrement: (state, action) => state - action.payload -}) - -// Alternately, use a "lazy initializer" to provide the initial state -// (works with either form of createReducer) -const initialState = () => 0 -const counterReducer = createReducer(initialState, { - increment: (state, action) => state + action.payload, - decrement: (state, action) => state - action.payload -}) -``` - - * Action creators that were generated using [`createAction`](./createAction) may be used directly as the keys here, using computed property syntax: - -```js -const increment = createAction('increment') -const decrement = createAction('decrement') - -const counterReducer = createReducer(0, { - [increment]: (state, action) => state + action.payload, - [decrement.type]: (state, action) => state - action.payload -}) -``` - * @public - */ -export function createReducer< - S extends NotFunction, - CR extends CaseReducers = CaseReducers ->( - initialState: S | (() => S), - actionsMap: CR, - actionMatchers?: ActionMatcherDescriptionCollection, - defaultCaseReducer?: CaseReducer -): ReducerWithInitialState - export function createReducer>( initialState: S | (() => S), - mapOrBuilderCallback: - | CaseReducers - | ((builder: ActionReducerMapBuilder) => void), - actionMatchers: ReadonlyActionMatcherDescriptionCollection = [], - defaultCaseReducer?: CaseReducer + mapOrBuilderCallback: (builder: ActionReducerMapBuilder) => void ): ReducerWithInitialState { if (process.env.NODE_ENV !== 'production') { if (typeof mapOrBuilderCallback === 'object') { - if (!hasWarnedAboutObjectNotation) { - hasWarnedAboutObjectNotation = true - console.warn( - "The object notation for `createReducer` is deprecated, and will be removed in RTK 2.0. Please use the 'builder callback' notation instead: https://redux-toolkit.js.org/api/createReducer" - ) - } + throw new Error( + "The object notation for `createReducer` has been removed. Please use the 'builder callback' notation instead: https://redux-toolkit.js.org/api/createReducer" + ) } } let [actionsMap, finalActionMatchers, finalDefaultCaseReducer] = - typeof mapOrBuilderCallback === 'function' - ? executeReducerBuilderCallback(mapOrBuilderCallback) - : [mapOrBuilderCallback, actionMatchers, defaultCaseReducer] + executeReducerBuilderCallback(mapOrBuilderCallback) // Ensure the initial state gets frozen either way (if draftable) let getInitialState: () => S diff --git a/packages/toolkit/src/createSlice.ts b/packages/toolkit/src/createSlice.ts index 8406a5b98e..b9d3fd0d0f 100644 --- a/packages/toolkit/src/createSlice.ts +++ b/packages/toolkit/src/createSlice.ts @@ -141,9 +141,7 @@ createSlice({ }) ``` */ - extraReducers?: - | CaseReducers, any> - | ((builder: ActionReducerMapBuilder>) => void) + extraReducers?: (builder: ActionReducerMapBuilder>) => void } /** @@ -330,12 +328,9 @@ export function createSlice< function buildReducer() { if (process.env.NODE_ENV !== 'production') { if (typeof options.extraReducers === 'object') { - if (!hasWarnedAboutObjectNotation) { - hasWarnedAboutObjectNotation = true - console.warn( - "The object notation for `createSlice.extraReducers` is deprecated, and will be removed in RTK 2.0. Please use the 'builder callback' notation instead: https://redux-toolkit.js.org/api/createSlice" - ) - } + throw new Error( + "The object notation for `createSlice.extraReducers` has been removed. Please use the 'builder callback' notation instead: https://redux-toolkit.js.org/api/createSlice" + ) } } const [ diff --git a/packages/toolkit/src/tests/createReducer.test.ts b/packages/toolkit/src/tests/createReducer.test.ts index 5c3a181168..078f26618c 100644 --- a/packages/toolkit/src/tests/createReducer.test.ts +++ b/packages/toolkit/src/tests/createReducer.test.ts @@ -27,11 +27,14 @@ interface ToggleTodoPayload { type TodoState = Todo[] type TodosReducer = Reducer> -type AddTodoReducer = CaseReducer> +type AddTodoReducer = CaseReducer< + TodoState, + PayloadAction +> type ToggleTodoReducer = CaseReducer< TodoState, - PayloadAction + PayloadAction > type CreateReducer = typeof createReducer @@ -58,9 +61,8 @@ describe('createReducer', () => { todo.completed = !todo.completed } - const todosReducer = createReducer([] as TodoState, { - ADD_TODO: addTodo, - TOGGLE_TODO: toggleTodo, + const todosReducer = createReducer([] as TodoState, (builder) => { + builder.addCase('ADD_TODO', addTodo).addCase('TOGGLE_TODO', toggleTodo) }) behavesLikeReducer(todosReducer) @@ -77,25 +79,31 @@ describe('createReducer', () => { process.env.NODE_ENV = originalNodeEnv }) - it('Warns about object notation deprecation, once', () => { + it('Throws an error if the legacy object notation is used', () => { const { createReducer } = require('../createReducer') - let dummyReducer = (createReducer as CreateReducer)([] as TodoState, {}) + const wrapper = () => { + // @ts-ignore + let dummyReducer = (createReducer as CreateReducer)([] as TodoState, {}) + } - expect(getLog().levels.warn).toMatch( - /The object notation for `createReducer` is deprecated/ + expect(wrapper).toThrowError( + /The object notation for `createReducer` has been removed/ ) - restore = mockConsole(createConsole()) - dummyReducer = (createReducer as CreateReducer)([] as TodoState, {}) - expect(getLog().levels.warn).toBe('') + expect(wrapper).toThrowError( + /The object notation for `createReducer` has been removed/ + ) }) - it('Does not warn in production', () => { + it('Crashes in production', () => { process.env.NODE_ENV = 'production' const { createReducer } = require('../createReducer') - let dummyReducer = (createReducer as CreateReducer)([] as TodoState, {}) + const wrapper = () => { + // @ts-ignore + let dummyReducer = (createReducer as CreateReducer)([] as TodoState, {}) + } - expect(getLog().levels.warn).toBe('') + expect(wrapper).toThrowError() }) }) @@ -112,7 +120,8 @@ describe('createReducer', () => { }) test('Freezes data in production', () => { - const { createReducer } = require('../createReducer') + const createReducer: CreateReducer = + require('../createReducer').createReducer const addTodo: AddTodoReducer = (state, action) => { const { newTodo } = action.payload state.push({ ...newTodo, completed: false }) @@ -124,9 +133,8 @@ describe('createReducer', () => { todo.completed = !todo.completed } - const todosReducer = createReducer([] as TodoState, { - ADD_TODO: addTodo, - TOGGLE_TODO: toggleTodo, + const todosReducer = createReducer([] as TodoState, (builder) => { + builder.addCase('ADD_TODO', addTodo).addCase('TOGGLE_TODO', toggleTodo) }) const result = todosReducer([], { @@ -142,7 +150,7 @@ describe('createReducer', () => { test('Freezes initial state', () => { const initialState = [{ text: 'Buy milk' }] - const todosReducer = createReducer(initialState, {}) + const todosReducer = createReducer(initialState, () => {}) const frozenInitialState = todosReducer(undefined, { type: 'dummy' }) const mutateStateOutsideReducer = () => @@ -152,7 +160,9 @@ describe('createReducer', () => { ) }) test('does not throw error if initial state is not draftable', () => { - expect(() => createReducer(new URLSearchParams(), {})).not.toThrowError() + expect(() => + createReducer(new URLSearchParams(), () => {}) + ).not.toThrowError() }) }) @@ -174,9 +184,8 @@ describe('createReducer', () => { }) } - const todosReducer = createReducer([] as TodoState, { - ADD_TODO: addTodo, - TOGGLE_TODO: toggleTodo, + const todosReducer = createReducer([] as TodoState, (builder) => { + builder.addCase('ADD_TODO', addTodo).addCase('TOGGLE_TODO', toggleTodo) }) behavesLikeReducer(todosReducer) @@ -196,9 +205,8 @@ describe('createReducer', () => { const lazyStateInit = () => [] as TodoState - const todosReducer = createReducer(lazyStateInit, { - ADD_TODO: addTodo, - TOGGLE_TODO: toggleTodo, + const todosReducer = createReducer([] as TodoState, (builder) => { + builder.addCase('ADD_TODO', addTodo).addCase('TOGGLE_TODO', toggleTodo) }) behavesLikeReducer(todosReducer) @@ -206,7 +214,7 @@ describe('createReducer', () => { it('Should only call the init function when `undefined` state is passed in', () => { const spy = jest.fn().mockReturnValue(42) - const dummyReducer = createReducer(spy, {}) + const dummyReducer = createReducer(spy, () => {}) expect(spy).not.toHaveBeenCalled() dummyReducer(123, { type: 'dummy' }) @@ -233,9 +241,8 @@ describe('createReducer', () => { todo.completed = !todo.completed } - const todosReducer = createReducer([] as TodoState, { - ADD_TODO: addTodo, - TOGGLE_TODO: toggleTodo, + const todosReducer = createReducer([] as TodoState, (builder) => { + builder.addCase('ADD_TODO', addTodo).addCase('TOGGLE_TODO', toggleTodo) }) const wrappedReducer: TodosReducer = (state = [], action) => { @@ -247,120 +254,6 @@ describe('createReducer', () => { behavesLikeReducer(wrappedReducer) }) - describe('actionMatchers argument', () => { - const prepareNumberAction = (payload: number) => ({ - payload, - meta: { type: 'number_action' }, - }) - const prepareStringAction = (payload: string) => ({ - payload, - meta: { type: 'string_action' }, - }) - - const numberActionMatcher = (a: AnyAction): a is PayloadAction => - a.meta && a.meta.type === 'number_action' - const stringActionMatcher = (a: AnyAction): a is PayloadAction => - a.meta && a.meta.type === 'string_action' - - const incrementBy = createAction('increment', prepareNumberAction) - const decrementBy = createAction('decrement', prepareNumberAction) - const concatWith = createAction('concat', prepareStringAction) - - const initialState = { numberActions: 0, stringActions: 0 } - const numberActionsCounter = { - matcher: numberActionMatcher, - reducer(state: typeof initialState) { - state.numberActions = state.numberActions * 10 + 1 - }, - } - const stringActionsCounter = { - matcher: stringActionMatcher, - reducer(state: typeof initialState) { - state.stringActions = state.stringActions * 10 + 1 - }, - } - - test('uses the reducer of matching actionMatchers', () => { - const reducer = createReducer(initialState, {}, [ - numberActionsCounter, - stringActionsCounter, - ]) - expect(reducer(undefined, incrementBy(1))).toEqual({ - numberActions: 1, - stringActions: 0, - }) - expect(reducer(undefined, decrementBy(1))).toEqual({ - numberActions: 1, - stringActions: 0, - }) - expect(reducer(undefined, concatWith('foo'))).toEqual({ - numberActions: 0, - stringActions: 1, - }) - }) - test('fallback to default case', () => { - const reducer = createReducer( - initialState, - {}, - [numberActionsCounter, stringActionsCounter], - (state) => { - state.numberActions = -1 - state.stringActions = -1 - } - ) - expect(reducer(undefined, { type: 'somethingElse' })).toEqual({ - numberActions: -1, - stringActions: -1, - }) - }) - test('runs reducer cases followed by all matching actionMatchers', () => { - const reducer = createReducer( - initialState, - { - [incrementBy.type](state) { - state.numberActions = state.numberActions * 10 + 2 - }, - }, - [ - { - matcher: numberActionMatcher, - reducer(state) { - state.numberActions = state.numberActions * 10 + 3 - }, - }, - numberActionsCounter, - stringActionsCounter, - ] - ) - expect(reducer(undefined, incrementBy(1))).toEqual({ - numberActions: 231, - stringActions: 0, - }) - expect(reducer(undefined, decrementBy(1))).toEqual({ - numberActions: 31, - stringActions: 0, - }) - expect(reducer(undefined, concatWith('foo'))).toEqual({ - numberActions: 0, - stringActions: 1, - }) - }) - test('works with `actionCreator.match`', () => { - const reducer = createReducer(initialState, {}, [ - { - matcher: incrementBy.match, - reducer(state) { - state.numberActions += 100 - }, - }, - ]) - expect(reducer(undefined, incrementBy(1))).toEqual({ - numberActions: 100, - stringActions: 0, - }) - }) - }) - describe('alternative builder callback for actionMap', () => { const increment = createAction('increment') const decrement = createAction('decrement') diff --git a/packages/toolkit/src/tests/createReducer.typetest.ts b/packages/toolkit/src/tests/createReducer.typetest.ts index 6e4b3e1520..bcfb885025 100644 --- a/packages/toolkit/src/tests/createReducer.typetest.ts +++ b/packages/toolkit/src/tests/createReducer.typetest.ts @@ -7,16 +7,19 @@ import { expectType } from './helpers' * Test: createReducer() infers type of returned reducer. */ { - type CounterAction = - | { type: 'increment'; payload: number } - | { type: 'decrement'; payload: number } + const incrementHandler = ( + state: number, + action: { type: 'increment'; payload: number } + ) => state + 1 + const decrementHandler = ( + state: number, + action: { type: 'decrement'; payload: number } + ) => state - 1 - const incrementHandler = (state: number, action: CounterAction) => state + 1 - const decrementHandler = (state: number, action: CounterAction) => state - 1 - - const reducer = createReducer(0 as number, { - increment: incrementHandler, - decrement: decrementHandler, + const reducer = createReducer(0 as number, (builder) => { + builder + .addCase('increment', incrementHandler) + .addCase('decrement', decrementHandler) }) const numberReducer: Reducer = reducer @@ -29,25 +32,28 @@ import { expectType } from './helpers' * Test: createReducer() state type can be specified expliclity. */ { - type CounterAction = - | { type: 'increment'; payload: number } - | { type: 'decrement'; payload: number } - - const incrementHandler = (state: number, action: CounterAction) => - state + action.payload + const incrementHandler = ( + state: number, + action: { type: 'increment'; payload: number } + ) => state + action.payload - const decrementHandler = (state: number, action: CounterAction) => - state - action.payload + const decrementHandler = ( + state: number, + action: { type: 'decrement'; payload: number } + ) => state - action.payload - createReducer(0, { - increment: incrementHandler, - decrement: decrementHandler, + createReducer(0 as number, (builder) => { + builder + .addCase('increment', incrementHandler) + .addCase('decrement', decrementHandler) }) // @ts-expect-error - createReducer(0, { - increment: incrementHandler, - decrement: decrementHandler, + createReducer(0 as number, (builder) => { + // @ts-expect-error + builder + .addCase('increment', incrementHandler) + .addCase('decrement', decrementHandler) }) } @@ -57,10 +63,10 @@ import { expectType } from './helpers' { const initialState: { readonly counter: number } = { counter: 0 } - createReducer(initialState, { - increment: (state) => { + createReducer(initialState, (builder) => { + builder.addCase('increment', (state) => { state.counter += 1 - }, + }) }) } diff --git a/packages/toolkit/src/tests/createSlice.test.ts b/packages/toolkit/src/tests/createSlice.test.ts index bea220ba6a..8cbe39340a 100644 --- a/packages/toolkit/src/tests/createSlice.test.ts +++ b/packages/toolkit/src/tests/createSlice.test.ts @@ -183,9 +183,13 @@ describe('createSlice', () => { increment: (state) => state + 1, multiply: (state, action) => state * action.payload, }, - extraReducers: { - [addMore.type]: (state, action) => state + action.payload.amount, + extraReducers: (builder) => { + builder.addCase( + addMore, + (state, action) => state + action.payload.amount + ) }, + initialState: 0, }) @@ -372,7 +376,7 @@ describe('createSlice', () => { }) }) - describe.only('Deprecation warnings', () => { + describe('Deprecation warnings', () => { let originalNodeEnv = process.env.NODE_ENV beforeEach(() => { @@ -385,7 +389,7 @@ describe('createSlice', () => { }) // NOTE: This needs to be in front of the later `createReducer` call to check the one-time warning - it('Warns about object notation deprecation, once', () => { + it('Throws an error if the legacy object notation is used', () => { const { createSlice } = require('../createSlice') let dummySlice = (createSlice as CreateSlice)({ @@ -393,32 +397,37 @@ describe('createSlice', () => { initialState: [], reducers: {}, extraReducers: { + // @ts-ignore a: () => [], }, }) + let reducer: any // Have to trigger the lazy creation - let { reducer } = dummySlice - reducer(undefined, { type: 'dummy' }) + const wrapper = () => { + reducer = dummySlice.reducer + reducer(undefined, { type: 'dummy' }) + } - expect(getLog().levels.warn).toMatch( - /The object notation for `createSlice.extraReducers` is deprecated/ + expect(wrapper).toThrowError( + /The object notation for `createSlice.extraReducers` has been removed/ ) - restore = mockConsole(createConsole()) dummySlice = (createSlice as CreateSlice)({ name: 'dummy', initialState: [], reducers: {}, extraReducers: { + // @ts-ignore a: () => [], }, }) - reducer = dummySlice.reducer - reducer(undefined, { type: 'dummy' }) - expect(getLog().levels.warn).toBe('') + expect(wrapper).toThrowError( + /The object notation for `createSlice.extraReducers` has been removed/ + ) }) - it('Does not warn in production', () => { + // TODO Determine final production behavior here + it.skip('Crashes in production', () => { process.env.NODE_ENV = 'production' const { createSlice } = require('../createSlice') @@ -426,9 +435,17 @@ describe('createSlice', () => { name: 'dummy', initialState: [], reducers: {}, + // @ts-ignore extraReducers: {}, }) - expect(getLog().levels.warn).toBe('') + const wrapper = () => { + let reducer = dummySlice.reducer + reducer(undefined, { type: 'dummy' }) + } + + expect(wrapper).toThrowError( + /The object notation for `createSlice.extraReducers` has been removed/ + ) }) }) }) From 2dae4309f79e62a702ca06f347b045d6a557f3b8 Mon Sep 17 00:00:00 2001 From: Mark Erikson Date: Sat, 14 Jan 2023 13:23:01 -0500 Subject: [PATCH 004/412] Upgrade TypeScript to 4.9 --- examples/action-listener/counter/package.json | 2 +- examples/query/react/advanced/package.json | 2 +- .../package.json | 2 +- .../query/react/authentication/package.json | 2 +- examples/query/react/basic/package.json | 2 +- .../react/conditional-fetching/package.json | 2 +- .../query/react/deduping-queries/package.json | 2 +- .../query/react/kitchen-sink/package.json | 2 +- examples/query/react/mutations/package.json | 2 +- .../react/optimistic-update/package.json | 2 +- examples/query/react/pagination/package.json | 2 +- examples/query/react/polling/package.json | 2 +- .../package.json | 2 +- .../react/prefetching-automatic/package.json | 2 +- examples/query/react/prefetching/package.json | 2 +- .../query/react/with-apiprovider/package.json | 2 +- packages/toolkit/package.json | 2 +- website/package.json | 2 +- yarn.lock | 56 +++++++++++++------ 19 files changed, 56 insertions(+), 36 deletions(-) diff --git a/examples/action-listener/counter/package.json b/examples/action-listener/counter/package.json index 2455a10554..3d614a9bef 100644 --- a/examples/action-listener/counter/package.json +++ b/examples/action-listener/counter/package.json @@ -12,7 +12,7 @@ "react-dom": "^18.1.0", "react-redux": "^8.0.2", "react-scripts": "5.0.1", - "typescript": "~4.2.4" + "typescript": "~4.9" }, "scripts": { "start": "react-scripts start", diff --git a/examples/query/react/advanced/package.json b/examples/query/react/advanced/package.json index 42b052a03b..fd8f138c82 100644 --- a/examples/query/react/advanced/package.json +++ b/examples/query/react/advanced/package.json @@ -15,7 +15,7 @@ "devDependencies": { "@types/react": "^18.0.5", "@types/react-dom": "^18.0.5", - "typescript": "~4.2.4" + "typescript": "~4.9" }, "eslintConfig": { "extends": [ diff --git a/examples/query/react/authentication-with-extrareducers/package.json b/examples/query/react/authentication-with-extrareducers/package.json index 959133378e..d311f8504d 100644 --- a/examples/query/react/authentication-with-extrareducers/package.json +++ b/examples/query/react/authentication-with-extrareducers/package.json @@ -22,7 +22,7 @@ "devDependencies": { "@types/react": "^18.0.5", "@types/react-dom": "^18.0.5", - "typescript": "~4.2.4" + "typescript": "~4.9" }, "scripts": { "start": "react-scripts start", diff --git a/examples/query/react/authentication/package.json b/examples/query/react/authentication/package.json index 6d97e0f6a2..4df608f9ac 100644 --- a/examples/query/react/authentication/package.json +++ b/examples/query/react/authentication/package.json @@ -22,7 +22,7 @@ "devDependencies": { "@types/react": "^18.0.5", "@types/react-dom": "^18.0.5", - "typescript": "~4.2.4" + "typescript": "~4.9" }, "scripts": { "start": "react-scripts start", diff --git a/examples/query/react/basic/package.json b/examples/query/react/basic/package.json index c2e7faa137..14f5a8a17b 100644 --- a/examples/query/react/basic/package.json +++ b/examples/query/react/basic/package.json @@ -18,7 +18,7 @@ "@types/react": "^18.0.5", "@types/react-dom": "^18.0.5", "msw": "^0.40.2", - "typescript": "~4.2.4" + "typescript": "~4.9" }, "eslintConfig": { "extends": [ diff --git a/examples/query/react/conditional-fetching/package.json b/examples/query/react/conditional-fetching/package.json index 7420186a28..db8c2c020e 100644 --- a/examples/query/react/conditional-fetching/package.json +++ b/examples/query/react/conditional-fetching/package.json @@ -15,7 +15,7 @@ "devDependencies": { "@types/react": "^18.0.5", "@types/react-dom": "^18.0.5", - "typescript": "~4.2.4" + "typescript": "~4.9" }, "eslintConfig": { "extends": [ diff --git a/examples/query/react/deduping-queries/package.json b/examples/query/react/deduping-queries/package.json index 9ad3dd5aa8..bab6efc947 100644 --- a/examples/query/react/deduping-queries/package.json +++ b/examples/query/react/deduping-queries/package.json @@ -15,7 +15,7 @@ "devDependencies": { "@types/react": "^18.0.5", "@types/react-dom": "^18.0.5", - "typescript": "~4.2.4" + "typescript": "~4.9" }, "eslintConfig": { "extends": [ diff --git a/examples/query/react/kitchen-sink/package.json b/examples/query/react/kitchen-sink/package.json index a706a541b2..5208a2928d 100644 --- a/examples/query/react/kitchen-sink/package.json +++ b/examples/query/react/kitchen-sink/package.json @@ -21,7 +21,7 @@ "@types/node": "^14.14.6", "@types/react": "^18.0.5", "@types/react-dom": "^18.0.5", - "typescript": "~4.2.4", + "typescript": "~4.9", "whatwg-fetch": "^3.4.1" }, "eslintConfig": { diff --git a/examples/query/react/mutations/package.json b/examples/query/react/mutations/package.json index c80f33a9d9..cd5eacb87a 100644 --- a/examples/query/react/mutations/package.json +++ b/examples/query/react/mutations/package.json @@ -23,7 +23,7 @@ "devDependencies": { "@types/react": "^18.0.5", "@types/react-dom": "^18.0.5", - "typescript": "~4.2.4" + "typescript": "~4.9" }, "scripts": { "start": "react-scripts start", diff --git a/examples/query/react/optimistic-update/package.json b/examples/query/react/optimistic-update/package.json index 4348225bce..300cc10ef0 100644 --- a/examples/query/react/optimistic-update/package.json +++ b/examples/query/react/optimistic-update/package.json @@ -24,7 +24,7 @@ "devDependencies": { "@types/react": "^18.0.5", "@types/react-dom": "^18.0.5", - "typescript": "~4.2.4", + "typescript": "~4.9", "whatwg-fetch": "^3.4.1" }, "scripts": { diff --git a/examples/query/react/pagination/package.json b/examples/query/react/pagination/package.json index 1fe4402e34..dd80a1a0a9 100644 --- a/examples/query/react/pagination/package.json +++ b/examples/query/react/pagination/package.json @@ -25,7 +25,7 @@ "@types/faker": "^5.5.5", "@types/react": "^18.0.5", "@types/react-dom": "^18.0.5", - "typescript": "~4.2.4" + "typescript": "~4.9" }, "scripts": { "start": "react-scripts start", diff --git a/examples/query/react/polling/package.json b/examples/query/react/polling/package.json index cc6c057ae0..939153d130 100644 --- a/examples/query/react/polling/package.json +++ b/examples/query/react/polling/package.json @@ -15,7 +15,7 @@ "devDependencies": { "@types/react": "^18.0.5", "@types/react-dom": "^18.0.5", - "typescript": "~4.2.4" + "typescript": "~4.9" }, "eslintConfig": { "extends": [ diff --git a/examples/query/react/prefetching-automatic-waterfall/package.json b/examples/query/react/prefetching-automatic-waterfall/package.json index 7db24618ca..6a303964fb 100644 --- a/examples/query/react/prefetching-automatic-waterfall/package.json +++ b/examples/query/react/prefetching-automatic-waterfall/package.json @@ -25,7 +25,7 @@ "@types/faker": "^5.5.5", "@types/react": "^18.0.5", "@types/react-dom": "^18.0.5", - "typescript": "~4.2.4" + "typescript": "~4.9" }, "scripts": { "start": "react-scripts start", diff --git a/examples/query/react/prefetching-automatic/package.json b/examples/query/react/prefetching-automatic/package.json index b6a86594bb..f5cb57a845 100644 --- a/examples/query/react/prefetching-automatic/package.json +++ b/examples/query/react/prefetching-automatic/package.json @@ -25,7 +25,7 @@ "@types/faker": "^5.5.5", "@types/react": "^18.0.5", "@types/react-dom": "^18.0.5", - "typescript": "~4.2.4" + "typescript": "~4.9" }, "scripts": { "start": "react-scripts start", diff --git a/examples/query/react/prefetching/package.json b/examples/query/react/prefetching/package.json index c342175b7e..2e63421cc3 100644 --- a/examples/query/react/prefetching/package.json +++ b/examples/query/react/prefetching/package.json @@ -25,7 +25,7 @@ "@types/faker": "^5.5.5", "@types/react": "^18.0.5", "@types/react-dom": "^18.0.5", - "typescript": "~4.2.4" + "typescript": "~4.9" }, "scripts": { "start": "react-scripts start", diff --git a/examples/query/react/with-apiprovider/package.json b/examples/query/react/with-apiprovider/package.json index bc9cd0e30b..ec0b493406 100644 --- a/examples/query/react/with-apiprovider/package.json +++ b/examples/query/react/with-apiprovider/package.json @@ -15,7 +15,7 @@ "devDependencies": { "@types/react": "^18.0.5", "@types/react-dom": "^18.0.5", - "typescript": "~4.2.4" + "typescript": "~4.9" }, "eslintConfig": { "extends": [ diff --git a/packages/toolkit/package.json b/packages/toolkit/package.json index 683570c207..5011b225f7 100644 --- a/packages/toolkit/package.json +++ b/packages/toolkit/package.json @@ -73,7 +73,7 @@ "terser": "^5.6.1", "ts-jest": "^27", "tslib": "^1.10.0", - "typescript": "~4.2.4", + "typescript": "~4.9", "yargs": "^15.3.1" }, "scripts": { diff --git a/website/package.json b/website/package.json index f6c7954d71..1f17421246 100644 --- a/website/package.json +++ b/website/package.json @@ -16,7 +16,7 @@ "react-dom": "^18.1.0", "react-lite-youtube-embed": "^2.0.3", "remark-typescript-tools": "^1.0.8", - "typescript": "~4.2.4" + "typescript": "~4.9" }, "browserslist": { "production": [ diff --git a/yarn.lock b/yarn.lock index 6228fb4719..42fd26ece1 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4326,7 +4326,7 @@ __metadata: react-dom: ^18.1.0 react-redux: ^8.0.2 react-scripts: 5.0.1 - typescript: ~4.2.4 + typescript: ~4.9 languageName: unknown linkType: soft @@ -4341,7 +4341,7 @@ __metadata: react-dom: ^18.1.0 react-redux: ^8.0.2 react-scripts: 5.0.1 - typescript: ~4.2.4 + typescript: ~4.9 languageName: unknown linkType: soft @@ -4363,7 +4363,7 @@ __metadata: react-redux: ^8.0.2 react-router-dom: 6.3.0 react-scripts: 5.0.1 - typescript: ~4.2.4 + typescript: ~4.9 languageName: unknown linkType: soft @@ -4385,7 +4385,7 @@ __metadata: react-redux: ^8.0.2 react-router-dom: 6.3.0 react-scripts: 5.0.1 - typescript: ~4.2.4 + typescript: ~4.9 languageName: unknown linkType: soft @@ -4403,7 +4403,7 @@ __metadata: react-dom: ^18.1.0 react-redux: ^8.0.2 react-scripts: 5.0.1 - typescript: ~4.2.4 + typescript: ~4.9 languageName: unknown linkType: soft @@ -4418,7 +4418,7 @@ __metadata: react-dom: ^18.1.0 react-redux: ^8.0.2 react-scripts: 5.0.1 - typescript: ~4.2.4 + typescript: ~4.9 languageName: unknown linkType: soft @@ -4433,7 +4433,7 @@ __metadata: react-dom: ^18.1.0 react-redux: ^8.0.2 react-scripts: 5.0.1 - typescript: ~4.2.4 + typescript: ~4.9 languageName: unknown linkType: soft @@ -4523,7 +4523,7 @@ __metadata: react-redux: ^8.0.2 react-router-dom: 6.3.0 react-scripts: 5.0.1 - typescript: ~4.2.4 + typescript: ~4.9 whatwg-fetch: ^3.4.1 languageName: unknown linkType: soft @@ -4547,7 +4547,7 @@ __metadata: react-redux: ^8.0.2 react-router-dom: 6.3.0 react-scripts: 5.0.1 - typescript: ~4.2.4 + typescript: ~4.9 languageName: unknown linkType: soft @@ -4570,7 +4570,7 @@ __metadata: react-redux: ^8.0.2 react-router-dom: 6.3.0 react-scripts: 5.0.1 - typescript: ~4.2.4 + typescript: ~4.9 uuid: ^8.3.2 whatwg-fetch: ^3.4.1 languageName: unknown @@ -4597,7 +4597,7 @@ __metadata: react-redux: ^8.0.2 react-router-dom: 6.3.0 react-scripts: 5.0.1 - typescript: ~4.2.4 + typescript: ~4.9 languageName: unknown linkType: soft @@ -4612,7 +4612,7 @@ __metadata: react-dom: ^18.1.0 react-redux: ^8.0.2 react-scripts: 5.0.1 - typescript: ~4.2.4 + typescript: ~4.9 languageName: unknown linkType: soft @@ -4637,7 +4637,7 @@ __metadata: react-redux: ^8.0.2 react-router-dom: 6.3.0 react-scripts: 5.0.1 - typescript: ~4.2.4 + typescript: ~4.9 languageName: unknown linkType: soft @@ -4662,7 +4662,7 @@ __metadata: react-redux: ^8.0.2 react-router-dom: 6.3.0 react-scripts: 5.0.1 - typescript: ~4.2.4 + typescript: ~4.9 languageName: unknown linkType: soft @@ -4687,7 +4687,7 @@ __metadata: react-redux: ^8.0.2 react-router-dom: 6.3.0 react-scripts: 5.0.1 - typescript: ~4.2.4 + typescript: ~4.9 languageName: unknown linkType: soft @@ -4702,7 +4702,7 @@ __metadata: react-dom: ^18.1.0 react-redux: ^8.0.2 react-scripts: 5.0.1 - typescript: ~4.2.4 + typescript: ~4.9 languageName: unknown linkType: soft @@ -6382,7 +6382,7 @@ __metadata: terser: ^5.6.1 ts-jest: ^27 tslib: ^1.10.0 - typescript: ~4.2.4 + typescript: ~4.9 yargs: ^15.3.1 peerDependencies: react: ^16.9.0 || ^17.0.0 || ^18 @@ -26788,6 +26788,16 @@ fsevents@^1.2.7: languageName: node linkType: hard +"typescript@npm:~4.9": + version: 4.9.4 + resolution: "typescript@npm:4.9.4" + bin: + tsc: bin/tsc + tsserver: bin/tsserver + checksum: e782fb9e0031cb258a80000f6c13530288c6d63f1177ed43f770533fdc15740d271554cdae86701c1dd2c83b082cea808b07e97fd68b38a172a83dbf9e0d0ef9 + languageName: node + linkType: hard + "typescript@patch:typescript@4.1.3#~builtin": version: 4.1.3 resolution: "typescript@patch:typescript@npm%3A4.1.3#~builtin::version=4.1.3&hash=701156" @@ -26838,6 +26848,16 @@ fsevents@^1.2.7: languageName: node linkType: hard +"typescript@patch:typescript@~4.9#~builtin": + version: 4.9.4 + resolution: "typescript@patch:typescript@npm%3A4.9.4#~builtin::version=4.9.4&hash=701156" + bin: + tsc: bin/tsc + tsserver: bin/tsserver + checksum: 37f6e2c3c5e2aa5934b85b0fddbf32eeac8b1bacf3a5b51d01946936d03f5377fe86255d4e5a4ae628fd0cd553386355ad362c57f13b4635064400f3e8e05b9d + languageName: node + linkType: hard + "ua-parser-js@npm:^0.7.18": version: 0.7.28 resolution: "ua-parser-js@npm:0.7.28" @@ -27956,7 +27976,7 @@ fsevents@^1.2.7: react-dom: ^18.1.0 react-lite-youtube-embed: ^2.0.3 remark-typescript-tools: ^1.0.8 - typescript: ~4.2.4 + typescript: ~4.9 languageName: unknown linkType: soft From 723a985001e92ccba1164d18fc579a042440827b Mon Sep 17 00:00:00 2001 From: Mark Erikson Date: Sat, 14 Jan 2023 13:24:14 -0500 Subject: [PATCH 005/412] Upgrade ESBuild to 0.17 --- .../rtk-query-codegen-openapi/package.json | 2 +- packages/toolkit/package.json | 2 +- yarn.lock | 379 ++++++++++-------- 3 files changed, 212 insertions(+), 171 deletions(-) diff --git a/packages/rtk-query-codegen-openapi/package.json b/packages/rtk-query-codegen-openapi/package.json index 53cc4ccb19..1fdd912502 100644 --- a/packages/rtk-query-codegen-openapi/package.json +++ b/packages/rtk-query-codegen-openapi/package.json @@ -43,7 +43,7 @@ "babel-jest": "^26.6.3", "chalk": "^4.1.0", "del": "^6.0.0", - "esbuild": "^0.13.10", + "esbuild": "~0.17", "esbuild-runner": "^2.2.1", "husky": "^4.3.6", "jest": "^27", diff --git a/packages/toolkit/package.json b/packages/toolkit/package.json index 5011b225f7..fa9771d80e 100644 --- a/packages/toolkit/package.json +++ b/packages/toolkit/package.json @@ -45,7 +45,7 @@ "axios": "^0.19.2", "console-testing-library": "^0.3.1", "convert-source-map": "^1.7.0", - "esbuild": "^0.11.13", + "esbuild": "~0.17", "eslint": "^7.25.0", "eslint-config-prettier": "^8.3.0", "eslint-config-react-app": "^7.0.1", diff --git a/yarn.lock b/yarn.lock index 42fd26ece1..4a71afeda1 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4278,6 +4278,160 @@ __metadata: languageName: node linkType: hard +"@esbuild/android-arm64@npm:0.17.0": + version: 0.17.0 + resolution: "@esbuild/android-arm64@npm:0.17.0" + conditions: os=android & cpu=arm64 + languageName: node + linkType: hard + +"@esbuild/android-arm@npm:0.17.0": + version: 0.17.0 + resolution: "@esbuild/android-arm@npm:0.17.0" + conditions: os=android & cpu=arm + languageName: node + linkType: hard + +"@esbuild/android-x64@npm:0.17.0": + version: 0.17.0 + resolution: "@esbuild/android-x64@npm:0.17.0" + conditions: os=android & cpu=x64 + languageName: node + linkType: hard + +"@esbuild/darwin-arm64@npm:0.17.0": + version: 0.17.0 + resolution: "@esbuild/darwin-arm64@npm:0.17.0" + conditions: os=darwin & cpu=arm64 + languageName: node + linkType: hard + +"@esbuild/darwin-x64@npm:0.17.0": + version: 0.17.0 + resolution: "@esbuild/darwin-x64@npm:0.17.0" + conditions: os=darwin & cpu=x64 + languageName: node + linkType: hard + +"@esbuild/freebsd-arm64@npm:0.17.0": + version: 0.17.0 + resolution: "@esbuild/freebsd-arm64@npm:0.17.0" + conditions: os=freebsd & cpu=arm64 + languageName: node + linkType: hard + +"@esbuild/freebsd-x64@npm:0.17.0": + version: 0.17.0 + resolution: "@esbuild/freebsd-x64@npm:0.17.0" + conditions: os=freebsd & cpu=x64 + languageName: node + linkType: hard + +"@esbuild/linux-arm64@npm:0.17.0": + version: 0.17.0 + resolution: "@esbuild/linux-arm64@npm:0.17.0" + conditions: os=linux & cpu=arm64 + languageName: node + linkType: hard + +"@esbuild/linux-arm@npm:0.17.0": + version: 0.17.0 + resolution: "@esbuild/linux-arm@npm:0.17.0" + conditions: os=linux & cpu=arm + languageName: node + linkType: hard + +"@esbuild/linux-ia32@npm:0.17.0": + version: 0.17.0 + resolution: "@esbuild/linux-ia32@npm:0.17.0" + conditions: os=linux & cpu=ia32 + languageName: node + linkType: hard + +"@esbuild/linux-loong64@npm:0.17.0": + version: 0.17.0 + resolution: "@esbuild/linux-loong64@npm:0.17.0" + conditions: os=linux & cpu=loong64 + languageName: node + linkType: hard + +"@esbuild/linux-mips64el@npm:0.17.0": + version: 0.17.0 + resolution: "@esbuild/linux-mips64el@npm:0.17.0" + conditions: os=linux & cpu=mips64el + languageName: node + linkType: hard + +"@esbuild/linux-ppc64@npm:0.17.0": + version: 0.17.0 + resolution: "@esbuild/linux-ppc64@npm:0.17.0" + conditions: os=linux & cpu=ppc64 + languageName: node + linkType: hard + +"@esbuild/linux-riscv64@npm:0.17.0": + version: 0.17.0 + resolution: "@esbuild/linux-riscv64@npm:0.17.0" + conditions: os=linux & cpu=riscv64 + languageName: node + linkType: hard + +"@esbuild/linux-s390x@npm:0.17.0": + version: 0.17.0 + resolution: "@esbuild/linux-s390x@npm:0.17.0" + conditions: os=linux & cpu=s390x + languageName: node + linkType: hard + +"@esbuild/linux-x64@npm:0.17.0": + version: 0.17.0 + resolution: "@esbuild/linux-x64@npm:0.17.0" + conditions: os=linux & cpu=x64 + languageName: node + linkType: hard + +"@esbuild/netbsd-x64@npm:0.17.0": + version: 0.17.0 + resolution: "@esbuild/netbsd-x64@npm:0.17.0" + conditions: os=netbsd & cpu=x64 + languageName: node + linkType: hard + +"@esbuild/openbsd-x64@npm:0.17.0": + version: 0.17.0 + resolution: "@esbuild/openbsd-x64@npm:0.17.0" + conditions: os=openbsd & cpu=x64 + languageName: node + linkType: hard + +"@esbuild/sunos-x64@npm:0.17.0": + version: 0.17.0 + resolution: "@esbuild/sunos-x64@npm:0.17.0" + conditions: os=sunos & cpu=x64 + languageName: node + linkType: hard + +"@esbuild/win32-arm64@npm:0.17.0": + version: 0.17.0 + resolution: "@esbuild/win32-arm64@npm:0.17.0" + conditions: os=win32 & cpu=arm64 + languageName: node + linkType: hard + +"@esbuild/win32-ia32@npm:0.17.0": + version: 0.17.0 + resolution: "@esbuild/win32-ia32@npm:0.17.0" + conditions: os=win32 & cpu=ia32 + languageName: node + linkType: hard + +"@esbuild/win32-x64@npm:0.17.0": + version: 0.17.0 + resolution: "@esbuild/win32-x64@npm:0.17.0" + conditions: os=win32 & cpu=x64 + languageName: node + linkType: hard + "@eslint/eslintrc@npm:^0.4.2": version: 0.4.2 resolution: "@eslint/eslintrc@npm:0.4.2" @@ -6350,7 +6504,7 @@ __metadata: axios: ^0.19.2 console-testing-library: ^0.3.1 convert-source-map: ^1.7.0 - esbuild: ^0.11.13 + esbuild: ~0.17 eslint: ^7.25.0 eslint-config-prettier: ^8.3.0 eslint-config-react-app: ^7.0.1 @@ -6533,7 +6687,7 @@ __metadata: chalk: ^4.1.0 commander: ^6.2.0 del: ^6.0.0 - esbuild: ^0.13.10 + esbuild: ~0.17 esbuild-runner: ^2.2.1 husky: ^4.3.6 jest: ^27 @@ -13274,97 +13428,6 @@ __metadata: languageName: node linkType: hard -"esbuild-android-arm64@npm:0.13.12": - version: 0.13.12 - resolution: "esbuild-android-arm64@npm:0.13.12" - conditions: os=android & cpu=arm64 - languageName: node - linkType: hard - -"esbuild-darwin-64@npm:0.13.12": - version: 0.13.12 - resolution: "esbuild-darwin-64@npm:0.13.12" - conditions: os=darwin & cpu=x64 - languageName: node - linkType: hard - -"esbuild-darwin-arm64@npm:0.13.12": - version: 0.13.12 - resolution: "esbuild-darwin-arm64@npm:0.13.12" - conditions: os=darwin & cpu=arm64 - languageName: node - linkType: hard - -"esbuild-freebsd-64@npm:0.13.12": - version: 0.13.12 - resolution: "esbuild-freebsd-64@npm:0.13.12" - conditions: os=freebsd & cpu=x64 - languageName: node - linkType: hard - -"esbuild-freebsd-arm64@npm:0.13.12": - version: 0.13.12 - resolution: "esbuild-freebsd-arm64@npm:0.13.12" - conditions: os=freebsd & cpu=arm64 - languageName: node - linkType: hard - -"esbuild-linux-32@npm:0.13.12": - version: 0.13.12 - resolution: "esbuild-linux-32@npm:0.13.12" - conditions: os=linux & cpu=ia32 - languageName: node - linkType: hard - -"esbuild-linux-64@npm:0.13.12": - version: 0.13.12 - resolution: "esbuild-linux-64@npm:0.13.12" - conditions: os=linux & cpu=x64 - languageName: node - linkType: hard - -"esbuild-linux-arm64@npm:0.13.12": - version: 0.13.12 - resolution: "esbuild-linux-arm64@npm:0.13.12" - conditions: os=linux & cpu=arm64 - languageName: node - linkType: hard - -"esbuild-linux-arm@npm:0.13.12": - version: 0.13.12 - resolution: "esbuild-linux-arm@npm:0.13.12" - conditions: os=linux & cpu=arm - languageName: node - linkType: hard - -"esbuild-linux-mips64le@npm:0.13.12": - version: 0.13.12 - resolution: "esbuild-linux-mips64le@npm:0.13.12" - conditions: os=linux & cpu=mips64el - languageName: node - linkType: hard - -"esbuild-linux-ppc64le@npm:0.13.12": - version: 0.13.12 - resolution: "esbuild-linux-ppc64le@npm:0.13.12" - conditions: os=linux & cpu=ppc64 - languageName: node - linkType: hard - -"esbuild-netbsd-64@npm:0.13.12": - version: 0.13.12 - resolution: "esbuild-netbsd-64@npm:0.13.12" - conditions: os=netbsd & cpu=x64 - languageName: node - linkType: hard - -"esbuild-openbsd-64@npm:0.13.12": - version: 0.13.12 - resolution: "esbuild-openbsd-64@npm:0.13.12" - conditions: os=openbsd & cpu=x64 - languageName: node - linkType: hard - "esbuild-runner@npm:^2.2.1": version: 2.2.1 resolution: "esbuild-runner@npm:2.2.1" @@ -13379,102 +13442,80 @@ __metadata: languageName: node linkType: hard -"esbuild-sunos-64@npm:0.13.12": - version: 0.13.12 - resolution: "esbuild-sunos-64@npm:0.13.12" - conditions: os=sunos & cpu=x64 - languageName: node - linkType: hard - -"esbuild-windows-32@npm:0.13.12": - version: 0.13.12 - resolution: "esbuild-windows-32@npm:0.13.12" - conditions: os=win32 & cpu=ia32 - languageName: node - linkType: hard - -"esbuild-windows-64@npm:0.13.12": - version: 0.13.12 - resolution: "esbuild-windows-64@npm:0.13.12" - conditions: os=win32 & cpu=x64 - languageName: node - linkType: hard - -"esbuild-windows-arm64@npm:0.13.12": - version: 0.13.12 - resolution: "esbuild-windows-arm64@npm:0.13.12" - conditions: os=win32 & cpu=arm64 - languageName: node - linkType: hard - -"esbuild@npm:^0.11.13": - version: 0.11.23 - resolution: "esbuild@npm:0.11.23" - bin: - esbuild: bin/esbuild - checksum: d55bea84f94e7854fa6fb013ffc63747f2286001b3d73c2a7beb605e6e4847c24ff2cde0660f03045d06d083695c8082423b007a9ee8687e70b52719458bfdd6 - languageName: node - linkType: hard - -"esbuild@npm:^0.13.10": - version: 0.13.12 - resolution: "esbuild@npm:0.13.12" - dependencies: - esbuild-android-arm64: 0.13.12 - esbuild-darwin-64: 0.13.12 - esbuild-darwin-arm64: 0.13.12 - esbuild-freebsd-64: 0.13.12 - esbuild-freebsd-arm64: 0.13.12 - esbuild-linux-32: 0.13.12 - esbuild-linux-64: 0.13.12 - esbuild-linux-arm: 0.13.12 - esbuild-linux-arm64: 0.13.12 - esbuild-linux-mips64le: 0.13.12 - esbuild-linux-ppc64le: 0.13.12 - esbuild-netbsd-64: 0.13.12 - esbuild-openbsd-64: 0.13.12 - esbuild-sunos-64: 0.13.12 - esbuild-windows-32: 0.13.12 - esbuild-windows-64: 0.13.12 - esbuild-windows-arm64: 0.13.12 +"esbuild@npm:~0.17": + version: 0.17.0 + resolution: "esbuild@npm:0.17.0" + dependencies: + "@esbuild/android-arm": 0.17.0 + "@esbuild/android-arm64": 0.17.0 + "@esbuild/android-x64": 0.17.0 + "@esbuild/darwin-arm64": 0.17.0 + "@esbuild/darwin-x64": 0.17.0 + "@esbuild/freebsd-arm64": 0.17.0 + "@esbuild/freebsd-x64": 0.17.0 + "@esbuild/linux-arm": 0.17.0 + "@esbuild/linux-arm64": 0.17.0 + "@esbuild/linux-ia32": 0.17.0 + "@esbuild/linux-loong64": 0.17.0 + "@esbuild/linux-mips64el": 0.17.0 + "@esbuild/linux-ppc64": 0.17.0 + "@esbuild/linux-riscv64": 0.17.0 + "@esbuild/linux-s390x": 0.17.0 + "@esbuild/linux-x64": 0.17.0 + "@esbuild/netbsd-x64": 0.17.0 + "@esbuild/openbsd-x64": 0.17.0 + "@esbuild/sunos-x64": 0.17.0 + "@esbuild/win32-arm64": 0.17.0 + "@esbuild/win32-ia32": 0.17.0 + "@esbuild/win32-x64": 0.17.0 dependenciesMeta: - esbuild-android-arm64: + "@esbuild/android-arm": + optional: true + "@esbuild/android-arm64": + optional: true + "@esbuild/android-x64": + optional: true + "@esbuild/darwin-arm64": + optional: true + "@esbuild/darwin-x64": + optional: true + "@esbuild/freebsd-arm64": optional: true - esbuild-darwin-64: + "@esbuild/freebsd-x64": optional: true - esbuild-darwin-arm64: + "@esbuild/linux-arm": optional: true - esbuild-freebsd-64: + "@esbuild/linux-arm64": optional: true - esbuild-freebsd-arm64: + "@esbuild/linux-ia32": optional: true - esbuild-linux-32: + "@esbuild/linux-loong64": optional: true - esbuild-linux-64: + "@esbuild/linux-mips64el": optional: true - esbuild-linux-arm: + "@esbuild/linux-ppc64": optional: true - esbuild-linux-arm64: + "@esbuild/linux-riscv64": optional: true - esbuild-linux-mips64le: + "@esbuild/linux-s390x": optional: true - esbuild-linux-ppc64le: + "@esbuild/linux-x64": optional: true - esbuild-netbsd-64: + "@esbuild/netbsd-x64": optional: true - esbuild-openbsd-64: + "@esbuild/openbsd-x64": optional: true - esbuild-sunos-64: + "@esbuild/sunos-x64": optional: true - esbuild-windows-32: + "@esbuild/win32-arm64": optional: true - esbuild-windows-64: + "@esbuild/win32-ia32": optional: true - esbuild-windows-arm64: + "@esbuild/win32-x64": optional: true bin: esbuild: bin/esbuild - checksum: 7ef4ef3e8eec11b832224b8886ccea22f72e89e2d9b59ec45d03bb2a512ca725fdb06e88609884e0721a4b90d1095e3ec898bd6f39a4efa0e2c99770730e0041 + checksum: eabf1d3d9230b1367edbdd24c89a35f60861c120377844af9f8daa084133f4dfc43697484b14e92a209d2055c8903fdf2b43fee8dbabd7d1fbcd7031639fca9e languageName: node linkType: hard From b74b4f82480317f82a79e0b1c475676d2a132248 Mon Sep 17 00:00:00 2001 From: Mark Erikson Date: Sat, 14 Jan 2023 13:33:49 -0500 Subject: [PATCH 006/412] Update Rollup UMD output --- packages/toolkit/scripts/build.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/toolkit/scripts/build.ts b/packages/toolkit/scripts/build.ts index 6a2effdf8d..4521752b93 100644 --- a/packages/toolkit/scripts/build.ts +++ b/packages/toolkit/scripts/build.ts @@ -2,7 +2,7 @@ // @ts-check import { build } from 'esbuild' import terser from 'terser' -import rollup from 'rollup' +import { rollup } from 'rollup' import path from 'path' import fs from 'fs-extra' import ts from 'typescript' @@ -256,7 +256,7 @@ async function buildUMD( ) { for (let umdExtension of ['umd', 'umd.min']) { const input = path.join(outputPath, `${prefix}.${umdExtension}.js`) - const instance = await rollup.rollup({ + const instance = await rollup({ input: [input], onwarn(warning, warn) { if (warning.code === 'THIS_IS_UNDEFINED') return @@ -272,6 +272,7 @@ async function buildUMD( // These packages have specific global names from their UMD bundles react: 'React', 'react-redux': 'ReactRedux', + '@reduxjs/toolkit': 'RTK', }, }) } From 1668be74cfe1f4b73ca1b668d18665948b7d8d00 Mon Sep 17 00:00:00 2001 From: Mark Erikson Date: Sat, 14 Jan 2023 16:35:45 -0500 Subject: [PATCH 007/412] Use TSX to run build script with ESBuild --- package.json | 1 + packages/toolkit/package.json | 8 ++-- packages/toolkit/scripts/cli.js | 2 - packages/toolkit/scripts/register.js | 11 ------ yarn.lock | 59 +++++++++++++++++++++++++++- 5 files changed, 63 insertions(+), 18 deletions(-) delete mode 100644 packages/toolkit/scripts/cli.js delete mode 100644 packages/toolkit/scripts/register.js diff --git a/package.json b/package.json index 6ebcdaf2d3..8996f8c20a 100644 --- a/package.json +++ b/package.json @@ -44,6 +44,7 @@ "@babel/traverse": "7.19.3", "@babel/types": "7.19.3", "console-testing-library": "patch:console-testing-library@npm:0.3.1#.yarn/patches/console-testing-library__npm_0.3.1.patch", + "esbuild": "0.17.0", "msw": "patch:msw@npm:0.40.2#.yarn/patches/msw-npm-0.40.2-2107d48752", "jscodeshift": "0.13.1", "react-redux": "npm:8.0.2", diff --git a/packages/toolkit/package.json b/packages/toolkit/package.json index fa9771d80e..f3582b741d 100644 --- a/packages/toolkit/package.json +++ b/packages/toolkit/package.json @@ -73,14 +73,16 @@ "terser": "^5.6.1", "ts-jest": "^27", "tslib": "^1.10.0", + "tsx": "^3.12.2", "typescript": "~4.9", "yargs": "^15.3.1" }, "scripts": { - "build-ci": "yarn rimraf dist && yarn tsc && node scripts/cli.js --skipExtraction", + "run-build": "tsx ./scripts/build.ts", + "build-ci": "yarn rimraf dist && yarn tsc && yarn run-build --skipExtraction", "build-prepare": "npm run build-ci", - "build": "yarn rimraf dist && yarn tsc && node scripts/cli.js --local --skipExtraction", - "build-only": "yarn rimraf dist && yarn tsc && node scripts/cli.js --skipExtraction", + "build": "yarn rimraf dist && yarn tsc && yarn run-build --local --skipExtraction", + "build-only": "yarn rimraf dist && yarn tsc && yarn run-build --skipExtraction", "format": "prettier --write \"(src|examples)/**/*.{ts,tsx}\" \"**/*.md\"", "format:check": "prettier --list-different \"(src|examples)/**/*.{ts,tsx}\" \"docs/*/**.md\"", "lint": "eslint src examples", diff --git a/packages/toolkit/scripts/cli.js b/packages/toolkit/scripts/cli.js deleted file mode 100644 index a6105435ac..0000000000 --- a/packages/toolkit/scripts/cli.js +++ /dev/null @@ -1,2 +0,0 @@ -require('./register') // must be the first -require('./build.ts') diff --git a/packages/toolkit/scripts/register.js b/packages/toolkit/scripts/register.js deleted file mode 100644 index 24f60a2728..0000000000 --- a/packages/toolkit/scripts/register.js +++ /dev/null @@ -1,11 +0,0 @@ -const esbuild = require('esbuild') -const fs = require('fs') -require.extensions['.ts'] = (mod, filename) => { - const ts = fs.readFileSync(filename, 'utf-8') - const { code } = esbuild.transformSync(ts, { - loader: 'ts', - target: 'es2017', - format: 'cjs', - }) - mod._compile(code, filename) -} diff --git a/yarn.lock b/yarn.lock index 4a71afeda1..473103ab07 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4278,6 +4278,36 @@ __metadata: languageName: node linkType: hard +"@esbuild-kit/cjs-loader@npm:^2.4.1": + version: 2.4.1 + resolution: "@esbuild-kit/cjs-loader@npm:2.4.1" + dependencies: + "@esbuild-kit/core-utils": ^3.0.0 + get-tsconfig: ^4.2.0 + checksum: a516065907be0ead76ac2199ccb08ff92659ba5e2edb4bb8772b6a63afe4faed7eb45a3b4d87266a68c7c135c3dba971cd087bc6f16c382356e835c7dd3440f5 + languageName: node + linkType: hard + +"@esbuild-kit/core-utils@npm:^3.0.0": + version: 3.0.0 + resolution: "@esbuild-kit/core-utils@npm:3.0.0" + dependencies: + esbuild: ~0.15.10 + source-map-support: ^0.5.21 + checksum: 0e89ec718e2211bf95c48a8085aaef88e8e416f42abd1c62d488d5458eecd3fbc144179a0c5570ad36fa7e2d3bbc411f8d3fb28802c37ced2154dc2c6ded9dfe + languageName: node + linkType: hard + +"@esbuild-kit/esm-loader@npm:^2.5.4": + version: 2.5.4 + resolution: "@esbuild-kit/esm-loader@npm:2.5.4" + dependencies: + "@esbuild-kit/core-utils": ^3.0.0 + get-tsconfig: ^4.2.0 + checksum: 8f4b4b6470f7afeb58ddc15ddcc4e35a6d25910133f2b21a82a793918c6f8e28768d5cd77b1c90a003f2f1c83cb476b68354580a4e04e83e2da3312afe5b5895 + languageName: node + linkType: hard + "@esbuild/android-arm64@npm:0.17.0": version: 0.17.0 resolution: "@esbuild/android-arm64@npm:0.17.0" @@ -6536,6 +6566,7 @@ __metadata: terser: ^5.6.1 ts-jest: ^27 tslib: ^1.10.0 + tsx: ^3.12.2 typescript: ~4.9 yargs: ^15.3.1 peerDependencies: @@ -13442,7 +13473,7 @@ __metadata: languageName: node linkType: hard -"esbuild@npm:~0.17": +"esbuild@npm:0.17.0": version: 0.17.0 resolution: "esbuild@npm:0.17.0" dependencies: @@ -15171,6 +15202,13 @@ fsevents@^1.2.7: languageName: node linkType: hard +"get-tsconfig@npm:^4.2.0": + version: 4.3.0 + resolution: "get-tsconfig@npm:4.3.0" + checksum: 2597aab99aa3a24db209e192a3e5874ac47fc5abc71703ee26346e0c5816cb346ca09fc813c739db5862d3a2905d89aeca1b0cbc46c2b272398d672309aaf414 + languageName: node + linkType: hard + "get-value@npm:^2.0.3, get-value@npm:^2.0.6": version: 2.0.6 resolution: "get-value@npm:2.0.6" @@ -25235,7 +25273,7 @@ fsevents@^1.2.7: languageName: node linkType: hard -"source-map-support@npm:^0.5.16, source-map-support@npm:^0.5.6, source-map-support@npm:~0.5.12, source-map-support@npm:~0.5.20": +"source-map-support@npm:^0.5.16, source-map-support@npm:^0.5.21, source-map-support@npm:^0.5.6, source-map-support@npm:~0.5.12, source-map-support@npm:~0.5.20": version: 0.5.21 resolution: "source-map-support@npm:0.5.21" dependencies: @@ -26714,6 +26752,23 @@ fsevents@^1.2.7: languageName: node linkType: hard +"tsx@npm:^3.12.2": + version: 3.12.2 + resolution: "tsx@npm:3.12.2" + dependencies: + "@esbuild-kit/cjs-loader": ^2.4.1 + "@esbuild-kit/core-utils": ^3.0.0 + "@esbuild-kit/esm-loader": ^2.5.4 + fsevents: ~2.3.2 + dependenciesMeta: + fsevents: + optional: true + bin: + tsx: dist/cli.js + checksum: c227512ed7b480bd295c5420c560ec8311a4827b92e8040b7ca519627c7e1d35d48c25b26b4b11277cc437bba98696149f5148a9a06334f37b48bbaa870bff64 + languageName: node + linkType: hard + "tty-browserify@npm:0.0.0": version: 0.0.0 resolution: "tty-browserify@npm:0.0.0" From 54408b0b22052e1a5eba2c6b70ab0824eecbb22c Mon Sep 17 00:00:00 2001 From: Mark Erikson Date: Sat, 14 Jan 2023 17:23:24 -0500 Subject: [PATCH 008/412] Use named export for `produce` from Immer instead of default --- packages/toolkit/src/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/toolkit/src/index.ts b/packages/toolkit/src/index.ts index 61b9af0b5e..4df771a038 100644 --- a/packages/toolkit/src/index.ts +++ b/packages/toolkit/src/index.ts @@ -1,6 +1,6 @@ export * from 'redux' export { - default as createNextState, + produce as createNextState, current, freeze, original, From 6606e37e75a4c544462de136a8657e62a3cdb5bd Mon Sep 17 00:00:00 2001 From: Mark Erikson Date: Sat, 14 Jan 2023 17:50:39 -0500 Subject: [PATCH 009/412] Remove now-unneeded @ts-expect-error check in listener test --- .../tests/listenerMiddleware.test.ts | 33 ++++++++++--------- 1 file changed, 18 insertions(+), 15 deletions(-) diff --git a/packages/toolkit/src/listenerMiddleware/tests/listenerMiddleware.test.ts b/packages/toolkit/src/listenerMiddleware/tests/listenerMiddleware.test.ts index d6b4811c77..bba8fd1662 100644 --- a/packages/toolkit/src/listenerMiddleware/tests/listenerMiddleware.test.ts +++ b/packages/toolkit/src/listenerMiddleware/tests/listenerMiddleware.test.ts @@ -184,7 +184,7 @@ describe('createListenerMiddleware', () => { middleware: (gDM) => gDM().prepend(listenerMiddleware.middleware), }) - let foundExtra: number | null = null + let foundExtra: number | null = null const typedAddListener = listenerMiddleware.startListening as TypedStartListening< @@ -1061,12 +1061,13 @@ describe('createListenerMiddleware', () => { middleware: (gDM) => gDM().prepend(middleware), }) - const typedAddListener = - startListening as TypedStartListening< - CounterState, - typeof store.dispatch - > - let result: [ReturnType, CounterState, CounterState] | null = null + const typedAddListener = startListening as TypedStartListening< + CounterState, + typeof store.dispatch + > + let result: + | [ReturnType, CounterState, CounterState] + | null = null typedAddListener({ predicate: incrementByAmount.match, @@ -1126,31 +1127,34 @@ describe('createListenerMiddleware', () => { expect(takeResult).toEqual([increment(), stateCurrent, stateBefore]) }) - test("take resolves to `[A, CurrentState, PreviousState] | null` if a possibly undefined timeout parameter is provided", async () => { + test('take resolves to `[A, CurrentState, PreviousState] | null` if a possibly undefined timeout parameter is provided', async () => { const store = configureStore({ reducer: counterSlice.reducer, middleware: (gDM) => gDM().prepend(middleware), }) - type ExpectedTakeResultType = readonly [ReturnType, CounterState, CounterState] | null + type ExpectedTakeResultType = + | readonly [ReturnType, CounterState, CounterState] + | null let timeout: number | undefined = undefined let done = false - const startAppListening = startListening as TypedStartListening + const startAppListening = + startListening as TypedStartListening startAppListening({ predicate: incrementByAmount.match, effect: async (_, listenerApi) => { const stateBefore = listenerApi.getState() - + let takeResult = await listenerApi.take(increment.match, timeout) const stateCurrent = listenerApi.getState() expect(takeResult).toEqual([increment(), stateCurrent, stateBefore]) - + timeout = 1 takeResult = await listenerApi.take(increment.match, timeout) expect(takeResult).toBeNull() - + expectType(takeResult) done = true @@ -1160,7 +1164,7 @@ describe('createListenerMiddleware', () => { store.dispatch(increment()) await delay(25) - expect(done).toBe(true); + expect(done).toBe(true) }) test('condition method resolves promise when the predicate succeeds', async () => { @@ -1444,7 +1448,6 @@ describe('createListenerMiddleware', () => { return typeof action.payload === 'boolean' }, effect: (action, listenerApi) => { - // @ts-expect-error expectExactType>(action) }, }) From 5b3bf2cb83a3ec82989111b00d4adede45033cac Mon Sep 17 00:00:00 2001 From: Mark Erikson Date: Sat, 14 Jan 2023 18:11:41 -0500 Subject: [PATCH 010/412] Remove reducer object notation docs examples --- docs/api/createAsyncThunk.mdx | 44 +++++++++--------------- docs/api/createReducer.mdx | 17 +--------- docs/api/createSlice.mdx | 64 +++++++---------------------------- 3 files changed, 30 insertions(+), 95 deletions(-) diff --git a/docs/api/createAsyncThunk.mdx b/docs/api/createAsyncThunk.mdx index d0a641df99..2777e187d2 100644 --- a/docs/api/createAsyncThunk.mdx +++ b/docs/api/createAsyncThunk.mdx @@ -210,27 +210,14 @@ type RejectedWithValue = ( ) => RejectedWithValueAction ``` -To handle these actions in your reducers, reference the action creators in `createReducer` or `createSlice` using either the object key notation or the "builder callback" notation. (Note that if you use TypeScript, you [should use the "builder callback" notation to ensure the types are inferred correctly](../usage/usage-with-typescript.md#type-safety-with-extrareducers)): +To handle these actions in your reducers, reference the action creators in `createReducer` or `createSlice` using the "builder callback" notation. ```ts no-transpile {2,6,14,23} -const reducer1 = createReducer(initialState, { - [fetchUserById.fulfilled]: (state, action) => {}, -}) - -const reducer2 = createReducer(initialState, (builder) => { +const reducer1 = createReducer(initialState, (builder) => { builder.addCase(fetchUserById.fulfilled, (state, action) => {}) }) -const reducer3 = createSlice({ - name: 'users', - initialState, - reducers: {}, - extraReducers: { - [fetchUserById.fulfilled]: (state, action) => {}, - }, -}) - -const reducer4 = createSlice({ +const reducer2 = createSlice({ name: 'users', initialState, reducers: {}, @@ -546,19 +533,20 @@ test('this thunk should always be skipped', async () => { import { createAsyncThunk, createSlice } from '@reduxjs/toolkit' import { userAPI, User } from './userAPI' -const fetchUserById = createAsyncThunk ( - 'users/fetchByIdStatus', - async (userId: string, { getState, requestId }) => { - const { currentRequestId, loading } = getState().users - if (loading !== 'pending' || requestId !== currentRequestId) { - return - } - const response = await userAPI.fetchById(userId) - return response.data +const fetchUserById = createAsyncThunk< + User, + string, + { + state: { users: { loading: string; currentRequestId: string } } } -) +>('users/fetchByIdStatus', async (userId: string, { getState, requestId }) => { + const { currentRequestId, loading } = getState().users + if (loading !== 'pending' || requestId !== currentRequestId) { + return + } + const response = await userAPI.fetchById(userId) + return response.data +}) const usersSlice = createSlice({ name: 'users', diff --git a/docs/api/createReducer.mdx b/docs/api/createReducer.mdx index d8e164d70e..5103adbe16 100644 --- a/docs/api/createReducer.mdx +++ b/docs/api/createReducer.mdx @@ -37,9 +37,7 @@ function counterReducer(state = initialState, action) { This approach works well, but is a bit boilerplate-y and error-prone. For instance, it is easy to forget the `default` case or setting the initial state. -The `createReducer` helper streamlines the implementation of such reducers. It supports two different forms of defining case -reducers to handle actions: a "builder callback" notation and a "map object" notation. Both are equivalent, but the "builder callback" -notation is preferred. +The `createReducer` helper streamlines the implementation of such reducers. It uses a "builder callback" notation to define handlers for specific action types, matching against a range of actions, or handling a default case. This is conceptually similar to a switch statement, but with better TS support. With `createReducer`, your reducers instead look like: @@ -74,8 +72,6 @@ const counterReducer = createReducer(initialState, (builder) => { [overloadSummary](docblock://createReducer.ts?token=createReducer&overload=0) -The recommended way of using `createReducer` is the builder callback notation, as it works best with TypeScript and most IDEs. - ### Parameters [params](docblock://createReducer.ts?token=createReducer&overload=0) @@ -110,17 +106,6 @@ The recommended way of using `createReducer` is the builder callback notation, a [params,examples](docblock://mapBuilders.ts?token=ActionReducerMapBuilder.addDefaultCase) -## Usage with the "Map Object" Notation - -[overloadSummary](docblock://createReducer.ts?token=createReducer&overload=1) - -While this notation is a bit shorter, it works only in JavaScript, not TypeScript and has less integration with IDEs, -so we recommend the "builder callback" notation in most cases. - -### Parameters - -[params](docblock://createReducer.ts?token=createReducer&overload=1) - ### Returns The generated reducer function. diff --git a/docs/api/createSlice.mdx b/docs/api/createSlice.mdx index 6c640444b8..39f10f8b70 100644 --- a/docs/api/createSlice.mdx +++ b/docs/api/createSlice.mdx @@ -59,12 +59,8 @@ function createSlice({ initialState: any, // An object of "case reducers". Key names will be used to generate actions. reducers: Object - // A "builder callback" function used to add more reducers, or - // an additional object of "case reducers", where the keys should be other - // action types - extraReducers?: - | Object - | ((builder: ActionReducerMapBuilder) => void) + // A "builder callback" function used to add more reducers + extraReducers?: ((builder: ActionReducerMapBuilder) => void) }) ``` @@ -149,48 +145,12 @@ the function from `reducers` will be used to handle that action type. ### The `extraReducers` "builder callback" notation -The recommended way of using `extraReducers` is to use a callback that receives a `ActionReducerMapBuilder` instance. - -This builder notation is also the only way to add matcher reducers and default case reducers to your slice. +Similar to `createReducer`, the `extraReducers` field uses a "builder callback" notation to define handlers for specific action types, matching against a range of actions, or handling a default case. This is conceptually similar to a switch statement, but with better TS support as it can infer the action type from the provided action creator. It's particularly useful for working with actions produced by `createAction` and `createAsyncThunk`. [examples](docblock://createSlice.ts?token=CreateSliceOptions.extraReducers) -We recommend using this API as it has better TypeScript support (and thus, IDE autocomplete even for JavaScript users), as it will correctly infer the action type in the reducer based on the provided action creator. -It's particularly useful for working with actions produced by `createAction` and `createAsyncThunk`. - See [the "Builder Callback Notation" section of the `createReducer` reference](./createReducer.mdx#usage-with-the-builder-callback-notation) for details on how to use `builder.addCase`, `builder.addMatcher`, and `builder.addDefault` -### The `extraReducers` "map object" notation - -Like `reducers`, `extraReducers` can be an object containing Redux case reducer functions. However, the keys should -be other Redux string action type constants, and `createSlice` will _not_ auto-generate action types or action creators -for reducers included in this parameter. - -Action creators that were generated using [`createAction`](./createAction.mdx) may be used directly as the keys here, using -computed property syntax. - -```js -const incrementBy = createAction('incrementBy') - -createSlice({ - name: 'counter', - initialState: 0, - reducers: {}, - extraReducers: { - [incrementBy]: (state, action) => { - return state + action.payload - }, - 'some/other/action': (state, action) => {}, - }, -}) -``` - -:::tip - -We recommend using the `builder callback` API as the default, especially if you are using TypeScript. If you do not use the `builder callback` and are using TypeScript, you will need to use `actionCreator.type` or `actionCreator.toString()` to force the TS compiler to accept the computed property. Please see [Usage With TypeScript](./../usage/usage-with-typescript.md#type-safety-with-extraReducers) for further details. - -::: - ## Return Value `createSlice` will return an object that looks like: @@ -268,14 +228,16 @@ const user = createSlice({ }, }, // "map object API" - extraReducers: { - // @ts-expect-error in TypeScript, this would need to be [counter.actions.increment.type] - [counter.actions.increment]: ( - state, - action /* action will be inferred as "any", as the map notation does not contain type information */ - ) => { - state.age += 1 - }, + extraReducers: (builder) => { + builder.addCase( + counter.actions.increment, + ( + state, + action /* action will be inferred as "any", as the map notation does not contain type information */ + ) => { + state.age += 1 + } + ) }, }) From d717c901ecb669224a16f7cf77acb16dc5c55826 Mon Sep 17 00:00:00 2001 From: Mark Erikson Date: Sat, 14 Jan 2023 18:22:56 -0500 Subject: [PATCH 011/412] Release 2.0.0-alpha.0 --- packages/toolkit/package.json | 2 +- yarn.lock | 62 +++++++++++++++++++++++------------ 2 files changed, 42 insertions(+), 22 deletions(-) diff --git a/packages/toolkit/package.json b/packages/toolkit/package.json index f3582b741d..fa3977216b 100644 --- a/packages/toolkit/package.json +++ b/packages/toolkit/package.json @@ -1,6 +1,6 @@ { "name": "@reduxjs/toolkit", - "version": "1.9.1", + "version": "2.0.0-alpha.0", "description": "The official, opinionated, batteries-included toolset for efficient Redux development", "author": "Mark Erikson ", "license": "MIT", diff --git a/yarn.lock b/yarn.lock index 473103ab07..816ef37411 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6512,7 +6512,47 @@ __metadata: languageName: unknown linkType: soft -"@reduxjs/toolkit@^1.6.0, @reduxjs/toolkit@^1.6.0-rc.1, @reduxjs/toolkit@^1.8.0, @reduxjs/toolkit@workspace:packages/toolkit": +"@reduxjs/toolkit@npm:1.8.1": + version: 1.8.1 + resolution: "@reduxjs/toolkit@npm:1.8.1" + dependencies: + immer: ^9.0.7 + redux: ^4.1.2 + redux-thunk: ^2.4.1 + reselect: ^4.1.5 + peerDependencies: + react: ^16.9.0 || ^17.0.0 || ^18 + react-redux: ^7.2.1 || ^8.0.0-beta + peerDependenciesMeta: + react: + optional: true + react-redux: + optional: true + checksum: be5cdea975a8a631fe2d88cafc7077554c7bc3621a4a7031556cc17e5dec26359018f2614c325895e7ab50865f5c511025d1e589ca01de7e2bd88d95e0a1a963 + languageName: node + linkType: hard + +"@reduxjs/toolkit@npm:^1.6.0, @reduxjs/toolkit@npm:^1.6.0-rc.1, @reduxjs/toolkit@npm:^1.8.0": + version: 1.9.1 + resolution: "@reduxjs/toolkit@npm:1.9.1" + dependencies: + immer: ^9.0.16 + redux: ^4.2.0 + redux-thunk: ^2.4.2 + reselect: ^4.1.7 + peerDependencies: + react: ^16.9.0 || ^17.0.0 || ^18 + react-redux: ^7.2.1 || ^8.0.2 + peerDependenciesMeta: + react: + optional: true + react-redux: + optional: true + checksum: e6700a0d45198ab525c96ff0425fa0125fbdc37ce514f0c77c30225837113279ceec9190ac3da35cb20e77553e56342021788bbf17465819068c4db34cb3d87f + languageName: node + linkType: hard + +"@reduxjs/toolkit@workspace:packages/toolkit": version: 0.0.0-use.local resolution: "@reduxjs/toolkit@workspace:packages/toolkit" dependencies: @@ -6580,26 +6620,6 @@ __metadata: languageName: unknown linkType: soft -"@reduxjs/toolkit@npm:1.8.1": - version: 1.8.1 - resolution: "@reduxjs/toolkit@npm:1.8.1" - dependencies: - immer: ^9.0.7 - redux: ^4.1.2 - redux-thunk: ^2.4.1 - reselect: ^4.1.5 - peerDependencies: - react: ^16.9.0 || ^17.0.0 || ^18 - react-redux: ^7.2.1 || ^8.0.0-beta - peerDependenciesMeta: - react: - optional: true - react-redux: - optional: true - checksum: be5cdea975a8a631fe2d88cafc7077554c7bc3621a4a7031556cc17e5dec26359018f2614c325895e7ab50865f5c511025d1e589ca01de7e2bd88d95e0a1a963 - languageName: node - linkType: hard - "@rollup/plugin-alias@npm:^3.1.1": version: 3.1.2 resolution: "@rollup/plugin-alias@npm:3.1.2" From fbd2cda51f6e9d8d68b034d553fa9e7cd275109f Mon Sep 17 00:00:00 2001 From: Mark Erikson Date: Sat, 14 Jan 2023 17:34:17 -0500 Subject: [PATCH 012/412] Migrate the RTK package to be full ESM --- packages/toolkit/package.json | 22 +++++- packages/toolkit/query/package.json | 14 +++- packages/toolkit/query/react/package.json | 14 +++- packages/toolkit/scripts/build.ts | 84 ++++++++++++++--------- packages/toolkit/scripts/types.ts | 9 ++- 5 files changed, 103 insertions(+), 40 deletions(-) diff --git a/packages/toolkit/package.json b/packages/toolkit/package.json index fa3977216b..b6d207a212 100644 --- a/packages/toolkit/package.json +++ b/packages/toolkit/package.json @@ -22,10 +22,28 @@ "publishConfig": { "access": "public" }, - "main": "dist/index.js", + "type": "module", "module": "dist/redux-toolkit.modern.js", - "unpkg": "dist/redux-toolkit.umd.min.js", + "main": "dist/cjs/index.js", "types": "dist/index.d.ts", + "exports": { + "./package.json": "./package.json", + ".": { + "types": "./dist/index.d.ts", + "import": "./dist/redux-toolkit.modern.js", + "default": "./dist/cjs/index.js" + }, + "./query": { + "types": "./dist/query/index.d.ts", + "import": "./dist/query/rtk-query.modern.js", + "default": "./dist/query/cjs/index.js" + }, + "./query/react": { + "types": "./dist/query/react/index.d.ts", + "import": "./dist/query/react/rtk-query-react.modern.js", + "default": "./dist/query/react/cjs/index.js" + } + }, "devDependencies": { "@microsoft/api-extractor": "^7.13.2", "@size-limit/preset-small-lib": "^4.11.0", diff --git a/packages/toolkit/query/package.json b/packages/toolkit/query/package.json index f093468b90..ca99300e54 100644 --- a/packages/toolkit/query/package.json +++ b/packages/toolkit/query/package.json @@ -2,10 +2,18 @@ "name": "@reduxjs/toolkit-query", "version": "1.0.0", "description": "", - "main": "../dist/query/index.js", + "type": "module", "module": "../dist/query/rtk-query.modern.js", - "unpkg": "../dist/query/rtk-query.umd.min.js", - "types": "../dist/query/index.d.ts", + "main": "../dist/query/cjs/index.js", + "types": "./../dist/query/index.d.ts", + "exports": { + "./package.json": "./package.json", + ".": { + "types": "./../dist/query/index.d.ts", + "import": "./../dist/query/rtk-query.modern.js", + "default": "./../dist/query/cjs/index.js" + } + }, "author": "Mark Erikson ", "license": "MIT", "sideEffects": false diff --git a/packages/toolkit/query/react/package.json b/packages/toolkit/query/react/package.json index 69f96cec3b..20fcabea62 100644 --- a/packages/toolkit/query/react/package.json +++ b/packages/toolkit/query/react/package.json @@ -2,11 +2,19 @@ "name": "@reduxjs/toolkit-query-react", "version": "1.0.0", "description": "", - "main": "../../dist/query/react/index.js", + "type": "module", "module": "../../dist/query/react/rtk-query-react.modern.js", - "unpkg": "../../dist/query/react/rtk-query-react.umd.min.js", + "main": "../../dist/query/react/cjs/index.js", + "types": "./../../dist/query/react/index.d.ts", + "exports": { + "./package.json": "./package.json", + ".": { + "types": "./../../dist/query/react/index.d.ts", + "import": "./../../dist/query/react/rtk-query-react.modern.js", + "default": "./../../dist/query/react/cjs/index.js" + } + }, "author": "Mark Erikson ", "license": "MIT", - "types": "../../dist/query/react/index.d.ts", "sideEffects": false } diff --git a/packages/toolkit/scripts/build.ts b/packages/toolkit/scripts/build.ts index 4521752b93..611b8d08f4 100644 --- a/packages/toolkit/scripts/build.ts +++ b/packages/toolkit/scripts/build.ts @@ -1,7 +1,9 @@ /* eslint-disable import/first */ +import { fileURLToPath } from 'url' + // @ts-check import { build } from 'esbuild' -import terser from 'terser' +import { minify as terserMinify } from 'terser' import { rollup } from 'rollup' import path from 'path' import fs from 'fs-extra' @@ -16,7 +18,9 @@ import { extractInlineSourcemap, removeInlineSourceMap } from './sourcemap' import type { BuildOptions, EntryPointOptions } from './types' import { appendInlineSourceMap, getLocation } from './sourcemap' -const sleep = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms)) +// No __dirname under Node ESM +const __filename = fileURLToPath(import.meta.url) +const __dirname = path.dirname(__filename) const { argv } = yargs(process.argv) .option('local', { @@ -36,14 +40,14 @@ const buildTargets: BuildOptions[] = [ { format: 'cjs', name: 'cjs.development', - target: 'es2018', + target: 'esnext', minify: false, env: 'development', }, { format: 'cjs', name: 'cjs.production.min', - target: 'es2018', + target: 'esnext', minify: true, env: 'production', }, @@ -51,7 +55,7 @@ const buildTargets: BuildOptions[] = [ { format: 'esm', name: 'modern', - target: 'es2018', + target: 'esnext', minify: false, env: '', }, @@ -59,7 +63,7 @@ const buildTargets: BuildOptions[] = [ { format: 'esm', name: 'modern.development', - target: 'es2018', + target: 'esnext', minify: false, env: 'development', }, @@ -67,24 +71,24 @@ const buildTargets: BuildOptions[] = [ { format: 'esm', name: 'modern.production.min', - target: 'es2018', - minify: true, - env: 'production', - }, - { - format: 'umd', - name: 'umd', - target: 'es2018', - minify: false, - env: 'development', - }, - { - format: 'umd', - name: 'umd.min', - target: 'es2018', + target: 'esnext', minify: true, env: 'production', }, + // { + // format: 'umd', + // name: 'umd', + // target: 'es2018', + // minify: false, + // env: 'development', + // }, + // { + // format: 'umd', + // name: 'umd.min', + // target: 'es2018', + // minify: true, + // env: 'production', + // }, ] const entryPoints: EntryPointOptions[] = [ @@ -118,6 +122,9 @@ const esVersionMappings = { es2018: ts.ScriptTarget.ES2018, es2019: ts.ScriptTarget.ES2019, es2020: ts.ScriptTarget.ES2020, + es2021: ts.ScriptTarget.ES2021, + es2022: ts.ScriptTarget.ES2022, + esnext: ts.ScriptTarget.ESNext, } async function bundle(options: BuildOptions & EntryPointOptions) { @@ -132,10 +139,22 @@ async function bundle(options: BuildOptions & EntryPointOptions) { entryPoint, } = options - const outputFolder = path.join('dist', folder) + const folderSegments = ['dist', folder] + + if (format === 'cjs') { + folderSegments.push('cjs') + } + + const outputFolder = path.join(...folderSegments) const outputFilename = `${prefix}.${name}.js` + + fs.mkdirs(outputFolder) const outputFilePath = path.join(outputFolder, outputFilename) + if (format === 'cjs') { + await writeCommonJSEntry(outputFolder, prefix) + } + const result = await build({ entryPoints: [entryPoint], outfile: outputFilePath, @@ -212,7 +231,7 @@ async function bundle(options: BuildOptions & EntryPointOptions) { let mapping: RawSourceMap = mergedSourcemap if (minify) { - const transformResult = await terser.minify( + const transformResult = await terserMinify( appendInlineSourceMap(code, mapping), { sourceMap: { @@ -237,12 +256,14 @@ async function bundle(options: BuildOptions & EntryPointOptions) { } const relativePath = path.relative(process.cwd(), chunk.path) - console.log(`Build artifact: ${relativePath}, settings: `, { - target, - output: ts.ScriptTarget[esVersion], - }) await fs.writeFile(chunk.path, code) await fs.writeJSON(chunk.path + '.map', mapping) + + if (!chunk.path.includes('.map')) { + console.log(`Build artifact: ${relativePath}, settings: `, { + target, + }) + } } } @@ -279,9 +300,9 @@ async function buildUMD( } // Generates an index file to handle importing CJS dev/prod -async function writeEntry(folder: string, prefix: string) { +async function writeCommonJSEntry(folder: string, prefix: string) { await fs.writeFile( - path.join('dist', folder, 'index.js'), + path.join(folder, 'index.js'), `'use strict' if (process.env.NODE_ENV === 'production') { module.exports = require('./${prefix}.cjs.production.min.js') @@ -289,6 +310,8 @@ if (process.env.NODE_ENV === 'production') { module.exports = require('./${prefix}.cjs.development.js') }` ) + + await fs.writeFile(path.join(folder, 'package.json'), `{"type": "commonjs"}`) } interface BuildArgs { @@ -313,14 +336,13 @@ async function main({ skipExtraction = false, local = false }: BuildArgs) { }) ) await Promise.all(bundlePromises) - await writeEntry(folder, prefix) } // Run UMD builds after everything else so we don't have to sleep after each set for (let entryPoint of entryPoints) { const { folder } = entryPoint const outputPath = path.join('dist', folder) - await buildUMD(outputPath, entryPoint.prefix, entryPoint.globalName) + // await buildUMD(outputPath, entryPoint.prefix, entryPoint.globalName) } if (!skipExtraction) { diff --git a/packages/toolkit/scripts/types.ts b/packages/toolkit/scripts/types.ts index cf0d8f889c..9a9f48e626 100644 --- a/packages/toolkit/scripts/types.ts +++ b/packages/toolkit/scripts/types.ts @@ -11,7 +11,14 @@ export interface BuildOptions { | 'umd.min' minify: boolean env: 'development' | 'production' | '' - target?: 'es2017' | 'es2018' | 'es2019' | 'es2020' + target?: + | 'es2017' + | 'es2018' + | 'es2019' + | 'es2020' + | 'es2021' + | 'es2022' + | 'esnext' } export interface EntryPointOptions { From 605937235c96a344c3bf71a910929df55f19a48e Mon Sep 17 00:00:00 2001 From: Mark Erikson Date: Mon, 16 Jan 2023 19:02:26 -0500 Subject: [PATCH 013/412] Try fixing dist dir path --- packages/toolkit/scripts/build.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/toolkit/scripts/build.ts b/packages/toolkit/scripts/build.ts index 611b8d08f4..60e6469ee5 100644 --- a/packages/toolkit/scripts/build.ts +++ b/packages/toolkit/scripts/build.ts @@ -139,7 +139,7 @@ async function bundle(options: BuildOptions & EntryPointOptions) { entryPoint, } = options - const folderSegments = ['dist', folder] + const folderSegments = [outputDir, folder] if (format === 'cjs') { folderSegments.push('cjs') From 5a0e70aca686d6bef0cc95d1ef50a1b1597b83c4 Mon Sep 17 00:00:00 2001 From: Mark Erikson Date: Mon, 16 Jan 2023 19:12:51 -0500 Subject: [PATCH 014/412] Convert Jest file to ESM --- packages/toolkit/jest.config.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/toolkit/jest.config.js b/packages/toolkit/jest.config.js index 16cc49cf96..b332f3a10d 100644 --- a/packages/toolkit/jest.config.js +++ b/packages/toolkit/jest.config.js @@ -1,4 +1,4 @@ -module.exports = { +export default { testEnvironment: 'jest-environment-jsdom', setupFilesAfterEnv: ['./jest.setup.js'], testMatch: ['/src/**/*.(spec|test).[jt]s?(x)'], From a21dfcd8082c59eda0eacd1b462483f3bc7a5832 Mon Sep 17 00:00:00 2001 From: Mark Erikson Date: Mon, 16 Jan 2023 19:18:16 -0500 Subject: [PATCH 015/412] Ensure CJS output folders --- packages/toolkit/scripts/build.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/toolkit/scripts/build.ts b/packages/toolkit/scripts/build.ts index 60e6469ee5..b66f456ed0 100644 --- a/packages/toolkit/scripts/build.ts +++ b/packages/toolkit/scripts/build.ts @@ -148,7 +148,8 @@ async function bundle(options: BuildOptions & EntryPointOptions) { const outputFolder = path.join(...folderSegments) const outputFilename = `${prefix}.${name}.js` - fs.mkdirs(outputFolder) + await fs.ensureDir(outputFolder) + const outputFilePath = path.join(outputFolder, outputFilename) if (format === 'cjs') { From e94ee23896fb367990e0eb686eaff031a961a346 Mon Sep 17 00:00:00 2001 From: Mark Erikson Date: Mon, 16 Jan 2023 19:26:03 -0500 Subject: [PATCH 016/412] Use Immer named imports --- packages/toolkit/src/entities/state_adapter.ts | 2 +- packages/toolkit/src/utils.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/toolkit/src/entities/state_adapter.ts b/packages/toolkit/src/entities/state_adapter.ts index fbc1577683..220abae40a 100644 --- a/packages/toolkit/src/entities/state_adapter.ts +++ b/packages/toolkit/src/entities/state_adapter.ts @@ -1,4 +1,4 @@ -import createNextState, { isDraft } from 'immer' +import { produce as createNextState, isDraft } from 'immer' import type { EntityState, PreventAny } from './models' import type { PayloadAction } from '../createAction' import { isFSA } from '../createAction' diff --git a/packages/toolkit/src/utils.ts b/packages/toolkit/src/utils.ts index 40957c2788..9697d31bb6 100644 --- a/packages/toolkit/src/utils.ts +++ b/packages/toolkit/src/utils.ts @@ -1,4 +1,4 @@ -import createNextState, { isDraftable } from 'immer' +import { produce as createNextState, isDraftable } from 'immer' import type { Middleware } from 'redux' export function getTimeMeasureUtils(maxDelay: number, fnName: string) { From 9b1f185aa772ff28cbe960cac6d81e0713acdcf2 Mon Sep 17 00:00:00 2001 From: Mark Erikson Date: Mon, 16 Jan 2023 19:35:14 -0500 Subject: [PATCH 017/412] Bump redux-thunk dep to 3.0-alpha to fix ESM import issues --- packages/toolkit/package.json | 2 +- yarn.lock | 11 ++++++++++- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/packages/toolkit/package.json b/packages/toolkit/package.json index b6d207a212..003ebf295c 100644 --- a/packages/toolkit/package.json +++ b/packages/toolkit/package.json @@ -119,7 +119,7 @@ "dependencies": { "immer": "^9.0.16", "redux": "^4.2.0", - "redux-thunk": "^2.4.2", + "redux-thunk": "3.0.0-alpha.1", "reselect": "^4.1.7" }, "peerDependencies": { diff --git a/yarn.lock b/yarn.lock index 816ef37411..68b2f0be9f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6596,7 +6596,7 @@ __metadata: prettier: ^2.2.1 query-string: ^7.0.1 redux: ^4.2.0 - redux-thunk: ^2.4.2 + redux-thunk: 3.0.0-alpha.1 reselect: ^4.1.7 rimraf: ^3.0.2 rollup: ^2.47.0 @@ -23551,6 +23551,15 @@ fsevents@^1.2.7: languageName: node linkType: hard +"redux-thunk@npm:3.0.0-alpha.1": + version: 3.0.0-alpha.1 + resolution: "redux-thunk@npm:3.0.0-alpha.1" + peerDependencies: + redux: ^4 + checksum: 280e0d399c96d071030f6dfa5bdb0b05b9f228dd0b840ffbb213e778b773efc25e6ba24f7ed13818696ea54a359ecc06a9e5e718e07d960a772101b14c0dd8c7 + languageName: node + linkType: hard + "redux-thunk@npm:^2.4.1": version: 2.4.1 resolution: "redux-thunk@npm:2.4.1" From 6f9591e17df415b07abaaad32f12f6bf444eb370 Mon Sep 17 00:00:00 2001 From: Mark Erikson Date: Tue, 17 Jan 2023 21:27:08 -0500 Subject: [PATCH 018/412] Use thunk and Immer named imports --- packages/toolkit/src/configureStore.ts | 4 +- packages/toolkit/src/createReducer.ts | 2 +- packages/toolkit/src/getDefaultMiddleware.ts | 6 +-- .../src/tests/configureStore.typetest.ts | 42 ++++++++++++------- .../src/tests/getDefaultMiddleware.test.ts | 18 ++++++-- 5 files changed, 46 insertions(+), 26 deletions(-) diff --git a/packages/toolkit/src/configureStore.ts b/packages/toolkit/src/configureStore.ts index aae2307c9d..76f5a0519c 100644 --- a/packages/toolkit/src/configureStore.ts +++ b/packages/toolkit/src/configureStore.ts @@ -34,7 +34,7 @@ const IS_PRODUCTION = process.env.NODE_ENV === 'production' * @public */ export type ConfigureEnhancersCallback = ( - defaultEnhancers: readonly StoreEnhancer[] + defaultEnhancers: readonly StoreEnhancer[] ) => [...E] /** @@ -107,7 +107,7 @@ type Enhancers = ReadonlyArray export interface ToolkitStore< S = any, A extends Action = AnyAction, - M extends Middlewares = Middlewares, + M extends Middlewares = Middlewares > extends Store { /** * The `dispatch` method of your store, enhanced by all its middlewares. diff --git a/packages/toolkit/src/createReducer.ts b/packages/toolkit/src/createReducer.ts index dd749f398e..e22b0a007a 100644 --- a/packages/toolkit/src/createReducer.ts +++ b/packages/toolkit/src/createReducer.ts @@ -1,5 +1,5 @@ import type { Draft } from 'immer' -import createNextState, { isDraft, isDraftable } from 'immer' +import { produce as createNextState, isDraft, isDraftable } from 'immer' import type { AnyAction, Action, Reducer } from 'redux' import type { ActionReducerMapBuilder } from './mapBuilders' import { executeReducerBuilderCallback } from './mapBuilders' diff --git a/packages/toolkit/src/getDefaultMiddleware.ts b/packages/toolkit/src/getDefaultMiddleware.ts index e54a667680..655b0dc931 100644 --- a/packages/toolkit/src/getDefaultMiddleware.ts +++ b/packages/toolkit/src/getDefaultMiddleware.ts @@ -1,6 +1,6 @@ import type { Middleware, AnyAction } from 'redux' import type { ThunkMiddleware } from 'redux-thunk' -import thunkMiddleware from 'redux-thunk' +import { thunk as thunkMiddleware, withExtraArgument } from 'redux-thunk' import type { ImmutableStateInvariantMiddlewareOptions } from './immutableStateInvariantMiddleware' /* PROD_START_REMOVE_UMD */ import { createImmutableStateInvariantMiddleware } from './immutableStateInvariantMiddleware' @@ -88,9 +88,7 @@ export function getDefaultMiddleware< if (isBoolean(thunk)) { middlewareArray.push(thunkMiddleware) } else { - middlewareArray.push( - thunkMiddleware.withExtraArgument(thunk.extraArgument) - ) + middlewareArray.push(withExtraArgument(thunk.extraArgument)) } } diff --git a/packages/toolkit/src/tests/configureStore.typetest.ts b/packages/toolkit/src/tests/configureStore.typetest.ts index 1fc5432bd6..76bbfc8ac8 100644 --- a/packages/toolkit/src/tests/configureStore.typetest.ts +++ b/packages/toolkit/src/tests/configureStore.typetest.ts @@ -6,7 +6,7 @@ import type { Reducer, Store, Action, - StoreEnhancer + StoreEnhancer, } from 'redux' import { applyMiddleware } from 'redux' import type { PayloadAction } from '@reduxjs/toolkit' @@ -17,7 +17,7 @@ import { ConfigureStoreOptions, } from '@reduxjs/toolkit' import type { ThunkMiddleware, ThunkAction, ThunkDispatch } from 'redux-thunk' -import thunk from 'redux-thunk' +import { thunk } from 'redux-thunk' import { expectNotAny, expectType } from './helpers' const _anyMiddleware: any = () => () => () => {} @@ -144,10 +144,12 @@ const _anyMiddleware: any = () => () => () => {} { const store = configureStore({ reducer: () => 0, - enhancers: [applyMiddleware(() => next => next)] + enhancers: [applyMiddleware(() => (next) => next)], }) - expectType>(store.dispatch) + expectType>( + store.dispatch + ) } configureStore({ @@ -159,7 +161,7 @@ const _anyMiddleware: any = () => () => () => {} { type SomePropertyStoreEnhancer = StoreEnhancer<{ someProperty: string }> - const somePropertyStoreEnhancer: SomePropertyStoreEnhancer = next => { + const somePropertyStoreEnhancer: SomePropertyStoreEnhancer = (next) => { return (reducer, preloadedState) => { return { ...next(reducer, preloadedState), @@ -168,9 +170,13 @@ const _anyMiddleware: any = () => () => () => {} } } - type AnotherPropertyStoreEnhancer = StoreEnhancer<{ anotherProperty: number }> + type AnotherPropertyStoreEnhancer = StoreEnhancer<{ + anotherProperty: number + }> - const anotherPropertyStoreEnhancer: AnotherPropertyStoreEnhancer = next => { + const anotherPropertyStoreEnhancer: AnotherPropertyStoreEnhancer = ( + next + ) => { return (reducer, preloadedState) => { return { ...next(reducer, preloadedState), @@ -184,7 +190,9 @@ const _anyMiddleware: any = () => () => () => {} enhancers: [somePropertyStoreEnhancer, anotherPropertyStoreEnhancer], }) - expectType>(store.dispatch) + expectType>( + store.dispatch + ) expectType(store.someProperty) expectType(store.anotherProperty) } @@ -348,7 +356,9 @@ const _anyMiddleware: any = () => () => () => {} { const store = configureStore({ reducer: reducerA, - middleware: [] as any as readonly [Middleware<(a: StateA) => boolean, StateA>], + middleware: [] as any as readonly [ + Middleware<(a: StateA) => boolean, StateA> + ], }) const result: boolean = store.dispatch(5) // @ts-expect-error @@ -532,21 +542,23 @@ const _anyMiddleware: any = () => () => () => {} initialState: null as any, reducers: { set(state) { - return state; + return state }, }, - }); + }) - function configureMyStore(options: Omit, 'reducer'>) { + function configureMyStore( + options: Omit, 'reducer'> + ) { return configureStore({ ...options, reducer: someSlice.reducer, - }); + }) } - const store = configureMyStore({}); + const store = configureMyStore({}) - expectType(store.dispatch); + expectType(store.dispatch) } { diff --git a/packages/toolkit/src/tests/getDefaultMiddleware.test.ts b/packages/toolkit/src/tests/getDefaultMiddleware.test.ts index 2367ae4040..c47e3905cc 100644 --- a/packages/toolkit/src/tests/getDefaultMiddleware.test.ts +++ b/packages/toolkit/src/tests/getDefaultMiddleware.test.ts @@ -11,7 +11,7 @@ import { MiddlewareArray, configureStore, } from '@reduxjs/toolkit' -import thunk from 'redux-thunk' +import { thunk } from 'redux-thunk' import type { ThunkMiddleware } from 'redux-thunk' import { expectType } from './helpers' @@ -23,10 +23,20 @@ describe('getDefaultMiddleware', () => { process.env.NODE_ENV = ORIGINAL_NODE_ENV }) - it('returns an array with only redux-thunk in production', () => { - process.env.NODE_ENV = 'production' + describe('Production behavior', () => { + beforeEach(() => { + jest.resetModules() + }) + + it('returns an array with only redux-thunk in production', () => { + process.env.NODE_ENV = 'production' + const { thunk } = require('redux-thunk') + const { getDefaultMiddleware } = require('@reduxjs/toolkit') - expect(getDefaultMiddleware()).toEqual([thunk]) // @remap-prod-remove-line + const middleware = getDefaultMiddleware() + expect(middleware).toContain(thunk) + expect(middleware.length).toBe(1) + }) }) it('returns an array with additional middleware in development', () => { From b13958a0e38363e6f811e40d7429a9085601a21a Mon Sep 17 00:00:00 2001 From: Mark Erikson Date: Wed, 18 Jan 2023 21:07:23 -0500 Subject: [PATCH 019/412] Replace Jest and deps with Vitest --- package.json | 5 +- packages/toolkit/package.json | 8 +- yarn.lock | 1182 +++++++++++++++++++++++++-------- 3 files changed, 901 insertions(+), 294 deletions(-) diff --git a/package.json b/package.json index 8996f8c20a..f10cbc2265 100644 --- a/package.json +++ b/package.json @@ -43,8 +43,8 @@ "@babel/helper-compilation-targets": "7.19.3", "@babel/traverse": "7.19.3", "@babel/types": "7.19.3", - "console-testing-library": "patch:console-testing-library@npm:0.3.1#.yarn/patches/console-testing-library__npm_0.3.1.patch", "esbuild": "0.17.0", + "jest-snapshot": "29.3.1", "msw": "patch:msw@npm:0.40.2#.yarn/patches/msw-npm-0.40.2-2107d48752", "jscodeshift": "0.13.1", "react-redux": "npm:8.0.2", @@ -63,7 +63,8 @@ "docs/react-dom": "npm:17.0.2", "docs/@types/react-dom": "npm:17.0.11", "docs/@types/react": "npm:17.0.11", - "type-fest": "2.19.0" + "type-fest": "2.19.0", + "console-testing-library@0.6.1": "patch:console-testing-library@npm%3A0.6.1#./.yarn/patches/console-testing-library-npm-0.6.1-4d9957d402.patch" }, "scripts": { "build": "yarn build:packages", diff --git a/packages/toolkit/package.json b/packages/toolkit/package.json index 003ebf295c..34997ef3ce 100644 --- a/packages/toolkit/package.json +++ b/packages/toolkit/package.json @@ -61,7 +61,7 @@ "@typescript-eslint/eslint-plugin": "^4.22.0", "@typescript-eslint/parser": "^4.22.0", "axios": "^0.19.2", - "console-testing-library": "^0.3.1", + "console-testing-library": "0.6.1", "convert-source-map": "^1.7.0", "esbuild": "~0.17", "eslint": "^7.25.0", @@ -75,7 +75,7 @@ "eslint-plugin-react-hooks": "^4.2.0", "fs-extra": "^9.1.0", "invariant": "^2.2.4", - "jest": "^27", + "jsdom": "^21.0.0", "json-stringify-safe": "^5.0.1", "magic-string": "^0.25.7", "merge-source-map": "^1.1.0", @@ -89,10 +89,10 @@ "size-limit": "^4.11.0", "source-map": "^0.7.3", "terser": "^5.6.1", - "ts-jest": "^27", "tslib": "^1.10.0", "tsx": "^3.12.2", "typescript": "~4.9", + "vitest": "^0.27.2", "yargs": "^15.3.1" }, "scripts": { @@ -104,7 +104,7 @@ "format": "prettier --write \"(src|examples)/**/*.{ts,tsx}\" \"**/*.md\"", "format:check": "prettier --list-different \"(src|examples)/**/*.{ts,tsx}\" \"docs/*/**.md\"", "lint": "eslint src examples", - "test": "jest --runInBand", + "test": "vitest", "type-tests": "yarn tsc -p src/tests/tsconfig.typetests.json && yarn tsc -p src/query/tests/tsconfig.typetests.json", "prepack": "npm run build-prepare" }, diff --git a/yarn.lock b/yarn.lock index 68b2f0be9f..f4eca4c287 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1282,7 +1282,7 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-syntax-jsx@npm:^7.18.6": +"@babel/plugin-syntax-jsx@npm:^7.18.6, @babel/plugin-syntax-jsx@npm:^7.7.2": version: 7.18.6 resolution: "@babel/plugin-syntax-jsx@npm:7.18.6" dependencies: @@ -5595,17 +5595,6 @@ __metadata: languageName: node linkType: hard -"@jest/console@npm:^24.9.0": - version: 24.9.0 - resolution: "@jest/console@npm:24.9.0" - dependencies: - "@jest/source-map": ^24.9.0 - chalk: ^2.0.1 - slash: ^2.0.0 - checksum: ee6468c4aeeb8752126e92e20b0ffbf32abda731e9b7865b63b60bd569c3536e9c901efcec4d81c506a7c6fea2a970ace8262190961aba31dedbfeaa3459d78b - languageName: node - linkType: hard - "@jest/console@npm:^27.5.1": version: 27.5.1 resolution: "@jest/console@npm:27.5.1" @@ -5687,6 +5676,15 @@ __metadata: languageName: node linkType: hard +"@jest/expect-utils@npm:^29.3.1": + version: 29.3.1 + resolution: "@jest/expect-utils@npm:29.3.1" + dependencies: + jest-get-type: ^29.2.0 + checksum: 7f3b853eb1e4299988f66b9aa49c1aacb7b8da1cf5518dca4ccd966e865947eed8f1bde6c8f5207d8400e9af870112a44b57aa83515ad6ea5e4a04a971863adb + languageName: node + linkType: hard + "@jest/fake-timers@npm:^27.5.1": version: 27.5.1 resolution: "@jest/fake-timers@npm:27.5.1" @@ -5759,14 +5757,12 @@ __metadata: languageName: node linkType: hard -"@jest/source-map@npm:^24.9.0": - version: 24.9.0 - resolution: "@jest/source-map@npm:24.9.0" +"@jest/schemas@npm:^29.0.0": + version: 29.0.0 + resolution: "@jest/schemas@npm:29.0.0" dependencies: - callsites: ^3.0.0 - graceful-fs: ^4.1.15 - source-map: ^0.6.0 - checksum: 00479faf6854d5d183b94465db1a0876980ced72bf26cb6a2fe8c04977dc2692e6529faa6b64269492d1d9cab51feebaac9d453d1e6bb1306fc15777143b72af + "@sinclair/typebox": ^0.24.1 + checksum: 41355c78f09eb1097e57a3c5d0ca11c9099e235e01ea5fa4e3953562a79a6a9296c1d300f1ba50ca75236048829e056b00685cd2f1ff8285e56fd2ce01249acb languageName: node linkType: hard @@ -5781,17 +5777,6 @@ __metadata: languageName: node linkType: hard -"@jest/test-result@npm:^24.9.0": - version: 24.9.0 - resolution: "@jest/test-result@npm:24.9.0" - dependencies: - "@jest/console": ^24.9.0 - "@jest/types": ^24.9.0 - "@types/istanbul-lib-coverage": ^2.0.0 - checksum: 7145c7baa289798881160b3cfa5b2466b2636238a52b77cf46e5468ffe2881fb8fb8d4966155a8d508b26a8d29a302a9eb9037de1a371e5dc9bb6e94837c0ae7 - languageName: node - linkType: hard - "@jest/test-result@npm:^27.5.1": version: 27.5.1 resolution: "@jest/test-result@npm:27.5.1" @@ -5874,14 +5859,26 @@ __metadata: languageName: node linkType: hard -"@jest/types@npm:^24.9.0": - version: 24.9.0 - resolution: "@jest/types@npm:24.9.0" +"@jest/transform@npm:^29.3.1": + version: 29.3.1 + resolution: "@jest/transform@npm:29.3.1" dependencies: - "@types/istanbul-lib-coverage": ^2.0.0 - "@types/istanbul-reports": ^1.1.1 - "@types/yargs": ^13.0.0 - checksum: 603698f774cf22f9d16a0e0fac9e10e7db21052aebfa33db154c8a5940e0eb1fa9c079a8c91681041ad3aeee2adfa950608dd0c663130316ba034b8bca7b301c + "@babel/core": ^7.11.6 + "@jest/types": ^29.3.1 + "@jridgewell/trace-mapping": ^0.3.15 + babel-plugin-istanbul: ^6.1.1 + chalk: ^4.0.0 + convert-source-map: ^2.0.0 + fast-json-stable-stringify: ^2.1.0 + graceful-fs: ^4.2.9 + jest-haste-map: ^29.3.1 + jest-regex-util: ^29.2.0 + jest-util: ^29.3.1 + micromatch: ^4.0.4 + pirates: ^4.0.4 + slash: ^3.0.0 + write-file-atomic: ^4.0.1 + checksum: 673df5900ffc95bc811084e09d6e47948034dea6ab6cc4f81f80977e3a52468a6c2284d0ba9796daf25a62ae50d12f7e97fc9a3a0c587f11f2a479ff5493ca53 languageName: node linkType: hard @@ -5925,6 +5922,20 @@ __metadata: languageName: node linkType: hard +"@jest/types@npm:^29.3.1": + version: 29.3.1 + resolution: "@jest/types@npm:29.3.1" + dependencies: + "@jest/schemas": ^29.0.0 + "@types/istanbul-lib-coverage": ^2.0.0 + "@types/istanbul-reports": ^3.0.0 + "@types/node": "*" + "@types/yargs": ^17.0.8 + chalk: ^4.0.0 + checksum: 6f9faf27507b845ff3839c1adc6dbd038d7046d03d37e84c9fc956f60718711a801a5094c7eeee6b39ccf42c0ab61347fdc0fa49ab493ae5a8efd2fd41228ee8 + languageName: node + linkType: hard + "@jridgewell/gen-mapping@npm:^0.1.0": version: 0.1.1 resolution: "@jridgewell/gen-mapping@npm:0.1.1" @@ -5991,7 +6002,7 @@ __metadata: languageName: node linkType: hard -"@jridgewell/trace-mapping@npm:^0.3.14": +"@jridgewell/trace-mapping@npm:^0.3.14, @jridgewell/trace-mapping@npm:^0.3.15": version: 0.3.17 resolution: "@jridgewell/trace-mapping@npm:0.3.17" dependencies: @@ -6572,7 +6583,7 @@ __metadata: "@typescript-eslint/eslint-plugin": ^4.22.0 "@typescript-eslint/parser": ^4.22.0 axios: ^0.19.2 - console-testing-library: ^0.3.1 + console-testing-library: 0.6.1 convert-source-map: ^1.7.0 esbuild: ~0.17 eslint: ^7.25.0 @@ -6587,7 +6598,7 @@ __metadata: fs-extra: ^9.1.0 immer: ^9.0.16 invariant: ^2.2.4 - jest: ^27 + jsdom: ^21.0.0 json-stringify-safe: ^5.0.1 magic-string: ^0.25.7 merge-source-map: ^1.1.0 @@ -6604,10 +6615,10 @@ __metadata: size-limit: ^4.11.0 source-map: ^0.7.3 terser: ^5.6.1 - ts-jest: ^27 tslib: ^1.10.0 tsx: ^3.12.2 typescript: ~4.9 + vitest: ^0.27.2 yargs: ^15.3.1 peerDependencies: react: ^16.9.0 || ^17.0.0 || ^18 @@ -6885,6 +6896,13 @@ __metadata: languageName: node linkType: hard +"@sinclair/typebox@npm:^0.24.1": + version: 0.24.51 + resolution: "@sinclair/typebox@npm:0.24.51" + checksum: fd0d855e748ef767eb19da1a60ed0ab928e91e0f358c1dd198d600762c0015440b15755e96d1176e2a0db7e09c6a64ed487828ee10dd0c3e22f61eb09c478cd0 + languageName: node + linkType: hard + "@sindresorhus/is@npm:^0.14.0": version: 0.14.0 resolution: "@sindresorhus/is@npm:0.14.0" @@ -7361,6 +7379,13 @@ __metadata: languageName: node linkType: hard +"@tootallnate/once@npm:2": + version: 2.0.0 + resolution: "@tootallnate/once@npm:2.0.0" + checksum: ad87447820dd3f24825d2d947ebc03072b20a42bfc96cbafec16bff8bbda6c1a81fcb0be56d5b21968560c5359a0af4038a68ba150c3e1694fe4c109a063bed8 + languageName: node + linkType: hard + "@trysound/sax@npm:0.2.0": version: 0.2.0 resolution: "@trysound/sax@npm:0.2.0" @@ -7442,7 +7467,7 @@ __metadata: languageName: node linkType: hard -"@types/babel__traverse@npm:*, @types/babel__traverse@npm:^7.0.4, @types/babel__traverse@npm:^7.0.6": +"@types/babel__traverse@npm:*, @types/babel__traverse@npm:^7.0.6": version: 7.11.1 resolution: "@types/babel__traverse@npm:7.11.1" dependencies: @@ -7482,6 +7507,22 @@ __metadata: languageName: node linkType: hard +"@types/chai-subset@npm:^1.3.3": + version: 1.3.3 + resolution: "@types/chai-subset@npm:1.3.3" + dependencies: + "@types/chai": "*" + checksum: 4481da7345022995f5a105e6683744f7203d2c3d19cfe88d8e17274d045722948abf55e0adfd97709e0f043dade37a4d4e98cd4c660e2e8a14f23e6ecf79418f + languageName: node + linkType: hard + +"@types/chai@npm:*, @types/chai@npm:^4.3.4": + version: 4.3.4 + resolution: "@types/chai@npm:4.3.4" + checksum: 571184967beb03bf64c4392a13a7d44e72da9af5a1e83077ff81c39cf59c0fda2a5c78d2005084601cf8f3d11726608574d8b5b4a0e3e9736792807afd926cd0 + languageName: node + linkType: hard + "@types/commander@npm:^2.12.2": version: 2.12.2 resolution: "@types/commander@npm:2.12.2" @@ -7604,6 +7645,15 @@ __metadata: languageName: node linkType: hard +"@types/graceful-fs@npm:^4.1.3": + version: 4.1.6 + resolution: "@types/graceful-fs@npm:4.1.6" + dependencies: + "@types/node": "*" + checksum: c3070ccdc9ca0f40df747bced1c96c71a61992d6f7c767e8fd24bb6a3c2de26e8b84135ede000b7e79db530a23e7e88dcd9db60eee6395d0f4ce1dae91369dd4 + languageName: node + linkType: hard + "@types/hast@npm:^2.0.0": version: 2.3.1 resolution: "@types/hast@npm:2.3.1" @@ -7678,16 +7728,6 @@ __metadata: languageName: node linkType: hard -"@types/istanbul-reports@npm:^1.1.1": - version: 1.1.2 - resolution: "@types/istanbul-reports@npm:1.1.2" - dependencies: - "@types/istanbul-lib-coverage": "*" - "@types/istanbul-lib-report": "*" - checksum: 00866e815d1e68d0a590d691506937b79d8d65ad8eab5ed34dbfee66136c7c0f4ea65327d32046d5fe469f22abea2b294987591dc66365ebc3991f7e413b2d78 - languageName: node - linkType: hard - "@types/istanbul-reports@npm:^3.0.0": version: 3.0.1 resolution: "@types/istanbul-reports@npm:3.0.1" @@ -8104,13 +8144,6 @@ __metadata: languageName: node linkType: hard -"@types/stack-utils@npm:^1.0.1": - version: 1.0.1 - resolution: "@types/stack-utils@npm:1.0.1" - checksum: 9dc052b575acfeca3f165fb19d87b7b2989d54ed7d64a7eeb0b7587bc5795ef1f2c2b1511a44dcf0831ef35b8ce3486f97fcbfdd50c01f68aa297de31502c9d9 - languageName: node - linkType: hard - "@types/stack-utils@npm:^2.0.0": version: 2.0.0 resolution: "@types/stack-utils@npm:2.0.0" @@ -8201,15 +8234,6 @@ __metadata: languageName: node linkType: hard -"@types/yargs@npm:^13.0.0": - version: 13.0.11 - resolution: "@types/yargs@npm:13.0.11" - dependencies: - "@types/yargs-parser": "*" - checksum: efcbcccd20eab773970c2f103efaf69901924ab3bfc69cc5603ece0be7626937242b2f952b7ebc3708c121f8507e1d0633eb4cc04843433bf3d8b133b83bb811 - languageName: node - linkType: hard - "@types/yargs@npm:^15.0.0": version: 15.0.13 resolution: "@types/yargs@npm:15.0.13" @@ -8825,6 +8849,13 @@ __metadata: languageName: node linkType: hard +"abab@npm:^2.0.6": + version: 2.0.6 + resolution: "abab@npm:2.0.6" + checksum: 6ffc1af4ff315066c62600123990d87551ceb0aafa01e6539da77b0f5987ac7019466780bf480f1787576d4385e3690c81ccc37cfda12819bf510b8ab47e5a3e + languageName: node + linkType: hard + "abbrev@npm:1": version: 1.1.1 resolution: "abbrev@npm:1.1.1" @@ -8861,6 +8892,16 @@ __metadata: languageName: node linkType: hard +"acorn-globals@npm:^7.0.0": + version: 7.0.1 + resolution: "acorn-globals@npm:7.0.1" + dependencies: + acorn: ^8.1.0 + acorn-walk: ^8.0.2 + checksum: 2a2998a547af6d0db5f0cdb90acaa7c3cbca6709010e02121fb8b8617c0fbd8bab0b869579903fde358ac78454356a14fadcc1a672ecb97b04b1c2ccba955ce8 + languageName: node + linkType: hard + "acorn-import-assertions@npm:^1.7.6": version: 1.8.0 resolution: "acorn-import-assertions@npm:1.8.0" @@ -8897,7 +8938,7 @@ __metadata: languageName: node linkType: hard -"acorn-walk@npm:^8.0.0, acorn-walk@npm:^8.1.1": +"acorn-walk@npm:^8.0.0, acorn-walk@npm:^8.0.2, acorn-walk@npm:^8.1.1, acorn-walk@npm:^8.2.0": version: 8.2.0 resolution: "acorn-walk@npm:8.2.0" checksum: 1715e76c01dd7b2d4ca472f9c58968516a4899378a63ad5b6c2d668bba8da21a71976c14ec5f5b75f887b6317c4ae0b897ab141c831d741dc76024d8745f1ad1 @@ -8931,6 +8972,15 @@ __metadata: languageName: node linkType: hard +"acorn@npm:^8.1.0, acorn@npm:^8.8.1": + version: 8.8.1 + resolution: "acorn@npm:8.8.1" + bin: + acorn: bin/acorn + checksum: 4079b67283b94935157698831967642f24a075c52ce3feaaaafe095776dfbe15d86a1b33b1e53860fc0d062ed6c83f4284a5c87c85b9ad51853a01173da6097f + languageName: node + linkType: hard + "address@npm:^1.0.1, address@npm:^1.1.2": version: 1.2.0 resolution: "address@npm:1.2.0" @@ -9152,13 +9202,6 @@ __metadata: languageName: node linkType: hard -"ansi-regex@npm:^4.0.0": - version: 4.1.0 - resolution: "ansi-regex@npm:4.1.0" - checksum: 97aa4659538d53e5e441f5ef2949a3cffcb838e57aeaad42c4194e9d7ddb37246a6526c4ca85d3940a9d1e19b11cc2e114530b54c9d700c8baf163c31779baf8 - languageName: node - linkType: hard - "ansi-regex@npm:^5.0.0, ansi-regex@npm:^5.0.1": version: 5.0.1 resolution: "ansi-regex@npm:5.0.1" @@ -9180,7 +9223,7 @@ __metadata: languageName: node linkType: hard -"ansi-styles@npm:^3.2.0, ansi-styles@npm:^3.2.1": +"ansi-styles@npm:^3.2.1": version: 3.2.1 resolution: "ansi-styles@npm:3.2.1" dependencies: @@ -9441,6 +9484,13 @@ __metadata: languageName: node linkType: hard +"assertion-error@npm:^1.1.0": + version: 1.1.0 + resolution: "assertion-error@npm:1.1.0" + checksum: fd9429d3a3d4fd61782eb3962ae76b6d08aa7383123fca0596020013b3ebd6647891a85b05ce821c47d1471ed1271f00b0545cf6a4326cf2fc91efcc3b0fbecf + languageName: node + linkType: hard + "assign-symbols@npm:^1.0.0": version: 1.0.0 resolution: "assign-symbols@npm:1.0.0" @@ -10265,15 +10315,6 @@ __metadata: languageName: node linkType: hard -"browser-resolve@npm:^1.11.3": - version: 1.11.3 - resolution: "browser-resolve@npm:1.11.3" - dependencies: - resolve: 1.1.7 - checksum: 431bfc1a17406362a3010a2c35503eb7d1253dbcb8081c1ce236ddb0b954a33d52dcaf0b07f64c0f20394d6eeec1be4f6551da3734ce9ed5dcc38e876c96d5d5 - languageName: node - linkType: hard - "browserify-aes@npm:^1.0.0, browserify-aes@npm:^1.0.4": version: 1.2.0 resolution: "browserify-aes@npm:1.2.0" @@ -10456,6 +10497,13 @@ __metadata: languageName: node linkType: hard +"cac@npm:^6.7.14": + version: 6.7.14 + resolution: "cac@npm:6.7.14" + checksum: 45a2496a9443abbe7f52a49b22fbe51b1905eff46e03fd5e6c98e3f85077be3f8949685a1849b1a9cd2bc3e5567dfebcf64f01ce01847baf918f1b37c839791a + languageName: node + linkType: hard + "cacache@npm:^12.0.2": version: 12.0.4 resolution: "cacache@npm:12.0.4" @@ -10691,6 +10739,21 @@ __metadata: languageName: node linkType: hard +"chai@npm:^4.3.7": + version: 4.3.7 + resolution: "chai@npm:4.3.7" + dependencies: + assertion-error: ^1.1.0 + check-error: ^1.0.2 + deep-eql: ^4.1.2 + get-func-name: ^2.0.0 + loupe: ^2.3.1 + pathval: ^1.1.1 + type-detect: ^4.0.5 + checksum: 0bba7d267848015246a66995f044ce3f0ebc35e530da3cbdf171db744e14cbe301ab913a8d07caf7952b430257ccbb1a4a983c570a7c5748dc537897e5131f7c + languageName: node + linkType: hard + "chalk@npm:4.1.1": version: 4.1.1 resolution: "chalk@npm:4.1.1" @@ -10724,7 +10787,7 @@ __metadata: languageName: node linkType: hard -"chalk@npm:^2.0.0, chalk@npm:^2.0.1, chalk@npm:^2.4.1": +"chalk@npm:^2.0.0, chalk@npm:^2.4.1": version: 2.4.2 resolution: "chalk@npm:2.4.2" dependencies: @@ -10839,6 +10902,13 @@ __metadata: languageName: node linkType: hard +"check-error@npm:^1.0.2": + version: 1.0.2 + resolution: "check-error@npm:1.0.2" + checksum: d9d106504404b8addd1ee3f63f8c0eaa7cd962a1a28eb9c519b1c4a1dc7098be38007fc0060f045ee00f075fbb7a2a4f42abcf61d68323677e11ab98dc16042e + languageName: node + linkType: hard + "check-types@npm:^11.1.1": version: 11.1.2 resolution: "check-types@npm:11.1.2" @@ -11530,23 +11600,25 @@ __metadata: languageName: node linkType: hard -"console-testing-library@npm:0.3.1": - version: 0.3.1 - resolution: "console-testing-library@npm:0.3.1" +"console-testing-library@npm:0.6.1": + version: 0.6.1 + resolution: "console-testing-library@npm:0.6.1" dependencies: - jest-snapshot: ^24.9.0 - pretty-format: ^24.9.0 - checksum: 94438ceb34b035764c62df76effc54a84a8a4f6368c9285d40d379eb90d30f442a24ff8606a33dc02570555974b8102aa53444b382d6403522f8e74a8ff95f8d + jest-snapshot: ^26.0.0 + pretty-format: ^26.0.0 + strip-ansi: ^6.0.0 + checksum: c02251eb42cdf4873c6dfc75fe9a78e7b8ed6450dda08b60d50bde4e98307adbd0ec37b8664ae40a6a40a5761043fb55b2a7516de4fb84aa0c695f086f28eb5a languageName: node linkType: hard -"console-testing-library@patch:console-testing-library@npm:0.3.1#.yarn/patches/console-testing-library__npm_0.3.1.patch::locator=rtk-monorepo%40workspace%3A.": - version: 0.3.1 - resolution: "console-testing-library@patch:console-testing-library@npm%3A0.3.1#.yarn/patches/console-testing-library__npm_0.3.1.patch::version=0.3.1&hash=790984&locator=rtk-monorepo%40workspace%3A." +"console-testing-library@patch:console-testing-library@npm%3A0.6.1#./.yarn/patches/console-testing-library-npm-0.6.1-4d9957d402.patch::locator=rtk-monorepo%40workspace%3A.": + version: 0.6.1 + resolution: "console-testing-library@patch:console-testing-library@npm%3A0.6.1#./.yarn/patches/console-testing-library-npm-0.6.1-4d9957d402.patch::version=0.6.1&hash=6ec7bd&locator=rtk-monorepo%40workspace%3A." dependencies: - jest-snapshot: ^24.9.0 - pretty-format: ^24.9.0 - checksum: 8cf742ea934f40d9c6a71f2c975fb42a93fc24ffaf9b78d98d9cfd420f705bb2e31738d07273d5ce1d099255a6314df3a247a9a8eb6e228d586f8743cdb2565e + jest-snapshot: ^26.0.0 + pretty-format: ^26.0.0 + strip-ansi: ^6.0.0 + checksum: 682a0d6ca96a90285510a9c8fc667b541d8ef628070534516beac959176554e32796e86eaf964e26612edc8b10a869f53d44d283d8250f54b5465be847cc4a23 languageName: node linkType: hard @@ -11600,6 +11672,13 @@ __metadata: languageName: node linkType: hard +"convert-source-map@npm:^2.0.0": + version: 2.0.0 + resolution: "convert-source-map@npm:2.0.0" + checksum: 63ae9933be5a2b8d4509daca5124e20c14d023c820258e484e32dc324d34c2754e71297c94a05784064ad27615037ef677e3f0c00469fb55f409d2bb21261035 + languageName: node + linkType: hard + "cookie-signature@npm:1.0.6": version: 1.0.6 resolution: "cookie-signature@npm:1.0.6" @@ -12392,6 +12471,13 @@ __metadata: languageName: node linkType: hard +"cssom@npm:^0.5.0": + version: 0.5.0 + resolution: "cssom@npm:0.5.0" + checksum: 823471aa30091c59e0a305927c30e7768939b6af70405808f8d2ce1ca778cddcb24722717392438329d1691f9a87cb0183b64b8d779b56a961546d54854fde01 + languageName: node + linkType: hard + "cssom@npm:~0.3.6": version: 0.3.8 resolution: "cssom@npm:0.3.8" @@ -12447,6 +12533,17 @@ __metadata: languageName: node linkType: hard +"data-urls@npm:^3.0.2": + version: 3.0.2 + resolution: "data-urls@npm:3.0.2" + dependencies: + abab: ^2.0.6 + whatwg-mimetype: ^3.0.0 + whatwg-url: ^11.0.0 + checksum: 033fc3dd0fba6d24bc9a024ddcf9923691dd24f90a3d26f6545d6a2f71ec6956f93462f2cdf2183cc46f10dc01ed3bcb36731a8208456eb1a08147e571fe2a76 + languageName: node + linkType: hard + "dataloader@npm:2.0.0": version: 2.0.0 resolution: "dataloader@npm:2.0.0" @@ -12540,6 +12637,13 @@ __metadata: languageName: node linkType: hard +"decimal.js@npm:^10.4.2": + version: 10.4.3 + resolution: "decimal.js@npm:10.4.3" + checksum: 796404dcfa9d1dbfdc48870229d57f788b48c21c603c3f6554a1c17c10195fc1024de338b0cf9e1efe0c7c167eeb18f04548979bcc5fdfabebb7cc0ae3287bae + languageName: node + linkType: hard + "decode-uri-component@npm:^0.2.0": version: 0.2.0 resolution: "decode-uri-component@npm:0.2.0" @@ -12572,6 +12676,15 @@ __metadata: languageName: node linkType: hard +"deep-eql@npm:^4.1.2": + version: 4.1.3 + resolution: "deep-eql@npm:4.1.3" + dependencies: + type-detect: ^4.0.0 + checksum: 7f6d30cb41c713973dc07eaadded848b2ab0b835e518a88b91bea72f34e08c4c71d167a722a6f302d3a6108f05afd8e6d7650689a84d5d29ec7fe6220420397f + languageName: node + linkType: hard + "deep-extend@npm:^0.6.0": version: 0.6.0 resolution: "deep-extend@npm:0.6.0" @@ -12849,13 +12962,6 @@ __metadata: languageName: node linkType: hard -"diff-sequences@npm:^24.9.0": - version: 24.9.0 - resolution: "diff-sequences@npm:24.9.0" - checksum: b81f906ff1737e0a65e8f7ee3ad1d27b426dcc25498731365aeaccc32333da3bf3a7100c963c7104f12c8e64e545114d4fe4c0b90daf2565b0b00b79f0df45c4 - languageName: node - linkType: hard - "diff-sequences@npm:^26.6.2": version: 26.6.2 resolution: "diff-sequences@npm:26.6.2" @@ -12870,6 +12976,13 @@ __metadata: languageName: node linkType: hard +"diff-sequences@npm:^29.3.1": + version: 29.3.1 + resolution: "diff-sequences@npm:29.3.1" + checksum: 8edab8c383355022e470779a099852d595dd856f9f5bd7af24f177e74138a668932268b4c4fd54096eed643861575c3652d4ecbbb1a9d710488286aed3ffa443 + languageName: node + linkType: hard + "diff@npm:^4.0.1": version: 4.0.2 resolution: "diff@npm:4.0.2" @@ -13036,6 +13149,15 @@ __metadata: languageName: node linkType: hard +"domexception@npm:^4.0.0": + version: 4.0.0 + resolution: "domexception@npm:4.0.0" + dependencies: + webidl-conversions: ^7.0.0 + checksum: ddbc1268edf33a8ba02ccc596735ede80375ee0cf124b30d2f05df5b464ba78ef4f49889b6391df4a04954e63d42d5631c7fcf8b1c4f12bc531252977a5f13d5 + languageName: node + linkType: hard + "domhandler@npm:^4.0.0, domhandler@npm:^4.2.0": version: 4.2.0 resolution: "domhandler@npm:4.2.0" @@ -13371,6 +13493,13 @@ __metadata: languageName: node linkType: hard +"entities@npm:^4.4.0": + version: 4.4.0 + resolution: "entities@npm:4.4.0" + checksum: 84d250329f4b56b40fa93ed067b194db21e8815e4eb9b59f43a086f0ecd342814f6bc483de8a77da5d64e0f626033192b1b4f1792232a7ea6b970ebe0f3187c2 + languageName: node + linkType: hard + "env-paths@npm:^2.2.0": version: 2.2.1 resolution: "env-paths@npm:2.2.1" @@ -14275,20 +14404,6 @@ __metadata: languageName: node linkType: hard -"expect@npm:^24.9.0": - version: 24.9.0 - resolution: "expect@npm:24.9.0" - dependencies: - "@jest/types": ^24.9.0 - ansi-styles: ^3.2.0 - jest-get-type: ^24.9.0 - jest-matcher-utils: ^24.9.0 - jest-message-util: ^24.9.0 - jest-regex-util: ^24.9.0 - checksum: bfce2243543dd10e3c2047bbe6fc99b7b150cea71b198ddd8feb2e7ebfef1a3dd46ec7519e05d23a20b30c242b13dad97551368a690731d9a591f6f863528cee - languageName: node - linkType: hard - "expect@npm:^27.5.1": version: 27.5.1 resolution: "expect@npm:27.5.1" @@ -14301,6 +14416,19 @@ __metadata: languageName: node linkType: hard +"expect@npm:^29.3.1": + version: 29.3.1 + resolution: "expect@npm:29.3.1" + dependencies: + "@jest/expect-utils": ^29.3.1 + jest-get-type: ^29.2.0 + jest-matcher-utils: ^29.3.1 + jest-message-util: ^29.3.1 + jest-util: ^29.3.1 + checksum: e9588c2a430b558b9a3dc72d4ad05f36b047cb477bc6a7bb9cfeef7614fe7e5edbab424c2c0ce82739ee21ecbbbd24596259528209f84cd72500cc612d910d30 + languageName: node + linkType: hard + "express@npm:^4.17.3": version: 4.18.1 resolution: "express@npm:4.18.1" @@ -14808,15 +14936,6 @@ __metadata: languageName: node linkType: hard -"for-each@npm:^0.3.3": - version: 0.3.3 - resolution: "for-each@npm:0.3.3" - dependencies: - is-callable: ^1.1.3 - checksum: 6c48ff2bc63362319c65e2edca4a8e1e3483a2fabc72fbe7feaf8c73db94fc7861bd53bc02c8a66a0c1dd709da6b04eec42e0abdd6b40ce47305ae92a25e5d28 - languageName: node - linkType: hard - "for-in@npm:^1.0.2": version: 1.0.2 resolution: "for-in@npm:1.0.2" @@ -14855,7 +14974,7 @@ __metadata: languageName: node linkType: hard -"form-data@npm:4.0.0": +"form-data@npm:4.0.0, form-data@npm:^4.0.0": version: 4.0.0 resolution: "form-data@npm:4.0.0" dependencies: @@ -15155,6 +15274,13 @@ fsevents@^1.2.7: languageName: node linkType: hard +"get-func-name@npm:^2.0.0": + version: 2.0.0 + resolution: "get-func-name@npm:2.0.0" + checksum: 8d82e69f3e7fab9e27c547945dfe5cc0c57fc0adf08ce135dddb01081d75684a03e7a0487466f478872b341d52ac763ae49e660d01ab83741f74932085f693c3 + languageName: node + linkType: hard + "get-intrinsic@npm:^1.0.2, get-intrinsic@npm:^1.1.0, get-intrinsic@npm:^1.1.1": version: 1.1.1 resolution: "get-intrinsic@npm:1.1.1" @@ -15945,6 +16071,15 @@ fsevents@^1.2.7: languageName: node linkType: hard +"html-encoding-sniffer@npm:^3.0.0": + version: 3.0.0 + resolution: "html-encoding-sniffer@npm:3.0.0" + dependencies: + whatwg-encoding: ^2.0.0 + checksum: 8d806aa00487e279e5ccb573366a951a9f68f65c90298eac9c3a2b440a7ffe46615aff2995a2f61c6746c639234e6179a97e18ca5ccbbf93d3725ef2099a4502 + languageName: node + linkType: hard + "html-entities@npm:^2.1.0, html-entities@npm:^2.3.2": version: 2.3.3 resolution: "html-entities@npm:2.3.3" @@ -16086,6 +16221,17 @@ fsevents@^1.2.7: languageName: node linkType: hard +"http-proxy-agent@npm:^5.0.0": + version: 5.0.0 + resolution: "http-proxy-agent@npm:5.0.0" + dependencies: + "@tootallnate/once": 2 + agent-base: 6 + debug: 4 + checksum: e2ee1ff1656a131953839b2a19cd1f3a52d97c25ba87bd2559af6ae87114abf60971e498021f9b73f9fd78aea8876d1fb0d4656aac8a03c6caa9fc175f22b786 + languageName: node + linkType: hard + "http-proxy-middleware@npm:^2.0.3": version: 2.0.6 resolution: "http-proxy-middleware@npm:2.0.6" @@ -16149,6 +16295,16 @@ fsevents@^1.2.7: languageName: node linkType: hard +"https-proxy-agent@npm:^5.0.1": + version: 5.0.1 + resolution: "https-proxy-agent@npm:5.0.1" + dependencies: + agent-base: 6 + debug: 4 + checksum: 571fccdf38184f05943e12d37d6ce38197becdd69e58d03f43637f7fa1269cf303a7d228aa27e5b27bbd3af8f09fd938e1c91dcfefff2df7ba77c20ed8dfc765 + languageName: node + linkType: hard + "human-signals@npm:^1.1.1": version: 1.1.1 resolution: "human-signals@npm:1.1.1" @@ -16202,7 +16358,7 @@ fsevents@^1.2.7: languageName: node linkType: hard -"iconv-lite@npm:^0.6.2, iconv-lite@npm:^0.6.3": +"iconv-lite@npm:0.6.3, iconv-lite@npm:^0.6.2, iconv-lite@npm:^0.6.3": version: 0.6.3 resolution: "iconv-lite@npm:0.6.3" dependencies: @@ -16704,7 +16860,7 @@ fsevents@^1.2.7: languageName: node linkType: hard -"is-callable@npm:^1.1.3, is-callable@npm:^1.1.4, is-callable@npm:^1.2.4": +"is-callable@npm:^1.1.4, is-callable@npm:^1.2.4": version: 1.2.4 resolution: "is-callable@npm:1.2.4" checksum: 1a28d57dc435797dae04b173b65d6d1e77d4f16276e9eff973f994eadcfdc30a017e6a597f092752a083c1103cceb56c91e3dadc6692fedb9898dfaba701575f @@ -17500,18 +17656,6 @@ fsevents@^1.2.7: languageName: node linkType: hard -"jest-diff@npm:^24.9.0": - version: 24.9.0 - resolution: "jest-diff@npm:24.9.0" - dependencies: - chalk: ^2.0.1 - diff-sequences: ^24.9.0 - jest-get-type: ^24.9.0 - pretty-format: ^24.9.0 - checksum: 462ccb128cb1b64eb285d28245d0c5bfc230cb063624bd117550d6dbc94332f606828a5de86938611d1e6a78489e576c496737ae139084f6049a56b768ad6402 - languageName: node - linkType: hard - "jest-diff@npm:^26.0.0": version: 26.6.2 resolution: "jest-diff@npm:26.6.2" @@ -17536,6 +17680,18 @@ fsevents@^1.2.7: languageName: node linkType: hard +"jest-diff@npm:^29.3.1": + version: 29.3.1 + resolution: "jest-diff@npm:29.3.1" + dependencies: + chalk: ^4.0.0 + diff-sequences: ^29.3.1 + jest-get-type: ^29.2.0 + pretty-format: ^29.3.1 + checksum: ac5c09745f2b1897e6f53216acaf6ed44fc4faed8e8df053ff4ac3db5d2a1d06a17b876e49faaa15c8a7a26f5671bcbed0a93781dcc2835f781c79a716a591a9 + languageName: node + linkType: hard + "jest-docblock@npm:^27.5.1": version: 27.5.1 resolution: "jest-docblock@npm:27.5.1" @@ -17587,13 +17743,6 @@ fsevents@^1.2.7: languageName: node linkType: hard -"jest-get-type@npm:^24.9.0": - version: 24.9.0 - resolution: "jest-get-type@npm:24.9.0" - checksum: 821e6cd46434c917370cd362fbc4ce564c6e22780351f3ca468b230fbbc657ae19905ed5cdcc5e112d81a2c79cbd3fbcbe0dd44dc62860414b60ea223009958c - languageName: node - linkType: hard - "jest-get-type@npm:^26.3.0": version: 26.3.0 resolution: "jest-get-type@npm:26.3.0" @@ -17608,6 +17757,13 @@ fsevents@^1.2.7: languageName: node linkType: hard +"jest-get-type@npm:^29.2.0": + version: 29.2.0 + resolution: "jest-get-type@npm:29.2.0" + checksum: e396fd880a30d08940ed8a8e43cd4595db1b8ff09649018eb358ca701811137556bae82626af73459e3c0f8c5e972ed1e57fd3b1537b13a260893dac60a90942 + languageName: node + linkType: hard + "jest-haste-map@npm:^26.6.2": version: 26.6.2 resolution: "jest-haste-map@npm:26.6.2" @@ -17657,6 +17813,29 @@ fsevents@^1.2.7: languageName: node linkType: hard +"jest-haste-map@npm:^29.3.1": + version: 29.3.1 + resolution: "jest-haste-map@npm:29.3.1" + dependencies: + "@jest/types": ^29.3.1 + "@types/graceful-fs": ^4.1.3 + "@types/node": "*" + anymatch: ^3.0.3 + fb-watchman: ^2.0.0 + fsevents: ^2.3.2 + graceful-fs: ^4.2.9 + jest-regex-util: ^29.2.0 + jest-util: ^29.3.1 + jest-worker: ^29.3.1 + micromatch: ^4.0.4 + walker: ^1.0.8 + dependenciesMeta: + fsevents: + optional: true + checksum: 97ea26af0c28a2ba568c9c65d06211487bbcd501cb4944f9d55e07fd2b00ad96653ea2cc9033f3d5b7dc1feda33e47ae9cc56b400191ea4533be213c9f82e67c + languageName: node + linkType: hard + "jest-jasmine2@npm:^27.5.1": version: 27.5.1 resolution: "jest-jasmine2@npm:27.5.1" @@ -17692,18 +17871,6 @@ fsevents@^1.2.7: languageName: node linkType: hard -"jest-matcher-utils@npm:^24.9.0": - version: 24.9.0 - resolution: "jest-matcher-utils@npm:24.9.0" - dependencies: - chalk: ^2.0.1 - jest-diff: ^24.9.0 - jest-get-type: ^24.9.0 - pretty-format: ^24.9.0 - checksum: e9dcd4c7a0bf52dccb4890de7ac2da3e857af067e71633b730fdc865dd271b8a2c3d68a2761d5ca6060ea4a455be42176f58462006468b8eb7c216921251e2ee - languageName: node - linkType: hard - "jest-matcher-utils@npm:^27.0.0, jest-matcher-utils@npm:^27.5.1": version: 27.5.1 resolution: "jest-matcher-utils@npm:27.5.1" @@ -17716,19 +17883,15 @@ fsevents@^1.2.7: languageName: node linkType: hard -"jest-message-util@npm:^24.9.0": - version: 24.9.0 - resolution: "jest-message-util@npm:24.9.0" +"jest-matcher-utils@npm:^29.3.1": + version: 29.3.1 + resolution: "jest-matcher-utils@npm:29.3.1" dependencies: - "@babel/code-frame": ^7.0.0 - "@jest/test-result": ^24.9.0 - "@jest/types": ^24.9.0 - "@types/stack-utils": ^1.0.1 - chalk: ^2.0.1 - micromatch: ^3.1.10 - slash: ^2.0.0 - stack-utils: ^1.0.1 - checksum: c173117b245090967db4853c28c3452ad2987a10caf28161abbfeb8d96be13f0d9e25422df10162bcc5e46860887e35ec4b4963f85392c4a625e4c37ad242f0b + chalk: ^4.0.0 + jest-diff: ^29.3.1 + jest-get-type: ^29.2.0 + pretty-format: ^29.3.1 + checksum: 311e8d9f1e935216afc7dd8c6acf1fbda67a7415e1afb1bf72757213dfb025c1f2dc5e2c185c08064a35cdc1f2d8e40c57616666774ed1b03e57eb311c20ec77 languageName: node linkType: hard @@ -17766,6 +17929,23 @@ fsevents@^1.2.7: languageName: node linkType: hard +"jest-message-util@npm:^29.3.1": + version: 29.3.1 + resolution: "jest-message-util@npm:29.3.1" + dependencies: + "@babel/code-frame": ^7.12.13 + "@jest/types": ^29.3.1 + "@types/stack-utils": ^2.0.0 + chalk: ^4.0.0 + graceful-fs: ^4.2.9 + micromatch: ^4.0.4 + pretty-format: ^29.3.1 + slash: ^3.0.0 + stack-utils: ^2.0.3 + checksum: 15d0a2fca3919eb4570bbf575734780c4b9e22de6aae903c4531b346699f7deba834c6c86fe6e9a83ad17fac0f7935511cf16dce4d71a93a71ebb25f18a6e07b + languageName: node + linkType: hard + "jest-mock@npm:^27.5.1": version: 27.5.1 resolution: "jest-mock@npm:27.5.1" @@ -17776,7 +17956,7 @@ fsevents@^1.2.7: languageName: node linkType: hard -"jest-pnp-resolver@npm:^1.2.1, jest-pnp-resolver@npm:^1.2.2": +"jest-pnp-resolver@npm:^1.2.2": version: 1.2.2 resolution: "jest-pnp-resolver@npm:1.2.2" peerDependencies: @@ -17788,13 +17968,6 @@ fsevents@^1.2.7: languageName: node linkType: hard -"jest-regex-util@npm:^24.9.0": - version: 24.9.0 - resolution: "jest-regex-util@npm:24.9.0" - checksum: 94299972501ae5dfc3932673b263fd15dba5e28698571687a28cc59b5a173edcbf52b992f4d5a6eded9da5b7e1468d263ef96a1564267832799b41c2986fc423 - languageName: node - linkType: hard - "jest-regex-util@npm:^26.0.0": version: 26.0.0 resolution: "jest-regex-util@npm:26.0.0" @@ -17816,6 +17989,13 @@ fsevents@^1.2.7: languageName: node linkType: hard +"jest-regex-util@npm:^29.2.0": + version: 29.2.0 + resolution: "jest-regex-util@npm:29.2.0" + checksum: 7c533e51c51230dac20c0d7395b19b8366cb022f7c6e08e6bcf2921626840ff90424af4c9b4689f02f0addfc9b071c4cd5f8f7a989298a4c8e0f9c94418ca1c3 + languageName: node + linkType: hard + "jest-resolve-dependencies@npm:^27.5.1": version: 27.5.1 resolution: "jest-resolve-dependencies@npm:27.5.1" @@ -17827,19 +18007,6 @@ fsevents@^1.2.7: languageName: node linkType: hard -"jest-resolve@npm:^24.9.0": - version: 24.9.0 - resolution: "jest-resolve@npm:24.9.0" - dependencies: - "@jest/types": ^24.9.0 - browser-resolve: ^1.11.3 - chalk: ^2.0.1 - jest-pnp-resolver: ^1.2.1 - realpath-native: ^1.1.0 - checksum: 60a84cbd75d5cdab1ad29c8ed62e43fbc374c906e5a0f166fae5170f91c863ee9372aaab7dbdb3a06a38b0362131fa7c907c114be76a8bc1aeac47013ec308e4 - languageName: node - linkType: hard - "jest-resolve@npm:^27.4.2, jest-resolve@npm:^27.5.1": version: 27.5.1 resolution: "jest-resolve@npm:27.5.1" @@ -17937,54 +18104,35 @@ fsevents@^1.2.7: languageName: node linkType: hard -"jest-snapshot@npm:^24.9.0": - version: 24.9.0 - resolution: "jest-snapshot@npm:24.9.0" - dependencies: - "@babel/types": ^7.0.0 - "@jest/types": ^24.9.0 - chalk: ^2.0.1 - expect: ^24.9.0 - jest-diff: ^24.9.0 - jest-get-type: ^24.9.0 - jest-matcher-utils: ^24.9.0 - jest-message-util: ^24.9.0 - jest-resolve: ^24.9.0 - mkdirp: ^0.5.1 - natural-compare: ^1.4.0 - pretty-format: ^24.9.0 - semver: ^6.2.0 - checksum: 474dc05ededdb8b39fb79801498fcd16c1a13a01b4701a27172be0ee3ebc5640e2bfb2780a9afa49bd825b19fc2be1e2ec5fc3d501afa76a5f7bc40f0120aaf3 - languageName: node - linkType: hard - -"jest-snapshot@npm:^27.5.1": - version: 27.5.1 - resolution: "jest-snapshot@npm:27.5.1" +"jest-snapshot@npm:29.3.1": + version: 29.3.1 + resolution: "jest-snapshot@npm:29.3.1" dependencies: - "@babel/core": ^7.7.2 + "@babel/core": ^7.11.6 "@babel/generator": ^7.7.2 + "@babel/plugin-syntax-jsx": ^7.7.2 "@babel/plugin-syntax-typescript": ^7.7.2 "@babel/traverse": ^7.7.2 - "@babel/types": ^7.0.0 - "@jest/transform": ^27.5.1 - "@jest/types": ^27.5.1 - "@types/babel__traverse": ^7.0.4 + "@babel/types": ^7.3.3 + "@jest/expect-utils": ^29.3.1 + "@jest/transform": ^29.3.1 + "@jest/types": ^29.3.1 + "@types/babel__traverse": ^7.0.6 "@types/prettier": ^2.1.5 babel-preset-current-node-syntax: ^1.0.0 chalk: ^4.0.0 - expect: ^27.5.1 + expect: ^29.3.1 graceful-fs: ^4.2.9 - jest-diff: ^27.5.1 - jest-get-type: ^27.5.1 - jest-haste-map: ^27.5.1 - jest-matcher-utils: ^27.5.1 - jest-message-util: ^27.5.1 - jest-util: ^27.5.1 + jest-diff: ^29.3.1 + jest-get-type: ^29.2.0 + jest-haste-map: ^29.3.1 + jest-matcher-utils: ^29.3.1 + jest-message-util: ^29.3.1 + jest-util: ^29.3.1 natural-compare: ^1.4.0 - pretty-format: ^27.5.1 - semver: ^7.3.2 - checksum: a5cfadf0d21cd76063925d1434bc076443ed6d87847d0e248f0b245f11db3d98ff13e45cc03b15404027dabecd712d925f47b6eae4f64986f688640a7d362514 + pretty-format: ^29.3.1 + semver: ^7.3.5 + checksum: d7d0077935e78c353c828be78ccb092e12ba7622cb0577f21641fadd728ae63a7c1f4a0d8113bfb38db3453a64bfa232fb1cdeefe0e2b48c52ef4065b0ab75ae languageName: node linkType: hard @@ -18030,6 +18178,20 @@ fsevents@^1.2.7: languageName: node linkType: hard +"jest-util@npm:^29.3.1": + version: 29.3.1 + resolution: "jest-util@npm:29.3.1" + dependencies: + "@jest/types": ^29.3.1 + "@types/node": "*" + chalk: ^4.0.0 + ci-info: ^3.2.0 + graceful-fs: ^4.2.9 + picomatch: ^2.2.3 + checksum: f67c60f062b94d21cb60e84b3b812d64b7bfa81fe980151de5c17a74eb666042d0134e2e756d099b7606a1fcf1d633824d2e58197d01d76dde1e2dc00dfcd413 + languageName: node + linkType: hard + "jest-validate@npm:^27.5.1": version: 27.5.1 resolution: "jest-validate@npm:27.5.1" @@ -18114,6 +18276,18 @@ fsevents@^1.2.7: languageName: node linkType: hard +"jest-worker@npm:^29.3.1": + version: 29.3.1 + resolution: "jest-worker@npm:29.3.1" + dependencies: + "@types/node": "*" + jest-util: ^29.3.1 + merge-stream: ^2.0.0 + supports-color: ^8.0.0 + checksum: 38687fcbdc2b7ddc70bbb5dfc703ae095b46b3c7f206d62ecdf5f4d16e336178e217302138f3b906125576bb1cfe4cfe8d43681276fa5899d138ed9422099fb3 + languageName: node + linkType: hard + "jest@npm:^27, jest@npm:^27.4.3": version: 27.5.1 resolution: "jest@npm:27.5.1" @@ -18260,6 +18434,45 @@ fsevents@^1.2.7: languageName: node linkType: hard +"jsdom@npm:^21.0.0": + version: 21.0.0 + resolution: "jsdom@npm:21.0.0" + dependencies: + abab: ^2.0.6 + acorn: ^8.8.1 + acorn-globals: ^7.0.0 + cssom: ^0.5.0 + cssstyle: ^2.3.0 + data-urls: ^3.0.2 + decimal.js: ^10.4.2 + domexception: ^4.0.0 + escodegen: ^2.0.0 + form-data: ^4.0.0 + html-encoding-sniffer: ^3.0.0 + http-proxy-agent: ^5.0.0 + https-proxy-agent: ^5.0.1 + is-potential-custom-element-name: ^1.0.1 + nwsapi: ^2.2.2 + parse5: ^7.1.1 + saxes: ^6.0.0 + symbol-tree: ^3.2.4 + tough-cookie: ^4.1.2 + w3c-xmlserializer: ^4.0.0 + webidl-conversions: ^7.0.0 + whatwg-encoding: ^2.0.0 + whatwg-mimetype: ^3.0.0 + whatwg-url: ^11.0.0 + ws: ^8.11.0 + xml-name-validator: ^4.0.0 + peerDependencies: + canvas: ^2.5.0 + peerDependenciesMeta: + canvas: + optional: true + checksum: 5e553f8a106716a7fc8084350418c69bbbe9121e792760a9b153d5eb184464497c660f2dadcc425f3a17fdde31a3ed4c8bc7b9e9515832f4bbc9d877041bce3a + languageName: node + linkType: hard + "jsesc@npm:^2.5.1": version: 2.5.2 resolution: "jsesc@npm:2.5.2" @@ -18380,6 +18593,13 @@ fsevents@^1.2.7: languageName: node linkType: hard +"jsonc-parser@npm:^3.2.0": + version: 3.2.0 + resolution: "jsonc-parser@npm:3.2.0" + checksum: 946dd9a5f326b745aa326d48a7257e3f4a4b62c5e98ec8e49fa2bdd8d96cef7e6febf1399f5c7016114fd1f68a1c62c6138826d5d90bc650448e3cf0951c53c7 + languageName: node + linkType: hard + "jsonfile@npm:^4.0.0": version: 4.0.0 resolution: "jsonfile@npm:4.0.0" @@ -18712,6 +18932,13 @@ fsevents@^1.2.7: languageName: node linkType: hard +"local-pkg@npm:^0.4.2": + version: 0.4.2 + resolution: "local-pkg@npm:0.4.2" + checksum: 22be451353c25c4411b552bf01880ebc9e995b93574b2facc7757968d888356df59199cacada14162ab53bbc9da055bb692c907b4171f008dbce45a2afc777c1 + languageName: node + linkType: hard + "locate-path@npm:^2.0.0": version: 2.0.0 resolution: "locate-path@npm:2.0.0" @@ -18952,6 +19179,15 @@ fsevents@^1.2.7: languageName: node linkType: hard +"loupe@npm:^2.3.1": + version: 2.3.6 + resolution: "loupe@npm:2.3.6" + dependencies: + get-func-name: ^2.0.0 + checksum: cc83f1b124a1df7384601d72d8d1f5fe95fd7a8185469fec48bb2e4027e45243949e7a013e8d91051a138451ff0552310c32aa9786e60b6a30d1e801bdc2163f + languageName: node + linkType: hard + "lower-case-first@npm:^2.0.2": version: 2.0.2 resolution: "lower-case-first@npm:2.0.2" @@ -19085,6 +19321,15 @@ fsevents@^1.2.7: languageName: node linkType: hard +"makeerror@npm:1.0.12": + version: 1.0.12 + resolution: "makeerror@npm:1.0.12" + dependencies: + tmpl: 1.0.5 + checksum: b38a025a12c8146d6eeea5a7f2bf27d51d8ad6064da8ca9405fcf7bf9b54acd43e3b30ddd7abb9b1bfa4ddb266019133313482570ddb207de568f71ecfcf6060 + languageName: node + linkType: hard + "makeerror@npm:1.0.x": version: 1.0.11 resolution: "makeerror@npm:1.0.11" @@ -19682,6 +19927,18 @@ fsevents@^1.2.7: languageName: node linkType: hard +"mlly@npm:^1.0.0, mlly@npm:^1.1.0": + version: 1.1.0 + resolution: "mlly@npm:1.1.0" + dependencies: + acorn: ^8.8.1 + pathe: ^1.0.0 + pkg-types: ^1.0.1 + ufo: ^1.0.1 + checksum: d53147a2f5f83499589c47a00e00df30cbae2e630dfcfdfdeee2b70b49aff6612f2fa13195a1c6268b8f8ecd6064cb9a35febbdf895b2cbfeacdf9a9b3e31493 + languageName: node + linkType: hard + "move-concurrently@npm:^1.0.1": version: 1.0.1 resolution: "move-concurrently@npm:1.0.1" @@ -20228,6 +20485,13 @@ fsevents@^1.2.7: languageName: node linkType: hard +"nwsapi@npm:^2.2.2": + version: 2.2.2 + resolution: "nwsapi@npm:2.2.2" + checksum: 43769106292bc95f776756ca2f3513dab7b4d506a97c67baec32406447841a35f65f29c1f95ab5d42785210fd41668beed33ca16fa058780be43b101ad73e205 + languageName: node + linkType: hard + "oas-kit-common@npm:^1.0.8": version: 1.0.8 resolution: "oas-kit-common@npm:1.0.8" @@ -20368,7 +20632,7 @@ fsevents@^1.2.7: languageName: node linkType: hard -"object.getownpropertydescriptors@npm:^2.1.0, object.getownpropertydescriptors@npm:^2.1.1": +"object.getownpropertydescriptors@npm:^2.1.0": version: 2.1.2 resolution: "object.getownpropertydescriptors@npm:2.1.2" dependencies: @@ -20903,6 +21167,15 @@ fsevents@^1.2.7: languageName: node linkType: hard +"parse5@npm:^7.1.1": + version: 7.1.2 + resolution: "parse5@npm:7.1.2" + dependencies: + entities: ^4.4.0 + checksum: 59465dd05eb4c5ec87b76173d1c596e152a10e290b7abcda1aecf0f33be49646ea74840c69af975d7887543ea45564801736356c568d6b5e71792fd0f4055713 + languageName: node + linkType: hard + "parseurl@npm:~1.3.2, parseurl@npm:~1.3.3": version: 1.3.3 resolution: "parseurl@npm:1.3.3" @@ -21053,6 +21326,27 @@ fsevents@^1.2.7: languageName: node linkType: hard +"pathe@npm:^0.2.0": + version: 0.2.0 + resolution: "pathe@npm:0.2.0" + checksum: 9a8149ce152088f30d15b0b03a7c128ba21f16b4dc1f3f90fe38eee9f6d0f1d6da8e4e47bd2a4f9e14aaac7c30ed01cfc86216479011de2bdc598b65e6f19f41 + languageName: node + linkType: hard + +"pathe@npm:^1.0.0": + version: 1.0.0 + resolution: "pathe@npm:1.0.0" + checksum: 7b71a4930a5b46111c92149632f74b0e87bade3eabe6c9168dcc4846857a4e124eacc0c2bf044fe0d2a8b7f87ae62b9b2cb11938c61899d485cc36dd1a243a23 + languageName: node + linkType: hard + +"pathval@npm:^1.1.1": + version: 1.1.1 + resolution: "pathval@npm:1.1.1" + checksum: 090e3147716647fb7fb5b4b8c8e5b55e5d0a6086d085b6cd23f3d3c01fcf0ff56fd3cc22f2f4a033bd2e46ed55d61ed8379e123b42afe7d531a2a5fc8bb556d6 + languageName: node + linkType: hard + "pbkdf2@npm:^3.0.3": version: 3.1.2 resolution: "pbkdf2@npm:3.1.2" @@ -21149,6 +21443,17 @@ fsevents@^1.2.7: languageName: node linkType: hard +"pkg-types@npm:^1.0.1": + version: 1.0.1 + resolution: "pkg-types@npm:1.0.1" + dependencies: + jsonc-parser: ^3.2.0 + mlly: ^1.0.0 + pathe: ^1.0.0 + checksum: fe73cc22fb72ddb09227e2837a7b2ed1e0706a18e69a58a6ce13cde2b7eab122cb98de44d5c54fca5715d203ef3d2eb004b3ec84a3c05decb11e7c49a80fe2f9 + languageName: node + linkType: hard + "pkg-up@npm:^3.1.0": version: 3.1.0 resolution: "pkg-up@npm:3.1.0" @@ -22441,6 +22746,17 @@ fsevents@^1.2.7: languageName: node linkType: hard +"postcss@npm:^8.4.20": + version: 8.4.21 + resolution: "postcss@npm:8.4.21" + dependencies: + nanoid: ^3.3.4 + picocolors: ^1.0.0 + source-map-js: ^1.0.2 + checksum: e39ac60ccd1542d4f9d93d894048aac0d686b3bb38e927d8386005718e6793dbbb46930f0a523fe382f1bbd843c6d980aaea791252bf5e176180e5a4336d9679 + languageName: node + linkType: hard + "prelude-ls@npm:^1.2.1": version: 1.2.1 resolution: "prelude-ls@npm:1.2.1" @@ -22506,18 +22822,6 @@ fsevents@^1.2.7: languageName: node linkType: hard -"pretty-format@npm:^24.9.0": - version: 24.9.0 - resolution: "pretty-format@npm:24.9.0" - dependencies: - "@jest/types": ^24.9.0 - ansi-regex: ^4.0.0 - ansi-styles: ^3.2.0 - react-is: ^16.8.4 - checksum: ba9291c8dafd50d2fea1fbad5d2863a6f94e0c8835cce9778ec03bc11bb0f52b9ed0e4ee56aaa331d022ccae2fe52b92f73465a0af58fd0edb59deb6391c6847 - languageName: node - linkType: hard - "pretty-format@npm:^26.0.0, pretty-format@npm:^26.6.2": version: 26.6.2 resolution: "pretty-format@npm:26.6.2" @@ -22553,6 +22857,17 @@ fsevents@^1.2.7: languageName: node linkType: hard +"pretty-format@npm:^29.3.1": + version: 29.3.1 + resolution: "pretty-format@npm:29.3.1" + dependencies: + "@jest/schemas": ^29.0.0 + ansi-styles: ^5.0.0 + react-is: ^18.0.0 + checksum: 9917a0bb859cd7a24a343363f70d5222402c86d10eb45bcc2f77b23a4e67586257390e959061aec22762a782fe6bafb59bf34eb94527bc2e5d211afdb287eb4e + languageName: node + linkType: hard + "pretty-quick@npm:^3.1.0": version: 3.1.1 resolution: "pretty-quick@npm:3.1.1" @@ -22854,6 +23169,13 @@ fsevents@^1.2.7: languageName: node linkType: hard +"querystringify@npm:^2.1.1": + version: 2.2.0 + resolution: "querystringify@npm:2.2.0" + checksum: 5641ea231bad7ef6d64d9998faca95611ed4b11c2591a8cae741e178a974f6a8e0ebde008475259abe1621cb15e692404e6b6626e927f7b849d5c09392604b15 + languageName: node + linkType: hard + "queue-microtask@npm:^1.2.2": version: 1.2.3 resolution: "queue-microtask@npm:1.2.3" @@ -23090,7 +23412,7 @@ fsevents@^1.2.7: languageName: node linkType: hard -"react-is@npm:^16.13.1, react-is@npm:^16.6.0, react-is@npm:^16.7.0, react-is@npm:^16.8.4": +"react-is@npm:^16.13.1, react-is@npm:^16.6.0, react-is@npm:^16.7.0": version: 16.13.1 resolution: "react-is@npm:16.13.1" checksum: f7a19ac3496de32ca9ae12aa030f00f14a3d45374f1ceca0af707c831b2a6098ef0d6bdae51bd437b0a306d7f01d4677fcc8de7c0d331eb47ad0f46130e53c5f @@ -23493,15 +23815,6 @@ fsevents@^1.2.7: languageName: node linkType: hard -"realpath-native@npm:^1.1.0": - version: 1.1.0 - resolution: "realpath-native@npm:1.1.0" - dependencies: - util.promisify: ^1.0.0 - checksum: 75ef0595dea6186384b785a9e0993c58ec604f8be2e39b602fec6d7837c7f770af4a4eb3c81f864a7d81c518a7167a6eaabbc7695b7a88c56e1ef04b91c1d586 - languageName: node - linkType: hard - "recast@npm:^0.20.3, recast@npm:^0.20.4": version: 0.20.5 resolution: "recast@npm:0.20.5" @@ -24362,6 +24675,20 @@ fsevents@^1.2.7: languageName: node linkType: hard +"rollup@npm:^3.7.0": + version: 3.10.0 + resolution: "rollup@npm:3.10.0" + dependencies: + fsevents: ~2.3.2 + dependenciesMeta: + fsevents: + optional: true + bin: + rollup: dist/bin/rollup + checksum: 31a882689c58d084ac36362aeaf2422dc4b80d671bd88c856693c37d63a26ddac9b9819dfba7f79c2d50d5207868b0e3d75f728fe551bbc347cf5dedf8ece18e + languageName: node + linkType: hard + "rsvp@npm:^4.8.4": version: 4.8.5 resolution: "rsvp@npm:4.8.5" @@ -24565,6 +24892,15 @@ fsevents@^1.2.7: languageName: node linkType: hard +"saxes@npm:^6.0.0": + version: 6.0.0 + resolution: "saxes@npm:6.0.0" + dependencies: + xmlchars: ^2.2.0 + checksum: d3fa3e2aaf6c65ed52ee993aff1891fc47d5e47d515164b5449cbf5da2cbdc396137e55590472e64c5c436c14ae64a8a03c29b9e7389fc6f14035cf4e982ef3b + languageName: node + linkType: hard + "scheduler@npm:^0.22.0": version: 0.22.0 resolution: "scheduler@npm:0.22.0" @@ -25031,6 +25367,13 @@ fsevents@^1.2.7: languageName: node linkType: hard +"siginfo@npm:^2.0.0": + version: 2.0.0 + resolution: "siginfo@npm:2.0.0" + checksum: 8aa5a98640ca09fe00d74416eca97551b3e42991614a3d1b824b115fc1401543650914f651ab1311518177e4d297e80b953f4cd4cd7ea1eabe824e8f2091de01 + languageName: node + linkType: hard + "signal-exit@npm:^3.0.0, signal-exit@npm:^3.0.2, signal-exit@npm:^3.0.3": version: 3.0.3 resolution: "signal-exit@npm:3.0.3" @@ -25038,6 +25381,13 @@ fsevents@^1.2.7: languageName: node linkType: hard +"signal-exit@npm:^3.0.7": + version: 3.0.7 + resolution: "signal-exit@npm:3.0.7" + checksum: a2f098f247adc367dffc27845853e9959b9e88b01cb301658cfe4194352d8d2bb32e18467c786a7fe15f1d44b233ea35633d076d5e737870b7139949d1ab6318 + languageName: node + linkType: hard + "signedsource@npm:^1.0.0": version: 1.0.0 resolution: "signedsource@npm:1.0.0" @@ -25111,13 +25461,6 @@ fsevents@^1.2.7: languageName: node linkType: hard -"slash@npm:^2.0.0": - version: 2.0.0 - resolution: "slash@npm:2.0.0" - checksum: 512d4350735375bd11647233cb0e2f93beca6f53441015eea241fe784d8068281c3987fbaa93e7ef1c38df68d9c60013045c92837423c69115297d6169aa85e6 - languageName: node - linkType: hard - "slash@npm:^3.0.0": version: 3.0.0 resolution: "slash@npm:3.0.0" @@ -25488,15 +25831,6 @@ fsevents@^1.2.7: languageName: node linkType: hard -"stack-utils@npm:^1.0.1": - version: 1.0.5 - resolution: "stack-utils@npm:1.0.5" - dependencies: - escape-string-regexp: ^2.0.0 - checksum: f82baf8d89536252a55c76866d5be3d04c96b09693a8d2ab3794b9fdec3674e05bd3f3d19345093e2cbba116a1f8f413858e0537bc3c81c605249261c3d26182 - languageName: node - linkType: hard - "stack-utils@npm:^2.0.3": version: 2.0.5 resolution: "stack-utils@npm:2.0.5" @@ -25506,6 +25840,13 @@ fsevents@^1.2.7: languageName: node linkType: hard +"stackback@npm:0.0.2": + version: 0.0.2 + resolution: "stackback@npm:0.0.2" + checksum: 2d4dc4e64e2db796de4a3c856d5943daccdfa3dd092e452a1ce059c81e9a9c29e0b9badba91b43ef0d5ff5c04ee62feb3bcc559a804e16faf447bac2d883aa99 + languageName: node + linkType: hard + "stackframe@npm:^1.1.1": version: 1.2.0 resolution: "stackframe@npm:1.2.0" @@ -25866,6 +26207,15 @@ fsevents@^1.2.7: languageName: node linkType: hard +"strip-literal@npm:^1.0.0": + version: 1.0.0 + resolution: "strip-literal@npm:1.0.0" + dependencies: + acorn: ^8.8.1 + checksum: ada9b60f322ce3e3fd167b65a186ab77a8c76b8f9074dcdbad4c1a810b46f21c9dca30d4d807e98af580cbe99bfbccd6d8176f69183a454ae2868d8ddd6d4f88 + languageName: node + linkType: hard + "style-inject@npm:^0.3.0": version: 0.3.0 resolution: "style-inject@npm:0.3.0" @@ -26420,6 +26770,13 @@ fsevents@^1.2.7: languageName: node linkType: hard +"tinybench@npm:^2.3.1": + version: 2.3.1 + resolution: "tinybench@npm:2.3.1" + checksum: 74d45fa546d964a8123f98847fc59550945ed7f0d3e5a4ce0f9596d836b51c1d340c2ae0277a8023c15dc9ea3d7cb948a79173bfc46338c9b367c6323ea1eaf3 + languageName: node + linkType: hard + "tinycolor2@npm:1.4.1": version: 1.4.1 resolution: "tinycolor2@npm:1.4.1" @@ -26427,6 +26784,20 @@ fsevents@^1.2.7: languageName: node linkType: hard +"tinypool@npm:^0.3.0": + version: 0.3.0 + resolution: "tinypool@npm:0.3.0" + checksum: 92291c309ed8d004c1ee1ef7f610cd90352383f12c52b0ec16abd9ebc665c03626e7afbc9993df97f63e67fea002b5cc18ba5e8f90260643867cbcac278a183c + languageName: node + linkType: hard + +"tinyspy@npm:^1.0.2": + version: 1.0.2 + resolution: "tinyspy@npm:1.0.2" + checksum: 32096121aa8d52bb625ad62c9314b3e4daba4ab9ac428217b12b1e1dfe9860e3c94d54a7affa279cc70dc6f10d88c6ba46b51de68896b318a06d02f05e87dcc3 + languageName: node + linkType: hard + "title-case@npm:^3.0.3": version: 3.0.3 resolution: "title-case@npm:3.0.3" @@ -26454,6 +26825,13 @@ fsevents@^1.2.7: languageName: node linkType: hard +"tmpl@npm:1.0.5": + version: 1.0.5 + resolution: "tmpl@npm:1.0.5" + checksum: cd922d9b853c00fe414c5a774817be65b058d54a2d01ebb415840960406c669a0fc632f66df885e24cb022ec812739199ccbdb8d1164c3e513f85bfca5ab2873 + languageName: node + linkType: hard + "tmpl@npm:1.0.x": version: 1.0.4 resolution: "tmpl@npm:1.0.4" @@ -26554,6 +26932,18 @@ fsevents@^1.2.7: languageName: node linkType: hard +"tough-cookie@npm:^4.1.2": + version: 4.1.2 + resolution: "tough-cookie@npm:4.1.2" + dependencies: + psl: ^1.1.33 + punycode: ^2.1.1 + universalify: ^0.2.0 + url-parse: ^1.5.3 + checksum: a7359e9a3e875121a84d6ba40cc184dec5784af84f67f3a56d1d2ae39b87c0e004e6ba7c7331f9622a7d2c88609032473488b28fe9f59a1fec115674589de39a + languageName: node + linkType: hard + "tr46@npm:^1.0.1": version: 1.0.1 resolution: "tr46@npm:1.0.1" @@ -26572,6 +26962,15 @@ fsevents@^1.2.7: languageName: node linkType: hard +"tr46@npm:^3.0.0": + version: 3.0.0 + resolution: "tr46@npm:3.0.0" + dependencies: + punycode: ^2.1.1 + checksum: 44c3cc6767fb800490e6e9fd64fd49041aa4e49e1f6a012b34a75de739cc9ed3a6405296072c1df8b6389ae139c5e7c6496f659cfe13a04a4bff3a1422981270 + languageName: node + linkType: hard + "tr46@npm:~0.0.3": version: 0.0.3 resolution: "tr46@npm:0.0.3" @@ -26823,7 +27222,7 @@ fsevents@^1.2.7: languageName: node linkType: hard -"type-detect@npm:4.0.8": +"type-detect@npm:4.0.8, type-detect@npm:^4.0.0, type-detect@npm:^4.0.5": version: 4.0.8 resolution: "type-detect@npm:4.0.8" checksum: 62b5628bff67c0eb0b66afa371bd73e230399a8d2ad30d852716efcc4656a7516904570cd8631a49a3ce57c10225adf5d0cbdcb47f6b0255fe6557c453925a15 @@ -26990,6 +27389,13 @@ fsevents@^1.2.7: languageName: node linkType: hard +"ufo@npm:^1.0.1": + version: 1.0.1 + resolution: "ufo@npm:1.0.1" + checksum: 63024876f21b7cc44267255a8043062046d3215e09212bd682787a13ccf1e0c5d23f7686a7f1bc7ac9f34c7e8a88100af234f42b509db50f17ce638af6ac87cc + languageName: node + linkType: hard + "unbox-primitive@npm:^1.0.2": version: 1.0.2 resolution: "unbox-primitive@npm:1.0.2" @@ -27228,6 +27634,13 @@ fsevents@^1.2.7: languageName: node linkType: hard +"universalify@npm:^0.2.0": + version: 0.2.0 + resolution: "universalify@npm:0.2.0" + checksum: e86134cb12919d177c2353196a4cc09981524ee87abf621f7bc8d249dbbbebaec5e7d1314b96061497981350df786e4c5128dbf442eba104d6e765bc260678b5 + languageName: node + linkType: hard + "universalify@npm:^2.0.0": version: 2.0.0 resolution: "universalify@npm:2.0.0" @@ -27378,6 +27791,16 @@ fsevents@^1.2.7: languageName: node linkType: hard +"url-parse@npm:^1.5.3": + version: 1.5.10 + resolution: "url-parse@npm:1.5.10" + dependencies: + querystringify: ^2.1.1 + requires-port: ^1.0.0 + checksum: fbdba6b1d83336aca2216bbdc38ba658d9cfb8fc7f665eb8b17852de638ff7d1a162c198a8e4ed66001ddbf6c9888d41e4798912c62b4fd777a31657989f7bdf + languageName: node + linkType: hard + "url@npm:^0.11.0": version: 0.11.0 resolution: "url@npm:0.11.0" @@ -27473,19 +27896,6 @@ fsevents@^1.2.7: languageName: node linkType: hard -"util.promisify@npm:^1.0.0": - version: 1.1.1 - resolution: "util.promisify@npm:1.1.1" - dependencies: - call-bind: ^1.0.0 - define-properties: ^1.1.3 - for-each: ^0.3.3 - has-symbols: ^1.0.1 - object.getownpropertydescriptors: ^2.1.1 - checksum: ea371c30b90576862487ae4efd7182aa5855019549a4019d82629acc2709e8ccb0f38944403eebec622fff8ebb44ac3f46a52d745d5f543d30606132a4905f96 - languageName: node - linkType: hard - "util.promisify@npm:~1.0.0": version: 1.0.1 resolution: "util.promisify@npm:1.0.1" @@ -27645,6 +28055,107 @@ fsevents@^1.2.7: languageName: node linkType: hard +"vite-node@npm:0.27.2": + version: 0.27.2 + resolution: "vite-node@npm:0.27.2" + dependencies: + cac: ^6.7.14 + debug: ^4.3.4 + mlly: ^1.1.0 + pathe: ^0.2.0 + picocolors: ^1.0.0 + source-map: ^0.6.1 + source-map-support: ^0.5.21 + vite: ^3.0.0 || ^4.0.0 + bin: + vite-node: vite-node.mjs + checksum: 4cdb4fd952481548dbece6bc86c339cf806f07d58b9e95e7f9e57e4a4f7d5faaf1629dcea4d2a7366080884c1d82b2794ab595e9e5e003c0cf63f17e32a17d13 + languageName: node + linkType: hard + +"vite@npm:^3.0.0 || ^4.0.0": + version: 4.0.4 + resolution: "vite@npm:4.0.4" + dependencies: + esbuild: ^0.16.3 + fsevents: ~2.3.2 + postcss: ^8.4.20 + resolve: ^1.22.1 + rollup: ^3.7.0 + peerDependencies: + "@types/node": ">= 14" + less: "*" + sass: "*" + stylus: "*" + sugarss: "*" + terser: ^5.4.0 + dependenciesMeta: + fsevents: + optional: true + peerDependenciesMeta: + "@types/node": + optional: true + less: + optional: true + sass: + optional: true + stylus: + optional: true + sugarss: + optional: true + terser: + optional: true + bin: + vite: bin/vite.js + checksum: eb86c8cdfe8dcb6644005486b31cb60bc596f2aa683cb194abb5c0afca7c2a5dfdb02bbc7f83f419ad170227ac9c3b898f4406a6d1433105fb61d79d78e47d52 + languageName: node + linkType: hard + +"vitest@npm:^0.27.2": + version: 0.27.2 + resolution: "vitest@npm:0.27.2" + dependencies: + "@types/chai": ^4.3.4 + "@types/chai-subset": ^1.3.3 + "@types/node": "*" + acorn: ^8.8.1 + acorn-walk: ^8.2.0 + cac: ^6.7.14 + chai: ^4.3.7 + debug: ^4.3.4 + local-pkg: ^0.4.2 + picocolors: ^1.0.0 + source-map: ^0.6.1 + strip-literal: ^1.0.0 + tinybench: ^2.3.1 + tinypool: ^0.3.0 + tinyspy: ^1.0.2 + vite: ^3.0.0 || ^4.0.0 + vite-node: 0.27.2 + why-is-node-running: ^2.2.2 + peerDependencies: + "@edge-runtime/vm": "*" + "@vitest/browser": "*" + "@vitest/ui": "*" + happy-dom: "*" + jsdom: "*" + peerDependenciesMeta: + "@edge-runtime/vm": + optional: true + "@vitest/browser": + optional: true + "@vitest/ui": + optional: true + happy-dom: + optional: true + jsdom: + optional: true + bin: + vitest: vitest.mjs + checksum: 0c441656f476ed49fb3d0238a070e836272156d80161ff2153397aa06e711abd6779fad6769126840eda2b1d12568b77aea953e14fbad8569cde2d6fb900f165 + languageName: node + linkType: hard + "vm-browserify@npm:^1.0.1": version: 1.1.2 resolution: "vm-browserify@npm:1.1.2" @@ -27670,6 +28181,15 @@ fsevents@^1.2.7: languageName: node linkType: hard +"w3c-xmlserializer@npm:^4.0.0": + version: 4.0.0 + resolution: "w3c-xmlserializer@npm:4.0.0" + dependencies: + xml-name-validator: ^4.0.0 + checksum: eba070e78deb408ae8defa4d36b429f084b2b47a4741c4a9be3f27a0a3d1845e277e3072b04391a138f7e43776842627d1334e448ff13ff90ad9fb1214ee7091 + languageName: node + linkType: hard + "wait-on@npm:^6.0.1": version: 6.0.1 resolution: "wait-on@npm:6.0.1" @@ -27694,6 +28214,15 @@ fsevents@^1.2.7: languageName: node linkType: hard +"walker@npm:^1.0.8": + version: 1.0.8 + resolution: "walker@npm:1.0.8" + dependencies: + makeerror: 1.0.12 + checksum: ad7a257ea1e662e57ef2e018f97b3c02a7240ad5093c392186ce0bcf1f1a60bbadd520d073b9beb921ed99f64f065efb63dfc8eec689a80e569f93c1c5d5e16c + languageName: node + linkType: hard + "warning@npm:^4.0.3": version: 4.0.3 resolution: "warning@npm:4.0.3" @@ -27792,6 +28321,13 @@ fsevents@^1.2.7: languageName: node linkType: hard +"webidl-conversions@npm:^7.0.0": + version: 7.0.0 + resolution: "webidl-conversions@npm:7.0.0" + checksum: f05588567a2a76428515333eff87200fae6c83c3948a7482ebb109562971e77ef6dc49749afa58abb993391227c5697b3ecca52018793e0cb4620a48f10bd21b + languageName: node + linkType: hard + "webpack-bundle-analyzer@npm:^4.4.2, webpack-bundle-analyzer@npm:^4.5.0": version: 4.5.0 resolution: "webpack-bundle-analyzer@npm:4.5.0" @@ -28132,6 +28668,15 @@ fsevents@^1.2.7: languageName: node linkType: hard +"whatwg-encoding@npm:^2.0.0": + version: 2.0.0 + resolution: "whatwg-encoding@npm:2.0.0" + dependencies: + iconv-lite: 0.6.3 + checksum: 7087810c410aa9b689cbd6af8773341a53cdc1f3aae2a882c163bd5522ec8ca4cdfc269aef417a5792f411807d5d77d50df4c24e3abb00bb60192858a40cc675 + languageName: node + linkType: hard + "whatwg-fetch@npm:^3.4.1, whatwg-fetch@npm:^3.6.2": version: 3.6.2 resolution: "whatwg-fetch@npm:3.6.2" @@ -28146,6 +28691,23 @@ fsevents@^1.2.7: languageName: node linkType: hard +"whatwg-mimetype@npm:^3.0.0": + version: 3.0.0 + resolution: "whatwg-mimetype@npm:3.0.0" + checksum: ce08bbb36b6aaf64f3a84da89707e3e6a31e5ab1c1a2379fd68df79ba712a4ab090904f0b50e6693b0dafc8e6343a6157e40bf18fdffd26e513cf95ee2a59824 + languageName: node + linkType: hard + +"whatwg-url@npm:^11.0.0": + version: 11.0.0 + resolution: "whatwg-url@npm:11.0.0" + dependencies: + tr46: ^3.0.0 + webidl-conversions: ^7.0.0 + checksum: ed4826aaa57e66bb3488a4b25c9cd476c46ba96052747388b5801f137dd740b73fde91ad207d96baf9f17fbcc80fc1a477ad65181b5eb5fa718d27c69501d7af + languageName: node + linkType: hard + "whatwg-url@npm:^5.0.0": version: 5.0.0 resolution: "whatwg-url@npm:5.0.0" @@ -28227,6 +28789,18 @@ fsevents@^1.2.7: languageName: node linkType: hard +"why-is-node-running@npm:^2.2.2": + version: 2.2.2 + resolution: "why-is-node-running@npm:2.2.2" + dependencies: + siginfo: ^2.0.0 + stackback: 0.0.2 + bin: + why-is-node-running: cli.js + checksum: 50820428f6a82dfc3cbce661570bcae9b658723217359b6037b67e495255409b4c8bc7931745f5c175df71210450464517cab32b2f7458ac9c40b4925065200a + languageName: node + linkType: hard + "wide-align@npm:^1.1.0": version: 1.1.3 resolution: "wide-align@npm:1.1.3" @@ -28571,6 +29145,16 @@ fsevents@^1.2.7: languageName: node linkType: hard +"write-file-atomic@npm:^4.0.1": + version: 4.0.2 + resolution: "write-file-atomic@npm:4.0.2" + dependencies: + imurmurhash: ^0.1.4 + signal-exit: ^3.0.7 + checksum: 5da60bd4eeeb935eec97ead3df6e28e5917a6bd317478e4a85a5285e8480b8ed96032bbcc6ecd07b236142a24f3ca871c924ec4a6575e623ec1b11bf8c1c253c + languageName: node + linkType: hard + "ws@npm:7.4.5": version: 7.4.5 resolution: "ws@npm:7.4.5" @@ -28601,6 +29185,21 @@ fsevents@^1.2.7: languageName: node linkType: hard +"ws@npm:^8.11.0": + version: 8.12.0 + resolution: "ws@npm:8.12.0" + peerDependencies: + bufferutil: ^4.0.1 + utf-8-validate: ">=5.0.2" + peerDependenciesMeta: + bufferutil: + optional: true + utf-8-validate: + optional: true + checksum: 818ff3f8749c172a95a114cceb8b89cedd27e43a82d65c7ad0f7882b1e96a2ee6709e3746a903c3fa88beec0c8bae9a9fcd75f20858b32a166dfb7519316a5d7 + languageName: node + linkType: hard + "ws@npm:^8.4.2": version: 8.8.0 resolution: "ws@npm:8.8.0" @@ -28641,6 +29240,13 @@ fsevents@^1.2.7: languageName: node linkType: hard +"xml-name-validator@npm:^4.0.0": + version: 4.0.0 + resolution: "xml-name-validator@npm:4.0.0" + checksum: af100b79c29804f05fa35aa3683e29a321db9b9685d5e5febda3fa1e40f13f85abc40f45a6b2bf7bee33f68a1dc5e8eaef4cec100a304a9db565e6061d4cb5ad + languageName: node + linkType: hard + "xmlchars@npm:^2.2.0": version: 2.2.0 resolution: "xmlchars@npm:2.2.0" From 34ce9b5b0d8af6da668d94bd9e4f9c4bcd86bd1a Mon Sep 17 00:00:00 2001 From: Mark Erikson Date: Wed, 18 Jan 2023 21:08:39 -0500 Subject: [PATCH 020/412] Replace Jest config with Vitest config --- .github/workflows/tests.yml | 4 ++-- packages/toolkit/jest.config.js | 22 ------------------- packages/toolkit/tsconfig.base.json | 1 + packages/toolkit/vitest.config.ts | 22 +++++++++++++++++++ .../{jest.setup.js => vitest.setup.js} | 9 ++++---- 5 files changed, 30 insertions(+), 28 deletions(-) delete mode 100644 packages/toolkit/jest.config.js create mode 100644 packages/toolkit/vitest.config.ts rename packages/toolkit/{jest.setup.js => vitest.setup.js} (61%) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index ae99805904..395b1acec0 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -82,7 +82,7 @@ jobs: - name: Install build artifact run: yarn workspace @reduxjs/toolkit add $(pwd)/package.tgz - - run: sed -i -e /@remap-prod-remove-line/d ./tsconfig.base.json ./jest.config.js ./src/tests/*.* ./src/query/tests/*.* + - run: sed -i -e /@remap-prod-remove-line/d ./tsconfig.base.json ./vitest.config.ts ./src/tests/*.* ./src/query/tests/*.* - name: Run tests, against dist run: yarn test @@ -121,7 +121,7 @@ jobs: - name: Install build artifact run: yarn add ./package.tgz - - run: sed -i -e /@remap-prod-remove-line/d ./tsconfig.base.json ./jest.config.js ./src/tests/*.* ./src/query/tests/*.* + - run: sed -i -e /@remap-prod-remove-line/d ./tsconfig.base.json ./vitest.config.ts ./src/tests/*.* ./src/query/tests/*.* - name: Test types run: | diff --git a/packages/toolkit/jest.config.js b/packages/toolkit/jest.config.js deleted file mode 100644 index b332f3a10d..0000000000 --- a/packages/toolkit/jest.config.js +++ /dev/null @@ -1,22 +0,0 @@ -export default { - testEnvironment: 'jest-environment-jsdom', - setupFilesAfterEnv: ['./jest.setup.js'], - testMatch: ['/src/**/*.(spec|test).[jt]s?(x)'], - moduleNameMapper: { - '^@reduxjs/toolkit$': '/src/index.ts', // @remap-prod-remove-line - '^@reduxjs/toolkit/query$': '/src/query/index.ts', // @remap-prod-remove-line - '^@reduxjs/toolkit/query/react$': '/src/query/react/index.ts', // @remap-prod-remove-line - // this mapping is disabled as we want `dist` imports in the tests only to be used for "type-only" imports which don't play a role for jest - //'^@reduxjs/toolkit/dist/(.*)$': '/src/*', - '^@internal/(.*)$': '/src/$1', - }, - preset: 'ts-jest', - globals: { - 'ts-jest': { - tsconfig: 'tsconfig.test.json', - diagnostics: { - ignoreCodes: [6133], - }, - }, - }, -} diff --git a/packages/toolkit/tsconfig.base.json b/packages/toolkit/tsconfig.base.json index 09cbbdf4b8..e3aeedb31d 100644 --- a/packages/toolkit/tsconfig.base.json +++ b/packages/toolkit/tsconfig.base.json @@ -30,6 +30,7 @@ "allowSyntheticDefaultImports": true, "emitDeclarationOnly": true, "baseUrl": ".", + "types": ["vitest/globals"], "paths": { "@reduxjs/toolkit": ["src/index.ts"], // @remap-prod-remove-line "@reduxjs/toolkit/query": ["src/query/index.ts"], // @remap-prod-remove-line diff --git a/packages/toolkit/vitest.config.ts b/packages/toolkit/vitest.config.ts new file mode 100644 index 0000000000..514c9cc28d --- /dev/null +++ b/packages/toolkit/vitest.config.ts @@ -0,0 +1,22 @@ +import { defineConfig } from 'vitest/config' + +export default defineConfig({ + test: { + globals: true, + environment: 'jsdom', + setupFiles: ['./vitest.setup.js'], + include: ['./src/**/*.(spec|test).[jt]s?(x)'], + alias: { + '@reduxjs/toolkit/query/react': './src/query/react/index.ts', // @remap-prod-remove-line + '@reduxjs/toolkit/query': './src/query/index.ts', // @remap-prod-remove-line + '@reduxjs/toolkit': './src/index.ts', // @remap-prod-remove-line + + // this mapping is disabled as we want `dist` imports in the tests only to be used for "type-only" imports which don't play a role for jest + //'^@reduxjs/toolkit/dist/(.*)$': '/src/*', + '@internal/': './src/', + }, + deps: { + interopDefault: true, + }, + }, +}) diff --git a/packages/toolkit/jest.setup.js b/packages/toolkit/vitest.setup.js similarity index 61% rename from packages/toolkit/jest.setup.js rename to packages/toolkit/vitest.setup.js index 14673adc7a..fc4a30240e 100644 --- a/packages/toolkit/jest.setup.js +++ b/packages/toolkit/vitest.setup.js @@ -1,10 +1,11 @@ //@ts-ignore -const nodeFetch = require('node-fetch') +import nodeFetch from 'node-fetch' //@ts-ignore -global.fetch = nodeFetch +globalThis.fetch = nodeFetch //@ts-ignore -global.Request = nodeFetch.Request -const { server } = require('./src/query/tests/mocks/server') +globalThis.Request = nodeFetch.Request +globalThis.Headers = nodeFetch.Headers +import { server } from './src/query/tests/mocks/server' beforeAll(() => server.listen({ onUnhandledRequest: 'error' })) afterEach(() => server.resetHandlers()) From e3c5837bd78e3ac234754d041506a18b7a17ed7e Mon Sep 17 00:00:00 2001 From: Mark Erikson Date: Wed, 18 Jan 2023 21:08:57 -0500 Subject: [PATCH 021/412] Add patch for console-testing-library to fix ESM + Vitest usage --- ...testing-library-npm-0.6.1-4d9957d402.patch | 41 +++++++++++++++++++ 1 file changed, 41 insertions(+) create mode 100644 .yarn/patches/console-testing-library-npm-0.6.1-4d9957d402.patch diff --git a/.yarn/patches/console-testing-library-npm-0.6.1-4d9957d402.patch b/.yarn/patches/console-testing-library-npm-0.6.1-4d9957d402.patch new file mode 100644 index 0000000000..74ee5988a2 --- /dev/null +++ b/.yarn/patches/console-testing-library-npm-0.6.1-4d9957d402.patch @@ -0,0 +1,41 @@ +diff --git a/src/index.js b/src/index.js +index 90ff7fa3d7d4fa62dbbf638958ae4e28abd089a8..28434687b5163b7472e86bdb11bed69e0868e660 100644 +--- a/src/index.js ++++ b/src/index.js +@@ -1,4 +1,4 @@ +-import { mockConsole, createConsole } from './pure'; ++import { mockConsole, createConsole } from './pure.js'; + + // Keep an instance of the original console and export it + const originalConsole = global.console; +diff --git a/src/pure.js b/src/pure.js +index b00ea2abbaea833e336676aa46e7ced2d59d6d88..42b83ed83fa16cf2234571500fe09868debd9f01 100644 +--- a/src/pure.js ++++ b/src/pure.js +@@ -228,10 +228,11 @@ export function restore() { + global.console = global.originalConsole; + } + ++/* + if (typeof expect === 'function' && typeof expect.extend === 'function') { + expect.extend({ + toMatchInlineSnapshot(received, ...args) { +- /* ------- Workaround for custom inline snapshot matchers ------- */ ++ // Workaround for custom inline snapshot matchers + const error = new Error(); + const stacks = error.stack.split('\n'); + +@@ -245,7 +246,6 @@ if (typeof expect === 'function' && typeof expect.extend === 'function') { + error.stack = stacks.join('\n'); + + const context = Object.assign(this, { error }); +- /* -------------------------------------------------------------- */ + + const testingConsoleInstance = + (received && received.testingConsole) || received; +@@ -270,3 +270,4 @@ if (typeof expect === 'function' && typeof expect.extend === 'function') { + }, + }); + } ++*/ +\ No newline at end of file From e74d461faad6753e331a1caefd3b2dddb6ea9e91 Mon Sep 17 00:00:00 2001 From: Mark Erikson Date: Wed, 18 Jan 2023 21:11:31 -0500 Subject: [PATCH 022/412] Migrate tests from Jest references to Vitest references --- .../tests/sorted_state_adapter.test.ts | 157 +++++++++--------- .../tests/unsorted_state_adapter.test.ts | 143 ++++++++-------- .../toolkit/src/entities/tests/utils.spec.ts | 35 ++-- .../tests/effectScenarios.test.ts | 3 +- .../tests/listenerMiddleware.test.ts | 51 +++--- .../src/query/tests/buildHooks.test.tsx | 7 +- .../src/query/tests/buildThunks.test.tsx | 5 +- .../src/query/tests/cacheCollection.test.ts | 36 ++-- .../src/query/tests/cacheLifecycle.test.ts | 72 ++++---- .../toolkit/src/query/tests/cleanup.test.tsx | 38 ++--- .../toolkit/src/query/tests/createApi.test.ts | 13 +- .../src/query/tests/devWarnings.test.tsx | 2 +- .../src/query/tests/fetchBaseQuery.test.tsx | 5 +- packages/toolkit/src/query/tests/helpers.tsx | 27 ++- .../query/tests/optimisticUpdates.test.tsx | 14 +- .../query/tests/optimisticUpserts.test.tsx | 13 +- .../toolkit/src/query/tests/polling.test.tsx | 3 +- .../toolkit/src/query/tests/queryFn.test.tsx | 15 +- .../src/query/tests/queryLifecycle.test.tsx | 17 +- .../query/tests/refetchingBehaviors.test.tsx | 7 +- .../toolkit/src/query/tests/retry.test.ts | 95 ++++++----- .../toolkit/src/query/tests/utils.test.ts | 51 +++--- .../createAsyncThunk.test.ts.snap | 8 - ...zableStateInvariantMiddleware.test.ts.snap | 52 ------ .../toolkit/src/tests/configureStore.test.ts | 23 +-- .../src/tests/createAsyncThunk.test.ts | 45 ++--- .../toolkit/src/tests/createReducer.test.ts | 20 +-- .../toolkit/src/tests/createSlice.test.ts | 11 +- .../src/tests/getDefaultMiddleware.test.ts | 9 +- .../immutableStateInvariantMiddleware.test.ts | 6 +- packages/toolkit/src/tests/matchers.test.ts | 9 +- 31 files changed, 499 insertions(+), 493 deletions(-) delete mode 100644 packages/toolkit/src/tests/__snapshots__/createAsyncThunk.test.ts.snap delete mode 100644 packages/toolkit/src/tests/__snapshots__/serializableStateInvariantMiddleware.test.ts.snap diff --git a/packages/toolkit/src/entities/tests/sorted_state_adapter.test.ts b/packages/toolkit/src/entities/tests/sorted_state_adapter.test.ts index c1a2ebaa88..cbb5a702a3 100644 --- a/packages/toolkit/src/entities/tests/sorted_state_adapter.test.ts +++ b/packages/toolkit/src/entities/tests/sorted_state_adapter.test.ts @@ -591,9 +591,16 @@ describe('Sorted State Adapter', () => { adapter.removeAll(draft) }) expect(result).toMatchInlineSnapshot(` - Object { - "entities": Object {}, - "ids": Array [], + { + "entities": { + "tgg": { + "id": "tgg", + "title": "The Great Gatsby", + }, + }, + "ids": [ + "tgg", + ], } `) }) @@ -604,14 +611,19 @@ describe('Sorted State Adapter', () => { }) expect(result).toMatchInlineSnapshot(` - Object { - "entities": Object { - "tgg": Object { + { + "entities": { + "af": { + "id": "af", + "title": "Animal Farm", + }, + "tgg": { "id": "tgg", "title": "The Great Gatsby", }, }, - "ids": Array [ + "ids": [ + "af", "tgg", ], } @@ -624,18 +636,18 @@ describe('Sorted State Adapter', () => { }) expect(result).toMatchInlineSnapshot(` - Object { - "entities": Object { - "af": Object { + { + "entities": { + "af": { "id": "af", "title": "Animal Farm", }, - "tgg": Object { + "tgg": { "id": "tgg", "title": "The Great Gatsby", }, }, - "ids": Array [ + "ids": [ "af", "tgg", ], @@ -679,14 +691,14 @@ describe('Sorted State Adapter', () => { }) expect(result).toMatchInlineSnapshot(` - Object { - "entities": Object { - "tgg": Object { + { + "entities": { + "tgg": { "id": "tgg", "title": "A New Hope", }, }, - "ids": Array [ + "ids": [ "tgg", ], } @@ -714,26 +726,15 @@ describe('Sorted State Adapter', () => { }) expect(result).toMatchInlineSnapshot(` - Object { - "entities": Object { - "aco": Object { - "id": "aco", - "title": "Third Change", - }, - "tgg": Object { + { + "entities": { + "tgg": { "id": "tgg", - "title": "Second Change", - }, - "th": Object { - "author": "Fourth Change", - "id": "th", - "title": "First Change", + "title": "The Great Gatsby", }, }, - "ids": Array [ - "th", + "ids": [ "tgg", - "aco", ], } `) @@ -744,14 +745,14 @@ describe('Sorted State Adapter', () => { adapter.upsertOne(draft, TheGreatGatsby) }) expect(result).toMatchInlineSnapshot(` - Object { - "entities": Object { - "tgg": Object { + { + "entities": { + "tgg": { "id": "tgg", - "title": "The Great Gatsby", + "title": "A New Hope", }, }, - "ids": Array [ + "ids": [ "tgg", ], } @@ -767,15 +768,20 @@ describe('Sorted State Adapter', () => { }) }) expect(result).toMatchInlineSnapshot(` - Object { - "entities": Object { - "tgg": Object { + { + "entities": { + "af": { + "id": "af", + "title": "Animal Farm", + }, + "tgg": { "id": "tgg", "title": "A New Hope", }, }, - "ids": Array [ + "ids": [ "tgg", + "af", ], } `) @@ -793,20 +799,15 @@ describe('Sorted State Adapter', () => { ]) }) expect(result).toMatchInlineSnapshot(` - Object { - "entities": Object { - "af": Object { - "id": "af", - "title": "Animal Farm", - }, - "tgg": Object { + { + "entities": { + "tgg": { "id": "tgg", - "title": "A New Hope", + "title": "The Great Gatsby", }, }, - "ids": Array [ + "ids": [ "tgg", - "af", ], } `) @@ -817,15 +818,15 @@ describe('Sorted State Adapter', () => { adapter.setOne(draft, TheGreatGatsby) }) expect(result).toMatchInlineSnapshot(` - Object { - "entities": Object { - "tgg": Object { - "id": "tgg", - "title": "The Great Gatsby", + { + "entities": { + "th": { + "id": "th", + "title": "Silmarillion", }, }, - "ids": Array [ - "tgg", + "ids": [ + "th", ], } `) @@ -840,14 +841,19 @@ describe('Sorted State Adapter', () => { }) }) expect(result).toMatchInlineSnapshot(` - Object { - "entities": Object { - "th": Object { + { + "entities": { + "af": { + "id": "af", + "title": "Animal Farm", + }, + "th": { "id": "th", "title": "Silmarillion", }, }, - "ids": Array [ + "ids": [ + "af", "th", ], } @@ -866,20 +872,15 @@ describe('Sorted State Adapter', () => { ]) }) expect(result).toMatchInlineSnapshot(` - Object { - "entities": Object { - "af": Object { + { + "entities": { + "af": { "id": "af", "title": "Animal Farm", }, - "th": Object { - "id": "th", - "title": "Silmarillion", - }, }, - "ids": Array [ + "ids": [ "af", - "th", ], } `) @@ -891,15 +892,15 @@ describe('Sorted State Adapter', () => { adapter.removeOne(draft, TheGreatGatsby.id) }) expect(result).toMatchInlineSnapshot(` - Object { - "entities": Object { - "af": Object { - "id": "af", - "title": "Animal Farm", + { + "entities": { + "aco": { + "id": "aco", + "title": "A Clockwork Orange", }, }, - "ids": Array [ - "af", + "ids": [ + "aco", ], } `) diff --git a/packages/toolkit/src/entities/tests/unsorted_state_adapter.test.ts b/packages/toolkit/src/entities/tests/unsorted_state_adapter.test.ts index 092522fff5..bf1a304b2e 100644 --- a/packages/toolkit/src/entities/tests/unsorted_state_adapter.test.ts +++ b/packages/toolkit/src/entities/tests/unsorted_state_adapter.test.ts @@ -436,9 +436,16 @@ describe('Unsorted State Adapter', () => { adapter.removeAll(draft) }) expect(result).toMatchInlineSnapshot(` - Object { - "entities": Object {}, - "ids": Array [], + { + "entities": { + "tgg": { + "id": "tgg", + "title": "The Great Gatsby", + }, + }, + "ids": [ + "tgg", + ], } `) }) @@ -469,18 +476,18 @@ describe('Unsorted State Adapter', () => { }) expect(result).toMatchInlineSnapshot(` - Object { - "entities": Object { - "af": Object { + { + "entities": { + "af": { "id": "af", "title": "Animal Farm", }, - "tgg": Object { + "tgg": { "id": "tgg", "title": "The Great Gatsby", }, }, - "ids": Array [ + "ids": [ "tgg", "af", ], @@ -494,18 +501,18 @@ describe('Unsorted State Adapter', () => { }) expect(result).toMatchInlineSnapshot(` - Object { - "entities": Object { - "af": Object { + { + "entities": { + "af": { "id": "af", "title": "Animal Farm", }, - "tgg": Object { + "tgg": { "id": "tgg", "title": "The Great Gatsby", }, }, - "ids": Array [ + "ids": [ "tgg", "af", ], @@ -524,14 +531,14 @@ describe('Unsorted State Adapter', () => { }) expect(result).toMatchInlineSnapshot(` - Object { - "entities": Object { - "tgg": Object { + { + "entities": { + "tgg": { "id": "tgg", "title": "A New Hope", }, }, - "ids": Array [ + "ids": [ "tgg", ], } @@ -559,23 +566,23 @@ describe('Unsorted State Adapter', () => { }) expect(result).toMatchInlineSnapshot(` - Object { - "entities": Object { - "aco": Object { + { + "entities": { + "aco": { "id": "aco", "title": "Third Change", }, - "tgg": Object { + "tgg": { "id": "tgg", "title": "Second Change", }, - "th": Object { + "th": { "author": "Fourth Change", "id": "th", "title": "First Change", }, }, - "ids": Array [ + "ids": [ "tgg", "aco", "th", @@ -589,14 +596,14 @@ describe('Unsorted State Adapter', () => { adapter.upsertOne(draft, TheGreatGatsby) }) expect(result).toMatchInlineSnapshot(` - Object { - "entities": Object { - "tgg": Object { + { + "entities": { + "tgg": { "id": "tgg", "title": "The Great Gatsby", }, }, - "ids": Array [ + "ids": [ "tgg", ], } @@ -612,15 +619,20 @@ describe('Unsorted State Adapter', () => { }) }) expect(result).toMatchInlineSnapshot(` - Object { - "entities": Object { - "tgg": Object { + { + "entities": { + "af": { + "id": "af", + "title": "Animal Farm", + }, + "tgg": { "id": "tgg", "title": "A New Hope", }, }, - "ids": Array [ + "ids": [ "tgg", + "af", ], } `) @@ -638,20 +650,15 @@ describe('Unsorted State Adapter', () => { ]) }) expect(result).toMatchInlineSnapshot(` - Object { - "entities": Object { - "af": Object { - "id": "af", - "title": "Animal Farm", - }, - "tgg": Object { + { + "entities": { + "tgg": { "id": "tgg", - "title": "A New Hope", + "title": "The Great Gatsby", }, }, - "ids": Array [ + "ids": [ "tgg", - "af", ], } `) @@ -662,15 +669,15 @@ describe('Unsorted State Adapter', () => { adapter.setOne(draft, TheGreatGatsby) }) expect(result).toMatchInlineSnapshot(` - Object { - "entities": Object { - "tgg": Object { - "id": "tgg", - "title": "The Great Gatsby", + { + "entities": { + "th": { + "id": "th", + "title": "Silmarillion", }, }, - "ids": Array [ - "tgg", + "ids": [ + "th", ], } `) @@ -685,15 +692,20 @@ describe('Unsorted State Adapter', () => { }) }) expect(result).toMatchInlineSnapshot(` - Object { - "entities": Object { - "th": Object { + { + "entities": { + "af": { + "id": "af", + "title": "Animal Farm", + }, + "th": { "id": "th", "title": "Silmarillion", }, }, - "ids": Array [ + "ids": [ "th", + "af", ], } `) @@ -711,19 +723,14 @@ describe('Unsorted State Adapter', () => { ]) }) expect(result).toMatchInlineSnapshot(` - Object { - "entities": Object { - "af": Object { + { + "entities": { + "af": { "id": "af", "title": "Animal Farm", }, - "th": Object { - "id": "th", - "title": "Silmarillion", - }, }, - "ids": Array [ - "th", + "ids": [ "af", ], } @@ -736,15 +743,15 @@ describe('Unsorted State Adapter', () => { adapter.removeOne(draft, TheGreatGatsby.id) }) expect(result).toMatchInlineSnapshot(` - Object { - "entities": Object { - "af": Object { - "id": "af", - "title": "Animal Farm", + { + "entities": { + "aco": { + "id": "aco", + "title": "A Clockwork Orange", }, }, - "ids": Array [ - "af", + "ids": [ + "aco", ], } `) diff --git a/packages/toolkit/src/entities/tests/utils.spec.ts b/packages/toolkit/src/entities/tests/utils.spec.ts index 3e72a03ec5..cc4053cd18 100644 --- a/packages/toolkit/src/entities/tests/utils.spec.ts +++ b/packages/toolkit/src/entities/tests/utils.spec.ts @@ -1,3 +1,4 @@ +import { vi } from 'vitest' import { AClockworkOrange } from './fixtures/book' describe('Entity utils', () => { @@ -5,35 +6,35 @@ describe('Entity utils', () => { const OLD_ENV = process.env beforeEach(() => { - jest.resetModules() // this is important - it clears the cache + vi.resetModules() // this is important - it clears the cache process.env = { ...OLD_ENV, NODE_ENV: 'development' } }) afterEach(() => { process.env = OLD_ENV - jest.resetAllMocks() + vi.resetAllMocks() }) - it('should not warn when key does exist', () => { - const { selectIdValue } = require('../utils') - const spy = jest.spyOn(console, 'warn') + it('should not warn when key does exist', async () => { + const { selectIdValue } = await import('../utils') + const spy = vi.spyOn(console, 'warn') selectIdValue(AClockworkOrange, (book: any) => book.id) expect(spy).not.toHaveBeenCalled() }) - it('should warn when key does not exist in dev mode', () => { - const { selectIdValue } = require('../utils') - const spy = jest.spyOn(console, 'warn') + it('should warn when key does not exist in dev mode', async () => { + const { selectIdValue } = await import('../utils') + const spy = vi.spyOn(console, 'warn') selectIdValue(AClockworkOrange, (book: any) => book.foo) expect(spy).toHaveBeenCalled() }) - it('should warn when key is undefined in dev mode', () => { - const { selectIdValue } = require('../utils') - const spy = jest.spyOn(console, 'warn') + it('should warn when key is undefined in dev mode', async () => { + const { selectIdValue } = await import('../utils') + const spy = vi.spyOn(console, 'warn') const undefinedAClockworkOrange = { ...AClockworkOrange, id: undefined } selectIdValue(undefinedAClockworkOrange, (book: any) => book.id) @@ -41,20 +42,20 @@ describe('Entity utils', () => { expect(spy).toHaveBeenCalled() }) - it('should not warn when key does not exist in prod mode', () => { + it('should not warn when key does not exist in prod mode', async () => { process.env.NODE_ENV = 'production' - const { selectIdValue } = require('../utils') - const spy = jest.spyOn(console, 'warn') + const { selectIdValue } = await import('../utils') + const spy = vi.spyOn(console, 'warn') selectIdValue(AClockworkOrange, (book: any) => book.foo) expect(spy).not.toHaveBeenCalled() }) - it('should not warn when key is undefined in prod mode', () => { + it('should not warn when key is undefined in prod mode', async () => { process.env.NODE_ENV = 'production' - const { selectIdValue } = require('../utils') - const spy = jest.spyOn(console, 'warn') + const { selectIdValue } = await import('../utils') + const spy = vi.spyOn(console, 'warn') const undefinedAClockworkOrange = { ...AClockworkOrange, id: undefined } selectIdValue(undefinedAClockworkOrange, (book: any) => book.id) diff --git a/packages/toolkit/src/listenerMiddleware/tests/effectScenarios.test.ts b/packages/toolkit/src/listenerMiddleware/tests/effectScenarios.test.ts index 87ed4b1eef..0084c01b04 100644 --- a/packages/toolkit/src/listenerMiddleware/tests/effectScenarios.test.ts +++ b/packages/toolkit/src/listenerMiddleware/tests/effectScenarios.test.ts @@ -4,6 +4,7 @@ import { createSlice, isAnyOf, } from '@reduxjs/toolkit' +import { vi } from 'vitest' import type { AnyAction, PayloadAction, Action } from '@reduxjs/toolkit' @@ -58,7 +59,7 @@ describe('Saga-style Effects Scenarios', () => { beforeAll(() => { const noop = () => {} - jest.spyOn(console, 'error').mockImplementation(noop) + vi.spyOn(console, 'error').mockImplementation(noop) }) beforeEach(() => { diff --git a/packages/toolkit/src/listenerMiddleware/tests/listenerMiddleware.test.ts b/packages/toolkit/src/listenerMiddleware/tests/listenerMiddleware.test.ts index bba8fd1662..5417383685 100644 --- a/packages/toolkit/src/listenerMiddleware/tests/listenerMiddleware.test.ts +++ b/packages/toolkit/src/listenerMiddleware/tests/listenerMiddleware.test.ts @@ -4,6 +4,7 @@ import { createSlice, isAnyOf, } from '@reduxjs/toolkit' +import { vi, Mock } from 'vitest' import type { AnyAction, PayloadAction, Action } from '@reduxjs/toolkit' @@ -141,7 +142,7 @@ describe('createListenerMiddleware', () => { return new Promise((resolve) => setTimeout(resolve, ms)) } - let reducer: jest.Mock + let reducer: Mock let listenerMiddleware = createListenerMiddleware() let { middleware, startListening, stopListening, clearListeners } = listenerMiddleware @@ -157,7 +158,7 @@ describe('createListenerMiddleware', () => { type TestAction3 = ReturnType beforeAll(() => { - jest.spyOn(console, 'error').mockImplementation(noop) + vi.spyOn(console, 'error').mockImplementation(noop) }) beforeEach(() => { @@ -166,7 +167,7 @@ describe('createListenerMiddleware', () => { startListening = listenerMiddleware.startListening stopListening = listenerMiddleware.stopListening clearListeners = listenerMiddleware.clearListeners - reducer = jest.fn(() => ({})) + reducer = vi.fn(() => ({})) store = configureStore({ reducer, middleware: (gDM) => gDM().prepend(middleware), @@ -214,7 +215,7 @@ describe('createListenerMiddleware', () => { describe('Subscription and unsubscription', () => { test('directly subscribing', () => { - const effect = jest.fn((_: TestAction1) => {}) + const effect = vi.fn((_: TestAction1) => {}) startListening({ actionCreator: testAction1, @@ -232,7 +233,7 @@ describe('createListenerMiddleware', () => { }) test('stopListening returns true if an entry has been unsubscribed, false otherwise', () => { - const effect = jest.fn((_: TestAction1) => {}) + const effect = vi.fn((_: TestAction1) => {}) startListening({ actionCreator: testAction1, @@ -244,7 +245,7 @@ describe('createListenerMiddleware', () => { }) test('dispatch(removeListener({...})) returns true if an entry has been unsubscribed, false otherwise', () => { - const effect = jest.fn((_: TestAction1) => {}) + const effect = vi.fn((_: TestAction1) => {}) startListening({ actionCreator: testAction1, @@ -270,7 +271,7 @@ describe('createListenerMiddleware', () => { }) test('can subscribe with a string action type', () => { - const effect = jest.fn((_: AnyAction) => {}) + const effect = vi.fn((_: AnyAction) => {}) store.dispatch( addListener({ @@ -289,7 +290,7 @@ describe('createListenerMiddleware', () => { }) test('can subscribe with a matcher function', () => { - const effect = jest.fn((_: AnyAction) => {}) + const effect = vi.fn((_: AnyAction) => {}) const isAction1Or2 = isAnyOf(testAction1, testAction2) @@ -356,7 +357,7 @@ describe('createListenerMiddleware', () => { }) test('subscribing with the same listener will not make it trigger twice (like EventTarget.addEventListener())', () => { - const effect = jest.fn((_: TestAction1) => {}) + const effect = vi.fn((_: TestAction1) => {}) startListening({ actionCreator: testAction1, @@ -378,7 +379,7 @@ describe('createListenerMiddleware', () => { }) test('unsubscribing via callback', () => { - const effect = jest.fn((_: TestAction1) => {}) + const effect = vi.fn((_: TestAction1) => {}) const unsubscribe = startListening({ actionCreator: testAction1, @@ -394,7 +395,7 @@ describe('createListenerMiddleware', () => { }) test('directly unsubscribing', () => { - const effect = jest.fn((_: TestAction1) => {}) + const effect = vi.fn((_: TestAction1) => {}) startListening({ actionCreator: testAction1, @@ -415,7 +416,7 @@ describe('createListenerMiddleware', () => { }) test('subscribing via action', () => { - const effect = jest.fn((_: TestAction1) => {}) + const effect = vi.fn((_: TestAction1) => {}) store.dispatch( addListener({ @@ -435,7 +436,7 @@ describe('createListenerMiddleware', () => { }) test('unsubscribing via callback from dispatch', () => { - const effect = jest.fn((_: TestAction1) => {}) + const effect = vi.fn((_: TestAction1) => {}) const unsubscribe = store.dispatch( addListener({ @@ -456,7 +457,7 @@ describe('createListenerMiddleware', () => { }) test('unsubscribing via action', () => { - const effect = jest.fn((_: TestAction1) => {}) + const effect = vi.fn((_: TestAction1) => {}) startListening({ actionCreator: testAction1, @@ -575,7 +576,7 @@ describe('createListenerMiddleware', () => { AnyAction, typeof store.getState, typeof store.dispatch - > = jest.fn() + > = vi.fn() startListening({ ...params, effect } as any) @@ -646,7 +647,7 @@ describe('createListenerMiddleware', () => { }) test('"can unsubscribe via middleware api', () => { - const effect = jest.fn( + const effect = vi.fn( (action: TestAction1, api: ListenerEffectAPI) => { if (action.payload === 'b') { api.unsubscribe() @@ -847,7 +848,7 @@ describe('createListenerMiddleware', () => { }) test('getOriginalState can only be invoked synchronously', async () => { - const onError = jest.fn() + const onError = vi.fn() const listenerMiddleware = createListenerMiddleware({ onError, @@ -897,7 +898,7 @@ describe('createListenerMiddleware', () => { test('by default, actions are forwarded to the store', () => { reducer.mockClear() - const effect = jest.fn((_: TestAction1) => {}) + const effect = vi.fn((_: TestAction1) => {}) startListening({ actionCreator: testAction1, @@ -962,7 +963,7 @@ describe('createListenerMiddleware', () => { }, }) - const effect = jest.fn(() => {}) + const effect = vi.fn(() => {}) startListening({ matcher, effect }) store.dispatch(testAction1('a')) @@ -971,8 +972,8 @@ describe('createListenerMiddleware', () => { test('Continues running other listeners if a predicate raises an error', () => { const matcher = (action: any): action is any => true - const firstListener = jest.fn(() => {}) - const secondListener = jest.fn(() => {}) + const firstListener = vi.fn(() => {}) + const secondListener = vi.fn(() => {}) startListening({ // @ts-expect-error @@ -992,12 +993,12 @@ describe('createListenerMiddleware', () => { }) test('Notifies sync listener errors to `onError`, if provided', async () => { - const onError = jest.fn() + const onError = vi.fn() const listenerMiddleware = createListenerMiddleware({ onError, }) const { middleware, startListening } = listenerMiddleware - reducer = jest.fn(() => ({})) + reducer = vi.fn(() => ({})) store = configureStore({ reducer, middleware: (gDM) => gDM().prepend(middleware), @@ -1023,12 +1024,12 @@ describe('createListenerMiddleware', () => { }) test('Notifies async listeners errors to `onError`, if provided', async () => { - const onError = jest.fn() + const onError = vi.fn() const listenerMiddleware = createListenerMiddleware({ onError, }) const { middleware, startListening } = listenerMiddleware - reducer = jest.fn(() => ({})) + reducer = vi.fn(() => ({})) store = configureStore({ reducer, middleware: (gDM) => gDM().prepend(middleware), diff --git a/packages/toolkit/src/query/tests/buildHooks.test.tsx b/packages/toolkit/src/query/tests/buildHooks.test.tsx index 19e4659e84..743b122599 100644 --- a/packages/toolkit/src/query/tests/buildHooks.test.tsx +++ b/packages/toolkit/src/query/tests/buildHooks.test.tsx @@ -1,4 +1,5 @@ import * as React from 'react' +import { vi, SpyInstance } from 'vitest' import type { UseMutation, UseQuery, @@ -672,7 +673,7 @@ describe('hooks tests', () => { ) }) test('with `selectFromResult`', async () => { - const selectFromResult = jest.fn((x) => x) + const selectFromResult = vi.fn((x) => x) const { result } = renderHook( () => api.endpoints.getUser.useQuery(5, { selectFromResult }), { @@ -718,10 +719,10 @@ describe('hooks tests', () => { }) describe('Hook middleware requirements', () => { - let mock: jest.SpyInstance + let mock: SpyInstance beforeEach(() => { - mock = jest.spyOn(console, 'error').mockImplementation(() => {}) + mock = vi.spyOn(console, 'error').mockImplementation(() => {}) }) afterEach(() => { diff --git a/packages/toolkit/src/query/tests/buildThunks.test.tsx b/packages/toolkit/src/query/tests/buildThunks.test.tsx index 70fc0cab87..ed40bd9342 100644 --- a/packages/toolkit/src/query/tests/buildThunks.test.tsx +++ b/packages/toolkit/src/query/tests/buildThunks.test.tsx @@ -1,4 +1,5 @@ import { configureStore } from '@reduxjs/toolkit' +import { vi } from 'vitest' import { createApi } from '@reduxjs/toolkit/query/react' import { renderHook, waitFor } from '@testing-library/react' import type { BaseQueryApi } from '../baseQueryTypes' @@ -86,7 +87,7 @@ describe('re-triggering behavior on arg change', () => { middleware: (gDM) => gDM().concat(api.middleware), }) - const spy = jest.spyOn(getUser, 'initiate') + const spy = vi.spyOn(getUser, 'initiate') beforeEach(() => void spy.mockClear()) test('re-trigger on literal value change', async () => { @@ -101,7 +102,7 @@ describe('re-triggering behavior on arg change', () => { await waitFor(() => { expect(result.current.status).not.toBe('pending') }) - + expect(spy).toHaveBeenCalledTimes(1) for (let x = 1; x < 3; x++) { diff --git a/packages/toolkit/src/query/tests/cacheCollection.test.ts b/packages/toolkit/src/query/tests/cacheCollection.test.ts index 29ccce1fb4..802c9ff7c9 100644 --- a/packages/toolkit/src/query/tests/cacheCollection.test.ts +++ b/packages/toolkit/src/query/tests/cacheCollection.test.ts @@ -1,6 +1,6 @@ import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query' import { configureStore } from '@reduxjs/toolkit' -import { waitMs } from './helpers' +import { vi } from 'vitest' import type { Middleware, Reducer } from 'redux' import { THIRTY_TWO_BIT_MAX_INT, @@ -8,10 +8,10 @@ import { } from '../core/buildMiddleware/cacheCollection' beforeAll(() => { - jest.useFakeTimers('legacy') + vi.useFakeTimers() }) -const onCleanup = jest.fn() +const onCleanup = vi.fn() beforeEach(() => { onCleanup.mockClear() @@ -30,9 +30,9 @@ test(`query: await cleanup, defaults`, async () => { ) store.dispatch(api.endpoints.query.initiate('arg')).unsubscribe() - jest.advanceTimersByTime(59000), await waitMs() + vi.advanceTimersByTime(59000) expect(onCleanup).not.toHaveBeenCalled() - jest.advanceTimersByTime(2000), await waitMs() + vi.advanceTimersByTime(2000) expect(onCleanup).toHaveBeenCalled() }) @@ -50,9 +50,9 @@ test(`query: await cleanup, keepUnusedDataFor set`, async () => { ) store.dispatch(api.endpoints.query.initiate('arg')).unsubscribe() - jest.advanceTimersByTime(28000), await waitMs() + vi.advanceTimersByTime(28000) expect(onCleanup).not.toHaveBeenCalled() - jest.advanceTimersByTime(2000), await waitMs() + vi.advanceTimersByTime(2000) expect(onCleanup).toHaveBeenCalled() }) @@ -72,17 +72,16 @@ test(`query: handles large keepUnuseDataFor values over 32-bit ms`, async () => store.dispatch(api.endpoints.query.initiate('arg')).unsubscribe() // Shouldn't have been called right away - jest.advanceTimersByTime(1000), await waitMs() + vi.advanceTimersByTime(1000) expect(onCleanup).not.toHaveBeenCalled() // Shouldn't have been called any time in the next few minutes - jest.advanceTimersByTime(1_000_000), await waitMs() + vi.advanceTimersByTime(1_000_000) expect(onCleanup).not.toHaveBeenCalled() // _Should_ be called _wayyyy_ in the future (like 24.8 days from now) - jest.advanceTimersByTime(THIRTY_TWO_BIT_MAX_TIMER_SECONDS * 1000), - await waitMs() - expect(onCleanup).toHaveBeenCalled() + vi.advanceTimersByTime(THIRTY_TWO_BIT_MAX_TIMER_SECONDS * 1000), + expect(onCleanup).toHaveBeenCalled() }) describe(`query: await cleanup, keepUnusedDataFor set`, () => { @@ -112,17 +111,17 @@ describe(`query: await cleanup, keepUnusedDataFor set`, () => { test('global keepUnusedDataFor', async () => { store.dispatch(api.endpoints.query.initiate('arg')).unsubscribe() - jest.advanceTimersByTime(28000), await waitMs() + vi.advanceTimersByTime(28000) expect(onCleanup).not.toHaveBeenCalled() - jest.advanceTimersByTime(2000), await waitMs() + vi.advanceTimersByTime(2000) expect(onCleanup).toHaveBeenCalled() }) test('endpoint keepUnusedDataFor', async () => { store.dispatch(api.endpoints.query2.initiate('arg')).unsubscribe() - jest.advanceTimersByTime(34000), await waitMs() + vi.advanceTimersByTime(34000) expect(onCleanup).not.toHaveBeenCalled() - jest.advanceTimersByTime(2000), await waitMs() + vi.advanceTimersByTime(2000) expect(onCleanup).toHaveBeenCalled() }) @@ -130,8 +129,7 @@ describe(`query: await cleanup, keepUnusedDataFor set`, () => { expect(onCleanup).not.toHaveBeenCalled() store.dispatch(api.endpoints.query3.initiate('arg')).unsubscribe() expect(onCleanup).not.toHaveBeenCalled() - jest.advanceTimersByTime(1) - await waitMs() + vi.advanceTimersByTime(1) expect(onCleanup).toHaveBeenCalled() }) @@ -139,7 +137,7 @@ describe(`query: await cleanup, keepUnusedDataFor set`, () => { expect(onCleanup).not.toHaveBeenCalled() store.dispatch(api.endpoints.query4.initiate('arg')).unsubscribe() expect(onCleanup).not.toHaveBeenCalled() - jest.advanceTimersByTime(THIRTY_TWO_BIT_MAX_INT) + vi.advanceTimersByTime(THIRTY_TWO_BIT_MAX_INT) expect(onCleanup).not.toHaveBeenCalled() }) }) diff --git a/packages/toolkit/src/query/tests/cacheLifecycle.test.ts b/packages/toolkit/src/query/tests/cacheLifecycle.test.ts index 9b9284d857..54bac7eda8 100644 --- a/packages/toolkit/src/query/tests/cacheLifecycle.test.ts +++ b/packages/toolkit/src/query/tests/cacheLifecycle.test.ts @@ -1,10 +1,16 @@ import { createApi } from '@reduxjs/toolkit/query' import type { FetchBaseQueryMeta } from '@reduxjs/toolkit/query' +import { vi } from 'vitest' import { fetchBaseQuery } from '@reduxjs/toolkit/query' -import { expectType, fakeTimerWaitFor, setupApiStore, waitMs } from './helpers' +import { + expectType, + fakeTimerWaitFor, + setupApiStore, + DEFAULT_DELAY_MS, +} from './helpers' beforeAll(() => { - jest.useFakeTimers('legacy') + vi.useFakeTimers() }) const api = createApi({ @@ -13,10 +19,10 @@ const api = createApi({ }) const storeRef = setupApiStore(api) -const onNewCacheEntry = jest.fn() -const gotFirstValue = jest.fn() -const onCleanup = jest.fn() -const onCatch = jest.fn() +const onNewCacheEntry = vi.fn() +const gotFirstValue = vi.fn() +const onCleanup = vi.fn() +const onCatch = vi.fn() beforeEach(() => { onNewCacheEntry.mockClear() @@ -68,11 +74,12 @@ describe.each([['query'], ['mutation']] as const)( expect(onNewCacheEntry).toHaveBeenCalledWith('arg') expect(onCleanup).not.toHaveBeenCalled() - promise.unsubscribe(), await waitMs() + promise.unsubscribe() + await vi.advanceTimersByTimeAsync(DEFAULT_DELAY_MS) if (type === 'query') { - jest.advanceTimersByTime(59000), await waitMs() + await vi.advanceTimersByTimeAsync(59000) expect(onCleanup).not.toHaveBeenCalled() - jest.advanceTimersByTime(2000), await waitMs() + await vi.advanceTimersByTimeAsync(2000) } expect(onCleanup).toHaveBeenCalled() @@ -121,11 +128,12 @@ describe.each([['query'], ['mutation']] as const)( }) expect(onCleanup).not.toHaveBeenCalled() - promise.unsubscribe(), await waitMs() + promise.unsubscribe() + await vi.advanceTimersByTimeAsync(DEFAULT_DELAY_MS) if (type === 'query') { - jest.advanceTimersByTime(59000), await waitMs() + await vi.advanceTimersByTimeAsync(59000) expect(onCleanup).not.toHaveBeenCalled() - jest.advanceTimersByTime(2000), await waitMs() + await vi.advanceTimersByTimeAsync(2000) } expect(onCleanup).toHaveBeenCalled() @@ -158,9 +166,10 @@ describe.each([['query'], ['mutation']] as const)( ) expect(onNewCacheEntry).toHaveBeenCalledWith('arg') - promise.unsubscribe(), await waitMs() + promise.unsubscribe() + await vi.advanceTimersByTimeAsync(DEFAULT_DELAY_MS) if (type === 'query') { - jest.advanceTimersByTime(120000), await waitMs() + await vi.advanceTimersByTimeAsync(120000) } expect(gotFirstValue).not.toHaveBeenCalled() expect(onCleanup).not.toHaveBeenCalled() @@ -196,11 +205,13 @@ describe.each([['query'], ['mutation']] as const)( ) expect(onNewCacheEntry).toHaveBeenCalledWith('arg') - promise.unsubscribe(), await waitMs() + promise.unsubscribe() + await vi.advanceTimersByTimeAsync(DEFAULT_DELAY_MS) + if (type === 'query') { - jest.advanceTimersByTime(59000), await waitMs() + await vi.advanceTimersByTimeAsync(59000) expect(onCleanup).not.toHaveBeenCalled() - jest.advanceTimersByTime(2000), await waitMs() + await vi.advanceTimersByTimeAsync(2000) } expect(onCleanup).toHaveBeenCalled() @@ -242,11 +253,12 @@ describe.each([['query'], ['mutation']] as const)( expect(onNewCacheEntry).toHaveBeenCalledWith('arg') - promise.unsubscribe(), await waitMs() + promise.unsubscribe() + await vi.advanceTimersByTimeAsync(DEFAULT_DELAY_MS) if (type === 'query') { - jest.advanceTimersByTime(59000), await waitMs() + await vi.advanceTimersByTimeAsync(59000) expect(onCleanup).not.toHaveBeenCalled() - jest.advanceTimersByTime(2000), await waitMs() + await vi.advanceTimersByTimeAsync(2000) } expect(onCleanup).not.toHaveBeenCalled() expect(gotFirstValue).not.toHaveBeenCalled() @@ -287,11 +299,12 @@ describe.each([['query'], ['mutation']] as const)( expect(onNewCacheEntry).toHaveBeenCalledWith('arg') - promise.unsubscribe(), await waitMs() + promise.unsubscribe() + await vi.advanceTimersByTimeAsync(DEFAULT_DELAY_MS) if (type === 'query') { - jest.advanceTimersByTime(59000), await waitMs() + await vi.advanceTimersByTimeAsync(59000) expect(onCleanup).not.toHaveBeenCalled() - jest.advanceTimersByTime(2000), await waitMs() + await vi.advanceTimersByTimeAsync(2000) } expect(onCleanup).toHaveBeenCalled() expect(gotFirstValue).not.toHaveBeenCalled() @@ -303,7 +316,7 @@ describe.each([['query'], ['mutation']] as const)( ) test(`query: getCacheEntry`, async () => { - const snapshot = jest.fn() + const snapshot = vi.fn() const extended = api.injectEndpoints({ overrideExisting: true, endpoints: (build) => ({ @@ -337,7 +350,7 @@ test(`query: getCacheEntry`, async () => { expect(gotFirstValue).toHaveBeenCalled() }) - jest.advanceTimersByTime(120000), await waitMs() + await vi.advanceTimersByTimeAsync(120000) expect(snapshot).toHaveBeenCalledTimes(3) expect(snapshot.mock.calls[0][0]).toMatchObject({ @@ -376,7 +389,7 @@ test(`query: getCacheEntry`, async () => { }) test(`mutation: getCacheEntry`, async () => { - const snapshot = jest.fn() + const snapshot = vi.fn() const extended = api.injectEndpoints({ overrideExisting: true, endpoints: (build) => ({ @@ -408,7 +421,8 @@ test(`mutation: getCacheEntry`, async () => { expect(gotFirstValue).toHaveBeenCalled() }) - promise.unsubscribe(), await waitMs() + promise.unsubscribe() + await vi.advanceTimersByTimeAsync(DEFAULT_DELAY_MS) expect(snapshot).toHaveBeenCalledTimes(3) expect(snapshot.mock.calls[0][0]).toMatchObject({ @@ -443,7 +457,7 @@ test(`mutation: getCacheEntry`, async () => { }) test('updateCachedData', async () => { - const trackCalls = jest.fn() + const trackCalls = vi.fn() const extended = api.injectEndpoints({ overrideExisting: true, @@ -505,7 +519,7 @@ test('updateCachedData', async () => { expect(gotFirstValue).toHaveBeenCalled() }) - jest.advanceTimersByTime(61000) + await vi.advanceTimersByTimeAsync(61000) await fakeTimerWaitFor(() => { expect(onCleanup).toHaveBeenCalled() diff --git a/packages/toolkit/src/query/tests/cleanup.test.tsx b/packages/toolkit/src/query/tests/cleanup.test.tsx index a9cf2b94c2..55cbd8e806 100644 --- a/packages/toolkit/src/query/tests/cleanup.test.tsx +++ b/packages/toolkit/src/query/tests/cleanup.test.tsx @@ -1,16 +1,15 @@ // tests for "cleanup-after-unsubscribe" behaviour - +import { vi } from 'vitest' import React, { Profiler, ProfilerOnRenderCallback } from 'react' import { createListenerMiddleware } from '@reduxjs/toolkit' import { createApi, QueryStatus } from '@reduxjs/toolkit/query/react' import { render, waitFor, act, screen } from '@testing-library/react' import { setupApiStore } from './helpers' -import { delay } from '../../utils' const tick = () => new Promise((res) => setImmediate(res)) -export const runAllTimers = async () => jest.runAllTimers() && (await tick()) +export const runAllTimers = async () => vi.runAllTimers() && (await tick()) const api = createApi({ baseQuery: () => ({ data: 42 }), @@ -44,7 +43,7 @@ function UsingAB() { } beforeAll(() => { - jest.useFakeTimers('legacy') + vi.useFakeTimers() }) test('data stays in store when component stays rendered', async () => { @@ -55,11 +54,9 @@ test('data stays in store when component stays rendered', async () => { expect(getSubStateA()?.status).toBe(QueryStatus.fulfilled) ) - jest.advanceTimersByTime(120000) + vi.advanceTimersByTime(120000) - await waitFor(() => - expect(getSubStateA()?.status).toBe(QueryStatus.fulfilled) - ) + expect(getSubStateA()?.status).toBe(QueryStatus.fulfilled) }) test('data is removed from store after 60 seconds', async () => { @@ -72,11 +69,11 @@ test('data is removed from store after 60 seconds', async () => { unmount() - jest.advanceTimersByTime(59000) + vi.advanceTimersByTime(59000) expect(getSubStateA()?.status).toBe(QueryStatus.fulfilled) - jest.advanceTimersByTime(2000) + vi.advanceTimersByTime(2000) expect(getSubStateA()).toBeUndefined() }) @@ -106,10 +103,10 @@ test('data stays in store when component stays rendered while data for another c ) - jest.advanceTimersByTime(10) + vi.advanceTimersByTime(10) }) - jest.advanceTimersByTime(120000) + vi.advanceTimersByTime(120000) expect(getSubStateA()).toEqual(statusA) expect(getSubStateB()).toBeUndefined() @@ -140,20 +137,20 @@ test('data stays in store when one component requiring the data stays in the sto ) - jest.advanceTimersByTime(10) - jest.runAllTimers() + vi.advanceTimersByTime(10) + vi.runAllTimers() }) await act(async () => { - jest.advanceTimersByTime(120000) - jest.runAllTimers() + vi.advanceTimersByTime(120000) + vi.runAllTimers() }) expect(getSubStateA()).toEqual(statusA) expect(getSubStateB()).toEqual(statusB) }) -test('Minimizes the number of subscription dispatches when multiple components ask for the same data', async () => { +test.only('Minimizes the number of subscription dispatches when multiple components ask for the same data', async () => { const listenerMiddleware = createListenerMiddleware() const storeRef = setupApiStore(api, undefined, { middleware: { @@ -188,14 +185,15 @@ test('Minimizes the number of subscription dispatches when multiple components a wrapper: storeRef.wrapper, }) - jest.advanceTimersByTime(10) + await act(async () => { + vi.advanceTimersByTime(10) + vi.runAllTimers() + }) await waitFor(() => { return screen.getAllByText(/42/).length > 0 }) - await runAllTimers() - const subscriptions = getSubscriptionsA() expect(Object.keys(subscriptions!).length).toBe(NUM_LIST_ITEMS) diff --git a/packages/toolkit/src/query/tests/createApi.test.ts b/packages/toolkit/src/query/tests/createApi.test.ts index 75d7690ada..5539debc1d 100644 --- a/packages/toolkit/src/query/tests/createApi.test.ts +++ b/packages/toolkit/src/query/tests/createApi.test.ts @@ -1,4 +1,5 @@ import { configureStore, createAction, createReducer } from '@reduxjs/toolkit' +import { vi, SpyInstance } from 'vitest' import type { Api, MutationDefinition, @@ -24,9 +25,9 @@ const originalEnv = process.env.NODE_ENV beforeAll(() => void ((process.env as any).NODE_ENV = 'development')) afterAll(() => void ((process.env as any).NODE_ENV = originalEnv)) -let spy: jest.SpyInstance +let spy: SpyInstance beforeAll(() => { - spy = jest.spyOn(console, 'error').mockImplementation(() => {}) + spy = vi.spyOn(console, 'error').mockImplementation(() => {}) }) afterEach(() => { spy.mockReset() @@ -75,7 +76,7 @@ test('sensible defaults', () => { }) describe('wrong tagTypes log errors', () => { - const baseQuery = jest.fn() + const baseQuery = vi.fn() const api = createApi({ baseQuery, tagTypes: ['User'], @@ -319,7 +320,7 @@ describe('endpoint definition typings', () => { }) describe('enhancing endpoint definitions', () => { - const baseQuery = jest.fn((x: string) => ({ data: 'success' })) + const baseQuery = vi.fn((x: string) => ({ data: 'success' })) const commonBaseQueryApi = { dispatch: expect.any(Function), endpoint: expect.any(String), @@ -864,7 +865,7 @@ describe('custom serializeQueryArgs per endpoint', () => { type SuccessResponse = { value: 'success' } - const serializer1 = jest.fn(customArgsSerializer) + const serializer1 = vi.fn(customArgsSerializer) interface MyApiClient { fetchPost: (id: string) => Promise @@ -971,7 +972,7 @@ describe('custom serializeQueryArgs per endpoint', () => { ).toBeTruthy() }) - const serializer2 = jest.fn(customArgsSerializer) + const serializer2 = vi.fn(customArgsSerializer) const injectedApi = api.injectEndpoints({ endpoints: (build) => ({ diff --git a/packages/toolkit/src/query/tests/devWarnings.test.tsx b/packages/toolkit/src/query/tests/devWarnings.test.tsx index ce50aa9080..404adf635d 100644 --- a/packages/toolkit/src/query/tests/devWarnings.test.tsx +++ b/packages/toolkit/src/query/tests/devWarnings.test.tsx @@ -6,7 +6,7 @@ import { } from 'console-testing-library/pure' import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query' -let restore: () => void +let restore: () => void = () => {} let nodeEnv: string beforeEach(() => { diff --git a/packages/toolkit/src/query/tests/fetchBaseQuery.test.tsx b/packages/toolkit/src/query/tests/fetchBaseQuery.test.tsx index eab78bd59b..91044869ac 100644 --- a/packages/toolkit/src/query/tests/fetchBaseQuery.test.tsx +++ b/packages/toolkit/src/query/tests/fetchBaseQuery.test.tsx @@ -1,3 +1,4 @@ +import { vi } from 'vitest' import { createSlice } from '@reduxjs/toolkit' import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query' import { setupApiStore, waitMs } from './helpers' @@ -18,7 +19,7 @@ const defaultHeaders: Record = { const baseUrl = 'https://example.com' // @ts-ignore -const fetchFn = jest.fn, any[]>(global.fetch) +const fetchFn = vi.fn, any[]>(global.fetch) const baseQuery = fetchBaseQuery({ baseUrl, @@ -983,7 +984,7 @@ describe('fetchFn', () => { clone: () => fakeResponse, } - const spiedFetch = jest.spyOn(window, 'fetch') + const spiedFetch = vi.spyOn(window, 'fetch') spiedFetch.mockResolvedValueOnce(fakeResponse as any) const { data } = await baseQuery({ url: '/echo' }, commonBaseQueryApi, {}) diff --git a/packages/toolkit/src/query/tests/helpers.tsx b/packages/toolkit/src/query/tests/helpers.tsx index faad21e41b..f6d1f34c68 100644 --- a/packages/toolkit/src/query/tests/helpers.tsx +++ b/packages/toolkit/src/query/tests/helpers.tsx @@ -1,13 +1,14 @@ +import React, { useCallback } from 'react' import type { AnyAction, EnhancedStore, Middleware, Store, + Reducer, } from '@reduxjs/toolkit' import { configureStore } from '@reduxjs/toolkit' import { setupListeners } from '@reduxjs/toolkit/query' -import type { Reducer } from 'react' -import React, { useCallback } from 'react' + import { Provider } from 'react-redux' import { @@ -57,11 +58,29 @@ export const hookWaitFor = async (cb: () => void, time = 2000) => { if (Date.now() > startedAt + time) { throw e } - await act(() => waitMs(2)) + await act(async () => { + await waitMs(2) + }) + } + } +} +export const fakeTimerWaitFor = async (cb: () => void, time = 2000) => { + const startedAt = Date.now() + + while (true) { + try { + cb() + return true + } catch (e) { + if (Date.now() > startedAt + time) { + throw e + } + await act(async () => { + await vi.advanceTimersByTimeAsync(2) + }) } } } -export const fakeTimerWaitFor = hookWaitFor export const useRenderCounter = () => { const countRef = React.useRef(0) diff --git a/packages/toolkit/src/query/tests/optimisticUpdates.test.tsx b/packages/toolkit/src/query/tests/optimisticUpdates.test.tsx index 65237090da..51ee44fb44 100644 --- a/packages/toolkit/src/query/tests/optimisticUpdates.test.tsx +++ b/packages/toolkit/src/query/tests/optimisticUpdates.test.tsx @@ -1,3 +1,4 @@ +import { vi } from 'vitest' import { createApi } from '@reduxjs/toolkit/query/react' import { actionsReducer, hookWaitFor, setupApiStore, waitMs } from './helpers' import { renderHook, act } from '@testing-library/react' @@ -8,7 +9,7 @@ interface Post { contents: string } -const baseQuery = jest.fn() +const baseQuery = vi.fn() beforeEach(() => baseQuery.mockReset()) const api = createApi({ @@ -50,9 +51,9 @@ const storeRef = setupApiStore(api, { }) describe('basic lifecycle', () => { - let onStart = jest.fn(), - onError = jest.fn(), - onSuccess = jest.fn() + let onStart = vi.fn(), + onError = vi.fn(), + onSuccess = vi.fn() const extendedApi = api.injectEndpoints({ endpoints: (build) => ({ @@ -109,14 +110,13 @@ describe('basic lifecycle', () => { } ) - baseQuery.mockRejectedValue('error') - + baseQuery.mockRejectedValueOnce('error') expect(onStart).not.toHaveBeenCalled() expect(baseQuery).not.toHaveBeenCalled() + act(() => void result.current[0]('arg')) expect(onStart).toHaveBeenCalledWith('arg') expect(baseQuery).toHaveBeenCalledWith('arg', expect.any(Object), undefined) - expect(onError).not.toHaveBeenCalled() expect(onSuccess).not.toHaveBeenCalled() await act(() => waitMs(5)) diff --git a/packages/toolkit/src/query/tests/optimisticUpserts.test.tsx b/packages/toolkit/src/query/tests/optimisticUpserts.test.tsx index c901202696..1d67f0f1a0 100644 --- a/packages/toolkit/src/query/tests/optimisticUpserts.test.tsx +++ b/packages/toolkit/src/query/tests/optimisticUpserts.test.tsx @@ -1,3 +1,4 @@ +import { vi } from 'vitest' import { createApi } from '@reduxjs/toolkit/query/react' import { actionsReducer, hookWaitFor, setupApiStore, waitMs } from './helpers' import { skipToken } from '../core/buildSelectors' @@ -10,7 +11,7 @@ interface Post { contents: string } -const baseQuery = jest.fn() +const baseQuery = vi.fn() beforeEach(() => baseQuery.mockReset()) const api = createApi({ @@ -67,9 +68,9 @@ const storeRef = setupApiStore(api, { }) describe('basic lifecycle', () => { - let onStart = jest.fn(), - onError = jest.fn(), - onSuccess = jest.fn() + let onStart = vi.fn(), + onError = vi.fn(), + onSuccess = vi.fn() const extendedApi = api.injectEndpoints({ endpoints: (build) => ({ @@ -161,7 +162,7 @@ describe('basic lifecycle', () => { } ) - baseQuery.mockRejectedValue('error') + baseQuery.mockRejectedValueOnce('error') expect(onStart).not.toHaveBeenCalled() expect(baseQuery).not.toHaveBeenCalled() @@ -295,7 +296,7 @@ describe('upsertQueryData', () => { expect(state.data).toEqual(fetchedData) }) test('upsert while a normal query is running (rejected)', async () => { - baseQuery.mockImplementation(async () => { + baseQuery.mockImplementationOnce(async () => { await delay(20) // eslint-disable-next-line no-throw-literal throw 'Error!' diff --git a/packages/toolkit/src/query/tests/polling.test.tsx b/packages/toolkit/src/query/tests/polling.test.tsx index 357575b0da..7443d94048 100644 --- a/packages/toolkit/src/query/tests/polling.test.tsx +++ b/packages/toolkit/src/query/tests/polling.test.tsx @@ -1,8 +1,9 @@ +import { vi } from 'vitest' import { createApi } from '@reduxjs/toolkit/query' import { setupApiStore, waitMs } from './helpers' import { delay } from '../../utils' -const mockBaseQuery = jest +const mockBaseQuery = vi .fn() .mockImplementation((args: any) => ({ data: args })) diff --git a/packages/toolkit/src/query/tests/queryFn.test.tsx b/packages/toolkit/src/query/tests/queryFn.test.tsx index 1562153aff..daf751c6a3 100644 --- a/packages/toolkit/src/query/tests/queryFn.test.tsx +++ b/packages/toolkit/src/query/tests/queryFn.test.tsx @@ -1,3 +1,4 @@ +import { vi } from 'vitest' import type { SerializedError } from '@reduxjs/toolkit' import { configureStore } from '@reduxjs/toolkit' import type { BaseQueryFn, FetchBaseQueryError } from '@reduxjs/toolkit/query' @@ -9,9 +10,11 @@ import type { QuerySubState } from '@reduxjs/toolkit/dist/query/core/apiState' describe('queryFn base implementation tests', () => { const baseQuery: BaseQueryFn = - jest.fn((arg: string) => arg.includes('withErrorQuery') - ? ({ error: `cut${arg}` }) - : ({ data: { wrappedByBaseQuery: arg } })) + vi.fn((arg: string) => + arg.includes('withErrorQuery') + ? { error: `cut${arg}` } + : { data: { wrappedByBaseQuery: arg } } + ) const api = createApi({ baseQuery, @@ -308,11 +311,11 @@ describe('usage scenario tests', () => { exists: () => true, data: () => mockData, } - const get = jest.fn(() => Promise.resolve(mockDocResult)) - const doc = jest.fn((name) => ({ + const get = vi.fn(() => Promise.resolve(mockDocResult)) + const doc = vi.fn((name) => ({ get, })) - const collection = jest.fn((name) => ({ get, doc })) + const collection = vi.fn((name) => ({ get, doc })) const firestore = () => { return { collection, doc } } diff --git a/packages/toolkit/src/query/tests/queryLifecycle.test.tsx b/packages/toolkit/src/query/tests/queryLifecycle.test.tsx index 8bf191943e..d3099799b8 100644 --- a/packages/toolkit/src/query/tests/queryLifecycle.test.tsx +++ b/packages/toolkit/src/query/tests/queryLifecycle.test.tsx @@ -1,3 +1,4 @@ +import { vi } from 'vitest' import { createApi } from '@reduxjs/toolkit/query' import { waitFor } from '@testing-library/react' import type { @@ -15,9 +16,9 @@ const api = createApi({ }) const storeRef = setupApiStore(api) -const onStart = jest.fn() -const onSuccess = jest.fn() -const onError = jest.fn() +const onStart = vi.fn() +const onSuccess = vi.fn() +const onError = vi.fn() beforeEach(() => { onStart.mockClear() @@ -113,7 +114,7 @@ describe.each([['query'], ['mutation']] as const)( ) test('query: getCacheEntry (success)', async () => { - const snapshot = jest.fn() + const snapshot = vi.fn() const extended = api.injectEndpoints({ overrideExisting: true, endpoints: (build) => ({ @@ -174,7 +175,7 @@ test('query: getCacheEntry (success)', async () => { }) test('query: getCacheEntry (error)', async () => { - const snapshot = jest.fn() + const snapshot = vi.fn() const extended = api.injectEndpoints({ overrideExisting: true, endpoints: (build) => ({ @@ -234,7 +235,7 @@ test('query: getCacheEntry (error)', async () => { }) test('mutation: getCacheEntry (success)', async () => { - const snapshot = jest.fn() + const snapshot = vi.fn() const extended = api.injectEndpoints({ overrideExisting: true, endpoints: (build) => ({ @@ -291,7 +292,7 @@ test('mutation: getCacheEntry (success)', async () => { }) test('mutation: getCacheEntry (error)', async () => { - const snapshot = jest.fn() + const snapshot = vi.fn() const extended = api.injectEndpoints({ overrideExisting: true, endpoints: (build) => ({ @@ -347,7 +348,7 @@ test('mutation: getCacheEntry (error)', async () => { }) test('query: updateCachedData', async () => { - const trackCalls = jest.fn() + const trackCalls = vi.fn() const extended = api.injectEndpoints({ overrideExisting: true, diff --git a/packages/toolkit/src/query/tests/refetchingBehaviors.test.tsx b/packages/toolkit/src/query/tests/refetchingBehaviors.test.tsx index d531ae7b4e..cd2dd45c6f 100644 --- a/packages/toolkit/src/query/tests/refetchingBehaviors.test.tsx +++ b/packages/toolkit/src/query/tests/refetchingBehaviors.test.tsx @@ -1,3 +1,4 @@ +import { vi } from 'vitest' import * as React from 'react' import { createApi, setupListeners } from '@reduxjs/toolkit/query/react' import { act, fireEvent, render, waitFor, screen } from '@testing-library/react' @@ -372,11 +373,11 @@ describe('customListenersHandler', () => { }) test('setupListeners accepts a custom callback and executes it', async () => { - const consoleSpy = jest.spyOn(console, 'log') - consoleSpy.mockImplementation((...args) => { + const consoleSpy = vi.spyOn(console, 'log') + consoleSpy.mockImplementation((...args: any[]) => { // console.info(...args) }) - const dispatchSpy = jest.spyOn(storeRef.store, 'dispatch') + const dispatchSpy = vi.spyOn(storeRef.store, 'dispatch') let unsubscribe = () => {} unsubscribe = setupListeners( diff --git a/packages/toolkit/src/query/tests/retry.test.ts b/packages/toolkit/src/query/tests/retry.test.ts index 05e1fa7b83..2bb35e89ae 100644 --- a/packages/toolkit/src/query/tests/retry.test.ts +++ b/packages/toolkit/src/query/tests/retry.test.ts @@ -1,26 +1,29 @@ +import { vi } from 'vitest' import type { BaseQueryFn } from '@reduxjs/toolkit/query' import { createApi, retry } from '@reduxjs/toolkit/query' import { setupApiStore, waitMs } from './helpers' import type { RetryOptions } from '../retry' beforeEach(() => { - jest.useFakeTimers('legacy') + vi.useFakeTimers() }) const loopTimers = async (max: number = 12) => { let count = 0 while (count < max) { - await waitMs(1) - jest.advanceTimersByTime(120000) + await vi.advanceTimersByTimeAsync(1) + vi.advanceTimersByTime(120000) count++ } } +vi.fn() + describe('configuration', () => { test('retrying without any config options', async () => { - const baseBaseQuery = jest.fn< - ReturnType, - Parameters + const baseBaseQuery = vi.fn< + Parameters, + ReturnType >() baseBaseQuery.mockResolvedValue({ error: 'rejected' }) @@ -45,9 +48,9 @@ describe('configuration', () => { }) test('retrying with baseQuery config that overrides default behavior (maxRetries: 5)', async () => { - const baseBaseQuery = jest.fn< - ReturnType, - Parameters + const baseBaseQuery = vi.fn< + Parameters, + ReturnType >() baseBaseQuery.mockResolvedValue({ error: 'rejected' }) @@ -72,9 +75,9 @@ describe('configuration', () => { }) test('retrying with endpoint config that overrides baseQuery config', async () => { - const baseBaseQuery = jest.fn< - ReturnType, - Parameters + const baseBaseQuery = vi.fn< + Parameters, + ReturnType >() baseBaseQuery.mockResolvedValue({ error: 'rejected' }) @@ -111,9 +114,9 @@ describe('configuration', () => { }) test('stops retrying a query after a success', async () => { - const baseBaseQuery = jest.fn< - ReturnType, - Parameters + const baseBaseQuery = vi.fn< + Parameters, + ReturnType >() baseBaseQuery .mockResolvedValueOnce({ error: 'rejected' }) @@ -141,9 +144,9 @@ describe('configuration', () => { }) test('retrying also works with mutations', async () => { - const baseBaseQuery = jest.fn< - ReturnType, - Parameters + const baseBaseQuery = vi.fn< + Parameters, + ReturnType >() baseBaseQuery.mockResolvedValue({ error: 'rejected' }) @@ -169,9 +172,9 @@ describe('configuration', () => { }) test('retrying stops after a success from a mutation', async () => { - const baseBaseQuery = jest.fn< - ReturnType, - Parameters + const baseBaseQuery = vi.fn< + Parameters, + ReturnType >() baseBaseQuery .mockRejectedValueOnce(new Error('rejected')) @@ -199,9 +202,9 @@ describe('configuration', () => { expect(baseBaseQuery).toHaveBeenCalledTimes(3) }) test('non-error-cases should **not** retry', async () => { - const baseBaseQuery = jest.fn< - ReturnType, - Parameters + const baseBaseQuery = vi.fn< + Parameters, + ReturnType >() baseBaseQuery.mockResolvedValue({ data: { success: true } }) @@ -228,9 +231,9 @@ describe('configuration', () => { test('calling retry.fail(error) will skip retrying and expose the error directly', async () => { const error = { message: 'banana' } - const baseBaseQuery = jest.fn< - ReturnType, - Parameters + const baseBaseQuery = vi.fn< + Parameters, + ReturnType >() baseBaseQuery.mockImplementation((input) => { retry.fail(error) @@ -276,9 +279,9 @@ describe('configuration', () => { * Note: * This will retry 16 total times because we try the initial + 3 retries (sum: 4), then retry that process 3 times (starting at 0 for a total of 4)... 4x4=16 (allegedly) */ - const baseBaseQuery = jest.fn< - ReturnType, - Parameters + const baseBaseQuery = vi.fn< + Parameters, + ReturnType >() baseBaseQuery.mockResolvedValue({ error: 'rejected' }) @@ -306,9 +309,9 @@ describe('configuration', () => { }) test('accepts a custom backoff fn', async () => { - const baseBaseQuery = jest.fn< - ReturnType, - Parameters + const baseBaseQuery = vi.fn< + Parameters, + ReturnType >() baseBaseQuery.mockResolvedValue({ error: 'rejected' }) @@ -342,9 +345,9 @@ describe('configuration', () => { }) test('accepts a custom retryCondition fn', async () => { - const baseBaseQuery = jest.fn< - ReturnType, - Parameters + const baseBaseQuery = vi.fn< + Parameters, + ReturnType >() baseBaseQuery.mockResolvedValue({ error: 'rejected' }) @@ -373,9 +376,9 @@ describe('configuration', () => { }) test('retryCondition with endpoint config that overrides baseQuery config', async () => { - const baseBaseQuery = jest.fn< - ReturnType, - Parameters + const baseBaseQuery = vi.fn< + Parameters, + ReturnType >() baseBaseQuery.mockResolvedValue({ error: 'rejected' }) @@ -405,9 +408,9 @@ describe('configuration', () => { }) test('retryCondition also works with mutations', async () => { - const baseBaseQuery = jest.fn< - ReturnType, - Parameters + const baseBaseQuery = vi.fn< + Parameters, + ReturnType >() baseBaseQuery @@ -440,9 +443,9 @@ describe('configuration', () => { }) test('Specifying maxRetries as 0 in RetryOptions prevents retries', async () => { - const baseBaseQuery = jest.fn< - ReturnType, - Parameters + const baseBaseQuery = vi.fn< + Parameters, + ReturnType >() baseBaseQuery.mockResolvedValue({ error: 'rejected' }) @@ -464,7 +467,7 @@ describe('configuration', () => { await loopTimers(2) expect(baseBaseQuery).toHaveBeenCalledTimes(1) - }); + }) test.skip('RetryOptions only accepts one of maxRetries or retryCondition', () => { // @ts-expect-error Should complain if both exist at once diff --git a/packages/toolkit/src/query/tests/utils.test.ts b/packages/toolkit/src/query/tests/utils.test.ts index 25bec07f0b..1124f0294b 100644 --- a/packages/toolkit/src/query/tests/utils.test.ts +++ b/packages/toolkit/src/query/tests/utils.test.ts @@ -1,3 +1,4 @@ +import { vi } from 'vitest' import { isOnline, isDocumentVisible, @@ -6,68 +7,68 @@ import { } from '@internal/query/utils' afterAll(() => { - jest.restoreAllMocks() + vi.restoreAllMocks() }) describe('isOnline', () => { test('Assumes online=true in a node env', () => { - jest - .spyOn(window, 'navigator', 'get') - .mockImplementation(() => undefined as any) + vi.spyOn(window, 'navigator', 'get').mockImplementation( + () => undefined as any + ) expect(navigator).toBeUndefined() expect(isOnline()).toBe(true) }) test('Returns false if navigator isOnline=false', () => { - jest - .spyOn(window, 'navigator', 'get') - .mockImplementation(() => ({ onLine: false } as any)) + vi.spyOn(window, 'navigator', 'get').mockImplementation( + () => ({ onLine: false } as any) + ) expect(isOnline()).toBe(false) }) test('Returns true if navigator isOnline=true', () => { - jest - .spyOn(window, 'navigator', 'get') - .mockImplementation(() => ({ onLine: true } as any)) + vi.spyOn(window, 'navigator', 'get').mockImplementation( + () => ({ onLine: true } as any) + ) expect(isOnline()).toBe(true) }) }) describe('isDocumentVisible', () => { test('Assumes true when in a non-browser env', () => { - jest - .spyOn(window, 'document', 'get') - .mockImplementation(() => undefined as any) + vi.spyOn(window, 'document', 'get').mockImplementation( + () => undefined as any + ) expect(window.document).toBeUndefined() expect(isDocumentVisible()).toBe(true) }) test('Returns false when hidden=true', () => { - jest - .spyOn(window, 'document', 'get') - .mockImplementation(() => ({ visibilityState: 'hidden' } as any)) + vi.spyOn(window, 'document', 'get').mockImplementation( + () => ({ visibilityState: 'hidden' } as any) + ) expect(isDocumentVisible()).toBe(false) }) test('Returns true when visibilityState=prerender', () => { - jest - .spyOn(window, 'document', 'get') - .mockImplementation(() => ({ visibilityState: 'prerender' } as any)) + vi.spyOn(window, 'document', 'get').mockImplementation( + () => ({ visibilityState: 'prerender' } as any) + ) expect(document.visibilityState).toBe('prerender') expect(isDocumentVisible()).toBe(true) }) test('Returns true when visibilityState=visible', () => { - jest - .spyOn(window, 'document', 'get') - .mockImplementation(() => ({ visibilityState: 'visible' } as any)) + vi.spyOn(window, 'document', 'get').mockImplementation( + () => ({ visibilityState: 'visible' } as any) + ) expect(document.visibilityState).toBe('visible') expect(isDocumentVisible()).toBe(true) }) test('Returns true when visibilityState=undefined', () => { - jest - .spyOn(window, 'document', 'get') - .mockImplementation(() => ({ visibilityState: undefined } as any)) + vi.spyOn(window, 'document', 'get').mockImplementation( + () => ({ visibilityState: undefined } as any) + ) expect(document.visibilityState).toBeUndefined() expect(isDocumentVisible()).toBe(true) }) diff --git a/packages/toolkit/src/tests/__snapshots__/createAsyncThunk.test.ts.snap b/packages/toolkit/src/tests/__snapshots__/createAsyncThunk.test.ts.snap deleted file mode 100644 index 81895b595e..0000000000 --- a/packages/toolkit/src/tests/__snapshots__/createAsyncThunk.test.ts.snap +++ /dev/null @@ -1,8 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`createAsyncThunk with abortController behaviour with missing AbortController calling \`abort\` on an asyncThunk works with a FallbackAbortController if no global abortController is not available 1`] = ` -"This platform does not implement AbortController. -If you want to use the AbortController to react to \`abort\` events, please consider importing a polyfill like 'abortcontroller-polyfill/dist/abortcontroller-polyfill-only'." -`; - -exports[`non-serializable arguments are ignored by serializableStateInvariantMiddleware 1`] = `""`; diff --git a/packages/toolkit/src/tests/__snapshots__/serializableStateInvariantMiddleware.test.ts.snap b/packages/toolkit/src/tests/__snapshots__/serializableStateInvariantMiddleware.test.ts.snap deleted file mode 100644 index e4db1dd358..0000000000 --- a/packages/toolkit/src/tests/__snapshots__/serializableStateInvariantMiddleware.test.ts.snap +++ /dev/null @@ -1,52 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`serializableStateInvariantMiddleware Should log an error when a non-serializable action is dispatched 1`] = ` -"A non-serializable value was detected in an action, in the path: \`type\`. Value: Symbol(SOME_CONSTANT) -Take a look at the logic that dispatched this action: Object { - \\"type\\": Symbol(SOME_CONSTANT), -} -(See https://redux.js.org/faq/actions#why-should-type-be-a-string-or-at-least-serializable-why-should-my-action-types-be-constants) -(To allow non-serializable values see: https://redux-toolkit.js.org/usage/usage-guide#working-with-non-serializable-data)" -`; - -exports[`serializableStateInvariantMiddleware Should log an error when a non-serializable value is in state 1`] = ` -"A non-serializable value was detected in the state, in the path: \`testSlice.a\`. Value: Map {} -Take a look at the reducer(s) handling this action type: TEST_ACTION. -(See https://redux.js.org/faq/organizing-state#can-i-put-functions-promises-or-other-non-serializable-items-in-my-store-state)" -`; - -exports[`serializableStateInvariantMiddleware allows ignoring state entirely 1`] = `""`; - -exports[`serializableStateInvariantMiddleware consumer tolerated structures Should log an error when a non-serializable value is nested in state 1`] = ` -"A non-serializable value was detected in the state, in the path: \`testSlice.a.entries\`. Value: [Function entries] -Take a look at the reducer(s) handling this action type: TEST_ACTION. -(See https://redux.js.org/faq/organizing-state#can-i-put-functions-promises-or-other-non-serializable-items-in-my-store-state)" -`; - -exports[`serializableStateInvariantMiddleware consumer tolerated structures Should use consumer supplied isSerializable and getEntries options to tolerate certain structures 1`] = ` -"A non-serializable value was detected in the state, in the path: \`testSlice.a.third.bad-map-instance\`. Value: Map {} -Take a look at the reducer(s) handling this action type: TEST_ACTION. -(See https://redux.js.org/faq/organizing-state#can-i-put-functions-promises-or-other-non-serializable-items-in-my-store-state)" -`; - -exports[`serializableStateInvariantMiddleware ignored action paths can specify (multiple) different values 1`] = `""`; - -exports[`serializableStateInvariantMiddleware ignored action paths default value can be overridden 1`] = ` -"A non-serializable value was detected in an action, in the path: \`meta.arg\`. Value: Map {} -Take a look at the logic that dispatched this action: Object { - \\"meta\\": Object { - \\"arg\\": Map {}, - }, - \\"type\\": \\"test\\", -} -(See https://redux.js.org/faq/actions#why-should-type-be-a-string-or-at-least-serializable-why-should-my-action-types-be-constants) -(To allow non-serializable values see: https://redux-toolkit.js.org/usage/usage-guide#working-with-non-serializable-data)" -`; - -exports[`serializableStateInvariantMiddleware ignored action paths default value: meta.arg 1`] = `""`; - -exports[`serializableStateInvariantMiddleware should not check serializability for ignored slice names 1`] = ` -"A non-serializable value was detected in the state, in the path: \`testSlice.b.d\`. Value: Map {} -Take a look at the reducer(s) handling this action type: TEST_ACTION. -(See https://redux.js.org/faq/organizing-state#can-i-put-functions-promises-or-other-non-serializable-items-in-my-store-state)" -`; diff --git a/packages/toolkit/src/tests/configureStore.test.ts b/packages/toolkit/src/tests/configureStore.test.ts index 49831e51e6..a963a4c54a 100644 --- a/packages/toolkit/src/tests/configureStore.test.ts +++ b/packages/toolkit/src/tests/configureStore.test.ts @@ -1,3 +1,4 @@ +import { vi } from 'vitest' import type { StoreEnhancer, StoreEnhancerStoreCreator } from '@reduxjs/toolkit' import { configureStore } from '@reduxjs/toolkit' import * as RTK from '@reduxjs/toolkit' @@ -5,15 +6,17 @@ import * as redux from 'redux' import * as devtools from '@internal/devtoolsExtension' describe('configureStore', () => { - jest.spyOn(redux, 'applyMiddleware') - jest.spyOn(redux, 'combineReducers') - jest.spyOn(redux, 'compose') - jest.spyOn(redux, 'createStore') - jest.spyOn(devtools, 'composeWithDevTools') // @remap-prod-remove-line + vi.spyOn(redux, 'applyMiddleware') + vi.spyOn(redux, 'combineReducers') + vi.spyOn(redux, 'compose') + vi.spyOn(redux, 'createStore') + vi.spyOn(devtools, 'composeWithDevTools') // @remap-prod-remove-line const reducer: redux.Reducer = (state = {}, _action) => state - beforeEach(() => jest.clearAllMocks()) + beforeEach(() => { + vi.clearAllMocks() + }) describe('given a function reducer', () => { it('calls createStore with the reducer', () => { @@ -90,7 +93,7 @@ describe('configureStore', () => { describe('given a middleware creation function that returns undefined', () => { it('throws an error', () => { - const invalidBuilder = jest.fn((getDefaultMiddleware) => undefined as any) + const invalidBuilder = vi.fn((getDefaultMiddleware) => undefined as any) expect(() => configureStore({ middleware: invalidBuilder, reducer }) ).toThrow( @@ -101,7 +104,7 @@ describe('configureStore', () => { describe('given a middleware creation function that returns an array with non-functions', () => { it('throws an error', () => { - const invalidBuilder = jest.fn((getDefaultMiddleware) => [true] as any) + const invalidBuilder = vi.fn((getDefaultMiddleware) => [true] as any) expect(() => configureStore({ middleware: invalidBuilder, reducer }) ).toThrow('each middleware provided to configureStore must be a function') @@ -135,11 +138,11 @@ describe('configureStore', () => { describe('middleware builder notation', () => { it('calls builder, passes getDefaultMiddleware and uses returned middlewares', () => { - const thank = jest.fn( + const thank = vi.fn( ((_store) => (next) => (action) => 'foobar') as redux.Middleware ) - const builder = jest.fn((getDefaultMiddleware) => { + const builder = vi.fn((getDefaultMiddleware) => { expect(getDefaultMiddleware).toEqual(expect.any(Function)) expect(getDefaultMiddleware()).toEqual(expect.any(Array)) diff --git a/packages/toolkit/src/tests/createAsyncThunk.test.ts b/packages/toolkit/src/tests/createAsyncThunk.test.ts index 26550c93b4..8208d8693e 100644 --- a/packages/toolkit/src/tests/createAsyncThunk.test.ts +++ b/packages/toolkit/src/tests/createAsyncThunk.test.ts @@ -1,3 +1,4 @@ +import { vi } from 'vitest' import type { AnyAction } from '@reduxjs/toolkit' import { createAsyncThunk, @@ -58,7 +59,7 @@ describe('createAsyncThunk', () => { }) it('accepts arguments and dispatches the actions on resolve', async () => { - const dispatch = jest.fn() + const dispatch = vi.fn() let passedArg: any @@ -98,7 +99,7 @@ describe('createAsyncThunk', () => { }) it('accepts arguments and dispatches the actions on reject', async () => { - const dispatch = jest.fn() + const dispatch = vi.fn() const args = 123 let generatedRequestId = '' @@ -134,7 +135,7 @@ describe('createAsyncThunk', () => { }) it('dispatches an empty error when throwing a random object without serializedError properties', async () => { - const dispatch = jest.fn() + const dispatch = vi.fn() const args = 123 let generatedRequestId = '' @@ -169,7 +170,7 @@ describe('createAsyncThunk', () => { }) it('dispatches an action with a formatted error when throwing an object with known error keys', async () => { - const dispatch = jest.fn() + const dispatch = vi.fn() const args = 123 let generatedRequestId = '' @@ -210,7 +211,7 @@ describe('createAsyncThunk', () => { }) it('dispatches a rejected action with a customized payload when a user returns rejectWithValue()', async () => { - const dispatch = jest.fn() + const dispatch = vi.fn() const args = 123 let generatedRequestId = '' @@ -255,7 +256,7 @@ describe('createAsyncThunk', () => { }) it('dispatches a rejected action with a customized payload when a user throws rejectWithValue()', async () => { - const dispatch = jest.fn() + const dispatch = vi.fn() const args = 123 let generatedRequestId = '' @@ -300,7 +301,7 @@ describe('createAsyncThunk', () => { }) it('dispatches a rejected action with a miniSerializeError when rejectWithValue conditions are not satisfied', async () => { - const dispatch = jest.fn() + const dispatch = vi.fn() const args = 123 let generatedRequestId = '' @@ -484,14 +485,14 @@ describe('createAsyncThunk with abortController', () => { describe('behaviour with missing AbortController', () => { let keepAbortController: typeof window['AbortController'] let freshlyLoadedModule: typeof import('../createAsyncThunk') - let restore: () => void + let restore: () => void = () => {} let nodeEnv: string - beforeEach(() => { + beforeEach(async () => { keepAbortController = window.AbortController delete (window as any).AbortController - jest.resetModules() - freshlyLoadedModule = require('../createAsyncThunk') + vi.resetModules() + freshlyLoadedModule = await import('../createAsyncThunk') restore = mockConsole(createConsole()) nodeEnv = process.env.NODE_ENV! ;(process.env as any).NODE_ENV = 'development' @@ -501,7 +502,7 @@ describe('createAsyncThunk with abortController', () => { ;(process.env as any).NODE_ENV = nodeEnv restore() window.AbortController = keepAbortController - jest.resetModules() + vi.resetModules() }) test('calling `abort` on an asyncThunk works with a FallbackAbortController if no global abortController is not available', async () => { @@ -539,10 +540,10 @@ test('non-serializable arguments are ignored by serializableStateInvariantMiddle describe('conditional skipping of asyncThunks', () => { const arg = {} - const getState = jest.fn(() => ({})) - const dispatch = jest.fn((x: any) => x) - const payloadCreator = jest.fn((x: typeof arg) => 10) - const condition = jest.fn(() => false) + const getState = vi.fn(() => ({})) + const dispatch = vi.fn((x: any) => x) + const payloadCreator = vi.fn((x: typeof arg) => 10) + const condition = vi.fn(() => false) const extra = {} beforeEach(() => { @@ -645,7 +646,7 @@ describe('conditional skipping of asyncThunks', () => { }) test('does not fail when attempting to abort a canceled promise', async () => { - const asyncPayloadCreator = jest.fn(async (x: typeof arg) => { + const asyncPayloadCreator = vi.fn(async (x: typeof arg) => { await delay(200) return 10 }) @@ -720,8 +721,8 @@ test('serializeError implementation', async () => { }) describe('unwrapResult', () => { - const getState = jest.fn(() => ({})) - const dispatch = jest.fn((x: any) => x) + const getState = vi.fn(() => ({})) + const dispatch = vi.fn((x: any) => x) const extra = {} test('fulfilled case', async () => { const asyncThunk = createAsyncThunk('test', () => { @@ -779,7 +780,7 @@ describe('idGenerator option', () => { test('idGenerator implementation - can customizes how request IDs are generated', async () => { function makeFakeIdGenerator() { let id = 0 - return jest.fn(() => { + return vi.fn(() => { id++ return `fake-random-id-${id}` }) @@ -833,7 +834,7 @@ describe('idGenerator option', () => { }) test('idGenerator should be called with thunkArg', async () => { - const customIdGenerator = jest.fn((seed) => `fake-unique-random-id-${seed}`) + const customIdGenerator = vi.fn((seed) => `fake-unique-random-id-${seed}`) let generatedRequestId = '' const asyncThunk = createAsyncThunk( 'test', @@ -855,7 +856,7 @@ describe('idGenerator option', () => { test('`condition` will see state changes from a synchronously invoked asyncThunk', () => { type State = ReturnType - const onStart = jest.fn() + const onStart = vi.fn() const asyncThunk = createAsyncThunk< void, { force?: boolean }, diff --git a/packages/toolkit/src/tests/createReducer.test.ts b/packages/toolkit/src/tests/createReducer.test.ts index 078f26618c..243cecf943 100644 --- a/packages/toolkit/src/tests/createReducer.test.ts +++ b/packages/toolkit/src/tests/createReducer.test.ts @@ -1,3 +1,4 @@ +import { vi } from 'vitest' import type { CaseReducer, PayloadAction, @@ -72,15 +73,15 @@ describe('createReducer', () => { let originalNodeEnv = process.env.NODE_ENV beforeEach(() => { - jest.resetModules() + vi.resetModules() }) afterEach(() => { process.env.NODE_ENV = originalNodeEnv }) - it('Throws an error if the legacy object notation is used', () => { - const { createReducer } = require('../createReducer') + it('Throws an error if the legacy object notation is used', async () => { + const { createReducer } = await import('../createReducer') const wrapper = () => { // @ts-ignore let dummyReducer = (createReducer as CreateReducer)([] as TodoState, {}) @@ -95,9 +96,9 @@ describe('createReducer', () => { ) }) - it('Crashes in production', () => { + it('Crashes in production', async () => { process.env.NODE_ENV = 'production' - const { createReducer } = require('../createReducer') + const { createReducer } = await import('../createReducer') const wrapper = () => { // @ts-ignore let dummyReducer = (createReducer as CreateReducer)([] as TodoState, {}) @@ -111,7 +112,7 @@ describe('createReducer', () => { let originalNodeEnv = process.env.NODE_ENV beforeEach(() => { - jest.resetModules() + vi.resetModules() process.env.NODE_ENV = 'production' }) @@ -119,9 +120,8 @@ describe('createReducer', () => { process.env.NODE_ENV = originalNodeEnv }) - test('Freezes data in production', () => { - const createReducer: CreateReducer = - require('../createReducer').createReducer + test('Freezes data in production', async () => { + const { createReducer } = await import('../createReducer') const addTodo: AddTodoReducer = (state, action) => { const { newTodo } = action.payload state.push({ ...newTodo, completed: false }) @@ -212,7 +212,7 @@ describe('createReducer', () => { behavesLikeReducer(todosReducer) it('Should only call the init function when `undefined` state is passed in', () => { - const spy = jest.fn().mockReturnValue(42) + const spy = vi.fn().mockReturnValue(42) const dummyReducer = createReducer(spy, () => {}) expect(spy).not.toHaveBeenCalled() diff --git a/packages/toolkit/src/tests/createSlice.test.ts b/packages/toolkit/src/tests/createSlice.test.ts index 8cbe39340a..38038dbefc 100644 --- a/packages/toolkit/src/tests/createSlice.test.ts +++ b/packages/toolkit/src/tests/createSlice.test.ts @@ -1,3 +1,4 @@ +import { vi } from 'vitest' import type { PayloadAction } from '@reduxjs/toolkit' import { createSlice, createAction } from '@reduxjs/toolkit' import { @@ -280,7 +281,7 @@ describe('createSlice', () => { describe('behaviour with enhanced case reducers', () => { it('should pass all arguments to the prepare function', () => { - const prepare = jest.fn((payload, somethingElse) => ({ payload })) + const prepare = vi.fn((payload, somethingElse) => ({ payload })) const testSlice = createSlice({ name: 'test', @@ -301,7 +302,7 @@ describe('createSlice', () => { }) it('should call the reducer function', () => { - const reducer = jest.fn(() => 5) + const reducer = vi.fn(() => 5) const testSlice = createSlice({ name: 'test', @@ -380,7 +381,7 @@ describe('createSlice', () => { let originalNodeEnv = process.env.NODE_ENV beforeEach(() => { - jest.resetModules() + vi.resetModules() restore = mockConsole(createConsole()) }) @@ -389,8 +390,8 @@ describe('createSlice', () => { }) // NOTE: This needs to be in front of the later `createReducer` call to check the one-time warning - it('Throws an error if the legacy object notation is used', () => { - const { createSlice } = require('../createSlice') + it('Throws an error if the legacy object notation is used', async () => { + const { createSlice } = await import('../createSlice') let dummySlice = (createSlice as CreateSlice)({ name: 'dummy', diff --git a/packages/toolkit/src/tests/getDefaultMiddleware.test.ts b/packages/toolkit/src/tests/getDefaultMiddleware.test.ts index c47e3905cc..4da69101e3 100644 --- a/packages/toolkit/src/tests/getDefaultMiddleware.test.ts +++ b/packages/toolkit/src/tests/getDefaultMiddleware.test.ts @@ -1,3 +1,4 @@ +import { vi } from 'vitest' import type { AnyAction, Middleware, @@ -25,13 +26,13 @@ describe('getDefaultMiddleware', () => { describe('Production behavior', () => { beforeEach(() => { - jest.resetModules() + vi.resetModules() }) - it('returns an array with only redux-thunk in production', () => { + it('returns an array with only redux-thunk in production', async () => { process.env.NODE_ENV = 'production' - const { thunk } = require('redux-thunk') - const { getDefaultMiddleware } = require('@reduxjs/toolkit') + const { thunk } = await import('redux-thunk') + const { getDefaultMiddleware } = await import('@reduxjs/toolkit') const middleware = getDefaultMiddleware() expect(middleware).toContain(thunk) diff --git a/packages/toolkit/src/tests/immutableStateInvariantMiddleware.test.ts b/packages/toolkit/src/tests/immutableStateInvariantMiddleware.test.ts index bc90e4906a..a258de7961 100644 --- a/packages/toolkit/src/tests/immutableStateInvariantMiddleware.test.ts +++ b/packages/toolkit/src/tests/immutableStateInvariantMiddleware.test.ts @@ -10,7 +10,11 @@ import { } from '@reduxjs/toolkit' import { trackForMutations } from '@internal/immutableStateInvariantMiddleware' -import { mockConsole, createConsole, getLog } from 'console-testing-library' +import { + mockConsole, + createConsole, + getLog, +} from 'console-testing-library/pure' describe('createImmutableStateInvariantMiddleware', () => { let state: { foo: { bar: number[]; baz: string } } diff --git a/packages/toolkit/src/tests/matchers.test.ts b/packages/toolkit/src/tests/matchers.test.ts index b9307c97f3..7282c5fa65 100644 --- a/packages/toolkit/src/tests/matchers.test.ts +++ b/packages/toolkit/src/tests/matchers.test.ts @@ -1,3 +1,4 @@ +import { vi } from 'vitest' import type { ThunkAction, AnyAction } from '@reduxjs/toolkit' import { isAllOf, @@ -282,8 +283,8 @@ describe('isRejectedWithValue', () => { ) expect(isRejectedWithValue()(rejectedAction)).toBe(false) - const getState = jest.fn(() => ({})) - const dispatch = jest.fn((x: any) => x) + const getState = vi.fn(() => ({})) + const dispatch = vi.fn((x: any) => x) const extra = {} // note: doesn't throw because we don't unwrap it @@ -321,8 +322,8 @@ describe('isRejectedWithValue', () => { // rejected-with-value is a narrower requirement than rejected expect(matchAC(rejectedAction)).toBe(false) - const getState = jest.fn(() => ({})) - const dispatch = jest.fn((x: any) => x) + const getState = vi.fn(() => ({})) + const dispatch = vi.fn((x: any) => x) const extra = {} // note: doesn't throw because we don't unwrap it From 2c16ba9a922821d15ff852f764b99aa9c2b4a0e5 Mon Sep 17 00:00:00 2001 From: Mark Erikson Date: Wed, 18 Jan 2023 22:53:59 -0500 Subject: [PATCH 023/412] Use Jest 29 in codegen-openapi --- .../rtk-query-codegen-openapi/package.json | 4 +- yarn.lock | 606 +++++++++++++++++- 2 files changed, 603 insertions(+), 7 deletions(-) diff --git a/packages/rtk-query-codegen-openapi/package.json b/packages/rtk-query-codegen-openapi/package.json index 1fdd912502..59d0648f96 100644 --- a/packages/rtk-query-codegen-openapi/package.json +++ b/packages/rtk-query-codegen-openapi/package.json @@ -46,11 +46,11 @@ "esbuild": "~0.17", "esbuild-runner": "^2.2.1", "husky": "^4.3.6", - "jest": "^27", + "jest": "^29", "msw": "^0.40.2", "openapi-types": "^9.1.0", "pretty-quick": "^3.1.0", - "ts-jest": "^27", + "ts-jest": "^29", "ts-node": "^10.4.0", "yalc": "^1.0.0-pre.47" }, diff --git a/yarn.lock b/yarn.lock index f4eca4c287..25ae80c928 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5623,6 +5623,20 @@ __metadata: languageName: node linkType: hard +"@jest/console@npm:^29.3.1": + version: 29.3.1 + resolution: "@jest/console@npm:29.3.1" + dependencies: + "@jest/types": ^29.3.1 + "@types/node": "*" + chalk: ^4.0.0 + jest-message-util: ^29.3.1 + jest-util: ^29.3.1 + slash: ^3.0.0 + checksum: 9eecbfb6df4f5b810374849b7566d321255e6fd6e804546236650384966be532ff75a3e445a3277eadefe67ddf4dc56cd38332abd72d6a450f1bea9866efc6d7 + languageName: node + linkType: hard + "@jest/core@npm:^27.5.1": version: 27.5.1 resolution: "@jest/core@npm:27.5.1" @@ -5664,6 +5678,47 @@ __metadata: languageName: node linkType: hard +"@jest/core@npm:^29.3.1": + version: 29.3.1 + resolution: "@jest/core@npm:29.3.1" + dependencies: + "@jest/console": ^29.3.1 + "@jest/reporters": ^29.3.1 + "@jest/test-result": ^29.3.1 + "@jest/transform": ^29.3.1 + "@jest/types": ^29.3.1 + "@types/node": "*" + ansi-escapes: ^4.2.1 + chalk: ^4.0.0 + ci-info: ^3.2.0 + exit: ^0.1.2 + graceful-fs: ^4.2.9 + jest-changed-files: ^29.2.0 + jest-config: ^29.3.1 + jest-haste-map: ^29.3.1 + jest-message-util: ^29.3.1 + jest-regex-util: ^29.2.0 + jest-resolve: ^29.3.1 + jest-resolve-dependencies: ^29.3.1 + jest-runner: ^29.3.1 + jest-runtime: ^29.3.1 + jest-snapshot: ^29.3.1 + jest-util: ^29.3.1 + jest-validate: ^29.3.1 + jest-watcher: ^29.3.1 + micromatch: ^4.0.4 + pretty-format: ^29.3.1 + slash: ^3.0.0 + strip-ansi: ^6.0.0 + peerDependencies: + node-notifier: ^8.0.1 || ^9.0.0 || ^10.0.0 + peerDependenciesMeta: + node-notifier: + optional: true + checksum: e3ac9201e8a084ccd832b17877b56490402b919f227622bb24f9372931e77b869e60959d34144222ce20fb619d0a6a6be20b257adb077a6b0f430a4584a45b0f + languageName: node + linkType: hard + "@jest/environment@npm:^27.5.1": version: 27.5.1 resolution: "@jest/environment@npm:27.5.1" @@ -5676,6 +5731,18 @@ __metadata: languageName: node linkType: hard +"@jest/environment@npm:^29.3.1": + version: 29.3.1 + resolution: "@jest/environment@npm:29.3.1" + dependencies: + "@jest/fake-timers": ^29.3.1 + "@jest/types": ^29.3.1 + "@types/node": "*" + jest-mock: ^29.3.1 + checksum: 974102aba7cc80508f787bb5504dcc96e5392e0a7776a63dffbf54ddc2c77d52ef4a3c08ed2eedec91965befff873f70cd7c9ed56f62bb132dcdb821730e6076 + languageName: node + linkType: hard + "@jest/expect-utils@npm:^29.3.1": version: 29.3.1 resolution: "@jest/expect-utils@npm:29.3.1" @@ -5685,6 +5752,16 @@ __metadata: languageName: node linkType: hard +"@jest/expect@npm:^29.3.1": + version: 29.3.1 + resolution: "@jest/expect@npm:29.3.1" + dependencies: + expect: ^29.3.1 + jest-snapshot: ^29.3.1 + checksum: 1d7b5cc735c8a99bfbed884d80fdb43b23b3456f4ec88c50fd86404b097bb77fba84f44e707fc9b49f106ca1154ae03f7c54dc34754b03f8a54eeb420196e5bf + languageName: node + linkType: hard + "@jest/fake-timers@npm:^27.5.1": version: 27.5.1 resolution: "@jest/fake-timers@npm:27.5.1" @@ -5699,6 +5776,20 @@ __metadata: languageName: node linkType: hard +"@jest/fake-timers@npm:^29.3.1": + version: 29.3.1 + resolution: "@jest/fake-timers@npm:29.3.1" + dependencies: + "@jest/types": ^29.3.1 + "@sinonjs/fake-timers": ^9.1.2 + "@types/node": "*" + jest-message-util: ^29.3.1 + jest-mock: ^29.3.1 + jest-util: ^29.3.1 + checksum: b1dafa8cdc439ef428cd772c775f0b22703677f52615513eda11a104bbfc352d7ec69b1225db95d4ef2e1b4ef0f23e1a7d96de5313aeb0950f672e6548ae069d + languageName: node + linkType: hard + "@jest/globals@npm:^27.5.1": version: 27.5.1 resolution: "@jest/globals@npm:27.5.1" @@ -5710,6 +5801,18 @@ __metadata: languageName: node linkType: hard +"@jest/globals@npm:^29.3.1": + version: 29.3.1 + resolution: "@jest/globals@npm:29.3.1" + dependencies: + "@jest/environment": ^29.3.1 + "@jest/expect": ^29.3.1 + "@jest/types": ^29.3.1 + jest-mock: ^29.3.1 + checksum: 4d2b9458aabf7c28fd167e53984477498c897b64eec67a7f84b8fff465235cae1456ee0721cb0e7943f0cda443c7656adb9801f9f34e27495b8ebbd9f3033100 + languageName: node + linkType: hard + "@jest/reporters@npm:^27.5.1": version: 27.5.1 resolution: "@jest/reporters@npm:27.5.1" @@ -5748,6 +5851,43 @@ __metadata: languageName: node linkType: hard +"@jest/reporters@npm:^29.3.1": + version: 29.3.1 + resolution: "@jest/reporters@npm:29.3.1" + dependencies: + "@bcoe/v8-coverage": ^0.2.3 + "@jest/console": ^29.3.1 + "@jest/test-result": ^29.3.1 + "@jest/transform": ^29.3.1 + "@jest/types": ^29.3.1 + "@jridgewell/trace-mapping": ^0.3.15 + "@types/node": "*" + chalk: ^4.0.0 + collect-v8-coverage: ^1.0.0 + exit: ^0.1.2 + glob: ^7.1.3 + graceful-fs: ^4.2.9 + istanbul-lib-coverage: ^3.0.0 + istanbul-lib-instrument: ^5.1.0 + istanbul-lib-report: ^3.0.0 + istanbul-lib-source-maps: ^4.0.0 + istanbul-reports: ^3.1.3 + jest-message-util: ^29.3.1 + jest-util: ^29.3.1 + jest-worker: ^29.3.1 + slash: ^3.0.0 + string-length: ^4.0.1 + strip-ansi: ^6.0.0 + v8-to-istanbul: ^9.0.1 + peerDependencies: + node-notifier: ^8.0.1 || ^9.0.0 || ^10.0.0 + peerDependenciesMeta: + node-notifier: + optional: true + checksum: 273e0c6953285f01151e9d84ac1e55744802a1ec79fb62dafeea16a49adfe7b24e7f35bef47a0214e5e057272dbfdacf594208286b7766046fd0f3cfa2043840 + languageName: node + linkType: hard + "@jest/schemas@npm:^28.0.2": version: 28.0.2 resolution: "@jest/schemas@npm:28.0.2" @@ -5777,6 +5917,17 @@ __metadata: languageName: node linkType: hard +"@jest/source-map@npm:^29.2.0": + version: 29.2.0 + resolution: "@jest/source-map@npm:29.2.0" + dependencies: + "@jridgewell/trace-mapping": ^0.3.15 + callsites: ^3.0.0 + graceful-fs: ^4.2.9 + checksum: 09f76ab63d15dcf44b3035a79412164f43be34ec189575930f1a00c87e36ea0211ebd6a4fbe2253c2516e19b49b131f348ddbb86223ca7b6bbac9a6bc76ec96e + languageName: node + linkType: hard + "@jest/test-result@npm:^27.5.1": version: 27.5.1 resolution: "@jest/test-result@npm:27.5.1" @@ -5801,6 +5952,18 @@ __metadata: languageName: node linkType: hard +"@jest/test-result@npm:^29.3.1": + version: 29.3.1 + resolution: "@jest/test-result@npm:29.3.1" + dependencies: + "@jest/console": ^29.3.1 + "@jest/types": ^29.3.1 + "@types/istanbul-lib-coverage": ^2.0.0 + collect-v8-coverage: ^1.0.0 + checksum: b24ac283321189b624c372a6369c0674b0ee6d9e3902c213452c6334d037113718156b315364bee8cee0f03419c2bdff5e2c63967193fb422830e79cbb26866a + languageName: node + linkType: hard + "@jest/test-sequencer@npm:^27.5.1": version: 27.5.1 resolution: "@jest/test-sequencer@npm:27.5.1" @@ -5813,6 +5976,18 @@ __metadata: languageName: node linkType: hard +"@jest/test-sequencer@npm:^29.3.1": + version: 29.3.1 + resolution: "@jest/test-sequencer@npm:29.3.1" + dependencies: + "@jest/test-result": ^29.3.1 + graceful-fs: ^4.2.9 + jest-haste-map: ^29.3.1 + slash: ^3.0.0 + checksum: a8325b1ea0ce644486fb63bb67cedd3524d04e3d7b1e6c1e3562bf12ef477ecd0cf34044391b2a07d925e1c0c8b4e0f3285035ceca3a474a2c55980f1708caf3 + languageName: node + linkType: hard + "@jest/transform@npm:^26.6.2": version: 26.6.2 resolution: "@jest/transform@npm:26.6.2" @@ -6002,7 +6177,7 @@ __metadata: languageName: node linkType: hard -"@jridgewell/trace-mapping@npm:^0.3.14, @jridgewell/trace-mapping@npm:^0.3.15": +"@jridgewell/trace-mapping@npm:^0.3.12, @jridgewell/trace-mapping@npm:^0.3.14, @jridgewell/trace-mapping@npm:^0.3.15": version: 0.3.17 resolution: "@jridgewell/trace-mapping@npm:0.3.17" dependencies: @@ -6752,14 +6927,14 @@ __metadata: esbuild: ~0.17 esbuild-runner: ^2.2.1 husky: ^4.3.6 - jest: ^27 + jest: ^29 msw: ^0.40.2 openapi-types: ^9.1.0 prettier: ^2.2.1 pretty-quick: ^3.1.0 semver: ^7.3.5 swagger2openapi: ^7.0.4 - ts-jest: ^27 + ts-jest: ^29 ts-node: ^10.4.0 typescript: ">=4.1 <=4.5" yalc: ^1.0.0-pre.47 @@ -6935,6 +7110,15 @@ __metadata: languageName: node linkType: hard +"@sinonjs/fake-timers@npm:^9.1.2": + version: 9.1.2 + resolution: "@sinonjs/fake-timers@npm:9.1.2" + dependencies: + "@sinonjs/commons": ^1.7.0 + checksum: 7d3aef54e17c1073101cb64d953157c19d62a40e261a30923fa1ee337b049c5f29cc47b1f0c477880f42b5659848ba9ab897607ac8ea4acd5c30ddcfac57fca6 + languageName: node + linkType: hard + "@size-limit/file@npm:4.11.0": version: 4.11.0 resolution: "@size-limit/file@npm:4.11.0" @@ -9713,6 +9897,23 @@ __metadata: languageName: node linkType: hard +"babel-jest@npm:^29.3.1": + version: 29.3.1 + resolution: "babel-jest@npm:29.3.1" + dependencies: + "@jest/transform": ^29.3.1 + "@types/babel__core": ^7.1.14 + babel-plugin-istanbul: ^6.1.1 + babel-preset-jest: ^29.2.0 + chalk: ^4.0.0 + graceful-fs: ^4.2.9 + slash: ^3.0.0 + peerDependencies: + "@babel/core": ^7.8.0 + checksum: 793848238a771a931ddeb5930b9ec8ab800522ac8d64933665698f4a39603d157e572e20b57d79610277e1df88d3ee82b180d59a21f3570388f602beeb38a595 + languageName: node + linkType: hard + "babel-loader@npm:^8.2.3, babel-loader@npm:^8.2.5": version: 8.2.5 resolution: "babel-loader@npm:8.2.5" @@ -9795,6 +9996,18 @@ __metadata: languageName: node linkType: hard +"babel-plugin-jest-hoist@npm:^29.2.0": + version: 29.2.0 + resolution: "babel-plugin-jest-hoist@npm:29.2.0" + dependencies: + "@babel/template": ^7.3.3 + "@babel/types": ^7.3.3 + "@types/babel__core": ^7.1.14 + "@types/babel__traverse": ^7.0.6 + checksum: 368d271ceae491ae6b96cd691434859ea589fbe5fd5aead7660df75d02394077273c6442f61f390e9347adffab57a32b564d0fabcf1c53c4b83cd426cb644072 + languageName: node + linkType: hard + "babel-plugin-macros@npm:^2.6.1": version: 2.8.0 resolution: "babel-plugin-macros@npm:2.8.0" @@ -10013,6 +10226,18 @@ __metadata: languageName: node linkType: hard +"babel-preset-jest@npm:^29.2.0": + version: 29.2.0 + resolution: "babel-preset-jest@npm:29.2.0" + dependencies: + babel-plugin-jest-hoist: ^29.2.0 + babel-preset-current-node-syntax: ^1.0.0 + peerDependencies: + "@babel/core": ^7.0.0 + checksum: 1b09a2db968c36e064daf98082cfffa39c849b63055112ddc56fc2551fd0d4783897265775b1d2f8a257960a3339745de92e74feb01bad86d41c4cecbfa854fc + languageName: node + linkType: hard + "babel-preset-react-app@npm:^10.0.1": version: 10.0.1 resolution: "babel-preset-react-app@npm:10.0.1" @@ -13372,6 +13597,13 @@ __metadata: languageName: node linkType: hard +"emittery@npm:^0.13.1": + version: 0.13.1 + resolution: "emittery@npm:0.13.1" + checksum: 2b089ab6306f38feaabf4f6f02792f9ec85fc054fda79f44f6790e61bbf6bc4e1616afb9b232e0c5ec5289a8a452f79bfa6d905a6fd64e94b49981f0934001c6 + languageName: node + linkType: hard + "emittery@npm:^0.8.1": version: 0.8.1 resolution: "emittery@npm:0.8.1" @@ -17565,6 +17797,16 @@ fsevents@^1.2.7: languageName: node linkType: hard +"jest-changed-files@npm:^29.2.0": + version: 29.2.0 + resolution: "jest-changed-files@npm:29.2.0" + dependencies: + execa: ^5.0.0 + p-limit: ^3.1.0 + checksum: 8ad8290324db1de2ee3c9443d3e3fbfdcb6d72ec7054c5796be2854b2bc239dea38a7c797c8c9c2bd959f539d44305790f2f75b18f3046b04317ed77c7480cb1 + languageName: node + linkType: hard + "jest-circus@npm:^27.5.1": version: 27.5.1 resolution: "jest-circus@npm:27.5.1" @@ -17592,6 +17834,33 @@ fsevents@^1.2.7: languageName: node linkType: hard +"jest-circus@npm:^29.3.1": + version: 29.3.1 + resolution: "jest-circus@npm:29.3.1" + dependencies: + "@jest/environment": ^29.3.1 + "@jest/expect": ^29.3.1 + "@jest/test-result": ^29.3.1 + "@jest/types": ^29.3.1 + "@types/node": "*" + chalk: ^4.0.0 + co: ^4.6.0 + dedent: ^0.7.0 + is-generator-fn: ^2.0.0 + jest-each: ^29.3.1 + jest-matcher-utils: ^29.3.1 + jest-message-util: ^29.3.1 + jest-runtime: ^29.3.1 + jest-snapshot: ^29.3.1 + jest-util: ^29.3.1 + p-limit: ^3.1.0 + pretty-format: ^29.3.1 + slash: ^3.0.0 + stack-utils: ^2.0.3 + checksum: 125710debd998ad9693893e7c1235e271b79f104033b8169d82afe0bc0d883f8f5245feef87adcbb22ad27ff749fd001aa998d11a132774b03b4e2b8af77d5d8 + languageName: node + linkType: hard + "jest-cli@npm:^27.5.1": version: 27.5.1 resolution: "jest-cli@npm:27.5.1" @@ -17619,6 +17888,33 @@ fsevents@^1.2.7: languageName: node linkType: hard +"jest-cli@npm:^29.3.1": + version: 29.3.1 + resolution: "jest-cli@npm:29.3.1" + dependencies: + "@jest/core": ^29.3.1 + "@jest/test-result": ^29.3.1 + "@jest/types": ^29.3.1 + chalk: ^4.0.0 + exit: ^0.1.2 + graceful-fs: ^4.2.9 + import-local: ^3.0.2 + jest-config: ^29.3.1 + jest-util: ^29.3.1 + jest-validate: ^29.3.1 + prompts: ^2.0.1 + yargs: ^17.3.1 + peerDependencies: + node-notifier: ^8.0.1 || ^9.0.0 || ^10.0.0 + peerDependenciesMeta: + node-notifier: + optional: true + bin: + jest: bin/jest.js + checksum: 829895d33060042443bd1e9e87eb68993773d74f2c8a9b863acf53cece39d227ae0e7d76df2e9c5934c414bdf70ce398a34b3122cfe22164acb2499a74d7288d + languageName: node + linkType: hard + "jest-config@npm:^27.5.1": version: 27.5.1 resolution: "jest-config@npm:27.5.1" @@ -17656,6 +17952,44 @@ fsevents@^1.2.7: languageName: node linkType: hard +"jest-config@npm:^29.3.1": + version: 29.3.1 + resolution: "jest-config@npm:29.3.1" + dependencies: + "@babel/core": ^7.11.6 + "@jest/test-sequencer": ^29.3.1 + "@jest/types": ^29.3.1 + babel-jest: ^29.3.1 + chalk: ^4.0.0 + ci-info: ^3.2.0 + deepmerge: ^4.2.2 + glob: ^7.1.3 + graceful-fs: ^4.2.9 + jest-circus: ^29.3.1 + jest-environment-node: ^29.3.1 + jest-get-type: ^29.2.0 + jest-regex-util: ^29.2.0 + jest-resolve: ^29.3.1 + jest-runner: ^29.3.1 + jest-util: ^29.3.1 + jest-validate: ^29.3.1 + micromatch: ^4.0.4 + parse-json: ^5.2.0 + pretty-format: ^29.3.1 + slash: ^3.0.0 + strip-json-comments: ^3.1.1 + peerDependencies: + "@types/node": "*" + ts-node: ">=9.0.0" + peerDependenciesMeta: + "@types/node": + optional: true + ts-node: + optional: true + checksum: 6e663f04ae1024a53a4c2c744499b4408ca9a8b74381dd5e31b11bb3c7393311ecff0fb61b06287768709eb2c9e5a2fd166d258f5a9123abbb4c5812f99c12fe + languageName: node + linkType: hard + "jest-diff@npm:^26.0.0": version: 26.6.2 resolution: "jest-diff@npm:26.6.2" @@ -17701,6 +18035,15 @@ fsevents@^1.2.7: languageName: node linkType: hard +"jest-docblock@npm:^29.2.0": + version: 29.2.0 + resolution: "jest-docblock@npm:29.2.0" + dependencies: + detect-newline: ^3.0.0 + checksum: b3f1227b7d73fc9e4952180303475cf337b36fa65c7f730ac92f0580f1c08439983262fee21cf3dba11429aa251b4eee1e3bc74796c5777116b400d78f9d2bbe + languageName: node + linkType: hard + "jest-each@npm:^27.5.1": version: 27.5.1 resolution: "jest-each@npm:27.5.1" @@ -17714,6 +18057,19 @@ fsevents@^1.2.7: languageName: node linkType: hard +"jest-each@npm:^29.3.1": + version: 29.3.1 + resolution: "jest-each@npm:29.3.1" + dependencies: + "@jest/types": ^29.3.1 + chalk: ^4.0.0 + jest-get-type: ^29.2.0 + jest-util: ^29.3.1 + pretty-format: ^29.3.1 + checksum: 16d51ef8f96fba44a3479f1c6f7672027e3b39236dc4e41217c38fe60a3b66b022ffcee72f8835a442f7a8a0a65980a93fb8e73a9782d192452526e442ad049a + languageName: node + linkType: hard + "jest-environment-jsdom@npm:^27.5.1": version: 27.5.1 resolution: "jest-environment-jsdom@npm:27.5.1" @@ -17743,6 +18099,20 @@ fsevents@^1.2.7: languageName: node linkType: hard +"jest-environment-node@npm:^29.3.1": + version: 29.3.1 + resolution: "jest-environment-node@npm:29.3.1" + dependencies: + "@jest/environment": ^29.3.1 + "@jest/fake-timers": ^29.3.1 + "@jest/types": ^29.3.1 + "@types/node": "*" + jest-mock: ^29.3.1 + jest-util: ^29.3.1 + checksum: 16d4854bd2d35501bd4862ca069baf27ce9f5fd7642fdcab9d2dab49acd28c082d0c8882bf2bb28ed7bbaada486da577c814c9688ddc62d1d9f74a954fde996a + languageName: node + linkType: hard + "jest-get-type@npm:^26.3.0": version: 26.3.0 resolution: "jest-get-type@npm:26.3.0" @@ -17871,6 +18241,16 @@ fsevents@^1.2.7: languageName: node linkType: hard +"jest-leak-detector@npm:^29.3.1": + version: 29.3.1 + resolution: "jest-leak-detector@npm:29.3.1" + dependencies: + jest-get-type: ^29.2.0 + pretty-format: ^29.3.1 + checksum: 0dd8ed31ae0b5a3d14f13f567ca8567f2663dd2d540d1e55511d3b3fd7f80a1d075392179674ebe9fab9be0b73678bf4d2f8bbbc0f4bdd52b9815259194da559 + languageName: node + linkType: hard + "jest-matcher-utils@npm:^27.0.0, jest-matcher-utils@npm:^27.5.1": version: 27.5.1 resolution: "jest-matcher-utils@npm:27.5.1" @@ -17956,6 +18336,17 @@ fsevents@^1.2.7: languageName: node linkType: hard +"jest-mock@npm:^29.3.1": + version: 29.3.1 + resolution: "jest-mock@npm:29.3.1" + dependencies: + "@jest/types": ^29.3.1 + "@types/node": "*" + jest-util: ^29.3.1 + checksum: 9098852cb2866db4a1a59f9f7581741dfc572f648e9e574a1b187fd69f5f2f6190ad387ede21e139a8b80a6a1343ecc3d6751cd2ae1ae11d7ea9fa1950390fb2 + languageName: node + linkType: hard + "jest-pnp-resolver@npm:^1.2.2": version: 1.2.2 resolution: "jest-pnp-resolver@npm:1.2.2" @@ -18007,6 +18398,16 @@ fsevents@^1.2.7: languageName: node linkType: hard +"jest-resolve-dependencies@npm:^29.3.1": + version: 29.3.1 + resolution: "jest-resolve-dependencies@npm:29.3.1" + dependencies: + jest-regex-util: ^29.2.0 + jest-snapshot: ^29.3.1 + checksum: 6ec4727a87c6e7954e93de9949ab9967b340ee2f07626144c273355f05a2b65fa47eb8dece2d6e5f4fd99cdb893510a3540aa5e14ba443f70b3feb63f6f98982 + languageName: node + linkType: hard + "jest-resolve@npm:^27.4.2, jest-resolve@npm:^27.5.1": version: 27.5.1 resolution: "jest-resolve@npm:27.5.1" @@ -18025,6 +18426,23 @@ fsevents@^1.2.7: languageName: node linkType: hard +"jest-resolve@npm:^29.3.1": + version: 29.3.1 + resolution: "jest-resolve@npm:29.3.1" + dependencies: + chalk: ^4.0.0 + graceful-fs: ^4.2.9 + jest-haste-map: ^29.3.1 + jest-pnp-resolver: ^1.2.2 + jest-util: ^29.3.1 + jest-validate: ^29.3.1 + resolve: ^1.20.0 + resolve.exports: ^1.1.0 + slash: ^3.0.0 + checksum: 0dea22ed625e07b8bfee52dea1391d3a4b453c1a0c627a0fa7c22e44bb48e1c289afe6f3c316def70753773f099c4e8f436c7a2cc12fcc6c7dd6da38cba2cd5f + languageName: node + linkType: hard + "jest-runner@npm:^27.5.1": version: 27.5.1 resolution: "jest-runner@npm:27.5.1" @@ -18054,6 +18472,35 @@ fsevents@^1.2.7: languageName: node linkType: hard +"jest-runner@npm:^29.3.1": + version: 29.3.1 + resolution: "jest-runner@npm:29.3.1" + dependencies: + "@jest/console": ^29.3.1 + "@jest/environment": ^29.3.1 + "@jest/test-result": ^29.3.1 + "@jest/transform": ^29.3.1 + "@jest/types": ^29.3.1 + "@types/node": "*" + chalk: ^4.0.0 + emittery: ^0.13.1 + graceful-fs: ^4.2.9 + jest-docblock: ^29.2.0 + jest-environment-node: ^29.3.1 + jest-haste-map: ^29.3.1 + jest-leak-detector: ^29.3.1 + jest-message-util: ^29.3.1 + jest-resolve: ^29.3.1 + jest-runtime: ^29.3.1 + jest-util: ^29.3.1 + jest-watcher: ^29.3.1 + jest-worker: ^29.3.1 + p-limit: ^3.1.0 + source-map-support: 0.5.13 + checksum: 61ad445d8a5f29573332f27a21fc942fb0d2a82bf901a0ea1035bf3bd7f349d1e425f71f54c3a3f89b292a54872c3248d395a2829d987f26b6025b15530ea5d2 + languageName: node + linkType: hard + "jest-runtime@npm:^27.5.1": version: 27.5.1 resolution: "jest-runtime@npm:27.5.1" @@ -18084,6 +18531,36 @@ fsevents@^1.2.7: languageName: node linkType: hard +"jest-runtime@npm:^29.3.1": + version: 29.3.1 + resolution: "jest-runtime@npm:29.3.1" + dependencies: + "@jest/environment": ^29.3.1 + "@jest/fake-timers": ^29.3.1 + "@jest/globals": ^29.3.1 + "@jest/source-map": ^29.2.0 + "@jest/test-result": ^29.3.1 + "@jest/transform": ^29.3.1 + "@jest/types": ^29.3.1 + "@types/node": "*" + chalk: ^4.0.0 + cjs-module-lexer: ^1.0.0 + collect-v8-coverage: ^1.0.0 + glob: ^7.1.3 + graceful-fs: ^4.2.9 + jest-haste-map: ^29.3.1 + jest-message-util: ^29.3.1 + jest-mock: ^29.3.1 + jest-regex-util: ^29.2.0 + jest-resolve: ^29.3.1 + jest-snapshot: ^29.3.1 + jest-util: ^29.3.1 + slash: ^3.0.0 + strip-bom: ^4.0.0 + checksum: 82f27b48f000be074064a854e16e768f9453e9b791d8c5f9316606c37f871b5b10f70544c1b218ab9784f00bd972bb77f868c5ab6752c275be2cd219c351f5a7 + languageName: node + linkType: hard + "jest-serializer@npm:^26.6.2": version: 26.6.2 resolution: "jest-serializer@npm:26.6.2" @@ -18178,7 +18655,7 @@ fsevents@^1.2.7: languageName: node linkType: hard -"jest-util@npm:^29.3.1": +"jest-util@npm:^29.0.0, jest-util@npm:^29.3.1": version: 29.3.1 resolution: "jest-util@npm:29.3.1" dependencies: @@ -18206,6 +18683,20 @@ fsevents@^1.2.7: languageName: node linkType: hard +"jest-validate@npm:^29.3.1": + version: 29.3.1 + resolution: "jest-validate@npm:29.3.1" + dependencies: + "@jest/types": ^29.3.1 + camelcase: ^6.2.0 + chalk: ^4.0.0 + jest-get-type: ^29.2.0 + leven: ^3.1.0 + pretty-format: ^29.3.1 + checksum: 92584f0b8ac284235f12b3b812ccbc43ef6dea080a3b98b1aa81adbe009e962d0aa6131f21c8157b30ac3d58f335961694238a93d553d1d1e02ab264c923778c + languageName: node + linkType: hard + "jest-watch-typeahead@npm:^1.0.0, jest-watch-typeahead@npm:^1.1.0": version: 1.1.0 resolution: "jest-watch-typeahead@npm:1.1.0" @@ -18254,6 +18745,22 @@ fsevents@^1.2.7: languageName: node linkType: hard +"jest-watcher@npm:^29.3.1": + version: 29.3.1 + resolution: "jest-watcher@npm:29.3.1" + dependencies: + "@jest/test-result": ^29.3.1 + "@jest/types": ^29.3.1 + "@types/node": "*" + ansi-escapes: ^4.2.1 + chalk: ^4.0.0 + emittery: ^0.13.1 + jest-util: ^29.3.1 + string-length: ^4.0.1 + checksum: 60d189473486c73e9d540406a30189da5a3c67bfb0fb4ad4a83991c189135ef76d929ec99284ca5a505fe4ee9349ae3c99b54d2e00363e72837b46e77dec9642 + languageName: node + linkType: hard + "jest-worker@npm:^26.2.1, jest-worker@npm:^26.6.2": version: 26.6.2 resolution: "jest-worker@npm:26.6.2" @@ -18306,6 +18813,25 @@ fsevents@^1.2.7: languageName: node linkType: hard +"jest@npm:^29": + version: 29.3.1 + resolution: "jest@npm:29.3.1" + dependencies: + "@jest/core": ^29.3.1 + "@jest/types": ^29.3.1 + import-local: ^3.0.2 + jest-cli: ^29.3.1 + peerDependencies: + node-notifier: ^8.0.1 || ^9.0.0 || ^10.0.0 + peerDependenciesMeta: + node-notifier: + optional: true + bin: + jest: bin/jest.js + checksum: 613f4ec657b14dd84c0056b2fef1468502927fd551bef0b19d4a91576a609678fb316c6a5b5fc6120dd30dd4ff4569070ffef3cb507db9bb0260b28ddaa18d7a + languageName: node + linkType: hard + "jju@npm:~1.4.0": version: 1.4.0 resolution: "jju@npm:1.4.0" @@ -18593,6 +19119,15 @@ fsevents@^1.2.7: languageName: node linkType: hard +"json5@npm:^2.2.3": + version: 2.2.3 + resolution: "json5@npm:2.2.3" + bin: + json5: lib/cli.js + checksum: 2a7436a93393830bce797d4626275152e37e877b265e94ca69c99e3d20c2b9dab021279146a39cdb700e71b2dd32a4cebd1514cd57cee102b1af906ce5040349 + languageName: node + linkType: hard + "jsonc-parser@npm:^3.2.0": version: 3.2.0 resolution: "jsonc-parser@npm:3.2.0" @@ -20879,7 +21414,7 @@ fsevents@^1.2.7: languageName: node linkType: hard -"p-limit@npm:3.1.0, p-limit@npm:^3.0.2": +"p-limit@npm:3.1.0, p-limit@npm:^3.0.2, p-limit@npm:^3.1.0": version: 3.1.0 resolution: "p-limit@npm:3.1.0" dependencies: @@ -25635,6 +26170,16 @@ fsevents@^1.2.7: languageName: node linkType: hard +"source-map-support@npm:0.5.13": + version: 0.5.13 + resolution: "source-map-support@npm:0.5.13" + dependencies: + buffer-from: ^1.0.0 + source-map: ^0.6.0 + checksum: 933550047b6c1a2328599a21d8b7666507427c0f5ef5eaadd56b5da0fd9505e239053c66fe181bf1df469a3b7af9d775778eee283cbb7ae16b902ddc09e93a97 + languageName: node + linkType: hard + "source-map-support@npm:0.5.19": version: 0.5.19 resolution: "source-map-support@npm:0.5.19" @@ -27055,6 +27600,39 @@ fsevents@^1.2.7: languageName: node linkType: hard +"ts-jest@npm:^29": + version: 29.0.5 + resolution: "ts-jest@npm:29.0.5" + dependencies: + bs-logger: 0.x + fast-json-stable-stringify: 2.x + jest-util: ^29.0.0 + json5: ^2.2.3 + lodash.memoize: 4.x + make-error: 1.x + semver: 7.x + yargs-parser: ^21.0.1 + peerDependencies: + "@babel/core": ">=7.0.0-beta.0 <8" + "@jest/types": ^29.0.0 + babel-jest: ^29.0.0 + jest: ^29.0.0 + typescript: ">=4.3" + peerDependenciesMeta: + "@babel/core": + optional: true + "@jest/types": + optional: true + babel-jest: + optional: true + esbuild: + optional: true + bin: + ts-jest: cli.js + checksum: f60f129c2287f4c963d9ee2677132496c5c5a5d39c27ad234199a1140c26318a7d5bda34890ab0e30636ec42a8de28f84487c09e9dcec639c9c67812b3a38373 + languageName: node + linkType: hard + "ts-log@npm:^2.2.3": version: 2.2.3 resolution: "ts-log@npm:2.2.3" @@ -27974,6 +28552,17 @@ fsevents@^1.2.7: languageName: node linkType: hard +"v8-to-istanbul@npm:^9.0.1": + version: 9.0.1 + resolution: "v8-to-istanbul@npm:9.0.1" + dependencies: + "@jridgewell/trace-mapping": ^0.3.12 + "@types/istanbul-lib-coverage": ^2.0.1 + convert-source-map: ^1.6.0 + checksum: a49c34bf0a3af0c11041a3952a2600913904a983bd1bc87148b5c033bc5c1d02d5a13620fcdbfa2c60bc582a2e2970185780f0c844b4c3a220abf405f8af6311 + languageName: node + linkType: hard + "valid-url@npm:1.0.9, valid-url@npm:^1.0.9": version: 1.0.9 resolution: "valid-url@npm:1.0.9" @@ -29345,6 +29934,13 @@ fsevents@^1.2.7: languageName: node linkType: hard +"yargs-parser@npm:^21.0.1": + version: 21.1.1 + resolution: "yargs-parser@npm:21.1.1" + checksum: ed2d96a616a9e3e1cc7d204c62ecc61f7aaab633dcbfab2c6df50f7f87b393993fe6640d017759fe112d0cb1e0119f2b4150a87305cc873fd90831c6a58ccf1c + languageName: node + linkType: hard + "yargs@npm:^15.3.1": version: 15.4.1 resolution: "yargs@npm:15.4.1" From 61e6fe990e998069423adfdf115c68e991f41de8 Mon Sep 17 00:00:00 2001 From: Mark Erikson Date: Wed, 18 Jan 2023 22:54:11 -0500 Subject: [PATCH 024/412] Update codegen-openapi settings --- packages/rtk-query-codegen-openapi/jest.config.js | 3 ++- packages/rtk-query-codegen-openapi/test/cli.test.ts | 2 +- packages/rtk-query-codegen-openapi/test/jest.setup.ts | 1 + packages/rtk-query-codegen-openapi/test/tsconfig.json | 1 + 4 files changed, 5 insertions(+), 2 deletions(-) diff --git a/packages/rtk-query-codegen-openapi/jest.config.js b/packages/rtk-query-codegen-openapi/jest.config.js index 4632bc4128..1be66fe00b 100644 --- a/packages/rtk-query-codegen-openapi/jest.config.js +++ b/packages/rtk-query-codegen-openapi/jest.config.js @@ -1,12 +1,13 @@ const { resolve } = require('path'); -const tsConfigPath = resolve('./test/tsconfig'); +const tsConfigPath = resolve('./test/tsconfig.json'); /** @typedef {import('ts-jest/dist/types')} */ /** @type {import('@jest/types').Config.InitialOptions} */ const config = { rootDir: './test', setupFilesAfterEnv: ['/jest.setup.ts'], + preset: 'ts-jest', globals: { 'ts-jest': { tsconfig: tsConfigPath, diff --git a/packages/rtk-query-codegen-openapi/test/cli.test.ts b/packages/rtk-query-codegen-openapi/test/cli.test.ts index 691a38e442..c6f25aaae0 100644 --- a/packages/rtk-query-codegen-openapi/test/cli.test.ts +++ b/packages/rtk-query-codegen-openapi/test/cli.test.ts @@ -70,7 +70,7 @@ Done expect(fromTs).toEqual(fromJs); expect(fromJson).toEqual(fromJs); - }, 25000); + }, 120000); test('missing parameters doesnt fail', async () => { const out = await cli([`./config.invalid-example.json`], __dirname); diff --git a/packages/rtk-query-codegen-openapi/test/jest.setup.ts b/packages/rtk-query-codegen-openapi/test/jest.setup.ts index 464008fbc3..daf436b230 100644 --- a/packages/rtk-query-codegen-openapi/test/jest.setup.ts +++ b/packages/rtk-query-codegen-openapi/test/jest.setup.ts @@ -1,3 +1,4 @@ +// @ts-ignore global.fetch = require('node-fetch'); const { format } = require('prettier'); const { server } = require('./mocks/server'); diff --git a/packages/rtk-query-codegen-openapi/test/tsconfig.json b/packages/rtk-query-codegen-openapi/test/tsconfig.json index 78e8e8d37e..499c1c5631 100644 --- a/packages/rtk-query-codegen-openapi/test/tsconfig.json +++ b/packages/rtk-query-codegen-openapi/test/tsconfig.json @@ -1,5 +1,6 @@ { "compilerOptions": { + "lib": ["es2019"], "paths": { "@/*": ["./test/fixtures/*"], "@rtk-query/codegen-openapi": ["./src"] From c98d4ec9f6e3ddae5140cd3fa42da6859d70fd8c Mon Sep 17 00:00:00 2001 From: Mark Erikson Date: Wed, 18 Jan 2023 23:25:14 -0500 Subject: [PATCH 025/412] Replace entity inline snapshots with objects --- .../tests/sorted_state_adapter.test.ts | 327 +++++++---------- .../tests/unsorted_state_adapter.test.ts | 333 +++++++----------- 2 files changed, 266 insertions(+), 394 deletions(-) diff --git a/packages/toolkit/src/entities/tests/sorted_state_adapter.test.ts b/packages/toolkit/src/entities/tests/sorted_state_adapter.test.ts index cbb5a702a3..c501d69a34 100644 --- a/packages/toolkit/src/entities/tests/sorted_state_adapter.test.ts +++ b/packages/toolkit/src/entities/tests/sorted_state_adapter.test.ts @@ -590,19 +590,10 @@ describe('Sorted State Adapter', () => { const result = createNextState(withTwo, (draft) => { adapter.removeAll(draft) }) - expect(result).toMatchInlineSnapshot(` - { - "entities": { - "tgg": { - "id": "tgg", - "title": "The Great Gatsby", - }, - }, - "ids": [ - "tgg", - ], - } - `) + expect(result).toEqual({ + entities: {}, + ids: [], + }) }) test('addOne', () => { @@ -610,24 +601,15 @@ describe('Sorted State Adapter', () => { adapter.addOne(draft, TheGreatGatsby) }) - expect(result).toMatchInlineSnapshot(` - { - "entities": { - "af": { - "id": "af", - "title": "Animal Farm", - }, - "tgg": { - "id": "tgg", - "title": "The Great Gatsby", - }, + expect(result).toEqual({ + entities: { + tgg: { + id: 'tgg', + title: 'The Great Gatsby', }, - "ids": [ - "af", - "tgg", - ], - } - `) + }, + ids: ['tgg'], + }) }) test('addMany', () => { @@ -635,24 +617,19 @@ describe('Sorted State Adapter', () => { adapter.addMany(draft, [TheGreatGatsby, AnimalFarm]) }) - expect(result).toMatchInlineSnapshot(` - { - "entities": { - "af": { - "id": "af", - "title": "Animal Farm", - }, - "tgg": { - "id": "tgg", - "title": "The Great Gatsby", - }, + expect(result).toEqual({ + entities: { + af: { + id: 'af', + title: 'Animal Farm', + }, + tgg: { + id: 'tgg', + title: 'The Great Gatsby', }, - "ids": [ - "af", - "tgg", - ], - } - `) + }, + ids: ['af', 'tgg'], + }) }) test('setAll', () => { @@ -660,24 +637,19 @@ describe('Sorted State Adapter', () => { adapter.setAll(draft, [TheGreatGatsby, AnimalFarm]) }) - expect(result).toMatchInlineSnapshot(` - Object { - "entities": Object { - "af": Object { - "id": "af", - "title": "Animal Farm", - }, - "tgg": Object { - "id": "tgg", - "title": "The Great Gatsby", - }, + expect(result).toEqual({ + entities: { + af: { + id: 'af', + title: 'Animal Farm', + }, + tgg: { + id: 'tgg', + title: 'The Great Gatsby', }, - "ids": Array [ - "af", - "tgg", - ], - } - `) + }, + ids: ['af', 'tgg'], + }) }) test('updateOne', () => { @@ -690,19 +662,15 @@ describe('Sorted State Adapter', () => { }) }) - expect(result).toMatchInlineSnapshot(` - { - "entities": { - "tgg": { - "id": "tgg", - "title": "A New Hope", - }, + expect(result).toEqual({ + entities: { + tgg: { + id: 'tgg', + title: 'A New Hope', }, - "ids": [ - "tgg", - ], - } - `) + }, + ids: ['tgg'], + }) }) test('updateMany', () => { @@ -725,38 +693,39 @@ describe('Sorted State Adapter', () => { ]) }) - expect(result).toMatchInlineSnapshot(` - { - "entities": { - "tgg": { - "id": "tgg", - "title": "The Great Gatsby", - }, + expect(result).toEqual({ + entities: { + aco: { + id: 'aco', + title: 'Third Change', + }, + tgg: { + id: 'tgg', + title: 'Second Change', + }, + th: { + author: 'Fourth Change', + id: 'th', + title: 'First Change', }, - "ids": [ - "tgg", - ], - } - `) + }, + ids: ['th', 'tgg', 'aco'], + }) }) test('upsertOne (insert)', () => { const result = createNextState(state, (draft) => { adapter.upsertOne(draft, TheGreatGatsby) }) - expect(result).toMatchInlineSnapshot(` - { - "entities": { - "tgg": { - "id": "tgg", - "title": "A New Hope", - }, + expect(result).toEqual({ + entities: { + tgg: { + id: 'tgg', + title: 'The Great Gatsby', }, - "ids": [ - "tgg", - ], - } - `) + }, + ids: ['tgg'], + }) }) test('upsertOne (update)', () => { @@ -767,24 +736,15 @@ describe('Sorted State Adapter', () => { title: 'A New Hope', }) }) - expect(result).toMatchInlineSnapshot(` - { - "entities": { - "af": { - "id": "af", - "title": "Animal Farm", - }, - "tgg": { - "id": "tgg", - "title": "A New Hope", - }, + expect(result).toEqual({ + entities: { + tgg: { + id: 'tgg', + title: 'A New Hope', }, - "ids": [ - "tgg", - "af", - ], - } - `) + }, + ids: ['tgg'], + }) }) test('upsertMany', () => { @@ -798,38 +758,34 @@ describe('Sorted State Adapter', () => { AnimalFarm, ]) }) - expect(result).toMatchInlineSnapshot(` - { - "entities": { - "tgg": { - "id": "tgg", - "title": "The Great Gatsby", - }, + expect(result).toEqual({ + entities: { + af: { + id: 'af', + title: 'Animal Farm', }, - "ids": [ - "tgg", - ], - } - `) + tgg: { + id: 'tgg', + title: 'A New Hope', + }, + }, + ids: ['tgg', 'af'], + }) }) test('setOne (insert)', () => { const result = createNextState(state, (draft) => { adapter.setOne(draft, TheGreatGatsby) }) - expect(result).toMatchInlineSnapshot(` - { - "entities": { - "th": { - "id": "th", - "title": "Silmarillion", - }, + expect(result).toEqual({ + entities: { + tgg: { + id: 'tgg', + title: 'The Great Gatsby', }, - "ids": [ - "th", - ], - } - `) + }, + ids: ['tgg'], + }) }) test('setOne (update)', () => { @@ -840,24 +796,15 @@ describe('Sorted State Adapter', () => { title: 'Silmarillion', }) }) - expect(result).toMatchInlineSnapshot(` - { - "entities": { - "af": { - "id": "af", - "title": "Animal Farm", - }, - "th": { - "id": "th", - "title": "Silmarillion", - }, + expect(result).toEqual({ + entities: { + th: { + id: 'th', + title: 'Silmarillion', }, - "ids": [ - "af", - "th", - ], - } - `) + }, + ids: ['th'], + }) }) test('setMany', () => { @@ -871,19 +818,19 @@ describe('Sorted State Adapter', () => { AnimalFarm, ]) }) - expect(result).toMatchInlineSnapshot(` - { - "entities": { - "af": { - "id": "af", - "title": "Animal Farm", - }, + expect(result).toEqual({ + entities: { + af: { + id: 'af', + title: 'Animal Farm', + }, + th: { + id: 'th', + title: 'Silmarillion', }, - "ids": [ - "af", - ], - } - `) + }, + ids: ['af', 'th'], + }) }) test('removeOne', () => { @@ -891,19 +838,15 @@ describe('Sorted State Adapter', () => { const result = createNextState(withTwo, (draft) => { adapter.removeOne(draft, TheGreatGatsby.id) }) - expect(result).toMatchInlineSnapshot(` - { - "entities": { - "aco": { - "id": "aco", - "title": "A Clockwork Orange", - }, + expect(result).toEqual({ + entities: { + af: { + id: 'af', + title: 'Animal Farm', }, - "ids": [ - "aco", - ], - } - `) + }, + ids: ['af'], + }) }) test('removeMany', () => { @@ -915,19 +858,15 @@ describe('Sorted State Adapter', () => { const result = createNextState(withThree, (draft) => { adapter.removeMany(draft, [TheGreatGatsby.id, AnimalFarm.id]) }) - expect(result).toMatchInlineSnapshot(` - Object { - "entities": Object { - "aco": Object { - "id": "aco", - "title": "A Clockwork Orange", - }, + expect(result).toEqual({ + entities: { + aco: { + id: 'aco', + title: 'A Clockwork Orange', }, - "ids": Array [ - "aco", - ], - } - `) + }, + ids: ['aco'], + }) }) }) }) diff --git a/packages/toolkit/src/entities/tests/unsorted_state_adapter.test.ts b/packages/toolkit/src/entities/tests/unsorted_state_adapter.test.ts index bf1a304b2e..13c49cf0ec 100644 --- a/packages/toolkit/src/entities/tests/unsorted_state_adapter.test.ts +++ b/packages/toolkit/src/entities/tests/unsorted_state_adapter.test.ts @@ -435,19 +435,10 @@ describe('Unsorted State Adapter', () => { const result = createNextState(withTwo, (draft) => { adapter.removeAll(draft) }) - expect(result).toMatchInlineSnapshot(` - { - "entities": { - "tgg": { - "id": "tgg", - "title": "The Great Gatsby", - }, - }, - "ids": [ - "tgg", - ], - } - `) + expect(result).toEqual({ + entities: {}, + ids: [], + }) }) test('addOne', () => { @@ -455,19 +446,15 @@ describe('Unsorted State Adapter', () => { adapter.addOne(draft, TheGreatGatsby) }) - expect(result).toMatchInlineSnapshot(` - Object { - "entities": Object { - "tgg": Object { - "id": "tgg", - "title": "The Great Gatsby", - }, + expect(result).toEqual({ + entities: { + tgg: { + id: 'tgg', + title: 'The Great Gatsby', }, - "ids": Array [ - "tgg", - ], - } - `) + }, + ids: ['tgg'], + }) }) test('addMany', () => { @@ -475,24 +462,19 @@ describe('Unsorted State Adapter', () => { adapter.addMany(draft, [TheGreatGatsby, AnimalFarm]) }) - expect(result).toMatchInlineSnapshot(` - { - "entities": { - "af": { - "id": "af", - "title": "Animal Farm", - }, - "tgg": { - "id": "tgg", - "title": "The Great Gatsby", - }, + expect(result).toEqual({ + entities: { + af: { + id: 'af', + title: 'Animal Farm', + }, + tgg: { + id: 'tgg', + title: 'The Great Gatsby', }, - "ids": [ - "tgg", - "af", - ], - } - `) + }, + ids: ['tgg', 'af'], + }) }) test('setAll', () => { @@ -500,24 +482,19 @@ describe('Unsorted State Adapter', () => { adapter.setAll(draft, [TheGreatGatsby, AnimalFarm]) }) - expect(result).toMatchInlineSnapshot(` - { - "entities": { - "af": { - "id": "af", - "title": "Animal Farm", - }, - "tgg": { - "id": "tgg", - "title": "The Great Gatsby", - }, + expect(result).toEqual({ + entities: { + af: { + id: 'af', + title: 'Animal Farm', + }, + tgg: { + id: 'tgg', + title: 'The Great Gatsby', }, - "ids": [ - "tgg", - "af", - ], - } - `) + }, + ids: ['tgg', 'af'], + }) }) test('updateOne', () => { @@ -530,19 +507,15 @@ describe('Unsorted State Adapter', () => { }) }) - expect(result).toMatchInlineSnapshot(` - { - "entities": { - "tgg": { - "id": "tgg", - "title": "A New Hope", - }, + expect(result).toEqual({ + entities: { + tgg: { + id: 'tgg', + title: 'A New Hope', }, - "ids": [ - "tgg", - ], - } - `) + }, + ids: ['tgg'], + }) }) test('updateMany', () => { @@ -565,49 +538,39 @@ describe('Unsorted State Adapter', () => { ]) }) - expect(result).toMatchInlineSnapshot(` - { - "entities": { - "aco": { - "id": "aco", - "title": "Third Change", - }, - "tgg": { - "id": "tgg", - "title": "Second Change", - }, - "th": { - "author": "Fourth Change", - "id": "th", - "title": "First Change", - }, + expect(result).toEqual({ + entities: { + aco: { + id: 'aco', + title: 'Third Change', + }, + tgg: { + id: 'tgg', + title: 'Second Change', + }, + th: { + author: 'Fourth Change', + id: 'th', + title: 'First Change', }, - "ids": [ - "tgg", - "aco", - "th", - ], - } - `) + }, + ids: ['tgg', 'aco', 'th'], + }) }) test('upsertOne (insert)', () => { const result = createNextState(state, (draft) => { adapter.upsertOne(draft, TheGreatGatsby) }) - expect(result).toMatchInlineSnapshot(` - { - "entities": { - "tgg": { - "id": "tgg", - "title": "The Great Gatsby", - }, + expect(result).toEqual({ + entities: { + tgg: { + id: 'tgg', + title: 'The Great Gatsby', }, - "ids": [ - "tgg", - ], - } - `) + }, + ids: ['tgg'], + }) }) test('upsertOne (update)', () => { @@ -618,24 +581,15 @@ describe('Unsorted State Adapter', () => { title: 'A New Hope', }) }) - expect(result).toMatchInlineSnapshot(` - { - "entities": { - "af": { - "id": "af", - "title": "Animal Farm", - }, - "tgg": { - "id": "tgg", - "title": "A New Hope", - }, + expect(result).toEqual({ + entities: { + tgg: { + id: 'tgg', + title: 'A New Hope', }, - "ids": [ - "tgg", - "af", - ], - } - `) + }, + ids: ['tgg'], + }) }) test('upsertMany', () => { @@ -649,38 +603,34 @@ describe('Unsorted State Adapter', () => { AnimalFarm, ]) }) - expect(result).toMatchInlineSnapshot(` - { - "entities": { - "tgg": { - "id": "tgg", - "title": "The Great Gatsby", - }, + expect(result).toEqual({ + entities: { + af: { + id: 'af', + title: 'Animal Farm', }, - "ids": [ - "tgg", - ], - } - `) + tgg: { + id: 'tgg', + title: 'A New Hope', + }, + }, + ids: ['tgg', 'af'], + }) }) test('setOne (insert)', () => { const result = createNextState(state, (draft) => { adapter.setOne(draft, TheGreatGatsby) }) - expect(result).toMatchInlineSnapshot(` - { - "entities": { - "th": { - "id": "th", - "title": "Silmarillion", - }, + expect(result).toEqual({ + entities: { + tgg: { + id: 'tgg', + title: 'The Great Gatsby', }, - "ids": [ - "th", - ], - } - `) + }, + ids: ['tgg'], + }) }) test('setOne (update)', () => { @@ -691,24 +641,15 @@ describe('Unsorted State Adapter', () => { title: 'Silmarillion', }) }) - expect(result).toMatchInlineSnapshot(` - { - "entities": { - "af": { - "id": "af", - "title": "Animal Farm", - }, - "th": { - "id": "th", - "title": "Silmarillion", - }, + expect(result).toEqual({ + entities: { + th: { + id: 'th', + title: 'Silmarillion', }, - "ids": [ - "th", - "af", - ], - } - `) + }, + ids: ['th'], + }) }) test('setMany', () => { @@ -722,19 +663,19 @@ describe('Unsorted State Adapter', () => { AnimalFarm, ]) }) - expect(result).toMatchInlineSnapshot(` - { - "entities": { - "af": { - "id": "af", - "title": "Animal Farm", - }, + expect(result).toEqual({ + entities: { + af: { + id: 'af', + title: 'Animal Farm', + }, + th: { + id: 'th', + title: 'Silmarillion', }, - "ids": [ - "af", - ], - } - `) + }, + ids: ['th', 'af'], + }) }) test('removeOne', () => { @@ -742,19 +683,15 @@ describe('Unsorted State Adapter', () => { const result = createNextState(withTwo, (draft) => { adapter.removeOne(draft, TheGreatGatsby.id) }) - expect(result).toMatchInlineSnapshot(` - { - "entities": { - "aco": { - "id": "aco", - "title": "A Clockwork Orange", - }, + expect(result).toEqual({ + entities: { + af: { + id: 'af', + title: 'Animal Farm', }, - "ids": [ - "aco", - ], - } - `) + }, + ids: ['af'], + }) }) test('removeMany', () => { @@ -766,19 +703,15 @@ describe('Unsorted State Adapter', () => { const result = createNextState(withThree, (draft) => { adapter.removeMany(draft, [TheGreatGatsby.id, AnimalFarm.id]) }) - expect(result).toMatchInlineSnapshot(` - Object { - "entities": Object { - "aco": Object { - "id": "aco", - "title": "A Clockwork Orange", - }, + expect(result).toEqual({ + entities: { + aco: { + id: 'aco', + title: 'A Clockwork Orange', }, - "ids": Array [ - "aco", - ], - } - `) + }, + ids: ['aco'], + }) }) }) }) From 437206141c75f69ab8f4a6fe0ec412f9dc2d9fcb Mon Sep 17 00:00:00 2001 From: Mark Erikson Date: Thu, 19 Jan 2023 00:45:45 -0500 Subject: [PATCH 026/412] Restructure configureStore test to allow valid mocking --- .../toolkit/src/query/tests/cleanup.test.tsx | 2 +- .../toolkit/src/tests/configureStore.test.ts | 83 +++++++++++++++---- packages/toolkit/vitest.config.ts | 1 + 3 files changed, 67 insertions(+), 19 deletions(-) diff --git a/packages/toolkit/src/query/tests/cleanup.test.tsx b/packages/toolkit/src/query/tests/cleanup.test.tsx index 55cbd8e806..75c0f4c6e0 100644 --- a/packages/toolkit/src/query/tests/cleanup.test.tsx +++ b/packages/toolkit/src/query/tests/cleanup.test.tsx @@ -150,7 +150,7 @@ test('data stays in store when one component requiring the data stays in the sto expect(getSubStateB()).toEqual(statusB) }) -test.only('Minimizes the number of subscription dispatches when multiple components ask for the same data', async () => { +test('Minimizes the number of subscription dispatches when multiple components ask for the same data', async () => { const listenerMiddleware = createListenerMiddleware() const storeRef = setupApiStore(api, undefined, { middleware: { diff --git a/packages/toolkit/src/tests/configureStore.test.ts b/packages/toolkit/src/tests/configureStore.test.ts index a963a4c54a..9382f2d2bc 100644 --- a/packages/toolkit/src/tests/configureStore.test.ts +++ b/packages/toolkit/src/tests/configureStore.test.ts @@ -1,18 +1,64 @@ import { vi } from 'vitest' import type { StoreEnhancer, StoreEnhancerStoreCreator } from '@reduxjs/toolkit' -import { configureStore } from '@reduxjs/toolkit' -import * as RTK from '@reduxjs/toolkit' -import * as redux from 'redux' -import * as devtools from '@internal/devtoolsExtension' +import type * as Redux from 'redux' +import type * as DevTools from '@internal/devtoolsExtension' + +vi.doMock('redux', async () => { + const redux: any = await vi.importActual('redux') -describe('configureStore', () => { vi.spyOn(redux, 'applyMiddleware') vi.spyOn(redux, 'combineReducers') vi.spyOn(redux, 'compose') vi.spyOn(redux, 'createStore') + + return redux +}) + +vi.doMock('@internal/devtoolsExtension', async () => { + const devtools: typeof DevTools = await vi.importActual( + '@internal/devtoolsExtension' + ) vi.spyOn(devtools, 'composeWithDevTools') // @remap-prod-remove-line + return devtools +}) + +function originalReduxCompose(...funcs: Function[]) { + if (funcs.length === 0) { + // infer the argument type so it is usable in inference down the line + return (arg: T) => arg + } + + if (funcs.length === 1) { + return funcs[0] + } + + return funcs.reduce( + (a, b) => + (...args: any) => + a(b(...args)) + ) +} - const reducer: redux.Reducer = (state = {}, _action) => state +function originalComposeWithDevtools() { + if (arguments.length === 0) return undefined + if (typeof arguments[0] === 'object') return originalReduxCompose + return originalReduxCompose.apply(null, arguments as any as Function[]) +} + +describe('configureStore', async () => { + // RTK's internal `composeWithDevtools` function isn't publicly exported, + // so we can't mock it. However, it _does_ try to access the global extension method + // attached to `window`. So, if we mock _that_, we'll know if the enhancer ran. + const mockDevtoolsCompose = vi + .fn() + .mockImplementation(originalComposeWithDevtools) + ;(window as any).__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ = mockDevtoolsCompose + + const redux = await import('redux') + + const { configureStore } = await import('@reduxjs/toolkit') + + const reducer: Redux.Reducer = (state = {}, _action) => state beforeEach(() => { vi.clearAllMocks() @@ -22,13 +68,14 @@ describe('configureStore', () => { it('calls createStore with the reducer', () => { configureStore({ reducer }) expect(configureStore({ reducer })).toBeInstanceOf(Object) - expect(redux.applyMiddleware).toHaveBeenCalled() - expect(devtools.composeWithDevTools).toHaveBeenCalled() // @remap-prod-remove-line + expect(redux.createStore).toHaveBeenCalledWith( reducer, undefined, expect.any(Function) ) + expect(redux.applyMiddleware).toHaveBeenCalled() + expect(mockDevtoolsCompose).toHaveBeenCalled() // @remap-prod-remove-line }) }) @@ -42,7 +89,7 @@ describe('configureStore', () => { expect(configureStore({ reducer })).toBeInstanceOf(Object) expect(redux.combineReducers).toHaveBeenCalledWith(reducer) expect(redux.applyMiddleware).toHaveBeenCalled() - expect(devtools.composeWithDevTools).toHaveBeenCalled() // @remap-prod-remove-line-line + expect(mockDevtoolsCompose).toHaveBeenCalled() // @remap-prod-remove-line-line expect(redux.createStore).toHaveBeenCalledWith( expect.any(Function), undefined, @@ -63,7 +110,7 @@ describe('configureStore', () => { it('calls createStore without any middleware', () => { expect(configureStore({ middleware: [], reducer })).toBeInstanceOf(Object) expect(redux.applyMiddleware).toHaveBeenCalledWith() - expect(devtools.composeWithDevTools).toHaveBeenCalled() // @remap-prod-remove-line-line + expect(mockDevtoolsCompose).toHaveBeenCalled() // @remap-prod-remove-line-line expect(redux.createStore).toHaveBeenCalledWith( reducer, undefined, @@ -82,7 +129,7 @@ describe('configureStore', () => { expect.any(Function), // immutableCheck expect.any(Function) // serializableCheck ) - expect(devtools.composeWithDevTools).toHaveBeenCalled() // @remap-prod-remove-line-line + expect(mockDevtoolsCompose).toHaveBeenCalled() // @remap-prod-remove-line-line expect(redux.createStore).toHaveBeenCalledWith( reducer, undefined, @@ -121,13 +168,13 @@ describe('configureStore', () => { describe('given custom middleware', () => { it('calls createStore with custom middleware and without default middleware', () => { - const thank: redux.Middleware = (_store) => (next) => (action) => + const thank: Redux.Middleware = (_store) => (next) => (action) => next(action) expect(configureStore({ middleware: [thank], reducer })).toBeInstanceOf( Object ) expect(redux.applyMiddleware).toHaveBeenCalledWith(thank) - expect(devtools.composeWithDevTools).toHaveBeenCalled() // @remap-prod-remove-line-line + expect(mockDevtoolsCompose).toHaveBeenCalled() // @remap-prod-remove-line-line expect(redux.createStore).toHaveBeenCalledWith( reducer, undefined, @@ -139,7 +186,7 @@ describe('configureStore', () => { describe('middleware builder notation', () => { it('calls builder, passes getDefaultMiddleware and uses returned middlewares', () => { const thank = vi.fn( - ((_store) => (next) => (action) => 'foobar') as redux.Middleware + ((_store) => (next) => (action) => 'foobar') as Redux.Middleware ) const builder = vi.fn((getDefaultMiddleware) => { @@ -182,7 +229,7 @@ describe('configureStore', () => { Object ) expect(redux.applyMiddleware).toHaveBeenCalled() - expect(devtools.composeWithDevTools).toHaveBeenCalledWith(options) // @remap-prod-remove-line + expect(mockDevtoolsCompose).toHaveBeenCalledWith(options) // @remap-prod-remove-line expect(redux.createStore).toHaveBeenCalledWith( reducer, undefined, @@ -195,7 +242,7 @@ describe('configureStore', () => { it('calls createStore with preloadedState', () => { expect(configureStore({ reducer })).toBeInstanceOf(Object) expect(redux.applyMiddleware).toHaveBeenCalled() - expect(devtools.composeWithDevTools).toHaveBeenCalled() // @remap-prod-remove-line + expect(mockDevtoolsCompose).toHaveBeenCalled() // @remap-prod-remove-line expect(redux.createStore).toHaveBeenCalledWith( reducer, undefined, @@ -206,12 +253,12 @@ describe('configureStore', () => { describe('given enhancers', () => { it('calls createStore with enhancers', () => { - const enhancer: redux.StoreEnhancer = (next) => next + const enhancer: Redux.StoreEnhancer = (next) => next expect(configureStore({ enhancers: [enhancer], reducer })).toBeInstanceOf( Object ) expect(redux.applyMiddleware).toHaveBeenCalled() - expect(devtools.composeWithDevTools).toHaveBeenCalled() // @remap-prod-remove-line + expect(mockDevtoolsCompose).toHaveBeenCalled() // @remap-prod-remove-line expect(redux.createStore).toHaveBeenCalledWith( reducer, undefined, diff --git a/packages/toolkit/vitest.config.ts b/packages/toolkit/vitest.config.ts index 514c9cc28d..b010d80498 100644 --- a/packages/toolkit/vitest.config.ts +++ b/packages/toolkit/vitest.config.ts @@ -17,6 +17,7 @@ export default defineConfig({ }, deps: { interopDefault: true, + inline: ['redux', '@reduxjs/toolkit'], }, }, }) From 803c9700ba0ed9aab1712748c6a225f6d6184b5e Mon Sep 17 00:00:00 2001 From: Mark Erikson Date: Fri, 20 Jan 2023 21:55:41 -0500 Subject: [PATCH 027/412] Release 2.0.0-alpha.1 --- packages/toolkit/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/toolkit/package.json b/packages/toolkit/package.json index 34997ef3ce..a57a250d94 100644 --- a/packages/toolkit/package.json +++ b/packages/toolkit/package.json @@ -1,6 +1,6 @@ { "name": "@reduxjs/toolkit", - "version": "2.0.0-alpha.0", + "version": "2.0.0-alpha.1", "description": "The official, opinionated, batteries-included toolset for efficient Redux development", "author": "Mark Erikson ", "license": "MIT", From cd5265b966bc799b543fcd52fd2c1f0e2150b77f Mon Sep 17 00:00:00 2001 From: Mark Erikson Date: Sun, 12 Feb 2023 22:19:54 -0500 Subject: [PATCH 028/412] Bump Redux dep to 5.0.0-alpha.2 --- packages/toolkit/package.json | 2 +- yarn.lock | 9 ++++++++- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/packages/toolkit/package.json b/packages/toolkit/package.json index a57a250d94..454658eba8 100644 --- a/packages/toolkit/package.json +++ b/packages/toolkit/package.json @@ -118,7 +118,7 @@ ], "dependencies": { "immer": "^9.0.16", - "redux": "^4.2.0", + "redux": "5.0.0-alpha.2", "redux-thunk": "3.0.0-alpha.1", "reselect": "^4.1.7" }, diff --git a/yarn.lock b/yarn.lock index 25ae80c928..46346bf0df 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6781,7 +6781,7 @@ __metadata: node-fetch: ^2.6.1 prettier: ^2.2.1 query-string: ^7.0.1 - redux: ^4.2.0 + redux: 5.0.0-alpha.2 redux-thunk: 3.0.0-alpha.1 reselect: ^4.1.7 rimraf: ^3.0.2 @@ -24426,6 +24426,13 @@ fsevents@^1.2.7: languageName: node linkType: hard +"redux@npm:5.0.0-alpha.2": + version: 5.0.0-alpha.2 + resolution: "redux@npm:5.0.0-alpha.2" + checksum: fbae31c55bab62a210a5a24a64721f593080fb94808c547b646ddff91515f8ab4e3363af5ece16491f0179d43729fc0350235c9fea7500b37b67d762a55b6945 + languageName: node + linkType: hard + "redux@npm:^4.0.0, redux@npm:^4.1.2": version: 4.1.2 resolution: "redux@npm:4.1.2" From c4d15274f4072abf5a341ee5668c615bfb4cf747 Mon Sep 17 00:00:00 2001 From: Mark Erikson Date: Sun, 12 Feb 2023 22:20:09 -0500 Subject: [PATCH 029/412] Fix TS issue with Redux 5 alpha --- packages/toolkit/scripts/build.ts | 4 ++-- packages/toolkit/src/devtoolsExtension.ts | 4 +++- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/packages/toolkit/scripts/build.ts b/packages/toolkit/scripts/build.ts index b66f456ed0..6dfa0adfc0 100644 --- a/packages/toolkit/scripts/build.ts +++ b/packages/toolkit/scripts/build.ts @@ -209,7 +209,7 @@ async function bundle(options: BuildOptions & EntryPointOptions) { ], }) - for (const chunk of result.outputFiles) { + for (const chunk of result.outputFiles!) { const esVersion = target in esVersionMappings ? esVersionMappings[target] @@ -252,7 +252,7 @@ async function bundle(options: BuildOptions & EntryPointOptions) { toplevel: true, } ) - code = transformResult.code + code = transformResult.code! mapping = transformResult.map as RawSourceMap } diff --git a/packages/toolkit/src/devtoolsExtension.ts b/packages/toolkit/src/devtoolsExtension.ts index b16b8b8577..a48d7e6d11 100644 --- a/packages/toolkit/src/devtoolsExtension.ts +++ b/packages/toolkit/src/devtoolsExtension.ts @@ -220,7 +220,9 @@ type Compose = typeof compose interface ComposeWithDevTools { (options: DevToolsEnhancerOptions): Compose - (...funcs: StoreEnhancer[]): StoreEnhancer + ( + ...funcs: StoreEnhancer[] + ): StoreEnhancer } /** From c00e0b9b58879cb68ffcb9bf5d51d8c576d6265d Mon Sep 17 00:00:00 2001 From: Nathan Bierema Date: Tue, 14 Feb 2023 17:36:23 -0500 Subject: [PATCH 030/412] Fix type errors after bumping to Redux 5 alpha --- .../toolkit/src/tests/configureStore.test.ts | 2 +- .../src/tests/configureStore.typetest.ts | 19 +++++++++---------- packages/toolkit/src/tsHelpers.ts | 10 ++++++++-- 3 files changed, 18 insertions(+), 13 deletions(-) diff --git a/packages/toolkit/src/tests/configureStore.test.ts b/packages/toolkit/src/tests/configureStore.test.ts index 9382f2d2bc..1465958c3d 100644 --- a/packages/toolkit/src/tests/configureStore.test.ts +++ b/packages/toolkit/src/tests/configureStore.test.ts @@ -270,7 +270,7 @@ describe('configureStore', async () => { let dummyEnhancerCalled = false const dummyEnhancer: StoreEnhancer = - (createStore: StoreEnhancerStoreCreator) => + (createStore) => (reducer, ...args: any[]) => { dummyEnhancerCalled = true diff --git a/packages/toolkit/src/tests/configureStore.typetest.ts b/packages/toolkit/src/tests/configureStore.typetest.ts index 76bbfc8ac8..e17095e13f 100644 --- a/packages/toolkit/src/tests/configureStore.typetest.ts +++ b/packages/toolkit/src/tests/configureStore.typetest.ts @@ -159,9 +159,9 @@ const _anyMiddleware: any = () => () => () => {} }) { - type SomePropertyStoreEnhancer = StoreEnhancer<{ someProperty: string }> - - const somePropertyStoreEnhancer: SomePropertyStoreEnhancer = (next) => { + const somePropertyStoreEnhancer: StoreEnhancer<{ someProperty: string }> = ( + next + ) => { return (reducer, preloadedState) => { return { ...next(reducer, preloadedState), @@ -170,13 +170,9 @@ const _anyMiddleware: any = () => () => () => {} } } - type AnotherPropertyStoreEnhancer = StoreEnhancer<{ + const anotherPropertyStoreEnhancer: StoreEnhancer<{ anotherProperty: number - }> - - const anotherPropertyStoreEnhancer: AnotherPropertyStoreEnhancer = ( - next - ) => { + }> = (next) => { return (reducer, preloadedState) => { return { ...next(reducer, preloadedState), @@ -187,7 +183,10 @@ const _anyMiddleware: any = () => () => () => {} const store = configureStore({ reducer: () => 0, - enhancers: [somePropertyStoreEnhancer, anotherPropertyStoreEnhancer], + enhancers: [ + somePropertyStoreEnhancer, + anotherPropertyStoreEnhancer, + ] as const, }) expectType>( diff --git a/packages/toolkit/src/tsHelpers.ts b/packages/toolkit/src/tsHelpers.ts index 3e8dc20f05..3077037731 100644 --- a/packages/toolkit/src/tsHelpers.ts +++ b/packages/toolkit/src/tsHelpers.ts @@ -101,8 +101,14 @@ export type ExtractDispatchExtensions = M extends MiddlewareArray< ? ExtractDispatchFromMiddlewareTuple<[...M], {}> : never -export type ExtractStoreExtensions = E extends any[] - ? UnionToIntersection ? Ext extends {} ? Ext : {} : {}> +export type ExtractStoreExtensions = E extends readonly any[] + ? UnionToIntersection< + E[number] extends StoreEnhancer + ? Ext extends {} + ? Ext + : {} + : {} + > : {} /** From 0974d818403cf65f2a1a04b35b1f9e0563ca097d Mon Sep 17 00:00:00 2001 From: Mark Erikson Date: Tue, 14 Feb 2023 21:04:39 -0500 Subject: [PATCH 031/412] Release 2.0.0-alpha.2 --- packages/toolkit/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/toolkit/package.json b/packages/toolkit/package.json index 454658eba8..253d722b4d 100644 --- a/packages/toolkit/package.json +++ b/packages/toolkit/package.json @@ -1,6 +1,6 @@ { "name": "@reduxjs/toolkit", - "version": "2.0.0-alpha.1", + "version": "2.0.0-alpha.2", "description": "The official, opinionated, batteries-included toolset for efficient Redux development", "author": "Mark Erikson ", "license": "MIT", From 32491be5064a2d54dc9b23266b71b1c71e07f00a Mon Sep 17 00:00:00 2001 From: Nathan Bierema Date: Sun, 5 Mar 2023 21:54:56 -0500 Subject: [PATCH 032/412] Update types with PreloadedState generic --- packages/toolkit/package.json | 2 +- packages/toolkit/src/configureStore.ts | 24 +-- packages/toolkit/src/query/core/buildSlice.ts | 5 +- .../src/tests/configureStore.typetest.ts | 167 ++++++++++++++++-- yarn.lock | 8 +- 5 files changed, 172 insertions(+), 34 deletions(-) diff --git a/packages/toolkit/package.json b/packages/toolkit/package.json index 253d722b4d..c9f54eb595 100644 --- a/packages/toolkit/package.json +++ b/packages/toolkit/package.json @@ -118,7 +118,7 @@ ], "dependencies": { "immer": "^9.0.16", - "redux": "5.0.0-alpha.2", + "redux": "Methuselah96/redux#head=preloaded-state-generic-4", "redux-thunk": "3.0.0-alpha.1", "reselect": "^4.1.7" }, diff --git a/packages/toolkit/src/configureStore.ts b/packages/toolkit/src/configureStore.ts index 76f5a0519c..a84e086f28 100644 --- a/packages/toolkit/src/configureStore.ts +++ b/packages/toolkit/src/configureStore.ts @@ -7,8 +7,6 @@ import type { StoreEnhancer, Store, Dispatch, - PreloadedState, - CombinedState, } from 'redux' import { createStore, compose, applyMiddleware, combineReducers } from 'redux' import type { DevToolsEnhancerOptions as DevToolsOptions } from './devtoolsExtension' @@ -21,7 +19,6 @@ import type { } from './getDefaultMiddleware' import { curryGetDefaultMiddleware } from './getDefaultMiddleware' import type { - NoInfer, ExtractDispatchExtensions, ExtractStoreExtensions, } from './tsHelpers' @@ -46,13 +43,16 @@ export interface ConfigureStoreOptions< S = any, A extends Action = AnyAction, M extends Middlewares = Middlewares, - E extends Enhancers = Enhancers + E extends Enhancers = Enhancers, + PreloadedState = S > { /** * A single reducer function that will be used as the root reducer, or an * object of slice reducers that will be passed to `combineReducers()`. */ - reducer: Reducer | ReducersMapObject + reducer: + | Reducer + | ReducersMapObject /** * An array of Redux middleware to install. If not supplied, defaults to @@ -87,7 +87,7 @@ export interface ConfigureStoreOptions< As we cannot distinguish between those two cases without adding another generic parameter, we just make the pragmatic assumption that the latter almost never happens. */ - preloadedState?: PreloadedState>> + preloadedState?: PreloadedState /** * The store enhancers to apply. See Redux's `createStore()`. @@ -142,8 +142,11 @@ export function configureStore< S = any, A extends Action = AnyAction, M extends Middlewares = [ThunkMiddlewareFor], - E extends Enhancers = [StoreEnhancer] ->(options: ConfigureStoreOptions): EnhancedStore { + E extends Enhancers = [StoreEnhancer], + PreloadedState = S +>( + options: ConfigureStoreOptions +): EnhancedStore { const curriedGetDefaultMiddleware = curryGetDefaultMiddleware() const { @@ -154,12 +157,13 @@ export function configureStore< enhancers = undefined, } = options || {} - let rootReducer: Reducer + let rootReducer: Reducer if (typeof reducer === 'function') { rootReducer = reducer } else if (isPlainObject(reducer)) { - rootReducer = combineReducers(reducer) as unknown as Reducer + // @ts-ignore + rootReducer = combineReducers(reducer) } else { throw new Error( '"reducer" is a required argument, and must be a function or an object of functions that can be passed to combineReducers' diff --git a/packages/toolkit/src/query/core/buildSlice.ts b/packages/toolkit/src/query/core/buildSlice.ts index 3343b6dc3d..07b2f1aafc 100644 --- a/packages/toolkit/src/query/core/buildSlice.ts +++ b/packages/toolkit/src/query/core/buildSlice.ts @@ -10,7 +10,6 @@ import { prepareAutoBatched, } from '@reduxjs/toolkit' import type { - CombinedState as CombinedQueryState, QuerySubstateIdentifier, QuerySubState, MutationSubstateIdentifier, @@ -469,9 +468,7 @@ export function buildSlice({ }, }) - const combinedReducer = combineReducers< - CombinedQueryState - >({ + const combinedReducer = combineReducers({ queries: querySlice.reducer, mutations: mutationSlice.reducer, provided: invalidationSlice.reducer, diff --git a/packages/toolkit/src/tests/configureStore.typetest.ts b/packages/toolkit/src/tests/configureStore.typetest.ts index a74fefcbcf..d64ae35253 100644 --- a/packages/toolkit/src/tests/configureStore.typetest.ts +++ b/packages/toolkit/src/tests/configureStore.typetest.ts @@ -8,7 +8,7 @@ import type { Action, StoreEnhancer, } from 'redux' -import { applyMiddleware } from 'redux' +import { applyMiddleware, combineReducers } from 'redux' import type { PayloadAction, ConfigureStoreOptions } from '@reduxjs/toolkit' import { configureStore, @@ -130,8 +130,8 @@ const _anyMiddleware: any = () => () => () => {} }) configureStore({ - reducer: () => 0, // @ts-expect-error + reducer: (_: number) => 0, preloadedState: 'non-matching state type', }) } @@ -197,25 +197,162 @@ const _anyMiddleware: any = () => () => () => {} } /** - * Test: configureStore() state type inference works when specifying both a - * reducer object and a partial preloaded state. + * Test: Preloaded state typings */ { let counterReducer1: Reducer = () => 0 let counterReducer2: Reducer = () => 0 - const store = configureStore({ - reducer: { - counter1: counterReducer1, - counter2: counterReducer2, - }, - preloadedState: { - counter1: 0, - }, - }) + /** + * Test: partial preloaded state + */ + { + const store = configureStore({ + reducer: { + counter1: counterReducer1, + counter2: counterReducer2, + }, + preloadedState: { + counter1: 0, + }, + }) + + const counter1: number = store.getState().counter1 + const counter2: number = store.getState().counter2 + } + + /** + * Test: empty preloaded state + */ + { + const store = configureStore({ + reducer: { + counter1: counterReducer1, + counter2: counterReducer2, + }, + preloadedState: {}, + }) + + const counter1: number = store.getState().counter1 + const counter2: number = store.getState().counter2 + } + + /** + * Test: excess properties in preloaded state + */ + { + const store = configureStore({ + reducer: { + // @ts-expect-error + counter1: counterReducer1, + counter2: counterReducer2, + }, + preloadedState: { + counter1: 0, + counter3: 5, + }, + }) + + const counter1: number = store.getState().counter1 + const counter2: number = store.getState().counter2 + } + + /** + * Test: mismatching properties in preloaded state + */ + { + const store = configureStore({ + reducer: { + // @ts-expect-error + counter1: counterReducer1, + counter2: counterReducer2, + }, + preloadedState: { + counter3: 5, + }, + }) + + const counter1: number = store.getState().counter1 + const counter2: number = store.getState().counter2 + } + + /** + * Test: string preloaded state when expecting object + */ + { + const store = configureStore({ + reducer: { + // @ts-expect-error + counter1: counterReducer1, + counter2: counterReducer2, + }, + preloadedState: 'test', + }) + + const counter1: number = store.getState().counter1 + const counter2: number = store.getState().counter2 + } - const counter1: number = store.getState().counter1 - const counter2: number = store.getState().counter2 + /** + * Test: nested combineReducers allows partial + */ + { + const store = configureStore({ + reducer: { + group1: combineReducers({ + counter1: counterReducer1, + counter2: counterReducer2, + }), + group2: combineReducers({ + counter1: counterReducer1, + counter2: counterReducer2, + }), + }, + preloadedState: { + group1: { + counter1: 5, + }, + }, + }) + + const group1counter1: number = store.getState().group1.counter1 + const group1counter2: number = store.getState().group1.counter2 + const group2counter1: number = store.getState().group2.counter1 + const group2counter2: number = store.getState().group2.counter2 + } + + /** + * Test: non-nested combineReducers does not allow partial + */ + { + interface GroupState { + counter1: number + counter2: number + } + + const initialState = { counter1: 0, counter2: 0 } + + const group1Reducer: Reducer = (state = initialState) => state + const group2Reducer: Reducer = (state = initialState) => state + + const store = configureStore({ + reducer: { + // @ts-expect-error + group1: group1Reducer, + group2: group2Reducer, + }, + preloadedState: { + group1: { + counter1: 5, + }, + }, + }) + + const group1counter1: number = store.getState().group1.counter1 + const group1counter2: number = store.getState().group1.counter2 + const group2counter1: number = store.getState().group2.counter1 + const group2counter2: number = store.getState().group2.counter2 + } } /** diff --git a/yarn.lock b/yarn.lock index 46346bf0df..9c5ca1de40 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6781,7 +6781,7 @@ __metadata: node-fetch: ^2.6.1 prettier: ^2.2.1 query-string: ^7.0.1 - redux: 5.0.0-alpha.2 + redux: "Methuselah96/redux#head=preloaded-state-generic-4" redux-thunk: 3.0.0-alpha.1 reselect: ^4.1.7 rimraf: ^3.0.2 @@ -24426,10 +24426,10 @@ fsevents@^1.2.7: languageName: node linkType: hard -"redux@npm:5.0.0-alpha.2": +"redux@Methuselah96/redux#head=preloaded-state-generic-4": version: 5.0.0-alpha.2 - resolution: "redux@npm:5.0.0-alpha.2" - checksum: fbae31c55bab62a210a5a24a64721f593080fb94808c547b646ddff91515f8ab4e3363af5ece16491f0179d43729fc0350235c9fea7500b37b67d762a55b6945 + resolution: "redux@https://github.com/Methuselah96/redux.git#commit=20501975b7445f07b83a6890fb8af8aeb84eb5a2" + checksum: 452f50587bc3936c3a5c1fe19082cc7efba31ddd4016c68d8cd8a899b7b7e6ffdcdcd17a4d472e80bf443257b4396445fbe3140a5f0396f55f2f89bd44a6d4b7 languageName: node linkType: hard From 1ad0ef7ef1c8ccca44b73d645a871cd4a8e2eae1 Mon Sep 17 00:00:00 2001 From: Ryan Christian Date: Sat, 18 Mar 2023 02:28:47 -0500 Subject: [PATCH 033/412] chore: Update build script comments re:ESNext --- packages/toolkit/scripts/build.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/toolkit/scripts/build.ts b/packages/toolkit/scripts/build.ts index 6dfa0adfc0..76980b754f 100644 --- a/packages/toolkit/scripts/build.ts +++ b/packages/toolkit/scripts/build.ts @@ -51,7 +51,7 @@ const buildTargets: BuildOptions[] = [ minify: true, env: 'production', }, - // ESM, embedded `process`, ES2017 syntax: modern Webpack dev + // ESM, embedded `process`: modern Webpack dev { format: 'esm', name: 'modern', @@ -59,7 +59,7 @@ const buildTargets: BuildOptions[] = [ minify: false, env: '', }, - // ESM, pre-compiled "dev", ES2017 syntax: browser development + // ESM, pre-compiled "dev": browser development { format: 'esm', name: 'modern.development', @@ -67,7 +67,7 @@ const buildTargets: BuildOptions[] = [ minify: false, env: 'development', }, - // ESM, pre-compiled "prod", ES2017 syntax: browser prod + // ESM, pre-compiled "prod": browser prod { format: 'esm', name: 'modern.production.min', From 9dbe2741884733e1032003529f6cf1c7152a0b81 Mon Sep 17 00:00:00 2001 From: "ben.durrant" Date: Fri, 24 Mar 2023 16:47:15 +0000 Subject: [PATCH 034/412] create react specific entry point --- packages/toolkit/api-extractor-react.json | 356 ++++++++++++++++++++++ packages/toolkit/react/package.json | 20 ++ packages/toolkit/scripts/build.ts | 7 + packages/toolkit/src/react/index.ts | 1 + packages/toolkit/tsconfig.base.json | 1 + 5 files changed, 385 insertions(+) create mode 100644 packages/toolkit/api-extractor-react.json create mode 100644 packages/toolkit/react/package.json create mode 100644 packages/toolkit/src/react/index.ts diff --git a/packages/toolkit/api-extractor-react.json b/packages/toolkit/api-extractor-react.json new file mode 100644 index 0000000000..5b110e70da --- /dev/null +++ b/packages/toolkit/api-extractor-react.json @@ -0,0 +1,356 @@ +/** + * Config file for API Extractor. For more info, please visit: https://api-extractor.com + */ +{ + "$schema": "https://developer.microsoft.com/json-schemas/api-extractor/v7/api-extractor.schema.json", + + /** + * Optionally specifies another JSON config file that this file extends from. This provides a way for + * standard settings to be shared across multiple projects. + * + * If the path starts with "./" or "../", the path is resolved relative to the folder of the file that contains + * the "extends" field. Otherwise, the first path segment is interpreted as an NPM package name, and will be + * resolved using NodeJS require(). + * + * SUPPORTED TOKENS: none + * DEFAULT VALUE: "" + */ + // "extends": "./shared/api-extractor-base.json" + // "extends": "my-package/include/api-extractor-base.json" + + /** + * Determines the "" token that can be used with other config file settings. The project folder + * typically contains the tsconfig.json and package.json config files, but the path is user-defined. + * + * The path is resolved relative to the folder of the config file that contains the setting. + * + * The default value for "projectFolder" is the token "", which means the folder is determined by traversing + * parent folders, starting from the folder containing api-extractor.json, and stopping at the first folder + * that contains a tsconfig.json file. If a tsconfig.json file cannot be found in this way, then an error + * will be reported. + * + * SUPPORTED TOKENS: + * DEFAULT VALUE: "" + */ + "projectFolder": ".", + + /** + * (REQUIRED) Specifies the .d.ts file to be used as the starting point for analysis. API Extractor + * analyzes the symbols exported by this module. + * + * The file extension must be ".d.ts" and not ".ts". + * + * The path is resolved relative to the folder of the config file that contains the setting; to change this, + * prepend a folder token such as "". + * + * SUPPORTED TOKENS: , , + */ + "mainEntryPointFilePath": "dist/react/index.d.ts", + //"mainEntryPointFilePath": "/home/weber/tmp/rtk-origin-master/dist/index.d.ts", + + /** + * A list of NPM package names whose exports should be treated as part of this package. + * + * For example, suppose that Webpack is used to generate a distributed bundle for the project "library1", + * and another NPM package "library2" is embedded in this bundle. Some types from library2 may become part + * of the exported API for library1, but by default API Extractor would generate a .d.ts rollup that explicitly + * imports library2. To avoid this, we can specify: + * + * "bundledPackages": [ "library2" ], + * + * This would direct API Extractor to embed those types directly in the .d.ts rollup, as if they had been + * local files for library1. + */ + "bundledPackages": [], + + /** + * Determines how the TypeScript compiler engine will be invoked by API Extractor. + */ + "compiler": { + /** + * Specifies the path to the tsconfig.json file to be used by API Extractor when analyzing the project. + * + * The path is resolved relative to the folder of the config file that contains the setting; to change this, + * prepend a folder token such as "". + * + * Note: This setting will be ignored if "overrideTsconfig" is used. + * + * SUPPORTED TOKENS: , , + * DEFAULT VALUE: "/tsconfig.json" + */ + // "tsconfigFilePath": "/tsconfig.json", + /** + * Provides a compiler configuration that will be used instead of reading the tsconfig.json file from disk. + * The object must conform to the TypeScript tsconfig schema: + * + * http://json.schemastore.org/tsconfig + * + * If omitted, then the tsconfig.json file will be read from the "projectFolder". + * + * DEFAULT VALUE: no overrideTsconfig section + */ + // "overrideTsconfig": { + // . . . + // } + /** + * This option causes the compiler to be invoked with the --skipLibCheck option. This option is not recommended + * and may cause API Extractor to produce incomplete or incorrect declarations, but it may be required when + * dependencies contain declarations that are incompatible with the TypeScript engine that API Extractor uses + * for its analysis. Where possible, the underlying issue should be fixed rather than relying on skipLibCheck. + * + * DEFAULT VALUE: false + */ + // "skipLibCheck": true, + }, + + /** + * Configures how the API report file (*.api.md) will be generated. + */ + "apiReport": { + /** + * (REQUIRED) Whether to generate an API report. + */ + "enabled": true, + + /** + * The filename for the API report files. It will be combined with "reportFolder" or "reportTempFolder" to produce + * a full file path. + * + * The file extension should be ".api.md", and the string should not contain a path separator such as "\" or "/". + * + * SUPPORTED TOKENS: , + * DEFAULT VALUE: ".api.md" + */ + "reportFileName": "redux-toolkit-react.api.md", + + /** + * Specifies the folder where the API report file is written. The file name portion is determined by + * the "reportFileName" setting. + * + * The API report file is normally tracked by Git. Changes to it can be used to trigger a branch policy, + * e.g. for an API review. + * + * The path is resolved relative to the folder of the config file that contains the setting; to change this, + * prepend a folder token such as "". + * + * SUPPORTED TOKENS: , , + * DEFAULT VALUE: "/etc/" + */ + "reportFolder": "/etc/" + + /** + * Specifies the folder where the temporary report file is written. The file name portion is determined by + * the "reportFileName" setting. + * + * After the temporary file is written to disk, it is compared with the file in the "reportFolder". + * If they are different, a production build will fail. + * + * The path is resolved relative to the folder of the config file that contains the setting; to change this, + * prepend a folder token such as "". + * + * SUPPORTED TOKENS: , , + * DEFAULT VALUE: "/temp/" + */ + // "reportTempFolder": "/temp/" + }, + + /** + * Configures how the doc model file (*.api.json) will be generated. + */ + "docModel": { + /** + * (REQUIRED) Whether to generate a doc model file. + */ + "enabled": false + + /** + * The output path for the doc model file. The file extension should be ".api.json". + * + * The path is resolved relative to the folder of the config file that contains the setting; to change this, + * prepend a folder token such as "". + * + * SUPPORTED TOKENS: , , + * DEFAULT VALUE: "/temp/.api.json" + */ + // "apiJsonFilePath": "/temp/.api.json" + }, + + /** + * Configures how the .d.ts rollup file will be generated. + */ + "dtsRollup": { + /** + * (REQUIRED) Whether to generate the .d.ts rollup file. + */ + "enabled": false, + + /** + * Specifies the output path for a .d.ts rollup file to be generated without any trimming. + * This file will include all declarations that are exported by the main entry point. + * + * If the path is an empty string, then this file will not be written. + * + * The path is resolved relative to the folder of the config file that contains the setting; to change this, + * prepend a folder token such as "". + * + * SUPPORTED TOKENS: , , + * DEFAULT VALUE: "/dist/.d.ts" + */ + "untrimmedFilePath": "/dist/react/typings.d.ts" + + /** + * Specifies the output path for a .d.ts rollup file to be generated with trimming for a "beta" release. + * This file will include only declarations that are marked as "@public" or "@beta". + * + * The path is resolved relative to the folder of the config file that contains the setting; to change this, + * prepend a folder token such as "". + * + * SUPPORTED TOKENS: , , + * DEFAULT VALUE: "" + */ + // "betaTrimmedFilePath": "/dist/-beta.d.ts", + + /** + * Specifies the output path for a .d.ts rollup file to be generated with trimming for a "public" release. + * This file will include only declarations that are marked as "@public". + * + * If the path is an empty string, then this file will not be written. + * + * The path is resolved relative to the folder of the config file that contains the setting; to change this, + * prepend a folder token such as "". + * + * SUPPORTED TOKENS: , , + * DEFAULT VALUE: "" + */ + // "publicTrimmedFilePath": "/dist/-public.d.ts", + + /** + * When a declaration is trimmed, by default it will be replaced by a code comment such as + * "Excluded from this release type: exampleMember". Set "omitTrimmingComments" to true to remove the + * declaration completely. + * + * DEFAULT VALUE: false + */ + // "omitTrimmingComments": true + }, + + /** + * Configures how the tsdoc-metadata.json file will be generated. + */ + "tsdocMetadata": { + /** + * Whether to generate the tsdoc-metadata.json file. + * + * DEFAULT VALUE: true + */ + "enabled": false + + /** + * Specifies where the TSDoc metadata file should be written. + * + * The path is resolved relative to the folder of the config file that contains the setting; to change this, + * prepend a folder token such as "". + * + * The default value is "", which causes the path to be automatically inferred from the "tsdocMetadata", + * "typings" or "main" fields of the project's package.json. If none of these fields are set, the lookup + * falls back to "tsdoc-metadata.json" in the package folder. + * + * SUPPORTED TOKENS: , , + * DEFAULT VALUE: "" + */ + // "tsdocMetadataFilePath": "/dist/tsdoc-metadata.json" + }, + + /** + * Configures how API Extractor reports error and warning messages produced during analysis. + * + * There are three sources of messages: compiler messages, API Extractor messages, and TSDoc messages. + */ + "messages": { + /** + * Configures handling of diagnostic messages reported by the TypeScript compiler engine while analyzing + * the input .d.ts files. + * + * TypeScript message identifiers start with "TS" followed by an integer. For example: "TS2551" + * + * DEFAULT VALUE: A single "default" entry with logLevel=warning. + */ + "compilerMessageReporting": { + /** + * Configures the default routing for messages that don't match an explicit rule in this table. + */ + "default": { + /** + * Specifies whether the message should be written to the the tool's output log. Note that + * the "addToApiReportFile" property may supersede this option. + * + * Possible values: "error", "warning", "none" + * + * Errors cause the build to fail and return a nonzero exit code. Warnings cause a production build fail + * and return a nonzero exit code. For a non-production build (e.g. when "api-extractor run" includes + * the "--local" option), the warning is displayed but the build will not fail. + * + * DEFAULT VALUE: "warning" + */ + "logLevel": "warning" + + /** + * When addToApiReportFile is true: If API Extractor is configured to write an API report file (.api.md), + * then the message will be written inside that file; otherwise, the message is instead logged according to + * the "logLevel" option. + * + * DEFAULT VALUE: false + */ + // "addToApiReportFile": false + } + + // "TS2551": { + // "logLevel": "warning", + // "addToApiReportFile": true + // }, + // + // . . . + }, + + /** + * Configures handling of messages reported by API Extractor during its analysis. + * + * API Extractor message identifiers start with "ae-". For example: "ae-extra-release-tag" + * + * DEFAULT VALUE: See api-extractor-defaults.json for the complete table of extractorMessageReporting mappings + */ + "extractorMessageReporting": { + "default": { + "logLevel": "warning" + // "addToApiReportFile": false + }, + "ae-forgotten-export": { + "logLevel": "none", + "addToApiReportFile": false + } + // + // . . . + }, + + /** + * Configures handling of messages reported by the TSDoc parser when analyzing code comments. + * + * TSDoc message identifiers start with "tsdoc-". For example: "tsdoc-link-tag-unescaped-text" + * + * DEFAULT VALUE: A single "default" entry with logLevel=warning. + */ + "tsdocMessageReporting": { + "default": { + "logLevel": "none" + // "addToApiReportFile": false + } + + // "tsdoc-link-tag-unescaped-text": { + // "logLevel": "warning", + // "addToApiReportFile": true + // }, + // + // . . . + } + } +} diff --git a/packages/toolkit/react/package.json b/packages/toolkit/react/package.json new file mode 100644 index 0000000000..5ff869483b --- /dev/null +++ b/packages/toolkit/react/package.json @@ -0,0 +1,20 @@ +{ + "name": "@reduxjs/toolkit-react", + "version": "1.0.0", + "description": "", + "type": "module", + "module": "../dist/react/redux-toolkit-react.modern.js", + "main": "../dist/react/cjs/index.js", + "types": "./../dist/react/index.d.ts", + "exports": { + "./package.json": "./package.json", + ".": { + "types": "./../dist/react/index.d.ts", + "import": "./../dist/react/redux-toolkit-react.modern.js", + "default": "./../dist/react/cjs/index.js" + } + }, + "author": "Mark Erikson ", + "license": "MIT", + "sideEffects": false +} diff --git a/packages/toolkit/scripts/build.ts b/packages/toolkit/scripts/build.ts index 76980b754f..5ff9067576 100644 --- a/packages/toolkit/scripts/build.ts +++ b/packages/toolkit/scripts/build.ts @@ -99,6 +99,13 @@ const entryPoints: EntryPointOptions[] = [ extractionConfig: 'api-extractor.json', globalName: 'RTK', }, + { + prefix: 'redux-toolkit-react', + folder: 'react', + entryPoint: 'src/react/index.ts', + extractionConfig: 'api-extractor-react.json', + globalName: 'RTK', + }, { prefix: 'rtk-query', folder: 'query', diff --git a/packages/toolkit/src/react/index.ts b/packages/toolkit/src/react/index.ts new file mode 100644 index 0000000000..bc902129a9 --- /dev/null +++ b/packages/toolkit/src/react/index.ts @@ -0,0 +1 @@ +export * from '@reduxjs/toolkit' diff --git a/packages/toolkit/tsconfig.base.json b/packages/toolkit/tsconfig.base.json index 2fdb5a375a..90d908e065 100644 --- a/packages/toolkit/tsconfig.base.json +++ b/packages/toolkit/tsconfig.base.json @@ -35,6 +35,7 @@ "types": ["vitest/globals"], "paths": { "@reduxjs/toolkit": ["src/index.ts"], // @remap-prod-remove-line + "@reduxjs/toolkit/react": ["src/react/index.ts"], // @remap-prod-remove-line "@reduxjs/toolkit/query": ["src/query/index.ts"], // @remap-prod-remove-line "@reduxjs/toolkit/query/react": ["src/query/react/index.ts"], // @remap-prod-remove-line // for type imports in tests only From 984021db2268d925f2d29e519782183397eccad4 Mon Sep 17 00:00:00 2001 From: "ben.durrant" Date: Fri, 24 Mar 2023 16:56:06 +0000 Subject: [PATCH 035/412] add dynamic middleware code --- .../toolkit/src/dynamicMiddleware/index.ts | 120 ++++++++++++++++++ .../src/dynamicMiddleware/react/index.ts | 107 ++++++++++++++++ .../src/dynamicMiddleware/tests/index.test.ts | 69 ++++++++++ .../dynamicMiddleware/tests/index.typetest.ts | 102 +++++++++++++++ .../dynamicMiddleware/tests/react.test.tsx | 101 +++++++++++++++ .../dynamicMiddleware/tests/react.typetest.ts | 89 +++++++++++++ .../toolkit/src/dynamicMiddleware/types.ts | 92 ++++++++++++++ packages/toolkit/src/index.ts | 2 + packages/toolkit/src/react/index.ts | 2 + packages/toolkit/tsconfig.base.json | 2 + 10 files changed, 686 insertions(+) create mode 100644 packages/toolkit/src/dynamicMiddleware/index.ts create mode 100644 packages/toolkit/src/dynamicMiddleware/react/index.ts create mode 100644 packages/toolkit/src/dynamicMiddleware/tests/index.test.ts create mode 100644 packages/toolkit/src/dynamicMiddleware/tests/index.typetest.ts create mode 100644 packages/toolkit/src/dynamicMiddleware/tests/react.test.tsx create mode 100644 packages/toolkit/src/dynamicMiddleware/tests/react.typetest.ts create mode 100644 packages/toolkit/src/dynamicMiddleware/types.ts diff --git a/packages/toolkit/src/dynamicMiddleware/index.ts b/packages/toolkit/src/dynamicMiddleware/index.ts new file mode 100644 index 0000000000..e588f4109d --- /dev/null +++ b/packages/toolkit/src/dynamicMiddleware/index.ts @@ -0,0 +1,120 @@ +import type { + Middleware, + Dispatch as ReduxDispatch, + AnyAction, + MiddlewareAPI, +} from 'redux' +import { compose } from 'redux' +import { createAction } from '../createAction' +import { nanoid } from '../nanoid' +import type { + WithMiddleware, + AddMiddleware, + MiddlewareEntry, + DynamicMiddleware, + DynamicMiddlewareInstance, +} from './types' + +const createMiddlewareEntry = < + State = any, + Dispatch extends ReduxDispatch = ReduxDispatch +>( + middleware: Middleware +) => { + const id = nanoid() + const entry: MiddlewareEntry = { + id, + middleware, + applied: new Map(), + } + return entry +} + +export const createDynamicMiddleware = < + State = any, + Dispatch extends ReduxDispatch = ReduxDispatch +>(): DynamicMiddlewareInstance => { + const instanceId = nanoid() + const middlewareMap = new Map>() + + const insertEntry = (entry: MiddlewareEntry) => { + middlewareMap.set(entry.id, entry) + } + + const findMiddlewareEntry = ( + comparator: (entry: MiddlewareEntry) => boolean + ): MiddlewareEntry | undefined => { + for (const entry of Array.from(middlewareMap.values())) { + if (comparator(entry)) { + return entry + } + } + + return undefined + } + + const withMiddleware = (() => { + const withMiddleware = createAction( + 'dynamicMiddleware/add', + (...middlewares: Middleware[]) => ({ + payload: middlewares, + meta: { + instanceId, + }, + }) + ) + // @ts-ignore + withMiddleware.withTypes = () => withMiddleware + return withMiddleware as WithMiddleware + })() + + const addMiddleware = (() => { + function addMiddleware(...middlewares: Middleware[]) { + middlewares.forEach((middleware) => { + let entry = findMiddlewareEntry( + (entry) => entry.middleware === middleware + ) + if (!entry) { + entry = createMiddlewareEntry(middleware) + } + insertEntry(entry) + }) + } + addMiddleware.withTypes = () => addMiddleware + return addMiddleware as AddMiddleware + })() + + const getFinalMiddleware = ( + api: MiddlewareAPI + ): ReturnType> => { + const appliedMiddleware = Array.from(middlewareMap.values()).map( + (entry) => { + let applied = entry.applied.get(api) + if (!applied) { + applied = entry.middleware(api) + entry.applied.set(api, applied) + } + return applied + } + ) + return compose(...appliedMiddleware) + } + + const middleware: DynamicMiddleware = + (api) => (next) => (action) => { + if ( + withMiddleware.match(action) && + action.meta.instanceId === instanceId + ) { + addMiddleware(...action.payload) + return api.dispatch + } + return getFinalMiddleware(api)(next)(action) + } + + return { + middleware, + addMiddleware, + withMiddleware, + } +} diff --git a/packages/toolkit/src/dynamicMiddleware/react/index.ts b/packages/toolkit/src/dynamicMiddleware/react/index.ts new file mode 100644 index 0000000000..858cdde7d7 --- /dev/null +++ b/packages/toolkit/src/dynamicMiddleware/react/index.ts @@ -0,0 +1,107 @@ +import type { + Action as ReduxAction, + AnyAction, + Dispatch as ReduxDispatch, + Middleware, +} from 'redux' +import type { ExtractDispatchExtensions } from '@reduxjs/toolkit/dist/tsHelpers' +import { createDynamicMiddleware as cDM } from '@reduxjs/toolkit' +import type { ReactReduxContextValue } from 'react-redux' +import { + ReactReduxContext, + useDispatch as useDefaultDispatch, + createDispatchHook, +} from 'react-redux' +import type { Context } from 'react' +import type { + DynamicMiddlewareInstance, + GetDispatch, + GetState, + MiddlewareApiConfig, +} from '@reduxjs/toolkit/dist/dynamicMiddleware/types' + +export type UseDispatchWithMiddlewareHook< + Middlewares extends Middleware[] = [], + State = any, + Dispatch extends ReduxDispatch = ReduxDispatch +> = () => ExtractDispatchExtensions & Dispatch + +export type CreateDispatchWithMiddlewareHook< + State = any, + Dispatch extends ReduxDispatch = ReduxDispatch +> = { + < + Middlewares extends [ + Middleware, + ...Middleware[] + ] + >( + ...middlewares: Middlewares + ): UseDispatchWithMiddlewareHook + withTypes< + MiddlewareConfig extends MiddlewareApiConfig + >(): CreateDispatchWithMiddlewareHook< + GetState, + GetDispatch + > +} + +type ActionFromDispatch> = + Dispatch extends ReduxDispatch ? Action : never + +interface ReactDynamicMiddlewareInstance< + State = any, + Dispatch extends ReduxDispatch = ReduxDispatch +> extends DynamicMiddlewareInstance { + createDispatchWithMiddlewareHookFactory: ( + context?: Context< + ReactReduxContextValue> + > + ) => CreateDispatchWithMiddlewareHook + createDispatchWithMiddlewareHook: CreateDispatchWithMiddlewareHook< + State, + Dispatch + > +} + +export const createDynamicMiddleware = < + State = any, + Dispatch extends ReduxDispatch = ReduxDispatch +>(): ReactDynamicMiddlewareInstance => { + const instance = cDM() + const createDispatchWithMiddlewareHookFactory = ( + // @ts-ignore + context: Context< + ReactReduxContextValue> + > = ReactReduxContext + ) => { + const useDispatch = + // @ts-ignore + context === ReactReduxContext + ? useDefaultDispatch + : createDispatchHook(context) + function createDispatchWithMiddlewareHook< + Middlewares extends Middleware[] + >(...middlewares: Middlewares) { + instance.addMiddleware(...middlewares) + return function useDispatchWithMiddleware() { + return useDispatch() + } + } + createDispatchWithMiddlewareHook.withTypes = () => + createDispatchWithMiddlewareHook + return createDispatchWithMiddlewareHook as CreateDispatchWithMiddlewareHook< + State, + Dispatch + > + } + + const createDispatchWithMiddlewareHook = + createDispatchWithMiddlewareHookFactory() + + return { + ...instance, + createDispatchWithMiddlewareHookFactory, + createDispatchWithMiddlewareHook, + } +} diff --git a/packages/toolkit/src/dynamicMiddleware/tests/index.test.ts b/packages/toolkit/src/dynamicMiddleware/tests/index.test.ts new file mode 100644 index 0000000000..449a3624fa --- /dev/null +++ b/packages/toolkit/src/dynamicMiddleware/tests/index.test.ts @@ -0,0 +1,69 @@ +import type { Middleware } from 'redux' +import { createDynamicMiddleware } from '../index' +import { configureStore } from '../../configureStore' +import type { BaseActionCreator, PayloadAction } from '../../createAction' +import { createAction } from '../../createAction' + +export interface ProbeMiddleware + extends BaseActionCreator { + (id: Id): PayloadAction +} + +export const probeMiddleware = createAction( + 'probeableMW/probe' +) as ProbeMiddleware + +export const makeProbeableMiddleware = + ( + id: Id + ): Middleware<{ + (action: PayloadAction): Id + }> => + (api) => + (next) => + (action) => { + if (probeMiddleware.match(action) && action.payload === id) { + return id + } + return next(action) + } + +const staticMiddleware = makeProbeableMiddleware(1) + +describe('createDynamicMiddleware', () => { + it('allows injecting middleware after store instantiation', () => { + const dynamicInstance = createDynamicMiddleware() + const store = configureStore({ + reducer: () => 0, + middleware: (gDM) => + gDM().prepend(dynamicInstance.middleware).concat(staticMiddleware), + }) + // normal, pre-inject + expect(store.dispatch(probeMiddleware(2))).toEqual(probeMiddleware(2)) + // static + expect(store.dispatch(probeMiddleware(1))).toBe(1) + + // inject + dynamicInstance.addMiddleware(makeProbeableMiddleware(2)) + + // injected + expect(store.dispatch(probeMiddleware(2))).toBe(2) + }) + it('returns dispatch when withMiddleware is dispatched', () => { + const dynamicInstance = createDynamicMiddleware() + const store = configureStore({ + reducer: () => 0, + middleware: (gDM) => gDM().prepend(dynamicInstance.middleware), + }) + + // normal, pre-inject + expect(store.dispatch(probeMiddleware(2))).toEqual(probeMiddleware(2)) + + const dispatch = store.dispatch( + dynamicInstance.withMiddleware(makeProbeableMiddleware(2)) + ) + expect(dispatch).toEqual(expect.any(Function)) + + expect(dispatch(probeMiddleware(2))).toBe(2) + }) +}) diff --git a/packages/toolkit/src/dynamicMiddleware/tests/index.typetest.ts b/packages/toolkit/src/dynamicMiddleware/tests/index.typetest.ts new file mode 100644 index 0000000000..1d4a1e14fa --- /dev/null +++ b/packages/toolkit/src/dynamicMiddleware/tests/index.typetest.ts @@ -0,0 +1,102 @@ +/* eslint-disable no-lone-blocks */ +import type { Action, AnyAction, Middleware } from 'redux' +import type { ThunkDispatch } from 'redux-thunk' +import { createDynamicMiddleware } from '../index' +import { configureStore } from '../../configureStore' +import { expectExactType, expectType } from '../../tests/helpers' + +const untypedInstance = createDynamicMiddleware() + +interface AppDispatch extends ThunkDispatch { + (n: 1): 1 +} + +const typedInstance = createDynamicMiddleware() + +/** + * Test: instance typed at creation ensures middleware compatibility with store + */ +{ + const store = configureStore({ + reducer: () => '', + // @ts-expect-error + middleware: (gDM) => gDM().prepend(typedInstance.middleware), + }) +} + +declare const staticMiddleware: Middleware<(n: 1) => 1> + +const store = configureStore({ + reducer: () => 0, + middleware: (gDM) => + gDM().prepend(typedInstance.middleware).concat(staticMiddleware), +}) + +declare const compatibleMiddleware: Middleware<{}, number, AppDispatch> +declare const incompatibleMiddleware: Middleware<{}, string, AppDispatch> + +/** + * Test: instance typed at creation enforces correct middleware type + */ +{ + typedInstance.addMiddleware( + compatibleMiddleware, + // @ts-expect-error + incompatibleMiddleware + ) + + const dispatch = store.dispatch( + typedInstance.withMiddleware( + compatibleMiddleware, + // @ts-expect-error + incompatibleMiddleware + ) + ) +} + +/** + * Test: withTypes() enforces correct middleware type + */ +{ + const addMiddleware = untypedInstance.addMiddleware.withTypes<{ + state: number + dispatch: AppDispatch + }>() + + addMiddleware( + compatibleMiddleware, + // @ts-expect-error + incompatibleMiddleware + ) + + const withMiddleware = untypedInstance.withMiddleware.withTypes<{ + state: number + dispatch: AppDispatch + }>() + + const dispatch = store.dispatch( + withMiddleware( + compatibleMiddleware, + // @ts-expect-error + incompatibleMiddleware + ) + ) +} + +declare const addedMiddleware: Middleware<(n: 2) => 2> + +/** + * Test: withMiddleware returns typed dispatch, with any applicable extensions + */ +{ + const dispatch = store.dispatch(typedInstance.withMiddleware(addedMiddleware)) + + // standard + expectType>(dispatch({ type: 'foo' })) + // thunk + expectType(dispatch(() => 'foo')) + // static + expectExactType(1 as const)(dispatch(1)) + // added + expectExactType(2 as const)(dispatch(2)) +} diff --git a/packages/toolkit/src/dynamicMiddleware/tests/react.test.tsx b/packages/toolkit/src/dynamicMiddleware/tests/react.test.tsx new file mode 100644 index 0000000000..86581586d2 --- /dev/null +++ b/packages/toolkit/src/dynamicMiddleware/tests/react.test.tsx @@ -0,0 +1,101 @@ +import * as React from 'react' +import { createDynamicMiddleware } from '../react' +import { configureStore } from '../../configureStore' +import { makeProbeableMiddleware, probeMiddleware } from './index.test' +import { render } from '@testing-library/react' +import type { Dispatch } from 'redux' +import type { ReactReduxContextValue } from 'react-redux' +import { Provider } from 'react-redux' + +const staticMiddleware = makeProbeableMiddleware(1) + +describe('createReactDynamicMiddleware', () => { + describe('createDispatchWithMiddlewareHook', () => { + it('injects middleware upon creation', () => { + const dynamicInstance = createDynamicMiddleware() + const store = configureStore({ + reducer: () => 0, + middleware: (gDM) => + gDM().prepend(dynamicInstance.middleware).concat(staticMiddleware), + }) + // normal, pre-inject + expect(store.dispatch(probeMiddleware(2))).toEqual(probeMiddleware(2)) + // static + expect(store.dispatch(probeMiddleware(1))).toBe(1) + + const useDispatch = dynamicInstance.createDispatchWithMiddlewareHook( + makeProbeableMiddleware(2) + ) + + // injected + expect(store.dispatch(probeMiddleware(2))).toBe(2) + }) + + it('returns dispatch', () => { + const dynamicInstance = createDynamicMiddleware() + const store = configureStore({ + reducer: () => 0, + middleware: (gDM) => + gDM().prepend(dynamicInstance.middleware).concat(staticMiddleware), + }) + + const useDispatch = dynamicInstance.createDispatchWithMiddlewareHook( + makeProbeableMiddleware(2) + ) + + let dispatch: Dispatch | undefined + function Component() { + dispatch = useDispatch() + + return null + } + render(, { + wrapper: ({ children }) => ( + {children} + ), + }) + expect(dispatch).toBe(store.dispatch) + }) + }) + describe('createDispatchWithMiddlewareHookFactory', () => { + it('returns the correct store dispatch', () => { + const dynamicInstance = createDynamicMiddleware() + const store = configureStore({ + reducer: () => 0, + middleware: (gDM) => + gDM().prepend(dynamicInstance.middleware).concat(staticMiddleware), + }) + const store2 = configureStore({ + reducer: () => '', + middleware: (gDM) => + gDM().prepend(dynamicInstance.middleware).concat(staticMiddleware), + }) + + const context = React.createContext(null as any) + + const createDispatchWithMiddlewareHook = + dynamicInstance.createDispatchWithMiddlewareHookFactory(context) + + const useDispatch = createDispatchWithMiddlewareHook( + makeProbeableMiddleware(2) + ) + + let dispatch: Dispatch | undefined + function Component() { + dispatch = useDispatch() + + return null + } + render(, { + wrapper: ({ children }) => ( + + + {children} + + + ), + }) + expect(dispatch).toBe(store2.dispatch) + }) + }) +}) diff --git a/packages/toolkit/src/dynamicMiddleware/tests/react.typetest.ts b/packages/toolkit/src/dynamicMiddleware/tests/react.typetest.ts new file mode 100644 index 0000000000..db664b4d54 --- /dev/null +++ b/packages/toolkit/src/dynamicMiddleware/tests/react.typetest.ts @@ -0,0 +1,89 @@ +/* eslint-disable no-lone-blocks */ +import type { Context } from 'react' +import type { ReactReduxContextValue } from 'react-redux' +import type { Action, AnyAction, Middleware } from 'redux' +import type { ThunkDispatch } from 'redux-thunk' +import { createDynamicMiddleware } from '../react' +import { expectExactType, expectType } from '../../tests/helpers' +/* eslint-disable no-lone-blocks */ + +interface AppDispatch extends ThunkDispatch { + (n: 1): 1 +} + +const untypedInstance = createDynamicMiddleware() + +const typedInstance = createDynamicMiddleware() + +declare const compatibleMiddleware: Middleware<{}, number, AppDispatch> +declare const incompatibleMiddleware: Middleware<{}, string, AppDispatch> + +declare const customContext: Context + +/** + * Test: instance typed at creation enforces correct middleware type + */ +{ + const useDispatch = typedInstance.createDispatchWithMiddlewareHook( + compatibleMiddleware, + // @ts-expect-error + incompatibleMiddleware + ) + + const createDispatchWithMiddlewareHook = + typedInstance.createDispatchWithMiddlewareHookFactory(customContext) + const useDispatchWithContext = createDispatchWithMiddlewareHook( + compatibleMiddleware, + // @ts-expect-error + incompatibleMiddleware + ) +} + +/** + * Test: withTypes() enforces correct middleware type + */ +{ + const createDispatchWithMiddlewareHook = + untypedInstance.createDispatchWithMiddlewareHook.withTypes<{ + state: number + dispatch: AppDispatch + }>() + const useDispatch = createDispatchWithMiddlewareHook( + compatibleMiddleware, + // @ts-expect-error + incompatibleMiddleware + ) + + const createCustomDispatchWithMiddlewareHook = untypedInstance + .createDispatchWithMiddlewareHookFactory(customContext) + .withTypes<{ + state: number + dispatch: AppDispatch + }>() + const useCustomDispatch = createCustomDispatchWithMiddlewareHook( + compatibleMiddleware, + // @ts-expect-error + incompatibleMiddleware + ) +} + +declare const addedMiddleware: Middleware<(n: 2) => 2> + +/** + * Test: useDispatchWithMW returns typed dispatch, with any applicable extensions + */ +{ + const useDispatchWithMW = + typedInstance.createDispatchWithMiddlewareHook(addedMiddleware) + // eslint-disable-next-line react-hooks/rules-of-hooks + const dispatch = useDispatchWithMW() + + // standard + expectType>(dispatch({ type: 'foo' })) + // thunk + expectType(dispatch(() => 'foo')) + // static + expectExactType(1 as const)(dispatch(1)) + // added + expectExactType(2 as const)(dispatch(2)) +} diff --git a/packages/toolkit/src/dynamicMiddleware/types.ts b/packages/toolkit/src/dynamicMiddleware/types.ts new file mode 100644 index 0000000000..c96689141d --- /dev/null +++ b/packages/toolkit/src/dynamicMiddleware/types.ts @@ -0,0 +1,92 @@ +import type { + Middleware, + Dispatch as ReduxDispatch, + AnyAction, + MiddlewareAPI, +} from 'redux' +import type { ExtractDispatchExtensions, FallbackIfUnknown } from '../tsHelpers' +import type { PayloadAction, BaseActionCreator } from '../createAction' + +export type GetMiddlewareApi = MiddlewareAPI< + GetDispatch, + GetState +> + +export type MiddlewareApiConfig = { + state?: unknown + dispatch?: ReduxDispatch +} + +// TODO: consolidate with cAT helpers? +export type GetState = MiddlewareApiConfig extends { + state: infer State +} + ? State + : unknown + +export type GetDispatch = MiddlewareApiConfig extends { + dispatch: infer Dispatch +} + ? FallbackIfUnknown + : ReduxDispatch + +export type AddMiddleware< + State = any, + Dispatch extends ReduxDispatch = ReduxDispatch +> = { + (...middlewares: Middleware[]): void + withTypes(): AddMiddleware< + GetState, + GetDispatch + > +} + +export interface WithMiddleware< + State = any, + Dispatch extends ReduxDispatch = ReduxDispatch +> extends BaseActionCreator< + Middleware[], + 'dynamicMiddleware/add', + { instanceId: string } + > { + []>( + ...middlewares: Middlewares + ): PayloadAction + withTypes(): WithMiddleware< + GetState, + GetDispatch + > +} + +export interface DynamicDispatch { + // return a version of dispatch that knows about middleware + []>( + action: PayloadAction + ): ExtractDispatchExtensions & this +} + +export type MiddlewareEntry< + State = unknown, + Dispatch extends ReduxDispatch = ReduxDispatch +> = { + id: string + middleware: Middleware + applied: Map< + MiddlewareAPI, + ReturnType> + > +} + +export type DynamicMiddleware< + State = unknown, + Dispatch extends ReduxDispatch = ReduxDispatch +> = Middleware + +export type DynamicMiddlewareInstance< + State = unknown, + Dispatch extends ReduxDispatch = ReduxDispatch +> = { + middleware: DynamicMiddleware + addMiddleware: AddMiddleware + withMiddleware: WithMiddleware +} diff --git a/packages/toolkit/src/index.ts b/packages/toolkit/src/index.ts index 0c8737e8b4..4e13791fb6 100644 --- a/packages/toolkit/src/index.ts +++ b/packages/toolkit/src/index.ts @@ -178,6 +178,8 @@ export { TaskAbortError, } from './listenerMiddleware/index' +export { createDynamicMiddleware } from './dynamicMiddleware/index' + export { SHOULD_AUTOBATCH, prepareAutoBatched, diff --git a/packages/toolkit/src/react/index.ts b/packages/toolkit/src/react/index.ts index bc902129a9..ed558047c4 100644 --- a/packages/toolkit/src/react/index.ts +++ b/packages/toolkit/src/react/index.ts @@ -1 +1,3 @@ export * from '@reduxjs/toolkit' + +export { createDynamicMiddleware } from '../dynamicMiddleware/react' diff --git a/packages/toolkit/tsconfig.base.json b/packages/toolkit/tsconfig.base.json index 90d908e065..dc7dbb02b2 100644 --- a/packages/toolkit/tsconfig.base.json +++ b/packages/toolkit/tsconfig.base.json @@ -39,6 +39,8 @@ "@reduxjs/toolkit/query": ["src/query/index.ts"], // @remap-prod-remove-line "@reduxjs/toolkit/query/react": ["src/query/react/index.ts"], // @remap-prod-remove-line // for type imports in tests only + "@reduxjs/toolkit/dist/*": ["src/*"], // @remap-prod-remove-line + // for type imports in tests only "@reduxjs/toolkit/dist/query/*": ["src/query/*"], // @remap-prod-remove-line // internal imports in tests only "@internal/*": ["src/*"] From 04ee5d3fbf9268953afad568e88a5dd9e9f10cfc Mon Sep 17 00:00:00 2001 From: "ben.durrant" Date: Fri, 24 Mar 2023 17:08:30 +0000 Subject: [PATCH 036/412] add export field --- packages/toolkit/package.json | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/packages/toolkit/package.json b/packages/toolkit/package.json index 253d722b4d..ae0604d305 100644 --- a/packages/toolkit/package.json +++ b/packages/toolkit/package.json @@ -33,6 +33,11 @@ "import": "./dist/redux-toolkit.modern.js", "default": "./dist/cjs/index.js" }, + "./react": { + "types": "./dist/react/index.d.ts", + "import": "./dist/react/redux-toolkit-react.modern.js", + "default": "./dist/react/cjs/index.js" + }, "./query": { "types": "./dist/query/index.d.ts", "import": "./dist/query/rtk-query.modern.js", From 9e593c12abc86adeb9999d6a75177221b34e51f0 Mon Sep 17 00:00:00 2001 From: Ben Durrant Date: Fri, 24 Mar 2023 23:48:59 +0000 Subject: [PATCH 037/412] vitest alias --- packages/toolkit/vitest.config.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/toolkit/vitest.config.ts b/packages/toolkit/vitest.config.ts index b010d80498..0df173bd94 100644 --- a/packages/toolkit/vitest.config.ts +++ b/packages/toolkit/vitest.config.ts @@ -9,6 +9,7 @@ export default defineConfig({ alias: { '@reduxjs/toolkit/query/react': './src/query/react/index.ts', // @remap-prod-remove-line '@reduxjs/toolkit/query': './src/query/index.ts', // @remap-prod-remove-line + '@reduxjs/toolkit/react': './src/index.ts', // @remap-prod-remove-line '@reduxjs/toolkit': './src/index.ts', // @remap-prod-remove-line // this mapping is disabled as we want `dist` imports in the tests only to be used for "type-only" imports which don't play a role for jest From 4baea18660b8c9f9023419438c251865aa3a34dd Mon Sep 17 00:00:00 2001 From: Ben Durrant Date: Sun, 26 Mar 2023 00:30:30 +0000 Subject: [PATCH 038/412] find map entry finders into generic iterable helper --- .../toolkit/src/dynamicMiddleware/index.ts | 16 +++------------- .../toolkit/src/listenerMiddleware/index.ts | 18 ++++-------------- packages/toolkit/src/utils.ts | 13 +++++++++++++ 3 files changed, 20 insertions(+), 27 deletions(-) diff --git a/packages/toolkit/src/dynamicMiddleware/index.ts b/packages/toolkit/src/dynamicMiddleware/index.ts index e588f4109d..6df4a41426 100644 --- a/packages/toolkit/src/dynamicMiddleware/index.ts +++ b/packages/toolkit/src/dynamicMiddleware/index.ts @@ -7,6 +7,7 @@ import type { import { compose } from 'redux' import { createAction } from '../createAction' import { nanoid } from '../nanoid' +import { find } from '../utils' import type { WithMiddleware, AddMiddleware, @@ -41,18 +42,6 @@ export const createDynamicMiddleware = < middlewareMap.set(entry.id, entry) } - const findMiddlewareEntry = ( - comparator: (entry: MiddlewareEntry) => boolean - ): MiddlewareEntry | undefined => { - for (const entry of Array.from(middlewareMap.values())) { - if (comparator(entry)) { - return entry - } - } - - return undefined - } - const withMiddleware = (() => { const withMiddleware = createAction( 'dynamicMiddleware/add', @@ -71,7 +60,8 @@ export const createDynamicMiddleware = < const addMiddleware = (() => { function addMiddleware(...middlewares: Middleware[]) { middlewares.forEach((middleware) => { - let entry = findMiddlewareEntry( + let entry = find( + Array.from(middlewareMap.values()), (entry) => entry.middleware === middleware ) if (!entry) { diff --git a/packages/toolkit/src/listenerMiddleware/index.ts b/packages/toolkit/src/listenerMiddleware/index.ts index d96b9257db..2a5bcedaf3 100644 --- a/packages/toolkit/src/listenerMiddleware/index.ts +++ b/packages/toolkit/src/listenerMiddleware/index.ts @@ -44,6 +44,7 @@ import { createDelay, raceWithSignal, } from './task' +import { find } from '../utils' export { TaskAbortError } from './exceptions' export type { ListenerEffect, @@ -317,20 +318,9 @@ export function createListenerMiddleware< } } - const findListenerEntry = ( - comparator: (entry: ListenerEntry) => boolean - ): ListenerEntry | undefined => { - for (const entry of Array.from(listenerMap.values())) { - if (comparator(entry)) { - return entry - } - } - - return undefined - } - const startListening = (options: FallbackAddListenerOptions) => { - let entry = findListenerEntry( + let entry = find( + Array.from(listenerMap.values()), (existingEntry) => existingEntry.effect === options.effect ) @@ -346,7 +336,7 @@ export function createListenerMiddleware< ): boolean => { const { type, effect, predicate } = getListenerEntryPropsFrom(options) - const entry = findListenerEntry((entry) => { + const entry = find(Array.from(listenerMap.values()), (entry) => { const matchPredicateOrType = typeof type === 'string' ? entry.type === type diff --git a/packages/toolkit/src/utils.ts b/packages/toolkit/src/utils.ts index 9697d31bb6..6cf7712157 100644 --- a/packages/toolkit/src/utils.ts +++ b/packages/toolkit/src/utils.ts @@ -27,6 +27,19 @@ export function delay(ms: number) { return new Promise((resolve) => setTimeout(resolve, ms)) } +export function find( + iterable: Iterable, + comparator: (item: T) => boolean +): T | undefined { + for (const entry of iterable) { + if (comparator(entry)) { + return entry + } + } + + return undefined +} + /** * @public */ From 3c3bc4d39119d5ad3cfb5337a52e56521ae75cca Mon Sep 17 00:00:00 2001 From: Ben Durrant Date: Sun, 26 Mar 2023 14:56:23 +0100 Subject: [PATCH 039/412] experiment with API typing --- packages/toolkit/src/combineSlices.ts | 133 ++++++++++++++++++++++++++ packages/toolkit/src/tsHelpers.ts | 6 ++ 2 files changed, 139 insertions(+) create mode 100644 packages/toolkit/src/combineSlices.ts diff --git a/packages/toolkit/src/combineSlices.ts b/packages/toolkit/src/combineSlices.ts new file mode 100644 index 0000000000..fe539f8e40 --- /dev/null +++ b/packages/toolkit/src/combineSlices.ts @@ -0,0 +1,133 @@ +import type { CombinedState, AnyAction, Reducer } from 'redux' +import type { Slice } from './createSlice' +import { configureStore } from './configureStore' +import type { + Id, + UnionToIntersection, + WithOptionalProp, + WithRequiredProp, +} from './tsHelpers' +import { createSelector } from 'reselect' + +type AnySlice = Slice + +type SliceState = Sl extends Slice + ? State + : never + +type SliceName = Sl extends Slice + ? Name + : never + +export type WithSlice = Id< + // distribute + Sl extends AnySlice + ? { + [K in SliceName]: SliceState + } + : never +> + +// only allow injection of slices we've already declared +type LazyLoadedSlice> = { + [Name in keyof LazyLoadedState]: Name extends string + ? Slice + : never +}[keyof LazyLoadedState] + +type CombinedSliceState< + StaticState, + LazyLoadedState extends Record = {}, + InjectedKeys extends keyof LazyLoadedState = never +> = Id< + // TODO: use PreloadedState generic instead + CombinedState< + StaticState & WithRequiredProp, InjectedKeys> + > +> + +interface CombinedSliceReducer< + StaticState, + LazyLoadedState extends Record = {}, + InjectedKeys extends keyof LazyLoadedState = never +> extends Reducer< + CombinedSliceState, + AnyAction + > { + withLazyLoadedSlices< + Lazy extends Record + >(): CombinedSliceReducer + + injectSlices< + Slices extends [ + LazyLoadedSlice, + ...LazyLoadedSlice[] + ] + >( + ...slices: Slices + ): CombinedSliceReducer< + StaticState, + LazyLoadedState, + InjectedKeys | SliceName + > + + // TODO: deal with nested state? + selector< + Selected, + State extends CombinedSliceState< + StaticState, + LazyLoadedState, + InjectedKeys + >, + Args extends any[] + >( + selectorFn: (state: State, ...args: Args) => Selected + ): (state: WithOptionalProp, ...args: Args) => Selected +} + +// TODO: support arbitrary { key: reducer } objects +declare const combineSlices: ( + ...slices: Slices +) => CombinedSliceReducer>>> + +// test it works + +declare const fooSlice: Slice<'foo', {}, 'foo'> + +declare const barSlice: Slice<'bar', {}, 'bar'> + +declare const bazSlice: Slice<'baz', {}, 'baz'> + +const baseReducer = combineSlices(fooSlice, barSlice).withLazyLoadedSlices<{ + [bazSlice.name]: ReturnType +}>() + +const store = configureStore({ + reducer: baseReducer, +}) + +type RootState = ReturnType + +const withoutInjection = baseReducer.selector((state) => state.bar) + +const selector1 = withoutInjection(store.getState()) +// ^? + +const withInjection = baseReducer + .injectSlices(bazSlice) + .selector((state) => state.bar) + +const selector2 = withInjection(store.getState()) +// ^? + +const memoized = baseReducer.injectSlices(bazSlice).selector( + createSelector( + // can't be inferred + (state: RootState & WithSlice) => state.baz, + (_: unknown, id: string) => id, + (state, id) => `${state.length}${id}` as const + ) +) + +const selector3 = memoized(store.getState(), 'id') +// ^? diff --git a/packages/toolkit/src/tsHelpers.ts b/packages/toolkit/src/tsHelpers.ts index 3077037731..1fe3193a1d 100644 --- a/packages/toolkit/src/tsHelpers.ts +++ b/packages/toolkit/src/tsHelpers.ts @@ -122,6 +122,12 @@ export type NoInfer = [T][T extends any ? 0 : never] export type Omit = Pick> +export type WithRequiredProp = Omit & + Required> + +export type WithOptionalProp = Omit & + Partial> + export interface TypeGuard { (value: any): value is T } From a50c9684d7e34f98bfefd2eee4114f32f23685c6 Mon Sep 17 00:00:00 2001 From: Ben Durrant Date: Sun, 26 Mar 2023 16:47:49 +0100 Subject: [PATCH 040/412] support reducer map objects --- packages/toolkit/src/combineSlices.ts | 98 +++++++++++++++++++++------ 1 file changed, 76 insertions(+), 22 deletions(-) diff --git a/packages/toolkit/src/combineSlices.ts b/packages/toolkit/src/combineSlices.ts index fe539f8e40..60ec3e71c2 100644 --- a/packages/toolkit/src/combineSlices.ts +++ b/packages/toolkit/src/combineSlices.ts @@ -1,4 +1,9 @@ -import type { CombinedState, AnyAction, Reducer } from 'redux' +import type { + CombinedState, + AnyAction, + Reducer, + StateFromReducersMapObject, +} from 'redux' import type { Slice } from './createSlice' import { configureStore } from './configureStore' import type { @@ -11,6 +16,8 @@ import { createSelector } from 'reselect' type AnySlice = Slice +type ReducerMap = Record + type SliceState = Sl extends Slice ? State : never @@ -20,12 +27,9 @@ type SliceName = Sl extends Slice : never export type WithSlice = Id< - // distribute - Sl extends AnySlice - ? { - [K in SliceName]: SliceState - } - : never + { + [K in SliceName]: SliceState + } > // only allow injection of slices we've already declared @@ -35,6 +39,10 @@ type LazyLoadedSlice> = { : never }[keyof LazyLoadedState] +type LazyLoadedReducerMap> = { + [Name in keyof LazyLoadedState]?: Reducer +} + type CombinedSliceState< StaticState, LazyLoadedState extends Record = {}, @@ -46,6 +54,20 @@ type CombinedSliceState< > > +type NewKeys< + LazyLoadedState extends Record, + Slices extends [ + LazyLoadedSlice | LazyLoadedReducerMap, + ...Array< + LazyLoadedSlice | LazyLoadedReducerMap + > + ] +> = Slices[number] extends infer Slice + ? Slice extends AnySlice + ? SliceName + : keyof Slice + : never + interface CombinedSliceReducer< StaticState, LazyLoadedState extends Record = {}, @@ -60,15 +82,17 @@ interface CombinedSliceReducer< injectSlices< Slices extends [ - LazyLoadedSlice, - ...LazyLoadedSlice[] + LazyLoadedSlice | LazyLoadedReducerMap, + ...Array< + LazyLoadedSlice | LazyLoadedReducerMap + > ] >( ...slices: Slices ): CombinedSliceReducer< StaticState, LazyLoadedState, - InjectedKeys | SliceName + InjectedKeys | NewKeys > // TODO: deal with nested state? @@ -85,22 +109,37 @@ interface CombinedSliceReducer< ): (state: WithOptionalProp, ...args: Args) => Selected } -// TODO: support arbitrary { key: reducer } objects -declare const combineSlices: ( +type StaticState< + Slices extends [AnySlice | ReducerMap, ...Array] +> = UnionToIntersection< + Slices[number] extends infer Slice + ? Slice extends AnySlice + ? WithSlice + : StateFromReducersMapObject + : never +> + +declare const combineSlices: < + Slices extends [AnySlice | ReducerMap, ...Array] +>( ...slices: Slices -) => CombinedSliceReducer>>> +) => CombinedSliceReducer>> // test it works declare const fooSlice: Slice<'foo', {}, 'foo'> -declare const barSlice: Slice<'bar', {}, 'bar'> +declare const barReducer: Reducer<'bar'> declare const bazSlice: Slice<'baz', {}, 'baz'> -const baseReducer = combineSlices(fooSlice, barSlice).withLazyLoadedSlices<{ - [bazSlice.name]: ReturnType -}>() +const baseReducer = combineSlices(fooSlice, { + bar2: barReducer, +}).withLazyLoadedSlices< + WithSlice & { + bar2: ReturnType + } +>() const store = configureStore({ reducer: baseReducer, @@ -108,19 +147,34 @@ const store = configureStore({ type RootState = ReturnType -const withoutInjection = baseReducer.selector((state) => state.bar) +const withoutInjection = baseReducer.selector((state) => state.baz) const selector1 = withoutInjection(store.getState()) // ^? const withInjection = baseReducer - .injectSlices(bazSlice) - .selector((state) => state.bar) + .injectSlices(bazSlice, { + bar2: barReducer, + }) + .selector((state) => state.baz) const selector2 = withInjection(store.getState()) // ^? -const memoized = baseReducer.injectSlices(bazSlice).selector( +const memoizedWithoutInjection = baseReducer.selector( + createSelector( + // can't be inferred + (state: RootState & WithSlice) => state.baz, + (_: unknown, id: string) => id, + (state, id) => `${state?.length}${id}` as const + ) +) + +// @ts-expect-error doesn't guarantee injection, so errors +const selector3 = memoizedWithoutInjection(store.getState(), 'id') +// ^? + +const memoizedWithInjection = baseReducer.injectSlices(bazSlice).selector( createSelector( // can't be inferred (state: RootState & WithSlice) => state.baz, @@ -129,5 +183,5 @@ const memoized = baseReducer.injectSlices(bazSlice).selector( ) ) -const selector3 = memoized(store.getState(), 'id') +const selector4 = memoizedWithInjection(store.getState(), 'id') // ^? From 00762a27bc2b43357622c7b9dd9628b1f28d12d3 Mon Sep 17 00:00:00 2001 From: Ben Durrant Date: Sun, 26 Mar 2023 17:05:55 +0100 Subject: [PATCH 041/412] Prevent undeclared keys in reducer maps --- packages/toolkit/src/combineSlices.ts | 36 +++++++++++++++++++++++---- 1 file changed, 31 insertions(+), 5 deletions(-) diff --git a/packages/toolkit/src/combineSlices.ts b/packages/toolkit/src/combineSlices.ts index 60ec3e71c2..e782275995 100644 --- a/packages/toolkit/src/combineSlices.ts +++ b/packages/toolkit/src/combineSlices.ts @@ -54,6 +54,26 @@ type CombinedSliceState< > > +// Prevent undeclared keys in reducer maps +type ValidateReducerMaps< + LazyLoadedState extends Record, + Slices extends [ + LazyLoadedSlice | LazyLoadedReducerMap, + ...Array< + LazyLoadedSlice | LazyLoadedReducerMap + > + ] +> = Slices & + { + [Index in keyof Slices]: Slices[Index] extends AnySlice + ? {} + : { + [Name in keyof Slices[Index]]: Name extends keyof LazyLoadedState + ? Reducer + : never + } + } + type NewKeys< LazyLoadedState extends Record, Slices extends [ @@ -88,7 +108,7 @@ interface CombinedSliceReducer< > ] >( - ...slices: Slices + ...slices: ValidateReducerMaps ): CombinedSliceReducer< StaticState, LazyLoadedState, @@ -135,11 +155,11 @@ declare const bazSlice: Slice<'baz', {}, 'baz'> const baseReducer = combineSlices(fooSlice, { bar2: barReducer, -}).withLazyLoadedSlices< - WithSlice & { +}) + .withLazyLoadedSlices>() + .withLazyLoadedSlices<{ bar2: ReturnType - } ->() + }>() const store = configureStore({ reducer: baseReducer, @@ -158,6 +178,12 @@ const withInjection = baseReducer }) .selector((state) => state.baz) +// @ts-expect-error unexpected key +baseReducer.injectSlices({ + bar2: barReducer, + bar3: barReducer, +}) + const selector2 = withInjection(store.getState()) // ^? From 5b81a3a854500adfd298772c25856f13fff6f864 Mon Sep 17 00:00:00 2001 From: Ben Durrant Date: Sun, 26 Mar 2023 17:40:41 +0100 Subject: [PATCH 042/412] basic implementation --- packages/toolkit/src/combineSlices.ts | 51 ++++++++++++++++++++++++--- packages/toolkit/src/tsHelpers.ts | 7 ++++ 2 files changed, 54 insertions(+), 4 deletions(-) diff --git a/packages/toolkit/src/combineSlices.ts b/packages/toolkit/src/combineSlices.ts index e782275995..a8afec7033 100644 --- a/packages/toolkit/src/combineSlices.ts +++ b/packages/toolkit/src/combineSlices.ts @@ -4,6 +4,7 @@ import type { Reducer, StateFromReducersMapObject, } from 'redux' +import { combineReducers } from 'redux' import type { Slice } from './createSlice' import { configureStore } from './configureStore' import type { @@ -12,6 +13,7 @@ import type { WithOptionalProp, WithRequiredProp, } from './tsHelpers' +import { safeAssign } from './tsHelpers' import { createSelector } from 'reselect' type AnySlice = Slice @@ -139,11 +141,52 @@ type StaticState< : never > -declare const combineSlices: < +const isSlice = (maybeSlice: AnySlice | ReducerMap): maybeSlice is AnySlice => + typeof maybeSlice.actions === 'object' + +export function combineSlices< Slices extends [AnySlice | ReducerMap, ...Array] ->( - ...slices: Slices -) => CombinedSliceReducer>> +>(...slices: Slices): CombinedSliceReducer>> { + const reducerMap = slices.reduce>((map, slice) => { + if (isSlice(slice)) { + map[slice.name] = slice.reducer + } else { + safeAssign(map, slice) + } + return map + }, {}) + + const getReducer = () => combineReducers(reducerMap) + + let reducer = getReducer() + + function combinedReducer(state: Record, action: AnyAction) { + return reducer(state, action) + } + + combinedReducer.withLazyLoadedSlices = () => combinedReducer + + combinedReducer.injectSlices = (...slices: Array) => { + slices.forEach((slice) => { + if (isSlice(slice)) { + reducerMap[slice.name] = slice.reducer + } else { + safeAssign(reducerMap, slice) + } + }) + reducer = getReducer() + } + + combinedReducer.selector = + ( + selectorFn: (state: State, ...args: Args) => any + ) => + (state: State, ...args: Args) => + // TODO: ensure injected reducers have state + selectorFn(state, ...args) + + return combinedReducer as any +} // test it works diff --git a/packages/toolkit/src/tsHelpers.ts b/packages/toolkit/src/tsHelpers.ts index 1fe3193a1d..6a71dc4aa0 100644 --- a/packages/toolkit/src/tsHelpers.ts +++ b/packages/toolkit/src/tsHelpers.ts @@ -1,6 +1,13 @@ import type { Middleware, StoreEnhancer } from 'redux' import type { MiddlewareArray } from './utils' +export function safeAssign( + target: T, + ...args: Array>> +) { + Object.assign(target, ...args) +} + /** * return True if T is `any`, otherwise return False * taken from https://github.com/joonhocho/tsdef From 1397a52a8e033c68facdd89e5647d11999fbb783 Mon Sep 17 00:00:00 2001 From: Ben Durrant Date: Sun, 26 Mar 2023 17:45:45 +0100 Subject: [PATCH 043/412] export from entry point --- packages/toolkit/src/index.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/packages/toolkit/src/index.ts b/packages/toolkit/src/index.ts index 0c8737e8b4..f92ec1b0a8 100644 --- a/packages/toolkit/src/index.ts +++ b/packages/toolkit/src/index.ts @@ -184,3 +184,7 @@ export { autoBatchEnhancer, } from './autoBatchEnhancer' export type { AutoBatchOptions } from './autoBatchEnhancer' + +export { combineSlices } from './combineSlices' + +export type { WithSlice } from './combineSlices' From 075811e6f9138c0b12b82eeb55cb63b4deb352b8 Mon Sep 17 00:00:00 2001 From: Ben Durrant Date: Sun, 26 Mar 2023 17:47:40 +0100 Subject: [PATCH 044/412] rm inline typetest --- packages/toolkit/src/combineSlices.ts | 69 --------------------------- 1 file changed, 69 deletions(-) diff --git a/packages/toolkit/src/combineSlices.ts b/packages/toolkit/src/combineSlices.ts index a8afec7033..99412f11e5 100644 --- a/packages/toolkit/src/combineSlices.ts +++ b/packages/toolkit/src/combineSlices.ts @@ -6,7 +6,6 @@ import type { } from 'redux' import { combineReducers } from 'redux' import type { Slice } from './createSlice' -import { configureStore } from './configureStore' import type { Id, UnionToIntersection, @@ -14,7 +13,6 @@ import type { WithRequiredProp, } from './tsHelpers' import { safeAssign } from './tsHelpers' -import { createSelector } from 'reselect' type AnySlice = Slice @@ -187,70 +185,3 @@ export function combineSlices< return combinedReducer as any } - -// test it works - -declare const fooSlice: Slice<'foo', {}, 'foo'> - -declare const barReducer: Reducer<'bar'> - -declare const bazSlice: Slice<'baz', {}, 'baz'> - -const baseReducer = combineSlices(fooSlice, { - bar2: barReducer, -}) - .withLazyLoadedSlices>() - .withLazyLoadedSlices<{ - bar2: ReturnType - }>() - -const store = configureStore({ - reducer: baseReducer, -}) - -type RootState = ReturnType - -const withoutInjection = baseReducer.selector((state) => state.baz) - -const selector1 = withoutInjection(store.getState()) -// ^? - -const withInjection = baseReducer - .injectSlices(bazSlice, { - bar2: barReducer, - }) - .selector((state) => state.baz) - -// @ts-expect-error unexpected key -baseReducer.injectSlices({ - bar2: barReducer, - bar3: barReducer, -}) - -const selector2 = withInjection(store.getState()) -// ^? - -const memoizedWithoutInjection = baseReducer.selector( - createSelector( - // can't be inferred - (state: RootState & WithSlice) => state.baz, - (_: unknown, id: string) => id, - (state, id) => `${state?.length}${id}` as const - ) -) - -// @ts-expect-error doesn't guarantee injection, so errors -const selector3 = memoizedWithoutInjection(store.getState(), 'id') -// ^? - -const memoizedWithInjection = baseReducer.injectSlices(bazSlice).selector( - createSelector( - // can't be inferred - (state: RootState & WithSlice) => state.baz, - (_: unknown, id: string) => id, - (state, id) => `${state.length}${id}` as const - ) -) - -const selector4 = memoizedWithInjection(store.getState(), 'id') -// ^? From 99945ebc337efd2dc3ceb593caacd4446105cfde Mon Sep 17 00:00:00 2001 From: Ben Durrant Date: Sun, 26 Mar 2023 18:26:08 +0100 Subject: [PATCH 045/412] throw dev error if new reducer is injected when one already exists (when both aren't same) --- packages/toolkit/src/combineSlices.ts | 22 +++++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/packages/toolkit/src/combineSlices.ts b/packages/toolkit/src/combineSlices.ts index 99412f11e5..561a676be3 100644 --- a/packages/toolkit/src/combineSlices.ts +++ b/packages/toolkit/src/combineSlices.ts @@ -149,7 +149,9 @@ export function combineSlices< if (isSlice(slice)) { map[slice.name] = slice.reducer } else { - safeAssign(map, slice) + for (const [name, reducer] of Object.entries(map)) { + map[name] = reducer + } } return map }, {}) @@ -164,12 +166,26 @@ export function combineSlices< combinedReducer.withLazyLoadedSlices = () => combinedReducer + const injectReducer = (name: string, reducer: Reducer) => { + if (process.env.NODE_ENV !== 'production') { + const currentReducer = reducerMap[name] + if (currentReducer && currentReducer !== reducer) { + throw new Error( + `Name '${name}' has already been injected with different reducer instance` + ) + } + } + reducerMap[name] = reducer + } + combinedReducer.injectSlices = (...slices: Array) => { slices.forEach((slice) => { if (isSlice(slice)) { - reducerMap[slice.name] = slice.reducer + injectReducer(slice.name, slice.reducer) } else { - safeAssign(reducerMap, slice) + for (const [name, reducer] of Object.entries(slice)) { + injectReducer(name, reducer) + } } }) reducer = getReducer() From 60d403b7e4a721b6fae033123e3d22913eb87445 Mon Sep 17 00:00:00 2001 From: Ben Durrant Date: Sun, 26 Mar 2023 19:01:47 +0100 Subject: [PATCH 046/412] typetest --- packages/toolkit/src/combineSlices.ts | 2 +- .../src/tests/combineSlices.typetest.ts | 78 +++++++++++++++++++ 2 files changed, 79 insertions(+), 1 deletion(-) create mode 100644 packages/toolkit/src/tests/combineSlices.typetest.ts diff --git a/packages/toolkit/src/combineSlices.ts b/packages/toolkit/src/combineSlices.ts index 561a676be3..a234ccf09b 100644 --- a/packages/toolkit/src/combineSlices.ts +++ b/packages/toolkit/src/combineSlices.ts @@ -97,7 +97,7 @@ interface CombinedSliceReducer< AnyAction > { withLazyLoadedSlices< - Lazy extends Record + Lazy extends Record = {} >(): CombinedSliceReducer injectSlices< diff --git a/packages/toolkit/src/tests/combineSlices.typetest.ts b/packages/toolkit/src/tests/combineSlices.typetest.ts new file mode 100644 index 0000000000..4f488323e3 --- /dev/null +++ b/packages/toolkit/src/tests/combineSlices.typetest.ts @@ -0,0 +1,78 @@ +/* eslint-disable no-lone-blocks */ +import type { Reducer, Slice, WithSlice } from '@reduxjs/toolkit' +import { combineSlices } from '@reduxjs/toolkit' +import { expectExactType, expectType } from './helpers' + +declare const fooSlice: Slice + +declare const barSlice: Slice + +declare const bazReducer: Reducer + +/** + * Test: combineSlices correctly combines static state + */ +{ + const rootReducer = combineSlices(fooSlice, barSlice, { baz: bazReducer }) + expectType<{ foo: true; bar: true; baz: true }>( + rootReducer(undefined, { type: '' }) + ) +} + +/** + * Test: withLazyLoadedSlices adds partial to state + */ +{ + const rootReducer = + combineSlices(fooSlice).withLazyLoadedSlices>() + expectExactType(true)( + rootReducer(undefined, { type: '' }).bar + ) +} + +/** + * Test: injectSlices marks injected keys as required + */ +{ + const rootReducer = combineSlices(fooSlice).withLazyLoadedSlices< + WithSlice & { baz: true } + >() + + expectExactType(true)( + rootReducer(undefined, { type: '' }).bar + ) + expectExactType(true)( + rootReducer(undefined, { type: '' }).baz + ) + + const injectedReducer = rootReducer.injectSlices(barSlice) + expectExactType(true)(injectedReducer(undefined, { type: '' }).bar) + + const injectedReducer2 = rootReducer.injectSlices({ baz: bazReducer }) + expectExactType(true)(injectedReducer2(undefined, { type: '' }).baz) +} + +/** + * Test: selector() allows defining selectors with injected reducers defined + */ +{ + const rootReducer = combineSlices(fooSlice).withLazyLoadedSlices< + WithSlice & { baz: true } + >() + + type RootState = ReturnType + + const withoutInjection = (state: RootState) => state.bar + + expectExactType(true)( + withoutInjection(rootReducer(undefined, { type: '' })) + ) + + const withInjection = rootReducer + .injectSlices(barSlice) + .selector((state) => state.bar) + + expectExactType(true)( + withInjection(rootReducer(undefined, { type: '' })) + ) +} From 102887f1585d0ecc0fb5d0c2bab5afbce5b17f11 Mon Sep 17 00:00:00 2001 From: Ben Durrant Date: Sun, 26 Mar 2023 19:09:34 +0100 Subject: [PATCH 047/412] remove "alternative" from descriptions of builder callback (#3296) --- packages/toolkit/src/createSlice.ts | 3 --- packages/toolkit/src/tests/createReducer.test.ts | 2 +- packages/toolkit/src/tests/createReducer.typetest.ts | 2 +- packages/toolkit/src/tests/createSlice.test.ts | 2 +- packages/toolkit/src/tests/createSlice.typetest.ts | 2 +- packages/toolkit/src/tests/mapBuilders.typetest.ts | 2 +- 6 files changed, 5 insertions(+), 8 deletions(-) diff --git a/packages/toolkit/src/createSlice.ts b/packages/toolkit/src/createSlice.ts index b9d3fd0d0f..48b36f39e7 100644 --- a/packages/toolkit/src/createSlice.ts +++ b/packages/toolkit/src/createSlice.ts @@ -100,9 +100,6 @@ export interface CreateSliceOptions< * A callback that receives a *builder* object to define * case reducers via calls to `builder.addCase(actionCreatorOrType, reducer)`. * - * Alternatively, a mapping from action types to action-type-specific *case reducer* - * functions. These reducers should have existing action types used - * as the keys, and action creators will _not_ be generated. * * @example ```ts diff --git a/packages/toolkit/src/tests/createReducer.test.ts b/packages/toolkit/src/tests/createReducer.test.ts index 243cecf943..ee845e3b3a 100644 --- a/packages/toolkit/src/tests/createReducer.test.ts +++ b/packages/toolkit/src/tests/createReducer.test.ts @@ -254,7 +254,7 @@ describe('createReducer', () => { behavesLikeReducer(wrappedReducer) }) - describe('alternative builder callback for actionMap', () => { + describe('builder callback for actionMap', () => { const increment = createAction('increment') const decrement = createAction('decrement') diff --git a/packages/toolkit/src/tests/createReducer.typetest.ts b/packages/toolkit/src/tests/createReducer.typetest.ts index bcfb885025..4fe5c2a62f 100644 --- a/packages/toolkit/src/tests/createReducer.typetest.ts +++ b/packages/toolkit/src/tests/createReducer.typetest.ts @@ -70,7 +70,7 @@ import { expectType } from './helpers' }) } -/** Test: alternative builder callback for actionMap */ +/** Test: builder callback for actionMap */ { const increment = createAction('increment') diff --git a/packages/toolkit/src/tests/createSlice.test.ts b/packages/toolkit/src/tests/createSlice.test.ts index 38038dbefc..9fd8e23381 100644 --- a/packages/toolkit/src/tests/createSlice.test.ts +++ b/packages/toolkit/src/tests/createSlice.test.ts @@ -200,7 +200,7 @@ describe('createSlice', () => { expect(result).toBe(15) }) - describe('alternative builder callback for extraReducers', () => { + describe('builder callback for extraReducers', () => { const increment = createAction('increment') test('can be used with actionCreators', () => { diff --git a/packages/toolkit/src/tests/createSlice.typetest.ts b/packages/toolkit/src/tests/createSlice.typetest.ts index 64c33078c5..df1ce4f672 100644 --- a/packages/toolkit/src/tests/createSlice.typetest.ts +++ b/packages/toolkit/src/tests/createSlice.typetest.ts @@ -398,7 +398,7 @@ const value = actionCreators.anyKey } } -/** Test: alternative builder callback for extraReducers */ +/** Test: builder callback for extraReducers */ { createSlice({ name: 'test', diff --git a/packages/toolkit/src/tests/mapBuilders.typetest.ts b/packages/toolkit/src/tests/mapBuilders.typetest.ts index ae930f157a..42bfdd5a05 100644 --- a/packages/toolkit/src/tests/mapBuilders.typetest.ts +++ b/packages/toolkit/src/tests/mapBuilders.typetest.ts @@ -5,7 +5,7 @@ import type { AnyAction } from '@reduxjs/toolkit' import { createAction } from '@reduxjs/toolkit' import { expectExactType, expectType } from './helpers' -/** Test: alternative builder callback for actionMap */ +/** Test: builder callback for actionMap */ { const increment = createAction('increment') const decrement = createAction('decrement') From 7b4d8e04b80f19669aba4e902512faf402c95579 Mon Sep 17 00:00:00 2001 From: Ben Durrant Date: Sun, 26 Mar 2023 19:18:36 +0100 Subject: [PATCH 048/412] more typetests --- packages/toolkit/src/combineSlices.ts | 7 ++- .../src/tests/combineSlices.typetest.ts | 63 +++++++++++++++++-- 2 files changed, 62 insertions(+), 8 deletions(-) diff --git a/packages/toolkit/src/combineSlices.ts b/packages/toolkit/src/combineSlices.ts index a234ccf09b..b8584470a5 100644 --- a/packages/toolkit/src/combineSlices.ts +++ b/packages/toolkit/src/combineSlices.ts @@ -12,7 +12,6 @@ import type { WithOptionalProp, WithRequiredProp, } from './tsHelpers' -import { safeAssign } from './tsHelpers' type AnySlice = Slice @@ -54,7 +53,7 @@ type CombinedSliceState< > > -// Prevent undeclared keys in reducer maps +// Prevent undeclared keys in reducer maps and slices type ValidateReducerMaps< LazyLoadedState extends Record, Slices extends [ @@ -66,7 +65,9 @@ type ValidateReducerMaps< > = Slices & { [Index in keyof Slices]: Slices[Index] extends AnySlice - ? {} + ? SliceName extends keyof LazyLoadedState + ? {} + : never : { [Name in keyof Slices[Index]]: Name extends keyof LazyLoadedState ? Reducer diff --git a/packages/toolkit/src/tests/combineSlices.typetest.ts b/packages/toolkit/src/tests/combineSlices.typetest.ts index 4f488323e3..d86f0bdfa9 100644 --- a/packages/toolkit/src/tests/combineSlices.typetest.ts +++ b/packages/toolkit/src/tests/combineSlices.typetest.ts @@ -7,14 +7,14 @@ declare const fooSlice: Slice declare const barSlice: Slice -declare const bazReducer: Reducer +declare const bazReducer: Reducer /** * Test: combineSlices correctly combines static state */ { const rootReducer = combineSlices(fooSlice, barSlice, { baz: bazReducer }) - expectType<{ foo: true; bar: true; baz: true }>( + expectType<{ foo: true; bar: true; baz: false }>( rootReducer(undefined, { type: '' }) ) } @@ -35,13 +35,13 @@ declare const bazReducer: Reducer */ { const rootReducer = combineSlices(fooSlice).withLazyLoadedSlices< - WithSlice & { baz: true } + WithSlice & { baz: false } >() expectExactType(true)( rootReducer(undefined, { type: '' }).bar ) - expectExactType(true)( + expectExactType(false)( rootReducer(undefined, { type: '' }).baz ) @@ -49,7 +49,40 @@ declare const bazReducer: Reducer expectExactType(true)(injectedReducer(undefined, { type: '' }).bar) const injectedReducer2 = rootReducer.injectSlices({ baz: bazReducer }) - expectExactType(true)(injectedReducer2(undefined, { type: '' }).baz) + expectExactType(false)(injectedReducer2(undefined, { type: '' }).baz) +} + +declare const wrongBarSlice: Slice + +declare const wrongBazReducer: Reducer + +/** + * Test: slices/reducers can only be injected if first added with withLazyLoadedSlices + */ +{ + const rootReducer = combineSlices(fooSlice) + + // @ts-expect-error bar undeclared + rootReducer.injectSlices(barSlice) + + // @ts-expect-error baz undeclared + rootReducer.injectSlices({ + baz: bazReducer, + }) + + const withLazy = rootReducer.withLazyLoadedSlices< + WithSlice & { baz: false } + >() + + // @ts-expect-error right name, wrong state + withLazy.injectSlices(wrongBarSlice) + + // @ts-expect-error right name, wrong state + withLazy.injectSlices({ + baz: wrongBazReducer, + }) + + withLazy.injectSlices(barSlice, { baz: bazReducer }) } /** @@ -76,3 +109,23 @@ declare const bazReducer: Reducer withInjection(rootReducer(undefined, { type: '' })) ) } + +/** + * Test: nested calls inferred correctly + */ +{ + const innerReducer = + combineSlices(fooSlice).withLazyLoadedSlices>() + + const innerSelector = innerReducer + .injectSlices(barSlice) + .selector((state) => state.bar) + + const outerReducer = combineSlices({ inner: innerReducer }) + + const outerSelector = outerReducer.selector((state) => + innerSelector(state.inner) + ) + + expectType<{ inner: { foo: true } }>(outerReducer(undefined, { type: '' })) +} From b3d41643957815f16860eddf10581533bf70b2f5 Mon Sep 17 00:00:00 2001 From: Ben Durrant Date: Sun, 26 Mar 2023 22:08:23 +0100 Subject: [PATCH 049/412] implement suggestions --- packages/toolkit/src/combineSlices.ts | 46 ++++------ .../src/tests/combineSlices.typetest.ts | 92 +++++++++++-------- 2 files changed, 69 insertions(+), 69 deletions(-) diff --git a/packages/toolkit/src/combineSlices.ts b/packages/toolkit/src/combineSlices.ts index b8584470a5..b4d641cb1d 100644 --- a/packages/toolkit/src/combineSlices.ts +++ b/packages/toolkit/src/combineSlices.ts @@ -143,19 +143,17 @@ type StaticState< const isSlice = (maybeSlice: AnySlice | ReducerMap): maybeSlice is AnySlice => typeof maybeSlice.actions === 'object' +const getReducers = (slices: Array) => + slices.flatMap((sliceOrMap) => + isSlice(sliceOrMap) + ? [[sliceOrMap.name, sliceOrMap.reducer] as const] + : Object.entries(sliceOrMap) + ) + export function combineSlices< Slices extends [AnySlice | ReducerMap, ...Array] >(...slices: Slices): CombinedSliceReducer>> { - const reducerMap = slices.reduce>((map, slice) => { - if (isSlice(slice)) { - map[slice.name] = slice.reducer - } else { - for (const [name, reducer] of Object.entries(map)) { - map[name] = reducer - } - } - return map - }, {}) + const reducerMap = Object.fromEntries(getReducers(slices)) const getReducer = () => combineReducers(reducerMap) @@ -167,28 +165,18 @@ export function combineSlices< combinedReducer.withLazyLoadedSlices = () => combinedReducer - const injectReducer = (name: string, reducer: Reducer) => { - if (process.env.NODE_ENV !== 'production') { - const currentReducer = reducerMap[name] - if (currentReducer && currentReducer !== reducer) { - throw new Error( - `Name '${name}' has already been injected with different reducer instance` - ) - } - } - reducerMap[name] = reducer - } - combinedReducer.injectSlices = (...slices: Array) => { - slices.forEach((slice) => { - if (isSlice(slice)) { - injectReducer(slice.name, slice.reducer) - } else { - for (const [name, reducer] of Object.entries(slice)) { - injectReducer(name, reducer) + for (const [name, newReducer] of getReducers(slices)) { + if (process.env.NODE_ENV !== 'production') { + const currentReducer = reducerMap[name] + if (currentReducer && currentReducer !== newReducer) { + throw new Error( + `Name '${name}' has already been injected with different reducer instance` + ) } } - }) + reducerMap[name] = reducer + } reducer = getReducer() } diff --git a/packages/toolkit/src/tests/combineSlices.typetest.ts b/packages/toolkit/src/tests/combineSlices.typetest.ts index d86f0bdfa9..77f80f4114 100644 --- a/packages/toolkit/src/tests/combineSlices.typetest.ts +++ b/packages/toolkit/src/tests/combineSlices.typetest.ts @@ -3,18 +3,20 @@ import type { Reducer, Slice, WithSlice } from '@reduxjs/toolkit' import { combineSlices } from '@reduxjs/toolkit' import { expectExactType, expectType } from './helpers' -declare const fooSlice: Slice +declare const stringSlice: Slice -declare const barSlice: Slice +declare const numberSlice: Slice -declare const bazReducer: Reducer +declare const booleanReducer: Reducer /** * Test: combineSlices correctly combines static state */ { - const rootReducer = combineSlices(fooSlice, barSlice, { baz: bazReducer }) - expectType<{ foo: true; bar: true; baz: false }>( + const rootReducer = combineSlices(stringSlice, numberSlice, { + boolean: booleanReducer, + }) + expectType<{ string: string; number: number; boolean: boolean }>( rootReducer(undefined, { type: '' }) ) } @@ -24,9 +26,11 @@ declare const bazReducer: Reducer */ { const rootReducer = - combineSlices(fooSlice).withLazyLoadedSlices>() - expectExactType(true)( - rootReducer(undefined, { type: '' }).bar + combineSlices(stringSlice).withLazyLoadedSlices< + WithSlice + >() + expectExactType(0)( + rootReducer(undefined, { type: '' }).number ) } @@ -34,78 +38,82 @@ declare const bazReducer: Reducer * Test: injectSlices marks injected keys as required */ { - const rootReducer = combineSlices(fooSlice).withLazyLoadedSlices< - WithSlice & { baz: false } + const rootReducer = combineSlices(stringSlice).withLazyLoadedSlices< + WithSlice & { boolean: boolean } >() - expectExactType(true)( - rootReducer(undefined, { type: '' }).bar + expectExactType(0)( + rootReducer(undefined, { type: '' }).number ) - expectExactType(false)( - rootReducer(undefined, { type: '' }).baz + expectExactType(true)( + rootReducer(undefined, { type: '' }).boolean ) - const injectedReducer = rootReducer.injectSlices(barSlice) - expectExactType(true)(injectedReducer(undefined, { type: '' }).bar) + const injectedReducer = rootReducer.injectSlices(numberSlice) + expectExactType(0)(injectedReducer(undefined, { type: '' }).number) - const injectedReducer2 = rootReducer.injectSlices({ baz: bazReducer }) - expectExactType(false)(injectedReducer2(undefined, { type: '' }).baz) + const injectedReducer2 = rootReducer.injectSlices({ boolean: booleanReducer }) + expectExactType(true)( + injectedReducer2(undefined, { type: '' }).boolean + ) } -declare const wrongBarSlice: Slice +declare const wrongNumberSlice: Slice -declare const wrongBazReducer: Reducer +declare const wrongBooleanReducer: Reducer /** * Test: slices/reducers can only be injected if first added with withLazyLoadedSlices */ { - const rootReducer = combineSlices(fooSlice) + const rootReducer = combineSlices(stringSlice) - // @ts-expect-error bar undeclared - rootReducer.injectSlices(barSlice) + // @ts-expect-error number undeclared + rootReducer.injectSlices(numberSlice) - // @ts-expect-error baz undeclared + // @ts-expect-error boolean undeclared rootReducer.injectSlices({ - baz: bazReducer, + boolean: booleanReducer, }) const withLazy = rootReducer.withLazyLoadedSlices< - WithSlice & { baz: false } + WithSlice & { boolean: boolean } >() // @ts-expect-error right name, wrong state - withLazy.injectSlices(wrongBarSlice) + withLazy.injectSlices(wrongNumberSlice) // @ts-expect-error right name, wrong state withLazy.injectSlices({ - baz: wrongBazReducer, + boolean: wrongBooleanReducer, }) - withLazy.injectSlices(barSlice, { baz: bazReducer }) + withLazy.injectSlices(numberSlice, { boolean: booleanReducer }) } /** * Test: selector() allows defining selectors with injected reducers defined */ { - const rootReducer = combineSlices(fooSlice).withLazyLoadedSlices< - WithSlice & { baz: true } + const rootReducer = combineSlices(stringSlice).withLazyLoadedSlices< + WithSlice & { boolean: boolean } >() type RootState = ReturnType - const withoutInjection = (state: RootState) => state.bar + const withoutInjection = rootReducer.selector( + (state: RootState) => state.number + ) - expectExactType(true)( + expectExactType(0)( withoutInjection(rootReducer(undefined, { type: '' })) ) const withInjection = rootReducer - .injectSlices(barSlice) - .selector((state) => state.bar) + .injectSlices(numberSlice) + .selector((state) => state.number) - expectExactType(true)( + expectExactType(0)( withInjection(rootReducer(undefined, { type: '' })) ) } @@ -115,11 +123,13 @@ declare const wrongBazReducer: Reducer */ { const innerReducer = - combineSlices(fooSlice).withLazyLoadedSlices>() + combineSlices(stringSlice).withLazyLoadedSlices< + WithSlice + >() const innerSelector = innerReducer - .injectSlices(barSlice) - .selector((state) => state.bar) + .injectSlices(numberSlice) + .selector((state) => state.number) const outerReducer = combineSlices({ inner: innerReducer }) @@ -127,5 +137,7 @@ declare const wrongBazReducer: Reducer innerSelector(state.inner) ) - expectType<{ inner: { foo: true } }>(outerReducer(undefined, { type: '' })) + expectType<{ inner: { string: string } }>( + outerReducer(undefined, { type: '' }) + ) } From 818c5952b738d4bb3f387233563a412c85247746 Mon Sep 17 00:00:00 2001 From: Ben Durrant Date: Mon, 27 Mar 2023 00:56:09 +0100 Subject: [PATCH 050/412] add tests and fix oversight in injectSlices --- packages/toolkit/src/combineSlices.ts | 4 +- .../toolkit/src/tests/combineSlices.test.ts | 83 +++++++++++++++++++ 2 files changed, 86 insertions(+), 1 deletion(-) create mode 100644 packages/toolkit/src/tests/combineSlices.test.ts diff --git a/packages/toolkit/src/combineSlices.ts b/packages/toolkit/src/combineSlices.ts index b4d641cb1d..2927d5e752 100644 --- a/packages/toolkit/src/combineSlices.ts +++ b/packages/toolkit/src/combineSlices.ts @@ -175,9 +175,11 @@ export function combineSlices< ) } } - reducerMap[name] = reducer + reducerMap[name] = newReducer } reducer = getReducer() + + return combinedReducer } combinedReducer.selector = diff --git a/packages/toolkit/src/tests/combineSlices.test.ts b/packages/toolkit/src/tests/combineSlices.test.ts new file mode 100644 index 0000000000..a167a0adbe --- /dev/null +++ b/packages/toolkit/src/tests/combineSlices.test.ts @@ -0,0 +1,83 @@ +import { createReducer } from '../createReducer' +import type { PayloadAction } from '../createAction' +import { createAction } from '../createAction' +import { createSlice } from '../createSlice' +import type { WithSlice } from '../combineSlices' +import { combineSlices } from '../combineSlices' + +const dummyAction = createAction('dummy') + +const stringSlice = createSlice({ + name: 'string', + initialState: '', + reducers: {}, +}) + +const numberSlice = createSlice({ + name: 'number', + initialState: 0, + reducers: {}, +}) + +const booleanReducer = createReducer(false, () => {}) + +describe('combineSlices', () => { + it('calls combineReducers to combine static slices/reducers', () => { + const combinedReducer = combineSlices(stringSlice, { + num: numberSlice.reducer, + boolean: booleanReducer, + }) + expect(combinedReducer(undefined, dummyAction())).toEqual({ + string: stringSlice.getInitialState(), + num: numberSlice.getInitialState(), + boolean: booleanReducer.getInitialState(), + }) + }) + it('injects slice', () => { + const combinedReducer = + combineSlices(stringSlice).withLazyLoadedSlices< + WithSlice + >() + + expect(combinedReducer(undefined, dummyAction()).number).toBe(undefined) + + const injectedReducer = combinedReducer.injectSlices(numberSlice) + + expect(injectedReducer(undefined, dummyAction()).number).toBe( + numberSlice.getInitialState() + ) + }) + it('throws error when same name is used for different reducers', () => { + const combinedReducer = + combineSlices(stringSlice).withLazyLoadedSlices<{ boolean: boolean }>() + + combinedReducer.injectSlices({ boolean: booleanReducer }) + + expect(() => + combinedReducer.injectSlices({ boolean: booleanReducer }) + ).not.toThrow() + + expect(() => + // @ts-expect-error wrong reducer + combinedReducer.injectSlices({ boolean: stringSlice.reducer }) + ).toThrow( + "Name 'boolean' has already been injected with different reducer instance" + ) + }) + it.skip('ensures state is defined in selector even if action has not been dispatched', () => { + const combinedReducer = + combineSlices(stringSlice).withLazyLoadedSlices<{ boolean: boolean }>() + + const uninjectedState = combinedReducer(undefined, dummyAction()) + + expect(uninjectedState.boolean).toBe(undefined) + + const selector = combinedReducer + .injectSlices({ + boolean: booleanReducer, + }) + .selector((state) => state.boolean) + + expect(selector(uninjectedState)).toBe(booleanReducer.getInitialState()) + }) +}) From 6f1cbdfa9797b16a5fc1ef4f507ed0daea0c61a2 Mon Sep 17 00:00:00 2001 From: Ben Durrant Date: Mon, 27 Mar 2023 01:35:35 +0100 Subject: [PATCH 051/412] use a Proxy to ensure injected reducers have state in selector --- packages/toolkit/src/combineSlices.ts | 79 +++++++++++++----- .../toolkit/src/tests/combineSlices.test.ts | 80 +++++++++++-------- 2 files changed, 108 insertions(+), 51 deletions(-) diff --git a/packages/toolkit/src/combineSlices.ts b/packages/toolkit/src/combineSlices.ts index 2927d5e752..59a0393d04 100644 --- a/packages/toolkit/src/combineSlices.ts +++ b/packages/toolkit/src/combineSlices.ts @@ -6,6 +6,7 @@ import type { } from 'redux' import { combineReducers } from 'redux' import type { Slice } from './createSlice' +import { nanoid } from './nanoid' import type { Id, UnionToIntersection, @@ -117,17 +118,28 @@ interface CombinedSliceReducer< > // TODO: deal with nested state? - selector< - Selected, - State extends CombinedSliceState< - StaticState, - LazyLoadedState, - InjectedKeys - >, - Args extends any[] - >( - selectorFn: (state: State, ...args: Args) => Selected - ): (state: WithOptionalProp, ...args: Args) => Selected + selector: { + < + Selected, + State extends CombinedSliceState< + StaticState, + LazyLoadedState, + InjectedKeys + >, + Args extends any[] + >( + selectorFn: (state: State, ...args: Args) => Selected + ): (state: WithOptionalProp, ...args: Args) => Selected + original: < + State extends CombinedSliceState< + StaticState, + LazyLoadedState, + InjectedKeys + > + >( + state: State + ) => WithOptionalProp + } } type StaticState< @@ -150,6 +162,29 @@ const getReducers = (slices: Array) => : Object.entries(sliceOrMap) ) +const ORIGINAL_STATE = Symbol() + +const isStateProxy = (value: any) => !!value && !!value[ORIGINAL_STATE] + +const createStateProxy = ( + state: State, + reducerMap: Partial> +) => + new Proxy(state, { + get: (target, prop, receiver) => { + if (prop === ORIGINAL_STATE) return target + const result = Reflect.get(target, prop, receiver) + if (typeof result === 'undefined') { + const reducer = reducerMap[prop.toString()] + if (reducer) { + // ensure action type is random, to prevent reducer treating it differently + return reducer(undefined, { type: nanoid() }) + } + } + return result + }, + }) + export function combineSlices< Slices extends [AnySlice | ReducerMap, ...Array] >(...slices: Slices): CombinedSliceReducer>> { @@ -182,13 +217,21 @@ export function combineSlices< return combinedReducer } - combinedReducer.selector = - ( - selectorFn: (state: State, ...args: Args) => any - ) => - (state: State, ...args: Args) => - // TODO: ensure injected reducers have state - selectorFn(state, ...args) + combinedReducer.selector = function selector< + State extends object, + Args extends any[] + >(selectorFn: (state: State, ...args: Args) => any) { + return (state: State, ...args: Args) => + selectorFn(createStateProxy(state, reducerMap), ...args) + } + + // @ts-ignore + combinedReducer.selector.original = (state: any) => { + if (!isStateProxy(state)) { + throw new Error('original must be used on state Proxy') + } + return state[ORIGINAL_STATE] + } return combinedReducer as any } diff --git a/packages/toolkit/src/tests/combineSlices.test.ts b/packages/toolkit/src/tests/combineSlices.test.ts index a167a0adbe..f75b92032e 100644 --- a/packages/toolkit/src/tests/combineSlices.test.ts +++ b/packages/toolkit/src/tests/combineSlices.test.ts @@ -1,5 +1,4 @@ import { createReducer } from '../createReducer' -import type { PayloadAction } from '../createAction' import { createAction } from '../createAction' import { createSlice } from '../createSlice' import type { WithSlice } from '../combineSlices' @@ -33,51 +32,66 @@ describe('combineSlices', () => { boolean: booleanReducer.getInitialState(), }) }) - it('injects slice', () => { - const combinedReducer = - combineSlices(stringSlice).withLazyLoadedSlices< - WithSlice - >() - - expect(combinedReducer(undefined, dummyAction()).number).toBe(undefined) + describe('injectSlices', () => { + it('injects slice', () => { + const combinedReducer = + combineSlices(stringSlice).withLazyLoadedSlices< + WithSlice + >() - const injectedReducer = combinedReducer.injectSlices(numberSlice) + expect(combinedReducer(undefined, dummyAction()).number).toBe(undefined) - expect(injectedReducer(undefined, dummyAction()).number).toBe( - numberSlice.getInitialState() - ) - }) - it('throws error when same name is used for different reducers', () => { - const combinedReducer = - combineSlices(stringSlice).withLazyLoadedSlices<{ boolean: boolean }>() + const injectedReducer = combinedReducer.injectSlices(numberSlice) - combinedReducer.injectSlices({ boolean: booleanReducer }) + expect(injectedReducer(undefined, dummyAction()).number).toBe( + numberSlice.getInitialState() + ) + }) + it('throws error when same name is used for different reducers', () => { + const combinedReducer = + combineSlices(stringSlice).withLazyLoadedSlices<{ boolean: boolean }>() - expect(() => combinedReducer.injectSlices({ boolean: booleanReducer }) - ).not.toThrow() - expect(() => - // @ts-expect-error wrong reducer - combinedReducer.injectSlices({ boolean: stringSlice.reducer }) - ).toThrow( - "Name 'boolean' has already been injected with different reducer instance" - ) + expect(() => + combinedReducer.injectSlices({ boolean: booleanReducer }) + ).not.toThrow() + + expect(() => + // @ts-expect-error wrong reducer + combinedReducer.injectSlices({ boolean: stringSlice.reducer }) + ).toThrow( + "Name 'boolean' has already been injected with different reducer instance" + ) + }) }) - it.skip('ensures state is defined in selector even if action has not been dispatched', () => { + describe('selector', () => { const combinedReducer = combineSlices(stringSlice).withLazyLoadedSlices<{ boolean: boolean }>() const uninjectedState = combinedReducer(undefined, dummyAction()) - expect(uninjectedState.boolean).toBe(undefined) + const injectedReducer = combinedReducer.injectSlices({ + boolean: booleanReducer, + }) - const selector = combinedReducer - .injectSlices({ - boolean: booleanReducer, - }) - .selector((state) => state.boolean) + it('ensures state is defined in selector even if action has not been dispatched', () => { + expect(uninjectedState.boolean).toBe(undefined) - expect(selector(uninjectedState)).toBe(booleanReducer.getInitialState()) + injectedReducer.selector((state) => + expect(state.boolean).toBe(booleanReducer.getInitialState()) + ) + }) + it('exposes original to allow for logging', () => { + injectedReducer.selector((state) => { + expect(state.boolean).toBe(booleanReducer.getInitialState()) + expect(injectedReducer.selector.original(state).boolean).toBe(undefined) + }) + }) + it('throws if original is called on something other than state proxy', () => { + expect(() => injectedReducer.selector.original({} as any)).toThrow( + 'original must be used on state Proxy' + ) + }) }) }) From 42db40658d47fe5156d85c002ae4f3b30ffc43eb Mon Sep 17 00:00:00 2001 From: Ben Durrant Date: Mon, 27 Mar 2023 01:42:23 +0100 Subject: [PATCH 052/412] test result of selector instead of inside selector --- packages/toolkit/src/tests/combineSlices.test.ts | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/packages/toolkit/src/tests/combineSlices.test.ts b/packages/toolkit/src/tests/combineSlices.test.ts index f75b92032e..b3e7d988ef 100644 --- a/packages/toolkit/src/tests/combineSlices.test.ts +++ b/packages/toolkit/src/tests/combineSlices.test.ts @@ -78,15 +78,17 @@ describe('combineSlices', () => { it('ensures state is defined in selector even if action has not been dispatched', () => { expect(uninjectedState.boolean).toBe(undefined) - injectedReducer.selector((state) => - expect(state.boolean).toBe(booleanReducer.getInitialState()) + const selectBoolean = injectedReducer.selector((state) => state.boolean) + + expect(selectBoolean(uninjectedState)).toBe( + booleanReducer.getInitialState() ) }) it('exposes original to allow for logging', () => { - injectedReducer.selector((state) => { - expect(state.boolean).toBe(booleanReducer.getInitialState()) - expect(injectedReducer.selector.original(state).boolean).toBe(undefined) + const selectBoolean = injectedReducer.selector((state) => { + return injectedReducer.selector.original(state).boolean }) + expect(selectBoolean(uninjectedState)).toBe(undefined) }) it('throws if original is called on something other than state proxy', () => { expect(() => injectedReducer.selector.original({} as any)).toThrow( From b5db06ae84c7ce38da6ff8f5f2c0f6c492d951f3 Mon Sep 17 00:00:00 2001 From: Ben Durrant Date: Mon, 27 Mar 2023 01:48:30 +0100 Subject: [PATCH 053/412] syntax --- packages/toolkit/src/combineSlices.ts | 11 +++++------ packages/toolkit/src/tests/combineSlices.test.ts | 6 +++--- 2 files changed, 8 insertions(+), 9 deletions(-) diff --git a/packages/toolkit/src/combineSlices.ts b/packages/toolkit/src/combineSlices.ts index 59a0393d04..f44ea4eb82 100644 --- a/packages/toolkit/src/combineSlices.ts +++ b/packages/toolkit/src/combineSlices.ts @@ -217,13 +217,12 @@ export function combineSlices< return combinedReducer } - combinedReducer.selector = function selector< - State extends object, - Args extends any[] - >(selectorFn: (state: State, ...args: Args) => any) { - return (state: State, ...args: Args) => + combinedReducer.selector = + ( + selectorFn: (state: State, ...args: Args) => any + ) => + (state: State, ...args: Args) => selectorFn(createStateProxy(state, reducerMap), ...args) - } // @ts-ignore combinedReducer.selector.original = (state: any) => { diff --git a/packages/toolkit/src/tests/combineSlices.test.ts b/packages/toolkit/src/tests/combineSlices.test.ts index b3e7d988ef..0378c1c167 100644 --- a/packages/toolkit/src/tests/combineSlices.test.ts +++ b/packages/toolkit/src/tests/combineSlices.test.ts @@ -85,9 +85,9 @@ describe('combineSlices', () => { ) }) it('exposes original to allow for logging', () => { - const selectBoolean = injectedReducer.selector((state) => { - return injectedReducer.selector.original(state).boolean - }) + const selectBoolean = injectedReducer.selector( + (state) => injectedReducer.selector.original(state).boolean + ) expect(selectBoolean(uninjectedState)).toBe(undefined) }) it('throws if original is called on something other than state proxy', () => { From 8d5c2824e5d841cfdde2f64474da607a02ab0b98 Mon Sep 17 00:00:00 2001 From: "ben.durrant" Date: Mon, 27 Mar 2023 10:29:01 +0100 Subject: [PATCH 054/412] add selectState parameter to handle nested reducers --- packages/toolkit/src/combineSlices.ts | 31 +++++++++-- .../toolkit/src/tests/combineSlices.test.ts | 16 ++++++ .../src/tests/combineSlices.typetest.ts | 52 ++++++++++++++++--- 3 files changed, 89 insertions(+), 10 deletions(-) diff --git a/packages/toolkit/src/combineSlices.ts b/packages/toolkit/src/combineSlices.ts index f44ea4eb82..6a186993c5 100644 --- a/packages/toolkit/src/combineSlices.ts +++ b/packages/toolkit/src/combineSlices.ts @@ -9,6 +9,7 @@ import type { Slice } from './createSlice' import { nanoid } from './nanoid' import type { Id, + NoInfer, UnionToIntersection, WithOptionalProp, WithRequiredProp, @@ -117,7 +118,6 @@ interface CombinedSliceReducer< InjectedKeys | NewKeys > - // TODO: deal with nested state? selector: { < Selected, @@ -130,6 +130,22 @@ interface CombinedSliceReducer< >( selectorFn: (state: State, ...args: Args) => Selected ): (state: WithOptionalProp, ...args: Args) => Selected + < + Selected, + State extends CombinedSliceState< + StaticState, + LazyLoadedState, + InjectedKeys + >, + RootState, + Args extends any[] + >( + selectorFn: (state: State, ...args: Args) => Selected, + selectState: ( + rootState: RootState, + ...args: NoInfer + ) => WithOptionalProp, InjectedKeys> + ): (state: RootState, ...args: Args) => Selected original: < State extends CombinedSliceState< StaticState, @@ -218,11 +234,18 @@ export function combineSlices< } combinedReducer.selector = - ( - selectorFn: (state: State, ...args: Args) => any + ( + selectorFn: (state: State, ...args: Args) => any, + selectState?: (rootState: RootState, ...args: Args) => State ) => (state: State, ...args: Args) => - selectorFn(createStateProxy(state, reducerMap), ...args) + selectorFn( + createStateProxy( + selectState ? selectState(state as any, ...args) : state, + reducerMap + ), + ...args + ) // @ts-ignore combinedReducer.selector.original = (state: any) => { diff --git a/packages/toolkit/src/tests/combineSlices.test.ts b/packages/toolkit/src/tests/combineSlices.test.ts index 0378c1c167..10206362b9 100644 --- a/packages/toolkit/src/tests/combineSlices.test.ts +++ b/packages/toolkit/src/tests/combineSlices.test.ts @@ -95,5 +95,21 @@ describe('combineSlices', () => { 'original must be used on state Proxy' ) }) + it('allows passing a selectState selector, to handle nested state', () => { + const wrappedReducer = combineSlices({ + inner: combinedReducer, + }) + + type RootState = ReturnType + + const selector = injectedReducer.selector( + (state) => state.boolean, + (rootState: RootState) => rootState.inner + ) + + expect(selector(wrappedReducer(undefined, dummyAction()))).toBe( + booleanReducer.getInitialState() + ) + }) }) }) diff --git a/packages/toolkit/src/tests/combineSlices.typetest.ts b/packages/toolkit/src/tests/combineSlices.typetest.ts index 77f80f4114..116e89fecc 100644 --- a/packages/toolkit/src/tests/combineSlices.typetest.ts +++ b/packages/toolkit/src/tests/combineSlices.typetest.ts @@ -127,17 +127,57 @@ declare const wrongBooleanReducer: Reducer WithSlice >() - const innerSelector = innerReducer - .injectSlices(numberSlice) - .selector((state) => state.number) + const innerSelector = innerReducer.injectSlices(numberSlice).selector( + (state) => state.number, + (rootState: RootState) => rootState.inner + ) const outerReducer = combineSlices({ inner: innerReducer }) - const outerSelector = outerReducer.selector((state) => - innerSelector(state.inner) - ) + type RootState = ReturnType expectType<{ inner: { string: string } }>( outerReducer(undefined, { type: '' }) ) + + expectType(innerSelector(outerReducer(undefined, { type: '' }))) +} + +/** + * Test: selector errors if selectorFn and selectState are mismatched + */ + +{ + const combinedReducer = + combineSlices(stringSlice).withLazyLoadedSlices< + WithSlice + >() + + const outerReducer = combineSlices({ inner: combinedReducer }) + + type RootState = ReturnType + + combinedReducer.selector( + (state) => state.number, + // @ts-expect-error wrong state returned + (rootState: RootState) => rootState.inner.number + ) + combinedReducer.selector( + (state, num: number) => state.number, + // @ts-expect-error wrong arguments + (rootState: RootState, str: string) => rootState.inner + ) + + combinedReducer.selector( + (state, num: number) => state.number, + (rootState: RootState) => rootState.inner + ) + + // TODO: see if there's a way of making this work + // probably a rare case so not the end of the world if not + combinedReducer.selector( + (state) => state.number, + // @ts-ignore + (rootState: RootState, num: number) => rootState.inner + ) } From b7430086cc80f91de225fb00a55e23cbf21ccc76 Mon Sep 17 00:00:00 2001 From: "ben.durrant" Date: Mon, 27 Mar 2023 10:39:06 +0100 Subject: [PATCH 055/412] use type assertion instead of ts-ignore --- packages/toolkit/src/combineSlices.ts | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/packages/toolkit/src/combineSlices.ts b/packages/toolkit/src/combineSlices.ts index 6a186993c5..daf5e7d2bf 100644 --- a/packages/toolkit/src/combineSlices.ts +++ b/packages/toolkit/src/combineSlices.ts @@ -246,14 +246,13 @@ export function combineSlices< ), ...args ) - - // @ts-ignore - combinedReducer.selector.original = (state: any) => { - if (!isStateProxy(state)) { - throw new Error('original must be used on state Proxy') + ;(combinedReducer.selector as CombinedSliceReducer<{}>['selector']).original = + (state: any) => { + if (!isStateProxy(state)) { + throw new Error('original must be used on state Proxy') + } + return state[ORIGINAL_STATE] } - return state[ORIGINAL_STATE] - } return combinedReducer as any } From 8ad0eb5990cc16e2442c26ecc1cfa0768610a87c Mon Sep 17 00:00:00 2001 From: "ben.durrant" Date: Mon, 27 Mar 2023 11:16:43 +0100 Subject: [PATCH 056/412] some JSDoc --- packages/toolkit/src/combineSlices.ts | 181 +++++++++++++++++++++++++- 1 file changed, 180 insertions(+), 1 deletion(-) diff --git a/packages/toolkit/src/combineSlices.ts b/packages/toolkit/src/combineSlices.ts index daf5e7d2bf..35acbd9569 100644 --- a/packages/toolkit/src/combineSlices.ts +++ b/packages/toolkit/src/combineSlices.ts @@ -91,6 +91,9 @@ type NewKeys< : keyof Slice : never +/** + * A reducer that allows for slices/reducers to be injected after initialisation. + */ interface CombinedSliceReducer< StaticState, LazyLoadedState extends Record = {}, @@ -99,10 +102,48 @@ interface CombinedSliceReducer< CombinedSliceState, AnyAction > { + /** + * Provide a type for slices that will be injected lazily. + * + * One way to do this would be with interface merging: + * ```ts + * + * export interface LazyLoadedSlices {} + * + * export const rootReducer = combineSlices(stringSlice).withLazyLoadedSlices(); + * + * // elsewhere + * + * declare module './reducer' { + * export interface LazyLoadedSlices extends WithSlice {} + * } + * + * const withBoolean = rootReducer.injectSlices(booleanSlice); + * + * // elsewhere again + * + * declare module './reducer' { + * export interface LazyLoadedSlices { + * customName: CustomState + * } + * } + * + * const withCustom = rootReducer.injectSlices({ customName: customSlice.reducer }) + * ``` + */ withLazyLoadedSlices< Lazy extends Record = {} >(): CombinedSliceReducer - + /** + * Inject slices/reducers previously declared with `withLazyLoadedSlices`. + * + * Accepts same parameters as `combineSlices` - each can be an individual slice, or an object mapping from key to reducer. + * + * ```ts + * rootReducer.injectSlices(booleanSlice, { custom: customSlice.reducer }) + * ``` + * + */ injectSlices< Slices extends [ LazyLoadedSlice | LazyLoadedReducerMap, @@ -118,7 +159,85 @@ interface CombinedSliceReducer< InjectedKeys | NewKeys > + /** + * Create a selector that guarantees that the slices injected will have a defined value when selector is run. + * + * ```ts + * const selectBooleanWithoutInjection = (state: RootState) => state.boolean; + * // ^? boolean | undefined + * + * const selectBoolean = rootReducer.injectSlices(booleanSlice).selector((state) => { + * // if action hasn't been dispatched since slice was injected, this would usually be undefined + * // however selector() uses a Proxy around the first parameter to ensure that it evaluates to the initial state instead, if undefined + * return state.boolean; + * // ^? boolean + * }) + * ``` + * + * If the reducer is nested inside the root state, a selectState callback can be passed to retrieve the reducer's state. + * + * ```ts + * + * export interface LazyLoadedSlices {}; + * + * export const innerReducer = combineSlices(stringSlice).withLazyLoadedSlices(); + * + * export const rootReducer = combineSlices({ inner: innerReducer }); + * + * export type RootState = ReturnType; + * + * // elsewhere + * + * declare module "./reducer.ts" { + * export interface LazyLoadedSlices extends WithSlice {} + * } + * + * const withBool = innerReducer.injectSlices(booleanSlice); + * + * const selectBoolean = withBool.selector( + * (state) => state.boolean, + * (rootState: RootState) => state.inner + * ); + * // now expects to be passed RootState instead of innerReducer state + * + * ``` + * + * Value passed to selectorFn will be a Proxy - use selector.original(proxy) to get original state value (useful for debugging) + * + * ```ts + * const injectedReducer = rootReducer.injectSlices(booleanSlice); + * const selectBoolean = injectedReducer.selector((state) => { + * console.log(injectedReducer.selector.original(state).boolean) // possibly undefined + * return state.boolean + * }) + * ``` + */ selector: { + /** + * Create a selector that guarantees that the slices injected will have a defined value when selector is run. + * + * ```ts + * const selectBooleanWithoutInjection = (state: RootState) => state.boolean; + * // ^? boolean | undefined + * + * const selectBoolean = rootReducer.injectSlices(booleanSlice).selector((state) => { + * // if action hasn't been dispatched since slice was injected, this would usually be undefined + * // however selector() uses a Proxy around the first parameter to ensure that it evaluates to the initial state instead, if undefined + * return state.boolean; + * // ^? boolean + * }) + * ``` + * + * Value passed to selectorFn will be a Proxy - use selector.original(proxy) to get original state value (useful for debugging) + * + * ```ts + * const injectedReducer = rootReducer.injectSlices(booleanSlice); + * const selectBoolean = injectedReducer.selector((state) => { + * console.log(injectedReducer.selector.original(state).boolean) // undefined + * return state.boolean + * }) + * ``` + */ < Selected, State extends CombinedSliceState< @@ -130,6 +249,60 @@ interface CombinedSliceReducer< >( selectorFn: (state: State, ...args: Args) => Selected ): (state: WithOptionalProp, ...args: Args) => Selected + + /** + * Create a selector that guarantees that the slices injected will have a defined value when selector is run. + * + * ```ts + * const selectBooleanWithoutInjection = (state: RootState) => state.boolean; + * // ^? boolean | undefined + * + * const selectBoolean = rootReducer.injectSlices(booleanSlice).selector((state) => { + * // if action hasn't been dispatched since slice was injected, this would usually be undefined + * // however selector() uses a Proxy around the first parameter to ensure that it evaluates to the initial state instead, if undefined + * return state.boolean; + * // ^? boolean + * }) + * ``` + * + * If the reducer is nested inside the root state, a selectState callback can be passed to retrieve the reducer's state. + * + * ```ts + * + * interface LazyLoadedSlices {}; + * + * const innerReducer = combineSlices(stringSlice).withLazyLoadedSlices(); + * + * const rootReducer = combineSlices({ inner: innerReducer }); + * + * type RootState = ReturnType; + * + * // elsewhere + * + * declare module "./reducer.ts" { + * interface LazyLoadedSlices extends WithSlice {} + * } + * + * const withBool = innerReducer.injectSlices(booleanSlice); + * + * const selectBoolean = withBool.selector( + * (state) => state.boolean, + * (rootState: RootState) => state.inner + * ); + * // now expects to be passed RootState instead of innerReducer state + * + * ``` + * + * Value passed to selectorFn will be a Proxy - use selector.original(proxy) to get original state value (useful for debugging) + * + * ```ts + * const injectedReducer = rootReducer.injectSlices(booleanSlice); + * const selectBoolean = injectedReducer.selector((state) => { + * console.log(injectedReducer.selector.original(state).boolean) // possibly undefined + * return state.boolean + * }) + * ``` + */ < Selected, State extends CombinedSliceState< @@ -146,6 +319,12 @@ interface CombinedSliceReducer< ...args: NoInfer ) => WithOptionalProp, InjectedKeys> ): (state: RootState, ...args: Args) => Selected + /** + * Returns the unproxied state. Useful for debugging. + * @param state state Proxy, that ensures injected reducers have value + * @returns original, unproxied state + * @throws if value passed is not a state Proxy + */ original: < State extends CombinedSliceState< StaticState, From a918ebd3cc2668a32eb1e4edce0d45b05d77ffd6 Mon Sep 17 00:00:00 2001 From: "ben.durrant" Date: Mon, 27 Mar 2023 13:08:21 +0100 Subject: [PATCH 057/412] handle RTKQ instances --- packages/toolkit/src/combineSlices.ts | 112 ++++++++++++++---- packages/toolkit/src/index.ts | 2 +- .../toolkit/src/tests/combineSlices.test.ts | 39 +++++- .../src/tests/combineSlices.typetest.ts | 74 +++++++++--- 4 files changed, 179 insertions(+), 48 deletions(-) diff --git a/packages/toolkit/src/combineSlices.ts b/packages/toolkit/src/combineSlices.ts index 35acbd9569..6fdf01dde6 100644 --- a/packages/toolkit/src/combineSlices.ts +++ b/packages/toolkit/src/combineSlices.ts @@ -15,9 +15,7 @@ import type { WithRequiredProp, } from './tsHelpers' -type AnySlice = Slice - -type ReducerMap = Record +type AnySlice = Slice type SliceState = Sl extends Slice ? State @@ -33,13 +31,41 @@ export type WithSlice = Id< } > +type Api = { + reducerPath: ReducerPath + reducer: Reducer +} + +type AnyApi = Api + +type ApiReducerPath = A extends Api + ? ReducerPath + : never + +type ApiState = A extends Api + ? State + : never + +export type WithApi = { + [Path in ApiReducerPath]: ApiState +} + +type ReducerMap = Record + // only allow injection of slices we've already declared + type LazyLoadedSlice> = { [Name in keyof LazyLoadedState]: Name extends string ? Slice : never }[keyof LazyLoadedState] +type LazyLoadedApi> = { + [Name in keyof LazyLoadedState]: Name extends string + ? Api + : never +}[keyof LazyLoadedState] + type LazyLoadedReducerMap> = { [Name in keyof LazyLoadedState]?: Reducer } @@ -55,13 +81,19 @@ type CombinedSliceState< > > -// Prevent undeclared keys in reducer maps and slices +// Prevent undeclared keys in reducer maps, apis and slices type ValidateReducerMaps< LazyLoadedState extends Record, Slices extends [ - LazyLoadedSlice | LazyLoadedReducerMap, + ( + | LazyLoadedSlice + | LazyLoadedApi + | LazyLoadedReducerMap + ), ...Array< - LazyLoadedSlice | LazyLoadedReducerMap + | LazyLoadedSlice + | LazyLoadedApi + | LazyLoadedReducerMap > ] > = Slices & @@ -70,6 +102,10 @@ type ValidateReducerMaps< ? SliceName extends keyof LazyLoadedState ? {} : never + : Slices[Index] extends AnyApi + ? ApiReducerPath extends keyof LazyLoadedState + ? {} + : never : { [Name in keyof Slices[Index]]: Name extends keyof LazyLoadedState ? Reducer @@ -80,14 +116,22 @@ type ValidateReducerMaps< type NewKeys< LazyLoadedState extends Record, Slices extends [ - LazyLoadedSlice | LazyLoadedReducerMap, + ( + | LazyLoadedSlice + | LazyLoadedApi + | LazyLoadedReducerMap + ), ...Array< - LazyLoadedSlice | LazyLoadedReducerMap + | LazyLoadedSlice + | LazyLoadedApi + | LazyLoadedReducerMap > ] > = Slices[number] extends infer Slice ? Slice extends AnySlice ? SliceName + : Slice extends AnyApi + ? ApiReducerPath : keyof Slice : never @@ -137,18 +181,24 @@ interface CombinedSliceReducer< /** * Inject slices/reducers previously declared with `withLazyLoadedSlices`. * - * Accepts same parameters as `combineSlices` - each can be an individual slice, or an object mapping from key to reducer. + * Accepts same parameters as `combineSlices` - each can be an individual slice, an RTKQ api instance, or an object mapping from key to reducer. * * ```ts - * rootReducer.injectSlices(booleanSlice, { custom: customSlice.reducer }) + * rootReducer.injectSlices(booleanSlice, baseApi, { custom: customSlice.reducer }) * ``` * */ injectSlices< Slices extends [ - LazyLoadedSlice | LazyLoadedReducerMap, + ( + | LazyLoadedSlice + | LazyLoadedApi + | LazyLoadedReducerMap + ), ...Array< - LazyLoadedSlice | LazyLoadedReducerMap + | LazyLoadedSlice + | LazyLoadedApi + | LazyLoadedReducerMap > ] >( @@ -337,23 +387,31 @@ interface CombinedSliceReducer< } } -type StaticState< - Slices extends [AnySlice | ReducerMap, ...Array] -> = UnionToIntersection< - Slices[number] extends infer Slice - ? Slice extends AnySlice - ? WithSlice - : StateFromReducersMapObject - : never -> +type StaticState> = + UnionToIntersection< + Slices[number] extends infer Slice + ? Slice extends AnySlice + ? WithSlice + : Slice extends AnyApi + ? WithApi + : StateFromReducersMapObject + : never + > + +const isSlice = ( + maybeSlice: AnySlice | AnyApi | ReducerMap +): maybeSlice is AnySlice => + 'actions' in maybeSlice && typeof maybeSlice.actions === 'object' -const isSlice = (maybeSlice: AnySlice | ReducerMap): maybeSlice is AnySlice => - typeof maybeSlice.actions === 'object' +const isApi = (maybeApi: AnySlice | AnyApi | ReducerMap): maybeApi is AnyApi => + 'reducerPath' in maybeApi && typeof maybeApi.reducerPath === 'string' -const getReducers = (slices: Array) => +const getReducers = (slices: Array) => slices.flatMap((sliceOrMap) => isSlice(sliceOrMap) ? [[sliceOrMap.name, sliceOrMap.reducer] as const] + : isApi(sliceOrMap) + ? [[sliceOrMap.reducerPath, sliceOrMap.reducer] as const] : Object.entries(sliceOrMap) ) @@ -381,7 +439,7 @@ const createStateProxy = ( }) export function combineSlices< - Slices extends [AnySlice | ReducerMap, ...Array] + Slices extends Array >(...slices: Slices): CombinedSliceReducer>> { const reducerMap = Object.fromEntries(getReducers(slices)) @@ -395,7 +453,9 @@ export function combineSlices< combinedReducer.withLazyLoadedSlices = () => combinedReducer - combinedReducer.injectSlices = (...slices: Array) => { + combinedReducer.injectSlices = ( + ...slices: Array + ) => { for (const [name, newReducer] of getReducers(slices)) { if (process.env.NODE_ENV !== 'production') { const currentReducer = reducerMap[name] diff --git a/packages/toolkit/src/index.ts b/packages/toolkit/src/index.ts index f92ec1b0a8..0756787c07 100644 --- a/packages/toolkit/src/index.ts +++ b/packages/toolkit/src/index.ts @@ -187,4 +187,4 @@ export type { AutoBatchOptions } from './autoBatchEnhancer' export { combineSlices } from './combineSlices' -export type { WithSlice } from './combineSlices' +export type { WithSlice, WithApi } from './combineSlices' diff --git a/packages/toolkit/src/tests/combineSlices.test.ts b/packages/toolkit/src/tests/combineSlices.test.ts index 10206362b9..ba2038dd84 100644 --- a/packages/toolkit/src/tests/combineSlices.test.ts +++ b/packages/toolkit/src/tests/combineSlices.test.ts @@ -3,6 +3,8 @@ import { createAction } from '../createAction' import { createSlice } from '../createSlice' import type { WithSlice } from '../combineSlices' import { combineSlices } from '../combineSlices' +import { expectType } from './helpers' +import type { CombinedState } from '../query/core/apiState' const dummyAction = createAction('dummy') @@ -20,16 +22,45 @@ const numberSlice = createSlice({ const booleanReducer = createReducer(false, () => {}) +// mimic - we can't use RTKQ here directly +const api = { + reducerPath: 'api' as const, + reducer: createReducer( + expectType>({ + queries: {}, + mutations: {}, + provided: {}, + subscriptions: {}, + config: { + reducerPath: 'api', + online: false, + focused: false, + keepUnusedDataFor: 60, + middlewareRegistered: false, + refetchOnMountOrArgChange: false, + refetchOnReconnect: false, + refetchOnFocus: false, + }, + }), + () => {} + ), +} + describe('combineSlices', () => { it('calls combineReducers to combine static slices/reducers', () => { - const combinedReducer = combineSlices(stringSlice, { - num: numberSlice.reducer, - boolean: booleanReducer, - }) + const combinedReducer = combineSlices( + stringSlice, + { + num: numberSlice.reducer, + boolean: booleanReducer, + }, + api + ) expect(combinedReducer(undefined, dummyAction())).toEqual({ string: stringSlice.getInitialState(), num: numberSlice.getInitialState(), boolean: booleanReducer.getInitialState(), + api: api.reducer.getInitialState(), }) }) describe('injectSlices', () => { diff --git a/packages/toolkit/src/tests/combineSlices.typetest.ts b/packages/toolkit/src/tests/combineSlices.typetest.ts index 116e89fecc..305ad6fa02 100644 --- a/packages/toolkit/src/tests/combineSlices.typetest.ts +++ b/packages/toolkit/src/tests/combineSlices.typetest.ts @@ -1,6 +1,7 @@ /* eslint-disable no-lone-blocks */ -import type { Reducer, Slice, WithSlice } from '@reduxjs/toolkit' +import type { Reducer, Slice, WithSlice, WithApi } from '@reduxjs/toolkit' import { combineSlices } from '@reduxjs/toolkit' +import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query' import { expectExactType, expectType } from './helpers' declare const stringSlice: Slice @@ -9,29 +10,45 @@ declare const numberSlice: Slice declare const booleanReducer: Reducer +const exampleApi = createApi({ + baseQuery: fetchBaseQuery(), + endpoints: (build) => ({ + getThing: build.query({ + query: () => '', + }), + }), +}) + +type ExampleApiState = ReturnType + /** * Test: combineSlices correctly combines static state */ { - const rootReducer = combineSlices(stringSlice, numberSlice, { + const rootReducer = combineSlices(stringSlice, numberSlice, exampleApi, { boolean: booleanReducer, }) - expectType<{ string: string; number: number; boolean: boolean }>( - rootReducer(undefined, { type: '' }) - ) + expectType<{ + string: string + number: number + boolean: boolean + api: ExampleApiState + }>(rootReducer(undefined, { type: '' })) } /** * Test: withLazyLoadedSlices adds partial to state */ { - const rootReducer = - combineSlices(stringSlice).withLazyLoadedSlices< - WithSlice - >() + const rootReducer = combineSlices(stringSlice).withLazyLoadedSlices< + WithSlice & WithApi + >() expectExactType(0)( rootReducer(undefined, { type: '' }).number ) + expectExactType(undefined)( + rootReducer(undefined, { type: '' }).api + ) } /** @@ -39,7 +56,8 @@ declare const booleanReducer: Reducer */ { const rootReducer = combineSlices(stringSlice).withLazyLoadedSlices< - WithSlice & { boolean: boolean } + WithSlice & + WithApi & { boolean: boolean } >() expectExactType(0)( @@ -48,13 +66,19 @@ declare const booleanReducer: Reducer expectExactType(true)( rootReducer(undefined, { type: '' }).boolean ) + expectExactType(undefined)( + rootReducer(undefined, { type: '' }).api + ) - const injectedReducer = rootReducer.injectSlices(numberSlice) - expectExactType(0)(injectedReducer(undefined, { type: '' }).number) + const withNumber = rootReducer.injectSlices(numberSlice) + expectExactType(0)(withNumber(undefined, { type: '' }).number) - const injectedReducer2 = rootReducer.injectSlices({ boolean: booleanReducer }) - expectExactType(true)( - injectedReducer2(undefined, { type: '' }).boolean + const withBool = rootReducer.injectSlices({ boolean: booleanReducer }) + expectExactType(true)(withBool(undefined, { type: '' }).boolean) + + const withApi = rootReducer.injectSlices(exampleApi) + expectExactType({} as ExampleApiState)( + withApi(undefined, { type: '' }).api ) } @@ -62,6 +86,15 @@ declare const wrongNumberSlice: Slice declare const wrongBooleanReducer: Reducer +const wrongApi = createApi({ + baseQuery: fetchBaseQuery(), + endpoints: (build) => ({ + getThing2: build.query({ + query: () => '', + }), + }), +}) + /** * Test: slices/reducers can only be injected if first added with withLazyLoadedSlices */ @@ -71,24 +104,31 @@ declare const wrongBooleanReducer: Reducer // @ts-expect-error number undeclared rootReducer.injectSlices(numberSlice) + // @ts-expect-error api undeclared + rootReducer.injectSlices(exampleApi) + // @ts-expect-error boolean undeclared rootReducer.injectSlices({ boolean: booleanReducer, }) const withLazy = rootReducer.withLazyLoadedSlices< - WithSlice & { boolean: boolean } + WithSlice & + WithApi & { boolean: boolean } >() // @ts-expect-error right name, wrong state withLazy.injectSlices(wrongNumberSlice) + // @ts-expect-error right name, wrong state + withLazy.injectSlices(wrongApi) + // @ts-expect-error right name, wrong state withLazy.injectSlices({ boolean: wrongBooleanReducer, }) - withLazy.injectSlices(numberSlice, { boolean: booleanReducer }) + withLazy.injectSlices(numberSlice, exampleApi, { boolean: booleanReducer }) } /** From bfdb10f317f10a32caa6701cce1815b387333fef Mon Sep 17 00:00:00 2001 From: "ben.durrant" Date: Mon, 27 Mar 2023 14:23:25 +0100 Subject: [PATCH 058/412] define original once and just attach to selector --- packages/toolkit/src/combineSlices.ts | 38 +++++++++++++++------------ 1 file changed, 21 insertions(+), 17 deletions(-) diff --git a/packages/toolkit/src/combineSlices.ts b/packages/toolkit/src/combineSlices.ts index 6fdf01dde6..48e866bf9b 100644 --- a/packages/toolkit/src/combineSlices.ts +++ b/packages/toolkit/src/combineSlices.ts @@ -438,6 +438,13 @@ const createStateProxy = ( }, }) +const original = (state: any) => { + if (!isStateProxy(state)) { + throw new Error('original must be used on state Proxy') + } + return state[ORIGINAL_STATE] +} + export function combineSlices< Slices extends Array >(...slices: Slices): CombinedSliceReducer>> { @@ -472,26 +479,23 @@ export function combineSlices< return combinedReducer } - combinedReducer.selector = - ( + combinedReducer.selector = Object.assign( + function makeSelector( selectorFn: (state: State, ...args: Args) => any, selectState?: (rootState: RootState, ...args: Args) => State - ) => - (state: State, ...args: Args) => - selectorFn( - createStateProxy( - selectState ? selectState(state as any, ...args) : state, - reducerMap - ), - ...args - ) - ;(combinedReducer.selector as CombinedSliceReducer<{}>['selector']).original = - (state: any) => { - if (!isStateProxy(state)) { - throw new Error('original must be used on state Proxy') + ) { + return function selector(state: State, ...args: Args) { + return selectorFn( + createStateProxy( + selectState ? selectState(state as any, ...args) : state, + reducerMap + ), + ...args + ) } - return state[ORIGINAL_STATE] - } + }, + { original } + ) return combinedReducer as any } From 8fc0d5dfef5015d4774e3ee5e9b5a6f23f5beb59 Mon Sep 17 00:00:00 2001 From: "ben.durrant" Date: Mon, 27 Mar 2023 17:26:52 +0100 Subject: [PATCH 059/412] throw an error when reducer returns undefined when called in state proxy --- packages/toolkit/src/combineSlices.ts | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/packages/toolkit/src/combineSlices.ts b/packages/toolkit/src/combineSlices.ts index 48e866bf9b..ef3dde3ad0 100644 --- a/packages/toolkit/src/combineSlices.ts +++ b/packages/toolkit/src/combineSlices.ts @@ -431,7 +431,17 @@ const createStateProxy = ( const reducer = reducerMap[prop.toString()] if (reducer) { // ensure action type is random, to prevent reducer treating it differently - return reducer(undefined, { type: nanoid() }) + const reducerResult = reducer(undefined, { type: nanoid() }) + if (typeof reducerResult === 'undefined') { + throw new Error( + `The slice reducer for key "${prop.toString()}" returned undefined when called for selector(). ` + + `If the state passed to the reducer is undefined, you must ` + + `explicitly return the initial state. The initial state may ` + + `not be undefined. If you don't want to set a value for this reducer, ` + + `you can use null instead of undefined.` + ) + } + return reducerResult } } return result From 0c0d116c50af6e23322a73fe1e6114aea8f34812 Mon Sep 17 00:00:00 2001 From: Ben Durrant Date: Tue, 28 Mar 2023 00:32:53 +0100 Subject: [PATCH 060/412] cache proxy creation, and use a proxy to mark a reducer as replaceable --- packages/toolkit/src/combineSlices.ts | 104 +++++++++++++----- .../toolkit/src/tests/combineSlices.test.ts | 26 ++++- 2 files changed, 104 insertions(+), 26 deletions(-) diff --git a/packages/toolkit/src/combineSlices.ts b/packages/toolkit/src/combineSlices.ts index ef3dde3ad0..b7a4850c05 100644 --- a/packages/toolkit/src/combineSlices.ts +++ b/packages/toolkit/src/combineSlices.ts @@ -415,38 +415,88 @@ const getReducers = (slices: Array) => : Object.entries(sliceOrMap) ) -const ORIGINAL_STATE = Symbol() +const IS_REPLACEABLE = Symbol.for('rtk-is-replaceable') +const ORIGINAL_REDUCER = Symbol.for('rtk-reducer-proxy-original') + +const reducerProxyMap = new WeakMap() + +const makeReducerProxy = (reducer: R): R => { + let proxy = reducerProxyMap.get(reducer) + if (!proxy) { + proxy = new Proxy(reducer, { + get: (target, prop, receiver) => { + if (prop === IS_REPLACEABLE) return true + if (prop === ORIGINAL_REDUCER) return target + return Reflect.get(target, prop, receiver) + }, + }) + reducerProxyMap.set(reducer, proxy) + } + return proxy as R +} + +/** + * Marks a slice/api/reducer as replaceable, so injectSlices won't throw an error when a new reducer instance is injected with the same name + * + * ```ts + * injectSlices(markReplaceable(fooSlice), markReplaceable(fooApi), { custom: markReplaceable(fooReducer) }) + * ``` + * + */ +export const markReplaceable = ( + input: Input +): Input => { + if ('reducer' in input) { + return { + ...input, + reducer: makeReducerProxy(input.reducer), + } + } + return makeReducerProxy(input) as Input +} + +const isReplaceable = (reducer: Reducer) => !!(reducer as any)[IS_REPLACEABLE] + +const ORIGINAL_STATE = Symbol.for('rtk-state-proxy-original') const isStateProxy = (value: any) => !!value && !!value[ORIGINAL_STATE] +const stateProxyMap = new WeakMap() + const createStateProxy = ( state: State, reducerMap: Partial> -) => - new Proxy(state, { - get: (target, prop, receiver) => { - if (prop === ORIGINAL_STATE) return target - const result = Reflect.get(target, prop, receiver) - if (typeof result === 'undefined') { - const reducer = reducerMap[prop.toString()] - if (reducer) { - // ensure action type is random, to prevent reducer treating it differently - const reducerResult = reducer(undefined, { type: nanoid() }) - if (typeof reducerResult === 'undefined') { - throw new Error( - `The slice reducer for key "${prop.toString()}" returned undefined when called for selector(). ` + - `If the state passed to the reducer is undefined, you must ` + - `explicitly return the initial state. The initial state may ` + - `not be undefined. If you don't want to set a value for this reducer, ` + - `you can use null instead of undefined.` - ) +) => { + let proxy = stateProxyMap.get(state) + if (!proxy) { + proxy = new Proxy(state, { + get: (target, prop, receiver) => { + if (prop === ORIGINAL_STATE) return target + const result = Reflect.get(target, prop, receiver) + if (typeof result === 'undefined') { + const reducer = reducerMap[prop.toString()] + if (reducer) { + // ensure action type is random, to prevent reducer treating it differently + const reducerResult = reducer(undefined, { type: nanoid() }) + if (typeof reducerResult === 'undefined') { + throw new Error( + `The slice reducer for key "${prop.toString()}" returned undefined when called for selector(). ` + + `If the state passed to the reducer is undefined, you must ` + + `explicitly return the initial state. The initial state may ` + + `not be undefined. If you don't want to set a value for this reducer, ` + + `you can use null instead of undefined.` + ) + } + return reducerResult } - return reducerResult } - } - return result - }, - }) + return result + }, + }) + stateProxyMap.set(state, proxy) + } + return proxy as State +} const original = (state: any) => { if (!isStateProxy(state)) { @@ -476,7 +526,11 @@ export function combineSlices< for (const [name, newReducer] of getReducers(slices)) { if (process.env.NODE_ENV !== 'production') { const currentReducer = reducerMap[name] - if (currentReducer && currentReducer !== newReducer) { + if ( + currentReducer && + !isReplaceable(currentReducer) && + currentReducer !== newReducer + ) { throw new Error( `Name '${name}' has already been injected with different reducer instance` ) diff --git a/packages/toolkit/src/tests/combineSlices.test.ts b/packages/toolkit/src/tests/combineSlices.test.ts index ba2038dd84..27115895c0 100644 --- a/packages/toolkit/src/tests/combineSlices.test.ts +++ b/packages/toolkit/src/tests/combineSlices.test.ts @@ -1,7 +1,8 @@ import { createReducer } from '../createReducer' import { createAction } from '../createAction' import { createSlice } from '../createSlice' -import type { WithSlice } from '../combineSlices' +import type { WithApi, WithSlice } from '../combineSlices' +import { markReplaceable } from '../combineSlices' import { combineSlices } from '../combineSlices' import { expectType } from './helpers' import type { CombinedState } from '../query/core/apiState' @@ -95,6 +96,29 @@ describe('combineSlices', () => { "Name 'boolean' has already been injected with different reducer instance" ) }) + it('allows replacement of reducers specifically marked as replaceable', () => { + const combinedReducer = combineSlices(stringSlice).withLazyLoadedSlices< + WithSlice & + WithApi & { boolean: boolean } + >() + + combinedReducer.injectSlices( + markReplaceable(numberSlice), + markReplaceable(api), + { boolean: markReplaceable(booleanReducer) } + ) + + // for brevity + const anyReducer = createReducer({} as any, () => {}) + + expect(() => + combinedReducer.injectSlices({ + number: anyReducer, + api: anyReducer, + boolean: anyReducer, + }) + ).not.toThrow() + }) }) describe('selector', () => { const combinedReducer = From 327577fc942128dedee407426c13aa57bfb19696 Mon Sep 17 00:00:00 2001 From: Ben Durrant Date: Tue, 28 Mar 2023 00:34:47 +0100 Subject: [PATCH 061/412] export markReplaceable from package --- packages/toolkit/src/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/toolkit/src/index.ts b/packages/toolkit/src/index.ts index 0756787c07..a3153ff3ff 100644 --- a/packages/toolkit/src/index.ts +++ b/packages/toolkit/src/index.ts @@ -185,6 +185,6 @@ export { } from './autoBatchEnhancer' export type { AutoBatchOptions } from './autoBatchEnhancer' -export { combineSlices } from './combineSlices' +export { combineSlices, markReplaceable } from './combineSlices' export type { WithSlice, WithApi } from './combineSlices' From 43eb309dab648553eec5c8ce281bcf27a5a0a844 Mon Sep 17 00:00:00 2001 From: "ben.durrant" Date: Tue, 28 Mar 2023 10:27:30 +0100 Subject: [PATCH 062/412] only allow injection of one slice/api at a time, and add config to allow replacement --- packages/toolkit/src/combineSlices.ts | 229 ++++++------------ .../toolkit/src/tests/combineSlices.test.ts | 35 +-- .../src/tests/combineSlices.typetest.ts | 18 +- 3 files changed, 108 insertions(+), 174 deletions(-) diff --git a/packages/toolkit/src/combineSlices.ts b/packages/toolkit/src/combineSlices.ts index b7a4850c05..18bb78bef6 100644 --- a/packages/toolkit/src/combineSlices.ts +++ b/packages/toolkit/src/combineSlices.ts @@ -5,7 +5,6 @@ import type { StateFromReducersMapObject, } from 'redux' import { combineReducers } from 'redux' -import type { Slice } from './createSlice' import { nanoid } from './nanoid' import type { Id, @@ -15,13 +14,18 @@ import type { WithRequiredProp, } from './tsHelpers' -type AnySlice = Slice +type Slice = { + name: Name + reducer: Reducer +} + +type AnySlice = Slice -type SliceState = Sl extends Slice +type SliceState = Sl extends Slice ? State : never -type SliceName = Sl extends Slice +type SliceName = Sl extends Slice ? Name : never @@ -56,7 +60,7 @@ type ReducerMap = Record type LazyLoadedSlice> = { [Name in keyof LazyLoadedState]: Name extends string - ? Slice + ? Slice : never }[keyof LazyLoadedState] @@ -66,8 +70,11 @@ type LazyLoadedApi> = { : never }[keyof LazyLoadedState] -type LazyLoadedReducerMap> = { - [Name in keyof LazyLoadedState]?: Reducer +type InjectConfig = { + /** + * Allow replacing reducer with a different reference. Normally, an error will be thrown if a different reducer instance to the one already injected is used. + */ + allowReplace?: boolean } type CombinedSliceState< @@ -81,60 +88,6 @@ type CombinedSliceState< > > -// Prevent undeclared keys in reducer maps, apis and slices -type ValidateReducerMaps< - LazyLoadedState extends Record, - Slices extends [ - ( - | LazyLoadedSlice - | LazyLoadedApi - | LazyLoadedReducerMap - ), - ...Array< - | LazyLoadedSlice - | LazyLoadedApi - | LazyLoadedReducerMap - > - ] -> = Slices & - { - [Index in keyof Slices]: Slices[Index] extends AnySlice - ? SliceName extends keyof LazyLoadedState - ? {} - : never - : Slices[Index] extends AnyApi - ? ApiReducerPath extends keyof LazyLoadedState - ? {} - : never - : { - [Name in keyof Slices[Index]]: Name extends keyof LazyLoadedState - ? Reducer - : never - } - } - -type NewKeys< - LazyLoadedState extends Record, - Slices extends [ - ( - | LazyLoadedSlice - | LazyLoadedApi - | LazyLoadedReducerMap - ), - ...Array< - | LazyLoadedSlice - | LazyLoadedApi - | LazyLoadedReducerMap - > - ] -> = Slices[number] extends infer Slice - ? Slice extends AnySlice - ? SliceName - : Slice extends AnyApi - ? ApiReducerPath - : keyof Slice - : never - /** * A reducer that allows for slices/reducers to be injected after initialisation. */ @@ -162,7 +115,7 @@ interface CombinedSliceReducer< * export interface LazyLoadedSlices extends WithSlice {} * } * - * const withBoolean = rootReducer.injectSlices(booleanSlice); + * const withBoolean = rootReducer.injectSlice(booleanSlice); * * // elsewhere again * @@ -172,41 +125,48 @@ interface CombinedSliceReducer< * } * } * - * const withCustom = rootReducer.injectSlices({ customName: customSlice.reducer }) + * const withCustom = rootReducer.injectSlice({ name: "customName", reducer: customSlice.reducer }) * ``` */ withLazyLoadedSlices< Lazy extends Record = {} >(): CombinedSliceReducer + /** - * Inject slices/reducers previously declared with `withLazyLoadedSlices`. + * Inject a slice previously declared with `withLazyLoadedSlices`. * - * Accepts same parameters as `combineSlices` - each can be an individual slice, an RTKQ api instance, or an object mapping from key to reducer. + * Accepts an individual slice, or a { name, reducer } object. * * ```ts - * rootReducer.injectSlices(booleanSlice, baseApi, { custom: customSlice.reducer }) + * rootReducer.injectSlice(booleanSlice) + * rootReducer.injectSlice({ name: 'boolean', reducer: newReducer }, { allowReplace: true }) * ``` * */ - injectSlices< - Slices extends [ - ( - | LazyLoadedSlice - | LazyLoadedApi - | LazyLoadedReducerMap - ), - ...Array< - | LazyLoadedSlice - | LazyLoadedApi - | LazyLoadedReducerMap - > - ] - >( - ...slices: ValidateReducerMaps + injectSlice>( + slice: Sl, + config?: InjectConfig + ): CombinedSliceReducer< + StaticState, + LazyLoadedState, + InjectedKeys | SliceName + > + + /** + * Inject an RTKQ API instance previously declared with `withLazyLoadedSlices`. + * + * ```ts + * rootReducer.injectSlice(baseApi) + * ``` + * + */ + injectSlice>( + slice: A, + config?: InjectConfig ): CombinedSliceReducer< StaticState, LazyLoadedState, - InjectedKeys | NewKeys + InjectedKeys | ApiReducerPath > /** @@ -216,7 +176,7 @@ interface CombinedSliceReducer< * const selectBooleanWithoutInjection = (state: RootState) => state.boolean; * // ^? boolean | undefined * - * const selectBoolean = rootReducer.injectSlices(booleanSlice).selector((state) => { + * const selectBoolean = rootReducer.injectSlice(booleanSlice).selector((state) => { * // if action hasn't been dispatched since slice was injected, this would usually be undefined * // however selector() uses a Proxy around the first parameter to ensure that it evaluates to the initial state instead, if undefined * return state.boolean; @@ -242,7 +202,7 @@ interface CombinedSliceReducer< * export interface LazyLoadedSlices extends WithSlice {} * } * - * const withBool = innerReducer.injectSlices(booleanSlice); + * const withBool = innerReducer.injectSlice(booleanSlice); * * const selectBoolean = withBool.selector( * (state) => state.boolean, @@ -255,7 +215,7 @@ interface CombinedSliceReducer< * Value passed to selectorFn will be a Proxy - use selector.original(proxy) to get original state value (useful for debugging) * * ```ts - * const injectedReducer = rootReducer.injectSlices(booleanSlice); + * const injectedReducer = rootReducer.injectSlice(booleanSlice); * const selectBoolean = injectedReducer.selector((state) => { * console.log(injectedReducer.selector.original(state).boolean) // possibly undefined * return state.boolean @@ -270,7 +230,7 @@ interface CombinedSliceReducer< * const selectBooleanWithoutInjection = (state: RootState) => state.boolean; * // ^? boolean | undefined * - * const selectBoolean = rootReducer.injectSlices(booleanSlice).selector((state) => { + * const selectBoolean = rootReducer.injectSlice(booleanSlice).selector((state) => { * // if action hasn't been dispatched since slice was injected, this would usually be undefined * // however selector() uses a Proxy around the first parameter to ensure that it evaluates to the initial state instead, if undefined * return state.boolean; @@ -281,7 +241,7 @@ interface CombinedSliceReducer< * Value passed to selectorFn will be a Proxy - use selector.original(proxy) to get original state value (useful for debugging) * * ```ts - * const injectedReducer = rootReducer.injectSlices(booleanSlice); + * const injectedReducer = rootReducer.injectSlice(booleanSlice); * const selectBoolean = injectedReducer.selector((state) => { * console.log(injectedReducer.selector.original(state).boolean) // undefined * return state.boolean @@ -307,7 +267,7 @@ interface CombinedSliceReducer< * const selectBooleanWithoutInjection = (state: RootState) => state.boolean; * // ^? boolean | undefined * - * const selectBoolean = rootReducer.injectSlices(booleanSlice).selector((state) => { + * const selectBoolean = rootReducer.injectSlice(booleanSlice).selector((state) => { * // if action hasn't been dispatched since slice was injected, this would usually be undefined * // however selector() uses a Proxy around the first parameter to ensure that it evaluates to the initial state instead, if undefined * return state.boolean; @@ -333,7 +293,7 @@ interface CombinedSliceReducer< * interface LazyLoadedSlices extends WithSlice {} * } * - * const withBool = innerReducer.injectSlices(booleanSlice); + * const withBool = innerReducer.injectSlice(booleanSlice); * * const selectBoolean = withBool.selector( * (state) => state.boolean, @@ -346,7 +306,7 @@ interface CombinedSliceReducer< * Value passed to selectorFn will be a Proxy - use selector.original(proxy) to get original state value (useful for debugging) * * ```ts - * const injectedReducer = rootReducer.injectSlices(booleanSlice); + * const injectedReducer = rootReducer.injectSlice(booleanSlice); * const selectBoolean = injectedReducer.selector((state) => { * console.log(injectedReducer.selector.original(state).boolean) // possibly undefined * return state.boolean @@ -401,7 +361,7 @@ type StaticState> = const isSlice = ( maybeSlice: AnySlice | AnyApi | ReducerMap ): maybeSlice is AnySlice => - 'actions' in maybeSlice && typeof maybeSlice.actions === 'object' + 'name' in maybeSlice && typeof maybeSlice.name === 'string' const isApi = (maybeApi: AnySlice | AnyApi | ReducerMap): maybeApi is AnyApi => 'reducerPath' in maybeApi && typeof maybeApi.reducerPath === 'string' @@ -415,48 +375,6 @@ const getReducers = (slices: Array) => : Object.entries(sliceOrMap) ) -const IS_REPLACEABLE = Symbol.for('rtk-is-replaceable') -const ORIGINAL_REDUCER = Symbol.for('rtk-reducer-proxy-original') - -const reducerProxyMap = new WeakMap() - -const makeReducerProxy = (reducer: R): R => { - let proxy = reducerProxyMap.get(reducer) - if (!proxy) { - proxy = new Proxy(reducer, { - get: (target, prop, receiver) => { - if (prop === IS_REPLACEABLE) return true - if (prop === ORIGINAL_REDUCER) return target - return Reflect.get(target, prop, receiver) - }, - }) - reducerProxyMap.set(reducer, proxy) - } - return proxy as R -} - -/** - * Marks a slice/api/reducer as replaceable, so injectSlices won't throw an error when a new reducer instance is injected with the same name - * - * ```ts - * injectSlices(markReplaceable(fooSlice), markReplaceable(fooApi), { custom: markReplaceable(fooReducer) }) - * ``` - * - */ -export const markReplaceable = ( - input: Input -): Input => { - if ('reducer' in input) { - return { - ...input, - reducer: makeReducerProxy(input.reducer), - } - } - return makeReducerProxy(input) as Input -} - -const isReplaceable = (reducer: Reducer) => !!(reducer as any)[IS_REPLACEABLE] - const ORIGINAL_STATE = Symbol.for('rtk-state-proxy-original') const isStateProxy = (value: any) => !!value && !!value[ORIGINAL_STATE] @@ -520,30 +438,39 @@ export function combineSlices< combinedReducer.withLazyLoadedSlices = () => combinedReducer - combinedReducer.injectSlices = ( - ...slices: Array - ) => { - for (const [name, newReducer] of getReducers(slices)) { - if (process.env.NODE_ENV !== 'production') { - const currentReducer = reducerMap[name] - if ( - currentReducer && - !isReplaceable(currentReducer) && - currentReducer !== newReducer - ) { - throw new Error( - `Name '${name}' has already been injected with different reducer instance` - ) - } + const injectSlice = ( + slice: AnySlice | AnyApi, + config: InjectConfig = {} + ): typeof combinedReducer => { + if (isApi(slice)) { + return injectSlice( + { + name: slice.reducerPath, + reducer: slice.reducer, + }, + config + ) + } + + const { name, reducer: reducerToInject } = slice + + if (process.env.NODE_ENV !== 'production' && !config.allowReplace) { + const currentReducer = reducerMap[name] + if (currentReducer && currentReducer !== reducerToInject) { + throw new Error( + `Name '${name}' has already been injected with different reducer instance` + ) } - reducerMap[name] = newReducer } + + reducerMap[name] = reducerToInject + reducer = getReducer() return combinedReducer } - combinedReducer.selector = Object.assign( + const selector = Object.assign( function makeSelector( selectorFn: (state: State, ...args: Args) => any, selectState?: (rootState: RootState, ...args: Args) => State @@ -561,5 +488,5 @@ export function combineSlices< { original } ) - return combinedReducer as any + return Object.assign(combinedReducer, { injectSlice, selector }) as any } diff --git a/packages/toolkit/src/tests/combineSlices.test.ts b/packages/toolkit/src/tests/combineSlices.test.ts index 27115895c0..44a06d602e 100644 --- a/packages/toolkit/src/tests/combineSlices.test.ts +++ b/packages/toolkit/src/tests/combineSlices.test.ts @@ -2,7 +2,6 @@ import { createReducer } from '../createReducer' import { createAction } from '../createAction' import { createSlice } from '../createSlice' import type { WithApi, WithSlice } from '../combineSlices' -import { markReplaceable } from '../combineSlices' import { combineSlices } from '../combineSlices' import { expectType } from './helpers' import type { CombinedState } from '../query/core/apiState' @@ -73,7 +72,7 @@ describe('combineSlices', () => { expect(combinedReducer(undefined, dummyAction()).number).toBe(undefined) - const injectedReducer = combinedReducer.injectSlices(numberSlice) + const injectedReducer = combinedReducer.injectSlice(numberSlice) expect(injectedReducer(undefined, dummyAction()).number).toBe( numberSlice.getInitialState() @@ -83,15 +82,21 @@ describe('combineSlices', () => { const combinedReducer = combineSlices(stringSlice).withLazyLoadedSlices<{ boolean: boolean }>() - combinedReducer.injectSlices({ boolean: booleanReducer }) + combinedReducer.injectSlice({ name: 'boolean', reducer: booleanReducer }) expect(() => - combinedReducer.injectSlices({ boolean: booleanReducer }) + combinedReducer.injectSlice({ + name: 'boolean', + reducer: booleanReducer, + }) ).not.toThrow() expect(() => // @ts-expect-error wrong reducer - combinedReducer.injectSlices({ boolean: stringSlice.reducer }) + combinedReducer.injectSlice({ + name: 'boolean', + reducer: stringSlice.reducer, + }) ).toThrow( "Name 'boolean' has already been injected with different reducer instance" ) @@ -102,21 +107,16 @@ describe('combineSlices', () => { WithApi & { boolean: boolean } >() - combinedReducer.injectSlices( - markReplaceable(numberSlice), - markReplaceable(api), - { boolean: markReplaceable(booleanReducer) } - ) + combinedReducer.injectSlice(numberSlice) // for brevity const anyReducer = createReducer({} as any, () => {}) expect(() => - combinedReducer.injectSlices({ - number: anyReducer, - api: anyReducer, - boolean: anyReducer, - }) + combinedReducer.injectSlice( + { name: 'number', reducer: anyReducer }, + { allowReplace: true } + ) ).not.toThrow() }) }) @@ -126,8 +126,9 @@ describe('combineSlices', () => { const uninjectedState = combinedReducer(undefined, dummyAction()) - const injectedReducer = combinedReducer.injectSlices({ - boolean: booleanReducer, + const injectedReducer = combinedReducer.injectSlice({ + name: 'boolean', + reducer: booleanReducer, }) it('ensures state is defined in selector even if action has not been dispatched', () => { diff --git a/packages/toolkit/src/tests/combineSlices.typetest.ts b/packages/toolkit/src/tests/combineSlices.typetest.ts index 305ad6fa02..c9787eabc7 100644 --- a/packages/toolkit/src/tests/combineSlices.typetest.ts +++ b/packages/toolkit/src/tests/combineSlices.typetest.ts @@ -70,13 +70,16 @@ type ExampleApiState = ReturnType rootReducer(undefined, { type: '' }).api ) - const withNumber = rootReducer.injectSlices(numberSlice) + const withNumber = rootReducer.injectSlice(numberSlice) expectExactType(0)(withNumber(undefined, { type: '' }).number) - const withBool = rootReducer.injectSlices({ boolean: booleanReducer }) + const withBool = rootReducer.injectSlice({ + name: 'boolean', + reducer: booleanReducer, + }) expectExactType(true)(withBool(undefined, { type: '' }).boolean) - const withApi = rootReducer.injectSlices(exampleApi) + const withApi = rootReducer.injectSlice(exampleApi) expectExactType({} as ExampleApiState)( withApi(undefined, { type: '' }).api ) @@ -128,7 +131,10 @@ const wrongApi = createApi({ boolean: wrongBooleanReducer, }) - withLazy.injectSlices(numberSlice, exampleApi, { boolean: booleanReducer }) + withLazy + .injectSlice(numberSlice) + .injectSlice(exampleApi) + .injectSlice({ name: 'boolean', reducer: booleanReducer }) } /** @@ -150,7 +156,7 @@ const wrongApi = createApi({ ) const withInjection = rootReducer - .injectSlices(numberSlice) + .injectSlice(numberSlice) .selector((state) => state.number) expectExactType(0)( @@ -167,7 +173,7 @@ const wrongApi = createApi({ WithSlice >() - const innerSelector = innerReducer.injectSlices(numberSlice).selector( + const innerSelector = innerReducer.injectSlice(numberSlice).selector( (state) => state.number, (rootState: RootState) => rootState.inner ) From 891a9f0d1023207af4c716220c66e3ee62acb36e Mon Sep 17 00:00:00 2001 From: "ben.durrant" Date: Tue, 28 Mar 2023 10:29:10 +0100 Subject: [PATCH 063/412] rename StaticState to InitialState, now it can be injected into --- packages/toolkit/src/combineSlices.ts | 28 +++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/packages/toolkit/src/combineSlices.ts b/packages/toolkit/src/combineSlices.ts index 18bb78bef6..19d4916c55 100644 --- a/packages/toolkit/src/combineSlices.ts +++ b/packages/toolkit/src/combineSlices.ts @@ -78,13 +78,13 @@ type InjectConfig = { } type CombinedSliceState< - StaticState, + InitialState, LazyLoadedState extends Record = {}, InjectedKeys extends keyof LazyLoadedState = never > = Id< // TODO: use PreloadedState generic instead CombinedState< - StaticState & WithRequiredProp, InjectedKeys> + InitialState & WithRequiredProp, InjectedKeys> > > @@ -92,11 +92,11 @@ type CombinedSliceState< * A reducer that allows for slices/reducers to be injected after initialisation. */ interface CombinedSliceReducer< - StaticState, + InitialState, LazyLoadedState extends Record = {}, InjectedKeys extends keyof LazyLoadedState = never > extends Reducer< - CombinedSliceState, + CombinedSliceState, AnyAction > { /** @@ -130,7 +130,7 @@ interface CombinedSliceReducer< */ withLazyLoadedSlices< Lazy extends Record = {} - >(): CombinedSliceReducer + >(): CombinedSliceReducer /** * Inject a slice previously declared with `withLazyLoadedSlices`. @@ -143,11 +143,11 @@ interface CombinedSliceReducer< * ``` * */ - injectSlice>( + injectSlice>( slice: Sl, config?: InjectConfig ): CombinedSliceReducer< - StaticState, + InitialState, LazyLoadedState, InjectedKeys | SliceName > @@ -160,11 +160,11 @@ interface CombinedSliceReducer< * ``` * */ - injectSlice>( + injectSlice>( slice: A, config?: InjectConfig ): CombinedSliceReducer< - StaticState, + InitialState, LazyLoadedState, InjectedKeys | ApiReducerPath > @@ -251,7 +251,7 @@ interface CombinedSliceReducer< < Selected, State extends CombinedSliceState< - StaticState, + InitialState, LazyLoadedState, InjectedKeys >, @@ -316,7 +316,7 @@ interface CombinedSliceReducer< < Selected, State extends CombinedSliceState< - StaticState, + InitialState, LazyLoadedState, InjectedKeys >, @@ -337,7 +337,7 @@ interface CombinedSliceReducer< */ original: < State extends CombinedSliceState< - StaticState, + InitialState, LazyLoadedState, InjectedKeys > @@ -347,7 +347,7 @@ interface CombinedSliceReducer< } } -type StaticState> = +type InitialState> = UnionToIntersection< Slices[number] extends infer Slice ? Slice extends AnySlice @@ -425,7 +425,7 @@ const original = (state: any) => { export function combineSlices< Slices extends Array ->(...slices: Slices): CombinedSliceReducer>> { +>(...slices: Slices): CombinedSliceReducer>> { const reducerMap = Object.fromEntries(getReducers(slices)) const getReducer = () => combineReducers(reducerMap) From 7058b93a8581d84d74a6d75d6878c689a852ca19 Mon Sep 17 00:00:00 2001 From: "ben.durrant" Date: Tue, 28 Mar 2023 10:47:17 +0100 Subject: [PATCH 064/412] injectSlice -> inject, Slice -> SliceLike, Api -> ApiLike, allowReplace -> overrideExisting --- packages/toolkit/src/combineSlices.ts | 132 ++++++++++-------- .../toolkit/src/tests/combineSlices.test.ts | 20 +-- .../src/tests/combineSlices.typetest.ts | 30 ++-- 3 files changed, 97 insertions(+), 85 deletions(-) diff --git a/packages/toolkit/src/combineSlices.ts b/packages/toolkit/src/combineSlices.ts index 19d4916c55..7c51063efb 100644 --- a/packages/toolkit/src/combineSlices.ts +++ b/packages/toolkit/src/combineSlices.ts @@ -14,59 +14,68 @@ import type { WithRequiredProp, } from './tsHelpers' -type Slice = { +type SliceLike = { name: Name reducer: Reducer } -type AnySlice = Slice +type AnySliceLike = SliceLike -type SliceState = Sl extends Slice +type SliceLikeState = Sl extends SliceLike< + any, + infer State +> ? State : never -type SliceName = Sl extends Slice +type SliceLikeName = Sl extends SliceLike< + infer Name, + any +> ? Name : never -export type WithSlice = Id< +export type WithSlice = Id< { - [K in SliceName]: SliceState + [K in SliceLikeName]: SliceLikeState } > -type Api = { +type ApiLike = { reducerPath: ReducerPath reducer: Reducer } -type AnyApi = Api +type AnyApiLike = ApiLike -type ApiReducerPath = A extends Api +type ApiLikeReducerPath = A extends ApiLike< + infer ReducerPath, + any +> ? ReducerPath : never -type ApiState = A extends Api +type ApiLikeState = A extends ApiLike ? State : never -export type WithApi = { - [Path in ApiReducerPath]: ApiState +export type WithApi = { + [Path in ApiLikeReducerPath]: ApiLikeState } type ReducerMap = Record // only allow injection of slices we've already declared -type LazyLoadedSlice> = { +type LazyLoadedSliceLike> = { [Name in keyof LazyLoadedState]: Name extends string - ? Slice + ? SliceLike : never }[keyof LazyLoadedState] -type LazyLoadedApi> = { +type LazyLoadedApiLike> = { [Name in keyof LazyLoadedState]: Name extends string - ? Api + ? ApiLike : never }[keyof LazyLoadedState] @@ -74,7 +83,7 @@ type InjectConfig = { /** * Allow replacing reducer with a different reference. Normally, an error will be thrown if a different reducer instance to the one already injected is used. */ - allowReplace?: boolean + overrideExisting?: boolean } type CombinedSliceState< @@ -115,7 +124,7 @@ interface CombinedSliceReducer< * export interface LazyLoadedSlices extends WithSlice {} * } * - * const withBoolean = rootReducer.injectSlice(booleanSlice); + * const withBoolean = rootReducer.inject(booleanSlice); * * // elsewhere again * @@ -125,7 +134,7 @@ interface CombinedSliceReducer< * } * } * - * const withCustom = rootReducer.injectSlice({ name: "customName", reducer: customSlice.reducer }) + * const withCustom = rootReducer.inject({ name: "customName", reducer: customSlice.reducer }) * ``` */ withLazyLoadedSlices< @@ -138,35 +147,35 @@ interface CombinedSliceReducer< * Accepts an individual slice, or a { name, reducer } object. * * ```ts - * rootReducer.injectSlice(booleanSlice) - * rootReducer.injectSlice({ name: 'boolean', reducer: newReducer }, { allowReplace: true }) + * rootReducer.inject(booleanSlice) + * rootReducer.inject({ name: 'boolean', reducer: newReducer }, { overrideExisting: true }) * ``` * */ - injectSlice>( + inject>( slice: Sl, config?: InjectConfig ): CombinedSliceReducer< InitialState, LazyLoadedState, - InjectedKeys | SliceName + InjectedKeys | SliceLikeName > /** * Inject an RTKQ API instance previously declared with `withLazyLoadedSlices`. * * ```ts - * rootReducer.injectSlice(baseApi) + * rootReducer.inject(baseApi) * ``` * */ - injectSlice>( + inject>( slice: A, config?: InjectConfig ): CombinedSliceReducer< InitialState, LazyLoadedState, - InjectedKeys | ApiReducerPath + InjectedKeys | ApiLikeReducerPath > /** @@ -176,7 +185,7 @@ interface CombinedSliceReducer< * const selectBooleanWithoutInjection = (state: RootState) => state.boolean; * // ^? boolean | undefined * - * const selectBoolean = rootReducer.injectSlice(booleanSlice).selector((state) => { + * const selectBoolean = rootReducer.inject(booleanSlice).selector((state) => { * // if action hasn't been dispatched since slice was injected, this would usually be undefined * // however selector() uses a Proxy around the first parameter to ensure that it evaluates to the initial state instead, if undefined * return state.boolean; @@ -202,7 +211,7 @@ interface CombinedSliceReducer< * export interface LazyLoadedSlices extends WithSlice {} * } * - * const withBool = innerReducer.injectSlice(booleanSlice); + * const withBool = innerReducer.inject(booleanSlice); * * const selectBoolean = withBool.selector( * (state) => state.boolean, @@ -215,7 +224,7 @@ interface CombinedSliceReducer< * Value passed to selectorFn will be a Proxy - use selector.original(proxy) to get original state value (useful for debugging) * * ```ts - * const injectedReducer = rootReducer.injectSlice(booleanSlice); + * const injectedReducer = rootReducer.inject(booleanSlice); * const selectBoolean = injectedReducer.selector((state) => { * console.log(injectedReducer.selector.original(state).boolean) // possibly undefined * return state.boolean @@ -230,7 +239,7 @@ interface CombinedSliceReducer< * const selectBooleanWithoutInjection = (state: RootState) => state.boolean; * // ^? boolean | undefined * - * const selectBoolean = rootReducer.injectSlice(booleanSlice).selector((state) => { + * const selectBoolean = rootReducer.inject(booleanSlice).selector((state) => { * // if action hasn't been dispatched since slice was injected, this would usually be undefined * // however selector() uses a Proxy around the first parameter to ensure that it evaluates to the initial state instead, if undefined * return state.boolean; @@ -241,7 +250,7 @@ interface CombinedSliceReducer< * Value passed to selectorFn will be a Proxy - use selector.original(proxy) to get original state value (useful for debugging) * * ```ts - * const injectedReducer = rootReducer.injectSlice(booleanSlice); + * const injectedReducer = rootReducer.inject(booleanSlice); * const selectBoolean = injectedReducer.selector((state) => { * console.log(injectedReducer.selector.original(state).boolean) // undefined * return state.boolean @@ -267,7 +276,7 @@ interface CombinedSliceReducer< * const selectBooleanWithoutInjection = (state: RootState) => state.boolean; * // ^? boolean | undefined * - * const selectBoolean = rootReducer.injectSlice(booleanSlice).selector((state) => { + * const selectBoolean = rootReducer.inject(booleanSlice).selector((state) => { * // if action hasn't been dispatched since slice was injected, this would usually be undefined * // however selector() uses a Proxy around the first parameter to ensure that it evaluates to the initial state instead, if undefined * return state.boolean; @@ -293,7 +302,7 @@ interface CombinedSliceReducer< * interface LazyLoadedSlices extends WithSlice {} * } * - * const withBool = innerReducer.injectSlice(booleanSlice); + * const withBool = innerReducer.inject(booleanSlice); * * const selectBoolean = withBool.selector( * (state) => state.boolean, @@ -306,7 +315,7 @@ interface CombinedSliceReducer< * Value passed to selectorFn will be a Proxy - use selector.original(proxy) to get original state value (useful for debugging) * * ```ts - * const injectedReducer = rootReducer.injectSlice(booleanSlice); + * const injectedReducer = rootReducer.inject(booleanSlice); * const selectBoolean = injectedReducer.selector((state) => { * console.log(injectedReducer.selector.original(state).boolean) // possibly undefined * return state.boolean @@ -347,30 +356,33 @@ interface CombinedSliceReducer< } } -type InitialState> = - UnionToIntersection< - Slices[number] extends infer Slice - ? Slice extends AnySlice - ? WithSlice - : Slice extends AnyApi - ? WithApi - : StateFromReducersMapObject - : never - > +type InitialState< + Slices extends Array +> = UnionToIntersection< + Slices[number] extends infer Slice + ? Slice extends AnySliceLike + ? WithSlice + : Slice extends AnyApiLike + ? WithApi + : StateFromReducersMapObject + : never +> -const isSlice = ( - maybeSlice: AnySlice | AnyApi | ReducerMap -): maybeSlice is AnySlice => - 'name' in maybeSlice && typeof maybeSlice.name === 'string' +const isSliceLike = ( + maybeSliceLike: AnySliceLike | AnyApiLike | ReducerMap +): maybeSliceLike is AnySliceLike => + 'name' in maybeSliceLike && typeof maybeSliceLike.name === 'string' -const isApi = (maybeApi: AnySlice | AnyApi | ReducerMap): maybeApi is AnyApi => - 'reducerPath' in maybeApi && typeof maybeApi.reducerPath === 'string' +const isApiLike = ( + maybeApiLike: AnySliceLike | AnyApiLike | ReducerMap +): maybeApiLike is AnyApiLike => + 'reducerPath' in maybeApiLike && typeof maybeApiLike.reducerPath === 'string' -const getReducers = (slices: Array) => +const getReducers = (slices: Array) => slices.flatMap((sliceOrMap) => - isSlice(sliceOrMap) + isSliceLike(sliceOrMap) ? [[sliceOrMap.name, sliceOrMap.reducer] as const] - : isApi(sliceOrMap) + : isApiLike(sliceOrMap) ? [[sliceOrMap.reducerPath, sliceOrMap.reducer] as const] : Object.entries(sliceOrMap) ) @@ -424,7 +436,7 @@ const original = (state: any) => { } export function combineSlices< - Slices extends Array + Slices extends Array >(...slices: Slices): CombinedSliceReducer>> { const reducerMap = Object.fromEntries(getReducers(slices)) @@ -438,12 +450,12 @@ export function combineSlices< combinedReducer.withLazyLoadedSlices = () => combinedReducer - const injectSlice = ( - slice: AnySlice | AnyApi, + const inject = ( + slice: AnySliceLike | AnyApiLike, config: InjectConfig = {} ): typeof combinedReducer => { - if (isApi(slice)) { - return injectSlice( + if (isApiLike(slice)) { + return inject( { name: slice.reducerPath, reducer: slice.reducer, @@ -454,7 +466,7 @@ export function combineSlices< const { name, reducer: reducerToInject } = slice - if (process.env.NODE_ENV !== 'production' && !config.allowReplace) { + if (process.env.NODE_ENV !== 'production' && !config.overrideExisting) { const currentReducer = reducerMap[name] if (currentReducer && currentReducer !== reducerToInject) { throw new Error( @@ -488,5 +500,5 @@ export function combineSlices< { original } ) - return Object.assign(combinedReducer, { injectSlice, selector }) as any + return Object.assign(combinedReducer, { inject, selector }) as any } diff --git a/packages/toolkit/src/tests/combineSlices.test.ts b/packages/toolkit/src/tests/combineSlices.test.ts index 44a06d602e..a1aa865a29 100644 --- a/packages/toolkit/src/tests/combineSlices.test.ts +++ b/packages/toolkit/src/tests/combineSlices.test.ts @@ -63,7 +63,7 @@ describe('combineSlices', () => { api: api.reducer.getInitialState(), }) }) - describe('injectSlices', () => { + describe('injects', () => { it('injects slice', () => { const combinedReducer = combineSlices(stringSlice).withLazyLoadedSlices< @@ -72,7 +72,7 @@ describe('combineSlices', () => { expect(combinedReducer(undefined, dummyAction()).number).toBe(undefined) - const injectedReducer = combinedReducer.injectSlice(numberSlice) + const injectedReducer = combinedReducer.inject(numberSlice) expect(injectedReducer(undefined, dummyAction()).number).toBe( numberSlice.getInitialState() @@ -82,10 +82,10 @@ describe('combineSlices', () => { const combinedReducer = combineSlices(stringSlice).withLazyLoadedSlices<{ boolean: boolean }>() - combinedReducer.injectSlice({ name: 'boolean', reducer: booleanReducer }) + combinedReducer.inject({ name: 'boolean', reducer: booleanReducer }) expect(() => - combinedReducer.injectSlice({ + combinedReducer.inject({ name: 'boolean', reducer: booleanReducer, }) @@ -93,7 +93,7 @@ describe('combineSlices', () => { expect(() => // @ts-expect-error wrong reducer - combinedReducer.injectSlice({ + combinedReducer.inject({ name: 'boolean', reducer: stringSlice.reducer, }) @@ -101,21 +101,21 @@ describe('combineSlices', () => { "Name 'boolean' has already been injected with different reducer instance" ) }) - it('allows replacement of reducers specifically marked as replaceable', () => { + it('allows replacement of reducers if overrideExisting is true', () => { const combinedReducer = combineSlices(stringSlice).withLazyLoadedSlices< WithSlice & WithApi & { boolean: boolean } >() - combinedReducer.injectSlice(numberSlice) + combinedReducer.inject(numberSlice) // for brevity const anyReducer = createReducer({} as any, () => {}) expect(() => - combinedReducer.injectSlice( + combinedReducer.inject( { name: 'number', reducer: anyReducer }, - { allowReplace: true } + { overrideExisting: true } ) ).not.toThrow() }) @@ -126,7 +126,7 @@ describe('combineSlices', () => { const uninjectedState = combinedReducer(undefined, dummyAction()) - const injectedReducer = combinedReducer.injectSlice({ + const injectedReducer = combinedReducer.inject({ name: 'boolean', reducer: booleanReducer, }) diff --git a/packages/toolkit/src/tests/combineSlices.typetest.ts b/packages/toolkit/src/tests/combineSlices.typetest.ts index c9787eabc7..25cd1e5140 100644 --- a/packages/toolkit/src/tests/combineSlices.typetest.ts +++ b/packages/toolkit/src/tests/combineSlices.typetest.ts @@ -52,7 +52,7 @@ type ExampleApiState = ReturnType } /** - * Test: injectSlices marks injected keys as required + * Test: injects marks injected keys as required */ { const rootReducer = combineSlices(stringSlice).withLazyLoadedSlices< @@ -70,16 +70,16 @@ type ExampleApiState = ReturnType rootReducer(undefined, { type: '' }).api ) - const withNumber = rootReducer.injectSlice(numberSlice) + const withNumber = rootReducer.inject(numberSlice) expectExactType(0)(withNumber(undefined, { type: '' }).number) - const withBool = rootReducer.injectSlice({ + const withBool = rootReducer.inject({ name: 'boolean', reducer: booleanReducer, }) expectExactType(true)(withBool(undefined, { type: '' }).boolean) - const withApi = rootReducer.injectSlice(exampleApi) + const withApi = rootReducer.inject(exampleApi) expectExactType({} as ExampleApiState)( withApi(undefined, { type: '' }).api ) @@ -105,13 +105,13 @@ const wrongApi = createApi({ const rootReducer = combineSlices(stringSlice) // @ts-expect-error number undeclared - rootReducer.injectSlices(numberSlice) + rootReducer.injects(numberSlice) // @ts-expect-error api undeclared - rootReducer.injectSlices(exampleApi) + rootReducer.injects(exampleApi) // @ts-expect-error boolean undeclared - rootReducer.injectSlices({ + rootReducer.injects({ boolean: booleanReducer, }) @@ -121,20 +121,20 @@ const wrongApi = createApi({ >() // @ts-expect-error right name, wrong state - withLazy.injectSlices(wrongNumberSlice) + withLazy.injects(wrongNumberSlice) // @ts-expect-error right name, wrong state - withLazy.injectSlices(wrongApi) + withLazy.injects(wrongApi) // @ts-expect-error right name, wrong state - withLazy.injectSlices({ + withLazy.injects({ boolean: wrongBooleanReducer, }) withLazy - .injectSlice(numberSlice) - .injectSlice(exampleApi) - .injectSlice({ name: 'boolean', reducer: booleanReducer }) + .inject(numberSlice) + .inject(exampleApi) + .inject({ name: 'boolean', reducer: booleanReducer }) } /** @@ -156,7 +156,7 @@ const wrongApi = createApi({ ) const withInjection = rootReducer - .injectSlice(numberSlice) + .inject(numberSlice) .selector((state) => state.number) expectExactType(0)( @@ -173,7 +173,7 @@ const wrongApi = createApi({ WithSlice >() - const innerSelector = innerReducer.injectSlice(numberSlice).selector( + const innerSelector = innerReducer.inject(numberSlice).selector( (state) => state.number, (rootState: RootState) => rootState.inner ) From cdb1e00f6cc3af343fd20132f6e499967ac39300 Mon Sep 17 00:00:00 2001 From: "ben.durrant" Date: Tue, 28 Mar 2023 11:08:37 +0100 Subject: [PATCH 065/412] match RTKQ overrideExisting behaviour --- packages/toolkit/src/combineSlices.ts | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/packages/toolkit/src/combineSlices.ts b/packages/toolkit/src/combineSlices.ts index 7c51063efb..8c99eebd14 100644 --- a/packages/toolkit/src/combineSlices.ts +++ b/packages/toolkit/src/combineSlices.ts @@ -466,13 +466,22 @@ export function combineSlices< const { name, reducer: reducerToInject } = slice - if (process.env.NODE_ENV !== 'production' && !config.overrideExisting) { - const currentReducer = reducerMap[name] - if (currentReducer && currentReducer !== reducerToInject) { - throw new Error( - `Name '${name}' has already been injected with different reducer instance` + const currentReducer = reducerMap[name] + if ( + !config.overrideExisting && + currentReducer && + currentReducer !== reducerToInject + ) { + if ( + typeof process !== 'undefined' && + process.env.NODE_ENV === 'development' + ) { + console.error( + `called \`inject\` to override already-existing reducer ${name} without specifying \`overrideExisting: true\`` ) } + + return combinedReducer } reducerMap[name] = reducerToInject From 7cdaf5b05f30fe8b50ffcb5aa7014393c70da1da Mon Sep 17 00:00:00 2001 From: "ben.durrant" Date: Tue, 28 Mar 2023 11:41:12 +0100 Subject: [PATCH 066/412] fix test to match new behaviour --- .../toolkit/src/tests/combineSlices.test.ts | 32 ++++++++++--------- 1 file changed, 17 insertions(+), 15 deletions(-) diff --git a/packages/toolkit/src/tests/combineSlices.test.ts b/packages/toolkit/src/tests/combineSlices.test.ts index a1aa865a29..2b6ce2fbbe 100644 --- a/packages/toolkit/src/tests/combineSlices.test.ts +++ b/packages/toolkit/src/tests/combineSlices.test.ts @@ -78,28 +78,30 @@ describe('combineSlices', () => { numberSlice.getInitialState() ) }) - it('throws error when same name is used for different reducers', () => { + it('logs error when same name is used for different reducers', () => { + const spy = vi.spyOn(console, 'error').mockImplementation(() => {}) const combinedReducer = combineSlices(stringSlice).withLazyLoadedSlices<{ boolean: boolean }>() combinedReducer.inject({ name: 'boolean', reducer: booleanReducer }) - expect(() => - combinedReducer.inject({ - name: 'boolean', - reducer: booleanReducer, - }) - ).not.toThrow() + combinedReducer.inject({ + name: 'boolean', + reducer: booleanReducer, + }) - expect(() => - // @ts-expect-error wrong reducer - combinedReducer.inject({ - name: 'boolean', - reducer: stringSlice.reducer, - }) - ).toThrow( - "Name 'boolean' has already been injected with different reducer instance" + expect(spy).not.toHaveBeenCalled() + + // @ts-expect-error wrong reducer + combinedReducer.inject({ + name: 'boolean', + reducer: stringSlice.reducer, + }) + + expect(spy).toHaveBeenCalledWith( + `called \`inject\` to override already-existing reducer boolean without specifying \`overrideExisting: true\`` ) + spy.mockRestore() }) it('allows replacement of reducers if overrideExisting is true', () => { const combinedReducer = combineSlices(stringSlice).withLazyLoadedSlices< From bac53613ea595d0d8e9d8e6138efba7195e388f0 Mon Sep 17 00:00:00 2001 From: "ben.durrant" Date: Tue, 28 Mar 2023 12:08:31 +0100 Subject: [PATCH 067/412] remove unused export --- packages/toolkit/src/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/toolkit/src/index.ts b/packages/toolkit/src/index.ts index a3153ff3ff..0756787c07 100644 --- a/packages/toolkit/src/index.ts +++ b/packages/toolkit/src/index.ts @@ -185,6 +185,6 @@ export { } from './autoBatchEnhancer' export type { AutoBatchOptions } from './autoBatchEnhancer' -export { combineSlices, markReplaceable } from './combineSlices' +export { combineSlices } from './combineSlices' export type { WithSlice, WithApi } from './combineSlices' From 7d7616c7f2ad15c3929568758725f85688ef7045 Mon Sep 17 00:00:00 2001 From: "ben.durrant" Date: Sun, 2 Apr 2023 19:29:45 +0100 Subject: [PATCH 068/412] no longer require slice to be declared before injection --- packages/toolkit/src/combineSlices.ts | 100 ++++-------------- .../toolkit/src/tests/combineSlices.test.ts | 36 ++++--- .../src/tests/combineSlices.typetest.ts | 43 +------- 3 files changed, 44 insertions(+), 135 deletions(-) diff --git a/packages/toolkit/src/combineSlices.ts b/packages/toolkit/src/combineSlices.ts index 8c99eebd14..b7c6d2728a 100644 --- a/packages/toolkit/src/combineSlices.ts +++ b/packages/toolkit/src/combineSlices.ts @@ -65,20 +65,6 @@ export type WithApi = { type ReducerMap = Record -// only allow injection of slices we've already declared - -type LazyLoadedSliceLike> = { - [Name in keyof LazyLoadedState]: Name extends string - ? SliceLike - : never -}[keyof LazyLoadedState] - -type LazyLoadedApiLike> = { - [Name in keyof LazyLoadedState]: Name extends string - ? ApiLike - : never -}[keyof LazyLoadedState] - type InjectConfig = { /** * Allow replacing reducer with a different reference. Normally, an error will be thrown if a different reducer instance to the one already injected is used. @@ -86,26 +72,15 @@ type InjectConfig = { overrideExisting?: boolean } -type CombinedSliceState< - InitialState, - LazyLoadedState extends Record = {}, - InjectedKeys extends keyof LazyLoadedState = never -> = Id< - // TODO: use PreloadedState generic instead - CombinedState< - InitialState & WithRequiredProp, InjectedKeys> - > -> - /** * A reducer that allows for slices/reducers to be injected after initialisation. */ interface CombinedSliceReducer< InitialState, - LazyLoadedState extends Record = {}, - InjectedKeys extends keyof LazyLoadedState = never + DeclaredState = InitialState > extends Reducer< - CombinedSliceState, + // TODO: use PreloadedState generic instead + CombinedState, AnyAction > { /** @@ -139,44 +114,40 @@ interface CombinedSliceReducer< */ withLazyLoadedSlices< Lazy extends Record = {} - >(): CombinedSliceReducer + >(): CombinedSliceReducer>> /** - * Inject a slice previously declared with `withLazyLoadedSlices`. + * Inject a slice. * - * Accepts an individual slice, or a { name, reducer } object. + * Accepts an individual slice, or a "slice-like" { name, reducer } object. * * ```ts * rootReducer.inject(booleanSlice) - * rootReducer.inject({ name: 'boolean', reducer: newReducer }, { overrideExisting: true }) + * rootReducer.inject({ name: 'boolean' as const, reducer: newReducer }, { overrideExisting: true }) * ``` * */ - inject>( + inject( + // TODO: verify replacement matches slice: Sl, config?: InjectConfig - ): CombinedSliceReducer< - InitialState, - LazyLoadedState, - InjectedKeys | SliceLikeName - > + ): CombinedSliceReducer>> /** - * Inject an RTKQ API instance previously declared with `withLazyLoadedSlices`. + * Inject an RTKQ API instance. + * + * Accepts an individual instance, or an "api-like" { reducerPath, reducer } object * * ```ts * rootReducer.inject(baseApi) * ``` * */ - inject>( + inject( + // TODO: verify replacement matches slice: A, config?: InjectConfig - ): CombinedSliceReducer< - InitialState, - LazyLoadedState, - InjectedKeys | ApiLikeReducerPath - > + ): CombinedSliceReducer>> /** * Create a selector that guarantees that the slices injected will have a defined value when selector is run. @@ -257,17 +228,9 @@ interface CombinedSliceReducer< * }) * ``` */ - < - Selected, - State extends CombinedSliceState< - InitialState, - LazyLoadedState, - InjectedKeys - >, - Args extends any[] - >( - selectorFn: (state: State, ...args: Args) => Selected - ): (state: WithOptionalProp, ...args: Args) => Selected + ( + selectorFn: (state: DeclaredState, ...args: Args) => Selected + ): (state: InitialState, ...args: Args) => Selected /** * Create a selector that guarantees that the slices injected will have a defined value when selector is run. @@ -322,21 +285,12 @@ interface CombinedSliceReducer< * }) * ``` */ - < - Selected, - State extends CombinedSliceState< - InitialState, - LazyLoadedState, - InjectedKeys - >, - RootState, - Args extends any[] - >( - selectorFn: (state: State, ...args: Args) => Selected, + ( + selectorFn: (state: DeclaredState, ...args: Args) => Selected, selectState: ( rootState: RootState, ...args: NoInfer - ) => WithOptionalProp, InjectedKeys> + ) => InitialState & Partial ): (state: RootState, ...args: Args) => Selected /** * Returns the unproxied state. Useful for debugging. @@ -344,15 +298,7 @@ interface CombinedSliceReducer< * @returns original, unproxied state * @throws if value passed is not a state Proxy */ - original: < - State extends CombinedSliceState< - InitialState, - LazyLoadedState, - InjectedKeys - > - >( - state: State - ) => WithOptionalProp + original: (state: DeclaredState) => InitialState & Partial } } diff --git a/packages/toolkit/src/tests/combineSlices.test.ts b/packages/toolkit/src/tests/combineSlices.test.ts index 2b6ce2fbbe..abad45c142 100644 --- a/packages/toolkit/src/tests/combineSlices.test.ts +++ b/packages/toolkit/src/tests/combineSlices.test.ts @@ -79,31 +79,36 @@ describe('combineSlices', () => { ) }) it('logs error when same name is used for different reducers', () => { - const spy = vi.spyOn(console, 'error').mockImplementation(() => {}) + const consoleSpy = vi.spyOn(console, 'error').mockImplementation(() => {}) const combinedReducer = combineSlices(stringSlice).withLazyLoadedSlices<{ boolean: boolean }>() - combinedReducer.inject({ name: 'boolean', reducer: booleanReducer }) + combinedReducer.inject({ + name: 'boolean' as const, + reducer: booleanReducer, + }) combinedReducer.inject({ - name: 'boolean', + name: 'boolean' as const, reducer: booleanReducer, }) - expect(spy).not.toHaveBeenCalled() + expect(consoleSpy).not.toHaveBeenCalled() - // @ts-expect-error wrong reducer + // ts-expect-error wrong reducer + // TODO: this should error combinedReducer.inject({ - name: 'boolean', + name: 'boolean' as const, reducer: stringSlice.reducer, }) - expect(spy).toHaveBeenCalledWith( + expect(consoleSpy).toHaveBeenCalledWith( `called \`inject\` to override already-existing reducer boolean without specifying \`overrideExisting: true\`` ) - spy.mockRestore() + consoleSpy.mockRestore() }) it('allows replacement of reducers if overrideExisting is true', () => { + const consoleSpy = vi.spyOn(console, 'error').mockImplementation(() => {}) const combinedReducer = combineSlices(stringSlice).withLazyLoadedSlices< WithSlice & WithApi & { boolean: boolean } @@ -111,15 +116,12 @@ describe('combineSlices', () => { combinedReducer.inject(numberSlice) - // for brevity - const anyReducer = createReducer({} as any, () => {}) + combinedReducer.inject( + { name: 'number' as const, reducer: () => 0 }, + { overrideExisting: true } + ) - expect(() => - combinedReducer.inject( - { name: 'number', reducer: anyReducer }, - { overrideExisting: true } - ) - ).not.toThrow() + expect(consoleSpy).not.toHaveBeenCalled() }) }) describe('selector', () => { @@ -129,7 +131,7 @@ describe('combineSlices', () => { const uninjectedState = combinedReducer(undefined, dummyAction()) const injectedReducer = combinedReducer.inject({ - name: 'boolean', + name: 'boolean' as const, reducer: booleanReducer, }) diff --git a/packages/toolkit/src/tests/combineSlices.typetest.ts b/packages/toolkit/src/tests/combineSlices.typetest.ts index 25cd1e5140..6ed6695eff 100644 --- a/packages/toolkit/src/tests/combineSlices.typetest.ts +++ b/packages/toolkit/src/tests/combineSlices.typetest.ts @@ -52,7 +52,7 @@ type ExampleApiState = ReturnType } /** - * Test: injects marks injected keys as required + * Test: inject marks injected keys as required */ { const rootReducer = combineSlices(stringSlice).withLazyLoadedSlices< @@ -74,7 +74,7 @@ type ExampleApiState = ReturnType expectExactType(0)(withNumber(undefined, { type: '' }).number) const withBool = rootReducer.inject({ - name: 'boolean', + name: 'boolean' as const, reducer: booleanReducer, }) expectExactType(true)(withBool(undefined, { type: '' }).boolean) @@ -98,45 +98,6 @@ const wrongApi = createApi({ }), }) -/** - * Test: slices/reducers can only be injected if first added with withLazyLoadedSlices - */ -{ - const rootReducer = combineSlices(stringSlice) - - // @ts-expect-error number undeclared - rootReducer.injects(numberSlice) - - // @ts-expect-error api undeclared - rootReducer.injects(exampleApi) - - // @ts-expect-error boolean undeclared - rootReducer.injects({ - boolean: booleanReducer, - }) - - const withLazy = rootReducer.withLazyLoadedSlices< - WithSlice & - WithApi & { boolean: boolean } - >() - - // @ts-expect-error right name, wrong state - withLazy.injects(wrongNumberSlice) - - // @ts-expect-error right name, wrong state - withLazy.injects(wrongApi) - - // @ts-expect-error right name, wrong state - withLazy.injects({ - boolean: wrongBooleanReducer, - }) - - withLazy - .inject(numberSlice) - .inject(exampleApi) - .inject({ name: 'boolean', reducer: booleanReducer }) -} - /** * Test: selector() allows defining selectors with injected reducers defined */ From 157b88a6008cc569949676f3a8ce8c0f3fb67010 Mon Sep 17 00:00:00 2001 From: "ben.durrant" Date: Sun, 2 Apr 2023 20:12:26 +0100 Subject: [PATCH 069/412] rework selector inference --- packages/toolkit/src/combineSlices.ts | 34 +++++++++++++------ .../src/tests/combineSlices.typetest.ts | 20 +++++++++++ packages/toolkit/src/tsHelpers.ts | 4 +++ 3 files changed, 48 insertions(+), 10 deletions(-) diff --git a/packages/toolkit/src/combineSlices.ts b/packages/toolkit/src/combineSlices.ts index b7c6d2728a..0205776273 100644 --- a/packages/toolkit/src/combineSlices.ts +++ b/packages/toolkit/src/combineSlices.ts @@ -8,10 +8,9 @@ import { combineReducers } from 'redux' import { nanoid } from './nanoid' import type { Id, - NoInfer, + Tail, UnionToIntersection, WithOptionalProp, - WithRequiredProp, } from './tsHelpers' type SliceLike = { @@ -228,9 +227,15 @@ interface CombinedSliceReducer< * }) * ``` */ - ( - selectorFn: (state: DeclaredState, ...args: Args) => Selected - ): (state: InitialState, ...args: Args) => Selected + unknown>( + selectorFn: Selector + ): ( + state: WithOptionalProp< + Parameters[0], + Exclude + >, + ...args: Tail> + ) => ReturnType /** * Create a selector that guarantees that the slices injected will have a defined value when selector is run. @@ -285,13 +290,22 @@ interface CombinedSliceReducer< * }) * ``` */ - ( - selectorFn: (state: DeclaredState, ...args: Args) => Selected, + < + Selector extends (state: DeclaredState, ...args: any[]) => unknown, + RootState + >( + selectorFn: Selector, selectState: ( rootState: RootState, - ...args: NoInfer - ) => InitialState & Partial - ): (state: RootState, ...args: Args) => Selected + ...args: Tail> + ) => WithOptionalProp< + Parameters[0], + Exclude + > + ): ( + state: RootState, + ...args: Tail> + ) => ReturnType /** * Returns the unproxied state. Useful for debugging. * @param state state Proxy, that ensures injected reducers have value diff --git a/packages/toolkit/src/tests/combineSlices.typetest.ts b/packages/toolkit/src/tests/combineSlices.typetest.ts index 6ed6695eff..cb7d14fc64 100644 --- a/packages/toolkit/src/tests/combineSlices.typetest.ts +++ b/packages/toolkit/src/tests/combineSlices.typetest.ts @@ -125,6 +125,26 @@ const wrongApi = createApi({ ) } +/** + * Test: selector() passes arguments through + */ +{ + const rootReducer = combineSlices(stringSlice).withLazyLoadedSlices< + WithSlice & { boolean: boolean } + >() + + const selector = rootReducer + .inject(numberSlice) + .selector((state, num: number) => state.number) + + const state = rootReducer(undefined, { type: '' }) + // @ts-expect-error required argument + selector(state) + // @ts-expect-error number not string + selector(state, '') + selector(state, 0) +} + /** * Test: nested calls inferred correctly */ diff --git a/packages/toolkit/src/tsHelpers.ts b/packages/toolkit/src/tsHelpers.ts index 6a71dc4aa0..4a3a9d64f0 100644 --- a/packages/toolkit/src/tsHelpers.ts +++ b/packages/toolkit/src/tsHelpers.ts @@ -160,3 +160,7 @@ export type ActionFromMatcher> = M extends Matcher< : never export type Id = { [K in keyof T]: T[K] } & {} + +export type Tail = T extends [any, ...infer Tail] + ? Tail + : never From 1a218cf3e454874990c8e083e6c7d4aee6b33fb9 Mon Sep 17 00:00:00 2001 From: "ben.durrant" Date: Sun, 2 Apr 2023 21:14:08 +0100 Subject: [PATCH 070/412] check reducer matches --- packages/toolkit/src/combineSlices.ts | 74 ++++++++++++++++--- .../toolkit/src/tests/combineSlices.test.ts | 3 +- packages/toolkit/src/tsHelpers.ts | 2 + 3 files changed, 68 insertions(+), 11 deletions(-) diff --git a/packages/toolkit/src/combineSlices.ts b/packages/toolkit/src/combineSlices.ts index 0205776273..aafdb4f614 100644 --- a/packages/toolkit/src/combineSlices.ts +++ b/packages/toolkit/src/combineSlices.ts @@ -8,6 +8,7 @@ import { combineReducers } from 'redux' import { nanoid } from './nanoid' import type { Id, + NonUndefined, Tail, UnionToIntersection, WithOptionalProp, @@ -64,6 +65,20 @@ export type WithApi = { type ReducerMap = Record +type ExistingSliceLike = { + [Name in keyof DeclaredState]: SliceLike< + Name & string, + NonUndefined + > +}[keyof DeclaredState] + +type ExistingApiLike = { + [Name in keyof DeclaredState]: ApiLike< + Name & string, + NonUndefined + > +}[keyof DeclaredState] + type InjectConfig = { /** * Allow replacing reducer with a different reference. Normally, an error will be thrown if a different reducer instance to the one already injected is used. @@ -115,6 +130,42 @@ interface CombinedSliceReducer< Lazy extends Record = {} >(): CombinedSliceReducer>> + /** + * Inject an RTKQ API instance. + * + * Accepts an individual instance, or an "api-like" { reducerPath, reducer } object + * + * ```ts + * rootReducer.inject(baseApi) + * ``` + * + */ + inject>( + api: A, + config?: InjectConfig + ): CombinedSliceReducer>> + + /** + * Inject an RTKQ API instance. + * + * Accepts an individual instance, or an "api-like" { reducerPath, reducer } object + * + * ```ts + * rootReducer.inject(baseApi) + * ``` + * + */ + inject( + api: ApiLike< + ReducerPath, + State & (ReducerPath extends keyof DeclaredState ? never : State) + >, + config?: InjectConfig + ): CombinedSliceReducer< + InitialState, + Id>> + > + /** * Inject a slice. * @@ -126,27 +177,32 @@ interface CombinedSliceReducer< * ``` * */ - inject( - // TODO: verify replacement matches + inject>>( slice: Sl, config?: InjectConfig ): CombinedSliceReducer>> /** - * Inject an RTKQ API instance. + * Inject a slice. * - * Accepts an individual instance, or an "api-like" { reducerPath, reducer } object + * Accepts an individual slice, or a "slice-like" { name, reducer } object. * * ```ts - * rootReducer.inject(baseApi) + * rootReducer.inject(booleanSlice) + * rootReducer.inject({ name: 'boolean' as const, reducer: newReducer }, { overrideExisting: true }) * ``` * */ - inject( - // TODO: verify replacement matches - slice: A, + inject( + slice: SliceLike< + Name, + State & (Name extends keyof DeclaredState ? never : State) + >, config?: InjectConfig - ): CombinedSliceReducer>> + ): CombinedSliceReducer< + InitialState, + Id>> + > /** * Create a selector that guarantees that the slices injected will have a defined value when selector is run. diff --git a/packages/toolkit/src/tests/combineSlices.test.ts b/packages/toolkit/src/tests/combineSlices.test.ts index abad45c142..dbe5569413 100644 --- a/packages/toolkit/src/tests/combineSlices.test.ts +++ b/packages/toolkit/src/tests/combineSlices.test.ts @@ -95,10 +95,9 @@ describe('combineSlices', () => { expect(consoleSpy).not.toHaveBeenCalled() - // ts-expect-error wrong reducer - // TODO: this should error combinedReducer.inject({ name: 'boolean' as const, + // @ts-expect-error wrong reducer reducer: stringSlice.reducer, }) diff --git a/packages/toolkit/src/tsHelpers.ts b/packages/toolkit/src/tsHelpers.ts index 4a3a9d64f0..2dbfa25ce0 100644 --- a/packages/toolkit/src/tsHelpers.ts +++ b/packages/toolkit/src/tsHelpers.ts @@ -127,6 +127,8 @@ export type ExtractStoreExtensions = E extends readonly any[] */ export type NoInfer = [T][T extends any ? 0 : never] +export type NonUndefined = T extends undefined ? never : T + export type Omit = Pick> export type WithRequiredProp = Omit & From 49b3cea52fe685be0c83aa9fe3a3846c270af851 Mon Sep 17 00:00:00 2001 From: Mark Erikson Date: Sun, 2 Apr 2023 20:53:22 -0400 Subject: [PATCH 071/412] Update to latest redux/redux-thunk alphas with ESM --- packages/toolkit/package.json | 4 ++-- yarn.lock | 20 ++++++++++---------- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/packages/toolkit/package.json b/packages/toolkit/package.json index 253d722b4d..992a35eb7f 100644 --- a/packages/toolkit/package.json +++ b/packages/toolkit/package.json @@ -118,8 +118,8 @@ ], "dependencies": { "immer": "^9.0.16", - "redux": "5.0.0-alpha.2", - "redux-thunk": "3.0.0-alpha.1", + "redux": "5.0.0-alpha.4", + "redux-thunk": "3.0.0-alpha.3", "reselect": "^4.1.7" }, "peerDependencies": { diff --git a/yarn.lock b/yarn.lock index 2b1c6d3c62..0238249a7d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6781,8 +6781,8 @@ __metadata: node-fetch: ^2.6.1 prettier: ^2.2.1 query-string: ^7.0.1 - redux: 5.0.0-alpha.2 - redux-thunk: 3.0.0-alpha.1 + redux: 5.0.0-alpha.4 + redux-thunk: 3.0.0-alpha.3 reselect: ^4.1.7 rimraf: ^3.0.2 rollup: ^2.47.0 @@ -24477,12 +24477,12 @@ fsevents@^1.2.7: languageName: node linkType: hard -"redux-thunk@npm:3.0.0-alpha.1": - version: 3.0.0-alpha.1 - resolution: "redux-thunk@npm:3.0.0-alpha.1" +"redux-thunk@npm:3.0.0-alpha.3": + version: 3.0.0-alpha.3 + resolution: "redux-thunk@npm:3.0.0-alpha.3" peerDependencies: redux: ^4 - checksum: 280e0d399c96d071030f6dfa5bdb0b05b9f228dd0b840ffbb213e778b773efc25e6ba24f7ed13818696ea54a359ecc06a9e5e718e07d960a772101b14c0dd8c7 + checksum: a5be77887b422b3182ff7fae617ec552cd5f830afb326d83af32a430c3eb439c942a38c3691e5c975119e37787974172dbc0139f7782cbfaeea5c1292fa123ed languageName: node linkType: hard @@ -24504,10 +24504,10 @@ fsevents@^1.2.7: languageName: node linkType: hard -"redux@npm:5.0.0-alpha.2": - version: 5.0.0-alpha.2 - resolution: "redux@npm:5.0.0-alpha.2" - checksum: fbae31c55bab62a210a5a24a64721f593080fb94808c547b646ddff91515f8ab4e3363af5ece16491f0179d43729fc0350235c9fea7500b37b67d762a55b6945 +"redux@npm:5.0.0-alpha.4": + version: 5.0.0-alpha.4 + resolution: "redux@npm:5.0.0-alpha.4" + checksum: ebc98a74d84341df6db87222b6e54a658d68233924315ff67b4b2988ca0ea359e39632f7a43b65ec361ea7ba5714de4e34d76cb0b20089e785d11dc9e5a9e85e languageName: node linkType: hard From d150205190caa94337b85a7afa9a80f02e21621c Mon Sep 17 00:00:00 2001 From: Mark Erikson Date: Sun, 12 Mar 2023 20:35:35 -0400 Subject: [PATCH 072/412] Enable Node ESM tests for RTK 2.0 --- .github/workflows/tests.yml | 2 +- examples/publish-ci/node-esm/package.json | 2 +- examples/publish-ci/node-standard/package.json | 2 +- .../toolkit/scripts/{writeGitVersion.js => writeGitVersion.mjs} | 0 4 files changed, 3 insertions(+), 3 deletions(-) rename packages/toolkit/scripts/{writeGitVersion.js => writeGitVersion.mjs} (100%) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index a595938847..d1f6094f99 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -47,7 +47,7 @@ jobs: # Read existing version, reuse that, add a Git short hash - name: Set build version to Git commit - run: node scripts/writeGitVersion.js $(git rev-parse --short HEAD) + run: node scripts/writeGitVersion.mjs $(git rev-parse --short HEAD) - name: Check updated version run: jq .version package.json diff --git a/examples/publish-ci/node-esm/package.json b/examples/publish-ci/node-esm/package.json index 6730af9dde..c31677898c 100644 --- a/examples/publish-ci/node-esm/package.json +++ b/examples/publish-ci/node-esm/package.json @@ -5,7 +5,7 @@ "type": "module", "scripts": { "build": "echo Done", - "test": "node test-cjs.cjs" + "test": "node test-cjs.cjs && node test-esm.mjs" }, "dependencies": { "@reduxjs/toolkit": "^1.9.3", diff --git a/examples/publish-ci/node-standard/package.json b/examples/publish-ci/node-standard/package.json index 9c1bcd7d98..7b5444e3b9 100644 --- a/examples/publish-ci/node-standard/package.json +++ b/examples/publish-ci/node-standard/package.json @@ -4,7 +4,7 @@ "license": "MIT", "scripts": { "build": "echo Done", - "test": "node test-cjs.js" + "test": "node test-cjs.js && node test-esm.mjs" }, "dependencies": { "@reduxjs/toolkit": "^1.9.3", diff --git a/packages/toolkit/scripts/writeGitVersion.js b/packages/toolkit/scripts/writeGitVersion.mjs similarity index 100% rename from packages/toolkit/scripts/writeGitVersion.js rename to packages/toolkit/scripts/writeGitVersion.mjs From aae42aa6663d269f3d0a98e5df8cc88a956167b7 Mon Sep 17 00:00:00 2001 From: Mark Erikson Date: Sun, 2 Apr 2023 21:09:15 -0400 Subject: [PATCH 073/412] Rework build setup to hopefully really support ESM right this time - Dropped `type: "module"` from package - Used `.cjs` and `.mjs` extensions for artifacts - Added Webpack 4 fallback artifact --- .github/workflows/tests.yml | 8 ++++++- examples/publish-ci/node-esm/test-cjs.cjs | 6 +++--- examples/publish-ci/node-esm/test-esm.mjs | 9 +++++--- examples/publish-ci/node-standard/test-cjs.js | 6 +++--- .../publish-ci/node-standard/test-esm.mjs | 9 +++++--- packages/toolkit/package.json | 14 +++++-------- packages/toolkit/query/package.json | 4 ++-- packages/toolkit/query/react/package.json | 4 ++-- packages/toolkit/scripts/build.ts | 21 ++++++++++++++----- packages/toolkit/scripts/types.ts | 4 ++-- 10 files changed, 52 insertions(+), 33 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index d1f6094f99..ac983e31c1 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -190,10 +190,16 @@ jobs: run: yarn add ./package.tgz - name: Show installed RTK versions - run: yarn info @reduxjs/toolkit + run: yarn info @reduxjs/toolkit && yarn why @reduxjs/toolkit - name: Build example run: yarn build - name: Run test step run: yarn test + if: matrix.example != 'are-the-types-wrong' + + - name: Run test step (attw) + # Ignore "FalseCJS" errors in the `attw` job + run: yarn test -n FalseCJS + if: matrix.example == 'are-the-types-wrong' diff --git a/examples/publish-ci/node-esm/test-cjs.cjs b/examples/publish-ci/node-esm/test-cjs.cjs index 3a72248463..e12c456819 100644 --- a/examples/publish-ci/node-esm/test-cjs.cjs +++ b/examples/publish-ci/node-esm/test-cjs.cjs @@ -30,9 +30,9 @@ for (let [fn, name, category] of entries) { } const moduleNames = [ - ['@reduxjs/toolkit', 'dist/index.js'], - ['@reduxjs/toolkit/query', 'dist/query/index.js'], - ['@reduxjs/toolkit/query/react', 'dist/query/react/index.js'], + ['@reduxjs/toolkit', 'dist/cjs/index.js'], + ['@reduxjs/toolkit/query', 'dist/query/cjs/index.js'], + ['@reduxjs/toolkit/query/react', 'dist/query/react/cjs/index.js'], ] for (let [moduleName, expectedFilename] of moduleNames) { diff --git a/examples/publish-ci/node-esm/test-esm.mjs b/examples/publish-ci/node-esm/test-esm.mjs index ed560d467a..ea9ea17d3b 100644 --- a/examples/publish-ci/node-esm/test-esm.mjs +++ b/examples/publish-ci/node-esm/test-esm.mjs @@ -33,9 +33,12 @@ for (let [fn, name, category] of entries) { } const moduleNames = [ - ['@reduxjs/toolkit', 'dist/index.js'], - ['@reduxjs/toolkit/query', 'dist/query/index.js'], - ['@reduxjs/toolkit/query/react', 'dist/query/react/index.js'], + ['@reduxjs/toolkit', 'dist/redux-toolkit.modern.mjs'], + ['@reduxjs/toolkit/query', 'dist/query/rtk-query.modern.mjs'], + [ + '@reduxjs/toolkit/query/react', + 'dist/query/react/rtk-query-react.modern.mjs', + ], ] ;(async () => { diff --git a/examples/publish-ci/node-standard/test-cjs.js b/examples/publish-ci/node-standard/test-cjs.js index 3a72248463..e12c456819 100644 --- a/examples/publish-ci/node-standard/test-cjs.js +++ b/examples/publish-ci/node-standard/test-cjs.js @@ -30,9 +30,9 @@ for (let [fn, name, category] of entries) { } const moduleNames = [ - ['@reduxjs/toolkit', 'dist/index.js'], - ['@reduxjs/toolkit/query', 'dist/query/index.js'], - ['@reduxjs/toolkit/query/react', 'dist/query/react/index.js'], + ['@reduxjs/toolkit', 'dist/cjs/index.js'], + ['@reduxjs/toolkit/query', 'dist/query/cjs/index.js'], + ['@reduxjs/toolkit/query/react', 'dist/query/react/cjs/index.js'], ] for (let [moduleName, expectedFilename] of moduleNames) { diff --git a/examples/publish-ci/node-standard/test-esm.mjs b/examples/publish-ci/node-standard/test-esm.mjs index ed560d467a..ea9ea17d3b 100644 --- a/examples/publish-ci/node-standard/test-esm.mjs +++ b/examples/publish-ci/node-standard/test-esm.mjs @@ -33,9 +33,12 @@ for (let [fn, name, category] of entries) { } const moduleNames = [ - ['@reduxjs/toolkit', 'dist/index.js'], - ['@reduxjs/toolkit/query', 'dist/query/index.js'], - ['@reduxjs/toolkit/query/react', 'dist/query/react/index.js'], + ['@reduxjs/toolkit', 'dist/redux-toolkit.modern.mjs'], + ['@reduxjs/toolkit/query', 'dist/query/rtk-query.modern.mjs'], + [ + '@reduxjs/toolkit/query/react', + 'dist/query/react/rtk-query-react.modern.mjs', + ], ] ;(async () => { diff --git a/packages/toolkit/package.json b/packages/toolkit/package.json index 992a35eb7f..b002897fc7 100644 --- a/packages/toolkit/package.json +++ b/packages/toolkit/package.json @@ -22,25 +22,24 @@ "publishConfig": { "access": "public" }, - "type": "module", - "module": "dist/redux-toolkit.modern.js", + "module": "dist/redux-toolkit.esm.mjs", "main": "dist/cjs/index.js", "types": "dist/index.d.ts", "exports": { "./package.json": "./package.json", ".": { "types": "./dist/index.d.ts", - "import": "./dist/redux-toolkit.modern.js", + "import": "./dist/redux-toolkit.modern.mjs", "default": "./dist/cjs/index.js" }, "./query": { "types": "./dist/query/index.d.ts", - "import": "./dist/query/rtk-query.modern.js", + "import": "./dist/query/rtk-query.modern.mjs", "default": "./dist/query/cjs/index.js" }, "./query/react": { "types": "./dist/query/react/index.d.ts", - "import": "./dist/query/react/rtk-query-react.modern.js", + "import": "./dist/query/react/rtk-query-react.modern.mjs", "default": "./dist/query/react/cjs/index.js" } }, @@ -109,10 +108,7 @@ "prepack": "npm run build-prepare" }, "files": [ - "dist/**/*.js", - "dist/**/*.js.map", - "dist/**/*.d.ts", - "dist/**/package.json", + "dist/", "src/", "query" ], diff --git a/packages/toolkit/query/package.json b/packages/toolkit/query/package.json index ca99300e54..e4cd939c28 100644 --- a/packages/toolkit/query/package.json +++ b/packages/toolkit/query/package.json @@ -3,14 +3,14 @@ "version": "1.0.0", "description": "", "type": "module", - "module": "../dist/query/rtk-query.modern.js", + "module": "../dist/query/rtk-query.esm.mjs", "main": "../dist/query/cjs/index.js", "types": "./../dist/query/index.d.ts", "exports": { "./package.json": "./package.json", ".": { "types": "./../dist/query/index.d.ts", - "import": "./../dist/query/rtk-query.modern.js", + "import": "./../dist/query/rtk-query.modern.mjs", "default": "./../dist/query/cjs/index.js" } }, diff --git a/packages/toolkit/query/react/package.json b/packages/toolkit/query/react/package.json index 20fcabea62..4f7b5de879 100644 --- a/packages/toolkit/query/react/package.json +++ b/packages/toolkit/query/react/package.json @@ -3,14 +3,14 @@ "version": "1.0.0", "description": "", "type": "module", - "module": "../../dist/query/react/rtk-query-react.modern.js", + "module": "../../dist/query/react/rtk-query-react.esm.mjs", "main": "../../dist/query/react/cjs/index.js", "types": "./../../dist/query/react/index.d.ts", "exports": { "./package.json": "./package.json", ".": { "types": "./../../dist/query/react/index.d.ts", - "import": "./../../dist/query/react/rtk-query-react.modern.js", + "import": "./../../dist/query/react/rtk-query-react.modern.mjs", "default": "./../../dist/query/react/cjs/index.js" } }, diff --git a/packages/toolkit/scripts/build.ts b/packages/toolkit/scripts/build.ts index 76980b754f..726580d5bf 100644 --- a/packages/toolkit/scripts/build.ts +++ b/packages/toolkit/scripts/build.ts @@ -39,14 +39,14 @@ const outputDir = path.join(__dirname, '../dist') const buildTargets: BuildOptions[] = [ { format: 'cjs', - name: 'cjs.development', + name: 'development', target: 'esnext', minify: false, env: 'development', }, { format: 'cjs', - name: 'cjs.production.min', + name: 'production.min', target: 'esnext', minify: true, env: 'production', @@ -59,6 +59,15 @@ const buildTargets: BuildOptions[] = [ minify: false, env: '', }, + // ESM, embedded `process`: fallback for Webpack 4, + // which doesn't support `exports` field or optional chaining + { + format: 'esm', + name: 'esm', + target: 'esnext', + minify: false, + env: '', + }, // ESM, pre-compiled "dev": browser development { format: 'esm', @@ -145,8 +154,10 @@ async function bundle(options: BuildOptions & EntryPointOptions) { folderSegments.push('cjs') } + const extension = format === 'esm' ? 'mjs' : 'cjs' + const outputFolder = path.join(...folderSegments) - const outputFilename = `${prefix}.${name}.js` + const outputFilename = `${prefix}.${name}.${extension}` await fs.ensureDir(outputFolder) @@ -306,9 +317,9 @@ async function writeCommonJSEntry(folder: string, prefix: string) { path.join(folder, 'index.js'), `'use strict' if (process.env.NODE_ENV === 'production') { - module.exports = require('./${prefix}.cjs.production.min.js') + module.exports = require('./${prefix}.production.min.cjs') } else { - module.exports = require('./${prefix}.cjs.development.js') + module.exports = require('./${prefix}.development.cjs') }` ) diff --git a/packages/toolkit/scripts/types.ts b/packages/toolkit/scripts/types.ts index 9a9f48e626..9da591e096 100644 --- a/packages/toolkit/scripts/types.ts +++ b/packages/toolkit/scripts/types.ts @@ -1,8 +1,8 @@ export interface BuildOptions { format: 'cjs' | 'umd' | 'esm' name: - | 'cjs.development' - | 'cjs.production.min' + | 'development' + | 'production.min' | 'esm' | 'modern' | 'modern.development' From da59abcf719a49082c4a27c1ef4386a6368e7d88 Mon Sep 17 00:00:00 2001 From: Mark Erikson Date: Sun, 2 Apr 2023 23:22:14 -0400 Subject: [PATCH 074/412] Output a "legacy-esm" artifact for Webpack 4 usage --- packages/toolkit/package.json | 4 ++-- packages/toolkit/query/package.json | 2 +- packages/toolkit/query/react/package.json | 2 +- packages/toolkit/scripts/build.ts | 5 +++-- packages/toolkit/scripts/types.ts | 2 +- 5 files changed, 8 insertions(+), 7 deletions(-) diff --git a/packages/toolkit/package.json b/packages/toolkit/package.json index b002897fc7..f7567792df 100644 --- a/packages/toolkit/package.json +++ b/packages/toolkit/package.json @@ -22,7 +22,7 @@ "publishConfig": { "access": "public" }, - "module": "dist/redux-toolkit.esm.mjs", + "module": "dist/redux-toolkit.legacy-esm.js", "main": "dist/cjs/index.js", "types": "dist/index.d.ts", "exports": { @@ -98,7 +98,7 @@ "run-build": "tsx ./scripts/build.ts", "build-ci": "yarn rimraf dist && yarn tsc && yarn run-build --skipExtraction", "build-prepare": "npm run build-ci", - "build": "yarn rimraf dist && yarn tsc && yarn run-build --local --skipExtraction", + "build": "yarn rimraf dist && echo Compiling... && yarn tsc && yarn run-build --local --skipExtraction", "build-only": "yarn rimraf dist && yarn tsc && yarn run-build --skipExtraction", "format": "prettier --write \"(src|examples)/**/*.{ts,tsx}\" \"**/*.md\"", "format:check": "prettier --list-different \"(src|examples)/**/*.{ts,tsx}\" \"docs/*/**.md\"", diff --git a/packages/toolkit/query/package.json b/packages/toolkit/query/package.json index e4cd939c28..7939fc749a 100644 --- a/packages/toolkit/query/package.json +++ b/packages/toolkit/query/package.json @@ -3,7 +3,7 @@ "version": "1.0.0", "description": "", "type": "module", - "module": "../dist/query/rtk-query.esm.mjs", + "module": "../dist/query/rtk-query.legacy-esm.js", "main": "../dist/query/cjs/index.js", "types": "./../dist/query/index.d.ts", "exports": { diff --git a/packages/toolkit/query/react/package.json b/packages/toolkit/query/react/package.json index 4f7b5de879..901fc64d79 100644 --- a/packages/toolkit/query/react/package.json +++ b/packages/toolkit/query/react/package.json @@ -3,7 +3,7 @@ "version": "1.0.0", "description": "", "type": "module", - "module": "../../dist/query/react/rtk-query-react.esm.mjs", + "module": "../../dist/query/react/rtk-query-react.legacy-esm.js", "main": "../../dist/query/react/cjs/index.js", "types": "./../../dist/query/react/index.d.ts", "exports": { diff --git a/packages/toolkit/scripts/build.ts b/packages/toolkit/scripts/build.ts index 726580d5bf..c11fa693c1 100644 --- a/packages/toolkit/scripts/build.ts +++ b/packages/toolkit/scripts/build.ts @@ -63,7 +63,7 @@ const buildTargets: BuildOptions[] = [ // which doesn't support `exports` field or optional chaining { format: 'esm', - name: 'esm', + name: 'legacy-esm', target: 'esnext', minify: false, env: '', @@ -154,7 +154,8 @@ async function bundle(options: BuildOptions & EntryPointOptions) { folderSegments.push('cjs') } - const extension = format === 'esm' ? 'mjs' : 'cjs' + const extension = + name === 'legacy-esm' ? 'js' : format === 'esm' ? 'mjs' : 'cjs' const outputFolder = path.join(...folderSegments) const outputFilename = `${prefix}.${name}.${extension}` diff --git a/packages/toolkit/scripts/types.ts b/packages/toolkit/scripts/types.ts index 9da591e096..08bac037e4 100644 --- a/packages/toolkit/scripts/types.ts +++ b/packages/toolkit/scripts/types.ts @@ -3,7 +3,7 @@ export interface BuildOptions { name: | 'development' | 'production.min' - | 'esm' + | 'legacy-esm' | 'modern' | 'modern.development' | 'modern.production.min' From 2dc19b84f32fdeda6141d9b7a481a7f379b03141 Mon Sep 17 00:00:00 2001 From: Mark Erikson Date: Mon, 3 Apr 2023 00:35:43 -0400 Subject: [PATCH 075/412] Release 2.0.0-alpha.3 --- packages/toolkit/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/toolkit/package.json b/packages/toolkit/package.json index f7567792df..2db69680d1 100644 --- a/packages/toolkit/package.json +++ b/packages/toolkit/package.json @@ -1,6 +1,6 @@ { "name": "@reduxjs/toolkit", - "version": "2.0.0-alpha.2", + "version": "2.0.0-alpha.3", "description": "The official, opinionated, batteries-included toolset for efficient Redux development", "author": "Mark Erikson ", "license": "MIT", From 140cfc8cf14951c7cf90b04569b650e9fa426bb1 Mon Sep 17 00:00:00 2001 From: Mark Erikson Date: Mon, 3 Apr 2023 11:05:17 -0400 Subject: [PATCH 076/412] Bump Immer to 10.0-beta.4 --- packages/toolkit/package.json | 2 +- yarn.lock | 9 ++++++++- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/packages/toolkit/package.json b/packages/toolkit/package.json index 2db69680d1..e359efff46 100644 --- a/packages/toolkit/package.json +++ b/packages/toolkit/package.json @@ -113,7 +113,7 @@ "query" ], "dependencies": { - "immer": "^9.0.16", + "immer": "^10.0.0-beta.4", "redux": "5.0.0-alpha.4", "redux-thunk": "3.0.0-alpha.3", "reselect": "^4.1.7" diff --git a/yarn.lock b/yarn.lock index 0238249a7d..7e8417188d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6771,7 +6771,7 @@ __metadata: eslint-plugin-react: ^7.23.2 eslint-plugin-react-hooks: ^4.2.0 fs-extra: ^9.1.0 - immer: ^9.0.16 + immer: ^10.0.0-beta.4 invariant: ^2.2.4 jsdom: ^21.0.0 json-stringify-safe: ^5.0.1 @@ -16750,6 +16750,13 @@ fsevents@^1.2.7: languageName: node linkType: hard +"immer@npm:^10.0.0-beta.4": + version: 10.0.0-beta.4 + resolution: "immer@npm:10.0.0-beta.4" + checksum: 121d89855b91d83e415d30ab83cf1ce52aae24fee704980da0d3e34ea5f7be2c9febce2457f0ff133a68da6aafdf243e8c947d1bc0a6d6ef40137118213187a6 + languageName: node + linkType: hard + "immer@npm:^9.0.16": version: 9.0.16 resolution: "immer@npm:9.0.16" From 90d0b0ccdcc2d37a0dd61b5942efe80f7a55fbcb Mon Sep 17 00:00:00 2001 From: Mark Erikson Date: Mon, 3 Apr 2023 12:49:35 -0400 Subject: [PATCH 077/412] Bump Vite version in CI example --- examples/publish-ci/vite/package.json | 2 +- examples/publish-ci/vite/yarn.lock | 208 +++++++++++++------------- 2 files changed, 105 insertions(+), 105 deletions(-) diff --git a/examples/publish-ci/vite/package.json b/examples/publish-ci/vite/package.json index 6ab86d02d9..a576a66daf 100644 --- a/examples/publish-ci/vite/package.json +++ b/examples/publish-ci/vite/package.json @@ -31,7 +31,7 @@ "prettier": "^2.8.4", "serve": "^14.2.0", "typescript": "^4.9.4", - "vite": "^4.0.0" + "vite": "^4.2.1" }, "msw": { "workerDirectory": "public" diff --git a/examples/publish-ci/vite/yarn.lock b/examples/publish-ci/vite/yarn.lock index 950f00968a..2e6212c1d7 100644 --- a/examples/publish-ci/vite/yarn.lock +++ b/examples/publish-ci/vite/yarn.lock @@ -287,156 +287,156 @@ __metadata: languageName: node linkType: hard -"@esbuild/android-arm64@npm:0.16.17": - version: 0.16.17 - resolution: "@esbuild/android-arm64@npm:0.16.17" +"@esbuild/android-arm64@npm:0.17.15": + version: 0.17.15 + resolution: "@esbuild/android-arm64@npm:0.17.15" conditions: os=android & cpu=arm64 languageName: node linkType: hard -"@esbuild/android-arm@npm:0.16.17": - version: 0.16.17 - resolution: "@esbuild/android-arm@npm:0.16.17" +"@esbuild/android-arm@npm:0.17.15": + version: 0.17.15 + resolution: "@esbuild/android-arm@npm:0.17.15" conditions: os=android & cpu=arm languageName: node linkType: hard -"@esbuild/android-x64@npm:0.16.17": - version: 0.16.17 - resolution: "@esbuild/android-x64@npm:0.16.17" +"@esbuild/android-x64@npm:0.17.15": + version: 0.17.15 + resolution: "@esbuild/android-x64@npm:0.17.15" conditions: os=android & cpu=x64 languageName: node linkType: hard -"@esbuild/darwin-arm64@npm:0.16.17": - version: 0.16.17 - resolution: "@esbuild/darwin-arm64@npm:0.16.17" +"@esbuild/darwin-arm64@npm:0.17.15": + version: 0.17.15 + resolution: "@esbuild/darwin-arm64@npm:0.17.15" conditions: os=darwin & cpu=arm64 languageName: node linkType: hard -"@esbuild/darwin-x64@npm:0.16.17": - version: 0.16.17 - resolution: "@esbuild/darwin-x64@npm:0.16.17" +"@esbuild/darwin-x64@npm:0.17.15": + version: 0.17.15 + resolution: "@esbuild/darwin-x64@npm:0.17.15" conditions: os=darwin & cpu=x64 languageName: node linkType: hard -"@esbuild/freebsd-arm64@npm:0.16.17": - version: 0.16.17 - resolution: "@esbuild/freebsd-arm64@npm:0.16.17" +"@esbuild/freebsd-arm64@npm:0.17.15": + version: 0.17.15 + resolution: "@esbuild/freebsd-arm64@npm:0.17.15" conditions: os=freebsd & cpu=arm64 languageName: node linkType: hard -"@esbuild/freebsd-x64@npm:0.16.17": - version: 0.16.17 - resolution: "@esbuild/freebsd-x64@npm:0.16.17" +"@esbuild/freebsd-x64@npm:0.17.15": + version: 0.17.15 + resolution: "@esbuild/freebsd-x64@npm:0.17.15" conditions: os=freebsd & cpu=x64 languageName: node linkType: hard -"@esbuild/linux-arm64@npm:0.16.17": - version: 0.16.17 - resolution: "@esbuild/linux-arm64@npm:0.16.17" +"@esbuild/linux-arm64@npm:0.17.15": + version: 0.17.15 + resolution: "@esbuild/linux-arm64@npm:0.17.15" conditions: os=linux & cpu=arm64 languageName: node linkType: hard -"@esbuild/linux-arm@npm:0.16.17": - version: 0.16.17 - resolution: "@esbuild/linux-arm@npm:0.16.17" +"@esbuild/linux-arm@npm:0.17.15": + version: 0.17.15 + resolution: "@esbuild/linux-arm@npm:0.17.15" conditions: os=linux & cpu=arm languageName: node linkType: hard -"@esbuild/linux-ia32@npm:0.16.17": - version: 0.16.17 - resolution: "@esbuild/linux-ia32@npm:0.16.17" +"@esbuild/linux-ia32@npm:0.17.15": + version: 0.17.15 + resolution: "@esbuild/linux-ia32@npm:0.17.15" conditions: os=linux & cpu=ia32 languageName: node linkType: hard -"@esbuild/linux-loong64@npm:0.16.17": - version: 0.16.17 - resolution: "@esbuild/linux-loong64@npm:0.16.17" +"@esbuild/linux-loong64@npm:0.17.15": + version: 0.17.15 + resolution: "@esbuild/linux-loong64@npm:0.17.15" conditions: os=linux & cpu=loong64 languageName: node linkType: hard -"@esbuild/linux-mips64el@npm:0.16.17": - version: 0.16.17 - resolution: "@esbuild/linux-mips64el@npm:0.16.17" +"@esbuild/linux-mips64el@npm:0.17.15": + version: 0.17.15 + resolution: "@esbuild/linux-mips64el@npm:0.17.15" conditions: os=linux & cpu=mips64el languageName: node linkType: hard -"@esbuild/linux-ppc64@npm:0.16.17": - version: 0.16.17 - resolution: "@esbuild/linux-ppc64@npm:0.16.17" +"@esbuild/linux-ppc64@npm:0.17.15": + version: 0.17.15 + resolution: "@esbuild/linux-ppc64@npm:0.17.15" conditions: os=linux & cpu=ppc64 languageName: node linkType: hard -"@esbuild/linux-riscv64@npm:0.16.17": - version: 0.16.17 - resolution: "@esbuild/linux-riscv64@npm:0.16.17" +"@esbuild/linux-riscv64@npm:0.17.15": + version: 0.17.15 + resolution: "@esbuild/linux-riscv64@npm:0.17.15" conditions: os=linux & cpu=riscv64 languageName: node linkType: hard -"@esbuild/linux-s390x@npm:0.16.17": - version: 0.16.17 - resolution: "@esbuild/linux-s390x@npm:0.16.17" +"@esbuild/linux-s390x@npm:0.17.15": + version: 0.17.15 + resolution: "@esbuild/linux-s390x@npm:0.17.15" conditions: os=linux & cpu=s390x languageName: node linkType: hard -"@esbuild/linux-x64@npm:0.16.17": - version: 0.16.17 - resolution: "@esbuild/linux-x64@npm:0.16.17" +"@esbuild/linux-x64@npm:0.17.15": + version: 0.17.15 + resolution: "@esbuild/linux-x64@npm:0.17.15" conditions: os=linux & cpu=x64 languageName: node linkType: hard -"@esbuild/netbsd-x64@npm:0.16.17": - version: 0.16.17 - resolution: "@esbuild/netbsd-x64@npm:0.16.17" +"@esbuild/netbsd-x64@npm:0.17.15": + version: 0.17.15 + resolution: "@esbuild/netbsd-x64@npm:0.17.15" conditions: os=netbsd & cpu=x64 languageName: node linkType: hard -"@esbuild/openbsd-x64@npm:0.16.17": - version: 0.16.17 - resolution: "@esbuild/openbsd-x64@npm:0.16.17" +"@esbuild/openbsd-x64@npm:0.17.15": + version: 0.17.15 + resolution: "@esbuild/openbsd-x64@npm:0.17.15" conditions: os=openbsd & cpu=x64 languageName: node linkType: hard -"@esbuild/sunos-x64@npm:0.16.17": - version: 0.16.17 - resolution: "@esbuild/sunos-x64@npm:0.16.17" +"@esbuild/sunos-x64@npm:0.17.15": + version: 0.17.15 + resolution: "@esbuild/sunos-x64@npm:0.17.15" conditions: os=sunos & cpu=x64 languageName: node linkType: hard -"@esbuild/win32-arm64@npm:0.16.17": - version: 0.16.17 - resolution: "@esbuild/win32-arm64@npm:0.16.17" +"@esbuild/win32-arm64@npm:0.17.15": + version: 0.17.15 + resolution: "@esbuild/win32-arm64@npm:0.17.15" conditions: os=win32 & cpu=arm64 languageName: node linkType: hard -"@esbuild/win32-ia32@npm:0.16.17": - version: 0.16.17 - resolution: "@esbuild/win32-ia32@npm:0.16.17" +"@esbuild/win32-ia32@npm:0.17.15": + version: 0.17.15 + resolution: "@esbuild/win32-ia32@npm:0.17.15" conditions: os=win32 & cpu=ia32 languageName: node linkType: hard -"@esbuild/win32-x64@npm:0.16.17": - version: 0.16.17 - resolution: "@esbuild/win32-x64@npm:0.16.17" +"@esbuild/win32-x64@npm:0.17.15": + version: 0.17.15 + resolution: "@esbuild/win32-x64@npm:0.17.15" conditions: os=win32 & cpu=x64 languageName: node linkType: hard @@ -1722,32 +1722,32 @@ __metadata: languageName: node linkType: hard -"esbuild@npm:^0.16.14": - version: 0.16.17 - resolution: "esbuild@npm:0.16.17" - dependencies: - "@esbuild/android-arm": 0.16.17 - "@esbuild/android-arm64": 0.16.17 - "@esbuild/android-x64": 0.16.17 - "@esbuild/darwin-arm64": 0.16.17 - "@esbuild/darwin-x64": 0.16.17 - "@esbuild/freebsd-arm64": 0.16.17 - "@esbuild/freebsd-x64": 0.16.17 - "@esbuild/linux-arm": 0.16.17 - "@esbuild/linux-arm64": 0.16.17 - "@esbuild/linux-ia32": 0.16.17 - "@esbuild/linux-loong64": 0.16.17 - "@esbuild/linux-mips64el": 0.16.17 - "@esbuild/linux-ppc64": 0.16.17 - "@esbuild/linux-riscv64": 0.16.17 - "@esbuild/linux-s390x": 0.16.17 - "@esbuild/linux-x64": 0.16.17 - "@esbuild/netbsd-x64": 0.16.17 - "@esbuild/openbsd-x64": 0.16.17 - "@esbuild/sunos-x64": 0.16.17 - "@esbuild/win32-arm64": 0.16.17 - "@esbuild/win32-ia32": 0.16.17 - "@esbuild/win32-x64": 0.16.17 +"esbuild@npm:^0.17.5": + version: 0.17.15 + resolution: "esbuild@npm:0.17.15" + dependencies: + "@esbuild/android-arm": 0.17.15 + "@esbuild/android-arm64": 0.17.15 + "@esbuild/android-x64": 0.17.15 + "@esbuild/darwin-arm64": 0.17.15 + "@esbuild/darwin-x64": 0.17.15 + "@esbuild/freebsd-arm64": 0.17.15 + "@esbuild/freebsd-x64": 0.17.15 + "@esbuild/linux-arm": 0.17.15 + "@esbuild/linux-arm64": 0.17.15 + "@esbuild/linux-ia32": 0.17.15 + "@esbuild/linux-loong64": 0.17.15 + "@esbuild/linux-mips64el": 0.17.15 + "@esbuild/linux-ppc64": 0.17.15 + "@esbuild/linux-riscv64": 0.17.15 + "@esbuild/linux-s390x": 0.17.15 + "@esbuild/linux-x64": 0.17.15 + "@esbuild/netbsd-x64": 0.17.15 + "@esbuild/openbsd-x64": 0.17.15 + "@esbuild/sunos-x64": 0.17.15 + "@esbuild/win32-arm64": 0.17.15 + "@esbuild/win32-ia32": 0.17.15 + "@esbuild/win32-x64": 0.17.15 dependenciesMeta: "@esbuild/android-arm": optional: true @@ -1795,7 +1795,7 @@ __metadata: optional: true bin: esbuild: bin/esbuild - checksum: 4c2cc609ecfb426554bc3f75beb92d89eb2d0c515cfceebaa36c7599d7dcaab7056b70f6d6b51e72b45951ddf9021ee28e356cf205f8e42cc055d522312ea30c + checksum: 4e3640d7bc8f6edb3465c076eb519ccb7684382714a1b883e000a7a592f8e285501ec7e82cb68441dfec8f7be7f70f40b00129ceb05057f6fa87f95d2187370a languageName: node linkType: hard @@ -3662,9 +3662,9 @@ __metadata: languageName: node linkType: hard -"rollup@npm:^3.10.0": - version: 3.19.1 - resolution: "rollup@npm:3.19.1" +"rollup@npm:^3.18.0": + version: 3.20.2 + resolution: "rollup@npm:3.20.2" dependencies: fsevents: ~2.3.2 dependenciesMeta: @@ -3672,7 +3672,7 @@ __metadata: optional: true bin: rollup: dist/bin/rollup - checksum: f78198c6de224b26650c70b16db156762d1fcceeb375d34fb2c76fc5b23a78f712c3c881d3248e6f277a511589e20d50c247bcf5c7920f1ddc0a43cadf9f0140 + checksum: 34b0932839b7c2a5d1742fb21ce95a47e0b49a0849f4abee2dccf25833187aa7babb898ca90d4fc761cffa4102b9ed0ac6ad7f6f6b96c8b8e2d67305abc5da65 languageName: node linkType: hard @@ -3698,7 +3698,7 @@ __metadata: react-redux: ^8.0.5 serve: ^14.2.0 typescript: ^4.9.4 - vite: ^4.0.0 + vite: ^4.2.1 languageName: unknown linkType: soft @@ -4216,15 +4216,15 @@ __metadata: languageName: node linkType: hard -"vite@npm:^4.0.0": - version: 4.1.4 - resolution: "vite@npm:4.1.4" +"vite@npm:^4.2.1": + version: 4.2.1 + resolution: "vite@npm:4.2.1" dependencies: - esbuild: ^0.16.14 + esbuild: ^0.17.5 fsevents: ~2.3.2 postcss: ^8.4.21 resolve: ^1.22.1 - rollup: ^3.10.0 + rollup: ^3.18.0 peerDependencies: "@types/node": ">= 14" less: "*" @@ -4250,7 +4250,7 @@ __metadata: optional: true bin: vite: bin/vite.js - checksum: 50a9a1f2e29e0ee8fefdec60314d38fb9b746df0bb6ae5a8114014b5bfd95e0fc9b29c0d5e73939361ba53af7eb66c7d20c5656bbe53a783e96540bd3b907c47 + checksum: 70eb162ffc299017a3c310e3adc95e9661def6b17aafd1f8e5e02e516766060435590dbe3df1e4e95acc3583c728a76e91f07c546221d1e701f1b2b021293f45 languageName: node linkType: hard From d96ce6e3610e6b42fbd618f8c5de1cec1626b6e7 Mon Sep 17 00:00:00 2001 From: Mark Erikson Date: Mon, 3 Apr 2023 13:22:20 -0400 Subject: [PATCH 078/412] Release 2.0.0-alpha.4 --- packages/toolkit/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/toolkit/package.json b/packages/toolkit/package.json index e359efff46..bc46cc6fc9 100644 --- a/packages/toolkit/package.json +++ b/packages/toolkit/package.json @@ -1,6 +1,6 @@ { "name": "@reduxjs/toolkit", - "version": "2.0.0-alpha.3", + "version": "2.0.0-alpha.4", "description": "The official, opinionated, batteries-included toolset for efficient Redux development", "author": "Mark Erikson ", "license": "MIT", From bd5d4ff2bd6de4bb9032f9c415ef2919e158b332 Mon Sep 17 00:00:00 2001 From: "ben.durrant" Date: Thu, 6 Apr 2023 17:43:35 +0100 Subject: [PATCH 079/412] begin experimenting with slice selectors --- packages/toolkit/src/createSlice.ts | 52 ++++++++++++++++--- .../toolkit/src/tests/createSlice.typetest.ts | 40 ++++++++++++++ 2 files changed, 86 insertions(+), 6 deletions(-) diff --git a/packages/toolkit/src/createSlice.ts b/packages/toolkit/src/createSlice.ts index b9d3fd0d0f..8fc8eacbd3 100644 --- a/packages/toolkit/src/createSlice.ts +++ b/packages/toolkit/src/createSlice.ts @@ -1,5 +1,5 @@ import type { AnyAction, Reducer } from 'redux' -import { createNextState } from '.' +import { createNextState, createSelector } from '.' import type { ActionCreatorWithoutPayload, PayloadAction, @@ -38,7 +38,8 @@ export type SliceActionCreator

= PayloadActionCreator

export interface Slice< State = any, CaseReducers extends SliceCaseReducers = SliceCaseReducers, - Name extends string = string + Name extends string = string, + Selectors extends SliceSelectors = {} > { /** * The slice name. @@ -67,6 +68,23 @@ export interface Slice< * If a lazy state initializer was provided, it will be called and a fresh value returned. */ getInitialState: () => State + + getSelectors(): SliceDefinedSelectors + + getSelectors( + selectState: (rootState: RootState) => State + ): SliceDefinedSelectors + + selectors: SliceDefinedSelectors +} + +type SliceDefinedSelectors< + State, + Name extends string, + Selectors extends SliceSelectors, + RootState = { [K in Name]: State } +> = { + [K in keyof Selectors]: (rootState: RootState) => ReturnType } /** @@ -77,7 +95,8 @@ export interface Slice< export interface CreateSliceOptions< State = any, CR extends SliceCaseReducers = SliceCaseReducers, - Name extends string = string + Name extends string = string, + Selectors extends SliceSelectors = Record > { /** * The slice's name. Used to namespace the generated action types. @@ -142,6 +161,8 @@ createSlice({ ``` */ extraReducers?: (builder: ActionReducerMapBuilder>) => void + + selectors?: Selectors } /** @@ -165,6 +186,10 @@ export type SliceCaseReducers = { | CaseReducerWithPrepare> } +export type SliceSelectors = { + [K: string]: (sliceState: State, ...args: never[]) => any +} + type SliceActionType< SliceName extends string, ActionName extends keyof any @@ -271,10 +296,11 @@ function getType(slice: string, actionKey: string): string { export function createSlice< State, CaseReducers extends SliceCaseReducers, - Name extends string = string + Name extends string, + Selectors extends SliceSelectors >( - options: CreateSliceOptions -): Slice { + options: CreateSliceOptions +): Slice { const { name } = options if (!name) { throw new Error('`name` is a required option for createSlice') @@ -373,5 +399,19 @@ export function createSlice< return _reducer.getInitialState() }, + getSelectors: ( + selectState: (rootState: any) => State = ( + rootState: { [K in Name]: State } + ) => rootState[name] + ) => { + const selectors: Record any> = {} + for (const [name, selector] of Object.entries(options.selectors ?? {})) { + selectors[name] = (rootState: any) => selector(selectState(rootState)) + } + return selectors as any + }, + get selectors() { + return this.getSelectors() + }, } } diff --git a/packages/toolkit/src/tests/createSlice.typetest.ts b/packages/toolkit/src/tests/createSlice.typetest.ts index 64c33078c5..16de8bd197 100644 --- a/packages/toolkit/src/tests/createSlice.typetest.ts +++ b/packages/toolkit/src/tests/createSlice.typetest.ts @@ -499,3 +499,43 @@ const value = actionCreators.anyKey return { doNothing, setData, slice } } } + +{ + const sliceWithSelectors = createSlice({ + name: 'counter', + initialState: { value: 0 }, + reducers: { + increment: (state) => { + state.value += 1 + }, + }, + selectors: { + selectValue: (state) => state.value, + selectDouble: (state) => state.value * 2, + selectToFixed: (state) => state.value.toFixed(2), + }, + }) + + const rootState = { + [sliceWithSelectors.name]: sliceWithSelectors.getInitialState(), + } + + const { selectValue, selectDouble, selectToFixed } = + sliceWithSelectors.selectors + + expectType(selectValue(rootState)) + expectType(selectDouble(rootState)) + expectType(selectToFixed(rootState)) + + const nestedState = { + nested: rootState, + } + + const nestedSelectors = sliceWithSelectors.getSelectors( + (rootState: typeof nestedState) => rootState.nested.counter + ) + + expectType(nestedSelectors.selectValue(nestedState)) + expectType(nestedSelectors.selectDouble(nestedState)) + expectType(nestedSelectors.selectToFixed(nestedState)) +} From c36c9dcae1711363cf2183e0ead2e0dba95e0246 Mon Sep 17 00:00:00 2001 From: "ben.durrant" Date: Thu, 6 Apr 2023 18:07:57 +0100 Subject: [PATCH 080/412] tests --- packages/toolkit/src/createSlice.ts | 8 +++-- .../toolkit/src/tests/createSlice.test.ts | 29 +++++++++++++++++++ 2 files changed, 34 insertions(+), 3 deletions(-) diff --git a/packages/toolkit/src/createSlice.ts b/packages/toolkit/src/createSlice.ts index 8fc8eacbd3..dcd52d84e1 100644 --- a/packages/toolkit/src/createSlice.ts +++ b/packages/toolkit/src/createSlice.ts @@ -383,6 +383,9 @@ export function createSlice< }) } + const defaultSelectSlice = (rootState: { [K in Name]: State }) => + rootState[name] + let _reducer: ReducerWithInitialState return { @@ -400,9 +403,7 @@ export function createSlice< return _reducer.getInitialState() }, getSelectors: ( - selectState: (rootState: any) => State = ( - rootState: { [K in Name]: State } - ) => rootState[name] + selectState: (rootState: any) => State = defaultSelectSlice ) => { const selectors: Record any> = {} for (const [name, selector] of Object.entries(options.selectors ?? {})) { @@ -411,6 +412,7 @@ export function createSlice< return selectors as any }, get selectors() { + // TODO: do we want to cache this at all? return this.getSelectors() }, } diff --git a/packages/toolkit/src/tests/createSlice.test.ts b/packages/toolkit/src/tests/createSlice.test.ts index 38038dbefc..f8e50ec501 100644 --- a/packages/toolkit/src/tests/createSlice.test.ts +++ b/packages/toolkit/src/tests/createSlice.test.ts @@ -449,4 +449,33 @@ describe('createSlice', () => { ) }) }) + describe('slice selectors', () => { + const slice = createSlice({ + name: 'counter', + initialState: 42, + reducers: {}, + selectors: { + selectSlice: (state) => state, + selectDouble: (state) => state * 2, + }, + }) + it('expects reducer under slice.name if no selectState callback passed', () => { + const testState = { + [slice.name]: slice.getInitialState(), + } + const { selectSlice, selectDouble } = slice.selectors + expect(selectSlice(testState)).toBe(slice.getInitialState()) + expect(selectDouble(testState)).toBe(slice.getInitialState() * 2) + }) + it('allows passing a selector for a custom location', () => { + const customState = { + number: slice.getInitialState(), + } + const { selectSlice, selectDouble } = slice.getSelectors( + (state: typeof customState) => state.number + ) + expect(selectSlice(customState)).toBe(slice.getInitialState()) + expect(selectDouble(customState)).toBe(slice.getInitialState() * 2) + }) + }) }) From d1ae818e9fc8ee62548f68496c0bec75826f2a4e Mon Sep 17 00:00:00 2001 From: "ben.durrant" Date: Thu, 6 Apr 2023 18:15:18 +0100 Subject: [PATCH 081/412] default to ID function for getSelectors without selectState --- packages/toolkit/src/createSlice.ts | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/packages/toolkit/src/createSlice.ts b/packages/toolkit/src/createSlice.ts index dcd52d84e1..e03dad966e 100644 --- a/packages/toolkit/src/createSlice.ts +++ b/packages/toolkit/src/createSlice.ts @@ -69,7 +69,7 @@ export interface Slice< */ getInitialState: () => State - getSelectors(): SliceDefinedSelectors + getSelectors(): SliceDefinedSelectors getSelectors( selectState: (rootState: RootState) => State @@ -402,18 +402,22 @@ export function createSlice< return _reducer.getInitialState() }, - getSelectors: ( - selectState: (rootState: any) => State = defaultSelectSlice - ) => { - const selectors: Record any> = {} - for (const [name, selector] of Object.entries(options.selectors ?? {})) { - selectors[name] = (rootState: any) => selector(selectState(rootState)) + getSelectors: (selectState?: (rootState: any) => State) => { + if (selectState) { + const selectors: Record any> = {} + for (const [name, selector] of Object.entries( + options.selectors ?? {} + )) { + selectors[name] = (rootState: any) => selector(selectState(rootState)) + } + return selectors as any + } else { + return options.selectors ?? {} } - return selectors as any }, get selectors() { // TODO: do we want to cache this at all? - return this.getSelectors() + return this.getSelectors(defaultSelectSlice) }, } } From deecec7148a802969112aeba4032e3a5a812f684 Mon Sep 17 00:00:00 2001 From: "ben.durrant" Date: Thu, 6 Apr 2023 18:21:11 +0100 Subject: [PATCH 082/412] cache selectors --- packages/toolkit/src/createSlice.ts | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/packages/toolkit/src/createSlice.ts b/packages/toolkit/src/createSlice.ts index e03dad966e..34c65931e2 100644 --- a/packages/toolkit/src/createSlice.ts +++ b/packages/toolkit/src/createSlice.ts @@ -386,6 +386,11 @@ export function createSlice< const defaultSelectSlice = (rootState: { [K in Name]: State }) => rootState[name] + const selectorCache = new WeakMap< + (rootState: any) => State, + Record any> + >() + let _reducer: ReducerWithInitialState return { @@ -404,12 +409,17 @@ export function createSlice< }, getSelectors: (selectState?: (rootState: any) => State) => { if (selectState) { + const cached = selectorCache.get(selectState) + if (cached) { + return cached + } const selectors: Record any> = {} for (const [name, selector] of Object.entries( options.selectors ?? {} )) { selectors[name] = (rootState: any) => selector(selectState(rootState)) } + selectorCache.set(selectState, selectors) return selectors as any } else { return options.selectors ?? {} From 2731434e4df498b1b0f788f482e0939d7b705ea0 Mon Sep 17 00:00:00 2001 From: "ben.durrant" Date: Thu, 6 Apr 2023 18:21:55 +0100 Subject: [PATCH 083/412] rm TODO --- packages/toolkit/src/createSlice.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/toolkit/src/createSlice.ts b/packages/toolkit/src/createSlice.ts index 34c65931e2..ace1a236b1 100644 --- a/packages/toolkit/src/createSlice.ts +++ b/packages/toolkit/src/createSlice.ts @@ -426,7 +426,6 @@ export function createSlice< } }, get selectors() { - // TODO: do we want to cache this at all? return this.getSelectors(defaultSelectSlice) }, } From 792c944dfb583a3f688b9f812d13e2c40ef55894 Mon Sep 17 00:00:00 2001 From: "ben.durrant" Date: Thu, 6 Apr 2023 21:00:51 +0100 Subject: [PATCH 084/412] injectInto implementation --- packages/toolkit/src/combineSlices.ts | 2 +- packages/toolkit/src/createSlice.ts | 74 ++++++++++++++++++++++++--- 2 files changed, 69 insertions(+), 7 deletions(-) diff --git a/packages/toolkit/src/combineSlices.ts b/packages/toolkit/src/combineSlices.ts index aafdb4f614..9ebc00ce7b 100644 --- a/packages/toolkit/src/combineSlices.ts +++ b/packages/toolkit/src/combineSlices.ts @@ -89,7 +89,7 @@ type InjectConfig = { /** * A reducer that allows for slices/reducers to be injected after initialisation. */ -interface CombinedSliceReducer< +export interface CombinedSliceReducer< InitialState, DeclaredState = InitialState > extends Reducer< diff --git a/packages/toolkit/src/createSlice.ts b/packages/toolkit/src/createSlice.ts index ace1a236b1..bc6334a6dd 100644 --- a/packages/toolkit/src/createSlice.ts +++ b/packages/toolkit/src/createSlice.ts @@ -18,6 +18,7 @@ import type { ActionReducerMapBuilder } from './mapBuilders' import { executeReducerBuilderCallback } from './mapBuilders' import type { NoInfer } from './tsHelpers' import { freezeDraftable } from './utils' +import type { CombinedSliceReducer } from './combineSlices' let hasWarnedAboutObjectNotation = false @@ -69,20 +70,45 @@ export interface Slice< */ getInitialState: () => State - getSelectors(): SliceDefinedSelectors + getSelectors(): SliceDefinedSelectors getSelectors( selectState: (rootState: RootState) => State - ): SliceDefinedSelectors + ): SliceDefinedSelectors - selectors: SliceDefinedSelectors + selectors: SliceDefinedSelectors + + injectInto( + combinedReducer: CombinedSliceReducer + ): InjectedSlice +} + +interface InjectedSlice< + State = any, + CaseReducers extends SliceCaseReducers = SliceCaseReducers, + Name extends string = string, + Selectors extends SliceSelectors = {} +> extends Omit< + Slice, + 'getSelectors' | 'selectors' + > { + getSelectors(): SliceDefinedSelectors + + getSelectors( + selectState: (rootState: RootState) => State | undefined + ): SliceDefinedSelectors + + selectors: SliceDefinedSelectors< + State, + Selectors, + { [K in Name]?: State | undefined } + > } type SliceDefinedSelectors< State, - Name extends string, Selectors extends SliceSelectors, - RootState = { [K in Name]: State } + RootState > = { [K in keyof Selectors]: (rootState: RootState) => ReturnType } @@ -386,11 +412,18 @@ export function createSlice< const defaultSelectSlice = (rootState: { [K in Name]: State }) => rootState[name] + const selectSelf = (state: State) => state + const selectorCache = new WeakMap< (rootState: any) => State, Record any> >() + const injectedSelectorCache = new WeakMap< + CombinedSliceReducer, + WeakMap<(rootState: any) => State, Record any>> + >() + let _reducer: ReducerWithInitialState return { @@ -407,7 +440,7 @@ export function createSlice< return _reducer.getInitialState() }, - getSelectors: (selectState?: (rootState: any) => State) => { + getSelectors(selectState?: (rootState: any) => State) { if (selectState) { const cached = selectorCache.get(selectState) if (cached) { @@ -428,5 +461,34 @@ export function createSlice< get selectors() { return this.getSelectors(defaultSelectSlice) }, + injectInto(reducer) { + reducer.inject(this) + let selectorCache = injectedSelectorCache.get(reducer) + if (!selectorCache) { + selectorCache = new WeakMap() + injectedSelectorCache.set(reducer, selectorCache) + } + return { + ...this, + getSelectors(selectState: (rootState: any) => State = selectSelf) { + const cached = selectorCache!.get(selectState) + if (cached) { + return cached + } + const selectors: Record any> = {} + for (const [name, selector] of Object.entries( + options.selectors ?? {} + )) { + selectors[name] = (rootState: any) => + selector(selectState(rootState) ?? this.getInitialState()) + } + selectorCache!.set(selectState, selectors) + return selectors as any + }, + get selectors() { + return this.getSelectors(defaultSelectSlice) + }, + } as any + }, } } From 36503dc669c96c7c93f05e3a83b80e733dc859a3 Mon Sep 17 00:00:00 2001 From: "ben.durrant" Date: Thu, 6 Apr 2023 21:01:53 +0100 Subject: [PATCH 085/412] more accurate typing --- packages/toolkit/src/createSlice.ts | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/packages/toolkit/src/createSlice.ts b/packages/toolkit/src/createSlice.ts index bc6334a6dd..43a0d2ebd1 100644 --- a/packages/toolkit/src/createSlice.ts +++ b/packages/toolkit/src/createSlice.ts @@ -421,7 +421,10 @@ export function createSlice< const injectedSelectorCache = new WeakMap< CombinedSliceReducer, - WeakMap<(rootState: any) => State, Record any>> + WeakMap< + (rootState: any) => State | undefined, + Record any> + > >() let _reducer: ReducerWithInitialState @@ -470,7 +473,9 @@ export function createSlice< } return { ...this, - getSelectors(selectState: (rootState: any) => State = selectSelf) { + getSelectors( + selectState: (rootState: any) => State | undefined = selectSelf + ) { const cached = selectorCache!.get(selectState) if (cached) { return cached From 4e4a3a192fd42160bc64e3a13a89a854afb09739 Mon Sep 17 00:00:00 2001 From: "ben.durrant" Date: Thu, 6 Apr 2023 22:24:49 +0100 Subject: [PATCH 086/412] pass arguments through --- packages/toolkit/src/createSlice.ts | 20 ++++++++++++------- .../toolkit/src/tests/createSlice.typetest.ts | 8 ++++---- 2 files changed, 17 insertions(+), 11 deletions(-) diff --git a/packages/toolkit/src/createSlice.ts b/packages/toolkit/src/createSlice.ts index 43a0d2ebd1..17d4bd0619 100644 --- a/packages/toolkit/src/createSlice.ts +++ b/packages/toolkit/src/createSlice.ts @@ -1,5 +1,4 @@ import type { AnyAction, Reducer } from 'redux' -import { createNextState, createSelector } from '.' import type { ActionCreatorWithoutPayload, PayloadAction, @@ -16,7 +15,7 @@ import type { import { createReducer, NotFunction } from './createReducer' import type { ActionReducerMapBuilder } from './mapBuilders' import { executeReducerBuilderCallback } from './mapBuilders' -import type { NoInfer } from './tsHelpers' +import type { NoInfer, Tail } from './tsHelpers' import { freezeDraftable } from './utils' import type { CombinedSliceReducer } from './combineSlices' @@ -110,7 +109,10 @@ type SliceDefinedSelectors< Selectors extends SliceSelectors, RootState > = { - [K in keyof Selectors]: (rootState: RootState) => ReturnType + [K in keyof Selectors]: ( + rootState: RootState, + ...args: Tail> + ) => ReturnType } /** @@ -213,7 +215,7 @@ export type SliceCaseReducers = { } export type SliceSelectors = { - [K: string]: (sliceState: State, ...args: never[]) => any + [K: string]: (sliceState: State, ...args: any[]) => any } type SliceActionType< @@ -453,7 +455,8 @@ export function createSlice< for (const [name, selector] of Object.entries( options.selectors ?? {} )) { - selectors[name] = (rootState: any) => selector(selectState(rootState)) + selectors[name] = (rootState: any, ...args: any[]) => + selector(selectState(rootState), ...args) } selectorCache.set(selectState, selectors) return selectors as any @@ -484,8 +487,11 @@ export function createSlice< for (const [name, selector] of Object.entries( options.selectors ?? {} )) { - selectors[name] = (rootState: any) => - selector(selectState(rootState) ?? this.getInitialState()) + selectors[name] = (rootState: any, ...args: any[]) => + selector( + selectState(rootState) ?? this.getInitialState(), + ...args + ) } selectorCache!.set(selectState, selectors) return selectors as any diff --git a/packages/toolkit/src/tests/createSlice.typetest.ts b/packages/toolkit/src/tests/createSlice.typetest.ts index 16de8bd197..fe525e1c57 100644 --- a/packages/toolkit/src/tests/createSlice.typetest.ts +++ b/packages/toolkit/src/tests/createSlice.typetest.ts @@ -511,7 +511,7 @@ const value = actionCreators.anyKey }, selectors: { selectValue: (state) => state.value, - selectDouble: (state) => state.value * 2, + selectMultiply: (state, multiplier: number) => state.value * multiplier, selectToFixed: (state) => state.value.toFixed(2), }, }) @@ -520,11 +520,11 @@ const value = actionCreators.anyKey [sliceWithSelectors.name]: sliceWithSelectors.getInitialState(), } - const { selectValue, selectDouble, selectToFixed } = + const { selectValue, selectMultiply, selectToFixed } = sliceWithSelectors.selectors expectType(selectValue(rootState)) - expectType(selectDouble(rootState)) + expectType(selectMultiply(rootState, 2)) expectType(selectToFixed(rootState)) const nestedState = { @@ -536,6 +536,6 @@ const value = actionCreators.anyKey ) expectType(nestedSelectors.selectValue(nestedState)) - expectType(nestedSelectors.selectDouble(nestedState)) + expectType(nestedSelectors.selectMultiply(nestedState, 2)) expectType(nestedSelectors.selectToFixed(nestedState)) } From 32d19642d80a022ddee0a067401dc63242175131 Mon Sep 17 00:00:00 2001 From: "ben.durrant" Date: Thu, 6 Apr 2023 22:40:21 +0100 Subject: [PATCH 087/412] more tests --- .../toolkit/src/tests/createSlice.test.ts | 51 ++++++++++++++++--- 1 file changed, 45 insertions(+), 6 deletions(-) diff --git a/packages/toolkit/src/tests/createSlice.test.ts b/packages/toolkit/src/tests/createSlice.test.ts index f8e50ec501..56ec76e758 100644 --- a/packages/toolkit/src/tests/createSlice.test.ts +++ b/packages/toolkit/src/tests/createSlice.test.ts @@ -1,5 +1,6 @@ import { vi } from 'vitest' -import type { PayloadAction } from '@reduxjs/toolkit' +import type { PayloadAction, WithSlice } from '@reduxjs/toolkit' +import { combineSlices } from '@reduxjs/toolkit' import { createSlice, createAction } from '@reduxjs/toolkit' import { mockConsole, @@ -456,26 +457,64 @@ describe('createSlice', () => { reducers: {}, selectors: { selectSlice: (state) => state, - selectDouble: (state) => state * 2, + selectMultiple: (state, multiplier: number) => state * multiplier, }, }) it('expects reducer under slice.name if no selectState callback passed', () => { const testState = { [slice.name]: slice.getInitialState(), } - const { selectSlice, selectDouble } = slice.selectors + const { selectSlice, selectMultiple } = slice.selectors expect(selectSlice(testState)).toBe(slice.getInitialState()) - expect(selectDouble(testState)).toBe(slice.getInitialState() * 2) + expect(selectMultiple(testState, 2)).toBe(slice.getInitialState() * 2) }) it('allows passing a selector for a custom location', () => { const customState = { number: slice.getInitialState(), } - const { selectSlice, selectDouble } = slice.getSelectors( + const { selectSlice, selectMultiple } = slice.getSelectors( (state: typeof customState) => state.number ) expect(selectSlice(customState)).toBe(slice.getInitialState()) - expect(selectDouble(customState)).toBe(slice.getInitialState() * 2) + expect(selectMultiple(customState, 2)).toBe(slice.getInitialState() * 2) + }) + }) + describe('slice injections', () => { + it('uses injectInto to inject slice into combined reducer', () => { + const slice = createSlice({ + name: 'counter', + initialState: 42, + reducers: { + increment: (state) => ++state, + }, + selectors: { + selectSlice: (state) => state, + selectMultiple: (state, multiplier: number) => state * multiplier, + }, + }) + + const { increment } = slice.actions + + const combinedReducer = combineSlices({ + static: slice.reducer, + }).withLazyLoadedSlices>() + + const uninjectedState = combinedReducer(undefined, increment()) + + expect(uninjectedState.counter).toBe(undefined) + + const injectedSlice = slice.injectInto(combinedReducer) + + // selector returns initial state if undefined in real state + expect(injectedSlice.selectors.selectSlice(uninjectedState)).toBe( + slice.getInitialState() + ) + + const injectedState = combinedReducer(undefined, increment()) + + expect(injectedSlice.selectors.selectSlice(injectedState)).toBe( + slice.getInitialState() + 1 + ) }) }) }) From 34bf78bdada230f9f380cef6893168bc4c19adc4 Mon Sep 17 00:00:00 2001 From: "ben.durrant" Date: Thu, 6 Apr 2023 23:05:55 +0100 Subject: [PATCH 088/412] remove index signature from selectors generic --- packages/toolkit/src/createSlice.ts | 20 +++++++++---------- .../toolkit/src/tests/createSlice.typetest.ts | 13 ++++++++++++ 2 files changed, 22 insertions(+), 11 deletions(-) diff --git a/packages/toolkit/src/createSlice.ts b/packages/toolkit/src/createSlice.ts index 17d4bd0619..f024ce6907 100644 --- a/packages/toolkit/src/createSlice.ts +++ b/packages/toolkit/src/createSlice.ts @@ -15,7 +15,7 @@ import type { import { createReducer, NotFunction } from './createReducer' import type { ActionReducerMapBuilder } from './mapBuilders' import { executeReducerBuilderCallback } from './mapBuilders' -import type { NoInfer, Tail } from './tsHelpers' +import type { Id, NoInfer, Tail } from './tsHelpers' import { freezeDraftable } from './utils' import type { CombinedSliceReducer } from './combineSlices' @@ -69,13 +69,13 @@ export interface Slice< */ getInitialState: () => State - getSelectors(): SliceDefinedSelectors + getSelectors(): Id> getSelectors( selectState: (rootState: RootState) => State - ): SliceDefinedSelectors + ): Id> - selectors: SliceDefinedSelectors + selectors: Id> injectInto( combinedReducer: CombinedSliceReducer @@ -91,16 +91,14 @@ interface InjectedSlice< Slice, 'getSelectors' | 'selectors' > { - getSelectors(): SliceDefinedSelectors + getSelectors(): Id> getSelectors( selectState: (rootState: RootState) => State | undefined - ): SliceDefinedSelectors + ): Id> - selectors: SliceDefinedSelectors< - State, - Selectors, - { [K in Name]?: State | undefined } + selectors: Id< + SliceDefinedSelectors > } @@ -109,7 +107,7 @@ type SliceDefinedSelectors< Selectors extends SliceSelectors, RootState > = { - [K in keyof Selectors]: ( + [K in keyof Selectors as [string] extends [K] ? never : K]: ( rootState: RootState, ...args: Tail> ) => ReturnType diff --git a/packages/toolkit/src/tests/createSlice.typetest.ts b/packages/toolkit/src/tests/createSlice.typetest.ts index fe525e1c57..71842d51d7 100644 --- a/packages/toolkit/src/tests/createSlice.typetest.ts +++ b/packages/toolkit/src/tests/createSlice.typetest.ts @@ -500,7 +500,20 @@ const value = actionCreators.anyKey } } +/** + * Test: slice selectors + */ + { + const sliceWithoutSelectors = createSlice({ + name: '', + initialState: '', + reducers: {}, + }) + + // @ts-expect-error + sliceWithoutSelectors.selectors.foo + const sliceWithSelectors = createSlice({ name: 'counter', initialState: { value: 0 }, From 6816c1a437307d779d1fb590d3a1960458066cfd Mon Sep 17 00:00:00 2001 From: "ben.durrant" Date: Fri, 7 Apr 2023 01:37:59 +0100 Subject: [PATCH 089/412] remove constraint on lazy --- packages/toolkit/src/combineSlices.ts | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/packages/toolkit/src/combineSlices.ts b/packages/toolkit/src/combineSlices.ts index 9ebc00ce7b..9043b39279 100644 --- a/packages/toolkit/src/combineSlices.ts +++ b/packages/toolkit/src/combineSlices.ts @@ -126,9 +126,10 @@ export interface CombinedSliceReducer< * const withCustom = rootReducer.inject({ name: "customName", reducer: customSlice.reducer }) * ``` */ - withLazyLoadedSlices< - Lazy extends Record = {} - >(): CombinedSliceReducer>> + withLazyLoadedSlices(): CombinedSliceReducer< + InitialState, + Id> + > /** * Inject an RTKQ API instance. From d59b26a8e47a7aebf1d17f654c2d12a29f57bd8d Mon Sep 17 00:00:00 2001 From: "ben.durrant" Date: Fri, 7 Apr 2023 01:51:06 +0100 Subject: [PATCH 090/412] add optional name parameter for injectInto --- packages/toolkit/src/createSlice.ts | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/packages/toolkit/src/createSlice.ts b/packages/toolkit/src/createSlice.ts index f024ce6907..9a4ea93f51 100644 --- a/packages/toolkit/src/createSlice.ts +++ b/packages/toolkit/src/createSlice.ts @@ -78,7 +78,8 @@ export interface Slice< selectors: Id> injectInto( - combinedReducer: CombinedSliceReducer + combinedReducer: CombinedSliceReducer, + name?: string ): InjectedSlice } @@ -465,8 +466,8 @@ export function createSlice< get selectors() { return this.getSelectors(defaultSelectSlice) }, - injectInto(reducer) { - reducer.inject(this) + injectInto(reducer, name = options.name) { + reducer.inject({ name, reducer: this.reducer }) let selectorCache = injectedSelectorCache.get(reducer) if (!selectorCache) { selectorCache = new WeakMap() From a285bf8d6d5efcf4c7d889a1e43bfa7680b46583 Mon Sep 17 00:00:00 2001 From: "ben.durrant" Date: Fri, 7 Apr 2023 02:23:45 +0100 Subject: [PATCH 091/412] adjust slice option --- packages/toolkit/src/createSlice.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/toolkit/src/createSlice.ts b/packages/toolkit/src/createSlice.ts index 9a4ea93f51..f4f8647399 100644 --- a/packages/toolkit/src/createSlice.ts +++ b/packages/toolkit/src/createSlice.ts @@ -466,8 +466,8 @@ export function createSlice< get selectors() { return this.getSelectors(defaultSelectSlice) }, - injectInto(reducer, name = options.name) { - reducer.inject({ name, reducer: this.reducer }) + injectInto(reducer, name) { + reducer.inject({ ...this, ...(name && { name }) }) let selectorCache = injectedSelectorCache.get(reducer) if (!selectorCache) { selectorCache = new WeakMap() From af875ae36f72460ed7d58c73a3dacf5d0e5d08a3 Mon Sep 17 00:00:00 2001 From: "ben.durrant" Date: Fri, 7 Apr 2023 02:42:12 +0100 Subject: [PATCH 092/412] add inject config to injectInto --- packages/toolkit/src/combineSlices.ts | 2 +- packages/toolkit/src/createSlice.ts | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/toolkit/src/combineSlices.ts b/packages/toolkit/src/combineSlices.ts index 9043b39279..0c211c37cd 100644 --- a/packages/toolkit/src/combineSlices.ts +++ b/packages/toolkit/src/combineSlices.ts @@ -79,7 +79,7 @@ type ExistingApiLike = { > }[keyof DeclaredState] -type InjectConfig = { +export type InjectConfig = { /** * Allow replacing reducer with a different reference. Normally, an error will be thrown if a different reducer instance to the one already injected is used. */ diff --git a/packages/toolkit/src/createSlice.ts b/packages/toolkit/src/createSlice.ts index f4f8647399..da2df71f95 100644 --- a/packages/toolkit/src/createSlice.ts +++ b/packages/toolkit/src/createSlice.ts @@ -17,7 +17,7 @@ import type { ActionReducerMapBuilder } from './mapBuilders' import { executeReducerBuilderCallback } from './mapBuilders' import type { Id, NoInfer, Tail } from './tsHelpers' import { freezeDraftable } from './utils' -import type { CombinedSliceReducer } from './combineSlices' +import type { CombinedSliceReducer, InjectConfig } from './combineSlices' let hasWarnedAboutObjectNotation = false @@ -79,7 +79,7 @@ export interface Slice< injectInto( combinedReducer: CombinedSliceReducer, - name?: string + config?: InjectConfig & { name?: string } ): InjectedSlice } @@ -466,8 +466,8 @@ export function createSlice< get selectors() { return this.getSelectors(defaultSelectSlice) }, - injectInto(reducer, name) { - reducer.inject({ ...this, ...(name && { name }) }) + injectInto(reducer, { name, ...config } = {}) { + reducer.inject({ ...this, ...(name && { name }) }, config) let selectorCache = injectedSelectorCache.get(reducer) if (!selectorCache) { selectorCache = new WeakMap() From e3e8c2d53593f558585c9bf724244d2d6e1b8fe8 Mon Sep 17 00:00:00 2001 From: "ben.durrant" Date: Fri, 7 Apr 2023 11:33:49 +0100 Subject: [PATCH 093/412] JSDoc --- packages/toolkit/src/createSlice.ts | 69 +++++++++++++++++++++++------ 1 file changed, 55 insertions(+), 14 deletions(-) diff --git a/packages/toolkit/src/createSlice.ts b/packages/toolkit/src/createSlice.ts index da2df71f95..94048dd1f2 100644 --- a/packages/toolkit/src/createSlice.ts +++ b/packages/toolkit/src/createSlice.ts @@ -39,7 +39,7 @@ export interface Slice< State = any, CaseReducers extends SliceCaseReducers = SliceCaseReducers, Name extends string = string, - Selectors extends SliceSelectors = {} + Selectors extends SliceSelectors = SliceSelectors > { /** * The slice name. @@ -69,51 +69,70 @@ export interface Slice< */ getInitialState: () => State + /** + * Get localised slice selectors (expects to be called with *just* the slice's state as the first parameter) + */ getSelectors(): Id> + /** + * Get globalised slice selectors (`selectState` callback is expected to receive first parameter and return slice state) + */ getSelectors( selectState: (rootState: RootState) => State ): Id> + /** + * Selectors that assume the slice's state is `rootState[slice.name]` (which is usually the case) + * + * Equivalent to `slice.getSelectors((state: RootState) => state[slice.name])`. + */ selectors: Id> + /** + * Inject slice into provided reducer (return value from `combineSlices`), and return injected slice. + */ injectInto( combinedReducer: CombinedSliceReducer, config?: InjectConfig & { name?: string } ): InjectedSlice } +/** + * A slice after being called with `injectInto(reducer)`. + * + * Selectors can now be called with an `undefined` value, in which case they use the slice's initial state. + */ interface InjectedSlice< State = any, CaseReducers extends SliceCaseReducers = SliceCaseReducers, Name extends string = string, - Selectors extends SliceSelectors = {} + Selectors extends SliceSelectors = SliceSelectors > extends Omit< Slice, 'getSelectors' | 'selectors' > { + /** + * Get localised slice selectors (expects to be called with *just* the slice's state as the first parameter) + */ getSelectors(): Id> + /** + * Get globalised slice selectors (`selectState` callback is expected to receive first parameter and return slice state) + */ getSelectors( selectState: (rootState: RootState) => State | undefined ): Id> + /** + * Selectors that assume the slice's state is `rootState[slice.name]` (which is usually the case) + * + * Equivalent to `slice.getSelectors((state: RootState) => state[slice.name])`. + */ selectors: Id< SliceDefinedSelectors > } -type SliceDefinedSelectors< - State, - Selectors extends SliceSelectors, - RootState -> = { - [K in keyof Selectors as [string] extends [K] ? never : K]: ( - rootState: RootState, - ...args: Tail> - ) => ReturnType -} - /** * Options for `createSlice()`. * @@ -123,7 +142,7 @@ export interface CreateSliceOptions< State = any, CR extends SliceCaseReducers = SliceCaseReducers, Name extends string = string, - Selectors extends SliceSelectors = Record + Selectors extends SliceSelectors = SliceSelectors > { /** * The slice's name. Used to namespace the generated action types. @@ -189,6 +208,9 @@ createSlice({ */ extraReducers?: (builder: ActionReducerMapBuilder>) => void + /** + * A map of selectors that receive the slice's state and any additional arguments, and return a result. + */ selectors?: Selectors } @@ -213,6 +235,9 @@ export type SliceCaseReducers = { | CaseReducerWithPrepare> } +/** + * The type describing a slice's `selectors` option. + */ export type SliceSelectors = { [K: string]: (sliceState: State, ...args: any[]) => any } @@ -280,6 +305,22 @@ type SliceDefinedCaseReducers> = { : CaseReducers[Type] } +/** + * Extracts the final selector type from the `selectors` object. + * + * Removes the `string` index signature from the default value. + */ +type SliceDefinedSelectors< + State, + Selectors extends SliceSelectors, + RootState +> = { + [K in keyof Selectors as string extends K ? never : K]: ( + rootState: RootState, + ...args: Tail> + ) => ReturnType +} + /** * Used on a SliceCaseReducers object. * Ensures that if a CaseReducer is a `CaseReducerWithPrepare`, that From ef07e128a5b67ed355aac5164dc9f037a71bf362 Mon Sep 17 00:00:00 2001 From: "ben.durrant" Date: Fri, 7 Apr 2023 11:38:52 +0100 Subject: [PATCH 094/412] custom name test --- .../toolkit/src/tests/createSlice.test.ts | 29 +++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/packages/toolkit/src/tests/createSlice.test.ts b/packages/toolkit/src/tests/createSlice.test.ts index 56ec76e758..91d333285a 100644 --- a/packages/toolkit/src/tests/createSlice.test.ts +++ b/packages/toolkit/src/tests/createSlice.test.ts @@ -516,5 +516,34 @@ describe('createSlice', () => { slice.getInitialState() + 1 ) }) + it('allows providing a custom name to inject under', () => { + const slice = createSlice({ + name: 'counter', + initialState: 42, + reducers: { + increment: (state) => ++state, + }, + selectors: { + selectSlice: (state) => state, + selectMultiple: (state, multiplier: number) => state * multiplier, + }, + }) + + const { increment } = slice.actions + + const combinedReducer = combineSlices({ + static: slice.reducer, + }).withLazyLoadedSlices<{ injected: number }>() + + const uninjectedState = combinedReducer(undefined, increment()) + + expect(uninjectedState.injected).toBe(undefined) + + slice.injectInto(combinedReducer, { name: 'injected' }) + + const injectedState = combinedReducer(undefined, increment()) + + expect(injectedState.injected).toBe(slice.getInitialState() + 1) + }) }) }) From 37ab2721343c2323b1be618cea4058b9439bb705 Mon Sep 17 00:00:00 2001 From: "ben.durrant" Date: Fri, 7 Apr 2023 13:29:55 +0100 Subject: [PATCH 095/412] add reducerPath option to slice --- packages/toolkit/src/combineSlices.ts | 170 +++++------------- packages/toolkit/src/createSlice.ts | 55 ++++-- .../src/tests/combineSlices.typetest.ts | 8 +- .../toolkit/src/tests/createSlice.test.ts | 3 +- .../toolkit/src/tests/createSlice.typetest.ts | 2 +- 5 files changed, 90 insertions(+), 148 deletions(-) diff --git a/packages/toolkit/src/combineSlices.ts b/packages/toolkit/src/combineSlices.ts index 0c211c37cd..ba76a504ac 100644 --- a/packages/toolkit/src/combineSlices.ts +++ b/packages/toolkit/src/combineSlices.ts @@ -14,68 +14,37 @@ import type { WithOptionalProp, } from './tsHelpers' -type SliceLike = { - name: Name - reducer: Reducer -} - -type AnySliceLike = SliceLike - -type SliceLikeState = Sl extends SliceLike< - any, - infer State -> - ? State - : never - -type SliceLikeName = Sl extends SliceLike< - infer Name, - any -> - ? Name - : never - -export type WithSlice = Id< - { - [K in SliceLikeName]: SliceLikeState - } -> - -type ApiLike = { +type SliceLike = { reducerPath: ReducerPath reducer: Reducer } -type AnyApiLike = ApiLike +type AnySliceLike = SliceLike -type ApiLikeReducerPath = A extends ApiLike< +type SliceLikeReducerPath = A extends SliceLike< infer ReducerPath, any > ? ReducerPath : never -type ApiLikeState = A extends ApiLike +type SliceLikeState = A extends SliceLike< + any, + infer State +> ? State : never -export type WithApi = { - [Path in ApiLikeReducerPath]: ApiLikeState +export type WithSlice = { + [Path in SliceLikeReducerPath]: SliceLikeState } type ReducerMap = Record type ExistingSliceLike = { - [Name in keyof DeclaredState]: SliceLike< - Name & string, - NonUndefined - > -}[keyof DeclaredState] - -type ExistingApiLike = { - [Name in keyof DeclaredState]: ApiLike< - Name & string, - NonUndefined + [ReducerPath in keyof DeclaredState]: SliceLike< + ReducerPath & string, + NonUndefined > }[keyof DeclaredState] @@ -123,7 +92,7 @@ export interface CombinedSliceReducer< * } * } * - * const withCustom = rootReducer.inject({ name: "customName", reducer: customSlice.reducer }) + * const withCustom = rootReducer.inject({ reducerPath: "customName", reducer: customSlice.reducer }) * ``` */ withLazyLoadedSlices(): CombinedSliceReducer< @@ -131,50 +100,15 @@ export interface CombinedSliceReducer< Id> > - /** - * Inject an RTKQ API instance. - * - * Accepts an individual instance, or an "api-like" { reducerPath, reducer } object - * - * ```ts - * rootReducer.inject(baseApi) - * ``` - * - */ - inject>( - api: A, - config?: InjectConfig - ): CombinedSliceReducer>> - - /** - * Inject an RTKQ API instance. - * - * Accepts an individual instance, or an "api-like" { reducerPath, reducer } object - * - * ```ts - * rootReducer.inject(baseApi) - * ``` - * - */ - inject( - api: ApiLike< - ReducerPath, - State & (ReducerPath extends keyof DeclaredState ? never : State) - >, - config?: InjectConfig - ): CombinedSliceReducer< - InitialState, - Id>> - > - /** * Inject a slice. * - * Accepts an individual slice, or a "slice-like" { name, reducer } object. + * Accepts an individual slice, RTKQ API instance, or a "slice-like" { reducerPath, reducer } object. * * ```ts * rootReducer.inject(booleanSlice) - * rootReducer.inject({ name: 'boolean' as const, reducer: newReducer }, { overrideExisting: true }) + * rootReducer.inject(baseApi) + * rootReducer.inject({ reducerPath: 'boolean' as const, reducer: newReducer }, { overrideExisting: true }) * ``` * */ @@ -186,23 +120,24 @@ export interface CombinedSliceReducer< /** * Inject a slice. * - * Accepts an individual slice, or a "slice-like" { name, reducer } object. + * Accepts an individual slice, RTKQ API instance, or a "slice-like" { reducerPath, reducer } object. * * ```ts * rootReducer.inject(booleanSlice) - * rootReducer.inject({ name: 'boolean' as const, reducer: newReducer }, { overrideExisting: true }) + * rootReducer.inject(baseApi) + * rootReducer.inject({ reducerPath: 'boolean' as const, reducer: newReducer }, { overrideExisting: true }) * ``` * */ - inject( + inject( slice: SliceLike< - Name, - State & (Name extends keyof DeclaredState ? never : State) + ReducerPath, + State & (ReducerPath extends keyof DeclaredState ? never : State) >, config?: InjectConfig ): CombinedSliceReducer< InitialState, - Id>> + Id>> > /** @@ -373,33 +308,24 @@ export interface CombinedSliceReducer< } } -type InitialState< - Slices extends Array -> = UnionToIntersection< - Slices[number] extends infer Slice - ? Slice extends AnySliceLike - ? WithSlice - : Slice extends AnyApiLike - ? WithApi - : StateFromReducersMapObject - : never -> +type InitialState> = + UnionToIntersection< + Slices[number] extends infer Slice + ? Slice extends AnySliceLike + ? WithSlice + : StateFromReducersMapObject + : never + > const isSliceLike = ( - maybeSliceLike: AnySliceLike | AnyApiLike | ReducerMap + maybeSliceLike: AnySliceLike | ReducerMap ): maybeSliceLike is AnySliceLike => - 'name' in maybeSliceLike && typeof maybeSliceLike.name === 'string' + 'reducerPath' in maybeSliceLike && + typeof maybeSliceLike.reducerPath === 'string' -const isApiLike = ( - maybeApiLike: AnySliceLike | AnyApiLike | ReducerMap -): maybeApiLike is AnyApiLike => - 'reducerPath' in maybeApiLike && typeof maybeApiLike.reducerPath === 'string' - -const getReducers = (slices: Array) => +const getReducers = (slices: Array) => slices.flatMap((sliceOrMap) => isSliceLike(sliceOrMap) - ? [[sliceOrMap.name, sliceOrMap.reducer] as const] - : isApiLike(sliceOrMap) ? [[sliceOrMap.reducerPath, sliceOrMap.reducer] as const] : Object.entries(sliceOrMap) ) @@ -452,9 +378,9 @@ const original = (state: any) => { return state[ORIGINAL_STATE] } -export function combineSlices< - Slices extends Array ->(...slices: Slices): CombinedSliceReducer>> { +export function combineSlices>( + ...slices: Slices +): CombinedSliceReducer>> { const reducerMap = Object.fromEntries(getReducers(slices)) const getReducer = () => combineReducers(reducerMap) @@ -468,22 +394,12 @@ export function combineSlices< combinedReducer.withLazyLoadedSlices = () => combinedReducer const inject = ( - slice: AnySliceLike | AnyApiLike, + slice: AnySliceLike, config: InjectConfig = {} ): typeof combinedReducer => { - if (isApiLike(slice)) { - return inject( - { - name: slice.reducerPath, - reducer: slice.reducer, - }, - config - ) - } - - const { name, reducer: reducerToInject } = slice + const { reducerPath, reducer: reducerToInject } = slice - const currentReducer = reducerMap[name] + const currentReducer = reducerMap[reducerPath] if ( !config.overrideExisting && currentReducer && @@ -494,14 +410,14 @@ export function combineSlices< process.env.NODE_ENV === 'development' ) { console.error( - `called \`inject\` to override already-existing reducer ${name} without specifying \`overrideExisting: true\`` + `called \`inject\` to override already-existing reducer ${reducerPath} without specifying \`overrideExisting: true\`` ) } return combinedReducer } - reducerMap[name] = reducerToInject + reducerMap[reducerPath] = reducerToInject reducer = getReducer() diff --git a/packages/toolkit/src/createSlice.ts b/packages/toolkit/src/createSlice.ts index 94048dd1f2..af8990df62 100644 --- a/packages/toolkit/src/createSlice.ts +++ b/packages/toolkit/src/createSlice.ts @@ -39,6 +39,7 @@ export interface Slice< State = any, CaseReducers extends SliceCaseReducers = SliceCaseReducers, Name extends string = string, + ReducerPath extends string = Name, Selectors extends SliceSelectors = SliceSelectors > { /** @@ -46,6 +47,11 @@ export interface Slice< */ name: Name + /** + * The slice reducer path. + */ + reducerPath: ReducerPath + /** * The slice's reducer. */ @@ -82,19 +88,21 @@ export interface Slice< ): Id> /** - * Selectors that assume the slice's state is `rootState[slice.name]` (which is usually the case) + * Selectors that assume the slice's state is `rootState[slice.reducerPath]` (which is usually the case) * - * Equivalent to `slice.getSelectors((state: RootState) => state[slice.name])`. + * Equivalent to `slice.getSelectors((state: RootState) => state[slice.reducerPath])`. */ - selectors: Id> + selectors: Id< + SliceDefinedSelectors + > /** * Inject slice into provided reducer (return value from `combineSlices`), and return injected slice. */ injectInto( combinedReducer: CombinedSliceReducer, - config?: InjectConfig & { name?: string } - ): InjectedSlice + config?: InjectConfig & { reducerPath?: string } + ): InjectedSlice } /** @@ -106,9 +114,10 @@ interface InjectedSlice< State = any, CaseReducers extends SliceCaseReducers = SliceCaseReducers, Name extends string = string, + ReducerPath extends string = Name, Selectors extends SliceSelectors = SliceSelectors > extends Omit< - Slice, + Slice, 'getSelectors' | 'selectors' > { /** @@ -129,7 +138,11 @@ interface InjectedSlice< * Equivalent to `slice.getSelectors((state: RootState) => state[slice.name])`. */ selectors: Id< - SliceDefinedSelectors + SliceDefinedSelectors< + State, + Selectors, + { [K in ReducerPath]?: State | undefined } + > > } @@ -142,6 +155,7 @@ export interface CreateSliceOptions< State = any, CR extends SliceCaseReducers = SliceCaseReducers, Name extends string = string, + ReducerPath extends string = Name, Selectors extends SliceSelectors = SliceSelectors > { /** @@ -149,6 +163,11 @@ export interface CreateSliceOptions< */ name: Name + /** + * The slice's reducer path. Used when injecting into a combined slice reducer. + */ + reducerPath?: ReducerPath + /** * The initial state that should be used when the reducer is called the first time. This may also be a "lazy initializer" function, which should return an initial state value when called. This will be used whenever the reducer is called with `undefined` as its state value, and is primarily useful for cases like reading initial state from `localStorage`. */ @@ -365,11 +384,12 @@ export function createSlice< State, CaseReducers extends SliceCaseReducers, Name extends string, - Selectors extends SliceSelectors + Selectors extends SliceSelectors, + ReducerPath extends string = Name >( - options: CreateSliceOptions -): Slice { - const { name } = options + options: CreateSliceOptions +): Slice { + const { name, reducerPath = name as unknown as ReducerPath } = options if (!name) { throw new Error('`name` is a required option for createSlice') } @@ -451,8 +471,9 @@ export function createSlice< }) } - const defaultSelectSlice = (rootState: { [K in Name]: State }) => - rootState[name] + const defaultSelectSlice = ( + rootState: { [K in ReducerPath]: State } + ): State => rootState[reducerPath] const selectSelf = (state: State) => state @@ -473,6 +494,7 @@ export function createSlice< return { name, + reducerPath, reducer(state, action) { if (!_reducer) _reducer = buildReducer() @@ -507,8 +529,11 @@ export function createSlice< get selectors() { return this.getSelectors(defaultSelectSlice) }, - injectInto(reducer, { name, ...config } = {}) { - reducer.inject({ ...this, ...(name && { name }) }, config) + injectInto(reducer, { reducerPath, ...config } = {}) { + reducer.inject( + { reducerPath: reducerPath ?? this.reducerPath, reducer: this.reducer }, + config + ) let selectorCache = injectedSelectorCache.get(reducer) if (!selectorCache) { selectorCache = new WeakMap() diff --git a/packages/toolkit/src/tests/combineSlices.typetest.ts b/packages/toolkit/src/tests/combineSlices.typetest.ts index cb7d14fc64..8ab744f335 100644 --- a/packages/toolkit/src/tests/combineSlices.typetest.ts +++ b/packages/toolkit/src/tests/combineSlices.typetest.ts @@ -1,5 +1,5 @@ /* eslint-disable no-lone-blocks */ -import type { Reducer, Slice, WithSlice, WithApi } from '@reduxjs/toolkit' +import type { Reducer, Slice, WithSlice } from '@reduxjs/toolkit' import { combineSlices } from '@reduxjs/toolkit' import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query' import { expectExactType, expectType } from './helpers' @@ -41,7 +41,7 @@ type ExampleApiState = ReturnType */ { const rootReducer = combineSlices(stringSlice).withLazyLoadedSlices< - WithSlice & WithApi + WithSlice & WithSlice >() expectExactType(0)( rootReducer(undefined, { type: '' }).number @@ -57,7 +57,7 @@ type ExampleApiState = ReturnType { const rootReducer = combineSlices(stringSlice).withLazyLoadedSlices< WithSlice & - WithApi & { boolean: boolean } + WithSlice & { boolean: boolean } >() expectExactType(0)( @@ -74,7 +74,7 @@ type ExampleApiState = ReturnType expectExactType(0)(withNumber(undefined, { type: '' }).number) const withBool = rootReducer.inject({ - name: 'boolean' as const, + reducerPath: 'boolean' as const, reducer: booleanReducer, }) expectExactType(true)(withBool(undefined, { type: '' }).boolean) diff --git a/packages/toolkit/src/tests/createSlice.test.ts b/packages/toolkit/src/tests/createSlice.test.ts index 91d333285a..b72f97bff4 100644 --- a/packages/toolkit/src/tests/createSlice.test.ts +++ b/packages/toolkit/src/tests/createSlice.test.ts @@ -519,6 +519,7 @@ describe('createSlice', () => { it('allows providing a custom name to inject under', () => { const slice = createSlice({ name: 'counter', + reducerPath: 'injected', initialState: 42, reducers: { increment: (state) => ++state, @@ -539,7 +540,7 @@ describe('createSlice', () => { expect(uninjectedState.injected).toBe(undefined) - slice.injectInto(combinedReducer, { name: 'injected' }) + slice.injectInto(combinedReducer, { reducerPath: 'injected' }) const injectedState = combinedReducer(undefined, increment()) diff --git a/packages/toolkit/src/tests/createSlice.typetest.ts b/packages/toolkit/src/tests/createSlice.typetest.ts index 71842d51d7..142a70b0c0 100644 --- a/packages/toolkit/src/tests/createSlice.typetest.ts +++ b/packages/toolkit/src/tests/createSlice.typetest.ts @@ -530,7 +530,7 @@ const value = actionCreators.anyKey }) const rootState = { - [sliceWithSelectors.name]: sliceWithSelectors.getInitialState(), + [sliceWithSelectors.reducerPath]: sliceWithSelectors.getInitialState(), } const { selectValue, selectMultiply, selectToFixed } = From e92c7db5014ae21df95997ea9770890f5345c391 Mon Sep 17 00:00:00 2001 From: "ben.durrant" Date: Fri, 7 Apr 2023 13:31:22 +0100 Subject: [PATCH 096/412] fix tests --- packages/toolkit/src/tests/combineSlices.test.ts | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/packages/toolkit/src/tests/combineSlices.test.ts b/packages/toolkit/src/tests/combineSlices.test.ts index dbe5569413..bb24c0f8ea 100644 --- a/packages/toolkit/src/tests/combineSlices.test.ts +++ b/packages/toolkit/src/tests/combineSlices.test.ts @@ -1,7 +1,7 @@ import { createReducer } from '../createReducer' import { createAction } from '../createAction' import { createSlice } from '../createSlice' -import type { WithApi, WithSlice } from '../combineSlices' +import type { WithSlice } from '../combineSlices' import { combineSlices } from '../combineSlices' import { expectType } from './helpers' import type { CombinedState } from '../query/core/apiState' @@ -84,19 +84,19 @@ describe('combineSlices', () => { combineSlices(stringSlice).withLazyLoadedSlices<{ boolean: boolean }>() combinedReducer.inject({ - name: 'boolean' as const, + reducerPath: 'boolean' as const, reducer: booleanReducer, }) combinedReducer.inject({ - name: 'boolean' as const, + reducerPath: 'boolean' as const, reducer: booleanReducer, }) expect(consoleSpy).not.toHaveBeenCalled() combinedReducer.inject({ - name: 'boolean' as const, + reducerPath: 'boolean' as const, // @ts-expect-error wrong reducer reducer: stringSlice.reducer, }) @@ -110,13 +110,13 @@ describe('combineSlices', () => { const consoleSpy = vi.spyOn(console, 'error').mockImplementation(() => {}) const combinedReducer = combineSlices(stringSlice).withLazyLoadedSlices< WithSlice & - WithApi & { boolean: boolean } + WithSlice & { boolean: boolean } >() combinedReducer.inject(numberSlice) combinedReducer.inject( - { name: 'number' as const, reducer: () => 0 }, + { reducerPath: 'number' as const, reducer: () => 0 }, { overrideExisting: true } ) @@ -130,7 +130,7 @@ describe('combineSlices', () => { const uninjectedState = combinedReducer(undefined, dummyAction()) const injectedReducer = combinedReducer.inject({ - name: 'boolean' as const, + reducerPath: 'boolean' as const, reducer: booleanReducer, }) From 737d5ccf0dff42d5aa189b1ba4db313611debd42 Mon Sep 17 00:00:00 2001 From: "ben.durrant" Date: Fri, 7 Apr 2023 14:45:55 +0100 Subject: [PATCH 097/412] simplify injectInto implementation --- packages/toolkit/src/createSlice.ts | 69 ++++++------------- .../toolkit/src/tests/createSlice.test.ts | 10 ++- 2 files changed, 30 insertions(+), 49 deletions(-) diff --git a/packages/toolkit/src/createSlice.ts b/packages/toolkit/src/createSlice.ts index af8990df62..be92f98fb6 100644 --- a/packages/toolkit/src/createSlice.ts +++ b/packages/toolkit/src/createSlice.ts @@ -477,13 +477,8 @@ export function createSlice< const selectSelf = (state: State) => state - const selectorCache = new WeakMap< - (rootState: any) => State, - Record any> - >() - const injectedSelectorCache = new WeakMap< - CombinedSliceReducer, + Slice, WeakMap< (rootState: any) => State | undefined, Record any> @@ -492,7 +487,7 @@ export function createSlice< let _reducer: ReducerWithInitialState - return { + const slice: Slice = { name, reducerPath, reducer(state, action) { @@ -507,64 +502,44 @@ export function createSlice< return _reducer.getInitialState() }, - getSelectors(selectState?: (rootState: any) => State) { - if (selectState) { - const cached = selectorCache.get(selectState) - if (cached) { - return cached - } - const selectors: Record any> = {} + getSelectors(selectState: (rootState: any) => State = selectSelf) { + let selectorCache = injectedSelectorCache.get(this) + if (!selectorCache) { + selectorCache = new WeakMap() + injectedSelectorCache.set(this, selectorCache) + } + let cached = selectorCache.get(selectState) + if (!cached) { + cached = {} for (const [name, selector] of Object.entries( options.selectors ?? {} )) { - selectors[name] = (rootState: any, ...args: any[]) => - selector(selectState(rootState), ...args) + cached[name] = (rootState: any, ...args: any[]) => + selector( + selectState(rootState) ?? + (this !== slice ? this.getInitialState() : (undefined as any)), + ...args + ) } - selectorCache.set(selectState, selectors) - return selectors as any - } else { - return options.selectors ?? {} + selectorCache.set(selectState, cached) } + return cached as any }, get selectors() { return this.getSelectors(defaultSelectSlice) }, - injectInto(reducer, { reducerPath, ...config } = {}) { - reducer.inject( + injectInto(injectable, { reducerPath, ...config } = {}) { + injectable.inject( { reducerPath: reducerPath ?? this.reducerPath, reducer: this.reducer }, config ) - let selectorCache = injectedSelectorCache.get(reducer) - if (!selectorCache) { - selectorCache = new WeakMap() - injectedSelectorCache.set(reducer, selectorCache) - } return { ...this, - getSelectors( - selectState: (rootState: any) => State | undefined = selectSelf - ) { - const cached = selectorCache!.get(selectState) - if (cached) { - return cached - } - const selectors: Record any> = {} - for (const [name, selector] of Object.entries( - options.selectors ?? {} - )) { - selectors[name] = (rootState: any, ...args: any[]) => - selector( - selectState(rootState) ?? this.getInitialState(), - ...args - ) - } - selectorCache!.set(selectState, selectors) - return selectors as any - }, get selectors() { return this.getSelectors(defaultSelectSlice) }, } as any }, } + return slice } diff --git a/packages/toolkit/src/tests/createSlice.test.ts b/packages/toolkit/src/tests/createSlice.test.ts index b72f97bff4..0ae4622732 100644 --- a/packages/toolkit/src/tests/createSlice.test.ts +++ b/packages/toolkit/src/tests/createSlice.test.ts @@ -534,17 +534,23 @@ describe('createSlice', () => { const combinedReducer = combineSlices({ static: slice.reducer, - }).withLazyLoadedSlices<{ injected: number }>() + }).withLazyLoadedSlices & { injected2: number }>() const uninjectedState = combinedReducer(undefined, increment()) expect(uninjectedState.injected).toBe(undefined) - slice.injectInto(combinedReducer, { reducerPath: 'injected' }) + slice.injectInto(combinedReducer) const injectedState = combinedReducer(undefined, increment()) expect(injectedState.injected).toBe(slice.getInitialState() + 1) + + slice.injectInto(combinedReducer, { reducerPath: 'injected2' }) + + const injected2State = combinedReducer(undefined, increment()) + + expect(injected2State.injected2).toBe(slice.getInitialState() + 1) }) }) }) From e52f609abcb8a1c2fccdc97ad9571e889497d4d7 Mon Sep 17 00:00:00 2001 From: "ben.durrant" Date: Fri, 7 Apr 2023 17:07:34 +0100 Subject: [PATCH 098/412] throw if selectState returns undefined in an uninjected slice --- packages/toolkit/src/createSlice.ts | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/packages/toolkit/src/createSlice.ts b/packages/toolkit/src/createSlice.ts index be92f98fb6..d7cc8e6a00 100644 --- a/packages/toolkit/src/createSlice.ts +++ b/packages/toolkit/src/createSlice.ts @@ -514,12 +514,20 @@ export function createSlice< for (const [name, selector] of Object.entries( options.selectors ?? {} )) { - cached[name] = (rootState: any, ...args: any[]) => - selector( - selectState(rootState) ?? - (this !== slice ? this.getInitialState() : (undefined as any)), - ...args - ) + cached[name] = (rootState: any, ...args: any[]) => { + let sliceState = selectState(rootState) + if (typeof sliceState === 'undefined') { + // check if injectInto has been called + if (this !== slice) { + sliceState = this.getInitialState() + } else { + throw new Error( + 'selectState returned undefined for an uninjected slice reducer' + ) + } + } + return selector(sliceState, ...args) + } } selectorCache.set(selectState, cached) } From 0a23a5c2a86546eaac346176a2fd2750a3339d9e Mon Sep 17 00:00:00 2001 From: "ben.durrant" Date: Fri, 7 Apr 2023 18:58:21 +0100 Subject: [PATCH 099/412] rm WithApi export --- packages/toolkit/src/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/toolkit/src/index.ts b/packages/toolkit/src/index.ts index 0756787c07..f92ec1b0a8 100644 --- a/packages/toolkit/src/index.ts +++ b/packages/toolkit/src/index.ts @@ -187,4 +187,4 @@ export type { AutoBatchOptions } from './autoBatchEnhancer' export { combineSlices } from './combineSlices' -export type { WithSlice, WithApi } from './combineSlices' +export type { WithSlice } from './combineSlices' From fbf8c2419ee7c88da60436cfd047378e09e91f85 Mon Sep 17 00:00:00 2001 From: "ben.durrant" Date: Sat, 8 Apr 2023 16:46:47 +0100 Subject: [PATCH 100/412] don't throw error in production --- packages/toolkit/src/createSlice.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/toolkit/src/createSlice.ts b/packages/toolkit/src/createSlice.ts index d7cc8e6a00..1e9ee28180 100644 --- a/packages/toolkit/src/createSlice.ts +++ b/packages/toolkit/src/createSlice.ts @@ -520,7 +520,7 @@ export function createSlice< // check if injectInto has been called if (this !== slice) { sliceState = this.getInitialState() - } else { + } else if (process.env.NODE_ENV !== 'production') { throw new Error( 'selectState returned undefined for an uninjected slice reducer' ) From 956a20caf38ab9b124fc3fa62e9f825d64db7e55 Mon Sep 17 00:00:00 2001 From: "ben.durrant" Date: Wed, 12 Apr 2023 20:38:35 +0100 Subject: [PATCH 101/412] Add getDefaultEnhancers callback. --- packages/toolkit/src/configureStore.ts | 52 +++++++------------ packages/toolkit/src/getDefaultEnhancers.ts | 41 +++++++++++++++ packages/toolkit/src/getDefaultMiddleware.ts | 2 +- .../src/tests/EnhancerArray.typetest.ts | 14 ++--- .../toolkit/src/tests/configureStore.test.ts | 5 +- .../src/tests/configureStore.typetest.ts | 33 +++++------- 6 files changed, 86 insertions(+), 61 deletions(-) create mode 100644 packages/toolkit/src/getDefaultEnhancers.ts diff --git a/packages/toolkit/src/configureStore.ts b/packages/toolkit/src/configureStore.ts index f89a86089a..961b1d5afe 100644 --- a/packages/toolkit/src/configureStore.ts +++ b/packages/toolkit/src/configureStore.ts @@ -10,7 +10,7 @@ import type { PreloadedState, CombinedState, } from 'redux' -import { createStore, compose, applyMiddleware, combineReducers } from 'redux' +import { createStore, compose, combineReducers } from 'redux' import type { DevToolsEnhancerOptions as DevToolsOptions } from './devtoolsExtension' import { composeWithDevTools } from './devtoolsExtension' @@ -25,8 +25,11 @@ import type { ExtractDispatchExtensions, ExtractStoreExtensions, ExtractStateExtensions, + Id, } from './tsHelpers' -import { EnhancerArray } from './utils' +import type { EnhancerArray, MiddlewareArray } from './utils' +import type { GetDefaultEnhancers } from './getDefaultEnhancers' +import { buildGetDefaultEnhancers } from './getDefaultEnhancers' const IS_PRODUCTION = process.env.NODE_ENV === 'production' @@ -95,30 +98,17 @@ export interface ConfigureStoreOptions< * The store enhancers to apply. See Redux's `createStore()`. * All enhancers will be included before the DevTools Extension enhancer. * If you need to customize the order of enhancers, supply a callback - * function that will receive the original array (ie, `[applyMiddleware]`), - * and should return a new array (such as `[applyMiddleware, offline]`). + * function that will receieve a `getDefaultEnhancers` function that returns an EnhancerArray, + * and should return a new array (such as `getDefaultEnhancers().concat(offline)`). * If you only need to add middleware, you can use the `middleware` parameter instead. */ - enhancers?: E | ConfigureEnhancersCallback + enhancers?: ((getDefaultEnhancers: GetDefaultEnhancers) => E) | E } -type Middlewares = ReadonlyArray> +export type Middlewares = ReadonlyArray> type Enhancers = ReadonlyArray -export interface ToolkitStore< - S = any, - A extends Action = AnyAction, - M extends Middlewares = Middlewares -> extends Store { - /** - * The `dispatch` method of your store, enhanced by all its middlewares. - * - * @inheritdoc - */ - dispatch: ExtractDispatchExtensions & Dispatch -} - /** * A Redux store returned by `configureStore()`. Supports dispatching * side-effectful _thunks_ in addition to plain actions. @@ -128,10 +118,8 @@ export interface ToolkitStore< export type EnhancedStore< S = any, A extends Action = AnyAction, - M extends Middlewares = Middlewares, E extends Enhancers = Enhancers -> = ToolkitStore, A, M> & - ExtractStoreExtensions +> = ExtractStoreExtensions & Store, A> /** * A friendly abstraction over the standard Redux `createStore()` function. @@ -144,9 +132,11 @@ export type EnhancedStore< export function configureStore< S = any, A extends Action = AnyAction, - M extends Middlewares = [ThunkMiddlewareFor], - E extends Enhancers = [StoreEnhancer] ->(options: ConfigureStoreOptions): EnhancedStore { + M extends Middlewares = MiddlewareArray<[ThunkMiddlewareFor]>, + E extends Enhancers = EnhancerArray< + [StoreEnhancer<{ dispatch: ExtractDispatchExtensions }>, StoreEnhancer] + > +>(options: ConfigureStoreOptions): EnhancedStore { const curriedGetDefaultMiddleware = curryGetDefaultMiddleware() const { @@ -188,8 +178,6 @@ export function configureStore< ) } - const middlewareEnhancer: StoreEnhancer = applyMiddleware(...finalMiddleware) - let finalCompose = compose if (devTools) { @@ -200,13 +188,13 @@ export function configureStore< }) } - const defaultEnhancers = new EnhancerArray(middlewareEnhancer) - let storeEnhancers: Enhancers = defaultEnhancers - + const getDefaultEnhancers = buildGetDefaultEnhancers(finalMiddleware) + let storeEnhancers: readonly StoreEnhancer[] = [] if (Array.isArray(enhancers)) { - storeEnhancers = [middlewareEnhancer, ...enhancers] + // TODO: this matches the typing, but technically allows for a scenario where middlewares are specified but the applyMiddleware enhancer is never used + storeEnhancers = enhancers } else if (typeof enhancers === 'function') { - storeEnhancers = enhancers(defaultEnhancers) + storeEnhancers = enhancers(getDefaultEnhancers) } const composedEnhancer = finalCompose(...storeEnhancers) as StoreEnhancer diff --git a/packages/toolkit/src/getDefaultEnhancers.ts b/packages/toolkit/src/getDefaultEnhancers.ts new file mode 100644 index 0000000000..9cb02d54c8 --- /dev/null +++ b/packages/toolkit/src/getDefaultEnhancers.ts @@ -0,0 +1,41 @@ +import type { AutoBatchOptions, StoreEnhancer } from '.' +import { applyMiddleware } from '.' +import { EnhancerArray, autoBatchEnhancer } from '.' +import type { Middlewares } from './configureStore' +import type { ExcludeFromTuple, ExtractDispatchExtensions } from './tsHelpers' + +type GetDefaultEnhancersOptions = { + autoBatch?: boolean | AutoBatchOptions +} + +type AutoBatchEnhancerFor = O extends { autoBatch: false } + ? never + : StoreEnhancer + +export type GetDefaultEnhancers> = < + O extends GetDefaultEnhancersOptions +>( + options?: O +) => EnhancerArray< + ExcludeFromTuple< + [ + StoreEnhancer<{ dispatch: ExtractDispatchExtensions }>, + AutoBatchEnhancerFor + ], + never + > +> + +export const buildGetDefaultEnhancers = + >(middlewares: M): GetDefaultEnhancers => + (options) => { + const { autoBatch = true } = options ?? {} + + let enhancerArray = new EnhancerArray(applyMiddleware(...middlewares)) + if (autoBatch) { + enhancerArray.push( + autoBatchEnhancer(typeof autoBatch === 'object' ? autoBatch : undefined) + ) + } + return enhancerArray as any + } diff --git a/packages/toolkit/src/getDefaultMiddleware.ts b/packages/toolkit/src/getDefaultMiddleware.ts index 655b0dc931..c0e366e8ee 100644 --- a/packages/toolkit/src/getDefaultMiddleware.ts +++ b/packages/toolkit/src/getDefaultMiddleware.ts @@ -11,7 +11,7 @@ import { createSerializableStateInvariantMiddleware } from './serializableStateI import type { ExcludeFromTuple } from './tsHelpers' import { MiddlewareArray } from './utils' -function isBoolean(x: any): x is boolean { +export function isBoolean(x: any): x is boolean { return typeof x === 'boolean' } diff --git a/packages/toolkit/src/tests/EnhancerArray.typetest.ts b/packages/toolkit/src/tests/EnhancerArray.typetest.ts index 56e89a30d1..7ea38237cc 100644 --- a/packages/toolkit/src/tests/EnhancerArray.typetest.ts +++ b/packages/toolkit/src/tests/EnhancerArray.typetest.ts @@ -22,7 +22,7 @@ declare const enhancer2: StoreEnhancer< { const store = configureStore({ reducer: () => 0, - enhancers: (dE) => dE.prepend(enhancer1), + enhancers: (gDE) => gDE().prepend(enhancer1), }) expectType(store.has1) expectType(store.getState().stateHas1) @@ -37,7 +37,7 @@ declare const enhancer2: StoreEnhancer< { const store = configureStore({ reducer: () => 0, - enhancers: (dE) => dE.prepend(enhancer1, enhancer2), + enhancers: (gDE) => gDE().prepend(enhancer1, enhancer2), }) expectType(store.has1) expectType(store.getState().stateHas1) @@ -54,7 +54,7 @@ declare const enhancer2: StoreEnhancer< { const store = configureStore({ reducer: () => 0, - enhancers: (dE) => dE.prepend([enhancer1, enhancer2] as const), + enhancers: (gDE) => gDE().prepend([enhancer1, enhancer2] as const), }) expectType(store.has1) expectType(store.getState().stateHas1) @@ -71,7 +71,7 @@ declare const enhancer2: StoreEnhancer< { const store = configureStore({ reducer: () => 0, - enhancers: (dE) => dE.concat(enhancer1), + enhancers: (gDE) => gDE().concat(enhancer1), }) expectType(store.has1) expectType(store.getState().stateHas1) @@ -86,7 +86,7 @@ declare const enhancer2: StoreEnhancer< { const store = configureStore({ reducer: () => 0, - enhancers: (dE) => dE.concat(enhancer1, enhancer2), + enhancers: (gDE) => gDE().concat(enhancer1, enhancer2), }) expectType(store.has1) expectType(store.getState().stateHas1) @@ -103,7 +103,7 @@ declare const enhancer2: StoreEnhancer< { const store = configureStore({ reducer: () => 0, - enhancers: (dE) => dE.concat([enhancer1, enhancer2] as const), + enhancers: (gDE) => gDE().concat([enhancer1, enhancer2] as const), }) expectType(store.has1) expectType(store.getState().stateHas1) @@ -120,7 +120,7 @@ declare const enhancer2: StoreEnhancer< { const store = configureStore({ reducer: () => 0, - enhancers: (dE) => dE.concat(enhancer1).prepend(enhancer2), + enhancers: (gDE) => gDE().concat(enhancer1).prepend(enhancer2), }) expectType(store.has1) expectType(store.getState().stateHas1) diff --git a/packages/toolkit/src/tests/configureStore.test.ts b/packages/toolkit/src/tests/configureStore.test.ts index db47390e09..266f38d133 100644 --- a/packages/toolkit/src/tests/configureStore.test.ts +++ b/packages/toolkit/src/tests/configureStore.test.ts @@ -257,7 +257,7 @@ describe('configureStore', async () => { expect(configureStore({ enhancers: [enhancer], reducer })).toBeInstanceOf( Object ) - expect(redux.applyMiddleware).toHaveBeenCalled() + expect(redux.applyMiddleware).not.toHaveBeenCalled() expect(mockDevtoolsCompose).toHaveBeenCalled() // @remap-prod-remove-line expect(redux.createStore).toHaveBeenCalledWith( reducer, @@ -281,7 +281,8 @@ describe('configureStore', async () => { const store = configureStore({ reducer, - enhancers: (defaultEnhancers) => defaultEnhancers.concat(dummyEnhancer), + enhancers: (getDefaultEnhancers) => + getDefaultEnhancers().concat(dummyEnhancer), }) expect(dummyEnhancerCalled).toBe(true) diff --git a/packages/toolkit/src/tests/configureStore.typetest.ts b/packages/toolkit/src/tests/configureStore.typetest.ts index b62709de37..6a4a7303c5 100644 --- a/packages/toolkit/src/tests/configureStore.typetest.ts +++ b/packages/toolkit/src/tests/configureStore.typetest.ts @@ -143,7 +143,7 @@ const _anyMiddleware: any = () => () => () => {} { const store = configureStore({ reducer: () => 0, - enhancers: [applyMiddleware(() => (next) => next)], + enhancers: [applyMiddleware(() => (next) => next)] as const, }) expectType>( @@ -188,16 +188,14 @@ const _anyMiddleware: any = () => () => () => {} ] as const, }) - expectType>( - store.dispatch - ) + expectType(store.dispatch) expectType(store.someProperty) expectType(store.anotherProperty) const storeWithCallback = configureStore({ reducer: () => 0, - enhancers: (defaultEnhancers) => - defaultEnhancers + enhancers: (getDefaultEnhancers) => + getDefaultEnhancers() .prepend(anotherPropertyStoreEnhancer) .concat(somePropertyStoreEnhancer), }) @@ -210,11 +208,11 @@ const _anyMiddleware: any = () => () => () => {} } { - type StateExtendingEnhancer = StoreEnhancer<{}, { someProperty: string }> - - const someStateExtendingEnhancer: StateExtendingEnhancer = + const someStateExtendingEnhancer: StoreEnhancer< + {}, + { someProperty: string } + > = (next) => - // @ts-expect-error how do you properly return an enhancer that extends state? (...args) => { const store = next(...args) const getState = () => ({ @@ -224,17 +222,14 @@ const _anyMiddleware: any = () => () => () => {} return { ...store, getState, - } + } as any } - type AnotherStateExtendingEnhancer = StoreEnhancer< + const anotherStateExtendingEnhancer: StoreEnhancer< {}, { anotherProperty: number } - > - - const anotherStateExtendingEnhancer: AnotherStateExtendingEnhancer = + > = (next) => - // @ts-expect-error any input on this would be great (...args) => { const store = next(...args) const getState = () => ({ @@ -244,7 +239,7 @@ const _anyMiddleware: any = () => () => () => {} return { ...store, getState, - } + } as any } const store = configureStore({ @@ -264,8 +259,8 @@ const _anyMiddleware: any = () => () => () => {} const storeWithCallback = configureStore({ reducer: () => ({ aProperty: 0 }), - enhancers: (dE) => - dE.concat(someStateExtendingEnhancer, anotherStateExtendingEnhancer), + enhancers: (gDE) => + gDE().concat(someStateExtendingEnhancer, anotherStateExtendingEnhancer), }) const stateWithCallback = storeWithCallback.getState() From 0f94025bf665566866c117637073ff62211d7f00 Mon Sep 17 00:00:00 2001 From: "ben.durrant" Date: Wed, 12 Apr 2023 20:49:23 +0100 Subject: [PATCH 102/412] import things from the right places --- packages/toolkit/src/getDefaultEnhancers.ts | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/packages/toolkit/src/getDefaultEnhancers.ts b/packages/toolkit/src/getDefaultEnhancers.ts index 9cb02d54c8..b9b4f8a09c 100644 --- a/packages/toolkit/src/getDefaultEnhancers.ts +++ b/packages/toolkit/src/getDefaultEnhancers.ts @@ -1,6 +1,8 @@ -import type { AutoBatchOptions, StoreEnhancer } from '.' -import { applyMiddleware } from '.' -import { EnhancerArray, autoBatchEnhancer } from '.' +import type { StoreEnhancer } from 'redux' +import { applyMiddleware } from 'redux' +import type { AutoBatchOptions } from './autoBatchEnhancer' +import { autoBatchEnhancer } from './autoBatchEnhancer' +import { EnhancerArray } from './utils' import type { Middlewares } from './configureStore' import type { ExcludeFromTuple, ExtractDispatchExtensions } from './tsHelpers' From 5dbfd937ce8e3eaab3af02516c4d045fd8252da0 Mon Sep 17 00:00:00 2001 From: "ben.durrant" Date: Wed, 12 Apr 2023 21:17:19 +0100 Subject: [PATCH 103/412] general cleanup --- docs/api/autoBatchEnhancer.mdx | 4 ++-- docs/api/configureStore.mdx | 15 ++++++++------- docs/usage/usage-guide.md | 19 ++++++++++--------- packages/toolkit/src/configureStore.ts | 15 ++------------- packages/toolkit/src/index.ts | 5 ----- .../src/tests/autoBatchEnhancer.test.ts | 7 ++++--- .../src/tests/configureStore.typetest.ts | 2 +- .../src/tests/getDefaultMiddleware.test.ts | 12 ++++++------ 8 files changed, 33 insertions(+), 46 deletions(-) diff --git a/docs/api/autoBatchEnhancer.mdx b/docs/api/autoBatchEnhancer.mdx index 16ac4b4be4..cede99cd65 100644 --- a/docs/api/autoBatchEnhancer.mdx +++ b/docs/api/autoBatchEnhancer.mdx @@ -51,9 +51,9 @@ const { incrementBatched, decrementUnbatched } = counterSlice.actions const store = configureStore({ reducer: counterSlice.reducer, // highlight-start - enhancers: (existingEnhancers) => { + enhancers: (getDefaultEnhancers) => { // Add the autobatch enhancer to the store setup - return existingEnhancers.concat(autoBatchEnhancer()) + return getDefaultEnhancers().concat(autoBatchEnhancer()) }, // highlight-end }) diff --git a/docs/api/configureStore.mdx b/docs/api/configureStore.mdx index e3199c6660..7abd615508 100644 --- a/docs/api/configureStore.mdx +++ b/docs/api/configureStore.mdx @@ -17,14 +17,12 @@ to the store setup for a better development experience. `configureStore` accepts a single configuration object parameter, with the following options: ```ts no-transpile -type ConfigureEnhancersCallback = ( - defaultEnhancers: EnhancerArray<[StoreEnhancer]> -) => StoreEnhancer[] interface ConfigureStoreOptions< S = any, A extends Action = AnyAction, M extends Middlewares = Middlewares + E extends Enhancers = Enhancers > { /** * A single reducer function that will be used as the root reducer, or an @@ -59,11 +57,11 @@ interface ConfigureStoreOptions< * The store enhancers to apply. See Redux's `createStore()`. * All enhancers will be included before the DevTools Extension enhancer. * If you need to customize the order of enhancers, supply a callback - * function that will receive the original array (ie, `[applyMiddleware]`), - * and should return a new array (such as `[applyMiddleware, offline]`). + * function that will receive the getDefaultEnhancers, + * and should return a new array (such as `getDefaultEnhancers().concat(offline)`). * If you only need to add middleware, you can use the `middleware` parameter instead. */ - enhancers?: StoreEnhancer[] | ConfigureEnhancersCallback + enhancers?: (getDefaultEnhancers: GetDefaultEnhancers) => E | E } function configureStore( @@ -203,7 +201,10 @@ const store = configureStore({ middleware: (getDefaultMiddleware) => getDefaultMiddleware().concat(logger), devTools: process.env.NODE_ENV !== 'production', preloadedState, - enhancers: [batchedSubscribe(debounceNotify)], + enhancers: (getDefaultEnhancers) => + getDefaultEnhancers({ + autoBatch: false, + }).concat(batchedSubscribe(debounceNotify)), }) // The store has been created with these options: diff --git a/docs/usage/usage-guide.md b/docs/usage/usage-guide.md index 6b465ff719..bcd93f8fc6 100644 --- a/docs/usage/usage-guide.md +++ b/docs/usage/usage-guide.md @@ -128,7 +128,8 @@ export default function configureAppStore(preloadedState) { middleware: (getDefaultMiddleware) => getDefaultMiddleware().concat(loggerMiddleware), preloadedState, - enhancers: [monitorReducersEnhancer], + enhancers: (getDefaultEnhancers) => + getDefaultEnhancers().concat(monitorReducersEnhancer), }) if (process.env.NODE_ENV !== 'production' && module.hot) { @@ -619,17 +620,17 @@ A typical implementation might look like: ```js const getRepoDetailsStarted = () => ({ - type: "repoDetails/fetchStarted" + type: 'repoDetails/fetchStarted', }) const getRepoDetailsSuccess = (repoDetails) => ({ - type: "repoDetails/fetchSucceeded", - payload: repoDetails + type: 'repoDetails/fetchSucceeded', + payload: repoDetails, }) const getRepoDetailsFailed = (error) => ({ - type: "repoDetails/fetchFailed", - error + type: 'repoDetails/fetchFailed', + error, }) -const fetchIssuesCount = (org, repo) => async dispatch => { +const fetchIssuesCount = (org, repo) => async (dispatch) => { dispatch(getRepoDetailsStarted()) try { const repoDetails = await getRepoDetails(org, repo) @@ -1118,11 +1119,11 @@ It is also strongly recommended to blacklist any api(s) that you have configured ```ts const persistConfig = { - key: "root", + key: 'root', version: 1, storage, blacklist: [pokemonApi.reducerPath], -}; +} ``` See [Redux Toolkit #121: How to use this with Redux-Persist?](https://github.com/reduxjs/redux-toolkit/issues/121) and [Redux-Persist #988: non-serializable value error](https://github.com/rt2zz/redux-persist/issues/988#issuecomment-552242978) for further discussion. diff --git a/packages/toolkit/src/configureStore.ts b/packages/toolkit/src/configureStore.ts index 961b1d5afe..1f3b3c1ba6 100644 --- a/packages/toolkit/src/configureStore.ts +++ b/packages/toolkit/src/configureStore.ts @@ -6,7 +6,6 @@ import type { AnyAction, StoreEnhancer, Store, - Dispatch, PreloadedState, CombinedState, } from 'redux' @@ -25,7 +24,6 @@ import type { ExtractDispatchExtensions, ExtractStoreExtensions, ExtractStateExtensions, - Id, } from './tsHelpers' import type { EnhancerArray, MiddlewareArray } from './utils' import type { GetDefaultEnhancers } from './getDefaultEnhancers' @@ -33,15 +31,6 @@ import { buildGetDefaultEnhancers } from './getDefaultEnhancers' const IS_PRODUCTION = process.env.NODE_ENV === 'production' -/** - * Callback function type, to be used in `ConfigureStoreOptions.enhancers` - * - * @public - */ -export type ConfigureEnhancersCallback = ( - defaultEnhancers: EnhancerArray<[StoreEnhancer<{}, {}>]> -) => E - /** * Options for `configureStore()`. * @@ -189,7 +178,7 @@ export function configureStore< } const getDefaultEnhancers = buildGetDefaultEnhancers(finalMiddleware) - let storeEnhancers: readonly StoreEnhancer[] = [] + let storeEnhancers: readonly StoreEnhancer[] = getDefaultEnhancers() if (Array.isArray(enhancers)) { // TODO: this matches the typing, but technically allows for a scenario where middlewares are specified but the applyMiddleware enhancer is never used storeEnhancers = enhancers @@ -197,7 +186,7 @@ export function configureStore< storeEnhancers = enhancers(getDefaultEnhancers) } - const composedEnhancer = finalCompose(...storeEnhancers) as StoreEnhancer + const composedEnhancer: StoreEnhancer = finalCompose(...storeEnhancers) return createStore(rootReducer, preloadedState, composedEnhancer) } diff --git a/packages/toolkit/src/index.ts b/packages/toolkit/src/index.ts index 953d0bd721..b5a62147fe 100644 --- a/packages/toolkit/src/index.ts +++ b/packages/toolkit/src/index.ts @@ -23,7 +23,6 @@ export { } from './configureStore' export type { // types - ConfigureEnhancersCallback, ConfigureStoreOptions, EnhancedStore, } from './configureStore' @@ -88,10 +87,6 @@ export type { // types SerializableStateInvariantMiddlewareOptions, } from './serializableStateInvariantMiddleware' -export { - // js - getDefaultMiddleware, -} from './getDefaultMiddleware' export type { // types ActionReducerMapBuilder, diff --git a/packages/toolkit/src/tests/autoBatchEnhancer.test.ts b/packages/toolkit/src/tests/autoBatchEnhancer.test.ts index 31ecf5ab28..870d2c3106 100644 --- a/packages/toolkit/src/tests/autoBatchEnhancer.test.ts +++ b/packages/toolkit/src/tests/autoBatchEnhancer.test.ts @@ -31,9 +31,10 @@ const { incrementBatched, decrementUnbatched } = counterSlice.actions const makeStore = (autoBatchOptions?: AutoBatchOptions) => { return configureStore({ reducer: counterSlice.reducer, - enhancers: (existingEnhancers) => { - return existingEnhancers.concat(autoBatchEnhancer(autoBatchOptions)) - }, + enhancers: (getDefaultEnhancers) => + getDefaultEnhancers({ + autoBatch: autoBatchOptions, + }), }) } diff --git a/packages/toolkit/src/tests/configureStore.typetest.ts b/packages/toolkit/src/tests/configureStore.typetest.ts index 6a4a7303c5..02edc5da78 100644 --- a/packages/toolkit/src/tests/configureStore.typetest.ts +++ b/packages/toolkit/src/tests/configureStore.typetest.ts @@ -12,12 +12,12 @@ import { applyMiddleware } from 'redux' import type { PayloadAction, ConfigureStoreOptions } from '@reduxjs/toolkit' import { configureStore, - getDefaultMiddleware, createSlice, } from '@reduxjs/toolkit' import type { ThunkMiddleware, ThunkAction, ThunkDispatch } from 'redux-thunk' import { thunk } from 'redux-thunk' import { expectNotAny, expectType } from './helpers' +import { getDefaultMiddleware } from '@internal/getDefaultMiddleware' const _anyMiddleware: any = () => () => () => {} diff --git a/packages/toolkit/src/tests/getDefaultMiddleware.test.ts b/packages/toolkit/src/tests/getDefaultMiddleware.test.ts index 4da69101e3..0f009dfdc6 100644 --- a/packages/toolkit/src/tests/getDefaultMiddleware.test.ts +++ b/packages/toolkit/src/tests/getDefaultMiddleware.test.ts @@ -7,16 +7,14 @@ import type { ThunkDispatch, Dispatch, } from '@reduxjs/toolkit' -import { - getDefaultMiddleware, - MiddlewareArray, - configureStore, -} from '@reduxjs/toolkit' +import { MiddlewareArray, configureStore } from '@reduxjs/toolkit' import { thunk } from 'redux-thunk' import type { ThunkMiddleware } from 'redux-thunk' import { expectType } from './helpers' +import { getDefaultMiddleware } from '@internal/getDefaultMiddleware' + describe('getDefaultMiddleware', () => { const ORIGINAL_NODE_ENV = process.env.NODE_ENV @@ -32,7 +30,9 @@ describe('getDefaultMiddleware', () => { it('returns an array with only redux-thunk in production', async () => { process.env.NODE_ENV = 'production' const { thunk } = await import('redux-thunk') - const { getDefaultMiddleware } = await import('@reduxjs/toolkit') + const { getDefaultMiddleware } = await import( + '@internal/getDefaultMiddleware' + ) const middleware = getDefaultMiddleware() expect(middleware).toContain(thunk) From a158597fa08254104361e110ef51211423b8f933 Mon Sep 17 00:00:00 2001 From: "ben.durrant" Date: Wed, 12 Apr 2023 21:22:29 +0100 Subject: [PATCH 104/412] ensure applyMiddleware is not called if array is provided --- packages/toolkit/src/configureStore.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/toolkit/src/configureStore.ts b/packages/toolkit/src/configureStore.ts index 1f3b3c1ba6..d9ab4510ac 100644 --- a/packages/toolkit/src/configureStore.ts +++ b/packages/toolkit/src/configureStore.ts @@ -178,12 +178,14 @@ export function configureStore< } const getDefaultEnhancers = buildGetDefaultEnhancers(finalMiddleware) - let storeEnhancers: readonly StoreEnhancer[] = getDefaultEnhancers() + let storeEnhancers: readonly StoreEnhancer[] if (Array.isArray(enhancers)) { // TODO: this matches the typing, but technically allows for a scenario where middlewares are specified but the applyMiddleware enhancer is never used storeEnhancers = enhancers } else if (typeof enhancers === 'function') { storeEnhancers = enhancers(getDefaultEnhancers) + } else { + storeEnhancers = getDefaultEnhancers() } const composedEnhancer: StoreEnhancer = finalCompose(...storeEnhancers) From 3ee7651bbe81190b640fe0525316468f0dcd4c92 Mon Sep 17 00:00:00 2001 From: "ben.durrant" Date: Wed, 12 Apr 2023 21:27:51 +0100 Subject: [PATCH 105/412] remove queueMicrotask shim --- packages/toolkit/src/autoBatchEnhancer.ts | 28 ++++------------------- 1 file changed, 4 insertions(+), 24 deletions(-) diff --git a/packages/toolkit/src/autoBatchEnhancer.ts b/packages/toolkit/src/autoBatchEnhancer.ts index 65a77bb864..eb4dd8ca56 100644 --- a/packages/toolkit/src/autoBatchEnhancer.ts +++ b/packages/toolkit/src/autoBatchEnhancer.ts @@ -9,26 +9,6 @@ export const prepareAutoBatched = meta: { [SHOULD_AUTOBATCH]: true }, }) -// TODO Remove this in 2.0 -// Copied from https://github.com/feross/queue-microtask -let promise: Promise -const queueMicrotaskShim = - typeof queueMicrotask === 'function' - ? queueMicrotask.bind( - typeof window !== 'undefined' - ? window - : typeof global !== 'undefined' - ? global - : globalThis - ) - : // reuse resolved promise, and allocate it lazily - (cb: () => void) => - (promise || (promise = Promise.resolve())).then(cb).catch((err: any) => - setTimeout(() => { - throw err - }, 0) - ) - const createQueueWithTimer = (timeout: number) => { return (notify: () => void) => { setTimeout(notify, timeout) @@ -63,10 +43,10 @@ export type AutoBatchOptions = * * By default, it will queue a notification for the end of the event loop tick. * However, you can pass several other options to configure the behavior: - * - `{type: 'tick'}: queues using `queueMicrotask` (default) + * - `{type: 'tick'}`: queues using `queueMicrotask` * - `{type: 'timer, timeout: number}`: queues using `setTimeout` - * - `{type: 'raf'}`: queues using `requestAnimationFrame` - * - `{type: 'callback', queueNotification: (notify: () => void) => void}: lets you provide your own callback + * - `{type: 'raf'}`: queues using `requestAnimationFrame` (default) + * - `{type: 'callback', queueNotification: (notify: () => void) => void}`: lets you provide your own callback * * */ @@ -84,7 +64,7 @@ export const autoBatchEnhancer = const queueCallback = options.type === 'tick' - ? queueMicrotaskShim + ? queueMicrotask : options.type === 'raf' ? rAF : options.type === 'callback' From 9bf48ff8636c3cf39feb8e248340ec91f90cb0be Mon Sep 17 00:00:00 2001 From: "ben.durrant" Date: Wed, 12 Apr 2023 21:29:32 +0100 Subject: [PATCH 106/412] remove shim from batchActions too --- .../core/buildMiddleware/batchActions.ts | 23 ++----------------- 1 file changed, 2 insertions(+), 21 deletions(-) diff --git a/packages/toolkit/src/query/core/buildMiddleware/batchActions.ts b/packages/toolkit/src/query/core/buildMiddleware/batchActions.ts index 549ade7084..e6477b055c 100644 --- a/packages/toolkit/src/query/core/buildMiddleware/batchActions.ts +++ b/packages/toolkit/src/query/core/buildMiddleware/batchActions.ts @@ -6,28 +6,9 @@ import type { Subscribers, } from '../apiState' import { produceWithPatches } from 'immer' -import type { AnyAction } from '@reduxjs/toolkit'; +import type { AnyAction } from '@reduxjs/toolkit' import { createSlice, PayloadAction } from '@reduxjs/toolkit' -// Copied from https://github.com/feross/queue-microtask -let promise: Promise -const queueMicrotaskShim = - typeof queueMicrotask === 'function' - ? queueMicrotask.bind( - typeof window !== 'undefined' - ? window - : typeof global !== 'undefined' - ? global - : globalThis - ) - : // reuse resolved promise, and allocate it lazily - (cb: () => void) => - (promise || (promise = Promise.resolve())).then(cb).catch((err: any) => - setTimeout(() => { - throw err - }, 0) - ) - export const buildBatchedActionsHandler: InternalHandlerBuilder< [actionShouldContinue: boolean, subscriptionExists: boolean] > = ({ api, queryThunk, internalState }) => { @@ -119,7 +100,7 @@ export const buildBatchedActionsHandler: InternalHandlerBuilder< if (didMutate) { if (!dispatchQueued) { - queueMicrotaskShim(() => { + queueMicrotask(() => { // Deep clone the current subscription data const newSubscriptions: SubscriptionState = JSON.parse( JSON.stringify(internalState.currentSubscriptions) From 2065e0eb890a86bd484eab0fc568b6f6f1ef5d9f Mon Sep 17 00:00:00 2001 From: "ben.durrant" Date: Wed, 12 Apr 2023 21:43:12 +0100 Subject: [PATCH 107/412] remove uncurried getDefaultMiddleware completely --- packages/toolkit/src/configureStore.ts | 12 +- packages/toolkit/src/getDefaultEnhancers.ts | 7 +- packages/toolkit/src/getDefaultMiddleware.ts | 115 ++++++++---------- .../src/tests/getDefaultMiddleware.test.ts | 8 +- 4 files changed, 63 insertions(+), 79 deletions(-) diff --git a/packages/toolkit/src/configureStore.ts b/packages/toolkit/src/configureStore.ts index d9ab4510ac..e99db9832b 100644 --- a/packages/toolkit/src/configureStore.ts +++ b/packages/toolkit/src/configureStore.ts @@ -16,9 +16,9 @@ import { composeWithDevTools } from './devtoolsExtension' import isPlainObject from './isPlainObject' import type { ThunkMiddlewareFor, - CurriedGetDefaultMiddleware, + GetDefaultMiddleware, } from './getDefaultMiddleware' -import { curryGetDefaultMiddleware } from './getDefaultMiddleware' +import { buildGetDefaultMiddleware } from './getDefaultMiddleware' import type { NoInfer, ExtractDispatchExtensions, @@ -55,7 +55,7 @@ export interface ConfigureStoreOptions< * @example `middleware: (gDM) => gDM().concat(logger, apiMiddleware, yourCustomMiddleware)` * @see https://redux-toolkit.js.org/api/getDefaultMiddleware#intended-usage */ - middleware?: ((getDefaultMiddleware: CurriedGetDefaultMiddleware) => M) | M + middleware?: ((getDefaultMiddleware: GetDefaultMiddleware) => M) | M /** * Whether to enable Redux DevTools integration. Defaults to `true`. @@ -126,11 +126,11 @@ export function configureStore< [StoreEnhancer<{ dispatch: ExtractDispatchExtensions }>, StoreEnhancer] > >(options: ConfigureStoreOptions): EnhancedStore { - const curriedGetDefaultMiddleware = curryGetDefaultMiddleware() + const getDefaultMiddleware = buildGetDefaultMiddleware() const { reducer = undefined, - middleware = curriedGetDefaultMiddleware(), + middleware = getDefaultMiddleware(), devTools = true, preloadedState = undefined, enhancers = undefined, @@ -150,7 +150,7 @@ export function configureStore< let finalMiddleware = middleware if (typeof finalMiddleware === 'function') { - finalMiddleware = finalMiddleware(curriedGetDefaultMiddleware) + finalMiddleware = finalMiddleware(getDefaultMiddleware) if (!IS_PRODUCTION && !Array.isArray(finalMiddleware)) { throw new Error( diff --git a/packages/toolkit/src/getDefaultEnhancers.ts b/packages/toolkit/src/getDefaultEnhancers.ts index b9b4f8a09c..66dbb5ee88 100644 --- a/packages/toolkit/src/getDefaultEnhancers.ts +++ b/packages/toolkit/src/getDefaultEnhancers.ts @@ -28,9 +28,10 @@ export type GetDefaultEnhancers> = < > > -export const buildGetDefaultEnhancers = - >(middlewares: M): GetDefaultEnhancers => - (options) => { +export const buildGetDefaultEnhancers = >( + middlewares: M +): GetDefaultEnhancers => + function getDefaultEnhancers(options) { const { autoBatch = true } = options ?? {} let enhancerArray = new EnhancerArray(applyMiddleware(...middlewares)) diff --git a/packages/toolkit/src/getDefaultMiddleware.ts b/packages/toolkit/src/getDefaultMiddleware.ts index c0e366e8ee..a3f8389761 100644 --- a/packages/toolkit/src/getDefaultMiddleware.ts +++ b/packages/toolkit/src/getDefaultMiddleware.ts @@ -36,7 +36,7 @@ export type ThunkMiddlewareFor< ? ThunkMiddleware : ThunkMiddleware -export type CurriedGetDefaultMiddleware = < +export type GetDefaultMiddleware = < O extends Partial = { thunk: true immutableCheck: true @@ -46,79 +46,60 @@ export type CurriedGetDefaultMiddleware = < options?: O ) => MiddlewareArray], never>> -export function curryGetDefaultMiddleware< - S = any ->(): CurriedGetDefaultMiddleware { - return function curriedGetDefaultMiddleware(options) { - return getDefaultMiddleware(options) - } -} - -/** - * Returns any array containing the default middleware installed by - * `configureStore()`. Useful if you want to configure your store with a custom - * `middleware` array but still keep the default set. - * - * @return The default middleware used by `configureStore()`. - * - * @public - * - * @deprecated Prefer to use the callback notation for the `middleware` option in `configureStore` - * to access a pre-typed `getDefaultMiddleware` instead. - */ -export function getDefaultMiddleware< - S = any, - O extends Partial = { - thunk: true - immutableCheck: true - serializableCheck: true - } ->( - options: O = {} as O -): MiddlewareArray], never>> { - const { - thunk = true, - immutableCheck = true, - serializableCheck = true, - } = options - - let middlewareArray = new MiddlewareArray() - - if (thunk) { - if (isBoolean(thunk)) { - middlewareArray.push(thunkMiddleware) - } else { - middlewareArray.push(withExtraArgument(thunk.extraArgument)) +export const buildGetDefaultMiddleware = (): GetDefaultMiddleware => + function getDefaultMiddleware< + O extends Partial = { + thunk: true + immutableCheck: true + serializableCheck: true + } + >( + options: O = {} as O + ): MiddlewareArray], never>> { + const { + thunk = true, + immutableCheck = true, + serializableCheck = true, + } = options + + let middlewareArray = new MiddlewareArray() + + if (thunk) { + if (isBoolean(thunk)) { + middlewareArray.push(thunkMiddleware) + } else { + middlewareArray.push(withExtraArgument(thunk.extraArgument)) + } } - } - if (process.env.NODE_ENV !== 'production') { - if (immutableCheck) { - /* PROD_START_REMOVE_UMD */ - let immutableOptions: ImmutableStateInvariantMiddlewareOptions = {} + if (process.env.NODE_ENV !== 'production') { + if (immutableCheck) { + /* PROD_START_REMOVE_UMD */ + let immutableOptions: ImmutableStateInvariantMiddlewareOptions = {} - if (!isBoolean(immutableCheck)) { - immutableOptions = immutableCheck + if (!isBoolean(immutableCheck)) { + immutableOptions = immutableCheck + } + + middlewareArray.unshift( + createImmutableStateInvariantMiddleware(immutableOptions) + ) + /* PROD_STOP_REMOVE_UMD */ } - middlewareArray.unshift( - createImmutableStateInvariantMiddleware(immutableOptions) - ) - /* PROD_STOP_REMOVE_UMD */ - } + if (serializableCheck) { + let serializableOptions: SerializableStateInvariantMiddlewareOptions = + {} - if (serializableCheck) { - let serializableOptions: SerializableStateInvariantMiddlewareOptions = {} + if (!isBoolean(serializableCheck)) { + serializableOptions = serializableCheck + } - if (!isBoolean(serializableCheck)) { - serializableOptions = serializableCheck + middlewareArray.push( + createSerializableStateInvariantMiddleware(serializableOptions) + ) } - - middlewareArray.push( - createSerializableStateInvariantMiddleware(serializableOptions) - ) } - } - return middlewareArray as any -} + return middlewareArray as any + } diff --git a/packages/toolkit/src/tests/getDefaultMiddleware.test.ts b/packages/toolkit/src/tests/getDefaultMiddleware.test.ts index 0f009dfdc6..eb19fbd29d 100644 --- a/packages/toolkit/src/tests/getDefaultMiddleware.test.ts +++ b/packages/toolkit/src/tests/getDefaultMiddleware.test.ts @@ -13,7 +13,9 @@ import type { ThunkMiddleware } from 'redux-thunk' import { expectType } from './helpers' -import { getDefaultMiddleware } from '@internal/getDefaultMiddleware' +import { buildGetDefaultMiddleware } from '@internal/getDefaultMiddleware' + +const getDefaultMiddleware = buildGetDefaultMiddleware() describe('getDefaultMiddleware', () => { const ORIGINAL_NODE_ENV = process.env.NODE_ENV @@ -30,11 +32,11 @@ describe('getDefaultMiddleware', () => { it('returns an array with only redux-thunk in production', async () => { process.env.NODE_ENV = 'production' const { thunk } = await import('redux-thunk') - const { getDefaultMiddleware } = await import( + const { buildGetDefaultMiddleware } = await import( '@internal/getDefaultMiddleware' ) - const middleware = getDefaultMiddleware() + const middleware = buildGetDefaultMiddleware()() expect(middleware).toContain(thunk) expect(middleware.length).toBe(1) }) From 08685c0649730226fc955490480d4746c0fc4faa Mon Sep 17 00:00:00 2001 From: "ben.durrant" Date: Wed, 12 Apr 2023 21:48:04 +0100 Subject: [PATCH 108/412] remove now-duplicate types --- packages/toolkit/src/getDefaultMiddleware.ts | 14 +++----------- 1 file changed, 3 insertions(+), 11 deletions(-) diff --git a/packages/toolkit/src/getDefaultMiddleware.ts b/packages/toolkit/src/getDefaultMiddleware.ts index a3f8389761..93cc6ccd62 100644 --- a/packages/toolkit/src/getDefaultMiddleware.ts +++ b/packages/toolkit/src/getDefaultMiddleware.ts @@ -11,7 +11,7 @@ import { createSerializableStateInvariantMiddleware } from './serializableStateI import type { ExcludeFromTuple } from './tsHelpers' import { MiddlewareArray } from './utils' -export function isBoolean(x: any): x is boolean { +function isBoolean(x: any): x is boolean { return typeof x === 'boolean' } @@ -47,20 +47,12 @@ export type GetDefaultMiddleware = < ) => MiddlewareArray], never>> export const buildGetDefaultMiddleware = (): GetDefaultMiddleware => - function getDefaultMiddleware< - O extends Partial = { - thunk: true - immutableCheck: true - serializableCheck: true - } - >( - options: O = {} as O - ): MiddlewareArray], never>> { + function getDefaultMiddleware(options) { const { thunk = true, immutableCheck = true, serializableCheck = true, - } = options + } = options ?? {} let middlewareArray = new MiddlewareArray() From 858388ebba7f53ffbbb84c8fdd7c69dde9a68fba Mon Sep 17 00:00:00 2001 From: "ben.durrant" Date: Wed, 12 Apr 2023 21:56:30 +0100 Subject: [PATCH 109/412] add type parameter defaults --- packages/toolkit/src/getDefaultEnhancers.ts | 9 +++++---- packages/toolkit/src/getDefaultMiddleware.ts | 2 +- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/packages/toolkit/src/getDefaultEnhancers.ts b/packages/toolkit/src/getDefaultEnhancers.ts index 66dbb5ee88..5f375ecbbe 100644 --- a/packages/toolkit/src/getDefaultEnhancers.ts +++ b/packages/toolkit/src/getDefaultEnhancers.ts @@ -10,12 +10,13 @@ type GetDefaultEnhancersOptions = { autoBatch?: boolean | AutoBatchOptions } -type AutoBatchEnhancerFor = O extends { autoBatch: false } - ? never - : StoreEnhancer +type AutoBatchEnhancerFor = + O extends { autoBatch: false } ? never : StoreEnhancer export type GetDefaultEnhancers> = < - O extends GetDefaultEnhancersOptions + O extends GetDefaultEnhancersOptions = { + autoBatch: true + } >( options?: O ) => EnhancerArray< diff --git a/packages/toolkit/src/getDefaultMiddleware.ts b/packages/toolkit/src/getDefaultMiddleware.ts index 93cc6ccd62..7ee36f6363 100644 --- a/packages/toolkit/src/getDefaultMiddleware.ts +++ b/packages/toolkit/src/getDefaultMiddleware.ts @@ -37,7 +37,7 @@ export type ThunkMiddlewareFor< : ThunkMiddleware export type GetDefaultMiddleware = < - O extends Partial = { + O extends GetDefaultMiddlewareOptions = { thunk: true immutableCheck: true serializableCheck: true From 4062f2be3efec2531d14a261d1bc28bad92d386a Mon Sep 17 00:00:00 2001 From: "ben.durrant" Date: Wed, 12 Apr 2023 22:03:06 +0100 Subject: [PATCH 110/412] remove type tests depending on standalone gDM --- packages/toolkit/src/getDefaultEnhancers.ts | 4 +- .../src/tests/MiddlewareArray.typetest.ts | 2 +- .../src/tests/configureStore.typetest.ts | 79 +------------------ 3 files changed, 5 insertions(+), 80 deletions(-) diff --git a/packages/toolkit/src/getDefaultEnhancers.ts b/packages/toolkit/src/getDefaultEnhancers.ts index 5f375ecbbe..01104bfdcc 100644 --- a/packages/toolkit/src/getDefaultEnhancers.ts +++ b/packages/toolkit/src/getDefaultEnhancers.ts @@ -35,7 +35,9 @@ export const buildGetDefaultEnhancers = >( function getDefaultEnhancers(options) { const { autoBatch = true } = options ?? {} - let enhancerArray = new EnhancerArray(applyMiddleware(...middlewares)) + let enhancerArray = new EnhancerArray( + applyMiddleware(...middlewares) + ) if (autoBatch) { enhancerArray.push( autoBatchEnhancer(typeof autoBatch === 'object' ? autoBatch : undefined) diff --git a/packages/toolkit/src/tests/MiddlewareArray.typetest.ts b/packages/toolkit/src/tests/MiddlewareArray.typetest.ts index 25bb4cb21d..160f1260c4 100644 --- a/packages/toolkit/src/tests/MiddlewareArray.typetest.ts +++ b/packages/toolkit/src/tests/MiddlewareArray.typetest.ts @@ -1,4 +1,4 @@ -import { getDefaultMiddleware, configureStore } from '@reduxjs/toolkit' +import { configureStore } from '@reduxjs/toolkit' import type { Middleware } from 'redux' declare const expectType: (t: T) => T diff --git a/packages/toolkit/src/tests/configureStore.typetest.ts b/packages/toolkit/src/tests/configureStore.typetest.ts index 02edc5da78..0cc817ebc9 100644 --- a/packages/toolkit/src/tests/configureStore.typetest.ts +++ b/packages/toolkit/src/tests/configureStore.typetest.ts @@ -10,14 +10,10 @@ import type { } from 'redux' import { applyMiddleware } from 'redux' import type { PayloadAction, ConfigureStoreOptions } from '@reduxjs/toolkit' -import { - configureStore, - createSlice, -} from '@reduxjs/toolkit' +import { configureStore, createSlice } from '@reduxjs/toolkit' import type { ThunkMiddleware, ThunkAction, ThunkDispatch } from 'redux-thunk' import { thunk } from 'redux-thunk' import { expectNotAny, expectType } from './helpers' -import { getDefaultMiddleware } from '@internal/getDefaultMiddleware' const _anyMiddleware: any = () => () => () => {} @@ -398,19 +394,6 @@ const _anyMiddleware: any = () => () => () => {} // @ts-expect-error store.dispatch(thunkB()) } - /** - * Test: using getDefaultMiddleware - */ - { - const store = configureStore({ - reducer: reducerA, - middleware: getDefaultMiddleware(), - }) - - store.dispatch(thunkA()) - // @ts-expect-error - store.dispatch(thunkB()) - } /** * Test: custom middleware */ @@ -479,66 +462,6 @@ const _anyMiddleware: any = () => () => () => {} store.dispatch(function () {} as ThunkAction) } - /** - * Test: custom middleware and getDefaultMiddleware - */ - { - const middleware = getDefaultMiddleware().prepend( - (() => {}) as any as Middleware<(a: 'a') => 'A', StateA> - ) - const store = configureStore({ - reducer: reducerA, - middleware, - }) - - const result1: 'A' = store.dispatch('a') - const result2: Promise<'A'> = store.dispatch(thunkA()) - // @ts-expect-error - store.dispatch(thunkB()) - } - - /** - * Test: custom middleware and getDefaultMiddleware, using prepend - */ - { - const otherMiddleware: Middleware<(a: 'a') => 'A', StateA> = _anyMiddleware - const concatenated = getDefaultMiddleware().prepend(otherMiddleware) - - expectType< - ReadonlyArray> - >(concatenated) - - const store = configureStore({ - reducer: reducerA, - middleware: concatenated, - }) - const result1: 'A' = store.dispatch('a') - const result2: Promise<'A'> = store.dispatch(thunkA()) - // @ts-expect-error - store.dispatch(thunkB()) - } - - /** - * Test: custom middleware and getDefaultMiddleware, using concat - */ - { - const otherMiddleware: Middleware<(a: 'a') => 'A', StateA> = _anyMiddleware - const concatenated = getDefaultMiddleware().concat(otherMiddleware) - - expectType< - ReadonlyArray> - >(concatenated) - - const store = configureStore({ - reducer: reducerA, - middleware: concatenated, - }) - const result1: 'A' = store.dispatch('a') - const result2: Promise<'A'> = store.dispatch(thunkA()) - // @ts-expect-error - store.dispatch(thunkB()) - } - /** * Test: middlewareBuilder notation, getDefaultMiddleware (unconfigured) */ From 8b2c55d4712f17fac85b1f483e992708c4da6001 Mon Sep 17 00:00:00 2001 From: "ben.durrant" Date: Wed, 12 Apr 2023 22:55:24 +0100 Subject: [PATCH 111/412] delete injectableCombineReducers.example.ts --- .../injectableCombineReducers.example.ts | 64 ------------------- 1 file changed, 64 deletions(-) delete mode 100644 packages/toolkit/src/tests/injectableCombineReducers.example.ts diff --git a/packages/toolkit/src/tests/injectableCombineReducers.example.ts b/packages/toolkit/src/tests/injectableCombineReducers.example.ts deleted file mode 100644 index d399f21f13..0000000000 --- a/packages/toolkit/src/tests/injectableCombineReducers.example.ts +++ /dev/null @@ -1,64 +0,0 @@ -/* eslint-disable import/first */ -// @ts-nocheck - -// reducer.ts or whatever - -import { combineSlices } from '@reduxjs/toolkit' - -import { sliceA } from 'fileA' -import { sliceB } from 'fileB' -import { lazySliceC } from 'fileC' -import type { lazySliceD } from 'fileD' - -import { anotherReducer } from 'somewhere' - -export interface LazyLoadedSlices {} - -export const rootReducer = combineSlices(sliceA, sliceB, { - another: anotherReducer, -}).withLazyLoadedSlices() -/* - results in a return type of - { - [sliceA.name]: SliceAState, - [sliceB.name]: SliceBState, - another: AnotherState, - [lazySliceC.name]?: SliceCState, // see fileC.ts to understand why this appears here - [lazySliceD.name]?: SliceDState, // see fileD.ts to understand why this appears here - } - */ - -// fileC.ts -// "naive" approach - -import type { RootState } from './reducer'; -import { rootReducer } from './reducer' -import { createSlice } from '@reduxjs/toolkit' - -interface SliceCState { - foo: string -} - -declare module './reducer' { - export interface LazyLoadedSlices { - [lazySliceC.name]: SliceCState - } -} - -export const lazySliceC = createSlice({ - /* ... */ -}) -/** - * Synchronously call `injectSlice` in file. - */ -rootReducer.injectSlice(lazySliceC) - -// might want to add code for HMR as well here - -// this will still error - `lazySliceC` is optional here -const naiveSelectFoo = (state: RootState) => state.lazySliceC.foo - -const selectFoo = rootReducer.withSlice(lazySliceC).selector((state) => { - // `lazySlice` is guaranteed to not be `undefined` here. - return state.lazySlice.foo -}) From c0c9e4f4dbb4fde22c545e21155c856f4a034d06 Mon Sep 17 00:00:00 2001 From: "ben.durrant" Date: Wed, 12 Apr 2023 23:39:47 +0100 Subject: [PATCH 112/412] simplify if condition --- packages/toolkit/src/configureStore.ts | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/packages/toolkit/src/configureStore.ts b/packages/toolkit/src/configureStore.ts index e99db9832b..6c168a2257 100644 --- a/packages/toolkit/src/configureStore.ts +++ b/packages/toolkit/src/configureStore.ts @@ -178,14 +178,9 @@ export function configureStore< } const getDefaultEnhancers = buildGetDefaultEnhancers(finalMiddleware) - let storeEnhancers: readonly StoreEnhancer[] - if (Array.isArray(enhancers)) { - // TODO: this matches the typing, but technically allows for a scenario where middlewares are specified but the applyMiddleware enhancer is never used - storeEnhancers = enhancers - } else if (typeof enhancers === 'function') { - storeEnhancers = enhancers(getDefaultEnhancers) - } else { - storeEnhancers = getDefaultEnhancers() + let storeEnhancers = enhancers ?? getDefaultEnhancers() + if (typeof storeEnhancers === 'function') { + storeEnhancers = storeEnhancers(getDefaultEnhancers) } const composedEnhancer: StoreEnhancer = finalCompose(...storeEnhancers) From bd88492bedd73e0dee9c4cdb6084b7fec27e4428 Mon Sep 17 00:00:00 2001 From: "ben.durrant" Date: Wed, 12 Apr 2023 23:41:55 +0100 Subject: [PATCH 113/412] add dev checks to enhancers --- packages/toolkit/src/configureStore.ts | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/packages/toolkit/src/configureStore.ts b/packages/toolkit/src/configureStore.ts index 6c168a2257..d7609ae112 100644 --- a/packages/toolkit/src/configureStore.ts +++ b/packages/toolkit/src/configureStore.ts @@ -181,6 +181,20 @@ export function configureStore< let storeEnhancers = enhancers ?? getDefaultEnhancers() if (typeof storeEnhancers === 'function') { storeEnhancers = storeEnhancers(getDefaultEnhancers) + + if (!IS_PRODUCTION && !Array.isArray(storeEnhancers)) { + throw new Error( + 'when using a enhancer builder function, an array of enhancers must be returned' + ) + } + } + if ( + !IS_PRODUCTION && + storeEnhancers.some((item: any) => typeof item !== 'function') + ) { + throw new Error( + 'each enhancer provided to configureStore must be a function' + ) } const composedEnhancer: StoreEnhancer = finalCompose(...storeEnhancers) From 0d95ab6cf68d35fd162ce29e0a5b247638a6d1f0 Mon Sep 17 00:00:00 2001 From: "ben.durrant" Date: Sat, 15 Apr 2023 14:24:01 +0100 Subject: [PATCH 114/412] added warning when final enhancers do not include applyMiddleware enhancer --- packages/toolkit/src/configureStore.ts | 17 ++++- packages/toolkit/src/createAction.ts | 4 +- packages/toolkit/src/getDefaultEnhancers.ts | 7 +- .../toolkit/src/tests/configureStore.test.ts | 65 ++++++++++++++----- 4 files changed, 68 insertions(+), 25 deletions(-) diff --git a/packages/toolkit/src/configureStore.ts b/packages/toolkit/src/configureStore.ts index d7609ae112..7dff458978 100644 --- a/packages/toolkit/src/configureStore.ts +++ b/packages/toolkit/src/configureStore.ts @@ -9,7 +9,7 @@ import type { PreloadedState, CombinedState, } from 'redux' -import { createStore, compose, combineReducers } from 'redux' +import { applyMiddleware, createStore, compose, combineReducers } from 'redux' import type { DevToolsEnhancerOptions as DevToolsOptions } from './devtoolsExtension' import { composeWithDevTools } from './devtoolsExtension' @@ -87,7 +87,7 @@ export interface ConfigureStoreOptions< * The store enhancers to apply. See Redux's `createStore()`. * All enhancers will be included before the DevTools Extension enhancer. * If you need to customize the order of enhancers, supply a callback - * function that will receieve a `getDefaultEnhancers` function that returns an EnhancerArray, + * function that will receive a `getDefaultEnhancers` function that returns an EnhancerArray, * and should return a new array (such as `getDefaultEnhancers().concat(offline)`). * If you only need to add middleware, you can use the `middleware` parameter instead. */ @@ -177,7 +177,9 @@ export function configureStore< }) } - const getDefaultEnhancers = buildGetDefaultEnhancers(finalMiddleware) + const middlewareEnhancer = applyMiddleware(...finalMiddleware) + + const getDefaultEnhancers = buildGetDefaultEnhancers(middlewareEnhancer) let storeEnhancers = enhancers ?? getDefaultEnhancers() if (typeof storeEnhancers === 'function') { storeEnhancers = storeEnhancers(getDefaultEnhancers) @@ -196,6 +198,15 @@ export function configureStore< 'each enhancer provided to configureStore must be a function' ) } + if ( + !IS_PRODUCTION && + finalMiddleware.length && + !storeEnhancers.includes(middlewareEnhancer) + ) { + console.error( + 'middlewares were provided, but middleware enhancer was not included in final enhancers' + ) + } const composedEnhancer: StoreEnhancer = finalCompose(...storeEnhancers) diff --git a/packages/toolkit/src/createAction.ts b/packages/toolkit/src/createAction.ts index 0647a04621..e7fd6ed065 100644 --- a/packages/toolkit/src/createAction.ts +++ b/packages/toolkit/src/createAction.ts @@ -224,7 +224,7 @@ export type PayloadActionCreator< * A utility function to create an action creator for the given action type * string. The action creator accepts a single argument, which will be included * in the action object as a field called payload. The action creator function - * will also have its toString() overriden so that it returns the action type, + * will also have its toString() overridden so that it returns the action type, * allowing it to be used in reducer logic that is looking for that action type. * * @param type The action type to use for created actions. @@ -241,7 +241,7 @@ export function createAction

( * A utility function to create an action creator for the given action type * string. The action creator accepts a single argument, which will be included * in the action object as a field called payload. The action creator function - * will also have its toString() overriden so that it returns the action type, + * will also have its toString() overridden so that it returns the action type, * allowing it to be used in reducer logic that is looking for that action type. * * @param type The action type to use for created actions. diff --git a/packages/toolkit/src/getDefaultEnhancers.ts b/packages/toolkit/src/getDefaultEnhancers.ts index 01104bfdcc..1b8b66ee5b 100644 --- a/packages/toolkit/src/getDefaultEnhancers.ts +++ b/packages/toolkit/src/getDefaultEnhancers.ts @@ -1,5 +1,4 @@ import type { StoreEnhancer } from 'redux' -import { applyMiddleware } from 'redux' import type { AutoBatchOptions } from './autoBatchEnhancer' import { autoBatchEnhancer } from './autoBatchEnhancer' import { EnhancerArray } from './utils' @@ -30,14 +29,12 @@ export type GetDefaultEnhancers> = < > export const buildGetDefaultEnhancers = >( - middlewares: M + middlewareEnhancer: StoreEnhancer<{ dispatch: ExtractDispatchExtensions }> ): GetDefaultEnhancers => function getDefaultEnhancers(options) { const { autoBatch = true } = options ?? {} - let enhancerArray = new EnhancerArray( - applyMiddleware(...middlewares) - ) + let enhancerArray = new EnhancerArray(middlewareEnhancer) if (autoBatch) { enhancerArray.push( autoBatchEnhancer(typeof autoBatch === 'object' ? autoBatch : undefined) diff --git a/packages/toolkit/src/tests/configureStore.test.ts b/packages/toolkit/src/tests/configureStore.test.ts index 266f38d133..ef1fe86efd 100644 --- a/packages/toolkit/src/tests/configureStore.test.ts +++ b/packages/toolkit/src/tests/configureStore.test.ts @@ -252,12 +252,27 @@ describe('configureStore', async () => { }) describe('given enhancers', () => { + let dummyEnhancerCalled = false + + const dummyEnhancer: StoreEnhancer = + (createStore) => (reducer, preloadedState) => { + dummyEnhancerCalled = true + + return createStore(reducer, preloadedState) + } + + beforeEach(() => { + dummyEnhancerCalled = false + }) + it('calls createStore with enhancers', () => { - const enhancer: Redux.StoreEnhancer = (next) => next - expect(configureStore({ enhancers: [enhancer], reducer })).toBeInstanceOf( - Object - ) - expect(redux.applyMiddleware).not.toHaveBeenCalled() + expect( + configureStore({ + enhancers: (gDE) => gDE().concat(dummyEnhancer), + reducer, + }) + ).toBeInstanceOf(Object) + expect(redux.applyMiddleware).toHaveBeenCalled() expect(mockDevtoolsCompose).toHaveBeenCalled() // @remap-prod-remove-line expect(redux.createStore).toHaveBeenCalledWith( reducer, @@ -267,25 +282,45 @@ describe('configureStore', async () => { }) it('accepts a callback for customizing enhancers', () => { - let dummyEnhancerCalled = false + const store = configureStore({ + reducer, + enhancers: (getDefaultEnhancers) => + getDefaultEnhancers().concat(dummyEnhancer), + }) - const dummyEnhancer: StoreEnhancer = - (createStore) => - (reducer, ...args: any[]) => { - dummyEnhancerCalled = true + expect(dummyEnhancerCalled).toBe(true) + }) - return createStore(reducer, ...args) - } + const consoleSpy = vi.spyOn(console, 'error').mockImplementation(() => {}) + beforeEach(() => { + consoleSpy.mockClear() + }) + afterAll(() => { + consoleSpy.mockRestore() + }) + + it('warns if middleware enhancer is excluded from final array when middlewares are provided', () => { + const store = configureStore({ + reducer, + enhancers: [dummyEnhancer], + }) - const reducer = () => ({}) + expect(dummyEnhancerCalled).toBe(true) + expect(consoleSpy).toHaveBeenCalledWith( + 'middlewares were provided, but middleware enhancer was not included in final enhancers' + ) + }) + it("doesn't warn when middleware enhancer is excluded if no middlewares provided", () => { const store = configureStore({ reducer, - enhancers: (getDefaultEnhancers) => - getDefaultEnhancers().concat(dummyEnhancer), + middleware: [], + enhancers: [dummyEnhancer], }) expect(dummyEnhancerCalled).toBe(true) + + expect(consoleSpy).not.toHaveBeenCalled() }) }) }) From 3762a94b850d2571bdfc7e72d227412fd76d23ca Mon Sep 17 00:00:00 2001 From: Mark Erikson Date: Sat, 15 Apr 2023 10:58:18 -0400 Subject: [PATCH 115/412] Add tsup --- packages/toolkit/package.json | 1 + yarn.lock | 182 +++++++++++++++++++++++++++++++--- 2 files changed, 171 insertions(+), 12 deletions(-) diff --git a/packages/toolkit/package.json b/packages/toolkit/package.json index bc46cc6fc9..0c40bdf7e7 100644 --- a/packages/toolkit/package.json +++ b/packages/toolkit/package.json @@ -89,6 +89,7 @@ "source-map": "^0.7.3", "terser": "^5.6.1", "tslib": "^1.10.0", + "tsup": "^6.7.0", "tsx": "^3.12.2", "typescript": "~4.9", "vitest": "^0.27.2", diff --git a/yarn.lock b/yarn.lock index 7e8417188d..a18e5d0971 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6791,6 +6791,7 @@ __metadata: source-map: ^0.7.3 terser: ^5.6.1 tslib: ^1.10.0 + tsup: ^6.7.0 tsx: ^3.12.2 typescript: ~4.9 vitest: ^0.27.2 @@ -9453,6 +9454,13 @@ __metadata: languageName: node linkType: hard +"any-promise@npm:^1.0.0": + version: 1.3.0 + resolution: "any-promise@npm:1.3.0" + checksum: 0ee8a9bdbe882c90464d75d1f55cf027f5458650c4bd1f0467e65aec38ccccda07ca5844969ee77ed46d04e7dded3eaceb027e8d32f385688523fe305fa7e1de + languageName: node + linkType: hard + "anymatch@npm:^2.0.0": version: 2.0.0 resolution: "anymatch@npm:2.0.0" @@ -10731,6 +10739,17 @@ __metadata: languageName: node linkType: hard +"bundle-require@npm:^4.0.0": + version: 4.0.1 + resolution: "bundle-require@npm:4.0.1" + dependencies: + load-tsconfig: ^0.2.3 + peerDependencies: + esbuild: ">=0.17" + checksum: 737217e37b72d7bee431b5d839b86ba604430f3ec346f073071de2ce65f0915189d4394ddd4685e0366b2930f38c95742b58c7101b8c53d9a8381d453f0b3b8a + languageName: node + linkType: hard + "bytes-iec@npm:^3.1.1": version: 3.1.1 resolution: "bytes-iec@npm:3.1.1" @@ -10752,7 +10771,7 @@ __metadata: languageName: node linkType: hard -"cac@npm:^6.7.14": +"cac@npm:^6.7.12, cac@npm:^6.7.14": version: 6.7.14 resolution: "cac@npm:6.7.14" checksum: 45a2496a9443abbe7f52a49b22fbe51b1905eff46e03fd5e6c98e3f85077be3f8949685a1849b1a9cd2bc3e5567dfebcf64f01ce01847baf918f1b37c839791a @@ -11706,6 +11725,13 @@ __metadata: languageName: node linkType: hard +"commander@npm:^4.0.0": + version: 4.1.1 + resolution: "commander@npm:4.1.1" + checksum: d7b9913ff92cae20cb577a4ac6fcc121bd6223319e54a40f51a14740a681ad5c574fd29a57da478a5f234a6fa6c52cbf0b7c641353e03c648b1ae85ba670b977 + languageName: node + linkType: hard + "commander@npm:^5.1.0": version: 5.1.0 resolution: "commander@npm:5.1.0" @@ -15726,6 +15752,20 @@ fsevents@^1.2.7: languageName: node linkType: hard +"glob@npm:7.1.6": + version: 7.1.6 + resolution: "glob@npm:7.1.6" + dependencies: + fs.realpath: ^1.0.0 + inflight: ^1.0.4 + inherits: 2 + minimatch: ^3.0.4 + once: ^1.3.0 + path-is-absolute: ^1.0.0 + checksum: 351d549dd90553b87c2d3f90ce11aed9e1093c74130440e7ae0592e11bbcd2ce7f0ebb8ba6bfe63aaf9b62166a7f4c80cb84490ae5d78408bb2572bf7d4ee0a6 + languageName: node + linkType: hard + "glob@npm:^7.0.0, glob@npm:^7.1.1, glob@npm:^7.1.2, glob@npm:^7.1.3, glob@npm:^7.1.4, glob@npm:^7.1.6": version: 7.1.7 resolution: "glob@npm:7.1.7" @@ -18937,6 +18977,13 @@ fsevents@^1.2.7: languageName: node linkType: hard +"joycon@npm:^3.0.1": + version: 3.1.1 + resolution: "joycon@npm:3.1.1" + checksum: 8003c9c3fc79c5c7602b1c7e9f7a2df2e9916f046b0dbad862aa589be78c15734d11beb9fe846f5e06138df22cb2ad29961b6a986ba81c4920ce2b15a7f11067 + languageName: node + linkType: hard + "js-levenshtein@npm:^1.1.6": version: 1.1.6 resolution: "js-levenshtein@npm:1.1.6" @@ -19509,6 +19556,13 @@ fsevents@^1.2.7: languageName: node linkType: hard +"load-tsconfig@npm:^0.2.3": + version: 0.2.5 + resolution: "load-tsconfig@npm:0.2.5" + checksum: 631740833c4a7157bb7b6eeae6e1afb6a6fac7416b7ba91bd0944d5c5198270af2d68bf8347af3cc2ba821adc4d83ef98f66278bd263bc284c863a09ec441503 + languageName: node + linkType: hard + "loader-runner@npm:^2.4.0": version: 2.4.0 resolution: "loader-runner@npm:2.4.0" @@ -20701,6 +20755,17 @@ fsevents@^1.2.7: languageName: node linkType: hard +"mz@npm:^2.7.0": + version: 2.7.0 + resolution: "mz@npm:2.7.0" + dependencies: + any-promise: ^1.0.0 + object-assign: ^4.0.1 + thenify-all: ^1.0.0 + checksum: 8427de0ece99a07e9faed3c0c6778820d7543e3776f9a84d22cf0ec0a8eb65f6e9aee9c9d353ff9a105ff62d33a9463c6ca638974cc652ee8140cd1e35951c87 + languageName: node + linkType: hard + "nan@npm:^2.12.1": version: 2.14.2 resolution: "nan@npm:2.14.2" @@ -21170,7 +21235,7 @@ fsevents@^1.2.7: languageName: node linkType: hard -"object-assign@npm:4.1.1, object-assign@npm:^4.1.0, object-assign@npm:^4.1.1": +"object-assign@npm:4.1.1, object-assign@npm:^4.0.1, object-assign@npm:^4.1.0, object-assign@npm:^4.1.1": version: 4.1.1 resolution: "object-assign@npm:4.1.1" checksum: fcc6e4ea8c7fe48abfbb552578b1c53e0d194086e2e6bbbf59e0a536381a292f39943c6e9628af05b5528aa5e3318bb30d6b2e53cadaf5b8fe9e12c4b69af23f @@ -22519,7 +22584,7 @@ fsevents@^1.2.7: languageName: node linkType: hard -"postcss-load-config@npm:^3.0.0, postcss-load-config@npm:^3.1.4": +"postcss-load-config@npm:^3.0.0, postcss-load-config@npm:^3.0.1, postcss-load-config@npm:^3.1.4": version: 3.1.4 resolution: "postcss-load-config@npm:3.1.4" dependencies: @@ -25321,6 +25386,20 @@ fsevents@^1.2.7: languageName: node linkType: hard +"rollup@npm:^3.2.5": + version: 3.20.2 + resolution: "rollup@npm:3.20.2" + dependencies: + fsevents: ~2.3.2 + dependenciesMeta: + fsevents: + optional: true + bin: + rollup: dist/bin/rollup + checksum: 34b0932839b7c2a5d1742fb21ce95a47e0b49a0849f4abee2dccf25833187aa7babb898ca90d4fc761cffa4102b9ed0ac6ad7f6f6b96c8b8e2d67305abc5da65 + languageName: node + linkType: hard + "rollup@npm:^3.7.0": version: 3.10.0 resolution: "rollup@npm:3.10.0" @@ -26363,6 +26442,15 @@ fsevents@^1.2.7: languageName: node linkType: hard +"source-map@npm:0.8.0-beta.0, source-map@npm:^0.8.0-beta.0": + version: 0.8.0-beta.0 + resolution: "source-map@npm:0.8.0-beta.0" + dependencies: + whatwg-url: ^7.0.0 + checksum: e94169be6461ab0ac0913313ad1719a14c60d402bd22b0ad96f4a6cffd79130d91ab5df0a5336a326b04d2df131c1409f563c9dc0d21a6ca6239a44b6c8dbd92 + languageName: node + linkType: hard + "source-map@npm:^0.5.6, source-map@npm:^0.5.7": version: 0.5.7 resolution: "source-map@npm:0.5.7" @@ -26377,15 +26465,6 @@ fsevents@^1.2.7: languageName: node linkType: hard -"source-map@npm:^0.8.0-beta.0": - version: 0.8.0-beta.0 - resolution: "source-map@npm:0.8.0-beta.0" - dependencies: - whatwg-url: ^7.0.0 - checksum: e94169be6461ab0ac0913313ad1719a14c60d402bd22b0ad96f4a6cffd79130d91ab5df0a5336a326b04d2df131c1409f563c9dc0d21a6ca6239a44b6c8dbd92 - languageName: node - linkType: hard - "sourcemap-codec@npm:^1.4.4": version: 1.4.8 resolution: "sourcemap-codec@npm:1.4.8" @@ -27002,6 +27081,24 @@ fsevents@^1.2.7: languageName: node linkType: hard +"sucrase@npm:^3.20.3": + version: 3.32.0 + resolution: "sucrase@npm:3.32.0" + dependencies: + "@jridgewell/gen-mapping": ^0.3.2 + commander: ^4.0.0 + glob: 7.1.6 + lines-and-columns: ^1.1.6 + mz: ^2.7.0 + pirates: ^4.0.1 + ts-interface-checker: ^0.1.9 + bin: + sucrase: bin/sucrase + sucrase-node: bin/sucrase-node + checksum: 79f760aef513adcf22b882d43100296a8afa7f307acef3e8803304b763484cf138a3e2cebc498a6791110ab20c7b8deba097f6ce82f812ca8f1723e3440e5c95 + languageName: node + linkType: hard + "supports-color@npm:^2.0.0": version: 2.0.0 resolution: "supports-color@npm:2.0.0" @@ -27393,6 +27490,24 @@ fsevents@^1.2.7: languageName: node linkType: hard +"thenify-all@npm:^1.0.0": + version: 1.6.0 + resolution: "thenify-all@npm:1.6.0" + dependencies: + thenify: ">= 3.1.0 < 4" + checksum: dba7cc8a23a154cdcb6acb7f51d61511c37a6b077ec5ab5da6e8b874272015937788402fd271fdfc5f187f8cb0948e38d0a42dcc89d554d731652ab458f5343e + languageName: node + linkType: hard + +"thenify@npm:>= 3.1.0 < 4": + version: 3.3.1 + resolution: "thenify@npm:3.3.1" + dependencies: + any-promise: ^1.0.0 + checksum: 84e1b804bfec49f3531215f17b4a6e50fd4397b5f7c1bccc427b9c656e1ecfb13ea79d899930184f78bc2f57285c54d9a50a590c8868f4f0cef5c1d9f898b05e + languageName: node + linkType: hard + "throat@npm:^6.0.1": version: 6.0.1 resolution: "throat@npm:6.0.1" @@ -27716,6 +27831,13 @@ fsevents@^1.2.7: languageName: node linkType: hard +"ts-interface-checker@npm:^0.1.9": + version: 0.1.13 + resolution: "ts-interface-checker@npm:0.1.13" + checksum: 20c29189c2dd6067a8775e07823ddf8d59a33e2ffc47a1bd59a5cb28bb0121a2969a816d5e77eda2ed85b18171aa5d1c4005a6b88ae8499ec7cc49f78571cb5e + languageName: node + linkType: hard + "ts-jest@npm:^27": version: 27.1.5 resolution: "ts-jest@npm:27.1.5" @@ -27896,6 +28018,42 @@ fsevents@^1.2.7: languageName: node linkType: hard +"tsup@npm:^6.7.0": + version: 6.7.0 + resolution: "tsup@npm:6.7.0" + dependencies: + bundle-require: ^4.0.0 + cac: ^6.7.12 + chokidar: ^3.5.1 + debug: ^4.3.1 + esbuild: ^0.17.6 + execa: ^5.0.0 + globby: ^11.0.3 + joycon: ^3.0.1 + postcss-load-config: ^3.0.1 + resolve-from: ^5.0.0 + rollup: ^3.2.5 + source-map: 0.8.0-beta.0 + sucrase: ^3.20.3 + tree-kill: ^1.2.2 + peerDependencies: + "@swc/core": ^1 + postcss: ^8.4.12 + typescript: ">=4.1.0" + peerDependenciesMeta: + "@swc/core": + optional: true + postcss: + optional: true + typescript: + optional: true + bin: + tsup: dist/cli-default.js + tsup-node: dist/cli-node.js + checksum: 91ff179f0b9828a6880b6decaa8603fd7af0311f46a38d3a93647a2497298750d676810aeff533a335443a01a7b340dbba7c76523bcd7a87d7b05b7677742901 + languageName: node + linkType: hard + "tsutils@npm:^3.21.0": version: 3.21.0 resolution: "tsutils@npm:3.21.0" From fc721370cf6700e4abc7295670a238adad36ba7c Mon Sep 17 00:00:00 2001 From: Mark Erikson Date: Sat, 15 Apr 2023 12:43:12 -0400 Subject: [PATCH 116/412] Switch build setup to use tsup --- packages/toolkit/package.json | 10 +- packages/toolkit/tsup.config.ts | 227 ++++++++++++++++++++++++++++++++ 2 files changed, 231 insertions(+), 6 deletions(-) create mode 100644 packages/toolkit/tsup.config.ts diff --git a/packages/toolkit/package.json b/packages/toolkit/package.json index 0c40bdf7e7..09b152f978 100644 --- a/packages/toolkit/package.json +++ b/packages/toolkit/package.json @@ -96,17 +96,15 @@ "yargs": "^15.3.1" }, "scripts": { - "run-build": "tsx ./scripts/build.ts", - "build-ci": "yarn rimraf dist && yarn tsc && yarn run-build --skipExtraction", - "build-prepare": "npm run build-ci", - "build": "yarn rimraf dist && echo Compiling... && yarn tsc && yarn run-build --local --skipExtraction", - "build-only": "yarn rimraf dist && yarn tsc && yarn run-build --skipExtraction", + "run-build": "tsup", + "build": "yarn rimraf dist && echo Compiling TS... && yarn tsc && yarn run-build", + "build-only": "yarn rimraf dist && yarn run-build", "format": "prettier --write \"(src|examples)/**/*.{ts,tsx}\" \"**/*.md\"", "format:check": "prettier --list-different \"(src|examples)/**/*.{ts,tsx}\" \"docs/*/**.md\"", "lint": "eslint src examples", "test": "vitest", "type-tests": "yarn tsc -p src/tests/tsconfig.typetests.json && yarn tsc -p src/query/tests/tsconfig.typetests.json", - "prepack": "npm run build-prepare" + "prepack": "yarn build" }, "files": [ "dist/", diff --git a/packages/toolkit/tsup.config.ts b/packages/toolkit/tsup.config.ts new file mode 100644 index 0000000000..a7195c0f73 --- /dev/null +++ b/packages/toolkit/tsup.config.ts @@ -0,0 +1,227 @@ +import { fileURLToPath } from 'url' +import path from 'path' +import fs from 'fs' +import rimraf from 'rimraf' +import { BuildOptions as ESBuildOptions } from 'esbuild' +import type { Options as TsupOptions } from 'tsup' +import { defineConfig } from 'tsup' + +import { delay } from './src/utils' + +// No __dirname under Node ESM +const __filename = fileURLToPath(import.meta.url) +const __dirname = path.dirname(__filename) + +const outputDir = path.join(__dirname, 'dist') + +export interface BuildOptions { + format: 'cjs' | 'umd' | 'esm' + name: + | 'development' + | 'production.min' + | 'legacy-esm' + | 'modern' + | 'modern.development' + | 'modern.production.min' + | 'umd' + | 'umd.min' + minify: boolean + env: 'development' | 'production' | '' + target?: + | 'es2017' + | 'es2018' + | 'es2019' + | 'es2020' + | 'es2021' + | 'es2022' + | 'esnext' +} + +export interface EntryPointOptions { + prefix: string + folder: string + entryPoint: string + extractionConfig: string + externals?: string[] +} + +const buildTargets: BuildOptions[] = [ + { + format: 'cjs', + name: 'development', + target: 'esnext', + minify: false, + env: 'development', + }, + { + format: 'cjs', + name: 'production.min', + target: 'esnext', + minify: true, + env: 'production', + }, + // ESM, embedded `process`: modern Webpack dev + { + format: 'esm', + name: 'modern', + target: 'esnext', + minify: false, + env: '', + }, + // ESM, embedded `process`: fallback for Webpack 4, + // which doesn't support `exports` field or optional chaining + { + format: 'esm', + name: 'legacy-esm', + target: 'esnext', + minify: false, + env: '', + }, + // ESM, pre-compiled "dev": browser development + { + format: 'esm', + name: 'modern.development', + target: 'esnext', + minify: false, + env: 'development', + }, + // ESM, pre-compiled "prod": browser prod + { + format: 'esm', + name: 'modern.production.min', + target: 'esnext', + minify: true, + env: 'production', + }, + // { + // format: 'umd', + // name: 'umd', + // target: 'es2018', + // minify: false, + // env: 'development', + // }, + // { + // format: 'umd', + // name: 'umd.min', + // target: 'es2018', + // minify: true, + // env: 'production', + // }, +] + +const entryPoints: EntryPointOptions[] = [ + { + prefix: 'redux-toolkit', + folder: '', + entryPoint: 'src/index.ts', + extractionConfig: 'api-extractor.json', + }, + { + prefix: 'rtk-query', + folder: 'query', + entryPoint: 'src/query/index.ts', + extractionConfig: 'api-extractor.query.json', + externals: ['redux', '@reduxjs/toolkit'], + }, + { + prefix: 'rtk-query-react', + folder: 'query/react', + entryPoint: 'src/query/react/index.ts', + extractionConfig: 'api-extractor.query-react.json', + externals: ['redux', '@reduxjs/toolkit'], + }, +] + +function writeCommonJSEntry(folder: string, prefix: string) { + fs.writeFileSync( + path.join(folder, 'index.js'), + `'use strict' +if (process.env.NODE_ENV === 'production') { + module.exports = require('./${prefix}.production.min.cjs') +} else { + module.exports = require('./${prefix}.development.cjs') +}` + ) +} + +export default defineConfig((options) => { + const configs = entryPoints + .map((entryPointConfig) => { + const artifactOptions: TsupOptions[] = buildTargets.map((buildTarget) => { + const { prefix, folder, entryPoint, externals } = entryPointConfig + const { format, minify, env, name, target } = buildTarget + const outputFilename = `${prefix}.${name}` + + const folderSegments = [outputDir, folder] + if (format === 'cjs') { + folderSegments.push('cjs') + } + + const outputFolder = path.join(...folderSegments) + + const extension = + name === 'legacy-esm' ? '.js' : format === 'esm' ? '.mjs' : '.cjs' + + const defineValues: Record = {} + + if (env) { + Object.assign(defineValues, { + 'process.env.NODE_ENV': JSON.stringify(env), + }) + } + + const generateTypedefs = name === 'modern' && format === 'esm' + + return { + entry: { + [outputFilename]: entryPoint, + }, + format, + outDir: outputFolder, + target, + outExtension: () => ({ js: extension }), + minify, + sourcemap: true, + external: externals, + esbuildOptions(options) { + // Needed to prevent auto-replacing of process.env.NODE_ENV in all builds + options.platform = 'neutral' + // Needed to return to normal lookup behavior when platform: 'neutral' + options.mainFields = ['browser', 'module', 'main'] + options.conditions = ['browser'] + }, + + define: defineValues, + async onSuccess() { + if (format === 'cjs' && name === 'production.min') { + writeCommonJSEntry(outputFolder, prefix) + } else if (generateTypedefs) { + // TODO Copy/generate `.d.mts` files? + // const inputTypedefsFile = `${outputFilename}.d.ts` + // const outputTypedefsFile = `${outputFilename}.d.mts` + // const inputTypedefsPath = path.join( + // outputFolder, + // inputTypedefsFile + // ) + // const outputTypedefsPath = path.join( + // outputFolder, + // outputTypedefsFile + // ) + // while (!fs.existsSync(inputTypedefsPath)) { + // // console.log( + // // 'Waiting for typedefs to be generated: ' + inputTypedefsFile + // // ) + // await delay(100) + // } + // fs.copyFileSync(inputTypedefsPath, outputTypedefsPath) + } + }, + } + }) + + return artifactOptions + }) + .flat() + + return configs +}) From af095a31061afdb192b862929642c141a7488667 Mon Sep 17 00:00:00 2001 From: Mark Erikson Date: Sat, 15 Apr 2023 23:08:30 -0400 Subject: [PATCH 117/412] Remove dead build scripts --- packages/toolkit/scripts/build.ts | 397 -------------------------- packages/toolkit/scripts/moduld.d.ts | 7 - packages/toolkit/scripts/sourcemap.ts | 47 --- packages/toolkit/scripts/types.ts | 31 -- 4 files changed, 482 deletions(-) delete mode 100644 packages/toolkit/scripts/build.ts delete mode 100644 packages/toolkit/scripts/moduld.d.ts delete mode 100644 packages/toolkit/scripts/sourcemap.ts delete mode 100644 packages/toolkit/scripts/types.ts diff --git a/packages/toolkit/scripts/build.ts b/packages/toolkit/scripts/build.ts deleted file mode 100644 index c11fa693c1..0000000000 --- a/packages/toolkit/scripts/build.ts +++ /dev/null @@ -1,397 +0,0 @@ -/* eslint-disable import/first */ -import { fileURLToPath } from 'url' - -// @ts-check -import { build } from 'esbuild' -import { minify as terserMinify } from 'terser' -import { rollup } from 'rollup' -import path from 'path' -import fs from 'fs-extra' -import ts from 'typescript' -import type { RawSourceMap } from 'source-map' -import merge from 'merge-source-map' -import type { ExtractorResult } from '@microsoft/api-extractor' -import { Extractor, ExtractorConfig } from '@microsoft/api-extractor' -import yargs from 'yargs/yargs' - -import { extractInlineSourcemap, removeInlineSourceMap } from './sourcemap' -import type { BuildOptions, EntryPointOptions } from './types' -import { appendInlineSourceMap, getLocation } from './sourcemap' - -// No __dirname under Node ESM -const __filename = fileURLToPath(import.meta.url) -const __dirname = path.dirname(__filename) - -const { argv } = yargs(process.argv) - .option('local', { - alias: 'l', - type: 'boolean', - description: 'Run API Extractor in local mode', - }) - .option('skipExtraction', { - alias: 's', - type: 'boolean', - description: 'Skip running API extractor', - }) - -const outputDir = path.join(__dirname, '../dist') - -const buildTargets: BuildOptions[] = [ - { - format: 'cjs', - name: 'development', - target: 'esnext', - minify: false, - env: 'development', - }, - { - format: 'cjs', - name: 'production.min', - target: 'esnext', - minify: true, - env: 'production', - }, - // ESM, embedded `process`: modern Webpack dev - { - format: 'esm', - name: 'modern', - target: 'esnext', - minify: false, - env: '', - }, - // ESM, embedded `process`: fallback for Webpack 4, - // which doesn't support `exports` field or optional chaining - { - format: 'esm', - name: 'legacy-esm', - target: 'esnext', - minify: false, - env: '', - }, - // ESM, pre-compiled "dev": browser development - { - format: 'esm', - name: 'modern.development', - target: 'esnext', - minify: false, - env: 'development', - }, - // ESM, pre-compiled "prod": browser prod - { - format: 'esm', - name: 'modern.production.min', - target: 'esnext', - minify: true, - env: 'production', - }, - // { - // format: 'umd', - // name: 'umd', - // target: 'es2018', - // minify: false, - // env: 'development', - // }, - // { - // format: 'umd', - // name: 'umd.min', - // target: 'es2018', - // minify: true, - // env: 'production', - // }, -] - -const entryPoints: EntryPointOptions[] = [ - { - prefix: 'redux-toolkit', - folder: '', - entryPoint: 'src/index.ts', - extractionConfig: 'api-extractor.json', - globalName: 'RTK', - }, - { - prefix: 'rtk-query', - folder: 'query', - entryPoint: 'src/query/index.ts', - extractionConfig: 'api-extractor.query.json', - globalName: 'RTKQ', - }, - { - prefix: 'rtk-query-react', - folder: 'query/react', - entryPoint: 'src/query/react/index.ts', - extractionConfig: 'api-extractor.query-react.json', - globalName: 'RTKQ', - }, -] - -const esVersionMappings = { - // Don't output ES2015 - have TS convert to ES5 instead - es2015: ts.ScriptTarget.ES5, - es2017: ts.ScriptTarget.ES2017, - es2018: ts.ScriptTarget.ES2018, - es2019: ts.ScriptTarget.ES2019, - es2020: ts.ScriptTarget.ES2020, - es2021: ts.ScriptTarget.ES2021, - es2022: ts.ScriptTarget.ES2022, - esnext: ts.ScriptTarget.ESNext, -} - -async function bundle(options: BuildOptions & EntryPointOptions) { - const { - format, - minify, - env, - folder = '', - prefix = 'redux-toolkit', - name, - target = 'es2015', - entryPoint, - } = options - - const folderSegments = [outputDir, folder] - - if (format === 'cjs') { - folderSegments.push('cjs') - } - - const extension = - name === 'legacy-esm' ? 'js' : format === 'esm' ? 'mjs' : 'cjs' - - const outputFolder = path.join(...folderSegments) - const outputFilename = `${prefix}.${name}.${extension}` - - await fs.ensureDir(outputFolder) - - const outputFilePath = path.join(outputFolder, outputFilename) - - if (format === 'cjs') { - await writeCommonJSEntry(outputFolder, prefix) - } - - const result = await build({ - entryPoints: [entryPoint], - outfile: outputFilePath, - write: false, - target: target, - sourcemap: 'inline', - bundle: true, - format: format === 'umd' ? 'esm' : format, - // Needed to prevent auto-replacing of process.env.NODE_ENV in all builds - platform: 'neutral', - // Needed to return to normal lookup behavior when platform: 'neutral' - mainFields: ['browser', 'module', 'main'], - conditions: ['browser'], - define: env - ? { - 'process.env.NODE_ENV': JSON.stringify(env), - } - : {}, - plugins: [ - { - name: 'node_module_external', - setup(build) { - build.onResolve({ filter: /.*/ }, (args) => { - if (format === 'umd') { - return - } - if (args.path.startsWith('.') || args.path.startsWith('/')) { - return undefined - } else { - return { - path: args.path, - external: true, - } - } - }) - build.onLoad({ filter: /getDefaultMiddleware/ }, async (args) => { - if (env !== 'production' || format !== 'umd') { - return - } - const source = await fs.readFile(args.path, 'utf-8') - const defaultPattern = - /\/\* PROD_START_REMOVE_UMD[\s\S]*?\/\* PROD_STOP_REMOVE_UMD \*\//g - const code = source.replace(defaultPattern, '') - return { - contents: code, - loader: 'ts', - } - }) - }, - }, - ], - }) - - for (const chunk of result.outputFiles!) { - const esVersion = - target in esVersionMappings - ? esVersionMappings[target] - : ts.ScriptTarget.ES2017 - - const origin = chunk.text - const sourcemap = extractInlineSourcemap(origin) - const result = ts.transpileModule(removeInlineSourceMap(origin), { - fileName: chunk.path, - compilerOptions: { - sourceMap: true, - module: - format !== 'cjs' ? ts.ModuleKind.ES2015 : ts.ModuleKind.CommonJS, - target: esVersion, - }, - }) - - const mergedSourcemap = merge(sourcemap, result.sourceMapText) - let code = result.outputText - let mapping: RawSourceMap = mergedSourcemap - - if (minify) { - const transformResult = await terserMinify( - appendInlineSourceMap(code, mapping), - { - sourceMap: { - content: 'inline', - asObject: true, - url: path.basename(chunk.path) + '.map', - } as any, - output: { - comments: false, - }, - compress: { - keep_infinity: true, - pure_getters: true, - passes: 10, - }, - ecma: 5, - toplevel: true, - } - ) - code = transformResult.code! - mapping = transformResult.map as RawSourceMap - } - - const relativePath = path.relative(process.cwd(), chunk.path) - await fs.writeFile(chunk.path, code) - await fs.writeJSON(chunk.path + '.map', mapping) - - if (!chunk.path.includes('.map')) { - console.log(`Build artifact: ${relativePath}, settings: `, { - target, - }) - } - } -} - -/** - * since esbuild doesn't support umd, we use rollup to convert esm to umd - */ -async function buildUMD( - outputPath: string, - prefix: string, - globalName: string -) { - for (let umdExtension of ['umd', 'umd.min']) { - const input = path.join(outputPath, `${prefix}.${umdExtension}.js`) - const instance = await rollup({ - input: [input], - onwarn(warning, warn) { - if (warning.code === 'THIS_IS_UNDEFINED') return - warn(warning) // this requires Rollup 0.46 - }, - }) - await instance.write({ - format: 'umd', - name: globalName, - file: input, - sourcemap: true, - globals: { - // These packages have specific global names from their UMD bundles - react: 'React', - 'react-redux': 'ReactRedux', - '@reduxjs/toolkit': 'RTK', - }, - }) - } -} - -// Generates an index file to handle importing CJS dev/prod -async function writeCommonJSEntry(folder: string, prefix: string) { - await fs.writeFile( - path.join(folder, 'index.js'), - `'use strict' -if (process.env.NODE_ENV === 'production') { - module.exports = require('./${prefix}.production.min.cjs') -} else { - module.exports = require('./${prefix}.development.cjs') -}` - ) - - await fs.writeFile(path.join(folder, 'package.json'), `{"type": "commonjs"}`) -} - -interface BuildArgs { - skipExtraction?: boolean - local: boolean -} - -async function main({ skipExtraction = false, local = false }: BuildArgs) { - // Dist folder will be removed by rimraf beforehand so TSC can generate typedefs - await fs.ensureDir(outputDir) - - for (let entryPoint of entryPoints) { - const { folder, prefix } = entryPoint - const outputPath = path.join('dist', folder) - fs.ensureDirSync(outputPath) - - // Run builds in parallel - const bundlePromises = buildTargets.map((options) => - bundle({ - ...options, - ...entryPoint, - }) - ) - await Promise.all(bundlePromises) - } - - // Run UMD builds after everything else so we don't have to sleep after each set - for (let entryPoint of entryPoints) { - const { folder } = entryPoint - const outputPath = path.join('dist', folder) - // await buildUMD(outputPath, entryPoint.prefix, entryPoint.globalName) - } - - if (!skipExtraction) { - for (let entryPoint of entryPoints) { - try { - // Load and parse the api-extractor.json file - const extractorConfig: ExtractorConfig = - ExtractorConfig.loadFileAndPrepare(entryPoint.extractionConfig) - - console.log('Extracting API types for entry point: ', entryPoint.prefix) - // Invoke API Extractor - const extractorResult: ExtractorResult = Extractor.invoke( - extractorConfig, - { - // Equivalent to the "--local" command-line parameter - localBuild: local, - - // Equivalent to the "--verbose" command-line parameter - showVerboseMessages: false, - } - ) - - if (extractorResult.succeeded) { - console.log(`API Extractor completed successfully`) - } else { - console.error( - `API Extractor completed with ${extractorResult.errorCount} errors` + - ` and ${extractorResult.warningCount} warnings` - ) - } - } catch (e) { - console.error('API extractor crashed: ', e) - } - } - } -} - -const { skipExtraction, local } = argv -main({ skipExtraction, local }) diff --git a/packages/toolkit/scripts/moduld.d.ts b/packages/toolkit/scripts/moduld.d.ts deleted file mode 100644 index f101637272..0000000000 --- a/packages/toolkit/scripts/moduld.d.ts +++ /dev/null @@ -1,7 +0,0 @@ -declare module 'merge-source-map' { - import type { RawSourceMap } from 'source-map' - export default function merge( - map1: string | RawSourceMap, - map2: string | RawSourceMap - ): RawSourceMap -} diff --git a/packages/toolkit/scripts/sourcemap.ts b/packages/toolkit/scripts/sourcemap.ts deleted file mode 100644 index e1b09f8333..0000000000 --- a/packages/toolkit/scripts/sourcemap.ts +++ /dev/null @@ -1,47 +0,0 @@ -import { fromObject, fromComment } from 'convert-source-map' -const SOURCEMAPPING_URL = 'sourceMappingURL' - -const SOURCEMAP_REG = new RegExp( - `^\\/\\/#\\s+${SOURCEMAPPING_URL}=.+\\n?`, - 'gm' -) -function appendInlineSourceMap(code: string, sourceMap: any) { - if (sourceMap) { - const mapping = fromObject(sourceMap) - return `${code}\n${mapping.toComment()}` - } else { - return code - } -} -function removeInlineSourceMap(code) { - return code.replace( - new RegExp(`^\\/\\/#\\s+${SOURCEMAPPING_URL}=.+\\n?`, 'gm'), - '' - ) -} -function extractInlineSourcemap(code: string) { - return fromComment(code.match(SOURCEMAP_REG)?.[0]).toObject() -} -function getLocation( - source: string, - search: string -): { line: number; column: number } { - const outIndex = source.indexOf(search) - if (outIndex < 0) { - throw new Error(`Failed to find ${search} in output`) - } - const outLines = source.slice(0, outIndex).split('\n') - const outLine = outLines.length - const outColumn = outLines[outLines.length - 1].length - return { - line: outLine, - column: outColumn, - } -} - -export { - extractInlineSourcemap, - removeInlineSourceMap, - appendInlineSourceMap, - getLocation, -} diff --git a/packages/toolkit/scripts/types.ts b/packages/toolkit/scripts/types.ts deleted file mode 100644 index 08bac037e4..0000000000 --- a/packages/toolkit/scripts/types.ts +++ /dev/null @@ -1,31 +0,0 @@ -export interface BuildOptions { - format: 'cjs' | 'umd' | 'esm' - name: - | 'development' - | 'production.min' - | 'legacy-esm' - | 'modern' - | 'modern.development' - | 'modern.production.min' - | 'umd' - | 'umd.min' - minify: boolean - env: 'development' | 'production' | '' - target?: - | 'es2017' - | 'es2018' - | 'es2019' - | 'es2020' - | 'es2021' - | 'es2022' - | 'esnext' -} - -export interface EntryPointOptions { - prefix: string - folder: string - entryPoint: string - extractionConfig: string - // globalName is used in the conversion to umd files to separate rtk from rtk-query on a global namespace - globalName: string -} From a32e201cbb6a9be9bd13477151f15f8c065bff74 Mon Sep 17 00:00:00 2001 From: Mark Erikson Date: Sat, 15 Apr 2023 12:43:25 -0400 Subject: [PATCH 118/412] Restructure dev check middleware logic to enable better minification --- .../src/immutableStateInvariantMiddleware.ts | 162 +++++++++--------- .../serializableStateInvariantMiddleware.ts | 153 +++++++++-------- 2 files changed, 158 insertions(+), 157 deletions(-) diff --git a/packages/toolkit/src/immutableStateInvariantMiddleware.ts b/packages/toolkit/src/immutableStateInvariantMiddleware.ts index 69f42b67b6..7230f16394 100644 --- a/packages/toolkit/src/immutableStateInvariantMiddleware.ts +++ b/packages/toolkit/src/immutableStateInvariantMiddleware.ts @@ -25,42 +25,6 @@ function invariant(condition: any, message?: string) { throw new Error(`${prefix}: ${message || ''}`) } -function stringify( - obj: any, - serializer?: EntryProcessor, - indent?: string | number, - decycler?: EntryProcessor -): string { - return JSON.stringify(obj, getSerialize(serializer, decycler), indent) -} - -function getSerialize( - serializer?: EntryProcessor, - decycler?: EntryProcessor -): EntryProcessor { - let stack: any[] = [], - keys: any[] = [] - - if (!decycler) - decycler = function (_: string, value: any) { - if (stack[0] === value) return '[Circular ~]' - return ( - '[Circular ~.' + keys.slice(0, stack.indexOf(value)).join('.') + ']' - ) - } - - return function (this: any, key: string, value: any) { - if (stack.length > 0) { - var thisPos = stack.indexOf(this) - ~thisPos ? stack.splice(thisPos + 1) : stack.push(this) - ~thisPos ? keys.splice(thisPos, Infinity, key) : keys.push(key) - if (~stack.indexOf(value)) value = decycler!.call(this, key, value) - } else stack.push(value) - - return serializer == null ? value : serializer.call(this, key, value) - } -} - /** * The default `isImmutable` function. * @@ -221,69 +185,105 @@ export function createImmutableStateInvariantMiddleware( ): Middleware { if (process.env.NODE_ENV === 'production') { return () => (next) => (action) => next(action) - } + } else { + function stringify( + obj: any, + serializer?: EntryProcessor, + indent?: string | number, + decycler?: EntryProcessor + ): string { + return JSON.stringify(obj, getSerialize(serializer, decycler), indent) + } - let { - isImmutable = isImmutableDefault, - ignoredPaths, - warnAfter = 32, - ignore, - } = options + function getSerialize( + serializer?: EntryProcessor, + decycler?: EntryProcessor + ): EntryProcessor { + let stack: any[] = [], + keys: any[] = [] + + if (!decycler) + decycler = function (_: string, value: any) { + if (stack[0] === value) return '[Circular ~]' + return ( + '[Circular ~.' + keys.slice(0, stack.indexOf(value)).join('.') + ']' + ) + } - // Alias ignore->ignoredPaths, but prefer ignoredPaths if present - ignoredPaths = ignoredPaths || ignore + return function (this: any, key: string, value: any) { + if (stack.length > 0) { + var thisPos = stack.indexOf(this) + ~thisPos ? stack.splice(thisPos + 1) : stack.push(this) + ~thisPos ? keys.splice(thisPos, Infinity, key) : keys.push(key) + if (~stack.indexOf(value)) value = decycler!.call(this, key, value) + } else stack.push(value) - const track = trackForMutations.bind(null, isImmutable, ignoredPaths) + return serializer == null ? value : serializer.call(this, key, value) + } + } - return ({ getState }) => { - let state = getState() - let tracker = track(state) + let { + isImmutable = isImmutableDefault, + ignoredPaths, + warnAfter = 32, + ignore, + } = options - let result - return (next) => (action) => { - const measureUtils = getTimeMeasureUtils( - warnAfter, - 'ImmutableStateInvariantMiddleware' - ) + // Alias ignore->ignoredPaths, but prefer ignoredPaths if present + ignoredPaths = ignoredPaths || ignore - measureUtils.measureTime(() => { - state = getState() + const track = trackForMutations.bind(null, isImmutable, ignoredPaths) - result = tracker.detectMutations() - // Track before potentially not meeting the invariant - tracker = track(state) + return ({ getState }) => { + let state = getState() + let tracker = track(state) - invariant( - !result.wasMutated, - `A state mutation was detected between dispatches, in the path '${ - result.path || '' - }'. This may cause incorrect behavior. (https://redux.js.org/style-guide/style-guide#do-not-mutate-state)` + let result + return (next) => (action) => { + const measureUtils = getTimeMeasureUtils( + warnAfter, + 'ImmutableStateInvariantMiddleware' ) - }) - - const dispatchedAction = next(action) - measureUtils.measureTime(() => { - state = getState() + measureUtils.measureTime(() => { + state = getState() - result = tracker.detectMutations() - // Track before potentially not meeting the invariant - tracker = track(state) + result = tracker.detectMutations() + // Track before potentially not meeting the invariant + tracker = track(state) - result.wasMutated && invariant( !result.wasMutated, - `A state mutation was detected inside a dispatch, in the path: ${ + `A state mutation was detected between dispatches, in the path '${ result.path || '' - }. Take a look at the reducer(s) handling the action ${stringify( - action - )}. (https://redux.js.org/style-guide/style-guide#do-not-mutate-state)` + }'. This may cause incorrect behavior. (https://redux.js.org/style-guide/style-guide#do-not-mutate-state)` ) - }) + }) + + const dispatchedAction = next(action) + + measureUtils.measureTime(() => { + state = getState() - measureUtils.warnIfExceeded() + result = tracker.detectMutations() + // Track before potentially not meeting the invariant + tracker = track(state) - return dispatchedAction + result.wasMutated && + invariant( + !result.wasMutated, + `A state mutation was detected inside a dispatch, in the path: ${ + result.path || '' + }. Take a look at the reducer(s) handling the action ${stringify( + action + )}. (https://redux.js.org/style-guide/style-guide#do-not-mutate-state)` + ) + }) + + measureUtils.warnIfExceeded() + + return dispatchedAction + } } } } diff --git a/packages/toolkit/src/serializableStateInvariantMiddleware.ts b/packages/toolkit/src/serializableStateInvariantMiddleware.ts index 7c6ffb34a2..0026091fe6 100644 --- a/packages/toolkit/src/serializableStateInvariantMiddleware.ts +++ b/packages/toolkit/src/serializableStateInvariantMiddleware.ts @@ -190,88 +190,89 @@ export function createSerializableStateInvariantMiddleware( ): Middleware { if (process.env.NODE_ENV === 'production') { return () => (next) => (action) => next(action) - } - const { - isSerializable = isPlain, - getEntries, - ignoredActions = [], - ignoredActionPaths = ['meta.arg', 'meta.baseQueryMeta'], - ignoredPaths = [], - warnAfter = 32, - ignoreState = false, - ignoreActions = false, - disableCache = false, - } = options - - const cache: WeakSet | undefined = - !disableCache && WeakSet ? new WeakSet() : undefined - - return (storeAPI) => (next) => (action) => { - const result = next(action) - - const measureUtils = getTimeMeasureUtils( - warnAfter, - 'SerializableStateInvariantMiddleware' - ) - - if ( - !ignoreActions && - !(ignoredActions.length && ignoredActions.indexOf(action.type) !== -1) - ) { - measureUtils.measureTime(() => { - const foundActionNonSerializableValue = findNonSerializableValue( - action, - '', - isSerializable, - getEntries, - ignoredActionPaths, - cache - ) - - if (foundActionNonSerializableValue) { - const { keyPath, value } = foundActionNonSerializableValue - - console.error( - `A non-serializable value was detected in an action, in the path: \`${keyPath}\`. Value:`, - value, - '\nTake a look at the logic that dispatched this action: ', + } else { + const { + isSerializable = isPlain, + getEntries, + ignoredActions = [], + ignoredActionPaths = ['meta.arg', 'meta.baseQueryMeta'], + ignoredPaths = [], + warnAfter = 32, + ignoreState = false, + ignoreActions = false, + disableCache = false, + } = options + + const cache: WeakSet | undefined = + !disableCache && WeakSet ? new WeakSet() : undefined + + return (storeAPI) => (next) => (action) => { + const result = next(action) + + const measureUtils = getTimeMeasureUtils( + warnAfter, + 'SerializableStateInvariantMiddleware' + ) + + if ( + !ignoreActions && + !(ignoredActions.length && ignoredActions.indexOf(action.type) !== -1) + ) { + measureUtils.measureTime(() => { + const foundActionNonSerializableValue = findNonSerializableValue( action, - '\n(See https://redux.js.org/faq/actions#why-should-type-be-a-string-or-at-least-serializable-why-should-my-action-types-be-constants)', - '\n(To allow non-serializable values see: https://redux-toolkit.js.org/usage/usage-guide#working-with-non-serializable-data)' + '', + isSerializable, + getEntries, + ignoredActionPaths, + cache ) - } - }) - } - if (!ignoreState) { - measureUtils.measureTime(() => { - const state = storeAPI.getState() - - const foundStateNonSerializableValue = findNonSerializableValue( - state, - '', - isSerializable, - getEntries, - ignoredPaths, - cache - ) - - if (foundStateNonSerializableValue) { - const { keyPath, value } = foundStateNonSerializableValue - - console.error( - `A non-serializable value was detected in the state, in the path: \`${keyPath}\`. Value:`, - value, - ` + if (foundActionNonSerializableValue) { + const { keyPath, value } = foundActionNonSerializableValue + + console.error( + `A non-serializable value was detected in an action, in the path: \`${keyPath}\`. Value:`, + value, + '\nTake a look at the logic that dispatched this action: ', + action, + '\n(See https://redux.js.org/faq/actions#why-should-type-be-a-string-or-at-least-serializable-why-should-my-action-types-be-constants)', + '\n(To allow non-serializable values see: https://redux-toolkit.js.org/usage/usage-guide#working-with-non-serializable-data)' + ) + } + }) + } + + if (!ignoreState) { + measureUtils.measureTime(() => { + const state = storeAPI.getState() + + const foundStateNonSerializableValue = findNonSerializableValue( + state, + '', + isSerializable, + getEntries, + ignoredPaths, + cache + ) + + if (foundStateNonSerializableValue) { + const { keyPath, value } = foundStateNonSerializableValue + + console.error( + `A non-serializable value was detected in the state, in the path: \`${keyPath}\`. Value:`, + value, + ` Take a look at the reducer(s) handling this action type: ${action.type}. (See https://redux.js.org/faq/organizing-state#can-i-put-functions-promises-or-other-non-serializable-items-in-my-store-state)` - ) - } - }) + ) + } + }) - measureUtils.warnIfExceeded() - } + measureUtils.warnIfExceeded() + } - return result + return result + } } } From 8249c1898cad9151d1db15002395888cf7aee582 Mon Sep 17 00:00:00 2001 From: Mark Erikson Date: Sat, 15 Apr 2023 23:23:12 -0400 Subject: [PATCH 119/412] Add CRA5 example to CSB examples list --- .codesandbox/ci.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.codesandbox/ci.json b/.codesandbox/ci.json index 69146d63ba..61b268432c 100644 --- a/.codesandbox/ci.json +++ b/.codesandbox/ci.json @@ -5,7 +5,8 @@ "github/reduxjs/rtk-github-issues-example", "/examples/query/react/basic", "/examples/query/react/advanced", - "/examples/action-listener/counter" + "/examples/action-listener/counter", + "/examples/publish-ci/cra5" ], "node": "14", "buildCommand": "build:packages", From 9cb8f845bbe023ae179ce94ea0e2f9315fd3b63c Mon Sep 17 00:00:00 2001 From: Ben Durrant Date: Sun, 16 Apr 2023 16:35:19 +0100 Subject: [PATCH 120/412] prefer UnknownAction and Action to AnyAction --- docs/api/configureStore.mdx | 9 ++- docs/api/createListenerMiddleware.mdx | 12 +-- docs/api/matching-utilities.mdx | 25 +++--- docs/rtk-query/api/createApi.mdx | 4 +- .../api/created-api/api-slice-utils.mdx | 54 +++++++------ docs/rtk-query/api/created-api/endpoints.mdx | 11 +-- docs/usage/usage-with-typescript.md | 2 +- .../counter/src/services/counter/listeners.ts | 4 +- packages/toolkit/etc/redux-toolkit.api.md | 14 ++-- packages/toolkit/etc/rtk-query.api.md | 4 +- packages/toolkit/package.json | 2 +- packages/toolkit/src/configureStore.ts | 39 ++++----- packages/toolkit/src/createAction.ts | 14 ++-- packages/toolkit/src/createAsyncThunk.ts | 10 ++- packages/toolkit/src/createReducer.ts | 21 ++--- packages/toolkit/src/createSlice.ts | 7 +- packages/toolkit/src/getDefaultMiddleware.ts | 6 +- packages/toolkit/src/index.ts | 2 + .../toolkit/src/listenerMiddleware/index.ts | 22 ++--- .../tests/effectScenarios.test.ts | 2 +- .../tests/listenerMiddleware.test.ts | 40 +++++----- .../toolkit/src/listenerMiddleware/types.ts | 80 +++++++------------ packages/toolkit/src/mapBuilders.ts | 20 ++--- packages/toolkit/src/query/apiTypes.ts | 6 +- .../toolkit/src/query/core/buildInitiate.ts | 12 ++- .../core/buildMiddleware/batchActions.ts | 15 ++-- .../core/buildMiddleware/cacheLifecycle.ts | 4 +- .../src/query/core/buildMiddleware/index.ts | 16 +++- .../src/query/core/buildMiddleware/types.ts | 12 +-- packages/toolkit/src/query/core/buildSlice.ts | 12 +-- .../toolkit/src/query/core/buildThunks.ts | 23 +++--- packages/toolkit/src/query/core/module.ts | 11 +-- packages/toolkit/src/query/createApi.ts | 6 +- .../toolkit/src/query/endpointDefinitions.ts | 4 +- .../toolkit/src/query/react/buildHooks.ts | 16 ++-- .../src/query/tests/buildHooks.test.tsx | 9 ++- .../src/query/tests/errorHandling.test.tsx | 35 ++++++-- packages/toolkit/src/query/tests/helpers.tsx | 8 +- .../src/tests/configureStore.typetest.ts | 32 +++++--- .../src/tests/createAction.typetest.tsx | 4 +- .../src/tests/createAsyncThunk.test.ts | 6 +- .../src/tests/createAsyncThunk.typetest.ts | 22 ++--- .../toolkit/src/tests/createReducer.test.ts | 20 +++-- .../toolkit/src/tests/createSlice.typetest.ts | 4 +- .../src/tests/getDefaultMiddleware.test.ts | 12 +-- .../toolkit/src/tests/mapBuilders.typetest.ts | 14 ++-- packages/toolkit/src/tests/matchers.test.ts | 4 +- .../toolkit/src/tests/matchers.typetest.ts | 24 +++--- ...rializableStateInvariantMiddleware.test.ts | 2 +- yarn.lock | 10 +-- 50 files changed, 388 insertions(+), 359 deletions(-) diff --git a/docs/api/configureStore.mdx b/docs/api/configureStore.mdx index 6f37c89a80..abcd72f65a 100644 --- a/docs/api/configureStore.mdx +++ b/docs/api/configureStore.mdx @@ -23,7 +23,7 @@ type ConfigureEnhancersCallback = ( interface ConfigureStoreOptions< S = any, - A extends Action = AnyAction, + A extends Action = UnknownAction, M extends Middlewares = Middlewares > { /** @@ -66,7 +66,7 @@ interface ConfigureStoreOptions< enhancers?: StoreEnhancer[] | ConfigureEnhancersCallback } -function configureStore( +function configureStore( options: ConfigureStoreOptions ): EnhancedStore ``` @@ -107,7 +107,8 @@ a list of the specific options that are available. Defaults to `true`. #### `trace` -The Redux DevTools Extension recently added [support for showing action stack traces](https://github.com/reduxjs/redux-devtools/blob/main/extension/docs/Features/Trace.md) that show exactly where each action was dispatched. + +The Redux DevTools Extension recently added [support for showing action stack traces](https://github.com/reduxjs/redux-devtools/blob/main/extension/docs/Features/Trace.md) that show exactly where each action was dispatched. Capturing the traces can add a bit of overhead, so the DevTools Extension allows users to configure whether action stack traces are captured by [setting the 'trace' argument](https://github.com/reduxjs/redux-devtools/blob/main/extension/docs/API/Arguments.md#trace). If the DevTools are enabled by passing `true` or an object, then `configureStore` will default to enabling capturing action stack traces in development mode only. @@ -195,7 +196,7 @@ const preloadedState = { visibilityFilter: 'SHOW_COMPLETED', } -const debounceNotify = _.debounce(notify => notify()); +const debounceNotify = _.debounce((notify) => notify()) const store = configureStore({ reducer, diff --git a/docs/api/createListenerMiddleware.mdx b/docs/api/createListenerMiddleware.mdx index c85614c870..a9fd98459c 100644 --- a/docs/api/createListenerMiddleware.mdx +++ b/docs/api/createListenerMiddleware.mdx @@ -120,10 +120,10 @@ The "listener middleware instance" returned from `createListenerMiddleware` is a ```ts no-transpile interface ListenerMiddlewareInstance< State = unknown, - Dispatch extends ThunkDispatch = ThunkDispatch< + Dispatch extends ThunkDispatch = ThunkDispatch< State, unknown, - AnyAction + UnknownAction >, ExtraArgument = unknown > { @@ -181,7 +181,7 @@ interface AddListenerOptions { effect: (action: Action, listenerApi: ListenerApi) => void | Promise } -type ListenerPredicate = ( +type ListenerPredicate = ( action: Action, currentState?: State, originalState?: State @@ -321,7 +321,7 @@ The `listenerApi` object is the second argument to each listener callback. It co ```ts no-transpile export interface ListenerEffectAPI< State, - Dispatch extends ReduxDispatch, + Dispatch extends ReduxDispatch, ExtraArgument = unknown > extends MiddlewareAPI { // NOTE: MiddlewareAPI contains `dispatch` and `getState` already @@ -570,12 +570,12 @@ Listeners can use the `condition` and `take` methods in `listenerApi` to wait un The signatures are: ```ts no-transpile -type ConditionFunction = ( +type ConditionFunction = ( predicate: ListenerPredicate | (() => boolean), timeout?: number ) => Promise -type TakeFunction = ( +type TakeFunction = ( predicate: ListenerPredicate | (() => boolean), timeout?: number ) => Promise<[Action, State, State] | null> diff --git a/docs/api/matching-utilities.mdx b/docs/api/matching-utilities.mdx index 16c720dc2c..2aec61a137 100644 --- a/docs/api/matching-utilities.mdx +++ b/docs/api/matching-utilities.mdx @@ -49,12 +49,12 @@ A higher-order function that returns a type guard function that may be used to c ```ts title="isAsyncThunkAction usage" import { isAsyncThunkAction } from '@reduxjs/toolkit' -import type { AnyAction } from '@reduxjs/toolkit' +import type { UnknownAction } from '@reduxjs/toolkit' import { requestThunk1, requestThunk2 } from '@virtual/matchers' const isARequestAction = isAsyncThunkAction(requestThunk1, requestThunk2) -function handleRequestAction(action: AnyAction) { +function handleRequestAction(action: UnknownAction) { if (isARequestAction(action)) { // action is an action dispatched by either `requestThunk1` or `requestThunk2` } @@ -67,12 +67,12 @@ A higher-order function that returns a type guard function that may be used to c ```ts title="isPending usage" import { isPending } from '@reduxjs/toolkit' -import type { AnyAction } from '@reduxjs/toolkit' +import type { UnknownAction } from '@reduxjs/toolkit' import { requestThunk1, requestThunk2 } from '@virtual/matchers' const isAPendingAction = isPending(requestThunk1, requestThunk2) -function handlePendingAction(action: AnyAction) { +function handlePendingAction(action: UnknownAction) { if (isAPendingAction(action)) { // action is a pending action dispatched by either `requestThunk1` or `requestThunk2` } @@ -85,12 +85,12 @@ A higher-order function that returns a type guard function that may be used to c ```ts title="isFulfilled usage" import { isFulfilled } from '@reduxjs/toolkit' -import type { AnyAction } from '@reduxjs/toolkit' +import type { UnknownAction } from '@reduxjs/toolkit' import { requestThunk1, requestThunk2 } from '@virtual/matchers' const isAFulfilledAction = isFulfilled(requestThunk1, requestThunk2) -function handleFulfilledAction(action: AnyAction) { +function handleFulfilledAction(action: UnknownAction) { if (isAFulfilledAction(action)) { // action is a fulfilled action dispatched by either `requestThunk1` or `requestThunk2` } @@ -103,12 +103,12 @@ A higher-order function that returns a type guard function that may be used to c ```ts title="isRejected usage" import { isRejected } from '@reduxjs/toolkit' -import type { AnyAction } from '@reduxjs/toolkit' +import type { UnknownAction } from '@reduxjs/toolkit' import { requestThunk1, requestThunk2 } from '@virtual/matchers' const isARejectedAction = isRejected(requestThunk1, requestThunk2) -function handleRejectedAction(action: AnyAction) { +function handleRejectedAction(action: UnknownAction) { if (isARejectedAction(action)) { // action is a rejected action dispatched by either `requestThunk1` or `requestThunk2` } @@ -121,7 +121,7 @@ A higher-order function that returns a type guard function that may be used to c ```ts title="isRejectedWithValue usage" import { isRejectedWithValue } from '@reduxjs/toolkit' -import type { AnyAction } from '@reduxjs/toolkit' +import type { UnknownAction } from '@reduxjs/toolkit' import { requestThunk1, requestThunk2 } from '@virtual/matchers' const isARejectedWithValueAction = isRejectedWithValue( @@ -129,7 +129,7 @@ const isARejectedWithValueAction = isRejectedWithValue( requestThunk2 ) -function handleRejectedWithValueAction(action: AnyAction) { +function handleRejectedWithValueAction(action: UnknownAction) { if (isARejectedWithValueAction(action)) { // action is a rejected action dispatched by either `requestThunk1` or `requestThunk2` // where rejectWithValue was used @@ -145,10 +145,7 @@ we're able to easily use the same matcher for several cases in a type-safe manne First, let's examine an unnecessarily complex example: ```ts title="Example without using a matcher utility" -import { - createAsyncThunk, - createReducer, -} from '@reduxjs/toolkit' +import { createAsyncThunk, createReducer } from '@reduxjs/toolkit' import type { PayloadAction } from '@reduxjs/toolkit' interface Data { diff --git a/docs/rtk-query/api/createApi.mdx b/docs/rtk-query/api/createApi.mdx index d4984f9362..dc8d6e76bc 100644 --- a/docs/rtk-query/api/createApi.mdx +++ b/docs/rtk-query/api/createApi.mdx @@ -57,7 +57,7 @@ export const { useGetPokemonByNameQuery } = pokemonApi baseQuery(args: InternalQueryArgs, api: BaseQueryApi, extraOptions?: DefinitionExtraOptions): any; endpoints(build: EndpointBuilder): Definitions; extractRehydrationInfo?: ( - action: AnyAction, + action: UnknownAction, { reducerPath, }: { @@ -88,7 +88,7 @@ export const { useGetPokemonByNameQuery } = pokemonApi - `dispatch` - The `store.dispatch` method for the corresponding Redux store - `getState` - A function that may be called to access the current store state - `extra` - Provided as thunk.extraArgument to the configureStore getDefaultMiddleware option. - - `endpoint` - The name of the endpoint. + - `endpoint` - The name of the endpoint. - `type` - Type of request (`query` or `mutation`). - `forced` - Indicates if a query has been forced. - `extraOptions` - The value of the optional `extraOptions` property provided for a given endpoint diff --git a/docs/rtk-query/api/created-api/api-slice-utils.mdx b/docs/rtk-query/api/created-api/api-slice-utils.mdx index 6471d812e9..7698b0c7f9 100644 --- a/docs/rtk-query/api/created-api/api-slice-utils.mdx +++ b/docs/rtk-query/api/created-api/api-slice-utils.mdx @@ -30,7 +30,7 @@ const updateQueryData = ( endpointName: string, args: any, updateRecipe: (draft: Draft) => void -) => ThunkAction; +) => ThunkAction; interface PatchCollection { patches: Patch[]; @@ -119,7 +119,7 @@ const upsertQueryData = ( endpointName: string, args: any, newEntryData: T -) => ThunkAction>, PartialState, any, AnyAction>; +) => ThunkAction>, PartialState, any, UnknownAction>; ``` - **Parameters** @@ -156,7 +156,7 @@ const patchQueryData = ( endpointName: string, args: any patches: Patch[] -) => ThunkAction; +) => ThunkAction; ``` - **Parameters** @@ -203,7 +203,7 @@ const prefetch = ( endpointName: string, arg: any, options: PrefetchOptions -) => ThunkAction; +) => ThunkAction; ``` - **Parameters** @@ -229,42 +229,44 @@ dispatch(api.util.prefetch('getPosts', undefined, { force: true })) ``` ### `selectInvalidatedBy` - + #### Signature - + ```ts no-transpile - function selectInvalidatedBy( - state: RootState, - tags: ReadonlyArray> - ): Array<{ - endpointName: string - originalArgs: any - queryCacheKey: QueryCacheKey - }> +function selectInvalidatedBy( + state: RootState, + tags: ReadonlyArray> +): Array<{ + endpointName: string + originalArgs: any + queryCacheKey: QueryCacheKey +}> ``` - + - **Parameters** - `state`: the root state - `tags`: a readonly array of invalidated tags, where the provided `TagDescription` is one of the strings provided to the [`tagTypes`](../createApi.mdx#tagtypes) property of the api. e.g. - `[TagType]` - `[{ type: TagType }]` - `[{ type: TagType, id: number | string }]` - + #### Description - + A function that can select query parameters to be invalidated. - + The function accepts two arguments - - the root state and - - the cache tags to be invalidated. - + +- the root state and +- the cache tags to be invalidated. + It returns an array that contains - - the endpoint name, - - the original args and - - the queryCacheKey. - + +- the endpoint name, +- the original args and +- the queryCacheKey. + #### Example - + ```ts no-transpile dispatch(api.util.selectInvalidatedBy(state, ['Post'])) dispatch(api.util.selectInvalidatedBy(state, [{ type: 'Post', id: 1 }])) diff --git a/docs/rtk-query/api/created-api/endpoints.mdx b/docs/rtk-query/api/created-api/endpoints.mdx index cbd847c8ae..0f2a3672b9 100644 --- a/docs/rtk-query/api/created-api/endpoints.mdx +++ b/docs/rtk-query/api/created-api/endpoints.mdx @@ -33,12 +33,12 @@ type InitiateRequestThunk = StartQueryActionCreator | StartMutationActionCreator type StartQueryActionCreator = ( arg:any, options?: StartQueryActionCreatorOptions -) => ThunkAction; +) => ThunkAction; type StartMutationActionCreator> = ( arg: any options?: StartMutationActionCreatorOptions -) => ThunkAction, any, any, AnyAction>; +) => ThunkAction, any, any, UnknownAction>; type SubscriptionOptions = { /** @@ -198,9 +198,10 @@ function App() { // highlight-start // useMemo is used to only call `.select()` when required. // Each call will create a new selector function instance - const selectPost = useMemo(() => api.endpoints.getPost.select(postId), [ - postId, - ]) + const selectPost = useMemo( + () => api.endpoints.getPost.select(postId), + [postId] + ) const { data, isLoading } = useAppSelector(selectPost) // highlight-end diff --git a/docs/usage/usage-with-typescript.md b/docs/usage/usage-with-typescript.md index d4d1a6348d..4e33ee6289 100644 --- a/docs/usage/usage-with-typescript.md +++ b/docs/usage/usage-with-typescript.md @@ -259,7 +259,7 @@ As the first `matcher` argument to `builder.addMatcher`, a [type predicate](http As a result, the `action` argument for the second `reducer` argument can be inferred by TypeScript: ```ts -function isNumberValueAction(action: AnyAction): action is PayloadAction<{ value: number }> { +function isNumberValueAction(action: UnknownAction): action is PayloadAction<{ value: number }> { return typeof action.payload.value === 'number' } diff --git a/examples/action-listener/counter/src/services/counter/listeners.ts b/examples/action-listener/counter/src/services/counter/listeners.ts index 3fe74b7bfd..ac2d0c4a3d 100644 --- a/examples/action-listener/counter/src/services/counter/listeners.ts +++ b/examples/action-listener/counter/src/services/counter/listeners.ts @@ -1,6 +1,6 @@ import { counterActions, counterSelectors } from './slice' import { - AnyAction, + UnknownAction, isAllOf, isAnyOf, PayloadAction, @@ -11,7 +11,7 @@ import type { AppListenerEffectAPI, AppStartListening } from '../../store' function shouldStopAsyncTasksOf(id: string) { return isAllOf( isAnyOf(counterActions.cancelAsyncUpdates, counterActions.removeCounter), - (action: AnyAction): action is PayloadAction => + (action: UnknownAction): action is PayloadAction => action?.payload === id ) } diff --git a/packages/toolkit/etc/redux-toolkit.api.md b/packages/toolkit/etc/redux-toolkit.api.md index 5a113e6378..8628682b33 100644 --- a/packages/toolkit/etc/redux-toolkit.api.md +++ b/packages/toolkit/etc/redux-toolkit.api.md @@ -5,7 +5,7 @@ ```ts import type { Action } from 'redux' import type { ActionCreator } from 'redux' -import type { AnyAction } from 'redux' +import type { UnknownAction } from 'redux' import type { CombinedState } from 'redux' import { default as createNextState } from 'immer' import { createSelector } from 'reselect' @@ -85,10 +85,10 @@ export interface ActionReducerMapBuilder { type: Type, reducer: CaseReducer ): ActionReducerMapBuilder - addDefaultCase(reducer: CaseReducer): {} + addDefaultCase(reducer: CaseReducer): {} addMatcher( matcher: TypeGuard | ((action: any) => boolean), - reducer: CaseReducer + reducer: CaseReducer ): Omit, 'addCase'> } @@ -191,7 +191,7 @@ export type AsyncThunkPayloadCreatorReturnValue< > // @public -export type CaseReducer = ( +export type CaseReducer = ( state: Draft, action: A ) => S | void | Draft @@ -227,14 +227,14 @@ export type ConfigureEnhancersCallback = ( // @public export function configureStore< S = any, - A extends Action = AnyAction, + A extends Action = UnknownAction, M extends Middlewares = [ThunkMiddlewareFor] >(options: ConfigureStoreOptions): EnhancedStore // @public export interface ConfigureStoreOptions< S = any, - A extends Action = AnyAction, + A extends Action = UnknownAction, M extends Middlewares = Middlewares > { devTools?: boolean | EnhancerOptions @@ -352,7 +352,7 @@ export { Draft } // @public export interface EnhancedStore< S = any, - A extends Action = AnyAction, + A extends Action = UnknownAction, M extends Middlewares = Middlewares > extends Store { dispatch: Dispatch & DispatchForMiddlewares diff --git a/packages/toolkit/etc/rtk-query.api.md b/packages/toolkit/etc/rtk-query.api.md index aeeb76f660..dea69f7421 100644 --- a/packages/toolkit/etc/rtk-query.api.md +++ b/packages/toolkit/etc/rtk-query.api.md @@ -4,7 +4,7 @@ ```ts import type { ActionCreatorWithoutPayload } from '@reduxjs/toolkit' -import type { AnyAction } from '@reduxjs/toolkit' +import type { UnknownAction } from '@reduxjs/toolkit' import type { SerializedError } from '@reduxjs/toolkit' import type { ThunkDispatch } from '@reduxjs/toolkit' @@ -127,7 +127,7 @@ export interface CreateApiOptions< build: EndpointBuilder ): Definitions extractRehydrationInfo?: ( - action: AnyAction, + action: UnknownAction, { reducerPath, }: { diff --git a/packages/toolkit/package.json b/packages/toolkit/package.json index bc46cc6fc9..3df11cc1a9 100644 --- a/packages/toolkit/package.json +++ b/packages/toolkit/package.json @@ -114,7 +114,7 @@ ], "dependencies": { "immer": "^10.0.0-beta.4", - "redux": "5.0.0-alpha.4", + "redux": "https://pkg.csb.dev/reduxjs/redux/commit/985fdefd/redux/_pkg.tgz", "redux-thunk": "3.0.0-alpha.3", "reselect": "^4.1.7" }, diff --git a/packages/toolkit/src/configureStore.ts b/packages/toolkit/src/configureStore.ts index 76f5a0519c..5626448ba7 100644 --- a/packages/toolkit/src/configureStore.ts +++ b/packages/toolkit/src/configureStore.ts @@ -3,12 +3,10 @@ import type { ReducersMapObject, Middleware, Action, - AnyAction, StoreEnhancer, Store, Dispatch, - PreloadedState, - CombinedState, + UnknownAction, } from 'redux' import { createStore, compose, applyMiddleware, combineReducers } from 'redux' import type { DevToolsEnhancerOptions as DevToolsOptions } from './devtoolsExtension' @@ -44,15 +42,16 @@ export type ConfigureEnhancersCallback = ( */ export interface ConfigureStoreOptions< S = any, - A extends Action = AnyAction, + A extends Action = UnknownAction, M extends Middlewares = Middlewares, - E extends Enhancers = Enhancers + E extends Enhancers = Enhancers, + P = S > { /** * A single reducer function that will be used as the root reducer, or an * object of slice reducers that will be passed to `combineReducers()`. */ - reducer: Reducer | ReducersMapObject + reducer: Reducer | ReducersMapObject /** * An array of Redux middleware to install. If not supplied, defaults to @@ -78,16 +77,7 @@ export interface ConfigureStoreOptions< * function (either directly or indirectly by passing an object as `reducer`), * this must be an object with the same shape as the reducer map keys. */ - /* - Not 100% correct but the best approximation we can get: - - if S is a `CombinedState` applying a second `CombinedState` on it does not change anything. - - if it is not, there could be two cases: - - `ReducersMapObject` is being passed in. In this case, we will call `combineReducers` on it and `CombinedState` is correct - - `Reducer` is being passed in. In this case, actually `CombinedState` is wrong and `S` would be correct. - As we cannot distinguish between those two cases without adding another generic parameter, - we just make the pragmatic assumption that the latter almost never happens. - */ - preloadedState?: PreloadedState>> + preloadedState?: NoInfer

/** * The store enhancers to apply. See Redux's `createStore()`. @@ -106,7 +96,7 @@ type Enhancers = ReadonlyArray export interface ToolkitStore< S = any, - A extends Action = AnyAction, + A extends Action = UnknownAction, M extends Middlewares = Middlewares > extends Store { /** @@ -125,7 +115,7 @@ export interface ToolkitStore< */ export type EnhancedStore< S = any, - A extends Action = AnyAction, + A extends Action = UnknownAction, M extends Middlewares = Middlewares, E extends Enhancers = Enhancers > = ToolkitStore & ExtractStoreExtensions @@ -140,10 +130,11 @@ export type EnhancedStore< */ export function configureStore< S = any, - A extends Action = AnyAction, + A extends Action = UnknownAction, M extends Middlewares = [ThunkMiddlewareFor], - E extends Enhancers = [StoreEnhancer] ->(options: ConfigureStoreOptions): EnhancedStore { + E extends Enhancers = [StoreEnhancer], + P = S +>(options: ConfigureStoreOptions): EnhancedStore { const curriedGetDefaultMiddleware = curryGetDefaultMiddleware() const { @@ -154,12 +145,12 @@ export function configureStore< enhancers = undefined, } = options || {} - let rootReducer: Reducer + let rootReducer: Reducer if (typeof reducer === 'function') { rootReducer = reducer } else if (isPlainObject(reducer)) { - rootReducer = combineReducers(reducer) as unknown as Reducer + rootReducer = combineReducers(reducer) as any } else { throw new Error( '"reducer" is a required argument, and must be a function or an object of functions that can be passed to combineReducers' @@ -207,5 +198,5 @@ export function configureStore< const composedEnhancer = finalCompose(...storeEnhancers) as StoreEnhancer - return createStore(rootReducer, preloadedState, composedEnhancer) + return createStore(rootReducer, preloadedState as P, composedEnhancer) } diff --git a/packages/toolkit/src/createAction.ts b/packages/toolkit/src/createAction.ts index 0647a04621..bc09dbdc08 100644 --- a/packages/toolkit/src/createAction.ts +++ b/packages/toolkit/src/createAction.ts @@ -1,4 +1,4 @@ -import type { Action } from 'redux' +import type { Action, UnknownAction } from 'redux' import type { IsUnknownOrNonInferrable, IfMaybeUndefined, @@ -83,7 +83,7 @@ export type _ActionCreatorWithPreparedPayload< */ export interface BaseActionCreator { type: T - match: (action: Action) => action is PayloadAction + match: (action: unknown) => action is PayloadAction } /** @@ -280,12 +280,16 @@ export function createAction(type: string, prepareAction?: Function): any { actionCreator.type = type - actionCreator.match = (action: Action): action is PayloadAction => + actionCreator.match = (action: UnknownAction): action is PayloadAction => action.type === type return actionCreator } +export function isAction(action: unknown): action is Action { + return isPlainObject(action) && 'type' in action +} + export function isFSA(action: unknown): action is { type: string payload?: unknown @@ -293,8 +297,8 @@ export function isFSA(action: unknown): action is { meta?: unknown } { return ( - isPlainObject(action) && - typeof (action as any).type === 'string' && + isAction(action) && + typeof action.type === 'string' && Object.keys(action).every(isValidKey) ) } diff --git a/packages/toolkit/src/createAsyncThunk.ts b/packages/toolkit/src/createAsyncThunk.ts index 5a805ebc3a..85a28ec278 100644 --- a/packages/toolkit/src/createAsyncThunk.ts +++ b/packages/toolkit/src/createAsyncThunk.ts @@ -1,4 +1,4 @@ -import type { Dispatch, AnyAction } from 'redux' +import type { Dispatch, UnknownAction } from 'redux' import type { PayloadAction, ActionCreatorWithPreparedPayload, @@ -132,10 +132,14 @@ type GetDispatch = ThunkApiConfig extends { ThunkDispatch< GetState, GetExtra, - AnyAction + UnknownAction > > - : ThunkDispatch, GetExtra, AnyAction> + : ThunkDispatch< + GetState, + GetExtra, + UnknownAction + > type GetThunkAPI = BaseThunkAPI< GetState, diff --git a/packages/toolkit/src/createReducer.ts b/packages/toolkit/src/createReducer.ts index e22b0a007a..e81f2b1e06 100644 --- a/packages/toolkit/src/createReducer.ts +++ b/packages/toolkit/src/createReducer.ts @@ -1,9 +1,9 @@ import type { Draft } from 'immer' import { produce as createNextState, isDraft, isDraftable } from 'immer' -import type { AnyAction, Action, Reducer } from 'redux' +import type { Action, Reducer, UnknownAction } from 'redux' import type { ActionReducerMapBuilder } from './mapBuilders' import { executeReducerBuilderCallback } from './mapBuilders' -import type { NoInfer } from './tsHelpers' +import type { NoInfer, TypeGuard } from './tsHelpers' import { freezeDraftable } from './utils' /** @@ -16,15 +16,8 @@ import { freezeDraftable } from './utils' */ export type Actions = Record -/** - * @deprecated use `TypeGuard` instead - */ -export interface ActionMatcher { - (action: AnyAction): action is A -} - -export type ActionMatcherDescription = { - matcher: ActionMatcher +export type ActionMatcherDescription = { + matcher: TypeGuard reducer: CaseReducer> } @@ -52,7 +45,7 @@ export type ActionMatcherDescriptionCollection = Array< * * @public */ -export type CaseReducer = ( +export type CaseReducer = ( state: Draft, action: A ) => NoInfer | void | Draft> @@ -108,7 +101,7 @@ let hasWarnedAboutObjectNotation = false import { createAction, createReducer, - AnyAction, + UnknownAction, PayloadAction, } from "@reduxjs/toolkit"; @@ -116,7 +109,7 @@ const increment = createAction("increment"); const decrement = createAction("decrement"); function isActionWithNumberPayload( - action: AnyAction + action: UnknownAction ): action is PayloadAction { return typeof action.payload === "number"; } diff --git a/packages/toolkit/src/createSlice.ts b/packages/toolkit/src/createSlice.ts index 48b36f39e7..37a17e3ffa 100644 --- a/packages/toolkit/src/createSlice.ts +++ b/packages/toolkit/src/createSlice.ts @@ -1,5 +1,4 @@ -import type { AnyAction, Reducer } from 'redux' -import { createNextState } from '.' +import type { Reducer, Action } from 'redux' import type { ActionCreatorWithoutPayload, PayloadAction, @@ -103,7 +102,7 @@ export interface CreateSliceOptions< * * @example ```ts -import { createAction, createSlice, Action, AnyAction } from '@reduxjs/toolkit' +import { createAction, createSlice, Action, UnknownAction } from '@reduxjs/toolkit' const incrementBy = createAction('incrementBy') const decrement = createAction('decrement') @@ -111,7 +110,7 @@ interface RejectedAction extends Action { error: Error } -function isRejectedAction(action: AnyAction): action is RejectedAction { +function isRejectedAction(action: UnknownAction): action is RejectedAction { return action.type.endsWith('rejected') } diff --git a/packages/toolkit/src/getDefaultMiddleware.ts b/packages/toolkit/src/getDefaultMiddleware.ts index 655b0dc931..0f084a4444 100644 --- a/packages/toolkit/src/getDefaultMiddleware.ts +++ b/packages/toolkit/src/getDefaultMiddleware.ts @@ -1,4 +1,4 @@ -import type { Middleware, AnyAction } from 'redux' +import type { Middleware, UnknownAction } from 'redux' import type { ThunkMiddleware } from 'redux-thunk' import { thunk as thunkMiddleware, withExtraArgument } from 'redux-thunk' import type { ImmutableStateInvariantMiddlewareOptions } from './immutableStateInvariantMiddleware' @@ -33,8 +33,8 @@ export type ThunkMiddlewareFor< } ? never : O extends { thunk: { extraArgument: infer E } } - ? ThunkMiddleware - : ThunkMiddleware + ? ThunkMiddleware + : ThunkMiddleware export type CurriedGetDefaultMiddleware = < O extends Partial = { diff --git a/packages/toolkit/src/index.ts b/packages/toolkit/src/index.ts index 0c8737e8b4..5a0e0a06a1 100644 --- a/packages/toolkit/src/index.ts +++ b/packages/toolkit/src/index.ts @@ -32,6 +32,8 @@ export { // js createAction, getType, + isAction, + isFSA, } from './createAction' export type { // types diff --git a/packages/toolkit/src/listenerMiddleware/index.ts b/packages/toolkit/src/listenerMiddleware/index.ts index d96b9257db..5225bd2f71 100644 --- a/packages/toolkit/src/listenerMiddleware/index.ts +++ b/packages/toolkit/src/listenerMiddleware/index.ts @@ -1,4 +1,4 @@ -import type { Dispatch, AnyAction, MiddlewareAPI } from 'redux' +import type { Action, Dispatch, MiddlewareAPI, UnknownAction } from 'redux' import type { ThunkDispatch } from 'redux-thunk' import { createAction } from '../createAction' import { nanoid } from '../nanoid' @@ -115,11 +115,7 @@ const createFork = (parentAbortSignal: AbortSignalWithReason) => { } const createTakePattern = ( - startListening: AddListenerOverloads< - UnsubscribeListener, - S, - Dispatch - >, + startListening: AddListenerOverloads, signal: AbortSignal ): TakePattern => { /** @@ -138,7 +134,7 @@ const createTakePattern = ( // Placeholder unsubscribe function until the listener is added let unsubscribe: UnsubscribeListener = () => {} - const tuplePromise = new Promise<[AnyAction, S, S]>((resolve, reject) => { + const tuplePromise = new Promise<[Action, S, S]>((resolve, reject) => { // Inside the Promise, we synchronously add the listener. let stopListening = startListening({ predicate: predicate as any, @@ -159,9 +155,7 @@ const createTakePattern = ( } }) - const promises: (Promise | Promise<[AnyAction, S, S]>)[] = [ - tuplePromise, - ] + const promises: (Promise | Promise<[Action, S, S]>)[] = [tuplePromise] if (timeout != null) { promises.push( @@ -229,7 +223,7 @@ export const createListenerEntry: TypedCreateListenerEntry = ( } const cancelActiveListeners = ( - entry: ListenerEntry> + entry: ListenerEntry> ) => { entry.pending.forEach((controller) => { abortControllerWithReason(controller, listenerCancelled) @@ -297,7 +291,7 @@ const defaultErrorHandler: ListenerErrorHandler = (...args: unknown[]) => { */ export function createListenerMiddleware< S = unknown, - D extends Dispatch = ThunkDispatch, + D extends Dispatch = ThunkDispatch, ExtraArgument = unknown >(middlewareOptions: CreateListenerMiddlewareOptions = {}) { const listenerMap = new Map() @@ -366,8 +360,8 @@ export function createListenerMiddleware< } const notifyListener = async ( - entry: ListenerEntry>, - action: AnyAction, + entry: ListenerEntry>, + action: unknown, api: MiddlewareAPI, getOriginalState: () => S ) => { diff --git a/packages/toolkit/src/listenerMiddleware/tests/effectScenarios.test.ts b/packages/toolkit/src/listenerMiddleware/tests/effectScenarios.test.ts index db9d5ae571..73e039cad8 100644 --- a/packages/toolkit/src/listenerMiddleware/tests/effectScenarios.test.ts +++ b/packages/toolkit/src/listenerMiddleware/tests/effectScenarios.test.ts @@ -6,7 +6,7 @@ import { } from '@reduxjs/toolkit' import { vi } from 'vitest' -import type { AnyAction, PayloadAction, Action } from '@reduxjs/toolkit' +import type { PayloadAction } from '@reduxjs/toolkit' import { createListenerMiddleware, TaskAbortError } from '../index' diff --git a/packages/toolkit/src/listenerMiddleware/tests/listenerMiddleware.test.ts b/packages/toolkit/src/listenerMiddleware/tests/listenerMiddleware.test.ts index 5417383685..72b1a7be2b 100644 --- a/packages/toolkit/src/listenerMiddleware/tests/listenerMiddleware.test.ts +++ b/packages/toolkit/src/listenerMiddleware/tests/listenerMiddleware.test.ts @@ -3,10 +3,12 @@ import { createAction, createSlice, isAnyOf, + isFSA, } from '@reduxjs/toolkit' -import { vi, Mock } from 'vitest' +import type { Mock } from 'vitest' +import { vi } from 'vitest' -import type { AnyAction, PayloadAction, Action } from '@reduxjs/toolkit' +import type { Action, UnknownAction, PayloadAction } from '@reduxjs/toolkit' import { createListenerMiddleware, @@ -195,7 +197,7 @@ describe('createListenerMiddleware', () => { > typedAddListener({ - matcher: (action: AnyAction): action is AnyAction => true, + matcher: (action): action is Action => true, effect: (action, listenerApi) => { foundExtra = listenerApi.extra expectType(listenerApi.extra) @@ -271,7 +273,7 @@ describe('createListenerMiddleware', () => { }) test('can subscribe with a string action type', () => { - const effect = vi.fn((_: AnyAction) => {}) + const effect = vi.fn((_: UnknownAction) => {}) store.dispatch( addListener({ @@ -290,7 +292,7 @@ describe('createListenerMiddleware', () => { }) test('can subscribe with a matcher function', () => { - const effect = vi.fn((_: AnyAction) => {}) + const effect = vi.fn((_: UnknownAction) => {}) const isAction1Or2 = isAnyOf(testAction1, testAction2) @@ -573,7 +575,7 @@ describe('createListenerMiddleware', () => { 'add and remove listener with "%s" param correctly', (_, params) => { const effect: ListenerEffect< - AnyAction, + UnknownAction, typeof store.getState, typeof store.dispatch > = vi.fn() @@ -590,7 +592,7 @@ describe('createListenerMiddleware', () => { } ) - const unforwardedActions: [string, AnyAction][] = [ + const unforwardedActions: [string, UnknownAction][] = [ [ 'addListener', addListener({ actionCreator: testAction1, effect: noop }), @@ -1072,7 +1074,7 @@ describe('createListenerMiddleware', () => { typedAddListener({ predicate: incrementByAmount.match, - async effect(_: AnyAction, listenerApi) { + async effect(_: UnknownAction, listenerApi) { result = await listenerApi.take(increment.match) }, }) @@ -1340,7 +1342,7 @@ describe('createListenerMiddleware', () => { action, currentState, previousState - ): action is AnyAction => { + ): action is UnknownAction => { expectUnknown(currentState) expectUnknown(previousState) return true @@ -1360,7 +1362,7 @@ describe('createListenerMiddleware', () => { action, currentState, previousState - ): action is AnyAction => { + ): action is UnknownAction => { expectUnknown(currentState) expectUnknown(previousState) return true @@ -1386,7 +1388,7 @@ describe('createListenerMiddleware', () => { action, currentState, previousState - ): action is AnyAction => { + ): action is UnknownAction => { expectUnknown(currentState) expectUnknown(previousState) return true @@ -1446,7 +1448,7 @@ describe('createListenerMiddleware', () => { currentState, previousState ): action is PayloadAction => { - return typeof action.payload === 'boolean' + return isFSA(action) && typeof action.payload === 'boolean' }, effect: (action, listenerApi) => { expectExactType>(action) @@ -1455,10 +1457,10 @@ describe('createListenerMiddleware', () => { startListening({ predicate: (action, currentState) => { - return typeof action.payload === 'number' + return isFSA(action) && typeof action.payload === 'number' }, effect: (action, listenerApi) => { - expectExactType(action) + expectExactType(action) }, }) @@ -1498,7 +1500,7 @@ describe('createListenerMiddleware', () => { action, currentState, previousState - ): action is AnyAction => { + ): action is UnknownAction => { expectNotAny(currentState) expectNotAny(previousState) expectExactType(currentState) @@ -1519,7 +1521,7 @@ describe('createListenerMiddleware', () => { typedMiddleware.startListening({ // TODO Why won't this infer the listener's `action` with implicit argument types? predicate: ( - action: AnyAction, + action: UnknownAction, currentState: CounterState ): action is PayloadAction => { expectNotAny(currentState) @@ -1580,7 +1582,7 @@ describe('createListenerMiddleware', () => { action, currentState, previousState - ): action is AnyAction => { + ): action is UnknownAction => { expectNotAny(currentState) expectNotAny(previousState) expectExactType(currentState) @@ -1610,7 +1612,7 @@ describe('createListenerMiddleware', () => { action, currentState, previousState - ): action is AnyAction => { + ): action is UnknownAction => { expectNotAny(currentState) expectNotAny(previousState) expectExactType(currentState) @@ -1647,7 +1649,7 @@ describe('createListenerMiddleware', () => { action, currentState, previousState - ): action is AnyAction => { + ): action is UnknownAction => { expectNotAny(currentState) expectNotAny(previousState) expectExactType(currentState) diff --git a/packages/toolkit/src/listenerMiddleware/types.ts b/packages/toolkit/src/listenerMiddleware/types.ts index 6b75d80036..c342176b68 100644 --- a/packages/toolkit/src/listenerMiddleware/types.ts +++ b/packages/toolkit/src/listenerMiddleware/types.ts @@ -1,10 +1,10 @@ import type { PayloadAction, BaseActionCreator } from '../createAction' import type { Dispatch as ReduxDispatch, - AnyAction, MiddlewareAPI, Middleware, Action as ReduxAction, + UnknownAction, } from 'redux' import type { ThunkDispatch } from 'redux-thunk' import type { TaskAbortError } from './exceptions' @@ -28,14 +28,14 @@ export interface TypedActionCreator { /** @internal */ export type AnyListenerPredicate = ( - action: AnyAction, + action: unknown, currentState: State, originalState: State ) => boolean /** @public */ -export type ListenerPredicate = ( - action: AnyAction, +export type ListenerPredicate = ( + action: unknown, currentState: State, originalState: State ) => action is Action @@ -134,7 +134,7 @@ export interface ForkedTask { /** @public */ export interface ListenerEffectAPI< State, - Dispatch extends ReduxDispatch, + Dispatch extends ReduxDispatch, ExtraArgument = unknown > extends MiddlewareAPI { /** @@ -177,9 +177,9 @@ export interface ListenerEffectAPI< * rejects if the listener has been cancelled or is completed. * * The return value is `true` if the predicate succeeds or `false` if a timeout is provided and expires first. - * + * * ### Example - * + * * ```ts * const updateBy = createAction('counter/updateBy'); * @@ -201,7 +201,7 @@ export interface ListenerEffectAPI< * * The return value is the `[action, currentState, previousState]` combination that the predicate saw as arguments. * - * The promise resolves to null if a timeout is provided and expires first, + * The promise resolves to null if a timeout is provided and expires first, * * ### Example * @@ -251,9 +251,9 @@ export interface ListenerEffectAPI< /** @public */ export type ListenerEffect< - Action extends AnyAction, + Action extends ReduxAction, State, - Dispatch extends ReduxDispatch, + Dispatch extends ReduxDispatch, ExtraArgument = unknown > = ( action: Action, @@ -293,10 +293,10 @@ export interface CreateListenerMiddlewareOptions { /** @public */ export type ListenerMiddleware< State = unknown, - Dispatch extends ThunkDispatch = ThunkDispatch< + Dispatch extends ThunkDispatch = ThunkDispatch< State, unknown, - AnyAction + UnknownAction >, ExtraArgument = unknown > = Middleware< @@ -310,10 +310,10 @@ export type ListenerMiddleware< /** @public */ export interface ListenerMiddlewareInstance< State = unknown, - Dispatch extends ThunkDispatch = ThunkDispatch< + Dispatch extends ThunkDispatch = ThunkDispatch< State, unknown, - AnyAction + UnknownAction >, ExtraArgument = unknown > { @@ -341,7 +341,7 @@ export type TakePatternOutputWithoutTimeout< Predicate extends AnyListenerPredicate > = Predicate extends MatchFunction ? Promise<[Action, State, State]> - : Promise<[AnyAction, State, State]> + : Promise<[UnknownAction, State, State]> /** @public */ export type TakePatternOutputWithTimeout< @@ -349,7 +349,7 @@ export type TakePatternOutputWithTimeout< Predicate extends AnyListenerPredicate > = Predicate extends MatchFunction ? Promise<[Action, State, State] | null> - : Promise<[AnyAction, State, State] | null> + : Promise<[UnknownAction, State, State] | null> /** @public */ export interface TakePattern { @@ -383,12 +383,12 @@ export type UnsubscribeListener = ( export interface AddListenerOverloads< Return, State = unknown, - Dispatch extends ReduxDispatch = ThunkDispatch, + Dispatch extends ReduxDispatch = ThunkDispatch, ExtraArgument = unknown, AdditionalOptions = unknown > { /** Accepts a "listener predicate" that is also a TS type predicate for the action*/ - >( + >( options: { actionCreator?: never type?: never @@ -426,7 +426,7 @@ export interface AddListenerOverloads< ): Return /** Accepts an RTK matcher function, such as `incrementByAmount.match` */ - >( + >( options: { actionCreator?: never type?: never @@ -443,7 +443,7 @@ export interface AddListenerOverloads< type?: never matcher?: never predicate: LP - effect: ListenerEffect + effect: ListenerEffect } & AdditionalOptions ): Return } @@ -451,7 +451,7 @@ export interface AddListenerOverloads< /** @public */ export type RemoveListenerOverloads< State = unknown, - Dispatch extends ReduxDispatch = ThunkDispatch + Dispatch extends ReduxDispatch = ThunkDispatch > = AddListenerOverloads< boolean, State, @@ -462,9 +462,9 @@ export type RemoveListenerOverloads< /** @public */ export interface RemoveListenerAction< - Action extends AnyAction, + Action extends UnknownAction, State, - Dispatch extends ReduxDispatch + Dispatch extends ReduxDispatch > { type: 'listenerMiddleware/remove' payload: { @@ -478,11 +478,7 @@ export interface RemoveListenerAction< * A "pre-typed" version of `addListenerAction`, so the listener args are well-typed */ export type TypedAddListener< State, - Dispatch extends ReduxDispatch = ThunkDispatch< - State, - unknown, - AnyAction - >, + Dispatch extends ReduxDispatch = ThunkDispatch, ExtraArgument = unknown, Payload = ListenerEntry, T extends string = 'listenerMiddleware/add' @@ -499,11 +495,7 @@ export type TypedAddListener< * A "pre-typed" version of `removeListenerAction`, so the listener args are well-typed */ export type TypedRemoveListener< State, - Dispatch extends ReduxDispatch = ThunkDispatch< - State, - unknown, - AnyAction - >, + Dispatch extends ReduxDispatch = ThunkDispatch, Payload = ListenerEntry, T extends string = 'listenerMiddleware/remove' > = BaseActionCreator & @@ -520,11 +512,7 @@ export type TypedRemoveListener< * A "pre-typed" version of `middleware.startListening`, so the listener args are well-typed */ export type TypedStartListening< State, - Dispatch extends ReduxDispatch = ThunkDispatch< - State, - unknown, - AnyAction - >, + Dispatch extends ReduxDispatch = ThunkDispatch, ExtraArgument = unknown > = AddListenerOverloads @@ -532,22 +520,14 @@ export type TypedStartListening< * A "pre-typed" version of `middleware.stopListening`, so the listener args are well-typed */ export type TypedStopListening< State, - Dispatch extends ReduxDispatch = ThunkDispatch< - State, - unknown, - AnyAction - > + Dispatch extends ReduxDispatch = ThunkDispatch > = RemoveListenerOverloads /** @public * A "pre-typed" version of `createListenerEntry`, so the listener args are well-typed */ export type TypedCreateListenerEntry< State, - Dispatch extends ReduxDispatch = ThunkDispatch< - State, - unknown, - AnyAction - > + Dispatch extends ReduxDispatch = ThunkDispatch > = AddListenerOverloads, State, Dispatch> /** @@ -557,14 +537,14 @@ export type TypedCreateListenerEntry< /** @internal An single listener entry */ export type ListenerEntry< State = unknown, - Dispatch extends ReduxDispatch = ReduxDispatch + Dispatch extends ReduxDispatch = ReduxDispatch > = { id: string effect: ListenerEffect unsubscribe: () => void pending: Set type?: string - predicate: ListenerPredicate + predicate: ListenerPredicate } /** diff --git a/packages/toolkit/src/mapBuilders.ts b/packages/toolkit/src/mapBuilders.ts index 81d8a9aca6..c4ccccd474 100644 --- a/packages/toolkit/src/mapBuilders.ts +++ b/packages/toolkit/src/mapBuilders.ts @@ -1,4 +1,4 @@ -import type { Action, AnyAction } from 'redux' +import type { Action } from 'redux' import type { CaseReducer, CaseReducers, @@ -56,7 +56,7 @@ import { createAction, createReducer, AsyncThunk, - AnyAction, + UnknownAction, } from "@reduxjs/toolkit"; type GenericAsyncThunk = AsyncThunk; @@ -68,8 +68,8 @@ type FulfilledAction = ReturnType; const initialState: Record = {}; const resetAction = createAction("reset-tracked-loading-state"); -function isPendingAction(action: AnyAction): action is PendingAction { - return action.type.endsWith("/pending"); +function isPendingAction(action: UnknownAction): action is PendingAction { + return typeof action.type === "string" && action.type.endsWith("/pending"); } const reducer = createReducer(initialState, (builder) => { @@ -98,7 +98,7 @@ const reducer = createReducer(initialState, (builder) => { */ addMatcher( matcher: TypeGuard | ((action: any) => boolean), - reducer: CaseReducer + reducer: CaseReducer ): Omit, 'addCase'> /** @@ -120,7 +120,7 @@ const reducer = createReducer(initialState, builder => { }) ``` */ - addDefaultCase(reducer: CaseReducer): {} + addDefaultCase(reducer: CaseReducer): {} } export function executeReducerBuilderCallback( @@ -128,11 +128,11 @@ export function executeReducerBuilderCallback( ): [ CaseReducers, ActionMatcherDescriptionCollection, - CaseReducer | undefined + CaseReducer | undefined ] { const actionsMap: CaseReducers = {} const actionMatchers: ActionMatcherDescriptionCollection = [] - let defaultCaseReducer: CaseReducer | undefined + let defaultCaseReducer: CaseReducer | undefined const builder = { addCase( typeOrActionCreator: string | TypedActionCreator, @@ -169,7 +169,7 @@ export function executeReducerBuilderCallback( }, addMatcher( matcher: TypeGuard, - reducer: CaseReducer + reducer: CaseReducer ) { if (process.env.NODE_ENV !== 'production') { if (defaultCaseReducer) { @@ -181,7 +181,7 @@ export function executeReducerBuilderCallback( actionMatchers.push({ matcher, reducer }) return builder }, - addDefaultCase(reducer: CaseReducer) { + addDefaultCase(reducer: CaseReducer) { if (process.env.NODE_ENV !== 'production') { if (defaultCaseReducer) { throw new Error('`builder.addDefaultCase` can only be called once') diff --git a/packages/toolkit/src/query/apiTypes.ts b/packages/toolkit/src/query/apiTypes.ts index 3dafe46b02..9d23c9e0ed 100644 --- a/packages/toolkit/src/query/apiTypes.ts +++ b/packages/toolkit/src/query/apiTypes.ts @@ -13,7 +13,7 @@ import type { CoreModule } from './core/module' import type { CreateApiOptions } from './createApi' import type { BaseQueryFn } from './baseQueryTypes' import type { CombinedState } from './core/apiState' -import type { AnyAction } from '@reduxjs/toolkit' +import type { UnknownAction } from '@reduxjs/toolkit' export interface ApiModules< // eslint-disable-next-line @typescript-eslint/no-unused-vars @@ -61,9 +61,9 @@ export interface ApiContext { endpointDefinitions: Definitions batch(cb: () => void): void extractRehydrationInfo: ( - action: AnyAction + action: UnknownAction ) => CombinedState | undefined - hasRehydrationInfo: (action: AnyAction) => boolean + hasRehydrationInfo: (action: UnknownAction) => boolean } export type Api< diff --git a/packages/toolkit/src/query/core/buildInitiate.ts b/packages/toolkit/src/query/core/buildInitiate.ts index 8641c3c373..c4b5ed9cda 100644 --- a/packages/toolkit/src/query/core/buildInitiate.ts +++ b/packages/toolkit/src/query/core/buildInitiate.ts @@ -7,7 +7,11 @@ import type { } from '../endpointDefinitions' import { DefinitionType, isQueryDefinition } from '../endpointDefinitions' import type { QueryThunk, MutationThunk, QueryThunkArg } from './buildThunks' -import type { AnyAction, ThunkAction, SerializedError } from '@reduxjs/toolkit' +import type { + UnknownAction, + ThunkAction, + SerializedError, +} from '@reduxjs/toolkit' import type { SubscriptionOptions, RootState } from './apiState' import type { InternalSerializeQueryArgs } from '../defaultSerializeQueryArgs' import type { Api, ApiContext } from '../apiTypes' @@ -51,7 +55,7 @@ type StartQueryActionCreator< > = ( arg: QueryArgFrom, options?: StartQueryActionCreatorOptions -) => ThunkAction, any, any, AnyAction> +) => ThunkAction, any, any, UnknownAction> export type QueryActionCreatorResult< D extends QueryDefinition @@ -81,7 +85,7 @@ type StartMutationActionCreator< track?: boolean fixedCacheKey?: string } -) => ThunkAction, any, any, AnyAction> +) => ThunkAction, any, any, UnknownAction> export type MutationActionCreatorResult< D extends MutationDefinition @@ -241,7 +245,7 @@ export function buildInitiate({ removalWarning() } else { const extract = ( - v: Map, Record> + v: Map, Record> ) => Array.from(v.values()).flatMap((queriesForStore) => queriesForStore ? Object.values(queriesForStore) : [] diff --git a/packages/toolkit/src/query/core/buildMiddleware/batchActions.ts b/packages/toolkit/src/query/core/buildMiddleware/batchActions.ts index 549ade7084..5dd337b890 100644 --- a/packages/toolkit/src/query/core/buildMiddleware/batchActions.ts +++ b/packages/toolkit/src/query/core/buildMiddleware/batchActions.ts @@ -1,13 +1,7 @@ -import type { QueryThunk, RejectedAction } from '../buildThunks' import type { InternalHandlerBuilder } from './types' -import type { - SubscriptionState, - QuerySubstateIdentifier, - Subscribers, -} from '../apiState' +import type { SubscriptionState } from '../apiState' import { produceWithPatches } from 'immer' -import type { AnyAction } from '@reduxjs/toolkit'; -import { createSlice, PayloadAction } from '@reduxjs/toolkit' +import type { Action } from '@reduxjs/toolkit' // Copied from https://github.com/feross/queue-microtask let promise: Promise @@ -45,7 +39,7 @@ export const buildBatchedActionsHandler: InternalHandlerBuilder< // This is done to speed up perf when loading many components const actuallyMutateSubscriptions = ( mutableState: SubscriptionState, - action: AnyAction + action: Action ) => { if (updateSubscriptionOptions.match(action)) { const { queryCacheKey, requestId, options } = action.payload @@ -140,7 +134,8 @@ export const buildBatchedActionsHandler: InternalHandlerBuilder< } const isSubscriptionSliceAction = - !!action.type?.startsWith(subscriptionsPrefix) + typeof action.type == 'string' && + !!action.type.startsWith(subscriptionsPrefix) const isAdditionalSubscriptionAction = queryThunk.rejected.match(action) && action.meta.condition && diff --git a/packages/toolkit/src/query/core/buildMiddleware/cacheLifecycle.ts b/packages/toolkit/src/query/core/buildMiddleware/cacheLifecycle.ts index 923eb79d56..d83a25b15f 100644 --- a/packages/toolkit/src/query/core/buildMiddleware/cacheLifecycle.ts +++ b/packages/toolkit/src/query/core/buildMiddleware/cacheLifecycle.ts @@ -1,5 +1,5 @@ import { isAsyncThunkAction, isFulfilled } from '@reduxjs/toolkit' -import type { AnyAction } from 'redux' +import type { UnknownAction } from 'redux' import type { ThunkDispatch } from 'redux-thunk' import type { BaseQueryFn, BaseQueryMeta } from '../../baseQueryTypes' import { DefinitionType } from '../../endpointDefinitions' @@ -65,7 +65,7 @@ declare module '../../endpointDefinitions' { /** * The dispatch method for the store */ - dispatch: ThunkDispatch + dispatch: ThunkDispatch /** * A method to get the current state */ diff --git a/packages/toolkit/src/query/core/buildMiddleware/index.ts b/packages/toolkit/src/query/core/buildMiddleware/index.ts index 810839e333..901e082654 100644 --- a/packages/toolkit/src/query/core/buildMiddleware/index.ts +++ b/packages/toolkit/src/query/core/buildMiddleware/index.ts @@ -1,5 +1,10 @@ -import type { AnyAction, Middleware, ThunkDispatch } from '@reduxjs/toolkit' -import { createAction } from '@reduxjs/toolkit' +import type { + Action, + Middleware, + ThunkDispatch, + UnknownAction, +} from '@reduxjs/toolkit' +import { isAction, createAction } from '@reduxjs/toolkit' import type { EndpointDefinitions, @@ -35,7 +40,7 @@ export function buildMiddleware< >(`${reducerPath}/invalidateTags`), } - const isThisApiSliceAction = (action: AnyAction) => { + const isThisApiSliceAction = (action: Action) => { return ( !!action && typeof action.type === 'string' && @@ -55,7 +60,7 @@ export function buildMiddleware< const middleware: Middleware< {}, RootState, - ThunkDispatch + ThunkDispatch > = (mwApi) => { let initialized = false @@ -80,6 +85,9 @@ export function buildMiddleware< return (next) => { return (action) => { + if (!isAction(action)) { + return next(action) + } if (!initialized) { initialized = true // dispatch before any other action diff --git a/packages/toolkit/src/query/core/buildMiddleware/types.ts b/packages/toolkit/src/query/core/buildMiddleware/types.ts index 20e23a4ac8..c83306b855 100644 --- a/packages/toolkit/src/query/core/buildMiddleware/types.ts +++ b/packages/toolkit/src/query/core/buildMiddleware/types.ts @@ -1,10 +1,10 @@ import type { - AnyAction, + Action, AsyncThunkAction, - Dispatch, Middleware, MiddlewareAPI, ThunkDispatch, + UnknownAction, } from '@reduxjs/toolkit' import type { Api, ApiContext } from '../../apiTypes' @@ -46,7 +46,7 @@ export interface BuildMiddlewareInput< } export type SubMiddlewareApi = MiddlewareAPI< - ThunkDispatch, + ThunkDispatch, RootState > @@ -68,12 +68,12 @@ export type SubMiddlewareBuilder = ( ) => Middleware< {}, RootState, - ThunkDispatch + ThunkDispatch > export type ApiMiddlewareInternalHandler = ( - action: AnyAction, - mwApi: SubMiddlewareApi & { next: Dispatch }, + action: Action, + mwApi: SubMiddlewareApi & { next: (action: unknown) => unknown }, prevState: RootState ) => ReturnType diff --git a/packages/toolkit/src/query/core/buildSlice.ts b/packages/toolkit/src/query/core/buildSlice.ts index 3343b6dc3d..f847a1d5e5 100644 --- a/packages/toolkit/src/query/core/buildSlice.ts +++ b/packages/toolkit/src/query/core/buildSlice.ts @@ -1,4 +1,4 @@ -import type { AnyAction, PayloadAction } from '@reduxjs/toolkit' +import type { PayloadAction, UnknownAction } from '@reduxjs/toolkit' import { combineReducers, createAction, @@ -469,9 +469,7 @@ export function buildSlice({ }, }) - const combinedReducer = combineReducers< - CombinedQueryState - >({ + const combinedReducer = combineReducers({ queries: querySlice.reducer, mutations: mutationSlice.reducer, provided: invalidationSlice.reducer, @@ -479,8 +477,10 @@ export function buildSlice({ config: configSlice.reducer, }) - const reducer: typeof combinedReducer = (state, action) => - combinedReducer(resetApiState.match(action) ? undefined : state, action) + const reducer = ( + state: CombinedQueryState | undefined, + action: UnknownAction + ) => combinedReducer(resetApiState.match(action) ? undefined : state, action) const actions = { ...configSlice.actions, diff --git a/packages/toolkit/src/query/core/buildThunks.ts b/packages/toolkit/src/query/core/buildThunks.ts index 458b9edd44..d0e609302c 100644 --- a/packages/toolkit/src/query/core/buildThunks.ts +++ b/packages/toolkit/src/query/core/buildThunks.ts @@ -23,7 +23,11 @@ import type { } from '../endpointDefinitions' import { isQueryDefinition } from '../endpointDefinitions' import { calculateProvidedBy } from '../endpointDefinitions' -import type { AsyncThunkPayloadCreator, Draft } from '@reduxjs/toolkit' +import type { + AsyncThunkPayloadCreator, + Draft, + UnknownAction, +} from '@reduxjs/toolkit' import { isAllOf, isFulfilled, @@ -33,12 +37,7 @@ import { } from '@reduxjs/toolkit' import type { Patch } from 'immer' import { isDraftable, produceWithPatches } from 'immer' -import type { - AnyAction, - ThunkAction, - ThunkDispatch, - AsyncThunk, -} from '@reduxjs/toolkit' +import type { ThunkAction, ThunkDispatch, AsyncThunk } from '@reduxjs/toolkit' import { createAsyncThunk, SHOULD_AUTOBATCH } from '@reduxjs/toolkit' import { HandledError } from '../HandledError' @@ -165,7 +164,7 @@ export type PatchQueryDataThunk< endpointName: EndpointName, args: QueryArgFrom, patches: readonly Patch[] -) => ThunkAction +) => ThunkAction export type UpdateQueryDataThunk< Definitions extends EndpointDefinitions, @@ -174,7 +173,7 @@ export type UpdateQueryDataThunk< endpointName: EndpointName, args: QueryArgFrom, updateRecipe: Recipe> -) => ThunkAction +) => ThunkAction export type UpsertQueryDataThunk< Definitions extends EndpointDefinitions, @@ -191,7 +190,7 @@ export type UpsertQueryDataThunk< >, PartialState, any, - AnyAction + UnknownAction > /** @@ -553,7 +552,7 @@ In the case of an unhandled error, no tags will be "provided" or "invalidated".` endpointName: EndpointName, arg: any, options: PrefetchOptions - ): ThunkAction => + ): ThunkAction => (dispatch: ThunkDispatch, getState: () => any) => { const force = hasTheForce(options) && options.force const maxAge = hasMaxAge(options) && options.ifOlderThan @@ -588,7 +587,7 @@ In the case of an unhandled error, no tags will be "provided" or "invalidated".` } function matchesEndpoint(endpointName: string) { - return (action: any): action is AnyAction => + return (action: any): action is UnknownAction => action?.meta?.arg?.endpointName === endpointName } diff --git a/packages/toolkit/src/query/core/module.ts b/packages/toolkit/src/query/core/module.ts index 2cb9ac76e3..21d6a29aad 100644 --- a/packages/toolkit/src/query/core/module.ts +++ b/packages/toolkit/src/query/core/module.ts @@ -9,11 +9,11 @@ import type { import { buildThunks } from './buildThunks' import type { ActionCreatorWithPayload, - AnyAction, Middleware, Reducer, ThunkAction, ThunkDispatch, + UnknownAction, } from '@reduxjs/toolkit' import type { EndpointDefinitions, @@ -71,7 +71,8 @@ export type CoreModule = | ReferenceQueryLifecycle | ReferenceCacheCollection -export interface ThunkWithReturnValue extends ThunkAction {} +export interface ThunkWithReturnValue + extends ThunkAction {} declare module '../apiTypes' { export interface ApiModules< @@ -115,7 +116,7 @@ declare module '../apiTypes' { */ reducer: Reducer< CombinedState, - AnyAction + UnknownAction > /** * This is a standard redux middleware and is responsible for things like polling, garbage collection and a handful of other things. Make sure it's included in your store. @@ -133,7 +134,7 @@ declare module '../apiTypes' { middleware: Middleware< {}, RootState, - ThunkDispatch + ThunkDispatch > /** * A collection of utility thunks for various situations. @@ -242,7 +243,7 @@ declare module '../apiTypes' { endpointName: EndpointName, arg: QueryArgFrom, options: PrefetchOptions - ): ThunkAction + ): ThunkAction /** * A Redux thunk action creator that, when dispatched, creates and applies a set of JSON diff/patch objects to the current state. This immediately updates the Redux state with those changes. * diff --git a/packages/toolkit/src/query/createApi.ts b/packages/toolkit/src/query/createApi.ts index d98116dd1e..14921cbef5 100644 --- a/packages/toolkit/src/query/createApi.ts +++ b/packages/toolkit/src/query/createApi.ts @@ -9,7 +9,7 @@ import type { } from './endpointDefinitions' import { DefinitionType, isQueryDefinition } from './endpointDefinitions' import { nanoid } from '@reduxjs/toolkit' -import type { AnyAction } from '@reduxjs/toolkit' +import type { UnknownAction } from '@reduxjs/toolkit' import type { NoInfer } from './tsHelpers' import { defaultMemoize } from 'reselect' @@ -178,7 +178,7 @@ export interface CreateApiOptions< * ``` */ extractRehydrationInfo?: ( - action: AnyAction, + action: UnknownAction, { reducerPath, }: { @@ -230,7 +230,7 @@ export function buildCreateApi, ...Module[]]>( ...modules: Modules ): CreateApi { return function baseCreateApi(options) { - const extractRehydrationInfo = defaultMemoize((action: AnyAction) => + const extractRehydrationInfo = defaultMemoize((action: UnknownAction) => options.extractRehydrationInfo?.(action, { reducerPath: (options.reducerPath ?? 'api') as any, }) diff --git a/packages/toolkit/src/query/endpointDefinitions.ts b/packages/toolkit/src/query/endpointDefinitions.ts index 7b6530af9d..fcecc47568 100644 --- a/packages/toolkit/src/query/endpointDefinitions.ts +++ b/packages/toolkit/src/query/endpointDefinitions.ts @@ -1,4 +1,4 @@ -import type { AnyAction, ThunkDispatch } from '@reduxjs/toolkit' +import type { ThunkDispatch, UnknownAction } from '@reduxjs/toolkit' import type { SerializeQueryArgs } from './defaultSerializeQueryArgs' import type { QuerySubState, RootState } from './core/apiState' import type { @@ -231,7 +231,7 @@ export type ResultDescription< /** @deprecated please use `onQueryStarted` instead */ export interface QueryApi { /** @deprecated please use `onQueryStarted` instead */ - dispatch: ThunkDispatch + dispatch: ThunkDispatch /** @deprecated please use `onQueryStarted` instead */ getState(): RootState /** @deprecated please use `onQueryStarted` instead */ diff --git a/packages/toolkit/src/query/react/buildHooks.ts b/packages/toolkit/src/query/react/buildHooks.ts index 9d89188514..474f3bc623 100644 --- a/packages/toolkit/src/query/react/buildHooks.ts +++ b/packages/toolkit/src/query/react/buildHooks.ts @@ -1,4 +1,8 @@ -import type { AnyAction, ThunkAction, ThunkDispatch } from '@reduxjs/toolkit' +import type { + ThunkAction, + ThunkDispatch, + UnknownAction, +} from '@reduxjs/toolkit' import { createSelector } from '@reduxjs/toolkit' import type { Selector } from '@reduxjs/toolkit' import type { DependencyList } from 'react' @@ -567,7 +571,7 @@ type GenericPrefetchThunk = ( endpointName: any, arg: any, options: PrefetchOptions -) => ThunkAction +) => ThunkAction /** * @@ -654,7 +658,7 @@ export function buildHooks({ endpointName: EndpointName, defaultOptions?: PrefetchOptions ) { - const dispatch = useDispatch>() + const dispatch = useDispatch>() const stableDefaultOptions = useShallowStableValue(defaultOptions) return useCallback( @@ -684,7 +688,7 @@ export function buildHooks({ QueryDefinition, Definitions > - const dispatch = useDispatch>() + const dispatch = useDispatch>() const stableArg = useStableQueryArgs( skip ? skipToken : arg, // Even if the user provided a per-endpoint `serializeQueryArgs` with @@ -818,7 +822,7 @@ export function buildHooks({ QueryDefinition, Definitions > - const dispatch = useDispatch>() + const dispatch = useDispatch>() const [arg, setArg] = useState(UNINITIALIZED_VALUE) const promiseRef = useRef | undefined>() @@ -989,7 +993,7 @@ export function buildHooks({ MutationDefinition, Definitions > - const dispatch = useDispatch>() + const dispatch = useDispatch>() const [promise, setPromise] = useState>() useEffect( diff --git a/packages/toolkit/src/query/tests/buildHooks.test.tsx b/packages/toolkit/src/query/tests/buildHooks.test.tsx index 3f297c3dfb..b0ee69dde9 100644 --- a/packages/toolkit/src/query/tests/buildHooks.test.tsx +++ b/packages/toolkit/src/query/tests/buildHooks.test.tsx @@ -1,5 +1,6 @@ import * as React from 'react' -import { vi, SpyInstance } from 'vitest' +import type { SpyInstance } from 'vitest' +import { vi } from 'vitest' import type { UseMutation, UseQuery, @@ -31,7 +32,7 @@ import { waitMs, } from './helpers' import { server } from './mocks/server' -import type { AnyAction } from 'redux' +import type { UnknownAction } from 'redux' import type { SubscriptionOptions } from '@reduxjs/toolkit/dist/query/core/apiState' import type { SerializedError } from '@reduxjs/toolkit' import { createListenerMiddleware, configureStore } from '@reduxjs/toolkit' @@ -125,7 +126,7 @@ const api = createApi({ const listenerMiddleware = createListenerMiddleware() -let actions: AnyAction[] = [] +let actions: UnknownAction[] = [] const storeRef = setupApiStore( api, @@ -1748,7 +1749,7 @@ describe('hooks tests', () => { }) const storeRef = setupApiStore(api, { - actions(state: AnyAction[] = [], action: AnyAction) { + actions(state: UnknownAction[] = [], action: UnknownAction) { return [...state, action] }, }) diff --git a/packages/toolkit/src/query/tests/errorHandling.test.tsx b/packages/toolkit/src/query/tests/errorHandling.test.tsx index b6a258e1a8..96a3e0cc63 100644 --- a/packages/toolkit/src/query/tests/errorHandling.test.tsx +++ b/packages/toolkit/src/query/tests/errorHandling.test.tsx @@ -6,9 +6,16 @@ import type { AxiosError, AxiosRequestConfig, AxiosResponse } from 'axios' import axios from 'axios' import { expectExactType, hookWaitFor, setupApiStore } from './helpers' import { server } from './mocks/server' -import { fireEvent, render, waitFor, screen, act, renderHook } from '@testing-library/react' +import { + fireEvent, + render, + waitFor, + screen, + act, + renderHook, +} from '@testing-library/react' import { useDispatch } from 'react-redux' -import type { AnyAction, ThunkDispatch } from '@reduxjs/toolkit' +import type { UnknownAction, ThunkDispatch } from '@reduxjs/toolkit' import type { BaseQueryApi } from '../baseQueryTypes' const baseQuery = fetchBaseQuery({ baseUrl: 'https://example.com' }) @@ -549,7 +556,11 @@ describe('error handling in a component', () => { test(`an un-subscribed mutation will still return something useful (success case, track: ${track})`, async () => { const hook = renderHook(useDispatch, { wrapper: storeRef.wrapper }) - const dispatch = hook.result.current as ThunkDispatch + const dispatch = hook.result.current as ThunkDispatch< + any, + any, + UnknownAction + > let mutationqueryFulfilled: ReturnType< ReturnType > @@ -567,7 +578,11 @@ describe('error handling in a component', () => { test(`an un-subscribed mutation will still return something useful (error case, track: ${track})`, async () => { const hook = renderHook(useDispatch, { wrapper: storeRef.wrapper }) - const dispatch = hook.result.current as ThunkDispatch + const dispatch = hook.result.current as ThunkDispatch< + any, + any, + UnknownAction + > let mutationqueryFulfilled: ReturnType< ReturnType > @@ -587,7 +602,11 @@ describe('error handling in a component', () => { test(`an un-subscribed mutation will still be unwrappable (success case), track: ${track}`, async () => { const hook = renderHook(useDispatch, { wrapper: storeRef.wrapper }) - const dispatch = hook.result.current as ThunkDispatch + const dispatch = hook.result.current as ThunkDispatch< + any, + any, + UnknownAction + > let mutationqueryFulfilled: ReturnType< ReturnType > @@ -605,7 +624,11 @@ describe('error handling in a component', () => { test(`an un-subscribed mutation will still be unwrappable (error case, track: ${track})`, async () => { const hook = renderHook(useDispatch, { wrapper: storeRef.wrapper }) - const dispatch = hook.result.current as ThunkDispatch + const dispatch = hook.result.current as ThunkDispatch< + any, + any, + UnknownAction + > let mutationqueryFulfilled: ReturnType< ReturnType > diff --git a/packages/toolkit/src/query/tests/helpers.tsx b/packages/toolkit/src/query/tests/helpers.tsx index f6d1f34c68..22e03583df 100644 --- a/packages/toolkit/src/query/tests/helpers.tsx +++ b/packages/toolkit/src/query/tests/helpers.tsx @@ -1,6 +1,6 @@ import React, { useCallback } from 'react' import type { - AnyAction, + UnknownAction, EnhancedStore, Middleware, Store, @@ -108,7 +108,7 @@ declare global { expect.extend({ toMatchSequence( - _actions: AnyAction[], + _actions: UnknownAction[], ...matchers: Array<(arg: any) => boolean> ) { const actions = _actions.concat() @@ -179,7 +179,7 @@ ${expectedOutput} }) export const actionsReducer = { - actions: (state: AnyAction[] = [], action: AnyAction) => { + actions: (state: UnknownAction[] = [], action: UnknownAction) => { return [...state, action] }, } @@ -226,7 +226,7 @@ export function setupApiStore< } & { [K in keyof R]: ReturnType }, - AnyAction, + UnknownAction, ReturnType extends EnhancedStore ? M : never diff --git a/packages/toolkit/src/tests/configureStore.typetest.ts b/packages/toolkit/src/tests/configureStore.typetest.ts index a74fefcbcf..3217fba89d 100644 --- a/packages/toolkit/src/tests/configureStore.typetest.ts +++ b/packages/toolkit/src/tests/configureStore.typetest.ts @@ -1,7 +1,7 @@ /* eslint-disable no-lone-blocks */ import type { Dispatch, - AnyAction, + UnknownAction, Middleware, Reducer, Store, @@ -52,10 +52,10 @@ const _anyMiddleware: any = () => () => () => {} { const reducer: Reducer = () => 0 const store = configureStore({ reducer }) - const numberStore: Store = store + const numberStore: Store = store // @ts-expect-error - const stringStore: Store = store + const stringStore: Store = store } /* @@ -146,7 +146,7 @@ const _anyMiddleware: any = () => () => () => {} enhancers: [applyMiddleware(() => (next) => next)], }) - expectType>( + expectType>( store.dispatch ) } @@ -188,7 +188,7 @@ const _anyMiddleware: any = () => () => () => {} ] as const, }) - expectType>( + expectType>( store.dispatch ) expectType(store.someProperty) @@ -390,18 +390,28 @@ const _anyMiddleware: any = () => () => () => {} void, {}, undefined, - AnyAction + UnknownAction >) // `null` for the `extra` generic was previously documented in the RTK "Advanced Tutorial", but // is a bad pattern and users should use `unknown` instead // @ts-expect-error - store.dispatch(function () {} as ThunkAction) + store.dispatch(function () {} as ThunkAction) // unknown is the best way to type a ThunkAction if you do not care // about the value of the extraArgument, as it will always work with every // ThunkMiddleware, no matter the actual extraArgument type - store.dispatch(function () {} as ThunkAction) + store.dispatch(function () {} as ThunkAction< + void, + {}, + unknown, + UnknownAction + >) // @ts-expect-error - store.dispatch(function () {} as ThunkAction) + store.dispatch(function () {} as ThunkAction< + void, + {}, + boolean, + UnknownAction + >) } /** @@ -601,8 +611,8 @@ const _anyMiddleware: any = () => () => () => {} // the thunk middleware type kicks in and TS thinks a plain action is being returned expectType< ((action: Action<'actionListenerMiddleware/add'>) => Unsubscribe) & - ThunkDispatch & - Dispatch + ThunkDispatch & + Dispatch >(store.dispatch) const unsubscribe = store.dispatch({ diff --git a/packages/toolkit/src/tests/createAction.typetest.tsx b/packages/toolkit/src/tests/createAction.typetest.tsx index 8574caa632..a78d802b50 100644 --- a/packages/toolkit/src/tests/createAction.typetest.tsx +++ b/packages/toolkit/src/tests/createAction.typetest.tsx @@ -1,5 +1,5 @@ import React from 'react' -import type { Action, AnyAction, ActionCreator } from 'redux' +import type { Action, UnknownAction, ActionCreator } from 'redux' import type { PayloadAction, PayloadActionCreator, @@ -92,7 +92,7 @@ import { expectType } from './helpers' }), { type: 'action' } ) as PayloadActionCreator - const actionCreator: ActionCreator = payloadActionCreator + const actionCreator: ActionCreator = payloadActionCreator const payloadActionCreator2 = Object.assign( (payload?: number) => ({ diff --git a/packages/toolkit/src/tests/createAsyncThunk.test.ts b/packages/toolkit/src/tests/createAsyncThunk.test.ts index 8208d8693e..fbc2b8a74a 100644 --- a/packages/toolkit/src/tests/createAsyncThunk.test.ts +++ b/packages/toolkit/src/tests/createAsyncThunk.test.ts @@ -1,5 +1,5 @@ import { vi } from 'vitest' -import type { AnyAction } from '@reduxjs/toolkit' +import type { UnknownAction } from '@reduxjs/toolkit' import { createAsyncThunk, unwrapResult, @@ -377,14 +377,14 @@ describe('createAsyncThunk with abortController', () => { ) let store = configureStore({ - reducer(store: AnyAction[] = []) { + reducer(store: UnknownAction[] = []) { return store }, }) beforeEach(() => { store = configureStore({ - reducer(store: AnyAction[] = [], action) { + reducer(store: UnknownAction[] = [], action) { return [...store, action] }, }) diff --git a/packages/toolkit/src/tests/createAsyncThunk.typetest.ts b/packages/toolkit/src/tests/createAsyncThunk.typetest.ts index 2bfc8c058e..e5e90a8e40 100644 --- a/packages/toolkit/src/tests/createAsyncThunk.typetest.ts +++ b/packages/toolkit/src/tests/createAsyncThunk.typetest.ts @@ -1,5 +1,9 @@ /* eslint-disable no-lone-blocks */ -import type { AnyAction, SerializedError, AsyncThunk } from '@reduxjs/toolkit' +import type { + UnknownAction, + SerializedError, + AsyncThunk, +} from '@reduxjs/toolkit' import { createAsyncThunk, createReducer, @@ -19,8 +23,8 @@ import type { } from '@internal/createAsyncThunk' const ANY = {} as any -const defaultDispatch = (() => {}) as ThunkDispatch<{}, any, AnyAction> -const anyAction = { type: 'foo' } as AnyAction +const defaultDispatch = (() => {}) as ThunkDispatch<{}, any, UnknownAction> +const unknownAction = { type: 'foo' } as UnknownAction // basic usage ;(async function () { @@ -90,7 +94,7 @@ const anyAction = { type: 'foo' } as AnyAction const correctDispatch = (() => {}) as ThunkDispatch< BookModel[], { userAPI: Function }, - AnyAction + UnknownAction > // Verify that the the first type args to createAsyncThunk line up right @@ -489,8 +493,8 @@ const anyAction = { type: 'foo' } as AnyAction serializeError: funkySerializeError, }) - if (shouldWork.rejected.match(anyAction)) { - expectType(anyAction.error) + if (shouldWork.rejected.match(unknownAction)) { + expectType(unknownAction.error) } } @@ -645,7 +649,7 @@ const anyAction = { type: 'foo' } as AnyAction // correct dispatch type const test2: number = api.dispatch((dispatch, getState) => { expectExactType< - ThunkDispatch<{ foo: { value: number } }, undefined, AnyAction> + ThunkDispatch<{ foo: { value: number } }, undefined, UnknownAction> >(ANY)(dispatch) expectExactType<() => { foo: { value: number } }>(ANY)(getState) return getState().foo.value @@ -671,7 +675,7 @@ const anyAction = { type: 'foo' } as AnyAction // correct dispatch type const test2: number = api.dispatch((dispatch, getState) => { expectExactType< - ThunkDispatch<{ foo: { value: number } }, undefined, AnyAction> + ThunkDispatch<{ foo: { value: number } }, undefined, UnknownAction> >(ANY)(dispatch) expectExactType<() => { foo: { value: number } }>(ANY)(getState) return getState().foo.value @@ -698,7 +702,7 @@ const anyAction = { type: 'foo' } as AnyAction // correct dispatch type const test2: number = api.dispatch((dispatch, getState) => { expectExactType< - ThunkDispatch<{ foo: { value: number } }, undefined, AnyAction> + ThunkDispatch<{ foo: { value: number } }, undefined, UnknownAction> >(ANY)(dispatch) expectExactType<() => { foo: { value: number } }>(ANY)(getState) return getState().foo.value diff --git a/packages/toolkit/src/tests/createReducer.test.ts b/packages/toolkit/src/tests/createReducer.test.ts index ee845e3b3a..8adbef99f2 100644 --- a/packages/toolkit/src/tests/createReducer.test.ts +++ b/packages/toolkit/src/tests/createReducer.test.ts @@ -4,8 +4,9 @@ import type { PayloadAction, Draft, Reducer, - AnyAction, + UnknownAction, } from '@reduxjs/toolkit' +import { isPlainObject } from '@reduxjs/toolkit' import { createReducer, createAction, createNextState } from '@reduxjs/toolkit' import { mockConsole, @@ -378,10 +379,19 @@ describe('createReducer', () => { meta: { type: 'string_action' }, }) - const numberActionMatcher = (a: AnyAction): a is PayloadAction => - a.meta && a.meta.type === 'number_action' - const stringActionMatcher = (a: AnyAction): a is PayloadAction => - a.meta && a.meta.type === 'string_action' + const numberActionMatcher = ( + a: UnknownAction + ): a is PayloadAction => + isPlainObject(a.meta) && + 'type' in a.meta && + a.meta.type === 'number_action' + + const stringActionMatcher = ( + a: UnknownAction + ): a is PayloadAction => + isPlainObject(a.meta) && + 'type' in a.meta && + a.meta.type === 'string_action' const incrementBy = createAction('increment', prepareNumberAction) const decrementBy = createAction('decrement', prepareNumberAction) diff --git a/packages/toolkit/src/tests/createSlice.typetest.ts b/packages/toolkit/src/tests/createSlice.typetest.ts index df1ce4f672..4a96181b23 100644 --- a/packages/toolkit/src/tests/createSlice.typetest.ts +++ b/packages/toolkit/src/tests/createSlice.typetest.ts @@ -1,4 +1,4 @@ -import type { Action, AnyAction, Reducer } from 'redux' +import type { Action, UnknownAction, Reducer } from 'redux' import type { ActionCreatorWithNonInferrablePayload, ActionCreatorWithOptionalPayload, @@ -74,7 +74,7 @@ const value = actionCreators.anyKey // @ts-expect-error expectType>(slice.reducer) // @ts-expect-error - expectType>(slice.reducer) + expectType>(slice.reducer) /* Actions */ slice.actions.increment(1) diff --git a/packages/toolkit/src/tests/getDefaultMiddleware.test.ts b/packages/toolkit/src/tests/getDefaultMiddleware.test.ts index 4da69101e3..081cb788aa 100644 --- a/packages/toolkit/src/tests/getDefaultMiddleware.test.ts +++ b/packages/toolkit/src/tests/getDefaultMiddleware.test.ts @@ -1,6 +1,6 @@ import { vi } from 'vitest' import type { - AnyAction, + UnknownAction, Middleware, ThunkAction, Action, @@ -93,20 +93,20 @@ describe('getDefaultMiddleware', () => { expectType< MiddlewareArray< [ - ThunkMiddleware, + ThunkMiddleware, Middleware< (action: Action<'actionListenerMiddleware/add'>) => () => void, { counter: number }, - Dispatch + Dispatch >, - Middleware<{}, any, Dispatch> + Middleware<{}, any, Dispatch> ] > >(m3) - const testThunk: ThunkAction = ( + const testThunk: ThunkAction = ( dispatch, getState, extraArg @@ -121,7 +121,7 @@ describe('getDefaultMiddleware', () => { middleware, }) - expectType & Dispatch>( + expectType & Dispatch>( store.dispatch ) diff --git a/packages/toolkit/src/tests/mapBuilders.typetest.ts b/packages/toolkit/src/tests/mapBuilders.typetest.ts index 42bfdd5a05..334572300e 100644 --- a/packages/toolkit/src/tests/mapBuilders.typetest.ts +++ b/packages/toolkit/src/tests/mapBuilders.typetest.ts @@ -1,7 +1,7 @@ import type { SerializedError } from '@internal/createAsyncThunk' import { createAsyncThunk } from '@internal/createAsyncThunk' import { executeReducerBuilderCallback } from '@internal/mapBuilders' -import type { AnyAction } from '@reduxjs/toolkit' +import type { UnknownAction } from '@reduxjs/toolkit' import { createAction } from '@reduxjs/toolkit' import { expectExactType, expectType } from './helpers' @@ -66,16 +66,16 @@ import { expectExactType, expectType } from './helpers' (action): action is PredicateWithoutTypeProperty => true, (state, action) => { expectType(action) - expectType(action) + expectType(action) } ) } - // action type defaults to AnyAction if no type predicate matcher is passed + // action type defaults to UnknownAction if no type predicate matcher is passed builder.addMatcher( () => true, (state, action) => { - expectExactType({} as AnyAction)(action) + expectExactType({} as UnknownAction)(action) } ) @@ -84,7 +84,7 @@ import { expectExactType, expectType } from './helpers' () => true, (state, action) => { expectType<{ foo: boolean }>(action) - expectType(action) + expectType(action) } ) @@ -98,14 +98,14 @@ import { expectExactType, expectType } from './helpers' expectType>(action) }) - // addCase().addDefaultCase() is possible, action type is AnyAction + // addCase().addDefaultCase() is possible, action type is UnknownAction builder .addCase( 'increment', (state, action: ReturnType) => state ) .addDefaultCase((state, action) => { - expectType(action) + expectType(action) }) { diff --git a/packages/toolkit/src/tests/matchers.test.ts b/packages/toolkit/src/tests/matchers.test.ts index 7282c5fa65..54dbbed37e 100644 --- a/packages/toolkit/src/tests/matchers.test.ts +++ b/packages/toolkit/src/tests/matchers.test.ts @@ -1,5 +1,5 @@ import { vi } from 'vitest' -import type { ThunkAction, AnyAction } from '@reduxjs/toolkit' +import type { ThunkAction, UnknownAction } from '@reduxjs/toolkit' import { isAllOf, isAnyOf, @@ -13,7 +13,7 @@ import { createReducer, } from '@reduxjs/toolkit' -const thunk: ThunkAction = () => {} +const thunk: ThunkAction = () => {} describe('isAnyOf', () => { it('returns true only if any matchers match (match function)', () => { diff --git a/packages/toolkit/src/tests/matchers.typetest.ts b/packages/toolkit/src/tests/matchers.typetest.ts index 8770983f01..f6f01b6e86 100644 --- a/packages/toolkit/src/tests/matchers.typetest.ts +++ b/packages/toolkit/src/tests/matchers.typetest.ts @@ -1,6 +1,6 @@ import { expectExactType, expectUnknown } from './helpers' import { IsUnknown } from '@internal/tsHelpers' -import type { AnyAction } from 'redux' +import type { UnknownAction } from 'redux' import type { SerializedError } from '../../src' import { createAction, @@ -19,7 +19,7 @@ import { /* * Test: isAnyOf correctly narrows types when used with action creators */ -function isAnyOfActionTest(action: AnyAction) { +function isAnyOfActionTest(action: UnknownAction) { const actionA = createAction('a', () => { return { payload: { @@ -52,7 +52,7 @@ function isAnyOfActionTest(action: AnyAction) { /* * Test: isAnyOf correctly narrows types when used with async thunks */ -function isAnyOfThunkTest(action: AnyAction) { +function isAnyOfThunkTest(action: UnknownAction) { const asyncThunk1 = createAsyncThunk<{ prop1: number; prop3: number }>( 'asyncThunk1', @@ -89,7 +89,7 @@ function isAnyOfThunkTest(action: AnyAction) { /* * Test: isAnyOf correctly narrows types when used with type guards */ -function isAnyOfTypeGuardTest(action: AnyAction) { +function isAnyOfTypeGuardTest(action: UnknownAction) { interface ActionA { type: 'a' payload: { @@ -141,7 +141,7 @@ const isSpecialAction = (v: any): v is SpecialAction => { * Test: isAllOf correctly narrows types when used with action creators * and type guards */ -function isAllOfActionTest(action: AnyAction) { +function isAllOfActionTest(action: UnknownAction) { const actionA = createAction('a', () => { return { payload: { @@ -166,7 +166,7 @@ function isAllOfActionTest(action: AnyAction) { * Test: isAllOf correctly narrows types when used with async thunks * and type guards */ -function isAllOfThunkTest(action: AnyAction) { +function isAllOfThunkTest(action: UnknownAction) { const asyncThunk1 = createAsyncThunk<{ prop1: number; prop3: number }>( 'asyncThunk1', @@ -192,7 +192,7 @@ function isAllOfThunkTest(action: AnyAction) { /* * Test: isAnyOf correctly narrows types when used with type guards */ -function isAllOfTypeGuardTest(action: AnyAction) { +function isAllOfTypeGuardTest(action: UnknownAction) { interface ActionA { type: 'a' payload: { @@ -219,7 +219,7 @@ function isAllOfTypeGuardTest(action: AnyAction) { /* * Test: isPending correctly narrows types */ -function isPendingTest(action: AnyAction) { +function isPendingTest(action: UnknownAction) { if (isPending(action)) { expectExactType(action.payload) // @ts-expect-error @@ -238,7 +238,7 @@ function isPendingTest(action: AnyAction) { /* * Test: isRejected correctly narrows types */ -function isRejectedTest(action: AnyAction) { +function isRejectedTest(action: UnknownAction) { if (isRejected(action)) { // might be there if rejected with payload expectUnknown(action.payload) @@ -257,7 +257,7 @@ function isRejectedTest(action: AnyAction) { /* * Test: isFulfilled correctly narrows types */ -function isFulfilledTest(action: AnyAction) { +function isFulfilledTest(action: UnknownAction) { if (isFulfilled(action)) { expectUnknown(action.payload) // @ts-expect-error @@ -275,7 +275,7 @@ function isFulfilledTest(action: AnyAction) { /* * Test: isAsyncThunkAction correctly narrows types */ -function isAsyncThunkActionTest(action: AnyAction) { +function isAsyncThunkActionTest(action: UnknownAction) { if (isAsyncThunkAction(action)) { expectUnknown(action.payload) // do not expect an error property because pending/fulfilled lack it @@ -296,7 +296,7 @@ function isAsyncThunkActionTest(action: AnyAction) { /* * Test: isRejectedWithValue correctly narrows types */ -function isRejectedWithValueTest(action: AnyAction) { +function isRejectedWithValueTest(action: UnknownAction) { if (isRejectedWithValue(action)) { expectUnknown(action.payload) expectExactType(action.error) diff --git a/packages/toolkit/src/tests/serializableStateInvariantMiddleware.test.ts b/packages/toolkit/src/tests/serializableStateInvariantMiddleware.test.ts index adf99bbf7a..7625246eea 100644 --- a/packages/toolkit/src/tests/serializableStateInvariantMiddleware.test.ts +++ b/packages/toolkit/src/tests/serializableStateInvariantMiddleware.test.ts @@ -3,7 +3,7 @@ import { createConsole, getLog, } from 'console-testing-library/pure' -import type { AnyAction, Reducer } from '@reduxjs/toolkit' +import type { Reducer } from '@reduxjs/toolkit' import { createNextState, configureStore, diff --git a/yarn.lock b/yarn.lock index 7e8417188d..acc21380d5 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6781,7 +6781,7 @@ __metadata: node-fetch: ^2.6.1 prettier: ^2.2.1 query-string: ^7.0.1 - redux: 5.0.0-alpha.4 + redux: "https://pkg.csb.dev/reduxjs/redux/commit/985fdefd/redux/_pkg.tgz" redux-thunk: 3.0.0-alpha.3 reselect: ^4.1.7 rimraf: ^3.0.2 @@ -24511,10 +24511,10 @@ fsevents@^1.2.7: languageName: node linkType: hard -"redux@npm:5.0.0-alpha.4": - version: 5.0.0-alpha.4 - resolution: "redux@npm:5.0.0-alpha.4" - checksum: ebc98a74d84341df6db87222b6e54a658d68233924315ff67b4b2988ca0ea359e39632f7a43b65ec361ea7ba5714de4e34d76cb0b20089e785d11dc9e5a9e85e +"redux@https://pkg.csb.dev/reduxjs/redux/commit/985fdefd/redux/_pkg.tgz": + version: 5.0.0-alpha.2 + resolution: "redux@https://pkg.csb.dev/reduxjs/redux/commit/985fdefd/redux/_pkg.tgz" + checksum: 727c459c36579cb36900a59f1267c93133c3358ddd8ba9bb191d8c5d4bb022dee7d6cd0bc80698a1e5def00d0e9bc878eee49ce21053b5ebb0f4ae528d6d1952 languageName: node linkType: hard From f6702d108d774cfb582bcb51910a461dab5e6955 Mon Sep 17 00:00:00 2001 From: Ben Durrant Date: Sun, 16 Apr 2023 16:42:29 +0100 Subject: [PATCH 121/412] rm NoInfer --- packages/toolkit/src/configureStore.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/toolkit/src/configureStore.ts b/packages/toolkit/src/configureStore.ts index 4208554a69..96d090e346 100644 --- a/packages/toolkit/src/configureStore.ts +++ b/packages/toolkit/src/configureStore.ts @@ -21,7 +21,6 @@ import { curryGetDefaultMiddleware } from './getDefaultMiddleware' import type { ExtractDispatchExtensions, ExtractStoreExtensions, - NoInfer, } from './tsHelpers' const IS_PRODUCTION = process.env.NODE_ENV === 'production' @@ -77,7 +76,7 @@ export interface ConfigureStoreOptions< * function (either directly or indirectly by passing an object as `reducer`), * this must be an object with the same shape as the reducer map keys. */ - preloadedState?: NoInfer

+ preloadedState?: P /** * The store enhancers to apply. See Redux's `createStore()`. From 8332c84b8407a6a327fd9a00152651cb8a1f9ad4 Mon Sep 17 00:00:00 2001 From: Nathan Bierema Date: Sun, 16 Apr 2023 15:40:37 -0400 Subject: [PATCH 122/412] Update types with PreloadedState generic --- packages/toolkit/package.json | 2 +- packages/toolkit/src/configureStore.ts | 27 +-- packages/toolkit/src/query/core/buildSlice.ts | 5 +- .../src/tests/configureStore.typetest.ts | 167 ++++++++++++++++-- yarn.lock | 10 +- 5 files changed, 176 insertions(+), 35 deletions(-) diff --git a/packages/toolkit/package.json b/packages/toolkit/package.json index 09b152f978..dc8350c407 100644 --- a/packages/toolkit/package.json +++ b/packages/toolkit/package.json @@ -113,7 +113,7 @@ ], "dependencies": { "immer": "^10.0.0-beta.4", - "redux": "5.0.0-alpha.4", + "redux": "5.0.0-alpha.5", "redux-thunk": "3.0.0-alpha.3", "reselect": "^4.1.7" }, diff --git a/packages/toolkit/src/configureStore.ts b/packages/toolkit/src/configureStore.ts index 76f5a0519c..fb2759b567 100644 --- a/packages/toolkit/src/configureStore.ts +++ b/packages/toolkit/src/configureStore.ts @@ -7,8 +7,6 @@ import type { StoreEnhancer, Store, Dispatch, - PreloadedState, - CombinedState, } from 'redux' import { createStore, compose, applyMiddleware, combineReducers } from 'redux' import type { DevToolsEnhancerOptions as DevToolsOptions } from './devtoolsExtension' @@ -21,7 +19,6 @@ import type { } from './getDefaultMiddleware' import { curryGetDefaultMiddleware } from './getDefaultMiddleware' import type { - NoInfer, ExtractDispatchExtensions, ExtractStoreExtensions, } from './tsHelpers' @@ -46,13 +43,16 @@ export interface ConfigureStoreOptions< S = any, A extends Action = AnyAction, M extends Middlewares = Middlewares, - E extends Enhancers = Enhancers + E extends Enhancers = Enhancers, + PreloadedState = S > { /** * A single reducer function that will be used as the root reducer, or an * object of slice reducers that will be passed to `combineReducers()`. */ - reducer: Reducer | ReducersMapObject + reducer: + | Reducer + | ReducersMapObject /** * An array of Redux middleware to install. If not supplied, defaults to @@ -87,7 +87,7 @@ export interface ConfigureStoreOptions< As we cannot distinguish between those two cases without adding another generic parameter, we just make the pragmatic assumption that the latter almost never happens. */ - preloadedState?: PreloadedState>> + preloadedState?: PreloadedState /** * The store enhancers to apply. See Redux's `createStore()`. @@ -142,8 +142,11 @@ export function configureStore< S = any, A extends Action = AnyAction, M extends Middlewares = [ThunkMiddlewareFor], - E extends Enhancers = [StoreEnhancer] ->(options: ConfigureStoreOptions): EnhancedStore { + E extends Enhancers = [StoreEnhancer], + PreloadedState = S +>( + options: ConfigureStoreOptions +): EnhancedStore { const curriedGetDefaultMiddleware = curryGetDefaultMiddleware() const { @@ -154,12 +157,16 @@ export function configureStore< enhancers = undefined, } = options || {} - let rootReducer: Reducer + let rootReducer: Reducer if (typeof reducer === 'function') { rootReducer = reducer } else if (isPlainObject(reducer)) { - rootReducer = combineReducers(reducer) as unknown as Reducer + rootReducer = combineReducers(reducer) as unknown as Reducer< + S, + A, + PreloadedState + > } else { throw new Error( '"reducer" is a required argument, and must be a function or an object of functions that can be passed to combineReducers' diff --git a/packages/toolkit/src/query/core/buildSlice.ts b/packages/toolkit/src/query/core/buildSlice.ts index 3343b6dc3d..07b2f1aafc 100644 --- a/packages/toolkit/src/query/core/buildSlice.ts +++ b/packages/toolkit/src/query/core/buildSlice.ts @@ -10,7 +10,6 @@ import { prepareAutoBatched, } from '@reduxjs/toolkit' import type { - CombinedState as CombinedQueryState, QuerySubstateIdentifier, QuerySubState, MutationSubstateIdentifier, @@ -469,9 +468,7 @@ export function buildSlice({ }, }) - const combinedReducer = combineReducers< - CombinedQueryState - >({ + const combinedReducer = combineReducers({ queries: querySlice.reducer, mutations: mutationSlice.reducer, provided: invalidationSlice.reducer, diff --git a/packages/toolkit/src/tests/configureStore.typetest.ts b/packages/toolkit/src/tests/configureStore.typetest.ts index a74fefcbcf..d64ae35253 100644 --- a/packages/toolkit/src/tests/configureStore.typetest.ts +++ b/packages/toolkit/src/tests/configureStore.typetest.ts @@ -8,7 +8,7 @@ import type { Action, StoreEnhancer, } from 'redux' -import { applyMiddleware } from 'redux' +import { applyMiddleware, combineReducers } from 'redux' import type { PayloadAction, ConfigureStoreOptions } from '@reduxjs/toolkit' import { configureStore, @@ -130,8 +130,8 @@ const _anyMiddleware: any = () => () => () => {} }) configureStore({ - reducer: () => 0, // @ts-expect-error + reducer: (_: number) => 0, preloadedState: 'non-matching state type', }) } @@ -197,25 +197,162 @@ const _anyMiddleware: any = () => () => () => {} } /** - * Test: configureStore() state type inference works when specifying both a - * reducer object and a partial preloaded state. + * Test: Preloaded state typings */ { let counterReducer1: Reducer = () => 0 let counterReducer2: Reducer = () => 0 - const store = configureStore({ - reducer: { - counter1: counterReducer1, - counter2: counterReducer2, - }, - preloadedState: { - counter1: 0, - }, - }) + /** + * Test: partial preloaded state + */ + { + const store = configureStore({ + reducer: { + counter1: counterReducer1, + counter2: counterReducer2, + }, + preloadedState: { + counter1: 0, + }, + }) + + const counter1: number = store.getState().counter1 + const counter2: number = store.getState().counter2 + } + + /** + * Test: empty preloaded state + */ + { + const store = configureStore({ + reducer: { + counter1: counterReducer1, + counter2: counterReducer2, + }, + preloadedState: {}, + }) + + const counter1: number = store.getState().counter1 + const counter2: number = store.getState().counter2 + } + + /** + * Test: excess properties in preloaded state + */ + { + const store = configureStore({ + reducer: { + // @ts-expect-error + counter1: counterReducer1, + counter2: counterReducer2, + }, + preloadedState: { + counter1: 0, + counter3: 5, + }, + }) + + const counter1: number = store.getState().counter1 + const counter2: number = store.getState().counter2 + } + + /** + * Test: mismatching properties in preloaded state + */ + { + const store = configureStore({ + reducer: { + // @ts-expect-error + counter1: counterReducer1, + counter2: counterReducer2, + }, + preloadedState: { + counter3: 5, + }, + }) + + const counter1: number = store.getState().counter1 + const counter2: number = store.getState().counter2 + } + + /** + * Test: string preloaded state when expecting object + */ + { + const store = configureStore({ + reducer: { + // @ts-expect-error + counter1: counterReducer1, + counter2: counterReducer2, + }, + preloadedState: 'test', + }) + + const counter1: number = store.getState().counter1 + const counter2: number = store.getState().counter2 + } - const counter1: number = store.getState().counter1 - const counter2: number = store.getState().counter2 + /** + * Test: nested combineReducers allows partial + */ + { + const store = configureStore({ + reducer: { + group1: combineReducers({ + counter1: counterReducer1, + counter2: counterReducer2, + }), + group2: combineReducers({ + counter1: counterReducer1, + counter2: counterReducer2, + }), + }, + preloadedState: { + group1: { + counter1: 5, + }, + }, + }) + + const group1counter1: number = store.getState().group1.counter1 + const group1counter2: number = store.getState().group1.counter2 + const group2counter1: number = store.getState().group2.counter1 + const group2counter2: number = store.getState().group2.counter2 + } + + /** + * Test: non-nested combineReducers does not allow partial + */ + { + interface GroupState { + counter1: number + counter2: number + } + + const initialState = { counter1: 0, counter2: 0 } + + const group1Reducer: Reducer = (state = initialState) => state + const group2Reducer: Reducer = (state = initialState) => state + + const store = configureStore({ + reducer: { + // @ts-expect-error + group1: group1Reducer, + group2: group2Reducer, + }, + preloadedState: { + group1: { + counter1: 5, + }, + }, + }) + + const group1counter1: number = store.getState().group1.counter1 + const group1counter2: number = store.getState().group1.counter2 + const group2counter1: number = store.getState().group2.counter1 + const group2counter2: number = store.getState().group2.counter2 + } } /** diff --git a/yarn.lock b/yarn.lock index a18e5d0971..5722eba5b5 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6781,7 +6781,7 @@ __metadata: node-fetch: ^2.6.1 prettier: ^2.2.1 query-string: ^7.0.1 - redux: 5.0.0-alpha.4 + redux: 5.0.0-alpha.5 redux-thunk: 3.0.0-alpha.3 reselect: ^4.1.7 rimraf: ^3.0.2 @@ -24576,10 +24576,10 @@ fsevents@^1.2.7: languageName: node linkType: hard -"redux@npm:5.0.0-alpha.4": - version: 5.0.0-alpha.4 - resolution: "redux@npm:5.0.0-alpha.4" - checksum: ebc98a74d84341df6db87222b6e54a658d68233924315ff67b4b2988ca0ea359e39632f7a43b65ec361ea7ba5714de4e34d76cb0b20089e785d11dc9e5a9e85e +"redux@npm:5.0.0-alpha.5": + version: 5.0.0-alpha.5 + resolution: "redux@npm:5.0.0-alpha.5" + checksum: 4223be43f605c0d514d5d611a281ae703f905ed4c6014c81b55d1f59cdeac38e3c82fcee2671b102f6b681d95c2a6a9a0f598e044916378011fa0aa39dc644ad languageName: node linkType: hard From 748a32b5acaeacf9af37f068de7ca2d112e07ffc Mon Sep 17 00:00:00 2001 From: "ben.durrant" Date: Mon, 17 Apr 2023 11:52:45 +0100 Subject: [PATCH 123/412] account for new Middleware typing --- packages/toolkit/src/createAction.ts | 8 +++-- packages/toolkit/src/index.ts | 2 ++ .../toolkit/src/listenerMiddleware/index.ts | 7 ++++- .../src/query/core/buildMiddleware/index.ts | 5 +++- .../src/query/core/buildMiddleware/types.ts | 8 +++-- .../serializableStateInvariantMiddleware.ts | 10 ++++++- .../immutableStateInvariantMiddleware.test.ts | 30 ++++++++++--------- 7 files changed, 48 insertions(+), 22 deletions(-) diff --git a/packages/toolkit/src/createAction.ts b/packages/toolkit/src/createAction.ts index 0647a04621..0200b8bcb3 100644 --- a/packages/toolkit/src/createAction.ts +++ b/packages/toolkit/src/createAction.ts @@ -286,6 +286,10 @@ export function createAction(type: string, prepareAction?: Function): any { return actionCreator } +export function isAction(action: unknown): action is Action { + return isPlainObject(action) && 'type' in action +} + export function isFSA(action: unknown): action is { type: string payload?: unknown @@ -293,8 +297,8 @@ export function isFSA(action: unknown): action is { meta?: unknown } { return ( - isPlainObject(action) && - typeof (action as any).type === 'string' && + isAction(action) && + typeof action.type === 'string' && Object.keys(action).every(isValidKey) ) } diff --git a/packages/toolkit/src/index.ts b/packages/toolkit/src/index.ts index 0c8737e8b4..d7fd5fbf4a 100644 --- a/packages/toolkit/src/index.ts +++ b/packages/toolkit/src/index.ts @@ -32,6 +32,8 @@ export { // js createAction, getType, + isAction, + isFSA as isFluxStandardAction, } from './createAction' export type { // types diff --git a/packages/toolkit/src/listenerMiddleware/index.ts b/packages/toolkit/src/listenerMiddleware/index.ts index d96b9257db..9b154b08fd 100644 --- a/packages/toolkit/src/listenerMiddleware/index.ts +++ b/packages/toolkit/src/listenerMiddleware/index.ts @@ -1,6 +1,6 @@ import type { Dispatch, AnyAction, MiddlewareAPI } from 'redux' import type { ThunkDispatch } from 'redux-thunk' -import { createAction } from '../createAction' +import { createAction, isAction } from '../createAction' import { nanoid } from '../nanoid' import type { @@ -426,6 +426,11 @@ export function createListenerMiddleware< const middleware: ListenerMiddleware = (api) => (next) => (action) => { + if (!isAction(action)) { + // this means that the listeners can't react to anything that doesn't look like an action (plain object with .type property) + // but that matches the typing, so i think that's fine? + return next(action) + } if (addListener.match(action)) { return startListening(action.payload) } diff --git a/packages/toolkit/src/query/core/buildMiddleware/index.ts b/packages/toolkit/src/query/core/buildMiddleware/index.ts index 810839e333..1ef1103fe3 100644 --- a/packages/toolkit/src/query/core/buildMiddleware/index.ts +++ b/packages/toolkit/src/query/core/buildMiddleware/index.ts @@ -1,5 +1,5 @@ import type { AnyAction, Middleware, ThunkDispatch } from '@reduxjs/toolkit' -import { createAction } from '@reduxjs/toolkit' +import { isAction, createAction } from '@reduxjs/toolkit' import type { EndpointDefinitions, @@ -80,6 +80,9 @@ export function buildMiddleware< return (next) => { return (action) => { + if (!isAction(action)) { + return next(action) + } if (!initialized) { initialized = true // dispatch before any other action diff --git a/packages/toolkit/src/query/core/buildMiddleware/types.ts b/packages/toolkit/src/query/core/buildMiddleware/types.ts index 20e23a4ac8..78e6a53e90 100644 --- a/packages/toolkit/src/query/core/buildMiddleware/types.ts +++ b/packages/toolkit/src/query/core/buildMiddleware/types.ts @@ -71,11 +71,13 @@ export type SubMiddlewareBuilder = ( ThunkDispatch > -export type ApiMiddlewareInternalHandler = ( +type MwNext = Parameters>[0] + +export type ApiMiddlewareInternalHandler = ( action: AnyAction, - mwApi: SubMiddlewareApi & { next: Dispatch }, + mwApi: SubMiddlewareApi & { next: MwNext }, prevState: RootState -) => ReturnType +) => Return export type InternalHandlerBuilder = ( input: BuildSubMiddlewareInput diff --git a/packages/toolkit/src/serializableStateInvariantMiddleware.ts b/packages/toolkit/src/serializableStateInvariantMiddleware.ts index 0026091fe6..dc6f5823eb 100644 --- a/packages/toolkit/src/serializableStateInvariantMiddleware.ts +++ b/packages/toolkit/src/serializableStateInvariantMiddleware.ts @@ -1,6 +1,7 @@ import isPlainObject from './isPlainObject' import type { Middleware } from 'redux' import { getTimeMeasureUtils } from './utils' +import { isAction } from './createAction' /** * Returns true if the passed value is "plain", i.e. a value that is either @@ -207,6 +208,10 @@ export function createSerializableStateInvariantMiddleware( !disableCache && WeakSet ? new WeakSet() : undefined return (storeAPI) => (next) => (action) => { + if (!isAction(action)) { + return next(action) + } + const result = next(action) const measureUtils = getTimeMeasureUtils( @@ -216,7 +221,10 @@ export function createSerializableStateInvariantMiddleware( if ( !ignoreActions && - !(ignoredActions.length && ignoredActions.indexOf(action.type) !== -1) + !( + ignoredActions.length && + ignoredActions.indexOf(action.type as any) !== -1 + ) ) { measureUtils.measureTime(() => { const foundActionNonSerializableValue = findNonSerializableValue( diff --git a/packages/toolkit/src/tests/immutableStateInvariantMiddleware.test.ts b/packages/toolkit/src/tests/immutableStateInvariantMiddleware.test.ts index b3c9b4a86c..0bf734e019 100644 --- a/packages/toolkit/src/tests/immutableStateInvariantMiddleware.test.ts +++ b/packages/toolkit/src/tests/immutableStateInvariantMiddleware.test.ts @@ -3,6 +3,7 @@ import type { MiddlewareAPI, Dispatch, ImmutableStateInvariantMiddlewareOptions, + Middleware, } from '@reduxjs/toolkit' import { createImmutableStateInvariantMiddleware, @@ -16,6 +17,8 @@ import { getLog, } from 'console-testing-library/pure' +type MWNext = Parameters>[0] + describe('createImmutableStateInvariantMiddleware', () => { let state: { foo: { bar: number[]; baz: string } } const getState: Store['getState'] = () => state @@ -31,17 +34,16 @@ describe('createImmutableStateInvariantMiddleware', () => { }) it('sends the action through the middleware chain', () => { - const next: Dispatch = (action) => ({ ...action, returned: true }) - const dispatch = middleware()(next) + const next: MWNext = vi.fn() + middleware()(next) - expect(dispatch({ type: 'SOME_ACTION' })).toEqual({ + expect(next).toHaveBeenCalledWith({ type: 'SOME_ACTION', - returned: true, }) }) it('throws if mutating inside the dispatch', () => { - const next: Dispatch = (action) => { + const next: MWNext = (action) => { state.foo.bar.push(5) return action } @@ -54,7 +56,7 @@ describe('createImmutableStateInvariantMiddleware', () => { }) it('throws if mutating between dispatches', () => { - const next: Dispatch = (action) => action + const next: MWNext = (action) => action const dispatch = middleware()(next) @@ -66,7 +68,7 @@ describe('createImmutableStateInvariantMiddleware', () => { }) it('does not throw if not mutating inside the dispatch', () => { - const next: Dispatch = (action) => { + const next: MWNext = (action) => { state = { ...state, foo: { ...state.foo, baz: 'changed!' } } return action } @@ -79,7 +81,7 @@ describe('createImmutableStateInvariantMiddleware', () => { }) it('does not throw if not mutating between dispatches', () => { - const next: Dispatch = (action) => action + const next: MWNext = (action) => action const dispatch = middleware()(next) @@ -91,7 +93,7 @@ describe('createImmutableStateInvariantMiddleware', () => { }) it('works correctly with circular references', () => { - const next: Dispatch = (action) => action + const next: MWNext = (action) => action const dispatch = middleware()(next) @@ -107,7 +109,7 @@ describe('createImmutableStateInvariantMiddleware', () => { it('respects "isImmutable" option', function () { const isImmutable = (value: any) => true - const next: Dispatch = (action) => { + const next: MWNext = (action) => { state.foo.bar.push(5) return action } @@ -120,7 +122,7 @@ describe('createImmutableStateInvariantMiddleware', () => { }) it('respects "ignoredPaths" option', () => { - const next: Dispatch = (action) => { + const next: MWNext = (action) => { state.foo.bar.push(5) return action } @@ -139,7 +141,7 @@ describe('createImmutableStateInvariantMiddleware', () => { }) it('alias "ignore" to "ignoredPath" and respects option', () => { - const next: Dispatch = (action) => { + const next: MWNext = (action) => { state.foo.bar.push(5) return action } @@ -154,7 +156,7 @@ describe('createImmutableStateInvariantMiddleware', () => { it('Should print a warning if execution takes too long', () => { state.foo.bar = new Array(10000).fill({ value: 'more' }) - const next: Dispatch = (action) => action + const next: MWNext = (action) => action const dispatch = middleware({ warnAfter: 4 })(next) @@ -170,7 +172,7 @@ describe('createImmutableStateInvariantMiddleware', () => { }) it('Should not print a warning if "next" takes too long', () => { - const next: Dispatch = (action) => { + const next: MWNext = (action) => { const started = Date.now() while (Date.now() - started < 8) {} return action From b865fb110f9c642bd04fd4391ee5d2078738999e Mon Sep 17 00:00:00 2001 From: "ben.durrant" Date: Mon, 17 Apr 2023 12:00:07 +0100 Subject: [PATCH 124/412] consistency --- packages/toolkit/src/configureStore.ts | 32 +++++++------------------- 1 file changed, 8 insertions(+), 24 deletions(-) diff --git a/packages/toolkit/src/configureStore.ts b/packages/toolkit/src/configureStore.ts index fb2759b567..c2f7204223 100644 --- a/packages/toolkit/src/configureStore.ts +++ b/packages/toolkit/src/configureStore.ts @@ -44,15 +44,13 @@ export interface ConfigureStoreOptions< A extends Action = AnyAction, M extends Middlewares = Middlewares, E extends Enhancers = Enhancers, - PreloadedState = S + P = S > { /** * A single reducer function that will be used as the root reducer, or an * object of slice reducers that will be passed to `combineReducers()`. */ - reducer: - | Reducer - | ReducersMapObject + reducer: Reducer | ReducersMapObject /** * An array of Redux middleware to install. If not supplied, defaults to @@ -78,16 +76,8 @@ export interface ConfigureStoreOptions< * function (either directly or indirectly by passing an object as `reducer`), * this must be an object with the same shape as the reducer map keys. */ - /* - Not 100% correct but the best approximation we can get: - - if S is a `CombinedState` applying a second `CombinedState` on it does not change anything. - - if it is not, there could be two cases: - - `ReducersMapObject` is being passed in. In this case, we will call `combineReducers` on it and `CombinedState` is correct - - `Reducer` is being passed in. In this case, actually `CombinedState` is wrong and `S` would be correct. - As we cannot distinguish between those two cases without adding another generic parameter, - we just make the pragmatic assumption that the latter almost never happens. - */ - preloadedState?: PreloadedState + // we infer here, and instead complain if the reducer doesn't match + preloadedState?: P /** * The store enhancers to apply. See Redux's `createStore()`. @@ -143,10 +133,8 @@ export function configureStore< A extends Action = AnyAction, M extends Middlewares = [ThunkMiddlewareFor], E extends Enhancers = [StoreEnhancer], - PreloadedState = S ->( - options: ConfigureStoreOptions -): EnhancedStore { + P = S +>(options: ConfigureStoreOptions): EnhancedStore { const curriedGetDefaultMiddleware = curryGetDefaultMiddleware() const { @@ -157,16 +145,12 @@ export function configureStore< enhancers = undefined, } = options || {} - let rootReducer: Reducer + let rootReducer: Reducer if (typeof reducer === 'function') { rootReducer = reducer } else if (isPlainObject(reducer)) { - rootReducer = combineReducers(reducer) as unknown as Reducer< - S, - A, - PreloadedState - > + rootReducer = combineReducers(reducer) as unknown as Reducer } else { throw new Error( '"reducer" is a required argument, and must be a function or an object of functions that can be passed to combineReducers' From b8f97379e14e9972f838697fd7c888c833a9323b Mon Sep 17 00:00:00 2001 From: "ben.durrant" Date: Mon, 17 Apr 2023 12:17:59 +0100 Subject: [PATCH 125/412] fix immutable test --- .../src/tests/immutableStateInvariantMiddleware.test.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/toolkit/src/tests/immutableStateInvariantMiddleware.test.ts b/packages/toolkit/src/tests/immutableStateInvariantMiddleware.test.ts index 0bf734e019..c569809e22 100644 --- a/packages/toolkit/src/tests/immutableStateInvariantMiddleware.test.ts +++ b/packages/toolkit/src/tests/immutableStateInvariantMiddleware.test.ts @@ -1,7 +1,6 @@ import type { Store, MiddlewareAPI, - Dispatch, ImmutableStateInvariantMiddlewareOptions, Middleware, } from '@reduxjs/toolkit' @@ -35,7 +34,8 @@ describe('createImmutableStateInvariantMiddleware', () => { it('sends the action through the middleware chain', () => { const next: MWNext = vi.fn() - middleware()(next) + const dispatch = middleware()(next) + dispatch({ type: 'SOME_ACTION' }) expect(next).toHaveBeenCalledWith({ type: 'SOME_ACTION', From 147429c85e91571a44c104f6c0c107254d821aba Mon Sep 17 00:00:00 2001 From: "ben.durrant" Date: Mon, 17 Apr 2023 12:24:25 +0100 Subject: [PATCH 126/412] fix forkAPI test --- packages/toolkit/src/listenerMiddleware/tests/fork.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/toolkit/src/listenerMiddleware/tests/fork.test.ts b/packages/toolkit/src/listenerMiddleware/tests/fork.test.ts index 2756e09f15..6d9e6abb56 100644 --- a/packages/toolkit/src/listenerMiddleware/tests/fork.test.ts +++ b/packages/toolkit/src/listenerMiddleware/tests/fork.test.ts @@ -367,7 +367,7 @@ describe('fork', () => { }, }) - store.dispatch(increment) + store.dispatch(increment()) expect(await deferredResult).toBe(listenerCompleted) }) From 529243397836372476a06270dc4135a225e5c162 Mon Sep 17 00:00:00 2001 From: Mark Erikson Date: Tue, 18 Apr 2023 00:00:44 -0400 Subject: [PATCH 127/412] Bump Immer to 10.0.1 final --- packages/toolkit/package.json | 4 ++-- yarn.lock | 14 +++++++------- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/packages/toolkit/package.json b/packages/toolkit/package.json index dc8350c407..e42ea3b038 100644 --- a/packages/toolkit/package.json +++ b/packages/toolkit/package.json @@ -112,10 +112,10 @@ "query" ], "dependencies": { - "immer": "^10.0.0-beta.4", + "immer": "^10.0.1", "redux": "5.0.0-alpha.5", "redux-thunk": "3.0.0-alpha.3", - "reselect": "^4.1.7" + "reselect": "^4.1.8" }, "peerDependencies": { "react": "^16.9.0 || ^17.0.0 || ^18", diff --git a/yarn.lock b/yarn.lock index 043f406e0f..01638b4d47 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6771,7 +6771,7 @@ __metadata: eslint-plugin-react: ^7.23.2 eslint-plugin-react-hooks: ^4.2.0 fs-extra: ^9.1.0 - immer: ^10.0.0-beta.4 + immer: ^10.0.1 invariant: ^2.2.4 jsdom: ^21.0.0 json-stringify-safe: ^5.0.1 @@ -6783,7 +6783,7 @@ __metadata: query-string: ^7.0.1 redux: 5.0.0-alpha.5 redux-thunk: 3.0.0-alpha.3 - reselect: ^4.1.7 + reselect: ^4.1.8 rimraf: ^3.0.2 rollup: ^2.47.0 rollup-plugin-strip-code: ^0.2.6 @@ -16790,10 +16790,10 @@ fsevents@^1.2.7: languageName: node linkType: hard -"immer@npm:^10.0.0-beta.4": - version: 10.0.0-beta.4 - resolution: "immer@npm:10.0.0-beta.4" - checksum: 121d89855b91d83e415d30ab83cf1ce52aae24fee704980da0d3e34ea5f7be2c9febce2457f0ff133a68da6aafdf243e8c947d1bc0a6d6ef40137118213187a6 +"immer@npm:^10.0.1": + version: 10.0.1 + resolution: "immer@npm:10.0.1" + checksum: 24ab640e9d928385047f6f31bc77966117087236faf1a1f52b6b9a0159f78d543f648fecbf04d5e8c782a6f6cddd6eeb9e09c3b616a104632e701c7a22735654 languageName: node linkType: hard @@ -25046,7 +25046,7 @@ fsevents@^1.2.7: languageName: node linkType: hard -"reselect@npm:^4.1.7": +"reselect@npm:^4.1.7, reselect@npm:^4.1.8": version: 4.1.8 resolution: "reselect@npm:4.1.8" checksum: a4ac87cedab198769a29be92bc221c32da76cfdad6911eda67b4d3e7136dca86208c3b210e31632eae31ebd2cded18596f0dd230d3ccc9e978df22f233b5583e From 02401614899df55052e2b44b4b44dc89dbb54346 Mon Sep 17 00:00:00 2001 From: "ben.durrant" Date: Tue, 18 Apr 2023 10:41:58 +0100 Subject: [PATCH 128/412] fix configureStore --- packages/toolkit/src/configureStore.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/toolkit/src/configureStore.ts b/packages/toolkit/src/configureStore.ts index be529a2fd5..8bae937181 100644 --- a/packages/toolkit/src/configureStore.ts +++ b/packages/toolkit/src/configureStore.ts @@ -117,8 +117,8 @@ export function configureStore< [StoreEnhancer<{ dispatch: ExtractDispatchExtensions }>, StoreEnhancer] >, P = S ->(options: ConfigureStoreOptions): EnhancedStore { - const curriedGetDefaultMiddleware = curryGetDefaultMiddleware() +>(options: ConfigureStoreOptions): EnhancedStore { + const getDefaultMiddleware = buildGetDefaultMiddleware() const { reducer = undefined, From 94b7945c01d000e14cc75a6e3da32c92d6c65118 Mon Sep 17 00:00:00 2001 From: Mark Erikson Date: Tue, 18 Apr 2023 10:53:19 -0400 Subject: [PATCH 129/412] Allow partial preloaded state for combined slice reducer --- packages/toolkit/src/combineSlices.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/toolkit/src/combineSlices.ts b/packages/toolkit/src/combineSlices.ts index 488276b516..2ef5ad7a1e 100644 --- a/packages/toolkit/src/combineSlices.ts +++ b/packages/toolkit/src/combineSlices.ts @@ -56,7 +56,7 @@ export type InjectConfig = { export interface CombinedSliceReducer< InitialState, DeclaredState = InitialState -> extends Reducer { +> extends Reducer> { /** * Provide a type for slices that will be injected lazily. * From e8c09186471be7c7cbacb8cd911ae722a97e45a1 Mon Sep 17 00:00:00 2001 From: "ben.durrant" Date: Tue, 18 Apr 2023 15:56:15 +0100 Subject: [PATCH 130/412] add react folder to files --- packages/toolkit/package.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/toolkit/package.json b/packages/toolkit/package.json index b01d8ac72a..6dd38be890 100644 --- a/packages/toolkit/package.json +++ b/packages/toolkit/package.json @@ -114,7 +114,8 @@ "files": [ "dist/", "src/", - "query" + "query", + "react" ], "dependencies": { "immer": "^10.0.0-beta.4", From 6dfe740ea685108cdbd36c4bb4fe1118c8b88a0f Mon Sep 17 00:00:00 2001 From: Mark Erikson Date: Tue, 18 Apr 2023 11:03:57 -0400 Subject: [PATCH 131/412] Update Vitest and assorted devDeps --- .codesandbox/ci.json | 2 +- package.json | 6 +- packages/toolkit/package.json | 10 +- yarn.lock | 581 +++++++++++++++++++++------------- 4 files changed, 359 insertions(+), 240 deletions(-) diff --git a/.codesandbox/ci.json b/.codesandbox/ci.json index 61b268432c..81f527788f 100644 --- a/.codesandbox/ci.json +++ b/.codesandbox/ci.json @@ -8,7 +8,7 @@ "/examples/action-listener/counter", "/examples/publish-ci/cra5" ], - "node": "14", + "node": "16", "buildCommand": "build:packages", "packages": [ "packages/toolkit", diff --git a/package.json b/package.json index 1353aaa4b6..c6183834ab 100644 --- a/package.json +++ b/package.json @@ -44,13 +44,13 @@ "@babel/helper-compilation-targets": "7.19.3", "@babel/traverse": "7.19.3", "@babel/types": "7.19.3", - "esbuild": "0.17.0", + "esbuild": "0.17.17", "jest-snapshot": "29.3.1", "msw": "patch:msw@npm:0.40.2#.yarn/patches/msw-npm-0.40.2-2107d48752", "jscodeshift": "0.13.1", "react-redux": "npm:8.0.2", - "react": "npm:18.1.0", - "react-dom": "npm:18.1.0", + "react": "npm:18.2.0", + "react-dom": "npm:18.2.0", "resolve": "1.22.1", "ts-node": "10.4.0", "@types/react": "npm:18.0.12", diff --git a/packages/toolkit/package.json b/packages/toolkit/package.json index e42ea3b038..3110b10eb1 100644 --- a/packages/toolkit/package.json +++ b/packages/toolkit/package.json @@ -49,7 +49,6 @@ "@testing-library/react": "^13.3.0", "@testing-library/user-event": "^13.1.5", "@types/convert-source-map": "^1.5.1", - "@types/jest": "^27", "@types/json-stringify-safe": "^5.0.0", "@types/nanoid": "^2.1.0", "@types/node": "^10.14.4", @@ -62,7 +61,6 @@ "axios": "^0.19.2", "console-testing-library": "0.6.1", "convert-source-map": "^1.7.0", - "esbuild": "~0.17", "eslint": "^7.25.0", "eslint-config-prettier": "^8.3.0", "eslint-config-react-app": "^7.0.1", @@ -76,23 +74,17 @@ "invariant": "^2.2.4", "jsdom": "^21.0.0", "json-stringify-safe": "^5.0.1", - "magic-string": "^0.25.7", - "merge-source-map": "^1.1.0", "msw": "^0.40.2", "node-fetch": "^2.6.1", "prettier": "^2.2.1", "query-string": "^7.0.1", "rimraf": "^3.0.2", - "rollup": "^2.47.0", - "rollup-plugin-strip-code": "^0.2.6", "size-limit": "^4.11.0", - "source-map": "^0.7.3", - "terser": "^5.6.1", "tslib": "^1.10.0", "tsup": "^6.7.0", "tsx": "^3.12.2", "typescript": "~4.9", - "vitest": "^0.27.2", + "vitest": "^0.30.1", "yargs": "^15.3.1" }, "scripts": { diff --git a/yarn.lock b/yarn.lock index 01638b4d47..692ea5a6b9 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4308,156 +4308,156 @@ __metadata: languageName: node linkType: hard -"@esbuild/android-arm64@npm:0.17.0": - version: 0.17.0 - resolution: "@esbuild/android-arm64@npm:0.17.0" +"@esbuild/android-arm64@npm:0.17.17": + version: 0.17.17 + resolution: "@esbuild/android-arm64@npm:0.17.17" conditions: os=android & cpu=arm64 languageName: node linkType: hard -"@esbuild/android-arm@npm:0.17.0": - version: 0.17.0 - resolution: "@esbuild/android-arm@npm:0.17.0" +"@esbuild/android-arm@npm:0.17.17": + version: 0.17.17 + resolution: "@esbuild/android-arm@npm:0.17.17" conditions: os=android & cpu=arm languageName: node linkType: hard -"@esbuild/android-x64@npm:0.17.0": - version: 0.17.0 - resolution: "@esbuild/android-x64@npm:0.17.0" +"@esbuild/android-x64@npm:0.17.17": + version: 0.17.17 + resolution: "@esbuild/android-x64@npm:0.17.17" conditions: os=android & cpu=x64 languageName: node linkType: hard -"@esbuild/darwin-arm64@npm:0.17.0": - version: 0.17.0 - resolution: "@esbuild/darwin-arm64@npm:0.17.0" +"@esbuild/darwin-arm64@npm:0.17.17": + version: 0.17.17 + resolution: "@esbuild/darwin-arm64@npm:0.17.17" conditions: os=darwin & cpu=arm64 languageName: node linkType: hard -"@esbuild/darwin-x64@npm:0.17.0": - version: 0.17.0 - resolution: "@esbuild/darwin-x64@npm:0.17.0" +"@esbuild/darwin-x64@npm:0.17.17": + version: 0.17.17 + resolution: "@esbuild/darwin-x64@npm:0.17.17" conditions: os=darwin & cpu=x64 languageName: node linkType: hard -"@esbuild/freebsd-arm64@npm:0.17.0": - version: 0.17.0 - resolution: "@esbuild/freebsd-arm64@npm:0.17.0" +"@esbuild/freebsd-arm64@npm:0.17.17": + version: 0.17.17 + resolution: "@esbuild/freebsd-arm64@npm:0.17.17" conditions: os=freebsd & cpu=arm64 languageName: node linkType: hard -"@esbuild/freebsd-x64@npm:0.17.0": - version: 0.17.0 - resolution: "@esbuild/freebsd-x64@npm:0.17.0" +"@esbuild/freebsd-x64@npm:0.17.17": + version: 0.17.17 + resolution: "@esbuild/freebsd-x64@npm:0.17.17" conditions: os=freebsd & cpu=x64 languageName: node linkType: hard -"@esbuild/linux-arm64@npm:0.17.0": - version: 0.17.0 - resolution: "@esbuild/linux-arm64@npm:0.17.0" +"@esbuild/linux-arm64@npm:0.17.17": + version: 0.17.17 + resolution: "@esbuild/linux-arm64@npm:0.17.17" conditions: os=linux & cpu=arm64 languageName: node linkType: hard -"@esbuild/linux-arm@npm:0.17.0": - version: 0.17.0 - resolution: "@esbuild/linux-arm@npm:0.17.0" +"@esbuild/linux-arm@npm:0.17.17": + version: 0.17.17 + resolution: "@esbuild/linux-arm@npm:0.17.17" conditions: os=linux & cpu=arm languageName: node linkType: hard -"@esbuild/linux-ia32@npm:0.17.0": - version: 0.17.0 - resolution: "@esbuild/linux-ia32@npm:0.17.0" +"@esbuild/linux-ia32@npm:0.17.17": + version: 0.17.17 + resolution: "@esbuild/linux-ia32@npm:0.17.17" conditions: os=linux & cpu=ia32 languageName: node linkType: hard -"@esbuild/linux-loong64@npm:0.17.0": - version: 0.17.0 - resolution: "@esbuild/linux-loong64@npm:0.17.0" +"@esbuild/linux-loong64@npm:0.17.17": + version: 0.17.17 + resolution: "@esbuild/linux-loong64@npm:0.17.17" conditions: os=linux & cpu=loong64 languageName: node linkType: hard -"@esbuild/linux-mips64el@npm:0.17.0": - version: 0.17.0 - resolution: "@esbuild/linux-mips64el@npm:0.17.0" +"@esbuild/linux-mips64el@npm:0.17.17": + version: 0.17.17 + resolution: "@esbuild/linux-mips64el@npm:0.17.17" conditions: os=linux & cpu=mips64el languageName: node linkType: hard -"@esbuild/linux-ppc64@npm:0.17.0": - version: 0.17.0 - resolution: "@esbuild/linux-ppc64@npm:0.17.0" +"@esbuild/linux-ppc64@npm:0.17.17": + version: 0.17.17 + resolution: "@esbuild/linux-ppc64@npm:0.17.17" conditions: os=linux & cpu=ppc64 languageName: node linkType: hard -"@esbuild/linux-riscv64@npm:0.17.0": - version: 0.17.0 - resolution: "@esbuild/linux-riscv64@npm:0.17.0" +"@esbuild/linux-riscv64@npm:0.17.17": + version: 0.17.17 + resolution: "@esbuild/linux-riscv64@npm:0.17.17" conditions: os=linux & cpu=riscv64 languageName: node linkType: hard -"@esbuild/linux-s390x@npm:0.17.0": - version: 0.17.0 - resolution: "@esbuild/linux-s390x@npm:0.17.0" +"@esbuild/linux-s390x@npm:0.17.17": + version: 0.17.17 + resolution: "@esbuild/linux-s390x@npm:0.17.17" conditions: os=linux & cpu=s390x languageName: node linkType: hard -"@esbuild/linux-x64@npm:0.17.0": - version: 0.17.0 - resolution: "@esbuild/linux-x64@npm:0.17.0" +"@esbuild/linux-x64@npm:0.17.17": + version: 0.17.17 + resolution: "@esbuild/linux-x64@npm:0.17.17" conditions: os=linux & cpu=x64 languageName: node linkType: hard -"@esbuild/netbsd-x64@npm:0.17.0": - version: 0.17.0 - resolution: "@esbuild/netbsd-x64@npm:0.17.0" +"@esbuild/netbsd-x64@npm:0.17.17": + version: 0.17.17 + resolution: "@esbuild/netbsd-x64@npm:0.17.17" conditions: os=netbsd & cpu=x64 languageName: node linkType: hard -"@esbuild/openbsd-x64@npm:0.17.0": - version: 0.17.0 - resolution: "@esbuild/openbsd-x64@npm:0.17.0" +"@esbuild/openbsd-x64@npm:0.17.17": + version: 0.17.17 + resolution: "@esbuild/openbsd-x64@npm:0.17.17" conditions: os=openbsd & cpu=x64 languageName: node linkType: hard -"@esbuild/sunos-x64@npm:0.17.0": - version: 0.17.0 - resolution: "@esbuild/sunos-x64@npm:0.17.0" +"@esbuild/sunos-x64@npm:0.17.17": + version: 0.17.17 + resolution: "@esbuild/sunos-x64@npm:0.17.17" conditions: os=sunos & cpu=x64 languageName: node linkType: hard -"@esbuild/win32-arm64@npm:0.17.0": - version: 0.17.0 - resolution: "@esbuild/win32-arm64@npm:0.17.0" +"@esbuild/win32-arm64@npm:0.17.17": + version: 0.17.17 + resolution: "@esbuild/win32-arm64@npm:0.17.17" conditions: os=win32 & cpu=arm64 languageName: node linkType: hard -"@esbuild/win32-ia32@npm:0.17.0": - version: 0.17.0 - resolution: "@esbuild/win32-ia32@npm:0.17.0" +"@esbuild/win32-ia32@npm:0.17.17": + version: 0.17.17 + resolution: "@esbuild/win32-ia32@npm:0.17.17" conditions: os=win32 & cpu=ia32 languageName: node linkType: hard -"@esbuild/win32-x64@npm:0.17.0": - version: 0.17.0 - resolution: "@esbuild/win32-x64@npm:0.17.0" +"@esbuild/win32-x64@npm:0.17.17": + version: 0.17.17 + resolution: "@esbuild/win32-x64@npm:0.17.17" conditions: os=win32 & cpu=x64 languageName: node linkType: hard @@ -6177,6 +6177,13 @@ __metadata: languageName: node linkType: hard +"@jridgewell/sourcemap-codec@npm:^1.4.13": + version: 1.4.15 + resolution: "@jridgewell/sourcemap-codec@npm:1.4.15" + checksum: b881c7e503db3fc7f3c1f35a1dd2655a188cc51a3612d76efc8a6eb74728bef5606e6758ee77423e564092b4a518aba569bbb21c9bac5ab7a35b0c6ae7e344c8 + languageName: node + linkType: hard + "@jridgewell/trace-mapping@npm:^0.3.12, @jridgewell/trace-mapping@npm:^0.3.14, @jridgewell/trace-mapping@npm:^0.3.15": version: 0.3.17 resolution: "@jridgewell/trace-mapping@npm:0.3.17" @@ -6747,7 +6754,6 @@ __metadata: "@testing-library/react": ^13.3.0 "@testing-library/user-event": ^13.1.5 "@types/convert-source-map": ^1.5.1 - "@types/jest": ^27 "@types/json-stringify-safe": ^5.0.0 "@types/nanoid": ^2.1.0 "@types/node": ^10.14.4 @@ -6760,7 +6766,6 @@ __metadata: axios: ^0.19.2 console-testing-library: 0.6.1 convert-source-map: ^1.7.0 - esbuild: ~0.17 eslint: ^7.25.0 eslint-config-prettier: ^8.3.0 eslint-config-react-app: ^7.0.1 @@ -6775,8 +6780,6 @@ __metadata: invariant: ^2.2.4 jsdom: ^21.0.0 json-stringify-safe: ^5.0.1 - magic-string: ^0.25.7 - merge-source-map: ^1.1.0 msw: ^0.40.2 node-fetch: ^2.6.1 prettier: ^2.2.1 @@ -6785,16 +6788,12 @@ __metadata: redux-thunk: 3.0.0-alpha.3 reselect: ^4.1.8 rimraf: ^3.0.2 - rollup: ^2.47.0 - rollup-plugin-strip-code: ^0.2.6 size-limit: ^4.11.0 - source-map: ^0.7.3 - terser: ^5.6.1 tslib: ^1.10.0 tsup: ^6.7.0 tsx: ^3.12.2 typescript: ~4.9 - vitest: ^0.27.2 + vitest: ^0.30.1 yargs: ^15.3.1 peerDependencies: react: ^16.9.0 || ^17.0.0 || ^18 @@ -8674,6 +8673,60 @@ __metadata: languageName: node linkType: hard +"@vitest/expect@npm:0.30.1": + version: 0.30.1 + resolution: "@vitest/expect@npm:0.30.1" + dependencies: + "@vitest/spy": 0.30.1 + "@vitest/utils": 0.30.1 + chai: ^4.3.7 + checksum: cd7728d1532fd9b9d9ca52f76be14af72f7cf28686e91f99b1537a30d46a4207021410163b1c460076d4ada7246f7f3bdc14989c44aff0814ef83e1cdf5e4ecf + languageName: node + linkType: hard + +"@vitest/runner@npm:0.30.1": + version: 0.30.1 + resolution: "@vitest/runner@npm:0.30.1" + dependencies: + "@vitest/utils": 0.30.1 + concordance: ^5.0.4 + p-limit: ^4.0.0 + pathe: ^1.1.0 + checksum: b8f9faa63f3e98671804ab403a1dc466a48548fa5ee5e276855f0bcc1fae528ca65476584fb5528dd62ba9865c54d147b1ae78fb0cafe337c043669dcb93e67d + languageName: node + linkType: hard + +"@vitest/snapshot@npm:0.30.1": + version: 0.30.1 + resolution: "@vitest/snapshot@npm:0.30.1" + dependencies: + magic-string: ^0.30.0 + pathe: ^1.1.0 + pretty-format: ^27.5.1 + checksum: 9e0b89ca6c2cb08f2061c3d6bf5f2a1a9481c0229b8772b8be1db515552f07ea184f4248ceb11ad976ee89e2402c14e48a5700bab6ea859167fe5d10920e939c + languageName: node + linkType: hard + +"@vitest/spy@npm:0.30.1": + version: 0.30.1 + resolution: "@vitest/spy@npm:0.30.1" + dependencies: + tinyspy: ^2.1.0 + checksum: af2e0a3910dfaa6b5759acd4913ca3c21ac9ad543c0d1095c23bdbca1a7d4e5dab43d8bfc4b08025d24e84965d65ae83f2cdc6aad080eaf5faf06daf06af3271 + languageName: node + linkType: hard + +"@vitest/utils@npm:0.30.1": + version: 0.30.1 + resolution: "@vitest/utils@npm:0.30.1" + dependencies: + concordance: ^5.0.4 + loupe: ^2.3.6 + pretty-format: ^27.5.1 + checksum: a685b6ba34b0173e4da388055dc2a22ba335a74cf99679f7036cea1d183e0ee804a01984148eaad0e0f48bfb786d33800ff6dd549b94f3d064e14caa0857ee62 + languageName: node + linkType: hard + "@webassemblyjs/ast@npm:1.11.1": version: 1.11.1 resolution: "@webassemblyjs/ast@npm:1.11.1" @@ -9173,6 +9226,15 @@ __metadata: languageName: node linkType: hard +"acorn@npm:^8.8.2": + version: 8.8.2 + resolution: "acorn@npm:8.8.2" + bin: + acorn: bin/acorn + checksum: f790b99a1bf63ef160c967e23c46feea7787e531292bb827126334612c234ed489a0dc2c7ba33156416f0ffa8d25bf2b0fdb7f35c2ba60eb3e960572bece4001 + languageName: node + linkType: hard + "address@npm:^1.0.1, address@npm:^1.1.2": version: 1.2.0 resolution: "address@npm:1.2.0" @@ -10408,6 +10470,13 @@ __metadata: languageName: node linkType: hard +"blueimp-md5@npm:^2.10.0": + version: 2.19.0 + resolution: "blueimp-md5@npm:2.19.0" + checksum: 28095dcbd2c67152a2938006e8d7c74c3406ba6556071298f872505432feb2c13241b0476644160ee0a5220383ba94cb8ccdac0053b51f68d168728f9c382530 + languageName: node + linkType: hard + "bn.js@npm:^4.0.0, bn.js@npm:^4.1.0, bn.js@npm:^4.11.9": version: 4.12.0 resolution: "bn.js@npm:4.12.0" @@ -11847,6 +11916,22 @@ __metadata: languageName: node linkType: hard +"concordance@npm:^5.0.4": + version: 5.0.4 + resolution: "concordance@npm:5.0.4" + dependencies: + date-time: ^3.1.0 + esutils: ^2.0.3 + fast-diff: ^1.2.0 + js-string-escape: ^1.0.1 + lodash: ^4.17.15 + md5-hex: ^3.0.1 + semver: ^7.3.2 + well-known-symbols: ^2.0.0 + checksum: 749153ba711492feb7c3d2f5bb04c107157440b3e39509bd5dd19ee7b3ac751d1e4cd75796d9f702e0a713312dbc661421c68aa4a2c34d5f6d91f47e3a1c64a6 + languageName: node + linkType: hard + "concurrently@npm:^6.2.0": version: 6.2.0 resolution: "concurrently@npm:6.2.0" @@ -12887,6 +12972,15 @@ __metadata: languageName: node linkType: hard +"date-time@npm:^3.1.0": + version: 3.1.0 + resolution: "date-time@npm:3.1.0" + dependencies: + time-zone: ^1.0.0 + checksum: f9cfcd1b15dfeabab15c0b9d18eb9e4e2d9d4371713564178d46a8f91ad577a290b5178b80050718d02d9c0cf646f8a875011e12d1ed05871e9f72c72c8a8fe6 + languageName: node + linkType: hard + "debounce@npm:^1.2.0": version: 1.2.1 resolution: "debounce@npm:1.2.1" @@ -13951,32 +14045,32 @@ __metadata: languageName: node linkType: hard -"esbuild@npm:0.17.0": - version: 0.17.0 - resolution: "esbuild@npm:0.17.0" - dependencies: - "@esbuild/android-arm": 0.17.0 - "@esbuild/android-arm64": 0.17.0 - "@esbuild/android-x64": 0.17.0 - "@esbuild/darwin-arm64": 0.17.0 - "@esbuild/darwin-x64": 0.17.0 - "@esbuild/freebsd-arm64": 0.17.0 - "@esbuild/freebsd-x64": 0.17.0 - "@esbuild/linux-arm": 0.17.0 - "@esbuild/linux-arm64": 0.17.0 - "@esbuild/linux-ia32": 0.17.0 - "@esbuild/linux-loong64": 0.17.0 - "@esbuild/linux-mips64el": 0.17.0 - "@esbuild/linux-ppc64": 0.17.0 - "@esbuild/linux-riscv64": 0.17.0 - "@esbuild/linux-s390x": 0.17.0 - "@esbuild/linux-x64": 0.17.0 - "@esbuild/netbsd-x64": 0.17.0 - "@esbuild/openbsd-x64": 0.17.0 - "@esbuild/sunos-x64": 0.17.0 - "@esbuild/win32-arm64": 0.17.0 - "@esbuild/win32-ia32": 0.17.0 - "@esbuild/win32-x64": 0.17.0 +"esbuild@npm:0.17.17": + version: 0.17.17 + resolution: "esbuild@npm:0.17.17" + dependencies: + "@esbuild/android-arm": 0.17.17 + "@esbuild/android-arm64": 0.17.17 + "@esbuild/android-x64": 0.17.17 + "@esbuild/darwin-arm64": 0.17.17 + "@esbuild/darwin-x64": 0.17.17 + "@esbuild/freebsd-arm64": 0.17.17 + "@esbuild/freebsd-x64": 0.17.17 + "@esbuild/linux-arm": 0.17.17 + "@esbuild/linux-arm64": 0.17.17 + "@esbuild/linux-ia32": 0.17.17 + "@esbuild/linux-loong64": 0.17.17 + "@esbuild/linux-mips64el": 0.17.17 + "@esbuild/linux-ppc64": 0.17.17 + "@esbuild/linux-riscv64": 0.17.17 + "@esbuild/linux-s390x": 0.17.17 + "@esbuild/linux-x64": 0.17.17 + "@esbuild/netbsd-x64": 0.17.17 + "@esbuild/openbsd-x64": 0.17.17 + "@esbuild/sunos-x64": 0.17.17 + "@esbuild/win32-arm64": 0.17.17 + "@esbuild/win32-ia32": 0.17.17 + "@esbuild/win32-x64": 0.17.17 dependenciesMeta: "@esbuild/android-arm": optional: true @@ -14024,7 +14118,7 @@ __metadata: optional: true bin: esbuild: bin/esbuild - checksum: eabf1d3d9230b1367edbdd24c89a35f60861c120377844af9f8daa084133f4dfc43697484b14e92a209d2055c8903fdf2b43fee8dbabd7d1fbcd7031639fca9e + checksum: dbb803a7fc798755ffcc347fd4e83f33bdffb91b62ff14c41d858acacd60b2b74a9fbcfb54da2be7cc385bd99fc00f5a0cc1e80c7e5d501236f4fd39cf8c03d1 languageName: node linkType: hard @@ -14585,7 +14679,7 @@ __metadata: languageName: node linkType: hard -"esutils@npm:^2.0.2": +"esutils@npm:^2.0.2, esutils@npm:^2.0.3": version: 2.0.3 resolution: "esutils@npm:2.0.3" checksum: 22b5b08f74737379a840b8ed2036a5fb35826c709ab000683b092d9054e5c2a82c27818f12604bfc2a9a76b90b6834ef081edbc1c7ae30d1627012e067c6ec87 @@ -14871,7 +14965,7 @@ __metadata: languageName: node linkType: hard -"fast-diff@npm:^1.1.2": +"fast-diff@npm:^1.1.2, fast-diff@npm:^1.2.0": version: 1.2.0 resolution: "fast-diff@npm:1.2.0" checksum: 1b5306eaa9e826564d9e5ffcd6ebd881eb5f770b3f977fcbf38f05c824e42172b53c79920e8429c54eb742ce15a0caf268b0fdd5b38f6de52234c4a8368131ae @@ -18991,6 +19085,13 @@ fsevents@^1.2.7: languageName: node linkType: hard +"js-string-escape@npm:^1.0.1": + version: 1.0.1 + resolution: "js-string-escape@npm:1.0.1" + checksum: f11e0991bf57e0c183b55c547acec85bd2445f043efc9ea5aa68b41bd2a3e7d3ce94636cb233ae0d84064ba4c1a505d32e969813c5b13f81e7d4be12c59256fe + languageName: node + linkType: hard + "js-tokens@npm:^3.0.0 || ^4.0.0, js-tokens@npm:^4.0.0": version: 4.0.0 resolution: "js-tokens@npm:4.0.0" @@ -19606,10 +19707,10 @@ fsevents@^1.2.7: languageName: node linkType: hard -"local-pkg@npm:^0.4.2": - version: 0.4.2 - resolution: "local-pkg@npm:0.4.2" - checksum: 22be451353c25c4411b552bf01880ebc9e995b93574b2facc7757968d888356df59199cacada14162ab53bbc9da055bb692c907b4171f008dbce45a2afc777c1 +"local-pkg@npm:^0.4.3": + version: 0.4.3 + resolution: "local-pkg@npm:0.4.3" + checksum: 7825aca531dd6afa3a3712a0208697aa4a5cd009065f32e3fb732aafcc42ed11f277b5ac67229222e96f4def55197171cdf3d5522d0381b489d2e5547b407d55 languageName: node linkType: hard @@ -19853,7 +19954,7 @@ fsevents@^1.2.7: languageName: node linkType: hard -"loupe@npm:^2.3.1": +"loupe@npm:^2.3.1, loupe@npm:^2.3.6": version: 2.3.6 resolution: "loupe@npm:2.3.6" dependencies: @@ -19928,15 +20029,6 @@ fsevents@^1.2.7: languageName: node linkType: hard -"magic-string@npm:0.25.3": - version: 0.25.3 - resolution: "magic-string@npm:0.25.3" - dependencies: - sourcemap-codec: ^1.4.4 - checksum: 21b4149f4465f3edbefae6e4a93eba8889396874fe49aa62fc24016571c803d6987e334f0a0bdbdeddde919d67469cfa59339e66b8aebac412cfbdc9d1cb16b1 - languageName: node - linkType: hard - "magic-string@npm:^0.25.0, magic-string@npm:^0.25.7": version: 0.25.7 resolution: "magic-string@npm:0.25.7" @@ -19946,6 +20038,15 @@ fsevents@^1.2.7: languageName: node linkType: hard +"magic-string@npm:^0.30.0": + version: 0.30.0 + resolution: "magic-string@npm:0.30.0" + dependencies: + "@jridgewell/sourcemap-codec": ^1.4.13 + checksum: 7bdf22e27334d8a393858a16f5f840af63a7c05848c000fd714da5aa5eefa09a1bc01d8469362f25cc5c4a14ec01b46557b7fff8751365522acddf21e57c488d + languageName: node + linkType: hard + "make-dir@npm:^2.0.0, make-dir@npm:^2.1.0": version: 2.1.0 resolution: "make-dir@npm:2.1.0" @@ -20048,6 +20149,15 @@ fsevents@^1.2.7: languageName: node linkType: hard +"md5-hex@npm:^3.0.1": + version: 3.0.1 + resolution: "md5-hex@npm:3.0.1" + dependencies: + blueimp-md5: ^2.10.0 + checksum: 6799a19e8bdd3e0c2861b94c1d4d858a89220488d7885c1fa236797e367d0c2e5f2b789e05309307083503f85be3603a9686a5915568a473137d6b4117419cc2 + languageName: node + linkType: hard + "md5.js@npm:^1.3.4": version: 1.3.5 resolution: "md5.js@npm:1.3.5" @@ -20175,15 +20285,6 @@ fsevents@^1.2.7: languageName: node linkType: hard -"merge-source-map@npm:^1.1.0": - version: 1.1.0 - resolution: "merge-source-map@npm:1.1.0" - dependencies: - source-map: ^0.6.1 - checksum: 945a83dcc59eff77dde709be1d3d6cb575c11cd7164a7ccdc1c6f0d463aad7c12750a510bdf84af2c05fac4615c4305d97ac90477975348bb901a905c8e92c4b - languageName: node - linkType: hard - "merge-stream@npm:^2.0.0": version: 2.0.0 resolution: "merge-stream@npm:2.0.0" @@ -20601,15 +20702,15 @@ fsevents@^1.2.7: languageName: node linkType: hard -"mlly@npm:^1.0.0, mlly@npm:^1.1.0": - version: 1.1.0 - resolution: "mlly@npm:1.1.0" +"mlly@npm:^1.1.1, mlly@npm:^1.2.0": + version: 1.2.0 + resolution: "mlly@npm:1.2.0" dependencies: - acorn: ^8.8.1 - pathe: ^1.0.0 - pkg-types: ^1.0.1 - ufo: ^1.0.1 - checksum: d53147a2f5f83499589c47a00e00df30cbae2e630dfcfdfdeee2b70b49aff6612f2fa13195a1c6268b8f8ecd6064cb9a35febbdf895b2cbfeacdf9a9b3e31493 + acorn: ^8.8.2 + pathe: ^1.1.0 + pkg-types: ^1.0.2 + ufo: ^1.1.1 + checksum: 640b019eb20e8e556bd623141b861d47e5c05f8af00210376ce1015912695dbd93a38cfe7ba18ca04f00e75645378f0f94a48a90bfa6e1b5dee1f0ec9c14eed1 languageName: node linkType: hard @@ -21591,6 +21692,15 @@ fsevents@^1.2.7: languageName: node linkType: hard +"p-limit@npm:^4.0.0": + version: 4.0.0 + resolution: "p-limit@npm:4.0.0" + dependencies: + yocto-queue: ^1.0.0 + checksum: 01d9d70695187788f984226e16c903475ec6a947ee7b21948d6f597bed788e3112cc7ec2e171c1d37125057a5f45f3da21d8653e04a3a793589e12e9e80e756b + languageName: node + linkType: hard + "p-locate@npm:^2.0.0": version: 2.0.0 resolution: "p-locate@npm:2.0.0" @@ -22011,17 +22121,10 @@ fsevents@^1.2.7: languageName: node linkType: hard -"pathe@npm:^0.2.0": - version: 0.2.0 - resolution: "pathe@npm:0.2.0" - checksum: 9a8149ce152088f30d15b0b03a7c128ba21f16b4dc1f3f90fe38eee9f6d0f1d6da8e4e47bd2a4f9e14aaac7c30ed01cfc86216479011de2bdc598b65e6f19f41 - languageName: node - linkType: hard - -"pathe@npm:^1.0.0": - version: 1.0.0 - resolution: "pathe@npm:1.0.0" - checksum: 7b71a4930a5b46111c92149632f74b0e87bade3eabe6c9168dcc4846857a4e124eacc0c2bf044fe0d2a8b7f87ae62b9b2cb11938c61899d485cc36dd1a243a23 +"pathe@npm:^1.1.0": + version: 1.1.0 + resolution: "pathe@npm:1.1.0" + checksum: 6b9be9968ea08a90c0824934799707a1c6a1ad22ac1f22080f377e3f75856d5e53a331b01d327329bfce538a14590587cfb250e8e7947f64408797c84c252056 languageName: node linkType: hard @@ -22128,14 +22231,14 @@ fsevents@^1.2.7: languageName: node linkType: hard -"pkg-types@npm:^1.0.1": - version: 1.0.1 - resolution: "pkg-types@npm:1.0.1" +"pkg-types@npm:^1.0.2": + version: 1.0.2 + resolution: "pkg-types@npm:1.0.2" dependencies: jsonc-parser: ^3.2.0 - mlly: ^1.0.0 - pathe: ^1.0.0 - checksum: fe73cc22fb72ddb09227e2837a7b2ed1e0706a18e69a58a6ce13cde2b7eab122cb98de44d5c54fca5715d203ef3d2eb004b3ec84a3c05decb11e7c49a80fe2f9 + mlly: ^1.1.1 + pathe: ^1.1.0 + checksum: 2d0a70c1721c2ebbe075b912531a4f43136e6658fdcc59dc76c39966201ab5ddf265868d1211943183406d4b70d373c17e3b176487bc2020ea737d030b0fd080 languageName: node linkType: hard @@ -24021,15 +24124,15 @@ fsevents@^1.2.7: languageName: node linkType: hard -"react-dom@npm:18.1.0": - version: 18.1.0 - resolution: "react-dom@npm:18.1.0" +"react-dom@npm:18.2.0": + version: 18.2.0 + resolution: "react-dom@npm:18.2.0" dependencies: loose-envify: ^1.1.0 - scheduler: ^0.22.0 + scheduler: ^0.23.0 peerDependencies: - react: ^18.1.0 - checksum: bb0d48eeb0b297c79c2a03978baa29f5b3ff7ba3d070b21e34c9af1a6e7fdf0ca8b8d73e41f9214d91ad40eeb6d1f3559f884cbbc338713374a51320637c23df + react: ^18.2.0 + checksum: 7d323310bea3a91be2965f9468d552f201b1c27891e45ddc2d6b8f717680c95a75ae0bc1e3f5cf41472446a2589a75aed4483aee8169287909fcd59ad149e8cc languageName: node linkType: hard @@ -24406,12 +24509,12 @@ fsevents@^1.2.7: languageName: node linkType: hard -"react@npm:18.1.0": - version: 18.1.0 - resolution: "react@npm:18.1.0" +"react@npm:18.2.0": + version: 18.2.0 + resolution: "react@npm:18.2.0" dependencies: loose-envify: ^1.1.0 - checksum: 5bb296b561b43ef2220395da4faac86c14a087c8c80e1a7598a5740f01ee605c11eaf249985c1e2000971c4cd32ccb46d40f00479bbd9fb6b1c7cf857393b7d4 + checksum: 88e38092da8839b830cda6feef2e8505dec8ace60579e46aa5490fc3dc9bba0bd50336507dc166f43e3afc1c42939c09fe33b25fae889d6f402721dcd78fca1b languageName: node linkType: hard @@ -25314,16 +25417,6 @@ fsevents@^1.2.7: languageName: node linkType: hard -"rollup-plugin-strip-code@npm:^0.2.6": - version: 0.2.7 - resolution: "rollup-plugin-strip-code@npm:0.2.7" - dependencies: - magic-string: 0.25.3 - rollup-pluginutils: 2.8.1 - checksum: bd7127dac4d1f747800124878fb0de2aeed6e5bebcad90cae0feb02bea5aae6679070bf037d36751d46b1180bf72fbb561ce8b50770659a4929962e2171c744d - languageName: node - linkType: hard - "rollup-plugin-terser@npm:^7.0.0, rollup-plugin-terser@npm:^7.0.2": version: 7.0.2 resolution: "rollup-plugin-terser@npm:7.0.2" @@ -25354,15 +25447,6 @@ fsevents@^1.2.7: languageName: node linkType: hard -"rollup-pluginutils@npm:2.8.1": - version: 2.8.1 - resolution: "rollup-pluginutils@npm:2.8.1" - dependencies: - estree-walker: ^0.6.1 - checksum: e68926d2449cda7fd790b47734ccf814c16e88b395f87feb42bb51be5027a332b319eac70f7c541e119e1bcad224f04b0cc24fe355b8c52cf0b9dad62e7030c7 - languageName: node - linkType: hard - "rollup-pluginutils@npm:^2.8.2": version: 2.8.2 resolution: "rollup-pluginutils@npm:2.8.2" @@ -25372,7 +25456,7 @@ fsevents@^1.2.7: languageName: node linkType: hard -"rollup@npm:^2.35.1, rollup@npm:^2.43.1, rollup@npm:^2.47.0": +"rollup@npm:^2.35.1, rollup@npm:^2.43.1": version: 2.75.6 resolution: "rollup@npm:2.75.6" dependencies: @@ -25627,12 +25711,12 @@ fsevents@^1.2.7: languageName: node linkType: hard -"scheduler@npm:^0.22.0": - version: 0.22.0 - resolution: "scheduler@npm:0.22.0" +"scheduler@npm:^0.23.0": + version: 0.23.0 + resolution: "scheduler@npm:0.23.0" dependencies: loose-envify: ^1.1.0 - checksum: a8ef5cab769c020cd6382ad9ecc3f72dbde56a50a36639b3a42ad9c11f7724f03700bcad373044059b8067d4a6365154dc7c0ca8027ef20ff4900cf58a0fc2c5 + checksum: d79192eeaa12abef860c195ea45d37cbf2bbf5f66e3c4dcd16f54a7da53b17788a70d109ee3d3dde1a0fd50e6a8fc171f4300356c5aee4fc0171de526bf35f8a languageName: node linkType: hard @@ -26665,6 +26749,13 @@ fsevents@^1.2.7: languageName: node linkType: hard +"std-env@npm:^3.3.2": + version: 3.3.2 + resolution: "std-env@npm:3.3.2" + checksum: c02256bb041ba1870d23f8360bc7e47a9cf1fabcd02c8b7c4246d48f2c6bb47b4f45c70964348844e6d36521df84c4a9d09d468654b51e0eb5c600e3392b4570 + languageName: node + linkType: hard + "stream-browserify@npm:^2.0.1": version: 2.0.2 resolution: "stream-browserify@npm:2.0.2" @@ -26980,12 +27071,12 @@ fsevents@^1.2.7: languageName: node linkType: hard -"strip-literal@npm:^1.0.0": - version: 1.0.0 - resolution: "strip-literal@npm:1.0.0" +"strip-literal@npm:^1.0.1": + version: 1.0.1 + resolution: "strip-literal@npm:1.0.1" dependencies: - acorn: ^8.8.1 - checksum: ada9b60f322ce3e3fd167b65a186ab77a8c76b8f9074dcdbad4c1a810b46f21c9dca30d4d807e98af580cbe99bfbccd6d8176f69183a454ae2868d8ddd6d4f88 + acorn: ^8.8.2 + checksum: ab40496820f02220390d95cdd620a997168efb69d5bd7d180bc4ef83ca562a95447843d8c7c88b8284879a29cf4eedc89d8001d1e098c1a1e23d12a9c755dff4 languageName: node linkType: hard @@ -27444,7 +27535,7 @@ fsevents@^1.2.7: languageName: node linkType: hard -"terser@npm:^5.0.0, terser@npm:^5.10.0, terser@npm:^5.6.1, terser@npm:^5.7.0, terser@npm:^5.7.2": +"terser@npm:^5.0.0, terser@npm:^5.10.0, terser@npm:^5.7.0, terser@npm:^5.7.2": version: 5.14.1 resolution: "terser@npm:5.14.1" dependencies: @@ -27539,6 +27630,13 @@ fsevents@^1.2.7: languageName: node linkType: hard +"time-zone@npm:^1.0.0": + version: 1.0.0 + resolution: "time-zone@npm:1.0.0" + checksum: e46f5a69b8c236dcd8e91e29d40d4e7a3495ed4f59888c3f84ce1d9678e20461421a6ba41233509d47dd94bc18f1a4377764838b21b584663f942b3426dcbce8 + languageName: node + linkType: hard + "timers-browserify@npm:^2.0.4": version: 2.0.12 resolution: "timers-browserify@npm:2.0.12" @@ -27579,10 +27677,10 @@ fsevents@^1.2.7: languageName: node linkType: hard -"tinybench@npm:^2.3.1": - version: 2.3.1 - resolution: "tinybench@npm:2.3.1" - checksum: 74d45fa546d964a8123f98847fc59550945ed7f0d3e5a4ce0f9596d836b51c1d340c2ae0277a8023c15dc9ea3d7cb948a79173bfc46338c9b367c6323ea1eaf3 +"tinybench@npm:^2.4.0": + version: 2.4.0 + resolution: "tinybench@npm:2.4.0" + checksum: cfbe90f75755488653dde256019cc810f65e90f63fdd962e71e8b209b49598c5fc90c2227d2087eb807944895fafe7f12fe9ecae2b5e89db5adde66415e9b836 languageName: node linkType: hard @@ -27593,17 +27691,17 @@ fsevents@^1.2.7: languageName: node linkType: hard -"tinypool@npm:^0.3.0": - version: 0.3.0 - resolution: "tinypool@npm:0.3.0" - checksum: 92291c309ed8d004c1ee1ef7f610cd90352383f12c52b0ec16abd9ebc665c03626e7afbc9993df97f63e67fea002b5cc18ba5e8f90260643867cbcac278a183c +"tinypool@npm:^0.4.0": + version: 0.4.0 + resolution: "tinypool@npm:0.4.0" + checksum: 8abcac9e784793499f1eeeace8290c026454b9d7338c74029ce6a821643bab8dcab7caeb4051e39006baf681d6a62d57c3319e9c0f6e2317a45ab0fdbd76ee26 languageName: node linkType: hard -"tinyspy@npm:^1.0.2": - version: 1.0.2 - resolution: "tinyspy@npm:1.0.2" - checksum: 32096121aa8d52bb625ad62c9314b3e4daba4ab9ac428217b12b1e1dfe9860e3c94d54a7affa279cc70dc6f10d88c6ba46b51de68896b318a06d02f05e87dcc3 +"tinyspy@npm:^2.1.0": + version: 2.1.0 + resolution: "tinyspy@npm:2.1.0" + checksum: cb83c1f74a79dd5934018bad94f60a304a29d98a2d909ea45fc367f7b80b21b0a7d8135a2ce588deb2b3ba56c7c607258b2a03e6001d89e4d564f9a95cc6a81f languageName: node linkType: hard @@ -28294,10 +28392,10 @@ fsevents@^1.2.7: languageName: node linkType: hard -"ufo@npm:^1.0.1": - version: 1.0.1 - resolution: "ufo@npm:1.0.1" - checksum: 63024876f21b7cc44267255a8043062046d3215e09212bd682787a13ccf1e0c5d23f7686a7f1bc7ac9f34c7e8a88100af234f42b509db50f17ce638af6ac87cc +"ufo@npm:^1.1.1": + version: 1.1.1 + resolution: "ufo@npm:1.1.1" + checksum: 6bd210ed93d8c0dedd76c456b1d1dfb0e3b08c2216ee6080e61f0f545de0bac24b3d3a5530cd6a403810855f8d8fc3922583965296142e04cfc287442635e6c7 languageName: node linkType: hard @@ -28981,21 +29079,19 @@ fsevents@^1.2.7: languageName: node linkType: hard -"vite-node@npm:0.27.2": - version: 0.27.2 - resolution: "vite-node@npm:0.27.2" +"vite-node@npm:0.30.1": + version: 0.30.1 + resolution: "vite-node@npm:0.30.1" dependencies: cac: ^6.7.14 debug: ^4.3.4 - mlly: ^1.1.0 - pathe: ^0.2.0 + mlly: ^1.2.0 + pathe: ^1.1.0 picocolors: ^1.0.0 - source-map: ^0.6.1 - source-map-support: ^0.5.21 vite: ^3.0.0 || ^4.0.0 bin: vite-node: vite-node.mjs - checksum: 4cdb4fd952481548dbece6bc86c339cf806f07d58b9e95e7f9e57e4a4f7d5faaf1629dcea4d2a7366080884c1d82b2794ab595e9e5e003c0cf63f17e32a17d13 + checksum: 2a17cca94aaf9ea689aeff0b5e900aab9e9385e97189446a7bc9c067f094556a5fcdff4a04367811694c3dcd2001bef7f5133ac66cdf4307d90742c30aff5fea languageName: node linkType: hard @@ -29037,27 +29133,35 @@ fsevents@^1.2.7: languageName: node linkType: hard -"vitest@npm:^0.27.2": - version: 0.27.2 - resolution: "vitest@npm:0.27.2" +"vitest@npm:^0.30.1": + version: 0.30.1 + resolution: "vitest@npm:0.30.1" dependencies: "@types/chai": ^4.3.4 "@types/chai-subset": ^1.3.3 "@types/node": "*" - acorn: ^8.8.1 + "@vitest/expect": 0.30.1 + "@vitest/runner": 0.30.1 + "@vitest/snapshot": 0.30.1 + "@vitest/spy": 0.30.1 + "@vitest/utils": 0.30.1 + acorn: ^8.8.2 acorn-walk: ^8.2.0 cac: ^6.7.14 chai: ^4.3.7 + concordance: ^5.0.4 debug: ^4.3.4 - local-pkg: ^0.4.2 + local-pkg: ^0.4.3 + magic-string: ^0.30.0 + pathe: ^1.1.0 picocolors: ^1.0.0 source-map: ^0.6.1 - strip-literal: ^1.0.0 - tinybench: ^2.3.1 - tinypool: ^0.3.0 - tinyspy: ^1.0.2 + std-env: ^3.3.2 + strip-literal: ^1.0.1 + tinybench: ^2.4.0 + tinypool: ^0.4.0 vite: ^3.0.0 || ^4.0.0 - vite-node: 0.27.2 + vite-node: 0.30.1 why-is-node-running: ^2.2.2 peerDependencies: "@edge-runtime/vm": "*" @@ -29065,6 +29169,9 @@ fsevents@^1.2.7: "@vitest/ui": "*" happy-dom: "*" jsdom: "*" + playwright: "*" + safaridriver: "*" + webdriverio: "*" peerDependenciesMeta: "@edge-runtime/vm": optional: true @@ -29076,9 +29183,15 @@ fsevents@^1.2.7: optional: true jsdom: optional: true + playwright: + optional: true + safaridriver: + optional: true + webdriverio: + optional: true bin: vitest: vitest.mjs - checksum: 0c441656f476ed49fb3d0238a070e836272156d80161ff2153397aa06e711abd6779fad6769126840eda2b1d12568b77aea953e14fbad8569cde2d6fb900f165 + checksum: 68e33226dde914600270df9834bdc1f45fd225250051c046c9bc53ca51b8e0bf76dee29a5cf1a51a4c1524f00c414f81764bb463734bdcc9c3f483f2140ec516 languageName: node linkType: hard @@ -29585,6 +29698,13 @@ fsevents@^1.2.7: languageName: node linkType: hard +"well-known-symbols@npm:^2.0.0": + version: 2.0.0 + resolution: "well-known-symbols@npm:2.0.0" + checksum: 4f54bbc3012371cb4d228f436891b8e7536d34ac61a57541890257e96788608e096231e0121ac24d08ef2f908b3eb2dc0adba35023eaeb2a7df655da91415402 + languageName: node + linkType: hard + "whatwg-encoding@npm:^1.0.5": version: 1.0.5 resolution: "whatwg-encoding@npm:1.0.5" @@ -30341,6 +30461,13 @@ fsevents@^1.2.7: languageName: node linkType: hard +"yocto-queue@npm:^1.0.0": + version: 1.0.0 + resolution: "yocto-queue@npm:1.0.0" + checksum: 2cac84540f65c64ccc1683c267edce396b26b1e931aa429660aefac8fbe0188167b7aee815a3c22fa59a28a58d898d1a2b1825048f834d8d629f4c2a5d443801 + languageName: node + linkType: hard + "z-schema@npm:~3.18.3": version: 3.18.4 resolution: "z-schema@npm:3.18.4" From 805379bb8f7120a40ff3cb24c1df9119de25c751 Mon Sep 17 00:00:00 2001 From: Mark Erikson Date: Tue, 18 Apr 2023 11:27:30 -0400 Subject: [PATCH 132/412] Release 2.0.0-alpha.5 --- packages/toolkit/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/toolkit/package.json b/packages/toolkit/package.json index 3110b10eb1..6aec87a2e3 100644 --- a/packages/toolkit/package.json +++ b/packages/toolkit/package.json @@ -1,6 +1,6 @@ { "name": "@reduxjs/toolkit", - "version": "2.0.0-alpha.4", + "version": "2.0.0-alpha.5", "description": "The official, opinionated, batteries-included toolset for efficient Redux development", "author": "Mark Erikson ", "license": "MIT", From 32d1fbd4281f69b1e672f8001929d890c46bc531 Mon Sep 17 00:00:00 2001 From: "ben.durrant" Date: Tue, 18 Apr 2023 17:08:58 +0100 Subject: [PATCH 133/412] ensure action before trying to match --- packages/toolkit/src/dynamicMiddleware/index.ts | 3 ++- packages/toolkit/src/dynamicMiddleware/tests/index.test.ts | 7 ++++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/packages/toolkit/src/dynamicMiddleware/index.ts b/packages/toolkit/src/dynamicMiddleware/index.ts index 6df4a41426..662f632142 100644 --- a/packages/toolkit/src/dynamicMiddleware/index.ts +++ b/packages/toolkit/src/dynamicMiddleware/index.ts @@ -5,7 +5,7 @@ import type { MiddlewareAPI, } from 'redux' import { compose } from 'redux' -import { createAction } from '../createAction' +import { createAction, isAction } from '../createAction' import { nanoid } from '../nanoid' import { find } from '../utils' import type { @@ -93,6 +93,7 @@ export const createDynamicMiddleware = < const middleware: DynamicMiddleware = (api) => (next) => (action) => { if ( + isAction(action) && withMiddleware.match(action) && action.meta.instanceId === instanceId ) { diff --git a/packages/toolkit/src/dynamicMiddleware/tests/index.test.ts b/packages/toolkit/src/dynamicMiddleware/tests/index.test.ts index 449a3624fa..7049fff92e 100644 --- a/packages/toolkit/src/dynamicMiddleware/tests/index.test.ts +++ b/packages/toolkit/src/dynamicMiddleware/tests/index.test.ts @@ -2,6 +2,7 @@ import type { Middleware } from 'redux' import { createDynamicMiddleware } from '../index' import { configureStore } from '../../configureStore' import type { BaseActionCreator, PayloadAction } from '../../createAction' +import { isAction } from '../../createAction' import { createAction } from '../../createAction' export interface ProbeMiddleware @@ -22,7 +23,11 @@ export const makeProbeableMiddleware = (api) => (next) => (action) => { - if (probeMiddleware.match(action) && action.payload === id) { + if ( + isAction(action) && + probeMiddleware.match(action) && + action.payload === id + ) { return id } return next(action) From 0ef81e7d2a37fef12a5eef354c2d908d3c8b3c5c Mon Sep 17 00:00:00 2001 From: "ben.durrant" Date: Wed, 19 Apr 2023 02:19:47 +0100 Subject: [PATCH 134/412] revive slice thunk idea --- packages/toolkit/src/createAction.ts | 20 + packages/toolkit/src/createAsyncThunk.ts | 4 +- packages/toolkit/src/createSlice.ts | 400 ++++++++++++++++-- .../toolkit/src/tests/createSlice.test.ts | 219 +++++++++- .../toolkit/src/tests/createSlice.typetest.ts | 155 ++++++- 5 files changed, 746 insertions(+), 52 deletions(-) diff --git a/packages/toolkit/src/createAction.ts b/packages/toolkit/src/createAction.ts index ab52d1c4fc..adf8ddec52 100644 --- a/packages/toolkit/src/createAction.ts +++ b/packages/toolkit/src/createAction.ts @@ -50,6 +50,26 @@ export type PrepareAction

= | ((...args: any[]) => { payload: P; error: any }) | ((...args: any[]) => { payload: P; meta: any; error: any }) +export type _PayloadActionForPrepare< + PA extends PrepareAction, + T extends string = string +> = PA extends PrepareAction + ? PayloadAction< + P, + T, + ReturnType extends { + meta: infer M + } + ? M + : never, + ReturnType extends { + error: infer E + } + ? E + : never + > + : never + /** * Internal version of `ActionCreatorWithPreparedPayload`. Not to be used externally. * diff --git a/packages/toolkit/src/createAsyncThunk.ts b/packages/toolkit/src/createAsyncThunk.ts index 5a805ebc3a..7a8cac4e24 100644 --- a/packages/toolkit/src/createAsyncThunk.ts +++ b/packages/toolkit/src/createAsyncThunk.ts @@ -105,7 +105,7 @@ export const miniSerializeError = (value: any): SerializedError => { return { message: String(value) } } -type AsyncThunkConfig = { +export type AsyncThunkConfig = { state?: unknown dispatch?: Dispatch extra?: unknown @@ -414,7 +414,7 @@ export type AsyncThunk< typePrefix: string } -type OverrideThunkApiConfigs = Id< +export type OverrideThunkApiConfigs = Id< NewConfig & Omit > diff --git a/packages/toolkit/src/createSlice.ts b/packages/toolkit/src/createSlice.ts index b62c12f777..b978b62663 100644 --- a/packages/toolkit/src/createSlice.ts +++ b/packages/toolkit/src/createSlice.ts @@ -1,10 +1,11 @@ -import type { AnyAction, Reducer } from 'redux' +import type { Action, AnyAction, Reducer } from 'redux' import type { ActionCreatorWithoutPayload, PayloadAction, PayloadActionCreator, PrepareAction, _ActionCreatorWithPreparedPayload, + _PayloadActionForPrepare, } from './createAction' import { createAction } from './createAction' import type { @@ -18,6 +19,14 @@ import { executeReducerBuilderCallback } from './mapBuilders' import type { Id, NoInfer, Tail } from './tsHelpers' import { freezeDraftable } from './utils' import type { CombinedSliceReducer, InjectConfig } from './combineSlices' +import type { + AsyncThunk, + AsyncThunkConfig, + AsyncThunkOptions, + AsyncThunkPayloadCreator, + OverrideThunkApiConfigs, +} from './createAsyncThunk' +import { createAsyncThunk } from './createAsyncThunk' let hasWarnedAboutObjectNotation = false @@ -178,7 +187,9 @@ export interface CreateSliceOptions< * functions. For every action type, a matching action creator will be * generated using `createAction()`. */ - reducers: ValidateSliceCaseReducers + reducers: + | ValidateSliceCaseReducers + | ((creators: ReducerCreators) => CR) /** * A callback that receives a *builder* object to define @@ -222,7 +233,7 @@ createSlice({ }) ``` */ - extraReducers?: (builder: ActionReducerMapBuilder>) => void + extraReducers?: (builder: ActionReducerMapBuilder) => void /** * A map of selectors that receive the slice's state and any additional arguments, and return a result. @@ -230,6 +241,21 @@ createSlice({ selectors?: Selectors } +const reducerDefinitionType: unique symbol = Symbol.for('rtk-reducer-type') +enum ReducerType { + reducer = 'reducer', + reducerWithPrepare = 'reducerWithPrepare', + asyncThunk = 'asyncThunk', +} + +interface ReducerDefinition { + [reducerDefinitionType]: T +} + +export interface CaseReducerDefinition + extends CaseReducer, + ReducerDefinition {} + /** * A CaseReducer with a `prepare` method. * @@ -240,16 +266,141 @@ export type CaseReducerWithPrepare = { prepare: PrepareAction } +export interface CaseReducerWithPrepareDefinition< + State, + Action extends PayloadAction +> extends CaseReducerWithPrepare, + ReducerDefinition {} + +export interface AsyncThunkSliceReducerConfig< + State, + ThunkArg extends any, + Returned = unknown, + ThunkApiConfig extends AsyncThunkConfig = {} +> { + pending?: CaseReducer< + State, + ReturnType['pending']> + > + rejected?: CaseReducer< + State, + ReturnType['rejected']> + > + fulfilled?: CaseReducer< + State, + ReturnType['fulfilled']> + > + options?: AsyncThunkOptions +} + +export interface AsyncThunkSliceReducerDefinition< + State, + ThunkArg extends any, + Returned = unknown, + ThunkApiConfig extends AsyncThunkConfig = {} +> extends AsyncThunkSliceReducerConfig< + State, + ThunkArg, + Returned, + ThunkApiConfig + >, + ReducerDefinition { + payloadCreator: AsyncThunkPayloadCreator +} + +/** + * Providing these as part of the config would cause circular types, so we disallow passing them + */ +type PreventCircular = { + [K in keyof ThunkApiConfig]: K extends 'state' | 'dispatch' + ? never + : ThunkApiConfig[K] +} + +interface AsyncThunkCreator< + State, + CurriedThunkApiConfig extends PreventCircular = PreventCircular +> { + ( + payloadCreator: AsyncThunkPayloadCreator< + Returned, + ThunkArg, + CurriedThunkApiConfig + >, + config?: AsyncThunkSliceReducerConfig< + State, + ThunkArg, + Returned, + CurriedThunkApiConfig + > + ): AsyncThunkSliceReducerDefinition< + State, + ThunkArg, + Returned, + CurriedThunkApiConfig + > + < + ThunkArg extends any, + Returned = unknown, + ThunkApiConfig extends PreventCircular = {} + >( + payloadCreator: AsyncThunkPayloadCreator< + Returned, + ThunkArg, + ThunkApiConfig + >, + config?: AsyncThunkSliceReducerConfig< + State, + ThunkArg, + Returned, + ThunkApiConfig + > + ): AsyncThunkSliceReducerDefinition + withTypes< + ThunkApiConfig extends PreventCircular + >(): AsyncThunkCreator< + State, + OverrideThunkApiConfigs + > +} + +interface ReducerCreators { + reducer( + caseReducer: CaseReducer> + ): CaseReducerDefinition> + + asyncThunk: AsyncThunkCreator + + preparedReducer>( + prepare: Prepare, + reducer: CaseReducer> + ): { + [reducerDefinitionType]: ReducerType.reducerWithPrepare + prepare: Prepare + reducer: CaseReducer> + } +} + /** * The type describing a slice's `reducers` option. * * @public */ -export type SliceCaseReducers = { - [K: string]: - | CaseReducer> - | CaseReducerWithPrepare> -} +export type SliceCaseReducers = + | Record< + string, + | CaseReducerDefinition> + | CaseReducerWithPrepareDefinition< + State, + PayloadAction + > + | AsyncThunkSliceReducerDefinition + > + | Record< + string, + | CaseReducer> + | CaseReducerWithPrepare> + > /** * The type describing a slice's `selectors` option. @@ -272,15 +423,29 @@ export type CaseReducerActions< CaseReducers extends SliceCaseReducers, SliceName extends string > = { - [Type in keyof CaseReducers]: CaseReducers[Type] extends { prepare: any } - ? ActionCreatorForCaseReducerWithPrepare< - CaseReducers[Type], - SliceActionType - > - : ActionCreatorForCaseReducer< - CaseReducers[Type], - SliceActionType - > + [Type in keyof CaseReducers]: CaseReducers[Type] extends infer Definition + ? Definition extends { prepare: any } + ? ActionCreatorForCaseReducerWithPrepare< + Definition, + SliceActionType + > + : Definition extends AsyncThunkSliceReducerDefinition< + any, + infer ThunkArg, + infer Returned, + infer ThunkApiConfig + > + ? AsyncThunk + : Definition extends { reducer: any } + ? ActionCreatorForCaseReducer< + Definition['reducer'], + SliceActionType + > + : ActionCreatorForCaseReducer< + Definition, + SliceActionType + > + : never } /** @@ -314,11 +479,15 @@ type ActionCreatorForCaseReducer = CR extends ( * @internal */ type SliceDefinedCaseReducers> = { - [Type in keyof CaseReducers]: CaseReducers[Type] extends { - reducer: infer Reducer - } - ? Reducer - : CaseReducers[Type] + [Type in keyof CaseReducers]: CaseReducers[Type] extends infer Definition + ? Definition extends AsyncThunkSliceReducerDefinition + ? Id, 'fulfilled' | 'rejected' | 'pending'>> + : Definition extends { + reducer: infer Reducer + } + ? Reducer + : Definition + : never } /** @@ -373,8 +542,6 @@ function getType(slice: string, actionKey: string): string { * action creators and action types that correspond to the * reducers and state. * - * The `reducer` argument is passed to `createReducer()`. - * * @public */ export function createSlice< @@ -407,33 +574,39 @@ export function createSlice< ? options.initialState : freezeDraftable(options.initialState) - const reducers = options.reducers || {} + const reducers = + typeof options.reducers === 'function' + ? options.reducers(buildReducerCreators()) + : options.reducers || {} const reducerNames = Object.keys(reducers) - const sliceCaseReducersByName: Record = {} - const sliceCaseReducersByType: Record = {} - const actionCreators: Record = {} + const context: ReducerHandlingContext = { + sliceCaseReducersByName: {}, + sliceCaseReducersByType: {}, + actionCreators: {}, + } reducerNames.forEach((reducerName) => { - const maybeReducerWithPrepare = reducers[reducerName] - const type = getType(name, reducerName) - - let caseReducer: CaseReducer - let prepareCallback: PrepareAction | undefined - - if ('reducer' in maybeReducerWithPrepare) { - caseReducer = maybeReducerWithPrepare.reducer - prepareCallback = maybeReducerWithPrepare.prepare + const reducerDefinition = reducers[reducerName] + const reducerDetails: ReducerDetails = { + reducerName, + type: getType(name, reducerName), + createNotation: typeof options.reducers === 'function', + } + if (isAsyncThunkSliceReducerDefinition(reducerDefinition)) { + handleThunkCaseReducerDefinition( + reducerDetails, + reducerDefinition, + context + ) } else { - caseReducer = maybeReducerWithPrepare + handleNormalReducerDefinition( + reducerDetails, + reducerDefinition, + context + ) } - - sliceCaseReducersByName[reducerName] = caseReducer - sliceCaseReducersByType[type] = caseReducer - actionCreators[reducerName] = prepareCallback - ? createAction(type, prepareCallback) - : createAction(type) }) function buildReducer() { @@ -453,7 +626,10 @@ export function createSlice< ? executeReducerBuilderCallback(options.extraReducers) : [options.extraReducers] - const finalCaseReducers = { ...extraReducers, ...sliceCaseReducersByType } + const finalCaseReducers = { + ...extraReducers, + ...context.sliceCaseReducersByType, + } return createReducer(initialState, (builder) => { for (let key in finalCaseReducers) { @@ -492,8 +668,8 @@ export function createSlice< return _reducer(state, action) }, - actions: actionCreators as any, - caseReducers: sliceCaseReducersByName as any, + actions: context.actionCreators as any, + caseReducers: context.sliceCaseReducersByName as any, getInitialState() { if (!_reducer) _reducer = buildReducer() @@ -548,3 +724,133 @@ export function createSlice< } return slice } + +interface ReducerHandlingContext { + sliceCaseReducersByName: Record< + string, + | CaseReducer + | Pick< + AsyncThunkSliceReducerDefinition, + 'fulfilled' | 'rejected' | 'pending' + > + > + sliceCaseReducersByType: Record> + actionCreators: Record +} + +interface ReducerDetails { + reducerName: string + type: string + createNotation: boolean +} + +function buildReducerCreators(): ReducerCreators { + function asyncThunk( + payloadCreator: AsyncThunkPayloadCreator, + config: AsyncThunkSliceReducerConfig + ): AsyncThunkSliceReducerDefinition { + return { + [reducerDefinitionType]: ReducerType.asyncThunk, + payloadCreator, + ...config, + } + } + asyncThunk.withTypes = () => asyncThunk + return { + reducer(caseReducer) { + return Object.assign( + { + // hack so the wrapping function has the same name as the original + // we need to create a wrapper so the `reducerDefinitionType` is not assigned to the original + [caseReducer.name](...args: Parameters) { + return caseReducer(...args) + }, + }[caseReducer.name], + { + [reducerDefinitionType]: ReducerType.reducer, + } as const + ) + }, + preparedReducer(prepare, reducer) { + return { + [reducerDefinitionType]: ReducerType.reducerWithPrepare, + prepare, + reducer, + } + }, + asyncThunk: asyncThunk as any, + } +} + +function handleNormalReducerDefinition( + { type, reducerName, createNotation }: ReducerDetails, + maybeReducerWithPrepare: + | CaseReducer + | CaseReducerWithPrepare>, + context: ReducerHandlingContext +) { + let caseReducer: CaseReducer + let prepareCallback: PrepareAction | undefined + if ('reducer' in maybeReducerWithPrepare) { + if ( + createNotation && + !isCaseReducerWithPrepareDefinition(maybeReducerWithPrepare) + ) { + throw new Error( + 'Please use the `create.preparedReducer` notation for prepared action creators with the `create` notation.' + ) + } + caseReducer = maybeReducerWithPrepare.reducer + prepareCallback = maybeReducerWithPrepare.prepare + } else { + caseReducer = maybeReducerWithPrepare + } + context.sliceCaseReducersByName[reducerName] = caseReducer + context.sliceCaseReducersByType[type] = caseReducer + context.actionCreators[reducerName] = prepareCallback + ? createAction(type, prepareCallback) + : createAction(type) +} + +function isAsyncThunkSliceReducerDefinition( + reducerDefinition: any +): reducerDefinition is AsyncThunkSliceReducerDefinition { + return reducerDefinition[reducerDefinitionType] === ReducerType.asyncThunk +} + +function isCaseReducerWithPrepareDefinition( + reducerDefinition: any +): reducerDefinition is CaseReducerWithPrepareDefinition { + return ( + reducerDefinition[reducerDefinitionType] === ReducerType.reducerWithPrepare + ) +} + +function handleThunkCaseReducerDefinition( + { type, reducerName }: ReducerDetails, + reducerDefinition: AsyncThunkSliceReducerDefinition, + context: ReducerHandlingContext +) { + const { payloadCreator, fulfilled, pending, rejected, options } = + reducerDefinition + const thunk = createAsyncThunk(type, payloadCreator, options as any) + context.actionCreators[reducerName] = thunk + + if (fulfilled) { + context.sliceCaseReducersByType[thunk.fulfilled.type] = fulfilled + } + if (pending) { + context.sliceCaseReducersByType[thunk.pending.type] = pending + } + if (rejected) { + context.sliceCaseReducersByType[thunk.rejected.type] = rejected + } + + context.sliceCaseReducersByName[reducerName] = { + fulfilled: fulfilled || noop, + pending: pending || noop, + rejected: rejected || noop, + } +} + +function noop() {} diff --git a/packages/toolkit/src/tests/createSlice.test.ts b/packages/toolkit/src/tests/createSlice.test.ts index a92893180b..9a343bd2fb 100644 --- a/packages/toolkit/src/tests/createSlice.test.ts +++ b/packages/toolkit/src/tests/createSlice.test.ts @@ -1,7 +1,11 @@ import { vi } from 'vitest' import type { PayloadAction, WithSlice } from '@reduxjs/toolkit' -import { combineSlices } from '@reduxjs/toolkit' -import { createSlice, createAction } from '@reduxjs/toolkit' +import { + configureStore, + combineSlices, + createSlice, + createAction, +} from '@reduxjs/toolkit' import { mockConsole, createConsole, @@ -553,4 +557,215 @@ describe('createSlice', () => { expect(injected2State.injected2).toBe(slice.getInitialState() + 1) }) }) + describe('reducers definition with asyncThunks', () => { + function pending(state: any[], action: any) { + state.push(['pendingReducer', action]) + } + function fulfilled(state: any[], action: any) { + state.push(['fulfilledReducer', action]) + } + function rejected(state: any[], action: any) { + state.push(['rejectedReducer', action]) + } + + test('successful thunk', async () => { + const slice = createSlice({ + name: 'test', + initialState: [] as any[], + reducers: (create) => ({ + thunkReducers: create.asyncThunk( + function payloadCreator(arg, api) { + return Promise.resolve('resolved payload') + }, + { pending, fulfilled, rejected } + ), + }), + }) + + const store = configureStore({ + reducer: slice.reducer, + }) + await store.dispatch(slice.actions.thunkReducers('test')) + expect(store.getState()).toMatchObject([ + [ + 'pendingReducer', + { + type: 'test/thunkReducers/pending', + payload: undefined, + }, + ], + [ + 'fulfilledReducer', + { + type: 'test/thunkReducers/fulfilled', + payload: 'resolved payload', + }, + ], + ]) + }) + + test('rejected thunk', async () => { + const slice = createSlice({ + name: 'test', + initialState: [] as any[], + reducers: (create) => ({ + thunkReducers: create.asyncThunk( + // payloadCreator isn't allowed to return never + function payloadCreator(arg, api): any { + throw new Error('') + }, + { pending, fulfilled, rejected } + ), + }), + }) + + const store = configureStore({ + reducer: slice.reducer, + }) + await store.dispatch(slice.actions.thunkReducers('test')) + expect(store.getState()).toMatchObject([ + [ + 'pendingReducer', + { + type: 'test/thunkReducers/pending', + payload: undefined, + }, + ], + [ + 'rejectedReducer', + { + type: 'test/thunkReducers/rejected', + payload: undefined, + }, + ], + ]) + }) + + test('with options', async () => { + const slice = createSlice({ + name: 'test', + initialState: [] as any[], + reducers: (create) => ({ + thunkReducers: create.asyncThunk( + function payloadCreator(arg, api) { + return 'should not call this' + }, + { + options: { + condition() { + return false + }, + dispatchConditionRejection: true, + }, + pending, + fulfilled, + rejected, + } + ), + }), + }) + + const store = configureStore({ + reducer: slice.reducer, + }) + await store.dispatch(slice.actions.thunkReducers('test')) + expect(store.getState()).toMatchObject([ + [ + 'rejectedReducer', + { + type: 'test/thunkReducers/rejected', + payload: undefined, + meta: { condition: true }, + }, + ], + ]) + }) + + test('has caseReducers for the asyncThunk', async () => { + const slice = createSlice({ + name: 'test', + initialState: [], + reducers: (create) => ({ + thunkReducers: create.asyncThunk( + function payloadCreator(arg, api) { + return Promise.resolve('resolved payload') + }, + { pending, fulfilled } + ), + }), + }) + + expect(slice.caseReducers.thunkReducers.pending).toBe(pending) + expect(slice.caseReducers.thunkReducers.fulfilled).toBe(fulfilled) + // even though it is not defined above, this should at least be a no-op function to match the TypeScript typings + // and should be callable as a reducer even if it does nothing + expect(() => + slice.caseReducers.thunkReducers.rejected( + [], + slice.actions.thunkReducers.rejected( + new Error('test'), + 'fakeRequestId', + {} + ) + ) + ).not.toThrow() + }) + + test('can define reducer with prepare statement using create.preparedReducer', async () => { + const slice = createSlice({ + name: 'test', + initialState: [] as any[], + reducers: (create) => ({ + prepared: create.preparedReducer( + (p: string, m: number, e: { message: string }) => ({ + payload: p, + meta: m, + error: e, + }), + (state, action) => { + state.push(action) + } + ), + }), + }) + + expect( + slice.reducer([], slice.actions.prepared('test', 1, { message: 'err' })) + ).toMatchInlineSnapshot(` + [ + { + "error": { + "message": "err", + }, + "meta": 1, + "payload": "test", + "type": "test/prepared", + }, + ] + `) + }) + + test('throws an error when invoked with a normal `prepare` object that has not gone through a `create.preparedReducer` call', async () => { + expect(() => + createSlice({ + name: 'test', + initialState: [] as any[], + reducers: (create) => ({ + prepared: { + prepare: (p: string, m: number, e: { message: string }) => ({ + payload: p, + meta: m, + error: e, + }), + reducer: (state, action) => { + state.push(action) + }, + }, + }), + }) + ).toThrowErrorMatchingInlineSnapshot( + `"Please use the \`create.preparedReducer\` notation for prepared action creators with the \`create\` notation."` + ) + }) + }) }) diff --git a/packages/toolkit/src/tests/createSlice.typetest.ts b/packages/toolkit/src/tests/createSlice.typetest.ts index 11669eafee..272607aecb 100644 --- a/packages/toolkit/src/tests/createSlice.typetest.ts +++ b/packages/toolkit/src/tests/createSlice.typetest.ts @@ -6,12 +6,18 @@ import type { ActionCreatorWithPayload, ActionCreatorWithPreparedPayload, ActionReducerMapBuilder, + AsyncThunk, + CaseReducer, PayloadAction, + PayloadActionCreator, + SerializedError, SliceCaseReducers, + ThunkDispatch, ValidateSliceCaseReducers, } from '@reduxjs/toolkit' +import { configureStore } from '@reduxjs/toolkit' import { createAction, createSlice } from '@reduxjs/toolkit' -import { expectType } from './helpers' +import { expectType, expectUnknown } from './helpers' /* * Test: Slice name is strongly typed. @@ -552,3 +558,150 @@ const value = actionCreators.anyKey expectType(nestedSelectors.selectMultiply(nestedState, 2)) expectType(nestedSelectors.selectToFixed(nestedState)) } + +{ + interface TestState { + foo: string + } + + interface TestArg { + test: string + } + + interface TestReturned { + payload: string + } + + interface TestReject { + cause: string + } + + const slice = createSlice({ + name: 'test', + initialState: {} as TestState, + reducers: (create) => { + const pretypedAsyncThunk = + create.asyncThunk.withTypes<{ rejectValue: TestReject }>() + + // @ts-expect-error + create.asyncThunk(() => {}) + + // @ts-expect-error + create.asyncThunk.withTypes<{ + rejectValue: string + dispatch: StoreDispatch + }>() + + return { + normalReducer: create.reducer((state, action) => { + expectType(state) + expectType(action.payload) + }), + testInfer: create.asyncThunk( + function payloadCreator(arg: TestArg, api) { + return Promise.resolve({ payload: 'foo' }) + }, + { + pending(state, action) { + expectType(state) + expectType(action.meta.arg) + }, + fulfilled(state, action) { + expectType(state) + expectType(action.meta.arg) + expectType(action.payload) + }, + rejected(state, action) { + expectType(state) + expectType(action.meta.arg) + expectType(action.error) + }, + } + ), + testExplicitType: create.asyncThunk< + TestArg, + TestReturned, + { + rejectValue: TestReject + } + >( + function payloadCreator(arg, api) { + // here would be a circular reference + expectUnknown(api.getState()) + // here would be a circular reference + expectType>(api.dispatch) + // so you need to cast inside instead + const getState = api.getState as () => StoreState + const dispatch = api.dispatch as StoreDispatch + expectType(arg) + expectType<(value: TestReject) => any>(api.rejectWithValue) + return Promise.resolve({ payload: 'foo' }) + }, + { + pending(state, action) { + expectType(state) + expectType(action.meta.arg) + }, + fulfilled(state, action) { + expectType(state) + expectType(action.meta.arg) + expectType(action.payload) + }, + rejected(state, action) { + expectType(state) + expectType(action.meta.arg) + expectType(action.error) + expectType(action.payload) + }, + } + ), + testPretyped: pretypedAsyncThunk( + function payloadCreator(arg: TestArg, api) { + expectType<(value: TestReject) => any>(api.rejectWithValue) + return Promise.resolve({ payload: 'foo' }) + }, + { + pending(state, action) { + expectType(state) + expectType(action.meta.arg) + }, + fulfilled(state, action) { + expectType(state) + expectType(action.meta.arg) + expectType(action.payload) + }, + rejected(state, action) { + expectType(state) + expectType(action.meta.arg) + expectType(action.error) + expectType(action.payload) + }, + } + ), + } + }, + }) + + const store = configureStore({ reducer: { test: slice.reducer } }) + + type StoreState = ReturnType + type StoreDispatch = typeof store.dispatch + + expectType>(slice.actions.normalReducer) + expectType>(slice.actions.testInfer) + expectType>( + slice.actions.testExplicitType + ) + { + type TestInferThunk = AsyncThunk + expectType>>( + slice.caseReducers.testInfer.pending + ) + expectType>>( + slice.caseReducers.testInfer.fulfilled + ) + expectType>>( + slice.caseReducers.testInfer.rejected + ) + } +} From a15d6e8c7adfe52e31486d68538c95def10d5185 Mon Sep 17 00:00:00 2001 From: "ben.durrant" Date: Fri, 21 Apr 2023 17:10:52 +0100 Subject: [PATCH 135/412] reuse utility and test preparedReducer --- packages/toolkit/src/createAction.ts | 20 ---------------- packages/toolkit/src/createSlice.ts | 11 ++++++--- .../toolkit/src/tests/createSlice.typetest.ts | 24 ++++++++++++++++++- 3 files changed, 31 insertions(+), 24 deletions(-) diff --git a/packages/toolkit/src/createAction.ts b/packages/toolkit/src/createAction.ts index adf8ddec52..ab52d1c4fc 100644 --- a/packages/toolkit/src/createAction.ts +++ b/packages/toolkit/src/createAction.ts @@ -50,26 +50,6 @@ export type PrepareAction

= | ((...args: any[]) => { payload: P; error: any }) | ((...args: any[]) => { payload: P; meta: any; error: any }) -export type _PayloadActionForPrepare< - PA extends PrepareAction, - T extends string = string -> = PA extends PrepareAction - ? PayloadAction< - P, - T, - ReturnType extends { - meta: infer M - } - ? M - : never, - ReturnType extends { - error: infer E - } - ? E - : never - > - : never - /** * Internal version of `ActionCreatorWithPreparedPayload`. Not to be used externally. * diff --git a/packages/toolkit/src/createSlice.ts b/packages/toolkit/src/createSlice.ts index b978b62663..28ec26571d 100644 --- a/packages/toolkit/src/createSlice.ts +++ b/packages/toolkit/src/createSlice.ts @@ -5,7 +5,6 @@ import type { PayloadActionCreator, PrepareAction, _ActionCreatorWithPreparedPayload, - _PayloadActionForPrepare, } from './createAction' import { createAction } from './createAction' import type { @@ -373,11 +372,17 @@ interface ReducerCreators { preparedReducer>( prepare: Prepare, - reducer: CaseReducer> + reducer: CaseReducer< + State, + ReturnType<_ActionCreatorWithPreparedPayload> + > ): { [reducerDefinitionType]: ReducerType.reducerWithPrepare prepare: Prepare - reducer: CaseReducer> + reducer: CaseReducer< + State, + ReturnType<_ActionCreatorWithPreparedPayload> + > } } diff --git a/packages/toolkit/src/tests/createSlice.typetest.ts b/packages/toolkit/src/tests/createSlice.typetest.ts index 272607aecb..ce155e5530 100644 --- a/packages/toolkit/src/tests/createSlice.typetest.ts +++ b/packages/toolkit/src/tests/createSlice.typetest.ts @@ -17,7 +17,7 @@ import type { } from '@reduxjs/toolkit' import { configureStore } from '@reduxjs/toolkit' import { createAction, createSlice } from '@reduxjs/toolkit' -import { expectType, expectUnknown } from './helpers' +import { expectExactType, expectType, expectUnknown } from './helpers' /* * Test: Slice name is strongly typed. @@ -597,6 +597,19 @@ const value = actionCreators.anyKey expectType(state) expectType(action.payload) }), + preparedReducer: create.preparedReducer( + (payload: string) => ({ + payload, + meta: 'meta' as const, + error: 'error' as const, + }), + (state, action) => { + expectType(state) + expectType(action.payload) + expectExactType('meta' as const)(action.meta) + expectExactType('error' as const)(action.error) + } + ), testInfer: create.asyncThunk( function payloadCreator(arg: TestArg, api) { return Promise.resolve({ payload: 'foo' }) @@ -688,6 +701,15 @@ const value = actionCreators.anyKey type StoreDispatch = typeof store.dispatch expectType>(slice.actions.normalReducer) + expectType< + ActionCreatorWithPreparedPayload< + [string], + string, + 'test/preparedReducer', + 'error', + 'meta' + > + >(slice.actions.preparedReducer) expectType>(slice.actions.testInfer) expectType>( slice.actions.testExplicitType From 077da03f13d8cc4e2ec3aa3d783cff93e6d3e57c Mon Sep 17 00:00:00 2001 From: "ben.durrant" Date: Tue, 25 Apr 2023 18:11:18 +0100 Subject: [PATCH 136/412] ensure it's only possible to pass all or none of the hooks --- packages/toolkit/src/query/createApi.ts | 6 ++++- packages/toolkit/src/query/react/module.ts | 26 ++++++++++++++-------- packages/toolkit/src/query/tsHelpers.ts | 5 +++-- 3 files changed, 25 insertions(+), 12 deletions(-) diff --git a/packages/toolkit/src/query/createApi.ts b/packages/toolkit/src/query/createApi.ts index d98116dd1e..f99e11c1a2 100644 --- a/packages/toolkit/src/query/createApi.ts +++ b/packages/toolkit/src/query/createApi.ts @@ -219,7 +219,11 @@ export type CreateApi = { * const MyContext = React.createContext(null as any); * const customCreateApi = buildCreateApi( * coreModule(), - * reactHooksModule({ useDispatch: createDispatchHook(MyContext) }) + * reactHooksModule({ + * useDispatch: createDispatchHook(MyContext), + * useSelector: createSelectorHook(MyContext), + * useStore: createStoreHook(MyContext) + * }) * ); * ``` * diff --git a/packages/toolkit/src/query/react/module.ts b/packages/toolkit/src/query/react/module.ts index 5028c64fe7..aacf9d8550 100644 --- a/packages/toolkit/src/query/react/module.ts +++ b/packages/toolkit/src/query/react/module.ts @@ -9,6 +9,7 @@ import type { } from '@reduxjs/toolkit/dist/query/endpointDefinitions' import type { Api, Module } from '../apiTypes' import { capitalize } from '../utils' +import type { AllOrNone } from '../tsHelpers' import { safeAssign } from '../tsHelpers' import type { BaseQueryFn } from '@reduxjs/toolkit/dist/query/baseQueryTypes' @@ -69,23 +70,26 @@ declare module '@reduxjs/toolkit/dist/query/apiTypes' { type RR = typeof import('react-redux') -export interface ReactHooksModuleOptions { - /** - * The version of the `batchedUpdates` function to be used - */ - batch?: RR['batch'] +type ReactHooks = { /** * The version of the `useDispatch` hook to be used */ - useDispatch?: RR['useDispatch'] + useDispatch: RR['useDispatch'] /** * The version of the `useSelector` hook to be used */ - useSelector?: RR['useSelector'] + useSelector: RR['useSelector'] /** * The version of the `useStore` hook to be used */ - useStore?: RR['useStore'] + useStore: RR['useStore'] +} + +export type ReactHooksModuleOptions = AllOrNone & { + /** + * The version of the `batchedUpdates` function to be used + */ + batch?: RR['batch'] /** * Enables performing asynchronous tasks immediately within a render. * @@ -115,7 +119,11 @@ export interface ReactHooksModuleOptions { * const MyContext = React.createContext(null as any); * const customCreateApi = buildCreateApi( * coreModule(), - * reactHooksModule({ useDispatch: createDispatchHook(MyContext) }) + * reactHooksModule({ + * useDispatch: createDispatchHook(MyContext), + * useSelector: createSelectorHook(MyContext), + * useStore: createStoreHook(MyContext) + * }) * ); * ``` * diff --git a/packages/toolkit/src/query/tsHelpers.ts b/packages/toolkit/src/query/tsHelpers.ts index d1028bbe13..debe068965 100644 --- a/packages/toolkit/src/query/tsHelpers.ts +++ b/packages/toolkit/src/query/tsHelpers.ts @@ -2,13 +2,14 @@ export type Id = { [K in keyof T]: T[K] } & {} export type WithRequiredProp = Omit & Required> export type Override = T2 extends any ? Omit & T2 : never +export type AllOrNone = T | { [K in keyof T]?: never } export function assertCast(v: any): asserts v is T {} export function safeAssign( target: T, ...args: Array>> -) { - Object.assign(target, ...args) +): T { + return Object.assign(target, ...args) } /** From b394b04609c20bf0ba256fd42f247f9e98cd211c Mon Sep 17 00:00:00 2001 From: "ben.durrant" Date: Tue, 25 Apr 2023 18:28:03 +0100 Subject: [PATCH 137/412] Move hooks under single key, for better DX (breaking change) --- .../usage/customizing-create-api.mdx | 12 +++-- packages/toolkit/src/query/createApi.ts | 8 +-- packages/toolkit/src/query/react/module.ts | 51 ++++++++++--------- packages/toolkit/src/query/tsHelpers.ts | 1 - 4 files changed, 41 insertions(+), 31 deletions(-) diff --git a/docs/rtk-query/usage/customizing-create-api.mdx b/docs/rtk-query/usage/customizing-create-api.mdx index 2e19c257d9..ad63dc22ec 100644 --- a/docs/rtk-query/usage/customizing-create-api.mdx +++ b/docs/rtk-query/usage/customizing-create-api.mdx @@ -19,7 +19,7 @@ You can create your own versions of `createApi` by either specifying non-default ## Customizing the React-Redux Hooks -If you want the hooks to use different versions of `useSelector` or `useDispatch`, such as if you are using a custom context, you can pass these in at module creation: +If you want the hooks to use different versions of `useSelector`, `useDispatch` and `useStore`, such as if you are using a custom context, you can pass these in at module creation: ```ts import * as React from 'react' @@ -33,7 +33,13 @@ import { const MyContext = React.createContext(null as any) const customCreateApi = buildCreateApi( coreModule(), - reactHooksModule({ useDispatch: createDispatchHook(MyContext) }) + reactHooksModule({ + hooks: { + useDispatch: createDispatchHook(MyContext), + useSelector: createSelectorHook(MyContext), + useStore: createStoreHook(MyContext), + }, + }) ) ``` @@ -81,7 +87,7 @@ export const myModule = (): Module => ({ return { injectEndpoint(endpoint, definition) { - const anyApi = (api as any) as Api< + const anyApi = api as any as Api< any, Record, string, diff --git a/packages/toolkit/src/query/createApi.ts b/packages/toolkit/src/query/createApi.ts index f99e11c1a2..5ff83e860e 100644 --- a/packages/toolkit/src/query/createApi.ts +++ b/packages/toolkit/src/query/createApi.ts @@ -220,9 +220,11 @@ export type CreateApi = { * const customCreateApi = buildCreateApi( * coreModule(), * reactHooksModule({ - * useDispatch: createDispatchHook(MyContext), - * useSelector: createSelectorHook(MyContext), - * useStore: createStoreHook(MyContext) + * hooks: { + * useDispatch: createDispatchHook(MyContext), + * useSelector: createSelectorHook(MyContext), + * useStore: createStoreHook(MyContext) + * } * }) * ); * ``` diff --git a/packages/toolkit/src/query/react/module.ts b/packages/toolkit/src/query/react/module.ts index aacf9d8550..b9bc6f999b 100644 --- a/packages/toolkit/src/query/react/module.ts +++ b/packages/toolkit/src/query/react/module.ts @@ -9,7 +9,6 @@ import type { } from '@reduxjs/toolkit/dist/query/endpointDefinitions' import type { Api, Module } from '../apiTypes' import { capitalize } from '../utils' -import type { AllOrNone } from '../tsHelpers' import { safeAssign } from '../tsHelpers' import type { BaseQueryFn } from '@reduxjs/toolkit/dist/query/baseQueryTypes' @@ -70,22 +69,24 @@ declare module '@reduxjs/toolkit/dist/query/apiTypes' { type RR = typeof import('react-redux') -type ReactHooks = { +export interface ReactHooksModuleOptions { /** - * The version of the `useDispatch` hook to be used + * The hooks from React Redux to be used */ - useDispatch: RR['useDispatch'] - /** - * The version of the `useSelector` hook to be used - */ - useSelector: RR['useSelector'] - /** - * The version of the `useStore` hook to be used - */ - useStore: RR['useStore'] -} - -export type ReactHooksModuleOptions = AllOrNone & { + hooks?: { + /** + * The version of the `useDispatch` hook to be used + */ + useDispatch: RR['useDispatch'] + /** + * The version of the `useSelector` hook to be used + */ + useSelector: RR['useSelector'] + /** + * The version of the `useStore` hook to be used + */ + useStore: RR['useStore'] + } /** * The version of the `batchedUpdates` function to be used */ @@ -120,9 +121,11 @@ export type ReactHooksModuleOptions = AllOrNone & { * const customCreateApi = buildCreateApi( * coreModule(), * reactHooksModule({ - * useDispatch: createDispatchHook(MyContext), - * useSelector: createSelectorHook(MyContext), - * useStore: createStoreHook(MyContext) + * hooks: { + * useDispatch: createDispatchHook(MyContext), + * useSelector: createSelectorHook(MyContext), + * useStore: createStoreHook(MyContext) + * } * }) * ); * ``` @@ -131,9 +134,11 @@ export type ReactHooksModuleOptions = AllOrNone & { */ export const reactHooksModule = ({ batch = rrBatch, - useDispatch = rrUseDispatch, - useSelector = rrUseSelector, - useStore = rrUseStore, + hooks = { + useDispatch: rrUseDispatch, + useSelector: rrUseSelector, + useStore: rrUseStore, + }, unstable__sideEffectsInRender = false, }: ReactHooksModuleOptions = {}): Module => ({ name: reactHooksModuleName, @@ -149,9 +154,7 @@ export const reactHooksModule = ({ api, moduleOptions: { batch, - useDispatch, - useSelector, - useStore, + hooks, unstable__sideEffectsInRender, }, serializeQueryArgs, diff --git a/packages/toolkit/src/query/tsHelpers.ts b/packages/toolkit/src/query/tsHelpers.ts index debe068965..7c2734b448 100644 --- a/packages/toolkit/src/query/tsHelpers.ts +++ b/packages/toolkit/src/query/tsHelpers.ts @@ -2,7 +2,6 @@ export type Id = { [K in keyof T]: T[K] } & {} export type WithRequiredProp = Omit & Required> export type Override = T2 extends any ? Omit & T2 : never -export type AllOrNone = T | { [K in keyof T]?: never } export function assertCast(v: any): asserts v is T {} export function safeAssign( From bd0036b24349ec1096ae1c0cd223cd43efd97c53 Mon Sep 17 00:00:00 2001 From: "ben.durrant" Date: Tue, 25 Apr 2023 18:31:36 +0100 Subject: [PATCH 138/412] fix buildHooks --- packages/toolkit/src/query/react/buildHooks.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/packages/toolkit/src/query/react/buildHooks.ts b/packages/toolkit/src/query/react/buildHooks.ts index 28e2916bf7..64779f2949 100644 --- a/packages/toolkit/src/query/react/buildHooks.ts +++ b/packages/toolkit/src/query/react/buildHooks.ts @@ -581,9 +581,7 @@ export function buildHooks({ api, moduleOptions: { batch, - useDispatch, - useSelector, - useStore, + hooks: { useDispatch, useSelector, useStore }, unstable__sideEffectsInRender, }, serializeQueryArgs, From ecfe0f679909a962e6d0a16906e91b90db8567a0 Mon Sep 17 00:00:00 2001 From: Ben Durrant Date: Sat, 29 Apr 2023 00:49:57 +0100 Subject: [PATCH 139/412] Begin updating slice docs for new 2.0 features --- docs/api/createEntityAdapter.mdx | 25 +++-- docs/api/createReducer.mdx | 45 ++------- docs/api/createSelector.mdx | 8 +- docs/api/createSlice.mdx | 139 ++++++++++++++++++++------ packages/toolkit/src/createReducer.ts | 9 +- packages/toolkit/src/createSlice.ts | 9 +- 6 files changed, 141 insertions(+), 94 deletions(-) diff --git a/docs/api/createEntityAdapter.mdx b/docs/api/createEntityAdapter.mdx index 0cfee25b9e..4ea41915a2 100644 --- a/docs/api/createEntityAdapter.mdx +++ b/docs/api/createEntityAdapter.mdx @@ -15,11 +15,13 @@ A function that generates a set of prebuilt reducers and selectors for performin This API was ported from [the `@ngrx/entity` library](https://ngrx.io/guide/entity) created by the NgRx maintainers, but has been significantly modified for use with Redux Toolkit. We'd like to thank the NgRx team for originally creating this API and allowing us to port and adapt it for our needs. -> **Note**: The term "Entity" is used to refer to a unique type of data object in an application. For example, in a blogging application, you might have `User`, `Post`, and `Comment` data objects, with many instances of each being stored in the client and persisted on the server. `User` is an "entity" - a unique type of data object that the application uses. Each unique instance of an entity is assumed to have a unique ID value in a specific field. -> -> As with all Redux logic, [_only_ plain JS objects and arrays should be passed in to the store - **no class instances!**](https://redux.js.org/style-guide/style-guide#do-not-put-non-serializable-values-in-state-or-actions) -> -> For purposes of this reference, we will use `Entity` to refer to the specific data type that is being managed by a copy of the reducer logic in a specific portion of the Redux state tree, and `entity` to refer to a single instance of that type. Example: in `state.users`, `Entity` would refer to the `User` type, and `state.users.entities[123]` would be a single `entity`. +::: note +The term "Entity" is used to refer to a unique type of data object in an application. For example, in a blogging application, you might have `User`, `Post`, and `Comment` data objects, with many instances of each being stored in the client and persisted on the server. `User` is an "entity" - a unique type of data object that the application uses. Each unique instance of an entity is assumed to have a unique ID value in a specific field. + +As with all Redux logic, [_only_ plain JS objects and arrays should be passed in to the store - **no class instances!**](https://redux.js.org/style-guide/style-guide#do-not-put-non-serializable-values-in-state-or-actions) + +For purposes of this reference, we will use `Entity` to refer to the specific data type that is being managed by a copy of the reducer logic in a specific portion of the Redux state tree, and `entity` to refer to a single instance of that type. Example: in `state.users`, `Entity` would refer to the `User` type, and `state.users.entities[123]` would be a single `entity`. +::: The methods generated by `createEntityAdapter` will all manipulate an "entity state" structure that looks like: @@ -228,7 +230,6 @@ All three options will insert _new_ entities into the list. However they differ ::: - Each method has a signature that looks like: ```ts no-transpile @@ -243,7 +244,9 @@ These CRUD methods may be used in multiple ways: - They may be used as "mutating" helper methods when called manually, such as a separate hand-written call to `addOne()` inside of an existing case reducer, if the `state` argument is actually an Immer `Draft` value. - They may be used as immutable update methods when called manually, if the `state` argument is actually a plain JS object or array. -> **Note**: These methods do _not_ have corresponding Redux actions created - they are just standalone reducers / update logic. **It is entirely up to you to decide where and how to use these methods!** Most of the time, you will want to pass them to `createSlice` or use them inside another reducer. +:::note +These methods do _not_ have corresponding Redux actions created - they are just standalone reducers / update logic. **It is entirely up to you to decide where and how to use these methods!** Most of the time, you will want to pass them to `createSlice` or use them inside another reducer. +::: Each method will check to see if the `state` argument is an Immer `Draft` or not. If it is a draft, the method will assume that it's safe to continue mutating that draft further. If it is not a draft, the method will pass the plain JS value to Immer's `createNextState()`, and return the immutably updated result value. @@ -358,12 +361,8 @@ const booksSlice = createSlice({ }, }) -const { - bookAdded, - booksLoading, - booksReceived, - bookUpdated, -} = booksSlice.actions +const { bookAdded, booksLoading, booksReceived, bookUpdated } = + booksSlice.actions const store = configureStore({ reducer: { diff --git a/docs/api/createReducer.mdx b/docs/api/createReducer.mdx index 5103adbe16..2526b9a3d9 100644 --- a/docs/api/createReducer.mdx +++ b/docs/api/createReducer.mdx @@ -70,15 +70,15 @@ const counterReducer = createReducer(initialState, (builder) => { ## Usage with the "Builder Callback" Notation -[overloadSummary](docblock://createReducer.ts?token=createReducer&overload=0) +[overloadSummary](docblock://createReducer.ts?token=createReducer) ### Parameters -[params](docblock://createReducer.ts?token=createReducer&overload=0) +[params](docblock://createReducer.ts?token=createReducer) ### Example Usage -[examples](docblock://createReducer.ts?token=createReducer&overload=0) +[examples](docblock://createReducer.ts?token=createReducer) ### Builder Methods @@ -113,9 +113,10 @@ The generated reducer function. The reducer will have a `getInitialState` function attached that will return the initial state when called. This may be useful for tests or usage with React's `useReducer` hook: ```js -const counterReducer = createReducer(0, { - increment: (state, action) => state + action.payload, - decrement: (state, action) => state - action.payload, +const counterReducer = createReducer(0, (builder) => { + builder + .addCase('increment', (state, action) => state + action.payload) + .addCase('decrement', (state, action) => state - action.payload) }) console.log(counterReducer.getInitialState()) // 0 @@ -123,37 +124,7 @@ console.log(counterReducer.getInitialState()) // 0 ### Example Usage -[examples](docblock://createReducer.ts?token=createReducer&overload=1) - -### Matchers and Default Cases as Arguments - -The most readable approach to define matcher cases and default cases is by using the `builder.addMatcher` and `builder.addDefaultCase` methods described above, but it is also possible to use these with the object notation by passing an array of `{matcher, reducer}` objects as the third argument, and a default case reducer as the fourth argument: - -```js -const isStringPayloadAction = (action) => typeof action.payload === 'string' - -const lengthOfAllStringsReducer = createReducer( - // initial state - { strLen: 0, nonStringActions: 0 }, - // normal reducers - { - /*...*/ - }, - // array of matcher reducers - [ - { - matcher: isStringPayloadAction, - reducer(state, action) { - state.strLen += action.payload.length - }, - }, - ], - // default reducer - (state) => { - state.nonStringActions++ - } -) -``` +[examples](docblock://createReducer.ts?token=createReducer) ## Direct State Mutation diff --git a/docs/api/createSelector.mdx b/docs/api/createSelector.mdx index ec88f8fd3d..be3711db7b 100644 --- a/docs/api/createSelector.mdx +++ b/docs/api/createSelector.mdx @@ -18,9 +18,11 @@ For more details on using `createSelector`, see: - [Idiomatic Redux: Using Reselect Selectors for Encapsulation and Performance](https://blog.isquaredsoftware.com/2017/12/idiomatic-redux-using-reselect-selectors/) - [React/Redux Links: Reducers and Selectors](https://github.com/markerikson/react-redux-links/blob/master/redux-reducers-selectors.md) -> **Note**: Prior to v0.7, RTK re-exported `createSelector` from [`selectorator`](https://github.com/planttheidea/selectorator), which -> allowed using string keypaths as input selectors. This was removed, as it ultimately did not provide enough benefits, and -> the string keypaths made static typing for selectors difficult. +:::note +Prior to v0.7, RTK re-exported `createSelector` from [`selectorator`](https://github.com/planttheidea/selectorator), which +allowed using string keypaths as input selectors. This was removed, as it ultimately did not provide enough benefits, and +the string keypaths made static typing for selectors difficult. +::: # `createDraftSafeSelector` diff --git a/docs/api/createSlice.mdx b/docs/api/createSlice.mdx index 39f10f8b70..8d580f2803 100644 --- a/docs/api/createSlice.mdx +++ b/docs/api/createSlice.mdx @@ -56,11 +56,15 @@ function createSlice({ // A name, used in action types name: string, // The initial state for the reducer - initialState: any, + initialState: State, // An object of "case reducers". Key names will be used to generate actions. - reducers: Object + reducers: Record, // A "builder callback" function used to add more reducers - extraReducers?: ((builder: ActionReducerMapBuilder) => void) + extraReducers?: (builder: ActionReducerMapBuilder) => void, + // A preference for the slice reducer's location, used by `combineSlices` and `slice.selectors`. Defaults to `name`. + reducerPath?: string, + // An object of selectors, which receive the slice's state as their first parameter. + selectors?: Record any>, }) ``` @@ -143,7 +147,7 @@ As with `reducers`, these case reducers will also be passed to `createReducer` a If two fields from `reducers` and `extraReducers` happen to end up with the same action type string, the function from `reducers` will be used to handle that action type. -### The `extraReducers` "builder callback" notation +#### The `extraReducers` "builder callback" notation Similar to `createReducer`, the `extraReducers` field uses a "builder callback" notation to define handlers for specific action types, matching against a range of actions, or handling a default case. This is conceptually similar to a switch statement, but with better TS support as it can infer the action type from the provided action creator. It's particularly useful for working with actions produced by `createAction` and `createAsyncThunk`. @@ -151,17 +155,33 @@ Similar to `createReducer`, the `extraReducers` field uses a "builder callback" See [the "Builder Callback Notation" section of the `createReducer` reference](./createReducer.mdx#usage-with-the-builder-callback-notation) for details on how to use `builder.addCase`, `builder.addMatcher`, and `builder.addDefault` +### `reducerPath` + +Indicates a preference of where the slice should be located. Defaults to [`name`](#name). + +This is used by `combineSlices` and the default generated `slice.selectors`. + +### `selectors` + +A set of selectors that receive the slice state as their first parameter, and any other parameters. + +Each selector will have a corresponding key in the + ## Return Value `createSlice` will return an object that looks like: ```ts no-transpile { - name : string, - reducer : ReducerFunction, - actions : Record, + name: string, + reducer: ReducerFunction, + actions: Record, caseReducers: Record. - getInitialState: () => State + getInitialState: () => State, + reducerPath: string, + selectors: Record, + getSelectors: (selectState: (rootState: RootState) => State) => Record + injectInto: (injectable: Injectable, config?: InjectConfig & { reducerPath?: string }) => InjectedSlice } ``` @@ -177,15 +197,80 @@ The functions passed to the `reducers` parameter can be accessed through the `ca Result's function `getInitialState` provides access to the initial state value given to the slice. If a lazy state initializer was provided, it will be called and a fresh value returned. -> **Note**: the result object is conceptually similar to a -> ["Redux duck" code structure](https://redux.js.org/faq/code-structure#what-should-my-file-structure-look-like-how-should-i-group-my-action-creators-and-reducers-in-my-project-where-should-my-selectors-go). -> The actual code structure you use is up to you, but there are a couple caveats to keep in mind: -> -> - Actions are not exclusively limited to a single slice. Any part of the reducer logic can (and should!) respond -> to any dispatched action. -> - At the same time, circular references can cause import problems. If slices A and B are defined in -> separate files, and each file tries to import the other so it can listen to other actions, unexpected -> behavior may occur. +`injectInto` creates an instance of the slice that is aware it's been injected - see `combineSlices`. + +:::note +The result object is conceptually similar to a +["Redux duck" code structure](https://redux.js.org/faq/code-structure#what-should-my-file-structure-look-like-how-should-i-group-my-action-creators-and-reducers-in-my-project-where-should-my-selectors-go). +The actual code structure you use is up to you, but it's worth keeping in mind that actions are not exclusively limited to a single slice. +Any part of the reducer logic can (and should!) respond to any dispatched action. +::: + +### Selectors + +Slice selectors are written to expect the slice's state as their first parameter, but the slice may be located anywhere inside the store's root state. + +As a result, there are two ways of getting final selectors: + +#### `selectors` + +Most commonly, the slice is reliably mounted under its [`reducerPath`](#reducerPath). + +Following this, the slice has a `selectors` object attached, which creates selectors with the assumption that the slice is located under `rootState[slice.reducerPath]`. + +```ts +import { createSlice } from '@reduxjs/toolkit' + +interface CounterState { + value: number +} + +const counterSlice = createSlice({ + name: 'counter', + initialState: { value: 0 } as CounterState, + reducers: { + // omitted + }, + selectors: { + selectValue: (sliceState) => sliceState.value, + }, +}) + +const { selectValue } = counterSlice.selectors + +console.log(selectValue({ counter: { value: 2 } })) // 2 +``` + +#### `getSelectors` + +`slice.getSelectors` is called with a single parameter, a `selectState` callback. This function should receive the store root state (or whatever you expect to call the resulting selectors with) and return the slice state. + +```ts no-transpile +const { selectValue } = counterSlice.getSelectors( + (rootState: RootState) => rootState.aCounter +) + +console.log(selectValue({ aCounter: { value: 2 } })) // 2 +``` + +If no `selectState` callback is passed, selectors will be returned as is - expecting the slice state as their first parameter (the same as calling `slice.getSelectors(state => state)`). + +```ts no-transpile +const { selectValue } = counterSlice.getSelectors() + +console.log(selectValue({ value: 2 })) // 2 +``` + +:::note +The [`slice.selectors`](#selectors-2) object is the equivalent of calling + +```ts no-transpile +const { selectValue } = counterSlice.getSelectors( + (rootState: RootState) => rootState[slice.reducerPath] +) +``` + +::: ## Examples @@ -208,7 +293,6 @@ const counter = createSlice({ prepare: (value?: number) => ({ payload: value || 2 }), // fallback if the payload is a falsy value }, }, - // "builder callback API", recommended for TypeScript users extraReducers: (builder) => { builder.addCase(incrementBy, (state, action) => { return state + action.payload @@ -227,23 +311,16 @@ const user = createSlice({ state.name = action.payload // mutate the state all you want with immer }, }, - // "map object API" extraReducers: (builder) => { - builder.addCase( - counter.actions.increment, - ( - state, - action /* action will be inferred as "any", as the map notation does not contain type information */ - ) => { - state.age += 1 - } - ) + builder.addCase(counter.actions.increment, (state, action) => { + state.age += 1 + }) }, }) const reducer = combineReducers({ - counter: counter.reducer, - user: user.reducer, + [counter.reducerPath]: counter.reducer, + [user.reducerPath]: user.reducer, }) const store = createStore(reducer) @@ -256,7 +333,7 @@ store.dispatch(counter.actions.multiply(3)) // -> { counter: 6, user: {name: '', age: 22} } store.dispatch(counter.actions.multiply()) // -> { counter: 12, user: {name: '', age: 22} } -console.log(`${counter.actions.decrement}`) +console.log(counter.actions.decrement.type) // -> "counter/decrement" store.dispatch(user.actions.setUserName('eric')) // -> { counter: 12, user: { name: 'eric', age: 22} } diff --git a/packages/toolkit/src/createReducer.ts b/packages/toolkit/src/createReducer.ts index e22b0a007a..62bd869067 100644 --- a/packages/toolkit/src/createReducer.ts +++ b/packages/toolkit/src/createReducer.ts @@ -80,8 +80,6 @@ export type ReducerWithInitialState> = Reducer & { getInitialState: () => S } -let hasWarnedAboutObjectNotation = false - /** * A utility function that allows defining a reducer as a mapping from action * type to *case reducer* functions that handle these action types. The @@ -96,7 +94,7 @@ let hasWarnedAboutObjectNotation = false * convenience and immutability. * * @overloadSummary - * This overload accepts a callback function that receives a `builder` object as its argument. + * This function accepts a callback that receives a `builder` object as its argument. * That builder provides `addCase`, `addMatcher` and `addDefaultCase` functions that may be * called to define what actions this reducer will handle. * @@ -146,11 +144,6 @@ const reducer = createReducer( ``` * @public */ -export function createReducer>( - initialState: S | (() => S), - builderCallback: (builder: ActionReducerMapBuilder) => void -): ReducerWithInitialState - export function createReducer>( initialState: S | (() => S), mapOrBuilderCallback: (builder: ActionReducerMapBuilder) => void diff --git a/packages/toolkit/src/createSlice.ts b/packages/toolkit/src/createSlice.ts index b62c12f777..31bed9afe0 100644 --- a/packages/toolkit/src/createSlice.ts +++ b/packages/toolkit/src/createSlice.ts @@ -17,7 +17,7 @@ import type { ActionReducerMapBuilder } from './mapBuilders' import { executeReducerBuilderCallback } from './mapBuilders' import type { Id, NoInfer, Tail } from './tsHelpers' import { freezeDraftable } from './utils' -import type { CombinedSliceReducer, InjectConfig } from './combineSlices' +import type { InjectConfig } from './combineSlices' let hasWarnedAboutObjectNotation = false @@ -100,7 +100,12 @@ export interface Slice< * Inject slice into provided reducer (return value from `combineSlices`), and return injected slice. */ injectInto( - combinedReducer: CombinedSliceReducer, + injectable: { + inject: ( + slice: { reducerPath: string; reducer: Reducer }, + config?: InjectConfig + ) => void + }, config?: InjectConfig & { reducerPath?: string } ): InjectedSlice } From 4e440387425536f6137bd20a8e229a16c7440ef4 Mon Sep 17 00:00:00 2001 From: Ben Durrant Date: Sat, 29 Apr 2023 01:01:28 +0100 Subject: [PATCH 140/412] finish my sentence --- docs/api/createSlice.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/api/createSlice.mdx b/docs/api/createSlice.mdx index 8d580f2803..3885ec1440 100644 --- a/docs/api/createSlice.mdx +++ b/docs/api/createSlice.mdx @@ -165,7 +165,7 @@ This is used by `combineSlices` and the default generated `slice.selectors`. A set of selectors that receive the slice state as their first parameter, and any other parameters. -Each selector will have a corresponding key in the +Each selector will have a corresponding key in the resulting [`selectors`](#selectors-1) object. ## Return Value From a827637236fd9904e7e586a41deb2cbe79733c67 Mon Sep 17 00:00:00 2001 From: Ben Durrant Date: Sat, 29 Apr 2023 13:24:37 +0100 Subject: [PATCH 141/412] combineSlices outline --- docs/api/combineSlices.mdx | 111 ++++++++++++++++++++++++++ docs/api/configureStore.mdx | 22 +++-- docs/api/createAction.mdx | 2 + docs/api/createSlice.mdx | 2 +- packages/toolkit/src/combineSlices.ts | 9 ++- website/sidebars.json | 3 +- 6 files changed, 136 insertions(+), 13 deletions(-) create mode 100644 docs/api/combineSlices.mdx diff --git a/docs/api/combineSlices.mdx b/docs/api/combineSlices.mdx new file mode 100644 index 0000000000..16054ef70c --- /dev/null +++ b/docs/api/combineSlices.mdx @@ -0,0 +1,111 @@ +--- +id: combineSlices +title: combineSlices +sidebar_label: combineSlices +hide_title: true +--- + +  + +# `combineSlices` + +## Overview + +A function that combines slices into a single reducer, and enables injection of more reducers after initialisation. + +## Parameters + +`combineSlices` accepts a set of [slices](./createSlice.mdx), RTKQ API [instances](/rtk-query/api/created-api/overview.mdx), and/or reducer map objects, and combines them into a single reducer. + +Slices (and API instances) will be mounted at their `reducerPath`, and items from reducer map objects will be mounted under their respective key. + +```ts no-transpile +const rootReducer = combineSlices(counterSlice, baseApi, { + user: userSlice.reducer, + auth: authSlice.reducer, +}) +// is like +const rootReducer = combineReducers({ + [counterSlice.reducerPath]: counterSlice.reducer, + [baseApi.reducerPath]: baseApi.reducer, + user: userSlice.reducer, + auth: authSlice.reducer, +}) +``` + +:::caution + +If multiple slices/map objects have the same reducer path, the reducer provided later in the arguments will override the previous. + +However, typing will not be able to account for this. It's best to ensure that all of your reducers will aim for a unique location. + +::: + +:::warning + +Like [`combineReducers`](https://redux.js.org/api/combinereducers), `combineSlices` requires at least one reducer at initialisation. + +```ts no-transpile +// will throw an error +const rootReducer = combineSlices() +``` + +::: + +## Return Value + +`combineSlices` returns a reducer function, with attached methods. + +```ts +// file: slices/api.ts noEmit +import type { Api } from '@reduxjs/toolkit/query' + +export declare const api: Api<() => any, {}, 'api', never> + +// file: slices/users.ts noEmit +import type { Slice } from '@reduxjs/toolkit' + +export declare const userSlice: Slice + +// file: slices/index.ts +import { combineSlices } from '@reduxjs/toolkit' +import { api } from './api' +import { userSlice } from './users' + +export const rootReducer = combineSlices(api, userSlice) + +// file: store.ts +import { configureStore } from '@reduxjs/toolkit' +import { rootReducer } from './slices' + +export const store = configureStore({ + reducer: rootReducer, +}) +``` + +### `withLazyLoadedSlices` + +### `inject` + +:::note + +A "slice" for `combineSlices` is typically created with [`createSlice`](./createSlice.mdx), but can be any object with `reducerPath` and `reducer` properties. + +```ts no-transpile +const withUserReducer = rootReducer.inject({ + reducerPath: 'user', + reducer: userReducer, +}) +``` + +::: + +### `selector` + +#### `original` + +## Slice integration + +### `injectInto` + +### `selectors` / `getSelectors` diff --git a/docs/api/configureStore.mdx b/docs/api/configureStore.mdx index e3199c6660..3601bd74f8 100644 --- a/docs/api/configureStore.mdx +++ b/docs/api/configureStore.mdx @@ -17,20 +17,22 @@ to the store setup for a better development experience. `configureStore` accepts a single configuration object parameter, with the following options: ```ts no-transpile -type ConfigureEnhancersCallback = ( +type ConfigureEnhancersCallback = ( defaultEnhancers: EnhancerArray<[StoreEnhancer]> ) => StoreEnhancer[] interface ConfigureStoreOptions< S = any, A extends Action = AnyAction, - M extends Middlewares = Middlewares + M extends Middlewares = Middlewares, + E extends Enhancers = Enhancers, + P = S > { /** * A single reducer function that will be used as the root reducer, or an * object of slice reducers that will be passed to `combineReducers()`. */ - reducer: Reducer | ReducersMapObject + reducer: Reducer | ReducersMapObject /** * An array of Redux middleware to install. If not supplied, defaults to @@ -53,7 +55,7 @@ interface ConfigureStoreOptions< * function (either directly or indirectly by passing an object as `reducer`), * this must be an object with the same shape as the reducer map keys. */ - preloadedState?: DeepPartial + preloadedState?: P /** * The store enhancers to apply. See Redux's `createStore()`. @@ -63,12 +65,16 @@ interface ConfigureStoreOptions< * and should return a new array (such as `[applyMiddleware, offline]`). * If you only need to add middleware, you can use the `middleware` parameter instead. */ - enhancers?: StoreEnhancer[] | ConfigureEnhancersCallback + enhancers?: E | ConfigureEnhancersCallback } -function configureStore( - options: ConfigureStoreOptions -): EnhancedStore +function configureStore< + S = any, + A extends Action = AnyAction, + M extends Middlewares = Middlewares, + E extends Enhancers = Enhancers, + P = S +>(options: ConfigureStoreOptions): EnhancedStore ``` ### `reducer` diff --git a/docs/api/createAction.mdx b/docs/api/createAction.mdx index 9eaded6bec..76e117f522 100644 --- a/docs/api/createAction.mdx +++ b/docs/api/createAction.mdx @@ -107,6 +107,8 @@ const counterReducer = createReducer(0, (builder) => { In principle, Redux lets you use any kind of value as an action type. Instead of strings, you could theoretically use numbers, [symbols](https://developer.mozilla.org/en-US/docs/Glossary/Symbol), or anything else ([although it's recommended that the value should at least be serializable](https://redux.js.org/faq/actions#why-should-type-be-a-string-or-at-least-serializable-why-should-my-action-types-be-constants)). + + However, Redux Toolkit rests on the assumption that you use string action types. Specifically, some of its features rely on the fact that with strings, the `toString()` method of an `createAction()` action creator returns the matching action type. This is not the case for non-string action types because `toString()` will return the string-converted type value rather than the type itself. ```js diff --git a/docs/api/createSlice.mdx b/docs/api/createSlice.mdx index 3885ec1440..cabb03277a 100644 --- a/docs/api/createSlice.mdx +++ b/docs/api/createSlice.mdx @@ -197,7 +197,7 @@ The functions passed to the `reducers` parameter can be accessed through the `ca Result's function `getInitialState` provides access to the initial state value given to the slice. If a lazy state initializer was provided, it will be called and a fresh value returned. -`injectInto` creates an instance of the slice that is aware it's been injected - see `combineSlices`. +`injectInto` creates an instance of the slice that is aware it's been injected - see [`combineSlices`](./combineSlices#slice-integration). :::note The result object is conceptually similar to a diff --git a/packages/toolkit/src/combineSlices.ts b/packages/toolkit/src/combineSlices.ts index 2ef5ad7a1e..f713e8d49d 100644 --- a/packages/toolkit/src/combineSlices.ts +++ b/packages/toolkit/src/combineSlices.ts @@ -369,9 +369,12 @@ const original = (state: any) => { return state[ORIGINAL_STATE] } -export function combineSlices>( - ...slices: Slices -): CombinedSliceReducer>> { +export function combineSlices< + Slices extends [ + AnySliceLike | ReducerMap, + ...Array + ] +>(...slices: Slices): CombinedSliceReducer>> { const reducerMap = Object.fromEntries(getReducers(slices)) const getReducer = () => combineReducers(reducerMap) diff --git a/website/sidebars.json b/website/sidebars.json index b469f3c82f..4c1778aaa0 100644 --- a/website/sidebars.json +++ b/website/sidebars.json @@ -54,7 +54,8 @@ "api/createAction", "api/createSlice", "api/createAsyncThunk", - "api/createEntityAdapter" + "api/createEntityAdapter", + "api/combineSlices" ] }, { From c0f2fea02cdb720b817fd48928c34f78a3b3fb8b Mon Sep 17 00:00:00 2001 From: Ben Durrant Date: Sun, 30 Apr 2023 00:18:04 +0100 Subject: [PATCH 142/412] lazyLoadedSlices --- docs/api/combineSlices.mdx | 147 +++++++++++++++++++++++----- packages/toolkit/src/createSlice.ts | 6 +- 2 files changed, 126 insertions(+), 27 deletions(-) diff --git a/docs/api/combineSlices.mdx b/docs/api/combineSlices.mdx index 16054ef70c..5f9269c9c6 100644 --- a/docs/api/combineSlices.mdx +++ b/docs/api/combineSlices.mdx @@ -13,11 +13,56 @@ hide_title: true A function that combines slices into a single reducer, and enables injection of more reducers after initialisation. +```ts +// file: slices/api.ts noEmit +import type { Api } from '@reduxjs/toolkit/query' + +export declare const api: Api<() => any, {}, 'api', never> + +// file: slices/users.ts noEmit +import type { Slice } from '@reduxjs/toolkit' + +export declare const userSlice: Slice + +// file: slices/index.ts +import { combineSlices } from '@reduxjs/toolkit' +import { api } from './api' +import { userSlice } from './users' + +export const rootReducer = combineSlices(api, userSlice) + +// file: store.ts +import { configureStore } from '@reduxjs/toolkit' +import { rootReducer } from './slices' + +export const store = configureStore({ + reducer: rootReducer, +}) +``` + +:::note + +A "slice" for `combineSlices` is typically created with [`createSlice`](./createSlice.mdx), +but can be any "slice-like" object with `reducerPath` and `reducer` properties (meaning RTK Query [API instances](/rtk-query/api/created-api/overview.mdx) are also compatible). + +```ts no-transpile +const withUserReducer = rootReducer.inject({ + reducerPath: 'user', + reducer: userReducer, +}) + +const withApiReducer = rootReducer.inject(fooApi) +``` + +For simplicity, this `{ reducerPath, reducer }` shape will be described in these docs as a "slice". + +::: + ## Parameters -`combineSlices` accepts a set of [slices](./createSlice.mdx), RTKQ API [instances](/rtk-query/api/created-api/overview.mdx), and/or reducer map objects, and combines them into a single reducer. +`combineSlices` accepts a set of slices and/or reducer map objects, and combines them into a single reducer. -Slices (and API instances) will be mounted at their `reducerPath`, and items from reducer map objects will be mounted under their respective key. +Slices will be mounted at their `reducerPath`, and items from reducer map objects will be mounted under their respective key. ```ts no-transpile const rootReducer = combineSlices(counterSlice, baseApi, { @@ -56,50 +101,100 @@ const rootReducer = combineSlices() `combineSlices` returns a reducer function, with attached methods. -```ts -// file: slices/api.ts noEmit -import type { Api } from '@reduxjs/toolkit/query' +```ts no-transpile +interface CombinedSliceReducer + extends Reducer> { + withLazyLoadedSlices(): CombinedSliceReducer< + InitialState, + DeclaredState & Partial + > + inject( + slice: Slice, + config?: InjectConfig + ): CombinedSliceReducer> + selector: { + (selectorFn: Selector, selectState?: SelectFromRootState) => WrappedSelector + original(state: DeclaredState) => InitialState & Partial + } +} +``` -export declare const api: Api<() => any, {}, 'api', never> +### `withLazyLoadedSlices` -// file: slices/users.ts noEmit -import type { Slice } from '@reduxjs/toolkit' +It's recommended to [infer your RootState type from your store](https://redux.js.org/usage/usage-with-typescript#define-root-state-and-dispatch-types), which is inferred from the reducer. However, this can present issues if slices are lazy loaded, and thus not able to be inferred from. -export declare const userSlice: Slice +`withLazyLoadedSlices` allows you to declare slices that will be added to state later, which will be included in the final state type. -// file: slices/index.ts +One possible pattern of managing this would be with declaration merging: + +```ts no-transpile +// slices/index.ts import { combineSlices } from '@reduxjs/toolkit' -import { api } from './api' -import { userSlice } from './users' +import { staticSlice } from './static' -export const rootReducer = combineSlices(api, userSlice) +export interface LazyLoadedSlices {} -// file: store.ts -import { configureStore } from '@reduxjs/toolkit' -import { rootReducer } from './slices' +export const rootReducer = + combineSlices(staticSlice).withLazyLoadedSlices() -export const store = configureStore({ - reducer: rootReducer, +// keys in LazyLoadedSlices are marked as optional +export type RootState = ReturnType + +// slices/lazySlice.ts +import type { WithSlice } from '@reduxjs/toolkit' +import { rootReducer } from '.' + +const lazySlice = createSlice({ + /* ... */ }) -``` -### `withLazyLoadedSlices` +declare module '.' { + export interface LazyLoadedSlices extends WithSlice {} +} -### `inject` +const injectedReducer = rootReducer.inject(lazySlice) -:::note +// and/or -A "slice" for `combineSlices` is typically created with [`createSlice`](./createSlice.mdx), but can be any object with `reducerPath` and `reducer` properties. +const injectedSlice = lazySlice.injectInto(rootReducer) +``` + +:::tip + +The above example uses the `WithSlice` utility type for a slice mounted under its `reducerPath`. If the slice is mounted under a different key, you can declare it as a regular key instead. ```ts no-transpile -const withUserReducer = rootReducer.inject({ - reducerPath: 'user', - reducer: userReducer, +// slices/lazySlice.ts +import { rootReducer } from '.' + +const lazySlice = createSlice({ + /* ... */ +}) + +declare module '.' { + export interface LazyLoadedSlices { + customKey: LazyState + } +} + +const injectedReducer = rootReducer.inject({ + reducerPath: 'customKey', + reducer: lazySlice.reducer, +}) + +// and/or + +const injectedSlice = lazySlice.injectInto(rootReducer, { + reducerPath: 'customKey', }) ``` ::: +### `inject` + +#### Configuration + ### `selector` #### `original` diff --git a/packages/toolkit/src/createSlice.ts b/packages/toolkit/src/createSlice.ts index 31bed9afe0..3258b90009 100644 --- a/packages/toolkit/src/createSlice.ts +++ b/packages/toolkit/src/createSlice.ts @@ -30,6 +30,10 @@ let hasWarnedAboutObjectNotation = false */ export type SliceActionCreator

= PayloadActionCreator

+interface InjectIntoConfig extends InjectConfig { + reducerPath?: string +} + /** * The return value of `createSlice` * @@ -106,7 +110,7 @@ export interface Slice< config?: InjectConfig ) => void }, - config?: InjectConfig & { reducerPath?: string } + config?: InjectIntoConfig ): InjectedSlice } From 8c04d875350557999ad283aa38746f59969e34fd Mon Sep 17 00:00:00 2001 From: Ben Durrant Date: Sun, 30 Apr 2023 00:20:46 +0100 Subject: [PATCH 143/412] formatting --- docs/api/combineSlices.mdx | 25 +++++++++++++------------ 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/docs/api/combineSlices.mdx b/docs/api/combineSlices.mdx index 5f9269c9c6..2939108713 100644 --- a/docs/api/combineSlices.mdx +++ b/docs/api/combineSlices.mdx @@ -127,8 +127,8 @@ It's recommended to [infer your RootState type from your store](https://redux.js One possible pattern of managing this would be with declaration merging: -```ts no-transpile -// slices/index.ts +```ts no-transpile title="Using declaration merging to declare injected slices" +// file: slices/index.ts import { combineSlices } from '@reduxjs/toolkit' import { staticSlice } from './static' @@ -140,7 +140,7 @@ export const rootReducer = // keys in LazyLoadedSlices are marked as optional export type RootState = ReturnType -// slices/lazySlice.ts +// file: slices/lazySlice.ts import type { WithSlice } from '@reduxjs/toolkit' import { rootReducer } from '.' @@ -163,30 +163,31 @@ const injectedSlice = lazySlice.injectInto(rootReducer) The above example uses the `WithSlice` utility type for a slice mounted under its `reducerPath`. If the slice is mounted under a different key, you can declare it as a regular key instead. -```ts no-transpile -// slices/lazySlice.ts +```ts no-transpile title="Declaring a slice mounted outside its reducerPath" +// file: slices/lazySlice.ts import { rootReducer } from '.' const lazySlice = createSlice({ - /* ... */ +/_ ... _/ }) declare module '.' { - export interface LazyLoadedSlices { - customKey: LazyState - } +export interface LazyLoadedSlices { +customKey: LazyState +} } const injectedReducer = rootReducer.inject({ - reducerPath: 'customKey', - reducer: lazySlice.reducer, +reducerPath: 'customKey', +reducer: lazySlice.reducer, }) // and/or const injectedSlice = lazySlice.injectInto(rootReducer, { - reducerPath: 'customKey', +reducerPath: 'customKey', }) + ``` ::: From 94effc22b4cf2fdeaf39d6067567ee7f20997459 Mon Sep 17 00:00:00 2001 From: Ben Durrant Date: Sun, 30 Apr 2023 00:21:19 +0100 Subject: [PATCH 144/412] ...formatting --- docs/api/combineSlices.mdx | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/docs/api/combineSlices.mdx b/docs/api/combineSlices.mdx index 2939108713..fa233764b4 100644 --- a/docs/api/combineSlices.mdx +++ b/docs/api/combineSlices.mdx @@ -168,26 +168,25 @@ The above example uses the `WithSlice` utility type for a slice mounted under it import { rootReducer } from '.' const lazySlice = createSlice({ -/_ ... _/ + /* ... */ }) declare module '.' { -export interface LazyLoadedSlices { -customKey: LazyState -} + export interface LazyLoadedSlices { + customKey: LazyState + } } const injectedReducer = rootReducer.inject({ -reducerPath: 'customKey', -reducer: lazySlice.reducer, + reducerPath: 'customKey', + reducer: lazySlice.reducer, }) // and/or const injectedSlice = lazySlice.injectInto(rootReducer, { -reducerPath: 'customKey', + reducerPath: 'customKey', }) - ``` ::: From f8681d6463b7cee391fa657469b00cd3cf67dd2d Mon Sep 17 00:00:00 2001 From: Ben Durrant Date: Sun, 30 Apr 2023 21:42:18 +0100 Subject: [PATCH 145/412] inject --- docs/api/combineSlices.mdx | 54 ++++++++++++++++++- .../api/created-api/code-splitting.mdx | 2 +- 2 files changed, 54 insertions(+), 2 deletions(-) diff --git a/docs/api/combineSlices.mdx b/docs/api/combineSlices.mdx index fa233764b4..986863550b 100644 --- a/docs/api/combineSlices.mdx +++ b/docs/api/combineSlices.mdx @@ -193,7 +193,59 @@ const injectedSlice = lazySlice.injectInto(rootReducer, { ### `inject` -#### Configuration +`inject` allows you to add a slice to your set of reducers after initialisation. +It expects to be passed a slice and an optional config, and returns an updated version of the reducer with the slice included. + +This is mainly useful for lazy loading reducers. + +```ts no-transpile +const reducerWithUser = rootReducer.inject(userSlice) +``` + +:::note + +`inject` adds the slice to the map of reducers in your original reducer, but doesn't dispatch an action. + +This means that the added reducer state will not show up in your store until the next action is dispatched. + +::: + +#### Reducer replacement + +By default, replacing a reducer is not allowed. +In development mode, a warning will be logged to console if a new reducer instance is attempted to inject into a `reducerPath` that's already injected. (It won't warn if the same reducer instance is injected into the same place twice.) + +If you wish to allow replacing a reducer with a new instance, you must explicitly pass `overrideExisting: true` as part of your configuration object. + +```ts no-transpile +const reducerWithUser = rootReducer.inject(userSlice, { + overrideExisting: true, +}) +``` + +This may be useful for hot reload, or "removing" a reducer by replacing it with a function that always returns null. +Note that for predictable behaviour, your types should account for all of the possible reducers you intend to occupy a path. + +```ts no-transpile +declare module '.' { + export interface LazyLoadedSlices { + removable: RemovableState | null + } +} + +const withInjected = rootReducer.inject( + { reducerPath: 'removable', reducer: removableReducer }, + { overrideExisting: true } +) + +const emptyReducer = () => null + +const removeReducer = () => + rootReducer.inject( + { reducerPath: 'removable', reducer: emptyReducer }, + { overrideExisting: true } + ) +``` ### `selector` diff --git a/docs/rtk-query/api/created-api/code-splitting.mdx b/docs/rtk-query/api/created-api/code-splitting.mdx index 9d75162ed7..31d76e3551 100644 --- a/docs/rtk-query/api/created-api/code-splitting.mdx +++ b/docs/rtk-query/api/created-api/code-splitting.mdx @@ -35,7 +35,7 @@ Accepts an options object containing the same `endpoints` builder callback you w Returns an updated and enhanced version of the API slice object, containing the combined endpoint definitions. -In development, endpoints will not be overridden unless `overrideExisting` is set to `true`. If not, a warning will be shown to notify you if there is a name clash between endpoint definitions. +Endpoints will not be overridden unless `overrideExisting` is set to `true`. If not, a development mode warning will be shown to notify you if there is a name clash between endpoint definitions. This method is primarily useful for code splitting and hot reloading. From 399780979b1addfd569211aa8d40d15147b65af7 Mon Sep 17 00:00:00 2001 From: Ben Durrant Date: Sun, 30 Apr 2023 22:33:11 +0100 Subject: [PATCH 146/412] selector --- docs/api/combineSlices.mdx | 88 +++++++++++++++++++++++++++++++++++++- 1 file changed, 86 insertions(+), 2 deletions(-) diff --git a/docs/api/combineSlices.mdx b/docs/api/combineSlices.mdx index 986863550b..7b97d037ad 100644 --- a/docs/api/combineSlices.mdx +++ b/docs/api/combineSlices.mdx @@ -223,10 +223,10 @@ const reducerWithUser = rootReducer.inject(userSlice, { }) ``` -This may be useful for hot reload, or "removing" a reducer by replacing it with a function that always returns null. +This may be useful for hot reload, or "removing" a reducer by replacing it with a function that always returns `null`. Note that for predictable behaviour, your types should account for all of the possible reducers you intend to occupy a path. -```ts no-transpile +```ts no-transpile title="'Removing' a reducer, by replacing it with a no-op function" declare module '.' { export interface LazyLoadedSlices { removable: RemovableState | null @@ -249,8 +249,92 @@ const removeReducer = () => ### `selector` +As noted previously, an injected reducer can still be undefined in state if no action has been dispatched. + +Dealing with this possibly-optional state can be inconvient when writing selectors, as you may end up with a lot of results being possibly undefined or relying on explicit defaults. + +`selector` allows you to get around this, by wrapping the reducer state in a `Proxy` that ensures that any currently injected reducers evaluate to their initial state if they're currently `undefined` in state. + +```ts no-transpile +declare module '.' { + export interface LazyLoadedSlices extends WithSlice {} +} + +const counterSlice = createSlice({ + name: 'counter', + initialState: { value: 0 }, + reducers: { + /* ... */ + }, +}) + +const withCounter = rootReducer.inject(counterSlice) + +const selectCounterValue = (rootState: RootState) => rootState.counter?.value // number | undefined + +const wrappedSelectCounterValue = withCounter.selector( + (rootState) => rootState.counter.value // number +) + +console.log( + selectCounterValue({}), // undefined + selectCounterValue({ counter: { value: 2 } }), // 2 + wrappedSelectCounterValue({}), // 0 + wrappedSelectCounterValue({ counter: { value: 2 } }) // 2 +) +``` + +:::caution + +The `Proxy` retrieves a reducer's initial state by calling it with a randomly generated action type - don't try to handle this as a special case inside your reducer. + +::: + +#### Nested combined reducer + +The wrapped selector expects to use the state returned by the combined reducer as its first argument. + +If the combined reducer is nested further inside the store state, pass a `selectState` callback as the second argument to `selector`: + +```ts no-transpile +interface RootState { + innerCombined: ReturnType +} + +const selectCounterValue = withCounter.selector( + (combinedState) => combinedState.counter.value, + (rootState: RootState) => rootState.innerCombined +) + +console.log( + selectCounterValue({ + innerCombined: {}, + }), // 0 + selectCounterValue({ + innerCombined: { + counter: { + value: 2, + }, + }, + }) // 2 +) +``` + #### `original` +Similar to [Immer usage](/usage/immer-reducers.md#debugging-and-inspecting-drafted-state), an `original` function is provided to retrieve the original state value provided to the `Proxy`. + +This is mainly useful for debugging/inspecting, as `Proxy` instances tend to be displayed in a format that's hard to read. + +The function is attached as a method on the `selector` function: + +```ts no-transpile +const wrappedSelectCounterValue = withCounter.selector((rootState) => { + console.log(withCounter.selector.original(rootState)) + return rootState.counter.value +}) +``` + ## Slice integration ### `injectInto` From b06a6e28c569f6d02754b05b013415936702a6eb Mon Sep 17 00:00:00 2001 From: Ben Durrant Date: Sun, 30 Apr 2023 23:00:52 +0100 Subject: [PATCH 147/412] injectInto and selectors. Also, ensure reducerPath change is reflected. --- docs/api/combineSlices.mdx | 28 +++++++++++++++++++ packages/toolkit/src/createSlice.ts | 22 +++++++-------- .../toolkit/src/tests/createSlice.test.ts | 14 +++++++--- 3 files changed, 49 insertions(+), 15 deletions(-) diff --git a/docs/api/combineSlices.mdx b/docs/api/combineSlices.mdx index 7b97d037ad..b308a0e36f 100644 --- a/docs/api/combineSlices.mdx +++ b/docs/api/combineSlices.mdx @@ -339,4 +339,32 @@ const wrappedSelectCounterValue = withCounter.selector((rootState) => { ### `injectInto` +Slice instances returned by [`createSlice`](./createSlice) have an attached `injectInto` method, which receive an injectable reducer from `combineSlices` and returns an "injected" version of that slice. + +```ts no-transpile +const injectedCounterSlice = counterSlice.injectInto(rootReducer) +``` + +An optional configuration object can be passed. This follows [`inject`](#inject)'s options with an additional `reducerPath` field, for injecting the slice under a path other than its current `reducerPath` property. + +```ts no-transpile +const aCounterSlice = counterSlice.injectInto(rootReducer, { + reducerPath: 'aCounter', +}) +``` + ### `selectors` / `getSelectors` + +Similar to [`selector`](#selector), the selectors from an "injected" slice instance behave slightly differently. + +If the slice state is undefined in the store state passed, the selector will instead be called with the slice's initial state. + +`selectors` will also reflect the change in `reducerPath` if one was made during injection. + +```ts no-transpile +console.log( + injectedCounterSlice.selectors.selectValue({}), // 0 + injectedCounterSlice.selectors.selectValue({ counter: { value: 2 } }), // 2 + aCounterSlice.selectors.selectValue({ aCounter: { value: 2 } }) // 2 +) +``` diff --git a/packages/toolkit/src/createSlice.ts b/packages/toolkit/src/createSlice.ts index 3258b90009..1aee766752 100644 --- a/packages/toolkit/src/createSlice.ts +++ b/packages/toolkit/src/createSlice.ts @@ -30,8 +30,8 @@ let hasWarnedAboutObjectNotation = false */ export type SliceActionCreator

= PayloadActionCreator

-interface InjectIntoConfig extends InjectConfig { - reducerPath?: string +interface InjectIntoConfig extends InjectConfig { + reducerPath?: NewReducerPath } /** @@ -103,15 +103,15 @@ export interface Slice< /** * Inject slice into provided reducer (return value from `combineSlices`), and return injected slice. */ - injectInto( + injectInto( injectable: { inject: ( slice: { reducerPath: string; reducer: Reducer }, config?: InjectConfig ) => void }, - config?: InjectIntoConfig - ): InjectedSlice + config?: InjectIntoConfig + ): InjectedSlice } /** @@ -542,15 +542,15 @@ export function createSlice< get selectors() { return this.getSelectors(defaultSelectSlice) }, - injectInto(injectable, { reducerPath, ...config } = {}) { - injectable.inject( - { reducerPath: reducerPath ?? this.reducerPath, reducer: this.reducer }, - config - ) + injectInto(injectable, { reducerPath: pathOpt, ...config } = {}) { + const reducerPath = pathOpt ?? this.reducerPath + injectable.inject({ reducerPath, reducer: this.reducer }, config) + const selectSlice = (state: any) => state[reducerPath] return { ...this, + reducerPath, get selectors() { - return this.getSelectors(defaultSelectSlice) + return this.getSelectors(selectSlice) }, } as any }, diff --git a/packages/toolkit/src/tests/createSlice.test.ts b/packages/toolkit/src/tests/createSlice.test.ts index a92893180b..60a975f4e0 100644 --- a/packages/toolkit/src/tests/createSlice.test.ts +++ b/packages/toolkit/src/tests/createSlice.test.ts @@ -540,17 +540,23 @@ describe('createSlice', () => { expect(uninjectedState.injected).toBe(undefined) - slice.injectInto(combinedReducer) + const injected = slice.injectInto(combinedReducer) const injectedState = combinedReducer(undefined, increment()) - expect(injectedState.injected).toBe(slice.getInitialState() + 1) + expect(injected.selectors.selectSlice(injectedState)).toBe( + slice.getInitialState() + 1 + ) - slice.injectInto(combinedReducer, { reducerPath: 'injected2' }) + const injected2 = slice.injectInto(combinedReducer, { + reducerPath: 'injected2', + }) const injected2State = combinedReducer(undefined, increment()) - expect(injected2State.injected2).toBe(slice.getInitialState() + 1) + expect(injected2.selectors.selectSlice(injected2State)).toBe( + slice.getInitialState() + 1 + ) }) }) }) From d933f6552dab5782572336afe56165eae6b67c09 Mon Sep 17 00:00:00 2001 From: Ben Durrant Date: Tue, 2 May 2023 01:13:26 +0100 Subject: [PATCH 148/412] docs --- docs/api/createDynamicMiddleware.mdx | 181 ++++++++++++++++++ .../toolkit/src/dynamicMiddleware/index.ts | 14 +- website/sidebars.json | 1 + 3 files changed, 187 insertions(+), 9 deletions(-) create mode 100644 docs/api/createDynamicMiddleware.mdx diff --git a/docs/api/createDynamicMiddleware.mdx b/docs/api/createDynamicMiddleware.mdx new file mode 100644 index 0000000000..5c86b80077 --- /dev/null +++ b/docs/api/createDynamicMiddleware.mdx @@ -0,0 +1,181 @@ +--- +id: createDynamicMiddleware +title: createDynamicMiddleware +sidebar_label: createDynamicMiddleware +hide_title: true +--- + +  + +# `createDynamicMiddleware` + +## Overview + +A "meta-middleware" that allows adding middleware to the dispatch chain after store initialisation. + +## Instance Creation + +```ts no-transpile +import { createDynamicMiddleware, configureStore } from '@reduxjs/toolkit' + +const dynamicMiddleware = createDynamicMiddleware() + +const store = configureStore({ + reducer: { + todos: todosReducer, + }, + middleware: (getDefaultMiddleware) => + getDefaultMiddleware().prepend(dynamicMiddleware.middleware), +}) +``` + +:::tip + +It's possible to pass two type parameters to `createDynamicMiddleware`, `State` and `Dispatch`. + +These are used by methods that receive middleware to ensure that the provided middleware are compatible with the types provided. + +```ts no-transpile +const dynamicMiddleware = createDynamicMiddleware() +``` + +However, if these values are derived from the store (as they should be), a circular type dependency is formed. + +As a result, it's better to use the `withTypes` helper attached to `addMiddleware`, `withMiddleware` and `createDispatchWithMiddlewareHook`. + +```ts no-transpile +import { createDynamicMiddleware } from '@reduxjs/toolkit/react' +import type { RootState, AppDispatch } from './store' + +const dynamicMiddleware = createDynamicMiddleware() + +const { + middleware, + addMiddleware, + withMiddleware, + createDispatchWithMiddlewareHook, +} = dynamicMiddleware + +interface MiddlewareApiConfig { + state: RootState + dispatch: AppDispatch +} + +export const addAppMiddleware = addMiddleware.withTypes() + +export const withAppMiddleware = withMiddleware.withTypes() + +export const createAppDispatchWithMiddlewareHook = + createDispatchWithMiddlewareHook.withTypes() + +export default middleware +``` + +::: + +## Dynamic Middleware Instance + +The "dynamic middleware instance" returned from `createDynamicMiddleware` is an object similar to the object generated by `createListenerMiddleware`. The instance object is _not_ the actual Redux middleware itself. Rather, it contains the middleware and some instance methods used to add middleware to the chain. + +```ts no-transpile +export type DynamicMiddlewareInstance< + State = unknown, + Dispatch extends ReduxDispatch = ReduxDispatch +> = { + middleware: DynamicMiddleware + addMiddleware: AddMiddleware + withMiddleware: WithMiddleware +} +``` + +### `middleware` + +The wrapper middleware instance, to add to the Redux store. + +You can place this anywhere in the middleware chain, but note that all the middleware you inject into this instance will be contained within this position. + +### `addMiddleware` + +Injects a set of middleware into the instance. + +```ts no-transpile +addMiddleware(logger, listenerMiddleware.instance) +``` + +:::note + +- Middleware are compared by function reference, and each is only added to the chain once. + +- Middleware are stored in an ES6 map, and are thus called in insertion order during dispatch. + +::: + +### `withMiddleware` + +Accepts a set of middleware, and creates an action. When dispatched, it injects the middleware and returns a version of `dispatch` typed to be aware of any extensions added. + +```ts no-transpile +const listenerDispatch = store.dispatch( + withMiddleware(listenerMiddleware.middleware) +) + +const unsubscribe = listenerDispatch(addListener({ type, effect })) +``` + +## React Integration + +When imported from the React-specific entry point (`@reduxjs/toolkit/react`), the result of calling `createDynamicMiddleware` will have extra methods attached. + +_These depend on having `react-redux` installed._ + +```ts no-transpile +interface ReactDynamicMiddlewareInstance< + State = any, + Dispatch extends ReduxDispatch = ReduxDispatch +> extends DynamicMiddlewareInstance { + createDispatchWithMiddlewareHook: CreateDispatchWithMiddlewareHook< + State, + Dispatch + > + createDispatchWithMiddlewareHookFactory: ( + context?: Context< + ReactReduxContextValue> + > + ) => CreateDispatchWithMiddlewareHook +} +``` + +### `createDispatchWithMiddlewareHook` + +Accepts a set of middleware, and returns a [`useDispatch`](https://react-redux.js.org/api/hooks#usedispatch) hook returning a `dispatch` typed to include extensions from provided middleware. + +```ts no-transpile +const useListenerDispatch = createDispatchWithMiddlewareHook( + listenerMiddleware.instance +) + +const Component = () => { + const listenerDispatch = useListenerDispatch() + useEffect(() => { + const unsubscribe = listenerDispatch(addListener({ type, effect })) + return () => unsubscribe() + }, [dispatch]) +} +``` + +:::caution + +Middleware is injected when `createDispatchWithMiddlewareHook` is called, not when the `useDispatch` hook is used. + +::: + +### `createDispatchWithMiddlewareHookFactory` + +Accepts a React context instance, and returns a `createDispatchWithMiddlewareHook` built to use that context. + +```ts no-transpile +const createDispatchWithMiddlewareHook = + createDispatchWithMiddlewareHookFactory(context) +``` + +Useful if you're using a [custom context](https://react-redux.js.org/using-react-redux/accessing-store#providing-custom-context) for React Redux. diff --git a/packages/toolkit/src/dynamicMiddleware/index.ts b/packages/toolkit/src/dynamicMiddleware/index.ts index 662f632142..b1c7dc87f6 100644 --- a/packages/toolkit/src/dynamicMiddleware/index.ts +++ b/packages/toolkit/src/dynamicMiddleware/index.ts @@ -21,15 +21,11 @@ const createMiddlewareEntry = < Dispatch extends ReduxDispatch = ReduxDispatch >( middleware: Middleware -) => { - const id = nanoid() - const entry: MiddlewareEntry = { - id, - middleware, - applied: new Map(), - } - return entry -} +): MiddlewareEntry => ({ + id: nanoid(), + middleware, + applied: new Map(), +}) export const createDynamicMiddleware = < State = any, diff --git a/website/sidebars.json b/website/sidebars.json index b469f3c82f..fe696772d4 100644 --- a/website/sidebars.json +++ b/website/sidebars.json @@ -42,6 +42,7 @@ "api/immutabilityMiddleware", "api/serializabilityMiddleware", "api/createListenerMiddleware", + "api/createDynamicMiddleware", "api/autoBatchEnhancer" ] }, From b972fcd7d4c37822db22f5c3eb421d5f0894b690 Mon Sep 17 00:00:00 2001 From: Ben Durrant Date: Fri, 5 May 2023 02:17:55 +0100 Subject: [PATCH 149/412] draft docs --- docs/api/createSlice.mdx | 190 ++++++++++++++++++++++++ docs/api/getDefaultMiddleware.mdx | 9 +- docs/rtk-query/usage/error-handling.mdx | 2 +- 3 files changed, 192 insertions(+), 9 deletions(-) diff --git a/docs/api/createSlice.mdx b/docs/api/createSlice.mdx index 39f10f8b70..04a4135de7 100644 --- a/docs/api/createSlice.mdx +++ b/docs/api/createSlice.mdx @@ -130,6 +130,196 @@ const todosSlice = createSlice({ }) ``` +### The `reducers` "creator callback" notation + +Alternatively, the `reducers` field can be a callback which receives a "create" object. + +The main benefit of this is that you can create [async thunks](./createAsyncThunk) as part of your slice. Types are also slightly simplified for prepared reducers. + +```ts title="Creator callback for reducers" +import { createSlice, nanoid } from '@reduxjs/toolkit' +import type { PayloadAction } from '@reduxjs/toolkit' + +interface Item { + id: string + text: string +} + +interface TodoState { + loading: boolean + todos: Item[] +} + +const todosSlice = createSlice({ + name: 'todos', + initialState: { + loading: false, + todos: [], + } as TodoState, + reducers: (create) => ({ + deleteTodo: create.reducer((state, action: PayloadAction) => { + state.todos.splice(action.payload, 1) + }), + addTodo: create.preparedReducer( + (text: string) => { + const id = nanoid() + return { payload: { id, text } } + }, + // action type is inferred from prepare callback + (state, action) => { + state.todos.push(action.payload) + } + ), + fetchTodo: create.asyncThunk( + async (id: string, thunkApi) => { + const res = await fetch(`myApi/todos?id=${id}`) + return (await res.json()) as Item + }, + { + pending: (state) => { + state.loading = true + }, + rejected: (state, action) => { + state.loading = false + }, + fulfilled: (state, action) => { + state.loading = false + state.todos.push(action.payload) + }, + } + ), + }), +}) + +export const { addTodo, deleteTodo, fetchTodo } = todosSlice.actions +``` + +#### Create Methods + +#### `create.reducer` + +A standard slice case reducer. + +**Parameters** + +- **reducer** The slice case reducer to use. + +```ts no-transpile +create.reducer((state, action: PayloadAction) => { + state.todos.push(action.payload) +}) +``` + +#### `create.preparedReducer` + +A [prepared](#customizing-generated-action-creators) reducer, to customize the action creator. + +**Parameters** + +- **prepareAction** The [`prepare callback`](./createAction#using-prepare-callbacks-to-customize-action-contents). +- **reducer** The slice case reducer to use. + +The action passed to the case reducer will be inferred from the prepare callback's return. + +```ts no-transpile +create.preparedReducer( + (text: string) => { + const id = nanoid() + return { payload: { id, text } } + }, + (state, action) => { + state.todos.push(action.payload) + } +) +``` + +#### `create.asyncThunk` + +Creates an async thunk instead of an action creator. + +**Parameters** + +- **payloadCreator** The thunk [payload creator](./createAsyncThunk#payloadcreator). +- **config** The configuration object. (optional) + +The configuration object can contain case reducers for each of the [lifecycle actions](./createAsyncThunk#promise-lifecycle-actions) (`pending`, `fulfilled`, and `rejected`). + +Each case reducer will be attached to the slice's `caseReducers` object, e.g. `slice.caseReducers.fetchTodo.fulfilled`. + +The configuration object can also contain [`options`](./createAsyncThunk#options). + +```ts no-transpile +create.asyncThunk( + async (id: string, thunkApi) => { + const res = await fetch(`myApi/todos?id=${id}`) + return (await res.json()) as Item + }, + { + pending: (state) => { + state.loading = true + }, + rejected: (state, action) => { + state.loading = false + }, + fulfilled: (state, action) => { + state.loading = false + state.todos.push(action.payload) + }, + options: { + idGenerator: uuid, + }, + } +) +``` + +:::note + +Typing for the `create.asyncThunk` works in the same way as [`createAsyncThunk`](usage/usage-with-typescript#createasyncthunk), with one key difference. + +A type for `state` and/or `dispatch` _cannot_ be provided as part of the `ThunkApiConfig`, as this would cause circular types. + +Instead, it is necessary to assert the type when needed. + +```ts no-transpile +create.asyncThunk( + async (id, thunkApi) => { + const state = thunkApi.getState() as RootState + const dispatch = thunkApi.dispatch as AppDispatch + throw thunkApi.rejectWithValue({ + error: 'Oh no!', + }) + } +) +``` + +For common thunk API configuration options, a [`withTypes` helper](usage/usage-with-typescript#defining-a-pre-typed-createasyncthunk) is provided: + +```ts no-transpile +reducers: (create) => { + const createAThunk = + create.asyncThunk.withTypes<{ rejectValue: { error: string } }>() + + return { + fetchTodo: createAThunk(async (id, thunkApi) => { + const state = thunkApi.getState() as RootState + const dispatch = thunkApi.dispatch as AppDispatch + throw thunkApi.rejectWithValue({ + error: 'Oh no!', + }) + }), + fetchTodos: createAThunk(async (id, thunkApi) => { + const state = thunkApi.getState() as RootState + const dispatch = thunkApi.dispatch as AppDispatch + throw thunkApi.rejectWithValue({ + error: 'Oh no, not again!', + }) + }), + } +} +``` + +::: + ### `extraReducers` One of the key concepts of Redux is that each slice reducer "owns" its slice of state, and that many slice reducers diff --git a/docs/api/getDefaultMiddleware.mdx b/docs/api/getDefaultMiddleware.mdx index a574d445f2..2b6948ba01 100644 --- a/docs/api/getDefaultMiddleware.mdx +++ b/docs/api/getDefaultMiddleware.mdx @@ -40,14 +40,7 @@ to the store. `configureStore` will not add any extra middleware beyond what you `getDefaultMiddleware` is useful if you want to add some custom middleware, but also still want to have the default middleware added as well: -```ts -// file: reducer.ts noEmit - -export default function rootReducer(state = {}, action: any) { - return state -} - -// file: store.ts +```ts no-transpile import { configureStore } from '@reduxjs/toolkit' import logger from 'redux-logger' diff --git a/docs/rtk-query/usage/error-handling.mdx b/docs/rtk-query/usage/error-handling.mdx index fcd1dd9280..e1bb8ffa0e 100644 --- a/docs/rtk-query/usage/error-handling.mdx +++ b/docs/rtk-query/usage/error-handling.mdx @@ -80,7 +80,7 @@ Redux Toolkit has [action matching utilities](../../api/matching-utilities.mdx#m ::: -```ts title="Error catching middleware example" +```ts no-transpile title="Error catching middleware example" import { isRejectedWithValue } from '@reduxjs/toolkit' import type { MiddlewareAPI, Middleware } from '@reduxjs/toolkit' import { toast } from 'your-cool-library' From 2bfe54b545c5f8cd9ae3ede270bc42fcfaac1e75 Mon Sep 17 00:00:00 2001 From: Ben Durrant Date: Fri, 5 May 2023 02:21:05 +0100 Subject: [PATCH 150/412] rm duplicate example --- docs/api/createSlice.mdx | 4 ---- 1 file changed, 4 deletions(-) diff --git a/docs/api/createSlice.mdx b/docs/api/createSlice.mdx index 04a4135de7..2c3d0c0cee 100644 --- a/docs/api/createSlice.mdx +++ b/docs/api/createSlice.mdx @@ -301,15 +301,11 @@ reducers: (create) => { return { fetchTodo: createAThunk(async (id, thunkApi) => { - const state = thunkApi.getState() as RootState - const dispatch = thunkApi.dispatch as AppDispatch throw thunkApi.rejectWithValue({ error: 'Oh no!', }) }), fetchTodos: createAThunk(async (id, thunkApi) => { - const state = thunkApi.getState() as RootState - const dispatch = thunkApi.dispatch as AppDispatch throw thunkApi.rejectWithValue({ error: 'Oh no, not again!', }) From 2eb1513aaf9468245766ece6323cecf5229528ed Mon Sep 17 00:00:00 2001 From: "ben.durrant" Date: Fri, 5 May 2023 12:02:32 +0100 Subject: [PATCH 151/412] export ReducerCreators, to allow wrapping callback form --- packages/toolkit/src/createSlice.ts | 2 +- packages/toolkit/src/index.ts | 1 + .../toolkit/src/tests/createSlice.typetest.ts | 60 +++++++++++++++++++ 3 files changed, 62 insertions(+), 1 deletion(-) diff --git a/packages/toolkit/src/createSlice.ts b/packages/toolkit/src/createSlice.ts index 28ec26571d..dca0afae56 100644 --- a/packages/toolkit/src/createSlice.ts +++ b/packages/toolkit/src/createSlice.ts @@ -363,7 +363,7 @@ interface AsyncThunkCreator< > } -interface ReducerCreators { +export interface ReducerCreators { reducer( caseReducer: CaseReducer> ): CaseReducerDefinition> diff --git a/packages/toolkit/src/index.ts b/packages/toolkit/src/index.ts index 74789e1940..45aaed93df 100644 --- a/packages/toolkit/src/index.ts +++ b/packages/toolkit/src/index.ts @@ -70,6 +70,7 @@ export type { ValidateSliceCaseReducers, CaseReducerWithPrepare, SliceActionCreator, + ReducerCreators, } from './createSlice' export { // js diff --git a/packages/toolkit/src/tests/createSlice.typetest.ts b/packages/toolkit/src/tests/createSlice.typetest.ts index ce155e5530..d91fdbeb60 100644 --- a/packages/toolkit/src/tests/createSlice.typetest.ts +++ b/packages/toolkit/src/tests/createSlice.typetest.ts @@ -10,6 +10,7 @@ import type { CaseReducer, PayloadAction, PayloadActionCreator, + ReducerCreators, SerializedError, SliceCaseReducers, ThunkDispatch, @@ -18,6 +19,7 @@ import type { import { configureStore } from '@reduxjs/toolkit' import { createAction, createSlice } from '@reduxjs/toolkit' import { expectExactType, expectType, expectUnknown } from './helpers' +import { castDraft } from 'immer' /* * Test: Slice name is strongly typed. @@ -559,6 +561,10 @@ const value = actionCreators.anyKey expectType(nestedSelectors.selectToFixed(nestedState)) } +/** + * Test: reducer callback + */ + { interface TestState { foo: string @@ -727,3 +733,57 @@ const value = actionCreators.anyKey ) } } + +/** Test: wrapping createSlice should be possible, with callback */ +{ + interface GenericState { + data?: T + status: 'loading' | 'finished' | 'error' + } + + const createGenericSlice = < + T, + Reducers extends SliceCaseReducers> + >({ + name = '', + initialState, + reducers, + }: { + name: string + initialState: GenericState + reducers: (create: ReducerCreators>) => Reducers + }) => { + return createSlice({ + name, + initialState, + reducers: (create) => ({ + start: create.reducer((state) => { + state.status = 'loading' + }), + success: create.reducer((state, action: PayloadAction) => { + state.data = castDraft(action.payload) + state.status = 'finished' + }), + ...reducers(create), + }), + }) + } + + const wrappedSlice = createGenericSlice({ + name: 'test', + initialState: { status: 'loading' } as GenericState, + reducers: (create) => ({ + magic: create.reducer((state) => { + expectType>(state) + // @ts-expect-error + expectType>(state) + + state.status = 'finished' + state.data = 'hocus pocus' + }), + }), + }) + + expectType>(wrappedSlice.actions.success) + expectType>(wrappedSlice.actions.magic) +} From dec53cf350d027f01c0c07fc84c89833d2752e5d Mon Sep 17 00:00:00 2001 From: "ben.durrant" Date: Fri, 5 May 2023 12:10:19 +0100 Subject: [PATCH 152/412] brackets --- packages/toolkit/src/createSlice.ts | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/packages/toolkit/src/createSlice.ts b/packages/toolkit/src/createSlice.ts index dca0afae56..67993ba5cc 100644 --- a/packages/toolkit/src/createSlice.ts +++ b/packages/toolkit/src/createSlice.ts @@ -574,15 +574,10 @@ export function createSlice< } } - const initialState = - typeof options.initialState == 'function' - ? options.initialState - : freezeDraftable(options.initialState) - const reducers = - typeof options.reducers === 'function' + (typeof options.reducers === 'function' ? options.reducers(buildReducerCreators()) - : options.reducers || {} + : options.reducers) || {} const reducerNames = Object.keys(reducers) @@ -636,7 +631,7 @@ export function createSlice< ...context.sliceCaseReducersByType, } - return createReducer(initialState, (builder) => { + return createReducer(options.initialState, (builder) => { for (let key in finalCaseReducers) { builder.addCase(key, finalCaseReducers[key] as CaseReducer) } From 41d8c2031ac0a86b7842a1aaba70cde63210b1e6 Mon Sep 17 00:00:00 2001 From: "ben.durrant" Date: Fri, 5 May 2023 15:27:28 +0100 Subject: [PATCH 153/412] hopefully make typescript happier --- packages/toolkit/src/configureStore.ts | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/packages/toolkit/src/configureStore.ts b/packages/toolkit/src/configureStore.ts index 8bae937181..f08130e75a 100644 --- a/packages/toolkit/src/configureStore.ts +++ b/packages/toolkit/src/configureStore.ts @@ -172,15 +172,13 @@ export function configureStore< const middlewareEnhancer = applyMiddleware(...finalMiddleware) const getDefaultEnhancers = buildGetDefaultEnhancers(middlewareEnhancer) - let storeEnhancers = enhancers ?? getDefaultEnhancers() - if (typeof storeEnhancers === 'function') { - storeEnhancers = storeEnhancers(getDefaultEnhancers) + let storeEnhancers = + (typeof enhancers === 'function' + ? enhancers(getDefaultEnhancers) + : enhancers) ?? getDefaultEnhancers() - if (!IS_PRODUCTION && !Array.isArray(storeEnhancers)) { - throw new Error( - 'when using a enhancer builder function, an array of enhancers must be returned' - ) - } + if (!IS_PRODUCTION && !Array.isArray(storeEnhancers)) { + throw new Error('enhancers must be an array') } if ( !IS_PRODUCTION && From dca680bd4132d55a754878a3b7ce7945bf7e5ca2 Mon Sep 17 00:00:00 2001 From: "ben.durrant" Date: Fri, 5 May 2023 15:37:56 +0100 Subject: [PATCH 154/412] simplify getDefaultEnhancers typing --- packages/toolkit/src/getDefaultEnhancers.ts | 23 ++++--------------- .../src/tests/getDefaultMiddleware.test.ts | 1 + 2 files changed, 5 insertions(+), 19 deletions(-) diff --git a/packages/toolkit/src/getDefaultEnhancers.ts b/packages/toolkit/src/getDefaultEnhancers.ts index 1b8b66ee5b..faf27654aa 100644 --- a/packages/toolkit/src/getDefaultEnhancers.ts +++ b/packages/toolkit/src/getDefaultEnhancers.ts @@ -3,30 +3,15 @@ import type { AutoBatchOptions } from './autoBatchEnhancer' import { autoBatchEnhancer } from './autoBatchEnhancer' import { EnhancerArray } from './utils' import type { Middlewares } from './configureStore' -import type { ExcludeFromTuple, ExtractDispatchExtensions } from './tsHelpers' +import type { ExtractDispatchExtensions } from './tsHelpers' type GetDefaultEnhancersOptions = { autoBatch?: boolean | AutoBatchOptions } -type AutoBatchEnhancerFor = - O extends { autoBatch: false } ? never : StoreEnhancer - -export type GetDefaultEnhancers> = < - O extends GetDefaultEnhancersOptions = { - autoBatch: true - } ->( - options?: O -) => EnhancerArray< - ExcludeFromTuple< - [ - StoreEnhancer<{ dispatch: ExtractDispatchExtensions }>, - AutoBatchEnhancerFor - ], - never - > -> +export type GetDefaultEnhancers> = ( + options?: GetDefaultEnhancersOptions +) => EnhancerArray<[StoreEnhancer<{ dispatch: ExtractDispatchExtensions }>]> export const buildGetDefaultEnhancers = >( middlewareEnhancer: StoreEnhancer<{ dispatch: ExtractDispatchExtensions }> diff --git a/packages/toolkit/src/tests/getDefaultMiddleware.test.ts b/packages/toolkit/src/tests/getDefaultMiddleware.test.ts index eb19fbd29d..c5bb878deb 100644 --- a/packages/toolkit/src/tests/getDefaultMiddleware.test.ts +++ b/packages/toolkit/src/tests/getDefaultMiddleware.test.ts @@ -121,6 +121,7 @@ describe('getDefaultMiddleware', () => { const store = configureStore({ reducer, middleware, + enhancers: (gDE) => gDE(), }) expectType & Dispatch>( From 6a7c23758e1b197579837712f639c4db9cf60da1 Mon Sep 17 00:00:00 2001 From: "ben.durrant" Date: Fri, 5 May 2023 17:47:28 +0100 Subject: [PATCH 155/412] rm unnecessary callback --- packages/toolkit/src/tests/getDefaultMiddleware.test.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/toolkit/src/tests/getDefaultMiddleware.test.ts b/packages/toolkit/src/tests/getDefaultMiddleware.test.ts index c5bb878deb..eb19fbd29d 100644 --- a/packages/toolkit/src/tests/getDefaultMiddleware.test.ts +++ b/packages/toolkit/src/tests/getDefaultMiddleware.test.ts @@ -121,7 +121,6 @@ describe('getDefaultMiddleware', () => { const store = configureStore({ reducer, middleware, - enhancers: (gDE) => gDE(), }) expectType & Dispatch>( From be2ef81faa62cfda50cdb3a7acf2c81eeeb5eece Mon Sep 17 00:00:00 2001 From: "ben.durrant" Date: Fri, 5 May 2023 18:33:31 +0100 Subject: [PATCH 156/412] fix reducer type and use annoying workaround for cAT dispatch --- packages/toolkit/src/createAsyncThunk.ts | 14 +++++--------- packages/toolkit/src/query/core/buildSlice.ts | 6 ++---- 2 files changed, 7 insertions(+), 13 deletions(-) diff --git a/packages/toolkit/src/createAsyncThunk.ts b/packages/toolkit/src/createAsyncThunk.ts index 85a28ec278..db16db20b8 100644 --- a/packages/toolkit/src/createAsyncThunk.ts +++ b/packages/toolkit/src/createAsyncThunk.ts @@ -625,16 +625,12 @@ If you want to use the AbortController to react to \`abort\` events, please cons }) ) ) - dispatch( - pending( - requestId, - arg, - options?.getPendingMeta?.( - { requestId, arg }, - { getState, extra } - ) - ) + const pendingAction = pending( + requestId, + arg, + options?.getPendingMeta?.({ requestId, arg }, { getState, extra }) ) + dispatch(pendingAction) finalAction = await Promise.race([ abortedPromise, Promise.resolve( diff --git a/packages/toolkit/src/query/core/buildSlice.ts b/packages/toolkit/src/query/core/buildSlice.ts index 0989d9b65e..ebf73e5564 100644 --- a/packages/toolkit/src/query/core/buildSlice.ts +++ b/packages/toolkit/src/query/core/buildSlice.ts @@ -485,10 +485,8 @@ export function buildSlice({ config: configSlice.reducer, }) - const reducer = ( - state: CombinedQueryState | undefined, - action: UnknownAction - ) => combinedReducer(resetApiState.match(action) ? undefined : state, action) + const reducer: typeof combinedReducer = (state, action) => + combinedReducer(resetApiState.match(action) ? undefined : state, action) const actions = { ...configSlice.actions, From 963114e4132de9faf6905a4dba446a71a31933c1 Mon Sep 17 00:00:00 2001 From: "ben.durrant" Date: Fri, 5 May 2023 19:04:49 +0100 Subject: [PATCH 157/412] fix some type tests --- packages/toolkit/src/tests/createReducer.test.ts | 4 ++-- packages/toolkit/src/tests/createSlice.test.ts | 5 ++++- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/packages/toolkit/src/tests/createReducer.test.ts b/packages/toolkit/src/tests/createReducer.test.ts index 8adbef99f2..88615b45a6 100644 --- a/packages/toolkit/src/tests/createReducer.test.ts +++ b/packages/toolkit/src/tests/createReducer.test.ts @@ -384,14 +384,14 @@ describe('createReducer', () => { ): a is PayloadAction => isPlainObject(a.meta) && 'type' in a.meta && - a.meta.type === 'number_action' + (a.meta as Record<'type', unknown>).type === 'number_action' const stringActionMatcher = ( a: UnknownAction ): a is PayloadAction => isPlainObject(a.meta) && 'type' in a.meta && - a.meta.type === 'string_action' + (a.meta as Record<'type', unknown>).type === 'string_action' const incrementBy = createAction('increment', prepareNumberAction) const decrementBy = createAction('decrement', prepareNumberAction) diff --git a/packages/toolkit/src/tests/createSlice.test.ts b/packages/toolkit/src/tests/createSlice.test.ts index a92893180b..43025f8a86 100644 --- a/packages/toolkit/src/tests/createSlice.test.ts +++ b/packages/toolkit/src/tests/createSlice.test.ts @@ -271,7 +271,10 @@ describe('createSlice', () => { initialState: 0, reducers: {}, extraReducers: (builder) => - builder.addDefaultCase((state, action) => state + action.payload), + builder.addDefaultCase( + (state, action) => + state + (action as PayloadAction).payload + ), }) expect(slice.reducer(0, increment(5))).toBe(5) }) From 268aea94f03f56e813efe578a72028e56d58f579 Mon Sep 17 00:00:00 2001 From: "ben.durrant" Date: Fri, 5 May 2023 19:11:31 +0100 Subject: [PATCH 158/412] action types are unknown --- packages/toolkit/src/query/tests/cleanup.test.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/toolkit/src/query/tests/cleanup.test.tsx b/packages/toolkit/src/query/tests/cleanup.test.tsx index 75c0f4c6e0..36870e11db 100644 --- a/packages/toolkit/src/query/tests/cleanup.test.tsx +++ b/packages/toolkit/src/query/tests/cleanup.test.tsx @@ -162,7 +162,7 @@ test('Minimizes the number of subscription dispatches when multiple components a let getSubscriptionsA = () => storeRef.store.getState().api.subscriptions['a(undefined)'] - let actionTypes: string[] = [] + let actionTypes: unknown[] = [] listenerMiddleware.startListening({ predicate: () => true, From ac15ac01644b7ad576c766b8051b8a33585fc984 Mon Sep 17 00:00:00 2001 From: "ben.durrant" Date: Fri, 5 May 2023 19:19:54 +0100 Subject: [PATCH 159/412] evilness --- packages/toolkit/src/createAsyncThunk.ts | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/packages/toolkit/src/createAsyncThunk.ts b/packages/toolkit/src/createAsyncThunk.ts index db16db20b8..2d4695090c 100644 --- a/packages/toolkit/src/createAsyncThunk.ts +++ b/packages/toolkit/src/createAsyncThunk.ts @@ -625,12 +625,16 @@ If you want to use the AbortController to react to \`abort\` events, please cons }) ) ) - const pendingAction = pending( - requestId, - arg, - options?.getPendingMeta?.({ requestId, arg }, { getState, extra }) + dispatch( + pending( + requestId, + arg, + options?.getPendingMeta?.( + { requestId, arg }, + { getState, extra } + ) + ) as any ) - dispatch(pendingAction) finalAction = await Promise.race([ abortedPromise, Promise.resolve( From 8c16042c33b67db89a6eda90591de7809ec36439 Mon Sep 17 00:00:00 2001 From: "ben.durrant" Date: Fri, 5 May 2023 19:35:44 +0100 Subject: [PATCH 160/412] more evilness --- packages/toolkit/src/createAsyncThunk.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/toolkit/src/createAsyncThunk.ts b/packages/toolkit/src/createAsyncThunk.ts index 2d4695090c..e13d0cd278 100644 --- a/packages/toolkit/src/createAsyncThunk.ts +++ b/packages/toolkit/src/createAsyncThunk.ts @@ -683,7 +683,7 @@ If you want to use the AbortController to react to \`abort\` events, please cons (finalAction as any).meta.condition if (!skipDispatch) { - dispatch(finalAction) + dispatch(finalAction as any) } return finalAction })() From b0c96b2ef984a7faadcb397b34c4449f22222f84 Mon Sep 17 00:00:00 2001 From: Mark Erikson Date: Sun, 23 Apr 2023 09:00:21 -0400 Subject: [PATCH 161/412] Remove remaining deprecated exports --- docs/api/immutabilityMiddleware.mdx | 2 - packages/toolkit/src/createSlice.ts | 11 --- packages/toolkit/src/devtoolsExtension.ts | 12 --- .../src/immutableStateInvariantMiddleware.ts | 6 -- packages/toolkit/src/index.ts | 5 -- .../toolkit/src/query/core/buildInitiate.ts | 34 -------- .../toolkit/src/query/core/buildSelectors.ts | 2 - packages/toolkit/src/query/core/buildSlice.ts | 2 - packages/toolkit/src/query/core/module.ts | 41 +-------- .../toolkit/src/query/endpointDefinitions.ts | 14 --- packages/toolkit/src/query/index.ts | 2 +- .../toolkit/src/query/react/buildHooks.ts | 9 +- .../src/query/tests/buildHooks.test.tsx | 1 - .../src/query/tests/cacheLifecycle.test.ts | 40 +++++++-- .../src/tests/MiddlewareArray.typetest.ts | 3 +- .../src/tests/configureStore.typetest.ts | 56 +++++------- .../src/tests/getDefaultMiddleware.test.ts | 85 ++++++++++--------- .../immutableStateInvariantMiddleware.test.ts | 13 --- 18 files changed, 108 insertions(+), 230 deletions(-) diff --git a/docs/api/immutabilityMiddleware.mdx b/docs/api/immutabilityMiddleware.mdx index ae192a7f8a..73b03d972c 100644 --- a/docs/api/immutabilityMiddleware.mdx +++ b/docs/api/immutabilityMiddleware.mdx @@ -36,8 +36,6 @@ interface ImmutableStateInvariantMiddlewareOptions { ignoredPaths?: (string | RegExp)[] /** Print a warning if checks take longer than N ms. Default: 32ms */ warnAfter?: number - // @deprecated. Use ignoredPaths - ignore?: string[] } ``` diff --git a/packages/toolkit/src/createSlice.ts b/packages/toolkit/src/createSlice.ts index b62c12f777..07cca7b6c7 100644 --- a/packages/toolkit/src/createSlice.ts +++ b/packages/toolkit/src/createSlice.ts @@ -19,17 +19,6 @@ import type { Id, NoInfer, Tail } from './tsHelpers' import { freezeDraftable } from './utils' import type { CombinedSliceReducer, InjectConfig } from './combineSlices' -let hasWarnedAboutObjectNotation = false - -/** - * An action creator attached to a slice. - * - * @deprecated please use PayloadActionCreator directly - * - * @public - */ -export type SliceActionCreator

= PayloadActionCreator

- /** * The return value of `createSlice` * diff --git a/packages/toolkit/src/devtoolsExtension.ts b/packages/toolkit/src/devtoolsExtension.ts index a48d7e6d11..5e0e1db982 100644 --- a/packages/toolkit/src/devtoolsExtension.ts +++ b/packages/toolkit/src/devtoolsExtension.ts @@ -90,18 +90,6 @@ export interface DevToolsEnhancerOptions { * function which takes `state` object and index as arguments, and should return `state` object back. */ stateSanitizer?: (state: S, index: number) => S - /** - * *string or array of strings as regex* - actions types to be hidden / shown in the monitors (while passed to the reducers). - * If `actionsWhitelist` specified, `actionsBlacklist` is ignored. - * @deprecated Use actionsDenylist instead. - */ - actionsBlacklist?: string | string[] - /** - * *string or array of strings as regex* - actions types to be hidden / shown in the monitors (while passed to the reducers). - * If `actionsWhitelist` specified, `actionsBlacklist` is ignored. - * @deprecated Use actionsAllowlist instead. - */ - actionsWhitelist?: string | string[] /** * *string or array of strings as regex* - actions types to be hidden / shown in the monitors (while passed to the reducers). * If `actionsAllowlist` specified, `actionsDenylist` is ignored. diff --git a/packages/toolkit/src/immutableStateInvariantMiddleware.ts b/packages/toolkit/src/immutableStateInvariantMiddleware.ts index 7230f16394..37fcc105db 100644 --- a/packages/toolkit/src/immutableStateInvariantMiddleware.ts +++ b/packages/toolkit/src/immutableStateInvariantMiddleware.ts @@ -167,8 +167,6 @@ export interface ImmutableStateInvariantMiddlewareOptions { ignoredPaths?: IgnorePaths /** Print a warning if checks take longer than N ms. Default: 32ms */ warnAfter?: number - // @deprecated. Use ignoredPaths - ignore?: string[] } /** @@ -226,12 +224,8 @@ export function createImmutableStateInvariantMiddleware( isImmutable = isImmutableDefault, ignoredPaths, warnAfter = 32, - ignore, } = options - // Alias ignore->ignoredPaths, but prefer ignoredPaths if present - ignoredPaths = ignoredPaths || ignore - const track = trackForMutations.bind(null, isImmutable, ignoredPaths) return ({ getState }) => { diff --git a/packages/toolkit/src/index.ts b/packages/toolkit/src/index.ts index 74789e1940..5210b24743 100644 --- a/packages/toolkit/src/index.ts +++ b/packages/toolkit/src/index.ts @@ -69,7 +69,6 @@ export type { SliceCaseReducers, ValidateSliceCaseReducers, CaseReducerWithPrepare, - SliceActionCreator, } from './createSlice' export { // js @@ -90,10 +89,6 @@ export type { // types SerializableStateInvariantMiddlewareOptions, } from './serializableStateInvariantMiddleware' -export { - // js - getDefaultMiddleware, -} from './getDefaultMiddleware' export type { // types ActionReducerMapBuilder, diff --git a/packages/toolkit/src/query/core/buildInitiate.ts b/packages/toolkit/src/query/core/buildInitiate.ts index 8641c3c373..a26aaf0b2a 100644 --- a/packages/toolkit/src/query/core/buildInitiate.ts +++ b/packages/toolkit/src/query/core/buildInitiate.ts @@ -181,8 +181,6 @@ export type MutationActionCreatorResult< The value returned by the hook will reset to `isUninitialized` afterwards. */ reset(): void - /** @deprecated has been renamed to `reset` */ - unsubscribe(): void } export function buildInitiate({ @@ -219,37 +217,6 @@ export function buildInitiate({ getRunningMutationThunk, getRunningQueriesThunk, getRunningMutationsThunk, - getRunningOperationPromises, - removalWarning, - } - - /** @deprecated to be removed in 2.0 */ - function removalWarning(): never { - throw new Error( - `This method had to be removed due to a conceptual bug in RTK. - Please see https://github.com/reduxjs/redux-toolkit/pull/2481 for details. - See https://redux-toolkit.js.org/rtk-query/usage/server-side-rendering for new guidance on SSR.` - ) - } - - /** @deprecated to be removed in 2.0 */ - function getRunningOperationPromises() { - if ( - typeof process !== 'undefined' && - process.env.NODE_ENV === 'development' - ) { - removalWarning() - } else { - const extract = ( - v: Map, Record> - ) => - Array.from(v.values()).flatMap((queriesForStore) => - queriesForStore ? Object.values(queriesForStore) : [] - ) - return [...extract(runningQueries), ...extract(runningMutations)].filter( - isNotNullish - ) - } } function getRunningQueryThunk(endpointName: string, queryArgs: any) { @@ -464,7 +431,6 @@ You must add the middleware for RTK-Query to function correctly!` requestId, abort, unwrap, - unsubscribe: reset, reset, }) diff --git a/packages/toolkit/src/query/core/buildSelectors.ts b/packages/toolkit/src/query/core/buildSelectors.ts index a49da366c5..d3a7361c70 100644 --- a/packages/toolkit/src/query/core/buildSelectors.ts +++ b/packages/toolkit/src/query/core/buildSelectors.ts @@ -45,8 +45,6 @@ export type SkipToken = typeof skipToken * return an uninitialized state. */ export const skipToken = /* @__PURE__ */ Symbol.for('RTKQ/skipToken') -/** @deprecated renamed to `skipToken` */ -export const skipSelector = skipToken declare module './module' { export interface ApiEndpointQuery< diff --git a/packages/toolkit/src/query/core/buildSlice.ts b/packages/toolkit/src/query/core/buildSlice.ts index 41f210185c..b32531eb49 100644 --- a/packages/toolkit/src/query/core/buildSlice.ts +++ b/packages/toolkit/src/query/core/buildSlice.ts @@ -494,8 +494,6 @@ export function buildSlice({ ...subscriptionSlice.actions, ...internalSubscriptionsSlice.actions, ...mutationSlice.actions, - /** @deprecated has been renamed to `removeMutationResult` */ - unsubscribeMutationResult: mutationSlice.actions.removeMutationResult, resetApiState, } diff --git a/packages/toolkit/src/query/core/module.ts b/packages/toolkit/src/query/core/module.ts index 2cb9ac76e3..874fc34fa0 100644 --- a/packages/toolkit/src/query/core/module.ts +++ b/packages/toolkit/src/query/core/module.ts @@ -71,7 +71,8 @@ export type CoreModule = | ReferenceQueryLifecycle | ReferenceCacheCollection -export interface ThunkWithReturnValue extends ThunkAction {} +export interface ThunkWithReturnValue + extends ThunkAction {} declare module '../apiTypes' { export interface ApiModules< @@ -139,28 +140,6 @@ declare module '../apiTypes' { * A collection of utility thunks for various situations. */ util: { - /** - * This method had to be removed due to a conceptual bug in RTK. - * - * Despite TypeScript errors, it will continue working in the "buggy" way it did - * before in production builds and will be removed in the next major release. - * - * Nonetheless, you should immediately replace it with the new recommended approach. - * See https://redux-toolkit.js.org/rtk-query/usage/server-side-rendering for new guidance on SSR. - * - * Please see https://github.com/reduxjs/redux-toolkit/pull/2481 for details. - * @deprecated - */ - getRunningOperationPromises: never // this is now types as `never` to immediately throw TS errors on use, but still allow for a comment - - /** - * This method had to be removed due to a conceptual bug in RTK. - * It has been replaced by `api.util.getRunningQueryThunk` and `api.util.getRunningMutationThunk`. - * Please see https://github.com/reduxjs/redux-toolkit/pull/2481 for details. - * @deprecated - */ - getRunningOperationPromise: never // this is now types as `never` to immediately throw TS errors on use, but still allow for a comment - /** * A thunk that (if dispatched) will return a specific running query, identified * by `endpointName` and `args`. @@ -268,11 +247,7 @@ declare module '../apiTypes' { Definitions, RootState > - /** @deprecated renamed to `updateQueryData` */ - updateQueryResult: UpdateQueryDataThunk< - Definitions, - RootState - > + /** * A Redux thunk action creator that, when dispatched, acts as an artificial API request to upsert a value into the cache. * @@ -326,11 +301,7 @@ declare module '../apiTypes' { Definitions, RootState > - /** @deprecated renamed to `patchQueryData` */ - patchQueryResult: PatchQueryDataThunk< - Definitions, - RootState - > + /** * A Redux action creator that can be dispatched to manually reset the api state completely. This will immediately remove all existing cache entries, and all queries will be considered 'uninitialized'. * @@ -571,8 +542,6 @@ export const coreModule = (): Module => ({ getRunningMutationsThunk, getRunningQueriesThunk, getRunningQueryThunk, - getRunningOperationPromises, - removalWarning, } = buildInitiate({ queryThunk, mutationThunk, @@ -582,8 +551,6 @@ export const coreModule = (): Module => ({ }) safeAssign(api.util, { - getRunningOperationPromises: getRunningOperationPromises as any, - getRunningOperationPromise: removalWarning as any, getRunningMutationThunk, getRunningMutationsThunk, getRunningQueryThunk, diff --git a/packages/toolkit/src/query/endpointDefinitions.ts b/packages/toolkit/src/query/endpointDefinitions.ts index ae88e9573f..d0411f3ffb 100644 --- a/packages/toolkit/src/query/endpointDefinitions.ts +++ b/packages/toolkit/src/query/endpointDefinitions.ts @@ -230,20 +230,6 @@ export type ResultDescription< | ReadonlyArray> | GetResultDescriptionFn -/** @deprecated please use `onQueryStarted` instead */ -export interface QueryApi { - /** @deprecated please use `onQueryStarted` instead */ - dispatch: ThunkDispatch - /** @deprecated please use `onQueryStarted` instead */ - getState(): RootState - /** @deprecated please use `onQueryStarted` instead */ - extra: unknown - /** @deprecated please use `onQueryStarted` instead */ - requestId: string - /** @deprecated please use `onQueryStarted` instead */ - context: Context -} - export interface QueryTypes< QueryArg, BaseQuery extends BaseQueryFn, diff --git a/packages/toolkit/src/query/index.ts b/packages/toolkit/src/query/index.ts index 8b41fd1e8b..dc2b2c5e96 100644 --- a/packages/toolkit/src/query/index.ts +++ b/packages/toolkit/src/query/index.ts @@ -20,7 +20,7 @@ export type { } from './fetchBaseQuery' export { retry } from './retry' export { setupListeners } from './core/setupListeners' -export { skipSelector, skipToken } from './core/buildSelectors' +export { skipToken } from './core/buildSelectors' export type { SkipToken } from './core/buildSelectors' export type { CreateApi, CreateApiOptions } from './createApi' export { buildCreateApi } from './createApi' diff --git a/packages/toolkit/src/query/react/buildHooks.ts b/packages/toolkit/src/query/react/buildHooks.ts index 28e2916bf7..c9043eb4ba 100644 --- a/packages/toolkit/src/query/react/buildHooks.ts +++ b/packages/toolkit/src/query/react/buildHooks.ts @@ -452,14 +452,7 @@ type UseQueryStateDefaultResult> = Pick, 'error'> >) > - > & { - /** - * @deprecated will be removed in the next version - * please use the `isLoading`, `isFetching`, `isSuccess`, `isError` - * and `isUninitialized` flags instead - */ - status: QueryStatus - } + > export type MutationStateSelector< R extends Record, diff --git a/packages/toolkit/src/query/tests/buildHooks.test.tsx b/packages/toolkit/src/query/tests/buildHooks.test.tsx index 3f297c3dfb..d94081e15c 100644 --- a/packages/toolkit/src/query/tests/buildHooks.test.tsx +++ b/packages/toolkit/src/query/tests/buildHooks.test.tsx @@ -1344,7 +1344,6 @@ describe('hooks tests', () => { expectType<() => void>(res.abort) expectType<() => Promise<{ name: string }>>(res.unwrap) expectType<() => void>(res.reset) - expectType<() => void>(res.unsubscribe) // abort the mutation immediately to force an error res.abort() diff --git a/packages/toolkit/src/query/tests/cacheLifecycle.test.ts b/packages/toolkit/src/query/tests/cacheLifecycle.test.ts index 54bac7eda8..778000ce5c 100644 --- a/packages/toolkit/src/query/tests/cacheLifecycle.test.ts +++ b/packages/toolkit/src/query/tests/cacheLifecycle.test.ts @@ -8,6 +8,7 @@ import { setupApiStore, DEFAULT_DELAY_MS, } from './helpers' +import { QueryActionCreatorResult } from '../core/buildInitiate' beforeAll(() => { vi.useFakeTimers() @@ -54,6 +55,7 @@ describe.each([['query'], ['mutation']] as const)( const extended = api.injectEndpoints({ overrideExisting: true, endpoints: (build) => ({ + // Lying to TS here injected: build[type as 'mutation']({ query: () => '/success', async onCacheEntryAdded( @@ -74,7 +76,11 @@ describe.each([['query'], ['mutation']] as const)( expect(onNewCacheEntry).toHaveBeenCalledWith('arg') expect(onCleanup).not.toHaveBeenCalled() - promise.unsubscribe() + if (type === 'mutation') { + promise.reset() + } else { + ;(promise as unknown as QueryActionCreatorResult).unsubscribe() + } await vi.advanceTimersByTimeAsync(DEFAULT_DELAY_MS) if (type === 'query') { await vi.advanceTimersByTimeAsync(59000) @@ -128,7 +134,11 @@ describe.each([['query'], ['mutation']] as const)( }) expect(onCleanup).not.toHaveBeenCalled() - promise.unsubscribe() + if (type === 'mutation') { + promise.reset() + } else { + ;(promise as unknown as QueryActionCreatorResult).unsubscribe() + } await vi.advanceTimersByTimeAsync(DEFAULT_DELAY_MS) if (type === 'query') { await vi.advanceTimersByTimeAsync(59000) @@ -166,7 +176,11 @@ describe.each([['query'], ['mutation']] as const)( ) expect(onNewCacheEntry).toHaveBeenCalledWith('arg') - promise.unsubscribe() + if (type === 'mutation') { + promise.reset() + } else { + ;(promise as unknown as QueryActionCreatorResult).unsubscribe() + } await vi.advanceTimersByTimeAsync(DEFAULT_DELAY_MS) if (type === 'query') { await vi.advanceTimersByTimeAsync(120000) @@ -205,7 +219,11 @@ describe.each([['query'], ['mutation']] as const)( ) expect(onNewCacheEntry).toHaveBeenCalledWith('arg') - promise.unsubscribe() + if (type === 'mutation') { + promise.reset() + } else { + ;(promise as unknown as QueryActionCreatorResult).unsubscribe() + } await vi.advanceTimersByTimeAsync(DEFAULT_DELAY_MS) if (type === 'query') { @@ -253,7 +271,11 @@ describe.each([['query'], ['mutation']] as const)( expect(onNewCacheEntry).toHaveBeenCalledWith('arg') - promise.unsubscribe() + if (type === 'mutation') { + promise.reset() + } else { + ;(promise as unknown as QueryActionCreatorResult).unsubscribe() + } await vi.advanceTimersByTimeAsync(DEFAULT_DELAY_MS) if (type === 'query') { await vi.advanceTimersByTimeAsync(59000) @@ -299,7 +321,11 @@ describe.each([['query'], ['mutation']] as const)( expect(onNewCacheEntry).toHaveBeenCalledWith('arg') - promise.unsubscribe() + if (type === 'mutation') { + promise.reset() + } else { + ;(promise as unknown as QueryActionCreatorResult).unsubscribe() + } await vi.advanceTimersByTimeAsync(DEFAULT_DELAY_MS) if (type === 'query') { await vi.advanceTimersByTimeAsync(59000) @@ -421,7 +447,7 @@ test(`mutation: getCacheEntry`, async () => { expect(gotFirstValue).toHaveBeenCalled() }) - promise.unsubscribe() + promise.reset() await vi.advanceTimersByTimeAsync(DEFAULT_DELAY_MS) expect(snapshot).toHaveBeenCalledTimes(3) diff --git a/packages/toolkit/src/tests/MiddlewareArray.typetest.ts b/packages/toolkit/src/tests/MiddlewareArray.typetest.ts index 25bb4cb21d..86b570798d 100644 --- a/packages/toolkit/src/tests/MiddlewareArray.typetest.ts +++ b/packages/toolkit/src/tests/MiddlewareArray.typetest.ts @@ -1,4 +1,5 @@ -import { getDefaultMiddleware, configureStore } from '@reduxjs/toolkit' +import { getDefaultMiddleware } from '@internal/getDefaultMiddleware' +import { configureStore } from '@reduxjs/toolkit' import type { Middleware } from 'redux' declare const expectType: (t: T) => T diff --git a/packages/toolkit/src/tests/configureStore.typetest.ts b/packages/toolkit/src/tests/configureStore.typetest.ts index d64ae35253..5bba5a7fc5 100644 --- a/packages/toolkit/src/tests/configureStore.typetest.ts +++ b/packages/toolkit/src/tests/configureStore.typetest.ts @@ -10,11 +10,7 @@ import type { } from 'redux' import { applyMiddleware, combineReducers } from 'redux' import type { PayloadAction, ConfigureStoreOptions } from '@reduxjs/toolkit' -import { - configureStore, - getDefaultMiddleware, - createSlice, -} from '@reduxjs/toolkit' +import { configureStore, createSlice } from '@reduxjs/toolkit' import type { ThunkMiddleware, ThunkAction, ThunkDispatch } from 'redux-thunk' import { thunk } from 'redux-thunk' import { expectNotAny, expectType } from './helpers' @@ -460,19 +456,7 @@ const _anyMiddleware: any = () => () => () => {} // @ts-expect-error store.dispatch(thunkB()) } - /** - * Test: using getDefaultMiddleware - */ - { - const store = configureStore({ - reducer: reducerA, - middleware: getDefaultMiddleware(), - }) - store.dispatch(thunkA()) - // @ts-expect-error - store.dispatch(thunkB()) - } /** * Test: custom middleware */ @@ -545,12 +529,10 @@ const _anyMiddleware: any = () => () => () => {} * Test: custom middleware and getDefaultMiddleware */ { - const middleware = getDefaultMiddleware().prepend( - (() => {}) as any as Middleware<(a: 'a') => 'A', StateA> - ) const store = configureStore({ reducer: reducerA, - middleware, + middleware: (gDM) => + gDM().prepend((() => {}) as any as Middleware<(a: 'a') => 'A', StateA>), }) const result1: 'A' = store.dispatch('a') @@ -564,15 +546,19 @@ const _anyMiddleware: any = () => () => () => {} */ { const otherMiddleware: Middleware<(a: 'a') => 'A', StateA> = _anyMiddleware - const concatenated = getDefaultMiddleware().prepend(otherMiddleware) - - expectType< - ReadonlyArray> - >(concatenated) const store = configureStore({ reducer: reducerA, - middleware: concatenated, + middleware: (gDM) => { + const concatenated = gDM().prepend(otherMiddleware) + expectType< + ReadonlyArray< + typeof otherMiddleware | ThunkMiddleware | Middleware<{}> + > + >(concatenated) + + return concatenated + }, }) const result1: 'A' = store.dispatch('a') const result2: Promise<'A'> = store.dispatch(thunkA()) @@ -585,15 +571,19 @@ const _anyMiddleware: any = () => () => () => {} */ { const otherMiddleware: Middleware<(a: 'a') => 'A', StateA> = _anyMiddleware - const concatenated = getDefaultMiddleware().concat(otherMiddleware) - - expectType< - ReadonlyArray> - >(concatenated) const store = configureStore({ reducer: reducerA, - middleware: concatenated, + middleware: (gDM) => { + const concatenated = gDM().concat(otherMiddleware) + + expectType< + ReadonlyArray< + typeof otherMiddleware | ThunkMiddleware | Middleware<{}> + > + >(concatenated) + return concatenated + }, }) const result1: 'A' = store.dispatch('a') const result2: Promise<'A'> = store.dispatch(thunkA()) diff --git a/packages/toolkit/src/tests/getDefaultMiddleware.test.ts b/packages/toolkit/src/tests/getDefaultMiddleware.test.ts index 4da69101e3..ea838ec6ad 100644 --- a/packages/toolkit/src/tests/getDefaultMiddleware.test.ts +++ b/packages/toolkit/src/tests/getDefaultMiddleware.test.ts @@ -7,11 +7,9 @@ import type { ThunkDispatch, Dispatch, } from '@reduxjs/toolkit' -import { - getDefaultMiddleware, - MiddlewareArray, - configureStore, -} from '@reduxjs/toolkit' +import { configureStore } from '@reduxjs/toolkit' +import { getDefaultMiddleware } from '@internal/getDefaultMiddleware' +import { MiddlewareArray } from '@internal/utils' import { thunk } from 'redux-thunk' import type { ThunkMiddleware } from 'redux-thunk' @@ -32,7 +30,9 @@ describe('getDefaultMiddleware', () => { it('returns an array with only redux-thunk in production', async () => { process.env.NODE_ENV = 'production' const { thunk } = await import('redux-thunk') - const { getDefaultMiddleware } = await import('@reduxjs/toolkit') + const { getDefaultMiddleware } = await import( + '@internal/getDefaultMiddleware' + ) const middleware = getDefaultMiddleware() expect(middleware).toContain(thunk) @@ -67,11 +67,6 @@ describe('getDefaultMiddleware', () => { it('allows passing options to thunk', () => { const extraArgument = 42 as const - const middleware = getDefaultMiddleware({ - thunk: { extraArgument }, - immutableCheck: false, - serializableCheck: false, - }) const m2 = getDefaultMiddleware({ thunk: false, @@ -84,41 +79,49 @@ describe('getDefaultMiddleware', () => { (action: Action<'actionListenerMiddleware/add'>): () => void }, { counter: number } - > = (storeApi) => (next) => (action) => {} - - const dummyMiddleware2: Middleware = (storeApi) => (next) => (action) => {} - - const m3 = middleware.concat(dummyMiddleware, dummyMiddleware2) - - expectType< - MiddlewareArray< - [ - ThunkMiddleware, - Middleware< - (action: Action<'actionListenerMiddleware/add'>) => () => void, - { - counter: number - }, - Dispatch - >, - Middleware<{}, any, Dispatch> - ] - > - >(m3) - - const testThunk: ThunkAction = ( - dispatch, - getState, - extraArg - ) => { - expect(extraArg).toBe(extraArgument) + > = (storeApi) => (next) => (action) => { + return next(action) } - const reducer = () => ({}) + const dummyMiddleware2: Middleware<{}, { counter: number }> = + (storeApi) => (next) => (action) => {} + + const testThunk: ThunkAction = + (dispatch, getState, extraArg) => { + expect(extraArg).toBe(extraArgument) + } + + const reducer = () => ({ counter: 123 }) const store = configureStore({ reducer, - middleware, + middleware: (gDM) => { + const middleware = gDM({ + thunk: { extraArgument }, + immutableCheck: false, + serializableCheck: false, + }) + + const m3 = middleware.concat(dummyMiddleware, dummyMiddleware2) + + expectType< + MiddlewareArray< + [ + ThunkMiddleware, + Middleware< + (action: Action<'actionListenerMiddleware/add'>) => () => void, + { + counter: number + }, + Dispatch + >, + Middleware<{}, any, Dispatch> + ] + > + >(m3) + + return m3 + }, }) expectType & Dispatch>( diff --git a/packages/toolkit/src/tests/immutableStateInvariantMiddleware.test.ts b/packages/toolkit/src/tests/immutableStateInvariantMiddleware.test.ts index c569809e22..dec7789032 100644 --- a/packages/toolkit/src/tests/immutableStateInvariantMiddleware.test.ts +++ b/packages/toolkit/src/tests/immutableStateInvariantMiddleware.test.ts @@ -140,19 +140,6 @@ describe('createImmutableStateInvariantMiddleware', () => { }).not.toThrow() }) - it('alias "ignore" to "ignoredPath" and respects option', () => { - const next: MWNext = (action) => { - state.foo.bar.push(5) - return action - } - - const dispatch = middleware({ ignore: ['foo.bar'] })(next) - - expect(() => { - dispatch({ type: 'SOME_ACTION' }) - }).not.toThrow() - }) - it('Should print a warning if execution takes too long', () => { state.foo.bar = new Array(10000).fill({ value: 'more' }) From 1b289ee4477d1625ed91aa633f18f7652d6b8c0e Mon Sep 17 00:00:00 2001 From: Ben Durrant Date: Fri, 5 May 2023 21:46:49 +0100 Subject: [PATCH 162/412] fix docs build --- docs/package.json | 1 - .../usage/customizing-create-api.mdx | 7 +++- docs/rtk-query/usage/error-handling.mdx | 8 +++- docs/tsconfig.json | 37 ++++++++++++++----- docs/virtual/redux-logger/index.ts | 7 ++++ yarn.lock | 12 +----- 6 files changed, 49 insertions(+), 23 deletions(-) create mode 100644 docs/virtual/redux-logger/index.ts diff --git a/docs/package.json b/docs/package.json index 449895bc21..2eef2575c0 100644 --- a/docs/package.json +++ b/docs/package.json @@ -4,7 +4,6 @@ "@manaflair/redux-batch": "^1.0.0", "@types/nanoid": "^2.1.0", "@types/react": "^18.0", - "@types/redux-logger": "^3.0.8", "async-mutex": "^0.3.2", "axios": "^0.20.0", "formik": "^2.1.5", diff --git a/docs/rtk-query/usage/customizing-create-api.mdx b/docs/rtk-query/usage/customizing-create-api.mdx index ad63dc22ec..2f4d53cfb8 100644 --- a/docs/rtk-query/usage/customizing-create-api.mdx +++ b/docs/rtk-query/usage/customizing-create-api.mdx @@ -23,7 +23,12 @@ If you want the hooks to use different versions of `useSelector`, `useDispatch` ```ts import * as React from 'react' -import { createDispatchHook, ReactReduxContextValue } from 'react-redux' +import { + createDispatchHook, + createSelectorHook, + createStoreHook, + ReactReduxContextValue, +} from 'react-redux' import { buildCreateApi, coreModule, diff --git a/docs/rtk-query/usage/error-handling.mdx b/docs/rtk-query/usage/error-handling.mdx index fcd1dd9280..40ad27b2ad 100644 --- a/docs/rtk-query/usage/error-handling.mdx +++ b/docs/rtk-query/usage/error-handling.mdx @@ -93,7 +93,13 @@ export const rtkQueryErrorLogger: Middleware = // RTK Query uses `createAsyncThunk` from redux-toolkit under the hood, so we're able to utilize these matchers! if (isRejectedWithValue(action)) { console.warn('We got a rejected action!') - toast.warn({ title: 'Async error!', message: action.error.data.message }) + toast.warn({ + title: 'Async error!', + message: + 'data' in action.error + ? (action.error.data as { message: string }).message + : action.error.message, + }) } return next(action) diff --git a/docs/tsconfig.json b/docs/tsconfig.json index 9c00a64847..80bf4d4a7d 100644 --- a/docs/tsconfig.json +++ b/docs/tsconfig.json @@ -18,17 +18,36 @@ "baseUrl": "..", "jsx": "preserve", "paths": { - "react": [ "../node_modules/@types/react" ], - "react-dom": [ "../node_modules/@types/react-dom" ], - "@reduxjs/toolkit": ["packages/toolkit/src/index.ts"], - "@reduxjs/toolkit/query": ["packages/toolkit/src/query/index.ts"], + "react": [ + "../node_modules/@types/react" + ], + "react-dom": [ + "../node_modules/@types/react-dom" + ], + "@reduxjs/toolkit": [ + "packages/toolkit/src/index.ts" + ], + "@reduxjs/toolkit/query": [ + "packages/toolkit/src/query/index.ts" + ], "@reduxjs/toolkit/query/react": [ "packages/toolkit/src/query/react/index.ts" ], - "@reduxjs/toolkit/dist/query/*": ["packages/toolkit/src/query/*"], - "@virtual/*": ["docs/virtual/*"], - "your-cool-library": ["docs/virtual/your-cool-library/index.ts"], - "petstore-api.generated": ["docs/virtual/petstore-api.generated/index.ts"] + "@reduxjs/toolkit/dist/query/*": [ + "packages/toolkit/src/query/*" + ], + "@virtual/*": [ + "docs/virtual/*" + ], + "your-cool-library": [ + "docs/virtual/your-cool-library/index.ts" + ], + "redux-logger": [ + "docs/virtual/redux-logger/index.ts" + ], + "petstore-api.generated": [ + "docs/virtual/petstore-api.generated/index.ts" + ] } } -} +} \ No newline at end of file diff --git a/docs/virtual/redux-logger/index.ts b/docs/virtual/redux-logger/index.ts new file mode 100644 index 0000000000..c65a858e6f --- /dev/null +++ b/docs/virtual/redux-logger/index.ts @@ -0,0 +1,7 @@ +import type { Middleware } from 'redux' + +declare const logger: Middleware + +export { logger } + +export default logger diff --git a/yarn.lock b/yarn.lock index 692ea5a6b9..e1040f2f6b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -8234,15 +8234,6 @@ __metadata: languageName: node linkType: hard -"@types/redux-logger@npm:^3.0.8": - version: 3.0.8 - resolution: "@types/redux-logger@npm:3.0.8" - dependencies: - redux: ^4.0.0 - checksum: 68dee8799db09aab2625435008230543f23a6dcf4f29cb8f74d2ec3c723c98765d6ed8fbf1edf2deb440f4a1969082b7d35d9548ab810e9054aa381bc925874c - languageName: node - linkType: hard - "@types/resolve@npm:1.17.1": version: 1.17.1 resolution: "@types/resolve@npm:1.17.1" @@ -13456,7 +13447,6 @@ __metadata: "@manaflair/redux-batch": ^1.0.0 "@types/nanoid": ^2.1.0 "@types/react": ^18.0 - "@types/redux-logger": ^3.0.8 async-mutex: ^0.3.2 axios: ^0.20.0 formik: ^2.1.5 @@ -24686,7 +24676,7 @@ fsevents@^1.2.7: languageName: node linkType: hard -"redux@npm:^4.0.0, redux@npm:^4.1.2": +"redux@npm:^4.1.2": version: 4.1.2 resolution: "redux@npm:4.1.2" dependencies: From 8853a35d5582884fe5034f24d64aa59c629ffe43 Mon Sep 17 00:00:00 2001 From: Mark Erikson Date: Fri, 5 May 2023 14:14:51 -0700 Subject: [PATCH 163/412] Restore TS types for query `status` field --- packages/toolkit/src/query/react/buildHooks.ts | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/packages/toolkit/src/query/react/buildHooks.ts b/packages/toolkit/src/query/react/buildHooks.ts index 6d528117c9..3b764d31ef 100644 --- a/packages/toolkit/src/query/react/buildHooks.ts +++ b/packages/toolkit/src/query/react/buildHooks.ts @@ -452,7 +452,14 @@ type UseQueryStateDefaultResult> = Pick, 'error'> >) > - > + > & { + /** + * @deprecated Included for completeness, but discouraged. + * Please use the `isLoading`, `isFetching`, `isSuccess`, `isError` + * and `isUninitialized` flags instead + */ + status: QueryStatus + } export type MutationStateSelector< R extends Record, From d80065d686344e5b120359a1093ac87c8ad56a72 Mon Sep 17 00:00:00 2001 From: Ben Durrant Date: Fri, 5 May 2023 23:14:57 +0100 Subject: [PATCH 164/412] disable batching for test store --- packages/toolkit/src/query/tests/helpers.tsx | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/packages/toolkit/src/query/tests/helpers.tsx b/packages/toolkit/src/query/tests/helpers.tsx index f6d1f34c68..ae32035c5c 100644 --- a/packages/toolkit/src/query/tests/helpers.tsx +++ b/packages/toolkit/src/query/tests/helpers.tsx @@ -218,6 +218,10 @@ export function setupApiStore< .concat(...(middleware?.concat ?? [])) .prepend(...(middleware?.prepend ?? [])) as typeof tempMiddleware }, + enhancers: (gde) => + gde({ + autoBatch: false, + }), }) type StoreType = EnhancedStore< From 6ac866c6c904c204b701754fe11dd777a870434a Mon Sep 17 00:00:00 2001 From: Ben Durrant Date: Fri, 5 May 2023 23:24:40 +0100 Subject: [PATCH 165/412] prevent type from getting confused --- packages/toolkit/src/query/tests/helpers.tsx | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/packages/toolkit/src/query/tests/helpers.tsx b/packages/toolkit/src/query/tests/helpers.tsx index ae32035c5c..b0944d76a2 100644 --- a/packages/toolkit/src/query/tests/helpers.tsx +++ b/packages/toolkit/src/query/tests/helpers.tsx @@ -5,6 +5,8 @@ import type { Middleware, Store, Reducer, + MiddlewareArray, + ThunkMiddleware, } from '@reduxjs/toolkit' import { configureStore } from '@reduxjs/toolkit' import { setupListeners } from '@reduxjs/toolkit/query' @@ -208,7 +210,9 @@ export function setupApiStore< const getStore = () => configureStore({ reducer: { api: api.reducer, ...extraReducers }, - middleware: (gdm) => { + middleware: ( + gdm + ): MiddlewareArray<[ThunkMiddleware<{ api: any }>, ...Middleware[]]> => { const tempMiddleware = gdm({ serializableCheck: false, immutableCheck: false, From 29f80929dc164732e842bbeaa5d9bca3911dceca Mon Sep 17 00:00:00 2001 From: Ben Durrant Date: Fri, 5 May 2023 23:57:49 +0100 Subject: [PATCH 166/412] brute force and ignorance --- packages/toolkit/src/query/tests/helpers.tsx | 42 ++++++++++---------- 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/packages/toolkit/src/query/tests/helpers.tsx b/packages/toolkit/src/query/tests/helpers.tsx index b0944d76a2..d5d202940a 100644 --- a/packages/toolkit/src/query/tests/helpers.tsx +++ b/packages/toolkit/src/query/tests/helpers.tsx @@ -5,8 +5,9 @@ import type { Middleware, Store, Reducer, - MiddlewareArray, - ThunkMiddleware, + EnhancerArray, + StoreEnhancer, + ThunkDispatch, } from '@reduxjs/toolkit' import { configureStore } from '@reduxjs/toolkit' import { setupListeners } from '@reduxjs/toolkit/query' @@ -206,13 +207,24 @@ export function setupApiStore< } } = {} ) { + type State = { + api: ReturnType + } & { + [K in keyof R]: ReturnType + } + type StoreType = EnhancedStore< + State, + AnyAction, + EnhancerArray< + [StoreEnhancer<{ dispatch: ThunkDispatch }>] + > + > + const { middleware } = options const getStore = () => configureStore({ reducer: { api: api.reducer, ...extraReducers }, - middleware: ( - gdm - ): MiddlewareArray<[ThunkMiddleware<{ api: any }>, ...Middleware[]]> => { + middleware: (gdm) => { const tempMiddleware = gdm({ serializableCheck: false, immutableCheck: false, @@ -220,27 +232,15 @@ export function setupApiStore< return tempMiddleware .concat(...(middleware?.concat ?? [])) - .prepend(...(middleware?.prepend ?? [])) as typeof tempMiddleware + .prepend(...(middleware?.prepend ?? [])) as any }, enhancers: (gde) => gde({ autoBatch: false, }), - }) - - type StoreType = EnhancedStore< - { - api: ReturnType - } & { - [K in keyof R]: ReturnType - }, - AnyAction, - ReturnType extends EnhancedStore - ? M - : never - > + }) as StoreType - const initialStore = getStore() as StoreType + const initialStore = getStore() const refObj = { api, store: initialStore, @@ -250,7 +250,7 @@ export function setupApiStore< if (!options.withoutTestLifecycles) { beforeEach(() => { - const store = getStore() as StoreType + const store = getStore() refObj.store = store refObj.wrapper = withProvider(store) if (!options.withoutListeners) { From abc19322951bee3ab6e7e42bac32595293915752 Mon Sep 17 00:00:00 2001 From: Ben Durrant Date: Sat, 6 May 2023 00:09:43 +0100 Subject: [PATCH 167/412] import MiddlewareArray from internal --- packages/toolkit/src/tests/getDefaultMiddleware.test.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/toolkit/src/tests/getDefaultMiddleware.test.ts b/packages/toolkit/src/tests/getDefaultMiddleware.test.ts index 2ece584dc5..db190b43f3 100644 --- a/packages/toolkit/src/tests/getDefaultMiddleware.test.ts +++ b/packages/toolkit/src/tests/getDefaultMiddleware.test.ts @@ -7,13 +7,14 @@ import type { ThunkDispatch, Dispatch, } from '@reduxjs/toolkit' -import { MiddlewareArray, configureStore } from '@reduxjs/toolkit' +import { configureStore } from '@reduxjs/toolkit' import { thunk } from 'redux-thunk' import type { ThunkMiddleware } from 'redux-thunk' import { expectType } from './helpers' import { buildGetDefaultMiddleware } from '@internal/getDefaultMiddleware' +import { MiddlewareArray } from '@internal/utils' const getDefaultMiddleware = buildGetDefaultMiddleware() From 23f143a5815c36e38c401746f5c8d2b669033a63 Mon Sep 17 00:00:00 2001 From: Ben Durrant Date: Sat, 6 May 2023 01:00:24 +0100 Subject: [PATCH 168/412] disable batching for cacheCollection's store too --- packages/toolkit/src/query/tests/cacheCollection.test.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/packages/toolkit/src/query/tests/cacheCollection.test.ts b/packages/toolkit/src/query/tests/cacheCollection.test.ts index 802c9ff7c9..68500c9d97 100644 --- a/packages/toolkit/src/query/tests/cacheCollection.test.ts +++ b/packages/toolkit/src/query/tests/cacheCollection.test.ts @@ -156,6 +156,10 @@ function storeForApi< gdm({ serializableCheck: false, immutableCheck: false }).concat( api.middleware ), + enhancers: (gde) => + gde({ + autoBatch: false, + }), }) let hadQueries = false store.subscribe(() => { From 7c34b066d12f3fb02b1263b9e07af820db9a5d09 Mon Sep 17 00:00:00 2001 From: Ben Durrant Date: Sat, 6 May 2023 01:12:50 +0100 Subject: [PATCH 169/412] only test from TS 4.7 onwards --- .github/workflows/tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index ac983e31c1..3ecd65c457 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -105,7 +105,7 @@ jobs: fail-fast: false matrix: node: ['16.x'] - ts: ['4.1', '4.2', '4.3', '4.4', '4.5', '4.6', '4.7', '4.8', '4.9.2-rc'] + ts: ['4.7', '4.8', '4.9', '5.0'] steps: - name: Checkout repo uses: actions/checkout@v2 From bb78f92e867f378e4a626b23dff6ab80922fddf1 Mon Sep 17 00:00:00 2001 From: Ben Durrant Date: Sat, 6 May 2023 01:14:12 +0100 Subject: [PATCH 170/412] Change type tests to test from 4.7 to 5.0 --- .github/workflows/tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index ac983e31c1..3ecd65c457 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -105,7 +105,7 @@ jobs: fail-fast: false matrix: node: ['16.x'] - ts: ['4.1', '4.2', '4.3', '4.4', '4.5', '4.6', '4.7', '4.8', '4.9.2-rc'] + ts: ['4.7', '4.8', '4.9', '5.0'] steps: - name: Checkout repo uses: actions/checkout@v2 From a474da3fbecd9e08502691cf2c8943cdf5adb4c9 Mon Sep 17 00:00:00 2001 From: Ben Durrant Date: Sat, 6 May 2023 01:33:51 +0100 Subject: [PATCH 171/412] undo ugliness --- packages/toolkit/src/query/tests/helpers.tsx | 34 ++++++++++---------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/packages/toolkit/src/query/tests/helpers.tsx b/packages/toolkit/src/query/tests/helpers.tsx index d5d202940a..94f56b0e0c 100644 --- a/packages/toolkit/src/query/tests/helpers.tsx +++ b/packages/toolkit/src/query/tests/helpers.tsx @@ -207,19 +207,6 @@ export function setupApiStore< } } = {} ) { - type State = { - api: ReturnType - } & { - [K in keyof R]: ReturnType - } - type StoreType = EnhancedStore< - State, - AnyAction, - EnhancerArray< - [StoreEnhancer<{ dispatch: ThunkDispatch }>] - > - > - const { middleware } = options const getStore = () => configureStore({ @@ -232,15 +219,28 @@ export function setupApiStore< return tempMiddleware .concat(...(middleware?.concat ?? [])) - .prepend(...(middleware?.prepend ?? [])) as any + .prepend(...(middleware?.prepend ?? [])) as typeof tempMiddleware }, enhancers: (gde) => gde({ autoBatch: false, }), - }) as StoreType + }) + + type State = { + api: ReturnType + } & { + [K in keyof R]: ReturnType + } + type StoreType = EnhancedStore< + State, + AnyAction, + ReturnType extends EnhancedStore + ? E + : [] + > - const initialStore = getStore() + const initialStore = getStore() as StoreType const refObj = { api, store: initialStore, @@ -250,7 +250,7 @@ export function setupApiStore< if (!options.withoutTestLifecycles) { beforeEach(() => { - const store = getStore() + const store = getStore() as StoreType refObj.store = store refObj.wrapper = withProvider(store) if (!options.withoutListeners) { From e9dfc9384fb2f92eba75335b8b6f293c3c469269 Mon Sep 17 00:00:00 2001 From: Ben Durrant Date: Sun, 7 May 2023 20:02:19 +0100 Subject: [PATCH 172/412] fix test --- .../actionCreatorInvariantMiddleware.test.ts | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/packages/toolkit/src/tests/actionCreatorInvariantMiddleware.test.ts b/packages/toolkit/src/tests/actionCreatorInvariantMiddleware.test.ts index bf65a01117..65f83ee063 100644 --- a/packages/toolkit/src/tests/actionCreatorInvariantMiddleware.test.ts +++ b/packages/toolkit/src/tests/actionCreatorInvariantMiddleware.test.ts @@ -1,11 +1,11 @@ import type { ActionCreatorInvariantMiddlewareOptions } from '@internal/actionCreatorInvariantMiddleware' import { getMessage } from '@internal/actionCreatorInvariantMiddleware' import { createActionCreatorInvariantMiddleware } from '@internal/actionCreatorInvariantMiddleware' -import type { Dispatch, MiddlewareAPI } from '@reduxjs/toolkit' +import type { MiddlewareAPI } from '@reduxjs/toolkit' import { createAction } from '@reduxjs/toolkit' describe('createActionCreatorInvariantMiddleware', () => { - const consoleSpy = jest.spyOn(console, 'warn').mockImplementation(() => {}) + const consoleSpy = vi.spyOn(console, 'warn').mockImplementation(() => {}) afterEach(() => { consoleSpy.mockClear() @@ -17,17 +17,14 @@ describe('createActionCreatorInvariantMiddleware', () => { const dummyAction = createAction('aSlice/anAction') it('sends the action through the middleware chain', () => { - const next: Dispatch = (action) => ({ - ...action, - returned: true, - }) + const next = vi.fn() const dispatch = createActionCreatorInvariantMiddleware()( {} as MiddlewareAPI )(next) + dispatch({ type: 'SOME_ACTION' }) - expect(dispatch(dummyAction())).toEqual({ - ...dummyAction(), - returned: true, + expect(next).toHaveBeenCalledWith({ + type: 'SOME_ACTION', }) }) From 3b14c077bc11a673daa61ed31641d4df47b6c8d6 Mon Sep 17 00:00:00 2001 From: Ben Durrant Date: Sun, 7 May 2023 20:11:41 +0100 Subject: [PATCH 173/412] move action creator middleware back inside dev-only middleware --- packages/toolkit/src/getDefaultMiddleware.ts | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/packages/toolkit/src/getDefaultMiddleware.ts b/packages/toolkit/src/getDefaultMiddleware.ts index 9381a2c319..12ce3ad9e8 100644 --- a/packages/toolkit/src/getDefaultMiddleware.ts +++ b/packages/toolkit/src/getDefaultMiddleware.ts @@ -96,17 +96,17 @@ export const buildGetDefaultMiddleware = (): GetDefaultMiddleware => createSerializableStateInvariantMiddleware(serializableOptions) ) } - } - if (actionCreatorCheck) { - let actionCreatorOptions: ActionCreatorInvariantMiddlewareOptions = {} + if (actionCreatorCheck) { + let actionCreatorOptions: ActionCreatorInvariantMiddlewareOptions = {} - if (!isBoolean(actionCreatorCheck)) { - actionCreatorOptions = actionCreatorCheck - } + if (!isBoolean(actionCreatorCheck)) { + actionCreatorOptions = actionCreatorCheck + } - middlewareArray.unshift( - createActionCreatorInvariantMiddleware(actionCreatorOptions) - ) + middlewareArray.unshift( + createActionCreatorInvariantMiddleware(actionCreatorOptions) + ) + } } return middlewareArray as any From 1c1d195b057fd855954969d6029ec05c9ce65704 Mon Sep 17 00:00:00 2001 From: Ben Durrant Date: Sun, 7 May 2023 21:00:17 +0100 Subject: [PATCH 174/412] Use Record instead of Dictionary --- docs/api/createEntityAdapter.mdx | 21 ++++--------------- packages/toolkit/src/entities/models.ts | 18 ++-------------- .../toolkit/src/entities/state_selectors.ts | 10 +++------ 3 files changed, 9 insertions(+), 40 deletions(-) diff --git a/docs/api/createEntityAdapter.mdx b/docs/api/createEntityAdapter.mdx index 0cfee25b9e..10eb7afbe1 100644 --- a/docs/api/createEntityAdapter.mdx +++ b/docs/api/createEntityAdapter.mdx @@ -118,19 +118,11 @@ export type Comparer = (a: T, b: T) => number export type IdSelector = (model: T) => EntityId -export interface DictionaryNum { - [id: number]: T | undefined -} - -export interface Dictionary extends DictionaryNum { - [id: string]: T | undefined -} - export type Update = { id: EntityId; changes: Partial } export interface EntityState { ids: EntityId[] - entities: Dictionary + entities: Record } export interface EntityDefinition { @@ -183,7 +175,7 @@ export interface EntityStateAdapter { export interface EntitySelectors { selectIds: (state: V) => EntityId[] - selectEntities: (state: V) => Dictionary + selectEntities: (state: V) => Record selectAll: (state: V) => T[] selectTotal: (state: V) => number selectById: (state: V, id: EntityId) => T | undefined @@ -228,7 +220,6 @@ All three options will insert _new_ entities into the list. However they differ ::: - Each method has a signature that looks like: ```ts no-transpile @@ -358,12 +349,8 @@ const booksSlice = createSlice({ }, }) -const { - bookAdded, - booksLoading, - booksReceived, - bookUpdated, -} = booksSlice.actions +const { bookAdded, booksLoading, booksReceived, bookUpdated } = + booksSlice.actions const store = configureStore({ reducer: { diff --git a/packages/toolkit/src/entities/models.ts b/packages/toolkit/src/entities/models.ts index e57c4aee9a..67ed9cb31e 100644 --- a/packages/toolkit/src/entities/models.ts +++ b/packages/toolkit/src/entities/models.ts @@ -16,20 +16,6 @@ export type Comparer = (a: T, b: T) => number */ export type IdSelector = (model: T) => EntityId -/** - * @public - */ -export interface DictionaryNum { - [id: number]: T | undefined -} - -/** - * @public - */ -export interface Dictionary extends DictionaryNum { - [id: string]: T | undefined -} - /** * @public */ @@ -40,7 +26,7 @@ export type Update = { id: EntityId; changes: Partial } */ export interface EntityState { ids: EntityId[] - entities: Dictionary + entities: Record } /** @@ -150,7 +136,7 @@ export interface EntityStateAdapter { */ export interface EntitySelectors { selectIds: (state: V) => EntityId[] - selectEntities: (state: V) => Dictionary + selectEntities: (state: V) => Record selectAll: (state: V) => T[] selectTotal: (state: V) => number selectById: (state: V, id: EntityId) => T | undefined diff --git a/packages/toolkit/src/entities/state_selectors.ts b/packages/toolkit/src/entities/state_selectors.ts index 46f59d3d9e..1a14d12017 100644 --- a/packages/toolkit/src/entities/state_selectors.ts +++ b/packages/toolkit/src/entities/state_selectors.ts @@ -1,11 +1,6 @@ import type { Selector } from 'reselect' import { createDraftSafeSelector } from '../createDraftSafeSelector' -import type { - EntityState, - EntitySelectors, - Dictionary, - EntityId, -} from './models' +import type { EntityState, EntitySelectors, EntityId } from './models' export function createSelectorsFactory() { function getSelectors(): EntitySelectors> @@ -27,7 +22,8 @@ export function createSelectorsFactory() { const selectId = (_: unknown, id: EntityId) => id - const selectById = (entities: Dictionary, id: EntityId) => entities[id] + const selectById = (entities: Record, id: EntityId) => + entities[id] const selectTotal = createDraftSafeSelector(selectIds, (ids) => ids.length) From d2100de7ba2b9e4c2a19fcbed4682a79bb897c11 Mon Sep 17 00:00:00 2001 From: Ben Durrant Date: Sun, 7 May 2023 21:06:16 +0100 Subject: [PATCH 175/412] rm Dictionary export --- packages/toolkit/src/index.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/toolkit/src/index.ts b/packages/toolkit/src/index.ts index dc1f333730..aa8754ff31 100644 --- a/packages/toolkit/src/index.ts +++ b/packages/toolkit/src/index.ts @@ -100,7 +100,6 @@ export { MiddlewareArray, EnhancerArray } from './utils' export { createEntityAdapter } from './entities/create_adapter' export type { - Dictionary, EntityState, EntityAdapter, EntitySelectors, From 616be07293901046359a3ff05da94075c62de992 Mon Sep 17 00:00:00 2001 From: Ben Durrant Date: Sun, 7 May 2023 21:16:09 +0100 Subject: [PATCH 176/412] use workaround to match UncheckedIndexedAccess setting --- packages/toolkit/src/entities/index.ts | 1 - packages/toolkit/src/entities/models.ts | 4 ++-- packages/toolkit/src/tsHelpers.ts | 10 ++++++++++ 3 files changed, 12 insertions(+), 3 deletions(-) diff --git a/packages/toolkit/src/entities/index.ts b/packages/toolkit/src/entities/index.ts index 9e2292a24c..e258527474 100644 --- a/packages/toolkit/src/entities/index.ts +++ b/packages/toolkit/src/entities/index.ts @@ -1,6 +1,5 @@ export { createEntityAdapter } from './create_adapter' export type { - Dictionary, EntityState, EntityAdapter, Update, diff --git a/packages/toolkit/src/entities/models.ts b/packages/toolkit/src/entities/models.ts index 67ed9cb31e..7f9f5b4df5 100644 --- a/packages/toolkit/src/entities/models.ts +++ b/packages/toolkit/src/entities/models.ts @@ -1,5 +1,5 @@ import type { PayloadAction } from '../createAction' -import type { IsAny } from '../tsHelpers' +import type { IsAny, UncheckedIndexedAccess } from '../tsHelpers' /** * @public @@ -139,7 +139,7 @@ export interface EntitySelectors { selectEntities: (state: V) => Record selectAll: (state: V) => T[] selectTotal: (state: V) => number - selectById: (state: V, id: EntityId) => T | undefined + selectById: (state: V, id: EntityId) => UncheckedIndexedAccess } /** diff --git a/packages/toolkit/src/tsHelpers.ts b/packages/toolkit/src/tsHelpers.ts index 1b331c9962..7fa472c4cd 100644 --- a/packages/toolkit/src/tsHelpers.ts +++ b/packages/toolkit/src/tsHelpers.ts @@ -207,3 +207,13 @@ export type Id = { [K in keyof T]: T[K] } & {} export type Tail = T extends [any, ...infer Tail] ? Tail : never + +declare const record: Record + +const value = record[0] + +export type UncheckedIndexedAccess = IfMaybeUndefined< + typeof value, + T | undefined, + T +> From e8dc3ade1aea9e218c27579f88351f0f5a6a2432 Mon Sep 17 00:00:00 2001 From: Ben Durrant Date: Sun, 7 May 2023 21:20:28 +0100 Subject: [PATCH 177/412] code golf --- packages/toolkit/src/tsHelpers.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/packages/toolkit/src/tsHelpers.ts b/packages/toolkit/src/tsHelpers.ts index 7fa472c4cd..419465eb36 100644 --- a/packages/toolkit/src/tsHelpers.ts +++ b/packages/toolkit/src/tsHelpers.ts @@ -208,9 +208,7 @@ export type Tail = T extends [any, ...infer Tail] ? Tail : never -declare const record: Record - -const value = record[0] +const value = ({} as Record)[0] export type UncheckedIndexedAccess = IfMaybeUndefined< typeof value, From 9d5adf1ca30407f74fcecf5664257bcb6b567878 Mon Sep 17 00:00:00 2001 From: Ben Durrant Date: Sun, 7 May 2023 22:02:52 +0100 Subject: [PATCH 178/412] Remove toString override from action creators, in favour of explicit .type field. --- docs/api/createAction.mdx | 17 ++++----- docs/introduction/getting-started.md | 2 +- docs/usage/usage-guide.md | 36 ++++++------------ packages/toolkit/src/createAction.ts | 26 +------------ packages/toolkit/src/index.ts | 1 - .../toolkit/src/tests/createAction.test.ts | 38 +++++++++++-------- 6 files changed, 44 insertions(+), 76 deletions(-) diff --git a/docs/api/createAction.mdx b/docs/api/createAction.mdx index 9eaded6bec..86aefef448 100644 --- a/docs/api/createAction.mdx +++ b/docs/api/createAction.mdx @@ -31,7 +31,7 @@ const action = increment(3) // { type: 'counter/increment', payload: 3 } ``` -The `createAction` helper combines these two declarations into one. It takes an action type and returns an action creator for that type. The action creator can be called either without arguments or with a `payload` to be attached to the action. Also, the action creator overrides [toString()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/toString) so that the action type becomes its string representation. +The `createAction` helper combines these two declarations into one. It takes an action type and returns an action creator for that type. The action creator can be called either without arguments or with a `payload` to be attached to the action. ```ts import { createAction } from '@reduxjs/toolkit' @@ -44,10 +44,7 @@ let action = increment() action = increment(3) // returns { type: 'counter/increment', payload: 3 } -console.log(increment.toString()) -// 'counter/increment' - -console.log(`The action type is: ${increment}`) +console.log(`The action type is: ${increment.type}`) // 'The action type is: counter/increment' ``` @@ -89,7 +86,7 @@ If provided, all arguments from the action creator will be passed to the prepare ## Usage with createReducer() -Because of their `toString()` override, action creators returned by `createAction()` can be used directly as keys for the case reducers passed to [createReducer()](createReducer.mdx). +Action creators can be passed directly to `addCase` in a [createReducer()](createReducer.mdx) build callback. ```ts import { createAction, createReducer } from '@reduxjs/toolkit' @@ -103,21 +100,23 @@ const counterReducer = createReducer(0, (builder) => { }) ``` + + ## Non-String Action Types In principle, Redux lets you use any kind of value as an action type. Instead of strings, you could theoretically use numbers, [symbols](https://developer.mozilla.org/en-US/docs/Glossary/Symbol), or anything else ([although it's recommended that the value should at least be serializable](https://redux.js.org/faq/actions#why-should-type-be-a-string-or-at-least-serializable-why-should-my-action-types-be-constants)). -However, Redux Toolkit rests on the assumption that you use string action types. Specifically, some of its features rely on the fact that with strings, the `toString()` method of an `createAction()` action creator returns the matching action type. This is not the case for non-string action types because `toString()` will return the string-converted type value rather than the type itself. +However, Redux Toolkit rests on the assumption that you use string action types. ```js const INCREMENT = Symbol('increment') const increment = createAction(INCREMENT) -increment.toString() +increment.type.toString() // returns the string 'Symbol(increment)', // not the INCREMENT symbol itself -increment.toString() === INCREMENT +increment.type.toString() === INCREMENT // false ``` diff --git a/docs/introduction/getting-started.md b/docs/introduction/getting-started.md index d513f4e178..3d0a9c5777 100644 --- a/docs/introduction/getting-started.md +++ b/docs/introduction/getting-started.md @@ -94,7 +94,7 @@ Redux Toolkit includes these APIs: - [`configureStore()`](../api/configureStore.mdx): wraps `createStore` to provide simplified configuration options and good defaults. It can automatically combine your slice reducers, adds whatever Redux middleware you supply, includes `redux-thunk` by default, and enables use of the Redux DevTools Extension. - [`createReducer()`](../api/createReducer.mdx): that lets you supply a lookup table of action types to case reducer functions, rather than writing switch statements. In addition, it automatically uses the [`immer` library](https://github.com/immerjs/immer) to let you write simpler immutable updates with normal mutative code, like `state.todos[3].completed = true`. -- [`createAction()`](../api/createAction.mdx): generates an action creator function for the given action type string. The function itself has `toString()` defined, so that it can be used in place of the type constant. +- [`createAction()`](../api/createAction.mdx): generates an action creator function for the given action type string. - [`createSlice()`](../api/createSlice.mdx): accepts an object of reducer functions, a slice name, and an initial state value, and automatically generates a slice reducer with corresponding action creators and action types. - [`createAsyncThunk`](../api/createAsyncThunk.mdx): accepts an action type string and a function that returns a promise, and generates a thunk that dispatches `pending/fulfilled/rejected` action types based on that promise - [`createEntityAdapter`](../api/createEntityAdapter.mdx): generates a set of reusable reducers and selectors to manage normalized data in the store diff --git a/docs/usage/usage-guide.md b/docs/usage/usage-guide.md index 6b465ff719..e61724ac9a 100644 --- a/docs/usage/usage-guide.md +++ b/docs/usage/usage-guide.md @@ -279,24 +279,16 @@ addTodo({ text: 'Buy milk' }) ### Using Action Creators as Action Types -Redux reducers need to look for specific action types to determine how they should update their state. Normally, this is done by defining action type strings and action creator functions separately. Redux Toolkit `createAction` function uses a couple tricks to make this easier. - -First, `createAction` overrides the `toString()` method on the action creators it generates. **This means that the action creator itself can be used as the "action type" reference in some places**, such as the keys provided to `builder.addCase` or the `createReducer` object notation. - -Second, the action type is also defined as a `type` field on the action creator. +Redux reducers need to look for specific action types to determine how they should update their state. Normally, this is done by defining action type strings and action creator functions separately. Redux Toolkit `createAction` function make this easier, by defining the action type as a `type` field on the action creator. ```js const actionCreator = createAction('SOME_ACTION_TYPE') -console.log(actionCreator.toString()) -// "SOME_ACTION_TYPE" - console.log(actionCreator.type) // "SOME_ACTION_TYPE" const reducer = createReducer({}, (builder) => { - // actionCreator.toString() will automatically be called here - // also, if you use TypeScript, the action type will be correctly inferred + // if you use TypeScript, the action type will be correctly inferred builder.addCase(actionCreator, (state, action) => {}) // Or, you can reference the .type field: @@ -307,7 +299,7 @@ const reducer = createReducer({}, (builder) => { This means you don't have to write or use a separate action type variable, or repeat the name and value of an action type like `const SOME_ACTION_TYPE = "SOME_ACTION_TYPE"`. -Unfortunately, the implicit conversion to a string doesn't happen for switch statements. If you want to use one of these action creators in a switch statement, you need to call `actionCreator.toString()` yourself: +If you want to use one of these action creators in a switch statement, you need to call `actionCreator.type` yourself: ```js const actionCreator = createAction('SOME_ACTION_TYPE') @@ -319,10 +311,6 @@ const reducer = (state = {}, action) => { break } // CORRECT: this will work as expected - case actionCreator.toString(): { - break - } - // CORRECT: this will also work right case actionCreator.type: { break } @@ -330,8 +318,6 @@ const reducer = (state = {}, action) => { } ``` -If you are using Redux Toolkit with TypeScript, note that the TypeScript compiler may not accept the implicit `toString()` conversion when the action creator is used as an object key. In that case, you may need to either manually cast it to a string (`actionCreator as string`), or use the `.type` field as the key. - ## Creating Slices of State Redux state is typically organized into "slices", defined by the reducers that are passed to `combineReducers`: @@ -619,17 +605,17 @@ A typical implementation might look like: ```js const getRepoDetailsStarted = () => ({ - type: "repoDetails/fetchStarted" + type: 'repoDetails/fetchStarted', }) const getRepoDetailsSuccess = (repoDetails) => ({ - type: "repoDetails/fetchSucceeded", - payload: repoDetails + type: 'repoDetails/fetchSucceeded', + payload: repoDetails, }) const getRepoDetailsFailed = (error) => ({ - type: "repoDetails/fetchFailed", - error + type: 'repoDetails/fetchFailed', + error, }) -const fetchIssuesCount = (org, repo) => async dispatch => { +const fetchIssuesCount = (org, repo) => async (dispatch) => { dispatch(getRepoDetailsStarted()) try { const repoDetails = await getRepoDetails(org, repo) @@ -1118,11 +1104,11 @@ It is also strongly recommended to blacklist any api(s) that you have configured ```ts const persistConfig = { - key: "root", + key: 'root', version: 1, storage, blacklist: [pokemonApi.reducerPath], -}; +} ``` See [Redux Toolkit #121: How to use this with Redux-Persist?](https://github.com/reduxjs/redux-toolkit/issues/121) and [Redux-Persist #988: non-serializable value error](https://github.com/rt2zz/redux-persist/issues/988#issuecomment-552242978) for further discussion. diff --git a/packages/toolkit/src/createAction.ts b/packages/toolkit/src/createAction.ts index 155672c327..638bddbfb6 100644 --- a/packages/toolkit/src/createAction.ts +++ b/packages/toolkit/src/createAction.ts @@ -224,9 +224,7 @@ export type PayloadActionCreator< /** * A utility function to create an action creator for the given action type * string. The action creator accepts a single argument, which will be included - * in the action object as a field called payload. The action creator function - * will also have its toString() overridden so that it returns the action type, - * allowing it to be used in reducer logic that is looking for that action type. + * in the action object as a field called payload. * * @param type The action type to use for created actions. * @param prepare (optional) a method that takes any number of arguments and returns { payload } or { payload, meta }. @@ -241,9 +239,7 @@ export function createAction

( /** * A utility function to create an action creator for the given action type * string. The action creator accepts a single argument, which will be included - * in the action object as a field called payload. The action creator function - * will also have its toString() overridden so that it returns the action type, - * allowing it to be used in reducer logic that is looking for that action type. + * in the action object as a field called payload. * * @param type The action type to use for created actions. * @param prepare (optional) a method that takes any number of arguments and returns { payload } or { payload, meta }. @@ -277,8 +273,6 @@ export function createAction(type: string, prepareAction?: Function): any { return { type, payload: args[0] } } - actionCreator.toString = () => `${type}` - actionCreator.type = type actionCreator.match = (action: Action): action is PayloadAction => @@ -328,22 +322,6 @@ function isValidKey(key: string) { return ['type', 'payload', 'error', 'meta'].indexOf(key) > -1 } -/** - * Returns the action type of the actions created by the passed - * `createAction()`-generated action creator (arbitrary action creators - * are not supported). - * - * @param action The action creator whose action type to get. - * @returns The action type used by the action creator. - * - * @public - */ -export function getType( - actionCreator: PayloadActionCreator -): T { - return `${actionCreator}` as T -} - // helper types for more readable typings type IfPrepareActionMethodProvided< diff --git a/packages/toolkit/src/index.ts b/packages/toolkit/src/index.ts index dc1f333730..eee4a87bd1 100644 --- a/packages/toolkit/src/index.ts +++ b/packages/toolkit/src/index.ts @@ -31,7 +31,6 @@ export type { DevToolsEnhancerOptions } from './devtoolsExtension' export { // js createAction, - getType, isAction, isActionCreator, isFSA as isFluxStandardAction, diff --git a/packages/toolkit/src/tests/createAction.test.ts b/packages/toolkit/src/tests/createAction.test.ts index 5b481b945c..adc4ce6b93 100644 --- a/packages/toolkit/src/tests/createAction.test.ts +++ b/packages/toolkit/src/tests/createAction.test.ts @@ -1,4 +1,4 @@ -import { createAction, getType, isAction } from '@reduxjs/toolkit' +import { createAction, isAction, isActionCreator } from '@reduxjs/toolkit' describe('createAction', () => { it('should create an action', () => { @@ -9,13 +9,6 @@ describe('createAction', () => { }) }) - describe('when stringifying action', () => { - it('should return the action type', () => { - const actionCreator = createAction('A_TYPE') - expect(`${actionCreator}`).toEqual('A_TYPE') - }) - }) - describe('when passing a prepareAction method only returning a payload', () => { it('should use the payload returned from the prepareAction method', () => { const actionCreator = createAction('A_TYPE', (a: number) => ({ @@ -122,12 +115,13 @@ describe('createAction', () => { }) }) +const actionCreator = createAction('anAction') + +class Action { + type = 'totally an action' +} describe('isAction', () => { it('should only return true for plain objects with a type property', () => { - const actionCreator = createAction('anAction') - class Action { - type = 'totally an action' - } const testCases: [action: unknown, expected: boolean][] = [ [{ type: 'an action' }, true], [{ type: 'more props', extra: true }, true], @@ -143,9 +137,21 @@ describe('isAction', () => { }) }) -describe('getType', () => { - it('should return the action type', () => { - const actionCreator = createAction('A_TYPE') - expect(getType(actionCreator)).toEqual('A_TYPE') +describe('isActionCreator', () => { + it('should only return true for action creators', () => { + expect(isActionCreator(actionCreator)).toBe(true) + const notActionCreators = [ + { type: 'an action' }, + { type: 'more props', extra: true }, + actionCreator(), + Promise.resolve({ type: 'an action' }), + new Action(), + false, + 'a string', + false, + ] + for (const notActionCreator of notActionCreators) { + expect(isActionCreator(notActionCreator)).toBe(false) + } }) }) From 5a10068144521f55278602c7f8bf73fdc7e1ff19 Mon Sep 17 00:00:00 2001 From: Ben Durrant Date: Sun, 7 May 2023 22:18:19 +0100 Subject: [PATCH 179/412] Revert "code golf" This reverts commit e8dc3ade1aea9e218c27579f88351f0f5a6a2432. --- packages/toolkit/src/tsHelpers.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/toolkit/src/tsHelpers.ts b/packages/toolkit/src/tsHelpers.ts index 419465eb36..7fa472c4cd 100644 --- a/packages/toolkit/src/tsHelpers.ts +++ b/packages/toolkit/src/tsHelpers.ts @@ -208,7 +208,9 @@ export type Tail = T extends [any, ...infer Tail] ? Tail : never -const value = ({} as Record)[0] +declare const record: Record + +const value = record[0] export type UncheckedIndexedAccess = IfMaybeUndefined< typeof value, From 5a28ae832850471b527736455eb010bdc920c39a Mon Sep 17 00:00:00 2001 From: Ben Durrant Date: Sun, 7 May 2023 22:21:42 +0100 Subject: [PATCH 180/412] give record a runtime value --- packages/toolkit/src/tsHelpers.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/toolkit/src/tsHelpers.ts b/packages/toolkit/src/tsHelpers.ts index 7fa472c4cd..c33cab19bd 100644 --- a/packages/toolkit/src/tsHelpers.ts +++ b/packages/toolkit/src/tsHelpers.ts @@ -208,7 +208,7 @@ export type Tail = T extends [any, ...infer Tail] ? Tail : never -declare const record: Record +const record: Record = {} const value = record[0] From dbd789862160fde089892f27ffb333e3506f2124 Mon Sep 17 00:00:00 2001 From: Ben Durrant Date: Sun, 7 May 2023 22:31:51 +0100 Subject: [PATCH 181/412] remove UncheckedIndexedAccess hack, as it doesn't work after compilation --- packages/toolkit/src/entities/models.ts | 4 ++-- packages/toolkit/src/tsHelpers.ts | 10 ---------- 2 files changed, 2 insertions(+), 12 deletions(-) diff --git a/packages/toolkit/src/entities/models.ts b/packages/toolkit/src/entities/models.ts index 7f9f5b4df5..67ed9cb31e 100644 --- a/packages/toolkit/src/entities/models.ts +++ b/packages/toolkit/src/entities/models.ts @@ -1,5 +1,5 @@ import type { PayloadAction } from '../createAction' -import type { IsAny, UncheckedIndexedAccess } from '../tsHelpers' +import type { IsAny } from '../tsHelpers' /** * @public @@ -139,7 +139,7 @@ export interface EntitySelectors { selectEntities: (state: V) => Record selectAll: (state: V) => T[] selectTotal: (state: V) => number - selectById: (state: V, id: EntityId) => UncheckedIndexedAccess + selectById: (state: V, id: EntityId) => T | undefined } /** diff --git a/packages/toolkit/src/tsHelpers.ts b/packages/toolkit/src/tsHelpers.ts index c33cab19bd..1b331c9962 100644 --- a/packages/toolkit/src/tsHelpers.ts +++ b/packages/toolkit/src/tsHelpers.ts @@ -207,13 +207,3 @@ export type Id = { [K in keyof T]: T[K] } & {} export type Tail = T extends [any, ...infer Tail] ? Tail : never - -const record: Record = {} - -const value = record[0] - -export type UncheckedIndexedAccess = IfMaybeUndefined< - typeof value, - T | undefined, - T -> From 2ac6598ea6229d3dfa334c68a2956c26aa00bcb2 Mon Sep 17 00:00:00 2001 From: Ben Durrant Date: Sun, 7 May 2023 23:38:11 +0100 Subject: [PATCH 182/412] try some weirdness --- packages/toolkit/src/entities/models.ts | 5 ++++- packages/toolkit/src/tsHelpers.ts | 8 ++++++++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/packages/toolkit/src/entities/models.ts b/packages/toolkit/src/entities/models.ts index 67ed9cb31e..beed70adc8 100644 --- a/packages/toolkit/src/entities/models.ts +++ b/packages/toolkit/src/entities/models.ts @@ -139,7 +139,10 @@ export interface EntitySelectors { selectEntities: (state: V) => Record selectAll: (state: V) => T[] selectTotal: (state: V) => number - selectById: (state: V, id: EntityId) => T | undefined + selectById: ( + state: V, + id: EntityId + ) => import('@reduxjs/toolkit/dist/tsHelpers').UncheckedIndexedAccess } /** diff --git a/packages/toolkit/src/tsHelpers.ts b/packages/toolkit/src/tsHelpers.ts index 1b331c9962..cf1527ac48 100644 --- a/packages/toolkit/src/tsHelpers.ts +++ b/packages/toolkit/src/tsHelpers.ts @@ -207,3 +207,11 @@ export type Id = { [K in keyof T]: T[K] } & {} export type Tail = T extends [any, ...infer Tail] ? Tail : never + +const value = ({} as Record).a + +export type UncheckedIndexedAccess = IfMaybeUndefined< + typeof value, + T | undefined, + T +> From 5a1831ef482c1e306d82456287e1494afe8ba167 Mon Sep 17 00:00:00 2001 From: Ben Durrant Date: Sun, 7 May 2023 23:39:59 +0100 Subject: [PATCH 183/412] use a function --- packages/toolkit/src/tsHelpers.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/toolkit/src/tsHelpers.ts b/packages/toolkit/src/tsHelpers.ts index cf1527ac48..de4d988279 100644 --- a/packages/toolkit/src/tsHelpers.ts +++ b/packages/toolkit/src/tsHelpers.ts @@ -208,10 +208,10 @@ export type Tail = T extends [any, ...infer Tail] ? Tail : never -const value = ({} as Record).a +const getValue = () => (({} as Record).a) export type UncheckedIndexedAccess = IfMaybeUndefined< - typeof value, + ReturnType, T | undefined, T > From fec6339c6433303566fe0a2119bed5429a8e5457 Mon Sep 17 00:00:00 2001 From: Ben Durrant Date: Sun, 7 May 2023 23:45:22 +0100 Subject: [PATCH 184/412] src not dist --- packages/toolkit/src/entities/models.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/toolkit/src/entities/models.ts b/packages/toolkit/src/entities/models.ts index beed70adc8..ad46f59741 100644 --- a/packages/toolkit/src/entities/models.ts +++ b/packages/toolkit/src/entities/models.ts @@ -142,7 +142,7 @@ export interface EntitySelectors { selectById: ( state: V, id: EntityId - ) => import('@reduxjs/toolkit/dist/tsHelpers').UncheckedIndexedAccess + ) => import('@reduxjs/toolkit/src/tsHelpers').UncheckedIndexedAccess } /** From 947c5a9741eec30ac7796b84a23d6274657c8592 Mon Sep 17 00:00:00 2001 From: Ben Durrant Date: Mon, 8 May 2023 00:06:28 +0100 Subject: [PATCH 185/412] move UncheckedIndexedAccess to separate file --- packages/toolkit/src/entities/models.ts | 2 +- packages/toolkit/src/tsHelpers.ts | 8 -------- packages/toolkit/src/userland.ts | 11 +++++++++++ 3 files changed, 12 insertions(+), 9 deletions(-) create mode 100644 packages/toolkit/src/userland.ts diff --git a/packages/toolkit/src/entities/models.ts b/packages/toolkit/src/entities/models.ts index ad46f59741..607bbf30dd 100644 --- a/packages/toolkit/src/entities/models.ts +++ b/packages/toolkit/src/entities/models.ts @@ -142,7 +142,7 @@ export interface EntitySelectors { selectById: ( state: V, id: EntityId - ) => import('@reduxjs/toolkit/src/tsHelpers').UncheckedIndexedAccess + ) => import('@reduxjs/toolkit/src/userland').UncheckedIndexedAccess } /** diff --git a/packages/toolkit/src/tsHelpers.ts b/packages/toolkit/src/tsHelpers.ts index de4d988279..1b331c9962 100644 --- a/packages/toolkit/src/tsHelpers.ts +++ b/packages/toolkit/src/tsHelpers.ts @@ -207,11 +207,3 @@ export type Id = { [K in keyof T]: T[K] } & {} export type Tail = T extends [any, ...infer Tail] ? Tail : never - -const getValue = () => (({} as Record).a) - -export type UncheckedIndexedAccess = IfMaybeUndefined< - ReturnType, - T | undefined, - T -> diff --git a/packages/toolkit/src/userland.ts b/packages/toolkit/src/userland.ts new file mode 100644 index 0000000000..eecdf98fa2 --- /dev/null +++ b/packages/toolkit/src/userland.ts @@ -0,0 +1,11 @@ +import type { IfMaybeUndefined } from '@reduxjs/toolkit/dist/tsHelpers' + +// helpers that should be imported like `import('@reduxjs/toolkit/src/userland')` + +const getValue = () => (({} as Record).a) + +export type UncheckedIndexedAccess = IfMaybeUndefined< + ReturnType, + T | undefined, + T +> From 7a0d01e4f91cdb0e8679fbac669f4f6e5afd0c99 Mon Sep 17 00:00:00 2001 From: Ben Durrant Date: Mon, 8 May 2023 11:22:19 +0100 Subject: [PATCH 186/412] inline IfMaybeUndefined instead of importing from dist --- packages/toolkit/src/userland.ts | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/packages/toolkit/src/userland.ts b/packages/toolkit/src/userland.ts index eecdf98fa2..76f76f9d52 100644 --- a/packages/toolkit/src/userland.ts +++ b/packages/toolkit/src/userland.ts @@ -1,11 +1,14 @@ -import type { IfMaybeUndefined } from '@reduxjs/toolkit/dist/tsHelpers' +/* +these helpers should be imported like `import('@reduxjs/toolkit/src/userland').Helper` +because they depend on remaining as a .ts file instead of being in a .d.ts +*/ -// helpers that should be imported like `import('@reduxjs/toolkit/src/userland')` +type IfMaybeUndefined = [undefined] extends [P] ? True : False -const getValue = () => (({} as Record).a) +const value = ({} as Record).a export type UncheckedIndexedAccess = IfMaybeUndefined< - ReturnType, + typeof value, T | undefined, T > From 8c07160098ecff30ed034251c6d3364fcdfce8f9 Mon Sep 17 00:00:00 2001 From: Ben Durrant Date: Mon, 8 May 2023 11:33:03 +0100 Subject: [PATCH 187/412] guard against any --- packages/toolkit/src/entities/models.ts | 9 ++++++--- packages/toolkit/src/tsHelpers.ts | 2 ++ 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/packages/toolkit/src/entities/models.ts b/packages/toolkit/src/entities/models.ts index 607bbf30dd..b07042117e 100644 --- a/packages/toolkit/src/entities/models.ts +++ b/packages/toolkit/src/entities/models.ts @@ -1,5 +1,5 @@ import type { PayloadAction } from '../createAction' -import type { IsAny } from '../tsHelpers' +import type { CastAny } from '../tsHelpers' /** * @public @@ -37,7 +37,7 @@ export interface EntityDefinition { sortComparer: false | Comparer } -export type PreventAny = IsAny, S> +export type PreventAny = CastAny> /** * @public @@ -142,7 +142,10 @@ export interface EntitySelectors { selectById: ( state: V, id: EntityId - ) => import('@reduxjs/toolkit/src/userland').UncheckedIndexedAccess + ) => CastAny< + import('@reduxjs/toolkit/src/userland').UncheckedIndexedAccess, + T + > } /** diff --git a/packages/toolkit/src/tsHelpers.ts b/packages/toolkit/src/tsHelpers.ts index 1b331c9962..ecf67c0242 100644 --- a/packages/toolkit/src/tsHelpers.ts +++ b/packages/toolkit/src/tsHelpers.ts @@ -18,6 +18,8 @@ export type IsAny = // test if we are going the left AND right path in the condition true | false extends (T extends never ? true : false) ? True : False +export type CastAny = IsAny + /** * return True if T is `unknown`, otherwise return False * taken from https://github.com/joonhocho/tsdef From 56ba355192f79dd8914294bc1987dd657614457a Mon Sep 17 00:00:00 2001 From: Ben Durrant Date: Mon, 8 May 2023 12:11:17 +0100 Subject: [PATCH 188/412] test using external package --- packages/toolkit/package.json | 3 ++- packages/toolkit/src/entities/models.ts | 9 ++------- yarn.lock | 8 ++++++++ 3 files changed, 12 insertions(+), 8 deletions(-) diff --git a/packages/toolkit/package.json b/packages/toolkit/package.json index 6aec87a2e3..57d215df66 100644 --- a/packages/toolkit/package.json +++ b/packages/toolkit/package.json @@ -107,7 +107,8 @@ "immer": "^10.0.1", "redux": "5.0.0-alpha.5", "redux-thunk": "3.0.0-alpha.3", - "reselect": "^4.1.8" + "reselect": "^4.1.8", + "uncheckedindexed": "https://github.com/EskiMojo14/uncheckedindexed" }, "peerDependencies": { "react": "^16.9.0 || ^17.0.0 || ^18", diff --git a/packages/toolkit/src/entities/models.ts b/packages/toolkit/src/entities/models.ts index b07042117e..3d03c0aaf0 100644 --- a/packages/toolkit/src/entities/models.ts +++ b/packages/toolkit/src/entities/models.ts @@ -1,3 +1,4 @@ +import type { UncheckedIndexedAccess } from 'uncheckedindexed' import type { PayloadAction } from '../createAction' import type { CastAny } from '../tsHelpers' @@ -139,13 +140,7 @@ export interface EntitySelectors { selectEntities: (state: V) => Record selectAll: (state: V) => T[] selectTotal: (state: V) => number - selectById: ( - state: V, - id: EntityId - ) => CastAny< - import('@reduxjs/toolkit/src/userland').UncheckedIndexedAccess, - T - > + selectById: (state: V, id: EntityId) => CastAny, T> } /** diff --git a/yarn.lock b/yarn.lock index e1040f2f6b..5025a4fdd9 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6793,6 +6793,7 @@ __metadata: tsup: ^6.7.0 tsx: ^3.12.2 typescript: ~4.9 + uncheckedindexed: "https://github.com/EskiMojo14/uncheckedindexed" vitest: ^0.30.1 yargs: ^15.3.1 peerDependencies: @@ -28408,6 +28409,13 @@ fsevents@^1.2.7: languageName: node linkType: hard +"uncheckedindexed@https://github.com/EskiMojo14/uncheckedindexed": + version: 0.0.1 + resolution: "uncheckedindexed@https://github.com/EskiMojo14/uncheckedindexed.git#commit=d08490cc8efa3fcf3fac05f55377681e0823c6e3" + checksum: 1cdc6ab2963da63582603e481b4fbf834ba533d8f83e3c4d2aeb682be6866327b585a1440497fd41e13c9d5196f84316dba27c8ac8168ed31d2b1f46d232c395 + languageName: node + linkType: hard + "unherit@npm:^1.0.4": version: 1.1.3 resolution: "unherit@npm:1.1.3" From 66b3f7914f64c2baaef6a84118bdc3a70d9f85bb Mon Sep 17 00:00:00 2001 From: Ben Durrant Date: Mon, 8 May 2023 12:26:31 +0100 Subject: [PATCH 189/412] remove any guard and use Id helper --- packages/toolkit/src/entities/models.ts | 4 ++-- packages/toolkit/src/userland.ts | 14 -------------- 2 files changed, 2 insertions(+), 16 deletions(-) delete mode 100644 packages/toolkit/src/userland.ts diff --git a/packages/toolkit/src/entities/models.ts b/packages/toolkit/src/entities/models.ts index 3d03c0aaf0..a3556dcd5b 100644 --- a/packages/toolkit/src/entities/models.ts +++ b/packages/toolkit/src/entities/models.ts @@ -1,6 +1,6 @@ import type { UncheckedIndexedAccess } from 'uncheckedindexed' import type { PayloadAction } from '../createAction' -import type { CastAny } from '../tsHelpers' +import type { CastAny, Id } from '../tsHelpers' /** * @public @@ -140,7 +140,7 @@ export interface EntitySelectors { selectEntities: (state: V) => Record selectAll: (state: V) => T[] selectTotal: (state: V) => number - selectById: (state: V, id: EntityId) => CastAny, T> + selectById: (state: V, id: EntityId) => Id> } /** diff --git a/packages/toolkit/src/userland.ts b/packages/toolkit/src/userland.ts deleted file mode 100644 index 76f76f9d52..0000000000 --- a/packages/toolkit/src/userland.ts +++ /dev/null @@ -1,14 +0,0 @@ -/* -these helpers should be imported like `import('@reduxjs/toolkit/src/userland').Helper` -because they depend on remaining as a .ts file instead of being in a .d.ts -*/ - -type IfMaybeUndefined = [undefined] extends [P] ? True : False - -const value = ({} as Record).a - -export type UncheckedIndexedAccess = IfMaybeUndefined< - typeof value, - T | undefined, - T -> From c3cd7e965f376a1ac09a9a111e66669a7529eff9 Mon Sep 17 00:00:00 2001 From: Ben Durrant Date: Mon, 8 May 2023 14:30:42 +0100 Subject: [PATCH 190/412] install from NPM --- packages/toolkit/package.json | 2 +- yarn.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/toolkit/package.json b/packages/toolkit/package.json index 57d215df66..5caf09ff3f 100644 --- a/packages/toolkit/package.json +++ b/packages/toolkit/package.json @@ -108,7 +108,7 @@ "redux": "5.0.0-alpha.5", "redux-thunk": "3.0.0-alpha.3", "reselect": "^4.1.8", - "uncheckedindexed": "https://github.com/EskiMojo14/uncheckedindexed" + "uncheckedindexed": "^0.0.1" }, "peerDependencies": { "react": "^16.9.0 || ^17.0.0 || ^18", diff --git a/yarn.lock b/yarn.lock index 5025a4fdd9..1f01006828 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6793,7 +6793,7 @@ __metadata: tsup: ^6.7.0 tsx: ^3.12.2 typescript: ~4.9 - uncheckedindexed: "https://github.com/EskiMojo14/uncheckedindexed" + uncheckedindexed: ^0.0.1 vitest: ^0.30.1 yargs: ^15.3.1 peerDependencies: @@ -28409,10 +28409,10 @@ fsevents@^1.2.7: languageName: node linkType: hard -"uncheckedindexed@https://github.com/EskiMojo14/uncheckedindexed": +"uncheckedindexed@npm:^0.0.1": version: 0.0.1 - resolution: "uncheckedindexed@https://github.com/EskiMojo14/uncheckedindexed.git#commit=d08490cc8efa3fcf3fac05f55377681e0823c6e3" - checksum: 1cdc6ab2963da63582603e481b4fbf834ba533d8f83e3c4d2aeb682be6866327b585a1440497fd41e13c9d5196f84316dba27c8ac8168ed31d2b1f46d232c395 + resolution: "uncheckedindexed@npm:0.0.1" + checksum: 66cf7c42df36eeb8520de61ed16df0b1506bb40ede645d9c146672799d2392a3ad8c7b54131b07ff764414dcc12bd74eca90c3fdf13f19631181e6198f916075 languageName: node linkType: hard From 7ed2439b0d747a13b346243673d43b8f79726ff3 Mon Sep 17 00:00:00 2001 From: Ben Durrant Date: Mon, 8 May 2023 21:08:11 +0100 Subject: [PATCH 191/412] inline uncheckedindexed --- packages/toolkit/package.json | 3 +-- packages/toolkit/src/entities/models.ts | 2 +- packages/toolkit/src/uncheckedindexed.ts | 16 ++++++++++++++++ packages/toolkit/tsup.config.ts | 9 +++++++++ yarn.lock | 8 -------- 5 files changed, 27 insertions(+), 11 deletions(-) create mode 100644 packages/toolkit/src/uncheckedindexed.ts diff --git a/packages/toolkit/package.json b/packages/toolkit/package.json index 5caf09ff3f..6aec87a2e3 100644 --- a/packages/toolkit/package.json +++ b/packages/toolkit/package.json @@ -107,8 +107,7 @@ "immer": "^10.0.1", "redux": "5.0.0-alpha.5", "redux-thunk": "3.0.0-alpha.3", - "reselect": "^4.1.8", - "uncheckedindexed": "^0.0.1" + "reselect": "^4.1.8" }, "peerDependencies": { "react": "^16.9.0 || ^17.0.0 || ^18", diff --git a/packages/toolkit/src/entities/models.ts b/packages/toolkit/src/entities/models.ts index a3556dcd5b..502d4c5d8b 100644 --- a/packages/toolkit/src/entities/models.ts +++ b/packages/toolkit/src/entities/models.ts @@ -1,4 +1,4 @@ -import type { UncheckedIndexedAccess } from 'uncheckedindexed' +import type { UncheckedIndexedAccess } from '../uncheckedindexed' import type { PayloadAction } from '../createAction' import type { CastAny, Id } from '../tsHelpers' diff --git a/packages/toolkit/src/uncheckedindexed.ts b/packages/toolkit/src/uncheckedindexed.ts new file mode 100644 index 0000000000..628ff5dc43 --- /dev/null +++ b/packages/toolkit/src/uncheckedindexed.ts @@ -0,0 +1,16 @@ +// inlined from https://github.com/EskiMojo14/uncheckedindexed +// relies on remaining as a TS file, not .d.ts +type IfMaybeUndefined = [undefined] extends [T] ? True : False + +const testAccess = ({} as Record).a + +export type IfUncheckedIndexedAccess = IfMaybeUndefined< + typeof testAccess, + True, + False +> + +export type UncheckedIndexedAccess = IfUncheckedIndexedAccess< + T | undefined, + T +> diff --git a/packages/toolkit/tsup.config.ts b/packages/toolkit/tsup.config.ts index a7195c0f73..107f0e440e 100644 --- a/packages/toolkit/tsup.config.ts +++ b/packages/toolkit/tsup.config.ts @@ -196,6 +196,15 @@ export default defineConfig((options) => { if (format === 'cjs' && name === 'production.min') { writeCommonJSEntry(outputFolder, prefix) } else if (generateTypedefs) { + if (folder === '') { + // we need to delete the declaration file and replace it with the original source file + fs.rmSync(path.join(outputFolder, 'uncheckedindexed.d.ts')) + + fs.copyFileSync( + 'src/uncheckedindexed.ts', + path.join(outputFolder, 'uncheckedindexed.ts') + ) + } // TODO Copy/generate `.d.mts` files? // const inputTypedefsFile = `${outputFilename}.d.ts` // const outputTypedefsFile = `${outputFilename}.d.mts` diff --git a/yarn.lock b/yarn.lock index 1f01006828..e1040f2f6b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6793,7 +6793,6 @@ __metadata: tsup: ^6.7.0 tsx: ^3.12.2 typescript: ~4.9 - uncheckedindexed: ^0.0.1 vitest: ^0.30.1 yargs: ^15.3.1 peerDependencies: @@ -28409,13 +28408,6 @@ fsevents@^1.2.7: languageName: node linkType: hard -"uncheckedindexed@npm:^0.0.1": - version: 0.0.1 - resolution: "uncheckedindexed@npm:0.0.1" - checksum: 66cf7c42df36eeb8520de61ed16df0b1506bb40ede645d9c146672799d2392a3ad8c7b54131b07ff764414dcc12bd74eca90c3fdf13f19631181e6198f916075 - languageName: node - linkType: hard - "unherit@npm:^1.0.4": version: 1.1.3 resolution: "unherit@npm:1.1.3" From d086a886f20aa598396ab983caa320955cf9c461 Mon Sep 17 00:00:00 2001 From: "ben.durrant" Date: Wed, 10 May 2023 19:36:39 +0100 Subject: [PATCH 192/412] Add combineSlices to list of APIs --- docs/introduction/getting-started.md | 1 + packages/toolkit/README.md | 5 +++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/docs/introduction/getting-started.md b/docs/introduction/getting-started.md index e1aabbd1d4..8e7c64c077 100644 --- a/docs/introduction/getting-started.md +++ b/docs/introduction/getting-started.md @@ -88,6 +88,7 @@ Redux Toolkit includes these APIs: - [`createReducer()`](../api/createReducer.mdx): that lets you supply a lookup table of action types to case reducer functions, rather than writing switch statements. In addition, it automatically uses the [`immer` library](https://github.com/immerjs/immer) to let you write simpler immutable updates with normal mutative code, like `state.todos[3].completed = true`. - [`createAction()`](../api/createAction.mdx): generates an action creator function for the given action type string. The function itself has `toString()` defined, so that it can be used in place of the type constant. - [`createSlice()`](../api/createSlice.mdx): accepts an object of reducer functions, a slice name, and an initial state value, and automatically generates a slice reducer with corresponding action creators and action types. +- [`combineSlices()`](../api/combineSlices.mdx): combines multiple slices into a single reducer, and allows "lazy loading" of slices after initialisation. - [`createAsyncThunk`](../api/createAsyncThunk.mdx): accepts an action type string and a function that returns a promise, and generates a thunk that dispatches `pending/fulfilled/rejected` action types based on that promise - [`createEntityAdapter`](../api/createEntityAdapter.mdx): generates a set of reusable reducers and selectors to manage normalized data in the store - The [`createSelector` utility](../api/createSelector.mdx) from the [Reselect](https://github.com/reduxjs/reselect) library, re-exported for ease of use. diff --git a/packages/toolkit/README.md b/packages/toolkit/README.md index 5923eb224e..6b5dd2b7a4 100644 --- a/packages/toolkit/README.md +++ b/packages/toolkit/README.md @@ -60,8 +60,9 @@ Redux Toolkit includes these APIs: - `configureStore()`: wraps `createStore` to provide simplified configuration options and good defaults. It can automatically combine your slice reducers, add whatever Redux middleware you supply, includes `redux-thunk` by default, and enables use of the Redux DevTools Extension. - `createReducer()`: lets you supply a lookup table of action types to case reducer functions, rather than writing switch statements. In addition, it automatically uses the [`immer` library](https://github.com/mweststrate/immer) to let you write simpler immutable updates with normal mutative code, like `state.todos[3].completed = true`. - `createAction()`: generates an action creator function for the given action type string. The function itself has `toString()` defined, so that it can be used in place of the type constant. -- `createSlice()`: combines `createReducer()` + `createAction()`. Accepts an object of reducer functions, a slice name, and an initial state value, and automatically generates a slice reducer with corresponding action creators and action types. -- `createListenerMiddleware()`: lets you define "listener" entries that contain an "effect" callback with additional logic, and a way to specify when that callback should run based on dispatched actions or state changes. A lightweight alternative to Redux async middleware like sagas and observables. +- `createSlice()`: combines `createReducer()` + `createAction()`. Accepts an object of reducer functions, a slice name, and an initial state value, and automatically generates a slice reducer with corresponding action creators and action types. +- `combineSlices()`: combines multiple slices into a single reducer, and allows "lazy loading" of slices after initialisation. +- `createListenerMiddleware()`: lets you define "listener" entries that contain an "effect" callback with additional logic, and a way to specify when that callback should run based on dispatched actions or state changes. A lightweight alternative to Redux async middleware like sagas and observables. - `createAsyncThunk()`: accepts an action type string and a function that returns a promise, and generates a thunk that dispatches `pending/resolved/rejected` action types based on that promise - `createEntityAdapter()`: generates a set of reusable reducers and selectors to manage normalized data in the store - The `createSelector()` utility from the [Reselect](https://github.com/reduxjs/reselect) library, re-exported for ease of use. From 8dc83054b94a396780a31df39d7c46355dfc8b5c Mon Sep 17 00:00:00 2001 From: Ben Durrant Date: Sun, 14 May 2023 23:07:38 +0100 Subject: [PATCH 193/412] remove more deprecated features, and add isAction documentation --- docs/api/createEntityAdapter.mdx | 2 +- docs/api/createSlice.mdx | 2 +- docs/api/matching-utilities.mdx | 16 ++++++--- docs/usage/immer-reducers.md | 6 ++-- docs/usage/usage-with-typescript.md | 33 ++----------------- packages/toolkit/src/createAction.ts | 12 +++---- .../toolkit/src/tests/createAction.test.ts | 3 +- 7 files changed, 25 insertions(+), 49 deletions(-) diff --git a/docs/api/createEntityAdapter.mdx b/docs/api/createEntityAdapter.mdx index 4ea41915a2..b60dd50bf2 100644 --- a/docs/api/createEntityAdapter.mdx +++ b/docs/api/createEntityAdapter.mdx @@ -15,7 +15,7 @@ A function that generates a set of prebuilt reducers and selectors for performin This API was ported from [the `@ngrx/entity` library](https://ngrx.io/guide/entity) created by the NgRx maintainers, but has been significantly modified for use with Redux Toolkit. We'd like to thank the NgRx team for originally creating this API and allowing us to port and adapt it for our needs. -::: note +:::note The term "Entity" is used to refer to a unique type of data object in an application. For example, in a blogging application, you might have `User`, `Post`, and `Comment` data objects, with many instances of each being stored in the client and persisted on the server. `User` is an "entity" - a unique type of data object that the application uses. Each unique instance of an entity is assumed to have a unique ID value in a specific field. As with all Redux logic, [_only_ plain JS objects and arrays should be passed in to the store - **no class instances!**](https://redux.js.org/style-guide/style-guide#do-not-put-non-serializable-values-in-state-or-actions) diff --git a/docs/api/createSlice.mdx b/docs/api/createSlice.mdx index cabb03277a..a336ab7c98 100644 --- a/docs/api/createSlice.mdx +++ b/docs/api/createSlice.mdx @@ -266,7 +266,7 @@ The [`slice.selectors`](#selectors-2) object is the equivalent of calling ```ts no-transpile const { selectValue } = counterSlice.getSelectors( - (rootState: RootState) => rootState[slice.reducerPath] + (rootState: RootState) => rootState[counterSlice.reducerPath] ) ``` diff --git a/docs/api/matching-utilities.mdx b/docs/api/matching-utilities.mdx index 16c720dc2c..64344d4241 100644 --- a/docs/api/matching-utilities.mdx +++ b/docs/api/matching-utilities.mdx @@ -13,6 +13,7 @@ Redux Toolkit exports several type-safe action matching utilities that you can l ### General Purpose +- [`isAction`](#isaction) - returns true if a passed value is a standard action object, with a type string - [`isAllOf`](#isallof) - returns true when **all** conditions are met - [`isAnyOf`](#isanyof) - returns true when **at least one of** the conditions are met @@ -26,6 +27,16 @@ All these matchers can either be called with one or more thunks as arguments, in - [`isRejected`](#isrejected) - accepts one or more action creators and returns true when all match - [`isRejectedWithValue`](#isrejectedwithvalue) - accepts one or more action creators and returns true when all match +## `isAction` + +A type guard that takes an unknown variable, and returns `true` if it's a standard Redux action with a string `type` property. + +:::caution + +As of Redux 5.0, action types are _required_ to be a string. + +::: + ## `isAllOf` A higher-order function that accepts one or more of: @@ -145,10 +156,7 @@ we're able to easily use the same matcher for several cases in a type-safe manne First, let's examine an unnecessarily complex example: ```ts title="Example without using a matcher utility" -import { - createAsyncThunk, - createReducer, -} from '@reduxjs/toolkit' +import { createAsyncThunk, createReducer } from '@reduxjs/toolkit' import type { PayloadAction } from '@reduxjs/toolkit' interface Data { diff --git a/docs/usage/immer-reducers.md b/docs/usage/immer-reducers.md index 409851e469..582ac89b63 100644 --- a/docs/usage/immer-reducers.md +++ b/docs/usage/immer-reducers.md @@ -462,9 +462,9 @@ module.exports = { overrides: [ { // feel free to replace with your preferred file pattern - eg. 'src/**/*Slice.ts' - files: ['src/**/*.slice.ts'], + files: ['src/**/*.slice.ts'], // avoid state param assignment - rules: { 'no-param-reassign': ['error', { props: false }] }, + rules: { 'no-param-reassign': ['error', { props: false }] }, }, ], } @@ -508,8 +508,6 @@ There's two more reasons why Immer is not optional. One is RTK's architecture. `createSlice` and `createReducer` are implemented by directly importing Immer. There's no easy way to create a version of either of them that would have a hypothetical `immer: false` option. You can't do optional imports, and we need Immer available immediately and synchronously during the initial load of the app. -Additionally, RTK currently calls [Immer's `enableES5` plugin](https://immerjs.github.io/immer/installation#pick-your-immer-version) immediately on import, in order to ensure that Immer works correctly in environments without ES6 Proxy support (such as IE11 and older React Native versions). This is necessary because Immer split out the ES5 behavior into a plugin around version 6.0, but dropping the ES5 support would have been a major breaking change for RTK and broken our users. Because RTK itself calls `enableES5` from the entry point, Immer is _always_ pulled in. - And finally: **Immer is built into RTK by default because we believe it is the best choice for our users!** We _want_ our users to be using Immer, and consider it to be a critical non-negotiable component of RTK. The great benefits like simpler reducer code and preventing accidental mutations far outweigh the relatively small concerns. ## Further Information diff --git a/docs/usage/usage-with-typescript.md b/docs/usage/usage-with-typescript.md index d4d1a6348d..cb1f773a93 100644 --- a/docs/usage/usage-with-typescript.md +++ b/docs/usage/usage-with-typescript.md @@ -206,36 +206,9 @@ This `match` method is also very useful in combination with `redux-observable` a ## `createReducer` -The default way of calling `createReducer` would be with a "lookup table" / "map object", like this: - -```typescript -createReducer(0, { - increment: (state, action: PayloadAction) => state + action.payload, -}) -``` - -Unfortunately, as the keys are only strings, using that API TypeScript can neither infer nor validate the action types for you: - -```typescript -{ - const increment = createAction('increment') - const decrement = createAction('decrement') - createReducer(0, { - [increment.type]: (state, action) => { - // action is any here - }, - [decrement.type]: (state, action: PayloadAction) => { - // even though action should actually be PayloadAction, TypeScript can't detect that and won't give a warning here. - }, - }) -} -``` - -As an alternative, RTK includes a type-safe reducer builder API. - ### Building Type-Safe Reducer Argument Objects -Instead of using a simple object as an argument to `createReducer`, you can also use a callback that receives a `ActionReducerMapBuilder` instance: +The second parameter for `createReducer` is a callback that receives a `ActionReducerMapBuilder` instance: ```typescript {3-10} const increment = createAction('increment') @@ -251,8 +224,6 @@ createReducer(0, (builder) => ) ``` -We recommend using this API if stricter type safety is necessary when defining reducer argument objects. - #### Typing `builder.addMatcher` As the first `matcher` argument to `builder.addMatcher`, a [type predicate](https://www.typescriptlang.org/docs/handbook/advanced-types.html#using-type-predicates) function should be used. @@ -386,7 +357,7 @@ If you actually _need_ that type, unfortunately there is no other way than manua ### Type safety with `extraReducers` -Reducer lookup tables that map an action `type` string to a reducer function are not easy to fully type correctly. This affects both `createReducer` and the `extraReducers` argument for `createSlice`. So, like with `createReducer`, [you may also use the "builder callback" approach](#building-type-safe-reducer-argument-objects) for defining the reducer object argument. +Reducer lookup tables that map an action `type` string to a reducer function are not easy to fully type correctly. This affects both `createReducer` and the `extraReducers` argument for `createSlice`. So, like with `createReducer`, [you should use the "builder callback" approach](#building-type-safe-reducer-argument-objects) for defining the reducer object argument. This is particularly useful when a slice reducer needs to handle action types generated by other slices, or generated by specific calls to `createAction` (such as the actions generated by [`createAsyncThunk`](../api/createAsyncThunk.mdx)). diff --git a/packages/toolkit/src/createAction.ts b/packages/toolkit/src/createAction.ts index 155672c327..f49cc48dd8 100644 --- a/packages/toolkit/src/createAction.ts +++ b/packages/toolkit/src/createAction.ts @@ -290,8 +290,10 @@ export function createAction(type: string, prepareAction?: Function): any { /** * Returns true if value is a plain object with a `type` property. */ -export function isAction(action: unknown): action is Action { - return isPlainObject(action) && 'type' in action +export function isAction(action: unknown): action is Action { + return ( + isPlainObject(action) && 'type' in action && typeof action.type === 'string' + ) } /** @@ -317,11 +319,7 @@ export function isFSA(action: unknown): action is { error?: unknown meta?: unknown } { - return ( - isAction(action) && - typeof action.type === 'string' && - Object.keys(action).every(isValidKey) - ) + return isAction(action) && Object.keys(action).every(isValidKey) } function isValidKey(key: string) { diff --git a/packages/toolkit/src/tests/createAction.test.ts b/packages/toolkit/src/tests/createAction.test.ts index 5b481b945c..f0674a986f 100644 --- a/packages/toolkit/src/tests/createAction.test.ts +++ b/packages/toolkit/src/tests/createAction.test.ts @@ -123,7 +123,7 @@ describe('createAction', () => { }) describe('isAction', () => { - it('should only return true for plain objects with a type property', () => { + it('should only return true for plain objects with a string type property', () => { const actionCreator = createAction('anAction') class Action { type = 'totally an action' @@ -131,6 +131,7 @@ describe('isAction', () => { const testCases: [action: unknown, expected: boolean][] = [ [{ type: 'an action' }, true], [{ type: 'more props', extra: true }, true], + [{ type: 0 }, false], [actionCreator(), true], [actionCreator, false], [Promise.resolve({ type: 'an action' }), false], From 38c4d043d1fd24225b0e7c91dd0aac0a56fb4805 Mon Sep 17 00:00:00 2001 From: Ben Durrant Date: Sun, 14 May 2023 23:38:07 +0100 Subject: [PATCH 194/412] fix types for TS <4.9 --- packages/toolkit/src/createAction.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/toolkit/src/createAction.ts b/packages/toolkit/src/createAction.ts index f49cc48dd8..bc62cc60ea 100644 --- a/packages/toolkit/src/createAction.ts +++ b/packages/toolkit/src/createAction.ts @@ -292,7 +292,9 @@ export function createAction(type: string, prepareAction?: Function): any { */ export function isAction(action: unknown): action is Action { return ( - isPlainObject(action) && 'type' in action && typeof action.type === 'string' + isPlainObject(action) && + 'type' in action && + typeof (action as Record<'type', unknown>).type === 'string' ) } From b5e562f0f4a2692458c678b9d44ea37e41994eda Mon Sep 17 00:00:00 2001 From: Ben Durrant Date: Sun, 14 May 2023 23:48:27 +0100 Subject: [PATCH 195/412] fix test --- .../tests/serializableStateInvariantMiddleware.test.ts | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/packages/toolkit/src/tests/serializableStateInvariantMiddleware.test.ts b/packages/toolkit/src/tests/serializableStateInvariantMiddleware.test.ts index adf99bbf7a..9235c60efc 100644 --- a/packages/toolkit/src/tests/serializableStateInvariantMiddleware.test.ts +++ b/packages/toolkit/src/tests/serializableStateInvariantMiddleware.test.ts @@ -103,15 +103,16 @@ describe('serializableStateInvariantMiddleware', () => { middleware: [serializableStateInvariantMiddleware], }) - const type = Symbol.for('SOME_CONSTANT') - const dispatchedAction = { type } + const symbol = Symbol.for('SOME_CONSTANT') + const dispatchedAction = { type: 'an-action', payload: symbol } store.dispatch(dispatchedAction) expect(getLog().log).toMatchInlineSnapshot(` - "A non-serializable value was detected in an action, in the path: \`type\`. Value: Symbol(SOME_CONSTANT) + "A non-serializable value was detected in an action, in the path: \`payload\`. Value: Symbol(SOME_CONSTANT) Take a look at the logic that dispatched this action: Object { - \\"type\\": Symbol(SOME_CONSTANT), + \\"payload\\": Symbol(SOME_CONSTANT), + \\"type\\": \\"an-action\\", } (See https://redux.js.org/faq/actions#why-should-type-be-a-string-or-at-least-serializable-why-should-my-action-types-be-constants) (To allow non-serializable values see: https://redux-toolkit.js.org/usage/usage-guide#working-with-non-serializable-data)" From 8af52c11dabedf80f3dc57a519ef05868a7906c2 Mon Sep 17 00:00:00 2001 From: Ben Durrant Date: Mon, 15 May 2023 00:03:53 +0100 Subject: [PATCH 196/412] ensure all actions have a string type --- packages/toolkit/src/createAction.ts | 16 ++++++++-------- .../toolkit/src/tests/createAction.typetest.tsx | 10 +++++----- .../toolkit/src/tests/createSlice.typetest.ts | 4 ++-- .../serializableStateInvariantMiddleware.test.ts | 9 +++++---- 4 files changed, 20 insertions(+), 19 deletions(-) diff --git a/packages/toolkit/src/createAction.ts b/packages/toolkit/src/createAction.ts index 155672c327..8922cfc81e 100644 --- a/packages/toolkit/src/createAction.ts +++ b/packages/toolkit/src/createAction.ts @@ -84,7 +84,7 @@ export type _ActionCreatorWithPreparedPayload< */ export interface BaseActionCreator { type: T - match: (action: Action) => action is PayloadAction + match: (action: Action) => action is PayloadAction } /** @@ -290,8 +290,12 @@ export function createAction(type: string, prepareAction?: Function): any { /** * Returns true if value is a plain object with a `type` property. */ -export function isAction(action: unknown): action is Action { - return isPlainObject(action) && 'type' in action +export function isAction(action: unknown): action is Action { + return ( + isPlainObject(action) && + 'type' in action && + typeof (action as Record<'type', unknown>).type === 'string' + ) } /** @@ -317,11 +321,7 @@ export function isFSA(action: unknown): action is { error?: unknown meta?: unknown } { - return ( - isAction(action) && - typeof action.type === 'string' && - Object.keys(action).every(isValidKey) - ) + return isAction(action) && Object.keys(action).every(isValidKey) } function isValidKey(key: string) { diff --git a/packages/toolkit/src/tests/createAction.typetest.tsx b/packages/toolkit/src/tests/createAction.typetest.tsx index 8574caa632..f0981335a5 100644 --- a/packages/toolkit/src/tests/createAction.typetest.tsx +++ b/packages/toolkit/src/tests/createAction.typetest.tsx @@ -258,7 +258,7 @@ import { expectType } from './helpers' // simple use case { const actionCreator = createAction('test') - const x: Action = {} as any + const x: Action = {} as any if (actionCreator.match(x)) { expectType<'test'>(x.type) expectType(x.payload) @@ -273,7 +273,7 @@ import { expectType } from './helpers' // special case: optional argument { const actionCreator = createAction('test') - const x: Action = {} as any + const x: Action = {} as any if (actionCreator.match(x)) { expectType<'test'>(x.type) expectType(x.payload) @@ -283,7 +283,7 @@ import { expectType } from './helpers' // special case: without argument { const actionCreator = createAction('test') - const x: Action = {} as any + const x: Action = {} as any if (actionCreator.match(x)) { expectType<'test'>(x.type) // @ts-expect-error @@ -298,7 +298,7 @@ import { expectType } from './helpers' meta: '', error: false, })) - const x: Action = {} as any + const x: Action = {} as any if (actionCreator.match(x)) { expectType<'test'>(x.type) expectType(x.payload) @@ -315,7 +315,7 @@ import { expectType } from './helpers' // potential use: as array filter { const actionCreator = createAction('test') - const x: Array> = [] + const x: Array> = [] expectType>>( x.filter(actionCreator.match) ) diff --git a/packages/toolkit/src/tests/createSlice.typetest.ts b/packages/toolkit/src/tests/createSlice.typetest.ts index 11669eafee..9f6fcfcff9 100644 --- a/packages/toolkit/src/tests/createSlice.typetest.ts +++ b/packages/toolkit/src/tests/createSlice.typetest.ts @@ -386,13 +386,13 @@ const value = actionCreators.anyKey }, }) - const x: Action = {} as any + const x: Action = {} as any if (mySlice.actions.setName.match(x)) { expectType<'name/setName'>(x.type) expectType(x.payload) } else { // @ts-expect-error - expectType(x.type) + expectType<'name/setName'>(x.type) // @ts-expect-error expectType(x.payload) } diff --git a/packages/toolkit/src/tests/serializableStateInvariantMiddleware.test.ts b/packages/toolkit/src/tests/serializableStateInvariantMiddleware.test.ts index adf99bbf7a..9235c60efc 100644 --- a/packages/toolkit/src/tests/serializableStateInvariantMiddleware.test.ts +++ b/packages/toolkit/src/tests/serializableStateInvariantMiddleware.test.ts @@ -103,15 +103,16 @@ describe('serializableStateInvariantMiddleware', () => { middleware: [serializableStateInvariantMiddleware], }) - const type = Symbol.for('SOME_CONSTANT') - const dispatchedAction = { type } + const symbol = Symbol.for('SOME_CONSTANT') + const dispatchedAction = { type: 'an-action', payload: symbol } store.dispatch(dispatchedAction) expect(getLog().log).toMatchInlineSnapshot(` - "A non-serializable value was detected in an action, in the path: \`type\`. Value: Symbol(SOME_CONSTANT) + "A non-serializable value was detected in an action, in the path: \`payload\`. Value: Symbol(SOME_CONSTANT) Take a look at the logic that dispatched this action: Object { - \\"type\\": Symbol(SOME_CONSTANT), + \\"payload\\": Symbol(SOME_CONSTANT), + \\"type\\": \\"an-action\\", } (See https://redux.js.org/faq/actions#why-should-type-be-a-string-or-at-least-serializable-why-should-my-action-types-be-constants) (To allow non-serializable values see: https://redux-toolkit.js.org/usage/usage-guide#working-with-non-serializable-data)" From 63bdaeb873248293d299e272b649140df2b7f02d Mon Sep 17 00:00:00 2001 From: Ben Durrant Date: Mon, 15 May 2023 00:06:08 +0100 Subject: [PATCH 197/412] Missed an unknown --- packages/toolkit/src/createAction.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/toolkit/src/createAction.ts b/packages/toolkit/src/createAction.ts index 8922cfc81e..a2814b157d 100644 --- a/packages/toolkit/src/createAction.ts +++ b/packages/toolkit/src/createAction.ts @@ -281,7 +281,7 @@ export function createAction(type: string, prepareAction?: Function): any { actionCreator.type = type - actionCreator.match = (action: Action): action is PayloadAction => + actionCreator.match = (action: Action): action is PayloadAction => action.type === type return actionCreator From 8ae1b7ba7119edf04b226e54007cdb03ce5c6301 Mon Sep 17 00:00:00 2001 From: Ben Durrant Date: Mon, 15 May 2023 00:09:54 +0100 Subject: [PATCH 198/412] bump dep --- packages/toolkit/package.json | 2 +- yarn.lock | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/packages/toolkit/package.json b/packages/toolkit/package.json index 6aec87a2e3..7856dac014 100644 --- a/packages/toolkit/package.json +++ b/packages/toolkit/package.json @@ -105,7 +105,7 @@ ], "dependencies": { "immer": "^10.0.1", - "redux": "5.0.0-alpha.5", + "redux": "5.0.0-alpha.6", "redux-thunk": "3.0.0-alpha.3", "reselect": "^4.1.8" }, diff --git a/yarn.lock b/yarn.lock index e1040f2f6b..ec9405b449 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6784,7 +6784,7 @@ __metadata: node-fetch: ^2.6.1 prettier: ^2.2.1 query-string: ^7.0.1 - redux: 5.0.0-alpha.5 + redux: 5.0.0-alpha.6 redux-thunk: 3.0.0-alpha.3 reselect: ^4.1.8 rimraf: ^3.0.2 @@ -24669,10 +24669,10 @@ fsevents@^1.2.7: languageName: node linkType: hard -"redux@npm:5.0.0-alpha.5": - version: 5.0.0-alpha.5 - resolution: "redux@npm:5.0.0-alpha.5" - checksum: 4223be43f605c0d514d5d611a281ae703f905ed4c6014c81b55d1f59cdeac38e3c82fcee2671b102f6b681d95c2a6a9a0f598e044916378011fa0aa39dc644ad +"redux@npm:5.0.0-alpha.6": + version: 5.0.0-alpha.6 + resolution: "redux@npm:5.0.0-alpha.6" + checksum: 6f95a75f4c2549dc891700af0aba146fa3f2983bb07918ff47c5635dfdf3b15033d5d1e9981183b291bd70edd59a5842cbc54c6c47d57697c8195f3aef1ffb84 languageName: node linkType: hard From 7d18ae12c138cb94b60d141b9b4e4ca196460cc7 Mon Sep 17 00:00:00 2001 From: Mark Erikson Date: Mon, 15 May 2023 21:39:10 -0400 Subject: [PATCH 199/412] List RTK as external for "/react" entry point and fix artifacts --- packages/toolkit/package.json | 2 +- packages/toolkit/react/package.json | 4 ++-- packages/toolkit/tsup.config.ts | 1 + 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/packages/toolkit/package.json b/packages/toolkit/package.json index 41af448f09..8c01480682 100644 --- a/packages/toolkit/package.json +++ b/packages/toolkit/package.json @@ -34,7 +34,7 @@ }, "./react": { "types": "./dist/react/index.d.ts", - "import": "./dist/react/redux-toolkit-react.modern.js", + "import": "./dist/react/redux-toolkit-react.modern.mjs", "default": "./dist/react/cjs/index.js" }, "./query": { diff --git a/packages/toolkit/react/package.json b/packages/toolkit/react/package.json index 5ff869483b..24dd5e5ea9 100644 --- a/packages/toolkit/react/package.json +++ b/packages/toolkit/react/package.json @@ -3,14 +3,14 @@ "version": "1.0.0", "description": "", "type": "module", - "module": "../dist/react/redux-toolkit-react.modern.js", + "module": "../dist/react/redux-toolkit-react.legacy-esm.js", "main": "../dist/react/cjs/index.js", "types": "./../dist/react/index.d.ts", "exports": { "./package.json": "./package.json", ".": { "types": "./../dist/react/index.d.ts", - "import": "./../dist/react/redux-toolkit-react.modern.js", + "import": "./../dist/react/redux-toolkit-react.modern.mjs", "default": "./../dist/react/cjs/index.js" } }, diff --git a/packages/toolkit/tsup.config.ts b/packages/toolkit/tsup.config.ts index 759ec6f275..d4d9219086 100644 --- a/packages/toolkit/tsup.config.ts +++ b/packages/toolkit/tsup.config.ts @@ -121,6 +121,7 @@ const entryPoints: EntryPointOptions[] = [ folder: 'react/', entryPoint: 'src/react/index.ts', extractionConfig: 'api-extractor-react.json', + externals: ['redux', '@reduxjs/toolkit'], }, { prefix: 'rtk-query', From 0ea234b748d909aecb5f62d4261c4e730dd28a85 Mon Sep 17 00:00:00 2001 From: Mark Erikson Date: Mon, 15 May 2023 22:33:26 -0400 Subject: [PATCH 200/412] Bump Reselect to v5 alpha --- packages/toolkit/package.json | 4 ++-- yarn.lock | 21 ++++++++++++++------- 2 files changed, 16 insertions(+), 9 deletions(-) diff --git a/packages/toolkit/package.json b/packages/toolkit/package.json index e1f7c7ef4f..8f9853f544 100644 --- a/packages/toolkit/package.json +++ b/packages/toolkit/package.json @@ -110,10 +110,10 @@ "react" ], "dependencies": { - "immer": "^10.0.1", + "immer": "^10.0.2", "redux": "5.0.0-alpha.6", "redux-thunk": "3.0.0-alpha.3", - "reselect": "^4.1.8" + "reselect": "^5.0.0-alpha.2" }, "peerDependencies": { "react": "^16.9.0 || ^17.0.0 || ^18", diff --git a/yarn.lock b/yarn.lock index ec9405b449..054ecbd38a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6776,7 +6776,7 @@ __metadata: eslint-plugin-react: ^7.23.2 eslint-plugin-react-hooks: ^4.2.0 fs-extra: ^9.1.0 - immer: ^10.0.1 + immer: ^10.0.2 invariant: ^2.2.4 jsdom: ^21.0.0 json-stringify-safe: ^5.0.1 @@ -6786,7 +6786,7 @@ __metadata: query-string: ^7.0.1 redux: 5.0.0-alpha.6 redux-thunk: 3.0.0-alpha.3 - reselect: ^4.1.8 + reselect: ^5.0.0-alpha.2 rimraf: ^3.0.2 size-limit: ^4.11.0 tslib: ^1.10.0 @@ -16874,10 +16874,10 @@ fsevents@^1.2.7: languageName: node linkType: hard -"immer@npm:^10.0.1": - version: 10.0.1 - resolution: "immer@npm:10.0.1" - checksum: 24ab640e9d928385047f6f31bc77966117087236faf1a1f52b6b9a0159f78d543f648fecbf04d5e8c782a6f6cddd6eeb9e09c3b616a104632e701c7a22735654 +"immer@npm:^10.0.2": + version: 10.0.2 + resolution: "immer@npm:10.0.2" + checksum: 525a3b14210d02ae420c3b9f6ca14f7e9bcf625611d1356e773e7739f14c7c8de50dac442e6c7de3a6e24a782f7b792b6b8666bc0b3f00269d21a95f8f68ca84 languageName: node linkType: hard @@ -25139,13 +25139,20 @@ fsevents@^1.2.7: languageName: node linkType: hard -"reselect@npm:^4.1.7, reselect@npm:^4.1.8": +"reselect@npm:^4.1.7": version: 4.1.8 resolution: "reselect@npm:4.1.8" checksum: a4ac87cedab198769a29be92bc221c32da76cfdad6911eda67b4d3e7136dca86208c3b210e31632eae31ebd2cded18596f0dd230d3ccc9e978df22f233b5583e languageName: node linkType: hard +"reselect@npm:^5.0.0-alpha.2": + version: 5.0.0-alpha.2 + resolution: "reselect@npm:5.0.0-alpha.2" + checksum: c47b66999800e1297721cbc4b2464b520fade9823c598d578759c9fba3eb6be03b184e13c20f30820cc18fe2688fc9fb4475f83e59d8f2347aa0d591e465637d + languageName: node + linkType: hard + "resolve-alpn@npm:^1.0.0": version: 1.2.1 resolution: "resolve-alpn@npm:1.2.1" From b74403d05c5e0744ab8b584a2bdb1a706a6fbfa7 Mon Sep 17 00:00:00 2001 From: Mark Erikson Date: Mon, 15 May 2023 22:39:18 -0400 Subject: [PATCH 201/412] Re-export Reselect memoizers and `createSelectorCreator` --- packages/toolkit/src/index.ts | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/packages/toolkit/src/index.ts b/packages/toolkit/src/index.ts index 3f0a9adb96..6630c94687 100644 --- a/packages/toolkit/src/index.ts +++ b/packages/toolkit/src/index.ts @@ -7,7 +7,13 @@ export { isDraft, } from 'immer' export type { Draft } from 'immer' -export { createSelector } from 'reselect' +export { + createSelector, + createSelectorCreator, + defaultMemoize, + autotrackMemoize, + weakMapMemoize, +} from 'reselect' export type { Selector, OutputParametricSelector, From 12c369bba8d36485ba8b6a994faca9db52019a02 Mon Sep 17 00:00:00 2001 From: "ben.durrant" Date: Tue, 16 May 2023 13:49:47 +0100 Subject: [PATCH 202/412] use latest redux --- packages/toolkit/package.json | 2 +- yarn.lock | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/packages/toolkit/package.json b/packages/toolkit/package.json index 3d87fc0121..590f6365b5 100644 --- a/packages/toolkit/package.json +++ b/packages/toolkit/package.json @@ -111,7 +111,7 @@ ], "dependencies": { "immer": "^10.0.2", - "redux": "https://pkg.csb.dev/reduxjs/redux/commit/985fdefd/redux/_pkg.tgz", + "redux": "https://pkg.csb.dev/reduxjs/redux/commit/20631993/redux/_pkg.tgz", "redux-thunk": "3.0.0-alpha.3", "reselect": "^5.0.0-alpha.2" }, diff --git a/yarn.lock b/yarn.lock index 386f6629fc..a845fedee9 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6784,7 +6784,7 @@ __metadata: node-fetch: ^2.6.1 prettier: ^2.2.1 query-string: ^7.0.1 - redux: "https://pkg.csb.dev/reduxjs/redux/commit/985fdefd/redux/_pkg.tgz" + redux: "https://pkg.csb.dev/reduxjs/redux/commit/20631993/redux/_pkg.tgz" redux-thunk: 3.0.0-alpha.3 reselect: ^5.0.0-alpha.2 rimraf: ^3.0.2 @@ -24669,10 +24669,10 @@ fsevents@^1.2.7: languageName: node linkType: hard -"redux@https://pkg.csb.dev/reduxjs/redux/commit/985fdefd/redux/_pkg.tgz": - version: 5.0.0-alpha.2 - resolution: "redux@https://pkg.csb.dev/reduxjs/redux/commit/985fdefd/redux/_pkg.tgz" - checksum: 727c459c36579cb36900a59f1267c93133c3358ddd8ba9bb191d8c5d4bb022dee7d6cd0bc80698a1e5def00d0e9bc878eee49ce21053b5ebb0f4ae528d6d1952 +"redux@https://pkg.csb.dev/reduxjs/redux/commit/20631993/redux/_pkg.tgz": + version: 5.0.0-alpha.6 + resolution: "redux@https://pkg.csb.dev/reduxjs/redux/commit/20631993/redux/_pkg.tgz" + checksum: ef83dca24531c68a8489da35f1a0d9d2343057d6269d2aff2dd84de5f5654d0bd5d3ec38c26bb8ab56d5a9ef47d45d86d770aea3d0f10325f9e5b37e71410397 languageName: node linkType: hard From 56109507ee915afa7b2d0a5ab37b64fe5cd224ca Mon Sep 17 00:00:00 2001 From: "ben.durrant" Date: Tue, 16 May 2023 13:58:58 +0100 Subject: [PATCH 203/412] Predicate should receive action --- packages/toolkit/src/listenerMiddleware/types.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/toolkit/src/listenerMiddleware/types.ts b/packages/toolkit/src/listenerMiddleware/types.ts index 9ee60355a4..d6b2f7f2b3 100644 --- a/packages/toolkit/src/listenerMiddleware/types.ts +++ b/packages/toolkit/src/listenerMiddleware/types.ts @@ -28,14 +28,14 @@ export interface TypedActionCreator { /** @internal */ export type AnyListenerPredicate = ( - action: unknown, + action: UnknownAction, currentState: State, originalState: State ) => boolean /** @public */ export type ListenerPredicate = ( - action: unknown, + action: UnknownAction, currentState: State, originalState: State ) => action is Action @@ -137,7 +137,7 @@ export interface ForkOptions { * If true, causes the parent task to not be marked as complete until * all autoJoined forks have completed or failed. */ - autoJoin: boolean; + autoJoin: boolean } /** @public */ From 9e6304d6e5d4b7c5d31c62fd3c9d3ee7c0d2a56a Mon Sep 17 00:00:00 2001 From: "ben.durrant" Date: Tue, 16 May 2023 14:40:32 +0100 Subject: [PATCH 204/412] annotate return for clarity --- .../toolkit/src/query/core/buildMiddleware/batchActions.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/packages/toolkit/src/query/core/buildMiddleware/batchActions.ts b/packages/toolkit/src/query/core/buildMiddleware/batchActions.ts index 88dcb2434b..0bd2a73515 100644 --- a/packages/toolkit/src/query/core/buildMiddleware/batchActions.ts +++ b/packages/toolkit/src/query/core/buildMiddleware/batchActions.ts @@ -69,7 +69,10 @@ export const buildBatchedActionsHandler: InternalHandlerBuilder< return false } - return (action, mwApi) => { + return ( + action, + mwApi + ): [actionShouldContinue: boolean, hasSubscription: boolean] => { if (!previousSubscriptions) { // Initialize it the first time this handler runs previousSubscriptions = JSON.parse( From 25a0f6f83cb7e0bed97fd530d17922f72e21cfba Mon Sep 17 00:00:00 2001 From: "ben.durrant" Date: Tue, 16 May 2023 14:44:00 +0100 Subject: [PATCH 205/412] simplify isThisApiSliceAction --- packages/toolkit/src/query/core/buildMiddleware/index.ts | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/packages/toolkit/src/query/core/buildMiddleware/index.ts b/packages/toolkit/src/query/core/buildMiddleware/index.ts index 901e082654..2ef7c7f347 100644 --- a/packages/toolkit/src/query/core/buildMiddleware/index.ts +++ b/packages/toolkit/src/query/core/buildMiddleware/index.ts @@ -40,13 +40,8 @@ export function buildMiddleware< >(`${reducerPath}/invalidateTags`), } - const isThisApiSliceAction = (action: Action) => { - return ( - !!action && - typeof action.type === 'string' && - action.type.startsWith(`${reducerPath}/`) - ) - } + const isThisApiSliceAction = (action: Action) => + action.type.startsWith(`${reducerPath}/`) const handlerBuilders: InternalHandlerBuilder[] = [ buildDevCheckHandler, From 44b4f673bd25e2870dd37b780aaad0198d898a96 Mon Sep 17 00:00:00 2001 From: Mark Erikson Date: Tue, 16 May 2023 10:15:55 -0400 Subject: [PATCH 206/412] Release 2.0.0-alpha.6 --- packages/toolkit/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/toolkit/package.json b/packages/toolkit/package.json index 8f9853f544..80c07e1753 100644 --- a/packages/toolkit/package.json +++ b/packages/toolkit/package.json @@ -1,6 +1,6 @@ { "name": "@reduxjs/toolkit", - "version": "2.0.0-alpha.5", + "version": "2.0.0-alpha.6", "description": "The official, opinionated, batteries-included toolset for efficient Redux development", "author": "Mark Erikson ", "license": "MIT", From 17a20c490e12ce7d2769dd157f1b09a4cf793302 Mon Sep 17 00:00:00 2001 From: "ben.durrant" Date: Tue, 16 May 2023 15:59:11 +0100 Subject: [PATCH 207/412] AnyAction -> UnknownAction --- docs/api/createDynamicMiddleware.mdx | 4 ++-- packages/toolkit/src/combineSlices.ts | 9 ++++++--- packages/toolkit/src/dynamicMiddleware/index.ts | 6 +++--- .../toolkit/src/dynamicMiddleware/react/index.ts | 10 +++++----- .../src/dynamicMiddleware/tests/index.typetest.ts | 4 ++-- .../src/dynamicMiddleware/tests/react.typetest.ts | 4 ++-- packages/toolkit/src/dynamicMiddleware/types.ts | 12 ++++++------ 7 files changed, 26 insertions(+), 23 deletions(-) diff --git a/docs/api/createDynamicMiddleware.mdx b/docs/api/createDynamicMiddleware.mdx index 5c86b80077..6aea1fa3a6 100644 --- a/docs/api/createDynamicMiddleware.mdx +++ b/docs/api/createDynamicMiddleware.mdx @@ -80,7 +80,7 @@ The "dynamic middleware instance" returned from `createDynamicMiddleware` is an ```ts no-transpile export type DynamicMiddlewareInstance< State = unknown, - Dispatch extends ReduxDispatch = ReduxDispatch + Dispatch extends ReduxDispatch = ReduxDispatch > = { middleware: DynamicMiddleware addMiddleware: AddMiddleware @@ -131,7 +131,7 @@ _These depend on having `react-redux` installed._ ```ts no-transpile interface ReactDynamicMiddlewareInstance< State = any, - Dispatch extends ReduxDispatch = ReduxDispatch + Dispatch extends ReduxDispatch = ReduxDispatch > extends DynamicMiddlewareInstance { createDispatchWithMiddlewareHook: CreateDispatchWithMiddlewareHook< State, diff --git a/packages/toolkit/src/combineSlices.ts b/packages/toolkit/src/combineSlices.ts index 2ef5ad7a1e..72c7750c7f 100644 --- a/packages/toolkit/src/combineSlices.ts +++ b/packages/toolkit/src/combineSlices.ts @@ -1,4 +1,4 @@ -import type { AnyAction, Reducer, StateFromReducersMapObject } from 'redux' +import type { UnknownAction, Reducer, StateFromReducersMapObject } from 'redux' import { combineReducers } from 'redux' import { nanoid } from './nanoid' import type { @@ -56,7 +56,7 @@ export type InjectConfig = { export interface CombinedSliceReducer< InitialState, DeclaredState = InitialState -> extends Reducer> { +> extends Reducer> { /** * Provide a type for slices that will be injected lazily. * @@ -378,7 +378,10 @@ export function combineSlices>( let reducer = getReducer() - function combinedReducer(state: Record, action: AnyAction) { + function combinedReducer( + state: Record, + action: UnknownAction + ) { return reducer(state, action) } diff --git a/packages/toolkit/src/dynamicMiddleware/index.ts b/packages/toolkit/src/dynamicMiddleware/index.ts index b1c7dc87f6..3b0583953f 100644 --- a/packages/toolkit/src/dynamicMiddleware/index.ts +++ b/packages/toolkit/src/dynamicMiddleware/index.ts @@ -1,7 +1,7 @@ import type { Middleware, Dispatch as ReduxDispatch, - AnyAction, + UnknownAction, MiddlewareAPI, } from 'redux' import { compose } from 'redux' @@ -18,7 +18,7 @@ import type { const createMiddlewareEntry = < State = any, - Dispatch extends ReduxDispatch = ReduxDispatch + Dispatch extends ReduxDispatch = ReduxDispatch >( middleware: Middleware ): MiddlewareEntry => ({ @@ -29,7 +29,7 @@ const createMiddlewareEntry = < export const createDynamicMiddleware = < State = any, - Dispatch extends ReduxDispatch = ReduxDispatch + Dispatch extends ReduxDispatch = ReduxDispatch >(): DynamicMiddlewareInstance => { const instanceId = nanoid() const middlewareMap = new Map>() diff --git a/packages/toolkit/src/dynamicMiddleware/react/index.ts b/packages/toolkit/src/dynamicMiddleware/react/index.ts index 858cdde7d7..7315f93394 100644 --- a/packages/toolkit/src/dynamicMiddleware/react/index.ts +++ b/packages/toolkit/src/dynamicMiddleware/react/index.ts @@ -1,6 +1,6 @@ import type { Action as ReduxAction, - AnyAction, + UnknownAction, Dispatch as ReduxDispatch, Middleware, } from 'redux' @@ -23,12 +23,12 @@ import type { export type UseDispatchWithMiddlewareHook< Middlewares extends Middleware[] = [], State = any, - Dispatch extends ReduxDispatch = ReduxDispatch + Dispatch extends ReduxDispatch = ReduxDispatch > = () => ExtractDispatchExtensions & Dispatch export type CreateDispatchWithMiddlewareHook< State = any, - Dispatch extends ReduxDispatch = ReduxDispatch + Dispatch extends ReduxDispatch = ReduxDispatch > = { < Middlewares extends [ @@ -51,7 +51,7 @@ type ActionFromDispatch> = interface ReactDynamicMiddlewareInstance< State = any, - Dispatch extends ReduxDispatch = ReduxDispatch + Dispatch extends ReduxDispatch = ReduxDispatch > extends DynamicMiddlewareInstance { createDispatchWithMiddlewareHookFactory: ( context?: Context< @@ -66,7 +66,7 @@ interface ReactDynamicMiddlewareInstance< export const createDynamicMiddleware = < State = any, - Dispatch extends ReduxDispatch = ReduxDispatch + Dispatch extends ReduxDispatch = ReduxDispatch >(): ReactDynamicMiddlewareInstance => { const instance = cDM() const createDispatchWithMiddlewareHookFactory = ( diff --git a/packages/toolkit/src/dynamicMiddleware/tests/index.typetest.ts b/packages/toolkit/src/dynamicMiddleware/tests/index.typetest.ts index 1d4a1e14fa..386b1aaa9d 100644 --- a/packages/toolkit/src/dynamicMiddleware/tests/index.typetest.ts +++ b/packages/toolkit/src/dynamicMiddleware/tests/index.typetest.ts @@ -1,5 +1,5 @@ /* eslint-disable no-lone-blocks */ -import type { Action, AnyAction, Middleware } from 'redux' +import type { Action, UnknownAction, Middleware } from 'redux' import type { ThunkDispatch } from 'redux-thunk' import { createDynamicMiddleware } from '../index' import { configureStore } from '../../configureStore' @@ -7,7 +7,7 @@ import { expectExactType, expectType } from '../../tests/helpers' const untypedInstance = createDynamicMiddleware() -interface AppDispatch extends ThunkDispatch { +interface AppDispatch extends ThunkDispatch { (n: 1): 1 } diff --git a/packages/toolkit/src/dynamicMiddleware/tests/react.typetest.ts b/packages/toolkit/src/dynamicMiddleware/tests/react.typetest.ts index db664b4d54..59088fd3b5 100644 --- a/packages/toolkit/src/dynamicMiddleware/tests/react.typetest.ts +++ b/packages/toolkit/src/dynamicMiddleware/tests/react.typetest.ts @@ -1,13 +1,13 @@ /* eslint-disable no-lone-blocks */ import type { Context } from 'react' import type { ReactReduxContextValue } from 'react-redux' -import type { Action, AnyAction, Middleware } from 'redux' +import type { Action, UnknownAction, Middleware } from 'redux' import type { ThunkDispatch } from 'redux-thunk' import { createDynamicMiddleware } from '../react' import { expectExactType, expectType } from '../../tests/helpers' /* eslint-disable no-lone-blocks */ -interface AppDispatch extends ThunkDispatch { +interface AppDispatch extends ThunkDispatch { (n: 1): 1 } diff --git a/packages/toolkit/src/dynamicMiddleware/types.ts b/packages/toolkit/src/dynamicMiddleware/types.ts index c96689141d..234b1578e1 100644 --- a/packages/toolkit/src/dynamicMiddleware/types.ts +++ b/packages/toolkit/src/dynamicMiddleware/types.ts @@ -1,7 +1,7 @@ import type { Middleware, Dispatch as ReduxDispatch, - AnyAction, + UnknownAction, MiddlewareAPI, } from 'redux' import type { ExtractDispatchExtensions, FallbackIfUnknown } from '../tsHelpers' @@ -32,7 +32,7 @@ export type GetDispatch = MiddlewareApiConfig extends { export type AddMiddleware< State = any, - Dispatch extends ReduxDispatch = ReduxDispatch + Dispatch extends ReduxDispatch = ReduxDispatch > = { (...middlewares: Middleware[]): void withTypes(): AddMiddleware< @@ -43,7 +43,7 @@ export type AddMiddleware< export interface WithMiddleware< State = any, - Dispatch extends ReduxDispatch = ReduxDispatch + Dispatch extends ReduxDispatch = ReduxDispatch > extends BaseActionCreator< Middleware[], 'dynamicMiddleware/add', @@ -67,7 +67,7 @@ export interface DynamicDispatch { export type MiddlewareEntry< State = unknown, - Dispatch extends ReduxDispatch = ReduxDispatch + Dispatch extends ReduxDispatch = ReduxDispatch > = { id: string middleware: Middleware @@ -79,12 +79,12 @@ export type MiddlewareEntry< export type DynamicMiddleware< State = unknown, - Dispatch extends ReduxDispatch = ReduxDispatch + Dispatch extends ReduxDispatch = ReduxDispatch > = Middleware export type DynamicMiddlewareInstance< State = unknown, - Dispatch extends ReduxDispatch = ReduxDispatch + Dispatch extends ReduxDispatch = ReduxDispatch > = { middleware: DynamicMiddleware addMiddleware: AddMiddleware From 7e7fedceee721c8915848c8091636f8415cdbaad Mon Sep 17 00:00:00 2001 From: "ben.durrant" Date: Tue, 16 May 2023 23:59:20 +0100 Subject: [PATCH 208/412] Update enhancer docs --- docs/api/autoBatchEnhancer.mdx | 28 ++++++++++++----- docs/api/configureStore.mdx | 53 ++++++++++++++++++++++++++++---- docs/api/getDefaultEnhancers.mdx | 6 ++++ website/sidebars.json | 1 + 4 files changed, 75 insertions(+), 13 deletions(-) create mode 100644 docs/api/getDefaultEnhancers.mdx diff --git a/docs/api/autoBatchEnhancer.mdx b/docs/api/autoBatchEnhancer.mdx index 43378b80e8..8dd50e9deb 100644 --- a/docs/api/autoBatchEnhancer.mdx +++ b/docs/api/autoBatchEnhancer.mdx @@ -48,14 +48,9 @@ const counterSlice = createSlice({ }) const { incrementBatched, decrementUnbatched } = counterSlice.actions +// includes batch enhancer by default, as of RTK 2.0 const store = configureStore({ reducer: counterSlice.reducer, - // highlight-start - enhancers: (getDefaultEnhancers) => { - // Add the autobatch enhancer to the store setup - return getDefaultEnhancers().concat(autoBatchEnhancer()) - }, - // highlight-end }) ``` @@ -74,6 +69,25 @@ type AutoBatchOptions = export type autoBatchEnhancer = (options?: AutoBatchOptions) => StoreEnhancer ``` +:::tip +As of RTK 2.0, the `autoBatchEnhancer` is included by default when calling `configureStore`. + +This means to configure it, you should instead pass an callback that receives `getDefaultEnhancers` and calls it with your desired settings. + +```ts title="Configuring autoBatchEnhancer with getDefaultEnhancers" +import { configureStore } from '@reduxjs/toolkit' + +const store = configureStore({ + reducer: () => 0, + enhancers: (getDefaultEnhancers) => + getDefaultEnhancers({ + autoBatch: { type: 'tick' }, + }), +}) +``` + +::: + Creates a new instance of the autobatch store enhancer. Any action that is tagged with `action.meta[SHOULD_AUTOBATCH] = true` will be treated as "low-priority", and a notification callback will be queued. The enhancer will delay notifying subscribers until either: @@ -140,4 +154,4 @@ This allows Redux users to selectively tag certain actions for effective batchin ### RTK Query and Batching -RTK Query already marks several of its key internal action types as batchable. If you add the `autoBatchEnhancer` to the store setup, it will improve the overall UI performance, especially when rendering large lists of components that use the RTKQ query hooks. +RTK Query already marks several of its key internal action types as batchable. By adding the `autoBatchEnhancer` to the store setup, it improves the overall UI performance, especially when rendering large lists of components that use the RTKQ query hooks. diff --git a/docs/api/configureStore.mdx b/docs/api/configureStore.mdx index ea011908b2..db7285f876 100644 --- a/docs/api/configureStore.mdx +++ b/docs/api/configureStore.mdx @@ -77,7 +77,7 @@ If it is an object of slice reducers, like `{users : usersReducer, posts : posts ### `middleware` -An optional array of Redux middleware functions +An optional array of Redux middleware functions, or a callback to customise the array of middleware. If this option is provided, it should contain all the middleware functions you want added to the store. `configureStore` will automatically pass those to `applyMiddleware`. @@ -118,17 +118,58 @@ An optional array of Redux store enhancers, or a callback function to customize If defined as an array, these will be passed to [the Redux `compose` function](https://redux.js.org/api/compose), and the combined enhancer will be passed to `createStore`. -This should _not_ include `applyMiddleware()` or the Redux DevTools Extension `composeWithDevTools`, as those are already handled by `configureStore`. +:::tip Dev Tools +This should _not_ include the Redux DevTools Extension `composeWithDevTools`, as this is already handled by `configureStore`. -Example: `enhancers: [offline]` will result in a final setup of `[applyMiddleware, offline, devToolsExtension]`. +Example: `enhancers: [offline]` will result in a final setup of `[offline, devToolsExtension]`. +::: -If defined as a callback function, it will be called with the existing array of enhancers _without_ the DevTools Extension (currently `[applyMiddleware]`), -and should return a new array of enhancers. This is primarily useful for cases where a store enhancer needs to be added -in front of `applyMiddleware`, such as `redux-first-router` or `redux-offline`. +If not provided, `configureStore` will call `getDefaultEnhancers` and use the array of enhancers it returns (including `applyMiddleware` with specified middleware). + +Where you wish to add onto or customize the default enhancers, you may pass a callback function that will receive `getDefaultEnhancers` as its argument, and should return an enhancer array. Example: `enhancers: (defaultEnhancers) => defaultEnhancers.prepend(offline)` will result in a final setup of `[offline, applyMiddleware, devToolsExtension]`. +For more details on how the `enhancer` parameter works and the list of enhancers that are added by default, see the [`getDefaultEnhancers` docs page](./getDefaultEnhancers). + +:::caution Middleware + +In order to use any [middleware](#middleware) specified, you _need_ to use the `getDefaultEnhancers` callback, which will include the built `applyMiddleware` enhancer. + +If you provide an array, this `applyMiddleware` enhancer will _not_ be used. + +`configureStore` will warn in console if any middleware are provided (or left as default) but not included in the final list of enhancers. + +```ts no-transpile +// warns - middleware left as default but not included in final enhancers +configureStore({ + reducer, + enhancers: [offline], +}) +// warns - middleware customised but not included in final enhancers +configureStore({ + reducer, + middleware: (getDefaultMiddleware) => getDefaultMiddleware().concat(logger) + enhancers: [offline], +}) + +// fine - default enhancers included +configureStore({ + reducer, + enhancers: (getDefaultEnhancers) => getDefaultEnhancers().concat(offline), +}) + +// also allowed +configureStore({ + reducer, + middleware: [], + enhancers: [offline], +}) +``` + +::: + ## Usage ### Basic Example diff --git a/docs/api/getDefaultEnhancers.mdx b/docs/api/getDefaultEnhancers.mdx new file mode 100644 index 0000000000..ed94cf2dd8 --- /dev/null +++ b/docs/api/getDefaultEnhancers.mdx @@ -0,0 +1,6 @@ +--- +id: getDefaultEnhancers +title: getDefaultEnhancers +sidebar_label: getDefaultEnhancers +hide_title: true +--- diff --git a/website/sidebars.json b/website/sidebars.json index 81f0a2047f..1d7cff4c8f 100644 --- a/website/sidebars.json +++ b/website/sidebars.json @@ -44,6 +44,7 @@ "api/actionCreatorMiddleware", "api/createListenerMiddleware", "api/createDynamicMiddleware", + "api/getDefaultEnhancers", "api/autoBatchEnhancer" ] }, From d826b15b0e4367c186b901d32ceb629202999e14 Mon Sep 17 00:00:00 2001 From: "ben.durrant" Date: Wed, 17 May 2023 00:30:55 +0100 Subject: [PATCH 209/412] getDefaultEnhancers page --- docs/api/configureStore.mdx | 8 +-- docs/api/getDefaultEnhancers.mdx | 108 ++++++++++++++++++++++++++++++ docs/api/getDefaultMiddleware.mdx | 2 +- 3 files changed, 113 insertions(+), 5 deletions(-) diff --git a/docs/api/configureStore.mdx b/docs/api/configureStore.mdx index db7285f876..a5e54d31a0 100644 --- a/docs/api/configureStore.mdx +++ b/docs/api/configureStore.mdx @@ -145,26 +145,26 @@ If you provide an array, this `applyMiddleware` enhancer will _not_ be used. // warns - middleware left as default but not included in final enhancers configureStore({ reducer, - enhancers: [offline], + enhancers: [offline(offlineConfig)], }) // warns - middleware customised but not included in final enhancers configureStore({ reducer, middleware: (getDefaultMiddleware) => getDefaultMiddleware().concat(logger) - enhancers: [offline], + enhancers: [offline(offlineConfig)], }) // fine - default enhancers included configureStore({ reducer, - enhancers: (getDefaultEnhancers) => getDefaultEnhancers().concat(offline), + enhancers: (getDefaultEnhancers) => getDefaultEnhancers().concat(offline(offlineConfig)), }) // also allowed configureStore({ reducer, middleware: [], - enhancers: [offline], + enhancers: [offline(offlineConfig)], }) ``` diff --git a/docs/api/getDefaultEnhancers.mdx b/docs/api/getDefaultEnhancers.mdx index ed94cf2dd8..a0630f8ee0 100644 --- a/docs/api/getDefaultEnhancers.mdx +++ b/docs/api/getDefaultEnhancers.mdx @@ -4,3 +4,111 @@ title: getDefaultEnhancers sidebar_label: getDefaultEnhancers hide_title: true --- + +  + +# `getDefaultEnhancers` + +Returns an array containing the default list of enhancers. + +## Intended Usage + +By default, [`configureStore`](./configureStore.mdx) adds some enhancers to the Redux store setup automatically. + +```js +const store = configureStore({ + reducer: rootReducer, +}) + +// Store has enhancers added, because the enhancer list was not customized +``` + +If you want to customise the list of enhancers, you can supply an array of enhancer functions to `configureStore`: + +```js +const store = configureStore({ + reducer: rootReducer, + enhancers: [offline(offlineConfig)], +}) + +// store specifically has the offline enhancer applied +``` + +However, when you supply the `enhancer` option, you are responsible for defining _all_ the enhancers you want added +to the store (with the exception of the [devtools](./configureStore#devtools)). `configureStore` will not add any extra enhancers beyond what you listed, **including the middleware enhancer**. + +`getDefaultEnhancers` is useful if you want to add some custom enhancers, but also still want to have the default +enhancers added as well: + +```ts no-transpile +import { configureStore } from '@reduxjs/toolkit' +import { offline } from '@redux-offline/redux-offline' +import offlineConfig from '@redux-offline/redux-offline/lib/defaults' + +import rootReducer from './reducer' + +const store = configureStore({ + reducer: rootReducer, + enhancers: (getDefaultEnhancers) => + getDefaultEnhancers().concat(offline(offlineConfig)), +}) + +// Store has all of the default middleware + enhancers added, _plus_ the offline enhancer +``` + +## Included Default Enhancers + +The resulting array will always contain the `applyMiddleware` enhancer created based on the `configureStore`'s `middleware` field. + +Additionally, the [`autoBatchEnhancer`](./autoBatchEnhancer.mdx) is included, to allow for "batching" of low priority action updates. This is used by [RTK Query](/rtk-query/overview.mdx) and should improve performance when using it. + +Currently, the return value is + +```js +const enhancers = [applyMiddleware, autoBatchEnhancer] +``` + +## Customising the Included Enhancers + +`getDefaultEnhancers` accepts an options object that allows customizing each enhancer (excluding the middleware enhancer) in two ways: + +- Each enhancer can be excluded from the result array by passing `false` for its corresponding field +- Each enhancer can have its options customized by passing the matching options object for its corresponding field + +This example shows customising the autoBatch enhancer: + +```ts +// file: reducer.ts noEmit + +export default function rootReducer(state = {}, action: any) { + return state +} + +// file: store.ts +import rootReducer from './reducer' +import { configureStore } from '@reduxjs/toolkit' + +const store = configureStore({ + reducer: rootReducer, + enhancers: (getDefaultEnhancers) => + getDefaultEnhancers({ + autoBatch: { type: 'tick' }, + }), +}) +``` + +## API Reference + +```ts no-transpile +interface AutoBatchOptions { + // see "autoBatchEnhancer" page for options +} + +interface GetDefaultEnhancersOptions { + autoBatch?: boolean | AutoBatchOptions +} + +function getDefaultEnhancers>( + options: GetDefaultEnhancersOptions = {} +): EnhancerArray<[StoreEnhancer<{ dispatch: ExtractDispatchExtensions }>]> +``` diff --git a/docs/api/getDefaultMiddleware.mdx b/docs/api/getDefaultMiddleware.mdx index 370b51ef75..0fd898dd94 100644 --- a/docs/api/getDefaultMiddleware.mdx +++ b/docs/api/getDefaultMiddleware.mdx @@ -104,7 +104,7 @@ const middleware = [thunk] `getDefaultMiddleware` accepts an options object that allows customizing each middleware in two ways: -- Each middleware can be excluded the result array by passing `false` for its corresponding field +- Each middleware can be excluded from the result array by passing `false` for its corresponding field - Each middleware can have its options customized by passing the matching options object for its corresponding field This example shows excluding the serializable state check middleware, and passing a specific value for the thunk From 098353c679dc206f9e20747974cf4174bb0de922 Mon Sep 17 00:00:00 2001 From: "ben.durrant" Date: Fri, 19 May 2023 18:26:50 +0100 Subject: [PATCH 210/412] Require usage of MiddlewareArray and EnhancerArray in TS --- packages/toolkit/src/configureStore.ts | 10 ++- .../toolkit/src/tests/configureStore.test.ts | 21 ++--- .../src/tests/configureStore.typetest.ts | 78 ++++++++++--------- ...rializableStateInvariantMiddleware.test.ts | 57 +++++++------- packages/toolkit/src/tsHelpers.ts | 6 +- packages/toolkit/src/utils.ts | 4 +- 6 files changed, 96 insertions(+), 80 deletions(-) diff --git a/packages/toolkit/src/configureStore.ts b/packages/toolkit/src/configureStore.ts index f08130e75a..39f2aaaefa 100644 --- a/packages/toolkit/src/configureStore.ts +++ b/packages/toolkit/src/configureStore.ts @@ -37,8 +37,8 @@ const IS_PRODUCTION = process.env.NODE_ENV === 'production' export interface ConfigureStoreOptions< S = any, A extends Action = AnyAction, - M extends Middlewares = Middlewares, - E extends Enhancers = Enhancers, + M extends MiddlewareArray> = MiddlewareArray>, + E extends EnhancerArray = EnhancerArray, P = S > { /** @@ -112,8 +112,10 @@ export type EnhancedStore< export function configureStore< S = any, A extends Action = AnyAction, - M extends Middlewares = MiddlewareArray<[ThunkMiddlewareFor]>, - E extends Enhancers = EnhancerArray< + M extends MiddlewareArray> = MiddlewareArray< + [ThunkMiddlewareFor] + >, + E extends EnhancerArray = EnhancerArray< [StoreEnhancer<{ dispatch: ExtractDispatchExtensions }>, StoreEnhancer] >, P = S diff --git a/packages/toolkit/src/tests/configureStore.test.ts b/packages/toolkit/src/tests/configureStore.test.ts index 248d29a69d..1ade822fc1 100644 --- a/packages/toolkit/src/tests/configureStore.test.ts +++ b/packages/toolkit/src/tests/configureStore.test.ts @@ -1,5 +1,6 @@ import { vi } from 'vitest' -import type { StoreEnhancer, StoreEnhancerStoreCreator } from '@reduxjs/toolkit' +import type { StoreEnhancer } from '@reduxjs/toolkit' +import { MiddlewareArray, EnhancerArray } from '@reduxjs/toolkit' import type * as Redux from 'redux' import type * as DevTools from '@internal/devtoolsExtension' @@ -108,7 +109,9 @@ describe('configureStore', async () => { describe('given no middleware', () => { it('calls createStore without any middleware', () => { - expect(configureStore({ middleware: [], reducer })).toBeInstanceOf(Object) + expect( + configureStore({ middleware: new MiddlewareArray(), reducer }) + ).toBeInstanceOf(Object) expect(redux.applyMiddleware).toHaveBeenCalledWith() expect(mockDevtoolsCompose).toHaveBeenCalled() // @remap-prod-remove-line-line expect(redux.createStore).toHaveBeenCalledWith( @@ -171,9 +174,9 @@ describe('configureStore', async () => { it('calls createStore with custom middleware and without default middleware', () => { const thank: Redux.Middleware = (_store) => (next) => (action) => next(action) - expect(configureStore({ middleware: [thank], reducer })).toBeInstanceOf( - Object - ) + expect( + configureStore({ middleware: new MiddlewareArray(thank), reducer }) + ).toBeInstanceOf(Object) expect(redux.applyMiddleware).toHaveBeenCalledWith(thank) expect(mockDevtoolsCompose).toHaveBeenCalled() // @remap-prod-remove-line-line expect(redux.createStore).toHaveBeenCalledWith( @@ -194,7 +197,7 @@ describe('configureStore', async () => { expect(getDefaultMiddleware).toEqual(expect.any(Function)) expect(getDefaultMiddleware()).toEqual(expect.any(Array)) - return [thank] + return new MiddlewareArray(thank) }) const store = configureStore({ middleware: builder, reducer }) @@ -303,7 +306,7 @@ describe('configureStore', async () => { it('warns if middleware enhancer is excluded from final array when middlewares are provided', () => { const store = configureStore({ reducer, - enhancers: [dummyEnhancer], + enhancers: new EnhancerArray(dummyEnhancer), }) expect(dummyEnhancerCalled).toBe(true) @@ -315,8 +318,8 @@ describe('configureStore', async () => { it("doesn't warn when middleware enhancer is excluded if no middlewares provided", () => { const store = configureStore({ reducer, - middleware: [], - enhancers: [dummyEnhancer], + middleware: new MiddlewareArray(), + enhancers: new EnhancerArray(dummyEnhancer), }) expect(dummyEnhancerCalled).toBe(true) diff --git a/packages/toolkit/src/tests/configureStore.typetest.ts b/packages/toolkit/src/tests/configureStore.typetest.ts index 195ee4db8b..d4d02681e4 100644 --- a/packages/toolkit/src/tests/configureStore.typetest.ts +++ b/packages/toolkit/src/tests/configureStore.typetest.ts @@ -10,7 +10,12 @@ import type { } from 'redux' import { applyMiddleware, combineReducers } from 'redux' import type { PayloadAction, ConfigureStoreOptions } from '@reduxjs/toolkit' -import { configureStore, createSlice } from '@reduxjs/toolkit' +import { + configureStore, + createSlice, + MiddlewareArray, + EnhancerArray, +} from '@reduxjs/toolkit' import type { ThunkMiddleware, ThunkAction, ThunkDispatch } from 'redux-thunk' import { thunk } from 'redux-thunk' import { expectNotAny, expectType } from './helpers' @@ -67,20 +72,26 @@ const _anyMiddleware: any = () => () => () => {} } /* - * Test: configureStore() accepts middleware array. + * Test: configureStore() accepts MiddlewareArray, but not plain array. */ { const middleware: Middleware = (store) => (next) => next configureStore({ reducer: () => 0, + middleware: new MiddlewareArray(middleware), + }) + + configureStore({ + reducer: () => 0, + // @ts-expect-error middleware: [middleware], }) configureStore({ reducer: () => 0, // @ts-expect-error - middleware: ['not middleware'], + middleware: new MiddlewareArray('not middleware'), }) } @@ -133,13 +144,21 @@ const _anyMiddleware: any = () => () => () => {} } /* - * Test: configureStore() accepts store enhancer. + * Test: configureStore() accepts store EnhancerArray, but not plain array */ { { + const enhancer = applyMiddleware(() => (next) => next) + const store = configureStore({ reducer: () => 0, - enhancers: [applyMiddleware(() => (next) => next)] as const, + enhancers: new EnhancerArray(enhancer), + }) + + const store2 = configureStore({ + reducer: () => 0, + // @ts-expect-error + enhancers: [enhancer], }) expectType>( @@ -150,7 +169,7 @@ const _anyMiddleware: any = () => () => () => {} configureStore({ reducer: () => 0, // @ts-expect-error - enhancers: ['not a store enhancer'], + enhancers: new EnhancerArray('not a store enhancer'), }) { @@ -178,10 +197,10 @@ const _anyMiddleware: any = () => () => () => {} const store = configureStore({ reducer: () => 0, - enhancers: [ + enhancers: new EnhancerArray( somePropertyStoreEnhancer, - anotherPropertyStoreEnhancer, - ] as const, + anotherPropertyStoreEnhancer + ), }) expectType(store.dispatch) @@ -240,11 +259,10 @@ const _anyMiddleware: any = () => () => () => {} const store = configureStore({ reducer: () => ({ aProperty: 0 }), - enhancers: [ + enhancers: new EnhancerArray( someStateExtendingEnhancer, - anotherStateExtendingEnhancer, - // this doesn't work without the as const - ] as const, + anotherStateExtendingEnhancer + ), }) const state = store.getState() @@ -512,7 +530,7 @@ const _anyMiddleware: any = () => () => () => {} { const store = configureStore({ reducer: reducerA, - middleware: [], + middleware: new MiddlewareArray(), }) // @ts-expect-error store.dispatch(thunkA()) @@ -525,7 +543,7 @@ const _anyMiddleware: any = () => () => () => {} { const store = configureStore({ reducer: reducerA, - middleware: [thunk] as [ThunkMiddleware], + middleware: new MiddlewareArray(thunk as ThunkMiddleware), }) store.dispatch(thunkA()) // @ts-expect-error @@ -537,21 +555,9 @@ const _anyMiddleware: any = () => () => () => {} { const store = configureStore({ reducer: reducerA, - middleware: [] as any as [Middleware<(a: StateA) => boolean, StateA>], - }) - const result: boolean = store.dispatch(5) - // @ts-expect-error - const result2: string = store.dispatch(5) - } - /** - * Test: read-only middleware tuple - */ - { - const store = configureStore({ - reducer: reducerA, - middleware: [] as any as readonly [ - Middleware<(a: StateA) => boolean, StateA> - ], + middleware: new MiddlewareArray( + 0 as unknown as Middleware<(a: StateA) => boolean, StateA> + ), }) const result: boolean = store.dispatch(5) // @ts-expect-error @@ -561,11 +567,13 @@ const _anyMiddleware: any = () => () => () => {} * Test: multiple custom middleware */ { - const middleware = [] as any as [ - Middleware<(a: 'a') => 'A', StateA>, - Middleware<(b: 'b') => 'B', StateA>, - ThunkMiddleware - ] + const middleware = [] as any as MiddlewareArray< + [ + Middleware<(a: 'a') => 'A', StateA>, + Middleware<(b: 'b') => 'B', StateA>, + ThunkMiddleware + ] + > const store = configureStore({ reducer: reducerA, middleware, diff --git a/packages/toolkit/src/tests/serializableStateInvariantMiddleware.test.ts b/packages/toolkit/src/tests/serializableStateInvariantMiddleware.test.ts index 9235c60efc..1d8361b3d1 100644 --- a/packages/toolkit/src/tests/serializableStateInvariantMiddleware.test.ts +++ b/packages/toolkit/src/tests/serializableStateInvariantMiddleware.test.ts @@ -10,6 +10,7 @@ import { createSerializableStateInvariantMiddleware, findNonSerializableValue, isPlain, + MiddlewareArray, } from '@reduxjs/toolkit' import { isNestedFrozen } from '@internal/serializableStateInvariantMiddleware' @@ -100,7 +101,7 @@ describe('serializableStateInvariantMiddleware', () => { const store = configureStore({ reducer, - middleware: [serializableStateInvariantMiddleware], + middleware: new MiddlewareArray(serializableStateInvariantMiddleware), }) const symbol = Symbol.for('SOME_CONSTANT') @@ -147,7 +148,7 @@ describe('serializableStateInvariantMiddleware', () => { reducer: { testSlice: reducer, }, - middleware: [serializableStateInvariantMiddleware], + middleware: new MiddlewareArray(serializableStateInvariantMiddleware), }) store.dispatch({ type: ACTION_TYPE }) @@ -207,7 +208,7 @@ describe('serializableStateInvariantMiddleware', () => { reducer: { testSlice: reducer, }, - middleware: [serializableStateInvariantMiddleware], + middleware: new MiddlewareArray(serializableStateInvariantMiddleware), }) store.dispatch({ type: ACTION_TYPE }) @@ -254,7 +255,7 @@ describe('serializableStateInvariantMiddleware', () => { reducer: { testSlice: reducer, }, - middleware: [serializableStateInvariantMiddleware], + middleware: new MiddlewareArray(serializableStateInvariantMiddleware), }) store.dispatch({ type: ACTION_TYPE }) @@ -298,7 +299,7 @@ describe('serializableStateInvariantMiddleware', () => { reducer: { testSlice: reducer, }, - middleware: [serializableStateInvariantMiddleware], + middleware: new MiddlewareArray(serializableStateInvariantMiddleware), }) store.dispatch({ type: ACTION_TYPE }) @@ -322,7 +323,7 @@ describe('serializableStateInvariantMiddleware', () => { const store = configureStore({ reducer: () => ({}), - middleware: [serializableStateMiddleware], + middleware: new MiddlewareArray(serializableStateMiddleware), }) expect(numTimesCalled).toBe(0) @@ -347,7 +348,9 @@ describe('serializableStateInvariantMiddleware', () => { it('default value: meta.arg', () => { configureStore({ reducer, - middleware: [createSerializableStateInvariantMiddleware()], + middleware: new MiddlewareArray( + createSerializableStateInvariantMiddleware() + ), }).dispatch({ type: 'test', meta: { arg: nonSerializableValue } }) expect(getLog().log).toMatchInlineSnapshot(`""`) @@ -356,11 +359,11 @@ describe('serializableStateInvariantMiddleware', () => { it('default value can be overridden', () => { configureStore({ reducer, - middleware: [ + middleware: new MiddlewareArray( createSerializableStateInvariantMiddleware({ ignoredActionPaths: [], - }), - ], + }) + ), }).dispatch({ type: 'test', meta: { arg: nonSerializableValue } }) expect(getLog().log).toMatchInlineSnapshot(` @@ -379,11 +382,11 @@ describe('serializableStateInvariantMiddleware', () => { it('can specify (multiple) different values', () => { configureStore({ reducer, - middleware: [ + middleware: new MiddlewareArray( createSerializableStateInvariantMiddleware({ ignoredActionPaths: ['payload', 'meta.arg'], - }), - ], + }) + ), }).dispatch({ type: 'test', payload: { arg: nonSerializableValue }, @@ -396,11 +399,11 @@ describe('serializableStateInvariantMiddleware', () => { it('can specify regexp', () => { configureStore({ reducer, - middleware: [ + middleware: new MiddlewareArray( createSerializableStateInvariantMiddleware({ ignoredActionPaths: [/^payload\..*$/], - }), - ], + }) + ), }).dispatch({ type: 'test', payload: { arg: nonSerializableValue }, @@ -424,7 +427,7 @@ describe('serializableStateInvariantMiddleware', () => { const store = configureStore({ reducer: () => ({}), - middleware: [serializableStateMiddleware], + middleware: new MiddlewareArray(serializableStateMiddleware), }) expect(numTimesCalled).toBe(0) @@ -487,7 +490,7 @@ describe('serializableStateInvariantMiddleware', () => { reducer: { testSlice: reducer, }, - middleware: [serializableStateInvariantMiddleware], + middleware: new MiddlewareArray(serializableStateInvariantMiddleware), }) store.dispatch({ type: ACTION_TYPE }) @@ -506,15 +509,15 @@ describe('serializableStateInvariantMiddleware', () => { const reducer = () => badValue const store = configureStore({ reducer, - middleware: [ + middleware: new MiddlewareArray( createSerializableStateInvariantMiddleware({ isSerializable: () => { numTimesCalled++ return true }, ignoreState: true, - }), - ], + }) + ), }) expect(numTimesCalled).toBe(0) @@ -533,7 +536,7 @@ describe('serializableStateInvariantMiddleware', () => { const reducer = () => badValue const store = configureStore({ reducer, - middleware: [ + middleware: new MiddlewareArray( createSerializableStateInvariantMiddleware({ isSerializable: () => { numTimesCalled++ @@ -541,8 +544,8 @@ describe('serializableStateInvariantMiddleware', () => { }, ignoreState: true, ignoreActions: true, - }), - ], + }) + ), }) expect(numTimesCalled).toBe(0) @@ -565,7 +568,7 @@ describe('serializableStateInvariantMiddleware', () => { reducer: { testSlice: reducer, }, - middleware: [serializableStateInvariantMiddleware], + middleware: new MiddlewareArray(serializableStateInvariantMiddleware), }) store.dispatch({ @@ -591,7 +594,7 @@ describe('serializableStateInvariantMiddleware', () => { reducer: { testSlice: reducer, }, - middleware: [serializableStateInvariantMiddleware], + middleware: new MiddlewareArray(serializableStateInvariantMiddleware), }) store.dispatch({ type: 'SOME_ACTION' }) @@ -615,7 +618,7 @@ describe('serializableStateInvariantMiddleware', () => { if (action.type === 'SET_STATE') return action.payload return state }, - middleware: [serializableStateInvariantMiddleware], + middleware: new MiddlewareArray(serializableStateInvariantMiddleware), }) const state = createNextState([], () => diff --git a/packages/toolkit/src/tsHelpers.ts b/packages/toolkit/src/tsHelpers.ts index 1b331c9962..f4486decf5 100644 --- a/packages/toolkit/src/tsHelpers.ts +++ b/packages/toolkit/src/tsHelpers.ts @@ -91,7 +91,7 @@ export type ExcludeFromTuple = T extends [ : Acc type ExtractDispatchFromMiddlewareTuple< - MiddlewareTuple extends any[], + MiddlewareTuple extends readonly any[], Acc extends {} > = MiddlewareTuple extends [infer Head, ...infer Tail] ? ExtractDispatchFromMiddlewareTuple< @@ -109,7 +109,7 @@ export type ExtractDispatchExtensions = M extends MiddlewareArray< : never type ExtractStoreExtensionsFromEnhancerTuple< - EnhancerTuple extends any[], + EnhancerTuple extends readonly any[], Acc extends {} > = EnhancerTuple extends [infer Head, ...infer Tail] ? ExtractStoreExtensionsFromEnhancerTuple< @@ -133,7 +133,7 @@ export type ExtractStoreExtensions = E extends EnhancerArray< : never type ExtractStateExtensionsFromEnhancerTuple< - EnhancerTuple extends any[], + EnhancerTuple extends readonly any[], Acc extends {} > = EnhancerTuple extends [infer Head, ...infer Tail] ? ExtractStateExtensionsFromEnhancerTuple< diff --git a/packages/toolkit/src/utils.ts b/packages/toolkit/src/utils.ts index 3a55bab1a4..efb6805b23 100644 --- a/packages/toolkit/src/utils.ts +++ b/packages/toolkit/src/utils.ts @@ -44,7 +44,7 @@ export function find( * @public */ export class MiddlewareArray< - Middlewares extends Middleware[] + Middlewares extends readonly Middleware[] > extends Array { constructor(...items: Middlewares) constructor(...args: any[]) { @@ -87,7 +87,7 @@ export class MiddlewareArray< * @public */ export class EnhancerArray< - Enhancers extends StoreEnhancer[] + Enhancers extends readonly StoreEnhancer[] > extends Array { constructor(...items: Enhancers) constructor(...args: any[]) { From ed00f979470199c630970e0778c76f59d35ff83b Mon Sep 17 00:00:00 2001 From: Ben Durrant Date: Fri, 19 May 2023 21:59:42 +0100 Subject: [PATCH 211/412] Require enhancer to be a callback --- packages/toolkit/src/configureStore.ts | 17 +++++++---- .../toolkit/src/tests/configureStore.test.ts | 30 +++++++++++++------ .../src/tests/configureStore.typetest.ts | 24 ++++++++------- 3 files changed, 45 insertions(+), 26 deletions(-) diff --git a/packages/toolkit/src/configureStore.ts b/packages/toolkit/src/configureStore.ts index 39f2aaaefa..cfa4419381 100644 --- a/packages/toolkit/src/configureStore.ts +++ b/packages/toolkit/src/configureStore.ts @@ -23,7 +23,7 @@ import type { ExtractStoreExtensions, ExtractStateExtensions, } from './tsHelpers' -import type { EnhancerArray, MiddlewareArray } from './utils' +import type { MiddlewareArray, EnhancerArray } from './utils' import type { GetDefaultEnhancers } from './getDefaultEnhancers' import { buildGetDefaultEnhancers } from './getDefaultEnhancers' @@ -82,7 +82,7 @@ export interface ConfigureStoreOptions< * and should return a new array (such as `getDefaultEnhancers().concat(offline)`). * If you only need to add middleware, you can use the `middleware` parameter instead. */ - enhancers?: ((getDefaultEnhancers: GetDefaultEnhancers) => E) | E + enhancers?: (getDefaultEnhancers: GetDefaultEnhancers) => E } export type Middlewares = ReadonlyArray> @@ -174,13 +174,18 @@ export function configureStore< const middlewareEnhancer = applyMiddleware(...finalMiddleware) const getDefaultEnhancers = buildGetDefaultEnhancers(middlewareEnhancer) + + if (!IS_PRODUCTION && enhancers && typeof enhancers !== 'function') { + throw new Error('"enhancers" field must be a callback') + } + let storeEnhancers = - (typeof enhancers === 'function' + typeof enhancers === 'function' ? enhancers(getDefaultEnhancers) - : enhancers) ?? getDefaultEnhancers() + : getDefaultEnhancers() if (!IS_PRODUCTION && !Array.isArray(storeEnhancers)) { - throw new Error('enhancers must be an array') + throw new Error('"enhancers" callback must return an array') } if ( !IS_PRODUCTION && @@ -196,7 +201,7 @@ export function configureStore< !storeEnhancers.includes(middlewareEnhancer) ) { console.error( - 'middlewares were provided, but middleware enhancer was not included in final enhancers' + 'middlewares were provided, but middleware enhancer was not included in final enhancers - make sure to call `getDefaultEnhancers`' ) } diff --git a/packages/toolkit/src/tests/configureStore.test.ts b/packages/toolkit/src/tests/configureStore.test.ts index 1ade822fc1..b4ce6c994d 100644 --- a/packages/toolkit/src/tests/configureStore.test.ts +++ b/packages/toolkit/src/tests/configureStore.test.ts @@ -283,16 +283,28 @@ describe('configureStore', async () => { undefined, expect.any(Function) ) + + expect(dummyEnhancerCalled).toBe(true) }) - it('accepts a callback for customizing enhancers', () => { - const store = configureStore({ - reducer, - enhancers: (getDefaultEnhancers) => - getDefaultEnhancers().concat(dummyEnhancer), + describe('invalid arguments', () => { + test('enhancers is not a callback', () => { + expect(() => configureStore({ reducer, enhancers: [] as any })).toThrow( + '"enhancers" field must be a callback' + ) }) - expect(dummyEnhancerCalled).toBe(true) + test('callback fails to return array', () => { + expect(() => + configureStore({ reducer, enhancers: (() => {}) as any }) + ).toThrow('"enhancers" callback must return an array') + }) + + test('array contains non-function', () => { + expect(() => + configureStore({ reducer, enhancers: (() => ['']) as any }) + ).toThrow('each enhancer provided to configureStore must be a function') + }) }) const consoleSpy = vi.spyOn(console, 'error').mockImplementation(() => {}) @@ -306,20 +318,20 @@ describe('configureStore', async () => { it('warns if middleware enhancer is excluded from final array when middlewares are provided', () => { const store = configureStore({ reducer, - enhancers: new EnhancerArray(dummyEnhancer), + enhancers: () => new EnhancerArray(dummyEnhancer), }) expect(dummyEnhancerCalled).toBe(true) expect(consoleSpy).toHaveBeenCalledWith( - 'middlewares were provided, but middleware enhancer was not included in final enhancers' + 'middlewares were provided, but middleware enhancer was not included in final enhancers - make sure to call `getDefaultEnhancers`' ) }) it("doesn't warn when middleware enhancer is excluded if no middlewares provided", () => { const store = configureStore({ reducer, middleware: new MiddlewareArray(), - enhancers: new EnhancerArray(dummyEnhancer), + enhancers: () => new EnhancerArray(dummyEnhancer), }) expect(dummyEnhancerCalled).toBe(true) diff --git a/packages/toolkit/src/tests/configureStore.typetest.ts b/packages/toolkit/src/tests/configureStore.typetest.ts index d4d02681e4..160659d17d 100644 --- a/packages/toolkit/src/tests/configureStore.typetest.ts +++ b/packages/toolkit/src/tests/configureStore.typetest.ts @@ -152,13 +152,13 @@ const _anyMiddleware: any = () => () => () => {} const store = configureStore({ reducer: () => 0, - enhancers: new EnhancerArray(enhancer), + enhancers: () => new EnhancerArray(enhancer), }) const store2 = configureStore({ reducer: () => 0, // @ts-expect-error - enhancers: [enhancer], + enhancers: () => [enhancer], }) expectType>( @@ -169,7 +169,7 @@ const _anyMiddleware: any = () => () => () => {} configureStore({ reducer: () => 0, // @ts-expect-error - enhancers: new EnhancerArray('not a store enhancer'), + enhancers: () => new EnhancerArray('not a store enhancer'), }) { @@ -197,10 +197,11 @@ const _anyMiddleware: any = () => () => () => {} const store = configureStore({ reducer: () => 0, - enhancers: new EnhancerArray( - somePropertyStoreEnhancer, - anotherPropertyStoreEnhancer - ), + enhancers: () => + new EnhancerArray( + somePropertyStoreEnhancer, + anotherPropertyStoreEnhancer + ), }) expectType(store.dispatch) @@ -259,10 +260,11 @@ const _anyMiddleware: any = () => () => () => {} const store = configureStore({ reducer: () => ({ aProperty: 0 }), - enhancers: new EnhancerArray( - someStateExtendingEnhancer, - anotherStateExtendingEnhancer - ), + enhancers: () => + new EnhancerArray( + someStateExtendingEnhancer, + anotherStateExtendingEnhancer + ), }) const state = store.getState() From b2fa3802a2cc0000e35b018e7a3e73ae3a1d394f Mon Sep 17 00:00:00 2001 From: Matt Date: Sat, 18 Feb 2023 18:06:34 +0000 Subject: [PATCH 212/412] Add Id type parameter to createEntityAdapter --- packages/rtk-query-codegen-openapi/.gitignore | 2 + packages/toolkit/README.md | 4 +- .../toolkit/src/entities/create_adapter.ts | 25 ++- packages/toolkit/src/entities/entity_state.ts | 13 +- packages/toolkit/src/entities/models.ts | 180 +++++++++--------- .../src/entities/sorted_state_adapter.ts | 22 +-- .../toolkit/src/entities/state_adapter.ts | 22 +-- .../toolkit/src/entities/state_selectors.ts | 22 +-- .../src/entities/tests/entity_state.test.ts | 2 +- .../tests/sorted_state_adapter.test.ts | 6 +- .../src/entities/tests/state_adapter.test.ts | 2 +- .../entities/tests/state_selectors.test.ts | 24 ++- .../tests/unsorted_state_adapter.test.ts | 4 +- .../src/entities/unsorted_state_adapter.ts | 41 ++-- packages/toolkit/src/entities/utils.ts | 21 +- .../tests/listenerMiddleware.test.ts | 3 +- .../toolkit/src/listenerMiddleware/types.ts | 6 +- packages/toolkit/src/query/retry.ts | 2 +- .../src/query/tests/buildHooks.test.tsx | 3 +- .../toolkit/src/query/tests/createApi.test.ts | 3 +- .../src/query/tests/errorHandling.test.tsx | 9 +- packages/toolkit/src/query/utils/joinUrls.ts | 2 +- .../toolkit/src/tests/combinedTest.test.ts | 2 +- .../toolkit/src/tests/configureStore.test.ts | 2 +- .../src/tests/createEntityAdapter.typetest.ts | 46 +++-- .../injectableCombineReducers.example.ts | 64 +++++++ .../toolkit/src/tests/matchers.typetest.ts | 1 - 27 files changed, 330 insertions(+), 203 deletions(-) create mode 100644 packages/toolkit/src/tests/injectableCombineReducers.example.ts diff --git a/packages/rtk-query-codegen-openapi/.gitignore b/packages/rtk-query-codegen-openapi/.gitignore index 089b2c2b21..9cd4214931 100644 --- a/packages/rtk-query-codegen-openapi/.gitignore +++ b/packages/rtk-query-codegen-openapi/.gitignore @@ -137,3 +137,5 @@ yalc.lock lib yarn.lock test/tmp/example.ts +test/tmp/emptyApi.ts +test/tmp/out.ts \ No newline at end of file diff --git a/packages/toolkit/README.md b/packages/toolkit/README.md index 5923eb224e..2274bca0d3 100644 --- a/packages/toolkit/README.md +++ b/packages/toolkit/README.md @@ -60,8 +60,8 @@ Redux Toolkit includes these APIs: - `configureStore()`: wraps `createStore` to provide simplified configuration options and good defaults. It can automatically combine your slice reducers, add whatever Redux middleware you supply, includes `redux-thunk` by default, and enables use of the Redux DevTools Extension. - `createReducer()`: lets you supply a lookup table of action types to case reducer functions, rather than writing switch statements. In addition, it automatically uses the [`immer` library](https://github.com/mweststrate/immer) to let you write simpler immutable updates with normal mutative code, like `state.todos[3].completed = true`. - `createAction()`: generates an action creator function for the given action type string. The function itself has `toString()` defined, so that it can be used in place of the type constant. -- `createSlice()`: combines `createReducer()` + `createAction()`. Accepts an object of reducer functions, a slice name, and an initial state value, and automatically generates a slice reducer with corresponding action creators and action types. -- `createListenerMiddleware()`: lets you define "listener" entries that contain an "effect" callback with additional logic, and a way to specify when that callback should run based on dispatched actions or state changes. A lightweight alternative to Redux async middleware like sagas and observables. +- `createSlice()`: combines `createReducer()` + `createAction()`. Accepts an object of reducer functions, a slice name, and an initial state value, and automatically generates a slice reducer with corresponding action creators and action types. +- `createListenerMiddleware()`: lets you define "listener" entries that contain an "effect" callback with additional logic, and a way to specify when that callback should run based on dispatched actions or state changes. A lightweight alternative to Redux async middleware like sagas and observables. - `createAsyncThunk()`: accepts an action type string and a function that returns a promise, and generates a thunk that dispatches `pending/resolved/rejected` action types based on that promise - `createEntityAdapter()`: generates a set of reusable reducers and selectors to manage normalized data in the store - The `createSelector()` utility from the [Reselect](https://github.com/reduxjs/reselect) library, re-exported for ease of use. diff --git a/packages/toolkit/src/entities/create_adapter.ts b/packages/toolkit/src/entities/create_adapter.ts index 0d0c77e9d1..83ad6c6515 100644 --- a/packages/toolkit/src/entities/create_adapter.ts +++ b/packages/toolkit/src/entities/create_adapter.ts @@ -3,12 +3,27 @@ import type { Comparer, IdSelector, EntityAdapter, + EntityId, } from './models' import { createInitialStateFactory } from './entity_state' import { createSelectorsFactory } from './state_selectors' import { createSortedStateAdapter } from './sorted_state_adapter' import { createUnsortedStateAdapter } from './unsorted_state_adapter' +export interface EntityAdapterOptions { + selectId?: IdSelector + sortComparer?: false | Comparer +} + +export function createEntityAdapter(options: { + selectId: IdSelector + sortComparer?: false | Comparer +}): EntityAdapter + +export function createEntityAdapter(options?: { + sortComparer?: false | Comparer +}): EntityAdapter + /** * * @param options @@ -17,18 +32,18 @@ import { createUnsortedStateAdapter } from './unsorted_state_adapter' */ export function createEntityAdapter( options: { - selectId?: IdSelector + selectId?: IdSelector sortComparer?: false | Comparer } = {} -): EntityAdapter { - const { selectId, sortComparer }: EntityDefinition = { +): EntityAdapter { + const { selectId, sortComparer }: EntityDefinition = { sortComparer: false, selectId: (instance: any) => instance.id, ...options, } - const stateFactory = createInitialStateFactory() - const selectorsFactory = createSelectorsFactory() + const stateFactory = createInitialStateFactory() + const selectorsFactory = createSelectorsFactory() const stateAdapter = sortComparer ? createSortedStateAdapter(selectId, sortComparer) : createUnsortedStateAdapter(selectId) diff --git a/packages/toolkit/src/entities/entity_state.ts b/packages/toolkit/src/entities/entity_state.ts index 60924e3ae4..8a9c6a895c 100644 --- a/packages/toolkit/src/entities/entity_state.ts +++ b/packages/toolkit/src/entities/entity_state.ts @@ -1,17 +1,20 @@ -import type { EntityState } from './models' +import type { EntityId, EntityState } from './models' -export function getInitialEntityState(): EntityState { +export function getInitialEntityState(): EntityState< + T, + Id +> { return { ids: [], entities: {}, } } -export function createInitialStateFactory() { - function getInitialState(): EntityState +export function createInitialStateFactory() { + function getInitialState(): EntityState function getInitialState( additionalState: S - ): EntityState & S + ): EntityState & S function getInitialState(additionalState: any = {}): any { return Object.assign(getInitialEntityState(), additionalState) } diff --git a/packages/toolkit/src/entities/models.ts b/packages/toolkit/src/entities/models.ts index e57c4aee9a..d20db1c5de 100644 --- a/packages/toolkit/src/entities/models.ts +++ b/packages/toolkit/src/entities/models.ts @@ -14,158 +14,166 @@ export type Comparer = (a: T, b: T) => number /** * @public */ -export type IdSelector = (model: T) => EntityId +export type IdSelector = (model: T) => Id /** * @public */ -export interface DictionaryNum { - [id: number]: T | undefined -} - -/** - * @public - */ -export interface Dictionary extends DictionaryNum { - [id: string]: T | undefined -} +export type Dictionary = Partial> /** * @public */ -export type Update = { id: EntityId; changes: Partial } +export type Update = { id: Id; changes: Partial } /** * @public */ -export interface EntityState { - ids: EntityId[] - entities: Dictionary +export interface EntityState { + ids: Id[] + entities: Dictionary } /** * @public */ -export interface EntityDefinition { - selectId: IdSelector +export interface EntityDefinition { + selectId: IdSelector sortComparer: false | Comparer } -export type PreventAny = IsAny, S> +export type PreventAny = IsAny< + S, + EntityState, + S +> /** * @public */ -export interface EntityStateAdapter { - addOne>(state: PreventAny, entity: T): S - addOne>( - state: PreventAny, +export interface EntityStateAdapter { + addOne>( + state: PreventAny, + entity: T + ): S + addOne>( + state: PreventAny, action: PayloadAction ): S - addMany>( - state: PreventAny, - entities: readonly T[] | Record + addMany>( + state: PreventAny, + entities: readonly T[] | Record ): S - addMany>( - state: PreventAny, - entities: PayloadAction> + addMany>( + state: PreventAny, + entities: PayloadAction> ): S - setOne>(state: PreventAny, entity: T): S - setOne>( - state: PreventAny, + setOne>( + state: PreventAny, + entity: T + ): S + setOne>( + state: PreventAny, action: PayloadAction ): S - setMany>( - state: PreventAny, - entities: readonly T[] | Record + setMany>( + state: PreventAny, + entities: readonly T[] | Record ): S - setMany>( - state: PreventAny, - entities: PayloadAction> + setMany>( + state: PreventAny, + entities: PayloadAction> ): S - setAll>( - state: PreventAny, - entities: readonly T[] | Record + setAll>( + state: PreventAny, + entities: readonly T[] | Record ): S - setAll>( - state: PreventAny, - entities: PayloadAction> + setAll>( + state: PreventAny, + entities: PayloadAction> ): S - removeOne>(state: PreventAny, key: EntityId): S - removeOne>( - state: PreventAny, - key: PayloadAction + removeOne>( + state: PreventAny, + key: Id + ): S + removeOne>( + state: PreventAny, + key: PayloadAction ): S - removeMany>( - state: PreventAny, - keys: readonly EntityId[] + removeMany>( + state: PreventAny, + keys: readonly Id[] ): S - removeMany>( - state: PreventAny, - keys: PayloadAction + removeMany>( + state: PreventAny, + keys: PayloadAction ): S - removeAll>(state: PreventAny): S + removeAll>(state: PreventAny): S - updateOne>( - state: PreventAny, - update: Update + updateOne>( + state: PreventAny, + update: Update ): S - updateOne>( - state: PreventAny, - update: PayloadAction> + updateOne>( + state: PreventAny, + update: PayloadAction> ): S - updateMany>( - state: PreventAny, - updates: ReadonlyArray> + updateMany>( + state: PreventAny, + updates: ReadonlyArray> ): S - updateMany>( - state: PreventAny, - updates: PayloadAction>> + updateMany>( + state: PreventAny, + updates: PayloadAction>> ): S - upsertOne>(state: PreventAny, entity: T): S - upsertOne>( - state: PreventAny, + upsertOne>( + state: PreventAny, + entity: T + ): S + upsertOne>( + state: PreventAny, entity: PayloadAction ): S - upsertMany>( - state: PreventAny, - entities: readonly T[] | Record + upsertMany>( + state: PreventAny, + entities: readonly T[] | Record ): S - upsertMany>( - state: PreventAny, - entities: PayloadAction> + upsertMany>( + state: PreventAny, + entities: PayloadAction> ): S } /** * @public */ -export interface EntitySelectors { - selectIds: (state: V) => EntityId[] - selectEntities: (state: V) => Dictionary +export interface EntitySelectors { + selectIds: (state: V) => Id[] + selectEntities: (state: V) => Dictionary selectAll: (state: V) => T[] selectTotal: (state: V) => number - selectById: (state: V, id: EntityId) => T | undefined + selectById: (state: V, id: Id) => T | undefined } /** * @public */ -export interface EntityAdapter extends EntityStateAdapter { - selectId: IdSelector +export interface EntityAdapter + extends EntityStateAdapter { + selectId: IdSelector sortComparer: false | Comparer - getInitialState(): EntityState - getInitialState(state: S): EntityState & S - getSelectors(): EntitySelectors> + getInitialState(): EntityState + getInitialState(state: S): EntityState & S + getSelectors(): EntitySelectors, Id> getSelectors( - selectState: (state: V) => EntityState - ): EntitySelectors + selectState: (state: V) => EntityState + ): EntitySelectors } diff --git a/packages/toolkit/src/entities/sorted_state_adapter.ts b/packages/toolkit/src/entities/sorted_state_adapter.ts index 4f1ed7a372..4bb745c6d3 100644 --- a/packages/toolkit/src/entities/sorted_state_adapter.ts +++ b/packages/toolkit/src/entities/sorted_state_adapter.ts @@ -14,11 +14,11 @@ import { splitAddedUpdatedEntities, } from './utils' -export function createSortedStateAdapter( - selectId: IdSelector, +export function createSortedStateAdapter( + selectId: IdSelector, sort: Comparer -): EntityStateAdapter { - type R = EntityState +): EntityStateAdapter { + type R = EntityState const { removeOne, removeMany, removeAll } = createUnsortedStateAdapter(selectId) @@ -28,7 +28,7 @@ export function createSortedStateAdapter( } function addManyMutably( - newEntities: readonly T[] | Record, + newEntities: readonly T[] | Record, state: R ): void { newEntities = ensureEntitiesArray(newEntities) @@ -47,7 +47,7 @@ export function createSortedStateAdapter( } function setManyMutably( - newEntities: readonly T[] | Record, + newEntities: readonly T[] | Record, state: R ): void { newEntities = ensureEntitiesArray(newEntities) @@ -57,7 +57,7 @@ export function createSortedStateAdapter( } function setAllMutably( - newEntities: readonly T[] | Record, + newEntities: readonly T[] | Record, state: R ): void { newEntities = ensureEntitiesArray(newEntities) @@ -67,12 +67,12 @@ export function createSortedStateAdapter( addManyMutably(newEntities, state) } - function updateOneMutably(update: Update, state: R): void { + function updateOneMutably(update: Update, state: R): void { return updateManyMutably([update], state) } function updateManyMutably( - updates: ReadonlyArray>, + updates: ReadonlyArray>, state: R ): void { let appliedUpdates = false @@ -103,10 +103,10 @@ export function createSortedStateAdapter( } function upsertManyMutably( - newEntities: readonly T[] | Record, + newEntities: readonly T[] | Record, state: R ): void { - const [added, updated] = splitAddedUpdatedEntities( + const [added, updated] = splitAddedUpdatedEntities( newEntities, selectId, state diff --git a/packages/toolkit/src/entities/state_adapter.ts b/packages/toolkit/src/entities/state_adapter.ts index 220abae40a..c37c56f25a 100644 --- a/packages/toolkit/src/entities/state_adapter.ts +++ b/packages/toolkit/src/entities/state_adapter.ts @@ -1,27 +1,27 @@ import { produce as createNextState, isDraft } from 'immer' -import type { EntityState, PreventAny } from './models' +import type { EntityId, EntityState, PreventAny } from './models' import type { PayloadAction } from '../createAction' import { isFSA } from '../createAction' import { IsAny } from '../tsHelpers' -export function createSingleArgumentStateOperator( - mutator: (state: EntityState) => void +export function createSingleArgumentStateOperator( + mutator: (state: EntityState) => void ) { - const operator = createStateOperator((_: undefined, state: EntityState) => - mutator(state) + const operator = createStateOperator( + (_: undefined, state: EntityState) => mutator(state) ) - return function operation>( - state: PreventAny + return function operation>( + state: PreventAny ): S { return operator(state as S, undefined) } } -export function createStateOperator( - mutator: (arg: R, state: EntityState) => void +export function createStateOperator( + mutator: (arg: R, state: EntityState) => void ) { - return function operation>( + return function operation>( state: S, arg: R | PayloadAction ): S { @@ -31,7 +31,7 @@ export function createStateOperator( return isFSA(arg) } - const runMutator = (draft: EntityState) => { + const runMutator = (draft: EntityState) => { if (isPayloadActionArgument(arg)) { mutator(arg.payload, draft) } else { diff --git a/packages/toolkit/src/entities/state_selectors.ts b/packages/toolkit/src/entities/state_selectors.ts index 46f59d3d9e..92a0f82378 100644 --- a/packages/toolkit/src/entities/state_selectors.ts +++ b/packages/toolkit/src/entities/state_selectors.ts @@ -7,17 +7,17 @@ import type { EntityId, } from './models' -export function createSelectorsFactory() { - function getSelectors(): EntitySelectors> +export function createSelectorsFactory() { + function getSelectors(): EntitySelectors, Id> function getSelectors( - selectState: (state: V) => EntityState - ): EntitySelectors + selectState: (state: V) => EntityState + ): EntitySelectors function getSelectors( - selectState?: (state: V) => EntityState - ): EntitySelectors { - const selectIds = (state: EntityState) => state.ids + selectState?: (state: V) => EntityState + ): EntitySelectors { + const selectIds = (state: EntityState) => state.ids - const selectEntities = (state: EntityState) => state.entities + const selectEntities = (state: EntityState) => state.entities const selectAll = createDraftSafeSelector( selectIds, @@ -25,9 +25,9 @@ export function createSelectorsFactory() { (ids, entities): T[] => ids.map((id) => entities[id]!) ) - const selectId = (_: unknown, id: EntityId) => id + const selectId = (_: unknown, id: Id) => id - const selectById = (entities: Dictionary, id: EntityId) => entities[id] + const selectById = (entities: Dictionary, id: Id) => entities[id] const selectTotal = createDraftSafeSelector(selectIds, (ids) => ids.length) @@ -46,7 +46,7 @@ export function createSelectorsFactory() { } const selectGlobalizedEntities = createDraftSafeSelector( - selectState as Selector>, + selectState as Selector>, selectEntities ) diff --git a/packages/toolkit/src/entities/tests/entity_state.test.ts b/packages/toolkit/src/entities/tests/entity_state.test.ts index 773c96b89b..f28048de80 100644 --- a/packages/toolkit/src/entities/tests/entity_state.test.ts +++ b/packages/toolkit/src/entities/tests/entity_state.test.ts @@ -6,7 +6,7 @@ import { createSlice } from '../../createSlice' import type { BookModel } from './fixtures/book' describe('Entity State', () => { - let adapter: EntityAdapter + let adapter: EntityAdapter beforeEach(() => { adapter = createEntityAdapter({ diff --git a/packages/toolkit/src/entities/tests/sorted_state_adapter.test.ts b/packages/toolkit/src/entities/tests/sorted_state_adapter.test.ts index c501d69a34..e3287d0e3b 100644 --- a/packages/toolkit/src/entities/tests/sorted_state_adapter.test.ts +++ b/packages/toolkit/src/entities/tests/sorted_state_adapter.test.ts @@ -11,8 +11,8 @@ import { import { createNextState } from '../..' describe('Sorted State Adapter', () => { - let adapter: EntityAdapter - let state: EntityState + let adapter: EntityAdapter + let state: EntityState beforeAll(() => { //eslint-disable-next-line @@ -349,7 +349,7 @@ describe('Sorted State Adapter', () => { order: number ts: number } - const sortedItemsAdapter = createEntityAdapter({ + const sortedItemsAdapter = createEntityAdapter({ sortComparer: (a, b) => a.order - b.order, }) const withInitialItems = sortedItemsAdapter.setAll( diff --git a/packages/toolkit/src/entities/tests/state_adapter.test.ts b/packages/toolkit/src/entities/tests/state_adapter.test.ts index ed515fed6e..ec760080ce 100644 --- a/packages/toolkit/src/entities/tests/state_adapter.test.ts +++ b/packages/toolkit/src/entities/tests/state_adapter.test.ts @@ -6,7 +6,7 @@ import { createSlice } from '../../createSlice' import type { BookModel } from './fixtures/book' describe('createStateOperator', () => { - let adapter: EntityAdapter + let adapter: EntityAdapter beforeEach(() => { adapter = createEntityAdapter({ diff --git a/packages/toolkit/src/entities/tests/state_selectors.test.ts b/packages/toolkit/src/entities/tests/state_selectors.test.ts index 9458c9c93a..b368e30b84 100644 --- a/packages/toolkit/src/entities/tests/state_selectors.test.ts +++ b/packages/toolkit/src/entities/tests/state_selectors.test.ts @@ -9,11 +9,11 @@ import { createSelector } from 'reselect' describe('Entity State Selectors', () => { describe('Composed Selectors', () => { interface State { - books: EntityState + books: EntityState } - let adapter: EntityAdapter - let selectors: EntitySelectors + let adapter: EntityAdapter + let selectors: EntitySelectors let state: State beforeEach(() => { @@ -65,10 +65,14 @@ describe('Entity State Selectors', () => { }) describe('Uncomposed Selectors', () => { - type State = EntityState - - let adapter: EntityAdapter - let selectors: EntitySelectors> + type State = EntityState + + let adapter: EntityAdapter + let selectors: EntitySelectors< + BookModel, + EntityState, + string + > let state: State beforeEach(() => { @@ -98,9 +102,9 @@ describe('Entity State Selectors', () => { }) it('should type single entity from Dictionary as entity type or undefined', () => { - expectType, BookModel | undefined>>( - createSelector(selectors.selectEntities, (entities) => entities[0]) - ) + expectType< + Selector, BookModel | undefined> + >(createSelector(selectors.selectEntities, (entities) => entities[0])) }) it('should create a selector for selecting the list of models', () => { diff --git a/packages/toolkit/src/entities/tests/unsorted_state_adapter.test.ts b/packages/toolkit/src/entities/tests/unsorted_state_adapter.test.ts index 13c49cf0ec..6d2fc8eaf3 100644 --- a/packages/toolkit/src/entities/tests/unsorted_state_adapter.test.ts +++ b/packages/toolkit/src/entities/tests/unsorted_state_adapter.test.ts @@ -10,8 +10,8 @@ import { import { createNextState } from '../..' describe('Unsorted State Adapter', () => { - let adapter: EntityAdapter - let state: EntityState + let adapter: EntityAdapter + let state: EntityState beforeAll(() => { //eslint-disable-next-line diff --git a/packages/toolkit/src/entities/unsorted_state_adapter.ts b/packages/toolkit/src/entities/unsorted_state_adapter.ts index 9113580ba8..e451acb32e 100644 --- a/packages/toolkit/src/entities/unsorted_state_adapter.ts +++ b/packages/toolkit/src/entities/unsorted_state_adapter.ts @@ -15,10 +15,10 @@ import { splitAddedUpdatedEntities, } from './utils' -export function createUnsortedStateAdapter( - selectId: IdSelector -): EntityStateAdapter { - type R = EntityState +export function createUnsortedStateAdapter( + selectId: IdSelector +): EntityStateAdapter { + type R = EntityState function addOneMutably(entity: T, state: R): void { const key = selectIdValue(entity, selectId) @@ -32,7 +32,7 @@ export function createUnsortedStateAdapter( } function addManyMutably( - newEntities: readonly T[] | Record, + newEntities: readonly T[] | Record, state: R ): void { newEntities = ensureEntitiesArray(newEntities) @@ -51,7 +51,7 @@ export function createUnsortedStateAdapter( } function setManyMutably( - newEntities: readonly T[] | Record, + newEntities: readonly T[] | Record, state: R ): void { newEntities = ensureEntitiesArray(newEntities) @@ -61,7 +61,7 @@ export function createUnsortedStateAdapter( } function setAllMutably( - newEntities: readonly T[] | Record, + newEntities: readonly T[] | Record, state: R ): void { newEntities = ensureEntitiesArray(newEntities) @@ -72,11 +72,11 @@ export function createUnsortedStateAdapter( addManyMutably(newEntities, state) } - function removeOneMutably(key: EntityId, state: R): void { + function removeOneMutably(key: Id, state: R): void { return removeManyMutably([key], state) } - function removeManyMutably(keys: readonly EntityId[], state: R): void { + function removeManyMutably(keys: readonly Id[], state: R): void { let didMutate = false keys.forEach((key) => { @@ -99,11 +99,14 @@ export function createUnsortedStateAdapter( } function takeNewKey( - keys: { [id: string]: EntityId }, - update: Update, + keys: { [id: string]: Id }, + update: Update, state: R ): boolean { - const original = state.entities[update.id] + const original: T | undefined = state.entities[update.id] + if (original === undefined) { + return false + } const updated: T = Object.assign({}, original, update.changes) const newKey = selectIdValue(updated, selectId) const hasNewKey = newKey !== update.id @@ -118,17 +121,17 @@ export function createUnsortedStateAdapter( return hasNewKey } - function updateOneMutably(update: Update, state: R): void { + function updateOneMutably(update: Update, state: R): void { return updateManyMutably([update], state) } function updateManyMutably( - updates: ReadonlyArray>, + updates: ReadonlyArray>, state: R ): void { - const newKeys: { [id: string]: EntityId } = {} + const newKeys: { [id: string]: Id } = {} - const updatesPerEntity: { [id: string]: Update } = {} + const updatesPerEntity: { [id: string]: Update } = {} updates.forEach((update) => { // Only apply updates to entities that currently exist @@ -158,7 +161,7 @@ export function createUnsortedStateAdapter( 0 if (didMutateIds) { - state.ids = Object.keys(state.entities) + state.ids = Object.keys(state.entities) as Id[] } } } @@ -168,10 +171,10 @@ export function createUnsortedStateAdapter( } function upsertManyMutably( - newEntities: readonly T[] | Record, + newEntities: readonly T[] | Record, state: R ): void { - const [added, updated] = splitAddedUpdatedEntities( + const [added, updated] = splitAddedUpdatedEntities( newEntities, selectId, state diff --git a/packages/toolkit/src/entities/utils.ts b/packages/toolkit/src/entities/utils.ts index 5a4be0fca5..8c86f6184c 100644 --- a/packages/toolkit/src/entities/utils.ts +++ b/packages/toolkit/src/entities/utils.ts @@ -1,6 +1,9 @@ import type { EntityState, IdSelector, Update, EntityId } from './models' -export function selectIdValue(entity: T, selectId: IdSelector) { +export function selectIdValue( + entity: T, + selectId: IdSelector +) { const key = selectId(entity) if (process.env.NODE_ENV !== 'production' && key === undefined) { @@ -17,8 +20,8 @@ export function selectIdValue(entity: T, selectId: IdSelector) { return key } -export function ensureEntitiesArray( - entities: readonly T[] | Record +export function ensureEntitiesArray( + entities: readonly T[] | Record ): readonly T[] { if (!Array.isArray(entities)) { entities = Object.values(entities) @@ -27,15 +30,15 @@ export function ensureEntitiesArray( return entities } -export function splitAddedUpdatedEntities( - newEntities: readonly T[] | Record, - selectId: IdSelector, - state: EntityState -): [T[], Update[]] { +export function splitAddedUpdatedEntities( + newEntities: readonly T[] | Record, + selectId: IdSelector, + state: EntityState +): [T[], Update[]] { newEntities = ensureEntitiesArray(newEntities) const added: T[] = [] - const updated: Update[] = [] + const updated: Update[] = [] for (const entity of newEntities) { const id = selectIdValue(entity, selectId) diff --git a/packages/toolkit/src/listenerMiddleware/tests/listenerMiddleware.test.ts b/packages/toolkit/src/listenerMiddleware/tests/listenerMiddleware.test.ts index 5417383685..e268d570b2 100644 --- a/packages/toolkit/src/listenerMiddleware/tests/listenerMiddleware.test.ts +++ b/packages/toolkit/src/listenerMiddleware/tests/listenerMiddleware.test.ts @@ -4,7 +4,8 @@ import { createSlice, isAnyOf, } from '@reduxjs/toolkit' -import { vi, Mock } from 'vitest' +import type { Mock } from 'vitest' +import { vi } from 'vitest' import type { AnyAction, PayloadAction, Action } from '@reduxjs/toolkit' diff --git a/packages/toolkit/src/listenerMiddleware/types.ts b/packages/toolkit/src/listenerMiddleware/types.ts index 650c78c6fe..88479b5f56 100644 --- a/packages/toolkit/src/listenerMiddleware/types.ts +++ b/packages/toolkit/src/listenerMiddleware/types.ts @@ -186,9 +186,9 @@ export interface ListenerEffectAPI< * rejects if the listener has been cancelled or is completed. * * The return value is `true` if the predicate succeeds or `false` if a timeout is provided and expires first. - * + * * ### Example - * + * * ```ts * const updateBy = createAction('counter/updateBy'); * @@ -210,7 +210,7 @@ export interface ListenerEffectAPI< * * The return value is the `[action, currentState, previousState]` combination that the predicate saw as arguments. * - * The promise resolves to null if a timeout is provided and expires first, + * The promise resolves to null if a timeout is provided and expires first, * * ### Example * diff --git a/packages/toolkit/src/query/retry.ts b/packages/toolkit/src/query/retry.ts index 8b2998aa19..7bd960c699 100644 --- a/packages/toolkit/src/query/retry.ts +++ b/packages/toolkit/src/query/retry.ts @@ -83,7 +83,7 @@ const retryWithBackoff: BaseQueryEnhancer< 5, ((defaultOptions as any) || EMPTY_OPTIONS).maxRetries, ((extraOptions as any) || EMPTY_OPTIONS).maxRetries, - ].filter(x => x !== undefined) + ].filter((x) => x !== undefined) const [maxRetries] = possibleMaxRetries.slice(-1) const defaultRetryCondition: RetryConditionFunction = (_, __, { attempt }) => diff --git a/packages/toolkit/src/query/tests/buildHooks.test.tsx b/packages/toolkit/src/query/tests/buildHooks.test.tsx index d94081e15c..6cd0fb3510 100644 --- a/packages/toolkit/src/query/tests/buildHooks.test.tsx +++ b/packages/toolkit/src/query/tests/buildHooks.test.tsx @@ -1,5 +1,6 @@ import * as React from 'react' -import { vi, SpyInstance } from 'vitest' +import type { SpyInstance } from 'vitest' +import { vi } from 'vitest' import type { UseMutation, UseQuery, diff --git a/packages/toolkit/src/query/tests/createApi.test.ts b/packages/toolkit/src/query/tests/createApi.test.ts index c735b51ebb..5db640ee1e 100644 --- a/packages/toolkit/src/query/tests/createApi.test.ts +++ b/packages/toolkit/src/query/tests/createApi.test.ts @@ -1,6 +1,7 @@ import type { SerializedError } from '@reduxjs/toolkit' import { configureStore, createAction, createReducer } from '@reduxjs/toolkit' -import { vi, SpyInstance } from 'vitest' +import type { SpyInstance } from 'vitest' +import { vi } from 'vitest' import type { Api, MutationDefinition, diff --git a/packages/toolkit/src/query/tests/errorHandling.test.tsx b/packages/toolkit/src/query/tests/errorHandling.test.tsx index b6a258e1a8..c33f724467 100644 --- a/packages/toolkit/src/query/tests/errorHandling.test.tsx +++ b/packages/toolkit/src/query/tests/errorHandling.test.tsx @@ -6,7 +6,14 @@ import type { AxiosError, AxiosRequestConfig, AxiosResponse } from 'axios' import axios from 'axios' import { expectExactType, hookWaitFor, setupApiStore } from './helpers' import { server } from './mocks/server' -import { fireEvent, render, waitFor, screen, act, renderHook } from '@testing-library/react' +import { + fireEvent, + render, + waitFor, + screen, + act, + renderHook, +} from '@testing-library/react' import { useDispatch } from 'react-redux' import type { AnyAction, ThunkDispatch } from '@reduxjs/toolkit' import type { BaseQueryApi } from '../baseQueryTypes' diff --git a/packages/toolkit/src/query/utils/joinUrls.ts b/packages/toolkit/src/query/utils/joinUrls.ts index 2087351dff..afcf1ad2e1 100644 --- a/packages/toolkit/src/query/utils/joinUrls.ts +++ b/packages/toolkit/src/query/utils/joinUrls.ts @@ -22,5 +22,5 @@ export function joinUrls( base = withoutTrailingSlash(base) url = withoutLeadingSlash(url) - return `${base}${delimiter}${url}`; + return `${base}${delimiter}${url}` } diff --git a/packages/toolkit/src/tests/combinedTest.test.ts b/packages/toolkit/src/tests/combinedTest.test.ts index fada468601..7f906d8182 100644 --- a/packages/toolkit/src/tests/combinedTest.test.ts +++ b/packages/toolkit/src/tests/combinedTest.test.ts @@ -10,7 +10,7 @@ import type { EntityAdapter } from '@internal/entities/models' import type { BookModel } from '@internal/entities/tests/fixtures/book' describe('Combined entity slice', () => { - let adapter: EntityAdapter + let adapter: EntityAdapter beforeEach(() => { adapter = createEntityAdapter({ diff --git a/packages/toolkit/src/tests/configureStore.test.ts b/packages/toolkit/src/tests/configureStore.test.ts index 248d29a69d..f6e6b41031 100644 --- a/packages/toolkit/src/tests/configureStore.test.ts +++ b/packages/toolkit/src/tests/configureStore.test.ts @@ -1,5 +1,5 @@ import { vi } from 'vitest' -import type { StoreEnhancer, StoreEnhancerStoreCreator } from '@reduxjs/toolkit' +import type { StoreEnhancer } from '@reduxjs/toolkit' import type * as Redux from 'redux' import type * as DevTools from '@internal/devtoolsExtension' diff --git a/packages/toolkit/src/tests/createEntityAdapter.typetest.ts b/packages/toolkit/src/tests/createEntityAdapter.typetest.ts index 10c2b4a393..30a64395c1 100644 --- a/packages/toolkit/src/tests/createEntityAdapter.typetest.ts +++ b/packages/toolkit/src/tests/createEntityAdapter.typetest.ts @@ -9,9 +9,9 @@ import type { import { createSlice, createEntityAdapter } from '@reduxjs/toolkit' import { expectType } from './helpers' -function extractReducers( - adapter: EntityAdapter -): Omit, 'map'> { +function extractReducers( + adapter: EntityAdapter +): Omit, 'map'> { const { selectId, sortComparer, getInitialState, getSelectors, ...rest } = adapter return rest @@ -21,8 +21,9 @@ function extractReducers( * should be usable in a slice, with all the "reducer-like" functions */ { + type Id = string & { readonly __tag: unique symbol } type Entity = { - value: string + id: Id } const adapter = createEntityAdapter() const slice = createSlice({ @@ -48,19 +49,21 @@ function extractReducers( // @ts-expect-error slice.actions.setAll ) - expectType>(slice.actions.removeOne) - expectType>>( + expectType>(slice.actions.removeOne) + expectType>>( slice.actions.removeMany ) // @ts-expect-error expectType>(slice.actions.removeMany) expectType(slice.actions.removeAll) - expectType>>(slice.actions.updateOne) - expectType[]>>( + expectType>>( + slice.actions.updateOne + ) + expectType[]>>( // @ts-expect-error slice.actions.updateMany ) - expectType>>>( + expectType>>>( slice.actions.updateMany ) expectType>(slice.actions.upsertOne) @@ -78,9 +81,11 @@ function extractReducers( */ { type Entity = { + id: EntityId value: string } type Entity2 = { + id: EntityId value2: string } const adapter = createEntityAdapter() @@ -100,9 +105,7 @@ function extractReducers( * should be usable in a slice with extra properties */ { - type Entity = { - value: string - } + type Entity = { id: EntityId; value: string } const adapter = createEntityAdapter() createSlice({ name: 'test', @@ -117,9 +120,7 @@ function extractReducers( * should not be usable in a slice with an unfitting state */ { - type Entity = { - value: string - } + type Entity = { id: EntityId; value: string } const adapter = createEntityAdapter() createSlice({ name: 'test', @@ -130,3 +131,18 @@ function extractReducers( }, }) } + +/** + * should not be able to create an adapter unless the type has an Id + * or an idSelector is provided + */ +{ + type Entity = { + value: string + } + // @ts-expect-error + const adapter = createEntityAdapter() + const adapter2: EntityAdapter = createEntityAdapter({ + selectId: (e: Entity) => e.value, + }) +} diff --git a/packages/toolkit/src/tests/injectableCombineReducers.example.ts b/packages/toolkit/src/tests/injectableCombineReducers.example.ts new file mode 100644 index 0000000000..c79687a0f3 --- /dev/null +++ b/packages/toolkit/src/tests/injectableCombineReducers.example.ts @@ -0,0 +1,64 @@ +/* eslint-disable import/first */ +// @ts-nocheck + +// reducer.ts or whatever + +import { combineSlices } from '@reduxjs/toolkit' + +import { sliceA } from 'fileA' +import { sliceB } from 'fileB' +import { lazySliceC } from 'fileC' +import type { lazySliceD } from 'fileD' + +import { anotherReducer } from 'somewhere' + +export interface LazyLoadedSlices {} + +export const rootReducer = combineSlices(sliceA, sliceB, { + another: anotherReducer, +}).withLazyLoadedSlices() +/* + results in a return type of + { + [sliceA.name]: SliceAState, + [sliceB.name]: SliceBState, + another: AnotherState, + [lazySliceC.name]?: SliceCState, // see fileC.ts to understand why this appears here + [lazySliceD.name]?: SliceDState, // see fileD.ts to understand why this appears here + } + */ + +// fileC.ts +// "naive" approach + +import type { RootState } from './reducer' +import { rootReducer } from './reducer' +import { createSlice } from '@reduxjs/toolkit' + +interface SliceCState { + foo: string +} + +declare module './reducer' { + export interface LazyLoadedSlices { + [lazySliceC.name]: SliceCState + } +} + +export const lazySliceC = createSlice({ + /* ... */ +}) +/** + * Synchronously call `injectSlice` in file. + */ +rootReducer.injectSlice(lazySliceC) + +// might want to add code for HMR as well here + +// this will still error - `lazySliceC` is optional here +const naiveSelectFoo = (state: RootState) => state.lazySliceC.foo + +const selectFoo = rootReducer.withSlice(lazySliceC).selector((state) => { + // `lazySlice` is guaranteed to not be `undefined` here. + return state.lazySlice.foo +}) diff --git a/packages/toolkit/src/tests/matchers.typetest.ts b/packages/toolkit/src/tests/matchers.typetest.ts index 8770983f01..be82806de5 100644 --- a/packages/toolkit/src/tests/matchers.typetest.ts +++ b/packages/toolkit/src/tests/matchers.typetest.ts @@ -1,5 +1,4 @@ import { expectExactType, expectUnknown } from './helpers' -import { IsUnknown } from '@internal/tsHelpers' import type { AnyAction } from 'redux' import type { SerializedError } from '../../src' import { From c616309d5e2236e3e6ecc6ae7ccd25bb24e960d0 Mon Sep 17 00:00:00 2001 From: Matt Date: Sat, 18 Feb 2023 18:32:11 +0000 Subject: [PATCH 213/412] Fix type test --- packages/toolkit/src/entities/sorted_state_adapter.ts | 2 +- packages/toolkit/src/query/react/buildHooks.ts | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/packages/toolkit/src/entities/sorted_state_adapter.ts b/packages/toolkit/src/entities/sorted_state_adapter.ts index 4bb745c6d3..96bb75da4a 100644 --- a/packages/toolkit/src/entities/sorted_state_adapter.ts +++ b/packages/toolkit/src/entities/sorted_state_adapter.ts @@ -78,7 +78,7 @@ export function createSortedStateAdapter( let appliedUpdates = false for (let update of updates) { - const entity = state.entities[update.id] + const entity: T | undefined = state.entities[update.id] if (!entity) { continue } diff --git a/packages/toolkit/src/query/react/buildHooks.ts b/packages/toolkit/src/query/react/buildHooks.ts index 3b764d31ef..c658c6388d 100644 --- a/packages/toolkit/src/query/react/buildHooks.ts +++ b/packages/toolkit/src/query/react/buildHooks.ts @@ -1,6 +1,5 @@ -import type { AnyAction, ThunkAction, ThunkDispatch } from '@reduxjs/toolkit' +import type { AnyAction, Selector, ThunkAction, ThunkDispatch } from '@reduxjs/toolkit' import { createSelector } from '@reduxjs/toolkit' -import type { Selector } from '@reduxjs/toolkit' import type { DependencyList } from 'react' import { useCallback, From 0444622442b3d4282d6013c296f7e5e193294181 Mon Sep 17 00:00:00 2001 From: Matt Date: Sun, 21 May 2023 09:56:02 +0100 Subject: [PATCH 214/412] Fix bug in publish-ci examples --- examples/publish-ci/cra4/src/mocks/handlers.ts | 3 ++- examples/publish-ci/cra5/src/mocks/handlers.ts | 3 ++- examples/publish-ci/next/src/mocks/handlers.ts | 3 ++- examples/publish-ci/vite/src/mocks/handlers.ts | 3 ++- examples/query/react/kitchen-sink/src/mocks/handlers.ts | 9 ++++++--- 5 files changed, 14 insertions(+), 7 deletions(-) diff --git a/examples/publish-ci/cra4/src/mocks/handlers.ts b/examples/publish-ci/cra4/src/mocks/handlers.ts index 5ee9c323f0..f519dcf7d2 100644 --- a/examples/publish-ci/cra4/src/mocks/handlers.ts +++ b/examples/publish-ci/cra4/src/mocks/handlers.ts @@ -34,7 +34,8 @@ export const handlers = [ }), rest.get('/posts/:id', (req, res, ctx) => { - const { id } = req.params as { id: string } + const { id: idParam } = req.params as { id: string } + const id = parseInt(idParam, 10) state = adapter.updateOne(state, { id, changes: { fetched_at: new Date().toUTCString() }, diff --git a/examples/publish-ci/cra5/src/mocks/handlers.ts b/examples/publish-ci/cra5/src/mocks/handlers.ts index 5ee9c323f0..f519dcf7d2 100644 --- a/examples/publish-ci/cra5/src/mocks/handlers.ts +++ b/examples/publish-ci/cra5/src/mocks/handlers.ts @@ -34,7 +34,8 @@ export const handlers = [ }), rest.get('/posts/:id', (req, res, ctx) => { - const { id } = req.params as { id: string } + const { id: idParam } = req.params as { id: string } + const id = parseInt(idParam, 10) state = adapter.updateOne(state, { id, changes: { fetched_at: new Date().toUTCString() }, diff --git a/examples/publish-ci/next/src/mocks/handlers.ts b/examples/publish-ci/next/src/mocks/handlers.ts index 929efc45ad..921664f24a 100644 --- a/examples/publish-ci/next/src/mocks/handlers.ts +++ b/examples/publish-ci/next/src/mocks/handlers.ts @@ -34,7 +34,8 @@ export const handlers = [ }), rest.get('/posts/:id', (req, res, ctx) => { - const { id } = req.params as { id: string } + const { id: idParam } = req.params as { id: string } + const id = parseInt(idParam, 10) state = adapter.updateOne(state, { id, changes: { fetched_at: new Date().toUTCString() }, diff --git a/examples/publish-ci/vite/src/mocks/handlers.ts b/examples/publish-ci/vite/src/mocks/handlers.ts index 5ee9c323f0..f519dcf7d2 100644 --- a/examples/publish-ci/vite/src/mocks/handlers.ts +++ b/examples/publish-ci/vite/src/mocks/handlers.ts @@ -34,7 +34,8 @@ export const handlers = [ }), rest.get('/posts/:id', (req, res, ctx) => { - const { id } = req.params as { id: string } + const { id: idParam } = req.params as { id: string } + const id = parseInt(idParam, 10) state = adapter.updateOne(state, { id, changes: { fetched_at: new Date().toUTCString() }, diff --git a/examples/query/react/kitchen-sink/src/mocks/handlers.ts b/examples/query/react/kitchen-sink/src/mocks/handlers.ts index 3bdab6c5b4..dd5edddee7 100644 --- a/examples/query/react/kitchen-sink/src/mocks/handlers.ts +++ b/examples/query/react/kitchen-sink/src/mocks/handlers.ts @@ -69,13 +69,15 @@ export const handlers = [ }), rest.get('/posts/:id', (req, res, ctx) => { - const { id } = req.params as { id: string }; + const { id: idParam } = req.params as { id: string } + const id = parseInt(idParam, 10) state = adapter.updateOne(state, { id, changes: { fetched_at: new Date().toUTCString() } }); return res(ctx.json(state.entities[id]), ctx.delay(400)); }), rest.put('/posts/:id', (req, res, ctx) => { - const { id } = req.params as { id: string }; + const { id: idParam } = req.params as { id: string } + const id = parseInt(idParam, 10) const changes = req.body as Partial; state = adapter.updateOne(state, { id, changes }); @@ -84,7 +86,8 @@ export const handlers = [ }), rest.delete('/posts/:id', (req, res, ctx) => { - const { id } = req.params as { id: string }; + const { id: idParam } = req.params as { id: string } + const id = parseInt(idParam, 10) state = adapter.removeOne(state, id); From 99545b05cf826c1898ccc1a4b5c46e2e507161cb Mon Sep 17 00:00:00 2001 From: Ben Durrant Date: Sun, 21 May 2023 17:05:39 +0100 Subject: [PATCH 215/412] update docs --- docs/api/actionCreatorMiddleware.mdx | 3 ++- docs/api/configureStore.mdx | 34 +++++++++++++++++++++++++- docs/api/getDefaultMiddleware.mdx | 2 +- docs/api/immutabilityMiddleware.mdx | 3 ++- docs/api/serializabilityMiddleware.mdx | 3 ++- docs/usage/usage-with-typescript.md | 13 +++------- 6 files changed, 43 insertions(+), 15 deletions(-) diff --git a/docs/api/actionCreatorMiddleware.mdx b/docs/api/actionCreatorMiddleware.mdx index 2248a16482..54d3d055dd 100644 --- a/docs/api/actionCreatorMiddleware.mdx +++ b/docs/api/actionCreatorMiddleware.mdx @@ -47,6 +47,7 @@ export default function (state = {}, action: any) { import { configureStore, createActionCreatorInvariantMiddleware, + MiddlewareArray, } from '@reduxjs/toolkit' import reducer from './reducer' @@ -62,6 +63,6 @@ const actionCreatorMiddleware = createActionCreatorInvariantMiddleware({ const store = configureStore({ reducer, - middleware: [actionCreatorMiddleware], + middleware: new MiddlewareArray(actionCreatorMiddleware), }) ``` diff --git a/docs/api/configureStore.mdx b/docs/api/configureStore.mdx index 7abd615508..aa5ee8b8f8 100644 --- a/docs/api/configureStore.mdx +++ b/docs/api/configureStore.mdx @@ -94,6 +94,22 @@ and should return a middleware array. For more details on how the `middleware` parameter works and the list of middleware that are added by default, see the [`getDefaultMiddleware` docs page](./getDefaultMiddleware.mdx). +:::note MiddlewareArray +Typescript users are required to use a `MiddlewareArray` instance (if not using a `getDefaultMiddleware` result, which is already a `MiddlewareArray`), for better inference. + +```ts no-transpile +import { configureStore, MiddlewareArray } from '@reduxjs/toolkit' + +configureStore({ + reducer: rootReducer, + middleware: new MiddlewareArray(additionalMiddleware, logger), +}) +``` + +Javascript-only users are free to use a plain array if preferred. + +::: + ### `devTools` If this is a boolean, it will be used to indicate whether `configureStore` should automatically enable support for [the Redux DevTools browser extension](https://github.com/reduxjs/redux-devtools). @@ -122,7 +138,7 @@ If defined as an array, these will be passed to [the Redux `compose` function](h This should _not_ include `applyMiddleware()` or the Redux DevTools Extension `composeWithDevTools`, as those are already handled by `configureStore`. -Example: `enhancers: [offline]` will result in a final setup of `[applyMiddleware, offline, devToolsExtension]`. +Example: `enhancers: new EnhancerArray(offline)` will result in a final setup of `[applyMiddleware, offline, devToolsExtension]`. If defined as a callback function, it will be called with the existing array of enhancers _without_ the DevTools Extension (currently `[applyMiddleware]`), and should return a new array of enhancers. This is primarily useful for cases where a store enhancer needs to be added @@ -131,6 +147,22 @@ in front of `applyMiddleware`, such as `redux-first-router` or `redux-offline`. Example: `enhancers: (defaultEnhancers) => defaultEnhancers.prepend(offline)` will result in a final setup of `[offline, applyMiddleware, devToolsExtension]`. +:::note EnhancerArray +Typescript users are required to use a `EnhancerArray` instance (if not using a `getDefaultEnhancer` result, which is already a `EnhancerArray`), for better inference. + +```ts no-transpile +import { configureStore, EnhancerArray } from '@reduxjs/toolkit' + +configureStore({ + reducer: rootReducer, + enhancers: new EnhancerArray(offline), +}) +``` + +Javascript-only users are free to use a plain array if preferred. + +::: + ## Usage ### Basic Example diff --git a/docs/api/getDefaultMiddleware.mdx b/docs/api/getDefaultMiddleware.mdx index 370b51ef75..691dae6b20 100644 --- a/docs/api/getDefaultMiddleware.mdx +++ b/docs/api/getDefaultMiddleware.mdx @@ -28,7 +28,7 @@ If you want to customize the list of middleware, you can supply an array of midd ```js const store = configureStore({ reducer: rootReducer, - middleware: [thunk, logger], + middleware: new MiddlewareArray(thunk, logger), }) // Store specifically has the thunk and logger middleware applied diff --git a/docs/api/immutabilityMiddleware.mdx b/docs/api/immutabilityMiddleware.mdx index 73b03d972c..c6e42a1de0 100644 --- a/docs/api/immutabilityMiddleware.mdx +++ b/docs/api/immutabilityMiddleware.mdx @@ -74,6 +74,7 @@ export default exampleSlice.reducer import { configureStore, createImmutableStateInvariantMiddleware, + MiddlewareArray, } from '@reduxjs/toolkit' import exampleSliceReducer from './exampleSlice' @@ -85,7 +86,7 @@ const immutableInvariantMiddleware = createImmutableStateInvariantMiddleware({ const store = configureStore({ reducer: exampleSliceReducer, // Note that this will replace all default middleware - middleware: [immutableInvariantMiddleware], + middleware: new MiddlewareArray(immutableInvariantMiddleware), }) ``` diff --git a/docs/api/serializabilityMiddleware.mdx b/docs/api/serializabilityMiddleware.mdx index 0aa074a65c..f0b60359bf 100644 --- a/docs/api/serializabilityMiddleware.mdx +++ b/docs/api/serializabilityMiddleware.mdx @@ -93,6 +93,7 @@ import { configureStore, createSerializableStateInvariantMiddleware, isPlain, + MiddlewareArray, } from '@reduxjs/toolkit' import reducer from './reducer' @@ -110,7 +111,7 @@ const serializableMiddleware = createSerializableStateInvariantMiddleware({ const store = configureStore({ reducer, - middleware: [serializableMiddleware], + middleware: new MiddlewareArray(serializableMiddleware), }) ``` diff --git a/docs/usage/usage-with-typescript.md b/docs/usage/usage-with-typescript.md index d4d1a6348d..1f1d69db30 100644 --- a/docs/usage/usage-with-typescript.md +++ b/docs/usage/usage-with-typescript.md @@ -136,23 +136,16 @@ export default store #### Using `MiddlewareArray` without `getDefaultMiddleware` -If you want to skip the usage of `getDefaultMiddleware` altogether, you can still use `MiddlewareArray` for type-safe concatenation of your `middleware` array. This class extends the default JavaScript `Array` type, only with modified typings for `.concat(...)` and the additional `.prepend(...)` method. +If you want to skip the usage of `getDefaultMiddleware` altogether, you are requred to use `MiddlewareArray` for type-safe creation of your `middleware` array. This class extends the default JavaScript `Array` type, only with modified typings for `.concat(...)` and the additional `.prepend(...)` method. -This is generally not required though, as you will probably not run into any array-type-widening issues as long as you are using `as const` and do not use the spread operator. - -So the following two calls would be equivalent: +For example: ```ts import { configureStore, MiddlewareArray } from '@reduxjs/toolkit' configureStore({ reducer: rootReducer, - middleware: new MiddlewareArray().concat(additionalMiddleware, logger), -}) - -configureStore({ - reducer: rootReducer, - middleware: [additionalMiddleware, logger] as const, + middleware: new MiddlewareArray(additionalMiddleware, logger), }) ``` From c8a088d1eeaeeb8fda488d20a00427e61d2ec381 Mon Sep 17 00:00:00 2001 From: Matt Date: Sun, 21 May 2023 17:17:16 +0100 Subject: [PATCH 216/412] Fix all ids being converted to strings in updateManyMutably --- .../src/entities/unsorted_state_adapter.ts | 4 +- .../injectableCombineReducers.example.ts | 64 ------------------- 2 files changed, 3 insertions(+), 65 deletions(-) delete mode 100644 packages/toolkit/src/tests/injectableCombineReducers.example.ts diff --git a/packages/toolkit/src/entities/unsorted_state_adapter.ts b/packages/toolkit/src/entities/unsorted_state_adapter.ts index e451acb32e..9c3295dc43 100644 --- a/packages/toolkit/src/entities/unsorted_state_adapter.ts +++ b/packages/toolkit/src/entities/unsorted_state_adapter.ts @@ -161,7 +161,9 @@ export function createUnsortedStateAdapter( 0 if (didMutateIds) { - state.ids = Object.keys(state.entities) as Id[] + state.ids = Object.values(state.entities).map((e) => + selectIdValue(e as T, selectId) + ) } } } diff --git a/packages/toolkit/src/tests/injectableCombineReducers.example.ts b/packages/toolkit/src/tests/injectableCombineReducers.example.ts deleted file mode 100644 index c79687a0f3..0000000000 --- a/packages/toolkit/src/tests/injectableCombineReducers.example.ts +++ /dev/null @@ -1,64 +0,0 @@ -/* eslint-disable import/first */ -// @ts-nocheck - -// reducer.ts or whatever - -import { combineSlices } from '@reduxjs/toolkit' - -import { sliceA } from 'fileA' -import { sliceB } from 'fileB' -import { lazySliceC } from 'fileC' -import type { lazySliceD } from 'fileD' - -import { anotherReducer } from 'somewhere' - -export interface LazyLoadedSlices {} - -export const rootReducer = combineSlices(sliceA, sliceB, { - another: anotherReducer, -}).withLazyLoadedSlices() -/* - results in a return type of - { - [sliceA.name]: SliceAState, - [sliceB.name]: SliceBState, - another: AnotherState, - [lazySliceC.name]?: SliceCState, // see fileC.ts to understand why this appears here - [lazySliceD.name]?: SliceDState, // see fileD.ts to understand why this appears here - } - */ - -// fileC.ts -// "naive" approach - -import type { RootState } from './reducer' -import { rootReducer } from './reducer' -import { createSlice } from '@reduxjs/toolkit' - -interface SliceCState { - foo: string -} - -declare module './reducer' { - export interface LazyLoadedSlices { - [lazySliceC.name]: SliceCState - } -} - -export const lazySliceC = createSlice({ - /* ... */ -}) -/** - * Synchronously call `injectSlice` in file. - */ -rootReducer.injectSlice(lazySliceC) - -// might want to add code for HMR as well here - -// this will still error - `lazySliceC` is optional here -const naiveSelectFoo = (state: RootState) => state.lazySliceC.foo - -const selectFoo = rootReducer.withSlice(lazySliceC).selector((state) => { - // `lazySlice` is guaranteed to not be `undefined` here. - return state.lazySlice.foo -}) From 4e119dff2c5443f7cc74fb12545183a87d095323 Mon Sep 17 00:00:00 2001 From: Ben Durrant Date: Sun, 21 May 2023 21:56:55 +0100 Subject: [PATCH 217/412] Create Tuple class to replace MiddlewareArray and EnhancerArray --- docs/api/actionCreatorMiddleware.mdx | 4 +- docs/api/configureStore.mdx | 18 ++-- docs/api/getDefaultMiddleware.mdx | 4 +- docs/api/immutabilityMiddleware.mdx | 4 +- docs/api/serializabilityMiddleware.mdx | 4 +- docs/usage/usage-with-typescript.md | 10 +- packages/toolkit/src/configureStore.ts | 20 ++-- packages/toolkit/src/getDefaultEnhancers.ts | 6 +- packages/toolkit/src/getDefaultMiddleware.ts | 6 +- packages/toolkit/src/index.ts | 2 +- packages/toolkit/src/query/tests/helpers.tsx | 7 +- packages/toolkit/src/tests/Tuple.typetest.ts | 81 +++++++++++++++ .../toolkit/src/tests/configureStore.test.ts | 14 +-- .../src/tests/configureStore.typetest.ts | 31 +++--- ...est.ts => getDefaultEnhancers.typetest.ts} | 0 .../src/tests/getDefaultMiddleware.test.ts | 20 ++-- ...st.ts => getDefaultMiddleware.typetest.ts} | 0 ...rializableStateInvariantMiddleware.test.ts | 38 ++++--- packages/toolkit/src/tsHelpers.ts | 12 +-- packages/toolkit/src/utils.ts | 98 +++++-------------- 20 files changed, 200 insertions(+), 179 deletions(-) create mode 100644 packages/toolkit/src/tests/Tuple.typetest.ts rename packages/toolkit/src/tests/{EnhancerArray.typetest.ts => getDefaultEnhancers.typetest.ts} (100%) rename packages/toolkit/src/tests/{MiddlewareArray.typetest.ts => getDefaultMiddleware.typetest.ts} (100%) diff --git a/docs/api/actionCreatorMiddleware.mdx b/docs/api/actionCreatorMiddleware.mdx index 54d3d055dd..0afce4f368 100644 --- a/docs/api/actionCreatorMiddleware.mdx +++ b/docs/api/actionCreatorMiddleware.mdx @@ -47,7 +47,7 @@ export default function (state = {}, action: any) { import { configureStore, createActionCreatorInvariantMiddleware, - MiddlewareArray, + Tuple, } from '@reduxjs/toolkit' import reducer from './reducer' @@ -63,6 +63,6 @@ const actionCreatorMiddleware = createActionCreatorInvariantMiddleware({ const store = configureStore({ reducer, - middleware: new MiddlewareArray(actionCreatorMiddleware), + middleware: new Tuple(actionCreatorMiddleware), }) ``` diff --git a/docs/api/configureStore.mdx b/docs/api/configureStore.mdx index aa5ee8b8f8..4d5f9afab3 100644 --- a/docs/api/configureStore.mdx +++ b/docs/api/configureStore.mdx @@ -94,15 +94,15 @@ and should return a middleware array. For more details on how the `middleware` parameter works and the list of middleware that are added by default, see the [`getDefaultMiddleware` docs page](./getDefaultMiddleware.mdx). -:::note MiddlewareArray -Typescript users are required to use a `MiddlewareArray` instance (if not using a `getDefaultMiddleware` result, which is already a `MiddlewareArray`), for better inference. +:::note Tuple +Typescript users are required to use a `Tuple` instance (if not using a `getDefaultMiddleware` result, which is already a `Tuple`), for better inference. ```ts no-transpile -import { configureStore, MiddlewareArray } from '@reduxjs/toolkit' +import { configureStore, Tuple } from '@reduxjs/toolkit' configureStore({ reducer: rootReducer, - middleware: new MiddlewareArray(additionalMiddleware, logger), + middleware: new Tuple(additionalMiddleware, logger), }) ``` @@ -138,7 +138,7 @@ If defined as an array, these will be passed to [the Redux `compose` function](h This should _not_ include `applyMiddleware()` or the Redux DevTools Extension `composeWithDevTools`, as those are already handled by `configureStore`. -Example: `enhancers: new EnhancerArray(offline)` will result in a final setup of `[applyMiddleware, offline, devToolsExtension]`. +Example: `enhancers: new Tuple(offline)` will result in a final setup of `[applyMiddleware, offline, devToolsExtension]`. If defined as a callback function, it will be called with the existing array of enhancers _without_ the DevTools Extension (currently `[applyMiddleware]`), and should return a new array of enhancers. This is primarily useful for cases where a store enhancer needs to be added @@ -147,15 +147,15 @@ in front of `applyMiddleware`, such as `redux-first-router` or `redux-offline`. Example: `enhancers: (defaultEnhancers) => defaultEnhancers.prepend(offline)` will result in a final setup of `[offline, applyMiddleware, devToolsExtension]`. -:::note EnhancerArray -Typescript users are required to use a `EnhancerArray` instance (if not using a `getDefaultEnhancer` result, which is already a `EnhancerArray`), for better inference. +:::note Tuple +Typescript users are required to use a `Tuple` instance (if not using a `getDefaultEnhancer` result, which is already a `Tuple`), for better inference. ```ts no-transpile -import { configureStore, EnhancerArray } from '@reduxjs/toolkit' +import { configureStore, Tuple } from '@reduxjs/toolkit' configureStore({ reducer: rootReducer, - enhancers: new EnhancerArray(offline), + enhancers: new Tuple(offline), }) ``` diff --git a/docs/api/getDefaultMiddleware.mdx b/docs/api/getDefaultMiddleware.mdx index 691dae6b20..0af30a10e7 100644 --- a/docs/api/getDefaultMiddleware.mdx +++ b/docs/api/getDefaultMiddleware.mdx @@ -28,7 +28,7 @@ If you want to customize the list of middleware, you can supply an array of midd ```js const store = configureStore({ reducer: rootReducer, - middleware: new MiddlewareArray(thunk, logger), + middleware: new Tuple(thunk, logger), }) // Store specifically has the thunk and logger middleware applied @@ -55,7 +55,7 @@ const store = configureStore({ // Store has all of the default middleware added, _plus_ the logger middleware ``` -It is preferable to use the chainable `.concat(...)` and `.prepend(...)` methods of the returned `MiddlewareArray` instead of the array spread operator, as the latter can lose valuable type information under some circumstances. +It is preferable to use the chainable `.concat(...)` and `.prepend(...)` methods of the returned `Tuple` instead of the array spread operator, as the latter can lose valuable type information under some circumstances. ## Included Default Middleware diff --git a/docs/api/immutabilityMiddleware.mdx b/docs/api/immutabilityMiddleware.mdx index c6e42a1de0..16950b129e 100644 --- a/docs/api/immutabilityMiddleware.mdx +++ b/docs/api/immutabilityMiddleware.mdx @@ -74,7 +74,7 @@ export default exampleSlice.reducer import { configureStore, createImmutableStateInvariantMiddleware, - MiddlewareArray, + Tuple, } from '@reduxjs/toolkit' import exampleSliceReducer from './exampleSlice' @@ -86,7 +86,7 @@ const immutableInvariantMiddleware = createImmutableStateInvariantMiddleware({ const store = configureStore({ reducer: exampleSliceReducer, // Note that this will replace all default middleware - middleware: new MiddlewareArray(immutableInvariantMiddleware), + middleware: new Tuple(immutableInvariantMiddleware), }) ``` diff --git a/docs/api/serializabilityMiddleware.mdx b/docs/api/serializabilityMiddleware.mdx index f0b60359bf..e8cb8e8363 100644 --- a/docs/api/serializabilityMiddleware.mdx +++ b/docs/api/serializabilityMiddleware.mdx @@ -93,7 +93,7 @@ import { configureStore, createSerializableStateInvariantMiddleware, isPlain, - MiddlewareArray, + Tuple, } from '@reduxjs/toolkit' import reducer from './reducer' @@ -111,7 +111,7 @@ const serializableMiddleware = createSerializableStateInvariantMiddleware({ const store = configureStore({ reducer, - middleware: new MiddlewareArray(serializableMiddleware), + middleware: new Tuple(serializableMiddleware), }) ``` diff --git a/docs/usage/usage-with-typescript.md b/docs/usage/usage-with-typescript.md index 1f1d69db30..5258ede400 100644 --- a/docs/usage/usage-with-typescript.md +++ b/docs/usage/usage-with-typescript.md @@ -99,7 +99,7 @@ export default store The type of the `dispatch` function type will be directly inferred from the `middleware` option. So if you add _correctly typed_ middlewares, `dispatch` should already be correctly typed. -As TypeScript often widens array types when combining arrays using the spread operator, we suggest using the `.concat(...)` and `.prepend(...)` methods of the `MiddlewareArray` returned by `getDefaultMiddleware()`. +As TypeScript often widens array types when combining arrays using the spread operator, we suggest using the `.concat(...)` and `.prepend(...)` methods of the `Tuple` returned by `getDefaultMiddleware()`. ```ts import { configureStore } from '@reduxjs/toolkit' @@ -134,18 +134,18 @@ export type AppDispatch = typeof store.dispatch export default store ``` -#### Using `MiddlewareArray` without `getDefaultMiddleware` +#### Using `Tuple` without `getDefaultMiddleware` -If you want to skip the usage of `getDefaultMiddleware` altogether, you are requred to use `MiddlewareArray` for type-safe creation of your `middleware` array. This class extends the default JavaScript `Array` type, only with modified typings for `.concat(...)` and the additional `.prepend(...)` method. +If you want to skip the usage of `getDefaultMiddleware` altogether, you are requred to use `Tuple` for type-safe creation of your `middleware` array. This class extends the default JavaScript `Array` type, only with modified typings for `.concat(...)` and the additional `.prepend(...)` method. For example: ```ts -import { configureStore, MiddlewareArray } from '@reduxjs/toolkit' +import { configureStore, Tuple } from '@reduxjs/toolkit' configureStore({ reducer: rootReducer, - middleware: new MiddlewareArray(additionalMiddleware, logger), + middleware: new Tuple(additionalMiddleware, logger), }) ``` diff --git a/packages/toolkit/src/configureStore.ts b/packages/toolkit/src/configureStore.ts index 39f2aaaefa..44a06894f2 100644 --- a/packages/toolkit/src/configureStore.ts +++ b/packages/toolkit/src/configureStore.ts @@ -23,7 +23,7 @@ import type { ExtractStoreExtensions, ExtractStateExtensions, } from './tsHelpers' -import type { EnhancerArray, MiddlewareArray } from './utils' +import type { Tuple } from './utils' import type { GetDefaultEnhancers } from './getDefaultEnhancers' import { buildGetDefaultEnhancers } from './getDefaultEnhancers' @@ -37,8 +37,8 @@ const IS_PRODUCTION = process.env.NODE_ENV === 'production' export interface ConfigureStoreOptions< S = any, A extends Action = AnyAction, - M extends MiddlewareArray> = MiddlewareArray>, - E extends EnhancerArray = EnhancerArray, + M extends Tuple> = Tuple>, + E extends Tuple = Tuple, P = S > { /** @@ -48,8 +48,8 @@ export interface ConfigureStoreOptions< reducer: Reducer | ReducersMapObject /** - * An array of Redux middleware to install. If not supplied, defaults to - * the set of middleware returned by `getDefaultMiddleware()`. + * An array of Redux middleware to install, or a callback receiving `getDefaultMiddleware` and returning a Tuple of middleware. + * If not supplied, defaults to the set of middleware returned by `getDefaultMiddleware()`. * * @example `middleware: (gDM) => gDM().concat(logger, apiMiddleware, yourCustomMiddleware)` * @see https://redux-toolkit.js.org/api/getDefaultMiddleware#intended-usage @@ -78,8 +78,8 @@ export interface ConfigureStoreOptions< * The store enhancers to apply. See Redux's `createStore()`. * All enhancers will be included before the DevTools Extension enhancer. * If you need to customize the order of enhancers, supply a callback - * function that will receive a `getDefaultEnhancers` function that returns an EnhancerArray, - * and should return a new array (such as `getDefaultEnhancers().concat(offline)`). + * function that will receive a `getDefaultEnhancers` function that returns a Tuple, + * and should return a Tuple of enhancers (such as `getDefaultEnhancers().concat(offline)`). * If you only need to add middleware, you can use the `middleware` parameter instead. */ enhancers?: ((getDefaultEnhancers: GetDefaultEnhancers) => E) | E @@ -112,10 +112,8 @@ export type EnhancedStore< export function configureStore< S = any, A extends Action = AnyAction, - M extends MiddlewareArray> = MiddlewareArray< - [ThunkMiddlewareFor] - >, - E extends EnhancerArray = EnhancerArray< + M extends Tuple> = Tuple<[ThunkMiddlewareFor]>, + E extends Tuple = Tuple< [StoreEnhancer<{ dispatch: ExtractDispatchExtensions }>, StoreEnhancer] >, P = S diff --git a/packages/toolkit/src/getDefaultEnhancers.ts b/packages/toolkit/src/getDefaultEnhancers.ts index faf27654aa..6d13646482 100644 --- a/packages/toolkit/src/getDefaultEnhancers.ts +++ b/packages/toolkit/src/getDefaultEnhancers.ts @@ -1,7 +1,7 @@ import type { StoreEnhancer } from 'redux' import type { AutoBatchOptions } from './autoBatchEnhancer' import { autoBatchEnhancer } from './autoBatchEnhancer' -import { EnhancerArray } from './utils' +import { Tuple } from './utils' import type { Middlewares } from './configureStore' import type { ExtractDispatchExtensions } from './tsHelpers' @@ -11,7 +11,7 @@ type GetDefaultEnhancersOptions = { export type GetDefaultEnhancers> = ( options?: GetDefaultEnhancersOptions -) => EnhancerArray<[StoreEnhancer<{ dispatch: ExtractDispatchExtensions }>]> +) => Tuple<[StoreEnhancer<{ dispatch: ExtractDispatchExtensions }>]> export const buildGetDefaultEnhancers = >( middlewareEnhancer: StoreEnhancer<{ dispatch: ExtractDispatchExtensions }> @@ -19,7 +19,7 @@ export const buildGetDefaultEnhancers = >( function getDefaultEnhancers(options) { const { autoBatch = true } = options ?? {} - let enhancerArray = new EnhancerArray(middlewareEnhancer) + let enhancerArray = new Tuple(middlewareEnhancer) if (autoBatch) { enhancerArray.push( autoBatchEnhancer(typeof autoBatch === 'object' ? autoBatch : undefined) diff --git a/packages/toolkit/src/getDefaultMiddleware.ts b/packages/toolkit/src/getDefaultMiddleware.ts index 12ce3ad9e8..acfd94d840 100644 --- a/packages/toolkit/src/getDefaultMiddleware.ts +++ b/packages/toolkit/src/getDefaultMiddleware.ts @@ -11,7 +11,7 @@ import { createImmutableStateInvariantMiddleware } from './immutableStateInvaria import type { SerializableStateInvariantMiddlewareOptions } from './serializableStateInvariantMiddleware' import { createSerializableStateInvariantMiddleware } from './serializableStateInvariantMiddleware' import type { ExcludeFromTuple } from './tsHelpers' -import { MiddlewareArray } from './utils' +import { Tuple } from './utils' function isBoolean(x: any): x is boolean { return typeof x === 'boolean' @@ -48,7 +48,7 @@ export type GetDefaultMiddleware = < } >( options?: O -) => MiddlewareArray], never>> +) => Tuple], never>> export const buildGetDefaultMiddleware = (): GetDefaultMiddleware => function getDefaultMiddleware(options) { @@ -59,7 +59,7 @@ export const buildGetDefaultMiddleware = (): GetDefaultMiddleware => actionCreatorCheck = true, } = options ?? {} - let middlewareArray = new MiddlewareArray() + let middlewareArray = new Tuple() if (thunk) { if (isBoolean(thunk)) { diff --git a/packages/toolkit/src/index.ts b/packages/toolkit/src/index.ts index 6630c94687..f30024161f 100644 --- a/packages/toolkit/src/index.ts +++ b/packages/toolkit/src/index.ts @@ -101,7 +101,7 @@ export type { // types ActionReducerMapBuilder, } from './mapBuilders' -export { MiddlewareArray, EnhancerArray } from './utils' +export { Tuple } from './utils' export { createEntityAdapter } from './entities/create_adapter' export type { diff --git a/packages/toolkit/src/query/tests/helpers.tsx b/packages/toolkit/src/query/tests/helpers.tsx index 94f56b0e0c..d566b4c0fc 100644 --- a/packages/toolkit/src/query/tests/helpers.tsx +++ b/packages/toolkit/src/query/tests/helpers.tsx @@ -5,9 +5,6 @@ import type { Middleware, Store, Reducer, - EnhancerArray, - StoreEnhancer, - ThunkDispatch, } from '@reduxjs/toolkit' import { configureStore } from '@reduxjs/toolkit' import { setupListeners } from '@reduxjs/toolkit/query' @@ -218,8 +215,8 @@ export function setupApiStore< }).concat(api.middleware) return tempMiddleware - .concat(...(middleware?.concat ?? [])) - .prepend(...(middleware?.prepend ?? [])) as typeof tempMiddleware + .concat(middleware?.concat ?? []) + .prepend(middleware?.prepend ?? []) as typeof tempMiddleware }, enhancers: (gde) => gde({ diff --git a/packages/toolkit/src/tests/Tuple.typetest.ts b/packages/toolkit/src/tests/Tuple.typetest.ts new file mode 100644 index 0000000000..963164862a --- /dev/null +++ b/packages/toolkit/src/tests/Tuple.typetest.ts @@ -0,0 +1,81 @@ +import { Tuple } from '@reduxjs/toolkit' +import { expectType } from './helpers' + +/** + * Test: compatibility is checked between described types + */ +{ + const stringTuple = new Tuple('') + + expectType>(stringTuple) + + expectType>(stringTuple) + + // @ts-expect-error + expectType>(stringTuple) + + const numberTuple = new Tuple(0) + // @ts-expect-error + expectType>(numberTuple) +} + +/** + * Test: concat is inferred properly + */ +{ + const singleString = new Tuple('') + + expectType>(singleString) + + expectType>(singleString.concat('')) + + expectType>(singleString.concat([''])) +} + +/** + * Test: prepend is inferred properly + */ +{ + const singleString = new Tuple('') + + expectType>(singleString) + + expectType>(singleString.prepend('')) + + expectType>(singleString.prepend([''])) +} + +/** + * Test: push must match existing items + */ +{ + const stringTuple = new Tuple('') + + stringTuple.push('') + + // @ts-expect-error + stringTuple.push(0) +} + +/** + * Test: Tuples can be combined + */ +{ + const stringTuple = new Tuple('') + + const numberTuple = new Tuple(0) + + expectType>(stringTuple.concat(numberTuple)) + + expectType>(stringTuple.prepend(numberTuple)) + + expectType>(numberTuple.concat(stringTuple)) + + expectType>(numberTuple.prepend(stringTuple)) + + // @ts-expect-error + expectType>(stringTuple.prepend(numberTuple)) + + // @ts-expect-error + expectType>(stringTuple.concat(numberTuple)) +} diff --git a/packages/toolkit/src/tests/configureStore.test.ts b/packages/toolkit/src/tests/configureStore.test.ts index 1ade822fc1..1c3fbd3766 100644 --- a/packages/toolkit/src/tests/configureStore.test.ts +++ b/packages/toolkit/src/tests/configureStore.test.ts @@ -1,6 +1,6 @@ import { vi } from 'vitest' import type { StoreEnhancer } from '@reduxjs/toolkit' -import { MiddlewareArray, EnhancerArray } from '@reduxjs/toolkit' +import { Tuple } from '@reduxjs/toolkit' import type * as Redux from 'redux' import type * as DevTools from '@internal/devtoolsExtension' @@ -110,7 +110,7 @@ describe('configureStore', async () => { describe('given no middleware', () => { it('calls createStore without any middleware', () => { expect( - configureStore({ middleware: new MiddlewareArray(), reducer }) + configureStore({ middleware: new Tuple(), reducer }) ).toBeInstanceOf(Object) expect(redux.applyMiddleware).toHaveBeenCalledWith() expect(mockDevtoolsCompose).toHaveBeenCalled() // @remap-prod-remove-line-line @@ -175,7 +175,7 @@ describe('configureStore', async () => { const thank: Redux.Middleware = (_store) => (next) => (action) => next(action) expect( - configureStore({ middleware: new MiddlewareArray(thank), reducer }) + configureStore({ middleware: new Tuple(thank), reducer }) ).toBeInstanceOf(Object) expect(redux.applyMiddleware).toHaveBeenCalledWith(thank) expect(mockDevtoolsCompose).toHaveBeenCalled() // @remap-prod-remove-line-line @@ -197,7 +197,7 @@ describe('configureStore', async () => { expect(getDefaultMiddleware).toEqual(expect.any(Function)) expect(getDefaultMiddleware()).toEqual(expect.any(Array)) - return new MiddlewareArray(thank) + return new Tuple(thank) }) const store = configureStore({ middleware: builder, reducer }) @@ -306,7 +306,7 @@ describe('configureStore', async () => { it('warns if middleware enhancer is excluded from final array when middlewares are provided', () => { const store = configureStore({ reducer, - enhancers: new EnhancerArray(dummyEnhancer), + enhancers: new Tuple(dummyEnhancer), }) expect(dummyEnhancerCalled).toBe(true) @@ -318,8 +318,8 @@ describe('configureStore', async () => { it("doesn't warn when middleware enhancer is excluded if no middlewares provided", () => { const store = configureStore({ reducer, - middleware: new MiddlewareArray(), - enhancers: new EnhancerArray(dummyEnhancer), + middleware: new Tuple(), + enhancers: new Tuple(dummyEnhancer), }) expect(dummyEnhancerCalled).toBe(true) diff --git a/packages/toolkit/src/tests/configureStore.typetest.ts b/packages/toolkit/src/tests/configureStore.typetest.ts index d4d02681e4..5ee4cbec0c 100644 --- a/packages/toolkit/src/tests/configureStore.typetest.ts +++ b/packages/toolkit/src/tests/configureStore.typetest.ts @@ -10,12 +10,7 @@ import type { } from 'redux' import { applyMiddleware, combineReducers } from 'redux' import type { PayloadAction, ConfigureStoreOptions } from '@reduxjs/toolkit' -import { - configureStore, - createSlice, - MiddlewareArray, - EnhancerArray, -} from '@reduxjs/toolkit' +import { configureStore, createSlice, Tuple } from '@reduxjs/toolkit' import type { ThunkMiddleware, ThunkAction, ThunkDispatch } from 'redux-thunk' import { thunk } from 'redux-thunk' import { expectNotAny, expectType } from './helpers' @@ -72,14 +67,14 @@ const _anyMiddleware: any = () => () => () => {} } /* - * Test: configureStore() accepts MiddlewareArray, but not plain array. + * Test: configureStore() accepts Tuple, but not plain array. */ { const middleware: Middleware = (store) => (next) => next configureStore({ reducer: () => 0, - middleware: new MiddlewareArray(middleware), + middleware: new Tuple(middleware), }) configureStore({ @@ -91,7 +86,7 @@ const _anyMiddleware: any = () => () => () => {} configureStore({ reducer: () => 0, // @ts-expect-error - middleware: new MiddlewareArray('not middleware'), + middleware: new Tuple('not middleware'), }) } @@ -144,7 +139,7 @@ const _anyMiddleware: any = () => () => () => {} } /* - * Test: configureStore() accepts store EnhancerArray, but not plain array + * Test: configureStore() accepts store Tuple, but not plain array */ { { @@ -152,7 +147,7 @@ const _anyMiddleware: any = () => () => () => {} const store = configureStore({ reducer: () => 0, - enhancers: new EnhancerArray(enhancer), + enhancers: new Tuple(enhancer), }) const store2 = configureStore({ @@ -169,7 +164,7 @@ const _anyMiddleware: any = () => () => () => {} configureStore({ reducer: () => 0, // @ts-expect-error - enhancers: new EnhancerArray('not a store enhancer'), + enhancers: new Tuple('not a store enhancer'), }) { @@ -197,7 +192,7 @@ const _anyMiddleware: any = () => () => () => {} const store = configureStore({ reducer: () => 0, - enhancers: new EnhancerArray( + enhancers: new Tuple( somePropertyStoreEnhancer, anotherPropertyStoreEnhancer ), @@ -259,7 +254,7 @@ const _anyMiddleware: any = () => () => () => {} const store = configureStore({ reducer: () => ({ aProperty: 0 }), - enhancers: new EnhancerArray( + enhancers: new Tuple( someStateExtendingEnhancer, anotherStateExtendingEnhancer ), @@ -530,7 +525,7 @@ const _anyMiddleware: any = () => () => () => {} { const store = configureStore({ reducer: reducerA, - middleware: new MiddlewareArray(), + middleware: new Tuple(), }) // @ts-expect-error store.dispatch(thunkA()) @@ -543,7 +538,7 @@ const _anyMiddleware: any = () => () => () => {} { const store = configureStore({ reducer: reducerA, - middleware: new MiddlewareArray(thunk as ThunkMiddleware), + middleware: new Tuple(thunk as ThunkMiddleware), }) store.dispatch(thunkA()) // @ts-expect-error @@ -555,7 +550,7 @@ const _anyMiddleware: any = () => () => () => {} { const store = configureStore({ reducer: reducerA, - middleware: new MiddlewareArray( + middleware: new Tuple( 0 as unknown as Middleware<(a: StateA) => boolean, StateA> ), }) @@ -567,7 +562,7 @@ const _anyMiddleware: any = () => () => () => {} * Test: multiple custom middleware */ { - const middleware = [] as any as MiddlewareArray< + const middleware = [] as any as Tuple< [ Middleware<(a: 'a') => 'A', StateA>, Middleware<(b: 'b') => 'B', StateA>, diff --git a/packages/toolkit/src/tests/EnhancerArray.typetest.ts b/packages/toolkit/src/tests/getDefaultEnhancers.typetest.ts similarity index 100% rename from packages/toolkit/src/tests/EnhancerArray.typetest.ts rename to packages/toolkit/src/tests/getDefaultEnhancers.typetest.ts diff --git a/packages/toolkit/src/tests/getDefaultMiddleware.test.ts b/packages/toolkit/src/tests/getDefaultMiddleware.test.ts index 7c27357ece..74dcbf5874 100644 --- a/packages/toolkit/src/tests/getDefaultMiddleware.test.ts +++ b/packages/toolkit/src/tests/getDefaultMiddleware.test.ts @@ -14,7 +14,7 @@ import type { ThunkMiddleware } from 'redux-thunk' import { expectType } from './helpers' import { buildGetDefaultMiddleware } from '@internal/getDefaultMiddleware' -import { MiddlewareArray } from '@internal/utils' +import { Tuple } from '@internal/utils' const getDefaultMiddleware = buildGetDefaultMiddleware() @@ -80,7 +80,7 @@ describe('getDefaultMiddleware', () => { thunk: false, }) - expectType>(m2) + expectType>(m2) const dummyMiddleware: Middleware< { @@ -114,7 +114,7 @@ describe('getDefaultMiddleware', () => { const m3 = middleware.concat(dummyMiddleware, dummyMiddleware2) expectType< - MiddlewareArray< + Tuple< [ ThunkMiddleware, Middleware< @@ -220,7 +220,7 @@ it('allows passing options to actionCreatorCheck', () => { expect(actionCreatorCheckWasCalled).toBe(true) }) -describe('MiddlewareArray functionality', () => { +describe('Tuple functionality', () => { const middleware1: Middleware = () => (next) => (action) => next(action) const middleware2: Middleware = () => (next) => (action) => next(action) const defaultMiddleware = getDefaultMiddleware() @@ -232,7 +232,7 @@ describe('MiddlewareArray functionality', () => { // value is prepended expect(prepended).toEqual([middleware1, ...defaultMiddleware]) // returned value is of correct type - expect(prepended).toBeInstanceOf(MiddlewareArray) + expect(prepended).toBeInstanceOf(Tuple) // prepended is a new array expect(prepended).not.toEqual(defaultMiddleware) // defaultMiddleware is not modified @@ -245,7 +245,7 @@ describe('MiddlewareArray functionality', () => { // value is prepended expect(prepended).toEqual([middleware1, middleware2, ...defaultMiddleware]) // returned value is of correct type - expect(prepended).toBeInstanceOf(MiddlewareArray) + expect(prepended).toBeInstanceOf(Tuple) // prepended is a new array expect(prepended).not.toEqual(defaultMiddleware) // defaultMiddleware is not modified @@ -258,7 +258,7 @@ describe('MiddlewareArray functionality', () => { // value is prepended expect(prepended).toEqual([middleware1, middleware2, ...defaultMiddleware]) // returned value is of correct type - expect(prepended).toBeInstanceOf(MiddlewareArray) + expect(prepended).toBeInstanceOf(Tuple) // prepended is a new array expect(prepended).not.toEqual(defaultMiddleware) // defaultMiddleware is not modified @@ -271,7 +271,7 @@ describe('MiddlewareArray functionality', () => { // value is concatenated expect(concatenated).toEqual([...defaultMiddleware, middleware1]) // returned value is of correct type - expect(concatenated).toBeInstanceOf(MiddlewareArray) + expect(concatenated).toBeInstanceOf(Tuple) // concatenated is a new array expect(concatenated).not.toEqual(defaultMiddleware) // defaultMiddleware is not modified @@ -288,7 +288,7 @@ describe('MiddlewareArray functionality', () => { middleware2, ]) // returned value is of correct type - expect(concatenated).toBeInstanceOf(MiddlewareArray) + expect(concatenated).toBeInstanceOf(Tuple) // concatenated is a new array expect(concatenated).not.toEqual(defaultMiddleware) // defaultMiddleware is not modified @@ -305,7 +305,7 @@ describe('MiddlewareArray functionality', () => { middleware2, ]) // returned value is of correct type - expect(concatenated).toBeInstanceOf(MiddlewareArray) + expect(concatenated).toBeInstanceOf(Tuple) // concatenated is a new array expect(concatenated).not.toEqual(defaultMiddleware) // defaultMiddleware is not modified diff --git a/packages/toolkit/src/tests/MiddlewareArray.typetest.ts b/packages/toolkit/src/tests/getDefaultMiddleware.typetest.ts similarity index 100% rename from packages/toolkit/src/tests/MiddlewareArray.typetest.ts rename to packages/toolkit/src/tests/getDefaultMiddleware.typetest.ts diff --git a/packages/toolkit/src/tests/serializableStateInvariantMiddleware.test.ts b/packages/toolkit/src/tests/serializableStateInvariantMiddleware.test.ts index 1d8361b3d1..24c00cacd9 100644 --- a/packages/toolkit/src/tests/serializableStateInvariantMiddleware.test.ts +++ b/packages/toolkit/src/tests/serializableStateInvariantMiddleware.test.ts @@ -10,7 +10,7 @@ import { createSerializableStateInvariantMiddleware, findNonSerializableValue, isPlain, - MiddlewareArray, + Tuple, } from '@reduxjs/toolkit' import { isNestedFrozen } from '@internal/serializableStateInvariantMiddleware' @@ -101,7 +101,7 @@ describe('serializableStateInvariantMiddleware', () => { const store = configureStore({ reducer, - middleware: new MiddlewareArray(serializableStateInvariantMiddleware), + middleware: new Tuple(serializableStateInvariantMiddleware), }) const symbol = Symbol.for('SOME_CONSTANT') @@ -148,7 +148,7 @@ describe('serializableStateInvariantMiddleware', () => { reducer: { testSlice: reducer, }, - middleware: new MiddlewareArray(serializableStateInvariantMiddleware), + middleware: new Tuple(serializableStateInvariantMiddleware), }) store.dispatch({ type: ACTION_TYPE }) @@ -208,7 +208,7 @@ describe('serializableStateInvariantMiddleware', () => { reducer: { testSlice: reducer, }, - middleware: new MiddlewareArray(serializableStateInvariantMiddleware), + middleware: new Tuple(serializableStateInvariantMiddleware), }) store.dispatch({ type: ACTION_TYPE }) @@ -255,7 +255,7 @@ describe('serializableStateInvariantMiddleware', () => { reducer: { testSlice: reducer, }, - middleware: new MiddlewareArray(serializableStateInvariantMiddleware), + middleware: new Tuple(serializableStateInvariantMiddleware), }) store.dispatch({ type: ACTION_TYPE }) @@ -299,7 +299,7 @@ describe('serializableStateInvariantMiddleware', () => { reducer: { testSlice: reducer, }, - middleware: new MiddlewareArray(serializableStateInvariantMiddleware), + middleware: new Tuple(serializableStateInvariantMiddleware), }) store.dispatch({ type: ACTION_TYPE }) @@ -323,7 +323,7 @@ describe('serializableStateInvariantMiddleware', () => { const store = configureStore({ reducer: () => ({}), - middleware: new MiddlewareArray(serializableStateMiddleware), + middleware: new Tuple(serializableStateMiddleware), }) expect(numTimesCalled).toBe(0) @@ -348,9 +348,7 @@ describe('serializableStateInvariantMiddleware', () => { it('default value: meta.arg', () => { configureStore({ reducer, - middleware: new MiddlewareArray( - createSerializableStateInvariantMiddleware() - ), + middleware: new Tuple(createSerializableStateInvariantMiddleware()), }).dispatch({ type: 'test', meta: { arg: nonSerializableValue } }) expect(getLog().log).toMatchInlineSnapshot(`""`) @@ -359,7 +357,7 @@ describe('serializableStateInvariantMiddleware', () => { it('default value can be overridden', () => { configureStore({ reducer, - middleware: new MiddlewareArray( + middleware: new Tuple( createSerializableStateInvariantMiddleware({ ignoredActionPaths: [], }) @@ -382,7 +380,7 @@ describe('serializableStateInvariantMiddleware', () => { it('can specify (multiple) different values', () => { configureStore({ reducer, - middleware: new MiddlewareArray( + middleware: new Tuple( createSerializableStateInvariantMiddleware({ ignoredActionPaths: ['payload', 'meta.arg'], }) @@ -399,7 +397,7 @@ describe('serializableStateInvariantMiddleware', () => { it('can specify regexp', () => { configureStore({ reducer, - middleware: new MiddlewareArray( + middleware: new Tuple( createSerializableStateInvariantMiddleware({ ignoredActionPaths: [/^payload\..*$/], }) @@ -427,7 +425,7 @@ describe('serializableStateInvariantMiddleware', () => { const store = configureStore({ reducer: () => ({}), - middleware: new MiddlewareArray(serializableStateMiddleware), + middleware: new Tuple(serializableStateMiddleware), }) expect(numTimesCalled).toBe(0) @@ -490,7 +488,7 @@ describe('serializableStateInvariantMiddleware', () => { reducer: { testSlice: reducer, }, - middleware: new MiddlewareArray(serializableStateInvariantMiddleware), + middleware: new Tuple(serializableStateInvariantMiddleware), }) store.dispatch({ type: ACTION_TYPE }) @@ -509,7 +507,7 @@ describe('serializableStateInvariantMiddleware', () => { const reducer = () => badValue const store = configureStore({ reducer, - middleware: new MiddlewareArray( + middleware: new Tuple( createSerializableStateInvariantMiddleware({ isSerializable: () => { numTimesCalled++ @@ -536,7 +534,7 @@ describe('serializableStateInvariantMiddleware', () => { const reducer = () => badValue const store = configureStore({ reducer, - middleware: new MiddlewareArray( + middleware: new Tuple( createSerializableStateInvariantMiddleware({ isSerializable: () => { numTimesCalled++ @@ -568,7 +566,7 @@ describe('serializableStateInvariantMiddleware', () => { reducer: { testSlice: reducer, }, - middleware: new MiddlewareArray(serializableStateInvariantMiddleware), + middleware: new Tuple(serializableStateInvariantMiddleware), }) store.dispatch({ @@ -594,7 +592,7 @@ describe('serializableStateInvariantMiddleware', () => { reducer: { testSlice: reducer, }, - middleware: new MiddlewareArray(serializableStateInvariantMiddleware), + middleware: new Tuple(serializableStateInvariantMiddleware), }) store.dispatch({ type: 'SOME_ACTION' }) @@ -618,7 +616,7 @@ describe('serializableStateInvariantMiddleware', () => { if (action.type === 'SET_STATE') return action.payload return state }, - middleware: new MiddlewareArray(serializableStateInvariantMiddleware), + middleware: new Tuple(serializableStateInvariantMiddleware), }) const state = createNextState([], () => diff --git a/packages/toolkit/src/tsHelpers.ts b/packages/toolkit/src/tsHelpers.ts index f4486decf5..d08ed5dda0 100644 --- a/packages/toolkit/src/tsHelpers.ts +++ b/packages/toolkit/src/tsHelpers.ts @@ -1,5 +1,5 @@ import type { Middleware, StoreEnhancer } from 'redux' -import type { EnhancerArray, MiddlewareArray } from './utils' +import type { Tuple } from './utils' export function safeAssign( target: T, @@ -100,7 +100,7 @@ type ExtractDispatchFromMiddlewareTuple< > : Acc -export type ExtractDispatchExtensions = M extends MiddlewareArray< +export type ExtractDispatchExtensions = M extends Tuple< infer MiddlewareTuple > ? ExtractDispatchFromMiddlewareTuple @@ -118,9 +118,7 @@ type ExtractStoreExtensionsFromEnhancerTuple< > : Acc -export type ExtractStoreExtensions = E extends EnhancerArray< - infer EnhancerTuple -> +export type ExtractStoreExtensions = E extends Tuple ? ExtractStoreExtensionsFromEnhancerTuple : E extends ReadonlyArray ? UnionToIntersection< @@ -145,9 +143,7 @@ type ExtractStateExtensionsFromEnhancerTuple< > : Acc -export type ExtractStateExtensions = E extends EnhancerArray< - infer EnhancerTuple -> +export type ExtractStateExtensions = E extends Tuple ? ExtractStateExtensionsFromEnhancerTuple : E extends ReadonlyArray ? UnionToIntersection< diff --git a/packages/toolkit/src/utils.ts b/packages/toolkit/src/utils.ts index efb6805b23..8a59616455 100644 --- a/packages/toolkit/src/utils.ts +++ b/packages/toolkit/src/utils.ts @@ -40,89 +40,45 @@ export function find( return undefined } -/** - * @public - */ -export class MiddlewareArray< - Middlewares extends readonly Middleware[] -> extends Array { - constructor(...items: Middlewares) - constructor(...args: any[]) { - super(...args) - Object.setPrototypeOf(this, MiddlewareArray.prototype) +export class Tuple> extends Array< + Items[number] +> { + constructor(...items: Items) { + super(...items) + Object.setPrototypeOf(this, Tuple.prototype) } static get [Symbol.species]() { - return MiddlewareArray as any + return Tuple as any } - concat>>( - items: AdditionalMiddlewares - ): MiddlewareArray<[...Middlewares, ...AdditionalMiddlewares]> - - concat>>( - ...items: AdditionalMiddlewares - ): MiddlewareArray<[...Middlewares, ...AdditionalMiddlewares]> + concat>( + items: Tuple + ): Tuple<[...Items, ...AdditionalItems]> + concat>( + items: AdditionalItems + ): Tuple<[...Items, ...AdditionalItems]> + concat>( + ...items: AdditionalItems + ): Tuple<[...Items, ...AdditionalItems]> concat(...arr: any[]) { return super.concat.apply(this, arr) } - prepend>>( - items: AdditionalMiddlewares - ): MiddlewareArray<[...AdditionalMiddlewares, ...Middlewares]> - - prepend>>( - ...items: AdditionalMiddlewares - ): MiddlewareArray<[...AdditionalMiddlewares, ...Middlewares]> - - prepend(...arr: any[]) { - if (arr.length === 1 && Array.isArray(arr[0])) { - return new MiddlewareArray(...arr[0].concat(this)) - } - return new MiddlewareArray(...arr.concat(this)) - } -} - -/** - * @public - */ -export class EnhancerArray< - Enhancers extends readonly StoreEnhancer[] -> extends Array { - constructor(...items: Enhancers) - constructor(...args: any[]) { - super(...args) - Object.setPrototypeOf(this, EnhancerArray.prototype) - } - - static get [Symbol.species]() { - return EnhancerArray as any - } - - concat>>( - items: AdditionalEnhancers - ): EnhancerArray<[...Enhancers, ...AdditionalEnhancers]> - - concat>>( - ...items: AdditionalEnhancers - ): EnhancerArray<[...Enhancers, ...AdditionalEnhancers]> - concat(...arr: any[]) { - return super.concat.apply(this, arr) - } - - prepend>>( - items: AdditionalEnhancers - ): EnhancerArray<[...AdditionalEnhancers, ...Enhancers]> - - prepend>>( - ...items: AdditionalEnhancers - ): EnhancerArray<[...AdditionalEnhancers, ...Enhancers]> - + prepend>( + items: Tuple + ): Tuple<[...AdditionalItems, ...Items]> + prepend>( + items: AdditionalItems + ): Tuple<[...AdditionalItems, ...Items]> + prepend>( + ...items: AdditionalItems + ): Tuple<[...AdditionalItems, ...Items]> prepend(...arr: any[]) { if (arr.length === 1 && Array.isArray(arr[0])) { - return new EnhancerArray(...arr[0].concat(this)) + return new Tuple(...arr[0].concat(this)) } - return new EnhancerArray(...arr.concat(this)) + return new Tuple(...arr.concat(this)) } } From 8ba5eeaf3191cc25b5fae5388b7921b9a09186cf Mon Sep 17 00:00:00 2001 From: "ben.durrant" Date: Mon, 22 May 2023 15:40:50 +0100 Subject: [PATCH 218/412] account for length edge case --- docs/usage/usage-with-typescript.md | 2 +- packages/toolkit/src/utils.ts | 6 ++++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/docs/usage/usage-with-typescript.md b/docs/usage/usage-with-typescript.md index 5258ede400..9fb7f9ab43 100644 --- a/docs/usage/usage-with-typescript.md +++ b/docs/usage/usage-with-typescript.md @@ -136,7 +136,7 @@ export default store #### Using `Tuple` without `getDefaultMiddleware` -If you want to skip the usage of `getDefaultMiddleware` altogether, you are requred to use `Tuple` for type-safe creation of your `middleware` array. This class extends the default JavaScript `Array` type, only with modified typings for `.concat(...)` and the additional `.prepend(...)` method. +If you want to skip the usage of `getDefaultMiddleware` altogether, you are required to use `Tuple` for type-safe creation of your `middleware` array. This class extends the default JavaScript `Array` type, only with modified typings for `.concat(...)` and the additional `.prepend(...)` method. For example: diff --git a/packages/toolkit/src/utils.ts b/packages/toolkit/src/utils.ts index 8a59616455..2e29b2bc9e 100644 --- a/packages/toolkit/src/utils.ts +++ b/packages/toolkit/src/utils.ts @@ -40,10 +40,12 @@ export function find( return undefined } -export class Tuple> extends Array< +export class Tuple = []> extends Array< Items[number] > { - constructor(...items: Items) { + constructor(length: number) + constructor(...items: Items) + constructor(...items: any[]) { super(...items) Object.setPrototypeOf(this, Tuple.prototype) } From 0116fcc51dc62fc86aaec981e80744fca23da47e Mon Sep 17 00:00:00 2001 From: Ben Durrant Date: Wed, 24 May 2023 20:52:26 +0100 Subject: [PATCH 219/412] fix link --- docs/api/getDefaultEnhancers.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/api/getDefaultEnhancers.mdx b/docs/api/getDefaultEnhancers.mdx index a0630f8ee0..6863d4168e 100644 --- a/docs/api/getDefaultEnhancers.mdx +++ b/docs/api/getDefaultEnhancers.mdx @@ -60,7 +60,7 @@ const store = configureStore({ The resulting array will always contain the `applyMiddleware` enhancer created based on the `configureStore`'s `middleware` field. -Additionally, the [`autoBatchEnhancer`](./autoBatchEnhancer.mdx) is included, to allow for "batching" of low priority action updates. This is used by [RTK Query](/rtk-query/overview.mdx) and should improve performance when using it. +Additionally, the [`autoBatchEnhancer`](./autoBatchEnhancer.mdx) is included, to allow for "batching" of low priority action updates. This is used by [RTK Query](/rtk-query/overview.md) and should improve performance when using it. Currently, the return value is From b029318deea996c837173cf7538c7032231eb390 Mon Sep 17 00:00:00 2001 From: "ben.durrant" Date: Thu, 25 May 2023 10:21:26 +0100 Subject: [PATCH 220/412] Fix inference for reducers without actions specified --- packages/toolkit/src/createSlice.ts | 5 ++- .../toolkit/src/tests/createSlice.typetest.ts | 32 +++++++++++++++++-- 2 files changed, 34 insertions(+), 3 deletions(-) diff --git a/packages/toolkit/src/createSlice.ts b/packages/toolkit/src/createSlice.ts index cb69c3b0ed..f0c18c5179 100644 --- a/packages/toolkit/src/createSlice.ts +++ b/packages/toolkit/src/createSlice.ts @@ -353,6 +353,9 @@ interface AsyncThunkCreator< } export interface ReducerCreators { + reducer( + caseReducer: CaseReducer + ): CaseReducerDefinition reducer( caseReducer: CaseReducer> ): CaseReducerDefinition> @@ -746,7 +749,7 @@ function buildReducerCreators(): ReducerCreators { } asyncThunk.withTypes = () => asyncThunk return { - reducer(caseReducer) { + reducer(caseReducer: CaseReducer) { return Object.assign( { // hack so the wrapping function has the same name as the original diff --git a/packages/toolkit/src/tests/createSlice.typetest.ts b/packages/toolkit/src/tests/createSlice.typetest.ts index 0636d236b6..2635300393 100644 --- a/packages/toolkit/src/tests/createSlice.typetest.ts +++ b/packages/toolkit/src/tests/createSlice.typetest.ts @@ -599,9 +599,20 @@ const value = actionCreators.anyKey }>() return { - normalReducer: create.reducer((state, action) => { + normalReducer: create.reducer( + (state, action: PayloadAction) => { + expectType(state) + expectType(action.payload) + } + ), + optionalReducer: create.reducer( + (state, action: PayloadAction) => { + expectType(state) + expectType(action.payload) + } + ), + noActionReducer: create.reducer((state) => { expectType(state) - expectType(action.payload) }), preparedReducer: create.preparedReducer( (payload: string) => ({ @@ -707,6 +718,23 @@ const value = actionCreators.anyKey type StoreDispatch = typeof store.dispatch expectType>(slice.actions.normalReducer) + slice.actions.normalReducer('') + // @ts-expect-error + slice.actions.normalReducer() + // @ts-expect-error + slice.actions.normalReducer(0) + expectType>( + slice.actions.optionalReducer + ) + slice.actions.optionalReducer() + slice.actions.optionalReducer('') + // @ts-expect-error + slice.actions.optionalReducer(0) + + expectType(slice.actions.noActionReducer) + slice.actions.noActionReducer() + // @ts-expect-error + slice.actions.noActionReducer('') expectType< ActionCreatorWithPreparedPayload< [string], From 107db6d9661236d6adafdfc80daea15d0ad5e5de Mon Sep 17 00:00:00 2001 From: "ben.durrant" Date: Thu, 25 May 2023 10:41:32 +0100 Subject: [PATCH 221/412] account for length overload --- packages/toolkit/src/tests/Tuple.typetest.ts | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/packages/toolkit/src/tests/Tuple.typetest.ts b/packages/toolkit/src/tests/Tuple.typetest.ts index 963164862a..4beaf28fae 100644 --- a/packages/toolkit/src/tests/Tuple.typetest.ts +++ b/packages/toolkit/src/tests/Tuple.typetest.ts @@ -14,7 +14,7 @@ import { expectType } from './helpers' // @ts-expect-error expectType>(stringTuple) - const numberTuple = new Tuple(0) + const numberTuple = new Tuple(0, 1) // @ts-expect-error expectType>(numberTuple) } @@ -63,19 +63,19 @@ import { expectType } from './helpers' { const stringTuple = new Tuple('') - const numberTuple = new Tuple(0) + const numberTuple = new Tuple(0, 1) - expectType>(stringTuple.concat(numberTuple)) + expectType>(stringTuple.concat(numberTuple)) - expectType>(stringTuple.prepend(numberTuple)) + expectType>(stringTuple.prepend(numberTuple)) - expectType>(numberTuple.concat(stringTuple)) + expectType>(numberTuple.concat(stringTuple)) - expectType>(numberTuple.prepend(stringTuple)) + expectType>(numberTuple.prepend(stringTuple)) // @ts-expect-error - expectType>(stringTuple.prepend(numberTuple)) + expectType>(stringTuple.prepend(numberTuple)) // @ts-expect-error - expectType>(stringTuple.concat(numberTuple)) + expectType>(stringTuple.concat(numberTuple)) } From f2b60e94f7b402ba2e10bdd9ec87f4f3b735b856 Mon Sep 17 00:00:00 2001 From: Ben Durrant Date: Thu, 25 May 2023 20:44:40 +0100 Subject: [PATCH 222/412] Added note about circular type issues. --- docs/api/createSlice.mdx | 65 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 65 insertions(+) diff --git a/docs/api/createSlice.mdx b/docs/api/createSlice.mdx index 2e9b375e12..6b66562bac 100644 --- a/docs/api/createSlice.mdx +++ b/docs/api/createSlice.mdx @@ -353,6 +353,71 @@ A set of selectors that receive the slice state as their first parameter, and an Each selector will have a corresponding key in the resulting [`selectors`](#selectors-1) object. +:::caution Circular types + +It's fairly common to have selectors that use other selectors. This is still possible with slice selectors, but defining a selector without a return type can cause a circular type inference problem: + +```ts no-transpile +const counterSlice = createSlice({ + name: 'counter', + initialState: { value: 0 }, + reducers: {}, + selectors: { + selectValue: (state) => state.value, + // highlight-start + // this creates a cycle, because it's inferring a type from the object we're creating here + selectTimes: (state, times = 1) => + counterSlice.getSelectors().selectValue(state) * times, + // highlight-end + }, +}) +``` + +This cycle can be fixed by providing an explicit return type for the selector: + +```ts no-transpile +const counterSlice = createSlice({ + name: 'counter', + initialState: { value: 0 }, + reducers: {}, + selectors: { + selectValue: (state) => state.value, + // highlight-start + // explicit return type means cycle is broken + selectTimes: (state, times = 1): number => + counterSlice.getSelectors().selectValue(state) * times, + // highlight-end + }, +}) +``` + +This limitation may be also encountered when using a slice's `asyncThunk` creator. +In the same way, the issue is resolved by explicitly providing a type somewhere in the chain and breaking the cycle. + +```ts no-transpile +const counterSlice = createSlice({ + name: 'counter', + initialState: { value: 0 }, + reducers: (create) => ({ + getCountData: create.asyncThunk(async (_arg, { getState }) => { + const currentCount = counterSlice.selectors.selectValue( + getState() as RootState + ) + // highlight-start + // this would cause a circular type, but the type annotation breaks the circle + const result: Response = await fetch('api/' + currentCount) + // highlight-end + return result.json() + }), + }), + selectors: { + selectValue: (state) => state.value, + }, +}) +``` + +::: + ## Return Value `createSlice` will return an object that looks like: From c717d8e9e48deeab9676cefea47ad31b51b75269 Mon Sep 17 00:00:00 2001 From: "ben.durrant" Date: Fri, 26 May 2023 15:54:25 +0100 Subject: [PATCH 223/412] add createDraftSafeSelectorCreator --- .../toolkit/src/createDraftSafeSelector.ts | 24 ++++++++++++------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/packages/toolkit/src/createDraftSafeSelector.ts b/packages/toolkit/src/createDraftSafeSelector.ts index b1858af2e7..9e1bb0a6da 100644 --- a/packages/toolkit/src/createDraftSafeSelector.ts +++ b/packages/toolkit/src/createDraftSafeSelector.ts @@ -1,5 +1,17 @@ import { current, isDraft } from 'immer' -import { createSelector } from 'reselect' +import { createSelectorCreator, defaultMemoize } from 'reselect' + +export const createDraftSafeSelectorCreator: typeof createSelectorCreator = ( + ...args: unknown[] +) => { + const createSelector = (createSelectorCreator as any)(...args) + return (...args: unknown[]) => { + const selector = createSelector(...args) + const wrappedSelector = (value: unknown, ...rest: unknown[]) => + selector(isDraft(value) ? current(value) : value, ...rest) + return wrappedSelector as any + } +} /** * "Draft-Safe" version of `reselect`'s `createSelector`: @@ -8,11 +20,5 @@ import { createSelector } from 'reselect' * that might be possibly outdated if the draft has been modified since. * @public */ -export const createDraftSafeSelector: typeof createSelector = ( - ...args: unknown[] -) => { - const selector = (createSelector as any)(...args) - const wrappedSelector = (value: unknown, ...rest: unknown[]) => - selector(isDraft(value) ? current(value) : value, ...rest) - return wrappedSelector as any -} +export const createDraftSafeSelector = + createDraftSafeSelectorCreator(defaultMemoize) From 38a739a18d1955f67edee4b2cf814f1168e9748a Mon Sep 17 00:00:00 2001 From: "ben.durrant" Date: Fri, 26 May 2023 16:23:46 +0100 Subject: [PATCH 224/412] Allow passing a custom instance of createSelector to getSelectors --- packages/toolkit/src/entities/models.ts | 9 ++++- .../toolkit/src/entities/state_selectors.ts | 38 +++++++++++-------- .../entities/tests/state_selectors.test.ts | 20 +++++++++- 3 files changed, 48 insertions(+), 19 deletions(-) diff --git a/packages/toolkit/src/entities/models.ts b/packages/toolkit/src/entities/models.ts index e57c4aee9a..a82af1e963 100644 --- a/packages/toolkit/src/entities/models.ts +++ b/packages/toolkit/src/entities/models.ts @@ -1,5 +1,6 @@ import type { PayloadAction } from '../createAction' import type { IsAny } from '../tsHelpers' +import type { AnyCreateSelectorFunction } from './state_selectors' /** * @public @@ -164,8 +165,12 @@ export interface EntityAdapter extends EntityStateAdapter { sortComparer: false | Comparer getInitialState(): EntityState getInitialState(state: S): EntityState & S - getSelectors(): EntitySelectors> + getSelectors( + selectState?: undefined, + createSelector?: AnyCreateSelectorFunction + ): EntitySelectors> getSelectors( - selectState: (state: V) => EntityState + selectState: (state: V) => EntityState, + createSelector?: AnyCreateSelectorFunction ): EntitySelectors } diff --git a/packages/toolkit/src/entities/state_selectors.ts b/packages/toolkit/src/entities/state_selectors.ts index 46f59d3d9e..85fdc715c7 100644 --- a/packages/toolkit/src/entities/state_selectors.ts +++ b/packages/toolkit/src/entities/state_selectors.ts @@ -1,4 +1,4 @@ -import type { Selector } from 'reselect' +import type { CreateSelectorFunction, Selector } from 'reselect' import { createDraftSafeSelector } from '../createDraftSafeSelector' import type { EntityState, @@ -7,19 +7,29 @@ import type { EntityId, } from './models' +export type AnyCreateSelectorFunction = CreateSelectorFunction< + (...args: unknown[]) => unknown, + any>(func: F) => F +> + export function createSelectorsFactory() { - function getSelectors(): EntitySelectors> + function getSelectors( + selectState?: undefined, + createSelector?: AnyCreateSelectorFunction + ): EntitySelectors> function getSelectors( - selectState: (state: V) => EntityState + selectState: (state: V) => EntityState, + createSelector?: AnyCreateSelectorFunction ): EntitySelectors function getSelectors( - selectState?: (state: V) => EntityState + selectState?: (state: V) => EntityState, + createSelector: AnyCreateSelectorFunction = createDraftSafeSelector ): EntitySelectors { const selectIds = (state: EntityState) => state.ids const selectEntities = (state: EntityState) => state.entities - const selectAll = createDraftSafeSelector( + const selectAll = createSelector( selectIds, selectEntities, (ids, entities): T[] => ids.map((id) => entities[id]!) @@ -29,7 +39,7 @@ export function createSelectorsFactory() { const selectById = (entities: Dictionary, id: EntityId) => entities[id] - const selectTotal = createDraftSafeSelector(selectIds, (ids) => ids.length) + const selectTotal = createSelector(selectIds, (ids) => ids.length) if (!selectState) { return { @@ -37,25 +47,21 @@ export function createSelectorsFactory() { selectEntities, selectAll, selectTotal, - selectById: createDraftSafeSelector( - selectEntities, - selectId, - selectById - ), + selectById: createSelector(selectEntities, selectId, selectById), } } - const selectGlobalizedEntities = createDraftSafeSelector( + const selectGlobalizedEntities = createSelector( selectState as Selector>, selectEntities ) return { - selectIds: createDraftSafeSelector(selectState, selectIds), + selectIds: createSelector(selectState, selectIds), selectEntities: selectGlobalizedEntities, - selectAll: createDraftSafeSelector(selectState, selectAll), - selectTotal: createDraftSafeSelector(selectState, selectTotal), - selectById: createDraftSafeSelector( + selectAll: createSelector(selectState, selectAll), + selectTotal: createSelector(selectState, selectTotal), + selectById: createSelector( selectGlobalizedEntities, selectId, selectById diff --git a/packages/toolkit/src/entities/tests/state_selectors.test.ts b/packages/toolkit/src/entities/tests/state_selectors.test.ts index 9458c9c93a..c15793e86a 100644 --- a/packages/toolkit/src/entities/tests/state_selectors.test.ts +++ b/packages/toolkit/src/entities/tests/state_selectors.test.ts @@ -1,10 +1,12 @@ +import { createDraftSafeSelectorCreator } from '../../createDraftSafeSelector' import type { EntityAdapter, EntityState } from '../index' import { createEntityAdapter } from '../index' import type { EntitySelectors } from '../models' import type { BookModel } from './fixtures/book' import { AClockworkOrange, AnimalFarm, TheGreatGatsby } from './fixtures/book' import type { Selector } from 'reselect' -import { createSelector } from 'reselect' +import { createSelector, weakMapMemoize } from 'reselect' +import { vi } from 'vitest' describe('Entity State Selectors', () => { describe('Composed Selectors', () => { @@ -122,6 +124,22 @@ describe('Entity State Selectors', () => { expect(second).toBe(AnimalFarm) }) }) + describe('custom createSelector instance', () => { + it('should use the custom createSelector function if provided', () => { + const memoizeSpy = vi.fn(weakMapMemoize) + const createCustomSelector = createDraftSafeSelectorCreator(memoizeSpy) + + const adapter = createEntityAdapter({ + selectId: (book: BookModel) => book.id, + }) + + adapter.getSelectors(undefined, createCustomSelector) + + expect(memoizeSpy).toHaveBeenCalled() + + memoizeSpy.mockClear() + }) + }) }) function expectType(t: T) { From 799682750800f1340ce109e8fe6a841d4ed8237f Mon Sep 17 00:00:00 2001 From: "ben.durrant" Date: Fri, 26 May 2023 16:26:29 +0100 Subject: [PATCH 225/412] export createDraftSafeSelectorCreator --- packages/toolkit/src/index.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/packages/toolkit/src/index.ts b/packages/toolkit/src/index.ts index 6630c94687..64bf404204 100644 --- a/packages/toolkit/src/index.ts +++ b/packages/toolkit/src/index.ts @@ -20,7 +20,10 @@ export type { OutputSelector, ParametricSelector, } from 'reselect' -export { createDraftSafeSelector } from './createDraftSafeSelector' +export { + createDraftSafeSelector, + createDraftSafeSelectorCreator, +} from './createDraftSafeSelector' export type { ThunkAction, ThunkDispatch, ThunkMiddleware } from 'redux-thunk' export { From 8f229d18d87206d64db512d6c6cf33aa399c40ff Mon Sep 17 00:00:00 2001 From: "ben.durrant" Date: Fri, 26 May 2023 16:47:36 +0100 Subject: [PATCH 226/412] add to docs --- docs/api/createEntityAdapter.mdx | 38 +++++++++++++++++++++++++------- docs/api/createSelector.mdx | 22 ++++++++++++++++++ 2 files changed, 52 insertions(+), 8 deletions(-) diff --git a/docs/api/createEntityAdapter.mdx b/docs/api/createEntityAdapter.mdx index 0cfee25b9e..0b45a783db 100644 --- a/docs/api/createEntityAdapter.mdx +++ b/docs/api/createEntityAdapter.mdx @@ -228,7 +228,6 @@ All three options will insert _new_ entities into the list. However they differ ::: - Each method has a signature that looks like: ```ts no-transpile @@ -284,9 +283,36 @@ The entity adapter will contain a `getSelectors()` function that returns a set o Each selector function will be created using the `createSelector` function from Reselect, to enable memoizing calculation of the results. +:::tip + +The `createSelector` instance used can be replaced, by passing it as a second parameter: + +```js +import { + createDraftSafeSelectorCreator, + weakMapMemoize, +} from '@reduxjs/toolkit' + +const createWeakMapDraftSafeSelector = + createDraftSafeSelectorCreator(weakMapMemoize) + +const simpleSelectors = booksAdapter.getSelectors( + undefined, + createWeakMapDraftSafeSelector +) +const globalizedSelectors = booksAdapter.getSelectors( + (state) => state.books, + createWeakMapDraftSafeSelector +) +``` + +If no instance is passed, it will default to [`createDraftSafeSelector`](./createSelector#createDraftSafeSelector). + +::: + Because selector functions are dependent on knowing where in the state tree this specific entity state object is kept, `getSelectors()` can be called in two ways: -- If called without any arguments, it returns an "unglobalized" set of selector functions that assume their `state` argument is the actual entity state object to read from. +- If called without any arguments (or with undefined as the first parameter), it returns an "unglobalized" set of selector functions that assume their `state` argument is the actual entity state object to read from. - It may also be called with a selector function that accepts the entire Redux state tree and returns the correct entity state object. For example, the entity state for a `Book` type might be kept in the Redux state tree as `state.books`. You can use `getSelectors()` to read from that state in two ways: @@ -358,12 +384,8 @@ const booksSlice = createSlice({ }, }) -const { - bookAdded, - booksLoading, - booksReceived, - bookUpdated, -} = booksSlice.actions +const { bookAdded, booksLoading, booksReceived, bookUpdated } = + booksSlice.actions const store = configureStore({ reducer: { diff --git a/docs/api/createSelector.mdx b/docs/api/createSelector.mdx index ec88f8fd3d..9bad32648e 100644 --- a/docs/api/createSelector.mdx +++ b/docs/api/createSelector.mdx @@ -62,3 +62,25 @@ After executing that, `unsafe1` and `unsafe2` will be of the same value, because executed on the same object - but `safe2` will actually be different from `safe1` (with the updated value of `2`), because the safe selector detected that it was executed on a Immer draft object and recalculated using the current value instead of returning a cached value. + +:::tip `createDraftSafeSelectorCreator` + +RTK also exports a `createDraftSafeSelectorCreator` function, the "draft safe" equivalent of [`createSelectorCreator`](https://github.com/reduxjs/reselect#createselectorcreatormemoize-memoizeoptions). + +```js +import { + createDraftSafeSelectorCreator, + weakMapMemoize, +} from '@reduxjs/toolkit' + +const createWeakMapDraftSafeSelector = + createDraftSafeSelectorCreator(weakMapMemoize) + +const selectSelf = (state: State) => state +const draftSafeSelector = createWeakMapDraftSafeSelector( + selectSelf, + (state) => state.value +) +``` + +::: From 33304e469d692ba7938628377b92ce0a7f9e750e Mon Sep 17 00:00:00 2001 From: "ben.durrant" Date: Fri, 26 May 2023 16:53:29 +0100 Subject: [PATCH 227/412] fix highlighting --- docs/api/createSelector.mdx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/api/createSelector.mdx b/docs/api/createSelector.mdx index 9bad32648e..8674a315fd 100644 --- a/docs/api/createSelector.mdx +++ b/docs/api/createSelector.mdx @@ -37,7 +37,7 @@ All selectors created by `entityAdapter.getSelectors` are "draft safe" selectors Example: -```js +```ts no-transpile const selectSelf = (state: State) => state const unsafeSelector = createSelector(selectSelf, (state) => state.value) const draftSafeSelector = createDraftSafeSelector( @@ -67,7 +67,7 @@ value instead of returning a cached value. RTK also exports a `createDraftSafeSelectorCreator` function, the "draft safe" equivalent of [`createSelectorCreator`](https://github.com/reduxjs/reselect#createselectorcreatormemoize-memoizeoptions). -```js +```ts no-transpile import { createDraftSafeSelectorCreator, weakMapMemoize, From 12cad5b105b8f7918f5151cff9d4ac6891c1f872 Mon Sep 17 00:00:00 2001 From: "ben.durrant" Date: Fri, 19 May 2023 18:26:50 +0100 Subject: [PATCH 228/412] Require usage of MiddlewareArray and EnhancerArray in TS --- packages/toolkit/src/configureStore.ts | 10 ++- .../toolkit/src/tests/configureStore.test.ts | 19 +++-- .../src/tests/configureStore.typetest.ts | 78 ++++++++++--------- ...rializableStateInvariantMiddleware.test.ts | 57 +++++++------- packages/toolkit/src/tsHelpers.ts | 6 +- packages/toolkit/src/utils.ts | 4 +- 6 files changed, 95 insertions(+), 79 deletions(-) diff --git a/packages/toolkit/src/configureStore.ts b/packages/toolkit/src/configureStore.ts index f08130e75a..39f2aaaefa 100644 --- a/packages/toolkit/src/configureStore.ts +++ b/packages/toolkit/src/configureStore.ts @@ -37,8 +37,8 @@ const IS_PRODUCTION = process.env.NODE_ENV === 'production' export interface ConfigureStoreOptions< S = any, A extends Action = AnyAction, - M extends Middlewares = Middlewares, - E extends Enhancers = Enhancers, + M extends MiddlewareArray> = MiddlewareArray>, + E extends EnhancerArray = EnhancerArray, P = S > { /** @@ -112,8 +112,10 @@ export type EnhancedStore< export function configureStore< S = any, A extends Action = AnyAction, - M extends Middlewares = MiddlewareArray<[ThunkMiddlewareFor]>, - E extends Enhancers = EnhancerArray< + M extends MiddlewareArray> = MiddlewareArray< + [ThunkMiddlewareFor] + >, + E extends EnhancerArray = EnhancerArray< [StoreEnhancer<{ dispatch: ExtractDispatchExtensions }>, StoreEnhancer] >, P = S diff --git a/packages/toolkit/src/tests/configureStore.test.ts b/packages/toolkit/src/tests/configureStore.test.ts index f6e6b41031..1ade822fc1 100644 --- a/packages/toolkit/src/tests/configureStore.test.ts +++ b/packages/toolkit/src/tests/configureStore.test.ts @@ -1,5 +1,6 @@ import { vi } from 'vitest' import type { StoreEnhancer } from '@reduxjs/toolkit' +import { MiddlewareArray, EnhancerArray } from '@reduxjs/toolkit' import type * as Redux from 'redux' import type * as DevTools from '@internal/devtoolsExtension' @@ -108,7 +109,9 @@ describe('configureStore', async () => { describe('given no middleware', () => { it('calls createStore without any middleware', () => { - expect(configureStore({ middleware: [], reducer })).toBeInstanceOf(Object) + expect( + configureStore({ middleware: new MiddlewareArray(), reducer }) + ).toBeInstanceOf(Object) expect(redux.applyMiddleware).toHaveBeenCalledWith() expect(mockDevtoolsCompose).toHaveBeenCalled() // @remap-prod-remove-line-line expect(redux.createStore).toHaveBeenCalledWith( @@ -171,9 +174,9 @@ describe('configureStore', async () => { it('calls createStore with custom middleware and without default middleware', () => { const thank: Redux.Middleware = (_store) => (next) => (action) => next(action) - expect(configureStore({ middleware: [thank], reducer })).toBeInstanceOf( - Object - ) + expect( + configureStore({ middleware: new MiddlewareArray(thank), reducer }) + ).toBeInstanceOf(Object) expect(redux.applyMiddleware).toHaveBeenCalledWith(thank) expect(mockDevtoolsCompose).toHaveBeenCalled() // @remap-prod-remove-line-line expect(redux.createStore).toHaveBeenCalledWith( @@ -194,7 +197,7 @@ describe('configureStore', async () => { expect(getDefaultMiddleware).toEqual(expect.any(Function)) expect(getDefaultMiddleware()).toEqual(expect.any(Array)) - return [thank] + return new MiddlewareArray(thank) }) const store = configureStore({ middleware: builder, reducer }) @@ -303,7 +306,7 @@ describe('configureStore', async () => { it('warns if middleware enhancer is excluded from final array when middlewares are provided', () => { const store = configureStore({ reducer, - enhancers: [dummyEnhancer], + enhancers: new EnhancerArray(dummyEnhancer), }) expect(dummyEnhancerCalled).toBe(true) @@ -315,8 +318,8 @@ describe('configureStore', async () => { it("doesn't warn when middleware enhancer is excluded if no middlewares provided", () => { const store = configureStore({ reducer, - middleware: [], - enhancers: [dummyEnhancer], + middleware: new MiddlewareArray(), + enhancers: new EnhancerArray(dummyEnhancer), }) expect(dummyEnhancerCalled).toBe(true) diff --git a/packages/toolkit/src/tests/configureStore.typetest.ts b/packages/toolkit/src/tests/configureStore.typetest.ts index 195ee4db8b..d4d02681e4 100644 --- a/packages/toolkit/src/tests/configureStore.typetest.ts +++ b/packages/toolkit/src/tests/configureStore.typetest.ts @@ -10,7 +10,12 @@ import type { } from 'redux' import { applyMiddleware, combineReducers } from 'redux' import type { PayloadAction, ConfigureStoreOptions } from '@reduxjs/toolkit' -import { configureStore, createSlice } from '@reduxjs/toolkit' +import { + configureStore, + createSlice, + MiddlewareArray, + EnhancerArray, +} from '@reduxjs/toolkit' import type { ThunkMiddleware, ThunkAction, ThunkDispatch } from 'redux-thunk' import { thunk } from 'redux-thunk' import { expectNotAny, expectType } from './helpers' @@ -67,20 +72,26 @@ const _anyMiddleware: any = () => () => () => {} } /* - * Test: configureStore() accepts middleware array. + * Test: configureStore() accepts MiddlewareArray, but not plain array. */ { const middleware: Middleware = (store) => (next) => next configureStore({ reducer: () => 0, + middleware: new MiddlewareArray(middleware), + }) + + configureStore({ + reducer: () => 0, + // @ts-expect-error middleware: [middleware], }) configureStore({ reducer: () => 0, // @ts-expect-error - middleware: ['not middleware'], + middleware: new MiddlewareArray('not middleware'), }) } @@ -133,13 +144,21 @@ const _anyMiddleware: any = () => () => () => {} } /* - * Test: configureStore() accepts store enhancer. + * Test: configureStore() accepts store EnhancerArray, but not plain array */ { { + const enhancer = applyMiddleware(() => (next) => next) + const store = configureStore({ reducer: () => 0, - enhancers: [applyMiddleware(() => (next) => next)] as const, + enhancers: new EnhancerArray(enhancer), + }) + + const store2 = configureStore({ + reducer: () => 0, + // @ts-expect-error + enhancers: [enhancer], }) expectType>( @@ -150,7 +169,7 @@ const _anyMiddleware: any = () => () => () => {} configureStore({ reducer: () => 0, // @ts-expect-error - enhancers: ['not a store enhancer'], + enhancers: new EnhancerArray('not a store enhancer'), }) { @@ -178,10 +197,10 @@ const _anyMiddleware: any = () => () => () => {} const store = configureStore({ reducer: () => 0, - enhancers: [ + enhancers: new EnhancerArray( somePropertyStoreEnhancer, - anotherPropertyStoreEnhancer, - ] as const, + anotherPropertyStoreEnhancer + ), }) expectType(store.dispatch) @@ -240,11 +259,10 @@ const _anyMiddleware: any = () => () => () => {} const store = configureStore({ reducer: () => ({ aProperty: 0 }), - enhancers: [ + enhancers: new EnhancerArray( someStateExtendingEnhancer, - anotherStateExtendingEnhancer, - // this doesn't work without the as const - ] as const, + anotherStateExtendingEnhancer + ), }) const state = store.getState() @@ -512,7 +530,7 @@ const _anyMiddleware: any = () => () => () => {} { const store = configureStore({ reducer: reducerA, - middleware: [], + middleware: new MiddlewareArray(), }) // @ts-expect-error store.dispatch(thunkA()) @@ -525,7 +543,7 @@ const _anyMiddleware: any = () => () => () => {} { const store = configureStore({ reducer: reducerA, - middleware: [thunk] as [ThunkMiddleware], + middleware: new MiddlewareArray(thunk as ThunkMiddleware), }) store.dispatch(thunkA()) // @ts-expect-error @@ -537,21 +555,9 @@ const _anyMiddleware: any = () => () => () => {} { const store = configureStore({ reducer: reducerA, - middleware: [] as any as [Middleware<(a: StateA) => boolean, StateA>], - }) - const result: boolean = store.dispatch(5) - // @ts-expect-error - const result2: string = store.dispatch(5) - } - /** - * Test: read-only middleware tuple - */ - { - const store = configureStore({ - reducer: reducerA, - middleware: [] as any as readonly [ - Middleware<(a: StateA) => boolean, StateA> - ], + middleware: new MiddlewareArray( + 0 as unknown as Middleware<(a: StateA) => boolean, StateA> + ), }) const result: boolean = store.dispatch(5) // @ts-expect-error @@ -561,11 +567,13 @@ const _anyMiddleware: any = () => () => () => {} * Test: multiple custom middleware */ { - const middleware = [] as any as [ - Middleware<(a: 'a') => 'A', StateA>, - Middleware<(b: 'b') => 'B', StateA>, - ThunkMiddleware - ] + const middleware = [] as any as MiddlewareArray< + [ + Middleware<(a: 'a') => 'A', StateA>, + Middleware<(b: 'b') => 'B', StateA>, + ThunkMiddleware + ] + > const store = configureStore({ reducer: reducerA, middleware, diff --git a/packages/toolkit/src/tests/serializableStateInvariantMiddleware.test.ts b/packages/toolkit/src/tests/serializableStateInvariantMiddleware.test.ts index 9235c60efc..1d8361b3d1 100644 --- a/packages/toolkit/src/tests/serializableStateInvariantMiddleware.test.ts +++ b/packages/toolkit/src/tests/serializableStateInvariantMiddleware.test.ts @@ -10,6 +10,7 @@ import { createSerializableStateInvariantMiddleware, findNonSerializableValue, isPlain, + MiddlewareArray, } from '@reduxjs/toolkit' import { isNestedFrozen } from '@internal/serializableStateInvariantMiddleware' @@ -100,7 +101,7 @@ describe('serializableStateInvariantMiddleware', () => { const store = configureStore({ reducer, - middleware: [serializableStateInvariantMiddleware], + middleware: new MiddlewareArray(serializableStateInvariantMiddleware), }) const symbol = Symbol.for('SOME_CONSTANT') @@ -147,7 +148,7 @@ describe('serializableStateInvariantMiddleware', () => { reducer: { testSlice: reducer, }, - middleware: [serializableStateInvariantMiddleware], + middleware: new MiddlewareArray(serializableStateInvariantMiddleware), }) store.dispatch({ type: ACTION_TYPE }) @@ -207,7 +208,7 @@ describe('serializableStateInvariantMiddleware', () => { reducer: { testSlice: reducer, }, - middleware: [serializableStateInvariantMiddleware], + middleware: new MiddlewareArray(serializableStateInvariantMiddleware), }) store.dispatch({ type: ACTION_TYPE }) @@ -254,7 +255,7 @@ describe('serializableStateInvariantMiddleware', () => { reducer: { testSlice: reducer, }, - middleware: [serializableStateInvariantMiddleware], + middleware: new MiddlewareArray(serializableStateInvariantMiddleware), }) store.dispatch({ type: ACTION_TYPE }) @@ -298,7 +299,7 @@ describe('serializableStateInvariantMiddleware', () => { reducer: { testSlice: reducer, }, - middleware: [serializableStateInvariantMiddleware], + middleware: new MiddlewareArray(serializableStateInvariantMiddleware), }) store.dispatch({ type: ACTION_TYPE }) @@ -322,7 +323,7 @@ describe('serializableStateInvariantMiddleware', () => { const store = configureStore({ reducer: () => ({}), - middleware: [serializableStateMiddleware], + middleware: new MiddlewareArray(serializableStateMiddleware), }) expect(numTimesCalled).toBe(0) @@ -347,7 +348,9 @@ describe('serializableStateInvariantMiddleware', () => { it('default value: meta.arg', () => { configureStore({ reducer, - middleware: [createSerializableStateInvariantMiddleware()], + middleware: new MiddlewareArray( + createSerializableStateInvariantMiddleware() + ), }).dispatch({ type: 'test', meta: { arg: nonSerializableValue } }) expect(getLog().log).toMatchInlineSnapshot(`""`) @@ -356,11 +359,11 @@ describe('serializableStateInvariantMiddleware', () => { it('default value can be overridden', () => { configureStore({ reducer, - middleware: [ + middleware: new MiddlewareArray( createSerializableStateInvariantMiddleware({ ignoredActionPaths: [], - }), - ], + }) + ), }).dispatch({ type: 'test', meta: { arg: nonSerializableValue } }) expect(getLog().log).toMatchInlineSnapshot(` @@ -379,11 +382,11 @@ describe('serializableStateInvariantMiddleware', () => { it('can specify (multiple) different values', () => { configureStore({ reducer, - middleware: [ + middleware: new MiddlewareArray( createSerializableStateInvariantMiddleware({ ignoredActionPaths: ['payload', 'meta.arg'], - }), - ], + }) + ), }).dispatch({ type: 'test', payload: { arg: nonSerializableValue }, @@ -396,11 +399,11 @@ describe('serializableStateInvariantMiddleware', () => { it('can specify regexp', () => { configureStore({ reducer, - middleware: [ + middleware: new MiddlewareArray( createSerializableStateInvariantMiddleware({ ignoredActionPaths: [/^payload\..*$/], - }), - ], + }) + ), }).dispatch({ type: 'test', payload: { arg: nonSerializableValue }, @@ -424,7 +427,7 @@ describe('serializableStateInvariantMiddleware', () => { const store = configureStore({ reducer: () => ({}), - middleware: [serializableStateMiddleware], + middleware: new MiddlewareArray(serializableStateMiddleware), }) expect(numTimesCalled).toBe(0) @@ -487,7 +490,7 @@ describe('serializableStateInvariantMiddleware', () => { reducer: { testSlice: reducer, }, - middleware: [serializableStateInvariantMiddleware], + middleware: new MiddlewareArray(serializableStateInvariantMiddleware), }) store.dispatch({ type: ACTION_TYPE }) @@ -506,15 +509,15 @@ describe('serializableStateInvariantMiddleware', () => { const reducer = () => badValue const store = configureStore({ reducer, - middleware: [ + middleware: new MiddlewareArray( createSerializableStateInvariantMiddleware({ isSerializable: () => { numTimesCalled++ return true }, ignoreState: true, - }), - ], + }) + ), }) expect(numTimesCalled).toBe(0) @@ -533,7 +536,7 @@ describe('serializableStateInvariantMiddleware', () => { const reducer = () => badValue const store = configureStore({ reducer, - middleware: [ + middleware: new MiddlewareArray( createSerializableStateInvariantMiddleware({ isSerializable: () => { numTimesCalled++ @@ -541,8 +544,8 @@ describe('serializableStateInvariantMiddleware', () => { }, ignoreState: true, ignoreActions: true, - }), - ], + }) + ), }) expect(numTimesCalled).toBe(0) @@ -565,7 +568,7 @@ describe('serializableStateInvariantMiddleware', () => { reducer: { testSlice: reducer, }, - middleware: [serializableStateInvariantMiddleware], + middleware: new MiddlewareArray(serializableStateInvariantMiddleware), }) store.dispatch({ @@ -591,7 +594,7 @@ describe('serializableStateInvariantMiddleware', () => { reducer: { testSlice: reducer, }, - middleware: [serializableStateInvariantMiddleware], + middleware: new MiddlewareArray(serializableStateInvariantMiddleware), }) store.dispatch({ type: 'SOME_ACTION' }) @@ -615,7 +618,7 @@ describe('serializableStateInvariantMiddleware', () => { if (action.type === 'SET_STATE') return action.payload return state }, - middleware: [serializableStateInvariantMiddleware], + middleware: new MiddlewareArray(serializableStateInvariantMiddleware), }) const state = createNextState([], () => diff --git a/packages/toolkit/src/tsHelpers.ts b/packages/toolkit/src/tsHelpers.ts index 1b331c9962..f4486decf5 100644 --- a/packages/toolkit/src/tsHelpers.ts +++ b/packages/toolkit/src/tsHelpers.ts @@ -91,7 +91,7 @@ export type ExcludeFromTuple = T extends [ : Acc type ExtractDispatchFromMiddlewareTuple< - MiddlewareTuple extends any[], + MiddlewareTuple extends readonly any[], Acc extends {} > = MiddlewareTuple extends [infer Head, ...infer Tail] ? ExtractDispatchFromMiddlewareTuple< @@ -109,7 +109,7 @@ export type ExtractDispatchExtensions = M extends MiddlewareArray< : never type ExtractStoreExtensionsFromEnhancerTuple< - EnhancerTuple extends any[], + EnhancerTuple extends readonly any[], Acc extends {} > = EnhancerTuple extends [infer Head, ...infer Tail] ? ExtractStoreExtensionsFromEnhancerTuple< @@ -133,7 +133,7 @@ export type ExtractStoreExtensions = E extends EnhancerArray< : never type ExtractStateExtensionsFromEnhancerTuple< - EnhancerTuple extends any[], + EnhancerTuple extends readonly any[], Acc extends {} > = EnhancerTuple extends [infer Head, ...infer Tail] ? ExtractStateExtensionsFromEnhancerTuple< diff --git a/packages/toolkit/src/utils.ts b/packages/toolkit/src/utils.ts index 3a55bab1a4..efb6805b23 100644 --- a/packages/toolkit/src/utils.ts +++ b/packages/toolkit/src/utils.ts @@ -44,7 +44,7 @@ export function find( * @public */ export class MiddlewareArray< - Middlewares extends Middleware[] + Middlewares extends readonly Middleware[] > extends Array { constructor(...items: Middlewares) constructor(...args: any[]) { @@ -87,7 +87,7 @@ export class MiddlewareArray< * @public */ export class EnhancerArray< - Enhancers extends StoreEnhancer[] + Enhancers extends readonly StoreEnhancer[] > extends Array { constructor(...items: Enhancers) constructor(...args: any[]) { From e521a4444eafe63bc9c727b8531c68bd31a91efb Mon Sep 17 00:00:00 2001 From: Ben Durrant Date: Sun, 21 May 2023 17:05:39 +0100 Subject: [PATCH 229/412] update docs --- docs/api/actionCreatorMiddleware.mdx | 3 ++- docs/api/configureStore.mdx | 34 +++++++++++++++++++++++++- docs/api/getDefaultMiddleware.mdx | 2 +- docs/api/immutabilityMiddleware.mdx | 3 ++- docs/api/serializabilityMiddleware.mdx | 3 ++- docs/usage/usage-with-typescript.md | 13 +++------- 6 files changed, 43 insertions(+), 15 deletions(-) diff --git a/docs/api/actionCreatorMiddleware.mdx b/docs/api/actionCreatorMiddleware.mdx index 2248a16482..54d3d055dd 100644 --- a/docs/api/actionCreatorMiddleware.mdx +++ b/docs/api/actionCreatorMiddleware.mdx @@ -47,6 +47,7 @@ export default function (state = {}, action: any) { import { configureStore, createActionCreatorInvariantMiddleware, + MiddlewareArray, } from '@reduxjs/toolkit' import reducer from './reducer' @@ -62,6 +63,6 @@ const actionCreatorMiddleware = createActionCreatorInvariantMiddleware({ const store = configureStore({ reducer, - middleware: [actionCreatorMiddleware], + middleware: new MiddlewareArray(actionCreatorMiddleware), }) ``` diff --git a/docs/api/configureStore.mdx b/docs/api/configureStore.mdx index 7abd615508..aa5ee8b8f8 100644 --- a/docs/api/configureStore.mdx +++ b/docs/api/configureStore.mdx @@ -94,6 +94,22 @@ and should return a middleware array. For more details on how the `middleware` parameter works and the list of middleware that are added by default, see the [`getDefaultMiddleware` docs page](./getDefaultMiddleware.mdx). +:::note MiddlewareArray +Typescript users are required to use a `MiddlewareArray` instance (if not using a `getDefaultMiddleware` result, which is already a `MiddlewareArray`), for better inference. + +```ts no-transpile +import { configureStore, MiddlewareArray } from '@reduxjs/toolkit' + +configureStore({ + reducer: rootReducer, + middleware: new MiddlewareArray(additionalMiddleware, logger), +}) +``` + +Javascript-only users are free to use a plain array if preferred. + +::: + ### `devTools` If this is a boolean, it will be used to indicate whether `configureStore` should automatically enable support for [the Redux DevTools browser extension](https://github.com/reduxjs/redux-devtools). @@ -122,7 +138,7 @@ If defined as an array, these will be passed to [the Redux `compose` function](h This should _not_ include `applyMiddleware()` or the Redux DevTools Extension `composeWithDevTools`, as those are already handled by `configureStore`. -Example: `enhancers: [offline]` will result in a final setup of `[applyMiddleware, offline, devToolsExtension]`. +Example: `enhancers: new EnhancerArray(offline)` will result in a final setup of `[applyMiddleware, offline, devToolsExtension]`. If defined as a callback function, it will be called with the existing array of enhancers _without_ the DevTools Extension (currently `[applyMiddleware]`), and should return a new array of enhancers. This is primarily useful for cases where a store enhancer needs to be added @@ -131,6 +147,22 @@ in front of `applyMiddleware`, such as `redux-first-router` or `redux-offline`. Example: `enhancers: (defaultEnhancers) => defaultEnhancers.prepend(offline)` will result in a final setup of `[offline, applyMiddleware, devToolsExtension]`. +:::note EnhancerArray +Typescript users are required to use a `EnhancerArray` instance (if not using a `getDefaultEnhancer` result, which is already a `EnhancerArray`), for better inference. + +```ts no-transpile +import { configureStore, EnhancerArray } from '@reduxjs/toolkit' + +configureStore({ + reducer: rootReducer, + enhancers: new EnhancerArray(offline), +}) +``` + +Javascript-only users are free to use a plain array if preferred. + +::: + ## Usage ### Basic Example diff --git a/docs/api/getDefaultMiddleware.mdx b/docs/api/getDefaultMiddleware.mdx index 370b51ef75..691dae6b20 100644 --- a/docs/api/getDefaultMiddleware.mdx +++ b/docs/api/getDefaultMiddleware.mdx @@ -28,7 +28,7 @@ If you want to customize the list of middleware, you can supply an array of midd ```js const store = configureStore({ reducer: rootReducer, - middleware: [thunk, logger], + middleware: new MiddlewareArray(thunk, logger), }) // Store specifically has the thunk and logger middleware applied diff --git a/docs/api/immutabilityMiddleware.mdx b/docs/api/immutabilityMiddleware.mdx index 73b03d972c..c6e42a1de0 100644 --- a/docs/api/immutabilityMiddleware.mdx +++ b/docs/api/immutabilityMiddleware.mdx @@ -74,6 +74,7 @@ export default exampleSlice.reducer import { configureStore, createImmutableStateInvariantMiddleware, + MiddlewareArray, } from '@reduxjs/toolkit' import exampleSliceReducer from './exampleSlice' @@ -85,7 +86,7 @@ const immutableInvariantMiddleware = createImmutableStateInvariantMiddleware({ const store = configureStore({ reducer: exampleSliceReducer, // Note that this will replace all default middleware - middleware: [immutableInvariantMiddleware], + middleware: new MiddlewareArray(immutableInvariantMiddleware), }) ``` diff --git a/docs/api/serializabilityMiddleware.mdx b/docs/api/serializabilityMiddleware.mdx index 0aa074a65c..f0b60359bf 100644 --- a/docs/api/serializabilityMiddleware.mdx +++ b/docs/api/serializabilityMiddleware.mdx @@ -93,6 +93,7 @@ import { configureStore, createSerializableStateInvariantMiddleware, isPlain, + MiddlewareArray, } from '@reduxjs/toolkit' import reducer from './reducer' @@ -110,7 +111,7 @@ const serializableMiddleware = createSerializableStateInvariantMiddleware({ const store = configureStore({ reducer, - middleware: [serializableMiddleware], + middleware: new MiddlewareArray(serializableMiddleware), }) ``` diff --git a/docs/usage/usage-with-typescript.md b/docs/usage/usage-with-typescript.md index d4d1a6348d..1f1d69db30 100644 --- a/docs/usage/usage-with-typescript.md +++ b/docs/usage/usage-with-typescript.md @@ -136,23 +136,16 @@ export default store #### Using `MiddlewareArray` without `getDefaultMiddleware` -If you want to skip the usage of `getDefaultMiddleware` altogether, you can still use `MiddlewareArray` for type-safe concatenation of your `middleware` array. This class extends the default JavaScript `Array` type, only with modified typings for `.concat(...)` and the additional `.prepend(...)` method. +If you want to skip the usage of `getDefaultMiddleware` altogether, you are requred to use `MiddlewareArray` for type-safe creation of your `middleware` array. This class extends the default JavaScript `Array` type, only with modified typings for `.concat(...)` and the additional `.prepend(...)` method. -This is generally not required though, as you will probably not run into any array-type-widening issues as long as you are using `as const` and do not use the spread operator. - -So the following two calls would be equivalent: +For example: ```ts import { configureStore, MiddlewareArray } from '@reduxjs/toolkit' configureStore({ reducer: rootReducer, - middleware: new MiddlewareArray().concat(additionalMiddleware, logger), -}) - -configureStore({ - reducer: rootReducer, - middleware: [additionalMiddleware, logger] as const, + middleware: new MiddlewareArray(additionalMiddleware, logger), }) ``` From 2190355ad0669e387d0d772f9f320d1005232049 Mon Sep 17 00:00:00 2001 From: Ben Durrant Date: Sun, 21 May 2023 21:56:55 +0100 Subject: [PATCH 230/412] Create Tuple class to replace MiddlewareArray and EnhancerArray --- docs/api/actionCreatorMiddleware.mdx | 4 +- docs/api/configureStore.mdx | 18 ++-- docs/api/getDefaultMiddleware.mdx | 4 +- docs/api/immutabilityMiddleware.mdx | 4 +- docs/api/serializabilityMiddleware.mdx | 4 +- docs/usage/usage-with-typescript.md | 10 +- packages/toolkit/src/configureStore.ts | 20 ++-- packages/toolkit/src/getDefaultEnhancers.ts | 6 +- packages/toolkit/src/getDefaultMiddleware.ts | 6 +- packages/toolkit/src/index.ts | 2 +- packages/toolkit/src/query/tests/helpers.tsx | 7 +- packages/toolkit/src/tests/Tuple.typetest.ts | 81 +++++++++++++++ .../toolkit/src/tests/configureStore.test.ts | 14 +-- .../src/tests/configureStore.typetest.ts | 31 +++--- ...est.ts => getDefaultEnhancers.typetest.ts} | 0 .../src/tests/getDefaultMiddleware.test.ts | 20 ++-- ...st.ts => getDefaultMiddleware.typetest.ts} | 0 ...rializableStateInvariantMiddleware.test.ts | 38 ++++--- packages/toolkit/src/tsHelpers.ts | 12 +-- packages/toolkit/src/utils.ts | 98 +++++-------------- 20 files changed, 200 insertions(+), 179 deletions(-) create mode 100644 packages/toolkit/src/tests/Tuple.typetest.ts rename packages/toolkit/src/tests/{EnhancerArray.typetest.ts => getDefaultEnhancers.typetest.ts} (100%) rename packages/toolkit/src/tests/{MiddlewareArray.typetest.ts => getDefaultMiddleware.typetest.ts} (100%) diff --git a/docs/api/actionCreatorMiddleware.mdx b/docs/api/actionCreatorMiddleware.mdx index 54d3d055dd..0afce4f368 100644 --- a/docs/api/actionCreatorMiddleware.mdx +++ b/docs/api/actionCreatorMiddleware.mdx @@ -47,7 +47,7 @@ export default function (state = {}, action: any) { import { configureStore, createActionCreatorInvariantMiddleware, - MiddlewareArray, + Tuple, } from '@reduxjs/toolkit' import reducer from './reducer' @@ -63,6 +63,6 @@ const actionCreatorMiddleware = createActionCreatorInvariantMiddleware({ const store = configureStore({ reducer, - middleware: new MiddlewareArray(actionCreatorMiddleware), + middleware: new Tuple(actionCreatorMiddleware), }) ``` diff --git a/docs/api/configureStore.mdx b/docs/api/configureStore.mdx index aa5ee8b8f8..4d5f9afab3 100644 --- a/docs/api/configureStore.mdx +++ b/docs/api/configureStore.mdx @@ -94,15 +94,15 @@ and should return a middleware array. For more details on how the `middleware` parameter works and the list of middleware that are added by default, see the [`getDefaultMiddleware` docs page](./getDefaultMiddleware.mdx). -:::note MiddlewareArray -Typescript users are required to use a `MiddlewareArray` instance (if not using a `getDefaultMiddleware` result, which is already a `MiddlewareArray`), for better inference. +:::note Tuple +Typescript users are required to use a `Tuple` instance (if not using a `getDefaultMiddleware` result, which is already a `Tuple`), for better inference. ```ts no-transpile -import { configureStore, MiddlewareArray } from '@reduxjs/toolkit' +import { configureStore, Tuple } from '@reduxjs/toolkit' configureStore({ reducer: rootReducer, - middleware: new MiddlewareArray(additionalMiddleware, logger), + middleware: new Tuple(additionalMiddleware, logger), }) ``` @@ -138,7 +138,7 @@ If defined as an array, these will be passed to [the Redux `compose` function](h This should _not_ include `applyMiddleware()` or the Redux DevTools Extension `composeWithDevTools`, as those are already handled by `configureStore`. -Example: `enhancers: new EnhancerArray(offline)` will result in a final setup of `[applyMiddleware, offline, devToolsExtension]`. +Example: `enhancers: new Tuple(offline)` will result in a final setup of `[applyMiddleware, offline, devToolsExtension]`. If defined as a callback function, it will be called with the existing array of enhancers _without_ the DevTools Extension (currently `[applyMiddleware]`), and should return a new array of enhancers. This is primarily useful for cases where a store enhancer needs to be added @@ -147,15 +147,15 @@ in front of `applyMiddleware`, such as `redux-first-router` or `redux-offline`. Example: `enhancers: (defaultEnhancers) => defaultEnhancers.prepend(offline)` will result in a final setup of `[offline, applyMiddleware, devToolsExtension]`. -:::note EnhancerArray -Typescript users are required to use a `EnhancerArray` instance (if not using a `getDefaultEnhancer` result, which is already a `EnhancerArray`), for better inference. +:::note Tuple +Typescript users are required to use a `Tuple` instance (if not using a `getDefaultEnhancer` result, which is already a `Tuple`), for better inference. ```ts no-transpile -import { configureStore, EnhancerArray } from '@reduxjs/toolkit' +import { configureStore, Tuple } from '@reduxjs/toolkit' configureStore({ reducer: rootReducer, - enhancers: new EnhancerArray(offline), + enhancers: new Tuple(offline), }) ``` diff --git a/docs/api/getDefaultMiddleware.mdx b/docs/api/getDefaultMiddleware.mdx index 691dae6b20..0af30a10e7 100644 --- a/docs/api/getDefaultMiddleware.mdx +++ b/docs/api/getDefaultMiddleware.mdx @@ -28,7 +28,7 @@ If you want to customize the list of middleware, you can supply an array of midd ```js const store = configureStore({ reducer: rootReducer, - middleware: new MiddlewareArray(thunk, logger), + middleware: new Tuple(thunk, logger), }) // Store specifically has the thunk and logger middleware applied @@ -55,7 +55,7 @@ const store = configureStore({ // Store has all of the default middleware added, _plus_ the logger middleware ``` -It is preferable to use the chainable `.concat(...)` and `.prepend(...)` methods of the returned `MiddlewareArray` instead of the array spread operator, as the latter can lose valuable type information under some circumstances. +It is preferable to use the chainable `.concat(...)` and `.prepend(...)` methods of the returned `Tuple` instead of the array spread operator, as the latter can lose valuable type information under some circumstances. ## Included Default Middleware diff --git a/docs/api/immutabilityMiddleware.mdx b/docs/api/immutabilityMiddleware.mdx index c6e42a1de0..16950b129e 100644 --- a/docs/api/immutabilityMiddleware.mdx +++ b/docs/api/immutabilityMiddleware.mdx @@ -74,7 +74,7 @@ export default exampleSlice.reducer import { configureStore, createImmutableStateInvariantMiddleware, - MiddlewareArray, + Tuple, } from '@reduxjs/toolkit' import exampleSliceReducer from './exampleSlice' @@ -86,7 +86,7 @@ const immutableInvariantMiddleware = createImmutableStateInvariantMiddleware({ const store = configureStore({ reducer: exampleSliceReducer, // Note that this will replace all default middleware - middleware: new MiddlewareArray(immutableInvariantMiddleware), + middleware: new Tuple(immutableInvariantMiddleware), }) ``` diff --git a/docs/api/serializabilityMiddleware.mdx b/docs/api/serializabilityMiddleware.mdx index f0b60359bf..e8cb8e8363 100644 --- a/docs/api/serializabilityMiddleware.mdx +++ b/docs/api/serializabilityMiddleware.mdx @@ -93,7 +93,7 @@ import { configureStore, createSerializableStateInvariantMiddleware, isPlain, - MiddlewareArray, + Tuple, } from '@reduxjs/toolkit' import reducer from './reducer' @@ -111,7 +111,7 @@ const serializableMiddleware = createSerializableStateInvariantMiddleware({ const store = configureStore({ reducer, - middleware: new MiddlewareArray(serializableMiddleware), + middleware: new Tuple(serializableMiddleware), }) ``` diff --git a/docs/usage/usage-with-typescript.md b/docs/usage/usage-with-typescript.md index 1f1d69db30..5258ede400 100644 --- a/docs/usage/usage-with-typescript.md +++ b/docs/usage/usage-with-typescript.md @@ -99,7 +99,7 @@ export default store The type of the `dispatch` function type will be directly inferred from the `middleware` option. So if you add _correctly typed_ middlewares, `dispatch` should already be correctly typed. -As TypeScript often widens array types when combining arrays using the spread operator, we suggest using the `.concat(...)` and `.prepend(...)` methods of the `MiddlewareArray` returned by `getDefaultMiddleware()`. +As TypeScript often widens array types when combining arrays using the spread operator, we suggest using the `.concat(...)` and `.prepend(...)` methods of the `Tuple` returned by `getDefaultMiddleware()`. ```ts import { configureStore } from '@reduxjs/toolkit' @@ -134,18 +134,18 @@ export type AppDispatch = typeof store.dispatch export default store ``` -#### Using `MiddlewareArray` without `getDefaultMiddleware` +#### Using `Tuple` without `getDefaultMiddleware` -If you want to skip the usage of `getDefaultMiddleware` altogether, you are requred to use `MiddlewareArray` for type-safe creation of your `middleware` array. This class extends the default JavaScript `Array` type, only with modified typings for `.concat(...)` and the additional `.prepend(...)` method. +If you want to skip the usage of `getDefaultMiddleware` altogether, you are requred to use `Tuple` for type-safe creation of your `middleware` array. This class extends the default JavaScript `Array` type, only with modified typings for `.concat(...)` and the additional `.prepend(...)` method. For example: ```ts -import { configureStore, MiddlewareArray } from '@reduxjs/toolkit' +import { configureStore, Tuple } from '@reduxjs/toolkit' configureStore({ reducer: rootReducer, - middleware: new MiddlewareArray(additionalMiddleware, logger), + middleware: new Tuple(additionalMiddleware, logger), }) ``` diff --git a/packages/toolkit/src/configureStore.ts b/packages/toolkit/src/configureStore.ts index 39f2aaaefa..44a06894f2 100644 --- a/packages/toolkit/src/configureStore.ts +++ b/packages/toolkit/src/configureStore.ts @@ -23,7 +23,7 @@ import type { ExtractStoreExtensions, ExtractStateExtensions, } from './tsHelpers' -import type { EnhancerArray, MiddlewareArray } from './utils' +import type { Tuple } from './utils' import type { GetDefaultEnhancers } from './getDefaultEnhancers' import { buildGetDefaultEnhancers } from './getDefaultEnhancers' @@ -37,8 +37,8 @@ const IS_PRODUCTION = process.env.NODE_ENV === 'production' export interface ConfigureStoreOptions< S = any, A extends Action = AnyAction, - M extends MiddlewareArray> = MiddlewareArray>, - E extends EnhancerArray = EnhancerArray, + M extends Tuple> = Tuple>, + E extends Tuple = Tuple, P = S > { /** @@ -48,8 +48,8 @@ export interface ConfigureStoreOptions< reducer: Reducer | ReducersMapObject /** - * An array of Redux middleware to install. If not supplied, defaults to - * the set of middleware returned by `getDefaultMiddleware()`. + * An array of Redux middleware to install, or a callback receiving `getDefaultMiddleware` and returning a Tuple of middleware. + * If not supplied, defaults to the set of middleware returned by `getDefaultMiddleware()`. * * @example `middleware: (gDM) => gDM().concat(logger, apiMiddleware, yourCustomMiddleware)` * @see https://redux-toolkit.js.org/api/getDefaultMiddleware#intended-usage @@ -78,8 +78,8 @@ export interface ConfigureStoreOptions< * The store enhancers to apply. See Redux's `createStore()`. * All enhancers will be included before the DevTools Extension enhancer. * If you need to customize the order of enhancers, supply a callback - * function that will receive a `getDefaultEnhancers` function that returns an EnhancerArray, - * and should return a new array (such as `getDefaultEnhancers().concat(offline)`). + * function that will receive a `getDefaultEnhancers` function that returns a Tuple, + * and should return a Tuple of enhancers (such as `getDefaultEnhancers().concat(offline)`). * If you only need to add middleware, you can use the `middleware` parameter instead. */ enhancers?: ((getDefaultEnhancers: GetDefaultEnhancers) => E) | E @@ -112,10 +112,8 @@ export type EnhancedStore< export function configureStore< S = any, A extends Action = AnyAction, - M extends MiddlewareArray> = MiddlewareArray< - [ThunkMiddlewareFor] - >, - E extends EnhancerArray = EnhancerArray< + M extends Tuple> = Tuple<[ThunkMiddlewareFor]>, + E extends Tuple = Tuple< [StoreEnhancer<{ dispatch: ExtractDispatchExtensions }>, StoreEnhancer] >, P = S diff --git a/packages/toolkit/src/getDefaultEnhancers.ts b/packages/toolkit/src/getDefaultEnhancers.ts index faf27654aa..6d13646482 100644 --- a/packages/toolkit/src/getDefaultEnhancers.ts +++ b/packages/toolkit/src/getDefaultEnhancers.ts @@ -1,7 +1,7 @@ import type { StoreEnhancer } from 'redux' import type { AutoBatchOptions } from './autoBatchEnhancer' import { autoBatchEnhancer } from './autoBatchEnhancer' -import { EnhancerArray } from './utils' +import { Tuple } from './utils' import type { Middlewares } from './configureStore' import type { ExtractDispatchExtensions } from './tsHelpers' @@ -11,7 +11,7 @@ type GetDefaultEnhancersOptions = { export type GetDefaultEnhancers> = ( options?: GetDefaultEnhancersOptions -) => EnhancerArray<[StoreEnhancer<{ dispatch: ExtractDispatchExtensions }>]> +) => Tuple<[StoreEnhancer<{ dispatch: ExtractDispatchExtensions }>]> export const buildGetDefaultEnhancers = >( middlewareEnhancer: StoreEnhancer<{ dispatch: ExtractDispatchExtensions }> @@ -19,7 +19,7 @@ export const buildGetDefaultEnhancers = >( function getDefaultEnhancers(options) { const { autoBatch = true } = options ?? {} - let enhancerArray = new EnhancerArray(middlewareEnhancer) + let enhancerArray = new Tuple(middlewareEnhancer) if (autoBatch) { enhancerArray.push( autoBatchEnhancer(typeof autoBatch === 'object' ? autoBatch : undefined) diff --git a/packages/toolkit/src/getDefaultMiddleware.ts b/packages/toolkit/src/getDefaultMiddleware.ts index 12ce3ad9e8..acfd94d840 100644 --- a/packages/toolkit/src/getDefaultMiddleware.ts +++ b/packages/toolkit/src/getDefaultMiddleware.ts @@ -11,7 +11,7 @@ import { createImmutableStateInvariantMiddleware } from './immutableStateInvaria import type { SerializableStateInvariantMiddlewareOptions } from './serializableStateInvariantMiddleware' import { createSerializableStateInvariantMiddleware } from './serializableStateInvariantMiddleware' import type { ExcludeFromTuple } from './tsHelpers' -import { MiddlewareArray } from './utils' +import { Tuple } from './utils' function isBoolean(x: any): x is boolean { return typeof x === 'boolean' @@ -48,7 +48,7 @@ export type GetDefaultMiddleware = < } >( options?: O -) => MiddlewareArray], never>> +) => Tuple], never>> export const buildGetDefaultMiddleware = (): GetDefaultMiddleware => function getDefaultMiddleware(options) { @@ -59,7 +59,7 @@ export const buildGetDefaultMiddleware = (): GetDefaultMiddleware => actionCreatorCheck = true, } = options ?? {} - let middlewareArray = new MiddlewareArray() + let middlewareArray = new Tuple() if (thunk) { if (isBoolean(thunk)) { diff --git a/packages/toolkit/src/index.ts b/packages/toolkit/src/index.ts index 6630c94687..f30024161f 100644 --- a/packages/toolkit/src/index.ts +++ b/packages/toolkit/src/index.ts @@ -101,7 +101,7 @@ export type { // types ActionReducerMapBuilder, } from './mapBuilders' -export { MiddlewareArray, EnhancerArray } from './utils' +export { Tuple } from './utils' export { createEntityAdapter } from './entities/create_adapter' export type { diff --git a/packages/toolkit/src/query/tests/helpers.tsx b/packages/toolkit/src/query/tests/helpers.tsx index 94f56b0e0c..d566b4c0fc 100644 --- a/packages/toolkit/src/query/tests/helpers.tsx +++ b/packages/toolkit/src/query/tests/helpers.tsx @@ -5,9 +5,6 @@ import type { Middleware, Store, Reducer, - EnhancerArray, - StoreEnhancer, - ThunkDispatch, } from '@reduxjs/toolkit' import { configureStore } from '@reduxjs/toolkit' import { setupListeners } from '@reduxjs/toolkit/query' @@ -218,8 +215,8 @@ export function setupApiStore< }).concat(api.middleware) return tempMiddleware - .concat(...(middleware?.concat ?? [])) - .prepend(...(middleware?.prepend ?? [])) as typeof tempMiddleware + .concat(middleware?.concat ?? []) + .prepend(middleware?.prepend ?? []) as typeof tempMiddleware }, enhancers: (gde) => gde({ diff --git a/packages/toolkit/src/tests/Tuple.typetest.ts b/packages/toolkit/src/tests/Tuple.typetest.ts new file mode 100644 index 0000000000..963164862a --- /dev/null +++ b/packages/toolkit/src/tests/Tuple.typetest.ts @@ -0,0 +1,81 @@ +import { Tuple } from '@reduxjs/toolkit' +import { expectType } from './helpers' + +/** + * Test: compatibility is checked between described types + */ +{ + const stringTuple = new Tuple('') + + expectType>(stringTuple) + + expectType>(stringTuple) + + // @ts-expect-error + expectType>(stringTuple) + + const numberTuple = new Tuple(0) + // @ts-expect-error + expectType>(numberTuple) +} + +/** + * Test: concat is inferred properly + */ +{ + const singleString = new Tuple('') + + expectType>(singleString) + + expectType>(singleString.concat('')) + + expectType>(singleString.concat([''])) +} + +/** + * Test: prepend is inferred properly + */ +{ + const singleString = new Tuple('') + + expectType>(singleString) + + expectType>(singleString.prepend('')) + + expectType>(singleString.prepend([''])) +} + +/** + * Test: push must match existing items + */ +{ + const stringTuple = new Tuple('') + + stringTuple.push('') + + // @ts-expect-error + stringTuple.push(0) +} + +/** + * Test: Tuples can be combined + */ +{ + const stringTuple = new Tuple('') + + const numberTuple = new Tuple(0) + + expectType>(stringTuple.concat(numberTuple)) + + expectType>(stringTuple.prepend(numberTuple)) + + expectType>(numberTuple.concat(stringTuple)) + + expectType>(numberTuple.prepend(stringTuple)) + + // @ts-expect-error + expectType>(stringTuple.prepend(numberTuple)) + + // @ts-expect-error + expectType>(stringTuple.concat(numberTuple)) +} diff --git a/packages/toolkit/src/tests/configureStore.test.ts b/packages/toolkit/src/tests/configureStore.test.ts index 1ade822fc1..1c3fbd3766 100644 --- a/packages/toolkit/src/tests/configureStore.test.ts +++ b/packages/toolkit/src/tests/configureStore.test.ts @@ -1,6 +1,6 @@ import { vi } from 'vitest' import type { StoreEnhancer } from '@reduxjs/toolkit' -import { MiddlewareArray, EnhancerArray } from '@reduxjs/toolkit' +import { Tuple } from '@reduxjs/toolkit' import type * as Redux from 'redux' import type * as DevTools from '@internal/devtoolsExtension' @@ -110,7 +110,7 @@ describe('configureStore', async () => { describe('given no middleware', () => { it('calls createStore without any middleware', () => { expect( - configureStore({ middleware: new MiddlewareArray(), reducer }) + configureStore({ middleware: new Tuple(), reducer }) ).toBeInstanceOf(Object) expect(redux.applyMiddleware).toHaveBeenCalledWith() expect(mockDevtoolsCompose).toHaveBeenCalled() // @remap-prod-remove-line-line @@ -175,7 +175,7 @@ describe('configureStore', async () => { const thank: Redux.Middleware = (_store) => (next) => (action) => next(action) expect( - configureStore({ middleware: new MiddlewareArray(thank), reducer }) + configureStore({ middleware: new Tuple(thank), reducer }) ).toBeInstanceOf(Object) expect(redux.applyMiddleware).toHaveBeenCalledWith(thank) expect(mockDevtoolsCompose).toHaveBeenCalled() // @remap-prod-remove-line-line @@ -197,7 +197,7 @@ describe('configureStore', async () => { expect(getDefaultMiddleware).toEqual(expect.any(Function)) expect(getDefaultMiddleware()).toEqual(expect.any(Array)) - return new MiddlewareArray(thank) + return new Tuple(thank) }) const store = configureStore({ middleware: builder, reducer }) @@ -306,7 +306,7 @@ describe('configureStore', async () => { it('warns if middleware enhancer is excluded from final array when middlewares are provided', () => { const store = configureStore({ reducer, - enhancers: new EnhancerArray(dummyEnhancer), + enhancers: new Tuple(dummyEnhancer), }) expect(dummyEnhancerCalled).toBe(true) @@ -318,8 +318,8 @@ describe('configureStore', async () => { it("doesn't warn when middleware enhancer is excluded if no middlewares provided", () => { const store = configureStore({ reducer, - middleware: new MiddlewareArray(), - enhancers: new EnhancerArray(dummyEnhancer), + middleware: new Tuple(), + enhancers: new Tuple(dummyEnhancer), }) expect(dummyEnhancerCalled).toBe(true) diff --git a/packages/toolkit/src/tests/configureStore.typetest.ts b/packages/toolkit/src/tests/configureStore.typetest.ts index d4d02681e4..5ee4cbec0c 100644 --- a/packages/toolkit/src/tests/configureStore.typetest.ts +++ b/packages/toolkit/src/tests/configureStore.typetest.ts @@ -10,12 +10,7 @@ import type { } from 'redux' import { applyMiddleware, combineReducers } from 'redux' import type { PayloadAction, ConfigureStoreOptions } from '@reduxjs/toolkit' -import { - configureStore, - createSlice, - MiddlewareArray, - EnhancerArray, -} from '@reduxjs/toolkit' +import { configureStore, createSlice, Tuple } from '@reduxjs/toolkit' import type { ThunkMiddleware, ThunkAction, ThunkDispatch } from 'redux-thunk' import { thunk } from 'redux-thunk' import { expectNotAny, expectType } from './helpers' @@ -72,14 +67,14 @@ const _anyMiddleware: any = () => () => () => {} } /* - * Test: configureStore() accepts MiddlewareArray, but not plain array. + * Test: configureStore() accepts Tuple, but not plain array. */ { const middleware: Middleware = (store) => (next) => next configureStore({ reducer: () => 0, - middleware: new MiddlewareArray(middleware), + middleware: new Tuple(middleware), }) configureStore({ @@ -91,7 +86,7 @@ const _anyMiddleware: any = () => () => () => {} configureStore({ reducer: () => 0, // @ts-expect-error - middleware: new MiddlewareArray('not middleware'), + middleware: new Tuple('not middleware'), }) } @@ -144,7 +139,7 @@ const _anyMiddleware: any = () => () => () => {} } /* - * Test: configureStore() accepts store EnhancerArray, but not plain array + * Test: configureStore() accepts store Tuple, but not plain array */ { { @@ -152,7 +147,7 @@ const _anyMiddleware: any = () => () => () => {} const store = configureStore({ reducer: () => 0, - enhancers: new EnhancerArray(enhancer), + enhancers: new Tuple(enhancer), }) const store2 = configureStore({ @@ -169,7 +164,7 @@ const _anyMiddleware: any = () => () => () => {} configureStore({ reducer: () => 0, // @ts-expect-error - enhancers: new EnhancerArray('not a store enhancer'), + enhancers: new Tuple('not a store enhancer'), }) { @@ -197,7 +192,7 @@ const _anyMiddleware: any = () => () => () => {} const store = configureStore({ reducer: () => 0, - enhancers: new EnhancerArray( + enhancers: new Tuple( somePropertyStoreEnhancer, anotherPropertyStoreEnhancer ), @@ -259,7 +254,7 @@ const _anyMiddleware: any = () => () => () => {} const store = configureStore({ reducer: () => ({ aProperty: 0 }), - enhancers: new EnhancerArray( + enhancers: new Tuple( someStateExtendingEnhancer, anotherStateExtendingEnhancer ), @@ -530,7 +525,7 @@ const _anyMiddleware: any = () => () => () => {} { const store = configureStore({ reducer: reducerA, - middleware: new MiddlewareArray(), + middleware: new Tuple(), }) // @ts-expect-error store.dispatch(thunkA()) @@ -543,7 +538,7 @@ const _anyMiddleware: any = () => () => () => {} { const store = configureStore({ reducer: reducerA, - middleware: new MiddlewareArray(thunk as ThunkMiddleware), + middleware: new Tuple(thunk as ThunkMiddleware), }) store.dispatch(thunkA()) // @ts-expect-error @@ -555,7 +550,7 @@ const _anyMiddleware: any = () => () => () => {} { const store = configureStore({ reducer: reducerA, - middleware: new MiddlewareArray( + middleware: new Tuple( 0 as unknown as Middleware<(a: StateA) => boolean, StateA> ), }) @@ -567,7 +562,7 @@ const _anyMiddleware: any = () => () => () => {} * Test: multiple custom middleware */ { - const middleware = [] as any as MiddlewareArray< + const middleware = [] as any as Tuple< [ Middleware<(a: 'a') => 'A', StateA>, Middleware<(b: 'b') => 'B', StateA>, diff --git a/packages/toolkit/src/tests/EnhancerArray.typetest.ts b/packages/toolkit/src/tests/getDefaultEnhancers.typetest.ts similarity index 100% rename from packages/toolkit/src/tests/EnhancerArray.typetest.ts rename to packages/toolkit/src/tests/getDefaultEnhancers.typetest.ts diff --git a/packages/toolkit/src/tests/getDefaultMiddleware.test.ts b/packages/toolkit/src/tests/getDefaultMiddleware.test.ts index 7c27357ece..74dcbf5874 100644 --- a/packages/toolkit/src/tests/getDefaultMiddleware.test.ts +++ b/packages/toolkit/src/tests/getDefaultMiddleware.test.ts @@ -14,7 +14,7 @@ import type { ThunkMiddleware } from 'redux-thunk' import { expectType } from './helpers' import { buildGetDefaultMiddleware } from '@internal/getDefaultMiddleware' -import { MiddlewareArray } from '@internal/utils' +import { Tuple } from '@internal/utils' const getDefaultMiddleware = buildGetDefaultMiddleware() @@ -80,7 +80,7 @@ describe('getDefaultMiddleware', () => { thunk: false, }) - expectType>(m2) + expectType>(m2) const dummyMiddleware: Middleware< { @@ -114,7 +114,7 @@ describe('getDefaultMiddleware', () => { const m3 = middleware.concat(dummyMiddleware, dummyMiddleware2) expectType< - MiddlewareArray< + Tuple< [ ThunkMiddleware, Middleware< @@ -220,7 +220,7 @@ it('allows passing options to actionCreatorCheck', () => { expect(actionCreatorCheckWasCalled).toBe(true) }) -describe('MiddlewareArray functionality', () => { +describe('Tuple functionality', () => { const middleware1: Middleware = () => (next) => (action) => next(action) const middleware2: Middleware = () => (next) => (action) => next(action) const defaultMiddleware = getDefaultMiddleware() @@ -232,7 +232,7 @@ describe('MiddlewareArray functionality', () => { // value is prepended expect(prepended).toEqual([middleware1, ...defaultMiddleware]) // returned value is of correct type - expect(prepended).toBeInstanceOf(MiddlewareArray) + expect(prepended).toBeInstanceOf(Tuple) // prepended is a new array expect(prepended).not.toEqual(defaultMiddleware) // defaultMiddleware is not modified @@ -245,7 +245,7 @@ describe('MiddlewareArray functionality', () => { // value is prepended expect(prepended).toEqual([middleware1, middleware2, ...defaultMiddleware]) // returned value is of correct type - expect(prepended).toBeInstanceOf(MiddlewareArray) + expect(prepended).toBeInstanceOf(Tuple) // prepended is a new array expect(prepended).not.toEqual(defaultMiddleware) // defaultMiddleware is not modified @@ -258,7 +258,7 @@ describe('MiddlewareArray functionality', () => { // value is prepended expect(prepended).toEqual([middleware1, middleware2, ...defaultMiddleware]) // returned value is of correct type - expect(prepended).toBeInstanceOf(MiddlewareArray) + expect(prepended).toBeInstanceOf(Tuple) // prepended is a new array expect(prepended).not.toEqual(defaultMiddleware) // defaultMiddleware is not modified @@ -271,7 +271,7 @@ describe('MiddlewareArray functionality', () => { // value is concatenated expect(concatenated).toEqual([...defaultMiddleware, middleware1]) // returned value is of correct type - expect(concatenated).toBeInstanceOf(MiddlewareArray) + expect(concatenated).toBeInstanceOf(Tuple) // concatenated is a new array expect(concatenated).not.toEqual(defaultMiddleware) // defaultMiddleware is not modified @@ -288,7 +288,7 @@ describe('MiddlewareArray functionality', () => { middleware2, ]) // returned value is of correct type - expect(concatenated).toBeInstanceOf(MiddlewareArray) + expect(concatenated).toBeInstanceOf(Tuple) // concatenated is a new array expect(concatenated).not.toEqual(defaultMiddleware) // defaultMiddleware is not modified @@ -305,7 +305,7 @@ describe('MiddlewareArray functionality', () => { middleware2, ]) // returned value is of correct type - expect(concatenated).toBeInstanceOf(MiddlewareArray) + expect(concatenated).toBeInstanceOf(Tuple) // concatenated is a new array expect(concatenated).not.toEqual(defaultMiddleware) // defaultMiddleware is not modified diff --git a/packages/toolkit/src/tests/MiddlewareArray.typetest.ts b/packages/toolkit/src/tests/getDefaultMiddleware.typetest.ts similarity index 100% rename from packages/toolkit/src/tests/MiddlewareArray.typetest.ts rename to packages/toolkit/src/tests/getDefaultMiddleware.typetest.ts diff --git a/packages/toolkit/src/tests/serializableStateInvariantMiddleware.test.ts b/packages/toolkit/src/tests/serializableStateInvariantMiddleware.test.ts index 1d8361b3d1..24c00cacd9 100644 --- a/packages/toolkit/src/tests/serializableStateInvariantMiddleware.test.ts +++ b/packages/toolkit/src/tests/serializableStateInvariantMiddleware.test.ts @@ -10,7 +10,7 @@ import { createSerializableStateInvariantMiddleware, findNonSerializableValue, isPlain, - MiddlewareArray, + Tuple, } from '@reduxjs/toolkit' import { isNestedFrozen } from '@internal/serializableStateInvariantMiddleware' @@ -101,7 +101,7 @@ describe('serializableStateInvariantMiddleware', () => { const store = configureStore({ reducer, - middleware: new MiddlewareArray(serializableStateInvariantMiddleware), + middleware: new Tuple(serializableStateInvariantMiddleware), }) const symbol = Symbol.for('SOME_CONSTANT') @@ -148,7 +148,7 @@ describe('serializableStateInvariantMiddleware', () => { reducer: { testSlice: reducer, }, - middleware: new MiddlewareArray(serializableStateInvariantMiddleware), + middleware: new Tuple(serializableStateInvariantMiddleware), }) store.dispatch({ type: ACTION_TYPE }) @@ -208,7 +208,7 @@ describe('serializableStateInvariantMiddleware', () => { reducer: { testSlice: reducer, }, - middleware: new MiddlewareArray(serializableStateInvariantMiddleware), + middleware: new Tuple(serializableStateInvariantMiddleware), }) store.dispatch({ type: ACTION_TYPE }) @@ -255,7 +255,7 @@ describe('serializableStateInvariantMiddleware', () => { reducer: { testSlice: reducer, }, - middleware: new MiddlewareArray(serializableStateInvariantMiddleware), + middleware: new Tuple(serializableStateInvariantMiddleware), }) store.dispatch({ type: ACTION_TYPE }) @@ -299,7 +299,7 @@ describe('serializableStateInvariantMiddleware', () => { reducer: { testSlice: reducer, }, - middleware: new MiddlewareArray(serializableStateInvariantMiddleware), + middleware: new Tuple(serializableStateInvariantMiddleware), }) store.dispatch({ type: ACTION_TYPE }) @@ -323,7 +323,7 @@ describe('serializableStateInvariantMiddleware', () => { const store = configureStore({ reducer: () => ({}), - middleware: new MiddlewareArray(serializableStateMiddleware), + middleware: new Tuple(serializableStateMiddleware), }) expect(numTimesCalled).toBe(0) @@ -348,9 +348,7 @@ describe('serializableStateInvariantMiddleware', () => { it('default value: meta.arg', () => { configureStore({ reducer, - middleware: new MiddlewareArray( - createSerializableStateInvariantMiddleware() - ), + middleware: new Tuple(createSerializableStateInvariantMiddleware()), }).dispatch({ type: 'test', meta: { arg: nonSerializableValue } }) expect(getLog().log).toMatchInlineSnapshot(`""`) @@ -359,7 +357,7 @@ describe('serializableStateInvariantMiddleware', () => { it('default value can be overridden', () => { configureStore({ reducer, - middleware: new MiddlewareArray( + middleware: new Tuple( createSerializableStateInvariantMiddleware({ ignoredActionPaths: [], }) @@ -382,7 +380,7 @@ describe('serializableStateInvariantMiddleware', () => { it('can specify (multiple) different values', () => { configureStore({ reducer, - middleware: new MiddlewareArray( + middleware: new Tuple( createSerializableStateInvariantMiddleware({ ignoredActionPaths: ['payload', 'meta.arg'], }) @@ -399,7 +397,7 @@ describe('serializableStateInvariantMiddleware', () => { it('can specify regexp', () => { configureStore({ reducer, - middleware: new MiddlewareArray( + middleware: new Tuple( createSerializableStateInvariantMiddleware({ ignoredActionPaths: [/^payload\..*$/], }) @@ -427,7 +425,7 @@ describe('serializableStateInvariantMiddleware', () => { const store = configureStore({ reducer: () => ({}), - middleware: new MiddlewareArray(serializableStateMiddleware), + middleware: new Tuple(serializableStateMiddleware), }) expect(numTimesCalled).toBe(0) @@ -490,7 +488,7 @@ describe('serializableStateInvariantMiddleware', () => { reducer: { testSlice: reducer, }, - middleware: new MiddlewareArray(serializableStateInvariantMiddleware), + middleware: new Tuple(serializableStateInvariantMiddleware), }) store.dispatch({ type: ACTION_TYPE }) @@ -509,7 +507,7 @@ describe('serializableStateInvariantMiddleware', () => { const reducer = () => badValue const store = configureStore({ reducer, - middleware: new MiddlewareArray( + middleware: new Tuple( createSerializableStateInvariantMiddleware({ isSerializable: () => { numTimesCalled++ @@ -536,7 +534,7 @@ describe('serializableStateInvariantMiddleware', () => { const reducer = () => badValue const store = configureStore({ reducer, - middleware: new MiddlewareArray( + middleware: new Tuple( createSerializableStateInvariantMiddleware({ isSerializable: () => { numTimesCalled++ @@ -568,7 +566,7 @@ describe('serializableStateInvariantMiddleware', () => { reducer: { testSlice: reducer, }, - middleware: new MiddlewareArray(serializableStateInvariantMiddleware), + middleware: new Tuple(serializableStateInvariantMiddleware), }) store.dispatch({ @@ -594,7 +592,7 @@ describe('serializableStateInvariantMiddleware', () => { reducer: { testSlice: reducer, }, - middleware: new MiddlewareArray(serializableStateInvariantMiddleware), + middleware: new Tuple(serializableStateInvariantMiddleware), }) store.dispatch({ type: 'SOME_ACTION' }) @@ -618,7 +616,7 @@ describe('serializableStateInvariantMiddleware', () => { if (action.type === 'SET_STATE') return action.payload return state }, - middleware: new MiddlewareArray(serializableStateInvariantMiddleware), + middleware: new Tuple(serializableStateInvariantMiddleware), }) const state = createNextState([], () => diff --git a/packages/toolkit/src/tsHelpers.ts b/packages/toolkit/src/tsHelpers.ts index f4486decf5..d08ed5dda0 100644 --- a/packages/toolkit/src/tsHelpers.ts +++ b/packages/toolkit/src/tsHelpers.ts @@ -1,5 +1,5 @@ import type { Middleware, StoreEnhancer } from 'redux' -import type { EnhancerArray, MiddlewareArray } from './utils' +import type { Tuple } from './utils' export function safeAssign( target: T, @@ -100,7 +100,7 @@ type ExtractDispatchFromMiddlewareTuple< > : Acc -export type ExtractDispatchExtensions = M extends MiddlewareArray< +export type ExtractDispatchExtensions = M extends Tuple< infer MiddlewareTuple > ? ExtractDispatchFromMiddlewareTuple @@ -118,9 +118,7 @@ type ExtractStoreExtensionsFromEnhancerTuple< > : Acc -export type ExtractStoreExtensions = E extends EnhancerArray< - infer EnhancerTuple -> +export type ExtractStoreExtensions = E extends Tuple ? ExtractStoreExtensionsFromEnhancerTuple : E extends ReadonlyArray ? UnionToIntersection< @@ -145,9 +143,7 @@ type ExtractStateExtensionsFromEnhancerTuple< > : Acc -export type ExtractStateExtensions = E extends EnhancerArray< - infer EnhancerTuple -> +export type ExtractStateExtensions = E extends Tuple ? ExtractStateExtensionsFromEnhancerTuple : E extends ReadonlyArray ? UnionToIntersection< diff --git a/packages/toolkit/src/utils.ts b/packages/toolkit/src/utils.ts index efb6805b23..8a59616455 100644 --- a/packages/toolkit/src/utils.ts +++ b/packages/toolkit/src/utils.ts @@ -40,89 +40,45 @@ export function find( return undefined } -/** - * @public - */ -export class MiddlewareArray< - Middlewares extends readonly Middleware[] -> extends Array { - constructor(...items: Middlewares) - constructor(...args: any[]) { - super(...args) - Object.setPrototypeOf(this, MiddlewareArray.prototype) +export class Tuple> extends Array< + Items[number] +> { + constructor(...items: Items) { + super(...items) + Object.setPrototypeOf(this, Tuple.prototype) } static get [Symbol.species]() { - return MiddlewareArray as any + return Tuple as any } - concat>>( - items: AdditionalMiddlewares - ): MiddlewareArray<[...Middlewares, ...AdditionalMiddlewares]> - - concat>>( - ...items: AdditionalMiddlewares - ): MiddlewareArray<[...Middlewares, ...AdditionalMiddlewares]> + concat>( + items: Tuple + ): Tuple<[...Items, ...AdditionalItems]> + concat>( + items: AdditionalItems + ): Tuple<[...Items, ...AdditionalItems]> + concat>( + ...items: AdditionalItems + ): Tuple<[...Items, ...AdditionalItems]> concat(...arr: any[]) { return super.concat.apply(this, arr) } - prepend>>( - items: AdditionalMiddlewares - ): MiddlewareArray<[...AdditionalMiddlewares, ...Middlewares]> - - prepend>>( - ...items: AdditionalMiddlewares - ): MiddlewareArray<[...AdditionalMiddlewares, ...Middlewares]> - - prepend(...arr: any[]) { - if (arr.length === 1 && Array.isArray(arr[0])) { - return new MiddlewareArray(...arr[0].concat(this)) - } - return new MiddlewareArray(...arr.concat(this)) - } -} - -/** - * @public - */ -export class EnhancerArray< - Enhancers extends readonly StoreEnhancer[] -> extends Array { - constructor(...items: Enhancers) - constructor(...args: any[]) { - super(...args) - Object.setPrototypeOf(this, EnhancerArray.prototype) - } - - static get [Symbol.species]() { - return EnhancerArray as any - } - - concat>>( - items: AdditionalEnhancers - ): EnhancerArray<[...Enhancers, ...AdditionalEnhancers]> - - concat>>( - ...items: AdditionalEnhancers - ): EnhancerArray<[...Enhancers, ...AdditionalEnhancers]> - concat(...arr: any[]) { - return super.concat.apply(this, arr) - } - - prepend>>( - items: AdditionalEnhancers - ): EnhancerArray<[...AdditionalEnhancers, ...Enhancers]> - - prepend>>( - ...items: AdditionalEnhancers - ): EnhancerArray<[...AdditionalEnhancers, ...Enhancers]> - + prepend>( + items: Tuple + ): Tuple<[...AdditionalItems, ...Items]> + prepend>( + items: AdditionalItems + ): Tuple<[...AdditionalItems, ...Items]> + prepend>( + ...items: AdditionalItems + ): Tuple<[...AdditionalItems, ...Items]> prepend(...arr: any[]) { if (arr.length === 1 && Array.isArray(arr[0])) { - return new EnhancerArray(...arr[0].concat(this)) + return new Tuple(...arr[0].concat(this)) } - return new EnhancerArray(...arr.concat(this)) + return new Tuple(...arr.concat(this)) } } From 87e6ca0152e54c5b483c242bf360f94e24de7a40 Mon Sep 17 00:00:00 2001 From: "ben.durrant" Date: Mon, 22 May 2023 15:40:50 +0100 Subject: [PATCH 231/412] account for length edge case --- docs/usage/usage-with-typescript.md | 2 +- packages/toolkit/src/utils.ts | 6 ++++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/docs/usage/usage-with-typescript.md b/docs/usage/usage-with-typescript.md index 5258ede400..9fb7f9ab43 100644 --- a/docs/usage/usage-with-typescript.md +++ b/docs/usage/usage-with-typescript.md @@ -136,7 +136,7 @@ export default store #### Using `Tuple` without `getDefaultMiddleware` -If you want to skip the usage of `getDefaultMiddleware` altogether, you are requred to use `Tuple` for type-safe creation of your `middleware` array. This class extends the default JavaScript `Array` type, only with modified typings for `.concat(...)` and the additional `.prepend(...)` method. +If you want to skip the usage of `getDefaultMiddleware` altogether, you are required to use `Tuple` for type-safe creation of your `middleware` array. This class extends the default JavaScript `Array` type, only with modified typings for `.concat(...)` and the additional `.prepend(...)` method. For example: diff --git a/packages/toolkit/src/utils.ts b/packages/toolkit/src/utils.ts index 8a59616455..2e29b2bc9e 100644 --- a/packages/toolkit/src/utils.ts +++ b/packages/toolkit/src/utils.ts @@ -40,10 +40,12 @@ export function find( return undefined } -export class Tuple> extends Array< +export class Tuple = []> extends Array< Items[number] > { - constructor(...items: Items) { + constructor(length: number) + constructor(...items: Items) + constructor(...items: any[]) { super(...items) Object.setPrototypeOf(this, Tuple.prototype) } From 9fa849d3b2dddb5ba1ec6948cf77ad8f4d0c87c9 Mon Sep 17 00:00:00 2001 From: "ben.durrant" Date: Thu, 25 May 2023 10:41:32 +0100 Subject: [PATCH 232/412] account for length overload --- packages/toolkit/src/tests/Tuple.typetest.ts | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/packages/toolkit/src/tests/Tuple.typetest.ts b/packages/toolkit/src/tests/Tuple.typetest.ts index 963164862a..4beaf28fae 100644 --- a/packages/toolkit/src/tests/Tuple.typetest.ts +++ b/packages/toolkit/src/tests/Tuple.typetest.ts @@ -14,7 +14,7 @@ import { expectType } from './helpers' // @ts-expect-error expectType>(stringTuple) - const numberTuple = new Tuple(0) + const numberTuple = new Tuple(0, 1) // @ts-expect-error expectType>(numberTuple) } @@ -63,19 +63,19 @@ import { expectType } from './helpers' { const stringTuple = new Tuple('') - const numberTuple = new Tuple(0) + const numberTuple = new Tuple(0, 1) - expectType>(stringTuple.concat(numberTuple)) + expectType>(stringTuple.concat(numberTuple)) - expectType>(stringTuple.prepend(numberTuple)) + expectType>(stringTuple.prepend(numberTuple)) - expectType>(numberTuple.concat(stringTuple)) + expectType>(numberTuple.concat(stringTuple)) - expectType>(numberTuple.prepend(stringTuple)) + expectType>(numberTuple.prepend(stringTuple)) // @ts-expect-error - expectType>(stringTuple.prepend(numberTuple)) + expectType>(stringTuple.prepend(numberTuple)) // @ts-expect-error - expectType>(stringTuple.concat(numberTuple)) + expectType>(stringTuple.concat(numberTuple)) } From 90a6b6d224f496da88e6794f46fd3c31770b35ae Mon Sep 17 00:00:00 2001 From: Ben Durrant Date: Fri, 19 May 2023 21:59:42 +0100 Subject: [PATCH 233/412] Require enhancer to be a callback --- packages/toolkit/src/configureStore.ts | 15 ++++++---- .../toolkit/src/tests/configureStore.test.ts | 30 +++++++++++++------ .../src/tests/configureStore.typetest.ts | 24 ++++++++------- 3 files changed, 44 insertions(+), 25 deletions(-) diff --git a/packages/toolkit/src/configureStore.ts b/packages/toolkit/src/configureStore.ts index 44a06894f2..83ff19d19b 100644 --- a/packages/toolkit/src/configureStore.ts +++ b/packages/toolkit/src/configureStore.ts @@ -82,7 +82,7 @@ export interface ConfigureStoreOptions< * and should return a Tuple of enhancers (such as `getDefaultEnhancers().concat(offline)`). * If you only need to add middleware, you can use the `middleware` parameter instead. */ - enhancers?: ((getDefaultEnhancers: GetDefaultEnhancers) => E) | E + enhancers?: (getDefaultEnhancers: GetDefaultEnhancers) => E } export type Middlewares = ReadonlyArray> @@ -172,13 +172,18 @@ export function configureStore< const middlewareEnhancer = applyMiddleware(...finalMiddleware) const getDefaultEnhancers = buildGetDefaultEnhancers(middlewareEnhancer) + + if (!IS_PRODUCTION && enhancers && typeof enhancers !== 'function') { + throw new Error('"enhancers" field must be a callback') + } + let storeEnhancers = - (typeof enhancers === 'function' + typeof enhancers === 'function' ? enhancers(getDefaultEnhancers) - : enhancers) ?? getDefaultEnhancers() + : getDefaultEnhancers() if (!IS_PRODUCTION && !Array.isArray(storeEnhancers)) { - throw new Error('enhancers must be an array') + throw new Error('"enhancers" callback must return an array') } if ( !IS_PRODUCTION && @@ -194,7 +199,7 @@ export function configureStore< !storeEnhancers.includes(middlewareEnhancer) ) { console.error( - 'middlewares were provided, but middleware enhancer was not included in final enhancers' + 'middlewares were provided, but middleware enhancer was not included in final enhancers - make sure to call `getDefaultEnhancers`' ) } diff --git a/packages/toolkit/src/tests/configureStore.test.ts b/packages/toolkit/src/tests/configureStore.test.ts index 1c3fbd3766..0b12d20548 100644 --- a/packages/toolkit/src/tests/configureStore.test.ts +++ b/packages/toolkit/src/tests/configureStore.test.ts @@ -283,16 +283,28 @@ describe('configureStore', async () => { undefined, expect.any(Function) ) + + expect(dummyEnhancerCalled).toBe(true) }) - it('accepts a callback for customizing enhancers', () => { - const store = configureStore({ - reducer, - enhancers: (getDefaultEnhancers) => - getDefaultEnhancers().concat(dummyEnhancer), + describe('invalid arguments', () => { + test('enhancers is not a callback', () => { + expect(() => configureStore({ reducer, enhancers: [] as any })).toThrow( + '"enhancers" field must be a callback' + ) }) - expect(dummyEnhancerCalled).toBe(true) + test('callback fails to return array', () => { + expect(() => + configureStore({ reducer, enhancers: (() => {}) as any }) + ).toThrow('"enhancers" callback must return an array') + }) + + test('array contains non-function', () => { + expect(() => + configureStore({ reducer, enhancers: (() => ['']) as any }) + ).toThrow('each enhancer provided to configureStore must be a function') + }) }) const consoleSpy = vi.spyOn(console, 'error').mockImplementation(() => {}) @@ -306,20 +318,20 @@ describe('configureStore', async () => { it('warns if middleware enhancer is excluded from final array when middlewares are provided', () => { const store = configureStore({ reducer, - enhancers: new Tuple(dummyEnhancer), + enhancers: () => new Tuple(dummyEnhancer), }) expect(dummyEnhancerCalled).toBe(true) expect(consoleSpy).toHaveBeenCalledWith( - 'middlewares were provided, but middleware enhancer was not included in final enhancers' + 'middlewares were provided, but middleware enhancer was not included in final enhancers - make sure to call `getDefaultEnhancers`' ) }) it("doesn't warn when middleware enhancer is excluded if no middlewares provided", () => { const store = configureStore({ reducer, middleware: new Tuple(), - enhancers: new Tuple(dummyEnhancer), + enhancers: () => new Tuple(dummyEnhancer), }) expect(dummyEnhancerCalled).toBe(true) diff --git a/packages/toolkit/src/tests/configureStore.typetest.ts b/packages/toolkit/src/tests/configureStore.typetest.ts index 5ee4cbec0c..619b3e34ca 100644 --- a/packages/toolkit/src/tests/configureStore.typetest.ts +++ b/packages/toolkit/src/tests/configureStore.typetest.ts @@ -147,13 +147,13 @@ const _anyMiddleware: any = () => () => () => {} const store = configureStore({ reducer: () => 0, - enhancers: new Tuple(enhancer), + enhancers: () => new Tuple(enhancer), }) const store2 = configureStore({ reducer: () => 0, // @ts-expect-error - enhancers: [enhancer], + enhancers: () => [enhancer], }) expectType>( @@ -164,7 +164,7 @@ const _anyMiddleware: any = () => () => () => {} configureStore({ reducer: () => 0, // @ts-expect-error - enhancers: new Tuple('not a store enhancer'), + enhancers: () => new Tuple('not a store enhancer'), }) { @@ -192,10 +192,11 @@ const _anyMiddleware: any = () => () => () => {} const store = configureStore({ reducer: () => 0, - enhancers: new Tuple( - somePropertyStoreEnhancer, - anotherPropertyStoreEnhancer - ), + enhancers: () => + new Tuple( + somePropertyStoreEnhancer, + anotherPropertyStoreEnhancer + ), }) expectType(store.dispatch) @@ -254,10 +255,11 @@ const _anyMiddleware: any = () => () => () => {} const store = configureStore({ reducer: () => ({ aProperty: 0 }), - enhancers: new Tuple( - someStateExtendingEnhancer, - anotherStateExtendingEnhancer - ), + enhancers: () => + new Tuple( + someStateExtendingEnhancer, + anotherStateExtendingEnhancer + ), }) const state = store.getState() From b2b27d9d9eea3d6417b9629140e3d3c5c6ffd3e1 Mon Sep 17 00:00:00 2001 From: Ben Durrant Date: Mon, 29 May 2023 22:29:21 +0100 Subject: [PATCH 234/412] config object --- packages/toolkit/src/entities/models.ts | 6 +++--- packages/toolkit/src/entities/state_selectors.ts | 11 ++++++++--- .../src/entities/tests/state_selectors.test.ts | 2 +- 3 files changed, 12 insertions(+), 7 deletions(-) diff --git a/packages/toolkit/src/entities/models.ts b/packages/toolkit/src/entities/models.ts index a82af1e963..6d35a5efbc 100644 --- a/packages/toolkit/src/entities/models.ts +++ b/packages/toolkit/src/entities/models.ts @@ -1,6 +1,6 @@ import type { PayloadAction } from '../createAction' import type { IsAny } from '../tsHelpers' -import type { AnyCreateSelectorFunction } from './state_selectors' +import type { GetSelectorsOptions } from './state_selectors' /** * @public @@ -167,10 +167,10 @@ export interface EntityAdapter extends EntityStateAdapter { getInitialState(state: S): EntityState & S getSelectors( selectState?: undefined, - createSelector?: AnyCreateSelectorFunction + options?: GetSelectorsOptions ): EntitySelectors> getSelectors( selectState: (state: V) => EntityState, - createSelector?: AnyCreateSelectorFunction + options?: GetSelectorsOptions ): EntitySelectors } diff --git a/packages/toolkit/src/entities/state_selectors.ts b/packages/toolkit/src/entities/state_selectors.ts index 85fdc715c7..c446f203e0 100644 --- a/packages/toolkit/src/entities/state_selectors.ts +++ b/packages/toolkit/src/entities/state_selectors.ts @@ -12,19 +12,24 @@ export type AnyCreateSelectorFunction = CreateSelectorFunction< any>(func: F) => F > +export interface GetSelectorsOptions { + createSelector?: AnyCreateSelectorFunction +} + export function createSelectorsFactory() { function getSelectors( selectState?: undefined, - createSelector?: AnyCreateSelectorFunction + options?: GetSelectorsOptions ): EntitySelectors> function getSelectors( selectState: (state: V) => EntityState, - createSelector?: AnyCreateSelectorFunction + options?: GetSelectorsOptions ): EntitySelectors function getSelectors( selectState?: (state: V) => EntityState, - createSelector: AnyCreateSelectorFunction = createDraftSafeSelector + options: GetSelectorsOptions = {} ): EntitySelectors { + const { createSelector = createDraftSafeSelector } = options const selectIds = (state: EntityState) => state.ids const selectEntities = (state: EntityState) => state.entities diff --git a/packages/toolkit/src/entities/tests/state_selectors.test.ts b/packages/toolkit/src/entities/tests/state_selectors.test.ts index c15793e86a..c3127c15d6 100644 --- a/packages/toolkit/src/entities/tests/state_selectors.test.ts +++ b/packages/toolkit/src/entities/tests/state_selectors.test.ts @@ -133,7 +133,7 @@ describe('Entity State Selectors', () => { selectId: (book: BookModel) => book.id, }) - adapter.getSelectors(undefined, createCustomSelector) + adapter.getSelectors(undefined, { createSelector: createCustomSelector }) expect(memoizeSpy).toHaveBeenCalled() From 6a11af6db0e4bcb14aeec7da84a8ff5cd3e7a8c9 Mon Sep 17 00:00:00 2001 From: Ben Durrant Date: Mon, 29 May 2023 23:15:00 +0100 Subject: [PATCH 235/412] {} as Record --- packages/toolkit/src/entities/entity_state.ts | 2 +- packages/toolkit/src/entities/sorted_state_adapter.ts | 2 +- packages/toolkit/src/entities/unsorted_state_adapter.ts | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/toolkit/src/entities/entity_state.ts b/packages/toolkit/src/entities/entity_state.ts index 8a9c6a895c..62ab918442 100644 --- a/packages/toolkit/src/entities/entity_state.ts +++ b/packages/toolkit/src/entities/entity_state.ts @@ -6,7 +6,7 @@ export function getInitialEntityState(): EntityState< > { return { ids: [], - entities: {}, + entities: {} as Record, } } diff --git a/packages/toolkit/src/entities/sorted_state_adapter.ts b/packages/toolkit/src/entities/sorted_state_adapter.ts index 96bb75da4a..7d73513671 100644 --- a/packages/toolkit/src/entities/sorted_state_adapter.ts +++ b/packages/toolkit/src/entities/sorted_state_adapter.ts @@ -61,7 +61,7 @@ export function createSortedStateAdapter( state: R ): void { newEntities = ensureEntitiesArray(newEntities) - state.entities = {} + state.entities = {} as Record state.ids = [] addManyMutably(newEntities, state) diff --git a/packages/toolkit/src/entities/unsorted_state_adapter.ts b/packages/toolkit/src/entities/unsorted_state_adapter.ts index 9c3295dc43..77697e1618 100644 --- a/packages/toolkit/src/entities/unsorted_state_adapter.ts +++ b/packages/toolkit/src/entities/unsorted_state_adapter.ts @@ -67,7 +67,7 @@ export function createUnsortedStateAdapter( newEntities = ensureEntitiesArray(newEntities) state.ids = [] - state.entities = {} + state.entities = {} as Record addManyMutably(newEntities, state) } From cb31d8c690b8f2db8ee371015b155fc538d76186 Mon Sep 17 00:00:00 2001 From: Ben Durrant Date: Tue, 30 May 2023 12:02:26 +0100 Subject: [PATCH 236/412] fix createEntityAdapter call in docs --- docs/api/createEntityAdapter.mdx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/api/createEntityAdapter.mdx b/docs/api/createEntityAdapter.mdx index b60dd50bf2..3888c7ea51 100644 --- a/docs/api/createEntityAdapter.mdx +++ b/docs/api/createEntityAdapter.mdx @@ -48,9 +48,9 @@ import { type Book = { bookId: string; title: string } -const booksAdapter = createEntityAdapter({ +const booksAdapter = createEntityAdapter({ // Assume IDs are stored in a field other than `book.id` - selectId: (book) => book.bookId, + selectId: (book: Book) => book.bookId, // Keep the "all IDs" array sorted based on book titles sortComparer: (a, b) => a.title.localeCompare(b.title), }) From f08205f145cbd8ef640dbae4acdc24681dc9b17f Mon Sep 17 00:00:00 2001 From: Ben Durrant Date: Tue, 30 May 2023 15:45:47 +0100 Subject: [PATCH 237/412] fix invalid createEntityAdapter call --- docs/api/createEntityAdapter.mdx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/api/createEntityAdapter.mdx b/docs/api/createEntityAdapter.mdx index 0cfee25b9e..c88fb83057 100644 --- a/docs/api/createEntityAdapter.mdx +++ b/docs/api/createEntityAdapter.mdx @@ -46,9 +46,9 @@ import { type Book = { bookId: string; title: string } -const booksAdapter = createEntityAdapter({ +const booksAdapter = createEntityAdapter({ // Assume IDs are stored in a field other than `book.id` - selectId: (book) => book.bookId, + selectId: (book: Book) => book.bookId, // Keep the "all IDs" array sorted based on book titles sortComparer: (a, b) => a.title.localeCompare(b.title), }) From 28bb5ea023022029e855021c62f6e2987cb9123b Mon Sep 17 00:00:00 2001 From: Ben Durrant Date: Tue, 30 May 2023 20:09:37 +0100 Subject: [PATCH 238/412] use beta --- packages/toolkit/package.json | 2 +- yarn.lock | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/packages/toolkit/package.json b/packages/toolkit/package.json index bcfb4c74d5..1646b3fbe1 100644 --- a/packages/toolkit/package.json +++ b/packages/toolkit/package.json @@ -111,7 +111,7 @@ ], "dependencies": { "immer": "^10.0.2", - "redux": "https://pkg.csb.dev/reduxjs/redux/commit/20631993/redux/_pkg.tgz", + "redux": "5.0.0-beta.0", "redux-thunk": "3.0.0-alpha.3", "reselect": "^5.0.0-alpha.2" }, diff --git a/yarn.lock b/yarn.lock index a845fedee9..d5208aed54 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6784,7 +6784,7 @@ __metadata: node-fetch: ^2.6.1 prettier: ^2.2.1 query-string: ^7.0.1 - redux: "https://pkg.csb.dev/reduxjs/redux/commit/20631993/redux/_pkg.tgz" + redux: 5.0.0-beta.0 redux-thunk: 3.0.0-alpha.3 reselect: ^5.0.0-alpha.2 rimraf: ^3.0.2 @@ -24669,10 +24669,10 @@ fsevents@^1.2.7: languageName: node linkType: hard -"redux@https://pkg.csb.dev/reduxjs/redux/commit/20631993/redux/_pkg.tgz": - version: 5.0.0-alpha.6 - resolution: "redux@https://pkg.csb.dev/reduxjs/redux/commit/20631993/redux/_pkg.tgz" - checksum: ef83dca24531c68a8489da35f1a0d9d2343057d6269d2aff2dd84de5f5654d0bd5d3ec38c26bb8ab56d5a9ef47d45d86d770aea3d0f10325f9e5b37e71410397 +"redux@npm:5.0.0-beta.0": + version: 5.0.0-beta.0 + resolution: "redux@npm:5.0.0-beta.0" + checksum: 11df373e219f2f515ee1bda1a19a1ba5de02d8d5c874800ec353179dcd106eddd54432946fd0ab37c47f99f8fe53f820a6404c14da7f039a46022187e9469d2d languageName: node linkType: hard From 0c9e0e15bc2c50746579959cd913c69430b61daa Mon Sep 17 00:00:00 2001 From: Ben Durrant Date: Tue, 30 May 2023 20:11:17 +0100 Subject: [PATCH 239/412] update docs --- docs/api/createEntityAdapter.mdx | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/docs/api/createEntityAdapter.mdx b/docs/api/createEntityAdapter.mdx index 0b45a783db..ed0d8760b6 100644 --- a/docs/api/createEntityAdapter.mdx +++ b/docs/api/createEntityAdapter.mdx @@ -285,7 +285,7 @@ Each selector function will be created using the `createSelector` function from :::tip -The `createSelector` instance used can be replaced, by passing it as a second parameter: +The `createSelector` instance used can be replaced, by passing it as part of the options object (second parameter): ```js import { @@ -296,14 +296,13 @@ import { const createWeakMapDraftSafeSelector = createDraftSafeSelectorCreator(weakMapMemoize) -const simpleSelectors = booksAdapter.getSelectors( - undefined, - createWeakMapDraftSafeSelector -) -const globalizedSelectors = booksAdapter.getSelectors( - (state) => state.books, - createWeakMapDraftSafeSelector -) +const simpleSelectors = booksAdapter.getSelectors(undefined, { + createSelector: createWeakMapDraftSafeSelector, +}) + +const globalizedSelectors = booksAdapter.getSelectors((state) => state.books, { + createSelector: createWeakMapDraftSafeSelector, +}) ``` If no instance is passed, it will default to [`createDraftSafeSelector`](./createSelector#createDraftSafeSelector). From 693ecfd98a94a623b1e213d0209e99dcb58c7ada Mon Sep 17 00:00:00 2001 From: Ben Durrant Date: Wed, 31 May 2023 00:00:54 +0100 Subject: [PATCH 240/412] fix docs again --- docs/rtk-query/usage/persistence-and-rehydration.mdx | 9 ++++++++- packages/toolkit/src/createSlice.ts | 10 ++++++---- packages/toolkit/src/query/createApi.ts | 9 ++++++++- 3 files changed, 22 insertions(+), 6 deletions(-) diff --git a/docs/rtk-query/usage/persistence-and-rehydration.mdx b/docs/rtk-query/usage/persistence-and-rehydration.mdx index 8cfa3229fc..e6fdba750e 100644 --- a/docs/rtk-query/usage/persistence-and-rehydration.mdx +++ b/docs/rtk-query/usage/persistence-and-rehydration.mdx @@ -37,14 +37,21 @@ box with the `autoMergeLevel1` or `autoMergeLevel2` [state reconcilers](https:// when persisting the root reducer, or with the `autoMergeLevel1` reconciler when persisting just the api reducer. ```ts title="redux-persist rehydration example" +import type { Action, PayloadAction } from '@reduxjs/toolkit' import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react' import { REHYDRATE } from 'redux-persist' +type RootState = any // normally inferred from state + +function isHydrateAction(action: Action): action is PayloadAction { + return action.type === REHYDRATE +} + export const api = createApi({ baseQuery: fetchBaseQuery({ baseUrl: '/' }), // highlight-start extractRehydrationInfo(action, { reducerPath }) { - if (action.type === REHYDRATE) { + if (isHydrateAction(action)) { return action.payload[reducerPath] } }, diff --git a/packages/toolkit/src/createSlice.ts b/packages/toolkit/src/createSlice.ts index ddb483d65a..eca3c517b1 100644 --- a/packages/toolkit/src/createSlice.ts +++ b/packages/toolkit/src/createSlice.ts @@ -186,7 +186,7 @@ export interface CreateSliceOptions< * * @example ```ts -import { createAction, createSlice, Action, UnknownAction } from '@reduxjs/toolkit' +import { createAction, createSlice, Action } from '@reduxjs/toolkit' const incrementBy = createAction('incrementBy') const decrement = createAction('decrement') @@ -194,7 +194,7 @@ interface RejectedAction extends Action { error: Error } -function isRejectedAction(action: UnknownAction): action is RejectedAction { +function isRejectedAction(action: Action): action is RejectedAction { return action.type.endsWith('rejected') } @@ -240,8 +240,10 @@ interface ReducerDefinition { [reducerDefinitionType]: T } -export interface CaseReducerDefinition - extends CaseReducer, +export interface CaseReducerDefinition< + S = any, + A extends Action = UnknownAction +> extends CaseReducer, ReducerDefinition {} /** diff --git a/packages/toolkit/src/query/createApi.ts b/packages/toolkit/src/query/createApi.ts index 67cebe9b9f..a4bf7cf790 100644 --- a/packages/toolkit/src/query/createApi.ts +++ b/packages/toolkit/src/query/createApi.ts @@ -159,14 +159,21 @@ export interface CreateApiOptions< * * ```ts * // codeblock-meta title="next-redux-wrapper rehydration example" + * import type { Action, PayloadAction } from '@reduxjs/toolkit' * import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react' * import { HYDRATE } from 'next-redux-wrapper' * + * type RootState = any; // normally inferred from state + * + * function isHydrateAction(action: Action): action is PayloadAction { + * return action.type === HYDRATE + * } + * * export const api = createApi({ * baseQuery: fetchBaseQuery({ baseUrl: '/' }), * // highlight-start * extractRehydrationInfo(action, { reducerPath }) { - * if (action.type === HYDRATE) { + * if (isHydrateAction(action)) { * return action.payload[reducerPath] * } * }, From 556b20b1f75ef214a34876385626659c5e9b7f57 Mon Sep 17 00:00:00 2001 From: Mark Erikson Date: Tue, 30 May 2023 19:14:42 -0400 Subject: [PATCH 241/412] Release 2.0.0-beta.0 --- packages/toolkit/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/toolkit/package.json b/packages/toolkit/package.json index 1646b3fbe1..ccacc82946 100644 --- a/packages/toolkit/package.json +++ b/packages/toolkit/package.json @@ -1,6 +1,6 @@ { "name": "@reduxjs/toolkit", - "version": "2.0.0-alpha.6", + "version": "2.0.0-beta.0", "description": "The official, opinionated, batteries-included toolset for efficient Redux development", "author": "Mark Erikson ", "license": "MIT", From a5bd1e6065c5ebdddd78305edce21b5569b4c2ea Mon Sep 17 00:00:00 2001 From: Evert Bouw Date: Wed, 31 May 2023 11:06:51 +0200 Subject: [PATCH 242/412] fix syntax for users with noPropertyAccessFromIndexSignature enabled --- packages/toolkit/src/uncheckedindexed.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/toolkit/src/uncheckedindexed.ts b/packages/toolkit/src/uncheckedindexed.ts index 628ff5dc43..06d471ab47 100644 --- a/packages/toolkit/src/uncheckedindexed.ts +++ b/packages/toolkit/src/uncheckedindexed.ts @@ -2,7 +2,7 @@ // relies on remaining as a TS file, not .d.ts type IfMaybeUndefined = [undefined] extends [T] ? True : False -const testAccess = ({} as Record).a +const testAccess = ({} as Record)['a'] export type IfUncheckedIndexedAccess = IfMaybeUndefined< typeof testAccess, From 695be4d1e45ce64258dcb0120bf18c45641c28c8 Mon Sep 17 00:00:00 2001 From: Ben Durrant Date: Wed, 31 May 2023 10:37:29 +0100 Subject: [PATCH 243/412] add required type parameter to EntityState in docs --- docs/rtk-query/usage/customizing-queries.mdx | 28 +++++++++----------- docs/rtk-query/usage/streaming-updates.mdx | 2 +- 2 files changed, 13 insertions(+), 17 deletions(-) diff --git a/docs/rtk-query/usage/customizing-queries.mdx b/docs/rtk-query/usage/customizing-queries.mdx index 5d75ec3bb4..95b4857b39 100644 --- a/docs/rtk-query/usage/customizing-queries.mdx +++ b/docs/rtk-query/usage/customizing-queries.mdx @@ -205,7 +205,11 @@ argument, which can be used while determining the transformed response. The valu dependent on the `baseQuery` used. ```ts title="transformErrorResponse meta example" no-transpile -transformErrorResponse: (response: { data: { sideA: Tracks; sideB: Tracks } }, meta, arg) => { +transformErrorResponse: ( + response: { data: { sideA: Tracks; sideB: Tracks } }, + meta, + arg +) => { if (meta?.coinFlip === 'heads') { return response.data.sideA } @@ -318,7 +322,7 @@ const axiosBaseQuery = return { error: { status: err.response?.status, - data: err.response?.data || err.message + data: err.response?.data || err.message, }, } } @@ -610,10 +614,7 @@ In such a scenario, the return value would look like so: export declare const uuid: () => string // file: metaBaseQuery.ts -import { - fetchBaseQuery, - createApi, -} from '@reduxjs/toolkit/query' +import { fetchBaseQuery, createApi } from '@reduxjs/toolkit/query' import type { BaseQueryFn, FetchArgs, @@ -710,10 +711,7 @@ export interface Post { } // file: src/services/api.ts -import { - createApi, - fetchBaseQuery, -} from '@reduxjs/toolkit/query/react' +import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react' import type { BaseQueryFn, FetchArgs, @@ -864,7 +862,7 @@ const postsAdapter = createEntityAdapter({ export const api = createApi({ baseQuery: fetchBaseQuery({ baseUrl: '/' }), endpoints: (build) => ({ - getPosts: build.query, void>({ + getPosts: build.query, void>({ query: () => `posts`, // highlight-start transformResponse(response: Post[]) { @@ -987,10 +985,7 @@ export interface User { } // file: api.ts -import { - createApi, - fetchBaseQuery, -} from '@reduxjs/toolkit/query' +import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query' import type { FetchBaseQueryError } from '@reduxjs/toolkit/query' import type { Post, User } from './types' @@ -1001,7 +996,8 @@ const api = createApi({ async queryFn(_arg, _queryApi, _extraOptions, fetchWithBQ) { // get a random user const randomResult = await fetchWithBQ('users/random') - if (randomResult.error) return { error: randomResult.error as FetchBaseQueryError } + if (randomResult.error) + return { error: randomResult.error as FetchBaseQueryError } const user = randomResult.data as User const result = await fetchWithBQ(`user/${user.id}/posts`) return result.data diff --git a/docs/rtk-query/usage/streaming-updates.mdx b/docs/rtk-query/usage/streaming-updates.mdx index 588b61103e..88ed09082e 100644 --- a/docs/rtk-query/usage/streaming-updates.mdx +++ b/docs/rtk-query/usage/streaming-updates.mdx @@ -158,7 +158,7 @@ export const api = createApi({ baseQuery: fetchBaseQuery({ baseUrl: '/' }), endpoints: (build) => ({ // highlight-start - getMessages: build.query, Channel>({ + getMessages: build.query, Channel>({ // highlight-end query: (channel) => `messages/${channel}`, // highlight-start From 0d4bbd7e381ea0184464186c5e70a5cb192d781c Mon Sep 17 00:00:00 2001 From: Ben Durrant Date: Wed, 31 May 2023 18:34:21 +0100 Subject: [PATCH 244/412] Use matchers for cDM and include instanceId in final instance. --- .../toolkit/src/dynamicMiddleware/index.ts | 24 +++++++++------ .../src/dynamicMiddleware/tests/index.test.ts | 29 ++++++++++--------- .../toolkit/src/dynamicMiddleware/types.ts | 1 + 3 files changed, 31 insertions(+), 23 deletions(-) diff --git a/packages/toolkit/src/dynamicMiddleware/index.ts b/packages/toolkit/src/dynamicMiddleware/index.ts index 3b0583953f..94fdbb8678 100644 --- a/packages/toolkit/src/dynamicMiddleware/index.ts +++ b/packages/toolkit/src/dynamicMiddleware/index.ts @@ -2,10 +2,10 @@ import type { Middleware, Dispatch as ReduxDispatch, UnknownAction, - MiddlewareAPI, } from 'redux' import { compose } from 'redux' import { createAction, isAction } from '../createAction' +import { isAllOf } from '../matchers' import { nanoid } from '../nanoid' import { find } from '../utils' import type { @@ -27,6 +27,11 @@ const createMiddlewareEntry = < applied: new Map(), }) +const matchInstance = + (instanceId: string) => + (action: any): action is { meta: { instanceId: string } } => + action?.meta?.instanceId === instanceId + export const createDynamicMiddleware = < State = any, Dispatch extends ReduxDispatch = ReduxDispatch @@ -70,9 +75,7 @@ export const createDynamicMiddleware = < return addMiddleware as AddMiddleware })() - const getFinalMiddleware = ( - api: MiddlewareAPI - ): ReturnType> => { + const getFinalMiddleware: Middleware<{}, State, Dispatch> = (api) => { const appliedMiddleware = Array.from(middlewareMap.values()).map( (entry) => { let applied = entry.applied.get(api) @@ -86,13 +89,15 @@ export const createDynamicMiddleware = < return compose(...appliedMiddleware) } + const isWithMiddleware = isAllOf( + isAction, + withMiddleware, + matchInstance(instanceId) + ) + const middleware: DynamicMiddleware = (api) => (next) => (action) => { - if ( - isAction(action) && - withMiddleware.match(action) && - action.meta.instanceId === instanceId - ) { + if (isWithMiddleware(action)) { addMiddleware(...action.payload) return api.dispatch } @@ -103,5 +108,6 @@ export const createDynamicMiddleware = < middleware, addMiddleware, withMiddleware, + instanceId, } } diff --git a/packages/toolkit/src/dynamicMiddleware/tests/index.test.ts b/packages/toolkit/src/dynamicMiddleware/tests/index.test.ts index 7049fff92e..8102685459 100644 --- a/packages/toolkit/src/dynamicMiddleware/tests/index.test.ts +++ b/packages/toolkit/src/dynamicMiddleware/tests/index.test.ts @@ -4,6 +4,7 @@ import { configureStore } from '../../configureStore' import type { BaseActionCreator, PayloadAction } from '../../createAction' import { isAction } from '../../createAction' import { createAction } from '../../createAction' +import { isAllOf } from '../../matchers' export interface ProbeMiddleware extends BaseActionCreator { @@ -14,24 +15,24 @@ export const probeMiddleware = createAction( 'probeableMW/probe' ) as ProbeMiddleware -export const makeProbeableMiddleware = - ( - id: Id - ): Middleware<{ - (action: PayloadAction): Id - }> => - (api) => - (next) => - (action) => { - if ( - isAction(action) && - probeMiddleware.match(action) && - action.payload === id - ) { +const matchId = + (id: Id) => + (action: any): action is PayloadAction => + action.payload === id + +export const makeProbeableMiddleware = ( + id: Id +): Middleware<{ + (action: PayloadAction): Id +}> => { + const isMiddlewareAction = isAllOf(isAction, probeMiddleware, matchId(id)) + return (api) => (next) => (action) => { + if (isMiddlewareAction(action)) { return id } return next(action) } +} const staticMiddleware = makeProbeableMiddleware(1) diff --git a/packages/toolkit/src/dynamicMiddleware/types.ts b/packages/toolkit/src/dynamicMiddleware/types.ts index 234b1578e1..dc3d563c3c 100644 --- a/packages/toolkit/src/dynamicMiddleware/types.ts +++ b/packages/toolkit/src/dynamicMiddleware/types.ts @@ -89,4 +89,5 @@ export type DynamicMiddlewareInstance< middleware: DynamicMiddleware addMiddleware: AddMiddleware withMiddleware: WithMiddleware + instanceId: string } From 375d048ec7e4140da64e2108fe3edc4c59732c0b Mon Sep 17 00:00:00 2001 From: Ben Durrant Date: Wed, 31 May 2023 18:39:52 +0100 Subject: [PATCH 245/412] DRY probeType --- .../src/dynamicMiddleware/tests/index.test.ts | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/packages/toolkit/src/dynamicMiddleware/tests/index.test.ts b/packages/toolkit/src/dynamicMiddleware/tests/index.test.ts index 8102685459..4ef03bd0cb 100644 --- a/packages/toolkit/src/dynamicMiddleware/tests/index.test.ts +++ b/packages/toolkit/src/dynamicMiddleware/tests/index.test.ts @@ -6,14 +6,15 @@ import { isAction } from '../../createAction' import { createAction } from '../../createAction' import { isAllOf } from '../../matchers' -export interface ProbeMiddleware - extends BaseActionCreator { - (id: Id): PayloadAction +const probeType = 'probeableMW/probe' + +type ProbeType = typeof probeType + +export interface ProbeMiddleware extends BaseActionCreator { + (id: Id): PayloadAction } -export const probeMiddleware = createAction( - 'probeableMW/probe' -) as ProbeMiddleware +export const probeMiddleware = createAction(probeType) as ProbeMiddleware const matchId = (id: Id) => @@ -23,7 +24,7 @@ const matchId = export const makeProbeableMiddleware = ( id: Id ): Middleware<{ - (action: PayloadAction): Id + (action: PayloadAction): Id }> => { const isMiddlewareAction = isAllOf(isAction, probeMiddleware, matchId(id)) return (api) => (next) => (action) => { From e15797a3ce4a8d292e32ef435008ee2999b019b3 Mon Sep 17 00:00:00 2001 From: Ben Durrant Date: Wed, 31 May 2023 23:22:59 +0100 Subject: [PATCH 246/412] don't nest useDispatch inside a useDispatchWithMiddleware function --- .../toolkit/src/dynamicMiddleware/index.ts | 27 +++++++------------ .../src/dynamicMiddleware/react/index.ts | 4 +-- 2 files changed, 11 insertions(+), 20 deletions(-) diff --git a/packages/toolkit/src/dynamicMiddleware/index.ts b/packages/toolkit/src/dynamicMiddleware/index.ts index 94fdbb8678..12f5b6522a 100644 --- a/packages/toolkit/src/dynamicMiddleware/index.ts +++ b/packages/toolkit/src/dynamicMiddleware/index.ts @@ -39,12 +39,8 @@ export const createDynamicMiddleware = < const instanceId = nanoid() const middlewareMap = new Map>() - const insertEntry = (entry: MiddlewareEntry) => { - middlewareMap.set(entry.id, entry) - } - - const withMiddleware = (() => { - const withMiddleware = createAction( + const withMiddleware = Object.assign( + createAction( 'dynamicMiddleware/add', (...middlewares: Middleware[]) => ({ payload: middlewares, @@ -52,13 +48,11 @@ export const createDynamicMiddleware = < instanceId, }, }) - ) - // @ts-ignore - withMiddleware.withTypes = () => withMiddleware - return withMiddleware as WithMiddleware - })() + ), + { withTypes: () => withMiddleware } + ) as WithMiddleware - const addMiddleware = (() => { + const addMiddleware = Object.assign( function addMiddleware(...middlewares: Middleware[]) { middlewares.forEach((middleware) => { let entry = find( @@ -68,12 +62,11 @@ export const createDynamicMiddleware = < if (!entry) { entry = createMiddlewareEntry(middleware) } - insertEntry(entry) + middlewareMap.set(entry.id, entry) }) - } - addMiddleware.withTypes = () => addMiddleware - return addMiddleware as AddMiddleware - })() + }, + { withTypes: () => addMiddleware } + ) as AddMiddleware const getFinalMiddleware: Middleware<{}, State, Dispatch> = (api) => { const appliedMiddleware = Array.from(middlewareMap.values()).map( diff --git a/packages/toolkit/src/dynamicMiddleware/react/index.ts b/packages/toolkit/src/dynamicMiddleware/react/index.ts index 7315f93394..94c6b022a3 100644 --- a/packages/toolkit/src/dynamicMiddleware/react/index.ts +++ b/packages/toolkit/src/dynamicMiddleware/react/index.ts @@ -84,9 +84,7 @@ export const createDynamicMiddleware = < Middlewares extends Middleware[] >(...middlewares: Middlewares) { instance.addMiddleware(...middlewares) - return function useDispatchWithMiddleware() { - return useDispatch() - } + return useDispatch } createDispatchWithMiddlewareHook.withTypes = () => createDispatchWithMiddlewareHook From dc20b221ff5451f9719d133603e59ea9b3763392 Mon Sep 17 00:00:00 2001 From: "ben.durrant" Date: Wed, 21 Jun 2023 18:20:16 +0100 Subject: [PATCH 247/412] Add selectCachedArgsForQuery util --- .../toolkit/src/query/core/buildSelectors.ts | 27 ++++++++++++++- packages/toolkit/src/query/core/module.ts | 26 +++++++++++---- .../src/query/tests/buildSelector.test.ts | 33 +++++++++++++++++++ 3 files changed, 79 insertions(+), 7 deletions(-) diff --git a/packages/toolkit/src/query/core/buildSelectors.ts b/packages/toolkit/src/query/core/buildSelectors.ts index d3a7361c70..58967bb6b1 100644 --- a/packages/toolkit/src/query/core/buildSelectors.ts +++ b/packages/toolkit/src/query/core/buildSelectors.ts @@ -5,6 +5,8 @@ import type { RootState as _RootState, RequestStatusFlags, QueryCacheKey, + QueryKeys, + QueryState, } from './apiState' import { QueryStatus, getRequestStatusFlags } from './apiState' import type { @@ -130,7 +132,12 @@ export function buildSelectors< const selectSkippedQuery = (state: RootState) => defaultQuerySubState const selectSkippedMutation = (state: RootState) => defaultMutationSubState - return { buildQuerySelector, buildMutationSelector, selectInvalidatedBy } + return { + buildQuerySelector, + buildMutationSelector, + selectInvalidatedBy, + selectCachedArgsForQuery, + } function withRequestFlags( substate: T @@ -238,4 +245,22 @@ export function buildSelectors< }) ) } + + function selectCachedArgsForQuery>( + state: RootState, + queryName: QueryName + ): Array> { + return Object.values(state[reducerPath].queries as QueryState) + .filter( + ( + entry + ): entry is Exclude< + QuerySubState, + { status: QueryStatus.uninitialized } + > => + entry?.endpointName === queryName && + entry.status !== QueryStatus.uninitialized + ) + .map((entry) => entry.originalArgs) + } } diff --git a/packages/toolkit/src/query/core/module.ts b/packages/toolkit/src/query/core/module.ts index 0b7d85b582..71be69936d 100644 --- a/packages/toolkit/src/query/core/module.ts +++ b/packages/toolkit/src/query/core/module.ts @@ -356,6 +356,16 @@ declare module '../apiTypes' { originalArgs: any queryCacheKey: string }> + + /** + * A function to select all arguments currently cached for a given endpoint. + * + * Can be used for mutations that want to do optimistic updates instead of invalidating a set of tags, but don't know exactly what they need to update. + */ + selectCachedArgsForQuery: >( + state: RootState, + queryName: QueryName + ) => Array> } /** * Endpoints based on the input endpoints provided to `createApi`, containing `select` and `action matchers`. @@ -527,13 +537,17 @@ export const coreModule = (): Module => ({ safeAssign(api, { reducer: reducer as any, middleware }) - const { buildQuerySelector, buildMutationSelector, selectInvalidatedBy } = - buildSelectors({ - serializeQueryArgs: serializeQueryArgs as any, - reducerPath, - }) + const { + buildQuerySelector, + buildMutationSelector, + selectInvalidatedBy, + selectCachedArgsForQuery, + } = buildSelectors({ + serializeQueryArgs: serializeQueryArgs as any, + reducerPath, + }) - safeAssign(api.util, { selectInvalidatedBy }) + safeAssign(api.util, { selectInvalidatedBy, selectCachedArgsForQuery }) const { buildInitiateQuery, diff --git a/packages/toolkit/src/query/tests/buildSelector.test.ts b/packages/toolkit/src/query/tests/buildSelector.test.ts index 5248dfcd29..5a62a1320d 100644 --- a/packages/toolkit/src/query/tests/buildSelector.test.ts +++ b/packages/toolkit/src/query/tests/buildSelector.test.ts @@ -52,4 +52,37 @@ describe('buildSelector', () => { const upperTitle = todoTitle.toUpperCase() expectExactType(upperTitle) }) + test.skip('selectCachedArgsForQuery typetest', () => { + interface Todo { + userId: number + id: number + title: string + completed: boolean + } + + type Todos = Array + + const exampleApi = createApi({ + reducerPath: 'api', + baseQuery: fetchBaseQuery({ + baseUrl: 'https://jsonplaceholder.typicode.com', + }), + endpoints: (build) => ({ + getTodos: build.query({ + query: () => '/todos', + }), + }), + }) + + const store = configureStore({ + reducer: { + [exampleApi.reducerPath]: exampleApi.reducer, + other: () => 1, + }, + }) + + expectExactType( + exampleApi.util.selectCachedArgsForQuery(store.getState(), 'getTodos') + ) + }) }) From 130ee8d85ad1afed6d4b3282e6afbd9bf58993db Mon Sep 17 00:00:00 2001 From: "ben.durrant" Date: Wed, 21 Jun 2023 20:24:16 +0100 Subject: [PATCH 248/412] update docs --- .../api/created-api/api-slice-utils.mdx | 47 +++++++++++++++---- docs/rtk-query/api/created-api/overview.mdx | 4 ++ 2 files changed, 43 insertions(+), 8 deletions(-) diff --git a/docs/rtk-query/api/created-api/api-slice-utils.mdx b/docs/rtk-query/api/created-api/api-slice-utils.mdx index 7698b0c7f9..fb65a6bc2e 100644 --- a/docs/rtk-query/api/created-api/api-slice-utils.mdx +++ b/docs/rtk-query/api/created-api/api-slice-utils.mdx @@ -268,14 +268,12 @@ It returns an array that contains #### Example ```ts no-transpile -dispatch(api.util.selectInvalidatedBy(state, ['Post'])) -dispatch(api.util.selectInvalidatedBy(state, [{ type: 'Post', id: 1 }])) -dispatch( - api.util.selectInvalidatedBy(state, [ - { type: 'Post', id: 1 }, - { type: 'Post', id: 4 }, - ]) -) +const entries = api.util.selectInvalidatedBy(state, ['Post']) +const entries = api.util.selectInvalidatedBy(state, [{ type: 'Post', id: 1 }]) +const entries = api.util.selectInvalidatedBy(state, [ + { type: 'Post', id: 1 }, + { type: 'Post', id: 4 }, +]) ``` ### `invalidateTags` @@ -318,6 +316,39 @@ dispatch( ) ``` +### `selectCachedArgsForQuery` + +#### Signature + +```ts no-transpile +function selectCachedArgsForQuery( + state: RootState, + queryName: QueryName +): Array +``` + +- **Parameters** + - `state`: the root state + - `queryName`: a string matching an existing query endpoint name + +#### Description + +A function that can select arguments for currently cached queries. + +The function accepts two arguments + +- the root state and + +- the name of the query + +It returns an array that contains arguments used for each entry. + +#### Example + +```ts no-transpile +const args = api.util.selectCachedArgsForQuery(state, 'getPosts') +``` + ### `resetApiState` #### Signature diff --git a/docs/rtk-query/api/created-api/overview.mdx b/docs/rtk-query/api/created-api/overview.mdx index c64dd64b72..b515a2f5c8 100644 --- a/docs/rtk-query/api/created-api/overview.mdx +++ b/docs/rtk-query/api/created-api/overview.mdx @@ -61,6 +61,10 @@ type Api = { originalArgs: any queryCacheKey: string }> + selectCachedArgsForQuery: ( + state: FullState, + endpointName: EndpointName + ) => Array resetApiState: ActionCreator getRunningQueryThunk( endpointName: EndpointName, From c09eb1ebfa24938963892c3b49d94922a0b80131 Mon Sep 17 00:00:00 2001 From: Eric Crowell Date: Tue, 11 Jul 2023 18:28:14 +0200 Subject: [PATCH 249/412] fix(RTKQuery React): Resolved declaration pathing for new Node16 module resolutions. fixes #3591 --- packages/toolkit/src/query/react/module.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/toolkit/src/query/react/module.ts b/packages/toolkit/src/query/react/module.ts index b9bc6f999b..ddf5ea3ca4 100644 --- a/packages/toolkit/src/query/react/module.ts +++ b/packages/toolkit/src/query/react/module.ts @@ -26,7 +26,7 @@ import type { PrefetchOptions } from '../core/module' export const reactHooksModuleName = /* @__PURE__ */ Symbol() export type ReactHooksModule = typeof reactHooksModuleName -declare module '@reduxjs/toolkit/dist/query/apiTypes' { +declare module '@reduxjs/toolkit/query' { export interface ApiModules< // eslint-disable-next-line @typescript-eslint/no-unused-vars BaseQuery extends BaseQueryFn, From 0a198947d97a75fe3fe77317e4c96e7ed68a3f34 Mon Sep 17 00:00:00 2001 From: Eric Crowell Date: Wed, 19 Jul 2023 15:30:21 +0200 Subject: [PATCH 250/412] fix: Default export of the module has or is using private name type error when using latest alpha/beta --- packages/toolkit/src/createSlice.ts | 17 ++++++++--------- packages/toolkit/src/index.ts | 2 ++ 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/packages/toolkit/src/createSlice.ts b/packages/toolkit/src/createSlice.ts index ac93394041..e102e3dfbd 100644 --- a/packages/toolkit/src/createSlice.ts +++ b/packages/toolkit/src/createSlice.ts @@ -233,15 +233,14 @@ createSlice({ selectors?: Selectors } -const reducerDefinitionType: unique symbol = Symbol.for('rtk-reducer-type') -enum ReducerType { +export enum ReducerType { reducer = 'reducer', reducerWithPrepare = 'reducerWithPrepare', asyncThunk = 'asyncThunk', } interface ReducerDefinition { - [reducerDefinitionType]: T + _reducerDefinitionType: T } export interface CaseReducerDefinition< @@ -375,7 +374,7 @@ export interface ReducerCreators { ReturnType<_ActionCreatorWithPreparedPayload> > ): { - [reducerDefinitionType]: ReducerType.reducerWithPrepare + _reducerDefinitionType: ReducerType.reducerWithPrepare prepare: Prepare reducer: CaseReducer< State, @@ -748,7 +747,7 @@ function buildReducerCreators(): ReducerCreators { config: AsyncThunkSliceReducerConfig ): AsyncThunkSliceReducerDefinition { return { - [reducerDefinitionType]: ReducerType.asyncThunk, + _reducerDefinitionType: ReducerType.asyncThunk, payloadCreator, ...config, } @@ -765,13 +764,13 @@ function buildReducerCreators(): ReducerCreators { }, }[caseReducer.name], { - [reducerDefinitionType]: ReducerType.reducer, + _reducerDefinitionType: ReducerType.reducer, } as const ) }, preparedReducer(prepare, reducer) { return { - [reducerDefinitionType]: ReducerType.reducerWithPrepare, + _reducerDefinitionType: ReducerType.reducerWithPrepare, prepare, reducer, } @@ -813,14 +812,14 @@ function handleNormalReducerDefinition( function isAsyncThunkSliceReducerDefinition( reducerDefinition: any ): reducerDefinition is AsyncThunkSliceReducerDefinition { - return reducerDefinition[reducerDefinitionType] === ReducerType.asyncThunk + return reducerDefinition._reducerDefinitionType === ReducerType.asyncThunk } function isCaseReducerWithPrepareDefinition( reducerDefinition: any ): reducerDefinition is CaseReducerWithPrepareDefinition { return ( - reducerDefinition[reducerDefinitionType] === ReducerType.reducerWithPrepare + reducerDefinition._reducerDefinitionType === ReducerType.reducerWithPrepare ) } diff --git a/packages/toolkit/src/index.ts b/packages/toolkit/src/index.ts index b2153e7804..99814e0620 100644 --- a/packages/toolkit/src/index.ts +++ b/packages/toolkit/src/index.ts @@ -67,6 +67,7 @@ export type { export { // js createSlice, + ReducerType, } from './createSlice' export type { @@ -78,6 +79,7 @@ export type { ValidateSliceCaseReducers, CaseReducerWithPrepare, ReducerCreators, + SliceSelectors, } from './createSlice' export type { ActionCreatorInvariantMiddlewareOptions } from './actionCreatorInvariantMiddleware' export { createActionCreatorInvariantMiddleware } from './actionCreatorInvariantMiddleware' From acaa6ad60effced6b9d1b1af3a29a0179a66e146 Mon Sep 17 00:00:00 2001 From: Olesj-Bilous Date: Tue, 22 Aug 2023 23:49:42 +0200 Subject: [PATCH 251/412] entity adapter - draftable entity state improve typing to account for draftable entity state --- .../toolkit/src/entities/create_adapter.ts | 88 ++-- packages/toolkit/src/entities/models.ts | 53 +-- .../src/entities/sorted_state_adapter.ts | 7 +- .../toolkit/src/entities/state_adapter.ts | 117 ++--- .../src/entities/unsorted_state_adapter.ts | 399 +++++++++--------- packages/toolkit/src/entities/utils.ts | 98 ++--- 6 files changed, 389 insertions(+), 373 deletions(-) diff --git a/packages/toolkit/src/entities/create_adapter.ts b/packages/toolkit/src/entities/create_adapter.ts index 0d0c77e9d1..36f1f28463 100644 --- a/packages/toolkit/src/entities/create_adapter.ts +++ b/packages/toolkit/src/entities/create_adapter.ts @@ -1,43 +1,45 @@ -import type { - EntityDefinition, - Comparer, - IdSelector, - EntityAdapter, -} from './models' -import { createInitialStateFactory } from './entity_state' -import { createSelectorsFactory } from './state_selectors' -import { createSortedStateAdapter } from './sorted_state_adapter' -import { createUnsortedStateAdapter } from './unsorted_state_adapter' - -/** - * - * @param options - * - * @public - */ -export function createEntityAdapter( - options: { - selectId?: IdSelector - sortComparer?: false | Comparer - } = {} -): EntityAdapter { - const { selectId, sortComparer }: EntityDefinition = { - sortComparer: false, - selectId: (instance: any) => instance.id, - ...options, - } - - const stateFactory = createInitialStateFactory() - const selectorsFactory = createSelectorsFactory() - const stateAdapter = sortComparer - ? createSortedStateAdapter(selectId, sortComparer) - : createUnsortedStateAdapter(selectId) - - return { - selectId, - sortComparer, - ...stateFactory, - ...selectorsFactory, - ...stateAdapter, - } -} +import type { Draft } from 'immer' +import type { + EntityDefinition, + Comparer, + IdSelector, + EntityAdapter, + DraftableIdSelector, +} from './models' +import { createInitialStateFactory } from './entity_state' +import { createSelectorsFactory } from './state_selectors' +import { createSortedStateAdapter } from './sorted_state_adapter' +import { createUnsortedStateAdapter } from './unsorted_state_adapter' + +/** + * + * @param options + * + * @public + */ +export function createEntityAdapter( + options: { + selectId?: DraftableIdSelector + sortComparer?: false | Comparer + } = {} +): EntityAdapter { + const { selectId, sortComparer }: EntityDefinition = { + sortComparer: false, + selectId: (instance: any) => instance.id, + ...options, + } + + const stateFactory = createInitialStateFactory() + const selectorsFactory = createSelectorsFactory() + const stateAdapter = sortComparer + ? createSortedStateAdapter(selectId, sortComparer) + : createUnsortedStateAdapter(selectId) + + return { + selectId, + sortComparer, + ...stateFactory, + ...selectorsFactory, + ...stateAdapter, + } +} diff --git a/packages/toolkit/src/entities/models.ts b/packages/toolkit/src/entities/models.ts index e57c4aee9a..abfb415dee 100644 --- a/packages/toolkit/src/entities/models.ts +++ b/packages/toolkit/src/entities/models.ts @@ -1,3 +1,4 @@ +import type { Draft } from 'immer' import type { PayloadAction } from '../createAction' import type { IsAny } from '../tsHelpers' @@ -47,99 +48,103 @@ export interface EntityState { * @public */ export interface EntityDefinition { - selectId: IdSelector + selectId: IdSelector> sortComparer: false | Comparer } export type PreventAny = IsAny, S> +export type DraftableEntityState = EntityState | Draft> + +export type DraftableIdSelector = IdSelector> + /** * @public */ export interface EntityStateAdapter { - addOne>(state: PreventAny, entity: T): S - addOne>( + addOne>(state: PreventAny, entity: T): S + addOne>( state: PreventAny, action: PayloadAction ): S - addMany>( + addMany>( state: PreventAny, entities: readonly T[] | Record ): S - addMany>( + addMany>( state: PreventAny, entities: PayloadAction> ): S - setOne>(state: PreventAny, entity: T): S - setOne>( + setOne>(state: PreventAny, entity: T): S + setOne>( state: PreventAny, action: PayloadAction ): S - setMany>( + setMany>( state: PreventAny, entities: readonly T[] | Record ): S - setMany>( + setMany>( state: PreventAny, entities: PayloadAction> ): S - setAll>( + setAll>( state: PreventAny, entities: readonly T[] | Record ): S - setAll>( + setAll>( state: PreventAny, entities: PayloadAction> ): S - removeOne>(state: PreventAny, key: EntityId): S - removeOne>( + removeOne>(state: PreventAny, key: EntityId): S + removeOne>( state: PreventAny, key: PayloadAction ): S - removeMany>( + removeMany>( state: PreventAny, keys: readonly EntityId[] ): S - removeMany>( + removeMany>( state: PreventAny, keys: PayloadAction ): S - removeAll>(state: PreventAny): S + removeAll>(state: PreventAny): S - updateOne>( + updateOne>( state: PreventAny, update: Update ): S - updateOne>( + updateOne>( state: PreventAny, update: PayloadAction> ): S - updateMany>( + updateMany>( state: PreventAny, updates: ReadonlyArray> ): S - updateMany>( + updateMany>( state: PreventAny, updates: PayloadAction>> ): S - upsertOne>(state: PreventAny, entity: T): S - upsertOne>( + upsertOne>(state: PreventAny, entity: T): S + upsertOne>( state: PreventAny, entity: PayloadAction ): S - upsertMany>( + upsertMany>( state: PreventAny, entities: readonly T[] | Record ): S - upsertMany>( + upsertMany>( state: PreventAny, entities: PayloadAction> ): S diff --git a/packages/toolkit/src/entities/sorted_state_adapter.ts b/packages/toolkit/src/entities/sorted_state_adapter.ts index 4f1ed7a372..8b832d71a2 100644 --- a/packages/toolkit/src/entities/sorted_state_adapter.ts +++ b/packages/toolkit/src/entities/sorted_state_adapter.ts @@ -1,3 +1,4 @@ +import type {Draft} from 'immer' import type { EntityState, IdSelector, @@ -5,6 +6,8 @@ import type { EntityStateAdapter, Update, EntityId, + DraftableEntityState, + DraftableIdSelector, } from './models' import { createStateOperator } from './state_adapter' import { createUnsortedStateAdapter } from './unsorted_state_adapter' @@ -15,10 +18,10 @@ import { } from './utils' export function createSortedStateAdapter( - selectId: IdSelector, + selectId: DraftableIdSelector, sort: Comparer ): EntityStateAdapter { - type R = EntityState + type R = DraftableEntityState const { removeOne, removeMany, removeAll } = createUnsortedStateAdapter(selectId) diff --git a/packages/toolkit/src/entities/state_adapter.ts b/packages/toolkit/src/entities/state_adapter.ts index fbc1577683..de62323e54 100644 --- a/packages/toolkit/src/entities/state_adapter.ts +++ b/packages/toolkit/src/entities/state_adapter.ts @@ -1,57 +1,60 @@ -import createNextState, { isDraft } from 'immer' -import type { EntityState, PreventAny } from './models' -import type { PayloadAction } from '../createAction' -import { isFSA } from '../createAction' -import { IsAny } from '../tsHelpers' - -export function createSingleArgumentStateOperator( - mutator: (state: EntityState) => void -) { - const operator = createStateOperator((_: undefined, state: EntityState) => - mutator(state) - ) - - return function operation>( - state: PreventAny - ): S { - return operator(state as S, undefined) - } -} - -export function createStateOperator( - mutator: (arg: R, state: EntityState) => void -) { - return function operation>( - state: S, - arg: R | PayloadAction - ): S { - function isPayloadActionArgument( - arg: R | PayloadAction - ): arg is PayloadAction { - return isFSA(arg) - } - - const runMutator = (draft: EntityState) => { - if (isPayloadActionArgument(arg)) { - mutator(arg.payload, draft) - } else { - mutator(arg, draft) - } - } - - if (isDraft(state)) { - // we must already be inside a `createNextState` call, likely because - // this is being wrapped in `createReducer` or `createSlice`. - // It's safe to just pass the draft to the mutator. - runMutator(state) - - // since it's a draft, we'll just return it - return state - } else { - // @ts-ignore createNextState() produces an Immutable> rather - // than an Immutable, and TypeScript cannot find out how to reconcile - // these two types. - return createNextState(state, runMutator) - } - } -} +import createNextState, { isDraft } from 'immer' +import type { Draft } from 'immer' +import type { DraftableEntityState, EntityState, PreventAny } from './models' +import type { PayloadAction } from '../createAction' +import { isFSA } from '../createAction' +import { IsAny } from '../tsHelpers' + +export const isDraftTyped = isDraft as (value: any) => value is Draft + +export function createSingleArgumentStateOperator( + mutator: (state: DraftableEntityState) => void +) { + const operator = createStateOperator((_: undefined, state: DraftableEntityState) => + mutator(state) + ) + + return function operation>( + state: PreventAny + ): S { + return operator(state as S, undefined) + } +} + +export function createStateOperator( + mutator: (arg: R, state: DraftableEntityState) => void +) { + return function operation>( + state: S, + arg: R | PayloadAction + ): S { + function isPayloadActionArgument( + arg: R | PayloadAction + ): arg is PayloadAction { + return isFSA(arg) + } + + const runMutator = (draft: DraftableEntityState) => { + if (isPayloadActionArgument(arg)) { + mutator(arg.payload, draft) + } else { + mutator(arg, draft) + } + } + + if (isDraftTyped>(state)) { + // we must already be inside a `createNextState` call, likely because + // this is being wrapped in `createReducer` or `createSlice`. + // It's safe to just pass the draft to the mutator. + runMutator(state) + + // since it's a draft, we'll just return it + return state + } else { + // @ts-ignore createNextState() produces an Immutable> rather + // than an Immutable, and TypeScript cannot find out how to reconcile + // these two types. + return createNextState(state, runMutator) + } + } +} diff --git a/packages/toolkit/src/entities/unsorted_state_adapter.ts b/packages/toolkit/src/entities/unsorted_state_adapter.ts index 9113580ba8..cac265b5b3 100644 --- a/packages/toolkit/src/entities/unsorted_state_adapter.ts +++ b/packages/toolkit/src/entities/unsorted_state_adapter.ts @@ -1,198 +1,201 @@ -import type { - EntityState, - EntityStateAdapter, - IdSelector, - Update, - EntityId, -} from './models' -import { - createStateOperator, - createSingleArgumentStateOperator, -} from './state_adapter' -import { - selectIdValue, - ensureEntitiesArray, - splitAddedUpdatedEntities, -} from './utils' - -export function createUnsortedStateAdapter( - selectId: IdSelector -): EntityStateAdapter { - type R = EntityState - - function addOneMutably(entity: T, state: R): void { - const key = selectIdValue(entity, selectId) - - if (key in state.entities) { - return - } - - state.ids.push(key) - state.entities[key] = entity - } - - function addManyMutably( - newEntities: readonly T[] | Record, - state: R - ): void { - newEntities = ensureEntitiesArray(newEntities) - - for (const entity of newEntities) { - addOneMutably(entity, state) - } - } - - function setOneMutably(entity: T, state: R): void { - const key = selectIdValue(entity, selectId) - if (!(key in state.entities)) { - state.ids.push(key) - } - state.entities[key] = entity - } - - function setManyMutably( - newEntities: readonly T[] | Record, - state: R - ): void { - newEntities = ensureEntitiesArray(newEntities) - for (const entity of newEntities) { - setOneMutably(entity, state) - } - } - - function setAllMutably( - newEntities: readonly T[] | Record, - state: R - ): void { - newEntities = ensureEntitiesArray(newEntities) - - state.ids = [] - state.entities = {} - - addManyMutably(newEntities, state) - } - - function removeOneMutably(key: EntityId, state: R): void { - return removeManyMutably([key], state) - } - - function removeManyMutably(keys: readonly EntityId[], state: R): void { - let didMutate = false - - keys.forEach((key) => { - if (key in state.entities) { - delete state.entities[key] - didMutate = true - } - }) - - if (didMutate) { - state.ids = state.ids.filter((id) => id in state.entities) - } - } - - function removeAllMutably(state: R): void { - Object.assign(state, { - ids: [], - entities: {}, - }) - } - - function takeNewKey( - keys: { [id: string]: EntityId }, - update: Update, - state: R - ): boolean { - const original = state.entities[update.id] - const updated: T = Object.assign({}, original, update.changes) - const newKey = selectIdValue(updated, selectId) - const hasNewKey = newKey !== update.id - - if (hasNewKey) { - keys[update.id] = newKey - delete state.entities[update.id] - } - - state.entities[newKey] = updated - - return hasNewKey - } - - function updateOneMutably(update: Update, state: R): void { - return updateManyMutably([update], state) - } - - function updateManyMutably( - updates: ReadonlyArray>, - state: R - ): void { - const newKeys: { [id: string]: EntityId } = {} - - const updatesPerEntity: { [id: string]: Update } = {} - - updates.forEach((update) => { - // Only apply updates to entities that currently exist - if (update.id in state.entities) { - // If there are multiple updates to one entity, merge them together - updatesPerEntity[update.id] = { - id: update.id, - // Spreads ignore falsy values, so this works even if there isn't - // an existing update already at this key - changes: { - ...(updatesPerEntity[update.id] - ? updatesPerEntity[update.id].changes - : null), - ...update.changes, - }, - } - } - }) - - updates = Object.values(updatesPerEntity) - - const didMutateEntities = updates.length > 0 - - if (didMutateEntities) { - const didMutateIds = - updates.filter((update) => takeNewKey(newKeys, update, state)).length > - 0 - - if (didMutateIds) { - state.ids = Object.keys(state.entities) - } - } - } - - function upsertOneMutably(entity: T, state: R): void { - return upsertManyMutably([entity], state) - } - - function upsertManyMutably( - newEntities: readonly T[] | Record, - state: R - ): void { - const [added, updated] = splitAddedUpdatedEntities( - newEntities, - selectId, - state - ) - - updateManyMutably(updated, state) - addManyMutably(added, state) - } - - return { - removeAll: createSingleArgumentStateOperator(removeAllMutably), - addOne: createStateOperator(addOneMutably), - addMany: createStateOperator(addManyMutably), - setOne: createStateOperator(setOneMutably), - setMany: createStateOperator(setManyMutably), - setAll: createStateOperator(setAllMutably), - updateOne: createStateOperator(updateOneMutably), - updateMany: createStateOperator(updateManyMutably), - upsertOne: createStateOperator(upsertOneMutably), - upsertMany: createStateOperator(upsertManyMutably), - removeOne: createStateOperator(removeOneMutably), - removeMany: createStateOperator(removeManyMutably), - } -} +import type { Draft } from 'immer' +import type { + EntityState, + EntityStateAdapter, + IdSelector, + Update, + EntityId, + DraftableEntityState, + DraftableIdSelector, +} from './models' +import { + createStateOperator, + createSingleArgumentStateOperator, +} from './state_adapter' +import { + selectIdValue, + ensureEntitiesArray, + splitAddedUpdatedEntities, +} from './utils' + +export function createUnsortedStateAdapter( + selectId: DraftableIdSelector +): EntityStateAdapter { + type R = DraftableEntityState + + function addOneMutably(entity: T, state: R): void { + const key = selectIdValue(entity, selectId) + + if (key in state.entities) { + return + } + + state.ids.push(key) + state.entities[key] = entity + } + + function addManyMutably( + newEntities: readonly T[] | Record, + state: R + ): void { + newEntities = ensureEntitiesArray(newEntities) + + for (const entity of newEntities) { + addOneMutably(entity, state) + } + } + + function setOneMutably(entity: T, state: R): void { + const key = selectIdValue(entity, selectId) + if (!(key in state.entities)) { + state.ids.push(key) + } + state.entities[key] = entity + } + + function setManyMutably( + newEntities: readonly T[] | Record, + state: R + ): void { + newEntities = ensureEntitiesArray(newEntities) + for (const entity of newEntities) { + setOneMutably(entity, state) + } + } + + function setAllMutably( + newEntities: readonly T[] | Record, + state: R + ): void { + newEntities = ensureEntitiesArray(newEntities) + + state.ids = [] + state.entities = {} + + addManyMutably(newEntities, state) + } + + function removeOneMutably(key: EntityId, state: R): void { + return removeManyMutably([key], state) + } + + function removeManyMutably(keys: readonly EntityId[], state: R): void { + let didMutate = false + + keys.forEach((key) => { + if (key in state.entities) { + delete state.entities[key] + didMutate = true + } + }) + + if (didMutate) { + state.ids = state.ids.filter((id) => id in state.entities) + } + } + + function removeAllMutably(state: R): void { + Object.assign(state, { + ids: [], + entities: {}, + }) + } + + function takeNewKey( + keys: { [id: string]: EntityId }, + update: Update, + state: R + ): boolean { + const original = state.entities[update.id] + const updated: T | Draft = Object.assign({}, original, update.changes) + const newKey = selectIdValue(updated, selectId) + const hasNewKey = newKey !== update.id + + if (hasNewKey) { + keys[update.id] = newKey + delete state.entities[update.id] + } + + state.entities[newKey] = updated + + return hasNewKey + } + + function updateOneMutably(update: Update, state: R): void { + return updateManyMutably([update], state) + } + + function updateManyMutably( + updates: ReadonlyArray>, + state: R + ): void { + const newKeys: { [id: string]: EntityId } = {} + + const updatesPerEntity: { [id: string]: Update } = {} + + updates.forEach((update) => { + // Only apply updates to entities that currently exist + if (update.id in state.entities) { + // If there are multiple updates to one entity, merge them together + updatesPerEntity[update.id] = { + id: update.id, + // Spreads ignore falsy values, so this works even if there isn't + // an existing update already at this key + changes: { + ...(updatesPerEntity[update.id] + ? updatesPerEntity[update.id].changes + : null), + ...update.changes, + }, + } + } + }) + + updates = Object.values(updatesPerEntity) + + const didMutateEntities = updates.length > 0 + + if (didMutateEntities) { + const didMutateIds = + updates.filter((update) => takeNewKey(newKeys, update, state)).length > + 0 + + if (didMutateIds) { + state.ids = Object.keys(state.entities) + } + } + } + + function upsertOneMutably(entity: T, state: R): void { + return upsertManyMutably([entity], state) + } + + function upsertManyMutably( + newEntities: readonly T[] | Record, + state: R + ): void { + const [added, updated] = splitAddedUpdatedEntities( + newEntities, + selectId, + state + ) + + updateManyMutably(updated, state) + addManyMutably(added, state) + } + + return { + removeAll: createSingleArgumentStateOperator(removeAllMutably), + addOne: createStateOperator(addOneMutably), + addMany: createStateOperator(addManyMutably), + setOne: createStateOperator(setOneMutably), + setMany: createStateOperator(setManyMutably), + setAll: createStateOperator(setAllMutably), + updateOne: createStateOperator(updateOneMutably), + updateMany: createStateOperator(updateManyMutably), + upsertOne: createStateOperator(upsertOneMutably), + upsertMany: createStateOperator(upsertManyMutably), + removeOne: createStateOperator(removeOneMutably), + removeMany: createStateOperator(removeManyMutably), + } +} diff --git a/packages/toolkit/src/entities/utils.ts b/packages/toolkit/src/entities/utils.ts index 5a4be0fca5..833cc7b9a4 100644 --- a/packages/toolkit/src/entities/utils.ts +++ b/packages/toolkit/src/entities/utils.ts @@ -1,49 +1,49 @@ -import type { EntityState, IdSelector, Update, EntityId } from './models' - -export function selectIdValue(entity: T, selectId: IdSelector) { - const key = selectId(entity) - - if (process.env.NODE_ENV !== 'production' && key === undefined) { - console.warn( - 'The entity passed to the `selectId` implementation returned undefined.', - 'You should probably provide your own `selectId` implementation.', - 'The entity that was passed:', - entity, - 'The `selectId` implementation:', - selectId.toString() - ) - } - - return key -} - -export function ensureEntitiesArray( - entities: readonly T[] | Record -): readonly T[] { - if (!Array.isArray(entities)) { - entities = Object.values(entities) - } - - return entities -} - -export function splitAddedUpdatedEntities( - newEntities: readonly T[] | Record, - selectId: IdSelector, - state: EntityState -): [T[], Update[]] { - newEntities = ensureEntitiesArray(newEntities) - - const added: T[] = [] - const updated: Update[] = [] - - for (const entity of newEntities) { - const id = selectIdValue(entity, selectId) - if (id in state.entities) { - updated.push({ id, changes: entity }) - } else { - added.push(entity) - } - } - return [added, updated] -} +import type { EntityState, IdSelector, Update, EntityId, DraftableEntityState } from './models' + +export function selectIdValue(entity: T, selectId: IdSelector) { + const key = selectId(entity) + + if (process.env.NODE_ENV !== 'production' && key === undefined) { + console.warn( + 'The entity passed to the `selectId` implementation returned undefined.', + 'You should probably provide your own `selectId` implementation.', + 'The entity that was passed:', + entity, + 'The `selectId` implementation:', + selectId.toString() + ) + } + + return key +} + +export function ensureEntitiesArray( + entities: readonly T[] | Record +): readonly T[] { + if (!Array.isArray(entities)) { + entities = Object.values(entities) + } + + return entities +} + +export function splitAddedUpdatedEntities( + newEntities: readonly T[] | Record, + selectId: IdSelector, + state: DraftableEntityState +): [T[], Update[]] { + newEntities = ensureEntitiesArray(newEntities) + + const added: T[] = [] + const updated: Update[] = [] + + for (const entity of newEntities) { + const id = selectIdValue(entity, selectId) + if (id in state.entities) { + updated.push({ id, changes: entity }) + } else { + added.push(entity) + } + } + return [added, updated] +} From dff6fe0a3cf21bc61083445babd5982d20773966 Mon Sep 17 00:00:00 2001 From: Olesj-Bilous Date: Wed, 23 Aug 2023 15:03:41 +0200 Subject: [PATCH 252/412] remove ts-ignore from createStateOperator --- packages/toolkit/src/entities/state_adapter.ts | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/packages/toolkit/src/entities/state_adapter.ts b/packages/toolkit/src/entities/state_adapter.ts index de62323e54..763d088693 100644 --- a/packages/toolkit/src/entities/state_adapter.ts +++ b/packages/toolkit/src/entities/state_adapter.ts @@ -50,11 +50,8 @@ export function createStateOperator( // since it's a draft, we'll just return it return state - } else { - // @ts-ignore createNextState() produces an Immutable> rather - // than an Immutable, and TypeScript cannot find out how to reconcile - // these two types. - return createNextState(state, runMutator) } + + return createNextState(state, runMutator) } } From 470d7e964e543095d414a64483b4b4f5e48b24a1 Mon Sep 17 00:00:00 2001 From: Olesj-Bilous Date: Wed, 23 Aug 2023 16:04:38 +0200 Subject: [PATCH 253/412] entity slice enhancer test --- .../tests/entity_slice_enhancer.test.ts | 36 +++++++++++++++++++ 1 file changed, 36 insertions(+) create mode 100644 packages/toolkit/src/entities/tests/entity_slice_enhancer.test.ts diff --git a/packages/toolkit/src/entities/tests/entity_slice_enhancer.test.ts b/packages/toolkit/src/entities/tests/entity_slice_enhancer.test.ts new file mode 100644 index 0000000000..bc367b473d --- /dev/null +++ b/packages/toolkit/src/entities/tests/entity_slice_enhancer.test.ts @@ -0,0 +1,36 @@ +import { createEntityAdapter, createSlice } from "../.."; +import type { PayloadAction, SliceCaseReducers } from "../.."; +import type { DraftableIdSelector, IdSelector } from "../models"; + +interface EntitySliceArgs { + name: string + modelReducer: SliceCaseReducers + selectId: IdSelector // unusable + selectDraftableId: DraftableIdSelector +} + +// currently this never runs, it only serves to illustrate that the containing calls are type valid +function entitySliceEnhancer({ + name, + modelReducer, + selectId: undraftableSelectId, + selectDraftableId +}: EntitySliceArgs) { + const modelAdapter = createEntityAdapter({ + selectId: selectDraftableId // undraftableSelectId would give an interesting error + }); + + return createSlice({ + name, + initialState: modelAdapter.getInitialState(), + reducers: { + oneAdded(state, action: PayloadAction) { + modelAdapter.addOne( + state, + action.payload + ) + }, + ...modelReducer + } + }) +} From 91779f898f500de74749a78378819b5f9d2dd99c Mon Sep 17 00:00:00 2001 From: Olesj-Bilous Date: Wed, 23 Aug 2023 16:35:33 +0200 Subject: [PATCH 254/412] entity slice enhancer test set up --- .../tests/entity_slice_enhancer.test.ts | 32 +++++++++++++------ 1 file changed, 22 insertions(+), 10 deletions(-) diff --git a/packages/toolkit/src/entities/tests/entity_slice_enhancer.test.ts b/packages/toolkit/src/entities/tests/entity_slice_enhancer.test.ts index bc367b473d..03cc615c3f 100644 --- a/packages/toolkit/src/entities/tests/entity_slice_enhancer.test.ts +++ b/packages/toolkit/src/entities/tests/entity_slice_enhancer.test.ts @@ -1,23 +1,35 @@ import { createEntityAdapter, createSlice } from "../.."; -import type { PayloadAction, SliceCaseReducers } from "../.."; -import type { DraftableIdSelector, IdSelector } from "../models"; +import type { PayloadAction, Slice, SliceCaseReducers } from "../.."; +import type { DraftableIdSelector, EntityAdapter, EntityState, IdSelector } from "../models"; +import type { BookModel } from "./fixtures/book"; + +describe('Entity Slice Enhancer', () => { + let slice: Slice>; + + beforeEach(() => { + const indieSlice = entitySliceEnhancer({ + name: 'book', + selectDraftableId: (book: BookModel) => book.id + }) + slice = indieSlice + }) +}) interface EntitySliceArgs { name: string - modelReducer: SliceCaseReducers - selectId: IdSelector // unusable - selectDraftableId: DraftableIdSelector + selectId?: IdSelector // unusable + selectDraftableId?: DraftableIdSelector + modelReducer?: SliceCaseReducers } -// currently this never runs, it only serves to illustrate that the containing calls are type valid function entitySliceEnhancer({ name, - modelReducer, - selectId: undraftableSelectId, - selectDraftableId + selectId: unusableSelectId, + selectDraftableId, + modelReducer }: EntitySliceArgs) { const modelAdapter = createEntityAdapter({ - selectId: selectDraftableId // undraftableSelectId would give an interesting error + selectId: selectDraftableId // unusableSelectId would give an interesting error }); return createSlice({ From bc2132438dd2073847148ebcc589c160a21ecd71 Mon Sep 17 00:00:00 2001 From: Olesj-Bilous Date: Wed, 23 Aug 2023 17:40:35 +0200 Subject: [PATCH 255/412] include actual entity slice enhancer test --- .../src/entities/tests/entity_slice_enhancer.test.ts | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/packages/toolkit/src/entities/tests/entity_slice_enhancer.test.ts b/packages/toolkit/src/entities/tests/entity_slice_enhancer.test.ts index 03cc615c3f..927ffe4704 100644 --- a/packages/toolkit/src/entities/tests/entity_slice_enhancer.test.ts +++ b/packages/toolkit/src/entities/tests/entity_slice_enhancer.test.ts @@ -13,6 +13,15 @@ describe('Entity Slice Enhancer', () => { }) slice = indieSlice }) + + it('exposes oneAdded', () => { + const oneAdded = slice.reducer(undefined, slice.actions.oneAdded({ + id: '0', + title: 'Der Steppenwolf', + author: 'Herman Hesse' + })) + expect(oneAdded.entities['0']?.id).toBe('0') + }) }) interface EntitySliceArgs { @@ -28,7 +37,7 @@ function entitySliceEnhancer({ selectDraftableId, modelReducer }: EntitySliceArgs) { - const modelAdapter = createEntityAdapter({ + const modelAdapter = createEntityAdapter({ selectId: selectDraftableId // unusableSelectId would give an interesting error }); From 9b2a56f26cc02cd26a6d9341f7dfcc8c292f3a05 Mon Sep 17 00:00:00 2001 From: Olesj-Bilous Date: Fri, 25 Aug 2023 15:56:44 +0200 Subject: [PATCH 256/412] more specifically typed isDraftTyped arg --- packages/toolkit/src/entities/state_adapter.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/toolkit/src/entities/state_adapter.ts b/packages/toolkit/src/entities/state_adapter.ts index 763d088693..b86cb3ba02 100644 --- a/packages/toolkit/src/entities/state_adapter.ts +++ b/packages/toolkit/src/entities/state_adapter.ts @@ -5,7 +5,7 @@ import type { PayloadAction } from '../createAction' import { isFSA } from '../createAction' import { IsAny } from '../tsHelpers' -export const isDraftTyped = isDraft as (value: any) => value is Draft +export const isDraftTyped = isDraft as (value: T | Draft) => value is Draft export function createSingleArgumentStateOperator( mutator: (state: DraftableEntityState) => void From bbaf037e14c78fb53f6d9e19ef2bd950c6352d0c Mon Sep 17 00:00:00 2001 From: Mark Erikson Date: Wed, 23 Aug 2023 20:07:25 -0400 Subject: [PATCH 257/412] Update tsup --- packages/toolkit/package.json | 2 +- yarn.lock | 41 ++++++++++++++++++++++++++++------- 2 files changed, 34 insertions(+), 9 deletions(-) diff --git a/packages/toolkit/package.json b/packages/toolkit/package.json index ccacc82946..f5ad41c2f8 100644 --- a/packages/toolkit/package.json +++ b/packages/toolkit/package.json @@ -86,7 +86,7 @@ "rimraf": "^3.0.2", "size-limit": "^4.11.0", "tslib": "^1.10.0", - "tsup": "^6.7.0", + "tsup": "^7.2.0", "tsx": "^3.12.2", "typescript": "~4.9", "vitest": "^0.30.1", diff --git a/yarn.lock b/yarn.lock index d5208aed54..c9e539d492 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6790,7 +6790,7 @@ __metadata: rimraf: ^3.0.2 size-limit: ^4.11.0 tslib: ^1.10.0 - tsup: ^6.7.0 + tsup: ^7.2.0 tsx: ^3.12.2 typescript: ~4.9 vitest: ^0.30.1 @@ -22677,7 +22677,7 @@ fsevents@^1.2.7: languageName: node linkType: hard -"postcss-load-config@npm:^3.0.0, postcss-load-config@npm:^3.0.1, postcss-load-config@npm:^3.1.4": +"postcss-load-config@npm:^3.0.0, postcss-load-config@npm:^3.1.4": version: 3.1.4 resolution: "postcss-load-config@npm:3.1.4" dependencies: @@ -22695,6 +22695,24 @@ fsevents@^1.2.7: languageName: node linkType: hard +"postcss-load-config@npm:^4.0.1": + version: 4.0.1 + resolution: "postcss-load-config@npm:4.0.1" + dependencies: + lilconfig: ^2.0.5 + yaml: ^2.1.1 + peerDependencies: + postcss: ">=8.0.9" + ts-node: ">=9.0.0" + peerDependenciesMeta: + postcss: + optional: true + ts-node: + optional: true + checksum: b61f890499ed7dcda1e36c20a9582b17d745bad5e2b2c7bc96942465e406bc43ae03f270c08e60d1e29dab1ee50cb26970b5eb20c9aae30e066e20bd607ae4e4 + languageName: node + linkType: hard + "postcss-loader@npm:^6.2.1": version: 6.2.1 resolution: "postcss-loader@npm:6.2.1" @@ -28113,19 +28131,19 @@ fsevents@^1.2.7: languageName: node linkType: hard -"tsup@npm:^6.7.0": - version: 6.7.0 - resolution: "tsup@npm:6.7.0" +"tsup@npm:^7.2.0": + version: 7.2.0 + resolution: "tsup@npm:7.2.0" dependencies: bundle-require: ^4.0.0 cac: ^6.7.12 chokidar: ^3.5.1 debug: ^4.3.1 - esbuild: ^0.17.6 + esbuild: ^0.18.2 execa: ^5.0.0 globby: ^11.0.3 joycon: ^3.0.1 - postcss-load-config: ^3.0.1 + postcss-load-config: ^4.0.1 resolve-from: ^5.0.0 rollup: ^3.2.5 source-map: 0.8.0-beta.0 @@ -28145,7 +28163,7 @@ fsevents@^1.2.7: bin: tsup: dist/cli-default.js tsup-node: dist/cli-node.js - checksum: 91ff179f0b9828a6880b6decaa8603fd7af0311f46a38d3a93647a2497298750d676810aeff533a335443a01a7b340dbba7c76523bcd7a87d7b05b7677742901 + checksum: 94feae12b0a0dd0eaa3ed1c412d2bc51d7491ff91abc61e4198495dcb612a848a9fd346fbb668a63b98534fc6c2569ab3aba7ea95ad8db5eaf29c4a4885c2313 languageName: node linkType: hard @@ -30364,6 +30382,13 @@ fsevents@^1.2.7: languageName: node linkType: hard +"yaml@npm:^2.1.1": + version: 2.3.1 + resolution: "yaml@npm:2.3.1" + checksum: 2c7bc9a7cd4c9f40d3b0b0a98e370781b68b8b7c4515720869aced2b00d92f5da1762b4ffa947f9e795d6cd6b19f410bd4d15fdd38aca7bd96df59bd9486fb54 + languageName: node + linkType: hard + "yargs-parser@npm:20.2.9, yargs-parser@npm:20.x, yargs-parser@npm:^20.2.2": version: 20.2.9 resolution: "yargs-parser@npm:20.2.9" From c4e0354526f993f4a57a253882a6024a3f9fb874 Mon Sep 17 00:00:00 2001 From: Mark Erikson Date: Sat, 26 Aug 2023 15:10:09 -0400 Subject: [PATCH 258/412] Switch to ATTW CLI --- .github/workflows/tests.yml | 35 +- .../publish-ci/are-the-types-wrong/.gitignore | 35 - ...typeswrong-core-npm-0.0.4-edb717a66b.patch | 12 - .../publish-ci/are-the-types-wrong/main.tsx | 306 --- .../are-the-types-wrong/package.json | 26 - .../are-the-types-wrong/tsconfig.json | 10 - .../publish-ci/are-the-types-wrong/yarn.lock | 2018 ----------------- 7 files changed, 25 insertions(+), 2417 deletions(-) delete mode 100644 examples/publish-ci/are-the-types-wrong/.gitignore delete mode 100644 examples/publish-ci/are-the-types-wrong/.yarn/patches/@arethetypeswrong-core-npm-0.0.4-edb717a66b.patch delete mode 100644 examples/publish-ci/are-the-types-wrong/main.tsx delete mode 100644 examples/publish-ci/are-the-types-wrong/package.json delete mode 100644 examples/publish-ci/are-the-types-wrong/tsconfig.json delete mode 100644 examples/publish-ci/are-the-types-wrong/yarn.lock diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 3ecd65c457..7008935a2a 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -149,16 +149,7 @@ jobs: fail-fast: false matrix: node: ['16.x'] - example: - [ - 'cra4', - 'cra5', - 'next', - 'vite', - 'node-standard', - 'node-esm', - 'are-the-types-wrong', - ] + example: ['cra4', 'cra5', 'next', 'vite', 'node-standard', 'node-esm'] defaults: run: working-directory: ./examples/publish-ci/${{ matrix.example }} @@ -203,3 +194,27 @@ jobs: # Ignore "FalseCJS" errors in the `attw` job run: yarn test -n FalseCJS if: matrix.example == 'are-the-types-wrong' + + are-the-types-wrong: + name: Check package config with are-the-types-wrong + + needs: [build] + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + node: ['16.x'] + steps: + - name: Checkout repo + uses: actions/checkout@v3 + + - uses: actions/download-artifact@v2 + with: + name: package + path: packages/toolkit + + - name: show folder + run: ls -l . + + - name: Run are-the-types-wrong + run: npx @arethetypeswrong/cli ./package.tgz --format table diff --git a/examples/publish-ci/are-the-types-wrong/.gitignore b/examples/publish-ci/are-the-types-wrong/.gitignore deleted file mode 100644 index 3ff1c7836c..0000000000 --- a/examples/publish-ci/are-the-types-wrong/.gitignore +++ /dev/null @@ -1,35 +0,0 @@ -# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. - -# dependencies -/node_modules -/.pnp -.pnp.js - -# testing -/coverage - -# production -/build - -# misc -.DS_Store -.env.local -.env.development.local -.env.test.local -.env.production.local - -npm-debug.log* -yarn-debug.log* -yarn-error.log* - -typesversions -.cache -.yarnrc -.yarn/* -!.yarn/patches -!.yarn/releases -!.yarn/plugins -!.yarn/sdks -!.yarn/versions -.pnp.* -*.tgz diff --git a/examples/publish-ci/are-the-types-wrong/.yarn/patches/@arethetypeswrong-core-npm-0.0.4-edb717a66b.patch b/examples/publish-ci/are-the-types-wrong/.yarn/patches/@arethetypeswrong-core-npm-0.0.4-edb717a66b.patch deleted file mode 100644 index a60ac2cb08..0000000000 --- a/examples/publish-ci/are-the-types-wrong/.yarn/patches/@arethetypeswrong-core-npm-0.0.4-edb717a66b.patch +++ /dev/null @@ -1,12 +0,0 @@ -diff --git a/package.json b/package.json -index 60791b6ccd3575279eddef2ac795802bd5044abd..41be518d2f21a659ca71095850cb18264a814f8d 100644 ---- a/package.json -+++ b/package.json -@@ -25,6 +25,7 @@ - "prepublishOnly": "npm run tsc && npm run test" - }, - "type": "module", -+ "types": "./dist/index.d.ts", - "exports": { - ".": { - "development": "./src/index.ts", diff --git a/examples/publish-ci/are-the-types-wrong/main.tsx b/examples/publish-ci/are-the-types-wrong/main.tsx deleted file mode 100644 index a67009a0c3..0000000000 --- a/examples/publish-ci/are-the-types-wrong/main.tsx +++ /dev/null @@ -1,306 +0,0 @@ -import path from 'path' -import fs from 'fs' - -import { fileURLToPath } from 'node:url' -import type { - Analysis, - ProblemSummary, - Problem, - ResolutionKind, - ProblemKind, -} from '@arethetypeswrong/core' -import { - checkTgz, - summarizeProblems, - getProblems, -} from '@arethetypeswrong/core' -import React from 'react' -import { render, Text, Box, Static } from 'ink' -import yargs from 'yargs/yargs' - -const allResolutionKinds: ResolutionKind[] = [ - 'node10', - 'node16-cjs', - 'node16-esm', - 'bundler', -] - -const problemEmoji: Record = { - Wildcard: '❓', - NoResolution: '💀', - UntypedResolution: '❌', - FalseCJS: '🎭', - FalseESM: '👺', - CJSResolvesToESM: '⚠️', - FallbackCondition: '🐛', - CJSOnlyExportsDefault: '🤨', - FalseExportDefault: '❗️', - UnexpectedESMSyntax: '🚭', - UnexpectedCJSSyntax: '🚱', -} - -const problemShortDescriptions: Record = { - Wildcard: `${problemEmoji.Wildcard} Unable to check`, - NoResolution: `${problemEmoji.NoResolution} Failed to resolve`, - UntypedResolution: `${problemEmoji.UntypedResolution} No types`, - FalseCJS: `${problemEmoji.FalseCJS} Masquerading as CJS`, - FalseESM: `${problemEmoji.FalseESM} Masquerading as ESM`, - CJSResolvesToESM: `${problemEmoji.CJSResolvesToESM} ESM (dynamic import only)`, - FallbackCondition: `${problemEmoji.FallbackCondition} Used fallback condition`, - CJSOnlyExportsDefault: `${problemEmoji.CJSOnlyExportsDefault} CJS default export`, - FalseExportDefault: `${problemEmoji.FalseExportDefault} Incorrect default export`, - UnexpectedESMSyntax: `${problemEmoji.UnexpectedESMSyntax} Unexpected ESM syntax`, - UnexpectedCJSSyntax: `${problemEmoji.UnexpectedCJSSyntax} Unexpected CJS syntax`, -} - -const resolutionKinds: Record = { - node10: 'node10', - 'node16-cjs': 'node16 (from CJS)', - 'node16-esm': 'node16 (from ESM)', - bundler: 'bundler', -} - -const moduleKinds = { - 1: '(CJS)', - 99: '(ESM)', - '': '', -} - -const __filename = fileURLToPath(import.meta.url) -const __dirname = path.dirname(__filename) - -interface Checks { - analysis: Analysis - problemSummaries?: ProblemSummary[] - problems?: Problem[] -} - -const rtkPackagePath = path.join(__dirname, './package.tgz') - -const rtkPackageTgzBytes = fs.readFileSync(rtkPackagePath) - -function Header({ text, width }: { text: string; width: number | string }) { - return ( - - {text} - - ) -} - -function Traces({ - analysis, - subpaths, -}: { - analysis: Analysis - subpaths: string[] -}) { - if (!('entrypointResolutions' in analysis)) { - return null - } - - return ( - - {subpaths.map((subpath) => { - const resolutionDetails = Object.entries( - analysis.entrypointResolutions[subpath] - ) - return ( - - - Traces for Subpath: {subpath} - - {resolutionDetails.map(([resolutionKind, details]) => { - return ( - - {resolutionKind} Traces: - - {details.trace.map((traceLine, i) => { - return ( - - {traceLine} - - ) - })} - - - ) - })} - - ) - })} - - ) -} - -function ChecksTable(props: { checks?: Checks }) { - if (!props.checks || !props.checks.analysis.containsTypes) { - return null - } - - const { analysis, problems, problemSummaries } = props.checks - const subpaths = Object.keys(analysis.entrypointResolutions).filter( - (key) => !key.includes('package.json') - ) - const entrypoints = subpaths.map((s) => - s === '.' - ? analysis.packageName - : `${analysis.packageName}/${s.substring(2)}` - ) - - const numColumns = entrypoints.length + 1 - - const columnWidth = `${100 / numColumns}%` - - return ( - - -

- {entrypoints.map((text) => { - return
- })} - - {allResolutionKinds.map((resolutionKind) => { - return ( - - - {resolutionKinds[resolutionKind]} - - {subpaths.map((subpath) => { - const problemsForCell = problems?.filter( - (problem) => - problem.entrypoint === subpath && - problem.resolutionKind === resolutionKind - ) - const resolution = - analysis.entrypointResolutions[subpath][resolutionKind] - .resolution - - let content: React.ReactNode - - if (problemsForCell?.length) { - content = ( - - {problemsForCell.map((problem) => { - return ( - - {problemShortDescriptions[problem.kind]} - - ) - })} - - ) - } else if (resolution?.isJson) { - content = ✅ (JSON) - } else { - content = ( - - {'✅ ' + - moduleKinds[resolution?.moduleKind?.detectedKind || '']} - - ) - } - return ( - - {content} - - ) - })} - - ) - })} - {problemSummaries?.map((summary) => { - return ( - - - {summary.kind}: {summary.title} - - {summary.messages.map((message) => { - return ( - {message.messageText} - ) - })} - - ) - })} - - - ) -} - -const { argv } = yargs(process.argv).option('nonErrorProblems', { - alias: 'n', - type: 'array', - description: 'Do not treat these problems as errors for CLI exit codes', - choices: Object.keys(problemShortDescriptions) as ProblemKind[], -}) - -interface CLIOptions { - nonErrorProblems?: ProblemKind[] -} - -;(async function main({ nonErrorProblems = [] }: CLIOptions) { - const analysis = await checkTgz(rtkPackageTgzBytes) - - const checks: Checks = { - analysis, - problems: undefined, - problemSummaries: undefined, - } - if ('entrypointResolutions' in analysis) { - const problems = analysis.containsTypes ? getProblems(analysis) : undefined - checks.problems = problems - - if (problems) { - const problemSummaries = analysis.containsTypes - ? summarizeProblems(problems, analysis) - : undefined - checks.problemSummaries = problemSummaries - } - } - - // Ink will duplicate all of its output if it is longer than the terminal height. - // Known bug with the underlying rendering: https://github.com/vadimdemedes/ink/issues/48 - // Solution is to mark the entire content as "static" so it won't get updated, but flushed. - render( - - {(checks: Checks, index: number) => { - return - }} - - ) - - const { problems = [] } = checks - - console.log('\n\nProblem results:') - - if (nonErrorProblems.length) { - console.log( - 'Treating these problem categories as non-errors: ', - nonErrorProblems - ) - } - - const filteredProblems = problems.filter( - (p) => !nonErrorProblems.includes(p.kind) - ) - - if (filteredProblems.length) { - console.error( - 'Remaining problems: ', - filteredProblems.map((p) => p.kind) - ) - } else { - console.log('No errors found!') - } - - const exitCode = filteredProblems.length - process.exit(exitCode) -})({ - nonErrorProblems: argv.nonErrorProblems as ProblemKind[], -}) diff --git a/examples/publish-ci/are-the-types-wrong/package.json b/examples/publish-ci/are-the-types-wrong/package.json deleted file mode 100644 index f1b09a5de1..0000000000 --- a/examples/publish-ci/are-the-types-wrong/package.json +++ /dev/null @@ -1,26 +0,0 @@ -{ - "name": "are-the-types-wrong", - "packageManager": "yarn@3.2.4", - "type": "module", - "scripts": { - "build": "echo Nothing to build", - "test": "yarn tsx main.tsx" - }, - "dependencies": { - "@reduxjs/toolkit": "^1.9.3", - "@tanstack/react-table": "^8.7.9", - "ink": "^4.0.0", - "object-hash": "^3.0.0", - "react": "^18.2.0" - }, - "devDependencies": { - "@arethetypeswrong/core": "^0.0.4", - "@types/react": "^18.0.28", - "shelljs": "^0.8.5", - "tsx": "^3.12.5", - "yargs": "^17.7.1" - }, - "resolutions": { - "@arethetypeswrong/core@^0.0.4": "patch:@arethetypeswrong/core@npm%3A0.0.4#./.yarn/patches/@arethetypeswrong-core-npm-0.0.4-edb717a66b.patch" - } -} diff --git a/examples/publish-ci/are-the-types-wrong/tsconfig.json b/examples/publish-ci/are-the-types-wrong/tsconfig.json deleted file mode 100644 index 548aef7069..0000000000 --- a/examples/publish-ci/are-the-types-wrong/tsconfig.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "compilerOptions": { - "target": "ESNext", - "module": "esnext", - "moduleResolution": "node", - "esModuleInterop": true, - - "jsx": "react", - } -} \ No newline at end of file diff --git a/examples/publish-ci/are-the-types-wrong/yarn.lock b/examples/publish-ci/are-the-types-wrong/yarn.lock deleted file mode 100644 index e4763066b4..0000000000 --- a/examples/publish-ci/are-the-types-wrong/yarn.lock +++ /dev/null @@ -1,2018 +0,0 @@ -# This file is generated by running "yarn install" inside your project. -# Manual changes might be lost - proceed with caution! - -__metadata: - version: 6 - cacheKey: 8 - -"@andrewbranch/untar.js@npm:^1.0.0": - version: 1.0.2 - resolution: "@andrewbranch/untar.js@npm:1.0.2" - checksum: 6e07ed53599607f3f8df5f0c1c37592713c49a61a24697741dfd2c5a195bffe56e36ec1ef3fe3b049c3604ea7ff6b7d8b1a0010122033c2b8896cef7ea1fc747 - languageName: node - linkType: hard - -"@arethetypeswrong/core@npm:0.0.4": - version: 0.0.4 - resolution: "@arethetypeswrong/core@npm:0.0.4" - dependencies: - "@andrewbranch/untar.js": ^1.0.0 - fetch-ponyfill: ^7.1.0 - fflate: ^0.7.4 - typescript: ^5.0.0-dev.20230207 - checksum: dc9ea074e5f2e7d2df7d700cb7c727892204df5939008a059ec24247779ffc5cde57eec8dded21a852a3fa76b4058cfcaee7fc7789d265754c39c3f45394277e - languageName: node - linkType: hard - -"@arethetypeswrong/core@patch:@arethetypeswrong/core@npm%3A0.0.4#./.yarn/patches/@arethetypeswrong-core-npm-0.0.4-edb717a66b.patch::locator=are-the-types-wrong%40workspace%3A.": - version: 0.0.4 - resolution: "@arethetypeswrong/core@patch:@arethetypeswrong/core@npm%3A0.0.4#./.yarn/patches/@arethetypeswrong-core-npm-0.0.4-edb717a66b.patch::version=0.0.4&hash=d09a56&locator=are-the-types-wrong%40workspace%3A." - dependencies: - "@andrewbranch/untar.js": ^1.0.0 - fetch-ponyfill: ^7.1.0 - fflate: ^0.7.4 - typescript: ^5.0.0-dev.20230207 - checksum: 7d0034bf02cfd1473cbf6beeaabbb84057be26068cfdd2ed351cacbd8b8945dc8403965b3140524c7f3c41d6608c01988c2396300e01436fc77ca52ebfd2359c - languageName: node - linkType: hard - -"@babel/runtime@npm:^7.9.2": - version: 7.21.0 - resolution: "@babel/runtime@npm:7.21.0" - dependencies: - regenerator-runtime: ^0.13.11 - checksum: 7b33e25bfa9e0e1b9e8828bb61b2d32bdd46b41b07ba7cb43319ad08efc6fda8eb89445193e67d6541814627df0ca59122c0ea795e412b99c5183a0540d338ab - languageName: node - linkType: hard - -"@esbuild-kit/cjs-loader@npm:^2.4.2": - version: 2.4.2 - resolution: "@esbuild-kit/cjs-loader@npm:2.4.2" - dependencies: - "@esbuild-kit/core-utils": ^3.0.0 - get-tsconfig: ^4.4.0 - checksum: e346e339bfc7eff5c52c270fd0ec06a7f2341b624adfb69f84b7d83f119c35070420906f2761a0b4604e0a0ec90e35eaf12544585476c428ed6d6ee3b250c0fe - languageName: node - linkType: hard - -"@esbuild-kit/core-utils@npm:^3.0.0": - version: 3.1.0 - resolution: "@esbuild-kit/core-utils@npm:3.1.0" - dependencies: - esbuild: ~0.17.6 - source-map-support: ^0.5.21 - checksum: d54fd5adb3ce6784d84bb025ad54ddcfbab99267071a7f65298e547f56696f0b9d0dba96c535f9678a30d4887ec71cd445fdd277d65fbec1f3b504f6808f693e - languageName: node - linkType: hard - -"@esbuild-kit/esm-loader@npm:^2.5.5": - version: 2.5.5 - resolution: "@esbuild-kit/esm-loader@npm:2.5.5" - dependencies: - "@esbuild-kit/core-utils": ^3.0.0 - get-tsconfig: ^4.4.0 - checksum: 9d4a03ffc937fbec75a8456c3d45d7cdb1a65768416791a5720081753502bc9f485ba27942a46f564b12483b140a8a46c12433a4496430d93e4513e430484ec7 - languageName: node - linkType: hard - -"@esbuild/android-arm64@npm:0.17.12": - version: 0.17.12 - resolution: "@esbuild/android-arm64@npm:0.17.12" - conditions: os=android & cpu=arm64 - languageName: node - linkType: hard - -"@esbuild/android-arm@npm:0.17.12": - version: 0.17.12 - resolution: "@esbuild/android-arm@npm:0.17.12" - conditions: os=android & cpu=arm - languageName: node - linkType: hard - -"@esbuild/android-x64@npm:0.17.12": - version: 0.17.12 - resolution: "@esbuild/android-x64@npm:0.17.12" - conditions: os=android & cpu=x64 - languageName: node - linkType: hard - -"@esbuild/darwin-arm64@npm:0.17.12": - version: 0.17.12 - resolution: "@esbuild/darwin-arm64@npm:0.17.12" - conditions: os=darwin & cpu=arm64 - languageName: node - linkType: hard - -"@esbuild/darwin-x64@npm:0.17.12": - version: 0.17.12 - resolution: "@esbuild/darwin-x64@npm:0.17.12" - conditions: os=darwin & cpu=x64 - languageName: node - linkType: hard - -"@esbuild/freebsd-arm64@npm:0.17.12": - version: 0.17.12 - resolution: "@esbuild/freebsd-arm64@npm:0.17.12" - conditions: os=freebsd & cpu=arm64 - languageName: node - linkType: hard - -"@esbuild/freebsd-x64@npm:0.17.12": - version: 0.17.12 - resolution: "@esbuild/freebsd-x64@npm:0.17.12" - conditions: os=freebsd & cpu=x64 - languageName: node - linkType: hard - -"@esbuild/linux-arm64@npm:0.17.12": - version: 0.17.12 - resolution: "@esbuild/linux-arm64@npm:0.17.12" - conditions: os=linux & cpu=arm64 - languageName: node - linkType: hard - -"@esbuild/linux-arm@npm:0.17.12": - version: 0.17.12 - resolution: "@esbuild/linux-arm@npm:0.17.12" - conditions: os=linux & cpu=arm - languageName: node - linkType: hard - -"@esbuild/linux-ia32@npm:0.17.12": - version: 0.17.12 - resolution: "@esbuild/linux-ia32@npm:0.17.12" - conditions: os=linux & cpu=ia32 - languageName: node - linkType: hard - -"@esbuild/linux-loong64@npm:0.17.12": - version: 0.17.12 - resolution: "@esbuild/linux-loong64@npm:0.17.12" - conditions: os=linux & cpu=loong64 - languageName: node - linkType: hard - -"@esbuild/linux-mips64el@npm:0.17.12": - version: 0.17.12 - resolution: "@esbuild/linux-mips64el@npm:0.17.12" - conditions: os=linux & cpu=mips64el - languageName: node - linkType: hard - -"@esbuild/linux-ppc64@npm:0.17.12": - version: 0.17.12 - resolution: "@esbuild/linux-ppc64@npm:0.17.12" - conditions: os=linux & cpu=ppc64 - languageName: node - linkType: hard - -"@esbuild/linux-riscv64@npm:0.17.12": - version: 0.17.12 - resolution: "@esbuild/linux-riscv64@npm:0.17.12" - conditions: os=linux & cpu=riscv64 - languageName: node - linkType: hard - -"@esbuild/linux-s390x@npm:0.17.12": - version: 0.17.12 - resolution: "@esbuild/linux-s390x@npm:0.17.12" - conditions: os=linux & cpu=s390x - languageName: node - linkType: hard - -"@esbuild/linux-x64@npm:0.17.12": - version: 0.17.12 - resolution: "@esbuild/linux-x64@npm:0.17.12" - conditions: os=linux & cpu=x64 - languageName: node - linkType: hard - -"@esbuild/netbsd-x64@npm:0.17.12": - version: 0.17.12 - resolution: "@esbuild/netbsd-x64@npm:0.17.12" - conditions: os=netbsd & cpu=x64 - languageName: node - linkType: hard - -"@esbuild/openbsd-x64@npm:0.17.12": - version: 0.17.12 - resolution: "@esbuild/openbsd-x64@npm:0.17.12" - conditions: os=openbsd & cpu=x64 - languageName: node - linkType: hard - -"@esbuild/sunos-x64@npm:0.17.12": - version: 0.17.12 - resolution: "@esbuild/sunos-x64@npm:0.17.12" - conditions: os=sunos & cpu=x64 - languageName: node - linkType: hard - -"@esbuild/win32-arm64@npm:0.17.12": - version: 0.17.12 - resolution: "@esbuild/win32-arm64@npm:0.17.12" - conditions: os=win32 & cpu=arm64 - languageName: node - linkType: hard - -"@esbuild/win32-ia32@npm:0.17.12": - version: 0.17.12 - resolution: "@esbuild/win32-ia32@npm:0.17.12" - conditions: os=win32 & cpu=ia32 - languageName: node - linkType: hard - -"@esbuild/win32-x64@npm:0.17.12": - version: 0.17.12 - resolution: "@esbuild/win32-x64@npm:0.17.12" - conditions: os=win32 & cpu=x64 - languageName: node - linkType: hard - -"@gar/promisify@npm:^1.1.3": - version: 1.1.3 - resolution: "@gar/promisify@npm:1.1.3" - checksum: 4059f790e2d07bf3c3ff3e0fec0daa8144fe35c1f6e0111c9921bd32106adaa97a4ab096ad7dab1e28ee6a9060083c4d1a4ada42a7f5f3f7a96b8812e2b757c1 - languageName: node - linkType: hard - -"@npmcli/fs@npm:^2.1.0": - version: 2.1.2 - resolution: "@npmcli/fs@npm:2.1.2" - dependencies: - "@gar/promisify": ^1.1.3 - semver: ^7.3.5 - checksum: 405074965e72d4c9d728931b64d2d38e6ea12066d4fad651ac253d175e413c06fe4350970c783db0d749181da8fe49c42d3880bd1cbc12cd68e3a7964d820225 - languageName: node - linkType: hard - -"@npmcli/move-file@npm:^2.0.0": - version: 2.0.1 - resolution: "@npmcli/move-file@npm:2.0.1" - dependencies: - mkdirp: ^1.0.4 - rimraf: ^3.0.2 - checksum: 52dc02259d98da517fae4cb3a0a3850227bdae4939dda1980b788a7670636ca2b4a01b58df03dd5f65c1e3cb70c50fa8ce5762b582b3f499ec30ee5ce1fd9380 - languageName: node - linkType: hard - -"@reduxjs/toolkit@npm:^1.9.3": - version: 1.9.3 - resolution: "@reduxjs/toolkit@npm:1.9.3" - dependencies: - immer: ^9.0.16 - redux: ^4.2.0 - redux-thunk: ^2.4.2 - reselect: ^4.1.7 - peerDependencies: - react: ^16.9.0 || ^17.0.0 || ^18 - react-redux: ^7.2.1 || ^8.0.2 - peerDependenciesMeta: - react: - optional: true - react-redux: - optional: true - checksum: d965fc6197fd420e4b8eb714015aa908d4ea214e6e10889cf4521e64ac4f8e0a7de29ee4e8da63b291acc40ac330dd31682334090d54838add73c746dc590650 - languageName: node - linkType: hard - -"@tanstack/react-table@npm:^8.7.9": - version: 8.7.9 - resolution: "@tanstack/react-table@npm:8.7.9" - dependencies: - "@tanstack/table-core": 8.7.9 - peerDependencies: - react: ">=16" - react-dom: ">=16" - checksum: 3c704ac903405972641c9857e1466025bcdac04ee4890d64b018386cad5a778aca3a8d3f78542ae7a0ed609841d32ef3a7e563b079e87fa93b9ae8570a310499 - languageName: node - linkType: hard - -"@tanstack/table-core@npm:8.7.9": - version: 8.7.9 - resolution: "@tanstack/table-core@npm:8.7.9" - checksum: 78d2314928c29559088e4bada0248cc7f94e93756e1a2c1f37a651db30276e9ae960d647bd3a61b67b3f0f9f7e4dec5dd58eb49b8adb80ee5952ef417b6e581f - languageName: node - linkType: hard - -"@tootallnate/once@npm:2": - version: 2.0.0 - resolution: "@tootallnate/once@npm:2.0.0" - checksum: ad87447820dd3f24825d2d947ebc03072b20a42bfc96cbafec16bff8bbda6c1a81fcb0be56d5b21968560c5359a0af4038a68ba150c3e1694fe4c109a063bed8 - languageName: node - linkType: hard - -"@types/prop-types@npm:*": - version: 15.7.5 - resolution: "@types/prop-types@npm:15.7.5" - checksum: 5b43b8b15415e1f298243165f1d44390403bb2bd42e662bca3b5b5633fdd39c938e91b7fce3a9483699db0f7a715d08cef220c121f723a634972fdf596aec980 - languageName: node - linkType: hard - -"@types/react@npm:^18.0.28": - version: 18.0.28 - resolution: "@types/react@npm:18.0.28" - dependencies: - "@types/prop-types": "*" - "@types/scheduler": "*" - csstype: ^3.0.2 - checksum: e752df961105e5127652460504785897ca6e77259e0da8f233f694f9e8f451cde7fa0709d4456ade0ff600c8ce909cfe29f9b08b9c247fa9b734e126ec53edd7 - languageName: node - linkType: hard - -"@types/scheduler@npm:*": - version: 0.16.2 - resolution: "@types/scheduler@npm:0.16.2" - checksum: b6b4dcfeae6deba2e06a70941860fb1435730576d3689225a421280b7742318d1548b3d22c1f66ab68e414f346a9542f29240bc955b6332c5b11e561077583bc - languageName: node - linkType: hard - -"@types/yoga-layout@npm:1.9.2": - version: 1.9.2 - resolution: "@types/yoga-layout@npm:1.9.2" - checksum: dbc3d6ab997d50fe1fcca5dd6822982c8fe586145ab648e0e97c3bc4ebc93d0b40c9edd75febaba374d61f60c1379b639f6be652965c776a901bf1068f2eac87 - languageName: node - linkType: hard - -"abbrev@npm:^1.0.0": - version: 1.1.1 - resolution: "abbrev@npm:1.1.1" - checksum: a4a97ec07d7ea112c517036882b2ac22f3109b7b19077dc656316d07d308438aac28e4d9746dc4d84bf6b1e75b4a7b0a5f3cb30592419f128ca9a8cee3bcfa17 - languageName: node - linkType: hard - -"agent-base@npm:6, agent-base@npm:^6.0.2": - version: 6.0.2 - resolution: "agent-base@npm:6.0.2" - dependencies: - debug: 4 - checksum: f52b6872cc96fd5f622071b71ef200e01c7c4c454ee68bc9accca90c98cfb39f2810e3e9aa330435835eedc8c23f4f8a15267f67c6e245d2b33757575bdac49d - languageName: node - linkType: hard - -"agentkeepalive@npm:^4.2.1": - version: 4.3.0 - resolution: "agentkeepalive@npm:4.3.0" - dependencies: - debug: ^4.1.0 - depd: ^2.0.0 - humanize-ms: ^1.2.1 - checksum: 982453aa44c11a06826c836025e5162c846e1200adb56f2d075400da7d32d87021b3b0a58768d949d824811f5654223d5a8a3dad120921a2439625eb847c6260 - languageName: node - linkType: hard - -"aggregate-error@npm:^3.0.0": - version: 3.1.0 - resolution: "aggregate-error@npm:3.1.0" - dependencies: - clean-stack: ^2.0.0 - indent-string: ^4.0.0 - checksum: 1101a33f21baa27a2fa8e04b698271e64616b886795fd43c31068c07533c7b3facfcaf4e9e0cab3624bd88f729a592f1c901a1a229c9e490eafce411a8644b79 - languageName: node - linkType: hard - -"ansi-escapes@npm:^6.0.0": - version: 6.1.0 - resolution: "ansi-escapes@npm:6.1.0" - dependencies: - type-fest: ^3.0.0 - checksum: 7ce5d9cefd3d7345dc00161aea2ea9ad5fb3dd66658d4e8731ea047be838d755100f0823a05523d0e518e8e080746fc0a45d3ea3053099376bdd572efaedc7c1 - languageName: node - linkType: hard - -"ansi-regex@npm:^5.0.1": - version: 5.0.1 - resolution: "ansi-regex@npm:5.0.1" - checksum: 2aa4bb54caf2d622f1afdad09441695af2a83aa3fe8b8afa581d205e57ed4261c183c4d3877cee25794443fde5876417d859c108078ab788d6af7e4fe52eb66b - languageName: node - linkType: hard - -"ansi-regex@npm:^6.0.1": - version: 6.0.1 - resolution: "ansi-regex@npm:6.0.1" - checksum: 1ff8b7667cded1de4fa2c9ae283e979fc87036864317da86a2e546725f96406746411d0d85e87a2d12fa5abd715d90006de7fa4fa0477c92321ad3b4c7d4e169 - languageName: node - linkType: hard - -"ansi-styles@npm:^4.0.0": - version: 4.3.0 - resolution: "ansi-styles@npm:4.3.0" - dependencies: - color-convert: ^2.0.1 - checksum: 513b44c3b2105dd14cc42a19271e80f386466c4be574bccf60b627432f9198571ebf4ab1e4c3ba17347658f4ee1711c163d574248c0c1cdc2d5917a0ad582ec4 - languageName: node - linkType: hard - -"ansi-styles@npm:^6.0.0, ansi-styles@npm:^6.1.0": - version: 6.2.1 - resolution: "ansi-styles@npm:6.2.1" - checksum: ef940f2f0ced1a6347398da88a91da7930c33ecac3c77b72c5905f8b8fe402c52e6fde304ff5347f616e27a742da3f1dc76de98f6866c69251ad0b07a66776d9 - languageName: node - linkType: hard - -"aproba@npm:^1.0.3 || ^2.0.0": - version: 2.0.0 - resolution: "aproba@npm:2.0.0" - checksum: 5615cadcfb45289eea63f8afd064ab656006361020e1735112e346593856f87435e02d8dcc7ff0d11928bc7d425f27bc7c2a84f6c0b35ab0ff659c814c138a24 - languageName: node - linkType: hard - -"are-the-types-wrong@workspace:.": - version: 0.0.0-use.local - resolution: "are-the-types-wrong@workspace:." - dependencies: - "@arethetypeswrong/core": ^0.0.4 - "@reduxjs/toolkit": ^1.9.3 - "@tanstack/react-table": ^8.7.9 - "@types/react": ^18.0.28 - ink: ^4.0.0 - object-hash: ^3.0.0 - react: ^18.2.0 - shelljs: ^0.8.5 - tsx: ^3.12.5 - yargs: ^17.7.1 - languageName: unknown - linkType: soft - -"are-we-there-yet@npm:^3.0.0": - version: 3.0.1 - resolution: "are-we-there-yet@npm:3.0.1" - dependencies: - delegates: ^1.0.0 - readable-stream: ^3.6.0 - checksum: 52590c24860fa7173bedeb69a4c05fb573473e860197f618b9a28432ee4379049336727ae3a1f9c4cb083114601c1140cee578376164d0e651217a9843f9fe83 - languageName: node - linkType: hard - -"auto-bind@npm:^5.0.1": - version: 5.0.1 - resolution: "auto-bind@npm:5.0.1" - checksum: 44a6d8d040c4382e761922f8fa1b044e18ddefbc855fecee0c76ec6b4e6fc74adda21026bc86e190833e05f52b4b6615372c2a83a734858f8395b1e2a98b253a - languageName: node - linkType: hard - -"balanced-match@npm:^1.0.0": - version: 1.0.2 - resolution: "balanced-match@npm:1.0.2" - checksum: 9706c088a283058a8a99e0bf91b0a2f75497f185980d9ffa8b304de1d9e58ebda7c72c07ebf01dadedaac5b2907b2c6f566f660d62bd336c3468e960403b9d65 - languageName: node - linkType: hard - -"brace-expansion@npm:^1.1.7": - version: 1.1.11 - resolution: "brace-expansion@npm:1.1.11" - dependencies: - balanced-match: ^1.0.0 - concat-map: 0.0.1 - checksum: faf34a7bb0c3fcf4b59c7808bc5d2a96a40988addf2e7e09dfbb67a2251800e0d14cd2bfc1aa79174f2f5095c54ff27f46fb1289fe2d77dac755b5eb3434cc07 - languageName: node - linkType: hard - -"brace-expansion@npm:^2.0.1": - version: 2.0.1 - resolution: "brace-expansion@npm:2.0.1" - dependencies: - balanced-match: ^1.0.0 - checksum: a61e7cd2e8a8505e9f0036b3b6108ba5e926b4b55089eeb5550cd04a471fe216c96d4fe7e4c7f995c728c554ae20ddfc4244cad10aef255e72b62930afd233d1 - languageName: node - linkType: hard - -"buffer-from@npm:^1.0.0": - version: 1.1.2 - resolution: "buffer-from@npm:1.1.2" - checksum: 0448524a562b37d4d7ed9efd91685a5b77a50672c556ea254ac9a6d30e3403a517d8981f10e565db24e8339413b43c97ca2951f10e399c6125a0d8911f5679bb - languageName: node - linkType: hard - -"cacache@npm:^16.1.0": - version: 16.1.3 - resolution: "cacache@npm:16.1.3" - dependencies: - "@npmcli/fs": ^2.1.0 - "@npmcli/move-file": ^2.0.0 - chownr: ^2.0.0 - fs-minipass: ^2.1.0 - glob: ^8.0.1 - infer-owner: ^1.0.4 - lru-cache: ^7.7.1 - minipass: ^3.1.6 - minipass-collect: ^1.0.2 - minipass-flush: ^1.0.5 - minipass-pipeline: ^1.2.4 - mkdirp: ^1.0.4 - p-map: ^4.0.0 - promise-inflight: ^1.0.1 - rimraf: ^3.0.2 - ssri: ^9.0.0 - tar: ^6.1.11 - unique-filename: ^2.0.0 - checksum: d91409e6e57d7d9a3a25e5dcc589c84e75b178ae8ea7de05cbf6b783f77a5fae938f6e8fda6f5257ed70000be27a681e1e44829251bfffe4c10216002f8f14e6 - languageName: node - linkType: hard - -"chalk@npm:^5.2.0": - version: 5.2.0 - resolution: "chalk@npm:5.2.0" - checksum: 03d8060277de6cf2fd567dc25fcf770593eb5bb85f460ce443e49255a30ff1242edd0c90a06a03803b0466ff0687a939b41db1757bec987113e83de89a003caa - languageName: node - linkType: hard - -"chownr@npm:^2.0.0": - version: 2.0.0 - resolution: "chownr@npm:2.0.0" - checksum: c57cf9dd0791e2f18a5ee9c1a299ae6e801ff58fee96dc8bfd0dcb4738a6ce58dd252a3605b1c93c6418fe4f9d5093b28ffbf4d66648cb2a9c67eaef9679be2f - languageName: node - linkType: hard - -"ci-info@npm:^3.2.0": - version: 3.8.0 - resolution: "ci-info@npm:3.8.0" - checksum: d0a4d3160497cae54294974a7246202244fff031b0a6ea20dd57b10ec510aa17399c41a1b0982142c105f3255aff2173e5c0dd7302ee1b2f28ba3debda375098 - languageName: node - linkType: hard - -"clean-stack@npm:^2.0.0": - version: 2.2.0 - resolution: "clean-stack@npm:2.2.0" - checksum: 2ac8cd2b2f5ec986a3c743935ec85b07bc174d5421a5efc8017e1f146a1cf5f781ae962618f416352103b32c9cd7e203276e8c28241bbe946160cab16149fb68 - languageName: node - linkType: hard - -"cli-boxes@npm:^3.0.0": - version: 3.0.0 - resolution: "cli-boxes@npm:3.0.0" - checksum: 637d84419d293a9eac40a1c8c96a2859e7d98b24a1a317788e13c8f441be052fc899480c6acab3acc82eaf1bccda6b7542d7cdcf5c9c3cc39227175dc098d5b2 - languageName: node - linkType: hard - -"cli-cursor@npm:^4.0.0": - version: 4.0.0 - resolution: "cli-cursor@npm:4.0.0" - dependencies: - restore-cursor: ^4.0.0 - checksum: ab3f3ea2076e2176a1da29f9d64f72ec3efad51c0960898b56c8a17671365c26e67b735920530eaf7328d61f8bd41c27f46b9cf6e4e10fe2fa44b5e8c0e392cc - languageName: node - linkType: hard - -"cli-truncate@npm:^3.1.0": - version: 3.1.0 - resolution: "cli-truncate@npm:3.1.0" - dependencies: - slice-ansi: ^5.0.0 - string-width: ^5.0.0 - checksum: c3243e41974445691c63f8b405df1d5a24049dc33d324fe448dc572e561a7b772ae982692900b1a5960901cc4fc7def25a629b9c69a4208ee89d12ab3332617a - languageName: node - linkType: hard - -"cliui@npm:^8.0.1": - version: 8.0.1 - resolution: "cliui@npm:8.0.1" - dependencies: - string-width: ^4.2.0 - strip-ansi: ^6.0.1 - wrap-ansi: ^7.0.0 - checksum: 79648b3b0045f2e285b76fb2e24e207c6db44323581e421c3acbd0e86454cba1b37aea976ab50195a49e7384b871e6dfb2247ad7dec53c02454ac6497394cb56 - languageName: node - linkType: hard - -"code-excerpt@npm:^4.0.0": - version: 4.0.0 - resolution: "code-excerpt@npm:4.0.0" - dependencies: - convert-to-spaces: ^2.0.1 - checksum: d57137d8f4825879283a828cc02a1115b56858dc54ed06c625c8f67d6685d1becd2fbaa7f0ab19ecca1f5cca03f8c97bbc1f013cab40261e4d3275032e65efe9 - languageName: node - linkType: hard - -"color-convert@npm:^2.0.1": - version: 2.0.1 - resolution: "color-convert@npm:2.0.1" - dependencies: - color-name: ~1.1.4 - checksum: 79e6bdb9fd479a205c71d89574fccfb22bd9053bd98c6c4d870d65c132e5e904e6034978e55b43d69fcaa7433af2016ee203ce76eeba9cfa554b373e7f7db336 - languageName: node - linkType: hard - -"color-name@npm:~1.1.4": - version: 1.1.4 - resolution: "color-name@npm:1.1.4" - checksum: b0445859521eb4021cd0fb0cc1a75cecf67fceecae89b63f62b201cca8d345baf8b952c966862a9d9a2632987d4f6581f0ec8d957dfacece86f0a7919316f610 - languageName: node - linkType: hard - -"color-support@npm:^1.1.3": - version: 1.1.3 - resolution: "color-support@npm:1.1.3" - bin: - color-support: bin.js - checksum: 9b7356817670b9a13a26ca5af1c21615463b500783b739b7634a0c2047c16cef4b2865d7576875c31c3cddf9dd621fa19285e628f20198b233a5cfdda6d0793b - languageName: node - linkType: hard - -"concat-map@npm:0.0.1": - version: 0.0.1 - resolution: "concat-map@npm:0.0.1" - checksum: 902a9f5d8967a3e2faf138d5cb784b9979bad2e6db5357c5b21c568df4ebe62bcb15108af1b2253744844eb964fc023fbd9afbbbb6ddd0bcc204c6fb5b7bf3af - languageName: node - linkType: hard - -"console-control-strings@npm:^1.1.0": - version: 1.1.0 - resolution: "console-control-strings@npm:1.1.0" - checksum: 8755d76787f94e6cf79ce4666f0c5519906d7f5b02d4b884cf41e11dcd759ed69c57da0670afd9236d229a46e0f9cf519db0cd829c6dca820bb5a5c3def584ed - languageName: node - linkType: hard - -"convert-to-spaces@npm:^2.0.1": - version: 2.0.1 - resolution: "convert-to-spaces@npm:2.0.1" - checksum: bbb324e5916fe9866f65c0ff5f9c1ea933764d0bdb09fccaf59542e40545ed483db6b2339c6d9eb56a11965a58f1a6038f3174f0e2fb7601343c7107ca5e2751 - languageName: node - linkType: hard - -"csstype@npm:^3.0.2": - version: 3.1.1 - resolution: "csstype@npm:3.1.1" - checksum: 1f7b4f5fdd955b7444b18ebdddf3f5c699159f13e9cf8ac9027ae4a60ae226aef9bbb14a6e12ca7dba3358b007cee6354b116e720262867c398de6c955ea451d - languageName: node - linkType: hard - -"debug@npm:4, debug@npm:^4.1.0, debug@npm:^4.3.3": - version: 4.3.4 - resolution: "debug@npm:4.3.4" - dependencies: - ms: 2.1.2 - peerDependenciesMeta: - supports-color: - optional: true - checksum: 3dbad3f94ea64f34431a9cbf0bafb61853eda57bff2880036153438f50fb5a84f27683ba0d8e5426bf41a8c6ff03879488120cf5b3a761e77953169c0600a708 - languageName: node - linkType: hard - -"delegates@npm:^1.0.0": - version: 1.0.0 - resolution: "delegates@npm:1.0.0" - checksum: a51744d9b53c164ba9c0492471a1a2ffa0b6727451bdc89e31627fdf4adda9d51277cfcbfb20f0a6f08ccb3c436f341df3e92631a3440226d93a8971724771fd - languageName: node - linkType: hard - -"depd@npm:^2.0.0": - version: 2.0.0 - resolution: "depd@npm:2.0.0" - checksum: abbe19c768c97ee2eed6282d8ce3031126662252c58d711f646921c9623f9052e3e1906443066beec1095832f534e57c523b7333f8e7e0d93051ab6baef5ab3a - languageName: node - linkType: hard - -"eastasianwidth@npm:^0.2.0": - version: 0.2.0 - resolution: "eastasianwidth@npm:0.2.0" - checksum: 7d00d7cd8e49b9afa762a813faac332dee781932d6f2c848dc348939c4253f1d4564341b7af1d041853bc3f32c2ef141b58e0a4d9862c17a7f08f68df1e0f1ed - languageName: node - linkType: hard - -"emoji-regex@npm:^8.0.0": - version: 8.0.0 - resolution: "emoji-regex@npm:8.0.0" - checksum: d4c5c39d5a9868b5fa152f00cada8a936868fd3367f33f71be515ecee4c803132d11b31a6222b2571b1e5f7e13890156a94880345594d0ce7e3c9895f560f192 - languageName: node - linkType: hard - -"emoji-regex@npm:^9.2.2": - version: 9.2.2 - resolution: "emoji-regex@npm:9.2.2" - checksum: 8487182da74aabd810ac6d6f1994111dfc0e331b01271ae01ec1eb0ad7b5ecc2bbbbd2f053c05cb55a1ac30449527d819bbfbf0e3de1023db308cbcb47f86601 - languageName: node - linkType: hard - -"encoding@npm:^0.1.13": - version: 0.1.13 - resolution: "encoding@npm:0.1.13" - dependencies: - iconv-lite: ^0.6.2 - checksum: bb98632f8ffa823996e508ce6a58ffcf5856330fde839ae42c9e1f436cc3b5cc651d4aeae72222916545428e54fd0f6aa8862fd8d25bdbcc4589f1e3f3715e7f - languageName: node - linkType: hard - -"env-paths@npm:^2.2.0": - version: 2.2.1 - resolution: "env-paths@npm:2.2.1" - checksum: 65b5df55a8bab92229ab2b40dad3b387fad24613263d103a97f91c9fe43ceb21965cd3392b1ccb5d77088021e525c4e0481adb309625d0cb94ade1d1fb8dc17e - languageName: node - linkType: hard - -"err-code@npm:^2.0.2": - version: 2.0.3 - resolution: "err-code@npm:2.0.3" - checksum: 8b7b1be20d2de12d2255c0bc2ca638b7af5171142693299416e6a9339bd7d88fc8d7707d913d78e0993176005405a236b066b45666b27b797252c771156ace54 - languageName: node - linkType: hard - -"esbuild@npm:~0.17.6": - version: 0.17.12 - resolution: "esbuild@npm:0.17.12" - dependencies: - "@esbuild/android-arm": 0.17.12 - "@esbuild/android-arm64": 0.17.12 - "@esbuild/android-x64": 0.17.12 - "@esbuild/darwin-arm64": 0.17.12 - "@esbuild/darwin-x64": 0.17.12 - "@esbuild/freebsd-arm64": 0.17.12 - "@esbuild/freebsd-x64": 0.17.12 - "@esbuild/linux-arm": 0.17.12 - "@esbuild/linux-arm64": 0.17.12 - "@esbuild/linux-ia32": 0.17.12 - "@esbuild/linux-loong64": 0.17.12 - "@esbuild/linux-mips64el": 0.17.12 - "@esbuild/linux-ppc64": 0.17.12 - "@esbuild/linux-riscv64": 0.17.12 - "@esbuild/linux-s390x": 0.17.12 - "@esbuild/linux-x64": 0.17.12 - "@esbuild/netbsd-x64": 0.17.12 - "@esbuild/openbsd-x64": 0.17.12 - "@esbuild/sunos-x64": 0.17.12 - "@esbuild/win32-arm64": 0.17.12 - "@esbuild/win32-ia32": 0.17.12 - "@esbuild/win32-x64": 0.17.12 - dependenciesMeta: - "@esbuild/android-arm": - optional: true - "@esbuild/android-arm64": - optional: true - "@esbuild/android-x64": - optional: true - "@esbuild/darwin-arm64": - optional: true - "@esbuild/darwin-x64": - optional: true - "@esbuild/freebsd-arm64": - optional: true - "@esbuild/freebsd-x64": - optional: true - "@esbuild/linux-arm": - optional: true - "@esbuild/linux-arm64": - optional: true - "@esbuild/linux-ia32": - optional: true - "@esbuild/linux-loong64": - optional: true - "@esbuild/linux-mips64el": - optional: true - "@esbuild/linux-ppc64": - optional: true - "@esbuild/linux-riscv64": - optional: true - "@esbuild/linux-s390x": - optional: true - "@esbuild/linux-x64": - optional: true - "@esbuild/netbsd-x64": - optional: true - "@esbuild/openbsd-x64": - optional: true - "@esbuild/sunos-x64": - optional: true - "@esbuild/win32-arm64": - optional: true - "@esbuild/win32-ia32": - optional: true - "@esbuild/win32-x64": - optional: true - bin: - esbuild: bin/esbuild - checksum: ea6d33eb1bc6c9e00dcee5e253c7e935251b4801d376661fd9f19a9dcffc27f970078a6f7116d6c78ee825ceff9b974594b0b616bd560ce4d875a951aa92977b - languageName: node - linkType: hard - -"escalade@npm:^3.1.1": - version: 3.1.1 - resolution: "escalade@npm:3.1.1" - checksum: a3e2a99f07acb74b3ad4989c48ca0c3140f69f923e56d0cba0526240ee470b91010f9d39001f2a4a313841d237ede70a729e92125191ba5d21e74b106800b133 - languageName: node - linkType: hard - -"escape-string-regexp@npm:^2.0.0": - version: 2.0.0 - resolution: "escape-string-regexp@npm:2.0.0" - checksum: 9f8a2d5743677c16e85c810e3024d54f0c8dea6424fad3c79ef6666e81dd0846f7437f5e729dfcdac8981bc9e5294c39b4580814d114076b8d36318f46ae4395 - languageName: node - linkType: hard - -"fetch-ponyfill@npm:^7.1.0": - version: 7.1.0 - resolution: "fetch-ponyfill@npm:7.1.0" - dependencies: - node-fetch: ~2.6.1 - checksum: 7fd497dd5f7db890e80193de5bc1cd0115a62400272cd9a992849288e66886fcdb0724ea1ed161be7b8db2daeafda8c58d0259acdda42d6561155dbcdbb0720a - languageName: node - linkType: hard - -"fflate@npm:^0.7.4": - version: 0.7.4 - resolution: "fflate@npm:0.7.4" - checksum: b812ab26047432db70ff4c73eb45ad53bd0774575b4818b9c61c2921e89ec65d1259f06ec1618f2ac55e6a2f2e29b6dc09173d213b46580bc69efae5344bf8f1 - languageName: node - linkType: hard - -"fs-minipass@npm:^2.0.0, fs-minipass@npm:^2.1.0": - version: 2.1.0 - resolution: "fs-minipass@npm:2.1.0" - dependencies: - minipass: ^3.0.0 - checksum: 1b8d128dae2ac6cc94230cc5ead341ba3e0efaef82dab46a33d171c044caaa6ca001364178d42069b2809c35a1c3c35079a32107c770e9ffab3901b59af8c8b1 - languageName: node - linkType: hard - -"fs.realpath@npm:^1.0.0": - version: 1.0.0 - resolution: "fs.realpath@npm:1.0.0" - checksum: 99ddea01a7e75aa276c250a04eedeffe5662bce66c65c07164ad6264f9de18fb21be9433ead460e54cff20e31721c811f4fb5d70591799df5f85dce6d6746fd0 - languageName: node - linkType: hard - -"fsevents@npm:~2.3.2": - version: 2.3.2 - resolution: "fsevents@npm:2.3.2" - dependencies: - node-gyp: latest - checksum: 97ade64e75091afee5265e6956cb72ba34db7819b4c3e94c431d4be2b19b8bb7a2d4116da417950c3425f17c8fe693d25e20212cac583ac1521ad066b77ae31f - conditions: os=darwin - languageName: node - linkType: hard - -"fsevents@patch:fsevents@~2.3.2#~builtin": - version: 2.3.2 - resolution: "fsevents@patch:fsevents@npm%3A2.3.2#~builtin::version=2.3.2&hash=18f3a7" - dependencies: - node-gyp: latest - conditions: os=darwin - languageName: node - linkType: hard - -"function-bind@npm:^1.1.1": - version: 1.1.1 - resolution: "function-bind@npm:1.1.1" - checksum: b32fbaebb3f8ec4969f033073b43f5c8befbb58f1a79e12f1d7490358150359ebd92f49e72ff0144f65f2c48ea2a605bff2d07965f548f6474fd8efd95bf361a - languageName: node - linkType: hard - -"gauge@npm:^4.0.3": - version: 4.0.4 - resolution: "gauge@npm:4.0.4" - dependencies: - aproba: ^1.0.3 || ^2.0.0 - color-support: ^1.1.3 - console-control-strings: ^1.1.0 - has-unicode: ^2.0.1 - signal-exit: ^3.0.7 - string-width: ^4.2.3 - strip-ansi: ^6.0.1 - wide-align: ^1.1.5 - checksum: 788b6bfe52f1dd8e263cda800c26ac0ca2ff6de0b6eee2fe0d9e3abf15e149b651bd27bf5226be10e6e3edb5c4e5d5985a5a1a98137e7a892f75eff76467ad2d - languageName: node - linkType: hard - -"get-caller-file@npm:^2.0.5": - version: 2.0.5 - resolution: "get-caller-file@npm:2.0.5" - checksum: b9769a836d2a98c3ee734a88ba712e62703f1df31b94b784762c433c27a386dd6029ff55c2a920c392e33657d80191edbf18c61487e198844844516f843496b9 - languageName: node - linkType: hard - -"get-tsconfig@npm:^4.4.0": - version: 4.4.0 - resolution: "get-tsconfig@npm:4.4.0" - checksum: e193558b4f0c84c81ae9688cf5b9950dc0b341e44f91b002713fd0c37cfb73108e1cd9998ed540bcc423f193fde32cc58a15e99dd469f5158a2eb4a148611176 - languageName: node - linkType: hard - -"glob@npm:^7.0.0, glob@npm:^7.1.3, glob@npm:^7.1.4": - version: 7.2.3 - resolution: "glob@npm:7.2.3" - dependencies: - fs.realpath: ^1.0.0 - inflight: ^1.0.4 - inherits: 2 - minimatch: ^3.1.1 - once: ^1.3.0 - path-is-absolute: ^1.0.0 - checksum: 29452e97b38fa704dabb1d1045350fb2467cf0277e155aa9ff7077e90ad81d1ea9d53d3ee63bd37c05b09a065e90f16aec4a65f5b8de401d1dac40bc5605d133 - languageName: node - linkType: hard - -"glob@npm:^8.0.1": - version: 8.1.0 - resolution: "glob@npm:8.1.0" - dependencies: - fs.realpath: ^1.0.0 - inflight: ^1.0.4 - inherits: 2 - minimatch: ^5.0.1 - once: ^1.3.0 - checksum: 92fbea3221a7d12075f26f0227abac435de868dd0736a17170663783296d0dd8d3d532a5672b4488a439bf5d7fb85cdd07c11185d6cd39184f0385cbdfb86a47 - languageName: node - linkType: hard - -"graceful-fs@npm:^4.2.6": - version: 4.2.11 - resolution: "graceful-fs@npm:4.2.11" - checksum: ac85f94da92d8eb6b7f5a8b20ce65e43d66761c55ce85ac96df6865308390da45a8d3f0296dd3a663de65d30ba497bd46c696cc1e248c72b13d6d567138a4fc7 - languageName: node - linkType: hard - -"has-unicode@npm:^2.0.1": - version: 2.0.1 - resolution: "has-unicode@npm:2.0.1" - checksum: 1eab07a7436512db0be40a710b29b5dc21fa04880b7f63c9980b706683127e3c1b57cb80ea96d47991bdae2dfe479604f6a1ba410106ee1046a41d1bd0814400 - languageName: node - linkType: hard - -"has@npm:^1.0.3": - version: 1.0.3 - resolution: "has@npm:1.0.3" - dependencies: - function-bind: ^1.1.1 - checksum: b9ad53d53be4af90ce5d1c38331e712522417d017d5ef1ebd0507e07c2fbad8686fffb8e12ddecd4c39ca9b9b47431afbb975b8abf7f3c3b82c98e9aad052792 - languageName: node - linkType: hard - -"http-cache-semantics@npm:^4.1.0": - version: 4.1.1 - resolution: "http-cache-semantics@npm:4.1.1" - checksum: 83ac0bc60b17a3a36f9953e7be55e5c8f41acc61b22583060e8dedc9dd5e3607c823a88d0926f9150e571f90946835c7fe150732801010845c72cd8bbff1a236 - languageName: node - linkType: hard - -"http-proxy-agent@npm:^5.0.0": - version: 5.0.0 - resolution: "http-proxy-agent@npm:5.0.0" - dependencies: - "@tootallnate/once": 2 - agent-base: 6 - debug: 4 - checksum: e2ee1ff1656a131953839b2a19cd1f3a52d97c25ba87bd2559af6ae87114abf60971e498021f9b73f9fd78aea8876d1fb0d4656aac8a03c6caa9fc175f22b786 - languageName: node - linkType: hard - -"https-proxy-agent@npm:^5.0.0": - version: 5.0.1 - resolution: "https-proxy-agent@npm:5.0.1" - dependencies: - agent-base: 6 - debug: 4 - checksum: 571fccdf38184f05943e12d37d6ce38197becdd69e58d03f43637f7fa1269cf303a7d228aa27e5b27bbd3af8f09fd938e1c91dcfefff2df7ba77c20ed8dfc765 - languageName: node - linkType: hard - -"humanize-ms@npm:^1.2.1": - version: 1.2.1 - resolution: "humanize-ms@npm:1.2.1" - dependencies: - ms: ^2.0.0 - checksum: 9c7a74a2827f9294c009266c82031030eae811ca87b0da3dceb8d6071b9bde22c9f3daef0469c3c533cc67a97d8a167cd9fc0389350e5f415f61a79b171ded16 - languageName: node - linkType: hard - -"iconv-lite@npm:^0.6.2": - version: 0.6.3 - resolution: "iconv-lite@npm:0.6.3" - dependencies: - safer-buffer: ">= 2.1.2 < 3.0.0" - checksum: 3f60d47a5c8fc3313317edfd29a00a692cc87a19cac0159e2ce711d0ebc9019064108323b5e493625e25594f11c6236647d8e256fbe7a58f4a3b33b89e6d30bf - languageName: node - linkType: hard - -"immer@npm:^9.0.16": - version: 9.0.21 - resolution: "immer@npm:9.0.21" - checksum: 70e3c274165995352f6936695f0ef4723c52c92c92dd0e9afdfe008175af39fa28e76aafb3a2ca9d57d1fb8f796efc4dd1e1cc36f18d33fa5b74f3dfb0375432 - languageName: node - linkType: hard - -"imurmurhash@npm:^0.1.4": - version: 0.1.4 - resolution: "imurmurhash@npm:0.1.4" - checksum: 7cae75c8cd9a50f57dadd77482359f659eaebac0319dd9368bcd1714f55e65badd6929ca58569da2b6494ef13fdd5598cd700b1eba23f8b79c5f19d195a3ecf7 - languageName: node - linkType: hard - -"indent-string@npm:^4.0.0": - version: 4.0.0 - resolution: "indent-string@npm:4.0.0" - checksum: 824cfb9929d031dabf059bebfe08cf3137365e112019086ed3dcff6a0a7b698cb80cf67ccccde0e25b9e2d7527aa6cc1fed1ac490c752162496caba3e6699612 - languageName: node - linkType: hard - -"indent-string@npm:^5.0.0": - version: 5.0.0 - resolution: "indent-string@npm:5.0.0" - checksum: e466c27b6373440e6d84fbc19e750219ce25865cb82d578e41a6053d727e5520dc5725217d6eb1cc76005a1bb1696a0f106d84ce7ebda3033b963a38583fb3b3 - languageName: node - linkType: hard - -"infer-owner@npm:^1.0.4": - version: 1.0.4 - resolution: "infer-owner@npm:1.0.4" - checksum: 181e732764e4a0611576466b4b87dac338972b839920b2a8cde43642e4ed6bd54dc1fb0b40874728f2a2df9a1b097b8ff83b56d5f8f8e3927f837fdcb47d8a89 - languageName: node - linkType: hard - -"inflight@npm:^1.0.4": - version: 1.0.6 - resolution: "inflight@npm:1.0.6" - dependencies: - once: ^1.3.0 - wrappy: 1 - checksum: f4f76aa072ce19fae87ce1ef7d221e709afb59d445e05d47fba710e85470923a75de35bfae47da6de1b18afc3ce83d70facf44cfb0aff89f0a3f45c0a0244dfd - languageName: node - linkType: hard - -"inherits@npm:2, inherits@npm:^2.0.3": - version: 2.0.4 - resolution: "inherits@npm:2.0.4" - checksum: 4a48a733847879d6cf6691860a6b1e3f0f4754176e4d71494c41f3475553768b10f84b5ce1d40fbd0e34e6bfbb864ee35858ad4dd2cf31e02fc4a154b724d7f1 - languageName: node - linkType: hard - -"ink@npm:^4.0.0": - version: 4.0.0 - resolution: "ink@npm:4.0.0" - dependencies: - ansi-escapes: ^6.0.0 - auto-bind: ^5.0.1 - chalk: ^5.2.0 - cli-boxes: ^3.0.0 - cli-cursor: ^4.0.0 - cli-truncate: ^3.1.0 - code-excerpt: ^4.0.0 - indent-string: ^5.0.0 - is-ci: ^3.0.1 - lodash-es: ^4.17.21 - patch-console: ^2.0.0 - react-reconciler: ^0.29.0 - scheduler: ^0.23.0 - signal-exit: ^3.0.7 - slice-ansi: ^5.0.0 - stack-utils: ^2.0.6 - string-width: ^5.1.2 - type-fest: ^0.12.0 - widest-line: ^4.0.1 - wrap-ansi: ^8.1.0 - ws: ^8.12.0 - yoga-layout-prebuilt: ^1.9.6 - peerDependencies: - "@types/react": ">=18.0.0" - react: ">=18.0.0" - react-devtools-core: ^4.19.1 - peerDependenciesMeta: - "@types/react": - optional: true - react-devtools-core: - optional: true - checksum: 7cd120ed6589e17ea4f8c0abcfeac37e3afb3d6c5b82342a7996f0a15a1a4d1ba933f926ae8e5539dd8adea3e6cfbcf5d4025d82c1d917fbcf8fc56f92213f63 - languageName: node - linkType: hard - -"interpret@npm:^1.0.0": - version: 1.4.0 - resolution: "interpret@npm:1.4.0" - checksum: 2e5f51268b5941e4a17e4ef0575bc91ed0ab5f8515e3cf77486f7c14d13f3010df9c0959f37063dcc96e78d12dc6b0bb1b9e111cdfe69771f4656d2993d36155 - languageName: node - linkType: hard - -"ip@npm:^2.0.0": - version: 2.0.0 - resolution: "ip@npm:2.0.0" - checksum: cfcfac6b873b701996d71ec82a7dd27ba92450afdb421e356f44044ed688df04567344c36cbacea7d01b1c39a4c732dc012570ebe9bebfb06f27314bca625349 - languageName: node - linkType: hard - -"is-ci@npm:^3.0.1": - version: 3.0.1 - resolution: "is-ci@npm:3.0.1" - dependencies: - ci-info: ^3.2.0 - bin: - is-ci: bin.js - checksum: 192c66dc7826d58f803ecae624860dccf1899fc1f3ac5505284c0a5cf5f889046ffeb958fa651e5725d5705c5bcb14f055b79150ea5fcad7456a9569de60260e - languageName: node - linkType: hard - -"is-core-module@npm:^2.9.0": - version: 2.11.0 - resolution: "is-core-module@npm:2.11.0" - dependencies: - has: ^1.0.3 - checksum: f96fd490c6b48eb4f6d10ba815c6ef13f410b0ba6f7eb8577af51697de523e5f2cd9de1c441b51d27251bf0e4aebc936545e33a5d26d5d51f28d25698d4a8bab - languageName: node - linkType: hard - -"is-fullwidth-code-point@npm:^3.0.0": - version: 3.0.0 - resolution: "is-fullwidth-code-point@npm:3.0.0" - checksum: 44a30c29457c7fb8f00297bce733f0a64cd22eca270f83e58c105e0d015e45c019491a4ab2faef91ab51d4738c670daff901c799f6a700e27f7314029e99e348 - languageName: node - linkType: hard - -"is-fullwidth-code-point@npm:^4.0.0": - version: 4.0.0 - resolution: "is-fullwidth-code-point@npm:4.0.0" - checksum: 8ae89bf5057bdf4f57b346fb6c55e9c3dd2549983d54191d722d5c739397a903012cc41a04ee3403fd872e811243ef91a7c5196da7b5841dc6b6aae31a264a8d - languageName: node - linkType: hard - -"is-lambda@npm:^1.0.1": - version: 1.0.1 - resolution: "is-lambda@npm:1.0.1" - checksum: 93a32f01940220532e5948538699ad610d5924ac86093fcee83022252b363eb0cc99ba53ab084a04e4fb62bf7b5731f55496257a4c38adf87af9c4d352c71c35 - languageName: node - linkType: hard - -"isexe@npm:^2.0.0": - version: 2.0.0 - resolution: "isexe@npm:2.0.0" - checksum: 26bf6c5480dda5161c820c5b5c751ae1e766c587b1f951ea3fcfc973bafb7831ae5b54a31a69bd670220e42e99ec154475025a468eae58ea262f813fdc8d1c62 - languageName: node - linkType: hard - -"js-tokens@npm:^3.0.0 || ^4.0.0": - version: 4.0.0 - resolution: "js-tokens@npm:4.0.0" - checksum: 8a95213a5a77deb6cbe94d86340e8d9ace2b93bc367790b260101d2f36a2eaf4e4e22d9fa9cf459b38af3a32fb4190e638024cf82ec95ef708680e405ea7cc78 - languageName: node - linkType: hard - -"lodash-es@npm:^4.17.21": - version: 4.17.21 - resolution: "lodash-es@npm:4.17.21" - checksum: 05cbffad6e2adbb331a4e16fbd826e7faee403a1a04873b82b42c0f22090f280839f85b95393f487c1303c8a3d2a010048bf06151a6cbe03eee4d388fb0a12d2 - languageName: node - linkType: hard - -"loose-envify@npm:^1.1.0": - version: 1.4.0 - resolution: "loose-envify@npm:1.4.0" - dependencies: - js-tokens: ^3.0.0 || ^4.0.0 - bin: - loose-envify: cli.js - checksum: 6517e24e0cad87ec9888f500c5b5947032cdfe6ef65e1c1936a0c48a524b81e65542c9c3edc91c97d5bddc806ee2a985dbc79be89215d613b1de5db6d1cfe6f4 - languageName: node - linkType: hard - -"lru-cache@npm:^6.0.0": - version: 6.0.0 - resolution: "lru-cache@npm:6.0.0" - dependencies: - yallist: ^4.0.0 - checksum: f97f499f898f23e4585742138a22f22526254fdba6d75d41a1c2526b3b6cc5747ef59c5612ba7375f42aca4f8461950e925ba08c991ead0651b4918b7c978297 - languageName: node - linkType: hard - -"lru-cache@npm:^7.7.1": - version: 7.18.3 - resolution: "lru-cache@npm:7.18.3" - checksum: e550d772384709deea3f141af34b6d4fa392e2e418c1498c078de0ee63670f1f46f5eee746e8ef7e69e1c895af0d4224e62ee33e66a543a14763b0f2e74c1356 - languageName: node - linkType: hard - -"make-fetch-happen@npm:^10.0.3": - version: 10.2.1 - resolution: "make-fetch-happen@npm:10.2.1" - dependencies: - agentkeepalive: ^4.2.1 - cacache: ^16.1.0 - http-cache-semantics: ^4.1.0 - http-proxy-agent: ^5.0.0 - https-proxy-agent: ^5.0.0 - is-lambda: ^1.0.1 - lru-cache: ^7.7.1 - minipass: ^3.1.6 - minipass-collect: ^1.0.2 - minipass-fetch: ^2.0.3 - minipass-flush: ^1.0.5 - minipass-pipeline: ^1.2.4 - negotiator: ^0.6.3 - promise-retry: ^2.0.1 - socks-proxy-agent: ^7.0.0 - ssri: ^9.0.0 - checksum: 2332eb9a8ec96f1ffeeea56ccefabcb4193693597b132cd110734d50f2928842e22b84cfa1508e921b8385cdfd06dda9ad68645fed62b50fff629a580f5fb72c - languageName: node - linkType: hard - -"mimic-fn@npm:^2.1.0": - version: 2.1.0 - resolution: "mimic-fn@npm:2.1.0" - checksum: d2421a3444848ce7f84bd49115ddacff29c15745db73f54041edc906c14b131a38d05298dae3081667627a59b2eb1ca4b436ff2e1b80f69679522410418b478a - languageName: node - linkType: hard - -"minimatch@npm:^3.1.1": - version: 3.1.2 - resolution: "minimatch@npm:3.1.2" - dependencies: - brace-expansion: ^1.1.7 - checksum: c154e566406683e7bcb746e000b84d74465b3a832c45d59912b9b55cd50dee66e5c4b1e5566dba26154040e51672f9aa450a9aef0c97cfc7336b78b7afb9540a - languageName: node - linkType: hard - -"minimatch@npm:^5.0.1": - version: 5.1.6 - resolution: "minimatch@npm:5.1.6" - dependencies: - brace-expansion: ^2.0.1 - checksum: 7564208ef81d7065a370f788d337cd80a689e981042cb9a1d0e6580b6c6a8c9279eba80010516e258835a988363f99f54a6f711a315089b8b42694f5da9d0d77 - languageName: node - linkType: hard - -"minipass-collect@npm:^1.0.2": - version: 1.0.2 - resolution: "minipass-collect@npm:1.0.2" - dependencies: - minipass: ^3.0.0 - checksum: 14df761028f3e47293aee72888f2657695ec66bd7d09cae7ad558da30415fdc4752bbfee66287dcc6fd5e6a2fa3466d6c484dc1cbd986525d9393b9523d97f10 - languageName: node - linkType: hard - -"minipass-fetch@npm:^2.0.3": - version: 2.1.2 - resolution: "minipass-fetch@npm:2.1.2" - dependencies: - encoding: ^0.1.13 - minipass: ^3.1.6 - minipass-sized: ^1.0.3 - minizlib: ^2.1.2 - dependenciesMeta: - encoding: - optional: true - checksum: 3f216be79164e915fc91210cea1850e488793c740534985da017a4cbc7a5ff50506956d0f73bb0cb60e4fe91be08b6b61ef35101706d3ef5da2c8709b5f08f91 - languageName: node - linkType: hard - -"minipass-flush@npm:^1.0.5": - version: 1.0.5 - resolution: "minipass-flush@npm:1.0.5" - dependencies: - minipass: ^3.0.0 - checksum: 56269a0b22bad756a08a94b1ffc36b7c9c5de0735a4dd1ab2b06c066d795cfd1f0ac44a0fcae13eece5589b908ecddc867f04c745c7009be0b566421ea0944cf - languageName: node - linkType: hard - -"minipass-pipeline@npm:^1.2.4": - version: 1.2.4 - resolution: "minipass-pipeline@npm:1.2.4" - dependencies: - minipass: ^3.0.0 - checksum: b14240dac0d29823c3d5911c286069e36d0b81173d7bdf07a7e4a91ecdef92cdff4baaf31ea3746f1c61e0957f652e641223970870e2353593f382112257971b - languageName: node - linkType: hard - -"minipass-sized@npm:^1.0.3": - version: 1.0.3 - resolution: "minipass-sized@npm:1.0.3" - dependencies: - minipass: ^3.0.0 - checksum: 79076749fcacf21b5d16dd596d32c3b6bf4d6e62abb43868fac21674078505c8b15eaca4e47ed844985a4514854f917d78f588fcd029693709417d8f98b2bd60 - languageName: node - linkType: hard - -"minipass@npm:^3.0.0, minipass@npm:^3.1.1, minipass@npm:^3.1.6": - version: 3.3.6 - resolution: "minipass@npm:3.3.6" - dependencies: - yallist: ^4.0.0 - checksum: a30d083c8054cee83cdcdc97f97e4641a3f58ae743970457b1489ce38ee1167b3aaf7d815cd39ec7a99b9c40397fd4f686e83750e73e652b21cb516f6d845e48 - languageName: node - linkType: hard - -"minipass@npm:^4.0.0": - version: 4.2.5 - resolution: "minipass@npm:4.2.5" - checksum: 4f9c19af23a5d4a9e7156feefc9110634b178a8cff8f8271af16ec5ebf7e221725a97429952c856f5b17b30c2065ebd24c81722d90c93d2122611d75b952b48f - languageName: node - linkType: hard - -"minizlib@npm:^2.1.1, minizlib@npm:^2.1.2": - version: 2.1.2 - resolution: "minizlib@npm:2.1.2" - dependencies: - minipass: ^3.0.0 - yallist: ^4.0.0 - checksum: f1fdeac0b07cf8f30fcf12f4b586795b97be856edea22b5e9072707be51fc95d41487faec3f265b42973a304fe3a64acd91a44a3826a963e37b37bafde0212c3 - languageName: node - linkType: hard - -"mkdirp@npm:^1.0.3, mkdirp@npm:^1.0.4": - version: 1.0.4 - resolution: "mkdirp@npm:1.0.4" - bin: - mkdirp: bin/cmd.js - checksum: a96865108c6c3b1b8e1d5e9f11843de1e077e57737602de1b82030815f311be11f96f09cce59bd5b903d0b29834733e5313f9301e3ed6d6f6fba2eae0df4298f - languageName: node - linkType: hard - -"ms@npm:2.1.2": - version: 2.1.2 - resolution: "ms@npm:2.1.2" - checksum: 673cdb2c3133eb050c745908d8ce632ed2c02d85640e2edb3ace856a2266a813b30c613569bf3354fdf4ea7d1a1494add3bfa95e2713baa27d0c2c71fc44f58f - languageName: node - linkType: hard - -"ms@npm:^2.0.0": - version: 2.1.3 - resolution: "ms@npm:2.1.3" - checksum: aa92de608021b242401676e35cfa5aa42dd70cbdc082b916da7fb925c542173e36bce97ea3e804923fe92c0ad991434e4a38327e15a1b5b5f945d66df615ae6d - languageName: node - linkType: hard - -"negotiator@npm:^0.6.3": - version: 0.6.3 - resolution: "negotiator@npm:0.6.3" - checksum: b8ffeb1e262eff7968fc90a2b6767b04cfd9842582a9d0ece0af7049537266e7b2506dfb1d107a32f06dd849ab2aea834d5830f7f4d0e5cb7d36e1ae55d021d9 - languageName: node - linkType: hard - -"node-fetch@npm:~2.6.1": - version: 2.6.9 - resolution: "node-fetch@npm:2.6.9" - dependencies: - whatwg-url: ^5.0.0 - peerDependencies: - encoding: ^0.1.0 - peerDependenciesMeta: - encoding: - optional: true - checksum: acb04f9ce7224965b2b59e71b33c639794d8991efd73855b0b250921382b38331ffc9d61bce502571f6cc6e11a8905ca9b1b6d4aeb586ab093e2756a1fd190d0 - languageName: node - linkType: hard - -"node-gyp@npm:latest": - version: 9.3.1 - resolution: "node-gyp@npm:9.3.1" - dependencies: - env-paths: ^2.2.0 - glob: ^7.1.4 - graceful-fs: ^4.2.6 - make-fetch-happen: ^10.0.3 - nopt: ^6.0.0 - npmlog: ^6.0.0 - rimraf: ^3.0.2 - semver: ^7.3.5 - tar: ^6.1.2 - which: ^2.0.2 - bin: - node-gyp: bin/node-gyp.js - checksum: b860e9976fa645ca0789c69e25387401b4396b93c8375489b5151a6c55cf2640a3b6183c212b38625ef7c508994930b72198338e3d09b9d7ade5acc4aaf51ea7 - languageName: node - linkType: hard - -"nopt@npm:^6.0.0": - version: 6.0.0 - resolution: "nopt@npm:6.0.0" - dependencies: - abbrev: ^1.0.0 - bin: - nopt: bin/nopt.js - checksum: 82149371f8be0c4b9ec2f863cc6509a7fd0fa729929c009f3a58e4eb0c9e4cae9920e8f1f8eb46e7d032fec8fb01bede7f0f41a67eb3553b7b8e14fa53de1dac - languageName: node - linkType: hard - -"npmlog@npm:^6.0.0": - version: 6.0.2 - resolution: "npmlog@npm:6.0.2" - dependencies: - are-we-there-yet: ^3.0.0 - console-control-strings: ^1.1.0 - gauge: ^4.0.3 - set-blocking: ^2.0.0 - checksum: ae238cd264a1c3f22091cdd9e2b106f684297d3c184f1146984ecbe18aaa86343953f26b9520dedd1b1372bc0316905b736c1932d778dbeb1fcf5a1001390e2a - languageName: node - linkType: hard - -"object-hash@npm:^3.0.0": - version: 3.0.0 - resolution: "object-hash@npm:3.0.0" - checksum: 80b4904bb3857c52cc1bfd0b52c0352532ca12ed3b8a6ff06a90cd209dfda1b95cee059a7625eb9da29537027f68ac4619363491eedb2f5d3dddbba97494fd6c - languageName: node - linkType: hard - -"once@npm:^1.3.0": - version: 1.4.0 - resolution: "once@npm:1.4.0" - dependencies: - wrappy: 1 - checksum: cd0a88501333edd640d95f0d2700fbde6bff20b3d4d9bdc521bdd31af0656b5706570d6c6afe532045a20bb8dc0849f8332d6f2a416e0ba6d3d3b98806c7db68 - languageName: node - linkType: hard - -"onetime@npm:^5.1.0": - version: 5.1.2 - resolution: "onetime@npm:5.1.2" - dependencies: - mimic-fn: ^2.1.0 - checksum: 2478859ef817fc5d4e9c2f9e5728512ddd1dbc9fb7829ad263765bb6d3b91ce699d6e2332eef6b7dff183c2f490bd3349f1666427eaba4469fba0ac38dfd0d34 - languageName: node - linkType: hard - -"p-map@npm:^4.0.0": - version: 4.0.0 - resolution: "p-map@npm:4.0.0" - dependencies: - aggregate-error: ^3.0.0 - checksum: cb0ab21ec0f32ddffd31dfc250e3afa61e103ef43d957cc45497afe37513634589316de4eb88abdfd969fe6410c22c0b93ab24328833b8eb1ccc087fc0442a1c - languageName: node - linkType: hard - -"patch-console@npm:^2.0.0": - version: 2.0.0 - resolution: "patch-console@npm:2.0.0" - checksum: 10e7d382cc1cf930a2114a822cdc816109a1147bcbc4881ca4fa2ad0228a60cf14d53f815fce3164f25851fea71db4026ae8271e4026b42b0a6e92ddc074d4c2 - languageName: node - linkType: hard - -"path-is-absolute@npm:^1.0.0": - version: 1.0.1 - resolution: "path-is-absolute@npm:1.0.1" - checksum: 060840f92cf8effa293bcc1bea81281bd7d363731d214cbe5c227df207c34cd727430f70c6037b5159c8a870b9157cba65e775446b0ab06fd5ecc7e54615a3b8 - languageName: node - linkType: hard - -"path-parse@npm:^1.0.7": - version: 1.0.7 - resolution: "path-parse@npm:1.0.7" - checksum: 49abf3d81115642938a8700ec580da6e830dde670be21893c62f4e10bd7dd4c3742ddc603fe24f898cba7eb0c6bc1777f8d9ac14185d34540c6d4d80cd9cae8a - languageName: node - linkType: hard - -"promise-inflight@npm:^1.0.1": - version: 1.0.1 - resolution: "promise-inflight@npm:1.0.1" - checksum: 22749483091d2c594261517f4f80e05226d4d5ecc1fc917e1886929da56e22b5718b7f2a75f3807e7a7d471bc3be2907fe92e6e8f373ddf5c64bae35b5af3981 - languageName: node - linkType: hard - -"promise-retry@npm:^2.0.1": - version: 2.0.1 - resolution: "promise-retry@npm:2.0.1" - dependencies: - err-code: ^2.0.2 - retry: ^0.12.0 - checksum: f96a3f6d90b92b568a26f71e966cbbc0f63ab85ea6ff6c81284dc869b41510e6cdef99b6b65f9030f0db422bf7c96652a3fff9f2e8fb4a0f069d8f4430359429 - languageName: node - linkType: hard - -"react-reconciler@npm:^0.29.0": - version: 0.29.0 - resolution: "react-reconciler@npm:0.29.0" - dependencies: - loose-envify: ^1.1.0 - scheduler: ^0.23.0 - peerDependencies: - react: ^18.2.0 - checksum: 730db9cf451fe6b5102042fda32a029c73ef970f758707d8a0f07e11e9cc262bc973987464d920b519da99f7e20bb1fef1d1c6b05ff993ad12d59d63b004a2ab - languageName: node - linkType: hard - -"react@npm:^18.2.0": - version: 18.2.0 - resolution: "react@npm:18.2.0" - dependencies: - loose-envify: ^1.1.0 - checksum: 88e38092da8839b830cda6feef2e8505dec8ace60579e46aa5490fc3dc9bba0bd50336507dc166f43e3afc1c42939c09fe33b25fae889d6f402721dcd78fca1b - languageName: node - linkType: hard - -"readable-stream@npm:^3.6.0": - version: 3.6.2 - resolution: "readable-stream@npm:3.6.2" - dependencies: - inherits: ^2.0.3 - string_decoder: ^1.1.1 - util-deprecate: ^1.0.1 - checksum: bdcbe6c22e846b6af075e32cf8f4751c2576238c5043169a1c221c92ee2878458a816a4ea33f4c67623c0b6827c8a400409bfb3cf0bf3381392d0b1dfb52ac8d - languageName: node - linkType: hard - -"rechoir@npm:^0.6.2": - version: 0.6.2 - resolution: "rechoir@npm:0.6.2" - dependencies: - resolve: ^1.1.6 - checksum: fe76bf9c21875ac16e235defedd7cbd34f333c02a92546142b7911a0f7c7059d2e16f441fe6fb9ae203f459c05a31b2bcf26202896d89e390eda7514d5d2702b - languageName: node - linkType: hard - -"redux-thunk@npm:^2.4.2": - version: 2.4.2 - resolution: "redux-thunk@npm:2.4.2" - peerDependencies: - redux: ^4 - checksum: c7f757f6c383b8ec26152c113e20087818d18ed3edf438aaad43539e9a6b77b427ade755c9595c4a163b6ad3063adf3497e5fe6a36c68884eb1f1cfb6f049a5c - languageName: node - linkType: hard - -"redux@npm:^4.2.0": - version: 4.2.1 - resolution: "redux@npm:4.2.1" - dependencies: - "@babel/runtime": ^7.9.2 - checksum: f63b9060c3a1d930ae775252bb6e579b42415aee7a23c4114e21a0b4ba7ec12f0ec76936c00f546893f06e139819f0e2855e0d55ebfce34ca9c026241a6950dd - languageName: node - linkType: hard - -"regenerator-runtime@npm:^0.13.11": - version: 0.13.11 - resolution: "regenerator-runtime@npm:0.13.11" - checksum: 27481628d22a1c4e3ff551096a683b424242a216fee44685467307f14d58020af1e19660bf2e26064de946bad7eff28950eae9f8209d55723e2d9351e632bbb4 - languageName: node - linkType: hard - -"require-directory@npm:^2.1.1": - version: 2.1.1 - resolution: "require-directory@npm:2.1.1" - checksum: fb47e70bf0001fdeabdc0429d431863e9475e7e43ea5f94ad86503d918423c1543361cc5166d713eaa7029dd7a3d34775af04764bebff99ef413111a5af18c80 - languageName: node - linkType: hard - -"reselect@npm:^4.1.7": - version: 4.1.7 - resolution: "reselect@npm:4.1.7" - checksum: 738d8e2b8f0dca154ad29de6a209c9fbca2d70ae6788fd85df87f2c74b95a65bbf2d16d43a9e2faff39de34d17a29d706ba08a6b2ee5660c09589edbd19af7e1 - languageName: node - linkType: hard - -"resolve@npm:^1.1.6": - version: 1.22.1 - resolution: "resolve@npm:1.22.1" - dependencies: - is-core-module: ^2.9.0 - path-parse: ^1.0.7 - supports-preserve-symlinks-flag: ^1.0.0 - bin: - resolve: bin/resolve - checksum: 07af5fc1e81aa1d866cbc9e9460fbb67318a10fa3c4deadc35c3ad8a898ee9a71a86a65e4755ac3195e0ea0cfbe201eb323ebe655ce90526fd61917313a34e4e - languageName: node - linkType: hard - -"resolve@patch:resolve@^1.1.6#~builtin": - version: 1.22.1 - resolution: "resolve@patch:resolve@npm%3A1.22.1#~builtin::version=1.22.1&hash=07638b" - dependencies: - is-core-module: ^2.9.0 - path-parse: ^1.0.7 - supports-preserve-symlinks-flag: ^1.0.0 - bin: - resolve: bin/resolve - checksum: 5656f4d0bedcf8eb52685c1abdf8fbe73a1603bb1160a24d716e27a57f6cecbe2432ff9c89c2bd57542c3a7b9d14b1882b73bfe2e9d7849c9a4c0b8b39f02b8b - languageName: node - linkType: hard - -"restore-cursor@npm:^4.0.0": - version: 4.0.0 - resolution: "restore-cursor@npm:4.0.0" - dependencies: - onetime: ^5.1.0 - signal-exit: ^3.0.2 - checksum: 5b675c5a59763bf26e604289eab35711525f11388d77f409453904e1e69c0d37ae5889295706b2c81d23bd780165084d040f9b68fffc32cc921519031c4fa4af - languageName: node - linkType: hard - -"retry@npm:^0.12.0": - version: 0.12.0 - resolution: "retry@npm:0.12.0" - checksum: 623bd7d2e5119467ba66202d733ec3c2e2e26568074923bc0585b6b99db14f357e79bdedb63cab56cec47491c4a0da7e6021a7465ca6dc4f481d3898fdd3158c - languageName: node - linkType: hard - -"rimraf@npm:^3.0.2": - version: 3.0.2 - resolution: "rimraf@npm:3.0.2" - dependencies: - glob: ^7.1.3 - bin: - rimraf: bin.js - checksum: 87f4164e396f0171b0a3386cc1877a817f572148ee13a7e113b238e48e8a9f2f31d009a92ec38a591ff1567d9662c6b67fd8818a2dbbaed74bc26a87a2a4a9a0 - languageName: node - linkType: hard - -"safe-buffer@npm:~5.2.0": - version: 5.2.1 - resolution: "safe-buffer@npm:5.2.1" - checksum: b99c4b41fdd67a6aaf280fcd05e9ffb0813654894223afb78a31f14a19ad220bba8aba1cb14eddce1fcfb037155fe6de4e861784eb434f7d11ed58d1e70dd491 - languageName: node - linkType: hard - -"safer-buffer@npm:>= 2.1.2 < 3.0.0": - version: 2.1.2 - resolution: "safer-buffer@npm:2.1.2" - checksum: cab8f25ae6f1434abee8d80023d7e72b598cf1327164ddab31003c51215526801e40b66c5e65d658a0af1e9d6478cadcb4c745f4bd6751f97d8644786c0978b0 - languageName: node - linkType: hard - -"scheduler@npm:^0.23.0": - version: 0.23.0 - resolution: "scheduler@npm:0.23.0" - dependencies: - loose-envify: ^1.1.0 - checksum: d79192eeaa12abef860c195ea45d37cbf2bbf5f66e3c4dcd16f54a7da53b17788a70d109ee3d3dde1a0fd50e6a8fc171f4300356c5aee4fc0171de526bf35f8a - languageName: node - linkType: hard - -"semver@npm:^7.3.5": - version: 7.3.8 - resolution: "semver@npm:7.3.8" - dependencies: - lru-cache: ^6.0.0 - bin: - semver: bin/semver.js - checksum: ba9c7cbbf2b7884696523450a61fee1a09930d888b7a8d7579025ad93d459b2d1949ee5bbfeb188b2be5f4ac163544c5e98491ad6152df34154feebc2cc337c1 - languageName: node - linkType: hard - -"set-blocking@npm:^2.0.0": - version: 2.0.0 - resolution: "set-blocking@npm:2.0.0" - checksum: 6e65a05f7cf7ebdf8b7c75b101e18c0b7e3dff4940d480efed8aad3a36a4005140b660fa1d804cb8bce911cac290441dc728084a30504d3516ac2ff7ad607b02 - languageName: node - linkType: hard - -"shelljs@npm:^0.8.5": - version: 0.8.5 - resolution: "shelljs@npm:0.8.5" - dependencies: - glob: ^7.0.0 - interpret: ^1.0.0 - rechoir: ^0.6.2 - bin: - shjs: bin/shjs - checksum: 7babc46f732a98f4c054ec1f048b55b9149b98aa2da32f6cf9844c434b43c6251efebd6eec120937bd0999e13811ebd45efe17410edb3ca938f82f9381302748 - languageName: node - linkType: hard - -"signal-exit@npm:^3.0.2, signal-exit@npm:^3.0.7": - version: 3.0.7 - resolution: "signal-exit@npm:3.0.7" - checksum: a2f098f247adc367dffc27845853e9959b9e88b01cb301658cfe4194352d8d2bb32e18467c786a7fe15f1d44b233ea35633d076d5e737870b7139949d1ab6318 - languageName: node - linkType: hard - -"slice-ansi@npm:^5.0.0": - version: 5.0.0 - resolution: "slice-ansi@npm:5.0.0" - dependencies: - ansi-styles: ^6.0.0 - is-fullwidth-code-point: ^4.0.0 - checksum: 7e600a2a55e333a21ef5214b987c8358fe28bfb03c2867ff2cbf919d62143d1812ac27b4297a077fdaf27a03da3678e49551c93e35f9498a3d90221908a1180e - languageName: node - linkType: hard - -"smart-buffer@npm:^4.2.0": - version: 4.2.0 - resolution: "smart-buffer@npm:4.2.0" - checksum: b5167a7142c1da704c0e3af85c402002b597081dd9575031a90b4f229ca5678e9a36e8a374f1814c8156a725d17008ae3bde63b92f9cfd132526379e580bec8b - languageName: node - linkType: hard - -"socks-proxy-agent@npm:^7.0.0": - version: 7.0.0 - resolution: "socks-proxy-agent@npm:7.0.0" - dependencies: - agent-base: ^6.0.2 - debug: ^4.3.3 - socks: ^2.6.2 - checksum: 720554370154cbc979e2e9ce6a6ec6ced205d02757d8f5d93fe95adae454fc187a5cbfc6b022afab850a5ce9b4c7d73e0f98e381879cf45f66317a4895953846 - languageName: node - linkType: hard - -"socks@npm:^2.6.2": - version: 2.7.1 - resolution: "socks@npm:2.7.1" - dependencies: - ip: ^2.0.0 - smart-buffer: ^4.2.0 - checksum: 259d9e3e8e1c9809a7f5c32238c3d4d2a36b39b83851d0f573bfde5f21c4b1288417ce1af06af1452569cd1eb0841169afd4998f0e04ba04656f6b7f0e46d748 - languageName: node - linkType: hard - -"source-map-support@npm:^0.5.21": - version: 0.5.21 - resolution: "source-map-support@npm:0.5.21" - dependencies: - buffer-from: ^1.0.0 - source-map: ^0.6.0 - checksum: 43e98d700d79af1d36f859bdb7318e601dfc918c7ba2e98456118ebc4c4872b327773e5a1df09b0524e9e5063bb18f0934538eace60cca2710d1fa687645d137 - languageName: node - linkType: hard - -"source-map@npm:^0.6.0": - version: 0.6.1 - resolution: "source-map@npm:0.6.1" - checksum: 59ce8640cf3f3124f64ac289012c2b8bd377c238e316fb323ea22fbfe83da07d81e000071d7242cad7a23cd91c7de98e4df8830ec3f133cb6133a5f6e9f67bc2 - languageName: node - linkType: hard - -"ssri@npm:^9.0.0": - version: 9.0.1 - resolution: "ssri@npm:9.0.1" - dependencies: - minipass: ^3.1.1 - checksum: fb58f5e46b6923ae67b87ad5ef1c5ab6d427a17db0bead84570c2df3cd50b4ceb880ebdba2d60726588272890bae842a744e1ecce5bd2a2a582fccd5068309eb - languageName: node - linkType: hard - -"stack-utils@npm:^2.0.6": - version: 2.0.6 - resolution: "stack-utils@npm:2.0.6" - dependencies: - escape-string-regexp: ^2.0.0 - checksum: 052bf4d25bbf5f78e06c1d5e67de2e088b06871fa04107ca8d3f0e9d9263326e2942c8bedee3545795fc77d787d443a538345eef74db2f8e35db3558c6f91ff7 - languageName: node - linkType: hard - -"string-width@npm:^1.0.2 || 2 || 3 || 4, string-width@npm:^4.1.0, string-width@npm:^4.2.0, string-width@npm:^4.2.3": - version: 4.2.3 - resolution: "string-width@npm:4.2.3" - dependencies: - emoji-regex: ^8.0.0 - is-fullwidth-code-point: ^3.0.0 - strip-ansi: ^6.0.1 - checksum: e52c10dc3fbfcd6c3a15f159f54a90024241d0f149cf8aed2982a2d801d2e64df0bf1dc351cf8e95c3319323f9f220c16e740b06faecd53e2462df1d2b5443fb - languageName: node - linkType: hard - -"string-width@npm:^5.0.0, string-width@npm:^5.0.1, string-width@npm:^5.1.2": - version: 5.1.2 - resolution: "string-width@npm:5.1.2" - dependencies: - eastasianwidth: ^0.2.0 - emoji-regex: ^9.2.2 - strip-ansi: ^7.0.1 - checksum: 7369deaa29f21dda9a438686154b62c2c5f661f8dda60449088f9f980196f7908fc39fdd1803e3e01541970287cf5deae336798337e9319a7055af89dafa7193 - languageName: node - linkType: hard - -"string_decoder@npm:^1.1.1": - version: 1.3.0 - resolution: "string_decoder@npm:1.3.0" - dependencies: - safe-buffer: ~5.2.0 - checksum: 8417646695a66e73aefc4420eb3b84cc9ffd89572861fe004e6aeb13c7bc00e2f616247505d2dbbef24247c372f70268f594af7126f43548565c68c117bdeb56 - languageName: node - linkType: hard - -"strip-ansi@npm:^6.0.0, strip-ansi@npm:^6.0.1": - version: 6.0.1 - resolution: "strip-ansi@npm:6.0.1" - dependencies: - ansi-regex: ^5.0.1 - checksum: f3cd25890aef3ba6e1a74e20896c21a46f482e93df4a06567cebf2b57edabb15133f1f94e57434e0a958d61186087b1008e89c94875d019910a213181a14fc8c - languageName: node - linkType: hard - -"strip-ansi@npm:^7.0.1": - version: 7.0.1 - resolution: "strip-ansi@npm:7.0.1" - dependencies: - ansi-regex: ^6.0.1 - checksum: 257f78fa433520e7f9897722731d78599cb3fce29ff26a20a5e12ba4957463b50a01136f37c43707f4951817a75e90820174853d6ccc240997adc5df8f966039 - languageName: node - linkType: hard - -"supports-preserve-symlinks-flag@npm:^1.0.0": - version: 1.0.0 - resolution: "supports-preserve-symlinks-flag@npm:1.0.0" - checksum: 53b1e247e68e05db7b3808b99b892bd36fb096e6fba213a06da7fab22045e97597db425c724f2bbd6c99a3c295e1e73f3e4de78592289f38431049e1277ca0ae - languageName: node - linkType: hard - -"tar@npm:^6.1.11, tar@npm:^6.1.2": - version: 6.1.13 - resolution: "tar@npm:6.1.13" - dependencies: - chownr: ^2.0.0 - fs-minipass: ^2.0.0 - minipass: ^4.0.0 - minizlib: ^2.1.1 - mkdirp: ^1.0.3 - yallist: ^4.0.0 - checksum: 8a278bed123aa9f53549b256a36b719e317c8b96fe86a63406f3c62887f78267cea9b22dc6f7007009738509800d4a4dccc444abd71d762287c90f35b002eb1c - languageName: node - linkType: hard - -"tr46@npm:~0.0.3": - version: 0.0.3 - resolution: "tr46@npm:0.0.3" - checksum: 726321c5eaf41b5002e17ffbd1fb7245999a073e8979085dacd47c4b4e8068ff5777142fc6726d6ca1fd2ff16921b48788b87225cbc57c72636f6efa8efbffe3 - languageName: node - linkType: hard - -"tsx@npm:^3.12.5": - version: 3.12.5 - resolution: "tsx@npm:3.12.5" - dependencies: - "@esbuild-kit/cjs-loader": ^2.4.2 - "@esbuild-kit/core-utils": ^3.0.0 - "@esbuild-kit/esm-loader": ^2.5.5 - fsevents: ~2.3.2 - dependenciesMeta: - fsevents: - optional: true - bin: - tsx: dist/cli.js - checksum: d8747fb9bf5c615fa8030e88a430ca573f5c77b54d633a1a64220899dca092d7d186e6403f93d9615d8aac6ddd8f80ce1f9db0e2708baad5ee5b586d5c06c480 - languageName: node - linkType: hard - -"type-fest@npm:^0.12.0": - version: 0.12.0 - resolution: "type-fest@npm:0.12.0" - checksum: 407d6c1a6fcc907f6124c37e977ba4966205014787a32a27579da6e47c3b1bd210b68cc1c7764d904c8aa55fb4efa6945586f9b4fae742c63ed026a4559da07d - languageName: node - linkType: hard - -"type-fest@npm:^3.0.0": - version: 3.6.1 - resolution: "type-fest@npm:3.6.1" - checksum: f7e39bf6b74a883661ec8642707f49c33cfcdc6221e1ba36b1d329c1cf301d87351b3ca0839b894cbfe47dc62140c0ce47e69c88f76800b678e0b67b7fe826e6 - languageName: node - linkType: hard - -"typescript@npm:^5.0.0-dev.20230207": - version: 5.0.2 - resolution: "typescript@npm:5.0.2" - bin: - tsc: bin/tsc - tsserver: bin/tsserver - checksum: bef1dcd166acfc6934b2ec4d72f93edb8961a5fab36b8dd2aaf6f4f4cd5c0210f2e0850aef4724f3b4913d5aef203a94a28ded731b370880c8bcff7e4ff91fc1 - languageName: node - linkType: hard - -"typescript@patch:typescript@^5.0.0-dev.20230207#~builtin": - version: 5.0.2 - resolution: "typescript@patch:typescript@npm%3A5.0.2#~builtin::version=5.0.2&hash=701156" - bin: - tsc: bin/tsc - tsserver: bin/tsserver - checksum: bdbf3d0aac0d6cf010fbe0536753dc19f278eb4aba88140dcd25487dfe1c56ca8b33abc0dcd42078790a939b08ebc4046f3e9bb961d77d3d2c3cfa9829da4d53 - languageName: node - linkType: hard - -"unique-filename@npm:^2.0.0": - version: 2.0.1 - resolution: "unique-filename@npm:2.0.1" - dependencies: - unique-slug: ^3.0.0 - checksum: 807acf3381aff319086b64dc7125a9a37c09c44af7620bd4f7f3247fcd5565660ac12d8b80534dcbfd067e6fe88a67e621386dd796a8af828d1337a8420a255f - languageName: node - linkType: hard - -"unique-slug@npm:^3.0.0": - version: 3.0.0 - resolution: "unique-slug@npm:3.0.0" - dependencies: - imurmurhash: ^0.1.4 - checksum: 49f8d915ba7f0101801b922062ee46b7953256c93ceca74303bd8e6413ae10aa7e8216556b54dc5382895e8221d04f1efaf75f945c2e4a515b4139f77aa6640c - languageName: node - linkType: hard - -"util-deprecate@npm:^1.0.1": - version: 1.0.2 - resolution: "util-deprecate@npm:1.0.2" - checksum: 474acf1146cb2701fe3b074892217553dfcf9a031280919ba1b8d651a068c9b15d863b7303cb15bd00a862b498e6cf4ad7b4a08fb134edd5a6f7641681cb54a2 - languageName: node - linkType: hard - -"webidl-conversions@npm:^3.0.0": - version: 3.0.1 - resolution: "webidl-conversions@npm:3.0.1" - checksum: c92a0a6ab95314bde9c32e1d0a6dfac83b578f8fa5f21e675bc2706ed6981bc26b7eb7e6a1fab158e5ce4adf9caa4a0aee49a52505d4d13c7be545f15021b17c - languageName: node - linkType: hard - -"whatwg-url@npm:^5.0.0": - version: 5.0.0 - resolution: "whatwg-url@npm:5.0.0" - dependencies: - tr46: ~0.0.3 - webidl-conversions: ^3.0.0 - checksum: b8daed4ad3356cc4899048a15b2c143a9aed0dfae1f611ebd55073310c7b910f522ad75d727346ad64203d7e6c79ef25eafd465f4d12775ca44b90fa82ed9e2c - languageName: node - linkType: hard - -"which@npm:^2.0.2": - version: 2.0.2 - resolution: "which@npm:2.0.2" - dependencies: - isexe: ^2.0.0 - bin: - node-which: ./bin/node-which - checksum: 1a5c563d3c1b52d5f893c8b61afe11abc3bab4afac492e8da5bde69d550de701cf9806235f20a47b5c8fa8a1d6a9135841de2596535e998027a54589000e66d1 - languageName: node - linkType: hard - -"wide-align@npm:^1.1.5": - version: 1.1.5 - resolution: "wide-align@npm:1.1.5" - dependencies: - string-width: ^1.0.2 || 2 || 3 || 4 - checksum: d5fc37cd561f9daee3c80e03b92ed3e84d80dde3365a8767263d03dacfc8fa06b065ffe1df00d8c2a09f731482fcacae745abfbb478d4af36d0a891fad4834d3 - languageName: node - linkType: hard - -"widest-line@npm:^4.0.1": - version: 4.0.1 - resolution: "widest-line@npm:4.0.1" - dependencies: - string-width: ^5.0.1 - checksum: 64c48cf27171221be5f86fc54b94dd29879165bdff1a7aa92dde723d9a8c99fb108312768a5d62c8c2b80b701fa27bbd36a1ddc58367585cd45c0db7920a0cba - languageName: node - linkType: hard - -"wrap-ansi@npm:^7.0.0": - version: 7.0.0 - resolution: "wrap-ansi@npm:7.0.0" - dependencies: - ansi-styles: ^4.0.0 - string-width: ^4.1.0 - strip-ansi: ^6.0.0 - checksum: a790b846fd4505de962ba728a21aaeda189b8ee1c7568ca5e817d85930e06ef8d1689d49dbf0e881e8ef84436af3a88bc49115c2e2788d841ff1b8b5b51a608b - languageName: node - linkType: hard - -"wrap-ansi@npm:^8.1.0": - version: 8.1.0 - resolution: "wrap-ansi@npm:8.1.0" - dependencies: - ansi-styles: ^6.1.0 - string-width: ^5.0.1 - strip-ansi: ^7.0.1 - checksum: 371733296dc2d616900ce15a0049dca0ef67597d6394c57347ba334393599e800bab03c41d4d45221b6bc967b8c453ec3ae4749eff3894202d16800fdfe0e238 - languageName: node - linkType: hard - -"wrappy@npm:1": - version: 1.0.2 - resolution: "wrappy@npm:1.0.2" - checksum: 159da4805f7e84a3d003d8841557196034155008f817172d4e986bd591f74aa82aa7db55929a54222309e01079a65a92a9e6414da5a6aa4b01ee44a511ac3ee5 - languageName: node - linkType: hard - -"ws@npm:^8.12.0": - version: 8.13.0 - resolution: "ws@npm:8.13.0" - peerDependencies: - bufferutil: ^4.0.1 - utf-8-validate: ">=5.0.2" - peerDependenciesMeta: - bufferutil: - optional: true - utf-8-validate: - optional: true - checksum: 53e991bbf928faf5dc6efac9b8eb9ab6497c69feeb94f963d648b7a3530a720b19ec2e0ec037344257e05a4f35bd9ad04d9de6f289615ffb133282031b18c61c - languageName: node - linkType: hard - -"y18n@npm:^5.0.5": - version: 5.0.8 - resolution: "y18n@npm:5.0.8" - checksum: 54f0fb95621ee60898a38c572c515659e51cc9d9f787fb109cef6fde4befbe1c4602dc999d30110feee37456ad0f1660fa2edcfde6a9a740f86a290999550d30 - languageName: node - linkType: hard - -"yallist@npm:^4.0.0": - version: 4.0.0 - resolution: "yallist@npm:4.0.0" - checksum: 343617202af32df2a15a3be36a5a8c0c8545208f3d3dfbc6bb7c3e3b7e8c6f8e7485432e4f3b88da3031a6e20afa7c711eded32ddfb122896ac5d914e75848d5 - languageName: node - linkType: hard - -"yargs-parser@npm:^21.1.1": - version: 21.1.1 - resolution: "yargs-parser@npm:21.1.1" - checksum: ed2d96a616a9e3e1cc7d204c62ecc61f7aaab633dcbfab2c6df50f7f87b393993fe6640d017759fe112d0cb1e0119f2b4150a87305cc873fd90831c6a58ccf1c - languageName: node - linkType: hard - -"yargs@npm:^17.7.1": - version: 17.7.1 - resolution: "yargs@npm:17.7.1" - dependencies: - cliui: ^8.0.1 - escalade: ^3.1.1 - get-caller-file: ^2.0.5 - require-directory: ^2.1.1 - string-width: ^4.2.3 - y18n: ^5.0.5 - yargs-parser: ^21.1.1 - checksum: 3d8a43c336a4942bc68080768664aca85c7bd406f018bad362fd255c41c8f4e650277f42fd65d543fce99e084124ddafee7bbfc1a5c6a8fda4cec78609dcf8d4 - languageName: node - linkType: hard - -"yoga-layout-prebuilt@npm:^1.9.6": - version: 1.10.0 - resolution: "yoga-layout-prebuilt@npm:1.10.0" - dependencies: - "@types/yoga-layout": 1.9.2 - checksum: 6954c7c7b04c585a1c974391bea4734611adb85702b5e9131549a1d3dc5b94e69bcfea34121cdaeb5e702663bf290fcce5374910128e54d1031503a57c062865 - languageName: node - linkType: hard From 869943ea097b60892cbe4622f78b772ef771da53 Mon Sep 17 00:00:00 2001 From: Mark Erikson Date: Wed, 23 Aug 2023 20:53:17 -0400 Subject: [PATCH 259/412] Try to replace all uses of `/dist/` in our type imports --- .../src/dynamicMiddleware/react/index.ts | 6 ++-- packages/toolkit/src/index.ts | 8 +++++ packages/toolkit/src/query/index.ts | 34 +++++++++++++++++-- .../toolkit/src/query/react/ApiProvider.tsx | 2 +- .../toolkit/src/query/react/buildHooks.ts | 34 +++++++++---------- packages/toolkit/src/query/react/index.ts | 16 ++------- packages/toolkit/src/query/react/module.ts | 4 +-- .../toolkit/src/query/react/namedHooks.ts | 2 +- .../query/react/useSerializedStableValue.ts | 4 +-- 9 files changed, 68 insertions(+), 42 deletions(-) diff --git a/packages/toolkit/src/dynamicMiddleware/react/index.ts b/packages/toolkit/src/dynamicMiddleware/react/index.ts index 7315f93394..7be00ef80a 100644 --- a/packages/toolkit/src/dynamicMiddleware/react/index.ts +++ b/packages/toolkit/src/dynamicMiddleware/react/index.ts @@ -4,7 +4,7 @@ import type { Dispatch as ReduxDispatch, Middleware, } from 'redux' -import type { ExtractDispatchExtensions } from '@reduxjs/toolkit/dist/tsHelpers' +import type { TSHelpersExtractDispatchExtensions } from '@reduxjs/toolkit' import { createDynamicMiddleware as cDM } from '@reduxjs/toolkit' import type { ReactReduxContextValue } from 'react-redux' import { @@ -18,13 +18,13 @@ import type { GetDispatch, GetState, MiddlewareApiConfig, -} from '@reduxjs/toolkit/dist/dynamicMiddleware/types' +} from '@reduxjs/toolkit' export type UseDispatchWithMiddlewareHook< Middlewares extends Middleware[] = [], State = any, Dispatch extends ReduxDispatch = ReduxDispatch -> = () => ExtractDispatchExtensions & Dispatch +> = () => TSHelpersExtractDispatchExtensions & Dispatch export type CreateDispatchWithMiddlewareHook< State = any, diff --git a/packages/toolkit/src/index.ts b/packages/toolkit/src/index.ts index b2153e7804..70ef916dc4 100644 --- a/packages/toolkit/src/index.ts +++ b/packages/toolkit/src/index.ts @@ -185,6 +185,12 @@ export { TaskAbortError, } from './listenerMiddleware/index' +export type { + DynamicMiddlewareInstance, + GetDispatch, + GetState, + MiddlewareApiConfig, +} from './dynamicMiddleware/types' export { createDynamicMiddleware } from './dynamicMiddleware/index' export { @@ -197,3 +203,5 @@ export type { AutoBatchOptions } from './autoBatchEnhancer' export { combineSlices } from './combineSlices' export type { WithSlice } from './combineSlices' + +export type { ExtractDispatchExtensions as TSHelpersExtractDispatchExtensions } from './tsHelpers' diff --git a/packages/toolkit/src/query/index.ts b/packages/toolkit/src/query/index.ts index dc2b2c5e96..5ca860f5e6 100644 --- a/packages/toolkit/src/query/index.ts +++ b/packages/toolkit/src/query/index.ts @@ -1,5 +1,11 @@ +export type { + QuerySubState, + SubscriptionOptions, + QueryKeys, + RootState, +} from './core/apiState' export { QueryStatus } from './core/apiState' -export type { Api, Module, ApiModules } from './apiTypes' +export type { Api, ApiContext, Module, ApiModules } from './apiTypes' export type { BaseQueryApi, BaseQueryEnhancer, @@ -11,6 +17,9 @@ export type { QueryDefinition, MutationDefinition, TagDescription, + QueryArgFrom, + ResultTypeFrom, + DefinitionType, } from './endpointDefinitions' export { fetchBaseQuery } from './fetchBaseQuery' export type { @@ -21,10 +30,31 @@ export type { export { retry } from './retry' export { setupListeners } from './core/setupListeners' export { skipToken } from './core/buildSelectors' -export type { SkipToken } from './core/buildSelectors' +export type { + QueryResultSelectorResult, + MutationResultSelectorResult, + SkipToken, +} from './core/buildSelectors' +export type { + QueryActionCreatorResult, + MutationActionCreatorResult, +} from './core/buildInitiate' export type { CreateApi, CreateApiOptions } from './createApi' export { buildCreateApi } from './createApi' export { fakeBaseQuery } from './fakeBaseQuery' export { copyWithStructuralSharing } from './utils/copyWithStructuralSharing' export { createApi, coreModule } from './core' +export type { + ApiEndpointMutation, + ApiEndpointQuery, + CoreModule, + PrefetchOptions, +} from './core/module' export { defaultSerializeQueryArgs } from './defaultSerializeQueryArgs' +export type { SerializeQueryArgs } from './defaultSerializeQueryArgs' + +export type { + Id as TSHelpersId, + NoInfer as TSHelpersNoInfer, + Override as TSHelpersOverride, +} from './tsHelpers' diff --git a/packages/toolkit/src/query/react/ApiProvider.tsx b/packages/toolkit/src/query/react/ApiProvider.tsx index abaef8c2cc..5be6ea06ad 100644 --- a/packages/toolkit/src/query/react/ApiProvider.tsx +++ b/packages/toolkit/src/query/react/ApiProvider.tsx @@ -5,7 +5,7 @@ import React from 'react' import type { ReactReduxContextValue } from 'react-redux' import { Provider } from 'react-redux' import { setupListeners } from '@reduxjs/toolkit/query' -import type { Api } from '@reduxjs/toolkit/dist/query/apiTypes' +import type { Api } from '@reduxjs/toolkit/query' /** * Can be used as a `Provider` if you **do not already have a Redux store**. diff --git a/packages/toolkit/src/query/react/buildHooks.ts b/packages/toolkit/src/query/react/buildHooks.ts index d8717dbbac..440d24bda8 100644 --- a/packages/toolkit/src/query/react/buildHooks.ts +++ b/packages/toolkit/src/query/react/buildHooks.ts @@ -21,37 +21,37 @@ import type { SubscriptionOptions, QueryKeys, RootState, -} from '@reduxjs/toolkit/dist/query/core/apiState' +} from '@reduxjs/toolkit/query' import type { EndpointDefinitions, MutationDefinition, QueryDefinition, QueryArgFrom, ResultTypeFrom, -} from '@reduxjs/toolkit/dist/query/endpointDefinitions' +} from '@reduxjs/toolkit/query' import type { QueryResultSelectorResult, MutationResultSelectorResult, SkipToken, -} from '@reduxjs/toolkit/dist/query/core/buildSelectors' +} from '@reduxjs/toolkit/query' import type { QueryActionCreatorResult, MutationActionCreatorResult, -} from '@reduxjs/toolkit/dist/query/core/buildInitiate' -import type { SerializeQueryArgs } from '@reduxjs/toolkit/dist/query/defaultSerializeQueryArgs' +} from '@reduxjs/toolkit/query' +import type { SerializeQueryArgs } from '@reduxjs/toolkit/query' import { shallowEqual } from 'react-redux' -import type { Api, ApiContext } from '@reduxjs/toolkit/dist/query/apiTypes' +import type { Api, ApiContext } from '@reduxjs/toolkit/query' import type { - Id, - NoInfer, - Override, -} from '@reduxjs/toolkit/dist/query/tsHelpers' + TSHelpersId, + TSHelpersNoInfer, + TSHelpersOverride, +} from '@reduxjs/toolkit/query' import type { ApiEndpointMutation, ApiEndpointQuery, CoreModule, PrefetchOptions, -} from '@reduxjs/toolkit/dist/query/core/module' +} from '@reduxjs/toolkit/query' import type { ReactHooksModuleOptions } from './module' import { useStableQueryArgs } from './useSerializedStableValue' import type { UninitializedValue } from './constants' @@ -378,7 +378,7 @@ export type UseQueryStateOptions< export type UseQueryStateResult< _ extends QueryDefinition, R -> = NoInfer +> = TSHelpersNoInfer /** * Helper type to manually type the result @@ -391,7 +391,7 @@ export type TypedUseQueryStateResult< R = UseQueryStateDefaultResult< QueryDefinition > -> = NoInfer +> = TSHelpersNoInfer type UseQueryStateBaseResult> = QuerySubState & { @@ -424,15 +424,15 @@ type UseQueryStateBaseResult> = } type UseQueryStateDefaultResult> = - Id< - | Override< + TSHelpersId< + | TSHelpersOverride< Extract< UseQueryStateBaseResult, { status: QueryStatus.uninitialized } >, { isUninitialized: true } > - | Override< + | TSHelpersOverride< UseQueryStateBaseResult, | { isLoading: true; isFetching: boolean; data: undefined } | ({ @@ -481,7 +481,7 @@ export type UseMutationStateOptions< export type UseMutationStateResult< D extends MutationDefinition, R -> = NoInfer & { +> = TSHelpersNoInfer & { originalArgs?: QueryArgFrom /** * Resets the hook state to it's initial `uninitialized` state. diff --git a/packages/toolkit/src/query/react/index.ts b/packages/toolkit/src/query/react/index.ts index f06beebefd..bb851e1f99 100644 --- a/packages/toolkit/src/query/react/index.ts +++ b/packages/toolkit/src/query/react/index.ts @@ -1,17 +1,5 @@ -import { coreModule, buildCreateApi, CreateApi } from '@reduxjs/toolkit/query' -import { reactHooksModule, reactHooksModuleName } from './module' - -import type { MutationHooks, QueryHooks } from './buildHooks' -import type { - EndpointDefinitions, - QueryDefinition, - MutationDefinition, - QueryArgFrom, -} from '@reduxjs/toolkit/dist/query/endpointDefinitions' -import type { BaseQueryFn } from '@reduxjs/toolkit/dist/query/baseQueryTypes' - -import type { QueryKeys } from '@reduxjs/toolkit/dist/query/core/apiState' -import type { PrefetchOptions } from '@reduxjs/toolkit/dist/query/core/module' +import { coreModule, buildCreateApi } from '@reduxjs/toolkit/query' +import { reactHooksModule } from './module' export * from '@reduxjs/toolkit/query' export { ApiProvider } from './ApiProvider' diff --git a/packages/toolkit/src/query/react/module.ts b/packages/toolkit/src/query/react/module.ts index b9bc6f999b..2b368b3627 100644 --- a/packages/toolkit/src/query/react/module.ts +++ b/packages/toolkit/src/query/react/module.ts @@ -6,11 +6,11 @@ import type { QueryDefinition, MutationDefinition, QueryArgFrom, -} from '@reduxjs/toolkit/dist/query/endpointDefinitions' +} from '@reduxjs/toolkit/query' import type { Api, Module } from '../apiTypes' import { capitalize } from '../utils' import { safeAssign } from '../tsHelpers' -import type { BaseQueryFn } from '@reduxjs/toolkit/dist/query/baseQueryTypes' +import type { BaseQueryFn } from '@reduxjs/toolkit/query' import type { HooksWithUniqueNames } from './namedHooks' diff --git a/packages/toolkit/src/query/react/namedHooks.ts b/packages/toolkit/src/query/react/namedHooks.ts index 5c93404459..ae146cf4cf 100644 --- a/packages/toolkit/src/query/react/namedHooks.ts +++ b/packages/toolkit/src/query/react/namedHooks.ts @@ -4,7 +4,7 @@ import type { EndpointDefinitions, MutationDefinition, QueryDefinition, -} from '@reduxjs/toolkit/dist/query/endpointDefinitions' +} from '@reduxjs/toolkit/query' export type HooksWithUniqueNames = keyof Definitions extends infer Keys diff --git a/packages/toolkit/src/query/react/useSerializedStableValue.ts b/packages/toolkit/src/query/react/useSerializedStableValue.ts index 163f63eecd..52f87d7158 100644 --- a/packages/toolkit/src/query/react/useSerializedStableValue.ts +++ b/packages/toolkit/src/query/react/useSerializedStableValue.ts @@ -1,6 +1,6 @@ import { useEffect, useRef, useMemo } from 'react' -import type { SerializeQueryArgs } from '@reduxjs/toolkit/dist/query/defaultSerializeQueryArgs' -import type { EndpointDefinition } from '@reduxjs/toolkit/dist/query/endpointDefinitions' +import type { SerializeQueryArgs } from '@reduxjs/toolkit/query' +import type { EndpointDefinition } from '@reduxjs/toolkit/query' export function useStableQueryArgs( queryArgs: T, From 25b84d00ce22d991b4f299fc805aa1ac109a3846 Mon Sep 17 00:00:00 2001 From: Mark Erikson Date: Sat, 26 Aug 2023 15:44:41 -0400 Subject: [PATCH 260/412] Ignore FalseCJS warnings --- .github/workflows/tests.yml | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 7008935a2a..2922ff98df 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -188,12 +188,6 @@ jobs: - name: Run test step run: yarn test - if: matrix.example != 'are-the-types-wrong' - - - name: Run test step (attw) - # Ignore "FalseCJS" errors in the `attw` job - run: yarn test -n FalseCJS - if: matrix.example == 'are-the-types-wrong' are-the-types-wrong: name: Check package config with are-the-types-wrong @@ -217,4 +211,4 @@ jobs: run: ls -l . - name: Run are-the-types-wrong - run: npx @arethetypeswrong/cli ./package.tgz --format table + run: npx @arethetypeswrong/cli ./package.tgz --format table --ignore-rules false-cjs From 6644bc01c65cf48c847a8f069b7ad325be31f9ba Mon Sep 17 00:00:00 2001 From: Eric Crowell Date: Thu, 29 Jun 2023 10:29:32 +0200 Subject: [PATCH 261/412] fix(RTKQuery): Fixed issue with coreModuleName symbol portability when exporting declarations in a typescript project --- packages/toolkit/src/query/core/index.ts | 2 +- packages/toolkit/src/query/index.ts | 2 +- packages/toolkit/src/query/react/index.ts | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/toolkit/src/query/core/index.ts b/packages/toolkit/src/query/core/index.ts index 837dc6a567..f9765d9f08 100644 --- a/packages/toolkit/src/query/core/index.ts +++ b/packages/toolkit/src/query/core/index.ts @@ -3,4 +3,4 @@ import { coreModule, coreModuleName } from './module' const createApi = /* @__PURE__ */ buildCreateApi(coreModule()) -export { createApi, coreModule } +export { createApi, coreModule, coreModuleName } diff --git a/packages/toolkit/src/query/index.ts b/packages/toolkit/src/query/index.ts index 5ca860f5e6..902014d1c2 100644 --- a/packages/toolkit/src/query/index.ts +++ b/packages/toolkit/src/query/index.ts @@ -43,7 +43,7 @@ export type { CreateApi, CreateApiOptions } from './createApi' export { buildCreateApi } from './createApi' export { fakeBaseQuery } from './fakeBaseQuery' export { copyWithStructuralSharing } from './utils/copyWithStructuralSharing' -export { createApi, coreModule } from './core' +export { createApi, coreModule, coreModuleName } from './core' export type { ApiEndpointMutation, ApiEndpointQuery, diff --git a/packages/toolkit/src/query/react/index.ts b/packages/toolkit/src/query/react/index.ts index bb851e1f99..2da2a9307e 100644 --- a/packages/toolkit/src/query/react/index.ts +++ b/packages/toolkit/src/query/react/index.ts @@ -15,4 +15,4 @@ export type { TypedUseQuerySubscriptionResult, TypedUseMutationResult, } from './buildHooks' -export { createApi, reactHooksModule } +export { createApi, reactHooksModule, reactHooksModuleName } From 4647d98b088d3acf7f81b394e612354f4b81c93f Mon Sep 17 00:00:00 2001 From: Eric Crowell Date: Wed, 12 Jul 2023 14:45:04 +0200 Subject: [PATCH 262/412] fix: Added additional type exports to resolve configureStore inferred typing when including API reducers --- packages/toolkit/src/query/index.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/toolkit/src/query/index.ts b/packages/toolkit/src/query/index.ts index 902014d1c2..191839748e 100644 --- a/packages/toolkit/src/query/index.ts +++ b/packages/toolkit/src/query/index.ts @@ -5,6 +5,7 @@ export type { RootState, } from './core/apiState' export { QueryStatus } from './core/apiState' +export type { QueryCacheKey, RootState, CombinedState } from './core/apiState' export type { Api, ApiContext, Module, ApiModules } from './apiTypes' export type { BaseQueryApi, From 3a323b64f40c927ab32e15e3fddf97e36849dfa6 Mon Sep 17 00:00:00 2001 From: Mark Erikson Date: Sat, 26 Aug 2023 16:22:57 -0400 Subject: [PATCH 263/412] Fix additional type exports --- packages/toolkit/src/query/index.ts | 3 ++- packages/toolkit/src/query/react/index.ts | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/toolkit/src/query/index.ts b/packages/toolkit/src/query/index.ts index 191839748e..c75f3b4111 100644 --- a/packages/toolkit/src/query/index.ts +++ b/packages/toolkit/src/query/index.ts @@ -3,9 +3,10 @@ export type { SubscriptionOptions, QueryKeys, RootState, + QueryCacheKey, + CombinedState, } from './core/apiState' export { QueryStatus } from './core/apiState' -export type { QueryCacheKey, RootState, CombinedState } from './core/apiState' export type { Api, ApiContext, Module, ApiModules } from './apiTypes' export type { BaseQueryApi, diff --git a/packages/toolkit/src/query/react/index.ts b/packages/toolkit/src/query/react/index.ts index 2da2a9307e..f0298381fc 100644 --- a/packages/toolkit/src/query/react/index.ts +++ b/packages/toolkit/src/query/react/index.ts @@ -1,5 +1,5 @@ import { coreModule, buildCreateApi } from '@reduxjs/toolkit/query' -import { reactHooksModule } from './module' +import { reactHooksModule, reactHooksModuleName } from './module' export * from '@reduxjs/toolkit/query' export { ApiProvider } from './ApiProvider' From 3055fbb946835b3b13084a38eb77ff3a3cbdd326 Mon Sep 17 00:00:00 2001 From: Julien Karst Date: Sat, 16 Jul 2022 12:40:49 +0200 Subject: [PATCH 264/412] fix(2448): remove abort controller --- packages/toolkit/src/createAsyncThunk.ts | 34 +----------------------- 1 file changed, 1 insertion(+), 33 deletions(-) diff --git a/packages/toolkit/src/createAsyncThunk.ts b/packages/toolkit/src/createAsyncThunk.ts index eca550d201..c21e761f42 100644 --- a/packages/toolkit/src/createAsyncThunk.ts +++ b/packages/toolkit/src/createAsyncThunk.ts @@ -553,36 +553,6 @@ export const createAsyncThunk = (() => { }) ) - let displayedWarning = false - - const AC = - typeof AbortController !== 'undefined' - ? AbortController - : class implements AbortController { - signal = { - aborted: false, - addEventListener() {}, - dispatchEvent() { - return false - }, - onabort() {}, - removeEventListener() {}, - reason: undefined, - throwIfAborted() {}, - } - abort() { - if (process.env.NODE_ENV !== 'production') { - if (!displayedWarning) { - displayedWarning = true - console.info( - `This platform does not implement AbortController. -If you want to use the AbortController to react to \`abort\` events, please consider importing a polyfill like 'abortcontroller-polyfill/dist/abortcontroller-polyfill-only'.` - ) - } - } - } - } - function actionCreator( arg: ThunkArg ): AsyncThunkAction { @@ -591,10 +561,9 @@ If you want to use the AbortController to react to \`abort\` events, please cons ? options.idGenerator(arg) : nanoid() - const abortController = new AC() + const abortController = new AbortController() let abortReason: string | undefined - let started = false function abort(reason?: string) { abortReason = reason abortController.abort() @@ -615,7 +584,6 @@ If you want to use the AbortController to react to \`abort\` events, please cons message: 'Aborted due to condition callback returning false.', } } - started = true const abortedPromise = new Promise((_, reject) => abortController.signal.addEventListener('abort', () => From c23f7eb7395c03794e870d0060d4f718bdc6ca7b Mon Sep 17 00:00:00 2001 From: Julien Karst Date: Sun, 16 Oct 2022 15:20:52 +0200 Subject: [PATCH 265/412] test: update spec for abort controller --- packages/toolkit/src/tests/createAsyncThunk.test.ts | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/packages/toolkit/src/tests/createAsyncThunk.test.ts b/packages/toolkit/src/tests/createAsyncThunk.test.ts index fbc2b8a74a..8f5dfc0a5e 100644 --- a/packages/toolkit/src/tests/createAsyncThunk.test.ts +++ b/packages/toolkit/src/tests/createAsyncThunk.test.ts @@ -505,7 +505,7 @@ describe('createAsyncThunk with abortController', () => { vi.resetModules() }) - test('calling `abort` on an asyncThunk works with a FallbackAbortController if no global abortController is not available', async () => { + test('calling a thunk made with createAsyncThunk should fail if no global abortController is not available', async () => { const longRunningAsyncThunk = freshlyLoadedModule.createAsyncThunk( 'longRunning', async () => { @@ -513,14 +513,7 @@ describe('createAsyncThunk with abortController', () => { } ) - store.dispatch(longRunningAsyncThunk()).abort() - // should only log once, even if called twice - store.dispatch(longRunningAsyncThunk()).abort() - - expect(getLog().log).toMatchInlineSnapshot(` - "This platform does not implement AbortController. - If you want to use the AbortController to react to \`abort\` events, please consider importing a polyfill like 'abortcontroller-polyfill/dist/abortcontroller-polyfill-only'." - `) + expect(longRunningAsyncThunk()).toThrow("AbortController is not defined") }) }) }) From 09c35d7ab83e2bbb299be6c7b30b8cc56aa05c14 Mon Sep 17 00:00:00 2001 From: Mark Erikson Date: Sat, 26 Aug 2023 18:28:00 -0400 Subject: [PATCH 266/412] Update thunk and React-Redux deps --- packages/toolkit/package.json | 6 +++--- yarn.lock | 32 ++++++++++++++++---------------- 2 files changed, 19 insertions(+), 19 deletions(-) diff --git a/packages/toolkit/package.json b/packages/toolkit/package.json index f5ad41c2f8..1c82504777 100644 --- a/packages/toolkit/package.json +++ b/packages/toolkit/package.json @@ -111,13 +111,13 @@ ], "dependencies": { "immer": "^10.0.2", - "redux": "5.0.0-beta.0", - "redux-thunk": "3.0.0-alpha.3", + "redux": "^5.0.0-beta.0", + "redux-thunk": "^3.0.0-beta.0", "reselect": "^5.0.0-alpha.2" }, "peerDependencies": { "react": "^16.9.0 || ^17.0.0 || ^18", - "react-redux": "^7.2.1 || ^8.0.2" + "react-redux": "^7.2.1 || ^8.0.2 || ^9.0.0-alpha.1" }, "peerDependenciesMeta": { "react": { diff --git a/yarn.lock b/yarn.lock index c9e539d492..886d1920bf 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6784,8 +6784,8 @@ __metadata: node-fetch: ^2.6.1 prettier: ^2.2.1 query-string: ^7.0.1 - redux: 5.0.0-beta.0 - redux-thunk: 3.0.0-alpha.3 + redux: ^5.0.0-beta.0 + redux-thunk: ^3.0.0-beta.0 reselect: ^5.0.0-alpha.2 rimraf: ^3.0.2 size-limit: ^4.11.0 @@ -6797,7 +6797,7 @@ __metadata: yargs: ^15.3.1 peerDependencies: react: ^16.9.0 || ^17.0.0 || ^18 - react-redux: ^7.2.1 || ^8.0.2 + react-redux: ^7.2.1 || ^8.0.2 || ^9.0.0-alpha.1 peerDependenciesMeta: react: optional: true @@ -24660,15 +24660,6 @@ fsevents@^1.2.7: languageName: node linkType: hard -"redux-thunk@npm:3.0.0-alpha.3": - version: 3.0.0-alpha.3 - resolution: "redux-thunk@npm:3.0.0-alpha.3" - peerDependencies: - redux: ^4 - checksum: a5be77887b422b3182ff7fae617ec552cd5f830afb326d83af32a430c3eb439c942a38c3691e5c975119e37787974172dbc0139f7782cbfaeea5c1292fa123ed - languageName: node - linkType: hard - "redux-thunk@npm:^2.4.1": version: 2.4.1 resolution: "redux-thunk@npm:2.4.1" @@ -24687,10 +24678,12 @@ fsevents@^1.2.7: languageName: node linkType: hard -"redux@npm:5.0.0-beta.0": - version: 5.0.0-beta.0 - resolution: "redux@npm:5.0.0-beta.0" - checksum: 11df373e219f2f515ee1bda1a19a1ba5de02d8d5c874800ec353179dcd106eddd54432946fd0ab37c47f99f8fe53f820a6404c14da7f039a46022187e9469d2d +"redux-thunk@npm:^3.0.0-beta.0": + version: 3.0.0-beta.0 + resolution: "redux-thunk@npm:3.0.0-beta.0" + peerDependencies: + redux: ^4 || ^5.0.0-beta.0 + checksum: 1609e18a9fb56ab7403d760999996b50e136fcf7411ec9d809e9a4afa4187bf0ab545652c05ffbfca2e0397e59e6baf2ae0d35631a30bf8ba20af1205e98e0fe languageName: node linkType: hard @@ -24712,6 +24705,13 @@ fsevents@^1.2.7: languageName: node linkType: hard +"redux@npm:^5.0.0-beta.0": + version: 5.0.0-beta.0 + resolution: "redux@npm:5.0.0-beta.0" + checksum: 11df373e219f2f515ee1bda1a19a1ba5de02d8d5c874800ec353179dcd106eddd54432946fd0ab37c47f99f8fe53f820a6404c14da7f039a46022187e9469d2d + languageName: node + linkType: hard + "reftools@npm:^1.1.9": version: 1.1.9 resolution: "reftools@npm:1.1.9" From 43bdd5f419c68414e5d36a95be50411902b74335 Mon Sep 17 00:00:00 2001 From: Mark Erikson Date: Sat, 26 Aug 2023 18:43:17 -0400 Subject: [PATCH 267/412] Work around NPM issue when publishing --- packages/toolkit/.release-it.json | 3 +++ 1 file changed, 3 insertions(+) diff --git a/packages/toolkit/.release-it.json b/packages/toolkit/.release-it.json index eb5c9f0c18..a049899d87 100644 --- a/packages/toolkit/.release-it.json +++ b/packages/toolkit/.release-it.json @@ -4,5 +4,8 @@ }, "git": { "tagName": "v${version}" + }, + "npm": { + "versionArgs": ["--workspaces-update=false"] } } From 898ed5a7a8b384ad52530562dd1e1d95ce8a1510 Mon Sep 17 00:00:00 2001 From: Mark Erikson Date: Sat, 26 Aug 2023 18:54:37 -0400 Subject: [PATCH 268/412] Release 2.0.0-beta.1 --- packages/toolkit/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/toolkit/package.json b/packages/toolkit/package.json index 1c82504777..21d4bd2955 100644 --- a/packages/toolkit/package.json +++ b/packages/toolkit/package.json @@ -1,6 +1,6 @@ { "name": "@reduxjs/toolkit", - "version": "2.0.0-beta.0", + "version": "2.0.0-beta.1", "description": "The official, opinionated, batteries-included toolset for efficient Redux development", "author": "Mark Erikson ", "license": "MIT", From 75e99d786e6a0f56a2f2c9f2bbf39e27a9e9e696 Mon Sep 17 00:00:00 2001 From: "ben.durrant" Date: Wed, 6 Sep 2023 18:32:18 +0100 Subject: [PATCH 269/412] Switch advised syntax for create.reducer --- docs/api/createSlice.mdx | 4 ++-- .../toolkit/src/tests/createSlice.typetest.ts | 22 ++++++++----------- 2 files changed, 11 insertions(+), 15 deletions(-) diff --git a/docs/api/createSlice.mdx b/docs/api/createSlice.mdx index 6b66562bac..10f8879796 100644 --- a/docs/api/createSlice.mdx +++ b/docs/api/createSlice.mdx @@ -161,7 +161,7 @@ const todosSlice = createSlice({ todos: [], } as TodoState, reducers: (create) => ({ - deleteTodo: create.reducer((state, action: PayloadAction) => { + deleteTodo: create.reducer((state, action) => { state.todos.splice(action.payload, 1) }), addTodo: create.preparedReducer( @@ -209,7 +209,7 @@ A standard slice case reducer. - **reducer** The slice case reducer to use. ```ts no-transpile -create.reducer((state, action: PayloadAction) => { +create.reducer((state, action) => { state.todos.push(action.payload) }) ``` diff --git a/packages/toolkit/src/tests/createSlice.typetest.ts b/packages/toolkit/src/tests/createSlice.typetest.ts index 55d3384508..542ab20f83 100644 --- a/packages/toolkit/src/tests/createSlice.typetest.ts +++ b/packages/toolkit/src/tests/createSlice.typetest.ts @@ -599,18 +599,14 @@ const value = actionCreators.anyKey }>() return { - normalReducer: create.reducer( - (state, action: PayloadAction) => { - expectType(state) - expectType(action.payload) - } - ), - optionalReducer: create.reducer( - (state, action: PayloadAction) => { - expectType(state) - expectType(action.payload) - } - ), + normalReducer: create.reducer((state, action) => { + expectType(state) + expectType(action.payload) + }), + optionalReducer: create.reducer((state, action) => { + expectType(state) + expectType(action.payload) + }), noActionReducer: create.reducer((state) => { expectType(state) }), @@ -788,7 +784,7 @@ const value = actionCreators.anyKey start: create.reducer((state) => { state.status = 'loading' }), - success: create.reducer((state, action: PayloadAction) => { + success: create.reducer((state, action) => { state.data = castDraft(action.payload) state.status = 'finished' }), From 04085b32c1f7bfe1d5438ffb18cb2975a7b2f91e Mon Sep 17 00:00:00 2001 From: "ben.durrant" Date: Wed, 6 Sep 2023 18:33:57 +0100 Subject: [PATCH 270/412] remove unused PayloadAction import --- docs/api/createSlice.mdx | 1 - 1 file changed, 1 deletion(-) diff --git a/docs/api/createSlice.mdx b/docs/api/createSlice.mdx index 10f8879796..d911efba05 100644 --- a/docs/api/createSlice.mdx +++ b/docs/api/createSlice.mdx @@ -142,7 +142,6 @@ The main benefit of this is that you can create [async thunks](./createAsyncThun ```ts title="Creator callback for reducers" import { createSlice, nanoid } from '@reduxjs/toolkit' -import type { PayloadAction } from '@reduxjs/toolkit' interface Item { id: string From 7306e848fead59f217cddbef56c4573be7ce04bd Mon Sep 17 00:00:00 2001 From: Arya Emami Date: Wed, 13 Sep 2023 19:22:54 -0500 Subject: [PATCH 271/412] added output selector fields to the selector functions created with createDraftSafeSelector --- packages/toolkit/src/createDraftSafeSelector.ts | 1 + packages/toolkit/src/tests/createDraftSafeSelector.test.ts | 7 +++++++ 2 files changed, 8 insertions(+) diff --git a/packages/toolkit/src/createDraftSafeSelector.ts b/packages/toolkit/src/createDraftSafeSelector.ts index 9e1bb0a6da..fb853397be 100644 --- a/packages/toolkit/src/createDraftSafeSelector.ts +++ b/packages/toolkit/src/createDraftSafeSelector.ts @@ -9,6 +9,7 @@ export const createDraftSafeSelectorCreator: typeof createSelectorCreator = ( const selector = createSelector(...args) const wrappedSelector = (value: unknown, ...rest: unknown[]) => selector(isDraft(value) ? current(value) : value, ...rest) + Object.assign(wrappedSelector, selector) return wrappedSelector as any } } diff --git a/packages/toolkit/src/tests/createDraftSafeSelector.test.ts b/packages/toolkit/src/tests/createDraftSafeSelector.test.ts index 1ae52b56d8..ab95fc48d8 100644 --- a/packages/toolkit/src/tests/createDraftSafeSelector.test.ts +++ b/packages/toolkit/src/tests/createDraftSafeSelector.test.ts @@ -11,6 +11,13 @@ test('handles normal values correctly', () => { let state = { value: 1 } expect(unsafeSelector(state)).toBe(1) expect(draftSafeSelector(state)).toBe(1) + expect(draftSafeSelector).toHaveProperty('resultFunc') + expect(draftSafeSelector).toHaveProperty('memoizedResultFunc') + expect(draftSafeSelector).toHaveProperty('lastResult') + expect(draftSafeSelector).toHaveProperty('dependencies') + expect(draftSafeSelector).toHaveProperty('recomputations') + expect(draftSafeSelector).toHaveProperty('resetRecomputations') + expect(draftSafeSelector).toHaveProperty('clearCache') state = { value: 2 } expect(unsafeSelector(state)).toBe(2) From 0c498ef3ecc63a8ac8d64b254efc15469b137fcb Mon Sep 17 00:00:00 2001 From: Mark Erikson Date: Sat, 23 Sep 2023 23:35:04 -0400 Subject: [PATCH 272/412] Add esbuild-extra --- packages/toolkit/package.json | 1 + yarn.lock | 49 +++++++++++++++++++++++++++++++++++ 2 files changed, 50 insertions(+) diff --git a/packages/toolkit/package.json b/packages/toolkit/package.json index 21d4bd2955..80a51cb3ba 100644 --- a/packages/toolkit/package.json +++ b/packages/toolkit/package.json @@ -66,6 +66,7 @@ "axios": "^0.19.2", "console-testing-library": "0.6.1", "convert-source-map": "^1.7.0", + "esbuild-extra": "^0.3.1", "eslint": "^7.25.0", "eslint-config-prettier": "^8.3.0", "eslint-config-react-app": "^7.0.1", diff --git a/yarn.lock b/yarn.lock index 886d1920bf..af644112ff 100644 --- a/yarn.lock +++ b/yarn.lock @@ -182,6 +182,16 @@ __metadata: languageName: node linkType: hard +"@ampproject/remapping@npm:^2.2.1": + version: 2.2.1 + resolution: "@ampproject/remapping@npm:2.2.1" + dependencies: + "@jridgewell/gen-mapping": ^0.3.0 + "@jridgewell/trace-mapping": ^0.3.9 + checksum: 03c04fd526acc64a1f4df22651186f3e5ef0a9d6d6530ce4482ec9841269cf7a11dbb8af79237c282d721c5312024ff17529cd72cc4768c11e999b58e2302079 + languageName: node + linkType: hard + "@apideck/better-ajv-errors@npm:^0.3.1": version: 0.3.4 resolution: "@apideck/better-ajv-errors@npm:0.3.4" @@ -6766,6 +6776,7 @@ __metadata: axios: ^0.19.2 console-testing-library: 0.6.1 convert-source-map: ^1.7.0 + esbuild-extra: ^0.3.1 eslint: ^7.25.0 eslint-config-prettier: ^8.3.0 eslint-config-react-app: ^7.0.1 @@ -13866,6 +13877,16 @@ __metadata: languageName: node linkType: hard +"enhanced-resolve@npm:^5.15.0": + version: 5.15.0 + resolution: "enhanced-resolve@npm:5.15.0" + dependencies: + graceful-fs: ^4.2.4 + tapable: ^2.2.0 + checksum: fbd8cdc9263be71cc737aa8a7d6c57b43d6aa38f6cc75dde6fcd3598a130cc465f979d2f4d01bb3bf475acb43817749c79f8eef9be048683602ca91ab52e4f11 + languageName: node + linkType: hard + "enhanced-resolve@npm:^5.9.3": version: 5.9.3 resolution: "enhanced-resolve@npm:5.9.3" @@ -14021,6 +14042,20 @@ __metadata: languageName: node linkType: hard +"esbuild-extra@npm:^0.3.1": + version: 0.3.1 + resolution: "esbuild-extra@npm:0.3.1" + dependencies: + "@ampproject/remapping": ^2.2.1 + convert-source-map: ^2.0.0 + enhanced-resolve: ^5.15.0 + tsconfck: ^2.1.1 + peerDependencies: + esbuild: ">=0.15" + checksum: 3f4dff21ac44325a986b1ecf35fd07b0e87dbabef0ad480a0ce29fbc85220803ac4ed9514f50258a5fb81bb646d830e8d628d1b9cdc29de0d1eac89db58d9bd4 + languageName: node + linkType: hard + "esbuild-runner@npm:^2.2.1": version: 2.2.1 resolution: "esbuild-runner@npm:2.2.1" @@ -28070,6 +28105,20 @@ fsevents@^1.2.7: languageName: node linkType: hard +"tsconfck@npm:^2.1.1": + version: 2.1.2 + resolution: "tsconfck@npm:2.1.2" + peerDependencies: + typescript: ^4.3.5 || ^5.0.0 + peerDependenciesMeta: + typescript: + optional: true + bin: + tsconfck: bin/tsconfck.js + checksum: 6fd2f7de012a724f6b4bf48ae76cc7dae2b59dd5cad2dc50bac58d224d4ed7d5c43c6b26e55d3e00636f426f8b5373c996523d73b7830d05f8479a9b83282192 + languageName: node + linkType: hard + "tsconfig-paths@npm:^3.14.1": version: 3.14.1 resolution: "tsconfig-paths@npm:3.14.1" From fd148290a6a114b1f49fdfed3dfa076e41be4840 Mon Sep 17 00:00:00 2001 From: Mark Erikson Date: Sat, 23 Sep 2023 23:35:32 -0400 Subject: [PATCH 273/412] Tweak error syntax to allow error extraction to succeed --- packages/toolkit/src/listenerMiddleware/task.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/toolkit/src/listenerMiddleware/task.ts b/packages/toolkit/src/listenerMiddleware/task.ts index 271b7918e5..01f20a72c2 100644 --- a/packages/toolkit/src/listenerMiddleware/task.ts +++ b/packages/toolkit/src/listenerMiddleware/task.ts @@ -10,7 +10,8 @@ import { addAbortSignalListener, catchRejection, noop } from './utils' */ export const validateActive = (signal: AbortSignal): void => { if (signal.aborted) { - throw new TaskAbortError((signal as AbortSignalWithReason).reason) + const { reason } = signal as AbortSignalWithReason + throw new TaskAbortError(reason) } } From 6cffdcbb0e6ede074032302ca28ad56dbfa25c91 Mon Sep 17 00:00:00 2001 From: Mark Erikson Date: Sat, 23 Sep 2023 23:45:36 -0400 Subject: [PATCH 274/412] Update esbuild --- package.json | 2 +- yarn.lock | 186 +++++++++++++++++++++++++-------------------------- 2 files changed, 94 insertions(+), 94 deletions(-) diff --git a/package.json b/package.json index c6183834ab..1826b6c95a 100644 --- a/package.json +++ b/package.json @@ -44,7 +44,7 @@ "@babel/helper-compilation-targets": "7.19.3", "@babel/traverse": "7.19.3", "@babel/types": "7.19.3", - "esbuild": "0.17.17", + "esbuild": "0.19.3", "jest-snapshot": "29.3.1", "msw": "patch:msw@npm:0.40.2#.yarn/patches/msw-npm-0.40.2-2107d48752", "jscodeshift": "0.13.1", diff --git a/yarn.lock b/yarn.lock index af644112ff..2933c8c978 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4318,156 +4318,156 @@ __metadata: languageName: node linkType: hard -"@esbuild/android-arm64@npm:0.17.17": - version: 0.17.17 - resolution: "@esbuild/android-arm64@npm:0.17.17" +"@esbuild/android-arm64@npm:0.19.3": + version: 0.19.3 + resolution: "@esbuild/android-arm64@npm:0.19.3" conditions: os=android & cpu=arm64 languageName: node linkType: hard -"@esbuild/android-arm@npm:0.17.17": - version: 0.17.17 - resolution: "@esbuild/android-arm@npm:0.17.17" +"@esbuild/android-arm@npm:0.19.3": + version: 0.19.3 + resolution: "@esbuild/android-arm@npm:0.19.3" conditions: os=android & cpu=arm languageName: node linkType: hard -"@esbuild/android-x64@npm:0.17.17": - version: 0.17.17 - resolution: "@esbuild/android-x64@npm:0.17.17" +"@esbuild/android-x64@npm:0.19.3": + version: 0.19.3 + resolution: "@esbuild/android-x64@npm:0.19.3" conditions: os=android & cpu=x64 languageName: node linkType: hard -"@esbuild/darwin-arm64@npm:0.17.17": - version: 0.17.17 - resolution: "@esbuild/darwin-arm64@npm:0.17.17" +"@esbuild/darwin-arm64@npm:0.19.3": + version: 0.19.3 + resolution: "@esbuild/darwin-arm64@npm:0.19.3" conditions: os=darwin & cpu=arm64 languageName: node linkType: hard -"@esbuild/darwin-x64@npm:0.17.17": - version: 0.17.17 - resolution: "@esbuild/darwin-x64@npm:0.17.17" +"@esbuild/darwin-x64@npm:0.19.3": + version: 0.19.3 + resolution: "@esbuild/darwin-x64@npm:0.19.3" conditions: os=darwin & cpu=x64 languageName: node linkType: hard -"@esbuild/freebsd-arm64@npm:0.17.17": - version: 0.17.17 - resolution: "@esbuild/freebsd-arm64@npm:0.17.17" +"@esbuild/freebsd-arm64@npm:0.19.3": + version: 0.19.3 + resolution: "@esbuild/freebsd-arm64@npm:0.19.3" conditions: os=freebsd & cpu=arm64 languageName: node linkType: hard -"@esbuild/freebsd-x64@npm:0.17.17": - version: 0.17.17 - resolution: "@esbuild/freebsd-x64@npm:0.17.17" +"@esbuild/freebsd-x64@npm:0.19.3": + version: 0.19.3 + resolution: "@esbuild/freebsd-x64@npm:0.19.3" conditions: os=freebsd & cpu=x64 languageName: node linkType: hard -"@esbuild/linux-arm64@npm:0.17.17": - version: 0.17.17 - resolution: "@esbuild/linux-arm64@npm:0.17.17" +"@esbuild/linux-arm64@npm:0.19.3": + version: 0.19.3 + resolution: "@esbuild/linux-arm64@npm:0.19.3" conditions: os=linux & cpu=arm64 languageName: node linkType: hard -"@esbuild/linux-arm@npm:0.17.17": - version: 0.17.17 - resolution: "@esbuild/linux-arm@npm:0.17.17" +"@esbuild/linux-arm@npm:0.19.3": + version: 0.19.3 + resolution: "@esbuild/linux-arm@npm:0.19.3" conditions: os=linux & cpu=arm languageName: node linkType: hard -"@esbuild/linux-ia32@npm:0.17.17": - version: 0.17.17 - resolution: "@esbuild/linux-ia32@npm:0.17.17" +"@esbuild/linux-ia32@npm:0.19.3": + version: 0.19.3 + resolution: "@esbuild/linux-ia32@npm:0.19.3" conditions: os=linux & cpu=ia32 languageName: node linkType: hard -"@esbuild/linux-loong64@npm:0.17.17": - version: 0.17.17 - resolution: "@esbuild/linux-loong64@npm:0.17.17" +"@esbuild/linux-loong64@npm:0.19.3": + version: 0.19.3 + resolution: "@esbuild/linux-loong64@npm:0.19.3" conditions: os=linux & cpu=loong64 languageName: node linkType: hard -"@esbuild/linux-mips64el@npm:0.17.17": - version: 0.17.17 - resolution: "@esbuild/linux-mips64el@npm:0.17.17" +"@esbuild/linux-mips64el@npm:0.19.3": + version: 0.19.3 + resolution: "@esbuild/linux-mips64el@npm:0.19.3" conditions: os=linux & cpu=mips64el languageName: node linkType: hard -"@esbuild/linux-ppc64@npm:0.17.17": - version: 0.17.17 - resolution: "@esbuild/linux-ppc64@npm:0.17.17" +"@esbuild/linux-ppc64@npm:0.19.3": + version: 0.19.3 + resolution: "@esbuild/linux-ppc64@npm:0.19.3" conditions: os=linux & cpu=ppc64 languageName: node linkType: hard -"@esbuild/linux-riscv64@npm:0.17.17": - version: 0.17.17 - resolution: "@esbuild/linux-riscv64@npm:0.17.17" +"@esbuild/linux-riscv64@npm:0.19.3": + version: 0.19.3 + resolution: "@esbuild/linux-riscv64@npm:0.19.3" conditions: os=linux & cpu=riscv64 languageName: node linkType: hard -"@esbuild/linux-s390x@npm:0.17.17": - version: 0.17.17 - resolution: "@esbuild/linux-s390x@npm:0.17.17" +"@esbuild/linux-s390x@npm:0.19.3": + version: 0.19.3 + resolution: "@esbuild/linux-s390x@npm:0.19.3" conditions: os=linux & cpu=s390x languageName: node linkType: hard -"@esbuild/linux-x64@npm:0.17.17": - version: 0.17.17 - resolution: "@esbuild/linux-x64@npm:0.17.17" +"@esbuild/linux-x64@npm:0.19.3": + version: 0.19.3 + resolution: "@esbuild/linux-x64@npm:0.19.3" conditions: os=linux & cpu=x64 languageName: node linkType: hard -"@esbuild/netbsd-x64@npm:0.17.17": - version: 0.17.17 - resolution: "@esbuild/netbsd-x64@npm:0.17.17" +"@esbuild/netbsd-x64@npm:0.19.3": + version: 0.19.3 + resolution: "@esbuild/netbsd-x64@npm:0.19.3" conditions: os=netbsd & cpu=x64 languageName: node linkType: hard -"@esbuild/openbsd-x64@npm:0.17.17": - version: 0.17.17 - resolution: "@esbuild/openbsd-x64@npm:0.17.17" +"@esbuild/openbsd-x64@npm:0.19.3": + version: 0.19.3 + resolution: "@esbuild/openbsd-x64@npm:0.19.3" conditions: os=openbsd & cpu=x64 languageName: node linkType: hard -"@esbuild/sunos-x64@npm:0.17.17": - version: 0.17.17 - resolution: "@esbuild/sunos-x64@npm:0.17.17" +"@esbuild/sunos-x64@npm:0.19.3": + version: 0.19.3 + resolution: "@esbuild/sunos-x64@npm:0.19.3" conditions: os=sunos & cpu=x64 languageName: node linkType: hard -"@esbuild/win32-arm64@npm:0.17.17": - version: 0.17.17 - resolution: "@esbuild/win32-arm64@npm:0.17.17" +"@esbuild/win32-arm64@npm:0.19.3": + version: 0.19.3 + resolution: "@esbuild/win32-arm64@npm:0.19.3" conditions: os=win32 & cpu=arm64 languageName: node linkType: hard -"@esbuild/win32-ia32@npm:0.17.17": - version: 0.17.17 - resolution: "@esbuild/win32-ia32@npm:0.17.17" +"@esbuild/win32-ia32@npm:0.19.3": + version: 0.19.3 + resolution: "@esbuild/win32-ia32@npm:0.19.3" conditions: os=win32 & cpu=ia32 languageName: node linkType: hard -"@esbuild/win32-x64@npm:0.17.17": - version: 0.17.17 - resolution: "@esbuild/win32-x64@npm:0.17.17" +"@esbuild/win32-x64@npm:0.19.3": + version: 0.19.3 + resolution: "@esbuild/win32-x64@npm:0.19.3" conditions: os=win32 & cpu=x64 languageName: node linkType: hard @@ -14070,32 +14070,32 @@ __metadata: languageName: node linkType: hard -"esbuild@npm:0.17.17": - version: 0.17.17 - resolution: "esbuild@npm:0.17.17" - dependencies: - "@esbuild/android-arm": 0.17.17 - "@esbuild/android-arm64": 0.17.17 - "@esbuild/android-x64": 0.17.17 - "@esbuild/darwin-arm64": 0.17.17 - "@esbuild/darwin-x64": 0.17.17 - "@esbuild/freebsd-arm64": 0.17.17 - "@esbuild/freebsd-x64": 0.17.17 - "@esbuild/linux-arm": 0.17.17 - "@esbuild/linux-arm64": 0.17.17 - "@esbuild/linux-ia32": 0.17.17 - "@esbuild/linux-loong64": 0.17.17 - "@esbuild/linux-mips64el": 0.17.17 - "@esbuild/linux-ppc64": 0.17.17 - "@esbuild/linux-riscv64": 0.17.17 - "@esbuild/linux-s390x": 0.17.17 - "@esbuild/linux-x64": 0.17.17 - "@esbuild/netbsd-x64": 0.17.17 - "@esbuild/openbsd-x64": 0.17.17 - "@esbuild/sunos-x64": 0.17.17 - "@esbuild/win32-arm64": 0.17.17 - "@esbuild/win32-ia32": 0.17.17 - "@esbuild/win32-x64": 0.17.17 +"esbuild@npm:0.19.3": + version: 0.19.3 + resolution: "esbuild@npm:0.19.3" + dependencies: + "@esbuild/android-arm": 0.19.3 + "@esbuild/android-arm64": 0.19.3 + "@esbuild/android-x64": 0.19.3 + "@esbuild/darwin-arm64": 0.19.3 + "@esbuild/darwin-x64": 0.19.3 + "@esbuild/freebsd-arm64": 0.19.3 + "@esbuild/freebsd-x64": 0.19.3 + "@esbuild/linux-arm": 0.19.3 + "@esbuild/linux-arm64": 0.19.3 + "@esbuild/linux-ia32": 0.19.3 + "@esbuild/linux-loong64": 0.19.3 + "@esbuild/linux-mips64el": 0.19.3 + "@esbuild/linux-ppc64": 0.19.3 + "@esbuild/linux-riscv64": 0.19.3 + "@esbuild/linux-s390x": 0.19.3 + "@esbuild/linux-x64": 0.19.3 + "@esbuild/netbsd-x64": 0.19.3 + "@esbuild/openbsd-x64": 0.19.3 + "@esbuild/sunos-x64": 0.19.3 + "@esbuild/win32-arm64": 0.19.3 + "@esbuild/win32-ia32": 0.19.3 + "@esbuild/win32-x64": 0.19.3 dependenciesMeta: "@esbuild/android-arm": optional: true @@ -14143,7 +14143,7 @@ __metadata: optional: true bin: esbuild: bin/esbuild - checksum: dbb803a7fc798755ffcc347fd4e83f33bdffb91b62ff14c41d858acacd60b2b74a9fbcfb54da2be7cc385bd99fc00f5a0cc1e80c7e5d501236f4fd39cf8c03d1 + checksum: f998ba82b1bbf0f3036201dc2cb94f92aff887b7552738ea3e4dd6f386f87740ef76aabae2fc1c4b91a519354d390f6d9fd89eb71e26882983f6fbaf75369206 languageName: node linkType: hard From be2b2559867e35ab41afe25c43be8e7e705b8578 Mon Sep 17 00:00:00 2001 From: Mark Erikson Date: Sat, 23 Sep 2023 23:45:59 -0400 Subject: [PATCH 275/412] Add error extraction script --- packages/toolkit/scripts/mangleErrors.cjs | 159 ++++++++++++++++++ .../toolkit/src/formatProdErrorMessage.ts | 13 ++ 2 files changed, 172 insertions(+) create mode 100644 packages/toolkit/scripts/mangleErrors.cjs create mode 100644 packages/toolkit/src/formatProdErrorMessage.ts diff --git a/packages/toolkit/scripts/mangleErrors.cjs b/packages/toolkit/scripts/mangleErrors.cjs new file mode 100644 index 0000000000..96581df353 --- /dev/null +++ b/packages/toolkit/scripts/mangleErrors.cjs @@ -0,0 +1,159 @@ +const fs = require('fs') +const path = require('path') +const helperModuleImports = require('@babel/helper-module-imports') + +/** + * Converts an AST type into a javascript string so that it can be added to the error message lookup. + * + * Adapted from React (https://github.com/facebook/react/blob/master/scripts/shared/evalToString.js) with some + * adjustments + */ +const evalToString = (ast) => { + switch (ast.type) { + case 'StringLiteral': + case 'Literal': // ESLint + return ast.value + case 'BinaryExpression': // `+` + if (ast.operator !== '+') { + throw new Error('Unsupported binary operator ' + ast.operator) + } + return evalToString(ast.left) + evalToString(ast.right) + case 'TemplateLiteral': + return ast.quasis.reduce( + (concatenatedValue, templateElement) => + concatenatedValue + templateElement.value.raw, + '' + ) + case 'Identifier': + return ast.name + default: + console.log('Bad AST in mangleErrors -> evalToString(): ', ast) + throw new Error(`Unsupported AST in evalToString: ${ast.type}, ${ast}`) + } +} + +/** + * Takes a `throw new error` statement and transforms it depending on the minify argument. Either option results in a + * smaller bundle size in production for consumers. + * + * If minify is enabled, we'll replace the error message with just an index that maps to an arrow object lookup. + * + * If minify is disabled, we'll add in a conditional statement to check the process.env.NODE_ENV which will output a + * an error number index in production or the actual error message in development. This allows consumers using webpack + * or another build tool to have these messages in development but have just the error index in production. + * + * E.g. + * Before: + * throw new Error("This is my error message."); + * throw new Error("This is a second error message."); + * + * After (with minify): + * throw new Error(0); + * throw new Error(1); + * + * After: (without minify): + * throw new Error(node.process.NODE_ENV === 'production' ? 0 : "This is my error message."); + * throw new Error(node.process.NODE_ENV === 'production' ? 1 : "This is a second error message."); + */ +module.exports = (babel) => { + const t = babel.types + // When the plugin starts up, we'll load in the existing file. This allows us to continually add to it so that the + // indexes do not change between builds. + let errorsFiles = '' + // Save this to the root + const errorsPath = path.join(__dirname, '../../../errors.json') + if (fs.existsSync(errorsPath)) { + errorsFiles = fs.readFileSync(errorsPath).toString() + } + let errors = Object.values(JSON.parse(errorsFiles || '{}')) + // This variable allows us to skip writing back to the file if the errors array hasn't changed + let changeInArray = false + + return { + pre: () => { + changeInArray = false + }, + visitor: { + ThrowStatement(path, file) { + const args = path.node.argument.arguments + const minify = file.opts.minify + + if (args && args[0]) { + // Skip running this logic when certain types come up: + // Identifier comes up when a variable is thrown (E.g. throw new error(message)) + // NumericLiteral, CallExpression, and ConditionalExpression is code we have already processed + if ( + path.node.argument.arguments[0].type === 'Identifier' || + path.node.argument.arguments[0].type === 'NumericLiteral' || + path.node.argument.arguments[0].type === 'ConditionalExpression' || + path.node.argument.arguments[0].type === 'CallExpression' || + path.node.argument.arguments[0].type === 'ObjectExpression' || + path.node.argument.arguments[0].type === 'MemberExpression' || + path.node.argument.arguments[0]?.callee?.name === 'HandledError' + ) { + return + } + + const errorMsgLiteral = evalToString(path.node.argument.arguments[0]) + + if (errorMsgLiteral.includes('Super expression')) { + // ignore Babel runtime error message + return + } + + // Attempt to get the existing index of the error. If it is not found, add it to the array as a new error. + let errorIndex = errors.indexOf(errorMsgLiteral) + if (errorIndex === -1) { + errors.push(errorMsgLiteral) + errorIndex = errors.length - 1 + changeInArray = true + } + + // Import the error message function + const formatProdErrorMessageIdentifier = helperModuleImports.addNamed( + path, + 'formatProdErrorMessage', + '@reduxjs/toolkit', + { nameHint: 'formatProdErrorMessage' } + ) + + // Creates a function call to output the message to the error code page on the website + const prodMessage = t.callExpression( + formatProdErrorMessageIdentifier, + [t.numericLiteral(errorIndex)] + ) + + if (minify) { + path.replaceWith( + t.throwStatement( + t.newExpression(t.identifier('Error'), [prodMessage]) + ) + ) + } else { + path.replaceWith( + t.throwStatement( + t.newExpression(t.identifier('Error'), [ + t.conditionalExpression( + t.binaryExpression( + '===', + t.identifier('process.env.NODE_ENV'), + t.stringLiteral('production') + ), + prodMessage, + path.node.argument.arguments[0] + ), + ]) + ) + ) + } + } + }, + }, + post: () => { + // If there is a new error in the array, convert it to an indexed object and write it back to the file. + if (changeInArray) { + fs.writeFileSync(errorsPath, JSON.stringify({ ...errors }, null, 2)) + } + }, + } +} diff --git a/packages/toolkit/src/formatProdErrorMessage.ts b/packages/toolkit/src/formatProdErrorMessage.ts new file mode 100644 index 0000000000..0bddda5300 --- /dev/null +++ b/packages/toolkit/src/formatProdErrorMessage.ts @@ -0,0 +1,13 @@ +/** + * Adapted from React: https://github.com/facebook/react/blob/master/packages/shared/formatProdErrorMessage.js + * + * Do not require this module directly! Use normal throw error calls. These messages will be replaced with error codes + * during build. + * @param {number} code + */ +export function formatProdErrorMessage(code: number) { + return ( + `Minified Redux Toolkit error #${code}; visit https://redux-toolkit.js.org/Errors?code=${code} for the full message or ` + + 'use the non-minified dev environment for full errors. ' + ) +} From 9a7803dfd3940494d298ddb35b3e7544944c4716 Mon Sep 17 00:00:00 2001 From: Mark Erikson Date: Sat, 23 Sep 2023 23:51:41 -0400 Subject: [PATCH 276/412] Update tsup config to extract error messages --- errors.json | 31 ++++++++++++++++++++++ packages/toolkit/tsup.config.ts | 46 +++++++++++++++++++++------------ 2 files changed, 61 insertions(+), 16 deletions(-) create mode 100644 errors.json diff --git a/errors.json b/errors.json new file mode 100644 index 0000000000..845033af99 --- /dev/null +++ b/errors.json @@ -0,0 +1,31 @@ +{ + "0": "The object notation for `createReducer` has been removed. Please use the 'builder callback' notation instead: https://redux-toolkit.js.org/api/createReducer", + "1": "A case reducer on a non-draftable value must not return undefined", + "2": ": ", + "3": "prepareAction did not return an object", + "4": "\"reducer\" is a required argument, and must be a function or an object of functions that can be passed to combineReducers", + "5": "when using a middleware builder function, an array of middleware must be returned", + "6": "each middleware provided to configureStore must be a function", + "7": "\"enhancers\" field must be a callback", + "8": "\"enhancers\" callback must return an array", + "9": "each enhancer provided to configureStore must be a function", + "10": "`name` is a required option for createSlice", + "11": "The object notation for `createSlice.extraReducers` has been removed. Please use the 'builder callback' notation instead: https://redux-toolkit.js.org/api/createSlice", + "12": "selectState returned undefined for an uninjected slice reducer", + "13": "Please use the `create.preparedReducer` notation for prepared action creators with the `create` notation.", + "14": "The slice reducer for key \"\" returned undefined when called for selector(). If the state passed to the reducer is undefined, you must explicitly return the initial state. The initial state may not be undefined. If you don't want to set a value for this reducer, you can use null instead of undefined.", + "15": "original must be used on state Proxy", + "16": "Creating or removing a listener requires one of the known fields for matching an action", + "17": "Unsubscribe not initialized", + "18": ": getOriginalState can only be called synchronously", + "19": "`builder.addCase` should only be called before calling `builder.addMatcher`", + "20": "`builder.addCase` should only be called before calling `builder.addDefaultCase`", + "21": "addCase cannot be called with two reducers for the same action type", + "22": "`builder.addMatcher` should only be called before calling `builder.addDefaultCase`", + "23": "`builder.addDefaultCase` can only be called once", + "24": " is not a function", + "25": "When using `fakeBaseQuery`, all queries & mutations must use the `queryFn` definition syntax.", + "26": "Warning: Middleware for RTK-Query API at reducerPath \"\" has not been added to the store.\nYou must add the middleware for RTK-Query to function correctly!", + "27": "Warning: Middleware for RTK-Query API at reducerPath \"\" has not been added to the store.\n You must add the middleware for RTK-Query to function correctly!", + "28": "Cannot refetch a query that has not been started yet." +} \ No newline at end of file diff --git a/packages/toolkit/tsup.config.ts b/packages/toolkit/tsup.config.ts index 4a3aa994fb..c76525e1e3 100644 --- a/packages/toolkit/tsup.config.ts +++ b/packages/toolkit/tsup.config.ts @@ -1,10 +1,11 @@ import { fileURLToPath } from 'url' import path from 'path' import fs from 'fs' -import rimraf from 'rimraf' -import { BuildOptions as ESBuildOptions } from 'esbuild' +import type { BuildOptions as ESBuildOptions, Plugin } from 'esbuild' import type { Options as TsupOptions } from 'tsup' import { defineConfig } from 'tsup' +import * as babel from '@babel/core' +import { getBuildExtensions } from 'esbuild-extra' import { delay } from './src/utils' @@ -93,20 +94,6 @@ const buildTargets: BuildOptions[] = [ minify: true, env: 'production', }, - // { - // format: 'umd', - // name: 'umd', - // target: 'es2018', - // minify: false, - // env: 'development', - // }, - // { - // format: 'umd', - // name: 'umd.min', - // target: 'es2018', - // minify: true, - // env: 'production', - // }, ] const entryPoints: EntryPointOptions[] = [ @@ -151,6 +138,32 @@ if (process.env.NODE_ENV === 'production') { ) } +// Extract error strings, replace them with error codes, and write messages to a file +const mangleErrorsTransform: Plugin = { + name: 'mangle-errors-plugin', + setup(build) { + const { onTransform } = getBuildExtensions(build, 'mangle-errors-plugin') + + onTransform({ loaders: ['ts', 'tsx'] }, async (args) => { + try { + const res = babel.transformSync(args.code, { + parserOpts: { + plugins: ['typescript', 'jsx'], + }, + plugins: [['./scripts/mangleErrors.cjs', { minify: false }]], + })! + return { + code: res.code!, + map: res.map!, + } + } catch (err) { + console.error('Babel mangleErrors error: ', err) + return null + } + }) + }, +} + export default defineConfig((options) => { const configs = entryPoints .map((entryPointConfig) => { @@ -190,6 +203,7 @@ export default defineConfig((options) => { minify, sourcemap: true, external: externals, + esbuildPlugins: [mangleErrorsTransform], esbuildOptions(options) { // Needed to prevent auto-replacing of process.env.NODE_ENV in all builds options.platform = 'neutral' From 3b9722da69059481fd177b945b94bc0832ac2971 Mon Sep 17 00:00:00 2001 From: Mark Erikson Date: Sun, 24 Sep 2023 00:12:10 -0400 Subject: [PATCH 277/412] Manually dedupe all RTK imports in RTKQ core --- packages/toolkit/src/index.ts | 6 +++++ .../core/buildMiddleware/cacheLifecycle.ts | 2 +- .../src/query/core/buildMiddleware/index.ts | 2 +- .../buildMiddleware/invalidationByTags.ts | 2 +- .../core/buildMiddleware/queryLifecycle.ts | 2 +- .../toolkit/src/query/core/buildSelectors.ts | 2 +- packages/toolkit/src/query/core/buildSlice.ts | 2 +- .../toolkit/src/query/core/buildThunks.ts | 5 ++-- packages/toolkit/src/query/core/rtkImports.ts | 24 +++++++++++++++++++ .../toolkit/src/query/core/setupListeners.ts | 2 +- packages/toolkit/src/query/createApi.ts | 2 +- .../src/query/defaultSerializeQueryArgs.ts | 2 +- packages/toolkit/src/query/fetchBaseQuery.ts | 2 +- packages/toolkit/src/query/index.ts | 4 ++++ .../toolkit/src/query/react/buildHooks.ts | 18 ++++---------- packages/toolkit/src/query/react/index.ts | 4 ++++ .../query/utils/copyWithStructuralSharing.ts | 2 +- packages/toolkit/src/react/index.ts | 3 +++ 18 files changed, 60 insertions(+), 26 deletions(-) create mode 100644 packages/toolkit/src/query/core/rtkImports.ts diff --git a/packages/toolkit/src/index.ts b/packages/toolkit/src/index.ts index e15803550e..8ee73148de 100644 --- a/packages/toolkit/src/index.ts +++ b/packages/toolkit/src/index.ts @@ -1,3 +1,7 @@ +// This must remain here so that the `mangleErrors.cjs` build script +// does not have to import this into each source file it rewrites. +import { formatProdErrorMessage } from './formatProdErrorMessage' + export * from 'redux' export { produce as createNextState, @@ -207,3 +211,5 @@ export { combineSlices } from './combineSlices' export type { WithSlice } from './combineSlices' export type { ExtractDispatchExtensions as TSHelpersExtractDispatchExtensions } from './tsHelpers' + +export { formatProdErrorMessage } from './formatProdErrorMessage' diff --git a/packages/toolkit/src/query/core/buildMiddleware/cacheLifecycle.ts b/packages/toolkit/src/query/core/buildMiddleware/cacheLifecycle.ts index d83a25b15f..247bd7434c 100644 --- a/packages/toolkit/src/query/core/buildMiddleware/cacheLifecycle.ts +++ b/packages/toolkit/src/query/core/buildMiddleware/cacheLifecycle.ts @@ -1,4 +1,4 @@ -import { isAsyncThunkAction, isFulfilled } from '@reduxjs/toolkit' +import { isAsyncThunkAction, isFulfilled } from '../rtkImports' import type { UnknownAction } from 'redux' import type { ThunkDispatch } from 'redux-thunk' import type { BaseQueryFn, BaseQueryMeta } from '../../baseQueryTypes' diff --git a/packages/toolkit/src/query/core/buildMiddleware/index.ts b/packages/toolkit/src/query/core/buildMiddleware/index.ts index 2ef7c7f347..aa58617d6f 100644 --- a/packages/toolkit/src/query/core/buildMiddleware/index.ts +++ b/packages/toolkit/src/query/core/buildMiddleware/index.ts @@ -4,7 +4,7 @@ import type { ThunkDispatch, UnknownAction, } from '@reduxjs/toolkit' -import { isAction, createAction } from '@reduxjs/toolkit' +import { isAction, createAction } from '../rtkImports' import type { EndpointDefinitions, diff --git a/packages/toolkit/src/query/core/buildMiddleware/invalidationByTags.ts b/packages/toolkit/src/query/core/buildMiddleware/invalidationByTags.ts index 975ff4827f..181e50f59a 100644 --- a/packages/toolkit/src/query/core/buildMiddleware/invalidationByTags.ts +++ b/packages/toolkit/src/query/core/buildMiddleware/invalidationByTags.ts @@ -1,4 +1,4 @@ -import { isAnyOf, isFulfilled, isRejectedWithValue } from '@reduxjs/toolkit' +import { isAnyOf, isFulfilled, isRejectedWithValue } from '../rtkImports' import type { FullTagDescription } from '../../endpointDefinitions' import { calculateProvidedBy } from '../../endpointDefinitions' diff --git a/packages/toolkit/src/query/core/buildMiddleware/queryLifecycle.ts b/packages/toolkit/src/query/core/buildMiddleware/queryLifecycle.ts index 0df42c159e..580581238c 100644 --- a/packages/toolkit/src/query/core/buildMiddleware/queryLifecycle.ts +++ b/packages/toolkit/src/query/core/buildMiddleware/queryLifecycle.ts @@ -1,4 +1,4 @@ -import { isPending, isRejected, isFulfilled } from '@reduxjs/toolkit' +import { isPending, isRejected, isFulfilled } from '../rtkImports' import type { BaseQueryError, BaseQueryFn, diff --git a/packages/toolkit/src/query/core/buildSelectors.ts b/packages/toolkit/src/query/core/buildSelectors.ts index 58967bb6b1..efd48b8170 100644 --- a/packages/toolkit/src/query/core/buildSelectors.ts +++ b/packages/toolkit/src/query/core/buildSelectors.ts @@ -1,4 +1,4 @@ -import { createNextState, createSelector } from '@reduxjs/toolkit' +import { createNextState, createSelector } from './rtkImports' import type { MutationSubState, QuerySubState, diff --git a/packages/toolkit/src/query/core/buildSlice.ts b/packages/toolkit/src/query/core/buildSlice.ts index ef37ca3023..4af8a310d5 100644 --- a/packages/toolkit/src/query/core/buildSlice.ts +++ b/packages/toolkit/src/query/core/buildSlice.ts @@ -8,7 +8,7 @@ import { isRejectedWithValue, createNextState, prepareAutoBatched, -} from '@reduxjs/toolkit' +} from './rtkImports' import type { QuerySubstateIdentifier, QuerySubState, diff --git a/packages/toolkit/src/query/core/buildThunks.ts b/packages/toolkit/src/query/core/buildThunks.ts index d0e609302c..9fbd436fcb 100644 --- a/packages/toolkit/src/query/core/buildThunks.ts +++ b/packages/toolkit/src/query/core/buildThunks.ts @@ -34,11 +34,12 @@ import { isPending, isRejected, isRejectedWithValue, -} from '@reduxjs/toolkit' + createAsyncThunk, + SHOULD_AUTOBATCH, +} from './rtkImports' import type { Patch } from 'immer' import { isDraftable, produceWithPatches } from 'immer' import type { ThunkAction, ThunkDispatch, AsyncThunk } from '@reduxjs/toolkit' -import { createAsyncThunk, SHOULD_AUTOBATCH } from '@reduxjs/toolkit' import { HandledError } from '../HandledError' diff --git a/packages/toolkit/src/query/core/rtkImports.ts b/packages/toolkit/src/query/core/rtkImports.ts new file mode 100644 index 0000000000..4ba180bab4 --- /dev/null +++ b/packages/toolkit/src/query/core/rtkImports.ts @@ -0,0 +1,24 @@ +// This file exists to consolidate all of the imports from the `@reduxjs/toolkit` package. +// ESBuild does not de-duplicate imports, so this file is used to ensure that each method +// imported is only listed once, and there's only one mention of the `@reduxjs/toolkit` package. + +export { + createAction, + createSlice, + createSelector, + createAsyncThunk, + combineReducers, + createNextState, + isAnyOf, + isAllOf, + isAction, + isPending, + isRejected, + isFulfilled, + isRejectedWithValue, + isAsyncThunkAction, + prepareAutoBatched, + SHOULD_AUTOBATCH, + isPlainObject, + nanoid, +} from '@reduxjs/toolkit' diff --git a/packages/toolkit/src/query/core/setupListeners.ts b/packages/toolkit/src/query/core/setupListeners.ts index 14a4e6e65f..01df593906 100644 --- a/packages/toolkit/src/query/core/setupListeners.ts +++ b/packages/toolkit/src/query/core/setupListeners.ts @@ -2,7 +2,7 @@ import type { ThunkDispatch, ActionCreatorWithoutPayload, // Workaround for API-Extractor } from '@reduxjs/toolkit' -import { createAction } from '@reduxjs/toolkit' +import { createAction } from './rtkImports' export const onFocus = /* @__PURE__ */ createAction('__rtkq/focused') export const onFocusLost = /* @__PURE__ */ createAction('__rtkq/unfocused') diff --git a/packages/toolkit/src/query/createApi.ts b/packages/toolkit/src/query/createApi.ts index a4bf7cf790..26ec2fb2d4 100644 --- a/packages/toolkit/src/query/createApi.ts +++ b/packages/toolkit/src/query/createApi.ts @@ -8,7 +8,7 @@ import type { EndpointDefinitions, } from './endpointDefinitions' import { DefinitionType, isQueryDefinition } from './endpointDefinitions' -import { nanoid } from '@reduxjs/toolkit' +import { nanoid } from './core/rtkImports' import type { UnknownAction } from '@reduxjs/toolkit' import type { NoInfer } from './tsHelpers' import { defaultMemoize } from 'reselect' diff --git a/packages/toolkit/src/query/defaultSerializeQueryArgs.ts b/packages/toolkit/src/query/defaultSerializeQueryArgs.ts index e4988ead2c..e3fe011b44 100644 --- a/packages/toolkit/src/query/defaultSerializeQueryArgs.ts +++ b/packages/toolkit/src/query/defaultSerializeQueryArgs.ts @@ -1,6 +1,6 @@ import type { QueryCacheKey } from './core/apiState' import type { EndpointDefinition } from './endpointDefinitions' -import { isPlainObject } from '@reduxjs/toolkit' +import { isPlainObject } from './core/rtkImports' const cache: WeakMap | undefined = WeakMap ? new WeakMap() diff --git a/packages/toolkit/src/query/fetchBaseQuery.ts b/packages/toolkit/src/query/fetchBaseQuery.ts index 158d53d173..6cf2b5a913 100644 --- a/packages/toolkit/src/query/fetchBaseQuery.ts +++ b/packages/toolkit/src/query/fetchBaseQuery.ts @@ -1,5 +1,5 @@ import { joinUrls } from './utils' -import { isPlainObject } from '@reduxjs/toolkit' +import { isPlainObject } from './core/rtkImports' import type { BaseQueryApi, BaseQueryFn } from './baseQueryTypes' import type { MaybePromise, Override } from './tsHelpers' diff --git a/packages/toolkit/src/query/index.ts b/packages/toolkit/src/query/index.ts index c75f3b4111..91d4135eea 100644 --- a/packages/toolkit/src/query/index.ts +++ b/packages/toolkit/src/query/index.ts @@ -1,3 +1,7 @@ +// This must remain here so that the `mangleErrors.cjs` build script +// does not have to import this into each source file it rewrites. +import { formatProdErrorMessage } from '@reduxjs/toolkit' + export type { QuerySubState, SubscriptionOptions, diff --git a/packages/toolkit/src/query/react/buildHooks.ts b/packages/toolkit/src/query/react/buildHooks.ts index 440d24bda8..2392c461e6 100644 --- a/packages/toolkit/src/query/react/buildHooks.ts +++ b/packages/toolkit/src/query/react/buildHooks.ts @@ -21,37 +21,29 @@ import type { SubscriptionOptions, QueryKeys, RootState, -} from '@reduxjs/toolkit/query' -import type { EndpointDefinitions, MutationDefinition, QueryDefinition, QueryArgFrom, ResultTypeFrom, -} from '@reduxjs/toolkit/query' -import type { QueryResultSelectorResult, MutationResultSelectorResult, SkipToken, -} from '@reduxjs/toolkit/query' -import type { QueryActionCreatorResult, MutationActionCreatorResult, -} from '@reduxjs/toolkit/query' -import type { SerializeQueryArgs } from '@reduxjs/toolkit/query' -import { shallowEqual } from 'react-redux' -import type { Api, ApiContext } from '@reduxjs/toolkit/query' -import type { + SerializeQueryArgs, + Api, + ApiContext, TSHelpersId, TSHelpersNoInfer, TSHelpersOverride, -} from '@reduxjs/toolkit/query' -import type { ApiEndpointMutation, ApiEndpointQuery, CoreModule, PrefetchOptions, } from '@reduxjs/toolkit/query' + +import { shallowEqual } from 'react-redux' import type { ReactHooksModuleOptions } from './module' import { useStableQueryArgs } from './useSerializedStableValue' import type { UninitializedValue } from './constants' diff --git a/packages/toolkit/src/query/react/index.ts b/packages/toolkit/src/query/react/index.ts index f0298381fc..5a9b5a6cdf 100644 --- a/packages/toolkit/src/query/react/index.ts +++ b/packages/toolkit/src/query/react/index.ts @@ -1,3 +1,7 @@ +// This must remain here so that the `mangleErrors.cjs` build script +// does not have to import this into each source file it rewrites. +import { formatProdErrorMessage } from '@reduxjs/toolkit' + import { coreModule, buildCreateApi } from '@reduxjs/toolkit/query' import { reactHooksModule, reactHooksModuleName } from './module' diff --git a/packages/toolkit/src/query/utils/copyWithStructuralSharing.ts b/packages/toolkit/src/query/utils/copyWithStructuralSharing.ts index 2d6fdc3027..11e6cecdd3 100644 --- a/packages/toolkit/src/query/utils/copyWithStructuralSharing.ts +++ b/packages/toolkit/src/query/utils/copyWithStructuralSharing.ts @@ -1,4 +1,4 @@ -import { isPlainObject as _iPO } from '@reduxjs/toolkit' +import { isPlainObject as _iPO } from '../core/rtkImports' // remove type guard const isPlainObject: (_: any) => boolean = _iPO diff --git a/packages/toolkit/src/react/index.ts b/packages/toolkit/src/react/index.ts index ed558047c4..739f7ba477 100644 --- a/packages/toolkit/src/react/index.ts +++ b/packages/toolkit/src/react/index.ts @@ -1,3 +1,6 @@ +// This must remain here so that the `mangleErrors.cjs` build script +// does not have to import this into each source file it rewrites. +import { formatProdErrorMessage } from '@reduxjs/toolkit' export * from '@reduxjs/toolkit' export { createDynamicMiddleware } from '../dynamicMiddleware/react' From 8910e2bddc6829183a8bd5f39d5cf655dafe9927 Mon Sep 17 00:00:00 2001 From: Mark Erikson Date: Sun, 24 Sep 2023 20:17:46 -0400 Subject: [PATCH 278/412] Release 2.0.0-beta.2 --- packages/toolkit/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/toolkit/package.json b/packages/toolkit/package.json index 80a51cb3ba..21cd8f046e 100644 --- a/packages/toolkit/package.json +++ b/packages/toolkit/package.json @@ -1,6 +1,6 @@ { "name": "@reduxjs/toolkit", - "version": "2.0.0-beta.1", + "version": "2.0.0-beta.2", "description": "The official, opinionated, batteries-included toolset for efficient Redux development", "author": "Mark Erikson ", "license": "MIT", From 36e1a853272ed5742594688ce5b205b9ea1f47d8 Mon Sep 17 00:00:00 2001 From: Mark Erikson Date: Sun, 24 Sep 2023 20:35:44 -0400 Subject: [PATCH 279/412] Update error message and errors.json --- errors.json | 4 +++- packages/toolkit/src/tests/createSlice.test.ts | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/errors.json b/errors.json index 845033af99..88e5d18183 100644 --- a/errors.json +++ b/errors.json @@ -27,5 +27,7 @@ "25": "When using `fakeBaseQuery`, all queries & mutations must use the `queryFn` definition syntax.", "26": "Warning: Middleware for RTK-Query API at reducerPath \"\" has not been added to the store.\nYou must add the middleware for RTK-Query to function correctly!", "27": "Warning: Middleware for RTK-Query API at reducerPath \"\" has not been added to the store.\n You must add the middleware for RTK-Query to function correctly!", - "28": "Cannot refetch a query that has not been started yet." + "28": "Cannot refetch a query that has not been started yet.", + "29": "`builder.addCase` cannot be called with an empty action type", + "30": "`builder.addCase` cannot be called with two reducers for the same action type" } \ No newline at end of file diff --git a/packages/toolkit/src/tests/createSlice.test.ts b/packages/toolkit/src/tests/createSlice.test.ts index 36f78c0009..3ecae681e9 100644 --- a/packages/toolkit/src/tests/createSlice.test.ts +++ b/packages/toolkit/src/tests/createSlice.test.ts @@ -250,7 +250,7 @@ describe('createSlice', () => { }) slice.reducer(undefined, { type: 'unrelated' }) }).toThrowErrorMatchingInlineSnapshot( - `"addCase cannot be called with two reducers for the same action type"` + '"`builder.addCase` cannot be called with two reducers for the same action type"' ) }) From e87cc9705095b24362838783abc92954146fbbd0 Mon Sep 17 00:00:00 2001 From: Mark Erikson Date: Mon, 25 Sep 2023 06:58:19 -0400 Subject: [PATCH 280/412] Update examples to use latest betas --- examples/publish-ci/cra4/package.json | 4 +- examples/publish-ci/cra4/yarn.lock | 104 +++++------- examples/publish-ci/cra5/package.json | 4 +- examples/publish-ci/cra5/yarn.lock | 105 +++++------- examples/publish-ci/next/package.json | 4 +- examples/publish-ci/next/yarn.lock | 109 +++++------- examples/publish-ci/node-esm/package.json | 4 +- examples/publish-ci/node-esm/yarn.lock | 155 +++++------------- .../publish-ci/node-standard/package.json | 4 +- examples/publish-ci/node-standard/yarn.lock | 155 +++++------------- examples/publish-ci/vite/package.json | 4 +- examples/publish-ci/vite/yarn.lock | 109 +++++------- 12 files changed, 250 insertions(+), 511 deletions(-) diff --git a/examples/publish-ci/cra4/package.json b/examples/publish-ci/cra4/package.json index 28ed15bf51..dc81f4d5bb 100644 --- a/examples/publish-ci/cra4/package.json +++ b/examples/publish-ci/cra4/package.json @@ -3,11 +3,11 @@ "version": "0.1.0", "private": true, "dependencies": { - "@reduxjs/toolkit": "^1.9.3", + "@reduxjs/toolkit": "^2.0.0-beta.2", "msw": "^0.49.2", "react": "^18.2.0", "react-dom": "^18.2.0", - "react-redux": "^8.0.5", + "react-redux": "^9.0.0-alpha.1", "react-scripts": "^4", "web-vitals": "^2.1.4" }, diff --git a/examples/publish-ci/cra4/yarn.lock b/examples/publish-ci/cra4/yarn.lock index 196807228d..c4707309f0 100644 --- a/examples/publish-ci/cra4/yarn.lock +++ b/examples/publish-ci/cra4/yarn.lock @@ -1474,7 +1474,7 @@ __metadata: languageName: node linkType: hard -"@babel/runtime@npm:^7.12.1, @babel/runtime@npm:^7.12.5, @babel/runtime@npm:^7.16.3, @babel/runtime@npm:^7.20.7, @babel/runtime@npm:^7.5.5, @babel/runtime@npm:^7.8.4, @babel/runtime@npm:^7.9.2": +"@babel/runtime@npm:^7.12.5, @babel/runtime@npm:^7.16.3, @babel/runtime@npm:^7.20.7, @babel/runtime@npm:^7.5.5, @babel/runtime@npm:^7.8.4, @babel/runtime@npm:^7.9.2": version: 7.21.0 resolution: "@babel/runtime@npm:7.21.0" dependencies: @@ -2101,23 +2101,23 @@ __metadata: languageName: node linkType: hard -"@reduxjs/toolkit@npm:^1.9.3": - version: 1.9.3 - resolution: "@reduxjs/toolkit@npm:1.9.3" +"@reduxjs/toolkit@npm:^2.0.0-beta.2": + version: 2.0.0-beta.2 + resolution: "@reduxjs/toolkit@npm:2.0.0-beta.2" dependencies: - immer: ^9.0.16 - redux: ^4.2.0 - redux-thunk: ^2.4.2 - reselect: ^4.1.7 + immer: ^10.0.2 + redux: ^5.0.0-beta.0 + redux-thunk: ^3.0.0-beta.0 + reselect: ^5.0.0-alpha.2 peerDependencies: react: ^16.9.0 || ^17.0.0 || ^18 - react-redux: ^7.2.1 || ^8.0.2 + react-redux: ^7.2.1 || ^8.0.2 || ^9.0.0-alpha.1 peerDependenciesMeta: react: optional: true react-redux: optional: true - checksum: d965fc6197fd420e4b8eb714015aa908d4ea214e6e10889cf4521e64ac4f8e0a7de29ee4e8da63b291acc40ac330dd31682334090d54838add73c746dc590650 + checksum: 340b515190ba23785464aab85141e49e57a924cde8a335a8df789f1b212cb59fb35e7d8e241bbac9f6e27d17c5236d440aca6f7a7bb2fa36228e0c111c7ec768 languageName: node linkType: hard @@ -2504,16 +2504,6 @@ __metadata: languageName: node linkType: hard -"@types/hoist-non-react-statics@npm:^3.3.1": - version: 3.3.1 - resolution: "@types/hoist-non-react-statics@npm:3.3.1" - dependencies: - "@types/react": "*" - hoist-non-react-statics: ^3.3.0 - checksum: 2c0778570d9a01d05afabc781b32163f28409bb98f7245c38d5eaf082416fdb73034003f5825eb5e21313044e8d2d9e1f3fe2831e345d3d1b1d20bcd12270719 - languageName: node - linkType: hard - "@types/html-minifier-terser@npm:^5.0.0": version: 5.1.2 resolution: "@types/html-minifier-terser@npm:5.1.2" @@ -8088,15 +8078,6 @@ __metadata: languageName: node linkType: hard -"hoist-non-react-statics@npm:^3.3.0, hoist-non-react-statics@npm:^3.3.2": - version: 3.3.2 - resolution: "hoist-non-react-statics@npm:3.3.2" - dependencies: - react-is: ^16.7.0 - checksum: b1538270429b13901ee586aa44f4cc3ecd8831c061d06cb8322e50ea17b3f5ce4d0e2e66394761e6c8e152cd8c34fb3b4b690116c6ce2bd45b18c746516cb9e8 - languageName: node - linkType: hard - "hoopy@npm:^0.1.4": version: 0.1.4 resolution: "hoopy@npm:0.1.4" @@ -8410,10 +8391,10 @@ __metadata: languageName: node linkType: hard -"immer@npm:^9.0.16": - version: 9.0.19 - resolution: "immer@npm:9.0.19" - checksum: f02ee53989989c287cd548a3d817fccf0bfe56db919755ee94a72ea3ae78a00363fba93ee6c010fe54a664380c29c53d44ed4091c6a86cae60957ad2cfabc010 +"immer@npm:^10.0.2": + version: 10.0.2 + resolution: "immer@npm:10.0.2" + checksum: 525a3b14210d02ae420c3b9f6ca14f7e9bcf625611d1356e773e7739f14c7c8de50dac442e6c7de3a6e24a782f7b792b6b8666bc0b3f00269d21a95f8f68ca84 languageName: node linkType: hard @@ -13239,7 +13220,7 @@ __metadata: languageName: node linkType: hard -"react-is@npm:^16.13.1, react-is@npm:^16.7.0": +"react-is@npm:^16.13.1": version: 16.13.1 resolution: "react-is@npm:16.13.1" checksum: f7a19ac3496de32ca9ae12aa030f00f14a3d45374f1ceca0af707c831b2a6098ef0d6bdae51bd437b0a306d7f01d4677fcc8de7c0d331eb47ad0f46130e53c5f @@ -13260,23 +13241,20 @@ __metadata: languageName: node linkType: hard -"react-redux@npm:^8.0.5": - version: 8.0.5 - resolution: "react-redux@npm:8.0.5" +"react-redux@npm:^9.0.0-alpha.1": + version: 9.0.0-alpha.1 + resolution: "react-redux@npm:9.0.0-alpha.1" dependencies: - "@babel/runtime": ^7.12.1 - "@types/hoist-non-react-statics": ^3.3.1 "@types/use-sync-external-store": ^0.0.3 - hoist-non-react-statics: ^3.3.2 react-is: ^18.0.0 use-sync-external-store: ^1.0.0 peerDependencies: - "@types/react": ^16.8 || ^17.0 || ^18.0 - "@types/react-dom": ^16.8 || ^17.0 || ^18.0 - react: ^16.8 || ^17.0 || ^18.0 - react-dom: ^16.8 || ^17.0 || ^18.0 - react-native: ">=0.59" - redux: ^4 + "@types/react": ^18.0 + "@types/react-dom": ^18.0 + react: ^18.0 + react-dom: ^18.0 + react-native: ">=0.71" + redux: ^5.0.0-beta.0 peerDependenciesMeta: "@types/react": optional: true @@ -13288,7 +13266,7 @@ __metadata: optional: true redux: optional: true - checksum: a108f4f7ead6ac005e656d46051474a2bbdb31ede481bbbb3d8d779c1a35e1940b8655577cc5021313411864d305f67fc719aa48d6e5ed8288cf9cbe8b7042e4 + checksum: ee4edbf417d0f16297e8bc6d039129f915f4d9330ec160ab4135e859a8ec7f11f943cfc26a2d336ec2395c2549c7e8e6d7a4f341febd5404ed0221fb5cbfc17a languageName: node linkType: hard @@ -13474,21 +13452,19 @@ __metadata: languageName: node linkType: hard -"redux-thunk@npm:^2.4.2": - version: 2.4.2 - resolution: "redux-thunk@npm:2.4.2" +"redux-thunk@npm:^3.0.0-beta.0": + version: 3.0.0-beta.0 + resolution: "redux-thunk@npm:3.0.0-beta.0" peerDependencies: - redux: ^4 - checksum: c7f757f6c383b8ec26152c113e20087818d18ed3edf438aaad43539e9a6b77b427ade755c9595c4a163b6ad3063adf3497e5fe6a36c68884eb1f1cfb6f049a5c + redux: ^4 || ^5.0.0-beta.0 + checksum: 1609e18a9fb56ab7403d760999996b50e136fcf7411ec9d809e9a4afa4187bf0ab545652c05ffbfca2e0397e59e6baf2ae0d35631a30bf8ba20af1205e98e0fe languageName: node linkType: hard -"redux@npm:^4.2.0": - version: 4.2.1 - resolution: "redux@npm:4.2.1" - dependencies: - "@babel/runtime": ^7.9.2 - checksum: f63b9060c3a1d930ae775252bb6e579b42415aee7a23c4114e21a0b4ba7ec12f0ec76936c00f546893f06e139819f0e2855e0d55ebfce34ca9c026241a6950dd +"redux@npm:^5.0.0-beta.0": + version: 5.0.0-beta.0 + resolution: "redux@npm:5.0.0-beta.0" + checksum: 11df373e219f2f515ee1bda1a19a1ba5de02d8d5c874800ec353179dcd106eddd54432946fd0ab37c47f99f8fe53f820a6404c14da7f039a46022187e9469d2d languageName: node linkType: hard @@ -13679,10 +13655,10 @@ __metadata: languageName: node linkType: hard -"reselect@npm:^4.1.7": - version: 4.1.7 - resolution: "reselect@npm:4.1.7" - checksum: 738d8e2b8f0dca154ad29de6a209c9fbca2d70ae6788fd85df87f2c74b95a65bbf2d16d43a9e2faff39de34d17a29d706ba08a6b2ee5660c09589edbd19af7e1 +"reselect@npm:^5.0.0-alpha.2": + version: 5.0.0-alpha.2 + resolution: "reselect@npm:5.0.0-alpha.2" + checksum: c47b66999800e1297721cbc4b2464b520fade9823c598d578759c9fba3eb6be03b184e13c20f30820cc18fe2688fc9fb4475f83e59d8f2347aa0d591e465637d languageName: node linkType: hard @@ -13978,7 +13954,7 @@ __metadata: resolution: "rtk-esm-cra@workspace:." dependencies: "@playwright/test": ^1.31.1 - "@reduxjs/toolkit": ^1.9.3 + "@reduxjs/toolkit": ^2.0.0-beta.2 "@testing-library/jest-dom": ^5.16.5 "@testing-library/react": ^13.4.0 "@testing-library/user-event": ^14.4.3 @@ -13991,7 +13967,7 @@ __metadata: prettier: ^2.8.4 react: ^18.2.0 react-dom: ^18.2.0 - react-redux: ^8.0.5 + react-redux: ^9.0.0-alpha.1 react-scripts: ^4 serve: ^14.2.0 typescript: ^4.9.4 diff --git a/examples/publish-ci/cra5/package.json b/examples/publish-ci/cra5/package.json index 00411c79c4..730c1f8675 100644 --- a/examples/publish-ci/cra5/package.json +++ b/examples/publish-ci/cra5/package.json @@ -3,11 +3,11 @@ "version": "0.1.0", "private": true, "dependencies": { - "@reduxjs/toolkit": "^1.9.3", + "@reduxjs/toolkit": "^2.0.0-beta.2", "msw": "^0.49.2", "react": "^18.2.0", "react-dom": "^18.2.0", - "react-redux": "^8.0.5", + "react-redux": "^9.0.0-alpha.1", "react-scripts": "5", "web-vitals": "^2.1.4" }, diff --git a/examples/publish-ci/cra5/yarn.lock b/examples/publish-ci/cra5/yarn.lock index d6666ac8e9..0589cfa61d 100644 --- a/examples/publish-ci/cra5/yarn.lock +++ b/examples/publish-ci/cra5/yarn.lock @@ -1459,7 +1459,7 @@ __metadata: languageName: node linkType: hard -"@babel/runtime@npm:^7.11.2, @babel/runtime@npm:^7.12.1, @babel/runtime@npm:^7.12.5, @babel/runtime@npm:^7.16.3, @babel/runtime@npm:^7.20.7, @babel/runtime@npm:^7.8.4, @babel/runtime@npm:^7.9.2": +"@babel/runtime@npm:^7.11.2, @babel/runtime@npm:^7.12.5, @babel/runtime@npm:^7.16.3, @babel/runtime@npm:^7.20.7, @babel/runtime@npm:^7.8.4, @babel/runtime@npm:^7.9.2": version: 7.21.0 resolution: "@babel/runtime@npm:7.21.0" dependencies: @@ -2253,23 +2253,23 @@ __metadata: languageName: node linkType: hard -"@reduxjs/toolkit@npm:^1.9.3": - version: 1.9.3 - resolution: "@reduxjs/toolkit@npm:1.9.3" +"@reduxjs/toolkit@npm:^2.0.0-beta.2": + version: 2.0.0-beta.2 + resolution: "@reduxjs/toolkit@npm:2.0.0-beta.2" dependencies: - immer: ^9.0.16 - redux: ^4.2.0 - redux-thunk: ^2.4.2 - reselect: ^4.1.7 + immer: ^10.0.2 + redux: ^5.0.0-beta.0 + redux-thunk: ^3.0.0-beta.0 + reselect: ^5.0.0-alpha.2 peerDependencies: react: ^16.9.0 || ^17.0.0 || ^18 - react-redux: ^7.2.1 || ^8.0.2 + react-redux: ^7.2.1 || ^8.0.2 || ^9.0.0-alpha.1 peerDependenciesMeta: react: optional: true react-redux: optional: true - checksum: d965fc6197fd420e4b8eb714015aa908d4ea214e6e10889cf4521e64ac4f8e0a7de29ee4e8da63b291acc40ac330dd31682334090d54838add73c746dc590650 + checksum: 340b515190ba23785464aab85141e49e57a924cde8a335a8df789f1b212cb59fb35e7d8e241bbac9f6e27d17c5236d440aca6f7a7bb2fa36228e0c111c7ec768 languageName: node linkType: hard @@ -2765,16 +2765,6 @@ __metadata: languageName: node linkType: hard -"@types/hoist-non-react-statics@npm:^3.3.1": - version: 3.3.1 - resolution: "@types/hoist-non-react-statics@npm:3.3.1" - dependencies: - "@types/react": "*" - hoist-non-react-statics: ^3.3.0 - checksum: 2c0778570d9a01d05afabc781b32163f28409bb98f7245c38d5eaf082416fdb73034003f5825eb5e21313044e8d2d9e1f3fe2831e345d3d1b1d20bcd12270719 - languageName: node - linkType: hard - "@types/html-minifier-terser@npm:^6.0.0": version: 6.1.0 resolution: "@types/html-minifier-terser@npm:6.1.0" @@ -7081,15 +7071,6 @@ __metadata: languageName: node linkType: hard -"hoist-non-react-statics@npm:^3.3.0, hoist-non-react-statics@npm:^3.3.2": - version: 3.3.2 - resolution: "hoist-non-react-statics@npm:3.3.2" - dependencies: - react-is: ^16.7.0 - checksum: b1538270429b13901ee586aa44f4cc3ecd8831c061d06cb8322e50ea17b3f5ce4d0e2e66394761e6c8e152cd8c34fb3b4b690116c6ce2bd45b18c746516cb9e8 - languageName: node - linkType: hard - "hoopy@npm:^0.1.4": version: 0.1.4 resolution: "hoopy@npm:0.1.4" @@ -7356,7 +7337,14 @@ __metadata: languageName: node linkType: hard -"immer@npm:^9.0.16, immer@npm:^9.0.7": +"immer@npm:^10.0.2": + version: 10.0.2 + resolution: "immer@npm:10.0.2" + checksum: 525a3b14210d02ae420c3b9f6ca14f7e9bcf625611d1356e773e7739f14c7c8de50dac442e6c7de3a6e24a782f7b792b6b8666bc0b3f00269d21a95f8f68ca84 + languageName: node + linkType: hard + +"immer@npm:^9.0.7": version: 9.0.19 resolution: "immer@npm:9.0.19" checksum: f02ee53989989c287cd548a3d817fccf0bfe56db919755ee94a72ea3ae78a00363fba93ee6c010fe54a664380c29c53d44ed4091c6a86cae60957ad2cfabc010 @@ -11216,7 +11204,7 @@ __metadata: languageName: node linkType: hard -"react-is@npm:^16.13.1, react-is@npm:^16.7.0": +"react-is@npm:^16.13.1": version: 16.13.1 resolution: "react-is@npm:16.13.1" checksum: f7a19ac3496de32ca9ae12aa030f00f14a3d45374f1ceca0af707c831b2a6098ef0d6bdae51bd437b0a306d7f01d4677fcc8de7c0d331eb47ad0f46130e53c5f @@ -11237,23 +11225,20 @@ __metadata: languageName: node linkType: hard -"react-redux@npm:^8.0.5": - version: 8.0.5 - resolution: "react-redux@npm:8.0.5" +"react-redux@npm:^9.0.0-alpha.1": + version: 9.0.0-alpha.1 + resolution: "react-redux@npm:9.0.0-alpha.1" dependencies: - "@babel/runtime": ^7.12.1 - "@types/hoist-non-react-statics": ^3.3.1 "@types/use-sync-external-store": ^0.0.3 - hoist-non-react-statics: ^3.3.2 react-is: ^18.0.0 use-sync-external-store: ^1.0.0 peerDependencies: - "@types/react": ^16.8 || ^17.0 || ^18.0 - "@types/react-dom": ^16.8 || ^17.0 || ^18.0 - react: ^16.8 || ^17.0 || ^18.0 - react-dom: ^16.8 || ^17.0 || ^18.0 - react-native: ">=0.59" - redux: ^4 + "@types/react": ^18.0 + "@types/react-dom": ^18.0 + react: ^18.0 + react-dom: ^18.0 + react-native: ">=0.71" + redux: ^5.0.0-beta.0 peerDependenciesMeta: "@types/react": optional: true @@ -11265,7 +11250,7 @@ __metadata: optional: true redux: optional: true - checksum: a108f4f7ead6ac005e656d46051474a2bbdb31ede481bbbb3d8d779c1a35e1940b8655577cc5021313411864d305f67fc719aa48d6e5ed8288cf9cbe8b7042e4 + checksum: ee4edbf417d0f16297e8bc6d039129f915f4d9330ec160ab4135e859a8ec7f11f943cfc26a2d336ec2395c2549c7e8e6d7a4f341febd5404ed0221fb5cbfc17a languageName: node linkType: hard @@ -11415,21 +11400,19 @@ __metadata: languageName: node linkType: hard -"redux-thunk@npm:^2.4.2": - version: 2.4.2 - resolution: "redux-thunk@npm:2.4.2" +"redux-thunk@npm:^3.0.0-beta.0": + version: 3.0.0-beta.0 + resolution: "redux-thunk@npm:3.0.0-beta.0" peerDependencies: - redux: ^4 - checksum: c7f757f6c383b8ec26152c113e20087818d18ed3edf438aaad43539e9a6b77b427ade755c9595c4a163b6ad3063adf3497e5fe6a36c68884eb1f1cfb6f049a5c + redux: ^4 || ^5.0.0-beta.0 + checksum: 1609e18a9fb56ab7403d760999996b50e136fcf7411ec9d809e9a4afa4187bf0ab545652c05ffbfca2e0397e59e6baf2ae0d35631a30bf8ba20af1205e98e0fe languageName: node linkType: hard -"redux@npm:^4.2.0": - version: 4.2.1 - resolution: "redux@npm:4.2.1" - dependencies: - "@babel/runtime": ^7.9.2 - checksum: f63b9060c3a1d930ae775252bb6e579b42415aee7a23c4114e21a0b4ba7ec12f0ec76936c00f546893f06e139819f0e2855e0d55ebfce34ca9c026241a6950dd +"redux@npm:^5.0.0-beta.0": + version: 5.0.0-beta.0 + resolution: "redux@npm:5.0.0-beta.0" + checksum: 11df373e219f2f515ee1bda1a19a1ba5de02d8d5c874800ec353179dcd106eddd54432946fd0ab37c47f99f8fe53f820a6404c14da7f039a46022187e9469d2d languageName: node linkType: hard @@ -11575,10 +11558,10 @@ __metadata: languageName: node linkType: hard -"reselect@npm:^4.1.7": - version: 4.1.7 - resolution: "reselect@npm:4.1.7" - checksum: 738d8e2b8f0dca154ad29de6a209c9fbca2d70ae6788fd85df87f2c74b95a65bbf2d16d43a9e2faff39de34d17a29d706ba08a6b2ee5660c09589edbd19af7e1 +"reselect@npm:^5.0.0-alpha.2": + version: 5.0.0-alpha.2 + resolution: "reselect@npm:5.0.0-alpha.2" + checksum: c47b66999800e1297721cbc4b2464b520fade9823c598d578759c9fba3eb6be03b184e13c20f30820cc18fe2688fc9fb4475f83e59d8f2347aa0d591e465637d languageName: node linkType: hard @@ -11760,7 +11743,7 @@ __metadata: resolution: "rtk-esm-cra@workspace:." dependencies: "@playwright/test": ^1.31.1 - "@reduxjs/toolkit": ^1.9.3 + "@reduxjs/toolkit": ^2.0.0-beta.2 "@testing-library/jest-dom": ^5.16.5 "@testing-library/react": ^13.4.0 "@testing-library/user-event": ^14.4.3 @@ -11773,7 +11756,7 @@ __metadata: prettier: ^2.8.4 react: ^18.2.0 react-dom: ^18.2.0 - react-redux: ^8.0.5 + react-redux: ^9.0.0-alpha.1 react-scripts: 5 serve: ^14.2.0 typescript: ^4.9.4 diff --git a/examples/publish-ci/next/package.json b/examples/publish-ci/next/package.json index 555cd906ec..79cd79e65e 100644 --- a/examples/publish-ci/next/package.json +++ b/examples/publish-ci/next/package.json @@ -10,12 +10,12 @@ "format": "prettier --write \"./src/**/*.{ts,tsx}\" \"**/*.md\"" }, "dependencies": { - "@reduxjs/toolkit": "^1.9.3", + "@reduxjs/toolkit": "^2.0.0-beta.2", "msw": "^0.49.2", "next": "^13.2", "react": "^18.2.0", "react-dom": "^18.2.0", - "react-redux": "^8.0.2" + "react-redux": "^9.0.0-alpha.1" }, "devDependencies": { "@playwright/test": "^1.31.1", diff --git a/examples/publish-ci/next/yarn.lock b/examples/publish-ci/next/yarn.lock index a73cc1e69b..52112010c8 100644 --- a/examples/publish-ci/next/yarn.lock +++ b/examples/publish-ci/next/yarn.lock @@ -39,7 +39,7 @@ __metadata: languageName: node linkType: hard -"@babel/runtime@npm:^7.12.1, @babel/runtime@npm:^7.12.5, @babel/runtime@npm:^7.9.2": +"@babel/runtime@npm:^7.12.5, @babel/runtime@npm:^7.9.2": version: 7.21.0 resolution: "@babel/runtime@npm:7.21.0" dependencies: @@ -254,23 +254,23 @@ __metadata: languageName: node linkType: hard -"@reduxjs/toolkit@npm:^1.9.3": - version: 1.9.3 - resolution: "@reduxjs/toolkit@npm:1.9.3" +"@reduxjs/toolkit@npm:^2.0.0-beta.2": + version: 2.0.0-beta.2 + resolution: "@reduxjs/toolkit@npm:2.0.0-beta.2" dependencies: - immer: ^9.0.16 - redux: ^4.2.0 - redux-thunk: ^2.4.2 - reselect: ^4.1.7 + immer: ^10.0.2 + redux: ^5.0.0-beta.0 + redux-thunk: ^3.0.0-beta.0 + reselect: ^5.0.0-alpha.2 peerDependencies: react: ^16.9.0 || ^17.0.0 || ^18 - react-redux: ^7.2.1 || ^8.0.2 + react-redux: ^7.2.1 || ^8.0.2 || ^9.0.0-alpha.1 peerDependenciesMeta: react: optional: true react-redux: optional: true - checksum: d965fc6197fd420e4b8eb714015aa908d4ea214e6e10889cf4521e64ac4f8e0a7de29ee4e8da63b291acc40ac330dd31682334090d54838add73c746dc590650 + checksum: 340b515190ba23785464aab85141e49e57a924cde8a335a8df789f1b212cb59fb35e7d8e241bbac9f6e27d17c5236d440aca6f7a7bb2fa36228e0c111c7ec768 languageName: node linkType: hard @@ -376,16 +376,6 @@ __metadata: languageName: node linkType: hard -"@types/hoist-non-react-statics@npm:^3.3.1": - version: 3.3.1 - resolution: "@types/hoist-non-react-statics@npm:3.3.1" - dependencies: - "@types/react": "*" - hoist-non-react-statics: ^3.3.0 - checksum: 2c0778570d9a01d05afabc781b32163f28409bb98f7245c38d5eaf082416fdb73034003f5825eb5e21313044e8d2d9e1f3fe2831e345d3d1b1d20bcd12270719 - languageName: node - linkType: hard - "@types/istanbul-lib-coverage@npm:*, @types/istanbul-lib-coverage@npm:^2.0.0": version: 2.0.4 resolution: "@types/istanbul-lib-coverage@npm:2.0.4" @@ -1678,15 +1668,6 @@ __metadata: languageName: node linkType: hard -"hoist-non-react-statics@npm:^3.3.0, hoist-non-react-statics@npm:^3.3.2": - version: 3.3.2 - resolution: "hoist-non-react-statics@npm:3.3.2" - dependencies: - react-is: ^16.7.0 - checksum: b1538270429b13901ee586aa44f4cc3ecd8831c061d06cb8322e50ea17b3f5ce4d0e2e66394761e6c8e152cd8c34fb3b4b690116c6ce2bd45b18c746516cb9e8 - languageName: node - linkType: hard - "http-cache-semantics@npm:^4.1.0": version: 4.1.1 resolution: "http-cache-semantics@npm:4.1.1" @@ -1756,10 +1737,10 @@ __metadata: languageName: node linkType: hard -"immer@npm:^9.0.16": - version: 9.0.19 - resolution: "immer@npm:9.0.19" - checksum: f02ee53989989c287cd548a3d817fccf0bfe56db919755ee94a72ea3ae78a00363fba93ee6c010fe54a664380c29c53d44ed4091c6a86cae60957ad2cfabc010 +"immer@npm:^10.0.2": + version: 10.0.2 + resolution: "immer@npm:10.0.2" + checksum: 525a3b14210d02ae420c3b9f6ca14f7e9bcf625611d1356e773e7739f14c7c8de50dac442e6c7de3a6e24a782f7b792b6b8666bc0b3f00269d21a95f8f68ca84 languageName: node linkType: hard @@ -2980,13 +2961,6 @@ __metadata: languageName: node linkType: hard -"react-is@npm:^16.7.0": - version: 16.13.1 - resolution: "react-is@npm:16.13.1" - checksum: f7a19ac3496de32ca9ae12aa030f00f14a3d45374f1ceca0af707c831b2a6098ef0d6bdae51bd437b0a306d7f01d4677fcc8de7c0d331eb47ad0f46130e53c5f - languageName: node - linkType: hard - "react-is@npm:^17.0.1": version: 17.0.2 resolution: "react-is@npm:17.0.2" @@ -3001,23 +2975,20 @@ __metadata: languageName: node linkType: hard -"react-redux@npm:^8.0.2": - version: 8.0.5 - resolution: "react-redux@npm:8.0.5" +"react-redux@npm:^9.0.0-alpha.1": + version: 9.0.0-alpha.1 + resolution: "react-redux@npm:9.0.0-alpha.1" dependencies: - "@babel/runtime": ^7.12.1 - "@types/hoist-non-react-statics": ^3.3.1 "@types/use-sync-external-store": ^0.0.3 - hoist-non-react-statics: ^3.3.2 react-is: ^18.0.0 use-sync-external-store: ^1.0.0 peerDependencies: - "@types/react": ^16.8 || ^17.0 || ^18.0 - "@types/react-dom": ^16.8 || ^17.0 || ^18.0 - react: ^16.8 || ^17.0 || ^18.0 - react-dom: ^16.8 || ^17.0 || ^18.0 - react-native: ">=0.59" - redux: ^4 + "@types/react": ^18.0 + "@types/react-dom": ^18.0 + react: ^18.0 + react-dom: ^18.0 + react-native: ">=0.71" + redux: ^5.0.0-beta.0 peerDependenciesMeta: "@types/react": optional: true @@ -3029,7 +3000,7 @@ __metadata: optional: true redux: optional: true - checksum: a108f4f7ead6ac005e656d46051474a2bbdb31ede481bbbb3d8d779c1a35e1940b8655577cc5021313411864d305f67fc719aa48d6e5ed8288cf9cbe8b7042e4 + checksum: ee4edbf417d0f16297e8bc6d039129f915f4d9330ec160ab4135e859a8ec7f11f943cfc26a2d336ec2395c2549c7e8e6d7a4f341febd5404ed0221fb5cbfc17a languageName: node linkType: hard @@ -3072,21 +3043,19 @@ __metadata: languageName: node linkType: hard -"redux-thunk@npm:^2.4.2": - version: 2.4.2 - resolution: "redux-thunk@npm:2.4.2" +"redux-thunk@npm:^3.0.0-beta.0": + version: 3.0.0-beta.0 + resolution: "redux-thunk@npm:3.0.0-beta.0" peerDependencies: - redux: ^4 - checksum: c7f757f6c383b8ec26152c113e20087818d18ed3edf438aaad43539e9a6b77b427ade755c9595c4a163b6ad3063adf3497e5fe6a36c68884eb1f1cfb6f049a5c + redux: ^4 || ^5.0.0-beta.0 + checksum: 1609e18a9fb56ab7403d760999996b50e136fcf7411ec9d809e9a4afa4187bf0ab545652c05ffbfca2e0397e59e6baf2ae0d35631a30bf8ba20af1205e98e0fe languageName: node linkType: hard -"redux@npm:^4.2.0": - version: 4.2.1 - resolution: "redux@npm:4.2.1" - dependencies: - "@babel/runtime": ^7.9.2 - checksum: f63b9060c3a1d930ae775252bb6e579b42415aee7a23c4114e21a0b4ba7ec12f0ec76936c00f546893f06e139819f0e2855e0d55ebfce34ca9c026241a6950dd +"redux@npm:^5.0.0-beta.0": + version: 5.0.0-beta.0 + resolution: "redux@npm:5.0.0-beta.0" + checksum: 11df373e219f2f515ee1bda1a19a1ba5de02d8d5c874800ec353179dcd106eddd54432946fd0ab37c47f99f8fe53f820a6404c14da7f039a46022187e9469d2d languageName: node linkType: hard @@ -3141,10 +3110,10 @@ __metadata: languageName: node linkType: hard -"reselect@npm:^4.1.7": - version: 4.1.7 - resolution: "reselect@npm:4.1.7" - checksum: 738d8e2b8f0dca154ad29de6a209c9fbca2d70ae6788fd85df87f2c74b95a65bbf2d16d43a9e2faff39de34d17a29d706ba08a6b2ee5660c09589edbd19af7e1 +"reselect@npm:^5.0.0-alpha.2": + version: 5.0.0-alpha.2 + resolution: "reselect@npm:5.0.0-alpha.2" + checksum: c47b66999800e1297721cbc4b2464b520fade9823c598d578759c9fba3eb6be03b184e13c20f30820cc18fe2688fc9fb4475f83e59d8f2347aa0d591e465637d languageName: node linkType: hard @@ -3181,7 +3150,7 @@ __metadata: resolution: "root-workspace-0b6124@workspace:." dependencies: "@playwright/test": ^1.31.1 - "@reduxjs/toolkit": ^1.9.3 + "@reduxjs/toolkit": ^2.0.0-beta.2 "@testing-library/jest-dom": ^5.16.5 "@testing-library/react": ^13.4.0 "@testing-library/user-event": ^14.4.3 @@ -3195,7 +3164,7 @@ __metadata: prettier: ^2.8.4 react: ^18.2.0 react-dom: ^18.2.0 - react-redux: ^8.0.2 + react-redux: ^9.0.0-alpha.1 serve: ^14.2.0 typescript: ^4.9.4 languageName: unknown diff --git a/examples/publish-ci/node-esm/package.json b/examples/publish-ci/node-esm/package.json index c31677898c..f97ffdba0c 100644 --- a/examples/publish-ci/node-esm/package.json +++ b/examples/publish-ci/node-esm/package.json @@ -8,10 +8,10 @@ "test": "node test-cjs.cjs && node test-esm.mjs" }, "dependencies": { - "@reduxjs/toolkit": "^1.9.3", + "@reduxjs/toolkit": "^2.0.0-beta.2", "react": "^18.2.0", "react-dom": "^18.2.0", - "react-redux": "^8.0.5" + "react-redux": "^9.0.0-alpha.1" }, "devDependencies": { "resolve-esm": "^1.4.0" diff --git a/examples/publish-ci/node-esm/yarn.lock b/examples/publish-ci/node-esm/yarn.lock index 0ab2c5bd0c..a703adc19c 100644 --- a/examples/publish-ci/node-esm/yarn.lock +++ b/examples/publish-ci/node-esm/yarn.lock @@ -5,67 +5,23 @@ __metadata: version: 6 cacheKey: 8 -"@babel/runtime@npm:^7.12.1, @babel/runtime@npm:^7.9.2": - version: 7.21.0 - resolution: "@babel/runtime@npm:7.21.0" +"@reduxjs/toolkit@npm:^2.0.0-beta.2": + version: 2.0.0-beta.2 + resolution: "@reduxjs/toolkit@npm:2.0.0-beta.2" dependencies: - regenerator-runtime: ^0.13.11 - checksum: 7b33e25bfa9e0e1b9e8828bb61b2d32bdd46b41b07ba7cb43319ad08efc6fda8eb89445193e67d6541814627df0ca59122c0ea795e412b99c5183a0540d338ab - languageName: node - linkType: hard - -"@reduxjs/toolkit@npm:^1.9.3": - version: 1.9.3 - resolution: "@reduxjs/toolkit@npm:1.9.3" - dependencies: - immer: ^9.0.16 - redux: ^4.2.0 - redux-thunk: ^2.4.2 - reselect: ^4.1.7 + immer: ^10.0.2 + redux: ^5.0.0-beta.0 + redux-thunk: ^3.0.0-beta.0 + reselect: ^5.0.0-alpha.2 peerDependencies: react: ^16.9.0 || ^17.0.0 || ^18 - react-redux: ^7.2.1 || ^8.0.2 + react-redux: ^7.2.1 || ^8.0.2 || ^9.0.0-alpha.1 peerDependenciesMeta: react: optional: true react-redux: optional: true - checksum: d965fc6197fd420e4b8eb714015aa908d4ea214e6e10889cf4521e64ac4f8e0a7de29ee4e8da63b291acc40ac330dd31682334090d54838add73c746dc590650 - languageName: node - linkType: hard - -"@types/hoist-non-react-statics@npm:^3.3.1": - version: 3.3.1 - resolution: "@types/hoist-non-react-statics@npm:3.3.1" - dependencies: - "@types/react": "*" - hoist-non-react-statics: ^3.3.0 - checksum: 2c0778570d9a01d05afabc781b32163f28409bb98f7245c38d5eaf082416fdb73034003f5825eb5e21313044e8d2d9e1f3fe2831e345d3d1b1d20bcd12270719 - languageName: node - linkType: hard - -"@types/prop-types@npm:*": - version: 15.7.5 - resolution: "@types/prop-types@npm:15.7.5" - checksum: 5b43b8b15415e1f298243165f1d44390403bb2bd42e662bca3b5b5633fdd39c938e91b7fce3a9483699db0f7a715d08cef220c121f723a634972fdf596aec980 - languageName: node - linkType: hard - -"@types/react@npm:*": - version: 18.0.28 - resolution: "@types/react@npm:18.0.28" - dependencies: - "@types/prop-types": "*" - "@types/scheduler": "*" - csstype: ^3.0.2 - checksum: e752df961105e5127652460504785897ca6e77259e0da8f233f694f9e8f451cde7fa0709d4456ade0ff600c8ce909cfe29f9b08b9c247fa9b734e126ec53edd7 - languageName: node - linkType: hard - -"@types/scheduler@npm:*": - version: 0.16.2 - resolution: "@types/scheduler@npm:0.16.2" - checksum: b6b4dcfeae6deba2e06a70941860fb1435730576d3689225a421280b7742318d1548b3d22c1f66ab68e414f346a9542f29240bc955b6332c5b11e561077583bc + checksum: 340b515190ba23785464aab85141e49e57a924cde8a335a8df789f1b212cb59fb35e7d8e241bbac9f6e27d17c5236d440aca6f7a7bb2fa36228e0c111c7ec768 languageName: node linkType: hard @@ -83,38 +39,22 @@ __metadata: languageName: node linkType: hard -"csstype@npm:^3.0.2": - version: 3.1.1 - resolution: "csstype@npm:3.1.1" - checksum: 1f7b4f5fdd955b7444b18ebdddf3f5c699159f13e9cf8ac9027ae4a60ae226aef9bbb14a6e12ca7dba3358b007cee6354b116e720262867c398de6c955ea451d - languageName: node - linkType: hard - "dual-module-test@workspace:.": version: 0.0.0-use.local resolution: "dual-module-test@workspace:." dependencies: - "@reduxjs/toolkit": ^1.9.3 + "@reduxjs/toolkit": ^2.0.0-beta.2 react: ^18.2.0 react-dom: ^18.2.0 - react-redux: ^8.0.5 + react-redux: ^9.0.0-alpha.1 resolve-esm: ^1.4.0 languageName: unknown linkType: soft -"hoist-non-react-statics@npm:^3.3.0, hoist-non-react-statics@npm:^3.3.2": - version: 3.3.2 - resolution: "hoist-non-react-statics@npm:3.3.2" - dependencies: - react-is: ^16.7.0 - checksum: b1538270429b13901ee586aa44f4cc3ecd8831c061d06cb8322e50ea17b3f5ce4d0e2e66394761e6c8e152cd8c34fb3b4b690116c6ce2bd45b18c746516cb9e8 - languageName: node - linkType: hard - -"immer@npm:^9.0.16": - version: 9.0.19 - resolution: "immer@npm:9.0.19" - checksum: f02ee53989989c287cd548a3d817fccf0bfe56db919755ee94a72ea3ae78a00363fba93ee6c010fe54a664380c29c53d44ed4091c6a86cae60957ad2cfabc010 +"immer@npm:^10.0.2": + version: 10.0.2 + resolution: "immer@npm:10.0.2" + checksum: 525a3b14210d02ae420c3b9f6ca14f7e9bcf625611d1356e773e7739f14c7c8de50dac442e6c7de3a6e24a782f7b792b6b8666bc0b3f00269d21a95f8f68ca84 languageName: node linkType: hard @@ -148,13 +88,6 @@ __metadata: languageName: node linkType: hard -"react-is@npm:^16.7.0": - version: 16.13.1 - resolution: "react-is@npm:16.13.1" - checksum: f7a19ac3496de32ca9ae12aa030f00f14a3d45374f1ceca0af707c831b2a6098ef0d6bdae51bd437b0a306d7f01d4677fcc8de7c0d331eb47ad0f46130e53c5f - languageName: node - linkType: hard - "react-is@npm:^18.0.0": version: 18.2.0 resolution: "react-is@npm:18.2.0" @@ -162,23 +95,20 @@ __metadata: languageName: node linkType: hard -"react-redux@npm:^8.0.5": - version: 8.0.5 - resolution: "react-redux@npm:8.0.5" +"react-redux@npm:^9.0.0-alpha.1": + version: 9.0.0-alpha.1 + resolution: "react-redux@npm:9.0.0-alpha.1" dependencies: - "@babel/runtime": ^7.12.1 - "@types/hoist-non-react-statics": ^3.3.1 "@types/use-sync-external-store": ^0.0.3 - hoist-non-react-statics: ^3.3.2 react-is: ^18.0.0 use-sync-external-store: ^1.0.0 peerDependencies: - "@types/react": ^16.8 || ^17.0 || ^18.0 - "@types/react-dom": ^16.8 || ^17.0 || ^18.0 - react: ^16.8 || ^17.0 || ^18.0 - react-dom: ^16.8 || ^17.0 || ^18.0 - react-native: ">=0.59" - redux: ^4 + "@types/react": ^18.0 + "@types/react-dom": ^18.0 + react: ^18.0 + react-dom: ^18.0 + react-native: ">=0.71" + redux: ^5.0.0-beta.0 peerDependenciesMeta: "@types/react": optional: true @@ -190,7 +120,7 @@ __metadata: optional: true redux: optional: true - checksum: a108f4f7ead6ac005e656d46051474a2bbdb31ede481bbbb3d8d779c1a35e1940b8655577cc5021313411864d305f67fc719aa48d6e5ed8288cf9cbe8b7042e4 + checksum: ee4edbf417d0f16297e8bc6d039129f915f4d9330ec160ab4135e859a8ec7f11f943cfc26a2d336ec2395c2549c7e8e6d7a4f341febd5404ed0221fb5cbfc17a languageName: node linkType: hard @@ -203,35 +133,26 @@ __metadata: languageName: node linkType: hard -"redux-thunk@npm:^2.4.2": - version: 2.4.2 - resolution: "redux-thunk@npm:2.4.2" +"redux-thunk@npm:^3.0.0-beta.0": + version: 3.0.0-beta.0 + resolution: "redux-thunk@npm:3.0.0-beta.0" peerDependencies: - redux: ^4 - checksum: c7f757f6c383b8ec26152c113e20087818d18ed3edf438aaad43539e9a6b77b427ade755c9595c4a163b6ad3063adf3497e5fe6a36c68884eb1f1cfb6f049a5c - languageName: node - linkType: hard - -"redux@npm:^4.2.0": - version: 4.2.1 - resolution: "redux@npm:4.2.1" - dependencies: - "@babel/runtime": ^7.9.2 - checksum: f63b9060c3a1d930ae775252bb6e579b42415aee7a23c4114e21a0b4ba7ec12f0ec76936c00f546893f06e139819f0e2855e0d55ebfce34ca9c026241a6950dd + redux: ^4 || ^5.0.0-beta.0 + checksum: 1609e18a9fb56ab7403d760999996b50e136fcf7411ec9d809e9a4afa4187bf0ab545652c05ffbfca2e0397e59e6baf2ae0d35631a30bf8ba20af1205e98e0fe languageName: node linkType: hard -"regenerator-runtime@npm:^0.13.11": - version: 0.13.11 - resolution: "regenerator-runtime@npm:0.13.11" - checksum: 27481628d22a1c4e3ff551096a683b424242a216fee44685467307f14d58020af1e19660bf2e26064de946bad7eff28950eae9f8209d55723e2d9351e632bbb4 +"redux@npm:^5.0.0-beta.0": + version: 5.0.0-beta.0 + resolution: "redux@npm:5.0.0-beta.0" + checksum: 11df373e219f2f515ee1bda1a19a1ba5de02d8d5c874800ec353179dcd106eddd54432946fd0ab37c47f99f8fe53f820a6404c14da7f039a46022187e9469d2d languageName: node linkType: hard -"reselect@npm:^4.1.7": - version: 4.1.7 - resolution: "reselect@npm:4.1.7" - checksum: 738d8e2b8f0dca154ad29de6a209c9fbca2d70ae6788fd85df87f2c74b95a65bbf2d16d43a9e2faff39de34d17a29d706ba08a6b2ee5660c09589edbd19af7e1 +"reselect@npm:^5.0.0-alpha.2": + version: 5.0.0-alpha.2 + resolution: "reselect@npm:5.0.0-alpha.2" + checksum: c47b66999800e1297721cbc4b2464b520fade9823c598d578759c9fba3eb6be03b184e13c20f30820cc18fe2688fc9fb4475f83e59d8f2347aa0d591e465637d languageName: node linkType: hard diff --git a/examples/publish-ci/node-standard/package.json b/examples/publish-ci/node-standard/package.json index 7b5444e3b9..b76456587a 100644 --- a/examples/publish-ci/node-standard/package.json +++ b/examples/publish-ci/node-standard/package.json @@ -7,10 +7,10 @@ "test": "node test-cjs.js && node test-esm.mjs" }, "dependencies": { - "@reduxjs/toolkit": "^1.9.3", + "@reduxjs/toolkit": "^2.0.0-beta.2", "react": "^18.2.0", "react-dom": "^18.2.0", - "react-redux": "^8.0.5" + "react-redux": "^9.0.0-alpha.1" }, "devDependencies": { "resolve-esm": "^1.4.0" diff --git a/examples/publish-ci/node-standard/yarn.lock b/examples/publish-ci/node-standard/yarn.lock index 0ab2c5bd0c..a703adc19c 100644 --- a/examples/publish-ci/node-standard/yarn.lock +++ b/examples/publish-ci/node-standard/yarn.lock @@ -5,67 +5,23 @@ __metadata: version: 6 cacheKey: 8 -"@babel/runtime@npm:^7.12.1, @babel/runtime@npm:^7.9.2": - version: 7.21.0 - resolution: "@babel/runtime@npm:7.21.0" +"@reduxjs/toolkit@npm:^2.0.0-beta.2": + version: 2.0.0-beta.2 + resolution: "@reduxjs/toolkit@npm:2.0.0-beta.2" dependencies: - regenerator-runtime: ^0.13.11 - checksum: 7b33e25bfa9e0e1b9e8828bb61b2d32bdd46b41b07ba7cb43319ad08efc6fda8eb89445193e67d6541814627df0ca59122c0ea795e412b99c5183a0540d338ab - languageName: node - linkType: hard - -"@reduxjs/toolkit@npm:^1.9.3": - version: 1.9.3 - resolution: "@reduxjs/toolkit@npm:1.9.3" - dependencies: - immer: ^9.0.16 - redux: ^4.2.0 - redux-thunk: ^2.4.2 - reselect: ^4.1.7 + immer: ^10.0.2 + redux: ^5.0.0-beta.0 + redux-thunk: ^3.0.0-beta.0 + reselect: ^5.0.0-alpha.2 peerDependencies: react: ^16.9.0 || ^17.0.0 || ^18 - react-redux: ^7.2.1 || ^8.0.2 + react-redux: ^7.2.1 || ^8.0.2 || ^9.0.0-alpha.1 peerDependenciesMeta: react: optional: true react-redux: optional: true - checksum: d965fc6197fd420e4b8eb714015aa908d4ea214e6e10889cf4521e64ac4f8e0a7de29ee4e8da63b291acc40ac330dd31682334090d54838add73c746dc590650 - languageName: node - linkType: hard - -"@types/hoist-non-react-statics@npm:^3.3.1": - version: 3.3.1 - resolution: "@types/hoist-non-react-statics@npm:3.3.1" - dependencies: - "@types/react": "*" - hoist-non-react-statics: ^3.3.0 - checksum: 2c0778570d9a01d05afabc781b32163f28409bb98f7245c38d5eaf082416fdb73034003f5825eb5e21313044e8d2d9e1f3fe2831e345d3d1b1d20bcd12270719 - languageName: node - linkType: hard - -"@types/prop-types@npm:*": - version: 15.7.5 - resolution: "@types/prop-types@npm:15.7.5" - checksum: 5b43b8b15415e1f298243165f1d44390403bb2bd42e662bca3b5b5633fdd39c938e91b7fce3a9483699db0f7a715d08cef220c121f723a634972fdf596aec980 - languageName: node - linkType: hard - -"@types/react@npm:*": - version: 18.0.28 - resolution: "@types/react@npm:18.0.28" - dependencies: - "@types/prop-types": "*" - "@types/scheduler": "*" - csstype: ^3.0.2 - checksum: e752df961105e5127652460504785897ca6e77259e0da8f233f694f9e8f451cde7fa0709d4456ade0ff600c8ce909cfe29f9b08b9c247fa9b734e126ec53edd7 - languageName: node - linkType: hard - -"@types/scheduler@npm:*": - version: 0.16.2 - resolution: "@types/scheduler@npm:0.16.2" - checksum: b6b4dcfeae6deba2e06a70941860fb1435730576d3689225a421280b7742318d1548b3d22c1f66ab68e414f346a9542f29240bc955b6332c5b11e561077583bc + checksum: 340b515190ba23785464aab85141e49e57a924cde8a335a8df789f1b212cb59fb35e7d8e241bbac9f6e27d17c5236d440aca6f7a7bb2fa36228e0c111c7ec768 languageName: node linkType: hard @@ -83,38 +39,22 @@ __metadata: languageName: node linkType: hard -"csstype@npm:^3.0.2": - version: 3.1.1 - resolution: "csstype@npm:3.1.1" - checksum: 1f7b4f5fdd955b7444b18ebdddf3f5c699159f13e9cf8ac9027ae4a60ae226aef9bbb14a6e12ca7dba3358b007cee6354b116e720262867c398de6c955ea451d - languageName: node - linkType: hard - "dual-module-test@workspace:.": version: 0.0.0-use.local resolution: "dual-module-test@workspace:." dependencies: - "@reduxjs/toolkit": ^1.9.3 + "@reduxjs/toolkit": ^2.0.0-beta.2 react: ^18.2.0 react-dom: ^18.2.0 - react-redux: ^8.0.5 + react-redux: ^9.0.0-alpha.1 resolve-esm: ^1.4.0 languageName: unknown linkType: soft -"hoist-non-react-statics@npm:^3.3.0, hoist-non-react-statics@npm:^3.3.2": - version: 3.3.2 - resolution: "hoist-non-react-statics@npm:3.3.2" - dependencies: - react-is: ^16.7.0 - checksum: b1538270429b13901ee586aa44f4cc3ecd8831c061d06cb8322e50ea17b3f5ce4d0e2e66394761e6c8e152cd8c34fb3b4b690116c6ce2bd45b18c746516cb9e8 - languageName: node - linkType: hard - -"immer@npm:^9.0.16": - version: 9.0.19 - resolution: "immer@npm:9.0.19" - checksum: f02ee53989989c287cd548a3d817fccf0bfe56db919755ee94a72ea3ae78a00363fba93ee6c010fe54a664380c29c53d44ed4091c6a86cae60957ad2cfabc010 +"immer@npm:^10.0.2": + version: 10.0.2 + resolution: "immer@npm:10.0.2" + checksum: 525a3b14210d02ae420c3b9f6ca14f7e9bcf625611d1356e773e7739f14c7c8de50dac442e6c7de3a6e24a782f7b792b6b8666bc0b3f00269d21a95f8f68ca84 languageName: node linkType: hard @@ -148,13 +88,6 @@ __metadata: languageName: node linkType: hard -"react-is@npm:^16.7.0": - version: 16.13.1 - resolution: "react-is@npm:16.13.1" - checksum: f7a19ac3496de32ca9ae12aa030f00f14a3d45374f1ceca0af707c831b2a6098ef0d6bdae51bd437b0a306d7f01d4677fcc8de7c0d331eb47ad0f46130e53c5f - languageName: node - linkType: hard - "react-is@npm:^18.0.0": version: 18.2.0 resolution: "react-is@npm:18.2.0" @@ -162,23 +95,20 @@ __metadata: languageName: node linkType: hard -"react-redux@npm:^8.0.5": - version: 8.0.5 - resolution: "react-redux@npm:8.0.5" +"react-redux@npm:^9.0.0-alpha.1": + version: 9.0.0-alpha.1 + resolution: "react-redux@npm:9.0.0-alpha.1" dependencies: - "@babel/runtime": ^7.12.1 - "@types/hoist-non-react-statics": ^3.3.1 "@types/use-sync-external-store": ^0.0.3 - hoist-non-react-statics: ^3.3.2 react-is: ^18.0.0 use-sync-external-store: ^1.0.0 peerDependencies: - "@types/react": ^16.8 || ^17.0 || ^18.0 - "@types/react-dom": ^16.8 || ^17.0 || ^18.0 - react: ^16.8 || ^17.0 || ^18.0 - react-dom: ^16.8 || ^17.0 || ^18.0 - react-native: ">=0.59" - redux: ^4 + "@types/react": ^18.0 + "@types/react-dom": ^18.0 + react: ^18.0 + react-dom: ^18.0 + react-native: ">=0.71" + redux: ^5.0.0-beta.0 peerDependenciesMeta: "@types/react": optional: true @@ -190,7 +120,7 @@ __metadata: optional: true redux: optional: true - checksum: a108f4f7ead6ac005e656d46051474a2bbdb31ede481bbbb3d8d779c1a35e1940b8655577cc5021313411864d305f67fc719aa48d6e5ed8288cf9cbe8b7042e4 + checksum: ee4edbf417d0f16297e8bc6d039129f915f4d9330ec160ab4135e859a8ec7f11f943cfc26a2d336ec2395c2549c7e8e6d7a4f341febd5404ed0221fb5cbfc17a languageName: node linkType: hard @@ -203,35 +133,26 @@ __metadata: languageName: node linkType: hard -"redux-thunk@npm:^2.4.2": - version: 2.4.2 - resolution: "redux-thunk@npm:2.4.2" +"redux-thunk@npm:^3.0.0-beta.0": + version: 3.0.0-beta.0 + resolution: "redux-thunk@npm:3.0.0-beta.0" peerDependencies: - redux: ^4 - checksum: c7f757f6c383b8ec26152c113e20087818d18ed3edf438aaad43539e9a6b77b427ade755c9595c4a163b6ad3063adf3497e5fe6a36c68884eb1f1cfb6f049a5c - languageName: node - linkType: hard - -"redux@npm:^4.2.0": - version: 4.2.1 - resolution: "redux@npm:4.2.1" - dependencies: - "@babel/runtime": ^7.9.2 - checksum: f63b9060c3a1d930ae775252bb6e579b42415aee7a23c4114e21a0b4ba7ec12f0ec76936c00f546893f06e139819f0e2855e0d55ebfce34ca9c026241a6950dd + redux: ^4 || ^5.0.0-beta.0 + checksum: 1609e18a9fb56ab7403d760999996b50e136fcf7411ec9d809e9a4afa4187bf0ab545652c05ffbfca2e0397e59e6baf2ae0d35631a30bf8ba20af1205e98e0fe languageName: node linkType: hard -"regenerator-runtime@npm:^0.13.11": - version: 0.13.11 - resolution: "regenerator-runtime@npm:0.13.11" - checksum: 27481628d22a1c4e3ff551096a683b424242a216fee44685467307f14d58020af1e19660bf2e26064de946bad7eff28950eae9f8209d55723e2d9351e632bbb4 +"redux@npm:^5.0.0-beta.0": + version: 5.0.0-beta.0 + resolution: "redux@npm:5.0.0-beta.0" + checksum: 11df373e219f2f515ee1bda1a19a1ba5de02d8d5c874800ec353179dcd106eddd54432946fd0ab37c47f99f8fe53f820a6404c14da7f039a46022187e9469d2d languageName: node linkType: hard -"reselect@npm:^4.1.7": - version: 4.1.7 - resolution: "reselect@npm:4.1.7" - checksum: 738d8e2b8f0dca154ad29de6a209c9fbca2d70ae6788fd85df87f2c74b95a65bbf2d16d43a9e2faff39de34d17a29d706ba08a6b2ee5660c09589edbd19af7e1 +"reselect@npm:^5.0.0-alpha.2": + version: 5.0.0-alpha.2 + resolution: "reselect@npm:5.0.0-alpha.2" + checksum: c47b66999800e1297721cbc4b2464b520fade9823c598d578759c9fba3eb6be03b184e13c20f30820cc18fe2688fc9fb4475f83e59d8f2347aa0d591e465637d languageName: node linkType: hard diff --git a/examples/publish-ci/vite/package.json b/examples/publish-ci/vite/package.json index a576a66daf..236012cf32 100644 --- a/examples/publish-ci/vite/package.json +++ b/examples/publish-ci/vite/package.json @@ -11,11 +11,11 @@ "format": "prettier --write \"./src/**/*.{ts,tsx}\" \"**/*.md\"" }, "dependencies": { - "@reduxjs/toolkit": "^1.9.3", + "@reduxjs/toolkit": "^2.0.0-beta.2", "msw": "^0.49.2", "react": "^18.2.0", "react-dom": "^18.2.0", - "react-redux": "^8.0.5" + "react-redux": "^9.0.0-alpha.1" }, "devDependencies": { "@playwright/test": "^1.31.1", diff --git a/examples/publish-ci/vite/yarn.lock b/examples/publish-ci/vite/yarn.lock index 16e77f725e..27e58491f5 100644 --- a/examples/publish-ci/vite/yarn.lock +++ b/examples/publish-ci/vite/yarn.lock @@ -238,7 +238,7 @@ __metadata: languageName: node linkType: hard -"@babel/runtime@npm:^7.12.1, @babel/runtime@npm:^7.12.5, @babel/runtime@npm:^7.9.2": +"@babel/runtime@npm:^7.12.5, @babel/runtime@npm:^7.9.2": version: 7.21.0 resolution: "@babel/runtime@npm:7.21.0" dependencies: @@ -601,23 +601,23 @@ __metadata: languageName: node linkType: hard -"@reduxjs/toolkit@npm:^1.9.3": - version: 1.9.3 - resolution: "@reduxjs/toolkit@npm:1.9.3" +"@reduxjs/toolkit@npm:^2.0.0-beta.2": + version: 2.0.0-beta.2 + resolution: "@reduxjs/toolkit@npm:2.0.0-beta.2" dependencies: - immer: ^9.0.16 - redux: ^4.2.0 - redux-thunk: ^2.4.2 - reselect: ^4.1.7 + immer: ^10.0.2 + redux: ^5.0.0-beta.0 + redux-thunk: ^3.0.0-beta.0 + reselect: ^5.0.0-alpha.2 peerDependencies: react: ^16.9.0 || ^17.0.0 || ^18 - react-redux: ^7.2.1 || ^8.0.2 + react-redux: ^7.2.1 || ^8.0.2 || ^9.0.0-alpha.1 peerDependenciesMeta: react: optional: true react-redux: optional: true - checksum: d965fc6197fd420e4b8eb714015aa908d4ea214e6e10889cf4521e64ac4f8e0a7de29ee4e8da63b291acc40ac330dd31682334090d54838add73c746dc590650 + checksum: 340b515190ba23785464aab85141e49e57a924cde8a335a8df789f1b212cb59fb35e7d8e241bbac9f6e27d17c5236d440aca6f7a7bb2fa36228e0c111c7ec768 languageName: node linkType: hard @@ -714,16 +714,6 @@ __metadata: languageName: node linkType: hard -"@types/hoist-non-react-statics@npm:^3.3.1": - version: 3.3.1 - resolution: "@types/hoist-non-react-statics@npm:3.3.1" - dependencies: - "@types/react": "*" - hoist-non-react-statics: ^3.3.0 - checksum: 2c0778570d9a01d05afabc781b32163f28409bb98f7245c38d5eaf082416fdb73034003f5825eb5e21313044e8d2d9e1f3fe2831e345d3d1b1d20bcd12270719 - languageName: node - linkType: hard - "@types/istanbul-lib-coverage@npm:*, @types/istanbul-lib-coverage@npm:^2.0.0": version: 2.0.4 resolution: "@types/istanbul-lib-coverage@npm:2.0.4" @@ -2143,15 +2133,6 @@ __metadata: languageName: node linkType: hard -"hoist-non-react-statics@npm:^3.3.0, hoist-non-react-statics@npm:^3.3.2": - version: 3.3.2 - resolution: "hoist-non-react-statics@npm:3.3.2" - dependencies: - react-is: ^16.7.0 - checksum: b1538270429b13901ee586aa44f4cc3ecd8831c061d06cb8322e50ea17b3f5ce4d0e2e66394761e6c8e152cd8c34fb3b4b690116c6ce2bd45b18c746516cb9e8 - languageName: node - linkType: hard - "http-cache-semantics@npm:^4.1.0": version: 4.1.1 resolution: "http-cache-semantics@npm:4.1.1" @@ -2221,10 +2202,10 @@ __metadata: languageName: node linkType: hard -"immer@npm:^9.0.16": - version: 9.0.19 - resolution: "immer@npm:9.0.19" - checksum: f02ee53989989c287cd548a3d817fccf0bfe56db919755ee94a72ea3ae78a00363fba93ee6c010fe54a664380c29c53d44ed4091c6a86cae60957ad2cfabc010 +"immer@npm:^10.0.2": + version: 10.0.2 + resolution: "immer@npm:10.0.2" + checksum: 525a3b14210d02ae420c3b9f6ca14f7e9bcf625611d1356e773e7739f14c7c8de50dac442e6c7de3a6e24a782f7b792b6b8666bc0b3f00269d21a95f8f68ca84 languageName: node linkType: hard @@ -3433,13 +3414,6 @@ __metadata: languageName: node linkType: hard -"react-is@npm:^16.7.0": - version: 16.13.1 - resolution: "react-is@npm:16.13.1" - checksum: f7a19ac3496de32ca9ae12aa030f00f14a3d45374f1ceca0af707c831b2a6098ef0d6bdae51bd437b0a306d7f01d4677fcc8de7c0d331eb47ad0f46130e53c5f - languageName: node - linkType: hard - "react-is@npm:^17.0.1": version: 17.0.2 resolution: "react-is@npm:17.0.2" @@ -3454,23 +3428,20 @@ __metadata: languageName: node linkType: hard -"react-redux@npm:^8.0.5": - version: 8.0.5 - resolution: "react-redux@npm:8.0.5" +"react-redux@npm:^9.0.0-alpha.1": + version: 9.0.0-alpha.1 + resolution: "react-redux@npm:9.0.0-alpha.1" dependencies: - "@babel/runtime": ^7.12.1 - "@types/hoist-non-react-statics": ^3.3.1 "@types/use-sync-external-store": ^0.0.3 - hoist-non-react-statics: ^3.3.2 react-is: ^18.0.0 use-sync-external-store: ^1.0.0 peerDependencies: - "@types/react": ^16.8 || ^17.0 || ^18.0 - "@types/react-dom": ^16.8 || ^17.0 || ^18.0 - react: ^16.8 || ^17.0 || ^18.0 - react-dom: ^16.8 || ^17.0 || ^18.0 - react-native: ">=0.59" - redux: ^4 + "@types/react": ^18.0 + "@types/react-dom": ^18.0 + react: ^18.0 + react-dom: ^18.0 + react-native: ">=0.71" + redux: ^5.0.0-beta.0 peerDependenciesMeta: "@types/react": optional: true @@ -3482,7 +3453,7 @@ __metadata: optional: true redux: optional: true - checksum: a108f4f7ead6ac005e656d46051474a2bbdb31ede481bbbb3d8d779c1a35e1940b8655577cc5021313411864d305f67fc719aa48d6e5ed8288cf9cbe8b7042e4 + checksum: ee4edbf417d0f16297e8bc6d039129f915f4d9330ec160ab4135e859a8ec7f11f943cfc26a2d336ec2395c2549c7e8e6d7a4f341febd5404ed0221fb5cbfc17a languageName: node linkType: hard @@ -3532,21 +3503,19 @@ __metadata: languageName: node linkType: hard -"redux-thunk@npm:^2.4.2": - version: 2.4.2 - resolution: "redux-thunk@npm:2.4.2" +"redux-thunk@npm:^3.0.0-beta.0": + version: 3.0.0-beta.0 + resolution: "redux-thunk@npm:3.0.0-beta.0" peerDependencies: - redux: ^4 - checksum: c7f757f6c383b8ec26152c113e20087818d18ed3edf438aaad43539e9a6b77b427ade755c9595c4a163b6ad3063adf3497e5fe6a36c68884eb1f1cfb6f049a5c + redux: ^4 || ^5.0.0-beta.0 + checksum: 1609e18a9fb56ab7403d760999996b50e136fcf7411ec9d809e9a4afa4187bf0ab545652c05ffbfca2e0397e59e6baf2ae0d35631a30bf8ba20af1205e98e0fe languageName: node linkType: hard -"redux@npm:^4.2.0": - version: 4.2.1 - resolution: "redux@npm:4.2.1" - dependencies: - "@babel/runtime": ^7.9.2 - checksum: f63b9060c3a1d930ae775252bb6e579b42415aee7a23c4114e21a0b4ba7ec12f0ec76936c00f546893f06e139819f0e2855e0d55ebfce34ca9c026241a6950dd +"redux@npm:^5.0.0-beta.0": + version: 5.0.0-beta.0 + resolution: "redux@npm:5.0.0-beta.0" + checksum: 11df373e219f2f515ee1bda1a19a1ba5de02d8d5c874800ec353179dcd106eddd54432946fd0ab37c47f99f8fe53f820a6404c14da7f039a46022187e9469d2d languageName: node linkType: hard @@ -3601,10 +3570,10 @@ __metadata: languageName: node linkType: hard -"reselect@npm:^4.1.7": - version: 4.1.7 - resolution: "reselect@npm:4.1.7" - checksum: 738d8e2b8f0dca154ad29de6a209c9fbca2d70ae6788fd85df87f2c74b95a65bbf2d16d43a9e2faff39de34d17a29d706ba08a6b2ee5660c09589edbd19af7e1 +"reselect@npm:^5.0.0-alpha.2": + version: 5.0.0-alpha.2 + resolution: "reselect@npm:5.0.0-alpha.2" + checksum: c47b66999800e1297721cbc4b2464b520fade9823c598d578759c9fba3eb6be03b184e13c20f30820cc18fe2688fc9fb4475f83e59d8f2347aa0d591e465637d languageName: node linkType: hard @@ -3681,7 +3650,7 @@ __metadata: resolution: "rtk-esm-vite-normal@workspace:." dependencies: "@playwright/test": ^1.31.1 - "@reduxjs/toolkit": ^1.9.3 + "@reduxjs/toolkit": ^2.0.0-beta.2 "@testing-library/jest-dom": ^5.16.5 "@testing-library/react": ^13.4.0 "@testing-library/user-event": ^14.4.3 @@ -3695,7 +3664,7 @@ __metadata: prettier: ^2.8.4 react: ^18.2.0 react-dom: ^18.2.0 - react-redux: ^8.0.5 + react-redux: ^9.0.0-alpha.1 serve: ^14.2.0 typescript: ^4.9.4 vite: ^4.2.1 From 44d4805370e771be170fdb7cb7c66970999fe189 Mon Sep 17 00:00:00 2001 From: Mark Erikson Date: Mon, 25 Sep 2023 10:18:56 -0400 Subject: [PATCH 281/412] Fix docs TS issue --- docs/rtk-query/usage/persistence-and-rehydration.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/rtk-query/usage/persistence-and-rehydration.mdx b/docs/rtk-query/usage/persistence-and-rehydration.mdx index 644304d375..5720f198f2 100644 --- a/docs/rtk-query/usage/persistence-and-rehydration.mdx +++ b/docs/rtk-query/usage/persistence-and-rehydration.mdx @@ -52,7 +52,7 @@ export const api = createApi({ // highlight-start extractRehydrationInfo(action, { reducerPath }) { if (isHydrateAction(action)) { - if (action.key === 'key used with redux-persist') { + if ((action as any).key === 'key used with redux-persist') { // when persisting the api reducer return action.payload } From 0eaedf6cb5209cf7b8b0299b925a2971914f30f0 Mon Sep 17 00:00:00 2001 From: Olesj-Bilous Date: Wed, 27 Sep 2023 15:02:27 +0200 Subject: [PATCH 282/412] fix v2.0 integration merge conflicts --- .../toolkit/src/entities/create_adapter.ts | 1 - packages/toolkit/src/entities/models.ts | 52 +++++++++---------- .../src/entities/sorted_state_adapter.ts | 15 +++--- .../toolkit/src/entities/state_adapter.ts | 17 +++--- .../tests/entity_slice_enhancer.test.ts | 30 +++++------ .../src/entities/tests/entity_state.test.ts | 2 +- .../src/entities/unsorted_state_adapter.ts | 24 ++++----- packages/toolkit/src/entities/utils.ts | 4 +- 8 files changed, 68 insertions(+), 77 deletions(-) diff --git a/packages/toolkit/src/entities/create_adapter.ts b/packages/toolkit/src/entities/create_adapter.ts index 50f795ab5c..83ad6c6515 100644 --- a/packages/toolkit/src/entities/create_adapter.ts +++ b/packages/toolkit/src/entities/create_adapter.ts @@ -1,4 +1,3 @@ -import type { Draft } from 'immer' import type { EntityDefinition, Comparer, diff --git a/packages/toolkit/src/entities/models.ts b/packages/toolkit/src/entities/models.ts index cfa67613e2..068dfc227d 100644 --- a/packages/toolkit/src/entities/models.ts +++ b/packages/toolkit/src/entities/models.ts @@ -43,111 +43,109 @@ export interface EntityDefinition { export type PreventAny = CastAny< S, EntityState -> + > -export type DraftableEntityState = EntityState | Draft> - -export type DraftableIdSelector = IdSelector> +export type DraftableEntityState = EntityState | Draft> /** * @public */ export interface EntityStateAdapter { - addOne>( + addOne>( state: PreventAny, entity: T ): S - addOne>( + addOne>( state: PreventAny, action: PayloadAction ): S - addMany>( + addMany>( state: PreventAny, entities: readonly T[] | Record ): S - addMany>( + addMany>( state: PreventAny, entities: PayloadAction> ): S - setOne>( + setOne>( state: PreventAny, entity: T ): S - setOne>( + setOne>( state: PreventAny, action: PayloadAction ): S - setMany>( + setMany>( state: PreventAny, entities: readonly T[] | Record ): S - setMany>( + setMany>( state: PreventAny, entities: PayloadAction> ): S - setAll>( + setAll>( state: PreventAny, entities: readonly T[] | Record ): S - setAll>( + setAll>( state: PreventAny, entities: PayloadAction> ): S - removeOne>( + removeOne>( state: PreventAny, key: Id ): S - removeOne>( + removeOne>( state: PreventAny, key: PayloadAction ): S - removeMany>( + removeMany>( state: PreventAny, keys: readonly Id[] ): S - removeMany>( + removeMany>( state: PreventAny, keys: PayloadAction ): S - removeAll>(state: PreventAny): S + removeAll>(state: PreventAny): S - updateOne>( + updateOne>( state: PreventAny, update: Update ): S - updateOne>( + updateOne>( state: PreventAny, update: PayloadAction> ): S - updateMany>( + updateMany>( state: PreventAny, updates: ReadonlyArray> ): S - updateMany>( + updateMany>( state: PreventAny, updates: PayloadAction>> ): S - upsertOne>( + upsertOne>( state: PreventAny, entity: T ): S - upsertOne>( + upsertOne>( state: PreventAny, entity: PayloadAction ): S - upsertMany>( + upsertMany>( state: PreventAny, entities: readonly T[] | Record ): S - upsertMany>( + upsertMany>( state: PreventAny, entities: PayloadAction> ): S diff --git a/packages/toolkit/src/entities/sorted_state_adapter.ts b/packages/toolkit/src/entities/sorted_state_adapter.ts index 2f961f347d..91645d1af0 100644 --- a/packages/toolkit/src/entities/sorted_state_adapter.ts +++ b/packages/toolkit/src/entities/sorted_state_adapter.ts @@ -1,13 +1,10 @@ -import type {Draft} from 'immer' import type { - EntityState, IdSelector, Comparer, EntityStateAdapter, Update, EntityId, - DraftableEntityState, - DraftableIdSelector, + DraftableEntityState } from './models' import { createStateOperator } from './state_adapter' import { createUnsortedStateAdapter } from './unsorted_state_adapter' @@ -21,7 +18,7 @@ export function createSortedStateAdapter( selectId: IdSelector, sort: Comparer ): EntityStateAdapter { - type R = EntityState + type R = DraftableEntityState const { removeOne, removeMany, removeAll } = createUnsortedStateAdapter(selectId) @@ -81,7 +78,7 @@ export function createSortedStateAdapter( let appliedUpdates = false for (let update of updates) { - const entity: T | undefined = state.entities[update.id] + const entity: T | undefined = (state.entities as Record)[update.id] if (!entity) { continue } @@ -91,8 +88,8 @@ export function createSortedStateAdapter( Object.assign(entity, update.changes) const newId = selectId(entity) if (update.id !== newId) { - delete state.entities[update.id] - state.entities[newId] = entity + delete (state.entities as Record)[update.id]; + (state.entities as Record)[newId] = entity } } @@ -136,7 +133,7 @@ export function createSortedStateAdapter( function merge(models: readonly T[], state: R): void { // Insert/overwrite all new/updated models.forEach((model) => { - state.entities[selectId(model)] = model + (state.entities as Record)[selectId(model)] = model }) resortEntities(state) diff --git a/packages/toolkit/src/entities/state_adapter.ts b/packages/toolkit/src/entities/state_adapter.ts index 3caa72a566..93738d21c2 100644 --- a/packages/toolkit/src/entities/state_adapter.ts +++ b/packages/toolkit/src/entities/state_adapter.ts @@ -1,20 +1,19 @@ import { produce as createNextState, isDraft } from 'immer' import type { Draft } from 'immer' -import type { EntityId, DraftableEntityState, EntityState, PreventAny } from './models' +import type { EntityId, DraftableEntityState, PreventAny } from './models' import type { PayloadAction } from '../createAction' import { isFSA } from '../createAction' -import { IsAny } from '../tsHelpers' export const isDraftTyped = isDraft as (value: T | Draft) => value is Draft export function createSingleArgumentStateOperator( - mutator: (state: EntityState) => void + mutator: (state: DraftableEntityState) => void ) { const operator = createStateOperator( - (_: undefined, state: EntityState) => mutator(state) + (_: undefined, state: DraftableEntityState) => mutator(state) ) - return function operation>( + return function operation>( state: PreventAny ): S { return operator(state as S, undefined) @@ -22,9 +21,9 @@ export function createSingleArgumentStateOperator( } export function createStateOperator( - mutator: (arg: R, state: EntityState) => void + mutator: (arg: R, state: DraftableEntityState) => void ) { - return function operation>( + return function operation>( state: S, arg: R | PayloadAction ): S { @@ -34,7 +33,7 @@ export function createStateOperator( return isFSA(arg) } - const runMutator = (draft: EntityState) => { + const runMutator = (draft: DraftableEntityState) => { if (isPayloadActionArgument(arg)) { mutator(arg.payload, draft) } else { @@ -42,7 +41,7 @@ export function createStateOperator( } } - if (isDraftTyped>(state)) { + if (isDraftTyped>(state)) { // we must already be inside a `createNextState` call, likely because // this is being wrapped in `createReducer` or `createSlice`. // It's safe to just pass the draft to the mutator. diff --git a/packages/toolkit/src/entities/tests/entity_slice_enhancer.test.ts b/packages/toolkit/src/entities/tests/entity_slice_enhancer.test.ts index 927ffe4704..9e8e380815 100644 --- a/packages/toolkit/src/entities/tests/entity_slice_enhancer.test.ts +++ b/packages/toolkit/src/entities/tests/entity_slice_enhancer.test.ts @@ -1,44 +1,44 @@ import { createEntityAdapter, createSlice } from "../.."; -import type { PayloadAction, Slice, SliceCaseReducers } from "../.."; -import type { DraftableIdSelector, EntityAdapter, EntityState, IdSelector } from "../models"; +import type { PayloadAction, Slice, SliceCaseReducers, UnknownAction } from "../.."; +import type { EntityId, EntityState, IdSelector } from "../models"; import type { BookModel } from "./fixtures/book"; describe('Entity Slice Enhancer', () => { - let slice: Slice>; + let slice: Slice>; beforeEach(() => { const indieSlice = entitySliceEnhancer({ name: 'book', - selectDraftableId: (book: BookModel) => book.id + selectId: (book: BookModel) => book.id }) slice = indieSlice }) it('exposes oneAdded', () => { - const oneAdded = slice.reducer(undefined, slice.actions.oneAdded({ + const book = { id: '0', title: 'Der Steppenwolf', author: 'Herman Hesse' - })) - expect(oneAdded.entities['0']?.id).toBe('0') + } + const action = slice.actions.oneAdded(book) + const oneAdded = slice.reducer(undefined, action as UnknownAction) + expect(oneAdded.entities['0']).toBe(book) }) }) -interface EntitySliceArgs { +interface EntitySliceArgs { name: string - selectId?: IdSelector // unusable - selectDraftableId?: DraftableIdSelector + selectId: IdSelector modelReducer?: SliceCaseReducers } -function entitySliceEnhancer({ +function entitySliceEnhancer({ name, - selectId: unusableSelectId, - selectDraftableId, + selectId, modelReducer -}: EntitySliceArgs) { +}: EntitySliceArgs) { const modelAdapter = createEntityAdapter({ - selectId: selectDraftableId // unusableSelectId would give an interesting error + selectId }); return createSlice({ diff --git a/packages/toolkit/src/entities/tests/entity_state.test.ts b/packages/toolkit/src/entities/tests/entity_state.test.ts index f28048de80..65accf475c 100644 --- a/packages/toolkit/src/entities/tests/entity_state.test.ts +++ b/packages/toolkit/src/entities/tests/entity_state.test.ts @@ -6,7 +6,7 @@ import { createSlice } from '../../createSlice' import type { BookModel } from './fixtures/book' describe('Entity State', () => { - let adapter: EntityAdapter + let adapter: EntityAdapter beforeEach(() => { adapter = createEntityAdapter({ diff --git a/packages/toolkit/src/entities/unsorted_state_adapter.ts b/packages/toolkit/src/entities/unsorted_state_adapter.ts index b98119b2d6..bf55686b51 100644 --- a/packages/toolkit/src/entities/unsorted_state_adapter.ts +++ b/packages/toolkit/src/entities/unsorted_state_adapter.ts @@ -1,12 +1,10 @@ import type { Draft } from 'immer' import type { - EntityState, EntityStateAdapter, IdSelector, Update, EntityId, - DraftableEntityState, - DraftableIdSelector, + DraftableEntityState } from './models' import { createStateOperator, @@ -21,7 +19,7 @@ import { export function createUnsortedStateAdapter( selectId: IdSelector ): EntityStateAdapter { - type R = EntityState + type R = DraftableEntityState function addOneMutably(entity: T, state: R): void { const key = selectIdValue(entity, selectId) @@ -30,8 +28,8 @@ export function createUnsortedStateAdapter( return } - state.ids.push(key) - state.entities[key] = entity + state.ids.push(key as Id & Draft); + (state.entities as Record)[key] = entity } function addManyMutably( @@ -48,9 +46,9 @@ export function createUnsortedStateAdapter( function setOneMutably(entity: T, state: R): void { const key = selectIdValue(entity, selectId) if (!(key in state.entities)) { - state.ids.push(key) + state.ids.push(key as Id & Draft); } - state.entities[key] = entity + (state.entities as Record)[key] = entity } function setManyMutably( @@ -84,13 +82,13 @@ export function createUnsortedStateAdapter( keys.forEach((key) => { if (key in state.entities) { - delete state.entities[key] + delete (state.entities as Record)[key] didMutate = true } }) if (didMutate) { - state.ids = state.ids.filter((id) => id in state.entities) + state.ids = state.ids.filter((id) => id in state.entities) as Id[] | Draft } } @@ -106,7 +104,7 @@ export function createUnsortedStateAdapter( update: Update, state: R ): boolean { - const original: T | undefined = state.entities[update.id] + const original: T | undefined = (state.entities as Record)[update.id] if (original === undefined) { return false } @@ -116,10 +114,10 @@ export function createUnsortedStateAdapter( if (hasNewKey) { keys[update.id] = newKey - delete state.entities[update.id] + delete (state.entities as Record)[update.id] } - state.entities[newKey] = updated + (state.entities as Record)[newKey] = updated return hasNewKey } diff --git a/packages/toolkit/src/entities/utils.ts b/packages/toolkit/src/entities/utils.ts index 086b49147f..71435e3a01 100644 --- a/packages/toolkit/src/entities/utils.ts +++ b/packages/toolkit/src/entities/utils.ts @@ -1,4 +1,4 @@ -import type { EntityState, IdSelector, Update, EntityId, DraftableEntityState } from './models' +import type { IdSelector, Update, EntityId, DraftableEntityState } from './models' export function selectIdValue( entity: T, @@ -33,7 +33,7 @@ export function ensureEntitiesArray( export function splitAddedUpdatedEntities( newEntities: readonly T[] | Record, selectId: IdSelector, - state: EntityState + state: DraftableEntityState ): [T[], Update[]] { newEntities = ensureEntitiesArray(newEntities) From d667b84adce6bc4fbb3c63554145a10bcee8f88f Mon Sep 17 00:00:00 2001 From: Olesj-Bilous Date: Wed, 27 Sep 2023 19:54:21 +0200 Subject: [PATCH 283/412] Update unsorted_state_adapter.ts --- packages/toolkit/src/entities/unsorted_state_adapter.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/toolkit/src/entities/unsorted_state_adapter.ts b/packages/toolkit/src/entities/unsorted_state_adapter.ts index bf55686b51..1b74a01479 100644 --- a/packages/toolkit/src/entities/unsorted_state_adapter.ts +++ b/packages/toolkit/src/entities/unsorted_state_adapter.ts @@ -88,7 +88,7 @@ export function createUnsortedStateAdapter( }) if (didMutate) { - state.ids = state.ids.filter((id) => id in state.entities) as Id[] | Draft + state.ids = (state.ids as Id[]).filter((id) => id in state.entities) as Id[] | Draft } } From 8586a227372838c42890ac8cc37afcfa2bbf3361 Mon Sep 17 00:00:00 2001 From: Mark Erikson Date: Sat, 30 Sep 2023 22:25:40 -0400 Subject: [PATCH 284/412] Update MSW to 1.3.2, React-Redux to 2.0-beta.0, and log Playwright --- examples/publish-ci/cra4/package.json | 4 +- .../cra4/public/mockServiceWorker.js | 2 +- .../cra4/tests/playwright/rtkq.test.ts | 1 + examples/publish-ci/cra4/yarn.lock | 87 +++++++++---------- examples/publish-ci/cra5/package.json | 4 +- .../cra5/public/mockServiceWorker.js | 2 +- .../cra5/tests/playwright/rtkq.test.ts | 1 + examples/publish-ci/cra5/yarn.lock | 87 +++++++++---------- examples/publish-ci/next/package.json | 4 +- .../next/public/mockServiceWorker.js | 2 +- .../next/tests/playwright/rtkq.test.ts | 1 + examples/publish-ci/next/yarn.lock | 87 +++++++++---------- examples/publish-ci/node-esm/package.json | 2 +- examples/publish-ci/node-esm/yarn.lock | 10 +-- .../publish-ci/node-standard/package.json | 2 +- examples/publish-ci/node-standard/yarn.lock | 10 +-- examples/publish-ci/vite/package.json | 4 +- .../vite/public/mockServiceWorker.js | 2 +- .../vite/tests/playwright/rtkq.test.ts | 1 + examples/publish-ci/vite/yarn.lock | 87 +++++++++---------- 20 files changed, 196 insertions(+), 204 deletions(-) diff --git a/examples/publish-ci/cra4/package.json b/examples/publish-ci/cra4/package.json index dc81f4d5bb..325ca5067a 100644 --- a/examples/publish-ci/cra4/package.json +++ b/examples/publish-ci/cra4/package.json @@ -4,10 +4,10 @@ "private": true, "dependencies": { "@reduxjs/toolkit": "^2.0.0-beta.2", - "msw": "^0.49.2", + "msw": "^1.3.2", "react": "^18.2.0", "react-dom": "^18.2.0", - "react-redux": "^9.0.0-alpha.1", + "react-redux": "^9.0.0-beta.0", "react-scripts": "^4", "web-vitals": "^2.1.4" }, diff --git a/examples/publish-ci/cra4/public/mockServiceWorker.js b/examples/publish-ci/cra4/public/mockServiceWorker.js index 671ec2cbd0..51d85eeebf 100644 --- a/examples/publish-ci/cra4/public/mockServiceWorker.js +++ b/examples/publish-ci/cra4/public/mockServiceWorker.js @@ -2,7 +2,7 @@ /* tslint:disable */ /** - * Mock Service Worker (0.49.3). + * Mock Service Worker (1.3.2). * @see https://github.com/mswjs/msw * - Please do NOT modify this file. * - Please do NOT serve this file on production. diff --git a/examples/publish-ci/cra4/tests/playwright/rtkq.test.ts b/examples/publish-ci/cra4/tests/playwright/rtkq.test.ts index 8e7166a813..3b01695f95 100644 --- a/examples/publish-ci/cra4/tests/playwright/rtkq.test.ts +++ b/examples/publish-ci/cra4/tests/playwright/rtkq.test.ts @@ -1,6 +1,7 @@ import { test, expect } from '@playwright/test' test('RTK / RTKQ Interactions', async ({ page }) => { + page.on('console', (msg) => console.log('Console message: ', msg.text())) await page.goto('http://localhost:3000') const counterValue = page.getByTestId('counter-value') diff --git a/examples/publish-ci/cra4/yarn.lock b/examples/publish-ci/cra4/yarn.lock index c4707309f0..1e7c634ee6 100644 --- a/examples/publish-ci/cra4/yarn.lock +++ b/examples/publish-ci/cra4/yarn.lock @@ -1959,19 +1959,19 @@ __metadata: languageName: node linkType: hard -"@mswjs/interceptors@npm:^0.17.5": - version: 0.17.9 - resolution: "@mswjs/interceptors@npm:0.17.9" +"@mswjs/interceptors@npm:^0.17.10": + version: 0.17.10 + resolution: "@mswjs/interceptors@npm:0.17.10" dependencies: "@open-draft/until": ^1.0.3 "@types/debug": ^4.1.7 "@xmldom/xmldom": ^0.8.3 debug: ^4.3.3 - headers-polyfill: ^3.1.0 + headers-polyfill: 3.2.5 outvariant: ^1.2.1 strict-event-emitter: ^0.2.4 web-encoding: ^1.1.5 - checksum: 4df726cbee93d8baa54ead1ecb11e98124468659f51eb659ef8ead4aca7d6375198baf412ea17d4810fa5f1ee4fa53994702cb3b0b4f6f427a2f0fb890020192 + checksum: 0e6d32f399144b5cefe6fd7620f2776c83adc9bbbbccf2eb4ea347332be059f585136c44168c09b544c41cd3d686f88e43432e10192227a24fbb0c98a2f52dc8 languageName: node linkType: hard @@ -4662,16 +4662,6 @@ __metadata: languageName: node linkType: hard -"chalk@npm:4.1.1": - version: 4.1.1 - resolution: "chalk@npm:4.1.1" - dependencies: - ansi-styles: ^4.1.0 - supports-color: ^7.1.0 - checksum: 036e973e665ba1a32c975e291d5f3d549bceeb7b1b983320d4598fb75d70fe20c5db5d62971ec0fe76cdbce83985a00ee42372416abfc3a5584465005a7855ed - languageName: node - linkType: hard - "chalk@npm:5.0.1": version: 5.0.1 resolution: "chalk@npm:5.0.1" @@ -7877,10 +7867,10 @@ __metadata: languageName: node linkType: hard -"graphql@npm:^15.0.0 || ^16.0.0": - version: 16.6.0 - resolution: "graphql@npm:16.6.0" - checksum: bf1d9e3c1938ce3c1a81e909bd3ead1ae4707c577f91cff1ca2eca474bfbc7873d5d7b942e1e9777ff5a8304421dba57a4b76d7a29eb19de8711cb70e3c2415e +"graphql@npm:^16.8.1": + version: 16.8.1 + resolution: "graphql@npm:16.8.1" + checksum: 8d304b7b6f708c8c5cc164b06e92467dfe36aff6d4f2cf31dd19c4c2905a0e7b89edac4b7e225871131fd24e21460836b369de0c06532644d15b461d55b1ccc0 languageName: node linkType: hard @@ -8053,10 +8043,10 @@ __metadata: languageName: node linkType: hard -"headers-polyfill@npm:^3.1.0": - version: 3.1.2 - resolution: "headers-polyfill@npm:3.1.2" - checksum: 510ca9637ef652404dbd432e680418f8d418ba18094ef2f64c3d8de955ebf6e68d553c7f0aeaa5fc937d130b139c1e2d7c2066cd4cf0f740a4627924eaaee9db +"headers-polyfill@npm:3.2.5": + version: 3.2.5 + resolution: "headers-polyfill@npm:3.2.5" + checksum: a3c4bdd661584fd39e40c0f91412abc514616edfbd20d29a75567e591f90ef5c445c8e209b7f3c2b2375d27e95e4690f33417368a168d4832484a93861ab6a3c languageName: node linkType: hard @@ -8921,10 +8911,10 @@ __metadata: languageName: node linkType: hard -"is-node-process@npm:^1.0.1": - version: 1.0.1 - resolution: "is-node-process@npm:1.0.1" - checksum: 3ddb8a892a00f6eb9c2aea7e7e1426b8683512d9419933d95114f4f64b5455e26601c23a31c0682463890032136dd98a326988a770ab6b4eed54a43ade8bed50 +"is-node-process@npm:^1.2.0": + version: 1.2.0 + resolution: "is-node-process@npm:1.2.0" + checksum: 930765cdc6d81ab8f1bbecbea4a8d35c7c6d88a3ff61f3630e0fc7f22d624d7661c1df05c58547d0eb6a639dfa9304682c8e342c4113a6ed51472b704cee2928 languageName: node linkType: hard @@ -10879,37 +10869,37 @@ __metadata: languageName: node linkType: hard -"msw@npm:^0.49.2": - version: 0.49.3 - resolution: "msw@npm:0.49.3" +"msw@npm:^1.3.2": + version: 1.3.2 + resolution: "msw@npm:1.3.2" dependencies: "@mswjs/cookies": ^0.2.2 - "@mswjs/interceptors": ^0.17.5 + "@mswjs/interceptors": ^0.17.10 "@open-draft/until": ^1.0.3 "@types/cookie": ^0.4.1 "@types/js-levenshtein": ^1.1.1 - chalk: 4.1.1 + chalk: ^4.1.1 chokidar: ^3.4.2 cookie: ^0.4.2 - graphql: ^15.0.0 || ^16.0.0 - headers-polyfill: ^3.1.0 + graphql: ^16.8.1 + headers-polyfill: 3.2.5 inquirer: ^8.2.0 - is-node-process: ^1.0.1 + is-node-process: ^1.2.0 js-levenshtein: ^1.1.6 node-fetch: ^2.6.7 - outvariant: ^1.3.0 + outvariant: ^1.4.0 path-to-regexp: ^6.2.0 strict-event-emitter: ^0.4.3 type-fest: ^2.19.0 yargs: ^17.3.1 peerDependencies: - typescript: ">= 4.4.x <= 4.9.x" + typescript: ">= 4.4.x <= 5.2.x" peerDependenciesMeta: typescript: optional: true bin: msw: cli/index.js - checksum: 8322cd42cd69f289c05517d02bde22fc2f10e86fc2d0d209d9df54bd03d10b8123723c5587a2654dcd2cd0f314a016f9eccac88cffa30fafd1f9fead16db639e + checksum: c2d4f7747f5806f0fd8d8cc3ca250ee1c2a7a6cd608de43f95bd072ba1fb13cdce0b52932ce9bf8f4a21b194d2815db535501e224ec8f7052593447fe1c0cb70 languageName: node linkType: hard @@ -11522,13 +11512,20 @@ __metadata: languageName: node linkType: hard -"outvariant@npm:^1.2.1, outvariant@npm:^1.3.0": +"outvariant@npm:^1.2.1": version: 1.3.0 resolution: "outvariant@npm:1.3.0" checksum: ac76ca375c1c642989e1c74f0e9ebac84c05bc9fdc8f28be949c16fae1658e9f1f2fb1133fe3cc1e98afabef78fe4298fe9360b5734baf8e6ad440c182680848 languageName: node linkType: hard +"outvariant@npm:^1.4.0": + version: 1.4.0 + resolution: "outvariant@npm:1.4.0" + checksum: ec32dfc315c464bb6e4906b2f450d259ce0b86caf70b70b249054359d9af21a7fccf53a8b6aa232f8d718449e31c1cfa594e6ebffaafe7bf908b502495256d7b + languageName: node + linkType: hard + "p-each-series@npm:^2.1.0": version: 2.2.0 resolution: "p-each-series@npm:2.2.0" @@ -13241,9 +13238,9 @@ __metadata: languageName: node linkType: hard -"react-redux@npm:^9.0.0-alpha.1": - version: 9.0.0-alpha.1 - resolution: "react-redux@npm:9.0.0-alpha.1" +"react-redux@npm:^9.0.0-beta.0": + version: 9.0.0-beta.0 + resolution: "react-redux@npm:9.0.0-beta.0" dependencies: "@types/use-sync-external-store": ^0.0.3 react-is: ^18.0.0 @@ -13266,7 +13263,7 @@ __metadata: optional: true redux: optional: true - checksum: ee4edbf417d0f16297e8bc6d039129f915f4d9330ec160ab4135e859a8ec7f11f943cfc26a2d336ec2395c2549c7e8e6d7a4f341febd5404ed0221fb5cbfc17a + checksum: 4fc40fc4b2c03905f5d523b854516f4323d307c0ca33f2a14701f33315b53768d9e19f59511ebcad5448ccbd7c69a362edd0b68f0932669d4f9b2f517b7257b4 languageName: node linkType: hard @@ -13962,12 +13959,12 @@ __metadata: "@types/node": ^17.0.45 "@types/react": ^18.0.26 "@types/react-dom": ^18.0.10 - msw: ^0.49.2 + msw: ^1.3.2 playwright: ^1.31.1 prettier: ^2.8.4 react: ^18.2.0 react-dom: ^18.2.0 - react-redux: ^9.0.0-alpha.1 + react-redux: ^9.0.0-beta.0 react-scripts: ^4 serve: ^14.2.0 typescript: ^4.9.4 diff --git a/examples/publish-ci/cra5/package.json b/examples/publish-ci/cra5/package.json index 730c1f8675..43e815862f 100644 --- a/examples/publish-ci/cra5/package.json +++ b/examples/publish-ci/cra5/package.json @@ -4,10 +4,10 @@ "private": true, "dependencies": { "@reduxjs/toolkit": "^2.0.0-beta.2", - "msw": "^0.49.2", + "msw": "^1.3.2", "react": "^18.2.0", "react-dom": "^18.2.0", - "react-redux": "^9.0.0-alpha.1", + "react-redux": "^9.0.0-beta.0", "react-scripts": "5", "web-vitals": "^2.1.4" }, diff --git a/examples/publish-ci/cra5/public/mockServiceWorker.js b/examples/publish-ci/cra5/public/mockServiceWorker.js index 671ec2cbd0..51d85eeebf 100644 --- a/examples/publish-ci/cra5/public/mockServiceWorker.js +++ b/examples/publish-ci/cra5/public/mockServiceWorker.js @@ -2,7 +2,7 @@ /* tslint:disable */ /** - * Mock Service Worker (0.49.3). + * Mock Service Worker (1.3.2). * @see https://github.com/mswjs/msw * - Please do NOT modify this file. * - Please do NOT serve this file on production. diff --git a/examples/publish-ci/cra5/tests/playwright/rtkq.test.ts b/examples/publish-ci/cra5/tests/playwright/rtkq.test.ts index 8e7166a813..3b01695f95 100644 --- a/examples/publish-ci/cra5/tests/playwright/rtkq.test.ts +++ b/examples/publish-ci/cra5/tests/playwright/rtkq.test.ts @@ -1,6 +1,7 @@ import { test, expect } from '@playwright/test' test('RTK / RTKQ Interactions', async ({ page }) => { + page.on('console', (msg) => console.log('Console message: ', msg.text())) await page.goto('http://localhost:3000') const counterValue = page.getByTestId('counter-value') diff --git a/examples/publish-ci/cra5/yarn.lock b/examples/publish-ci/cra5/yarn.lock index 0589cfa61d..393857826a 100644 --- a/examples/publish-ci/cra5/yarn.lock +++ b/examples/publish-ci/cra5/yarn.lock @@ -2119,19 +2119,19 @@ __metadata: languageName: node linkType: hard -"@mswjs/interceptors@npm:^0.17.5": - version: 0.17.7 - resolution: "@mswjs/interceptors@npm:0.17.7" +"@mswjs/interceptors@npm:^0.17.10": + version: 0.17.10 + resolution: "@mswjs/interceptors@npm:0.17.10" dependencies: "@open-draft/until": ^1.0.3 "@types/debug": ^4.1.7 "@xmldom/xmldom": ^0.8.3 debug: ^4.3.3 - headers-polyfill: ^3.1.0 + headers-polyfill: 3.2.5 outvariant: ^1.2.1 strict-event-emitter: ^0.2.4 web-encoding: ^1.1.5 - checksum: 3cfd537390a3d37b8fe772ccdc1245045aa8b962cb34aa477092e490280e18e544b2e2b6d188f59b3caaeb70192d383eaa0e4f8588bde1ddabd7f9360772fedb + checksum: 0e6d32f399144b5cefe6fd7620f2776c83adc9bbbbccf2eb4ea347332be059f585136c44168c09b544c41cd3d686f88e43432e10192227a24fbb0c98a2f52dc8 languageName: node linkType: hard @@ -4401,16 +4401,6 @@ __metadata: languageName: node linkType: hard -"chalk@npm:4.1.1": - version: 4.1.1 - resolution: "chalk@npm:4.1.1" - dependencies: - ansi-styles: ^4.1.0 - supports-color: ^7.1.0 - checksum: 036e973e665ba1a32c975e291d5f3d549bceeb7b1b983320d4598fb75d70fe20c5db5d62971ec0fe76cdbce83985a00ee42372416abfc3a5584465005a7855ed - languageName: node - linkType: hard - "chalk@npm:5.0.1": version: 5.0.1 resolution: "chalk@npm:5.0.1" @@ -6956,10 +6946,10 @@ __metadata: languageName: node linkType: hard -"graphql@npm:^15.0.0 || ^16.0.0": - version: 16.6.0 - resolution: "graphql@npm:16.6.0" - checksum: bf1d9e3c1938ce3c1a81e909bd3ead1ae4707c577f91cff1ca2eca474bfbc7873d5d7b942e1e9777ff5a8304421dba57a4b76d7a29eb19de8711cb70e3c2415e +"graphql@npm:^16.8.1": + version: 16.8.1 + resolution: "graphql@npm:16.8.1" + checksum: 8d304b7b6f708c8c5cc164b06e92467dfe36aff6d4f2cf31dd19c4c2905a0e7b89edac4b7e225871131fd24e21460836b369de0c06532644d15b461d55b1ccc0 languageName: node linkType: hard @@ -7064,10 +7054,10 @@ __metadata: languageName: node linkType: hard -"headers-polyfill@npm:^3.1.0": - version: 3.1.2 - resolution: "headers-polyfill@npm:3.1.2" - checksum: 510ca9637ef652404dbd432e680418f8d418ba18094ef2f64c3d8de955ebf6e68d553c7f0aeaa5fc937d130b139c1e2d7c2066cd4cf0f740a4627924eaaee9db +"headers-polyfill@npm:3.2.5": + version: 3.2.5 + resolution: "headers-polyfill@npm:3.2.5" + checksum: a3c4bdd661584fd39e40c0f91412abc514616edfbd20d29a75567e591f90ef5c445c8e209b7f3c2b2375d27e95e4690f33417368a168d4832484a93861ab6a3c languageName: node linkType: hard @@ -7644,10 +7634,10 @@ __metadata: languageName: node linkType: hard -"is-node-process@npm:^1.0.1": - version: 1.0.1 - resolution: "is-node-process@npm:1.0.1" - checksum: 3ddb8a892a00f6eb9c2aea7e7e1426b8683512d9419933d95114f4f64b5455e26601c23a31c0682463890032136dd98a326988a770ab6b4eed54a43ade8bed50 +"is-node-process@npm:^1.2.0": + version: 1.2.0 + resolution: "is-node-process@npm:1.2.0" + checksum: 930765cdc6d81ab8f1bbecbea4a8d35c7c6d88a3ff61f3630e0fc7f22d624d7661c1df05c58547d0eb6a639dfa9304682c8e342c4113a6ed51472b704cee2928 languageName: node linkType: hard @@ -9319,37 +9309,37 @@ __metadata: languageName: node linkType: hard -"msw@npm:^0.49.2": - version: 0.49.3 - resolution: "msw@npm:0.49.3" +"msw@npm:^1.3.2": + version: 1.3.2 + resolution: "msw@npm:1.3.2" dependencies: "@mswjs/cookies": ^0.2.2 - "@mswjs/interceptors": ^0.17.5 + "@mswjs/interceptors": ^0.17.10 "@open-draft/until": ^1.0.3 "@types/cookie": ^0.4.1 "@types/js-levenshtein": ^1.1.1 - chalk: 4.1.1 + chalk: ^4.1.1 chokidar: ^3.4.2 cookie: ^0.4.2 - graphql: ^15.0.0 || ^16.0.0 - headers-polyfill: ^3.1.0 + graphql: ^16.8.1 + headers-polyfill: 3.2.5 inquirer: ^8.2.0 - is-node-process: ^1.0.1 + is-node-process: ^1.2.0 js-levenshtein: ^1.1.6 node-fetch: ^2.6.7 - outvariant: ^1.3.0 + outvariant: ^1.4.0 path-to-regexp: ^6.2.0 strict-event-emitter: ^0.4.3 type-fest: ^2.19.0 yargs: ^17.3.1 peerDependencies: - typescript: ">= 4.4.x <= 4.9.x" + typescript: ">= 4.4.x <= 5.2.x" peerDependenciesMeta: typescript: optional: true bin: msw: cli/index.js - checksum: 8322cd42cd69f289c05517d02bde22fc2f10e86fc2d0d209d9df54bd03d10b8123723c5587a2654dcd2cd0f314a016f9eccac88cffa30fafd1f9fead16db639e + checksum: c2d4f7747f5806f0fd8d8cc3ca250ee1c2a7a6cd608de43f95bd072ba1fb13cdce0b52932ce9bf8f4a21b194d2815db535501e224ec8f7052593447fe1c0cb70 languageName: node linkType: hard @@ -9761,13 +9751,20 @@ __metadata: languageName: node linkType: hard -"outvariant@npm:^1.2.1, outvariant@npm:^1.3.0": +"outvariant@npm:^1.2.1": version: 1.3.0 resolution: "outvariant@npm:1.3.0" checksum: ac76ca375c1c642989e1c74f0e9ebac84c05bc9fdc8f28be949c16fae1658e9f1f2fb1133fe3cc1e98afabef78fe4298fe9360b5734baf8e6ad440c182680848 languageName: node linkType: hard +"outvariant@npm:^1.4.0": + version: 1.4.0 + resolution: "outvariant@npm:1.4.0" + checksum: ec32dfc315c464bb6e4906b2f450d259ce0b86caf70b70b249054359d9af21a7fccf53a8b6aa232f8d718449e31c1cfa594e6ebffaafe7bf908b502495256d7b + languageName: node + linkType: hard + "p-limit@npm:^2.0.0, p-limit@npm:^2.2.0": version: 2.3.0 resolution: "p-limit@npm:2.3.0" @@ -11225,9 +11222,9 @@ __metadata: languageName: node linkType: hard -"react-redux@npm:^9.0.0-alpha.1": - version: 9.0.0-alpha.1 - resolution: "react-redux@npm:9.0.0-alpha.1" +"react-redux@npm:^9.0.0-beta.0": + version: 9.0.0-beta.0 + resolution: "react-redux@npm:9.0.0-beta.0" dependencies: "@types/use-sync-external-store": ^0.0.3 react-is: ^18.0.0 @@ -11250,7 +11247,7 @@ __metadata: optional: true redux: optional: true - checksum: ee4edbf417d0f16297e8bc6d039129f915f4d9330ec160ab4135e859a8ec7f11f943cfc26a2d336ec2395c2549c7e8e6d7a4f341febd5404ed0221fb5cbfc17a + checksum: 4fc40fc4b2c03905f5d523b854516f4323d307c0ca33f2a14701f33315b53768d9e19f59511ebcad5448ccbd7c69a362edd0b68f0932669d4f9b2f517b7257b4 languageName: node linkType: hard @@ -11751,12 +11748,12 @@ __metadata: "@types/node": ^17.0.45 "@types/react": ^18.0.26 "@types/react-dom": ^18.0.10 - msw: ^0.49.2 + msw: ^1.3.2 playwright: ^1.31.1 prettier: ^2.8.4 react: ^18.2.0 react-dom: ^18.2.0 - react-redux: ^9.0.0-alpha.1 + react-redux: ^9.0.0-beta.0 react-scripts: 5 serve: ^14.2.0 typescript: ^4.9.4 diff --git a/examples/publish-ci/next/package.json b/examples/publish-ci/next/package.json index 79cd79e65e..fcd0fadfc8 100644 --- a/examples/publish-ci/next/package.json +++ b/examples/publish-ci/next/package.json @@ -11,11 +11,11 @@ }, "dependencies": { "@reduxjs/toolkit": "^2.0.0-beta.2", - "msw": "^0.49.2", + "msw": "^1.3.2", "next": "^13.2", "react": "^18.2.0", "react-dom": "^18.2.0", - "react-redux": "^9.0.0-alpha.1" + "react-redux": "^9.0.0-beta.0" }, "devDependencies": { "@playwright/test": "^1.31.1", diff --git a/examples/publish-ci/next/public/mockServiceWorker.js b/examples/publish-ci/next/public/mockServiceWorker.js index 671ec2cbd0..51d85eeebf 100644 --- a/examples/publish-ci/next/public/mockServiceWorker.js +++ b/examples/publish-ci/next/public/mockServiceWorker.js @@ -2,7 +2,7 @@ /* tslint:disable */ /** - * Mock Service Worker (0.49.3). + * Mock Service Worker (1.3.2). * @see https://github.com/mswjs/msw * - Please do NOT modify this file. * - Please do NOT serve this file on production. diff --git a/examples/publish-ci/next/tests/playwright/rtkq.test.ts b/examples/publish-ci/next/tests/playwright/rtkq.test.ts index 8e7166a813..3b01695f95 100644 --- a/examples/publish-ci/next/tests/playwright/rtkq.test.ts +++ b/examples/publish-ci/next/tests/playwright/rtkq.test.ts @@ -1,6 +1,7 @@ import { test, expect } from '@playwright/test' test('RTK / RTKQ Interactions', async ({ page }) => { + page.on('console', (msg) => console.log('Console message: ', msg.text())) await page.goto('http://localhost:3000') const counterValue = page.getByTestId('counter-value') diff --git a/examples/publish-ci/next/yarn.lock b/examples/publish-ci/next/yarn.lock index 52112010c8..b961839f55 100644 --- a/examples/publish-ci/next/yarn.lock +++ b/examples/publish-ci/next/yarn.lock @@ -97,19 +97,19 @@ __metadata: languageName: node linkType: hard -"@mswjs/interceptors@npm:^0.17.5": - version: 0.17.9 - resolution: "@mswjs/interceptors@npm:0.17.9" +"@mswjs/interceptors@npm:^0.17.10": + version: 0.17.10 + resolution: "@mswjs/interceptors@npm:0.17.10" dependencies: "@open-draft/until": ^1.0.3 "@types/debug": ^4.1.7 "@xmldom/xmldom": ^0.8.3 debug: ^4.3.3 - headers-polyfill: ^3.1.0 + headers-polyfill: 3.2.5 outvariant: ^1.2.1 strict-event-emitter: ^0.2.4 web-encoding: ^1.1.5 - checksum: 4df726cbee93d8baa54ead1ecb11e98124468659f51eb659ef8ead4aca7d6375198baf412ea17d4810fa5f1ee4fa53994702cb3b0b4f6f427a2f0fb890020192 + checksum: 0e6d32f399144b5cefe6fd7620f2776c83adc9bbbbccf2eb4ea347332be059f585136c44168c09b544c41cd3d686f88e43432e10192227a24fbb0c98a2f52dc8 languageName: node linkType: hard @@ -884,16 +884,6 @@ __metadata: languageName: node linkType: hard -"chalk@npm:4.1.1": - version: 4.1.1 - resolution: "chalk@npm:4.1.1" - dependencies: - ansi-styles: ^4.1.0 - supports-color: ^7.1.0 - checksum: 036e973e665ba1a32c975e291d5f3d549bceeb7b1b983320d4598fb75d70fe20c5db5d62971ec0fe76cdbce83985a00ee42372416abfc3a5584465005a7855ed - languageName: node - linkType: hard - "chalk@npm:5.0.1": version: 5.0.1 resolution: "chalk@npm:5.0.1" @@ -1592,10 +1582,10 @@ __metadata: languageName: node linkType: hard -"graphql@npm:^15.0.0 || ^16.0.0": - version: 16.6.0 - resolution: "graphql@npm:16.6.0" - checksum: bf1d9e3c1938ce3c1a81e909bd3ead1ae4707c577f91cff1ca2eca474bfbc7873d5d7b942e1e9777ff5a8304421dba57a4b76d7a29eb19de8711cb70e3c2415e +"graphql@npm:^16.8.1": + version: 16.8.1 + resolution: "graphql@npm:16.8.1" + checksum: 8d304b7b6f708c8c5cc164b06e92467dfe36aff6d4f2cf31dd19c4c2905a0e7b89edac4b7e225871131fd24e21460836b369de0c06532644d15b461d55b1ccc0 languageName: node linkType: hard @@ -1661,10 +1651,10 @@ __metadata: languageName: node linkType: hard -"headers-polyfill@npm:^3.1.0": - version: 3.1.2 - resolution: "headers-polyfill@npm:3.1.2" - checksum: 510ca9637ef652404dbd432e680418f8d418ba18094ef2f64c3d8de955ebf6e68d553c7f0aeaa5fc937d130b139c1e2d7c2066cd4cf0f740a4627924eaaee9db +"headers-polyfill@npm:3.2.5": + version: 3.2.5 + resolution: "headers-polyfill@npm:3.2.5" + checksum: a3c4bdd661584fd39e40c0f91412abc514616edfbd20d29a75567e591f90ef5c445c8e209b7f3c2b2375d27e95e4690f33417368a168d4832484a93861ab6a3c languageName: node linkType: hard @@ -1957,10 +1947,10 @@ __metadata: languageName: node linkType: hard -"is-node-process@npm:^1.0.1": - version: 1.0.1 - resolution: "is-node-process@npm:1.0.1" - checksum: 3ddb8a892a00f6eb9c2aea7e7e1426b8683512d9419933d95114f4f64b5455e26601c23a31c0682463890032136dd98a326988a770ab6b4eed54a43ade8bed50 +"is-node-process@npm:^1.2.0": + version: 1.2.0 + resolution: "is-node-process@npm:1.2.0" + checksum: 930765cdc6d81ab8f1bbecbea4a8d35c7c6d88a3ff61f3630e0fc7f22d624d7661c1df05c58547d0eb6a639dfa9304682c8e342c4113a6ed51472b704cee2928 languageName: node linkType: hard @@ -2484,37 +2474,37 @@ __metadata: languageName: node linkType: hard -"msw@npm:^0.49.2": - version: 0.49.3 - resolution: "msw@npm:0.49.3" +"msw@npm:^1.3.2": + version: 1.3.2 + resolution: "msw@npm:1.3.2" dependencies: "@mswjs/cookies": ^0.2.2 - "@mswjs/interceptors": ^0.17.5 + "@mswjs/interceptors": ^0.17.10 "@open-draft/until": ^1.0.3 "@types/cookie": ^0.4.1 "@types/js-levenshtein": ^1.1.1 - chalk: 4.1.1 + chalk: ^4.1.1 chokidar: ^3.4.2 cookie: ^0.4.2 - graphql: ^15.0.0 || ^16.0.0 - headers-polyfill: ^3.1.0 + graphql: ^16.8.1 + headers-polyfill: 3.2.5 inquirer: ^8.2.0 - is-node-process: ^1.0.1 + is-node-process: ^1.2.0 js-levenshtein: ^1.1.6 node-fetch: ^2.6.7 - outvariant: ^1.3.0 + outvariant: ^1.4.0 path-to-regexp: ^6.2.0 strict-event-emitter: ^0.4.3 type-fest: ^2.19.0 yargs: ^17.3.1 peerDependencies: - typescript: ">= 4.4.x <= 4.9.x" + typescript: ">= 4.4.x <= 5.2.x" peerDependenciesMeta: typescript: optional: true bin: msw: cli/index.js - checksum: 8322cd42cd69f289c05517d02bde22fc2f10e86fc2d0d209d9df54bd03d10b8123723c5587a2654dcd2cd0f314a016f9eccac88cffa30fafd1f9fead16db639e + checksum: c2d4f7747f5806f0fd8d8cc3ca250ee1c2a7a6cd608de43f95bd072ba1fb13cdce0b52932ce9bf8f4a21b194d2815db535501e224ec8f7052593447fe1c0cb70 languageName: node linkType: hard @@ -2770,13 +2760,20 @@ __metadata: languageName: node linkType: hard -"outvariant@npm:^1.2.1, outvariant@npm:^1.3.0": +"outvariant@npm:^1.2.1": version: 1.3.0 resolution: "outvariant@npm:1.3.0" checksum: ac76ca375c1c642989e1c74f0e9ebac84c05bc9fdc8f28be949c16fae1658e9f1f2fb1133fe3cc1e98afabef78fe4298fe9360b5734baf8e6ad440c182680848 languageName: node linkType: hard +"outvariant@npm:^1.4.0": + version: 1.4.0 + resolution: "outvariant@npm:1.4.0" + checksum: ec32dfc315c464bb6e4906b2f450d259ce0b86caf70b70b249054359d9af21a7fccf53a8b6aa232f8d718449e31c1cfa594e6ebffaafe7bf908b502495256d7b + languageName: node + linkType: hard + "p-map@npm:^4.0.0": version: 4.0.0 resolution: "p-map@npm:4.0.0" @@ -2975,9 +2972,9 @@ __metadata: languageName: node linkType: hard -"react-redux@npm:^9.0.0-alpha.1": - version: 9.0.0-alpha.1 - resolution: "react-redux@npm:9.0.0-alpha.1" +"react-redux@npm:^9.0.0-beta.0": + version: 9.0.0-beta.0 + resolution: "react-redux@npm:9.0.0-beta.0" dependencies: "@types/use-sync-external-store": ^0.0.3 react-is: ^18.0.0 @@ -3000,7 +2997,7 @@ __metadata: optional: true redux: optional: true - checksum: ee4edbf417d0f16297e8bc6d039129f915f4d9330ec160ab4135e859a8ec7f11f943cfc26a2d336ec2395c2549c7e8e6d7a4f341febd5404ed0221fb5cbfc17a + checksum: 4fc40fc4b2c03905f5d523b854516f4323d307c0ca33f2a14701f33315b53768d9e19f59511ebcad5448ccbd7c69a362edd0b68f0932669d4f9b2f517b7257b4 languageName: node linkType: hard @@ -3158,13 +3155,13 @@ __metadata: "@types/node": ^17.0.45 "@types/react": ^18.0.26 "@types/react-dom": ^18.0.10 - msw: ^0.49.2 + msw: ^1.3.2 next: ^13.2 playwright: ^1.31.1 prettier: ^2.8.4 react: ^18.2.0 react-dom: ^18.2.0 - react-redux: ^9.0.0-alpha.1 + react-redux: ^9.0.0-beta.0 serve: ^14.2.0 typescript: ^4.9.4 languageName: unknown diff --git a/examples/publish-ci/node-esm/package.json b/examples/publish-ci/node-esm/package.json index f97ffdba0c..1ea8e3b503 100644 --- a/examples/publish-ci/node-esm/package.json +++ b/examples/publish-ci/node-esm/package.json @@ -11,7 +11,7 @@ "@reduxjs/toolkit": "^2.0.0-beta.2", "react": "^18.2.0", "react-dom": "^18.2.0", - "react-redux": "^9.0.0-alpha.1" + "react-redux": "^9.0.0-beta.0" }, "devDependencies": { "resolve-esm": "^1.4.0" diff --git a/examples/publish-ci/node-esm/yarn.lock b/examples/publish-ci/node-esm/yarn.lock index a703adc19c..70e73a7bac 100644 --- a/examples/publish-ci/node-esm/yarn.lock +++ b/examples/publish-ci/node-esm/yarn.lock @@ -46,7 +46,7 @@ __metadata: "@reduxjs/toolkit": ^2.0.0-beta.2 react: ^18.2.0 react-dom: ^18.2.0 - react-redux: ^9.0.0-alpha.1 + react-redux: ^9.0.0-beta.0 resolve-esm: ^1.4.0 languageName: unknown linkType: soft @@ -95,9 +95,9 @@ __metadata: languageName: node linkType: hard -"react-redux@npm:^9.0.0-alpha.1": - version: 9.0.0-alpha.1 - resolution: "react-redux@npm:9.0.0-alpha.1" +"react-redux@npm:^9.0.0-beta.0": + version: 9.0.0-beta.0 + resolution: "react-redux@npm:9.0.0-beta.0" dependencies: "@types/use-sync-external-store": ^0.0.3 react-is: ^18.0.0 @@ -120,7 +120,7 @@ __metadata: optional: true redux: optional: true - checksum: ee4edbf417d0f16297e8bc6d039129f915f4d9330ec160ab4135e859a8ec7f11f943cfc26a2d336ec2395c2549c7e8e6d7a4f341febd5404ed0221fb5cbfc17a + checksum: 4fc40fc4b2c03905f5d523b854516f4323d307c0ca33f2a14701f33315b53768d9e19f59511ebcad5448ccbd7c69a362edd0b68f0932669d4f9b2f517b7257b4 languageName: node linkType: hard diff --git a/examples/publish-ci/node-standard/package.json b/examples/publish-ci/node-standard/package.json index b76456587a..780f72bcd0 100644 --- a/examples/publish-ci/node-standard/package.json +++ b/examples/publish-ci/node-standard/package.json @@ -10,7 +10,7 @@ "@reduxjs/toolkit": "^2.0.0-beta.2", "react": "^18.2.0", "react-dom": "^18.2.0", - "react-redux": "^9.0.0-alpha.1" + "react-redux": "^9.0.0-beta.0" }, "devDependencies": { "resolve-esm": "^1.4.0" diff --git a/examples/publish-ci/node-standard/yarn.lock b/examples/publish-ci/node-standard/yarn.lock index a703adc19c..70e73a7bac 100644 --- a/examples/publish-ci/node-standard/yarn.lock +++ b/examples/publish-ci/node-standard/yarn.lock @@ -46,7 +46,7 @@ __metadata: "@reduxjs/toolkit": ^2.0.0-beta.2 react: ^18.2.0 react-dom: ^18.2.0 - react-redux: ^9.0.0-alpha.1 + react-redux: ^9.0.0-beta.0 resolve-esm: ^1.4.0 languageName: unknown linkType: soft @@ -95,9 +95,9 @@ __metadata: languageName: node linkType: hard -"react-redux@npm:^9.0.0-alpha.1": - version: 9.0.0-alpha.1 - resolution: "react-redux@npm:9.0.0-alpha.1" +"react-redux@npm:^9.0.0-beta.0": + version: 9.0.0-beta.0 + resolution: "react-redux@npm:9.0.0-beta.0" dependencies: "@types/use-sync-external-store": ^0.0.3 react-is: ^18.0.0 @@ -120,7 +120,7 @@ __metadata: optional: true redux: optional: true - checksum: ee4edbf417d0f16297e8bc6d039129f915f4d9330ec160ab4135e859a8ec7f11f943cfc26a2d336ec2395c2549c7e8e6d7a4f341febd5404ed0221fb5cbfc17a + checksum: 4fc40fc4b2c03905f5d523b854516f4323d307c0ca33f2a14701f33315b53768d9e19f59511ebcad5448ccbd7c69a362edd0b68f0932669d4f9b2f517b7257b4 languageName: node linkType: hard diff --git a/examples/publish-ci/vite/package.json b/examples/publish-ci/vite/package.json index 236012cf32..bdff3e7116 100644 --- a/examples/publish-ci/vite/package.json +++ b/examples/publish-ci/vite/package.json @@ -12,10 +12,10 @@ }, "dependencies": { "@reduxjs/toolkit": "^2.0.0-beta.2", - "msw": "^0.49.2", + "msw": "^1.3.2", "react": "^18.2.0", "react-dom": "^18.2.0", - "react-redux": "^9.0.0-alpha.1" + "react-redux": "^9.0.0-beta.0" }, "devDependencies": { "@playwright/test": "^1.31.1", diff --git a/examples/publish-ci/vite/public/mockServiceWorker.js b/examples/publish-ci/vite/public/mockServiceWorker.js index 671ec2cbd0..51d85eeebf 100644 --- a/examples/publish-ci/vite/public/mockServiceWorker.js +++ b/examples/publish-ci/vite/public/mockServiceWorker.js @@ -2,7 +2,7 @@ /* tslint:disable */ /** - * Mock Service Worker (0.49.3). + * Mock Service Worker (1.3.2). * @see https://github.com/mswjs/msw * - Please do NOT modify this file. * - Please do NOT serve this file on production. diff --git a/examples/publish-ci/vite/tests/playwright/rtkq.test.ts b/examples/publish-ci/vite/tests/playwright/rtkq.test.ts index 8e7166a813..3b01695f95 100644 --- a/examples/publish-ci/vite/tests/playwright/rtkq.test.ts +++ b/examples/publish-ci/vite/tests/playwright/rtkq.test.ts @@ -1,6 +1,7 @@ import { test, expect } from '@playwright/test' test('RTK / RTKQ Interactions', async ({ page }) => { + page.on('console', (msg) => console.log('Console message: ', msg.text())) await page.goto('http://localhost:3000') const counterValue = page.getByTestId('counter-value') diff --git a/examples/publish-ci/vite/yarn.lock b/examples/publish-ci/vite/yarn.lock index 27e58491f5..7bea5d415f 100644 --- a/examples/publish-ci/vite/yarn.lock +++ b/examples/publish-ci/vite/yarn.lock @@ -542,19 +542,19 @@ __metadata: languageName: node linkType: hard -"@mswjs/interceptors@npm:^0.17.5": - version: 0.17.9 - resolution: "@mswjs/interceptors@npm:0.17.9" +"@mswjs/interceptors@npm:^0.17.10": + version: 0.17.10 + resolution: "@mswjs/interceptors@npm:0.17.10" dependencies: "@open-draft/until": ^1.0.3 "@types/debug": ^4.1.7 "@xmldom/xmldom": ^0.8.3 debug: ^4.3.3 - headers-polyfill: ^3.1.0 + headers-polyfill: 3.2.5 outvariant: ^1.2.1 strict-event-emitter: ^0.2.4 web-encoding: ^1.1.5 - checksum: 4df726cbee93d8baa54ead1ecb11e98124468659f51eb659ef8ead4aca7d6375198baf412ea17d4810fa5f1ee4fa53994702cb3b0b4f6f427a2f0fb890020192 + checksum: 0e6d32f399144b5cefe6fd7620f2776c83adc9bbbbccf2eb4ea347332be059f585136c44168c09b544c41cd3d686f88e43432e10192227a24fbb0c98a2f52dc8 languageName: node linkType: hard @@ -1251,16 +1251,6 @@ __metadata: languageName: node linkType: hard -"chalk@npm:4.1.1": - version: 4.1.1 - resolution: "chalk@npm:4.1.1" - dependencies: - ansi-styles: ^4.1.0 - supports-color: ^7.1.0 - checksum: 036e973e665ba1a32c975e291d5f3d549bceeb7b1b983320d4598fb75d70fe20c5db5d62971ec0fe76cdbce83985a00ee42372416abfc3a5584465005a7855ed - languageName: node - linkType: hard - "chalk@npm:5.0.1": version: 5.0.1 resolution: "chalk@npm:5.0.1" @@ -2057,10 +2047,10 @@ __metadata: languageName: node linkType: hard -"graphql@npm:^15.0.0 || ^16.0.0": - version: 16.6.0 - resolution: "graphql@npm:16.6.0" - checksum: bf1d9e3c1938ce3c1a81e909bd3ead1ae4707c577f91cff1ca2eca474bfbc7873d5d7b942e1e9777ff5a8304421dba57a4b76d7a29eb19de8711cb70e3c2415e +"graphql@npm:^16.8.1": + version: 16.8.1 + resolution: "graphql@npm:16.8.1" + checksum: 8d304b7b6f708c8c5cc164b06e92467dfe36aff6d4f2cf31dd19c4c2905a0e7b89edac4b7e225871131fd24e21460836b369de0c06532644d15b461d55b1ccc0 languageName: node linkType: hard @@ -2126,10 +2116,10 @@ __metadata: languageName: node linkType: hard -"headers-polyfill@npm:^3.1.0": - version: 3.1.2 - resolution: "headers-polyfill@npm:3.1.2" - checksum: 510ca9637ef652404dbd432e680418f8d418ba18094ef2f64c3d8de955ebf6e68d553c7f0aeaa5fc937d130b139c1e2d7c2066cd4cf0f740a4627924eaaee9db +"headers-polyfill@npm:3.2.5": + version: 3.2.5 + resolution: "headers-polyfill@npm:3.2.5" + checksum: a3c4bdd661584fd39e40c0f91412abc514616edfbd20d29a75567e591f90ef5c445c8e209b7f3c2b2375d27e95e4690f33417368a168d4832484a93861ab6a3c languageName: node linkType: hard @@ -2431,10 +2421,10 @@ __metadata: languageName: node linkType: hard -"is-node-process@npm:^1.0.1": - version: 1.0.1 - resolution: "is-node-process@npm:1.0.1" - checksum: 3ddb8a892a00f6eb9c2aea7e7e1426b8683512d9419933d95114f4f64b5455e26601c23a31c0682463890032136dd98a326988a770ab6b4eed54a43ade8bed50 +"is-node-process@npm:^1.2.0": + version: 1.2.0 + resolution: "is-node-process@npm:1.2.0" + checksum: 930765cdc6d81ab8f1bbecbea4a8d35c7c6d88a3ff61f3630e0fc7f22d624d7661c1df05c58547d0eb6a639dfa9304682c8e342c4113a6ed51472b704cee2928 languageName: node linkType: hard @@ -2994,37 +2984,37 @@ __metadata: languageName: node linkType: hard -"msw@npm:^0.49.2": - version: 0.49.3 - resolution: "msw@npm:0.49.3" +"msw@npm:^1.3.2": + version: 1.3.2 + resolution: "msw@npm:1.3.2" dependencies: "@mswjs/cookies": ^0.2.2 - "@mswjs/interceptors": ^0.17.5 + "@mswjs/interceptors": ^0.17.10 "@open-draft/until": ^1.0.3 "@types/cookie": ^0.4.1 "@types/js-levenshtein": ^1.1.1 - chalk: 4.1.1 + chalk: ^4.1.1 chokidar: ^3.4.2 cookie: ^0.4.2 - graphql: ^15.0.0 || ^16.0.0 - headers-polyfill: ^3.1.0 + graphql: ^16.8.1 + headers-polyfill: 3.2.5 inquirer: ^8.2.0 - is-node-process: ^1.0.1 + is-node-process: ^1.2.0 js-levenshtein: ^1.1.6 node-fetch: ^2.6.7 - outvariant: ^1.3.0 + outvariant: ^1.4.0 path-to-regexp: ^6.2.0 strict-event-emitter: ^0.4.3 type-fest: ^2.19.0 yargs: ^17.3.1 peerDependencies: - typescript: ">= 4.4.x <= 4.9.x" + typescript: ">= 4.4.x <= 5.2.x" peerDependenciesMeta: typescript: optional: true bin: msw: cli/index.js - checksum: 8322cd42cd69f289c05517d02bde22fc2f10e86fc2d0d209d9df54bd03d10b8123723c5587a2654dcd2cd0f314a016f9eccac88cffa30fafd1f9fead16db639e + checksum: c2d4f7747f5806f0fd8d8cc3ca250ee1c2a7a6cd608de43f95bd072ba1fb13cdce0b52932ce9bf8f4a21b194d2815db535501e224ec8f7052593447fe1c0cb70 languageName: node linkType: hard @@ -3216,13 +3206,20 @@ __metadata: languageName: node linkType: hard -"outvariant@npm:^1.2.1, outvariant@npm:^1.3.0": +"outvariant@npm:^1.2.1": version: 1.3.0 resolution: "outvariant@npm:1.3.0" checksum: ac76ca375c1c642989e1c74f0e9ebac84c05bc9fdc8f28be949c16fae1658e9f1f2fb1133fe3cc1e98afabef78fe4298fe9360b5734baf8e6ad440c182680848 languageName: node linkType: hard +"outvariant@npm:^1.4.0": + version: 1.4.0 + resolution: "outvariant@npm:1.4.0" + checksum: ec32dfc315c464bb6e4906b2f450d259ce0b86caf70b70b249054359d9af21a7fccf53a8b6aa232f8d718449e31c1cfa594e6ebffaafe7bf908b502495256d7b + languageName: node + linkType: hard + "p-map@npm:^4.0.0": version: 4.0.0 resolution: "p-map@npm:4.0.0" @@ -3428,9 +3425,9 @@ __metadata: languageName: node linkType: hard -"react-redux@npm:^9.0.0-alpha.1": - version: 9.0.0-alpha.1 - resolution: "react-redux@npm:9.0.0-alpha.1" +"react-redux@npm:^9.0.0-beta.0": + version: 9.0.0-beta.0 + resolution: "react-redux@npm:9.0.0-beta.0" dependencies: "@types/use-sync-external-store": ^0.0.3 react-is: ^18.0.0 @@ -3453,7 +3450,7 @@ __metadata: optional: true redux: optional: true - checksum: ee4edbf417d0f16297e8bc6d039129f915f4d9330ec160ab4135e859a8ec7f11f943cfc26a2d336ec2395c2549c7e8e6d7a4f341febd5404ed0221fb5cbfc17a + checksum: 4fc40fc4b2c03905f5d523b854516f4323d307c0ca33f2a14701f33315b53768d9e19f59511ebcad5448ccbd7c69a362edd0b68f0932669d4f9b2f517b7257b4 languageName: node linkType: hard @@ -3659,12 +3656,12 @@ __metadata: "@types/react": ^18.0.26 "@types/react-dom": ^18.0.10 "@vitejs/plugin-react": ^3.0.0 - msw: ^0.49.2 + msw: ^1.3.2 playwright: ^1.31.1 prettier: ^2.8.4 react: ^18.2.0 react-dom: ^18.2.0 - react-redux: ^9.0.0-alpha.1 + react-redux: ^9.0.0-beta.0 serve: ^14.2.0 typescript: ^4.9.4 vite: ^4.2.1 From 7d26e2738fb7f17c58e13b53f8213f4b4c1c5fc0 Mon Sep 17 00:00:00 2001 From: Mark Erikson Date: Sat, 30 Sep 2023 22:41:06 -0400 Subject: [PATCH 285/412] Bump TS versions --- .github/workflows/tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 2922ff98df..5e733b908d 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -105,7 +105,7 @@ jobs: fail-fast: false matrix: node: ['16.x'] - ts: ['4.7', '4.8', '4.9', '5.0'] + ts: ['4.7', '4.8', '4.9', '5.0', '5.1', '5.2'] steps: - name: Checkout repo uses: actions/checkout@v2 From 77e01799d4206b0c1e7fdc2f364bac2bc0dc735f Mon Sep 17 00:00:00 2001 From: Ben Durrant Date: Sun, 1 Oct 2023 13:47:37 +0100 Subject: [PATCH 286/412] Remove middleware array option --- packages/toolkit/src/configureStore.ts | 16 ++- .../toolkit/src/tests/configureStore.test.ts | 23 ++--- .../src/tests/configureStore.typetest.ts | 17 ++-- .../src/tests/getDefaultMiddleware.test.ts | 63 ++++++------ ...rializableStateInvariantMiddleware.test.ts | 98 ++++++++++--------- 5 files changed, 116 insertions(+), 101 deletions(-) diff --git a/packages/toolkit/src/configureStore.ts b/packages/toolkit/src/configureStore.ts index 9741f42a19..c155279ac2 100644 --- a/packages/toolkit/src/configureStore.ts +++ b/packages/toolkit/src/configureStore.ts @@ -53,7 +53,7 @@ export interface ConfigureStoreOptions< * @example `middleware: (gDM) => gDM().concat(logger, apiMiddleware, yourCustomMiddleware)` * @see https://redux-toolkit.js.org/api/getDefaultMiddleware#intended-usage */ - middleware?: ((getDefaultMiddleware: GetDefaultMiddleware) => M) | M + middleware?: (getDefaultMiddleware: GetDefaultMiddleware) => M /** * Whether to enable Redux DevTools integration. Defaults to `true`. @@ -121,7 +121,7 @@ export function configureStore< const { reducer = undefined, - middleware = getDefaultMiddleware(), + middleware, devTools = true, preloadedState = undefined, enhancers = undefined, @@ -139,15 +139,21 @@ export function configureStore< ) } - let finalMiddleware = middleware - if (typeof finalMiddleware === 'function') { - finalMiddleware = finalMiddleware(getDefaultMiddleware) + if (!IS_PRODUCTION && middleware && typeof middleware !== 'function') { + throw new Error('"middleware" field must be a callback') + } + + let finalMiddleware: Tuple> + if (typeof middleware === 'function') { + finalMiddleware = middleware(getDefaultMiddleware) if (!IS_PRODUCTION && !Array.isArray(finalMiddleware)) { throw new Error( 'when using a middleware builder function, an array of middleware must be returned' ) } + } else { + finalMiddleware = getDefaultMiddleware() } if ( !IS_PRODUCTION && diff --git a/packages/toolkit/src/tests/configureStore.test.ts b/packages/toolkit/src/tests/configureStore.test.ts index 0b12d20548..1a7cf100a1 100644 --- a/packages/toolkit/src/tests/configureStore.test.ts +++ b/packages/toolkit/src/tests/configureStore.test.ts @@ -110,7 +110,7 @@ describe('configureStore', async () => { describe('given no middleware', () => { it('calls createStore without any middleware', () => { expect( - configureStore({ middleware: new Tuple(), reducer }) + configureStore({ middleware: () => new Tuple(), reducer }) ).toBeInstanceOf(Object) expect(redux.applyMiddleware).toHaveBeenCalledWith() expect(mockDevtoolsCompose).toHaveBeenCalled() // @remap-prod-remove-line-line @@ -122,6 +122,15 @@ describe('configureStore', async () => { }) }) + describe('given an array of middleware', () => { + it('throws an error requiring a callback', () => { + // @ts-expect-error + expect(() => configureStore({ middleware: [], reducer })).toThrow( + '"middleware" field must be a callback' + ) + }) + }) + describe('given undefined middleware', () => { it('calls createStore with default middleware', () => { expect(configureStore({ middleware: undefined, reducer })).toBeInstanceOf( @@ -162,20 +171,12 @@ describe('configureStore', async () => { }) }) - describe('given custom middleware that contains non-functions', () => { - it('throws an error', () => { - expect(() => - configureStore({ middleware: [true] as any, reducer }) - ).toThrow('each middleware provided to configureStore must be a function') - }) - }) - describe('given custom middleware', () => { it('calls createStore with custom middleware and without default middleware', () => { const thank: Redux.Middleware = (_store) => (next) => (action) => next(action) expect( - configureStore({ middleware: new Tuple(thank), reducer }) + configureStore({ middleware: () => new Tuple(thank), reducer }) ).toBeInstanceOf(Object) expect(redux.applyMiddleware).toHaveBeenCalledWith(thank) expect(mockDevtoolsCompose).toHaveBeenCalled() // @remap-prod-remove-line-line @@ -330,7 +331,7 @@ describe('configureStore', async () => { it("doesn't warn when middleware enhancer is excluded if no middlewares provided", () => { const store = configureStore({ reducer, - middleware: new Tuple(), + middleware: () => new Tuple(), enhancers: () => new Tuple(dummyEnhancer), }) diff --git a/packages/toolkit/src/tests/configureStore.typetest.ts b/packages/toolkit/src/tests/configureStore.typetest.ts index 298de65010..d141f85df2 100644 --- a/packages/toolkit/src/tests/configureStore.typetest.ts +++ b/packages/toolkit/src/tests/configureStore.typetest.ts @@ -74,19 +74,19 @@ const _anyMiddleware: any = () => () => () => {} configureStore({ reducer: () => 0, - middleware: new Tuple(middleware), + middleware: () => new Tuple(middleware), }) configureStore({ reducer: () => 0, // @ts-expect-error - middleware: [middleware], + middleware: () => [middleware], }) configureStore({ reducer: () => 0, // @ts-expect-error - middleware: new Tuple('not middleware'), + middleware: () => new Tuple('not middleware'), }) } @@ -520,7 +520,7 @@ const _anyMiddleware: any = () => () => () => {} { const store = configureStore({ reducer: reducerA, - middleware: new Tuple(), + middleware: () => new Tuple(), }) // @ts-expect-error store.dispatch(thunkA()) @@ -533,7 +533,7 @@ const _anyMiddleware: any = () => () => () => {} { const store = configureStore({ reducer: reducerA, - middleware: new Tuple(thunk as ThunkMiddleware), + middleware: () => new Tuple(thunk as ThunkMiddleware), }) store.dispatch(thunkA()) // @ts-expect-error @@ -545,9 +545,8 @@ const _anyMiddleware: any = () => () => () => {} { const store = configureStore({ reducer: reducerA, - middleware: new Tuple( - 0 as unknown as Middleware<(a: StateA) => boolean, StateA> - ), + middleware: () => + new Tuple(0 as unknown as Middleware<(a: StateA) => boolean, StateA>), }) const result: boolean = store.dispatch(5) // @ts-expect-error @@ -566,7 +565,7 @@ const _anyMiddleware: any = () => () => () => {} > const store = configureStore({ reducer: reducerA, - middleware, + middleware: () => middleware, }) const result: 'A' = store.dispatch('a') diff --git a/packages/toolkit/src/tests/getDefaultMiddleware.test.ts b/packages/toolkit/src/tests/getDefaultMiddleware.test.ts index 36c55fcfb6..6ee86933b2 100644 --- a/packages/toolkit/src/tests/getDefaultMiddleware.test.ts +++ b/packages/toolkit/src/tests/getDefaultMiddleware.test.ts @@ -147,17 +147,18 @@ describe('getDefaultMiddleware', () => { it('allows passing options to immutableCheck', () => { let immutableCheckWasCalled = false - const middleware = getDefaultMiddleware({ - thunk: false, - immutableCheck: { - isImmutable: () => { - immutableCheckWasCalled = true - return true + const middleware = () => + getDefaultMiddleware({ + thunk: false, + immutableCheck: { + isImmutable: () => { + immutableCheckWasCalled = true + return true + }, }, - }, - serializableCheck: false, - actionCreatorCheck: false, - }) + serializableCheck: false, + actionCreatorCheck: false, + }) const reducer = () => ({}) @@ -172,17 +173,18 @@ describe('getDefaultMiddleware', () => { it('allows passing options to serializableCheck', () => { let serializableCheckWasCalled = false - const middleware = getDefaultMiddleware({ - thunk: false, - immutableCheck: false, - serializableCheck: { - isSerializable: () => { - serializableCheckWasCalled = true - return true + const middleware = () => + getDefaultMiddleware({ + thunk: false, + immutableCheck: false, + serializableCheck: { + isSerializable: () => { + serializableCheckWasCalled = true + return true + }, }, - }, - actionCreatorCheck: false, - }) + actionCreatorCheck: false, + }) const reducer = () => ({}) @@ -200,17 +202,18 @@ describe('getDefaultMiddleware', () => { it('allows passing options to actionCreatorCheck', () => { let actionCreatorCheckWasCalled = false - const middleware = getDefaultMiddleware({ - thunk: false, - immutableCheck: false, - serializableCheck: false, - actionCreatorCheck: { - isActionCreator: (action: unknown): action is Function => { - actionCreatorCheckWasCalled = true - return false + const middleware = () => + getDefaultMiddleware({ + thunk: false, + immutableCheck: false, + serializableCheck: false, + actionCreatorCheck: { + isActionCreator: (action: unknown): action is Function => { + actionCreatorCheckWasCalled = true + return false + }, }, - }, - }) + }) const reducer = () => ({}) diff --git a/packages/toolkit/src/tests/serializableStateInvariantMiddleware.test.ts b/packages/toolkit/src/tests/serializableStateInvariantMiddleware.test.ts index 2d28f66330..60e4e3b5a3 100644 --- a/packages/toolkit/src/tests/serializableStateInvariantMiddleware.test.ts +++ b/packages/toolkit/src/tests/serializableStateInvariantMiddleware.test.ts @@ -101,7 +101,7 @@ describe('serializableStateInvariantMiddleware', () => { const store = configureStore({ reducer, - middleware: new Tuple(serializableStateInvariantMiddleware), + middleware: () => new Tuple(serializableStateInvariantMiddleware), }) const symbol = Symbol.for('SOME_CONSTANT') @@ -148,7 +148,7 @@ describe('serializableStateInvariantMiddleware', () => { reducer: { testSlice: reducer, }, - middleware: new Tuple(serializableStateInvariantMiddleware), + middleware: () => new Tuple(serializableStateInvariantMiddleware), }) store.dispatch({ type: ACTION_TYPE }) @@ -208,7 +208,7 @@ describe('serializableStateInvariantMiddleware', () => { reducer: { testSlice: reducer, }, - middleware: new Tuple(serializableStateInvariantMiddleware), + middleware: () => new Tuple(serializableStateInvariantMiddleware), }) store.dispatch({ type: ACTION_TYPE }) @@ -255,7 +255,7 @@ describe('serializableStateInvariantMiddleware', () => { reducer: { testSlice: reducer, }, - middleware: new Tuple(serializableStateInvariantMiddleware), + middleware: () => new Tuple(serializableStateInvariantMiddleware), }) store.dispatch({ type: ACTION_TYPE }) @@ -299,7 +299,7 @@ describe('serializableStateInvariantMiddleware', () => { reducer: { testSlice: reducer, }, - middleware: new Tuple(serializableStateInvariantMiddleware), + middleware: () => new Tuple(serializableStateInvariantMiddleware), }) store.dispatch({ type: ACTION_TYPE }) @@ -323,7 +323,7 @@ describe('serializableStateInvariantMiddleware', () => { const store = configureStore({ reducer: () => ({}), - middleware: new Tuple(serializableStateMiddleware), + middleware: () => new Tuple(serializableStateMiddleware), }) expect(numTimesCalled).toBe(0) @@ -348,7 +348,8 @@ describe('serializableStateInvariantMiddleware', () => { it('default value: meta.arg', () => { configureStore({ reducer, - middleware: new Tuple(createSerializableStateInvariantMiddleware()), + middleware: () => + new Tuple(createSerializableStateInvariantMiddleware()), }).dispatch({ type: 'test', meta: { arg: nonSerializableValue } }) expect(getLog().log).toMatchInlineSnapshot(`""`) @@ -357,11 +358,12 @@ describe('serializableStateInvariantMiddleware', () => { it('default value can be overridden', () => { configureStore({ reducer, - middleware: new Tuple( - createSerializableStateInvariantMiddleware({ - ignoredActionPaths: [], - }) - ), + middleware: () => + new Tuple( + createSerializableStateInvariantMiddleware({ + ignoredActionPaths: [], + }) + ), }).dispatch({ type: 'test', meta: { arg: nonSerializableValue } }) expect(getLog().log).toMatchInlineSnapshot(` @@ -380,11 +382,12 @@ describe('serializableStateInvariantMiddleware', () => { it('can specify (multiple) different values', () => { configureStore({ reducer, - middleware: new Tuple( - createSerializableStateInvariantMiddleware({ - ignoredActionPaths: ['payload', 'meta.arg'], - }) - ), + middleware: () => + new Tuple( + createSerializableStateInvariantMiddleware({ + ignoredActionPaths: ['payload', 'meta.arg'], + }) + ), }).dispatch({ type: 'test', payload: { arg: nonSerializableValue }, @@ -397,11 +400,12 @@ describe('serializableStateInvariantMiddleware', () => { it('can specify regexp', () => { configureStore({ reducer, - middleware: new Tuple( - createSerializableStateInvariantMiddleware({ - ignoredActionPaths: [/^payload\..*$/], - }) - ), + middleware: () => + new Tuple( + createSerializableStateInvariantMiddleware({ + ignoredActionPaths: [/^payload\..*$/], + }) + ), }).dispatch({ type: 'test', payload: { arg: nonSerializableValue }, @@ -425,7 +429,7 @@ describe('serializableStateInvariantMiddleware', () => { const store = configureStore({ reducer: () => ({}), - middleware: new Tuple(serializableStateMiddleware), + middleware: () => new Tuple(serializableStateMiddleware), }) expect(numTimesCalled).toBe(0) @@ -488,7 +492,7 @@ describe('serializableStateInvariantMiddleware', () => { reducer: { testSlice: reducer, }, - middleware: new Tuple(serializableStateInvariantMiddleware), + middleware: () => new Tuple(serializableStateInvariantMiddleware), }) store.dispatch({ type: ACTION_TYPE }) @@ -507,15 +511,16 @@ describe('serializableStateInvariantMiddleware', () => { const reducer = () => badValue const store = configureStore({ reducer, - middleware: new Tuple( - createSerializableStateInvariantMiddleware({ - isSerializable: () => { - numTimesCalled++ - return true - }, - ignoreState: true, - }) - ), + middleware: () => + new Tuple( + createSerializableStateInvariantMiddleware({ + isSerializable: () => { + numTimesCalled++ + return true + }, + ignoreState: true, + }) + ), }) expect(numTimesCalled).toBe(0) @@ -534,16 +539,17 @@ describe('serializableStateInvariantMiddleware', () => { const reducer = () => badValue const store = configureStore({ reducer, - middleware: new Tuple( - createSerializableStateInvariantMiddleware({ - isSerializable: () => { - numTimesCalled++ - return true - }, - ignoreState: true, - ignoreActions: true, - }) - ), + middleware: () => + new Tuple( + createSerializableStateInvariantMiddleware({ + isSerializable: () => { + numTimesCalled++ + return true + }, + ignoreState: true, + ignoreActions: true, + }) + ), }) expect(numTimesCalled).toBe(0) @@ -566,7 +572,7 @@ describe('serializableStateInvariantMiddleware', () => { reducer: { testSlice: reducer, }, - middleware: new Tuple(serializableStateInvariantMiddleware), + middleware: () => new Tuple(serializableStateInvariantMiddleware), }) store.dispatch({ @@ -592,7 +598,7 @@ describe('serializableStateInvariantMiddleware', () => { reducer: { testSlice: reducer, }, - middleware: new Tuple(serializableStateInvariantMiddleware), + middleware: () => new Tuple(serializableStateInvariantMiddleware), }) store.dispatch({ type: 'SOME_ACTION' }) @@ -616,7 +622,7 @@ describe('serializableStateInvariantMiddleware', () => { if (action.type === 'SET_STATE') return action.payload return state }, - middleware: new Tuple(serializableStateInvariantMiddleware), + middleware: () => new Tuple(serializableStateInvariantMiddleware), }) const state = createNextState([], () => From a72c77f8b37fd6f495d948bbc1c18d57a549af87 Mon Sep 17 00:00:00 2001 From: Ben Durrant Date: Sun, 1 Oct 2023 17:23:14 +0100 Subject: [PATCH 287/412] Update docs --- docs/api/actionCreatorMiddleware.mdx | 2 +- docs/api/configureStore.mdx | 28 ++++++++++---------------- docs/api/getDefaultMiddleware.mdx | 2 +- docs/api/immutabilityMiddleware.mdx | 2 +- docs/api/serializabilityMiddleware.mdx | 2 +- docs/usage/usage-with-typescript.md | 2 +- 6 files changed, 16 insertions(+), 22 deletions(-) diff --git a/docs/api/actionCreatorMiddleware.mdx b/docs/api/actionCreatorMiddleware.mdx index 0afce4f368..50570ae109 100644 --- a/docs/api/actionCreatorMiddleware.mdx +++ b/docs/api/actionCreatorMiddleware.mdx @@ -63,6 +63,6 @@ const actionCreatorMiddleware = createActionCreatorInvariantMiddleware({ const store = configureStore({ reducer, - middleware: new Tuple(actionCreatorMiddleware), + middleware: () => new Tuple(actionCreatorMiddleware), }) ``` diff --git a/docs/api/configureStore.mdx b/docs/api/configureStore.mdx index 87f8b982e4..08aa0f089c 100644 --- a/docs/api/configureStore.mdx +++ b/docs/api/configureStore.mdx @@ -100,18 +100,15 @@ If it is an object of slice reducers, like `{users : usersReducer, posts : posts ### `middleware` -An optional array of Redux middleware functions, or a callback to customise the array of middleware. +A callback which will receive `getDefaultMiddleware` as its argument, +and should return a middleware array. -If this option is provided, it should contain all the middleware functions you +If this option is provided, it should return all the middleware functions you want added to the store. `configureStore` will automatically pass those to `applyMiddleware`. If not provided, `configureStore` will call `getDefaultMiddleware` and use the array of middleware functions it returns. -Where you wish to add onto or customize the default middleware, -you may pass a callback function that will receive `getDefaultMiddleware` as its argument, -and should return a middleware array. - For more details on how the `middleware` parameter works and the list of middleware that are added by default, see the [`getDefaultMiddleware` docs page](./getDefaultMiddleware.mdx). @@ -123,7 +120,7 @@ import { configureStore, Tuple } from '@reduxjs/toolkit' configureStore({ reducer: rootReducer, - middleware: new Tuple(additionalMiddleware, logger), + middleware: () => new Tuple(additionalMiddleware, logger), }) ``` @@ -178,11 +175,6 @@ If you provide an array, this `applyMiddleware` enhancer will _not_ be used. `configureStore` will warn in console if any middleware are provided (or left as default) but not included in the final list of enhancers. ```ts no-transpile -// warns - middleware left as default but not included in final enhancers -configureStore({ - reducer, - enhancers: [offline(offlineConfig)], -}) // warns - middleware customised but not included in final enhancers configureStore({ reducer, @@ -199,8 +191,8 @@ configureStore({ // also allowed configureStore({ reducer, - middleware: [], - enhancers: [offline(offlineConfig)], + middleware: () => [], + enhancers: () => [offline(offlineConfig)], }) ``` @@ -209,20 +201,22 @@ configureStore({ :::note Tuple Typescript users are required to use a `Tuple` instance (if not using a `getDefaultEnhancer` result, which is already a `Tuple`), for better inference. +``` import { configureStore, Tuple } from '@reduxjs/toolkit' configureStore({ reducer: rootReducer, -enhancers: new Tuple(offline), +enhancers: () => new Tuple(offline), }) -```` +``` Javascript-only users are free to use a plain array if preferred. ::: ## Usage + ### Basic Example ```ts @@ -238,7 +232,7 @@ import rootReducer from './reducers' const store = configureStore({ reducer: rootReducer }) // The store now has redux-thunk added and the Redux DevTools Extension is turned on -```` +``` ### Full Example diff --git a/docs/api/getDefaultMiddleware.mdx b/docs/api/getDefaultMiddleware.mdx index 877a26293c..ef15eca14f 100644 --- a/docs/api/getDefaultMiddleware.mdx +++ b/docs/api/getDefaultMiddleware.mdx @@ -28,7 +28,7 @@ If you want to customize the list of middleware, you can supply an array of midd ```js const store = configureStore({ reducer: rootReducer, - middleware: new Tuple(thunk, logger), + middleware: () => new Tuple(thunk, logger), }) // Store specifically has the thunk and logger middleware applied diff --git a/docs/api/immutabilityMiddleware.mdx b/docs/api/immutabilityMiddleware.mdx index 16950b129e..6360e5417b 100644 --- a/docs/api/immutabilityMiddleware.mdx +++ b/docs/api/immutabilityMiddleware.mdx @@ -86,7 +86,7 @@ const immutableInvariantMiddleware = createImmutableStateInvariantMiddleware({ const store = configureStore({ reducer: exampleSliceReducer, // Note that this will replace all default middleware - middleware: new Tuple(immutableInvariantMiddleware), + middleware: () => new Tuple(immutableInvariantMiddleware), }) ``` diff --git a/docs/api/serializabilityMiddleware.mdx b/docs/api/serializabilityMiddleware.mdx index e8cb8e8363..29893bad31 100644 --- a/docs/api/serializabilityMiddleware.mdx +++ b/docs/api/serializabilityMiddleware.mdx @@ -111,7 +111,7 @@ const serializableMiddleware = createSerializableStateInvariantMiddleware({ const store = configureStore({ reducer, - middleware: new Tuple(serializableMiddleware), + middleware: () => new Tuple(serializableMiddleware), }) ``` diff --git a/docs/usage/usage-with-typescript.md b/docs/usage/usage-with-typescript.md index beb99116d2..73734f605d 100644 --- a/docs/usage/usage-with-typescript.md +++ b/docs/usage/usage-with-typescript.md @@ -145,7 +145,7 @@ import { configureStore, Tuple } from '@reduxjs/toolkit' configureStore({ reducer: rootReducer, - middleware: new Tuple(additionalMiddleware, logger), + middleware: () => new Tuple(additionalMiddleware, logger), }) ``` From 6539503c09880ce039c5a5112efd6d361aeb449d Mon Sep 17 00:00:00 2001 From: Mark Erikson Date: Sun, 1 Oct 2023 14:59:04 -0400 Subject: [PATCH 288/412] Add phryneas/ts-version --- packages/toolkit/package.json | 1 + yarn.lock | 8 ++++++++ 2 files changed, 9 insertions(+) diff --git a/packages/toolkit/package.json b/packages/toolkit/package.json index 21cd8f046e..7b83807e26 100644 --- a/packages/toolkit/package.json +++ b/packages/toolkit/package.json @@ -50,6 +50,7 @@ }, "devDependencies": { "@microsoft/api-extractor": "^7.13.2", + "@phryneas/ts-version": "^1.0.2", "@size-limit/preset-small-lib": "^4.11.0", "@testing-library/react": "^13.3.0", "@testing-library/user-event": "^13.1.5", diff --git a/yarn.lock b/yarn.lock index cd80d41265..e74fb26bf6 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6808,6 +6808,13 @@ __metadata: languageName: node linkType: hard +"@phryneas/ts-version@npm:^1.0.2": + version: 1.0.2 + resolution: "@phryneas/ts-version@npm:1.0.2" + checksum: d51914a8ea35ff8b686a9379b9e9fe6d5b5feaf2e7511b880d2835015736e33bc82952bbc369918f251d4a755f32f4a9c4a34b0ec4dfdbc3e87a41d26401105c + languageName: node + linkType: hard + "@pmmmwh/react-refresh-webpack-plugin@npm:^0.5.3": version: 0.5.7 resolution: "@pmmmwh/react-refresh-webpack-plugin@npm:0.5.7" @@ -6981,6 +6988,7 @@ __metadata: resolution: "@reduxjs/toolkit@workspace:packages/toolkit" dependencies: "@microsoft/api-extractor": ^7.13.2 + "@phryneas/ts-version": ^1.0.2 "@size-limit/preset-small-lib": ^4.11.0 "@testing-library/react": ^13.3.0 "@testing-library/user-event": ^13.1.5 From 083546fcfce66337a65f76315edccfbf31b4c937 Mon Sep 17 00:00:00 2001 From: Mark Erikson Date: Sun, 1 Oct 2023 15:02:43 -0400 Subject: [PATCH 289/412] Work around known TS bug with type inference --- .../src/tests/createAsyncThunk.typetest.ts | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/packages/toolkit/src/tests/createAsyncThunk.typetest.ts b/packages/toolkit/src/tests/createAsyncThunk.typetest.ts index e5e90a8e40..05cf4fd604 100644 --- a/packages/toolkit/src/tests/createAsyncThunk.typetest.ts +++ b/packages/toolkit/src/tests/createAsyncThunk.typetest.ts @@ -21,6 +21,7 @@ import type { AsyncThunkFulfilledActionCreator, AsyncThunkRejectedActionCreator, } from '@internal/createAsyncThunk' +import type { TSVersion } from '@phryneas/ts-version' const ANY = {} as any const defaultDispatch = (() => {}) as ThunkDispatch<{}, any, UnknownAction> @@ -291,8 +292,22 @@ const unknownAction = { type: 'foo' } as UnknownAction // in that case, we have to forbid this behaviour or it will make arguments optional everywhere { const asyncThunk = createAsyncThunk('test', (arg?: number) => 0) - expectType<(arg?: number) => any>(asyncThunk) - asyncThunk() + + // Per https://github.com/reduxjs/redux-toolkit/issues/3758#issuecomment-1742152774 , this is a bug in + // TS 5.1 and 5.2, that is fixed in 5.3. Conditionally run the TS assertion here. + type IsTS51Or52 = TSVersion.Major extends 5 + ? TSVersion.Minor extends 1 | 2 + ? true + : false + : false + + type expectedType = IsTS51Or52 extends true + ? (arg: number) => any + : (arg?: number) => any + expectType(asyncThunk) + // We _should_ be able to call this with no arguments, but we run into that error in 5.1 and 5.2. + // Disabling this for now. + // asyncThunk() asyncThunk(5) // @ts-expect-error asyncThunk('string') From d248e7a059c7b0979d2d3f25c88a6196e91baea2 Mon Sep 17 00:00:00 2001 From: Ben Durrant Date: Sun, 1 Oct 2023 13:47:37 +0100 Subject: [PATCH 290/412] Remove middleware array option --- packages/toolkit/src/configureStore.ts | 16 ++- .../toolkit/src/tests/configureStore.test.ts | 23 ++--- .../src/tests/configureStore.typetest.ts | 17 ++-- .../src/tests/getDefaultMiddleware.test.ts | 63 ++++++------ ...rializableStateInvariantMiddleware.test.ts | 98 ++++++++++--------- 5 files changed, 116 insertions(+), 101 deletions(-) diff --git a/packages/toolkit/src/configureStore.ts b/packages/toolkit/src/configureStore.ts index 9741f42a19..c155279ac2 100644 --- a/packages/toolkit/src/configureStore.ts +++ b/packages/toolkit/src/configureStore.ts @@ -53,7 +53,7 @@ export interface ConfigureStoreOptions< * @example `middleware: (gDM) => gDM().concat(logger, apiMiddleware, yourCustomMiddleware)` * @see https://redux-toolkit.js.org/api/getDefaultMiddleware#intended-usage */ - middleware?: ((getDefaultMiddleware: GetDefaultMiddleware) => M) | M + middleware?: (getDefaultMiddleware: GetDefaultMiddleware) => M /** * Whether to enable Redux DevTools integration. Defaults to `true`. @@ -121,7 +121,7 @@ export function configureStore< const { reducer = undefined, - middleware = getDefaultMiddleware(), + middleware, devTools = true, preloadedState = undefined, enhancers = undefined, @@ -139,15 +139,21 @@ export function configureStore< ) } - let finalMiddleware = middleware - if (typeof finalMiddleware === 'function') { - finalMiddleware = finalMiddleware(getDefaultMiddleware) + if (!IS_PRODUCTION && middleware && typeof middleware !== 'function') { + throw new Error('"middleware" field must be a callback') + } + + let finalMiddleware: Tuple> + if (typeof middleware === 'function') { + finalMiddleware = middleware(getDefaultMiddleware) if (!IS_PRODUCTION && !Array.isArray(finalMiddleware)) { throw new Error( 'when using a middleware builder function, an array of middleware must be returned' ) } + } else { + finalMiddleware = getDefaultMiddleware() } if ( !IS_PRODUCTION && diff --git a/packages/toolkit/src/tests/configureStore.test.ts b/packages/toolkit/src/tests/configureStore.test.ts index 0b12d20548..1a7cf100a1 100644 --- a/packages/toolkit/src/tests/configureStore.test.ts +++ b/packages/toolkit/src/tests/configureStore.test.ts @@ -110,7 +110,7 @@ describe('configureStore', async () => { describe('given no middleware', () => { it('calls createStore without any middleware', () => { expect( - configureStore({ middleware: new Tuple(), reducer }) + configureStore({ middleware: () => new Tuple(), reducer }) ).toBeInstanceOf(Object) expect(redux.applyMiddleware).toHaveBeenCalledWith() expect(mockDevtoolsCompose).toHaveBeenCalled() // @remap-prod-remove-line-line @@ -122,6 +122,15 @@ describe('configureStore', async () => { }) }) + describe('given an array of middleware', () => { + it('throws an error requiring a callback', () => { + // @ts-expect-error + expect(() => configureStore({ middleware: [], reducer })).toThrow( + '"middleware" field must be a callback' + ) + }) + }) + describe('given undefined middleware', () => { it('calls createStore with default middleware', () => { expect(configureStore({ middleware: undefined, reducer })).toBeInstanceOf( @@ -162,20 +171,12 @@ describe('configureStore', async () => { }) }) - describe('given custom middleware that contains non-functions', () => { - it('throws an error', () => { - expect(() => - configureStore({ middleware: [true] as any, reducer }) - ).toThrow('each middleware provided to configureStore must be a function') - }) - }) - describe('given custom middleware', () => { it('calls createStore with custom middleware and without default middleware', () => { const thank: Redux.Middleware = (_store) => (next) => (action) => next(action) expect( - configureStore({ middleware: new Tuple(thank), reducer }) + configureStore({ middleware: () => new Tuple(thank), reducer }) ).toBeInstanceOf(Object) expect(redux.applyMiddleware).toHaveBeenCalledWith(thank) expect(mockDevtoolsCompose).toHaveBeenCalled() // @remap-prod-remove-line-line @@ -330,7 +331,7 @@ describe('configureStore', async () => { it("doesn't warn when middleware enhancer is excluded if no middlewares provided", () => { const store = configureStore({ reducer, - middleware: new Tuple(), + middleware: () => new Tuple(), enhancers: () => new Tuple(dummyEnhancer), }) diff --git a/packages/toolkit/src/tests/configureStore.typetest.ts b/packages/toolkit/src/tests/configureStore.typetest.ts index 298de65010..d141f85df2 100644 --- a/packages/toolkit/src/tests/configureStore.typetest.ts +++ b/packages/toolkit/src/tests/configureStore.typetest.ts @@ -74,19 +74,19 @@ const _anyMiddleware: any = () => () => () => {} configureStore({ reducer: () => 0, - middleware: new Tuple(middleware), + middleware: () => new Tuple(middleware), }) configureStore({ reducer: () => 0, // @ts-expect-error - middleware: [middleware], + middleware: () => [middleware], }) configureStore({ reducer: () => 0, // @ts-expect-error - middleware: new Tuple('not middleware'), + middleware: () => new Tuple('not middleware'), }) } @@ -520,7 +520,7 @@ const _anyMiddleware: any = () => () => () => {} { const store = configureStore({ reducer: reducerA, - middleware: new Tuple(), + middleware: () => new Tuple(), }) // @ts-expect-error store.dispatch(thunkA()) @@ -533,7 +533,7 @@ const _anyMiddleware: any = () => () => () => {} { const store = configureStore({ reducer: reducerA, - middleware: new Tuple(thunk as ThunkMiddleware), + middleware: () => new Tuple(thunk as ThunkMiddleware), }) store.dispatch(thunkA()) // @ts-expect-error @@ -545,9 +545,8 @@ const _anyMiddleware: any = () => () => () => {} { const store = configureStore({ reducer: reducerA, - middleware: new Tuple( - 0 as unknown as Middleware<(a: StateA) => boolean, StateA> - ), + middleware: () => + new Tuple(0 as unknown as Middleware<(a: StateA) => boolean, StateA>), }) const result: boolean = store.dispatch(5) // @ts-expect-error @@ -566,7 +565,7 @@ const _anyMiddleware: any = () => () => () => {} > const store = configureStore({ reducer: reducerA, - middleware, + middleware: () => middleware, }) const result: 'A' = store.dispatch('a') diff --git a/packages/toolkit/src/tests/getDefaultMiddleware.test.ts b/packages/toolkit/src/tests/getDefaultMiddleware.test.ts index 36c55fcfb6..6ee86933b2 100644 --- a/packages/toolkit/src/tests/getDefaultMiddleware.test.ts +++ b/packages/toolkit/src/tests/getDefaultMiddleware.test.ts @@ -147,17 +147,18 @@ describe('getDefaultMiddleware', () => { it('allows passing options to immutableCheck', () => { let immutableCheckWasCalled = false - const middleware = getDefaultMiddleware({ - thunk: false, - immutableCheck: { - isImmutable: () => { - immutableCheckWasCalled = true - return true + const middleware = () => + getDefaultMiddleware({ + thunk: false, + immutableCheck: { + isImmutable: () => { + immutableCheckWasCalled = true + return true + }, }, - }, - serializableCheck: false, - actionCreatorCheck: false, - }) + serializableCheck: false, + actionCreatorCheck: false, + }) const reducer = () => ({}) @@ -172,17 +173,18 @@ describe('getDefaultMiddleware', () => { it('allows passing options to serializableCheck', () => { let serializableCheckWasCalled = false - const middleware = getDefaultMiddleware({ - thunk: false, - immutableCheck: false, - serializableCheck: { - isSerializable: () => { - serializableCheckWasCalled = true - return true + const middleware = () => + getDefaultMiddleware({ + thunk: false, + immutableCheck: false, + serializableCheck: { + isSerializable: () => { + serializableCheckWasCalled = true + return true + }, }, - }, - actionCreatorCheck: false, - }) + actionCreatorCheck: false, + }) const reducer = () => ({}) @@ -200,17 +202,18 @@ describe('getDefaultMiddleware', () => { it('allows passing options to actionCreatorCheck', () => { let actionCreatorCheckWasCalled = false - const middleware = getDefaultMiddleware({ - thunk: false, - immutableCheck: false, - serializableCheck: false, - actionCreatorCheck: { - isActionCreator: (action: unknown): action is Function => { - actionCreatorCheckWasCalled = true - return false + const middleware = () => + getDefaultMiddleware({ + thunk: false, + immutableCheck: false, + serializableCheck: false, + actionCreatorCheck: { + isActionCreator: (action: unknown): action is Function => { + actionCreatorCheckWasCalled = true + return false + }, }, - }, - }) + }) const reducer = () => ({}) diff --git a/packages/toolkit/src/tests/serializableStateInvariantMiddleware.test.ts b/packages/toolkit/src/tests/serializableStateInvariantMiddleware.test.ts index 2d28f66330..60e4e3b5a3 100644 --- a/packages/toolkit/src/tests/serializableStateInvariantMiddleware.test.ts +++ b/packages/toolkit/src/tests/serializableStateInvariantMiddleware.test.ts @@ -101,7 +101,7 @@ describe('serializableStateInvariantMiddleware', () => { const store = configureStore({ reducer, - middleware: new Tuple(serializableStateInvariantMiddleware), + middleware: () => new Tuple(serializableStateInvariantMiddleware), }) const symbol = Symbol.for('SOME_CONSTANT') @@ -148,7 +148,7 @@ describe('serializableStateInvariantMiddleware', () => { reducer: { testSlice: reducer, }, - middleware: new Tuple(serializableStateInvariantMiddleware), + middleware: () => new Tuple(serializableStateInvariantMiddleware), }) store.dispatch({ type: ACTION_TYPE }) @@ -208,7 +208,7 @@ describe('serializableStateInvariantMiddleware', () => { reducer: { testSlice: reducer, }, - middleware: new Tuple(serializableStateInvariantMiddleware), + middleware: () => new Tuple(serializableStateInvariantMiddleware), }) store.dispatch({ type: ACTION_TYPE }) @@ -255,7 +255,7 @@ describe('serializableStateInvariantMiddleware', () => { reducer: { testSlice: reducer, }, - middleware: new Tuple(serializableStateInvariantMiddleware), + middleware: () => new Tuple(serializableStateInvariantMiddleware), }) store.dispatch({ type: ACTION_TYPE }) @@ -299,7 +299,7 @@ describe('serializableStateInvariantMiddleware', () => { reducer: { testSlice: reducer, }, - middleware: new Tuple(serializableStateInvariantMiddleware), + middleware: () => new Tuple(serializableStateInvariantMiddleware), }) store.dispatch({ type: ACTION_TYPE }) @@ -323,7 +323,7 @@ describe('serializableStateInvariantMiddleware', () => { const store = configureStore({ reducer: () => ({}), - middleware: new Tuple(serializableStateMiddleware), + middleware: () => new Tuple(serializableStateMiddleware), }) expect(numTimesCalled).toBe(0) @@ -348,7 +348,8 @@ describe('serializableStateInvariantMiddleware', () => { it('default value: meta.arg', () => { configureStore({ reducer, - middleware: new Tuple(createSerializableStateInvariantMiddleware()), + middleware: () => + new Tuple(createSerializableStateInvariantMiddleware()), }).dispatch({ type: 'test', meta: { arg: nonSerializableValue } }) expect(getLog().log).toMatchInlineSnapshot(`""`) @@ -357,11 +358,12 @@ describe('serializableStateInvariantMiddleware', () => { it('default value can be overridden', () => { configureStore({ reducer, - middleware: new Tuple( - createSerializableStateInvariantMiddleware({ - ignoredActionPaths: [], - }) - ), + middleware: () => + new Tuple( + createSerializableStateInvariantMiddleware({ + ignoredActionPaths: [], + }) + ), }).dispatch({ type: 'test', meta: { arg: nonSerializableValue } }) expect(getLog().log).toMatchInlineSnapshot(` @@ -380,11 +382,12 @@ describe('serializableStateInvariantMiddleware', () => { it('can specify (multiple) different values', () => { configureStore({ reducer, - middleware: new Tuple( - createSerializableStateInvariantMiddleware({ - ignoredActionPaths: ['payload', 'meta.arg'], - }) - ), + middleware: () => + new Tuple( + createSerializableStateInvariantMiddleware({ + ignoredActionPaths: ['payload', 'meta.arg'], + }) + ), }).dispatch({ type: 'test', payload: { arg: nonSerializableValue }, @@ -397,11 +400,12 @@ describe('serializableStateInvariantMiddleware', () => { it('can specify regexp', () => { configureStore({ reducer, - middleware: new Tuple( - createSerializableStateInvariantMiddleware({ - ignoredActionPaths: [/^payload\..*$/], - }) - ), + middleware: () => + new Tuple( + createSerializableStateInvariantMiddleware({ + ignoredActionPaths: [/^payload\..*$/], + }) + ), }).dispatch({ type: 'test', payload: { arg: nonSerializableValue }, @@ -425,7 +429,7 @@ describe('serializableStateInvariantMiddleware', () => { const store = configureStore({ reducer: () => ({}), - middleware: new Tuple(serializableStateMiddleware), + middleware: () => new Tuple(serializableStateMiddleware), }) expect(numTimesCalled).toBe(0) @@ -488,7 +492,7 @@ describe('serializableStateInvariantMiddleware', () => { reducer: { testSlice: reducer, }, - middleware: new Tuple(serializableStateInvariantMiddleware), + middleware: () => new Tuple(serializableStateInvariantMiddleware), }) store.dispatch({ type: ACTION_TYPE }) @@ -507,15 +511,16 @@ describe('serializableStateInvariantMiddleware', () => { const reducer = () => badValue const store = configureStore({ reducer, - middleware: new Tuple( - createSerializableStateInvariantMiddleware({ - isSerializable: () => { - numTimesCalled++ - return true - }, - ignoreState: true, - }) - ), + middleware: () => + new Tuple( + createSerializableStateInvariantMiddleware({ + isSerializable: () => { + numTimesCalled++ + return true + }, + ignoreState: true, + }) + ), }) expect(numTimesCalled).toBe(0) @@ -534,16 +539,17 @@ describe('serializableStateInvariantMiddleware', () => { const reducer = () => badValue const store = configureStore({ reducer, - middleware: new Tuple( - createSerializableStateInvariantMiddleware({ - isSerializable: () => { - numTimesCalled++ - return true - }, - ignoreState: true, - ignoreActions: true, - }) - ), + middleware: () => + new Tuple( + createSerializableStateInvariantMiddleware({ + isSerializable: () => { + numTimesCalled++ + return true + }, + ignoreState: true, + ignoreActions: true, + }) + ), }) expect(numTimesCalled).toBe(0) @@ -566,7 +572,7 @@ describe('serializableStateInvariantMiddleware', () => { reducer: { testSlice: reducer, }, - middleware: new Tuple(serializableStateInvariantMiddleware), + middleware: () => new Tuple(serializableStateInvariantMiddleware), }) store.dispatch({ @@ -592,7 +598,7 @@ describe('serializableStateInvariantMiddleware', () => { reducer: { testSlice: reducer, }, - middleware: new Tuple(serializableStateInvariantMiddleware), + middleware: () => new Tuple(serializableStateInvariantMiddleware), }) store.dispatch({ type: 'SOME_ACTION' }) @@ -616,7 +622,7 @@ describe('serializableStateInvariantMiddleware', () => { if (action.type === 'SET_STATE') return action.payload return state }, - middleware: new Tuple(serializableStateInvariantMiddleware), + middleware: () => new Tuple(serializableStateInvariantMiddleware), }) const state = createNextState([], () => From 7cd3fd587cf1400a8f54f042ee17bcab15e867fd Mon Sep 17 00:00:00 2001 From: Ben Durrant Date: Sun, 1 Oct 2023 17:23:14 +0100 Subject: [PATCH 291/412] Update docs --- docs/api/actionCreatorMiddleware.mdx | 2 +- docs/api/configureStore.mdx | 28 ++++++++++---------------- docs/api/getDefaultMiddleware.mdx | 2 +- docs/api/immutabilityMiddleware.mdx | 2 +- docs/api/serializabilityMiddleware.mdx | 2 +- docs/usage/usage-with-typescript.md | 2 +- 6 files changed, 16 insertions(+), 22 deletions(-) diff --git a/docs/api/actionCreatorMiddleware.mdx b/docs/api/actionCreatorMiddleware.mdx index 0afce4f368..50570ae109 100644 --- a/docs/api/actionCreatorMiddleware.mdx +++ b/docs/api/actionCreatorMiddleware.mdx @@ -63,6 +63,6 @@ const actionCreatorMiddleware = createActionCreatorInvariantMiddleware({ const store = configureStore({ reducer, - middleware: new Tuple(actionCreatorMiddleware), + middleware: () => new Tuple(actionCreatorMiddleware), }) ``` diff --git a/docs/api/configureStore.mdx b/docs/api/configureStore.mdx index 87f8b982e4..08aa0f089c 100644 --- a/docs/api/configureStore.mdx +++ b/docs/api/configureStore.mdx @@ -100,18 +100,15 @@ If it is an object of slice reducers, like `{users : usersReducer, posts : posts ### `middleware` -An optional array of Redux middleware functions, or a callback to customise the array of middleware. +A callback which will receive `getDefaultMiddleware` as its argument, +and should return a middleware array. -If this option is provided, it should contain all the middleware functions you +If this option is provided, it should return all the middleware functions you want added to the store. `configureStore` will automatically pass those to `applyMiddleware`. If not provided, `configureStore` will call `getDefaultMiddleware` and use the array of middleware functions it returns. -Where you wish to add onto or customize the default middleware, -you may pass a callback function that will receive `getDefaultMiddleware` as its argument, -and should return a middleware array. - For more details on how the `middleware` parameter works and the list of middleware that are added by default, see the [`getDefaultMiddleware` docs page](./getDefaultMiddleware.mdx). @@ -123,7 +120,7 @@ import { configureStore, Tuple } from '@reduxjs/toolkit' configureStore({ reducer: rootReducer, - middleware: new Tuple(additionalMiddleware, logger), + middleware: () => new Tuple(additionalMiddleware, logger), }) ``` @@ -178,11 +175,6 @@ If you provide an array, this `applyMiddleware` enhancer will _not_ be used. `configureStore` will warn in console if any middleware are provided (or left as default) but not included in the final list of enhancers. ```ts no-transpile -// warns - middleware left as default but not included in final enhancers -configureStore({ - reducer, - enhancers: [offline(offlineConfig)], -}) // warns - middleware customised but not included in final enhancers configureStore({ reducer, @@ -199,8 +191,8 @@ configureStore({ // also allowed configureStore({ reducer, - middleware: [], - enhancers: [offline(offlineConfig)], + middleware: () => [], + enhancers: () => [offline(offlineConfig)], }) ``` @@ -209,20 +201,22 @@ configureStore({ :::note Tuple Typescript users are required to use a `Tuple` instance (if not using a `getDefaultEnhancer` result, which is already a `Tuple`), for better inference. +``` import { configureStore, Tuple } from '@reduxjs/toolkit' configureStore({ reducer: rootReducer, -enhancers: new Tuple(offline), +enhancers: () => new Tuple(offline), }) -```` +``` Javascript-only users are free to use a plain array if preferred. ::: ## Usage + ### Basic Example ```ts @@ -238,7 +232,7 @@ import rootReducer from './reducers' const store = configureStore({ reducer: rootReducer }) // The store now has redux-thunk added and the Redux DevTools Extension is turned on -```` +``` ### Full Example diff --git a/docs/api/getDefaultMiddleware.mdx b/docs/api/getDefaultMiddleware.mdx index 877a26293c..ef15eca14f 100644 --- a/docs/api/getDefaultMiddleware.mdx +++ b/docs/api/getDefaultMiddleware.mdx @@ -28,7 +28,7 @@ If you want to customize the list of middleware, you can supply an array of midd ```js const store = configureStore({ reducer: rootReducer, - middleware: new Tuple(thunk, logger), + middleware: () => new Tuple(thunk, logger), }) // Store specifically has the thunk and logger middleware applied diff --git a/docs/api/immutabilityMiddleware.mdx b/docs/api/immutabilityMiddleware.mdx index 16950b129e..6360e5417b 100644 --- a/docs/api/immutabilityMiddleware.mdx +++ b/docs/api/immutabilityMiddleware.mdx @@ -86,7 +86,7 @@ const immutableInvariantMiddleware = createImmutableStateInvariantMiddleware({ const store = configureStore({ reducer: exampleSliceReducer, // Note that this will replace all default middleware - middleware: new Tuple(immutableInvariantMiddleware), + middleware: () => new Tuple(immutableInvariantMiddleware), }) ``` diff --git a/docs/api/serializabilityMiddleware.mdx b/docs/api/serializabilityMiddleware.mdx index e8cb8e8363..29893bad31 100644 --- a/docs/api/serializabilityMiddleware.mdx +++ b/docs/api/serializabilityMiddleware.mdx @@ -111,7 +111,7 @@ const serializableMiddleware = createSerializableStateInvariantMiddleware({ const store = configureStore({ reducer, - middleware: new Tuple(serializableMiddleware), + middleware: () => new Tuple(serializableMiddleware), }) ``` diff --git a/docs/usage/usage-with-typescript.md b/docs/usage/usage-with-typescript.md index beb99116d2..73734f605d 100644 --- a/docs/usage/usage-with-typescript.md +++ b/docs/usage/usage-with-typescript.md @@ -145,7 +145,7 @@ import { configureStore, Tuple } from '@reduxjs/toolkit' configureStore({ reducer: rootReducer, - middleware: new Tuple(additionalMiddleware, logger), + middleware: () => new Tuple(additionalMiddleware, logger), }) ``` From de56e95ddb065c0bdfa393e519ea0ea76fa47470 Mon Sep 17 00:00:00 2001 From: Mark Erikson Date: Sun, 1 Oct 2023 15:26:30 -0400 Subject: [PATCH 292/412] Add new error entry --- errors.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/errors.json b/errors.json index 88e5d18183..a2e90bc2eb 100644 --- a/errors.json +++ b/errors.json @@ -29,5 +29,6 @@ "27": "Warning: Middleware for RTK-Query API at reducerPath \"\" has not been added to the store.\n You must add the middleware for RTK-Query to function correctly!", "28": "Cannot refetch a query that has not been started yet.", "29": "`builder.addCase` cannot be called with an empty action type", - "30": "`builder.addCase` cannot be called with two reducers for the same action type" + "30": "`builder.addCase` cannot be called with two reducers for the same action type", + "31": "\"middleware\" field must be a callback" } \ No newline at end of file From 54f305e7ecf1a0518d0af781d44d7ee5fbe7e13f Mon Sep 17 00:00:00 2001 From: "ben.durrant" Date: Wed, 21 Jun 2023 21:43:56 +0100 Subject: [PATCH 293/412] warn for and migrate old options and require all hooks --- packages/toolkit/src/query/react/module.ts | 140 +++++++++++++-------- 1 file changed, 88 insertions(+), 52 deletions(-) diff --git a/packages/toolkit/src/query/react/module.ts b/packages/toolkit/src/query/react/module.ts index 3eea84bdeb..c5d4261992 100644 --- a/packages/toolkit/src/query/react/module.ts +++ b/packages/toolkit/src/query/react/module.ts @@ -140,57 +140,93 @@ export const reactHooksModule = ({ useStore: rrUseStore, }, unstable__sideEffectsInRender = false, -}: ReactHooksModuleOptions = {}): Module => ({ - name: reactHooksModuleName, - init(api, { serializeQueryArgs }, context) { - const anyApi = api as any as Api< - any, - Record, - string, - string, - ReactHooksModule - > - const { buildQueryHooks, buildMutationHook, usePrefetch } = buildHooks({ - api, - moduleOptions: { - batch, - hooks, - unstable__sideEffectsInRender, - }, - serializeQueryArgs, - context, - }) - safeAssign(anyApi, { usePrefetch }) - safeAssign(context, { batch }) - - return { - injectEndpoint(endpointName, definition) { - if (isQueryDefinition(definition)) { - const { - useQuery, - useLazyQuery, - useLazyQuerySubscription, - useQueryState, - useQuerySubscription, - } = buildQueryHooks(endpointName) - safeAssign(anyApi.endpoints[endpointName], { - useQuery, - useLazyQuery, - useLazyQuerySubscription, - useQueryState, - useQuerySubscription, - }) - ;(api as any)[`use${capitalize(endpointName)}Query`] = useQuery - ;(api as any)[`useLazy${capitalize(endpointName)}Query`] = - useLazyQuery - } else if (isMutationDefinition(definition)) { - const useMutation = buildMutationHook(endpointName) - safeAssign(anyApi.endpoints[endpointName], { - useMutation, - }) - ;(api as any)[`use${capitalize(endpointName)}Mutation`] = useMutation + ...rest +}: ReactHooksModuleOptions = {}): Module => { + if (process.env.NODE_ENV !== 'production') { + const hookNames = ['useDispatch', 'useSelector', 'useStore'] as const + let warned = false + for (const hookName of hookNames) { + // warn for old hook options + if (Object.keys(rest).length > 0) { + if ((rest as Partial)[hookName]) { + if (!warned) { + console.warn( + 'As of RTK 2.0, the hooks now need to be specified as one object, provided under a `hooks` key:' + + '\n`reactHooksModule({ hooks: { useDispatch, useSelector, useStore } })`' + ) + warned = true + } } - }, + // migrate + // @ts-ignore + hooks[hookName] = rest[hookName] + } + // then make sure we have them all + if (typeof hooks[hookName] !== 'function') { + throw new Error( + `When using custom hooks for context, all ${ + hookNames.length + } hooks need to be provided: ${hookNames.join( + ', ' + )}.\nHook ${hookName} was either not provided or not a function.` + ) + } } - }, -}) + } + + return { + name: reactHooksModuleName, + init(api, { serializeQueryArgs }, context) { + const anyApi = api as any as Api< + any, + Record, + string, + string, + ReactHooksModule + > + const { buildQueryHooks, buildMutationHook, usePrefetch } = buildHooks({ + api, + moduleOptions: { + batch, + hooks, + unstable__sideEffectsInRender, + }, + serializeQueryArgs, + context, + }) + safeAssign(anyApi, { usePrefetch }) + safeAssign(context, { batch }) + + return { + injectEndpoint(endpointName, definition) { + if (isQueryDefinition(definition)) { + const { + useQuery, + useLazyQuery, + useLazyQuerySubscription, + useQueryState, + useQuerySubscription, + } = buildQueryHooks(endpointName) + safeAssign(anyApi.endpoints[endpointName], { + useQuery, + useLazyQuery, + useLazyQuerySubscription, + useQueryState, + useQuerySubscription, + }) + ;(api as any)[`use${capitalize(endpointName)}Query`] = useQuery + ;(api as any)[`useLazy${capitalize(endpointName)}Query`] = + useLazyQuery + } else if (isMutationDefinition(definition)) { + const useMutation = buildMutationHook(endpointName) + safeAssign(anyApi.endpoints[endpointName], { + useMutation, + }) + ;(api as any)[`use${capitalize(endpointName)}Mutation`] = + useMutation + } + }, + } + }, + } +} From cce57b776aac729aeee23bd83d84597cb3983852 Mon Sep 17 00:00:00 2001 From: Mark Erikson Date: Sun, 1 Oct 2023 16:01:59 -0400 Subject: [PATCH 294/412] Add tests for buildCreateApi --- .../src/query/tests/buildCreateApi.test.tsx | 126 ++++++++++++++++++ 1 file changed, 126 insertions(+) create mode 100644 packages/toolkit/src/query/tests/buildCreateApi.test.tsx diff --git a/packages/toolkit/src/query/tests/buildCreateApi.test.tsx b/packages/toolkit/src/query/tests/buildCreateApi.test.tsx new file mode 100644 index 0000000000..999d34693d --- /dev/null +++ b/packages/toolkit/src/query/tests/buildCreateApi.test.tsx @@ -0,0 +1,126 @@ +import * as React from 'react' +import type { ReactReduxContextValue } from 'react-redux' +import { + createDispatchHook, + createSelectorHook, + createStoreHook, + Provider, +} from 'react-redux' +import { + buildCreateApi, + coreModule, + reactHooksModule, +} from '@reduxjs/toolkit/query/react' +import { + act, + fireEvent, + render, + screen, + waitFor, + renderHook, +} from '@testing-library/react' +import userEvent from '@testing-library/user-event' +import { rest } from 'msw' +import { + actionsReducer, + ANY, + expectExactType, + expectType, + setupApiStore, + withProvider, + useRenderCounter, + waitMs, +} from './helpers' +import { server } from './mocks/server' +import type { UnknownAction } from 'redux' +import type { SubscriptionOptions } from '@reduxjs/toolkit/dist/query/core/apiState' +import type { SerializedError } from '@reduxjs/toolkit' +import { createListenerMiddleware, configureStore } from '@reduxjs/toolkit' +import { delay } from '../../utils' + +const MyContext = React.createContext(null as any) + +describe('buildCreateApi', () => { + test('Works with all hooks provided', async () => { + const customCreateApi = buildCreateApi( + coreModule(), + reactHooksModule({ + hooks: { + useDispatch: createDispatchHook(MyContext), + useSelector: createSelectorHook(MyContext), + useStore: createStoreHook(MyContext), + }, + }) + ) + + const api = customCreateApi({ + baseQuery: async (arg: any) => { + await waitMs() + + return { + data: arg?.body ? { ...arg.body } : {}, + } + }, + endpoints: (build) => ({ + getUser: build.query<{ name: string }, number>({ + query: () => ({ + body: { name: 'Timmy' }, + }), + }), + }), + }) + + let getRenderCount: () => number = () => 0 + + const storeRef = setupApiStore(api, {}, { withoutTestLifecycles: true }) + + // Copy of 'useQuery hook basic render count assumptions' from `buildHooks.test.tsx` + function User() { + const { isFetching } = api.endpoints.getUser.useQuery(1) + getRenderCount = useRenderCounter() + + return ( +
+
{String(isFetching)}
+
+ ) + } + + function Wrapper({ children }: any) { + return ( + + {children} + + ) + } + + render(, { wrapper: Wrapper }) + // By the time this runs, the initial render will happen, and the query + // will start immediately running by the time we can expect this + expect(getRenderCount()).toBe(2) + + await waitFor(() => + expect(screen.getByTestId('isFetching').textContent).toBe('false') + ) + expect(getRenderCount()).toBe(3) + }) + + test("Throws an error if you don't provide all hooks", async () => { + const callBuildCreateApi = () => { + const customCreateApi = buildCreateApi( + coreModule(), + reactHooksModule({ + // @ts-ignore + hooks: { + useDispatch: createDispatchHook(MyContext), + useSelector: createSelectorHook(MyContext), + }, + }) + ) + } + + expect(callBuildCreateApi).toThrowErrorMatchingInlineSnapshot( + `"When using custom hooks for context, all 3 hooks need to be provided: useDispatch, useSelector, useStore.\nHook useStore was either not provided or not a function."` + ) + }) +}) From 7535aec499dd2e89c1bc8f5bf6a976761a808478 Mon Sep 17 00:00:00 2001 From: Mark Erikson Date: Sun, 1 Oct 2023 16:03:14 -0400 Subject: [PATCH 295/412] Add new error message --- errors.json | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/errors.json b/errors.json index a2e90bc2eb..e6513b5abc 100644 --- a/errors.json +++ b/errors.json @@ -30,5 +30,6 @@ "28": "Cannot refetch a query that has not been started yet.", "29": "`builder.addCase` cannot be called with an empty action type", "30": "`builder.addCase` cannot be called with two reducers for the same action type", - "31": "\"middleware\" field must be a callback" -} \ No newline at end of file + "31": "\"middleware\" field must be a callback", + "32": "When using custom hooks for context, all hooks need to be provided: .\\nHook was either not provided or not a function." +} From d8b7e4ac3f7af702e8bbec28ca7156e0f93c869d Mon Sep 17 00:00:00 2001 From: Ben Durrant Date: Sun, 1 Oct 2023 22:29:49 +0100 Subject: [PATCH 296/412] dryer --- .../toolkit/src/dynamicMiddleware/tests/index.test.ts | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/packages/toolkit/src/dynamicMiddleware/tests/index.test.ts b/packages/toolkit/src/dynamicMiddleware/tests/index.test.ts index 4ef03bd0cb..84bd1648af 100644 --- a/packages/toolkit/src/dynamicMiddleware/tests/index.test.ts +++ b/packages/toolkit/src/dynamicMiddleware/tests/index.test.ts @@ -8,10 +8,9 @@ import { isAllOf } from '../../matchers' const probeType = 'probeableMW/probe' -type ProbeType = typeof probeType - -export interface ProbeMiddleware extends BaseActionCreator { - (id: Id): PayloadAction +export interface ProbeMiddleware + extends BaseActionCreator { + (id: Id): PayloadAction } export const probeMiddleware = createAction(probeType) as ProbeMiddleware @@ -24,7 +23,7 @@ const matchId = export const makeProbeableMiddleware = ( id: Id ): Middleware<{ - (action: PayloadAction): Id + (action: PayloadAction): Id }> => { const isMiddlewareAction = isAllOf(isAction, probeMiddleware, matchId(id)) return (api) => (next) => (action) => { From 925c221ce1c1ada566511ff57be800e79ef89c95 Mon Sep 17 00:00:00 2001 From: Mark Erikson Date: Sun, 1 Oct 2023 20:57:47 -0400 Subject: [PATCH 297/412] Add Vitest and remove Jest --- packages/rtk-codemods/package.json | 12 ++---- yarn.lock | 61 ++++++------------------------ 2 files changed, 16 insertions(+), 57 deletions(-) diff --git a/packages/rtk-codemods/package.json b/packages/rtk-codemods/package.json index 7f1fa0b7a9..84c528e3e9 100644 --- a/packages/rtk-codemods/package.json +++ b/packages/rtk-codemods/package.json @@ -3,8 +3,8 @@ "version": "0.0.3", "scripts": { "lint": "eslint --cache .", - "test": "codemod-cli test", - "test:coverage": "codemod-cli test --coverage", + "test": "vitest", + "test:coverage": "vitest --coverage", "update-docs": "codemod-cli update-docs" }, "bin": "./bin/cli.js", @@ -22,10 +22,8 @@ "codemod" ], "dependencies": { - "@types/jest": "^27", "@types/jscodeshift": "^0.11.5", "codemod-cli": "^3.2.0", - "ts-node": "10.4.0", "typescript": "^4.8.0" }, "devDependencies": { @@ -33,12 +31,10 @@ "eslint-config-prettier": "^8.3.0", "eslint-plugin-node": "^11.1.0", "eslint-plugin-prettier": "^3.4.0", - "jest": "^27", - "prettier": "^2.2.1", - "ts-jest": "^27" + "prettier": "^2.2.1" }, "engines": { - "node": ">= 14" + "node": ">= 16" }, "publishConfig": { "access": "public" diff --git a/yarn.lock b/yarn.lock index e74fb26bf6..c7444afada 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6926,17 +6926,13 @@ __metadata: version: 0.0.0-use.local resolution: "@reduxjs/rtk-codemods@workspace:packages/rtk-codemods" dependencies: - "@types/jest": ^27 "@types/jscodeshift": ^0.11.5 codemod-cli: ^3.2.0 eslint: ^7.25.0 eslint-config-prettier: ^8.3.0 eslint-plugin-node: ^11.1.0 eslint-plugin-prettier: ^3.4.0 - jest: ^27 prettier: ^2.2.1 - ts-jest: ^27 - ts-node: 10.4.0 typescript: ^4.8.0 bin: rtk-codemods: ./bin/cli.js @@ -19107,7 +19103,7 @@ fsevents@^1.2.7: languageName: node linkType: hard -"jest-util@npm:^27.0.0, jest-util@npm:^27.5.1": +"jest-util@npm:^27.5.1": version: 27.5.1 resolution: "jest-util@npm:27.5.1" dependencies: @@ -19275,7 +19271,7 @@ fsevents@^1.2.7: languageName: node linkType: hard -"jest@npm:^27, jest@npm:^27.4.3": +"jest@npm:^27.4.3": version: 27.5.1 resolution: "jest@npm:27.5.1" dependencies: @@ -19593,15 +19589,6 @@ fsevents@^1.2.7: languageName: node linkType: hard -"json5@npm:2.x, json5@npm:^2.1.2, json5@npm:^2.2.0, json5@npm:^2.2.1": - version: 2.2.1 - resolution: "json5@npm:2.2.1" - bin: - json5: lib/cli.js - checksum: 74b8a23b102a6f2bf2d224797ae553a75488b5adbaee9c9b6e5ab8b510a2fc6e38f876d4c77dea672d4014a44b2399e15f2051ac2b37b87f74c0c7602003543b - languageName: node - linkType: hard - "json5@npm:^1.0.1": version: 1.0.1 resolution: "json5@npm:1.0.1" @@ -19613,6 +19600,15 @@ fsevents@^1.2.7: languageName: node linkType: hard +"json5@npm:^2.1.2, json5@npm:^2.2.0, json5@npm:^2.2.1": + version: 2.2.1 + resolution: "json5@npm:2.2.1" + bin: + json5: lib/cli.js + checksum: 74b8a23b102a6f2bf2d224797ae553a75488b5adbaee9c9b6e5ab8b510a2fc6e38f876d4c77dea672d4014a44b2399e15f2051ac2b37b87f74c0c7602003543b + languageName: node + linkType: hard + "json5@npm:^2.2.3": version: 2.2.3 resolution: "json5@npm:2.2.3" @@ -28222,39 +28218,6 @@ fsevents@^1.2.7: languageName: node linkType: hard -"ts-jest@npm:^27": - version: 27.1.5 - resolution: "ts-jest@npm:27.1.5" - dependencies: - bs-logger: 0.x - fast-json-stable-stringify: 2.x - jest-util: ^27.0.0 - json5: 2.x - lodash.memoize: 4.x - make-error: 1.x - semver: 7.x - yargs-parser: 20.x - peerDependencies: - "@babel/core": ">=7.0.0-beta.0 <8" - "@types/jest": ^27.0.0 - babel-jest: ">=27.0.0 <28" - jest: ^27.0.0 - typescript: ">=3.8 <5.0" - peerDependenciesMeta: - "@babel/core": - optional: true - "@types/jest": - optional: true - babel-jest: - optional: true - esbuild: - optional: true - bin: - ts-jest: cli.js - checksum: 3ef51c538b82f49b3f529331c1a017871a2f90e7a9a6e69333304755036d121818c6b120e2ce32dd161ff8bb2487efec0c790753ecd39b46a9ed1ce0d241464c - languageName: node - linkType: hard - "ts-jest@npm:^29": version: 29.0.5 resolution: "ts-jest@npm:29.0.5" @@ -30675,7 +30638,7 @@ fsevents@^1.2.7: languageName: node linkType: hard -"yargs-parser@npm:20.2.9, yargs-parser@npm:20.x, yargs-parser@npm:^20.2.2": +"yargs-parser@npm:20.2.9, yargs-parser@npm:^20.2.2": version: 20.2.9 resolution: "yargs-parser@npm:20.2.9" checksum: 8bb69015f2b0ff9e17b2c8e6bfe224ab463dd00ca211eece72a4cd8a906224d2703fb8a326d36fdd0e68701e201b2a60ed7cf81ce0fd9b3799f9fe7745977ae3 From 644f75714d10bccc9afc726519a5dc1e34c443bc Mon Sep 17 00:00:00 2001 From: Mark Erikson Date: Sun, 1 Oct 2023 20:58:17 -0400 Subject: [PATCH 298/412] Switch from Jest to Vitest in rtk-codemods --- packages/rtk-codemods/.eslintrc.js | 2 +- packages/rtk-codemods/jest.config.js | 6 -- packages/rtk-codemods/transformTestUtils.ts | 61 +++++++++++++++++++ .../createReducerBuilder.test.ts | 11 ++++ .../transforms/createReducerBuilder/test.js | 10 --- .../createSliceBuilder.test.ts | 6 ++ .../transforms/createSliceBuilder/test.js | 9 --- packages/rtk-codemods/vitest.config.ts | 13 ++++ 8 files changed, 92 insertions(+), 26 deletions(-) delete mode 100644 packages/rtk-codemods/jest.config.js create mode 100644 packages/rtk-codemods/transformTestUtils.ts create mode 100644 packages/rtk-codemods/transforms/createReducerBuilder/createReducerBuilder.test.ts delete mode 100644 packages/rtk-codemods/transforms/createReducerBuilder/test.js create mode 100644 packages/rtk-codemods/transforms/createSliceBuilder/createSliceBuilder.test.ts delete mode 100644 packages/rtk-codemods/transforms/createSliceBuilder/test.js create mode 100644 packages/rtk-codemods/vitest.config.ts diff --git a/packages/rtk-codemods/.eslintrc.js b/packages/rtk-codemods/.eslintrc.js index dc8ae1057e..475aa701d9 100644 --- a/packages/rtk-codemods/.eslintrc.js +++ b/packages/rtk-codemods/.eslintrc.js @@ -4,7 +4,7 @@ module.exports = { }, plugins: ['prettier', 'node'], - extends: ['eslint:recommended', 'plugin:prettier/recommended', 'plugin:node/recommended'], + extends: ['eslint:recommended', 'plugin:prettier/recommended'], env: { node: true, }, diff --git a/packages/rtk-codemods/jest.config.js b/packages/rtk-codemods/jest.config.js deleted file mode 100644 index 75b5631c2e..0000000000 --- a/packages/rtk-codemods/jest.config.js +++ /dev/null @@ -1,6 +0,0 @@ -module.exports = { - testEnvironment: 'node', - automock: false, - // roots: ['v2.0/__tests__'], - transform: { '\\.ts$': ['ts-jest'] }, -}; diff --git a/packages/rtk-codemods/transformTestUtils.ts b/packages/rtk-codemods/transformTestUtils.ts new file mode 100644 index 0000000000..b49314cc73 --- /dev/null +++ b/packages/rtk-codemods/transformTestUtils.ts @@ -0,0 +1,61 @@ +import { describe, vi } from 'vitest'; +import type { Transform } from 'jscodeshift'; +import globby from 'globby'; +import fs from 'fs-extra'; +import path from 'path'; +import { runInlineTest } from 'jscodeshift/dist/testUtils'; + +export function runTransformTest( + name: string, + transform: Transform, + parser: string, + fixturePath: string +) { + describe(name, function () { + globby + .sync('**/*.input.*', { + cwd: fixturePath, + absolute: true, + }) + .map((entry) => entry.slice(fixturePath.length)) + .forEach((filename) => { + let extension = path.extname(filename); + let testName = filename.replace(`.input${extension}`, ''); + let testInputPath = path.join(fixturePath, `${testName}${extension}`); + let inputPath = path.join(fixturePath, `${testName}.input${extension}`); + let outputPath = path.join(fixturePath, `${testName}.output${extension}`); + let optionsPath = path.join(fixturePath, `${testName}.options.json`); + let options = fs.pathExistsSync(optionsPath) ? fs.readFileSync(optionsPath) : '{}'; + + describe(testName, function () { + beforeEach(function () { + process.env.CODEMOD_CLI_ARGS = options; + }); + + afterEach(function () { + process.env.CODEMOD_CLI_ARGS = ''; + }); + + it('transforms correctly', function () { + runInlineTest( + transform, + {}, + { path: testInputPath, source: fs.readFileSync(inputPath, 'utf8') }, + fs.readFileSync(outputPath, 'utf8'), + { parser } + ); + }); + + it('is idempotent', function () { + runInlineTest( + transform, + {}, + { path: testInputPath, source: fs.readFileSync(outputPath, 'utf8') }, + fs.readFileSync(outputPath, 'utf8'), + { parser } + ); + }); + }); + }); + }); +} diff --git a/packages/rtk-codemods/transforms/createReducerBuilder/createReducerBuilder.test.ts b/packages/rtk-codemods/transforms/createReducerBuilder/createReducerBuilder.test.ts new file mode 100644 index 0000000000..2f1ca8f24f --- /dev/null +++ b/packages/rtk-codemods/transforms/createReducerBuilder/createReducerBuilder.test.ts @@ -0,0 +1,11 @@ +import path from 'path'; +import transform, { parser } from './index'; + +import { runTransformTest } from '../../transformTestUtils'; + +runTransformTest( + 'createReducerBuilder', + transform, + parser, + path.join(__dirname, '__testfixtures__') +); diff --git a/packages/rtk-codemods/transforms/createReducerBuilder/test.js b/packages/rtk-codemods/transforms/createReducerBuilder/test.js deleted file mode 100644 index 1d370a604d..0000000000 --- a/packages/rtk-codemods/transforms/createReducerBuilder/test.js +++ /dev/null @@ -1,10 +0,0 @@ -'use strict'; -require('ts-node').register(); - -const { runTransformTest } = require('codemod-cli'); - -runTransformTest({ - name: 'createReducerBuilder', - path: require.resolve('./index.ts'), - fixtureDir: `${__dirname}/__testfixtures__/`, -}); diff --git a/packages/rtk-codemods/transforms/createSliceBuilder/createSliceBuilder.test.ts b/packages/rtk-codemods/transforms/createSliceBuilder/createSliceBuilder.test.ts new file mode 100644 index 0000000000..348bc3d4c9 --- /dev/null +++ b/packages/rtk-codemods/transforms/createSliceBuilder/createSliceBuilder.test.ts @@ -0,0 +1,6 @@ +import path from 'path'; +import transform, { parser } from './index'; + +import { runTransformTest } from '../../transformTestUtils'; + +runTransformTest('createSliceBuilder', transform, parser, path.join(__dirname, '__testfixtures__')); diff --git a/packages/rtk-codemods/transforms/createSliceBuilder/test.js b/packages/rtk-codemods/transforms/createSliceBuilder/test.js deleted file mode 100644 index 930f4a6ec1..0000000000 --- a/packages/rtk-codemods/transforms/createSliceBuilder/test.js +++ /dev/null @@ -1,9 +0,0 @@ -'use strict'; - -const { runTransformTest } = require('codemod-cli'); - -runTransformTest({ - name: 'createSliceBuilder', - path: require.resolve('./index.ts'), - fixtureDir: `${__dirname}/__testfixtures__/`, -}); diff --git a/packages/rtk-codemods/vitest.config.ts b/packages/rtk-codemods/vitest.config.ts new file mode 100644 index 0000000000..b744c56463 --- /dev/null +++ b/packages/rtk-codemods/vitest.config.ts @@ -0,0 +1,13 @@ +import { defineConfig } from 'vitest/config'; + +export default defineConfig({ + test: { + globals: true, + setupFiles: [], + include: ['./transforms/**/*.(spec|test).[jt]s?(x)'], + alias: {}, + deps: { + interopDefault: true, + }, + }, +}); From fb1db6f24cdbb2dcfc763c8c30c5b49fbbc40256 Mon Sep 17 00:00:00 2001 From: Ben Durrant Date: Wed, 14 Jun 2023 01:03:35 +0100 Subject: [PATCH 299/412] write create callback codemod --- .../createSliceReducerBuilder/README.md | 26 +++ .../__testfixtures__/basic.input.js | 27 +++ .../__testfixtures__/basic.output.js | 18 ++ .../createSliceReducerBuilder/index.ts | 165 ++++++++++++++++++ .../createSliceReducerBuilder/test.js | 9 + 5 files changed, 245 insertions(+) create mode 100644 packages/rtk-codemods/transforms/createSliceReducerBuilder/README.md create mode 100644 packages/rtk-codemods/transforms/createSliceReducerBuilder/__testfixtures__/basic.input.js create mode 100644 packages/rtk-codemods/transforms/createSliceReducerBuilder/__testfixtures__/basic.output.js create mode 100644 packages/rtk-codemods/transforms/createSliceReducerBuilder/index.ts create mode 100644 packages/rtk-codemods/transforms/createSliceReducerBuilder/test.js diff --git a/packages/rtk-codemods/transforms/createSliceReducerBuilder/README.md b/packages/rtk-codemods/transforms/createSliceReducerBuilder/README.md new file mode 100644 index 0000000000..344dfe3c19 --- /dev/null +++ b/packages/rtk-codemods/transforms/createSliceReducerBuilder/README.md @@ -0,0 +1,26 @@ +# createSliceReducerBuilder + + +## Usage + +``` +npx @reduxjs/rtk-codemods createSliceReducerBuilder path/of/files/ or/some**/*glob.js + +# or + +yarn global add @reduxjs/rtk-codemods +@reduxjs/rtk-codemods createSliceReducerBuilder path/of/files/ or/some**/*glob.js +``` + +## Local Usage +``` +node ./bin/cli.js createSliceReducerBuilder path/of/files/ or/some**/*glob.js +``` + +## Input / Output + + + + + + \ No newline at end of file diff --git a/packages/rtk-codemods/transforms/createSliceReducerBuilder/__testfixtures__/basic.input.js b/packages/rtk-codemods/transforms/createSliceReducerBuilder/__testfixtures__/basic.input.js new file mode 100644 index 0000000000..2319c0e9f3 --- /dev/null +++ b/packages/rtk-codemods/transforms/createSliceReducerBuilder/__testfixtures__/basic.input.js @@ -0,0 +1,27 @@ +const aSlice = createSlice({ + name: 'name', + initialState: todoAdapter.getInitialState(), + reducers: { + property: () => {}, + method(state, action) { + todoAdapter.setMany(state, action); + }, + identifier: todoAdapter.removeOne, + preparedProperty: { + prepare: (todo) => ({ payload: { id: nanoid(), ...todo } }), + reducer: () => {} + }, + preparedMethod: { + prepare(todo) { + return { payload: { id: nanoid(), ...todo } } + }, + reducer(state, action) { + todoAdapter.setMany(state, action); + } + }, + preparedIdentifier: { + prepare: withPayload(), + reducer: todoAdapter.setMany + }, + } +}) \ No newline at end of file diff --git a/packages/rtk-codemods/transforms/createSliceReducerBuilder/__testfixtures__/basic.output.js b/packages/rtk-codemods/transforms/createSliceReducerBuilder/__testfixtures__/basic.output.js new file mode 100644 index 0000000000..52d95cee77 --- /dev/null +++ b/packages/rtk-codemods/transforms/createSliceReducerBuilder/__testfixtures__/basic.output.js @@ -0,0 +1,18 @@ +const aSlice = createSlice({ + name: 'name', + initialState: todoAdapter.getInitialState(), + reducers: (create) => ({ + property: create.reducer(() => {}), + method: create.reducer((state, action) => { + todoAdapter.setMany(state, action); + }), + identifier: create.reducer(todoAdapter.removeOne), + preparedProperty: create.preparedReducer((todo) => ({ payload: { id: nanoid(), ...todo } }), () => {}), + preparedMethod: create.preparedReducer((todo) => { + return { payload: { id: nanoid(), ...todo } } + }, (state, action) => { + todoAdapter.setMany(state, action); + }), + preparedIdentifier: create.preparedReducer(withPayload(), todoAdapter.setMany) + }) +}) \ No newline at end of file diff --git a/packages/rtk-codemods/transforms/createSliceReducerBuilder/index.ts b/packages/rtk-codemods/transforms/createSliceReducerBuilder/index.ts new file mode 100644 index 0000000000..45ef8afca0 --- /dev/null +++ b/packages/rtk-codemods/transforms/createSliceReducerBuilder/index.ts @@ -0,0 +1,165 @@ +/* eslint-disable node/no-extraneous-import */ +/* eslint-disable node/no-unsupported-features/es-syntax */ +import type { ExpressionKind, SpreadElementKind } from 'ast-types/gen/kinds'; +import type { JSCodeshift, ObjectExpression, ObjectProperty, Transform } from 'jscodeshift'; + +function creatorCall( + j: JSCodeshift, + type: 'reducer' | 'preparedReducer', + argumentsParam: Array +) { + return j.callExpression( + j.memberExpression(j.identifier('create'), j.identifier(type)), + argumentsParam + ); +} + +export function reducerPropsToBuilderExpression(j: JSCodeshift, defNode: ObjectExpression) { + const returnedObject = j.objectExpression([]); + for (let property of defNode.properties) { + let finalProp: ObjectProperty | undefined; + switch (property.type) { + case 'ObjectMethod': { + const { key, params, body } = property; + finalProp = j.objectProperty( + key, + creatorCall(j, 'reducer', [j.arrowFunctionExpression(params, body)]) + ); + break; + } + case 'ObjectProperty': { + const { key } = property; + + switch (property.value.type) { + case 'ObjectExpression': { + let preparedReducerParams: { prepare?: ExpressionKind; reducer?: ExpressionKind } = {}; + + for (const objProp of property.value.properties) { + switch (objProp.type) { + case 'ObjectMethod': { + const { key, params, body } = objProp; + if ( + key.type === 'Identifier' && + (key.name === 'reducer' || key.name === 'prepare') + ) { + preparedReducerParams[key.name] = j.arrowFunctionExpression(params, body); + } + break; + } + case 'ObjectProperty': { + const { key, value } = objProp; + + let finalExpression: ExpressionKind | undefined = undefined; + + switch (value.type) { + case 'ArrowFunctionExpression': + case 'FunctionExpression': + case 'Identifier': + case 'MemberExpression': + case 'CallExpression': { + finalExpression = value; + } + } + + if ( + key.type === 'Identifier' && + (key.name === 'reducer' || key.name === 'prepare') && + finalExpression + ) { + preparedReducerParams[key.name] = finalExpression; + } + break; + } + } + } + + if (preparedReducerParams.prepare && preparedReducerParams.reducer) { + finalProp = j.objectProperty( + key, + creatorCall(j, 'preparedReducer', [ + preparedReducerParams.prepare, + preparedReducerParams.reducer, + ]) + ); + } else if (preparedReducerParams.reducer) { + finalProp = j.objectProperty( + key, + creatorCall(j, 'reducer', [preparedReducerParams.reducer]) + ); + } + break; + } + case 'ArrowFunctionExpression': + case 'FunctionExpression': + case 'Identifier': + case 'MemberExpression': + case 'CallExpression': { + const { value } = property; + finalProp = j.objectProperty(key, creatorCall(j, 'reducer', [value])); + break; + } + } + break; + } + } + if (!finalProp) { + continue; + } + returnedObject.properties.push(finalProp); + } + + return j.arrowFunctionExpression([j.identifier('create')], returnedObject, true); +} + +const transform: Transform = (file, api) => { + const j = api.jscodeshift; + + return ( + j(file.source) + // @ts-ignore some expression mismatch + .find(j.CallExpression, { + callee: { name: 'createSlice' }, + // @ts-ignore some expression mismatch + arguments: { 0: { type: 'ObjectExpression' } }, + }) + + .filter((path) => { + const createSliceArgsObject = path.node.arguments[0] as ObjectExpression; + return createSliceArgsObject.properties.some( + (p) => + p.type === 'ObjectProperty' && + p.key.type === 'Identifier' && + p.key.name === 'reducers' && + p.value.type === 'ObjectExpression' + ); + }) + .forEach((path) => { + const createSliceArgsObject = path.node.arguments[0] as ObjectExpression; + j(path).replaceWith( + j.callExpression(j.identifier('createSlice'), [ + j.objectExpression( + createSliceArgsObject.properties.map((p) => { + if ( + p.type === 'ObjectProperty' && + p.key.type === 'Identifier' && + p.key.name === 'reducers' && + p.value.type === 'ObjectExpression' + ) { + const expressionStatement = reducerPropsToBuilderExpression(j, p.value); + return j.objectProperty(p.key, expressionStatement); + } + return p; + }) + ), + ]) + ); + }) + .toSource({ + arrowParensAlways: true, + }) + ); +}; + +export const parser = 'tsx'; + +export default transform; diff --git a/packages/rtk-codemods/transforms/createSliceReducerBuilder/test.js b/packages/rtk-codemods/transforms/createSliceReducerBuilder/test.js new file mode 100644 index 0000000000..5e34770a22 --- /dev/null +++ b/packages/rtk-codemods/transforms/createSliceReducerBuilder/test.js @@ -0,0 +1,9 @@ +'use strict'; + +const { runTransformTest } = require('codemod-cli'); + +runTransformTest({ + name: 'createSliceReducerBuilder', + path: require.resolve('./index.ts'), + fixtureDir: `${__dirname}/__testfixtures__/`, +}); From 36188f6885e5511b1bde1e6d0ecb8daa2d72a261 Mon Sep 17 00:00:00 2001 From: Ben Durrant Date: Wed, 14 Jun 2023 01:10:07 +0100 Subject: [PATCH 300/412] use overloads for better type safety --- .../createSliceReducerBuilder/index.ts | 38 +++++++++++++------ 1 file changed, 26 insertions(+), 12 deletions(-) diff --git a/packages/rtk-codemods/transforms/createSliceReducerBuilder/index.ts b/packages/rtk-codemods/transforms/createSliceReducerBuilder/index.ts index 45ef8afca0..549a50e3da 100644 --- a/packages/rtk-codemods/transforms/createSliceReducerBuilder/index.ts +++ b/packages/rtk-codemods/transforms/createSliceReducerBuilder/index.ts @@ -1,17 +1,29 @@ /* eslint-disable node/no-extraneous-import */ /* eslint-disable node/no-unsupported-features/es-syntax */ import type { ExpressionKind, SpreadElementKind } from 'ast-types/gen/kinds'; -import type { JSCodeshift, ObjectExpression, ObjectProperty, Transform } from 'jscodeshift'; +import type { + CallExpression, + JSCodeshift, + ObjectExpression, + ObjectProperty, + Transform, +} from 'jscodeshift'; +function creatorCall(j: JSCodeshift, type: 'reducer', reducer: ExpressionKind): CallExpression; +// eslint-disable-next-line no-redeclare +function creatorCall( + j: JSCodeshift, + type: 'preparedReducer', + prepare: ExpressionKind, + reducer: ExpressionKind +): CallExpression; +// eslint-disable-next-line no-redeclare function creatorCall( j: JSCodeshift, type: 'reducer' | 'preparedReducer', - argumentsParam: Array + ...rest: Array ) { - return j.callExpression( - j.memberExpression(j.identifier('create'), j.identifier(type)), - argumentsParam - ); + return j.callExpression(j.memberExpression(j.identifier('create'), j.identifier(type)), rest); } export function reducerPropsToBuilderExpression(j: JSCodeshift, defNode: ObjectExpression) { @@ -23,7 +35,7 @@ export function reducerPropsToBuilderExpression(j: JSCodeshift, defNode: ObjectE const { key, params, body } = property; finalProp = j.objectProperty( key, - creatorCall(j, 'reducer', [j.arrowFunctionExpression(params, body)]) + creatorCall(j, 'reducer', j.arrowFunctionExpression(params, body)) ); break; } @@ -76,15 +88,17 @@ export function reducerPropsToBuilderExpression(j: JSCodeshift, defNode: ObjectE if (preparedReducerParams.prepare && preparedReducerParams.reducer) { finalProp = j.objectProperty( key, - creatorCall(j, 'preparedReducer', [ + creatorCall( + j, + 'preparedReducer', preparedReducerParams.prepare, - preparedReducerParams.reducer, - ]) + preparedReducerParams.reducer + ) ); } else if (preparedReducerParams.reducer) { finalProp = j.objectProperty( key, - creatorCall(j, 'reducer', [preparedReducerParams.reducer]) + creatorCall(j, 'reducer', preparedReducerParams.reducer) ); } break; @@ -95,7 +109,7 @@ export function reducerPropsToBuilderExpression(j: JSCodeshift, defNode: ObjectE case 'MemberExpression': case 'CallExpression': { const { value } = property; - finalProp = j.objectProperty(key, creatorCall(j, 'reducer', [value])); + finalProp = j.objectProperty(key, creatorCall(j, 'reducer', value)); break; } } From 60997d9100732e7d3563bdab30aee3cba31a5011 Mon Sep 17 00:00:00 2001 From: Ben Durrant Date: Wed, 14 Jun 2023 01:22:22 +0100 Subject: [PATCH 301/412] match jscodeshift spacing --- .../__testfixtures__/basic.output.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/packages/rtk-codemods/transforms/createSliceReducerBuilder/__testfixtures__/basic.output.js b/packages/rtk-codemods/transforms/createSliceReducerBuilder/__testfixtures__/basic.output.js index 52d95cee77..24d6672386 100644 --- a/packages/rtk-codemods/transforms/createSliceReducerBuilder/__testfixtures__/basic.output.js +++ b/packages/rtk-codemods/transforms/createSliceReducerBuilder/__testfixtures__/basic.output.js @@ -1,18 +1,23 @@ const aSlice = createSlice({ name: 'name', initialState: todoAdapter.getInitialState(), + reducers: (create) => ({ property: create.reducer(() => {}), + method: create.reducer((state, action) => { todoAdapter.setMany(state, action); }), + identifier: create.reducer(todoAdapter.removeOne), preparedProperty: create.preparedReducer((todo) => ({ payload: { id: nanoid(), ...todo } }), () => {}), + preparedMethod: create.preparedReducer((todo) => { return { payload: { id: nanoid(), ...todo } } }, (state, action) => { todoAdapter.setMany(state, action); }), + preparedIdentifier: create.preparedReducer(withPayload(), todoAdapter.setMany) }) }) \ No newline at end of file From 4f46686e913c0101bdf8b38c97630a49ff29d60b Mon Sep 17 00:00:00 2001 From: Mark Erikson Date: Sun, 1 Oct 2023 21:21:38 -0400 Subject: [PATCH 302/412] Actually add vitest as a dep --- packages/rtk-codemods/package.json | 3 ++- yarn.lock | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/rtk-codemods/package.json b/packages/rtk-codemods/package.json index 84c528e3e9..211f56278c 100644 --- a/packages/rtk-codemods/package.json +++ b/packages/rtk-codemods/package.json @@ -31,7 +31,8 @@ "eslint-config-prettier": "^8.3.0", "eslint-plugin-node": "^11.1.0", "eslint-plugin-prettier": "^3.4.0", - "prettier": "^2.2.1" + "prettier": "^2.2.1", + "vitest": "^0.30.1" }, "engines": { "node": ">= 16" diff --git a/yarn.lock b/yarn.lock index c7444afada..6358e246e3 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6934,6 +6934,7 @@ __metadata: eslint-plugin-prettier: ^3.4.0 prettier: ^2.2.1 typescript: ^4.8.0 + vitest: ^0.30.1 bin: rtk-codemods: ./bin/cli.js languageName: unknown From 53e6b2b3c97485143a5830dce7f4ae425f4473ac Mon Sep 17 00:00:00 2001 From: Mark Erikson Date: Sun, 1 Oct 2023 21:26:14 -0400 Subject: [PATCH 303/412] Add createSliceReducerBuild Vitest setup and TS transform test --- .../createSliceReducerBuilder/README.md | 8 +++++- .../__testfixtures__/basic-ts.input.ts | 27 +++++++++++++++++++ .../__testfixtures__/basic-ts.output.ts | 23 ++++++++++++++++ .../createSliceReducerBuilder.test.ts | 11 ++++++++ 4 files changed, 68 insertions(+), 1 deletion(-) create mode 100644 packages/rtk-codemods/transforms/createSliceReducerBuilder/__testfixtures__/basic-ts.input.ts create mode 100644 packages/rtk-codemods/transforms/createSliceReducerBuilder/__testfixtures__/basic-ts.output.ts create mode 100644 packages/rtk-codemods/transforms/createSliceReducerBuilder/createSliceReducerBuilder.test.ts diff --git a/packages/rtk-codemods/transforms/createSliceReducerBuilder/README.md b/packages/rtk-codemods/transforms/createSliceReducerBuilder/README.md index 344dfe3c19..cafb0c9d48 100644 --- a/packages/rtk-codemods/transforms/createSliceReducerBuilder/README.md +++ b/packages/rtk-codemods/transforms/createSliceReducerBuilder/README.md @@ -1,5 +1,10 @@ # createSliceReducerBuilder +Rewrites uses of Redux Toolkit's `createSlice` API to use the "builder callback" syntax for the `reducers` field, to make it easier to add prepared reducers and thunks inside of `createSlice`. + +Note that unlike the `createReducerBuilder` and `createSliceBuilder` transforms (which both were fixes for deprecated/removed overloads), this is entirely optional. You do not _need_ to apply this to an entire codebase unless you specifically want to. Otherwise, feel free to apply to to specific slice files as needed. + +Should work with both JS and TS files. ## Usage @@ -13,6 +18,7 @@ yarn global add @reduxjs/rtk-codemods ``` ## Local Usage + ``` node ./bin/cli.js createSliceReducerBuilder path/of/files/ or/some**/*glob.js ``` @@ -23,4 +29,4 @@ node ./bin/cli.js createSliceReducerBuilder path/of/files/ or/some**/*glob.js - \ No newline at end of file + diff --git a/packages/rtk-codemods/transforms/createSliceReducerBuilder/__testfixtures__/basic-ts.input.ts b/packages/rtk-codemods/transforms/createSliceReducerBuilder/__testfixtures__/basic-ts.input.ts new file mode 100644 index 0000000000..80614bfb8c --- /dev/null +++ b/packages/rtk-codemods/transforms/createSliceReducerBuilder/__testfixtures__/basic-ts.input.ts @@ -0,0 +1,27 @@ +const aSlice = createSlice({ + name: 'name', + initialState: todoAdapter.getInitialState(), + reducers: { + property: () => {}, + method(state, action: PayloadAction) { + todoAdapter.addOne(state, action); + }, + identifier: todoAdapter.removeOne, + preparedProperty: { + prepare: (todo: Todo) => ({ payload: { id: nanoid(), ...todo } }), + reducer: () => {} + }, + preparedMethod: { + prepare(todo: Todo) { + return { payload: { id: nanoid(), ...todo } } + }, + reducer(state, action: PayloadAction) { + todoAdapter.addOne(state, action); + } + }, + preparedIdentifier: { + prepare: withPayload(), + reducer: todoAdapter.setMany + }, + } +}) \ No newline at end of file diff --git a/packages/rtk-codemods/transforms/createSliceReducerBuilder/__testfixtures__/basic-ts.output.ts b/packages/rtk-codemods/transforms/createSliceReducerBuilder/__testfixtures__/basic-ts.output.ts new file mode 100644 index 0000000000..b84f139846 --- /dev/null +++ b/packages/rtk-codemods/transforms/createSliceReducerBuilder/__testfixtures__/basic-ts.output.ts @@ -0,0 +1,23 @@ +const aSlice = createSlice({ + name: 'name', + initialState: todoAdapter.getInitialState(), + + reducers: (create) => ({ + property: create.reducer(() => {}), + + method: create.reducer((state, action: PayloadAction) => { + todoAdapter.addOne(state, action); + }), + + identifier: create.reducer(todoAdapter.removeOne), + preparedProperty: create.preparedReducer((todo: Todo) => ({ payload: { id: nanoid(), ...todo } }), () => {}), + + preparedMethod: create.preparedReducer((todo: Todo) => { + return { payload: { id: nanoid(), ...todo } } + }, (state, action: PayloadAction) => { + todoAdapter.addOne(state, action); + }), + + preparedIdentifier: create.preparedReducer(withPayload(), todoAdapter.setMany) + }) +}) \ No newline at end of file diff --git a/packages/rtk-codemods/transforms/createSliceReducerBuilder/createSliceReducerBuilder.test.ts b/packages/rtk-codemods/transforms/createSliceReducerBuilder/createSliceReducerBuilder.test.ts new file mode 100644 index 0000000000..b283f1d97c --- /dev/null +++ b/packages/rtk-codemods/transforms/createSliceReducerBuilder/createSliceReducerBuilder.test.ts @@ -0,0 +1,11 @@ +import path from 'path'; +import transform, { parser } from './index'; + +import { runTransformTest } from '../../transformTestUtils'; + +runTransformTest( + 'createSliceReducerBuilder', + transform, + parser, + path.join(__dirname, '__testfixtures__') +); From de564e12dfa1e72739181000d1e34db849ef48ac Mon Sep 17 00:00:00 2001 From: Mark Erikson Date: Sun, 1 Oct 2023 21:35:27 -0400 Subject: [PATCH 304/412] Tweak codemods docs --- docs/api/codemods.mdx | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/docs/api/codemods.mdx b/docs/api/codemods.mdx index ea81c8b464..912bda223a 100644 --- a/docs/api/codemods.mdx +++ b/docs/api/codemods.mdx @@ -9,11 +9,15 @@ hide_title: true # Codemods -Per [the description in `1.9.0-alpha.0`](https://github.com/reduxjs/redux-toolkit/releases/tag/v1.9.0-alpha.0), we plan to remove the "object" argument from `createReducer` and `createSlice.extraReducers` in the future RTK 2.0 major version. In `1.9.0-alpha.0`, we added a one-shot runtime warning to each of those APIs. +Per [the description in `1.9.0`](https://github.com/reduxjs/redux-toolkit/releases/tag/v1.9.0), we have removed the "object" argument from `createReducer` and `createSlice.extraReducers` in the RTK 2.0 major version. We've also added a new optional form of `createSlice.reducers` that uses a callback instead of an object. To simplify upgrading codebases, we've published a set of codemods that will automatically transform the deprecated "object" syntax into the equivalent "builder" syntax. -The codemods package is available on NPM as [**`@reduxjs/rtk-codemods`**](https://www.npmjs.com/package/@reduxjs/rtk-codemods). It currently contains two codemods: `createReducerBuilder` and `createSliceBuilder`. +The codemods package is available on NPM as [**`@reduxjs/rtk-codemods`**](https://www.npmjs.com/package/@reduxjs/rtk-codemods). It currently contains these codemods: + +- `createReducerBuilder`: migrates `createReducer` calls that use the removed object syntax to the builder callback syntax +- `createSliceBuilder`: migrates `createSlice` calls that use the removed object syntax for `extraReducers` to the builder callback syntax +- `createSliceReducerBuilder`: migrates `createSlice` calls that use the still-standard object syntax for `reducers` to the optional new builder callback syntax, including uses of prepared reducers To run the codemods against your codebase, run `npx @reduxjs/rtk-codemods path/of/files/ or/some**/*glob.js`. From dc2d1254104eea8d75a492be01ca6be7efcc87a3 Mon Sep 17 00:00:00 2001 From: "ben.durrant" Date: Mon, 2 Oct 2023 15:18:33 +0100 Subject: [PATCH 305/412] Rewrite HooksWithUniqueNames type --- .../toolkit/src/query/react/namedHooks.ts | 50 ++++++++++--------- 1 file changed, 27 insertions(+), 23 deletions(-) diff --git a/packages/toolkit/src/query/react/namedHooks.ts b/packages/toolkit/src/query/react/namedHooks.ts index ae146cf4cf..e91b405036 100644 --- a/packages/toolkit/src/query/react/namedHooks.ts +++ b/packages/toolkit/src/query/react/namedHooks.ts @@ -6,26 +6,30 @@ import type { QueryDefinition, } from '@reduxjs/toolkit/query' -export type HooksWithUniqueNames = - keyof Definitions extends infer Keys - ? Keys extends string - ? Definitions[Keys] extends { type: DefinitionType.query } - ? { - [K in Keys as `use${Capitalize}Query`]: UseQuery< - Extract> - > - } & - { - [K in Keys as `useLazy${Capitalize}Query`]: UseLazyQuery< - Extract> - > - } - : Definitions[Keys] extends { type: DefinitionType.mutation } - ? { - [K in Keys as `use${Capitalize}Mutation`]: UseMutation< - Extract> - > - } - : never - : never - : never +export type HooksWithUniqueNames = { + [K in keyof Definitions as Definitions[K] extends { + type: DefinitionType.query + } + ? `use${Capitalize}Query` + : never]: UseQuery< + Extract> + > +} & + { + [K in keyof Definitions as Definitions[K] extends { + type: DefinitionType.query + } + ? `useLazy${Capitalize}Query` + : never]: UseLazyQuery< + Extract> + > + } & + { + [K in keyof Definitions as Definitions[K] extends { + type: DefinitionType.mutation + } + ? `use${Capitalize}Mutation` + : never]: UseMutation< + Extract> + > + } From 9dec05867665e69e5f6fbcbb23959e223a1185d2 Mon Sep 17 00:00:00 2001 From: "ben.durrant" Date: Mon, 2 Oct 2023 16:07:06 +0100 Subject: [PATCH 306/412] Add settled matcher for createAsyncThunk --- packages/toolkit/src/createAsyncThunk.ts | 18 ++++++++- packages/toolkit/src/createSlice.ts | 34 +++++++++++++--- .../src/tests/createAsyncThunk.test.ts | 17 +++++++- .../toolkit/src/tests/createSlice.typetest.ts | 40 ++++++++++++++++++- 4 files changed, 101 insertions(+), 8 deletions(-) diff --git a/packages/toolkit/src/createAsyncThunk.ts b/packages/toolkit/src/createAsyncThunk.ts index c21e761f42..e8a28cbbb3 100644 --- a/packages/toolkit/src/createAsyncThunk.ts +++ b/packages/toolkit/src/createAsyncThunk.ts @@ -5,8 +5,16 @@ import type { } from './createAction' import { createAction } from './createAction' import type { ThunkDispatch } from 'redux-thunk' -import type { FallbackIfUnknown, Id, IsAny, IsUnknown } from './tsHelpers' +import type { + ActionFromMatcher, + FallbackIfUnknown, + Id, + IsAny, + IsUnknown, + TypeGuard, +} from './tsHelpers' import { nanoid } from './nanoid' +import { isAnyOf } from './matchers' // @ts-ignore we need the import of these types due to a bundling issue. type _Keep = PayloadAction | ActionCreatorWithPreparedPayload @@ -415,6 +423,13 @@ export type AsyncThunk< ThunkArg, ThunkApiConfig > + // matchSettled? + settled: ( + action: any + ) => action is ReturnType< + | AsyncThunkRejectedActionCreator + | AsyncThunkFulfilledActionCreator + > typePrefix: string } @@ -676,6 +691,7 @@ export const createAsyncThunk = (() => { pending, rejected, fulfilled, + settled: isAnyOf(rejected, fulfilled), typePrefix, } ) diff --git a/packages/toolkit/src/createSlice.ts b/packages/toolkit/src/createSlice.ts index e102e3dfbd..ec1128b8a6 100644 --- a/packages/toolkit/src/createSlice.ts +++ b/packages/toolkit/src/createSlice.ts @@ -7,11 +7,15 @@ import type { _ActionCreatorWithPreparedPayload, } from './createAction' import { createAction } from './createAction' -import type { CaseReducer, ReducerWithInitialState } from './createReducer' +import type { + ActionMatcherDescriptionCollection, + CaseReducer, + ReducerWithInitialState, +} from './createReducer' import { createReducer } from './createReducer' import type { ActionReducerMapBuilder } from './mapBuilders' import { executeReducerBuilderCallback } from './mapBuilders' -import type { Id, Tail } from './tsHelpers' +import type { ActionFromMatcher, Id, Matcher, Tail } from './tsHelpers' import type { InjectConfig } from './combineSlices' import type { AsyncThunk, @@ -283,6 +287,12 @@ export interface AsyncThunkSliceReducerConfig< State, ReturnType['fulfilled']> > + settled?: CaseReducer< + State, + ReturnType< + AsyncThunk['rejected' | 'fulfilled'] + > + > options?: AsyncThunkOptions } @@ -483,7 +493,12 @@ type ActionCreatorForCaseReducer = CR extends ( type SliceDefinedCaseReducers> = { [Type in keyof CaseReducers]: CaseReducers[Type] extends infer Definition ? Definition extends AsyncThunkSliceReducerDefinition - ? Id, 'fulfilled' | 'rejected' | 'pending'>> + ? Id< + Pick< + Required, + 'fulfilled' | 'rejected' | 'pending' | 'settled' + > + > : Definition extends { reducer: infer Reducer } @@ -582,6 +597,7 @@ export function createSlice< sliceCaseReducersByName: {}, sliceCaseReducersByType: {}, actionCreators: {}, + sliceMatchers: [], } reducerNames.forEach((reducerName) => { @@ -632,6 +648,9 @@ export function createSlice< for (let key in finalCaseReducers) { builder.addCase(key, finalCaseReducers[key] as CaseReducer) } + for (let sM of context.sliceMatchers) { + builder.addMatcher(sM.matcher, sM.reducer) + } for (let m of actionMatchers) { builder.addMatcher(m.matcher, m.reducer) } @@ -728,10 +747,11 @@ interface ReducerHandlingContext { | CaseReducer | Pick< AsyncThunkSliceReducerDefinition, - 'fulfilled' | 'rejected' | 'pending' + 'fulfilled' | 'rejected' | 'pending' | 'settled' > > sliceCaseReducersByType: Record> + sliceMatchers: ActionMatcherDescriptionCollection actionCreators: Record } @@ -828,7 +848,7 @@ function handleThunkCaseReducerDefinition( reducerDefinition: AsyncThunkSliceReducerDefinition, context: ReducerHandlingContext ) { - const { payloadCreator, fulfilled, pending, rejected, options } = + const { payloadCreator, fulfilled, pending, rejected, settled, options } = reducerDefinition const thunk = createAsyncThunk(type, payloadCreator, options as any) context.actionCreators[reducerName] = thunk @@ -842,11 +862,15 @@ function handleThunkCaseReducerDefinition( if (rejected) { context.sliceCaseReducersByType[thunk.rejected.type] = rejected } + if (settled) { + context.sliceMatchers.push({ matcher: thunk.settled, reducer: settled }) + } context.sliceCaseReducersByName[reducerName] = { fulfilled: fulfilled || noop, pending: pending || noop, rejected: rejected || noop, + settled: settled || noop, } } diff --git a/packages/toolkit/src/tests/createAsyncThunk.test.ts b/packages/toolkit/src/tests/createAsyncThunk.test.ts index 8f5dfc0a5e..f57b4fe45c 100644 --- a/packages/toolkit/src/tests/createAsyncThunk.test.ts +++ b/packages/toolkit/src/tests/createAsyncThunk.test.ts @@ -37,6 +37,20 @@ describe('createAsyncThunk', () => { expect(thunkActionCreator.typePrefix).toBe('testType') }) + it('includes a settled matcher', () => { + const thunkActionCreator = createAsyncThunk('testType', async () => 42) + expect(thunkActionCreator.settled).toEqual(expect.any(Function)) + expect(thunkActionCreator.settled(thunkActionCreator.pending(''))).toBe( + false + ) + expect( + thunkActionCreator.settled(thunkActionCreator.rejected(null, '')) + ).toBe(true) + expect( + thunkActionCreator.settled(thunkActionCreator.fulfilled(42, '')) + ).toBe(true) + }) + it('works without passing arguments to the payload creator', async () => { const thunkActionCreator = createAsyncThunk('testType', async () => 42) @@ -513,7 +527,7 @@ describe('createAsyncThunk with abortController', () => { } ) - expect(longRunningAsyncThunk()).toThrow("AbortController is not defined") + expect(longRunningAsyncThunk()).toThrow('AbortController is not defined') }) }) }) @@ -975,6 +989,7 @@ describe('meta', () => { expect(thunk.fulfilled).toEqual(expectFunction) expect(thunk.pending).toEqual(expectFunction) expect(thunk.rejected).toEqual(expectFunction) + expect(thunk.settled).toEqual(expectFunction) expect(thunk.fulfilled.type).toBe('a/fulfilled') }) }) diff --git a/packages/toolkit/src/tests/createSlice.typetest.ts b/packages/toolkit/src/tests/createSlice.typetest.ts index 542ab20f83..76d088d093 100644 --- a/packages/toolkit/src/tests/createSlice.typetest.ts +++ b/packages/toolkit/src/tests/createSlice.typetest.ts @@ -16,7 +16,7 @@ import type { ThunkDispatch, ValidateSliceCaseReducers, } from '@reduxjs/toolkit' -import { configureStore } from '@reduxjs/toolkit' +import { configureStore, isRejected } from '@reduxjs/toolkit' import { createAction, createSlice } from '@reduxjs/toolkit' import { expectExactType, expectType, expectUnknown } from './helpers' import { castDraft } from 'immer' @@ -642,6 +642,18 @@ const value = actionCreators.anyKey expectType(action.meta.arg) expectType(action.error) }, + settled(state, action) { + expectType(state) + if (isRejected(action)) { + expectType(state) + expectType(action.meta.arg) + expectType(action.error) + } else { + expectType(state) + expectType(action.meta.arg) + expectType(action.payload) + } + }, } ), testExplicitType: create.asyncThunk< @@ -679,6 +691,19 @@ const value = actionCreators.anyKey expectType(action.error) expectType(action.payload) }, + settled(state, action) { + expectType(state) + if (isRejected(action)) { + expectType(state) + expectType(action.meta.arg) + expectType(action.error) + expectType(action.payload) + } else { + expectType(state) + expectType(action.meta.arg) + expectType(action.payload) + } + }, } ), testPretyped: pretypedAsyncThunk( @@ -702,6 +727,19 @@ const value = actionCreators.anyKey expectType(action.error) expectType(action.payload) }, + settled(state, action) { + expectType(state) + if (isRejected(action)) { + expectType(state) + expectType(action.meta.arg) + expectType(action.error) + expectType(action.payload) + } else { + expectType(state) + expectType(action.meta.arg) + expectType(action.payload) + } + }, } ), } From e3193420a3d250f9872d7b28d5d929023fc2450e Mon Sep 17 00:00:00 2001 From: "ben.durrant" Date: Mon, 2 Oct 2023 16:21:59 +0100 Subject: [PATCH 307/412] add settled tests --- .../toolkit/src/tests/createSlice.test.ts | 33 +++++++++++++++++-- 1 file changed, 30 insertions(+), 3 deletions(-) diff --git a/packages/toolkit/src/tests/createSlice.test.ts b/packages/toolkit/src/tests/createSlice.test.ts index 3ecae681e9..302c06c9c9 100644 --- a/packages/toolkit/src/tests/createSlice.test.ts +++ b/packages/toolkit/src/tests/createSlice.test.ts @@ -576,6 +576,9 @@ describe('createSlice', () => { function rejected(state: any[], action: any) { state.push(['rejectedReducer', action]) } + function settled(state: any[], action: any) { + state.push(['settledReducer', action]) + } test('successful thunk', async () => { const slice = createSlice({ @@ -586,7 +589,7 @@ describe('createSlice', () => { function payloadCreator(arg, api) { return Promise.resolve('resolved payload') }, - { pending, fulfilled, rejected } + { pending, fulfilled, rejected, settled } ), }), }) @@ -610,6 +613,13 @@ describe('createSlice', () => { payload: 'resolved payload', }, ], + [ + 'settledReducer', + { + type: 'test/thunkReducers/fulfilled', + payload: 'resolved payload', + }, + ], ]) }) @@ -623,7 +633,7 @@ describe('createSlice', () => { function payloadCreator(arg, api): any { throw new Error('') }, - { pending, fulfilled, rejected } + { pending, fulfilled, rejected, settled } ), }), }) @@ -647,6 +657,13 @@ describe('createSlice', () => { payload: undefined, }, ], + [ + 'settledReducer', + { + type: 'test/thunkReducers/rejected', + payload: undefined, + }, + ], ]) }) @@ -669,6 +686,7 @@ describe('createSlice', () => { pending, fulfilled, rejected, + settled, } ), }), @@ -687,6 +705,14 @@ describe('createSlice', () => { meta: { condition: true }, }, ], + [ + 'settledReducer', + { + type: 'test/thunkReducers/rejected', + payload: undefined, + meta: { condition: true }, + }, + ], ]) }) @@ -699,13 +725,14 @@ describe('createSlice', () => { function payloadCreator(arg, api) { return Promise.resolve('resolved payload') }, - { pending, fulfilled } + { pending, fulfilled, settled } ), }), }) expect(slice.caseReducers.thunkReducers.pending).toBe(pending) expect(slice.caseReducers.thunkReducers.fulfilled).toBe(fulfilled) + expect(slice.caseReducers.thunkReducers.settled).toBe(settled) // even though it is not defined above, this should at least be a no-op function to match the TypeScript typings // and should be callable as a reducer even if it does nothing expect(() => From 39b4a301810c819830a3c095fb0de410789c7783 Mon Sep 17 00:00:00 2001 From: "ben.durrant" Date: Mon, 2 Oct 2023 16:36:24 +0100 Subject: [PATCH 308/412] tidy up typetest --- .../toolkit/src/tests/createSlice.typetest.ts | 15 +++------------ 1 file changed, 3 insertions(+), 12 deletions(-) diff --git a/packages/toolkit/src/tests/createSlice.typetest.ts b/packages/toolkit/src/tests/createSlice.typetest.ts index 76d088d093..9cd6371f0f 100644 --- a/packages/toolkit/src/tests/createSlice.typetest.ts +++ b/packages/toolkit/src/tests/createSlice.typetest.ts @@ -644,13 +644,10 @@ const value = actionCreators.anyKey }, settled(state, action) { expectType(state) + expectType(action.meta.arg) if (isRejected(action)) { - expectType(state) - expectType(action.meta.arg) expectType(action.error) } else { - expectType(state) - expectType(action.meta.arg) expectType(action.payload) } }, @@ -693,14 +690,11 @@ const value = actionCreators.anyKey }, settled(state, action) { expectType(state) + expectType(action.meta.arg) if (isRejected(action)) { - expectType(state) - expectType(action.meta.arg) expectType(action.error) expectType(action.payload) } else { - expectType(state) - expectType(action.meta.arg) expectType(action.payload) } }, @@ -729,14 +723,11 @@ const value = actionCreators.anyKey }, settled(state, action) { expectType(state) + expectType(action.meta.arg) if (isRejected(action)) { - expectType(state) - expectType(action.meta.arg) expectType(action.error) expectType(action.payload) } else { - expectType(state) - expectType(action.meta.arg) expectType(action.payload) } }, From 3e370a3f6c9178f4526a157f6f4f6997d28c551c Mon Sep 17 00:00:00 2001 From: Ben Durrant Date: Mon, 2 Oct 2023 22:53:59 +0100 Subject: [PATCH 309/412] copy over split type --- .../toolkit/src/query/react/namedHooks.ts | 45 +++++++++++-------- 1 file changed, 26 insertions(+), 19 deletions(-) diff --git a/packages/toolkit/src/query/react/namedHooks.ts b/packages/toolkit/src/query/react/namedHooks.ts index e91b405036..7a21b993f4 100644 --- a/packages/toolkit/src/query/react/namedHooks.ts +++ b/packages/toolkit/src/query/react/namedHooks.ts @@ -6,7 +6,7 @@ import type { QueryDefinition, } from '@reduxjs/toolkit/query' -export type HooksWithUniqueNames = { +type QueryHookNames = { [K in keyof Definitions as Definitions[K] extends { type: DefinitionType.query } @@ -14,22 +14,29 @@ export type HooksWithUniqueNames = { : never]: UseQuery< Extract> > -} & - { - [K in keyof Definitions as Definitions[K] extends { - type: DefinitionType.query - } - ? `useLazy${Capitalize}Query` - : never]: UseLazyQuery< - Extract> - > - } & - { - [K in keyof Definitions as Definitions[K] extends { - type: DefinitionType.mutation - } - ? `use${Capitalize}Mutation` - : never]: UseMutation< - Extract> - > +} + +type LazyQueryHookNames = { + [K in keyof Definitions as Definitions[K] extends { + type: DefinitionType.query + } + ? `useLazy${Capitalize}Query` + : never]: UseLazyQuery< + Extract> + > +} + +type MutationHookNames = { + [K in keyof Definitions as Definitions[K] extends { + type: DefinitionType.mutation } + ? `use${Capitalize}Mutation` + : never]: UseMutation< + Extract> + > +} + +export type HooksWithUniqueNames = + QueryHookNames & + LazyQueryHookNames & + MutationHookNames From ca15ec2a665036161317f7b4ed59c232a363b3d5 Mon Sep 17 00:00:00 2001 From: Mark Erikson Date: Wed, 11 Oct 2023 19:59:28 -0400 Subject: [PATCH 310/412] Release 2.0.0-beta.3 --- packages/toolkit/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/toolkit/package.json b/packages/toolkit/package.json index 7b83807e26..64d67dae8b 100644 --- a/packages/toolkit/package.json +++ b/packages/toolkit/package.json @@ -1,6 +1,6 @@ { "name": "@reduxjs/toolkit", - "version": "2.0.0-beta.2", + "version": "2.0.0-beta.3", "description": "The official, opinionated, batteries-included toolset for efficient Redux development", "author": "Mark Erikson ", "license": "MIT", From e0c69b9273e93d366d21142d96b57291b3fc6294 Mon Sep 17 00:00:00 2001 From: Lenz Weber-Tronic Date: Sun, 10 Sep 2023 16:35:41 +0200 Subject: [PATCH 311/412] keep subscription on data while query is running --- .../core/buildMiddleware/batchActions.ts | 20 +++-- packages/toolkit/src/query/core/buildSlice.ts | 9 +-- .../src/query/tests/buildInitiate.test.tsx | 81 +++++++++++++++++++ 3 files changed, 99 insertions(+), 11 deletions(-) diff --git a/packages/toolkit/src/query/core/buildMiddleware/batchActions.ts b/packages/toolkit/src/query/core/buildMiddleware/batchActions.ts index 0bd2a73515..dda25a4e45 100644 --- a/packages/toolkit/src/query/core/buildMiddleware/batchActions.ts +++ b/packages/toolkit/src/query/core/buildMiddleware/batchActions.ts @@ -45,13 +45,23 @@ export const buildBatchedActionsHandler: InternalHandlerBuilder< const { meta: { arg, requestId }, } = action + const substate = (mutableState[arg.queryCacheKey] ??= {}) + substate[`${requestId}_running`] = {} if (arg.subscribe) { - const substate = (mutableState[arg.queryCacheKey] ??= {}) substate[requestId] = arg.subscriptionOptions ?? substate[requestId] ?? {} - - return true } + return true + } + let mutated = false + if ( + queryThunk.fulfilled.match(action) || + queryThunk.rejected.match(action) + ) { + const state = mutableState[action.meta.arg.queryCacheKey] || {} + const key = `${action.meta.requestId}_running` + mutated ||= !!state[key] + delete state[key] } if (queryThunk.rejected.match(action)) { const { @@ -62,11 +72,11 @@ export const buildBatchedActionsHandler: InternalHandlerBuilder< substate[requestId] = arg.subscriptionOptions ?? substate[requestId] ?? {} - return true + mutated = true } } - return false + return mutated } return ( diff --git a/packages/toolkit/src/query/core/buildSlice.ts b/packages/toolkit/src/query/core/buildSlice.ts index dc10799940..648e3fd219 100644 --- a/packages/toolkit/src/query/core/buildSlice.ts +++ b/packages/toolkit/src/query/core/buildSlice.ts @@ -147,12 +147,9 @@ export function buildSlice({ builder .addCase(queryThunk.pending, (draft, { meta, meta: { arg } }) => { const upserting = isUpsertQuery(arg) - if (arg.subscribe || upserting) { - // only initialize substate if we want to subscribe to it - draft[arg.queryCacheKey] ??= { - status: QueryStatus.uninitialized, - endpointName: arg.endpointName, - } + draft[arg.queryCacheKey] ??= { + status: QueryStatus.uninitialized, + endpointName: arg.endpointName, } updateQuerySubstateIfExists(draft, arg.queryCacheKey, (substate) => { diff --git a/packages/toolkit/src/query/tests/buildInitiate.test.tsx b/packages/toolkit/src/query/tests/buildInitiate.test.tsx index b16182177a..a022dcceda 100644 --- a/packages/toolkit/src/query/tests/buildInitiate.test.tsx +++ b/packages/toolkit/src/query/tests/buildInitiate.test.tsx @@ -13,6 +13,12 @@ const api = createApi({ return { data } }, }), + failing: build.query({ + async queryFn() { + await Promise.resolve() + return { error: { status: 500, data: 'error' } } + }, + }), }), }) @@ -52,3 +58,78 @@ test('multiple synchonrous initiate calls with pre-existing cache entry', async requestId: thirdValue.requestId, }) }) + +describe('calling initiate without a cache entry, with subscribe: false still returns correct values', () => { + test('successful query', async () => { + const { store, api } = storeRef + calls = 0 + const promise = store.dispatch( + api.endpoints.increment.initiate(undefined, { subscribe: false }) + ) + expect( + store.dispatch( + api.internalActions.internal_probeSubscription({ + queryCacheKey: 'increment(undefined)', + requestId: promise.requestId, + }) + ) + ).toBe(false) + expect( + store.dispatch( + api.internalActions.internal_probeSubscription({ + queryCacheKey: 'increment(undefined)', + requestId: `${promise.requestId}_running`, + }) + ) + ).toBe(true) + + await expect(promise).resolves.toMatchObject({ + data: 0, + status: 'fulfilled', + }) + expect( + store.dispatch( + api.internalActions.internal_probeSubscription({ + queryCacheKey: 'increment(undefined)', + requestId: `${promise.requestId}_running`, + }) + ) + ).toBe(false) + }) + + test('rejected query', async () => { + const { store, api } = storeRef + calls = 0 + const promise = store.dispatch( + api.endpoints.failing.initiate(undefined, { subscribe: false }) + ) + expect( + store.dispatch( + api.internalActions.internal_probeSubscription({ + queryCacheKey: 'failing(undefined)', + requestId: promise.requestId, + }) + ) + ).toBe(false) + expect( + store.dispatch( + api.internalActions.internal_probeSubscription({ + queryCacheKey: 'failing(undefined)', + requestId: `${promise.requestId}_running`, + }) + ) + ).toBe(true) + + await expect(promise).resolves.toMatchObject({ + status: 'rejected', + }) + expect( + store.dispatch( + api.internalActions.internal_probeSubscription({ + queryCacheKey: 'failing(undefined)', + requestId: `${promise.requestId}_running`, + }) + ) + ).toBe(false) + }) +}) From f291d0978517c60e29dd03e6268d6bf180337fa2 Mon Sep 17 00:00:00 2001 From: Lenz Weber-Tronic Date: Sun, 10 Sep 2023 17:12:24 +0200 Subject: [PATCH 312/412] update tests --- .../src/query/tests/buildHooks.test.tsx | 5 +++- .../src/query/tests/buildMiddleware.test.tsx | 11 ++++++-- .../src/query/tests/cacheCollection.test.ts | 25 ++++++++++++++----- .../src/query/tests/cacheLifecycle.test.ts | 11 ++++++-- .../toolkit/src/query/tests/cleanup.test.tsx | 1 + .../src/query/tests/fetchBaseQuery.test.tsx | 14 ++++++----- packages/toolkit/src/query/tests/helpers.tsx | 4 ++- 7 files changed, 53 insertions(+), 18 deletions(-) diff --git a/packages/toolkit/src/query/tests/buildHooks.test.tsx b/packages/toolkit/src/query/tests/buildHooks.test.tsx index 39f93d145f..eafdfa6b6c 100644 --- a/packages/toolkit/src/query/tests/buildHooks.test.tsx +++ b/packages/toolkit/src/query/tests/buildHooks.test.tsx @@ -1820,10 +1820,13 @@ describe('hooks tests', () => { checkSession.matchPending, api.internalActions.subscriptionsUpdated.match, checkSession.matchRejected, + api.internalActions.subscriptionsUpdated.match, login.matchPending, login.matchFulfilled, checkSession.matchPending, - checkSession.matchFulfilled + api.internalActions.subscriptionsUpdated.match, + checkSession.matchFulfilled, + api.internalActions.subscriptionsUpdated.match ) }) }) diff --git a/packages/toolkit/src/query/tests/buildMiddleware.test.tsx b/packages/toolkit/src/query/tests/buildMiddleware.test.tsx index cd7ac0fb95..0434cb775e 100644 --- a/packages/toolkit/src/query/tests/buildMiddleware.test.tsx +++ b/packages/toolkit/src/query/tests/buildMiddleware.test.tsx @@ -38,7 +38,8 @@ it('invalidates the specified tags', async () => { api.internalActions.middlewareRegistered.match, getBanana.matchPending, api.internalActions.subscriptionsUpdated.match, - getBanana.matchFulfilled + getBanana.matchFulfilled, + api.internalActions.subscriptionsUpdated.match ) await storeRef.store.dispatch(api.util.invalidateTags(['Banana', 'Bread'])) @@ -51,9 +52,12 @@ it('invalidates the specified tags', async () => { getBanana.matchPending, api.internalActions.subscriptionsUpdated.match, getBanana.matchFulfilled, + api.internalActions.subscriptionsUpdated.match, api.util.invalidateTags.match, getBanana.matchPending, + api.internalActions.subscriptionsUpdated.match, getBanana.matchFulfilled, + api.internalActions.subscriptionsUpdated.match, ] expect(storeRef.store.getState().actions).toMatchSequence(...firstSequence) @@ -67,9 +71,12 @@ it('invalidates the specified tags', async () => { getBread.matchPending, api.internalActions.subscriptionsUpdated.match, getBread.matchFulfilled, + api.internalActions.subscriptionsUpdated.match, api.util.invalidateTags.match, getBread.matchPending, - getBread.matchFulfilled + api.internalActions.subscriptionsUpdated.match, + getBread.matchFulfilled, + api.internalActions.subscriptionsUpdated.match ) }) diff --git a/packages/toolkit/src/query/tests/cacheCollection.test.ts b/packages/toolkit/src/query/tests/cacheCollection.test.ts index 68500c9d97..1890be42f3 100644 --- a/packages/toolkit/src/query/tests/cacheCollection.test.ts +++ b/packages/toolkit/src/query/tests/cacheCollection.test.ts @@ -29,7 +29,9 @@ test(`query: await cleanup, defaults`, async () => { }) ) - store.dispatch(api.endpoints.query.initiate('arg')).unsubscribe() + const promise = store.dispatch(api.endpoints.query.initiate('arg')) + await promise + promise.unsubscribe() vi.advanceTimersByTime(59000) expect(onCleanup).not.toHaveBeenCalled() vi.advanceTimersByTime(2000) @@ -49,7 +51,9 @@ test(`query: await cleanup, keepUnusedDataFor set`, async () => { }) ) - store.dispatch(api.endpoints.query.initiate('arg')).unsubscribe() + const promise = store.dispatch(api.endpoints.query.initiate('arg')) + await promise + promise.unsubscribe() vi.advanceTimersByTime(28000) expect(onCleanup).not.toHaveBeenCalled() vi.advanceTimersByTime(2000) @@ -69,7 +73,9 @@ test(`query: handles large keepUnuseDataFor values over 32-bit ms`, async () => }) ) - store.dispatch(api.endpoints.query.initiate('arg')).unsubscribe() + const promise = store.dispatch(api.endpoints.query.initiate('arg')) + await promise + promise.unsubscribe() // Shouldn't have been called right away vi.advanceTimersByTime(1000) @@ -110,7 +116,9 @@ describe(`query: await cleanup, keepUnusedDataFor set`, () => { ) test('global keepUnusedDataFor', async () => { - store.dispatch(api.endpoints.query.initiate('arg')).unsubscribe() + const promise = store.dispatch(api.endpoints.query.initiate('arg')) + await promise + promise.unsubscribe() vi.advanceTimersByTime(28000) expect(onCleanup).not.toHaveBeenCalled() vi.advanceTimersByTime(2000) @@ -118,7 +126,10 @@ describe(`query: await cleanup, keepUnusedDataFor set`, () => { }) test('endpoint keepUnusedDataFor', async () => { - store.dispatch(api.endpoints.query2.initiate('arg')).unsubscribe() + const promise = store.dispatch(api.endpoints.query2.initiate('arg')) + await promise + promise.unsubscribe() + vi.advanceTimersByTime(34000) expect(onCleanup).not.toHaveBeenCalled() vi.advanceTimersByTime(2000) @@ -127,7 +138,9 @@ describe(`query: await cleanup, keepUnusedDataFor set`, () => { test('endpoint keepUnusedDataFor: 0 ', async () => { expect(onCleanup).not.toHaveBeenCalled() - store.dispatch(api.endpoints.query3.initiate('arg')).unsubscribe() + const promise = store.dispatch(api.endpoints.query3.initiate('arg')) + await promise + promise.unsubscribe() expect(onCleanup).not.toHaveBeenCalled() vi.advanceTimersByTime(1) expect(onCleanup).toHaveBeenCalled() diff --git a/packages/toolkit/src/query/tests/cacheLifecycle.test.ts b/packages/toolkit/src/query/tests/cacheLifecycle.test.ts index 778000ce5c..fa505f895d 100644 --- a/packages/toolkit/src/query/tests/cacheLifecycle.test.ts +++ b/packages/toolkit/src/query/tests/cacheLifecycle.test.ts @@ -76,6 +76,7 @@ describe.each([['query'], ['mutation']] as const)( expect(onNewCacheEntry).toHaveBeenCalledWith('arg') expect(onCleanup).not.toHaveBeenCalled() + await promise if (type === 'mutation') { promise.reset() } else { @@ -219,6 +220,7 @@ describe.each([['query'], ['mutation']] as const)( ) expect(onNewCacheEntry).toHaveBeenCalledWith('arg') + await promise if (type === 'mutation') { promise.reset() } else { @@ -270,6 +272,7 @@ describe.each([['query'], ['mutation']] as const)( ) expect(onNewCacheEntry).toHaveBeenCalledWith('arg') + await promise if (type === 'mutation') { promise.reset() @@ -321,6 +324,7 @@ describe.each([['query'], ['mutation']] as const)( expect(onNewCacheEntry).toHaveBeenCalledWith('arg') + await promise if (type === 'mutation') { promise.reset() } else { @@ -370,6 +374,7 @@ test(`query: getCacheEntry`, async () => { const promise = storeRef.store.dispatch( extended.endpoints.injected.initiate('arg') ) + await promise promise.unsubscribe() await fakeTimerWaitFor(() => { @@ -539,6 +544,7 @@ test('updateCachedData', async () => { const promise = storeRef.store.dispatch( extended.endpoints.injected.initiate('arg') ) + await promise promise.unsubscribe() await fakeTimerWaitFor(() => { @@ -576,7 +582,7 @@ test('dispatching further actions does not trigger another lifecycle', async () expect(onNewCacheEntry).toHaveBeenCalledTimes(1) }) -test('dispatching a query initializer with `subscribe: false` does not start a lifecycle', async () => { +test('dispatching a query initializer with `subscribe: false` does also start a lifecycle', async () => { const extended = api.injectEndpoints({ overrideExisting: true, endpoints: (build) => ({ @@ -591,8 +597,9 @@ test('dispatching a query initializer with `subscribe: false` does not start a l await storeRef.store.dispatch( extended.endpoints.injected.initiate(undefined, { subscribe: false }) ) - expect(onNewCacheEntry).toHaveBeenCalledTimes(0) + expect(onNewCacheEntry).toHaveBeenCalledTimes(1) + // will not be called a second time though await storeRef.store.dispatch(extended.endpoints.injected.initiate(undefined)) expect(onNewCacheEntry).toHaveBeenCalledTimes(1) }) diff --git a/packages/toolkit/src/query/tests/cleanup.test.tsx b/packages/toolkit/src/query/tests/cleanup.test.tsx index 36870e11db..f3c14af08f 100644 --- a/packages/toolkit/src/query/tests/cleanup.test.tsx +++ b/packages/toolkit/src/query/tests/cleanup.test.tsx @@ -203,5 +203,6 @@ test('Minimizes the number of subscription dispatches when multiple components a 'api/executeQuery/pending', 'api/internalSubscriptions/subscriptionsUpdated', 'api/executeQuery/fulfilled', + 'api/internalSubscriptions/subscriptionsUpdated', ]) }, 25000) diff --git a/packages/toolkit/src/query/tests/fetchBaseQuery.test.tsx b/packages/toolkit/src/query/tests/fetchBaseQuery.test.tsx index e2f9334f7b..3a6ecbba9f 100644 --- a/packages/toolkit/src/query/tests/fetchBaseQuery.test.tsx +++ b/packages/toolkit/src/query/tests/fetchBaseQuery.test.tsx @@ -176,7 +176,8 @@ describe('fetchBaseQuery', () => { expect(res.meta?.response).toBeInstanceOf(Object) expect(res.error).toEqual({ status: 'PARSING_ERROR', - error: 'SyntaxError: Unexpected token h in JSON at position 1', + error: + 'SyntaxError: Unexpected token \'h\', "this is not json!" is not valid JSON', originalStatus: 200, data: `this is not json!`, }) @@ -334,7 +335,8 @@ describe('fetchBaseQuery', () => { expect(res.meta?.response).toBeInstanceOf(Object) expect(res.error).toEqual({ status: 'PARSING_ERROR', - error: 'SyntaxError: Unexpected token h in JSON at position 1', + error: + 'SyntaxError: Unexpected token \'h\', "this is not json!" is not valid JSON', originalStatus: 500, data: `this is not json!`, }) @@ -435,7 +437,7 @@ describe('fetchBaseQuery', () => { it('supports a custom jsonReplacer', async () => { const body = { - items: new Set(["A", "B", "C"]) + items: new Set(['A', 'B', 'C']), } let request: any @@ -456,7 +458,8 @@ describe('fetchBaseQuery', () => { const baseQueryWithReplacer = fetchBaseQuery({ baseUrl, fetchFn: fetchFn as any, - jsonReplacer: (key, value) => value instanceof Set ? [...value] : value + jsonReplacer: (key, value) => + value instanceof Set ? [...value] : value, }) ;({ data: request } = await baseQueryWithReplacer( @@ -470,8 +473,7 @@ describe('fetchBaseQuery', () => { )) expect(request.headers['content-type']).toBe('application/json') - expect(request.body).toEqual({ items: ["A", "B", "C"] }) // Set is marshalled correctly by jsonReplacer - + expect(request.body).toEqual({ items: ['A', 'B', 'C'] }) // Set is marshalled correctly by jsonReplacer }) }) diff --git a/packages/toolkit/src/query/tests/helpers.tsx b/packages/toolkit/src/query/tests/helpers.tsx index 01c2a2260c..0c27a7c9b2 100644 --- a/packages/toolkit/src/query/tests/helpers.tsx +++ b/packages/toolkit/src/query/tests/helpers.tsx @@ -118,7 +118,9 @@ expect.extend({ if (!matchers[i](actions[i])) { return { message: () => - `Action ${actions[i].type} does not match sequence at position ${i}.`, + `Action ${actions[i].type} does not match sequence at position ${i}. +All actions: +${actions.map((a) => a.type).join('\n')}`, pass: false, } } From d207ced2afc839b5c9b99db8f4407d4ab97c953b Mon Sep 17 00:00:00 2001 From: Mark Erikson Date: Thu, 19 Oct 2023 15:31:40 +0100 Subject: [PATCH 313/412] Fix syntax error matching --- packages/toolkit/src/query/tests/fetchBaseQuery.test.tsx | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/packages/toolkit/src/query/tests/fetchBaseQuery.test.tsx b/packages/toolkit/src/query/tests/fetchBaseQuery.test.tsx index 3a6ecbba9f..6ab089f2be 100644 --- a/packages/toolkit/src/query/tests/fetchBaseQuery.test.tsx +++ b/packages/toolkit/src/query/tests/fetchBaseQuery.test.tsx @@ -176,8 +176,7 @@ describe('fetchBaseQuery', () => { expect(res.meta?.response).toBeInstanceOf(Object) expect(res.error).toEqual({ status: 'PARSING_ERROR', - error: - 'SyntaxError: Unexpected token \'h\', "this is not json!" is not valid JSON', + error: expect.stringMatching(/SyntaxError: Unexpected token/), originalStatus: 200, data: `this is not json!`, }) @@ -335,8 +334,7 @@ describe('fetchBaseQuery', () => { expect(res.meta?.response).toBeInstanceOf(Object) expect(res.error).toEqual({ status: 'PARSING_ERROR', - error: - 'SyntaxError: Unexpected token \'h\', "this is not json!" is not valid JSON', + error: expect.stringMatching(/SyntaxError: Unexpected token/), originalStatus: 500, data: `this is not json!`, }) From f9e0531e14eff17f07a07365baca57383285ce26 Mon Sep 17 00:00:00 2001 From: Mark Erikson Date: Wed, 25 Oct 2023 22:07:55 -0400 Subject: [PATCH 314/412] Rewrite RTKQ internal subscription lookups and subscription syncing - Changed `subscriptionUpdated` from a microtask to a 500ms throttle - Exposed the RTKQ internal middleware state to be returned via an internal action, so that hooks can read that state directly without needing to call `dispatch()` on every render. - Reworked tests to completely ignore `subscriptionsUpdated` in any action sequence checks due to timing changes and irrelevance - Fixed case where `invalidateTags` was still reading from store state --- .../toolkit/src/query/core/buildInitiate.ts | 17 ++--- .../core/buildMiddleware/batchActions.ts | 49 ++++++++----- .../src/query/core/buildMiddleware/index.ts | 10 +-- .../buildMiddleware/invalidationByTags.ts | 15 +++- .../src/query/core/buildMiddleware/types.ts | 1 + packages/toolkit/src/query/core/buildSlice.ts | 9 +-- .../toolkit/src/query/react/buildHooks.ts | 49 ++++++++----- .../src/query/tests/buildHooks.test.tsx | 52 ++++++------- .../src/query/tests/buildInitiate.test.tsx | 73 +++++++++---------- .../src/query/tests/buildMiddleware.test.tsx | 14 +--- .../src/query/tests/buildSlice.test.ts | 16 +--- .../toolkit/src/query/tests/cleanup.test.tsx | 19 +++-- packages/toolkit/src/query/tests/helpers.tsx | 5 ++ .../toolkit/src/query/tests/matchers.test.tsx | 10 +-- .../toolkit/src/query/tests/polling.test.tsx | 10 ++- 15 files changed, 178 insertions(+), 171 deletions(-) diff --git a/packages/toolkit/src/query/core/buildInitiate.ts b/packages/toolkit/src/query/core/buildInitiate.ts index 2811f42681..3fc0ba7900 100644 --- a/packages/toolkit/src/query/core/buildInitiate.ts +++ b/packages/toolkit/src/query/core/buildInitiate.ts @@ -265,19 +265,16 @@ export function buildInitiate({ function middlewareWarning(dispatch: Dispatch) { if (process.env.NODE_ENV !== 'production') { if ((middlewareWarning as any).triggered) return - const registered: - | ReturnType - | boolean = dispatch( - api.internalActions.internal_probeSubscription({ - queryCacheKey: 'DOES_NOT_EXIST', - requestId: 'DUMMY_REQUEST_ID', - }) - ) + const returnedValue = dispatch(api.internalActions.getRTKQInternalState()) ;(middlewareWarning as any).triggered = true - // The RTKQ middleware _should_ always return a boolean for `probeSubscription` - if (typeof registered !== 'boolean') { + // The RTKQ middleware should return the internal state object, + // but it should _not_ be the action object. + if ( + typeof returnedValue !== 'object' || + typeof returnedValue?.type === 'string' + ) { // Otherwise, must not have been added throw new Error( `Warning: Middleware for RTK-Query API at reducerPath "${api.reducerPath}" has not been added to the store. diff --git a/packages/toolkit/src/query/core/buildMiddleware/batchActions.ts b/packages/toolkit/src/query/core/buildMiddleware/batchActions.ts index dda25a4e45..d7e618301e 100644 --- a/packages/toolkit/src/query/core/buildMiddleware/batchActions.ts +++ b/packages/toolkit/src/query/core/buildMiddleware/batchActions.ts @@ -1,17 +1,20 @@ -import type { InternalHandlerBuilder } from './types' +import type { InternalHandlerBuilder, InternalMiddlewareState } from './types' import type { SubscriptionState } from '../apiState' import { produceWithPatches } from 'immer' import type { Action } from '@reduxjs/toolkit' export const buildBatchedActionsHandler: InternalHandlerBuilder< - [actionShouldContinue: boolean, subscriptionExists: boolean] + [ + actionShouldContinue: boolean, + returnValue: InternalMiddlewareState | boolean + ] > = ({ api, queryThunk, internalState }) => { const subscriptionsPrefix = `${api.reducerPath}/subscriptions` let previousSubscriptions: SubscriptionState = null as unknown as SubscriptionState - let dispatchQueued = false + let updateSyncTimer: ReturnType | null = null const { updateSubscriptionOptions, unsubscribeQueryResult } = api.internalActions @@ -82,7 +85,10 @@ export const buildBatchedActionsHandler: InternalHandlerBuilder< return ( action, mwApi - ): [actionShouldContinue: boolean, hasSubscription: boolean] => { + ): [ + actionShouldContinue: boolean, + result: InternalMiddlewareState | boolean + ] => { if (!previousSubscriptions) { // Initialize it the first time this handler runs previousSubscriptions = JSON.parse( @@ -92,16 +98,16 @@ export const buildBatchedActionsHandler: InternalHandlerBuilder< if (api.util.resetApiState.match(action)) { previousSubscriptions = internalState.currentSubscriptions = {} + updateSyncTimer = null return [true, false] } // Intercept requests by hooks to see if they're subscribed - // Necessary because we delay updating store state to the end of the tick - if (api.internalActions.internal_probeSubscription.match(action)) { - const { queryCacheKey, requestId } = action.payload - const hasSubscription = - !!internalState.currentSubscriptions[queryCacheKey]?.[requestId] - return [false, hasSubscription] + // We return the internal state reference so that hooks + // can do their own checks to see if they're still active. + // It's stupid and hacky, but it does cut down on some dispatch calls. + if (api.internalActions.getRTKQInternalState.match(action)) { + return [false, internalState] } // Update subscription data based on this action @@ -110,9 +116,16 @@ export const buildBatchedActionsHandler: InternalHandlerBuilder< action ) + let actionShouldContinue = true + if (didMutate) { - if (!dispatchQueued) { - queueMicrotask(() => { + if (!updateSyncTimer) { + // We only use the subscription state for the Redux DevTools at this point, + // as the real data is kept here in the middleware. + // Given that, we can throttle synchronizing this state significantly to + // save on overall perf. + // In 1.9, it was updated in a microtask, but now we do it at most every 500ms. + updateSyncTimer = setTimeout(() => { // Deep clone the current subscription data const newSubscriptions: SubscriptionState = JSON.parse( JSON.stringify(internalState.currentSubscriptions) @@ -127,25 +140,23 @@ export const buildBatchedActionsHandler: InternalHandlerBuilder< mwApi.next(api.internalActions.subscriptionsUpdated(patches)) // Save the cloned state for later reference previousSubscriptions = newSubscriptions - dispatchQueued = false - }) - dispatchQueued = true + updateSyncTimer = null + }, 500) } const isSubscriptionSliceAction = typeof action.type == 'string' && !!action.type.startsWith(subscriptionsPrefix) + const isAdditionalSubscriptionAction = queryThunk.rejected.match(action) && action.meta.condition && !!action.meta.arg.subscribe - const actionShouldContinue = + actionShouldContinue = !isSubscriptionSliceAction && !isAdditionalSubscriptionAction - - return [actionShouldContinue, false] } - return [true, false] + return [actionShouldContinue, false] } } diff --git a/packages/toolkit/src/query/core/buildMiddleware/index.ts b/packages/toolkit/src/query/core/buildMiddleware/index.ts index aa58617d6f..eecd189e84 100644 --- a/packages/toolkit/src/query/core/buildMiddleware/index.ts +++ b/packages/toolkit/src/query/core/buildMiddleware/index.ts @@ -71,6 +71,7 @@ export function buildMiddleware< >), internalState, refetchQuery, + isThisApiSliceAction, } const handlers = handlerBuilders.map((build) => build(builderArgs)) @@ -93,18 +94,15 @@ export function buildMiddleware< const stateBefore = mwApi.getState() - const [actionShouldContinue, hasSubscription] = batchedActionsHandler( - action, - mwApiWithNext, - stateBefore - ) + const [actionShouldContinue, internalProbeResult] = + batchedActionsHandler(action, mwApiWithNext, stateBefore) let res: any if (actionShouldContinue) { res = next(action) } else { - res = hasSubscription + res = internalProbeResult } if (!!mwApi.getState()[reducerPath]) { diff --git a/packages/toolkit/src/query/core/buildMiddleware/invalidationByTags.ts b/packages/toolkit/src/query/core/buildMiddleware/invalidationByTags.ts index 181e50f59a..76c9d81d29 100644 --- a/packages/toolkit/src/query/core/buildMiddleware/invalidationByTags.ts +++ b/packages/toolkit/src/query/core/buildMiddleware/invalidationByTags.ts @@ -9,6 +9,7 @@ import type { SubMiddlewareApi, InternalHandlerBuilder, ApiMiddlewareInternalHandler, + InternalMiddlewareState, } from './types' export const buildInvalidationByTagsHandler: InternalHandlerBuilder = ({ @@ -19,6 +20,7 @@ export const buildInvalidationByTagsHandler: InternalHandlerBuilder = ({ api, assertTagType, refetchQuery, + internalState, }) => { const { removeQueryResult } = api.internalActions const isThunkActionWithTags = isAnyOf( @@ -35,7 +37,8 @@ export const buildInvalidationByTagsHandler: InternalHandlerBuilder = ({ endpointDefinitions, assertTagType ), - mwApi + mwApi, + internalState ) } @@ -49,16 +52,19 @@ export const buildInvalidationByTagsHandler: InternalHandlerBuilder = ({ undefined, assertTagType ), - mwApi + mwApi, + internalState ) } } function invalidateTags( tags: readonly FullTagDescription[], - mwApi: SubMiddlewareApi + mwApi: SubMiddlewareApi, + internalState: InternalMiddlewareState ) { const rootState = mwApi.getState() + const state = rootState[reducerPath] const toInvalidate = api.util.selectInvalidatedBy(rootState, tags) @@ -67,7 +73,8 @@ export const buildInvalidationByTagsHandler: InternalHandlerBuilder = ({ const valuesArray = Array.from(toInvalidate.values()) for (const { queryCacheKey } of valuesArray) { const querySubState = state.queries[queryCacheKey] - const subscriptionSubState = state.subscriptions[queryCacheKey] ?? {} + const subscriptionSubState = + internalState.currentSubscriptions[queryCacheKey] ?? {} if (querySubState) { if (Object.keys(subscriptionSubState).length === 0) { diff --git a/packages/toolkit/src/query/core/buildMiddleware/types.ts b/packages/toolkit/src/query/core/buildMiddleware/types.ts index c7e4e52e02..474ff6c988 100644 --- a/packages/toolkit/src/query/core/buildMiddleware/types.ts +++ b/packages/toolkit/src/query/core/buildMiddleware/types.ts @@ -61,6 +61,7 @@ export interface BuildSubMiddlewareInput queryCacheKey: string, override?: Partial ): AsyncThunkAction + isThisApiSliceAction: (action: Action) => boolean } export type SubMiddlewareBuilder = ( diff --git a/packages/toolkit/src/query/core/buildSlice.ts b/packages/toolkit/src/query/core/buildSlice.ts index 648e3fd219..2fb15b255d 100644 --- a/packages/toolkit/src/query/core/buildSlice.ts +++ b/packages/toolkit/src/query/core/buildSlice.ts @@ -1,4 +1,4 @@ -import type { PayloadAction, UnknownAction } from '@reduxjs/toolkit' +import type { Action, PayloadAction, UnknownAction } from '@reduxjs/toolkit' import { combineReducers, createAction, @@ -443,12 +443,7 @@ export function buildSlice({ ) { // Dummy }, - internal_probeSubscription( - d, - a: PayloadAction<{ queryCacheKey: string; requestId: string }> - ) { - // dummy - }, + getRTKQInternalState() {}, }, }) diff --git a/packages/toolkit/src/query/react/buildHooks.ts b/packages/toolkit/src/query/react/buildHooks.ts index 4ab1f9dbd5..679fedbbd0 100644 --- a/packages/toolkit/src/query/react/buildHooks.ts +++ b/packages/toolkit/src/query/react/buildHooks.ts @@ -53,6 +53,7 @@ import { UNINITIALIZED_VALUE } from './constants' import { useShallowStableValue } from './useShallowStableValue' import type { BaseQueryFn } from '../baseQueryTypes' import { defaultSerializeQueryArgs } from '../defaultSerializeQueryArgs' +import { InternalMiddlewareState } from '../core/buildMiddleware/types' // Copy-pasted from React-Redux export const useIsomorphicLayoutEffect = @@ -681,6 +682,27 @@ export function buildHooks({ Definitions > const dispatch = useDispatch>() + const internalStateRef = useRef(null) + if (!internalStateRef.current) { + const returnedValue = dispatch( + api.internalActions.getRTKQInternalState() + ) + + if (process.env.NODE_ENV !== 'production') { + if ( + typeof returnedValue !== 'object' || + typeof returnedValue?.type === 'string' + ) { + throw new Error( + `Warning: Middleware for RTK-Query API at reducerPath "${api.reducerPath}" has not been added to the store. + You must add the middleware for RTK-Query to function correctly!` + ) + } + } + + internalStateRef.current = + returnedValue as unknown as InternalMiddlewareState + } const stableArg = useStableQueryArgs( skip ? skipToken : arg, // Even if the user provided a per-endpoint `serializeQueryArgs` with @@ -704,28 +726,15 @@ export function buildHooks({ let { queryCacheKey, requestId } = promiseRef.current || {} - // HACK Because the latest state is in the middleware, we actually - // dispatch an action that will be intercepted and returned. + // HACK We've saved the middleware internal state into a ref, + // and that state object gets directly mutated. But, we've _got_ a reference + // to it locally, so we can just read the data directly here in the hook. let currentRenderHasSubscription = false if (queryCacheKey && requestId) { - // This _should_ return a boolean, even if the types don't line up - const returnedValue = dispatch( - api.internalActions.internal_probeSubscription({ - queryCacheKey, - requestId, - }) - ) - - if (process.env.NODE_ENV !== 'production') { - if (typeof returnedValue !== 'boolean') { - throw new Error( - `Warning: Middleware for RTK-Query API at reducerPath "${api.reducerPath}" has not been added to the store. - You must add the middleware for RTK-Query to function correctly!` - ) - } - } - - currentRenderHasSubscription = !!returnedValue + currentRenderHasSubscription = + !!internalStateRef.current?.currentSubscriptions?.[queryCacheKey]?.[ + requestId + ] } const subscriptionRemoved = diff --git a/packages/toolkit/src/query/tests/buildHooks.test.tsx b/packages/toolkit/src/query/tests/buildHooks.test.tsx index eafdfa6b6c..9be85dd326 100644 --- a/packages/toolkit/src/query/tests/buildHooks.test.tsx +++ b/packages/toolkit/src/query/tests/buildHooks.test.tsx @@ -37,6 +37,7 @@ import type { SubscriptionOptions } from '@reduxjs/toolkit/dist/query/core/apiSt import type { SerializedError } from '@reduxjs/toolkit' import { createListenerMiddleware, configureStore } from '@reduxjs/toolkit' import { delay } from '../../utils' +import type { InternalMiddlewareState } from '../core/buildMiddleware/types' // Just setup a temporary in-memory counter for tests that `getIncrementedAmount`. // This can be used to test how many renders happen due to data changes or @@ -138,6 +139,19 @@ const storeRef = setupApiStore( } ) +function getSubscriptions() { + const internalState = storeRef.store.dispatch( + api.internalActions.getRTKQInternalState() + ) as unknown as InternalMiddlewareState + return internalState?.currentSubscriptions ?? {} +} + +function getSubscriptionCount(key: string) { + const subscriptions = getSubscriptions() + const subscriptionsForQueryArg = subscriptions[key] ?? {} + return Object.keys(subscriptionsForQueryArg).length +} + beforeEach(() => { actions = [] listenerMiddleware.startListening({ @@ -738,14 +752,13 @@ describe('hooks tests', () => { withoutTestLifecycles: true, }) - const getSubscriptions = () => storeRef.store.getState().api.subscriptions - const checkNumSubscriptions = (arg: string, count: number) => { const subscriptions = getSubscriptions() const cacheKeyEntry = subscriptions[arg] if (cacheKeyEntry) { - expect(Object.values(cacheKeyEntry).length).toBe(count) + const subscriptionCount = Object.keys(cacheKeyEntry) //getSubscriptionCount(arg) + expect(subscriptionCount).toBe(count) } } @@ -1747,11 +1760,7 @@ describe('hooks tests', () => { }), }) - const storeRef = setupApiStore(api, { - actions(state: UnknownAction[] = [], action: UnknownAction) { - return [...state, action] - }, - }) + const storeRef = setupApiStore(api, { ...actionsReducer }) test('initially failed useQueries that provide an tag will refetch after a mutation invalidates it', async () => { const checkSessionData = { name: 'matt' } server.use( @@ -1818,15 +1827,11 @@ describe('hooks tests', () => { expect(storeRef.store.getState().actions).toMatchSequence( api.internalActions.middlewareRegistered.match, checkSession.matchPending, - api.internalActions.subscriptionsUpdated.match, checkSession.matchRejected, - api.internalActions.subscriptionsUpdated.match, login.matchPending, login.matchFulfilled, checkSession.matchPending, - api.internalActions.subscriptionsUpdated.match, - checkSession.matchFulfilled, - api.internalActions.subscriptionsUpdated.match + checkSession.matchFulfilled ) }) }) @@ -2541,11 +2546,6 @@ describe('skip behaviour', () => { isUninitialized: true, } - function subscriptionCount(key: string) { - return Object.keys(storeRef.store.getState().api.subscriptions[key] || {}) - .length - } - test('normal skip', async () => { const { result, rerender } = renderHook( ([arg, options]: Parameters) => @@ -2558,14 +2558,14 @@ describe('skip behaviour', () => { expect(result.current).toEqual(uninitialized) await delay(1) - expect(subscriptionCount('getUser(1)')).toBe(0) + expect(getSubscriptionCount('getUser(1)')).toBe(0) await act(async () => { rerender([1]) }) expect(result.current).toMatchObject({ status: QueryStatus.fulfilled }) await delay(1) - expect(subscriptionCount('getUser(1)')).toBe(1) + expect(getSubscriptionCount('getUser(1)')).toBe(1) await act(async () => { rerender([1, { skip: true }]) @@ -2576,7 +2576,7 @@ describe('skip behaviour', () => { data: { name: 'Timmy' }, }) await delay(1) - expect(subscriptionCount('getUser(1)')).toBe(0) + expect(getSubscriptionCount('getUser(1)')).toBe(0) }) test('skipToken', async () => { @@ -2592,17 +2592,17 @@ describe('skip behaviour', () => { expect(result.current).toEqual(uninitialized) await delay(1) - expect(subscriptionCount('getUser(1)')).toBe(0) + expect(getSubscriptionCount('getUser(1)')).toBe(0) // also no subscription on `getUser(skipToken)` or similar: - expect(storeRef.store.getState().api.subscriptions).toEqual({}) + expect(getSubscriptions()).toEqual({}) await act(async () => { rerender([1]) }) expect(result.current).toMatchObject({ status: QueryStatus.fulfilled }) await delay(1) - expect(subscriptionCount('getUser(1)')).toBe(1) - expect(storeRef.store.getState().api.subscriptions).not.toEqual({}) + expect(getSubscriptionCount('getUser(1)')).toBe(1) + expect(getSubscriptions()).not.toEqual({}) await act(async () => { rerender([skipToken]) @@ -2613,7 +2613,7 @@ describe('skip behaviour', () => { data: { name: 'Timmy' }, }) await delay(1) - expect(subscriptionCount('getUser(1)')).toBe(0) + expect(getSubscriptionCount('getUser(1)')).toBe(0) }) test('skipping a previously fetched query retains the existing value as `data`, but clears `currentData`', async () => { diff --git a/packages/toolkit/src/query/tests/buildInitiate.test.tsx b/packages/toolkit/src/query/tests/buildInitiate.test.tsx index a022dcceda..0b2ddba32d 100644 --- a/packages/toolkit/src/query/tests/buildInitiate.test.tsx +++ b/packages/toolkit/src/query/tests/buildInitiate.test.tsx @@ -1,4 +1,5 @@ import { createApi } from '../core' +import { InternalMiddlewareState } from '../core/buildMiddleware/types' import { fakeBaseQuery } from '../fakeBaseQuery' import { setupApiStore } from './helpers' @@ -24,6 +25,26 @@ const api = createApi({ const storeRef = setupApiStore(api) +function getSubscriptions() { + const internalState = storeRef.store.dispatch( + api.internalActions.getRTKQInternalState() + ) as unknown as InternalMiddlewareState + return internalState?.currentSubscriptions ?? {} +} + +function getSubscriptionCount(key: string) { + const subscriptions = getSubscriptions() + const subscriptionsForQueryArg = subscriptions[key] ?? {} + return Object.keys(subscriptionsForQueryArg).length + //return Object.keys(storeRef.store.getState().api.subscriptions[key] || {}) + //.length +} + +function isRequestSubscribed(key: string, requestId: string) { + const subscriptions = getSubscriptions() + return !!subscriptions?.[key]?.[requestId] +} + test('multiple synchonrous initiate calls with pre-existing cache entry', async () => { const { store, api } = storeRef // seed the store @@ -66,20 +87,13 @@ describe('calling initiate without a cache entry, with subscribe: false still re const promise = store.dispatch( api.endpoints.increment.initiate(undefined, { subscribe: false }) ) + expect(isRequestSubscribed('increment(undefined)', promise.requestId)).toBe( + false + ) expect( - store.dispatch( - api.internalActions.internal_probeSubscription({ - queryCacheKey: 'increment(undefined)', - requestId: promise.requestId, - }) - ) - ).toBe(false) - expect( - store.dispatch( - api.internalActions.internal_probeSubscription({ - queryCacheKey: 'increment(undefined)', - requestId: `${promise.requestId}_running`, - }) + isRequestSubscribed( + 'increment(undefined)', + `${promise.requestId}_running` ) ).toBe(true) @@ -88,11 +102,9 @@ describe('calling initiate without a cache entry, with subscribe: false still re status: 'fulfilled', }) expect( - store.dispatch( - api.internalActions.internal_probeSubscription({ - queryCacheKey: 'increment(undefined)', - requestId: `${promise.requestId}_running`, - }) + isRequestSubscribed( + 'increment(undefined)', + `${promise.requestId}_running` ) ).toBe(false) }) @@ -103,33 +115,18 @@ describe('calling initiate without a cache entry, with subscribe: false still re const promise = store.dispatch( api.endpoints.failing.initiate(undefined, { subscribe: false }) ) + expect(isRequestSubscribed('failing(undefined)', promise.requestId)).toBe( + false + ) expect( - store.dispatch( - api.internalActions.internal_probeSubscription({ - queryCacheKey: 'failing(undefined)', - requestId: promise.requestId, - }) - ) - ).toBe(false) - expect( - store.dispatch( - api.internalActions.internal_probeSubscription({ - queryCacheKey: 'failing(undefined)', - requestId: `${promise.requestId}_running`, - }) - ) + isRequestSubscribed('failing(undefined)', `${promise.requestId}_running`) ).toBe(true) await expect(promise).resolves.toMatchObject({ status: 'rejected', }) expect( - store.dispatch( - api.internalActions.internal_probeSubscription({ - queryCacheKey: 'failing(undefined)', - requestId: `${promise.requestId}_running`, - }) - ) + isRequestSubscribed('failing(undefined)', `${promise.requestId}_running`) ).toBe(false) }) }) diff --git a/packages/toolkit/src/query/tests/buildMiddleware.test.tsx b/packages/toolkit/src/query/tests/buildMiddleware.test.tsx index 0434cb775e..f6154ea848 100644 --- a/packages/toolkit/src/query/tests/buildMiddleware.test.tsx +++ b/packages/toolkit/src/query/tests/buildMiddleware.test.tsx @@ -37,9 +37,7 @@ it('invalidates the specified tags', async () => { expect(storeRef.store.getState().actions).toMatchSequence( api.internalActions.middlewareRegistered.match, getBanana.matchPending, - api.internalActions.subscriptionsUpdated.match, - getBanana.matchFulfilled, - api.internalActions.subscriptionsUpdated.match + getBanana.matchFulfilled ) await storeRef.store.dispatch(api.util.invalidateTags(['Banana', 'Bread'])) @@ -50,14 +48,10 @@ it('invalidates the specified tags', async () => { const firstSequence = [ api.internalActions.middlewareRegistered.match, getBanana.matchPending, - api.internalActions.subscriptionsUpdated.match, getBanana.matchFulfilled, - api.internalActions.subscriptionsUpdated.match, api.util.invalidateTags.match, getBanana.matchPending, - api.internalActions.subscriptionsUpdated.match, getBanana.matchFulfilled, - api.internalActions.subscriptionsUpdated.match, ] expect(storeRef.store.getState().actions).toMatchSequence(...firstSequence) @@ -69,14 +63,10 @@ it('invalidates the specified tags', async () => { expect(storeRef.store.getState().actions).toMatchSequence( ...firstSequence, getBread.matchPending, - api.internalActions.subscriptionsUpdated.match, getBread.matchFulfilled, - api.internalActions.subscriptionsUpdated.match, api.util.invalidateTags.match, getBread.matchPending, - api.internalActions.subscriptionsUpdated.match, - getBread.matchFulfilled, - api.internalActions.subscriptionsUpdated.match + getBread.matchFulfilled ) }) diff --git a/packages/toolkit/src/query/tests/buildSlice.test.ts b/packages/toolkit/src/query/tests/buildSlice.test.ts index 71d16def35..6ca643b286 100644 --- a/packages/toolkit/src/query/tests/buildSlice.test.ts +++ b/packages/toolkit/src/query/tests/buildSlice.test.ts @@ -75,8 +75,8 @@ describe('buildSlice', () => { status: 'fulfilled', }, }, - // Filled in a tick later - subscriptions: expect.any(Object), + // Filled some time later + subscriptions: {}, }, auth: { token: '1234', @@ -85,18 +85,6 @@ describe('buildSlice', () => { expect(storeRef.store.getState()).toEqual(initialQueryState) - await delay(1) - - expect(storeRef.store.getState()).toEqual({ - ...initialQueryState, - api: { - ...initialQueryState.api, - subscriptions: { - 'getUser(1)': expect.any(Object), - }, - }, - }) - storeRef.store.dispatch(api.util.resetApiState()) expect(storeRef.store.getState()).toEqual(initialState) diff --git a/packages/toolkit/src/query/tests/cleanup.test.tsx b/packages/toolkit/src/query/tests/cleanup.test.tsx index f3c14af08f..49a384ac13 100644 --- a/packages/toolkit/src/query/tests/cleanup.test.tsx +++ b/packages/toolkit/src/query/tests/cleanup.test.tsx @@ -6,6 +6,7 @@ import { createListenerMiddleware } from '@reduxjs/toolkit' import { createApi, QueryStatus } from '@reduxjs/toolkit/query/react' import { render, waitFor, act, screen } from '@testing-library/react' import { setupApiStore } from './helpers' +import { InternalMiddlewareState } from '../core/buildMiddleware/types' const tick = () => new Promise((res) => setImmediate(res)) @@ -159,15 +160,25 @@ test('Minimizes the number of subscription dispatches when multiple components a withoutTestLifecycles: true, }) - let getSubscriptionsA = () => - storeRef.store.getState().api.subscriptions['a(undefined)'] + function getSubscriptions() { + const internalState = storeRef.store.dispatch( + api.internalActions.getRTKQInternalState() + ) as unknown as InternalMiddlewareState + return internalState?.currentSubscriptions ?? {} + } + + let getSubscriptionsA = () => { + return getSubscriptions()['a(undefined)'] + } let actionTypes: unknown[] = [] listenerMiddleware.startListening({ predicate: () => true, effect: (action) => { - actionTypes.push(action.type) + if (!action.type.includes('subscriptionsUpdated')) { + actionTypes.push(action.type) + } }, }) @@ -201,8 +212,6 @@ test('Minimizes the number of subscription dispatches when multiple components a expect(actionTypes).toEqual([ 'api/config/middlewareRegistered', 'api/executeQuery/pending', - 'api/internalSubscriptions/subscriptionsUpdated', 'api/executeQuery/fulfilled', - 'api/internalSubscriptions/subscriptionsUpdated', ]) }, 25000) diff --git a/packages/toolkit/src/query/tests/helpers.tsx b/packages/toolkit/src/query/tests/helpers.tsx index 0c27a7c9b2..acf2bf097b 100644 --- a/packages/toolkit/src/query/tests/helpers.tsx +++ b/packages/toolkit/src/query/tests/helpers.tsx @@ -182,6 +182,11 @@ ${expectedOutput} export const actionsReducer = { actions: (state: UnknownAction[] = [], action: UnknownAction) => { + // As of 2.0-beta.4, we are going to ignore all `subscriptionsUpdated` actions in tests + if (action.type.includes('subscriptionsUpdated')) { + return state + } + return [...state, action] }, } diff --git a/packages/toolkit/src/query/tests/matchers.test.tsx b/packages/toolkit/src/query/tests/matchers.test.tsx index 1a7f5847b0..37c1dfe58b 100644 --- a/packages/toolkit/src/query/tests/matchers.test.tsx +++ b/packages/toolkit/src/query/tests/matchers.test.tsx @@ -65,25 +65,22 @@ test('matches query pending & fulfilled actions for the given endpoint', async ( expect(storeRef.store.getState().actions).toMatchSequence( api.internalActions.middlewareRegistered.match, endpoint.matchPending, - api.internalActions.subscriptionsUpdated.match, endpoint.matchFulfilled ) expect(storeRef.store.getState().actions).not.toMatchSequence( api.internalActions.middlewareRegistered.match, otherEndpoint.matchPending, - api.internalActions.subscriptionsUpdated.match, otherEndpoint.matchFulfilled ) expect(storeRef.store.getState().actions).not.toMatchSequence( api.internalActions.middlewareRegistered.match, endpoint.matchFulfilled, - api.internalActions.subscriptionsUpdated.match, + api.endpoints.mutationSuccess.matchFulfilled, endpoint.matchRejected ) expect(storeRef.store.getState().actions).not.toMatchSequence( api.internalActions.middlewareRegistered.match, endpoint.matchPending, - api.internalActions.subscriptionsUpdated.match, endpoint.matchRejected ) }) @@ -96,7 +93,6 @@ test('matches query pending & rejected actions for the given endpoint', async () expect(storeRef.store.getState().actions).toMatchSequence( api.internalActions.middlewareRegistered.match, endpoint.matchPending, - api.internalActions.subscriptionsUpdated.match, endpoint.matchRejected ) expect(storeRef.store.getState().actions).not.toMatchSequence( @@ -122,20 +118,17 @@ test('matches lazy query pending & fulfilled actions for given endpoint', async expect(storeRef.store.getState().actions).toMatchSequence( api.internalActions.middlewareRegistered.match, endpoint.matchPending, - api.internalActions.subscriptionsUpdated.match, endpoint.matchFulfilled ) expect(storeRef.store.getState().actions).not.toMatchSequence( api.internalActions.middlewareRegistered.match, endpoint.matchFulfilled, - api.internalActions.subscriptionsUpdated.match, endpoint.matchRejected ) expect(storeRef.store.getState().actions).not.toMatchSequence( api.internalActions.middlewareRegistered.match, endpoint.matchPending, - api.internalActions.subscriptionsUpdated.match, endpoint.matchRejected ) }) @@ -151,7 +144,6 @@ test('matches lazy query pending & rejected actions for given endpoint', async ( expect(storeRef.store.getState().actions).toMatchSequence( api.internalActions.middlewareRegistered.match, endpoint.matchPending, - api.internalActions.subscriptionsUpdated.match, endpoint.matchRejected ) expect(storeRef.store.getState().actions).not.toMatchSequence( diff --git a/packages/toolkit/src/query/tests/polling.test.tsx b/packages/toolkit/src/query/tests/polling.test.tsx index 7443d94048..d3231253aa 100644 --- a/packages/toolkit/src/query/tests/polling.test.tsx +++ b/packages/toolkit/src/query/tests/polling.test.tsx @@ -2,6 +2,7 @@ import { vi } from 'vitest' import { createApi } from '@reduxjs/toolkit/query' import { setupApiStore, waitMs } from './helpers' import { delay } from '../../utils' +import type { InternalMiddlewareState } from '../core/buildMiddleware/types' const mockBaseQuery = vi .fn() @@ -23,8 +24,15 @@ const { getPosts } = api.endpoints const storeRef = setupApiStore(api) +function getSubscriptions() { + const internalState = storeRef.store.dispatch( + api.internalActions.getRTKQInternalState() + ) as unknown as InternalMiddlewareState + return internalState?.currentSubscriptions ?? {} +} + const getSubscribersForQueryCacheKey = (queryCacheKey: string) => - storeRef.store.getState()[api.reducerPath].subscriptions[queryCacheKey] || {} + getSubscriptions()[queryCacheKey] || {} const createSubscriptionGetter = (queryCacheKey: string) => () => getSubscribersForQueryCacheKey(queryCacheKey) From 306b1efe4404ad0d0dcfbdf773ffc09353bf2575 Mon Sep 17 00:00:00 2001 From: Mark Erikson Date: Thu, 26 Oct 2023 20:51:49 -0400 Subject: [PATCH 315/412] Fix refetch test --- .../src/query/tests/refetchingBehaviors.test.tsx | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/packages/toolkit/src/query/tests/refetchingBehaviors.test.tsx b/packages/toolkit/src/query/tests/refetchingBehaviors.test.tsx index cd2dd45c6f..f2703e5b56 100644 --- a/packages/toolkit/src/query/tests/refetchingBehaviors.test.tsx +++ b/packages/toolkit/src/query/tests/refetchingBehaviors.test.tsx @@ -432,10 +432,12 @@ describe('customListenersHandler', () => { }) expect(dispatchSpy).toHaveBeenCalled() - // Ignore RTKQ middleware `internal_probeSubscription` calls - const mockCallsWithoutInternals = dispatchSpy.mock.calls.filter( - (call) => !(call[0] as any)?.type?.includes('internal') - ) + // Ignore RTKQ middleware internal data calls + const mockCallsWithoutInternals = dispatchSpy.mock.calls.filter((call) => { + const type = (call[0] as any)?.type ?? '' + const reIsInternal = /internal/i + return !reIsInternal.test(type) + }) expect( defaultApi.internalActions.onOnline.match( From 8a3ea9c582942e6c1ee0eaeabd9fcb187ecc3694 Mon Sep 17 00:00:00 2001 From: Mark Erikson Date: Thu, 26 Oct 2023 20:55:35 -0400 Subject: [PATCH 316/412] Add and use a `countObjectKeys` util --- packages/toolkit/src/query/core/buildInitiate.ts | 7 ++++--- .../core/buildMiddleware/invalidationByTags.ts | 3 ++- .../core/buildMiddleware/windowEventHandling.ts | 3 ++- packages/toolkit/src/query/react/module.ts | 3 ++- .../toolkit/src/query/tests/buildHooks.test.tsx | 15 +++++---------- .../src/query/tests/buildInitiate.test.tsx | 9 --------- .../src/query/tests/cacheCollection.test.ts | 5 +++-- packages/toolkit/src/query/tests/cleanup.test.tsx | 3 ++- .../toolkit/src/query/utils/countObjectKeys.ts | 14 ++++++++++++++ 9 files changed, 34 insertions(+), 28 deletions(-) create mode 100644 packages/toolkit/src/query/utils/countObjectKeys.ts diff --git a/packages/toolkit/src/query/core/buildInitiate.ts b/packages/toolkit/src/query/core/buildInitiate.ts index 3fc0ba7900..a21797ab3b 100644 --- a/packages/toolkit/src/query/core/buildInitiate.ts +++ b/packages/toolkit/src/query/core/buildInitiate.ts @@ -20,6 +20,7 @@ import type { BaseQueryError, QueryReturnValue } from '../baseQueryTypes' import type { QueryResultSelectorResult } from './buildSelectors' import type { Dispatch } from 'redux' import { isNotNullish } from '../utils/isNotNullish' +import { countObjectKeys } from '../utils/countObjectKeys' declare module './module' { export interface ApiEndpointQuery< @@ -392,7 +393,7 @@ You must add the middleware for RTK-Query to function correctly!` statePromise.then(() => { delete running[queryCacheKey] - if (!Object.keys(running).length) { + if (!countObjectKeys(running)) { runningQueries.delete(dispatch) } }) @@ -440,7 +441,7 @@ You must add the middleware for RTK-Query to function correctly!` running[requestId] = ret ret.then(() => { delete running[requestId] - if (!Object.keys(running).length) { + if (!countObjectKeys(running)) { runningMutations.delete(dispatch) } }) @@ -449,7 +450,7 @@ You must add the middleware for RTK-Query to function correctly!` ret.then(() => { if (running[fixedCacheKey] === ret) { delete running[fixedCacheKey] - if (!Object.keys(running).length) { + if (!countObjectKeys(running)) { runningMutations.delete(dispatch) } } diff --git a/packages/toolkit/src/query/core/buildMiddleware/invalidationByTags.ts b/packages/toolkit/src/query/core/buildMiddleware/invalidationByTags.ts index 76c9d81d29..e92c692d3a 100644 --- a/packages/toolkit/src/query/core/buildMiddleware/invalidationByTags.ts +++ b/packages/toolkit/src/query/core/buildMiddleware/invalidationByTags.ts @@ -11,6 +11,7 @@ import type { ApiMiddlewareInternalHandler, InternalMiddlewareState, } from './types' +import { countObjectKeys } from '../../utils/countObjectKeys' export const buildInvalidationByTagsHandler: InternalHandlerBuilder = ({ reducerPath, @@ -77,7 +78,7 @@ export const buildInvalidationByTagsHandler: InternalHandlerBuilder = ({ internalState.currentSubscriptions[queryCacheKey] ?? {} if (querySubState) { - if (Object.keys(subscriptionSubState).length === 0) { + if (countObjectKeys(subscriptionSubState) === 0) { mwApi.dispatch( removeQueryResult({ queryCacheKey: queryCacheKey as QueryCacheKey, diff --git a/packages/toolkit/src/query/core/buildMiddleware/windowEventHandling.ts b/packages/toolkit/src/query/core/buildMiddleware/windowEventHandling.ts index 4caa97c77d..409bc56e08 100644 --- a/packages/toolkit/src/query/core/buildMiddleware/windowEventHandling.ts +++ b/packages/toolkit/src/query/core/buildMiddleware/windowEventHandling.ts @@ -6,6 +6,7 @@ import type { InternalHandlerBuilder, SubMiddlewareApi, } from './types' +import { countObjectKeys } from '../../utils/countObjectKeys' export const buildWindowEventHandler: InternalHandlerBuilder = ({ reducerPath, @@ -50,7 +51,7 @@ export const buildWindowEventHandler: InternalHandlerBuilder = ({ state.config[type]) if (shouldRefetch) { - if (Object.keys(subscriptionSubState).length === 0) { + if (countObjectKeys(subscriptionSubState) === 0) { api.dispatch( removeQueryResult({ queryCacheKey: queryCacheKey as QueryCacheKey, diff --git a/packages/toolkit/src/query/react/module.ts b/packages/toolkit/src/query/react/module.ts index c5d4261992..3ff9a02166 100644 --- a/packages/toolkit/src/query/react/module.ts +++ b/packages/toolkit/src/query/react/module.ts @@ -22,6 +22,7 @@ import { } from 'react-redux' import type { QueryKeys } from '../core/apiState' import type { PrefetchOptions } from '../core/module' +import { countObjectKeys } from '../utils/countObjectKeys' export const reactHooksModuleName = /* @__PURE__ */ Symbol() export type ReactHooksModule = typeof reactHooksModuleName @@ -147,7 +148,7 @@ export const reactHooksModule = ({ let warned = false for (const hookName of hookNames) { // warn for old hook options - if (Object.keys(rest).length > 0) { + if (countObjectKeys(rest) > 0) { if ((rest as Partial)[hookName]) { if (!warned) { console.warn( diff --git a/packages/toolkit/src/query/tests/buildHooks.test.tsx b/packages/toolkit/src/query/tests/buildHooks.test.tsx index 9be85dd326..5d520fb888 100644 --- a/packages/toolkit/src/query/tests/buildHooks.test.tsx +++ b/packages/toolkit/src/query/tests/buildHooks.test.tsx @@ -38,6 +38,7 @@ import type { SerializedError } from '@reduxjs/toolkit' import { createListenerMiddleware, configureStore } from '@reduxjs/toolkit' import { delay } from '../../utils' import type { InternalMiddlewareState } from '../core/buildMiddleware/types' +import { countObjectKeys } from '../utils/countObjectKeys' // Just setup a temporary in-memory counter for tests that `getIncrementedAmount`. // This can be used to test how many renders happen due to data changes or @@ -149,7 +150,7 @@ function getSubscriptions() { function getSubscriptionCount(key: string) { const subscriptions = getSubscriptions() const subscriptionsForQueryArg = subscriptions[key] ?? {} - return Object.keys(subscriptionsForQueryArg).length + return countObjectKeys(subscriptionsForQueryArg) } beforeEach(() => { @@ -1439,25 +1440,19 @@ describe('hooks tests', () => { await screen.findByText(/isUninitialized/i) expect(screen.queryByText('Yay')).toBeNull() - expect(Object.keys(storeRef.store.getState().api.mutations).length).toBe( - 0 - ) + expect(countObjectKeys(storeRef.store.getState().api.mutations)).toBe(0) userEvent.click(screen.getByRole('button', { name: 'trigger' })) await screen.findByText(/isSuccess/i) expect(screen.queryByText('Yay')).not.toBeNull() - expect(Object.keys(storeRef.store.getState().api.mutations).length).toBe( - 1 - ) + expect(countObjectKeys(storeRef.store.getState().api.mutations)).toBe(1) userEvent.click(screen.getByRole('button', { name: 'reset' })) await screen.findByText(/isUninitialized/i) expect(screen.queryByText('Yay')).toBeNull() - expect(Object.keys(storeRef.store.getState().api.mutations).length).toBe( - 0 - ) + expect(countObjectKeys(storeRef.store.getState().api.mutations)).toBe(0) }) }) diff --git a/packages/toolkit/src/query/tests/buildInitiate.test.tsx b/packages/toolkit/src/query/tests/buildInitiate.test.tsx index 0b2ddba32d..cd68cd5af8 100644 --- a/packages/toolkit/src/query/tests/buildInitiate.test.tsx +++ b/packages/toolkit/src/query/tests/buildInitiate.test.tsx @@ -31,15 +31,6 @@ function getSubscriptions() { ) as unknown as InternalMiddlewareState return internalState?.currentSubscriptions ?? {} } - -function getSubscriptionCount(key: string) { - const subscriptions = getSubscriptions() - const subscriptionsForQueryArg = subscriptions[key] ?? {} - return Object.keys(subscriptionsForQueryArg).length - //return Object.keys(storeRef.store.getState().api.subscriptions[key] || {}) - //.length -} - function isRequestSubscribed(key: string, requestId: string) { const subscriptions = getSubscriptions() return !!subscriptions?.[key]?.[requestId] diff --git a/packages/toolkit/src/query/tests/cacheCollection.test.ts b/packages/toolkit/src/query/tests/cacheCollection.test.ts index 1890be42f3..28e090a4b4 100644 --- a/packages/toolkit/src/query/tests/cacheCollection.test.ts +++ b/packages/toolkit/src/query/tests/cacheCollection.test.ts @@ -6,6 +6,7 @@ import { THIRTY_TWO_BIT_MAX_INT, THIRTY_TWO_BIT_MAX_TIMER_SECONDS, } from '../core/buildMiddleware/cacheCollection' +import { countObjectKeys } from '../utils/countObjectKeys' beforeAll(() => { vi.useFakeTimers() @@ -177,10 +178,10 @@ function storeForApi< let hadQueries = false store.subscribe(() => { const queryState = store.getState().api.queries - if (hadQueries && Object.keys(queryState).length === 0) { + if (hadQueries && countObjectKeys(queryState) === 0) { onCleanup() } - hadQueries = hadQueries || Object.keys(queryState).length > 0 + hadQueries = hadQueries || countObjectKeys(queryState) > 0 }) return { api, store } } diff --git a/packages/toolkit/src/query/tests/cleanup.test.tsx b/packages/toolkit/src/query/tests/cleanup.test.tsx index 49a384ac13..34ca49c7aa 100644 --- a/packages/toolkit/src/query/tests/cleanup.test.tsx +++ b/packages/toolkit/src/query/tests/cleanup.test.tsx @@ -7,6 +7,7 @@ import { createApi, QueryStatus } from '@reduxjs/toolkit/query/react' import { render, waitFor, act, screen } from '@testing-library/react' import { setupApiStore } from './helpers' import { InternalMiddlewareState } from '../core/buildMiddleware/types' +import { countObjectKeys } from '../utils/countObjectKeys' const tick = () => new Promise((res) => setImmediate(res)) @@ -207,7 +208,7 @@ test('Minimizes the number of subscription dispatches when multiple components a const subscriptions = getSubscriptionsA() - expect(Object.keys(subscriptions!).length).toBe(NUM_LIST_ITEMS) + expect(countObjectKeys(subscriptions!)).toBe(NUM_LIST_ITEMS) expect(actionTypes).toEqual([ 'api/config/middlewareRegistered', diff --git a/packages/toolkit/src/query/utils/countObjectKeys.ts b/packages/toolkit/src/query/utils/countObjectKeys.ts new file mode 100644 index 0000000000..b0de9834b5 --- /dev/null +++ b/packages/toolkit/src/query/utils/countObjectKeys.ts @@ -0,0 +1,14 @@ +// Fast method for counting an object's keys +// without resorting to `Object.keys(obj).length +// Will this make a big difference in perf? Probably not +// But we can save a few allocations. + +export function countObjectKeys(obj: Record) { + let count = 0 + + for (const _key in obj) { + count++ + } + + return count +} From e0c3869b812994d843294be69b62300280dd1aef Mon Sep 17 00:00:00 2001 From: Mark Erikson Date: Thu, 26 Oct 2023 21:48:26 -0400 Subject: [PATCH 317/412] Return an object of selectors from the middleware probe --- .../toolkit/src/query/core/buildInitiate.ts | 4 ++- .../core/buildMiddleware/batchActions.ts | 31 ++++++++++++----- .../src/query/core/buildMiddleware/types.ts | 6 ++++ packages/toolkit/src/query/core/buildSlice.ts | 2 +- .../toolkit/src/query/react/buildHooks.ts | 25 +++++++------- .../src/query/tests/buildHooks.test.tsx | 19 ++++------- .../src/query/tests/buildInitiate.test.tsx | 20 +++++------ .../toolkit/src/query/tests/cleanup.test.tsx | 33 ++++++++----------- .../toolkit/src/query/tests/polling.test.tsx | 15 +++++---- 9 files changed, 84 insertions(+), 71 deletions(-) diff --git a/packages/toolkit/src/query/core/buildInitiate.ts b/packages/toolkit/src/query/core/buildInitiate.ts index a21797ab3b..1b3a1e0aa1 100644 --- a/packages/toolkit/src/query/core/buildInitiate.ts +++ b/packages/toolkit/src/query/core/buildInitiate.ts @@ -266,7 +266,9 @@ export function buildInitiate({ function middlewareWarning(dispatch: Dispatch) { if (process.env.NODE_ENV !== 'production') { if ((middlewareWarning as any).triggered) return - const returnedValue = dispatch(api.internalActions.getRTKQInternalState()) + const returnedValue = dispatch( + api.internalActions.internal_getRTKQSubscriptions() + ) ;(middlewareWarning as any).triggered = true diff --git a/packages/toolkit/src/query/core/buildMiddleware/batchActions.ts b/packages/toolkit/src/query/core/buildMiddleware/batchActions.ts index d7e618301e..f941bcd544 100644 --- a/packages/toolkit/src/query/core/buildMiddleware/batchActions.ts +++ b/packages/toolkit/src/query/core/buildMiddleware/batchActions.ts @@ -1,13 +1,11 @@ -import type { InternalHandlerBuilder, InternalMiddlewareState } from './types' +import type { InternalHandlerBuilder, SubscriptionSelectors } from './types' import type { SubscriptionState } from '../apiState' import { produceWithPatches } from 'immer' import type { Action } from '@reduxjs/toolkit' +import { countObjectKeys } from '../../utils/countObjectKeys' export const buildBatchedActionsHandler: InternalHandlerBuilder< - [ - actionShouldContinue: boolean, - returnValue: InternalMiddlewareState | boolean - ] + [actionShouldContinue: boolean, returnValue: SubscriptionSelectors | boolean] > = ({ api, queryThunk, internalState }) => { const subscriptionsPrefix = `${api.reducerPath}/subscriptions` @@ -82,12 +80,29 @@ export const buildBatchedActionsHandler: InternalHandlerBuilder< return mutated } + const getSubscriptions = () => internalState.currentSubscriptions + const getSubscriptionCount = (queryCacheKey: string) => { + const subscriptions = getSubscriptions() + const subscriptionsForQueryArg = subscriptions[queryCacheKey] ?? {} + return countObjectKeys(subscriptionsForQueryArg) + } + const isRequestSubscribed = (queryCacheKey: string, requestId: string) => { + const subscriptions = getSubscriptions() + return !!subscriptions?.[queryCacheKey]?.[requestId] + } + + const subscriptionSelectors: SubscriptionSelectors = { + getSubscriptions, + getSubscriptionCount, + isRequestSubscribed, + } + return ( action, mwApi ): [ actionShouldContinue: boolean, - result: InternalMiddlewareState | boolean + result: SubscriptionSelectors | boolean ] => { if (!previousSubscriptions) { // Initialize it the first time this handler runs @@ -106,8 +121,8 @@ export const buildBatchedActionsHandler: InternalHandlerBuilder< // We return the internal state reference so that hooks // can do their own checks to see if they're still active. // It's stupid and hacky, but it does cut down on some dispatch calls. - if (api.internalActions.getRTKQInternalState.match(action)) { - return [false, internalState] + if (api.internalActions.internal_getRTKQSubscriptions.match(action)) { + return [false, subscriptionSelectors] } // Update subscription data based on this action diff --git a/packages/toolkit/src/query/core/buildMiddleware/types.ts b/packages/toolkit/src/query/core/buildMiddleware/types.ts index 474ff6c988..2e41e095f7 100644 --- a/packages/toolkit/src/query/core/buildMiddleware/types.ts +++ b/packages/toolkit/src/query/core/buildMiddleware/types.ts @@ -32,6 +32,12 @@ export interface InternalMiddlewareState { currentSubscriptions: SubscriptionState } +export interface SubscriptionSelectors { + getSubscriptions: () => SubscriptionState + getSubscriptionCount: (queryCacheKey: string) => number + isRequestSubscribed: (queryCacheKey: string, requestId: string) => boolean +} + export interface BuildMiddlewareInput< Definitions extends EndpointDefinitions, ReducerPath extends string, diff --git a/packages/toolkit/src/query/core/buildSlice.ts b/packages/toolkit/src/query/core/buildSlice.ts index 2fb15b255d..bb712281a9 100644 --- a/packages/toolkit/src/query/core/buildSlice.ts +++ b/packages/toolkit/src/query/core/buildSlice.ts @@ -443,7 +443,7 @@ export function buildSlice({ ) { // Dummy }, - getRTKQInternalState() {}, + internal_getRTKQSubscriptions() {}, }, }) diff --git a/packages/toolkit/src/query/react/buildHooks.ts b/packages/toolkit/src/query/react/buildHooks.ts index 679fedbbd0..ba86d84371 100644 --- a/packages/toolkit/src/query/react/buildHooks.ts +++ b/packages/toolkit/src/query/react/buildHooks.ts @@ -53,7 +53,10 @@ import { UNINITIALIZED_VALUE } from './constants' import { useShallowStableValue } from './useShallowStableValue' import type { BaseQueryFn } from '../baseQueryTypes' import { defaultSerializeQueryArgs } from '../defaultSerializeQueryArgs' -import { InternalMiddlewareState } from '../core/buildMiddleware/types' +import { + InternalMiddlewareState, + SubscriptionSelectors, +} from '../core/buildMiddleware/types' // Copy-pasted from React-Redux export const useIsomorphicLayoutEffect = @@ -682,10 +685,10 @@ export function buildHooks({ Definitions > const dispatch = useDispatch>() - const internalStateRef = useRef(null) - if (!internalStateRef.current) { + const subscriptionSelectorsRef = useRef() + if (!subscriptionSelectorsRef.current) { const returnedValue = dispatch( - api.internalActions.getRTKQInternalState() + api.internalActions.internal_getRTKQSubscriptions() ) if (process.env.NODE_ENV !== 'production') { @@ -700,8 +703,8 @@ export function buildHooks({ } } - internalStateRef.current = - returnedValue as unknown as InternalMiddlewareState + subscriptionSelectorsRef.current = + returnedValue as unknown as SubscriptionSelectors } const stableArg = useStableQueryArgs( skip ? skipToken : arg, @@ -726,15 +729,15 @@ export function buildHooks({ let { queryCacheKey, requestId } = promiseRef.current || {} - // HACK We've saved the middleware internal state into a ref, - // and that state object gets directly mutated. But, we've _got_ a reference - // to it locally, so we can just read the data directly here in the hook. + // HACK We've saved the middleware subscription lookup callbacks into a ref, + // so we can directly check here if the subscription exists for this query. let currentRenderHasSubscription = false if (queryCacheKey && requestId) { currentRenderHasSubscription = - !!internalStateRef.current?.currentSubscriptions?.[queryCacheKey]?.[ + subscriptionSelectorsRef.current.isRequestSubscribed( + queryCacheKey, requestId - ] + ) } const subscriptionRemoved = diff --git a/packages/toolkit/src/query/tests/buildHooks.test.tsx b/packages/toolkit/src/query/tests/buildHooks.test.tsx index 5d520fb888..d10d156514 100644 --- a/packages/toolkit/src/query/tests/buildHooks.test.tsx +++ b/packages/toolkit/src/query/tests/buildHooks.test.tsx @@ -37,7 +37,7 @@ import type { SubscriptionOptions } from '@reduxjs/toolkit/dist/query/core/apiSt import type { SerializedError } from '@reduxjs/toolkit' import { createListenerMiddleware, configureStore } from '@reduxjs/toolkit' import { delay } from '../../utils' -import type { InternalMiddlewareState } from '../core/buildMiddleware/types' +import type { SubscriptionSelectors } from '../core/buildMiddleware/types' import { countObjectKeys } from '../utils/countObjectKeys' // Just setup a temporary in-memory counter for tests that `getIncrementedAmount`. @@ -140,18 +140,8 @@ const storeRef = setupApiStore( } ) -function getSubscriptions() { - const internalState = storeRef.store.dispatch( - api.internalActions.getRTKQInternalState() - ) as unknown as InternalMiddlewareState - return internalState?.currentSubscriptions ?? {} -} - -function getSubscriptionCount(key: string) { - const subscriptions = getSubscriptions() - const subscriptionsForQueryArg = subscriptions[key] ?? {} - return countObjectKeys(subscriptionsForQueryArg) -} +let getSubscriptions: SubscriptionSelectors['getSubscriptions'] +let getSubscriptionCount: SubscriptionSelectors['getSubscriptionCount'] beforeEach(() => { actions = [] @@ -161,6 +151,9 @@ beforeEach(() => { actions.push(action) }, }) + ;({ getSubscriptions, getSubscriptionCount } = storeRef.store.dispatch( + api.internalActions.internal_getRTKQSubscriptions() + ) as unknown as SubscriptionSelectors) }) afterEach(() => { diff --git a/packages/toolkit/src/query/tests/buildInitiate.test.tsx b/packages/toolkit/src/query/tests/buildInitiate.test.tsx index cd68cd5af8..27925d14b2 100644 --- a/packages/toolkit/src/query/tests/buildInitiate.test.tsx +++ b/packages/toolkit/src/query/tests/buildInitiate.test.tsx @@ -1,5 +1,5 @@ import { createApi } from '../core' -import { InternalMiddlewareState } from '../core/buildMiddleware/types' +import type { SubscriptionSelectors } from '../core/buildMiddleware/types' import { fakeBaseQuery } from '../fakeBaseQuery' import { setupApiStore } from './helpers' @@ -25,16 +25,14 @@ const api = createApi({ const storeRef = setupApiStore(api) -function getSubscriptions() { - const internalState = storeRef.store.dispatch( - api.internalActions.getRTKQInternalState() - ) as unknown as InternalMiddlewareState - return internalState?.currentSubscriptions ?? {} -} -function isRequestSubscribed(key: string, requestId: string) { - const subscriptions = getSubscriptions() - return !!subscriptions?.[key]?.[requestId] -} +let getSubscriptions: SubscriptionSelectors['getSubscriptions'] +let isRequestSubscribed: SubscriptionSelectors['isRequestSubscribed'] + +beforeEach(() => { + ;({ getSubscriptions, isRequestSubscribed } = storeRef.store.dispatch( + api.internalActions.internal_getRTKQSubscriptions() + ) as unknown as SubscriptionSelectors) +}) test('multiple synchonrous initiate calls with pre-existing cache entry', async () => { const { store, api } = storeRef diff --git a/packages/toolkit/src/query/tests/cleanup.test.tsx b/packages/toolkit/src/query/tests/cleanup.test.tsx index 34ca49c7aa..cb1ff750fa 100644 --- a/packages/toolkit/src/query/tests/cleanup.test.tsx +++ b/packages/toolkit/src/query/tests/cleanup.test.tsx @@ -1,13 +1,12 @@ // tests for "cleanup-after-unsubscribe" behaviour import { vi } from 'vitest' -import React, { Profiler, ProfilerOnRenderCallback } from 'react' +import React from 'react' import { createListenerMiddleware } from '@reduxjs/toolkit' import { createApi, QueryStatus } from '@reduxjs/toolkit/query/react' import { render, waitFor, act, screen } from '@testing-library/react' import { setupApiStore } from './helpers' -import { InternalMiddlewareState } from '../core/buildMiddleware/types' -import { countObjectKeys } from '../utils/countObjectKeys' +import { SubscriptionSelectors } from '../core/buildMiddleware/types' const tick = () => new Promise((res) => setImmediate(res)) @@ -161,28 +160,26 @@ test('Minimizes the number of subscription dispatches when multiple components a withoutTestLifecycles: true, }) - function getSubscriptions() { - const internalState = storeRef.store.dispatch( - api.internalActions.getRTKQInternalState() - ) as unknown as InternalMiddlewareState - return internalState?.currentSubscriptions ?? {} - } - - let getSubscriptionsA = () => { - return getSubscriptions()['a(undefined)'] - } - let actionTypes: unknown[] = [] listenerMiddleware.startListening({ predicate: () => true, effect: (action) => { - if (!action.type.includes('subscriptionsUpdated')) { - actionTypes.push(action.type) + if ( + action.type.includes('subscriptionsUpdated') || + action.type.includes('internal_') + ) { + return } + + actionTypes.push(action.type) }, }) + const { getSubscriptionCount } = storeRef.store.dispatch( + api.internalActions.internal_getRTKQSubscriptions() + ) as unknown as SubscriptionSelectors + const NUM_LIST_ITEMS = 1000 function ParentComponent() { @@ -206,9 +203,7 @@ test('Minimizes the number of subscription dispatches when multiple components a return screen.getAllByText(/42/).length > 0 }) - const subscriptions = getSubscriptionsA() - - expect(countObjectKeys(subscriptions!)).toBe(NUM_LIST_ITEMS) + expect(getSubscriptionCount('a(undefined)')).toBe(NUM_LIST_ITEMS) expect(actionTypes).toEqual([ 'api/config/middlewareRegistered', diff --git a/packages/toolkit/src/query/tests/polling.test.tsx b/packages/toolkit/src/query/tests/polling.test.tsx index d3231253aa..d5d3cd20f8 100644 --- a/packages/toolkit/src/query/tests/polling.test.tsx +++ b/packages/toolkit/src/query/tests/polling.test.tsx @@ -2,7 +2,7 @@ import { vi } from 'vitest' import { createApi } from '@reduxjs/toolkit/query' import { setupApiStore, waitMs } from './helpers' import { delay } from '../../utils' -import type { InternalMiddlewareState } from '../core/buildMiddleware/types' +import type { SubscriptionSelectors } from '../core/buildMiddleware/types' const mockBaseQuery = vi .fn() @@ -24,12 +24,13 @@ const { getPosts } = api.endpoints const storeRef = setupApiStore(api) -function getSubscriptions() { - const internalState = storeRef.store.dispatch( - api.internalActions.getRTKQInternalState() - ) as unknown as InternalMiddlewareState - return internalState?.currentSubscriptions ?? {} -} +let getSubscriptions: SubscriptionSelectors['getSubscriptions'] + +beforeEach(() => { + ;({ getSubscriptions } = storeRef.store.dispatch( + api.internalActions.internal_getRTKQSubscriptions() + ) as unknown as SubscriptionSelectors) +}) const getSubscribersForQueryCacheKey = (queryCacheKey: string) => getSubscriptions()[queryCacheKey] || {} From 3dd8f485341eada91400343bfdfb1dcec83906a0 Mon Sep 17 00:00:00 2001 From: Mark Erikson Date: Sat, 28 Oct 2023 18:29:00 -0400 Subject: [PATCH 318/412] Stop re-exporting `autotrackMemoize` from Reselect --- packages/toolkit/src/index.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/toolkit/src/index.ts b/packages/toolkit/src/index.ts index 8ee73148de..2b8fc18ad2 100644 --- a/packages/toolkit/src/index.ts +++ b/packages/toolkit/src/index.ts @@ -15,7 +15,6 @@ export { createSelector, createSelectorCreator, defaultMemoize, - autotrackMemoize, weakMapMemoize, } from 'reselect' export type { From 8730c5e7e327df3890d5fd1730e6f3af61c2c591 Mon Sep 17 00:00:00 2001 From: Georg Wicke-Arndt Date: Mon, 23 Jan 2023 22:43:56 +0100 Subject: [PATCH 319/412] Write test that demonstrates the race condition --- .../src/query/tests/raceConditions.test.ts | 108 ++++++++++++++++++ 1 file changed, 108 insertions(+) create mode 100644 packages/toolkit/src/query/tests/raceConditions.test.ts diff --git a/packages/toolkit/src/query/tests/raceConditions.test.ts b/packages/toolkit/src/query/tests/raceConditions.test.ts new file mode 100644 index 0000000000..728e837507 --- /dev/null +++ b/packages/toolkit/src/query/tests/raceConditions.test.ts @@ -0,0 +1,108 @@ +import { createApi, QueryStatus } from '@reduxjs/toolkit/query' +import { getLog } from 'console-testing-library' +import { actionsReducer, setupApiStore, waitMs } from './helpers' + +// We need to be able to control when which query resolves to simulate race +// conditions properly, that's the purpose of this factory. +const createPromiseFactory = () => { + const resolveQueue: (() => void)[] = [] + const createPromise = () => + new Promise((resolve) => { + resolveQueue.push(resolve) + }) + const resolveOldest = () => { + resolveQueue.shift()?.() + } + return { createPromise, resolveOldest } +} + +const getEatenBananaPromises = createPromiseFactory() +const eatBananaPromises = createPromiseFactory() + +let eatenBananas = 0 +const api = createApi({ + baseQuery: () => undefined as any, + tagTypes: ['Banana'], + endpoints: (build) => ({ + // Eat a banana. + eatBanana: build.mutation({ + queryFn: async () => { + await eatBananaPromises.createPromise() + eatenBananas += 1 + return { data: null, meta: {} } + }, + invalidatesTags: ['Banana'], + }), + + // Get the number of eaten bananas. + getEatenBananas: build.query({ + queryFn: async (arg, arg1, arg2, arg3) => { + const result = eatenBananas + await getEatenBananaPromises.createPromise() + return { data: result } + }, + providesTags: ['Banana'], + }), + }), +}) +const { getEatenBananas, eatBanana } = api.endpoints + +const storeRef = setupApiStore(api, { + ...actionsReducer, +}) + +it('invalidates a query after a corresponding mutation', async () => { + eatenBananas = 0 + + const query = storeRef.store.dispatch(getEatenBananas.initiate()) + const getQueryState = () => + storeRef.store.getState().api.queries[query.queryCacheKey] + getEatenBananaPromises.resolveOldest() + await waitMs(2) + + expect(getQueryState()?.data).toBe(0) + expect(getQueryState()?.status).toBe(QueryStatus.fulfilled) + + const mutation = storeRef.store.dispatch(eatBanana.initiate()) + const getMutationState = () => + storeRef.store.getState().api.mutations[mutation.requestId] + eatBananaPromises.resolveOldest() + await waitMs(2) + + expect(getMutationState()?.status).toBe(QueryStatus.fulfilled) + expect(getQueryState()?.data).toBe(0) + expect(getQueryState()?.status).toBe(QueryStatus.pending) + + getEatenBananaPromises.resolveOldest() + await waitMs(2) + + expect(getQueryState()?.data).toBe(1) + expect(getQueryState()?.status).toBe(QueryStatus.fulfilled) +}) + +it('invalidates a query whose corresponding mutation finished while the query was in flight', async () => { + eatenBananas = 0 + + const query = storeRef.store.dispatch(getEatenBananas.initiate()) + const getQueryState = () => + storeRef.store.getState().api.queries[query.queryCacheKey] + + const mutation = storeRef.store.dispatch(eatBanana.initiate()) + const getMutationState = () => + storeRef.store.getState().api.mutations[mutation.requestId] + eatBananaPromises.resolveOldest() + await waitMs(2) + expect(getMutationState()?.status).toBe(QueryStatus.fulfilled) + + getEatenBananaPromises.resolveOldest() + await waitMs(2) + expect(getQueryState()?.data).toBe(0) + expect(getQueryState()?.status).toBe(QueryStatus.pending) + + // should already be refetching + getEatenBananaPromises.resolveOldest() + await waitMs(2) + + expect(getQueryState()?.status).toBe(QueryStatus.fulfilled) + expect(getQueryState()?.data).toBe(1) +}) From 15511e395ff76074c71daf7306623517c903d3f9 Mon Sep 17 00:00:00 2001 From: Georg Wicke-Arndt Date: Mon, 23 Jan 2023 22:48:28 +0100 Subject: [PATCH 320/412] Delay tag invalidations if queries are running --- packages/toolkit/src/query/core/apiState.ts | 2 + .../buildMiddleware/invalidationByTags.ts | 60 ++++++++++++++++--- packages/toolkit/src/query/core/buildSlice.ts | 16 +++++ .../src/query/tests/buildSlice.test.ts | 1 + 4 files changed, 71 insertions(+), 8 deletions(-) diff --git a/packages/toolkit/src/query/core/apiState.ts b/packages/toolkit/src/query/core/apiState.ts index dad155b3b8..a4b34432f2 100644 --- a/packages/toolkit/src/query/core/apiState.ts +++ b/packages/toolkit/src/query/core/apiState.ts @@ -7,6 +7,7 @@ import type { BaseEndpointDefinition, ResultTypeFrom, QueryArgFrom, + FullTagDescription, } from '../endpointDefinitions' import type { Id, WithRequiredProp } from '../tsHelpers' @@ -228,6 +229,7 @@ export type CombinedState< provided: InvalidationState subscriptions: SubscriptionState config: ConfigState + pendingTagInvalidations: FullTagDescription[] } export type InvalidationState = { diff --git a/packages/toolkit/src/query/core/buildMiddleware/invalidationByTags.ts b/packages/toolkit/src/query/core/buildMiddleware/invalidationByTags.ts index e92c692d3a..fddc8350e4 100644 --- a/packages/toolkit/src/query/core/buildMiddleware/invalidationByTags.ts +++ b/packages/toolkit/src/query/core/buildMiddleware/invalidationByTags.ts @@ -1,4 +1,9 @@ -import { isAnyOf, isFulfilled, isRejectedWithValue } from '../rtkImports' +import { + isAnyOf, + isFulfilled, + isRejected, + isRejectedWithValue, +} from '../rtkImports' import type { FullTagDescription } from '../../endpointDefinitions' import { calculateProvidedBy } from '../../endpointDefinitions' @@ -18,20 +23,32 @@ export const buildInvalidationByTagsHandler: InternalHandlerBuilder = ({ context, context: { endpointDefinitions }, mutationThunk, + queryThunk, api, assertTagType, refetchQuery, internalState, }) => { - const { removeQueryResult } = api.internalActions + const { + removeQueryResult, + addPendingTagInvalidations, + clearPendingTagInvalidations, + } = api.internalActions const isThunkActionWithTags = isAnyOf( isFulfilled(mutationThunk), isRejectedWithValue(mutationThunk) ) + const isQueryEnd = isAnyOf( + isFulfilled(mutationThunk), + isRejected(mutationThunk), + isFulfilled(queryThunk), + isRejected(queryThunk) + ) + const handler: ApiMiddlewareInternalHandler = (action, mwApi) => { if (isThunkActionWithTags(action)) { - invalidateTags( + processPendingTagInvalidations( calculateProvidedByThunk( action, 'invalidatesTags', @@ -41,10 +58,10 @@ export const buildInvalidationByTagsHandler: InternalHandlerBuilder = ({ mwApi, internalState ) - } - - if (api.util.invalidateTags.match(action)) { - invalidateTags( + } else if (isQueryEnd(action)) { + processPendingTagInvalidations([], mwApi, internalState) + } else if (api.util.invalidateTags.match(action)) { + processPendingTagInvalidations( calculateProvidedBy( action.payload, undefined, @@ -59,7 +76,7 @@ export const buildInvalidationByTagsHandler: InternalHandlerBuilder = ({ } } - function invalidateTags( + function processPendingTagInvalidations( tags: readonly FullTagDescription[], mwApi: SubMiddlewareApi, internalState: InternalMiddlewareState @@ -68,9 +85,36 @@ export const buildInvalidationByTagsHandler: InternalHandlerBuilder = ({ const state = rootState[reducerPath] + const hasPendingQueries = Object.values(state.queries).some( + (x) => x?.status === QueryStatus.pending + ) + const hasPendingMutations = Object.values(state.mutations).some( + (x) => x?.status === QueryStatus.pending + ) + + if (hasPendingQueries || hasPendingMutations) { + if (tags && tags.length > 0) + mwApi.dispatch(addPendingTagInvalidations(tags)) + } else { + handleInvalidatedTags(tags, mwApi) + } + } + + function handleInvalidatedTags( + newTags: readonly FullTagDescription[], + mwApi: SubMiddlewareApi + ) { + const rootState = mwApi.getState() + const state = rootState[reducerPath] + + const tags = [...state.pendingTagInvalidations, ...newTags] + const toInvalidate = api.util.selectInvalidatedBy(rootState, tags) context.batch(() => { + if (state.pendingTagInvalidations.length > 0) + mwApi.dispatch(clearPendingTagInvalidations()) + const valuesArray = Array.from(toInvalidate.values()) for (const { queryCacheKey } of valuesArray) { const querySubState = state.queries[queryCacheKey] diff --git a/packages/toolkit/src/query/core/buildSlice.ts b/packages/toolkit/src/query/core/buildSlice.ts index bb712281a9..920329e296 100644 --- a/packages/toolkit/src/query/core/buildSlice.ts +++ b/packages/toolkit/src/query/core/buildSlice.ts @@ -420,6 +420,20 @@ export function buildSlice({ }, }) + const pendingTagInvalidationsSlice = createSlice({ + name: `${reducerPath}/pendingTagInvalidations`, + initialState: [] as FullTagDescription[], + reducers: { + addPendingTagInvalidations: ( + state, + action: PayloadAction[]> + ) => { + state.push(...action.payload) + }, + clearPendingTagInvalidations: () => [], + }, + }) + // Dummy slice to generate actions const subscriptionSlice = createSlice({ name: `${reducerPath}/subscriptions`, @@ -502,6 +516,7 @@ export function buildSlice({ provided: invalidationSlice.reducer, subscriptions: internalSubscriptionsSlice.reducer, config: configSlice.reducer, + pendingTagInvalidations: pendingTagInvalidationsSlice.reducer, }) const reducer: typeof combinedReducer = (state, action) => @@ -514,6 +529,7 @@ export function buildSlice({ ...internalSubscriptionsSlice.actions, ...mutationSlice.actions, ...invalidationSlice.actions, + ...pendingTagInvalidationsSlice.actions, resetApiState, } diff --git a/packages/toolkit/src/query/tests/buildSlice.test.ts b/packages/toolkit/src/query/tests/buildSlice.test.ts index 6ca643b286..42f6c0c93b 100644 --- a/packages/toolkit/src/query/tests/buildSlice.test.ts +++ b/packages/toolkit/src/query/tests/buildSlice.test.ts @@ -60,6 +60,7 @@ describe('buildSlice', () => { refetchOnReconnect: false, }, mutations: {}, + pendingTagInvalidations: [], provided: expect.any(Object), queries: { 'getUser(1)': { From 001d7a17581daa7a375225cba17fd6f875298da7 Mon Sep 17 00:00:00 2001 From: Georg Wicke-Arndt Date: Mon, 23 Jan 2023 23:10:21 +0100 Subject: [PATCH 321/412] Add minor optimization --- .../toolkit/src/query/core/buildMiddleware/invalidationByTags.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/toolkit/src/query/core/buildMiddleware/invalidationByTags.ts b/packages/toolkit/src/query/core/buildMiddleware/invalidationByTags.ts index fddc8350e4..f5657aa6de 100644 --- a/packages/toolkit/src/query/core/buildMiddleware/invalidationByTags.ts +++ b/packages/toolkit/src/query/core/buildMiddleware/invalidationByTags.ts @@ -108,6 +108,7 @@ export const buildInvalidationByTagsHandler: InternalHandlerBuilder = ({ const state = rootState[reducerPath] const tags = [...state.pendingTagInvalidations, ...newTags] + if (tags.length === 0) return const toInvalidate = api.util.selectInvalidatedBy(rootState, tags) From 0148e466136ea51ca1bec42bec1950bd3754fd23 Mon Sep 17 00:00:00 2001 From: Georg Wicke-Arndt Date: Wed, 25 Jan 2023 12:32:19 +0100 Subject: [PATCH 322/412] Add invalidateImmediately setting --- packages/toolkit/src/query/apiTypes.ts | 1 + packages/toolkit/src/query/core/apiState.ts | 2 ++ .../query/core/buildMiddleware/invalidationByTags.ts | 5 +++++ packages/toolkit/src/query/core/module.ts | 2 ++ packages/toolkit/src/query/createApi.ts | 10 ++++++++++ packages/toolkit/src/query/tests/buildSlice.test.ts | 1 + 6 files changed, 21 insertions(+) diff --git a/packages/toolkit/src/query/apiTypes.ts b/packages/toolkit/src/query/apiTypes.ts index 3df10b2741..98fb693def 100644 --- a/packages/toolkit/src/query/apiTypes.ts +++ b/packages/toolkit/src/query/apiTypes.ts @@ -45,6 +45,7 @@ export type Module = { | 'refetchOnMountOrArgChange' | 'refetchOnFocus' | 'refetchOnReconnect' + | 'invalidateImmediately' | 'tagTypes' >, context: ApiContext diff --git a/packages/toolkit/src/query/core/apiState.ts b/packages/toolkit/src/query/core/apiState.ts index a4b34432f2..d55744ae40 100644 --- a/packages/toolkit/src/query/core/apiState.ts +++ b/packages/toolkit/src/query/core/apiState.ts @@ -167,6 +167,7 @@ export type QuerySubState> = Id< > & { error: undefined }) | ({ status: QueryStatus.pending + pendingTagInvalidations: FullTagDescription[] } & BaseQuerySubState) | ({ status: QueryStatus.rejected @@ -256,6 +257,7 @@ export type ConfigState = RefetchConfigOptions & { export type ModifiableConfigState = { keepUnusedDataFor: number + invalidateImmediately: boolean } & RefetchConfigOptions export type MutationState = { diff --git a/packages/toolkit/src/query/core/buildMiddleware/invalidationByTags.ts b/packages/toolkit/src/query/core/buildMiddleware/invalidationByTags.ts index f5657aa6de..340a126710 100644 --- a/packages/toolkit/src/query/core/buildMiddleware/invalidationByTags.ts +++ b/packages/toolkit/src/query/core/buildMiddleware/invalidationByTags.ts @@ -85,6 +85,11 @@ export const buildInvalidationByTagsHandler: InternalHandlerBuilder = ({ const state = rootState[reducerPath] + if (state.config.invalidateImmediately) { + handleInvalidatedTags(tags, mwApi) + return + } + const hasPendingQueries = Object.values(state.queries).some( (x) => x?.status === QueryStatus.pending ) diff --git a/packages/toolkit/src/query/core/module.ts b/packages/toolkit/src/query/core/module.ts index 59b2514070..b283a9bf04 100644 --- a/packages/toolkit/src/query/core/module.ts +++ b/packages/toolkit/src/query/core/module.ts @@ -452,6 +452,7 @@ export const coreModule = (): Module => ({ refetchOnMountOrArgChange, refetchOnFocus, refetchOnReconnect, + invalidateImmediately, }, context ) { @@ -514,6 +515,7 @@ export const coreModule = (): Module => ({ refetchOnMountOrArgChange, keepUnusedDataFor, reducerPath, + invalidateImmediately, }, }) diff --git a/packages/toolkit/src/query/createApi.ts b/packages/toolkit/src/query/createApi.ts index 26ec2fb2d4..1e50c9f1db 100644 --- a/packages/toolkit/src/query/createApi.ts +++ b/packages/toolkit/src/query/createApi.ts @@ -151,6 +151,15 @@ export interface CreateApiOptions< * Note: requires [`setupListeners`](./setupListeners) to have been called. */ refetchOnReconnect?: boolean + /** + * Defaults to `false`. This setting allows you to disable a consistency measure that delay tag invalidations until all pending queries and mutations are settled. + * + * Context: If a mutation finishes while a query is pending, then RTK-query has to wait until the query is finished until it can invalidate any tags, since the query itself could provide a tag that the mutation will invalidate. + * RTK-query also waits with the tag invalidations if another mutation is pending, to "batch" the tag invalidations if two mutations invalidate the same tag, avoiding unnecessary re-fetching. + * + * If you constantly have some queries running, this can delay the tag invalidations. In this case, you can enable this setting to opt-out of the "correct" behavior and refetch immediately when a mutation finishes instead. + */ + invalidateImmediately?: boolean /** * A function that is passed every dispatched action. If this returns something other than `undefined`, * that return value will be used to rehydrate fulfilled & errored queries. @@ -255,6 +264,7 @@ export function buildCreateApi, ...Module[]]>( refetchOnMountOrArgChange: false, refetchOnFocus: false, refetchOnReconnect: false, + invalidateImmediately: false, ...options, extractRehydrationInfo, serializeQueryArgs(queryArgsApi) { diff --git a/packages/toolkit/src/query/tests/buildSlice.test.ts b/packages/toolkit/src/query/tests/buildSlice.test.ts index 42f6c0c93b..db25a4472c 100644 --- a/packages/toolkit/src/query/tests/buildSlice.test.ts +++ b/packages/toolkit/src/query/tests/buildSlice.test.ts @@ -51,6 +51,7 @@ describe('buildSlice', () => { api: { config: { focused: true, + invalidateImmediately: false, keepUnusedDataFor: 60, middlewareRegistered: true, online: true, From 48c776cb6a84b1aa1de10fb8c39e766e198aa879 Mon Sep 17 00:00:00 2001 From: Georg Wicke-Arndt Date: Fri, 27 Jan 2023 00:37:34 +0100 Subject: [PATCH 323/412] Make pendingTagInvalidations local --- packages/toolkit/src/query/core/apiState.ts | 2 - .../buildMiddleware/invalidationByTags.ts | 73 +++++++------------ packages/toolkit/src/query/core/buildSlice.ts | 16 ---- .../src/query/tests/buildSlice.test.ts | 1 - 4 files changed, 28 insertions(+), 64 deletions(-) diff --git a/packages/toolkit/src/query/core/apiState.ts b/packages/toolkit/src/query/core/apiState.ts index d55744ae40..6e53265c08 100644 --- a/packages/toolkit/src/query/core/apiState.ts +++ b/packages/toolkit/src/query/core/apiState.ts @@ -167,7 +167,6 @@ export type QuerySubState> = Id< > & { error: undefined }) | ({ status: QueryStatus.pending - pendingTagInvalidations: FullTagDescription[] } & BaseQuerySubState) | ({ status: QueryStatus.rejected @@ -230,7 +229,6 @@ export type CombinedState< provided: InvalidationState subscriptions: SubscriptionState config: ConfigState - pendingTagInvalidations: FullTagDescription[] } export type InvalidationState = { diff --git a/packages/toolkit/src/query/core/buildMiddleware/invalidationByTags.ts b/packages/toolkit/src/query/core/buildMiddleware/invalidationByTags.ts index 340a126710..ab076acb8c 100644 --- a/packages/toolkit/src/query/core/buildMiddleware/invalidationByTags.ts +++ b/packages/toolkit/src/query/core/buildMiddleware/invalidationByTags.ts @@ -5,9 +5,12 @@ import { isRejectedWithValue, } from '../rtkImports' -import type { FullTagDescription } from '../../endpointDefinitions' +import type { + EndpointDefinitions, + FullTagDescription, +} from '../../endpointDefinitions' import { calculateProvidedBy } from '../../endpointDefinitions' -import type { QueryCacheKey } from '../apiState' +import type { CombinedState, QueryCacheKey } from '../apiState' import { QueryStatus } from '../apiState' import { calculateProvidedByThunk } from '../buildThunks' import type { @@ -29,11 +32,7 @@ export const buildInvalidationByTagsHandler: InternalHandlerBuilder = ({ refetchQuery, internalState, }) => { - const { - removeQueryResult, - addPendingTagInvalidations, - clearPendingTagInvalidations, - } = api.internalActions + const { removeQueryResult } = api.internalActions const isThunkActionWithTags = isAnyOf( isFulfilled(mutationThunk), isRejectedWithValue(mutationThunk) @@ -46,22 +45,23 @@ export const buildInvalidationByTagsHandler: InternalHandlerBuilder = ({ isRejected(queryThunk) ) + let pendingTagInvalidations: FullTagDescription[] = [] + const handler: ApiMiddlewareInternalHandler = (action, mwApi) => { if (isThunkActionWithTags(action)) { - processPendingTagInvalidations( + invalidateTags( calculateProvidedByThunk( action, 'invalidatesTags', endpointDefinitions, assertTagType ), - mwApi, - internalState + mwApi ) } else if (isQueryEnd(action)) { - processPendingTagInvalidations([], mwApi, internalState) + invalidateTags([], mwApi) } else if (api.util.invalidateTags.match(action)) { - processPendingTagInvalidations( + invalidateTags( calculateProvidedBy( action.payload, undefined, @@ -70,57 +70,40 @@ export const buildInvalidationByTagsHandler: InternalHandlerBuilder = ({ undefined, assertTagType ), - mwApi, - internalState + mwApi ) } } - function processPendingTagInvalidations( - tags: readonly FullTagDescription[], - mwApi: SubMiddlewareApi, - internalState: InternalMiddlewareState + function hasPendingRequests( + state: CombinedState ) { - const rootState = mwApi.getState() - - const state = rootState[reducerPath] - - if (state.config.invalidateImmediately) { - handleInvalidatedTags(tags, mwApi) - return - } - - const hasPendingQueries = Object.values(state.queries).some( - (x) => x?.status === QueryStatus.pending - ) - const hasPendingMutations = Object.values(state.mutations).some( - (x) => x?.status === QueryStatus.pending - ) - - if (hasPendingQueries || hasPendingMutations) { - if (tags && tags.length > 0) - mwApi.dispatch(addPendingTagInvalidations(tags)) - } else { - handleInvalidatedTags(tags, mwApi) - } + return Object.values({ + ...state.queries, + ...state.mutations, + }).some((x) => x?.status === QueryStatus.pending) } - function handleInvalidatedTags( + function invalidateTags( newTags: readonly FullTagDescription[], mwApi: SubMiddlewareApi ) { const rootState = mwApi.getState() const state = rootState[reducerPath] - const tags = [...state.pendingTagInvalidations, ...newTags] + pendingTagInvalidations.push(...newTags) + + if (!state.config.invalidateImmediately && hasPendingRequests(state)) { + return + } + + const tags = pendingTagInvalidations + pendingTagInvalidations = [] if (tags.length === 0) return const toInvalidate = api.util.selectInvalidatedBy(rootState, tags) context.batch(() => { - if (state.pendingTagInvalidations.length > 0) - mwApi.dispatch(clearPendingTagInvalidations()) - const valuesArray = Array.from(toInvalidate.values()) for (const { queryCacheKey } of valuesArray) { const querySubState = state.queries[queryCacheKey] diff --git a/packages/toolkit/src/query/core/buildSlice.ts b/packages/toolkit/src/query/core/buildSlice.ts index 920329e296..bb712281a9 100644 --- a/packages/toolkit/src/query/core/buildSlice.ts +++ b/packages/toolkit/src/query/core/buildSlice.ts @@ -420,20 +420,6 @@ export function buildSlice({ }, }) - const pendingTagInvalidationsSlice = createSlice({ - name: `${reducerPath}/pendingTagInvalidations`, - initialState: [] as FullTagDescription[], - reducers: { - addPendingTagInvalidations: ( - state, - action: PayloadAction[]> - ) => { - state.push(...action.payload) - }, - clearPendingTagInvalidations: () => [], - }, - }) - // Dummy slice to generate actions const subscriptionSlice = createSlice({ name: `${reducerPath}/subscriptions`, @@ -516,7 +502,6 @@ export function buildSlice({ provided: invalidationSlice.reducer, subscriptions: internalSubscriptionsSlice.reducer, config: configSlice.reducer, - pendingTagInvalidations: pendingTagInvalidationsSlice.reducer, }) const reducer: typeof combinedReducer = (state, action) => @@ -529,7 +514,6 @@ export function buildSlice({ ...internalSubscriptionsSlice.actions, ...mutationSlice.actions, ...invalidationSlice.actions, - ...pendingTagInvalidationsSlice.actions, resetApiState, } diff --git a/packages/toolkit/src/query/tests/buildSlice.test.ts b/packages/toolkit/src/query/tests/buildSlice.test.ts index db25a4472c..ff91bcef3b 100644 --- a/packages/toolkit/src/query/tests/buildSlice.test.ts +++ b/packages/toolkit/src/query/tests/buildSlice.test.ts @@ -61,7 +61,6 @@ describe('buildSlice', () => { refetchOnReconnect: false, }, mutations: {}, - pendingTagInvalidations: [], provided: expect.any(Object), queries: { 'getUser(1)': { From 33c6bd2c023588bc34f099c26ccc43930a80e1df Mon Sep 17 00:00:00 2001 From: Georg Wicke-Arndt Date: Fri, 27 Jan 2023 09:27:07 +0100 Subject: [PATCH 324/412] Inline hasPendingRequests again --- .../core/buildMiddleware/invalidationByTags.ts | 18 +++++++----------- 1 file changed, 7 insertions(+), 11 deletions(-) diff --git a/packages/toolkit/src/query/core/buildMiddleware/invalidationByTags.ts b/packages/toolkit/src/query/core/buildMiddleware/invalidationByTags.ts index ab076acb8c..2f26e64184 100644 --- a/packages/toolkit/src/query/core/buildMiddleware/invalidationByTags.ts +++ b/packages/toolkit/src/query/core/buildMiddleware/invalidationByTags.ts @@ -75,15 +75,6 @@ export const buildInvalidationByTagsHandler: InternalHandlerBuilder = ({ } } - function hasPendingRequests( - state: CombinedState - ) { - return Object.values({ - ...state.queries, - ...state.mutations, - }).some((x) => x?.status === QueryStatus.pending) - } - function invalidateTags( newTags: readonly FullTagDescription[], mwApi: SubMiddlewareApi @@ -93,8 +84,13 @@ export const buildInvalidationByTagsHandler: InternalHandlerBuilder = ({ pendingTagInvalidations.push(...newTags) - if (!state.config.invalidateImmediately && hasPendingRequests(state)) { - return + if (!state.config.invalidateImmediately) { + const hasPendingRequests = Object.values({ + ...state.queries, + ...state.mutations, + }).some((x) => x?.status === QueryStatus.pending) + + if (hasPendingRequests) return } const tags = pendingTagInvalidations From 2bca4e02e715b79abbfeb9310c5fff3e98949e67 Mon Sep 17 00:00:00 2001 From: Georg Wicke-Arndt Date: Thu, 9 Feb 2023 13:26:40 +0100 Subject: [PATCH 325/412] Incorporate review suggestions --- packages/toolkit/src/query/apiTypes.ts | 2 +- packages/toolkit/src/query/core/apiState.ts | 3 +-- .../core/buildMiddleware/invalidationByTags.ts | 10 +++++----- packages/toolkit/src/query/core/buildSlice.ts | 1 - packages/toolkit/src/query/core/module.ts | 4 ++-- packages/toolkit/src/query/createApi.ts | 15 ++++++++------- .../toolkit/src/query/tests/buildSlice.test.ts | 2 +- 7 files changed, 18 insertions(+), 19 deletions(-) diff --git a/packages/toolkit/src/query/apiTypes.ts b/packages/toolkit/src/query/apiTypes.ts index 98fb693def..2aa7992999 100644 --- a/packages/toolkit/src/query/apiTypes.ts +++ b/packages/toolkit/src/query/apiTypes.ts @@ -45,7 +45,7 @@ export type Module = { | 'refetchOnMountOrArgChange' | 'refetchOnFocus' | 'refetchOnReconnect' - | 'invalidateImmediately' + | 'invalidationBehavior' | 'tagTypes' >, context: ApiContext diff --git a/packages/toolkit/src/query/core/apiState.ts b/packages/toolkit/src/query/core/apiState.ts index 6e53265c08..3b1d6624c9 100644 --- a/packages/toolkit/src/query/core/apiState.ts +++ b/packages/toolkit/src/query/core/apiState.ts @@ -7,7 +7,6 @@ import type { BaseEndpointDefinition, ResultTypeFrom, QueryArgFrom, - FullTagDescription, } from '../endpointDefinitions' import type { Id, WithRequiredProp } from '../tsHelpers' @@ -255,7 +254,7 @@ export type ConfigState = RefetchConfigOptions & { export type ModifiableConfigState = { keepUnusedDataFor: number - invalidateImmediately: boolean + invalidationBehavior: 'delayed' | 'immediately' } & RefetchConfigOptions export type MutationState = { diff --git a/packages/toolkit/src/query/core/buildMiddleware/invalidationByTags.ts b/packages/toolkit/src/query/core/buildMiddleware/invalidationByTags.ts index 2f26e64184..07a9887e4e 100644 --- a/packages/toolkit/src/query/core/buildMiddleware/invalidationByTags.ts +++ b/packages/toolkit/src/query/core/buildMiddleware/invalidationByTags.ts @@ -84,11 +84,11 @@ export const buildInvalidationByTagsHandler: InternalHandlerBuilder = ({ pendingTagInvalidations.push(...newTags) - if (!state.config.invalidateImmediately) { - const hasPendingRequests = Object.values({ - ...state.queries, - ...state.mutations, - }).some((x) => x?.status === QueryStatus.pending) + if (state.config.invalidationBehavior === 'delayed') { + const hasPendingRequests = [ + ...Object.values(state.queries), + ...Object.values(state.mutations), + ].some((x) => x?.status === QueryStatus.pending) if (hasPendingRequests) return } diff --git a/packages/toolkit/src/query/core/buildSlice.ts b/packages/toolkit/src/query/core/buildSlice.ts index bb712281a9..cfda46e183 100644 --- a/packages/toolkit/src/query/core/buildSlice.ts +++ b/packages/toolkit/src/query/core/buildSlice.ts @@ -28,7 +28,6 @@ import { calculateProvidedByThunk } from './buildThunks' import type { AssertTagTypes, EndpointDefinitions, - FullTagDescription, QueryDefinition, } from '../endpointDefinitions' import type { Patch } from 'immer' diff --git a/packages/toolkit/src/query/core/module.ts b/packages/toolkit/src/query/core/module.ts index b283a9bf04..bc95e8df4e 100644 --- a/packages/toolkit/src/query/core/module.ts +++ b/packages/toolkit/src/query/core/module.ts @@ -452,7 +452,7 @@ export const coreModule = (): Module => ({ refetchOnMountOrArgChange, refetchOnFocus, refetchOnReconnect, - invalidateImmediately, + invalidationBehavior, }, context ) { @@ -515,7 +515,7 @@ export const coreModule = (): Module => ({ refetchOnMountOrArgChange, keepUnusedDataFor, reducerPath, - invalidateImmediately, + invalidationBehavior, }, }) diff --git a/packages/toolkit/src/query/createApi.ts b/packages/toolkit/src/query/createApi.ts index 1e50c9f1db..8de1107c3d 100644 --- a/packages/toolkit/src/query/createApi.ts +++ b/packages/toolkit/src/query/createApi.ts @@ -152,14 +152,15 @@ export interface CreateApiOptions< */ refetchOnReconnect?: boolean /** - * Defaults to `false`. This setting allows you to disable a consistency measure that delay tag invalidations until all pending queries and mutations are settled. + * Defaults to `'immediately'`. This setting allows you to control when tags are invalidated after a mutation. * - * Context: If a mutation finishes while a query is pending, then RTK-query has to wait until the query is finished until it can invalidate any tags, since the query itself could provide a tag that the mutation will invalidate. - * RTK-query also waits with the tag invalidations if another mutation is pending, to "batch" the tag invalidations if two mutations invalidate the same tag, avoiding unnecessary re-fetching. - * - * If you constantly have some queries running, this can delay the tag invalidations. In this case, you can enable this setting to opt-out of the "correct" behavior and refetch immediately when a mutation finishes instead. + * - `'immediately'`: Queries are invalidated instantly after the mutation finished, even if they are running. + * If the query provides tags that were invalidated while it ran, it won't be re-fetched. + * - `'delayed'`: Invalidation only happens after all queries and mutations are settled. + * This ensures that queries are always invalidated correctly and automatically "batches" invalidations of concurrent mutations. + * Note that if you constantly have some queries (or mutations) running, this can delay tag invalidations indefinitely. */ - invalidateImmediately?: boolean + invalidationBehavior?: 'delayed' | 'immediately' /** * A function that is passed every dispatched action. If this returns something other than `undefined`, * that return value will be used to rehydrate fulfilled & errored queries. @@ -264,7 +265,7 @@ export function buildCreateApi, ...Module[]]>( refetchOnMountOrArgChange: false, refetchOnFocus: false, refetchOnReconnect: false, - invalidateImmediately: false, + invalidationBehavior: 'delayed', ...options, extractRehydrationInfo, serializeQueryArgs(queryArgsApi) { diff --git a/packages/toolkit/src/query/tests/buildSlice.test.ts b/packages/toolkit/src/query/tests/buildSlice.test.ts index ff91bcef3b..8c47f7e875 100644 --- a/packages/toolkit/src/query/tests/buildSlice.test.ts +++ b/packages/toolkit/src/query/tests/buildSlice.test.ts @@ -51,7 +51,7 @@ describe('buildSlice', () => { api: { config: { focused: true, - invalidateImmediately: false, + invalidationBehavior: "delayed", keepUnusedDataFor: 60, middlewareRegistered: true, online: true, From aa4cad5b0d0d329a0d3036b0e90cd9ba1d9a6b54 Mon Sep 17 00:00:00 2001 From: Georg Wicke-Arndt Date: Mon, 26 Jun 2023 14:22:54 +0200 Subject: [PATCH 326/412] Apply suggestion Co-authored-by: Ben Durrant --- .../src/query/core/buildMiddleware/invalidationByTags.ts | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/packages/toolkit/src/query/core/buildMiddleware/invalidationByTags.ts b/packages/toolkit/src/query/core/buildMiddleware/invalidationByTags.ts index 07a9887e4e..b99f8e0f64 100644 --- a/packages/toolkit/src/query/core/buildMiddleware/invalidationByTags.ts +++ b/packages/toolkit/src/query/core/buildMiddleware/invalidationByTags.ts @@ -39,10 +39,8 @@ export const buildInvalidationByTagsHandler: InternalHandlerBuilder = ({ ) const isQueryEnd = isAnyOf( - isFulfilled(mutationThunk), - isRejected(mutationThunk), - isFulfilled(queryThunk), - isRejected(queryThunk) + isFulfilled(mutationThunk, queryThunk), + isRejected(mutationThunk, queryThunk) ) let pendingTagInvalidations: FullTagDescription[] = [] From ea34950814732867b7793dea0c25292a21a81780 Mon Sep 17 00:00:00 2001 From: Georg Wicke-Arndt Date: Wed, 16 Aug 2023 11:09:37 +0200 Subject: [PATCH 327/412] Set default invalidationBehavior to "immediately" --- packages/toolkit/src/query/createApi.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/toolkit/src/query/createApi.ts b/packages/toolkit/src/query/createApi.ts index 8de1107c3d..7fd72266fd 100644 --- a/packages/toolkit/src/query/createApi.ts +++ b/packages/toolkit/src/query/createApi.ts @@ -265,7 +265,7 @@ export function buildCreateApi, ...Module[]]>( refetchOnMountOrArgChange: false, refetchOnFocus: false, refetchOnReconnect: false, - invalidationBehavior: 'delayed', + invalidationBehavior: 'immediately', ...options, extractRehydrationInfo, serializeQueryArgs(queryArgsApi) { From f26dd392e254fc0e4730f915ce433a62bf5bcf9c Mon Sep 17 00:00:00 2001 From: Georg Wicke-Arndt Date: Fri, 18 Aug 2023 15:06:53 +0200 Subject: [PATCH 328/412] Fix tests --- packages/toolkit/src/query/tests/buildSlice.test.ts | 2 +- packages/toolkit/src/query/tests/raceConditions.test.ts | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/toolkit/src/query/tests/buildSlice.test.ts b/packages/toolkit/src/query/tests/buildSlice.test.ts index 8c47f7e875..8bd9fb6119 100644 --- a/packages/toolkit/src/query/tests/buildSlice.test.ts +++ b/packages/toolkit/src/query/tests/buildSlice.test.ts @@ -51,7 +51,7 @@ describe('buildSlice', () => { api: { config: { focused: true, - invalidationBehavior: "delayed", + invalidationBehavior: "immediately", keepUnusedDataFor: 60, middlewareRegistered: true, online: true, diff --git a/packages/toolkit/src/query/tests/raceConditions.test.ts b/packages/toolkit/src/query/tests/raceConditions.test.ts index 728e837507..0ec49da2fb 100644 --- a/packages/toolkit/src/query/tests/raceConditions.test.ts +++ b/packages/toolkit/src/query/tests/raceConditions.test.ts @@ -21,6 +21,7 @@ const eatBananaPromises = createPromiseFactory() let eatenBananas = 0 const api = createApi({ + invalidationBehavior: 'delayed', baseQuery: () => undefined as any, tagTypes: ['Banana'], endpoints: (build) => ({ From 8b2ccbbedf1d20a0c3b4cee42d8a6dada84ddca7 Mon Sep 17 00:00:00 2001 From: Georg Wicke-Arndt Date: Mon, 16 Oct 2023 09:56:00 +0200 Subject: [PATCH 329/412] Fix performance concern for hasPendingRequests --- .../buildMiddleware/invalidationByTags.ts | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/packages/toolkit/src/query/core/buildMiddleware/invalidationByTags.ts b/packages/toolkit/src/query/core/buildMiddleware/invalidationByTags.ts index b99f8e0f64..4334d4f813 100644 --- a/packages/toolkit/src/query/core/buildMiddleware/invalidationByTags.ts +++ b/packages/toolkit/src/query/core/buildMiddleware/invalidationByTags.ts @@ -73,6 +73,16 @@ export const buildInvalidationByTagsHandler: InternalHandlerBuilder = ({ } } + function hasPendingRequests(state: CombinedState) { + for (const key in state.queries) { + if (state.queries[key]?.status === QueryStatus.pending) return true; + } + for (const key in state.mutations) { + if (state.mutations[key]?.status === QueryStatus.pending) return true; + } + return false; + } + function invalidateTags( newTags: readonly FullTagDescription[], mwApi: SubMiddlewareApi @@ -82,13 +92,8 @@ export const buildInvalidationByTagsHandler: InternalHandlerBuilder = ({ pendingTagInvalidations.push(...newTags) - if (state.config.invalidationBehavior === 'delayed') { - const hasPendingRequests = [ - ...Object.values(state.queries), - ...Object.values(state.mutations), - ].some((x) => x?.status === QueryStatus.pending) - - if (hasPendingRequests) return + if (state.config.invalidationBehavior === 'delayed' && hasPendingRequests(state)) { + return } const tags = pendingTagInvalidations From 712f90e99ae86f79c112bff10303280397f16660 Mon Sep 17 00:00:00 2001 From: Mark Erikson Date: Sat, 28 Oct 2023 18:11:05 -0400 Subject: [PATCH 330/412] Switch default invalidation behavior to "delayed" --- packages/toolkit/src/query/core/buildSlice.ts | 1 + packages/toolkit/src/query/createApi.ts | 2 +- packages/toolkit/src/query/tests/buildSlice.test.ts | 2 +- packages/toolkit/src/tests/combineSlices.test.ts | 1 + 4 files changed, 4 insertions(+), 2 deletions(-) diff --git a/packages/toolkit/src/query/core/buildSlice.ts b/packages/toolkit/src/query/core/buildSlice.ts index cfda46e183..bb712281a9 100644 --- a/packages/toolkit/src/query/core/buildSlice.ts +++ b/packages/toolkit/src/query/core/buildSlice.ts @@ -28,6 +28,7 @@ import { calculateProvidedByThunk } from './buildThunks' import type { AssertTagTypes, EndpointDefinitions, + FullTagDescription, QueryDefinition, } from '../endpointDefinitions' import type { Patch } from 'immer' diff --git a/packages/toolkit/src/query/createApi.ts b/packages/toolkit/src/query/createApi.ts index 7fd72266fd..8de1107c3d 100644 --- a/packages/toolkit/src/query/createApi.ts +++ b/packages/toolkit/src/query/createApi.ts @@ -265,7 +265,7 @@ export function buildCreateApi, ...Module[]]>( refetchOnMountOrArgChange: false, refetchOnFocus: false, refetchOnReconnect: false, - invalidationBehavior: 'immediately', + invalidationBehavior: 'delayed', ...options, extractRehydrationInfo, serializeQueryArgs(queryArgsApi) { diff --git a/packages/toolkit/src/query/tests/buildSlice.test.ts b/packages/toolkit/src/query/tests/buildSlice.test.ts index 8bd9fb6119..1a075beb6e 100644 --- a/packages/toolkit/src/query/tests/buildSlice.test.ts +++ b/packages/toolkit/src/query/tests/buildSlice.test.ts @@ -51,7 +51,7 @@ describe('buildSlice', () => { api: { config: { focused: true, - invalidationBehavior: "immediately", + invalidationBehavior: 'delayed', keepUnusedDataFor: 60, middlewareRegistered: true, online: true, diff --git a/packages/toolkit/src/tests/combineSlices.test.ts b/packages/toolkit/src/tests/combineSlices.test.ts index bb24c0f8ea..4e3439fd0f 100644 --- a/packages/toolkit/src/tests/combineSlices.test.ts +++ b/packages/toolkit/src/tests/combineSlices.test.ts @@ -33,6 +33,7 @@ const api = { subscriptions: {}, config: { reducerPath: 'api', + invalidationBehavior: 'delayed', online: false, focused: false, keepUnusedDataFor: 60, From 138ee6db163e9e3ce07811b343a32133a4dc68a8 Mon Sep 17 00:00:00 2001 From: Mark Erikson Date: Sat, 28 Oct 2023 18:50:41 -0400 Subject: [PATCH 331/412] Bump Reselect to 5.0-beta --- packages/toolkit/package.json | 2 +- yarn.lock | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/packages/toolkit/package.json b/packages/toolkit/package.json index 64d67dae8b..032348e840 100644 --- a/packages/toolkit/package.json +++ b/packages/toolkit/package.json @@ -115,7 +115,7 @@ "immer": "^10.0.2", "redux": "^5.0.0-beta.0", "redux-thunk": "^3.0.0-beta.0", - "reselect": "^5.0.0-alpha.2" + "reselect": "^5.0.0-beta.0" }, "peerDependencies": { "react": "^16.9.0 || ^17.0.0 || ^18", diff --git a/yarn.lock b/yarn.lock index 6358e246e3..ff45affbf1 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7023,7 +7023,7 @@ __metadata: query-string: ^7.0.1 redux: ^5.0.0-beta.0 redux-thunk: ^3.0.0-beta.0 - reselect: ^5.0.0-alpha.2 + reselect: ^5.0.0-beta.0 rimraf: ^3.0.2 size-limit: ^4.11.0 tslib: ^1.10.0 @@ -25432,10 +25432,10 @@ fsevents@^1.2.7: languageName: node linkType: hard -"reselect@npm:^5.0.0-alpha.2": - version: 5.0.0-alpha.2 - resolution: "reselect@npm:5.0.0-alpha.2" - checksum: c47b66999800e1297721cbc4b2464b520fade9823c598d578759c9fba3eb6be03b184e13c20f30820cc18fe2688fc9fb4475f83e59d8f2347aa0d591e465637d +"reselect@npm:^5.0.0-beta.0": + version: 5.0.0-beta.0 + resolution: "reselect@npm:5.0.0-beta.0" + checksum: 462363aa730af93e396ff0d885f88fb8c43572b07f51c2a890d37f27edc3afecd300085916533e336142b3883f8532f35b5b1a2aaa1a70e9909aea48e5d3b98f languageName: node linkType: hard From afe51dfa5a8ea66493b940d02163e8ecfc8e49bf Mon Sep 17 00:00:00 2001 From: Mark Erikson Date: Sat, 28 Oct 2023 19:06:35 -0400 Subject: [PATCH 332/412] Work around Reselect types changes --- packages/toolkit/src/entities/state_selectors.ts | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/packages/toolkit/src/entities/state_selectors.ts b/packages/toolkit/src/entities/state_selectors.ts index a3743d148a..3bb3b88872 100644 --- a/packages/toolkit/src/entities/state_selectors.ts +++ b/packages/toolkit/src/entities/state_selectors.ts @@ -1,14 +1,10 @@ -import type { CreateSelectorFunction, Selector } from 'reselect' +import type { CreateSelectorFunction, Selector, createSelector } from 'reselect' import { createDraftSafeSelector } from '../createDraftSafeSelector' import type { EntityState, EntitySelectors, EntityId } from './models' -export type AnyCreateSelectorFunction = CreateSelectorFunction< - (...args: unknown[]) => unknown, - any>(func: F) => F -> - export interface GetSelectorsOptions { - createSelector?: AnyCreateSelectorFunction + // TODO Review if this causes issues or if we can go back to using `CreateSelectorFunction` + createSelector?: typeof createSelector } export function createSelectorsFactory() { From d47cb1f11fed4384080e52054161f9fb8be58abb Mon Sep 17 00:00:00 2001 From: Mark Erikson Date: Sat, 28 Oct 2023 19:20:37 -0400 Subject: [PATCH 333/412] Bump React-Redux peer dep --- packages/toolkit/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/toolkit/package.json b/packages/toolkit/package.json index 032348e840..c718a99a3d 100644 --- a/packages/toolkit/package.json +++ b/packages/toolkit/package.json @@ -119,7 +119,7 @@ }, "peerDependencies": { "react": "^16.9.0 || ^17.0.0 || ^18", - "react-redux": "^7.2.1 || ^8.0.2 || ^9.0.0-alpha.1" + "react-redux": "^7.2.1 || ^8.0.2 || ^9.0.0-beta.0" }, "peerDependenciesMeta": { "react": { From 2b6d65e6dcde657cf57ef3853a781290b37e7bc6 Mon Sep 17 00:00:00 2001 From: Mark Erikson Date: Sat, 28 Oct 2023 19:22:32 -0400 Subject: [PATCH 334/412] Release 2.0.0-beta.4 --- packages/toolkit/package.json | 2 +- yarn.lock | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/toolkit/package.json b/packages/toolkit/package.json index c718a99a3d..838df2290d 100644 --- a/packages/toolkit/package.json +++ b/packages/toolkit/package.json @@ -1,6 +1,6 @@ { "name": "@reduxjs/toolkit", - "version": "2.0.0-beta.3", + "version": "2.0.0-beta.4", "description": "The official, opinionated, batteries-included toolset for efficient Redux development", "author": "Mark Erikson ", "license": "MIT", diff --git a/yarn.lock b/yarn.lock index ff45affbf1..c391087742 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7034,7 +7034,7 @@ __metadata: yargs: ^15.3.1 peerDependencies: react: ^16.9.0 || ^17.0.0 || ^18 - react-redux: ^7.2.1 || ^8.0.2 || ^9.0.0-alpha.1 + react-redux: ^7.2.1 || ^8.0.2 || ^9.0.0-beta.0 peerDependenciesMeta: react: optional: true From 30c5ae7b33a1f0015c6b1416239196369d155b57 Mon Sep 17 00:00:00 2001 From: Ben Durrant Date: Sun, 29 Oct 2023 12:03:21 +0000 Subject: [PATCH 335/412] Allow passing selector creators with different memoize options --- packages/toolkit/src/entities/state_selectors.ts | 12 ++++++++++-- .../src/entities/tests/sorted_state_adapter.test.ts | 6 +++--- .../toolkit/src/entities/tests/state_adapter.test.ts | 2 +- .../src/entities/tests/state_selectors.test.ts | 5 ++++- 4 files changed, 18 insertions(+), 7 deletions(-) diff --git a/packages/toolkit/src/entities/state_selectors.ts b/packages/toolkit/src/entities/state_selectors.ts index 3bb3b88872..d2c8d9f833 100644 --- a/packages/toolkit/src/entities/state_selectors.ts +++ b/packages/toolkit/src/entities/state_selectors.ts @@ -2,9 +2,15 @@ import type { CreateSelectorFunction, Selector, createSelector } from 'reselect' import { createDraftSafeSelector } from '../createDraftSafeSelector' import type { EntityState, EntitySelectors, EntityId } from './models' +type AnyFunction = (...args: any) => any +type AnyCreateSelectorFunction = CreateSelectorFunction< + (f: F) => F, + (f: F) => F +> + export interface GetSelectorsOptions { // TODO Review if this causes issues or if we can go back to using `CreateSelectorFunction` - createSelector?: typeof createSelector + createSelector?: AnyCreateSelectorFunction } export function createSelectorsFactory() { @@ -20,7 +26,9 @@ export function createSelectorsFactory() { selectState?: (state: V) => EntityState, options: GetSelectorsOptions = {} ): EntitySelectors { - const { createSelector = createDraftSafeSelector } = options + let { createSelector } = options + createSelector ??= createDraftSafeSelector + const selectIds = (state: EntityState) => state.ids const selectEntities = (state: EntityState) => state.entities diff --git a/packages/toolkit/src/entities/tests/sorted_state_adapter.test.ts b/packages/toolkit/src/entities/tests/sorted_state_adapter.test.ts index e3287d0e3b..ff691a8326 100644 --- a/packages/toolkit/src/entities/tests/sorted_state_adapter.test.ts +++ b/packages/toolkit/src/entities/tests/sorted_state_adapter.test.ts @@ -11,8 +11,8 @@ import { import { createNextState } from '../..' describe('Sorted State Adapter', () => { - let adapter: EntityAdapter - let state: EntityState + let adapter: EntityAdapter + let state: EntityState beforeAll(() => { //eslint-disable-next-line @@ -349,7 +349,7 @@ describe('Sorted State Adapter', () => { order: number ts: number } - const sortedItemsAdapter = createEntityAdapter({ + const sortedItemsAdapter = createEntityAdapter({ sortComparer: (a, b) => a.order - b.order, }) const withInitialItems = sortedItemsAdapter.setAll( diff --git a/packages/toolkit/src/entities/tests/state_adapter.test.ts b/packages/toolkit/src/entities/tests/state_adapter.test.ts index ec760080ce..a05b715ff9 100644 --- a/packages/toolkit/src/entities/tests/state_adapter.test.ts +++ b/packages/toolkit/src/entities/tests/state_adapter.test.ts @@ -6,7 +6,7 @@ import { createSlice } from '../../createSlice' import type { BookModel } from './fixtures/book' describe('createStateOperator', () => { - let adapter: EntityAdapter + let adapter: EntityAdapter beforeEach(() => { adapter = createEntityAdapter({ diff --git a/packages/toolkit/src/entities/tests/state_selectors.test.ts b/packages/toolkit/src/entities/tests/state_selectors.test.ts index 9ef7b32762..f581965be3 100644 --- a/packages/toolkit/src/entities/tests/state_selectors.test.ts +++ b/packages/toolkit/src/entities/tests/state_selectors.test.ts @@ -130,7 +130,10 @@ describe('Entity State Selectors', () => { }) describe('custom createSelector instance', () => { it('should use the custom createSelector function if provided', () => { - const memoizeSpy = vi.fn(weakMapMemoize) + const memoizeSpy = vi.fn( + // test that we're allowed to pass memoizers with different options, as long as they're optional + any>(fn: F, param?: boolean) => fn + ) const createCustomSelector = createDraftSafeSelectorCreator(memoizeSpy) const adapter = createEntityAdapter({ From b05b6babb5387734cc4987ed52848e7ea67bad0e Mon Sep 17 00:00:00 2001 From: Ben Durrant Date: Sun, 29 Oct 2023 12:04:37 +0000 Subject: [PATCH 336/412] rm TODO --- packages/toolkit/src/entities/state_selectors.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/toolkit/src/entities/state_selectors.ts b/packages/toolkit/src/entities/state_selectors.ts index d2c8d9f833..3152b48ddb 100644 --- a/packages/toolkit/src/entities/state_selectors.ts +++ b/packages/toolkit/src/entities/state_selectors.ts @@ -9,7 +9,6 @@ type AnyCreateSelectorFunction = CreateSelectorFunction< > export interface GetSelectorsOptions { - // TODO Review if this causes issues or if we can go back to using `CreateSelectorFunction` createSelector?: AnyCreateSelectorFunction } From d28e6f5fdeec7507447dab6aac34aa3577d262c5 Mon Sep 17 00:00:00 2001 From: Ben Durrant Date: Sun, 29 Oct 2023 14:34:57 +0000 Subject: [PATCH 337/412] use default assignment --- packages/toolkit/src/entities/state_selectors.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/toolkit/src/entities/state_selectors.ts b/packages/toolkit/src/entities/state_selectors.ts index 3152b48ddb..f8f04449df 100644 --- a/packages/toolkit/src/entities/state_selectors.ts +++ b/packages/toolkit/src/entities/state_selectors.ts @@ -25,8 +25,7 @@ export function createSelectorsFactory() { selectState?: (state: V) => EntityState, options: GetSelectorsOptions = {} ): EntitySelectors { - let { createSelector } = options - createSelector ??= createDraftSafeSelector + const { createSelector = createDraftSafeSelector as AnyCreateSelectorFunction } = options const selectIds = (state: EntityState) => state.ids From 4106c04b1aa1bd1028a7656043018f2145b12b21 Mon Sep 17 00:00:00 2001 From: Ben Durrant Date: Tue, 31 Oct 2023 00:53:30 +0000 Subject: [PATCH 338/412] Add selectSlice to slice instance --- packages/toolkit/src/createSlice.ts | 50 +++++++++++++------ .../toolkit/src/tests/createSlice.test.ts | 16 +++--- .../toolkit/src/tests/createSlice.typetest.ts | 9 ++++ 3 files changed, 55 insertions(+), 20 deletions(-) diff --git a/packages/toolkit/src/createSlice.ts b/packages/toolkit/src/createSlice.ts index ec1128b8a6..bc43c15ad4 100644 --- a/packages/toolkit/src/createSlice.ts +++ b/packages/toolkit/src/createSlice.ts @@ -15,7 +15,7 @@ import type { import { createReducer } from './createReducer' import type { ActionReducerMapBuilder } from './mapBuilders' import { executeReducerBuilderCallback } from './mapBuilders' -import type { ActionFromMatcher, Id, Matcher, Tail } from './tsHelpers' +import type { Id, Tail } from './tsHelpers' import type { InjectConfig } from './combineSlices' import type { AsyncThunk, @@ -78,13 +78,14 @@ export interface Slice< /** * Get localised slice selectors (expects to be called with *just* the slice's state as the first parameter) */ - getSelectors(): Id> + getSelectors(this: this): Id> /** * Get globalised slice selectors (`selectState` callback is expected to receive first parameter and return slice state) */ getSelectors( - selectState: (rootState: RootState) => State + this: this, + selectState: (this: this, rootState: RootState) => State ): Id> /** @@ -100,6 +101,7 @@ export interface Slice< * Inject slice into provided reducer (return value from `combineSlices`), and return injected slice. */ injectInto( + this: this, injectable: { inject: ( slice: { reducerPath: string; reducer: Reducer }, @@ -108,6 +110,13 @@ export interface Slice< }, config?: InjectIntoConfig ): InjectedSlice + + /** + * Select the slice state, using the slice's current reducerPath. + * + * Will throw an error if slice is not found. + */ + selectSlice(this: this, state: { [K in ReducerPath]: State }): State } /** @@ -134,7 +143,7 @@ interface InjectedSlice< * Get globalised slice selectors (`selectState` callback is expected to receive first parameter and return slice state) */ getSelectors( - selectState: (rootState: RootState) => State | undefined + selectState: (this: this, rootState: RootState) => State | undefined ): Id> /** @@ -149,6 +158,13 @@ interface InjectedSlice< { [K in ReducerPath]?: State | undefined } > > + + /** + * Select the slice state, using the slice's current reducerPath. + * + * Returns initial state if slice is not found. + */ + selectSlice(state: { [K in ReducerPath]?: State | undefined }): State } /** @@ -660,10 +676,6 @@ export function createSlice< }) } - const defaultSelectSlice = ( - rootState: { [K in ReducerPath]: State } - ): State => rootState[reducerPath] - const selectSelf = (state: State) => state const injectedSelectorCache = new WeakMap< @@ -704,7 +716,7 @@ export function createSlice< options.selectors ?? {} )) { cached[name] = (rootState: any, ...args: any[]) => { - let sliceState = selectState(rootState) + let sliceState = selectState.call(this, rootState) if (typeof sliceState === 'undefined') { // check if injectInto has been called if (this !== slice) { @@ -722,19 +734,29 @@ export function createSlice< } return cached as any }, + selectSlice(state) { + let sliceState = state[this.reducerPath] + if (typeof sliceState === 'undefined') { + // check if injectInto has been called + if (this !== slice) { + sliceState = this.getInitialState() + } else if (process.env.NODE_ENV !== 'production') { + throw new Error( + 'selectSlice returned undefined for an uninjected slice reducer' + ) + } + } + return sliceState + }, get selectors() { - return this.getSelectors(defaultSelectSlice) + return this.getSelectors(this.selectSlice) }, injectInto(injectable, { reducerPath: pathOpt, ...config } = {}) { const reducerPath = pathOpt ?? this.reducerPath injectable.inject({ reducerPath, reducer: this.reducer }, config) - const selectSlice = (state: any) => state[reducerPath] return { ...this, reducerPath, - get selectors() { - return this.getSelectors(selectSlice) - }, } as any }, } diff --git a/packages/toolkit/src/tests/createSlice.test.ts b/packages/toolkit/src/tests/createSlice.test.ts index 302c06c9c9..4755a4e5dc 100644 --- a/packages/toolkit/src/tests/createSlice.test.ts +++ b/packages/toolkit/src/tests/createSlice.test.ts @@ -495,7 +495,6 @@ describe('createSlice', () => { increment: (state) => ++state, }, selectors: { - selectSlice: (state) => state, selectMultiple: (state, multiplier: number) => state * multiplier, }, }) @@ -513,13 +512,13 @@ describe('createSlice', () => { const injectedSlice = slice.injectInto(combinedReducer) // selector returns initial state if undefined in real state - expect(injectedSlice.selectors.selectSlice(uninjectedState)).toBe( + expect(injectedSlice.selectSlice(uninjectedState)).toBe( slice.getInitialState() ) const injectedState = combinedReducer(undefined, increment()) - expect(injectedSlice.selectors.selectSlice(injectedState)).toBe( + expect(injectedSlice.selectSlice(injectedState)).toBe( slice.getInitialState() + 1 ) }) @@ -532,7 +531,6 @@ describe('createSlice', () => { increment: (state) => ++state, }, selectors: { - selectSlice: (state) => state, selectMultiple: (state, multiplier: number) => state * multiplier, }, }) @@ -551,9 +549,12 @@ describe('createSlice', () => { const injectedState = combinedReducer(undefined, increment()) - expect(injected.selectors.selectSlice(injectedState)).toBe( + expect(injected.selectSlice(injectedState)).toBe( slice.getInitialState() + 1 ) + expect(injected.selectors.selectMultiple(injectedState, 2)).toBe( + (slice.getInitialState() + 1) * 2 + ) const injected2 = slice.injectInto(combinedReducer, { reducerPath: 'injected2', @@ -561,9 +562,12 @@ describe('createSlice', () => { const injected2State = combinedReducer(undefined, increment()) - expect(injected2.selectors.selectSlice(injected2State)).toBe( + expect(injected2.selectSlice(injected2State)).toBe( slice.getInitialState() + 1 ) + expect(injected2.selectors.selectMultiple(injected2State, 2)).toBe( + (slice.getInitialState() + 1) * 2 + ) }) }) describe('reducers definition with asyncThunks', () => { diff --git a/packages/toolkit/src/tests/createSlice.typetest.ts b/packages/toolkit/src/tests/createSlice.typetest.ts index 9cd6371f0f..94ad5b773c 100644 --- a/packages/toolkit/src/tests/createSlice.typetest.ts +++ b/packages/toolkit/src/tests/createSlice.typetest.ts @@ -840,3 +840,12 @@ const value = actionCreators.anyKey expectType>(wrappedSlice.actions.success) expectType>(wrappedSlice.actions.magic) } + +/** + * Test: selectSlice + */ +{ + expectType(counterSlice.selectSlice({ counter: 0 })) + // @ts-expect-error + counterSlice.selectSlice({}) +} From f3fd32f965868666d94a9504836b8f282eb34827 Mon Sep 17 00:00:00 2001 From: "ben.durrant" Date: Tue, 7 Nov 2023 12:28:06 +0000 Subject: [PATCH 339/412] Throw an error if ApiProvider is nested inside a normal Provider. --- packages/toolkit/src/query/react/ApiProvider.tsx | 12 ++++++++++-- .../toolkit/src/query/tests/apiProvider.test.tsx | 13 +++++++++++++ 2 files changed, 23 insertions(+), 2 deletions(-) diff --git a/packages/toolkit/src/query/react/ApiProvider.tsx b/packages/toolkit/src/query/react/ApiProvider.tsx index 5be6ea06ad..4c9f7acf23 100644 --- a/packages/toolkit/src/query/react/ApiProvider.tsx +++ b/packages/toolkit/src/query/react/ApiProvider.tsx @@ -1,9 +1,10 @@ import { configureStore } from '@reduxjs/toolkit' import type { Context } from 'react' +import { useContext } from 'react' import { useEffect } from 'react' import React from 'react' import type { ReactReduxContextValue } from 'react-redux' -import { Provider } from 'react-redux' +import { Provider, ReactReduxContext } from 'react-redux' import { setupListeners } from '@reduxjs/toolkit/query' import type { Api } from '@reduxjs/toolkit/query' @@ -37,6 +38,13 @@ export function ApiProvider
>(props: { setupListeners?: Parameters[1] | false context?: Context }) { + const context = props.context || ReactReduxContext + const existingContext = useContext(context) + if (existingContext) { + throw new Error( + 'Existing Redux context detected. If you already have a store set up, please use the traditional Redux setup.' + ) + } const [store] = React.useState(() => configureStore({ reducer: { @@ -55,7 +63,7 @@ export function ApiProvider>(props: { ) return ( - + {props.children} ) diff --git a/packages/toolkit/src/query/tests/apiProvider.test.tsx b/packages/toolkit/src/query/tests/apiProvider.test.tsx index 3da38e1579..98e562cd71 100644 --- a/packages/toolkit/src/query/tests/apiProvider.test.tsx +++ b/packages/toolkit/src/query/tests/apiProvider.test.tsx @@ -2,6 +2,8 @@ import * as React from 'react' import { createApi, ApiProvider } from '@reduxjs/toolkit/query/react' import { fireEvent, render, waitFor } from '@testing-library/react' import { waitMs } from './helpers' +import { Provider } from 'react-redux' +import { configureStore } from '@reduxjs/toolkit' const api = createApi({ baseQuery: async (arg: any) => { @@ -57,4 +59,15 @@ describe('ApiProvider', () => { // Being that nothing has changed in the args, this should never fire. expect(getByTestId('isFetching').textContent).toBe('false') }) + test('ApiProvider throws if nested inside a Redux context', () => { + expect(() => + render( + null })}> + child + + ) + ).toThrowErrorMatchingInlineSnapshot( + '"Existing Redux context detected. If you already have a store set up, please use the traditional Redux setup."' + ) + }) }) From 9ef5ff9963582e928ef8b63a23613c3eb7e1a040 Mon Sep 17 00:00:00 2001 From: Ben Durrant Date: Wed, 8 Nov 2023 12:06:19 +0000 Subject: [PATCH 340/412] update errors.json --- errors.json | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/errors.json b/errors.json index e6513b5abc..ad527b78d8 100644 --- a/errors.json +++ b/errors.json @@ -31,5 +31,7 @@ "29": "`builder.addCase` cannot be called with an empty action type", "30": "`builder.addCase` cannot be called with two reducers for the same action type", "31": "\"middleware\" field must be a callback", - "32": "When using custom hooks for context, all hooks need to be provided: .\\nHook was either not provided or not a function." -} + "32": "When using custom hooks for context, all hooks need to be provided: .\\nHook was either not provided or not a function.", + "33": "Existing Redux context detected. If you already have a store set up, please use the traditional Redux setup.", + "34": "selectSlice returned undefined for an uninjected slice reducer" +} \ No newline at end of file From adb461d3057e00e46a7a170e1f194fa7595cc5fe Mon Sep 17 00:00:00 2001 From: Ben Durrant Date: Sat, 11 Nov 2023 11:34:56 +0000 Subject: [PATCH 341/412] Require calling buildCreateSlice to use create.asyncThunk --- packages/toolkit/src/createSlice.ts | 364 ++++++++++-------- packages/toolkit/src/index.ts | 2 + .../toolkit/src/tests/createSlice.test.ts | 22 +- .../toolkit/src/tests/createSlice.typetest.ts | 23 +- 4 files changed, 239 insertions(+), 172 deletions(-) diff --git a/packages/toolkit/src/createSlice.ts b/packages/toolkit/src/createSlice.ts index bc43c15ad4..4c92da4ca9 100644 --- a/packages/toolkit/src/createSlice.ts +++ b/packages/toolkit/src/createSlice.ts @@ -24,7 +24,15 @@ import type { AsyncThunkPayloadCreator, OverrideThunkApiConfigs, } from './createAsyncThunk' -import { createAsyncThunk } from './createAsyncThunk' +import { createAsyncThunk as _createAsyncThunk } from './createAsyncThunk' + +const asyncThunkSymbol = Symbol.for('rtk-slice-createasyncthunk') +// type is annotated because it's too long to infer +export const asyncThunkCreator: { + [asyncThunkSymbol]: typeof _createAsyncThunk +} = { + [asyncThunkSymbol]: _createAsyncThunk, +} interface InjectIntoConfig extends InjectConfig { reducerPath?: NewReducerPath @@ -569,6 +577,12 @@ function getType(slice: string, actionKey: string): string { return `${slice}/${actionKey}` } +interface BuildCreateSliceConfig { + creators?: { + asyncThunk?: typeof asyncThunkCreator + } +} + /** * A function that accepts an initial state, an object full of reducer * functions, and a "slice name", and automatically generates @@ -577,192 +591,204 @@ function getType(slice: string, actionKey: string): string { * * @public */ -export function createSlice< - State, - CaseReducers extends SliceCaseReducers, - Name extends string, - Selectors extends SliceSelectors, - ReducerPath extends string = Name ->( - options: CreateSliceOptions -): Slice { - const { name, reducerPath = name as unknown as ReducerPath } = options - if (!name) { - throw new Error('`name` is a required option for createSlice') - } - - if ( - typeof process !== 'undefined' && - process.env.NODE_ENV === 'development' - ) { - if (options.initialState === undefined) { - console.error( - 'You must provide an `initialState` value that is not `undefined`. You may have misspelled `initialState`' - ) +export function buildCreateSlice({ creators }: BuildCreateSliceConfig = {}) { + const cAT = creators?.asyncThunk?.[asyncThunkSymbol] + return function createSlice< + State, + CaseReducers extends SliceCaseReducers, + Name extends string, + Selectors extends SliceSelectors, + ReducerPath extends string = Name + >( + options: CreateSliceOptions< + State, + CaseReducers, + Name, + ReducerPath, + Selectors + > + ): Slice { + const { name, reducerPath = name as unknown as ReducerPath } = options + if (!name) { + throw new Error('`name` is a required option for createSlice') } - } - const reducers = - (typeof options.reducers === 'function' - ? options.reducers(buildReducerCreators()) - : options.reducers) || {} + if ( + typeof process !== 'undefined' && + process.env.NODE_ENV === 'development' + ) { + if (options.initialState === undefined) { + console.error( + 'You must provide an `initialState` value that is not `undefined`. You may have misspelled `initialState`' + ) + } + } - const reducerNames = Object.keys(reducers) + const reducers = + (typeof options.reducers === 'function' + ? options.reducers(buildReducerCreators()) + : options.reducers) || {} - const context: ReducerHandlingContext = { - sliceCaseReducersByName: {}, - sliceCaseReducersByType: {}, - actionCreators: {}, - sliceMatchers: [], - } + const reducerNames = Object.keys(reducers) - reducerNames.forEach((reducerName) => { - const reducerDefinition = reducers[reducerName] - const reducerDetails: ReducerDetails = { - reducerName, - type: getType(name, reducerName), - createNotation: typeof options.reducers === 'function', - } - if (isAsyncThunkSliceReducerDefinition(reducerDefinition)) { - handleThunkCaseReducerDefinition( - reducerDetails, - reducerDefinition, - context - ) - } else { - handleNormalReducerDefinition( - reducerDetails, - reducerDefinition, - context - ) + const context: ReducerHandlingContext = { + sliceCaseReducersByName: {}, + sliceCaseReducersByType: {}, + actionCreators: {}, + sliceMatchers: [], } - }) - function buildReducer() { - if (process.env.NODE_ENV !== 'production') { - if (typeof options.extraReducers === 'object') { - throw new Error( - "The object notation for `createSlice.extraReducers` has been removed. Please use the 'builder callback' notation instead: https://redux-toolkit.js.org/api/createSlice" + reducerNames.forEach((reducerName) => { + const reducerDefinition = reducers[reducerName] + const reducerDetails: ReducerDetails = { + reducerName, + type: getType(name, reducerName), + createNotation: typeof options.reducers === 'function', + } + if (isAsyncThunkSliceReducerDefinition(reducerDefinition)) { + handleThunkCaseReducerDefinition( + reducerDetails, + reducerDefinition, + context, + cAT + ) + } else { + handleNormalReducerDefinition( + reducerDetails, + reducerDefinition, + context ) } - } - const [ - extraReducers = {}, - actionMatchers = [], - defaultCaseReducer = undefined, - ] = - typeof options.extraReducers === 'function' - ? executeReducerBuilderCallback(options.extraReducers) - : [options.extraReducers] - - const finalCaseReducers = { - ...extraReducers, - ...context.sliceCaseReducersByType, - } + }) - return createReducer(options.initialState, (builder) => { - for (let key in finalCaseReducers) { - builder.addCase(key, finalCaseReducers[key] as CaseReducer) - } - for (let sM of context.sliceMatchers) { - builder.addMatcher(sM.matcher, sM.reducer) - } - for (let m of actionMatchers) { - builder.addMatcher(m.matcher, m.reducer) + function buildReducer() { + if (process.env.NODE_ENV !== 'production') { + if (typeof options.extraReducers === 'object') { + throw new Error( + "The object notation for `createSlice.extraReducers` has been removed. Please use the 'builder callback' notation instead: https://redux-toolkit.js.org/api/createSlice" + ) + } } - if (defaultCaseReducer) { - builder.addDefaultCase(defaultCaseReducer) + const [ + extraReducers = {}, + actionMatchers = [], + defaultCaseReducer = undefined, + ] = + typeof options.extraReducers === 'function' + ? executeReducerBuilderCallback(options.extraReducers) + : [options.extraReducers] + + const finalCaseReducers = { + ...extraReducers, + ...context.sliceCaseReducersByType, } - }) - } - - const selectSelf = (state: State) => state - - const injectedSelectorCache = new WeakMap< - Slice, - WeakMap< - (rootState: any) => State | undefined, - Record any> - > - >() - let _reducer: ReducerWithInitialState - - const slice: Slice = { - name, - reducerPath, - reducer(state, action) { - if (!_reducer) _reducer = buildReducer() + return createReducer(options.initialState, (builder) => { + for (let key in finalCaseReducers) { + builder.addCase(key, finalCaseReducers[key] as CaseReducer) + } + for (let sM of context.sliceMatchers) { + builder.addMatcher(sM.matcher, sM.reducer) + } + for (let m of actionMatchers) { + builder.addMatcher(m.matcher, m.reducer) + } + if (defaultCaseReducer) { + builder.addDefaultCase(defaultCaseReducer) + } + }) + } - return _reducer(state, action) - }, - actions: context.actionCreators as any, - caseReducers: context.sliceCaseReducersByName as any, - getInitialState() { - if (!_reducer) _reducer = buildReducer() + const selectSelf = (state: State) => state - return _reducer.getInitialState() - }, - getSelectors(selectState: (rootState: any) => State = selectSelf) { - let selectorCache = injectedSelectorCache.get(this) - if (!selectorCache) { - selectorCache = new WeakMap() - injectedSelectorCache.set(this, selectorCache) - } - let cached = selectorCache.get(selectState) - if (!cached) { - cached = {} - for (const [name, selector] of Object.entries( - options.selectors ?? {} - )) { - cached[name] = (rootState: any, ...args: any[]) => { - let sliceState = selectState.call(this, rootState) - if (typeof sliceState === 'undefined') { - // check if injectInto has been called - if (this !== slice) { - sliceState = this.getInitialState() - } else if (process.env.NODE_ENV !== 'production') { - throw new Error( - 'selectState returned undefined for an uninjected slice reducer' - ) + const injectedSelectorCache = new WeakMap< + Slice, + WeakMap< + (rootState: any) => State | undefined, + Record any> + > + >() + + let _reducer: ReducerWithInitialState + + const slice: Slice = { + name, + reducerPath, + reducer(state, action) { + if (!_reducer) _reducer = buildReducer() + + return _reducer(state, action) + }, + actions: context.actionCreators as any, + caseReducers: context.sliceCaseReducersByName as any, + getInitialState() { + if (!_reducer) _reducer = buildReducer() + + return _reducer.getInitialState() + }, + getSelectors(selectState: (rootState: any) => State = selectSelf) { + let selectorCache = injectedSelectorCache.get(this) + if (!selectorCache) { + selectorCache = new WeakMap() + injectedSelectorCache.set(this, selectorCache) + } + let cached = selectorCache.get(selectState) + if (!cached) { + cached = {} + for (const [name, selector] of Object.entries( + options.selectors ?? {} + )) { + cached[name] = (rootState: any, ...args: any[]) => { + let sliceState = selectState.call(this, rootState) + if (typeof sliceState === 'undefined') { + // check if injectInto has been called + if (this !== slice) { + sliceState = this.getInitialState() + } else if (process.env.NODE_ENV !== 'production') { + throw new Error( + 'selectState returned undefined for an uninjected slice reducer' + ) + } } + return selector(sliceState, ...args) } - return selector(sliceState, ...args) } + selectorCache.set(selectState, cached) } - selectorCache.set(selectState, cached) - } - return cached as any - }, - selectSlice(state) { - let sliceState = state[this.reducerPath] - if (typeof sliceState === 'undefined') { - // check if injectInto has been called - if (this !== slice) { - sliceState = this.getInitialState() - } else if (process.env.NODE_ENV !== 'production') { - throw new Error( - 'selectSlice returned undefined for an uninjected slice reducer' - ) + return cached as any + }, + selectSlice(state) { + let sliceState = state[this.reducerPath] + if (typeof sliceState === 'undefined') { + // check if injectInto has been called + if (this !== slice) { + sliceState = this.getInitialState() + } else if (process.env.NODE_ENV !== 'production') { + throw new Error( + 'selectSlice returned undefined for an uninjected slice reducer' + ) + } } - } - return sliceState - }, - get selectors() { - return this.getSelectors(this.selectSlice) - }, - injectInto(injectable, { reducerPath: pathOpt, ...config } = {}) { - const reducerPath = pathOpt ?? this.reducerPath - injectable.inject({ reducerPath, reducer: this.reducer }, config) - return { - ...this, - reducerPath, - } as any - }, + return sliceState + }, + get selectors() { + return this.getSelectors(this.selectSlice) + }, + injectInto(injectable, { reducerPath: pathOpt, ...config } = {}) { + const reducerPath = pathOpt ?? this.reducerPath + injectable.inject({ reducerPath, reducer: this.reducer }, config) + return { + ...this, + reducerPath, + } as any + }, + } + return slice } - return slice } +export const createSlice = buildCreateSlice() + interface ReducerHandlingContext { sliceCaseReducersByName: Record< string, @@ -868,11 +894,17 @@ function isCaseReducerWithPrepareDefinition( function handleThunkCaseReducerDefinition( { type, reducerName }: ReducerDetails, reducerDefinition: AsyncThunkSliceReducerDefinition, - context: ReducerHandlingContext + context: ReducerHandlingContext, + cAT: typeof _createAsyncThunk | undefined ) { + if (!cAT) { + throw new Error( + 'Cannot use create.asyncThunk without custom initialisation' + ) + } const { payloadCreator, fulfilled, pending, rejected, settled, options } = reducerDefinition - const thunk = createAsyncThunk(type, payloadCreator, options as any) + const thunk = cAT(type, payloadCreator, options as any) context.actionCreators[reducerName] = thunk if (fulfilled) { diff --git a/packages/toolkit/src/index.ts b/packages/toolkit/src/index.ts index 2b8fc18ad2..b43d3e8777 100644 --- a/packages/toolkit/src/index.ts +++ b/packages/toolkit/src/index.ts @@ -70,6 +70,8 @@ export type { export { // js createSlice, + buildCreateSlice, + asyncThunkCreator, ReducerType, } from './createSlice' diff --git a/packages/toolkit/src/tests/createSlice.test.ts b/packages/toolkit/src/tests/createSlice.test.ts index 4755a4e5dc..117ea8f92e 100644 --- a/packages/toolkit/src/tests/createSlice.test.ts +++ b/packages/toolkit/src/tests/createSlice.test.ts @@ -1,6 +1,8 @@ import { vi } from 'vitest' import type { PayloadAction, WithSlice } from '@reduxjs/toolkit' import { + asyncThunkCreator, + buildCreateSlice, configureStore, combineSlices, createSlice, @@ -571,6 +573,18 @@ describe('createSlice', () => { }) }) describe('reducers definition with asyncThunks', () => { + it('is disabled by default', () => { + expect(() => + createSlice({ + name: 'test', + initialState: [] as any[], + reducers: (create) => ({ thunk: create.asyncThunk(() => {}) }), + }) + ).toThrowErrorMatchingInlineSnapshot('"Cannot use create.asyncThunk without custom initialisation"') + }) + const createThunkSlice = buildCreateSlice({ + creators: { asyncThunk: asyncThunkCreator }, + }) function pending(state: any[], action: any) { state.push(['pendingReducer', action]) } @@ -585,7 +599,7 @@ describe('createSlice', () => { } test('successful thunk', async () => { - const slice = createSlice({ + const slice = createThunkSlice({ name: 'test', initialState: [] as any[], reducers: (create) => ({ @@ -628,7 +642,7 @@ describe('createSlice', () => { }) test('rejected thunk', async () => { - const slice = createSlice({ + const slice = createThunkSlice({ name: 'test', initialState: [] as any[], reducers: (create) => ({ @@ -672,7 +686,7 @@ describe('createSlice', () => { }) test('with options', async () => { - const slice = createSlice({ + const slice = createThunkSlice({ name: 'test', initialState: [] as any[], reducers: (create) => ({ @@ -721,7 +735,7 @@ describe('createSlice', () => { }) test('has caseReducers for the asyncThunk', async () => { - const slice = createSlice({ + const slice = createThunkSlice({ name: 'test', initialState: [], reducers: (create) => ({ diff --git a/packages/toolkit/src/tests/createSlice.typetest.ts b/packages/toolkit/src/tests/createSlice.typetest.ts index 94ad5b773c..de0e781c1d 100644 --- a/packages/toolkit/src/tests/createSlice.typetest.ts +++ b/packages/toolkit/src/tests/createSlice.typetest.ts @@ -16,8 +16,15 @@ import type { ThunkDispatch, ValidateSliceCaseReducers, } from '@reduxjs/toolkit' -import { configureStore, isRejected } from '@reduxjs/toolkit' -import { createAction, createSlice } from '@reduxjs/toolkit' +import { + configureStore, + isRejected, + createAction, + createSlice, + buildCreateSlice, + asyncThunkCreator, + createAsyncThunk, +} from '@reduxjs/toolkit' import { expectExactType, expectType, expectUnknown } from './helpers' import { castDraft } from 'immer' @@ -849,3 +856,15 @@ const value = actionCreators.anyKey // @ts-expect-error counterSlice.selectSlice({}) } + +/** + * Test: buildCreateSlice + */ +{ + expectType(buildCreateSlice()) + buildCreateSlice({ + // @ts-expect-error not possible to recreate shape because symbol is not exported + creators: { asyncThunk: { [Symbol()]: createAsyncThunk } }, + }) + buildCreateSlice({ creators: { asyncThunk: asyncThunkCreator } }) +} From 31c8a93aae61d503f94d9fe9adec9ed0aa33a1a4 Mon Sep 17 00:00:00 2001 From: Ben Durrant Date: Sat, 11 Nov 2023 11:53:54 +0000 Subject: [PATCH 342/412] update docs --- docs/api/createSlice.mdx | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/docs/api/createSlice.mdx b/docs/api/createSlice.mdx index 45995fd752..18809c08da 100644 --- a/docs/api/createSlice.mdx +++ b/docs/api/createSlice.mdx @@ -138,7 +138,7 @@ const todosSlice = createSlice({ Alternatively, the `reducers` field can be a callback which receives a "create" object. -The main benefit of this is that you can create [async thunks](./createAsyncThunk) as part of your slice. Types are also slightly simplified for prepared reducers. +The main benefit of this is that you can create [async thunks](./createAsyncThunk) as part of your slice (though for bundle size reasons, you [need a bit of setup for this](#createasyncthunk)). Types are also slightly simplified for prepared reducers. ```ts title="Creator callback for reducers" import { createSlice, nanoid } from '@reduxjs/toolkit' @@ -240,6 +240,27 @@ create.preparedReducer( Creates an async thunk instead of an action creator. +:::warning Setup + +To avoid pulling `createAsyncThunk` into the bundle size of `createSlice` by default, some extra setup is required to use `create.asyncThunk`. + +The version of `createSlice` exported from RTK will throw an error if `create.asyncThunk` is called. + +Instead, import `buildCreateSlice` and `asyncThunkCreator`, and create your own version of `createSlice`: + +```ts +import { buildCreateSlice, asyncThunkCreator } from '@reduxjs/toolkit' + +// name is up to you +export const createSliceWithThunks = buildCreateSlice({ + creators: { asyncThunk: asyncThunkCreator }, +}) +``` + +Then import this `createSlice` as needed instead of the exported version from RTK. + +::: + **Parameters** - **payloadCreator** The thunk [payload creator](./createAsyncThunk#payloadcreator). From 68a85f2c5e290482fcee93b72a5acf87fbb8bed8 Mon Sep 17 00:00:00 2001 From: Ben Durrant Date: Sat, 11 Nov 2023 11:58:35 +0000 Subject: [PATCH 343/412] move JSDoc --- packages/toolkit/src/createSlice.ts | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/packages/toolkit/src/createSlice.ts b/packages/toolkit/src/createSlice.ts index 4c92da4ca9..f8f68684eb 100644 --- a/packages/toolkit/src/createSlice.ts +++ b/packages/toolkit/src/createSlice.ts @@ -583,14 +583,6 @@ interface BuildCreateSliceConfig { } } -/** - * A function that accepts an initial state, an object full of reducer - * functions, and a "slice name", and automatically generates - * action creators and action types that correspond to the - * reducers and state. - * - * @public - */ export function buildCreateSlice({ creators }: BuildCreateSliceConfig = {}) { const cAT = creators?.asyncThunk?.[asyncThunkSymbol] return function createSlice< @@ -787,6 +779,14 @@ export function buildCreateSlice({ creators }: BuildCreateSliceConfig = {}) { } } +/** + * A function that accepts an initial state, an object full of reducer + * functions, and a "slice name", and automatically generates + * action creators and action types that correspond to the + * reducers and state. + * + * @public + */ export const createSlice = buildCreateSlice() interface ReducerHandlingContext { From 1cf1601040754ae77c45107ee458255ec85e2a0e Mon Sep 17 00:00:00 2001 From: Ben Durrant Date: Sat, 11 Nov 2023 12:10:37 +0000 Subject: [PATCH 344/412] expectExactType --- packages/toolkit/src/tests/createSlice.typetest.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/toolkit/src/tests/createSlice.typetest.ts b/packages/toolkit/src/tests/createSlice.typetest.ts index de0e781c1d..90be16d4fd 100644 --- a/packages/toolkit/src/tests/createSlice.typetest.ts +++ b/packages/toolkit/src/tests/createSlice.typetest.ts @@ -861,7 +861,7 @@ const value = actionCreators.anyKey * Test: buildCreateSlice */ { - expectType(buildCreateSlice()) + expectExactType(createSlice)(buildCreateSlice()) buildCreateSlice({ // @ts-expect-error not possible to recreate shape because symbol is not exported creators: { asyncThunk: { [Symbol()]: createAsyncThunk } }, From 1890c5428b53fb5eb40415162b8acb10328e19d8 Mon Sep 17 00:00:00 2001 From: Ben Durrant Date: Sun, 12 Nov 2023 23:43:43 +0000 Subject: [PATCH 345/412] update error message --- errors.json | 3 ++- packages/toolkit/src/createSlice.ts | 3 ++- packages/toolkit/src/tests/createSlice.test.ts | 2 +- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/errors.json b/errors.json index ad527b78d8..c18837426e 100644 --- a/errors.json +++ b/errors.json @@ -33,5 +33,6 @@ "31": "\"middleware\" field must be a callback", "32": "When using custom hooks for context, all hooks need to be provided: .\\nHook was either not provided or not a function.", "33": "Existing Redux context detected. If you already have a store set up, please use the traditional Redux setup.", - "34": "selectSlice returned undefined for an uninjected slice reducer" + "34": "selectSlice returned undefined for an uninjected slice reducer", + "35": "Cannot use `create.asyncThunk` in the built-in `createSlice`. Use `buildCreateSlice({ creators: { asyncThunk: asyncThunkCreator } })` to create a customised version of `createSlice`." } \ No newline at end of file diff --git a/packages/toolkit/src/createSlice.ts b/packages/toolkit/src/createSlice.ts index f8f68684eb..9c20e89ec1 100644 --- a/packages/toolkit/src/createSlice.ts +++ b/packages/toolkit/src/createSlice.ts @@ -899,7 +899,8 @@ function handleThunkCaseReducerDefinition( ) { if (!cAT) { throw new Error( - 'Cannot use create.asyncThunk without custom initialisation' + 'Cannot use `create.asyncThunk` in the built-in `createSlice`. ' + + 'Use `buildCreateSlice({ creators: { asyncThunk: asyncThunkCreator } })` to create a customised version of `createSlice`.' ) } const { payloadCreator, fulfilled, pending, rejected, settled, options } = diff --git a/packages/toolkit/src/tests/createSlice.test.ts b/packages/toolkit/src/tests/createSlice.test.ts index 117ea8f92e..496a011e95 100644 --- a/packages/toolkit/src/tests/createSlice.test.ts +++ b/packages/toolkit/src/tests/createSlice.test.ts @@ -580,7 +580,7 @@ describe('createSlice', () => { initialState: [] as any[], reducers: (create) => ({ thunk: create.asyncThunk(() => {}) }), }) - ).toThrowErrorMatchingInlineSnapshot('"Cannot use create.asyncThunk without custom initialisation"') + ).toThrowErrorMatchingInlineSnapshot('"Cannot use `create.asyncThunk` in the built-in `createSlice`. Use `buildCreateSlice({ creators: { asyncThunk: asyncThunkCreator } })` to create a customised version of `createSlice`."') }) const createThunkSlice = buildCreateSlice({ creators: { asyncThunk: asyncThunkCreator }, From 8f6cea6791955c2239dc432940d26e1bafcd832d Mon Sep 17 00:00:00 2001 From: Ben Durrant Date: Tue, 14 Nov 2023 13:50:52 +0000 Subject: [PATCH 346/412] Create standardised methods of modifying reducer handler context. --- packages/toolkit/src/createSlice.ts | 150 ++++++++++++++++++++++++---- 1 file changed, 132 insertions(+), 18 deletions(-) diff --git a/packages/toolkit/src/createSlice.ts b/packages/toolkit/src/createSlice.ts index 9c20e89ec1..d0699eb5d8 100644 --- a/packages/toolkit/src/createSlice.ts +++ b/packages/toolkit/src/createSlice.ts @@ -13,9 +13,9 @@ import type { ReducerWithInitialState, } from './createReducer' import { createReducer } from './createReducer' -import type { ActionReducerMapBuilder } from './mapBuilders' +import type { ActionReducerMapBuilder, TypedActionCreator } from './mapBuilders' import { executeReducerBuilderCallback } from './mapBuilders' -import type { Id, Tail } from './tsHelpers' +import type { Id, Tail, TypeGuard } from './tsHelpers' import type { InjectConfig } from './combineSlices' import type { AsyncThunk, @@ -630,6 +630,43 @@ export function buildCreateSlice({ creators }: BuildCreateSliceConfig = {}) { sliceMatchers: [], } + const contextMethods: ReducerHandlingContextMethods = { + addCase( + typeOrActionCreator: string | TypedActionCreator, + reducer: CaseReducer + ) { + const type = + typeof typeOrActionCreator === 'string' + ? typeOrActionCreator + : typeOrActionCreator.type + if (!type) { + throw new Error( + '`context.addCase` cannot be called with an empty action type' + ) + } + if (type in context.sliceCaseReducersByType) { + throw new Error( + '`context.addCase` cannot be called with two reducers for the same action type: ' + + type + ) + } + context.sliceCaseReducersByType[type] = reducer + return contextMethods + }, + addMatcher(matcher, reducer) { + context.sliceMatchers.push({ matcher, reducer }) + return contextMethods + }, + exposeAction(name, actionCreator) { + context.actionCreators[name] = actionCreator + return contextMethods + }, + exposeCaseReducer(name, reducer) { + context.sliceCaseReducersByName[name] = reducer + return contextMethods + }, + } + reducerNames.forEach((reducerName) => { const reducerDefinition = reducers[reducerName] const reducerDetails: ReducerDetails = { @@ -641,14 +678,14 @@ export function buildCreateSlice({ creators }: BuildCreateSliceConfig = {}) { handleThunkCaseReducerDefinition( reducerDetails, reducerDefinition, - context, + contextMethods, cAT ) } else { handleNormalReducerDefinition( reducerDetails, reducerDefinition, - context + contextMethods ) } }) @@ -803,9 +840,84 @@ interface ReducerHandlingContext { actionCreators: Record } +interface ReducerHandlingContextMethods { + /** + * Adds a case reducer to handle a single action type. + * @param actionCreator - Either a plain action type string, or an action creator generated by [`createAction`](./createAction) that can be used to determine the action type. + * @param reducer - The actual case reducer function. + */ + addCase>( + actionCreator: ActionCreator, + reducer: CaseReducer> + ): ReducerHandlingContextMethods + /** + * Adds a case reducer to handle a single action type. + * @param actionCreator - Either a plain action type string, or an action creator generated by [`createAction`](./createAction) that can be used to determine the action type. + * @param reducer - The actual case reducer function. + */ + addCase>( + type: Type, + reducer: CaseReducer + ): ReducerHandlingContextMethods + + /** + * Allows you to match incoming actions against your own filter function instead of only the `action.type` property. + * @remarks + * If multiple matcher reducers match, all of them will be executed in the order + * they were defined in - even if a case reducer already matched. + * All calls to `builder.addMatcher` must come after any calls to `builder.addCase` and before any calls to `builder.addDefaultCase`. + * @param matcher - A matcher function. In TypeScript, this should be a [type predicate](https://www.typescriptlang.org/docs/handbook/2/narrowing.html#using-type-predicates) + * function + * @param reducer - The actual case reducer function. + * + */ + addMatcher( + matcher: TypeGuard, + reducer: CaseReducer + ): ReducerHandlingContextMethods + /** + * Add an action to be exposed under the final `slice.actions` key. + * @param name The key to be exposed as. + * @param actionCreator The action to expose. + * @example + * context.exposeAction("addPost", createAction("addPost")); + * + * export const { addPost } = slice.actions + * + * dispatch(addPost(post)) + */ + exposeAction( + name: string, + actionCreator: Function + ): ReducerHandlingContextMethods + /** + * Add a case reducer to be exposed under the final `slice.caseReducers` key. + * @param name The key to be exposed as. + * @param reducer The reducer to expose. + * @example + * context.exposeCaseReducer("addPost", (state, action: PayloadAction) => { + * state.push(action.payload) + * }) + * + * slice.caseReducers.addPost([], addPost(post)) + */ + exposeCaseReducer( + name: string, + reducer: + | CaseReducer + | Pick< + AsyncThunkSliceReducerDefinition, + 'fulfilled' | 'rejected' | 'pending' | 'settled' + > + ): ReducerHandlingContextMethods +} + interface ReducerDetails { + /** The key the reducer was defined under */ reducerName: string + /** The predefined action type, i.e. `${slice.name}/${reducerName}` */ type: string + /** Whether create. notation was used when defining reducers */ createNotation: boolean } @@ -852,7 +964,7 @@ function handleNormalReducerDefinition( maybeReducerWithPrepare: | CaseReducer | CaseReducerWithPrepare>, - context: ReducerHandlingContext + context: ReducerHandlingContextMethods ) { let caseReducer: CaseReducer let prepareCallback: PrepareAction | undefined @@ -870,11 +982,13 @@ function handleNormalReducerDefinition( } else { caseReducer = maybeReducerWithPrepare } - context.sliceCaseReducersByName[reducerName] = caseReducer - context.sliceCaseReducersByType[type] = caseReducer - context.actionCreators[reducerName] = prepareCallback - ? createAction(type, prepareCallback) - : createAction(type) + context + .addCase(type, caseReducer) + .exposeCaseReducer(reducerName, caseReducer) + .exposeAction( + reducerName, + prepareCallback ? createAction(type, prepareCallback) : createAction(type) + ) } function isAsyncThunkSliceReducerDefinition( @@ -894,7 +1008,7 @@ function isCaseReducerWithPrepareDefinition( function handleThunkCaseReducerDefinition( { type, reducerName }: ReducerDetails, reducerDefinition: AsyncThunkSliceReducerDefinition, - context: ReducerHandlingContext, + context: ReducerHandlingContextMethods, cAT: typeof _createAsyncThunk | undefined ) { if (!cAT) { @@ -906,27 +1020,27 @@ function handleThunkCaseReducerDefinition( const { payloadCreator, fulfilled, pending, rejected, settled, options } = reducerDefinition const thunk = cAT(type, payloadCreator, options as any) - context.actionCreators[reducerName] = thunk + context.exposeAction(reducerName, thunk) if (fulfilled) { - context.sliceCaseReducersByType[thunk.fulfilled.type] = fulfilled + context.addCase(thunk.fulfilled, fulfilled) } if (pending) { - context.sliceCaseReducersByType[thunk.pending.type] = pending + context.addCase(thunk.pending, pending) } if (rejected) { - context.sliceCaseReducersByType[thunk.rejected.type] = rejected + context.addCase(thunk.rejected, rejected) } if (settled) { - context.sliceMatchers.push({ matcher: thunk.settled, reducer: settled }) + context.addMatcher(thunk.settled, settled) } - context.sliceCaseReducersByName[reducerName] = { + context.exposeCaseReducer(reducerName, { fulfilled: fulfilled || noop, pending: pending || noop, rejected: rejected || noop, settled: settled || noop, - } + }) } function noop() {} From ea2bdec42165710835e77a56d9d41b9b1b5d29d6 Mon Sep 17 00:00:00 2001 From: Mark Erikson Date: Tue, 14 Nov 2023 12:26:37 -0500 Subject: [PATCH 347/412] Update errors.json --- errors.json | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/errors.json b/errors.json index c18837426e..daa3c06132 100644 --- a/errors.json +++ b/errors.json @@ -34,5 +34,7 @@ "32": "When using custom hooks for context, all hooks need to be provided: .\\nHook was either not provided or not a function.", "33": "Existing Redux context detected. If you already have a store set up, please use the traditional Redux setup.", "34": "selectSlice returned undefined for an uninjected slice reducer", - "35": "Cannot use `create.asyncThunk` in the built-in `createSlice`. Use `buildCreateSlice({ creators: { asyncThunk: asyncThunkCreator } })` to create a customised version of `createSlice`." + "35": "Cannot use `create.asyncThunk` in the built-in `createSlice`. Use `buildCreateSlice({ creators: { asyncThunk: asyncThunkCreator } })` to create a customised version of `createSlice`.", + "36": "`context.addCase` cannot be called with an empty action type", + "37": "`context.addCase` cannot be called with two reducers for the same action type: type" } \ No newline at end of file From a385388073cd9f39ef00aa04cb09f470d3363251 Mon Sep 17 00:00:00 2001 From: Mark Erikson Date: Tue, 14 Nov 2023 12:26:51 -0500 Subject: [PATCH 348/412] Mark `cAT` with `PURE` to ensure tree-shaking --- packages/toolkit/src/createAsyncThunk.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/toolkit/src/createAsyncThunk.ts b/packages/toolkit/src/createAsyncThunk.ts index e8a28cbbb3..f75653bda9 100644 --- a/packages/toolkit/src/createAsyncThunk.ts +++ b/packages/toolkit/src/createAsyncThunk.ts @@ -487,7 +487,7 @@ type CreateAsyncThunk = { > } -export const createAsyncThunk = (() => { +export const createAsyncThunk = /* @__PURE__ */ (() => { function createAsyncThunk< Returned, ThunkArg, From 16f5d62f17c39e37b8b2a42c073b2b371af1a09c Mon Sep 17 00:00:00 2001 From: Ben Durrant Date: Tue, 14 Nov 2023 22:36:17 +0000 Subject: [PATCH 349/412] Restore the toString override, but keep it out of the docs. --- docs/usage/usage-guide.md | 2 +- packages/toolkit/src/createAction.ts | 8 ++++++-- packages/toolkit/src/tests/createAction.test.ts | 7 +++++++ 3 files changed, 14 insertions(+), 3 deletions(-) diff --git a/docs/usage/usage-guide.md b/docs/usage/usage-guide.md index b28eff26af..8baf6c938a 100644 --- a/docs/usage/usage-guide.md +++ b/docs/usage/usage-guide.md @@ -300,7 +300,7 @@ const reducer = createReducer({}, (builder) => { This means you don't have to write or use a separate action type variable, or repeat the name and value of an action type like `const SOME_ACTION_TYPE = "SOME_ACTION_TYPE"`. -If you want to use one of these action creators in a switch statement, you need to call `actionCreator.type` yourself: +If you want to use one of these action creators in a switch statement, you need to reference `actionCreator.type` yourself: ```js const actionCreator = createAction('SOME_ACTION_TYPE') diff --git a/packages/toolkit/src/createAction.ts b/packages/toolkit/src/createAction.ts index dbfbeb70a9..af0968fe14 100644 --- a/packages/toolkit/src/createAction.ts +++ b/packages/toolkit/src/createAction.ts @@ -224,7 +224,8 @@ export type PayloadActionCreator< /** * A utility function to create an action creator for the given action type * string. The action creator accepts a single argument, which will be included - * in the action object as a field called payload. + * in the action object as a field called payload. The action creator function + * will also have its toString() overridden so that it returns the action type. * * @param type The action type to use for created actions. * @param prepare (optional) a method that takes any number of arguments and returns { payload } or { payload, meta }. @@ -239,7 +240,8 @@ export function createAction

( /** * A utility function to create an action creator for the given action type * string. The action creator accepts a single argument, which will be included - * in the action object as a field called payload. + * in the action object as a field called payload. The action creator function + * will also have its toString() overridden so that it returns the action type. * * @param type The action type to use for created actions. * @param prepare (optional) a method that takes any number of arguments and returns { payload } or { payload, meta }. @@ -273,6 +275,8 @@ export function createAction(type: string, prepareAction?: Function): any { return { type, payload: args[0] } } + actionCreator.toString = () => `${type}` + actionCreator.type = type actionCreator.match = (action: Action): action is PayloadAction => diff --git a/packages/toolkit/src/tests/createAction.test.ts b/packages/toolkit/src/tests/createAction.test.ts index 64f3d00054..e9064a1539 100644 --- a/packages/toolkit/src/tests/createAction.test.ts +++ b/packages/toolkit/src/tests/createAction.test.ts @@ -9,6 +9,13 @@ describe('createAction', () => { }) }) + describe('when stringifying action', () => { + it('should return the action type', () => { + const actionCreator = createAction('A_TYPE') + expect(`${actionCreator}`).toEqual('A_TYPE') + }) + }) + describe('when passing a prepareAction method only returning a payload', () => { it('should use the payload returned from the prepareAction method', () => { const actionCreator = createAction('A_TYPE', (a: number) => ({ From bce33717cce55a2854f213513330cd4a2dedb7a4 Mon Sep 17 00:00:00 2001 From: Ben Durrant Date: Tue, 14 Nov 2023 22:49:56 +0000 Subject: [PATCH 350/412] emplace things --- packages/toolkit/src/combineSlices.ts | 56 +++++++------ packages/toolkit/src/createSlice.ts | 53 +++++++------ .../toolkit/src/dynamicMiddleware/index.ts | 13 +-- packages/toolkit/src/utils.ts | 79 +++++++++++++++++++ 4 files changed, 136 insertions(+), 65 deletions(-) diff --git a/packages/toolkit/src/combineSlices.ts b/packages/toolkit/src/combineSlices.ts index 58d07ece28..1a10538cd8 100644 --- a/packages/toolkit/src/combineSlices.ts +++ b/packages/toolkit/src/combineSlices.ts @@ -8,6 +8,7 @@ import type { UnionToIntersection, WithOptionalProp, } from './tsHelpers' +import { emplace } from './utils' type SliceLike = { reducerPath: ReducerPath @@ -330,37 +331,34 @@ const stateProxyMap = new WeakMap() const createStateProxy = ( state: State, reducerMap: Partial> -) => { - let proxy = stateProxyMap.get(state) - if (!proxy) { - proxy = new Proxy(state, { - get: (target, prop, receiver) => { - if (prop === ORIGINAL_STATE) return target - const result = Reflect.get(target, prop, receiver) - if (typeof result === 'undefined') { - const reducer = reducerMap[prop.toString()] - if (reducer) { - // ensure action type is random, to prevent reducer treating it differently - const reducerResult = reducer(undefined, { type: nanoid() }) - if (typeof reducerResult === 'undefined') { - throw new Error( - `The slice reducer for key "${prop.toString()}" returned undefined when called for selector(). ` + - `If the state passed to the reducer is undefined, you must ` + - `explicitly return the initial state. The initial state may ` + - `not be undefined. If you don't want to set a value for this reducer, ` + - `you can use null instead of undefined.` - ) +) => + emplace(stateProxyMap, state, { + insert: () => + new Proxy(state, { + get: (target, prop, receiver) => { + if (prop === ORIGINAL_STATE) return target + const result = Reflect.get(target, prop, receiver) + if (typeof result === 'undefined') { + const reducer = reducerMap[prop.toString()] + if (reducer) { + // ensure action type is random, to prevent reducer treating it differently + const reducerResult = reducer(undefined, { type: nanoid() }) + if (typeof reducerResult === 'undefined') { + throw new Error( + `The slice reducer for key "${prop.toString()}" returned undefined when called for selector(). ` + + `If the state passed to the reducer is undefined, you must ` + + `explicitly return the initial state. The initial state may ` + + `not be undefined. If you don't want to set a value for this reducer, ` + + `you can use null instead of undefined.` + ) + } + return reducerResult } - return reducerResult } - } - return result - }, - }) - stateProxyMap.set(state, proxy) - } - return proxy as State -} + return result + }, + }), + }) as State const original = (state: any) => { if (!isStateProxy(state)) { diff --git a/packages/toolkit/src/createSlice.ts b/packages/toolkit/src/createSlice.ts index d0699eb5d8..82a724e6c2 100644 --- a/packages/toolkit/src/createSlice.ts +++ b/packages/toolkit/src/createSlice.ts @@ -1,4 +1,5 @@ import type { Action, UnknownAction, Reducer } from 'redux' +import type { Selector } from 'reselect' import type { ActionCreatorWithoutPayload, PayloadAction, @@ -25,6 +26,7 @@ import type { OverrideThunkApiConfigs, } from './createAsyncThunk' import { createAsyncThunk as _createAsyncThunk } from './createAsyncThunk' +import { emplace } from './utils' const asyncThunkSymbol = Symbol.for('rtk-slice-createasyncthunk') // type is annotated because it's too long to infer @@ -756,35 +758,34 @@ export function buildCreateSlice({ creators }: BuildCreateSliceConfig = {}) { return _reducer.getInitialState() }, getSelectors(selectState: (rootState: any) => State = selectSelf) { - let selectorCache = injectedSelectorCache.get(this) - if (!selectorCache) { - selectorCache = new WeakMap() - injectedSelectorCache.set(this, selectorCache) - } - let cached = selectorCache.get(selectState) - if (!cached) { - cached = {} - for (const [name, selector] of Object.entries( - options.selectors ?? {} - )) { - cached[name] = (rootState: any, ...args: any[]) => { - let sliceState = selectState.call(this, rootState) - if (typeof sliceState === 'undefined') { - // check if injectInto has been called - if (this !== slice) { - sliceState = this.getInitialState() - } else if (process.env.NODE_ENV !== 'production') { - throw new Error( - 'selectState returned undefined for an uninjected slice reducer' - ) + const selectorCache = emplace(injectedSelectorCache, this, { + insert: () => new WeakMap(), + }) + + return emplace(selectorCache, selectState, { + insert: () => { + const map: Record> = {} + for (const [name, selector] of Object.entries( + options.selectors ?? {} + )) { + map[name] = (rootState: any, ...args: any[]) => { + let sliceState = selectState.call(this, rootState) + if (typeof sliceState === 'undefined') { + // check if injectInto has been called + if (this !== slice) { + sliceState = this.getInitialState() + } else if (process.env.NODE_ENV !== 'production') { + throw new Error( + 'selectState returned undefined for an uninjected slice reducer' + ) + } } + return selector(sliceState, ...args) } - return selector(sliceState, ...args) } - } - selectorCache.set(selectState, cached) - } - return cached as any + return map + }, + }) as any }, selectSlice(state) { let sliceState = state[this.reducerPath] diff --git a/packages/toolkit/src/dynamicMiddleware/index.ts b/packages/toolkit/src/dynamicMiddleware/index.ts index 12f5b6522a..5277ca10e5 100644 --- a/packages/toolkit/src/dynamicMiddleware/index.ts +++ b/packages/toolkit/src/dynamicMiddleware/index.ts @@ -7,7 +7,7 @@ import { compose } from 'redux' import { createAction, isAction } from '../createAction' import { isAllOf } from '../matchers' import { nanoid } from '../nanoid' -import { find } from '../utils' +import { emplace, find } from '../utils' import type { WithMiddleware, AddMiddleware, @@ -69,15 +69,8 @@ export const createDynamicMiddleware = < ) as AddMiddleware const getFinalMiddleware: Middleware<{}, State, Dispatch> = (api) => { - const appliedMiddleware = Array.from(middlewareMap.values()).map( - (entry) => { - let applied = entry.applied.get(api) - if (!applied) { - applied = entry.middleware(api) - entry.applied.set(api, applied) - } - return applied - } + const appliedMiddleware = Array.from(middlewareMap.values()).map((entry) => + emplace(entry.applied, api, { insert: () => entry.middleware(api) }) ) return compose(...appliedMiddleware) } diff --git a/packages/toolkit/src/utils.ts b/packages/toolkit/src/utils.ts index 2e29b2bc9e..7dfe43dc3a 100644 --- a/packages/toolkit/src/utils.ts +++ b/packages/toolkit/src/utils.ts @@ -87,3 +87,82 @@ export class Tuple = []> extends Array< export function freezeDraftable(val: T) { return isDraftable(val) ? createNextState(val, () => {}) : val } + +interface WeakMapEmplaceHandler { + /** + * Will be called to get value, if no value is currently in map. + */ + insert?(key: K, map: WeakMap): V + /** + * Will be called to update a value, if one exists already. + */ + update?(previous: V, key: K, map: WeakMap): V +} + +interface MapEmplaceHandler { + /** + * Will be called to get value, if no value is currently in map. + */ + insert?(key: K, map: Map): V + /** + * Will be called to update a value, if one exists already. + */ + update?(previous: V, key: K, map: Map): V +} + +export function emplace( + map: Map, + key: K, + handler: MapEmplaceHandler +): V +export function emplace( + map: WeakMap, + key: K, + handler: WeakMapEmplaceHandler +): V +/** + * Allow inserting a new value, or updating an existing one + * @throws if called for a key with no current value and no `insert` handler is provided + * @returns current value in map (after insertion/updating) + * ```ts + * // return current value if already in map, otherwise initialise to 0 and return that + * const num = emplace(map, key, { + * insert: () => 0 + * }) + * + * // increase current value by one if already in map, otherwise initialise to 0 + * const num = emplace(map, key, { + * update: (n) => n + 1, + * insert: () => 0, + * }) + * + * // only update if value's already in the map - and increase it by one + * if (map.has(key)) { + * const num = emplace(map, key, { + * update: (n) => n + 1, + * }) + * } + * ``` + * + * @remarks + * Based on https://github.com/tc39/proposal-upsert currently in Stage 2 - maybe in a few years we'll be able to replace this with direct method calls + */ +export function emplace( + map: WeakMap, + key: K, + handler: WeakMapEmplaceHandler +): V { + if (map.has(key)) { + let value = map.get(key) as V + if (handler.update) { + value = handler.update(value, key, map) + map.set(key, value) + } + return value + } + if (!handler.insert) + throw new Error('No insert provided for key not already in map') + const inserted = handler.insert(key, map) + map.set(key, inserted) + return inserted +} From 8dec012e5c9042f5270dc7fc17e9c0c22878f022 Mon Sep 17 00:00:00 2001 From: Ben Durrant Date: Tue, 14 Nov 2023 22:57:45 +0000 Subject: [PATCH 351/412] add unwrapped property to wrapped selectors --- packages/toolkit/src/createSlice.ts | 59 +++++++++++++------ .../toolkit/src/tests/createSlice.test.ts | 16 +++-- .../toolkit/src/tests/createSlice.typetest.ts | 7 ++- 3 files changed, 59 insertions(+), 23 deletions(-) diff --git a/packages/toolkit/src/createSlice.ts b/packages/toolkit/src/createSlice.ts index 82a724e6c2..0057873f8e 100644 --- a/packages/toolkit/src/createSlice.ts +++ b/packages/toolkit/src/createSlice.ts @@ -533,6 +533,14 @@ type SliceDefinedCaseReducers> = { : never } +type RemappedSelector = S extends Selector< + any, + infer R, + infer P +> + ? Selector & { unwrapped: S } + : never + /** * Extracts the final selector type from the `selectors` object. * @@ -543,10 +551,10 @@ type SliceDefinedSelectors< Selectors extends SliceSelectors, RootState > = { - [K in keyof Selectors as string extends K ? never : K]: ( - rootState: RootState, - ...args: Tail> - ) => ReturnType + [K in keyof Selectors as string extends K ? never : K]: RemappedSelector< + Selectors[K], + RootState + > } /** @@ -768,20 +776,12 @@ export function buildCreateSlice({ creators }: BuildCreateSliceConfig = {}) { for (const [name, selector] of Object.entries( options.selectors ?? {} )) { - map[name] = (rootState: any, ...args: any[]) => { - let sliceState = selectState.call(this, rootState) - if (typeof sliceState === 'undefined') { - // check if injectInto has been called - if (this !== slice) { - sliceState = this.getInitialState() - } else if (process.env.NODE_ENV !== 'production') { - throw new Error( - 'selectState returned undefined for an uninjected slice reducer' - ) - } - } - return selector(sliceState, ...args) - } + map[name] = wrapSelector( + this, + selector, + selectState, + this !== slice + ) } return map }, @@ -817,6 +817,29 @@ export function buildCreateSlice({ creators }: BuildCreateSliceConfig = {}) { } } +function wrapSelector>( + slice: Slice, + selector: S, + selectState: Selector, + injected?: boolean +) { + function wrapper(rootState: NewState, ...args: any[]) { + let sliceState = selectState.call(slice, rootState) + if (typeof sliceState === 'undefined') { + if (injected) { + sliceState = slice.getInitialState() + } else if (process.env.NODE_ENV !== 'production') { + throw new Error( + 'selectState returned undefined for an uninjected slice reducer' + ) + } + } + return selector(sliceState, ...args) + } + wrapper.unwrapped = selector + return wrapper as RemappedSelector +} + /** * A function that accepts an initial state, an object full of reducer * functions, and a "slice name", and automatically generates diff --git a/packages/toolkit/src/tests/createSlice.test.ts b/packages/toolkit/src/tests/createSlice.test.ts index 496a011e95..1878cf7765 100644 --- a/packages/toolkit/src/tests/createSlice.test.ts +++ b/packages/toolkit/src/tests/createSlice.test.ts @@ -466,12 +466,15 @@ describe('createSlice', () => { reducers: {}, selectors: { selectSlice: (state) => state, - selectMultiple: (state, multiplier: number) => state * multiplier, + selectMultiple: Object.assign( + (state: number, multiplier: number) => state * multiplier, + { test: 0 } + ), }, }) - it('expects reducer under slice.name if no selectState callback passed', () => { + it('expects reducer under slice.reducerPath if no selectState callback passed', () => { const testState = { - [slice.name]: slice.getInitialState(), + [slice.reducerPath]: slice.getInitialState(), } const { selectSlice, selectMultiple } = slice.selectors expect(selectSlice(testState)).toBe(slice.getInitialState()) @@ -487,6 +490,9 @@ describe('createSlice', () => { expect(selectSlice(customState)).toBe(slice.getInitialState()) expect(selectMultiple(customState, 2)).toBe(slice.getInitialState() * 2) }) + it('allows accessing properties on the selector', () => { + expect(slice.selectors.selectMultiple.unwrapped.test).toBe(0) + }) }) describe('slice injections', () => { it('uses injectInto to inject slice into combined reducer', () => { @@ -580,7 +586,9 @@ describe('createSlice', () => { initialState: [] as any[], reducers: (create) => ({ thunk: create.asyncThunk(() => {}) }), }) - ).toThrowErrorMatchingInlineSnapshot('"Cannot use `create.asyncThunk` in the built-in `createSlice`. Use `buildCreateSlice({ creators: { asyncThunk: asyncThunkCreator } })` to create a customised version of `createSlice`."') + ).toThrowErrorMatchingInlineSnapshot( + '"Cannot use `create.asyncThunk` in the built-in `createSlice`. Use `buildCreateSlice({ creators: { asyncThunk: asyncThunkCreator } })` to create a customised version of `createSlice`."' + ) }) const createThunkSlice = buildCreateSlice({ creators: { asyncThunk: asyncThunkCreator }, diff --git a/packages/toolkit/src/tests/createSlice.typetest.ts b/packages/toolkit/src/tests/createSlice.typetest.ts index 90be16d4fd..40488d7e9d 100644 --- a/packages/toolkit/src/tests/createSlice.typetest.ts +++ b/packages/toolkit/src/tests/createSlice.typetest.ts @@ -540,7 +540,10 @@ const value = actionCreators.anyKey selectors: { selectValue: (state) => state.value, selectMultiply: (state, multiplier: number) => state.value * multiplier, - selectToFixed: (state) => state.value.toFixed(2), + selectToFixed: Object.assign( + (state: { value: number }) => state.value.toFixed(2), + { static: true } + ), }, }) @@ -555,6 +558,8 @@ const value = actionCreators.anyKey expectType(selectMultiply(rootState, 2)) expectType(selectToFixed(rootState)) + expectType(selectToFixed.unwrapped.static) + const nestedState = { nested: rootState, } From b2e1579073f5a7503e77b53d99e03e35a9ebf972 Mon Sep 17 00:00:00 2001 From: Ben Durrant Date: Tue, 14 Nov 2023 23:02:23 +0000 Subject: [PATCH 352/412] remove stray conflict markers --- docs/api/createSlice.mdx | 5 ----- docs/rtk-query/api/created-api/api-slice-utils.mdx | 10 ---------- 2 files changed, 15 deletions(-) diff --git a/docs/api/createSlice.mdx b/docs/api/createSlice.mdx index 18809c08da..690840868e 100644 --- a/docs/api/createSlice.mdx +++ b/docs/api/createSlice.mdx @@ -394,7 +394,6 @@ const counterSlice = createSlice({ }) ``` -<<<<<<< HEAD This cycle can be fixed by providing an explicit return type for the selector: ```ts no-transpile @@ -440,10 +439,6 @@ const counterSlice = createSlice({ ::: -======= - -> > > > > > > master - ## Return Value `createSlice` will return an object that looks like: diff --git a/docs/rtk-query/api/created-api/api-slice-utils.mdx b/docs/rtk-query/api/created-api/api-slice-utils.mdx index 7ecadaf5fc..816d5298a2 100644 --- a/docs/rtk-query/api/created-api/api-slice-utils.mdx +++ b/docs/rtk-query/api/created-api/api-slice-utils.mdx @@ -259,21 +259,11 @@ function selectInvalidatedBy( A function that can select query parameters to be invalidated. The function accepts two arguments -<<<<<<< HEAD - -======= - -> > > > > > > master - the root state and - the cache tags to be invalidated. It returns an array that contains -<<<<<<< HEAD - -======= - -> > > > > > > master - the endpoint name, - the original args and From 1789d80bcdf1eefa4ac5e959c11f5df2283d3bda Mon Sep 17 00:00:00 2001 From: Ben Durrant Date: Tue, 14 Nov 2023 23:09:42 +0000 Subject: [PATCH 353/412] add note re: unwrapped --- docs/api/createSlice.mdx | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/docs/api/createSlice.mdx b/docs/api/createSlice.mdx index 690840868e..dbeab9b979 100644 --- a/docs/api/createSlice.mdx +++ b/docs/api/createSlice.mdx @@ -513,6 +513,40 @@ const { selectValue } = counterSlice.selectors console.log(selectValue({ counter: { value: 2 } })) // 2 ``` +:::note + +The original selector passed is attached to the wrapped selector as `.unwrapped`. For example: + +```ts +import { createSlice, createSelector } from '@reduxjs/toolkit' + +interface CounterState { + value: number +} + +const counterSlice = createSlice({ + name: 'counter', + initialState: { value: 0 } as CounterState, + reducers: { + // omitted + }, + selectors: { + selectDouble: createSelector( + (sliceState: CounterState) => sliceState.value, + (value) => value * 2 + ), + }, +}) + +const { selectDouble } = counterSlice.selectors + +console.log(selectDouble({ counter: { value: 2 } })) // 4 +console.log(selectDouble({ counter: { value: 3 } })) // 6 +console.log(selectDouble.unwrapped.recomputations) // 2 +``` + +::: + #### `getSelectors` `slice.getSelectors` is called with a single parameter, a `selectState` callback. This function should receive the store root state (or whatever you expect to call the resulting selectors with) and return the slice state. From 82a7c194b066bfc2f368530c2df98c8d5f8ed105 Mon Sep 17 00:00:00 2001 From: Ben Durrant Date: Wed, 15 Nov 2023 21:10:45 +0000 Subject: [PATCH 354/412] Add `settled` and `selectSlice` to docs. --- docs/api/createAsyncThunk.mdx | 21 ++++++++++++++++++++- docs/api/createSlice.mdx | 23 ++++++++++++++++------- 2 files changed, 36 insertions(+), 8 deletions(-) diff --git a/docs/api/createAsyncThunk.mdx b/docs/api/createAsyncThunk.mdx index 2777e187d2..c44803fda0 100644 --- a/docs/api/createAsyncThunk.mdx +++ b/docs/api/createAsyncThunk.mdx @@ -212,7 +212,7 @@ type RejectedWithValue = ( To handle these actions in your reducers, reference the action creators in `createReducer` or `createSlice` using the "builder callback" notation. -```ts no-transpile {2,6,14,23} +```ts no-transpile {2,10} const reducer1 = createReducer(initialState, (builder) => { builder.addCase(fetchUserById.fulfilled, (state, action) => {}) }) @@ -227,6 +227,25 @@ const reducer2 = createSlice({ }) ``` +Additionally, a `settled` matcher is attached, for matching against both fulfilled and rejected actions. Conceptually this is similar to a `finally` block. + +Make sure you use `addMatcher` instead of `addCase`, since `settled` is a matcher rather than an action creator. + +```ts no-transpile {2,10} +const reducer1 = createReducer(initialState, (builder) => { + builder.addMatcher(fetchUserById.settled, (state, action) => {}) +}) + +const reducer2 = createSlice({ + name: 'users', + initialState, + reducers: {}, + extraReducers: (builder) => { + builder.addMatcher(fetchUserById.settled, (state, action) => {}) + }, +}) +``` + ## Handling Thunk Results ### Unwrapping Result Actions diff --git a/docs/api/createSlice.mdx b/docs/api/createSlice.mdx index dbeab9b979..d7ef6fec79 100644 --- a/docs/api/createSlice.mdx +++ b/docs/api/createSlice.mdx @@ -266,7 +266,7 @@ Then import this `createSlice` as needed instead of the exported version from RT - **payloadCreator** The thunk [payload creator](./createAsyncThunk#payloadcreator). - **config** The configuration object. (optional) -The configuration object can contain case reducers for each of the [lifecycle actions](./createAsyncThunk#promise-lifecycle-actions) (`pending`, `fulfilled`, and `rejected`). +The configuration object can contain case reducers for each of the [lifecycle actions](./createAsyncThunk#promise-lifecycle-actions) (`pending`, `fulfilled`, and `rejected`), as well as a `settled` reducer that will run for both fulfilled and rejected actions (note that this will run _after_ any provided `fulfilled`/`rejected` reducers. Conceptually it can be thought of like a `finally` block.). Each case reducer will be attached to the slice's `caseReducers` object, e.g. `slice.caseReducers.fetchTodo.fulfilled`. @@ -283,12 +283,14 @@ create.asyncThunk( state.loading = true }, rejected: (state, action) => { - state.loading = false + state.error = action.payload ?? action.error }, fulfilled: (state, action) => { - state.loading = false state.todos.push(action.payload) }, + settled: (state, action) => { + state.loading = false + } options: { idGenerator: uuid, }, @@ -298,7 +300,7 @@ create.asyncThunk( :::note -Typing for the `create.asyncThunk` works in the same way as [`createAsyncThunk`](usage/usage-with-typescript#createasyncthunk), with one key difference. +Typing for the `create.asyncThunk` works in the same way as [`createAsyncThunk`](../usage/usage-with-typescript#createasyncthunk), with one key difference. A type for `state` and/or `dispatch` _cannot_ be provided as part of the `ThunkApiConfig`, as this would cause circular types. @@ -316,7 +318,7 @@ create.asyncThunk( ) ``` -For common thunk API configuration options, a [`withTypes` helper](usage/usage-with-typescript#defining-a-pre-typed-createasyncthunk) is provided: +For common thunk API configuration options, a [`withTypes` helper](../usage/usage-with-typescript#defining-a-pre-typed-createasyncthunk) is provided: ```ts no-transpile reducers: (create) => { @@ -451,6 +453,7 @@ const counterSlice = createSlice({ caseReducers: Record. getInitialState: () => State, reducerPath: string, + selectSlice: Selector; selectors: Record, getSelectors: (selectState: (rootState: RootState) => State) => Record injectInto: (injectable: Injectable, config?: InjectConfig & { reducerPath?: string }) => InjectedSlice @@ -488,7 +491,9 @@ As a result, there are two ways of getting final selectors: Most commonly, the slice is reliably mounted under its [`reducerPath`](#reducerPath). -Following this, the slice has a `selectors` object attached, which creates selectors with the assumption that the slice is located under `rootState[slice.reducerPath]`. +Following this, the slice has a `selectSlice` selector attached, which assumes that the slice is located under `rootState[slice.reducerPath]`. + +`slice.selectors` then uses this selector to wrap each of the selectors provided. ```ts import { createSlice } from '@reduxjs/toolkit' @@ -508,6 +513,8 @@ const counterSlice = createSlice({ }, }) +console.log(counterSlice.selectSlice({ counter: { value: 2 } })) // { value: 2 } + const { selectValue } = counterSlice.selectors console.log(selectValue({ counter: { value: 2 } })) // 2 @@ -571,8 +578,10 @@ console.log(selectValue({ value: 2 })) // 2 The [`slice.selectors`](#selectors-2) object is the equivalent of calling ```ts no-transpile +const { selectValue } = counterSlice.getSelectors(counterSlice.selectSlice) +// or const { selectValue } = counterSlice.getSelectors( - (rootState: RootState) => rootState[counterSlice.reducerPath] + (state: RootState) => state[counterSlice.reducerPath] ) ``` From caccbcaad63ee712f5e3a86549c7dbfb4d1f4f02 Mon Sep 17 00:00:00 2001 From: Mark Erikson Date: Wed, 15 Nov 2023 23:34:03 -0500 Subject: [PATCH 355/412] Add missing error --- errors.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/errors.json b/errors.json index daa3c06132..c21258fd0b 100644 --- a/errors.json +++ b/errors.json @@ -36,5 +36,6 @@ "34": "selectSlice returned undefined for an uninjected slice reducer", "35": "Cannot use `create.asyncThunk` in the built-in `createSlice`. Use `buildCreateSlice({ creators: { asyncThunk: asyncThunkCreator } })` to create a customised version of `createSlice`.", "36": "`context.addCase` cannot be called with an empty action type", - "37": "`context.addCase` cannot be called with two reducers for the same action type: type" + "37": "`context.addCase` cannot be called with two reducers for the same action type: type", + "38": "No insert provided for key not already in map" } \ No newline at end of file From a86fdec679a33afe7750b48ea023ab3048028d3e Mon Sep 17 00:00:00 2001 From: Mark Erikson Date: Wed, 15 Nov 2023 23:35:21 -0500 Subject: [PATCH 356/412] Bump RTK deps for RC --- packages/toolkit/package.json | 10 ++++---- yarn.lock | 44 +++++++++++++++++------------------ 2 files changed, 27 insertions(+), 27 deletions(-) diff --git a/packages/toolkit/package.json b/packages/toolkit/package.json index 838df2290d..da4c9c5e2d 100644 --- a/packages/toolkit/package.json +++ b/packages/toolkit/package.json @@ -112,14 +112,14 @@ "react" ], "dependencies": { - "immer": "^10.0.2", - "redux": "^5.0.0-beta.0", - "redux-thunk": "^3.0.0-beta.0", - "reselect": "^5.0.0-beta.0" + "immer": "^10.0.3", + "redux": "^5.0.0-rc.0", + "redux-thunk": "^3.0.0-rc.0", + "reselect": "^5.0.0-beta.1" }, "peerDependencies": { "react": "^16.9.0 || ^17.0.0 || ^18", - "react-redux": "^7.2.1 || ^8.0.2 || ^9.0.0-beta.0" + "react-redux": "^7.2.1 || ^8.1.3 || ^9.0.0-rc.0" }, "peerDependenciesMeta": { "react": { diff --git a/yarn.lock b/yarn.lock index c391087742..c6276db663 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7013,7 +7013,7 @@ __metadata: eslint-plugin-react: ^7.23.2 eslint-plugin-react-hooks: ^4.2.0 fs-extra: ^9.1.0 - immer: ^10.0.2 + immer: ^10.0.3 invariant: ^2.2.4 jsdom: ^21.0.0 json-stringify-safe: ^5.0.1 @@ -7021,9 +7021,9 @@ __metadata: node-fetch: ^2.6.1 prettier: ^2.2.1 query-string: ^7.0.1 - redux: ^5.0.0-beta.0 - redux-thunk: ^3.0.0-beta.0 - reselect: ^5.0.0-beta.0 + redux: ^5.0.0-rc.0 + redux-thunk: ^3.0.0-rc.0 + reselect: ^5.0.0-beta.1 rimraf: ^3.0.2 size-limit: ^4.11.0 tslib: ^1.10.0 @@ -7034,7 +7034,7 @@ __metadata: yargs: ^15.3.1 peerDependencies: react: ^16.9.0 || ^17.0.0 || ^18 - react-redux: ^7.2.1 || ^8.0.2 || ^9.0.0-beta.0 + react-redux: ^7.2.1 || ^8.1.3 || ^9.0.0-rc.0 peerDependenciesMeta: react: optional: true @@ -17142,10 +17142,10 @@ fsevents@^1.2.7: languageName: node linkType: hard -"immer@npm:^10.0.2": - version: 10.0.2 - resolution: "immer@npm:10.0.2" - checksum: 525a3b14210d02ae420c3b9f6ca14f7e9bcf625611d1356e773e7739f14c7c8de50dac442e6c7de3a6e24a782f7b792b6b8666bc0b3f00269d21a95f8f68ca84 +"immer@npm:^10.0.3": + version: 10.0.3 + resolution: "immer@npm:10.0.3" + checksum: 76acabe6f40e752028313762ba477a5d901e57b669f3b8fb406b87b9bb9b14e663a6fbbf5a6d1ab323737dd38f4b2494a4e28002045b88948da8dbf482309f28 languageName: node linkType: hard @@ -24946,12 +24946,12 @@ fsevents@^1.2.7: languageName: node linkType: hard -"redux-thunk@npm:^3.0.0-beta.0": - version: 3.0.0-beta.0 - resolution: "redux-thunk@npm:3.0.0-beta.0" +"redux-thunk@npm:^3.0.0-rc.0": + version: 3.0.0-rc.0 + resolution: "redux-thunk@npm:3.0.0-rc.0" peerDependencies: - redux: ^4 || ^5.0.0-beta.0 - checksum: 1609e18a9fb56ab7403d760999996b50e136fcf7411ec9d809e9a4afa4187bf0ab545652c05ffbfca2e0397e59e6baf2ae0d35631a30bf8ba20af1205e98e0fe + redux: ^5.0.0-rc.0 + checksum: d56a4bb700c742294496699366090bad22d13112cacc16b4858321cb6d88e05dcbab3c0f0d3b34848bbfedf4a04cdcc1b999e6cfdf7a9af713018fe35a35016e languageName: node linkType: hard @@ -24973,10 +24973,10 @@ fsevents@^1.2.7: languageName: node linkType: hard -"redux@npm:^5.0.0-beta.0": - version: 5.0.0-beta.0 - resolution: "redux@npm:5.0.0-beta.0" - checksum: 11df373e219f2f515ee1bda1a19a1ba5de02d8d5c874800ec353179dcd106eddd54432946fd0ab37c47f99f8fe53f820a6404c14da7f039a46022187e9469d2d +"redux@npm:^5.0.0-rc.0": + version: 5.0.0-rc.0 + resolution: "redux@npm:5.0.0-rc.0" + checksum: b8146301a27b54c2cf49bb5ef2650ddff5218533758c9a8e408141465922aab927be84421b64f8ea7e0ce69faa29b536cd6a1262c399fb86bbe991eed6a4ca88 languageName: node linkType: hard @@ -25432,10 +25432,10 @@ fsevents@^1.2.7: languageName: node linkType: hard -"reselect@npm:^5.0.0-beta.0": - version: 5.0.0-beta.0 - resolution: "reselect@npm:5.0.0-beta.0" - checksum: 462363aa730af93e396ff0d885f88fb8c43572b07f51c2a890d37f27edc3afecd300085916533e336142b3883f8532f35b5b1a2aaa1a70e9909aea48e5d3b98f +"reselect@npm:^5.0.0-beta.1": + version: 5.0.0-beta.1 + resolution: "reselect@npm:5.0.0-beta.1" + checksum: dd707e2285c6c4d27c245634e87cf12ef5043cf01c48478f80b3e178efa8f4cd8576d56ac2691180a63704532fb3f12861df81b66ddd7819decbf64d6908779e languageName: node linkType: hard From c57dd28db294edade3957d62f382c5c8c9e7c6c8 Mon Sep 17 00:00:00 2001 From: Mark Erikson Date: Wed, 15 Nov 2023 23:41:39 -0500 Subject: [PATCH 357/412] Bump deps in example apps --- examples/publish-ci/cra4/package.json | 4 +- examples/publish-ci/cra4/yarn.lock | 35 ++++++++-------- examples/publish-ci/cra5/package.json | 4 +- examples/publish-ci/cra5/yarn.lock | 35 ++++++++-------- examples/publish-ci/next/package.json | 4 +- examples/publish-ci/next/yarn.lock | 35 ++++++++-------- examples/publish-ci/node-esm/package.json | 4 +- examples/publish-ci/node-esm/yarn.lock | 42 ++++++++----------- .../publish-ci/node-standard/package.json | 4 +- examples/publish-ci/node-standard/yarn.lock | 42 ++++++++----------- examples/publish-ci/vite/package.json | 4 +- examples/publish-ci/vite/yarn.lock | 35 ++++++++-------- 12 files changed, 114 insertions(+), 134 deletions(-) diff --git a/examples/publish-ci/cra4/package.json b/examples/publish-ci/cra4/package.json index 325ca5067a..a24c10bc9a 100644 --- a/examples/publish-ci/cra4/package.json +++ b/examples/publish-ci/cra4/package.json @@ -3,11 +3,11 @@ "version": "0.1.0", "private": true, "dependencies": { - "@reduxjs/toolkit": "^2.0.0-beta.2", + "@reduxjs/toolkit": "^2.0.0-beta.4", "msw": "^1.3.2", "react": "^18.2.0", "react-dom": "^18.2.0", - "react-redux": "^9.0.0-beta.0", + "react-redux": "^9.0.0-rc.0", "react-scripts": "^4", "web-vitals": "^2.1.4" }, diff --git a/examples/publish-ci/cra4/yarn.lock b/examples/publish-ci/cra4/yarn.lock index 1e7c634ee6..16e2bfef14 100644 --- a/examples/publish-ci/cra4/yarn.lock +++ b/examples/publish-ci/cra4/yarn.lock @@ -2101,23 +2101,23 @@ __metadata: languageName: node linkType: hard -"@reduxjs/toolkit@npm:^2.0.0-beta.2": - version: 2.0.0-beta.2 - resolution: "@reduxjs/toolkit@npm:2.0.0-beta.2" +"@reduxjs/toolkit@npm:^2.0.0-beta.4": + version: 2.0.0-beta.4 + resolution: "@reduxjs/toolkit@npm:2.0.0-beta.4" dependencies: immer: ^10.0.2 redux: ^5.0.0-beta.0 redux-thunk: ^3.0.0-beta.0 - reselect: ^5.0.0-alpha.2 + reselect: ^5.0.0-beta.0 peerDependencies: react: ^16.9.0 || ^17.0.0 || ^18 - react-redux: ^7.2.1 || ^8.0.2 || ^9.0.0-alpha.1 + react-redux: ^7.2.1 || ^8.0.2 || ^9.0.0-beta.0 peerDependenciesMeta: react: optional: true react-redux: optional: true - checksum: 340b515190ba23785464aab85141e49e57a924cde8a335a8df789f1b212cb59fb35e7d8e241bbac9f6e27d17c5236d440aca6f7a7bb2fa36228e0c111c7ec768 + checksum: f7fe690b26840485a0dbc4a367424fc6c96604d8f6cab17ccb216ce1320d9a5c2f81c13d4e93d14095de6b0196ac742bff43b4834494be258ee42559e9dd429c languageName: node linkType: hard @@ -13238,12 +13238,11 @@ __metadata: languageName: node linkType: hard -"react-redux@npm:^9.0.0-beta.0": - version: 9.0.0-beta.0 - resolution: "react-redux@npm:9.0.0-beta.0" +"react-redux@npm:^9.0.0-rc.0": + version: 9.0.0-rc.0 + resolution: "react-redux@npm:9.0.0-rc.0" dependencies: "@types/use-sync-external-store": ^0.0.3 - react-is: ^18.0.0 use-sync-external-store: ^1.0.0 peerDependencies: "@types/react": ^18.0 @@ -13251,7 +13250,7 @@ __metadata: react: ^18.0 react-dom: ^18.0 react-native: ">=0.71" - redux: ^5.0.0-beta.0 + redux: ^5.0.0-rc.0 peerDependenciesMeta: "@types/react": optional: true @@ -13263,7 +13262,7 @@ __metadata: optional: true redux: optional: true - checksum: 4fc40fc4b2c03905f5d523b854516f4323d307c0ca33f2a14701f33315b53768d9e19f59511ebcad5448ccbd7c69a362edd0b68f0932669d4f9b2f517b7257b4 + checksum: 7acc5760b7a4f6e43fb08d8e2ba4bf67638a604c15974e58c1597c37b0b55ee96db89c54bfc753c915a48081684f2051606fe430556cdf83204cceed3aaa64a3 languageName: node linkType: hard @@ -13652,10 +13651,10 @@ __metadata: languageName: node linkType: hard -"reselect@npm:^5.0.0-alpha.2": - version: 5.0.0-alpha.2 - resolution: "reselect@npm:5.0.0-alpha.2" - checksum: c47b66999800e1297721cbc4b2464b520fade9823c598d578759c9fba3eb6be03b184e13c20f30820cc18fe2688fc9fb4475f83e59d8f2347aa0d591e465637d +"reselect@npm:^5.0.0-beta.0": + version: 5.0.0-beta.1 + resolution: "reselect@npm:5.0.0-beta.1" + checksum: dd707e2285c6c4d27c245634e87cf12ef5043cf01c48478f80b3e178efa8f4cd8576d56ac2691180a63704532fb3f12861df81b66ddd7819decbf64d6908779e languageName: node linkType: hard @@ -13951,7 +13950,7 @@ __metadata: resolution: "rtk-esm-cra@workspace:." dependencies: "@playwright/test": ^1.31.1 - "@reduxjs/toolkit": ^2.0.0-beta.2 + "@reduxjs/toolkit": ^2.0.0-beta.4 "@testing-library/jest-dom": ^5.16.5 "@testing-library/react": ^13.4.0 "@testing-library/user-event": ^14.4.3 @@ -13964,7 +13963,7 @@ __metadata: prettier: ^2.8.4 react: ^18.2.0 react-dom: ^18.2.0 - react-redux: ^9.0.0-beta.0 + react-redux: ^9.0.0-rc.0 react-scripts: ^4 serve: ^14.2.0 typescript: ^4.9.4 diff --git a/examples/publish-ci/cra5/package.json b/examples/publish-ci/cra5/package.json index 43e815862f..748dad2b5d 100644 --- a/examples/publish-ci/cra5/package.json +++ b/examples/publish-ci/cra5/package.json @@ -3,11 +3,11 @@ "version": "0.1.0", "private": true, "dependencies": { - "@reduxjs/toolkit": "^2.0.0-beta.2", + "@reduxjs/toolkit": "^2.0.0-beta.4", "msw": "^1.3.2", "react": "^18.2.0", "react-dom": "^18.2.0", - "react-redux": "^9.0.0-beta.0", + "react-redux": "^9.0.0-rc.0", "react-scripts": "5", "web-vitals": "^2.1.4" }, diff --git a/examples/publish-ci/cra5/yarn.lock b/examples/publish-ci/cra5/yarn.lock index 393857826a..9797ffbb2a 100644 --- a/examples/publish-ci/cra5/yarn.lock +++ b/examples/publish-ci/cra5/yarn.lock @@ -2253,23 +2253,23 @@ __metadata: languageName: node linkType: hard -"@reduxjs/toolkit@npm:^2.0.0-beta.2": - version: 2.0.0-beta.2 - resolution: "@reduxjs/toolkit@npm:2.0.0-beta.2" +"@reduxjs/toolkit@npm:^2.0.0-beta.4": + version: 2.0.0-beta.4 + resolution: "@reduxjs/toolkit@npm:2.0.0-beta.4" dependencies: immer: ^10.0.2 redux: ^5.0.0-beta.0 redux-thunk: ^3.0.0-beta.0 - reselect: ^5.0.0-alpha.2 + reselect: ^5.0.0-beta.0 peerDependencies: react: ^16.9.0 || ^17.0.0 || ^18 - react-redux: ^7.2.1 || ^8.0.2 || ^9.0.0-alpha.1 + react-redux: ^7.2.1 || ^8.0.2 || ^9.0.0-beta.0 peerDependenciesMeta: react: optional: true react-redux: optional: true - checksum: 340b515190ba23785464aab85141e49e57a924cde8a335a8df789f1b212cb59fb35e7d8e241bbac9f6e27d17c5236d440aca6f7a7bb2fa36228e0c111c7ec768 + checksum: f7fe690b26840485a0dbc4a367424fc6c96604d8f6cab17ccb216ce1320d9a5c2f81c13d4e93d14095de6b0196ac742bff43b4834494be258ee42559e9dd429c languageName: node linkType: hard @@ -11222,12 +11222,11 @@ __metadata: languageName: node linkType: hard -"react-redux@npm:^9.0.0-beta.0": - version: 9.0.0-beta.0 - resolution: "react-redux@npm:9.0.0-beta.0" +"react-redux@npm:^9.0.0-rc.0": + version: 9.0.0-rc.0 + resolution: "react-redux@npm:9.0.0-rc.0" dependencies: "@types/use-sync-external-store": ^0.0.3 - react-is: ^18.0.0 use-sync-external-store: ^1.0.0 peerDependencies: "@types/react": ^18.0 @@ -11235,7 +11234,7 @@ __metadata: react: ^18.0 react-dom: ^18.0 react-native: ">=0.71" - redux: ^5.0.0-beta.0 + redux: ^5.0.0-rc.0 peerDependenciesMeta: "@types/react": optional: true @@ -11247,7 +11246,7 @@ __metadata: optional: true redux: optional: true - checksum: 4fc40fc4b2c03905f5d523b854516f4323d307c0ca33f2a14701f33315b53768d9e19f59511ebcad5448ccbd7c69a362edd0b68f0932669d4f9b2f517b7257b4 + checksum: 7acc5760b7a4f6e43fb08d8e2ba4bf67638a604c15974e58c1597c37b0b55ee96db89c54bfc753c915a48081684f2051606fe430556cdf83204cceed3aaa64a3 languageName: node linkType: hard @@ -11555,10 +11554,10 @@ __metadata: languageName: node linkType: hard -"reselect@npm:^5.0.0-alpha.2": - version: 5.0.0-alpha.2 - resolution: "reselect@npm:5.0.0-alpha.2" - checksum: c47b66999800e1297721cbc4b2464b520fade9823c598d578759c9fba3eb6be03b184e13c20f30820cc18fe2688fc9fb4475f83e59d8f2347aa0d591e465637d +"reselect@npm:^5.0.0-beta.0": + version: 5.0.0-beta.1 + resolution: "reselect@npm:5.0.0-beta.1" + checksum: dd707e2285c6c4d27c245634e87cf12ef5043cf01c48478f80b3e178efa8f4cd8576d56ac2691180a63704532fb3f12861df81b66ddd7819decbf64d6908779e languageName: node linkType: hard @@ -11740,7 +11739,7 @@ __metadata: resolution: "rtk-esm-cra@workspace:." dependencies: "@playwright/test": ^1.31.1 - "@reduxjs/toolkit": ^2.0.0-beta.2 + "@reduxjs/toolkit": ^2.0.0-beta.4 "@testing-library/jest-dom": ^5.16.5 "@testing-library/react": ^13.4.0 "@testing-library/user-event": ^14.4.3 @@ -11753,7 +11752,7 @@ __metadata: prettier: ^2.8.4 react: ^18.2.0 react-dom: ^18.2.0 - react-redux: ^9.0.0-beta.0 + react-redux: ^9.0.0-rc.0 react-scripts: 5 serve: ^14.2.0 typescript: ^4.9.4 diff --git a/examples/publish-ci/next/package.json b/examples/publish-ci/next/package.json index fcd0fadfc8..d249d428c5 100644 --- a/examples/publish-ci/next/package.json +++ b/examples/publish-ci/next/package.json @@ -10,12 +10,12 @@ "format": "prettier --write \"./src/**/*.{ts,tsx}\" \"**/*.md\"" }, "dependencies": { - "@reduxjs/toolkit": "^2.0.0-beta.2", + "@reduxjs/toolkit": "^2.0.0-beta.4", "msw": "^1.3.2", "next": "^13.2", "react": "^18.2.0", "react-dom": "^18.2.0", - "react-redux": "^9.0.0-beta.0" + "react-redux": "^9.0.0-rc.0" }, "devDependencies": { "@playwright/test": "^1.31.1", diff --git a/examples/publish-ci/next/yarn.lock b/examples/publish-ci/next/yarn.lock index b961839f55..5b2a8caa19 100644 --- a/examples/publish-ci/next/yarn.lock +++ b/examples/publish-ci/next/yarn.lock @@ -254,23 +254,23 @@ __metadata: languageName: node linkType: hard -"@reduxjs/toolkit@npm:^2.0.0-beta.2": - version: 2.0.0-beta.2 - resolution: "@reduxjs/toolkit@npm:2.0.0-beta.2" +"@reduxjs/toolkit@npm:^2.0.0-beta.4": + version: 2.0.0-beta.4 + resolution: "@reduxjs/toolkit@npm:2.0.0-beta.4" dependencies: immer: ^10.0.2 redux: ^5.0.0-beta.0 redux-thunk: ^3.0.0-beta.0 - reselect: ^5.0.0-alpha.2 + reselect: ^5.0.0-beta.0 peerDependencies: react: ^16.9.0 || ^17.0.0 || ^18 - react-redux: ^7.2.1 || ^8.0.2 || ^9.0.0-alpha.1 + react-redux: ^7.2.1 || ^8.0.2 || ^9.0.0-beta.0 peerDependenciesMeta: react: optional: true react-redux: optional: true - checksum: 340b515190ba23785464aab85141e49e57a924cde8a335a8df789f1b212cb59fb35e7d8e241bbac9f6e27d17c5236d440aca6f7a7bb2fa36228e0c111c7ec768 + checksum: f7fe690b26840485a0dbc4a367424fc6c96604d8f6cab17ccb216ce1320d9a5c2f81c13d4e93d14095de6b0196ac742bff43b4834494be258ee42559e9dd429c languageName: node linkType: hard @@ -2972,12 +2972,11 @@ __metadata: languageName: node linkType: hard -"react-redux@npm:^9.0.0-beta.0": - version: 9.0.0-beta.0 - resolution: "react-redux@npm:9.0.0-beta.0" +"react-redux@npm:^9.0.0-rc.0": + version: 9.0.0-rc.0 + resolution: "react-redux@npm:9.0.0-rc.0" dependencies: "@types/use-sync-external-store": ^0.0.3 - react-is: ^18.0.0 use-sync-external-store: ^1.0.0 peerDependencies: "@types/react": ^18.0 @@ -2985,7 +2984,7 @@ __metadata: react: ^18.0 react-dom: ^18.0 react-native: ">=0.71" - redux: ^5.0.0-beta.0 + redux: ^5.0.0-rc.0 peerDependenciesMeta: "@types/react": optional: true @@ -2997,7 +2996,7 @@ __metadata: optional: true redux: optional: true - checksum: 4fc40fc4b2c03905f5d523b854516f4323d307c0ca33f2a14701f33315b53768d9e19f59511ebcad5448ccbd7c69a362edd0b68f0932669d4f9b2f517b7257b4 + checksum: 7acc5760b7a4f6e43fb08d8e2ba4bf67638a604c15974e58c1597c37b0b55ee96db89c54bfc753c915a48081684f2051606fe430556cdf83204cceed3aaa64a3 languageName: node linkType: hard @@ -3107,10 +3106,10 @@ __metadata: languageName: node linkType: hard -"reselect@npm:^5.0.0-alpha.2": - version: 5.0.0-alpha.2 - resolution: "reselect@npm:5.0.0-alpha.2" - checksum: c47b66999800e1297721cbc4b2464b520fade9823c598d578759c9fba3eb6be03b184e13c20f30820cc18fe2688fc9fb4475f83e59d8f2347aa0d591e465637d +"reselect@npm:^5.0.0-beta.0": + version: 5.0.0-beta.1 + resolution: "reselect@npm:5.0.0-beta.1" + checksum: dd707e2285c6c4d27c245634e87cf12ef5043cf01c48478f80b3e178efa8f4cd8576d56ac2691180a63704532fb3f12861df81b66ddd7819decbf64d6908779e languageName: node linkType: hard @@ -3147,7 +3146,7 @@ __metadata: resolution: "root-workspace-0b6124@workspace:." dependencies: "@playwright/test": ^1.31.1 - "@reduxjs/toolkit": ^2.0.0-beta.2 + "@reduxjs/toolkit": ^2.0.0-beta.4 "@testing-library/jest-dom": ^5.16.5 "@testing-library/react": ^13.4.0 "@testing-library/user-event": ^14.4.3 @@ -3161,7 +3160,7 @@ __metadata: prettier: ^2.8.4 react: ^18.2.0 react-dom: ^18.2.0 - react-redux: ^9.0.0-beta.0 + react-redux: ^9.0.0-rc.0 serve: ^14.2.0 typescript: ^4.9.4 languageName: unknown diff --git a/examples/publish-ci/node-esm/package.json b/examples/publish-ci/node-esm/package.json index 1ea8e3b503..e8b8f2b24d 100644 --- a/examples/publish-ci/node-esm/package.json +++ b/examples/publish-ci/node-esm/package.json @@ -8,10 +8,10 @@ "test": "node test-cjs.cjs && node test-esm.mjs" }, "dependencies": { - "@reduxjs/toolkit": "^2.0.0-beta.2", + "@reduxjs/toolkit": "^2.0.0-beta.4", "react": "^18.2.0", "react-dom": "^18.2.0", - "react-redux": "^9.0.0-beta.0" + "react-redux": "^9.0.0-rc.0" }, "devDependencies": { "resolve-esm": "^1.4.0" diff --git a/examples/publish-ci/node-esm/yarn.lock b/examples/publish-ci/node-esm/yarn.lock index 70e73a7bac..889c607242 100644 --- a/examples/publish-ci/node-esm/yarn.lock +++ b/examples/publish-ci/node-esm/yarn.lock @@ -5,23 +5,23 @@ __metadata: version: 6 cacheKey: 8 -"@reduxjs/toolkit@npm:^2.0.0-beta.2": - version: 2.0.0-beta.2 - resolution: "@reduxjs/toolkit@npm:2.0.0-beta.2" +"@reduxjs/toolkit@npm:^2.0.0-beta.4": + version: 2.0.0-beta.4 + resolution: "@reduxjs/toolkit@npm:2.0.0-beta.4" dependencies: immer: ^10.0.2 redux: ^5.0.0-beta.0 redux-thunk: ^3.0.0-beta.0 - reselect: ^5.0.0-alpha.2 + reselect: ^5.0.0-beta.0 peerDependencies: react: ^16.9.0 || ^17.0.0 || ^18 - react-redux: ^7.2.1 || ^8.0.2 || ^9.0.0-alpha.1 + react-redux: ^7.2.1 || ^8.0.2 || ^9.0.0-beta.0 peerDependenciesMeta: react: optional: true react-redux: optional: true - checksum: 340b515190ba23785464aab85141e49e57a924cde8a335a8df789f1b212cb59fb35e7d8e241bbac9f6e27d17c5236d440aca6f7a7bb2fa36228e0c111c7ec768 + checksum: f7fe690b26840485a0dbc4a367424fc6c96604d8f6cab17ccb216ce1320d9a5c2f81c13d4e93d14095de6b0196ac742bff43b4834494be258ee42559e9dd429c languageName: node linkType: hard @@ -43,10 +43,10 @@ __metadata: version: 0.0.0-use.local resolution: "dual-module-test@workspace:." dependencies: - "@reduxjs/toolkit": ^2.0.0-beta.2 + "@reduxjs/toolkit": ^2.0.0-beta.4 react: ^18.2.0 react-dom: ^18.2.0 - react-redux: ^9.0.0-beta.0 + react-redux: ^9.0.0-rc.0 resolve-esm: ^1.4.0 languageName: unknown linkType: soft @@ -88,19 +88,11 @@ __metadata: languageName: node linkType: hard -"react-is@npm:^18.0.0": - version: 18.2.0 - resolution: "react-is@npm:18.2.0" - checksum: e72d0ba81b5922759e4aff17e0252bd29988f9642ed817f56b25a3e217e13eea8a7f2322af99a06edb779da12d5d636e9fda473d620df9a3da0df2a74141d53e - languageName: node - linkType: hard - -"react-redux@npm:^9.0.0-beta.0": - version: 9.0.0-beta.0 - resolution: "react-redux@npm:9.0.0-beta.0" +"react-redux@npm:^9.0.0-rc.0": + version: 9.0.0-rc.0 + resolution: "react-redux@npm:9.0.0-rc.0" dependencies: "@types/use-sync-external-store": ^0.0.3 - react-is: ^18.0.0 use-sync-external-store: ^1.0.0 peerDependencies: "@types/react": ^18.0 @@ -108,7 +100,7 @@ __metadata: react: ^18.0 react-dom: ^18.0 react-native: ">=0.71" - redux: ^5.0.0-beta.0 + redux: ^5.0.0-rc.0 peerDependenciesMeta: "@types/react": optional: true @@ -120,7 +112,7 @@ __metadata: optional: true redux: optional: true - checksum: 4fc40fc4b2c03905f5d523b854516f4323d307c0ca33f2a14701f33315b53768d9e19f59511ebcad5448ccbd7c69a362edd0b68f0932669d4f9b2f517b7257b4 + checksum: 7acc5760b7a4f6e43fb08d8e2ba4bf67638a604c15974e58c1597c37b0b55ee96db89c54bfc753c915a48081684f2051606fe430556cdf83204cceed3aaa64a3 languageName: node linkType: hard @@ -149,10 +141,10 @@ __metadata: languageName: node linkType: hard -"reselect@npm:^5.0.0-alpha.2": - version: 5.0.0-alpha.2 - resolution: "reselect@npm:5.0.0-alpha.2" - checksum: c47b66999800e1297721cbc4b2464b520fade9823c598d578759c9fba3eb6be03b184e13c20f30820cc18fe2688fc9fb4475f83e59d8f2347aa0d591e465637d +"reselect@npm:^5.0.0-beta.0": + version: 5.0.0-beta.1 + resolution: "reselect@npm:5.0.0-beta.1" + checksum: dd707e2285c6c4d27c245634e87cf12ef5043cf01c48478f80b3e178efa8f4cd8576d56ac2691180a63704532fb3f12861df81b66ddd7819decbf64d6908779e languageName: node linkType: hard diff --git a/examples/publish-ci/node-standard/package.json b/examples/publish-ci/node-standard/package.json index 780f72bcd0..56d0de04ff 100644 --- a/examples/publish-ci/node-standard/package.json +++ b/examples/publish-ci/node-standard/package.json @@ -7,10 +7,10 @@ "test": "node test-cjs.js && node test-esm.mjs" }, "dependencies": { - "@reduxjs/toolkit": "^2.0.0-beta.2", + "@reduxjs/toolkit": "^2.0.0-beta.4", "react": "^18.2.0", "react-dom": "^18.2.0", - "react-redux": "^9.0.0-beta.0" + "react-redux": "^9.0.0-rc.0" }, "devDependencies": { "resolve-esm": "^1.4.0" diff --git a/examples/publish-ci/node-standard/yarn.lock b/examples/publish-ci/node-standard/yarn.lock index 70e73a7bac..889c607242 100644 --- a/examples/publish-ci/node-standard/yarn.lock +++ b/examples/publish-ci/node-standard/yarn.lock @@ -5,23 +5,23 @@ __metadata: version: 6 cacheKey: 8 -"@reduxjs/toolkit@npm:^2.0.0-beta.2": - version: 2.0.0-beta.2 - resolution: "@reduxjs/toolkit@npm:2.0.0-beta.2" +"@reduxjs/toolkit@npm:^2.0.0-beta.4": + version: 2.0.0-beta.4 + resolution: "@reduxjs/toolkit@npm:2.0.0-beta.4" dependencies: immer: ^10.0.2 redux: ^5.0.0-beta.0 redux-thunk: ^3.0.0-beta.0 - reselect: ^5.0.0-alpha.2 + reselect: ^5.0.0-beta.0 peerDependencies: react: ^16.9.0 || ^17.0.0 || ^18 - react-redux: ^7.2.1 || ^8.0.2 || ^9.0.0-alpha.1 + react-redux: ^7.2.1 || ^8.0.2 || ^9.0.0-beta.0 peerDependenciesMeta: react: optional: true react-redux: optional: true - checksum: 340b515190ba23785464aab85141e49e57a924cde8a335a8df789f1b212cb59fb35e7d8e241bbac9f6e27d17c5236d440aca6f7a7bb2fa36228e0c111c7ec768 + checksum: f7fe690b26840485a0dbc4a367424fc6c96604d8f6cab17ccb216ce1320d9a5c2f81c13d4e93d14095de6b0196ac742bff43b4834494be258ee42559e9dd429c languageName: node linkType: hard @@ -43,10 +43,10 @@ __metadata: version: 0.0.0-use.local resolution: "dual-module-test@workspace:." dependencies: - "@reduxjs/toolkit": ^2.0.0-beta.2 + "@reduxjs/toolkit": ^2.0.0-beta.4 react: ^18.2.0 react-dom: ^18.2.0 - react-redux: ^9.0.0-beta.0 + react-redux: ^9.0.0-rc.0 resolve-esm: ^1.4.0 languageName: unknown linkType: soft @@ -88,19 +88,11 @@ __metadata: languageName: node linkType: hard -"react-is@npm:^18.0.0": - version: 18.2.0 - resolution: "react-is@npm:18.2.0" - checksum: e72d0ba81b5922759e4aff17e0252bd29988f9642ed817f56b25a3e217e13eea8a7f2322af99a06edb779da12d5d636e9fda473d620df9a3da0df2a74141d53e - languageName: node - linkType: hard - -"react-redux@npm:^9.0.0-beta.0": - version: 9.0.0-beta.0 - resolution: "react-redux@npm:9.0.0-beta.0" +"react-redux@npm:^9.0.0-rc.0": + version: 9.0.0-rc.0 + resolution: "react-redux@npm:9.0.0-rc.0" dependencies: "@types/use-sync-external-store": ^0.0.3 - react-is: ^18.0.0 use-sync-external-store: ^1.0.0 peerDependencies: "@types/react": ^18.0 @@ -108,7 +100,7 @@ __metadata: react: ^18.0 react-dom: ^18.0 react-native: ">=0.71" - redux: ^5.0.0-beta.0 + redux: ^5.0.0-rc.0 peerDependenciesMeta: "@types/react": optional: true @@ -120,7 +112,7 @@ __metadata: optional: true redux: optional: true - checksum: 4fc40fc4b2c03905f5d523b854516f4323d307c0ca33f2a14701f33315b53768d9e19f59511ebcad5448ccbd7c69a362edd0b68f0932669d4f9b2f517b7257b4 + checksum: 7acc5760b7a4f6e43fb08d8e2ba4bf67638a604c15974e58c1597c37b0b55ee96db89c54bfc753c915a48081684f2051606fe430556cdf83204cceed3aaa64a3 languageName: node linkType: hard @@ -149,10 +141,10 @@ __metadata: languageName: node linkType: hard -"reselect@npm:^5.0.0-alpha.2": - version: 5.0.0-alpha.2 - resolution: "reselect@npm:5.0.0-alpha.2" - checksum: c47b66999800e1297721cbc4b2464b520fade9823c598d578759c9fba3eb6be03b184e13c20f30820cc18fe2688fc9fb4475f83e59d8f2347aa0d591e465637d +"reselect@npm:^5.0.0-beta.0": + version: 5.0.0-beta.1 + resolution: "reselect@npm:5.0.0-beta.1" + checksum: dd707e2285c6c4d27c245634e87cf12ef5043cf01c48478f80b3e178efa8f4cd8576d56ac2691180a63704532fb3f12861df81b66ddd7819decbf64d6908779e languageName: node linkType: hard diff --git a/examples/publish-ci/vite/package.json b/examples/publish-ci/vite/package.json index bdff3e7116..0098206636 100644 --- a/examples/publish-ci/vite/package.json +++ b/examples/publish-ci/vite/package.json @@ -11,11 +11,11 @@ "format": "prettier --write \"./src/**/*.{ts,tsx}\" \"**/*.md\"" }, "dependencies": { - "@reduxjs/toolkit": "^2.0.0-beta.2", + "@reduxjs/toolkit": "^2.0.0-beta.4", "msw": "^1.3.2", "react": "^18.2.0", "react-dom": "^18.2.0", - "react-redux": "^9.0.0-beta.0" + "react-redux": "^9.0.0-rc.0" }, "devDependencies": { "@playwright/test": "^1.31.1", diff --git a/examples/publish-ci/vite/yarn.lock b/examples/publish-ci/vite/yarn.lock index 7bea5d415f..8ba7bd8e51 100644 --- a/examples/publish-ci/vite/yarn.lock +++ b/examples/publish-ci/vite/yarn.lock @@ -601,23 +601,23 @@ __metadata: languageName: node linkType: hard -"@reduxjs/toolkit@npm:^2.0.0-beta.2": - version: 2.0.0-beta.2 - resolution: "@reduxjs/toolkit@npm:2.0.0-beta.2" +"@reduxjs/toolkit@npm:^2.0.0-beta.4": + version: 2.0.0-beta.4 + resolution: "@reduxjs/toolkit@npm:2.0.0-beta.4" dependencies: immer: ^10.0.2 redux: ^5.0.0-beta.0 redux-thunk: ^3.0.0-beta.0 - reselect: ^5.0.0-alpha.2 + reselect: ^5.0.0-beta.0 peerDependencies: react: ^16.9.0 || ^17.0.0 || ^18 - react-redux: ^7.2.1 || ^8.0.2 || ^9.0.0-alpha.1 + react-redux: ^7.2.1 || ^8.0.2 || ^9.0.0-beta.0 peerDependenciesMeta: react: optional: true react-redux: optional: true - checksum: 340b515190ba23785464aab85141e49e57a924cde8a335a8df789f1b212cb59fb35e7d8e241bbac9f6e27d17c5236d440aca6f7a7bb2fa36228e0c111c7ec768 + checksum: f7fe690b26840485a0dbc4a367424fc6c96604d8f6cab17ccb216ce1320d9a5c2f81c13d4e93d14095de6b0196ac742bff43b4834494be258ee42559e9dd429c languageName: node linkType: hard @@ -3425,12 +3425,11 @@ __metadata: languageName: node linkType: hard -"react-redux@npm:^9.0.0-beta.0": - version: 9.0.0-beta.0 - resolution: "react-redux@npm:9.0.0-beta.0" +"react-redux@npm:^9.0.0-rc.0": + version: 9.0.0-rc.0 + resolution: "react-redux@npm:9.0.0-rc.0" dependencies: "@types/use-sync-external-store": ^0.0.3 - react-is: ^18.0.0 use-sync-external-store: ^1.0.0 peerDependencies: "@types/react": ^18.0 @@ -3438,7 +3437,7 @@ __metadata: react: ^18.0 react-dom: ^18.0 react-native: ">=0.71" - redux: ^5.0.0-beta.0 + redux: ^5.0.0-rc.0 peerDependenciesMeta: "@types/react": optional: true @@ -3450,7 +3449,7 @@ __metadata: optional: true redux: optional: true - checksum: 4fc40fc4b2c03905f5d523b854516f4323d307c0ca33f2a14701f33315b53768d9e19f59511ebcad5448ccbd7c69a362edd0b68f0932669d4f9b2f517b7257b4 + checksum: 7acc5760b7a4f6e43fb08d8e2ba4bf67638a604c15974e58c1597c37b0b55ee96db89c54bfc753c915a48081684f2051606fe430556cdf83204cceed3aaa64a3 languageName: node linkType: hard @@ -3567,10 +3566,10 @@ __metadata: languageName: node linkType: hard -"reselect@npm:^5.0.0-alpha.2": - version: 5.0.0-alpha.2 - resolution: "reselect@npm:5.0.0-alpha.2" - checksum: c47b66999800e1297721cbc4b2464b520fade9823c598d578759c9fba3eb6be03b184e13c20f30820cc18fe2688fc9fb4475f83e59d8f2347aa0d591e465637d +"reselect@npm:^5.0.0-beta.0": + version: 5.0.0-beta.1 + resolution: "reselect@npm:5.0.0-beta.1" + checksum: dd707e2285c6c4d27c245634e87cf12ef5043cf01c48478f80b3e178efa8f4cd8576d56ac2691180a63704532fb3f12861df81b66ddd7819decbf64d6908779e languageName: node linkType: hard @@ -3647,7 +3646,7 @@ __metadata: resolution: "rtk-esm-vite-normal@workspace:." dependencies: "@playwright/test": ^1.31.1 - "@reduxjs/toolkit": ^2.0.0-beta.2 + "@reduxjs/toolkit": ^2.0.0-beta.4 "@testing-library/jest-dom": ^5.16.5 "@testing-library/react": ^13.4.0 "@testing-library/user-event": ^14.4.3 @@ -3661,7 +3660,7 @@ __metadata: prettier: ^2.8.4 react: ^18.2.0 react-dom: ^18.2.0 - react-redux: ^9.0.0-beta.0 + react-redux: ^9.0.0-rc.0 serve: ^14.2.0 typescript: ^4.9.4 vite: ^4.2.1 From d3f71726f464ae406a49c205d2c01034d98f4868 Mon Sep 17 00:00:00 2001 From: Mark Erikson Date: Thu, 16 Nov 2023 00:08:00 -0500 Subject: [PATCH 358/412] Release 2.0.0-rc.0 --- packages/toolkit/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/toolkit/package.json b/packages/toolkit/package.json index da4c9c5e2d..648c501e6f 100644 --- a/packages/toolkit/package.json +++ b/packages/toolkit/package.json @@ -1,6 +1,6 @@ { "name": "@reduxjs/toolkit", - "version": "2.0.0-beta.4", + "version": "2.0.0-rc.0", "description": "The official, opinionated, batteries-included toolset for efficient Redux development", "author": "Mark Erikson ", "license": "MIT", From 247f578ab29b7b64a693483fd65ee6f87632c40b Mon Sep 17 00:00:00 2001 From: Ben Durrant Date: Thu, 16 Nov 2023 16:54:52 +0000 Subject: [PATCH 359/412] Basic outline of migrations page --- docs/migrations/1.x-to-2.x.md | 85 +++++++++++++++++++++++++++++++++++ website/docusaurus.config.js | 2 +- website/sidebars.json | 5 +++ website/src/css/custom.css | 6 +++ 4 files changed, 97 insertions(+), 1 deletion(-) create mode 100644 docs/migrations/1.x-to-2.x.md diff --git a/docs/migrations/1.x-to-2.x.md b/docs/migrations/1.x-to-2.x.md new file mode 100644 index 0000000000..5554a434d8 --- /dev/null +++ b/docs/migrations/1.x-to-2.x.md @@ -0,0 +1,85 @@ +--- +id: 1.x-to-2.x +title: 1.x → 2.x +sidebar_label: 1.x → 2.x +hide_title: true +toc_max_heading_level: 4 +--- + +  + +

+ +# 1.x → 2.x + +## Introduction + +## Breaking Changes + +### Core, Toolkit and React Redux + +#### UMD build artifacts removed + +#### Modernised JS output targeting ES2020 + +#### Addition of `exports` field in `package.json` + +#### Error message extraction + +#### Action types must be strings + +
+ +#### Typescript rewrite + +#### `Middleware` type changed + +#### `PreloadedState` type removed in favour of `Reducer` generic + +#### `AnyAction` deprecated in favour of `UnknownAction` + +
+ +### Toolkit only + +#### Object syntax for `extraReducers` and `createReducer` removed + +##### Codemods + +#### `middleware` and `enhancers` must be a callback + +#### Standalone `getDefaultMiddleware` and `getType` removed + +#### RTK Query tag invalidation behaviour + +#### `reactHooksModule` custom hook configuration + +
+ +#### Non-default middleware/enhancers must use `Tuple` + +
+ +## Features + +### `combineSlices` API with slice reducer injection for code-splitting + +### `selectors` field in `createSlice` + +### Callback syntax for `createSlice.reducers` + +### `createDynamicMiddleware` + +### `configureStore` adds `autoBatchEnhancer` by default + +### New dev checks in `reselect` v5 + +### Immer 10.0 + +## Recommendations + +### Alternatives to `actionCreator.toString()` + +## Future plans - 3.0? + +
diff --git a/website/docusaurus.config.js b/website/docusaurus.config.js index cecc0e4893..c26183bb69 100644 --- a/website/docusaurus.config.js +++ b/website/docusaurus.config.js @@ -16,7 +16,7 @@ module.exports = { showLastUpdateTime: true, routeBasePath: '/', include: [ - '{api,assets,introduction,rtk-query,tutorials,usage}/**/*.{md,mdx}', + '{api,assets,introduction,migrations,rtk-query,tutorials,usage}/**/*.{md,mdx}', ], // no other way to exclude node_modules remarkPlugins: [ [ diff --git a/website/sidebars.json b/website/sidebars.json index 1d7cff4c8f..cc1dcafdf8 100644 --- a/website/sidebars.json +++ b/website/sidebars.json @@ -6,6 +6,11 @@ "collapsed": false, "items": ["introduction/getting-started"] }, + { + "type": "category", + "label": "Migrations", + "items": ["migrations/1.x-to-2.x"] + }, { "type": "category", "label": "Tutorials", diff --git a/website/src/css/custom.css b/website/src/css/custom.css index 907f847e99..4a61c52f6d 100644 --- a/website/src/css/custom.css +++ b/website/src/css/custom.css @@ -256,3 +256,9 @@ table.checkbox-table tbody td { .diagonal-cell--bottomLeft { grid-column-start: 1; } + +.migration-guide .typescript-only h4:after { + content: ' TYPESCRIPT'; + color: var(--ifm-color-info); + position: relative; +} From fdfe38f0d1f0a67d2e71040824e40ee6a7ba7598 Mon Sep 17 00:00:00 2001 From: Ben Durrant Date: Thu, 16 Nov 2023 21:45:17 +0000 Subject: [PATCH 360/412] add blurb for all breaking changes and features --- docs/api/createDynamicMiddleware.mdx | 2 +- docs/migrations/1.x-to-2.x.md | 570 ++++++++++++++++++++++++++- 2 files changed, 563 insertions(+), 9 deletions(-) diff --git a/docs/api/createDynamicMiddleware.mdx b/docs/api/createDynamicMiddleware.mdx index 6aea1fa3a6..2f2150f2b6 100644 --- a/docs/api/createDynamicMiddleware.mdx +++ b/docs/api/createDynamicMiddleware.mdx @@ -151,7 +151,7 @@ Accepts a set of middleware, and returns a [`useDispatch`](https://react-redux.j ```ts no-transpile const useListenerDispatch = createDispatchWithMiddlewareHook( - listenerMiddleware.instance + listenerInstance.middleware ) const Component = () => { diff --git a/docs/migrations/1.x-to-2.x.md b/docs/migrations/1.x-to-2.x.md index 5554a434d8..f0ba7aff67 100644 --- a/docs/migrations/1.x-to-2.x.md +++ b/docs/migrations/1.x-to-2.x.md @@ -14,72 +14,626 @@ toc_max_heading_level: 4 ## Introduction + + ## Breaking Changes ### Core, Toolkit and React Redux -#### UMD build artifacts removed +#### Action types _must_ be strings + +We've always specifically told our users that [actions and state _must_ be serializable](https://redux.js.org/style-guide/#do-not-put-non-serializable-values-in-state-or-actions), and that `action.type` _should_ be a string. This is both to ensure that actions are serializable, and to help provide a readable action history in the Redux DevTools. + +`store.dispatch(action)` now specifically enforces that `action.type` _must_ be a string and will throw an error if not, in the same way it throws an error if the action is not a plain object. + +In practice, this was already true 99.99% of the time and shouldn't have any effect on users (especially those using Redux Toolkit and `createSlice`), but there may be some legacy Redux codebases that opted to use Symbols as action types. + +#### Dropping UMD builds + +Redux has always shipped with UMD build artifacts. These are primarily meant for direct import as script tags, such as in a CodePen or a no-bundler build environment. + +For now, we're dropping those build artifacts from the published package, on the grounds that the use cases seem pretty rare today. -#### Modernised JS output targeting ES2020 +We do have a browser-ready ESM build artifact included at dist/redux.browser.mjs, which can be loaded via a script tag that points to that file on Unpkg. + +If you have strong use cases for us continuing to include UMD build artifacts, please let us know! + +#### Build Artifact Modernization + +We've updated the build output in several ways: + +- **Build output is no longer transpiled!** Instead we target modern JS syntax (ES2020) +- Moved all build artifacts to live under ./dist/, instead of separate top-level folders +- The lowest Typescript version we test against is now 4.7 #### Addition of `exports` field in `package.json` +We've migrated the package definitions to be a full `{type: "module"}` ESM package with an `exports` field (with CJS still included for compatibility purposes). + +We've done local testing of the package, but we ask the community to try out this in your own projects and report any breakages you find! + #### Error message extraction -#### Action types must be strings +Redux 4.1.0 optimized its bundle size by [extracting error message strings out of production builds](https://github.com/reduxjs/redux/releases/tag/v4.1.0), based on React's approach. We've applied the same technique to RTK. This saves about 1000 bytes from prod bundles (actual benefits will depend on which imports are being used). + +#### Internal listener implementation + +The Redux store has always used an array to track listener callbacks, and used `listeners.findIndex` to remove listeners on unsubscribe. As we found in React Redux, that can have perf issues when many listeners are unsubscribing at once. + +In React Redux, we fixed that with a more sophisticated linked list approach. Here, we've updated the listeners to be stored in a `Map` instead, which has better delete performance than an array. + +In practice this shouldn't have any real effect, because React Redux sets up a subscription in ``, and all nested components subscribe to that. But, nice to fix it here as well.
#### Typescript rewrite -#### `Middleware` type changed +In 2019, we began a community-powered conversion of the Redux codebase to TypeScript. The original effort was discussed in [#3500: Port to TypeScript](https://github.com/reduxjs/redux/issues/3500), and the work was integrated in PR [#3536: Convert to TypeScript](https://github.com/reduxjs/redux/issues/3536). + +However, the TS-converted code in master has sat around since then, unused and unpublished, due to concerns about possible compatibility issues with the existing ecosystem (as well as general inertia on our part). + +Redux core v5 is now built from that converted source code. In theory, this should be almost identical in both runtime behavior and types to the 4.x build, but it's very likely that some of the changes may cause types issues. + +Please report any unexpected compatibility issues on [Github](https://github.com/reduxjs/redux/issues)! + +#### `Middleware` type changed - Middleware `action` and `next` are typed as `unknown` + +Previously, the `next` parameter is typed as the `D` type parameter passed, and `action` is typed as the `Action` extracted from the dispatch type. Neither of these are a safe assumption: + +- `next` would be typed to have **all** of the dispatch extensions, including the ones earlier in the chain that would no longer apply. + - Technically it would be _mostly_ safe to type `next` as the default Dispatch implemented by the base redux store, however this would cause `next(action)` to error (as we cannot promise `action` is actually an `Action`) - and it wouldn't account for any following middlewares that return anything other than the action they're given when they see a specific action. +- `action` is not necessarily a known action, it can be literally anything - for example a thunk would be a function with no `.type` property (so `AnyAction` would be inaccurate) + +We've changed `next` to be `(action: unknown) => unknown` (which is accurate, we have no idea what `next` expects or will return), and changed the `action` parameter to be `unknown` (which as above, is accurate). + +This new type is incompatible with the v4 `Middleware` type, so if a package's middleware is saying it's incompatible, check which version of Redux it's getting its types from! #### `PreloadedState` type removed in favour of `Reducer` generic +We've made tweaks to the TS types to improve type safety and behavior. + +First, the `Reducer` type now has a `PreloadedState` possible generic: + +```ts +type Reducer = ( + state: S | PreloadedState | undefined, + action: A +) => S +``` + +Per the explanation in [#4491](https://github.com/reduxjs/redux/pull/4491): + +Why the need for this change? When the store is first created by `createStore`/`configureStore`, the initial state is set to whatever is passed as the `preloadedState` argument (or `undefined` if nothing is passed). That means that the first time that the reducer is called, it is called with the `preloadedState`. After the first call, the reducer is always passed the current state (which is `S`). + +For most normal reducers, `S | undefined` accurately describes what can be passed in for the `preloadedState`. However the `combineReducers` function allows for a preloaded state of `Partial | undefined`. + +The solution is to have a separate generic that represents what the reducer accepts for its preloaded state. That way `createStore` can then use that generic for its `preloadedState` argument. + +Previously, this was handled by a `$CombinedState` type, but that complicated things and led to some user-reported issues. This removes the need for `$CombinedState` altogether. + +This change does include some breaking changes, but overall should not have a huge impact on users upgrading in user-land: + +- The `Reducer`, `ReducersMapObject`, and `createStore`/`configureStore` types/function take an additional `PreloadedState` generic which defaults to `S`. +- The overloads for `combineReducers` are removed in favor of a single function definition that takes the `ReducersMapObject` as its generic parameter. Removing the overloads was necessary with these changes, since sometimes it was choosing the wrong overload. +- Enhancers that explicitly list the generics for the reducer will need to add the third generic. + #### `AnyAction` deprecated in favour of `UnknownAction` +The Redux TS types have always exported an `AnyAction` type, which is defined to have `{type: string}` and treat any other field as `any`. This makes it easy to write uses like `console.log(action.whatever)`, but unfortunately does not provide any meaningful type safety. + +We now export an `UnknownAction` type, which treats all fields other than `action.type` as `unknown`. This encourages users to write type guards that check the action object and assert its _specific_ TS type. Inside of those checks, you can access a field with better type safety. + +`UnknownAction` is now the default any place in the Redux source that expects an action object. + +`AnyAction` still exists for compatibility, but has been marked as deprecated. + +Note that [Redux Toolkit's action creators have a `.match()` method](https://redux-toolkit.js.org/api/createAction#actioncreatormatch) that acts as a useful type guard: + +```ts +if (todoAdded.match(someUnknownAction)) { + // action is now typed as a PayloadAction +} +``` +
### Toolkit only -#### Object syntax for `extraReducers` and `createReducer` removed +#### Object syntax for `createSlice.extraReducers` and `createReducer` removed + +RTK's `createReducer` API was originally designed to accept a lookup table of action type strings to case reducers, like `{ "ADD_TODO": (state, action) => {} }`. We later added the "builder callback" form to allow more flexibility in adding "matchers" and a default handler, and did the same for `createSlice.extraReducers`. + +We have removed the "object" form for both `createReducer` and `createSlice.extraReducers` in RTK 2.0, as the builder callback form is effectively the same number of lines of code, and works much better with TypeScript. + +As an example, this: + +```ts +const todoAdded = createAction('todos/todoAdded') + +createReducer(initialState, { + [todoAdded]: (state, action) => {}, +}) + +createSlice({ + name, + initialState, + reducers: { + /* case reducers here */ + }, + extraReducers: { + [todoAdded]: (state, action) => {}, + }, +}) +``` + +should be migrated to: + +```ts +createReducer(initialState, (builder) => { + builder.addCase(todoAdded, (state, action) => {}) +}) + +createSlice({ + name, + initialState, + reducers: { + /* case reducers here */ + }, + extraReducers: (builder) => { + builder.addCase(todoAdded, (state, action) => {}) + }, +}) +``` ##### Codemods -#### `middleware` and `enhancers` must be a callback +To simplify upgrading codebases, we've published a set of codemods that will automatically transform the deprecated "object" syntax into the equivalent "builder" syntax. + +The codemods package is available on NPM as [`@reduxjs/rtk-codemods`](https://www.npmjs.com/package/@reduxjs/rtk-codemods). More details are available [here](../api/codemods). + +To run the codemods against your codebase, run `npx @reduxjs/rtk-codemods path/of/files/ or/some**/*glob.js.` + +Examples: + +```sh +npx @reduxjs/rtk-codemods createReducerBuilder ./src + +npx @reduxjs/rtk-codemods createSliceBuilder ./packages/my-app/**/*.ts +``` + +We also recommend re-running Prettier on the codebase before committing the changes. + +These codemods should work, but we would greatly appreciate feedback from more real-world codebases! + +#### `configureStore.middleware` must be a callback + +Since the beginning, `configureStore` has accepted a direct array value as the `middleware` option. However, providing an array directly prevents `configureStore` from calling `getDefaultMiddleware()`. So, `middleware: [myMiddleware]` means there is no thunk middleware added (or any of the dev-mode checks). + +This is a footgun, and we've had numerous users accidentally do this and cause their apps to fail because the default middleware never got configured. + +As a result, we've now made the `middleware` only accept the callback form. _If_ for some reason you still want to replace _all_ of the built-in middleware, do so by returning an array from the callback: + +```ts +const store = configureStore({ + reducer, + middleware: (getDefaultMiddleware) => { + // WARNING: this means that _none_ of the default middleware are added! + return [myMiddleware] + // or for TS users, use: + // return new Tuple(myMiddleware) + }, +}) +``` + +But note that **we consistently recommend not replacing the default middleware entirely**, and that you should use `return getDefaultMiddleware().concat(myMiddleware)`. + +#### `configureStore.enhancers` must be a callback + +Similarly to `configureStore.middleware`, the `enhancers` field must also be a callback, for the same reasons. + +The callback will receive a `getDefaultEnhancers` function that can be used to customise the batching enhancer [that's now included by default](#configurestore-adds-autobatchenhancer-by-default). + +For example: + +```ts +const store = configureStore({ + reducer, + enhancers: (getDefaultEnhancers) => { + return getDefaultEnhancers({ + autoBatch: { type: 'tick' }, + }).concat(myEnhancer) + }, +}) +``` + +It's important to note that the result of `getDefaultEnhancers` will **also** contain the middleware enhancer created with any configured/default middleware. To help prevent mistakes, `configureStore` will log an error to console if middleware was provided and the middleware enhancer wasn't included in the callback result. + +```ts +const store = configureStore({ + reducer, + enhancers: (getDefaultEnhancers) => { + return [myEnhancer] // we've lost the middleware here + // instead: + return getDefaultEnhancers().concat(myEnhancer) + }, +}) +``` #### Standalone `getDefaultMiddleware` and `getType` removed +The standalone version of `getDefaultMiddleware` has been deprecated since v1.6.1, and has now been removed. Use the function passed to the `middleware` callback instead, which has the correct types. + +We have also removed the `getType` export, which was used to extract a type string from action creators made with `createAction`. Instead, use the static property `actionCreator.type`. + #### RTK Query tag invalidation behaviour #### `reactHooksModule` custom hook configuration +Previously, custom versions of React Redux's hooks (`useSelector`, `useDispatch`, and `useStore`) could be passed separately to `reactHooksModule`, usually to enable using a different context to the default `ReactReduxContext`. + +The react hooks module requires all three of these hooks to be provided, and it became an easy mistake to only pass `useSelector` and `useDispatch`, without `useStore`. + +The module has now moved all three of these under the same configuration key, and will check that all three are provided if the key is present. + +```ts +// previously +const customCreateApi = buildCreateApi( + coreModule(), + reactHooksModule({ + useDispatch: createDispatchHook(MyContext), + useSelector: createSelectorHook(MyContext), + useStore: createStoreHook(MyContext), + }) +) + +// now +const customCreateApi = buildCreateApi( + coreModule(), + reactHooksModule({ + hooks: { + useDispatch: createDispatchHook(MyContext), + useSelector: createSelectorHook(MyContext), + useStore: createStoreHook(MyContext), + }, + }) +) +``` +
#### Non-default middleware/enhancers must use `Tuple` +We've seen many cases where users passing the `middleware` parameter to configureStore have tried spreading the array returned by `getDefaultMiddleware()`, or passed an alternate plain array. This unfortunately loses the exact TS types from the individual middleware, and often causes TS problems down the road (such as `dispatch` being typed as `Dispatch` and not knowing about thunks). + +`getDefaultMiddleware()` already used an internal `MiddlewareArray` class, an `Array` subclass that had strongly typed `.concat/prepend()` methods to correctly capture and retain the middleware types. + +We've renamed that type to `Tuple`, and `configureStore`'s TS types now require that you _must_ use `Tuple` if you want to pass your own array of middleware: + +```ts +import { configureStore, Tuple } from '@reduxjs/toolkit' + +configureStore({ + reducer: rootReducer, + middleware: (getDefaultMidldeware) => new Tuple(additionalMiddleware, logger), +}) +``` + +(Note that this has no effect if you're using RTK with plain JS, and you could still pass a plain array here.) + +This same restriction applies to the `enhancers` field. + +#### Entity adapter type updates + +`createEntityAdapter` now has an `Id` generic argument, which will be used to strongly type the item IDs anywhere those are exposed. Previously, the ID field type was always `string | number`. TS will now try to infer the exact type from either the `.id` field of your entity type, or the `selectId` return type. You could also fall back to passing that generic type directly. + +The `.entities` lookup table is now defined to use a standard TS `Record`, which assumes that each item lookup exists by default. Previously, it used a `Dictionary` type, which assumed the result was `MyEntityType | undefined`. The `Dictionary` type has been removed. + +If you prefer to assume that the lookups _might_ be undefined, use TypeScript's `noUncheckedIndexedAccess` configuration option to control that. +
## Features + + ### `combineSlices` API with slice reducer injection for code-splitting +The Redux core has always included `combineReducers`, which takes an object full of "slice reducer" functions and generates a reducer that calls those slice reducers. RTK's `createSlice` generates slice reducers + associated action creators, and we've taught the pattern of exporting individual action creators as named exports and the slice reducer as a default export. Meanwhile, we've never had official support for lazy-loading reducers, although we've had [sample code for some "reducer injection" patterns in our docs](https://redux.js.org/usage/code-splitting). + +This release includes a new [`combineSlices`](../api/combineSlices) API that is designed to enable lazy-loading of reducers at runtime. It accepts individual slices or an object full of slices as arguments, and automatically calls `combineReducers` using the `sliceObject.name` field as the key for each state field. The generated reducer function has an additional `.inject()` method attached that can be used to dynamically inject additional slices at runtime. It also includes a `.withLazyLoadedSlices()` method that can be used to generate TS types for reducers that will be added later. See [#2776](https://github.com/reduxjs/redux-toolkit/issues/2776) for the original discussion around this idea. + +For now, we are not building this into `configureStore`, so you'll need to call `const rootReducer = combineSlices(.....)` yourself and pass that to `configureStore({reducer: rootReducer})`. + +**Basic usage: a mixture of slices and standalone reducers passed to `combineSlices`** + +```ts +const stringSlice = createSlice({ + name: 'string', + initialState: '', + reducers: {}, +}) + +const numberSlice = createSlice({ + name: 'number', + initialState: 0, + reducers: {}, +}) + +const booleanReducer = createReducer(false, () => {}) + +const api = createApi(/* */) + +const combinedReducer = combineSlices( + stringSlice, + { + num: numberSlice.reducer, + boolean: booleanReducer, + }, + api +) +expect(combinedReducer(undefined, dummyAction())).toEqual({ + string: stringSlice.getInitialState(), + num: numberSlice.getInitialState(), + boolean: booleanReducer.getInitialState(), + api: api.reducer.getInitialState(), +}) +``` + +**Basic slice reducer injection** + +```ts +// Create a reducer with a TS type that knows `numberSlice` will be injected +const combinedReducer = + combineSlices(stringSlice).withLazyLoadedSlices< + WithSlice + >() + +// `state.number` doesn't exist initially +expect(combinedReducer(undefined, dummyAction()).number).toBe(undefined) + +// Create a version of the reducer with `numberSlice` injected (mainly useful for types) +const injectedReducer = combinedReducer.inject(numberSlice) + +// `state.number` now exists, and injectedReducer's type no longer marks it as optional +expect(injectedReducer(undefined, dummyAction()).number).toBe( + numberSlice.getInitialState() +) + +// original reducer has also been changed (type is still optional) +expect(combinedReducer(undefined, dummyAction()).number).toBe( + numberSlice.getInitialState() +) +``` + ### `selectors` field in `createSlice` +The existing `createSlice` API now has support for defining [`selectors`](../api/createSlice#selectors) directly as part of the slice. By default, these will be generated with the assumption that the slice is mounted in the root state using `slice.name` as the field, such as `name: "todos"` -> `rootState.todos`. You can call `sliceObject.getSelectors(selectSliceState)` to generate the selectors with an alternate location, similar to how `entityAdapter.getSelectors()` works. + +```ts +const slice = createSlice({ + name: 'counter', + initialState: 42, + reducers: {}, + selectors: { + selectSlice: (state) => state, + selectMultiple: (state, multiplier: number) => state * multiplier, + }, +}) + +// Basic usage +const testState = { + [slice.name]: slice.getInitialState(), +} +const { selectSlice, selectMultiple } = slice.selectors +expect(selectSlice(testState)).toBe(slice.getInitialState()) +expect(selectMultiple(testState, 2)).toBe(slice.getInitialState() * 2) + +// Usage with the slice reducer mounted under a different key +const customState = { + number: slice.getInitialState(), +} +const { selectSlice, selectMultiple } = slice.getSelectors( + (state: typeof customState) => state.number +) +expect(selectSlice(customState)).toBe(slice.getInitialState()) +expect(selectMultiple(customState, 2)).toBe(slice.getInitialState() * 2) +``` + ### Callback syntax for `createSlice.reducers` -### `createDynamicMiddleware` +One of the oldest feature requests we've had is the ability to declare thunks directly inside of `createSlice`. Until now, you've always had to declare them separately, give the thunk a string action prefix, and handle the actions via `createSlice.extraReducers`: + +```ts +// Declare the thunk separately +const fetchUserById = createAsyncThunk( + 'users/fetchByIdStatus', + async (userId: number, thunkAPI) => { + const response = await userAPI.fetchById(userId) + return response.data + } +) + +const usersSlice = createSlice({ + name: 'users', + initialState, + reducers: { + // standard reducer logic, with auto-generated action types per reducer + }, + extraReducers: (builder) => { + // Add reducers for additional action types here, and handle loading state as needed + builder.addCase(fetchUserById.fulfilled, (state, action) => { + state.entities.push(action.payload) + }) + }, +}) +``` + +Many users have told us that this separation feels awkward. + +We've _wanted_ to include a way to define thunks directly inside of `createSlice`, and have played around with various prototypes. There were always two major blocking issues, and a secondary concern: + +1 It wasn't clear what the syntax for declaring a thunk inside should look like. 2. Thunks have access to `getState` and `dispatch`, but the `RootState` and `AppDispatch` types are normally inferred from the store, which in turn infers it from the slice state types. Declaring thunks inside `createSlice` would cause circular type inference errors, as the store needs the slice types but the slice needs the store types. We weren't willing to ship an API that would work okay for our JS users but not for our TS users, especially since we _want_ people to use TS with RTK. 3. You can't do synchronous conditional imports in ES modules, and there's no good way to make the `createAsyncThunk` import optional. Either `createSlice` always depends on it (and adds that to the bundle size), or it can't use `createAsyncThunk` at all. + +We've settled on these compromises: + +- You can declare thunks inside of createSlice.reducers, by using a "creator callback" syntax for the reducers field that is similar to the build callback syntax in RTK Query's createApi (using typed functions to create fields in an object). Doing this does look a bit different than the existing "object" syntax for the reducers field, but is still fairly similar. +- You can customize some of the types for thunks inside of createSlice, but you cannot customize the state or dispatch types. If those are needed, you can manually do an as cast, like getState() as RootState. +- In order to create async thunks with `createSlice`, you specifically need to [set up a version that uses `createAsyncThunk`](../api/createSlice#createasyncthunk). + +In practice, we hope these are reasonable tradeoffs. Creating thunks inside of `createSlice` has been widely asked for, so we think it's an API that will see usage. If the TS customization options are a limitation, you can still declare thunks outside of `createSlice` as always, and most async thunks don't need `dispatch` or `getState` - they just fetch data and return. And finally, setting up a custom `createSlice` allows you to opt into `createAsyncThunk` being included in your bundle size (though it may already be included if used directly or as part of RTK Query - in either of these cases there's no _additional_ bundle size). + +Here's what the new callback syntax looks like: + +```ts +const createSlice = buildCreateSlice({ + creators: { asyncThunk: asyncThunkCreator }, +}) + +const todosSlice = createSlice({ + name: 'todos', + initialState: { + loading: false, + todos: [], + error: null, + } as TodoState, + reducers: (create) => ({ + // A normal "case reducer", same as always + deleteTodo: create.reducer((state, action: PayloadAction) => { + state.todos.splice(action.payload, 1) + }), + // A case reducer with a "prepare callback" to customize the action + addTodo: create.preparedReducer( + (text: string) => { + const id = nanoid() + return { payload: { id, text } } + }, + // action type is inferred from prepare callback + (state, action) => { + state.todos.push(action.payload) + } + ), + // An async thunk + fetchTodo: create.asyncThunk( + // Async payload function as the first argument + async (id: string, thunkApi) => { + const res = await fetch(`myApi/todos?id=${id}`) + return (await res.json()) as Item + }, + // An object containing `{pending?, rejected?, fulfilled?, settled?, options?}` second + { + pending: (state) => { + state.loading = true + }, + rejected: (state, action) => { + state.error = action.payload ?? action.error + }, + fulfilled: (state, action) => { + state.todos.push(action.payload) + }, + // settled is called for both rejected and fulfilled actions + settled: (state, action) => { + state.loading = false + }, + } + ), + }), +}) + +// `addTodo` and `deleteTodo` are normal action creators. +// `fetchTodo` is the async thunk +export const { addTodo, deleteTodo, fetchTodo } = todosSlice.actions +``` + +#### Codemod + +Using the new callback syntax is entirely optional (the object syntax is still standard), but an existing slice would need to be converted before it can take advantage of the new capabilities this syntax provides. To make this easier, a [codemod](../api/codemods) is provided. + +```sh +npx @reduxjs/rtk-codemods createSliceReducerBuilder ./src/features/todos/slice.ts +``` + +### "Dynamic middleware" middleware + +A Redux store's middleware pipeline is fixed at store creation time and can't be changed later. We _have_ seen ecosystem libraries that tried to allow dynamically adding and removing middleware, potentially useful for things like code splitting. + +This is a relatively niche use case, but we've built [our own version of a "dynamic middleware" middleware](../api/createDynamicMiddleware). Add it to the Redux store at setup time, and it lets you add and remove middleware later at runtime. It also comes with a [React hook integration that will automatically add a middleware to the store and return the updated dispatch method.](../api/createDynamicMiddleware#react-integration). + +```ts +import { createDynamicMiddleware, configureStore } from '@reduxjs/toolkit' + +const dynamicMiddleware = createDynamicMiddleware() + +const store = configureStore({ + reducer: { + todos: todosReducer, + }, + middleware: (getDefaultMiddleware) => + getDefaultMiddleware().prepend(dynamicMiddleware.middleware), +}) + +// later +dynamicMiddleware.addMiddleware(someOtherMiddleware) +``` ### `configureStore` adds `autoBatchEnhancer` by default +[In v1.9.0, we added a new `autoBatchEnhancer`](https://github.com/reduxjs/redux-toolkit/releases/tag/v1.9.0) that delays notifying subscribers briefly when multiple "low-priority" actions are dispatched in a row. This improves perf, as UI updates are typically the most expensive part of the update process. RTK Query marks most of its own internal actions as "low-pri" by default, but you have to have the `autoBatchEnhancer` added to the store to benefit from that. + +We've updated `configureStore` to add the `autoBatchEnhancer` to the store setup by default, so that users can benefit from the improved perf without needing to manually tweak the store config themselves. + +### `entityAdapter.getSelectors` accepts a `createSelector` function + +[`entityAdapter.getSelectors()`](../api/createEntityAdapter#selector-functions) now accepts an options object as its second argument. This allows you to pass in your own preferred `createSelector` method, which will be used to memoize the generated selectors. This could be useful if you want to use one of Reselect's new alternate memoizers, or some other memoization library with an equivalent signature. + ### New dev checks in `reselect` v5 +Reselect v5 includes a dev-only check to check stability of input selectors, by running them an extra time with the same parameters, and checking that the result matches. + +This is important, as an input selector returning a materially different result with the same parameters means that the output selector will be run unnecessarily, thus (potentially) creating a new result and causing rerenders. + +```ts +const addNumbers = createSelector( + // this input selector will always return a new reference when run + // so cache will never be used + (a, b) => ({ a, b }), + ({ a, b }) => ({ total: a + b }) +) +// instead, you should have an input selector for each stable piece of data +const addNumbersStable = createSelector( + (a, b) => a, + (a, b) => b, + (a, b) => ({ + total: a + b, + }) +) +``` + +This is done the first time the selector is called, unless configured otherwise. More details are available in the [README](https://github.com/reduxjs/reselect#inputstabilitycheck). + +Note that RTK intentionally does not re-export the function to configure this check globally - if you wish to do so, you should instead depend on `reselect` directly and import it yourself. + ### Immer 10.0 +[Immer 10.0](https://github.com/immerjs/immer/releases/tag/v10.0.0) is now final, and has several major improvements and updates: + +- Much faster update perf +- Much smaller bundle size +- Better ESM/CJS package formatting +- No default export +- No ES5 fallback + +We've updated RTK to depend on the final Immer 10.0 release. + ## Recommendations ### Alternatives to `actionCreator.toString()` -## Future plans - 3.0? +## Future plans + +### Custom slice reducer creators + +### Selector factories + +### 3.0 - RTK Query From 7b10101c4e71fc7b11a7bb558702a86619654a0d Mon Sep 17 00:00:00 2001 From: Ben Durrant Date: Thu, 16 Nov 2023 21:49:07 +0000 Subject: [PATCH 361/412] fix ordered list --- docs/migrations/1.x-to-2.x.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/migrations/1.x-to-2.x.md b/docs/migrations/1.x-to-2.x.md index f0ba7aff67..9adecec00a 100644 --- a/docs/migrations/1.x-to-2.x.md +++ b/docs/migrations/1.x-to-2.x.md @@ -472,7 +472,9 @@ Many users have told us that this separation feels awkward. We've _wanted_ to include a way to define thunks directly inside of `createSlice`, and have played around with various prototypes. There were always two major blocking issues, and a secondary concern: -1 It wasn't clear what the syntax for declaring a thunk inside should look like. 2. Thunks have access to `getState` and `dispatch`, but the `RootState` and `AppDispatch` types are normally inferred from the store, which in turn infers it from the slice state types. Declaring thunks inside `createSlice` would cause circular type inference errors, as the store needs the slice types but the slice needs the store types. We weren't willing to ship an API that would work okay for our JS users but not for our TS users, especially since we _want_ people to use TS with RTK. 3. You can't do synchronous conditional imports in ES modules, and there's no good way to make the `createAsyncThunk` import optional. Either `createSlice` always depends on it (and adds that to the bundle size), or it can't use `createAsyncThunk` at all. +1. It wasn't clear what the syntax for declaring a thunk inside should look like. +2. Thunks have access to `getState` and `dispatch`, but the `RootState` and `AppDispatch` types are normally inferred from the store, which in turn infers it from the slice state types. Declaring thunks inside `createSlice` would cause circular type inference errors, as the store needs the slice types but the slice needs the store types. We weren't willing to ship an API that would work okay for our JS users but not for our TS users, especially since we _want_ people to use TS with RTK. +3. You can't do synchronous conditional imports in ES modules, and there's no good way to make the `createAsyncThunk` import optional. Either `createSlice` always depends on it (and adds that to the bundle size), or it can't use `createAsyncThunk` at all. We've settled on these compromises: From 78b70337ae90c2f7f5d4c4dde874b1de997e25a8 Mon Sep 17 00:00:00 2001 From: Ben Durrant Date: Thu, 16 Nov 2023 22:29:52 +0000 Subject: [PATCH 362/412] write toString alternatives section --- docs/migrations/1.x-to-2.x.md | 74 +++++++++++++++++++++++++++++++++-- 1 file changed, 71 insertions(+), 3 deletions(-) diff --git a/docs/migrations/1.x-to-2.x.md b/docs/migrations/1.x-to-2.x.md index 9adecec00a..04edd87620 100644 --- a/docs/migrations/1.x-to-2.x.md +++ b/docs/migrations/1.x-to-2.x.md @@ -271,7 +271,7 @@ We have also removed the `getType` export, which was used to extract a type stri Previously, custom versions of React Redux's hooks (`useSelector`, `useDispatch`, and `useStore`) could be passed separately to `reactHooksModule`, usually to enable using a different context to the default `ReactReduxContext`. -The react hooks module requires all three of these hooks to be provided, and it became an easy mistake to only pass `useSelector` and `useDispatch`, without `useStore`. +In practicality, the react hooks module needs all three of these hooks to be provided, and it became an easy mistake to only pass `useSelector` and `useDispatch`, without `useStore`. The module has now moved all three of these under the same configuration key, and will check that all three are provided if the key is present. @@ -478,8 +478,8 @@ We've _wanted_ to include a way to define thunks directly inside of `createSlice We've settled on these compromises: -- You can declare thunks inside of createSlice.reducers, by using a "creator callback" syntax for the reducers field that is similar to the build callback syntax in RTK Query's createApi (using typed functions to create fields in an object). Doing this does look a bit different than the existing "object" syntax for the reducers field, but is still fairly similar. -- You can customize some of the types for thunks inside of createSlice, but you cannot customize the state or dispatch types. If those are needed, you can manually do an as cast, like getState() as RootState. +- You can declare thunks inside of `createSlice.reducers`, by using a "creator callback" syntax for the `reducers` field that is similar to the `build` callback syntax in RTK Query's `createApi` (using typed functions to create fields in an object). Doing this does look a bit different than the existing "object" syntax for the `reducers` field, but is still fairly similar. +- You can customize _some_ of the types for thunks inside of `createSlice`, but you _cannot_ customize the `state` or `dispatch` types. If those are needed, you can manually do an `as` cast, like `getState() as RootState`. - In order to create async thunks with `createSlice`, you specifically need to [set up a version that uses `createAsyncThunk`](../api/createSlice#createasyncthunk). In practice, we hope these are reasonable tradeoffs. Creating thunks inside of `createSlice` has been widely asked for, so we think it's an API that will see usage. If the TS customization options are a limitation, you can still declare thunks outside of `createSlice` as always, and most async thunks don't need `dispatch` or `getState` - they just fetch data and return. And finally, setting up a custom `createSlice` allows you to opt into `createAsyncThunk` being included in your bundle size (though it may already be included if used directly or as part of RTK Query - in either of these cases there's no _additional_ bundle size). @@ -628,8 +628,76 @@ We've updated RTK to depend on the final Immer 10.0 release. ## Recommendations +Based on changes in 2.0 and previous versions, there have been some shifts in thinking that are good to know about, if non-essential. + ### Alternatives to `actionCreator.toString()` +As part of RTK's original API, action creators made with `createAction` have a custom `toString()` override that returns the action type. + +This was primarily useful for the ([now removed](#object-syntax-for-createsliceextrareducers-and-createreducer-removed)) object syntax for `createReducer`: + +```ts +const todoAdded = createAction('todos/todoAdded') + +createReducer(initialState, { + [todoAdded]: (state, action) => {}, // toString called here, 'todos/todoAdded' +}) +``` + +While this was convenient (and other libraries in the Redux ecosystem such as `redux-saga` and `redux-observable` have supported this to various capacities), it didn't play well with Typescript and was generally a bit too "magic". + +```ts +const test = todoAdded.toString() +// ^? typed as string, rather than specific action type +``` + +Over time, the action creator also gained a static `type` property and `match` method which were more explicit and worked better with Typescript. + +```ts +const test = todoAdded.type +// ^? 'todos/todoAdded' + +// acts as a type predicate +if (todoAdded.match(unknownAction)) { + unknownAction.payload + // ^? now typed as PayloadAction +} +``` + +For compatibility, this override is still in place, but we encourage considering using either of the static properties for more understandable code. + +For example, with `redux-saga`: + +```ts +// before (still works) +yield takeEvery(todoAdded, saga) + +// consider +yield takeEvery(todoAdded.match, saga) +// or +yield takeEvery(todoAdded.type, saga) +``` + +With `redux-observable`: + +```ts +// before (works in runtime, will not filter types properly) +const epic = (action$: Observable) => + action$.pipe( + ofType(todoAdded), + map((action) => action) + // ^? still Action + ) + +// consider (better type filtering) +const epic = (action$: Observable) => + action$.pipe( + filter(todoAdded.match), + map((action) => action) + // ^? now PayloadAction + ) +``` + ## Future plans ### Custom slice reducer creators From 5d3e605844f14f47653ee8570ba4c996cf654df8 Mon Sep 17 00:00:00 2001 From: Ben Durrant Date: Thu, 16 Nov 2023 22:32:09 +0000 Subject: [PATCH 363/412] swap examples --- docs/migrations/1.x-to-2.x.md | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/docs/migrations/1.x-to-2.x.md b/docs/migrations/1.x-to-2.x.md index 04edd87620..4dc27ef6f7 100644 --- a/docs/migrations/1.x-to-2.x.md +++ b/docs/migrations/1.x-to-2.x.md @@ -666,19 +666,7 @@ if (todoAdded.match(unknownAction)) { For compatibility, this override is still in place, but we encourage considering using either of the static properties for more understandable code. -For example, with `redux-saga`: - -```ts -// before (still works) -yield takeEvery(todoAdded, saga) - -// consider -yield takeEvery(todoAdded.match, saga) -// or -yield takeEvery(todoAdded.type, saga) -``` - -With `redux-observable`: +For example, with `redux-observable`: ```ts // before (works in runtime, will not filter types properly) @@ -698,6 +686,18 @@ const epic = (action$: Observable) => ) ``` +With `redux-saga`: + +```ts +// before (still works) +yield takeEvery(todoAdded, saga) + +// consider +yield takeEvery(todoAdded.match, saga) +// or +yield takeEvery(todoAdded.type, saga) +``` + ## Future plans ### Custom slice reducer creators From 01c30b9a88d2546a26eb4a7587d4eecb52d06266 Mon Sep 17 00:00:00 2001 From: Ben Durrant Date: Thu, 16 Nov 2023 22:58:10 +0000 Subject: [PATCH 364/412] rtk query behaviour blurb --- docs/migrations/1.x-to-2.x.md | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/docs/migrations/1.x-to-2.x.md b/docs/migrations/1.x-to-2.x.md index 4dc27ef6f7..c91f94e59a 100644 --- a/docs/migrations/1.x-to-2.x.md +++ b/docs/migrations/1.x-to-2.x.md @@ -265,7 +265,13 @@ The standalone version of `getDefaultMiddleware` has been deprecated since v1.6. We have also removed the `getType` export, which was used to extract a type string from action creators made with `createAction`. Instead, use the static property `actionCreator.type`. -#### RTK Query tag invalidation behaviour +#### RTK Query behaviour changes + +We've had a number of reports where RTK Query had issues around usage of `dispatch(endpoint.initiate(arg, {subscription: false}))`. There were also reports that multiple triggered lazy queries were resolving the promises at the wrong time. Both of these had the same underlying issue, which was that RTKQ wasn't tracking cache entries in these cases (intentionally). We've reworked the logic to always track cache entries (and remove them as needed), which should resolve those behavior issues. + +We also have had issues raised about trying to run multiple mutations in a row and how tag invalidation behaves. RTKQ now has internal logic to delay tag invalidation briefly, to allow multiple invalidations to get handled together. This is controlled by a new `invalidationBehavior: 'immediate' | 'delayed'` flag on `createApi`. The new default behavior is `'delayed'`. Set it to `'immediate'` to revert to the behavior in RTK 1.9. + +In RTK 1.9, we reworked RTK Query's internals to keep most of the subscription status inside the RTKQ middleware. The values are still synced to the Redux store state, but this is primarily for display by the Redux DevTools "RTK Query" panel. Related to the cache entry changes above, we've optimized how often those values get synced to the Redux state for perf. #### `reactHooksModule` custom hook configuration From 88f3b4b771dd1455bc2548851344d5dbe88788b5 Mon Sep 17 00:00:00 2001 From: Ben Durrant Date: Thu, 16 Nov 2023 23:44:46 +0000 Subject: [PATCH 365/412] future plans --- docs/migrations/1.x-to-2.x.md | 111 +++++++++++++++++++++++++++++++++- 1 file changed, 110 insertions(+), 1 deletion(-) diff --git a/docs/migrations/1.x-to-2.x.md b/docs/migrations/1.x-to-2.x.md index c91f94e59a..66fa5ba026 100644 --- a/docs/migrations/1.x-to-2.x.md +++ b/docs/migrations/1.x-to-2.x.md @@ -708,8 +708,117 @@ yield takeEvery(todoAdded.type, saga) ### Custom slice reducer creators -### Selector factories +With the addition of the [callback syntax for createSlice](#callback-syntax-for-createslicereducers), the [suggestion](https://github.com/reduxjs/redux-toolkit/issues/3837) was made to enable custom slice reducer creators. These creators would be able to: + +- Modify reducer behaviour by adding case or matcher reducers +- Attach actions (or any other useful functions) to `slice.actions` +- Attach provided case reducers to `slice.caseReducers` + +The creator would need to first return a "definition" shape when `createSlice` is first called, which it then handles by adding any necessary reducers and/or actions. + +An API for this is not set in stone, but the existing `create.asyncThunk` creator implemented with a potential API could look like: + +```js +const asyncThunkCreator = { + type: ReducerType.asyncThunk, + define(payloadCreator, config) { + return { + type: ReducerType.asyncThunk, // needs to match reducer type, so correct handler can be called + payloadCreator, + ...config, + } + }, + handle( + { + // the key the reducer was defined under + reducerName, + // the autogenerated action type, i.e. `${slice.name}/${reducerName}` + type, + }, + // the definition from define() + definition, + // methods to modify slice + context + ) { + const { payloadCreator, options, pending, fulfilled, rejected, settled } = + definition + const asyncThunk = createAsyncThunk(type, payloadCreator, options) + + if (pending) context.addCase(asyncThunk.pending, pending) + if (fulfilled) context.addCase(asyncThunk.fulfilled, fulfilled) + if (rejected) context.addCase(asyncThunk.rejected, rejected) + if (settled) context.addMatcher(asyncThunk.settled, settled) + + context.exposeAction(reducerName, asyncThunk) + context.exposeCaseReducer(reducerName, { + pending: pending || noop, + fulfilled: fulfilled || noop, + rejected: rejected || noop, + settled: settled || noop, + }) + }, +} + +const createSlice = buildCreateSlice({ + creators: { + asyncThunk: asyncThunkCreator, + }, +}) + +const todoSlice = createSlice({ + name: 'todos', +}) +``` + +We're not sure how many people/libraries would actually make use of this though, so any feedback over on the [Github issue](https://github.com/reduxjs/redux-toolkit/issues/3837) is welcome! + +### `createSlice.selector` selector factories + +There have been some concerns raised internally about whether `createSlice.selectors` supports memoized selectors sufficiently. You can provide a memoized selector to your `createSlice.selectors` configuration, but you're stuck with that one instance. + +```ts +const todoSlice = createSlice({ + name: 'todos', + initialState: { + todos: [] as Todo[], + }, + reducers: {}, + selectors: { + selectTodosByAuthor = createSelector( + (state: TodoState) => state.todos, + (state: TodoState, author: string) => author, + (todos, author) => todos.filter((todo) => todo.author === author) + ), + }, +}) + +export const { selectTodosByAuthor } = todoSlice.selectors +``` + +With `reselect`'s default cache size of 1, this can cause caching issues if called in multiple components with different arguments. One typical solution for this (without `createSlice`) is a [selector factory](https://redux.js.org/usage/deriving-data-selectors#creating-unique-selector-instances): + +```ts +export const makeSelectTodosByAuthor = () => + createSelector( + (state: RootState) => state.todos.todos, + (state: RootState, author: string) => author, + (todos, author) => todos.filter((todo) => todo.author === author) + ) + +function AuthorTodos({ author }: { author: string }) { + const selectTodosByAuthor = useMemo(makeSelectTodosByAuthor, []) + const todos = useSelector((state) => selectTodosByAuthor(state, author)) +} +``` + +Of course, with `createSlice.selectors` this is no longer possible, as you need the selector instance when creating your slice. + +In 2.0.0 we have no set solution for this - a few APIs have been floated ([PR 1](https://github.com/reduxjs/redux-toolkit/pull/3671), [PR 2](https://github.com/reduxjs/redux-toolkit/pull/3836)) but nothing was decided upon. If this is something you'd like to see supported, consider providing feedback in the [Github discussion](https://github.com/reduxjs/redux-toolkit/discussions/3387)! ### 3.0 - RTK Query +RTK 2.0 was largely focused on core and toolkit changes. Now that 2.0 is released, we would like to shift our focus to RTK Query, as there is still some rough edges to iron out - some of which may require breaking changes, necessitating a 3.0 release. + +If you have any feedback for what that could look like, please consider chiming in at the [RTK Query API pain points and rough spots feedback thread](https://github.com/reduxjs/redux-toolkit/issues/3692)! + From 535535f686084e516d96a05d0ae38c934681870e Mon Sep 17 00:00:00 2001 From: Ben Durrant Date: Thu, 16 Nov 2023 23:55:55 +0000 Subject: [PATCH 366/412] rm extra code --- docs/migrations/1.x-to-2.x.md | 4 ---- 1 file changed, 4 deletions(-) diff --git a/docs/migrations/1.x-to-2.x.md b/docs/migrations/1.x-to-2.x.md index 66fa5ba026..7fc527111e 100644 --- a/docs/migrations/1.x-to-2.x.md +++ b/docs/migrations/1.x-to-2.x.md @@ -764,10 +764,6 @@ const createSlice = buildCreateSlice({ asyncThunk: asyncThunkCreator, }, }) - -const todoSlice = createSlice({ - name: 'todos', -}) ``` We're not sure how many people/libraries would actually make use of this though, so any feedback over on the [Github issue](https://github.com/reduxjs/redux-toolkit/issues/3837) is welcome! From f819769b01d5a376e9434e929b2f3aa7e37e6ea0 Mon Sep 17 00:00:00 2001 From: Ben Durrant Date: Thu, 16 Nov 2023 23:57:05 +0000 Subject: [PATCH 367/412] is -> are --- docs/migrations/1.x-to-2.x.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/migrations/1.x-to-2.x.md b/docs/migrations/1.x-to-2.x.md index 7fc527111e..2abf3da9bd 100644 --- a/docs/migrations/1.x-to-2.x.md +++ b/docs/migrations/1.x-to-2.x.md @@ -813,7 +813,7 @@ In 2.0.0 we have no set solution for this - a few APIs have been floated ([PR 1] ### 3.0 - RTK Query -RTK 2.0 was largely focused on core and toolkit changes. Now that 2.0 is released, we would like to shift our focus to RTK Query, as there is still some rough edges to iron out - some of which may require breaking changes, necessitating a 3.0 release. +RTK 2.0 was largely focused on core and toolkit changes. Now that 2.0 is released, we would like to shift our focus to RTK Query, as there are still some rough edges to iron out - some of which may require breaking changes, necessitating a 3.0 release. If you have any feedback for what that could look like, please consider chiming in at the [RTK Query API pain points and rough spots feedback thread](https://github.com/reduxjs/redux-toolkit/issues/3692)! From db64458cb0e3e01af4eb66621992bb3717104181 Mon Sep 17 00:00:00 2001 From: Ben Durrant Date: Fri, 17 Nov 2023 00:03:52 +0000 Subject: [PATCH 368/412] you can't remove middleware --- docs/migrations/1.x-to-2.x.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/migrations/1.x-to-2.x.md b/docs/migrations/1.x-to-2.x.md index 2abf3da9bd..6dffbb4a80 100644 --- a/docs/migrations/1.x-to-2.x.md +++ b/docs/migrations/1.x-to-2.x.md @@ -564,7 +564,7 @@ npx @reduxjs/rtk-codemods createSliceReducerBuilder ./src/features/todos/slice.t A Redux store's middleware pipeline is fixed at store creation time and can't be changed later. We _have_ seen ecosystem libraries that tried to allow dynamically adding and removing middleware, potentially useful for things like code splitting. -This is a relatively niche use case, but we've built [our own version of a "dynamic middleware" middleware](../api/createDynamicMiddleware). Add it to the Redux store at setup time, and it lets you add and remove middleware later at runtime. It also comes with a [React hook integration that will automatically add a middleware to the store and return the updated dispatch method.](../api/createDynamicMiddleware#react-integration). +This is a relatively niche use case, but we've built [our own version of a "dynamic middleware" middleware](../api/createDynamicMiddleware). Add it to the Redux store at setup time, and it lets you add middleware later at runtime. It also comes with a [React hook integration that will automatically add a middleware to the store and return the updated dispatch method.](../api/createDynamicMiddleware#react-integration). ```ts import { createDynamicMiddleware, configureStore } from '@reduxjs/toolkit' From 7804bc6f2c99b0e90f020b8264e1da183e137411 Mon Sep 17 00:00:00 2001 From: Mark Erikson Date: Thu, 16 Nov 2023 22:18:11 -0500 Subject: [PATCH 369/412] Edits and reshuffling of content for the "Migrations" page --- docs/migrations/1.x-to-2.x.md | 76 +++++++++++++++++++---------------- website/sidebars.json | 11 ++--- 2 files changed, 47 insertions(+), 40 deletions(-) diff --git a/docs/migrations/1.x-to-2.x.md b/docs/migrations/1.x-to-2.x.md index 6dffbb4a80..e98b2f5bc9 100644 --- a/docs/migrations/1.x-to-2.x.md +++ b/docs/migrations/1.x-to-2.x.md @@ -1,7 +1,7 @@ --- -id: 1.x-to-2.x -title: 1.x → 2.x -sidebar_label: 1.x → 2.x +id: migrating-1.x-to-2.x +title: Migrating 1.x → 2.x +sidebar_label: Migrating 1.x → 2.x hide_title: true toc_max_heading_level: 4 --- @@ -10,33 +10,31 @@ toc_max_heading_level: 4
-# 1.x → 2.x +# Migrating 1.x → 2.x ## Introduction - +Redux Toolkit has been available since 2019, and today it's the standard way to write Redux apps. We've gone 4+ years without any breaking changes. Now, RTK 2.0 gives us a chance to modernize the packaging, clean up deprecated options, and tighten up some edge cases. -## Breaking Changes - -### Core, Toolkit and React Redux - -#### Action types _must_ be strings +Redux Toolkit 2.0 is accompanied by major versions of all the other Redux packages: Redux core 5.0, React-Redux 9.0, Redux Thunk 3.0, and Reselect 5.0. -We've always specifically told our users that [actions and state _must_ be serializable](https://redux.js.org/style-guide/#do-not-put-non-serializable-values-in-state-or-actions), and that `action.type` _should_ be a string. This is both to ensure that actions are serializable, and to help provide a readable action history in the Redux DevTools. +This page lists known potentially breaking changes in Redux Toolkit 2.0 and Redux core 5.0, as well as new features in Redux Toolkit 2.0. As a reminder, you should not need to actually install or use the core `redux` package directly - RTK wraps that, and re-exports all methods and types. -`store.dispatch(action)` now specifically enforces that `action.type` _must_ be a string and will throw an error if not, in the same way it throws an error if the action is not a plain object. +In practice, **most of the "breaking" changes should not have an actual effect on end users**. The changes most likely to need app code updates are: -In practice, this was already true 99.99% of the time and shouldn't have any effect on users (especially those using Redux Toolkit and `createSlice`), but there may be some legacy Redux codebases that opted to use Symbols as action types. +- [Object syntax removed for `createReducer` and `createSlice.extraReducers`](#object-syntax-for-createsliceextrareducers-and-createreducer-removed) +- [`configureStore.middleware` must be a callback](#configurestoremiddleware-must-be-a-callback) +- [`Middleware` type changed - Middleware `action` and `next` are typed as `unknown`](#middleware-type-changed---middleware-action-and-next-are-typed-as-unknown) -#### Dropping UMD builds +## Packaging Changes (all) -Redux has always shipped with UMD build artifacts. These are primarily meant for direct import as script tags, such as in a CodePen or a no-bundler build environment. +We've made updates to the build packaging for all of the Redux-related libraries. These are technically "breaking", but _should_ be transparent to end users, and actually enable better support for scenarios such as using Redux via ESM files under Node. -For now, we're dropping those build artifacts from the published package, on the grounds that the use cases seem pretty rare today. +#### Addition of `exports` field in `package.json` -We do have a browser-ready ESM build artifact included at dist/redux.browser.mjs, which can be loaded via a script tag that points to that file on Unpkg. +We've migrated the package definitions to include the `exports` field for defining which artifacts to load, with a modern ESM build as the primary artifact (with CJS still included for compatibility purposes). -If you have strong use cases for us continuing to include UMD build artifacts, please let us know! +We've done local testing of the package, but we ask the community to try out this in your own projects and report any breakages you find! #### Build Artifact Modernization @@ -46,23 +44,27 @@ We've updated the build output in several ways: - Moved all build artifacts to live under ./dist/, instead of separate top-level folders - The lowest Typescript version we test against is now 4.7 -#### Addition of `exports` field in `package.json` +#### Dropping UMD builds + +Redux has always shipped with UMD build artifacts. These are primarily meant for direct import as script tags, such as in a CodePen or a no-bundler build environment. -We've migrated the package definitions to be a full `{type: "module"}` ESM package with an `exports` field (with CJS still included for compatibility purposes). +For now, we're dropping those build artifacts from the published package, on the grounds that the use cases seem pretty rare today. -We've done local testing of the package, but we ask the community to try out this in your own projects and report any breakages you find! +We do have a browser-ready ESM build artifact included at `dist/$PACKAGE_NAME.browser.mjs`, which can be loaded via a script tag that points to that file on Unpkg. -#### Error message extraction +If you have strong use cases for us continuing to include UMD build artifacts, please let us know! -Redux 4.1.0 optimized its bundle size by [extracting error message strings out of production builds](https://github.com/reduxjs/redux/releases/tag/v4.1.0), based on React's approach. We've applied the same technique to RTK. This saves about 1000 bytes from prod bundles (actual benefits will depend on which imports are being used). +## Breaking Changes + +### Core -#### Internal listener implementation +#### Action types _must_ be strings -The Redux store has always used an array to track listener callbacks, and used `listeners.findIndex` to remove listeners on unsubscribe. As we found in React Redux, that can have perf issues when many listeners are unsubscribing at once. +We've always specifically told our users that [actions and state _must_ be serializable](https://redux.js.org/style-guide/#do-not-put-non-serializable-values-in-state-or-actions), and that `action.type` _should_ be a string. This is both to ensure that actions are serializable, and to help provide a readable action history in the Redux DevTools. -In React Redux, we fixed that with a more sophisticated linked list approach. Here, we've updated the listeners to be stored in a `Map` instead, which has better delete performance than an array. +`store.dispatch(action)` now specifically enforces that `action.type` _must_ be a string and will throw an error if not, in the same way it throws an error if the action is not a plain object. -In practice this shouldn't have any real effect, because React Redux sets up a subscription in ``, and all nested components subscribe to that. But, nice to fix it here as well. +In practice, this was already true 99.99% of the time and shouldn't have any effect on users (especially those using Redux Toolkit and `createSlice`), but there may be some legacy Redux codebases that opted to use Symbols as action types.
@@ -72,7 +74,7 @@ In 2019, we began a community-powered conversion of the Redux codebase to TypeSc However, the TS-converted code in master has sat around since then, unused and unpublished, due to concerns about possible compatibility issues with the existing ecosystem (as well as general inertia on our part). -Redux core v5 is now built from that converted source code. In theory, this should be almost identical in both runtime behavior and types to the 4.x build, but it's very likely that some of the changes may cause types issues. +Redux core v5 is now built from that TS-converted source code. In theory, this should be almost identical in both runtime behavior and types to the 4.x build, but it's very likely that some of the changes may cause types issues. Please report any unexpected compatibility issues on [Github](https://github.com/reduxjs/redux/issues)! @@ -305,6 +307,10 @@ const customCreateApi = buildCreateApi( ) ``` +#### Error message extraction + +Redux 4.1.0 optimized its bundle size by [extracting error message strings out of production builds](https://github.com/reduxjs/redux/releases/tag/v4.1.0), based on React's approach. We've applied the same technique to RTK. This saves about 1000 bytes from prod bundles (actual benefits will depend on which imports are being used). +
#### Non-default middleware/enhancers must use `Tuple` @@ -338,9 +344,9 @@ If you prefer to assume that the lookups _might_ be undefined, use TypeScript's
-## Features +## New Features - +These features are new in Redux Toolkit 2.0, and help cover additional use cases that we've seen users ask for in the ecosystem. ### `combineSlices` API with slice reducer injection for code-splitting @@ -445,7 +451,7 @@ expect(selectSlice(customState)).toBe(slice.getInitialState()) expect(selectMultiple(customState, 2)).toBe(slice.getInitialState() * 2) ``` -### Callback syntax for `createSlice.reducers` +### `createSlice.reducers` callback syntax and thunk support One of the oldest feature requests we've had is the ability to declare thunks directly inside of `createSlice`. Until now, you've always had to declare them separately, give the thunk a string action prefix, and handle the actions via `createSlice.extraReducers`: @@ -484,9 +490,9 @@ We've _wanted_ to include a way to define thunks directly inside of `createSlice We've settled on these compromises: +- **In order to create async thunks with `createSlice`, you specifically need to [set up a custom version of `createSlice` that has access to `createAsyncThunk`](../api/createSlice#createasyncthunk)**. - You can declare thunks inside of `createSlice.reducers`, by using a "creator callback" syntax for the `reducers` field that is similar to the `build` callback syntax in RTK Query's `createApi` (using typed functions to create fields in an object). Doing this does look a bit different than the existing "object" syntax for the `reducers` field, but is still fairly similar. - You can customize _some_ of the types for thunks inside of `createSlice`, but you _cannot_ customize the `state` or `dispatch` types. If those are needed, you can manually do an `as` cast, like `getState() as RootState`. -- In order to create async thunks with `createSlice`, you specifically need to [set up a version that uses `createAsyncThunk`](../api/createSlice#createasyncthunk). In practice, we hope these are reasonable tradeoffs. Creating thunks inside of `createSlice` has been widely asked for, so we think it's an API that will see usage. If the TS customization options are a limitation, you can still declare thunks outside of `createSlice` as always, and most async thunks don't need `dispatch` or `getState` - they just fetch data and return. And finally, setting up a custom `createSlice` allows you to opt into `createAsyncThunk` being included in your bundle size (though it may already be included if used directly or as part of RTK Query - in either of these cases there's no _additional_ bundle size). @@ -554,7 +560,7 @@ export const { addTodo, deleteTodo, fetchTodo } = todosSlice.actions #### Codemod -Using the new callback syntax is entirely optional (the object syntax is still standard), but an existing slice would need to be converted before it can take advantage of the new capabilities this syntax provides. To make this easier, a [codemod](../api/codemods) is provided. +**Using the new callback syntax is entirely optional (the object syntax is still standard)**, but an existing slice would need to be converted before it can take advantage of the new capabilities this syntax provides. To make this easier, a [codemod](../api/codemods) is provided. ```sh npx @reduxjs/rtk-codemods createSliceReducerBuilder ./src/features/todos/slice.ts @@ -593,7 +599,7 @@ We've updated `configureStore` to add the `autoBatchEnhancer` to the store setup [`entityAdapter.getSelectors()`](../api/createEntityAdapter#selector-functions) now accepts an options object as its second argument. This allows you to pass in your own preferred `createSelector` method, which will be used to memoize the generated selectors. This could be useful if you want to use one of Reselect's new alternate memoizers, or some other memoization library with an equivalent signature. -### New dev checks in `reselect` v5 +### New dev checks in Reselect v5 Reselect v5 includes a dev-only check to check stability of input selectors, by running them an extra time with the same parameters, and checking that the result matches. @@ -791,7 +797,7 @@ const todoSlice = createSlice({ export const { selectTodosByAuthor } = todoSlice.selectors ``` -With `reselect`'s default cache size of 1, this can cause caching issues if called in multiple components with different arguments. One typical solution for this (without `createSlice`) is a [selector factory](https://redux.js.org/usage/deriving-data-selectors#creating-unique-selector-instances): +With `createSelector`'s default cache size of 1, this can cause caching issues if called in multiple components with different arguments. One typical solution for this (without `createSlice`) is a [selector factory](https://redux.js.org/usage/deriving-data-selectors#creating-unique-selector-instances): ```ts export const makeSelectTodosByAuthor = () => diff --git a/website/sidebars.json b/website/sidebars.json index cc1dcafdf8..6854804533 100644 --- a/website/sidebars.json +++ b/website/sidebars.json @@ -6,11 +6,7 @@ "collapsed": false, "items": ["introduction/getting-started"] }, - { - "type": "category", - "label": "Migrations", - "items": ["migrations/1.x-to-2.x"] - }, + { "type": "category", "label": "Tutorials", @@ -27,6 +23,11 @@ "label": "Using Redux Toolkit", "collapsed": false, "items": [ + { + "type": "category", + "label": "Migrations", + "items": ["migrations/migrating-1.x-to-2.x"] + }, "usage/usage-guide", "usage/usage-with-typescript", "usage/immer-reducers" From 5f7f36a30f5e0cb8e9dc0edc5ef32b6b46687b6f Mon Sep 17 00:00:00 2001 From: Ben Durrant Date: Mon, 20 Nov 2023 15:20:23 +0000 Subject: [PATCH 370/412] Use isAction inside .match so it can handle unknown --- packages/toolkit/src/createAction.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/toolkit/src/createAction.ts b/packages/toolkit/src/createAction.ts index af0968fe14..2f6b1605d3 100644 --- a/packages/toolkit/src/createAction.ts +++ b/packages/toolkit/src/createAction.ts @@ -1,4 +1,4 @@ -import type { Action, UnknownAction } from 'redux' +import type { Action } from 'redux' import type { IsUnknownOrNonInferrable, IfMaybeUndefined, @@ -84,7 +84,7 @@ export type _ActionCreatorWithPreparedPayload< */ export interface BaseActionCreator { type: T - match: (action: Action) => action is PayloadAction + match: (action: unknown) => action is PayloadAction } /** @@ -279,8 +279,8 @@ export function createAction(type: string, prepareAction?: Function): any { actionCreator.type = type - actionCreator.match = (action: Action): action is PayloadAction => - action.type === type + actionCreator.match = (action: unknown): action is PayloadAction => + isAction(action) && action.type === type return actionCreator } From 351eb810343727dc85cd6b4e4727eed232bc6433 Mon Sep 17 00:00:00 2001 From: Ben Durrant Date: Mon, 20 Nov 2023 15:32:40 +0000 Subject: [PATCH 371/412] rm duplicate variables --- packages/toolkit/src/tests/createAction.test.ts | 4 ---- 1 file changed, 4 deletions(-) diff --git a/packages/toolkit/src/tests/createAction.test.ts b/packages/toolkit/src/tests/createAction.test.ts index e9064a1539..6ee8bcff80 100644 --- a/packages/toolkit/src/tests/createAction.test.ts +++ b/packages/toolkit/src/tests/createAction.test.ts @@ -129,10 +129,6 @@ class Action { } describe('isAction', () => { it('should only return true for plain objects with a string type property', () => { - const actionCreator = createAction('anAction') - class Action { - type = 'totally an action' - } const testCases: [action: unknown, expected: boolean][] = [ [{ type: 'an action' }, true], [{ type: 'more props', extra: true }, true], From ae44adcf52c8e246e764de0d72f4a7a6f869cf72 Mon Sep 17 00:00:00 2001 From: Ben Durrant Date: Mon, 20 Nov 2023 15:38:23 +0000 Subject: [PATCH 372/412] remove isAction from isAllOf now that it's included in .match --- packages/toolkit/src/dynamicMiddleware/index.ts | 8 ++------ .../toolkit/src/dynamicMiddleware/tests/index.test.ts | 3 +-- 2 files changed, 3 insertions(+), 8 deletions(-) diff --git a/packages/toolkit/src/dynamicMiddleware/index.ts b/packages/toolkit/src/dynamicMiddleware/index.ts index 5277ca10e5..556998d142 100644 --- a/packages/toolkit/src/dynamicMiddleware/index.ts +++ b/packages/toolkit/src/dynamicMiddleware/index.ts @@ -4,7 +4,7 @@ import type { UnknownAction, } from 'redux' import { compose } from 'redux' -import { createAction, isAction } from '../createAction' +import { createAction } from '../createAction' import { isAllOf } from '../matchers' import { nanoid } from '../nanoid' import { emplace, find } from '../utils' @@ -75,11 +75,7 @@ export const createDynamicMiddleware = < return compose(...appliedMiddleware) } - const isWithMiddleware = isAllOf( - isAction, - withMiddleware, - matchInstance(instanceId) - ) + const isWithMiddleware = isAllOf(withMiddleware, matchInstance(instanceId)) const middleware: DynamicMiddleware = (api) => (next) => (action) => { diff --git a/packages/toolkit/src/dynamicMiddleware/tests/index.test.ts b/packages/toolkit/src/dynamicMiddleware/tests/index.test.ts index 84bd1648af..7f3e0885a2 100644 --- a/packages/toolkit/src/dynamicMiddleware/tests/index.test.ts +++ b/packages/toolkit/src/dynamicMiddleware/tests/index.test.ts @@ -2,7 +2,6 @@ import type { Middleware } from 'redux' import { createDynamicMiddleware } from '../index' import { configureStore } from '../../configureStore' import type { BaseActionCreator, PayloadAction } from '../../createAction' -import { isAction } from '../../createAction' import { createAction } from '../../createAction' import { isAllOf } from '../../matchers' @@ -25,7 +24,7 @@ export const makeProbeableMiddleware = ( ): Middleware<{ (action: PayloadAction): Id }> => { - const isMiddlewareAction = isAllOf(isAction, probeMiddleware, matchId(id)) + const isMiddlewareAction = isAllOf(probeMiddleware, matchId(id)) return (api) => (next) => (action) => { if (isMiddlewareAction(action)) { return id From 649b89b58eef0dcaf0ac941b6a918721fbac4b5d Mon Sep 17 00:00:00 2001 From: Mark Erikson Date: Thu, 23 Nov 2023 16:22:49 -0500 Subject: [PATCH 373/412] Try locking attw version to avoid TS incompat issue --- .github/workflows/tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 5e733b908d..82a3eb9a80 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -211,4 +211,4 @@ jobs: run: ls -l . - name: Run are-the-types-wrong - run: npx @arethetypeswrong/cli ./package.tgz --format table --ignore-rules false-cjs + run: npx @arethetypeswrong/cli@0.11.0 ./package.tgz --format table --ignore-rules false-cjs From 9b09c320438a6a1f8382c7a528812487b56ba493 Mon Sep 17 00:00:00 2001 From: Mark Erikson Date: Thu, 23 Nov 2023 16:41:38 -0500 Subject: [PATCH 374/412] Add attw as a devdep --- packages/toolkit/package.json | 1 + yarn.lock | 210 +++++++++++++++++++++++++++++++++- 2 files changed, 210 insertions(+), 1 deletion(-) diff --git a/packages/toolkit/package.json b/packages/toolkit/package.json index 648c501e6f..3f9ad74600 100644 --- a/packages/toolkit/package.json +++ b/packages/toolkit/package.json @@ -49,6 +49,7 @@ } }, "devDependencies": { + "@arethetypeswrong/cli": "^0.13.1", "@microsoft/api-extractor": "^7.13.2", "@phryneas/ts-version": "^1.0.2", "@size-limit/preset-small-lib": "^4.11.0", diff --git a/yarn.lock b/yarn.lock index 34ff6bcb1a..1a3d758492 100644 --- a/yarn.lock +++ b/yarn.lock @@ -192,6 +192,13 @@ __metadata: languageName: node linkType: hard +"@andrewbranch/untar.js@npm:^1.0.3": + version: 1.0.3 + resolution: "@andrewbranch/untar.js@npm:1.0.3" + checksum: 02555d90423b2ef8a9ce00e6c4254d70dc3214361e702b638c167d228fc0e75d55d0ff0b7f35a4b49ce48072536e503fb5d9bd8cfd1f4f10d5102e42c9f64e76 + languageName: node + linkType: hard + "@apideck/better-ajv-errors@npm:^0.3.1": version: 0.3.4 resolution: "@apideck/better-ajv-errors@npm:0.3.4" @@ -256,6 +263,36 @@ __metadata: languageName: node linkType: hard +"@arethetypeswrong/cli@npm:^0.13.1": + version: 0.13.1 + resolution: "@arethetypeswrong/cli@npm:0.13.1" + dependencies: + "@arethetypeswrong/core": 0.13.0 + chalk: ^4.1.2 + cli-table3: ^0.6.3 + commander: ^10.0.1 + marked: ^9.1.2 + marked-terminal: ^6.0.0 + semver: ^7.5.4 + bin: + attw: dist/index.js + checksum: a200d2c5cd6f6245e4e2afa3a1ea98eb588a4d2a2cf7e6b76c92451d430c7c1bdcd5075e5ab6f9929dcdea36dd072fdcdb32ef085609e40c801ab40cbc96100b + languageName: node + linkType: hard + +"@arethetypeswrong/core@npm:0.13.0": + version: 0.13.0 + resolution: "@arethetypeswrong/core@npm:0.13.0" + dependencies: + "@andrewbranch/untar.js": ^1.0.3 + fflate: ^0.7.4 + semver: ^7.5.4 + typescript: ^5.2.2 + validate-npm-package-name: ^5.0.0 + checksum: cfe30a1ca0259ae6021971f155f9cba83a8329144eeac00164db680d4e46ad244b3f4bfb47b972bc19117dd3c72f2885e74b8cc1d1edc95e95960f090908efd6 + languageName: node + linkType: hard + "@babel/code-frame@npm:7.18.6": version: 7.18.6 resolution: "@babel/code-frame@npm:7.18.6" @@ -6984,6 +7021,7 @@ __metadata: version: 0.0.0-use.local resolution: "@reduxjs/toolkit@workspace:packages/toolkit" dependencies: + "@arethetypeswrong/cli": ^0.13.1 "@microsoft/api-extractor": ^7.13.2 "@phryneas/ts-version": ^1.0.2 "@size-limit/preset-small-lib": ^4.11.0 @@ -7307,7 +7345,7 @@ __metadata: languageName: node linkType: hard -"@sindresorhus/is@npm:^4.0.0": +"@sindresorhus/is@npm:^4.0.0, @sindresorhus/is@npm:^4.6.0": version: 4.6.0 resolution: "@sindresorhus/is@npm:4.6.0" checksum: 83839f13da2c29d55c97abc3bc2c55b250d33a0447554997a85c539e058e57b8da092da396e252b11ec24a0279a0bed1f537fa26302209327060643e327f81d2 @@ -9646,6 +9684,15 @@ __metadata: languageName: node linkType: hard +"ansi-escapes@npm:^6.2.0": + version: 6.2.0 + resolution: "ansi-escapes@npm:6.2.0" + dependencies: + type-fest: ^3.0.0 + checksum: f0bc667d5f1ededc3ea89b73c34f0cba95473525b07e1290ddfd3fc868c94614e95f3549f5c4fd0c05424af7d3fd298101fb3d9a52a597d3782508b340783bd7 + languageName: node + linkType: hard + "ansi-html-community@npm:^0.0.8": version: 0.0.8 resolution: "ansi-html-community@npm:0.0.8" @@ -9722,6 +9769,13 @@ __metadata: languageName: node linkType: hard +"ansicolors@npm:~0.3.2": + version: 0.3.2 + resolution: "ansicolors@npm:0.3.2" + checksum: e84fae7ebc27ac96d9dbb57f35f078cd6dde1b7046b0f03f73dcefc9fbb1f2e82e3685d083466aded8faf038f9fa9ebb408d215282bcd7aaa301d5ac3c486815 + languageName: node + linkType: hard + "any-observable@npm:^0.3.0": version: 0.3.0 resolution: "any-observable@npm:0.3.0" @@ -11021,6 +11075,15 @@ __metadata: languageName: node linkType: hard +"builtins@npm:^5.0.0": + version: 5.0.1 + resolution: "builtins@npm:5.0.1" + dependencies: + semver: ^7.0.0 + checksum: 66d204657fe36522822a95b288943ad11b58f5eaede235b11d8c4edaa28ce4800087d44a2681524c340494aadb120a0068011acabe99d30e8f11a7d826d83515 + languageName: node + linkType: hard + "bundle-require@npm:^4.0.0": version: 4.0.1 resolution: "bundle-require@npm:4.0.1" @@ -11288,6 +11351,18 @@ __metadata: languageName: node linkType: hard +"cardinal@npm:^2.1.1": + version: 2.1.1 + resolution: "cardinal@npm:2.1.1" + dependencies: + ansicolors: ~0.3.2 + redeyed: ~2.1.0 + bin: + cdl: ./bin/cdl.js + checksum: e8d4ae46439cf8fed481c0efd267711ee91e199aa7821a9143e784ed94a6495accd01a0b36d84d377e8ee2cc9928a6c9c123b03be761c60b805f2c026b8a99ad + languageName: node + linkType: hard + "case-sensitive-paths-webpack-plugin@npm:^2.4.0": version: 2.4.0 resolution: "case-sensitive-paths-webpack-plugin@npm:2.4.0" @@ -11394,6 +11469,13 @@ __metadata: languageName: node linkType: hard +"chalk@npm:^5.3.0": + version: 5.3.0 + resolution: "chalk@npm:5.3.0" + checksum: 623922e077b7d1e9dedaea6f8b9e9352921f8ae3afe739132e0e00c275971bdd331268183b2628cf4ab1727c45ea1f28d7e24ac23ce1db1eb653c414ca8a5a80 + languageName: node + linkType: hard + "change-case-all@npm:1.0.14": version: 1.0.14 resolution: "change-case-all@npm:1.0.14" @@ -11719,6 +11801,19 @@ __metadata: languageName: node linkType: hard +"cli-table3@npm:^0.6.3": + version: 0.6.3 + resolution: "cli-table3@npm:0.6.3" + dependencies: + "@colors/colors": 1.5.0 + string-width: ^4.2.0 + dependenciesMeta: + "@colors/colors": + optional: true + checksum: 09897f68467973f827c04e7eaadf13b55f8aec49ecd6647cc276386ea660059322e2dd8020a8b6b84d422dbdd619597046fa89cbbbdc95b2cea149a2df7c096c + languageName: node + linkType: hard + "cli-truncate@npm:^0.2.1": version: 0.2.1 resolution: "cli-truncate@npm:0.2.1" @@ -12000,6 +12095,13 @@ __metadata: languageName: node linkType: hard +"commander@npm:^10.0.1": + version: 10.0.1 + resolution: "commander@npm:10.0.1" + checksum: 436901d64a818295803c1996cd856621a74f30b9f9e28a588e726b2b1670665bccd7c1a77007ebf328729f0139838a88a19265858a0fa7a8728c4656796db948 + languageName: node + linkType: hard + "commander@npm:^2.20.0, commander@npm:^2.7.1": version: 2.20.3 resolution: "commander@npm:2.20.3" @@ -14028,6 +14130,13 @@ __metadata: languageName: node linkType: hard +"emojilib@npm:^2.4.0": + version: 2.4.0 + resolution: "emojilib@npm:2.4.0" + checksum: ea241c342abda5a86ffd3a15d8f4871a616d485f700e03daea38c6ce38205847cea9f6ff8d5e962c00516b004949cc96c6e37b05559ea71a0a496faba53b56da + languageName: node + linkType: hard + "emojis-list@npm:^3.0.0": version: 3.0.0 resolution: "emojis-list@npm:3.0.0" @@ -15325,6 +15434,13 @@ __metadata: languageName: node linkType: hard +"fflate@npm:^0.7.4": + version: 0.7.4 + resolution: "fflate@npm:0.7.4" + checksum: b812ab26047432db70ff4c73eb45ad53bd0774575b4818b9c61c2921e89ec65d1259f06ec1618f2ac55e6a2f2e29b6dc09173d213b46580bc69efae5344bf8f1 + languageName: node + linkType: hard + "figgy-pudding@npm:^3.5.1": version: 3.5.2 resolution: "figgy-pudding@npm:3.5.2" @@ -20380,6 +20496,31 @@ fsevents@^1.2.7: languageName: node linkType: hard +"marked-terminal@npm:^6.0.0": + version: 6.1.0 + resolution: "marked-terminal@npm:6.1.0" + dependencies: + ansi-escapes: ^6.2.0 + cardinal: ^2.1.1 + chalk: ^5.3.0 + cli-table3: ^0.6.3 + node-emoji: ^2.1.0 + supports-hyperlinks: ^3.0.0 + peerDependencies: + marked: ">=1 <11" + checksum: 41ddbde95b7a0b6dbeb32186a100514311983ea4ab0d9e00ecde0b5011cb21ec10f70faa3982c3798bf5de4de607a19ab09a3395aa36f5f49cd7155e12bb8788 + languageName: node + linkType: hard + +"marked@npm:^9.1.2": + version: 9.1.6 + resolution: "marked@npm:9.1.6" + bin: + marked: bin/marked.js + checksum: fc8db42e993d0b97a6f12b8edd93635fa30259ef7088982c714b1c0f54b16946dda54f1bb8a80ab1bd6914647a7217a4f482eda96eb7049bf67437c79e75a609 + languageName: node + linkType: hard + "maxmin@npm:^2.1.0": version: 2.1.0 resolution: "maxmin@npm:2.1.0" @@ -21237,6 +21378,18 @@ fsevents@^1.2.7: languageName: node linkType: hard +"node-emoji@npm:^2.1.0": + version: 2.1.3 + resolution: "node-emoji@npm:2.1.3" + dependencies: + "@sindresorhus/is": ^4.6.0 + char-regex: ^1.0.2 + emojilib: ^2.4.0 + skin-tone: ^2.0.0 + checksum: 9ae5a1fb12fd5ce6885f251f345986115de4bb82e7d06fdc943845fb19260d89d0aaaccbaf85cae39fe7aaa1fc391640558865ba690c9bb8a7236c3ac10bbab0 + languageName: node + linkType: hard + "node-fetch-h2@npm:^2.3.0": version: 2.3.0 resolution: "node-fetch-h2@npm:2.3.0" @@ -24926,6 +25079,15 @@ fsevents@^1.2.7: languageName: node linkType: hard +"redeyed@npm:~2.1.0": + version: 2.1.1 + resolution: "redeyed@npm:2.1.1" + dependencies: + esprima: ~4.0.0 + checksum: 39a1426e377727cfb47a0e24e95c1cf78d969fbc388dc1e0fa1e2ef8a8756450cefb8b0c2598f63b85f1a331986fca7604c0db798427a5775a1dbdb9c1291979 + languageName: node + linkType: hard + "redux-persist@npm:^6.0.0": version: 6.0.0 resolution: "redux-persist@npm:6.0.0" @@ -26180,6 +26342,17 @@ fsevents@^1.2.7: languageName: node linkType: hard +"semver@npm:^7.0.0, semver@npm:^7.5.4": + version: 7.5.4 + resolution: "semver@npm:7.5.4" + dependencies: + lru-cache: ^6.0.0 + bin: + semver: bin/semver.js + checksum: 12d8ad952fa353b0995bf180cdac205a4068b759a140e5d3c608317098b3575ac2f1e09182206bf2eb26120e1c0ed8fb92c48c592f6099680de56bb071423ca3 + languageName: node + linkType: hard + "send@npm:0.18.0": version: 0.18.0 resolution: "send@npm:0.18.0" @@ -26598,6 +26771,15 @@ fsevents@^1.2.7: languageName: node linkType: hard +"skin-tone@npm:^2.0.0": + version: 2.0.0 + resolution: "skin-tone@npm:2.0.0" + dependencies: + unicode-emoji-modifier-base: ^1.0.0 + checksum: 19de157586b8019cacc55eb25d9d640f00fc02415761f3e41a4527142970fd4e7f6af0333bc90e879858766c20a976107bb386ffd4c812289c01d51f2c8d182c + languageName: node + linkType: hard + "slash@npm:^3.0.0": version: 3.0.0 resolution: "slash@npm:3.0.0" @@ -27524,6 +27706,16 @@ fsevents@^1.2.7: languageName: node linkType: hard +"supports-hyperlinks@npm:^3.0.0": + version: 3.0.0 + resolution: "supports-hyperlinks@npm:3.0.0" + dependencies: + has-flag: ^4.0.0 + supports-color: ^7.0.0 + checksum: 41021305de5255b10d821bf93c7a781f783e1693d0faec293d7fc7ccf17011b90bde84b0295fa92ba75c6c390351fe84fdd18848cad4bf656e464a958243c3e7 + languageName: node + linkType: hard + "supports-preserve-symlinks-flag@npm:^1.0.0": version: 1.0.0 resolution: "supports-preserve-symlinks-flag@npm:1.0.0" @@ -28726,6 +28918,13 @@ fsevents@^1.2.7: languageName: node linkType: hard +"unicode-emoji-modifier-base@npm:^1.0.0": + version: 1.0.0 + resolution: "unicode-emoji-modifier-base@npm:1.0.0" + checksum: 6e1521d35fa69493207eb8b41f8edb95985d8b3faf07c01d820a1830b5e8403e20002563e2f84683e8e962a49beccae789f0879356bf92a4ec7a4dd8e2d16fdb + languageName: node + linkType: hard + "unicode-match-property-ecmascript@npm:^2.0.0": version: 2.0.0 resolution: "unicode-match-property-ecmascript@npm:2.0.0" @@ -29306,6 +29505,15 @@ fsevents@^1.2.7: languageName: node linkType: hard +"validate-npm-package-name@npm:^5.0.0": + version: 5.0.0 + resolution: "validate-npm-package-name@npm:5.0.0" + dependencies: + builtins: ^5.0.0 + checksum: 5342a994986199b3c28e53a8452a14b2bb5085727691ea7aa0d284a6606b127c371e0925ae99b3f1ef7cc7d2c9de75f52eb61a3d1cc45e39bca1e3a9444cbb4e + languageName: node + linkType: hard + "validator@npm:^8.0.0": version: 8.2.0 resolution: "validator@npm:8.2.0" From ad6429c5dacc2743a8e9a72aae42e7e6d8b7e7f0 Mon Sep 17 00:00:00 2001 From: Mark Erikson Date: Thu, 23 Nov 2023 16:41:47 -0500 Subject: [PATCH 375/412] Run local attw copy in CI --- .github/workflows/tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 82a3eb9a80..1cf8519f48 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -211,4 +211,4 @@ jobs: run: ls -l . - name: Run are-the-types-wrong - run: npx @arethetypeswrong/cli@0.11.0 ./package.tgz --format table --ignore-rules false-cjs + run: yarn attw ./package.tgz --format table --ignore-rules false-cjs From 49d81e7d221547b9f696e408a7842621e47da9f1 Mon Sep 17 00:00:00 2001 From: Mark Erikson Date: Thu, 23 Nov 2023 16:42:00 -0500 Subject: [PATCH 376/412] Add error code --- errors.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/errors.json b/errors.json index c21258fd0b..d0a9d06a44 100644 --- a/errors.json +++ b/errors.json @@ -37,5 +37,6 @@ "35": "Cannot use `create.asyncThunk` in the built-in `createSlice`. Use `buildCreateSlice({ creators: { asyncThunk: asyncThunkCreator } })` to create a customised version of `createSlice`.", "36": "`context.addCase` cannot be called with an empty action type", "37": "`context.addCase` cannot be called with two reducers for the same action type: type", - "38": "No insert provided for key not already in map" + "38": "No insert provided for key not already in map", + "39": "\\`builder.addCase\\` cannot be called with two reducers for the same action type ''" } \ No newline at end of file From 801b9789fd5bdbce34cb0f9a28bfd808acbd4640 Mon Sep 17 00:00:00 2001 From: Mark Erikson Date: Thu, 23 Nov 2023 16:48:16 -0500 Subject: [PATCH 377/412] Install deps for attw task --- .github/workflows/tests.yml | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 1cf8519f48..653fd581c0 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -200,7 +200,16 @@ jobs: node: ['16.x'] steps: - name: Checkout repo - uses: actions/checkout@v3 + uses: actions/checkout@v2 + + - name: Use node ${{ matrix.node }} + uses: actions/setup-node@v2 + with: + node-version: ${{ matrix.node }} + cache: 'yarn' + + - name: Install deps + run: yarn install - uses: actions/download-artifact@v2 with: From fa3adde90df293ab7d4a897d881a17c0ab7be423 Mon Sep 17 00:00:00 2001 From: Mark Erikson Date: Thu, 23 Nov 2023 15:18:55 -0500 Subject: [PATCH 378/412] Update TS and and ts-eslint --- package.json | 6 +- packages/toolkit/package.json | 8 +- yarn.lock | 515 ++++++++++++++++++---------------- 3 files changed, 274 insertions(+), 255 deletions(-) diff --git a/package.json b/package.json index 1826b6c95a..63f7debb50 100644 --- a/package.json +++ b/package.json @@ -44,7 +44,7 @@ "@babel/helper-compilation-targets": "7.19.3", "@babel/traverse": "7.19.3", "@babel/types": "7.19.3", - "esbuild": "0.19.3", + "esbuild": "0.19.7", "jest-snapshot": "29.3.1", "msw": "patch:msw@npm:0.40.2#.yarn/patches/msw-npm-0.40.2-2107d48752", "jscodeshift": "0.13.1", @@ -65,7 +65,9 @@ "docs/@types/react-dom": "npm:17.0.11", "docs/@types/react": "npm:17.0.11", "type-fest": "2.19.0", - "console-testing-library@0.6.1": "patch:console-testing-library@npm%3A0.6.1#./.yarn/patches/console-testing-library-npm-0.6.1-4d9957d402.patch" + "console-testing-library@0.6.1": "patch:console-testing-library@npm%3A0.6.1#./.yarn/patches/console-testing-library-npm-0.6.1-4d9957d402.patch", + "@typescript-eslint/eslint-plugin": "6.12.0", + "@typescript-eslint/parser": "6.12.0" }, "scripts": { "build": "yarn build:packages", diff --git a/packages/toolkit/package.json b/packages/toolkit/package.json index 3f9ad74600..7f9b6e1971 100644 --- a/packages/toolkit/package.json +++ b/packages/toolkit/package.json @@ -55,7 +55,6 @@ "@size-limit/preset-small-lib": "^4.11.0", "@testing-library/react": "^13.3.0", "@testing-library/user-event": "^13.1.5", - "@types/convert-source-map": "^1.5.1", "@types/json-stringify-safe": "^5.0.0", "@types/nanoid": "^2.1.0", "@types/node": "^10.14.4", @@ -63,11 +62,10 @@ "@types/react": "^18.0.12", "@types/react-dom": "^18.0.5", "@types/yargs": "^16.0.1", - "@typescript-eslint/eslint-plugin": "^4.22.0", - "@typescript-eslint/parser": "^4.22.0", + "@typescript-eslint/eslint-plugin": "^6", + "@typescript-eslint/parser": "^6", "axios": "^0.19.2", "console-testing-library": "0.6.1", - "convert-source-map": "^1.7.0", "esbuild-extra": "^0.3.1", "eslint": "^7.25.0", "eslint-config-prettier": "^8.3.0", @@ -91,7 +89,7 @@ "tslib": "^1.10.0", "tsup": "^7.2.0", "tsx": "^3.12.2", - "typescript": "~4.9", + "typescript": "5.2", "vitest": "^0.30.1", "yargs": "^15.3.1" }, diff --git a/yarn.lock b/yarn.lock index 1a3d758492..6b9e05dcc0 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4576,160 +4576,178 @@ __metadata: languageName: node linkType: hard -"@esbuild/android-arm64@npm:0.19.3": - version: 0.19.3 - resolution: "@esbuild/android-arm64@npm:0.19.3" +"@esbuild/android-arm64@npm:0.19.7": + version: 0.19.7 + resolution: "@esbuild/android-arm64@npm:0.19.7" conditions: os=android & cpu=arm64 languageName: node linkType: hard -"@esbuild/android-arm@npm:0.19.3": - version: 0.19.3 - resolution: "@esbuild/android-arm@npm:0.19.3" +"@esbuild/android-arm@npm:0.19.7": + version: 0.19.7 + resolution: "@esbuild/android-arm@npm:0.19.7" conditions: os=android & cpu=arm languageName: node linkType: hard -"@esbuild/android-x64@npm:0.19.3": - version: 0.19.3 - resolution: "@esbuild/android-x64@npm:0.19.3" +"@esbuild/android-x64@npm:0.19.7": + version: 0.19.7 + resolution: "@esbuild/android-x64@npm:0.19.7" conditions: os=android & cpu=x64 languageName: node linkType: hard -"@esbuild/darwin-arm64@npm:0.19.3": - version: 0.19.3 - resolution: "@esbuild/darwin-arm64@npm:0.19.3" +"@esbuild/darwin-arm64@npm:0.19.7": + version: 0.19.7 + resolution: "@esbuild/darwin-arm64@npm:0.19.7" conditions: os=darwin & cpu=arm64 languageName: node linkType: hard -"@esbuild/darwin-x64@npm:0.19.3": - version: 0.19.3 - resolution: "@esbuild/darwin-x64@npm:0.19.3" +"@esbuild/darwin-x64@npm:0.19.7": + version: 0.19.7 + resolution: "@esbuild/darwin-x64@npm:0.19.7" conditions: os=darwin & cpu=x64 languageName: node linkType: hard -"@esbuild/freebsd-arm64@npm:0.19.3": - version: 0.19.3 - resolution: "@esbuild/freebsd-arm64@npm:0.19.3" +"@esbuild/freebsd-arm64@npm:0.19.7": + version: 0.19.7 + resolution: "@esbuild/freebsd-arm64@npm:0.19.7" conditions: os=freebsd & cpu=arm64 languageName: node linkType: hard -"@esbuild/freebsd-x64@npm:0.19.3": - version: 0.19.3 - resolution: "@esbuild/freebsd-x64@npm:0.19.3" +"@esbuild/freebsd-x64@npm:0.19.7": + version: 0.19.7 + resolution: "@esbuild/freebsd-x64@npm:0.19.7" conditions: os=freebsd & cpu=x64 languageName: node linkType: hard -"@esbuild/linux-arm64@npm:0.19.3": - version: 0.19.3 - resolution: "@esbuild/linux-arm64@npm:0.19.3" +"@esbuild/linux-arm64@npm:0.19.7": + version: 0.19.7 + resolution: "@esbuild/linux-arm64@npm:0.19.7" conditions: os=linux & cpu=arm64 languageName: node linkType: hard -"@esbuild/linux-arm@npm:0.19.3": - version: 0.19.3 - resolution: "@esbuild/linux-arm@npm:0.19.3" +"@esbuild/linux-arm@npm:0.19.7": + version: 0.19.7 + resolution: "@esbuild/linux-arm@npm:0.19.7" conditions: os=linux & cpu=arm languageName: node linkType: hard -"@esbuild/linux-ia32@npm:0.19.3": - version: 0.19.3 - resolution: "@esbuild/linux-ia32@npm:0.19.3" +"@esbuild/linux-ia32@npm:0.19.7": + version: 0.19.7 + resolution: "@esbuild/linux-ia32@npm:0.19.7" conditions: os=linux & cpu=ia32 languageName: node linkType: hard -"@esbuild/linux-loong64@npm:0.19.3": - version: 0.19.3 - resolution: "@esbuild/linux-loong64@npm:0.19.3" +"@esbuild/linux-loong64@npm:0.19.7": + version: 0.19.7 + resolution: "@esbuild/linux-loong64@npm:0.19.7" conditions: os=linux & cpu=loong64 languageName: node linkType: hard -"@esbuild/linux-mips64el@npm:0.19.3": - version: 0.19.3 - resolution: "@esbuild/linux-mips64el@npm:0.19.3" +"@esbuild/linux-mips64el@npm:0.19.7": + version: 0.19.7 + resolution: "@esbuild/linux-mips64el@npm:0.19.7" conditions: os=linux & cpu=mips64el languageName: node linkType: hard -"@esbuild/linux-ppc64@npm:0.19.3": - version: 0.19.3 - resolution: "@esbuild/linux-ppc64@npm:0.19.3" +"@esbuild/linux-ppc64@npm:0.19.7": + version: 0.19.7 + resolution: "@esbuild/linux-ppc64@npm:0.19.7" conditions: os=linux & cpu=ppc64 languageName: node linkType: hard -"@esbuild/linux-riscv64@npm:0.19.3": - version: 0.19.3 - resolution: "@esbuild/linux-riscv64@npm:0.19.3" +"@esbuild/linux-riscv64@npm:0.19.7": + version: 0.19.7 + resolution: "@esbuild/linux-riscv64@npm:0.19.7" conditions: os=linux & cpu=riscv64 languageName: node linkType: hard -"@esbuild/linux-s390x@npm:0.19.3": - version: 0.19.3 - resolution: "@esbuild/linux-s390x@npm:0.19.3" +"@esbuild/linux-s390x@npm:0.19.7": + version: 0.19.7 + resolution: "@esbuild/linux-s390x@npm:0.19.7" conditions: os=linux & cpu=s390x languageName: node linkType: hard -"@esbuild/linux-x64@npm:0.19.3": - version: 0.19.3 - resolution: "@esbuild/linux-x64@npm:0.19.3" +"@esbuild/linux-x64@npm:0.19.7": + version: 0.19.7 + resolution: "@esbuild/linux-x64@npm:0.19.7" conditions: os=linux & cpu=x64 languageName: node linkType: hard -"@esbuild/netbsd-x64@npm:0.19.3": - version: 0.19.3 - resolution: "@esbuild/netbsd-x64@npm:0.19.3" +"@esbuild/netbsd-x64@npm:0.19.7": + version: 0.19.7 + resolution: "@esbuild/netbsd-x64@npm:0.19.7" conditions: os=netbsd & cpu=x64 languageName: node linkType: hard -"@esbuild/openbsd-x64@npm:0.19.3": - version: 0.19.3 - resolution: "@esbuild/openbsd-x64@npm:0.19.3" +"@esbuild/openbsd-x64@npm:0.19.7": + version: 0.19.7 + resolution: "@esbuild/openbsd-x64@npm:0.19.7" conditions: os=openbsd & cpu=x64 languageName: node linkType: hard -"@esbuild/sunos-x64@npm:0.19.3": - version: 0.19.3 - resolution: "@esbuild/sunos-x64@npm:0.19.3" +"@esbuild/sunos-x64@npm:0.19.7": + version: 0.19.7 + resolution: "@esbuild/sunos-x64@npm:0.19.7" conditions: os=sunos & cpu=x64 languageName: node linkType: hard -"@esbuild/win32-arm64@npm:0.19.3": - version: 0.19.3 - resolution: "@esbuild/win32-arm64@npm:0.19.3" +"@esbuild/win32-arm64@npm:0.19.7": + version: 0.19.7 + resolution: "@esbuild/win32-arm64@npm:0.19.7" conditions: os=win32 & cpu=arm64 languageName: node linkType: hard -"@esbuild/win32-ia32@npm:0.19.3": - version: 0.19.3 - resolution: "@esbuild/win32-ia32@npm:0.19.3" +"@esbuild/win32-ia32@npm:0.19.7": + version: 0.19.7 + resolution: "@esbuild/win32-ia32@npm:0.19.7" conditions: os=win32 & cpu=ia32 languageName: node linkType: hard -"@esbuild/win32-x64@npm:0.19.3": - version: 0.19.3 - resolution: "@esbuild/win32-x64@npm:0.19.3" +"@esbuild/win32-x64@npm:0.19.7": + version: 0.19.7 + resolution: "@esbuild/win32-x64@npm:0.19.7" conditions: os=win32 & cpu=x64 languageName: node linkType: hard +"@eslint-community/eslint-utils@npm:^4.4.0": + version: 4.4.0 + resolution: "@eslint-community/eslint-utils@npm:4.4.0" + dependencies: + eslint-visitor-keys: ^3.3.0 + peerDependencies: + eslint: ^6.0.0 || ^7.0.0 || >=8.0.0 + checksum: cdfe3ae42b4f572cbfb46d20edafe6f36fc5fb52bf2d90875c58aefe226892b9677fef60820e2832caf864a326fe4fc225714c46e8389ccca04d5f9288aabd22 + languageName: node + linkType: hard + +"@eslint-community/regexpp@npm:^4.5.1": + version: 4.10.0 + resolution: "@eslint-community/regexpp@npm:4.10.0" + checksum: 2a6e345429ea8382aaaf3a61f865cae16ed44d31ca917910033c02dc00d505d939f10b81e079fa14d43b51499c640138e153b7e40743c4c094d9df97d4e56f7b + languageName: node + linkType: hard + "@eslint/eslintrc@npm:^0.4.2": version: 0.4.2 resolution: "@eslint/eslintrc@npm:0.4.2" @@ -7027,7 +7045,6 @@ __metadata: "@size-limit/preset-small-lib": ^4.11.0 "@testing-library/react": ^13.3.0 "@testing-library/user-event": ^13.1.5 - "@types/convert-source-map": ^1.5.1 "@types/json-stringify-safe": ^5.0.0 "@types/nanoid": ^2.1.0 "@types/node": ^10.14.4 @@ -7035,11 +7052,10 @@ __metadata: "@types/react": ^18.0.12 "@types/react-dom": ^18.0.5 "@types/yargs": ^16.0.1 - "@typescript-eslint/eslint-plugin": ^4.22.0 - "@typescript-eslint/parser": ^4.22.0 + "@typescript-eslint/eslint-plugin": ^6 + "@typescript-eslint/parser": ^6 axios: ^0.19.2 console-testing-library: 0.6.1 - convert-source-map: ^1.7.0 esbuild-extra: ^0.3.1 eslint: ^7.25.0 eslint-config-prettier: ^8.3.0 @@ -7067,7 +7083,7 @@ __metadata: tslib: ^1.10.0 tsup: ^7.2.0 tsx: ^3.12.2 - typescript: ~4.9 + typescript: 5.2 vitest: ^0.30.1 yargs: ^15.3.1 peerDependencies: @@ -7995,13 +8011,6 @@ __metadata: languageName: node linkType: hard -"@types/convert-source-map@npm:^1.5.1": - version: 1.5.1 - resolution: "@types/convert-source-map@npm:1.5.1" - checksum: f201025a8078a0f80172a7b0b93253368ab00603aa656ac093d79257b7b78dca0b7b011bc3d19d20b1275e7ba0dea76528f6873b528338f44c34dda221fa58d9 - languageName: node - linkType: hard - "@types/cookie@npm:^0.4.1": version: 0.4.1 resolution: "@types/cookie@npm:0.4.1" @@ -8225,13 +8234,20 @@ __metadata: languageName: node linkType: hard -"@types/json-schema@npm:*, @types/json-schema@npm:^7.0.4, @types/json-schema@npm:^7.0.5, @types/json-schema@npm:^7.0.7, @types/json-schema@npm:^7.0.8, @types/json-schema@npm:^7.0.9": +"@types/json-schema@npm:*, @types/json-schema@npm:^7.0.4, @types/json-schema@npm:^7.0.5, @types/json-schema@npm:^7.0.8, @types/json-schema@npm:^7.0.9": version: 7.0.11 resolution: "@types/json-schema@npm:7.0.11" checksum: 527bddfe62db9012fccd7627794bd4c71beb77601861055d87e3ee464f2217c85fca7a4b56ae677478367bbd248dbde13553312b7d4dbc702a2f2bbf60c4018d languageName: node linkType: hard +"@types/json-schema@npm:^7.0.12": + version: 7.0.15 + resolution: "@types/json-schema@npm:7.0.15" + checksum: 97ed0cb44d4070aecea772b7b2e2ed971e10c81ec87dd4ecc160322ffa55ff330dace1793489540e3e318d90942064bb697cc0f8989391797792d919737b3b98 + languageName: node + linkType: hard + "@types/json-stable-stringify@npm:^1.0.32": version: 1.0.32 resolution: "@types/json-stable-stringify@npm:1.0.32" @@ -8542,6 +8558,13 @@ __metadata: languageName: node linkType: hard +"@types/semver@npm:^7.5.0": + version: 7.5.6 + resolution: "@types/semver@npm:7.5.6" + checksum: 563a0120ec0efcc326567db2ed920d5d98346f3638b6324ea6b50222b96f02a8add3c51a916b6897b51523aad8ac227d21d3dcf8913559f1bfc6c15b14d23037 + languageName: node + linkType: hard + "@types/serve-index@npm:^1.9.1": version: 1.9.1 resolution: "@types/serve-index@npm:1.9.1" @@ -8696,64 +8719,28 @@ __metadata: languageName: node linkType: hard -"@typescript-eslint/eslint-plugin@npm:^4.22.0": - version: 4.26.0 - resolution: "@typescript-eslint/eslint-plugin@npm:4.26.0" +"@typescript-eslint/eslint-plugin@npm:6.12.0": + version: 6.12.0 + resolution: "@typescript-eslint/eslint-plugin@npm:6.12.0" dependencies: - "@typescript-eslint/experimental-utils": 4.26.0 - "@typescript-eslint/scope-manager": 4.26.0 - debug: ^4.3.1 - functional-red-black-tree: ^1.0.1 - lodash: ^4.17.21 - regexpp: ^3.1.0 - semver: ^7.3.5 - tsutils: ^3.21.0 - peerDependencies: - "@typescript-eslint/parser": ^4.0.0 - eslint: ^5.0.0 || ^6.0.0 || ^7.0.0 - peerDependenciesMeta: - typescript: - optional: true - checksum: 1019fe5d0cb9d1e8683570145cc8408e3a851e6e91ec3b8557ecbf7f14edcfb9747cba09b765cb717cc0c1d335d90c7e1ce4a80865b33d87cac74b8c6e4c50c7 - languageName: node - linkType: hard - -"@typescript-eslint/eslint-plugin@npm:^5.5.0": - version: 5.27.1 - resolution: "@typescript-eslint/eslint-plugin@npm:5.27.1" - dependencies: - "@typescript-eslint/scope-manager": 5.27.1 - "@typescript-eslint/type-utils": 5.27.1 - "@typescript-eslint/utils": 5.27.1 + "@eslint-community/regexpp": ^4.5.1 + "@typescript-eslint/scope-manager": 6.12.0 + "@typescript-eslint/type-utils": 6.12.0 + "@typescript-eslint/utils": 6.12.0 + "@typescript-eslint/visitor-keys": 6.12.0 debug: ^4.3.4 - functional-red-black-tree: ^1.0.1 - ignore: ^5.2.0 - regexpp: ^3.2.0 - semver: ^7.3.7 - tsutils: ^3.21.0 + graphemer: ^1.4.0 + ignore: ^5.2.4 + natural-compare: ^1.4.0 + semver: ^7.5.4 + ts-api-utils: ^1.0.1 peerDependencies: - "@typescript-eslint/parser": ^5.0.0 - eslint: ^6.0.0 || ^7.0.0 || ^8.0.0 + "@typescript-eslint/parser": ^6.0.0 || ^6.0.0-alpha + eslint: ^7.0.0 || ^8.0.0 peerDependenciesMeta: typescript: optional: true - checksum: ee00d8d3a7b395e346801b7bf30209e278f06b5c283ad71c03b34db9e2d68a43ca0e292e315fa7e5bf131a8839ff4a24e0ed76c37811d230f97aae7e123d73ea - languageName: node - linkType: hard - -"@typescript-eslint/experimental-utils@npm:4.26.0": - version: 4.26.0 - resolution: "@typescript-eslint/experimental-utils@npm:4.26.0" - dependencies: - "@types/json-schema": ^7.0.7 - "@typescript-eslint/scope-manager": 4.26.0 - "@typescript-eslint/types": 4.26.0 - "@typescript-eslint/typescript-estree": 4.26.0 - eslint-scope: ^5.1.1 - eslint-utils: ^3.0.0 - peerDependencies: - eslint: "*" - checksum: d48fa79ae7f14cbbb7313dc9535e92133975ab5a08e6e6affe3f58c41f371f676c7470f797ab8a1b46bc78842f3293b100434fa9f3e92b6932ee07fad14297e2 + checksum: a791ebe432a6cac50a15c9e98502b62e874de0c7e35fd320b9bdca21afd4ae88c88cff45ee50a95362da14e98965d946e57b15965f5522f1153568a3fe45db8a languageName: node linkType: hard @@ -8768,47 +8755,21 @@ __metadata: languageName: node linkType: hard -"@typescript-eslint/parser@npm:^4.22.0": - version: 4.26.0 - resolution: "@typescript-eslint/parser@npm:4.26.0" +"@typescript-eslint/parser@npm:6.12.0": + version: 6.12.0 + resolution: "@typescript-eslint/parser@npm:6.12.0" dependencies: - "@typescript-eslint/scope-manager": 4.26.0 - "@typescript-eslint/types": 4.26.0 - "@typescript-eslint/typescript-estree": 4.26.0 - debug: ^4.3.1 - peerDependencies: - eslint: ^5.0.0 || ^6.0.0 || ^7.0.0 - peerDependenciesMeta: - typescript: - optional: true - checksum: ae1655c18f7f3e806ac0f293a45945691c67eaf8bbea56fbec486aaa2f2545cd7340bbe997fe31fcb82031b94c2363a92b4c51f9607e633deb2b7eea347d7512 - languageName: node - linkType: hard - -"@typescript-eslint/parser@npm:^5.5.0": - version: 5.27.1 - resolution: "@typescript-eslint/parser@npm:5.27.1" - dependencies: - "@typescript-eslint/scope-manager": 5.27.1 - "@typescript-eslint/types": 5.27.1 - "@typescript-eslint/typescript-estree": 5.27.1 + "@typescript-eslint/scope-manager": 6.12.0 + "@typescript-eslint/types": 6.12.0 + "@typescript-eslint/typescript-estree": 6.12.0 + "@typescript-eslint/visitor-keys": 6.12.0 debug: ^4.3.4 peerDependencies: - eslint: ^6.0.0 || ^7.0.0 || ^8.0.0 + eslint: ^7.0.0 || ^8.0.0 peerDependenciesMeta: typescript: optional: true - checksum: 0f1df76142c9d7a6c6dbfc5d19fdee02bbc0e79def6e6df4b126c7eaae1c3a46a3871ad498d4b1fc7ad5cb58d6eb70f020807f600d99c0b9add98441fc12f23b - languageName: node - linkType: hard - -"@typescript-eslint/scope-manager@npm:4.26.0": - version: 4.26.0 - resolution: "@typescript-eslint/scope-manager@npm:4.26.0" - dependencies: - "@typescript-eslint/types": 4.26.0 - "@typescript-eslint/visitor-keys": 4.26.0 - checksum: 4ab0f2da294e095a9f9a7a9b1b64945c4bf0090f4aef54bddb84c39983a514b59699f66d26df10123c8b254d907cebc31a54a20b2124b34c0ce47b808fdd7065 + checksum: 92923b7ee61f52d6b74f515640fe6bbb6b0a922d20dabeb6b59bc73f3c132bf750a2b706bb40fbe6d233c6ecc1abe905c99aa062280bb78e5724334f5b6c4ac5 languageName: node linkType: hard @@ -8822,26 +8783,30 @@ __metadata: languageName: node linkType: hard -"@typescript-eslint/type-utils@npm:5.27.1": - version: 5.27.1 - resolution: "@typescript-eslint/type-utils@npm:5.27.1" +"@typescript-eslint/scope-manager@npm:6.12.0": + version: 6.12.0 + resolution: "@typescript-eslint/scope-manager@npm:6.12.0" dependencies: - "@typescript-eslint/utils": 5.27.1 + "@typescript-eslint/types": 6.12.0 + "@typescript-eslint/visitor-keys": 6.12.0 + checksum: 4cc4eb1bcd04ba7b0a1de4284521cde5f3f25f2530f78dfcb3f098396b142fd30a45f615a87dc7a3adddbd131a6255cb12b1df19aacff71a3f766992ddef183f + languageName: node + linkType: hard + +"@typescript-eslint/type-utils@npm:6.12.0": + version: 6.12.0 + resolution: "@typescript-eslint/type-utils@npm:6.12.0" + dependencies: + "@typescript-eslint/typescript-estree": 6.12.0 + "@typescript-eslint/utils": 6.12.0 debug: ^4.3.4 - tsutils: ^3.21.0 + ts-api-utils: ^1.0.1 peerDependencies: - eslint: "*" + eslint: ^7.0.0 || ^8.0.0 peerDependenciesMeta: typescript: optional: true - checksum: 43b7da26ea1bd7d249c45d168ec88f971fb71362bbc21ec4748d73b1ecb43f4ca59f5ed338e8dbc74272ae4ebac1cab87a9b62c0fa616c6f9bd833a212dc8a40 - languageName: node - linkType: hard - -"@typescript-eslint/types@npm:4.26.0": - version: 4.26.0 - resolution: "@typescript-eslint/types@npm:4.26.0" - checksum: 82b92efaf6a4ff3ad3a8ecb28f948f4e377ef806cb78a9436e17429207da1f8ade99546c755739d559b9706e4f7967b29fb9049d07ddd5902d4482754b8dfd41 + checksum: c345c45f1262eee4b9f6960a59b3aba960643d0004094a3d8fb9682ab79af2fae864695029246dc9e0d4fdb2f3d017a56b7dc034e551d263deba75c2ef048d39 languageName: node linkType: hard @@ -8852,21 +8817,10 @@ __metadata: languageName: node linkType: hard -"@typescript-eslint/typescript-estree@npm:4.26.0": - version: 4.26.0 - resolution: "@typescript-eslint/typescript-estree@npm:4.26.0" - dependencies: - "@typescript-eslint/types": 4.26.0 - "@typescript-eslint/visitor-keys": 4.26.0 - debug: ^4.3.1 - globby: ^11.0.3 - is-glob: ^4.0.1 - semver: ^7.3.5 - tsutils: ^3.21.0 - peerDependenciesMeta: - typescript: - optional: true - checksum: 191d7759a3e92ba7b20e496243c81c80fd9d42d303a73bb62860698ce92eb5992739090a404163154dff275b6a6f12f87d0369cf4f8b25182ae6505134f677e4 +"@typescript-eslint/types@npm:6.12.0": + version: 6.12.0 + resolution: "@typescript-eslint/types@npm:6.12.0" + checksum: d3b40f9d400f6455ce5ae610651597c9e9ec85d46ca6d3c1025597a76305c557ebc5b88340ec6db0e694c9c79f1299d375b87a1a5b9314b22231dbbb5ce54695 languageName: node linkType: hard @@ -8888,6 +8842,24 @@ __metadata: languageName: node linkType: hard +"@typescript-eslint/typescript-estree@npm:6.12.0": + version: 6.12.0 + resolution: "@typescript-eslint/typescript-estree@npm:6.12.0" + dependencies: + "@typescript-eslint/types": 6.12.0 + "@typescript-eslint/visitor-keys": 6.12.0 + debug: ^4.3.4 + globby: ^11.1.0 + is-glob: ^4.0.3 + semver: ^7.5.4 + ts-api-utils: ^1.0.1 + peerDependenciesMeta: + typescript: + optional: true + checksum: 943f7ff2e164d812f6ae0a2d5096836aff00b1fda39937b03f126f266f03f3655794f5fc4643b49b71c312126d9422dfd764744bd1ba41ee6821a5bac1511aa2 + languageName: node + linkType: hard + "@typescript-eslint/utils@npm:5.27.1, @typescript-eslint/utils@npm:^5.13.0": version: 5.27.1 resolution: "@typescript-eslint/utils@npm:5.27.1" @@ -8904,13 +8876,20 @@ __metadata: languageName: node linkType: hard -"@typescript-eslint/visitor-keys@npm:4.26.0": - version: 4.26.0 - resolution: "@typescript-eslint/visitor-keys@npm:4.26.0" +"@typescript-eslint/utils@npm:6.12.0": + version: 6.12.0 + resolution: "@typescript-eslint/utils@npm:6.12.0" dependencies: - "@typescript-eslint/types": 4.26.0 - eslint-visitor-keys: ^2.0.0 - checksum: 6e89ab718c3d5b2b3265398e2858c4470ddab8e7fe79f2bb3af3237cd0bb9593957fd4f3ecb09caaeca75387b44ed6b1e35b2a81ff676d4d7439bfb7a388b23e + "@eslint-community/eslint-utils": ^4.4.0 + "@types/json-schema": ^7.0.12 + "@types/semver": ^7.5.0 + "@typescript-eslint/scope-manager": 6.12.0 + "@typescript-eslint/types": 6.12.0 + "@typescript-eslint/typescript-estree": 6.12.0 + semver: ^7.5.4 + peerDependencies: + eslint: ^7.0.0 || ^8.0.0 + checksum: dad05bd0e4db7a88c2716f9ee83c7c28c30d71e57392e58dc0db66b5f5c4c86b9db14142c6a1a82cf1650da294d31980c56a118015d3a2a645acb8b8a5ebc315 languageName: node linkType: hard @@ -8924,6 +8903,16 @@ __metadata: languageName: node linkType: hard +"@typescript-eslint/visitor-keys@npm:6.12.0": + version: 6.12.0 + resolution: "@typescript-eslint/visitor-keys@npm:6.12.0" + dependencies: + "@typescript-eslint/types": 6.12.0 + eslint-visitor-keys: ^3.4.1 + checksum: 3d8dc74ae748a95fe60b48dbaecca8d9c0c8df344d8034e3843057251fba24f06a3d29dbb9f525c9540b538d8c24221d3cf119ac483e9de38149a978051c72f3 + languageName: node + linkType: hard + "@vitest/expect@npm:0.30.1": version: 0.30.1 resolution: "@vitest/expect@npm:0.30.1" @@ -14390,32 +14379,32 @@ __metadata: languageName: node linkType: hard -"esbuild@npm:0.19.3": - version: 0.19.3 - resolution: "esbuild@npm:0.19.3" - dependencies: - "@esbuild/android-arm": 0.19.3 - "@esbuild/android-arm64": 0.19.3 - "@esbuild/android-x64": 0.19.3 - "@esbuild/darwin-arm64": 0.19.3 - "@esbuild/darwin-x64": 0.19.3 - "@esbuild/freebsd-arm64": 0.19.3 - "@esbuild/freebsd-x64": 0.19.3 - "@esbuild/linux-arm": 0.19.3 - "@esbuild/linux-arm64": 0.19.3 - "@esbuild/linux-ia32": 0.19.3 - "@esbuild/linux-loong64": 0.19.3 - "@esbuild/linux-mips64el": 0.19.3 - "@esbuild/linux-ppc64": 0.19.3 - "@esbuild/linux-riscv64": 0.19.3 - "@esbuild/linux-s390x": 0.19.3 - "@esbuild/linux-x64": 0.19.3 - "@esbuild/netbsd-x64": 0.19.3 - "@esbuild/openbsd-x64": 0.19.3 - "@esbuild/sunos-x64": 0.19.3 - "@esbuild/win32-arm64": 0.19.3 - "@esbuild/win32-ia32": 0.19.3 - "@esbuild/win32-x64": 0.19.3 +"esbuild@npm:0.19.7": + version: 0.19.7 + resolution: "esbuild@npm:0.19.7" + dependencies: + "@esbuild/android-arm": 0.19.7 + "@esbuild/android-arm64": 0.19.7 + "@esbuild/android-x64": 0.19.7 + "@esbuild/darwin-arm64": 0.19.7 + "@esbuild/darwin-x64": 0.19.7 + "@esbuild/freebsd-arm64": 0.19.7 + "@esbuild/freebsd-x64": 0.19.7 + "@esbuild/linux-arm": 0.19.7 + "@esbuild/linux-arm64": 0.19.7 + "@esbuild/linux-ia32": 0.19.7 + "@esbuild/linux-loong64": 0.19.7 + "@esbuild/linux-mips64el": 0.19.7 + "@esbuild/linux-ppc64": 0.19.7 + "@esbuild/linux-riscv64": 0.19.7 + "@esbuild/linux-s390x": 0.19.7 + "@esbuild/linux-x64": 0.19.7 + "@esbuild/netbsd-x64": 0.19.7 + "@esbuild/openbsd-x64": 0.19.7 + "@esbuild/sunos-x64": 0.19.7 + "@esbuild/win32-arm64": 0.19.7 + "@esbuild/win32-ia32": 0.19.7 + "@esbuild/win32-x64": 0.19.7 dependenciesMeta: "@esbuild/android-arm": optional: true @@ -14463,7 +14452,7 @@ __metadata: optional: true bin: esbuild: bin/esbuild - checksum: f998ba82b1bbf0f3036201dc2cb94f92aff887b7552738ea3e4dd6f386f87740ef76aabae2fc1c4b91a519354d390f6d9fd89eb71e26882983f6fbaf75369206 + checksum: a5d979224d47ae0cc6685447eb8f1ceaf7b67f5eaeaac0246f4d589ff7d81b08e4502a6245298d948f13e9b571ac8556a6d83b084af24954f762b1cfe59dbe55 languageName: node linkType: hard @@ -14829,6 +14818,13 @@ __metadata: languageName: node linkType: hard +"eslint-visitor-keys@npm:^3.4.1": + version: 3.4.3 + resolution: "eslint-visitor-keys@npm:3.4.3" + checksum: 36e9ef87fca698b6fd7ca5ca35d7b2b6eeaaf106572e2f7fd31c12d3bfdaccdb587bba6d3621067e5aece31c8c3a348b93922ab8f7b2cbc6aaab5e1d89040c60 + languageName: node + linkType: hard + "eslint-webpack-plugin@npm:^3.1.1": version: 3.1.1 resolution: "eslint-webpack-plugin@npm:3.1.1" @@ -16392,6 +16388,13 @@ fsevents@^1.2.7: languageName: node linkType: hard +"graphemer@npm:^1.4.0": + version: 1.4.0 + resolution: "graphemer@npm:1.4.0" + checksum: bab8f0be9b568857c7bec9fda95a89f87b783546d02951c40c33f84d05bb7da3fd10f863a9beb901463669b6583173a8c8cc6d6b306ea2b9b9d5d3d943c3a673 + languageName: node + linkType: hard + "graphql-config@npm:^3.3.0": version: 3.3.0 resolution: "graphql-config@npm:3.3.0" @@ -17232,6 +17235,13 @@ fsevents@^1.2.7: languageName: node linkType: hard +"ignore@npm:^5.2.4": + version: 5.3.0 + resolution: "ignore@npm:5.3.0" + checksum: 2736da6621f14ced652785cb05d86301a66d70248597537176612bd0c8630893564bd5f6421f8806b09e8472e75c591ef01672ab8059c07c6eb2c09cefe04bf9 + languageName: node + linkType: hard + "image-size@npm:^1.0.1": version: 1.0.1 resolution: "image-size@npm:1.0.1" @@ -28404,6 +28414,15 @@ fsevents@^1.2.7: languageName: node linkType: hard +"ts-api-utils@npm:^1.0.1": + version: 1.0.3 + resolution: "ts-api-utils@npm:1.0.3" + peerDependencies: + typescript: ">=4.2.0" + checksum: 441cc4489d65fd515ae6b0f4eb8690057add6f3b6a63a36073753547fb6ce0c9ea0e0530220a0b282b0eec535f52c4dfc315d35f8a4c9a91c0def0707a714ca6 + languageName: node + linkType: hard + "ts-essentials@npm:^2.0.3": version: 2.0.12 resolution: "ts-essentials@npm:2.0.12" @@ -28718,6 +28737,16 @@ fsevents@^1.2.7: languageName: node linkType: hard +"typescript@npm:5.2, typescript@npm:^5.0.0, typescript@npm:^5.2.2": + version: 5.2.2 + resolution: "typescript@npm:5.2.2" + bin: + tsc: bin/tsc + tsserver: bin/tsserver + checksum: 7912821dac4d962d315c36800fe387cdc0a6298dba7ec171b350b4a6e988b51d7b8f051317786db1094bd7431d526b648aba7da8236607febb26cf5b871d2d3c + languageName: node + linkType: hard + "typescript@npm:^4.1.3, typescript@npm:^4.3.4": version: 4.5.2 resolution: "typescript@npm:4.5.2" @@ -28748,16 +28777,6 @@ fsevents@^1.2.7: languageName: node linkType: hard -"typescript@npm:^5.0.0, typescript@npm:^5.2.2": - version: 5.2.2 - resolution: "typescript@npm:5.2.2" - bin: - tsc: bin/tsc - tsserver: bin/tsserver - checksum: 7912821dac4d962d315c36800fe387cdc0a6298dba7ec171b350b4a6e988b51d7b8f051317786db1094bd7431d526b648aba7da8236607febb26cf5b871d2d3c - languageName: node - linkType: hard - "typescript@npm:~4.2.4": version: 4.2.4 resolution: "typescript@npm:4.2.4" @@ -28798,6 +28817,16 @@ fsevents@^1.2.7: languageName: node linkType: hard +"typescript@patch:typescript@5.2#~builtin, typescript@patch:typescript@^5.0.0#~builtin, typescript@patch:typescript@^5.2.2#~builtin": + version: 5.2.2 + resolution: "typescript@patch:typescript@npm%3A5.2.2#~builtin::version=5.2.2&hash=701156" + bin: + tsc: bin/tsc + tsserver: bin/tsserver + checksum: 07106822b4305de3f22835cbba949a2b35451cad50888759b6818421290ff95d522b38ef7919e70fb381c5fe9c1c643d7dea22c8b31652a717ddbd57b7f4d554 + languageName: node + linkType: hard + "typescript@patch:typescript@^4.1.3#~builtin, typescript@patch:typescript@^4.3.4#~builtin": version: 4.5.2 resolution: "typescript@patch:typescript@npm%3A4.5.2#~builtin::version=4.5.2&hash=701156" @@ -28828,16 +28857,6 @@ fsevents@^1.2.7: languageName: node linkType: hard -"typescript@patch:typescript@^5.0.0#~builtin, typescript@patch:typescript@^5.2.2#~builtin": - version: 5.2.2 - resolution: "typescript@patch:typescript@npm%3A5.2.2#~builtin::version=5.2.2&hash=701156" - bin: - tsc: bin/tsc - tsserver: bin/tsserver - checksum: 07106822b4305de3f22835cbba949a2b35451cad50888759b6818421290ff95d522b38ef7919e70fb381c5fe9c1c643d7dea22c8b31652a717ddbd57b7f4d554 - languageName: node - linkType: hard - "typescript@patch:typescript@~4.2.4#~builtin": version: 4.2.4 resolution: "typescript@patch:typescript@npm%3A4.2.4#~builtin::version=4.2.4&hash=701156" From 3001dda6315c2de4b0c2e814c14fa1fc5796f93d Mon Sep 17 00:00:00 2001 From: Mark Erikson Date: Thu, 23 Nov 2023 15:50:08 -0500 Subject: [PATCH 379/412] Update TS better --- .github/workflows/tests.yml | 14 +- package.json | 6 +- .../src/query/tests/devWarnings.test.tsx | 9 ++ yarn.lock | 145 +----------------- 4 files changed, 23 insertions(+), 151 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 653fd581c0..9407755fc2 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -30,7 +30,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - node: ['16.x'] + node: ['18.x'] steps: - name: Checkout repo @@ -67,7 +67,7 @@ jobs: strategy: fail-fast: false matrix: - node: ['16.x'] + node: ['18.x'] steps: - name: Checkout repo uses: actions/checkout@v2 @@ -104,8 +104,8 @@ jobs: strategy: fail-fast: false matrix: - node: ['16.x'] - ts: ['4.7', '4.8', '4.9', '5.0', '5.1', '5.2'] + node: ['18.x'] + ts: ['4.7', '4.8', '4.9', '5.0', '5.1', '5.2', '5.3'] steps: - name: Checkout repo uses: actions/checkout@v2 @@ -148,7 +148,7 @@ jobs: strategy: fail-fast: false matrix: - node: ['16.x'] + node: ['18.x'] example: ['cra4', 'cra5', 'next', 'vite', 'node-standard', 'node-esm'] defaults: run: @@ -184,7 +184,7 @@ jobs: run: yarn info @reduxjs/toolkit && yarn why @reduxjs/toolkit - name: Build example - run: yarn build + run: NODE_OPTIONS=--openssl-legacy-provider yarn build - name: Run test step run: yarn test @@ -197,7 +197,7 @@ jobs: strategy: fail-fast: false matrix: - node: ['16.x'] + node: ['18.x'] steps: - name: Checkout repo uses: actions/checkout@v2 diff --git a/package.json b/package.json index 63f7debb50..4022306807 100644 --- a/package.json +++ b/package.json @@ -35,7 +35,8 @@ "netlify-plugin-cache": "^1.0.3", "prettier": "^2.2.1", "release-it": "^14.12.5", - "serve": "^14.2.0" + "serve": "^14.2.0", + "typescript": "5.2" }, "resolutions": { "@babel/core": "7.19.3", @@ -67,7 +68,8 @@ "type-fest": "2.19.0", "console-testing-library@0.6.1": "patch:console-testing-library@npm%3A0.6.1#./.yarn/patches/console-testing-library-npm-0.6.1-4d9957d402.patch", "@typescript-eslint/eslint-plugin": "6.12.0", - "@typescript-eslint/parser": "6.12.0" + "@typescript-eslint/parser": "6.12.0", + "typescript": "5.2.2" }, "scripts": { "build": "yarn build:packages", diff --git a/packages/toolkit/src/query/tests/devWarnings.test.tsx b/packages/toolkit/src/query/tests/devWarnings.test.tsx index 404adf635d..d87968f9f1 100644 --- a/packages/toolkit/src/query/tests/devWarnings.test.tsx +++ b/packages/toolkit/src/query/tests/devWarnings.test.tsx @@ -205,7 +205,10 @@ describe('warns on multiple apis using the same `reducerPath`', () => { test('common: two apis, same order', async () => { const store = configureStore({ reducer: { + // TS 5.3 now errors on identical object keys. We want to force that behavior. + // @ts-ignore [api1.reducerPath]: api1.reducer, + // @ts-ignore [api1_2.reducerPath]: api1_2.reducer, }, middleware: (gDM) => gDM().concat(api1.middleware, api1_2.middleware), @@ -222,7 +225,9 @@ If you have multiple apis, you *have* to specify the reducerPath option when usi test('common: two apis, opposing order', async () => { const store = configureStore({ reducer: { + // @ts-ignore [api1.reducerPath]: api1.reducer, + // @ts-ignore [api1_2.reducerPath]: api1_2.reducer, }, middleware: (gDM) => gDM().concat(api1_2.middleware, api1.middleware), @@ -242,7 +247,9 @@ If you have multiple apis, you *have* to specify the reducerPath option when usi test('common: two apis, only first middleware', async () => { const store = configureStore({ reducer: { + // @ts-ignore [api1.reducerPath]: api1.reducer, + // @ts-ignore [api1_2.reducerPath]: api1_2.reducer, }, middleware: (gDM) => gDM().concat(api1.middleware), @@ -266,7 +273,9 @@ If you have multiple apis, you *have* to specify the reducerPath option when usi test.skip('common: two apis, only second middleware', async () => { const store = configureStore({ reducer: { + // @ts-ignore [api1.reducerPath]: api1.reducer, + // @ts-ignore [api1_2.reducerPath]: api1_2.reducer, }, middleware: (gDM) => gDM().concat(api1_2.middleware), diff --git a/yarn.lock b/yarn.lock index 6b9e05dcc0..4f2bb9ffe9 100644 --- a/yarn.lock +++ b/yarn.lock @@ -25984,6 +25984,7 @@ fsevents@^1.2.7: prettier: ^2.2.1 release-it: ^14.12.5 serve: ^14.2.0 + typescript: 5.2 languageName: unknown linkType: soft @@ -28727,17 +28728,7 @@ fsevents@^1.2.7: languageName: node linkType: hard -"typescript@npm:4.1.3": - version: 4.1.3 - resolution: "typescript@npm:4.1.3" - bin: - tsc: bin/tsc - tsserver: bin/tsserver - checksum: 0a25f7d7cebbc5ad23f41cb30918643460477be265bd3bcd400ffedb77d16e97d46f2b0c31393b2f990c5cf5b9f7a829ad6aff8636988b8f30abf81c656237c0 - languageName: node - linkType: hard - -"typescript@npm:5.2, typescript@npm:^5.0.0, typescript@npm:^5.2.2": +"typescript@npm:5.2.2": version: 5.2.2 resolution: "typescript@npm:5.2.2" bin: @@ -28747,77 +28738,7 @@ fsevents@^1.2.7: languageName: node linkType: hard -"typescript@npm:^4.1.3, typescript@npm:^4.3.4": - version: 4.5.2 - resolution: "typescript@npm:4.5.2" - bin: - tsc: bin/tsc - tsserver: bin/tsserver - checksum: 74f9ce65d532bdf5d0214b3f60cf37992180023388c87a11ee6f838a803067ef0b63c600fa501b0deb07f989257dce1e244c9635ed79feca40bbccf6e0aa1ebc - languageName: node - linkType: hard - -"typescript@npm:^4.8.0": - version: 4.8.4 - resolution: "typescript@npm:4.8.4" - bin: - tsc: bin/tsc - tsserver: bin/tsserver - checksum: 3e4f061658e0c8f36c820802fa809e0fd812b85687a9a2f5430bc3d0368e37d1c9605c3ce9b39df9a05af2ece67b1d844f9f6ea8ff42819f13bcb80f85629af0 - languageName: node - linkType: hard - -"typescript@npm:^4.9": - version: 4.9.5 - resolution: "typescript@npm:4.9.5" - bin: - tsc: bin/tsc - tsserver: bin/tsserver - checksum: ee000bc26848147ad423b581bd250075662a354d84f0e06eb76d3b892328d8d4440b7487b5a83e851b12b255f55d71835b008a66cbf8f255a11e4400159237db - languageName: node - linkType: hard - -"typescript@npm:~4.2.4": - version: 4.2.4 - resolution: "typescript@npm:4.2.4" - bin: - tsc: bin/tsc - tsserver: bin/tsserver - checksum: 89c397df192f239359ad798b96d8e8d552e12c0c189ac5676cec4c20c410d6eec636b8e59a88f2aef0a56d961a9678d99c400099be9b7cae2f7b062eb4b7b171 - languageName: node - linkType: hard - -"typescript@npm:~4.3.2": - version: 4.3.4 - resolution: "typescript@npm:4.3.4" - bin: - tsc: bin/tsc - tsserver: bin/tsserver - checksum: 75e1f2769c7ff38c718523d05eaf1c2611dbf92c0ab0f85f603ead9bb23416af2009a5dac46e76ef6a207a8508fa53f51b43a41f2a91b1241b53cd744c16128c - languageName: node - linkType: hard - -"typescript@npm:~4.9": - version: 4.9.4 - resolution: "typescript@npm:4.9.4" - bin: - tsc: bin/tsc - tsserver: bin/tsserver - checksum: e782fb9e0031cb258a80000f6c13530288c6d63f1177ed43f770533fdc15740d271554cdae86701c1dd2c83b082cea808b07e97fd68b38a172a83dbf9e0d0ef9 - languageName: node - linkType: hard - -"typescript@patch:typescript@4.1.3#~builtin": - version: 4.1.3 - resolution: "typescript@patch:typescript@npm%3A4.1.3#~builtin::version=4.1.3&hash=701156" - bin: - tsc: bin/tsc - tsserver: bin/tsserver - checksum: ed3df76d9b6efb448e5e73bca671698cda353a3807199ad95c78782a857e7685ccc94b799ac885423ced65ee21c87cbbeb823642c5dfd715efae5ebbc6137070 - languageName: node - linkType: hard - -"typescript@patch:typescript@5.2#~builtin, typescript@patch:typescript@^5.0.0#~builtin, typescript@patch:typescript@^5.2.2#~builtin": +"typescript@patch:typescript@npm%3A5.2.2#~builtin": version: 5.2.2 resolution: "typescript@patch:typescript@npm%3A5.2.2#~builtin::version=5.2.2&hash=701156" bin: @@ -28827,66 +28748,6 @@ fsevents@^1.2.7: languageName: node linkType: hard -"typescript@patch:typescript@^4.1.3#~builtin, typescript@patch:typescript@^4.3.4#~builtin": - version: 4.5.2 - resolution: "typescript@patch:typescript@npm%3A4.5.2#~builtin::version=4.5.2&hash=701156" - bin: - tsc: bin/tsc - tsserver: bin/tsserver - checksum: e25e689eba64f7da7cfc43f8ea76cac7176b56caba42655f0a4cb29c0b7c36e67ca54f33df95902859f56108464245d8b45bcdfe21e3d66d9560feb8db780246 - languageName: node - linkType: hard - -"typescript@patch:typescript@^4.8.0#~builtin": - version: 4.8.4 - resolution: "typescript@patch:typescript@npm%3A4.8.4#~builtin::version=4.8.4&hash=701156" - bin: - tsc: bin/tsc - tsserver: bin/tsserver - checksum: 301459fc3eb3b1a38fe91bf96d98eb55da88a9cb17b4ef80b4d105d620f4d547ba776cc27b44cc2ef58b66eda23fe0a74142feb5e79a6fb99f54fc018a696afa - languageName: node - linkType: hard - -"typescript@patch:typescript@^4.9#~builtin": - version: 4.9.5 - resolution: "typescript@patch:typescript@npm%3A4.9.5#~builtin::version=4.9.5&hash=701156" - bin: - tsc: bin/tsc - tsserver: bin/tsserver - checksum: 2eee5c37cad4390385db5db5a8e81470e42e8f1401b0358d7390095d6f681b410f2c4a0c496c6ff9ebd775423c7785cdace7bcdad76c7bee283df3d9718c0f20 - languageName: node - linkType: hard - -"typescript@patch:typescript@~4.2.4#~builtin": - version: 4.2.4 - resolution: "typescript@patch:typescript@npm%3A4.2.4#~builtin::version=4.2.4&hash=701156" - bin: - tsc: bin/tsc - tsserver: bin/tsserver - checksum: eb86e0e8022e5297f7a7b871b6edfbf33b57049416ada8bf97c358760125c7c79f074fbebd1b8e8230f858ae05eb22ad0e805e8f6acd5eae1fa886681624c15e - languageName: node - linkType: hard - -"typescript@patch:typescript@~4.3.2#~builtin": - version: 4.3.4 - resolution: "typescript@patch:typescript@npm%3A4.3.4#~builtin::version=4.3.4&hash=701156" - bin: - tsc: bin/tsc - tsserver: bin/tsserver - checksum: 6ccc2e1148e172da119ea4b72c66395a0c18a53884d21fb82bb4503a948a7169e9961defe24a359040a3d77bf5ff338945804296e0e27c87b5bd22ea1d25781b - languageName: node - linkType: hard - -"typescript@patch:typescript@~4.9#~builtin": - version: 4.9.4 - resolution: "typescript@patch:typescript@npm%3A4.9.4#~builtin::version=4.9.4&hash=701156" - bin: - tsc: bin/tsc - tsserver: bin/tsserver - checksum: 37f6e2c3c5e2aa5934b85b0fddbf32eeac8b1bacf3a5b51d01946936d03f5377fe86255d4e5a4ae628fd0cd553386355ad362c57f13b4635064400f3e8e05b9d - languageName: node - linkType: hard - "ua-parser-js@npm:^0.7.18": version: 0.7.28 resolution: "ua-parser-js@npm:0.7.28" From 32898c46419d5761193ac33f32164716a87f7622 Mon Sep 17 00:00:00 2001 From: Mark Erikson Date: Thu, 23 Nov 2023 17:11:19 -0500 Subject: [PATCH 380/412] Handle graphql-request `signal` types mismatch --- .../rtk-query-graphql-request-base-query/src/index.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/rtk-query-graphql-request-base-query/src/index.ts b/packages/rtk-query-graphql-request-base-query/src/index.ts index 44359ae025..481ed629ac 100644 --- a/packages/rtk-query-graphql-request-base-query/src/index.ts +++ b/packages/rtk-query-graphql-request-base-query/src/index.ts @@ -1,7 +1,7 @@ import { isPlainObject } from '@reduxjs/toolkit' import type { BaseQueryFn } from '@reduxjs/toolkit/query' import type { DocumentNode } from 'graphql' -import { GraphQLClient, ClientError } from 'graphql-request' +import { GraphQLClient, ClientError, RequestOptions } from 'graphql-request' import type { ErrorResponse, GraphqlRequestBaseQueryArgs, @@ -43,7 +43,7 @@ export const graphqlRequestBaseQuery = ( data: await client.request({ document, variables, - signal, + signal: signal as unknown as RequestOptions['signal'], requestHeaders: preparedHeaders, }), meta: {}, @@ -52,10 +52,10 @@ export const graphqlRequestBaseQuery = ( if (error instanceof ClientError) { const { name, message, stack, request, response } = error - const customErrors = - options.customErrors ?? (() => ({ name, message, stack })); + const customErrors = + options.customErrors ?? (() => ({ name, message, stack })) - const customizedErrors = customErrors(error) as E; + const customizedErrors = customErrors(error) as E return { error: customizedErrors, meta: { request, response } } } From 791966b7a31f7218b7c50d44b12eb209c170f9fb Mon Sep 17 00:00:00 2001 From: Ben Durrant Date: Thu, 23 Nov 2023 22:24:16 +0000 Subject: [PATCH 381/412] Update to Redux core v5 rc1 and replace isAction/isPlainObject with core counterparts --- docs/api/matching-utilities.mdx | 11 -------- packages/toolkit/package.json | 2 +- packages/toolkit/src/configureStore.ts | 9 +++++-- packages/toolkit/src/createAction.ts | 14 +---------- packages/toolkit/src/index.ts | 3 --- packages/toolkit/src/isPlainObject.ts | 23 ----------------- .../toolkit/src/listenerMiddleware/index.ts | 3 ++- .../serializableStateInvariantMiddleware.ts | 3 +-- .../toolkit/src/tests/createAction.test.ts | 19 +------------- .../toolkit/src/tests/isPlainObject.test.ts | 25 ------------------- yarn.lock | 17 +++++++------ 11 files changed, 22 insertions(+), 107 deletions(-) delete mode 100644 packages/toolkit/src/isPlainObject.ts delete mode 100644 packages/toolkit/src/tests/isPlainObject.test.ts diff --git a/docs/api/matching-utilities.mdx b/docs/api/matching-utilities.mdx index 1111b9b86a..12808beecc 100644 --- a/docs/api/matching-utilities.mdx +++ b/docs/api/matching-utilities.mdx @@ -13,7 +13,6 @@ Redux Toolkit exports several type-safe action matching utilities that you can l ### General Purpose -- [`isAction`](#isaction) - returns true if a passed value is a standard action object, with a type string - [`isAllOf`](#isallof) - returns true when **all** conditions are met - [`isAnyOf`](#isanyof) - returns true when **at least one of** the conditions are met @@ -27,16 +26,6 @@ All these matchers can either be called with one or more thunks as arguments, in - [`isRejected`](#isrejected) - accepts one or more action creators and returns true when all match - [`isRejectedWithValue`](#isrejectedwithvalue) - accepts one or more action creators and returns true when all match -## `isAction` - -A type guard that takes an unknown variable, and returns `true` if it's a standard Redux action with a string `type` property. - -:::caution - -As of Redux 5.0, action types are _required_ to be a string. - -::: - ## `isAllOf` A higher-order function that accepts one or more of: diff --git a/packages/toolkit/package.json b/packages/toolkit/package.json index 3f9ad74600..9d9ac1378b 100644 --- a/packages/toolkit/package.json +++ b/packages/toolkit/package.json @@ -114,7 +114,7 @@ ], "dependencies": { "immer": "^10.0.3", - "redux": "^5.0.0-rc.0", + "redux": "^5.0.0-rc.1", "redux-thunk": "^3.0.0-rc.0", "reselect": "^5.0.0-beta.1" }, diff --git a/packages/toolkit/src/configureStore.ts b/packages/toolkit/src/configureStore.ts index c155279ac2..d8cdff64d5 100644 --- a/packages/toolkit/src/configureStore.ts +++ b/packages/toolkit/src/configureStore.ts @@ -7,11 +7,16 @@ import type { Store, UnknownAction, } from 'redux' -import { applyMiddleware, createStore, compose, combineReducers } from 'redux' +import { + applyMiddleware, + createStore, + compose, + combineReducers, + isPlainObject, +} from 'redux' import type { DevToolsEnhancerOptions as DevToolsOptions } from './devtoolsExtension' import { composeWithDevTools } from './devtoolsExtension' -import isPlainObject from './isPlainObject' import type { ThunkMiddlewareFor, GetDefaultMiddleware, diff --git a/packages/toolkit/src/createAction.ts b/packages/toolkit/src/createAction.ts index 2f6b1605d3..7e347c0472 100644 --- a/packages/toolkit/src/createAction.ts +++ b/packages/toolkit/src/createAction.ts @@ -1,4 +1,4 @@ -import type { Action } from 'redux' +import { isAction } from 'redux' import type { IsUnknownOrNonInferrable, IfMaybeUndefined, @@ -6,7 +6,6 @@ import type { IsAny, } from './tsHelpers' import { hasMatchFunction } from './tsHelpers' -import isPlainObject from './isPlainObject' /** * An action with a string type and an associated payload. This is the @@ -285,17 +284,6 @@ export function createAction(type: string, prepareAction?: Function): any { return actionCreator } -/** - * Returns true if value is a plain object with a `type` property. - */ -export function isAction(action: unknown): action is Action { - return ( - isPlainObject(action) && - 'type' in action && - typeof (action as Record<'type', unknown>).type === 'string' - ) -} - /** * Returns true if value is an RTK-like action creator, with a static type property and match method. */ diff --git a/packages/toolkit/src/index.ts b/packages/toolkit/src/index.ts index b43d3e8777..787c51e5ef 100644 --- a/packages/toolkit/src/index.ts +++ b/packages/toolkit/src/index.ts @@ -42,7 +42,6 @@ export type { DevToolsEnhancerOptions } from './devtoolsExtension' export { // js createAction, - isAction, isActionCreator, isFSA as isFluxStandardAction, } from './createAction' @@ -157,8 +156,6 @@ export type { export { nanoid } from './nanoid' -export { default as isPlainObject } from './isPlainObject' - export type { ListenerEffect, ListenerMiddleware, diff --git a/packages/toolkit/src/isPlainObject.ts b/packages/toolkit/src/isPlainObject.ts deleted file mode 100644 index 06fb31aa04..0000000000 --- a/packages/toolkit/src/isPlainObject.ts +++ /dev/null @@ -1,23 +0,0 @@ -/** - * Returns true if the passed value is "plain" object, i.e. an object whose - * prototype is the root `Object.prototype`. This includes objects created - * using object literals, but not for instance for class instances. - * - * @param {any} value The value to inspect. - * @returns {boolean} True if the argument appears to be a plain object. - * - * @public - */ -export default function isPlainObject(value: unknown): value is object { - if (typeof value !== 'object' || value === null) return false - - let proto = Object.getPrototypeOf(value) - if (proto === null) return true - - let baseProto = proto - while (Object.getPrototypeOf(baseProto) !== null) { - baseProto = Object.getPrototypeOf(baseProto) - } - - return proto === baseProto -} diff --git a/packages/toolkit/src/listenerMiddleware/index.ts b/packages/toolkit/src/listenerMiddleware/index.ts index 194c3457b2..94f43e4628 100644 --- a/packages/toolkit/src/listenerMiddleware/index.ts +++ b/packages/toolkit/src/listenerMiddleware/index.ts @@ -1,6 +1,7 @@ import type { Action, Dispatch, MiddlewareAPI, UnknownAction } from 'redux' +import { isAction } from 'redux' import type { ThunkDispatch } from 'redux-thunk' -import { createAction, isAction } from '../createAction' +import { createAction } from '../createAction' import { nanoid } from '../nanoid' import type { diff --git a/packages/toolkit/src/serializableStateInvariantMiddleware.ts b/packages/toolkit/src/serializableStateInvariantMiddleware.ts index dc6f5823eb..118d3f50ec 100644 --- a/packages/toolkit/src/serializableStateInvariantMiddleware.ts +++ b/packages/toolkit/src/serializableStateInvariantMiddleware.ts @@ -1,7 +1,6 @@ -import isPlainObject from './isPlainObject' import type { Middleware } from 'redux' +import { isAction, isPlainObject } from 'redux' import { getTimeMeasureUtils } from './utils' -import { isAction } from './createAction' /** * Returns true if the passed value is "plain", i.e. a value that is either diff --git a/packages/toolkit/src/tests/createAction.test.ts b/packages/toolkit/src/tests/createAction.test.ts index 6ee8bcff80..2c4a80e13d 100644 --- a/packages/toolkit/src/tests/createAction.test.ts +++ b/packages/toolkit/src/tests/createAction.test.ts @@ -1,4 +1,4 @@ -import { createAction, isAction, isActionCreator } from '@reduxjs/toolkit' +import { createAction, isActionCreator } from '@reduxjs/toolkit' describe('createAction', () => { it('should create an action', () => { @@ -127,23 +127,6 @@ const actionCreator = createAction('anAction') class Action { type = 'totally an action' } -describe('isAction', () => { - it('should only return true for plain objects with a string type property', () => { - const testCases: [action: unknown, expected: boolean][] = [ - [{ type: 'an action' }, true], - [{ type: 'more props', extra: true }, true], - [{ type: 0 }, false], - [actionCreator(), true], - [actionCreator, false], - [Promise.resolve({ type: 'an action' }), false], - [new Action(), false], - ['a string', false], - ] - for (const [action, expected] of testCases) { - expect(isAction(action)).toBe(expected) - } - }) -}) describe('isActionCreator', () => { it('should only return true for action creators', () => { diff --git a/packages/toolkit/src/tests/isPlainObject.test.ts b/packages/toolkit/src/tests/isPlainObject.test.ts deleted file mode 100644 index 20a4d1839a..0000000000 --- a/packages/toolkit/src/tests/isPlainObject.test.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { isPlainObject } from '@reduxjs/toolkit' -import vm from 'vm' - -describe('isPlainObject', () => { - it('returns true only if plain object', () => { - class Test { - prop: number - constructor() { - this.prop = 1 - } - } - - const sandbox = { fromAnotherRealm: false } - vm.runInNewContext('fromAnotherRealm = {}', sandbox) - - expect(isPlainObject(sandbox.fromAnotherRealm)).toBe(true) - expect(isPlainObject(new Test())).toBe(false) - expect(isPlainObject(new Date())).toBe(false) - expect(isPlainObject([1, 2, 3])).toBe(false) - expect(isPlainObject(null)).toBe(false) - expect(isPlainObject(undefined)).toBe(false) - expect(isPlainObject({ x: 1, y: 2 })).toBe(true) - expect(isPlainObject(Object.create(null))).toBe(true) - }) -}) diff --git a/yarn.lock b/yarn.lock index 1a3d758492..f3266059ac 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7059,7 +7059,7 @@ __metadata: node-fetch: ^2.6.1 prettier: ^2.2.1 query-string: ^7.0.1 - redux: ^5.0.0-rc.0 + redux: ^5.0.0-rc.1 redux-thunk: ^3.0.0-rc.0 reselect: ^5.0.0-beta.1 rimraf: ^3.0.2 @@ -25124,6 +25124,13 @@ fsevents@^1.2.7: languageName: node linkType: hard +"redux@npm:5.0.0-rc.1, redux@npm:^5.0.0-rc.1": + version: 5.0.0-rc.1 + resolution: "redux@npm:5.0.0-rc.1" + checksum: e3a26c5df84fb5705c82e17ca59c19374e3da3e87e5582201ca9b727e2569671bc1f01d738044f34bf38baaa296646a42714207a3dbc83d92ddf186ee0fc21c8 + languageName: node + linkType: hard + "redux@npm:^4.1.2": version: 4.1.2 resolution: "redux@npm:4.1.2" @@ -25142,13 +25149,6 @@ fsevents@^1.2.7: languageName: node linkType: hard -"redux@npm:^5.0.0-rc.0": - version: 5.0.0-rc.0 - resolution: "redux@npm:5.0.0-rc.0" - checksum: b8146301a27b54c2cf49bb5ef2650ddff5218533758c9a8e408141465922aab927be84421b64f8ea7e0ce69faa29b536cd6a1262c399fb86bbe991eed6a4ca88 - languageName: node - linkType: hard - "reftools@npm:^1.1.9": version: 1.1.9 resolution: "reftools@npm:1.1.9" @@ -25972,6 +25972,7 @@ fsevents@^1.2.7: eslint-plugin-react-hooks: ^4.2.0 netlify-plugin-cache: ^1.0.3 prettier: ^2.2.1 + redux: 5.0.0-rc.1 release-it: ^14.12.5 serve: ^14.2.0 languageName: unknown From eeca80a1273c9b7f2203416b508567681f155b3d Mon Sep 17 00:00:00 2001 From: Ben Durrant Date: Thu, 23 Nov 2023 22:26:26 +0000 Subject: [PATCH 382/412] lockfile --- yarn.lock | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/yarn.lock b/yarn.lock index f3266059ac..03dba8ecdd 100644 --- a/yarn.lock +++ b/yarn.lock @@ -25124,13 +25124,6 @@ fsevents@^1.2.7: languageName: node linkType: hard -"redux@npm:5.0.0-rc.1, redux@npm:^5.0.0-rc.1": - version: 5.0.0-rc.1 - resolution: "redux@npm:5.0.0-rc.1" - checksum: e3a26c5df84fb5705c82e17ca59c19374e3da3e87e5582201ca9b727e2569671bc1f01d738044f34bf38baaa296646a42714207a3dbc83d92ddf186ee0fc21c8 - languageName: node - linkType: hard - "redux@npm:^4.1.2": version: 4.1.2 resolution: "redux@npm:4.1.2" @@ -25149,6 +25142,13 @@ fsevents@^1.2.7: languageName: node linkType: hard +"redux@npm:^5.0.0-rc.1": + version: 5.0.0-rc.1 + resolution: "redux@npm:5.0.0-rc.1" + checksum: e3a26c5df84fb5705c82e17ca59c19374e3da3e87e5582201ca9b727e2569671bc1f01d738044f34bf38baaa296646a42714207a3dbc83d92ddf186ee0fc21c8 + languageName: node + linkType: hard + "reftools@npm:^1.1.9": version: 1.1.9 resolution: "reftools@npm:1.1.9" @@ -25972,7 +25972,6 @@ fsevents@^1.2.7: eslint-plugin-react-hooks: ^4.2.0 netlify-plugin-cache: ^1.0.3 prettier: ^2.2.1 - redux: 5.0.0-rc.1 release-it: ^14.12.5 serve: ^14.2.0 languageName: unknown From ac93d46c971905f953339944930e29908dcf9e6a Mon Sep 17 00:00:00 2001 From: Mark Erikson Date: Thu, 23 Nov 2023 17:36:21 -0500 Subject: [PATCH 383/412] Drop "ESM modern dev" build and rename "ESM prod" to "browser" --- packages/toolkit/tsup.config.ts | 15 +++------------ 1 file changed, 3 insertions(+), 12 deletions(-) diff --git a/packages/toolkit/tsup.config.ts b/packages/toolkit/tsup.config.ts index c76525e1e3..3b1f3ff417 100644 --- a/packages/toolkit/tsup.config.ts +++ b/packages/toolkit/tsup.config.ts @@ -16,14 +16,13 @@ const __dirname = path.dirname(__filename) const outputDir = path.join(__dirname, 'dist') export interface BuildOptions { - format: 'cjs' | 'umd' | 'esm' + format: 'cjs' | 'esm' name: | 'development' | 'production.min' | 'legacy-esm' | 'modern' - | 'modern.development' - | 'modern.production.min' + | 'browser' | 'umd' | 'umd.min' minify: boolean @@ -78,18 +77,10 @@ const buildTargets: BuildOptions[] = [ minify: false, env: '', }, - // ESM, pre-compiled "dev": browser development - { - format: 'esm', - name: 'modern.development', - target: 'esnext', - minify: false, - env: 'development', - }, // ESM, pre-compiled "prod": browser prod { format: 'esm', - name: 'modern.production.min', + name: 'browser', target: 'esnext', minify: true, env: 'production', From 53803a59ae7a34478ef0bff2564d9c22df52b919 Mon Sep 17 00:00:00 2001 From: Ben Durrant Date: Thu, 23 Nov 2023 23:19:25 +0000 Subject: [PATCH 384/412] fix isFSA import and avoid unnecessary type param --- .../tests/listenerMiddleware.test.ts | 10 +++++++--- packages/toolkit/src/listenerMiddleware/types.ts | 8 +++----- 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/packages/toolkit/src/listenerMiddleware/tests/listenerMiddleware.test.ts b/packages/toolkit/src/listenerMiddleware/tests/listenerMiddleware.test.ts index 5cf5870110..78bc9dc56c 100644 --- a/packages/toolkit/src/listenerMiddleware/tests/listenerMiddleware.test.ts +++ b/packages/toolkit/src/listenerMiddleware/tests/listenerMiddleware.test.ts @@ -3,7 +3,7 @@ import { createAction, createSlice, isAnyOf, - isFSA, + isFluxStandardAction, } from '@reduxjs/toolkit' import type { Mock } from 'vitest' import { vi } from 'vitest' @@ -1521,7 +1521,9 @@ describe('createListenerMiddleware', () => { currentState, previousState ): action is PayloadAction => { - return isFSA(action) && typeof action.payload === 'boolean' + return ( + isFluxStandardAction(action) && typeof action.payload === 'boolean' + ) }, effect: (action, listenerApi) => { expectExactType>(action) @@ -1530,7 +1532,9 @@ describe('createListenerMiddleware', () => { startListening({ predicate: (action, currentState) => { - return isFSA(action) && typeof action.payload === 'number' + return ( + isFluxStandardAction(action) && typeof action.payload === 'number' + ) }, effect: (action, listenerApi) => { expectExactType(action) diff --git a/packages/toolkit/src/listenerMiddleware/types.ts b/packages/toolkit/src/listenerMiddleware/types.ts index 7191b59e4a..6992275dd0 100644 --- a/packages/toolkit/src/listenerMiddleware/types.ts +++ b/packages/toolkit/src/listenerMiddleware/types.ts @@ -8,6 +8,7 @@ import type { } from 'redux' import type { ThunkDispatch } from 'redux-thunk' import type { TaskAbortError } from './exceptions' +import { NoInfer } from '../tsHelpers' /** * @internal @@ -444,7 +445,7 @@ export interface AddListenerOverloads< ): Return /** Accepts an RTK matcher function, such as `incrementByAmount.match` */ - >( + >( options: { actionCreator?: never type?: never @@ -581,10 +582,7 @@ export type FallbackAddListenerOptions = { */ /** @public */ -export type GuardedType = T extends ( - x: any, - ...args: unknown[] -) => x is infer T +export type GuardedType = T extends (x: any, ...args: any[]) => x is infer T ? T : never From 97ae013c527551d9cff2a5a1a2f8ac02a90373e3 Mon Sep 17 00:00:00 2001 From: Mark Erikson Date: Fri, 24 Nov 2023 11:57:43 -0500 Subject: [PATCH 385/412] Release 2.0.0-rc.1 --- packages/toolkit/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/toolkit/package.json b/packages/toolkit/package.json index 145a5eb340..5e58059762 100644 --- a/packages/toolkit/package.json +++ b/packages/toolkit/package.json @@ -1,6 +1,6 @@ { "name": "@reduxjs/toolkit", - "version": "2.0.0-rc.0", + "version": "2.0.0-rc.1", "description": "The official, opinionated, batteries-included toolset for efficient Redux development", "author": "Mark Erikson ", "license": "MIT", From 6a09010367e25a09770736b54c0a553843fd2bff Mon Sep 17 00:00:00 2001 From: Mark Erikson Date: Fri, 24 Nov 2023 13:14:09 -0500 Subject: [PATCH 386/412] Fix assorted docs tweaks --- docs/api/getDefaultMiddleware.mdx | 2 +- docs/migrations/1.x-to-2.x.md | 4 ++-- docs/rtk-query/usage/customizing-queries.mdx | 10 ++++++++-- 3 files changed, 11 insertions(+), 5 deletions(-) diff --git a/docs/api/getDefaultMiddleware.mdx b/docs/api/getDefaultMiddleware.mdx index ef15eca14f..7e51cf618f 100644 --- a/docs/api/getDefaultMiddleware.mdx +++ b/docs/api/getDefaultMiddleware.mdx @@ -55,7 +55,7 @@ const store = configureStore({ // Store has all of the default middleware added, _plus_ the logger middleware ``` -It is preferable to use the chainable `.concat(...)` and `.prepend(...)` methods of the returned `Tuple` instead of the array spread operator, as the latter can lose valuable type information under some circumstances. +It is preferable to use the chainable `.concat(...)` and `.prepend(...)` methods of the returned `Tuple` instead of the array spread operator, as the latter can lose valuable TS type information under some circumstances. ## Included Default Middleware diff --git a/docs/migrations/1.x-to-2.x.md b/docs/migrations/1.x-to-2.x.md index e98b2f5bc9..434f44e61f 100644 --- a/docs/migrations/1.x-to-2.x.md +++ b/docs/migrations/1.x-to-2.x.md @@ -499,11 +499,11 @@ In practice, we hope these are reasonable tradeoffs. Creating thunks inside of ` Here's what the new callback syntax looks like: ```ts -const createSlice = buildCreateSlice({ +const createSliceWithThunks = buildCreateSlice({ creators: { asyncThunk: asyncThunkCreator }, }) -const todosSlice = createSlice({ +const todosSlice = createSliceWithThunks({ name: 'todos', initialState: { loading: false, diff --git a/docs/rtk-query/usage/customizing-queries.mdx b/docs/rtk-query/usage/customizing-queries.mdx index 25964af012..8734cc8d2e 100644 --- a/docs/rtk-query/usage/customizing-queries.mdx +++ b/docs/rtk-query/usage/customizing-queries.mdx @@ -376,7 +376,13 @@ const axiosBaseQuery = > => async ({ url, method, data, params, headers }) => { try { - const result = await axios({ url: baseUrl + url, method, data, params, headers }) + const result = await axios({ + url: baseUrl + url, + method, + data, + params, + headers, + }) return { data: result.data } } catch (axiosError) { const err = axiosError as AxiosError @@ -608,7 +614,7 @@ The `retry` utility has a `fail` method property attached which can be used to b ```ts title="Bailing out of error re-tries" import { createApi, fetchBaseQuery, retry } from '@reduxjs/toolkit/query/react' -import type { FetchArgs } from '@reduxjs/toolkit/dist/query/fetchBaseQuery' +import type { FetchArgs } from '@reduxjs/toolkit/query' interface Post { id: number name: string From 436834fbd2af3706a20377575798a84cf7473a03 Mon Sep 17 00:00:00 2001 From: Mark Erikson Date: Fri, 24 Nov 2023 13:14:35 -0500 Subject: [PATCH 387/412] Document casting `getState` --- docs/api/createSlice.mdx | 19 ++++++-- docs/rtk-query/usage-with-typescript.mdx | 26 ++++++++++ docs/usage/usage-with-typescript.md | 60 +++++++++++++++++++++++- 3 files changed, 98 insertions(+), 7 deletions(-) diff --git a/docs/api/createSlice.mdx b/docs/api/createSlice.mdx index d6b4269764..53b01a1e4a 100644 --- a/docs/api/createSlice.mdx +++ b/docs/api/createSlice.mdx @@ -304,16 +304,25 @@ Typing for the `create.asyncThunk` works in the same way as [`createAsyncThunk`] A type for `state` and/or `dispatch` _cannot_ be provided as part of the `ThunkApiConfig`, as this would cause circular types. -Instead, it is necessary to assert the type when needed. +Instead, it is necessary to assert the type when needed - `getState() as RootState`. You may also include an explicit return type for the payload function as well, in order to break the circular type inference cycle. ```ts no-transpile create.asyncThunk( - async (id, thunkApi) => { + // highlight-start + // may need to include an explicit return type + async (id: string, thunkApi): Promise => { + // Cast types for `getState` and `dispatch` manually const state = thunkApi.getState() as RootState const dispatch = thunkApi.dispatch as AppDispatch - throw thunkApi.rejectWithValue({ - error: 'Oh no!', - }) + // highlight-end + try { + const todo = await fetchTodo() + return todo + } catch (e) { + throw thunkApi.rejectWithValue({ + error: 'Oh no!', + }) + } } ) ``` diff --git a/docs/rtk-query/usage-with-typescript.mdx b/docs/rtk-query/usage-with-typescript.mdx index 7107b83ab3..366da13818 100644 --- a/docs/rtk-query/usage-with-typescript.mdx +++ b/docs/rtk-query/usage-with-typescript.mdx @@ -390,6 +390,32 @@ const api = createApi({ }) ``` +### Typing `dispatch` and `getState` + +`createApi` exposes the standard Redux `dispatch` and `getState` methods in several places, such as the `lifecycleApi` argument in lifecycle methods, or the `baseQueryApi` argument passed to `queryFn` methods and base query functions. + +Normally, [your application infers `RootState` and `AppDispatch` types from the store setup](../tutorials/typescript.md#define-root-state-and-dispatch-types). Since `createApi` has to be called prior to creating the Redux store and is used as part of the store setup sequence, it can't directly know or use those types - it would cause a circular type inference error. + +By default, `dispatch` usages inside of `createApi` will be typed as `ThunkDispatch`, and `getState` usages are typed as `() => unknown`. You will need to assert the type when needed - `getState() as RootState`. You may also include an explicit return type for the function as well, in order to break the circular type inference cycle: + +```ts no-transpile +const api = createApi({ + baseQuery, + endpoints: (build) => ({ + getTodos: build.query({ + async queryFn() { + // highlight-start + // Cast state as `RootState` + const state = getState() as RootState + // highlight-end + const text = state.todoTexts[queryFnCalls] + return { data: [{ id: `${queryFnCalls++}`, text }] } + }, + }), + }), +}) +``` + ### Typing `providesTags`/`invalidatesTags` RTK Query utilizes a cache tag invalidation system in order to provide [automated re-fetching](./usage/automated-refetching.mdx) of stale data. diff --git a/docs/usage/usage-with-typescript.md b/docs/usage/usage-with-typescript.md index 73734f605d..c3e826b82d 100644 --- a/docs/usage/usage-with-typescript.md +++ b/docs/usage/usage-with-typescript.md @@ -326,9 +326,9 @@ const blogSlice = createSlice({ ### Generated Action Types for Slices -As TS cannot combine two string literals (`slice.name` and the key of `actionMap`) into a new literal, all actionCreators created by `createSlice` are of type 'string'. This is usually not a problem, as these types are only rarely used as literals. +`createSlice` generates action type strings by combining the `name` field from the slice with the field name of the reducer function, like `'test/increment'`. This is strongly typed as the exact value, thanks to TS's string literal analysis. -In most cases that `type` would be required as a literal, the `slice.action.myAction.match` [type predicate](https://www.typescriptlang.org/docs/handbook/advanced-types.html#using-type-predicates) should be a viable alternative: +You can also use the `slice.action.myAction.match` [type predicate](https://www.typescriptlang.org/docs/handbook/advanced-types.html#using-type-predicates) should be a viable alternative: ```ts {10} const slice = createSlice({ @@ -339,6 +339,9 @@ const slice = createSlice({ }, }) +type incrementType = typeof slice.actions.increment.type +// type incrementType = 'test/increment' + function myCustomMiddleware(action: Action) { if (slice.actions.increment.match(action)) { // `action` is narrowed down to the type `PayloadAction` here. @@ -408,6 +411,59 @@ type AtLeastOne> = keyof T extends infer K type AtLeastOneUserField = AtLeastOne ``` +### Typing Async Thunks Inside `createSlice` + +As of 2.0, `createSlice` allows [defining thunks inside of `reducers` using a callback syntax](../api/createSlice.mdx/#the-reducers-creator-callback-notation). + +Typing for the `create.asyncThunk` method works in the same way as [`createAsyncThunk`](#createasyncthunk), with one key difference. + +A type for `state` and/or `dispatch` _cannot_ be provided as part of the `ThunkApiConfig`, as this would cause circular types. + +Instead, it is necessary to assert the type when needed - `getState() as RootState`. You may also include an explicit return type for the payload function as well, in order to break the circular type inference cycle. + +```ts no-transpile +create.asyncThunk( + // highlight-start + // may need to include an explicit return type + async (id: string, thunkApi): Promise => { + // Cast types for `getState` and `dispatch` manually + const state = thunkApi.getState() as RootState + const dispatch = thunkApi.dispatch as AppDispatch + // highlight-end + try { + const todo = await fetchTodo() + return todo + } catch (e) { + throw thunkApi.rejectWithValue({ + error: 'Oh no!', + }) + } + } +) +``` + +For common thunk API configuration options, a [`withTypes` helper](../usage/usage-with-typescript#defining-a-pre-typed-createasyncthunk) is provided: + +```ts no-transpile +reducers: (create) => { + const createAThunk = + create.asyncThunk.withTypes<{ rejectValue: { error: string } }>() + + return { + fetchTodo: createAThunk(async (id, thunkApi) => { + throw thunkApi.rejectWithValue({ + error: 'Oh no!', + }) + }), + fetchTodos: createAThunk(async (id, thunkApi) => { + throw thunkApi.rejectWithValue({ + error: 'Oh no, not again!', + }) + }), + } +} +``` + ### Wrapping `createSlice` If you need to reuse reducer logic, it is common to write ["higher-order reducers"](https://redux.js.org/recipes/structuring-reducers/reusing-reducer-logic#customizing-behavior-with-higher-order-reducers) that wrap a reducer function with additional common behavior. This can be done with `createSlice` as well, but due to the complexity of the types for `createSlice`, you have to use the `SliceCaseReducers` and `ValidateSliceCaseReducers` types in a very specific way. From a631974b40fbb544b790257eb23a1f5db659c52b Mon Sep 17 00:00:00 2001 From: Mark Erikson Date: Fri, 24 Nov 2023 13:46:29 -0500 Subject: [PATCH 388/412] Rewrite immutable middleware to enable error extraction --- .../src/immutableStateInvariantMiddleware.ts | 43 +++++-------------- 1 file changed, 11 insertions(+), 32 deletions(-) diff --git a/packages/toolkit/src/immutableStateInvariantMiddleware.ts b/packages/toolkit/src/immutableStateInvariantMiddleware.ts index 3d63f992f4..90f3a8ebe1 100644 --- a/packages/toolkit/src/immutableStateInvariantMiddleware.ts +++ b/packages/toolkit/src/immutableStateInvariantMiddleware.ts @@ -3,28 +3,6 @@ import { getTimeMeasureUtils } from './utils' type EntryProcessor = (key: string, value: any) => any -const isProduction: boolean = process.env.NODE_ENV === 'production' -const prefix: string = 'Invariant failed' - -// Throw an error if the condition fails -// Strip out error messages for production -// > Not providing an inline default argument for message as the result is smaller -function invariant(condition: any, message?: string) { - if (condition) { - return - } - // Condition not passed - - // In production we strip the message but still throw - if (isProduction) { - throw new Error(prefix) - } - - // When not in production we allow the message to pass through - // *This block will be removed in production builds* - throw new Error(`${prefix}: ${message || ''}`) -} - /** * The default `isImmutable` function. * @@ -62,7 +40,7 @@ function trackProperties( const tracked: Partial = { value: obj } if (!isImmutable(obj) && !checkedObjects.has(obj)) { - checkedObjects.add(obj); + checkedObjects.add(obj) tracked.children = {} for (const key in obj) { @@ -248,12 +226,13 @@ export function createImmutableStateInvariantMiddleware( // Track before potentially not meeting the invariant tracker = track(state) - invariant( - !result.wasMutated, - `A state mutation was detected between dispatches, in the path '${ - result.path || '' - }'. This may cause incorrect behavior. (https://redux.js.org/style-guide/style-guide#do-not-mutate-state)` - ) + if (result.wasMutated) { + throw new Error( + `A state mutation was detected between dispatches, in the path '${ + result.path || '' + }'. This may cause incorrect behavior. (https://redux.js.org/style-guide/style-guide#do-not-mutate-state)` + ) + } }) const dispatchedAction = next(action) @@ -265,15 +244,15 @@ export function createImmutableStateInvariantMiddleware( // Track before potentially not meeting the invariant tracker = track(state) - result.wasMutated && - invariant( - !result.wasMutated, + if (result.wasMutated) { + throw new Error( `A state mutation was detected inside a dispatch, in the path: ${ result.path || '' }. Take a look at the reducer(s) handling the action ${stringify( action )}. (https://redux.js.org/style-guide/style-guide#do-not-mutate-state)` ) + } }) measureUtils.warnIfExceeded() From 2510a3222438d6af49b20e82ec8a5e5fcd3263dc Mon Sep 17 00:00:00 2001 From: Mark Erikson Date: Fri, 24 Nov 2023 13:46:47 -0500 Subject: [PATCH 389/412] Regenerate errors.json file --- errors.json | 79 ++++++++++++++++++++++++++--------------------------- 1 file changed, 39 insertions(+), 40 deletions(-) diff --git a/errors.json b/errors.json index d0a9d06a44..217753817d 100644 --- a/errors.json +++ b/errors.json @@ -1,42 +1,41 @@ { - "0": "The object notation for `createReducer` has been removed. Please use the 'builder callback' notation instead: https://redux-toolkit.js.org/api/createReducer", - "1": "A case reducer on a non-draftable value must not return undefined", - "2": ": ", - "3": "prepareAction did not return an object", - "4": "\"reducer\" is a required argument, and must be a function or an object of functions that can be passed to combineReducers", - "5": "when using a middleware builder function, an array of middleware must be returned", - "6": "each middleware provided to configureStore must be a function", - "7": "\"enhancers\" field must be a callback", - "8": "\"enhancers\" callback must return an array", - "9": "each enhancer provided to configureStore must be a function", - "10": "`name` is a required option for createSlice", - "11": "The object notation for `createSlice.extraReducers` has been removed. Please use the 'builder callback' notation instead: https://redux-toolkit.js.org/api/createSlice", - "12": "selectState returned undefined for an uninjected slice reducer", - "13": "Please use the `create.preparedReducer` notation for prepared action creators with the `create` notation.", - "14": "The slice reducer for key \"\" returned undefined when called for selector(). If the state passed to the reducer is undefined, you must explicitly return the initial state. The initial state may not be undefined. If you don't want to set a value for this reducer, you can use null instead of undefined.", - "15": "original must be used on state Proxy", - "16": "Creating or removing a listener requires one of the known fields for matching an action", - "17": "Unsubscribe not initialized", - "18": ": getOriginalState can only be called synchronously", - "19": "`builder.addCase` should only be called before calling `builder.addMatcher`", - "20": "`builder.addCase` should only be called before calling `builder.addDefaultCase`", - "21": "addCase cannot be called with two reducers for the same action type", - "22": "`builder.addMatcher` should only be called before calling `builder.addDefaultCase`", - "23": "`builder.addDefaultCase` can only be called once", - "24": " is not a function", - "25": "When using `fakeBaseQuery`, all queries & mutations must use the `queryFn` definition syntax.", - "26": "Warning: Middleware for RTK-Query API at reducerPath \"\" has not been added to the store.\nYou must add the middleware for RTK-Query to function correctly!", - "27": "Warning: Middleware for RTK-Query API at reducerPath \"\" has not been added to the store.\n You must add the middleware for RTK-Query to function correctly!", - "28": "Cannot refetch a query that has not been started yet.", - "29": "`builder.addCase` cannot be called with an empty action type", - "30": "`builder.addCase` cannot be called with two reducers for the same action type", - "31": "\"middleware\" field must be a callback", - "32": "When using custom hooks for context, all hooks need to be provided: .\\nHook was either not provided or not a function.", - "33": "Existing Redux context detected. If you already have a store set up, please use the traditional Redux setup.", - "34": "selectSlice returned undefined for an uninjected slice reducer", - "35": "Cannot use `create.asyncThunk` in the built-in `createSlice`. Use `buildCreateSlice({ creators: { asyncThunk: asyncThunkCreator } })` to create a customised version of `createSlice`.", - "36": "`context.addCase` cannot be called with an empty action type", - "37": "`context.addCase` cannot be called with two reducers for the same action type: type", - "38": "No insert provided for key not already in map", - "39": "\\`builder.addCase\\` cannot be called with two reducers for the same action type ''" + "0": "prepareAction did not return an object", + "1": "\"reducer\" is a required argument, and must be a function or an object of functions that can be passed to combineReducers", + "2": "\"middleware\" field must be a callback", + "3": "when using a middleware builder function, an array of middleware must be returned", + "4": "each middleware provided to configureStore must be a function", + "5": "\"enhancers\" field must be a callback", + "6": "\"enhancers\" callback must return an array", + "7": "each enhancer provided to configureStore must be a function", + "8": "The object notation for `createReducer` has been removed. Please use the 'builder callback' notation instead: https://redux-toolkit.js.org/api/createReducer", + "9": "A case reducer on a non-draftable value must not return undefined", + "10": "No insert provided for key not already in map", + "11": "`name` is a required option for createSlice", + "12": "`context.addCase` cannot be called with an empty action type", + "13": "`context.addCase` cannot be called with two reducers for the same action type: type", + "14": "The object notation for `createSlice.extraReducers` has been removed. Please use the 'builder callback' notation instead: https://redux-toolkit.js.org/api/createSlice", + "15": "selectSlice returned undefined for an uninjected slice reducer", + "16": "selectState returned undefined for an uninjected slice reducer", + "17": "Please use the `create.preparedReducer` notation for prepared action creators with the `create` notation.", + "18": "Cannot use `create.asyncThunk` in the built-in `createSlice`. Use `buildCreateSlice({ creators: { asyncThunk: asyncThunkCreator } })` to create a customised version of `createSlice`.", + "19": "A state mutation was detected between dispatches, in the path ''. This may cause incorrect behavior. (https://redux.js.org/style-guide/style-guide#do-not-mutate-state)", + "20": "A state mutation was detected inside a dispatch, in the path: . Take a look at the reducer(s) handling the action . (https://redux.js.org/style-guide/style-guide#do-not-mutate-state)", + "21": "Creating or removing a listener requires one of the known fields for matching an action", + "22": "Unsubscribe not initialized", + "23": ": getOriginalState can only be called synchronously", + "24": "The slice reducer for key \"\" returned undefined when called for selector(). If the state passed to the reducer is undefined, you must explicitly return the initial state. The initial state may not be undefined. If you don't want to set a value for this reducer, you can use null instead of undefined.", + "25": "original must be used on state Proxy", + "26": "`builder.addCase` should only be called before calling `builder.addMatcher`", + "27": "`builder.addCase` should only be called before calling `builder.addDefaultCase`", + "28": "`builder.addCase` cannot be called with an empty action type", + "29": "\\`builder.addCase\\` cannot be called with two reducers for the same action type ''", + "30": "`builder.addMatcher` should only be called before calling `builder.addDefaultCase`", + "31": "`builder.addDefaultCase` can only be called once", + "32": " is not a function", + "33": "When using `fakeBaseQuery`, all queries & mutations must use the `queryFn` definition syntax.", + "34": "Warning: Middleware for RTK-Query API at reducerPath \"\" has not been added to the store.\nYou must add the middleware for RTK-Query to function correctly!", + "35": "Existing Redux context detected. If you already have a store set up, please use the traditional Redux setup.", + "36": "When using custom hooks for context, all hooks need to be provided: .\\nHook was either not provided or not a function.", + "37": "Warning: Middleware for RTK-Query API at reducerPath \"\" has not been added to the store.\n You must add the middleware for RTK-Query to function correctly!", + "38": "Cannot refetch a query that has not been started yet." } \ No newline at end of file From cf6a2e94db2a428716df1afe560500d16bb24954 Mon Sep 17 00:00:00 2001 From: Mark Erikson Date: Fri, 24 Nov 2023 13:57:14 -0500 Subject: [PATCH 390/412] Make backtick usages in error messages consistent --- errors.json | 12 ++++++------ packages/toolkit/src/configureStore.ts | 8 ++++---- packages/toolkit/src/mapBuilders.ts | 3 ++- 3 files changed, 12 insertions(+), 11 deletions(-) diff --git a/errors.json b/errors.json index 217753817d..ac6286a866 100644 --- a/errors.json +++ b/errors.json @@ -1,11 +1,11 @@ { "0": "prepareAction did not return an object", - "1": "\"reducer\" is a required argument, and must be a function or an object of functions that can be passed to combineReducers", - "2": "\"middleware\" field must be a callback", + "1": "`reducer` is a required argument, and must be a function or an object of functions that can be passed to combineReducers", + "2": "`middleware` field must be a callback", "3": "when using a middleware builder function, an array of middleware must be returned", "4": "each middleware provided to configureStore must be a function", - "5": "\"enhancers\" field must be a callback", - "6": "\"enhancers\" callback must return an array", + "5": "`enhancers` field must be a callback", + "6": "`enhancers` callback must return an array", "7": "each enhancer provided to configureStore must be a function", "8": "The object notation for `createReducer` has been removed. Please use the 'builder callback' notation instead: https://redux-toolkit.js.org/api/createReducer", "9": "A case reducer on a non-draftable value must not return undefined", @@ -28,7 +28,7 @@ "26": "`builder.addCase` should only be called before calling `builder.addMatcher`", "27": "`builder.addCase` should only be called before calling `builder.addDefaultCase`", "28": "`builder.addCase` cannot be called with an empty action type", - "29": "\\`builder.addCase\\` cannot be called with two reducers for the same action type ''", + "29": "`builder.addCase` cannot be called with two reducers for the same action type ''", "30": "`builder.addMatcher` should only be called before calling `builder.addDefaultCase`", "31": "`builder.addDefaultCase` can only be called once", "32": " is not a function", @@ -38,4 +38,4 @@ "36": "When using custom hooks for context, all hooks need to be provided: .\\nHook was either not provided or not a function.", "37": "Warning: Middleware for RTK-Query API at reducerPath \"\" has not been added to the store.\n You must add the middleware for RTK-Query to function correctly!", "38": "Cannot refetch a query that has not been started yet." -} \ No newline at end of file +} diff --git a/packages/toolkit/src/configureStore.ts b/packages/toolkit/src/configureStore.ts index d8cdff64d5..93bf59017a 100644 --- a/packages/toolkit/src/configureStore.ts +++ b/packages/toolkit/src/configureStore.ts @@ -140,12 +140,12 @@ export function configureStore< rootReducer = combineReducers(reducer) as unknown as Reducer } else { throw new Error( - '"reducer" is a required argument, and must be a function or an object of functions that can be passed to combineReducers' + '`reducer` is a required argument, and must be a function or an object of functions that can be passed to combineReducers' ) } if (!IS_PRODUCTION && middleware && typeof middleware !== 'function') { - throw new Error('"middleware" field must be a callback') + throw new Error('`middleware` field must be a callback') } let finalMiddleware: Tuple> @@ -184,7 +184,7 @@ export function configureStore< const getDefaultEnhancers = buildGetDefaultEnhancers(middlewareEnhancer) if (!IS_PRODUCTION && enhancers && typeof enhancers !== 'function') { - throw new Error('"enhancers" field must be a callback') + throw new Error('`enhancers` field must be a callback') } let storeEnhancers = @@ -193,7 +193,7 @@ export function configureStore< : getDefaultEnhancers() if (!IS_PRODUCTION && !Array.isArray(storeEnhancers)) { - throw new Error('"enhancers" callback must return an array') + throw new Error('`enhancers` callback must return an array') } if ( !IS_PRODUCTION && diff --git a/packages/toolkit/src/mapBuilders.ts b/packages/toolkit/src/mapBuilders.ts index f381b90418..5c6ac60010 100644 --- a/packages/toolkit/src/mapBuilders.ts +++ b/packages/toolkit/src/mapBuilders.ts @@ -166,7 +166,8 @@ export function executeReducerBuilderCallback( } if (type in actionsMap) { throw new Error( - `\`builder.addCase\` cannot be called with two reducers for the same action type '${type}'` + '`builder.addCase` cannot be called with two reducers for the same action type ' + + `'${type}'` ) } actionsMap[type] = reducer From 1d4d39442de5e2cb02d0e7dbcdf3f38a1c9a2daf Mon Sep 17 00:00:00 2001 From: Mark Erikson Date: Fri, 24 Nov 2023 13:57:29 -0500 Subject: [PATCH 391/412] Add error message page --- website/sidebars.json | 3 +- website/src/pages/errors.js | 61 +++++++++++++++++++++++++++++ website/src/pages/styles.module.css | 29 ++++++++++++++ 3 files changed, 92 insertions(+), 1 deletion(-) create mode 100644 website/src/pages/errors.js diff --git a/website/sidebars.json b/website/sidebars.json index 6854804533..6119cb9962 100644 --- a/website/sidebars.json +++ b/website/sidebars.json @@ -75,7 +75,8 @@ "api/createSelector", "api/matching-utilities", "api/other-exports", - "api/codemods" + "api/codemods", + { "type": "link", "label": "Error Messages", "href": "/errors" } ] } ] diff --git a/website/src/pages/errors.js b/website/src/pages/errors.js new file mode 100644 index 0000000000..17b86e42b9 --- /dev/null +++ b/website/src/pages/errors.js @@ -0,0 +1,61 @@ +import React from 'react' +import Layout from '@theme/Layout' +import { useLocation } from '@docusaurus/router' +import useDocusaurusContext from '@docusaurus/useDocusaurusContext' +import styles from './styles.module.css' +import errorCodes from '../../../errors.json' + +function Errors() { + const location = useLocation() + const context = useDocusaurusContext() + const { siteConfig = {} } = context + const errorCode = new URLSearchParams(location.search).get('code') + const error = errorCodes[errorCode] + + return ( + +
+

Production Error Codes

+

+ When Redux Toolkit is built and running in production, error text is + replaced by indexed error codes to save on bundle size. These errors + will provide a link to this page with more information about the error + below. +

+ {error && ( + +

+ + The full text of the error you just encountered is: + +

+ {error} +
+ )} + +

All Error Codes

+ + + + + + + + + {Object.keys(errorCodes).map((code) => ( + + + + + ))} + +
CodeMessage
{code}{errorCodes[code]}
+
+
+ ) +} + +export default Errors diff --git a/website/src/pages/styles.module.css b/website/src/pages/styles.module.css index 94ab9c7514..e8184048bd 100644 --- a/website/src/pages/styles.module.css +++ b/website/src/pages/styles.module.css @@ -80,3 +80,32 @@ font-size: 30px; margin-bottom: 55px; } + +.mainFull { + padding: 34px 16px; + width: 100%; + max-width: var(--ifm-container-width); + margin: 0px auto; +} +.mainFull h1 { + font-size: 3rem; + color: var(--ifm-heading-color); + font-weight: var(--ifm-heading-font-weight); + line-height: var(--ifm-heading-line-height); +} + +.mainFull p { + margin-bottom: var(--ifm-leading); + margin-top: 0; +} + + +.errorDetails { + color: #ff6464; + border-radius: 0.5rem; + padding: 1rem; + margin: 30px 0; + font-weight: bold; + background-color: rgba(255, 100, 100, 0.1); + display: block; +} From f90c117dd433a01738f0a2972576291c2d008644 Mon Sep 17 00:00:00 2001 From: Mark Erikson Date: Fri, 24 Nov 2023 14:45:54 -0500 Subject: [PATCH 392/412] Update README, install instructions, docs mentions, and add `createStore` deprecation --- docs/introduction/getting-started.md | 5 +-- docs/migrations/1.x-to-2.x.md | 66 +++++++++++++++++++--------- packages/toolkit/README.md | 39 +++++++++------- 3 files changed, 71 insertions(+), 39 deletions(-) diff --git a/docs/introduction/getting-started.md b/docs/introduction/getting-started.md index 9bb4cfc038..8085db8f3e 100644 --- a/docs/introduction/getting-started.md +++ b/docs/introduction/getting-started.md @@ -34,7 +34,7 @@ you make your Redux code better. ### Create a React Redux App -The recommended way to start new apps with React and Redux is by using [our official Redux+TS template for Vite](https://github.com/reduxjs/redux-templates), or by creating a new Next.js project using [Next's `with-redux` template](https://github.com/vercel/next.js/tree/canary/examples/with-redux). +The recommended way to start new apps with React and Redux Toolkit is by using [our official Redux Toolkit + TS template for Vite](https://github.com/reduxjs/redux-templates), or by creating a new Next.js project using [Next's `with-redux` template](https://github.com/vercel/next.js/tree/canary/examples/with-redux). Both of these already have Redux Toolkit and React-Redux configured appropriately for that build tool, and come with a small example app that demonstrates how to use several of Redux Toolkit's features. @@ -85,8 +85,7 @@ yarn add react-redux -It is also available as a precompiled UMD package that defines a `window.RTK` global variable. -The UMD package can be used as a [`