@@ -25,6 +25,8 @@ const supportedTargets = ['react', 'vue', 'svelte'];
25
25
type TargetFramework = ( typeof supportedTargets ) [ number ] ;
26
26
type TanStackVersion = 'v4' | 'v5' ;
27
27
28
+ // TODO: turn it into a class to simplify parameter passing
29
+
28
30
export async function generate ( model : Model , options : PluginOptions , dmmf : DMMF . Document ) {
29
31
const project = createProject ( ) ;
30
32
const warnings : string [ ] = [ ] ;
@@ -40,6 +42,17 @@ export async function generate(model: Model, options: PluginOptions, dmmf: DMMF.
40
42
throw new PluginError ( name , `Unsupported version "${ version } ": use "v4" or "v5"` ) ;
41
43
}
42
44
45
+ if ( options . generatePrefetch !== undefined && typeof options . generatePrefetch !== 'boolean' ) {
46
+ throw new PluginError (
47
+ name ,
48
+ `Invalid "generatePrefetch" option: expected boolean, got ${ options . generatePrefetch } `
49
+ ) ;
50
+ }
51
+
52
+ if ( options . generatePrefetch === true && version === 'v4' ) {
53
+ throw new PluginError ( name , `"generatePrefetch" is not supported for version "v4"` ) ;
54
+ }
55
+
43
56
let outDir = requireOption < string > ( options , 'output' , name ) ;
44
57
outDir = resolvePath ( outDir , options ) ;
45
58
ensureEmptyDir ( outDir ) ;
@@ -71,27 +84,20 @@ function generateQueryHook(
71
84
model : string ,
72
85
operation : string ,
73
86
returnArray : boolean ,
87
+ returnNullable : boolean ,
74
88
optionalInput : boolean ,
75
89
overrideReturnType ?: string ,
76
90
overrideInputType ?: string ,
77
91
overrideTypeParameters ?: string [ ] ,
78
92
supportInfinite = false ,
79
93
supportOptimistic = false ,
80
- supportPrefetching = false ,
94
+ generatePrefetch = false
81
95
) {
82
96
const generateModes : ( '' | 'Infinite' | 'Suspense' | 'SuspenseInfinite' | 'Prefetch' | 'PrefetchInfinite' ) [ ] = [ '' ] ;
83
97
if ( supportInfinite ) {
84
98
generateModes . push ( 'Infinite' ) ;
85
99
}
86
100
87
- if ( supportPrefetching ) {
88
- generateModes . push ( 'Prefetch' ) ;
89
-
90
- if ( supportInfinite ) {
91
- generateModes . push ( 'PrefetchInfinite' ) ;
92
- }
93
- }
94
-
95
101
if ( target === 'react' && version === 'v5' ) {
96
102
// react-query v5 supports suspense query
97
103
generateModes . push ( 'Suspense' ) ;
@@ -100,34 +106,45 @@ function generateQueryHook(
100
106
}
101
107
}
102
108
103
- for ( const generateMode of generateModes ) {
104
- const capOperation = upperCaseFirst ( operation ) ;
105
-
106
- const argsType = overrideInputType ?? `Prisma.${ model } ${ capOperation } Args` ;
107
- const inputType = makeQueryArgsType ( target , argsType ) ;
108
-
109
- const infinite = generateMode . includes ( 'Infinite' ) ;
110
- const suspense = generateMode . includes ( 'Suspense' ) ;
111
- const prefetch = generateMode . includes ( 'Prefetch' ) ;
112
- const prefetchInfinite = generateMode . includes ( 'PrefetchInfinite' ) ;
109
+ const getArgsType = ( ) => {
110
+ return overrideInputType ?? `Prisma.${ model } ${ upperCaseFirst ( operation ) } Args` ;
111
+ } ;
113
112
114
- const optimistic =
115
- supportOptimistic &&
116
- // infinite queries are not subject to optimistic updates
117
- ! infinite ;
113
+ const getInputType = ( prefetch : boolean ) => {
114
+ return makeQueryArgsType ( target , getArgsType ( ) , prefetch ) ;
115
+ } ;
118
116
117
+ const getReturnType = ( optimistic : boolean ) => {
119
118
let defaultReturnType = `Prisma.${ model } GetPayload<TArgs>` ;
120
119
if ( optimistic ) {
121
120
defaultReturnType += '& { $optimistic?: boolean }' ;
122
121
}
123
122
if ( returnArray ) {
124
123
defaultReturnType = `Array<${ defaultReturnType } >` ;
125
124
}
126
- if ( prefetch || prefetchInfinite ) {
127
- defaultReturnType = `Promise<void> ` ;
125
+ if ( returnNullable ) {
126
+ defaultReturnType = `( ${ defaultReturnType } ) | null ` ;
128
127
}
129
128
130
129
const returnType = overrideReturnType ?? defaultReturnType ;
130
+ return returnType ;
131
+ } ;
132
+
133
+ const capOperation = upperCaseFirst ( operation ) ;
134
+
135
+ for ( const generateMode of generateModes ) {
136
+ const argsType = getArgsType ( ) ;
137
+ const inputType = getInputType ( false ) ;
138
+
139
+ const infinite = generateMode . includes ( 'Infinite' ) ;
140
+ const suspense = generateMode . includes ( 'Suspense' ) ;
141
+
142
+ const optimistic =
143
+ supportOptimistic &&
144
+ // infinite queries are not subject to optimistic updates
145
+ ! infinite ;
146
+
147
+ const returnType = getReturnType ( optimistic ) ;
131
148
const optionsType = makeQueryOptions ( target , 'TQueryFnData' , 'TData' , infinite , suspense , version ) ;
132
149
133
150
const func = sf . addFunction ( {
@@ -163,6 +180,58 @@ function generateQueryHook(
163
180
) } /${ operation } \`, args, options, fetch);`,
164
181
] ) ;
165
182
}
183
+
184
+ if ( generatePrefetch ) {
185
+ const argsType = getArgsType ( ) ;
186
+ const inputType = getInputType ( true ) ;
187
+ const returnType = getReturnType ( false ) ;
188
+
189
+ const modes = [
190
+ { mode : 'prefetch' , infinite : false } ,
191
+ { mode : 'fetch' , infinite : false } ,
192
+ ] ;
193
+ if ( supportInfinite ) {
194
+ modes . push ( { mode : 'prefetch' , infinite : true } , { mode : 'fetch' , infinite : true } ) ;
195
+ }
196
+
197
+ for ( const { mode, infinite } of modes ) {
198
+ const optionsType = makePrefetchQueryOptions ( target , 'TQueryFnData' , 'TData' , infinite ) ;
199
+
200
+ const func = sf . addFunction ( {
201
+ name : `${ mode } ${ infinite ? 'Infinite' : '' } ${ capOperation } ${ model } ` ,
202
+ typeParameters : overrideTypeParameters ?? [
203
+ `TArgs extends ${ argsType } ` ,
204
+ `TQueryFnData = ${ returnType } ` ,
205
+ 'TData = TQueryFnData' ,
206
+ 'TError = DefaultError' ,
207
+ ] ,
208
+ parameters : [
209
+ {
210
+ name : 'queryClient' ,
211
+ type : 'QueryClient' ,
212
+ } ,
213
+ {
214
+ name : optionalInput ? 'args?' : 'args' ,
215
+ type : inputType ,
216
+ } ,
217
+ {
218
+ name : 'options?' ,
219
+ type : optionsType ,
220
+ } ,
221
+ ] ,
222
+ isExported : true ,
223
+ } ) ;
224
+
225
+ func . addStatements ( [
226
+ makeGetContext ( target ) ,
227
+ `return ${ mode } ${
228
+ infinite ? 'Infinite' : ''
229
+ } ModelQuery<TQueryFnData, TData, TError>(queryClient, '${ model } ', \`\${endpoint}/${ lowerCaseFirst (
230
+ model
231
+ ) } /${ operation } \`, args, options, fetch);`,
232
+ ] ) ;
233
+ }
234
+ }
166
235
}
167
236
168
237
function generateMutationHook (
@@ -349,13 +418,15 @@ function generateModelHooks(
349
418
350
419
sf . addStatements ( '/* eslint-disable */' ) ;
351
420
421
+ const generatePrefetch = options . generatePrefetch === true ;
422
+
352
423
const prismaImport = getPrismaClientImportSpec ( outDir , options ) ;
353
424
sf . addImportDeclaration ( {
354
425
namedImports : [ 'Prisma' , model . name ] ,
355
426
isTypeOnly : true ,
356
427
moduleSpecifier : prismaImport ,
357
428
} ) ;
358
- sf . addStatements ( makeBaseImports ( target , version ) ) ;
429
+ sf . addStatements ( makeBaseImports ( target , version , generatePrefetch ) ) ;
359
430
360
431
// Note: delegate models don't support create and upsert operations
361
432
@@ -380,13 +451,14 @@ function generateModelHooks(
380
451
model . name ,
381
452
'findMany' ,
382
453
true ,
454
+ false ,
383
455
true ,
384
456
undefined ,
385
457
undefined ,
386
458
undefined ,
387
459
true ,
388
460
true ,
389
- true
461
+ generatePrefetch
390
462
) ;
391
463
}
392
464
@@ -399,13 +471,14 @@ function generateModelHooks(
399
471
model . name ,
400
472
'findUnique' ,
401
473
false ,
474
+ true ,
402
475
false ,
403
476
undefined ,
404
477
undefined ,
405
478
undefined ,
406
479
false ,
407
480
true ,
408
- true
481
+ generatePrefetch
409
482
) ;
410
483
}
411
484
@@ -419,12 +492,13 @@ function generateModelHooks(
419
492
'findFirst' ,
420
493
false ,
421
494
true ,
495
+ true ,
422
496
undefined ,
423
497
undefined ,
424
498
undefined ,
425
499
false ,
426
500
true ,
427
- true
501
+ generatePrefetch
428
502
) ;
429
503
}
430
504
@@ -469,7 +543,13 @@ function generateModelHooks(
469
543
'aggregate' ,
470
544
false ,
471
545
false ,
472
- `Prisma.Get${ modelNameCap } AggregateType<TArgs>`
546
+ false ,
547
+ `Prisma.Get${ modelNameCap } AggregateType<TArgs>` ,
548
+ undefined ,
549
+ undefined ,
550
+ false ,
551
+ false ,
552
+ generatePrefetch
473
553
) ;
474
554
}
475
555
@@ -553,9 +633,13 @@ function generateModelHooks(
553
633
'groupBy' ,
554
634
false ,
555
635
false ,
636
+ false ,
556
637
returnType ,
557
638
`Prisma.SubsetIntersection<TArgs, Prisma.${ useName } GroupByArgs, OrderByArg> & InputErrors` ,
558
- typeParameters
639
+ typeParameters ,
640
+ false ,
641
+ false ,
642
+ generatePrefetch
559
643
) ;
560
644
}
561
645
@@ -568,8 +652,14 @@ function generateModelHooks(
568
652
model . name ,
569
653
'count' ,
570
654
false ,
655
+ false ,
571
656
true ,
572
- `TArgs extends { select: any; } ? TArgs['select'] extends true ? number : Prisma.GetScalarType<TArgs['select'], Prisma.${ modelNameCap } CountAggregateOutputType> : number`
657
+ `TArgs extends { select: any; } ? TArgs['select'] extends true ? number : Prisma.GetScalarType<TArgs['select'], Prisma.${ modelNameCap } CountAggregateOutputType> : number` ,
658
+ undefined ,
659
+ undefined ,
660
+ false ,
661
+ false ,
662
+ generatePrefetch
573
663
) ;
574
664
}
575
665
@@ -616,15 +706,25 @@ function makeGetContext(target: TargetFramework) {
616
706
}
617
707
}
618
708
619
- function makeBaseImports ( target : TargetFramework , version : TanStackVersion ) {
709
+ function makeBaseImports ( target : TargetFramework , version : TanStackVersion , generatePrefetch : boolean ) {
620
710
const runtimeImportBase = makeRuntimeImportBase ( version ) ;
621
711
const shared = [
622
- `import { useModelQuery, useInfiniteModelQuery, useModelMutation, usePrefetchModelQuery, usePrefetchInfiniteModelQuery } from '${ runtimeImportBase } /${ target } ';` ,
712
+ `import { useModelQuery, useInfiniteModelQuery, useModelMutation } from '${ runtimeImportBase } /${ target } ';` ,
623
713
`import type { PickEnumerable, CheckSelect, QueryError, ExtraQueryOptions, ExtraMutationOptions } from '${ runtimeImportBase } ';` ,
624
714
`import type { PolicyCrudKind } from '${ RUNTIME_PACKAGE } '` ,
625
715
`import metadata from './__model_meta';` ,
626
716
`type DefaultError = QueryError;` ,
627
717
] ;
718
+
719
+ if ( version === 'v5' && generatePrefetch ) {
720
+ shared . push (
721
+ `import { fetchModelQuery, prefetchModelQuery, fetchInfiniteModelQuery, prefetchInfiniteModelQuery } from '${ runtimeImportBase } /${ target } ';`
722
+ ) ;
723
+ shared . push (
724
+ `import type { QueryClient, FetchQueryOptions, FetchInfiniteQueryOptions } from '@tanstack/${ target } -query';`
725
+ ) ;
726
+ }
727
+
628
728
switch ( target ) {
629
729
case 'react' : {
630
730
const suspense =
@@ -645,7 +745,8 @@ function makeBaseImports(target: TargetFramework, version: TanStackVersion) {
645
745
return [
646
746
`import type { UseMutationOptions, UseQueryOptions, UseInfiniteQueryOptions, InfiniteData } from '@tanstack/vue-query';` ,
647
747
`import { getHooksContext } from '${ runtimeImportBase } /${ target } ';` ,
648
- `import type { MaybeRefOrGetter, ComputedRef, UnwrapRef } from 'vue';` ,
748
+ `import type { MaybeRef, MaybeRefOrGetter, ComputedRef, UnwrapRef } from 'vue';` ,
749
+ ...( generatePrefetch ? [ `import { type MaybeRefDeep } from '${ runtimeImportBase } /${ target } ';` ] : [ ] ) ,
649
750
...shared ,
650
751
] ;
651
752
}
@@ -665,10 +766,14 @@ function makeBaseImports(target: TargetFramework, version: TanStackVersion) {
665
766
}
666
767
}
667
768
668
- function makeQueryArgsType ( target : string , argsType : string ) {
769
+ function makeQueryArgsType ( target : string , argsType : string , prefetch : boolean ) {
669
770
const type = `Prisma.SelectSubset<TArgs, ${ argsType } >` ;
670
771
if ( target === 'vue' ) {
671
- return `MaybeRefOrGetter<${ type } > | ComputedRef<${ type } >` ;
772
+ if ( prefetch ) {
773
+ return `MaybeRef<${ type } >` ;
774
+ } else {
775
+ return `MaybeRefOrGetter<${ type } > | ComputedRef<${ type } >` ;
776
+ }
672
777
} else {
673
778
return type ;
674
779
}
@@ -721,6 +826,35 @@ function makeQueryOptions(
721
826
return result ;
722
827
}
723
828
829
+ function makePrefetchQueryOptions ( target : string , returnType : string , dataType : string , infinite : boolean ) {
830
+ let result = match ( target )
831
+ . with ( 'react' , ( ) =>
832
+ infinite
833
+ ? `Omit<FetchInfiniteQueryOptions<${ returnType } , TError, ${ dataType } >, 'queryKey' | 'initialPageParam'>`
834
+ : `Omit<FetchQueryOptions<${ returnType } , TError, ${ dataType } >, 'queryKey'>`
835
+ )
836
+ . with ( 'vue' , ( ) =>
837
+ infinite
838
+ ? `MaybeRefDeep<Omit<FetchInfiniteQueryOptions<${ returnType } , TError, ${ dataType } >, 'queryKey' | 'initialPageParam'>>`
839
+ : `MaybeRefDeep<Omit<FetchQueryOptions<${ returnType } , TError, ${ dataType } >, 'queryKey'>>`
840
+ )
841
+ . with ( 'svelte' , ( ) =>
842
+ infinite
843
+ ? `Omit<FetchInfiniteQueryOptions<${ returnType } , TError, ${ dataType } >, 'queryKey' | 'initialPageParam'>`
844
+ : `Omit<FetchQueryOptions<${ returnType } , TError, ${ dataType } >, 'queryKey'>`
845
+ )
846
+ . otherwise ( ( ) => {
847
+ throw new PluginError ( name , `Unsupported target: ${ target } ` ) ;
848
+ } ) ;
849
+
850
+ if ( ! infinite ) {
851
+ // non-infinite queries support extra options like optimistic updates
852
+ result = `(${ result } & ExtraQueryOptions)` ;
853
+ }
854
+
855
+ return result ;
856
+ }
857
+
724
858
function makeMutationOptions ( target : string , returnType : string , argsType : string ) {
725
859
let result = match ( target )
726
860
. with ( 'react' , ( ) => `UseMutationOptions<${ returnType } , DefaultError, ${ argsType } >` )
0 commit comments