Skip to content

Commit 74581cf

Browse files
authoredJul 12, 2021
feat: support Gatsby-style directives in extensions (#3185)
* feat: support Gatsby-style directives in extensions BREAKING CHANGE: getDirectives now always return an array of DirectiveAnnotation objects New function getDirective returns an array of args records for each use of the directive. Note: this is true even when the directive is non-repeatable. This is because one use of this function is to throw an error if more than one directive annotation is used for a non repeatable directive! * add changeset
1 parent c5342de commit 74581cf

11 files changed

+482
-343
lines changed
 

‎.changeset/quick-hotels-beam.md

+16
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
---
2+
'@graphql-tools/stitch': major
3+
'@graphql-tools/stitching-directives': major
4+
'@graphql-tools/utils': major
5+
'@graphql-tools/wrap': major
6+
---
7+
8+
fix(getDirectives): preserve order around repeatable directives
9+
10+
BREAKING CHANGE: getDirectives now always return an array of individual DirectiveAnnotation objects consisting of `name` and `args` properties.
11+
12+
New useful function `getDirective` returns an array of objects representing any args for each use of a single directive (returning the empty object `{}` when a directive is used without arguments).
13+
14+
Note: The `getDirective` function returns an array even when the specified directive is non-repeatable. This is because one use of this function is to throw an error if more than one directive annotation is used for a non repeatable directive!
15+
16+
When specifying directives in extensions, one can use either the old or new format.

‎packages/stitch/src/subschemaConfigTransforms/computedDirectiveTransformer.ts

+3-3
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { getDirectives, MapperKind, mapSchema } from '@graphql-tools/utils';
1+
import { getDirective, MapperKind, mapSchema } from '@graphql-tools/utils';
22
import { cloneSubschemaConfig, SubschemaConfig } from '@graphql-tools/delegate';
33

44
import { SubschemaConfigTransform } from '../types';
@@ -15,13 +15,13 @@ export function computedDirectiveTransformer(computedDirectiveName: string): Sub
1515
return undefined;
1616
}
1717

18-
const computed = getDirectives(schema, fieldConfig)[computedDirectiveName];
18+
const computed = getDirective(schema, fieldConfig, computedDirectiveName)?.[0];
1919

2020
if (computed == null) {
2121
return undefined;
2222
}
2323

24-
const selectionSet = computed.fields != null ? `{ ${computed.fields} }` : computed.selectionSet;
24+
const selectionSet = computed['fields'] != null ? `{ ${computed['fields']} }` : computed['selectionSet'];
2525

2626
if (selectionSet == null) {
2727
return undefined;

‎packages/stitch/tests/mergeDefinitions.test.ts

+19-19
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { makeExecutableSchema } from '@graphql-tools/schema';
22
import { stitchSchemas } from '@graphql-tools/stitch';
3-
import { getDirectives } from '@graphql-tools/utils';
3+
import { getDirective } from '@graphql-tools/utils';
44
import { stitchingDirectives } from '@graphql-tools/stitching-directives';
55
import {
66
GraphQLObjectType,
@@ -282,22 +282,22 @@ describe('merge canonical types', () => {
282282
const scalarType = gatewaySchema.getType('ProductScalar');
283283
assertGraphQLScalerType(scalarType)
284284

285-
expect(getDirectives(firstSchema, queryType.toConfig())['mydir'].value).toEqual('first');
286-
expect(getDirectives(firstSchema, objectType.toConfig())['mydir'].value).toEqual('first');
287-
expect(getDirectives(firstSchema, interfaceType.toConfig())['mydir'].value).toEqual('first');
288-
expect(getDirectives(firstSchema, inputType.toConfig())['mydir'].value).toEqual('first');
289-
expect(getDirectives(firstSchema, enumType.toConfig())['mydir'].value).toEqual('first');
290-
expect(getDirectives(firstSchema, unionType.toConfig())['mydir'].value).toEqual('first');
291-
expect(getDirectives(firstSchema, scalarType.toConfig())['mydir'].value).toEqual('first');
292-
293-
expect(getDirectives(firstSchema, queryType.getFields()['field1'])['mydir'].value).toEqual('first');
294-
expect(getDirectives(firstSchema, queryType.getFields()['field2'])['mydir'].value).toEqual('second');
295-
expect(getDirectives(firstSchema, objectType.getFields()['id'])['mydir'].value).toEqual('first');
296-
expect(getDirectives(firstSchema, objectType.getFields()['url'])['mydir'].value).toEqual('second');
297-
expect(getDirectives(firstSchema, interfaceType.getFields()['id'])['mydir'].value).toEqual('first');
298-
expect(getDirectives(firstSchema, interfaceType.getFields()['url'])['mydir'].value).toEqual('second');
299-
expect(getDirectives(firstSchema, inputType.getFields()['id'])['mydir'].value).toEqual('first');
300-
expect(getDirectives(firstSchema, inputType.getFields()['url'])['mydir'].value).toEqual('second');
285+
expect(getDirective(firstSchema, queryType.toConfig(), 'mydir')?.[0]['value']).toEqual('first');
286+
expect(getDirective(firstSchema, objectType.toConfig(), 'mydir')?.[0]['value']).toEqual('first');
287+
expect(getDirective(firstSchema, interfaceType.toConfig(), 'mydir')?.[0]['value']).toEqual('first');
288+
expect(getDirective(firstSchema, inputType.toConfig(), 'mydir')?.[0]['value']).toEqual('first');
289+
expect(getDirective(firstSchema, enumType.toConfig(), 'mydir')?.[0]['value']).toEqual('first');
290+
expect(getDirective(firstSchema, unionType.toConfig(), 'mydir')?.[0]['value']).toEqual('first');
291+
expect(getDirective(firstSchema, scalarType.toConfig(), 'mydir')?.[0]['value']).toEqual('first');
292+
293+
expect(getDirective(firstSchema, queryType.getFields()['field1'], 'mydir')?.[0]['value']).toEqual('first');
294+
expect(getDirective(firstSchema, queryType.getFields()['field2'], 'mydir')?.[0]['value']).toEqual('second');
295+
expect(getDirective(firstSchema, objectType.getFields()['id'], 'mydir')?.[0]['value']).toEqual('first');
296+
expect(getDirective(firstSchema, objectType.getFields()['url'], 'mydir')?.[0]['value']).toEqual('second');
297+
expect(getDirective(firstSchema, interfaceType.getFields()['id'], 'mydir')?.[0]['value']).toEqual('first');
298+
expect(getDirective(firstSchema, interfaceType.getFields()['url'], 'mydir')?.[0]['value']).toEqual('second');
299+
expect(getDirective(firstSchema, inputType.getFields()['id'], 'mydir')?.[0]['value']).toEqual('first');
300+
expect(getDirective(firstSchema, inputType.getFields()['url'], 'mydir')?.[0]['value']).toEqual('second');
301301

302302
expect(enumType.toConfig().astNode?.values?.map(v => v.description?.value)).toEqual(['first', 'first', 'second']);
303303
expect(enumType.toConfig().values['YES'].astNode?.description?.value).toEqual('first');
@@ -309,8 +309,8 @@ describe('merge canonical types', () => {
309309
const objectType = gatewaySchema.getType('Product') as GraphQLObjectType;
310310
expect(objectType.getFields()['id'].deprecationReason).toEqual('first');
311311
expect(objectType.getFields()['url'].deprecationReason).toEqual('second');
312-
expect(getDirectives(firstSchema, objectType.getFields()['id'])['deprecated'].reason).toEqual('first');
313-
expect(getDirectives(firstSchema, objectType.getFields()['url'])['deprecated'].reason).toEqual('second');
312+
expect(getDirective(firstSchema, objectType.getFields()['id'], 'deprecated')?.[0]['reason']).toEqual('first');
313+
expect(getDirective(firstSchema, objectType.getFields()['url'], 'deprecated')?.[0]['reason']).toEqual('second');
314314
});
315315

316316
it('promotes canonical root field definitions', async () => {

‎packages/stitch/tests/typeMergingWithExtensions.test.ts

+9-8
Original file line numberDiff line numberDiff line change
@@ -52,9 +52,9 @@ describe('merging using type merging', () => {
5252
},
5353
resolve: (_root, { keys }) => keys.map((key: Record<string, any>) => users.find(u => u.id === key['id'])),
5454
extensions: {
55-
directives: {
56-
merge: {},
57-
},
55+
directives: [{
56+
name: 'merge',
57+
}],
5858
},
5959
}
6060
}),
@@ -68,12 +68,13 @@ describe('merging using type merging', () => {
6868
username: { type: GraphQLString }
6969
}),
7070
extensions: {
71-
directives: {
72-
key: {
71+
directives: [{
72+
name: 'key',
73+
args: {
7374
selectionSet: '{ id }',
74-
}
75-
}
76-
}
75+
},
76+
}],
77+
},
7778
});
7879

7980
const accountsSchema = stitchingDirectivesValidator(new GraphQLSchema({

‎packages/stitching-directives/src/stitchingDirectivesTransformer.ts

+95-53
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ import {
1717

1818
import { cloneSubschemaConfig, SubschemaConfig, MergedTypeConfig, MergedFieldConfig } from '@graphql-tools/delegate';
1919
import {
20-
getDirectives,
20+
getDirective,
2121
getImplementingTypes,
2222
MapperKind,
2323
mapSchema,
@@ -73,45 +73,48 @@ export function stitchingDirectivesTransformer(
7373

7474
mapSchema(schema, {
7575
[MapperKind.OBJECT_TYPE]: type => {
76-
const directives = getDirectives(schema, type, pathToDirectivesInExtensions);
77-
78-
if (keyDirectiveName != null && directives[keyDirectiveName] != null) {
79-
const keyDirective = directives[keyDirectiveName];
80-
const selectionSet = parseSelectionSet(keyDirective.selectionSet, { noLocation: true });
76+
const keyDirective = getDirective(schema, type, keyDirectiveName, pathToDirectivesInExtensions)?.[0];
77+
if (keyDirective != null) {
78+
const selectionSet = parseSelectionSet(keyDirective['selectionSet'], { noLocation: true });
8179
selectionSetsByType[type.name] = selectionSet;
8280
}
8381

84-
if (canonicalDirectiveName != null && directives[canonicalDirectiveName]) {
82+
const canonicalDirective = getDirective(
83+
schema,
84+
type,
85+
canonicalDirectiveName,
86+
pathToDirectivesInExtensions
87+
)?.[0];
88+
if (canonicalDirective != null) {
8589
setCanonicalDefinition(type.name);
8690
}
87-
8891
return undefined;
8992
},
9093
[MapperKind.OBJECT_FIELD]: (fieldConfig, fieldName, typeName) => {
91-
const directives = getDirectives(schema, fieldConfig, pathToDirectivesInExtensions);
92-
93-
if (computedDirectiveName != null && directives[computedDirectiveName] != null) {
94-
const computedDirective = directives[computedDirectiveName];
95-
const selectionSet = parseSelectionSet(computedDirective.selectionSet, { noLocation: true });
94+
const computedDirective = getDirective(
95+
schema,
96+
fieldConfig,
97+
computedDirectiveName,
98+
pathToDirectivesInExtensions
99+
)?.[0];
100+
if (computedDirective != null) {
101+
const selectionSet = parseSelectionSet(computedDirective['selectionSet'], { noLocation: true });
96102
if (!computedFieldSelectionSets[typeName]) {
97103
computedFieldSelectionSets[typeName] = Object.create(null);
98104
}
99105
computedFieldSelectionSets[typeName][fieldName] = selectionSet;
100106
}
101107

102-
if (
103-
mergeDirectiveName != null &&
104-
directives[mergeDirectiveName] != null &&
105-
directives[mergeDirectiveName].keyField
106-
) {
107-
const mergeDirectiveKeyField = directives[mergeDirectiveName].keyField;
108+
const mergeDirective = getDirective(schema, fieldConfig, mergeDirectiveName, pathToDirectivesInExtensions)?.[0];
109+
if (mergeDirective?.['keyField'] != null) {
110+
const mergeDirectiveKeyField = mergeDirective['keyField'];
108111
const selectionSet = parseSelectionSet(`{ ${mergeDirectiveKeyField}}`, { noLocation: true });
109112

110-
const typeNames: Array<string> = directives[mergeDirectiveName].types;
113+
const typeNames: Array<string> = mergeDirective['types'];
111114

112115
const returnType = getNamedType(fieldConfig.type);
113116

114-
forEachConcreteType(schema, returnType, directives[mergeDirectiveName]?.types, typeName => {
117+
forEachConcreteType(schema, returnType, typeNames, typeName => {
115118
if (typeNames == null || typeNames.includes(typeName)) {
116119
const existingSelectionSet = selectionSetsByType[typeName];
117120
selectionSetsByType[typeName] = existingSelectionSet
@@ -121,70 +124,111 @@ export function stitchingDirectivesTransformer(
121124
});
122125
}
123126

124-
if (canonicalDirectiveName != null && directives[canonicalDirectiveName] != null) {
127+
const canonicalDirective = getDirective(
128+
schema,
129+
fieldConfig,
130+
canonicalDirectiveName,
131+
pathToDirectivesInExtensions
132+
)?.[0];
133+
if (canonicalDirective != null) {
125134
setCanonicalDefinition(typeName, fieldName);
126135
}
127136

128137
return undefined;
129138
},
130139
[MapperKind.INTERFACE_TYPE]: type => {
131-
const directives = getDirectives(schema, type, pathToDirectivesInExtensions);
132-
133-
if (canonicalDirectiveName != null && directives[canonicalDirectiveName] != null) {
140+
const canonicalDirective = getDirective(
141+
schema,
142+
type,
143+
canonicalDirectiveName,
144+
pathToDirectivesInExtensions
145+
)?.[0];
146+
147+
if (canonicalDirective) {
134148
setCanonicalDefinition(type.name);
135149
}
136150

137151
return undefined;
138152
},
139153
[MapperKind.INTERFACE_FIELD]: (fieldConfig, fieldName, typeName) => {
140-
const directives = getDirectives(schema, fieldConfig, pathToDirectivesInExtensions);
141-
142-
if (canonicalDirectiveName != null && directives[canonicalDirectiveName]) {
154+
const canonicalDirective = getDirective(
155+
schema,
156+
fieldConfig,
157+
canonicalDirectiveName,
158+
pathToDirectivesInExtensions
159+
)?.[0];
160+
161+
if (canonicalDirective) {
143162
setCanonicalDefinition(typeName, fieldName);
144163
}
145164

146165
return undefined;
147166
},
148167
[MapperKind.INPUT_OBJECT_TYPE]: type => {
149-
const directives = getDirectives(schema, type, pathToDirectivesInExtensions);
150-
151-
if (canonicalDirectiveName != null && directives[canonicalDirectiveName] != null) {
168+
const canonicalDirective = getDirective(
169+
schema,
170+
type,
171+
canonicalDirectiveName,
172+
pathToDirectivesInExtensions
173+
)?.[0];
174+
175+
if (canonicalDirective) {
152176
setCanonicalDefinition(type.name);
153177
}
154178

155179
return undefined;
156180
},
157181
[MapperKind.INPUT_OBJECT_FIELD]: (inputFieldConfig, fieldName, typeName) => {
158-
const directives = getDirectives(schema, inputFieldConfig, pathToDirectivesInExtensions);
159-
160-
if (canonicalDirectiveName != null && directives[canonicalDirectiveName] != null) {
182+
const canonicalDirective = getDirective(
183+
schema,
184+
inputFieldConfig,
185+
canonicalDirectiveName,
186+
pathToDirectivesInExtensions
187+
)?.[0];
188+
189+
if (canonicalDirective != null) {
161190
setCanonicalDefinition(typeName, fieldName);
162191
}
163192

164193
return undefined;
165194
},
166195
[MapperKind.UNION_TYPE]: type => {
167-
const directives = getDirectives(schema, type, pathToDirectivesInExtensions);
168-
169-
if (canonicalDirectiveName != null && directives[canonicalDirectiveName] != null) {
196+
const canonicalDirective = getDirective(
197+
schema,
198+
type,
199+
canonicalDirectiveName,
200+
pathToDirectivesInExtensions
201+
)?.[0];
202+
203+
if (canonicalDirective != null) {
170204
setCanonicalDefinition(type.name);
171205
}
172206

173207
return undefined;
174208
},
175209
[MapperKind.ENUM_TYPE]: type => {
176-
const directives = getDirectives(schema, type, pathToDirectivesInExtensions);
177-
178-
if (canonicalDirectiveName != null && directives[canonicalDirectiveName] != null) {
210+
const canonicalDirective = getDirective(
211+
schema,
212+
type,
213+
canonicalDirectiveName,
214+
pathToDirectivesInExtensions
215+
)?.[0];
216+
217+
if (canonicalDirective != null) {
179218
setCanonicalDefinition(type.name);
180219
}
181220

182221
return undefined;
183222
},
184223
[MapperKind.SCALAR_TYPE]: type => {
185-
const directives = getDirectives(schema, type, pathToDirectivesInExtensions);
186-
187-
if (canonicalDirectiveName != null && directives[canonicalDirectiveName] != null) {
224+
const canonicalDirective = getDirective(
225+
schema,
226+
type,
227+
canonicalDirectiveName,
228+
pathToDirectivesInExtensions
229+
)?.[0];
230+
231+
if (canonicalDirective != null) {
188232
setCanonicalDefinition(type.name);
189233
}
190234

@@ -248,23 +292,21 @@ export function stitchingDirectivesTransformer(
248292

249293
mapSchema(schema, {
250294
[MapperKind.OBJECT_FIELD]: (fieldConfig, fieldName) => {
251-
const directives = getDirectives(schema, fieldConfig, pathToDirectivesInExtensions);
252-
253-
if (mergeDirectiveName != null && directives[mergeDirectiveName] != null) {
254-
const directiveArgumentMap = directives[mergeDirectiveName];
295+
const mergeDirective = getDirective(schema, fieldConfig, mergeDirectiveName, pathToDirectivesInExtensions)?.[0];
255296

297+
if (mergeDirective != null) {
256298
const returnType = getNullableType(fieldConfig.type);
257299
const returnsList = isListType(returnType);
258300
const namedType = getNamedType(returnType);
259301

260-
let mergeArgsExpr: string = directiveArgumentMap.argsExpr;
302+
let mergeArgsExpr: string = mergeDirective['argsExpr'];
261303

262304
if (mergeArgsExpr == null) {
263-
const key: Array<string> = directiveArgumentMap.key;
264-
const keyField: string = directiveArgumentMap.keyField;
305+
const key: Array<string> = mergeDirective['key'];
306+
const keyField: string = mergeDirective['keyField'];
265307
const keyExpr = key != null ? buildKeyExpr(key) : keyField != null ? `$key.${keyField}` : '$key';
266308

267-
const keyArg: string = directiveArgumentMap.keyArg;
309+
const keyArg: string = mergeDirective['keyArg'];
268310
const argNames = keyArg == null ? [Object.keys(fieldConfig.args ?? {})[0]] : keyArg.split('.');
269311

270312
const lastArgName = argNames.pop();
@@ -275,7 +317,7 @@ export function stitchingDirectivesTransformer(
275317
}
276318
}
277319

278-
const typeNames: Array<string> = directiveArgumentMap.types;
320+
const typeNames: Array<string> = mergeDirective['types'];
279321

280322
forEachConcreteTypeName(namedType, schema, typeNames, typeName => {
281323
const parsedMergeArgsExpr = parseMergeArgsExpr(
@@ -285,7 +327,7 @@ export function stitchingDirectivesTransformer(
285327
: mergeSelectionSets(...allSelectionSetsByType[typeName])
286328
);
287329

288-
const additionalArgs = directiveArgumentMap.additionalArgs;
330+
const additionalArgs = mergeDirective['additionalArgs'];
289331
if (additionalArgs != null) {
290332
parsedMergeArgsExpr.args = mergeDeep(
291333
parsedMergeArgsExpr.args,

‎packages/stitching-directives/src/stitchingDirectivesValidator.ts

+21-19
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import {
1111
} from 'graphql';
1212

1313
import {
14-
getDirectives,
14+
getDirective,
1515
getImplementingTypes,
1616
isSome,
1717
MapperKind,
@@ -39,26 +39,28 @@ export function stitchingDirectivesValidator(
3939

4040
mapSchema(schema, {
4141
[MapperKind.OBJECT_TYPE]: type => {
42-
const directives = getDirectives(schema, type, pathToDirectivesInExtensions);
42+
const keyDirective = getDirective(schema, type, keyDirectiveName, pathToDirectivesInExtensions)?.[0];
4343

44-
if (keyDirectiveName != null && directives[keyDirectiveName]) {
45-
const directiveArgumentMap = directives[keyDirectiveName];
46-
parseSelectionSet(directiveArgumentMap.selectionSet);
44+
if (keyDirective != null) {
45+
parseSelectionSet(keyDirective['selectionSet']);
4746
}
4847

4948
return undefined;
5049
},
5150
[MapperKind.OBJECT_FIELD]: (fieldConfig, _fieldName, typeName) => {
52-
const directives = getDirectives(schema, fieldConfig, pathToDirectivesInExtensions);
53-
54-
if (computedDirectiveName != null && directives[computedDirectiveName]) {
55-
const directiveArgumentMap = directives[computedDirectiveName];
56-
parseSelectionSet(directiveArgumentMap.selectionSet);
51+
const computedDirective = getDirective(
52+
schema,
53+
fieldConfig,
54+
computedDirectiveName,
55+
pathToDirectivesInExtensions
56+
)?.[0];
57+
58+
if (computedDirective != null) {
59+
parseSelectionSet(computedDirective['selectionSet']);
5760
}
5861

59-
if (mergeDirectiveName != null && directives[mergeDirectiveName]) {
60-
const directiveArgumentMap = directives[mergeDirectiveName];
61-
62+
const mergeDirective = getDirective(schema, fieldConfig, mergeDirectiveName, pathToDirectivesInExtensions)?.[0];
63+
if (mergeDirective != null) {
6264
if (typeName !== queryTypeName) {
6365
throw new Error('@merge directive may be used only for root fields of the root Query type.');
6466
}
@@ -73,14 +75,14 @@ export function stitchingDirectivesValidator(
7375
throw new Error('@merge directive must be used on a field that returns an object or a list of objects.');
7476
}
7577

76-
const mergeArgsExpr = directiveArgumentMap.argsExpr;
78+
const mergeArgsExpr = mergeDirective['argsExpr'];
7779
if (mergeArgsExpr != null) {
7880
parseMergeArgsExpr(mergeArgsExpr);
7981
}
8082

8183
const args = Object.keys(fieldConfig.args ?? {});
8284

83-
const keyArg = directiveArgumentMap.keyArg;
85+
const keyArg = mergeDirective['keyArg'];
8486
if (keyArg == null) {
8587
if (!mergeArgsExpr && args.length !== 1) {
8688
throw new Error(
@@ -94,15 +96,15 @@ export function stitchingDirectivesValidator(
9496
// TODO: ideally we should check that the arg exists for the resolver
9597
}
9698

97-
const keyField = directiveArgumentMap.keyArg;
99+
const keyField = mergeDirective['keyField'];
98100
if (keyField != null && !keyField.match(dottedNameRegEx)) {
99101
throw new Error(
100102
'`keyField` argument for @merge directive must be a set of valid GraphQL SDL names separated by periods.'
101103
);
102104
// TODO: ideally we should check that it is part of the key
103105
}
104106

105-
const key: Array<string> = directiveArgumentMap.key;
107+
const key: Array<string> = mergeDirective['key'];
106108
if (key != null) {
107109
if (keyField != null) {
108110
throw new Error('Cannot use @merge directive with both `keyField` and `key` arguments.');
@@ -132,7 +134,7 @@ export function stitchingDirectivesValidator(
132134
}
133135
}
134136

135-
const additionalArgs = directiveArgumentMap.additionalArgs;
137+
const additionalArgs = mergeDirective['additionalArgs'];
136138
if (additionalArgs != null) {
137139
parseValue(`{ ${additionalArgs} }`, { noLocation: true });
138140
}
@@ -147,7 +149,7 @@ export function stitchingDirectivesValidator(
147149
);
148150
}
149151

150-
const typeNames: Array<string> = directiveArgumentMap.types;
152+
const typeNames: Array<string> = mergeDirective['types'];
151153
if (typeNames != null) {
152154
if (!isAbstractType(returnType)) {
153155
throw new Error('Types argument can only be used with a field that returns an abstract type.');

‎packages/utils/src/get-directives.ts

+103-14
Original file line numberDiff line numberDiff line change
@@ -23,11 +23,13 @@ import {
2323
GraphQLEnumValueConfig,
2424
EnumValueDefinitionNode,
2525
} from 'graphql';
26-
import { Maybe } from '@graphql-tools/utils';
2726

2827
import { getArgumentValues } from './getArgumentValues';
2928

30-
export type DirectiveUseMap = { [key: string]: any };
29+
export interface DirectiveAnnotation {
30+
name: string;
31+
args?: Record<string, any>;
32+
}
3133

3234
type SchemaOrTypeNode =
3335
| SchemaDefinitionNode
@@ -58,23 +60,70 @@ type DirectableGraphQLObject =
5860
export function getDirectivesInExtensions(
5961
node: DirectableGraphQLObject,
6062
pathToDirectivesInExtensions = ['directives']
61-
): Maybe<DirectiveUseMap> {
62-
const directivesInExtensions = pathToDirectivesInExtensions.reduce(
63+
): Array<DirectiveAnnotation> {
64+
return pathToDirectivesInExtensions.reduce(
6365
(acc, pathSegment) => (acc == null ? acc : acc[pathSegment]),
6466
node?.extensions
67+
) as Array<DirectiveAnnotation>;
68+
}
69+
70+
function _getDirectiveInExtensions(
71+
directivesInExtensions: Array<DirectiveAnnotation>,
72+
directiveName: string
73+
): Array<Record<string, any>> | undefined {
74+
const directiveInExtensions = directivesInExtensions.filter(
75+
directiveAnnotation => directiveAnnotation.name === directiveName
6576
);
77+
if (!directiveInExtensions.length) {
78+
return undefined;
79+
}
80+
81+
return directiveInExtensions.map(directive => directive.args ?? {});
82+
}
83+
84+
export function getDirectiveInExtensions(
85+
node: DirectableGraphQLObject,
86+
directiveName: string,
87+
pathToDirectivesInExtensions = ['directives']
88+
): Array<Record<string, any>> | undefined {
89+
const directivesInExtensions = pathToDirectivesInExtensions.reduce(
90+
(acc, pathSegment) => (acc == null ? acc : acc[pathSegment]),
91+
node?.extensions
92+
) as Record<string, Record<string, any> | Array<Record<string, any>>> | Array<DirectiveAnnotation> | undefined;
6693

67-
return directivesInExtensions;
94+
if (directivesInExtensions === undefined) {
95+
return undefined;
96+
}
97+
98+
if (Array.isArray(directivesInExtensions)) {
99+
return _getDirectiveInExtensions(directivesInExtensions, directiveName);
100+
}
101+
102+
// Support condensed format by converting to longer format
103+
// The condensed format does not preserve ordering of directives when repeatable directives are used.
104+
// See https://github.com/ardatan/graphql-tools/issues/2534
105+
const reformattedDirectivesInExtensions: Array<DirectiveAnnotation> = [];
106+
for (const [name, argsOrArrayOfArgs] of Object.entries(directivesInExtensions)) {
107+
if (Array.isArray(argsOrArrayOfArgs)) {
108+
for (const args of argsOrArrayOfArgs) {
109+
reformattedDirectivesInExtensions.push({ name, args });
110+
}
111+
} else {
112+
reformattedDirectivesInExtensions.push({ name, args: argsOrArrayOfArgs });
113+
}
114+
}
115+
116+
return _getDirectiveInExtensions(reformattedDirectivesInExtensions, directiveName);
68117
}
69118

70119
export function getDirectives(
71120
schema: GraphQLSchema,
72121
node: DirectableGraphQLObject,
73122
pathToDirectivesInExtensions = ['directives']
74-
): DirectiveUseMap {
123+
): Array<DirectiveAnnotation> {
75124
const directivesInExtensions = getDirectivesInExtensions(node, pathToDirectivesInExtensions);
76125

77-
if (directivesInExtensions != null) {
126+
if (directivesInExtensions != null && directivesInExtensions.length > 0) {
78127
return directivesInExtensions;
79128
}
80129

@@ -94,23 +143,63 @@ export function getDirectives(
94143
astNodes = [...astNodes, ...node.extensionASTNodes];
95144
}
96145

97-
const result: DirectiveUseMap = {};
146+
const result: Array<DirectiveAnnotation> = [];
98147

99148
for (const astNode of astNodes) {
100149
if (astNode.directives) {
101150
for (const directiveNode of astNode.directives) {
102151
const schemaDirective = schemaDirectiveMap[directiveNode.name.value];
103152
if (schemaDirective) {
104-
if (schemaDirective.isRepeatable) {
105-
result[schemaDirective.name] = result[schemaDirective.name] ?? [];
106-
result[schemaDirective.name].push(getArgumentValues(schemaDirective, directiveNode));
107-
} else {
108-
result[schemaDirective.name] = getArgumentValues(schemaDirective, directiveNode);
109-
}
153+
result.push({ name: directiveNode.name.value, args: getArgumentValues(schemaDirective, directiveNode) });
154+
}
155+
}
156+
}
157+
}
158+
159+
return result;
160+
}
161+
162+
export function getDirective(
163+
schema: GraphQLSchema,
164+
node: DirectableGraphQLObject,
165+
directiveName: string,
166+
pathToDirectivesInExtensions = ['directives']
167+
): Array<Record<string, any>> | undefined {
168+
const directiveInExtensions = getDirectiveInExtensions(node, directiveName, pathToDirectivesInExtensions);
169+
170+
if (directiveInExtensions != null) {
171+
return directiveInExtensions;
172+
}
173+
174+
const schemaDirective = schema && schema.getDirective ? schema.getDirective(directiveName) : undefined;
175+
176+
if (schemaDirective == null) {
177+
return undefined;
178+
}
179+
180+
let astNodes: Array<SchemaOrTypeNode> = [];
181+
if (node.astNode) {
182+
astNodes.push(node.astNode);
183+
}
184+
if ('extensionASTNodes' in node && node.extensionASTNodes) {
185+
astNodes = [...astNodes, ...node.extensionASTNodes];
186+
}
187+
188+
const result: Array<Record<string, any>> = [];
189+
190+
for (const astNode of astNodes) {
191+
if (astNode.directives) {
192+
for (const directiveNode of astNode.directives) {
193+
if (directiveNode.name.value === directiveName) {
194+
result.push(getArgumentValues(schemaDirective, directiveNode));
110195
}
111196
}
112197
}
113198
}
114199

200+
if (!result.length) {
201+
return undefined;
202+
}
203+
115204
return result;
116205
}

‎packages/utils/tests/get-directives.spec.ts

+35-27
Original file line numberDiff line numberDiff line change
@@ -4,35 +4,36 @@ import { assertGraphQLObjectType } from '../../testing/assertion';
44
import { GraphQLSchema } from 'graphql';
55

66
describe('getDirectives', () => {
7-
it('should return the correct directives map when no directives specified', () => {
7+
it('should return the correct directives when no directives specified', () => {
88
const typeDefs = `
99
type Query {
1010
test: String
1111
}
1212
`;
1313
const schema = makeExecutableSchema({ typeDefs, resolvers: {} }) as GraphQLSchema;
14-
const directivesMap = getDirectives(schema, schema.getQueryType()!);
14+
const directives = getDirectives(schema, schema.getQueryType()!);
1515

16-
expect(directivesMap).toEqual({});
16+
expect(directives).toEqual([]);
1717
});
1818

19-
it('should return the correct directives map when built-in directive specified over FIELD_DEFINITION', () => {
19+
it('should return the correct directives built-in directive specified over FIELD_DEFINITION', () => {
2020
const typeDefs = `
2121
type Query {
2222
test: String @deprecated
2323
}
2424
`;
2525

2626
const schema = makeExecutableSchema({ typeDefs, resolvers: {} }) as GraphQLSchema;
27-
const directivesMap = getDirectives(schema, schema.getQueryType()!.getFields()['test']);
28-
expect(directivesMap).toEqual({
29-
deprecated: {
27+
const directives = getDirectives(schema, schema.getQueryType()!.getFields()['test']);
28+
expect(directives).toEqual([{
29+
name: 'deprecated',
30+
args: {
3031
reason: 'No longer supported',
3132
},
32-
});
33+
}]);
3334
});
3435

35-
it('should return the correct directives map when using custom directive without arguments', () => {
36+
it('should return the correct directives when using custom directive without arguments', () => {
3637
const typeDefs = `
3738
type Query {
3839
test: String @mydir
@@ -42,13 +43,14 @@ describe('getDirectives', () => {
4243
`;
4344

4445
const schema = makeExecutableSchema({ typeDefs, resolvers: {} }) as GraphQLSchema;
45-
const directivesMap = getDirectives(schema, schema.getQueryType()!.getFields()['test']);
46-
expect(directivesMap).toEqual({
47-
mydir: {},
48-
});
46+
const directives = getDirectives(schema, schema.getQueryType()!.getFields()['test']);
47+
expect(directives).toEqual([{
48+
name: 'mydir',
49+
args: {},
50+
}]);
4951
});
5052

51-
it('should return the correct directives map when using custom directive with optional argument', () => {
53+
it('should return the correct directives when using custom directive with optional argument', () => {
5254
const typeDefs = `
5355
type Query {
5456
test: String @mydir(f1: "test")
@@ -58,15 +60,16 @@ describe('getDirectives', () => {
5860
`;
5961

6062
const schema = makeExecutableSchema({ typeDefs, resolvers: {} }) as GraphQLSchema;
61-
const directivesMap = getDirectives(schema, schema.getQueryType()!.getFields()['test']);
62-
expect(directivesMap).toEqual({
63-
mydir: {
63+
const directives = getDirectives(schema, schema.getQueryType()!.getFields()['test']);
64+
expect(directives).toEqual([{
65+
name: 'mydir',
66+
args: {
6467
f1: 'test',
6568
},
66-
});
69+
}]);
6770
});
6871

69-
it('should return the correct directives map when using custom directive with optional argument an no value', () => {
72+
it('should return the correct directives when using custom directive with optional argument an no value', () => {
7073
const typeDefs = `
7174
type Query {
7275
test: String @mydir
@@ -76,10 +79,11 @@ describe('getDirectives', () => {
7679
`;
7780

7881
const schema = makeExecutableSchema({ typeDefs, resolvers: {} }) as GraphQLSchema;
79-
const directivesMap = getDirectives(schema, schema.getQueryType()!.getFields()['test']);
80-
expect(directivesMap).toEqual({
81-
mydir: {},
82-
});
82+
const directives = getDirectives(schema, schema.getQueryType()!.getFields()['test']);
83+
expect(directives).toEqual([{
84+
name: 'mydir',
85+
args: {},
86+
}]);
8387
});
8488

8589
it('provides the extension definition', () => {
@@ -96,7 +100,7 @@ describe('getDirectives', () => {
96100
});
97101
const QueryType = schema.getQueryType()
98102
assertGraphQLObjectType(QueryType)
99-
expect(getDirectives(schema,QueryType)).toEqual({ mydir: { arg: 'ext1' } });
103+
expect(getDirectives(schema,QueryType)).toEqual([{ name: 'mydir', args: { arg: 'ext1' } }]);
100104
});
101105

102106
it('builds proper repeatable directives listing', () => {
@@ -110,8 +114,12 @@ describe('getDirectives', () => {
110114
});
111115
const QueryType = schema.getQueryType()
112116
assertGraphQLObjectType(QueryType)
113-
expect(getDirectives(schema, QueryType)).toEqual({
114-
mydir: [{ arg: "first" }, { arg: "second" }]
115-
});
117+
expect(getDirectives(schema, QueryType)).toEqual([{
118+
name: 'mydir',
119+
args: { arg: 'first' },
120+
}, {
121+
name: 'mydir',
122+
args: { arg: 'second' },
123+
}]);
116124
});
117125
});

‎packages/utils/tests/schemaTransforms.test.ts

+57-68
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ import {
3131
MapperKind,
3232
getDirectives,
3333
ExecutionResult,
34+
getDirective,
3435
} from '@graphql-tools/utils';
3536

3637
import { addMocksToSchema } from '@graphql-tools/mock';
@@ -122,8 +123,8 @@ describe('@directives', () => {
122123
return schema => mapSchema(schema, {
123124
[MapperKind.OBJECT_TYPE]: type => {
124125
const directives = getDirectives(schema, type);
125-
for (const directiveName in directives) {
126-
if (directiveNames.includes(directiveName)) {
126+
for (const directive of directives) {
127+
if (directiveNames.includes(directive.name)) {
127128
expect(type.name).toBe(schema.getQueryType()?.name);
128129
visited.add(type);
129130
}
@@ -149,8 +150,8 @@ describe('@directives', () => {
149150
function recordSchemaDirectiveUses(directiveNames: Array<string>): (schema: GraphQLSchema) => GraphQLSchema {
150151
return schema => {
151152
const directives = getDirectives(schema, schema);
152-
for (const directiveName in directives) {
153-
if (directiveNames.includes(directiveName)) {
153+
for (const directive of directives) {
154+
if (directiveNames.includes(directive.name)) {
154155
visited.push(schema);
155156
}
156157
}
@@ -179,8 +180,8 @@ describe('@directives', () => {
179180
upperDirectiveTypeDefs: `directive @${directiveName} on FIELD_DEFINITION`,
180181
upperDirectiveTransformer: (schema: GraphQLSchema) => mapSchema(schema, {
181182
[MapperKind.OBJECT_FIELD]: (fieldConfig) => {
182-
const directives = getDirectives(schema, fieldConfig);
183-
if (directives[directiveName]) {
183+
const upperDirective = getDirective(schema, fieldConfig, directiveName)?.[0];
184+
if (upperDirective) {
184185
const { resolve = defaultFieldResolver } = fieldConfig;
185186
fieldConfig.resolve = async function (source, args, context, info) {
186187
const result = await resolve(source, args, context, info);
@@ -234,18 +235,16 @@ describe('@directives', () => {
234235
deprecatedDirectiveTypeDefs: `directive @${directiveName}(reason: String) on FIELD_DEFINITION | ENUM_VALUE`,
235236
deprecatedDirectiveTransformer: (schema: GraphQLSchema) => mapSchema(schema, {
236237
[MapperKind.OBJECT_FIELD]: (fieldConfig) => {
237-
const directives = getDirectives(schema, fieldConfig);
238-
const directiveArgumentMap = directives[directiveName];
239-
if (directiveArgumentMap) {
240-
fieldConfig.deprecationReason = directiveArgumentMap.reason;
238+
const deprecatedDirective = getDirective(schema, fieldConfig, directiveName)?.[0];
239+
if (deprecatedDirective) {
240+
fieldConfig.deprecationReason = deprecatedDirective['reason'];
241241
return fieldConfig;
242242
}
243243
},
244244
[MapperKind.ENUM_VALUE]: (enumValueConfig) => {
245-
const directives = getDirectives(schema, enumValueConfig);
246-
const directiveArgumentMap = directives[directiveName];
247-
if (directiveArgumentMap) {
248-
enumValueConfig.deprecationReason = directiveArgumentMap.reason;
245+
const deprecatedDirective = getDirective(schema, enumValueConfig, directiveName)?.[0];
246+
if (deprecatedDirective) {
247+
enumValueConfig.deprecationReason = deprecatedDirective['reason'];
249248
return enumValueConfig;
250249
}
251250
}
@@ -278,16 +277,14 @@ describe('@directives', () => {
278277
dateDirectiveTypeDefs: `directive @${directiveName}(format: String) on FIELD_DEFINITION`,
279278
dateDirectiveTransformer: (schema: GraphQLSchema) => mapSchema(schema, {
280279
[MapperKind.OBJECT_FIELD]: (fieldConfig) => {
281-
const directives = getDirectives(schema, fieldConfig);
282-
const directiveArgumentMap = directives[directiveName];
283-
if (directiveArgumentMap) {
280+
const dateDirective = getDirective(schema, fieldConfig, directiveName)?.[0];
281+
if (dateDirective) {
284282
const { resolve = defaultFieldResolver } = fieldConfig;
285-
const { format } = directiveArgumentMap;
286-
fieldConfig.resolve = async function (source, args, context, info) {
283+
const { format } = dateDirective;
284+
fieldConfig.resolve = async (source, args, context, info) => {
287285
const date = await resolve(source, args, context, info);
288286
return formatDate(date, format, true);
289-
290-
}
287+
};
291288
return fieldConfig;
292289
}
293290
}
@@ -338,11 +335,10 @@ describe('@directives', () => {
338335
`,
339336
formattableDateDirectiveTransformer: (schema: GraphQLSchema) => mapSchema(schema, {
340337
[MapperKind.OBJECT_FIELD]: (fieldConfig) => {
341-
const directives = getDirectives(schema, fieldConfig);
342-
const directiveArgumentMap = directives[directiveName];
343-
if (directiveArgumentMap) {
338+
const dateDirective = getDirective(schema, fieldConfig, directiveName)?.[0];
339+
if (dateDirective) {
344340
const { resolve = defaultFieldResolver } = fieldConfig;
345-
const { defaultFormat } = directiveArgumentMap;
341+
const { defaultFormat } = dateDirective;
346342

347343
if (!fieldConfig.args) {
348344
throw new Error("Unexpected Error. args should be defined.")
@@ -353,12 +349,12 @@ describe('@directives', () => {
353349
};
354350

355351
fieldConfig.type = GraphQLString;
356-
fieldConfig.resolve = async function (
352+
fieldConfig.resolve = async (
357353
source,
358354
{ format, ...args },
359355
context,
360356
info,
361-
) {
357+
) => {
362358
const newFormat = format || defaultFormat;
363359
const date = await resolve(source, args, context, info);
364360
return formatDate(date, newFormat, true);
@@ -430,15 +426,16 @@ describe('@directives', () => {
430426
}`,
431427
authDirectiveTransformer: (schema: GraphQLSchema) => mapSchema(schema, {
432428
[MapperKind.TYPE]: (type) => {
433-
const typeDirectives = getDirectives(schema, type);
434-
typeDirectiveArgumentMaps[type.name] = typeDirectives[directiveName];
429+
const authDirective = getDirective(schema, type, directiveName)?.[0];
430+
if (authDirective) {
431+
typeDirectiveArgumentMaps[type.name] = authDirective;
432+
}
435433
return undefined;
436434
},
437435
[MapperKind.OBJECT_FIELD]: (fieldConfig, _fieldName, typeName) => {
438-
const fieldDirectives = getDirectives(schema, fieldConfig);
439-
const directiveArgumentMap = fieldDirectives[directiveName] ?? typeDirectiveArgumentMaps[typeName];
440-
if (directiveArgumentMap) {
441-
const { requires } = directiveArgumentMap;
436+
const authDirective = getDirective(schema, fieldConfig, directiveName)?.[0] ?? typeDirectiveArgumentMaps[typeName];
437+
if (authDirective) {
438+
const { requires } = authDirective;
442439
if (requires) {
443440
const { resolve = defaultFieldResolver } = fieldConfig;
444441
fieldConfig.resolve = function (source, args, context, info) {
@@ -626,10 +623,9 @@ describe('@directives', () => {
626623
lengthDirectiveTypeDefs: `directive @${directiveName}(max: Int) on FIELD_DEFINITION | INPUT_FIELD_DEFINITION`,
627624
lengthDirectiveTransformer: (schema: GraphQLSchema) => mapSchema(schema, {
628625
[MapperKind.FIELD]: (fieldConfig) => {
629-
const directives = getDirectives(schema, fieldConfig);
630-
const directiveArgumentMap = directives[directiveName];
631-
if (directiveArgumentMap) {
632-
wrapType(fieldConfig, directiveArgumentMap);
626+
const lengthDirective = getDirective(schema, fieldConfig, directiveName)?.[0];
627+
if (lengthDirective) {
628+
wrapType(fieldConfig, lengthDirective);
633629
return fieldConfig;
634630
}
635631
}
@@ -717,10 +713,9 @@ describe('@directives', () => {
717713
uniqueIDDirectiveTypeDefs: `directive @${directiveName}(name: String, from: [String]) on OBJECT`,
718714
uniqueIDDirectiveTransformer: (schema: GraphQLSchema) => mapSchema(schema, {
719715
[MapperKind.OBJECT_TYPE]: (type) => {
720-
const directives = getDirectives(schema, type);
721-
const directiveArgumentMap = directives[directiveName];
722-
if (directiveArgumentMap) {
723-
const { name, from } = directiveArgumentMap;
716+
const uniqueIDDirective = getDirective(schema, type, directiveName)?.[0];
717+
if (uniqueIDDirective) {
718+
const { name, from } = uniqueIDDirective;
724719
const config = type.toConfig();
725720
config.fields[name] = {
726721
type: GraphQLID,
@@ -827,9 +822,8 @@ describe('@directives', () => {
827822
function renameObjectTypeToHumanDirective(directiveName: string): (schema: GraphQLSchema) => GraphQLSchema {
828823
return schema => mapSchema(schema, {
829824
[MapperKind.OBJECT_TYPE]: (type) => {
830-
const directives = getDirectives(schema, type);
831-
const directiveArgumentMap = directives[directiveName];
832-
if (directiveArgumentMap) {
825+
const directive = getDirective(schema, type, directiveName)?.[0];
826+
if (directive) {
833827
const config = type.toConfig();
834828
config.name = 'Human';
835829
return new GraphQLObjectType(config);
@@ -875,9 +869,8 @@ describe('@directives', () => {
875869
function removeEnumValueDirective(directiveName: string): (schema: GraphQLSchema) => GraphQLSchema {
876870
return schema => mapSchema(schema, {
877871
[MapperKind.ENUM_VALUE]: (enumValueConfig) => {
878-
const directives = getDirectives(schema, enumValueConfig);
879-
const directiveArgumentMap = directives[directiveName];
880-
if (directiveArgumentMap && directiveArgumentMap.if) {
872+
const directive = getDirective(schema, enumValueConfig, directiveName)?.[0];
873+
if (directive?.['if']) {
881874
return null;
882875
}
883876
}
@@ -912,10 +905,9 @@ describe('@directives', () => {
912905
function modifyExternalEnumValueDirective(directiveName: string): (schema: GraphQLSchema) => GraphQLSchema {
913906
return schema => mapSchema(schema, {
914907
[MapperKind.ENUM_VALUE]: (enumValueConfig) => {
915-
const directives = getDirectives(schema, enumValueConfig);
916-
const directiveArgumentMap = directives[directiveName];
917-
if (directiveArgumentMap) {
918-
return [directiveArgumentMap.new, enumValueConfig];
908+
const directive = getDirective(schema, enumValueConfig, directiveName)?.[0];
909+
if (directive) {
910+
return [directive['new'], enumValueConfig];
919911
}
920912
}
921913
});
@@ -950,10 +942,9 @@ describe('@directives', () => {
950942
function modifyInternalEnumValueDirective(directiveName: string): (schema: GraphQLSchema) => GraphQLSchema {
951943
return schema => mapSchema(schema, {
952944
[MapperKind.ENUM_VALUE]: (enumValueConfig) => {
953-
const directives = getDirectives(schema, enumValueConfig);
954-
const directiveArgumentMap = directives[directiveName];
955-
if (directiveArgumentMap) {
956-
enumValueConfig.value = directiveArgumentMap.new;
945+
const directive = getDirective(schema, enumValueConfig, directiveName)?.[0];
946+
if (directive) {
947+
enumValueConfig.value = directive['new'];
957948
return enumValueConfig;
958949
}
959950
}
@@ -989,11 +980,10 @@ describe('@directives', () => {
989980
function renameObjectTypeDirective(directiveName: string): (schema: GraphQLSchema) => GraphQLSchema {
990981
return schema => mapSchema(schema, {
991982
[MapperKind.OBJECT_TYPE]: (type) => {
992-
const directives = getDirectives(schema, type);
993-
const directiveArgumentMap = directives[directiveName];
994-
if (directiveArgumentMap) {
983+
const directive = getDirective(schema, type, directiveName)?.[0];
984+
if (directive) {
995985
const config = type.toConfig();
996-
config.name = directiveArgumentMap.to;
986+
config.name = directive['to'];
997987
return new GraphQLObjectType(config);
998988
}
999989
}
@@ -1042,9 +1032,8 @@ describe('@directives', () => {
10421032
function addObjectTypeToSetDirective(directiveName: string): (schema: GraphQLSchema) => GraphQLSchema {
10431033
return schema => mapSchema(schema, {
10441034
[MapperKind.OBJECT_TYPE]: type => {
1045-
const directives = getDirectives(schema, type);
1046-
const directiveArgumentMap = directives[directiveName];
1047-
if (directiveArgumentMap) {
1035+
const directive = getDirective(schema, type, directiveName)?.[0];
1036+
if (directive) {
10481037
expect(type.name).toBe(schema.getQueryType()?.name);
10491038
visited.add(type);
10501039
}
@@ -1072,8 +1061,8 @@ describe('@directives', () => {
10721061
function upperDirective(directiveName: string): (schema: GraphQLSchema) => GraphQLSchema {
10731062
return schema => mapSchema(schema, {
10741063
[MapperKind.OBJECT_FIELD]: (fieldConfig) => {
1075-
const directives = getDirectives(schema, fieldConfig);
1076-
if (directives[directiveName]) {
1064+
const upperDirective = getDirective(schema, fieldConfig, directiveName)?.[0];
1065+
if (upperDirective) {
10771066
const { resolve = defaultFieldResolver } = fieldConfig;
10781067
fieldConfig.resolve = async function (source, args, context, info) {
10791068
const result = await resolve(source, args, context, info);
@@ -1091,8 +1080,8 @@ describe('@directives', () => {
10911080
function reverseDirective(directiveName: string): (schema: GraphQLSchema) => GraphQLSchema {
10921081
return schema => mapSchema(schema, {
10931082
[MapperKind.OBJECT_FIELD]: (fieldConfig) => {
1094-
const directives = getDirectives(schema, fieldConfig);
1095-
if (directives[directiveName]) {
1083+
const reverseDirective = getDirective(schema, fieldConfig, directiveName)?.[0];
1084+
if (reverseDirective) {
10961085
const { resolve = defaultFieldResolver } = fieldConfig;
10971086
fieldConfig.resolve = async function (source, args, context, info) {
10981087
const result = await resolve(source, args, context, info);
@@ -1145,10 +1134,10 @@ describe('@directives', () => {
11451134
const listWrapperTypes = new Map();
11461135
return mapSchema(schema, {
11471136
[MapperKind.COMPOSITE_FIELD]: (fieldConfig, fieldName) => {
1148-
const hasDirectiveAnnotation = !!getDirectives(schema, fieldConfig)['addListWrapper'];
1137+
const directive = getDirective(schema, fieldConfig, 'addListWrapper')?.[0];
11491138

11501139
// Leave the field untouched if it does not have the directive annotation
1151-
if (!hasDirectiveAnnotation) {
1140+
if (!directive) {
11521141
return undefined;
11531142
}
11541143

‎packages/wrap/src/transforms/RemoveObjectFieldsWithDirective.ts

+4-7
Original file line numberDiff line numberDiff line change
@@ -21,13 +21,10 @@ export default class RemoveObjectFieldsWithDirective implements Transform {
2121
transformedSchema?: GraphQLSchema
2222
): GraphQLSchema {
2323
const transformer = new FilterObjectFields((_typeName, _fieldName, fieldConfig) => {
24-
const valueMap = getDirectives(originalWrappingSchema, fieldConfig);
25-
return !Object.keys(valueMap).some(
26-
directiveName =>
27-
valueMatchesCriteria(directiveName, this.directiveName) &&
28-
((Array.isArray(valueMap[directiveName]) &&
29-
valueMap[directiveName].some((value: any) => valueMatchesCriteria(value, this.args))) ||
30-
valueMatchesCriteria(valueMap[directiveName], this.args))
24+
const directives = getDirectives(originalWrappingSchema, fieldConfig);
25+
return !directives.some(
26+
directive =>
27+
valueMatchesCriteria(directive.name, this.directiveName) && valueMatchesCriteria(directive.args, this.args)
3128
);
3229
});
3330

‎website/docs/schema-directives.md

+120-125
Large diffs are not rendered by default.

0 commit comments

Comments
 (0)
Please sign in to comment.