@trainstar/synchro-react-native 0.3.2 → 0.4.0-rc.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (42) hide show
  1. package/README.md +58 -4
  2. package/SynchroReactNative.podspec +6 -0
  3. package/android/build.gradle +5 -1
  4. package/android/src/androidTest/kotlin/com/trainstar/synchro/rn/SynchroModuleTransactionTest.kt +591 -0
  5. package/android/src/androidTest/kotlin/com/trainstar/synchro/rn/TransactionSessionTest.kt +314 -0
  6. package/android/src/main/kotlin/com/trainstar/synchro/rn/SynchroModule.kt +306 -110
  7. package/ios/SynchroModule.mm +27 -4
  8. package/ios/SynchroModule.swift +228 -84
  9. package/ios-tests/SynchroModuleTransactionTests.swift +707 -0
  10. package/ios-tests/TransactionSessionTests.swift +66 -0
  11. package/lib/module/NativeSynchro.js.map +1 -1
  12. package/lib/module/SynchroClient.js +115 -2
  13. package/lib/module/SynchroClient.js.map +1 -1
  14. package/lib/module/errors.js +15 -1
  15. package/lib/module/errors.js.map +1 -1
  16. package/lib/module/index.js +1 -1
  17. package/lib/module/index.js.map +1 -1
  18. package/lib/module/inspection.js +31 -3
  19. package/lib/module/inspection.js.map +1 -1
  20. package/lib/module/types.js +18 -0
  21. package/lib/module/types.js.map +1 -1
  22. package/lib/typescript/example/src/conformance/runner.d.ts +1 -0
  23. package/lib/typescript/example/src/conformance/runner.d.ts.map +1 -1
  24. package/lib/typescript/src/NativeSynchro.d.ts +7 -1
  25. package/lib/typescript/src/NativeSynchro.d.ts.map +1 -1
  26. package/lib/typescript/src/SynchroClient.d.ts +29 -1
  27. package/lib/typescript/src/SynchroClient.d.ts.map +1 -1
  28. package/lib/typescript/src/errors.d.ts +7 -1
  29. package/lib/typescript/src/errors.d.ts.map +1 -1
  30. package/lib/typescript/src/index.d.ts +3 -3
  31. package/lib/typescript/src/index.d.ts.map +1 -1
  32. package/lib/typescript/src/inspection.d.ts +13 -2
  33. package/lib/typescript/src/inspection.d.ts.map +1 -1
  34. package/lib/typescript/src/types.d.ts +44 -1
  35. package/lib/typescript/src/types.d.ts.map +1 -1
  36. package/package.json +3 -2
  37. package/src/NativeSynchro.ts +6 -1
  38. package/src/SynchroClient.ts +128 -1
  39. package/src/errors.ts +30 -0
  40. package/src/index.ts +7 -0
  41. package/src/inspection.ts +44 -2
  42. package/src/types.ts +49 -1
package/README.md CHANGED
@@ -12,19 +12,19 @@ React Native TurboModule bridge for Synchro. The package wraps the native Swift
12
12
 
13
13
  ## Installation
14
14
 
15
- Install the published package only after Synchro `0.3.2` is available. Before that,
15
+ Install the published package only after Synchro `0.4.0-rc.1` is available. Before that,
16
16
  use the local artifact flow in
17
17
  [Client Consumption](https://trainstar.github.io/synchro/clients/consumption/).
18
18
 
19
19
  ```sh
20
- npm install @trainstar/synchro-react-native@0.3.2
20
+ npm install @trainstar/synchro-react-native@0.4.0-rc.1
21
21
  ```
22
22
 
23
- Before you run `pod install`, add these published Synchro `0.3.2` dependencies to the
23
+ Before you run `pod install`, add these published Synchro `0.4.0-rc.1` dependencies to the
24
24
  application `ios/Podfile`:
25
25
 
26
26
  ```ruby
27
- pod 'Synchro', :git => 'https://github.com/trainstar/synchro.git', :tag => 'v0.3.2'
27
+ pod 'Synchro', :git => 'https://github.com/trainstar/synchro.git', :tag => 'v0.4.0-rc.1'
28
28
  pod 'GRDB.swift', :git => 'https://github.com/groue/GRDB.swift.git', :tag => 'v7.0.0'
29
29
  ```
30
30
 
@@ -168,6 +168,59 @@ await client.writeTransaction(async (tx) => {
168
168
  });
169
169
  ```
170
170
 
171
+ ## Atomic write transactions
172
+
173
+ `atomicWriteTransaction(fn)` runs one write transaction. The server applies all of its synced mutations or none of them. The native SDK owns the group. The bridge only forwards the call to the native `atomicWriteTransaction`.
174
+
175
+ - The API opens its transaction through the same path as `writeTransaction()`. Nesting behavior is therefore identical to `writeTransaction()`.
176
+ - The `tx` object exposes no nested transaction API.
177
+ - The transaction contract of `writeTransaction()` also applies.
178
+ - A group that captures no synced rows commits as an ordinary write.
179
+
180
+ ```ts
181
+ import { AtomicGroupInvalidError } from '@trainstar/synchro-react-native';
182
+
183
+ try {
184
+ await client.atomicWriteTransaction(async (tx) => {
185
+ await tx.execute('UPDATE notes SET body = ? WHERE id = ?', ['Reviewed', noteID]);
186
+ await tx.execute(
187
+ 'INSERT INTO notes (id, owner_id, body) VALUES (?, ?, ?)',
188
+ [reviewID, 'alice', `Review of ${noteID}`]
189
+ );
190
+ });
191
+ } catch (error) {
192
+ if (error instanceof AtomicGroupInvalidError) {
193
+ console.warn(`Atomic group rolled back: ${error.reason}`);
194
+ } else {
195
+ throw error;
196
+ }
197
+ }
198
+ ```
199
+
200
+ Before the local commit, the native SDK validates the group. An invalid group rolls back the local transaction. No row change and no queue entry remains. The promise rejects with `AtomicGroupInvalidError`. Its `code` is `atomic_group_invalid`, and its `reason` is one of these values:
201
+
202
+ | `reason` | Rule |
203
+ | --- | --- |
204
+ | `deleteFollowedByWrite` | A `delete` of a row is followed by a later write of the same row in the group. |
205
+ | `tooManyMutations` | The normalized group has more than 1000 mutations. |
206
+ | `mutationTooLarge` | A normalized mutation has more than 65,536 octets in canonical form or more than 256 authored columns. |
207
+ | `requestTooLarge` | The worst-case push request body or its RFC 8785 form is more than 1,048,576 octets. |
208
+
209
+ When one mutation in a group does not apply, the server applies no mutation of the group. The SDK uses the existing outcome paths and adds no revert path:
210
+
211
+ - The failing mutation keeps its own status and code.
212
+ - A failing conflict follows the existing conflict path. The SDK applies `server_row` and then reapplies later local intent for that row.
213
+ - Every other member gets `atomic_batch_rejected`. This outcome follows the existing terminal path. Its local row stays, and `inspectRejectedMutations()` reports the mutation.
214
+
215
+ After a failed group with a conflict, local state holds the server row for the conflicting member. It holds local values for every other member. The SDK does not revert those local values. Use `inspectRejectedMutations()` to find them and write new intent.
216
+
217
+ The atomic API requires the Synchro `0.4.0-rc.1` extension and adapter. Use this deployment order:
218
+
219
+ 1. Install the `0.4.0` extension and adapter on the server.
220
+ 2. Release the client code that calls `atomicWriteTransaction()`.
221
+
222
+ A `0.4.0` client that never calls the atomic API works with a `0.3.x` server. A `0.3.x` server answers an atomic batch with `400 invalid_request`. The client keeps the sealed batch and reports the failure. It does not lose data.
223
+
171
224
  ## Errors
172
225
 
173
226
  Native errors are normalized to typed JS errors, including:
@@ -185,6 +238,7 @@ Native errors are normalized to typed JS errors, including:
185
238
  - `AlreadyStartedError`
186
239
  - `NotStartedError`
187
240
  - `TransactionTimeoutError`
241
+ - `AtomicGroupInvalidError`
188
242
 
189
243
  The iOS schema bridge rejects malformed JSON with the existing `UNKNOWN` error code.
190
244
 
@@ -20,4 +20,10 @@ Pod::Spec.new do |s|
20
20
  s.dependency "Synchro", "= #{s.version}"
21
21
 
22
22
  install_modules_dependencies(s)
23
+
24
+ # Test sources stay outside source_files, so production targets never compile them.
25
+ s.test_spec "Tests" do |test_spec|
26
+ test_spec.source_files = "ios-tests/**/*.swift"
27
+ test_spec.requires_app_host = true
28
+ end
23
29
  end
@@ -32,7 +32,7 @@ apply plugin: "kotlin-android"
32
32
 
33
33
  apply plugin: "com.facebook.react"
34
34
 
35
- def defaultSynchroVersion = "0.3.2"
35
+ def defaultSynchroVersion = "0.4.0-rc.1"
36
36
  def resolvedSynchroVersion = findProperty("synchroVersion")?.toString() ?: defaultSynchroVersion
37
37
 
38
38
  android {
@@ -43,6 +43,7 @@ android {
43
43
  defaultConfig {
44
44
  minSdkVersion getExtOrDefault("minSdkVersion")
45
45
  targetSdkVersion getExtOrDefault("targetSdkVersion")
46
+ testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
46
47
  }
47
48
 
48
49
  sourceSets {
@@ -83,4 +84,7 @@ dependencies {
83
84
  implementation "org.jetbrains.kotlinx:kotlinx-serialization-json:1.6.3"
84
85
  implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.8.0"
85
86
  implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.8.0"
87
+
88
+ androidTestImplementation "androidx.test.ext:junit:1.1.5"
89
+ androidTestImplementation "androidx.test:runner:1.5.2"
86
90
  }