Repository navigation
Expand file tree
/
Copy pathTypes.swift
More file actions
273 lines (241 loc) · 8.85 KB
/
Copy pathTypes.swift
File metadata and controls
273 lines (241 loc) · 8.85 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
//
// Types.swift
//
//
// Created by Guilherme Souza on 13/05/24.
//
import Foundation
import HTTPTypes
public import Helpers
public import Logging
#if canImport(FoundationNetworking)
public import FoundationNetworking
#endif
/// The subscription state of a ``RealtimeChannel``, with the reason when there is one.
///
/// The set of cases is fixed until the next major version, so a `switch` needs no `default`.
/// The type is not `Equatable`: use ``isSubscribed`` and ``error``, or match a case.
///
/// ```swift
/// for await status in await channel.statusChanges() {
/// switch status {
/// case .resubscribing(_, let retryIn, let lastError):
/// print("Joining again in \(retryIn) after: \(lastError.message)")
/// case .failed(let error):
/// print("Gave up: \(error.message)")
/// default:
/// break
/// }
/// }
/// ```
///
/// ## Topics
/// ### States
/// - ``unsubscribed``
/// - ``subscribing(attempt:)``
/// - ``subscribed``
/// - ``resubscribing(attempt:retryIn:lastError:)``
/// - ``unsubscribing``
/// - ``failed(_:)``
/// ### Predicates
/// - ``isSubscribed``
/// - ``error``
public enum RealtimeChannelStatus: Sendable {
/// The channel has not joined the topic, or has left it.
case unsubscribed
/// The channel sent `phx_join` and waits for the server to confirm it. Attempt 1 is the
/// first join of a ``RealtimeChannel/subscribe()`` call; later attempts are rejoins.
case subscribing(attempt: Int)
/// The server confirmed the join. The channel receives events.
case subscribed
/// Join attempt `attempt` failed with `lastError`. The channel joins again after `retryIn`.
case resubscribing(attempt: Int, retryIn: Duration, lastError: RealtimeError)
/// The channel sent `phx_leave` and waits for the server to close it.
case unsubscribing
/// ``RealtimeChannel/subscribe()`` stopped because a retry cannot help. Call it again to
/// start over.
case failed(RealtimeError)
/// Whether the channel is ``subscribed``.
public var isSubscribed: Bool {
if case .subscribed = self { true } else { false }
}
/// The error behind ``resubscribing(attempt:retryIn:lastError:)`` or ``failed(_:)``, or `nil`.
public var error: RealtimeError? {
switch self {
case .resubscribing(_, _, let error), .failed(let error): error
case .unsubscribed, .subscribing, .subscribed, .unsubscribing: nil
}
}
}
extension RealtimeChannelStatus {
/// Whether `other` is the same case with the same values, so a repeat is not emitted.
func isSame(as other: RealtimeChannelStatus) -> Bool {
switch (self, other) {
case (.unsubscribed, .unsubscribed), (.subscribed, .subscribed),
(.unsubscribing, .unsubscribing):
true
case (.subscribing(let a), .subscribing(let b)):
a == b
case (.resubscribing(let a, let da, let ea), .resubscribing(let b, let db, let eb)):
a == b && da == db && ea.isSameFailure(as: eb)
case (.failed(let a), .failed(let b)):
a.isSameFailure(as: b)
default:
false
}
}
}
/// The connection state of a ``RealtimeClient``, with the reason when there is one.
///
/// The set of cases is fixed until the next major version, so a `switch` needs no `default`.
/// The type is not `Equatable`: use ``isConnected`` and ``error``, or match a case.
///
/// ```swift
/// for await status in await client.statusChanges() {
/// if case .reconnecting(let attempt, let retryIn, _) = status {
/// print("Attempt \(attempt + 1) in \(retryIn)")
/// }
/// }
/// ```
///
/// ## Topics
/// ### States
/// - ``disconnected(_:)``
/// - ``connecting(attempt:)``
/// - ``connected``
/// - ``reconnecting(attempt:retryIn:lastError:)``
/// ### Predicates
/// - ``isConnected``
/// - ``error``
public enum RealtimeClientStatus: Sendable {
/// The WebSocket is closed. The error is set when a failure, not the caller, closed it.
case disconnected(RealtimeError?)
/// The client is opening the WebSocket. Attempt 1 is the caller's
/// ``RealtimeClient/connect()``; later attempts reconnect after a drop.
case connecting(attempt: Int)
/// The WebSocket is open.
case connected
/// Connection attempt `attempt` failed or the socket dropped with `lastError`. The client
/// opens the WebSocket again after `retryIn`.
case reconnecting(attempt: Int, retryIn: Duration, lastError: RealtimeError)
/// Whether the client is ``connected``.
public var isConnected: Bool {
if case .connected = self { true } else { false }
}
/// The error behind ``disconnected(_:)`` or ``reconnecting(attempt:retryIn:lastError:)``,
/// or `nil`.
public var error: RealtimeError? {
switch self {
case .disconnected(let error): error
case .reconnecting(_, _, let error): error
case .connecting, .connected: nil
}
}
}
extension RealtimeClientStatus {
/// Whether `other` is the same case with the same values.
func isSame(as other: RealtimeClientStatus) -> Bool {
switch (self, other) {
case (.connected, .connected):
true
case (.disconnected(let a), .disconnected(let b)):
a?.isSameFailure(as: b) ?? (b == nil)
case (.connecting(let a), .connecting(let b)):
a == b
case (.reconnecting(let a, let da, let ea), .reconnecting(let b, let db, let eb)):
a == b && da == db && ea.isSameFailure(as: eb)
default:
false
}
}
}
/// A server event about a ``RealtimeChannel``, emitted by ``RealtimeChannel/events()``.
///
/// The set of cases is fixed until the next major version, so a `switch` needs no `default`.
///
/// ## Topics
/// ### Events
/// - ``resubscribed``
/// - ``postgresChangesReady``
/// - ``postgresChangesFailed(_:)``
/// - ``replicationReady``
/// - ``replicationFailed(_:)``
public enum RealtimeChannelEvent: Hashable, Sendable {
/// The channel joined again after it was subscribed, for example after a reconnect. Events
/// sent while it was away were missed; reload any state built from them.
case resubscribed
/// The server set up the channel's Postgres changes subscriptions ("Subscribed to
/// PostgreSQL"). Changes from now on are delivered.
case postgresChangesReady
/// The server could not set up the channel's Postgres changes subscriptions. The channel
/// stays subscribed, so broadcast and presence keep working. The value is the server's
/// message.
case postgresChangesFailed(String)
/// The Postgres replication connection behind the channel is ready ("Replication connection
/// established"). The server sends it only when you opt in with
/// ``RealtimeChannelConfiguration/Broadcast-swift.struct/waitsForReplication``.
case replicationReady
/// The Postgres replication connection did not become ready in time. The server sends it only
/// when you opt in with ``RealtimeChannelConfiguration/Broadcast-swift.struct/waitsForReplication``. The value is the server's
/// message.
case replicationFailed(String)
}
/// One step of the heartbeat cycle, emitted by ``RealtimeClient/heartbeats()``.
///
/// The client sends a heartbeat every ``RealtimeClient/Configuration/heartbeatInterval``. When
/// the next one is due and the server has not acknowledged the previous one, the client
/// reports ``timedOut`` and reconnects.
///
/// The set of cases is fixed until the next major version, so a `switch` needs no `default`.
///
/// ## Topics
/// ### Events
/// - ``sent``
/// - ``acknowledged(latency:)``
/// - ``timedOut``
public enum HeartbeatEvent: Hashable, Sendable {
/// The client sent a heartbeat.
case sent
/// The server acknowledged the heartbeat `latency` after it was sent.
case acknowledged(latency: Duration)
/// The server did not acknowledge the previous heartbeat in time. The client drops the
/// socket and reconnects.
case timedOut
}
/// How much the Realtime server logs for one connection.
///
/// Set it on ``RealtimeClient/Configuration/serverLogLevel``. The client sends it as the `log_level`
/// query item of the WebSocket URL. It does not change what the client's own `logger` prints.
///
/// The type is open: a level this SDK has no member for still works.
///
/// ```swift
/// var configuration = RealtimeClient.Configuration(url: realtimeURL)
/// configuration.serverLogLevel = .warning
/// ```
///
/// ## Topics
/// ### Levels
/// - ``info``
/// - ``warning``
/// - ``error``
public struct RealtimeServerLogLevel: RawRepresentable, Hashable, Sendable,
ExpressibleByStringLiteral
{
/// The value sent as the `log_level` query item.
public let rawValue: String
/// Creates a level from its raw value.
public init(rawValue: String) {
self.rawValue = rawValue
}
/// Creates a level from a string literal.
public init(stringLiteral value: String) {
self.init(rawValue: value)
}
/// Informational messages and above.
public static let info: RealtimeServerLogLevel = "info"
/// Warnings and errors. Sent as `warn`.
public static let warning: RealtimeServerLogLevel = "warn"
/// Errors only.
public static let error: RealtimeServerLogLevel = "error"
}