Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
48 commits
Select commit Hold shift + click to select a range
991bd57
Implement crud batches in Swift instead of Kotlin
simolus3 Apr 15, 2026
a2d0439
Changelog, format
simolus3 Apr 15, 2026
7a09324
AI feedback
simolus3 Apr 15, 2026
d0cecac
typo
simolus3 Apr 15, 2026
18eb196
Scaffold Swift sync client
simolus3 Apr 15, 2026
7b7e5fc
Drive sync process from Swift
simolus3 Apr 16, 2026
0bf8744
Implement schema serialization in Swift
simolus3 Apr 20, 2026
9aa2776
AI feedback
simolus3 Apr 20, 2026
7949c02
Restrict CI timeouts
simolus3 Apr 21, 2026
f8c57ff
Merge branch 'serializable-schema' into swift-sync-client
simolus3 Apr 21, 2026
dbecd32
Support raw tables
simolus3 Apr 21, 2026
6a6bf2c
Set request headers
simolus3 Apr 21, 2026
d561a2a
Support network logging
simolus3 Apr 21, 2026
60f7fb2
Support sync streams
simolus3 Apr 21, 2026
e7cf25f
Fix tests
simolus3 Apr 21, 2026
4b45996
Trigger crud uploads
simolus3 Apr 21, 2026
bbf9027
Use latest xcode
simolus3 Apr 21, 2026
1ab3ab9
Avoid updating status on end of upload attempt
simolus3 Apr 21, 2026
3e0abdb
Support old platform versions again
simolus3 Apr 21, 2026
6384f61
Test on old macOS
simolus3 Apr 21, 2026
76ae91d
More fixes
simolus3 Apr 21, 2026
21a7ed6
Implement connection pool in Swift
simolus3 Apr 22, 2026
72e99f0
Move to protocol extension
simolus3 Apr 22, 2026
84189c3
Merge branch 'swiftify-crud-transactions' into swift-sync-client
simolus3 Apr 22, 2026
e20b44c
Merge branch 'swift-sync-client' into swift-pool
simolus3 Apr 22, 2026
c04af11
Get tests to work
simolus3 Apr 22, 2026
d067ced
Use high-level GRDB APIs instead of pointer
simolus3 Apr 23, 2026
f4cdd3c
Cleanup
simolus3 Apr 27, 2026
642cdbb
Fix sync in demo app
simolus3 Apr 27, 2026
0f71c4a
AI feedback
simolus3 Apr 27, 2026
dd5adc3
Merge branch 'swift-sync-client' into swift-pool
simolus3 Apr 27, 2026
dfd3ef0
Add database groups
simolus3 Apr 27, 2026
f67be2a
Merge remote-tracking branch 'origin/main' into swift-sync-client
simolus3 Apr 27, 2026
b9ea1f3
Merge branch 'swift-sync-client' into swift-pool
simolus3 Apr 27, 2026
1808f1c
Fix a bunch of AsyncSemaphore bugs
simolus3 Apr 27, 2026
b99fcd2
AI feedback
simolus3 Apr 27, 2026
177433e
Avoid race in "reportsErrors" test
simolus3 Apr 27, 2026
81c7121
Properly use structured concurrency
simolus3 Apr 27, 2026
979586f
Merge branch 'swift-sync-client' into swift-pool
simolus3 Apr 28, 2026
c13ff32
Make cursor extension methods public
simolus3 Apr 28, 2026
562aa16
Merge remote-tracking branch 'origin/main' into swift-pool
simolus3 Apr 29, 2026
f73de9f
Some additional cleanup
simolus3 Apr 29, 2026
2321496
AI feedback, more tests
simolus3 Apr 29, 2026
937ed8c
Fix GRDB tests
simolus3 May 4, 2026
ff32bec
Remove unused GRDB data convertible code
simolus3 May 4, 2026
0e4893d
Mark connection lease and pool protocols as internal
simolus3 May 4, 2026
bae227e
Motivate update hooks with comment
simolus3 May 4, 2026
99b9d3b
Revert API changes
simolus3 May 6, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,14 @@

## 1.14.0 (unreleased)

* Remove internal dependency on the PowerSync Kotlin SDK. Going forward, the Swift SDK is implemented in Swift!
__Important__: While these changes are tested, they are a full rewrite of the internal connection pool logic.
Please also test queries in your app after upgrading.
* Add `opDataTyped` and `previousValuesTyped` to `CrudEntry`, providing typed values instead of strings.
* Make `CrudBatch`, `CrudEntry` and `CrudTransaction` a concrete struct. Note that these can no longer be created in user code.
* Remove the internal `withSession` API.
* Breaking (for internal `SQLiteConnectionPoolProtocol` implementers): Make callbacks generic.
* Breaking (for internal `SQLiteConnectionLease` implementers): Add methods to run statements.

## 1.13.1

Expand Down
4 changes: 2 additions & 2 deletions Demos/GRDBDemo/GRDBDemo.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -314,7 +314,7 @@
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
DEVELOPMENT_TEAM = ZGT7463CVJ;
DEVELOPMENT_TEAM = N2FNDTNV98;
ENABLE_APP_SANDBOX = YES;
ENABLE_HARDENED_RUNTIME = YES;
ENABLE_INCOMING_NETWORK_CONNECTIONS = NO;
Expand Down Expand Up @@ -368,7 +368,7 @@
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
DEVELOPMENT_TEAM = ZGT7463CVJ;
DEVELOPMENT_TEAM = N2FNDTNV98;
ENABLE_APP_SANDBOX = YES;
ENABLE_HARDENED_RUNTIME = YES;
ENABLE_INCOMING_NETWORK_CONNECTIONS = NO;
Expand Down

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

38 changes: 12 additions & 26 deletions Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,6 @@ import PackageDescription

let packageName = "PowerSync"

// Set this to the absolute path of your Kotlin SDK checkout if you want to use a local Kotlin
// build. Also see docs/LocalBuild.md for details
let localKotlinSdkOverride: String? = nil

// Set this to the absolute path of your powersync-sqlite-core checkout if you want to use a
// local build of the core extension.
let localCoreExtension: String? = nil
Expand All @@ -20,24 +16,6 @@ let localCoreExtension: String? = nil
// towards a local framework build
var conditionalDependencies: [Package.Dependency] = []
var conditionalTargets: [Target] = []
var kotlinTargetDependency = Target.Dependency.target(name: "PowerSyncKotlin")

if let kotlinSdkPath = localKotlinSdkOverride {
// We can't depend on local XCFrameworks outside of this project's root, so there's a Package.swift
// in the PowerSyncKotlin project pointing towards a local build.
conditionalDependencies.append(.package(path: "\(kotlinSdkPath)/internal/PowerSyncKotlin"))

kotlinTargetDependency = .product(name: "PowerSyncKotlin", package: "PowerSyncKotlin")
} else {
// 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.11.2/PowersyncKotlinRelease.zip",
checksum: "bd4f9a4411a10a30bd67c3231d1d0d0dde42f0ec19161ccbd26d4e58b31efdfd"
))
}

var corePackageName = "powersync-sqlite-core-swift"
if let corePath = localCoreExtension {
Expand Down Expand Up @@ -84,20 +62,28 @@ let package = Package(
dependencies: conditionalDependencies + [
.package(url: "https://github.com/groue/GRDB.swift.git", from: "7.9.0"),
.package(url: "https://github.com/powersync-ja/CSQLite.git", exact: "3.51.2"),
.package(url: "https://github.com/apple/swift-async-algorithms.git", from: "1.1.0")
.package(url: "https://github.com/apple/swift-async-algorithms.git", from: "1.1.0"),
.package(url: "https://github.com/apple/swift-collections.git", from: "1.4.0")
],
targets: [
// Targets are the basic building blocks of a package, defining a module or a test suite.
// Targets can depend on other targets in this package and products from dependencies.
.target(
name: packageName,
dependencies: [
kotlinTargetDependency,
.product(name: "PowerSyncSQLiteCore", package: corePackageName),
.target(name: "PowerSyncCoreShim"),
.product(name: "CSQLite", package: "CSQLite"),
.product(name: "AsyncAlgorithms", package: "swift-async-algorithms")
.product(name: "AsyncAlgorithms", package: "swift-async-algorithms"),
.product(name: "BasicContainers", package: "swift-collections"),
.product(name: "DequeModule", package: "swift-collections")
]
),
.target(
name: "PowerSyncCoreShim",
dependencies: [
.product(name: "PowerSyncSQLiteCore", package: corePackageName),
],
),
.target(
name: "PowerSyncGRDB",
dependencies: [
Expand Down
9 changes: 0 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -118,15 +118,6 @@ Feel free to use the `DatabasePool` for view logic and the `PowerSyncDatabase` f
- Updating the PowerSync schema, with `updateSchema`, is not currently fully supported with GRDB connections.
- This integration currently requires statically linking PowerSync and GRDB.
- This implementation requires defining the PowerSync Schema and GRDB data types. This results in some duplication.
- This implementation uses the SQLite session API to track updates made by the PowerSync SDK. This could use more memory compared to the Standard PowerSync SQLite implementation.
- This implementation can cause warnings such as "Thread Performance Checker: Thread running at User-interactive quality-of-service class waiting on a lower QoS thread running at Default quality-of-service class. Investigate ways to avoid priority inversions". This will be addressed in a future release.

## Underlying Kotlin Dependency

The PowerSync Swift SDK makes use of the [PowerSync Kotlin SDK](https://github.com/powersync-ja/powersync-kotlin) and the API tool [SKIE](https://skie.touchlab.co/) under the hood to implement the Swift package.
However, this dependency is resolved internally and all public APIs are written entirely in Swift.

For more details, see the [Swift SDK reference](https://docs.powersync.com/client-sdk-references/swift) and generated [API references](https://powersync-ja.github.io/powersync-swift/documentation/powersync/).

## Attachments

Expand Down
65 changes: 65 additions & 0 deletions Sources/PowerSync/Implementation/ActiveInstanceStore.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
final class DatabaseGroupCollection: Sendable {
private let groups: Mutex<[ActiveDatabaseGroupData]> = Mutex([])

fileprivate func closeGroup(identifier: String) {
groups.withLock { $0.removeAll { group in group.identifier == identifier } }
}

func referenceGroup(identifier: String, logger: LoggerProtocol) -> ActiveDatabaseGroup {
groups.withLock { activeDatabases in
let existingGroup = activeDatabases.first { $0.identifier == identifier }
let data: ActiveDatabaseGroupData
if let existingGroup {
logger.warning("""
Multiple PowerSync instances for the same database have been detected.
This can cause unexpected results.
Please check your PowerSync client instantiation logic if this is not intentional.
""", tag: "DatabaseGroupCollection")
data = existingGroup
} else {
data = ActiveDatabaseGroupData(identifier: identifier)
activeDatabases.append(data)
}

return ActiveDatabaseGroup(data: data, collection: self)
}
}

static let shared = DatabaseGroupCollection()
}

private final class ActiveDatabaseGroupData: Sendable {
let identifier: String
let syncCoordinator = SyncCoordinator()

init(identifier: String) {
self.identifier = identifier
}
}

/// A collection of PowerSync databases with the same path / identifier.
///
/// We expect that each group will only ever have one database because we encourage users to write their databases as
/// singletons. We print a warning when two databases are part of the same group.
/// Additionally, we want to avoid two databases in the same group having a sync stream open at the same time to avoid
/// duplicate resources being used. For this reason, each active database group has a single sync coordinator actor
/// responsible for initializing the sync process for all databases in the group.
final class ActiveDatabaseGroup: Sendable {
fileprivate let data: ActiveDatabaseGroupData
private weak let collection: DatabaseGroupCollection?

fileprivate init(data: ActiveDatabaseGroupData, collection: DatabaseGroupCollection) {
self.data = data
self.collection = collection
}

var syncCoordinator: SyncCoordinator {
data.syncCoordinator
}

deinit {
if let collection {
collection.closeGroup(identifier: self.data.identifier)
}
}
}
Loading
Loading