Skip to content

Commit ffc0285

Browse files
Merge pull request #3 from hakutakuAi/development
2 parents 31b9265 + e1c7306 commit ffc0285

File tree

67 files changed

+7163
-1038
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

67 files changed

+7163
-1038
lines changed

examples/advanced-graphql/prisma/schema.prisma

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,8 @@ datasource db {
99
}
1010

1111
generator client {
12-
provider = "prisma-client-js"
12+
provider = "prisma-client-js"
13+
binaryTargets = ["native", "debian-openssl-3.0.x"]
1314
}
1415

1516
enum ProductStatus {
Lines changed: 387 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,387 @@
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

Comments
 (0)