@@ -32,6 +32,8 @@ export async function generate(model: Model, options: PluginOptions, dmmf: DMMF.
3232 ) ;
3333 }
3434
35+ const legacyMutations = options . legacyMutations !== false ;
36+
3537 const models = getDataModels ( model ) ;
3638
3739 await generateModelMeta ( project , models , {
@@ -49,73 +51,76 @@ export async function generate(model: Model, options: PluginOptions, dmmf: DMMF.
4951 warnings . push ( `Unable to find mapping for model ${ dataModel . name } ` ) ;
5052 return ;
5153 }
52- generateModelHooks ( project , outDir , dataModel , mapping ) ;
54+ generateModelHooks ( project , outDir , dataModel , mapping , legacyMutations ) ;
5355 } ) ;
5456
5557 await saveProject ( project ) ;
5658 return warnings ;
5759}
5860
59- function generateModelHooks ( project : Project , outDir : string , model : DataModel , mapping : DMMF . ModelMapping ) {
61+ function generateModelHooks (
62+ project : Project ,
63+ outDir : string ,
64+ model : DataModel ,
65+ mapping : DMMF . ModelMapping ,
66+ legacyMutations : boolean
67+ ) {
6068 const fileName = paramCase ( model . name ) ;
6169 const sf = project . createSourceFile ( path . join ( outDir , `${ fileName } .ts` ) , undefined , { overwrite : true } ) ;
6270
6371 sf . addStatements ( '/* eslint-disable */' ) ;
6472
6573 const prismaImport = getPrismaClientImportSpec ( model . $container , outDir ) ;
6674 sf . addImportDeclaration ( {
67- namedImports : [ 'Prisma' , model . name ] ,
75+ namedImports : [ 'Prisma' ] ,
6876 isTypeOnly : true ,
6977 moduleSpecifier : prismaImport ,
7078 } ) ;
7179 sf . addStatements ( [
72- `import { RequestHandlerContext, type GetNextArgs, type RequestOptions , type InfiniteRequestOptions , type PickEnumerable , type CheckSelect , useHooksContext } from '@zenstackhq/swr/runtime';` ,
80+ `import { type GetNextArgs, type QueryOptions , type InfiniteQueryOptions , type MutationOptions , type PickEnumerable , useHooksContext } from '@zenstackhq/swr/runtime';` ,
7381 `import metadata from './__model_meta';` ,
7482 `import * as request from '@zenstackhq/swr/runtime';` ,
7583 ] ) ;
7684
7785 const modelNameCap = upperCaseFirst ( model . name ) ;
7886 const prismaVersion = getPrismaVersion ( ) ;
7987
80- const useMutation = sf . addFunction ( {
81- name : `useMutate${ model . name } ` ,
82- isExported : true ,
83- statements : [
84- 'const { endpoint, fetch, logging } = useHooksContext();' ,
85- `const mutate = request.useMutate('${ model . name } ', metadata, logging);` ,
86- ] ,
87- } ) ;
88+ const useMutation = legacyMutations
89+ ? sf . addFunction ( {
90+ name : `useMutate${ model . name } ` ,
91+ isExported : true ,
92+ statements : [
93+ 'const { endpoint, fetch } = useHooksContext();' ,
94+ `const invalidate = request.useInvalidation('${ model . name } ', metadata);` ,
95+ ] ,
96+ } )
97+ : undefined ;
98+
8899 const mutationFuncs : string [ ] = [ ] ;
89100
90101 // create is somehow named "createOne" in the DMMF
91102 // eslint-disable-next-line @typescript-eslint/no-explicit-any
92103 if ( mapping . create || ( mapping as any ) . createOne ) {
93104 const argsType = `Prisma.${ model . name } CreateArgs` ;
94- const inputType = `Prisma.SelectSubset<T, ${ argsType } >` ;
95- const returnType = `CheckSelect<T, ${ model . name } , Prisma.${ model . name } GetPayload<T>>` ;
96- mutationFuncs . push (
97- generateMutation ( useMutation , model , 'post' , 'create' , argsType , inputType , returnType , true )
98- ) ;
105+ mutationFuncs . push ( generateMutation ( sf , useMutation , model , 'POST' , 'create' , argsType , false ) ) ;
99106 }
100107
101108 // createMany
102109 if ( mapping . createMany ) {
103110 const argsType = `Prisma.${ model . name } CreateManyArgs` ;
104- const inputType = `Prisma.SelectSubset<T, ${ argsType } >` ;
105- const returnType = `Prisma.BatchPayload` ;
106- mutationFuncs . push (
107- generateMutation ( useMutation , model , 'post' , 'createMany' , argsType , inputType , returnType , false )
108- ) ;
111+ mutationFuncs . push ( generateMutation ( sf , useMutation , model , 'POST' , 'createMany' , argsType , true ) ) ;
109112 }
110113
111114 // findMany
112115 if ( mapping . findMany ) {
113116 const argsType = `Prisma.${ model . name } FindManyArgs` ;
114117 const inputType = `Prisma.SelectSubset<T, ${ argsType } >` ;
115- const returnType = `Array<Prisma.${ model . name } GetPayload<T>>` ;
118+ const returnElement = `Prisma.${ model . name } GetPayload<T>` ;
119+ const returnType = `Array<${ returnElement } >` ;
120+ const optimisticReturn = `Array<${ makeOptimistic ( returnElement ) } >` ;
116121
117122 // regular findMany
118- generateQueryHook ( sf , model , 'findMany' , argsType , inputType , returnType ) ;
123+ generateQueryHook ( sf , model , 'findMany' , argsType , inputType , optimisticReturn , undefined , false ) ;
119124
120125 // infinite findMany
121126 generateQueryHook ( sf , model , 'findMany' , argsType , inputType , returnType , undefined , true ) ;
@@ -125,72 +130,52 @@ function generateModelHooks(project: Project, outDir: string, model: DataModel,
125130 if ( mapping . findUnique ) {
126131 const argsType = `Prisma.${ model . name } FindUniqueArgs` ;
127132 const inputType = `Prisma.SelectSubset<T, ${ argsType } >` ;
128- const returnType = `Prisma.${ model . name } GetPayload<T>` ;
129- generateQueryHook ( sf , model , 'findUnique' , argsType , inputType , returnType ) ;
133+ const returnType = makeOptimistic ( `Prisma.${ model . name } GetPayload<T>` ) ;
134+ generateQueryHook ( sf , model , 'findUnique' , argsType , inputType , returnType , undefined , false ) ;
130135 }
131136
132137 // findFirst
133138 if ( mapping . findFirst ) {
134139 const argsType = `Prisma.${ model . name } FindFirstArgs` ;
135140 const inputType = `Prisma.SelectSubset<T, ${ argsType } >` ;
136- const returnType = `Prisma.${ model . name } GetPayload<T>` ;
137- generateQueryHook ( sf , model , 'findFirst' , argsType , inputType , returnType ) ;
141+ const returnType = makeOptimistic ( `Prisma.${ model . name } GetPayload<T>` ) ;
142+ generateQueryHook ( sf , model , 'findFirst' , argsType , inputType , returnType , undefined , false ) ;
138143 }
139144
140145 // update
141146 // update is somehow named "updateOne" in the DMMF
142147 // eslint-disable-next-line @typescript-eslint/no-explicit-any
143148 if ( mapping . update || ( mapping as any ) . updateOne ) {
144149 const argsType = `Prisma.${ model . name } UpdateArgs` ;
145- const inputType = `Prisma.SelectSubset<T, ${ argsType } >` ;
146- const returnType = `Prisma.${ model . name } GetPayload<T>` ;
147- mutationFuncs . push (
148- generateMutation ( useMutation , model , 'put' , 'update' , argsType , inputType , returnType , true )
149- ) ;
150+ mutationFuncs . push ( generateMutation ( sf , useMutation , model , 'PUT' , 'update' , argsType , false ) ) ;
150151 }
151152
152153 // updateMany
153154 if ( mapping . updateMany ) {
154155 const argsType = `Prisma.${ model . name } UpdateManyArgs` ;
155- const inputType = `Prisma.SelectSubset<T, ${ argsType } >` ;
156- const returnType = `Prisma.BatchPayload` ;
157- mutationFuncs . push (
158- generateMutation ( useMutation , model , 'put' , 'updateMany' , argsType , inputType , returnType , false )
159- ) ;
156+ mutationFuncs . push ( generateMutation ( sf , useMutation , model , 'PUT' , 'updateMany' , argsType , true ) ) ;
160157 }
161158
162159 // upsert
163160 // upsert is somehow named "upsertOne" in the DMMF
164161 // eslint-disable-next-line @typescript-eslint/no-explicit-any
165162 if ( mapping . upsert || ( mapping as any ) . upsertOne ) {
166163 const argsType = `Prisma.${ model . name } UpsertArgs` ;
167- const inputType = `Prisma.SelectSubset<T, ${ argsType } >` ;
168- const returnType = `Prisma.${ model . name } GetPayload<T>` ;
169- mutationFuncs . push (
170- generateMutation ( useMutation , model , 'post' , 'upsert' , argsType , inputType , returnType , true )
171- ) ;
164+ mutationFuncs . push ( generateMutation ( sf , useMutation , model , 'POST' , 'upsert' , argsType , false ) ) ;
172165 }
173166
174167 // del
175168 // delete is somehow named "deleteOne" in the DMMF
176169 // eslint-disable-next-line @typescript-eslint/no-explicit-any
177170 if ( mapping . delete || ( mapping as any ) . deleteOne ) {
178171 const argsType = `Prisma.${ model . name } DeleteArgs` ;
179- const inputType = `Prisma.SelectSubset<T, ${ argsType } >` ;
180- const returnType = `Prisma.${ model . name } GetPayload<T>` ;
181- mutationFuncs . push (
182- generateMutation ( useMutation , model , 'delete' , 'delete' , argsType , inputType , returnType , true )
183- ) ;
172+ mutationFuncs . push ( generateMutation ( sf , useMutation , model , 'DELETE' , 'delete' , argsType , false ) ) ;
184173 }
185174
186175 // deleteMany
187176 if ( mapping . deleteMany ) {
188177 const argsType = `Prisma.${ model . name } DeleteManyArgs` ;
189- const inputType = `Prisma.SelectSubset<T, ${ argsType } >` ;
190- const returnType = `Prisma.BatchPayload` ;
191- mutationFuncs . push (
192- generateMutation ( useMutation , model , 'delete' , 'deleteMany' , argsType , inputType , returnType , false )
193- ) ;
178+ mutationFuncs . push ( generateMutation ( sf , useMutation , model , 'DELETE' , 'deleteMany' , argsType , true ) ) ;
194179 }
195180
196181 // aggregate
@@ -283,7 +268,11 @@ function generateModelHooks(project: Project, outDir: string, model: DataModel,
283268 generateQueryHook ( sf , model , 'count' , argsType , inputType , returnType ) ;
284269 }
285270
286- useMutation . addStatements ( `return { ${ mutationFuncs . join ( ', ' ) } };` ) ;
271+ useMutation ?. addStatements ( `return { ${ mutationFuncs . join ( ', ' ) } };` ) ;
272+ }
273+
274+ function makeOptimistic ( returnType : string ) {
275+ return `${ returnType } & { $optimistic?: boolean }` ;
287276}
288277
289278function generateIndex ( project : Project , outDir : string , models : DataModel [ ] ) {
@@ -321,7 +310,7 @@ function generateQueryHook(
321310 }
322311 parameters . push ( {
323312 name : 'options?' ,
324- type : infinite ? `InfiniteRequestOptions <${ returnType } >` : `RequestOptions <${ returnType } >` ,
313+ type : infinite ? `InfiniteQueryOptions <${ returnType } >` : `QueryOptions <${ returnType } >` ,
325314 } ) ;
326315
327316 sf . addFunction ( {
@@ -332,40 +321,72 @@ function generateQueryHook(
332321 } )
333322 . addBody ( )
334323 . addStatements ( [
335- 'const { endpoint, fetch } = useHooksContext();' ,
336324 ! infinite
337- ? `return request.useGet< ${ returnType } > ('${ model . name } ', '${ operation } ', endpoint, args, options, fetch );`
338- : `return request.useInfiniteGet< ${ inputType } | undefined, ${ returnType } > ('${ model . name } ', '${ operation } ', endpoint, getNextArgs, options, fetch );` ,
325+ ? `return request.useModelQuery ('${ model . name } ', '${ operation } ', args, options);`
326+ : `return request.useInfiniteModelQuery ('${ model . name } ', '${ operation } ', getNextArgs, options);` ,
339327 ] ) ;
340328}
341329
342330function generateMutation (
343- func : FunctionDeclaration ,
331+ sf : SourceFile ,
332+ useMutateModelFunc : FunctionDeclaration | undefined ,
344333 model : DataModel ,
345- method : 'post ' | 'put ' | 'patch ' | 'delete ' ,
334+ method : 'POST ' | 'PUT ' | 'PATCH ' | 'DELETE ' ,
346335 operation : string ,
347336 argsType : string ,
348- inputType : string ,
349- returnType : string ,
350- checkReadBack : boolean
337+ batchResult : boolean
351338) {
339+ // non-batch mutations are subject to read-back check
340+ const checkReadBack = ! batchResult ;
341+ const genericReturnType = batchResult ? 'Prisma.BatchPayload' : `Prisma.${ model . name } GetPayload<T> | undefined` ;
342+ const returnType = batchResult ? 'Prisma.BatchPayload' : `Prisma.${ model . name } GetPayload<${ argsType } > | undefined` ;
343+ const genericInputType = `Prisma.SelectSubset<T, ${ argsType } >` ;
344+
352345 const modelRouteName = lowerCaseFirst ( model . name ) ;
353346 const funcName = `${ operation } ${ model . name } ` ;
354- const fetcherFunc = method === 'delete' ? 'del' : method ;
355- func . addFunction ( {
356- name : funcName ,
357- isAsync : true ,
358- typeParameters : [ `T extends ${ argsType } ` ] ,
347+
348+ if ( useMutateModelFunc ) {
349+ // generate async mutation function (legacy)
350+ const mutationFunc = useMutateModelFunc . addFunction ( {
351+ name : funcName ,
352+ isAsync : true ,
353+ typeParameters : [ `T extends ${ argsType } ` ] ,
354+ parameters : [
355+ {
356+ name : 'args' ,
357+ type : genericInputType ,
358+ } ,
359+ ] ,
360+ } ) ;
361+ mutationFunc . addJsDoc ( `@deprecated Use \`use${ upperCaseFirst ( operation ) } ${ model . name } \` hook instead.` ) ;
362+ mutationFunc
363+ . addBody ( )
364+ . addStatements ( [
365+ `return await request.mutationRequest<${ returnType } , ${ checkReadBack } >('${ method } ', \`\${endpoint}/${ modelRouteName } /${ operation } \`, args, invalidate, fetch, ${ checkReadBack } );` ,
366+ ] ) ;
367+ }
368+
369+ // generate mutation hook
370+ sf . addFunction ( {
371+ name : `use${ upperCaseFirst ( operation ) } ${ model . name } ` ,
372+ isExported : true ,
359373 parameters : [
360374 {
361- name : 'args ' ,
362- type : inputType ,
375+ name : 'options? ' ,
376+ type : `MutationOptions< ${ returnType } , unknown, ${ argsType } >` ,
363377 } ,
364378 ] ,
365379 } )
366380 . addBody ( )
367381 . addStatements ( [
368- `return await request.${ fetcherFunc } <${ returnType } , ${ checkReadBack } >(\`\${endpoint}/${ modelRouteName } /${ operation } \`, args, mutate, fetch, ${ checkReadBack } );` ,
382+ `const mutation = request.useModelMutation('${ model . name } ', '${ method } ', '${ operation } ', metadata, options, ${ checkReadBack } );` ,
383+ `return {
384+ ...mutation,
385+ trigger<T extends ${ argsType } >(args: ${ genericInputType } ) {
386+ return mutation.trigger(args, options as any) as Promise<${ genericReturnType } >;
387+ }
388+ };` ,
369389 ] ) ;
390+
370391 return funcName ;
371392}
0 commit comments