@@ -163,6 +163,101 @@ Default scalar mappings:
163163
164164> You can see a example on [ custom-naming] ( examples/custom-naming )
165165
166+ ## Helper Utilities
167+
168+ The plugin provides powerful helper utilities to make your resolver implementation easier and more efficient:
169+
170+ ### Available Helpers
171+
172+ ``` typescript
173+ import {
174+ buildConnection , // Create Relay-compatible connections
175+ buildFilter , // Process filter input arguments
176+ buildSort , // Process sort input arguments
177+ buildSelect , // Create Prisma select objects based on GraphQL fields
178+ QueryBuilder , // Comprehensive query builder
179+ } from ' @hakutakuai/zenstack-graphql/helpers'
180+ ```
181+
182+ ### Select Definitions Pattern
183+
184+ For efficient and type-safe resolvers, you can define reusable select objects:
185+
186+ ``` typescript
187+ // src/select-definitions.ts
188+ import type { Prisma } from ' @prisma/client'
189+
190+ // Base select objects for each model
191+ export const USER_BASE_SELECT = {
192+ id: true ,
193+ name: true ,
194+ email: true ,
195+ } satisfies Prisma .UserSelect
196+
197+ // Select objects with relations
198+ export const USER_WITH_POSTS_SELECT = {
199+ ... USER_BASE_SELECT ,
200+ posts: {
201+ select: POST_BASE_SELECT ,
202+ },
203+ } satisfies Prisma .UserSelect
204+
205+ // Type definitions for select return types
206+ export type UserWithPosts = Prisma .UserGetPayload <{
207+ select: typeof USER_WITH_POSTS_SELECT
208+ }>
209+ ` ` `
210+
211+ ### Complete Example Flow
212+
213+ Here's how to use the helpers in your resolver:
214+
215+ ` ` ` typescript
216+ import { Resolver , Query , Arg , Info } from ' type-graphql'
217+ import { GraphQLResolveInfo } from ' graphql'
218+ import { buildConnection , buildFilter , buildSort , buildSelect } from ' @hakutakuai/zenstack-graphql/helpers'
219+ import { USER_WITH_POSTS_SELECT } from ' ../select-definitions'
220+
221+ @Resolver ()
222+ export class UserResolver {
223+ @Query (() => UserConnection )
224+ async users(
225+ // Pagination arguments
226+ @Arg (' first' , () => Int , { nullable: true }) first : number | undefined ,
227+ @Arg (' after' , () => String , { nullable: true }) after : string | undefined ,
228+ @Arg (' last' , () => Int , { nullable: true }) last : number | undefined ,
229+ @Arg (' before' , () => String , { nullable: true }) before : string | undefined ,
230+ // Filter and sort arguments
231+ @Arg (' filter' , () => UserFilterInput , { nullable: true }) filter : UserFilterInput | undefined ,
232+ @Arg (' sort' , () => UserSortInput , { nullable: true }) sort : UserSortInput | undefined ,
233+ // GraphQL info for field selection
234+ @Info () info : GraphQLResolveInfo ,
235+ @Ctx () { prisma }: Context ,
236+ ): Promise <UserConnection > {
237+ // Build Prisma arguments
238+ const where = buildFilter (filter )
239+ const orderBy = buildSort (sort )
240+ const select = buildSelect (USER_WITH_POSTS_SELECT , info )
241+
242+ // Create connection config
243+ const config = buildConnection ({
244+ first ,
245+ after ,
246+ last ,
247+ before ,
248+ where ,
249+ orderBy ,
250+ select ,
251+ })
252+
253+ // Execute query and build connection response
254+ const [items, totalCount] = await Promise .all ([prisma .user .findMany (config .findMany ), prisma .user .count (config .count )])
255+
256+ return config .toConnection (items , totalCount ) as UserConnection
257+ }
258+ }
259+ ```
260+
166261## How It Works
167262
168263The plugin transforms your ZModel schema into a GraphQL schema through these steps:
@@ -173,6 +268,7 @@ The plugin transforms your ZModel schema into a GraphQL schema through these ste
1732684 . ** Connection Generation** : Builds Relay-compatible pagination support
1742695 . ** Schema Assembly** : Puts everything together into a complete GraphQL schema
1752706 . ** Output** : Writes the final schema to your specified location
271+ 7 . ** Helper Generation** : Provides utility functions for working with the generated schema
176272
177273> All this happens automatically when you run ` zenstack generate ` , you can see more about on [ Zenstack Plugin System] ( https://zenstack.dev/docs/the-complete-guide/part2/ ) !
178274
@@ -398,6 +494,257 @@ export class Post {
398494
399495These classes are ready to use with TypeGraphQL resolvers and provide full type safety for your GraphQL API.
400496
497+ ### Resolver Implementation with Helpers
498+
499+ With the generated classes, you can create efficient resolvers using the provided helper utilities:
500+
501+ ``` typescript
502+ import { Resolver , Query , Mutation , Arg , ID , Info , Ctx } from ' type-graphql'
503+ import { GraphQLResolveInfo } from ' graphql'
504+ import { User , UserConnection , UserFilterInput , UserSortInput } from ' ../schema'
505+ import { buildConnection , buildFilter , buildSort , buildSelect } from ' @hakutakuai/zenstack-graphql/helpers'
506+ import { USER_WITH_POSTS_SELECT } from ' ../select-definitions'
507+
508+ @Resolver (() => User )
509+ export class UserResolver {
510+ // Query with pagination, filtering, and sorting
511+ @Query (() => UserConnection )
512+ async users(
513+ @Arg (' filter' , () => UserFilterInput , { nullable: true }) filter : UserFilterInput | undefined ,
514+ @Arg (' sort' , () => UserSortInput , { nullable: true }) sort : UserSortInput | undefined ,
515+ @Arg (' first' , () => Int , { nullable: true }) first : number | undefined ,
516+ @Arg (' after' , () => String , { nullable: true }) after : string | undefined ,
517+ @Arg (' last' , () => Int , { nullable: true }) last : number | undefined ,
518+ @Arg (' before' , () => String , { nullable: true }) before : string | undefined ,
519+ @Info () info : GraphQLResolveInfo ,
520+ @Ctx () { prisma }: Context ,
521+ ): Promise <UserConnection > {
522+ // Convert GraphQL filter to Prisma where
523+ const where = buildFilter (filter as any )
524+
525+ // Convert GraphQL sort to Prisma orderBy
526+ const orderBy = buildSort (sort as any )
527+
528+ // Get Prisma select based on requested GraphQL fields
529+ const select = buildSelect (USER_WITH_POSTS_SELECT , info )
530+
531+ // Build connection config for pagination
532+ const config = buildConnection ({
533+ first ,
534+ after ,
535+ last ,
536+ before ,
537+ where ,
538+ orderBy ,
539+ select ,
540+ })
541+
542+ // Execute the query with pagination
543+ const [items, totalCount] = await Promise .all ([prisma .user .findMany (config .findMany ), prisma .user .count (config .count )])
544+
545+ // Convert results to a Connection format
546+ return config .toConnection (items , totalCount ) as UserConnection
547+ }
548+ }
549+ ```
550+
551+ ## Helper Utilities Reference
552+
553+ Here's a comprehensive reference of the helper functions:
554+
555+ ### ` buildConnection `
556+
557+ Creates a Relay-compatible connection for pagination with cursor support.
558+
559+ ``` typescript
560+ const config = buildConnection ({
561+ // Pagination params
562+ first?: number ; // Number of items to fetch
563+ after ?: string ; // Cursor to start after
564+ last ?: number ; // Number of items from the end
565+ before ?: string ; // Cursor to end before
566+
567+ // Optional Prisma params
568+ where ?: any ; // Prisma where condition
569+ orderBy ?: any ; // Prisma ordering
570+ select ?: any ; // Prisma field selection
571+ include ?: any ; // Prisma include relations
572+
573+ // Options
574+ defaultPageSize ?: number ; // Default page size
575+ maxPageSize ?: number ; // Maximum allowed page size
576+ })
577+
578+ // Returns an object with:
579+ // - findMany: Prisma args for fetching items
580+ // - count: Prisma args for counting total items
581+ // - toConnection: Function to convert results to a Connection
582+ ```
583+
584+ ### ` buildFilter `
585+
586+ Converts GraphQL filter input to Prisma where conditions.
587+
588+ ``` typescript
589+ const where = buildFilter (filterInput )
590+ ```
591+
592+ ### ` buildSort `
593+
594+ Converts GraphQL sort input to Prisma orderBy.
595+
596+ ``` typescript
597+ const orderBy = buildSort (sortInput )
598+ ```
599+
600+ ### ` buildSelect `
601+
602+ Creates a Prisma select object based on requested GraphQL fields.
603+
604+ ``` typescript
605+ const select = buildSelect (baseSelect , graphqlInfo )
606+ ```
607+
608+ ### ` QueryBuilder `
609+
610+ A comprehensive builder for constructing complex Prisma queries.
611+
612+ ``` typescript
613+ import { QueryBuilder } from ' @hakutakuai/zenstack-graphql/helpers'
614+
615+ const query = new QueryBuilder ().filter (filterInput ).sort (sortInput ).paginate ({ first , after , last , before }).select (baseSelect , graphqlInfo ).build ()
616+
617+ const results = await prisma .model .findMany (query )
618+ ```
619+
620+ ## Complete Server Setup Example
621+
622+ Below is a complete example of setting up a GraphQL server using the generated TypeGraphQL classes and helpers:
623+
624+ ``` typescript
625+ // src/server.ts
626+ import ' reflect-metadata'
627+ import { createYoga } from ' graphql-yoga'
628+ import { buildSchema } from ' type-graphql'
629+ import { createServer } from ' node:http'
630+ import { PrismaClient } from ' @prisma/client'
631+ import { resolvers } from ' ./resolvers'
632+
633+ async function main() {
634+ // Create Prisma client
635+ const prisma = new PrismaClient ()
636+
637+ // Create TypeGraphQL schema
638+ const schema = await buildSchema ({
639+ resolvers ,
640+ validate: false ,
641+ emitSchemaFile: ' ./generated-schema.graphql' ,
642+ })
643+
644+ // Create Yoga server
645+ const yoga = createYoga ({
646+ schema ,
647+ context: { prisma }, // Make Prisma available in resolvers
648+ })
649+
650+ // Create HTTP server
651+ const server = createServer (yoga )
652+
653+ // Start server
654+ server .listen (4000 , () => {
655+ console .log (' Server is running on http://localhost:4000/graphql' )
656+ })
657+ }
658+
659+ main ().catch (console .error )
660+ ```
661+
662+ ``` typescript
663+ // src/resolvers/index.ts
664+ import { UserResolver } from ' ./user.resolver'
665+ import { PostResolver } from ' ./post.resolver'
666+ // Import other resolvers...
667+
668+ // Export all resolvers for TypeGraphQL schema building
669+ export const resolvers = [
670+ UserResolver ,
671+ PostResolver ,
672+ // Add other resolvers...
673+ ] as const
674+ ```
675+
676+ ## Best Practices
677+
678+ Here are some best practices for using the ZenStack GraphQL plugin:
679+
680+ ### 1. Organize Select Definitions
681+
682+ Keep your select definitions in a separate file and organize them by model. This makes it easier to reuse select objects across resolvers.
683+
684+ ``` typescript
685+ // src/select-definitions.ts
686+ import type { Prisma } from ' @prisma/client'
687+
688+ // Group by model
689+ export const USER_BASE_SELECT = {... }
690+ export const USER_WITH_POSTS_SELECT = {... }
691+
692+ export const POST_BASE_SELECT = {... }
693+ export const POST_WITH_COMMENTS_SELECT = {... }
694+ ```
695+
696+ ### 2. Use TypedPayloads for Return Types
697+
698+ Define types for your select objects to ensure type safety in your resolvers:
699+
700+ ``` typescript
701+ export type UserWithPosts = Prisma .UserGetPayload <{
702+ select: typeof USER_WITH_POSTS_SELECT
703+ }>
704+
705+ // In your resolver:
706+ async function getUser(): Promise <UserWithPosts > {
707+ return prisma .user .findUnique ({... }) as UserWithPosts
708+ }
709+ ```
710+
711+ ### 3. Combine Helpers for Maximum Efficiency
712+
713+ Combine multiple helpers to handle complex query requirements:
714+
715+ ``` typescript
716+ const where = buildFilter (filter )
717+ const orderBy = buildSort (sort )
718+ const select = buildSelect (baseSelect , info )
719+ const config = buildConnection ({ where , orderBy , select , first , after })
720+ ```
721+
722+ ### 4. Use QueryBuilder for Complex Queries
723+
724+ For more complex queries, use the QueryBuilder to construct your Prisma arguments:
725+
726+ ``` typescript
727+ const query = new QueryBuilder ().filter (filter ).sort (sort ).paginate ({ first , after }).select (baseSelect , info ).build ()
728+
729+ const results = await prisma .model .findMany (query )
730+ ```
731+
732+ ### 5. Handle Errors Gracefully
733+
734+ Add error handling to your resolvers to provide meaningful feedback:
735+
736+ ``` typescript
737+ @Query (() => UserConnection )
738+ async users (@Ctx () ctx : Context ): Promise < UserConnection > {
739+ try {
740+ // Your implementation
741+ } catch (error) {
742+ console.error(' Failed to fetch users:' , error)
743+ throw new Error(' Failed to fetch users. Please try again later.' )
744+ }
745+ }
746+ ```
747+
401748> Check the [ examples] ( ./examples/ ) directory for more sample use cases!
402749
403750## Contributing
0 commit comments