@trainstar/synchro-react-native 0.3.1 → 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.
- package/README.md +58 -4
- package/SynchroReactNative.podspec +6 -0
- package/android/build.gradle +5 -1
- package/android/src/androidTest/kotlin/com/trainstar/synchro/rn/SynchroModuleTransactionTest.kt +591 -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 +307 -110
- package/ios/SynchroModule.mm +27 -4
- package/ios/SynchroModule.swift +228 -84
- package/ios-tests/SynchroModuleTransactionTests.swift +707 -0
- package/ios-tests/TransactionSessionTests.swift +66 -0
- package/lib/module/NativeSynchro.js.map +1 -1
- package/lib/module/SynchroClient.js +116 -3
- 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 +31 -3
- 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/corpus-harness.d.ts.map +1 -1
- package/lib/typescript/example/src/conformance/runner.d.ts +1 -0
- package/lib/typescript/example/src/conformance/runner.d.ts.map +1 -1
- package/lib/typescript/example/src/timeouts.d.ts +2 -0
- package/lib/typescript/example/src/timeouts.d.ts.map +1 -0
- 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 +13 -2
- package/lib/typescript/src/inspection.d.ts.map +1 -1
- package/lib/typescript/src/types.d.ts +45 -2
- package/lib/typescript/src/types.d.ts.map +1 -1
- package/package.json +3 -2
- package/src/NativeSynchro.ts +6 -1
- package/src/SynchroClient.ts +129 -1
- package/src/errors.ts +30 -0
- package/src/index.ts +7 -0
- package/src/inspection.ts +44 -2
- package/src/types.ts +51 -2
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,58 @@ 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
|
-
"rejected_mutation_count": capture.rejectedMutationCount,
|
|
1600
|
-
"scope_state_count": capture.scopeStateCount,
|
|
1601
|
-
"scope_row_count": capture.scopeRowCount,
|
|
1602
|
-
"provenance_count": capture.provenanceCount,
|
|
1603
|
-
"row_metadata_count": capture.rowMetadataCount,
|
|
1604
|
-
"rebuild_attempt_count": capture.rebuildAttemptCount,
|
|
1605
|
-
"rebuild_receipt_count": capture.rebuildReceiptCount,
|
|
1606
|
-
"scope_states_truncated": capture.scopeStatesTruncated,
|
|
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
|
+
"retained_mutations": snapshot.retainedMutations.map { $0.map(retainedMutationPayload) } ?? NSNull(),
|
|
1639
|
+
"rejected_mutations": snapshot.rejectedMutations.map { $0.map(retainedRejectionPayload) } ?? NSNull(),
|
|
1640
|
+
]
|
|
1641
|
+
resolve([
|
|
1642
|
+
"inspection": try encodeBridgeJSON(inspection),
|
|
1643
|
+
"applicationRows": rowsToBridgeRows(rows),
|
|
1644
|
+
])
|
|
1616
1645
|
} catch {
|
|
1617
1646
|
rejectWithError(reject, error)
|
|
1618
1647
|
}
|
|
1619
1648
|
}
|
|
1620
1649
|
|
|
1650
|
+
/// Reads one record's row metadata and every rebuild receipt. The inspection snapshot bounds
|
|
1651
|
+
/// row metadata, so a durable proof reads its identity directly.
|
|
1621
1652
|
@objc
|
|
1622
1653
|
public func inspectDurableState(
|
|
1623
1654
|
_ tableName: String,
|
|
@@ -1773,6 +1804,78 @@ public class SynchroModuleImpl: NSObject {
|
|
|
1773
1804
|
|
|
1774
1805
|
// MARK: - Helpers
|
|
1775
1806
|
|
|
1807
|
+
private func clientStatePayload(_ capture: ClientStateCaptureInspection) -> [String: Any] {
|
|
1808
|
+
let schema: Any = capture.schema.map { value in
|
|
1809
|
+
["version": value.version, "hash": value.hash]
|
|
1810
|
+
} ?? NSNull()
|
|
1811
|
+
let scopeStates = capture.scopeStates.map { value in
|
|
1812
|
+
[
|
|
1813
|
+
"scope_id": value.scopeID,
|
|
1814
|
+
"cursor": value.cursor ?? NSNull(),
|
|
1815
|
+
"checksum": value.checksum ?? NSNull(),
|
|
1816
|
+
"local_checksum": value.localChecksum,
|
|
1817
|
+
"generation": value.generation,
|
|
1818
|
+
] as [String: Any]
|
|
1819
|
+
}
|
|
1820
|
+
let scopeRows = capture.scopeRows.map { value in
|
|
1821
|
+
[
|
|
1822
|
+
"scope_id": value.scopeID,
|
|
1823
|
+
"table_name": value.tableName,
|
|
1824
|
+
"record_id": value.recordID,
|
|
1825
|
+
"checksum": value.checksum,
|
|
1826
|
+
"generation": value.generation,
|
|
1827
|
+
] as [String: Any]
|
|
1828
|
+
}
|
|
1829
|
+
let attempts = capture.rebuildAttempts.map { value in
|
|
1830
|
+
[
|
|
1831
|
+
"scope_id": value.scopeID,
|
|
1832
|
+
"rebuild_id": value.rebuildID,
|
|
1833
|
+
"client_generation": value.clientGeneration,
|
|
1834
|
+
"schema_version": value.schemaVersion,
|
|
1835
|
+
"schema_hash": value.schemaHash,
|
|
1836
|
+
"generation": value.generation,
|
|
1837
|
+
"cursor": value.cursor ?? NSNull(),
|
|
1838
|
+
"page_limit": value.pageLimit,
|
|
1839
|
+
] as [String: Any]
|
|
1840
|
+
}
|
|
1841
|
+
return [
|
|
1842
|
+
"schema": schema,
|
|
1843
|
+
"scope_states": scopeStates,
|
|
1844
|
+
"scope_rows": scopeRows,
|
|
1845
|
+
"rebuild_attempts": attempts,
|
|
1846
|
+
"application_row_count": capture.applicationRowCount,
|
|
1847
|
+
"mutation_ledger_count": capture.mutationLedgerCount,
|
|
1848
|
+
"mutation_outcome_count": capture.mutationOutcomeCount,
|
|
1849
|
+
"sealed_batch_count": capture.sealedBatchCount,
|
|
1850
|
+
"rejected_mutation_count": capture.rejectedMutationCount,
|
|
1851
|
+
"scope_state_count": capture.scopeStateCount,
|
|
1852
|
+
"scope_row_count": capture.scopeRowCount,
|
|
1853
|
+
"provenance_count": capture.provenanceCount,
|
|
1854
|
+
"row_metadata_count": capture.rowMetadataCount,
|
|
1855
|
+
"rebuild_attempt_count": capture.rebuildAttemptCount,
|
|
1856
|
+
"rebuild_receipt_count": capture.rebuildReceiptCount,
|
|
1857
|
+
"scope_states_truncated": capture.scopeStatesTruncated,
|
|
1858
|
+
"scope_rows_truncated": capture.scopeRowsTruncated,
|
|
1859
|
+
"rebuild_attempts_truncated": capture.rebuildAttemptsTruncated,
|
|
1860
|
+
"rebuild_receipts_truncated": capture.rebuildReceiptsTruncated,
|
|
1861
|
+
"row_metadata_truncated": capture.rowMetadataTruncated,
|
|
1862
|
+
"capture_overflowed": capture.overflowed,
|
|
1863
|
+
"provenance_maintenance_work_cursor": String(
|
|
1864
|
+
capture.provenanceMaintenanceWorkCursor
|
|
1865
|
+
),
|
|
1866
|
+
]
|
|
1867
|
+
}
|
|
1868
|
+
|
|
1869
|
+
private func bridgeStatements(_ statements: [[String: Any]]) throws -> [Synchro.SQLStatement] {
|
|
1870
|
+
try statements.enumerated().map { index, item in
|
|
1871
|
+
guard let sql = item["sql"] as? String else {
|
|
1872
|
+
throw SynchroError.invalidResponse(message: "Missing SQL at index \(index)")
|
|
1873
|
+
}
|
|
1874
|
+
let params = try (item["params"] as? [Any]).map(bridgeParams)
|
|
1875
|
+
return Synchro.SQLStatement(sql: sql, params: params)
|
|
1876
|
+
}
|
|
1877
|
+
}
|
|
1878
|
+
|
|
1776
1879
|
private func encodeBridgeJSON(_ value: Any) throws -> String {
|
|
1777
1880
|
guard JSONSerialization.isValidJSONObject(value) else {
|
|
1778
1881
|
throw SynchroError.invalidResponse(message: "bridge JSON value is invalid")
|
|
@@ -1784,6 +1887,26 @@ public class SynchroModuleImpl: NSObject {
|
|
|
1784
1887
|
return json
|
|
1785
1888
|
}
|
|
1786
1889
|
|
|
1890
|
+
private func retainedMutationPayload(_ value: RetainedMutationInspection) -> [String: Any] {
|
|
1891
|
+
switch value {
|
|
1892
|
+
case .current(let mutation):
|
|
1893
|
+
return pendingMutationPayload(mutation).merging(["representation": "current"]) { current, _ in current }
|
|
1894
|
+
case .legacy(let mutation):
|
|
1895
|
+
return [
|
|
1896
|
+
"representation": "legacy",
|
|
1897
|
+
"mutationID": mutation.mutationID,
|
|
1898
|
+
"localOrder": mutation.localOrder,
|
|
1899
|
+
"tableName": mutation.tableName,
|
|
1900
|
+
"recordID": mutation.recordID,
|
|
1901
|
+
"operation": mutation.operation.rawValue,
|
|
1902
|
+
"baseVersion": mutation.baseVersion ?? NSNull(),
|
|
1903
|
+
"clientVersion": mutation.clientVersion,
|
|
1904
|
+
"status": mutation.status.rawValue,
|
|
1905
|
+
"sourceKind": mutation.sourceKind
|
|
1906
|
+
]
|
|
1907
|
+
}
|
|
1908
|
+
}
|
|
1909
|
+
|
|
1787
1910
|
private func pendingMutationPayload(_ mutation: PendingMutationInspection) -> [String: Any] {
|
|
1788
1911
|
[
|
|
1789
1912
|
"mutationID": mutation.mutationID,
|
|
@@ -1816,6 +1939,27 @@ public class SynchroModuleImpl: NSObject {
|
|
|
1816
1939
|
]
|
|
1817
1940
|
}
|
|
1818
1941
|
|
|
1942
|
+
private func retainedRejectionPayload(_ value: RetainedRejectionInspection) -> [String: Any] {
|
|
1943
|
+
switch value {
|
|
1944
|
+
case .current(let rejection):
|
|
1945
|
+
return rejectedMutationPayload(rejection).merging(["representation": "current"]) { current, _ in current }
|
|
1946
|
+
case .legacy(let rejection):
|
|
1947
|
+
return [
|
|
1948
|
+
"representation": "legacy",
|
|
1949
|
+
"mutationID": rejection.mutationID,
|
|
1950
|
+
"tableName": rejection.tableName,
|
|
1951
|
+
"recordID": rejection.recordID,
|
|
1952
|
+
"status": rejection.status.rawValue,
|
|
1953
|
+
"code": rejection.code.rawValue,
|
|
1954
|
+
"message": rejection.message ?? NSNull(),
|
|
1955
|
+
"serverRowJSON": rejection.serverRowJSON ?? NSNull(),
|
|
1956
|
+
"serverVersion": rejection.serverVersion ?? NSNull(),
|
|
1957
|
+
"createdAt": rejection.createdAt,
|
|
1958
|
+
"updatedAt": rejection.updatedAt
|
|
1959
|
+
]
|
|
1960
|
+
}
|
|
1961
|
+
}
|
|
1962
|
+
|
|
1819
1963
|
private func rejectedMutationPayload(_ mutation: RejectedMutationInspection) -> [String: Any] {
|
|
1820
1964
|
[
|
|
1821
1965
|
"mutationID": mutation.mutationID,
|