diff --git a/Demo/PowerSyncExample.xcodeproj/project.pbxproj b/Demo/PowerSyncExample.xcodeproj/project.pbxproj index 6ebc829..d0ebf2c 100644 --- a/Demo/PowerSyncExample.xcodeproj/project.pbxproj +++ b/Demo/PowerSyncExample.xcodeproj/project.pbxproj @@ -14,7 +14,7 @@ 6A7315882B9854220004CB17 /* PowerSyncExampleApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6A7315872B9854220004CB17 /* PowerSyncExampleApp.swift */; }; 6A73158C2B9854240004CB17 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 6A73158B2B9854240004CB17 /* Assets.xcassets */; }; 6A73158F2B9854240004CB17 /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 6A73158E2B9854240004CB17 /* Preview Assets.xcassets */; }; - 6A7315BB2B98BDD30004CB17 /* PowerSyncManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6A7315BA2B98BDD30004CB17 /* PowerSyncManager.swift */; }; + 6A7315BB2B98BDD30004CB17 /* SystemManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6A7315BA2B98BDD30004CB17 /* SystemManager.swift */; }; 6A9668FE2B9EE4FE00B05DCF /* Auth in Frameworks */ = {isa = PBXBuildFile; productRef = 6A9668FD2B9EE4FE00B05DCF /* Auth */; }; 6A9669002B9EE4FE00B05DCF /* PostgREST in Frameworks */ = {isa = PBXBuildFile; productRef = 6A9668FF2B9EE4FE00B05DCF /* PostgREST */; }; 6A9669022B9EE69500B05DCF /* Supabase in Frameworks */ = {isa = PBXBuildFile; productRef = 6A9669012B9EE69500B05DCF /* Supabase */; }; @@ -64,7 +64,7 @@ 6A7315872B9854220004CB17 /* PowerSyncExampleApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PowerSyncExampleApp.swift; sourceTree = ""; }; 6A73158B2B9854240004CB17 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 6A73158E2B9854240004CB17 /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = ""; }; - 6A7315BA2B98BDD30004CB17 /* PowerSyncManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PowerSyncManager.swift; sourceTree = ""; }; + 6A7315BA2B98BDD30004CB17 /* SystemManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SystemManager.swift; sourceTree = ""; }; 6A9669032B9EE6FA00B05DCF /* SignInScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SignInScreen.swift; sourceTree = ""; }; 6ABD78662B9F2B4800558A41 /* RootView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RootView.swift; sourceTree = ""; }; 6ABD786A2B9F2C1500558A41 /* TodoListView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TodoListView.swift; sourceTree = ""; }; @@ -218,7 +218,7 @@ B65C4D6F2C60D58500176007 /* PowerSync */ = { isa = PBXGroup; children = ( - 6A7315BA2B98BDD30004CB17 /* PowerSyncManager.swift */, + 6A7315BA2B98BDD30004CB17 /* SystemManager.swift */, 6A4AD3842B9EE763005CBFD4 /* SupabaseConnector.swift */, 6ABD78772B9F2D2800558A41 /* Schema.swift */, B66658642C62314B00159A81 /* Lists.swift */, @@ -567,7 +567,7 @@ B66658632C621CA700159A81 /* AddTodoListView.swift in Sources */, B666585D2C620E9E00159A81 /* WifiIcon.swift in Sources */, 6A9669042B9EE6FA00B05DCF /* SignInScreen.swift in Sources */, - 6A7315BB2B98BDD30004CB17 /* PowerSyncManager.swift in Sources */, + 6A7315BB2B98BDD30004CB17 /* SystemManager.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/Demo/PowerSyncExample.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/Demo/PowerSyncExample.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 2fdd706..3d2f3e8 100644 --- a/Demo/PowerSyncExample.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/Demo/PowerSyncExample.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -1,5 +1,5 @@ { - "originHash" : "5d7fb7f47b01e814cbc6b4a65dfe62c7af5a96a435a0288b747750c370fcd28a", + "originHash" : "25b8cd5d97789d7e497d6a5e0b04419a426018d83f0e80ab6817b213aa976748", "pins" : [ { "identity" : "anycodable", @@ -10,15 +10,6 @@ "version" : "0.6.7" } }, - { - "identity" : "powersync-kotlin", - "kind" : "remoteSourceControl", - "location" : "https://github.com/powersync-ja/powersync-kotlin.git", - "state" : { - "revision" : "4186fa9a2004a4bc85a22c3f37bce4f3ebd4ff81", - "version" : "1.0.0-BETA5.0" - } - }, { "identity" : "powersync-sqlite-core-swift", "kind" : "remoteSourceControl", diff --git a/Demo/PowerSyncExample/PowerSync/SupabaseConnector.swift b/Demo/PowerSyncExample/PowerSync/SupabaseConnector.swift index b8333b4..c399441 100644 --- a/Demo/PowerSyncExample/PowerSync/SupabaseConnector.swift +++ b/Demo/PowerSyncExample/PowerSync/SupabaseConnector.swift @@ -2,7 +2,6 @@ import Auth import SwiftUI import Supabase import PowerSyncSwift -import PowerSync import AnyCodable @Observable @@ -16,7 +15,6 @@ class SupabaseConnector: PowerSyncBackendConnector { override init() { super.init() - observeAuthStateChangesTask = Task { [weak self] in guard let self = self else { return } @@ -49,7 +47,7 @@ class SupabaseConnector: PowerSyncBackendConnector { return PowerSyncCredentials(endpoint: self.powerSyncEndpoint, token: token, userId: currentUserID) } - override func uploadData(database: PowerSyncDatabase) async throws { + override func uploadData(database: PowerSyncDatabaseProtocol) async throws { guard let transaction = try await database.getNextCrudTransaction() else { return } @@ -75,7 +73,7 @@ class SupabaseConnector: PowerSyncBackendConnector { } } - try await transaction.complete.invoke(p1: nil) + _ = try await transaction.complete.invoke(p1: nil) } catch { print("Data upload error - retrying last entry: \(lastEntry!), \(error)") diff --git a/Package.resolved b/Package.resolved index b070bf8..a325876 100644 --- a/Package.resolved +++ b/Package.resolved @@ -5,8 +5,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/powersync-ja/powersync-kotlin.git", "state" : { - "revision" : "4186fa9a2004a4bc85a22c3f37bce4f3ebd4ff81", - "version" : "1.0.0-BETA5.0" + "revision" : "7fb870f4530e5629b11f1a9b079644b200385985", + "version" : "1.0.0-BETA6.0" } }, { diff --git a/Package.swift b/Package.swift index a8e6a22..6f6801b 100644 --- a/Package.swift +++ b/Package.swift @@ -17,7 +17,7 @@ let package = Package( targets: ["PowerSyncSwift"]), ], dependencies: [ - .package(url: "https://github.com/powersync-ja/powersync-kotlin.git", exact: "1.0.0-BETA5.0"), + .package(url: "https://github.com/powersync-ja/powersync-kotlin.git", exact: "1.0.0-BETA6.0"), .package(url: "https://github.com/powersync-ja/powersync-sqlite-core-swift.git", "0.3.1"..<"0.4.0"), ], targets: [ diff --git a/Sources/PowerSyncSwift/Kotlin/KotlinPowerSyncDatabaseImpl.swift b/Sources/PowerSyncSwift/Kotlin/KotlinPowerSyncDatabaseImpl.swift index 6fd3b22..5bfb048 100644 --- a/Sources/PowerSyncSwift/Kotlin/KotlinPowerSyncDatabaseImpl.swift +++ b/Sources/PowerSyncSwift/Kotlin/KotlinPowerSyncDatabaseImpl.swift @@ -2,10 +2,10 @@ import Foundation import PowerSync final class KotlinPowerSyncDatabaseImpl: PowerSyncDatabaseProtocol { - private let kmpDatabase: PowerSync.PowerSyncDatabase + private let kotlinDatabase: PowerSync.PowerSyncDatabase var currentStatus: SyncStatus { - get { kmpDatabase.currentStatus } + get { kotlinDatabase.currentStatus } } init( @@ -13,15 +13,19 @@ final class KotlinPowerSyncDatabaseImpl: PowerSyncDatabaseProtocol { dbFilename: String ) { let factory = PowerSync.DatabaseDriverFactory() - self.kmpDatabase = PowerSyncDatabase( + self.kotlinDatabase = PowerSyncDatabase( factory: factory, schema: KotlinAdapter.Schema.toKotlin(schema), dbFilename: dbFilename ) } - + + init(kotlinDatabase: KotlinPowerSyncDatabase) { + self.kotlinDatabase = kotlinDatabase + } + func waitForFirstSync() async throws { - try await kmpDatabase.waitForFirstSync() + try await kotlinDatabase.waitForFirstSync() } func connect( @@ -30,8 +34,10 @@ final class KotlinPowerSyncDatabaseImpl: PowerSyncDatabaseProtocol { retryDelayMs: Int64 = 5000, params: [String: JsonParam?] = [:] ) async throws { - try await kmpDatabase.connect( - connector: connector, + let connectorProxy = PowerSyncBackendConnectorAdapter(swiftBackendConnector: connector) + + try await kotlinDatabase.connect( + connector: connectorProxy, crudThrottleMs: crudThrottleMs, retryDelayMs: retryDelayMs, params: params @@ -39,27 +45,27 @@ final class KotlinPowerSyncDatabaseImpl: PowerSyncDatabaseProtocol { } func getCrudBatch(limit: Int32 = 100) async throws -> CrudBatch? { - try await kmpDatabase.getCrudBatch(limit: limit) + try await kotlinDatabase.getCrudBatch(limit: limit) } func getNextCrudTransaction() async throws -> CrudTransaction? { - try await kmpDatabase.getNextCrudTransaction() + try await kotlinDatabase.getNextCrudTransaction() } func getPowerSyncVersion() async throws -> String { - try await kmpDatabase.getPowerSyncVersion() + try await kotlinDatabase.getPowerSyncVersion() } func disconnect() async throws { - try await kmpDatabase.disconnect() + try await kotlinDatabase.disconnect() } func disconnectAndClear(clearLocal: Bool = true) async throws { - try await kmpDatabase.disconnectAndClear(clearLocal: clearLocal) + try await kotlinDatabase.disconnectAndClear(clearLocal: clearLocal) } func execute(sql: String, parameters: [Any]?) async throws -> Int64 { - Int64(truncating: try await kmpDatabase.execute(sql: sql, parameters: parameters)) + Int64(truncating: try await kotlinDatabase.execute(sql: sql, parameters: parameters)) } func get( @@ -67,7 +73,7 @@ final class KotlinPowerSyncDatabaseImpl: PowerSyncDatabaseProtocol { parameters: [Any]?, mapper: @escaping (SqlCursor) -> RowType ) async throws -> RowType { - try await kmpDatabase.get( + try await kotlinDatabase.get( sql: sql, parameters: parameters, mapper: mapper @@ -79,7 +85,7 @@ final class KotlinPowerSyncDatabaseImpl: PowerSyncDatabaseProtocol { parameters: [Any]?, mapper: @escaping (SqlCursor) -> RowType ) async throws -> [RowType] { - try await kmpDatabase.getAll( + try await kotlinDatabase.getAll( sql: sql, parameters: parameters, mapper: mapper @@ -91,7 +97,7 @@ final class KotlinPowerSyncDatabaseImpl: PowerSyncDatabaseProtocol { parameters: [Any]?, mapper: @escaping (SqlCursor) -> RowType ) async throws -> RowType? { - try await kmpDatabase.getOptional( + try await kotlinDatabase.getOptional( sql: sql, parameters: parameters, mapper: mapper @@ -105,7 +111,7 @@ final class KotlinPowerSyncDatabaseImpl: PowerSyncDatabaseProtocol { ) -> AsyncStream<[RowType]> { AsyncStream { continuation in Task { - for await values in self.kmpDatabase.watch( + for await values in self.kotlinDatabase.watch( sql: sql, parameters: parameters, mapper: mapper @@ -118,29 +124,29 @@ final class KotlinPowerSyncDatabaseImpl: PowerSyncDatabaseProtocol { } func writeTransaction(callback: @escaping (any PowerSyncTransactionProtocol) async throws -> R) async throws -> R { - let wrappedCallback = SuspendTaskWrapper { [kmpDatabase] in + let wrappedCallback = SuspendTaskWrapper { [kotlinDatabase] in // Create a wrapper that converts the KMP transaction to our Swift protocol - if let kmpTransaction = kmpDatabase as? PowerSyncTransactionProtocol { + if let kmpTransaction = kotlinDatabase as? PowerSyncTransactionProtocol { return try await callback(kmpTransaction) } else { throw PowerSyncError.invalidTransaction } } - return try await kmpDatabase.writeTransaction(callback: wrappedCallback) as! R + return try await kotlinDatabase.writeTransaction(callback: wrappedCallback) as! R } func readTransaction(callback: @escaping (any PowerSyncTransactionProtocol) async throws -> R) async throws -> R { - let wrappedCallback = SuspendTaskWrapper { [kmpDatabase] in + let wrappedCallback = SuspendTaskWrapper { [kotlinDatabase] in // Create a wrapper that converts the KMP transaction to our Swift protocol - if let kmpTransaction = kmpDatabase as? PowerSyncTransactionProtocol { + if let kmpTransaction = kotlinDatabase as? PowerSyncTransactionProtocol { return try await callback(kmpTransaction) } else { throw PowerSyncError.invalidTransaction } } - return try await kmpDatabase.readTransaction(callback: wrappedCallback) as! R + return try await kotlinDatabase.readTransaction(callback: wrappedCallback) as! R } } @@ -156,7 +162,7 @@ class SuspendTaskWrapper: KotlinSuspendFunction1 { } @MainActor - func invoke(p1: Any?, completionHandler: @escaping (Any?, Error?) -> Void) { + func __invoke(p1: Any?, completionHandler: @escaping (Any?, Error?) -> Void) { Task { do { let result = try await self.handle() diff --git a/Sources/PowerSyncSwift/Kotlin/KotlinTypes.swift b/Sources/PowerSyncSwift/Kotlin/KotlinTypes.swift index 1477e48..80ce44f 100644 --- a/Sources/PowerSyncSwift/Kotlin/KotlinTypes.swift +++ b/Sources/PowerSyncSwift/Kotlin/KotlinTypes.swift @@ -1,11 +1,12 @@ import PowerSync -public typealias PowerSyncBackendConnector = PowerSync.PowerSyncBackendConnector +public typealias KotlinPowerSyncBackendConnector = PowerSync.PowerSyncBackendConnector public typealias CrudEntry = PowerSync.CrudEntry public typealias CrudBatch = PowerSync.CrudBatch public typealias SyncStatus = PowerSync.SyncStatus public typealias SqlCursor = PowerSync.RuntimeSqlCursor public typealias JsonParam = PowerSync.JsonParam public typealias CrudTransaction = PowerSync.CrudTransaction -public typealias PowerSyncCredentials = PowerSync.PowerSyncCredentials +public typealias KotlinPowerSyncCredentials = PowerSync.PowerSyncCredentials +public typealias KotlinPowerSyncDatabase = PowerSync.PowerSyncDatabase diff --git a/Sources/PowerSyncSwift/PowerSyncBackendConnector.swift b/Sources/PowerSyncSwift/PowerSyncBackendConnector.swift new file mode 100644 index 0000000..6753f2b --- /dev/null +++ b/Sources/PowerSyncSwift/PowerSyncBackendConnector.swift @@ -0,0 +1,16 @@ +public protocol PowerSyncBackendConnectorProtocol { + func uploadData(database: PowerSyncDatabaseProtocol) async throws + + func fetchCredentials() async throws -> PowerSyncCredentials? +} + +open class PowerSyncBackendConnector: PowerSyncBackendConnectorProtocol { + public init() {} + + open func uploadData(database: PowerSyncDatabaseProtocol) async throws {} + + open func fetchCredentials() async throws -> PowerSyncCredentials? { + return nil + } +} + diff --git a/Sources/PowerSyncSwift/PowerSyncBackendConnectorAdapter.swift b/Sources/PowerSyncSwift/PowerSyncBackendConnectorAdapter.swift new file mode 100644 index 0000000..df2ce63 --- /dev/null +++ b/Sources/PowerSyncSwift/PowerSyncBackendConnectorAdapter.swift @@ -0,0 +1,18 @@ +class PowerSyncBackendConnectorAdapter: KotlinPowerSyncBackendConnector { + let swiftBackendConnector: PowerSyncBackendConnector + + init( + swiftBackendConnector: PowerSyncBackendConnector + ) { + self.swiftBackendConnector = swiftBackendConnector + } + + override func __fetchCredentials() async throws -> KotlinPowerSyncCredentials? { + try await swiftBackendConnector.fetchCredentials()?.kotlinCredentials + } + + override func __uploadData(database: KotlinPowerSyncDatabase) async throws { + let swiftDatabase = KotlinPowerSyncDatabaseImpl(kotlinDatabase: database) + try await swiftBackendConnector.uploadData(database: swiftDatabase) + } +} diff --git a/Sources/PowerSyncSwift/PowerSyncCredentials.swift b/Sources/PowerSyncSwift/PowerSyncCredentials.swift new file mode 100644 index 0000000..03727e1 --- /dev/null +++ b/Sources/PowerSyncSwift/PowerSyncCredentials.swift @@ -0,0 +1,38 @@ +import Foundation + +public struct PowerSyncCredentials: Codable { + /// PowerSync endpoint, e.g. "https://myinstance.powersync.co". + public let endpoint: String + + /// Temporary token to authenticate against the service. + public let token: String + + /// User ID. + public let userId: String? + + public init(endpoint: String, token: String, userId: String?) { + self.endpoint = endpoint + self.token = token + self.userId = userId + } + + internal init(kotlin: KotlinPowerSyncCredentials) { + self.endpoint = kotlin.endpoint + self.token = kotlin.token + self.userId = kotlin.userId + } + + internal var kotlinCredentials: KotlinPowerSyncCredentials { + return KotlinPowerSyncCredentials(endpoint: endpoint, token: token, userId: userId) + } + + public func endpointUri(path: String) -> String { + return "\(endpoint)/\(path)" + } +} + +extension PowerSyncCredentials: CustomStringConvertible { + public var description: String { + return "PowerSyncCredentials" + } +} diff --git a/Sources/PowerSyncSwift/PowerSyncDatabase.swift b/Sources/PowerSyncSwift/PowerSyncDatabase.swift index 600e346..454e7e3 100644 --- a/Sources/PowerSyncSwift/PowerSyncDatabase.swift +++ b/Sources/PowerSyncSwift/PowerSyncDatabase.swift @@ -8,7 +8,6 @@ public let DEFAULT_DB_FILENAME = "powersync.db" /// - schema: The database schema /// - dbFilename: The database filename. Defaults to "powersync.db" /// - Returns: A configured PowerSyncDatabase instance -@MainActor public func PowerSyncDatabase( schema: Schema, dbFilename: String = DEFAULT_DB_FILENAME