@khanacademy/graphql-flow 0.0.1 → 0.0.2
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.
- package/CHANGELOG.md +7 -0
- package/Readme.md +3 -2
- package/dist/generateVariablesType.js +6 -0
- package/dist/generateVariablesType.js.flow +6 -0
- package/dist/jest-mock-graphql-tag.js +25 -21
- package/dist/jest-mock-graphql-tag.js.flow +34 -21
- package/dist/types.js.flow +1 -0
- package/package.json +7 -7
- package/src/__test__/example-schema.graphql +2 -0
- package/src/__test__/graphql-flow.test.js +20 -0
- package/src/generateVariablesType.js +6 -0
- package/src/jest-mock-graphql-tag.js +34 -21
- package/src/types.js +1 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
# @khanacademy/graphql-flow
|
|
2
2
|
|
|
3
|
+
## 0.0.2
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- 9810bfe: Allow customization of the "regenerate command" that's in the file docstrings
|
|
8
|
+
- 7d27337: Support custom scalars as variables in an operation
|
|
9
|
+
|
|
3
10
|
## 0.0.1
|
|
4
11
|
|
|
5
12
|
### Patch Changes
|
package/Readme.md
CHANGED
|
@@ -94,10 +94,11 @@ Then you'll want to make a pseudo-'test' that makes sure to 'require' all of the
|
|
|
94
94
|
jest will process them and our mock will see them.
|
|
95
95
|
```js
|
|
96
96
|
// generate-types.test.js
|
|
97
|
-
import {
|
|
97
|
+
import {findGraphqlTagReferences} from '../tools/graphql-flow/find-files-with-gql';
|
|
98
|
+
import path from 'path';
|
|
98
99
|
|
|
99
100
|
if (process.env.GRAPHQL_FLOW) {
|
|
100
|
-
|
|
101
|
+
findGraphqlTagReferences(path.join(__dirname, '..')).forEach(name => {
|
|
101
102
|
require(name);
|
|
102
103
|
});
|
|
103
104
|
|
|
@@ -104,6 +104,12 @@ const _variableToFlow = (config, type) => {
|
|
|
104
104
|
return (0, _enums.enumTypeToFlow)(config, type.name.value);
|
|
105
105
|
}
|
|
106
106
|
|
|
107
|
+
const customScalarType = config.scalars[type.name.value];
|
|
108
|
+
|
|
109
|
+
if (customScalarType) {
|
|
110
|
+
return babelTypes.genericTypeAnnotation(babelTypes.identifier(customScalarType));
|
|
111
|
+
}
|
|
112
|
+
|
|
107
113
|
return inputObjectToFlow(config, type.name.value);
|
|
108
114
|
}
|
|
109
115
|
|
|
@@ -117,6 +117,12 @@ const _variableToFlow = (config: Config, type: TypeNode) => {
|
|
|
117
117
|
if (config.schema.enumsByName[type.name.value]) {
|
|
118
118
|
return enumTypeToFlow(config, type.name.value);
|
|
119
119
|
}
|
|
120
|
+
const customScalarType = config.scalars[type.name.value];
|
|
121
|
+
if (customScalarType) {
|
|
122
|
+
return babelTypes.genericTypeAnnotation(
|
|
123
|
+
babelTypes.identifier(customScalarType),
|
|
124
|
+
);
|
|
125
|
+
}
|
|
120
126
|
return inputObjectToFlow(config, type.name.value);
|
|
121
127
|
}
|
|
122
128
|
if (type.kind === 'ListType') {
|
|
@@ -12,11 +12,12 @@ var _schemaFromIntrospectionData = require("./schemaFromIntrospectionData");
|
|
|
12
12
|
|
|
13
13
|
// Import this in your jest setup, to mock out graphql-tag!
|
|
14
14
|
// eslint-disable-line flowtype-errors/uncovered
|
|
15
|
-
const indexPrelude = `// @flow
|
|
15
|
+
const indexPrelude = regenerateCommand => `// @flow
|
|
16
16
|
//
|
|
17
17
|
// AUTOGENERATED
|
|
18
18
|
// NOTE: New response types are added to this file automatically.
|
|
19
19
|
// Outdated response types can be removed manually as they are deprecated.
|
|
20
|
+
//${regenerateCommand ? ' To regenerate, run ' + regenerateCommand : ''}
|
|
20
21
|
//
|
|
21
22
|
|
|
22
23
|
`;
|
|
@@ -41,7 +42,7 @@ const generateTypeFiles = (schema, document, options) => {
|
|
|
41
42
|
recursive: true
|
|
42
43
|
}); // Now write an index.js for each __generated__ dir.
|
|
43
44
|
|
|
44
|
-
fs.writeFileSync(indexFile(generatedDir), indexPrelude);
|
|
45
|
+
fs.writeFileSync(indexFile(generatedDir), indexPrelude(options.regenerateCommand));
|
|
45
46
|
}
|
|
46
47
|
}; /// Write export for __generated__/index.js if it doesn't exist
|
|
47
48
|
|
|
@@ -87,31 +88,33 @@ const generateTypeFiles = (schema, document, options) => {
|
|
|
87
88
|
// );
|
|
88
89
|
|
|
89
90
|
const fileContents = format({
|
|
90
|
-
text: '// @' + `flow\n// AUTOGENERATED -- DO NOT EDIT\n` + `// Generated for operation '${name}' in file '../${path.basename(fileName)}'\n` + `// To regenerate, run '
|
|
91
|
+
text: '// @' + `flow\n// AUTOGENERATED -- DO NOT EDIT\n` + `// Generated for operation '${name}' in file '../${path.basename(fileName)}'\n` + (options.regenerateCommand ? `// To regenerate, run '${options.regenerateCommand}'.\n` : '') + code
|
|
91
92
|
});
|
|
92
93
|
fs.writeFileSync(targetPath, fileContents);
|
|
93
94
|
writeToIndex(targetPath, typeName);
|
|
94
95
|
});
|
|
95
96
|
};
|
|
96
97
|
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
98
|
+
/**
|
|
99
|
+
* This function is expected to be called like so:
|
|
100
|
+
*
|
|
101
|
+
* jest.mock('graphql-tag', () => {
|
|
102
|
+
* const introspectionData = jest.requireActual(
|
|
103
|
+
* './server-introspection-response.json',
|
|
104
|
+
* );
|
|
105
|
+
* const {spyOnGraphqlTagToCollectQueries} = jest.requireActual(
|
|
106
|
+
* 'graphql-flow/jest-mock-graphql-tag.js');
|
|
107
|
+
*
|
|
108
|
+
* return spyOnGraphqlTagToCollectQueries(
|
|
109
|
+
* jest.requireActual('graphql-tag'),
|
|
110
|
+
* introspectionData,
|
|
111
|
+
* );
|
|
112
|
+
* });
|
|
113
|
+
*
|
|
114
|
+
* If both pragma and loosePragma are empty, then all graphql
|
|
115
|
+
* documents will be processed. Otherwise, only documents
|
|
116
|
+
* with one of the pragmas will be processed.
|
|
117
|
+
*/
|
|
115
118
|
const spyOnGraphqlTagToCollectQueries = (realGraphqlTag, introspectionData, options = {}) => {
|
|
116
119
|
const collection = [];
|
|
117
120
|
const clientSchema = (0, _graphql.buildClientSchema)(introspectionData);
|
|
@@ -154,6 +157,7 @@ const processPragmas = (options, rawSource) => {
|
|
|
154
157
|
|
|
155
158
|
if (autogen || autogenStrict || noPragmas) {
|
|
156
159
|
return {
|
|
160
|
+
regenerateCommand: options.regenerateCommand,
|
|
157
161
|
strictNullability: noPragmas ? options.strictNullability : autogenStrict || !autogen,
|
|
158
162
|
readOnlyArray: options.readOnlyArray,
|
|
159
163
|
scalars: options.scalars
|
|
@@ -8,11 +8,12 @@ import {print} from 'graphql/language/printer';
|
|
|
8
8
|
import {addTypenameToDocument} from 'apollo-utilities'; // eslint-disable-line flowtype-errors/uncovered
|
|
9
9
|
import {schemaFromIntrospectionData} from './schemaFromIntrospectionData';
|
|
10
10
|
|
|
11
|
-
const indexPrelude = `// @flow
|
|
11
|
+
const indexPrelude = (regenerateCommand?: string) => `// @flow
|
|
12
12
|
//
|
|
13
13
|
// AUTOGENERATED
|
|
14
14
|
// NOTE: New response types are added to this file automatically.
|
|
15
15
|
// Outdated response types can be removed manually as they are deprecated.
|
|
16
|
+
//${regenerateCommand ? ' To regenerate, run ' + regenerateCommand : ''}
|
|
16
17
|
//
|
|
17
18
|
|
|
18
19
|
`;
|
|
@@ -34,7 +35,10 @@ const generateTypeFiles = (
|
|
|
34
35
|
fs.mkdirSync(generatedDir, {recursive: true});
|
|
35
36
|
|
|
36
37
|
// Now write an index.js for each __generated__ dir.
|
|
37
|
-
fs.writeFileSync(
|
|
38
|
+
fs.writeFileSync(
|
|
39
|
+
indexFile(generatedDir),
|
|
40
|
+
indexPrelude(options.regenerateCommand),
|
|
41
|
+
);
|
|
38
42
|
}
|
|
39
43
|
};
|
|
40
44
|
|
|
@@ -85,7 +89,9 @@ const generateTypeFiles = (
|
|
|
85
89
|
`// Generated for operation '${name}' in file '../${path.basename(
|
|
86
90
|
fileName,
|
|
87
91
|
)}'\n` +
|
|
88
|
-
|
|
92
|
+
(options.regenerateCommand
|
|
93
|
+
? `// To regenerate, run '${options.regenerateCommand}'.\n`
|
|
94
|
+
: '') +
|
|
89
95
|
code,
|
|
90
96
|
});
|
|
91
97
|
fs.writeFileSync(targetPath, fileContents);
|
|
@@ -101,27 +107,33 @@ type SpyOptions = {
|
|
|
101
107
|
loosePragma?: string,
|
|
102
108
|
scalars?: Scalars,
|
|
103
109
|
strictNullability?: boolean,
|
|
110
|
+
/**
|
|
111
|
+
* The command that users should run to regenerate the types files.
|
|
112
|
+
*/
|
|
113
|
+
regenerateCommand?: string,
|
|
104
114
|
readOnlyArray?: boolean,
|
|
105
115
|
};
|
|
106
116
|
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
117
|
+
/**
|
|
118
|
+
* This function is expected to be called like so:
|
|
119
|
+
*
|
|
120
|
+
* jest.mock('graphql-tag', () => {
|
|
121
|
+
* const introspectionData = jest.requireActual(
|
|
122
|
+
* './server-introspection-response.json',
|
|
123
|
+
* );
|
|
124
|
+
* const {spyOnGraphqlTagToCollectQueries} = jest.requireActual(
|
|
125
|
+
* 'graphql-flow/jest-mock-graphql-tag.js');
|
|
126
|
+
*
|
|
127
|
+
* return spyOnGraphqlTagToCollectQueries(
|
|
128
|
+
* jest.requireActual('graphql-tag'),
|
|
129
|
+
* introspectionData,
|
|
130
|
+
* );
|
|
131
|
+
* });
|
|
132
|
+
*
|
|
133
|
+
* If both pragma and loosePragma are empty, then all graphql
|
|
134
|
+
* documents will be processed. Otherwise, only documents
|
|
135
|
+
* with one of the pragmas will be processed.
|
|
136
|
+
*/
|
|
125
137
|
const spyOnGraphqlTagToCollectQueries = (
|
|
126
138
|
realGraphqlTag: GraphqlTagFn,
|
|
127
139
|
introspectionData: IntrospectionQuery,
|
|
@@ -174,6 +186,7 @@ const processPragmas = (
|
|
|
174
186
|
|
|
175
187
|
if (autogen || autogenStrict || noPragmas) {
|
|
176
188
|
return {
|
|
189
|
+
regenerateCommand: options.regenerateCommand,
|
|
177
190
|
strictNullability: noPragmas
|
|
178
191
|
? options.strictNullability
|
|
179
192
|
: autogenStrict || !autogen,
|
package/dist/types.js.flow
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@khanacademy/graphql-flow",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.2",
|
|
4
4
|
"scripts": {
|
|
5
5
|
"test": "jest",
|
|
6
6
|
"publish:ci": "yarn run build && yarn run copy-flow && changeset publish",
|
|
@@ -11,30 +11,30 @@
|
|
|
11
11
|
"devDependencies": {
|
|
12
12
|
"@babel/cli": "^7.17.6",
|
|
13
13
|
"@babel/eslint-parser": "^7.17.0",
|
|
14
|
+
"@babel/polyfill": "^7.0.0",
|
|
15
|
+
"@babel/preset-env": "^7.16.11",
|
|
16
|
+
"@babel/preset-flow": "^7.16.7",
|
|
17
|
+
"babel-jest": "23.4.2",
|
|
14
18
|
"@changesets/cli": "^2.21.1",
|
|
15
19
|
"@khanacademy/eslint-config": "^0.1.0",
|
|
16
20
|
"eslint": "8.7.0",
|
|
17
21
|
"eslint-config-prettier": "7.0.0",
|
|
22
|
+
"eslint-plugin-flowtype": "^8.0.3",
|
|
18
23
|
"eslint-plugin-flowtype-errors": "^4.5.0",
|
|
19
24
|
"eslint-plugin-jsx-a11y": "^6.5.1",
|
|
20
25
|
"eslint-plugin-prettier": "^4.0.0",
|
|
21
26
|
"eslint-plugin-react": "^7.29.2",
|
|
22
27
|
"eslint-plugin-react-hooks": "^4.3.0",
|
|
28
|
+
"graphql-tag": "2.10.1",
|
|
23
29
|
"flow-bin": "^0.172.0",
|
|
24
30
|
"jest": "^27.5.1"
|
|
25
31
|
},
|
|
26
32
|
"dependencies": {
|
|
27
33
|
"@babel/core": "^7.6.2",
|
|
28
34
|
"@babel/generator": "^7.17.3",
|
|
29
|
-
"@babel/polyfill": "^7.0.0",
|
|
30
|
-
"@babel/preset-env": "^7.16.11",
|
|
31
|
-
"@babel/preset-flow": "^7.16.7",
|
|
32
35
|
"@babel/types": "^7.17.0",
|
|
33
36
|
"apollo-utilities": "^1.3.4",
|
|
34
|
-
"babel-jest": "23.4.2",
|
|
35
|
-
"eslint-plugin-flowtype": "^8.0.3",
|
|
36
37
|
"graphql": "14.2.1",
|
|
37
|
-
"graphql-tag": "2.10.1",
|
|
38
38
|
"prettier": "^2.5.1",
|
|
39
39
|
"prettier-eslint": "^13.0.0"
|
|
40
40
|
}
|
|
@@ -49,6 +49,7 @@ type Query {
|
|
|
49
49
|
human(id: String!): Human
|
|
50
50
|
droid(id: String!): Droid
|
|
51
51
|
friend(id: String!): Friendable
|
|
52
|
+
candies(number: PositiveNumber!): String
|
|
52
53
|
}
|
|
53
54
|
|
|
54
55
|
"""A character to add"""
|
|
@@ -58,6 +59,7 @@ input CharacterInput {
|
|
|
58
59
|
"""The character's friends"""
|
|
59
60
|
friends: [ID!]
|
|
60
61
|
appearsIn: [Episode!]
|
|
62
|
+
candies: PositiveNumber!
|
|
61
63
|
}
|
|
62
64
|
|
|
63
65
|
type Mutation {
|
|
@@ -52,6 +52,25 @@ const rawQueryToFlowTypes = (query: string, options?: Options): string => {
|
|
|
52
52
|
};
|
|
53
53
|
|
|
54
54
|
describe('graphql-flow generation', () => {
|
|
55
|
+
it('should allow custom scalars as input', () => {
|
|
56
|
+
const result = rawQueryToFlowTypes(`
|
|
57
|
+
query SomeQuery($candies: PositiveNumber!) {
|
|
58
|
+
candies(number: $candies)
|
|
59
|
+
}
|
|
60
|
+
`);
|
|
61
|
+
|
|
62
|
+
expect(result).toMatchInlineSnapshot(`
|
|
63
|
+
export type SomeQueryType = {|
|
|
64
|
+
variables: {|
|
|
65
|
+
candies: number
|
|
66
|
+
|},
|
|
67
|
+
response: {|
|
|
68
|
+
candies: ?string
|
|
69
|
+
|}
|
|
70
|
+
|};
|
|
71
|
+
`);
|
|
72
|
+
});
|
|
73
|
+
|
|
55
74
|
it('should work with a basic query', () => {
|
|
56
75
|
const result = rawQueryToFlowTypes(`
|
|
57
76
|
query SomeQuery {
|
|
@@ -350,6 +369,7 @@ describe('graphql-flow generation', () => {
|
|
|
350
369
|
- EMPIRE
|
|
351
370
|
- JEDI*/
|
|
352
371
|
"NEW_HOPE" | "EMPIRE" | "JEDI">,
|
|
372
|
+
candies: number,
|
|
353
373
|
|}
|
|
354
374
|
|},
|
|
355
375
|
response: {|
|
|
@@ -117,6 +117,12 @@ const _variableToFlow = (config: Config, type: TypeNode) => {
|
|
|
117
117
|
if (config.schema.enumsByName[type.name.value]) {
|
|
118
118
|
return enumTypeToFlow(config, type.name.value);
|
|
119
119
|
}
|
|
120
|
+
const customScalarType = config.scalars[type.name.value];
|
|
121
|
+
if (customScalarType) {
|
|
122
|
+
return babelTypes.genericTypeAnnotation(
|
|
123
|
+
babelTypes.identifier(customScalarType),
|
|
124
|
+
);
|
|
125
|
+
}
|
|
120
126
|
return inputObjectToFlow(config, type.name.value);
|
|
121
127
|
}
|
|
122
128
|
if (type.kind === 'ListType') {
|
|
@@ -8,11 +8,12 @@ import {print} from 'graphql/language/printer';
|
|
|
8
8
|
import {addTypenameToDocument} from 'apollo-utilities'; // eslint-disable-line flowtype-errors/uncovered
|
|
9
9
|
import {schemaFromIntrospectionData} from './schemaFromIntrospectionData';
|
|
10
10
|
|
|
11
|
-
const indexPrelude = `// @flow
|
|
11
|
+
const indexPrelude = (regenerateCommand?: string) => `// @flow
|
|
12
12
|
//
|
|
13
13
|
// AUTOGENERATED
|
|
14
14
|
// NOTE: New response types are added to this file automatically.
|
|
15
15
|
// Outdated response types can be removed manually as they are deprecated.
|
|
16
|
+
//${regenerateCommand ? ' To regenerate, run ' + regenerateCommand : ''}
|
|
16
17
|
//
|
|
17
18
|
|
|
18
19
|
`;
|
|
@@ -34,7 +35,10 @@ const generateTypeFiles = (
|
|
|
34
35
|
fs.mkdirSync(generatedDir, {recursive: true});
|
|
35
36
|
|
|
36
37
|
// Now write an index.js for each __generated__ dir.
|
|
37
|
-
fs.writeFileSync(
|
|
38
|
+
fs.writeFileSync(
|
|
39
|
+
indexFile(generatedDir),
|
|
40
|
+
indexPrelude(options.regenerateCommand),
|
|
41
|
+
);
|
|
38
42
|
}
|
|
39
43
|
};
|
|
40
44
|
|
|
@@ -85,7 +89,9 @@ const generateTypeFiles = (
|
|
|
85
89
|
`// Generated for operation '${name}' in file '../${path.basename(
|
|
86
90
|
fileName,
|
|
87
91
|
)}'\n` +
|
|
88
|
-
|
|
92
|
+
(options.regenerateCommand
|
|
93
|
+
? `// To regenerate, run '${options.regenerateCommand}'.\n`
|
|
94
|
+
: '') +
|
|
89
95
|
code,
|
|
90
96
|
});
|
|
91
97
|
fs.writeFileSync(targetPath, fileContents);
|
|
@@ -101,27 +107,33 @@ type SpyOptions = {
|
|
|
101
107
|
loosePragma?: string,
|
|
102
108
|
scalars?: Scalars,
|
|
103
109
|
strictNullability?: boolean,
|
|
110
|
+
/**
|
|
111
|
+
* The command that users should run to regenerate the types files.
|
|
112
|
+
*/
|
|
113
|
+
regenerateCommand?: string,
|
|
104
114
|
readOnlyArray?: boolean,
|
|
105
115
|
};
|
|
106
116
|
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
117
|
+
/**
|
|
118
|
+
* This function is expected to be called like so:
|
|
119
|
+
*
|
|
120
|
+
* jest.mock('graphql-tag', () => {
|
|
121
|
+
* const introspectionData = jest.requireActual(
|
|
122
|
+
* './server-introspection-response.json',
|
|
123
|
+
* );
|
|
124
|
+
* const {spyOnGraphqlTagToCollectQueries} = jest.requireActual(
|
|
125
|
+
* 'graphql-flow/jest-mock-graphql-tag.js');
|
|
126
|
+
*
|
|
127
|
+
* return spyOnGraphqlTagToCollectQueries(
|
|
128
|
+
* jest.requireActual('graphql-tag'),
|
|
129
|
+
* introspectionData,
|
|
130
|
+
* );
|
|
131
|
+
* });
|
|
132
|
+
*
|
|
133
|
+
* If both pragma and loosePragma are empty, then all graphql
|
|
134
|
+
* documents will be processed. Otherwise, only documents
|
|
135
|
+
* with one of the pragmas will be processed.
|
|
136
|
+
*/
|
|
125
137
|
const spyOnGraphqlTagToCollectQueries = (
|
|
126
138
|
realGraphqlTag: GraphqlTagFn,
|
|
127
139
|
introspectionData: IntrospectionQuery,
|
|
@@ -174,6 +186,7 @@ const processPragmas = (
|
|
|
174
186
|
|
|
175
187
|
if (autogen || autogenStrict || noPragmas) {
|
|
176
188
|
return {
|
|
189
|
+
regenerateCommand: options.regenerateCommand,
|
|
177
190
|
strictNullability: noPragmas
|
|
178
191
|
? options.strictNullability
|
|
179
192
|
: autogenStrict || !autogen,
|
package/src/types.js
CHANGED