graphql-jit 0.8.4 → 0.8.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -1
- package/dist/{ast.d.ts → cjs/ast.d.ts} +1 -1
- package/dist/cjs/ast.js.map +1 -0
- package/dist/{compat.js → cjs/compat.js} +5 -1
- package/dist/cjs/compat.js.map +1 -0
- package/dist/cjs/error.js.map +1 -0
- package/dist/{execution.js → cjs/execution.js} +1 -1
- package/dist/cjs/execution.js.map +1 -0
- package/dist/cjs/index.js.map +1 -0
- package/dist/cjs/inspect.js.map +1 -0
- package/dist/cjs/json.js.map +1 -0
- package/dist/{memoize.d.ts → cjs/memoize.d.ts} +1 -1
- package/dist/cjs/memoize.js.map +1 -0
- package/dist/{non-null.d.ts → cjs/non-null.d.ts} +1 -1
- package/dist/{non-null.js → cjs/non-null.js} +4 -0
- package/dist/cjs/non-null.js.map +1 -0
- package/dist/{resolve-info.d.ts → cjs/resolve-info.d.ts} +1 -1
- package/dist/cjs/resolve-info.js.map +1 -0
- package/dist/cjs/types.d.ts +1 -0
- package/dist/cjs/types.js.map +1 -0
- package/dist/{variables.d.ts → cjs/variables.d.ts} +1 -1
- package/dist/cjs/variables.js.map +1 -0
- package/dist/esm/ast.d.ts +89 -0
- package/dist/esm/ast.js +704 -0
- package/dist/esm/ast.js.map +1 -0
- package/dist/esm/compat.d.ts +43 -0
- package/dist/esm/compat.js +101 -0
- package/dist/esm/compat.js.map +1 -0
- package/dist/esm/error.d.ts +7 -0
- package/dist/esm/error.js +59 -0
- package/dist/esm/error.js.map +1 -0
- package/dist/esm/execution.d.ts +148 -0
- package/dist/esm/execution.js +1200 -0
- package/dist/esm/execution.js.map +1 -0
- package/dist/esm/index.d.ts +2 -0
- package/dist/esm/index.js +10 -0
- package/dist/esm/index.js.map +1 -0
- package/dist/esm/inspect.d.ts +5 -0
- package/dist/esm/inspect.js +110 -0
- package/dist/esm/inspect.js.map +1 -0
- package/dist/esm/json.d.ts +9 -0
- package/dist/esm/json.js +145 -0
- package/dist/esm/json.js.map +1 -0
- package/dist/esm/memoize.d.ts +5 -0
- package/dist/esm/memoize.js +29 -0
- package/dist/esm/memoize.js.map +1 -0
- package/dist/esm/non-null.d.ts +9 -0
- package/dist/esm/non-null.js +207 -0
- package/dist/esm/non-null.js.map +1 -0
- package/dist/esm/resolve-info.d.ts +40 -0
- package/dist/esm/resolve-info.js +233 -0
- package/dist/esm/resolve-info.js.map +1 -0
- package/dist/esm/types.d.ts +1 -0
- package/dist/esm/types.js +3 -0
- package/dist/esm/types.js.map +1 -0
- package/dist/esm/variables.d.ts +15 -0
- package/dist/esm/variables.js +348 -0
- package/dist/esm/variables.js.map +1 -0
- package/package.json +61 -37
- package/dist/ast.js.map +0 -1
- package/dist/compat.js.map +0 -1
- package/dist/error.js.map +0 -1
- package/dist/execution.js.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/inspect.js.map +0 -1
- package/dist/json.js.map +0 -1
- package/dist/memoize.js.map +0 -1
- package/dist/non-null.js.map +0 -1
- package/dist/resolve-info.js.map +0 -1
- package/dist/types.d.ts +0 -1
- package/dist/types.js.map +0 -1
- package/dist/variables.js.map +0 -1
- /package/dist/{ast.js → cjs/ast.js} +0 -0
- /package/dist/{compat.d.ts → cjs/compat.d.ts} +0 -0
- /package/dist/{error.d.ts → cjs/error.d.ts} +0 -0
- /package/dist/{error.js → cjs/error.js} +0 -0
- /package/dist/{execution.d.ts → cjs/execution.d.ts} +0 -0
- /package/dist/{index.d.ts → cjs/index.d.ts} +0 -0
- /package/dist/{index.js → cjs/index.js} +0 -0
- /package/dist/{inspect.d.ts → cjs/inspect.d.ts} +0 -0
- /package/dist/{inspect.js → cjs/inspect.js} +0 -0
- /package/dist/{json.d.ts → cjs/json.d.ts} +0 -0
- /package/dist/{json.js → cjs/json.js} +0 -0
- /package/dist/{memoize.js → cjs/memoize.js} +0 -0
- /package/dist/{resolve-info.js → cjs/resolve-info.js} +0 -0
- /package/dist/{types.js → cjs/types.js} +0 -0
- /package/dist/{variables.js → cjs/variables.js} +0 -0
package/dist/esm/ast.js
ADDED
|
@@ -0,0 +1,704 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.joinSkipIncludePath = exports.serializeObjectPathForSkipInclude = exports.flattenPath = exports.addPath = exports.computeLocations = exports.valueFromAST = exports.getArgumentDefs = exports.collectSubfields = exports.resolveFieldDef = exports.collectFields = void 0;
|
|
7
|
+
const generate_function_1 = __importDefault(require("generate-function"));
|
|
8
|
+
const graphql_1 = require("graphql");
|
|
9
|
+
const language_1 = require("graphql/language");
|
|
10
|
+
const type_1 = require("graphql/type");
|
|
11
|
+
const execution_1 = require("./execution");
|
|
12
|
+
const inspect_1 = __importDefault(require("./inspect"));
|
|
13
|
+
const compat_1 = require("./compat");
|
|
14
|
+
Object.defineProperty(exports, "resolveFieldDef", { enumerable: true, get: function () { return compat_1.resolveFieldDef; } });
|
|
15
|
+
const inspect = (0, inspect_1.default)();
|
|
16
|
+
/**
|
|
17
|
+
* Given a selectionSet, adds all of the fields in that selection to
|
|
18
|
+
* the passed in map of fields, and returns it at the end.
|
|
19
|
+
*
|
|
20
|
+
* CollectFields requires the "runtime type" of an object. For a field which
|
|
21
|
+
* returns an Interface or Union type, the "runtime type" will be the actual
|
|
22
|
+
* Object type returned by that field.
|
|
23
|
+
*/
|
|
24
|
+
function collectFields(compilationContext, runtimeType, selectionSet, fields, visitedFragmentNames, parentResponsePath) {
|
|
25
|
+
return collectFieldsImpl(compilationContext, runtimeType, selectionSet, fields, visitedFragmentNames, undefined, serializeObjectPathForSkipInclude(parentResponsePath));
|
|
26
|
+
}
|
|
27
|
+
exports.collectFields = collectFields;
|
|
28
|
+
/**
|
|
29
|
+
* Implementation of collectFields defined above with extra parameters
|
|
30
|
+
* used for recursion and need not be exposed publically
|
|
31
|
+
*/
|
|
32
|
+
function collectFieldsImpl(compilationContext, runtimeType, selectionSet, fields, visitedFragmentNames, previousShouldInclude = "", parentResponsePath = "") {
|
|
33
|
+
for (const selection of selectionSet.selections) {
|
|
34
|
+
switch (selection.kind) {
|
|
35
|
+
case language_1.Kind.FIELD: {
|
|
36
|
+
const name = getFieldEntryKey(selection);
|
|
37
|
+
if (!fields[name]) {
|
|
38
|
+
fields[name] = [];
|
|
39
|
+
}
|
|
40
|
+
const fieldNode = selection;
|
|
41
|
+
// the current path of the field
|
|
42
|
+
// This is used to generate per path skip/include code
|
|
43
|
+
// because the same field can be reached from different paths (e.g. fragment reuse)
|
|
44
|
+
const currentPath = joinSkipIncludePath(parentResponsePath,
|
|
45
|
+
// use alias(instead of selection.name.value) if available as the responsePath used for lookup uses alias
|
|
46
|
+
name);
|
|
47
|
+
// `should include`s generated for the current fieldNode
|
|
48
|
+
const compiledSkipInclude = compileSkipInclude(compilationContext, selection);
|
|
49
|
+
/**
|
|
50
|
+
* Carry over fragment's skip and include code
|
|
51
|
+
*
|
|
52
|
+
* fieldNode.__internalShouldInclude
|
|
53
|
+
* ---------------------------------
|
|
54
|
+
* When the parent field has a skip or include, the current one
|
|
55
|
+
* should be skipped if the parent is skipped in the path.
|
|
56
|
+
*
|
|
57
|
+
* previousShouldInclude
|
|
58
|
+
* ---------------------
|
|
59
|
+
* `should include`s from fragment spread and inline fragments
|
|
60
|
+
*
|
|
61
|
+
* compileSkipInclude(selection)
|
|
62
|
+
* -----------------------------
|
|
63
|
+
* `should include`s generated for the current fieldNode
|
|
64
|
+
*/
|
|
65
|
+
if (compilationContext.options.useExperimentalPathBasedSkipInclude) {
|
|
66
|
+
if (!fieldNode.__internalShouldIncludePath)
|
|
67
|
+
fieldNode.__internalShouldIncludePath = {};
|
|
68
|
+
fieldNode.__internalShouldIncludePath[currentPath] =
|
|
69
|
+
joinShouldIncludeCompilations(fieldNode.__internalShouldIncludePath?.[currentPath] ?? "", previousShouldInclude, compiledSkipInclude);
|
|
70
|
+
}
|
|
71
|
+
else {
|
|
72
|
+
// @deprecated
|
|
73
|
+
fieldNode.__internalShouldInclude = joinShouldIncludeCompilations(fieldNode.__internalShouldInclude ?? "", previousShouldInclude, compiledSkipInclude);
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* We augment the entire subtree as the parent object's skip/include
|
|
77
|
+
* directives influence the child even if the child doesn't have
|
|
78
|
+
* skip/include on it's own.
|
|
79
|
+
*
|
|
80
|
+
* Refer the function definition for example.
|
|
81
|
+
*/
|
|
82
|
+
augmentFieldNodeTree(compilationContext, fieldNode, currentPath);
|
|
83
|
+
fields[name].push(fieldNode);
|
|
84
|
+
break;
|
|
85
|
+
}
|
|
86
|
+
case language_1.Kind.INLINE_FRAGMENT: {
|
|
87
|
+
if (!doesFragmentConditionMatch(compilationContext, selection, runtimeType)) {
|
|
88
|
+
continue;
|
|
89
|
+
}
|
|
90
|
+
// current fragment's shouldInclude
|
|
91
|
+
const compiledSkipInclude = compileSkipInclude(compilationContext, selection);
|
|
92
|
+
// recurse
|
|
93
|
+
collectFieldsImpl(compilationContext, runtimeType, selection.selectionSet, fields, visitedFragmentNames, joinShouldIncludeCompilations(
|
|
94
|
+
// `should include`s from previous fragments
|
|
95
|
+
previousShouldInclude,
|
|
96
|
+
// current fragment's shouldInclude
|
|
97
|
+
compiledSkipInclude), parentResponsePath);
|
|
98
|
+
break;
|
|
99
|
+
}
|
|
100
|
+
case language_1.Kind.FRAGMENT_SPREAD: {
|
|
101
|
+
const fragName = selection.name.value;
|
|
102
|
+
if (visitedFragmentNames[fragName]) {
|
|
103
|
+
continue;
|
|
104
|
+
}
|
|
105
|
+
visitedFragmentNames[fragName] = true;
|
|
106
|
+
const fragment = compilationContext.fragments[fragName];
|
|
107
|
+
if (!fragment ||
|
|
108
|
+
!doesFragmentConditionMatch(compilationContext, fragment, runtimeType)) {
|
|
109
|
+
continue;
|
|
110
|
+
}
|
|
111
|
+
// current fragment's shouldInclude
|
|
112
|
+
const compiledSkipInclude = compileSkipInclude(compilationContext, selection);
|
|
113
|
+
// recurse
|
|
114
|
+
collectFieldsImpl(compilationContext, runtimeType, fragment.selectionSet, fields, visitedFragmentNames, joinShouldIncludeCompilations(
|
|
115
|
+
// `should include`s from previous fragments
|
|
116
|
+
previousShouldInclude,
|
|
117
|
+
// current fragment's shouldInclude
|
|
118
|
+
compiledSkipInclude), parentResponsePath);
|
|
119
|
+
break;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
return fields;
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* Augment __internalShouldInclude code for all sub-fields in the
|
|
127
|
+
* tree with @param rootfieldNode as the root.
|
|
128
|
+
*
|
|
129
|
+
* This is required to handle cases where there are multiple paths to
|
|
130
|
+
* the same node. And each of those paths contain different skip/include
|
|
131
|
+
* values.
|
|
132
|
+
*
|
|
133
|
+
* For example,
|
|
134
|
+
*
|
|
135
|
+
* ```
|
|
136
|
+
* {
|
|
137
|
+
* foo @skip(if: $c1) {
|
|
138
|
+
* bar @skip(if: $c2)
|
|
139
|
+
* }
|
|
140
|
+
* ... {
|
|
141
|
+
* foo @skip(if: $c3) {
|
|
142
|
+
* bar
|
|
143
|
+
* }
|
|
144
|
+
* }
|
|
145
|
+
* }
|
|
146
|
+
* ```
|
|
147
|
+
*
|
|
148
|
+
* We decide shouldInclude at runtime per fieldNode. When we handle the
|
|
149
|
+
* field `foo`, the logic is straight forward - it requires one of $c1 or $c3
|
|
150
|
+
* to be false.
|
|
151
|
+
*
|
|
152
|
+
* But, when we handle the field `bar`, and we are in the context of the fieldNode,
|
|
153
|
+
* not enough information is available. This is because, if we only included $c2
|
|
154
|
+
* to decide if bar is included, consider the case -
|
|
155
|
+
*
|
|
156
|
+
* $c1: true, $c2: true, $c3: false
|
|
157
|
+
*
|
|
158
|
+
* If we considered only $c2, we would have skipped bar. But the correct implementation
|
|
159
|
+
* is to include bar, because foo($c3) { bar } is not skipped. The entire sub-tree's
|
|
160
|
+
* logic is required to handle bar.
|
|
161
|
+
*
|
|
162
|
+
* So, to handle this case, we augment the tree at each point to consider the
|
|
163
|
+
* skip/include logic from the parent as well.
|
|
164
|
+
*
|
|
165
|
+
* @param compilationContext {CompilationContext} Required for getFragment by
|
|
166
|
+
* name to handle fragment spread operation.
|
|
167
|
+
*
|
|
168
|
+
* @param rootFieldNode {JitFieldNode} The root field to traverse from for
|
|
169
|
+
* adding __internalShouldInclude to all sub field nodes.
|
|
170
|
+
*
|
|
171
|
+
* @param parentResponsePath {string} The response path of the parent field.
|
|
172
|
+
*/
|
|
173
|
+
function augmentFieldNodeTree(compilationContext, rootFieldNode, parentResponsePath) {
|
|
174
|
+
for (const selection of rootFieldNode.selectionSet?.selections ?? []) {
|
|
175
|
+
handle(rootFieldNode, selection, false, parentResponsePath);
|
|
176
|
+
}
|
|
177
|
+
/**
|
|
178
|
+
* Recursively traverse through sub-selection and combine `shouldInclude`s
|
|
179
|
+
* from parent and current ones.
|
|
180
|
+
*/
|
|
181
|
+
function handle(parentFieldNode, selection, comesFromFragmentSpread = false, parentResponsePath) {
|
|
182
|
+
switch (selection.kind) {
|
|
183
|
+
case language_1.Kind.FIELD: {
|
|
184
|
+
const jitFieldNode = selection;
|
|
185
|
+
const currentPath = joinSkipIncludePath(parentResponsePath,
|
|
186
|
+
// use alias(instead of selection.name.value) if available as the responsePath used for lookup uses alias
|
|
187
|
+
getFieldEntryKey(jitFieldNode));
|
|
188
|
+
if (!comesFromFragmentSpread) {
|
|
189
|
+
if (compilationContext.options.useExperimentalPathBasedSkipInclude) {
|
|
190
|
+
if (!jitFieldNode.__internalShouldIncludePath)
|
|
191
|
+
jitFieldNode.__internalShouldIncludePath = {};
|
|
192
|
+
jitFieldNode.__internalShouldIncludePath[currentPath] =
|
|
193
|
+
joinShouldIncludeCompilations(parentFieldNode.__internalShouldIncludePath?.[parentResponsePath] ?? "", jitFieldNode.__internalShouldIncludePath?.[currentPath] ?? "");
|
|
194
|
+
}
|
|
195
|
+
else {
|
|
196
|
+
// @deprecated
|
|
197
|
+
jitFieldNode.__internalShouldInclude =
|
|
198
|
+
joinShouldIncludeCompilations(parentFieldNode.__internalShouldInclude ?? "", jitFieldNode.__internalShouldInclude ?? "");
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
// go further down the query tree
|
|
202
|
+
for (const selection of jitFieldNode.selectionSet?.selections ?? []) {
|
|
203
|
+
handle(jitFieldNode, selection, false, currentPath);
|
|
204
|
+
}
|
|
205
|
+
break;
|
|
206
|
+
}
|
|
207
|
+
case language_1.Kind.INLINE_FRAGMENT: {
|
|
208
|
+
for (const subSelection of selection.selectionSet.selections) {
|
|
209
|
+
handle(parentFieldNode, subSelection, true, parentResponsePath);
|
|
210
|
+
}
|
|
211
|
+
break;
|
|
212
|
+
}
|
|
213
|
+
case language_1.Kind.FRAGMENT_SPREAD: {
|
|
214
|
+
const fragment = compilationContext.fragments[selection.name.value];
|
|
215
|
+
for (const subSelection of fragment.selectionSet.selections) {
|
|
216
|
+
handle(parentFieldNode, subSelection, true, parentResponsePath);
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
/**
|
|
223
|
+
* Joins a list of shouldInclude compiled code into a single logical
|
|
224
|
+
* statement.
|
|
225
|
+
*
|
|
226
|
+
* The operation is `&&` because, it is used to join parent->child
|
|
227
|
+
* relations in the query tree. Note: parent can be either parent field
|
|
228
|
+
* or fragment.
|
|
229
|
+
*
|
|
230
|
+
* For example,
|
|
231
|
+
* {
|
|
232
|
+
* foo @skip(if: $c1) {
|
|
233
|
+
* ... @skip(if: $c2) {
|
|
234
|
+
* bar @skip(if: $c3)
|
|
235
|
+
* }
|
|
236
|
+
* }
|
|
237
|
+
* }
|
|
238
|
+
*
|
|
239
|
+
* Only when a parent is included, the child is included. So, we use `&&`.
|
|
240
|
+
*
|
|
241
|
+
* compilationFor($c1) && compilationFor($c2) && compilationFor($c3)
|
|
242
|
+
*
|
|
243
|
+
* @param compilations
|
|
244
|
+
*/
|
|
245
|
+
function joinShouldIncludeCompilations(...compilations) {
|
|
246
|
+
// remove "true" since we are joining with '&&' as `true && X` = `X`
|
|
247
|
+
// This prevents an explosion of `&& true` which could break
|
|
248
|
+
// V8's internal size limit for string.
|
|
249
|
+
//
|
|
250
|
+
// Note: the `true` appears if a field does not have a skip/include directive
|
|
251
|
+
// So, the more nested the query is, the more of unnecessary `&& true`
|
|
252
|
+
// we get.
|
|
253
|
+
//
|
|
254
|
+
// Failing to do this results in [RangeError: invalid array length]
|
|
255
|
+
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Errors/Invalid_array_length
|
|
256
|
+
// remove empty strings
|
|
257
|
+
let filteredCompilations = compilations.filter((it) => it);
|
|
258
|
+
// Split conditions by && and flatten it
|
|
259
|
+
filteredCompilations = [].concat(...filteredCompilations.map((e) => e.split(" && ").map((it) => it.trim())));
|
|
260
|
+
// Deduplicate items
|
|
261
|
+
filteredCompilations = Array.from(new Set(filteredCompilations));
|
|
262
|
+
return filteredCompilations.join(" && ");
|
|
263
|
+
}
|
|
264
|
+
/**
|
|
265
|
+
* Compiles directives `skip` and `include` and generates the compilation
|
|
266
|
+
* code based on GraphQL specification.
|
|
267
|
+
*
|
|
268
|
+
* @param node {SelectionNode} The selection node (field/fragment/inline-fragment)
|
|
269
|
+
* for which we generate the compiled skipInclude.
|
|
270
|
+
*/
|
|
271
|
+
function compileSkipInclude(compilationContext, node) {
|
|
272
|
+
const gen = (0, generate_function_1.default)();
|
|
273
|
+
const { skipValue, includeValue } = compileSkipIncludeDirectiveValues(compilationContext, node);
|
|
274
|
+
/**
|
|
275
|
+
* Spec: https://spec.graphql.org/June2018/#sec--include
|
|
276
|
+
*
|
|
277
|
+
* Neither @skip nor @include has precedence over the other.
|
|
278
|
+
* In the case that both the @skip and @include directives
|
|
279
|
+
* are provided in on the same the field or fragment, it must
|
|
280
|
+
* be queried only if the @skip condition is false and the
|
|
281
|
+
* @include condition is true. Stated conversely, the field
|
|
282
|
+
* or fragment must not be queried if either the @skip
|
|
283
|
+
* condition is true or the @include condition is false.
|
|
284
|
+
*/
|
|
285
|
+
if (skipValue != null && includeValue != null) {
|
|
286
|
+
gen(`${skipValue} === false && ${includeValue} === true`);
|
|
287
|
+
}
|
|
288
|
+
else if (skipValue != null) {
|
|
289
|
+
gen(`(${skipValue} === false)`);
|
|
290
|
+
}
|
|
291
|
+
else if (includeValue != null) {
|
|
292
|
+
gen(`(${includeValue} === true)`);
|
|
293
|
+
}
|
|
294
|
+
else {
|
|
295
|
+
gen(`true`);
|
|
296
|
+
}
|
|
297
|
+
return gen.toString();
|
|
298
|
+
}
|
|
299
|
+
/**
|
|
300
|
+
* Compile skip or include directive values into JIT compatible
|
|
301
|
+
* runtime code.
|
|
302
|
+
*
|
|
303
|
+
* @param node {SelectionNode}
|
|
304
|
+
*/
|
|
305
|
+
function compileSkipIncludeDirectiveValues(compilationContext, node) {
|
|
306
|
+
const skipDirective = node.directives?.find((it) => it.name.value === graphql_1.GraphQLSkipDirective.name);
|
|
307
|
+
const includeDirective = node.directives?.find((it) => it.name.value === graphql_1.GraphQLIncludeDirective.name);
|
|
308
|
+
const skipValue = skipDirective
|
|
309
|
+
? compileSkipIncludeDirective(compilationContext, skipDirective)
|
|
310
|
+
: // The null here indicates the absense of the directive
|
|
311
|
+
// which is later used to determine if both skip and include
|
|
312
|
+
// are present
|
|
313
|
+
null;
|
|
314
|
+
const includeValue = includeDirective
|
|
315
|
+
? compileSkipIncludeDirective(compilationContext, includeDirective)
|
|
316
|
+
: // The null here indicates the absense of the directive
|
|
317
|
+
// which is later used to determine if both skip and include
|
|
318
|
+
// are present
|
|
319
|
+
null;
|
|
320
|
+
return { skipValue, includeValue };
|
|
321
|
+
}
|
|
322
|
+
/**
|
|
323
|
+
* Compile the skip/include directive node. Resolve variables to it's
|
|
324
|
+
* path from context, resolve scalars to their respective values.
|
|
325
|
+
*
|
|
326
|
+
* @param directive {DirectiveNode}
|
|
327
|
+
*/
|
|
328
|
+
function compileSkipIncludeDirective(compilationContext, directive) {
|
|
329
|
+
const ifNode = directive.arguments?.find((it) => it.name.value === "if");
|
|
330
|
+
if (ifNode == null) {
|
|
331
|
+
throw new graphql_1.GraphQLError(`Directive '${directive.name.value}' is missing required arguments: 'if'`, (0, compat_1.getGraphQLErrorOptions)([directive]));
|
|
332
|
+
}
|
|
333
|
+
switch (ifNode.value.kind) {
|
|
334
|
+
case language_1.Kind.VARIABLE:
|
|
335
|
+
validateSkipIncludeVariableType(compilationContext, ifNode.value);
|
|
336
|
+
return `${execution_1.GLOBAL_VARIABLES_NAME}["${ifNode.value.name.value}"]`;
|
|
337
|
+
case language_1.Kind.BOOLEAN:
|
|
338
|
+
return `${ifNode.value.value.toString()}`;
|
|
339
|
+
default:
|
|
340
|
+
throw new graphql_1.GraphQLError(`Argument 'if' on Directive '${directive.name.value}' has an invalid value (${(0, graphql_1.valueFromASTUntyped)(ifNode.value)}). Expected type 'Boolean!'`, (0, compat_1.getGraphQLErrorOptions)([ifNode]));
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
/**
|
|
344
|
+
* Validate the skip and include directive's argument values at compile time.
|
|
345
|
+
*
|
|
346
|
+
* This validation step is required as these directives are part of an
|
|
347
|
+
* implicit schema in GraphQL.
|
|
348
|
+
*
|
|
349
|
+
* @param compilationContext {CompilationContext}
|
|
350
|
+
* @param variable {VariableNode} the variable used in 'if' argument of the skip/include directive
|
|
351
|
+
*/
|
|
352
|
+
function validateSkipIncludeVariableType(compilationContext, variable) {
|
|
353
|
+
const variableDefinition = compilationContext.operation.variableDefinitions?.find((it) => it.variable.name.value === variable.name.value);
|
|
354
|
+
if (variableDefinition == null) {
|
|
355
|
+
throw new graphql_1.GraphQLError(`Variable '${variable.name.value}' is not defined`, (0, compat_1.getGraphQLErrorOptions)([variable]));
|
|
356
|
+
}
|
|
357
|
+
// Part of Spec text: https://spec.graphql.org/June2018/#sec-All-Variable-Usages-are-Allowed
|
|
358
|
+
if (!(
|
|
359
|
+
// The variable defintion is a Non-nullable Boolean type
|
|
360
|
+
((variableDefinition.type.kind === language_1.Kind.NON_NULL_TYPE &&
|
|
361
|
+
variableDefinition.type.type.kind === language_1.Kind.NAMED_TYPE &&
|
|
362
|
+
variableDefinition.type.type.name.value === "Boolean") ||
|
|
363
|
+
// or the variable definition is a nullable Boolean type with a default value
|
|
364
|
+
(variableDefinition.type.kind === language_1.Kind.NAMED_TYPE &&
|
|
365
|
+
variableDefinition.type.name.value === "Boolean" &&
|
|
366
|
+
variableDefinition.defaultValue != null)))) {
|
|
367
|
+
throw new graphql_1.GraphQLError(`Variable '${variable.name.value}' of type '${typeNodeToString(variableDefinition.type)}' used in position expecting type 'Boolean!'`, (0, compat_1.getGraphQLErrorOptions)([variableDefinition]));
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
/**
|
|
371
|
+
* Print the string representation of the TypeNode for error messages
|
|
372
|
+
*
|
|
373
|
+
* @param type {TypeNode} type node to be converted to string representation
|
|
374
|
+
*/
|
|
375
|
+
function typeNodeToString(type) {
|
|
376
|
+
switch (type.kind) {
|
|
377
|
+
case language_1.Kind.NAMED_TYPE:
|
|
378
|
+
return type.name.value;
|
|
379
|
+
case language_1.Kind.NON_NULL_TYPE:
|
|
380
|
+
return `${typeNodeToString(type.type)}!`;
|
|
381
|
+
case language_1.Kind.LIST_TYPE:
|
|
382
|
+
return `[${typeNodeToString(type.type)}]`;
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
/**
|
|
386
|
+
* Determines if a fragment is applicable to the given type.
|
|
387
|
+
*/
|
|
388
|
+
function doesFragmentConditionMatch(compilationContext, fragment, type) {
|
|
389
|
+
const typeConditionNode = fragment.typeCondition;
|
|
390
|
+
if (!typeConditionNode) {
|
|
391
|
+
return true;
|
|
392
|
+
}
|
|
393
|
+
const conditionalType = (0, graphql_1.typeFromAST)(compilationContext.schema, typeConditionNode);
|
|
394
|
+
if (conditionalType === type) {
|
|
395
|
+
return true;
|
|
396
|
+
}
|
|
397
|
+
if (!conditionalType) {
|
|
398
|
+
return false;
|
|
399
|
+
}
|
|
400
|
+
if ((0, type_1.isAbstractType)(conditionalType)) {
|
|
401
|
+
return compilationContext.schema.isSubType(conditionalType, type);
|
|
402
|
+
}
|
|
403
|
+
return false;
|
|
404
|
+
}
|
|
405
|
+
/**
|
|
406
|
+
* Implements the logic to compute the key of a given field's entry
|
|
407
|
+
*/
|
|
408
|
+
function getFieldEntryKey(node) {
|
|
409
|
+
return node.alias ? node.alias.value : node.name.value;
|
|
410
|
+
}
|
|
411
|
+
function collectSubfields(compilationContext, returnType, fieldNodes, parentResponsePath) {
|
|
412
|
+
let subFieldNodes = Object.create(null);
|
|
413
|
+
const visitedFragmentNames = Object.create(null);
|
|
414
|
+
for (const fieldNode of fieldNodes) {
|
|
415
|
+
const selectionSet = fieldNode.selectionSet;
|
|
416
|
+
if (selectionSet) {
|
|
417
|
+
subFieldNodes = collectFields(compilationContext, returnType, selectionSet, subFieldNodes, visitedFragmentNames, parentResponsePath);
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
return subFieldNodes;
|
|
421
|
+
}
|
|
422
|
+
exports.collectSubfields = collectSubfields;
|
|
423
|
+
/**
|
|
424
|
+
* Prepares an object map of argument values given a list of argument
|
|
425
|
+
* definitions and list of argument AST nodes.
|
|
426
|
+
*
|
|
427
|
+
* Note: The returned value is a plain Object with a prototype, since it is
|
|
428
|
+
* exposed to user code. Care should be taken to not pull values from the
|
|
429
|
+
* Object prototype.
|
|
430
|
+
*/
|
|
431
|
+
function getArgumentDefs(def, node) {
|
|
432
|
+
const values = {};
|
|
433
|
+
const missing = [];
|
|
434
|
+
const argDefs = def.args;
|
|
435
|
+
const argNodes = node.arguments || [];
|
|
436
|
+
const argNodeMap = keyMap(argNodes, (arg) => arg.name.value);
|
|
437
|
+
for (const argDef of argDefs) {
|
|
438
|
+
const name = argDef.name;
|
|
439
|
+
if (argDef.defaultValue !== undefined) {
|
|
440
|
+
// Set the coerced value to the default
|
|
441
|
+
values[name] = argDef.defaultValue;
|
|
442
|
+
}
|
|
443
|
+
const argType = argDef.type;
|
|
444
|
+
const argumentNode = argNodeMap[name];
|
|
445
|
+
let hasVariables = false;
|
|
446
|
+
if (argumentNode && argumentNode.value.kind === language_1.Kind.VARIABLE) {
|
|
447
|
+
hasVariables = true;
|
|
448
|
+
missing.push({
|
|
449
|
+
valueNode: argumentNode.value,
|
|
450
|
+
path: addPath(undefined, name, "literal"),
|
|
451
|
+
argument: { definition: argDef, node: argumentNode }
|
|
452
|
+
});
|
|
453
|
+
}
|
|
454
|
+
else if (argumentNode) {
|
|
455
|
+
const coercedValue = valueFromAST(argumentNode.value, argType);
|
|
456
|
+
if (coercedValue === undefined) {
|
|
457
|
+
// Note: ValuesOfCorrectType validation should catch this before
|
|
458
|
+
// execution. This is a runtime check to ensure execution does not
|
|
459
|
+
// continue with an invalid argument value.
|
|
460
|
+
throw new graphql_1.GraphQLError(`Argument "${name}" of type "${argType}" has invalid value ${(0, graphql_1.print)(argumentNode.value)}.`, (0, compat_1.getGraphQLErrorOptions)(argumentNode.value));
|
|
461
|
+
}
|
|
462
|
+
if (isASTValueWithVariables(coercedValue)) {
|
|
463
|
+
missing.push(...coercedValue.variables.map(({ valueNode, path }) => ({
|
|
464
|
+
valueNode,
|
|
465
|
+
path: addPath(path, name, "literal")
|
|
466
|
+
})));
|
|
467
|
+
}
|
|
468
|
+
values[name] = coercedValue.value;
|
|
469
|
+
}
|
|
470
|
+
if ((0, graphql_1.isNonNullType)(argType) && values[name] === undefined && !hasVariables) {
|
|
471
|
+
// If no value or a nullish value was provided to a variable with a
|
|
472
|
+
// non-null type (required), produce an error.
|
|
473
|
+
throw new graphql_1.GraphQLError(argumentNode
|
|
474
|
+
? `Argument "${name}" of non-null type ` +
|
|
475
|
+
`"${argType}" must not be null.`
|
|
476
|
+
: `Argument "${name}" of required type ` +
|
|
477
|
+
`"${argType}" was not provided.`, (0, compat_1.getGraphQLErrorOptions)(node));
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
return { values, missing };
|
|
481
|
+
}
|
|
482
|
+
exports.getArgumentDefs = getArgumentDefs;
|
|
483
|
+
function isASTValueWithVariables(x) {
|
|
484
|
+
return !!x.variables;
|
|
485
|
+
}
|
|
486
|
+
function valueFromAST(valueNode, type) {
|
|
487
|
+
if ((0, graphql_1.isNonNullType)(type)) {
|
|
488
|
+
if (valueNode.kind === language_1.Kind.NULL) {
|
|
489
|
+
return; // Invalid: intentionally return no value.
|
|
490
|
+
}
|
|
491
|
+
return valueFromAST(valueNode, type.ofType);
|
|
492
|
+
}
|
|
493
|
+
if (valueNode.kind === language_1.Kind.NULL) {
|
|
494
|
+
// This is explicitly returning the value null.
|
|
495
|
+
return {
|
|
496
|
+
value: null
|
|
497
|
+
};
|
|
498
|
+
}
|
|
499
|
+
if (valueNode.kind === language_1.Kind.VARIABLE) {
|
|
500
|
+
return { value: null, variables: [{ valueNode, path: undefined }] };
|
|
501
|
+
}
|
|
502
|
+
if ((0, graphql_1.isListType)(type)) {
|
|
503
|
+
const itemType = type.ofType;
|
|
504
|
+
if (valueNode.kind === language_1.Kind.LIST) {
|
|
505
|
+
const coercedValues = [];
|
|
506
|
+
const variables = [];
|
|
507
|
+
const itemNodes = valueNode.values;
|
|
508
|
+
for (let i = 0; i < itemNodes.length; i++) {
|
|
509
|
+
const itemNode = itemNodes[i];
|
|
510
|
+
if (itemNode.kind === language_1.Kind.VARIABLE) {
|
|
511
|
+
coercedValues.push(null);
|
|
512
|
+
variables.push({
|
|
513
|
+
valueNode: itemNode,
|
|
514
|
+
path: addPath(undefined, i.toString(), "literal")
|
|
515
|
+
});
|
|
516
|
+
}
|
|
517
|
+
else {
|
|
518
|
+
const itemValue = valueFromAST(itemNode, itemType);
|
|
519
|
+
if (!itemValue) {
|
|
520
|
+
return; // Invalid: intentionally return no value.
|
|
521
|
+
}
|
|
522
|
+
coercedValues.push(itemValue.value);
|
|
523
|
+
if (isASTValueWithVariables(itemValue)) {
|
|
524
|
+
variables.push(...itemValue.variables.map(({ valueNode, path }) => ({
|
|
525
|
+
valueNode,
|
|
526
|
+
path: addPath(path, i.toString(), "literal")
|
|
527
|
+
})));
|
|
528
|
+
}
|
|
529
|
+
}
|
|
530
|
+
}
|
|
531
|
+
return { value: coercedValues, variables };
|
|
532
|
+
}
|
|
533
|
+
// Single item which will be coerced to a list
|
|
534
|
+
const coercedValue = valueFromAST(valueNode, itemType);
|
|
535
|
+
if (coercedValue === undefined) {
|
|
536
|
+
return; // Invalid: intentionally return no value.
|
|
537
|
+
}
|
|
538
|
+
if (isASTValueWithVariables(coercedValue)) {
|
|
539
|
+
return {
|
|
540
|
+
value: [coercedValue.value],
|
|
541
|
+
variables: coercedValue.variables.map(({ valueNode, path }) => ({
|
|
542
|
+
valueNode,
|
|
543
|
+
path: addPath(path, "0", "literal")
|
|
544
|
+
}))
|
|
545
|
+
};
|
|
546
|
+
}
|
|
547
|
+
return { value: [coercedValue.value] };
|
|
548
|
+
}
|
|
549
|
+
if ((0, graphql_1.isInputObjectType)(type)) {
|
|
550
|
+
if (valueNode.kind !== language_1.Kind.OBJECT) {
|
|
551
|
+
return; // Invalid: intentionally return no value.
|
|
552
|
+
}
|
|
553
|
+
const coercedObj = Object.create(null);
|
|
554
|
+
const variables = [];
|
|
555
|
+
const fieldNodes = keyMap(valueNode.fields, (field) => field.name.value);
|
|
556
|
+
const fields = Object.values(type.getFields());
|
|
557
|
+
for (const field of fields) {
|
|
558
|
+
if (field.defaultValue !== undefined) {
|
|
559
|
+
coercedObj[field.name] = field.defaultValue;
|
|
560
|
+
}
|
|
561
|
+
const fieldNode = fieldNodes[field.name];
|
|
562
|
+
if (!fieldNode) {
|
|
563
|
+
continue;
|
|
564
|
+
}
|
|
565
|
+
const fieldValue = valueFromAST(fieldNode.value, field.type);
|
|
566
|
+
if (!fieldValue) {
|
|
567
|
+
return; // Invalid: intentionally return no value.
|
|
568
|
+
}
|
|
569
|
+
if (isASTValueWithVariables(fieldValue)) {
|
|
570
|
+
variables.push(...fieldValue.variables.map(({ valueNode, path }) => ({
|
|
571
|
+
valueNode,
|
|
572
|
+
path: addPath(path, field.name, "literal")
|
|
573
|
+
})));
|
|
574
|
+
}
|
|
575
|
+
coercedObj[field.name] = fieldValue.value;
|
|
576
|
+
}
|
|
577
|
+
return { value: coercedObj, variables };
|
|
578
|
+
}
|
|
579
|
+
if ((0, graphql_1.isEnumType)(type)) {
|
|
580
|
+
if (valueNode.kind !== language_1.Kind.ENUM) {
|
|
581
|
+
return; // Invalid: intentionally return no value.
|
|
582
|
+
}
|
|
583
|
+
const enumValue = type.getValue(valueNode.value);
|
|
584
|
+
if (!enumValue) {
|
|
585
|
+
return; // Invalid: intentionally return no value.
|
|
586
|
+
}
|
|
587
|
+
return { value: enumValue.value };
|
|
588
|
+
}
|
|
589
|
+
if ((0, graphql_1.isScalarType)(type)) {
|
|
590
|
+
// Scalars fulfill parsing a literal value via parseLiteral().
|
|
591
|
+
// Invalid values represent a failure to parse correctly, in which case
|
|
592
|
+
// no value is returned.
|
|
593
|
+
let result;
|
|
594
|
+
try {
|
|
595
|
+
if (type.parseLiteral.length > 1) {
|
|
596
|
+
// eslint-disable-next-line
|
|
597
|
+
console.error("Scalar with variable inputs detected for parsing AST literals. This is not supported.");
|
|
598
|
+
}
|
|
599
|
+
result = type.parseLiteral(valueNode, {});
|
|
600
|
+
}
|
|
601
|
+
catch (error) {
|
|
602
|
+
return; // Invalid: intentionally return no value.
|
|
603
|
+
}
|
|
604
|
+
if (isInvalid(result)) {
|
|
605
|
+
return; // Invalid: intentionally return no value.
|
|
606
|
+
}
|
|
607
|
+
return { value: result };
|
|
608
|
+
}
|
|
609
|
+
// Not reachable. All possible input types have been considered.
|
|
610
|
+
/* istanbul ignore next */
|
|
611
|
+
throw new Error(`Unexpected input type: "${inspect(type)}".`);
|
|
612
|
+
}
|
|
613
|
+
exports.valueFromAST = valueFromAST;
|
|
614
|
+
/**
|
|
615
|
+
* Creates a keyed JS object from an array, given a function to produce the keys
|
|
616
|
+
* for each value in the array.
|
|
617
|
+
*
|
|
618
|
+
* This provides a convenient lookup for the array items if the key function
|
|
619
|
+
* produces unique results.
|
|
620
|
+
*
|
|
621
|
+
* const phoneBook = [
|
|
622
|
+
* { name: 'Jon', num: '555-1234' },
|
|
623
|
+
* { name: 'Jenny', num: '867-5309' }
|
|
624
|
+
* ]
|
|
625
|
+
*
|
|
626
|
+
* // { Jon: { name: 'Jon', num: '555-1234' },
|
|
627
|
+
* // Jenny: { name: 'Jenny', num: '867-5309' } }
|
|
628
|
+
* const entriesByName = keyMap(
|
|
629
|
+
* phoneBook,
|
|
630
|
+
* entry => entry.name
|
|
631
|
+
* )
|
|
632
|
+
*
|
|
633
|
+
* // { name: 'Jenny', num: '857-6309' }
|
|
634
|
+
* const jennyEntry = entriesByName['Jenny']
|
|
635
|
+
*
|
|
636
|
+
*/
|
|
637
|
+
function keyMap(list, keyFn) {
|
|
638
|
+
return list.reduce(
|
|
639
|
+
// eslint-disable-next-line no-sequences
|
|
640
|
+
(map, item) => ((map[keyFn(item)] = item), map), Object.create(null));
|
|
641
|
+
}
|
|
642
|
+
function computeLocations(nodes) {
|
|
643
|
+
return nodes.reduce((list, node) => {
|
|
644
|
+
if (node.loc) {
|
|
645
|
+
list.push((0, graphql_1.getLocation)(node.loc.source, node.loc.start));
|
|
646
|
+
}
|
|
647
|
+
return list;
|
|
648
|
+
}, []);
|
|
649
|
+
}
|
|
650
|
+
exports.computeLocations = computeLocations;
|
|
651
|
+
function addPath(responsePath, key, type = "literal") {
|
|
652
|
+
return { prev: responsePath, key, type };
|
|
653
|
+
}
|
|
654
|
+
exports.addPath = addPath;
|
|
655
|
+
function flattenPath(path) {
|
|
656
|
+
const flattened = [];
|
|
657
|
+
let curr = path;
|
|
658
|
+
while (curr) {
|
|
659
|
+
flattened.push({ key: curr.key, type: curr.type });
|
|
660
|
+
curr = curr.prev;
|
|
661
|
+
}
|
|
662
|
+
return flattened;
|
|
663
|
+
}
|
|
664
|
+
exports.flattenPath = flattenPath;
|
|
665
|
+
/**
|
|
666
|
+
* Serialize a path for use in the skip/include directives.
|
|
667
|
+
*
|
|
668
|
+
* @param path The path to serialize
|
|
669
|
+
* @returns The path serialized as a string, with the root path first.
|
|
670
|
+
*/
|
|
671
|
+
function serializeObjectPathForSkipInclude(path) {
|
|
672
|
+
let serialized = "";
|
|
673
|
+
let curr = path;
|
|
674
|
+
while (curr) {
|
|
675
|
+
if (curr.type === "literal") {
|
|
676
|
+
serialized = joinSkipIncludePath(curr.key, serialized);
|
|
677
|
+
}
|
|
678
|
+
curr = curr.prev;
|
|
679
|
+
}
|
|
680
|
+
return serialized;
|
|
681
|
+
}
|
|
682
|
+
exports.serializeObjectPathForSkipInclude = serializeObjectPathForSkipInclude;
|
|
683
|
+
/**
|
|
684
|
+
* join two path segments to a dot notation, handling empty strings
|
|
685
|
+
*
|
|
686
|
+
* @param a path segment
|
|
687
|
+
* @param b path segment
|
|
688
|
+
* @returns combined path in dot notation
|
|
689
|
+
*/
|
|
690
|
+
function joinSkipIncludePath(a, b) {
|
|
691
|
+
if (a) {
|
|
692
|
+
if (b) {
|
|
693
|
+
return `${a}.${b}`;
|
|
694
|
+
}
|
|
695
|
+
return a;
|
|
696
|
+
}
|
|
697
|
+
return b;
|
|
698
|
+
}
|
|
699
|
+
exports.joinSkipIncludePath = joinSkipIncludePath;
|
|
700
|
+
function isInvalid(value) {
|
|
701
|
+
// eslint-disable-next-line no-self-compare
|
|
702
|
+
return value === undefined || value !== value;
|
|
703
|
+
}
|
|
704
|
+
//# sourceMappingURL=ast.js.map
|