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.
- package/.changeset/README.md +8 -0
- package/.changeset/config.json +14 -0
- package/.circleci/config.yml +91 -0
- package/.eslintrc.js +10 -0
- package/.git-blame-ignore-revs +2 -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 +66 -0
- package/.vscode/settings.json +16 -0
- package/.vscode/tasks.json +60 -0
- package/.vscodeignore +28 -1
- package/CHANGELOG.md +250 -1
- package/CODEOWNERS +4 -0
- package/LICENSE +2 -2
- package/README.md +104 -55
- package/codegen.yml +12 -0
- package/graphql.configuration.json +5 -1
- package/images/IconRun.svg +8 -0
- package/images/marketplace/apollo-wordmark.png +0 -0
- package/jest.config.ts +21 -0
- package/jest.e2e.config.js +17 -0
- package/package.json +102 -23
- package/renovate.json +30 -0
- package/sampleWorkspace/clientSchema/apollo.config.cjs +10 -0
- package/sampleWorkspace/clientSchema/src/clientSchema.js +16 -0
- package/sampleWorkspace/clientSchema/src/test.js +18 -0
- package/sampleWorkspace/fixtures/starwarsSchema.graphql +299 -0
- package/sampleWorkspace/httpSchema/apollo.config.ts +8 -0
- package/sampleWorkspace/httpSchema/src/test.js +9 -0
- package/sampleWorkspace/localSchema/apollo.config.js +8 -0
- package/sampleWorkspace/localSchema/src/test.js +8 -0
- package/sampleWorkspace/localSchemaArray/apollo.config.js +12 -0
- package/sampleWorkspace/localSchemaArray/planets.graphql +20 -0
- package/sampleWorkspace/localSchemaArray/src/test.js +12 -0
- package/sampleWorkspace/sampleWorkspace.code-workspace +20 -0
- package/sampleWorkspace/spotifyGraph/apollo.config.mjs +5 -0
- package/sampleWorkspace/spotifyGraph/src/test.js +11 -0
- package/src/__e2e__/mockServer.js +117 -0
- package/src/__e2e__/mocks.js +13094 -0
- package/src/__e2e__/run.js +23 -0
- package/src/__e2e__/runTests.js +44 -0
- package/src/__e2e__/setup.js +1 -0
- package/src/__e2e__/vscode-environment.js +16 -0
- package/src/__e2e__/vscode.js +1 -0
- package/src/__mocks__/fs.js +3 -0
- package/src/__tests__/statusBar.test.ts +8 -7
- package/src/build.js +57 -0
- package/src/debug.ts +2 -5
- package/src/env/index.ts +1 -0
- package/src/env/typescript-utility-types.ts +2 -0
- package/src/extension.ts +265 -170
- package/src/language-server/__e2e__/clientSchema.e2e.ts +147 -0
- package/src/language-server/__e2e__/httpSchema.e2e.ts +21 -0
- package/src/language-server/__e2e__/localSchema.e2e.ts +25 -0
- package/src/language-server/__e2e__/localSchemaArray.e2e.ts +31 -0
- package/src/language-server/__e2e__/studioGraph.e2e.ts +65 -0
- package/src/language-server/__e2e__/utils.ts +151 -0
- 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 +54 -0
- package/src/language-server/config/__tests__/loadConfig.ts +384 -0
- package/src/language-server/config/__tests__/utils.ts +99 -0
- package/src/language-server/config/config.ts +284 -0
- package/src/language-server/config/index.ts +3 -0
- package/src/language-server/config/loadConfig.ts +101 -0
- package/src/language-server/config/utils.ts +45 -0
- package/src/language-server/diagnostics.ts +118 -0
- package/src/language-server/document.ts +277 -0
- package/src/language-server/engine/index.ts +123 -0
- package/src/language-server/engine/operations/frontendUrlRoot.ts +15 -0
- package/src/language-server/engine/operations/schemaTagsAndFieldStats.ts +32 -0
- package/src/language-server/errors/__tests__/NoMissingClientDirectives.test.ts +225 -0
- package/src/language-server/errors/logger.ts +58 -0
- package/src/language-server/errors/validation.ts +274 -0
- package/src/language-server/fileSet.ts +63 -0
- package/src/language-server/format.ts +48 -0
- package/src/language-server/graphqlTypes.ts +16741 -0
- package/src/language-server/index.ts +28 -0
- package/src/language-server/languageProvider.ts +795 -0
- package/src/language-server/loadingHandler.ts +47 -0
- package/src/language-server/project/base.ts +406 -0
- package/src/language-server/project/client.ts +568 -0
- package/src/language-server/project/defaultClientSchema.ts +70 -0
- package/src/language-server/providers/schema/__tests__/file.ts +191 -0
- package/src/language-server/providers/schema/base.ts +15 -0
- package/src/language-server/providers/schema/endpoint.ts +138 -0
- package/src/language-server/providers/schema/engine.ts +204 -0
- package/src/language-server/providers/schema/file.ts +176 -0
- package/src/language-server/providers/schema/index.ts +59 -0
- package/src/language-server/server.ts +274 -0
- package/src/language-server/typings/graphql.d.ts +27 -0
- package/src/language-server/utilities/__tests__/graphql.test.ts +399 -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 +90 -0
- package/src/language-server/utilities/graphql.ts +433 -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 +254 -0
- package/src/languageServerClient.ts +22 -15
- package/src/messages.ts +75 -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 +7 -21
- package/syntaxes/graphql.dart.json +2 -4
- package/syntaxes/graphql.ex.json +1 -4
- 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 +11 -0
- package/tsconfig.json +22 -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,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
|
+
);
|