UNPKG

@silexlabs/grapesjs-data-source

Version:
890 lines 38.7 kB
"use strict"; /* * Silex website builder, free/libre no-code tool for makers. * Copyright (c) 2023 lexoyo and Silex Labs foundation * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as published by * the Free Software Foundation, either version 3 of the License, or any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see <https://www.gnu.org/licenses/>. */ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.GITLAB_BATCH_SIZE = exports.DEFAULT_BATCH_SIZE = exports.lightweightTypeNamesQuery = void 0; exports.getBatchSize = getBatchSize; exports.buildBatchTypeQuery = buildBatchTypeQuery; const types_1 = require("../types"); const graphql_introspection_query_1 = __importDefault(require("./graphql-introspection-query")); const dedent_js_1 = __importDefault(require("dedent-js")); const types_2 = require("../types"); const token_1 = require("../model/token"); /** * Lightweight query to fetch type names and kinds during datasource creation * Also fetches queryType name to know which type is the root query type * Does NOT fetch fields, interfaces, enums, inputs, or possibleTypes */ exports.lightweightTypeNamesQuery = ` query TypeNamesQuery { __schema { queryType { name } types { name kind } } } `; /** * Simplified fragment for selective introspection query * Fetches type name, field names, field types, and field arguments * This keeps the query lightweight and avoids GitLab complexity limits */ const selectiveIntrospectionFragment = ` fragment SelectiveType on __Type { name kind fields(includeDeprecated: false) { name args { name type { kind name ofType { kind name ofType { kind name } } } defaultValue } type { kind name possibleTypes { kind name } ofType { kind name possibleTypes { kind name } ofType { kind name possibleTypes { kind name } ofType { kind name possibleTypes { kind name } } } } } } } `; /** * Number of types to fetch per batch request * Balances between number of HTTP requests and query complexity * Default is 100, but GitLab requires smaller batches (5) due to query complexity limits */ exports.DEFAULT_BATCH_SIZE = 100; exports.GITLAB_BATCH_SIZE = 5; function getBatchSize(backendType) { return backendType === 'gitlab' ? exports.GITLAB_BATCH_SIZE : exports.DEFAULT_BATCH_SIZE; } /** * Build a selective introspection query for a batch of types * Uses __type(name: "...") for each type * @param typeNames - Array of type names to fetch * @returns GraphQL query string */ function buildBatchTypeQuery(typeNames) { const typeQueries = typeNames .map(name => { const alias = `type_${name.replace(/[^a-zA-Z0-9_]/g, '_')}`; return ` ${alias}: __type(name: "${name}") { ...SelectiveType }`; }) .join('\n'); return ` query BatchTypeIntrospection { ${typeQueries} } ${selectiveIntrospectionFragment} `; } /** * GraphQL DataSource implementation * Simple JS object that implements IDataSource interface */ class GraphQL { constructor(options) { this.type = 'graphql'; this.method = 'POST'; this.headers = {}; this.backendType = 'generic'; this.types = []; this.queryables = []; this.queryType = ''; this.ready = false; this.eventListeners = {}; this.id = options.id.toString(); this.label = options.label; this.url = options.url; this.type = options.type; this.method = options.method || 'POST'; this.headers = options.headers || {}; this.queryable = options.queryable; this.readonly = options.readonly; this.hidden = options.hidden; this.backendType = options.backendType || 'generic'; this.disabledTypes = options.disabledTypes; } // Simple event handling on(event, callback) { if (!this.eventListeners[event]) { this.eventListeners[event] = []; } this.eventListeners[event].push(callback); } off(event, callback) { if (!this.eventListeners[event]) return; if (callback) { this.eventListeners[event] = this.eventListeners[event].filter(cb => cb !== callback); } else { this.eventListeners[event] = []; } } trigger(event, ...args) { if (!this.eventListeners[event]) return; this.eventListeners[event].forEach(callback => callback(...args)); } /** * Fetch only type names and kinds from the GraphQL endpoint * This is a lightweight query used during datasource creation * Returns an object with types array and queryTypeName */ fetchTypeNames() { return __awaiter(this, void 0, void 0, function* () { var _a, _b, _c; try { const result = yield this.call(exports.lightweightTypeNamesQuery); if (!((_b = (_a = result.data) === null || _a === void 0 ? void 0 : _a.__schema) === null || _b === void 0 ? void 0 : _b.types)) { throw new Error(`Invalid response: ${JSON.stringify(result)}`); } const queryTypeName = ((_c = result.data.__schema.queryType) === null || _c === void 0 ? void 0 : _c.name) || 'Query'; const types = result.data.__schema.types // Filter out introspection types (starting with __) .filter((type) => !type.name.startsWith('__')); return { types, queryTypeName }; } catch (e) { console.error('[GraphQL] Failed to fetch type names:', e.message); throw new Error(`Failed to fetch type names: ${e.message}`); } }); } /** * Get default enabled types based on backend type * @param backendType - The backend type (gitlab, wordpress, generic) * @param allTypes - Array of all available types from the schema with their kinds * @param queryTypeName - The name of the query type from the schema (e.g., 'Query', 'RootQuery') * @returns Array of type names that should be enabled by default */ static getDefaultEnabledTypes(backendType, allTypes, queryTypeName = 'Query') { // Get all type names for filtering const allTypeNames = allTypes.map(t => t.name); // Automatically detect all SCALAR types from the schema // This includes String, Boolean, Int, Float, ID, and any custom scalars like DateTime, JSON, etc. const scalarTypeNames = allTypes .filter(t => t.kind === 'SCALAR') .map(t => t.name); switch (backendType) { case 'gitlab': { // GitLab: Essential types + connections for listing const gitlabCoreTypes = [ 'Project', 'Namespace', 'User', 'Group', 'Repository', 'Commit', 'Branch', 'MergeRequest', 'Issue', 'Pipeline', 'Job', 'Release', 'Milestone', 'Label', 'Note', 'Discussion', 'Snippet', 'Board', 'Epic', 'Vulnerability', 'Package', 'ContainerRepository', 'Environment', 'Deployment', 'CiRunner', 'Topic', 'PageInfo', ]; const gitlabDefaults = [ ...scalarTypeNames, queryTypeName, ...gitlabCoreTypes, // Include connection types for pagination (e.g., GroupConnection, ProjectConnection) ...allTypeNames.filter(name => { // Exclude input/enum types if (name.endsWith('Input') || name.endsWith('Enum')) return false; // Include connections and edges for core types return (name.endsWith('Connection') || name.endsWith('Edge') || // Include core type variations gitlabCoreTypes.some(core => name.startsWith(core))); }), ]; return allTypeNames.filter(name => gitlabDefaults.includes(name)); } case 'wordpress': { // WordPress/WPGraphQL: Blacklist approach for headless CMS // All types are enabled by default, except those explicitly blacklisted // This ensures CPTs (like ACF custom post types) are included automatically // Get all INPUT_OBJECT types (used for mutations, not queries) const inputTypes = allTypes .filter(t => t.kind === 'INPUT_OBJECT') .map(t => t.name); // Types to blacklist (not useful for static site generation) const blacklistedTypes = [ // Mutation root 'RootMutation', // Settings (admin only) 'Settings', 'DiscussionSettings', 'GeneralSettings', 'ReadingSettings', 'WritingSettings', // Admin-only types 'Plugin', 'Theme', 'UserRole', 'Avatar', // Content templates (WP internal, not CPTs) 'ContentTemplate', 'DefaultTemplate', ]; // Patterns to blacklist const blacklistPatterns = [ // All mutation inputs and payloads /^Create.+Input$/, /^Update.+Input$/, /^Delete.+Input$/, /^Register.+Input$/, /^Reset.+Input$/, /^Restore.+Input$/, /^Send.+Input$/, /Payload$/, // Admin connections /^RootQueryToPlugin/, /^RootQueryToTheme/, /^RootQueryToUserRole/, // Plugin/Theme/UserRole related /Plugin(?:Connection|Edge|PageInfo|Status)/, /Theme(?:Connection|Edge|PageInfo)/, /UserRole(?:Connection|Edge|PageInfo)/, // Enqueued assets (internal WP) /Enqueued/, /Script(?!$)/, // Exclude Script-related but not if it's just "Script" /Stylesheet/, // Revisions (internal WP) /Revision/, // Comments (usually not needed for static sites) /Comment/, /Commenter/, // Post formats (rarely used) /PostFormat/, // Generic content types (not needed if querying specific collections) /^RootQueryToContentNode/, ]; const isBlacklisted = (name) => { // Blacklist all INPUT_OBJECT types (mutation inputs) if (inputTypes.includes(name)) return true; // Blacklist explicitly named types if (blacklistedTypes.includes(name)) return true; // Blacklist by pattern return blacklistPatterns.some(pattern => pattern.test(name)); }; // Return all types except blacklisted ones return allTypeNames.filter(name => !isBlacklisted(name)); } case 'strapi': { // Strapi v4/v5 GraphQL: Blacklist approach for headless CMS // Strapi generates types for content types, media, and internal admin types // Get all INPUT_OBJECT types (used for mutations, not queries) const inputTypes = allTypes .filter(t => t.kind === 'INPUT_OBJECT') .map(t => t.name); // Types to blacklist (not useful for static site generation) const blacklistedTypes = [ // Mutation root 'Mutation', // Generic internal type 'GenericMorph', ]; // Patterns to blacklist const blacklistPatterns = [ // Mutation payloads /Payload$/, // Users & Permissions plugin (admin) /^UsersPermissions/, // Upload folder management (admin) /^UploadFolder/, // i18n internal types /^I18N/, // Entity wrappers (Strapi v4 internal) /EntityResponse$/, /EntityResponseCollection$/, // Content type metadata (admin) /^ContentType/, // Admin panel types /^Admin/, ]; const isBlacklisted = (name) => { // Blacklist all INPUT_OBJECT types (mutation inputs) if (inputTypes.includes(name)) return true; // Blacklist explicitly named types if (blacklistedTypes.includes(name)) return true; // Blacklist by pattern return blacklistPatterns.some(pattern => pattern.test(name)); }; // Return all types except blacklisted ones return allTypeNames.filter(name => !isBlacklisted(name)); } case 'supabase': { // Supabase (pg_graphql): Blacklist approach // pg_graphql generates types from PostgreSQL tables // Get all INPUT_OBJECT types (used for mutations, not queries) const inputTypes = allTypes .filter(t => t.kind === 'INPUT_OBJECT') .map(t => t.name); // Types to blacklist (not useful for static site generation) const blacklistedTypes = [ // Mutation root 'Mutation', // Cursor type (internal pagination) 'Cursor', ]; // Patterns to blacklist const blacklistPatterns = [ // Mutation-related types /InsertInput$/, /UpdateInput$/, /DeleteInput$/, /InsertResponse$/, /UpdateResponse$/, /DeleteResponse$/, // Ordering types (query params, not data) /OrderBy$/, /OrderByDirection$/, // Filter types (query params, not data) /Filter$/, /FilterInput$/, // Edge types (keep Connection but not Edge for simpler queries) /Edge$/, ]; const isBlacklisted = (name) => { // Blacklist all INPUT_OBJECT types (mutation inputs) if (inputTypes.includes(name)) return true; // Blacklist explicitly named types if (blacklistedTypes.includes(name)) return true; // Blacklist by pattern return blacklistPatterns.some(pattern => pattern.test(name)); }; // Return all types except blacklisted ones return allTypeNames.filter(name => !isBlacklisted(name)); } case 'generic': default: // Generic: All types checked by default return [...allTypeNames]; } } /** * @throws Error */ triggerError(message) { console.error('GraphQL error:', message); this.trigger(types_1.DATA_SOURCE_ERROR, message, this); throw new Error(message); } loadData() { return __awaiter(this, void 0, void 0, function* () { var _a, _b, _c, _d, _e, _f; try { let schemaTypes; let queryTypeName; // If disabledTypes is set, compute enabled types and use selective introspection if (this.disabledTypes && this.disabledTypes.length > 0) { // Fetch all type names (lightweight query) const lightweightResult = yield this.call(exports.lightweightTypeNamesQuery); if (!((_b = (_a = lightweightResult.data) === null || _a === void 0 ? void 0 : _a.__schema) === null || _b === void 0 ? void 0 : _b.types)) { return this.triggerError(`Invalid lightweight response: ${JSON.stringify(lightweightResult)}`); } const allTypeNames = lightweightResult.data.__schema.types .map(t => t.name) .filter(name => !name.startsWith('__')); // Get the queryType name from the lightweight query const lightweightQueryTypeName = ((_c = lightweightResult.data.__schema.queryType) === null || _c === void 0 ? void 0 : _c.name) || 'Query'; // Compute enabled types by excluding disabled ones (blacklist logic) // Always include queryType and SCALAR types - they cannot be blacklisted const scalarTypeNames = lightweightResult.data.__schema.types .filter(t => t.kind === 'SCALAR' && !t.name.startsWith('__')) .map(t => t.name); const enabledTypeNames = allTypeNames.filter(name => !this.disabledTypes.includes(name)); // Ensure queryType is always included (e.g., Query, RootQuery, etc.) if (!enabledTypeNames.includes(lightweightQueryTypeName) && allTypeNames.includes(lightweightQueryTypeName)) { enabledTypeNames.push(lightweightQueryTypeName); } // Ensure all SCALAR types are always included for (const scalarName of scalarTypeNames) { if (!enabledTypeNames.includes(scalarName)) { enabledTypeNames.push(scalarName); } } // Split types into batches (smaller batches for GitLab due to query complexity limits) const batchSize = getBatchSize(this.backendType); const batches = []; for (let i = 0; i < enabledTypeNames.length; i += batchSize) { batches.push(enabledTypeNames.slice(i, i + batchSize)); } // Fetch batches in parallel const batchResults = yield Promise.all(batches.map((batch) => __awaiter(this, void 0, void 0, function* () { try { const query = buildBatchTypeQuery(batch); const result = yield this.call(query); // Extract types from aliased responses const types = []; for (const [key, value] of Object.entries(result.data || {})) { if (key.startsWith('type_') && value !== null) { types.push(value); } } return types; } catch (e) { console.warn('[GraphQL] Failed to fetch batch:', e.message); return []; } }))); // Flatten batch results schemaTypes = batchResults.flat(); queryTypeName = lightweightQueryTypeName; } else { // No blacklist - use full introspection query const result = yield this.call(graphql_introspection_query_1.default); if (!((_e = (_d = result.data) === null || _d === void 0 ? void 0 : _d.__schema) === null || _e === void 0 ? void 0 : _e.types)) { return this.triggerError(`Invalid response: ${JSON.stringify(result)}`); } schemaTypes = result.data.__schema.types; queryTypeName = (_f = result.data.__schema.queryType) === null || _f === void 0 ? void 0 : _f.name; } if (!queryTypeName) { return this.triggerError('Invalid response, queryType not found'); } const allTypes = schemaTypes.map((type) => type.name) .concat(types_1.builtinTypeIds); const query = schemaTypes.find((type) => type.name === queryTypeName); if (!query) { return this.triggerError(`Query type "${queryTypeName}" not found in schema. Make sure to enable it.`); } // Get non-queryable types const nonQueryables = schemaTypes // Filter out introspection types .filter((type) => !type.name.startsWith('__')) // Filter out types that are in Query (the queryables are handled separately) .filter((type) => { var _a; return !((_a = query === null || query === void 0 ? void 0 : query.fields) === null || _a === void 0 ? void 0 : _a.find((field) => field.name === type.name)); }) // Map to Type .map((type) => this.graphQLToType(allTypes, type, 'SCALAR', false)) // Add builtin types .concat(types_1.builtinTypes); // Get queryable types const queryableTypes = (query.fields || []) // Map to GQLType, keeping kind for later .map((field) => ({ type: Object.assign(Object.assign({}, schemaTypes.find((type) => type.name === this.getOfTypeProp('name', field.type, field.name))), { name: field.name }), kind: this.ofKindToKind(field.type), })) // Filter out types that were excluded .filter(({ type }) => type.fields !== undefined) // Map to Type .map(({ type, kind }) => this.graphQLToType(allTypes, type, kind, true)); // Get all queryables as fields const queryableFields = (query.fields || []) // Filter out fields whose types were not fetched .filter((field) => { const typeName = this.getOfTypeProp('name', field.type, field.name); // Include if the type exists in schemaTypes or is a builtin type return schemaTypes.some(t => t.name === typeName) || types_1.builtinTypeIds.includes(typeName); }) // Map to Field .map((field) => this.graphQLToField(field)); // Return all types, queryables and non-queryables return [queryableTypes.concat(nonQueryables), queryableFields, queryTypeName]; } catch (e) { return this.triggerError(`GraphQL introspection failed: ${e.message}`); } }); } graphQLToField(field) { var _a; const kind = this.ofKindToKind(field.type); return { id: field.name, dataSourceId: this.id, label: field.name, typeIds: this.graphQLToTypes(field), kind: kind ? this.graphQLToKind(kind) : 'unknown', arguments: (_a = field.args) === null || _a === void 0 ? void 0 : _a.map(arg => ({ name: arg.name, typeId: this.getOfTypeProp('name', arg.type, arg.name), defaultValue: arg.defaultValue, })), }; } /** * Recursively search for a property on a GraphQL type * Check the deepest values in ofType first */ getOfTypeProp(prop, type, defaultValue) { const result = this.getOfTypePropRecursive(prop, type); if (result) return result; if (defaultValue) return defaultValue; throw new Error(`Type ${JSON.stringify(type)} has no property ${prop} and no default was provided`); } getOfTypePropRecursive(prop, type) { if (!type) { console.error('Invalid type', type); throw new Error('Invalid type'); } if (type.ofType) { const ofTypeResult = this.getOfTypePropRecursive(prop, type.ofType); if (ofTypeResult) return ofTypeResult; } return type[prop]; } /** * Recursively search for a property on a GraphQL type * Handles Union types with possibleTypes * Handles list and object and non-null types with ofType */ graphQLToTypes(field) { const possibleTypes = this.getOfTypeProp('possibleTypes', field.type, []); if (possibleTypes.length > 0) { return possibleTypes.map(type => type.name); } const typeName = this.getOfTypeProp('name', field.type, field.name); return [typeName]; } /** * Convert GraphQL kind to FieldKind * @throws Error if kind is not valid or is NON_NULL */ graphQLToKind(kind) { switch (kind) { case 'LIST': return 'list'; case 'OBJECT': return 'object'; case 'SCALAR': return 'scalar'; case 'UNION': case 'NON_NULL': default: throw new Error(`Unable to find a valid kind for ${kind}`); } } /** * Check if a GraphQL kind has a valid FieldKind equivalent */ validKind(kind) { return ['LIST', 'OBJECT', 'SCALAR'].includes(kind); } /** * Recursively search for a GraphQL kind of type list, object or scalar */ ofKindToKind(ofKind) { if (ofKind.possibleTypes) { const foundKind = ofKind.possibleTypes .reduce((prev, type) => { if (!prev) return type.kind; if (prev !== type.kind) { throw new Error(`Unable to find a valid kind for ${ofKind.kind}. Union types with different kind is not supported`); } return prev; }, null); if (!foundKind) { console.error('Unable to find a valid kind (1)', ofKind); return null; } return foundKind; } if (this.validKind(ofKind.kind)) return ofKind.kind; if (ofKind.ofType) return this.ofKindToKind(ofKind.ofType); // This happens when the type is missing // Remove the warning because it happens with directus and polutes the logs // console.error('Unable to find a valid kind (2)', ofKind) return null; } /** * Convert a GraphQL type to a Type */ graphQLToType(allTypes, type, kind, queryable) { var _a, _b, _c; const queryableOverride = this.queryable; const result = { id: type.name, dataSourceId: this.id, label: type.name, fields: (_c = (_b = (_a = type.fields) === null || _a === void 0 ? void 0 : _a.filter((field) => allTypes.includes(this.getOfTypeProp('name', field.type, field.name)))) === null || _b === void 0 ? void 0 : _b.map(field => this.graphQLToField(field))) !== null && _c !== void 0 ? _c : [], queryable: queryable && (!queryableOverride || queryableOverride.includes(type.name)), }; return result; } /** * Connect to the GraphQL endpoint and load the schema * This has to be implemented as it is a DataSource method */ connect() { return __awaiter(this, void 0, void 0, function* () { try { // const result = await this.call(` // query { // __typename // } // `) as any // if (!result?.data?.__typename) return this.triggerError(`Invalid response: ${JSON.stringify(result)}`) const [types, fields, queryType] = yield this.loadData(); if (types.length === 0) return this.triggerError('No types found in GraphQL schema'); if (fields.length === 0) return this.triggerError('No fields found in GraphQL schema'); if (!queryType) return this.triggerError('No query type found in GraphQL schema'); this.types = types; this.queryables = fields; this.queryType = queryType; if (this.ready) { this.trigger(types_1.DATA_SOURCE_CHANGED, this); } else { this.ready = true; this.trigger(types_1.DATA_SOURCE_READY, this); } } catch (e) { return this.triggerError(`GraphQL connection failed: ${e.message}`); } }); } /** * Check if the DataSource is ready * This has to be implemented as it is a DataSource method */ isConnected() { return this.ready; } /** * Get all types * This has to be implemented as it is a DataSource method */ getTypes() { if (!this.ready) { console.error('DataSource is not ready. Attempted to get types before ready status was achieved.'); throw new Error('DataSource is not ready. Ensure it is connected and ready before querying.'); } if (this.types.length === 0) { console.error('No types available. It seems the data source may not be connected or the schema is incomplete.', this.ready); throw new Error('No types found. The data source may not be connected or there might be an issue with the schema.'); } return this.types; } /** * Get all queryable fields * This has to be implemented as it is a DataSource method */ getQueryables() { return this.queryables; } /** * Call the GraphQL endpoint */ call(query) { return __awaiter(this, void 0, void 0, function* () { var _a; // Retrieve the URL for the GraphQL endpoint const url = this.url; if (!url) return this.triggerError('Missing GraphQL URL'); // Ensure the URL is provided // Retrieve the headers for the GraphQL request const headers = this.headers; if (!headers) return this.triggerError('Missing GraphQL headers'); // Ensure headers are provided // Ensure the Content-Type header is set to 'application/json', normalizing the case const key = Object.keys(headers).find(name => name.toLowerCase() === 'content-type'); headers[key || 'Content-Type'] = headers[key || 'Content-Type'] || 'application/json'; // Retrieve the HTTP method (defaulting to 'POST' for GraphQL queries) const method = (_a = this.method) !== null && _a !== void 0 ? _a : 'POST'; // Make the HTTP request to the GraphQL endpoint const response = yield fetch(url, Object.assign({ method, headers }, (method === 'POST' ? { body: JSON.stringify({ query }), } : {}))); // Handle non-OK responses with detailed error logging if (!(response === null || response === void 0 ? void 0 : response.ok)) { console.error('GraphQL call failed', response === null || response === void 0 ? void 0 : response.status, response === null || response === void 0 ? void 0 : response.statusText, query); return this.triggerError(`GraphQL call failed with \`${response === null || response === void 0 ? void 0 : response.statusText}\` and status ${response === null || response === void 0 ? void 0 : response.status}`); } // Return the parsed JSON response return response.json(); }); } /** * Build a GraphQL query from a tree */ getQuery(children) { return this.getQueryRecursive({ // Add the main query object which is the root of the tree token: { dataSourceId: this.id, fieldId: 'query', kind: 'object', typeIds: [this.queryType], }, children, }); } getQueryRecursive(tree, indent = '', fragment = '') { // Check if the tree is a fragment const typeOrFragment = fragment ? `...on ${fragment}` : `${tree.token.fieldId}${(0, token_1.buildArgs)(tree.token.options)}`; // Build the value switch (tree.token.kind) { case 'scalar': if (tree.token.fieldId === types_2.FIXED_TOKEN_ID) return ''; return indent + typeOrFragment; case 'object': case 'list': { const types = this.getTypes().filter(t => { var _a; return (_a = tree.token.typeIds) === null || _a === void 0 ? void 0 : _a.includes(t.id); }); if (types.length === 0) { throw new Error(`Type not found for ${tree.token.fieldId} (${tree.token.typeIds})`); } else if (types.length > 1) throw new Error(`Multiple types found for ${tree.token.fieldId}`); const type = types[0]; const fieldTypes = tree.children .map(child => { const fieldType = type.fields.find(f => f.id === child.token.fieldId); if (!fieldType) { // Not a queryable type return null; } return { fieldType, child, }; }) // Remove non-queryable types .filter(fieldType => fieldType !== null); // Handle fragments const fragments = fieldTypes .filter(({ fieldType }) => fieldType.typeIds.length > 1) .map(({ child }) => { return { query: this.getQueryRecursive(child, indent + ' ', child.token.typeIds[0]), child, }; }); const fragmentsQuery = fragments .map(({ query, child }) => (0, dedent_js_1.default) ` ${indent}${child.token.fieldId} { ${query} } `) .join('\n'); // Handle simple case, no fragment const childQuery = fieldTypes .filter(({ fieldType }) => fieldType.typeIds.length === 1) .map(({ child }) => { return this.getQueryRecursive(child, indent + ' '); }) .join('\n'); return (0, dedent_js_1.default) `${indent}${typeOrFragment} { ${indent} __typename ${childQuery} ${fragmentsQuery} ${indent}}`; } default: console.error('Unable to build GraphQL query', tree); throw new Error(`Unable to build GraphQL query: unable to build tree ${JSON.stringify(tree)}`); } } fetchValues(query) { return __awaiter(this, void 0, void 0, function* () { const result = yield this.call(query); return result.data; }); } } exports.default = GraphQL; //# sourceMappingURL=GraphQL.js.map