Hardens the offline-first sync path: a configurable replay policy for the offline mutation queue, surfaced flush failures, append-only journal storage, and a cluster of reconnect-correctness fixes that stop offline edits being silently clobbered when a subscription rehydrates. All additive — existing consumers keep working unchanged.
Added
OfflineQueuePolicy— opt-in control over how the offline mutation queue replays on reconnect. Configurable TTL (drop mutations older than a bound), queue size bounds, and anonBeforeReplayveto callback that lets the consumer inspect and reject queued mutations before they flush. Exported fromspacetimedb_sdk.dartandcodegen.dart. Defaults preserve prior behaviour (replay everything, no expiry).- Sync-failure surfacing on
SyncState. Flush rejections are now recorded as failure records onSyncStateinstead of being swallowed, with a configurable retention bound (also viaOfflineQueuePolicy). NewclearSyncErrors()to drain them; generated clients expose aclearSyncErrorspassthrough. - New exception type(s) on
lib/src/exceptions.dartfor offline-replay rejection paths.
Changed (non-breaking)
- Append-only journal storage for the offline mutation queue. The JSON file backend no longer rewrites the entire queue file on every mutation — it appends to a journal, cutting write amplification for large or churny queues. Transparent to consumers; same
OfflineStoragecontract.
Fixed
- Offline edits no longer clobbered on reconnect. Rows with pending offline mutations are protected from stale-snapshot overwrites when a subscription delivers a fresh server snapshot on reconnect (
5bc3898). - Subscription rehydrates via the awaited subscribe path on reconnect rather than racing the cache, so reconnect-time row state is correct (
b4bae9f). SubscribeAppliedcache clear scoped to the current query set — a multi-queryset client no longer wipes unrelated cached rows when one subscription re-applies (48b0a09).- Cache reconciled to committed server rows when an optimistic confirmation releases a key, eliminating a window where the local view diverged from the server (
1c638df).
Adds v3 WebSocket transport support (SpacetimeDB 2.2.0+) and unblocks Flutter web wasm builds. Both changes are additive — no consumer code changes required, no semver-breaking deltas.
Added
- v3 WebSocket subprotocol negotiation. SDK now advertises
[v3.bsatn.spacetimedb, v2.bsatn.spacetimedb]and uses whichever the server picks. v3 servers (SpacetimeDB 2.2.0+, upstream PR #4761) negotiate v3 automatically; older servers fall back to v2 with no behavioural change. NewSpacetimeDbConnection.negotiatedProtocolgetter exposes the result. New public enumNegotiatedWsProtocol { v2, v3 }. - Inbound v3 frame batching. On v3, the server may pack multiple
ServerMessages into one WebSocket frame to cut per-message overhead.MessageDecoder.decodeAll(bytes)reads the compression tag once, decompresses once, then splits the payload into N messages and dispatches each in logical order. The single-messageMessageDecoder.decode(bytes)is preserved for v2 callers and asserts a single-message payload. - Outbound v3 frame batching (opportunistic). Same-microtask
send(...)calls coalesce into one frame on v3 — captures the realistic win of N synchronous subscribes during reconnect-resubscribe. Capped atConnectionConfig.maxV3OutboundFrameBytes(default 256 KiB, matching the TypeScript SDK); a queue exceeding the cap splits across multiple frames with aTimer(Duration.zero)yield between them so a backlog never starves the read path. NewConnectionConfig.outboundBatchingknob (OutboundBatchingPolicy.opportunisticdefault,disabledfor one-frame-per-call). On v2 or withdisabled, everysendis one frame unchanged. - Flutter web wasm support.
flutter build web --wasmno longer fails at first connection withUnsupportedError: No WebSocket implementation for this platform. The conditional-import gate inlib/src/connection/websocket.dartandplatform.dartmigrated fromdart.library.html(false under wasm) todart.library.js_interop(true under both JS and wasm).HtmlWebSocketChannelinweb_socket_channel3.x is already wasm-safe — no behavioural change for existing JS web builds. - Integration tests against a live 2.2.0+ server:
test/integration/v3_protocol_negotiation_test.dartverifies (a) server accepts v3, (b)[v3, v2]lands on v3, (c) v2 fallback regression guard, (d)SpacetimeDbConnection.negotiatedProtocol == v3end-to-end with first-frame decode.
Changed (non-breaking)
- Renamed internal files
websocket_html.dart→websocket_web.dartandplatform_html.dart→platform_web.dart. Public surface unchanged — these files were never exported.
Compatibility
- Server: v3 features require SpacetimeDB 2.2.0+. Older servers transparently negotiate v2.
- Flutter web: wasm builds now work; JS builds behave identically to 2.0.0.
- Test infrastructure: local development requires
spacetime version use >= 2.2.0for the v3 integration tests to land on v3 (otherwise the suite still passes via v2 fallback assertions).