1
- import { FieldNode , GraphQLResolveInfo , Kind } from 'graphql'
1
+ import {
2
+ FieldNode ,
3
+ GraphQLResolveInfo ,
4
+ Kind ,
5
+ SelectionNode ,
6
+ SelectionSetNode ,
7
+ } from 'graphql'
2
8
3
- import type { ObjectKeys , KeysMatching } from '../types'
9
+ import type { ObjectKeys , KeysMatching , RecursiveRecord , MapRecordValues , RemoveNullables } from '../types'
10
+
11
+ /**
12
+ * The disallowed types that should be skipped during the processing.
13
+ */
14
+ type DisallowedTypes = Date | ( ( ...args : any ) => any )
15
+
16
+ /**
17
+ * The disallowed keys whose values are matching with {@link DisallowedTypes}.
18
+ */
19
+ type DisallowedKeys < T extends object > = KeysMatching < T , DisallowedTypes >
20
+
21
+ /**
22
+ * A function that will return the new string key value of a key that is found
23
+ * in the parents path.
24
+ *
25
+ * @param {string[] } parents The parent keys.
26
+ * @param {string } keyName The name of the key.
27
+ * @return {string } The new key name.
28
+ */
29
+ type SelectionNameMapperFn = ( parents : string [ ] , keyName : string ) => string
30
+
31
+ /**
32
+ * Selects the keys whose values are primitives (not object, not disallowed).
33
+ */
34
+ type PrimitiveKeys < T extends object >
35
+ = Exclude < keyof T , ObjectKeys < T > | DisallowedKeys < T > >
36
+
37
+ /**
38
+ * Selects the keys that are allowed.
39
+ */
40
+ type AllowedKeys < T extends object >
41
+ = PrimitiveKeys < T >
42
+ | Exclude < keyof T , DisallowedTypes >
43
+
44
+ /**
45
+ * The value that selection could be.
46
+ */
47
+ type SelectionOutput = string | SelectionNameMapperFn
48
+
49
+ /**
50
+ * Selects the properties of an object that are allowed.
51
+ */
52
+ type SelectObjectProperties <
53
+ T extends object ,
54
+ K extends keyof T = AllowedKeys < T >
55
+ > = {
56
+ [ k in K ] ?: T [ k ] extends infer U
57
+ ? ( U extends ( infer V ) [ ]
58
+ ? ( V extends DisallowedTypes
59
+ ? never
60
+ : ( V extends object
61
+ ? IGraphQLExtractSelectionMap < V , AllowedKeys < V > > | SelectionOutput
62
+ : SelectionOutput
63
+ )
64
+ )
65
+ : ( U extends DisallowedTypes
66
+ ? never
67
+ : ( U extends object
68
+ ? IGraphQLExtractSelectionMap < U , AllowedKeys < U > > | SelectionOutput
69
+ : SelectionOutput
70
+ )
71
+ )
72
+ )
73
+ : SelectionOutput
74
+ }
4
75
5
76
/**
6
77
* Defines how the fields should be renamed/mapped.
@@ -16,40 +87,37 @@ import type { ObjectKeys, KeysMatching } from '../types'
16
87
* @template T The type of the source.
17
88
* @template K The keys included of the source.
18
89
*/
19
- export type IGraphQLExtractSelectionMap < T extends object = object , K extends keyof T = Exclude < ObjectKeys < T > , KeysMatching < T , Date > > > = {
20
- [ k in K ] ?: string | ( ( parentKeys : string [ ] , fieldName : K ) => string )
21
- }
90
+ export type IGraphQLExtractSelectionMap <
91
+ T extends object = object ,
92
+ K extends keyof T = AllowedKeys < T >
93
+ > = RemoveNullables < SelectObjectProperties < T , K > , void | undefined | null >
22
94
23
95
/**
24
96
* A recursive type that supports partial `select` mapping of a `Prisma`
25
97
* select.
26
98
*/
27
- export type IGraphQLPrismaSelect < T extends object = object , K extends keyof T = Exclude < ObjectKeys < T > , KeysMatching < T , Date | Function > > > = {
28
- [ k in K ] ?: T [ k ] extends object
29
- ? ( T [ k ] extends Date
99
+ export type IGraphQLPrismaSelect <
100
+ T extends object = object ,
101
+ K extends keyof T = AllowedKeys < T >
102
+ > = {
103
+ [ k in K ] ?: T [ k ] extends DisallowedTypes
30
104
? boolean
31
- : (
32
- T [ k ] extends any [ ]
33
- ? boolean
34
- : ( T [ k ] extends ( ...args : any [ ] ) => any
35
- ? never
36
- : ( boolean | {
37
- select ?: IGraphQLPrismaSelect < T [ k ] , keyof T [ k ] >
38
- } )
39
- )
40
- )
41
- )
42
- : boolean
43
- }
105
+ : T [ k ] extends any [ ]
106
+ ? boolean
107
+ : T [ k ] extends object ? ( {
108
+ select : IGraphQLPrismaSelect < T [ k ] , AllowedKeys < T [ k ] > >
109
+ } ) | boolean
110
+ : boolean
111
+ }
44
112
45
- interface IExtractGraphQLSelectionsParams < T extends object = object , K extends keyof T = Exclude < ObjectKeys < T > , KeysMatching < T , Date > > > {
113
+ interface IExtractGraphQLSelectionsParams < T extends object = object , K extends keyof T = AllowedKeys < T > > {
46
114
/**
47
115
* The root node of the GraphQL request.
48
116
*
49
117
* @type {FieldNode }
50
118
* @memberof IParams
51
119
*/
52
- node : FieldNode
120
+ node : FieldNode | SelectionNode
53
121
54
122
/**
55
123
* The map for renaming the field names to database navigation keys.
@@ -58,14 +126,6 @@ interface IExtractGraphQLSelectionsParams<T extends object = object, K extends k
58
126
* @memberof IParams
59
127
*/
60
128
selectionMap ?: IGraphQLExtractSelectionMap < T , K >
61
-
62
- /**
63
- * The name array of the parent fields.
64
- *
65
- * @type {string[] }
66
- * @memberof IParams
67
- */
68
- parentFieldKeys ?: string [ ]
69
129
}
70
130
71
131
/**
@@ -80,49 +140,152 @@ interface IExtractGraphQLSelectionsParams<T extends object = object, K extends k
80
140
* @template T The type of the source.
81
141
* @template K The keys included of the source.
82
142
*/
83
- export function extractGraphQLSelections < T extends object = object , K extends keyof T = Exclude < ObjectKeys < T > , KeysMatching < T , Date > > > ( params : IExtractGraphQLSelectionsParams < T , K > ) {
84
- const { node, parentFieldKeys = [ ] , selectionMap = { } } = params
85
-
86
- const { selectionSet } = node
87
- if ( ! selectionSet ) return { }
88
-
89
- return selectionSet . selections . reduce ( ( prev , curr ) => {
90
- if ( curr . kind !== Kind . FIELD ) return prev
91
- const { name : { value : fieldName } , selectionSet } = curr
92
-
93
- if ( selectionSet ) {
94
- const data = extractGraphQLSelections ( {
95
- node : curr ,
96
- selectionMap,
97
- parentFieldKeys : [ ...parentFieldKeys , fieldName ]
98
- } )
99
-
100
- const mapper = selectionMap [ fieldName as keyof typeof selectionMap ] as any
101
- let mappedValue : string
102
- if ( typeof mapper === 'string' ) {
103
- mappedValue = mapper
104
- }
105
- else if ( typeof mapper === 'function' ) {
106
- mappedValue = mapper ( parentFieldKeys , fieldName )
107
- }
108
- else {
109
- mappedValue = fieldName
143
+ export function extractGraphQLSelections <
144
+ T extends object = object ,
145
+ K extends keyof T = AllowedKeys < T >
146
+ > ( params : IExtractGraphQLSelectionsParams < T , K > ) {
147
+ const { node } = params
148
+
149
+ const selectionObject
150
+ = getGraphQLSelectionsObject ( node ) as MapRecordValues < T , string >
151
+
152
+ const mappedSelections = modifyGraphQLSelections < T > (
153
+ selectionObject ,
154
+ //@ts -ignore
155
+ params . selectionMap
156
+ )
157
+
158
+ const values = intoPrismaSelection < T > ( mappedSelections , { } )
159
+
160
+ let name : string
161
+ if ( node . kind === Kind . FIELD ) {
162
+ name = node . name . value
163
+ }
164
+ else {
165
+ name = Object . keys ( values ) [ 0 ]
166
+ }
167
+
168
+ const returnValue = values [ name as keyof typeof values ]
169
+ if ( 'select' in returnValue ) {
170
+ return returnValue [ 'select' ]
171
+ }
172
+
173
+ return returnValue
174
+ }
175
+
176
+ /**
177
+ * Converts a selected keys object into prisma query selection object.
178
+ *
179
+ * @template T The type of the object.
180
+ * @param {Record<string, any> } info The selection information object.
181
+ * @param {RecursiveRecord<boolean> } [base={}] The base object for selections.
182
+ * @return {IGraphQLPrismaSelect<T> } The prisma selections result.
183
+ */
184
+ function intoPrismaSelection < T extends object > ( info : Record < string , any > , base : RecursiveRecord < boolean > = { } ) {
185
+ for ( const key in info ) {
186
+ const value = info [ key ]
187
+
188
+ if ( typeof value === 'object' ) {
189
+ const data = intoPrismaSelection ( value , { } )
190
+ base [ key ] = {
191
+ select : data
110
192
}
193
+ }
194
+ else {
195
+ base [ key ] = true
196
+ }
197
+ }
111
198
112
- prev [ fieldName ] = {
113
- include : {
114
- [ mappedValue ] : {
115
- select : data
199
+ return base as IGraphQLPrismaSelect < T >
200
+ }
201
+
202
+ /**
203
+ * Extracts the GraphQL selections into an object.
204
+ *
205
+ * @export
206
+ * @param {SelectionNode } node The root node to start.
207
+ * @return {RecursiveRecord<string> } A recursive record of string values.
208
+ */
209
+ export function getGraphQLSelectionsObject ( node : SelectionNode ) {
210
+ if ( node . kind === Kind . FIELD ) {
211
+ const { name : { value : name } , selectionSet } = node
212
+ const selections = selectionSet ?. selections
213
+
214
+ if ( Array . isArray ( selections ) ) {
215
+ const { selections } = selectionSet as SelectionSetNode
216
+ const collection : RecursiveRecord < string > = { }
217
+
218
+ for ( const selection of selections ) {
219
+ const data = getGraphQLSelectionsObject ( selection )
220
+
221
+ const previousValue = collection [ name ]
222
+ if ( typeof previousValue === 'object' ) {
223
+ collection [ name ] = {
224
+ ...previousValue ,
225
+ ...data
116
226
}
117
227
}
228
+ else {
229
+ collection [ name ] = data
230
+ }
118
231
}
232
+
233
+ return collection
119
234
}
120
235
else {
121
- prev [ fieldName ] = true
236
+ return { [ name ] : name }
122
237
}
238
+ }
239
+ else {
240
+ return { }
241
+ }
242
+ }
243
+
244
+ /**
245
+ * Applies given selection mappers object to given source.
246
+ *
247
+ * @export
248
+ * @template T The type of the actual object.
249
+ * @param {MapRecordValues<T, string> } source The source object.
250
+ * @param {SelectionMapOf<T> } [selectionMap={}] The mapper functions object.
251
+ * @param {string[] } [parentKeys=[]] The parent keys that will be passed to
252
+ * the functions found in the `selectionMap` parameter.
253
+ *
254
+ * @return {* } An object whose values are mapped with given mappers object.
255
+ */
256
+ export function modifyGraphQLSelections < T extends object > (
257
+ source : MapRecordValues < T , string > ,
258
+ selectionMap : Partial < IGraphQLExtractSelectionMap < T , AllowedKeys < T > > > = { } ,
259
+ parentKeys : string [ ] = [ ]
260
+ ) {
261
+ for ( const key in source ) {
262
+ const value = source [ key ] as string | MapRecordValues < T , string >
263
+ const newParentKeys = [ ...parentKeys , key ]
264
+
265
+ if ( typeof value === 'object' ) {
266
+ //@ts -ignore
267
+ const selMap = selectionMap [ key ] ?? { }
268
+
269
+ //@ts -ignore
270
+ source [ key ] = modifyGraphQLSelections ( value , selMap , newParentKeys )
271
+ }
272
+ else if ( ( key as string ) in selectionMap ) {
273
+ //@ts -ignore
274
+ const mapper = selectionMap [ key ]
275
+
276
+ if ( typeof mapper === 'function' ) {
277
+ const newValue = mapper ( newParentKeys , value )
278
+ //@ts -ignore
279
+ source [ key ] = newValue
280
+ }
281
+ else if ( typeof mapper === 'string' ) {
282
+ //@ts -ignore
283
+ source [ key ] = mapper
284
+ }
285
+ }
286
+ }
123
287
124
- return prev
125
- } , { } as Record < string , any > )
288
+ return source
126
289
}
127
290
128
291
interface IExtractGraphQLSelectionPathParams < T extends object = object , K extends keyof T = Exclude < ObjectKeys < T > , KeysMatching < T , Date > > > {
0 commit comments