postgraphile-plugin-connection-filter
Version:
Filtering on PostGraphile connections
128 lines • 7.55 kB
JavaScript
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
;