vscode-apollo 1.19.3 → 1.20.0
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/.changeset/README.md +8 -0
- package/.changeset/config.json +14 -0
- package/.circleci/config.yml +82 -0
- package/.eslintrc.js +10 -0
- package/.gitattributes +1 -0
- package/.github/workflows/release.yml +95 -0
- package/.gitleaks.toml +26 -0
- package/.nvmrc +1 -0
- package/.prettierrc +5 -0
- package/.vscode/launch.json +61 -0
- package/.vscode/settings.json +16 -0
- package/.vscode/tasks.json +18 -0
- package/.vscodeignore +17 -1
- package/CHANGELOG.md +172 -1
- package/LICENSE +2 -2
- package/README.md +9 -9
- package/codegen.yml +12 -0
- package/images/IconRun.svg +8 -0
- package/jest.config.ts +11 -0
- package/package.json +102 -22
- package/renovate.json +23 -0
- package/src/__mocks__/fs.js +3 -0
- package/src/__tests__/statusBar.test.ts +8 -7
- package/src/debug.ts +2 -5
- package/src/env/fetch/fetch.ts +32 -0
- package/src/env/fetch/global.ts +30 -0
- package/src/env/fetch/index.d.ts +2 -0
- package/src/env/fetch/index.ts +2 -0
- package/src/env/fetch/url.ts +9 -0
- package/src/env/index.ts +4 -0
- package/src/env/polyfills/array.ts +17 -0
- package/src/env/polyfills/index.ts +2 -0
- package/src/env/polyfills/object.ts +7 -0
- package/src/env/typescript-utility-types.ts +2 -0
- package/src/extension.ts +106 -37
- package/src/language-server/__tests__/diagnostics.test.ts +86 -0
- package/src/language-server/__tests__/document.test.ts +187 -0
- package/src/language-server/__tests__/fileSet.test.ts +46 -0
- package/src/language-server/__tests__/fixtures/starwarsSchema.ts +1917 -0
- package/src/language-server/config/__tests__/config.ts +128 -0
- package/src/language-server/config/__tests__/loadConfig.ts +508 -0
- package/src/language-server/config/__tests__/utils.ts +106 -0
- package/src/language-server/config/config.ts +219 -0
- package/src/language-server/config/index.ts +3 -0
- package/src/language-server/config/loadConfig.ts +228 -0
- package/src/language-server/config/utils.ts +56 -0
- package/src/language-server/diagnostics.ts +109 -0
- package/src/language-server/document.ts +277 -0
- package/src/language-server/engine/GraphQLDataSource.ts +124 -0
- package/src/language-server/engine/index.ts +105 -0
- package/src/language-server/engine/operations/frontendUrlRoot.ts +7 -0
- package/src/language-server/engine/operations/schemaTagsAndFieldStats.ts +24 -0
- package/src/language-server/errors/__tests__/NoMissingClientDirectives.test.ts +220 -0
- package/src/language-server/errors/logger.ts +58 -0
- package/src/language-server/errors/validation.ts +277 -0
- package/src/language-server/fileSet.ts +65 -0
- package/src/language-server/format.ts +48 -0
- package/src/language-server/graphqlTypes.ts +7176 -0
- package/src/language-server/index.ts +29 -0
- package/src/language-server/languageProvider.ts +798 -0
- package/src/language-server/loadingHandler.ts +64 -0
- package/src/language-server/project/base.ts +399 -0
- package/src/language-server/project/client.ts +602 -0
- package/src/language-server/project/defaultClientSchema.ts +45 -0
- package/src/language-server/project/service.ts +48 -0
- package/src/language-server/providers/schema/__tests__/file.ts +150 -0
- package/src/language-server/providers/schema/base.ts +15 -0
- package/src/language-server/providers/schema/endpoint.ts +157 -0
- package/src/language-server/providers/schema/engine.ts +197 -0
- package/src/language-server/providers/schema/file.ts +167 -0
- package/src/language-server/providers/schema/index.ts +75 -0
- package/src/language-server/server.ts +252 -0
- package/src/language-server/typings/codemirror.d.ts +4 -0
- package/src/language-server/typings/graphql.d.ts +27 -0
- package/src/language-server/utilities/__tests__/graphql.test.ts +411 -0
- package/src/language-server/utilities/__tests__/uri.ts +55 -0
- package/src/language-server/utilities/debouncer.ts +8 -0
- package/src/language-server/utilities/debug.ts +89 -0
- package/src/language-server/utilities/graphql.ts +432 -0
- package/src/language-server/utilities/index.ts +3 -0
- package/src/language-server/utilities/source.ts +182 -0
- package/src/language-server/utilities/uri.ts +19 -0
- package/src/language-server/workspace.ts +262 -0
- package/src/languageServerClient.ts +19 -12
- package/src/messages.ts +84 -0
- package/src/statusBar.ts +5 -5
- package/src/tools/__tests__/buildServiceDefinition.test.ts +491 -0
- package/src/tools/__tests__/snapshotSerializers/astSerializer.ts +19 -0
- package/src/tools/__tests__/snapshotSerializers/graphQLTypeSerializer.ts +14 -0
- package/src/tools/buildServiceDefinition.ts +241 -0
- package/src/tools/index.ts +6 -0
- package/src/tools/schema/index.ts +2 -0
- package/src/tools/schema/resolveObject.ts +18 -0
- package/src/tools/schema/resolverMap.ts +23 -0
- package/src/tools/utilities/graphql.ts +22 -0
- package/src/tools/utilities/index.ts +3 -0
- package/src/tools/utilities/invariant.ts +5 -0
- package/src/tools/utilities/predicates.ts +5 -0
- package/src/utils.ts +1 -16
- package/syntaxes/graphql.js.json +3 -3
- package/syntaxes/graphql.json +13 -9
- package/syntaxes/graphql.lua.json +51 -0
- package/syntaxes/graphql.rb.json +1 -1
- package/tsconfig.build.json +4 -0
- package/tsconfig.json +20 -7
- package/create-server-symlink.js +0 -8
- package/lib/debug.d.ts +0 -11
- package/lib/debug.d.ts.map +0 -1
- package/lib/debug.js +0 -48
- package/lib/debug.js.map +0 -1
- package/lib/extension.d.ts +0 -4
- package/lib/extension.d.ts.map +0 -1
- package/lib/extension.js +0 -187
- package/lib/extension.js.map +0 -1
- package/lib/languageServerClient.d.ts +0 -4
- package/lib/languageServerClient.d.ts.map +0 -1
- package/lib/languageServerClient.js +0 -57
- package/lib/languageServerClient.js.map +0 -1
- package/lib/statusBar.d.ts +0 -24
- package/lib/statusBar.d.ts.map +0 -1
- package/lib/statusBar.js +0 -46
- package/lib/statusBar.js.map +0 -1
- package/lib/testRunner/index.d.ts +0 -3
- package/lib/testRunner/index.d.ts.map +0 -1
- package/lib/testRunner/index.js +0 -49
- package/lib/testRunner/index.js.map +0 -1
- package/lib/testRunner/jest-config.d.ts +0 -14
- package/lib/testRunner/jest-config.d.ts.map +0 -1
- package/lib/testRunner/jest-config.js +0 -18
- package/lib/testRunner/jest-config.js.map +0 -1
- package/lib/testRunner/jest-vscode-environment.d.ts +0 -2
- package/lib/testRunner/jest-vscode-environment.d.ts.map +0 -1
- package/lib/testRunner/jest-vscode-environment.js +0 -19
- package/lib/testRunner/jest-vscode-environment.js.map +0 -1
- package/lib/testRunner/jest-vscode-framework-setup.d.ts +0 -1
- package/lib/testRunner/jest-vscode-framework-setup.d.ts.map +0 -1
- package/lib/testRunner/jest-vscode-framework-setup.js +0 -3
- package/lib/testRunner/jest-vscode-framework-setup.js.map +0 -1
- package/lib/testRunner/vscode-test-script.d.ts +0 -2
- package/lib/testRunner/vscode-test-script.d.ts.map +0 -1
- package/lib/testRunner/vscode-test-script.js +0 -23
- package/lib/testRunner/vscode-test-script.js.map +0 -1
- package/lib/utils.d.ts +0 -18
- package/lib/utils.d.ts.map +0 -1
- package/lib/utils.js +0 -52
- package/lib/utils.js.map +0 -1
- package/src/testRunner/README.md +0 -23
- package/src/testRunner/index.ts +0 -72
- package/src/testRunner/jest-config.ts +0 -17
- package/src/testRunner/jest-vscode-environment.ts +0 -25
- package/src/testRunner/jest-vscode-framework-setup.ts +0 -10
- package/src/testRunner/jest.d.ts +0 -37
- package/src/testRunner/vscode-test-script.ts +0 -38
- package/tsconfig.test.json +0 -4
- package/tsconfig.tsbuildinfo +0 -2486
|
@@ -0,0 +1,602 @@
|
|
|
1
|
+
import { GraphQLProject } from "./base";
|
|
2
|
+
import {
|
|
3
|
+
GraphQLSchema,
|
|
4
|
+
GraphQLError,
|
|
5
|
+
printSchema,
|
|
6
|
+
buildSchema,
|
|
7
|
+
Source,
|
|
8
|
+
TypeInfo,
|
|
9
|
+
visit,
|
|
10
|
+
visitWithTypeInfo,
|
|
11
|
+
FragmentDefinitionNode,
|
|
12
|
+
Kind,
|
|
13
|
+
FragmentSpreadNode,
|
|
14
|
+
separateOperations,
|
|
15
|
+
OperationDefinitionNode,
|
|
16
|
+
extendSchema,
|
|
17
|
+
DocumentNode,
|
|
18
|
+
FieldNode,
|
|
19
|
+
ObjectTypeDefinitionNode,
|
|
20
|
+
GraphQLObjectType,
|
|
21
|
+
DefinitionNode,
|
|
22
|
+
ExecutableDefinitionNode,
|
|
23
|
+
print,
|
|
24
|
+
} from "graphql";
|
|
25
|
+
import { ValidationRule } from "graphql/validation/ValidationContext";
|
|
26
|
+
import { NotificationHandler, DiagnosticSeverity } from "vscode-languageserver";
|
|
27
|
+
import LZString from "lz-string";
|
|
28
|
+
import { stringifyUrl } from "query-string";
|
|
29
|
+
|
|
30
|
+
import { rangeForASTNode } from "../utilities/source";
|
|
31
|
+
import { formatMS } from "../format";
|
|
32
|
+
import { LoadingHandler } from "../loadingHandler";
|
|
33
|
+
import { apolloClientSchemaDocument } from "./defaultClientSchema";
|
|
34
|
+
|
|
35
|
+
import {
|
|
36
|
+
FieldLatenciesMS,
|
|
37
|
+
SchemaTag,
|
|
38
|
+
ServiceID,
|
|
39
|
+
ClientIdentity,
|
|
40
|
+
} from "../engine";
|
|
41
|
+
import { ClientConfig } from "../config";
|
|
42
|
+
import {
|
|
43
|
+
removeDirectives,
|
|
44
|
+
removeDirectiveAnnotatedFields,
|
|
45
|
+
withTypenameFieldAddedWhereNeeded,
|
|
46
|
+
ClientSchemaInfo,
|
|
47
|
+
isDirectiveDefinitionNode,
|
|
48
|
+
} from "../utilities/graphql";
|
|
49
|
+
import { defaultValidationRules } from "../errors/validation";
|
|
50
|
+
|
|
51
|
+
import {
|
|
52
|
+
collectExecutableDefinitionDiagnositics,
|
|
53
|
+
DiagnosticSet,
|
|
54
|
+
diagnosticsFromError,
|
|
55
|
+
} from "../diagnostics";
|
|
56
|
+
import URI from "vscode-uri";
|
|
57
|
+
import type { EngineDecoration } from "src/messages";
|
|
58
|
+
import { join } from "path";
|
|
59
|
+
|
|
60
|
+
type Maybe<T> = null | undefined | T;
|
|
61
|
+
|
|
62
|
+
function schemaHasASTNodes(schema: GraphQLSchema): boolean {
|
|
63
|
+
const queryType = schema && schema.getQueryType();
|
|
64
|
+
return !!(queryType && queryType.astNode);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function augmentSchemaWithGeneratedSDLIfNeeded(
|
|
68
|
+
schema: GraphQLSchema
|
|
69
|
+
): GraphQLSchema {
|
|
70
|
+
if (schemaHasASTNodes(schema)) return schema;
|
|
71
|
+
|
|
72
|
+
const sdl = printSchema(schema);
|
|
73
|
+
|
|
74
|
+
return buildSchema(
|
|
75
|
+
// Rebuild the schema from a generated source file and attach the source to a `graphql-schema:/`
|
|
76
|
+
// URI that can be loaded as an in-memory file by VS Code.
|
|
77
|
+
new Source(sdl, `graphql-schema:/schema.graphql?${encodeURIComponent(sdl)}`)
|
|
78
|
+
);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export function isClientProject(
|
|
82
|
+
project: GraphQLProject
|
|
83
|
+
): project is GraphQLClientProject {
|
|
84
|
+
return project instanceof GraphQLClientProject;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export interface GraphQLClientProjectConfig {
|
|
88
|
+
clientIdentity?: ClientIdentity;
|
|
89
|
+
config: ClientConfig;
|
|
90
|
+
configFolderURI: URI;
|
|
91
|
+
loadingHandler: LoadingHandler;
|
|
92
|
+
}
|
|
93
|
+
export class GraphQLClientProject extends GraphQLProject {
|
|
94
|
+
public serviceID?: string;
|
|
95
|
+
public config!: ClientConfig;
|
|
96
|
+
|
|
97
|
+
private serviceSchema?: GraphQLSchema;
|
|
98
|
+
|
|
99
|
+
private _onDecorations?: (any: any) => void;
|
|
100
|
+
private _onSchemaTags?: NotificationHandler<[ServiceID, SchemaTag[]]>;
|
|
101
|
+
|
|
102
|
+
private fieldLatenciesMS?: FieldLatenciesMS;
|
|
103
|
+
private frontendUrlRoot?: string;
|
|
104
|
+
|
|
105
|
+
private _validationRules?: ValidationRule[];
|
|
106
|
+
|
|
107
|
+
public diagnosticSet?: DiagnosticSet;
|
|
108
|
+
|
|
109
|
+
constructor({
|
|
110
|
+
config,
|
|
111
|
+
loadingHandler,
|
|
112
|
+
configFolderURI,
|
|
113
|
+
clientIdentity,
|
|
114
|
+
}: GraphQLClientProjectConfig) {
|
|
115
|
+
super({ config, configFolderURI, loadingHandler, clientIdentity });
|
|
116
|
+
this.serviceID = config.graph;
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* This function is used in the Array.filter function below it to remove any .env files and config files.
|
|
120
|
+
* If there are 0 files remaining after removing those files, we should warn the user that their config
|
|
121
|
+
* may be wrong. We shouldn't throw an error here, since they could just be initially setting up a project
|
|
122
|
+
* and there's no way to know for sure that there _should_ be files.
|
|
123
|
+
*/
|
|
124
|
+
const filterConfigAndEnvFiles = (path: string) =>
|
|
125
|
+
!(
|
|
126
|
+
path.includes("apollo.config") ||
|
|
127
|
+
path.includes(".env") ||
|
|
128
|
+
(config.configURI && path === config.configURI.fsPath)
|
|
129
|
+
);
|
|
130
|
+
|
|
131
|
+
if (this.allIncludedFiles().filter(filterConfigAndEnvFiles).length === 0) {
|
|
132
|
+
console.warn(
|
|
133
|
+
"⚠️ It looks like there are 0 files associated with this Apollo Project. " +
|
|
134
|
+
"This may be because you don't have any files yet, or your includes/excludes " +
|
|
135
|
+
"fields are configured incorrectly, and Apollo can't find your files. " +
|
|
136
|
+
"For help configuring Apollo projects, see this guide: https://go.apollo.dev/t/config"
|
|
137
|
+
);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
const { validationRules } = this.config.client;
|
|
141
|
+
if (typeof validationRules === "function") {
|
|
142
|
+
this._validationRules = defaultValidationRules.filter(validationRules);
|
|
143
|
+
} else {
|
|
144
|
+
this._validationRules = validationRules;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
this.loadEngineData();
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
get displayName(): string {
|
|
151
|
+
return this.config.graph || "Unnamed Project";
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
initialize() {
|
|
155
|
+
return [this.scanAllIncludedFiles(), this.loadServiceSchema()];
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
public getProjectStats() {
|
|
159
|
+
// use this to remove primitives and internal fields for stats
|
|
160
|
+
const filterTypes = (type: string) =>
|
|
161
|
+
!/^__|Boolean|ID|Int|String|Float/.test(type);
|
|
162
|
+
|
|
163
|
+
// filter out primitives and internal Types for type stats to match engine
|
|
164
|
+
const serviceTypes = this.serviceSchema
|
|
165
|
+
? Object.keys(this.serviceSchema.getTypeMap()).filter(filterTypes).length
|
|
166
|
+
: 0;
|
|
167
|
+
const totalTypes = this.schema
|
|
168
|
+
? Object.keys(this.schema.getTypeMap()).filter(filterTypes).length
|
|
169
|
+
: 0;
|
|
170
|
+
|
|
171
|
+
return {
|
|
172
|
+
type: "client",
|
|
173
|
+
serviceId: this.serviceID,
|
|
174
|
+
types: {
|
|
175
|
+
service: serviceTypes,
|
|
176
|
+
client: totalTypes - serviceTypes,
|
|
177
|
+
total: totalTypes,
|
|
178
|
+
},
|
|
179
|
+
tag: this.config.variant,
|
|
180
|
+
loaded: Boolean(this.schema || this.serviceSchema),
|
|
181
|
+
lastFetch: this.lastLoadDate,
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
onDecorations(handler: (any: any) => void) {
|
|
186
|
+
this._onDecorations = handler;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
onSchemaTags(handler: NotificationHandler<[ServiceID, SchemaTag[]]>) {
|
|
190
|
+
this._onSchemaTags = handler;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
async updateSchemaTag(tag: SchemaTag) {
|
|
194
|
+
await this.loadServiceSchema(tag);
|
|
195
|
+
this.invalidate();
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
private async loadServiceSchema(tag?: SchemaTag) {
|
|
199
|
+
await this.loadingHandler.handle(
|
|
200
|
+
`Loading schema for ${this.displayName}`,
|
|
201
|
+
(async () => {
|
|
202
|
+
this.serviceSchema = augmentSchemaWithGeneratedSDLIfNeeded(
|
|
203
|
+
await this.schemaProvider.resolveSchema({
|
|
204
|
+
tag: tag || this.config.variant,
|
|
205
|
+
force: true,
|
|
206
|
+
})
|
|
207
|
+
);
|
|
208
|
+
|
|
209
|
+
this.schema = extendSchema(this.serviceSchema, this.clientSchema);
|
|
210
|
+
})()
|
|
211
|
+
);
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
async resolveSchema(): Promise<GraphQLSchema> {
|
|
215
|
+
if (!this.schema) throw new Error();
|
|
216
|
+
return this.schema;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
get clientSchema(): DocumentNode {
|
|
220
|
+
return {
|
|
221
|
+
kind: Kind.DOCUMENT,
|
|
222
|
+
definitions: [
|
|
223
|
+
...this.typeSystemDefinitionsAndExtensions,
|
|
224
|
+
...this.missingApolloClientDirectives,
|
|
225
|
+
],
|
|
226
|
+
};
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
get missingApolloClientDirectives(): readonly DefinitionNode[] {
|
|
230
|
+
const { serviceSchema } = this;
|
|
231
|
+
|
|
232
|
+
const serviceDirectives = serviceSchema
|
|
233
|
+
? serviceSchema.getDirectives().map((directive) => directive.name)
|
|
234
|
+
: [];
|
|
235
|
+
|
|
236
|
+
const clientDirectives = this.typeSystemDefinitionsAndExtensions
|
|
237
|
+
.filter(isDirectiveDefinitionNode)
|
|
238
|
+
.map((def) => def.name.value);
|
|
239
|
+
|
|
240
|
+
const existingDirectives = serviceDirectives.concat(clientDirectives);
|
|
241
|
+
|
|
242
|
+
const apolloAst = apolloClientSchemaDocument.ast;
|
|
243
|
+
if (!apolloAst) return [];
|
|
244
|
+
|
|
245
|
+
const apolloDirectives = apolloAst.definitions
|
|
246
|
+
.filter(isDirectiveDefinitionNode)
|
|
247
|
+
.map((def) => def.name.value);
|
|
248
|
+
|
|
249
|
+
// If there is overlap between existingDirectives and apolloDirectives,
|
|
250
|
+
// don't add apolloDirectives. This is in case someone is directly including
|
|
251
|
+
// the apollo directives or another framework's conflicting directives
|
|
252
|
+
for (const existingDirective of existingDirectives) {
|
|
253
|
+
if (apolloDirectives.includes(existingDirective)) {
|
|
254
|
+
return [];
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
return apolloAst.definitions;
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
private addClientMetadataToSchemaNodes() {
|
|
262
|
+
const { schema, serviceSchema } = this;
|
|
263
|
+
if (!schema || !serviceSchema) return;
|
|
264
|
+
|
|
265
|
+
visit(this.clientSchema, {
|
|
266
|
+
ObjectTypeExtension(node) {
|
|
267
|
+
const type = schema.getType(
|
|
268
|
+
node.name.value
|
|
269
|
+
) as Maybe<GraphQLObjectType>;
|
|
270
|
+
const { fields } = node;
|
|
271
|
+
if (!fields || !type) return;
|
|
272
|
+
|
|
273
|
+
const localInfo: ClientSchemaInfo = type.clientSchema || {};
|
|
274
|
+
|
|
275
|
+
localInfo.localFields = [
|
|
276
|
+
...(localInfo.localFields || []),
|
|
277
|
+
...fields.map((field) => field.name.value),
|
|
278
|
+
];
|
|
279
|
+
|
|
280
|
+
type.clientSchema = localInfo;
|
|
281
|
+
},
|
|
282
|
+
});
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
async validate() {
|
|
286
|
+
if (!this._onDiagnostics) return;
|
|
287
|
+
if (!this.serviceSchema) return;
|
|
288
|
+
|
|
289
|
+
const diagnosticSet = new DiagnosticSet();
|
|
290
|
+
|
|
291
|
+
try {
|
|
292
|
+
this.schema = extendSchema(this.serviceSchema, this.clientSchema);
|
|
293
|
+
this.addClientMetadataToSchemaNodes();
|
|
294
|
+
} catch (error) {
|
|
295
|
+
if (error instanceof GraphQLError) {
|
|
296
|
+
const uri = error.source && error.source.name;
|
|
297
|
+
if (uri) {
|
|
298
|
+
diagnosticSet.addDiagnostics(
|
|
299
|
+
uri,
|
|
300
|
+
diagnosticsFromError(error, DiagnosticSeverity.Error, "Validation")
|
|
301
|
+
);
|
|
302
|
+
}
|
|
303
|
+
} else {
|
|
304
|
+
console.error(error);
|
|
305
|
+
}
|
|
306
|
+
this.schema = this.serviceSchema;
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
const fragments = this.fragments;
|
|
310
|
+
|
|
311
|
+
for (const [uri, documentsForFile] of this.documentsByFile) {
|
|
312
|
+
for (const document of documentsForFile) {
|
|
313
|
+
diagnosticSet.addDiagnostics(
|
|
314
|
+
uri,
|
|
315
|
+
collectExecutableDefinitionDiagnositics(
|
|
316
|
+
this.schema,
|
|
317
|
+
document,
|
|
318
|
+
fragments,
|
|
319
|
+
this._validationRules
|
|
320
|
+
)
|
|
321
|
+
);
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
for (const [uri, diagnostics] of diagnosticSet.entries()) {
|
|
325
|
+
this._onDiagnostics({ uri, diagnostics });
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
this.diagnosticSet = diagnosticSet;
|
|
329
|
+
|
|
330
|
+
this.generateDecorations();
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
async loadEngineData() {
|
|
334
|
+
const engineClient = this.engineClient;
|
|
335
|
+
if (!engineClient) return;
|
|
336
|
+
|
|
337
|
+
const serviceID = this.serviceID;
|
|
338
|
+
|
|
339
|
+
await this.loadingHandler.handle(
|
|
340
|
+
`Loading Apollo data for ${this.displayName}`,
|
|
341
|
+
(async () => {
|
|
342
|
+
try {
|
|
343
|
+
if (serviceID) {
|
|
344
|
+
const { schemaTags, fieldLatenciesMS } =
|
|
345
|
+
await engineClient.loadSchemaTagsAndFieldLatencies(serviceID);
|
|
346
|
+
this._onSchemaTags && this._onSchemaTags([serviceID, schemaTags]);
|
|
347
|
+
this.fieldLatenciesMS = fieldLatenciesMS;
|
|
348
|
+
}
|
|
349
|
+
const frontendUrlRoot = await engineClient.loadFrontendUrlRoot();
|
|
350
|
+
this.frontendUrlRoot = frontendUrlRoot;
|
|
351
|
+
this.lastLoadDate = +new Date();
|
|
352
|
+
|
|
353
|
+
this.generateDecorations();
|
|
354
|
+
} catch (e) {
|
|
355
|
+
console.error(e);
|
|
356
|
+
}
|
|
357
|
+
})()
|
|
358
|
+
);
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
generateDecorations() {
|
|
362
|
+
if (!this._onDecorations) return;
|
|
363
|
+
if (!this.schema) return;
|
|
364
|
+
|
|
365
|
+
const decorations: EngineDecoration[] = [];
|
|
366
|
+
|
|
367
|
+
for (const [uri, queryDocumentsForFile] of this.documentsByFile) {
|
|
368
|
+
for (const queryDocument of queryDocumentsForFile) {
|
|
369
|
+
if (queryDocument.ast) {
|
|
370
|
+
const fieldLatenciesMS = this.fieldLatenciesMS;
|
|
371
|
+
const typeInfo = new TypeInfo(this.schema);
|
|
372
|
+
visit(
|
|
373
|
+
queryDocument.ast,
|
|
374
|
+
visitWithTypeInfo(typeInfo, {
|
|
375
|
+
enter: (node) => {
|
|
376
|
+
if (
|
|
377
|
+
node.kind == "Field" &&
|
|
378
|
+
typeInfo.getParentType() &&
|
|
379
|
+
fieldLatenciesMS
|
|
380
|
+
) {
|
|
381
|
+
const parentName = typeInfo.getParentType()!.name;
|
|
382
|
+
const parentEngineStatMS = fieldLatenciesMS.get(parentName);
|
|
383
|
+
const engineStatMS = parentEngineStatMS
|
|
384
|
+
? parentEngineStatMS.get(node.name.value)
|
|
385
|
+
: undefined;
|
|
386
|
+
if (engineStatMS && engineStatMS > 1) {
|
|
387
|
+
decorations.push({
|
|
388
|
+
type: "text",
|
|
389
|
+
document: uri,
|
|
390
|
+
message: `~${formatMS(engineStatMS, 0)}`,
|
|
391
|
+
range: rangeForASTNode(node),
|
|
392
|
+
});
|
|
393
|
+
}
|
|
394
|
+
} else if (node.kind == "OperationDefinition") {
|
|
395
|
+
const operationWithFragments =
|
|
396
|
+
this.getOperationWithFragments(node);
|
|
397
|
+
const document = operationWithFragments
|
|
398
|
+
.map(print)
|
|
399
|
+
.join("\n\n");
|
|
400
|
+
const explorerURLState =
|
|
401
|
+
LZString.compressToEncodedURIComponent(
|
|
402
|
+
JSON.stringify({ document })
|
|
403
|
+
);
|
|
404
|
+
|
|
405
|
+
const frontendUrlRoot =
|
|
406
|
+
this.frontendUrlRoot ?? "https://studio.apollographql.com";
|
|
407
|
+
|
|
408
|
+
const variant = this.config.variant;
|
|
409
|
+
const graphId = this.config.graph;
|
|
410
|
+
|
|
411
|
+
const { client, service } = this.config;
|
|
412
|
+
const remoteServiceConfig =
|
|
413
|
+
typeof client.service === "object" &&
|
|
414
|
+
"url" in client.service
|
|
415
|
+
? client.service
|
|
416
|
+
: service?.endpoint;
|
|
417
|
+
const endpoint = remoteServiceConfig?.url;
|
|
418
|
+
|
|
419
|
+
const runInExplorerPath = graphId
|
|
420
|
+
? stringifyUrl({
|
|
421
|
+
url: `/graph/${graphId}/explorer`,
|
|
422
|
+
query: {
|
|
423
|
+
variant,
|
|
424
|
+
explorerURLState,
|
|
425
|
+
referrer: "vscode",
|
|
426
|
+
},
|
|
427
|
+
})
|
|
428
|
+
: stringifyUrl({
|
|
429
|
+
url: "/sandbox/explorer",
|
|
430
|
+
query: {
|
|
431
|
+
endpoint,
|
|
432
|
+
explorerURLState,
|
|
433
|
+
referrer: "vscode",
|
|
434
|
+
},
|
|
435
|
+
});
|
|
436
|
+
const runInExplorerLink = join(
|
|
437
|
+
frontendUrlRoot,
|
|
438
|
+
runInExplorerPath
|
|
439
|
+
);
|
|
440
|
+
|
|
441
|
+
decorations.push({
|
|
442
|
+
type: "runGlyph",
|
|
443
|
+
document: uri,
|
|
444
|
+
range: rangeForASTNode(node),
|
|
445
|
+
hoverMessage: `[Run in Studio](${runInExplorerLink})`,
|
|
446
|
+
});
|
|
447
|
+
}
|
|
448
|
+
},
|
|
449
|
+
})
|
|
450
|
+
);
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
this._onDecorations(decorations);
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
get fragments(): { [fragmentName: string]: FragmentDefinitionNode } {
|
|
459
|
+
const fragments = Object.create(null);
|
|
460
|
+
for (const document of this.documents) {
|
|
461
|
+
if (!document.ast) continue;
|
|
462
|
+
for (const definition of document.ast.definitions) {
|
|
463
|
+
if (definition.kind === Kind.FRAGMENT_DEFINITION) {
|
|
464
|
+
fragments[definition.name.value] = definition;
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
return fragments;
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
get operations(): { [operationName: string]: OperationDefinitionNode } {
|
|
472
|
+
const operations = Object.create(null);
|
|
473
|
+
for (const document of this.documents) {
|
|
474
|
+
if (!document.ast) continue;
|
|
475
|
+
for (const definition of document.ast.definitions) {
|
|
476
|
+
if (definition.kind === Kind.OPERATION_DEFINITION) {
|
|
477
|
+
if (!definition.name) {
|
|
478
|
+
throw new GraphQLError(
|
|
479
|
+
"Apollo does not support anonymous operations",
|
|
480
|
+
[definition]
|
|
481
|
+
);
|
|
482
|
+
}
|
|
483
|
+
operations[definition.name.value] = definition;
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
return operations;
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
get mergedOperationsAndFragments(): {
|
|
491
|
+
[operationName: string]: DocumentNode;
|
|
492
|
+
} {
|
|
493
|
+
return separateOperations({
|
|
494
|
+
kind: Kind.DOCUMENT,
|
|
495
|
+
definitions: [
|
|
496
|
+
...Object.values(this.fragments),
|
|
497
|
+
...Object.values(this.operations),
|
|
498
|
+
],
|
|
499
|
+
});
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
get mergedOperationsAndFragmentsForService(): {
|
|
503
|
+
[operationName: string]: DocumentNode;
|
|
504
|
+
} {
|
|
505
|
+
const { clientOnlyDirectives, clientSchemaDirectives, addTypename } =
|
|
506
|
+
this.config.client;
|
|
507
|
+
const current = this.mergedOperationsAndFragments;
|
|
508
|
+
if (
|
|
509
|
+
(!clientOnlyDirectives || !clientOnlyDirectives.length) &&
|
|
510
|
+
(!clientSchemaDirectives || !clientSchemaDirectives.length)
|
|
511
|
+
)
|
|
512
|
+
return current;
|
|
513
|
+
|
|
514
|
+
const filtered = Object.create(null);
|
|
515
|
+
for (const operationName in current) {
|
|
516
|
+
const document = current[operationName];
|
|
517
|
+
|
|
518
|
+
let serviceOnly = removeDirectiveAnnotatedFields(
|
|
519
|
+
removeDirectives(document, clientOnlyDirectives as string[]),
|
|
520
|
+
clientSchemaDirectives as string[]
|
|
521
|
+
);
|
|
522
|
+
|
|
523
|
+
if (addTypename)
|
|
524
|
+
serviceOnly = withTypenameFieldAddedWhereNeeded(serviceOnly);
|
|
525
|
+
// In the case we've made a document empty by filtering client directives,
|
|
526
|
+
// we don't want to include that in the result we pass on.
|
|
527
|
+
if (serviceOnly.definitions.filter(Boolean).length) {
|
|
528
|
+
filtered[operationName] = serviceOnly;
|
|
529
|
+
}
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
return filtered;
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
getOperationFieldsFromFieldDefinition(
|
|
536
|
+
fieldName: string,
|
|
537
|
+
parent: ObjectTypeDefinitionNode | null
|
|
538
|
+
): FieldNode[] {
|
|
539
|
+
if (!this.schema || !parent) return [];
|
|
540
|
+
const fields: FieldNode[] = [];
|
|
541
|
+
const typeInfo = new TypeInfo(this.schema);
|
|
542
|
+
for (const document of this.documents) {
|
|
543
|
+
if (!document.ast) continue;
|
|
544
|
+
visit(
|
|
545
|
+
document.ast,
|
|
546
|
+
visitWithTypeInfo(typeInfo, {
|
|
547
|
+
Field(node: FieldNode) {
|
|
548
|
+
if (node.name.value !== fieldName) return;
|
|
549
|
+
const parentType = typeInfo.getParentType();
|
|
550
|
+
if (parentType && parentType.name === parent.name.value) {
|
|
551
|
+
fields.push(node);
|
|
552
|
+
}
|
|
553
|
+
return;
|
|
554
|
+
},
|
|
555
|
+
})
|
|
556
|
+
);
|
|
557
|
+
}
|
|
558
|
+
return fields;
|
|
559
|
+
}
|
|
560
|
+
fragmentSpreadsForFragment(fragmentName: string): FragmentSpreadNode[] {
|
|
561
|
+
const fragmentSpreads: FragmentSpreadNode[] = [];
|
|
562
|
+
for (const document of this.documents) {
|
|
563
|
+
if (!document.ast) continue;
|
|
564
|
+
|
|
565
|
+
visit(document.ast, {
|
|
566
|
+
FragmentSpread(node: FragmentSpreadNode) {
|
|
567
|
+
if (node.name.value === fragmentName) {
|
|
568
|
+
fragmentSpreads.push(node);
|
|
569
|
+
}
|
|
570
|
+
},
|
|
571
|
+
});
|
|
572
|
+
}
|
|
573
|
+
return fragmentSpreads;
|
|
574
|
+
}
|
|
575
|
+
getOperationWithFragments(
|
|
576
|
+
operationDefinition: OperationDefinitionNode
|
|
577
|
+
): ExecutableDefinitionNode[] {
|
|
578
|
+
const fragments = this.fragments;
|
|
579
|
+
const seenFragmentNames = new Set<string>([]);
|
|
580
|
+
const allDefinitions: ExecutableDefinitionNode[] = [operationDefinition];
|
|
581
|
+
|
|
582
|
+
const defintionsToSearch: ExecutableDefinitionNode[] = [
|
|
583
|
+
operationDefinition,
|
|
584
|
+
];
|
|
585
|
+
let currentDefinition: ExecutableDefinitionNode | undefined;
|
|
586
|
+
while ((currentDefinition = defintionsToSearch.shift())) {
|
|
587
|
+
visit(currentDefinition, {
|
|
588
|
+
FragmentSpread(node: FragmentSpreadNode) {
|
|
589
|
+
const fragmentName = node.name.value;
|
|
590
|
+
const fragment = fragments[fragmentName];
|
|
591
|
+
if (!seenFragmentNames.has(fragmentName) && fragment) {
|
|
592
|
+
defintionsToSearch.push(fragment);
|
|
593
|
+
allDefinitions.push(fragment);
|
|
594
|
+
seenFragmentNames.add(fragmentName);
|
|
595
|
+
}
|
|
596
|
+
},
|
|
597
|
+
});
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
return allDefinitions;
|
|
601
|
+
}
|
|
602
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { GraphQLDocument } from "../document";
|
|
2
|
+
import { Source } from "graphql";
|
|
3
|
+
|
|
4
|
+
export const apolloClientSchema = `#graphql
|
|
5
|
+
"""
|
|
6
|
+
Direct the client to resolve this field locally, either from the cache or local resolvers.
|
|
7
|
+
"""
|
|
8
|
+
directive @client(
|
|
9
|
+
"""
|
|
10
|
+
When true, the client will never use the cache for this value. See
|
|
11
|
+
https://www.apollographql.com/docs/react/essentials/local-state/#forcing-resolvers-with-clientalways-true
|
|
12
|
+
"""
|
|
13
|
+
always: Boolean
|
|
14
|
+
) on FIELD | FRAGMENT_DEFINITION | INLINE_FRAGMENT
|
|
15
|
+
|
|
16
|
+
"""
|
|
17
|
+
Export this locally resolved field as a variable to be used in the remainder of this query. See
|
|
18
|
+
https://www.apollographql.com/docs/react/essentials/local-state/#using-client-fields-as-variables
|
|
19
|
+
"""
|
|
20
|
+
directive @export(
|
|
21
|
+
"""
|
|
22
|
+
The variable name to export this field as.
|
|
23
|
+
"""
|
|
24
|
+
as: String!
|
|
25
|
+
) on FIELD
|
|
26
|
+
|
|
27
|
+
"""
|
|
28
|
+
Specify a custom store key for this result. See
|
|
29
|
+
https://www.apollographql.com/docs/react/advanced/caching/#the-connection-directive
|
|
30
|
+
"""
|
|
31
|
+
directive @connection(
|
|
32
|
+
"""
|
|
33
|
+
Specify the store key.
|
|
34
|
+
"""
|
|
35
|
+
key: String!
|
|
36
|
+
"""
|
|
37
|
+
An array of query argument names to include in the generated custom store key.
|
|
38
|
+
"""
|
|
39
|
+
filter: [String!]
|
|
40
|
+
) on FIELD
|
|
41
|
+
`;
|
|
42
|
+
|
|
43
|
+
export const apolloClientSchemaDocument = new GraphQLDocument(
|
|
44
|
+
new Source(apolloClientSchema)
|
|
45
|
+
);
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { GraphQLProject } from "./base";
|
|
2
|
+
import { LoadingHandler } from "../loadingHandler";
|
|
3
|
+
import { FileSet } from "../fileSet";
|
|
4
|
+
import { ServiceConfig } from "../config";
|
|
5
|
+
import { ClientIdentity } from "../engine";
|
|
6
|
+
import URI from "vscode-uri";
|
|
7
|
+
|
|
8
|
+
export function isServiceProject(
|
|
9
|
+
project: GraphQLProject
|
|
10
|
+
): project is GraphQLServiceProject {
|
|
11
|
+
return project instanceof GraphQLServiceProject;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export interface GraphQLServiceProjectConfig {
|
|
15
|
+
clientIdentity?: ClientIdentity;
|
|
16
|
+
config: ServiceConfig;
|
|
17
|
+
configFolderURI: URI;
|
|
18
|
+
loadingHandler: LoadingHandler;
|
|
19
|
+
}
|
|
20
|
+
export class GraphQLServiceProject extends GraphQLProject {
|
|
21
|
+
constructor({
|
|
22
|
+
clientIdentity,
|
|
23
|
+
config,
|
|
24
|
+
configFolderURI,
|
|
25
|
+
loadingHandler,
|
|
26
|
+
}: GraphQLServiceProjectConfig) {
|
|
27
|
+
super({ config, configFolderURI, loadingHandler, clientIdentity });
|
|
28
|
+
this.config = config;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
get displayName() {
|
|
32
|
+
return this.config.graph || "Unnamed Project";
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
initialize() {
|
|
36
|
+
return [];
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
validate() {}
|
|
40
|
+
|
|
41
|
+
getProjectStats() {
|
|
42
|
+
return { loaded: true, type: "service" };
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
resolveFederationInfo() {
|
|
46
|
+
return this.schemaProvider.resolveFederatedServiceSDL();
|
|
47
|
+
}
|
|
48
|
+
}
|