@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.
- package/.babelrc +6 -0
- package/.changeset/README.md +8 -0
- package/.changeset/config.json +11 -0
- package/.eslintignore +2 -0
- package/.eslintrc.js +10 -0
- package/.flowconfig +13 -0
- package/.github/actions/filter-files/action.yml +37 -0
- package/.github/actions/full-or-limited/action.yml +27 -0
- package/.github/actions/json-args/action.yml +32 -0
- package/.github/actions/setup/action.yml +28 -0
- package/.github/workflows/changeset-release.yml +80 -0
- package/.github/workflows/pr-checks.yml +64 -0
- package/.prettierrc +7 -0
- package/CHANGELOG.md +14 -0
- package/Readme.md +172 -0
- package/build-copy-source.js +28 -0
- package/dist/enums.js +57 -0
- package/dist/enums.js.flow +69 -0
- package/dist/generateResponseType.js +267 -0
- package/dist/generateResponseType.js.flow +419 -0
- package/dist/generateVariablesType.js +132 -0
- package/dist/generateVariablesType.js.flow +153 -0
- package/dist/index.js +88 -0
- package/dist/index.js.flow +93 -0
- package/dist/jest-mock-graphql-tag.js +169 -0
- package/dist/jest-mock-graphql-tag.js.flow +191 -0
- package/dist/schemaFromIntrospectionData.js +69 -0
- package/dist/schemaFromIntrospectionData.js.flow +68 -0
- package/dist/types.js +1 -0
- package/dist/types.js.flow +54 -0
- package/dist/utils.js +53 -0
- package/dist/utils.js.flow +50 -0
- package/flow-typed/npm/@babel/types_vx.x.x.js +5317 -0
- package/flow-typed/npm/jest_v23.x.x.js +1155 -0
- package/flow-typed/overrides.js +435 -0
- package/package.json +41 -0
- package/src/__test__/example-schema.graphql +65 -0
- package/src/__test__/graphql-flow.test.js +364 -0
- package/src/__test__/jest-mock-graphql-tag.test.js +51 -0
- package/src/enums.js +69 -0
- package/src/generateResponseType.js +419 -0
- package/src/generateVariablesType.js +153 -0
- package/src/index.js +93 -0
- package/src/jest-mock-graphql-tag.js +191 -0
- package/src/schemaFromIntrospectionData.js +68 -0
- package/src/types.js +54 -0
- package/src/utils.js +50 -0
- package/tools/find-files-with-gql.js +40 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
exports.documentToFlowTypes = exports.FlowGenerationError = void 0;
|
|
7
|
+
Object.defineProperty(exports, "spyOnGraphqlTagToCollectQueries", {
|
|
8
|
+
enumerable: true,
|
|
9
|
+
get: function () {
|
|
10
|
+
return _jestMockGraphqlTag.spyOnGraphqlTagToCollectQueries;
|
|
11
|
+
}
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
var _generateResponseType = require("./generateResponseType");
|
|
15
|
+
|
|
16
|
+
var _generateVariablesType = require("./generateVariablesType");
|
|
17
|
+
|
|
18
|
+
var _jestMockGraphqlTag = require("./jest-mock-graphql-tag");
|
|
19
|
+
|
|
20
|
+
/* eslint-disable no-console */
|
|
21
|
+
|
|
22
|
+
/* flow-uncovered-file */
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* This tool generates flowtype definitions from graphql queries.
|
|
26
|
+
*
|
|
27
|
+
* It relies on `introspection-query.json` existing in this directory,
|
|
28
|
+
* which is produced by running `./tools/graphql-flow/sendIntrospection.js`.
|
|
29
|
+
*/
|
|
30
|
+
const optionsToConfig = (schema, definitions, options, errors = []) => {
|
|
31
|
+
const internalOptions = {
|
|
32
|
+
strictNullability: (options === null || options === void 0 ? void 0 : options.strictNullability) ?? true,
|
|
33
|
+
readOnlyArray: (options === null || options === void 0 ? void 0 : options.readOnlyArray) ?? true,
|
|
34
|
+
scalars: (options === null || options === void 0 ? void 0 : options.scalars) ?? {}
|
|
35
|
+
};
|
|
36
|
+
const fragments = {};
|
|
37
|
+
definitions.forEach(def => {
|
|
38
|
+
if (def.kind === 'FragmentDefinition') {
|
|
39
|
+
fragments[def.name.value] = def;
|
|
40
|
+
}
|
|
41
|
+
});
|
|
42
|
+
const config = {
|
|
43
|
+
fragments,
|
|
44
|
+
schema,
|
|
45
|
+
errors,
|
|
46
|
+
...internalOptions
|
|
47
|
+
};
|
|
48
|
+
return config;
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
class FlowGenerationError extends Error {
|
|
52
|
+
constructor(errors) {
|
|
53
|
+
super(`Graphql-flow type generation failed! ${errors.join('; ')}`);
|
|
54
|
+
this.messages = errors;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
exports.FlowGenerationError = FlowGenerationError;
|
|
60
|
+
|
|
61
|
+
const documentToFlowTypes = (document, schema, options) => {
|
|
62
|
+
const errors = [];
|
|
63
|
+
const config = optionsToConfig(schema, document.definitions, options, errors);
|
|
64
|
+
const result = document.definitions.map(item => {
|
|
65
|
+
if (item.kind === 'OperationDefinition' && (item.operation === 'query' || item.operation === 'mutation') && item.name) {
|
|
66
|
+
const name = item.name.value;
|
|
67
|
+
const response = (0, _generateResponseType.generateResponseType)(schema, item, config);
|
|
68
|
+
const variables = (0, _generateVariablesType.generateVariablesType)(schema, item, config);
|
|
69
|
+
const typeName = `${name}Type`; // TODO(jared): Maybe make this template configurable?
|
|
70
|
+
// We'll see what's required to get webapp on board.
|
|
71
|
+
|
|
72
|
+
const code = `export type ${typeName} = {|\n variables: ${variables},\n response: ${response}\n|};`;
|
|
73
|
+
return {
|
|
74
|
+
name,
|
|
75
|
+
typeName,
|
|
76
|
+
code
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
}).filter(Boolean);
|
|
80
|
+
|
|
81
|
+
if (errors.length) {
|
|
82
|
+
throw new FlowGenerationError(errors);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
return result;
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
exports.documentToFlowTypes = documentToFlowTypes;
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
/* eslint-disable no-console */
|
|
2
|
+
/* flow-uncovered-file */
|
|
3
|
+
// @flow
|
|
4
|
+
/**
|
|
5
|
+
* This tool generates flowtype definitions from graphql queries.
|
|
6
|
+
*
|
|
7
|
+
* It relies on `introspection-query.json` existing in this directory,
|
|
8
|
+
* which is produced by running `./tools/graphql-flow/sendIntrospection.js`.
|
|
9
|
+
*/
|
|
10
|
+
import type {DefinitionNode, DocumentNode} from 'graphql';
|
|
11
|
+
|
|
12
|
+
import {generateResponseType} from './generateResponseType';
|
|
13
|
+
import {generateVariablesType} from './generateVariablesType';
|
|
14
|
+
export {spyOnGraphqlTagToCollectQueries} from './jest-mock-graphql-tag';
|
|
15
|
+
|
|
16
|
+
import type {Config, Options, Schema} from './types';
|
|
17
|
+
|
|
18
|
+
const optionsToConfig = (
|
|
19
|
+
schema: Schema,
|
|
20
|
+
definitions: $ReadOnlyArray<DefinitionNode>,
|
|
21
|
+
options?: Options,
|
|
22
|
+
errors: Array<string> = [],
|
|
23
|
+
): Config => {
|
|
24
|
+
const internalOptions = {
|
|
25
|
+
strictNullability: options?.strictNullability ?? true,
|
|
26
|
+
readOnlyArray: options?.readOnlyArray ?? true,
|
|
27
|
+
scalars: options?.scalars ?? {},
|
|
28
|
+
};
|
|
29
|
+
const fragments = {};
|
|
30
|
+
definitions.forEach((def) => {
|
|
31
|
+
if (def.kind === 'FragmentDefinition') {
|
|
32
|
+
fragments[def.name.value] = def;
|
|
33
|
+
}
|
|
34
|
+
});
|
|
35
|
+
const config = {
|
|
36
|
+
fragments,
|
|
37
|
+
schema,
|
|
38
|
+
errors,
|
|
39
|
+
...internalOptions,
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
return config;
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
export class FlowGenerationError extends Error {
|
|
46
|
+
messages: Array<string>;
|
|
47
|
+
constructor(errors: Array<string>) {
|
|
48
|
+
super(`Graphql-flow type generation failed! ${errors.join('; ')}`);
|
|
49
|
+
this.messages = errors;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export const documentToFlowTypes = (
|
|
54
|
+
document: DocumentNode,
|
|
55
|
+
schema: Schema,
|
|
56
|
+
options?: Options,
|
|
57
|
+
): $ReadOnlyArray<{
|
|
58
|
+
name: string,
|
|
59
|
+
typeName: string,
|
|
60
|
+
code: string,
|
|
61
|
+
}> => {
|
|
62
|
+
const errors: Array<string> = [];
|
|
63
|
+
const config = optionsToConfig(
|
|
64
|
+
schema,
|
|
65
|
+
document.definitions,
|
|
66
|
+
options,
|
|
67
|
+
errors,
|
|
68
|
+
);
|
|
69
|
+
const result = document.definitions
|
|
70
|
+
.map((item) => {
|
|
71
|
+
if (
|
|
72
|
+
item.kind === 'OperationDefinition' &&
|
|
73
|
+
(item.operation === 'query' || item.operation === 'mutation') &&
|
|
74
|
+
item.name
|
|
75
|
+
) {
|
|
76
|
+
const name = item.name.value;
|
|
77
|
+
const response = generateResponseType(schema, item, config);
|
|
78
|
+
const variables = generateVariablesType(schema, item, config);
|
|
79
|
+
|
|
80
|
+
const typeName = `${name}Type`;
|
|
81
|
+
// TODO(jared): Maybe make this template configurable?
|
|
82
|
+
// We'll see what's required to get webapp on board.
|
|
83
|
+
const code = `export type ${typeName} = {|\n variables: ${variables},\n response: ${response}\n|};`;
|
|
84
|
+
|
|
85
|
+
return {name, typeName, code};
|
|
86
|
+
}
|
|
87
|
+
})
|
|
88
|
+
.filter(Boolean);
|
|
89
|
+
if (errors.length) {
|
|
90
|
+
throw new FlowGenerationError(errors);
|
|
91
|
+
}
|
|
92
|
+
return result;
|
|
93
|
+
};
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
var _validation = require("graphql/validation");
|
|
4
|
+
|
|
5
|
+
var _graphql = require("graphql");
|
|
6
|
+
|
|
7
|
+
var _printer = require("graphql/language/printer");
|
|
8
|
+
|
|
9
|
+
var _apolloUtilities = require("apollo-utilities");
|
|
10
|
+
|
|
11
|
+
var _schemaFromIntrospectionData = require("./schemaFromIntrospectionData");
|
|
12
|
+
|
|
13
|
+
// Import this in your jest setup, to mock out graphql-tag!
|
|
14
|
+
// eslint-disable-line flowtype-errors/uncovered
|
|
15
|
+
const indexPrelude = `// @flow
|
|
16
|
+
//
|
|
17
|
+
// AUTOGENERATED
|
|
18
|
+
// NOTE: New response types are added to this file automatically.
|
|
19
|
+
// Outdated response types can be removed manually as they are deprecated.
|
|
20
|
+
//
|
|
21
|
+
|
|
22
|
+
`;
|
|
23
|
+
|
|
24
|
+
const generateTypeFiles = (schema, document, options) => {
|
|
25
|
+
const {
|
|
26
|
+
documentToFlowTypes
|
|
27
|
+
} = require('.');
|
|
28
|
+
|
|
29
|
+
const path = require('path');
|
|
30
|
+
|
|
31
|
+
const fs = require('fs');
|
|
32
|
+
|
|
33
|
+
const format = require('prettier-eslint'); // eslint-disable-line flowtype-errors/uncovered
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
const indexFile = generatedDir => path.join(generatedDir, 'index.js');
|
|
37
|
+
|
|
38
|
+
const maybeCreateGeneratedDir = generatedDir => {
|
|
39
|
+
if (!fs.existsSync(generatedDir)) {
|
|
40
|
+
fs.mkdirSync(generatedDir, {
|
|
41
|
+
recursive: true
|
|
42
|
+
}); // Now write an index.js for each __generated__ dir.
|
|
43
|
+
|
|
44
|
+
fs.writeFileSync(indexFile(generatedDir), indexPrelude);
|
|
45
|
+
}
|
|
46
|
+
}; /// Write export for __generated__/index.js if it doesn't exist
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
const writeToIndex = (filePath, typeName) => {
|
|
50
|
+
const index = indexFile(path.dirname(filePath));
|
|
51
|
+
const indexContents = fs.readFileSync(index, 'utf8');
|
|
52
|
+
const newLine = `export type {${typeName}} from './${path.basename(filePath)}';`;
|
|
53
|
+
|
|
54
|
+
if (indexContents.indexOf(path.basename(filePath)) === -1) {
|
|
55
|
+
fs.appendFileSync(index, newLine + '\n');
|
|
56
|
+
} else {
|
|
57
|
+
const lines = indexContents.split('\n').map(line => {
|
|
58
|
+
if (line.includes(path.basename(filePath))) {
|
|
59
|
+
return newLine;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
return line;
|
|
63
|
+
});
|
|
64
|
+
fs.writeFileSync(index, lines.join('\n'));
|
|
65
|
+
}
|
|
66
|
+
}; // Get the name of the file that `gql` was called from
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
const errorLines = new Error().stack.split('\n');
|
|
70
|
+
const fileName = errorLines[3].split('(').slice(-1)[0].split(':')[0];
|
|
71
|
+
const generated = documentToFlowTypes(document, schema, options);
|
|
72
|
+
generated.forEach(({
|
|
73
|
+
name,
|
|
74
|
+
typeName,
|
|
75
|
+
code
|
|
76
|
+
}) => {
|
|
77
|
+
// We write all generated files to a `__generated__` subdir to keep
|
|
78
|
+
// things tidy.
|
|
79
|
+
const targetFileName = `${typeName}.js`;
|
|
80
|
+
const generatedDir = path.join(path.dirname(fileName), '__generated__');
|
|
81
|
+
const targetPath = path.join(generatedDir, targetFileName);
|
|
82
|
+
maybeCreateGeneratedDir(generatedDir); // NOTE: Uncomment this to write the query definitions to disk if
|
|
83
|
+
// you need to add new features to the flow type generation
|
|
84
|
+
// fs.writeFileSync(
|
|
85
|
+
// targetFileName + '.query',
|
|
86
|
+
// JSON.stringify(definitions, null, 2),
|
|
87
|
+
// );
|
|
88
|
+
|
|
89
|
+
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
|
+
});
|
|
92
|
+
fs.writeFileSync(targetPath, fileContents);
|
|
93
|
+
writeToIndex(targetPath, typeName);
|
|
94
|
+
});
|
|
95
|
+
};
|
|
96
|
+
|
|
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.
|
|
115
|
+
const spyOnGraphqlTagToCollectQueries = (realGraphqlTag, introspectionData, options = {}) => {
|
|
116
|
+
const collection = [];
|
|
117
|
+
const clientSchema = (0, _graphql.buildClientSchema)(introspectionData);
|
|
118
|
+
const schema = (0, _schemaFromIntrospectionData.schemaFromIntrospectionData)(introspectionData);
|
|
119
|
+
|
|
120
|
+
const wrapper = function gql() {
|
|
121
|
+
const document = realGraphqlTag.apply(this, arguments); // eslint-disable-line flowtype-errors/uncovered
|
|
122
|
+
|
|
123
|
+
const hasNonFragments = document.definitions.some(({
|
|
124
|
+
kind
|
|
125
|
+
}) => kind !== 'FragmentDefinition');
|
|
126
|
+
|
|
127
|
+
if (hasNonFragments) {
|
|
128
|
+
// eslint-disable-next-line flowtype-errors/uncovered
|
|
129
|
+
const withTypeNames = (0, _apolloUtilities.addTypenameToDocument)(document);
|
|
130
|
+
collection.push({
|
|
131
|
+
raw: (0, _printer.print)(withTypeNames),
|
|
132
|
+
errors: (0, _validation.validate)(clientSchema, document)
|
|
133
|
+
});
|
|
134
|
+
const rawSource = arguments[0].raw[0]; // eslint-disable-line flowtype-errors/uncovered
|
|
135
|
+
|
|
136
|
+
const processedOptions = processPragmas(options, rawSource);
|
|
137
|
+
|
|
138
|
+
if (processedOptions) {
|
|
139
|
+
generateTypeFiles(schema, withTypeNames, processedOptions);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
return document;
|
|
144
|
+
};
|
|
145
|
+
|
|
146
|
+
wrapper.collectedQueries = collection;
|
|
147
|
+
return wrapper;
|
|
148
|
+
};
|
|
149
|
+
|
|
150
|
+
const processPragmas = (options, rawSource) => {
|
|
151
|
+
const autogen = options.loosePragma ? rawSource.includes(options.loosePragma) : false;
|
|
152
|
+
const autogenStrict = options.pragma ? rawSource.includes(options.pragma) : false;
|
|
153
|
+
const noPragmas = !options.loosePragma && !options.pragma;
|
|
154
|
+
|
|
155
|
+
if (autogen || autogenStrict || noPragmas) {
|
|
156
|
+
return {
|
|
157
|
+
strictNullability: noPragmas ? options.strictNullability : autogenStrict || !autogen,
|
|
158
|
+
readOnlyArray: options.readOnlyArray,
|
|
159
|
+
scalars: options.scalars
|
|
160
|
+
};
|
|
161
|
+
} else {
|
|
162
|
+
return null;
|
|
163
|
+
}
|
|
164
|
+
};
|
|
165
|
+
|
|
166
|
+
module.exports = {
|
|
167
|
+
processPragmas,
|
|
168
|
+
spyOnGraphqlTagToCollectQueries
|
|
169
|
+
};
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
// @flow
|
|
2
|
+
// Import this in your jest setup, to mock out graphql-tag!
|
|
3
|
+
import type {DocumentNode, IntrospectionQuery} from 'graphql';
|
|
4
|
+
import type {Schema, Options, Scalars} from './types';
|
|
5
|
+
import {validate} from 'graphql/validation';
|
|
6
|
+
import {buildClientSchema} from 'graphql';
|
|
7
|
+
import {print} from 'graphql/language/printer';
|
|
8
|
+
import {addTypenameToDocument} from 'apollo-utilities'; // eslint-disable-line flowtype-errors/uncovered
|
|
9
|
+
import {schemaFromIntrospectionData} from './schemaFromIntrospectionData';
|
|
10
|
+
|
|
11
|
+
const indexPrelude = `// @flow
|
|
12
|
+
//
|
|
13
|
+
// AUTOGENERATED
|
|
14
|
+
// NOTE: New response types are added to this file automatically.
|
|
15
|
+
// Outdated response types can be removed manually as they are deprecated.
|
|
16
|
+
//
|
|
17
|
+
|
|
18
|
+
`;
|
|
19
|
+
|
|
20
|
+
const generateTypeFiles = (
|
|
21
|
+
schema: Schema,
|
|
22
|
+
document: DocumentNode,
|
|
23
|
+
options: Options,
|
|
24
|
+
) => {
|
|
25
|
+
const {documentToFlowTypes} = require('.');
|
|
26
|
+
const path = require('path');
|
|
27
|
+
const fs = require('fs');
|
|
28
|
+
const format: ({text: string}) => string = require('prettier-eslint'); // eslint-disable-line flowtype-errors/uncovered
|
|
29
|
+
|
|
30
|
+
const indexFile = (generatedDir) => path.join(generatedDir, 'index.js');
|
|
31
|
+
|
|
32
|
+
const maybeCreateGeneratedDir = (generatedDir) => {
|
|
33
|
+
if (!fs.existsSync(generatedDir)) {
|
|
34
|
+
fs.mkdirSync(generatedDir, {recursive: true});
|
|
35
|
+
|
|
36
|
+
// Now write an index.js for each __generated__ dir.
|
|
37
|
+
fs.writeFileSync(indexFile(generatedDir), indexPrelude);
|
|
38
|
+
}
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
/// Write export for __generated__/index.js if it doesn't exist
|
|
42
|
+
const writeToIndex = (filePath, typeName) => {
|
|
43
|
+
const index = indexFile(path.dirname(filePath));
|
|
44
|
+
const indexContents = fs.readFileSync(index, 'utf8');
|
|
45
|
+
const newLine = `export type {${typeName}} from './${path.basename(
|
|
46
|
+
filePath,
|
|
47
|
+
)}';`;
|
|
48
|
+
if (indexContents.indexOf(path.basename(filePath)) === -1) {
|
|
49
|
+
fs.appendFileSync(index, newLine + '\n');
|
|
50
|
+
} else {
|
|
51
|
+
const lines = indexContents.split('\n').map((line) => {
|
|
52
|
+
if (line.includes(path.basename(filePath))) {
|
|
53
|
+
return newLine;
|
|
54
|
+
}
|
|
55
|
+
return line;
|
|
56
|
+
});
|
|
57
|
+
fs.writeFileSync(index, lines.join('\n'));
|
|
58
|
+
}
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
// Get the name of the file that `gql` was called from
|
|
62
|
+
const errorLines = new Error().stack.split('\n');
|
|
63
|
+
const fileName = errorLines[3].split('(').slice(-1)[0].split(':')[0];
|
|
64
|
+
|
|
65
|
+
const generated = documentToFlowTypes(document, schema, options);
|
|
66
|
+
generated.forEach(({name, typeName, code}) => {
|
|
67
|
+
// We write all generated files to a `__generated__` subdir to keep
|
|
68
|
+
// things tidy.
|
|
69
|
+
const targetFileName = `${typeName}.js`;
|
|
70
|
+
const generatedDir = path.join(path.dirname(fileName), '__generated__');
|
|
71
|
+
const targetPath = path.join(generatedDir, targetFileName);
|
|
72
|
+
|
|
73
|
+
maybeCreateGeneratedDir(generatedDir);
|
|
74
|
+
|
|
75
|
+
// NOTE: Uncomment this to write the query definitions to disk if
|
|
76
|
+
// you need to add new features to the flow type generation
|
|
77
|
+
// fs.writeFileSync(
|
|
78
|
+
// targetFileName + '.query',
|
|
79
|
+
// JSON.stringify(definitions, null, 2),
|
|
80
|
+
// );
|
|
81
|
+
const fileContents = format({
|
|
82
|
+
text:
|
|
83
|
+
'// @' +
|
|
84
|
+
`flow\n// AUTOGENERATED -- DO NOT EDIT\n` +
|
|
85
|
+
`// Generated for operation '${name}' in file '../${path.basename(
|
|
86
|
+
fileName,
|
|
87
|
+
)}'\n` +
|
|
88
|
+
`// To regenerate, run 'yarn test queries'.\n` +
|
|
89
|
+
code,
|
|
90
|
+
});
|
|
91
|
+
fs.writeFileSync(targetPath, fileContents);
|
|
92
|
+
|
|
93
|
+
writeToIndex(targetPath, typeName);
|
|
94
|
+
});
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
type GraphqlTagFn = (raw: string, ...args: Array<any>) => DocumentNode;
|
|
98
|
+
|
|
99
|
+
type SpyOptions = {
|
|
100
|
+
pragma?: string,
|
|
101
|
+
loosePragma?: string,
|
|
102
|
+
scalars?: Scalars,
|
|
103
|
+
strictNullability?: boolean,
|
|
104
|
+
readOnlyArray?: boolean,
|
|
105
|
+
};
|
|
106
|
+
|
|
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.
|
|
125
|
+
const spyOnGraphqlTagToCollectQueries = (
|
|
126
|
+
realGraphqlTag: GraphqlTagFn,
|
|
127
|
+
introspectionData: IntrospectionQuery,
|
|
128
|
+
options: SpyOptions = {},
|
|
129
|
+
): GraphqlTagFn => {
|
|
130
|
+
const collection: Array<{
|
|
131
|
+
raw: string,
|
|
132
|
+
errors: $ReadOnlyArray<Error>,
|
|
133
|
+
}> = [];
|
|
134
|
+
|
|
135
|
+
const clientSchema = buildClientSchema(introspectionData);
|
|
136
|
+
const schema = schemaFromIntrospectionData(introspectionData);
|
|
137
|
+
|
|
138
|
+
const wrapper = function gql() {
|
|
139
|
+
const document: DocumentNode = realGraphqlTag.apply(this, arguments); // eslint-disable-line flowtype-errors/uncovered
|
|
140
|
+
const hasNonFragments = document.definitions.some(
|
|
141
|
+
({kind}) => kind !== 'FragmentDefinition',
|
|
142
|
+
);
|
|
143
|
+
if (hasNonFragments) {
|
|
144
|
+
// eslint-disable-next-line flowtype-errors/uncovered
|
|
145
|
+
const withTypeNames: DocumentNode = addTypenameToDocument(document);
|
|
146
|
+
collection.push({
|
|
147
|
+
raw: print(withTypeNames),
|
|
148
|
+
errors: validate(clientSchema, document),
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
const rawSource: string = arguments[0].raw[0]; // eslint-disable-line flowtype-errors/uncovered
|
|
152
|
+
const processedOptions = processPragmas(options, rawSource);
|
|
153
|
+
if (processedOptions) {
|
|
154
|
+
generateTypeFiles(schema, withTypeNames, processedOptions);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
return document;
|
|
158
|
+
};
|
|
159
|
+
wrapper.collectedQueries = collection;
|
|
160
|
+
return wrapper;
|
|
161
|
+
};
|
|
162
|
+
|
|
163
|
+
const processPragmas = (
|
|
164
|
+
options: SpyOptions,
|
|
165
|
+
rawSource: string,
|
|
166
|
+
): null | Options => {
|
|
167
|
+
const autogen = options.loosePragma
|
|
168
|
+
? rawSource.includes(options.loosePragma)
|
|
169
|
+
: false;
|
|
170
|
+
const autogenStrict = options.pragma
|
|
171
|
+
? rawSource.includes(options.pragma)
|
|
172
|
+
: false;
|
|
173
|
+
const noPragmas = !options.loosePragma && !options.pragma;
|
|
174
|
+
|
|
175
|
+
if (autogen || autogenStrict || noPragmas) {
|
|
176
|
+
return {
|
|
177
|
+
strictNullability: noPragmas
|
|
178
|
+
? options.strictNullability
|
|
179
|
+
: autogenStrict || !autogen,
|
|
180
|
+
readOnlyArray: options.readOnlyArray,
|
|
181
|
+
scalars: options.scalars,
|
|
182
|
+
};
|
|
183
|
+
} else {
|
|
184
|
+
return null;
|
|
185
|
+
}
|
|
186
|
+
};
|
|
187
|
+
|
|
188
|
+
module.exports = {
|
|
189
|
+
processPragmas,
|
|
190
|
+
spyOnGraphqlTagToCollectQueries,
|
|
191
|
+
};
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
exports.schemaFromIntrospectionData = void 0;
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Takes the introspectionQuery response and parses it into the "Schema"
|
|
10
|
+
* type that we use to look up types, interfaces, etc.
|
|
11
|
+
*/
|
|
12
|
+
const schemaFromIntrospectionData = schema => {
|
|
13
|
+
const result = {
|
|
14
|
+
interfacesByName: {},
|
|
15
|
+
typesByName: {},
|
|
16
|
+
inputObjectsByName: {},
|
|
17
|
+
unionsByName: {},
|
|
18
|
+
enumsByName: {}
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
schema.__schema.types.forEach(type => {
|
|
22
|
+
if (type.kind === 'ENUM') {
|
|
23
|
+
result.enumsByName[type.name] = type;
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
if (type.kind === 'UNION') {
|
|
28
|
+
result.unionsByName[type.name] = type;
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
if (type.kind === 'INTERFACE') {
|
|
33
|
+
result.interfacesByName[type.name] = { ...type,
|
|
34
|
+
possibleTypesByName: {},
|
|
35
|
+
fieldsByName: {}
|
|
36
|
+
};
|
|
37
|
+
type.possibleTypes.forEach(p => result.interfacesByName[type.name].possibleTypesByName[p.name] = true);
|
|
38
|
+
type.fields.forEach(field => {
|
|
39
|
+
result.interfacesByName[type.name].fieldsByName[field.name] = field;
|
|
40
|
+
});
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
if (type.kind === 'INPUT_OBJECT') {
|
|
45
|
+
result.inputObjectsByName[type.name] = type;
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
if (type.kind === 'SCALAR') {
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
result.typesByName[type.name] = { ...type,
|
|
54
|
+
fieldsByName: {}
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
if (!type.fields) {
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
type.fields.forEach(field => {
|
|
62
|
+
result.typesByName[type.name].fieldsByName[field.name] = field;
|
|
63
|
+
});
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
return result;
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
exports.schemaFromIntrospectionData = schemaFromIntrospectionData;
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
// @flow
|
|
2
|
+
/**
|
|
3
|
+
* Takes the introspectionQuery response and parses it into the "Schema"
|
|
4
|
+
* type that we use to look up types, interfaces, etc.
|
|
5
|
+
*/
|
|
6
|
+
import type {IntrospectionQuery} from 'graphql';
|
|
7
|
+
import type {Schema} from './types';
|
|
8
|
+
|
|
9
|
+
export const schemaFromIntrospectionData = (
|
|
10
|
+
schema: IntrospectionQuery,
|
|
11
|
+
): Schema => {
|
|
12
|
+
const result: Schema = {
|
|
13
|
+
interfacesByName: {},
|
|
14
|
+
typesByName: {},
|
|
15
|
+
inputObjectsByName: {},
|
|
16
|
+
unionsByName: {},
|
|
17
|
+
enumsByName: {},
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
schema.__schema.types.forEach((type) => {
|
|
21
|
+
if (type.kind === 'ENUM') {
|
|
22
|
+
result.enumsByName[type.name] = type;
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
if (type.kind === 'UNION') {
|
|
26
|
+
result.unionsByName[type.name] = type;
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
if (type.kind === 'INTERFACE') {
|
|
30
|
+
result.interfacesByName[type.name] = {
|
|
31
|
+
...type,
|
|
32
|
+
possibleTypesByName: {},
|
|
33
|
+
fieldsByName: {},
|
|
34
|
+
};
|
|
35
|
+
type.possibleTypes.forEach(
|
|
36
|
+
(p) =>
|
|
37
|
+
(result.interfacesByName[type.name].possibleTypesByName[
|
|
38
|
+
p.name
|
|
39
|
+
] = true),
|
|
40
|
+
);
|
|
41
|
+
type.fields.forEach((field) => {
|
|
42
|
+
result.interfacesByName[type.name].fieldsByName[field.name] =
|
|
43
|
+
field;
|
|
44
|
+
});
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
if (type.kind === 'INPUT_OBJECT') {
|
|
48
|
+
result.inputObjectsByName[type.name] = type;
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
if (type.kind === 'SCALAR') {
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
result.typesByName[type.name] = {
|
|
55
|
+
...type,
|
|
56
|
+
fieldsByName: {},
|
|
57
|
+
};
|
|
58
|
+
if (!type.fields) {
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
type.fields.forEach((field) => {
|
|
63
|
+
result.typesByName[type.name].fieldsByName[field.name] = field;
|
|
64
|
+
});
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
return result;
|
|
68
|
+
};
|
package/dist/types.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"use strict";
|