-
Notifications
You must be signed in to change notification settings - Fork 13
Expand file tree
/
Copy pathPowerSyncDatabaseProtocol.swift
More file actions
387 lines (350 loc) · 15.3 KB
/
Copy pathPowerSyncDatabaseProtocol.swift
File metadata and controls
387 lines (350 loc) · 15.3 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
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
import Foundation
/// Configuration for the sync client used to connect to the PowerSync service.
///
/// Provides options to customize network behavior and logging for PowerSync
/// HTTP requests and responses.
public struct SyncClientConfiguration: Sendable {
/// Optional configuration for logging PowerSync HTTP requests.
///
/// When provided, network requests will be logged according to the
/// specified `SyncRequestLoggerConfiguration`. Set to `nil` to disable request logging entirely.
///
/// - SeeAlso: `SyncRequestLoggerConfiguration` for configuration options
public let requestLogger: SyncRequestLoggerConfiguration?
/// The URL session to use when connecting to the PowerSync service.
///
/// Customizing this may be convenient to add additional headers or to otherwise alter requests
/// sent by the PowerSync Swift SDK.
public var urlSession: URLSession
/// Creates a new sync client configuration.
/// - Parameter requestLogger: Optional network logger configuration
/// - Parameter urlSession: Optional custom URL session to use for network requests.
public init(
requestLogger: SyncRequestLoggerConfiguration? = nil,
urlSession: URLSession = .shared,
) {
self.requestLogger = requestLogger
self.urlSession = urlSession
}
}
/// Options for configuring a PowerSync connection.
///
/// Provides optional parameters to customize sync behavior such as throttling and retry policies.
public struct ConnectOptions: Sendable {
/// Defaults to 1 second
public static let DefaultCrudThrottle: TimeInterval = 1
/// Defaults to 5 seconds
public static let DefaultRetryDelay: TimeInterval = 5
/// TimeInterval (in seconds) between CRUD (Create, Read, Update, Delete) operations.
///
/// Default is ``ConnectOptions/DefaultCrudThrottle``.
/// Increase this value to reduce load on the backend server.
public var crudThrottle: TimeInterval
/// Delay TimeInterval (in seconds) before retrying after a connection failure.
///
/// Default is ``ConnectOptions/DefaultRetryDelay``.
/// Increase this value to wait longer before retrying connections in case of persistent failures.
public var retryDelay: TimeInterval
/// Additional sync parameters passed to the server during connection.
///
/// This can be used to send custom values such as user identifiers, feature flags, etc.
///
/// Example:
/// ```swift
/// [
/// "userId": .string("abc123"),
/// "debugMode": .boolean(true)
/// ]
/// ```
public var params: JsonParam
/// Application metadata that will be displayed in PowerSync service logs.
///
/// Provide small, non-sensitive key/value pairs (for example: `appName`, `version`, `environment`) to
/// help identify the client in logs and diagnostics. Do not include secrets or tokens.
///
/// Example:
/// ```swift
/// ["appName": "MyApp", "version": "1.2.3"]
/// ```
public var appMetadata: [String: String]
/// Uses a new sync client implemented in Rust instead of the one implemented in Kotlin.
///
/// This option is enabled by default and exists for backwards compatibility. Support for the legacy
/// Kotlin-based implementation has been removed.
public var newClientImplementation: Bool
/// Configuration for the sync client used for PowerSync requests.
///
/// Provides options to customize network behavior including logging of HTTP
/// requests and responses. When `nil`, default HTTP client settings are used
/// with no network logging.
///
/// Set this to configure network logging or other HTTP client behaviors
/// specific to PowerSync operations.
///
/// - SeeAlso: `SyncClientConfiguration` for available configuration options
public var clientConfiguration: SyncClientConfiguration?
/// Whether streams that have been defined with `auto_subscribe: true` should be synced even
/// when they don't have an explicit subscription.
public var includeDefaultStreams: Bool
/// Initializes a `ConnectOptions` instance with optional values.
///
/// - Parameters:
/// - crudThrottle: TimeInterval between CRUD operations in milliseconds. Defaults to `1` second.
/// - retryDelay: Delay TimeInterval between retry attempts in milliseconds. Defaults to `5` seconds.
/// - params: Custom sync parameters to send to the server. Defaults to an empty dictionary.
/// - clientConfiguration: Configuration for the HTTP client used to connect to PowerSync.
public init(
crudThrottle: TimeInterval = 1,
retryDelay: TimeInterval = 5,
params: JsonParam = [:],
clientConfiguration: SyncClientConfiguration? = nil,
appMetadata: [String: String] = [:],
includeDefaultStreams: Bool = true,
) {
self.crudThrottle = crudThrottle
self.retryDelay = retryDelay
self.params = params
newClientImplementation = true
self.clientConfiguration = clientConfiguration
self.appMetadata = appMetadata
self.includeDefaultStreams = includeDefaultStreams
}
/// Initializes a ``ConnectOptions`` instance with optional values, including experimental options.
@available(
*,
deprecated,
message: "Specifying the newClientImplementation flag is no longer needed. It is now enabled by default. Support for the legacy Kotlin-based client was removed."
)
public init(
crudThrottle: TimeInterval = 1,
retryDelay: TimeInterval = 5,
params: JsonParam = [:],
newClientImplementation: Bool = true,
clientConfiguration: SyncClientConfiguration? = nil,
appMetadata: [String: String] = [:],
includeDefaultStreams: Bool = true
) {
self.crudThrottle = crudThrottle
self.retryDelay = retryDelay
self.params = params
self.newClientImplementation = newClientImplementation
self.clientConfiguration = clientConfiguration
self.appMetadata = appMetadata
self.includeDefaultStreams = includeDefaultStreams
}
}
/// A PowerSync managed database.
///
/// Use one instance per database file.
///
/// Use `PowerSyncDatabase.connect` to connect to the PowerSync service, to keep the local database in sync with the remote database.
///
/// All changes to local tables are automatically recorded, whether connected or not. Once connected, the changes are uploaded.
public protocol PowerSyncDatabaseProtocol: Queries, Sendable {
/// The current sync status.
var currentStatus: SyncStatus { get }
/// Logger used for PowerSync operations
var logger: any LoggerProtocol { get }
/// Wait for the first sync to occur
func waitForFirstSync() async throws
/// Replace the schema with a new version. This is for advanced use cases - typically the schema
/// should just be specified once in the constructor.
///
/// Cannot be used while connected - this should only be called before connect.
func updateSchema(schema: SchemaProtocol) async throws
/// Wait for the first (possibly partial) sync to occur that contains all buckets in the given priority.
func waitForFirstSync(priority: Int32) async throws
/// Connects to the PowerSync service and keeps the local database in sync with the remote database.
///
/// The connection is automatically re-opened if it fails for any reason.
/// You can customize connection behavior using the `ConnectOptions` parameter.
///
/// - Parameters:
/// - connector: The `PowerSyncBackendConnector` used to manage the backend connection.
/// - options: Optional `ConnectOptions` to customize CRUD throttling, retry delays, and sync parameters.
/// If `nil`, default options are used (1000ms CRUD throttle, 5000ms retry delay, empty parameters).
///
/// Example usage:
/// ```swift
/// try await database.connect(
/// connector: connector,
/// options: ConnectOptions(
/// crudThrottleMs: 2000,
/// retryDelayMs: 10000,
/// params: [
/// "deviceId": .string("abc123"),
/// "platform": .string("iOS")
/// ]
/// )
/// )
/// ```
///
/// You can also omit the `options` parameter to use the default connection behavior:
/// ```swift
/// try await database.connect(connector: connector)
/// ```
///
/// - Throws: An error if the connection fails or if the database is not properly configured.
func connect(
connector: PowerSyncBackendConnectorProtocol,
options: ConnectOptions?
) async throws
/// Convenience method to get the current version of PowerSync.
func getPowerSyncVersion() async throws -> String
/// Close the sync connection.
///
/// Use `connect` to connect again.
func disconnect() async throws
/// Disconnect and clear the database.
///
/// Clearing the database is useful when a user logs out, to ensure another user logging in later would not see
/// previous data.
///
/// The database can still be queried after this is called, but the tables would be empty.
///
/// To perserve data in local-only tables, set `clearLocal` to `false`.
///
/// A `soft` clear deletes publicly visible data, but keeps internal copies of data synced in the database. This
/// usually means that if the same user logs out and back in again, the first sync is very fast because all internal
/// data is still available. When a different user logs in, no old data would be visible at any point.
/// Using soft clears is recommended where it's not a security issue that old data could be reconstructed from
/// the database.
func disconnectAndClear(clearLocal: Bool, soft: Bool) async throws
/// Create a ``SyncStream`` instance for the given name and parameters.
///
/// Use ``SyncStream/subscribe`` on the returned instance to subscribe to the stream.
func syncStream(name: String, params: JsonParam?) -> any SyncStream
/// Close the database, releasing resources.
/// Also disconnects any active connection.
///
/// Once close is called, this database cannot be used again - a new one must be constructed.
func close() async throws
/// Close the database, releasing resources.
/// Also disconnects any active connection.
///
/// Once close is called, this database cannot be used again - a new one must be constructed.
///
/// - Parameter deleteDatabase: Set to true to delete the SQLite database files. Defaults to `false`.
///
/// - Throws: An error if a database file exists but could not be deleted. Files that don't exist are ignored.
/// This includes the main database file and any WAL mode files (.wal, .shm, .journal).
func close(deleteDatabase: Bool) async throws
}
public extension PowerSyncDatabaseProtocol {
/// Get the next recorded transaction to upload.
///
/// Returns nil if there is no data to upload.
///
/// Use this from the `PowerSyncBackendConnector.uploadData` callback.
///
/// Once the data have been successfully uploaded, call `CrudTransaction.complete` before
/// requesting the next transaction.
///
/// Unlike `getCrudBatch`, this only returns data from a single transaction at a time.
/// All data for the transaction is loaded into memory.
func getNextCrudTransaction() async throws -> CrudTransaction? {
for try await transaction in getCrudTransactions() {
return transaction
}
return nil
}
///
/// The connection is automatically re-opened if it fails for any reason.
///
/// - Parameters:
/// - connector: The PowerSyncBackendConnector to use
/// - crudThrottle: TimeInterval between CRUD operations. Defaults to ``ConnectOptions/DefaultCrudThrottle``.
/// - retryDelay: Delay TimeInterval between retries after failure. Defaults to ``ConnectOptions/DefaultRetryDelay``.
/// - params: Sync parameters from the client
///
/// Example usage:
/// ```swift
/// let params: JsonParam = [
/// "name": .string("John Doe"),
/// "age": .number(30),
/// "isStudent": .boolean(false)
/// ]
///
/// try await connect(
/// connector: connector,
/// crudThrottleMs: 2000,
/// retryDelayMs: 10000,
/// params: params
/// )
func connect(
connector: PowerSyncBackendConnectorProtocol,
crudThrottle: TimeInterval = 1,
retryDelay: TimeInterval = 5,
params: JsonParam = [:]
) async throws {
try await connect(
connector: connector,
options: ConnectOptions(
crudThrottle: crudThrottle,
retryDelay: retryDelay,
params: params
)
)
}
func disconnectAndClear() async throws {
try await disconnectAndClear(clearLocal: true, soft: false)
}
func disconnectAndClear(clearLocal: Bool) async throws {
try await disconnectAndClear(clearLocal: clearLocal, soft: false)
}
func disconnectAndClear(soft: Bool) async throws {
try await disconnectAndClear(clearLocal: true, soft: soft)
}
/// Get a batch of crud data to upload.
///
/// Returns nil if there is no data to upload.
///
/// Use this from the `PowerSyncBackendConnector.uploadData` callback.
///
/// Once the data have been successfully uploaded, call `CrudBatch.complete` before
/// requesting the next batch.
///
/// - Parameter limit: Maximum number of updates to return in a single batch. Default is 100.
///
/// This method does include transaction ids in the result, but does not group
/// data by transaction. One batch may contain data from multiple transactions,
/// and a single transaction may be split over multiple batches.
func getCrudBatch(limit: Int32 = 100) async throws -> CrudBatch? {
var entries = try await getAll(
sql: "SELECT id, tx_id, data FROM ps_crud ORDER BY id ASC LIMIT ?",
parameters: [Int64(limit + 1)],
mapper: CrudEntry.fromCursor
)
if entries.isEmpty {
return nil
}
let hasMore = entries.count > limit
if hasMore {
entries.removeLast()
}
return CrudBatch(
hasMore: hasMore,
crud: entries,
db: self
)
}
/// Obtains an async iterator of completed transactions with local writes against the database.
///
/// This is typically used from the ``PowerSyncBackendConnectorProtocol/uploadData(database:)`` callback.
/// Each entry emitted by teh returned flow is a full transaction containing all local writes made while that transaction was
/// active.
///
/// Unlike ``getNextCrudTransaction()``, which always returns the oldest transaction that hasn't been
/// ``CrudTransaction/complete()``d yet, this iterator can be used to upload multiple transactions.
/// Calling ``CrudTransaction/complete()`` will mark that and all prior transactions returned by this iterator as
/// completed.
///
/// This can be used to upload multiple transactions in a single batch, e.g. with
///
/// ```Swift
///
/// ```
func getCrudTransactions() -> CrudTransactions {
CrudTransactions(db: self)
}
}