Repository navigation
0.5.0 Baton Pass
Honest data on the wire: field errors stored beside their fields, Relay's
error directives in Swift's terms, @defer over the incremental formats, and
subscriptions.
- Field errors. The ingest reads a response's
errors, resolves eachpath
through the plan to the record and slot it names, and the commit stores the
error beside the field; a payload that answers the field clears it, and
either change notifies the field. A plain accessor reads an errored field
as null, as before;@catchreads the error; a cached read sees what the
network read saw. A response withdata: nulland errors fails the fetch
withGraphQLErrors; errors whose path leads nowhere in the plan are dropped. @required(action:). A required field reads non-optional. NONE and LOG
bubble at the lens boundary, as Relay nulls the enclosing object: the
accessor that produces a lens (a linked field, a spread, a list element, a
connection node) produces nil when a required field in it is null, through
a generatedsatisfied; LOG also reports the path through
Environment.requiredFieldMissing. THROW makes the field's own accessor
get throws, raisingRequiredFieldError. A root whose required fields
bubble fails the operation, since there is no null data.@catch(to:). RESULT makes the accessor aResult<T, FieldErrors>whose
failure holds the field's error and every error below it, THROW-required
nulls included; NULL keeps the optional type and reads errors as null.@throwOnFieldError. On a fragment, the spread accessor isget throws
and throwsFieldErrorsfor an uncaught error inside; on an operation, an
uncaught field error puts the handle in.failed(FieldErrors)with the
data in the store regardless. Under either, and inside@catch,
@semanticNonNullfields read non-optional, as Relay types them.Environment.errorBehaviorsends theonErrorrequest parameter
(PROPAGATE,NULL,ABORT) when set.@defer. An operation with a deferred spread asks formultipart/mixed
and reads the parts as they arrive: the first commits and renders, each
later part is normalized at the record its path names with the fields its
label marks, and the availability check does not wait for deferred fields.
Three shapes are read: the June 2023incremental[{data, path, label}],
the 2024pending/incremental[{id, data}]/completed, and Relay's
{data, path, label}per part. A deferred spread's accessor is nil until
the fragment's fields are present. The schema has to declare@defer; a
server without it gets Relay's "Unknown directive", which is the truth.- Subscriptions.
@Subscription("…") var live: NoteAddedSubscriptionexpands
like@Query: the storage subscribes while the view lives and closes the
stream when it goes; the handle exposesevents,latest,errorand
isActive. Every event is normalized atclient:root:subscriptionand
committed, so its entities merge and edge directives on a subscription
payload work.GraphQLTransportWebSocketspeaksgraphql-transport-wsover
URLSessionWebSocketTask;SubscriptionTransportis the protocol behind
it, passed asEnvironment(transport:subscriptions:). Transport.stream(_:), with a default that answers once;
MultipartParsersplitsmultipart/mixedbodies however the bytes arrive.- The GitHub sample:
@catchon the repository lookup shows the server's
reason for a missing repository; issue rows require an author with
@required(action: LOG), so an issue without one is no row.
Tests: field errors land beside the field and a plain read, a @catch read, a
@catch(to: NULL) read and a cached read each see what they should; a
payload that answers an errored field clears the error and notifies; NONE
drops the list element, LOG reports the path, THROW throws at the read;
@throwOnFieldError throws at the spread and fails the operation while a
caught error does not; a semantic field reads non-optional; a response with
errors and no data fails with the messages; onError is sent; a deferred
fragment is absent after the first part and present after the second in both
incremental formats; a subscription's events commit and append through
@appendEdge and the stream closes on release; the multipart parser splits
parts at any chunking. The bench measures a payload with twenty field errors
and the cost of a @catch read and a satisfied check.
Deliberately not added: @stream; operation-level @catch (accepted, no
effect; @throwOnFieldError is the operation's policy); reconnection and
retry for the WebSocket transport beyond a clean error; extensions on
field errors; a sample screen for @defer or subscriptions, because neither
public API supports them (both ship on fixtures); the missing-data heal (a
refetch of the owning operation), still planned.
Numbers for the error bench are in BENCHMARKS.md.