Skip to content

Commit 5f32d69

Browse files
Attachments package (#159)
1 parent 568c3b1 commit 5f32d69

File tree

49 files changed

+3177
-194
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

49 files changed

+3177
-194
lines changed

CHANGELOG.md

+54-25
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
# Changelog
22

3+
## 1.0.0-BETA31
4+
5+
* Added helpers for Attachment syncing.
6+
37
## 1.0.0-BETA30
48

59
* Fix a deadlock when calling `connect()` immediately after opening a database.
@@ -10,22 +14,31 @@
1014
* Fix potential race condition between jobs in `connect()` and `disconnect()`.
1115
* [JVM Windows] Fixed PowerSync Extension temporary file deletion error on process shutdown.
1216
* [iOS] Fixed issue where automatic driver migrations would fail with the error:
17+
1318
```
1419
Sqlite operation failure database is locked attempted to run migration and failed. closing connection
1520
```
21+
1622
* Fix race condition causing data received during uploads not to be applied.
1723

1824
## 1.0.0-BETA28
1925

2026
* Update PowerSync SQLite core extension to 0.3.12.
21-
* Added queing protection and warnings when connecting multiple PowerSync clients to the same database file.
22-
* Improved concurrent SQLite connection support accross various platforms. All platforms now use a single write connection and multiple read connections for concurrent read queries.
23-
* Added the ability to open a SQLite database given a custom `dbDirectory` path. This is currently not supported on iOS due to internal driver restrictions.
27+
* Added queing protection and warnings when connecting multiple PowerSync clients to the same
28+
database file.
29+
* Improved concurrent SQLite connection support accross various platforms. All platforms now use a
30+
single write connection and multiple read connections for concurrent read queries.
31+
* Added the ability to open a SQLite database given a custom `dbDirectory` path. This is currently
32+
not supported on iOS due to internal driver restrictions.
2433
* Internaly improved the linking of SQLite for iOS.
2534
* Enabled Full Text Search on iOS platforms.
2635
* Added the ability to update the schema for existing PowerSync clients.
27-
* Fixed bug where local only, insert only and view name overrides were not applied for schema tables.
28-
* The Android SQLite driver now uses the [Xerial JDBC library](https://github.com/xerial/sqlite-jdbc). This removes the requirement for users to add the jitpack Maven repository to their projects.
36+
* Fixed bug where local only, insert only and view name overrides were not applied for schema
37+
tables.
38+
* The Android SQLite driver now uses
39+
the [Xerial JDBC library](https://github.com/xerial/sqlite-jdbc). This removes the requirement for
40+
users to add the jitpack Maven repository to their projects.
41+
2942
```diff
3043
// settings.gradle.kts example
3144
repositories {
@@ -53,8 +66,10 @@ Sqlite operation failure database is locked attempted to run migration and faile
5366

5467
## 1.0.0-BETA24
5568

56-
* Improve internal handling of watch queries to avoid issues where updates are not being received due to transaction commits occurring after the query is run.
57-
* Fix issue in JVM build where `columnNames` was throwing an error due to the index of the JDBC driver starting at 1 instead of 0 as in the other drivers/
69+
* Improve internal handling of watch queries to avoid issues where updates are not being received
70+
due to transaction commits occurring after the query is run.
71+
* Fix issue in JVM build where `columnNames` was throwing an error due to the index of the JDBC
72+
driver starting at 1 instead of 0 as in the other drivers/
5873
* Throw and not just catch `CancellationExceptions` in `runWrappedSuspending`
5974

6075
## 1.0.0-BETA23
@@ -72,14 +87,18 @@ Sqlite operation failure database is locked attempted to run migration and faile
7287

7388
## 1.0.0-BETA20
7489

75-
* Add cursor optional functions: `getStringOptional`, `getLongOptional`, `getDoubleOptional`, `getBooleanOptional` and `getBytesOptional` when using the column name which allow for optional return types
90+
* Add cursor optional functions: `getStringOptional`, `getLongOptional`, `getDoubleOptional`,
91+
`getBooleanOptional` and `getBytesOptional` when using the column name which allow for optional
92+
return types
7693
* Throw errors for invalid column on all cursor functions
77-
* `getString`, `getLong`, `getBytes`, `getDouble` and `getBoolean` used with the column name will now throw an error for non-null values and expect a non optional return type
94+
* `getString`, `getLong`, `getBytes`, `getDouble` and `getBoolean` used with the column name will
95+
now throw an error for non-null values and expect a non optional return type
7896

7997
## 1.0.0-BETA19
8098

8199
* Allow cursor to get values by column name e.g. `getStringOptional("id")`
82-
* BREAKING CHANGE: If you were using `SqlCursor` from SqlDelight previously for your own custom mapper then you must now change to `SqlCursor` exported by the PowerSync module.
100+
* BREAKING CHANGE: If you were using `SqlCursor` from SqlDelight previously for your own custom
101+
mapper then you must now change to `SqlCursor` exported by the PowerSync module.
83102

84103
Previously you would import it like this:
85104

@@ -95,7 +114,8 @@ Sqlite operation failure database is locked attempted to run migration and faile
95114

96115
## 1.0.0-BETA18
97116

98-
* BREAKING CHANGE: Move from async sqldelight calls to synchronous calls. This will only affect `readTransaction` and `writeTransaction`where the callback function is no longer asynchronous.
117+
* BREAKING CHANGE: Move from async sqldelight calls to synchronous calls. This will only affect
118+
`readTransaction` and `writeTransaction`where the callback function is no longer asynchronous.
99119

100120
## 1.0.0-BETA17
101121

@@ -104,7 +124,8 @@ Sqlite operation failure database is locked attempted to run migration and faile
104124
## 1.0.0-BETA16
105125

106126
* Add `close` method to database methods
107-
* Throw when error is a `CancellationError` and remove invalidation for all errors in `streamingSync` catch.
127+
* Throw when error is a `CancellationError` and remove invalidation for all errors in
128+
`streamingSync` catch.
108129

109130
## 1.0.0-BETA15
110131

@@ -118,7 +139,8 @@ Sqlite operation failure database is locked attempted to run migration and faile
118139

119140
## 1.0.0-BETA13
120141

121-
* Move iOS database driver to use IO dispatcher which should avoid race conditions and improve performance.
142+
* Move iOS database driver to use IO dispatcher which should avoid race conditions and improve
143+
performance.
122144

123145
## 1.0.0-BETA12
124146

@@ -135,7 +157,8 @@ Sqlite operation failure database is locked attempted to run migration and faile
135157
## 1.0.0-BETA9
136158

137159
* Re-enable SKIE `SuspendInterop`
138-
* Move transaction functions out of `PowerSyncTransactionFactory` to avoid threading issues in Swift SDK
160+
* Move transaction functions out of `PowerSyncTransactionFactory` to avoid threading issues in Swift
161+
SDK
139162

140163
## 1.0.0-BETA8
141164

@@ -164,37 +187,43 @@ Sqlite operation failure database is locked attempted to run migration and faile
164187
* Add `waitForFirstSync` function - which resolves after the initial sync is completed
165188
* Upgrade to Kotlin 2.0.20 - should not cause any issues with users who are still on Kotlin 1.9
166189
* Upgrade `powersync-sqlite-core` to 0.3.0 - improves incremental sync performance
167-
* Add client sync parameters - which allows you specify sync parameters from the client https://docs.powersync.com/usage/sync-rules/advanced-topics/client-parameters-beta
190+
* Add client sync parameters - which allows you specify sync parameters from the
191+
client https://docs.powersync.com/usage/sync-rules/advanced-topics/client-parameters-beta
192+
168193
```kotlin
169194
val params = JsonParam.Map(
170-
mapOf(
171-
"name" to JsonParam.String("John Doe"),
172-
"age" to JsonParam.Number(30),
173-
"isStudent" to JsonParam.Boolean(false)
174-
)
195+
mapOf(
196+
"name" to JsonParam.String("John Doe"),
197+
"age" to JsonParam.Number(30),
198+
"isStudent" to JsonParam.Boolean(false)
199+
)
175200
)
176201

177202
connect(
178-
...
179-
params = params
203+
...
204+
params = params
180205
)
181206
```
207+
182208
* Add schema validation when schema is generated
183-
* Add warning message if there is a crudItem in the queue that has not yet been synced and after a delay rerun the upload
209+
* Add warning message if there is a crudItem in the queue that has not yet been synced and after a
210+
delay rerun the upload
184211

185212
## 1.0.0-BETA2
186213

187214
* Publish persistence package
188215

189216
## 1.0.0-BETA1
190217

191-
* Improve API by changing from Builder pattern to simply instantiating the database `PowerSyncDatabase`
218+
* Improve API by changing from Builder pattern to simply instantiating the database
219+
`PowerSyncDatabase`
192220
E.g. `val db = PowerSyncDatabase(factory, schema)`
193221
* Use callback context in transactions
194222
E.g. `db.writeTransaction{ ctx -> ctx.execute(...) }`
195223
* Removed unnecessary expiredAt field
196224
* Added table max column validation as there is a hard limit of 63 columns
197225
* Moved SQLDelight models to a separate module to reduce export size
198-
* Replaced default Logger with [Kermit Logger](https://kermit.touchlab.co/) which allows users to more easily use and/or change Logger settings
226+
* Replaced default Logger with [Kermit Logger](https://kermit.touchlab.co/) which allows users to
227+
more easily use and/or change Logger settings
199228
* Add `retryDelay` and `crudThrottle` options when setting up database connection
200229
* Changed `_viewNameOverride` to `viewNameOverride`

connectors/supabase/build.gradle.kts

+1
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ kotlin {
2828
implementation(libs.kotlinx.coroutines.core)
2929
implementation(libs.supabase.client)
3030
api(libs.supabase.auth)
31+
api(libs.supabase.storage)
3132
}
3233
}
3334
}

connectors/supabase/src/commonMain/kotlin/com/powersync/connector/supabase/SupabaseConnector.kt

+23-2
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,9 @@ import io.github.jan.supabase.auth.user.UserSession
1717
import io.github.jan.supabase.createSupabaseClient
1818
import io.github.jan.supabase.postgrest.Postgrest
1919
import io.github.jan.supabase.postgrest.from
20+
import io.github.jan.supabase.storage.BucketApi
21+
import io.github.jan.supabase.storage.Storage
22+
import io.github.jan.supabase.storage.storage
2023
import io.ktor.client.plugins.HttpSend
2124
import io.ktor.client.plugins.plugin
2225
import io.ktor.client.statement.bodyAsText
@@ -31,6 +34,7 @@ import kotlinx.serialization.json.Json
3134
public class SupabaseConnector(
3235
public val supabaseClient: SupabaseClient,
3336
public val powerSyncEndpoint: String,
37+
private val storageBucket: String? = null,
3438
) : PowerSyncBackendConnector() {
3539
private var errorCode: String? = null
3640

@@ -52,17 +56,29 @@ public class SupabaseConnector(
5256
}
5357
}
5458

59+
public fun storageBucket(): BucketApi {
60+
if (storageBucket == null) {
61+
throw Exception("No bucket has been specified")
62+
}
63+
return supabaseClient.storage[storageBucket]
64+
}
65+
5566
public constructor(
5667
supabaseUrl: String,
5768
supabaseKey: String,
5869
powerSyncEndpoint: String,
70+
storageBucket: String? = null,
5971
) : this(
6072
supabaseClient =
6173
createSupabaseClient(supabaseUrl, supabaseKey) {
6274
install(Auth)
6375
install(Postgrest)
76+
if (storageBucket != null) {
77+
install(Storage)
78+
}
6479
},
6580
powerSyncEndpoint = powerSyncEndpoint,
81+
storageBucket = storageBucket,
6682
)
6783

6884
init {
@@ -81,7 +97,10 @@ public class SupabaseConnector(
8197
val responseText = response.bodyAsText()
8298

8399
try {
84-
val error = Json { coerceInputValues = true }.decodeFromString<Map<String, String?>>(responseText)
100+
val error =
101+
Json { coerceInputValues = true }.decodeFromString<Map<String, String?>>(
102+
responseText,
103+
)
85104
errorCode = error["code"]
86105
} catch (e: Exception) {
87106
Logger.e("Failed to parse error response: $e")
@@ -139,7 +158,9 @@ public class SupabaseConnector(
139158
check(supabaseClient.auth.sessionStatus.value is SessionStatus.Authenticated) { "Supabase client is not authenticated" }
140159

141160
// Use Supabase token for PowerSync
142-
val session = supabaseClient.auth.currentSessionOrNull() ?: error("Could not fetch Supabase credentials")
161+
val session =
162+
supabaseClient.auth.currentSessionOrNull()
163+
?: error("Could not fetch Supabase credentials")
143164

144165
check(session.user != null) { "No user data" }
145166

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
package com.powersync.connector.supabase
2+
3+
import com.powersync.attachments.Attachment
4+
import com.powersync.attachments.RemoteStorage
5+
import kotlinx.coroutines.flow.Flow
6+
import kotlinx.coroutines.flow.flowOf
7+
8+
/**
9+
* Implementation of [RemoteStorage] that uses Supabase as the backend storage provider.
10+
*
11+
* @property connector The Supabase connector used to interact with the Supabase storage bucket.
12+
*/
13+
public class SupabaseRemoteStorage(
14+
public val connector: SupabaseConnector,
15+
) : RemoteStorage {
16+
/**
17+
* Uploads a file to the Supabase storage bucket.
18+
*
19+
* @param fileData A [Flow] of [ByteArray] representing the file data to be uploaded.
20+
* @param attachment The [Attachment] metadata associated with the file.
21+
* @throws IllegalStateException If the attachment size is not specified.
22+
*/
23+
override suspend fun uploadFile(
24+
fileData: Flow<ByteArray>,
25+
attachment: Attachment,
26+
) {
27+
val byteSize =
28+
attachment.size?.toInt() ?: error("Cannot upload a file with no byte size specified")
29+
// Supabase wants a single ByteArray
30+
val buffer = ByteArray(byteSize)
31+
var position = 0
32+
fileData.collect {
33+
it.copyInto(buffer, destinationOffset = position)
34+
position += it.size
35+
}
36+
37+
connector.storageBucket().upload(attachment.filename, buffer)
38+
}
39+
40+
/**
41+
* Downloads a file from the Supabase storage bucket.
42+
*
43+
* @param attachment The [Attachment] record associated with the file to be downloaded.
44+
* @return A [Flow] of [ByteArray] representing the file data.
45+
*/
46+
override suspend fun downloadFile(attachment: Attachment): Flow<ByteArray> =
47+
flowOf(connector.storageBucket().downloadAuthenticated(attachment.filename))
48+
49+
/**
50+
* Deletes a file from the Supabase storage bucket.
51+
*
52+
* @param attachment The [Attachment] record associated with the file to be deleted.
53+
*/
54+
override suspend fun deleteFile(attachment: Attachment) {
55+
connector.storageBucket().delete(attachment.filename)
56+
}
57+
}

core/README.md

+7-1
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,14 @@ structure:
2424
## Note on SQLDelight
2525

2626
The PowerSync core module, internally makes use
27-
of [SQLDelight](https://sqldelight.github.io/sqldelight/latest/) for it database API and typesafe database
27+
of [SQLDelight](https://sqldelight.github.io/sqldelight/latest/) for it database API and typesafe
28+
database
2829
query generation.
2930

3031
The PowerSync core module does not currently support integrating with SQLDelight from client
3132
applications.
33+
34+
## Attachment Helpers
35+
36+
This module contains attachment helpers under the `com.powersync.attachments` package. See
37+
the [Attachment Helpers README](./src/commonMain/kotlin/com/powersync/attachments/README.md)

0 commit comments

Comments
 (0)