Skip to content

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.

  1. Normalized cache — done

    Objects with __typename + id are 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. Left for later: type policies (custom merge per field, connection merging) and automatic gc().

  2. Mutations — done

    client.mutate((m) => m.toggleFavorite(launchId: id)?.favorite) and MutationBuilder<Mutation>, with journaled optimistic writes rolled back on failure. See Mutations. Also done: refetchQueries sugar, typed cache access (client.cacheScope), and list membership after a mutation (cacheScope.list((q) => q.me?.favorites).append/remove, evict) — see Caching. Left for later: connection-aware type policies so paginated/filtered lists update without refetchQueries.

  3. Subscriptions

    The mock API serves launchStatusChanged and launchScheduled over SSE. A SubscriptionBuilder<Subscription> records a selection once, opens the stream, and writes each event into the cache.

  4. Unions and interfaces ($on)

    Generator emits PetOn get $on with one getter per possible type; printer emits inline fragments ... on Cat { meows }. Add a union to the mock schema first.

  5. Fine-grained rebuilds — mostly done

    Scopes now rebuild on entity.field intersection instead of root aliases. Remaining: list-index granularity for inline lists, and skipping rebuilds when a written value is equal to the cached one inside inline objects.

  6. Cache expiry and SWR — done

    FetchPolicy (cacheFirst / cacheAndNetwork / networkOnly) per widget or client-wide, maxAge stale-while-revalidate on per-field fetch stamps, state.isStale, soft state.revalidate() — see Fetch policies & freshness. Left: WeakReference-held stale entries as an eviction mechanism.

  7. Developer experience — in progress

    Done: real Dart enums with an unknown value (see Code generation), no setters on key fields or connection metadata, and PaginatedQueryBuilder (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.isSkeleton for the three-meanings-of-null story; refetchQueries on mutations; --scalar mapping; MutationBuilder resolving its root from SlingScope(schema:); the sling_gql_test package (MockGraphQLServer, pumpUntilSettled — see Testing). Next: the in-app overlay attributing each request to the widgets that recorded it; a pixel-style log line per operation; flutter_hooks adapter (useSlingQuery).

  8. Transport options — hook done

    SlingClient(transport:) is the single interception point (see Transport). Left: a gql_link adapter for auth headers, retries and persisted queries, without changing the runtime’s _post contract.

  • SSR hydration. Not applicable.
  • Lazy / transaction / paginated query variants. resolve() plus widget state cover the same ground.