Skip to content

0.8.0 Back Straight

Choose a tag to compare

@github-actions github-actions released this 07 Oct 04:28

The spine: the architecture the decision records describe, built. A
handle's fetch and a subscription's stream are values beside its phase,
and a failure says its kind; every write is a batch through one door; the
store owns the roots that keep records alive, stamps their ages at the
commit and collects from them alone; an environment ends; and the keys a
session renders are its store's. Around it, the shapes a production schema
has read as Swift types (enums, input objects, mapped scalars, configured
identity, client fields), the transport has one verb, the image evicts by
launch and keeps the rows a partial response did not name, and the
compiler validates, prints and reports what it compiled.

  • The runtime reads a plural link out as values: values, requiredValues,
    caughtValues and caughtRequiredValues on an anchor build one value per
    linked record at the read, for the @inline fragment the compiler is
    learning to emit. FieldErrors and every MappedScalar are Hashable,
    so a value holding a caught field or a mapped scalar can be. A mapped
    scalar type of the app's own that was not Hashable must become it.
  • A response with a few of a record's fields no longer empties the
    record's row in the image of the rest. The writer replaced every row with
    the commit's snapshot of the record, which for a record memory had not
    read from the image held only what this launch's responses wrote: a
    header fetched before a screen left the screen's next check a miss. The
    snapshot of a record memory has not read is now merged into its row, the
    response's cells over the row's; a record the check has read replaces
    its row as before, and so does a deleted one. Recorded in
    docs/decisions/the-image-is-sqlite.md.
  • An image's close() is final. A commit or a read that reached a closed
    image opened its file again and took it back from the next environment's
    image, which then ran without one or, in a debug build, tripped the
    assertion that one image holds a file. A closed image drops what is
    queued after and misses every read; removeAll() still deletes the
    file. Recorded in docs/decisions/an-image-belongs-to-one-store.md.
  • An operation's text is printed compact, with Relay's printer's own
    option: no newline, indentation or optional space, a comma between
    items, strings as they are. The test target's 115 operations hold
    61,797 bytes of text where they held 83,885, and the deepest realistic
    one 47% of what it held; the 65 KB operation of issue 36 that a server
    refused goes out at about half. Every persisted id changes with the
    text, so a team with registered ids regenerates the file and registers
    again; batonc print and spec/documents show the compact text.
    Recorded in docs/decisions/operation-text-is-printed-compact.md.
  • A connection on the query root comes back from the image. The
    availability check hydrates the root a waited field at a time, and the
    connection's client link, Relay's handle key on the root, was never among
    them, so a reopened store read the connection as ready and empty while the
    same connection under a record came back whole; the check now reads the
    root's cell for the link before walking the connection. Issue 35.
  • An operation's plan declares each distinct selection once, as a static
    member with its type stated, where it was one nested expression that
    copied a fragment at every spread. A union inside a union no longer
    exhausts the Swift compiler: the case of issue 34 that was killed at
    12 GB compiles in 1.7 s and 0.24 GB, and its plan holds 82 selections
    where it held 2,258. Recorded in
    docs/decisions/a-plan-declares-each-selection-once.md.
  • An object under an interface or union takes its type from its
    __typename by a byte comparison with the names the plan lists, where
    the ingest made a string and took the registry's lock for every object;
    an escaped or unlisted name still asks the registry. A page of 899 union
    results ingests in 508 µs against 541, in BENCHMARKS.md.
  • The compiler's tests fence the bytes of generated code per accessor line
    at 120 over the goldens, the budget 0.1.0 named and never enforced; the
    goldens stand at 106.1.
  • A part of a deferred response that names a place no earlier part created,
    or a label the plan does not know, is logged as partDropped with its
    response path, where it was dropped without a word.
  • An image over its size limit evicts instead of starting over: the rows
    of launches before the last go first, then the last launch's, and the
    file shrinks; only a file still over the limit with nothing left to evict
    starts again. Recency is the launch's, which the rows already record; no
    rule per type. Recorded in docs/decisions/the-image-evicts-by-launch.md.
  • CI compares the benchmark suite's deterministic counts, the notifications
    a commit path fires and the events a commit logs, with
    benchmarks/counts.txt: swift run -c release BatonBenchmarks --counts
    prints them, one count <name> <value> a line, and a change to them is a
    diff to review where a timing would be noise.
  • A recipe, docs/recipes/discover-once.md: a subject discovered once by
    its natural key and refreshed by id through nodes(ids:), the pattern an
    app with external keys needs; the GitHub sample refreshes its rows that
    way from the toolbar.
  • A recipe for derived state outside views, docs/recipes/derived-state.md:
    a model derives its value inside an Observations closure over the
    lenses it reads, and no commit signal is added; recorded in
    docs/decisions/derived-state-is-observed-not-signaled.md.
  • List.empty, the value a view substitutes for a nullable list it reads
    as empty: fragment.reviewRequests?.nodes ?? .empty. A nullable list
    still reads as List?, since the server's null and its empty list
    differ.
  • Record, Value, Slot, TypeID, Owner and Members are generated
    code's interface, behind @_spi(Generated), now that no hook hands them
    out; an app's own files see lenses, handles, the environment, the log,
    transports and persistence.
  • The GitHub sample keeps its store in an image across launches and signs
    out from the toolbar: the environment ends, the image's file is removed,
    and a new environment takes over, as the README describes.
  • Two recipes: docs/recipes/uikit.md, a handle held by a view controller
    and rendered through Observations, with a cell over a lens; and
    docs/recipes/porting-from-relay.md, Relay's words beside Baton's, what
    differs on purpose, and what is not ported with its reason.
  • A recipe for previews and tests, docs/recipes/testing.md: a fixture
    committed as a response, recorded responses, a held mutation and a driven
    subscription, the log in tests, and a bug report's dump as a fixture.
  • Environment.fetch has one spelling, the operation value's. The one by
    type and variables, which returned the uncaught field errors as an array
    beside the other's throwing under @throwOnFieldError, is gone: a
    fetch's field errors are read where a view reads them, from the data,
    and each one no @catch handled is a fieldError event of the log.
  • The environment logs. Environment.log is one closure called with each
    LogEvent, a value-free enum of names and counts: a fetch started,
    completed with its duration or failed with its failure's kind; a commit
    with its kind and the slots it changed in records that existed; a field error a fetch's response
    carried that no @catch handled, by operation and response path, as
    Relay's field logger reports them; the image opened, unavailable,
    written with its batches or failed; a field read and never fetched, a value
    a reader's type cannot hold, an id naming records of several types, a
    @required(action: LOG) field that is null, each by type and field. It
    replaces the four hooks, reportMissing, reportUnexpected,
    reportAmbiguousIdentity and requiredFieldMissing, which handed out
    records, slots and values. Debug builds print the missing-data cases
    until log is set.
  • A query or subscription value's resolution, the handle a view resolved
    it to, is the mechanism's: declared behind @_spi(Generated) on the
    protocols and in generated code, read through phase, fetch,
    isStale and the rest as before.
  • Lens.typeName is gone: a line of generated code per lens and a public
    requirement, read by nothing. Format 15.
  • A fragment no operation reaches, directly or through another fragment,
    is a warning at its definition: nothing can read its lens, and the code
    generated for it is dead. The test target's fragments now all reach an
    operation.
  • batonc validate, the same compilation with no output, for an editor or a
    hook; batonc print <Name>, one operation's text and id as the app sends
    them; and batonc generate --check, which writes nothing and names every
    output on disk that differs from what it would write, for a team that
    commits its generated code. The command's contract, with a Bazel
    genrule over it, is docs/recipes/batonc.md.
  • BatonInspector, a third product of the package for a debug menu:
    StoreInspector(environment) is a view over the store, its counts, its
    records by type searchable by key, each record's slots with their values
    and field errors, and a share button that exports the store in the dump
    format spec/ freezes, so a bug report can become a fixture. It reads
    and never writes.
  • The compiler writes a report of what it compiled for a target:
    batonc generate --report <file>, and Baton.report.json in the build's
    output directory under the plugin. Every operation with its kind, source,
    id, variables, the fragments it reaches and its text; every fragment with
    its type, source, the operations that reach it and its printed
    definition; the schema's digest. Deterministic and by name, so a diff of
    two reports is the contract's change. The report and the persisted
    documents file sit beside the generated Swift in the plugin's output
    directory and are not outputs the build bundles, so neither reaches the
    app.
  • A recipe, docs/recipes/exchange.md, and its sample, examples/Exchange,
    for what a production endpoint needs around the transport's one verb: one
    replay of an authorization challenge, a bounded retry with a jittered
    doubling backoff over a 5xx or a lost connection, a deadline across every
    attempt and its waits, and never a second send of a mutation or of a
    stream that delivered. The GitHub sample sends through it.
  • BatonTesting gains ScriptedTransport, for an app's tests: answers
    from fixtures by operation name or through a responder, mutations held
    until the test replies or refuses, subscriptions driven by hand, and the
    requests sent listed by kind; and wait(until:), which waits on the main
    actor for a handle or a store to settle.
  • Subscriptions over HTTP, with graphql-sse in its distinct-connections
    mode: URLSessionTransport asks a subscription for text/event-stream
    and yields the payload of each next event until complete, so the
    environment's subscriptions may be the HTTP transport where a gateway
    holds no sockets. EventStreamParser splits the events beside
    MultipartParser's parts, whatever the chunking. The single-connection
    mode waits for a gateway that cannot do HTTP/2.
  • A subscription reconnects in its handle. A stream that ends by a failure
    while the handle is retained waits and opens again, by a fixed backoff: a
    step doubling from one second to thirty, jittered, reset by an event; the
    stream's value says waiting(until:) meanwhile, and retry() skips the
    wait; a request error, the server's refusal of the operation, ends the
    stream instead. The handle counts resumptions, the times the stream was opened
    again, so an owner that observes it refetches its baseline. The
    environment's isActive, set from the app's scene phase, parks every
    retained subscription while false and resumes them when true.
  • The transport has one verb. Transport.send takes a request and yields
    a stream of payloads: one for a query, the parts of a deferred response,
    the events of a subscription, so a wrapper wraps one method; payload
    reads the one payload of a request that answers once. execute, stream
    and SubscriptionTransport are gone, and the environment's
    subscriptions is a Transport. A Request says its kind and carries
    the operation's document, text or id and never both; the operation's
    text and persistedID are its document, with text an optional
    convenience. One Encoding turns a request into the JSON a server
    receives, for the HTTP body and the socket's payload alike; the standard
    one writes query or documentId, and the built-in transports take
    another. They also read credentials per attempt, from a closure. Under
    Relay's persistConfig in baton.json the artifact carries the id,
    hashed with MD5, SHA256 or SHA1, and no text, and batonc generate
    writes the map from id to text; without it the text and no id. Generated
    code of this shape is format 14.
  • A concrete type's lens under an interface or union sees the conditions
    on the interfaces and unions its type satisfies: ... on Character reads
    the fields ... on Named selected beside it, under that condition's
    @include and @skip, as Relay's generated types give each concrete
    variant every field a matching condition selected. The set condition
    keeps its own lens for the types the document does not name.
  • A variable of an input object type takes a Swift struct generated for
    the type, declared once per module in the shared file: a property per
    field typed as the schema types it, an initializer with a parameter per
    field, nil for a field left absent, and variable, the object the
    request carries. A field name is checked by the compiler where it was
    checked by the server before. Generated code of this shape is format 13.
  • What may reach the image is configured. baton.json's transient block
    names types whose records are never written and root fields, as
    Query.search, whose cells, storage keys and fetch stamps never are,
    since those carry the arguments and variables they were asked with. A
    slot linking to a transient record is left out of its row; the next
    launch misses on it and fetches. Memory is unaffected. The lists join the
    schema's digest, so an image written under another list starts again.
    Generated code of this shape is format 12.
  • Client schema extensions, under Relay's key schemaExtensions in
    baton.json: files, or directories of .graphql files, that give server
    types client fields or declare types the server does not have. A client
    field is written by commitPayload for an operation that selects it,
    read by a lens like any field, left out of the text and the id a server
    receives, and neither waited for by the availability check nor healed.
    The plan marks each client field; the extensions fold into the schema's
    digest. A non-null client field is refused at its line, and an operation
    of client fields alone at its name. The SwiftPM plugin regenerates when
    an extension changes. Generated code of this shape is format 11.
  • A schema enum reads as a Swift enum generated for it, declared once per
    module in the shared file: a case per value the build knows, spelled as
    the schema spells it, and unknown(String) for a value it does not, so a
    schema's growth never fails a read. The conversion cannot fail, so the
    accessor keeps the schema's nullability; a null on a non-null field reads
    as unknown("") and is reported. Variables and optimistic responses of
    an enum type take the enum. A schema enum named like a fragment or an
    operation is a name error at the document; one named like a shared enum
    or a standard library type takes Enum after its name. Generated code of
    this shape is format 10.
  • A custom scalar reads as the Swift type baton.json maps it to, under
    Relay's key customScalarTypes: "Decimal": "Foundation.Decimal". The
    type conforms to MappedScalar, an initializer from the scalar's text
    and the text back; Decimal, Date, URL and UUID conform, each with
    one format. The store keeps the text; the accessor converts at the read
    and says the conversion can fail: optional wherever the schema puts the
    field, non-optional and throwing under @required or
    @throwOnFieldError, a Result under @catch whose failure carries the
    conversion's error. A value that does not convert is reported as
    unexpected and never reads as a zero. A variable of a mapped type takes
    the Swift type and is sent as its text. Generated files import
    Foundation. Generated code of this shape is format 9.
  • A lookup takes several arguments. baton.json's lookups name the
    arguments that carry a composite key, "arguments": ["base", "quote"],
    one per field of the type's key in its order; argument stays the
    spelling for a key of one field. The compiler refuses a lookup whose
    arguments do not match the type's key, and a lookup without a type probes
    only the types one value keys. The runtime composes the arguments' values
    into the record's key as the ingest composes the key fields, so a cached
    Quote satisfies quote(base:, quote:) before it is fetched. Generated
    code of this shape is format 8.
  • Identity is configured. baton.json's identity names the fields that
    key a record of each type: a default list, ["id"] unless written, and
    types entries for a type or for an interface, whose implementers take
    it. A key is own scalar fields, in order, several of them for a composite
    key, Quote:base:quote, each value escaped so that no two lists of values
    meet; a key does not rename, since it is the values at the write. The
    compiler selects the key fields wherever the type is read, as it selects
    id, refuses a configuration naming a field a type lacks or cannot key
    by, and the plan names each type's key; the ingest keys a record once
    every key field is read, looking ahead past a link as it did for id. A
    configuration other than the default joins the schema's digest, so an
    image keyed the old way is a miss and not a merge. What names a record by
    one value, @deleteRecord, @deleteEdge, a lookup without a type and
    the image's forget, reaches single-field keys only. Generated code of this
    shape is format 7.
  • An owner settles an @include or @skip condition once, as it resolves
    a key with variables once. Generated code declares each condition as a
    constant, Guards.<variable>_<value>, that the plan's fields and the
    lenses' reads both name, and an accessor's read tests the owner's answer
    rather than looking the variable up by name. Generated code of this shape
    is format 6. Guards joins the names the compiler keeps: a fragment or
    operation of that name is an error when the module has a condition, a
    field of that name where its lens tests one, and a variable of that name
    where its operation tests one, as with Sites; a nested lens that would
    take the name is GuardsLens.
  • A resolved variant carries the lists its walks need, made once at the
    resolution: the fields a response is read by and the ones a complete
    response must carry, the fields the availability check waits for, the
    connections' client links the check walks for merged pages, and the
    links the collector follows. A walk tests no field for what it is; a
    field's origin, the server, a @defer label or the client, is one
    attribute of it.
  • An anchor's third word is the record its fragment starts at. A fragment
    spread enters its record as the anchor's origin, and a connection below
    it, however many links down, paginates with that record's id, as its
    fragment's query takes it; before, it passed the id of the record it
    hung from, which a connection one link below its fragment's type got
    wrong. Generated code of this shape is format 5. The test schema's
    Note gains author: Character for the fixture.
  • Membership comes from the response. Whether a type satisfies an
    interface or union condition is a table by the type's number, filled
    from the sets the build compiled and from what responses say in Relay's
    __isX fields, which the plan now carries; a type condition in a lens
    is an array load where it was a hash per read. A record of a concrete
    type the build did not list, a type the schema gained after the build,
    takes its variant from the answers: the fields under each condition the
    response says it satisfies, with the ones every type reads, settled once
    per type and per set of conditions. A condition every compiled member
    satisfies is a condition still, since it says nothing of a type the
    build did not list. The image keeps what responses taught for the next
    launch. A lookup without a type probes the members the build compiled.
    Generated code of this shape is format 4; the image's format is 6.
  • The plan names the field that keys a record of a type, id, and the
    ingest reads that key and knows no field by name; a refetch reads the
    owner's id from the slot its descriptor names. A storage key leaves a
    null argument out, as Relay's does: notes(after:null,first:2) is
    notes(first:2), whether the null is a constant in the document or a
    variable given null; an argument that is an object or a list keeps its
    nulls inside. Generated code of this shape is format 3, and an image is
    format 5: both start again. The decision is
    A storage key leaves a null argument out.
  • A change set carries where each record's id starts in its key, so the
    store makes the record from it and asks the registry nothing per record
    it creates; an entity's key, Type:id, is built in one place, and what
    names a record by a bare value, a deletion, a lookup or the image's
    forget, reads the value part the one way it was written.
  • The plan IR carries a field's and a variable's type as one recursive
    shape, a named type or a list of a type with nullability at every level,
    built once in the lowering for the reader side and the normalization
    side alike. With it, a list whose elements the schema types nullable
    reads as an array of optionals, [String?], and a null element reads as
    nil where it was dropped before; a list of non-null elements reports an
    element it cannot hold, a null or a value of another type, once, as a
    scalar reports a value it cannot hold, and leaves it out. A variable
    typed as such a list is a property of the same shape. Generated code of
    this shape is format 2: code of format 1 fails to compile at its marker,
    with a message that says to rebuild. The decision is
    A list's null elements are typed as the schema says.
  • spec/manifest.json lists the cases a runtime is held to: each a
    document, its variables, the responses in order, the store's dump after
    them, and what a generated lens reads at each of a set of paths, in a
    language-neutral form spec/README.md spells out. The documents the
    manifest names are under spec/documents/, written from the generated
    code and checked against it. The oracle tests run the manifest's cases
    and read every row through the generated lens, beside the plan walk they
    ran before.
  • The keys a session renders from its variables, one per id looked up and
    per cursor paged past, are numbered by the store that renders them, in
    Keys, where the process's registry numbered them for its own life: the
    registry numbers the keys the build names, and nothing a session produces
    is kept in a table of the process. A text has one slot in a store: a
    rendering whose text the build names as a constant takes the constant's
    slot, and a constant the build names after the store rendered its text is
    adopted when the store next resolves, checks or commits: the two slots
    become twins, the records' values are copied across, and every write
    lands in both. A plan is resolved for a store, Plan.resolve(_:in:), and a change
    set made from the resolution is committed into that store; the image's
    writer names a row's slots through the committing store's keys.
    Slot.storageKey is gone: a report names a slot through
    Store.storageKey(of:).
  • The image sweeps its names with its rows: at a launch's first batch,
    after the rows that aged out go, the names no row uses any more are
    deleted and their ids used again, so the file's table of names is
    bounded by its rows, and the limit of 65,536 names, past which an image
    started again, is gone. The image's format is 4; a file of format 3 is a
    miss and starts again.
  • The collector frees the keys a session rendered once nothing can name
    them: a resolution and a lens's scope hold the numbers they took while
    they live, an optimistic layer and a row waiting for the image keep
    theirs, and the rest go with the records the pass sweeps, their numbers
    used again for the next renderings, lowest first, and the image told to
    forget their names. A key a record's row was read under stays numbered
    while the image lives, as the record reads its row once; a root field's
    is freed with the rest and read from the image again. After a long
    session whose roots left, the store's table is the size it was at the
    start.
  • The image's file is protected at creation:
    Persistence(url:version:sizeLimit:protection:) and the name: form take
    a FileProtectionType, which Apple's SQLite gives the file and its
    write-ahead log; an image made under another class starts again. A file
    that cannot be taken, locked or full, is waited for, not discarded: the
    writer keeps its work for the next commit or read, where a lost batch
    marked the image behind memory and the next open emptied the cache, so
    every background refresh on a locked device would have emptied it. Work
    that outgrows 50,000 rows is dropped and the image starts again. A marker
    beside the file has the next open finish a removal a crash interrupted or
    a locked device put off; the log is deleted before the database file.
  • What the image was told to forget and has not yet dropped is kept behind
    the image's interface, as one value the store holds, where three sets of
    the store's tracked it.
  • The image's row is one codec: the tags, the writing of a record's row and
    a root field's cell, and the reading back, in one type over bytes, where
    the writer was the disk's and the reader the hydration's. The disk keeps
    SQLite and knows nothing of the layout; a record's snapshot is the
    record's own type, so the record names the image no more.
  • An image belongs to one store. It is made for the store and lives as long
    as it: the environment's end closes it and gives the file back, and the
    next environment makes its own, on that file or another. The count of
    removals a store noted when it was made, which eight of the image's
    functions took to fence a store from before a sign-out, goes: an ended
    store commits nothing, which fences the same with no count.
    removeAll() stays as the deletion of the file after the end, which is
    hygiene; what keeps one account's rows from the next is the image's
    identity, the account in its path or in its version.
  • An environment ends. await environment.end() ends the session once and
    for good: it cancels every fetch and stream the environment started,
    drops the roots, clears every record and closes the image, giving its
    file back. An ended store commits nothing, checked at the one door a
    payload takes, so a response that lands after the end, a fetch awaited in
    a task of the app's own or a mutation the server applied, reaches neither
    memory nor the image; a handle still held reads
    .failed(EnvironmentError.gone) and tells its observers; every later call
    on the environment throws the same error. A store dropped without an end
    clears its records when it is deallocated, so records that link to each
    other are freed with it.
  • A list of links that only grew, a page appended or prepended to a
    connection, drops no link, so it schedules no collection pass of its
    own; a list that changed otherwise still does. The lifetime step's
    numbers are in BENCHMARKS.md: a pass over 50,000 records takes 2.8 ms,
    and a page costs about a millisecond more from the eleventh on, the pass
    that follows the root the release buffer pushes out.
  • The heal. A read that finds a slot the store never received, which a
    retargeted link leaves behind, marks the owning operation stale and
    refetches it if a holder allows the network, once per fetch of that
    operation; a field still missing after the heal's own refetch is reported
    as unexpected and healed no further. Missing data was reported and left
    to the next attach.
  • The fetch policy is the holder's. An attach's policy stays with the
    retention it makes, so a fetch the runtime starts later asks whether any
    holder allows the network: invalidate() no longer refetches an
    operation attached storeOnly. Environment.revalidate() refetches the
    retained operations that are stale or whose last fetch failed, where a
    holder allows the network, and marks nothing: for an app's return to the
    foreground or a connection regained.
  • The commit stamps an operation's age. Every server write dates its
    operation, whoever asked for it: a handle's fetch, a refetch, a page,
    Environment.fetch or commitPayload, and a deferred response when its
    stream completes; the age is the root's, in the store, and a handle reads
    it as fetchTime. A query just written that nothing retains waits in the
    release buffer, as Relay's does. Data with no known age is stale wherever
    an expiration applies, in memory as it already was from the image: an
    operation never fetched whose data another operation brought refetches
    under a storeOrNetwork attach when a cache expiration applies. The store
    no longer names its environment: a lens fetches through its owner, and
    the phases a commit settles are told through a hook.
  • The store owns what keeps records alive. A root, an operation's selection
    and the record it starts from, is the store's, with how many hold it;
    retain() returns a Retention, a token whose end releases, and
    release() is gone: @Query's storage holds the token for the view's
    life, and a model or a view controller holds one in a property. The
    release buffer is Store(releaseBufferSize:), and collect(),
    rootCount and collections are the store's; Environment takes no
    buffer size. The collector marks from the roots, the optimistic layers and
    the image's write queue, and from nothing else, and runs when a root left
    or a commit dropped a link: a screen that stays up and refetches is
    collected without another view going away, and a release that only moves
    a root into the buffer runs no pass.
  • A query states how old its data may be in its document:
    @cacheExpiration(seconds:), the first directive that is not Relay's,
    which the compiler emits as a constant of the operation and leaves out of
    the text a server receives. The handle reads it with the operation's age;
    an operation that states none takes Store(cacheExpiration:), the
    default given when the store is made. The settable
    Environment.queryCacheExpiration is gone: a handle has no one owner, so
    a number set at run time was whoever set it last. The directive is
    refused on a mutation or a subscription, and with a variable.
  • A response that omits a field the operation selected fails the fetch as
    malformed, an IngestError naming the field and its type, where before
    the handle settled ready and the first read of the field reported missing
    data: a server answers every field it was asked for, so the plan was not
    made for the response. A deferred field is expected in its part, and an
    optimistic response or a payload committed by hand may carry part of the
    selection, as before.
  • Environment.commitPayload(operation, payload) commits a payload for an
    operation that some other road delivered, a REST response, a socket's
    tick, a preview's fixture or a test's seed, through the same door a
    fetch's response takes: the records merge, the connections and the edge
    directives apply, and the image is written. A payload may carry part of
    what the operation selects.
  • One door from a payload to slots. A query's fetch, a deferred stream, a
    mutation, a subscription's event and a page each normalized and
    committed on their own; every payload now passes through one function of
    the environment, which reads it by the plan off the main actor and
    commits it as a server batch, and each entrance keeps only its rule for a
    caller cancelled on the way: a query checks before the commit, a mutation
    does not, since the server applied it, a subscription commits until its
    task ends. An ended store will refuse at that door, the commit will stamp
    the operation's age there, and a report will be raised there.
  • The ingest returns what it normalized and no more: a change set holds
    what the store writes, and the parts an incremental response announces,
    whether more follow, and the errors of a part the server could not
    deliver are read beside it rather than carried through the commit. The
    assembly of a stream of parts into change sets, by announced id or by
    path and label, leaves the environment's fetch for a delivery of its
    own; the socket's frame reader leaves the ingest for the transport.
  • Nothing outside a batch writes a record. A commit is a batch of one of
    three kinds: server, written to the image; optimistic, whose undo stays
    with its layer; and local, the runtime's own writes, a page's loading
    flag, a lookup's link bound, a link repaired, a cell filled from the
    image, a deferred slot cleared, which were written one by one and
    notified as they happened. The availability check's writes are one local
    batch notified once the walk is over, so an observer that asks the store
    a question from inside a notification sees a whole walk, never one in
    progress; the store no longer keeps the walk's state to guard against
    that. The connection merge and the edge edits are functions over a batch
    in a file of their own.
  • A subscription handle's stream is a value beside its events. stream
    reads .idle, .connecting until the first event, .open, or
    .ended(failure), with nil for the server's completion and a Failure
    for an error that ended it; isActive is connecting or open. A
    subscription has no loading, since it has no data of its own to wait for,
    so this is not a phase.
  • An error keeps its extensions. FieldError.extensions is the server's
    extensions for the error as a JSON value (Variable), kept in memory
    and in the image, so an app branches on the server's code and never on
    its message; GraphQLErrors.errors carries a request error's errors
    whole, with messages still readable. The image's row format moves to
    3, so an image written before starts again, as a cache does.
  • A handle's fetch is a value beside its phase. fetch on a query value
    and on OperationHandle reads .idle, .inFlight, or
    .failed(failure, at:) with the failure and when it failed, so a fetch
    that fails behind data is seen while the phase stays ready, by every view
    of the handle; the next response replaces it. isRefreshing is data
    present and a fetch in flight. The failure is a Failure, one of a closed
    set of kinds: the transport's, carrying what the transport threw
    unchanged; a request error (GraphQLErrors); a malformed response
    (IngestError); the environment's (EnvironmentError). Field errors are
    not among them. What phase's failed case and refetch() carry is the
    error as thrown, as before. Deriving the phase when it is read was decided
    behind a gate, and the gate failed on the bench, so the phase stays
    stored.