Skip to content

v2.2.0

Latest

Choose a tag to compare

@mikaelwills mikaelwills released this 27 Jun 07:33
· 1 commit to master since this release

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 an onBeforeReplay veto callback that lets the consumer inspect and reject queued mutations before they flush. Exported from spacetimedb_sdk.dart and codegen.dart. Defaults preserve prior behaviour (replay everything, no expiry).
  • Sync-failure surfacing on SyncState. Flush rejections are now recorded as failure records on SyncState instead of being swallowed, with a configurable retention bound (also via OfflineQueuePolicy). New clearSyncErrors() to drain them; generated clients expose a clearSyncErrors passthrough.
  • New exception type(s) on lib/src/exceptions.dart for 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 OfflineStorage contract.

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).
  • SubscribeApplied cache 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. New SpacetimeDbConnection.negotiatedProtocol getter exposes the result. New public enum NegotiatedWsProtocol { 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-message MessageDecoder.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 at ConnectionConfig.maxV3OutboundFrameBytes (default 256 KiB, matching the TypeScript SDK); a queue exceeding the cap splits across multiple frames with a Timer(Duration.zero) yield between them so a backlog never starves the read path. New ConnectionConfig.outboundBatching knob (OutboundBatchingPolicy.opportunistic default, disabled for one-frame-per-call). On v2 or with disabled, every send is one frame unchanged.
  • Flutter web wasm support. flutter build web --wasm no longer fails at first connection with UnsupportedError: No WebSocket implementation for this platform. The conditional-import gate in lib/src/connection/websocket.dart and platform.dart migrated from dart.library.html (false under wasm) to dart.library.js_interop (true under both JS and wasm). HtmlWebSocketChannel in web_socket_channel 3.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.dart verifies (a) server accepts v3, (b) [v3, v2] lands on v3, (c) v2 fallback regression guard, (d) SpacetimeDbConnection.negotiatedProtocol == v3 end-to-end with first-frame decode.

Changed (non-breaking)

  • Renamed internal files websocket_html.dartwebsocket_web.dart and platform_html.dartplatform_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.0 for the v3 integration tests to land on v3 (otherwise the suite still passes via v2 fallback assertions).