From a195867fdbda593cb2a7d3260731a8f249a5a4a4 Mon Sep 17 00:00:00 2001 From: Lenz Weber Date: Sun, 25 Sep 2022 19:57:24 +0200 Subject: [PATCH 1/2] split `enhanceEndpoints` into `addTagTypes` and `enhanceEndpoint` --- packages/toolkit/src/query/apiTypes.ts | 78 ++++++- .../src/query/tests/codeSplitting.test.ts | 218 ++++++++++++++++++ 2 files changed, 295 insertions(+), 1 deletion(-) create mode 100644 packages/toolkit/src/query/tests/codeSplitting.test.ts diff --git a/packages/toolkit/src/query/apiTypes.ts b/packages/toolkit/src/query/apiTypes.ts index 3dafe46b02..2b653180bd 100644 --- a/packages/toolkit/src/query/apiTypes.ts +++ b/packages/toolkit/src/query/apiTypes.ts @@ -3,6 +3,9 @@ import type { EndpointBuilder, EndpointDefinition, ReplaceTagTypes, + ResultTypeFrom, + QueryArgFrom, + QueryDefinition, } from './endpointDefinitions' import type { UnionToIntersection, @@ -12,7 +15,7 @@ import type { import type { CoreModule } from './core/module' import type { CreateApiOptions } from './createApi' import type { BaseQueryFn } from './baseQueryTypes' -import type { CombinedState } from './core/apiState' +import type { CombinedState, MutationKeys, QueryKeys } from './core/apiState' import type { AnyAction } from '@reduxjs/toolkit' export interface ApiModules< @@ -92,6 +95,8 @@ export type Api< > /** *A function to enhance a generated API with additional information. Useful with code-generation. + + @deprecated this will be replaced by `addTagTypes` and `enhanceEndpoint` */ enhanceEndpoints(_: { addTagTypes?: readonly NewTagTypes[] @@ -112,4 +117,75 @@ export type Api< TagTypes | NewTagTypes, Enhancers > + + /** + *A function to enhance a generated API with additional information. Useful with code-generation. + */ + addTagTypes( + ...addTagTypes: readonly NewTagTypes[] + ): Api< + BaseQuery, + ReplaceTagTypes, + ReducerPath, + TagTypes | NewTagTypes, + Enhancers + > + + /** + *A function to enhance a generated API with additional information. Useful with code-generation. + */ + enhanceEndpoint< + QueryName extends QueryKeys, + ResultType = ResultTypeFrom, + QueryArg = QueryArgFrom + >( + queryName: QueryName, + queryDefinition: Partial< + QueryDefinition + > + ): Api< + BaseQuery, + Omit & + { + [Q in QueryName]: QueryDefinition< + QueryArg, + BaseQuery, + TagTypes, + ResultType, + ReducerPath + > + }, + ReducerPath, + TagTypes, + Enhancers + > + + /** + *A function to enhance a generated API with additional information. Useful with code-generation. + */ + enhanceEndpoint< + MutationName extends MutationKeys, + ResultType = ResultTypeFrom, + QueryArg = QueryArgFrom + >( + mutationName: MutationName, + mutationDefinition: Partial< + QueryDefinition + > + ): Api< + BaseQuery, + Omit & + { + [Q in MutationName]: QueryDefinition< + QueryArg, + BaseQuery, + TagTypes, + ResultType, + ReducerPath + > + }, + ReducerPath, + TagTypes, + Enhancers + > } diff --git a/packages/toolkit/src/query/tests/codeSplitting.test.ts b/packages/toolkit/src/query/tests/codeSplitting.test.ts new file mode 100644 index 0000000000..a44f7c33d2 --- /dev/null +++ b/packages/toolkit/src/query/tests/codeSplitting.test.ts @@ -0,0 +1,218 @@ +import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query' +import { expectType } from './helpers' + +function buildInitialApi() { + return createApi({ + baseQuery: fetchBaseQuery(), + endpoints(build) { + return { + someQuery: build.query<'ReturnedFromQuery', 'QueryArgument'>({ + query() { + return '/' + }, + }), + someMutation: build.query<'ReturnedFromMutation', 'MutationArgument'>({ + query() { + return '/' + }, + }), + } + }, + }) +} +let baseApi = buildInitialApi() +beforeEach(() => { + baseApi = buildInitialApi() +}) +const emptyApiState = { + [baseApi.reducerPath]: baseApi.reducer(undefined, { type: 'foo' }), +} + +test('injectTagTypes', () => { + const injectedApi = baseApi.addTagTypes('Foo', 'Bar') + + injectedApi.util.selectInvalidatedBy(emptyApiState, ['Foo']) + // @ts-expect-error + injectedApi.util.selectInvalidatedBy(emptyApiState, ['Baz']) +}) + +function getEndpointDetails< + E extends { + initiate(arg: any): any + select: (arg: any) => (state: any) => any + } +>( + e: E +): { + arg: Parameters[0] + returned: NonNullable>['data']> +} { + return {} as any +} + +describe('enhanceEndpoint', () => { + describe('query', () => { + test('no changes', () => { + const injectedApi = baseApi.enhanceEndpoint('someQuery', {}) + const { arg, returned } = getEndpointDetails( + injectedApi.endpoints.someQuery + ) + + expectType<'QueryArgument'>(arg) + expectType<'ReturnedFromQuery'>(returned) + }) + + test('change return value through `tranformResponse`', () => { + const injectedApi = baseApi.enhanceEndpoint('someQuery', { + transformResponse(): 'Changed' { + return 'Changed' + }, + }) + const { arg, returned } = getEndpointDetails( + injectedApi.endpoints.someQuery + ) + + expectType<'QueryArgument'>(arg) + expectType<'Changed'>(returned) + // @ts-expect-error + expectType<'ReturnedFromQuery'>(returned) + }) + + test('change argument value through new `query` function', () => { + const injectedApi = baseApi.enhanceEndpoint('someQuery', { + query(arg: 'Changed') { + return '/' + }, + }) + const { arg, returned } = getEndpointDetails( + injectedApi.endpoints.someQuery + ) + + expectType<'Changed'>(arg) + // @ts-expect-error + expectType<'QueryArgument'>(arg) + expectType<'ReturnedFromQuery'>(returned) + }) + + test('change argument and return value through new `query` and `transformResponse` functions', () => { + const injectedApi = baseApi.enhanceEndpoint('someQuery', { + query(arg: 'Changed') { + return '' + }, + transformResponse(): 'AlsoChanged' { + return 'AlsoChanged' + }, + }) + const { arg, returned } = getEndpointDetails( + injectedApi.endpoints.someQuery + ) + + expectType<'Changed'>(arg) + // @ts-expect-error + expectType<'QueryArgument'>(arg) + expectType<'AlsoChanged'>(returned) + // @ts-expect-error + expectType<'ReturnedFromQuery'>(returned) + }) + + test('change argument and return value through new `queryFn` function', () => { + const injectedApi = baseApi.enhanceEndpoint('someQuery', { + queryFn(arg: 'Changed') { + return { data: 'AlsoChanged' as const } + }, + }) + const { arg, returned } = getEndpointDetails( + injectedApi.endpoints.someQuery + ) + + expectType<'Changed'>(arg) + // @ts-expect-error + expectType<'QueryArgument'>(arg) + expectType<'AlsoChanged'>(returned) + // @ts-expect-error + expectType<'ReturnedFromQuery'>(returned) + }) + }) + describe('mutation', () => { + test('no changes', () => { + const injectedApi = baseApi.enhanceEndpoint('someMutation', {}) + const { arg, returned } = getEndpointDetails( + injectedApi.endpoints.someMutation + ) + + expectType<'MutationArgument'>(arg) + expectType<'ReturnedFromMutation'>(returned) + }) + + test('change return value through `tranformResponse`', () => { + const injectedApi = baseApi.enhanceEndpoint('someMutation', { + transformResponse(): 'Changed' { + return 'Changed' + }, + }) + const { arg, returned } = getEndpointDetails( + injectedApi.endpoints.someMutation + ) + + expectType<'MutationArgument'>(arg) + expectType<'Changed'>(returned) + // @ts-expect-error + expectType<'ReturnedFromMutation'>(returned) + }) + + test('change argument value through new `query` function', () => { + const injectedApi = baseApi.enhanceEndpoint('someMutation', { + query(arg: 'Changed') { + return '/' + }, + }) + const { arg, returned } = getEndpointDetails( + injectedApi.endpoints.someMutation + ) + + expectType<'Changed'>(arg) + // @ts-expect-error + expectType<'MutationArgument'>(arg) + expectType<'ReturnedFromMutation'>(returned) + }) + + test('change argument and return value through new `queryFn` function', () => { + const injectedApi = baseApi.enhanceEndpoint('someMutation', { + query(arg: 'Changed') { + return '' + }, + transformResponse(): 'AlsoChanged' { + return 'AlsoChanged' + }, + }) + const { arg, returned } = getEndpointDetails( + injectedApi.endpoints.someMutation + ) + + expectType<'Changed'>(arg) + // @ts-expect-error + expectType<'MutationArgument'>(arg) + expectType<'AlsoChanged'>(returned) + // @ts-expect-error + expectType<'ReturnedFromMutation'>(returned) + }) + + test('change argument and return value through new `queryFn` function', () => { + const injectedApi = baseApi.enhanceEndpoint('someMutation', { + queryFn(arg: 'Changed') { + return { data: 'AlsoChanged' as const } + }, + }) + const { arg, returned } = getEndpointDetails( + injectedApi.endpoints.someMutation + ) + + expectType<'Changed'>(arg) + // @ts-expect-error + expectType<'MutationArgument'>(arg) + expectType<'AlsoChanged'>(returned) + // @ts-expect-error + expectType<'ReturnedFromMutation'>(returned) + }) + }) +}) From ca7ad478e8fedb31a4181e7e1d75bab5201d931c Mon Sep 17 00:00:00 2001 From: Lenz Weber Date: Sun, 25 Sep 2022 21:49:18 +0200 Subject: [PATCH 2/2] runtime code --- packages/toolkit/src/query/apiTypes.ts | 34 ++++++++++--------------- packages/toolkit/src/query/createApi.ts | 26 ++++++++++++++----- packages/toolkit/src/query/tsHelpers.ts | 4 +++ 3 files changed, 36 insertions(+), 28 deletions(-) diff --git a/packages/toolkit/src/query/apiTypes.ts b/packages/toolkit/src/query/apiTypes.ts index 2b653180bd..bf50b52e3b 100644 --- a/packages/toolkit/src/query/apiTypes.ts +++ b/packages/toolkit/src/query/apiTypes.ts @@ -6,11 +6,13 @@ import type { ResultTypeFrom, QueryArgFrom, QueryDefinition, + MutationDefinition, } from './endpointDefinitions' import type { UnionToIntersection, NoInfer, WithRequiredProp, + OverrideAtKey, } from './tsHelpers' import type { CoreModule } from './core/module' import type { CreateApiOptions } from './createApi' @@ -145,16 +147,11 @@ export type Api< > ): Api< BaseQuery, - Omit & - { - [Q in QueryName]: QueryDefinition< - QueryArg, - BaseQuery, - TagTypes, - ResultType, - ReducerPath - > - }, + OverrideAtKey< + Definitions, + QueryName, + QueryDefinition + >, ReducerPath, TagTypes, Enhancers @@ -170,20 +167,15 @@ export type Api< >( mutationName: MutationName, mutationDefinition: Partial< - QueryDefinition + MutationDefinition > ): Api< BaseQuery, - Omit & - { - [Q in MutationName]: QueryDefinition< - QueryArg, - BaseQuery, - TagTypes, - ResultType, - ReducerPath - > - }, + OverrideAtKey< + Definitions, + MutationName, + MutationDefinition + >, ReducerPath, TagTypes, Enhancers diff --git a/packages/toolkit/src/query/createApi.ts b/packages/toolkit/src/query/createApi.ts index 2b2daec350..d10c1ff37e 100644 --- a/packages/toolkit/src/query/createApi.ts +++ b/packages/toolkit/src/query/createApi.ts @@ -275,13 +275,24 @@ export function buildCreateApi, ...Module[]]>( const api = { injectEndpoints, + addTagTypes(...addTagTypes) { + for (const eT of addTagTypes) { + if (!optionsWithDefaults.tagTypes!.includes(eT as any)) { + ;(optionsWithDefaults.tagTypes as any[]).push(eT) + } + } + return api + }, + enhanceEndpoint(endpointName, partialDefinition) { + Object.assign( + context.endpointDefinitions[endpointName] || {}, + partialDefinition + ) + return api + }, enhanceEndpoints({ addTagTypes, endpoints }) { if (addTagTypes) { - for (const eT of addTagTypes) { - if (!optionsWithDefaults.tagTypes!.includes(eT as any)) { - ;(optionsWithDefaults.tagTypes as any[]).push(eT) - } - } + api.addTagTypes(...addTagTypes) } if (endpoints) { for (const [endpointName, partialDefinition] of Object.entries( @@ -290,8 +301,9 @@ export function buildCreateApi, ...Module[]]>( if (typeof partialDefinition === 'function') { partialDefinition(context.endpointDefinitions[endpointName]) } else { - Object.assign( - context.endpointDefinitions[endpointName] || {}, + // @ts-expect-error Type 'string' does not satisfy the constraint 'never'. + api.enhanceEndpoint( + endpointName, partialDefinition ) } diff --git a/packages/toolkit/src/query/tsHelpers.ts b/packages/toolkit/src/query/tsHelpers.ts index af79f5bd71..709def9817 100644 --- a/packages/toolkit/src/query/tsHelpers.ts +++ b/packages/toolkit/src/query/tsHelpers.ts @@ -47,3 +47,7 @@ export type IsAny = true | false extends ( : False export type CastAny = IsAny + +export type OverrideAtKey = { + [K in keyof Type]: Key extends K ? NewKeyType : Type[K] +}