Skip to content

Commit 1f5bf0d

Browse files
jemgillambenjie
andauthored
news: Polymorphism Epic reached! (#438)
Co-authored-by: Benjie Gillam <benjie@jemjie.com>
1 parent d25d9ae commit 1f5bf0d

File tree

3 files changed

+283
-10
lines changed

3 files changed

+283
-10
lines changed

src/data/nav.json

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,11 @@
1616
}
1717
],
1818
"pages": [
19+
{
20+
"to": "/news/20250607-last-epic-solved/",
21+
"title": "Grafast beta: last Epic reached!",
22+
"sectionId": "main"
23+
},
1924
{
2025
"to": "/news/20250324-major-grafast-beta/",
2126
"title": "Major Grafast release: beta.21",
Lines changed: 271 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,271 @@
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` &rarr; `objects`, `interface` &rarr; `interfaces`, `union` &rarr;
100+
`unions`, `input object` &rarr; `inputObjects`, `scalar` &rarr; `scalars`,
101+
`enum` &rarr; `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>

src/templates/page.js

Lines changed: 7 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -140,19 +140,16 @@ class Page extends React.Component {
140140
}
141141
render() {
142142
const {
143-
data: {
144-
remark: {
145-
html: rawHTML,
146-
tableOfContents: rawTableOfContents,
147-
timeToRead,
148-
frontmatter: { title, fullTitle, showExamples, noToc },
149-
},
150-
nav,
151-
examples,
152-
},
143+
data: { remark, nav = { edges: [] }, examples } = {},
153144
location,
154145
history,
155146
} = this.props;
147+
const {
148+
html: rawHTML = "",
149+
tableOfContents: rawTableOfContents = "",
150+
timeToRead = "",
151+
frontmatter: { title = "", fullTitle = "", showExamples, noToc } = {},
152+
} = remark ?? {};
156153
const tableOfContents =
157154
!noToc && timeToRead > 1
158155
? processTableOfContents(rawTableOfContents)

0 commit comments

Comments
 (0)