skills/powersync/references/sdks/powersync-swift.md

6.4 KiB

name description metadata
powersync-swift PowerSync Swift SDK — schema, queries, sync lifecycle, backend connectors, and GRDB ORM support
tags
swift, ios, macos, grdb, orm, sqlite, offline-first

PowerSync Swift SDK

Load this when building a Swift app (iOS, macOS) with PowerSync.

Best practices and guidance for building apps with the PowerSync Swift SDK.

Resource Description
Swift API reference Full API reference, consult only when the inline examples don't cover your case.
Supported Platforms Supported platforms and features, consult for compatibility details.

Installation

Method Instructions
Package.swift Installation - Package.swift
Xcode Installation - Xcode

Setup

1. Define Schema

import PowerSync

let lists = Table(
    name: "lists",
    columns: [
        // id column is automatically included
        .text("name"),
        .text("created_at"),
        .text("owner_id")
    ]
)

let todos = Table(
    name: "todos",
    columns: [
        .text("list_id"),
        .text("description"),
        .integer("completed"), // 0 or 1
        .text("created_at"),
        .text("completed_at"),
        .text("created_by"),
        .text("completed_by")
    ],
    indexes: [
        Index(name: "list_id", columns: [IndexedColumn.ascending("list_id")])
    ]
)

let AppSchema = Schema(tables: [lists, todos])

See Define the Client-Side Schema for more information.

2. Create Backend Connector

import PowerSync

@Observable
@MainActor
final class MyConnector: PowerSyncBackendConnectorProtocol {
    private let apiClient: APIClient

    init(apiClient: APIClient) {
        self.apiClient = apiClient
    }

    func fetchCredentials() async throws -> PowerSyncCredentials? {
        let response = try await apiClient.getPowerSyncToken()
        return PowerSyncCredentials(
            endpoint: "https://your-instance.powersync.journeyapps.com",
            token: response.token,
            expiresAt: response.expiresAt
        )
    }

    func uploadData(database: PowerSyncDatabaseProtocol) async throws {
        guard let transaction = try await database.getNextCrudTransaction() else { return }

        do {
            for entry in transaction.crud {
                switch entry.op {
                case .put:
                    var data = entry.opData ?? [:]
                    data["id"] = entry.id
                    try await apiClient.upsert(table: entry.table, id: entry.id, data: data)
                case .patch:
                    guard let opData = entry.opData else { continue }
                    try await apiClient.update(table: entry.table, id: entry.id, data: opData)
                case .delete:
                    try await apiClient.delete(table: entry.table, id: entry.id)
                }
            }
            try await transaction.complete()
        } catch {
            throw error
        }
    }
}

Use getCrudBatch instead of getNextCrudTransaction when uploading large numbers of mutations in bulk.

See Integrate with your Backend for more information.

3. Instantiate and Connect

@Observable
@MainActor
final class SystemManager {
    let connector = MyConnector()
    let db: PowerSyncDatabaseProtocol

    init() {
        db = PowerSyncDatabase(
            schema: AppSchema,
            dbFilename: "powersync-swift.sqlite"
        )
    }

    func connect() async throws {
        try await db.connect(connector: connector)
    }
}

See Instantiate the PowerSync Database for more information.

Sync Streams

See sync-config.md for how to subscribe to Sync Streams when auto_subscribe is not set to true in the PowerSync Service config.

Query Patterns

See Using PowerSync: CRUD for the full API reference.

One-Time Queries

// Fetch all matching rows
let todos = try await db.getAll("SELECT * FROM todos WHERE list_id = ?", parameters: [listId])

// Fetch single row — throws if not found
let todo = try await db.get("SELECT * FROM todos WHERE id = ?", parameters: [id])

// Fetch single row — returns nil if not found
let todo = try await db.getOptional("SELECT * FROM todos WHERE id = ?", parameters: [id])

Reactive Queries

// Watch a query — emits on every change to the watched tables
for try await todos in db.watch("SELECT * FROM todos WHERE list_id = ?", parameters: [listId]) {
    // update UI
}

Writing Data

// Single mutation
try await db.execute(
    "INSERT INTO todos (id, description, completed) VALUES (uuid(), ?, ?)",
    parameters: ["New todo", 0]
)

// Multiple related mutations as a single unit
try await db.writeTransaction { tx in
    try await tx.execute("INSERT INTO lists (id, name) VALUES (?, ?)", parameters: [listId, "Shopping"])
    try await tx.execute("INSERT INTO todos (id, list_id, description) VALUES (uuid(), ?, ?)", parameters: [listId, "Milk"])
}

ORM — GRDB

PowerSync Swift supports GRDB as an ORM. Requires PowerSync Swift v1.9.0+.

Setup requires a DatabasePool with PowerSync config — see GRDB Setup.

// Define a GRDB record type
struct Todo: Codable, Identifiable, FetchableRecord, PersistableRecord {
    var id: String
    var description: String
    var completed: Int
}

// Read
let todos = try await pool.read { db in
    try Todo.fetchAll(db)
}

// Write
try await pool.write { db in
    var todo = Todo(id: UUID().uuidString, description: "Buy milk", completed: 0)
    try todo.insert(db)
}

See GRDB Architecture for how the PowerSync + GRDB integration works.