Skip to content

Commit 0d04d8e

Browse files
authored
feat: tanstack-query v5 support (#788)
1 parent 8a370db commit 0d04d8e

File tree

13 files changed

+759
-89
lines changed

13 files changed

+759
-89
lines changed

packages/plugins/tanstack-query/package.json

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,30 @@
3434
"require": "./runtime/svelte.js",
3535
"default": "./runtime/svelte.js",
3636
"types": "./runtime/svelte.d.ts"
37+
},
38+
"./runtime-v5": {
39+
"import": "./runtime-v5/index.mjs",
40+
"require": "./runtime-v5/index.js",
41+
"default": "./runtime-v5/index.js",
42+
"types": "./runtime-v5/index.d.ts"
43+
},
44+
"./runtime-v5/react": {
45+
"import": "./runtime-v5/react.mjs",
46+
"require": "./runtime-v5/react.js",
47+
"default": "./runtime-v5/react.js",
48+
"types": "./runtime-v5/react.d.ts"
49+
},
50+
"./runtime-v5/vue": {
51+
"import": "./runtime-v5/vue.mjs",
52+
"require": "./runtime-v5/vue.js",
53+
"default": "./runtime-v5/vue.js",
54+
"types": "./runtime-v5/vue.d.ts"
55+
},
56+
"./runtime-v5/svelte": {
57+
"import": "./runtime-v5/svelte.mjs",
58+
"require": "./runtime-v5/svelte.js",
59+
"default": "./runtime-v5/svelte.js",
60+
"types": "./runtime-v5/svelte.d.ts"
3761
}
3862
},
3963
"repository": {
@@ -42,8 +66,8 @@
4266
},
4367
"scripts": {
4468
"clean": "rimraf dist",
45-
"build": "pnpm lint && pnpm clean && tsc && tsup-node && copyfiles ./package.json ./README.md ./LICENSE dist && pnpm pack dist --pack-destination '../../../../.build'",
46-
"watch": "concurrently \"tsc --watch\" \"tsup-node --watch\"",
69+
"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'",
70+
"watch": "concurrently \"tsc --watch\" \"tsup-node --config ./tsup.config.ts --watch\" \"tsup-node --config ./tsup-v5.config.ts --watch\"",
4771
"lint": "eslint src --ext ts",
4872
"test": "ZENSTACK_TEST=1 jest",
4973
"prepublishOnly": "pnpm build",
@@ -71,7 +95,9 @@
7195
},
7296
"devDependencies": {
7397
"@tanstack/react-query": "^4.29.7",
98+
"@tanstack/react-query-v5": "npm:@tanstack/react-query@^5.0.0",
7499
"@tanstack/svelte-query": "^4.29.7",
100+
"@tanstack/svelte-query-v5": "npm:@tanstack/svelte-query@^5.0.0",
75101
"@tanstack/vue-query": "^4.37.0",
76102
"@types/jest": "^29.5.0",
77103
"@types/node": "^18.0.0",
@@ -82,6 +108,7 @@
82108
"copyfiles": "^2.4.1",
83109
"jest": "^29.5.0",
84110
"react": "18.2.0",
111+
"replace-in-file": "^7.0.1",
85112
"rimraf": "^3.0.2",
86113
"svelte": "^4.2.1",
87114
"swr": "^2.0.3",
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
// tsup doesn't replace npm dependency aliases in the dist files, so we have to do it manually
2+
3+
const replace = require('replace-in-file');
4+
5+
console.log('Replacing @tanstack/react-query-v5');
6+
replace.sync({
7+
files: 'dist/runtime-v5/react*(.d.ts|.d.mts|.js|.mjs)',
8+
from: /@tanstack\/react-query-v5/g,
9+
to: '@tanstack/react-query',
10+
});
11+
12+
console.log('Replacing @tanstack/svelte-query-v5');
13+
replace.sync({
14+
files: 'dist/runtime-v5/svelte*(.d.ts|.d.mts|.js|.mjs)',
15+
from: /@tanstack\/svelte-query-v5/g,
16+
to: '@tanstack/svelte-query',
17+
});
18+
19+
console.log('Replacing @tanstack/vue-query-v5');
20+
replace.sync({
21+
files: 'dist/runtime-v5/vue*(.d.ts|.d.mts|.js|.mjs)',
22+
from: /@tanstack\/vue-query-v5/g,
23+
to: '@tanstack/vue-query',
24+
});

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

Lines changed: 78 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import { name } from '.';
2121

2222
const supportedTargets = ['react', 'vue', 'svelte'];
2323
type TargetFramework = (typeof supportedTargets)[number];
24+
type TanStackVersion = 'v4' | 'v5';
2425

2526
export async function generate(model: Model, options: PluginOptions, dmmf: DMMF.Document) {
2627
let outDir = requireOption<string>(options, 'output');
@@ -38,15 +39,20 @@ export async function generate(model: Model, options: PluginOptions, dmmf: DMMF.
3839
);
3940
}
4041

41-
generateIndex(project, outDir, models, target);
42+
const version = typeof options.version === 'string' ? options.version : 'v4';
43+
if (version !== 'v4' && version !== 'v5') {
44+
throw new PluginError(options.name, `Unsupported version "${version}": use "v4" or "v5"`);
45+
}
46+
47+
generateIndex(project, outDir, models, target, version);
4248

4349
models.forEach((dataModel) => {
4450
const mapping = dmmf.mappings.modelOperations.find((op) => op.model === dataModel.name);
4551
if (!mapping) {
4652
warnings.push(`Unable to find mapping for model ${dataModel.name}`);
4753
return;
4854
}
49-
generateModelHooks(target, project, outDir, dataModel, mapping);
55+
generateModelHooks(target, version, project, outDir, dataModel, mapping);
5056
});
5157

5258
await saveProject(project);
@@ -55,6 +61,7 @@ export async function generate(model: Model, options: PluginOptions, dmmf: DMMF.
5561

5662
function generateQueryHook(
5763
target: TargetFramework,
64+
version: TanStackVersion,
5865
sf: SourceFile,
5966
model: string,
6067
operation: string,
@@ -71,7 +78,7 @@ function generateQueryHook(
7178
const inputType = `Prisma.SelectSubset<T, ${argsType}>`;
7279
const returnType =
7380
overrideReturnType ?? (returnArray ? `Array<Prisma.${model}GetPayload<T>>` : `Prisma.${model}GetPayload<T>`);
74-
const optionsType = makeQueryOptions(target, returnType, infinite);
81+
const optionsType = makeQueryOptions(target, returnType, infinite, version);
7582

7683
const func = sf.addFunction({
7784
name: `use${infinite ? 'Infinite' : ''}${capOperation}${model}`,
@@ -89,9 +96,14 @@ function generateQueryHook(
8996
isExported: true,
9097
});
9198

99+
if (version === 'v5' && infinite && ['react', 'svelte'].includes(target)) {
100+
// initialPageParam and getNextPageParam options are required in v5
101+
func.addStatements([`options = options ?? { initialPageParam: undefined, getNextPageParam: () => null };`]);
102+
}
103+
92104
func.addStatements([
93105
makeGetContext(target),
94-
`return ${infinite ? 'infiniteQuery' : 'query'}<${returnType}>('${model}', \`\${endpoint}/${lowerCaseFirst(
106+
`return ${infinite ? 'infiniteQuery' : 'query'}('${model}', \`\${endpoint}/${lowerCaseFirst(
95107
model
96108
)}/${operation}\`, args, options, fetch);`,
97109
]);
@@ -217,6 +229,7 @@ function generateMutationHook(
217229

218230
function generateModelHooks(
219231
target: TargetFramework,
232+
version: TanStackVersion,
220233
project: Project,
221234
outDir: string,
222235
model: DataModel,
@@ -235,7 +248,7 @@ function generateModelHooks(
235248
isTypeOnly: true,
236249
moduleSpecifier: prismaImport,
237250
});
238-
sf.addStatements(makeBaseImports(target));
251+
sf.addStatements(makeBaseImports(target, version));
239252

240253
// create is somehow named "createOne" in the DMMF
241254
// eslint-disable-next-line @typescript-eslint/no-explicit-any
@@ -251,19 +264,31 @@ function generateModelHooks(
251264
// findMany
252265
if (mapping.findMany) {
253266
// regular findMany
254-
generateQueryHook(target, sf, model.name, 'findMany', true, true);
267+
generateQueryHook(target, version, sf, model.name, 'findMany', true, true);
255268
// infinite findMany
256-
generateQueryHook(target, sf, model.name, 'findMany', true, true, undefined, undefined, undefined, true);
269+
generateQueryHook(
270+
target,
271+
version,
272+
sf,
273+
model.name,
274+
'findMany',
275+
true,
276+
true,
277+
undefined,
278+
undefined,
279+
undefined,
280+
true
281+
);
257282
}
258283

259284
// findUnique
260285
if (mapping.findUnique) {
261-
generateQueryHook(target, sf, model.name, 'findUnique', false, false);
286+
generateQueryHook(target, version, sf, model.name, 'findUnique', false, false);
262287
}
263288

264289
// findFirst
265290
if (mapping.findFirst) {
266-
generateQueryHook(target, sf, model.name, 'findFirst', false, true);
291+
generateQueryHook(target, version, sf, model.name, 'findFirst', false, true);
267292
}
268293

269294
// update
@@ -301,6 +326,7 @@ function generateModelHooks(
301326
if (mapping.aggregate) {
302327
generateQueryHook(
303328
target,
329+
version,
304330
sf,
305331
modelNameCap,
306332
'aggregate',
@@ -385,6 +411,7 @@ function generateModelHooks(
385411

386412
generateQueryHook(
387413
target,
414+
version,
388415
sf,
389416
model.name,
390417
'groupBy',
@@ -400,6 +427,7 @@ function generateModelHooks(
400427
{
401428
generateQueryHook(
402429
target,
430+
version,
403431
sf,
404432
model.name,
405433
'count',
@@ -410,22 +438,25 @@ function generateModelHooks(
410438
}
411439
}
412440

413-
function generateIndex(project: Project, outDir: string, models: DataModel[], target: string) {
441+
function generateIndex(
442+
project: Project,
443+
outDir: string,
444+
models: DataModel[],
445+
target: string,
446+
version: TanStackVersion
447+
) {
414448
const sf = project.createSourceFile(path.join(outDir, 'index.ts'), undefined, { overwrite: true });
415449
sf.addStatements(models.map((d) => `export * from './${paramCase(d.name)}';`));
450+
const runtimeImportBase = makeRuntimeImportBase(version);
416451
switch (target) {
417452
case 'react':
418-
sf.addStatements(`export { Provider } from '@zenstackhq/tanstack-query/runtime/react';`);
453+
sf.addStatements(`export { Provider } from '${runtimeImportBase}/react';`);
419454
break;
420455
case 'vue':
421-
sf.addStatements(
422-
`export { VueQueryContextKey, provideHooksContext } from '@zenstackhq/tanstack-query/runtime/vue';`
423-
);
456+
sf.addStatements(`export { VueQueryContextKey, provideHooksContext } from '${runtimeImportBase}/vue';`);
424457
break;
425458
case 'svelte':
426-
sf.addStatements(
427-
`export { SvelteQueryContextKey, setHooksContext } from '@zenstackhq/tanstack-query/runtime/svelte';`
428-
);
459+
sf.addStatements(`export { SvelteQueryContextKey, setHooksContext } from '${runtimeImportBase}/svelte';`);
429460
break;
430461
}
431462
}
@@ -443,45 +474,60 @@ function makeGetContext(target: TargetFramework) {
443474
}
444475
}
445476

446-
function makeBaseImports(target: TargetFramework) {
477+
function makeBaseImports(target: TargetFramework, version: TanStackVersion) {
478+
const runtimeImportBase = makeRuntimeImportBase(version);
447479
const shared = [
448-
`import { query, infiniteQuery, postMutation, putMutation, deleteMutation } from '@zenstackhq/tanstack-query/runtime/${target}';`,
449-
`import type { PickEnumerable, CheckSelect } from '@zenstackhq/tanstack-query/runtime';`,
480+
`import { query, infiniteQuery, postMutation, putMutation, deleteMutation } from '${runtimeImportBase}/${target}';`,
481+
`import type { PickEnumerable, CheckSelect } from '${runtimeImportBase}';`,
450482
];
451483
switch (target) {
452484
case 'react':
453485
return [
454486
`import { useContext } from 'react';`,
455-
`import type { UseMutationOptions, UseQueryOptions, UseInfiniteQueryOptions } from '@tanstack/react-query';`,
456-
`import { RequestHandlerContext } from '@zenstackhq/tanstack-query/runtime/${target}';`,
487+
`import type { UseMutationOptions, UseQueryOptions, UseInfiniteQueryOptions, InfiniteData } from '@tanstack/react-query';`,
488+
`import { RequestHandlerContext } from '${runtimeImportBase}/${target}';`,
457489
...shared,
458490
];
459491
case 'vue':
460492
return [
461-
`import type { UseMutationOptions, UseQueryOptions, UseInfiniteQueryOptions } from '@tanstack/vue-query';`,
462-
`import { getContext } from '@zenstackhq/tanstack-query/runtime/${target}';`,
493+
`import type { UseMutationOptions, UseQueryOptions, UseInfiniteQueryOptions, InfiniteData } from '@tanstack/vue-query';`,
494+
`import { getContext } from '${runtimeImportBase}/${target}';`,
463495
...shared,
464496
];
465497
case 'svelte':
466498
return [
467499
`import { getContext } from 'svelte';`,
468500
`import { derived } from 'svelte/store';`,
469501
`import type { MutationOptions, QueryOptions, CreateInfiniteQueryOptions } from '@tanstack/svelte-query';`,
470-
`import { SvelteQueryContextKey, type RequestHandlerContext } from '@zenstackhq/tanstack-query/runtime/${target}';`,
502+
...(version === 'v5'
503+
? [`import type { InfiniteData, StoreOrVal } from '@tanstack/svelte-query';`]
504+
: []),
505+
`import { SvelteQueryContextKey, type RequestHandlerContext } from '${runtimeImportBase}/${target}';`,
471506
...shared,
472507
];
473508
default:
474509
throw new PluginError(name, `Unsupported target: ${target}`);
475510
}
476511
}
477512

478-
function makeQueryOptions(target: string, returnType: string, infinite: boolean) {
513+
function makeQueryOptions(target: string, returnType: string, infinite: boolean, version: TanStackVersion) {
479514
switch (target) {
480515
case 'react':
516+
return infinite
517+
? version === 'v4'
518+
? `Omit<UseInfiniteQueryOptions<${returnType}>, 'queryKey'>`
519+
: `Omit<UseInfiniteQueryOptions<${returnType}, unknown, InfiniteData<${returnType}>>, 'queryKey'>`
520+
: `Omit<UseQueryOptions<${returnType}>, 'queryKey'>`;
481521
case 'vue':
482-
return `Use${infinite ? 'Infinite' : ''}QueryOptions<${returnType}>`;
522+
return `Omit<Use${infinite ? 'Infinite' : ''}QueryOptions<${returnType}>, 'queryKey'>`;
483523
case 'svelte':
484-
return `${infinite ? 'CreateInfinite' : ''}QueryOptions<${returnType}>`;
524+
return infinite
525+
? version === 'v4'
526+
? `Omit<CreateInfiniteQueryOptions<${returnType}>, 'queryKey'>`
527+
: `StoreOrVal<Omit<CreateInfiniteQueryOptions<${returnType}, unknown, InfiniteData<${returnType}>>, 'queryKey'>>`
528+
: version === 'v4'
529+
? `Omit<QueryOptions<${returnType}>, 'queryKey'>`
530+
: `StoreOrVal<Omit<QueryOptions<${returnType}>, 'queryKey'>>`;
485531
default:
486532
throw new PluginError(name, `Unsupported target: ${target}`);
487533
}
@@ -499,3 +545,7 @@ function makeMutationOptions(target: string, returnType: string, argsType: strin
499545
throw new PluginError(name, `Unsupported target: ${target}`);
500546
}
501547
}
548+
549+
function makeRuntimeImportBase(version: TanStackVersion) {
550+
return `@zenstackhq/tanstack-query/runtime${version === 'v5' ? '-v5' : ''}`;
551+
}
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
export * from '../runtime/prisma-types';
2+
export type { FetchFn } from '../runtime/common';

0 commit comments

Comments
 (0)