From 63aae9d4b78b9648d1a80bbfb1343c1044077e6b Mon Sep 17 00:00:00 2001 From: Simon Binder Date: Mon, 15 Sep 2025 18:03:00 +0200 Subject: [PATCH 1/5] Update core extension to 0.4.6 --- CHANGELOG.md | 2 +- Package.resolved | 4 ++-- Package.swift | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 062cc31..a2894df 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,7 +2,7 @@ ## 1.5.1 -* Update core extension to 0.4.5 ([changelog](https://github.com/powersync-ja/powersync-sqlite-core/releases/tag/v0.4.5)) +* Update core extension to 0.4.6 ([changelog](https://github.com/powersync-ja/powersync-sqlite-core/releases/tag/v0.4.6)) * Additional Swift 6 Strict Concurrency Checking declarations added for remaining protocols. * Fix issue in legacy sync client where local writes made offline could have their upload delayed until a keepalive event was received. This could also cause downloaded updates to be delayed even further until all uploads were completed. diff --git a/Package.resolved b/Package.resolved index 8926049..a62a2d5 100644 --- a/Package.resolved +++ b/Package.resolved @@ -5,8 +5,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/powersync-ja/powersync-sqlite-core-swift.git", "state" : { - "revision" : "00776db5157c8648671b00e6673603144fafbfeb", - "version" : "0.4.5" + "revision" : "b2a81af14e9ad83393eb187bb02e62e6db8b5ad6", + "version" : "0.4.6" } } ], diff --git a/Package.swift b/Package.swift index aac1063..d9e7b73 100644 --- a/Package.swift +++ b/Package.swift @@ -45,7 +45,7 @@ if let corePath = localCoreExtension { // Not using a local build, so download from releases conditionalDependencies.append(.package( url: "https://github.com/powersync-ja/powersync-sqlite-core-swift.git", - exact: "0.4.5" + exact: "0.4.6" )) } From 522b0fd3566e0a69dc4364fdf3be225f17257097 Mon Sep 17 00:00:00 2001 From: Simon Binder Date: Tue, 16 Sep 2025 11:24:59 +0200 Subject: [PATCH 2/5] Update Kotlin SDK --- CHANGELOG.md | 8 +++- .../Kotlin/KotlinPowerSyncDatabaseImpl.swift | 18 +++----- Sources/PowerSync/Kotlin/KotlinTypes.swift | 1 + .../PowerSyncBackendConnectorAdapter.swift | 30 +++++-------- .../Protocol/PowerSyncDatabaseProtocol.swift | 42 +++++++++++++++---- .../Protocol/db/CrudTransaction.swift | 42 +++++++++++++++++++ Tests/PowerSyncTests/CrudTests.swift | 39 +++++++++++++++++ .../KotlinPowerSyncDatabaseImplTests.swift | 24 ++++------- 8 files changed, 147 insertions(+), 57 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a2894df..7ff781f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,8 +1,14 @@ # Changelog -## 1.5.1 +## 1.6.0 (unreleased) * Update core extension to 0.4.6 ([changelog](https://github.com/powersync-ja/powersync-sqlite-core/releases/tag/v0.4.6)) +* Add `getCrudTransactions()`, returning an async sequence of transactions. +* Compatibility with Swift 6.2 and XCode 26. + +## 1.5.1 + +* Update core extension to 0.4.5 ([changelog](https://github.com/powersync-ja/powersync-sqlite-core/releases/tag/v0.4.5)) * Additional Swift 6 Strict Concurrency Checking declarations added for remaining protocols. * Fix issue in legacy sync client where local writes made offline could have their upload delayed until a keepalive event was received. This could also cause downloaded updates to be delayed even further until all uploads were completed. diff --git a/Sources/PowerSync/Kotlin/KotlinPowerSyncDatabaseImpl.swift b/Sources/PowerSync/Kotlin/KotlinPowerSyncDatabaseImpl.swift index 83cdf6c..bc4fbad 100644 --- a/Sources/PowerSync/Kotlin/KotlinPowerSyncDatabaseImpl.swift +++ b/Sources/PowerSync/Kotlin/KotlinPowerSyncDatabaseImpl.swift @@ -48,10 +48,9 @@ final class KotlinPowerSyncDatabaseImpl: PowerSyncDatabaseProtocol, connector: PowerSyncBackendConnectorProtocol, options: ConnectOptions? ) async throws { - let connectorAdapter = PowerSyncBackendConnectorAdapter( - swiftBackendConnector: connector, - db: self - ) + let connectorAdapter = swiftBackendConnectorToPowerSyncConnector(connector: SwiftBackendConnectorBridge( + swiftBackendConnector: connector, db: self + )) let resolvedOptions = options ?? ConnectOptions() try await kotlinDatabase.connect( @@ -75,14 +74,9 @@ final class KotlinPowerSyncDatabaseImpl: PowerSyncDatabaseProtocol, batch: base ) } - - func getNextCrudTransaction() async throws -> CrudTransaction? { - guard let base = try await kotlinDatabase.getNextCrudTransaction() else { - return nil - } - return try KotlinCrudTransaction( - transaction: base - ) + + func getCrudTransactions() -> CrudTransactions { + return CrudTransactions(db: kotlinDatabase) } func getPowerSyncVersion() async throws -> String { diff --git a/Sources/PowerSync/Kotlin/KotlinTypes.swift b/Sources/PowerSync/Kotlin/KotlinTypes.swift index f85fce1..9c256a4 100644 --- a/Sources/PowerSync/Kotlin/KotlinTypes.swift +++ b/Sources/PowerSync/Kotlin/KotlinTypes.swift @@ -1,5 +1,6 @@ import PowerSyncKotlin +typealias KotlinSwiftBackendConnector = PowerSyncKotlin.SwiftBackendConnector typealias KotlinPowerSyncBackendConnector = PowerSyncKotlin.PowerSyncBackendConnector typealias KotlinPowerSyncCredentials = PowerSyncKotlin.PowerSyncCredentials typealias KotlinPowerSyncDatabase = PowerSyncKotlin.PowerSyncDatabase diff --git a/Sources/PowerSync/Kotlin/PowerSyncBackendConnectorAdapter.swift b/Sources/PowerSync/Kotlin/PowerSyncBackendConnectorAdapter.swift index 6507610..02b894b 100644 --- a/Sources/PowerSync/Kotlin/PowerSyncBackendConnectorAdapter.swift +++ b/Sources/PowerSync/Kotlin/PowerSyncBackendConnectorAdapter.swift @@ -1,9 +1,7 @@ import OSLog +import PowerSyncKotlin -final class PowerSyncBackendConnectorAdapter: KotlinPowerSyncBackendConnector, - // We need to declare this since we declared KotlinPowerSyncBackendConnector as @unchecked Sendable - @unchecked Sendable -{ +final class SwiftBackendConnectorBridge: KotlinSwiftBackendConnector, Sendable { let swiftBackendConnector: PowerSyncBackendConnectorProtocol let db: any PowerSyncDatabaseProtocol let logTag = "PowerSyncBackendConnector" @@ -15,31 +13,25 @@ final class PowerSyncBackendConnectorAdapter: KotlinPowerSyncBackendConnector, self.swiftBackendConnector = swiftBackendConnector self.db = db } - - override func __fetchCredentials() async throws -> KotlinPowerSyncCredentials? { + + func __fetchCredentials() async throws -> PowerSyncResult { do { let result = try await swiftBackendConnector.fetchCredentials() - return result?.kotlinCredentials + return PowerSyncResult.Success(value: result?.kotlinCredentials) } catch { db.logger.error("Error while fetching credentials", tag: logTag) - /// We can't use throwKotlinPowerSyncError here since the Kotlin connector - /// runs this in a Job - this seems to break the SKIEE error propagation. - /// returning nil here should still cause a retry - return nil + return PowerSyncResult.Failure(exception: error.toPowerSyncError()) } } - - override func __uploadData(database _: KotlinPowerSyncDatabase) async throws { + + func __uploadData() async throws -> PowerSyncResult { do { // Pass the Swift DB protocal to the connector - return try await swiftBackendConnector.uploadData(database: db) + try await swiftBackendConnector.uploadData(database: self.db) + return PowerSyncResult.Success(value: nil) } catch { db.logger.error("Error while uploading data: \(error)", tag: logTag) - // Relay the error to the Kotlin SDK - try throwKotlinPowerSyncError( - message: "Connector errored while uploading data: \(error.localizedDescription)", - cause: error.localizedDescription - ) + return PowerSyncResult.Failure(exception: error.toPowerSyncError()) } } } diff --git a/Sources/PowerSync/Protocol/PowerSyncDatabaseProtocol.swift b/Sources/PowerSync/Protocol/PowerSyncDatabaseProtocol.swift index 500dc10..00c549b 100644 --- a/Sources/PowerSync/Protocol/PowerSyncDatabaseProtocol.swift +++ b/Sources/PowerSync/Protocol/PowerSyncDatabaseProtocol.swift @@ -188,19 +188,24 @@ public protocol PowerSyncDatabaseProtocol: Queries, Sendable { /// 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) async throws -> CrudBatch? - - /// Get the next recorded transaction to upload. + + /// Obtains an async iterator of completed transactions with local writes against the database. /// - /// Returns nil if there is no data to upload. + /// 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. /// - /// Use this from the `PowerSyncBackendConnector.uploadData` callback. + /// 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. /// - /// Once the data have been successfully uploaded, call `CrudTransaction.complete` before - /// requesting the next transaction. + /// This can be used to upload multiple transactions in a single batch, e.g. with /// - /// 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? + /// ```Swift + /// + /// ``` + func getCrudTransactions() -> CrudTransactions /// Convenience method to get the current version of PowerSync. func getPowerSyncVersion() async throws -> String @@ -226,6 +231,25 @@ public protocol PowerSyncDatabaseProtocol: Queries, Sendable { } 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 self.getCrudTransactions() { + return transaction + } + + return nil + } + /// /// The connection is automatically re-opened if it fails for any reason. /// diff --git a/Sources/PowerSync/Protocol/db/CrudTransaction.swift b/Sources/PowerSync/Protocol/db/CrudTransaction.swift index ace3ea4..17100e4 100644 --- a/Sources/PowerSync/Protocol/db/CrudTransaction.swift +++ b/Sources/PowerSync/Protocol/db/CrudTransaction.swift @@ -1,4 +1,5 @@ import Foundation +import PowerSyncKotlin /// A transaction of client-side changes. public protocol CrudTransaction: Sendable { @@ -24,3 +25,44 @@ public extension CrudTransaction { ) } } + +/// A sequence of crud transactions in a PowerSync database. +/// +/// For details, see ``PowerSyncDatabaseProtocol/getCrudTransactions()``. +public struct CrudTransactions: AsyncSequence { + private let db: KotlinPowerSyncDatabase + + init(db: KotlinPowerSyncDatabase) { + self.db = db + } + + public func makeAsyncIterator() -> CrudTransactionIterator { + let kotlinIterator = errorHandledCrudTransactions(db: self.db).makeAsyncIterator() + return CrudTransactionIterator(inner: kotlinIterator) + } + + public struct CrudTransactionIterator: AsyncIteratorProtocol { + public typealias Element = any CrudTransaction + + private var inner: PowerSyncKotlin.SkieSwiftFlowIterator + + internal init(inner: PowerSyncKotlin.SkieSwiftFlowIterator) { + self.inner = inner + } + + public mutating func next() async throws -> (any CrudTransaction)? { + if let innerTx = await self.inner.next() { + if let success = innerTx as? PowerSyncResult.Success { + let tx = success.value as! PowerSyncKotlin.CrudTransaction + return try KotlinCrudTransaction(transaction: tx) + } else if let failure = innerTx as? PowerSyncResult.Failure { + try throwPowerSyncException(exception: failure.exception) + } + + fatalError("unreachable") + } else { + return nil + } + } + } +} diff --git a/Tests/PowerSyncTests/CrudTests.swift b/Tests/PowerSyncTests/CrudTests.swift index 5b303d6..c315b38 100644 --- a/Tests/PowerSyncTests/CrudTests.swift +++ b/Tests/PowerSyncTests/CrudTests.swift @@ -199,4 +199,43 @@ final class CrudTests: XCTestCase { let finalValidationBatch = try await database.getCrudBatch(limit: 100) XCTAssertNil(finalValidationBatch) } + + func testCrudTransactions() async throws { + func insertInTransaction(size: Int) async throws { + try await database.writeTransaction { tx in + for _ in 0 ..< size { + try tx.execute( + sql: "INSERT INTO users (id, name, email) VALUES (uuid(), null, null)", + parameters: [] + ) + } + } + } + + // Before inserting any data, the iterator should be empty. + for try await _ in database.getCrudTransactions() { + XCTFail("Unexpected transaction") + } + + try await insertInTransaction(size: 5) + try await insertInTransaction(size: 10) + try await insertInTransaction(size: 15) + + var batch = [CrudEntry]() + var lastTx: CrudTransaction? = nil + for try await tx in database.getCrudTransactions() { + batch.append(contentsOf: tx.crud) + lastTx = tx + + if (batch.count >= 10) { + break + } + } + + XCTAssertEqual(batch.count, 15) + try await lastTx!.complete() + + let finalTx = try await database.getNextCrudTransaction() + XCTAssertEqual(finalTx!.crud.count, 15) + } } diff --git a/Tests/PowerSyncTests/Kotlin/KotlinPowerSyncDatabaseImplTests.swift b/Tests/PowerSyncTests/Kotlin/KotlinPowerSyncDatabaseImplTests.swift index ebecd44..0c3b48e 100644 --- a/Tests/PowerSyncTests/Kotlin/KotlinPowerSyncDatabaseImplTests.swift +++ b/Tests/PowerSyncTests/Kotlin/KotlinPowerSyncDatabaseImplTests.swift @@ -42,8 +42,7 @@ final class KotlinPowerSyncDatabaseImplTests: XCTestCase { XCTFail("Expected an error to be thrown") } catch { XCTAssertEqual(error.localizedDescription, """ - error while compiling: INSERT INTO usersfail (id, name, email) VALUES (?, ?, ?) - no such table: usersfail + SqliteException(1): SQL logic error, no such table: usersfail for SQL: INSERT INTO usersfail (id, name, email) VALUES (?, ?, ?) """) } } @@ -85,8 +84,7 @@ final class KotlinPowerSyncDatabaseImplTests: XCTestCase { XCTFail("Expected an error to be thrown") } catch { XCTAssertEqual(error.localizedDescription, """ - error while compiling: SELECT id, name, email FROM usersfail WHERE id = ? - no such table: usersfail + SqliteException(1): SQL logic error, no such table: usersfail for SQL: SELECT id, name, email FROM usersfail WHERE id = ? """) } } @@ -131,8 +129,7 @@ final class KotlinPowerSyncDatabaseImplTests: XCTestCase { XCTFail("Expected an error to be thrown") } catch { XCTAssertEqual(error.localizedDescription, """ - error while compiling: SELECT id, name, email FROM usersfail WHERE id = ? - no such table: usersfail + SqliteException(1): SQL logic error, no such table: usersfail for SQL: SELECT id, name, email FROM usersfail WHERE id = ? """) } } @@ -197,8 +194,7 @@ final class KotlinPowerSyncDatabaseImplTests: XCTestCase { XCTFail("Expected an error to be thrown") } catch { XCTAssertEqual(error.localizedDescription, """ - error while compiling: SELECT id, name, email FROM usersfail WHERE id = ? - no such table: usersfail + SqliteException(1): SQL logic error, no such table: usersfail for SQL: SELECT id, name, email FROM usersfail WHERE id = ? """) } } @@ -281,8 +277,7 @@ final class KotlinPowerSyncDatabaseImplTests: XCTestCase { XCTFail("Expected an error to be thrown") } catch { XCTAssertEqual(error.localizedDescription, """ - error while compiling: EXPLAIN SELECT name FROM usersfail ORDER BY id - no such table: usersfail + SqliteException(1): SQL logic error, no such table: usersfail for SQL: EXPLAIN SELECT name FROM usersfail ORDER BY id """) } } @@ -371,8 +366,7 @@ final class KotlinPowerSyncDatabaseImplTests: XCTestCase { } } catch { XCTAssertEqual(error.localizedDescription, """ - error while compiling: INSERT INTO usersfail (id, name, email) VALUES (?, ?, ?) - no such table: usersfail + SqliteException(1): SQL logic error, no such table: usersfail for SQL: INSERT INTO usersfail (id, name, email) VALUES (?, ?, ?) """) } } @@ -392,8 +386,7 @@ final class KotlinPowerSyncDatabaseImplTests: XCTestCase { } } catch { XCTAssertEqual(error.localizedDescription, """ - error while compiling: INSERT INTO usersfail (id, name, email) VALUES (?, ?, ?) - no such table: usersfail + SqliteException(1): SQL logic error, no such table: usersfail for SQL: INSERT INTO usersfail (id, name, email) VALUES (?, ?, ?) """) } @@ -436,8 +429,7 @@ final class KotlinPowerSyncDatabaseImplTests: XCTestCase { } } catch { XCTAssertEqual(error.localizedDescription, """ - error while compiling: SELECT COUNT(*) FROM usersfail - no such table: usersfail + SqliteException(1): SQL logic error, no such table: usersfail for SQL: SELECT COUNT(*) FROM usersfail """) } } From 6089a031f34456c961d50030bc5d2003eef86c58 Mon Sep 17 00:00:00 2001 From: Simon Binder Date: Thu, 18 Sep 2025 14:19:25 +0200 Subject: [PATCH 3/5] Avoid importing Kotlin in interface --- Package.swift | 4 +- .../Kotlin/KotlinPowerSyncDatabaseImpl.swift | 4 +- .../Kotlin/db/KotlinCrudTransactions.swift | 39 +++++++++++++++++ .../Protocol/PowerSyncDatabaseProtocol.swift | 2 +- .../Protocol/db/CrudTransaction.swift | 42 ++----------------- 5 files changed, 48 insertions(+), 43 deletions(-) create mode 100644 Sources/PowerSync/Kotlin/db/KotlinCrudTransactions.swift diff --git a/Package.swift b/Package.swift index d9e7b73..bd8db2e 100644 --- a/Package.swift +++ b/Package.swift @@ -32,8 +32,8 @@ if let kotlinSdkPath = localKotlinSdkOverride { // Not using a local build, so download from releases conditionalTargets.append(.binaryTarget( name: "PowerSyncKotlin", - url: "https://github.com/powersync-ja/powersync-kotlin/releases/download/v1.5.1/PowersyncKotlinRelease.zip", - checksum: "3a2de1863d2844d49cebf4428d0ab49956ba384dcab9f3cc2ddbc7836013c434" + url: "https://github.com/powersync-ja/powersync-kotlin/releases/download/v1.6.0/PowersyncKotlinRelease.zip", + checksum: "4f70331c11e30625eecf4ebcebe7b562e2e0165774890d2a43480ebc3a9081cc" )) } diff --git a/Sources/PowerSync/Kotlin/KotlinPowerSyncDatabaseImpl.swift b/Sources/PowerSync/Kotlin/KotlinPowerSyncDatabaseImpl.swift index bc4fbad..10bbd97 100644 --- a/Sources/PowerSync/Kotlin/KotlinPowerSyncDatabaseImpl.swift +++ b/Sources/PowerSync/Kotlin/KotlinPowerSyncDatabaseImpl.swift @@ -75,8 +75,8 @@ final class KotlinPowerSyncDatabaseImpl: PowerSyncDatabaseProtocol, ) } - func getCrudTransactions() -> CrudTransactions { - return CrudTransactions(db: kotlinDatabase) + func getCrudTransactions() -> any CrudTransactions { + return KotlinCrudTransactions(db: kotlinDatabase) } func getPowerSyncVersion() async throws -> String { diff --git a/Sources/PowerSync/Kotlin/db/KotlinCrudTransactions.swift b/Sources/PowerSync/Kotlin/db/KotlinCrudTransactions.swift new file mode 100644 index 0000000..866e76e --- /dev/null +++ b/Sources/PowerSync/Kotlin/db/KotlinCrudTransactions.swift @@ -0,0 +1,39 @@ +import PowerSyncKotlin + +struct KotlinCrudTransactions: CrudTransactions { + typealias Element = KotlinCrudTransaction + + private let db: KotlinPowerSyncDatabase + + init(db: KotlinPowerSyncDatabase) { + self.db = db + } + + public func makeAsyncIterator() -> CrudTransactionIterator { + let kotlinIterator = errorHandledCrudTransactions(db: self.db).makeAsyncIterator() + return CrudTransactionIterator(inner: kotlinIterator) + } + + struct CrudTransactionIterator: CrudTransactionsIterator { + private var inner: PowerSyncKotlin.SkieSwiftFlowIterator + + internal init(inner: PowerSyncKotlin.SkieSwiftFlowIterator) { + self.inner = inner + } + + public mutating func next() async throws -> KotlinCrudTransaction? { + if let innerTx = await self.inner.next() { + if let success = innerTx as? PowerSyncResult.Success { + let tx = success.value as! PowerSyncKotlin.CrudTransaction + return try KotlinCrudTransaction(transaction: tx) + } else if let failure = innerTx as? PowerSyncResult.Failure { + try throwPowerSyncException(exception: failure.exception) + } + + fatalError("unreachable") + } else { + return nil + } + } + } +} diff --git a/Sources/PowerSync/Protocol/PowerSyncDatabaseProtocol.swift b/Sources/PowerSync/Protocol/PowerSyncDatabaseProtocol.swift index 00c549b..cd112a5 100644 --- a/Sources/PowerSync/Protocol/PowerSyncDatabaseProtocol.swift +++ b/Sources/PowerSync/Protocol/PowerSyncDatabaseProtocol.swift @@ -205,7 +205,7 @@ public protocol PowerSyncDatabaseProtocol: Queries, Sendable { /// ```Swift /// /// ``` - func getCrudTransactions() -> CrudTransactions + func getCrudTransactions() -> any CrudTransactions /// Convenience method to get the current version of PowerSync. func getPowerSyncVersion() async throws -> String diff --git a/Sources/PowerSync/Protocol/db/CrudTransaction.swift b/Sources/PowerSync/Protocol/db/CrudTransaction.swift index 17100e4..8a7dacf 100644 --- a/Sources/PowerSync/Protocol/db/CrudTransaction.swift +++ b/Sources/PowerSync/Protocol/db/CrudTransaction.swift @@ -1,5 +1,4 @@ import Foundation -import PowerSyncKotlin /// A transaction of client-side changes. public protocol CrudTransaction: Sendable { @@ -29,40 +28,7 @@ public extension CrudTransaction { /// A sequence of crud transactions in a PowerSync database. /// /// For details, see ``PowerSyncDatabaseProtocol/getCrudTransactions()``. -public struct CrudTransactions: AsyncSequence { - private let db: KotlinPowerSyncDatabase - - init(db: KotlinPowerSyncDatabase) { - self.db = db - } - - public func makeAsyncIterator() -> CrudTransactionIterator { - let kotlinIterator = errorHandledCrudTransactions(db: self.db).makeAsyncIterator() - return CrudTransactionIterator(inner: kotlinIterator) - } - - public struct CrudTransactionIterator: AsyncIteratorProtocol { - public typealias Element = any CrudTransaction - - private var inner: PowerSyncKotlin.SkieSwiftFlowIterator - - internal init(inner: PowerSyncKotlin.SkieSwiftFlowIterator) { - self.inner = inner - } - - public mutating func next() async throws -> (any CrudTransaction)? { - if let innerTx = await self.inner.next() { - if let success = innerTx as? PowerSyncResult.Success { - let tx = success.value as! PowerSyncKotlin.CrudTransaction - return try KotlinCrudTransaction(transaction: tx) - } else if let failure = innerTx as? PowerSyncResult.Failure { - try throwPowerSyncException(exception: failure.exception) - } - - fatalError("unreachable") - } else { - return nil - } - } - } -} +public protocol CrudTransactions: AsyncSequence where Element: CrudTransaction, AsyncIterator: CrudTransactionsIterator {} + +/// The iterator returned by ``CrudTransactions``. +public protocol CrudTransactionsIterator: AsyncIteratorProtocol where Element: CrudTransaction {} From 4328d4085774b774a28c2175cafc9fa8e255d6d3 Mon Sep 17 00:00:00 2001 From: Simon Binder Date: Thu, 18 Sep 2025 14:20:10 +0200 Subject: [PATCH 4/5] Prepare release --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7ff781f..eb71457 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,6 @@ # Changelog -## 1.6.0 (unreleased) +## 1.6.0 * Update core extension to 0.4.6 ([changelog](https://github.com/powersync-ja/powersync-sqlite-core/releases/tag/v0.4.6)) * Add `getCrudTransactions()`, returning an async sequence of transactions. From c00168bd866ee5f6359ef1f40c4332beb552630b Mon Sep 17 00:00:00 2001 From: Simon Binder Date: Thu, 18 Sep 2025 14:28:11 +0200 Subject: [PATCH 5/5] Fix example --- .../xcshareddata/swiftpm/Package.resolved | 6 +++--- Demo/PowerSyncExample/PowerSync/FtsSetup.swift | 3 ++- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/Demo/PowerSyncExample.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/Demo/PowerSyncExample.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 547bdd5..2aa1e82 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" : "33297127250b66812faa920958a24bae46bf9e9d1c38ea6b84ca413efaf16afd", + "originHash" : "2d885a1b46f17f9239b7876e3889168a6de98024718f2d7af03aede290c8a86a", "pins" : [ { "identity" : "anycodable", @@ -15,8 +15,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/powersync-ja/powersync-sqlite-core-swift.git", "state" : { - "revision" : "3396dd7eb9d4264b19e3d95bfe0d77347826f4c2", - "version" : "0.4.4" + "revision" : "b2a81af14e9ad83393eb187bb02e62e6db8b5ad6", + "version" : "0.4.6" } }, { diff --git a/Demo/PowerSyncExample/PowerSync/FtsSetup.swift b/Demo/PowerSyncExample/PowerSync/FtsSetup.swift index 4d23672..40c195f 100644 --- a/Demo/PowerSyncExample/PowerSync/FtsSetup.swift +++ b/Demo/PowerSyncExample/PowerSync/FtsSetup.swift @@ -159,10 +159,11 @@ func configureFts(db: PowerSyncDatabaseProtocol, schema: Schema) async throws { // --- Execute all generated SQL statements --- if !allSqlStatements.isEmpty { + let resultingStatements: [String] = allSqlStatements do { print("[FTS] Executing \(allSqlStatements.count) SQL statements in a transaction...") _ = try await db.writeTransaction { transaction in - for sql in allSqlStatements { + for sql in resultingStatements { print("[FTS] Executing SQL:\n\(sql)") _ = try transaction.execute(sql: sql, parameters: []) }