diff --git a/README.md b/README.md index f0231da..cabd4d3 100644 --- a/README.md +++ b/README.md @@ -29,10 +29,24 @@ The easiest way to test the PowerSync Swift SDK is to run our demo application. Add ```swift -.package(url: "https://github.com/powersync-ja/powersync-swift", exact: "") + dependencies: [ + ... + .package(url: "https://github.com/powersync-ja/powersync-swift", exact: "") + ], + targets: [ + .target( + name: "YourTargetName", + dependencies: [ + ... + .product( + name: "PowerSync", + package: "powersync-swift" + ), + ] + ) + ] ``` - to your `Package.swift` file and pin the dependency to a specific version. This is required because the package is in beta. ## Underlying Kotlin Dependency diff --git a/Sources/PowerSync/Kotlin/KotlinAdapter.swift b/Sources/PowerSync/Kotlin/KotlinAdapter.swift index 7cc12c9..8f864de 100644 --- a/Sources/PowerSync/Kotlin/KotlinAdapter.swift +++ b/Sources/PowerSync/Kotlin/KotlinAdapter.swift @@ -9,7 +9,7 @@ internal struct KotlinAdapter { ) } } - + struct IndexedColumn { static func toKotlin(_ column: IndexedColumnProtocol) -> PowerSyncKotlin.IndexedColumn { return PowerSyncKotlin.IndexedColumn( @@ -20,7 +20,7 @@ internal struct KotlinAdapter { ) } } - + struct Table { static func toKotlin(_ table: TableProtocol) -> PowerSyncKotlin.Table { PowerSyncKotlin.Table( @@ -33,7 +33,7 @@ internal struct KotlinAdapter { ) } } - + struct Column { static func toKotlin(_ column: any ColumnProtocol) -> PowerSyncKotlin.Column { PowerSyncKotlin.Column( @@ -41,7 +41,7 @@ internal struct KotlinAdapter { type: columnType(from: column.type) ) } - + private static func columnType(from swiftType: ColumnData) -> PowerSyncKotlin.ColumnType { switch swiftType { case .text: @@ -53,7 +53,7 @@ internal struct KotlinAdapter { } } } - + struct Schema { static func toKotlin(_ schema: SchemaProtocol) -> PowerSyncKotlin.Schema { PowerSyncKotlin.Schema( diff --git a/Sources/PowerSync/PowerSyncBackendConnector.swift b/Sources/PowerSync/PowerSyncBackendConnector.swift index 6753f2b..87fda9a 100644 --- a/Sources/PowerSync/PowerSyncBackendConnector.swift +++ b/Sources/PowerSync/PowerSyncBackendConnector.swift @@ -1,16 +1,40 @@ public protocol PowerSyncBackendConnectorProtocol { - func uploadData(database: PowerSyncDatabaseProtocol) async throws - + /// + /// Get credentials for PowerSync. + /// + /// This should always fetch a fresh set of credentials - don't use cached + /// values. + /// + /// Return null if the user is not signed in. Throw an error if credentials + /// cannot be fetched due to a network error or other temporary error. + /// + /// This token is kept for the duration of a sync connection. + /// func fetchCredentials() async throws -> PowerSyncCredentials? + + /// + /// Upload local changes to the app backend. + /// + /// Use [getCrudBatch] to get a batch of changes to upload. + /// + /// Any thrown errors will result in a retry after the configured wait period (default: 5 seconds). + /// + func uploadData(database: PowerSyncDatabaseProtocol) async throws } +/// Implement this to connect an app backend. +/// +/// The connector is responsible for: +/// 1. Creating credentials for connecting to the PowerSync service. +/// 2. Applying local changes against the backend application server. +/// +/// open class PowerSyncBackendConnector: PowerSyncBackendConnectorProtocol { public init() {} - - open func uploadData(database: PowerSyncDatabaseProtocol) async throws {} open func fetchCredentials() async throws -> PowerSyncCredentials? { return nil } -} + open func uploadData(database: PowerSyncDatabaseProtocol) async throws {} +} diff --git a/Sources/PowerSync/PowerSyncCredentials.swift b/Sources/PowerSync/PowerSyncCredentials.swift index 03727e1..2c7ffe8 100644 --- a/Sources/PowerSync/PowerSyncCredentials.swift +++ b/Sources/PowerSync/PowerSyncCredentials.swift @@ -1,5 +1,9 @@ import Foundation + +/// +/// Temporary credentials to connect to the PowerSync service. +/// public struct PowerSyncCredentials: Codable { /// PowerSync endpoint, e.g. "https://myinstance.powersync.co". public let endpoint: String diff --git a/Sources/PowerSync/Schema/Column.swift b/Sources/PowerSync/Schema/Column.swift index 60d2c97..1489666 100644 --- a/Sources/PowerSync/Schema/Column.swift +++ b/Sources/PowerSync/Schema/Column.swift @@ -2,7 +2,16 @@ import Foundation import PowerSyncKotlin public protocol ColumnProtocol: Equatable { + /// Name of the column. var name: String { get } + /// Type of the column. + /// + /// If the underlying data does not match this type, + /// it is cast automatically. + /// + /// For details on the cast, see: + /// https://www.sqlite.org/lang_expr.html#castexpr + /// var type: ColumnData { get } } @@ -12,6 +21,7 @@ public enum ColumnData { case real } +/// A single column in a table schema. public struct Column: ColumnProtocol { public let name: String public let type: ColumnData diff --git a/Sources/PowerSync/Schema/Index.swift b/Sources/PowerSync/Schema/Index.swift index cee8191..009fe83 100644 --- a/Sources/PowerSync/Schema/Index.swift +++ b/Sources/PowerSync/Schema/Index.swift @@ -2,7 +2,13 @@ import Foundation import PowerSyncKotlin public protocol IndexProtocol { + /// + /// Descriptive name of the index. + /// var name: String { get } + /// + /// List of columns used for the index. + /// var columns: [IndexedColumnProtocol] { get } } diff --git a/Sources/PowerSync/Schema/IndexedColumn.swift b/Sources/PowerSync/Schema/IndexedColumn.swift index 8d5bd17..3aee895 100644 --- a/Sources/PowerSync/Schema/IndexedColumn.swift +++ b/Sources/PowerSync/Schema/IndexedColumn.swift @@ -1,7 +1,16 @@ import Foundation +/// +/// Describes an indexed column. +/// public protocol IndexedColumnProtocol { + /// + /// Name of the column to index. + /// var column: String { get } + /// + /// Whether this column is stored in ascending order in the index. + /// var ascending: Bool { get } } @@ -17,10 +26,16 @@ public struct IndexedColumn: IndexedColumnProtocol { self.ascending = ascending } + /// + /// Creates ascending IndexedColumn + /// public static func ascending(_ column: String) -> IndexedColumn { IndexedColumn(column: column, ascending: true) } + /// + /// Creates descending IndexedColumn + /// public static func descending(_ column: String) -> IndexedColumn { IndexedColumn(column: column, ascending: false) } diff --git a/Sources/PowerSync/Schema/Schema.swift b/Sources/PowerSync/Schema/Schema.swift index 7ac4597..8ef15e1 100644 --- a/Sources/PowerSync/Schema/Schema.swift +++ b/Sources/PowerSync/Schema/Schema.swift @@ -1,23 +1,30 @@ public protocol SchemaProtocol { + /// + /// Tables used in Schema + /// var tables: [Table] { get } + /// + /// Validate tables + /// func validate() throws } public struct Schema: SchemaProtocol { public let tables: [Table] - + public init(tables: [Table]) { self.tables = tables } - - // Convenience initializer with variadic parameters + /// + /// Convenience initializer with variadic parameters + /// public init(_ tables: Table...) { self.init(tables: tables) } - + public func validate() throws { var tableNames = Set() - + for table in tables { if !tableNames.insert(table.name).inserted { throw SchemaError.duplicateTableName(table.name) @@ -30,4 +37,3 @@ public struct Schema: SchemaProtocol { public enum SchemaError: Error { case duplicateTableName(String) } - diff --git a/Sources/PowerSync/Schema/Table.swift b/Sources/PowerSync/Schema/Table.swift index a6bddef..1d6ac9e 100644 --- a/Sources/PowerSync/Schema/Table.swift +++ b/Sources/PowerSync/Schema/Table.swift @@ -1,17 +1,38 @@ import Foundation public protocol TableProtocol { + /// + /// The synced table name, matching sync rules. + /// var name: String { get } + /// + /// List of columns. + /// var columns: [Column] { get } + /// + /// List of indexes. + /// var indexes: [Index] { get } + /// + /// Whether the table only exists locally. + /// var localOnly: Bool { get } + /// + /// Whether this is an insert-only table. + /// var insertOnly: Bool { get } + /// + /// Override the name for the view + /// var viewNameOverride: String? { get } var viewName: String { get } } private let MAX_AMOUNT_OF_COLUMNS = 63 +/// +/// A single table in the schema. +/// public struct Table: TableProtocol { public let name: String public let columns: [Column] @@ -19,20 +40,20 @@ public struct Table: TableProtocol { public let localOnly: Bool public let insertOnly: Bool public let viewNameOverride: String? - + public var viewName: String { viewNameOverride ?? name } - + internal var internalName: String { localOnly ? "ps_data_local__\(name)" : "ps_data__\(name)" } - + private let invalidSqliteCharacters = try! NSRegularExpression( pattern: #"["'%,.#\s\[\]]"#, options: [] ) - + public init( name: String, columns: [Column], @@ -48,49 +69,52 @@ public struct Table: TableProtocol { self.insertOnly = insertOnly self.viewNameOverride = viewNameOverride } - + private func hasInvalidSqliteCharacters(_ string: String) -> Bool { let range = NSRange(location: 0, length: string.utf16.count) return invalidSqliteCharacters.firstMatch(in: string, options: [], range: range) != nil } - + + /// + /// Validate the table + /// public func validate() throws { if columns.count > MAX_AMOUNT_OF_COLUMNS { throw TableError.tooManyColumns(tableName: name, count: columns.count) } - + if let viewNameOverride = viewNameOverride, hasInvalidSqliteCharacters(viewNameOverride) { throw TableError.invalidViewName(viewName: viewNameOverride) } - + var columnNames = Set(["id"]) - + for column in columns { if column.name == "id" { throw TableError.customIdColumn(tableName: name) } - + if columnNames.contains(column.name) { throw TableError.duplicateColumn( tableName: name, columnName: column.name ) } - + if hasInvalidSqliteCharacters(column.name) { throw TableError.invalidColumnName( tableName: name, columnName: column.name ) } - + columnNames.insert(column.name) } - + // Check indexes var indexNames = Set() - + for index in indexes { if indexNames.contains(index.name) { throw TableError.duplicateIndex( @@ -98,14 +122,14 @@ public struct Table: TableProtocol { indexName: index.name ) } - + if hasInvalidSqliteCharacters(index.name) { throw TableError.invalidIndexName( tableName: name, indexName: index.name ) } - + // Check index columns exist in table for indexColumn in index.columns { if !columnNames.contains(indexColumn.column) { @@ -116,7 +140,7 @@ public struct Table: TableProtocol { ) } } - + indexNames.insert(index.name) } }