Skip to content

CRUD Operations

GenericPowerSyncService provides a reusable service class for CRUD operations on any PowerSync table.

import ZyraForm
let service = GenericPowerSyncService(
tableName: "\(AppConfig.dbPrefix)users",
userId: currentUserId
)
let config = UsersSchema.toTableFieldConfig()
try await service.loadRecords(
fields: config.allFields,
encryptedFields: config.encryptedFields,
integerFields: config.integerFields,
booleanFields: config.booleanFields,
orderBy: config.defaultOrderBy
)
// Access records
let users = service.records
try await service.loadRecords(
fields: config.allFields,
whereClause: "is_active = ? AND age > ?",
parameters: [true, 18],
encryptedFields: config.encryptedFields,
orderBy: "name ASC"
)
try await service.loadRecords(
fields: config.allFields,
whereClause: "id = ?",
parameters: [recordId],
encryptedFields: config.encryptedFields
)
guard let user = service.records.first else {
throw NSError(domain: "Service", code: 404)
}
let id = try await service.createRecord(
fields: [
"email": "user@example.com",
"name": "John Doe",
"age": 25
],
encryptedFields: config.encryptedFields,
autoGenerateId: true,
autoTimestamp: true
)
print("Created record with ID: \(id)")
let id = try await service.createRecord(
fields: [
"id": customId,
"email": "user@example.com",
"name": "John Doe"
],
encryptedFields: config.encryptedFields,
autoGenerateId: false
)
let ids = try await service.createRecords(
records: [
["email": "user1@example.com", "name": "User 1"],
["email": "user2@example.com", "name": "User 2"],
["email": "user3@example.com", "name": "User 3"]
],
encryptedFields: config.encryptedFields
)
print("Created \(ids.count) records")
try await service.updateRecord(
id: recordId,
fields: [
"name": "Updated Name",
"age": 26
],
encryptedFields: config.encryptedFields,
autoTimestamp: true
)
try await service.updateRecord(
id: recordId,
fields: ["name": "New Name"],
encryptedFields: config.encryptedFields,
autoTimestamp: false
)
try await service.deleteRecord(id: recordId)
try await service.deleteRecord(id: recordId, caseInsensitive: true)
try await service.deleteRecords(ids: [id1, id2, id3])
struct UserListView: View {
@StateObject private var service: GenericPowerSyncService
@State private var users: [[String: Any]] = []
@State private var selectedUserId: String?
private let config = UsersSchema.toTableFieldConfig()
init(userId: String) {
_service = StateObject(wrappedValue: GenericPowerSyncService(
tableName: "\(AppConfig.dbPrefix)users",
userId: userId
))
}
var body: some View {
List(users, id: \.self) { user in
HStack {
Text(user["name"] as? String ?? "Unknown")
.foregroundColor(selectedUserId == user["id"] as? String ? .blue : .primary)
Spacer()
Text(user["email"] as? String ?? "")
.font(.caption)
.foregroundColor(.secondary)
}
.onTapGesture {
selectedUserId = user["id"] as? String
}
}
.toolbar {
ToolbarItem(placement: .primaryAction) {
Button("Add") {
Task {
await createUser()
}
}
}
ToolbarItem(placement: .secondaryAction) {
Button("Delete") {
if let id = selectedUserId {
Task {
await deleteUser(id: id)
}
}
}
.disabled(selectedUserId == nil)
}
}
.task {
await loadUsers()
}
}
private func loadUsers() async {
do {
try await service.loadRecords(
fields: config.allFields,
encryptedFields: config.encryptedFields,
integerFields: config.integerFields,
orderBy: config.defaultOrderBy
)
await MainActor.run {
users = service.records
}
} catch {
print("Failed to load users: \(error)")
}
}
private func createUser() async {
do {
let id = try await service.createRecord(
fields: [
"email": "newuser@example.com",
"name": "New User",
"age": 25
],
encryptedFields: config.encryptedFields
)
await loadUsers()
await MainActor.run {
selectedUserId = id
}
} catch {
print("Failed to create user: \(error)")
}
}
private func updateUser(id: String) async {
guard let user = users.first(where: { $0["id"] as? String == id }) else { return }
do {
try await service.updateRecord(
id: id,
fields: [
"name": (user["name"] as? String ?? "") + " (Updated)"
],
encryptedFields: config.encryptedFields
)
await loadUsers()
} catch {
print("Failed to update user: \(error)")
}
}
private func deleteUser(id: String) async {
do {
try await service.deleteRecord(id: id)
await loadUsers()
await MainActor.run {
if selectedUserId == id {
selectedUserId = nil
}
}
} catch {
print("Failed to delete user: \(error)")
}
}
}

Use TableFieldConfig to organize field lists:

struct TableFieldConfig {
let allFields: [String]
let encryptedFields: [String]
let integerFields: [String]
let booleanFields: [String]
let defaultOrderBy: String
static let users = TableFieldConfig(
allFields: ["id", "email", "name", "age", "user_id", "created_at", "updated_at"],
encryptedFields: ["name"],
integerFields: ["age"],
booleanFields: [],
defaultOrderBy: "created_at DESC"
)
}
// Usage
let config = TableFieldConfig.users
try await service.loadRecords(
fields: config.allFields,
encryptedFields: config.encryptedFields,
integerFields: config.integerFields,
booleanFields: config.booleanFields,
orderBy: config.defaultOrderBy
)
  1. Always Reload After Mutations: The service automatically reloads, but you can manually call loadRecords() if needed
  2. Use Field Configurations: Define field lists once with TableFieldConfig
  3. Handle Errors: Wrap CRUD operations in do-catch blocks
  4. Type-Safe Access: Use type casting when accessing record fields:
    let name = record["name"] as? String ?? ""
    let age = record["age"] as? Int ?? 0
    let isActive = record["is_active"] as? Bool ?? false