feat(_Realtime): Realtime v3 — idiomatic Swift 6 actor-based API#978
Draft
feat(_Realtime): Realtime v3 — idiomatic Swift 6 actor-based API#978
Conversation
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…] and URLSession Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds a new standalone Swift 6.0 package at Packages/_Realtime with pure foundation types: RealtimeError, CloseReason, RealtimeLogger, transport protocol + URLSessionTransport, APIKeySource, ReconnectionPolicy, and Configuration. All 4 ReconnectionPolicyTests pass. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…, Duration truncation - Store and cancel the receive Task in URLSessionConnection to prevent a resource leak when close() is never called; also cancel in deinit. - Add LocalizedError conformance to RealtimeError with human-readable descriptions for every case. - Fix sub-second Duration truncation in ReconnectionPolicy.exponentialBackoff by including the attoseconds component in the Double conversion. - Add regression tests for sub-second initial delay and unbounded fixed policy. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Add a prominent doc comment to InMemoryServer explaining the mutual exclusion between receivedFrames and receive(), and document that close(code:reason:) ignores its parameters for API compatibility. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…encodePush error message - Assert payload content in decodeBinaryBroadcast test - Add encodeBroadcastPushRoundTrip test verifying binary header layout and JSON payload - Replace combined 255-byte guard in _encodePush with per-field guards that name the overflowing field Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…actor - Add ConnectionStatus with State enum (idle/connecting/connected/reconnecting/closed) - Add Realtime public final actor with connect/disconnect/channel registry - Add Channel stub (topic, options, realtime weak ref) and ChannelOptions/ChannelState stubs - Add withRealtimeTimeout helper for racing operations against a deadline - Add Clocks to test target dependencies for TestClock usage - All 17 tests pass (4 new RealtimeClientTests + 13 pre-existing) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ream initial value - Extract _connect() so attemptReconnect bypasses the idle/closed guard - Remove pre-assigned ref in updateToken (sendAndAwait always mints its own) - Yield current status immediately on status stream subscription - Make refCounter and _currentStatus private Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Implement full Channel actor with join/leave state machine, fan-out continuation infrastructure for broadcast/presence/postgres streams, and internal routing hooks called by the Realtime actor. Add Equatable conformance to RealtimeError and ChannelTests covering join, identity, options locking, and stream completion on leave. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Implements the full broadcast surface for the _Realtime package: - BroadcastMessage type with event, payload (JSONValue), and receivedAt - Channel.broadcasts() — untyped AsyncThrowingStream with auto-join and fan-out - Channel.broadcasts(of:event:decoder:) — typed, filtered, decoded stream - Channel.broadcast(_:as:) — typed JSON send (throws channelNotJoined if not joined) - Channel.broadcastBinary(_:as:) — binary-frame send via PhoenixSerializer - Realtime.httpBroadcast — single and batch HTTP POST broadcast without WebSocket - HttpBroadcastMessage value type for batch HTTP sends - 6 new BroadcastTests covering delivery, fan-out, typed decoding, binary frames, not-joined guard, and stream termination on leave Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…n config, JSONValue payload type - Rename `broadcastBinary(_:as:)` to `broadcast(_:as:)` for spec-correct call-site syntax - Add `urlSession: URLSession = .shared` to `Configuration` so HTTP broadcast uses the configured session - Update `httpBroadcast` to use `configuration.urlSession` instead of `URLSession.shared` - Change `HttpBroadcastMessage.payload` from existential `any Encodable & Sendable` to concrete `JSONValue` - Update generic `httpBroadcast<T>` convenience overload to encode `T` into `JSONValue` before building the message - Fix `join()` and `joinIfNeeded()` to allow re-joining from any `.closed(...)` state, not just `.closed(.userRequested)` Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…track Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ce tests - Move trackedStates.removeAll() out of finishAllContinuations so transient errors preserve tracked states for re-track after reconnect - Clear trackedStates on permanent closes: leave() and _join() rejection - rejoin() now iterates trackedStates and re-sends presence track messages - Add FLAG: trackSendsPresenceEvent verifies trackedStates is empty after cancel - Add Test: autoRetrackOnRejoin — verifies re-track after disconnect/rejoin - Add Test: multiTrackMultipleMetasPerKey — verifies count/cleanup of 2 handles - Add Test: handleLeakWarningDoesNotCrash — verifies deinit without cancel fires reportIssue but does not trap (marked as withKnownIssue) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…untyped escape hatch Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…nce stream tests Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add disabled-by-default integration test scaffold under _Realtime tests (requires local Supabase instance; enable by removing .disabled). - Add migration guide documenting V2 → V3 API mapping and behavioural differences. - Include a template SupabaseClient extension for consumers on iOS 17+; it cannot live inside the main Supabase target because _Realtime requires iOS 17+ while Supabase still supports iOS 13+ and SwiftPM applies package-level platforms to every target. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…serve path prefix
…wait, fix stale joinRef in track - Extract _Atomic<T> from PresenceHandle.swift into Internal/Atomic.swift - Add OnceResumingContinuation<T> that guards a CheckedContinuation so only the first resume call (timeout or server reply) wins, preventing a trap in debug and UB in release when both fire concurrently - Change pendingReplies to [String: OnceResumingContinuation<PhoenixMessage>] and withRealtimeTimeout to pass the guarded wrapper to both the timeout branch and the sendAndAwait closure, ensuring a single shared guard covers both racing paths - Add Channel.presenceTrackInfo() that returns (topic, joinRef) atomically; Presence.track(_:) now reads both in one await instead of two separate awaits, eliminating the stale-joinRef window across a channel rejoin Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Packages/_Realtime/— a standalone Swift 6.0 package implementing a ground-up redesign of the Realtime client targeting iOS 17+ / macOS 14+ with strict concurrencyPackages/_RealtimeTableMacros/— a companion macro package providing@RealtimeTableto synthesizeRealtimeTableconformance at compile timeInMemoryTransport.pair()as a public test helper for deterministic unit testing without real socketsWhat's in
_RealtimeCore architecture
Realtimeactor — manages WebSocket connection, heartbeat, reconnection policy, channel registryChannelactor — join/leave lifecycle, per-topic fan-out toAsyncThrowingStreamsubscribersRealtimeTransport/RealtimeConnectionprotocols +URLSessionTransportproduction implementationInMemoryTransporttest double (ships in the main target so users can write their own tests)Feature APIs (each via
Channelextensions)channel.broadcasts(),channel.broadcasts(of: T.self, event:),channel.broadcast(_:as:),channel.broadcast(_:as:)(binary),realtime.httpBroadcast(...)HTTP one-shot sendchannel.presence.track(_:) → PresenceHandle,channel.presence.observe(T.self),channel.presence.diffs(T.self), auto re-track on reconnectchannel.changes(to: T.self, where: Filter<T>),inserts/updates/deletesconvenience streams, untypedchanges(schema:table:filter:)escape hatchConfiguration —
ReconnectionPolicy(.never / .fixed / .exponentialBackoff),APIKeySource(.literal / .dynamic async),Configurationwith clock injection for testingTest plan
cd Packages/_Realtime && swift test— 45 tests pass, 1 known issue (intentionalPresenceHandleleak warning test)cd Packages/_RealtimeTableMacros && swift test— 2 macro expansion tests passswift test --filter RealtimeTests— 200 existing V2 tests unaffectedswift test --filter AuthTests— 167 existing Auth tests unaffectedNotes
SupabaseClient.realtimeV3is not wired into the rootSupabasemodule yet — the root package still targets iOS 13+ while_Realtimerequires iOS 17+. A ready-to-use extension template lives atdocs/migrations/SupabaseClient+RealtimeV3.swift.template. This will be resolved when the main package bumps its platform floor._Realtime→Realtimerename and fold into main package happens at release when the platform floor is bumped.docs/migrations/RealtimeV3 Migration Guide.md🤖 Generated with Claude Code