@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 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 {findFilesWithQueries} from '../tools/graphql-flow/find-files-with-gql';
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
- findFilesWithQueries(path.join(__dirname, '..')).forEach(name => {
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 'yarn test queries'.\n` + code
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
- // This function is expected to be called like so:
98
- //
99
- // jest.mock('graphql-tag', () => {
100
- // const introspectionData = jest.requireActual(
101
- // './our-introspection-query.json',
102
- // );
103
- // const {spyOnGraphqlTagToCollectQueries} = jest.requireActual(
104
- // 'graphql-flow/jest-mock-graphql-tag.js');
105
- //
106
- // return spyOnGraphqlTagToCollectQueries(
107
- // jest.requireActual('graphql-tag'),
108
- // introspectionData,
109
- // );
110
- // });
111
- //
112
- // If both pragma and loosePragma are empty, then all graphql
113
- // documents will be processed. Otherwise, only documents
114
- // with one of the pragmas will be processed.
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(indexFile(generatedDir), indexPrelude);
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
- `// To regenerate, run 'yarn test queries'.\n` +
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
- // This function is expected to be called like so:
108
- //
109
- // jest.mock('graphql-tag', () => {
110
- // const introspectionData = jest.requireActual(
111
- // './our-introspection-query.json',
112
- // );
113
- // const {spyOnGraphqlTagToCollectQueries} = jest.requireActual(
114
- // 'graphql-flow/jest-mock-graphql-tag.js');
115
- //
116
- // return spyOnGraphqlTagToCollectQueries(
117
- // jest.requireActual('graphql-tag'),
118
- // introspectionData,
119
- // );
120
- // });
121
- //
122
- // If both pragma and loosePragma are empty, then all graphql
123
- // documents will be processed. Otherwise, only documents
124
- // with one of the pragmas will be processed.
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,
@@ -14,6 +14,7 @@ import type {
14
14
  export type Selections = $ReadOnlyArray<SelectionNode>;
15
15
 
16
16
  export type Options = {|
17
+ regenerateCommand?: string,
17
18
  strictNullability?: boolean, // default true
18
19
  readOnlyArray?: boolean, // default true
19
20
  scalars?: Scalars,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@khanacademy/graphql-flow",
3
- "version": "0.0.1",
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(indexFile(generatedDir), indexPrelude);
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
- `// To regenerate, run 'yarn test queries'.\n` +
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
- // This function is expected to be called like so:
108
- //
109
- // jest.mock('graphql-tag', () => {
110
- // const introspectionData = jest.requireActual(
111
- // './our-introspection-query.json',
112
- // );
113
- // const {spyOnGraphqlTagToCollectQueries} = jest.requireActual(
114
- // 'graphql-flow/jest-mock-graphql-tag.js');
115
- //
116
- // return spyOnGraphqlTagToCollectQueries(
117
- // jest.requireActual('graphql-tag'),
118
- // introspectionData,
119
- // );
120
- // });
121
- //
122
- // If both pragma and loosePragma are empty, then all graphql
123
- // documents will be processed. Otherwise, only documents
124
- // with one of the pragmas will be processed.
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
@@ -14,6 +14,7 @@ import type {
14
14
  export type Selections = $ReadOnlyArray<SelectionNode>;
15
15
 
16
16
  export type Options = {|
17
+ regenerateCommand?: string,
17
18
  strictNullability?: boolean, // default true
18
19
  readOnlyArray?: boolean, // default true
19
20
  scalars?: Scalars,