@trainstar/synchro-react-native 0.3.2 → 0.4.0-rc.2
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.
- package/README.md +112 -7
- package/SynchroReactNative.podspec +18 -1
- package/android/build.gradle +5 -1
- package/android/src/androidTest/kotlin/com/trainstar/synchro/rn/SynchroModuleTransactionTest.kt +593 -0
- package/android/src/androidTest/kotlin/com/trainstar/synchro/rn/TransactionSessionTest.kt +314 -0
- package/android/src/main/kotlin/com/trainstar/synchro/rn/SynchroModule.kt +328 -112
- package/ios/SynchroModule.mm +27 -4
- package/ios/SynchroModule.swift +251 -89
- package/ios-test-host/main.m +48 -0
- package/ios-tests/SynchroModuleTransactionTests.swift +709 -0
- package/ios-tests/TransactionSessionTests.swift +66 -0
- package/lib/module/NativeSynchro.js.map +1 -1
- package/lib/module/SynchroClient.js +141 -2
- package/lib/module/SynchroClient.js.map +1 -1
- package/lib/module/errors.js +15 -1
- package/lib/module/errors.js.map +1 -1
- package/lib/module/index.js +1 -1
- package/lib/module/index.js.map +1 -1
- package/lib/module/inspection.js +99 -8
- package/lib/module/inspection.js.map +1 -1
- package/lib/module/types.js +18 -0
- package/lib/module/types.js.map +1 -1
- package/lib/typescript/example/e2e/schema-check.test.d.ts +2 -0
- package/lib/typescript/example/e2e/schema-check.test.d.ts.map +1 -0
- package/lib/typescript/example/src/conformance/runner.d.ts +17 -2
- package/lib/typescript/example/src/conformance/runner.d.ts.map +1 -1
- package/lib/typescript/src/NativeSynchro.d.ts +7 -1
- package/lib/typescript/src/NativeSynchro.d.ts.map +1 -1
- package/lib/typescript/src/SynchroClient.d.ts +29 -1
- package/lib/typescript/src/SynchroClient.d.ts.map +1 -1
- package/lib/typescript/src/errors.d.ts +7 -1
- package/lib/typescript/src/errors.d.ts.map +1 -1
- package/lib/typescript/src/index.d.ts +3 -3
- package/lib/typescript/src/index.d.ts.map +1 -1
- package/lib/typescript/src/inspection.d.ts +23 -5
- package/lib/typescript/src/inspection.d.ts.map +1 -1
- package/lib/typescript/src/types.d.ts +62 -1
- package/lib/typescript/src/types.d.ts.map +1 -1
- package/package.json +21 -6
- package/src/NativeSynchro.ts +6 -1
- package/src/SynchroClient.ts +160 -1
- package/src/errors.ts +30 -0
- package/src/index.ts +7 -0
- package/src/inspection.ts +138 -9
- package/src/types.ts +70 -1
- package/tooling-patches/apply.cjs +163 -0
- package/tooling-patches/cli-clean-20.2.0.patch +21 -0
- package/tooling-patches/cli-config-android-20.2.0.patch +13 -0
- package/tooling-patches/react-native-builder-bob-0.40.18.patch +74 -0
- package/tooling-patches/stream-json-1.9.1.patch +40 -0
package/ios/SynchroModule.mm
CHANGED
|
@@ -141,6 +141,11 @@
|
|
|
141
141
|
[self.impl beginWriteTransaction:resolve reject:reject];
|
|
142
142
|
}
|
|
143
143
|
|
|
144
|
+
- (void)beginAtomicWriteTransaction:(RCTPromiseResolveBlock)resolve
|
|
145
|
+
reject:(RCTPromiseRejectBlock)reject {
|
|
146
|
+
[self.impl beginAtomicWriteTransaction:resolve reject:reject];
|
|
147
|
+
}
|
|
148
|
+
|
|
144
149
|
- (void)beginReadTransaction:(RCTPromiseResolveBlock)resolve
|
|
145
150
|
reject:(RCTPromiseRejectBlock)reject {
|
|
146
151
|
[self.impl beginReadTransaction:resolve reject:reject];
|
|
@@ -282,14 +287,25 @@
|
|
|
282
287
|
[self.impl inspectRetainedMutations:resolve reject:reject];
|
|
283
288
|
}
|
|
284
289
|
|
|
290
|
+
- (void)inspectRetainedMutationRecords:(RCTPromiseResolveBlock)resolve
|
|
291
|
+
reject:(RCTPromiseRejectBlock)reject {
|
|
292
|
+
[self.impl inspectRetainedMutationRecords:resolve reject:reject];
|
|
293
|
+
}
|
|
294
|
+
|
|
285
295
|
- (void)inspectRejectedMutations:(RCTPromiseResolveBlock)resolve
|
|
286
296
|
reject:(RCTPromiseRejectBlock)reject {
|
|
287
297
|
[self.impl inspectRejectedMutations:resolve reject:reject];
|
|
288
298
|
}
|
|
289
299
|
|
|
290
|
-
- (void)
|
|
291
|
-
|
|
292
|
-
[self.impl
|
|
300
|
+
- (void)inspectRejectedMutationRecords:(RCTPromiseResolveBlock)resolve
|
|
301
|
+
reject:(RCTPromiseRejectBlock)reject {
|
|
302
|
+
[self.impl inspectRejectedMutationRecords:resolve reject:reject];
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
- (void)inspectClientStateSnapshot:(NSArray *)rowStatements
|
|
306
|
+
resolve:(RCTPromiseResolveBlock)resolve
|
|
307
|
+
reject:(RCTPromiseRejectBlock)reject {
|
|
308
|
+
[self.impl inspectClientStateSnapshot:rowStatements resolve:resolve reject:reject];
|
|
293
309
|
}
|
|
294
310
|
|
|
295
311
|
- (void)inspectDurableState:(NSString *)tableName
|
|
@@ -391,6 +407,8 @@ RCT_EXTERN_METHOD(executeBatch:(NSArray *)statements
|
|
|
391
407
|
reject:(RCTPromiseRejectBlock)reject)
|
|
392
408
|
RCT_EXTERN_METHOD(beginWriteTransaction:(RCTPromiseResolveBlock)resolve
|
|
393
409
|
reject:(RCTPromiseRejectBlock)reject)
|
|
410
|
+
RCT_EXTERN_METHOD(beginAtomicWriteTransaction:(RCTPromiseResolveBlock)resolve
|
|
411
|
+
reject:(RCTPromiseRejectBlock)reject)
|
|
394
412
|
RCT_EXTERN_METHOD(beginReadTransaction:(RCTPromiseResolveBlock)resolve
|
|
395
413
|
reject:(RCTPromiseRejectBlock)reject)
|
|
396
414
|
RCT_EXTERN_METHOD(txQuery:(NSString *)txID
|
|
@@ -463,9 +481,14 @@ RCT_EXTERN_METHOD(inspectPendingMutations:(RCTPromiseResolveBlock)resolve
|
|
|
463
481
|
reject:(RCTPromiseRejectBlock)reject)
|
|
464
482
|
RCT_EXTERN_METHOD(inspectRetainedMutations:(RCTPromiseResolveBlock)resolve
|
|
465
483
|
reject:(RCTPromiseRejectBlock)reject)
|
|
484
|
+
RCT_EXTERN_METHOD(inspectRetainedMutationRecords:(RCTPromiseResolveBlock)resolve
|
|
485
|
+
reject:(RCTPromiseRejectBlock)reject)
|
|
466
486
|
RCT_EXTERN_METHOD(inspectRejectedMutations:(RCTPromiseResolveBlock)resolve
|
|
467
487
|
reject:(RCTPromiseRejectBlock)reject)
|
|
468
|
-
RCT_EXTERN_METHOD(
|
|
488
|
+
RCT_EXTERN_METHOD(inspectRejectedMutationRecords:(RCTPromiseResolveBlock)resolve
|
|
489
|
+
reject:(RCTPromiseRejectBlock)reject)
|
|
490
|
+
RCT_EXTERN_METHOD(inspectClientStateSnapshot:(NSArray *)rowStatements
|
|
491
|
+
resolve:(RCTPromiseResolveBlock)resolve
|
|
469
492
|
reject:(RCTPromiseRejectBlock)reject)
|
|
470
493
|
RCT_EXTERN_METHOD(inspectDurableState:(NSString *)tableName
|
|
471
494
|
recordID:(NSString *)recordID
|
package/ios/SynchroModule.swift
CHANGED
|
@@ -12,7 +12,7 @@ import GRDB
|
|
|
12
12
|
|
|
13
13
|
// MARK: - Transaction Session
|
|
14
14
|
|
|
15
|
-
|
|
15
|
+
class TransactionSession {
|
|
16
16
|
private let condition = NSCondition()
|
|
17
17
|
private var operations: [TransactionOp] = []
|
|
18
18
|
private var closed = false
|
|
@@ -20,10 +20,36 @@ private class TransactionSession {
|
|
|
20
20
|
private var finalCompletion: ((Result<Void, Error>) -> Void)?
|
|
21
21
|
private var finished = false
|
|
22
22
|
private var finishWaiters: [CheckedContinuation<Void, Never>] = []
|
|
23
|
+
private var beginCompletion: ((Result<String, Error>) -> Void)?
|
|
23
24
|
let isWrite: Bool
|
|
24
25
|
|
|
25
|
-
init(isWrite: Bool) {
|
|
26
|
+
init(isWrite: Bool, beginCompletion: @escaping (Result<String, Error>) -> Void) {
|
|
26
27
|
self.isWrite = isWrite
|
|
28
|
+
self.beginCompletion = beginCompletion
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/// Settles begin when the native transaction callback starts. A close that
|
|
32
|
+
/// aborted the session before the callback started rejects begin instead.
|
|
33
|
+
func acceptBegin(_ txID: String) throws {
|
|
34
|
+
condition.lock()
|
|
35
|
+
let completion = beginCompletion
|
|
36
|
+
beginCompletion = nil
|
|
37
|
+
let error = abortError
|
|
38
|
+
condition.unlock()
|
|
39
|
+
if let error {
|
|
40
|
+
completion?(.failure(error))
|
|
41
|
+
throw error
|
|
42
|
+
}
|
|
43
|
+
completion?(.success(txID))
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/// Rejects begin when the transaction fails before its callback starts.
|
|
47
|
+
func rejectBegin(_ error: Error) {
|
|
48
|
+
condition.lock()
|
|
49
|
+
let completion = beginCompletion
|
|
50
|
+
beginCompletion = nil
|
|
51
|
+
condition.unlock()
|
|
52
|
+
completion?(.failure(error))
|
|
27
53
|
}
|
|
28
54
|
|
|
29
55
|
func enqueue(_ op: TransactionOp) -> Bool {
|
|
@@ -132,7 +158,7 @@ private class TransactionSession {
|
|
|
132
158
|
}
|
|
133
159
|
}
|
|
134
160
|
|
|
135
|
-
|
|
161
|
+
enum TransactionOp {
|
|
136
162
|
case query(sql: String, params: [Any], completion: (Result<[[String: Any]], Error>) -> Void)
|
|
137
163
|
case queryOne(sql: String, params: [Any], completion: (Result<[String: Any]?, Error>) -> Void)
|
|
138
164
|
case execute(sql: String, params: [Any], completion: (Result<[String: Any], Error>) -> Void)
|
|
@@ -191,10 +217,10 @@ private actor LifecycleMutex {
|
|
|
191
217
|
public class SynchroModuleImpl: NSObject {
|
|
192
218
|
@objc public weak var eventDelegate: SynchroEventEmitting?
|
|
193
219
|
|
|
194
|
-
private var client: SynchroClient?
|
|
220
|
+
private(set) var client: SynchroClient?
|
|
195
221
|
private let lifecycleMutex = LifecycleMutex()
|
|
196
|
-
|
|
197
|
-
|
|
222
|
+
var sessions: [String: TransactionSession] = [:]
|
|
223
|
+
let sessionsLock = NSLock()
|
|
198
224
|
private var acceptingTransactions = false
|
|
199
225
|
private var observers: [String: any Synchro.Cancellable] = [:]
|
|
200
226
|
private var statusSubscription: (any Synchro.Cancellable)?
|
|
@@ -277,6 +303,8 @@ public class SynchroModuleImpl: NSObject {
|
|
|
277
303
|
return ("ALREADY_STARTED", [:])
|
|
278
304
|
case .notStarted:
|
|
279
305
|
return ("NOT_STARTED", [:])
|
|
306
|
+
case .atomicGroupInvalid(let reason):
|
|
307
|
+
return ("atomic_group_invalid", ["reason": reason.rawValue])
|
|
280
308
|
}
|
|
281
309
|
}
|
|
282
310
|
|
|
@@ -557,17 +585,7 @@ public class SynchroModuleImpl: NSObject {
|
|
|
557
585
|
return
|
|
558
586
|
}
|
|
559
587
|
do {
|
|
560
|
-
let
|
|
561
|
-
let sql = item["sql"] as! String
|
|
562
|
-
let params: [(any DatabaseValueConvertible)?]?
|
|
563
|
-
if let bridgeValues = item["params"] as? [Any] {
|
|
564
|
-
params = try bridgeParams(bridgeValues)
|
|
565
|
-
} else {
|
|
566
|
-
params = nil
|
|
567
|
-
}
|
|
568
|
-
return Synchro.SQLStatement(sql: sql, params: params)
|
|
569
|
-
}
|
|
570
|
-
let total = try client.executeBatch(nativeStatements)
|
|
588
|
+
let total = try client.executeBatch(try bridgeStatements(statements))
|
|
571
589
|
resolve(["totalRowsAffected": total])
|
|
572
590
|
} catch {
|
|
573
591
|
rejectWithError(reject, error)
|
|
@@ -584,6 +602,14 @@ public class SynchroModuleImpl: NSObject {
|
|
|
584
602
|
beginTransaction(isWrite: true, resolve: resolve, reject: reject)
|
|
585
603
|
}
|
|
586
604
|
|
|
605
|
+
@objc
|
|
606
|
+
public func beginAtomicWriteTransaction(
|
|
607
|
+
_ resolve: @escaping RCTPromiseResolveBlock,
|
|
608
|
+
reject: @escaping RCTPromiseRejectBlock
|
|
609
|
+
) {
|
|
610
|
+
beginTransaction(isWrite: true, isAtomic: true, resolve: resolve, reject: reject)
|
|
611
|
+
}
|
|
612
|
+
|
|
587
613
|
@objc
|
|
588
614
|
public func beginReadTransaction(
|
|
589
615
|
_ resolve: @escaping RCTPromiseResolveBlock,
|
|
@@ -595,12 +621,11 @@ public class SynchroModuleImpl: NSObject {
|
|
|
595
621
|
private func runTransactionLoop(
|
|
596
622
|
session: TransactionSession,
|
|
597
623
|
txID: String,
|
|
598
|
-
resolve: @escaping RCTPromiseResolveBlock,
|
|
599
624
|
query: (String, [(any DatabaseValueConvertible)?]) throws -> [Row],
|
|
600
625
|
queryOne: (String, [(any DatabaseValueConvertible)?]) throws -> Row?,
|
|
601
626
|
execute: ((String, [(any DatabaseValueConvertible)?]) throws -> Int)?
|
|
602
627
|
) throws {
|
|
603
|
-
|
|
628
|
+
try session.acceptBegin(txID)
|
|
604
629
|
|
|
605
630
|
while true {
|
|
606
631
|
guard let op = try session.nextOperation(timeout: 5) else {
|
|
@@ -669,6 +694,7 @@ public class SynchroModuleImpl: NSObject {
|
|
|
669
694
|
|
|
670
695
|
private func beginTransaction(
|
|
671
696
|
isWrite: Bool,
|
|
697
|
+
isAtomic: Bool = false,
|
|
672
698
|
resolve: @escaping RCTPromiseResolveBlock,
|
|
673
699
|
reject: @escaping RCTPromiseRejectBlock
|
|
674
700
|
) {
|
|
@@ -680,7 +706,12 @@ public class SynchroModuleImpl: NSObject {
|
|
|
680
706
|
}
|
|
681
707
|
|
|
682
708
|
let txID = UUID().uuidString
|
|
683
|
-
let session = TransactionSession(isWrite: isWrite)
|
|
709
|
+
let session = TransactionSession(isWrite: isWrite) { result in
|
|
710
|
+
switch result {
|
|
711
|
+
case .success(let acceptedID): resolve(acceptedID)
|
|
712
|
+
case .failure(let error): self.rejectWithError(reject, error)
|
|
713
|
+
}
|
|
714
|
+
}
|
|
684
715
|
sessions[txID] = session
|
|
685
716
|
sessionsLock.unlock()
|
|
686
717
|
|
|
@@ -695,11 +726,10 @@ public class SynchroModuleImpl: NSObject {
|
|
|
695
726
|
do {
|
|
696
727
|
let finalResult: Result<Void, Error>
|
|
697
728
|
if isWrite {
|
|
698
|
-
|
|
729
|
+
let body: (ApplicationTransaction) throws -> Void = { transaction in
|
|
699
730
|
try self.runTransactionLoop(
|
|
700
731
|
session: session,
|
|
701
732
|
txID: txID,
|
|
702
|
-
resolve: resolve,
|
|
703
733
|
query: { sql, params in
|
|
704
734
|
try transaction.query(sql, params: params)
|
|
705
735
|
},
|
|
@@ -711,12 +741,16 @@ public class SynchroModuleImpl: NSObject {
|
|
|
711
741
|
}
|
|
712
742
|
)
|
|
713
743
|
}
|
|
744
|
+
if isAtomic {
|
|
745
|
+
try client.atomicWriteTransaction(body)
|
|
746
|
+
} else {
|
|
747
|
+
try client.writeTransaction(body)
|
|
748
|
+
}
|
|
714
749
|
} else {
|
|
715
750
|
try client.readTransaction { db in
|
|
716
751
|
try self.runTransactionLoop(
|
|
717
752
|
session: session,
|
|
718
753
|
txID: txID,
|
|
719
|
-
resolve: resolve,
|
|
720
754
|
query: { sql, params in
|
|
721
755
|
let statement = try db.makeStatement(sql: sql)
|
|
722
756
|
guard statement.isReadonly else {
|
|
@@ -748,6 +782,8 @@ public class SynchroModuleImpl: NSObject {
|
|
|
748
782
|
} catch is TransactionRollbackError {
|
|
749
783
|
session.completeFinal(.success(()))
|
|
750
784
|
} catch {
|
|
785
|
+
// Acquisition can fail before the callback settles begin.
|
|
786
|
+
session.rejectBegin(error)
|
|
751
787
|
session.completeFinal(.failure(error))
|
|
752
788
|
}
|
|
753
789
|
}
|
|
@@ -842,6 +878,7 @@ public class SynchroModuleImpl: NSObject {
|
|
|
842
878
|
let op = TransactionOp.commit { result in
|
|
843
879
|
switch result {
|
|
844
880
|
case .success: resolve(nil)
|
|
881
|
+
case .failure(let error as SynchroError): self.rejectWithError(reject, error)
|
|
845
882
|
case .failure(let error): reject("DATABASE_ERROR", error.localizedDescription, error)
|
|
846
883
|
}
|
|
847
884
|
}
|
|
@@ -1525,6 +1562,23 @@ public class SynchroModuleImpl: NSObject {
|
|
|
1525
1562
|
}
|
|
1526
1563
|
}
|
|
1527
1564
|
|
|
1565
|
+
@objc
|
|
1566
|
+
public func inspectRetainedMutationRecords(
|
|
1567
|
+
_ resolve: @escaping RCTPromiseResolveBlock,
|
|
1568
|
+
reject: @escaping RCTPromiseRejectBlock
|
|
1569
|
+
) {
|
|
1570
|
+
guard let client = client else {
|
|
1571
|
+
reject("NOT_CONNECTED", "Client not initialized", nil)
|
|
1572
|
+
return
|
|
1573
|
+
}
|
|
1574
|
+
do {
|
|
1575
|
+
let payload = try client.inspectRetainedMutationRecords().map(retainedMutationPayload)
|
|
1576
|
+
resolve(try encodeBridgeJSON(payload))
|
|
1577
|
+
} catch {
|
|
1578
|
+
rejectWithError(reject, error)
|
|
1579
|
+
}
|
|
1580
|
+
}
|
|
1581
|
+
|
|
1528
1582
|
@objc
|
|
1529
1583
|
public func inspectRejectedMutations(
|
|
1530
1584
|
_ resolve: @escaping RCTPromiseResolveBlock,
|
|
@@ -1543,81 +1597,66 @@ public class SynchroModuleImpl: NSObject {
|
|
|
1543
1597
|
}
|
|
1544
1598
|
|
|
1545
1599
|
@objc
|
|
1546
|
-
public func
|
|
1600
|
+
public func inspectRejectedMutationRecords(
|
|
1547
1601
|
_ resolve: @escaping RCTPromiseResolveBlock,
|
|
1548
1602
|
reject: @escaping RCTPromiseRejectBlock
|
|
1603
|
+
) {
|
|
1604
|
+
guard let client = client else {
|
|
1605
|
+
reject("NOT_CONNECTED", "Client not initialized", nil)
|
|
1606
|
+
return
|
|
1607
|
+
}
|
|
1608
|
+
do {
|
|
1609
|
+
let payload = try client.inspectRejectedMutationRecords().map(retainedRejectionPayload)
|
|
1610
|
+
resolve(try encodeBridgeJSON(payload))
|
|
1611
|
+
} catch {
|
|
1612
|
+
rejectWithError(reject, error)
|
|
1613
|
+
}
|
|
1614
|
+
}
|
|
1615
|
+
|
|
1616
|
+
/// Reads client state, retained details, and the requested application rows
|
|
1617
|
+
/// from one read-only snapshot, as the native conformance runners do.
|
|
1618
|
+
@objc
|
|
1619
|
+
public func inspectClientStateSnapshot(
|
|
1620
|
+
_ rowStatements: [[String: Any]],
|
|
1621
|
+
resolve: @escaping RCTPromiseResolveBlock,
|
|
1622
|
+
reject: @escaping RCTPromiseRejectBlock
|
|
1549
1623
|
) {
|
|
1550
1624
|
guard let client else {
|
|
1551
1625
|
reject("NOT_CONNECTED", "Client not initialized", nil)
|
|
1552
1626
|
return
|
|
1553
1627
|
}
|
|
1554
1628
|
do {
|
|
1555
|
-
let
|
|
1556
|
-
|
|
1557
|
-
let
|
|
1558
|
-
|
|
1559
|
-
|
|
1560
|
-
|
|
1561
|
-
[
|
|
1562
|
-
"scope_id": value.scopeID,
|
|
1563
|
-
"cursor": value.cursor ?? NSNull(),
|
|
1564
|
-
"checksum": value.checksum ?? NSNull(),
|
|
1565
|
-
"local_checksum": value.localChecksum,
|
|
1566
|
-
"generation": value.generation,
|
|
1567
|
-
] as [String: Any]
|
|
1568
|
-
}
|
|
1569
|
-
let scopeRows = capture.scopeRows.map { value in
|
|
1570
|
-
[
|
|
1571
|
-
"scope_id": value.scopeID,
|
|
1572
|
-
"table_name": value.tableName,
|
|
1573
|
-
"record_id": value.recordID,
|
|
1574
|
-
"checksum": value.checksum,
|
|
1575
|
-
"generation": value.generation,
|
|
1576
|
-
] as [String: Any]
|
|
1577
|
-
}
|
|
1578
|
-
let attempts = capture.rebuildAttempts.map { value in
|
|
1579
|
-
[
|
|
1580
|
-
"scope_id": value.scopeID,
|
|
1581
|
-
"rebuild_id": value.rebuildID,
|
|
1582
|
-
"client_generation": value.clientGeneration,
|
|
1583
|
-
"schema_version": value.schemaVersion,
|
|
1584
|
-
"schema_hash": value.schemaHash,
|
|
1585
|
-
"generation": value.generation,
|
|
1586
|
-
"cursor": value.cursor ?? NSNull(),
|
|
1587
|
-
"page_limit": value.pageLimit,
|
|
1588
|
-
] as [String: Any]
|
|
1629
|
+
let statements = try bridgeStatements(rowStatements)
|
|
1630
|
+
var rows: [Row] = []
|
|
1631
|
+
let snapshot = try SynchroInspection(client: client).captureSnapshot(maximumRecords: 512) { _, transaction in
|
|
1632
|
+
for statement in statements {
|
|
1633
|
+
rows += try transaction.query(statement.sql, params: statement.params)
|
|
1634
|
+
}
|
|
1589
1635
|
}
|
|
1590
|
-
|
|
1591
|
-
"
|
|
1592
|
-
"
|
|
1593
|
-
|
|
1594
|
-
|
|
1595
|
-
"
|
|
1596
|
-
"
|
|
1597
|
-
"
|
|
1598
|
-
"
|
|
1599
|
-
"
|
|
1600
|
-
"
|
|
1601
|
-
"
|
|
1602
|
-
|
|
1603
|
-
|
|
1604
|
-
"
|
|
1605
|
-
"
|
|
1606
|
-
|
|
1607
|
-
"scope_rows_truncated": capture.scopeRowsTruncated,
|
|
1608
|
-
"rebuild_attempts_truncated": capture.rebuildAttemptsTruncated,
|
|
1609
|
-
"rebuild_receipts_truncated": capture.rebuildReceiptsTruncated,
|
|
1610
|
-
"row_metadata_truncated": capture.rowMetadataTruncated,
|
|
1611
|
-
"capture_overflowed": capture.overflowed,
|
|
1612
|
-
"provenance_maintenance_work_cursor": String(
|
|
1613
|
-
capture.provenanceMaintenanceWorkCursor
|
|
1614
|
-
),
|
|
1615
|
-
]))
|
|
1636
|
+
let inspection: [String: Any] = [
|
|
1637
|
+
"client_state": clientStatePayload(snapshot.capture),
|
|
1638
|
+
"migration_journal": try snapshot.capture.migrationJournal.map {
|
|
1639
|
+
try JSONSerialization.jsonObject(with: JSONEncoder().encode($0))
|
|
1640
|
+
} ?? NSNull(),
|
|
1641
|
+
"migration_journal_truncated": snapshot.capture.migrationJournalTruncated,
|
|
1642
|
+
"physical_schema": try JSONSerialization.jsonObject(with: JSONEncoder().encode(snapshot.capture.physicalSchema)),
|
|
1643
|
+
"physical_schema_truncated": snapshot.capture.physicalSchemaTruncated,
|
|
1644
|
+
"accepted_mutation_outcomes": snapshot.capture.acceptedMutationOutcomes,
|
|
1645
|
+
"accepted_mutation_outcomes_truncated": snapshot.capture.acceptedMutationOutcomesTruncated,
|
|
1646
|
+
"retained_mutations": snapshot.retainedMutations.map { $0.map(retainedMutationPayload) } ?? NSNull(),
|
|
1647
|
+
"rejected_mutations": snapshot.rejectedMutations.map { $0.map(retainedRejectionPayload) } ?? NSNull(),
|
|
1648
|
+
]
|
|
1649
|
+
resolve([
|
|
1650
|
+
"inspection": try encodeBridgeJSON(inspection),
|
|
1651
|
+
"applicationRows": rowsToBridgeRows(rows),
|
|
1652
|
+
])
|
|
1616
1653
|
} catch {
|
|
1617
1654
|
rejectWithError(reject, error)
|
|
1618
1655
|
}
|
|
1619
1656
|
}
|
|
1620
1657
|
|
|
1658
|
+
/// Reads one record's row metadata and every rebuild receipt. The inspection snapshot bounds
|
|
1659
|
+
/// row metadata, so a durable proof reads its identity directly.
|
|
1621
1660
|
@objc
|
|
1622
1661
|
public func inspectDurableState(
|
|
1623
1662
|
_ tableName: String,
|
|
@@ -1693,13 +1732,18 @@ public class SynchroModuleImpl: NSObject {
|
|
|
1693
1732
|
resolve: @escaping RCTPromiseResolveBlock,
|
|
1694
1733
|
reject: @escaping RCTPromiseRejectBlock
|
|
1695
1734
|
) {
|
|
1696
|
-
guard let collector = transportObservations
|
|
1697
|
-
let operation = TransportOperationClass(rawValue: operationClass) else {
|
|
1735
|
+
guard let collector = transportObservations else {
|
|
1698
1736
|
reject("INVALID_CONFIG", "Transport pause operation is invalid", nil)
|
|
1699
1737
|
return
|
|
1700
1738
|
}
|
|
1701
1739
|
do {
|
|
1702
|
-
|
|
1740
|
+
if let checkpoint = MigrationCheckpoint(rawValue: operationClass) {
|
|
1741
|
+
try collector.armPause(for: checkpoint)
|
|
1742
|
+
} else if let operation = TransportOperationClass(rawValue: operationClass) {
|
|
1743
|
+
try collector.armPause(for: operation)
|
|
1744
|
+
} else {
|
|
1745
|
+
throw SynchroError.invalidResponse(message: "pause target is invalid")
|
|
1746
|
+
}
|
|
1703
1747
|
resolve(nil)
|
|
1704
1748
|
} catch {
|
|
1705
1749
|
rejectWithError(reject, error)
|
|
@@ -1714,14 +1758,19 @@ public class SynchroModuleImpl: NSObject {
|
|
|
1714
1758
|
reject: @escaping RCTPromiseRejectBlock
|
|
1715
1759
|
) {
|
|
1716
1760
|
guard let collector = transportObservations,
|
|
1717
|
-
let operation = TransportOperationClass(rawValue: operationClass),
|
|
1718
1761
|
timeoutMs.isFinite, timeoutMs >= 1, timeoutMs <= 60_000 else {
|
|
1719
1762
|
reject("INVALID_CONFIG", "Transport pause wait is invalid", nil)
|
|
1720
1763
|
return
|
|
1721
1764
|
}
|
|
1722
1765
|
Task {
|
|
1723
1766
|
do {
|
|
1724
|
-
|
|
1767
|
+
if let checkpoint = MigrationCheckpoint(rawValue: operationClass) {
|
|
1768
|
+
try await collector.awaitPause(for: checkpoint, timeout: timeoutMs / 1_000)
|
|
1769
|
+
} else if let operation = TransportOperationClass(rawValue: operationClass) {
|
|
1770
|
+
try await collector.awaitPause(for: operation, timeout: timeoutMs / 1_000)
|
|
1771
|
+
} else {
|
|
1772
|
+
throw SynchroError.invalidResponse(message: "pause target is invalid")
|
|
1773
|
+
}
|
|
1725
1774
|
DispatchQueue.main.async { resolve(nil) }
|
|
1726
1775
|
} catch {
|
|
1727
1776
|
DispatchQueue.main.async { self.rejectWithError(reject, error) }
|
|
@@ -1773,6 +1822,78 @@ public class SynchroModuleImpl: NSObject {
|
|
|
1773
1822
|
|
|
1774
1823
|
// MARK: - Helpers
|
|
1775
1824
|
|
|
1825
|
+
private func clientStatePayload(_ capture: ClientStateCaptureInspection) -> [String: Any] {
|
|
1826
|
+
let schema: Any = capture.schema.map { value in
|
|
1827
|
+
["version": value.version, "hash": value.hash]
|
|
1828
|
+
} ?? NSNull()
|
|
1829
|
+
let scopeStates = capture.scopeStates.map { value in
|
|
1830
|
+
[
|
|
1831
|
+
"scope_id": value.scopeID,
|
|
1832
|
+
"cursor": value.cursor ?? NSNull(),
|
|
1833
|
+
"checksum": value.checksum ?? NSNull(),
|
|
1834
|
+
"local_checksum": value.localChecksum,
|
|
1835
|
+
"generation": value.generation,
|
|
1836
|
+
] as [String: Any]
|
|
1837
|
+
}
|
|
1838
|
+
let scopeRows = capture.scopeRows.map { value in
|
|
1839
|
+
[
|
|
1840
|
+
"scope_id": value.scopeID,
|
|
1841
|
+
"table_name": value.tableName,
|
|
1842
|
+
"record_id": value.recordID,
|
|
1843
|
+
"checksum": value.checksum,
|
|
1844
|
+
"generation": value.generation,
|
|
1845
|
+
] as [String: Any]
|
|
1846
|
+
}
|
|
1847
|
+
let attempts = capture.rebuildAttempts.map { value in
|
|
1848
|
+
[
|
|
1849
|
+
"scope_id": value.scopeID,
|
|
1850
|
+
"rebuild_id": value.rebuildID,
|
|
1851
|
+
"client_generation": value.clientGeneration,
|
|
1852
|
+
"schema_version": value.schemaVersion,
|
|
1853
|
+
"schema_hash": value.schemaHash,
|
|
1854
|
+
"generation": value.generation,
|
|
1855
|
+
"cursor": value.cursor ?? NSNull(),
|
|
1856
|
+
"page_limit": value.pageLimit,
|
|
1857
|
+
] as [String: Any]
|
|
1858
|
+
}
|
|
1859
|
+
return [
|
|
1860
|
+
"schema": schema,
|
|
1861
|
+
"scope_states": scopeStates,
|
|
1862
|
+
"scope_rows": scopeRows,
|
|
1863
|
+
"rebuild_attempts": attempts,
|
|
1864
|
+
"application_row_count": capture.applicationRowCount,
|
|
1865
|
+
"mutation_ledger_count": capture.mutationLedgerCount,
|
|
1866
|
+
"mutation_outcome_count": capture.mutationOutcomeCount,
|
|
1867
|
+
"sealed_batch_count": capture.sealedBatchCount,
|
|
1868
|
+
"rejected_mutation_count": capture.rejectedMutationCount,
|
|
1869
|
+
"scope_state_count": capture.scopeStateCount,
|
|
1870
|
+
"scope_row_count": capture.scopeRowCount,
|
|
1871
|
+
"provenance_count": capture.provenanceCount,
|
|
1872
|
+
"row_metadata_count": capture.rowMetadataCount,
|
|
1873
|
+
"rebuild_attempt_count": capture.rebuildAttemptCount,
|
|
1874
|
+
"rebuild_receipt_count": capture.rebuildReceiptCount,
|
|
1875
|
+
"scope_states_truncated": capture.scopeStatesTruncated,
|
|
1876
|
+
"scope_rows_truncated": capture.scopeRowsTruncated,
|
|
1877
|
+
"rebuild_attempts_truncated": capture.rebuildAttemptsTruncated,
|
|
1878
|
+
"rebuild_receipts_truncated": capture.rebuildReceiptsTruncated,
|
|
1879
|
+
"row_metadata_truncated": capture.rowMetadataTruncated,
|
|
1880
|
+
"capture_overflowed": capture.overflowed,
|
|
1881
|
+
"provenance_maintenance_work_cursor": String(
|
|
1882
|
+
capture.provenanceMaintenanceWorkCursor
|
|
1883
|
+
),
|
|
1884
|
+
]
|
|
1885
|
+
}
|
|
1886
|
+
|
|
1887
|
+
private func bridgeStatements(_ statements: [[String: Any]]) throws -> [Synchro.SQLStatement] {
|
|
1888
|
+
try statements.enumerated().map { index, item in
|
|
1889
|
+
guard let sql = item["sql"] as? String else {
|
|
1890
|
+
throw SynchroError.invalidResponse(message: "Missing SQL at index \(index)")
|
|
1891
|
+
}
|
|
1892
|
+
let params = try (item["params"] as? [Any]).map(bridgeParams)
|
|
1893
|
+
return Synchro.SQLStatement(sql: sql, params: params)
|
|
1894
|
+
}
|
|
1895
|
+
}
|
|
1896
|
+
|
|
1776
1897
|
private func encodeBridgeJSON(_ value: Any) throws -> String {
|
|
1777
1898
|
guard JSONSerialization.isValidJSONObject(value) else {
|
|
1778
1899
|
throw SynchroError.invalidResponse(message: "bridge JSON value is invalid")
|
|
@@ -1784,6 +1905,26 @@ public class SynchroModuleImpl: NSObject {
|
|
|
1784
1905
|
return json
|
|
1785
1906
|
}
|
|
1786
1907
|
|
|
1908
|
+
private func retainedMutationPayload(_ value: RetainedMutationInspection) -> [String: Any] {
|
|
1909
|
+
switch value {
|
|
1910
|
+
case .current(let mutation):
|
|
1911
|
+
return pendingMutationPayload(mutation).merging(["representation": "current"]) { current, _ in current }
|
|
1912
|
+
case .legacy(let mutation):
|
|
1913
|
+
return [
|
|
1914
|
+
"representation": "legacy",
|
|
1915
|
+
"mutationID": mutation.mutationID,
|
|
1916
|
+
"localOrder": mutation.localOrder,
|
|
1917
|
+
"tableName": mutation.tableName,
|
|
1918
|
+
"recordID": mutation.recordID,
|
|
1919
|
+
"operation": mutation.operation.rawValue,
|
|
1920
|
+
"baseVersion": mutation.baseVersion ?? NSNull(),
|
|
1921
|
+
"clientVersion": mutation.clientVersion,
|
|
1922
|
+
"status": mutation.status.rawValue,
|
|
1923
|
+
"sourceKind": mutation.sourceKind
|
|
1924
|
+
]
|
|
1925
|
+
}
|
|
1926
|
+
}
|
|
1927
|
+
|
|
1787
1928
|
private func pendingMutationPayload(_ mutation: PendingMutationInspection) -> [String: Any] {
|
|
1788
1929
|
[
|
|
1789
1930
|
"mutationID": mutation.mutationID,
|
|
@@ -1816,6 +1957,27 @@ public class SynchroModuleImpl: NSObject {
|
|
|
1816
1957
|
]
|
|
1817
1958
|
}
|
|
1818
1959
|
|
|
1960
|
+
private func retainedRejectionPayload(_ value: RetainedRejectionInspection) -> [String: Any] {
|
|
1961
|
+
switch value {
|
|
1962
|
+
case .current(let rejection):
|
|
1963
|
+
return rejectedMutationPayload(rejection).merging(["representation": "current"]) { current, _ in current }
|
|
1964
|
+
case .legacy(let rejection):
|
|
1965
|
+
return [
|
|
1966
|
+
"representation": "legacy",
|
|
1967
|
+
"mutationID": rejection.mutationID,
|
|
1968
|
+
"tableName": rejection.tableName,
|
|
1969
|
+
"recordID": rejection.recordID,
|
|
1970
|
+
"status": rejection.status.rawValue,
|
|
1971
|
+
"code": rejection.code.rawValue,
|
|
1972
|
+
"message": rejection.message ?? NSNull(),
|
|
1973
|
+
"serverRowJSON": rejection.serverRowJSON ?? NSNull(),
|
|
1974
|
+
"serverVersion": rejection.serverVersion ?? NSNull(),
|
|
1975
|
+
"createdAt": rejection.createdAt,
|
|
1976
|
+
"updatedAt": rejection.updatedAt
|
|
1977
|
+
]
|
|
1978
|
+
}
|
|
1979
|
+
}
|
|
1980
|
+
|
|
1819
1981
|
private func rejectedMutationPayload(_ mutation: RejectedMutationInspection) -> [String: Any] {
|
|
1820
1982
|
[
|
|
1821
1983
|
"mutationID": mutation.mutationID,
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
#import <UIKit/UIKit.h>
|
|
2
|
+
|
|
3
|
+
@interface SynchroTestHostSceneDelegate : UIResponder <UIWindowSceneDelegate>
|
|
4
|
+
@property (nonatomic, strong) UIWindow *window;
|
|
5
|
+
@end
|
|
6
|
+
|
|
7
|
+
@implementation SynchroTestHostSceneDelegate
|
|
8
|
+
|
|
9
|
+
- (void)scene:(UIScene *)scene
|
|
10
|
+
willConnectToSession:(UISceneSession *)session
|
|
11
|
+
options:(UISceneConnectionOptions *)connectionOptions
|
|
12
|
+
{
|
|
13
|
+
if (![scene isKindOfClass:[UIWindowScene class]]) {
|
|
14
|
+
return;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
self.window = [[UIWindow alloc] initWithWindowScene:(UIWindowScene *)scene];
|
|
18
|
+
self.window.rootViewController = [UIViewController new];
|
|
19
|
+
[self.window makeKeyAndVisible];
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
@end
|
|
23
|
+
|
|
24
|
+
@interface SynchroTestHostAppDelegate : UIResponder <UIApplicationDelegate>
|
|
25
|
+
@end
|
|
26
|
+
|
|
27
|
+
@implementation SynchroTestHostAppDelegate
|
|
28
|
+
|
|
29
|
+
- (UISceneConfiguration *)application:(UIApplication *)application
|
|
30
|
+
configurationForConnectingSceneSession:(UISceneSession *)connectingSceneSession
|
|
31
|
+
options:(UISceneConnectionOptions *)options
|
|
32
|
+
{
|
|
33
|
+
UISceneConfiguration *configuration = [[UISceneConfiguration alloc]
|
|
34
|
+
initWithName:@"Default Configuration"
|
|
35
|
+
sessionRole:connectingSceneSession.role];
|
|
36
|
+
configuration.sceneClass = [UIWindowScene class];
|
|
37
|
+
configuration.delegateClass = [SynchroTestHostSceneDelegate class];
|
|
38
|
+
return configuration;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
@end
|
|
42
|
+
|
|
43
|
+
int main(int argc, char *argv[])
|
|
44
|
+
{
|
|
45
|
+
@autoreleasepool {
|
|
46
|
+
return UIApplicationMain(argc, argv, nil, NSStringFromClass([SynchroTestHostAppDelegate class]));
|
|
47
|
+
}
|
|
48
|
+
}
|