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.
Files changed (155) hide show
  1. package/.changeset/README.md +8 -0
  2. package/.changeset/config.json +14 -0
  3. package/.circleci/config.yml +82 -0
  4. package/.eslintrc.js +10 -0
  5. package/.gitattributes +1 -0
  6. package/.github/workflows/release.yml +95 -0
  7. package/.gitleaks.toml +26 -0
  8. package/.nvmrc +1 -0
  9. package/.prettierrc +5 -0
  10. package/.vscode/launch.json +61 -0
  11. package/.vscode/settings.json +16 -0
  12. package/.vscode/tasks.json +18 -0
  13. package/.vscodeignore +17 -1
  14. package/CHANGELOG.md +172 -1
  15. package/LICENSE +2 -2
  16. package/README.md +9 -9
  17. package/codegen.yml +12 -0
  18. package/images/IconRun.svg +8 -0
  19. package/jest.config.ts +11 -0
  20. package/package.json +102 -22
  21. package/renovate.json +23 -0
  22. package/src/__mocks__/fs.js +3 -0
  23. package/src/__tests__/statusBar.test.ts +8 -7
  24. package/src/debug.ts +2 -5
  25. package/src/env/fetch/fetch.ts +32 -0
  26. package/src/env/fetch/global.ts +30 -0
  27. package/src/env/fetch/index.d.ts +2 -0
  28. package/src/env/fetch/index.ts +2 -0
  29. package/src/env/fetch/url.ts +9 -0
  30. package/src/env/index.ts +4 -0
  31. package/src/env/polyfills/array.ts +17 -0
  32. package/src/env/polyfills/index.ts +2 -0
  33. package/src/env/polyfills/object.ts +7 -0
  34. package/src/env/typescript-utility-types.ts +2 -0
  35. package/src/extension.ts +106 -37
  36. package/src/language-server/__tests__/diagnostics.test.ts +86 -0
  37. package/src/language-server/__tests__/document.test.ts +187 -0
  38. package/src/language-server/__tests__/fileSet.test.ts +46 -0
  39. package/src/language-server/__tests__/fixtures/starwarsSchema.ts +1917 -0
  40. package/src/language-server/config/__tests__/config.ts +128 -0
  41. package/src/language-server/config/__tests__/loadConfig.ts +508 -0
  42. package/src/language-server/config/__tests__/utils.ts +106 -0
  43. package/src/language-server/config/config.ts +219 -0
  44. package/src/language-server/config/index.ts +3 -0
  45. package/src/language-server/config/loadConfig.ts +228 -0
  46. package/src/language-server/config/utils.ts +56 -0
  47. package/src/language-server/diagnostics.ts +109 -0
  48. package/src/language-server/document.ts +277 -0
  49. package/src/language-server/engine/GraphQLDataSource.ts +124 -0
  50. package/src/language-server/engine/index.ts +105 -0
  51. package/src/language-server/engine/operations/frontendUrlRoot.ts +7 -0
  52. package/src/language-server/engine/operations/schemaTagsAndFieldStats.ts +24 -0
  53. package/src/language-server/errors/__tests__/NoMissingClientDirectives.test.ts +220 -0
  54. package/src/language-server/errors/logger.ts +58 -0
  55. package/src/language-server/errors/validation.ts +277 -0
  56. package/src/language-server/fileSet.ts +65 -0
  57. package/src/language-server/format.ts +48 -0
  58. package/src/language-server/graphqlTypes.ts +7176 -0
  59. package/src/language-server/index.ts +29 -0
  60. package/src/language-server/languageProvider.ts +798 -0
  61. package/src/language-server/loadingHandler.ts +64 -0
  62. package/src/language-server/project/base.ts +399 -0
  63. package/src/language-server/project/client.ts +602 -0
  64. package/src/language-server/project/defaultClientSchema.ts +45 -0
  65. package/src/language-server/project/service.ts +48 -0
  66. package/src/language-server/providers/schema/__tests__/file.ts +150 -0
  67. package/src/language-server/providers/schema/base.ts +15 -0
  68. package/src/language-server/providers/schema/endpoint.ts +157 -0
  69. package/src/language-server/providers/schema/engine.ts +197 -0
  70. package/src/language-server/providers/schema/file.ts +167 -0
  71. package/src/language-server/providers/schema/index.ts +75 -0
  72. package/src/language-server/server.ts +252 -0
  73. package/src/language-server/typings/codemirror.d.ts +4 -0
  74. package/src/language-server/typings/graphql.d.ts +27 -0
  75. package/src/language-server/utilities/__tests__/graphql.test.ts +411 -0
  76. package/src/language-server/utilities/__tests__/uri.ts +55 -0
  77. package/src/language-server/utilities/debouncer.ts +8 -0
  78. package/src/language-server/utilities/debug.ts +89 -0
  79. package/src/language-server/utilities/graphql.ts +432 -0
  80. package/src/language-server/utilities/index.ts +3 -0
  81. package/src/language-server/utilities/source.ts +182 -0
  82. package/src/language-server/utilities/uri.ts +19 -0
  83. package/src/language-server/workspace.ts +262 -0
  84. package/src/languageServerClient.ts +19 -12
  85. package/src/messages.ts +84 -0
  86. package/src/statusBar.ts +5 -5
  87. package/src/tools/__tests__/buildServiceDefinition.test.ts +491 -0
  88. package/src/tools/__tests__/snapshotSerializers/astSerializer.ts +19 -0
  89. package/src/tools/__tests__/snapshotSerializers/graphQLTypeSerializer.ts +14 -0
  90. package/src/tools/buildServiceDefinition.ts +241 -0
  91. package/src/tools/index.ts +6 -0
  92. package/src/tools/schema/index.ts +2 -0
  93. package/src/tools/schema/resolveObject.ts +18 -0
  94. package/src/tools/schema/resolverMap.ts +23 -0
  95. package/src/tools/utilities/graphql.ts +22 -0
  96. package/src/tools/utilities/index.ts +3 -0
  97. package/src/tools/utilities/invariant.ts +5 -0
  98. package/src/tools/utilities/predicates.ts +5 -0
  99. package/src/utils.ts +1 -16
  100. package/syntaxes/graphql.js.json +3 -3
  101. package/syntaxes/graphql.json +13 -9
  102. package/syntaxes/graphql.lua.json +51 -0
  103. package/syntaxes/graphql.rb.json +1 -1
  104. package/tsconfig.build.json +4 -0
  105. package/tsconfig.json +20 -7
  106. package/create-server-symlink.js +0 -8
  107. package/lib/debug.d.ts +0 -11
  108. package/lib/debug.d.ts.map +0 -1
  109. package/lib/debug.js +0 -48
  110. package/lib/debug.js.map +0 -1
  111. package/lib/extension.d.ts +0 -4
  112. package/lib/extension.d.ts.map +0 -1
  113. package/lib/extension.js +0 -187
  114. package/lib/extension.js.map +0 -1
  115. package/lib/languageServerClient.d.ts +0 -4
  116. package/lib/languageServerClient.d.ts.map +0 -1
  117. package/lib/languageServerClient.js +0 -57
  118. package/lib/languageServerClient.js.map +0 -1
  119. package/lib/statusBar.d.ts +0 -24
  120. package/lib/statusBar.d.ts.map +0 -1
  121. package/lib/statusBar.js +0 -46
  122. package/lib/statusBar.js.map +0 -1
  123. package/lib/testRunner/index.d.ts +0 -3
  124. package/lib/testRunner/index.d.ts.map +0 -1
  125. package/lib/testRunner/index.js +0 -49
  126. package/lib/testRunner/index.js.map +0 -1
  127. package/lib/testRunner/jest-config.d.ts +0 -14
  128. package/lib/testRunner/jest-config.d.ts.map +0 -1
  129. package/lib/testRunner/jest-config.js +0 -18
  130. package/lib/testRunner/jest-config.js.map +0 -1
  131. package/lib/testRunner/jest-vscode-environment.d.ts +0 -2
  132. package/lib/testRunner/jest-vscode-environment.d.ts.map +0 -1
  133. package/lib/testRunner/jest-vscode-environment.js +0 -19
  134. package/lib/testRunner/jest-vscode-environment.js.map +0 -1
  135. package/lib/testRunner/jest-vscode-framework-setup.d.ts +0 -1
  136. package/lib/testRunner/jest-vscode-framework-setup.d.ts.map +0 -1
  137. package/lib/testRunner/jest-vscode-framework-setup.js +0 -3
  138. package/lib/testRunner/jest-vscode-framework-setup.js.map +0 -1
  139. package/lib/testRunner/vscode-test-script.d.ts +0 -2
  140. package/lib/testRunner/vscode-test-script.d.ts.map +0 -1
  141. package/lib/testRunner/vscode-test-script.js +0 -23
  142. package/lib/testRunner/vscode-test-script.js.map +0 -1
  143. package/lib/utils.d.ts +0 -18
  144. package/lib/utils.d.ts.map +0 -1
  145. package/lib/utils.js +0 -52
  146. package/lib/utils.js.map +0 -1
  147. package/src/testRunner/README.md +0 -23
  148. package/src/testRunner/index.ts +0 -72
  149. package/src/testRunner/jest-config.ts +0 -17
  150. package/src/testRunner/jest-vscode-environment.ts +0 -25
  151. package/src/testRunner/jest-vscode-framework-setup.ts +0 -10
  152. package/src/testRunner/jest.d.ts +0 -37
  153. package/src/testRunner/vscode-test-script.ts +0 -38
  154. package/tsconfig.test.json +0 -4
  155. 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
+ }