diff --git a/packages/plugins/tanstack-query/src/generator.ts b/packages/plugins/tanstack-query/src/generator.ts index c32799cbc..bf0c88e0a 100644 --- a/packages/plugins/tanstack-query/src/generator.ts +++ b/packages/plugins/tanstack-query/src/generator.ts @@ -84,9 +84,9 @@ function generateQueryHook( const capOperation = upperCaseFirst(operation); const argsType = overrideInputType ?? `Prisma.${model}${capOperation}Args`; - const inputType = `Prisma.SelectSubset`; + const inputType = `Prisma.SelectSubset`; - let defaultReturnType = `Prisma.${model}GetPayload`; + let defaultReturnType = `Prisma.${model}GetPayload`; if (optimisticUpdate) { defaultReturnType += '& { $optimistic?: boolean }'; } @@ -95,11 +95,16 @@ function generateQueryHook( } const returnType = overrideReturnType ?? defaultReturnType; - const optionsType = makeQueryOptions(target, returnType, infinite, version); + const optionsType = makeQueryOptions(target, 'TQueryFnData', 'TData', infinite, version); const func = sf.addFunction({ name: `use${infinite ? 'Infinite' : ''}${capOperation}${model}`, - typeParameters: overrideTypeParameters ?? [`T extends ${argsType}`], + typeParameters: overrideTypeParameters ?? [ + `TArgs extends ${argsType}`, + `TQueryFnData = ${returnType} `, + 'TData = TQueryFnData', + 'TError = DefaultError', + ], parameters: [ { name: optionalInput ? 'args?' : 'args', @@ -129,7 +134,9 @@ function generateQueryHook( func.addStatements([ makeGetContext(target), - `return ${infinite ? 'useInfiniteModelQuery' : 'useModelQuery'}('${model}', \`\${endpoint}/${lowerCaseFirst( + `return ${ + infinite ? 'useInfiniteModelQuery' : 'useModelQuery' + }('${model}', \`\${endpoint}/${lowerCaseFirst( model )}/${operation}\`, args, options, fetch${optimisticUpdate ? ', optimisticUpdate' : ''});`, ]); @@ -403,7 +410,7 @@ function generateModelHooks( 'aggregate', false, false, - `Prisma.Get${modelNameCap}AggregateType` + `Prisma.Get${modelNameCap}AggregateType` ); } @@ -415,16 +422,27 @@ function generateModelHooks( useName = model.name; } + const returnType = `{} extends InputErrors ? + Array & + { + [P in ((keyof TArgs) & (keyof Prisma.${modelNameCap}GroupByOutputType))]: P extends '_count' + ? TArgs[P] extends boolean + ? number + : Prisma.GetScalarType + : Prisma.GetScalarType + } + > : InputErrors`; + const typeParameters = [ - `T extends Prisma.${useName}GroupByArgs`, - `HasSelectOrTake extends Prisma.Or>, Prisma.Extends<'take', Prisma.Keys>>`, + `TArgs extends Prisma.${useName}GroupByArgs`, + `HasSelectOrTake extends Prisma.Or>, Prisma.Extends<'take', Prisma.Keys>>`, `OrderByArg extends Prisma.True extends HasSelectOrTake ? { orderBy: Prisma.${useName}GroupByArgs['orderBy'] }: { orderBy?: Prisma.${useName}GroupByArgs['orderBy'] },`, - `OrderFields extends Prisma.ExcludeUnderscoreKeys>>`, - `ByFields extends Prisma.MaybeTupleToUnion`, + `OrderFields extends Prisma.ExcludeUnderscoreKeys>>`, + `ByFields extends Prisma.MaybeTupleToUnion`, `ByValid extends Prisma.Has`, - `HavingFields extends Prisma.GetHavingFields`, + `HavingFields extends Prisma.GetHavingFields`, `HavingValid extends Prisma.Has`, - `ByEmpty extends T['by'] extends never[] ? Prisma.True : Prisma.False`, + `ByEmpty extends TArgs['by'] extends never[] ? Prisma.True : Prisma.False`, `InputErrors extends ByEmpty extends Prisma.True ? \`Error: "by" must not be empty.\` : HavingValid extends Prisma.False @@ -440,8 +458,8 @@ function generateModelHooks( \` in "having" needs to be provided in "by"\`, ] }[HavingFields] - : 'take' extends Prisma.Keys - ? 'orderBy' extends Prisma.Keys + : 'take' extends Prisma.Keys + ? 'orderBy' extends Prisma.Keys ? ByValid extends Prisma.True ? {} : { @@ -450,8 +468,8 @@ function generateModelHooks( : \`Error: Field "\${P}" in "orderBy" needs to be provided in "by"\` }[OrderFields] : 'Error: If you provide "take", you also need to provide "orderBy"' - : 'skip' extends Prisma.Keys - ? 'orderBy' extends Prisma.Keys + : 'skip' extends Prisma.Keys + ? 'orderBy' extends Prisma.Keys ? ByValid extends Prisma.True ? {} : { @@ -467,19 +485,11 @@ function generateModelHooks( ? never : \`Error: Field "\${P}" in "orderBy" needs to be provided in "by"\` }[OrderFields]`, + `TQueryFnData = ${returnType}`, + `TData = TQueryFnData`, + `TError = DefaultError`, ]; - const returnType = `{} extends InputErrors ? - Array & - { - [P in ((keyof T) & (keyof Prisma.${modelNameCap}GroupByOutputType))]: P extends '_count' - ? T[P] extends boolean - ? number - : Prisma.GetScalarType - : Prisma.GetScalarType - } - > : InputErrors`; - generateQueryHook( target, version, @@ -489,7 +499,7 @@ function generateModelHooks( false, false, returnType, - `Prisma.SubsetIntersection & InputErrors`, + `Prisma.SubsetIntersection & InputErrors`, typeParameters ); } @@ -504,7 +514,7 @@ function generateModelHooks( 'count', false, true, - `T extends { select: any; } ? T['select'] extends true ? number : Prisma.GetScalarType : number` + `TArgs extends { select: any; } ? TArgs['select'] extends true ? number : Prisma.GetScalarType : number` ); } } @@ -552,12 +562,13 @@ function makeBaseImports(target: TargetFramework, version: TanStackVersion) { `import { useModelQuery, useInfiniteModelQuery, useModelMutation } from '${runtimeImportBase}/${target}';`, `import type { PickEnumerable, CheckSelect } from '${runtimeImportBase}';`, `import metadata from './__model_meta';`, + `type DefaultError = Error;`, ]; switch (target) { case 'react': return [ `import type { UseMutationOptions, UseQueryOptions, UseInfiniteQueryOptions, InfiniteData } from '@tanstack/react-query';`, - `import { RequestHandlerContext, getHooksContext } from '${runtimeImportBase}/${target}';`, + `import { getHooksContext } from '${runtimeImportBase}/${target}';`, ...shared, ]; case 'vue': @@ -573,7 +584,7 @@ function makeBaseImports(target: TargetFramework, version: TanStackVersion) { ...(version === 'v5' ? [`import type { InfiniteData, StoreOrVal } from '@tanstack/svelte-query';`] : []), - `import { SvelteQueryContextKey, type RequestHandlerContext, getHooksContext } from '${runtimeImportBase}/${target}';`, + `import { getHooksContext } from '${runtimeImportBase}/${target}';`, ...shared, ]; default: @@ -581,24 +592,30 @@ function makeBaseImports(target: TargetFramework, version: TanStackVersion) { } } -function makeQueryOptions(target: string, returnType: string, infinite: boolean, version: TanStackVersion) { +function makeQueryOptions( + target: string, + returnType: string, + dataType: string, + infinite: boolean, + version: TanStackVersion +) { switch (target) { case 'react': return infinite ? version === 'v4' - ? `Omit, 'queryKey'>` - : `Omit>, 'queryKey'>` - : `Omit, 'queryKey'>`; + ? `Omit, 'queryKey'>` + : `Omit>, 'queryKey'>` + : `Omit, 'queryKey'>`; case 'vue': - return `Omit, 'queryKey'>`; + return `Omit, 'queryKey'>`; case 'svelte': return infinite ? version === 'v4' - ? `Omit, 'queryKey'>` - : `StoreOrVal>, 'queryKey'>>` + ? `Omit, 'queryKey'>` + : `StoreOrVal>, 'queryKey'>>` : version === 'v4' - ? `Omit, 'queryKey'>` - : `StoreOrVal, 'queryKey'>>`; + ? `Omit, 'queryKey'>` + : `StoreOrVal, 'queryKey'>>`; default: throw new PluginError(name, `Unsupported target: ${target}`); } diff --git a/packages/plugins/tanstack-query/src/runtime-v5/react.ts b/packages/plugins/tanstack-query/src/runtime-v5/react.ts index 907e42595..4871e8229 100644 --- a/packages/plugins/tanstack-query/src/runtime-v5/react.ts +++ b/packages/plugins/tanstack-query/src/runtime-v5/react.ts @@ -55,18 +55,18 @@ export const Provider = RequestHandlerContext.Provider; * @param optimisticUpdate Whether to enable automatic optimistic update * @returns useQuery hook */ -export function useModelQuery( +export function useModelQuery( model: string, url: string, args?: unknown, - options?: Omit, 'queryKey'>, + options?: Omit, 'queryKey'>, fetch?: FetchFn, optimisticUpdate = false ) { const reqUrl = makeUrl(url, args); return useQuery({ queryKey: getQueryKey(model, url, args, false, optimisticUpdate), - queryFn: () => fetcher(reqUrl, undefined, fetch, false), + queryFn: () => fetcher(reqUrl, undefined, fetch, false), ...options, }); } @@ -81,17 +81,17 @@ export function useModelQuery( * @param fetch The fetch function to use for sending the HTTP request * @returns useInfiniteQuery hook */ -export function useInfiniteModelQuery( +export function useInfiniteModelQuery( model: string, url: string, args: unknown, - options: Omit>, 'queryKey'>, + options: Omit>, 'queryKey'>, fetch?: FetchFn ) { return useInfiniteQuery({ queryKey: getQueryKey(model, url, args, true), queryFn: ({ pageParam }) => { - return fetcher(makeUrl(url, pageParam ?? args), undefined, fetch, false); + return fetcher(makeUrl(url, pageParam ?? args), undefined, fetch, false); }, ...options, }); diff --git a/packages/plugins/tanstack-query/src/runtime-v5/svelte.ts b/packages/plugins/tanstack-query/src/runtime-v5/svelte.ts index d01ed02e4..a4c7cec7e 100644 --- a/packages/plugins/tanstack-query/src/runtime-v5/svelte.ts +++ b/packages/plugins/tanstack-query/src/runtime-v5/svelte.ts @@ -5,10 +5,10 @@ import { createQuery, useQueryClient, type CreateInfiniteQueryOptions, + type CreateQueryOptions, type InfiniteData, type MutationOptions, type StoreOrVal, - type CreateQueryOptions, } from '@tanstack/svelte-query-v5'; import { ModelMeta } from '@zenstackhq/runtime/cross'; import { getContext, setContext } from 'svelte'; @@ -58,17 +58,17 @@ export function getHooksContext() { * @param optimisticUpdate Whether to enable automatic optimistic update * @returns useQuery hook */ -export function useModelQuery( +export function useModelQuery( model: string, url: string, args?: unknown, - options?: StoreOrVal, 'queryKey'>>, + options?: StoreOrVal, 'queryKey'>>, fetch?: FetchFn, optimisticUpdate = false ) { const reqUrl = makeUrl(url, args); const queryKey = getQueryKey(model, url, args, false, optimisticUpdate); - const queryFn = () => fetcher(reqUrl, undefined, fetch, false); + const queryFn = () => fetcher(reqUrl, undefined, fetch, false); let mergedOpt: any; if (isStore(options)) { @@ -100,19 +100,19 @@ export function useModelQuery( * @param options The svelte-query infinite query options object * @returns useQuery hook */ -export function useInfiniteModelQuery( +export function useInfiniteModelQuery( model: string, url: string, args: unknown, - options: StoreOrVal>, 'queryKey'>>, + options: StoreOrVal>, 'queryKey'>>, fetch?: FetchFn ) { const queryKey = getQueryKey(model, url, args, true); const queryFn = ({ pageParam }: { pageParam: unknown }) => - fetcher(makeUrl(url, pageParam ?? args), undefined, fetch, false); + fetcher(makeUrl(url, pageParam ?? args), undefined, fetch, false); - let mergedOpt: StoreOrVal>>; - if (isStore>>(options)) { + let mergedOpt: StoreOrVal>>; + if (isStore>>(options)) { // options is store mergedOpt = derived([options], ([$opt]) => { return { @@ -129,7 +129,7 @@ export function useInfiniteModelQuery( ...options, }; } - return createInfiniteQuery>(mergedOpt); + return createInfiniteQuery>(mergedOpt); } function isStore(opt: unknown): opt is Readable { diff --git a/packages/plugins/tanstack-query/src/runtime/react.ts b/packages/plugins/tanstack-query/src/runtime/react.ts index 617302a3c..2f75d88eb 100644 --- a/packages/plugins/tanstack-query/src/runtime/react.ts +++ b/packages/plugins/tanstack-query/src/runtime/react.ts @@ -55,18 +55,18 @@ export function getHooksContext() { * @param optimisticUpdate Whether to enable automatic optimistic update * @returns useQuery hook */ -export function useModelQuery( +export function useModelQuery( model: string, url: string, args?: unknown, - options?: Omit, 'queryKey'>, + options?: Omit, 'queryKey'>, fetch?: FetchFn, optimisticUpdate = false ) { const reqUrl = makeUrl(url, args); - return useQuery({ + return useQuery({ queryKey: getQueryKey(model, url, args, false, optimisticUpdate), - queryFn: () => fetcher(reqUrl, undefined, fetch, false), + queryFn: () => fetcher(reqUrl, undefined, fetch, false), ...options, }); } @@ -81,17 +81,17 @@ export function useModelQuery( * @param fetch The fetch function to use for sending the HTTP request * @returns useInfiniteQuery hook */ -export function useInfiniteModelQuery( +export function useInfiniteModelQuery( model: string, url: string, args?: unknown, - options?: Omit, 'queryKey'>, + options?: Omit, 'queryKey'>, fetch?: FetchFn ) { - return useInfiniteQuery({ + return useInfiniteQuery({ queryKey: getQueryKey(model, url, args, true), queryFn: ({ pageParam }) => { - return fetcher(makeUrl(url, pageParam ?? args), undefined, fetch, false); + return fetcher(makeUrl(url, pageParam ?? args), undefined, fetch, false); }, ...options, }); diff --git a/packages/plugins/tanstack-query/src/runtime/svelte.ts b/packages/plugins/tanstack-query/src/runtime/svelte.ts index 84a175f58..88c675a82 100644 --- a/packages/plugins/tanstack-query/src/runtime/svelte.ts +++ b/packages/plugins/tanstack-query/src/runtime/svelte.ts @@ -5,8 +5,8 @@ import { createQuery, useQueryClient, type CreateInfiniteQueryOptions, - type MutationOptions, type CreateQueryOptions, + type MutationOptions, } from '@tanstack/svelte-query'; import { ModelMeta } from '@zenstackhq/runtime/cross'; import { getContext, setContext } from 'svelte'; @@ -55,18 +55,18 @@ export function getHooksContext() { * @param optimisticUpdate Whether to enable automatic optimistic update * @returns useQuery hook */ -export function useModelQuery( +export function useModelQuery( model: string, url: string, args?: unknown, - options?: Omit, 'queryKey'>, + options?: Omit, 'queryKey'>, fetch?: FetchFn, optimisticUpdate = false ) { const reqUrl = makeUrl(url, args); - return createQuery({ + return createQuery({ queryKey: getQueryKey(model, url, args, false, optimisticUpdate), - queryFn: () => fetcher(reqUrl, undefined, fetch, false), + queryFn: () => fetcher(reqUrl, undefined, fetch, false), ...options, }); } @@ -81,16 +81,17 @@ export function useModelQuery( * @param fetch The fetch function to use for sending the HTTP request * @returns useQuery hook */ -export function useInfiniteModelQuery( +export function useInfiniteModelQuery( model: string, url: string, args?: unknown, - options?: Omit, 'queryKey'>, + options?: Omit, 'queryKey'>, fetch?: FetchFn ) { - return createInfiniteQuery({ + return createInfiniteQuery({ queryKey: getQueryKey(model, url, args, true), - queryFn: ({ pageParam }) => fetcher(makeUrl(url, pageParam ?? args), undefined, fetch, false), + queryFn: ({ pageParam }) => + fetcher(makeUrl(url, pageParam ?? args), undefined, fetch, false), ...options, }); } diff --git a/packages/plugins/tanstack-query/src/runtime/vue.ts b/packages/plugins/tanstack-query/src/runtime/vue.ts index fe6cfc6a2..a0f1055e8 100644 --- a/packages/plugins/tanstack-query/src/runtime/vue.ts +++ b/packages/plugins/tanstack-query/src/runtime/vue.ts @@ -57,18 +57,18 @@ export function getHooksContext() { * @param optimisticUpdate Whether to enable automatic optimistic update * @returns useQuery hook */ -export function useModelQuery( +export function useModelQuery( model: string, url: string, args?: unknown, - options?: UseQueryOptions, + options?: UseQueryOptions, fetch?: FetchFn, optimisticUpdate = false ) { const reqUrl = makeUrl(url, args); - return useQuery({ + return useQuery({ queryKey: getQueryKey(model, url, args, false, optimisticUpdate), - queryFn: () => fetcher(reqUrl, undefined, fetch, false), + queryFn: () => fetcher(reqUrl, undefined, fetch, false), ...options, }); } @@ -83,17 +83,17 @@ export function useModelQuery( * @param fetch The fetch function to use for sending the HTTP request * @returns useInfiniteQuery hook */ -export function useInfiniteModelQuery( +export function useInfiniteModelQuery( model: string, url: string, args?: unknown, - options?: UseInfiniteQueryOptions, + options?: UseInfiniteQueryOptions, fetch?: FetchFn ) { - return useInfiniteQuery({ + return useInfiniteQuery({ queryKey: getQueryKey(model, url, args, true), queryFn: ({ pageParam }) => { - return fetcher(makeUrl(url, pageParam ?? args), undefined, fetch, false); + return fetcher(makeUrl(url, pageParam ?? args), undefined, fetch, false); }, ...options, }); diff --git a/packages/plugins/tanstack-query/tests/plugin.test.ts b/packages/plugins/tanstack-query/tests/plugin.test.ts index 49a99df94..38370d38a 100644 --- a/packages/plugins/tanstack-query/tests/plugin.test.ts +++ b/packages/plugins/tanstack-query/tests/plugin.test.ts @@ -61,7 +61,7 @@ ${sharedModel} provider: 'postgresql', pushDb: false, extraDependencies: ['react@18.2.0', '@types/react@18.2.0', '@tanstack/react-query@4.29.7'], - copyDependencies: [`${origDir}/dist`], + copyDependencies: [`${path.join(__dirname, '..')}/dist`], compile: true, } ); @@ -83,7 +83,7 @@ ${sharedModel} provider: 'postgresql', pushDb: false, extraDependencies: ['react@18.2.0', '@types/react@18.2.0', '@tanstack/react-query@^5.0.0'], - copyDependencies: [`${origDir}/dist`], + copyDependencies: [`${path.join(__dirname, '..')}/dist`], compile: true, } ); @@ -104,7 +104,7 @@ ${sharedModel} provider: 'postgresql', pushDb: false, extraDependencies: ['vue@^3.3.4', '@tanstack/vue-query@4.37.0'], - copyDependencies: [`${origDir}/dist`], + copyDependencies: [`${path.join(__dirname, '..')}/dist`], compile: true, } ); @@ -126,7 +126,7 @@ ${sharedModel} provider: 'postgresql', pushDb: false, extraDependencies: ['vue@^3.3.4', '@tanstack/vue-query@latest'], - copyDependencies: [`${origDir}/dist`], + copyDependencies: [`${path.join(__dirname, '..')}/dist`], compile: true, } ); @@ -147,7 +147,7 @@ ${sharedModel} provider: 'postgresql', pushDb: false, extraDependencies: ['svelte@^3.0.0', '@tanstack/svelte-query@4.29.7'], - copyDependencies: [`${origDir}/dist`], + copyDependencies: [`${path.join(__dirname, '..')}/dist`], compile: true, } ); @@ -169,7 +169,7 @@ ${sharedModel} provider: 'postgresql', pushDb: false, extraDependencies: ['svelte@^3.0.0', '@tanstack/svelte-query@^5.0.0'], - copyDependencies: [`${origDir}/dist`], + copyDependencies: [`${path.join(__dirname, '..')}/dist`], compile: true, } );