-
Notifications
You must be signed in to change notification settings - Fork 2.7k
/
Copy pathfragments.ts
164 lines (150 loc) · 4.69 KB
/
fragments.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
import { invariant, newInvariantError } from "../globals/index.js";
import { BREAK, visit } from "graphql";
import type {
DocumentNode,
FragmentDefinitionNode,
InlineFragmentNode,
SelectionNode,
} from "graphql";
// TODO(brian): A hack until this issue is resolved (/~https://github.com/graphql/graphql-js/issues/3356)
type Kind = any;
type OperationTypeNode = any;
/**
* Returns a query document which adds a single query operation that only
* spreads the target fragment inside of it.
*
* So for example a document of:
*
* ```graphql
* fragment foo on Foo { a b c }
* ```
*
* Turns into:
*
* ```graphql
* { ...foo }
*
* fragment foo on Foo { a b c }
* ```
*
* The target fragment will either be the only fragment in the document, or a
* fragment specified by the provided `fragmentName`. If there is more than one
* fragment, but a `fragmentName` was not defined then an error will be thrown.
*/
export function getFragmentQueryDocument(
document: DocumentNode,
fragmentName?: string
): DocumentNode {
let actualFragmentName = fragmentName;
// Build an array of all our fragment definitions that will be used for
// validations. We also do some validations on the other definitions in the
// document while building this list.
const fragments: Array<FragmentDefinitionNode> = [];
document.definitions.forEach((definition) => {
// Throw an error if we encounter an operation definition because we will
// define our own operation definition later on.
if (definition.kind === "OperationDefinition") {
throw newInvariantError(
`Found a %s operation%s. ` +
"No operations are allowed when using a fragment as a query. Only fragments are allowed.",
definition.operation,
definition.name ? ` named '${definition.name.value}'` : ""
);
}
// Add our definition to the fragments array if it is a fragment
// definition.
if (definition.kind === "FragmentDefinition") {
fragments.push(definition);
}
});
// If the user did not give us a fragment name then let us try to get a
// name from a single fragment in the definition.
if (typeof actualFragmentName === "undefined") {
invariant(
fragments.length === 1,
`Found %s fragments. \`fragmentName\` must be provided when there is not exactly 1 fragment.`,
fragments.length
);
actualFragmentName = fragments[0].name.value;
}
// Generate a query document with an operation that simply spreads the
// fragment inside of it.
const query: DocumentNode = {
...document,
definitions: [
{
kind: "OperationDefinition" as Kind,
// OperationTypeNode is an enum
operation: "query" as OperationTypeNode,
selectionSet: {
kind: "SelectionSet" as Kind,
selections: [
{
kind: "FragmentSpread" as Kind,
name: {
kind: "Name" as Kind,
value: actualFragmentName,
},
},
],
},
},
...document.definitions,
],
};
return query;
}
/**
* This is an interface that describes a map from fragment names to fragment definitions.
*/
export interface FragmentMap {
[fragmentName: string]: FragmentDefinitionNode;
}
export type FragmentMapFunction = (
fragmentName: string
) => FragmentDefinitionNode | null;
// Utility function that takes a list of fragment definitions and makes a hash out of them
// that maps the name of the fragment to the fragment definition.
export function createFragmentMap(
fragments: FragmentDefinitionNode[] = []
): FragmentMap {
const symTable: FragmentMap = {};
fragments.forEach((fragment) => {
symTable[fragment.name.value] = fragment;
});
return symTable;
}
export function getFragmentFromSelection(
selection: SelectionNode,
fragmentMap?: FragmentMap | FragmentMapFunction
): InlineFragmentNode | FragmentDefinitionNode | null {
switch (selection.kind) {
case "InlineFragment":
return selection;
case "FragmentSpread": {
const fragmentName = selection.name.value;
if (typeof fragmentMap === "function") {
return fragmentMap(fragmentName);
}
const fragment = fragmentMap && fragmentMap[fragmentName];
invariant(fragment, `No fragment named %s`, fragmentName);
return fragment || null;
}
default:
return null;
}
}
export function isFullyUnmaskedOperation(document: DocumentNode) {
let isUnmasked = true;
visit(document, {
FragmentSpread: (node) => {
isUnmasked =
!!node.directives &&
node.directives.some((directive) => directive.name.value === "unmask");
if (!isUnmasked) {
return BREAK;
}
},
});
return isUnmasked;
}