All checks were successful
Publish To Prod / deploy_and_publish (push) Successful in 35s
128 lines
3.3 KiB
TypeScript
128 lines
3.3 KiB
TypeScript
// Provides the methods that allow QueryManager to handle the `skip` and
|
|
// `include` directives within GraphQL.
|
|
import {
|
|
FieldNode,
|
|
SelectionNode,
|
|
VariableNode,
|
|
BooleanValueNode,
|
|
DirectiveNode,
|
|
DocumentNode,
|
|
ArgumentNode,
|
|
ValueNode,
|
|
} from 'graphql';
|
|
|
|
import { visit } from 'graphql/language/visitor';
|
|
|
|
import { invariant } from 'ts-invariant';
|
|
|
|
import { argumentsObjectFromField } from './storeUtils';
|
|
|
|
export type DirectiveInfo = {
|
|
[fieldName: string]: { [argName: string]: any };
|
|
};
|
|
|
|
export function getDirectiveInfoFromField(
|
|
field: FieldNode,
|
|
variables: Object,
|
|
): DirectiveInfo {
|
|
if (field.directives && field.directives.length) {
|
|
const directiveObj: DirectiveInfo = {};
|
|
field.directives.forEach((directive: DirectiveNode) => {
|
|
directiveObj[directive.name.value] = argumentsObjectFromField(
|
|
directive,
|
|
variables,
|
|
);
|
|
});
|
|
return directiveObj;
|
|
}
|
|
return null;
|
|
}
|
|
|
|
export function shouldInclude(
|
|
selection: SelectionNode,
|
|
variables: { [name: string]: any } = {},
|
|
): boolean {
|
|
return getInclusionDirectives(
|
|
selection.directives,
|
|
).every(({ directive, ifArgument }) => {
|
|
let evaledValue: boolean = false;
|
|
if (ifArgument.value.kind === 'Variable') {
|
|
evaledValue = variables[(ifArgument.value as VariableNode).name.value];
|
|
invariant(
|
|
evaledValue !== void 0,
|
|
`Invalid variable referenced in @${directive.name.value} directive.`,
|
|
);
|
|
} else {
|
|
evaledValue = (ifArgument.value as BooleanValueNode).value;
|
|
}
|
|
return directive.name.value === 'skip' ? !evaledValue : evaledValue;
|
|
});
|
|
}
|
|
|
|
export function getDirectiveNames(doc: DocumentNode) {
|
|
const names: string[] = [];
|
|
|
|
visit(doc, {
|
|
Directive(node) {
|
|
names.push(node.name.value);
|
|
},
|
|
});
|
|
|
|
return names;
|
|
}
|
|
|
|
export function hasDirectives(names: string[], doc: DocumentNode) {
|
|
return getDirectiveNames(doc).some(
|
|
(name: string) => names.indexOf(name) > -1,
|
|
);
|
|
}
|
|
|
|
export function hasClientExports(document: DocumentNode) {
|
|
return (
|
|
document &&
|
|
hasDirectives(['client'], document) &&
|
|
hasDirectives(['export'], document)
|
|
);
|
|
}
|
|
|
|
export type InclusionDirectives = Array<{
|
|
directive: DirectiveNode;
|
|
ifArgument: ArgumentNode;
|
|
}>;
|
|
|
|
function isInclusionDirective({ name: { value } }: DirectiveNode): boolean {
|
|
return value === 'skip' || value === 'include';
|
|
}
|
|
|
|
export function getInclusionDirectives(
|
|
directives: ReadonlyArray<DirectiveNode>,
|
|
): InclusionDirectives {
|
|
return directives ? directives.filter(isInclusionDirective).map(directive => {
|
|
const directiveArguments = directive.arguments;
|
|
const directiveName = directive.name.value;
|
|
|
|
invariant(
|
|
directiveArguments && directiveArguments.length === 1,
|
|
`Incorrect number of arguments for the @${directiveName} directive.`,
|
|
);
|
|
|
|
const ifArgument = directiveArguments[0];
|
|
invariant(
|
|
ifArgument.name && ifArgument.name.value === 'if',
|
|
`Invalid argument for the @${directiveName} directive.`,
|
|
);
|
|
|
|
const ifValue: ValueNode = ifArgument.value;
|
|
|
|
// means it has to be a variable value if this is a valid @skip or @include directive
|
|
invariant(
|
|
ifValue &&
|
|
(ifValue.kind === 'Variable' || ifValue.kind === 'BooleanValue'),
|
|
`Argument for the @${directiveName} directive must be a variable or a boolean value.`,
|
|
);
|
|
|
|
return { directive, ifArgument };
|
|
}) : [];
|
|
}
|
|
|