UNPKG

18.9 kBJavaScriptView Raw
1'use strict';
2
3function _interopDefault (ex) { return (ex && (typeof ex === 'object') && 'default' in ex) ? ex['default'] : ex; }
4
5const graphql = require('graphql');
6const pascalCase = require('pascal-case');
7const graphqlScalars = require('graphql-scalars');
8const urlJoin = _interopDefault(require('url-join'));
9const utils = require('@graphql-mesh/utils');
10const AggregateError = _interopDefault(require('aggregate-error'));
11const fetchache = require('fetchache');
12
13const asArray = (maybeArray) => {
14 if (Array.isArray(maybeArray)) {
15 return maybeArray;
16 }
17 else if (maybeArray) {
18 return [maybeArray];
19 }
20 else {
21 return [];
22 }
23};
24class JSONSchemaVisitorCache {
25 constructor() {
26 this.inputSpecificTypesByIdentifier = new Map();
27 this.outputSpecificTypesByIdentifier = new Map();
28 this.sharedTypesByIdentifier = new Map();
29 this.typesByNames = new Map();
30 this.prefixedNames = new Set();
31 this.potentialPrefixes = new WeakMap();
32 }
33}
34class JSONSchemaVisitor {
35 constructor(cache = new JSONSchemaVisitorCache()) {
36 this.cache = cache;
37 this.warnedReferences = new Set();
38 }
39 getNameFromId(id) {
40 const [actualTypePath, genericTypePart] = id.split('<');
41 const actualTypePathParts = actualTypePath.split(':');
42 const actualTypeName = actualTypePathParts[actualTypePathParts.length - 1];
43 let finalTypeName = actualTypeName;
44 if (genericTypePart) {
45 const [genericTypePath] = genericTypePart.split('>');
46 const genericTypeParts = genericTypePath.split(':');
47 const genericTypeName = genericTypeParts[genericTypeParts.length - 1];
48 finalTypeName = actualTypeName + '_' + genericTypeName;
49 }
50 return pascalCase.pascalCase(finalTypeName);
51 }
52 visit(def, propertyName, prefix, isInput) {
53 if ('definitions' in def) {
54 for (const propertyName in def.definitions) {
55 const definition = def.definitions[propertyName];
56 this.visit(definition, propertyName, prefix, isInput);
57 }
58 }
59 if ('$defs' in def) {
60 for (const propertyName in def.$defs) {
61 const definition = def.$defs[propertyName];
62 this.visit(definition, propertyName, prefix, isInput);
63 }
64 }
65 switch (def.type) {
66 case 'array':
67 return this.visitArray(def, propertyName, prefix, isInput);
68 case 'boolean':
69 return this.visitBoolean();
70 case 'integer':
71 return this.visitInteger();
72 case 'number':
73 return this.visitNumber();
74 case 'string':
75 if ('enum' in def) {
76 return this.visitEnum(def, propertyName, prefix);
77 }
78 else {
79 return this.visitString();
80 }
81 case 'null':
82 case 'any':
83 return this.visitAny();
84 case 'object':
85 if ('$ref' in def) {
86 return this.visitObjectReference(def, isInput);
87 }
88 else if ('name' in def || 'title' in def) {
89 return this.visitTypedNamedObjectDefinition(def, prefix, isInput);
90 }
91 else if ('properties' in def) {
92 return this.visitTypedUnnamedObjectDefinition(def, propertyName, prefix, isInput);
93 }
94 else if ('additionalProperties' in def && def.additionalProperties) {
95 return this.visitAny();
96 }
97 break;
98 }
99 throw new Error(`Unexpected schema definition:
100 ${JSON.stringify(def, null, 2)}`);
101 }
102 visitArray(arrayDef, propertyName, prefix, isInput) {
103 const [itemsDef] = asArray(arrayDef.items);
104 return new graphql.GraphQLList(itemsDef ? this.visit(itemsDef, propertyName, prefix, isInput) : this.visitAny());
105 }
106 visitBoolean() {
107 return graphql.GraphQLBoolean;
108 }
109 visitInteger() {
110 return graphql.GraphQLInt;
111 }
112 visitNumber() {
113 return graphql.GraphQLFloat;
114 }
115 visitString() {
116 return graphql.GraphQLString;
117 }
118 visitEnum(enumDef, propertyName, prefix) {
119 const enumIdentifier = JSON.stringify(enumDef);
120 if (!this.cache.sharedTypesByIdentifier.has(enumIdentifier)) {
121 // If there is a different enum but with the same name,
122 // just change the existing one's name and use prefixes from now on
123 let name = pascalCase.pascalCase(propertyName);
124 if (!this.cache.prefixedNames.has(name) && this.cache.typesByNames.has(name)) {
125 const existingType = this.cache.typesByNames.get(name);
126 if ('name' in existingType) {
127 const prefix = this.cache.potentialPrefixes.get(existingType);
128 existingType.name = pascalCase.pascalCase(prefix + '_' + name);
129 this.cache.prefixedNames.add(name);
130 this.cache.typesByNames.delete(name);
131 if (this.cache.typesByNames.has(existingType.name)) {
132 throw existingType.name;
133 }
134 this.cache.typesByNames.set(existingType.name, existingType);
135 }
136 }
137 if (this.cache.prefixedNames.has(name)) {
138 name = pascalCase.pascalCase(prefix + '_' + name);
139 }
140 const type = new graphql.GraphQLEnumType({
141 name,
142 description: enumDef.description,
143 values: enumDef.enum.reduce((values, enumValue) => ({ ...values, [enumValue]: {} }), {}),
144 });
145 this.cache.potentialPrefixes.set(type, prefix);
146 this.cache.sharedTypesByIdentifier.set(enumIdentifier, type);
147 if (this.cache.typesByNames.has(name)) {
148 throw name;
149 }
150 this.cache.typesByNames.set(name, type);
151 }
152 return this.cache.sharedTypesByIdentifier.get(enumIdentifier);
153 }
154 createFieldsMapFromProperties(objectDef, prefix, isInput) {
155 var _a;
156 const fieldMap = {};
157 for (const propertyName in objectDef.properties) {
158 const fieldName = propertyName.split(':').join('_');
159 const property = objectDef.properties[propertyName];
160 const type = this.visit(property, propertyName, prefix, isInput);
161 const isRequired = 'required' in objectDef && ((_a = objectDef.required) === null || _a === void 0 ? void 0 : _a.includes(propertyName));
162 fieldMap[fieldName] = {
163 type: isRequired ? new graphql.GraphQLNonNull(type) : type,
164 description: property.description,
165 };
166 if (fieldName !== propertyName) {
167 fieldMap[fieldName].resolve = (root) => root[propertyName];
168 }
169 }
170 return fieldMap;
171 }
172 getSpecificTypeByIdentifier(identifier, isInput) {
173 return (this.cache.sharedTypesByIdentifier.get(identifier) ||
174 (isInput
175 ? this.cache.inputSpecificTypesByIdentifier.get(identifier)
176 : this.cache.outputSpecificTypesByIdentifier.get(identifier)));
177 }
178 getGraphQLObjectTypeWithTypedObjectDef(objectDef, objectIdentifier, rawName, prefix, isInput) {
179 const specificType = this.getSpecificTypeByIdentifier(objectIdentifier, isInput);
180 if (!specificType) {
181 let name = rawName;
182 // If there is a different object but with the same name,
183 // just change the existing one's name and use prefixes from now on
184 if (!this.cache.prefixedNames.has(name) && this.cache.typesByNames.has(name)) {
185 const existingType = this.cache.typesByNames.get(name);
186 if ('name' in existingType) {
187 const existingTypePrefix = this.cache.potentialPrefixes.get(existingType);
188 existingType.name = pascalCase.pascalCase(existingTypePrefix + '_' + name);
189 this.cache.prefixedNames.add(name);
190 this.cache.typesByNames.delete(name);
191 if (this.cache.typesByNames.has(existingType.name)) {
192 throw existingType.name;
193 }
194 this.cache.typesByNames.set(existingType.name, existingType);
195 }
196 }
197 // If this name should be prefixed
198 if (this.cache.prefixedNames.has(name)) {
199 name = pascalCase.pascalCase(prefix + '_' + name);
200 }
201 if (this.cache.typesByNames.has(name)) {
202 const suffix = isInput ? 'Input' : 'Output';
203 name = pascalCase.pascalCase(name + '_' + suffix);
204 }
205 if (isInput) {
206 const inputType = new graphql.GraphQLInputObjectType({
207 name,
208 description: objectDef.description,
209 fields: this.createFieldsMapFromProperties(objectDef, name, true),
210 });
211 this.cache.inputSpecificTypesByIdentifier.set(objectIdentifier, inputType);
212 this.cache.typesByNames.set(inputType.name, inputType);
213 this.cache.potentialPrefixes.set(inputType, prefix);
214 return inputType;
215 }
216 else {
217 const outputType = new graphql.GraphQLObjectType({
218 name,
219 description: objectDef.description,
220 fields: this.createFieldsMapFromProperties(objectDef, name, false),
221 });
222 this.cache.outputSpecificTypesByIdentifier.set(objectIdentifier, outputType);
223 this.cache.typesByNames.set(outputType.name, outputType);
224 this.cache.potentialPrefixes.set(outputType, prefix);
225 return outputType;
226 }
227 }
228 return specificType;
229 }
230 visitTypedUnnamedObjectDefinition(typedUnnamedObjectDef, propertyName, prefix, isInput) {
231 const objectIdentifier = 'id' in typedUnnamedObjectDef
232 ? typedUnnamedObjectDef.id
233 : '$id' in typedUnnamedObjectDef
234 ? typedUnnamedObjectDef.$id
235 : `${prefix}_${propertyName}`;
236 const name = this.getNameFromId(objectIdentifier);
237 return this.getGraphQLObjectTypeWithTypedObjectDef(typedUnnamedObjectDef, objectIdentifier, name, prefix, isInput);
238 }
239 visitTypedNamedObjectDefinition(typedNamedObjectDef, prefix, isInput) {
240 const objectIdentifier = 'name' in typedNamedObjectDef ? typedNamedObjectDef.name : typedNamedObjectDef.title;
241 const name = pascalCase.pascalCase(objectIdentifier);
242 return this.getGraphQLObjectTypeWithTypedObjectDef(typedNamedObjectDef, objectIdentifier, name, prefix, isInput);
243 }
244 visitObjectReference(objectRef, isInput) {
245 const referenceParts = objectRef.$ref.split('/');
246 const reference = referenceParts[referenceParts.length - 1];
247 const specificType = this.getSpecificTypeByIdentifier(reference, isInput);
248 if (!specificType) {
249 if (!this.warnedReferences.has(reference) && !this.warnedReferences.has(reference)) {
250 console.warn(`Missing JSON Schema reference: ${reference}. GraphQLJSON will be used instead!`);
251 this.warnedReferences.add(reference);
252 }
253 return this.visitAny();
254 }
255 return specificType;
256 }
257 visitAny() {
258 return graphqlScalars.JSONResolver;
259 }
260}
261
262const handler = {
263 async getMeshSource({ config, cache }) {
264 var _a, _b;
265 const visitorCache = new JSONSchemaVisitorCache();
266 await Promise.all(((_a = config.typeReferences) === null || _a === void 0 ? void 0 : _a.map(typeReference => Promise.all(Object.keys(typeReference).map(async (key) => {
267 switch (key) {
268 case 'sharedType': {
269 const sharedType = await utils.loadFromModuleExportExpression(typeReference.sharedType);
270 visitorCache.sharedTypesByIdentifier.set(typeReference.reference, sharedType);
271 break;
272 }
273 case 'outputType': {
274 const outputType = await utils.loadFromModuleExportExpression(typeReference.outputType);
275 visitorCache.outputSpecificTypesByIdentifier.set(typeReference.reference, outputType);
276 break;
277 }
278 case 'inputType': {
279 const inputType = await utils.loadFromModuleExportExpression(typeReference.inputType);
280 visitorCache.inputSpecificTypesByIdentifier.set(typeReference.reference, inputType);
281 break;
282 }
283 case 'reference':
284 break;
285 default:
286 throw new Error(`Unexpected type reference field: ${key}`);
287 }
288 })))) || []);
289 const queryFields = {};
290 const mutationFields = {};
291 const schemaVisitor = new JSONSchemaVisitor(visitorCache);
292 const contextVariables = [];
293 await Promise.all(((_b = config.operations) === null || _b === void 0 ? void 0 : _b.map(async (operationConfig) => {
294 const [requestSchema, responseSchema] = await Promise.all([
295 operationConfig.requestSchema
296 ? utils.readFileOrUrlWithCache(operationConfig.requestSchema, cache, {
297 headers: config.schemaHeaders,
298 })
299 : undefined,
300 utils.readFileOrUrlWithCache(operationConfig.responseSchema, cache, {
301 headers: config.schemaHeaders,
302 }),
303 ]);
304 operationConfig.method = operationConfig.method || (operationConfig.type === 'Mutation' ? 'POST' : 'GET');
305 operationConfig.type = operationConfig.type || (operationConfig.method === 'GET' ? 'Query' : 'Mutation');
306 const destination = operationConfig.type === 'Query' ? queryFields : mutationFields;
307 const type = schemaVisitor.visit(responseSchema, 'Response', operationConfig.field, false);
308 const { args, contextVariables: specificContextVariables } = utils.parseInterpolationStrings([
309 ...Object.values(config.operationHeaders || {}),
310 ...Object.values(operationConfig.headers || {}),
311 operationConfig.path,
312 ]);
313 contextVariables.push(...specificContextVariables);
314 if (requestSchema) {
315 args.input = {
316 type: schemaVisitor.visit(requestSchema, 'Request', operationConfig.field, true),
317 };
318 }
319 destination[operationConfig.field] = {
320 description: operationConfig.description ||
321 responseSchema.description ||
322 `${operationConfig.method} ${operationConfig.path}`,
323 type,
324 args,
325 resolve: async (root, args, context, info) => {
326 const interpolationData = { root, args, context, info };
327 const interpolatedPath = utils.stringInterpolator.parse(operationConfig.path, interpolationData);
328 const fullPath = urlJoin(config.baseUrl, interpolatedPath);
329 const method = operationConfig.method;
330 const headers = {
331 ...config.operationHeaders,
332 ...operationConfig === null || operationConfig === void 0 ? void 0 : operationConfig.headers,
333 };
334 for (const headerName in headers) {
335 headers[headerName] = utils.stringInterpolator.parse(headers[headerName], interpolationData);
336 }
337 const requestInit = {
338 method,
339 headers,
340 };
341 const urlObj = new URL(fullPath);
342 const input = args.input;
343 if (input) {
344 switch (method) {
345 case 'GET':
346 case 'DELETE': {
347 const newSearchParams = new URLSearchParams(input);
348 newSearchParams.forEach((value, key) => {
349 urlObj.searchParams.set(key, value);
350 });
351 break;
352 }
353 case 'POST':
354 case 'PUT': {
355 requestInit.body = JSON.stringify(input);
356 break;
357 }
358 default:
359 throw new Error(`Unknown method ${operationConfig.method}`);
360 }
361 }
362 const request = new fetchache.Request(urlObj.toString(), requestInit);
363 const response = await fetchache.fetchache(request, cache);
364 const responseText = await response.text();
365 let responseJson;
366 try {
367 responseJson = JSON.parse(responseText);
368 }
369 catch (e) {
370 throw responseText;
371 }
372 if (responseJson.errors) {
373 throw new AggregateError(responseJson.errors);
374 }
375 if (responseJson._errors) {
376 throw new AggregateError(responseJson._errors);
377 }
378 if (responseJson.error) {
379 throw responseJson.error;
380 }
381 return responseJson;
382 },
383 };
384 })) || []);
385 const schema = new graphql.GraphQLSchema({
386 query: new graphql.GraphQLObjectType({
387 name: 'Query',
388 fields: Object.keys(queryFields).length > 0 ? queryFields : { ping: { type: graphql.GraphQLBoolean, resolve: () => true } },
389 }),
390 mutation: Object.keys(mutationFields).length > 0
391 ? new graphql.GraphQLObjectType({
392 name: 'Mutation',
393 fields: mutationFields,
394 })
395 : undefined,
396 });
397 return {
398 schema,
399 contextVariables,
400 };
401 },
402};
403
404module.exports = handler;
405//# sourceMappingURL=index.cjs.js.map