Skip to content

Commit 3262abe

Browse files
committed
Improved deeply nested structures
1 parent 7d6ba60 commit 3262abe

File tree

4 files changed

+268
-65
lines changed

4 files changed

+268
-65
lines changed

CHANGELOG.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,13 @@ This file contains the changes made to the package.
33

44
The sections are in descending order of the change date.
55

6+
## [0.1.14]: - 2022-08-16
7+
### Fixed
8+
- Prisma Selections for deeply nested objects were not working correctly.
9+
10+
## Added
11+
- Better way to handle renaming.
12+
613
## [0.1.9] - 2022-04-27
714
### Changed
815
- `IOnResolvingParams.context` property.
@@ -31,6 +38,8 @@ object.
3138
The initial version of the package.
3239

3340
[Unreleased]: https://github.com/incetarik/nestjs-prisma-dynamic-resolvers/compare/v1.0.0...HEAD
41+
[0.1.13]: https://github.com/incetarik/nestjs-prisma-dynamic-resolvers/compare/0.1.3...0.1.14
42+
[0.1.13]: https://github.com/incetarik/nestjs-prisma-dynamic-resolvers/compare/0.1.9...0.1.13
3443
[0.1.9]: https://github.com/incetarik/nestjs-prisma-dynamic-resolvers/compare/0.1.7...0.1.9
3544
[0.1.7]: https://github.com/incetarik/nestjs-prisma-dynamic-resolvers/compare/0.1.6...0.1.7
3645
[0.1.6]: https://github.com/incetarik/nestjs-prisma-dynamic-resolvers/compare/0.1.5...0.1.6

src/helpers/extract-grapql-selections.ts

Lines changed: 228 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,77 @@
1-
import { FieldNode, GraphQLResolveInfo, Kind } from 'graphql'
1+
import {
2+
FieldNode,
3+
GraphQLResolveInfo,
4+
Kind,
5+
SelectionNode,
6+
SelectionSetNode,
7+
} from 'graphql'
28

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+
}
475

576
/**
677
* Defines how the fields should be renamed/mapped.
@@ -16,40 +87,37 @@ import type { ObjectKeys, KeysMatching } from '../types'
1687
* @template T The type of the source.
1788
* @template K The keys included of the source.
1889
*/
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>
2294

2395
/**
2496
* A recursive type that supports partial `select` mapping of a `Prisma`
2597
* select.
2698
*/
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
30104
? 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+
}
44112

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>> {
46114
/**
47115
* The root node of the GraphQL request.
48116
*
49117
* @type {FieldNode}
50118
* @memberof IParams
51119
*/
52-
node: FieldNode
120+
node: FieldNode | SelectionNode
53121

54122
/**
55123
* The map for renaming the field names to database navigation keys.
@@ -58,14 +126,6 @@ interface IExtractGraphQLSelectionsParams<T extends object = object, K extends k
58126
* @memberof IParams
59127
*/
60128
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[]
69129
}
70130

71131
/**
@@ -80,49 +140,152 @@ interface IExtractGraphQLSelectionsParams<T extends object = object, K extends k
80140
* @template T The type of the source.
81141
* @template K The keys included of the source.
82142
*/
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
110192
}
193+
}
194+
else {
195+
base[ key ] = true
196+
}
197+
}
111198

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
116226
}
117227
}
228+
else {
229+
collection[ name ] = data
230+
}
118231
}
232+
233+
return collection
119234
}
120235
else {
121-
prev[ fieldName ] = true
236+
return { [ name ]: name }
122237
}
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+
}
123287

124-
return prev
125-
}, {} as Record<string, any>)
288+
return source
126289
}
127290

128291
interface IExtractGraphQLSelectionPathParams<T extends object = object, K extends keyof T = Exclude<ObjectKeys<T>, KeysMatching<T, Date>>> {

src/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ export {
44
extractGraphQLSelectionPath,
55
extractGraphQLSelections,
66
provideDynamicResolvers,
7+
getGraphQLSelectionsObject,
8+
modifyGraphQLSelections,
79
IGraphQLExtractSelectionMap,
810
IGraphQLPrismaSelect,
911
} from './helpers'

0 commit comments

Comments
 (0)