Skip to content

Attachments package #159

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 52 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
52 commits
Select commit Hold shift + click to select a range
ee59eae
wip
stevensJourney Apr 1, 2025
f3ff250
wip
stevensJourney Apr 1, 2025
dbcea9f
wip tests
stevensJourney Apr 2, 2025
7de65fd
include as package in core module
stevensJourney Apr 2, 2025
adbacfb
wip: readme
stevensJourney Apr 3, 2025
d18e4d0
README updates
stevensJourney Apr 3, 2025
9a9955d
improve storage adapters
stevensJourney Apr 3, 2025
6172c08
cleanup
stevensJourney Apr 3, 2025
eb268b7
Merge remote-tracking branch 'origin/main' into attachments-package
stevensJourney Apr 3, 2025
408db0b
fix tests
stevensJourney Apr 3, 2025
6fafc3e
cleanup tests
stevensJourney Apr 3, 2025
6530ed0
cleanup deletes
stevensJourney Apr 3, 2025
561c02a
wip: Android Attachments Demo
stevensJourney Apr 4, 2025
f414eb2
Switch to open class.
stevensJourney Apr 4, 2025
67eee58
fix demo navigation state
stevensJourney Apr 6, 2025
d00f0b1
improve locking to avoid rare race conditions
stevensJourney Apr 7, 2025
c9b5077
optional attachments in demo
stevensJourney Apr 7, 2025
09eb616
cleanup service
stevensJourney Apr 7, 2025
439bb74
Merge remote-tracking branch 'origin/main' into attachments-package
stevensJourney Apr 7, 2025
8d8196c
upate tests
stevensJourney Apr 7, 2025
4efc67d
fix tests
stevensJourney Apr 7, 2025
fe0e67e
cleanup
stevensJourney Apr 8, 2025
79361c6
Merge remote-tracking branch 'origin/main' into attachments-package
stevensJourney Apr 8, 2025
8d19eed
update changelog
stevensJourney Apr 8, 2025
f1c698a
cleanup
stevensJourney Apr 8, 2025
2da25d6
Restructure the attachments Readme with some simplified wording
benitav Apr 16, 2025
2255da2
check if file exists before delete
stevensJourney Apr 16, 2025
48b3441
Move Supabase Remote Storage to Connectors module
stevensJourney Apr 16, 2025
6dfedd5
Use enum throughout for AttachmentState
stevensJourney Apr 16, 2025
75f5b28
Sync Kotlin Gradle Plugin version
stevensJourney Apr 16, 2025
5dba44a
throw IllegalStateException when deleting a missing attachment file
stevensJourney Apr 16, 2025
f729d42
use fileSystem SystemFileSystem on IOLocalStorageAdapter
stevensJourney Apr 16, 2025
66d1f9e
use Duration for Flow throttle extension
stevensJourney Apr 16, 2025
35bbd4f
Fix periodic syncing loop
stevensJourney Apr 16, 2025
e8fcf60
revert test db path
stevensJourney Apr 16, 2025
c48a288
rename SUPABASE_STORAGE_BUCKET
stevensJourney Apr 16, 2025
31d7e5c
fix build
stevensJourney Apr 16, 2025
ff39c87
Merge remote-tracking branch 'origin/main' into attachments-package
stevensJourney Apr 16, 2025
89ea92c
update shared build plugin
stevensJourney Apr 16, 2025
b0723b1
wip: improve coroutine scope concurrency
stevensJourney Apr 16, 2025
72c8288
accept input scope
stevensJourney Apr 17, 2025
93e3381
lint fixes
stevensJourney Apr 17, 2025
6cdbb09
fix ios test build
stevensJourney Apr 17, 2025
032e8b6
Readme polish
benitav Apr 17, 2025
ec011a5
Cleanup KDoc comments. Sync with Swift implementation.
stevensJourney Apr 17, 2025
7c5152d
add changelog entry
stevensJourney Apr 17, 2025
b4d4326
cleanup
stevensJourney Apr 17, 2025
f4e9b78
test
stevensJourney Apr 17, 2025
5fd3f84
test
stevensJourney Apr 17, 2025
39ea649
test
stevensJourney Apr 17, 2025
d1760c5
test
stevensJourney Apr 17, 2025
5b2f590
cleanup changelog
stevensJourney Apr 17, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
79 changes: 54 additions & 25 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
# Changelog

## 1.0.0-BETA31

* Added helpers for Attachment syncing.

## 1.0.0-BETA30

* Fix a deadlock when calling `connect()` immediately after opening a database.
Expand All @@ -10,22 +14,31 @@
* Fix potential race condition between jobs in `connect()` and `disconnect()`.
* [JVM Windows] Fixed PowerSync Extension temporary file deletion error on process shutdown.
* [iOS] Fixed issue where automatic driver migrations would fail with the error:

```
Sqlite operation failure database is locked attempted to run migration and failed. closing connection
```

* Fix race condition causing data received during uploads not to be applied.

## 1.0.0-BETA28

* Update PowerSync SQLite core extension to 0.3.12.
* Added queing protection and warnings when connecting multiple PowerSync clients to the same database file.
* Improved concurrent SQLite connection support accross various platforms. All platforms now use a single write connection and multiple read connections for concurrent read queries.
* 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.
* Added queing protection and warnings when connecting multiple PowerSync clients to the same
database file.
* Improved concurrent SQLite connection support accross various platforms. All platforms now use a
single write connection and multiple read connections for concurrent read queries.
* 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.
* Internaly improved the linking of SQLite for iOS.
* Enabled Full Text Search on iOS platforms.
* Added the ability to update the schema for existing PowerSync clients.
* Fixed bug where local only, insert only and view name overrides were not applied for schema tables.
* 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.
* Fixed bug where local only, insert only and view name overrides were not applied for schema
tables.
* 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.

```diff
// settings.gradle.kts example
repositories {
Expand Down Expand Up @@ -53,8 +66,10 @@ Sqlite operation failure database is locked attempted to run migration and faile

## 1.0.0-BETA24

* 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.
* 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/
* 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.
* 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/
* Throw and not just catch `CancellationExceptions` in `runWrappedSuspending`

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

## 1.0.0-BETA20

* Add cursor optional functions: `getStringOptional`, `getLongOptional`, `getDoubleOptional`, `getBooleanOptional` and `getBytesOptional` when using the column name which allow for optional return types
* Add cursor optional functions: `getStringOptional`, `getLongOptional`, `getDoubleOptional`,
`getBooleanOptional` and `getBytesOptional` when using the column name which allow for optional
return types
* Throw errors for invalid column on all cursor functions
* `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
* `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

## 1.0.0-BETA19

* Allow cursor to get values by column name e.g. `getStringOptional("id")`
* 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.
* 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.

Previously you would import it like this:

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

## 1.0.0-BETA18

* 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.
* 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.

## 1.0.0-BETA17

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

* Add `close` method to database methods
* Throw when error is a `CancellationError` and remove invalidation for all errors in `streamingSync` catch.
* Throw when error is a `CancellationError` and remove invalidation for all errors in
`streamingSync` catch.

## 1.0.0-BETA15

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

## 1.0.0-BETA13

* Move iOS database driver to use IO dispatcher which should avoid race conditions and improve performance.
* Move iOS database driver to use IO dispatcher which should avoid race conditions and improve
performance.

## 1.0.0-BETA12

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

* Re-enable SKIE `SuspendInterop`
* Move transaction functions out of `PowerSyncTransactionFactory` to avoid threading issues in Swift SDK
* Move transaction functions out of `PowerSyncTransactionFactory` to avoid threading issues in Swift
SDK

## 1.0.0-BETA8

Expand Down Expand Up @@ -164,37 +187,43 @@ Sqlite operation failure database is locked attempted to run migration and faile
* Add `waitForFirstSync` function - which resolves after the initial sync is completed
* Upgrade to Kotlin 2.0.20 - should not cause any issues with users who are still on Kotlin 1.9
* Upgrade `powersync-sqlite-core` to 0.3.0 - improves incremental sync performance
* 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
* 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

```kotlin
val params = JsonParam.Map(
mapOf(
"name" to JsonParam.String("John Doe"),
"age" to JsonParam.Number(30),
"isStudent" to JsonParam.Boolean(false)
)
mapOf(
"name" to JsonParam.String("John Doe"),
"age" to JsonParam.Number(30),
"isStudent" to JsonParam.Boolean(false)
)
)

connect(
...
params = params
...
params = params
)
```

* Add schema validation when schema is generated
* Add warning message if there is a crudItem in the queue that has not yet been synced and after a delay rerun the upload
* Add warning message if there is a crudItem in the queue that has not yet been synced and after a
delay rerun the upload

## 1.0.0-BETA2

* Publish persistence package

## 1.0.0-BETA1

* Improve API by changing from Builder pattern to simply instantiating the database `PowerSyncDatabase`
* Improve API by changing from Builder pattern to simply instantiating the database
`PowerSyncDatabase`
E.g. `val db = PowerSyncDatabase(factory, schema)`
* Use callback context in transactions
E.g. `db.writeTransaction{ ctx -> ctx.execute(...) }`
* Removed unnecessary expiredAt field
* Added table max column validation as there is a hard limit of 63 columns
* Moved SQLDelight models to a separate module to reduce export size
* Replaced default Logger with [Kermit Logger](https://kermit.touchlab.co/) which allows users to more easily use and/or change Logger settings
* Replaced default Logger with [Kermit Logger](https://kermit.touchlab.co/) which allows users to
more easily use and/or change Logger settings
* Add `retryDelay` and `crudThrottle` options when setting up database connection
* Changed `_viewNameOverride` to `viewNameOverride`
1 change: 1 addition & 0 deletions connectors/supabase/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ kotlin {
implementation(libs.kotlinx.coroutines.core)
implementation(libs.supabase.client)
api(libs.supabase.auth)
api(libs.supabase.storage)
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@ import io.github.jan.supabase.auth.user.UserSession
import io.github.jan.supabase.createSupabaseClient
import io.github.jan.supabase.postgrest.Postgrest
import io.github.jan.supabase.postgrest.from
import io.github.jan.supabase.storage.BucketApi
import io.github.jan.supabase.storage.Storage
import io.github.jan.supabase.storage.storage
import io.ktor.client.plugins.HttpSend
import io.ktor.client.plugins.plugin
import io.ktor.client.statement.bodyAsText
Expand All @@ -31,6 +34,7 @@ import kotlinx.serialization.json.Json
public class SupabaseConnector(
public val supabaseClient: SupabaseClient,
public val powerSyncEndpoint: String,
private val storageBucket: String? = null,
) : PowerSyncBackendConnector() {
private var errorCode: String? = null

Expand All @@ -52,17 +56,29 @@ public class SupabaseConnector(
}
}

public fun storageBucket(): BucketApi {
if (storageBucket == null) {
throw Exception("No bucket has been specified")
}
return supabaseClient.storage[storageBucket]
}

public constructor(
supabaseUrl: String,
supabaseKey: String,
powerSyncEndpoint: String,
storageBucket: String? = null,
) : this(
supabaseClient =
createSupabaseClient(supabaseUrl, supabaseKey) {
install(Auth)
install(Postgrest)
if (storageBucket != null) {
install(Storage)
}
},
powerSyncEndpoint = powerSyncEndpoint,
storageBucket = storageBucket,
)

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

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

// Use Supabase token for PowerSync
val session = supabaseClient.auth.currentSessionOrNull() ?: error("Could not fetch Supabase credentials")
val session =
supabaseClient.auth.currentSessionOrNull()
?: error("Could not fetch Supabase credentials")

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

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
package com.powersync.connector.supabase

import com.powersync.attachments.Attachment
import com.powersync.attachments.RemoteStorage
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flowOf

/**
* Implementation of [RemoteStorage] that uses Supabase as the backend storage provider.
*
* @property connector The Supabase connector used to interact with the Supabase storage bucket.
*/
public class SupabaseRemoteStorage(
public val connector: SupabaseConnector,
) : RemoteStorage {
/**
* Uploads a file to the Supabase storage bucket.
*
* @param fileData A [Flow] of [ByteArray] representing the file data to be uploaded.
* @param attachment The [Attachment] metadata associated with the file.
* @throws IllegalStateException If the attachment size is not specified.
*/
override suspend fun uploadFile(
fileData: Flow<ByteArray>,
attachment: Attachment,
) {
val byteSize =
attachment.size?.toInt() ?: error("Cannot upload a file with no byte size specified")
// Supabase wants a single ByteArray
val buffer = ByteArray(byteSize)
var position = 0
fileData.collect {
it.copyInto(buffer, destinationOffset = position)
position += it.size
}

connector.storageBucket().upload(attachment.filename, buffer)
}

/**
* Downloads a file from the Supabase storage bucket.
*
* @param attachment The [Attachment] record associated with the file to be downloaded.
* @return A [Flow] of [ByteArray] representing the file data.
*/
override suspend fun downloadFile(attachment: Attachment): Flow<ByteArray> =
flowOf(connector.storageBucket().downloadAuthenticated(attachment.filename))

/**
* Deletes a file from the Supabase storage bucket.
*
* @param attachment The [Attachment] record associated with the file to be deleted.
*/
override suspend fun deleteFile(attachment: Attachment) {
connector.storageBucket().delete(attachment.filename)
}
}
8 changes: 7 additions & 1 deletion core/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,14 @@ structure:
## Note on SQLDelight

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

The PowerSync core module does not currently support integrating with SQLDelight from client
applications.

## Attachment Helpers

This module contains attachment helpers under the `com.powersync.attachments` package. See
the [Attachment Helpers README](./src/commonMain/kotlin/com/powersync/attachments/README.md)
Loading