1 | "use strict";
|
2 | Object.defineProperty(exports, "__esModule", { value: true });
|
3 | exports.GraphQLClientProject = exports.isClientProject = void 0;
|
4 | const base_1 = require("./base");
|
5 | const graphql_1 = require("graphql");
|
6 | const vscode_languageserver_1 = require("vscode-languageserver");
|
7 | const source_1 = require("../utilities/source");
|
8 | const format_1 = require("../format");
|
9 | const fileSet_1 = require("../fileSet");
|
10 | const defaultClientSchema_1 = require("./defaultClientSchema");
|
11 | const graphql_2 = require("../utilities/graphql");
|
12 | const validation_1 = require("../errors/validation");
|
13 | const diagnostics_1 = require("../diagnostics");
|
14 | function schemaHasASTNodes(schema) {
|
15 | const queryType = schema && schema.getQueryType();
|
16 | return !!(queryType && queryType.astNode);
|
17 | }
|
18 | function 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 | }
|
24 | function isClientProject(project) {
|
25 | return project instanceof GraphQLClientProject;
|
26 | }
|
27 | exports.isClientProject = isClientProject;
|
28 | class 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 | }
|
345 | exports.GraphQLClientProject = GraphQLClientProject;
|
346 |
|
\ | No newline at end of file |