Skip to content

Commit

Permalink
feat: tanstack-query v5 support (#788)
Browse files Browse the repository at this point in the history
  • Loading branch information
ymc9 authored Oct 29, 2023
1 parent 8a370db commit 0d04d8e
Show file tree
Hide file tree
Showing 13 changed files with 759 additions and 89 deletions.
31 changes: 29 additions & 2 deletions packages/plugins/tanstack-query/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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": {
Expand All @@ -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",
Expand Down Expand Up @@ -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",
Expand All @@ -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",
Expand Down
24 changes: 24 additions & 0 deletions packages/plugins/tanstack-query/scripts/postbuild.js
Original file line number Diff line number Diff line change
@@ -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',
});
106 changes: 78 additions & 28 deletions packages/plugins/tanstack-query/src/generator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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');
Expand All @@ -38,15 +39,20 @@ 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);
if (!mapping) {
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);
Expand All @@ -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,
Expand All @@ -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}`,
Expand All @@ -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);`,
]);
Expand Down Expand Up @@ -217,6 +229,7 @@ function generateMutationHook(

function generateModelHooks(
target: TargetFramework,
version: TanStackVersion,
project: Project,
outDir: string,
model: DataModel,
Expand All @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -301,6 +326,7 @@ function generateModelHooks(
if (mapping.aggregate) {
generateQueryHook(
target,
version,
sf,
modelNameCap,
'aggregate',
Expand Down Expand Up @@ -385,6 +411,7 @@ function generateModelHooks(

generateQueryHook(
target,
version,
sf,
model.name,
'groupBy',
Expand All @@ -400,6 +427,7 @@ function generateModelHooks(
{
generateQueryHook(
target,
version,
sf,
model.name,
'count',
Expand All @@ -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;
}
}
Expand All @@ -443,45 +474,60 @@ 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':
return [
`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:
throw new PluginError(name, `Unsupported target: ${target}`);
}
}

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}`);
}
Expand All @@ -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' : ''}`;
}
2 changes: 2 additions & 0 deletions packages/plugins/tanstack-query/src/runtime-v5/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export * from '../runtime/prisma-types';
export type { FetchFn } from '../runtime/common';
Loading

0 comments on commit 0d04d8e

Please sign in to comment.