Roadmap
Each item below is scoped so it can be built and tested against the bundled
mock API, which already exposes the server side of
phases 2 and 3. The detailed, numbered backlog (including DX and performance findings
from the example app) is TODO.md at the repository root; this page is the summary.
-
Normalized cache — done
Objects with
__typename+idare stored once, referenced from the tree, and scopes are notified per entity field.launch(id:)is served from the entity the list wrote. See Caching. Unreachable entities are swept automatically (SlingClient(gcAfterWrites:)). Type policies (SlingClient(typePolicies:)): key arguments and a custom merge per field, andRelayStylePaginationkeeping a cursor connection in one growing list — see Pagination. -
Mutations — done
client.mutate((m) => m.toggleFavorite(launchId: id)?.favorite)andMutationBuilder<Mutation>, with journaled optimistic writes rolled back on failure. See Mutations. Also done:refetchQueriessugar, typed cache access (client.cacheScope), and list membership after a mutation (cacheScope.list((q) => q.me?.favorites).append/remove,evict) — see Caching. Partial errors fail the call but keep what the server resolved, over the rolled-back optimistic writes. List rules keep filtered lists and merged connections in sync with their entities. -
Subscriptions — done
client.subscribe((s) => s.launchStatusChanged?..status)andSubscriptionBuilder<Subscription>record a selection once, open a GraphQL-over-SSE stream (SlingClient(subscriptionTransport:)to swap the protocol), and write each event through the samewriteResponseas a query — see Subscriptions.MockGraphQLServer(subscription:)takesStreams. Reconnection (subscriptionRetryAfter,reconnect()) is done. SSE is the only built-in transport; agraphql-wsadapter may come later as its own package. -
Unions and interfaces — done
The generator emits one accessor per union/interface with an
asTypegetter per member (hit.asLaunch?.name) andwhen(launch: …, rocket: …, orElse: …); the printer emits inline fragments... on Launch { … }, every branch recorded on the skeleton so one request covers them all. Members normalize like any entity. The mock API hassearch(text:): [SearchResult!]!andnode(id:): Node; the example’s Search tab uses them — see Querying. Same-named fields across fragments are printed type-qualified and mapped back before caching. -
Fine-grained rebuilds — done
Scopes rebuild on
entity.fieldintersection instead of root aliases; inline objects and lists are compared structurally, so identical responses touch nothing;SlingRowgives a list row its own dependency set, so a write to one entity rebuilds that row, not the list (see Caching). Inline lists notify per element (entity.field[i]) and per length (entity.field[length]), so a change at one index rebuilds only its readers. -
Cache expiry and SWR — done
FetchPolicy(cacheFirst/cacheAndNetwork/networkOnly) per widget or client-wide,maxAgestale-while-revalidate on per-field fetch stamps,state.isStale, softstate.revalidate()— see Fetch policies & freshness. Left:WeakReference-held stale entries as an eviction mechanism. -
Developer experience — in progress
Done: real Dart
enums with anunknownvalue (see Code generation), no setters on key fields or connection metadata, andPaginatedQueryBuilder(see Pagination), and a dev-mode waterfall warning (SlingClient.onWaterfall,QueryBuilder.debugLabel) naming the scope and fields that caused a second round trip;Accessor.isFetched+QueryState.isSkeletonfor the three-meanings-of-nullstory;refetchQuerieson mutations;--scalarmapping;MutationBuilderresolving its root fromSlingScope(schema:); thesling_gql_testpackage (MockGraphQLServer,pumpUntilSettled— see Testing); example dev tools (latency picker, live cache stats,resolve()prefetch — see Example app); the in-app request overlay (SlingRequestOverlay) and one log line per operation (logRequests,client.requests), each request attributed to the widgets that recorded it (see Batching and waterfalls); theflutter_hooksadaptersling_gql_hooks(useSlingQuery,useSlingMutation,useSlingSubscription— see Hooks). -
Transport options — done
SlingClient(transport:)is the single interception point (see Transport).sling_gql_linkturns agql_linkchain (auth headers, retries, persisted queries) into that transport and the subscription transport, so the runtime stayshttp-only (see Transport). -
Live docs — in progress
Done: the example app has a web build that runs on this site — Try it live — with the mock API executing in your browser (the same resolvers as the Node server, behind the app’s
http.Client), so every visitor gets their own data and the site stays static. The batching, fetch-policies and mutations guides each run a small demo next to its code; the code shown is the demo’s source. Next: demos for the other guides. -
Persistence — done (SQLite)
sling_gql_sqflitekeeps a durable copy of the normalized cache: deltas saved in the background (changesSince, debounced, flushed when the app pauses), restored before the client starts, bounded by age and entity count, migrated along the generatedslingSchema.fieldswhen the schema’shashchanges (what the new code can still read is kept), recreated when the file is corrupt, one writer per file, with an optional row codec for encryption — see Persistence. Hive and file adapters are not planned until someone needs them. -
Production readiness — done, released as 1.0.0
Done: type policies and connection merging (one growing cache list for cursor pages,
RelayStylePagination); persistence migration across schema changes; a typed, sealed error model witherrorPolicy(Loading states & errors), timeouts and cancellation, query retry with backoff and a single-flight token-refresh hook (Transport); Android, a second GraphQL server (graphql-http) and a soak test in CI; an offline mutation queue (Mutations, persisted bysling_gql_sqflite); an API audit with a versioning policy and an upgrade guide. All six packages are 1.0.0 on pub.dev.
Explicitly not planned
Section titled “Explicitly not planned”- SSR hydration. Not applicable.
graphql-wsin the core. Subscriptions stay on SSE; a WebSocket transport would be a separate package plugged intoSlingClient(subscriptionTransport:).- Lazy / transaction / paginated query variants.
resolve()plus widget state cover the same ground.