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
189 lines (188 loc) • 7.52 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = void 0;
var _debugSql = _interopRequireDefault(require("./debugSql"));
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
var PgRowNode = async function PgRowNode(builder, {
subscriptions
}) {
builder.hook("GraphQLObjectType", (object, build, context) => {
const {
addNodeFetcherForTypeName,
pgSql: sql,
gql2pg,
pgQueryFromResolveData: queryFromResolveData,
pgOmit: omit,
pgPrepareAndRun
} = build;
const {
scope: {
isPgRowType,
pgIntrospection: table
}
} = context;
if (!addNodeFetcherForTypeName) {
// Node plugin must be disabled.
return object;
}
if (!isPgRowType || !table.namespace || omit(table, "read")) {
return object;
}
const sqlFullTableName = sql.identifier(table.namespace.name, table.name);
const primaryKeyConstraint = table.primaryKeyConstraint;
if (!primaryKeyConstraint) {
return object;
}
const primaryKeys = primaryKeyConstraint && primaryKeyConstraint.keyAttributes;
addNodeFetcherForTypeName(object.name, async (data, identifiers, resolveContext, parsedResolveInfoFragment, ReturnType, resolveData, resolveInfo) => {
const {
pgClient
} = resolveContext;
const liveRecord = resolveInfo && resolveInfo.rootValue && resolveInfo.rootValue.liveRecord;
if (identifiers.length !== primaryKeys.length) {
throw new Error("Invalid ID");
}
const query = queryFromResolveData(sqlFullTableName, undefined, resolveData, {
useAsterisk: false // Because it's only a single relation, no need
}, queryBuilder => {
if (subscriptions && table.primaryKeyConstraint) {
queryBuilder.selectIdentifiers(table);
}
primaryKeys.forEach((key, idx) => {
queryBuilder.where(sql.fragment`${queryBuilder.getTableAlias()}.${sql.identifier(key.name)} = ${gql2pg(identifiers[idx], primaryKeys[idx].type, primaryKeys[idx].typeModifier)}`);
});
}, resolveContext, resolveInfo && resolveInfo.rootValue);
const {
text,
values
} = sql.compile(query);
if (_debugSql.default.enabled) (0, _debugSql.default)(text);
const {
rows: [row]
} = await pgPrepareAndRun(pgClient, text, values);
if (subscriptions && liveRecord && row) {
liveRecord("pg", table, row.__identifiers);
}
return row;
});
return object;
}, ["PgRowNode"]);
builder.hook("GraphQLObjectType:fields", (fields, build, context) => {
const {
nodeIdFieldName,
getTypeAndIdentifiersFromNodeId,
extend,
parseResolveInfo,
pgGetGqlTypeByTypeIdAndModifier,
pgIntrospectionResultsByKind: introspectionResultsByKind,
pgSql: sql,
gql2pg,
graphql: {
GraphQLNonNull,
GraphQLID
},
inflection,
pgQueryFromResolveData: queryFromResolveData,
pgOmit: omit,
describePgEntity,
sqlCommentByAddingTags,
pgPrepareAndRun
} = build;
const {
scope: {
isRootQuery
},
fieldWithHooks
} = context;
if (!isRootQuery || !nodeIdFieldName) {
return fields;
}
return extend(fields, introspectionResultsByKind.class.reduce((memo, table) => {
// PERFORMANCE: These used to be .filter(...) calls
if (!table.namespace) return memo;
if (omit(table, "read")) return memo;
const TableType = pgGetGqlTypeByTypeIdAndModifier(table.type.id, null);
const sqlFullTableName = sql.identifier(table.namespace.name, table.name);
if (TableType) {
const primaryKeyConstraint = table.primaryKeyConstraint;
if (!primaryKeyConstraint) {
return memo;
}
const primaryKeys = primaryKeyConstraint && primaryKeyConstraint.keyAttributes;
const fieldName = inflection.tableNode(table);
memo = extend(memo, {
[fieldName]: fieldWithHooks(fieldName, ({
getDataFromParsedResolveInfoFragment
}) => {
return {
description: build.wrapDescription(`Reads a single \`${TableType.name}\` using its globally unique \`ID\`.`, "field"),
type: TableType,
args: {
[nodeIdFieldName]: {
description: build.wrapDescription(`The globally unique \`ID\` to be used in selecting a single \`${TableType.name}\`.`, "arg"),
type: new GraphQLNonNull(GraphQLID)
}
},
async resolve(parent, args, resolveContext, resolveInfo) {
const {
pgClient
} = resolveContext;
const liveRecord = resolveInfo.rootValue && resolveInfo.rootValue.liveRecord;
const nodeId = args[nodeIdFieldName];
try {
const {
Type,
identifiers
} = getTypeAndIdentifiersFromNodeId(nodeId);
if (Type !== TableType) {
throw new Error("Mismatched type");
}
if (identifiers.length !== primaryKeys.length) {
throw new Error("Invalid ID");
}
const parsedResolveInfoFragment = parseResolveInfo(resolveInfo);
parsedResolveInfoFragment.args = args; // Allow overriding via makeWrapResolversPlugin
const resolveData = getDataFromParsedResolveInfoFragment(parsedResolveInfoFragment, TableType);
const query = queryFromResolveData(sqlFullTableName, undefined, resolveData, {
useAsterisk: false // Because it's only a single relation, no need
}, queryBuilder => {
if (subscriptions && table.primaryKeyConstraint) {
queryBuilder.selectIdentifiers(table);
}
primaryKeys.forEach((key, idx) => {
queryBuilder.where(sql.fragment`${queryBuilder.getTableAlias()}.${sql.identifier(key.name)} = ${gql2pg(identifiers[idx], primaryKeys[idx].type, primaryKeys[idx].typeModifier)}`);
});
}, resolveContext, resolveInfo.rootValue);
const {
text,
values
} = sql.compile(query);
if (_debugSql.default.enabled) (0, _debugSql.default)(text);
const {
rows: [row]
} = await pgPrepareAndRun(pgClient, text, values);
if (liveRecord && row) {
liveRecord("pg", table, row.__identifiers);
}
return row;
} catch (e) {
return null;
}
}
};
}, {
isPgNodeQuery: true,
pgFieldIntrospection: table
})
}, `Adding row by globally unique identifier field for ${describePgEntity(table)}. You can rename this table via a 'Smart Comment':\n\n ${sqlCommentByAddingTags(table, {
name: "newNameHere"
})}`);
}
return memo;
}, {}), `Adding "row by node ID" fields to root Query type`);
}, ["PgRowNode"]);
};
exports.default = PgRowNode;
//# sourceMappingURL=PgRowNode.js.map