/
file.ts
167 lines (144 loc) · 5.09 KB
/
file.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
// FileSchemaProvider (FileProvider (SDL || IntrospectionResult) => schema)
import {
GraphQLSchema,
buildClientSchema,
Source,
buildSchema,
printSchema,
parse,
visit
} from "graphql";
import { readFileSync } from "fs";
import { extname, resolve } from "path";
import { GraphQLSchemaProvider, SchemaChangeUnsubscribeHandler } from "./base";
import { NotificationHandler } from "vscode-languageserver";
import { Debug } from "../../utilities";
import { buildSchemaFromSDL } from "apollo-graphql";
import {
buildFederatedSchema,
composeServices,
printSchema as printFederatedSchema
} from "@apollo/federation";
import URI from "vscode-uri";
// import federationDirectives from "@apollo/federation/src/directives";
export interface FileSchemaProviderConfig {
path?: string;
paths?: string[];
}
// XXX file subscription
export class FileSchemaProvider implements GraphQLSchemaProvider {
private schema?: GraphQLSchema;
private federatedServiceSDL?: string;
constructor(private config: FileSchemaProviderConfig) {}
async resolveSchema() {
if (this.schema) return this.schema;
const { path, paths } = this.config;
// load each path and get sdl string from each, if a list, concatenate them all
const documents = path
? [this.loadFileAndGetDocument(path)]
: paths
? paths.map(this.loadFileAndGetDocument, this)
: undefined;
if (!documents)
throw new Error(
`Schema could not be loaded for [${
path ? path : paths ? paths.join(", ") : "undefined"
}]`
);
this.schema = buildSchemaFromSDL(documents);
if (!this.schema) throw new Error(`Schema could not be loaded for ${path}`);
return this.schema;
}
// load a graphql file or introspection result and return the GraphQL DocumentNode
// this is the mechanism for loading a single file's DocumentNode
loadFileAndGetDocument(path: string) {
let result;
try {
result = readFileSync(path, {
encoding: "utf-8"
});
} catch (err) {
throw new Error(`Unable to read file ${path}. ${err.message}`);
}
const ext = extname(path);
// an actual introspection query result, convert to DocumentNode
if (ext === ".json") {
const parsed = JSON.parse(result);
const __schema = parsed.data
? parsed.data.__schema
: parsed.__schema
? parsed.__schema
: parsed;
const schema = buildClientSchema({ __schema });
return parse(printSchema(schema));
} else if (ext === ".graphql" || ext === ".graphqls" || ext === ".gql") {
const uri = URI.file(resolve(path)).toString();
return parse(new Source(result, uri));
}
throw new Error(
"File Type not supported for schema loading. Must be a .json, .graphql, .gql, or .graphqls file"
);
}
onSchemaChange(
_handler: NotificationHandler<GraphQLSchema>
): SchemaChangeUnsubscribeHandler {
throw new Error("File watching not implemented yet");
return () => {};
}
// Load SDL from files. This is only used with federated services,
// since they need full SDL and not the printout of GraphQLSchema
async resolveFederatedServiceSDL() {
if (this.federatedServiceSDL) return this.federatedServiceSDL;
const { path, paths } = this.config;
// load each path and get sdl string from each, if a list, concatenate them all
const SDLs = path
? [this.loadFileAndGetSDL(path)]
: paths
? paths.map(this.loadFileAndGetSDL, this)
: undefined;
if (!SDLs || SDLs.filter(s => !Boolean(s)).length > 0)
return Debug.error(
`SDL could not be loaded for one of more files: [${
path ? path : paths ? paths.join(", ") : "undefined"
}]`
);
const federatedSchema = buildFederatedSchema(
SDLs.map(sdl => ({ typeDefs: parse(sdl as string) }))
);
// call the `Query._service` resolver to get the actual printed sdl
const queryType = federatedSchema.getQueryType();
if (!queryType)
return Debug.error("No query type found for federated schema");
const serviceField = queryType.getFields()["_service"];
const serviceResults =
serviceField &&
serviceField.resolve &&
serviceField.resolve(null, {}, null, {} as any);
if (!serviceResults || !serviceResults.sdl)
return Debug.error(
"No SDL resolver or result from federated schema after building"
);
this.federatedServiceSDL = serviceResults.sdl;
return this.federatedServiceSDL;
}
// this is the mechanism for loading a single file's SDL
loadFileAndGetSDL(path: string) {
let result;
try {
result = readFileSync(path, {
encoding: "utf-8"
});
} catch (err) {
return Debug.error(`Unable to read file ${path}. ${err.message}`);
}
const ext = extname(path);
// this file should already be in sdl format
if (ext === ".graphql" || ext === ".graphqls" || ext === ".gql") {
return result as string;
} else {
return Debug.error(
"When using localSchemaFile to check or push a federated service, you can only use .graphql, .gql, and .graphqls files"
);
}
}
}