UNPKG

postgraphile-plugin-connection-filter

Version:
128 lines 7.55 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); const PgConnectionArgFilterForwardRelationsPlugin = (builder) => { builder.hook("inflection", (inflection) => (Object.assign(Object.assign({}, inflection), { filterForwardRelationExistsFieldName(relationFieldName) { return `${relationFieldName}Exists`; }, filterSingleRelationFieldName(fieldName) { return fieldName; } }))); builder.hook("GraphQLInputObjectType:fields", (fields, build, context) => { const { describePgEntity, extend, newWithHooks, inflection, graphql: { GraphQLBoolean }, pgOmit: omit, pgSql: sql, pgIntrospectionResultsByKind: introspectionResultsByKind, connectionFilterResolve, connectionFilterRegisterResolver, connectionFilterTypesByTypeName, connectionFilterType, } = build; const { fieldWithHooks, scope: { pgIntrospection: table, isPgConnectionFilter }, Self, } = context; if (!isPgConnectionFilter || table.kind !== "class") return fields; connectionFilterTypesByTypeName[Self.name] = Self; const forwardRelationSpecs = introspectionResultsByKind.constraint .filter((con) => con.type === "f") .filter((con) => con.classId === table.id) .reduce((memo, constraint) => { if (omit(constraint, "read") || omit(constraint, "filter")) { return memo; } const foreignTable = constraint.foreignClassId ? introspectionResultsByKind.classById[constraint.foreignClassId] : null; if (!foreignTable) { throw new Error(`Could not find the foreign table (constraint: ${constraint.name})`); } if (omit(foreignTable, "read") || omit(foreignTable, "filter")) { return memo; } const attributes = introspectionResultsByKind.attribute .filter((attr) => attr.classId === table.id) .sort((a, b) => a.num - b.num); const foreignAttributes = introspectionResultsByKind.attribute .filter((attr) => attr.classId === foreignTable.id) .sort((a, b) => a.num - b.num); const keyAttributes = constraint.keyAttributeNums.map((num) => attributes.filter((attr) => attr.num === num)[0]); const foreignKeyAttributes = constraint.foreignKeyAttributeNums.map((num) => foreignAttributes.filter((attr) => attr.num === num)[0]); if (keyAttributes.some((attr) => omit(attr, "read"))) { return memo; } if (foreignKeyAttributes.some((attr) => omit(attr, "read"))) { return memo; } memo.push({ table, keyAttributes, foreignTable, foreignKeyAttributes, constraint, }); return memo; }, []); let forwardRelationSpecByFieldName = {}; const addField = (fieldName, description, type, resolve, spec, hint) => { // Field fields = extend(fields, { [fieldName]: fieldWithHooks(fieldName, { description, type, }, { isPgConnectionFilterField: true, }), }, hint); // Spec for use in resolver forwardRelationSpecByFieldName = extend(forwardRelationSpecByFieldName, { [fieldName]: spec, }); // Resolver connectionFilterRegisterResolver(Self.name, fieldName, resolve); }; const resolve = ({ sourceAlias, fieldName, fieldValue, queryBuilder, }) => { if (fieldValue == null) return null; const { foreignTable, foreignKeyAttributes, keyAttributes } = forwardRelationSpecByFieldName[fieldName]; const foreignTableAlias = sql.identifier(Symbol()); const sqlIdentifier = sql.identifier(foreignTable.namespace.name, foreignTable.name); const sqlKeysMatch = sql.query `(${sql.join(keyAttributes.map((key, i) => { return sql.fragment `${sourceAlias}.${sql.identifier(key.name)} = ${foreignTableAlias}.${sql.identifier(foreignKeyAttributes[i].name)}`; }), ") and (")})`; const foreignTableTypeName = inflection.tableType(foreignTable); const foreignTableFilterTypeName = inflection.filterType(foreignTableTypeName); const sqlFragment = connectionFilterResolve(fieldValue, foreignTableAlias, foreignTableFilterTypeName, queryBuilder); return sqlFragment == null ? null : sql.query `\ exists( select 1 from ${sqlIdentifier} as ${foreignTableAlias} where ${sqlKeysMatch} and (${sqlFragment}) )`; }; const resolveExists = ({ sourceAlias, fieldName, fieldValue, }) => { if (fieldValue == null) return null; const { foreignTable, foreignKeyAttributes, keyAttributes } = forwardRelationSpecByFieldName[fieldName]; const foreignTableAlias = sql.identifier(Symbol()); const sqlIdentifier = sql.identifier(foreignTable.namespace.name, foreignTable.name); const sqlKeysMatch = sql.query `(${sql.join(keyAttributes.map((key, i) => { return sql.fragment `${sourceAlias}.${sql.identifier(key.name)} = ${foreignTableAlias}.${sql.identifier(foreignKeyAttributes[i].name)}`; }), ") and (")})`; const sqlSelectWhereKeysMatch = sql.query `select 1 from ${sqlIdentifier} as ${foreignTableAlias} where ${sqlKeysMatch}`; return fieldValue === true ? sql.query `exists(${sqlSelectWhereKeysMatch})` : sql.query `not exists(${sqlSelectWhereKeysMatch})`; }; for (const spec of forwardRelationSpecs) { const { constraint, foreignTable, keyAttributes } = spec; const fieldName = inflection.singleRelationByKeys(keyAttributes, foreignTable, table, constraint); const filterFieldName = inflection.filterSingleRelationFieldName(fieldName); const foreignTableTypeName = inflection.tableType(foreignTable); const foreignTableFilterTypeName = inflection.filterType(foreignTableTypeName); const ForeignTableFilterType = connectionFilterType(newWithHooks, foreignTableFilterTypeName, foreignTable, foreignTableTypeName); if (!ForeignTableFilterType) continue; addField(filterFieldName, `Filter by the object’s \`${fieldName}\` relation.`, ForeignTableFilterType, resolve, spec, `Adding connection filter forward relation field from ${describePgEntity(table)} to ${describePgEntity(foreignTable)}`); const keyIsNullable = !keyAttributes.every((attr) => attr.isNotNull); if (keyIsNullable) { const existsFieldName = inflection.filterForwardRelationExistsFieldName(fieldName); addField(existsFieldName, `A related \`${fieldName}\` exists.`, GraphQLBoolean, resolveExists, spec, `Adding connection filter forward relation exists field from ${describePgEntity(table)} to ${describePgEntity(foreignTable)}`); } } return fields; }); }; exports.default = PgConnectionArgFilterForwardRelationsPlugin; //# sourceMappingURL=PgConnectionArgFilterForwardRelationsPlugin.js.map