crystal icon indicating copy to clipboard operation
crystal copied to clipboard

v5: Safe way to rename / remove / relocate fields

Open zxpectre opened this issue 1 month ago • 3 comments

v5: Safe way to rename / remove / relocate fields (e.g. move totalCount into aggregate.count for Hasura-style aggregates)

Hi 👋. I’m using PostGraphile v5 with @graphile/pg-aggregates and Grafast, and I’m trying to build a plugin that reshapes the entire schema for connection types to look more like Hasura’s aggregate API.

Concretely, for any GraphQL object type that:

  • is backed by a ConnectionStep (i.e. Self.extensions.grafast.assertStep.$$export.exportName === "ConnectionStep"), and
  • exposes one of the aggregate fields added by @graphile/pg-aggregates (e.g. aggregates, aggregate, groupedAggregates),

I’d like to be able to:

  1. Rename fields (e.g. rename a connection root field or aggregates → aggregate).
  2. Remove fields at the connection level (e.g. hide totalCount / count from the top level).
  3. Clone / relocate fields while preserving their Grafast plan (e.g. move totalCount so that it is also available as aggregate.count inside the aggregate container).

The end goal is a Hasura-like shape such as:

foo_aggregate {
  aggregate {
    count
    sum { ... }
    avg { ... }
  }
  groupedAggregate { ... }
}

…where aggregate.count behaves exactly like the existing totalCount/count on the connection (same semantics, same plan), and ideally we can optionally drop the top-level totalCount field.

What I tried

I experimented with:

  • schema.hooks.GraphQLObjectType_fields (v5 hook API), and
  • processSchema(...) post-build transformations, and
  • copying the GraphQLField / GraphQLFieldConfig and/or extensions.grafast from the connection’s totalCount field into the aggregate container type.

This “works” at the type level (the new aggregate.count field appears in the schema), but at runtime it tends to break the aggregate object resolution – aggregate becomes null – because the copied plan was written assuming the parent step is the connection step, not the aggregate step.

In other words, naïvely cloning the field or its plan isn’t safe without deeper knowledge of the internal Step graph.

Request

Would it be possible to have either:

  • A documented pattern / example plugin that shows how to:

    • rename / hide fields on ConnectionStep-backed types, and
    • expose a count field inside the aggregate container that reuses the existing connection-level totalCount logic safely, or
  • A first-class option in @graphile/pg-aggregates / core that exposes a Hasura-style aggregate.count (optionally also allowing the top-level totalCount to be removed), or

  • A small API hook that lets a plugin get from the aggregate Step back to the underlying connection Step (so a custom aggregate.count plan can delegate to the same logic as totalCount without relying on internal, undocumented assumptions)?

I’m specifically trying to avoid:

  • casting everything to any and poking at private properties, and
  • brittle hacks that rely on internal Step structures that may change.

A supported way to do “rename / remove / relocate field while preserving its Grafast plan” for connection/aggregate types would make Hasura-style compatibility much easier to maintain.

Thanks a lot for any guidance or API hooks you can provide 🙏

zxpectre avatar Nov 28 '25 15:11 zxpectre

Quick message because it's Saturday, and preface: I have never once used Hasura so don't know it's layout or anything. Sounds like you're pretty close!

One option is instead of just taking the plan from the aggregates field, what you might need is the plan from the connection field and the aggregates field; something like:

const connectionPlan = connectionField.extensions.grafast.plan
const aggregatesPlan = aggregatesField.extensions.grafast.plan
const newPlan = ($parent, fieldArgs) => {
  const $connection = connectionPlan($parent, fieldArgs);
  return aggregatesPlan($connection);
}

But it sounds like foo_aggregate is a weak form of a connection field with the nodes/edges/pageInfo removed... So just have foo_aggregate use the connection plan - it doesn't much matter that you're not using those other features. In fact, you could:

  1. rename the connection field to foo_aggregate using inflection
  2. rename the return type from FooConnection to FooAggregates (or whatever Hasura uses) using inflection
  3. delete the fields you don't need from the connection type, like nodes, edges and payloadInfo
  4. rename totalCount to count (hopefully using inflection; not 100% sure if that one goes through an inflector or not)

benjie avatar Nov 29 '25 09:11 benjie

You could also just write the plans yourself, they're not that complex. Have a look at the exported schema around specifically these fields.

benjie avatar Nov 29 '25 09:11 benjie

Hey @zxpectre, how's it going? Can I offer any further guidance?

benjie avatar Dec 05 '25 16:12 benjie