graphile-build-pg
Version:
Build a GraphQL schema by reflection over a PostgreSQL schema. Easy to customize since it's built with plugins on graphile-build
144 lines (142 loc) • 6.33 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = void 0;
var _debug = _interopRequireDefault(require("debug"));
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
const debug = (0, _debug.default)("graphile-build-pg");
var PgForwardRelationPlugin = function PgForwardRelationPlugin(builder, {
subscriptions
}) {
builder.hook("GraphQLObjectType:fields", (fields, build, context) => {
const {
extend,
getSafeAliasFromResolveInfo,
getSafeAliasFromAlias,
pgGetGqlTypeByTypeIdAndModifier,
pgIntrospectionResultsByKind: introspectionResultsByKind,
pgSql: sql,
inflection,
pgQueryFromResolveData: queryFromResolveData,
pgOmit: omit,
sqlCommentByAddingTags,
describePgEntity
} = build;
const {
scope: {
isPgRowType,
isPgCompositeType,
isMutationPayload,
pgIntrospection,
pgIntrospectionTable
},
fieldWithHooks,
Self
} = context;
const table = pgIntrospectionTable || pgIntrospection;
if (!(isPgRowType || isMutationPayload || isPgCompositeType) || !table || table.kind !== "class" || !table.namespace) {
return fields;
}
if (isMutationPayload && pgIntrospection.kind === "procedure" && (pgIntrospection.returnTypeId !== table.typeId || pgIntrospection.returnsSet)) {
return fields;
}
// This is a relation in which we (table) are local, and there's a foreign table
const foreignKeyConstraints = table.constraints.filter(con => con.type === "f");
return extend(fields, foreignKeyConstraints.reduce((memo, constraint) => {
if (omit(constraint, "read")) {
return memo;
}
const gqlTableType = pgGetGqlTypeByTypeIdAndModifier(table.type.id, null);
const tableTypeName = gqlTableType.name;
if (!gqlTableType) {
debug(`Could not determine type for table with id ${constraint.classId}`);
return memo;
}
const foreignTable = introspectionResultsByKind.classById[constraint.foreignClassId];
const gqlForeignTableType = pgGetGqlTypeByTypeIdAndModifier(foreignTable.type.id, null);
const foreignTableTypeName = gqlForeignTableType.name;
if (!gqlForeignTableType) {
debug(`Could not determine type for foreign table with id ${constraint.foreignClassId}`);
return memo;
}
if (!foreignTable) {
throw new Error(`Could not find the foreign table (constraint: ${constraint.name})`);
}
if (omit(foreignTable, "read")) {
return memo;
}
const foreignSchema = foreignTable.namespace;
const keys = constraint.keyAttributes;
const foreignKeys = constraint.foreignKeyAttributes;
if (!keys.every(_ => _) || !foreignKeys.every(_ => _)) {
throw new Error("Could not find key columns!");
}
if (keys.some(key => omit(key, "read"))) {
return memo;
}
if (foreignKeys.some(key => omit(key, "read"))) {
return memo;
}
const fieldName = inflection.singleRelationByKeys(keys, foreignTable, table, constraint);
memo = extend(memo, {
[fieldName]: fieldWithHooks(fieldName, ({
getDataFromParsedResolveInfoFragment,
addDataGenerator
}) => {
addDataGenerator(parsedResolveInfoFragment => {
return {
pgQuery: queryBuilder => {
queryBuilder.select(() => {
const resolveData = getDataFromParsedResolveInfoFragment(parsedResolveInfoFragment, gqlForeignTableType);
const foreignTableAlias = sql.identifier(Symbol());
const query = queryFromResolveData(sql.identifier(foreignSchema.name, foreignTable.name), foreignTableAlias, resolveData, {
useAsterisk: false,
// Because it's only a single relation, no need
asJson: true
}, innerQueryBuilder => {
innerQueryBuilder.parentQueryBuilder = queryBuilder;
if (subscriptions && table.primaryKeyConstraint) {
queryBuilder.selectIdentifiers(table);
}
if (subscriptions && foreignTable.primaryKeyConstraint) {
innerQueryBuilder.selectIdentifiers(foreignTable);
}
keys.forEach((key, i) => {
innerQueryBuilder.where(sql.fragment`${queryBuilder.getTableAlias()}.${sql.identifier(key.name)} = ${foreignTableAlias}.${sql.identifier(foreignKeys[i].name)}`);
});
}, queryBuilder.context, queryBuilder.rootValue);
return sql.fragment`(${query})`;
}, getSafeAliasFromAlias(parsedResolveInfoFragment.alias));
}
};
});
return {
description: constraint.tags.forwardDescription || build.wrapDescription(`Reads a single \`${foreignTableTypeName}\` that is related to this \`${tableTypeName}\`.`, "field"),
type: gqlForeignTableType,
// Nullable since RLS may forbid fetching
resolve: (rawData, _args, resolveContext, resolveInfo) => {
const data = isMutationPayload ? rawData.data : rawData;
if (!data) return null;
const safeAlias = getSafeAliasFromResolveInfo(resolveInfo);
const record = data[safeAlias];
const liveRecord = resolveInfo.rootValue && resolveInfo.rootValue.liveRecord;
if (record && liveRecord) {
liveRecord("pg", foreignTable, record.__identifiers);
}
return record;
}
};
}, {
pgFieldIntrospection: constraint,
isPgForwardRelationField: true
})
}, `Forward relation for ${describePgEntity(constraint)}. To rename this relation with a 'Smart Comment':\n\n ${sqlCommentByAddingTags(constraint, {
fieldName: "newNameHere"
})}`);
return memo;
}, {}), `Adding forward relations to '${Self.name}'`);
}, ["PgForwardRelation"]);
};
exports.default = PgForwardRelationPlugin;
//# sourceMappingURL=PgForwardRelationPlugin.js.map