UNPKG

28.3 kBJavaScriptView Raw
1var __extends = (this && this.__extends) || (function () {
2 var extendStatics = function (d, b) {
3 extendStatics = Object.setPrototypeOf ||
4 ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
5 function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; };
6 return extendStatics(d, b);
7 };
8 return function (d, b) {
9 extendStatics(d, b);
10 function __() { this.constructor = d; }
11 d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
12 };
13})();
14var __spreadArrays = (this && this.__spreadArrays) || function () {
15 for (var s = 0, i = 0, il = arguments.length; i < il; i++) s += arguments[i].length;
16 for (var r = Array(s), k = 0, i = 0; i < il; i++)
17 for (var a = arguments[i], j = 0, jl = a.length; j < jl; j++, k++)
18 r[k] = a[j];
19 return r;
20};
21Object.defineProperty(exports, "__esModule", { value: true });
22var graphql_1 = require("graphql");
23var values_1 = require("graphql/execution/values");
24var hasOwn = Object.prototype.hasOwnProperty;
25// Abstract base class of any visitor implementation, defining the available
26// visitor methods along with their parameter types, and providing a static
27// helper function for determining whether a subclass implements a given
28// visitor method, as opposed to inheriting one of the stubs defined here.
29var SchemaVisitor = /** @class */ (function () {
30 function SchemaVisitor() {
31 }
32 // Determine if this SchemaVisitor (sub)class implements a particular
33 // visitor method.
34 SchemaVisitor.implementsVisitorMethod = function (methodName) {
35 if (!methodName.startsWith('visit')) {
36 return false;
37 }
38 var method = this.prototype[methodName];
39 if (typeof method !== 'function') {
40 return false;
41 }
42 if (this === SchemaVisitor) {
43 // The SchemaVisitor class implements every visitor method.
44 return true;
45 }
46 var stub = SchemaVisitor.prototype[methodName];
47 if (method === stub) {
48 // If this.prototype[methodName] was just inherited from SchemaVisitor,
49 // then this class does not really implement the method.
50 return false;
51 }
52 return true;
53 };
54 // Concrete subclasses of SchemaVisitor should override one or more of these
55 // visitor methods, in order to express their interest in handling certain
56 // schema types/locations. Each method may return null to remove the given
57 // type from the schema, a non-null value of the same type to update the
58 // type in the schema, or nothing to leave the type as it was.
59 /* tslint:disable:no-empty */
60 SchemaVisitor.prototype.visitSchema = function (schema) { };
61 SchemaVisitor.prototype.visitScalar = function (scalar) { };
62 SchemaVisitor.prototype.visitObject = function (object) { };
63 SchemaVisitor.prototype.visitFieldDefinition = function (field, details) { };
64 SchemaVisitor.prototype.visitArgumentDefinition = function (argument, details) { };
65 SchemaVisitor.prototype.visitInterface = function (iface) { };
66 SchemaVisitor.prototype.visitUnion = function (union) { };
67 SchemaVisitor.prototype.visitEnum = function (type) { };
68 SchemaVisitor.prototype.visitEnumValue = function (value, details) { };
69 SchemaVisitor.prototype.visitInputObject = function (object) { };
70 SchemaVisitor.prototype.visitInputFieldDefinition = function (field, details) { };
71 return SchemaVisitor;
72}());
73exports.SchemaVisitor = SchemaVisitor;
74// Generic function for visiting GraphQLSchema objects.
75function visitSchema(schema,
76// To accommodate as many different visitor patterns as possible, the
77// visitSchema function does not simply accept a single instance of the
78// SchemaVisitor class, but instead accepts a function that takes the
79// current VisitableSchemaType object and the name of a visitor method and
80// returns an array of SchemaVisitor instances that implement the visitor
81// method and have an interest in handling the given VisitableSchemaType
82// object. In the simplest case, this function can always return an array
83// containing a single visitor object, without even looking at the type or
84// methodName parameters. In other cases, this function might sometimes
85// return an empty array to indicate there are no visitors that should be
86// applied to the given VisitableSchemaType object. For an example of a
87// visitor pattern that benefits from this abstraction, see the
88// SchemaDirectiveVisitor class below.
89visitorSelector) {
90 // Helper function that calls visitorSelector and applies the resulting
91 // visitors to the given type, with arguments [type, ...args].
92 function callMethod(methodName, type) {
93 var args = [];
94 for (var _i = 2; _i < arguments.length; _i++) {
95 args[_i - 2] = arguments[_i];
96 }
97 visitorSelector(type, methodName).every(function (visitor) {
98 var newType = visitor[methodName].apply(visitor, __spreadArrays([type], args));
99 if (typeof newType === 'undefined') {
100 // Keep going without modifying type.
101 return true;
102 }
103 if (methodName === 'visitSchema' ||
104 type instanceof graphql_1.GraphQLSchema) {
105 throw new Error("Method " + methodName + " cannot replace schema with " + newType);
106 }
107 if (newType === null) {
108 // Stop the loop and return null form callMethod, which will cause
109 // the type to be removed from the schema.
110 type = null;
111 return false;
112 }
113 // Update type to the new type returned by the visitor method, so that
114 // later directives will see the new type, and callMethod will return
115 // the final type.
116 type = newType;
117 return true;
118 });
119 // If there were no directives for this type object, or if all visitor
120 // methods returned nothing, type will be returned unmodified.
121 return type;
122 }
123 // Recursive helper function that calls any appropriate visitor methods for
124 // each object in the schema, then traverses the object's children (if any).
125 function visit(type) {
126 if (type instanceof graphql_1.GraphQLSchema) {
127 // Unlike the other types, the root GraphQLSchema object cannot be
128 // replaced by visitor methods, because that would make life very hard
129 // for SchemaVisitor subclasses that rely on the original schema object.
130 callMethod('visitSchema', type);
131 updateEachKey(type.getTypeMap(), function (namedType, typeName) {
132 if (!typeName.startsWith('__')) {
133 // Call visit recursively to let it determine which concrete
134 // subclass of GraphQLNamedType we found in the type map. Because
135 // we're using updateEachKey, the result of visit(namedType) may
136 // cause the type to be removed or replaced.
137 return visit(namedType);
138 }
139 });
140 return type;
141 }
142 if (type instanceof graphql_1.GraphQLObjectType) {
143 // Note that callMethod('visitObject', type) may not actually call any
144 // methods, if there are no @directive annotations associated with this
145 // type, or if this SchemaDirectiveVisitor subclass does not override
146 // the visitObject method.
147 var newObject = callMethod('visitObject', type);
148 if (newObject) {
149 visitFields(newObject);
150 }
151 return newObject;
152 }
153 if (type instanceof graphql_1.GraphQLInterfaceType) {
154 var newInterface = callMethod('visitInterface', type);
155 if (newInterface) {
156 visitFields(newInterface);
157 }
158 return newInterface;
159 }
160 if (type instanceof graphql_1.GraphQLInputObjectType) {
161 var newInputObject_1 = callMethod('visitInputObject', type);
162 if (newInputObject_1) {
163 updateEachKey(newInputObject_1.getFields(), function (field) {
164 // Since we call a different method for input object fields, we
165 // can't reuse the visitFields function here.
166 return callMethod('visitInputFieldDefinition', field, {
167 objectType: newInputObject_1,
168 });
169 });
170 }
171 return newInputObject_1;
172 }
173 if (type instanceof graphql_1.GraphQLScalarType) {
174 return callMethod('visitScalar', type);
175 }
176 if (type instanceof graphql_1.GraphQLUnionType) {
177 return callMethod('visitUnion', type);
178 }
179 if (type instanceof graphql_1.GraphQLEnumType) {
180 var newEnum_1 = callMethod('visitEnum', type);
181 if (newEnum_1) {
182 updateEachKey(newEnum_1.getValues(), function (value) {
183 return callMethod('visitEnumValue', value, {
184 enumType: newEnum_1,
185 });
186 });
187 }
188 return newEnum_1;
189 }
190 throw new Error("Unexpected schema type: " + type);
191 }
192 function visitFields(type) {
193 updateEachKey(type.getFields(), function (field) {
194 // It would be nice if we could call visit(field) recursively here, but
195 // GraphQLField is merely a type, not a value that can be detected using
196 // an instanceof check, so we have to visit the fields in this lexical
197 // context, so that TypeScript can validate the call to
198 // visitFieldDefinition.
199 var newField = callMethod('visitFieldDefinition', field, {
200 // While any field visitor needs a reference to the field object, some
201 // field visitors may also need to know the enclosing (parent) type,
202 // perhaps to determine if the parent is a GraphQLObjectType or a
203 // GraphQLInterfaceType. To obtain a reference to the parent, a
204 // visitor method can have a second parameter, which will be an object
205 // with an .objectType property referring to the parent.
206 objectType: type,
207 });
208 if (newField && newField.args) {
209 updateEachKey(newField.args, function (arg) {
210 return callMethod('visitArgumentDefinition', arg, {
211 // Like visitFieldDefinition, visitArgumentDefinition takes a
212 // second parameter that provides additional context, namely the
213 // parent .field and grandparent .objectType. Remember that the
214 // current GraphQLSchema is always available via this.schema.
215 field: newField,
216 objectType: type,
217 });
218 });
219 }
220 return newField;
221 });
222 }
223 visit(schema);
224 // Return the original schema for convenience, even though it cannot have
225 // been replaced or removed by the code above.
226 return schema;
227}
228exports.visitSchema = visitSchema;
229// Update any references to named schema types that disagree with the named
230// types found in schema.getTypeMap().
231function healSchema(schema) {
232 heal(schema);
233 return schema;
234 function heal(type) {
235 if (type instanceof graphql_1.GraphQLSchema) {
236 var originalTypeMap_1 = type.getTypeMap();
237 var actualNamedTypeMap_1 = Object.create(null);
238 // If any of the .name properties of the GraphQLNamedType objects in
239 // schema.getTypeMap() have changed, the keys of the type map need to
240 // be updated accordingly.
241 each(originalTypeMap_1, function (namedType, typeName) {
242 if (typeName.startsWith('__')) {
243 return;
244 }
245 var actualName = namedType.name;
246 if (actualName.startsWith('__')) {
247 return;
248 }
249 if (hasOwn.call(actualNamedTypeMap_1, actualName)) {
250 throw new Error("Duplicate schema type name " + actualName);
251 }
252 actualNamedTypeMap_1[actualName] = namedType;
253 // Note: we are deliberately leaving namedType in the schema by its
254 // original name (which might be different from actualName), so that
255 // references by that name can be healed.
256 });
257 // Now add back every named type by its actual name.
258 each(actualNamedTypeMap_1, function (namedType, typeName) {
259 originalTypeMap_1[typeName] = namedType;
260 });
261 // Directive declaration argument types can refer to named types.
262 each(type.getDirectives(), function (decl) {
263 if (decl.args) {
264 each(decl.args, function (arg) {
265 arg.type = healType(arg.type);
266 });
267 }
268 });
269 each(originalTypeMap_1, function (namedType, typeName) {
270 if (!typeName.startsWith('__')) {
271 heal(namedType);
272 }
273 });
274 updateEachKey(originalTypeMap_1, function (namedType, typeName) {
275 // Dangling references to renamed types should remain in the schema
276 // during healing, but must be removed now, so that the following
277 // invariant holds for all names: schema.getType(name).name === name
278 if (!typeName.startsWith('__') &&
279 !hasOwn.call(actualNamedTypeMap_1, typeName)) {
280 return null;
281 }
282 });
283 }
284 else if (type instanceof graphql_1.GraphQLObjectType) {
285 healFields(type);
286 each(type.getInterfaces(), function (iface) { return heal(iface); });
287 }
288 else if (type instanceof graphql_1.GraphQLInterfaceType) {
289 healFields(type);
290 }
291 else if (type instanceof graphql_1.GraphQLInputObjectType) {
292 each(type.getFields(), function (field) {
293 field.type = healType(field.type);
294 });
295 }
296 else if (type instanceof graphql_1.GraphQLScalarType) {
297 // Nothing to do.
298 }
299 else if (type instanceof graphql_1.GraphQLUnionType) {
300 updateEachKey(type.getTypes(), function (t) { return healType(t); });
301 }
302 else if (type instanceof graphql_1.GraphQLEnumType) {
303 // Nothing to do.
304 }
305 else {
306 throw new Error("Unexpected schema type: " + type);
307 }
308 }
309 function healFields(type) {
310 each(type.getFields(), function (field) {
311 field.type = healType(field.type);
312 if (field.args) {
313 each(field.args, function (arg) {
314 arg.type = healType(arg.type);
315 });
316 }
317 });
318 }
319 function healType(type) {
320 // Unwrap the two known wrapper types
321 if (type instanceof graphql_1.GraphQLList) {
322 type = new graphql_1.GraphQLList(healType(type.ofType));
323 }
324 else if (type instanceof graphql_1.GraphQLNonNull) {
325 type = new graphql_1.GraphQLNonNull(healType(type.ofType));
326 }
327 else if (graphql_1.isNamedType(type)) {
328 // If a type annotation on a field or an argument or a union member is
329 // any `GraphQLNamedType` with a `name`, then it must end up identical
330 // to `schema.getType(name)`, since `schema.getTypeMap()` is the source
331 // of truth for all named schema types.
332 var namedType = type;
333 var officialType = schema.getType(namedType.name);
334 if (officialType && namedType !== officialType) {
335 return officialType;
336 }
337 }
338 return type;
339 }
340}
341exports.healSchema = healSchema;
342// This class represents a reusable implementation of a @directive that may
343// appear in a GraphQL schema written in Schema Definition Language.
344//
345// By overriding one or more visit{Object,Union,...} methods, a subclass
346// registers interest in certain schema types, such as GraphQLObjectType,
347// GraphQLUnionType, etc. When SchemaDirectiveVisitor.visitSchemaDirectives is
348// called with a GraphQLSchema object and a map of visitor subclasses, the
349// overidden methods of those subclasses allow the visitors to obtain
350// references to any type objects that have @directives attached to them,
351// enabling visitors to inspect or modify the schema as appropriate.
352//
353// For example, if a directive called @rest(url: "...") appears after a field
354// definition, a SchemaDirectiveVisitor subclass could provide meaning to that
355// directive by overriding the visitFieldDefinition method (which receives a
356// GraphQLField parameter), and then the body of that visitor method could
357// manipulate the field's resolver function to fetch data from a REST endpoint
358// described by the url argument passed to the @rest directive:
359//
360// const typeDefs = `
361// type Query {
362// people: [Person] @rest(url: "/api/v1/people")
363// }`;
364//
365// const schema = makeExecutableSchema({ typeDefs });
366//
367// SchemaDirectiveVisitor.visitSchemaDirectives(schema, {
368// rest: class extends SchemaDirectiveVisitor {
369// public visitFieldDefinition(field: GraphQLField<any, any>) {
370// const { url } = this.args;
371// field.resolve = () => fetch(url);
372// }
373// }
374// });
375//
376// The subclass in this example is defined as an anonymous class expression,
377// for brevity. A truly reusable SchemaDirectiveVisitor would most likely be
378// defined in a library using a named class declaration, and then exported for
379// consumption by other modules and packages.
380//
381// See below for a complete list of overridable visitor methods, their
382// parameter types, and more details about the properties exposed by instances
383// of the SchemaDirectiveVisitor class.
384var SchemaDirectiveVisitor = /** @class */ (function (_super) {
385 __extends(SchemaDirectiveVisitor, _super);
386 // Mark the constructor protected to enforce passing SchemaDirectiveVisitor
387 // subclasses (not instances) to visitSchemaDirectives.
388 function SchemaDirectiveVisitor(config) {
389 var _this = _super.call(this) || this;
390 _this.name = config.name;
391 _this.args = config.args;
392 _this.visitedType = config.visitedType;
393 _this.schema = config.schema;
394 _this.context = config.context;
395 return _this;
396 }
397 // Override this method to return a custom GraphQLDirective (or modify one
398 // already present in the schema) to enforce argument types, provide default
399 // argument values, or specify schema locations where this @directive may
400 // appear. By default, any declaration found in the schema will be returned.
401 SchemaDirectiveVisitor.getDirectiveDeclaration = function (directiveName, schema) {
402 return schema.getDirective(directiveName);
403 };
404 // Call SchemaDirectiveVisitor.visitSchemaDirectives to visit every
405 // @directive in the schema and create an appropriate SchemaDirectiveVisitor
406 // instance to visit the object decorated by the @directive.
407 SchemaDirectiveVisitor.visitSchemaDirectives = function (schema, directiveVisitors,
408 // Optional context object that will be available to all visitor instances
409 // via this.context. Defaults to an empty null-prototype object.
410 context) {
411 if (context === void 0) { context = Object.create(null); }
412 // If the schema declares any directives for public consumption, record
413 // them here so that we can properly coerce arguments when/if we encounter
414 // an occurrence of the directive while walking the schema below.
415 var declaredDirectives = this.getDeclaredDirectives(schema, directiveVisitors);
416 // Map from directive names to lists of SchemaDirectiveVisitor instances
417 // created while visiting the schema.
418 var createdVisitors = Object.create(null);
419 Object.keys(directiveVisitors).forEach(function (directiveName) {
420 createdVisitors[directiveName] = [];
421 });
422 function visitorSelector(type, methodName) {
423 var visitors = [];
424 var directiveNodes = type.astNode && type.astNode.directives;
425 if (!directiveNodes) {
426 return visitors;
427 }
428 directiveNodes.forEach(function (directiveNode) {
429 var directiveName = directiveNode.name.value;
430 if (!hasOwn.call(directiveVisitors, directiveName)) {
431 return;
432 }
433 var visitorClass = directiveVisitors[directiveName];
434 // Avoid creating visitor objects if visitorClass does not override
435 // the visitor method named by methodName.
436 if (!visitorClass.implementsVisitorMethod(methodName)) {
437 return;
438 }
439 var decl = declaredDirectives[directiveName];
440 var args;
441 if (decl) {
442 // If this directive was explicitly declared, use the declared
443 // argument types (and any default values) to check, coerce, and/or
444 // supply default values for the given arguments.
445 args = values_1.getArgumentValues(decl, directiveNode);
446 }
447 else {
448 // If this directive was not explicitly declared, just convert the
449 // argument nodes to their corresponding JavaScript values.
450 args = Object.create(null);
451 directiveNode.arguments.forEach(function (arg) {
452 args[arg.name.value] = valueFromASTUntyped(arg.value);
453 });
454 }
455 // As foretold in comments near the top of the visitSchemaDirectives
456 // method, this is where instances of the SchemaDirectiveVisitor class
457 // get created and assigned names. While subclasses could override the
458 // constructor method, the constructor is marked as protected, so
459 // these are the only arguments that will ever be passed.
460 visitors.push(new visitorClass({
461 name: directiveName,
462 args: args,
463 visitedType: type,
464 schema: schema,
465 context: context,
466 }));
467 });
468 if (visitors.length > 0) {
469 visitors.forEach(function (visitor) {
470 createdVisitors[visitor.name].push(visitor);
471 });
472 }
473 return visitors;
474 }
475 visitSchema(schema, visitorSelector);
476 // Automatically update any references to named schema types replaced
477 // during the traversal, so implementors don't have to worry about that.
478 healSchema(schema);
479 return createdVisitors;
480 };
481 SchemaDirectiveVisitor.getDeclaredDirectives = function (schema, directiveVisitors) {
482 var declaredDirectives = Object.create(null);
483 each(schema.getDirectives(), function (decl) {
484 declaredDirectives[decl.name] = decl;
485 });
486 // If the visitor subclass overrides getDirectiveDeclaration, and it
487 // returns a non-null GraphQLDirective, use that instead of any directive
488 // declared in the schema itself. Reasoning: if a SchemaDirectiveVisitor
489 // goes to the trouble of implementing getDirectiveDeclaration, it should
490 // be able to rely on that implementation.
491 each(directiveVisitors, function (visitorClass, directiveName) {
492 var decl = visitorClass.getDirectiveDeclaration(directiveName, schema);
493 if (decl) {
494 declaredDirectives[directiveName] = decl;
495 }
496 });
497 each(declaredDirectives, function (decl, name) {
498 if (!hasOwn.call(directiveVisitors, name)) {
499 // SchemaDirectiveVisitors.visitSchemaDirectives might be called
500 // multiple times with partial directiveVisitors maps, so it's not
501 // necessarily an error for directiveVisitors to be missing an
502 // implementation of a directive that was declared in the schema.
503 return;
504 }
505 var visitorClass = directiveVisitors[name];
506 each(decl.locations, function (loc) {
507 var visitorMethodName = directiveLocationToVisitorMethodName(loc);
508 if (SchemaVisitor.implementsVisitorMethod(visitorMethodName) &&
509 !visitorClass.implementsVisitorMethod(visitorMethodName)) {
510 // While visitor subclasses may implement extra visitor methods,
511 // it's definitely a mistake if the GraphQLDirective declares itself
512 // applicable to certain schema locations, and the visitor subclass
513 // does not implement all the corresponding methods.
514 throw new Error("SchemaDirectiveVisitor for @" + name + " must implement " + visitorMethodName + " method");
515 }
516 });
517 });
518 return declaredDirectives;
519 };
520 return SchemaDirectiveVisitor;
521}(SchemaVisitor));
522exports.SchemaDirectiveVisitor = SchemaDirectiveVisitor;
523// Convert a string like "FIELD_DEFINITION" to "visitFieldDefinition".
524function directiveLocationToVisitorMethodName(loc) {
525 return 'visit' + loc.replace(/([^_]*)_?/g, function (wholeMatch, part) {
526 return part.charAt(0).toUpperCase() + part.slice(1).toLowerCase();
527 });
528}
529function each(arrayOrObject, callback) {
530 Object.keys(arrayOrObject).forEach(function (key) {
531 callback(arrayOrObject[key], key);
532 });
533}
534// A more powerful version of each that has the ability to replace or remove
535// array or object keys.
536function updateEachKey(arrayOrObject,
537// The callback can return nothing to leave the key untouched, null to remove
538// the key from the array or object, or a non-null V to replace the value.
539callback) {
540 var deletedCount = 0;
541 Object.keys(arrayOrObject).forEach(function (key) {
542 var result = callback(arrayOrObject[key], key);
543 if (typeof result === 'undefined') {
544 return;
545 }
546 if (result === null) {
547 delete arrayOrObject[key];
548 deletedCount++;
549 return;
550 }
551 arrayOrObject[key] = result;
552 });
553 if (deletedCount > 0 && Array.isArray(arrayOrObject)) {
554 // Remove any holes from the array due to deleted elements.
555 arrayOrObject.splice(0).forEach(function (elem) {
556 arrayOrObject.push(elem);
557 });
558 }
559}
560// Similar to the graphql-js function of the same name, slightly simplified:
561// https://github.com/graphql/graphql-js/blob/master/src/utilities/valueFromASTUntyped.js
562function valueFromASTUntyped(valueNode) {
563 switch (valueNode.kind) {
564 case graphql_1.Kind.NULL:
565 return null;
566 case graphql_1.Kind.INT:
567 return parseInt(valueNode.value, 10);
568 case graphql_1.Kind.FLOAT:
569 return parseFloat(valueNode.value);
570 case graphql_1.Kind.STRING:
571 case graphql_1.Kind.ENUM:
572 case graphql_1.Kind.BOOLEAN:
573 return valueNode.value;
574 case graphql_1.Kind.LIST:
575 return valueNode.values.map(valueFromASTUntyped);
576 case graphql_1.Kind.OBJECT:
577 var obj_1 = Object.create(null);
578 valueNode.fields.forEach(function (field) {
579 obj_1[field.name.value] = valueFromASTUntyped(field.value);
580 });
581 return obj_1;
582 /* istanbul ignore next */
583 default:
584 throw new Error('Unexpected value kind: ' + valueNode.kind);
585 }
586}
587//# sourceMappingURL=schemaVisitor.js.map
\No newline at end of file