1 | 'use strict';
|
2 |
|
3 | function _interopDefault (ex) { return (ex && (typeof ex === 'object') && 'default' in ex) ? ex['default'] : ex; }
|
4 |
|
5 | const graphql = require('graphql');
|
6 | const pascalCase = require('pascal-case');
|
7 | const graphqlScalars = require('graphql-scalars');
|
8 | const urlJoin = _interopDefault(require('url-join'));
|
9 | const utils = require('@graphql-mesh/utils');
|
10 | const AggregateError = _interopDefault(require('aggregate-error'));
|
11 | const fetchache = require('fetchache');
|
12 |
|
13 | const 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 | };
|
24 | class 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 | }
|
34 | class 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 |
|
122 |
|
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 |
|
183 |
|
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 |
|
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 |
|
262 | const 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 |
|
404 | module.exports = handler;
|
405 |
|