UNPKG

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