UNPKG

11.9 kBJavaScriptView Raw
1'use strict';
2
3Object.defineProperty(exports, "__esModule", {
4 value: true
5});
6exports.extendSchema = extendSchema;
7
8var _invariant = require('../jsutils/invariant');
9
10var _invariant2 = _interopRequireDefault(_invariant);
11
12var _keyMap = require('../jsutils/keyMap');
13
14var _keyMap2 = _interopRequireDefault(_keyMap);
15
16var _buildASTSchema = require('./buildASTSchema');
17
18var _GraphQLError = require('../error/GraphQLError');
19
20var _schema = require('../type/schema');
21
22var _definition = require('../type/definition');
23
24var _wrappers = require('../type/wrappers');
25
26var _directives = require('../type/directives');
27
28var _kinds = require('../language/kinds');
29
30var Kind = _interopRequireWildcard(_kinds);
31
32function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj.default = obj; return newObj; } }
33
34function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
35
36/**
37 * Produces a new schema given an existing schema and a document which may
38 * contain GraphQL type extensions and definitions. The original schema will
39 * remain unaltered.
40 *
41 * Because a schema represents a graph of references, a schema cannot be
42 * extended without effectively making an entire copy. We do not know until it's
43 * too late if subgraphs remain unchanged.
44 *
45 * This algorithm copies the provided schema, applying extensions while
46 * producing the copy. The original schema remains unaltered.
47 *
48 * Accepts options as a third argument:
49 *
50 * - commentDescriptions:
51 * Provide true to use preceding comments as the description.
52 *
53 */
54function extendSchema(schema, documentAST, options) {
55 !(0, _schema.isSchema)(schema) ? (0, _invariant2.default)(0, 'Must provide valid GraphQLSchema') : void 0;
56
57 !(documentAST && documentAST.kind === Kind.DOCUMENT) ? (0, _invariant2.default)(0, 'Must provide valid Document AST') : void 0;
58
59 // Collect the type definitions and extensions found in the document.
60 var typeDefinitionMap = Object.create(null);
61 var typeExtensionsMap = Object.create(null);
62
63 // New directives and types are separate because a directives and types can
64 // have the same name. For example, a type named "skip".
65 var directiveDefinitions = [];
66
67 for (var i = 0; i < documentAST.definitions.length; i++) {
68 var def = documentAST.definitions[i];
69 switch (def.kind) {
70 case Kind.OBJECT_TYPE_DEFINITION:
71 case Kind.INTERFACE_TYPE_DEFINITION:
72 case Kind.ENUM_TYPE_DEFINITION:
73 case Kind.UNION_TYPE_DEFINITION:
74 case Kind.SCALAR_TYPE_DEFINITION:
75 case Kind.INPUT_OBJECT_TYPE_DEFINITION:
76 // Sanity check that none of the defined types conflict with the
77 // schema's existing types.
78 var typeName = def.name.value;
79 if (schema.getType(typeName)) {
80 throw new _GraphQLError.GraphQLError('Type "' + typeName + '" already exists in the schema. It cannot also ' + 'be defined in this type definition.', [def]);
81 }
82 typeDefinitionMap[typeName] = def;
83 break;
84 case Kind.OBJECT_TYPE_EXTENSION:
85 // Sanity check that this type extension exists within the
86 // schema's existing types.
87 var extendedTypeName = def.name.value;
88 var existingType = schema.getType(extendedTypeName);
89 if (!existingType) {
90 throw new _GraphQLError.GraphQLError('Cannot extend type "' + extendedTypeName + '" because it does not ' + 'exist in the existing schema.', [def]);
91 }
92 if (!(0, _definition.isObjectType)(existingType)) {
93 throw new _GraphQLError.GraphQLError('Cannot extend non-object type "' + extendedTypeName + '".', [def]);
94 }
95 var extensions = typeExtensionsMap[extendedTypeName];
96 if (extensions) {
97 extensions.push(def);
98 } else {
99 extensions = [def];
100 }
101 typeExtensionsMap[extendedTypeName] = extensions;
102 break;
103 case Kind.DIRECTIVE_DEFINITION:
104 var directiveName = def.name.value;
105 var existingDirective = schema.getDirective(directiveName);
106 if (existingDirective) {
107 throw new _GraphQLError.GraphQLError('Directive "' + directiveName + '" already exists in the schema. It ' + 'cannot be redefined.', [def]);
108 }
109 directiveDefinitions.push(def);
110 break;
111 case Kind.SCALAR_TYPE_EXTENSION:
112 case Kind.INTERFACE_TYPE_EXTENSION:
113 case Kind.UNION_TYPE_EXTENSION:
114 case Kind.ENUM_TYPE_EXTENSION:
115 case Kind.INPUT_OBJECT_TYPE_EXTENSION:
116 throw new Error('The ' + def.kind + ' kind is not yet supported by extendSchema().');
117 }
118 }
119
120 // If this document contains no new types, extensions, or directives then
121 // return the same unmodified GraphQLSchema instance.
122 if (Object.keys(typeExtensionsMap).length === 0 && Object.keys(typeDefinitionMap).length === 0 && directiveDefinitions.length === 0) {
123 return schema;
124 }
125
126 var definitionBuilder = new _buildASTSchema.ASTDefinitionBuilder(typeDefinitionMap, options, function (typeName, node) {
127 var existingType = schema.getType(typeName);
128 if (existingType) {
129 return extendType(existingType);
130 }
131
132 if (node) {
133 throw new _GraphQLError.GraphQLError('Unknown type: "' + typeName + '". Ensure that this type exists ' + 'either in the original schema, or is added in a type definition.', [node]);
134 }
135 throw (0, _GraphQLError.GraphQLError)('Missing type from schema');
136 });
137
138 // Get the root Query, Mutation, and Subscription object types.
139 // Note: While this could make early assertions to get the correctly
140 // typed values below, that would throw immediately while type system
141 // validation with validateSchema() will produce more actionable results.
142 var existingQueryType = schema.getQueryType();
143 var queryType = existingQueryType ? definitionBuilder.buildType(existingQueryType.name) : null;
144
145 var existingMutationType = schema.getMutationType();
146 var mutationType = existingMutationType ? definitionBuilder.buildType(existingMutationType.name) : null;
147
148 var existingSubscriptionType = schema.getSubscriptionType();
149 var subscriptionType = existingSubscriptionType ? definitionBuilder.buildType(existingSubscriptionType.name) : null;
150
151 // Iterate through all types, getting the type definition for each, ensuring
152 // that any type not directly referenced by a field will get created.
153 var typeMap = schema.getTypeMap();
154 var types = Object.keys(typeMap).map(function (typeName) {
155 return definitionBuilder.buildType(typeName);
156 });
157
158 // Do the same with new types, appending to the list of defined types.
159 Object.keys(typeDefinitionMap).forEach(function (typeName) {
160 types.push(definitionBuilder.buildType(typeName));
161 });
162
163 // Then produce and return a Schema with these types.
164 return new _schema.GraphQLSchema({
165 query: queryType,
166 mutation: mutationType,
167 subscription: subscriptionType,
168 types: types,
169 directives: getMergedDirectives(),
170 astNode: schema.astNode
171 });
172
173 // Below are functions used for producing this schema that have closed over
174 // this scope and have access to the schema, cache, and newly defined types.
175
176 function getMergedDirectives() {
177 var existingDirectives = schema.getDirectives();
178 !existingDirectives ? (0, _invariant2.default)(0, 'schema must have default directives') : void 0;
179
180 var newDirectives = directiveDefinitions.map(function (directiveNode) {
181 return definitionBuilder.buildDirective(directiveNode);
182 });
183 return existingDirectives.concat(newDirectives);
184 }
185
186 function getTypeFromDef(typeDef) {
187 var type = definitionBuilder.buildType(typeDef.name);
188 return type;
189 }
190
191 // Given a type's introspection result, construct the correct
192 // GraphQLType instance.
193 function extendType(type) {
194 if ((0, _definition.isObjectType)(type)) {
195 return extendObjectType(type);
196 }
197 if ((0, _definition.isInterfaceType)(type)) {
198 return extendInterfaceType(type);
199 }
200 if ((0, _definition.isUnionType)(type)) {
201 return extendUnionType(type);
202 }
203 return type;
204 }
205
206 function extendObjectType(type) {
207 var name = type.name;
208 var extensionASTNodes = typeExtensionsMap[name] ? type.extensionASTNodes ? type.extensionASTNodes.concat(typeExtensionsMap[name]) : typeExtensionsMap[name] : type.extensionASTNodes;
209 return new _definition.GraphQLObjectType({
210 name: name,
211 description: type.description,
212 interfaces: function interfaces() {
213 return extendImplementedInterfaces(type);
214 },
215 fields: function fields() {
216 return extendFieldMap(type);
217 },
218 astNode: type.astNode,
219 extensionASTNodes: extensionASTNodes,
220 isTypeOf: type.isTypeOf
221 });
222 }
223
224 function extendInterfaceType(type) {
225 return new _definition.GraphQLInterfaceType({
226 name: type.name,
227 description: type.description,
228 fields: function fields() {
229 return extendFieldMap(type);
230 },
231 astNode: type.astNode,
232 resolveType: type.resolveType
233 });
234 }
235
236 function extendUnionType(type) {
237 return new _definition.GraphQLUnionType({
238 name: type.name,
239 description: type.description,
240 types: type.getTypes().map(getTypeFromDef),
241 astNode: type.astNode,
242 resolveType: type.resolveType
243 });
244 }
245
246 function extendImplementedInterfaces(type) {
247 var interfaces = type.getInterfaces().map(getTypeFromDef);
248
249 // If there are any extensions to the interfaces, apply those here.
250 var extensions = typeExtensionsMap[type.name];
251 if (extensions) {
252 extensions.forEach(function (extension) {
253 extension.interfaces.forEach(function (namedType) {
254 // Note: While this could make early assertions to get the correctly
255 // typed values, that would throw immediately while type system
256 // validation with validateSchema() will produce more actionable results.
257 interfaces.push(definitionBuilder.buildType(namedType));
258 });
259 });
260 }
261
262 return interfaces;
263 }
264
265 function extendFieldMap(type) {
266 var newFieldMap = Object.create(null);
267 var oldFieldMap = type.getFields();
268 Object.keys(oldFieldMap).forEach(function (fieldName) {
269 var field = oldFieldMap[fieldName];
270 newFieldMap[fieldName] = {
271 description: field.description,
272 deprecationReason: field.deprecationReason,
273 type: extendFieldType(field.type),
274 args: (0, _keyMap2.default)(field.args, function (arg) {
275 return arg.name;
276 }),
277 astNode: field.astNode,
278 resolve: field.resolve
279 };
280 });
281
282 // If there are any extensions to the fields, apply those here.
283 var extensions = typeExtensionsMap[type.name];
284 if (extensions) {
285 extensions.forEach(function (extension) {
286 extension.fields.forEach(function (field) {
287 var fieldName = field.name.value;
288 if (oldFieldMap[fieldName]) {
289 throw new _GraphQLError.GraphQLError('Field "' + type.name + '.' + fieldName + '" already exists in the ' + 'schema. It cannot also be defined in this type extension.', [field]);
290 }
291 newFieldMap[fieldName] = definitionBuilder.buildField(field);
292 });
293 });
294 }
295
296 return newFieldMap;
297 }
298
299 function extendFieldType(typeDef) {
300 if ((0, _definition.isListType)(typeDef)) {
301 return (0, _wrappers.GraphQLList)(extendFieldType(typeDef.ofType));
302 }
303 if ((0, _definition.isNonNullType)(typeDef)) {
304 return (0, _wrappers.GraphQLNonNull)(extendFieldType(typeDef.ofType));
305 }
306 return getTypeFromDef(typeDef);
307 }
308} /**
309 * Copyright (c) 2015-present, Facebook, Inc.
310 *
311 * This source code is licensed under the MIT license found in the
312 * LICENSE file in the root directory of this source tree.
313 *
314 *
315 */
\No newline at end of file