Skip to content

Commit 2101f0e

Browse files
committed
Merge remote-tracking branch 'origin/main' into fix-loading-extension-from-jar
2 parents b0c57ab + a7b0eb5 commit 2101f0e

File tree

32 files changed

+575
-65
lines changed

32 files changed

+575
-65
lines changed

.github/workflows/docs-deploy.yml

+52
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
name: Deploy Docs
2+
3+
on:
4+
push:
5+
branches: [ 'main' ]
6+
7+
permissions:
8+
contents: read
9+
pages: write
10+
id-token: write
11+
12+
jobs:
13+
build:
14+
runs-on: ubuntu-latest
15+
steps:
16+
- uses: actions/checkout@v4
17+
- name: Validate Gradle Wrapper
18+
uses: gradle/wrapper-validation-action@v1
19+
- uses: actions/cache@v3
20+
with:
21+
path: ~/.konan
22+
key: ${{ runner.os }}-${{ hashFiles('**/.lock') }}
23+
- name: Set up JDK 17
24+
uses: actions/setup-java@v3
25+
with:
26+
java-version: '17'
27+
distribution: 'temurin'
28+
- name: Set up Gradle
29+
uses: gradle/actions/setup-gradle@v4
30+
- name: Build Docs
31+
run: |
32+
./gradlew \
33+
-PGITHUB_PUBLISH_TOKEN=${{ secrets.GITHUB_TOKEN }} \
34+
dokkaGenerate
35+
shell: bash
36+
- name: Upload static files as artifact
37+
id: deployment
38+
uses: actions/upload-pages-artifact@v3
39+
with:
40+
path: build/dokka/html
41+
42+
# Deployment job
43+
deploy:
44+
environment:
45+
name: github-pages
46+
url: ${{ steps.deployment.outputs.page_url }}
47+
runs-on: ubuntu-latest
48+
needs: build
49+
steps:
50+
- name: Deploy to GitHub Pages
51+
id: deployment
52+
uses: actions/deploy-pages@v4

CHANGELOG.md

+6-1
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,14 @@
11
# Changelog
22

3+
## 1.0.0-BETA32
4+
5+
* Added `onChange` method to the PowerSync client. This allows for observing table changes.
6+
* Fix loading native PowerSync extension for Java targets.
7+
38
## 1.0.0-BETA31
49

510
* Added helpers for Attachment syncing.
6-
* Fix loading native PowerSync extension for Java targets.
11+
* Fix `getNextCrudTransaction()` only returning a single item.
712

813
## 1.0.0-BETA30
914

build.gradle.kts

+61-1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,9 @@
1+
import com.sun.net.httpserver.HttpExchange
2+
import com.sun.net.httpserver.HttpServer
3+
import java.net.InetSocketAddress
4+
import java.net.URLDecoder
5+
import java.nio.file.Files
6+
17
plugins {
28
alias(libs.plugins.jetbrainsCompose) apply false
39
alias(libs.plugins.compose.compiler) apply false
@@ -16,6 +22,8 @@ plugins {
1622
alias(libs.plugins.kotlinter) apply false
1723
alias(libs.plugins.keeper) apply false
1824
alias(libs.plugins.kotlin.atomicfu) apply false
25+
id("org.jetbrains.dokka") version libs.versions.dokkaBase
26+
id("dokka-convention")
1927
}
2028

2129
allprojects {
@@ -54,6 +62,58 @@ subprojects {
5462
version = LIBRARY_VERSION
5563
}
5664

57-
tasks.register<Delete>("clean") {
65+
tasks.getByName<Delete>("clean") {
5866
delete(rootProject.layout.buildDirectory)
5967
}
68+
69+
// Merges individual module docs into a single HTML output
70+
dependencies {
71+
dokka(project(":core:"))
72+
dokka(project(":connectors:supabase"))
73+
dokka(project(":compose:"))
74+
}
75+
76+
dokka {
77+
moduleName.set("PowerSync Kotlin")
78+
}
79+
80+
// Serve the generated Dokka documentation using a simple HTTP server
81+
// File changes are not watched here
82+
tasks.register("serveDokka") {
83+
group = "dokka"
84+
dependsOn("dokkaGenerate")
85+
doLast {
86+
val server = HttpServer.create(InetSocketAddress(0), 0)
87+
val root = file("build/dokka/html")
88+
89+
val handler =
90+
com.sun.net.httpserver.HttpHandler { exchange: HttpExchange ->
91+
val rawPath = exchange.requestURI.path
92+
val cleanPath = URLDecoder.decode(rawPath.removePrefix("/"), "UTF-8")
93+
val requestedFile = File(root, cleanPath)
94+
95+
val file =
96+
when {
97+
requestedFile.exists() && !requestedFile.isDirectory -> requestedFile
98+
else -> File(root, "index.html") // fallback
99+
}
100+
101+
val contentType =
102+
Files.probeContentType(file.toPath()) ?: "application/octet-stream"
103+
val bytes = file.readBytes()
104+
exchange.responseHeaders.add("Content-Type", contentType)
105+
exchange.sendResponseHeaders(200, bytes.size.toLong())
106+
exchange.responseBody.use { it.write(bytes) }
107+
}
108+
109+
server.createContext("/", handler)
110+
server.executor = null
111+
server.start()
112+
113+
println("📘 Serving Dokka docs at http://localhost:${server.address.port}/")
114+
println("Press Ctrl+C to stop.")
115+
116+
// Keep the task alive
117+
Thread.currentThread().join()
118+
}
119+
}

compose/build.gradle.kts

+5
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ plugins {
88
alias(libs.plugins.compose.compiler)
99
alias(libs.plugins.kotlinter)
1010
id("com.powersync.plugins.sonatype")
11+
id("dokka-convention")
1112
}
1213

1314
kotlin {
@@ -45,3 +46,7 @@ android {
4546
}
4647

4748
setupGithubRepository()
49+
50+
dokka {
51+
moduleName.set("PowerSync Compose")
52+
}

connectors/supabase/build.gradle.kts

+5
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ plugins {
88
alias(libs.plugins.androidLibrary)
99
alias(libs.plugins.kotlinter)
1010
id("com.powersync.plugins.sonatype")
11+
id("dokka-convention")
1112
}
1213

1314
kotlin {
@@ -51,3 +52,7 @@ android {
5152
}
5253

5354
setupGithubRepository()
55+
56+
dokka {
57+
moduleName.set("PowerSync Supabase Connector")
58+
}

core/build.gradle.kts

+5
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ plugins {
1919
id("com.powersync.plugins.sharedbuild")
2020
alias(libs.plugins.mokkery)
2121
alias(libs.plugins.kotlin.atomicfu)
22+
id("dokka-convention")
2223
}
2324

2425
val binariesFolder = project.layout.buildDirectory.dir("binaries/desktop")
@@ -295,3 +296,7 @@ tasks.withType<KotlinTest> {
295296
}
296297
}
297298
setupGithubRepository()
299+
300+
dokka {
301+
moduleName.set("PowerSync Core")
302+
}
+2-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
@Suppress("EXPECT_ACTUAL_CLASSIFIERS_ARE_IN_BETA_WARNING")
2-
public actual object BuildConfig {
3-
public actual val isDebug: Boolean
2+
internal actual object BuildConfig {
3+
actual val isDebug: Boolean
44
get() = com.powersync.BuildConfig.DEBUG
55
}

core/src/appleMain/kotlin/BuildConfig.kt

+2-2
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import kotlin.experimental.ExperimentalNativeApi
22
import kotlin.native.Platform
33

44
@Suppress("EXPECT_ACTUAL_CLASSIFIERS_ARE_IN_BETA_WARNING")
5-
public actual object BuildConfig {
5+
internal actual object BuildConfig {
66
@OptIn(ExperimentalNativeApi::class)
7-
public actual val isDebug: Boolean = Platform.isDebugBinary
7+
actual val isDebug: Boolean = Platform.isDebugBinary
88
}

core/src/commonIntegrationTest/kotlin/com/powersync/DatabaseTest.kt

+49
Original file line numberDiff line numberDiff line change
@@ -183,6 +183,25 @@ class DatabaseTest {
183183
}
184184
}
185185

186+
@Test
187+
fun testTableChangesUpdates() =
188+
databaseTest {
189+
turbineScope {
190+
val query = database.onChange(tables = setOf("users")).testIn(this)
191+
192+
database.execute(
193+
"INSERT INTO users (id, name, email) VALUES (uuid(), ?, ?)",
194+
listOf("Test", "[email protected]"),
195+
)
196+
197+
val changeSet = query.awaitItem()
198+
changeSet.count() shouldBe 1
199+
changeSet.contains("users") shouldBe true
200+
201+
query.cancel()
202+
}
203+
}
204+
186205
@Test
187206
fun testClosingReadPool() =
188207
databaseTest {
@@ -369,4 +388,34 @@ class DatabaseTest {
369388
val count = database.get("SELECT COUNT(*) from people") { it.getLong(0)!! }
370389
count shouldBe 1
371390
}
391+
392+
@Test
393+
fun testCrudTransaction() =
394+
databaseTest {
395+
database.execute(
396+
"INSERT INTO users (id, name, email) VALUES (uuid(), ?, ?)",
397+
listOf("a", "[email protected]"),
398+
)
399+
400+
database.writeTransaction {
401+
it.execute(
402+
"INSERT INTO users (id, name, email) VALUES (uuid(), ?, ?)",
403+
listOf("b", "[email protected]"),
404+
)
405+
it.execute(
406+
"INSERT INTO users (id, name, email) VALUES (uuid(), ?, ?)",
407+
listOf("c", "[email protected]"),
408+
)
409+
}
410+
411+
var transaction = database.getNextCrudTransaction()
412+
transaction!!.crud shouldHaveSize 1
413+
transaction.complete(null)
414+
415+
transaction = database.getNextCrudTransaction()
416+
transaction!!.crud shouldHaveSize 2
417+
transaction.complete(null)
418+
419+
database.getNextCrudTransaction() shouldBe null
420+
}
372421
}
+2-2
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
@Suppress("EXPECT_ACTUAL_CLASSIFIERS_ARE_IN_BETA_WARNING")
2-
public expect object BuildConfig {
3-
public val isDebug: Boolean
2+
internal expect object BuildConfig {
3+
val isDebug: Boolean
44
}

core/src/commonMain/kotlin/com/powersync/bucket/BucketStorage.kt

+5
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,11 @@ internal interface BucketStorage {
1414

1515
fun nextCrudItem(transaction: PowerSyncTransaction): CrudEntry?
1616

17+
fun getCrudItemsByTransactionId(
18+
transactionId: Int,
19+
transaction: PowerSyncTransaction,
20+
): List<CrudEntry>
21+
1722
suspend fun hasCrud(): Boolean
1823

1924
fun hasCrud(transaction: PowerSyncTransaction): Boolean

core/src/commonMain/kotlin/com/powersync/bucket/BucketStorageImpl.kt

+14-3
Original file line numberDiff line numberDiff line change
@@ -41,13 +41,24 @@ internal class BucketStorageImpl(
4141
return id ?: throw IllegalStateException("Client ID not found")
4242
}
4343

44-
override suspend fun nextCrudItem(): CrudEntry? = db.getOptional(sql = nextCrudQuery, mapper = nextCrudMapper)
44+
override suspend fun nextCrudItem(): CrudEntry? = db.getOptional(sql = nextCrudQuery, mapper = crudEntryMapper)
4545

4646
override fun nextCrudItem(transaction: PowerSyncTransaction): CrudEntry? =
47-
transaction.getOptional(sql = nextCrudQuery, mapper = nextCrudMapper)
47+
transaction.getOptional(sql = nextCrudQuery, mapper = crudEntryMapper)
48+
49+
override fun getCrudItemsByTransactionId(
50+
transactionId: Int,
51+
transaction: PowerSyncTransaction,
52+
): List<CrudEntry> =
53+
transaction.getAll(
54+
sql = transactionCrudQuery,
55+
parameters = listOf(transactionId),
56+
mapper = crudEntryMapper,
57+
)
4858

4959
private val nextCrudQuery = "SELECT id, tx_id, data FROM ${InternalTable.CRUD} ORDER BY id ASC LIMIT 1"
50-
private val nextCrudMapper: (SqlCursor) -> CrudEntry = { cursor ->
60+
private val transactionCrudQuery = "SELECT id, tx_id, data FROM ${InternalTable.CRUD} WHERE tx_id = ? ORDER BY id ASC"
61+
private val crudEntryMapper: (SqlCursor) -> CrudEntry = { cursor ->
5162
CrudEntry.fromRow(
5263
CrudRow(
5364
id = cursor.getString(0)!!,

core/src/commonMain/kotlin/com/powersync/db/PowerSyncDatabaseImpl.kt

+16-10
Original file line numberDiff line numberDiff line change
@@ -298,15 +298,10 @@ internal class PowerSyncDatabaseImpl(
298298
if (txId == null) {
299299
listOf(entry)
300300
} else {
301-
transaction.getAll("SELECT id, tx_id, data FROM ps_crud ORDER BY id ASC LIMIT 1") {
302-
CrudEntry.fromRow(
303-
CrudRow(
304-
id = it.getString("id"),
305-
data = it.getString("data"),
306-
txId = it.getLongOptional("tx_id")?.toInt(),
307-
),
308-
)
309-
}
301+
bucketStorage.getCrudItemsByTransactionId(
302+
transactionId = txId,
303+
transaction = transaction,
304+
)
310305
}
311306

312307
return@readTransaction CrudTransaction(
@@ -353,10 +348,21 @@ internal class PowerSyncDatabaseImpl(
353348
return internalDb.getOptional(sql, parameters, mapper)
354349
}
355350

351+
override fun onChange(
352+
tables: Set<String>,
353+
throttleMs: Long,
354+
): Flow<Set<String>> =
355+
flow {
356+
waitReady()
357+
emitAll(
358+
internalDb.onChange(tables, throttleMs),
359+
)
360+
}
361+
356362
override fun <RowType : Any> watch(
357363
sql: String,
358364
parameters: List<Any?>?,
359-
throttleMs: Long?,
365+
throttleMs: Long,
360366
mapper: (SqlCursor) -> RowType,
361367
): Flow<List<RowType>> =
362368
flow {

0 commit comments

Comments
 (0)