1 |
|
2 |
|
3 | import {
|
4 | FieldNode,
|
5 | SelectionNode,
|
6 | VariableNode,
|
7 | BooleanValueNode,
|
8 | DirectiveNode,
|
9 | DocumentNode,
|
10 | ArgumentNode,
|
11 | ValueNode,
|
12 | } from 'graphql';
|
13 |
|
14 | import { visit } from 'graphql/language/visitor';
|
15 |
|
16 | import { invariant } from 'ts-invariant';
|
17 |
|
18 | import { argumentsObjectFromField } from './storeUtils';
|
19 |
|
20 | export type DirectiveInfo = {
|
21 | [fieldName: string]: { [argName: string]: any };
|
22 | };
|
23 |
|
24 | export 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 |
|
41 | export 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 |
|
62 | export 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 |
|
74 | export function hasDirectives(names: string[], doc: DocumentNode) {
|
75 | return getDirectiveNames(doc).some(
|
76 | (name: string) => names.indexOf(name) > -1,
|
77 | );
|
78 | }
|
79 |
|
80 | export function hasClientExports(document: DocumentNode) {
|
81 | return (
|
82 | document &&
|
83 | hasDirectives(['client'], document) &&
|
84 | hasDirectives(['export'], document)
|
85 | );
|
86 | }
|
87 |
|
88 | export type InclusionDirectives = Array<{
|
89 | directive: DirectiveNode;
|
90 | ifArgument: ArgumentNode;
|
91 | }>;
|
92 |
|
93 | function isInclusionDirective({ name: { value } }: DirectiveNode): boolean {
|
94 | return value === 'skip' || value === 'include';
|
95 | }
|
96 |
|
97 | export 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 |
|
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 |
|