|
| 1 | +--- |
| 2 | +layout: post |
| 3 | +title: "Grafast beta: last epic solved!" |
| 4 | +date: 2025-06-07T12:00:00Z |
| 5 | +path: /news/20250607-last-epic-solved/ |
| 6 | +thumbnail: /images/news/grafast-wordmark-2023.svg |
| 7 | +thumbnailAlt: "The Grafast logo" |
| 8 | +tags: announcements, releases, grafast, postgraphile |
| 9 | +noToc: false |
| 10 | + |
| 11 | +summary: |
| 12 | + "Polymorphism overhauled for greater efficiency and ergonomics. This is the |
| 13 | + last breaking change to plan resolvers we are expecting for v1." |
| 14 | +--- |
| 15 | + |
| 16 | +_Announced 2025-06-07 by the Graphile Team_ |
| 17 | + |
| 18 | +<p class='intro'> |
| 19 | +In the first Gra<em>fast</em> Working Group, we outlined 4 <em>major</em> issues in Gra<em>fast</em> that needed to be addressed before we could think about general release. The fourth, and final, epic has now been solved! |
| 20 | +</p> |
| 21 | + |
| 22 | +- ✅ Global dependencies — solved via “unary” steps |
| 23 | +- ✅ Early exit — solved via “flags” |
| 24 | +- ✅ Eradicating eval |
| 25 | +- ✅ **Polymorphism — this release!** |
| 26 | + |
| 27 | +In previous versions of Gra*fast* there was the possibility of exponential plan |
| 28 | +branching due to the naive method of resolution of abstract types — a known |
| 29 | +issue raised in the first Gra*fast* working group as one of four “epics” to be |
| 30 | +solved before v1.0. This release of `grafast@0.1.1-beta.22` (used as the core |
| 31 | +execution engine in `postgraphile@5.0.0-beta.41`) fixes this final epic through |
| 32 | +a complete overhaul of the polymorphism system. Let’s take a look! |
| 33 | + |
| 34 | +### Polymorphism epic achieved |
| 35 | + |
| 36 | +By moving the responsibility of polymorphic resolution from field plan resolvers |
| 37 | +into the abstract types themselves, we’ve centralized this logic, simplified |
| 38 | +field plan resolvers, and unlocked more optimization opportunities and greater |
| 39 | +execution efficiency. We no longer have the concept of “polymorphic capable” |
| 40 | +steps: any step may now be used for polymorphism. Abstract types now gain a |
| 41 | +`planType` method responsible for taking a “specifier” from the field plan |
| 42 | +resolver and returning a step representing the name of its concrete object type |
| 43 | +along with subplans for each possible object types. |
| 44 | + |
| 45 | +To solve the problem of exponential branching, we merge the new “specifier” |
| 46 | +steps from all of the previous polymorphic branches into a single combined step |
| 47 | +before planning the next level of polymorphism. |
| 48 | + |
| 49 | +Users of PostGraphile’s Postgres-based polymorphism should not need to take any |
| 50 | +action to benefit from these changes, and may notice that their SQL queries are |
| 51 | +now slightly smaller. |
| 52 | + |
| 53 | +For the few of you who have been brave enough to hand write polymorphic plan |
| 54 | +resolvers: first of all, thank you for trying it out! Hand written polymorphic |
| 55 | +plan resolvers will need to be updated to match the new paradigm, this will |
| 56 | +involve moving the polymorphic resolution from field plan resolvers into the new |
| 57 | +`planType` method on the relevant abstract type (interface or union) and |
| 58 | +adjusting the logic to fit the new pattern. Steps such as `polymorphicBranch`, |
| 59 | +`pgPolymorphism`, and other polymorphism related steps no longer exist as they |
| 60 | +are no longer supported or needed in this new paradigm. For guidance on how to |
| 61 | +write the `planType` method, see |
| 62 | +[the Gra*fast* docs](https://grafast.org/grafast/polymorphism) and please reach |
| 63 | +out to us on Discord or GitHub issues — we’d love to help you get migrated. |
| 64 | + |
| 65 | +Excitingly this is the last change to hand written plan resolvers that we expect |
| 66 | +to make for v1.0 (other than some improvements around TypeScript types), so |
| 67 | +we're getting a lot closer to release candidate stage! |
| 68 | + |
| 69 | +### TypeDefs / plans overhaul |
| 70 | + |
| 71 | +In order to make the libraries more type safe, `makeGrafastSchema` (from |
| 72 | +`grafast`) and `makeExtendSchemaPlugin` (from `postgraphile/utils`) have |
| 73 | +deprecated the `typeDefs`/`plans` pattern since `plans` (like `resolvers` in the |
| 74 | +traditional format) ended up being a mish-mash of lots of different types |
| 75 | +(objects, scalars, enums, etc) and `__`-prefixed fields (`__resolveType`, |
| 76 | +`__isTypeOf`, etc) for methods on the type itself. |
| 77 | + |
| 78 | +Going forwards, the configuration should be split into `typeDefs` with |
| 79 | +`objects`, `interfaces`, `unions`, `inputObjects`, `scalars` and `enums` as |
| 80 | +appropriate. Type-level properties such as |
| 81 | +`resolveType`/`isTypeOf`/`planType`/`scope`/etc are no longer prefixed with `__` |
| 82 | +and, to avoid conflicts with these type-level properties, object and input |
| 83 | +object fields should be specified inside a new `plans` property and enum values |
| 84 | +within the new `values` property. |
| 85 | + |
| 86 | +**The old pattern will still work** (this is not a breaking change), but we |
| 87 | +recommend moving to the new shape and will use it for all of our examples in the |
| 88 | +documentation from now on. |
| 89 | + |
| 90 | +Migration is quite straightforward: |
| 91 | + |
| 92 | +1. **Add new top-level properties**. Add `objects`, `interfaces`, `unions`, |
| 93 | + `inputObjects`, `scalars`, and `enums` as top level properties alongside |
| 94 | + `typeDefs` and `plans`. Each should be an empty object. You can skip any |
| 95 | + where you’re not defining types of that kind. |
| 96 | + |
| 97 | +1. **Split definitions based on type kind**. For each type defined in `plans` |
| 98 | + move it into the appropriate new object (based on keyword defining the type; |
| 99 | + i.e. `type` → `objects`, `interface` → `interfaces`, `union` → |
| 100 | + `unions`, `input object` → `inputObjects`, `scalar` → `scalars`, |
| 101 | + `enum` → `enums`). |
| 102 | + |
| 103 | +1. **Move field plans into nested `plans: {...}` object**. For each type defined |
| 104 | + in the new `objects` and `inputObjects` objects: create a `plans: { ... }` |
| 105 | + entry inside the type and move all fields (anything not prefixed with `__`) |
| 106 | + inside this new (nested) property. |
| 107 | + |
| 108 | +1. **Move enum values into nested `values: {...}` object**. For each type |
| 109 | + defined in the new `enums` object: create a `values: { ... }` entry inside |
| 110 | + the type and move all values (anything not prefixed with `__`) inside this |
| 111 | + new (nested) property. |
| 112 | + |
| 113 | +1. **Remove `__` prefixes**. For each type across |
| 114 | + `objects`/`interfaces`/`unions`/`interfaceObjects`/`scalars` and `enums`: |
| 115 | + remove the `__` prefix from any methods/properties. |
| 116 | + |
| 117 | +Example: |
| 118 | + |
| 119 | +```diff |
| 120 | + typeDefs: ..., |
| 121 | +-plans: { |
| 122 | ++objects: { |
| 123 | + User: { |
| 124 | +- __isTypeOf(v) { |
| 125 | ++ isTypeOf(v) { |
| 126 | + return v.username != null; |
| 127 | + }, |
| 128 | + plans: { |
| 129 | + fieldName($source, fieldArgs) { |
| 130 | + // ... |
| 131 | + }, |
| 132 | ++ }, |
| 133 | + }, |
| 134 | ++}, |
| 135 | ++interfaces: {, |
| 136 | + MyInterface: { |
| 137 | +- __resolveType($specifier) { |
| 138 | ++ resolveType($specifier) { |
| 139 | + // ... |
| 140 | + } |
| 141 | + } |
| 142 | ++}, |
| 143 | ++enums: { |
| 144 | + MyEnum: { |
| 145 | + ONE |
| 146 | + TWO |
| 147 | + THREE |
| 148 | + } |
| 149 | + }, |
| 150 | +``` |
| 151 | + |
| 152 | +_(Aside: I pasted the |
| 153 | +[markdown version](https://github.com/graphile/graphile.github.io/blob/6693b91d5dd9980b676876524d0a14d370800dcf/src/news/2025-06-06-last-epic-solved.md#L78-L150) |
| 154 | +of these instructions into ChatGPT and it managed to convert a number of plugins |
| 155 | +perfectly! YMMV.)_ |
| 156 | + |
| 157 | +Other changes: |
| 158 | + |
| 159 | +- `ObjectPlans`/`GrafastPlans`/`FieldPlans`/`InputObjectPlans`/`ScalarPlans` all |
| 160 | + changed to singular |
| 161 | +- `InterfaceOrUnionPlans` split to `InterfacePlan`/`UnionPlan` (identical |
| 162 | + currently) |
| 163 | +- Shape of `ObjectPlan`/`InterfacePlan`/`UnionPlan` has changed; |
| 164 | + `DeprecatedObjectPlan`/etc exist for back-compatibility |
| 165 | +- `FieldArgs` can now accept an input shape indicating the args and their types |
| 166 | +- `FieldPlanResolver<TArgs, TParentStep, TResultStep>` has switched the order of |
| 167 | + the first two generic parameters: |
| 168 | + `FieldPlanResolver<TParentStep, TArgs, TResultStep>` — this is to reflect the |
| 169 | + order of the arguments to the function. Also null has been removed from the |
| 170 | + generics. |
| 171 | +- Various generics (including `GrafastFieldConfig`) that used to take a GraphQL |
| 172 | + type instance as a generic parameter no longer do — you need to use external |
| 173 | + code generation because TypeScript cannot handle the dynamic creation. |
| 174 | +- `GrafastFieldConfig` last two generics swapped order. |
| 175 | +- `GrafastArgumentConfig` generics completely changed |
| 176 | + |
| 177 | +### Gra*fast* features |
| 178 | + |
| 179 | +#### New steps |
| 180 | + |
| 181 | +- `coalesce()`: Accepts a number of steps and represents the first value from |
| 182 | + them that isn’t `null` or `undefined` |
| 183 | + |
| 184 | +#### Step classes |
| 185 | + |
| 186 | +- Experimental support for adding “references” to other steps at plan-time only |
| 187 | + (via `refId = this.addRef($step)` and reciprocal `$step = this.getRef(refId)` |
| 188 | + methods). Useful for optimization; but use with great caution. Currently |
| 189 | + undocumented due to experimental nature. |
| 190 | + |
| 191 | +#### Grafserv |
| 192 | + |
| 193 | +Add `@whatwg-node/server` HTTP adaptor, thanks to @kzlar 🎉 |
| 194 | + |
| 195 | +### Improved type-safety |
| 196 | + |
| 197 | +- `each()` now reflects the type of the list item even if it’s not a “list |
| 198 | + capable” step |
| 199 | +- `loadOne()`/`loadMany()` can now track the underlying nullability of the |
| 200 | + callback |
| 201 | + |
| 202 | +🚨 This will potentially break your plan types quite a bit. In particular, the |
| 203 | +`LoadOneCallback` and `LoadManyCallback` types now have 5 (not 4) generic |
| 204 | +parameters, the new one is inserted in the middle (after the second parameter) |
| 205 | +and indicates the true return type of the callback (ignoring promises) — e.g. |
| 206 | +`Maybe<ReadonlyArray<Maybe<ItemType>>>` for `LoadManyCallback`. They have |
| 207 | +sensible defaults if you only specify the first two generics. |
| 208 | + |
| 209 | +### Other breaking changes |
| 210 | + |
| 211 | +- Minimum version of Node.js bumped to Node 22 (the latest LTS) in preparation |
| 212 | + for release candidate |
| 213 | +- Grafserv: Naked GraphQL errors (such as those you’d see from coercion) are now |
| 214 | + treated as safe to output. |
| 215 | +- graphile-export now supports and possibly requires ESLint v9. |
| 216 | +- Gra*fast*: plan JSON now has layer plans as a list rather than a tree, to |
| 217 | + account for combination layer plans that have many parents. May not be |
| 218 | + compatible with previous versions of Ruru. |
| 219 | + |
| 220 | +### Bug fixes |
| 221 | + |
| 222 | +- Gra*fast*: Fix bug in deduplication that only compared flags on first |
| 223 | + dependency. |
| 224 | +- Gra*fast*: Fix a number of edge-case issues relating to incremental delivery |
| 225 | +- PostGraphile: Fix bug in nullable nodeID handling for computed column |
| 226 | + arguments with the Relay preset that was causing the entire PgSelectStep to be |
| 227 | + inhibited on null/undefined. |
| 228 | +- PostGraphile: Fix bug with `@ref ... plural` smart tag where multiple |
| 229 | + `@refVia` are present but the target type is not abstract. |
| 230 | + |
| 231 | +### And here’s the rest... |
| 232 | + |
| 233 | +- Gra*fast*: Implement deduplication of `loadOne()` / `loadMany()` and |
| 234 | + `jsonParse()` steps |
| 235 | +- Gra*fast*: Planning field inputs now uses a cache so planning time should |
| 236 | + reduce marginally and step ids will be less inflated. |
| 237 | +- Gra*fast:* Don’t call `applyPlan` on arguments if the value is not specified |
| 238 | + (not even a variable) and there’s no default value. |
| 239 | +- Gra*fast*: Fixes bug where `undefined` values might not be flagged with |
| 240 | + `FLAG_NULL` |
| 241 | +- PostGraphile: General plan efficiency improvements |
| 242 | +- Ruru: Upgrade to Mermaid 11, and reduce verbosity of polymorphism in plan |
| 243 | + diagrams. |
| 244 | +- Ruru: Removes a lot of cruft from plan diagrams by hiding certain over-used |
| 245 | + global dependencies. |
| 246 | +- Grafserv: Add `ctx.ws?.normalizedConnectionParams` which can be treated as |
| 247 | + headers (i.e. has lower-cased keys). |
| 248 | +- @dataplan/pg: `pgSelectFromRecord` no longer requires a mutable array |
| 249 | +- Graphile Export: graphile-export now uses ES2022 syntax for ESLint check on |
| 250 | + exported schema, fixing compatibility with `@graphile/pg-aggregates`. |
| 251 | +- Graphile Export: Fix detection of SQL when `pg-sql2` isn’t available at |
| 252 | + runtime, and improve misleading error around class instances |
| 253 | +- pg-sql2: `util.inspect(someSql)` will now output a much nicer string |
| 254 | + |
| 255 | +### Thank you Sponsors |
| 256 | + |
| 257 | +Gra*fast* and PostGraphile are crowd-funded open-source software, they rely on |
| 258 | +crowd-sourced funding from individuals and companies to keep advancing. |
| 259 | + |
| 260 | +If your company benefits from Gra*fast*, PostGraphile or the wider Graphile |
| 261 | +suite, you should consider asking them to fund our work. By significantly |
| 262 | +reducing the amount of work needed to achieve business goals and reducing |
| 263 | +running costs, Graphile’s software results in huge time and money savings for |
| 264 | +users. We encourage companies to contribute a portion of these savings back, |
| 265 | +enabling the projects to advance more rapidly, and result in even greater |
| 266 | +savings for your company. |
| 267 | +[Find out more about sponsorship here on our website](/sponsor/). |
| 268 | + |
| 269 | +<div class="flex flex-wrap justify-around"> |
| 270 | +<img alt="Cartoon Benjie and Jem send cartoon hearts up into the sky" src="/images/news/postgraphile-thankyou.svg" style="max-height: 300px" /> |
| 271 | +</div> |
0 commit comments