UNPKG

24 kBJavaScriptView Raw
1/**
2 * Copyright (c) 2016, John Hewson
3 * All rights reserved.
4 */
5"use strict";
6/// <reference path="../typings/node.d.ts" />
7/// <reference path="../typings/es6-function.d.ts" />
8/// <reference path="../typings/graphql-types.d.ts" />
9/// <reference path="../typings/graphql-language.d.ts" />
10/// <reference path="../typings/graphql-utilities.d.ts" />
11var language_1 = require("graphql/language");
12var elm_ast_1 = require('./elm-ast');
13var type_1 = require('graphql/type');
14var utilities_1 = require('graphql/utilities');
15var query_to_decoder_1 = require('./query-to-decoder');
16var alphabet = ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p',
17 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z'];
18function queryToElm(graphql, moduleName, liveUrl, verb, schema) {
19 var queryDocument = language_1.parse(graphql);
20 var _a = translateQuery(liveUrl, queryDocument, schema, verb), decls = _a[0], expose = _a[1];
21 return elm_ast_1.moduleToString(moduleName, expose, [
22 'Task exposing (Task)',
23 'Json.Decode.Extra exposing ((|:))',
24 'Json.Decode exposing (..)',
25 'Json.Encode exposing (encode)',
26 'Http',
27 'GraphQL.Client as GraphQL exposing (Context, apply, maybeEncode, GQLError, HttpMapper)'
28 ], decls);
29}
30exports.queryToElm = queryToElm;
31function translateQuery(uri, doc, schema, verb) {
32 var expose = [];
33 var fragmentDefinitionMap = {};
34 function walkQueryDocument(doc, info) {
35 var decls = [];
36 // decls.push(new ElmFunctionDecl('endpointUrl', [], new ElmTypeName('String'), { expr: `"${uri}"` }));
37 buildFragmentDefinitionMap(doc);
38 var seenFragments = {};
39 var seenEnums = {};
40 var seenUnions = {};
41 for (var _i = 0, _a = doc.definitions; _i < _a.length; _i++) {
42 var def = _a[_i];
43 if (def.kind == 'OperationDefinition') {
44 decls.push.apply(decls, walkOperationDefinition(def, info));
45 }
46 else if (def.kind == 'FragmentDefinition') {
47 decls.push.apply(decls, walkFragmentDefinition(def, info));
48 }
49 collectFragments(def, seenFragments);
50 collectEnums(def, seenEnums);
51 collectUnions(def, seenUnions);
52 }
53 for (var fragName in seenFragments) {
54 var frag = seenFragments[fragName];
55 var decodeFragFuncName = fragName[0].toLowerCase() + fragName.substr(1) + 'Decoder';
56 var fragTypeName = fragName[0].toUpperCase() + fragName.substr(1);
57 decls.push(new elm_ast_1.ElmFunctionDecl(decodeFragFuncName, [], new elm_ast_1.ElmTypeName('Decoder ' + fragTypeName), query_to_decoder_1.decoderForFragment(frag, info, schema, fragmentDefinitionMap, seenFragments)));
58 expose.push(decodeFragFuncName);
59 expose.push(fragTypeName);
60 }
61 for (var name_1 in seenEnums) {
62 var seenEnum = seenEnums[name_1];
63 decls.unshift(walkEnum(seenEnum));
64 decls.push(decoderForEnum(seenEnum));
65 expose.push(seenEnum.name + "(..)");
66 }
67 for (var name_2 in seenUnions) {
68 var union = seenUnions[name_2];
69 decls.unshift(walkUnion(union));
70 expose.push((getRootType(union)['name'] + "(..)"));
71 }
72 return [decls, expose];
73 }
74 function buildFragmentDefinitionMap(doc) {
75 language_1.visit(doc, {
76 enter: function (node) {
77 if (node.kind == 'FragmentDefinition') {
78 var def = node;
79 var name_3 = def.name.value;
80 fragmentDefinitionMap[name_3] = def;
81 }
82 },
83 leave: function (node) { }
84 });
85 }
86 function collectFragments(def, fragments) {
87 if (fragments === void 0) { fragments = {}; }
88 language_1.visit(doc, {
89 enter: function (node) {
90 if (node.kind == 'FragmentSpread') {
91 var spread = node;
92 var name_4 = spread.name.value;
93 fragments[name_4] = fragmentDefinitionMap[name_4];
94 }
95 },
96 leave: function (node) { }
97 });
98 return fragments;
99 }
100 // Retrieve fragments used in the specified query definition selection set
101 function queryFragments(selectionSet, fragments) {
102 if (fragments === void 0) { fragments = {}; }
103 if (selectionSet) {
104 language_1.visit(selectionSet, {
105 enter: function (node) {
106 if (node.kind == 'FragmentSpread') {
107 var spread = node;
108 var name_5 = spread.name.value;
109 var frag = fragments[name_5] = fragmentDefinitionMap[name_5];
110 fragments = queryFragments(frag.selectionSet, fragments);
111 }
112 },
113 leave: function (node) {
114 }
115 });
116 }
117 return fragments;
118 }
119 function collectUnions(def, unions) {
120 if (unions === void 0) { unions = {}; }
121 var info = new utilities_1.TypeInfo(schema);
122 language_1.visit(doc, {
123 enter: function (node, key, parent) {
124 if (node.kind == 'InlineFragment') {
125 var parentType = info.getType();
126 unions[parentType.name] = parentType;
127 }
128 info.enter(node);
129 },
130 leave: function (node) {
131 info.leave(node);
132 }
133 });
134 return unions;
135 }
136 function collectEnums(def, enums) {
137 if (enums === void 0) { enums = {}; }
138 var info = new utilities_1.TypeInfo(schema);
139 language_1.visit(doc, {
140 enter: function (node, key, parent) {
141 info.enter(node);
142 if (node.kind == 'Field') {
143 var field = node;
144 var name_6 = field.name.value;
145 var type = info.getType();
146 collectEnumsForType(type, enums);
147 }
148 // todo: do we need to walk into fragment spreads?
149 },
150 leave: function (node, key, parent) {
151 info.leave(node);
152 }
153 });
154 return enums;
155 }
156 function collectEnumsForType(type, seen, seenTypes) {
157 if (seen === void 0) { seen = {}; }
158 if (seenTypes === void 0) { seenTypes = {}; }
159 if (type instanceof type_1.GraphQLEnumType) {
160 seen[type.name] = type;
161 }
162 else if (type instanceof type_1.GraphQLList) {
163 collectEnumsForType(type.ofType, seen, seenTypes);
164 }
165 else if (type instanceof type_1.GraphQLObjectType ||
166 type instanceof type_1.GraphQLInterfaceType ||
167 type instanceof type_1.GraphQLInputObjectType) {
168 if (seenTypes[type.name]) {
169 return;
170 }
171 else {
172 seenTypes[type.name] = type;
173 }
174 var fieldMap = type.getFields();
175 for (var fieldName in fieldMap) {
176 var field = fieldMap[fieldName];
177 collectEnumsForType(field.type, seen, seenTypes);
178 }
179 }
180 else if (type instanceof type_1.GraphQLNonNull) {
181 collectEnumsForType(type.ofType, seen, seenTypes);
182 }
183 }
184 function walkEnum(enumType) {
185 console.log(enumType.getValues());
186 return new elm_ast_1.ElmTypeDecl(enumType.name, enumType.getValues().map(function (v) { return v.name[0].toUpperCase() + v.name.substr(1); }));
187 }
188 function decoderForEnum(enumType) {
189 // might need to be Maybe Episode, with None -> fail in the Decoder
190 var decoderTypeName = enumType.name[0].toUpperCase() + enumType.name.substr(1);
191 return new elm_ast_1.ElmFunctionDecl(enumType.name.toLowerCase() + 'Decoder', [], new elm_ast_1.ElmTypeName('Decoder ' + decoderTypeName), { expr: 'string |> andThen (\\s ->\n' +
192 ' case s of\n' + enumType.getValues().map(function (v) {
193 return ' "' + v.name + '" -> succeed ' + v.name[0].toUpperCase() + v.name.substr(1);
194 }).join('\n') + '\n' +
195 ' _ -> fail "Unknown ' + enumType.name + '")'
196 });
197 }
198 function walkUnion(union) {
199 union = getRootType(union);
200 var types = union.getTypes();
201 var params = types.map(function (t, i) { return alphabet[i]; }).join(' ');
202 return new elm_ast_1.ElmTypeDecl(getRootType(union)['name'] + ' ' + params, types.map(function (t, i) { return elmSafeName(t.name) + ' ' + alphabet[i]; }));
203 }
204 function walkOperationDefinition(def, info) {
205 info.enter(def);
206 if (!info.getType()) {
207 throw new Error("GraphQL schema does not define " + def.operation + " '" + def.name.value + "'");
208 }
209 if (def.operation == 'query' || def.operation == 'mutation') {
210 var decls = [];
211 // Name
212 var name_7;
213 if (def.name) {
214 name_7 = def.name.value;
215 }
216 else {
217 name_7 = 'AnonymousQuery';
218 }
219 var resultType = name_7[0].toUpperCase() + name_7.substr(1);
220 // todo: Directives
221 // SelectionSet
222 var _a = walkSelectionSet(def.selectionSet, info), fields = _a[0], spreads = _a[1];
223 // todo: use spreads...
224 decls.push(new elm_ast_1.ElmTypeAliasDecl(resultType, new elm_ast_1.ElmTypeRecord(fields)));
225 // VariableDefinition
226 var parameters = [];
227 if (def.variableDefinitions) {
228 for (var _i = 0, _b = def.variableDefinitions; _i < _b.length; _i++) {
229 var varDef = _b[_i];
230 var name_8 = varDef.variable.name.value;
231 var schemaType = utilities_1.typeFromAST(schema, varDef.type);
232 var type = typeToElm(schemaType);
233 parameters.push({ name: name_8, type: type, schemaType: schemaType, hasDefault: varDef.defaultValue != null });
234 }
235 }
236 var funcName = name_7[0].toLowerCase() + name_7.substr(1);
237 // grabs all fragments
238 var seenFragments = collectFragments(def);
239 // grabs all fragment dependencies in the query
240 var qFragments = queryFragments(def.selectionSet);
241 var query = '';
242 for (var name_9 in qFragments) {
243 query += language_1.print(qFragments[name_9]) + ' ';
244 }
245 query += language_1.print(def);
246 var decodeFuncName = resultType[0].toLowerCase() + resultType.substr(1) + 'Decoder';
247 expose.push(funcName);
248 expose.push(resultType);
249 var resultTypeName = resultType[0].toUpperCase() + resultType.substr(1);
250 var elmContextType = new elm_ast_1.ElmTypeName("Context");
251 var elmMapperType = new elm_ast_1.ElmTypeName("HttpMapper success result");
252 var elmToMsgType = new elm_ast_1.ElmTypeName("(result -> msg)");
253 var elmToResultMapType = new elm_ast_1.ElmTypeName("(" + resultTypeName + " -> success)");
254 var elmParamsType = new elm_ast_1.ElmTypeRecord(parameters.map(function (p) { return new elm_ast_1.ElmFieldDecl(p.name, p.type); }));
255 var elmContext = new elm_ast_1.ElmParameterDecl('context', elmContextType);
256 var elmMapper = new elm_ast_1.ElmParameterDecl('mapper', elmMapperType);
257 var elmToMsg = new elm_ast_1.ElmParameterDecl('toMsg', elmToMsgType);
258 var elmToResultMap = new elm_ast_1.ElmParameterDecl('mapDecoder', elmToResultMapType);
259 var elmParams = new elm_ast_1.ElmParameterDecl('params', elmParamsType);
260 var elmParamsDecl = elmParamsType.fields.length > 0 ? [elmContext, elmParams, elmToResultMap, elmMapper, elmToMsg] : [elmContext, elmToResultMap, elmMapper, elmToMsg];
261 var methodParam = def.operation == 'query' ? "\"" + verb + "\" " : '';
262 decls.push(new elm_ast_1.ElmFunctionDecl(funcName, elmParamsDecl, new elm_ast_1.ElmTypeName("Cmd msg"), {
263 // we use awkward variable names to avoid naming collisions with query parameters
264 expr: ("let graphQLQuery = \"\"\"" + query.replace(/\s+/g, ' ') + "\"\"\" in\n") +
265 " let graphQLParams =\n" +
266 " Json.Encode.object\n" +
267 " [ " +
268 parameters.map(function (p) {
269 var encoder;
270 if (p.hasDefault) {
271 encoder = ("case params." + p.name + " of") +
272 ("\n Just val -> " + encoderForInputType(p.schemaType, true) + " val") +
273 "\n Nothing -> Json.Encode.null";
274 }
275 else {
276 encoder = encoderForInputType(p.schemaType, true, 'params.' + p.name);
277 }
278 return "(\"" + p.name + "\", " + encoder + ")";
279 })
280 .join("\n , ") + '\n' +
281 " ]\n" +
282 " in\n" +
283 (" GraphQL." + def.operation + " context " + methodParam + "graphQLQuery \"" + name_7 + "\" graphQLParams (Json.Decode.map mapDecoder " + decodeFuncName + ") mapper toMsg")
284 }));
285 decls.push(new elm_ast_1.ElmFunctionDecl(decodeFuncName, [], new elm_ast_1.ElmTypeName('Decoder ' + resultTypeName), query_to_decoder_1.decoderForQuery(def, info, schema, fragmentDefinitionMap, seenFragments)));
286 info.leave(def);
287 return decls;
288 }
289 }
290 function encoderForInputType(type, isNonNull, value) {
291 var encoder;
292 var isMaybe = false;
293 if (type instanceof type_1.GraphQLNonNull) {
294 type = type['ofType'];
295 }
296 else {
297 isMaybe = true;
298 }
299 if (type instanceof type_1.GraphQLInputObjectType) {
300 var fieldEncoders = [];
301 var fields = type.getFields();
302 for (var name_10 in fields) {
303 var field = fields[name_10];
304 var valuePath = value + '.' + field.name;
305 fieldEncoders.push("(\"" + field.name + "\", " + encoderForInputType(field.type, false, valuePath) + ")");
306 }
307 encoder = '(Json.Encode.object [' + fieldEncoders.join(", ") + '])';
308 }
309 else if (type instanceof type_1.GraphQLList) {
310 encoder = '(Json.Encode.list (List.map (\\x -> ' + encoderForInputType(type.ofType, true, 'x') + ') ' + value + '))';
311 }
312 else if (type instanceof type_1.GraphQLScalarType) {
313 switch (type.name) {
314 case 'Int':
315 encoder = 'Json.Encode.int ' + value;
316 break;
317 case 'Float':
318 encoder = 'Json.Encode.float ' + value;
319 break;
320 case 'Boolean':
321 encoder = 'Json.Encode.bool ' + value;
322 break;
323 case 'ID':
324 case 'String':
325 encoder = 'Json.Encode.string ' + value;
326 break;
327 }
328 }
329 else {
330 throw new Error('not implemented: ' + type.constructor.name);
331 }
332 if (isMaybe) {
333 encoder = '(maybeEncode ' + encoder + ')';
334 }
335 return encoder;
336 }
337 function walkFragmentDefinition(def, info) {
338 info.enter(def);
339 var name = def.name.value;
340 var decls = [];
341 var resultType = name[0].toUpperCase() + name.substr(1);
342 // todo: Directives
343 // SelectionSet
344 var _a = walkSelectionSet(def.selectionSet, info), fields = _a[0], spreads = _a[1];
345 var type = new elm_ast_1.ElmTypeRecord(fields, 'a');
346 for (var _i = 0, spreads_1 = spreads; _i < spreads_1.length; _i++) {
347 var spreadName = spreads_1[_i];
348 var typeName = spreadName[0].toUpperCase() + spreadName.substr(1) + '_';
349 type = new elm_ast_1.ElmTypeApp(typeName, [type]);
350 }
351 decls.push(new elm_ast_1.ElmTypeAliasDecl(resultType + '_', type, ['a']));
352 decls.push(new elm_ast_1.ElmTypeAliasDecl(resultType, new elm_ast_1.ElmTypeApp(resultType + '_', [new elm_ast_1.ElmTypeRecord([])])));
353 info.leave(def);
354 return decls;
355 }
356 function walkSelectionSet(selSet, info) {
357 info.enter(selSet);
358 var fields = [];
359 var spreads = [];
360 if (getRootType(info.getType()) instanceof type_1.GraphQLUnionType) {
361 var type = walkUnionSelectionSet(selSet, info);
362 return [[], [], type];
363 }
364 else {
365 for (var _i = 0, _a = selSet.selections; _i < _a.length; _i++) {
366 var sel = _a[_i];
367 if (sel.kind == 'Field') {
368 var field = sel;
369 fields.push(walkField(field, info));
370 }
371 else if (sel.kind == 'FragmentSpread') {
372 spreads.push(sel.name.value);
373 }
374 else if (sel.kind == 'InlineFragment') {
375 var frag = sel;
376 // todo: InlineFragment
377 throw new Error('not implemented: InlineFragment on ' + frag.typeCondition.name.value);
378 }
379 }
380 info.leave(selSet);
381 return [fields, spreads, null];
382 }
383 }
384 function walkUnionSelectionSet(selSet, info) {
385 var union = getRootType(info.getType());
386 var typeMap = {};
387 for (var _i = 0, _a = union.getTypes(); _i < _a.length; _i++) {
388 var type = _a[_i];
389 typeMap[type.name] = new elm_ast_1.ElmTypeRecord([]);
390 }
391 for (var _b = 0, _c = selSet.selections; _b < _c.length; _b++) {
392 var sel = _c[_b];
393 if (sel.kind == 'InlineFragment') {
394 var inline = sel;
395 info.enter(inline);
396 var _d = walkSelectionSet(inline.selectionSet, info), fields = _d[0], spreads = _d[1];
397 info.leave(inline);
398 // record
399 var type = new elm_ast_1.ElmTypeRecord(fields);
400 // spreads
401 for (var _e = 0, spreads_2 = spreads; _e < spreads_2.length; _e++) {
402 var spreadName = spreads_2[_e];
403 var typeName = spreadName[0].toUpperCase() + spreadName.substr(1) + '_';
404 type = new elm_ast_1.ElmTypeApp(typeName, [type]);
405 }
406 typeMap[inline.typeCondition.name.value] = type;
407 }
408 }
409 var args = [];
410 for (var name_11 in typeMap) {
411 args.push(typeMap[name_11]);
412 }
413 return new elm_ast_1.ElmTypeApp(getRootType(union)['name'], args);
414 }
415 function walkField(field, info) {
416 info.enter(field);
417 var info_type = info.getType();
418 // Name
419 var name = elmSafeName(field.name.value);
420 // Alias
421 if (field.alias) {
422 name = elmSafeName(field.alias.value);
423 }
424 // todo: Arguments, such as `id: $someId`, where $someId is a variable
425 var args = field.arguments; // e.g. id: "1000"
426 // todo: Directives
427 // SelectionSet
428 if (field.selectionSet) {
429 var isMaybe = false;
430 if (info_type instanceof type_1.GraphQLNonNull) {
431 info_type = info_type['ofType'];
432 }
433 else {
434 isMaybe = true;
435 }
436 var isList = info_type instanceof type_1.GraphQLList;
437 var _a = walkSelectionSet(field.selectionSet, info), fields = _a[0], spreads = _a[1], union = _a[2];
438 var type = union ? union : new elm_ast_1.ElmTypeRecord(fields);
439 for (var _i = 0, spreads_3 = spreads; _i < spreads_3.length; _i++) {
440 var spreadName = spreads_3[_i];
441 var typeName = spreadName[0].toUpperCase() + spreadName.substr(1) + '_';
442 type = new elm_ast_1.ElmTypeApp(typeName, [type]);
443 }
444 if (isList) {
445 type = new elm_ast_1.ElmTypeApp('List', [type]);
446 }
447 if (isMaybe) {
448 type = new elm_ast_1.ElmTypeApp('Maybe', [type]);
449 }
450 info.leave(field);
451 return new elm_ast_1.ElmFieldDecl(name, type);
452 }
453 else {
454 if (!info.getType()) {
455 var errorMessage = "Unknown GraphQL field: ' " + field.name.value + "\n" + JSON.stringify(info) + "\n";
456 throw new Error('Unknown GraphQL field: ' + field.name.value);
457 }
458 var type = typeToElm(info.getType());
459 info.leave(field);
460 return new elm_ast_1.ElmFieldDecl(name, type);
461 }
462 }
463 return walkQueryDocument(doc, new utilities_1.TypeInfo(schema));
464}
465function getRootType(type) {
466 if (type instanceof type_1.GraphQLList) {
467 return getRootType(type['ofType']);
468 }
469 else if (type instanceof type_1.GraphQLNonNull) {
470 return getRootType(type['ofType']);
471 }
472 else {
473 return type;
474 }
475}
476exports.getRootType = getRootType;
477function typeToElm(type, isNonNull) {
478 if (isNonNull === void 0) { isNonNull = false; }
479 var elmType;
480 if (type instanceof type_1.GraphQLNonNull) {
481 elmType = typeToElm(type.ofType, true);
482 }
483 else if (type instanceof type_1.GraphQLScalarType) {
484 switch (type.name) {
485 case 'Int':
486 elmType = new elm_ast_1.ElmTypeName('Int');
487 break;
488 case 'Float':
489 elmType = new elm_ast_1.ElmTypeName('Float');
490 break;
491 case 'Boolean':
492 elmType = new elm_ast_1.ElmTypeName('Bool');
493 break;
494 case 'ID':
495 case 'String':
496 elmType = new elm_ast_1.ElmTypeName('String');
497 break;
498 }
499 }
500 else if (type instanceof type_1.GraphQLEnumType) {
501 elmType = new elm_ast_1.ElmTypeName(type.name[0].toUpperCase() + type.name.substr(1));
502 }
503 else if (type instanceof type_1.GraphQLList) {
504 elmType = new elm_ast_1.ElmTypeApp('List', [typeToElm(type.ofType, true)]);
505 }
506 else if (type instanceof type_1.GraphQLObjectType ||
507 type instanceof type_1.GraphQLInterfaceType ||
508 type instanceof type_1.GraphQLInputObjectType) {
509 var fields = [];
510 var fieldMap = type.getFields();
511 for (var fieldName in fieldMap) {
512 var field = fieldMap[fieldName];
513 fields.push(new elm_ast_1.ElmFieldDecl(elmSafeName(fieldName), typeToElm(field.type)));
514 }
515 elmType = new elm_ast_1.ElmTypeRecord(fields);
516 }
517 else if (type instanceof type_1.GraphQLNonNull) {
518 elmType = typeToElm(type.ofType, true);
519 }
520 else {
521 throw new Error('Unexpected: ' + type.constructor.name);
522 }
523 if (!isNonNull && !(type instanceof type_1.GraphQLList) && !(type instanceof type_1.GraphQLNonNull)) {
524 elmType = new elm_ast_1.ElmTypeApp('Maybe', [elmType]);
525 }
526 return elmType;
527}
528exports.typeToElm = typeToElm;
529function elmSafeName(graphQlName) {
530 switch (graphQlName) {
531 case 'type': return "type_";
532 case 'Task': return "Task_";
533 case 'List': return "List_";
534 case 'Http': return "Http_";
535 case 'GraphQL': return "GraphQL_";
536 // todo: more...
537 default: return graphQlName;
538 }
539}
540exports.elmSafeName = elmSafeName;
541//# sourceMappingURL=query-to-elm.js.map
\No newline at end of file