vscode-apollo 1.19.3 → 1.20.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.
Files changed (156) 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/build-prs.yml +57 -0
  7. package/.github/workflows/release.yml +114 -0
  8. package/.gitleaks.toml +26 -0
  9. package/.nvmrc +1 -0
  10. package/.prettierrc +5 -0
  11. package/.vscode/launch.json +61 -0
  12. package/.vscode/settings.json +16 -0
  13. package/.vscode/tasks.json +18 -0
  14. package/.vscodeignore +17 -1
  15. package/CHANGELOG.md +178 -1
  16. package/LICENSE +2 -2
  17. package/README.md +9 -9
  18. package/codegen.yml +12 -0
  19. package/images/IconRun.svg +8 -0
  20. package/jest.config.ts +11 -0
  21. package/package.json +102 -22
  22. package/renovate.json +23 -0
  23. package/src/__mocks__/fs.js +3 -0
  24. package/src/__tests__/statusBar.test.ts +8 -7
  25. package/src/debug.ts +2 -5
  26. package/src/env/fetch/fetch.ts +32 -0
  27. package/src/env/fetch/global.ts +30 -0
  28. package/src/env/fetch/index.d.ts +2 -0
  29. package/src/env/fetch/index.ts +2 -0
  30. package/src/env/fetch/url.ts +9 -0
  31. package/src/env/index.ts +4 -0
  32. package/src/env/polyfills/array.ts +17 -0
  33. package/src/env/polyfills/index.ts +2 -0
  34. package/src/env/polyfills/object.ts +7 -0
  35. package/src/env/typescript-utility-types.ts +2 -0
  36. package/src/extension.ts +106 -37
  37. package/src/language-server/__tests__/diagnostics.test.ts +86 -0
  38. package/src/language-server/__tests__/document.test.ts +187 -0
  39. package/src/language-server/__tests__/fileSet.test.ts +46 -0
  40. package/src/language-server/__tests__/fixtures/starwarsSchema.ts +1917 -0
  41. package/src/language-server/config/__tests__/config.ts +128 -0
  42. package/src/language-server/config/__tests__/loadConfig.ts +508 -0
  43. package/src/language-server/config/__tests__/utils.ts +106 -0
  44. package/src/language-server/config/config.ts +219 -0
  45. package/src/language-server/config/index.ts +3 -0
  46. package/src/language-server/config/loadConfig.ts +228 -0
  47. package/src/language-server/config/utils.ts +56 -0
  48. package/src/language-server/diagnostics.ts +109 -0
  49. package/src/language-server/document.ts +277 -0
  50. package/src/language-server/engine/GraphQLDataSource.ts +124 -0
  51. package/src/language-server/engine/index.ts +105 -0
  52. package/src/language-server/engine/operations/frontendUrlRoot.ts +7 -0
  53. package/src/language-server/engine/operations/schemaTagsAndFieldStats.ts +24 -0
  54. package/src/language-server/errors/__tests__/NoMissingClientDirectives.test.ts +220 -0
  55. package/src/language-server/errors/logger.ts +58 -0
  56. package/src/language-server/errors/validation.ts +277 -0
  57. package/src/language-server/fileSet.ts +65 -0
  58. package/src/language-server/format.ts +48 -0
  59. package/src/language-server/graphqlTypes.ts +7176 -0
  60. package/src/language-server/index.ts +29 -0
  61. package/src/language-server/languageProvider.ts +798 -0
  62. package/src/language-server/loadingHandler.ts +64 -0
  63. package/src/language-server/project/base.ts +399 -0
  64. package/src/language-server/project/client.ts +602 -0
  65. package/src/language-server/project/defaultClientSchema.ts +45 -0
  66. package/src/language-server/project/service.ts +48 -0
  67. package/src/language-server/providers/schema/__tests__/file.ts +150 -0
  68. package/src/language-server/providers/schema/base.ts +15 -0
  69. package/src/language-server/providers/schema/endpoint.ts +157 -0
  70. package/src/language-server/providers/schema/engine.ts +197 -0
  71. package/src/language-server/providers/schema/file.ts +167 -0
  72. package/src/language-server/providers/schema/index.ts +75 -0
  73. package/src/language-server/server.ts +252 -0
  74. package/src/language-server/typings/codemirror.d.ts +4 -0
  75. package/src/language-server/typings/graphql.d.ts +27 -0
  76. package/src/language-server/utilities/__tests__/graphql.test.ts +411 -0
  77. package/src/language-server/utilities/__tests__/uri.ts +55 -0
  78. package/src/language-server/utilities/debouncer.ts +8 -0
  79. package/src/language-server/utilities/debug.ts +89 -0
  80. package/src/language-server/utilities/graphql.ts +432 -0
  81. package/src/language-server/utilities/index.ts +3 -0
  82. package/src/language-server/utilities/source.ts +182 -0
  83. package/src/language-server/utilities/uri.ts +19 -0
  84. package/src/language-server/workspace.ts +262 -0
  85. package/src/languageServerClient.ts +19 -12
  86. package/src/messages.ts +84 -0
  87. package/src/statusBar.ts +5 -5
  88. package/src/tools/__tests__/buildServiceDefinition.test.ts +491 -0
  89. package/src/tools/__tests__/snapshotSerializers/astSerializer.ts +19 -0
  90. package/src/tools/__tests__/snapshotSerializers/graphQLTypeSerializer.ts +14 -0
  91. package/src/tools/buildServiceDefinition.ts +241 -0
  92. package/src/tools/index.ts +6 -0
  93. package/src/tools/schema/index.ts +2 -0
  94. package/src/tools/schema/resolveObject.ts +18 -0
  95. package/src/tools/schema/resolverMap.ts +23 -0
  96. package/src/tools/utilities/graphql.ts +22 -0
  97. package/src/tools/utilities/index.ts +3 -0
  98. package/src/tools/utilities/invariant.ts +5 -0
  99. package/src/tools/utilities/predicates.ts +5 -0
  100. package/src/utils.ts +1 -16
  101. package/syntaxes/graphql.js.json +3 -3
  102. package/syntaxes/graphql.json +13 -9
  103. package/syntaxes/graphql.lua.json +51 -0
  104. package/syntaxes/graphql.rb.json +1 -1
  105. package/tsconfig.build.json +4 -0
  106. package/tsconfig.json +20 -7
  107. package/create-server-symlink.js +0 -8
  108. package/lib/debug.d.ts +0 -11
  109. package/lib/debug.d.ts.map +0 -1
  110. package/lib/debug.js +0 -48
  111. package/lib/debug.js.map +0 -1
  112. package/lib/extension.d.ts +0 -4
  113. package/lib/extension.d.ts.map +0 -1
  114. package/lib/extension.js +0 -187
  115. package/lib/extension.js.map +0 -1
  116. package/lib/languageServerClient.d.ts +0 -4
  117. package/lib/languageServerClient.d.ts.map +0 -1
  118. package/lib/languageServerClient.js +0 -57
  119. package/lib/languageServerClient.js.map +0 -1
  120. package/lib/statusBar.d.ts +0 -24
  121. package/lib/statusBar.d.ts.map +0 -1
  122. package/lib/statusBar.js +0 -46
  123. package/lib/statusBar.js.map +0 -1
  124. package/lib/testRunner/index.d.ts +0 -3
  125. package/lib/testRunner/index.d.ts.map +0 -1
  126. package/lib/testRunner/index.js +0 -49
  127. package/lib/testRunner/index.js.map +0 -1
  128. package/lib/testRunner/jest-config.d.ts +0 -14
  129. package/lib/testRunner/jest-config.d.ts.map +0 -1
  130. package/lib/testRunner/jest-config.js +0 -18
  131. package/lib/testRunner/jest-config.js.map +0 -1
  132. package/lib/testRunner/jest-vscode-environment.d.ts +0 -2
  133. package/lib/testRunner/jest-vscode-environment.d.ts.map +0 -1
  134. package/lib/testRunner/jest-vscode-environment.js +0 -19
  135. package/lib/testRunner/jest-vscode-environment.js.map +0 -1
  136. package/lib/testRunner/jest-vscode-framework-setup.d.ts +0 -1
  137. package/lib/testRunner/jest-vscode-framework-setup.d.ts.map +0 -1
  138. package/lib/testRunner/jest-vscode-framework-setup.js +0 -3
  139. package/lib/testRunner/jest-vscode-framework-setup.js.map +0 -1
  140. package/lib/testRunner/vscode-test-script.d.ts +0 -2
  141. package/lib/testRunner/vscode-test-script.d.ts.map +0 -1
  142. package/lib/testRunner/vscode-test-script.js +0 -23
  143. package/lib/testRunner/vscode-test-script.js.map +0 -1
  144. package/lib/utils.d.ts +0 -18
  145. package/lib/utils.d.ts.map +0 -1
  146. package/lib/utils.js +0 -52
  147. package/lib/utils.js.map +0 -1
  148. package/src/testRunner/README.md +0 -23
  149. package/src/testRunner/index.ts +0 -72
  150. package/src/testRunner/jest-config.ts +0 -17
  151. package/src/testRunner/jest-vscode-environment.ts +0 -25
  152. package/src/testRunner/jest-vscode-framework-setup.ts +0 -10
  153. package/src/testRunner/jest.d.ts +0 -37
  154. package/src/testRunner/vscode-test-script.ts +0 -38
  155. package/tsconfig.test.json +0 -4
  156. package/tsconfig.tsbuildinfo +0 -2486
@@ -0,0 +1,150 @@
1
+ import { FileSchemaProvider } from "../file";
2
+ import * as path from "path";
3
+ import * as fs from "fs";
4
+ import { Debug } from "../../../utilities";
5
+
6
+ const makeNestedDir = (dir: string) => {
7
+ if (fs.existsSync(dir)) return;
8
+
9
+ try {
10
+ fs.mkdirSync(dir);
11
+ } catch (err) {
12
+ if (err.code == "ENOENT") {
13
+ makeNestedDir(path.dirname(dir)); //create parent dir
14
+ makeNestedDir(dir); //create dir
15
+ }
16
+ }
17
+ };
18
+
19
+ const deleteFolderRecursive = (path: string) => {
20
+ // don't delete files on windows -- will get a resource locked error
21
+ if (require("os").type().includes("Windows")) {
22
+ return;
23
+ }
24
+
25
+ if (fs.existsSync(path)) {
26
+ fs.readdirSync(path).forEach(function (file, index) {
27
+ var curPath = path + "/" + file;
28
+ if (fs.lstatSync(curPath).isDirectory()) {
29
+ // recurse
30
+ deleteFolderRecursive(curPath);
31
+ } else {
32
+ // delete file
33
+ fs.unlinkSync(curPath);
34
+ }
35
+ });
36
+ fs.rmdirSync(path);
37
+ }
38
+ };
39
+
40
+ const writeFilesToDir = (dir: string, files: Record<string, string>) => {
41
+ Object.keys(files).forEach((key) => {
42
+ if (key.includes("/")) makeNestedDir(path.dirname(key));
43
+ fs.writeFileSync(`${dir}/${key}`, files[key]);
44
+ });
45
+ };
46
+
47
+ describe("FileSchemaProvider", () => {
48
+ let dir: string;
49
+ let dirPath: string;
50
+
51
+ // set up a temp dir
52
+ beforeEach(() => {
53
+ dir = fs.mkdtempSync("__tmp__");
54
+ dirPath = `${process.cwd()}/${dir}`;
55
+ });
56
+
57
+ // clean up our temp dir
58
+ afterEach(() => {
59
+ if (dir) {
60
+ deleteFolderRecursive(dir);
61
+ }
62
+ });
63
+
64
+ describe("resolveFederatedServiceSDL", () => {
65
+ it("finds and loads sdl from graphql file for a federated service", async () => {
66
+ writeFilesToDir(dir, {
67
+ "schema.graphql": `
68
+ extend type Query {
69
+ myProduct: Product
70
+ }
71
+
72
+ type Product @key(fields: "id") {
73
+ id: ID
74
+ sku: ID
75
+ name: String
76
+ }
77
+ `,
78
+ });
79
+
80
+ const provider = new FileSchemaProvider({
81
+ path: dir + "/schema.graphql",
82
+ });
83
+ const sdl = await provider.resolveFederatedServiceSDL();
84
+ expect(sdl).toMatchInlineSnapshot;
85
+ });
86
+
87
+ it("finds and loads sdl from multiple graphql files for a federated service", async () => {
88
+ writeFilesToDir(dir, {
89
+ "schema.graphql": `
90
+ extend type Query {
91
+ myProduct: Product
92
+ }
93
+
94
+ type Product @key(fields: "id") {
95
+ id: ID
96
+ sku: ID
97
+ name: String
98
+ }`,
99
+ "schema2.graphql": `
100
+ extend type Product {
101
+ weight: Float
102
+ }`,
103
+ });
104
+
105
+ const provider = new FileSchemaProvider({
106
+ paths: [dir + "/schema.graphql", dir + "/schema2.graphql"],
107
+ });
108
+ const sdl = await provider.resolveFederatedServiceSDL();
109
+ expect(sdl).toMatchInlineSnapshot(`
110
+ "type Product @key(fields: \\"id\\") {
111
+ id: ID
112
+ sku: ID
113
+ name: String
114
+ weight: Float
115
+ }
116
+
117
+ extend type Query {
118
+ myProduct: Product
119
+ }
120
+ "
121
+ `);
122
+ });
123
+
124
+ it("errors when sdl file is not a graphql file", async () => {
125
+ const toWrite = `
126
+ module.exports = \`
127
+ extend type Query {
128
+ myProduct: Product
129
+ }
130
+
131
+ type Product @key(fields: "id") {
132
+ id: ID
133
+ sku: ID
134
+ name: string
135
+ }\`
136
+ `;
137
+ writeFilesToDir(dir, {
138
+ "schema.js": toWrite,
139
+ });
140
+
141
+ // noop -- just spy on and silence the error
142
+ const errorSpy = jest.spyOn(Debug, "error");
143
+ errorSpy.mockImplementation(() => {});
144
+
145
+ const provider = new FileSchemaProvider({ path: dir + "/schema.js" });
146
+ const sdl = await provider.resolveFederatedServiceSDL();
147
+ expect(errorSpy).toBeCalledTimes(2);
148
+ });
149
+ });
150
+ });
@@ -0,0 +1,15 @@
1
+ import { GraphQLSchema } from "graphql";
2
+ import { NotificationHandler } from "vscode-languageserver";
3
+
4
+ export interface SchemaResolveConfig {
5
+ tag?: string;
6
+ force?: boolean;
7
+ }
8
+ export type SchemaChangeUnsubscribeHandler = () => void;
9
+ export interface GraphQLSchemaProvider {
10
+ resolveSchema(config?: SchemaResolveConfig): Promise<GraphQLSchema>;
11
+ onSchemaChange(
12
+ handler: NotificationHandler<GraphQLSchema>
13
+ ): SchemaChangeUnsubscribeHandler;
14
+ resolveFederatedServiceSDL(): Promise<string | void>;
15
+ }
@@ -0,0 +1,157 @@
1
+ // IntrospectionSchemaProvider (http => IntrospectionResult => schema)
2
+ import { NotificationHandler } from "vscode-languageserver";
3
+ import { execute as linkExecute, toPromise } from "apollo-link";
4
+ import { createHttpLink, HttpLink } from "apollo-link-http";
5
+ import {
6
+ GraphQLSchema,
7
+ buildClientSchema,
8
+ getIntrospectionQuery,
9
+ ExecutionResult,
10
+ IntrospectionQuery,
11
+ parse,
12
+ } from "graphql";
13
+ import { Agent as HTTPSAgent } from "https";
14
+ import { fetch } from "../../../env";
15
+ import { RemoteServiceConfig, DefaultServiceConfig } from "../../config";
16
+ import { GraphQLSchemaProvider, SchemaChangeUnsubscribeHandler } from "./base";
17
+ import { Debug } from "../../utilities";
18
+ import { isString } from "util";
19
+
20
+ export class EndpointSchemaProvider implements GraphQLSchemaProvider {
21
+ private schema?: GraphQLSchema;
22
+ private federatedServiceSDL?: string;
23
+
24
+ constructor(private config: Exclude<RemoteServiceConfig, "name">) {}
25
+ async resolveSchema() {
26
+ if (this.schema) return this.schema;
27
+ const { skipSSLValidation, url, headers } = this.config;
28
+ const options: HttpLink.Options = {
29
+ uri: url,
30
+ fetch,
31
+ };
32
+ if (url.startsWith("https:") && skipSSLValidation) {
33
+ options.fetchOptions = {
34
+ agent: new HTTPSAgent({ rejectUnauthorized: false }),
35
+ };
36
+ }
37
+
38
+ const { data, errors } = (await toPromise(
39
+ linkExecute(createHttpLink(options), {
40
+ query: parse(getIntrospectionQuery()),
41
+ context: { headers },
42
+ })
43
+ ).catch((e) => {
44
+ // html response from introspection
45
+ if (isString(e.message) && e.message.includes("token <")) {
46
+ throw new Error(
47
+ "Apollo tried to introspect a running GraphQL service at " +
48
+ url +
49
+ "\nIt expected a JSON schema introspection result, but got an HTML response instead." +
50
+ "\nYou may need to add headers to your request or adjust your endpoint url.\n" +
51
+ "-----------------------------\n" +
52
+ "For more information, please refer to: https://go.apollo.dev/t/config \n\n" +
53
+ "The following error occurred:\n-----------------------------\n" +
54
+ e.message
55
+ );
56
+ }
57
+
58
+ // 404 encountered with the default url
59
+ if (
60
+ url === DefaultServiceConfig.endpoint.url &&
61
+ isString(e.message) &&
62
+ e.message.includes("ECONNREFUSED")
63
+ ) {
64
+ throw new Error(
65
+ "Failed to connect to a running GraphQL endpoint at " +
66
+ url +
67
+ "\nThis may be because you didn't start your service.\n" +
68
+ "By default, when an endpoint, Apollo API key, or localSchemaFile isn't provided, Apollo tries to fetch a schema from " +
69
+ DefaultServiceConfig.endpoint.url +
70
+ "\n-----------------------------\n" +
71
+ "\nFor more information, please refer to: https://go.apollo.dev/t/config \n\n" +
72
+ "The following error occurred: \n" +
73
+ "-----------------------------\n" +
74
+ e.message
75
+ );
76
+ }
77
+ // 404 with a non-default url
78
+ if (isString(e.message) && e.message.includes("ECONNREFUSED")) {
79
+ throw new Error(
80
+ "Failed to connect to a running GraphQL endpoint at " +
81
+ url +
82
+ "\nThis may be because you didn't start your service or the endpoint URL is incorrect."
83
+ );
84
+ }
85
+ throw new Error(e);
86
+ })) as ExecutionResult<IntrospectionQuery>;
87
+
88
+ if (errors && errors.length) {
89
+ // XXX better error handling of GraphQL errors
90
+ throw new Error(errors.map(({ message }: Error) => message).join("\n"));
91
+ }
92
+
93
+ if (!data) {
94
+ throw new Error("No data received from server introspection.");
95
+ }
96
+
97
+ this.schema = buildClientSchema(data);
98
+ return this.schema;
99
+ }
100
+
101
+ onSchemaChange(
102
+ _handler: NotificationHandler<GraphQLSchema>
103
+ ): SchemaChangeUnsubscribeHandler {
104
+ throw new Error("Polling of endpoint not implemented yet");
105
+ return () => {};
106
+ }
107
+
108
+ async resolveFederatedServiceSDL() {
109
+ if (this.federatedServiceSDL) return this.federatedServiceSDL;
110
+
111
+ const { skipSSLValidation, url, headers } = this.config;
112
+ const options: HttpLink.Options = {
113
+ uri: url,
114
+ fetch,
115
+ };
116
+ if (url.startsWith("https:") && skipSSLValidation) {
117
+ options.fetchOptions = {
118
+ agent: new HTTPSAgent({ rejectUnauthorized: false }),
119
+ };
120
+ }
121
+
122
+ const getFederationInfoQuery = `
123
+ query getFederationInfo {
124
+ _service {
125
+ sdl
126
+ }
127
+ }
128
+ `;
129
+
130
+ const { data, errors } = (await toPromise(
131
+ linkExecute(createHttpLink(options), {
132
+ query: parse(getFederationInfoQuery),
133
+ context: { headers },
134
+ })
135
+ )) as ExecutionResult<{ _service: { sdl: string } }>;
136
+
137
+ if (errors && errors.length) {
138
+ return Debug.error(
139
+ errors.map(({ message }: Error) => message).join("\n")
140
+ );
141
+ }
142
+
143
+ if (!data || !data._service) {
144
+ return Debug.error(
145
+ "No data received from server when querying for _service."
146
+ );
147
+ }
148
+
149
+ this.federatedServiceSDL = data._service.sdl;
150
+ return data._service.sdl;
151
+ }
152
+
153
+ // public async isFederatedSchema() {
154
+ // const schema = this.schema || (await this.resolveSchema());
155
+ // return false;
156
+ // }
157
+ }
@@ -0,0 +1,197 @@
1
+ // EngineSchemaProvider (engine schema reg => schema)
2
+ import { NotificationHandler } from "vscode-languageserver";
3
+ import gql from "graphql-tag";
4
+ import { GraphQLSchema, buildClientSchema } from "graphql";
5
+ import { ApolloEngineClient, ClientIdentity } from "../../engine";
6
+ import { ClientConfig, keyEnvVar, parseServiceSpecifier } from "../../config";
7
+ import { getServiceFromKey, isServiceKey } from "../../config";
8
+ import {
9
+ GraphQLSchemaProvider,
10
+ SchemaChangeUnsubscribeHandler,
11
+ SchemaResolveConfig,
12
+ } from "./base";
13
+
14
+ import { GetSchemaByTagQuery } from "../../graphqlTypes";
15
+ import { Debug } from "../../utilities";
16
+
17
+ export class EngineSchemaProvider implements GraphQLSchemaProvider {
18
+ private schema?: GraphQLSchema;
19
+ private client?: ApolloEngineClient;
20
+
21
+ constructor(
22
+ private config: ClientConfig,
23
+ private clientIdentity?: ClientIdentity
24
+ ) {}
25
+
26
+ async resolveSchema(override: SchemaResolveConfig) {
27
+ if (this.schema && (!override || !override.force)) return this.schema;
28
+ const { engine, client } = this.config;
29
+
30
+ if (!this.config.graph) {
31
+ throw new Error(
32
+ `No graph ID found for client. Please specify a graph ID via the config or the --graph flag`
33
+ );
34
+ }
35
+
36
+ // create engine client
37
+ if (!this.client) {
38
+ if (!engine.apiKey) {
39
+ throw new Error(
40
+ `No API key found. Please set ${keyEnvVar} or use --key`
41
+ );
42
+ }
43
+ this.client = new ApolloEngineClient(
44
+ engine.apiKey,
45
+ engine.endpoint,
46
+ this.clientIdentity
47
+ );
48
+ }
49
+
50
+ const { data, errors } = await this.client.execute<GetSchemaByTagQuery>({
51
+ query: SCHEMA_QUERY,
52
+ variables: {
53
+ id: this.config.graph,
54
+ tag: override && override.tag ? override.tag : this.config.variant,
55
+ },
56
+ });
57
+ if (errors) {
58
+ // XXX better error handling of GraphQL errors
59
+ throw new Error(errors.map(({ message }: Error) => message).join("\n"));
60
+ }
61
+
62
+ if (!(data && data.service && data.service.__typename === "Service")) {
63
+ throw new Error(
64
+ `Unable to get schema from the Apollo registry for graph ${this.config.graph}`
65
+ );
66
+ }
67
+
68
+ // @ts-ignore
69
+ // XXX Types of `data.service.schema` won't match closely enough with `IntrospectionQuery`
70
+ this.schema = buildClientSchema(data.service.schema);
71
+ return this.schema;
72
+ }
73
+
74
+ onSchemaChange(
75
+ _handler: NotificationHandler<GraphQLSchema>
76
+ ): SchemaChangeUnsubscribeHandler {
77
+ throw new Error("Polling of Apollo not implemented yet");
78
+ return () => {};
79
+ }
80
+
81
+ async resolveFederatedServiceSDL() {
82
+ Debug.error(
83
+ "Cannot resolve a federated service's SDL from Apollo. Use an endpoint or a file instead"
84
+ );
85
+ return;
86
+ }
87
+ }
88
+
89
+ export const SCHEMA_QUERY = gql`
90
+ query GetSchemaByTag($tag: String!, $id: ID!) {
91
+ service(id: $id) {
92
+ ... on Service {
93
+ __typename
94
+ schema(tag: $tag) {
95
+ hash
96
+ __schema: introspection {
97
+ queryType {
98
+ name
99
+ }
100
+ mutationType {
101
+ name
102
+ }
103
+ subscriptionType {
104
+ name
105
+ }
106
+ types(filter: { includeBuiltInTypes: true }) {
107
+ ...IntrospectionFullType
108
+ }
109
+ directives {
110
+ name
111
+ description
112
+ locations
113
+ args {
114
+ ...IntrospectionInputValue
115
+ }
116
+ }
117
+ }
118
+ }
119
+ }
120
+ }
121
+ }
122
+
123
+ fragment IntrospectionFullType on IntrospectionType {
124
+ kind
125
+ name
126
+ description
127
+ fields {
128
+ name
129
+ description
130
+ args {
131
+ ...IntrospectionInputValue
132
+ }
133
+ type {
134
+ ...IntrospectionTypeRef
135
+ }
136
+ isDeprecated
137
+ deprecationReason
138
+ }
139
+ inputFields {
140
+ ...IntrospectionInputValue
141
+ }
142
+ interfaces {
143
+ ...IntrospectionTypeRef
144
+ }
145
+ enumValues(includeDeprecated: true) {
146
+ name
147
+ description
148
+ isDeprecated
149
+ deprecationReason
150
+ }
151
+ possibleTypes {
152
+ ...IntrospectionTypeRef
153
+ }
154
+ }
155
+
156
+ fragment IntrospectionInputValue on IntrospectionInputValue {
157
+ name
158
+ description
159
+ type {
160
+ ...IntrospectionTypeRef
161
+ }
162
+ defaultValue
163
+ }
164
+
165
+ fragment IntrospectionTypeRef on IntrospectionType {
166
+ kind
167
+ name
168
+ ofType {
169
+ kind
170
+ name
171
+ ofType {
172
+ kind
173
+ name
174
+ ofType {
175
+ kind
176
+ name
177
+ ofType {
178
+ kind
179
+ name
180
+ ofType {
181
+ kind
182
+ name
183
+ ofType {
184
+ kind
185
+ name
186
+ ofType {
187
+ kind
188
+ name
189
+ }
190
+ }
191
+ }
192
+ }
193
+ }
194
+ }
195
+ }
196
+ }
197
+ `;
@@ -0,0 +1,167 @@
1
+ // FileSchemaProvider (FileProvider (SDL || IntrospectionResult) => schema)
2
+ import {
3
+ GraphQLSchema,
4
+ buildClientSchema,
5
+ Source,
6
+ buildSchema,
7
+ printSchema,
8
+ parse,
9
+ visit,
10
+ } from "graphql";
11
+ import { readFileSync } from "fs";
12
+ import { extname, resolve } from "path";
13
+ import { GraphQLSchemaProvider, SchemaChangeUnsubscribeHandler } from "./base";
14
+ import { NotificationHandler } from "vscode-languageserver";
15
+ import { Debug } from "../../utilities";
16
+ import { buildSchemaFromSDL } from "apollo-graphql";
17
+ import {
18
+ buildFederatedSchema,
19
+ composeServices,
20
+ printSchema as printFederatedSchema,
21
+ } from "@apollo/federation";
22
+ import URI from "vscode-uri";
23
+ // import federationDirectives from "@apollo/federation/src/directives";
24
+
25
+ export interface FileSchemaProviderConfig {
26
+ path?: string;
27
+ paths?: string[];
28
+ }
29
+ // XXX file subscription
30
+ export class FileSchemaProvider implements GraphQLSchemaProvider {
31
+ private schema?: GraphQLSchema;
32
+ private federatedServiceSDL?: string;
33
+
34
+ constructor(private config: FileSchemaProviderConfig) {}
35
+
36
+ async resolveSchema() {
37
+ if (this.schema) return this.schema;
38
+ const { path, paths } = this.config;
39
+
40
+ // load each path and get sdl string from each, if a list, concatenate them all
41
+ const documents = path
42
+ ? [this.loadFileAndGetDocument(path)]
43
+ : paths
44
+ ? paths.map(this.loadFileAndGetDocument, this)
45
+ : undefined;
46
+
47
+ if (!documents)
48
+ throw new Error(
49
+ `Schema could not be loaded for [${
50
+ path ? path : paths ? paths.join(", ") : "undefined"
51
+ }]`
52
+ );
53
+
54
+ this.schema = buildSchemaFromSDL(documents);
55
+
56
+ if (!this.schema) throw new Error(`Schema could not be loaded for ${path}`);
57
+ return this.schema;
58
+ }
59
+
60
+ // load a graphql file or introspection result and return the GraphQL DocumentNode
61
+ // this is the mechanism for loading a single file's DocumentNode
62
+ loadFileAndGetDocument(path: string) {
63
+ let result;
64
+ try {
65
+ result = readFileSync(path, {
66
+ encoding: "utf-8",
67
+ });
68
+ } catch (err: any) {
69
+ throw new Error(`Unable to read file ${path}. ${err.message}`);
70
+ }
71
+
72
+ const ext = extname(path);
73
+
74
+ // an actual introspection query result, convert to DocumentNode
75
+ if (ext === ".json") {
76
+ const parsed = JSON.parse(result);
77
+ const __schema = parsed.data
78
+ ? parsed.data.__schema
79
+ : parsed.__schema
80
+ ? parsed.__schema
81
+ : parsed;
82
+
83
+ const schema = buildClientSchema({ __schema });
84
+ return parse(printSchema(schema));
85
+ } else if (ext === ".graphql" || ext === ".graphqls" || ext === ".gql") {
86
+ const uri = URI.file(resolve(path)).toString();
87
+ return parse(new Source(result, uri));
88
+ }
89
+ throw new Error(
90
+ "File Type not supported for schema loading. Must be a .json, .graphql, .gql, or .graphqls file"
91
+ );
92
+ }
93
+
94
+ onSchemaChange(
95
+ _handler: NotificationHandler<GraphQLSchema>
96
+ ): SchemaChangeUnsubscribeHandler {
97
+ throw new Error("File watching not implemented yet");
98
+ return () => {};
99
+ }
100
+
101
+ // Load SDL from files. This is only used with federated services,
102
+ // since they need full SDL and not the printout of GraphQLSchema
103
+ async resolveFederatedServiceSDL() {
104
+ if (this.federatedServiceSDL) return this.federatedServiceSDL;
105
+
106
+ const { path, paths } = this.config;
107
+
108
+ // load each path and get sdl string from each, if a list, concatenate them all
109
+ const SDLs = path
110
+ ? [this.loadFileAndGetSDL(path)]
111
+ : paths
112
+ ? paths.map(this.loadFileAndGetSDL, this)
113
+ : undefined;
114
+
115
+ if (!SDLs || SDLs.filter((s) => !Boolean(s)).length > 0)
116
+ return Debug.error(
117
+ `SDL could not be loaded for one of more files: [${
118
+ path ? path : paths ? paths.join(", ") : "undefined"
119
+ }]`
120
+ );
121
+
122
+ const federatedSchema = buildFederatedSchema(
123
+ SDLs.map((sdl) => ({ typeDefs: parse(sdl as string) }))
124
+ );
125
+
126
+ // call the `Query._service` resolver to get the actual printed sdl
127
+ const queryType = federatedSchema.getQueryType();
128
+ if (!queryType)
129
+ return Debug.error("No query type found for federated schema");
130
+ const serviceField = queryType.getFields()["_service"];
131
+ const serviceResults =
132
+ serviceField &&
133
+ serviceField.resolve &&
134
+ serviceField.resolve(null, {}, null, {} as any);
135
+
136
+ if (!serviceResults || !serviceResults.sdl)
137
+ return Debug.error(
138
+ "No SDL resolver or result from federated schema after building"
139
+ );
140
+
141
+ this.federatedServiceSDL = serviceResults.sdl;
142
+ return this.federatedServiceSDL;
143
+ }
144
+
145
+ // this is the mechanism for loading a single file's SDL
146
+ loadFileAndGetSDL(path: string) {
147
+ let result;
148
+ try {
149
+ result = readFileSync(path, {
150
+ encoding: "utf-8",
151
+ });
152
+ } catch (err: any) {
153
+ return Debug.error(`Unable to read file ${path}. ${err.message}`);
154
+ }
155
+
156
+ const ext = extname(path);
157
+
158
+ // this file should already be in sdl format
159
+ if (ext === ".graphql" || ext === ".graphqls" || ext === ".gql") {
160
+ return result as string;
161
+ } else {
162
+ return Debug.error(
163
+ "When using localSchemaFile to check or push a federated service, you can only use .graphql, .gql, and .graphqls files"
164
+ );
165
+ }
166
+ }
167
+ }