Why it is shaped this way
Lookups satisfy root fields from cached entities
Status: superseded, 2026-10-03, by A lookup binds in the availability check, never in a read; accepted, 2026-10-02. Serves The store is the UI's state and Relay's words. Reopen when a schema needs an entity resolved by more than one argument, or by a field below the root; the remedy is to widen the configuration, not to add a runtime hook.
Status: superseded, 2026-10-03, by A lookup binds in the availability check, never in a read; accepted, 2026-10-02. Serves The store is the UI's state and Relay's words. Reopen when a schema needs an entity resolved by more than one argument, or by a field below the root; the remedy is to widen the configuration, not to add a runtime hook.
Context#
A detail screen asks for character(id: $id). The list that led there fetched
the same character under characters(page: 1).results[3], so the entity
Character:4 is in the store, but the root link character(id:"4") was never
written. Relay solves this with a missing-field handler for node(id:),
registered in code at run time, and relies on the Node interface. The sample
API has no Node, and the store takes no runtime policy objects.
Decision#
baton.json names root fields that return an entity by one of their
arguments:
"lookups": [{ "field": "Query.character", "type": "Character", "argument": "id" }]The compiler bakes the lookup into the operation's plan. When the availability
check or a lens read finds the root link missing, it looks for Type:value
with the argument's value, uses the entity if present, and writes the link so
later reads are direct. Nothing is configured at run time.
Evidence#
- The spine's first-body test (2026-10-02): the detail handle is
.readyon creation with the lookup and.loadingwithout it; the lens read ofcharacter(id: "3")returns the cached "Summer Smith" and leaves the link written. - Relay's compiler already treats
Nodeas feature-scoped (refetchable fragments also acceptQuery,Viewerand@fetchabletypes); a configured lookup is the same idea with the schema's own root fields.
Not chosen#
- Requiring
Nodeandnode(id:): the sample API and many real schemas lack them. - A runtime missing-field handler registered on the environment: a policy object, decided late, invisible to the compiler.
- Keying root fields by their arguments automatically: guesses wrong whenever an argument is a filter rather than a key.
Baton