@khanacademy/graphql-flow 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (48) hide show
  1. package/.babelrc +6 -0
  2. package/.changeset/README.md +8 -0
  3. package/.changeset/config.json +11 -0
  4. package/.eslintignore +2 -0
  5. package/.eslintrc.js +10 -0
  6. package/.flowconfig +13 -0
  7. package/.github/actions/filter-files/action.yml +37 -0
  8. package/.github/actions/full-or-limited/action.yml +27 -0
  9. package/.github/actions/json-args/action.yml +32 -0
  10. package/.github/actions/setup/action.yml +28 -0
  11. package/.github/workflows/changeset-release.yml +80 -0
  12. package/.github/workflows/pr-checks.yml +64 -0
  13. package/.prettierrc +7 -0
  14. package/CHANGELOG.md +14 -0
  15. package/Readme.md +172 -0
  16. package/build-copy-source.js +28 -0
  17. package/dist/enums.js +57 -0
  18. package/dist/enums.js.flow +69 -0
  19. package/dist/generateResponseType.js +267 -0
  20. package/dist/generateResponseType.js.flow +419 -0
  21. package/dist/generateVariablesType.js +132 -0
  22. package/dist/generateVariablesType.js.flow +153 -0
  23. package/dist/index.js +88 -0
  24. package/dist/index.js.flow +93 -0
  25. package/dist/jest-mock-graphql-tag.js +169 -0
  26. package/dist/jest-mock-graphql-tag.js.flow +191 -0
  27. package/dist/schemaFromIntrospectionData.js +69 -0
  28. package/dist/schemaFromIntrospectionData.js.flow +68 -0
  29. package/dist/types.js +1 -0
  30. package/dist/types.js.flow +54 -0
  31. package/dist/utils.js +53 -0
  32. package/dist/utils.js.flow +50 -0
  33. package/flow-typed/npm/@babel/types_vx.x.x.js +5317 -0
  34. package/flow-typed/npm/jest_v23.x.x.js +1155 -0
  35. package/flow-typed/overrides.js +435 -0
  36. package/package.json +41 -0
  37. package/src/__test__/example-schema.graphql +65 -0
  38. package/src/__test__/graphql-flow.test.js +364 -0
  39. package/src/__test__/jest-mock-graphql-tag.test.js +51 -0
  40. package/src/enums.js +69 -0
  41. package/src/generateResponseType.js +419 -0
  42. package/src/generateVariablesType.js +153 -0
  43. package/src/index.js +93 -0
  44. package/src/jest-mock-graphql-tag.js +191 -0
  45. package/src/schemaFromIntrospectionData.js +68 -0
  46. package/src/types.js +54 -0
  47. package/src/utils.js +50 -0
  48. package/tools/find-files-with-gql.js +40 -0
@@ -0,0 +1,69 @@
1
+ // @flow
2
+ /**
3
+ * Both input & output types can have enums & scalars.
4
+ */
5
+ import * as babelTypes from '@babel/types';
6
+ import {type BabelNodeFlowType} from '@babel/types';
7
+ import type {Config} from './types';
8
+ import {maybeAddDescriptionComment} from './utils';
9
+
10
+ export const enumTypeToFlow = (
11
+ config: Config,
12
+ name: string,
13
+ ): BabelNodeFlowType => {
14
+ const enumConfig = config.schema.enumsByName[name];
15
+ let combinedDescription = enumConfig.enumValues
16
+ .map(
17
+ (n) =>
18
+ `- ${n.name}` +
19
+ (n.description
20
+ ? '\n\n ' + n.description.replace(/\n/g, '\n ')
21
+ : ''),
22
+ )
23
+ .join('\n');
24
+ if (enumConfig.description) {
25
+ combinedDescription =
26
+ enumConfig.description + '\n\n' + combinedDescription;
27
+ }
28
+ return maybeAddDescriptionComment(
29
+ combinedDescription,
30
+ babelTypes.unionTypeAnnotation(
31
+ enumConfig.enumValues.map((n) =>
32
+ babelTypes.stringLiteralTypeAnnotation(n.name),
33
+ ),
34
+ ),
35
+ );
36
+ };
37
+
38
+ export const builtinScalars: {[key: string]: string} = {
39
+ Boolean: 'boolean',
40
+ String: 'string',
41
+ DateTime: 'string',
42
+ Date: 'string',
43
+ ID: 'string',
44
+ Int: 'number',
45
+ Float: 'number',
46
+ };
47
+
48
+ export const scalarTypeToFlow = (
49
+ config: Config,
50
+ name: string,
51
+ ): BabelNodeFlowType => {
52
+ if (builtinScalars[name]) {
53
+ return babelTypes.genericTypeAnnotation(
54
+ babelTypes.identifier(builtinScalars[name]),
55
+ );
56
+ }
57
+ const underlyingType = config.scalars[name];
58
+ if (underlyingType != null) {
59
+ return babelTypes.genericTypeAnnotation(
60
+ babelTypes.identifier(underlyingType),
61
+ );
62
+ }
63
+ config.errors.push(
64
+ `Unexpected scalar '${name}'! Please add it to the "scalars" argument at the callsite of 'generateFlowTypes()'.`,
65
+ );
66
+ return babelTypes.genericTypeAnnotation(
67
+ babelTypes.identifier(`UNKNOWN_SCALAR["${name}"]`),
68
+ );
69
+ };
@@ -0,0 +1,267 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.unionOrInterfaceToFlow = exports.typeToFlow = exports.objectPropertiesToFlow = exports.generateResponseType = void 0;
7
+
8
+ var _generator = _interopRequireDefault(require("@babel/generator"));
9
+
10
+ var babelTypes = _interopRequireWildcard(require("@babel/types"));
11
+
12
+ var _utils = require("./utils");
13
+
14
+ var _enums = require("./enums");
15
+
16
+ function _getRequireWildcardCache(nodeInterop) { if (typeof WeakMap !== "function") return null; var cacheBabelInterop = new WeakMap(); var cacheNodeInterop = new WeakMap(); return (_getRequireWildcardCache = function (nodeInterop) { return nodeInterop ? cacheNodeInterop : cacheBabelInterop; })(nodeInterop); }
17
+
18
+ function _interopRequireWildcard(obj, nodeInterop) { if (!nodeInterop && obj && obj.__esModule) { return obj; } if (obj === null || typeof obj !== "object" && typeof obj !== "function") { return { default: obj }; } var cache = _getRequireWildcardCache(nodeInterop); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (key !== "default" && Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj.default = obj; if (cache) { cache.set(obj, newObj); } return newObj; }
19
+
20
+ function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
21
+
22
+ /* eslint-disable no-console */
23
+ // eslint-disable-line flowtype-errors/uncovered
24
+ const generateResponseType = (schema, query, config) => {
25
+ const ast = querySelectionToObjectType(config, query.selectionSet.selections, query.operation === 'mutation' ? schema.typesByName.Mutation : schema.typesByName.Query, query.operation === 'mutation' ? 'mutation' : 'query'); // eslint-disable-next-line flowtype-errors/uncovered
26
+
27
+ return (0, _generator.default)(ast).code;
28
+ };
29
+
30
+ exports.generateResponseType = generateResponseType;
31
+
32
+ const _typeToFlow = (config, type, selection) => {
33
+ if (type.kind === 'SCALAR') {
34
+ return (0, _enums.scalarTypeToFlow)(config, type.name);
35
+ }
36
+
37
+ if (type.kind === 'LIST') {
38
+ return babelTypes.genericTypeAnnotation(config.readOnlyArray ? babelTypes.identifier('$ReadOnlyArray') : babelTypes.identifier('Array'), babelTypes.typeParameterInstantiation([typeToFlow(config, type.ofType, selection)]));
39
+ }
40
+
41
+ if (type.kind === 'UNION') {
42
+ const union = config.schema.unionsByName[type.name];
43
+
44
+ if (!selection.selectionSet) {
45
+ console.log('no selection set', selection);
46
+ return babelTypes.anyTypeAnnotation();
47
+ }
48
+
49
+ return unionOrInterfaceToFlow(config, union, selection.selectionSet.selections);
50
+ }
51
+
52
+ if (type.kind === 'INTERFACE') {
53
+ if (!selection.selectionSet) {
54
+ console.log('no selection set', selection);
55
+ return babelTypes.anyTypeAnnotation();
56
+ }
57
+
58
+ return unionOrInterfaceToFlow(config, config.schema.interfacesByName[type.name], selection.selectionSet.selections);
59
+ }
60
+
61
+ if (type.kind === 'ENUM') {
62
+ return (0, _enums.enumTypeToFlow)(config, type.name);
63
+ }
64
+
65
+ if (type.kind !== 'OBJECT') {
66
+ console.log('not object', type);
67
+ return babelTypes.anyTypeAnnotation();
68
+ }
69
+
70
+ const tname = type.name;
71
+
72
+ if (!config.schema.typesByName[tname]) {
73
+ console.log('unknown referenced type', tname);
74
+ return babelTypes.anyTypeAnnotation();
75
+ }
76
+
77
+ const childType = config.schema.typesByName[tname];
78
+
79
+ if (!selection.selectionSet) {
80
+ console.log('no selection set', selection);
81
+ return babelTypes.anyTypeAnnotation();
82
+ }
83
+
84
+ return (0, _utils.maybeAddDescriptionComment)(childType.description, querySelectionToObjectType(config, selection.selectionSet.selections, childType, tname));
85
+ };
86
+
87
+ const typeToFlow = (config, type, selection) => {
88
+ // throw new Error('npoe');
89
+ if (type.kind === 'NON_NULL') {
90
+ return _typeToFlow(config, type.ofType, selection);
91
+ } // If we don'babelTypes care about strict nullability checking, then pretend everything is non-null
92
+
93
+
94
+ if (!config.strictNullability) {
95
+ return _typeToFlow(config, type, selection);
96
+ }
97
+
98
+ const inner = _typeToFlow(config, type, selection);
99
+
100
+ const result = babelTypes.nullableTypeAnnotation(inner);
101
+ return (0, _utils.transferLeadingComments)(inner, result);
102
+ };
103
+
104
+ exports.typeToFlow = typeToFlow;
105
+
106
+ const querySelectionToObjectType = (config, selections, type, typeName) => {
107
+ let seenTypeName = false;
108
+ return babelTypes.objectTypeAnnotation(objectPropertiesToFlow(config, type, typeName, selections).filter(type => {
109
+ // The apollo-utilities "addTypeName" utility will add it
110
+ // even if it's already specified :( so we have to filter out
111
+ // the extra one here.
112
+ if (type.type === 'ObjectTypeProperty' && type.key.name === '__typename') {
113
+ if (seenTypeName) {
114
+ return false;
115
+ }
116
+
117
+ seenTypeName = true;
118
+ }
119
+
120
+ return true;
121
+ }), undefined
122
+ /* indexers */
123
+ , undefined
124
+ /* callProperties */
125
+ , undefined
126
+ /* internalSlots */
127
+ , true
128
+ /* exact */
129
+ );
130
+ };
131
+
132
+ const objectPropertiesToFlow = (config, type, typeName, selections) => {
133
+ return [].concat(...selections.map(selection => {
134
+ switch (selection.kind) {
135
+ case 'FragmentSpread':
136
+ if (!config.fragments[selection.name.value]) {
137
+ config.errors.push(`No fragment named '${selection.name.value}'. Did you forget to include it in the template literal?`);
138
+ return [babelTypes.objectTypeProperty(babelTypes.identifier(selection.name.value), babelTypes.genericTypeAnnotation(babelTypes.identifier(`UNKNOWN_FRAGMENT`)))];
139
+ }
140
+
141
+ return objectPropertiesToFlow(config, type, typeName, config.fragments[selection.name.value].selectionSet.selections);
142
+
143
+ case 'Field':
144
+ const name = selection.name.value;
145
+ const alias = selection.alias ? selection.alias.value : name;
146
+
147
+ if (name === '__typename') {
148
+ return [babelTypes.objectTypeProperty(babelTypes.identifier(alias), babelTypes.stringLiteralTypeAnnotation(typeName))];
149
+ }
150
+
151
+ if (!type.fieldsByName[name]) {
152
+ config.errors.push(`Unknown field '${name}' for type '${typeName}'`);
153
+ return babelTypes.objectTypeProperty(babelTypes.identifier(alias), babelTypes.genericTypeAnnotation(babelTypes.identifier(`UNKNOWN_FIELD["${name}"]`)));
154
+ }
155
+
156
+ const typeField = type.fieldsByName[name];
157
+ return [(0, _utils.maybeAddDescriptionComment)(typeField.description, (0, _utils.liftLeadingPropertyComments)(babelTypes.objectTypeProperty(babelTypes.identifier(alias), typeToFlow(config, typeField.type, selection))))];
158
+
159
+ default:
160
+ config.errors.push(`Unsupported selection kind '${selection.kind}'`);
161
+ return [];
162
+ }
163
+ }));
164
+ };
165
+
166
+ exports.objectPropertiesToFlow = objectPropertiesToFlow;
167
+
168
+ const unionOrInterfaceToFlow = (config, type, selections) => {
169
+ const selectedAttributes = type.possibleTypes.map(possible => {
170
+ let seenTypeName = false;
171
+ return selections.map(selection => unionOrInterfaceSelection(config, type, possible, selection)).flat().filter(type => {
172
+ // The apollo-utilities "addTypeName" utility will add it
173
+ // even if it's already specified :( so we have to filter out
174
+ // the extra one here.
175
+ if (type.type === 'ObjectTypeProperty' && type.key.name === '__typename') {
176
+ if (seenTypeName) {
177
+ return false;
178
+ }
179
+
180
+ seenTypeName = true;
181
+ }
182
+
183
+ return true;
184
+ });
185
+ });
186
+ const allFields = selections.every(selection => selection.kind === 'Field');
187
+
188
+ if (selectedAttributes.length === 1 || allFields) {
189
+ return babelTypes.objectTypeAnnotation(selectedAttributes[0], undefined
190
+ /* indexers */
191
+ , undefined
192
+ /* callProperties */
193
+ , undefined
194
+ /* internalSlots */
195
+ , true
196
+ /* exact */
197
+ );
198
+ }
199
+
200
+ return babelTypes.unionTypeAnnotation(selectedAttributes.map(properties => babelTypes.objectTypeAnnotation(properties, undefined
201
+ /* indexers */
202
+ , undefined
203
+ /* callProperties */
204
+ , undefined
205
+ /* internalSlots */
206
+ , true
207
+ /* exact */
208
+ )));
209
+ };
210
+
211
+ exports.unionOrInterfaceToFlow = unionOrInterfaceToFlow;
212
+
213
+ const unionOrInterfaceSelection = (config, type, possible, selection) => {
214
+ if (selection.kind === 'Field' && selection.name.value === '__typename') {
215
+ const alias = selection.alias ? selection.alias.value : selection.name.value;
216
+ return [babelTypes.objectTypeProperty(babelTypes.identifier(alias), babelTypes.stringLiteralTypeAnnotation(possible.name))];
217
+ }
218
+
219
+ if (selection.kind === 'Field' && type.kind !== 'UNION') {
220
+ // this is an interface
221
+ const name = selection.name.value;
222
+ const alias = selection.alias ? selection.alias.value : name;
223
+
224
+ if (!type.fieldsByName[name]) {
225
+ config.errors.push('Unknown field: ' + name + ' on type ' + type.name + ' for possible ' + possible.name);
226
+ return [babelTypes.objectTypeProperty(babelTypes.identifier(alias), babelTypes.genericTypeAnnotation(babelTypes.identifier(`UNKNOWN_FIELD`)))];
227
+ }
228
+
229
+ const typeField = type.fieldsByName[name];
230
+ return [(0, _utils.liftLeadingPropertyComments)(babelTypes.objectTypeProperty(babelTypes.identifier(alias), typeToFlow(config, typeField.type, selection)))];
231
+ }
232
+
233
+ if (selection.kind === 'FragmentSpread') {
234
+ const fragment = config.fragments[selection.name.value];
235
+ const typeName = fragment.typeCondition.name.value;
236
+
237
+ if (config.schema.interfacesByName[typeName] && config.schema.interfacesByName[typeName].possibleTypesByName[possible.name] || typeName === possible.name) {
238
+ return [].concat(...fragment.selectionSet.selections.map(selection => unionOrInterfaceSelection(config, config.schema.typesByName[possible.name], possible, selection)));
239
+ } else {
240
+ return [];
241
+ }
242
+ }
243
+
244
+ if (selection.kind !== 'InlineFragment') {
245
+ config.errors.push(`union selectors must be inline fragment: found ${selection.kind}`);
246
+
247
+ if (type.kind === 'UNION') {
248
+ config.errors.push(`You're trying to select a field from the union ${type.name},
249
+ but the only field you're allowed to select is "__typename".
250
+ Try using an inline fragment "... on SomeType {}".`);
251
+ }
252
+
253
+ return [];
254
+ }
255
+
256
+ if (!selection.typeCondition) {
257
+ throw new Error('Expected selection to have a typeCondition');
258
+ }
259
+
260
+ const typeName = selection.typeCondition.name.value;
261
+
262
+ if (config.schema.interfacesByName[typeName] && config.schema.interfacesByName[typeName].possibleTypesByName[possible.name] || typeName === possible.name) {
263
+ return objectPropertiesToFlow(config, config.schema.typesByName[possible.name], possible.name, selection.selectionSet.selections);
264
+ }
265
+
266
+ return [];
267
+ };