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