UNPKG

3.42 kBPlain TextView Raw
1// Provides the methods that allow QueryManager to handle the `skip` and
2// `include` directives within GraphQL.
3import {
4 FieldNode,
5 SelectionNode,
6 VariableNode,
7 BooleanValueNode,
8 DirectiveNode,
9 DocumentNode,
10 ArgumentNode,
11 ValueNode,
12} from 'graphql';
13
14import { visit } from 'graphql/language/visitor';
15
16import { invariant } from 'ts-invariant';
17
18import { argumentsObjectFromField } from './storeUtils';
19
20export type DirectiveInfo = {
21 [fieldName: string]: { [argName: string]: any };
22};
23
24export function getDirectiveInfoFromField(
25 field: FieldNode,
26 variables: Object,
27): DirectiveInfo {
28 if (field.directives && field.directives.length) {
29 const directiveObj: DirectiveInfo = {};
30 field.directives.forEach((directive: DirectiveNode) => {
31 directiveObj[directive.name.value] = argumentsObjectFromField(
32 directive,
33 variables,
34 );
35 });
36 return directiveObj;
37 }
38 return null;
39}
40
41export function shouldInclude(
42 selection: SelectionNode,
43 variables: { [name: string]: any } = {},
44): boolean {
45 return getInclusionDirectives(
46 selection.directives,
47 ).every(({ directive, ifArgument }) => {
48 let evaledValue: boolean = false;
49 if (ifArgument.value.kind === 'Variable') {
50 evaledValue = variables[(ifArgument.value as VariableNode).name.value];
51 invariant(
52 evaledValue !== void 0,
53 `Invalid variable referenced in @${directive.name.value} directive.`,
54 );
55 } else {
56 evaledValue = (ifArgument.value as BooleanValueNode).value;
57 }
58 return directive.name.value === 'skip' ? !evaledValue : evaledValue;
59 });
60}
61
62export function getDirectiveNames(doc: DocumentNode) {
63 const names: string[] = [];
64
65 visit(doc, {
66 Directive(node) {
67 names.push(node.name.value);
68 },
69 });
70
71 return names;
72}
73
74export function hasDirectives(names: string[], doc: DocumentNode) {
75 return getDirectiveNames(doc).some(
76 (name: string) => names.indexOf(name) > -1,
77 );
78}
79
80export function hasClientExports(document: DocumentNode) {
81 return (
82 document &&
83 hasDirectives(['client'], document) &&
84 hasDirectives(['export'], document)
85 );
86}
87
88export type InclusionDirectives = Array<{
89 directive: DirectiveNode;
90 ifArgument: ArgumentNode;
91}>;
92
93function isInclusionDirective({ name: { value } }: DirectiveNode): boolean {
94 return value === 'skip' || value === 'include';
95}
96
97export function getInclusionDirectives(
98 directives: ReadonlyArray<DirectiveNode>,
99): InclusionDirectives {
100 return directives ? directives.filter(isInclusionDirective).map(directive => {
101 const directiveArguments = directive.arguments;
102 const directiveName = directive.name.value;
103
104 invariant(
105 directiveArguments && directiveArguments.length === 1,
106 `Incorrect number of arguments for the @${directiveName} directive.`,
107 );
108
109 const ifArgument = directiveArguments[0];
110 invariant(
111 ifArgument.name && ifArgument.name.value === 'if',
112 `Invalid argument for the @${directiveName} directive.`,
113 );
114
115 const ifValue: ValueNode = ifArgument.value;
116
117 // means it has to be a variable value if this is a valid @skip or @include directive
118 invariant(
119 ifValue &&
120 (ifValue.kind === 'Variable' || ifValue.kind === 'BooleanValue'),
121 `Argument for the @${directiveName} directive must be a variable or a boolean value.`,
122 );
123
124 return { directive, ifArgument };
125 }) : [];
126}
127