@trainstar/synchro-react-native 0.4.0-rc.2 → 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.
@@ -179,6 +179,7 @@ enum TransactionOp {
179
179
  private struct TransactionTimeoutError: Error {}
180
180
  private struct TransactionRollbackError: Error {}
181
181
  private struct DatabaseAlreadyExistsError: Error {}
182
+ private struct InvalidAuthoredWriteOperationError: Error {}
182
183
  private struct TransactionAbortedError: LocalizedError {
183
184
  let message: String
184
185
 
@@ -219,6 +220,7 @@ public class SynchroModuleImpl: NSObject {
219
220
 
220
221
  private(set) var client: SynchroClient?
221
222
  private let lifecycleMutex = LifecycleMutex()
223
+ private let databaseBridgeQueue = DispatchQueue(label: "com.trainstar.synchro.react-native.database")
222
224
  var sessions: [String: TransactionSession] = [:]
223
225
  let sessionsLock = NSLock()
224
226
  private var acceptingTransactions = false
@@ -253,6 +255,8 @@ public class SynchroModuleImpl: NSObject {
253
255
  reject("TRANSACTION_TIMEOUT", "Transaction timed out due to inactivity", nil)
254
256
  } else if error is TransactionAbortedError {
255
257
  reject("NOT_CONNECTED", "Client closed during transaction", error)
258
+ } else if error is InvalidAuthoredWriteOperationError {
259
+ reject("INVALID_CONFIG", "Invalid authored write operation", nil)
256
260
  } else {
257
261
  reject("UNKNOWN", error.localizedDescription, error)
258
262
  }
@@ -487,16 +491,13 @@ public class SynchroModuleImpl: NSObject {
487
491
  resolve: @escaping RCTPromiseResolveBlock,
488
492
  reject: @escaping RCTPromiseRejectBlock
489
493
  ) {
490
- guard let client = client else {
491
- reject("NOT_CONNECTED", "Client not initialized", nil)
492
- return
493
- }
494
- do {
495
- let rows = try client.query(sql, params: try bridgeParams(params))
496
- resolve(rowsToBridgeRows(rows))
497
- } catch {
498
- rejectWithError(reject, error)
499
- }
494
+ submitDatabaseOperation(
495
+ resolve: resolve,
496
+ reject: reject,
497
+ operation: { client in
498
+ try self.rowsToBridgeRows(client.query(sql, params: try self.bridgeParams(params)))
499
+ }
500
+ )
500
501
  }
501
502
 
502
503
  @objc
@@ -506,20 +507,16 @@ public class SynchroModuleImpl: NSObject {
506
507
  resolve: @escaping RCTPromiseResolveBlock,
507
508
  reject: @escaping RCTPromiseRejectBlock
508
509
  ) {
509
- guard let client = client else {
510
- reject("NOT_CONNECTED", "Client not initialized", nil)
511
- return
512
- }
513
- do {
514
- let row = try client.queryOne(sql, params: try bridgeParams(params))
515
- if let row = row {
516
- resolve(rowToBridgeRow(row))
517
- } else {
518
- resolve(NSNull())
510
+ submitDatabaseOperation(
511
+ resolve: resolve,
512
+ reject: reject,
513
+ operation: { client -> Any? in
514
+ if let row = try client.queryOne(sql, params: try self.bridgeParams(params)) {
515
+ return self.rowToBridgeRow(row)
516
+ }
517
+ return NSNull()
519
518
  }
520
- } catch {
521
- rejectWithError(reject, error)
522
- }
519
+ )
523
520
  }
524
521
 
525
522
  @objc
@@ -529,16 +526,14 @@ public class SynchroModuleImpl: NSObject {
529
526
  resolve: @escaping RCTPromiseResolveBlock,
530
527
  reject: @escaping RCTPromiseRejectBlock
531
528
  ) {
532
- guard let client = client else {
533
- reject("NOT_CONNECTED", "Client not initialized", nil)
534
- return
535
- }
536
- do {
537
- let result = try client.execute(sql, params: try bridgeParams(params))
538
- resolve(["rowsAffected": result.rowsAffected])
539
- } catch {
540
- rejectWithError(reject, error)
541
- }
529
+ submitDatabaseOperation(
530
+ resolve: resolve,
531
+ reject: reject,
532
+ operation: { client in
533
+ let result = try client.execute(sql, params: try self.bridgeParams(params))
534
+ return ["rowsAffected": result.rowsAffected]
535
+ }
536
+ )
542
537
  }
543
538
 
544
539
  @objc
@@ -551,27 +546,24 @@ public class SynchroModuleImpl: NSObject {
551
546
  resolve: @escaping RCTPromiseResolveBlock,
552
547
  reject: @escaping RCTPromiseRejectBlock
553
548
  ) {
554
- guard let client = client else {
555
- reject("NOT_CONNECTED", "Client not initialized", nil)
556
- return
557
- }
558
- guard let authoredOperation = Synchro.Operation(rawValue: operation) else {
559
- reject("INVALID_CONFIG", "Invalid authored write operation", nil)
560
- return
561
- }
562
- do {
563
- let params = try bridgeParams(values)
564
- let result = try client.authoredWriteTransaction(
565
- tableName: tableName,
566
- operation: authoredOperation,
567
- columnNames: columnNames
568
- ) { transaction in
569
- try transaction.execute(sql, params: params)
549
+ submitDatabaseOperation(
550
+ resolve: resolve,
551
+ reject: reject,
552
+ operation: { client in
553
+ guard let authoredOperation = Synchro.Operation(rawValue: operation) else {
554
+ throw InvalidAuthoredWriteOperationError()
555
+ }
556
+ let params = try self.bridgeParams(values)
557
+ let result = try client.authoredWriteTransaction(
558
+ tableName: tableName,
559
+ operation: authoredOperation,
560
+ columnNames: columnNames
561
+ ) { transaction in
562
+ try transaction.execute(sql, params: params)
563
+ }
564
+ return ["rowsAffected": result.rowsAffected]
570
565
  }
571
- resolve(["rowsAffected": result.rowsAffected])
572
- } catch {
573
- rejectWithError(reject, error)
574
- }
566
+ )
575
567
  }
576
568
 
577
569
  @objc
@@ -580,16 +572,14 @@ public class SynchroModuleImpl: NSObject {
580
572
  resolve: @escaping RCTPromiseResolveBlock,
581
573
  reject: @escaping RCTPromiseRejectBlock
582
574
  ) {
583
- guard let client = client else {
584
- reject("NOT_CONNECTED", "Client not initialized", nil)
585
- return
586
- }
587
- do {
588
- let total = try client.executeBatch(try bridgeStatements(statements))
589
- resolve(["totalRowsAffected": total])
590
- } catch {
591
- rejectWithError(reject, error)
592
- }
575
+ submitDatabaseOperation(
576
+ resolve: resolve,
577
+ reject: reject,
578
+ operation: { client in
579
+ let total = try client.executeBatch(try self.bridgeStatements(statements))
580
+ return ["totalRowsAffected": total]
581
+ }
582
+ )
593
583
  }
594
584
 
595
585
  // MARK: - Transactions
@@ -917,20 +907,17 @@ public class SynchroModuleImpl: NSObject {
917
907
  }
918
908
 
919
909
  private func wireClientEvents(_ client: SynchroClient) {
920
- statusSubscription?.cancel()
921
- syncEventSubscription?.cancel()
922
- conflictSubscription?.cancel()
923
910
  statusDetailsLock.lock()
924
911
  cachedBackoff = nil
925
912
  cachedFailure = nil
926
913
  statusDetailsLock.unlock()
927
914
 
928
- statusSubscription = client.onStatusChange { [weak self] status in
915
+ let statusSubscription = client.onStatusChange { [weak self] status in
929
916
  guard status != .backoff, status != .error else { return }
930
- self?.emit("onStatusChange", self?.statusPayload(status) ?? [:])
917
+ self?.emit("onStatusChange", self?.statusPayload(status, client: client) ?? [:])
931
918
  }
932
919
 
933
- syncEventSubscription = client.onSyncEvent { [weak self] event in
920
+ let syncEventSubscription = client.onSyncEvent { [weak self] event in
934
921
  guard let self else { return }
935
922
  self.recordStatusDetails(event)
936
923
  self.emit("onSyncEvent", self.syncEventPayload(event))
@@ -938,16 +925,22 @@ public class SynchroModuleImpl: NSObject {
938
925
  case .backoff, .failure:
939
926
  self.emit(
940
927
  "onStatusChange",
941
- self.statusPayload(client.getSyncStatus())
928
+ self.statusPayload(client.getSyncStatus(), client: client)
942
929
  )
943
930
  default:
944
931
  break
945
932
  }
946
933
  }
947
934
 
948
- conflictSubscription = client.onConflict { [weak self] event in
935
+ let conflictSubscription = client.onConflict { [weak self] event in
949
936
  self?.emit("onConflict", self?.conflictPayload(event) ?? [:])
950
937
  }
938
+
939
+ sessionsLock.lock()
940
+ self.statusSubscription = statusSubscription
941
+ self.syncEventSubscription = syncEventSubscription
942
+ self.conflictSubscription = conflictSubscription
943
+ sessionsLock.unlock()
951
944
  }
952
945
 
953
946
  private func setClient(_ client: SynchroClient?, acceptingTransactions: Bool) {
@@ -968,13 +961,30 @@ public class SynchroModuleImpl: NSObject {
968
961
  }
969
962
  }
970
963
 
964
+ private func submitDatabaseOperation(
965
+ resolve: @escaping RCTPromiseResolveBlock,
966
+ reject: @escaping RCTPromiseRejectBlock,
967
+ operation: @escaping (SynchroClient) throws -> Any?
968
+ ) {
969
+ sessionsLock.lock()
970
+ guard acceptingTransactions, let client else {
971
+ sessionsLock.unlock()
972
+ reject("NOT_CONNECTED", "Client not initialized", nil)
973
+ return
974
+ }
975
+ databaseBridgeQueue.async {
976
+ let result = Result { try operation(client) }
977
+ switch result {
978
+ case .success(let value):
979
+ resolve(value)
980
+ case .failure(let error):
981
+ self.rejectWithError(reject, error)
982
+ }
983
+ }
984
+ sessionsLock.unlock()
985
+ }
986
+
971
987
  private func detachRuntimeState() -> [TransactionSession] {
972
- statusSubscription?.cancel()
973
- syncEventSubscription?.cancel()
974
- conflictSubscription?.cancel()
975
- statusSubscription = nil
976
- syncEventSubscription = nil
977
- conflictSubscription = nil
978
988
  transportObservations?.cancelPauseBarrier()
979
989
  transportObservations = nil
980
990
  statusDetailsLock.lock()
@@ -982,14 +992,25 @@ public class SynchroModuleImpl: NSObject {
982
992
  cachedFailure = nil
983
993
  statusDetailsLock.unlock()
984
994
 
985
- observers.values.forEach { $0.cancel() }
986
- observers.removeAll()
987
-
988
995
  sessionsLock.lock()
989
996
  acceptingTransactions = false
990
997
  let activeSessions = Array(sessions.values)
991
998
  sessions.removeAll()
999
+ let activeObservers = Array(observers.values)
1000
+ observers.removeAll()
1001
+ let activeSubscriptions = (
1002
+ statusSubscription,
1003
+ syncEventSubscription,
1004
+ conflictSubscription
1005
+ )
1006
+ statusSubscription = nil
1007
+ syncEventSubscription = nil
1008
+ conflictSubscription = nil
992
1009
  sessionsLock.unlock()
1010
+ activeObservers.forEach { $0.cancel() }
1011
+ activeSubscriptions.0?.cancel()
1012
+ activeSubscriptions.1?.cancel()
1013
+ activeSubscriptions.2?.cancel()
993
1014
 
994
1015
  authLock.lock()
995
1016
  let authContinuations = pendingAuthContinuations
@@ -1013,9 +1034,14 @@ public class SynchroModuleImpl: NSObject {
1013
1034
  for session in activeSessions {
1014
1035
  await session.waitUntilFinished()
1015
1036
  }
1037
+ await withCheckedContinuation { continuation in
1038
+ databaseBridgeQueue.async {
1039
+ continuation.resume()
1040
+ }
1041
+ }
1016
1042
  }
1017
1043
 
1018
- private func statusPayload(_ status: SyncStatus) -> [String: Any] {
1044
+ private func statusPayload(_ status: SyncStatus, client: SynchroClient) -> [String: Any] {
1019
1045
  var payload: [String: Any] = [
1020
1046
  "status": status.rawValue,
1021
1047
  "retryAt": NSNull(),
@@ -1038,11 +1064,7 @@ public class SynchroModuleImpl: NSObject {
1038
1064
  let cachedFailure = self.cachedFailure
1039
1065
  statusDetailsLock.unlock()
1040
1066
  let persistedFailure: SyncFailure?
1041
- if let client = self.client {
1042
- persistedFailure = try? client.getBlockingFailure()
1043
- } else {
1044
- persistedFailure = nil
1045
- }
1067
+ persistedFailure = try? client.getBlockingFailure()
1046
1068
  if let failure = cachedFailure ?? persistedFailure {
1047
1069
  payload["failure"] = failurePayload(failure)
1048
1070
  }
@@ -1234,18 +1256,16 @@ public class SynchroModuleImpl: NSObject {
1234
1256
  resolve: @escaping RCTPromiseResolveBlock,
1235
1257
  reject: @escaping RCTPromiseRejectBlock
1236
1258
  ) {
1237
- guard let client = client else {
1238
- reject("NOT_CONNECTED", "Client not initialized", nil)
1239
- return
1240
- }
1241
- do {
1242
- let columns = try parseColumns(columnsJson)
1243
- let options = try optionsJson.flatMap { try parseTableOptions($0) }
1244
- try client.createTable(name, columns: columns, options: options)
1245
- resolve(nil)
1246
- } catch {
1247
- rejectWithError(reject, error)
1248
- }
1259
+ submitDatabaseOperation(
1260
+ resolve: resolve,
1261
+ reject: reject,
1262
+ operation: { client in
1263
+ let columns = try self.parseColumns(columnsJson)
1264
+ let options = try optionsJson.flatMap { try self.parseTableOptions($0) }
1265
+ try client.createTable(name, columns: columns, options: options)
1266
+ return nil
1267
+ }
1268
+ )
1249
1269
  }
1250
1270
 
1251
1271
  @objc
@@ -1255,17 +1275,15 @@ public class SynchroModuleImpl: NSObject {
1255
1275
  resolve: @escaping RCTPromiseResolveBlock,
1256
1276
  reject: @escaping RCTPromiseRejectBlock
1257
1277
  ) {
1258
- guard let client = client else {
1259
- reject("NOT_CONNECTED", "Client not initialized", nil)
1260
- return
1261
- }
1262
- do {
1263
- let columns = try parseColumns(columnsJson)
1264
- try client.alterTable(name, addColumns: columns)
1265
- resolve(nil)
1266
- } catch {
1267
- rejectWithError(reject, error)
1268
- }
1278
+ submitDatabaseOperation(
1279
+ resolve: resolve,
1280
+ reject: reject,
1281
+ operation: { client in
1282
+ let columns = try self.parseColumns(columnsJson)
1283
+ try client.alterTable(name, addColumns: columns)
1284
+ return nil
1285
+ }
1286
+ )
1269
1287
  }
1270
1288
 
1271
1289
  @objc
@@ -1276,16 +1294,14 @@ public class SynchroModuleImpl: NSObject {
1276
1294
  resolve: @escaping RCTPromiseResolveBlock,
1277
1295
  reject: @escaping RCTPromiseRejectBlock
1278
1296
  ) {
1279
- guard let client = client else {
1280
- reject("NOT_CONNECTED", "Client not initialized", nil)
1281
- return
1282
- }
1283
- do {
1284
- try client.createIndex(table, columns: columns, unique: unique)
1285
- resolve(nil)
1286
- } catch {
1287
- rejectWithError(reject, error)
1288
- }
1297
+ submitDatabaseOperation(
1298
+ resolve: resolve,
1299
+ reject: reject,
1300
+ operation: { client in
1301
+ try client.createIndex(table, columns: columns, unique: unique)
1302
+ return nil
1303
+ }
1304
+ )
1289
1305
  }
1290
1306
 
1291
1307
  // MARK: - Observation
@@ -1297,14 +1313,18 @@ public class SynchroModuleImpl: NSObject {
1297
1313
  resolve: @escaping RCTPromiseResolveBlock,
1298
1314
  reject: @escaping RCTPromiseRejectBlock
1299
1315
  ) {
1300
- guard let client = client else {
1316
+ sessionsLock.lock()
1317
+ guard acceptingTransactions, let client else {
1318
+ sessionsLock.unlock()
1301
1319
  reject("NOT_CONNECTED", "Client not initialized", nil)
1302
1320
  return
1303
1321
  }
1304
1322
  let cancellable = client.onChange(tables: tables) { [weak self] in
1305
1323
  self?.emit("onChange", ["observerID": observerID])
1306
1324
  }
1307
- observers[observerID] = cancellable
1325
+ let previous = observers.updateValue(cancellable, forKey: observerID)
1326
+ sessionsLock.unlock()
1327
+ previous?.cancel()
1308
1328
  resolve(nil)
1309
1329
  }
1310
1330
 
@@ -1317,7 +1337,9 @@ public class SynchroModuleImpl: NSObject {
1317
1337
  resolve: @escaping RCTPromiseResolveBlock,
1318
1338
  reject: @escaping RCTPromiseRejectBlock
1319
1339
  ) {
1320
- guard let client = client else {
1340
+ sessionsLock.lock()
1341
+ guard acceptingTransactions, let client else {
1342
+ sessionsLock.unlock()
1321
1343
  reject("NOT_CONNECTED", "Client not initialized", nil)
1322
1344
  return
1323
1345
  }
@@ -1328,9 +1350,12 @@ public class SynchroModuleImpl: NSObject {
1328
1350
  self?.emit("onQueryResult", ["observerID": observerID, "rows": payload])
1329
1351
  }
1330
1352
  }
1331
- observers[observerID] = cancellable
1353
+ let previous = observers.updateValue(cancellable, forKey: observerID)
1354
+ sessionsLock.unlock()
1355
+ previous?.cancel()
1332
1356
  resolve(nil)
1333
1357
  } catch {
1358
+ sessionsLock.unlock()
1334
1359
  rejectWithError(reject, error)
1335
1360
  }
1336
1361
  }
@@ -1341,9 +1366,10 @@ public class SynchroModuleImpl: NSObject {
1341
1366
  resolve: @escaping RCTPromiseResolveBlock,
1342
1367
  reject: @escaping RCTPromiseRejectBlock
1343
1368
  ) {
1344
- if let cancellable = observers.removeValue(forKey: observerID) {
1345
- cancellable.cancel()
1346
- }
1369
+ sessionsLock.lock()
1370
+ let cancellable = observers.removeValue(forKey: observerID)
1371
+ sessionsLock.unlock()
1372
+ cancellable?.cancel()
1347
1373
  resolve(nil)
1348
1374
  }
1349
1375
 
@@ -1501,15 +1527,11 @@ public class SynchroModuleImpl: NSObject {
1501
1527
  _ resolve: @escaping RCTPromiseResolveBlock,
1502
1528
  reject: @escaping RCTPromiseRejectBlock
1503
1529
  ) {
1504
- guard let client = client else {
1505
- reject("NOT_CONNECTED", "Client not initialized", nil)
1506
- return
1507
- }
1508
- do {
1509
- resolve(try client.pendingChangeCount())
1510
- } catch {
1511
- rejectWithError(reject, error)
1512
- }
1530
+ submitDatabaseOperation(
1531
+ resolve: resolve,
1532
+ reject: reject,
1533
+ operation: { client in try client.pendingChangeCount() }
1534
+ )
1513
1535
  }
1514
1536
 
1515
1537
  @objc
@@ -1517,15 +1539,13 @@ public class SynchroModuleImpl: NSObject {
1517
1539
  _ resolve: @escaping RCTPromiseResolveBlock,
1518
1540
  reject: @escaping RCTPromiseRejectBlock
1519
1541
  ) {
1520
- guard let client = client else {
1521
- reject("NOT_CONNECTED", "Client not initialized", nil)
1522
- return
1523
- }
1524
- do {
1525
- resolve(try encodeBridgeJSON(statusPayload(client.getSyncStatus())))
1526
- } catch {
1527
- rejectWithError(reject, error)
1528
- }
1542
+ submitDatabaseOperation(
1543
+ resolve: resolve,
1544
+ reject: reject,
1545
+ operation: { client in
1546
+ try self.encodeBridgeJSON(self.statusPayload(client.getSyncStatus(), client: client))
1547
+ }
1548
+ )
1529
1549
  }
1530
1550
 
1531
1551
  @objc
@@ -1533,16 +1553,14 @@ public class SynchroModuleImpl: NSObject {
1533
1553
  _ resolve: @escaping RCTPromiseResolveBlock,
1534
1554
  reject: @escaping RCTPromiseRejectBlock
1535
1555
  ) {
1536
- guard let client = client else {
1537
- reject("NOT_CONNECTED", "Client not initialized", nil)
1538
- return
1539
- }
1540
- do {
1541
- let payload = try client.inspectPendingMutations().map(pendingMutationPayload)
1542
- resolve(try encodeBridgeJSON(payload))
1543
- } catch {
1544
- rejectWithError(reject, error)
1545
- }
1556
+ submitDatabaseOperation(
1557
+ resolve: resolve,
1558
+ reject: reject,
1559
+ operation: { client in
1560
+ let payload = try client.inspectPendingMutations().map(self.pendingMutationPayload)
1561
+ return try self.encodeBridgeJSON(payload)
1562
+ }
1563
+ )
1546
1564
  }
1547
1565
 
1548
1566
  @objc
@@ -1550,16 +1568,14 @@ public class SynchroModuleImpl: NSObject {
1550
1568
  _ resolve: @escaping RCTPromiseResolveBlock,
1551
1569
  reject: @escaping RCTPromiseRejectBlock
1552
1570
  ) {
1553
- guard let client = client else {
1554
- reject("NOT_CONNECTED", "Client not initialized", nil)
1555
- return
1556
- }
1557
- do {
1558
- let payload = try client.inspectRetainedMutations().map(pendingMutationPayload)
1559
- resolve(try encodeBridgeJSON(payload))
1560
- } catch {
1561
- rejectWithError(reject, error)
1562
- }
1571
+ submitDatabaseOperation(
1572
+ resolve: resolve,
1573
+ reject: reject,
1574
+ operation: { client in
1575
+ let payload = try client.inspectRetainedMutations().map(self.pendingMutationPayload)
1576
+ return try self.encodeBridgeJSON(payload)
1577
+ }
1578
+ )
1563
1579
  }
1564
1580
 
1565
1581
  @objc
@@ -1567,16 +1583,14 @@ public class SynchroModuleImpl: NSObject {
1567
1583
  _ resolve: @escaping RCTPromiseResolveBlock,
1568
1584
  reject: @escaping RCTPromiseRejectBlock
1569
1585
  ) {
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
- }
1586
+ submitDatabaseOperation(
1587
+ resolve: resolve,
1588
+ reject: reject,
1589
+ operation: { client in
1590
+ let payload = try client.inspectRetainedMutationRecords().map(self.retainedMutationPayload)
1591
+ return try self.encodeBridgeJSON(payload)
1592
+ }
1593
+ )
1580
1594
  }
1581
1595
 
1582
1596
  @objc
@@ -1584,16 +1598,14 @@ public class SynchroModuleImpl: NSObject {
1584
1598
  _ resolve: @escaping RCTPromiseResolveBlock,
1585
1599
  reject: @escaping RCTPromiseRejectBlock
1586
1600
  ) {
1587
- guard let client = client else {
1588
- reject("NOT_CONNECTED", "Client not initialized", nil)
1589
- return
1590
- }
1591
- do {
1592
- let payload = try client.inspectRejectedMutations().map(rejectedMutationPayload)
1593
- resolve(try encodeBridgeJSON(payload))
1594
- } catch {
1595
- rejectWithError(reject, error)
1596
- }
1601
+ submitDatabaseOperation(
1602
+ resolve: resolve,
1603
+ reject: reject,
1604
+ operation: { client in
1605
+ let payload = try client.inspectRejectedMutations().map(self.rejectedMutationPayload)
1606
+ return try self.encodeBridgeJSON(payload)
1607
+ }
1608
+ )
1597
1609
  }
1598
1610
 
1599
1611
  @objc
@@ -1601,16 +1613,14 @@ public class SynchroModuleImpl: NSObject {
1601
1613
  _ resolve: @escaping RCTPromiseResolveBlock,
1602
1614
  reject: @escaping RCTPromiseRejectBlock
1603
1615
  ) {
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
- }
1616
+ submitDatabaseOperation(
1617
+ resolve: resolve,
1618
+ reject: reject,
1619
+ operation: { client in
1620
+ let payload = try client.inspectRejectedMutationRecords().map(self.retainedRejectionPayload)
1621
+ return try self.encodeBridgeJSON(payload)
1622
+ }
1623
+ )
1614
1624
  }
1615
1625
 
1616
1626
  /// Reads client state, retained details, and the requested application rows
@@ -1621,38 +1631,36 @@ public class SynchroModuleImpl: NSObject {
1621
1631
  resolve: @escaping RCTPromiseResolveBlock,
1622
1632
  reject: @escaping RCTPromiseRejectBlock
1623
1633
  ) {
1624
- guard let client else {
1625
- reject("NOT_CONNECTED", "Client not initialized", nil)
1626
- return
1627
- }
1628
- do {
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
+ submitDatabaseOperation(
1635
+ resolve: resolve,
1636
+ reject: reject,
1637
+ operation: { client in
1638
+ let statements = try self.bridgeStatements(rowStatements)
1639
+ var rows: [Row] = []
1640
+ let snapshot = try SynchroInspection(client: client).captureSnapshot(maximumRecords: 512) { _, transaction in
1641
+ for statement in statements {
1642
+ rows += try transaction.query(statement.sql, params: statement.params)
1643
+ }
1634
1644
  }
1645
+ let inspection: [String: Any] = [
1646
+ "client_state": self.clientStatePayload(snapshot.capture),
1647
+ "migration_journal": try snapshot.capture.migrationJournal.map {
1648
+ try JSONSerialization.jsonObject(with: JSONEncoder().encode($0))
1649
+ } ?? NSNull(),
1650
+ "migration_journal_truncated": snapshot.capture.migrationJournalTruncated,
1651
+ "physical_schema": try JSONSerialization.jsonObject(with: JSONEncoder().encode(snapshot.capture.physicalSchema)),
1652
+ "physical_schema_truncated": snapshot.capture.physicalSchemaTruncated,
1653
+ "accepted_mutation_outcomes": snapshot.capture.acceptedMutationOutcomes,
1654
+ "accepted_mutation_outcomes_truncated": snapshot.capture.acceptedMutationOutcomesTruncated,
1655
+ "retained_mutations": snapshot.retainedMutations.map { $0.map(self.retainedMutationPayload) } ?? NSNull(),
1656
+ "rejected_mutations": snapshot.rejectedMutations.map { $0.map(self.retainedRejectionPayload) } ?? NSNull(),
1657
+ ]
1658
+ return [
1659
+ "inspection": try self.encodeBridgeJSON(inspection),
1660
+ "applicationRows": self.rowsToBridgeRows(rows),
1661
+ ]
1635
1662
  }
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
- ])
1653
- } catch {
1654
- rejectWithError(reject, error)
1655
- }
1663
+ )
1656
1664
  }
1657
1665
 
1658
1666
  /// Reads one record's row metadata and every rebuild receipt. The inspection snapshot bounds
@@ -1664,46 +1672,44 @@ public class SynchroModuleImpl: NSObject {
1664
1672
  resolve: @escaping RCTPromiseResolveBlock,
1665
1673
  reject: @escaping RCTPromiseRejectBlock
1666
1674
  ) {
1667
- guard let client else {
1668
- reject("NOT_CONNECTED", "Client not initialized", nil)
1669
- return
1670
- }
1671
- do {
1672
- let inspection = SynchroInspection(client: client)
1673
- let metadata: Any = try inspection.rowMetadata(
1674
- tableName: tableName,
1675
- recordID: recordID
1676
- ).map { value in
1677
- [
1678
- "table_name": value.tableName,
1679
- "record_id": value.recordID,
1680
- "server_version": value.serverVersion,
1681
- "row_checksum": value.rowChecksum ?? NSNull(),
1682
- ] as [String: Any]
1683
- } ?? NSNull()
1684
- let receipts = try inspection.rebuildReceipts().map { value in
1685
- [
1686
- "rebuild_id_fingerprint": value.rebuildIDFingerprint,
1687
- "page_count": value.pageCount,
1688
- "returned_record_count": value.returnedRecordCount,
1689
- "request_chain_expected": value.requestChainExpected,
1690
- "request_chain_observed": value.requestChainObserved,
1691
- "record_identities_hex": value.recordIdentitiesHex,
1692
- "received_row_checksums": value.receivedRowChecksums,
1693
- "computed_row_checksums": value.computedRowChecksums,
1694
- "computed_scope_checksum": value.computedScopeChecksum ?? NSNull(),
1695
- "final_scope_checksum": value.finalScopeChecksum ?? NSNull(),
1696
- "stored_scope_checksum": value.storedScopeChecksum ?? NSNull(),
1697
- "local_scope_checksum": value.localScopeChecksum ?? NSNull(),
1698
- ] as [String: Any]
1675
+ submitDatabaseOperation(
1676
+ resolve: resolve,
1677
+ reject: reject,
1678
+ operation: { client in
1679
+ let inspection = SynchroInspection(client: client)
1680
+ let metadata: Any = try inspection.rowMetadata(
1681
+ tableName: tableName,
1682
+ recordID: recordID
1683
+ ).map { value in
1684
+ [
1685
+ "table_name": value.tableName,
1686
+ "record_id": value.recordID,
1687
+ "server_version": value.serverVersion,
1688
+ "row_checksum": value.rowChecksum ?? NSNull(),
1689
+ ] as [String: Any]
1690
+ } ?? NSNull()
1691
+ let receipts = try inspection.rebuildReceipts().map { value in
1692
+ [
1693
+ "rebuild_id_fingerprint": value.rebuildIDFingerprint,
1694
+ "page_count": value.pageCount,
1695
+ "returned_record_count": value.returnedRecordCount,
1696
+ "request_chain_expected": value.requestChainExpected,
1697
+ "request_chain_observed": value.requestChainObserved,
1698
+ "record_identities_hex": value.recordIdentitiesHex,
1699
+ "received_row_checksums": value.receivedRowChecksums,
1700
+ "computed_row_checksums": value.computedRowChecksums,
1701
+ "computed_scope_checksum": value.computedScopeChecksum ?? NSNull(),
1702
+ "final_scope_checksum": value.finalScopeChecksum ?? NSNull(),
1703
+ "stored_scope_checksum": value.storedScopeChecksum ?? NSNull(),
1704
+ "local_scope_checksum": value.localScopeChecksum ?? NSNull(),
1705
+ ] as [String: Any]
1706
+ }
1707
+ return try self.encodeBridgeJSON([
1708
+ "row_metadata": metadata,
1709
+ "rebuild_receipts": receipts,
1710
+ ])
1699
1711
  }
1700
- resolve(try encodeBridgeJSON([
1701
- "row_metadata": metadata,
1702
- "rebuild_receipts": receipts,
1703
- ]))
1704
- } catch {
1705
- rejectWithError(reject, error)
1706
- }
1712
+ )
1707
1713
  }
1708
1714
 
1709
1715
  @objc
@@ -1808,16 +1814,14 @@ public class SynchroModuleImpl: NSObject {
1808
1814
  _ resolve: @escaping RCTPromiseResolveBlock,
1809
1815
  reject: @escaping RCTPromiseRejectBlock
1810
1816
  ) {
1811
- guard let client = client else {
1812
- reject("NOT_CONNECTED", "Client not initialized", nil)
1813
- return
1814
- }
1815
- do {
1816
- try client.clearRejectedMutations()
1817
- resolve(nil)
1818
- } catch {
1819
- rejectWithError(reject, error)
1820
- }
1817
+ submitDatabaseOperation(
1818
+ resolve: resolve,
1819
+ reject: reject,
1820
+ operation: { client in
1821
+ try client.clearRejectedMutations()
1822
+ return nil
1823
+ }
1824
+ )
1821
1825
  }
1822
1826
 
1823
1827
  // MARK: - Helpers