Skip to content

Commit

Permalink
refactor(tanstack): merge zenstack query/mutation options into tansta…
Browse files Browse the repository at this point in the history
…ck options (#1136)
  • Loading branch information
ymc9 authored Mar 14, 2024
1 parent 6db11e2 commit 0d6674d
Show file tree
Hide file tree
Showing 16 changed files with 215 additions and 199 deletions.
2 changes: 1 addition & 1 deletion packages/plugins/swr/src/generator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ function generateModelHooks(
moduleSpecifier: prismaImport,
});
sf.addStatements([
`import { type GetNextArgs, type QueryOptions, type InfiniteQueryOptions, type MutationOptions, type PickEnumerable, useHooksContext } from '@zenstackhq/swr/runtime';`,
`import { type GetNextArgs, type QueryOptions, type InfiniteQueryOptions, type MutationOptions, type PickEnumerable } from '@zenstackhq/swr/runtime';`,
`import metadata from './__model_meta';`,
`import * as request from '@zenstackhq/swr/runtime';`,
]);
Expand Down
1 change: 1 addition & 0 deletions packages/plugins/tanstack-query/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,7 @@
"semver": "^7.5.2",
"superjson": "^1.11.0",
"ts-morph": "^16.0.0",
"ts-pattern": "^4.3.0",
"upper-case-first": "^2.0.2"
},
"devDependencies": {
Expand Down
78 changes: 36 additions & 42 deletions packages/plugins/tanstack-query/src/generator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import { lowerCaseFirst } from 'lower-case-first';
import path from 'path';
import semver from 'semver';
import { Project, SourceFile, VariableDeclarationKind } from 'ts-morph';
import { match } from 'ts-pattern';
import { upperCaseFirst } from 'upper-case-first';
import { name } from '.';

Expand All @@ -37,7 +38,7 @@ export async function generate(model: Model, options: PluginOptions, dmmf: DMMF.
throw new PluginError(name, `Unsupported target "${target}", supported values: ${supportedTargets.join(', ')}`);
}

const version = typeof options.version === 'string' ? options.version : 'v4';
const version = typeof options.version === 'string' ? options.version : 'v5';
if (version !== 'v4' && version !== 'v5') {
throw new PluginError(name, `Unsupported version "${version}": use "v4" or "v5"`);
}
Expand Down Expand Up @@ -130,15 +131,6 @@ function generateQueryHook(
name: 'options?',
type: optionsType,
},
...(optimistic
? [
{
name: 'optimisticUpdate',
type: 'boolean',
initializer: 'true',
},
]
: []),
],
isExported: true,
});
Expand All @@ -152,7 +144,7 @@ function generateQueryHook(
makeGetContext(target),
`return use${generateMode}ModelQuery<TQueryFnData, TData, TError>('${model}', \`\${endpoint}/${lowerCaseFirst(
model
)}/${operation}\`, args, options, fetch${optimistic ? ', optimisticUpdate' : ''});`,
)}/${operation}\`, args, options, fetch);`,
]);
}
}
Expand Down Expand Up @@ -189,16 +181,6 @@ function generateMutationHook(
name: 'options?',
type: nonGenericOptionsType,
},
{
name: 'invalidateQueries',
type: 'boolean',
initializer: 'true',
},
{
name: 'optimisticUpdate',
type: 'boolean',
initializer: 'false',
},
],
});

Expand All @@ -215,7 +197,7 @@ function generateMutationHook(
overrideReturnType ?? model
}, ${checkReadBack}>('${model}', '${httpVerb.toUpperCase()}', \`\${endpoint}/${lowerCaseFirst(
model
)}/${operation}\`, metadata, options, fetch, invalidateQueries, ${checkReadBack}, optimisticUpdate)
)}/${operation}\`, metadata, options, fetch, ${checkReadBack})
`,
},
],
Expand Down Expand Up @@ -561,7 +543,7 @@ function makeBaseImports(target: TargetFramework, version: TanStackVersion) {
const runtimeImportBase = makeRuntimeImportBase(version);
const shared = [
`import { useModelQuery, useInfiniteModelQuery, useModelMutation } from '${runtimeImportBase}/${target}';`,
`import type { PickEnumerable, CheckSelect, QueryError } from '${runtimeImportBase}';`,
`import type { PickEnumerable, CheckSelect, QueryError, ExtraQueryOptions, ExtraMutationOptions } from '${runtimeImportBase}';`,
`import metadata from './__model_meta';`,
`type DefaultError = QueryError;`,
];
Expand Down Expand Up @@ -612,41 +594,53 @@ function makeQueryOptions(
suspense: boolean,
version: TanStackVersion
) {
switch (target) {
case 'react':
return infinite
let result = match(target)
.with('react', () =>
infinite
? version === 'v4'
? `Omit<UseInfiniteQueryOptions<${returnType}, TError, ${dataType}>, 'queryKey'>`
: `Omit<Use${
suspense ? 'Suspense' : ''
}InfiniteQueryOptions<${returnType}, TError, InfiniteData<${dataType}>>, 'queryKey'>`
: `Omit<Use${suspense ? 'Suspense' : ''}QueryOptions<${returnType}, TError, ${dataType}>, 'queryKey'>`;
case 'vue':
return `Omit<Use${infinite ? 'Infinite' : ''}QueryOptions<${returnType}, TError, ${dataType}>, 'queryKey'>`;
case 'svelte':
return infinite
: `Omit<Use${suspense ? 'Suspense' : ''}QueryOptions<${returnType}, TError, ${dataType}>, 'queryKey'>`
)
.with(
'vue',
() => `Omit<Use${infinite ? 'Infinite' : ''}QueryOptions<${returnType}, TError, ${dataType}>, 'queryKey'>`
)
.with('svelte', () =>
infinite
? version === 'v4'
? `Omit<CreateInfiniteQueryOptions<${returnType}, TError, ${dataType}>, 'queryKey'>`
: `StoreOrVal<Omit<CreateInfiniteQueryOptions<${returnType}, TError, InfiniteData<${dataType}>>, 'queryKey'>>`
: version === 'v4'
? `Omit<CreateQueryOptions<${returnType}, TError, ${dataType}>, 'queryKey'>`
: `StoreOrVal<Omit<CreateQueryOptions<${returnType}, TError, ${dataType}>, 'queryKey'>>`;
default:
: `StoreOrVal<Omit<CreateQueryOptions<${returnType}, TError, ${dataType}>, 'queryKey'>>`
)
.otherwise(() => {
throw new PluginError(name, `Unsupported target: ${target}`);
});

if (!infinite) {
// non-infinite queries support extra options like optimistic updates
result = `(${result} & ExtraQueryOptions)`;
}

return result;
}

function makeMutationOptions(target: string, returnType: string, argsType: string) {
switch (target) {
case 'react':
return `UseMutationOptions<${returnType}, DefaultError, ${argsType}>`;
case 'vue':
return `UseMutationOptions<${returnType}, DefaultError, ${argsType}, unknown>`;
case 'svelte':
return `MutationOptions<${returnType}, DefaultError, ${argsType}>`;
default:
let result = match(target)
.with('react', () => `UseMutationOptions<${returnType}, DefaultError, ${argsType}>`)
.with('vue', () => `UseMutationOptions<${returnType}, DefaultError, ${argsType}, unknown>`)
.with('svelte', () => `MutationOptions<${returnType}, DefaultError, ${argsType}>`)
.otherwise(() => {
throw new PluginError(name, `Unsupported target: ${target}`);
}
});

result = `(${result} & ExtraMutationOptions)`;

return result;
}

function makeRuntimeImportBase(version: TanStackVersion) {
Expand Down
8 changes: 7 additions & 1 deletion packages/plugins/tanstack-query/src/runtime-v5/index.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,8 @@
export {
getQueryKey,
type ExtraMutationOptions,
type ExtraQueryOptions,
type FetchFn,
type QueryError,
} from '../runtime/common';
export * from '../runtime/prisma-types';
export { type FetchFn, type QueryError, getQueryKey } from '../runtime/common';
38 changes: 20 additions & 18 deletions packages/plugins/tanstack-query/src/runtime-v5/react.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ import {
setupInvalidation,
setupOptimisticUpdate,
type APIContext,
type ExtraMutationOptions,
type ExtraQueryOptions,
type FetchFn,
} from '../runtime/common';

Expand Down Expand Up @@ -56,20 +58,21 @@ export const Provider = RequestHandlerContext.Provider;
* @param args The request args object, URL-encoded and appended as "?q=" parameter
* @param options The react-query options object
* @param fetch The fetch function to use for sending the HTTP request
* @param optimisticUpdate Whether to enable automatic optimistic update
* @returns useQuery hook
*/
export function useModelQuery<TQueryFnData, TData, TError>(
model: string,
url: string,
args?: unknown,
options?: Omit<UseQueryOptions<TQueryFnData, TError, TData>, 'queryKey'>,
fetch?: FetchFn,
optimisticUpdate = false
options?: Omit<UseQueryOptions<TQueryFnData, TError, TData>, 'queryKey'> & ExtraQueryOptions,
fetch?: FetchFn
) {
const reqUrl = makeUrl(url, args);
return useQuery({
queryKey: getQueryKey(model, url, args, false, optimisticUpdate),
queryKey: getQueryKey(model, url, args, {
infinite: false,
optimisticUpdate: options?.optimisticUpdate !== false,
}),
queryFn: () => fetcher<TQueryFnData, false>(reqUrl, undefined, fetch, false),
...options,
});
Expand All @@ -83,20 +86,21 @@ export function useModelQuery<TQueryFnData, TData, TError>(
* @param args The request args object, URL-encoded and appended as "?q=" parameter
* @param options The react-query options object
* @param fetch The fetch function to use for sending the HTTP request
* @param optimisticUpdate Whether to enable automatic optimistic update
* @returns useSuspenseQuery hook
*/
export function useSuspenseModelQuery<TQueryFnData, TData, TError>(
model: string,
url: string,
args?: unknown,
options?: Omit<UseSuspenseQueryOptions<TQueryFnData, TError, TData>, 'queryKey'>,
fetch?: FetchFn,
optimisticUpdate = false
options?: Omit<UseSuspenseQueryOptions<TQueryFnData, TError, TData>, 'queryKey'> & ExtraQueryOptions,
fetch?: FetchFn
) {
const reqUrl = makeUrl(url, args);
return useSuspenseQuery({
queryKey: getQueryKey(model, url, args, false, optimisticUpdate),
queryKey: getQueryKey(model, url, args, {
infinite: false,
optimisticUpdate: options?.optimisticUpdate !== false,
}),
queryFn: () => fetcher<TQueryFnData, false>(reqUrl, undefined, fetch, false),
...options,
});
Expand All @@ -120,7 +124,7 @@ export function useInfiniteModelQuery<TQueryFnData, TData, TError>(
fetch?: FetchFn
) {
return useInfiniteQuery({
queryKey: getQueryKey(model, url, args, true),
queryKey: getQueryKey(model, url, args, { infinite: true, optimisticUpdate: false }),
queryFn: ({ pageParam }) => {
return fetcher<TQueryFnData, false>(makeUrl(url, pageParam ?? args), undefined, fetch, false);
},
Expand All @@ -146,7 +150,7 @@ export function useSuspenseInfiniteModelQuery<TQueryFnData, TData, TError>(
fetch?: FetchFn
) {
return useSuspenseInfiniteQuery({
queryKey: getQueryKey(model, url, args, true),
queryKey: getQueryKey(model, url, args, { infinite: true, optimisticUpdate: false }),
queryFn: ({ pageParam }) => {
return fetcher<TQueryFnData, false>(makeUrl(url, pageParam ?? args), undefined, fetch, false);
},
Expand All @@ -163,9 +167,7 @@ export function useSuspenseInfiniteModelQuery<TQueryFnData, TData, TError>(
* @param modelMeta The model metadata.
* @param options The react-query options.
* @param fetch The fetch function to use for sending the HTTP request
* @param invalidateQueries Whether to invalidate queries after mutation.
* @param checkReadBack Whether to check for read back errors and return undefined if found.
* @param optimisticUpdate Whether to enable automatic optimistic update
*/
export function useModelMutation<
TArgs,
Expand All @@ -178,11 +180,9 @@ export function useModelMutation<
method: 'POST' | 'PUT' | 'DELETE',
url: string,
modelMeta: ModelMeta,
options?: Omit<UseMutationOptions<Result, TError, TArgs>, 'mutationFn'>,
options?: Omit<UseMutationOptions<Result, TError, TArgs>, 'mutationFn'> & ExtraMutationOptions,
fetch?: FetchFn,
invalidateQueries = true,
checkReadBack?: C,
optimisticUpdate = false
checkReadBack?: C
) {
const queryClient = useQueryClient();
const mutationFn = (data: any) => {
Expand All @@ -201,6 +201,8 @@ export function useModelMutation<

const finalOptions = { ...options, mutationFn };
const operation = url.split('/').pop();
const invalidateQueries = options?.invalidateQueries !== false;
const optimisticUpdate = !!options?.optimisticUpdate;

if (operation) {
const { logging } = useContext(RequestHandlerContext);
Expand Down
24 changes: 13 additions & 11 deletions packages/plugins/tanstack-query/src/runtime-v5/svelte.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ import {
marshal,
setupInvalidation,
setupOptimisticUpdate,
type ExtraMutationOptions,
type ExtraQueryOptions,
type FetchFn,
} from '../runtime/common';

Expand Down Expand Up @@ -55,19 +57,20 @@ export function getHooksContext() {
* @param args The request args object, URL-encoded and appended as "?q=" parameter
* @param options The svelte-query options object
* @param fetch The fetch function to use for sending the HTTP request
* @param optimisticUpdate Whether to enable automatic optimistic update
* @returns useQuery hook
*/
export function useModelQuery<TQueryFnData, TData, TError>(
model: string,
url: string,
args?: unknown,
options?: StoreOrVal<Omit<CreateQueryOptions<TQueryFnData, TError, TData>, 'queryKey'>>,
fetch?: FetchFn,
optimisticUpdate = false
options?: StoreOrVal<Omit<CreateQueryOptions<TQueryFnData, TError, TData>, 'queryKey'>> & ExtraQueryOptions,
fetch?: FetchFn
) {
const reqUrl = makeUrl(url, args);
const queryKey = getQueryKey(model, url, args, false, optimisticUpdate);
const queryKey = getQueryKey(model, url, args, {
infinite: false,
optimisticUpdate: options?.optimisticUpdate !== false,
});
const queryFn = () => fetcher<TQueryFnData, false>(reqUrl, undefined, fetch, false);

let mergedOpt: any;
Expand Down Expand Up @@ -107,7 +110,7 @@ export function useInfiniteModelQuery<TQueryFnData, TData, TError>(
options: StoreOrVal<Omit<CreateInfiniteQueryOptions<TQueryFnData, TError, InfiniteData<TData>>, 'queryKey'>>,
fetch?: FetchFn
) {
const queryKey = getQueryKey(model, url, args, true);
const queryKey = getQueryKey(model, url, args, { infinite: true, optimisticUpdate: false });
const queryFn = ({ pageParam }: { pageParam: unknown }) =>
fetcher<TQueryFnData, false>(makeUrl(url, pageParam ?? args), undefined, fetch, false);

Expand Down Expand Up @@ -144,7 +147,6 @@ function isStore<T>(opt: unknown): opt is Readable<T> {
* @param modelMeta The model metadata.
* @param url The request URL.
* @param options The svelte-query options.
* @param invalidateQueries Whether to invalidate queries after mutation.
* @returns useMutation hooks
*/
export function useModelMutation<
Expand All @@ -158,11 +160,9 @@ export function useModelMutation<
method: 'POST' | 'PUT' | 'DELETE',
url: string,
modelMeta: ModelMeta,
options?: Omit<MutationOptions<Result, TError, TArgs>, 'mutationFn'>,
options?: Omit<MutationOptions<Result, TError, TArgs>, 'mutationFn'> & ExtraMutationOptions,
fetch?: FetchFn,
invalidateQueries = true,
checkReadBack?: C,
optimisticUpdate = false
checkReadBack?: C
) {
const queryClient = useQueryClient();
const mutationFn = (data: any) => {
Expand All @@ -181,6 +181,8 @@ export function useModelMutation<

const finalOptions = { ...options, mutationFn };
const operation = url.split('/').pop();
const invalidateQueries = options?.invalidateQueries !== false;
const optimisticUpdate = !!options?.optimisticUpdate;

if (operation) {
const { logging } = getContext<APIContext>(SvelteQueryContextKey);
Expand Down
Loading

0 comments on commit 0d6674d

Please sign in to comment.