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

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 (34) hide show
  1. package/README.md +62 -11
  2. package/SynchroReactNative.podspec +13 -1
  3. package/android/build.gradle +1 -1
  4. package/android/src/androidTest/kotlin/com/trainstar/synchro/rn/SynchroModuleTransactionTest.kt +10 -11
  5. package/android/src/main/kotlin/com/trainstar/synchro/rn/SynchroModule.kt +22 -2
  6. package/ios/SynchroModule.swift +292 -270
  7. package/ios-test-host/main.m +48 -0
  8. package/ios-tests/SynchroModuleTransactionTests.swift +267 -1
  9. package/lib/module/NativeSynchro.js.map +1 -1
  10. package/lib/module/SynchroClient.js +26 -0
  11. package/lib/module/SynchroClient.js.map +1 -1
  12. package/lib/module/inspection.js +69 -6
  13. package/lib/module/inspection.js.map +1 -1
  14. package/lib/typescript/example/e2e/schema-check.test.d.ts +2 -0
  15. package/lib/typescript/example/e2e/schema-check.test.d.ts.map +1 -0
  16. package/lib/typescript/example/src/conformance/runner.d.ts +16 -2
  17. package/lib/typescript/example/src/conformance/runner.d.ts.map +1 -1
  18. package/lib/typescript/src/NativeSynchro.d.ts +13 -14
  19. package/lib/typescript/src/NativeSynchro.d.ts.map +1 -1
  20. package/lib/typescript/src/SynchroClient.d.ts.map +1 -1
  21. package/lib/typescript/src/inspection.d.ts +11 -4
  22. package/lib/typescript/src/inspection.d.ts.map +1 -1
  23. package/lib/typescript/src/types.d.ts +18 -0
  24. package/lib/typescript/src/types.d.ts.map +1 -1
  25. package/package.json +24 -9
  26. package/src/NativeSynchro.ts +13 -17
  27. package/src/SynchroClient.ts +32 -0
  28. package/src/inspection.ts +95 -8
  29. package/src/types.ts +21 -0
  30. package/tooling-patches/apply.cjs +163 -0
  31. package/tooling-patches/cli-clean-20.2.0.patch +21 -0
  32. package/tooling-patches/cli-config-android-20.2.0.patch +13 -0
  33. package/tooling-patches/react-native-builder-bob-0.40.18.patch +74 -0
  34. package/tooling-patches/stream-json-1.9.1.patch +40 -0
package/README.md CHANGED
@@ -4,28 +4,78 @@ React Native TurboModule bridge for Synchro. The package wraps the native Swift
4
4
 
5
5
  ## Requirements
6
6
 
7
- - React Native `0.83.x`. Use `0.83.5` or later with Xcode `26.4` or later.
8
- - iOS `16.0+`
7
+ - React Native `>=0.82.1 <0.89.0` and React `>=19.1.1 <20.0.0`.
8
+ - React Native `0.88.0` requires React `19.3.0` and Node `^22.13.0 || ^24.3.0 || >=26.0.0`.
9
+ - React Native `0.82.1` needs the [fmt 12.1.0 backport](https://trainstar.github.io/synchro/reference/support-policy/#react-native-0821-fmt-backport) with Xcode 27.
10
+ - Applications built with Xcode 27 must adopt the [UIKit scene lifecycle](https://developer.apple.com/documentation/uikit/transitioning-to-the-uikit-scene-based-life-cycle).
11
+ - iOS `17.0+`
9
12
  - Android `minSdk 24`
10
- - Node `20.19.4+`
11
13
  - Android development and CI should use JDK `17`
12
14
 
13
15
  ## Installation
14
16
 
15
- Install the published package only after Synchro `0.4.0-rc.1` is available. Before that,
17
+ Install the published package only after Synchro `0.4.0-rc.3` is available. Before that,
16
18
  use the local artifact flow in
17
19
  [Client Consumption](https://trainstar.github.io/synchro/clients/consumption/).
18
20
 
19
21
  ```sh
20
- npm install @trainstar/synchro-react-native@0.4.0-rc.1
22
+ npm install @trainstar/synchro-react-native@0.4.0-rc.3
21
23
  ```
22
24
 
23
- Before you run `pod install`, add these published Synchro `0.4.0-rc.1` dependencies to the
25
+ Apply the delivered React Native CLI correction after each fresh npm install and
26
+ before you run Codegen, bundle, or native build commands:
27
+
28
+ ```sh
29
+ npm install --save-dev --save-exact \
30
+ @react-native-community/cli@20.2.0 \
31
+ @react-native-community/cli-platform-android@20.2.0 \
32
+ @react-native-community/cli-platform-ios@20.2.0
33
+ ```
34
+
35
+ Pin these CLI packages before the patch command. Default React Native templates
36
+ can install an older CLI.
37
+
38
+ ```sh
39
+ node node_modules/@trainstar/synchro-react-native/tooling-patches/apply.cjs cli
40
+ ```
41
+
42
+ The command resolves the CLI from the application that calls it. It verifies
43
+ the expected `20.2.0` source and post-patch SHA-256 values. It rejects another
44
+ version, a missing target, or a target outside the application installation.
45
+ The correction escapes the documented `jsSrcsDir` directory before the CLI
46
+ adds its fixed source-extension glob. It also preserves the CLI's authored
47
+ temporary-cache suffix patterns while treating the host temporary directory as
48
+ a literal prefix. It does not change the upstream version.
49
+
50
+ Applications that use `bunyamin` 1.6.3 with `stream-json` 1.9.1 must also apply
51
+ the delivered correction before they run that tooling:
52
+
53
+ ```sh
54
+ node node_modules/@trainstar/synchro-react-native/tooling-patches/apply.cjs stream-json
55
+ ```
56
+
57
+ This correction backports upstream commit
58
+ `2f2d35bbb547306991ded6487a279154d865a358` for both Assembler writes. It
59
+ preserves the installed `stream-json` version and its 1.9.1 reviver behavior.
60
+
61
+ Library projects that run `bob build` must apply the delivered Bob correction
62
+ after each fresh npm installation and before they build:
63
+
64
+ ```sh
65
+ node node_modules/@trainstar/synchro-react-native/tooling-patches/apply.cjs bob
66
+ ```
67
+
68
+ The correction replaces Bob's glob-based removal of its resolved output and
69
+ TypeScript build-info paths with its installed `fs-extra` literal operation.
70
+ It rejects the current project directory and paths outside it before removal.
71
+ It does not change compilation or TypeScript generation.
72
+
73
+ Before you run `pod install`, add these published Synchro `0.4.0-rc.3` dependencies to the
24
74
  application `ios/Podfile`:
25
75
 
26
76
  ```ruby
27
- pod 'Synchro', :git => 'https://github.com/trainstar/synchro.git', :tag => 'v0.4.0-rc.1'
28
- pod 'GRDB.swift', :git => 'https://github.com/groue/GRDB.swift.git', :tag => 'v7.0.0'
77
+ pod 'Synchro', :git => 'https://github.com/trainstar/synchro.git', :tag => 'v0.4.0-rc.3'
78
+ pod 'GRDB.swift', :git => 'https://github.com/groue/GRDB.swift.git', :tag => 'v7.8.0'
29
79
  ```
30
80
 
31
81
  Then install the pods:
@@ -214,7 +264,7 @@ When one mutation in a group does not apply, the server applies no mutation of t
214
264
 
215
265
  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
266
 
217
- The atomic API requires the Synchro `0.4.0-rc.1` extension and adapter. Use this deployment order:
267
+ The atomic API requires the Synchro `0.4.0-rc.3` extension and adapter. Use this deployment order:
218
268
 
219
269
  1. Install the `0.4.0` extension and adapter on the server.
220
270
  2. Release the client code that calls `atomicWriteTransaction()`.
@@ -244,8 +294,9 @@ The iOS schema bridge rejects malformed JSON with the existing `UNKNOWN` error c
244
294
 
245
295
  ## Development
246
296
 
247
- The published package accepts Node `20.19.4` or later. Repository development
248
- uses the Node `22.20.0` pin in `.nvmrc`.
297
+ The published package requires Node `20.19.4` or later.
298
+ React Native `0.88.0` adds the higher Node requirement listed above.
299
+ Repository development uses the Node `22.20.0` pin in `.nvmrc`.
249
300
 
250
301
  ```sh
251
302
  make lint-rn
@@ -10,20 +10,32 @@ Pod::Spec.new do |s|
10
10
  s.license = package["license"]
11
11
  s.authors = package["author"]
12
12
 
13
- s.platforms = { :ios => "16.0" }
13
+ s.platforms = { :ios => "17.0" }
14
14
  s.source = { :git => "https://github.com/trainstar/synchro.git", :tag => "v#{s.version}" }
15
15
 
16
16
  s.source_files = "ios/**/*.{h,m,mm,swift,cpp}"
17
17
  s.private_header_files = "ios/**/*.h"
18
18
  s.compiler_flags = "-Werror=protocol"
19
+ s.libraries = "c++"
19
20
 
20
21
  s.dependency "Synchro", "= #{s.version}"
21
22
 
22
23
  install_modules_dependencies(s)
23
24
 
25
+ s.app_spec "TestHost" do |app_spec|
26
+ app_spec.source_files = "ios-test-host/main.m"
27
+ app_spec.info_plist = {
28
+ "UIApplicationSceneManifest" => {
29
+ "UIApplicationSupportsMultipleScenes" => false
30
+ }
31
+ }
32
+ end
33
+
24
34
  # Test sources stay outside source_files, so production targets never compile them.
25
35
  s.test_spec "Tests" do |test_spec|
26
36
  test_spec.source_files = "ios-tests/**/*.swift"
27
37
  test_spec.requires_app_host = true
38
+ test_spec.app_host_name = "SynchroReactNative/TestHost"
39
+ test_spec.dependency "SynchroReactNative/TestHost"
28
40
  end
29
41
  end
@@ -32,7 +32,7 @@ apply plugin: "kotlin-android"
32
32
 
33
33
  apply plugin: "com.facebook.react"
34
34
 
35
- def defaultSynchroVersion = "0.4.0-rc.1"
35
+ def defaultSynchroVersion = "0.4.0-rc.3"
36
36
  def resolvedSynchroVersion = findProperty("synchroVersion")?.toString() ?: defaultSynchroVersion
37
37
 
38
38
  android {
@@ -268,7 +268,9 @@ class SynchroModuleTransactionTest {
268
268
  assertEquals("first", rows.getMap(0)!!.getString("name"))
269
269
  val inspection = JSONObject(result.getString("inspection")!!)
270
270
  assertEquals(
271
- setOf("client_state", "retained_mutations", "rejected_mutations"),
271
+ setOf("client_state", "retained_mutations", "rejected_mutations", "migration_journal",
272
+ "migration_journal_truncated", "physical_schema", "physical_schema_truncated",
273
+ "accepted_mutation_outcomes", "accepted_mutation_outcomes_truncated"),
272
274
  inspection.keys().asSequence().toSet(),
273
275
  )
274
276
  // bridge_items is a local table, so the mutation ledger stays empty.
@@ -405,10 +407,7 @@ class SynchroModuleTransactionTest {
405
407
  }
406
408
 
407
409
  /**
408
- * RN 0.83 makes ReactApplicationContext abstract. BridgeReactContext is its concrete
409
- * @VisibleForTesting subclass. Its legacy-architecture assertion is inert only because the pinned
410
- * react-android 0.83.0 debug artifact sets UNSTABLE_ENABLE_MINIFY_LEGACY_ARCHITECTURE to false.
411
- * This context runs no JavaScript, so these tests do not cover the bridgeless JavaScript host.
410
+ * This context exercises native SQLite calls. The application tests verify JavaScript dispatch.
412
411
  */
413
412
  @Suppress("DEPRECATION")
414
413
  internal fun bridgeTestContext(context: android.content.Context): BridgeReactContext = BridgeReactContext(context)
@@ -440,14 +439,14 @@ private class RecordingPromise(val name: String) : Promise {
440
439
  }
441
440
 
442
441
  override fun resolve(value: Any?) = record { resolved.add(value) }
443
- override fun reject(code: String, message: String?) = rejectWith(code, null)
444
- override fun reject(code: String, throwable: Throwable?) = rejectWith(code, throwable)
445
- override fun reject(code: String, message: String?, throwable: Throwable?) = rejectWith(code, throwable)
442
+ override fun reject(code: String?, message: String?) = rejectWith(code, null)
443
+ override fun reject(code: String?, throwable: Throwable?) = rejectWith(code, throwable)
444
+ override fun reject(code: String?, message: String?, throwable: Throwable?) = rejectWith(code, throwable)
446
445
  override fun reject(throwable: Throwable) = rejectWith(null, throwable)
447
446
  override fun reject(throwable: Throwable, userInfo: WritableMap) = rejectWith(null, throwable)
448
- override fun reject(code: String, userInfo: WritableMap) = rejectWith(code, null)
449
- override fun reject(code: String, throwable: Throwable?, userInfo: WritableMap) = rejectWith(code, throwable)
450
- override fun reject(code: String, message: String?, userInfo: WritableMap) = rejectWith(code, null)
447
+ override fun reject(code: String?, userInfo: WritableMap) = rejectWith(code, null)
448
+ override fun reject(code: String?, throwable: Throwable?, userInfo: WritableMap) = rejectWith(code, throwable)
449
+ override fun reject(code: String?, message: String?, userInfo: WritableMap) = rejectWith(code, null)
451
450
  override fun reject(code: String?, message: String?, throwable: Throwable?, userInfo: WritableMap?) =
452
451
  rejectWith(code, throwable)
453
452
 
@@ -13,6 +13,9 @@ import com.trainstar.synchro.inspection.SynchroInspection
13
13
  import com.trainstar.synchro.inspection.TransportObservationCollector
14
14
  import com.trainstar.synchro.inspection.TransportObservationSnapshot
15
15
  import com.trainstar.synchro.inspection.TransportOperationClass
16
+ import com.trainstar.synchro.inspection.MigrationCheckpoint
17
+ import com.trainstar.synchro.inspection.MigrationJournalInspection
18
+ import com.trainstar.synchro.inspection.PhysicalSchemaColumnInspection
16
19
  import com.trainstar.synchro.inspection.withTransportObservation
17
20
  import kotlinx.coroutines.ExperimentalCoroutinesApi
18
21
  import kotlinx.coroutines.*
@@ -1127,6 +1130,16 @@ class SynchroModule(reactContext: ReactApplicationContext) :
1127
1130
  }
1128
1131
  val inspection = JSONObject().apply {
1129
1132
  put("client_state", clientStateJson(snapshot.capture))
1133
+ put("migration_journal", snapshot.capture.migrationJournal?.let {
1134
+ JSONObject(Json.encodeToString(MigrationJournalInspection.serializer(), it))
1135
+ } ?: JSONObject.NULL)
1136
+ put("migration_journal_truncated", snapshot.capture.migrationJournalTruncated)
1137
+ put("physical_schema", JSONArray(snapshot.capture.physicalSchema.map {
1138
+ JSONObject(Json.encodeToString(PhysicalSchemaColumnInspection.serializer(), it))
1139
+ }))
1140
+ put("physical_schema_truncated", snapshot.capture.physicalSchemaTruncated)
1141
+ put("accepted_mutation_outcomes", JSONObject(snapshot.capture.acceptedMutationOutcomes))
1142
+ put("accepted_mutation_outcomes_truncated", snapshot.capture.acceptedMutationOutcomesTruncated)
1130
1143
  put("retained_mutations", snapshot.retainedMutations?.let { JSONArray(it.map(::retainedMutationJson)) } ?: JSONObject.NULL)
1131
1144
  put("rejected_mutations", snapshot.rejectedMutations?.let { JSONArray(it.map(::retainedRejectionJson)) } ?: JSONObject.NULL)
1132
1145
  }
@@ -1202,7 +1215,8 @@ class SynchroModule(reactContext: ReactApplicationContext) :
1202
1215
  override fun armTransportPause(operationClass: String, promise: Promise) {
1203
1216
  try {
1204
1217
  val collector = transportObservationCollector ?: error("Transport observation is not configured")
1205
- collector.armPause(transportOperationClass(operationClass))
1218
+ val checkpoint = MigrationCheckpoint.fromWire(operationClass)
1219
+ if (checkpoint != null) collector.armPause(checkpoint) else collector.armPause(transportOperationClass(operationClass))
1206
1220
  promise.resolve(null)
1207
1221
  } catch (error: Exception) {
1208
1222
  rejectWithError(promise, error)
@@ -1220,7 +1234,12 @@ class SynchroModule(reactContext: ReactApplicationContext) :
1220
1234
  timeoutMs % 1.0 == 0.0,
1221
1235
  ) { "Transport pause timeout is invalid" }
1222
1236
  val collector = transportObservationCollector ?: error("Transport observation is not configured")
1223
- collector.awaitPause(transportOperationClass(operationClass), timeoutMs.toLong())
1237
+ val checkpoint = MigrationCheckpoint.fromWire(operationClass)
1238
+ if (checkpoint != null) {
1239
+ collector.awaitPause(checkpoint, timeoutMs.toLong())
1240
+ } else {
1241
+ collector.awaitPause(transportOperationClass(operationClass), timeoutMs.toLong())
1242
+ }
1224
1243
  promise.resolve(null)
1225
1244
  } catch (error: Exception) {
1226
1245
  rejectWithError(promise, error)
@@ -1532,6 +1551,7 @@ class SynchroModule(reactContext: ReactApplicationContext) :
1532
1551
  put("row_metadata_count", capture.rowMetadataCount)
1533
1552
  put("rebuild_attempt_count", capture.rebuildAttemptCount)
1534
1553
  put("rebuild_receipt_count", capture.rebuildReceiptCount)
1554
+ put("capture_overflowed", capture.overflowed)
1535
1555
  put(
1536
1556
  "provenance_maintenance_work_cursor",
1537
1557
  capture.provenanceMaintenanceWorkCursor.toString(),