gql_sse_link 1.0.0-beta.3 copy "gql_sse_link: ^1.0.0-beta.3" to clipboard
gql_sse_link: ^1.0.0-beta.3 copied to clipboard

GQL terminating Link to execute GraphQL subscriptions over Server-Sent Events (graphql-sse "distinct connections" protocol).

gql_sse_link #

pub package MIT License PRs Welcome Star on GitHub

⚠️ Disclaimer: This is beta software, mostly generated by AI. We are still in the process of reviewing and testing it — use at your own risk. It is published anyway because it brings the number of options for GraphQL with SSE subscriptions in the gql_link ecosystem from 0 to 1. Contributions welcome!

GQL link to execute GraphQL subscriptions over Server-Sent Events using the graphql-sse "distinct connections" protocol.

SseLink is a terminating link that specialises in subscriptions. Queries and mutations should be routed to a different terminating link (typically HttpLink) via Link.split.

Usage #

import "package:gql_exec/gql_exec.dart";
import "package:gql_http_link/gql_http_link.dart";
import "package:gql_link/gql_link.dart";
import "package:gql_sse_link/gql_sse_link.dart";

void main() {
  final link = Link.split(
    (request) =>
        request.operation.getOperationType() == OperationType.subscription,
    SseLink("https://example.com/graphql/stream"),
    HttpLink("https://example.com/graphql"),
  );
}

HTTP/2 and HTTP/3 #

The SSE endpoint is called via package:http, which defaults to dart:io's HttpClient on io platforms — HTTP/1.1 only. To get HTTP/2 or HTTP/3 (connection multiplexing, QUIC), inject a different http.Client:

Platform Client Protocols
Android cronet_http HTTP/1, HTTP/2, HTTP/3
iOS/macOS cupertino_http HTTP/1, HTTP/2, HTTP/3 (iOS 15+)
Web browser (fetch) HTTP/1, HTTP/2, HTTP/3
SseLink(
  "https://example.com/graphql/stream",
  httpClient: CronetClient.defaultCronetEngine(),
);

Reconnection #

Subscriptions are long-lived, so the underlying connection can drop mid-stream — the OS stalls the app/tab, the server closes the stream, or the network blips. When that happens the transport either errors (on web: ClientException: Error in input stream) or the stream simply ends without an event: complete. SseLink treats both as transient: it transparently re-issues the POST in the background with exponential backoff, so consumers see one continuous response stream that does not error on a transient drop. No changes are needed downstream.

Terminal conditions are not retried:

  • An explicit event: complete closes the stream for good.
  • Deterministic failures propagate as errors instead of spinning forever: HTTP 4xx (e.g. 400/401/403), malformed next payloads (SseLinkParserException), and request-format/context errors.

Transient failures are retried: network errors, dropped/aborted streams, and HTTP 5xx.

Cancelling the subscription (e.g. navigating away) aborts the in-flight HTTP response cleanly and silently — no aborted-stream error escapes to the zone.

Configuration #

The policy is configurable via optional constructor parameters, modelled on gql_websocket_link. The public API is backward compatible; the defaults suit live subscriptions.

Parameter Type Default Description
retryAttempts int SseLink.unlimitedRetries (-1) Max consecutive reconnects before the stream errors out. -1 reconnects indefinitely. The counter resets after a connection stays healthy for retryHealthyThreshold.
retryWait Future<void> Function(int retries) SseLink.randomizedExponentialBackoff Backoff schedule; awaited before each reconnect. retries starts at 0 for the first reconnect after a healthy connection.
shouldRetry bool Function(Object error) SseLink.shouldRetryDefault Classifies a transport failure as transient (true → reconnect) or deterministic (false → propagate).
retryHealthyThreshold Duration Duration(seconds: 30) Once a connection has stayed open this long, the next drop restarts the backoff from its first step.
connectionParams FutureOr<Map<String, String>?> Function()? null Resolves extra HTTP headers on every (re)connection. Its headers override defaultHeaders and the request's context headers.
SseLink(
  "https://example.com/graphql/stream",
  // Stop after 10 consecutive failed reconnects instead of retrying forever.
  retryAttempts: 10,
  // Custom backoff: fixed 5s between attempts.
  retryWait: (retries) => Future<void>.delayed(const Duration(seconds: 5)),
);

The default randomizedExponentialBackoff starts at a 1s base delay, doubles per attempt up to a 30s cap, and adds a random 300ms–3s jitter.

Refreshing auth on reconnect #

Because a reconnect re-issues the original POST, headers baked into the request (e.g. by an AuthLink) are not refreshed — a bearer token that expired mid-subscription would be replayed stale. Use connectionParams to resolve headers fresh on every attempt instead:

SseLink(
  "https://example.com/graphql/stream",
  connectionParams: () async => {
    "Authorization": "Bearer ${await tokenStore.currentAccessToken()}",
  },
);

connectionParams is the SSE analog of gql_websocket_link's parameter of the same name; since SSE authenticates over HTTP headers rather than a connection_init payload, it returns headers. A failure while resolving them (e.g. the token endpoint is unreachable) is treated as a transient drop and retried, unless your shouldRetry classifies the thrown error as terminal.

Non-goal — Last-Event-ID resumption. The distinct-connections mode re-executes the subscription from scratch on every connection, and these subscriptions stream full current state on each event, so a plain re-POST is sufficient. The link does not send Last-Event-ID.

Protocol notes #

This link implements the distinct connections mode of graphql-sse: each subscription is a single POST with Accept: text/event-stream whose response is a stream of SSE events:

  • event: nextdata: is a GraphQL ExecutionResult. Parsed via the standard ResponseParser and emitted on the link's response stream.
  • event: complete — closes the response stream.

The single connection mode (reservation tokens + multiplexed operations) is not implemented. With HTTP/2 or HTTP/3, distinct connections multiplex over a single transport connection anyway, so there is no meaningful overhead.

0
likes
160
points
34
downloads

Documentation

API reference

Publisher

unverified uploader

Weekly Downloads

GQL terminating Link to execute GraphQL subscriptions over Server-Sent Events (graphql-sse "distinct connections" protocol).

Repository (GitHub)
View/report issues

Topics

#graphql #gql #gql-link #sse

License

MIT (license)

Dependencies

gql, gql_exec, gql_link, http, meta

More

Packages that depend on gql_sse_link