1+ import type { GraphQLResolveInfo } from 'graphql'
2+ import { Product , ProductQueryArgs , ProductConnection , ProductFilterInput , ProductSortInput , Review , ReviewQueryArgs , ReviewConnection , ReviewFilterInput , ReviewSortInput , Tag , TagQueryArgs , TagConnection , TagFilterInput , ProductTag , ProductTagQueryArgs , ProductTagConnection , ProductTagFilterInput } from './schema'
3+
4+ export interface PaginationArgs {
5+ first ?: number
6+ after ?: string
7+ last ?: number
8+ before ?: string
9+ }
10+
11+ export interface ConnectionResult < T > {
12+ pageInfo : {
13+ hasNextPage : boolean
14+ hasPreviousPage : boolean
15+ startCursor ?: string
16+ endCursor ?: string
17+ }
18+ edges : Array < {
19+ node : T
20+ cursor : string
21+ } >
22+ totalCount : number
23+ }
24+
25+ export interface ConnectionConfig {
26+ findManyOptions : {
27+ take : number
28+ where ?: any
29+ orderBy ?: any
30+ include ?: any
31+ cursor ?: any
32+ skip ?: number
33+ }
34+ countOptions : {
35+ where ?: any
36+ }
37+ paginationInfo : {
38+ first ?: number
39+ last ?: number
40+ after ?: string
41+ before ?: string
42+ cursorField : string
43+ hasIdField : boolean
44+ relationFields : string [ ]
45+ }
46+ }
47+
48+ export class ConnectionBuilder {
49+ static buildConfig ( args : {
50+ pagination : PaginationArgs
51+ where ?: any
52+ orderBy ?: any
53+ include ?: any
54+ info ?: any
55+ relationFields ?: string [ ]
56+ cursorField ?: string
57+ hasIdField ?: boolean
58+ } ) : ConnectionConfig {
59+ const {
60+ pagination,
61+ where,
62+ orderBy,
63+ include,
64+ info,
65+ relationFields = [ ] ,
66+ cursorField = 'id' ,
67+ hasIdField = true
68+ } = args
69+ const { first, after, last, before } = pagination
70+
71+ let take = first || last || 10
72+ if ( last ) take = - take
73+
74+ const cursor = ( hasIdField && ( after || before ) )
75+ ? { [ cursorField ] : ( after || before ) ! }
76+ : undefined
77+ const skip = cursor ? 1 : 0
78+
79+ const finalInclude = info ? buildPrismaInclude ( info , relationFields ) : include
80+
81+ const findManyOptions : any = {
82+ take : Math . abs ( take ) + 1 , // Get one extra to check for next page
83+ where,
84+ orderBy,
85+ include : finalInclude ,
86+ }
87+
88+ if ( hasIdField && cursor ) {
89+ findManyOptions . cursor = cursor
90+ findManyOptions . skip = skip
91+ }
92+
93+ return {
94+ findManyOptions,
95+ countOptions : { where } ,
96+ paginationInfo : {
97+ first,
98+ last,
99+ after,
100+ before,
101+ cursorField,
102+ hasIdField,
103+ relationFields
104+ }
105+ }
106+ }
107+
108+ static processResults < T > (
109+ items : T [ ] ,
110+ totalCount : number ,
111+ paginationInfo : ConnectionConfig [ 'paginationInfo' ]
112+ ) : ConnectionResult < T > {
113+ const { first, last, cursorField, hasIdField } = paginationInfo
114+
115+ const hasNextPage = first ? items . length > first : false
116+ const hasPreviousPage = last ? items . length > Math . abs ( last ) : false
117+
118+ const resultItems = hasNextPage || hasPreviousPage ? items . slice ( 0 , - 1 ) : items
119+
120+ const edges = resultItems . map ( ( item : any , index : number ) => {
121+ let cursor : string
122+ if ( hasIdField && item [ cursorField ] ) {
123+ cursor = item [ cursorField ]
124+ } else {
125+ cursor = item . postId && item . categoryId
126+ ? `${ item . postId } :${ item . categoryId } `
127+ : String ( index )
128+ }
129+
130+ return {
131+ node : item ,
132+ cursor,
133+ }
134+ } )
135+
136+ return {
137+ pageInfo : {
138+ hasNextPage,
139+ hasPreviousPage,
140+ startCursor : edges [ 0 ] ?. cursor ,
141+ endCursor : edges [ edges . length - 1 ] ?. cursor ,
142+ } ,
143+ edges,
144+ totalCount,
145+ }
146+ }
147+
148+
149+
150+ static buildProductConnectionConfig (
151+ args : ProductQueryArgs ,
152+ info ?: any
153+ ) : ConnectionConfig {
154+ const where = 'filter' in args ? FilterBuilder . buildProductFilter ( ( args as any ) . filter ) : { }
155+ const orderBy = 'sort' in args ? SortBuilder . buildProductSort ( ( args as any ) . sort ) : undefined
156+ const include = info ? FieldSelection . buildProductInclude ( info ) : PRODUCT_INCLUDES
157+
158+ return this . buildConfig ( {
159+ pagination : {
160+ first : args . first ,
161+ after : args . after ,
162+ last : args . last ,
163+ before : args . before ,
164+ } ,
165+ where,
166+ orderBy,
167+ include,
168+ info,
169+ relationFields : [ 'reviews' , 'tags' ] ,
170+ hasIdField : true ,
171+ cursorField : 'id' ,
172+ } )
173+ }
174+
175+ static buildReviewConnectionConfig (
176+ args : ReviewQueryArgs ,
177+ info ?: any
178+ ) : ConnectionConfig {
179+ const where = 'filter' in args ? FilterBuilder . buildReviewFilter ( ( args as any ) . filter ) : { }
180+ const orderBy = 'sort' in args ? SortBuilder . buildReviewSort ( ( args as any ) . sort ) : undefined
181+ const include = info ? FieldSelection . buildReviewInclude ( info ) : REVIEW_INCLUDES
182+
183+ return this . buildConfig ( {
184+ pagination : {
185+ first : args . first ,
186+ after : args . after ,
187+ last : args . last ,
188+ before : args . before ,
189+ } ,
190+ where,
191+ orderBy,
192+ include,
193+ info,
194+ relationFields : [ 'product' ] ,
195+ hasIdField : true ,
196+ cursorField : 'id' ,
197+ } )
198+ }
199+
200+ static buildTagConnectionConfig (
201+ args : TagQueryArgs ,
202+ info ?: any
203+ ) : ConnectionConfig {
204+ const where = 'filter' in args ? FilterBuilder . buildTagFilter ( ( args as any ) . filter ) : { }
205+ const orderBy = undefined
206+ const include = info ? FieldSelection . buildTagInclude ( info ) : TAG_INCLUDES
207+
208+ return this . buildConfig ( {
209+ pagination : {
210+ first : args . first ,
211+ after : args . after ,
212+ last : args . last ,
213+ before : args . before ,
214+ } ,
215+ where,
216+ orderBy,
217+ include,
218+ info,
219+ relationFields : [ 'products' ] ,
220+ hasIdField : true ,
221+ cursorField : 'id' ,
222+ } )
223+ }
224+
225+ static buildProductTagConnectionConfig (
226+ args : ProductTagQueryArgs ,
227+ info ?: any
228+ ) : ConnectionConfig {
229+ const where = { }
230+ const orderBy = undefined
231+ const include = info ? FieldSelection . buildProductTagInclude ( info ) : PRODUCTTAG_INCLUDES
232+
233+ return this . buildConfig ( {
234+ pagination : {
235+ first : args . first ,
236+ after : args . after ,
237+ last : args . last ,
238+ before : args . before ,
239+ } ,
240+ where,
241+ orderBy,
242+ include,
243+ info,
244+ relationFields : [ 'product' , 'tag' ] ,
245+ hasIdField : false ,
246+ cursorField : 'id' ,
247+ } )
248+ }
249+ }
250+
251+ export class FilterBuilder {
252+ static buildFilter ( filter : any ) : any {
253+ if ( ! filter || typeof filter !== 'object' ) return { }
254+
255+ const where : any = { }
256+
257+ for ( const [ field , value ] of Object . entries ( filter ) ) {
258+ if ( field === 'AND' && Array . isArray ( value ) ) {
259+ where . AND = value . map ( ( f : any ) => this . buildFilter ( f ) )
260+ } else if ( field === 'OR' && Array . isArray ( value ) ) {
261+ where . OR = value . map ( ( f : any ) => this . buildFilter ( f ) )
262+ } else if ( value && typeof value === 'object' ) {
263+ const fieldWhere : any = { }
264+
265+ for ( const [ operation , operationValue ] of Object . entries ( value ) ) {
266+ if ( operationValue !== undefined && operationValue !== null ) {
267+ fieldWhere [ operation ] = operationValue
268+ }
269+ }
270+
271+ if ( Object . keys ( fieldWhere ) . length > 0 ) {
272+ where [ field ] = fieldWhere
273+ }
274+ }
275+ }
276+
277+ return where
278+ }
279+
280+
281+ static buildProductFilter ( filter ?: ProductFilterInput ) : any {
282+ return this . buildFilter ( filter )
283+ }
284+
285+ static buildReviewFilter ( filter ?: ReviewFilterInput ) : any {
286+ return this . buildFilter ( filter )
287+ }
288+
289+ static buildTagFilter ( filter ?: TagFilterInput ) : any {
290+ return this . buildFilter ( filter )
291+ }
292+
293+ }
294+
295+ export class SortBuilder {
296+ static buildSort ( sort : any , fallbackSort : any = { id : 'asc' } ) : any {
297+ if ( ! sort || typeof sort !== 'object' ) return fallbackSort
298+
299+ const orderBy : any = { }
300+
301+ for ( const [ field , direction ] of Object . entries ( sort ) ) {
302+ if ( direction && typeof direction === 'string' ) {
303+ orderBy [ field ] = direction . toLowerCase ( )
304+ }
305+ }
306+
307+ return Object . keys ( orderBy ) . length > 0 ? orderBy : fallbackSort
308+ }
309+
310+
311+ static buildProductSort ( sort ?: ProductSortInput ) : any {
312+ const fallbackSort = true ? { id : 'asc' } : { }
313+ return this . buildSort ( sort , fallbackSort )
314+ }
315+
316+ static buildReviewSort ( sort ?: ReviewSortInput ) : any {
317+ const fallbackSort = true ? { id : 'asc' } : { }
318+ return this . buildSort ( sort , fallbackSort )
319+ }
320+
321+
322+ }
323+
324+
325+
326+ export interface ResolveTree {
327+ name : string
328+ alias : string
329+ args : { [ str : string ] : unknown }
330+ fieldsByTypeName : FieldsByTypeName
331+ }
332+
333+ export interface FieldsByTypeName {
334+ [ str : string ] : { [ str : string ] : ResolveTree }
335+ }
336+
337+ export function buildPrismaInclude ( _resolveInfo : any , relations : string [ ] = [ ] ) : any {
338+ const include : any = { }
339+
340+ relations . forEach ( relation => {
341+ include [ relation ] = true
342+ } )
343+
344+ return include
345+ }
346+
347+ export class FieldSelection {
348+
349+ static buildProductInclude ( info ?: any ) : any {
350+ const relationFields = [ 'reviews' , 'tags' ]
351+ return buildPrismaInclude ( info , relationFields )
352+ }
353+
354+ static buildReviewInclude ( info ?: any ) : any {
355+ const relationFields = [ 'product' ]
356+ return buildPrismaInclude ( info , relationFields )
357+ }
358+
359+ static buildTagInclude ( info ?: any ) : any {
360+ const relationFields = [ 'products' ]
361+ return buildPrismaInclude ( info , relationFields )
362+ }
363+
364+ static buildProductTagInclude ( info ?: any ) : any {
365+ const relationFields = [ 'product' , 'tag' ]
366+ return buildPrismaInclude ( info , relationFields )
367+ }
368+ }
369+
370+
371+ export const PRODUCT_INCLUDES = {
372+ reviews : true ,
373+ tags : true
374+ }
375+
376+ export const REVIEW_INCLUDES = {
377+ product : true
378+ }
379+
380+ export const TAG_INCLUDES = {
381+ products : true
382+ }
383+
384+ export const PRODUCTTAG_INCLUDES = {
385+ product : true ,
386+ tag : true
387+ }
0 commit comments