UNPKG

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

193 lines (185 loc) 5.75 kB
// @flow import isString from "lodash/isString"; import type { Plugin } from "graphile-build"; export default (function PgConnectionArgOrderBy(builder, { orderByNullsLast }) { builder.hook( "init", (_, build) => { const { newWithHooks, pgIntrospectionResultsByKind: introspectionResultsByKind, graphql: { GraphQLEnumType }, inflection, pgOmit: omit, sqlCommentByAddingTags, describePgEntity, } = build; introspectionResultsByKind.class.forEach(table => { // PERFORMANCE: These used to be .filter(...) calls if (!table.isSelectable || omit(table, "order")) return; if (!table.namespace) return; const tableTypeName = inflection.tableType(table); /* const TableOrderByType = */ newWithHooks( GraphQLEnumType, { name: inflection.orderByType(tableTypeName), description: build.wrapDescription( `Methods to use when ordering \`${tableTypeName}\`.`, "type" ), values: { [inflection.builtin("NATURAL")]: { value: { alias: null, specs: [], }, }, }, }, { __origin: `Adding connection "orderBy" argument for ${describePgEntity( table )}. You can rename the table's GraphQL type via a 'Smart Comment':\n\n ${sqlCommentByAddingTags( table, { name: "newNameHere", } )}`, pgIntrospection: table, isPgRowSortEnum: true, } ); }); return _; }, ["PgConnectionArgOrderBy"] ); builder.hook( "GraphQLObjectType:fields:field:args", (args, build, context) => { const { extend, getTypeByName, pgGetGqlTypeByTypeIdAndModifier, pgSql: sql, graphql: { GraphQLList, GraphQLNonNull }, inflection, pgOmit: omit, } = build; const { scope: { fieldName, isPgFieldConnection, isPgFieldSimpleCollection, pgFieldIntrospection, pgFieldIntrospectionTable, }, addArgDataGenerator, Self, } = context; if (!isPgFieldConnection && !isPgFieldSimpleCollection) { return args; } const proc = pgFieldIntrospection.kind === "procedure" ? pgFieldIntrospection : null; const table = pgFieldIntrospection.kind === "class" ? pgFieldIntrospection : proc ? pgFieldIntrospectionTable : null; if ( !table || !table.namespace || !table.isSelectable || omit(table, "order") ) { return args; } if (proc) { if (!proc.tags.sortable) { return args; } } const TableType = pgGetGqlTypeByTypeIdAndModifier(table.type.id, null); const tableTypeName = TableType.name; const TableOrderByType = getTypeByName( inflection.orderByType(tableTypeName) ); const cursorPrefixFromOrderBy = orderBy => { if (orderBy) { const cursorPrefixes = []; for ( let itemIndex = 0, itemCount = orderBy.length; itemIndex < itemCount; itemIndex++ ) { const item = orderBy[itemIndex]; if (item.alias) { cursorPrefixes.push(sql.literal(item.alias)); } } if (cursorPrefixes.length > 0) { return cursorPrefixes; } } return null; }; addArgDataGenerator(function connectionOrderBy({ orderBy: rawOrderBy }) { const orderBy = rawOrderBy ? Array.isArray(rawOrderBy) ? rawOrderBy : [rawOrderBy] : null; return { pgCursorPrefix: cursorPrefixFromOrderBy(orderBy), pgQuery: queryBuilder => { if (orderBy != null) { orderBy.forEach(item => { const { specs, unique } = item; const orders = Array.isArray(specs[0]) || specs.length === 0 ? specs : [specs]; orders.forEach(([col, ascending, specNullsFirst]) => { const expr = isString(col) ? sql.fragment`${queryBuilder.getTableAlias()}.${sql.identifier( col )}` : col; // If the enum specifies null ordering, use that // Otherwise, use the orderByNullsLast option if present const nullsFirst = specNullsFirst != null ? specNullsFirst : orderByNullsLast != null ? !orderByNullsLast : undefined; queryBuilder.orderBy(expr, ascending, nullsFirst); }); if (unique) { queryBuilder.setOrderIsUnique(); } }); } }, }; }); return extend( args, { orderBy: { description: build.wrapDescription( `The method to use when ordering \`${tableTypeName}\`.`, "arg" ), type: new GraphQLList(new GraphQLNonNull(TableOrderByType)), }, }, `Adding 'orderBy' argument to field '${fieldName}' of '${Self.name}'` ); }, ["PgConnectionArgOrderBy"] ); }: Plugin);