From 12f348520e9fdab3a201005c36341c49cb50e822 Mon Sep 17 00:00:00 2001 From: Dominic Date: Mon, 23 Dec 2024 15:26:20 +0200 Subject: [PATCH] chore: improve demo upload data to handle fatal errors --- .../PowerSync/SupabaseConnector.swift | 42 +++++++++++++++++++ README.md | 3 +- 2 files changed, 43 insertions(+), 2 deletions(-) diff --git a/Demo/PowerSyncExample/PowerSync/SupabaseConnector.swift b/Demo/PowerSyncExample/PowerSync/SupabaseConnector.swift index 004317f..fa719f1 100644 --- a/Demo/PowerSyncExample/PowerSync/SupabaseConnector.swift +++ b/Demo/PowerSyncExample/PowerSync/SupabaseConnector.swift @@ -4,11 +4,45 @@ import Supabase import PowerSync import AnyCodable +private struct PostgresFatalCodes { + /// Postgres Response codes that we cannot recover from by retrying. + static let fatalResponseCodes: [String] = [ + // Class 22 — Data Exception + // Examples include data type mismatch. + "22...", + // Class 23 — Integrity Constraint Violation. + // Examples include NOT NULL, FOREIGN KEY and UNIQUE violations. + "23...", + // INSUFFICIENT PRIVILEGE - typically a row-level security violation + "42501" + ] + + static func isFatalError(_ code: String) -> Bool { + return fatalResponseCodes.contains { pattern in + code.range(of: pattern, options: [.regularExpression]) != nil + } + } + + static func extractErrorCode(from error: any Error) -> String? { + // Look for code: Optional("XXXXX") pattern + let errorString = String(describing: error) + if let range = errorString.range(of: "code: Optional\\(\"([^\"]+)\"\\)", options: .regularExpression), + let codeRange = errorString[range].range(of: "\"([^\"]+)\"", options: .regularExpression) { + // Extract just the code from within the quotes + let code = errorString[codeRange].dropFirst().dropLast() + return String(code) + } + return nil + } +} + + @Observable class SupabaseConnector: PowerSyncBackendConnector { let powerSyncEndpoint: String = Secrets.powerSyncEndpoint let client: SupabaseClient = SupabaseClient(supabaseURL: Secrets.supabaseURL, supabaseKey: Secrets.supabaseAnonKey) var session: Session? + private var errorCode: String? @ObservationIgnored private var observeAuthStateChangesTask: Task? @@ -76,6 +110,14 @@ class SupabaseConnector: PowerSyncBackendConnector { _ = try await transaction.complete.invoke(p1: nil) } catch { + if let errorCode = PostgresFatalCodes.extractErrorCode(from: error), + PostgresFatalCodes.isFatalError(errorCode) { + print("Data upload error: \(error)") + print("Discarding entry: \(lastEntry!)") + _ = try await transaction.complete.invoke(p1: nil) + return + } + print("Data upload error - retrying last entry: \(lastEntry!), \(error)") throw error } diff --git a/README.md b/README.md index 9355803..40a4849 100644 --- a/README.md +++ b/README.md @@ -22,7 +22,7 @@ This SDK is currently in a beta release it is suitable for production use, given The easiest way to test the PowerSync Swift SDK is to run our demo application. -- [Demo/PowerSyncExample](./Demo/PowerSyncExample/README.md): A simple to-do list application demonstrating the use of the PowerSync Swift SDK using a Supabase connector. +- [Demo/PowerSyncExample](./Demo/README.md): A simple to-do list application demonstrating the use of the PowerSync Swift SDK using a Supabase connector. ## Installation @@ -59,4 +59,3 @@ The PowerSync Swift SDK currently makes use of the [PowerSync Kotlin Multiplatfo ## Migration from Alpha to Beta See these [developer notes](https://docs.powersync.com/client-sdk-references/swift#migrating-from-the-alpha-to-the-beta-sdk) if you are migrating from the alpha to the beta version of the Swift SDK. -