UNPKG

14.1 kBJavaScriptView Raw
1"use strict";
2Object.defineProperty(exports, "__esModule", { value: true });
3exports.GraphQLClientProject = exports.isClientProject = void 0;
4const base_1 = require("./base");
5const graphql_1 = require("graphql");
6const vscode_languageserver_1 = require("vscode-languageserver");
7const source_1 = require("../utilities/source");
8const format_1 = require("../format");
9const fileSet_1 = require("../fileSet");
10const defaultClientSchema_1 = require("./defaultClientSchema");
11const graphql_2 = require("../utilities/graphql");
12const validation_1 = require("../errors/validation");
13const diagnostics_1 = require("../diagnostics");
14function schemaHasASTNodes(schema) {
15 const queryType = schema && schema.getQueryType();
16 return !!(queryType && queryType.astNode);
17}
18function augmentSchemaWithGeneratedSDLIfNeeded(schema) {
19 if (schemaHasASTNodes(schema))
20 return schema;
21 const sdl = graphql_1.printSchema(schema);
22 return graphql_1.buildSchema(new graphql_1.Source(sdl, `graphql-schema:/schema.graphql?${encodeURIComponent(sdl)}`));
23}
24function isClientProject(project) {
25 return project instanceof GraphQLClientProject;
26}
27exports.isClientProject = isClientProject;
28class GraphQLClientProject extends base_1.GraphQLProject {
29 constructor({ config, loadingHandler, rootURI, clientIdentity }) {
30 const fileSet = new fileSet_1.FileSet({
31 rootURI: config.configDirURI || rootURI,
32 includes: [...config.client.includes, ".env", "apollo.config.js"],
33 excludes: config.client.excludes,
34 configURI: config.configURI
35 });
36 super({ config, fileSet, loadingHandler, clientIdentity });
37 this.rootURI = rootURI;
38 this.serviceID = config.graph;
39 const filterConfigAndEnvFiles = (path) => !(path.includes("apollo.config") ||
40 path.includes(".env") ||
41 (config.configURI && path === config.configURI.fsPath));
42 if (fileSet.allFiles().filter(filterConfigAndEnvFiles).length === 0) {
43 console.warn("⚠️ It looks like there are 0 files associated with this Apollo Project. " +
44 "This may be because you don't have any files yet, or your includes/excludes " +
45 "fields are configured incorrectly, and Apollo can't find your files. " +
46 "For help configuring Apollo projects, see this guide: https://go.apollo.dev/t/config");
47 }
48 const { validationRules } = this.config.client;
49 if (typeof validationRules === "function") {
50 this._validationRules = validation_1.defaultValidationRules.filter(validationRules);
51 }
52 else {
53 this._validationRules = validationRules;
54 }
55 this.loadEngineData();
56 }
57 get displayName() {
58 return this.config.graph || "Unnamed Project";
59 }
60 initialize() {
61 return [this.scanAllIncludedFiles(), this.loadServiceSchema()];
62 }
63 getProjectStats() {
64 const filterTypes = (type) => !/^__|Boolean|ID|Int|String|Float/.test(type);
65 const serviceTypes = this.serviceSchema
66 ? Object.keys(this.serviceSchema.getTypeMap()).filter(filterTypes).length
67 : 0;
68 const totalTypes = this.schema
69 ? Object.keys(this.schema.getTypeMap()).filter(filterTypes).length
70 : 0;
71 return {
72 type: "client",
73 serviceId: this.serviceID,
74 types: {
75 service: serviceTypes,
76 client: totalTypes - serviceTypes,
77 total: totalTypes
78 },
79 tag: this.config.variant,
80 loaded: Boolean(this.schema || this.serviceSchema),
81 lastFetch: this.lastLoadDate
82 };
83 }
84 onDecorations(handler) {
85 this._onDecorations = handler;
86 }
87 onSchemaTags(handler) {
88 this._onSchemaTags = handler;
89 }
90 async updateSchemaTag(tag) {
91 await this.loadServiceSchema(tag);
92 this.invalidate();
93 }
94 async loadServiceSchema(tag) {
95 await this.loadingHandler.handle(`Loading schema for ${this.displayName}`, (async () => {
96 this.serviceSchema = augmentSchemaWithGeneratedSDLIfNeeded(await this.schemaProvider.resolveSchema({
97 tag: tag || this.config.variant,
98 force: true
99 }));
100 this.schema = graphql_1.extendSchema(this.serviceSchema, this.clientSchema);
101 })());
102 }
103 async resolveSchema() {
104 if (!this.schema)
105 throw new Error();
106 return this.schema;
107 }
108 get clientSchema() {
109 return {
110 kind: graphql_1.Kind.DOCUMENT,
111 definitions: [
112 ...this.typeSystemDefinitionsAndExtensions,
113 ...this.missingApolloClientDirectives
114 ]
115 };
116 }
117 get missingApolloClientDirectives() {
118 const { serviceSchema } = this;
119 const serviceDirectives = serviceSchema
120 ? serviceSchema.getDirectives().map(directive => directive.name)
121 : [];
122 const clientDirectives = this.typeSystemDefinitionsAndExtensions
123 .filter(graphql_2.isDirectiveDefinitionNode)
124 .map(def => def.name.value);
125 const existingDirectives = serviceDirectives.concat(clientDirectives);
126 const apolloAst = defaultClientSchema_1.apolloClientSchemaDocument.ast;
127 if (!apolloAst)
128 return [];
129 const apolloDirectives = apolloAst.definitions
130 .filter(graphql_2.isDirectiveDefinitionNode)
131 .map(def => def.name.value);
132 for (const existingDirective of existingDirectives) {
133 if (apolloDirectives.includes(existingDirective)) {
134 return [];
135 }
136 }
137 return apolloAst.definitions;
138 }
139 addClientMetadataToSchemaNodes() {
140 const { schema, serviceSchema } = this;
141 if (!schema || !serviceSchema)
142 return;
143 graphql_1.visit(this.clientSchema, {
144 ObjectTypeExtension(node) {
145 const type = schema.getType(node.name.value);
146 const { fields } = node;
147 if (!fields || !type)
148 return;
149 const localInfo = type.clientSchema || {};
150 localInfo.localFields = [
151 ...(localInfo.localFields || []),
152 ...fields.map(field => field.name.value)
153 ];
154 type.clientSchema = localInfo;
155 }
156 });
157 }
158 async validate() {
159 if (!this._onDiagnostics)
160 return;
161 if (!this.serviceSchema)
162 return;
163 const diagnosticSet = new diagnostics_1.DiagnosticSet();
164 try {
165 this.schema = graphql_1.extendSchema(this.serviceSchema, this.clientSchema);
166 this.addClientMetadataToSchemaNodes();
167 }
168 catch (error) {
169 if (error instanceof graphql_1.GraphQLError) {
170 const uri = error.source && error.source.name;
171 if (uri) {
172 diagnosticSet.addDiagnostics(uri, diagnostics_1.diagnosticsFromError(error, vscode_languageserver_1.DiagnosticSeverity.Error, "Validation"));
173 }
174 }
175 else {
176 console.error(error);
177 }
178 this.schema = this.serviceSchema;
179 }
180 const fragments = this.fragments;
181 for (const [uri, documentsForFile] of this.documentsByFile) {
182 for (const document of documentsForFile) {
183 diagnosticSet.addDiagnostics(uri, diagnostics_1.collectExecutableDefinitionDiagnositics(this.schema, document, fragments, this._validationRules));
184 }
185 }
186 for (const [uri, diagnostics] of diagnosticSet.entries()) {
187 this._onDiagnostics({ uri, diagnostics });
188 }
189 this.diagnosticSet = diagnosticSet;
190 this.generateDecorations();
191 }
192 async loadEngineData() {
193 const engineClient = this.engineClient;
194 if (!engineClient)
195 return;
196 const serviceID = this.serviceID;
197 if (!serviceID)
198 return;
199 await this.loadingHandler.handle(`Loading Apollo data for ${this.displayName}`, (async () => {
200 try {
201 const { schemaTags, fieldStats } = await engineClient.loadSchemaTagsAndFieldStats(serviceID);
202 this._onSchemaTags && this._onSchemaTags([serviceID, schemaTags]);
203 this.fieldStats = fieldStats;
204 this.lastLoadDate = +new Date();
205 this.generateDecorations();
206 }
207 catch (e) {
208 console.error(e);
209 }
210 })());
211 }
212 generateDecorations() {
213 if (!this._onDecorations)
214 return;
215 if (!this.schema)
216 return;
217 const decorations = [];
218 for (const [uri, queryDocumentsForFile] of this.documentsByFile) {
219 for (const queryDocument of queryDocumentsForFile) {
220 if (queryDocument.ast && this.fieldStats) {
221 const fieldStats = this.fieldStats;
222 const typeInfo = new graphql_1.TypeInfo(this.schema);
223 graphql_1.visit(queryDocument.ast, graphql_1.visitWithTypeInfo(typeInfo, {
224 enter: node => {
225 if (node.kind == "Field" && typeInfo.getParentType()) {
226 const parentName = typeInfo.getParentType().name;
227 const parentEngineStat = fieldStats.get(parentName);
228 const engineStat = parentEngineStat
229 ? parentEngineStat.get(node.name.value)
230 : undefined;
231 if (engineStat && engineStat > 1) {
232 decorations.push({
233 document: uri,
234 message: `~${format_1.formatMS(engineStat, 0)}`,
235 range: source_1.rangeForASTNode(node)
236 });
237 }
238 }
239 }
240 }));
241 }
242 }
243 }
244 this._onDecorations(decorations);
245 }
246 get fragments() {
247 const fragments = Object.create(null);
248 for (const document of this.documents) {
249 if (!document.ast)
250 continue;
251 for (const definition of document.ast.definitions) {
252 if (definition.kind === graphql_1.Kind.FRAGMENT_DEFINITION) {
253 fragments[definition.name.value] = definition;
254 }
255 }
256 }
257 return fragments;
258 }
259 get operations() {
260 const operations = Object.create(null);
261 for (const document of this.documents) {
262 if (!document.ast)
263 continue;
264 for (const definition of document.ast.definitions) {
265 if (definition.kind === graphql_1.Kind.OPERATION_DEFINITION) {
266 if (!definition.name) {
267 throw new graphql_1.GraphQLError("Apollo does not support anonymous operations", [definition]);
268 }
269 operations[definition.name.value] = definition;
270 }
271 }
272 }
273 return operations;
274 }
275 get mergedOperationsAndFragments() {
276 return graphql_1.separateOperations({
277 kind: graphql_1.Kind.DOCUMENT,
278 definitions: [
279 ...Object.values(this.fragments),
280 ...Object.values(this.operations)
281 ]
282 });
283 }
284 get mergedOperationsAndFragmentsForService() {
285 const { clientOnlyDirectives, clientSchemaDirectives, addTypename } = this.config.client;
286 const current = this.mergedOperationsAndFragments;
287 if ((!clientOnlyDirectives || !clientOnlyDirectives.length) &&
288 (!clientSchemaDirectives || !clientSchemaDirectives.length))
289 return current;
290 const filtered = Object.create(null);
291 for (const operationName in current) {
292 const document = current[operationName];
293 let serviceOnly = graphql_2.removeDirectiveAnnotatedFields(graphql_2.removeDirectives(document, clientOnlyDirectives), clientSchemaDirectives);
294 if (addTypename)
295 serviceOnly = graphql_2.withTypenameFieldAddedWhereNeeded(serviceOnly);
296 if (serviceOnly.definitions.filter(Boolean).length) {
297 filtered[operationName] = serviceOnly;
298 }
299 }
300 return filtered;
301 }
302 getOperationFieldsFromFieldDefinition(fieldName, parent) {
303 if (!this.schema || !parent)
304 return [];
305 const fields = [];
306 const typeInfo = new graphql_1.TypeInfo(this.schema);
307 for (const document of this.documents) {
308 if (!document.ast)
309 continue;
310 graphql_1.visit(document.ast, graphql_1.visitWithTypeInfo(typeInfo, {
311 Field(node) {
312 if (node.name.value !== fieldName)
313 return;
314 const parentType = typeInfo.getParentType();
315 if (parentType && parentType.name === parent.name.value) {
316 fields.push(node);
317 }
318 return;
319 }
320 }));
321 }
322 return fields;
323 }
324 fragmentSpreadsForFragment(fragmentName) {
325 const fragmentSpreads = [];
326 for (const document of this.documents) {
327 if (!document.ast)
328 continue;
329 graphql_1.visit(document.ast, {
330 FragmentSpread(node) {
331 if (node.name.value === fragmentName) {
332 fragmentSpreads.push(node);
333 }
334 }
335 });
336 }
337 return fragmentSpreads;
338 }
339}
340exports.GraphQLClientProject = GraphQLClientProject;
341//# sourceMappingURL=client.js.map
\No newline at end of file