feat: tanstack-query v5 support (#788)

This commit is contained in:
Yiming 2023-10-28 22:09:41 -07:00 committed by GitHub
parent 8a370dbfea
commit 0d04d8e6da
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
13 changed files with 759 additions and 89 deletions

View file

@ -34,6 +34,30 @@
"require": "./runtime/svelte.js",
"default": "./runtime/svelte.js",
"types": "./runtime/svelte.d.ts"
},
"./runtime-v5": {
"import": "./runtime-v5/index.mjs",
"require": "./runtime-v5/index.js",
"default": "./runtime-v5/index.js",
"types": "./runtime-v5/index.d.ts"
},
"./runtime-v5/react": {
"import": "./runtime-v5/react.mjs",
"require": "./runtime-v5/react.js",
"default": "./runtime-v5/react.js",
"types": "./runtime-v5/react.d.ts"
},
"./runtime-v5/vue": {
"import": "./runtime-v5/vue.mjs",
"require": "./runtime-v5/vue.js",
"default": "./runtime-v5/vue.js",
"types": "./runtime-v5/vue.d.ts"
},
"./runtime-v5/svelte": {
"import": "./runtime-v5/svelte.mjs",
"require": "./runtime-v5/svelte.js",
"default": "./runtime-v5/svelte.js",
"types": "./runtime-v5/svelte.d.ts"
}
},
"repository": {
@ -42,8 +66,8 @@
},
"scripts": {
"clean": "rimraf dist",
"build": "pnpm lint && pnpm clean && tsc && tsup-node && copyfiles ./package.json ./README.md ./LICENSE dist && pnpm pack dist --pack-destination '../../../../.build'",
"watch": "concurrently \"tsc --watch\" \"tsup-node --watch\"",
"build": "pnpm lint && pnpm clean && tsc && tsup-node --config ./tsup.config.ts && tsup-node --config ./tsup-v5.config.ts && node scripts/postbuild && copyfiles ./package.json ./README.md ./LICENSE dist && pnpm pack dist --pack-destination '../../../../.build'",
"watch": "concurrently \"tsc --watch\" \"tsup-node --config ./tsup.config.ts --watch\" \"tsup-node --config ./tsup-v5.config.ts --watch\"",
"lint": "eslint src --ext ts",
"test": "ZENSTACK_TEST=1 jest",
"prepublishOnly": "pnpm build",
@ -71,7 +95,9 @@
},
"devDependencies": {
"@tanstack/react-query": "^4.29.7",
"@tanstack/react-query-v5": "npm:@tanstack/react-query@^5.0.0",
"@tanstack/svelte-query": "^4.29.7",
"@tanstack/svelte-query-v5": "npm:@tanstack/svelte-query@^5.0.0",
"@tanstack/vue-query": "^4.37.0",
"@types/jest": "^29.5.0",
"@types/node": "^18.0.0",
@ -82,6 +108,7 @@
"copyfiles": "^2.4.1",
"jest": "^29.5.0",
"react": "18.2.0",
"replace-in-file": "^7.0.1",
"rimraf": "^3.0.2",
"svelte": "^4.2.1",
"swr": "^2.0.3",

View file

@ -0,0 +1,24 @@
// tsup doesn't replace npm dependency aliases in the dist files, so we have to do it manually
const replace = require('replace-in-file');
console.log('Replacing @tanstack/react-query-v5');
replace.sync({
files: 'dist/runtime-v5/react*(.d.ts|.d.mts|.js|.mjs)',
from: /@tanstack\/react-query-v5/g,
to: '@tanstack/react-query',
});
console.log('Replacing @tanstack/svelte-query-v5');
replace.sync({
files: 'dist/runtime-v5/svelte*(.d.ts|.d.mts|.js|.mjs)',
from: /@tanstack\/svelte-query-v5/g,
to: '@tanstack/svelte-query',
});
console.log('Replacing @tanstack/vue-query-v5');
replace.sync({
files: 'dist/runtime-v5/vue*(.d.ts|.d.mts|.js|.mjs)',
from: /@tanstack\/vue-query-v5/g,
to: '@tanstack/vue-query',
});

View file

@ -21,6 +21,7 @@ import { name } from '.';
const supportedTargets = ['react', 'vue', 'svelte'];
type TargetFramework = (typeof supportedTargets)[number];
type TanStackVersion = 'v4' | 'v5';
export async function generate(model: Model, options: PluginOptions, dmmf: DMMF.Document) {
let outDir = requireOption<string>(options, 'output');
@ -38,7 +39,12 @@ export async function generate(model: Model, options: PluginOptions, dmmf: DMMF.
);
}
generateIndex(project, outDir, models, target);
const version = typeof options.version === 'string' ? options.version : 'v4';
if (version !== 'v4' && version !== 'v5') {
throw new PluginError(options.name, `Unsupported version "${version}": use "v4" or "v5"`);
}
generateIndex(project, outDir, models, target, version);
models.forEach((dataModel) => {
const mapping = dmmf.mappings.modelOperations.find((op) => op.model === dataModel.name);
@ -46,7 +52,7 @@ export async function generate(model: Model, options: PluginOptions, dmmf: DMMF.
warnings.push(`Unable to find mapping for model ${dataModel.name}`);
return;
}
generateModelHooks(target, project, outDir, dataModel, mapping);
generateModelHooks(target, version, project, outDir, dataModel, mapping);
});
await saveProject(project);
@ -55,6 +61,7 @@ export async function generate(model: Model, options: PluginOptions, dmmf: DMMF.
function generateQueryHook(
target: TargetFramework,
version: TanStackVersion,
sf: SourceFile,
model: string,
operation: string,
@ -71,7 +78,7 @@ function generateQueryHook(
const inputType = `Prisma.SelectSubset<T, ${argsType}>`;
const returnType =
overrideReturnType ?? (returnArray ? `Array<Prisma.${model}GetPayload<T>>` : `Prisma.${model}GetPayload<T>`);
const optionsType = makeQueryOptions(target, returnType, infinite);
const optionsType = makeQueryOptions(target, returnType, infinite, version);
const func = sf.addFunction({
name: `use${infinite ? 'Infinite' : ''}${capOperation}${model}`,
@ -89,9 +96,14 @@ function generateQueryHook(
isExported: true,
});
if (version === 'v5' && infinite && ['react', 'svelte'].includes(target)) {
// initialPageParam and getNextPageParam options are required in v5
func.addStatements([`options = options ?? { initialPageParam: undefined, getNextPageParam: () => null };`]);
}
func.addStatements([
makeGetContext(target),
`return ${infinite ? 'infiniteQuery' : 'query'}<${returnType}>('${model}', \`\${endpoint}/${lowerCaseFirst(
`return ${infinite ? 'infiniteQuery' : 'query'}('${model}', \`\${endpoint}/${lowerCaseFirst(
model
)}/${operation}\`, args, options, fetch);`,
]);
@ -217,6 +229,7 @@ function generateMutationHook(
function generateModelHooks(
target: TargetFramework,
version: TanStackVersion,
project: Project,
outDir: string,
model: DataModel,
@ -235,7 +248,7 @@ function generateModelHooks(
isTypeOnly: true,
moduleSpecifier: prismaImport,
});
sf.addStatements(makeBaseImports(target));
sf.addStatements(makeBaseImports(target, version));
// create is somehow named "createOne" in the DMMF
// eslint-disable-next-line @typescript-eslint/no-explicit-any
@ -251,19 +264,31 @@ function generateModelHooks(
// findMany
if (mapping.findMany) {
// regular findMany
generateQueryHook(target, sf, model.name, 'findMany', true, true);
generateQueryHook(target, version, sf, model.name, 'findMany', true, true);
// infinite findMany
generateQueryHook(target, sf, model.name, 'findMany', true, true, undefined, undefined, undefined, true);
generateQueryHook(
target,
version,
sf,
model.name,
'findMany',
true,
true,
undefined,
undefined,
undefined,
true
);
}
// findUnique
if (mapping.findUnique) {
generateQueryHook(target, sf, model.name, 'findUnique', false, false);
generateQueryHook(target, version, sf, model.name, 'findUnique', false, false);
}
// findFirst
if (mapping.findFirst) {
generateQueryHook(target, sf, model.name, 'findFirst', false, true);
generateQueryHook(target, version, sf, model.name, 'findFirst', false, true);
}
// update
@ -301,6 +326,7 @@ function generateModelHooks(
if (mapping.aggregate) {
generateQueryHook(
target,
version,
sf,
modelNameCap,
'aggregate',
@ -385,6 +411,7 @@ function generateModelHooks(
generateQueryHook(
target,
version,
sf,
model.name,
'groupBy',
@ -400,6 +427,7 @@ function generateModelHooks(
{
generateQueryHook(
target,
version,
sf,
model.name,
'count',
@ -410,22 +438,25 @@ function generateModelHooks(
}
}
function generateIndex(project: Project, outDir: string, models: DataModel[], target: string) {
function generateIndex(
project: Project,
outDir: string,
models: DataModel[],
target: string,
version: TanStackVersion
) {
const sf = project.createSourceFile(path.join(outDir, 'index.ts'), undefined, { overwrite: true });
sf.addStatements(models.map((d) => `export * from './${paramCase(d.name)}';`));
const runtimeImportBase = makeRuntimeImportBase(version);
switch (target) {
case 'react':
sf.addStatements(`export { Provider } from '@zenstackhq/tanstack-query/runtime/react';`);
sf.addStatements(`export { Provider } from '${runtimeImportBase}/react';`);
break;
case 'vue':
sf.addStatements(
`export { VueQueryContextKey, provideHooksContext } from '@zenstackhq/tanstack-query/runtime/vue';`
);
sf.addStatements(`export { VueQueryContextKey, provideHooksContext } from '${runtimeImportBase}/vue';`);
break;
case 'svelte':
sf.addStatements(
`export { SvelteQueryContextKey, setHooksContext } from '@zenstackhq/tanstack-query/runtime/svelte';`
);
sf.addStatements(`export { SvelteQueryContextKey, setHooksContext } from '${runtimeImportBase}/svelte';`);
break;
}
}
@ -443,23 +474,24 @@ function makeGetContext(target: TargetFramework) {
}
}
function makeBaseImports(target: TargetFramework) {
function makeBaseImports(target: TargetFramework, version: TanStackVersion) {
const runtimeImportBase = makeRuntimeImportBase(version);
const shared = [
`import { query, infiniteQuery, postMutation, putMutation, deleteMutation } from '@zenstackhq/tanstack-query/runtime/${target}';`,
`import type { PickEnumerable, CheckSelect } from '@zenstackhq/tanstack-query/runtime';`,
`import { query, infiniteQuery, postMutation, putMutation, deleteMutation } from '${runtimeImportBase}/${target}';`,
`import type { PickEnumerable, CheckSelect } from '${runtimeImportBase}';`,
];
switch (target) {
case 'react':
return [
`import { useContext } from 'react';`,
`import type { UseMutationOptions, UseQueryOptions, UseInfiniteQueryOptions } from '@tanstack/react-query';`,
`import { RequestHandlerContext } from '@zenstackhq/tanstack-query/runtime/${target}';`,
`import type { UseMutationOptions, UseQueryOptions, UseInfiniteQueryOptions, InfiniteData } from '@tanstack/react-query';`,
`import { RequestHandlerContext } from '${runtimeImportBase}/${target}';`,
...shared,
];
case 'vue':
return [
`import type { UseMutationOptions, UseQueryOptions, UseInfiniteQueryOptions } from '@tanstack/vue-query';`,
`import { getContext } from '@zenstackhq/tanstack-query/runtime/${target}';`,
`import type { UseMutationOptions, UseQueryOptions, UseInfiniteQueryOptions, InfiniteData } from '@tanstack/vue-query';`,
`import { getContext } from '${runtimeImportBase}/${target}';`,
...shared,
];
case 'svelte':
@ -467,7 +499,10 @@ function makeBaseImports(target: TargetFramework) {
`import { getContext } from 'svelte';`,
`import { derived } from 'svelte/store';`,
`import type { MutationOptions, QueryOptions, CreateInfiniteQueryOptions } from '@tanstack/svelte-query';`,
`import { SvelteQueryContextKey, type RequestHandlerContext } from '@zenstackhq/tanstack-query/runtime/${target}';`,
...(version === 'v5'
? [`import type { InfiniteData, StoreOrVal } from '@tanstack/svelte-query';`]
: []),
`import { SvelteQueryContextKey, type RequestHandlerContext } from '${runtimeImportBase}/${target}';`,
...shared,
];
default:
@ -475,13 +510,24 @@ function makeBaseImports(target: TargetFramework) {
}
}
function makeQueryOptions(target: string, returnType: string, infinite: boolean) {
function makeQueryOptions(target: string, returnType: string, infinite: boolean, version: TanStackVersion) {
switch (target) {
case 'react':
return infinite
? version === 'v4'
? `Omit<UseInfiniteQueryOptions<${returnType}>, 'queryKey'>`
: `Omit<UseInfiniteQueryOptions<${returnType}, unknown, InfiniteData<${returnType}>>, 'queryKey'>`
: `Omit<UseQueryOptions<${returnType}>, 'queryKey'>`;
case 'vue':
return `Use${infinite ? 'Infinite' : ''}QueryOptions<${returnType}>`;
return `Omit<Use${infinite ? 'Infinite' : ''}QueryOptions<${returnType}>, 'queryKey'>`;
case 'svelte':
return `${infinite ? 'CreateInfinite' : ''}QueryOptions<${returnType}>`;
return infinite
? version === 'v4'
? `Omit<CreateInfiniteQueryOptions<${returnType}>, 'queryKey'>`
: `StoreOrVal<Omit<CreateInfiniteQueryOptions<${returnType}, unknown, InfiniteData<${returnType}>>, 'queryKey'>>`
: version === 'v4'
? `Omit<QueryOptions<${returnType}>, 'queryKey'>`
: `StoreOrVal<Omit<QueryOptions<${returnType}>, 'queryKey'>>`;
default:
throw new PluginError(name, `Unsupported target: ${target}`);
}
@ -499,3 +545,7 @@ function makeMutationOptions(target: string, returnType: string, argsType: strin
throw new PluginError(name, `Unsupported target: ${target}`);
}
}
function makeRuntimeImportBase(version: TanStackVersion) {
return `@zenstackhq/tanstack-query/runtime${version === 'v5' ? '-v5' : ''}`;
}

View file

@ -0,0 +1,2 @@
export * from '../runtime/prisma-types';
export type { FetchFn } from '../runtime/common';

View file

@ -0,0 +1,211 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import {
useInfiniteQuery,
useMutation,
useQuery,
useQueryClient,
type InfiniteData,
type MutateFunction,
type QueryClient,
type UseInfiniteQueryOptions,
type UseMutationOptions,
type UseQueryOptions,
} from '@tanstack/react-query-v5';
import { createContext } from 'react';
import {
DEFAULT_QUERY_ENDPOINT,
FetchFn,
QUERY_KEY_PREFIX,
fetcher,
makeUrl,
marshal,
type APIContext,
} from '../runtime/common';
/**
* Context for configuring react hooks.
*/
export const RequestHandlerContext = createContext<APIContext>({
endpoint: DEFAULT_QUERY_ENDPOINT,
fetch: undefined,
});
/**
* Context provider.
*/
export const Provider = RequestHandlerContext.Provider;
/**
* Creates a react-query query.
*
* @param model The name of the model under query.
* @param url The request URL.
* @param args The request args object, URL-encoded and appended as "?q=" parameter
* @param options The react-query options object
* @returns useQuery hook
*/
export function query<R>(
model: string,
url: string,
args?: unknown,
options?: Omit<UseQueryOptions<R>, 'queryKey'>,
fetch?: FetchFn
) {
const reqUrl = makeUrl(url, args);
return useQuery({
queryKey: [QUERY_KEY_PREFIX + model, url, args],
queryFn: () => fetcher<R, false>(reqUrl, undefined, fetch, false),
...options,
});
}
/**
* Creates a react-query infinite query.
*
* @param model The name of the model under query.
* @param url The request URL.
* @param args The initial request args object, URL-encoded and appended as "?q=" parameter
* @param options The react-query infinite query options object
* @returns useInfiniteQuery hook
*/
export function infiniteQuery<R>(
model: string,
url: string,
args: unknown,
options: Omit<UseInfiniteQueryOptions<R, unknown, InfiniteData<R>>, 'queryKey'>,
fetch?: FetchFn
) {
return useInfiniteQuery({
queryKey: [QUERY_KEY_PREFIX + model, url, args],
queryFn: ({ pageParam }) => {
return fetcher<R, false>(makeUrl(url, pageParam ?? args), undefined, fetch, false);
},
...options,
});
}
/**
* Creates a POST mutation with react-query.
*
* @param model The name of the model under mutation.
* @param url The request URL.
* @param options The react-query options.
* @param invalidateQueries Whether to invalidate queries after mutation.
* @returns useMutation hooks
*/
export function postMutation<T, R = any, C extends boolean = boolean, Result = C extends true ? R | undefined : R>(
model: string,
url: string,
options?: Omit<UseMutationOptions<Result, unknown, T>, 'mutationFn'>,
fetch?: FetchFn,
invalidateQueries = true,
checkReadBack?: C
) {
const queryClient = useQueryClient();
const mutationFn = (data: any) =>
fetcher<R, C>(
url,
{
method: 'POST',
headers: {
'content-type': 'application/json',
},
body: marshal(data),
},
fetch,
checkReadBack
) as Promise<Result>;
const finalOptions = mergeOptions(model, options, invalidateQueries, mutationFn, queryClient);
const mutation = useMutation(finalOptions);
return mutation;
}
/**
* Creates a PUT mutation with react-query.
*
* @param model The name of the model under mutation.
* @param url The request URL.
* @param options The react-query options.
* @param invalidateQueries Whether to invalidate queries after mutation.
* @returns useMutation hooks
*/
export function putMutation<T, R = any, C extends boolean = boolean, Result = C extends true ? R | undefined : R>(
model: string,
url: string,
options?: Omit<UseMutationOptions<Result, unknown, T>, 'mutationFn'>,
fetch?: FetchFn,
invalidateQueries = true,
checkReadBack?: C
) {
const queryClient = useQueryClient();
const mutationFn = (data: any) =>
fetcher<R, C>(
url,
{
method: 'PUT',
headers: {
'content-type': 'application/json',
},
body: marshal(data),
},
fetch,
checkReadBack
) as Promise<Result>;
const finalOptions = mergeOptions(model, options, invalidateQueries, mutationFn, queryClient);
const mutation = useMutation(finalOptions);
return mutation;
}
/**
* Creates a DELETE mutation with react-query.
*
* @param model The name of the model under mutation.
* @param url The request URL.
* @param options The react-query options.
* @param invalidateQueries Whether to invalidate queries after mutation.
* @returns useMutation hooks
*/
export function deleteMutation<T, R = any, C extends boolean = boolean, Result = C extends true ? R | undefined : R>(
model: string,
url: string,
options?: Omit<UseMutationOptions<Result, unknown, T>, 'mutationFn'>,
fetch?: FetchFn,
invalidateQueries = true,
checkReadBack?: C
) {
const queryClient = useQueryClient();
const mutationFn = (data: any) =>
fetcher<R, C>(
makeUrl(url, data),
{
method: 'DELETE',
},
fetch,
checkReadBack
) as Promise<Result>;
const finalOptions = mergeOptions(model, options, invalidateQueries, mutationFn, queryClient);
const mutation = useMutation(finalOptions);
return mutation;
}
function mergeOptions<T, R = any>(
model: string,
options: Omit<UseMutationOptions<R, unknown, T, unknown>, 'mutationFn'> | undefined,
invalidateQueries: boolean,
mutationFn: MutateFunction<R, unknown, T>,
queryClient: QueryClient
): UseMutationOptions<R, unknown, T, unknown> {
const result = { ...options, mutationFn };
if (options?.onSuccess || invalidateQueries) {
result.onSuccess = (...args) => {
if (invalidateQueries) {
queryClient.invalidateQueries({ queryKey: [QUERY_KEY_PREFIX + model] });
}
return options?.onSuccess?.(...args);
};
}
return result;
}

View file

@ -0,0 +1,243 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import {
createInfiniteQuery,
createMutation,
createQuery,
useQueryClient,
type CreateInfiniteQueryOptions,
type InfiniteData,
type MutateFunction,
type MutationOptions,
type QueryClient,
type StoreOrVal,
} from '@tanstack/svelte-query-v5';
import { QueryOptions } from '@tanstack/vue-query';
import { setContext } from 'svelte';
import { Readable, derived } from 'svelte/store';
import { APIContext, FetchFn, QUERY_KEY_PREFIX, fetcher, makeUrl, marshal } from '../runtime/common';
export { APIContext as RequestHandlerContext } from '../runtime/common';
/**
* Key for setting and getting the global query context.
*/
export const SvelteQueryContextKey = 'zenstack-svelte-query-context';
/**
* Set context for the generated TanStack Query hooks.
*/
export function setHooksContext(context: APIContext) {
setContext(SvelteQueryContextKey, context);
}
/**
* Creates a svelte-query query.
*
* @param model The name of the model under query.
* @param url The request URL.
* @param args The request args object, URL-encoded and appended as "?q=" parameter
* @param options The svelte-query options object
* @returns useQuery hook
*/
export function query<R>(
model: string,
url: string,
args?: unknown,
options?: StoreOrVal<Omit<QueryOptions<R>, 'queryKey'>>,
fetch?: FetchFn
) {
const reqUrl = makeUrl(url, args);
const queryKey = [QUERY_KEY_PREFIX + model, url, args];
const queryFn = () => fetcher<R, false>(reqUrl, undefined, fetch, false);
let mergedOpt: any;
if (isStore(options)) {
// options is store
mergedOpt = derived([options], ([$opt]) => {
return {
queryKey,
queryFn,
...($opt as object),
};
});
} else {
// options is value
mergedOpt = {
queryKey,
queryFn,
...options,
};
}
return createQuery(mergedOpt);
}
/**
* Creates a svelte-query infinite query.
*
* @param model The name of the model under query.
* @param url The request URL.
* @param args The initial request args object, URL-encoded and appended as "?q=" parameter
* @param options The svelte-query infinite query options object
* @returns useQuery hook
*/
export function infiniteQuery<R>(
model: string,
url: string,
args: unknown,
options: StoreOrVal<Omit<CreateInfiniteQueryOptions<R, unknown, InfiniteData<R>>, 'queryKey'>>,
fetch?: FetchFn
) {
const queryKey = [QUERY_KEY_PREFIX + model, url, args];
const queryFn = ({ pageParam }: { pageParam: unknown }) =>
fetcher<R, false>(makeUrl(url, pageParam ?? args), undefined, fetch, false);
let mergedOpt: StoreOrVal<CreateInfiniteQueryOptions<R, unknown, InfiniteData<R>>>;
if (isStore<CreateInfiniteQueryOptions<R, unknown, InfiniteData<R>>>(options)) {
// options is store
mergedOpt = derived([options], ([$opt]) => {
return {
queryKey,
queryFn,
...$opt,
};
});
} else {
// options is value
mergedOpt = {
queryKey,
queryFn,
...options,
};
}
return createInfiniteQuery<R, unknown, InfiniteData<R>>(mergedOpt);
}
function isStore<T>(opt: unknown): opt is Readable<T> {
return typeof (opt as any)?.subscribe === 'function';
}
/**
* Creates a POST mutation with svelte-query.
*
* @param model The name of the model under mutation.
* @param url The request URL.
* @param options The svelte-query options.
* @param invalidateQueries Whether to invalidate queries after mutation.
* @returns useMutation hooks
*/
export function postMutation<T, R = any, C extends boolean = boolean, Result = C extends true ? R | undefined : R>(
model: string,
url: string,
options?: Omit<MutationOptions<Result, unknown, T>, 'mutationFn'>,
fetch?: FetchFn,
invalidateQueries = true,
checkReadBack?: C
) {
const queryClient = useQueryClient();
const mutationFn = (data: any) =>
fetcher<R, C>(
url,
{
method: 'POST',
headers: {
'content-type': 'application/json',
},
body: marshal(data),
},
fetch,
checkReadBack
) as Promise<Result>;
const finalOptions = mergeOptions(model, options, invalidateQueries, mutationFn, queryClient);
const mutation = createMutation(finalOptions);
return mutation;
}
/**
* Creates a PUT mutation with svelte-query.
*
* @param model The name of the model under mutation.
* @param url The request URL.
* @param options The svelte-query options.
* @param invalidateQueries Whether to invalidate queries after mutation.
* @returns useMutation hooks
*/
export function putMutation<T, R = any, C extends boolean = boolean, Result = C extends true ? R | undefined : R>(
model: string,
url: string,
options?: Omit<MutationOptions<Result, unknown, T>, 'mutationFn'>,
fetch?: FetchFn,
invalidateQueries = true,
checkReadBack?: C
) {
const queryClient = useQueryClient();
const mutationFn = (data: any) =>
fetcher<R, C>(
url,
{
method: 'PUT',
headers: {
'content-type': 'application/json',
},
body: marshal(data),
},
fetch,
checkReadBack
) as Promise<Result>;
const finalOptions = mergeOptions(model, options, invalidateQueries, mutationFn, queryClient);
const mutation = createMutation(finalOptions);
return mutation;
}
/**
* Creates a DELETE mutation with svelte-query.
*
* @param model The name of the model under mutation.
* @param url The request URL.
* @param options The svelte-query options.
* @param invalidateQueries Whether to invalidate queries after mutation.
* @returns useMutation hooks
*/
export function deleteMutation<T, R = any, C extends boolean = boolean, Result = C extends true ? R | undefined : R>(
model: string,
url: string,
options?: Omit<MutationOptions<Result, unknown, T>, 'mutationFn'>,
fetch?: FetchFn,
invalidateQueries = true,
checkReadBack?: C
) {
const queryClient = useQueryClient();
const mutationFn = (data: any) =>
fetcher<R, C>(
makeUrl(url, data),
{
method: 'DELETE',
},
fetch,
checkReadBack
) as Promise<Result>;
const finalOptions = mergeOptions(model, options, invalidateQueries, mutationFn, queryClient);
const mutation = createMutation(finalOptions);
return mutation;
}
function mergeOptions<T, R = any>(
model: string,
options: Omit<MutationOptions<R, unknown, T, unknown>, 'mutationFn'> | undefined,
invalidateQueries: boolean,
mutationFn: MutateFunction<R, unknown, T>,
queryClient: QueryClient
): MutationOptions<R, unknown, T, unknown> {
const result = { ...options, mutationFn };
if (options?.onSuccess || invalidateQueries) {
result.onSuccess = (...args) => {
if (invalidateQueries) {
queryClient.invalidateQueries({ queryKey: [QUERY_KEY_PREFIX + model] });
}
return options?.onSuccess?.(...args);
};
}
return result;
}

View file

@ -0,0 +1 @@
export * from '../runtime/vue';

View file

@ -43,7 +43,13 @@ export const Provider = RequestHandlerContext.Provider;
* @param options The react-query options object
* @returns useQuery hook
*/
export function query<R>(model: string, url: string, args?: unknown, options?: UseQueryOptions<R>, fetch?: FetchFn) {
export function query<R>(
model: string,
url: string,
args?: unknown,
options?: Omit<UseQueryOptions<R>, 'queryKey'>,
fetch?: FetchFn
) {
const reqUrl = makeUrl(url, args);
return useQuery<R>({
queryKey: [QUERY_KEY_PREFIX + model, url, args],
@ -65,7 +71,7 @@ export function infiniteQuery<R>(
model: string,
url: string,
args?: unknown,
options?: UseInfiniteQueryOptions<R>,
options?: Omit<UseInfiniteQueryOptions<R>, 'queryKey'>,
fetch?: FetchFn
) {
return useInfiniteQuery<R>({
@ -109,8 +115,8 @@ export function postMutation<T, R = any, C extends boolean = boolean, Result = C
checkReadBack
) as Promise<Result>;
const finalOptions = mergeOptions<T, Result>(model, options, invalidateQueries, mutationFn, queryClient);
const mutation = useMutation<Result, unknown, T>(finalOptions);
const finalOptions = mergeOptions(model, options, invalidateQueries, mutationFn, queryClient);
const mutation = useMutation(finalOptions);
return mutation;
}
@ -146,8 +152,8 @@ export function putMutation<T, R = any, C extends boolean = boolean, Result = C
checkReadBack
) as Promise<Result>;
const finalOptions = mergeOptions<T, Result>(model, options, invalidateQueries, mutationFn, queryClient);
const mutation = useMutation<Result, unknown, T>(finalOptions);
const finalOptions = mergeOptions(model, options, invalidateQueries, mutationFn, queryClient);
const mutation = useMutation(finalOptions);
return mutation;
}
@ -179,8 +185,8 @@ export function deleteMutation<T, R = any, C extends boolean = boolean, Result =
checkReadBack
) as Promise<Result>;
const finalOptions = mergeOptions<T, Result>(model, options, invalidateQueries, mutationFn, queryClient);
const mutation = useMutation<Result, unknown, T>(finalOptions);
const finalOptions = mergeOptions(model, options, invalidateQueries, mutationFn, queryClient);
const mutation = useMutation(finalOptions);
return mutation;
}

View file

@ -36,7 +36,13 @@ export function setHooksContext(context: APIContext) {
* @param options The svelte-query options object
* @returns useQuery hook
*/
export function query<R>(model: string, url: string, args?: unknown, options?: QueryOptions<R>, fetch?: FetchFn) {
export function query<R>(
model: string,
url: string,
args?: unknown,
options?: Omit<QueryOptions<R>, 'queryKey'>,
fetch?: FetchFn
) {
const reqUrl = makeUrl(url, args);
return createQuery<R>({
queryKey: [QUERY_KEY_PREFIX + model, url, args],
@ -58,7 +64,7 @@ export function infiniteQuery<R>(
model: string,
url: string,
args?: unknown,
options?: CreateInfiniteQueryOptions<R>,
options?: Omit<CreateInfiniteQueryOptions<R>, 'queryKey'>,
fetch?: FetchFn
) {
return createInfiniteQuery<R>({
@ -100,8 +106,8 @@ export function postMutation<T, R = any, C extends boolean = boolean, Result = C
checkReadBack
) as Promise<Result>;
const finalOptions = mergeOptions<T, Result>(model, options, invalidateQueries, mutationFn, queryClient);
const mutation = createMutation<Result, unknown, T>(finalOptions);
const finalOptions = mergeOptions(model, options, invalidateQueries, mutationFn, queryClient);
const mutation = createMutation(finalOptions);
return mutation;
}
@ -137,8 +143,8 @@ export function putMutation<T, R = any, C extends boolean = boolean, Result = C
checkReadBack
) as Promise<Result>;
const finalOptions = mergeOptions<T, Result>(model, options, invalidateQueries, mutationFn, queryClient);
const mutation = createMutation<Result, unknown, T>(finalOptions);
const finalOptions = mergeOptions(model, options, invalidateQueries, mutationFn, queryClient);
const mutation = createMutation(finalOptions);
return mutation;
}
@ -170,8 +176,8 @@ export function deleteMutation<T, R = any, C extends boolean = boolean, Result =
checkReadBack
) as Promise<Result>;
const finalOptions = mergeOptions<T, Result>(model, options, invalidateQueries, mutationFn, queryClient);
const mutation = createMutation<Result, unknown, T>(finalOptions);
const finalOptions = mergeOptions(model, options, invalidateQueries, mutationFn, queryClient);
const mutation = createMutation(finalOptions);
return mutation;
}

View file

@ -45,7 +45,7 @@ model Foo {
}
`;
it('react-query run plugin', async () => {
it('react-query run plugin v4', async () => {
await loadSchema(
`
plugin tanstack {
@ -59,18 +59,36 @@ ${sharedModel}
{
provider: 'postgresql',
pushDb: false,
extraDependencies: [
`${origDir}/dist`,
'react@18.2.0',
'@types/react@18.2.0',
'@tanstack/react-query@4.29.7',
],
extraDependencies: ['react@18.2.0', '@types/react@18.2.0', '@tanstack/react-query@4.29.7'],
copyDependencies: [`${origDir}/dist`],
compile: true,
}
);
});
it('vue-query run plugin', async () => {
it('react-query run plugin v5', async () => {
await loadSchema(
`
plugin tanstack {
provider = '${process.cwd()}/dist'
output = '$projectRoot/hooks'
target = 'react'
version = 'v5'
}
${sharedModel}
`,
{
provider: 'postgresql',
pushDb: false,
extraDependencies: ['react@18.2.0', '@types/react@18.2.0', '@tanstack/react-query@^5.0.0'],
copyDependencies: [`${origDir}/dist`],
compile: true,
}
);
});
it('vue-query run plugin v4', async () => {
await loadSchema(
`
plugin tanstack {
@ -84,13 +102,36 @@ ${sharedModel}
{
provider: 'postgresql',
pushDb: false,
extraDependencies: [`${origDir}/dist`, 'vue@^3.3.4', '@tanstack/vue-query@4.37.0'],
extraDependencies: ['vue@^3.3.4', '@tanstack/vue-query@4.37.0'],
copyDependencies: [`${origDir}/dist`],
compile: true,
}
);
});
it('svelte-query run plugin', async () => {
it('vue-query run plugin v5', async () => {
await loadSchema(
`
plugin tanstack {
provider = '${process.cwd()}/dist'
output = '$projectRoot/hooks'
target = 'vue'
version = 'v5'
}
${sharedModel}
`,
{
provider: 'postgresql',
pushDb: false,
extraDependencies: ['vue@^3.3.4', '@tanstack/vue-query@latest'],
copyDependencies: [`${origDir}/dist`],
compile: true,
}
);
});
it('svelte-query run plugin v4', async () => {
await loadSchema(
`
plugin tanstack {
@ -104,7 +145,30 @@ ${sharedModel}
{
provider: 'postgresql',
pushDb: false,
extraDependencies: [`${origDir}/dist`, 'svelte@^3.0.0', '@tanstack/svelte-query@4.29.7'],
extraDependencies: ['svelte@^3.0.0', '@tanstack/svelte-query@4.29.7'],
copyDependencies: [`${origDir}/dist`],
compile: true,
}
);
});
it('svelte-query run plugin v5', async () => {
await loadSchema(
`
plugin tanstack {
provider = '${process.cwd()}/dist'
output = '$projectRoot/hooks'
target = 'svelte'
version = 'v5'
}
${sharedModel}
`,
{
provider: 'postgresql',
pushDb: false,
extraDependencies: ['svelte@^3.0.0', '@tanstack/svelte-query@^5.0.0'],
copyDependencies: [`${origDir}/dist`],
compile: true,
}
);

View file

@ -0,0 +1,11 @@
import { defineConfig } from 'tsup';
export default defineConfig({
entry: ['src/runtime-v5/index.ts', 'src/runtime-v5/react.ts', 'src/runtime-v5/vue.ts', 'src/runtime-v5/svelte.ts'],
outDir: 'dist/runtime-v5',
splitting: false,
sourcemap: true,
clean: true,
dts: true,
format: ['cjs', 'esm'],
});

View file

@ -96,6 +96,7 @@ export type SchemaLoadOptions = {
pushDb?: boolean;
fullZod?: boolean;
extraDependencies?: string[];
copyDependencies?: string[];
compile?: boolean;
customSchemaFilePath?: string;
output?: string;
@ -202,6 +203,11 @@ export async function loadSchema(schema: string, options?: SchemaLoadOptions) {
run(`npm install ${dep}`);
});
opt.copyDependencies?.forEach((dep) => {
const pkgJson = JSON.parse(fs.readFileSync(path.join(dep, 'package.json'), { encoding: 'utf-8' }));
fs.cpSync(dep, path.join(projectRoot, 'node_modules', pkgJson.name), { recursive: true, force: true });
});
const PrismaClient = require(path.join(projectRoot, 'node_modules/.prisma/client')).PrismaClient;
let prisma = new PrismaClient({ log: ['info', 'warn', 'error'] });

View file

@ -246,9 +246,15 @@ importers:
'@tanstack/react-query':
specifier: ^4.29.7
version: 4.29.7(react-dom@18.2.0)(react@18.2.0)
'@tanstack/react-query-v5':
specifier: npm:@tanstack/react-query@^5.0.0
version: /@tanstack/react-query@5.0.5(react@18.2.0)
'@tanstack/svelte-query':
specifier: ^4.29.7
version: 4.29.7(svelte@4.2.1)
'@tanstack/svelte-query-v5':
specifier: npm:@tanstack/svelte-query@^5.0.0
version: /@tanstack/svelte-query@5.3.0(svelte@4.2.1)
'@tanstack/vue-query':
specifier: ^4.37.0
version: 4.37.0(vue@3.3.4)
@ -279,6 +285,9 @@ importers:
react:
specifier: 18.2.0
version: 18.2.0
replace-in-file:
specifier: ^7.0.1
version: 7.0.1
rimraf:
specifier: ^3.0.2
version: 3.0.2
@ -714,7 +723,7 @@ importers:
devDependencies:
'@sveltejs/kit':
specifier: 1.21.0
version: 1.21.0(svelte@4.0.5)(vite@4.4.11)
version: 1.21.0(svelte@4.2.1)(vite@4.4.11)
'@types/body-parser':
specifier: ^1.19.2
version: 1.19.2
@ -4168,7 +4177,7 @@ packages:
'@sinonjs/commons': 3.0.0
dev: true
/@sveltejs/kit@1.21.0(svelte@4.0.5)(vite@4.4.11):
/@sveltejs/kit@1.21.0(svelte@4.2.1)(vite@4.4.11):
resolution: {integrity: sha512-CBsYoI34SjtOQp0eG85dmVnvTR3Pjs8VgAQhO0CgQja9BIorKl808F1X8EunPhCcyek5r5lKQE1Mmbi0RuzHqA==}
engines: {node: ^16.14 || >=18}
hasBin: true
@ -4177,7 +4186,7 @@ packages:
svelte: ^3.54.0 || ^4.0.0-next.0
vite: ^4.0.0
dependencies:
'@sveltejs/vite-plugin-svelte': 2.4.2(svelte@4.0.5)(vite@4.4.11)
'@sveltejs/vite-plugin-svelte': 2.4.2(svelte@4.2.1)(vite@4.4.11)
'@types/cookie': 0.5.1
cookie: 0.5.0
devalue: 4.3.2
@ -4188,14 +4197,14 @@ packages:
sade: 1.8.1
set-cookie-parser: 2.6.0
sirv: 2.0.3
svelte: 4.0.5
svelte: 4.2.1
undici: 5.22.1
vite: 4.4.11(@types/node@18.0.0)
transitivePeerDependencies:
- supports-color
dev: true
/@sveltejs/vite-plugin-svelte-inspector@1.0.3(@sveltejs/vite-plugin-svelte@2.4.2)(svelte@4.0.5)(vite@4.4.11):
/@sveltejs/vite-plugin-svelte-inspector@1.0.3(@sveltejs/vite-plugin-svelte@2.4.2)(svelte@4.2.1)(vite@4.4.11):
resolution: {integrity: sha512-Khdl5jmmPN6SUsVuqSXatKpQTMIifoQPDanaxC84m9JxIibWvSABJyHpyys0Z+1yYrxY5TTEQm+6elh0XCMaOA==}
engines: {node: ^14.18.0 || >= 16}
peerDependencies:
@ -4203,28 +4212,28 @@ packages:
svelte: ^3.54.0 || ^4.0.0
vite: ^4.0.0
dependencies:
'@sveltejs/vite-plugin-svelte': 2.4.2(svelte@4.0.5)(vite@4.4.11)
'@sveltejs/vite-plugin-svelte': 2.4.2(svelte@4.2.1)(vite@4.4.11)
debug: 4.3.4
svelte: 4.0.5
svelte: 4.2.1
vite: 4.4.11(@types/node@18.0.0)
transitivePeerDependencies:
- supports-color
dev: true
/@sveltejs/vite-plugin-svelte@2.4.2(svelte@4.0.5)(vite@4.4.11):
/@sveltejs/vite-plugin-svelte@2.4.2(svelte@4.2.1)(vite@4.4.11):
resolution: {integrity: sha512-ePfcC48ftMKhkT0OFGdOyycYKnnkT6i/buzey+vHRTR/JpQvuPzzhf1PtKqCDQfJRgoPSN2vscXs6gLigx/zGw==}
engines: {node: ^14.18.0 || >= 16}
peerDependencies:
svelte: ^3.54.0 || ^4.0.0
vite: ^4.0.0
dependencies:
'@sveltejs/vite-plugin-svelte-inspector': 1.0.3(@sveltejs/vite-plugin-svelte@2.4.2)(svelte@4.0.5)(vite@4.4.11)
'@sveltejs/vite-plugin-svelte-inspector': 1.0.3(@sveltejs/vite-plugin-svelte@2.4.2)(svelte@4.2.1)(vite@4.4.11)
debug: 4.3.4
deepmerge: 4.3.1
kleur: 4.1.5
magic-string: 0.30.4
svelte: 4.0.5
svelte-hmr: 0.15.2(svelte@4.0.5)
svelte: 4.2.1
svelte-hmr: 0.15.2(svelte@4.2.1)
vite: 4.4.11(@types/node@18.0.0)
vitefu: 0.2.4(vite@4.4.11)
transitivePeerDependencies:
@ -4262,6 +4271,10 @@ packages:
resolution: {integrity: sha512-DJSilV5+ytBP1FbFcEJovv4rnnm/CokuVvrBEtW/Va9DvuJ3HksbXUJEpI0aV1KtuL4ZoO9AVE6PyNLzF7tLeA==}
dev: true
/@tanstack/query-core@5.0.5:
resolution: {integrity: sha512-MThCETMkHDHTnFZHp71L+SqTtD5d6XHftFCVR1xRJdWM3qGrlQ2VCXaj0SKVcyJej2e1Opa2c7iknu1llxCDNQ==}
dev: true
/@tanstack/react-query@4.28.0(react@18.2.0):
resolution: {integrity: sha512-8cGBV5300RHlvYdS4ea+G1JcZIt5CIuprXYFnsWggkmGoC0b5JaqG0fIX3qwDL9PTNkKvG76NGThIWbpXivMrQ==}
peerDependencies:
@ -4297,6 +4310,22 @@ packages:
use-sync-external-store: 1.2.0(react@18.2.0)
dev: true
/@tanstack/react-query@5.0.5(react@18.2.0):
resolution: {integrity: sha512-ZG0Q4HZ0iuI8mWiZ2/MdVYPHbrmAVhMn7+gLOkxJh6zLIgCL4luSZlohzN5Xt4MjxfxxWioO1nemwpudaTsmQg==}
peerDependencies:
react: ^18.0.0
react-dom: ^18.0.0
react-native: '*'
peerDependenciesMeta:
react-dom:
optional: true
react-native:
optional: true
dependencies:
'@tanstack/query-core': 5.0.5
react: 18.2.0
dev: true
/@tanstack/svelte-query@4.29.7(svelte@4.2.1):
resolution: {integrity: sha512-+GvOcGEiFZYj91luPcIIVakmDqXkS5tyWlBLf/zg9E9hohq7SiGug9C9DUWwvSBk80JefAOCioja5jrumrPp6Q==}
peerDependencies:
@ -4306,6 +4335,15 @@ packages:
svelte: 4.2.1
dev: true
/@tanstack/svelte-query@5.3.0(svelte@4.2.1):
resolution: {integrity: sha512-gGC2jh7VV2F2rZygLVbvGxYbwFIW7d3rrY8iuozDOHvBft/y8/rU9aNzm/vrOdsPt2TMF3IgS3diDlp+21z6Lw==}
peerDependencies:
svelte: '>=3 <5'
dependencies:
'@tanstack/query-core': 5.0.5
svelte: 4.2.1
dev: true
/@tanstack/vue-query@4.37.0(vue@3.3.4):
resolution: {integrity: sha512-ZZvlEuXzCwM9QYS1GCf9FTo1XycBxPSvjJbIHXTU2iZuUla02EzTO7R9QOnML6FRQRzD3n5Eyn2v6zbhCHS/qA==}
peerDependencies:
@ -13112,32 +13150,13 @@ packages:
resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==}
engines: {node: '>= 0.4'}
/svelte-hmr@0.15.2(svelte@4.0.5):
/svelte-hmr@0.15.2(svelte@4.2.1):
resolution: {integrity: sha512-q/bAruCvFLwvNbeE1x3n37TYFb3mTBJ6TrCq6p2CoFbSTNhDE9oAtEfpy+wmc9So8AG0Tja+X0/mJzX9tSfvIg==}
engines: {node: ^12.20 || ^14.13.1 || >= 16}
peerDependencies:
svelte: ^3.19.0 || ^4.0.0-next.0
dependencies:
svelte: 4.0.5
dev: true
/svelte@4.0.5:
resolution: {integrity: sha512-PHKPWP1wiWHBtsE57nCb8xiWB3Ht7/3Kvi3jac0XIxUM2rep8alO7YoAtgWeGD7++tFy46krilOrPW0mG3Dx+A==}
engines: {node: '>=16'}
dependencies:
'@ampproject/remapping': 2.2.1
'@jridgewell/sourcemap-codec': 1.4.15
'@jridgewell/trace-mapping': 0.3.18
acorn: 8.10.0
aria-query: 5.3.0
axobject-query: 3.2.1
code-red: 1.0.3
css-tree: 2.3.1
estree-walker: 3.0.3
is-reference: 3.0.1
locate-character: 3.0.0
magic-string: 0.30.4
periscopic: 3.1.0
svelte: 4.2.1
dev: true
/svelte@4.2.1: