vscode-apollo 1.20.0 → 2.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.circleci/config.yml +27 -18
- package/.git-blame-ignore-revs +2 -0
- package/.nvmrc +1 -1
- package/.vscode/launch.json +9 -4
- package/.vscode/tasks.json +58 -16
- package/.vscodeignore +12 -1
- package/CHANGELOG.md +84 -0
- package/CODEOWNERS +4 -0
- package/README.md +97 -48
- package/graphql.configuration.json +5 -1
- package/images/marketplace/apollo-wordmark.png +0 -0
- package/jest.config.ts +14 -4
- package/jest.e2e.config.js +17 -0
- package/package.json +67 -68
- package/renovate.json +7 -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/build.js +57 -0
- package/src/env/index.ts +0 -3
- package/src/extension.ts +251 -225
- 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 +8 -8
- package/src/language-server/__tests__/fileSet.test.ts +1 -1
- package/src/language-server/__tests__/fixtures/starwarsSchema.ts +2 -2
- package/src/language-server/config/__tests__/config.ts +22 -96
- package/src/language-server/config/__tests__/loadConfig.ts +120 -220
- package/src/language-server/config/__tests__/utils.ts +22 -29
- package/src/language-server/config/config.ts +221 -156
- package/src/language-server/config/loadConfig.ts +32 -153
- package/src/language-server/config/loadTsConfig.ts +70 -0
- package/src/language-server/config/utils.ts +5 -16
- package/src/language-server/diagnostics.ts +17 -8
- package/src/language-server/document.ts +16 -16
- package/src/language-server/engine/index.ts +57 -39
- package/src/language-server/engine/operations/frontendUrlRoot.ts +9 -1
- package/src/language-server/engine/operations/schemaTagsAndFieldStats.ts +9 -1
- package/src/language-server/errors/__tests__/NoMissingClientDirectives.test.ts +10 -5
- package/src/language-server/errors/logger.ts +1 -1
- package/src/language-server/errors/validation.ts +20 -23
- package/src/language-server/fileSet.ts +10 -12
- package/src/language-server/format.ts +1 -1
- package/src/language-server/graphqlTypes.ts +13020 -3455
- package/src/language-server/index.ts +0 -1
- package/src/language-server/languageProvider.ts +29 -32
- package/src/language-server/loadingHandler.ts +10 -27
- package/src/language-server/project/base.ts +32 -25
- package/src/language-server/project/client.ts +80 -114
- package/src/language-server/project/defaultClientSchema.ts +29 -4
- package/src/language-server/providers/schema/__tests__/file.ts +60 -19
- package/src/language-server/providers/schema/base.ts +2 -2
- package/src/language-server/providers/schema/endpoint.ts +15 -34
- package/src/language-server/providers/schema/engine.ts +25 -18
- package/src/language-server/providers/schema/file.ts +41 -32
- package/src/language-server/providers/schema/index.ts +5 -21
- package/src/language-server/server.ts +72 -50
- package/src/language-server/typings/graphql.d.ts +3 -3
- package/src/language-server/utilities/__tests__/graphql.test.ts +42 -54
- package/src/language-server/utilities/debouncer.ts +1 -1
- package/src/language-server/utilities/debug.ts +6 -5
- package/src/language-server/utilities/graphql.ts +17 -16
- package/src/language-server/utilities/source.ts +16 -16
- package/src/language-server/utilities/uri.ts +2 -2
- package/src/language-server/workspace.ts +29 -37
- package/src/languageServerClient.ts +4 -4
- package/src/messages.ts +38 -47
- package/src/tools/__tests__/buildServiceDefinition.test.ts +2 -2
- package/src/tools/buildServiceDefinition.ts +11 -11
- package/src/tools/schema/resolveObject.ts +1 -1
- package/src/tools/utilities/predicates.ts +1 -1
- package/src/utils.ts +7 -6
- package/syntaxes/graphql.dart.json +2 -4
- package/syntaxes/graphql.ex.json +1 -4
- package/tsconfig.build.json +8 -1
- package/tsconfig.json +5 -3
- package/src/env/fetch/fetch.ts +0 -32
- package/src/env/fetch/global.ts +0 -30
- package/src/env/fetch/index.d.ts +0 -2
- package/src/env/fetch/index.ts +0 -2
- package/src/env/fetch/url.ts +0 -9
- package/src/env/polyfills/array.ts +0 -17
- package/src/env/polyfills/index.ts +0 -2
- package/src/env/polyfills/object.ts +0 -7
- package/src/language-server/engine/GraphQLDataSource.ts +0 -124
- package/src/language-server/project/service.ts +0 -48
- package/src/language-server/typings/codemirror.d.ts +0 -4
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { Loader, defaultLoaders } from "cosmiconfig";
|
|
2
|
+
import { dirname } from "node:path";
|
|
3
|
+
import { rm, writeFile } from "node:fs/promises";
|
|
4
|
+
import { existsSync } from "node:fs";
|
|
5
|
+
|
|
6
|
+
// implementation based on https://github.com/cosmiconfig/cosmiconfig/blob/a5a842547c13392ebb89a485b9e56d9f37e3cbd3/src/loaders.ts
|
|
7
|
+
// Copyright (c) 2015 David Clark licensed MIT. Full license can be found here:
|
|
8
|
+
// https://github.com/cosmiconfig/cosmiconfig/blob/a5a842547c13392ebb89a485b9e56d9f37e3cbd3/LICENSE
|
|
9
|
+
|
|
10
|
+
let typescript: typeof import("typescript");
|
|
11
|
+
export const loadTs: Loader = async function loadTs(filepath, content) {
|
|
12
|
+
try {
|
|
13
|
+
return await defaultLoaders[".ts"](filepath, content);
|
|
14
|
+
} catch (error) {
|
|
15
|
+
if (
|
|
16
|
+
!(error instanceof Error) ||
|
|
17
|
+
!error.message.includes("module is not defined")
|
|
18
|
+
)
|
|
19
|
+
throw error;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
if (typescript === undefined) {
|
|
23
|
+
typescript = await import("typescript");
|
|
24
|
+
}
|
|
25
|
+
const compiledFilepath = `${filepath.slice(0, -2)}cjs`;
|
|
26
|
+
let transpiledContent;
|
|
27
|
+
try {
|
|
28
|
+
try {
|
|
29
|
+
const config = resolveTsConfig(dirname(filepath)) ?? {};
|
|
30
|
+
config.compilerOptions = {
|
|
31
|
+
...config.compilerOptions,
|
|
32
|
+
module: typescript.ModuleKind.CommonJS,
|
|
33
|
+
moduleResolution: typescript.ModuleResolutionKind.Bundler,
|
|
34
|
+
target: typescript.ScriptTarget.ES2022,
|
|
35
|
+
noEmit: false,
|
|
36
|
+
};
|
|
37
|
+
transpiledContent = typescript.transpileModule(
|
|
38
|
+
content,
|
|
39
|
+
config,
|
|
40
|
+
).outputText;
|
|
41
|
+
await writeFile(compiledFilepath, transpiledContent);
|
|
42
|
+
} catch (error: any) {
|
|
43
|
+
error.message = `TypeScript Error in ${filepath}:\n${error.message}`;
|
|
44
|
+
throw error;
|
|
45
|
+
}
|
|
46
|
+
// eslint-disable-next-line @typescript-eslint/return-await
|
|
47
|
+
return await defaultLoaders[".js"](compiledFilepath, transpiledContent);
|
|
48
|
+
} finally {
|
|
49
|
+
if (existsSync(compiledFilepath)) {
|
|
50
|
+
await rm(compiledFilepath);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
56
|
+
function resolveTsConfig(directory: string): any {
|
|
57
|
+
const filePath = typescript.findConfigFile(directory, (fileName) => {
|
|
58
|
+
return typescript.sys.fileExists(fileName);
|
|
59
|
+
});
|
|
60
|
+
if (filePath !== undefined) {
|
|
61
|
+
const { config, error } = typescript.readConfigFile(filePath, (path) =>
|
|
62
|
+
typescript.sys.readFile(path),
|
|
63
|
+
);
|
|
64
|
+
if (error) {
|
|
65
|
+
throw new Error(`Error in ${filePath}: ${error.messageText.toString()}`);
|
|
66
|
+
}
|
|
67
|
+
return config;
|
|
68
|
+
}
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
@@ -3,30 +3,21 @@ import {
|
|
|
3
3
|
ClientConfig,
|
|
4
4
|
ClientServiceConfig,
|
|
5
5
|
LocalServiceConfig,
|
|
6
|
-
|
|
7
|
-
ApolloConfigFormat,
|
|
6
|
+
ParsedApolloConfigFormat,
|
|
8
7
|
} from "./config";
|
|
9
8
|
import { ServiceSpecifier, ServiceIDAndTag } from "../engine";
|
|
10
9
|
|
|
11
10
|
export function isClientConfig(config: ApolloConfig): config is ClientConfig {
|
|
12
|
-
return config
|
|
11
|
+
return config instanceof ClientConfig;
|
|
13
12
|
}
|
|
14
13
|
|
|
15
14
|
// checks the `config.client.service` object for a localSchemaFile
|
|
16
15
|
export function isLocalServiceConfig(
|
|
17
|
-
config: ClientServiceConfig
|
|
16
|
+
config: ClientServiceConfig,
|
|
18
17
|
): config is LocalServiceConfig {
|
|
19
18
|
return !!(config as LocalServiceConfig).localSchemaFile;
|
|
20
19
|
}
|
|
21
20
|
|
|
22
|
-
export function isServiceConfig(config: ApolloConfig): config is ServiceConfig {
|
|
23
|
-
return config.isService;
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
export function isServiceKey(key?: string) {
|
|
27
|
-
return key && /service:.*:.*/.test(key);
|
|
28
|
-
}
|
|
29
|
-
|
|
30
21
|
export function getServiceFromKey(key?: string) {
|
|
31
22
|
if (key) {
|
|
32
23
|
const [type, service] = key.split(":");
|
|
@@ -35,13 +26,11 @@ export function getServiceFromKey(key?: string) {
|
|
|
35
26
|
return;
|
|
36
27
|
}
|
|
37
28
|
|
|
38
|
-
export function getGraphIdFromConfig(config:
|
|
39
|
-
if (config.service && config.service.name)
|
|
40
|
-
return parseServiceSpecifier(config.service.name)[0];
|
|
29
|
+
export function getGraphIdFromConfig(config: ParsedApolloConfigFormat) {
|
|
41
30
|
if (config.client) {
|
|
42
31
|
if (typeof config.client.service === "string") {
|
|
43
32
|
return parseServiceSpecifier(
|
|
44
|
-
config.client.service as ServiceSpecifier
|
|
33
|
+
config.client.service as ServiceSpecifier,
|
|
45
34
|
)[0];
|
|
46
35
|
}
|
|
47
36
|
return config.client.service && config.client.service.name;
|
|
@@ -2,11 +2,20 @@ import {
|
|
|
2
2
|
GraphQLSchema,
|
|
3
3
|
GraphQLError,
|
|
4
4
|
FragmentDefinitionNode,
|
|
5
|
-
findDeprecatedUsages,
|
|
6
5
|
isExecutableDefinitionNode,
|
|
6
|
+
DocumentNode,
|
|
7
|
+
validate,
|
|
8
|
+
NoDeprecatedCustomRule,
|
|
7
9
|
} from "graphql";
|
|
8
10
|
|
|
9
|
-
|
|
11
|
+
function findDeprecatedUsages(
|
|
12
|
+
schema: GraphQLSchema,
|
|
13
|
+
ast: DocumentNode,
|
|
14
|
+
): ReadonlyArray<GraphQLError> {
|
|
15
|
+
return validate(schema, ast, [NoDeprecatedCustomRule]);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
import { Diagnostic, DiagnosticSeverity } from "vscode-languageserver/node";
|
|
10
19
|
|
|
11
20
|
import { GraphQLDocument } from "./document";
|
|
12
21
|
import { highlightNodeForNode } from "./utilities/graphql";
|
|
@@ -23,7 +32,7 @@ export function collectExecutableDefinitionDiagnositics(
|
|
|
23
32
|
schema: GraphQLSchema,
|
|
24
33
|
queryDocument: GraphQLDocument,
|
|
25
34
|
fragments: { [fragmentName: string]: FragmentDefinitionNode } = {},
|
|
26
|
-
rules?: ValidationRule[]
|
|
35
|
+
rules?: ValidationRule[],
|
|
27
36
|
): Diagnostic[] {
|
|
28
37
|
const ast = queryDocument.ast;
|
|
29
38
|
if (!ast) return queryDocument.syntaxErrors;
|
|
@@ -39,19 +48,19 @@ export function collectExecutableDefinitionDiagnositics(
|
|
|
39
48
|
schema,
|
|
40
49
|
astWithExecutableDefinitions,
|
|
41
50
|
fragments,
|
|
42
|
-
rules
|
|
51
|
+
rules,
|
|
43
52
|
)) {
|
|
44
53
|
diagnostics.push(
|
|
45
|
-
...diagnosticsFromError(error, DiagnosticSeverity.Error, "Validation")
|
|
54
|
+
...diagnosticsFromError(error, DiagnosticSeverity.Error, "Validation"),
|
|
46
55
|
);
|
|
47
56
|
}
|
|
48
57
|
|
|
49
58
|
for (const error of findDeprecatedUsages(
|
|
50
59
|
schema,
|
|
51
|
-
astWithExecutableDefinitions
|
|
60
|
+
astWithExecutableDefinitions,
|
|
52
61
|
)) {
|
|
53
62
|
diagnostics.push(
|
|
54
|
-
...diagnosticsFromError(error, DiagnosticSeverity.Warning, "Deprecation")
|
|
63
|
+
...diagnosticsFromError(error, DiagnosticSeverity.Warning, "Deprecation"),
|
|
55
64
|
);
|
|
56
65
|
}
|
|
57
66
|
|
|
@@ -61,7 +70,7 @@ export function collectExecutableDefinitionDiagnositics(
|
|
|
61
70
|
export function diagnosticsFromError(
|
|
62
71
|
error: GraphQLError,
|
|
63
72
|
severity: DiagnosticSeverity,
|
|
64
|
-
type: string
|
|
73
|
+
type: string,
|
|
65
74
|
): GraphQLDiagnostic[] {
|
|
66
75
|
if (!error.nodes) {
|
|
67
76
|
return [];
|
|
@@ -6,9 +6,9 @@ import {
|
|
|
6
6
|
Position,
|
|
7
7
|
Diagnostic,
|
|
8
8
|
DiagnosticSeverity,
|
|
9
|
-
} from "vscode-languageserver";
|
|
9
|
+
} from "vscode-languageserver/node";
|
|
10
10
|
|
|
11
|
-
import { getRange as rangeOfTokenAtLocation } from "graphql-language-service
|
|
11
|
+
import { getRange as rangeOfTokenAtLocation } from "graphql-language-service";
|
|
12
12
|
|
|
13
13
|
import {
|
|
14
14
|
positionFromSourceLocation,
|
|
@@ -30,7 +30,7 @@ export class GraphQLDocument {
|
|
|
30
30
|
// So we use the online parser to get the range of the token at that location
|
|
31
31
|
const range = rangeInContainingDocument(
|
|
32
32
|
source,
|
|
33
|
-
rangeOfTokenAtLocation(error.locations[0], source.body)
|
|
33
|
+
rangeOfTokenAtLocation(error.locations[0], source.body),
|
|
34
34
|
);
|
|
35
35
|
this.syntaxErrors.push({
|
|
36
36
|
severity: DiagnosticSeverity.Error,
|
|
@@ -45,7 +45,7 @@ export class GraphQLDocument {
|
|
|
45
45
|
if (position.line < this.source.locationOffset.line - 1) return false;
|
|
46
46
|
const end = positionFromSourceLocation(
|
|
47
47
|
this.source,
|
|
48
|
-
getLocation(this.source, this.source.body.length)
|
|
48
|
+
getLocation(this.source, this.source.body.length),
|
|
49
49
|
);
|
|
50
50
|
return position.line <= end.line;
|
|
51
51
|
}
|
|
@@ -53,7 +53,7 @@ export class GraphQLDocument {
|
|
|
53
53
|
|
|
54
54
|
export function extractGraphQLDocuments(
|
|
55
55
|
document: TextDocument,
|
|
56
|
-
tagName: string = "gql"
|
|
56
|
+
tagName: string = "gql",
|
|
57
57
|
): GraphQLDocument[] | null {
|
|
58
58
|
switch (document.languageId) {
|
|
59
59
|
case "graphql":
|
|
@@ -84,7 +84,7 @@ export function extractGraphQLDocuments(
|
|
|
84
84
|
|
|
85
85
|
function extractGraphQLDocumentsFromJSTemplateLiterals(
|
|
86
86
|
document: TextDocument,
|
|
87
|
-
tagName: string
|
|
87
|
+
tagName: string,
|
|
88
88
|
): GraphQLDocument[] | null {
|
|
89
89
|
const text = document.getText();
|
|
90
90
|
|
|
@@ -92,7 +92,7 @@ function extractGraphQLDocumentsFromJSTemplateLiterals(
|
|
|
92
92
|
|
|
93
93
|
const regExp = new RegExp(
|
|
94
94
|
`(?:${tagName}(?:\\s|\\()*\`|\`#graphql)([\\s\\S]+?)\`\\)?`,
|
|
95
|
-
"gm"
|
|
95
|
+
"gm",
|
|
96
96
|
);
|
|
97
97
|
|
|
98
98
|
let result;
|
|
@@ -114,7 +114,7 @@ function extractGraphQLDocumentsFromJSTemplateLiterals(
|
|
|
114
114
|
|
|
115
115
|
function extractGraphQLDocumentsFromPythonStrings(
|
|
116
116
|
document: TextDocument,
|
|
117
|
-
tagName: string
|
|
117
|
+
tagName: string,
|
|
118
118
|
): GraphQLDocument[] | null {
|
|
119
119
|
const text = document.getText();
|
|
120
120
|
|
|
@@ -122,7 +122,7 @@ function extractGraphQLDocumentsFromPythonStrings(
|
|
|
122
122
|
|
|
123
123
|
const regExp = new RegExp(
|
|
124
124
|
`\\b(${tagName}\\s*\\(\\s*[bfru]*("(?:"")?|'(?:'')?))([\\s\\S]+?)\\2\\s*\\)`,
|
|
125
|
-
"gm"
|
|
125
|
+
"gm",
|
|
126
126
|
);
|
|
127
127
|
|
|
128
128
|
let result;
|
|
@@ -144,7 +144,7 @@ function extractGraphQLDocumentsFromPythonStrings(
|
|
|
144
144
|
|
|
145
145
|
function extractGraphQLDocumentsFromRubyStrings(
|
|
146
146
|
document: TextDocument,
|
|
147
|
-
tagName: string
|
|
147
|
+
tagName: string,
|
|
148
148
|
): GraphQLDocument[] | null {
|
|
149
149
|
const text = document.getText();
|
|
150
150
|
|
|
@@ -171,7 +171,7 @@ function extractGraphQLDocumentsFromRubyStrings(
|
|
|
171
171
|
|
|
172
172
|
function extractGraphQLDocumentsFromDartStrings(
|
|
173
173
|
document: TextDocument,
|
|
174
|
-
tagName: string
|
|
174
|
+
tagName: string,
|
|
175
175
|
): GraphQLDocument[] | null {
|
|
176
176
|
const text = document.getText();
|
|
177
177
|
|
|
@@ -179,7 +179,7 @@ function extractGraphQLDocumentsFromDartStrings(
|
|
|
179
179
|
|
|
180
180
|
const regExp = new RegExp(
|
|
181
181
|
`\\b(${tagName}\\(\\s*r?("""|'''))([\\s\\S]+?)\\2\\s*\\)`,
|
|
182
|
-
"gm"
|
|
182
|
+
"gm",
|
|
183
183
|
);
|
|
184
184
|
|
|
185
185
|
let result;
|
|
@@ -201,7 +201,7 @@ function extractGraphQLDocumentsFromDartStrings(
|
|
|
201
201
|
|
|
202
202
|
function extractGraphQLDocumentsFromReasonStrings(
|
|
203
203
|
document: TextDocument,
|
|
204
|
-
tagName: string
|
|
204
|
+
tagName: string,
|
|
205
205
|
): GraphQLDocument[] | null {
|
|
206
206
|
const text = document.getText();
|
|
207
207
|
|
|
@@ -214,7 +214,7 @@ function extractGraphQLDocumentsFromReasonStrings(
|
|
|
214
214
|
}
|
|
215
215
|
|
|
216
216
|
const reasonRegexp = new RegExp(
|
|
217
|
-
/(?<=\[%(graphql|relay\.\w*)[\s\S]*{\|)[.\s\S]+?(?=\|})/gm
|
|
217
|
+
/(?<=\[%(graphql|relay\.\w*)[\s\S]*{\|)[.\s\S]+?(?=\|})/gm,
|
|
218
218
|
);
|
|
219
219
|
|
|
220
220
|
let result;
|
|
@@ -236,14 +236,14 @@ function extractGraphQLDocumentsFromReasonStrings(
|
|
|
236
236
|
|
|
237
237
|
function extractGraphQLDocumentsFromElixirStrings(
|
|
238
238
|
document: TextDocument,
|
|
239
|
-
tagName: string
|
|
239
|
+
tagName: string,
|
|
240
240
|
): GraphQLDocument[] | null {
|
|
241
241
|
const text = document.getText();
|
|
242
242
|
const documents: GraphQLDocument[] = [];
|
|
243
243
|
|
|
244
244
|
const regExp = new RegExp(
|
|
245
245
|
`\\b(${tagName}\\(\\s*r?("""))([\\s\\S]+?)\\2\\s*\\)`,
|
|
246
|
-
"gm"
|
|
246
|
+
"gm",
|
|
247
247
|
);
|
|
248
248
|
|
|
249
249
|
let result;
|
|
@@ -1,16 +1,18 @@
|
|
|
1
|
-
import { GraphQLDataSource } from "./GraphQLDataSource";
|
|
2
|
-
import { DefaultEngineConfig } from "../config";
|
|
3
1
|
import { SCHEMA_TAGS_AND_FIELD_STATS } from "./operations/schemaTagsAndFieldStats";
|
|
4
|
-
import {
|
|
5
|
-
FrontendUrlRootQuery,
|
|
6
|
-
SchemaTagsAndFieldStatsQuery,
|
|
7
|
-
} from "../graphqlTypes";
|
|
8
2
|
import { FRONTEND_URL_ROOT } from "./operations/frontendUrlRoot";
|
|
3
|
+
import {
|
|
4
|
+
ApolloClient,
|
|
5
|
+
ApolloLink,
|
|
6
|
+
createHttpLink,
|
|
7
|
+
InMemoryCache,
|
|
8
|
+
} from "@apollo/client/core";
|
|
9
|
+
import { setContext } from "@apollo/client/link/context";
|
|
10
|
+
import { onError } from "@apollo/client/link/error";
|
|
9
11
|
|
|
10
12
|
export interface ClientIdentity {
|
|
11
|
-
name
|
|
12
|
-
version
|
|
13
|
-
referenceID
|
|
13
|
+
name: string;
|
|
14
|
+
version: string;
|
|
15
|
+
referenceID: string;
|
|
14
16
|
}
|
|
15
17
|
|
|
16
18
|
export type ServiceID = string;
|
|
@@ -27,54 +29,69 @@ export function noServiceError(service: string | undefined, endpoint?: string) {
|
|
|
27
29
|
} from Apollo at ${endpoint}. Please check your API key and graph ID`;
|
|
28
30
|
}
|
|
29
31
|
|
|
30
|
-
export class ApolloEngineClient
|
|
32
|
+
export class ApolloEngineClient {
|
|
33
|
+
public readonly client: ApolloClient<any>;
|
|
34
|
+
public readonly query: ApolloClient<any>["query"];
|
|
35
|
+
|
|
31
36
|
constructor(
|
|
32
|
-
private engineKey: string,
|
|
33
|
-
|
|
34
|
-
private clientIdentity
|
|
37
|
+
private readonly engineKey: string,
|
|
38
|
+
baseURL: string,
|
|
39
|
+
private readonly clientIdentity: ClientIdentity,
|
|
35
40
|
) {
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
41
|
+
const link = ApolloLink.from([
|
|
42
|
+
onError(({ graphQLErrors, networkError, operation }) => {
|
|
43
|
+
const { result, response } = operation.getContext();
|
|
44
|
+
if (graphQLErrors) {
|
|
45
|
+
graphQLErrors.map((graphqlError) =>
|
|
46
|
+
console.error(`[GraphQL error]: ${graphqlError.message}`),
|
|
47
|
+
);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
if (networkError) {
|
|
51
|
+
console.log(`[Network Error]: ${networkError}`);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
if (response && response.status >= 400) {
|
|
55
|
+
console.log(`[Network Error] ${response.bodyText}`);
|
|
56
|
+
}
|
|
57
|
+
}),
|
|
58
|
+
createHttpLink({
|
|
59
|
+
uri: baseURL,
|
|
60
|
+
headers: {
|
|
61
|
+
["x-api-key"]: this.engineKey,
|
|
62
|
+
["apollo-client-name"]: this.clientIdentity.name,
|
|
63
|
+
["apollo-client-reference-id"]: this.clientIdentity.referenceID,
|
|
64
|
+
["apollo-client-version"]: this.clientIdentity.version,
|
|
65
|
+
},
|
|
66
|
+
}),
|
|
67
|
+
]);
|
|
68
|
+
|
|
69
|
+
this.client = new ApolloClient({
|
|
70
|
+
link,
|
|
71
|
+
cache: new InMemoryCache(),
|
|
72
|
+
});
|
|
73
|
+
this.query = this.client.query.bind(this.client);
|
|
58
74
|
}
|
|
59
75
|
|
|
60
76
|
async loadSchemaTagsAndFieldLatencies(serviceID: string) {
|
|
61
|
-
const { data, errors } = await this.
|
|
77
|
+
const { data, errors } = await this.client.query({
|
|
62
78
|
query: SCHEMA_TAGS_AND_FIELD_STATS,
|
|
63
79
|
variables: {
|
|
64
80
|
id: serviceID,
|
|
65
81
|
},
|
|
82
|
+
fetchPolicy: "no-cache",
|
|
66
83
|
});
|
|
67
84
|
|
|
68
85
|
if (!(data && data.service && data.service.schemaTags) || errors) {
|
|
69
86
|
throw new Error(
|
|
70
87
|
errors
|
|
71
88
|
? errors.map((error) => error.message).join("\n")
|
|
72
|
-
: "No service returned. Make sure your service name and API key match"
|
|
89
|
+
: "No service returned. Make sure your service name and API key match",
|
|
73
90
|
);
|
|
74
91
|
}
|
|
75
92
|
|
|
76
93
|
const schemaTags: string[] = data.service.schemaTags.map(
|
|
77
|
-
({ tag }: { tag: string }) => tag
|
|
94
|
+
({ tag }: { tag: string }) => tag,
|
|
78
95
|
);
|
|
79
96
|
|
|
80
97
|
const fieldLatenciesMS: FieldLatenciesMS = new Map();
|
|
@@ -96,8 +113,9 @@ export class ApolloEngineClient extends GraphQLDataSource {
|
|
|
96
113
|
}
|
|
97
114
|
|
|
98
115
|
async loadFrontendUrlRoot() {
|
|
99
|
-
const { data } = await this.
|
|
116
|
+
const { data } = await this.client.query({
|
|
100
117
|
query: FRONTEND_URL_ROOT,
|
|
118
|
+
fetchPolicy: "cache-first",
|
|
101
119
|
});
|
|
102
120
|
|
|
103
121
|
return data?.frontendUrlRoot;
|
|
@@ -1,6 +1,14 @@
|
|
|
1
|
+
import { TypedDocumentNode } from "@apollo/client/core";
|
|
1
2
|
import gql from "graphql-tag";
|
|
3
|
+
import type {
|
|
4
|
+
FrontendUrlRootQuery,
|
|
5
|
+
FrontendUrlRootQueryVariables,
|
|
6
|
+
} from "src/language-server/graphqlTypes";
|
|
2
7
|
|
|
3
|
-
export const FRONTEND_URL_ROOT
|
|
8
|
+
export const FRONTEND_URL_ROOT: TypedDocumentNode<
|
|
9
|
+
FrontendUrlRootQuery,
|
|
10
|
+
FrontendUrlRootQueryVariables
|
|
11
|
+
> = gql`
|
|
4
12
|
query FrontendUrlRoot {
|
|
5
13
|
frontendUrlRoot
|
|
6
14
|
}
|
|
@@ -1,6 +1,14 @@
|
|
|
1
|
+
import { TypedDocumentNode } from "@apollo/client/core";
|
|
1
2
|
import gql from "graphql-tag";
|
|
3
|
+
import type {
|
|
4
|
+
SchemaTagsAndFieldStatsQuery,
|
|
5
|
+
SchemaTagsAndFieldStatsQueryVariables,
|
|
6
|
+
} from "src/language-server/graphqlTypes";
|
|
2
7
|
|
|
3
|
-
export const SCHEMA_TAGS_AND_FIELD_STATS
|
|
8
|
+
export const SCHEMA_TAGS_AND_FIELD_STATS: TypedDocumentNode<
|
|
9
|
+
SchemaTagsAndFieldStatsQuery,
|
|
10
|
+
SchemaTagsAndFieldStatsQueryVariables
|
|
11
|
+
> = gql`
|
|
4
12
|
query SchemaTagsAndFieldStats($id: ID!) {
|
|
5
13
|
service(id: $id) {
|
|
6
14
|
schemaTags {
|
|
@@ -4,8 +4,8 @@ import { basename } from "path";
|
|
|
4
4
|
|
|
5
5
|
import { vol } from "memfs";
|
|
6
6
|
import { LoadingHandler } from "../../loadingHandler";
|
|
7
|
-
import {
|
|
8
|
-
import URI from "vscode-uri";
|
|
7
|
+
import { ClientConfig, parseApolloConfig } from "../../config";
|
|
8
|
+
import { URI } from "vscode-uri";
|
|
9
9
|
|
|
10
10
|
const serviceSchema = /* GraphQL */ `
|
|
11
11
|
type Query {
|
|
@@ -130,7 +130,7 @@ const f = /* GraphQL */ `
|
|
|
130
130
|
|
|
131
131
|
const rootURI = URI.file(process.cwd());
|
|
132
132
|
|
|
133
|
-
const config =
|
|
133
|
+
const config = parseApolloConfig({
|
|
134
134
|
client: {
|
|
135
135
|
service: {
|
|
136
136
|
name: "server",
|
|
@@ -141,7 +141,7 @@ const config = new ApolloConfig({
|
|
|
141
141
|
validationRules: [NoMissingClientDirectives],
|
|
142
142
|
},
|
|
143
143
|
engine: {},
|
|
144
|
-
})
|
|
144
|
+
});
|
|
145
145
|
|
|
146
146
|
class MockLoadingHandler implements LoadingHandler {
|
|
147
147
|
handle<T>(_message: string, value: Promise<T>): Promise<T> {
|
|
@@ -179,9 +179,14 @@ describe("client state", () => {
|
|
|
179
179
|
|
|
180
180
|
it("should report validation errors for missing @client directives", async () => {
|
|
181
181
|
const project = new GraphQLClientProject({
|
|
182
|
-
config,
|
|
182
|
+
config: config as ClientConfig,
|
|
183
183
|
loadingHandler: new MockLoadingHandler(),
|
|
184
184
|
configFolderURI: rootURI,
|
|
185
|
+
clientIdentity: {
|
|
186
|
+
name: "",
|
|
187
|
+
version: "",
|
|
188
|
+
referenceID: "",
|
|
189
|
+
},
|
|
185
190
|
});
|
|
186
191
|
|
|
187
192
|
const errors = Object.create(null);
|
|
@@ -18,7 +18,7 @@ import {
|
|
|
18
18
|
isObjectType,
|
|
19
19
|
} from "graphql";
|
|
20
20
|
|
|
21
|
-
import { TextEdit } from "vscode-languageserver";
|
|
21
|
+
import { TextEdit } from "vscode-languageserver/node";
|
|
22
22
|
|
|
23
23
|
import { ToolError, logError } from "./logger";
|
|
24
24
|
import { ValidationRule } from "graphql/validation/ValidationContext";
|
|
@@ -35,7 +35,7 @@ export interface CodeActionInfo {
|
|
|
35
35
|
edits: TextEdit[];
|
|
36
36
|
}
|
|
37
37
|
|
|
38
|
-
const specifiedRulesToBeRemoved = [NoUnusedFragmentsRule];
|
|
38
|
+
const specifiedRulesToBeRemoved: ValidationRule[] = [NoUnusedFragmentsRule];
|
|
39
39
|
|
|
40
40
|
export const defaultValidationRules: ValidationRule[] = [
|
|
41
41
|
NoAnonymousQueries,
|
|
@@ -48,7 +48,7 @@ export function getValidationErrors(
|
|
|
48
48
|
schema: GraphQLSchema,
|
|
49
49
|
document: DocumentNode,
|
|
50
50
|
fragments?: { [fragmentName: string]: FragmentDefinitionNode },
|
|
51
|
-
rules: ValidationRule[] = defaultValidationRules
|
|
51
|
+
rules: ValidationRule[] = defaultValidationRules,
|
|
52
52
|
) {
|
|
53
53
|
const typeInfo = new TypeInfo(schema);
|
|
54
54
|
|
|
@@ -83,7 +83,7 @@ export function getValidationErrors(
|
|
|
83
83
|
|
|
84
84
|
export function validateQueryDocument(
|
|
85
85
|
schema: GraphQLSchema,
|
|
86
|
-
document: DocumentNode
|
|
86
|
+
document: DocumentNode,
|
|
87
87
|
) {
|
|
88
88
|
try {
|
|
89
89
|
const validationErrors = getValidationErrors(schema, document);
|
|
@@ -106,7 +106,7 @@ export function NoAnonymousQueries(context: ValidationContext) {
|
|
|
106
106
|
context.reportError(
|
|
107
107
|
new GraphQLError("Apollo does not support anonymous operations", [
|
|
108
108
|
node,
|
|
109
|
-
])
|
|
109
|
+
]),
|
|
110
110
|
);
|
|
111
111
|
}
|
|
112
112
|
return false;
|
|
@@ -122,8 +122,8 @@ export function NoTypenameAlias(context: ValidationContext) {
|
|
|
122
122
|
context.reportError(
|
|
123
123
|
new GraphQLError(
|
|
124
124
|
"Apollo needs to be able to insert __typename when needed, please do not use it as an alias",
|
|
125
|
-
[node]
|
|
126
|
-
)
|
|
125
|
+
[node],
|
|
126
|
+
),
|
|
127
127
|
);
|
|
128
128
|
}
|
|
129
129
|
},
|
|
@@ -138,7 +138,7 @@ function hasClientSchema(schema: GraphQLSchema): boolean {
|
|
|
138
138
|
return Boolean(
|
|
139
139
|
(query && query.clientSchema) ||
|
|
140
140
|
(mutation && mutation.clientSchema) ||
|
|
141
|
-
(subscription && subscription.clientSchema)
|
|
141
|
+
(subscription && subscription.clientSchema),
|
|
142
142
|
);
|
|
143
143
|
}
|
|
144
144
|
|
|
@@ -152,17 +152,14 @@ export function NoMissingClientDirectives(context: ValidationContext) {
|
|
|
152
152
|
// together correctly
|
|
153
153
|
// XXX we have a simplified version of this in @apollo/gateway that we could probably use
|
|
154
154
|
// intead of this
|
|
155
|
-
const executionContext = buildExecutionContext(
|
|
155
|
+
const executionContext = buildExecutionContext({
|
|
156
156
|
schema,
|
|
157
|
-
root,
|
|
158
|
-
Object.create(null),
|
|
159
|
-
Object.create(null),
|
|
160
|
-
|
|
161
|
-
undefined,
|
|
162
|
-
undefined
|
|
163
|
-
);
|
|
157
|
+
document: root,
|
|
158
|
+
rootValue: Object.create(null),
|
|
159
|
+
contextValue: Object.create(null),
|
|
160
|
+
});
|
|
164
161
|
function visitor(
|
|
165
|
-
node: FieldNode | InlineFragmentNode | FragmentDefinitionNode
|
|
162
|
+
node: FieldNode | InlineFragmentNode | FragmentDefinitionNode,
|
|
166
163
|
) {
|
|
167
164
|
// In cases where we are looking at a FragmentDefinition, there is no parent type
|
|
168
165
|
// but instead, the FragmentDefinition contains the type that we can read from the
|
|
@@ -194,7 +191,7 @@ export function NoMissingClientDirectives(context: ValidationContext) {
|
|
|
194
191
|
// fields are simple because we can just see if the name exists in the local fields
|
|
195
192
|
// array on the parent type
|
|
196
193
|
selectsClientFieldSet = Boolean(
|
|
197
|
-
clientFields && clientFields.includes(fieldDef?.name || "")
|
|
194
|
+
clientFields && clientFields.includes(fieldDef?.name || ""),
|
|
198
195
|
);
|
|
199
196
|
message += `local field "${node.name.value}"`;
|
|
200
197
|
break;
|
|
@@ -207,7 +204,7 @@ export function NoMissingClientDirectives(context: ValidationContext) {
|
|
|
207
204
|
executionContext as ExecutionContext,
|
|
208
205
|
node.selectionSet,
|
|
209
206
|
Object.create(null),
|
|
210
|
-
Object.create(null)
|
|
207
|
+
Object.create(null),
|
|
211
208
|
);
|
|
212
209
|
|
|
213
210
|
// once we have a list of fields on the fragment, we can compare them
|
|
@@ -215,7 +212,7 @@ export function NoMissingClientDirectives(context: ValidationContext) {
|
|
|
215
212
|
// subset of the overall local fields types
|
|
216
213
|
const fieldNames = Object.entries(fields).map(([name]) => name);
|
|
217
214
|
selectsClientFieldSet = fieldNames.every(
|
|
218
|
-
(field) => clientFields && clientFields.includes(field)
|
|
215
|
+
(field) => clientFields && clientFields.includes(field),
|
|
219
216
|
);
|
|
220
217
|
message += `fragment ${
|
|
221
218
|
"name" in node ? `"${node.name.value}" ` : ""
|
|
@@ -247,9 +244,9 @@ export function NoMissingClientDirectives(context: ValidationContext) {
|
|
|
247
244
|
TextEdit.insert(
|
|
248
245
|
positionFromSourceLocation(
|
|
249
246
|
source,
|
|
250
|
-
getLocation(source, locToInsertDirective)
|
|
247
|
+
getLocation(source, locToInsertDirective),
|
|
251
248
|
),
|
|
252
|
-
" @client"
|
|
249
|
+
" @client",
|
|
253
250
|
),
|
|
254
251
|
],
|
|
255
252
|
};
|
|
@@ -257,7 +254,7 @@ export function NoMissingClientDirectives(context: ValidationContext) {
|
|
|
257
254
|
}
|
|
258
255
|
|
|
259
256
|
context.reportError(
|
|
260
|
-
new GraphQLError(message, [node], null, null, null, null, extensions)
|
|
257
|
+
new GraphQLError(message, [node], null, null, null, null, extensions),
|
|
261
258
|
);
|
|
262
259
|
}
|
|
263
260
|
|