UNPKG

@directus/api

Version:

Directus is a real-time API and App dashboard for managing SQL database content

81 lines (80 loc) 3.04 kB
import { useLogger } from '../../../logger/index.js'; /** * Regex was taken from the spec * https://spec.graphql.org/June2018/#sec-Names */ const GRAPHQL_NAME_REGEX = /^[_A-Za-z][_0-9A-Za-z]*$/; /** * Manually curated list of GraphQL reserved names to cover the most likely naming footguns. * This list is not exhaustive and does not cover generated type names. */ const GRAPHQL_RESERVED_NAMES = [ 'Subscription', 'Query', 'Mutation', 'Int', 'Float', 'String', 'Boolean', 'DateTime', 'ID', 'uid', 'Point', 'PointList', 'Polygon', 'MultiPolygon', 'JSON', 'Hash', 'Date', 'Void', ]; /** * Filters out invalid collections to prevent graphql from errorring on schema generation * * @param schema * @returns sanitized schema */ export function sanitizeGraphqlSchema(schema) { const logger = useLogger(); const collectionEntries = Object.entries(schema.collections).filter(([collectionName, _data]) => { // double underscore __ is reserved for GraphQL introspection if (collectionName.startsWith('__') || !collectionName.match(GRAPHQL_NAME_REGEX)) { logger.warn(`GraphQL skipping collection "${collectionName}" because it is not a valid name matching /^[_A-Za-z][_0-9A-Za-z]*$/ or starts with __`); return false; } if (GRAPHQL_RESERVED_NAMES.includes(collectionName)) { logger.warn(`GraphQL skipping collection "${collectionName}" because it is a reserved keyword`); return false; } return true; }); const collections = Object.fromEntries(collectionEntries); const collectionExists = (collection) => Boolean(collections[collection]); const skipRelation = (relation) => { const relationName = relation.schema?.constraint_name ?? `${relation.collection}.${relation.field}`; logger.warn(`GraphQL skipping relation "${relationName}" because it links to a non-existent or invalid collection.`); return false; }; const relations = schema.relations.filter((relation) => { if (relation.collection && !collectionExists(relation.collection)) { return skipRelation(relation); } if (relation.related_collection && !collectionExists(relation.related_collection)) { return skipRelation(relation); } if (relation.meta) { if (relation.meta.many_collection && !collectionExists(relation.meta.many_collection)) { return skipRelation(relation); } if (relation.meta.one_collection && !collectionExists(relation.meta.one_collection)) { return skipRelation(relation); } if (relation.meta.one_allowed_collections && relation.meta.one_allowed_collections.some((allowed_collection) => !collectionExists(allowed_collection))) { return skipRelation(relation); } } return true; }); return { collections, relations }; }