Skip to content

Remove Kotlin Types from API #40

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 27 commits into from
May 2, 2025
Merged

Remove Kotlin Types from API #40

merged 27 commits into from
May 2, 2025

Conversation

stevensJourney
Copy link
Contributor

@stevensJourney stevensJourney commented Apr 23, 2025

Overview

This PR removes all Kotlin types from public-facing APIs and introduces numerous quality-of-life improvements to the Swift SDK.

Removing Kotlin interfaces removes coupling between the Swift and Kotlin SDKs. This removes some weird behaviour where users have to interface with translated Kotlin code. Declaring protocols in the Swift SDK also allows for generating better documentation with SwiftDoc comments.

Explicit Swift Interfaces

Previously, Kotlin interfaces/types were used for:

  • Sync Status
  • Transaction Contexts
  • CRUD batches, transactions, and entries
  • SQL Cursor
  • JSON parameters for client parameters

Some of these Kotlin interfaces made the Swift SDK unpleasant to use. These have now been replaced with Swift protocols, along with quality-of-life improvements using native Swift types where applicable. Adapters link the Kotlin implementation to these Swift protocols.

✅ Sync Status

The Kotlin SyncStatus interface was previously exposed directly through the PowerSyncDatabase's currentStatus field, requiring users to interface with SKIEE-translated Kotlin types.

For example, lastSyncedAt was represented as a Kotlin Date:

/// This is clunky and requires overhead for optional handling.
/// `lastSyncedAt` is of type PSKKotlinx_datetimeInstant
let time: Date = Date(
    timeIntervalSince1970: TimeInterval(database.currentStatus.lastSyncedAt!.epochSeconds)
)

This has been replaced with a native Swift interface. Users now receive Swift-native values like Date directly.

✅ Transaction Contexts

SKIEE translates generic Kotlin functions to return Any, which leads to the loss of generics and requires unsafe casting.

Before:

let attachmentIDs = try transaction.getAll(
    sql: "SELECT photo_id FROM \(TODOS_TABLE) WHERE list_id = ? AND photo_id IS NOT NULL",
    parameters: [id]
) { cursor in
    cursor.getString(index: 0)! // Force unwrap — not ideal
} as? [String] // Manual cast required

After:

let attachmentIDs = try transaction.getAll(
    sql: "SELECT photo_id FROM \(TODOS_TABLE) WHERE list_id = ? AND photo_id IS NOT NULL",
    parameters: [id]
) { cursor in
    try cursor.getString(index: 0)
} // Swift infers attachmentIDs as [String]

Bonus:
execute methods return an Int64 (affected rows), but for PowerSync, this is usually ignored due to use of views. The return value is now marked @discardableResult to silence warnings:

try await database.execute(
    sql: "INSERT INTO users (id, name, email) VALUES (?, ?, ?)",
    parameters: ["1", "Test User", "[email protected]"]
)

Extra Bonus:
SQL parameter arrays now support nil values via [Any?] instead of [Any], resolving compiler warnings:

Compiler warning screenshot

✅ CRUD Operations

CRUD operations like getNextCrudTransaction and getCrudBatch previously returned Kotlin containers.

Before:

_ = try await transaction.complete.invoke(p1: nil)

After:

try await transaction.complete()

The Swift interface is clearer and supports the optional writeCheckpoint parameter directly.

✅ SQL Cursors

Cursor APIs previously returned Kotlin primitive wrappers, requiring manual unwrapping:

Before:

let users: [(String, String)] = try await database.getAll(
    sql: "SELECT id, name FROM users WHERE password = ? ORDER BY id",
    parameters: [nil]
) { cursor in
    try (
        cursor.getBoolean(index: 0)?.boolValue, // Kotlin Bool
        cursor.getDouble(index: 1)?.value,      // Kotlin Double
        cursor.getLong(index: 2)?.int64Value    // Kotlin Long
    )
}

Improvements:

  • New getInt and getInt64 methods for size-specific integer access
  • Better consistency between index and name-based getters

Breaking change:
Index-based getters now match the behavior of named column getters:

A SqlCursorError Error type is introduced which handles error cases e.g. when required column values are nil valued or if a column name is not present in the query result set. These exceptions will propagate from the mapper to the top-level query method (e.g get)

try cursor.getBoolean(index: 0) // Non-optional Swift Bool, throws if nil
cursor.getBooleanOptional(index: 0) // Optional Swift Bool

✅ JSON Client Parameters

The connect method previously accepted [String: Any], which caused runtime crashes due to SKIEE requiring Kotlin JSONParam values.

Before (crashes at runtime):

try await database.connect(
    connector: PowerSyncBackendConnector(),
    params: [
        "foo": "bar"
    ]
)

After (strongly typed Swift API):

try await database.connect(
    connector: PowerSyncBackendConnector(),
    params: [
        "foo": .string("bar")
    ]
)

New Swift-native JsonParam and JsonValue enums prevent type mismatches and runtime crashes.

TODOS

@stevensJourney stevensJourney requested a review from Copilot April 29, 2025 14:36
Copy link

@Copilot Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull Request Overview

This pull request removes Kotlin types from public APIs and replaces them with native Swift protocols, in addition to implementing several quality‐of‐life improvements. Key changes include:

  • Replacing Kotlin interfaces with Swift-native types for sync status, transactions, and SQL cursors.
  • Updating API signatures to use optional parameters ([Any?]) and @discardableResult annotations.
  • Enhancing error handling in watched queries and introducing lock APIs for database access.

Reviewed Changes

Copilot reviewed 52 out of 53 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
Sources/PowerSync/Kotlin/sync/KotlinSyncStatusData.swift Wraps Kotlin sync status in a Swift-native interface.
Sources/PowerSync/Kotlin/sync/KotlinSyncStatus.swift Implements Swift SyncStatus using Kotlin data via protocol conformance.
Sources/PowerSync/Kotlin/db/KotlinSqlCursor.swift Provides native SQL cursor getters with improved error handling.
Sources/PowerSync/Kotlin/db/KotlinJsonParam.swift Adapts Swift JsonValue into Kotlin JsonParam types.
Sources/PowerSync/Kotlin/db/KotlinCrudTransaction.swift Wraps Kotlin CRUD transactions into Swift-friendly constructs.
Sources/PowerSync/Kotlin/db/KotlinCrudEntry.swift Converts Kotlin CRUD entries to Swift equivalents.
Sources/PowerSync/Kotlin/db/KotlinCrudBatch.swift Bridges CRUD batches from Kotlin types to Swift.
Sources/PowerSync/Kotlin/db/KotlinConnectionContext.swift Introduces connection context wrappers and helper methods for parameter mapping.
Sources/PowerSync/Kotlin/TransactionCallback.swift Refactors transaction callback implementations using updated Swift context types.
Sources/PowerSync/Kotlin/SqlCursor.swift Removes obsolete extension methods previously implemented for SqlCursor.
Sources/PowerSync/Kotlin/SafeCastError.swift Improves the safeCast implementation with optional handling.
Sources/PowerSync/Kotlin/KotlinTypes.swift Removes public typealiases to force usage of dedicated Swift protocols over Kotlin types.
Sources/PowerSync/Kotlin/KotlinPowerSyncDatabaseImpl.swift Updates core database implementation with new transaction/lock APIs and parameter handling.
Sources/PowerSync/Kotlin/KotlinAdapter.swift Refactors adapter code to use an enum for improved clarity.
Sources/PowerSync/Kotlin/DatabaseLogger.swift Adjusts logger bridging to align with updated visibility and logging semantics.
Package.swift Modifies dependency reference to point to a local path temporarily.
Demo/PowerSyncExample/PowerSync/SystemManager.swift Adapts demo usage to the new Swift API for sync status and CRUD operations.
Demo/PowerSyncExample/PowerSync/SupabaseConnector.swift Updates transaction completion calls with the new Swift-friendly syntax.
CHANGELOG.md Documents the breaking changes and quality-of-life improvements introduced by this PR.
Files not reviewed (1)
  • Demo/PowerSyncExample.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved: Language not supported
Comments suppressed due to low confidence (1)

CHANGELOG.md:25

  • Remove the duplicate word 'are' to improve clarity.
AttachmentContext", "AttachmentQueue", "AttachmentService" and "SyncingService" are are now explicitly declared as open classes,

@stevensJourney stevensJourney changed the title [wip] Remove Kotlin Types from API Remove Kotlin Types from API Apr 29, 2025
@stevensJourney stevensJourney marked this pull request as ready for review April 29, 2025 15:36
Copy link
Contributor

@simolus3 simolus3 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it's fantastic that we have a proper Swift API now, so the changes look good to me.

One thing that I think we should consider before we need to avoid breaking changes altogether: It looks like using a double representing seconds is the canonical way to represent durations in Swift, so maybe adopting that for our APIs that currently accept millis (and then normalizing to millis for Kotlin) may be a change worth making. But that's also pretty minor, so perhaps not worth it.

Also: PowerSyncBackendConnectorAdapter.swift was moved into kotlin (lowercase), the other files are in Kotlin (uppercase).

@stevensJourney stevensJourney requested a review from simolus3 May 2, 2025 13:17
Copy link
Contributor

@simolus3 simolus3 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good to me 🚀

@stevensJourney stevensJourney merged commit 916211d into main May 2, 2025
1 check passed
@stevensJourney stevensJourney deleted the api branch May 2, 2025 13:46
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants