Skip to content

Commit cc98e30

Browse files
authored
fix: tanstack-query, fix the incorrect query typing when user provides a custom selector (#967)
1 parent 8559a78 commit cc98e30

File tree

7 files changed

+105
-87
lines changed

7 files changed

+105
-87
lines changed

packages/plugins/tanstack-query/src/generator.ts

Lines changed: 57 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -84,9 +84,9 @@ function generateQueryHook(
8484
const capOperation = upperCaseFirst(operation);
8585

8686
const argsType = overrideInputType ?? `Prisma.${model}${capOperation}Args`;
87-
const inputType = `Prisma.SelectSubset<T, ${argsType}>`;
87+
const inputType = `Prisma.SelectSubset<TArgs, ${argsType}>`;
8888

89-
let defaultReturnType = `Prisma.${model}GetPayload<T>`;
89+
let defaultReturnType = `Prisma.${model}GetPayload<TArgs>`;
9090
if (optimisticUpdate) {
9191
defaultReturnType += '& { $optimistic?: boolean }';
9292
}
@@ -95,11 +95,16 @@ function generateQueryHook(
9595
}
9696

9797
const returnType = overrideReturnType ?? defaultReturnType;
98-
const optionsType = makeQueryOptions(target, returnType, infinite, version);
98+
const optionsType = makeQueryOptions(target, 'TQueryFnData', 'TData', infinite, version);
9999

100100
const func = sf.addFunction({
101101
name: `use${infinite ? 'Infinite' : ''}${capOperation}${model}`,
102-
typeParameters: overrideTypeParameters ?? [`T extends ${argsType}`],
102+
typeParameters: overrideTypeParameters ?? [
103+
`TArgs extends ${argsType}`,
104+
`TQueryFnData = ${returnType} `,
105+
'TData = TQueryFnData',
106+
'TError = DefaultError',
107+
],
103108
parameters: [
104109
{
105110
name: optionalInput ? 'args?' : 'args',
@@ -129,7 +134,9 @@ function generateQueryHook(
129134

130135
func.addStatements([
131136
makeGetContext(target),
132-
`return ${infinite ? 'useInfiniteModelQuery' : 'useModelQuery'}('${model}', \`\${endpoint}/${lowerCaseFirst(
137+
`return ${
138+
infinite ? 'useInfiniteModelQuery' : 'useModelQuery'
139+
}<TQueryFnData, TData, TError>('${model}', \`\${endpoint}/${lowerCaseFirst(
133140
model
134141
)}/${operation}\`, args, options, fetch${optimisticUpdate ? ', optimisticUpdate' : ''});`,
135142
]);
@@ -403,7 +410,7 @@ function generateModelHooks(
403410
'aggregate',
404411
false,
405412
false,
406-
`Prisma.Get${modelNameCap}AggregateType<T>`
413+
`Prisma.Get${modelNameCap}AggregateType<TArgs>`
407414
);
408415
}
409416

@@ -415,16 +422,27 @@ function generateModelHooks(
415422
useName = model.name;
416423
}
417424

425+
const returnType = `{} extends InputErrors ?
426+
Array<PickEnumerable<Prisma.${modelNameCap}GroupByOutputType, TArgs['by']> &
427+
{
428+
[P in ((keyof TArgs) & (keyof Prisma.${modelNameCap}GroupByOutputType))]: P extends '_count'
429+
? TArgs[P] extends boolean
430+
? number
431+
: Prisma.GetScalarType<TArgs[P], Prisma.${modelNameCap}GroupByOutputType[P]>
432+
: Prisma.GetScalarType<TArgs[P], Prisma.${modelNameCap}GroupByOutputType[P]>
433+
}
434+
> : InputErrors`;
435+
418436
const typeParameters = [
419-
`T extends Prisma.${useName}GroupByArgs`,
420-
`HasSelectOrTake extends Prisma.Or<Prisma.Extends<'skip', Prisma.Keys<T>>, Prisma.Extends<'take', Prisma.Keys<T>>>`,
437+
`TArgs extends Prisma.${useName}GroupByArgs`,
438+
`HasSelectOrTake extends Prisma.Or<Prisma.Extends<'skip', Prisma.Keys<TArgs>>, Prisma.Extends<'take', Prisma.Keys<TArgs>>>`,
421439
`OrderByArg extends Prisma.True extends HasSelectOrTake ? { orderBy: Prisma.${useName}GroupByArgs['orderBy'] }: { orderBy?: Prisma.${useName}GroupByArgs['orderBy'] },`,
422-
`OrderFields extends Prisma.ExcludeUnderscoreKeys<Prisma.Keys<Prisma.MaybeTupleToUnion<T['orderBy']>>>`,
423-
`ByFields extends Prisma.MaybeTupleToUnion<T['by']>`,
440+
`OrderFields extends Prisma.ExcludeUnderscoreKeys<Prisma.Keys<Prisma.MaybeTupleToUnion<TArgs['orderBy']>>>`,
441+
`ByFields extends Prisma.MaybeTupleToUnion<TArgs['by']>`,
424442
`ByValid extends Prisma.Has<ByFields, OrderFields>`,
425-
`HavingFields extends Prisma.GetHavingFields<T['having']>`,
443+
`HavingFields extends Prisma.GetHavingFields<TArgs['having']>`,
426444
`HavingValid extends Prisma.Has<ByFields, HavingFields>`,
427-
`ByEmpty extends T['by'] extends never[] ? Prisma.True : Prisma.False`,
445+
`ByEmpty extends TArgs['by'] extends never[] ? Prisma.True : Prisma.False`,
428446
`InputErrors extends ByEmpty extends Prisma.True
429447
? \`Error: "by" must not be empty.\`
430448
: HavingValid extends Prisma.False
@@ -440,8 +458,8 @@ function generateModelHooks(
440458
\` in "having" needs to be provided in "by"\`,
441459
]
442460
}[HavingFields]
443-
: 'take' extends Prisma.Keys<T>
444-
? 'orderBy' extends Prisma.Keys<T>
461+
: 'take' extends Prisma.Keys<TArgs>
462+
? 'orderBy' extends Prisma.Keys<TArgs>
445463
? ByValid extends Prisma.True
446464
? {}
447465
: {
@@ -450,8 +468,8 @@ function generateModelHooks(
450468
: \`Error: Field "\${P}" in "orderBy" needs to be provided in "by"\`
451469
}[OrderFields]
452470
: 'Error: If you provide "take", you also need to provide "orderBy"'
453-
: 'skip' extends Prisma.Keys<T>
454-
? 'orderBy' extends Prisma.Keys<T>
471+
: 'skip' extends Prisma.Keys<TArgs>
472+
? 'orderBy' extends Prisma.Keys<TArgs>
455473
? ByValid extends Prisma.True
456474
? {}
457475
: {
@@ -467,19 +485,11 @@ function generateModelHooks(
467485
? never
468486
: \`Error: Field "\${P}" in "orderBy" needs to be provided in "by"\`
469487
}[OrderFields]`,
488+
`TQueryFnData = ${returnType}`,
489+
`TData = TQueryFnData`,
490+
`TError = DefaultError`,
470491
];
471492

472-
const returnType = `{} extends InputErrors ?
473-
Array<PickEnumerable<Prisma.${modelNameCap}GroupByOutputType, T['by']> &
474-
{
475-
[P in ((keyof T) & (keyof Prisma.${modelNameCap}GroupByOutputType))]: P extends '_count'
476-
? T[P] extends boolean
477-
? number
478-
: Prisma.GetScalarType<T[P], Prisma.${modelNameCap}GroupByOutputType[P]>
479-
: Prisma.GetScalarType<T[P], Prisma.${modelNameCap}GroupByOutputType[P]>
480-
}
481-
> : InputErrors`;
482-
483493
generateQueryHook(
484494
target,
485495
version,
@@ -489,7 +499,7 @@ function generateModelHooks(
489499
false,
490500
false,
491501
returnType,
492-
`Prisma.SubsetIntersection<T, Prisma.${useName}GroupByArgs, OrderByArg> & InputErrors`,
502+
`Prisma.SubsetIntersection<TArgs, Prisma.${useName}GroupByArgs, OrderByArg> & InputErrors`,
493503
typeParameters
494504
);
495505
}
@@ -504,7 +514,7 @@ function generateModelHooks(
504514
'count',
505515
false,
506516
true,
507-
`T extends { select: any; } ? T['select'] extends true ? number : Prisma.GetScalarType<T['select'], Prisma.${modelNameCap}CountAggregateOutputType> : number`
517+
`TArgs extends { select: any; } ? TArgs['select'] extends true ? number : Prisma.GetScalarType<TArgs['select'], Prisma.${modelNameCap}CountAggregateOutputType> : number`
508518
);
509519
}
510520
}
@@ -552,12 +562,13 @@ function makeBaseImports(target: TargetFramework, version: TanStackVersion) {
552562
`import { useModelQuery, useInfiniteModelQuery, useModelMutation } from '${runtimeImportBase}/${target}';`,
553563
`import type { PickEnumerable, CheckSelect } from '${runtimeImportBase}';`,
554564
`import metadata from './__model_meta';`,
565+
`type DefaultError = Error;`,
555566
];
556567
switch (target) {
557568
case 'react':
558569
return [
559570
`import type { UseMutationOptions, UseQueryOptions, UseInfiniteQueryOptions, InfiniteData } from '@tanstack/react-query';`,
560-
`import { RequestHandlerContext, getHooksContext } from '${runtimeImportBase}/${target}';`,
571+
`import { getHooksContext } from '${runtimeImportBase}/${target}';`,
561572
...shared,
562573
];
563574
case 'vue':
@@ -573,32 +584,38 @@ function makeBaseImports(target: TargetFramework, version: TanStackVersion) {
573584
...(version === 'v5'
574585
? [`import type { InfiniteData, StoreOrVal } from '@tanstack/svelte-query';`]
575586
: []),
576-
`import { SvelteQueryContextKey, type RequestHandlerContext, getHooksContext } from '${runtimeImportBase}/${target}';`,
587+
`import { getHooksContext } from '${runtimeImportBase}/${target}';`,
577588
...shared,
578589
];
579590
default:
580591
throw new PluginError(name, `Unsupported target: ${target}`);
581592
}
582593
}
583594

584-
function makeQueryOptions(target: string, returnType: string, infinite: boolean, version: TanStackVersion) {
595+
function makeQueryOptions(
596+
target: string,
597+
returnType: string,
598+
dataType: string,
599+
infinite: boolean,
600+
version: TanStackVersion
601+
) {
585602
switch (target) {
586603
case 'react':
587604
return infinite
588605
? version === 'v4'
589-
? `Omit<UseInfiniteQueryOptions<${returnType}>, 'queryKey'>`
590-
: `Omit<UseInfiniteQueryOptions<${returnType}, unknown, InfiniteData<${returnType}>>, 'queryKey'>`
591-
: `Omit<UseQueryOptions<${returnType}>, 'queryKey'>`;
606+
? `Omit<UseInfiniteQueryOptions<${returnType}, TError, ${dataType}>, 'queryKey'>`
607+
: `Omit<UseInfiniteQueryOptions<${returnType}, TError, InfiniteData<${dataType}>>, 'queryKey'>`
608+
: `Omit<UseQueryOptions<${returnType}, TError, ${dataType}>, 'queryKey'>`;
592609
case 'vue':
593-
return `Omit<Use${infinite ? 'Infinite' : ''}QueryOptions<${returnType}>, 'queryKey'>`;
610+
return `Omit<Use${infinite ? 'Infinite' : ''}QueryOptions<${returnType}, TError, ${dataType}>, 'queryKey'>`;
594611
case 'svelte':
595612
return infinite
596613
? version === 'v4'
597-
? `Omit<CreateInfiniteQueryOptions<${returnType}>, 'queryKey'>`
598-
: `StoreOrVal<Omit<CreateInfiniteQueryOptions<${returnType}, unknown, InfiniteData<${returnType}>>, 'queryKey'>>`
614+
? `Omit<CreateInfiniteQueryOptions<${returnType}, TError, ${dataType}>, 'queryKey'>`
615+
: `StoreOrVal<Omit<CreateInfiniteQueryOptions<${returnType}, TError, InfiniteData<${dataType}>>, 'queryKey'>>`
599616
: version === 'v4'
600-
? `Omit<CreateQueryOptions<${returnType}>, 'queryKey'>`
601-
: `StoreOrVal<Omit<CreateQueryOptions<${returnType}>, 'queryKey'>>`;
617+
? `Omit<CreateQueryOptions<${returnType}, TError, ${dataType}>, 'queryKey'>`
618+
: `StoreOrVal<Omit<CreateQueryOptions<${returnType}, TError, ${dataType}>, 'queryKey'>>`;
602619
default:
603620
throw new PluginError(name, `Unsupported target: ${target}`);
604621
}

packages/plugins/tanstack-query/src/runtime-v5/react.ts

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -55,18 +55,18 @@ export const Provider = RequestHandlerContext.Provider;
5555
* @param optimisticUpdate Whether to enable automatic optimistic update
5656
* @returns useQuery hook
5757
*/
58-
export function useModelQuery<R>(
58+
export function useModelQuery<TQueryFnData, TData, TError>(
5959
model: string,
6060
url: string,
6161
args?: unknown,
62-
options?: Omit<UseQueryOptions<R>, 'queryKey'>,
62+
options?: Omit<UseQueryOptions<TQueryFnData, TError, TData>, 'queryKey'>,
6363
fetch?: FetchFn,
6464
optimisticUpdate = false
6565
) {
6666
const reqUrl = makeUrl(url, args);
6767
return useQuery({
6868
queryKey: getQueryKey(model, url, args, false, optimisticUpdate),
69-
queryFn: () => fetcher<R, false>(reqUrl, undefined, fetch, false),
69+
queryFn: () => fetcher<TQueryFnData, false>(reqUrl, undefined, fetch, false),
7070
...options,
7171
});
7272
}
@@ -81,17 +81,17 @@ export function useModelQuery<R>(
8181
* @param fetch The fetch function to use for sending the HTTP request
8282
* @returns useInfiniteQuery hook
8383
*/
84-
export function useInfiniteModelQuery<R>(
84+
export function useInfiniteModelQuery<TQueryFnData, TData, TError>(
8585
model: string,
8686
url: string,
8787
args: unknown,
88-
options: Omit<UseInfiniteQueryOptions<R, unknown, InfiniteData<R>>, 'queryKey'>,
88+
options: Omit<UseInfiniteQueryOptions<TQueryFnData, TError, InfiniteData<TData>>, 'queryKey'>,
8989
fetch?: FetchFn
9090
) {
9191
return useInfiniteQuery({
9292
queryKey: getQueryKey(model, url, args, true),
9393
queryFn: ({ pageParam }) => {
94-
return fetcher<R, false>(makeUrl(url, pageParam ?? args), undefined, fetch, false);
94+
return fetcher<TQueryFnData, false>(makeUrl(url, pageParam ?? args), undefined, fetch, false);
9595
},
9696
...options,
9797
});

packages/plugins/tanstack-query/src/runtime-v5/svelte.ts

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,10 @@ import {
55
createQuery,
66
useQueryClient,
77
type CreateInfiniteQueryOptions,
8+
type CreateQueryOptions,
89
type InfiniteData,
910
type MutationOptions,
1011
type StoreOrVal,
11-
type CreateQueryOptions,
1212
} from '@tanstack/svelte-query-v5';
1313
import { ModelMeta } from '@zenstackhq/runtime/cross';
1414
import { getContext, setContext } from 'svelte';
@@ -58,17 +58,17 @@ export function getHooksContext() {
5858
* @param optimisticUpdate Whether to enable automatic optimistic update
5959
* @returns useQuery hook
6060
*/
61-
export function useModelQuery<R>(
61+
export function useModelQuery<TQueryFnData, TData, TError>(
6262
model: string,
6363
url: string,
6464
args?: unknown,
65-
options?: StoreOrVal<Omit<CreateQueryOptions<R>, 'queryKey'>>,
65+
options?: StoreOrVal<Omit<CreateQueryOptions<TQueryFnData, TError, TData>, 'queryKey'>>,
6666
fetch?: FetchFn,
6767
optimisticUpdate = false
6868
) {
6969
const reqUrl = makeUrl(url, args);
7070
const queryKey = getQueryKey(model, url, args, false, optimisticUpdate);
71-
const queryFn = () => fetcher<R, false>(reqUrl, undefined, fetch, false);
71+
const queryFn = () => fetcher<TQueryFnData, false>(reqUrl, undefined, fetch, false);
7272

7373
let mergedOpt: any;
7474
if (isStore(options)) {
@@ -100,19 +100,19 @@ export function useModelQuery<R>(
100100
* @param options The svelte-query infinite query options object
101101
* @returns useQuery hook
102102
*/
103-
export function useInfiniteModelQuery<R>(
103+
export function useInfiniteModelQuery<TQueryFnData, TData, TError>(
104104
model: string,
105105
url: string,
106106
args: unknown,
107-
options: StoreOrVal<Omit<CreateInfiniteQueryOptions<R, unknown, InfiniteData<R>>, 'queryKey'>>,
107+
options: StoreOrVal<Omit<CreateInfiniteQueryOptions<TQueryFnData, TError, InfiniteData<TData>>, 'queryKey'>>,
108108
fetch?: FetchFn
109109
) {
110110
const queryKey = getQueryKey(model, url, args, true);
111111
const queryFn = ({ pageParam }: { pageParam: unknown }) =>
112-
fetcher<R, false>(makeUrl(url, pageParam ?? args), undefined, fetch, false);
112+
fetcher<TQueryFnData, false>(makeUrl(url, pageParam ?? args), undefined, fetch, false);
113113

114-
let mergedOpt: StoreOrVal<CreateInfiniteQueryOptions<R, unknown, InfiniteData<R>>>;
115-
if (isStore<CreateInfiniteQueryOptions<R, unknown, InfiniteData<R>>>(options)) {
114+
let mergedOpt: StoreOrVal<CreateInfiniteQueryOptions<TQueryFnData, TError, InfiniteData<TData>>>;
115+
if (isStore<CreateInfiniteQueryOptions<TQueryFnData, TError, InfiniteData<TData>>>(options)) {
116116
// options is store
117117
mergedOpt = derived([options], ([$opt]) => {
118118
return {
@@ -129,7 +129,7 @@ export function useInfiniteModelQuery<R>(
129129
...options,
130130
};
131131
}
132-
return createInfiniteQuery<R, unknown, InfiniteData<R>>(mergedOpt);
132+
return createInfiniteQuery<TQueryFnData, TError, InfiniteData<TData>>(mergedOpt);
133133
}
134134

135135
function isStore<T>(opt: unknown): opt is Readable<T> {

packages/plugins/tanstack-query/src/runtime/react.ts

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -55,18 +55,18 @@ export function getHooksContext() {
5555
* @param optimisticUpdate Whether to enable automatic optimistic update
5656
* @returns useQuery hook
5757
*/
58-
export function useModelQuery<R>(
58+
export function useModelQuery<TQueryFnData, TData, TError>(
5959
model: string,
6060
url: string,
6161
args?: unknown,
62-
options?: Omit<UseQueryOptions<R>, 'queryKey'>,
62+
options?: Omit<UseQueryOptions<TQueryFnData, TError, TData>, 'queryKey'>,
6363
fetch?: FetchFn,
6464
optimisticUpdate = false
6565
) {
6666
const reqUrl = makeUrl(url, args);
67-
return useQuery<R>({
67+
return useQuery<TQueryFnData, TError, TData>({
6868
queryKey: getQueryKey(model, url, args, false, optimisticUpdate),
69-
queryFn: () => fetcher<R, false>(reqUrl, undefined, fetch, false),
69+
queryFn: () => fetcher<TQueryFnData, false>(reqUrl, undefined, fetch, false),
7070
...options,
7171
});
7272
}
@@ -81,17 +81,17 @@ export function useModelQuery<R>(
8181
* @param fetch The fetch function to use for sending the HTTP request
8282
* @returns useInfiniteQuery hook
8383
*/
84-
export function useInfiniteModelQuery<R>(
84+
export function useInfiniteModelQuery<TQueryFnData, TData, TError>(
8585
model: string,
8686
url: string,
8787
args?: unknown,
88-
options?: Omit<UseInfiniteQueryOptions<R>, 'queryKey'>,
88+
options?: Omit<UseInfiniteQueryOptions<TQueryFnData, TError, TData>, 'queryKey'>,
8989
fetch?: FetchFn
9090
) {
91-
return useInfiniteQuery<R>({
91+
return useInfiniteQuery<TQueryFnData, TError, TData>({
9292
queryKey: getQueryKey(model, url, args, true),
9393
queryFn: ({ pageParam }) => {
94-
return fetcher<R, false>(makeUrl(url, pageParam ?? args), undefined, fetch, false);
94+
return fetcher<TQueryFnData, false>(makeUrl(url, pageParam ?? args), undefined, fetch, false);
9595
},
9696
...options,
9797
});

0 commit comments

Comments
 (0)