@dittolive/ditto 4.5.4-alpha.1 → 4.6.0

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 (88) hide show
  1. package/DittoReactNative.podspec +5 -3
  2. package/README.md +2 -2
  3. package/node/ditto.cjs.js +1111 -563
  4. package/node/ditto.darwin-arm64.node +0 -0
  5. package/node/ditto.darwin-x64.node +0 -0
  6. package/node/ditto.linux-arm.node +0 -0
  7. package/node/ditto.linux-arm64.node +0 -0
  8. package/node/ditto.linux-x64.node +0 -0
  9. package/node/ditto.win32-x64.node +0 -0
  10. package/node/transports.darwin-arm64.node +0 -0
  11. package/node/transports.darwin-x64.node +0 -0
  12. package/package.json +3 -11
  13. package/react-native/android/CMakeLists.txt +0 -1
  14. package/react-native/android/build.gradle +7 -3
  15. package/react-native/android/cpp-adapter.cpp +68 -59
  16. package/react-native/android/src/main/java/com/dittolive/rnsdk/DittoRNSDKModule.java +35 -0
  17. package/react-native/cpp/include/Arc.hpp +1 -1
  18. package/react-native/cpp/include/Attachment.h +5 -1
  19. package/react-native/cpp/include/DQL.h +9 -9
  20. package/react-native/cpp/include/FFIUtils.h +14 -0
  21. package/react-native/cpp/include/IO.h +13 -0
  22. package/react-native/cpp/include/Lifecycle.h +0 -1
  23. package/react-native/cpp/include/Misc.h +3 -0
  24. package/react-native/cpp/include/Utils.h +0 -2
  25. package/react-native/cpp/src/Attachment.cpp +200 -13
  26. package/react-native/cpp/src/Authentication.cpp +3 -3
  27. package/react-native/cpp/src/DQL.cpp +23 -23
  28. package/react-native/cpp/src/Document.cpp +10 -10
  29. package/react-native/cpp/src/FFIUtils.cpp +64 -0
  30. package/react-native/cpp/src/IO.cpp +35 -0
  31. package/react-native/cpp/src/Identity.cpp +3 -3
  32. package/react-native/cpp/src/Lifecycle.cpp +2 -19
  33. package/react-native/cpp/src/LiveQuery.cpp +3 -3
  34. package/react-native/cpp/src/Logger.cpp +171 -172
  35. package/react-native/cpp/src/Misc.cpp +52 -4
  36. package/react-native/cpp/src/Presence.cpp +1 -1
  37. package/react-native/cpp/src/SmallPeerInfo.cpp +1 -1
  38. package/react-native/cpp/src/Transports.cpp +10 -5
  39. package/react-native/cpp/src/Utils.cpp +110 -114
  40. package/react-native/cpp/src/main.cpp +28 -15
  41. package/react-native/dittoffi/dittoffi.h +328 -280
  42. package/react-native/ios/DittoRNSDK.mm +123 -71
  43. package/react-native/src/ditto.rn.ts +30 -6
  44. package/react-native/src/index.ts +7 -4
  45. package/react-native/src/sources/@cbor-redux.ts +1 -1
  46. package/react-native/src/sources/attachment-fetch-event.ts +2 -2
  47. package/react-native/src/sources/attachment-fetcher-manager.ts +5 -4
  48. package/react-native/src/sources/attachment-fetcher.ts +152 -21
  49. package/react-native/src/sources/attachment-token.ts +94 -13
  50. package/react-native/src/sources/attachment.ts +66 -19
  51. package/react-native/src/sources/augment.ts +13 -6
  52. package/react-native/src/sources/base-pending-cursor-operation.ts +22 -6
  53. package/react-native/src/sources/base-pending-id-specific-operation.ts +3 -0
  54. package/react-native/src/sources/bridge.ts +2 -2
  55. package/react-native/src/sources/cbor.ts +0 -15
  56. package/react-native/src/sources/collection-interface.ts +12 -6
  57. package/react-native/src/sources/collection.ts +9 -2
  58. package/react-native/src/sources/ditto.ts +26 -18
  59. package/react-native/src/sources/document-id.ts +11 -7
  60. package/react-native/src/sources/document-path.ts +4 -2
  61. package/react-native/src/sources/document.ts +49 -5
  62. package/react-native/src/sources/error-codes.ts +28 -0
  63. package/react-native/src/sources/error.ts +20 -1
  64. package/react-native/src/sources/essentials.ts +25 -3
  65. package/react-native/src/sources/ffi-error.ts +2 -1
  66. package/react-native/src/sources/ffi.ts +180 -102
  67. package/react-native/src/sources/internal.ts +37 -3
  68. package/react-native/src/sources/live-query-manager.ts +10 -1
  69. package/react-native/src/sources/live-query.ts +1 -1
  70. package/react-native/src/sources/observer-manager.ts +7 -0
  71. package/react-native/src/sources/pending-id-specific-operation.ts +2 -2
  72. package/react-native/src/sources/presence-manager.ts +12 -2
  73. package/react-native/src/sources/presence.ts +5 -0
  74. package/react-native/src/sources/query-result-item.ts +15 -0
  75. package/react-native/src/sources/small-peer-info.ts +2 -2
  76. package/react-native/src/sources/static-tcp-client.ts +2 -0
  77. package/react-native/src/sources/store-observer.ts +4 -2
  78. package/react-native/src/sources/store.ts +253 -3
  79. package/react-native/src/sources/sync.ts +6 -3
  80. package/react-native/src/sources/transport-config.ts +2 -2
  81. package/react-native/src/sources/update-results-map.ts +8 -0
  82. package/react-native/src/sources/write-transaction-collection.ts +1 -1
  83. package/react-native/src/sources/write-transaction.ts +1 -1
  84. package/types/ditto.d.ts +2866 -2568
  85. package/web/ditto.es6.js +1 -1
  86. package/web/ditto.umd.js +1 -1
  87. package/web/ditto.wasm +0 -0
  88. package/react-native/android/.project +0 -34
@@ -12,7 +12,7 @@
12
12
 
13
13
  import * as dittoCore from './@ditto.core'
14
14
  import * as Environment from './@environment'
15
- import { throwOnErrorResult } from './ffi-error'
15
+ import { DittoFFIError, throwOnErrorResult } from './ffi-error'
16
16
 
17
17
  // -------------------------------------------------------------- Prelude ------
18
18
 
@@ -40,10 +40,10 @@ export type FFIWriteStrategy = 'merge' | 'insertIfAbsent' | 'insertDefaultIfAbse
40
40
  export type FFIDocument = 'CDocument_t'
41
41
 
42
42
  /** @internal */
43
- export type FFIDqlResult = 'CDqlResult_t'
43
+ export type FFIDqlResult = 'dittoffi_query_result_item_t'
44
44
 
45
45
  /** @internal */
46
- export type FFIDqlResponse = 'CDqlResponse_t'
46
+ export type FFIDqlResponse = 'dittoffi_query_result_t'
47
47
 
48
48
  /** @internal */
49
49
  export type FFIReadTransaction = 'CReadTransaction_t'
@@ -141,6 +141,9 @@ export enum DittoCRDTType {
141
141
  rwMap = 4,
142
142
  }
143
143
 
144
+ /** @internal */
145
+ export type Base64PaddingMode = 'Padded' | 'Unpadded'
146
+
144
147
  /** @internal */
145
148
  export type CBParamsDocs = {
146
149
  documents: Pointer<FFIDocument>[]
@@ -180,7 +183,7 @@ export type RawAttachment = {
180
183
  handle: Pointer<AttachmentHandle>
181
184
  }
182
185
 
183
- // -------------------------------------- Linux & Windows Transports ------
186
+ // -------------------------------------- Linux & Windows Transports Hack ------
184
187
 
185
188
  export function dittoAddInternalBLEClientTransport(ditto: Pointer<FFIDitto>): any {
186
189
  if (Environment.isNodeBuild) {
@@ -290,6 +293,7 @@ export function documentID(self: Pointer<FFIDocument>): Uint8Array {
290
293
  trace()
291
294
  ensureInitialized()
292
295
 
296
+ // REFACTOR: add proper error handling.
293
297
  const documentIDX = dittoCore.ditto_document_id(self)
294
298
  return dittoCore.boxCBytesIntoBuffer(documentIDX)
295
299
  }
@@ -344,6 +348,7 @@ export function documentFree(self: Pointer<FFIDocument>) {
344
348
  trace()
345
349
  ensureInitialized()
346
350
 
351
+ // REFACTOR: add proper error handling.
347
352
  dittoCore.ditto_document_free(self)
348
353
  }
349
354
 
@@ -379,6 +384,7 @@ export async function collectionGet(ditto: Pointer<FFIDitto>, collectionName: st
379
384
  trace()
380
385
  ensureInitialized()
381
386
 
387
+ // REFACTOR: add proper error handling.
382
388
  const collectionNameX = bytesFromString(collectionName)
383
389
 
384
390
  const { status_code: errorCode, document } = await dittoCore.ditto_collection_get(ditto, collectionNameX, documentID, readTransaction)
@@ -392,6 +398,7 @@ export async function collectionInsertValue(ditto: Pointer<FFIDitto>, collection
392
398
  trace()
393
399
  ensureInitialized()
394
400
 
401
+ // REFACTOR: add proper error handling.
395
402
  const collectionNameX = bytesFromString(collectionName)
396
403
 
397
404
  let strategy
@@ -419,6 +426,7 @@ export async function collectionRemove(ditto: Pointer<FFIDitto>, collectionName:
419
426
  trace()
420
427
  ensureInitialized()
421
428
 
429
+ // REFACTOR: add proper error handling.
422
430
  const collectionNameX = bytesFromString(collectionName)
423
431
 
424
432
  const { status_code: errorCode, bool_value: didRemove } = await dittoCore.ditto_collection_remove(ditto, collectionNameX, writeTransaction, documentID)
@@ -431,6 +439,7 @@ export async function collectionEvict(ditto: Pointer<FFIDitto>, collectionName:
431
439
  trace()
432
440
  ensureInitialized()
433
441
 
442
+ // REFACTOR: add proper error handling.
434
443
  const collectionNameX = bytesFromString(collectionName)
435
444
 
436
445
  const { status_code: errorCode, bool_value: didEvict } = await dittoCore.ditto_collection_evict(ditto, collectionNameX, writeTransaction, documentID)
@@ -502,8 +511,8 @@ export async function tryExperimentalExecQueryStr(ditto: Pointer<FFIDitto>, writ
502
511
  ensureInitialized()
503
512
 
504
513
  const queryBytesPointer = bytesFromString(query)
505
- const result = await dittoCore.dittoffi_try_experimental_exec_statement_str(ditto, writeTransaction, queryBytesPointer, queryArgsCBOR)
506
- throwOnErrorResult(result.error, 'dittoffi_try_experimental_exec_statement_str')
514
+ const result = await dittoCore.dittoffi_try_exec_statement(ditto, writeTransaction, queryBytesPointer, queryArgsCBOR)
515
+ throwOnErrorResult(result.error, 'dittoffi_try_exec_statement')
507
516
  return result.success
508
517
  }
509
518
 
@@ -533,8 +542,8 @@ export function tryExperimentalAddDQLSubscription(dittoPointer: Pointer<FFIDitto
533
542
  ensureInitialized()
534
543
 
535
544
  const queryBuffer = bytesFromString(query)
536
- const result = dittoCore.dittoffi_try_experimental_add_dql_subscription(dittoPointer, queryBuffer, queryArgsCBOR)
537
- throwOnErrorResult(result.error, 'dittoffi_try_experimental_add_dql_subscription')
545
+ const result = dittoCore.dittoffi_try_add_sync_subscription(dittoPointer, queryBuffer, queryArgsCBOR)
546
+ throwOnErrorResult(result.error, 'dittoffi_try_add_sync_subscription')
538
547
  return
539
548
  }
540
549
 
@@ -544,8 +553,8 @@ export function tryExperimentalRemoveDQLSubscription(dittoPointer: Pointer<FFIDi
544
553
  ensureInitialized()
545
554
 
546
555
  const queryBuffer = bytesFromString(query)
547
- const result = dittoCore.dittoffi_try_experimental_remove_dql_subscription(dittoPointer, queryBuffer, queryArgsCBOR)
548
- throwOnErrorResult(result.error, 'dittoffi_try_experimental_remove_dql_subscription')
556
+ const result = dittoCore.dittoffi_try_remove_sync_subscription(dittoPointer, queryBuffer, queryArgsCBOR)
557
+ throwOnErrorResult(result.error, 'dittoffi_try_remove_sync_subscription')
549
558
  return
550
559
  }
551
560
 
@@ -560,7 +569,7 @@ export function dqlResponseFree(responsePointer: Pointer<FFIDqlResponse>) {
560
569
  trace()
561
570
  ensureInitialized()
562
571
 
563
- dittoCore.ditto_dql_response_free(responsePointer)
572
+ dittoCore.dittoffi_query_result_free(responsePointer)
564
573
  }
565
574
 
566
575
  /**
@@ -572,7 +581,7 @@ export function dqlResultFree(resultPointer: Pointer<FFIDqlResult>) {
572
581
  trace()
573
582
  ensureInitialized()
574
583
 
575
- dittoCore.ditto_dql_result_free(resultPointer)
584
+ dittoCore.dittoffi_query_result_item_free(resultPointer)
576
585
  }
577
586
 
578
587
  /**
@@ -584,9 +593,9 @@ export function dqlResponseResults(responsePointer: Pointer<FFIDqlResponse>): Po
584
593
  ensureInitialized()
585
594
 
586
595
  const rv = []
587
- const resultCount = dittoCore.ditto_dql_response_result_count(responsePointer)
596
+ const resultCount = dittoCore.dittoffi_query_result_item_count(responsePointer)
588
597
  for (let i = 0; i < resultCount; i++) {
589
- rv.push(dittoCore.ditto_dql_response_result_at(responsePointer, i))
598
+ rv.push(dittoCore.dittoffi_query_result_item_at(responsePointer, i))
590
599
  }
591
600
  return rv
592
601
  }
@@ -601,9 +610,9 @@ export function dqlMutatedDocumentIDs(responsePointer: Pointer<FFIDqlResponse>):
601
610
  ensureInitialized()
602
611
 
603
612
  const rv = []
604
- const resultCount = dittoCore.ditto_dql_response_affected_document_id_count(responsePointer)
613
+ const resultCount = dittoCore.dittoffi_query_result_mutated_document_id_count(responsePointer)
605
614
  for (let i = 0; i < resultCount; i++) {
606
- const cborBytes = dittoCore.ditto_dql_response_affected_document_id_at(responsePointer, i)
615
+ const cborBytes = dittoCore.dittoffi_query_result_mutated_document_id_at(responsePointer, i)
607
616
  rv.push(dittoCore.boxCBytesIntoBuffer(cborBytes))
608
617
  }
609
618
  return rv
@@ -624,7 +633,7 @@ export function dqlResultCBOR(resultPointer: Pointer<FFIDqlResult>): Uint8Array
624
633
  trace()
625
634
  ensureInitialized()
626
635
 
627
- const cborBytes = dittoCore.ditto_result_cbor(resultPointer)
636
+ const cborBytes = dittoCore.dittoffi_query_result_item_cbor(resultPointer)
628
637
  return dittoCore.boxCBytesIntoBuffer(cborBytes)
629
638
  }
630
639
 
@@ -641,7 +650,7 @@ export function dqlResultJSON(resultPointer: Pointer<FFIDqlResult>): string {
641
650
  trace()
642
651
  ensureInitialized()
643
652
 
644
- const jsonBytes = dittoCore.ditto_result_json(resultPointer)
653
+ const jsonBytes = dittoCore.dittoffi_query_result_item_json(resultPointer)
645
654
  return dittoCore.boxCStringIntoString(jsonBytes)
646
655
  }
647
656
 
@@ -741,8 +750,8 @@ export async function tryExperimentalWebhookRegisterDqlLiveQuery(ditto: Pointer<
741
750
  const queryBuffer = bytesFromString(query)
742
751
  const urlBuffer = bytesFromString(url)
743
752
 
744
- const result = await dittoCore.dittoffi_try_experimental_webhook_register_dql_live_query_str(ditto, queryBuffer, queryArgsCBOR, urlBuffer)
745
- throwOnErrorResult(result.error, 'dittoffi_try_experimental_webhook_register_dql_live_query_str')
753
+ const result = await dittoCore.dittoffi_try_register_store_observer_webhook(ditto, queryBuffer, queryArgsCBOR, urlBuffer)
754
+ throwOnErrorResult(result.error, 'dittoffi_try_register_store_observer_webhook')
746
755
  const documentIdCBOR = result.success
747
756
 
748
757
  return dittoCore.boxCBytesIntoBuffer(documentIdCBOR)
@@ -755,6 +764,8 @@ export async function readTransaction(ditto: Pointer<FFIDitto>): Promise<Pointer
755
764
  trace()
756
765
  ensureInitialized()
757
766
 
767
+ // REFACTOR: add proper error handling.
768
+
758
769
  const { status_code: errorCode, txn: readTransaction } = await dittoCore.ditto_read_transaction(ditto)
759
770
  if (errorCode !== 0) throw new Error(errorMessage() || `\`ditto_read_transaction()\` failed with error code: ${errorCode}`)
760
771
  return readTransaction
@@ -765,6 +776,7 @@ export function readTransactionFree(self: Pointer<FFIReadTransaction>) {
765
776
  trace()
766
777
  ensureInitialized()
767
778
 
779
+ // REFACTOR: add proper error handling.
768
780
  return dittoCore.ditto_read_transaction_free(self)
769
781
  }
770
782
 
@@ -775,6 +787,8 @@ export async function writeTransaction(ditto: Pointer<FFIDitto>): Promise<Pointe
775
787
  trace()
776
788
  ensureInitialized()
777
789
 
790
+ // REFACTOR: add proper error handling.
791
+
778
792
  const { status_code: errorCode, txn: writeTransaction } = await dittoCore.ditto_write_transaction(ditto, null)
779
793
  if (errorCode !== 0) throw new Error(errorMessage() || `ditto_write_transaction() failed with error code: ${errorCode}`)
780
794
  return writeTransaction
@@ -1060,18 +1074,12 @@ export async function dittoAuthClientLogout(ditto: Pointer<FFIDitto>) {
1060
1074
  // ---------------------------------------------------------------- Ditto ------
1061
1075
 
1062
1076
  /** @internal */
1063
- export function uninitializedDittoMake(path: string): Pointer<FFIUninitializedDitto> {
1077
+ export function dittoMake(path: string, identityConfig: Pointer<FFIIdentityConfig>, historyTracking: string): Pointer<FFIDitto> {
1064
1078
  trace()
1065
1079
  ensureInitialized()
1066
- const pathX = bytesFromString(path)
1067
- return dittoCore.ditto_uninitialized_ditto_make(pathX)
1068
- }
1069
1080
 
1070
- /** @internal */
1071
- export function dittoMake(uninitializedDitto: Pointer<FFIUninitializedDitto>, identityConfig: Pointer<FFIIdentityConfig>): Pointer<FFIDitto> {
1072
- trace()
1073
- ensureInitialized()
1074
- return dittoCore.ditto_make(uninitializedDitto, identityConfig, 'Disabled')
1081
+ const pathPointer = bytesFromString(path)
1082
+ return dittoCore.ditto_make(pathPointer, identityConfig, historyTracking)
1075
1083
  }
1076
1084
 
1077
1085
  /** @internal */
@@ -1093,6 +1101,7 @@ export function dittoFree(self: Pointer<FFIDitto>) {
1093
1101
  trace()
1094
1102
  ensureInitialized()
1095
1103
 
1104
+ // REFACTOR: add proper error handling.
1096
1105
  return dittoCore.ditto_free(self)
1097
1106
  }
1098
1107
 
@@ -1255,6 +1264,7 @@ export function dittoSetDeviceName(dittoPointer: Pointer<FFIDitto>, deviceName:
1255
1264
 
1256
1265
  if (Environment.isWebBuild) {
1257
1266
  if (deviceNameCString.length > 64) {
1267
+
1258
1268
  const sanitizedDeviceName = deviceName.replace(/[^ -~]+/g, '')
1259
1269
  deviceNameCString = bytesFromString(sanitizedDeviceName.substr(0, 63))
1260
1270
 
@@ -1301,7 +1311,7 @@ export function dittoNewAttachmentFromFile(ditto: Pointer<FFIDitto>, sourcePath:
1301
1311
  const outAttachment: any = {}
1302
1312
  const errorCode = dittoCore.ditto_new_attachment_from_file(ditto, sourcePathCString, fileOperation, outAttachment)
1303
1313
  if (errorCode !== 0) {
1304
- throw new Error(errorMessage() || `ditto_new_attachment_from_file() failed with error code: ${errorCode}`)
1314
+ throw new DittoFFIError(errorCode, null, `ditto_new_attachment_from_file() failed with error code: ${errorCode}`)
1305
1315
  }
1306
1316
  return outAttachment
1307
1317
  }
@@ -1314,12 +1324,15 @@ export async function dittoNewAttachmentFromBytes(ditto: Pointer<FFIDitto>, byte
1314
1324
  const outAttachment: any = {}
1315
1325
  const errorCode = await dittoCore.ditto_new_attachment_from_bytes(ditto, bytes, outAttachment)
1316
1326
  if (errorCode !== 0) {
1317
- throw new Error(errorMessage() || `ditto_new_attachment_from_bytes() failed with error code: ${errorCode}`)
1327
+ throw new DittoFFIError(errorCode, null, `ditto_new_attachment_from_bytes() failed with error code: ${errorCode}`)
1318
1328
  }
1319
1329
  return outAttachment
1320
1330
  }
1321
1331
 
1322
- /** @internal */
1332
+ /**
1333
+ * @throws {@link DittoFFIError}
1334
+ * @internal
1335
+ */
1323
1336
  export async function dittoResolveAttachment(
1324
1337
  ditto: Pointer<FFIDitto>,
1325
1338
  id: Uint8Array,
@@ -1337,14 +1350,16 @@ export async function dittoResolveAttachment(
1337
1350
 
1338
1351
  const { onComplete, onProgress, onDelete } = namedCallbacks
1339
1352
  const { status_code: errorCode, cancel_token: cancelToken } = await dittoCore.ditto_resolve_attachment(ditto, id, wrapBackgroundCbForFFI(onError, onComplete), wrapBackgroundCbForFFI(onError, onProgress), wrapBackgroundCbForFFI(onError, onDelete))
1353
+
1340
1354
  if (errorCode !== 0) {
1341
- throw new Error(errorMessage() || `ditto_resolve_attachment() failed with error code: ${errorCode}`)
1355
+ throw new DittoFFIError(errorCode, null, `ditto_resolve_attachment() failed with error code: ${errorCode}`)
1342
1356
  }
1357
+
1343
1358
  return cancelToken
1344
1359
  }
1345
1360
 
1346
1361
  /** @internal */
1347
- export function dittoCancelResolveAttachment(dittoPointer: Pointer<FFIDitto>, id: Uint8Array, cancelToken: any) {
1362
+ export function dittoCancelResolveAttachment(dittoPointer: Pointer<FFIDitto>, id: Uint8Array, cancelToken: number | BigInt) {
1348
1363
  trace()
1349
1364
  ensureInitialized()
1350
1365
 
@@ -1552,6 +1567,25 @@ export function documentsHashMnemonic(documents: Pointer<FFIDocument>[]): string
1552
1567
  return dittoCore.boxCStringIntoString(c_string)
1553
1568
  }
1554
1569
 
1570
+ // ------------------------------------------------------------- base64 --------
1571
+
1572
+ /** @internal */
1573
+ export function base64encode(bytes: Uint8Array, paddingMode: Base64PaddingMode): string {
1574
+ const base64CString = dittoCore.dittoffi_base64_encode(bytes, paddingMode)
1575
+ return dittoCore.boxCStringIntoString(base64CString)
1576
+ }
1577
+
1578
+ /**
1579
+ * @throws {@link DittoFFIError} if the base64 string is invalid
1580
+ * @internal
1581
+ */
1582
+ export function tryBase64Decode(base64: string, paddingMode?: Base64PaddingMode): Uint8Array {
1583
+ const base64BytesPointer = bytesFromString(base64)
1584
+ const result = dittoCore.dittoffi_try_base64_decode(base64BytesPointer, paddingMode || null)
1585
+ throwOnErrorResult(result.error, 'dittoffi_try_base64_decode')
1586
+ return dittoCore.boxCBytesIntoBuffer(result.success)
1587
+ }
1588
+
1555
1589
  // ------------------------------------------------------------- Auth ----------
1556
1590
 
1557
1591
  /** @internal */
@@ -1664,12 +1698,10 @@ export function transportsInit(): void {
1664
1698
  trace()
1665
1699
  ensureInitialized()
1666
1700
 
1667
- const transportsErrorPointer = dittoCore.ditto_sdk_transports_error_new()
1668
- const wasInitialized = dittoCore.ditto_sdk_transports_init(transportsErrorPointer)
1669
-
1670
- const errorType: TransportsError = dittoCore.ditto_sdk_transports_error_value(transportsErrorPointer)
1671
- dittoCore.ditto_sdk_transports_error_free(transportsErrorPointer)
1701
+ // The symbols called below are not exported in the Wasm build.
1702
+ if (Environment.isWebBuild) return
1672
1703
 
1704
+ const { output: wasInitialized, errorType } = withTransportsError(dittoCore.ditto_sdk_transports_init)
1673
1705
  if (wasInitialized === false) {
1674
1706
  throw new Error(`Failed to initialize transports (${errorType} error)`)
1675
1707
  }
@@ -1680,7 +1712,10 @@ export function transportsBLEIsAvailable(ditto: Pointer<FFIDitto>): boolean {
1680
1712
  trace()
1681
1713
  ensureInitialized()
1682
1714
 
1683
- return dittoCore.ditto_sdk_transports_ble_is_available(ditto)
1715
+ if (Environment.isNodeBuild) {
1716
+ return dittoCore.ditto_sdk_transports_ble_is_available(ditto)
1717
+ }
1718
+ return false
1684
1719
  }
1685
1720
 
1686
1721
  /** @internal */
@@ -1688,19 +1723,17 @@ export function transportsBLECreate(ditto: Pointer<FFIDitto>): Pointer<Transport
1688
1723
  trace()
1689
1724
  ensureInitialized()
1690
1725
 
1691
- const transportsErrorPointer = dittoCore.ditto_sdk_transports_error_new()
1692
- const blePointer: Pointer<TransportBluetooth> = dittoCore.ditto_sdk_transports_ble_create(ditto, transportsErrorPointer)
1726
+ if (Environment.isNodeBuild) {
1727
+ const { output: blePointer, errorType } = withTransportsError(dittoCore.ditto_sdk_transports_ble_create, ditto)
1693
1728
 
1694
- const errorType: TransportsError = dittoCore.ditto_sdk_transports_error_value(transportsErrorPointer)
1695
- dittoCore.ditto_sdk_transports_error_free(transportsErrorPointer)
1729
+ if (blePointer != null) {
1730
+ log('Info', `Bluetooth transport created.`)
1731
+ } else {
1732
+ log('Error', `Can't create bluetooth transport (${errorType} error).`)
1733
+ }
1696
1734
 
1697
- if (blePointer != null) {
1698
- log('Info', `Bluetooth transport created.`)
1699
- } else {
1700
- log('Error', `Can't create bluetooth transport (${errorType} error).`)
1735
+ return blePointer
1701
1736
  }
1702
-
1703
- return blePointer
1704
1737
  }
1705
1738
 
1706
1739
  /** @internal */
@@ -1708,18 +1741,16 @@ export function transportsBLEDestroy(ble: Pointer<TransportBluetooth>): boolean
1708
1741
  trace()
1709
1742
  ensureInitialized()
1710
1743
 
1711
- const ffiError = dittoCore.ditto_sdk_transports_error_new()
1712
- const wasDestroyed = dittoCore.ditto_sdk_transports_ble_destroy(ble, ffiError)
1713
-
1714
- const errorType: TransportsError = dittoCore.ditto_sdk_transports_error_value(ffiError)
1715
- dittoCore.ditto_sdk_transports_error_free(ffiError)
1744
+ if (Environment.isNodeBuild) {
1745
+ const { output: wasDestroyed, errorType } = withTransportsError(dittoCore.ditto_sdk_transports_ble_destroy, ble)
1716
1746
 
1717
- if (wasDestroyed === true) {
1718
- log('Info', 'Bluetooth transport disabled.')
1719
- } else {
1720
- log('Error', `Bluetooth transport could not be disabled (${errorType} error).`)
1747
+ if (wasDestroyed === true) {
1748
+ log('Info', 'Bluetooth transport disabled.')
1749
+ } else {
1750
+ log('Error', `Bluetooth transport could not be disabled (${errorType} error).`)
1751
+ }
1752
+ return wasDestroyed
1721
1753
  }
1722
- return wasDestroyed
1723
1754
  }
1724
1755
 
1725
1756
  /** @internal */
@@ -1727,7 +1758,10 @@ export function transportsLANIsAvailable(ditto: Pointer<FFIDitto>): boolean {
1727
1758
  trace()
1728
1759
  ensureInitialized()
1729
1760
 
1730
- return dittoCore.ditto_sdk_transports_lan_is_available(ditto)
1761
+ if (Environment.isNodeBuild) {
1762
+ return dittoCore.ditto_sdk_transports_lan_is_available(ditto)
1763
+ }
1764
+ return false
1731
1765
  }
1732
1766
 
1733
1767
  /** @internal */
@@ -1735,19 +1769,17 @@ export function transportsLANCreate(ditto: Pointer<FFIDitto>): Pointer<Transport
1735
1769
  trace()
1736
1770
  ensureInitialized()
1737
1771
 
1738
- const transportsErrorPointer = dittoCore.ditto_sdk_transports_error_new()
1739
- const lanPointer: Pointer<TransportLAN> = dittoCore.ditto_sdk_transports_lan_create(ditto, transportsErrorPointer)
1772
+ if (Environment.isNodeBuild) {
1773
+ const { output: lanPointer, errorType } = withTransportsError(dittoCore.ditto_sdk_transports_lan_create, ditto)
1740
1774
 
1741
- const errorType: TransportsError = dittoCore.ditto_sdk_transports_error_value(transportsErrorPointer)
1742
- dittoCore.ditto_sdk_transports_error_free(transportsErrorPointer)
1775
+ if (lanPointer != null) {
1776
+ log('Info', `LAN transport created.`)
1777
+ } else {
1778
+ log('Error', `Can't create LAN transport (${errorType} error).`)
1779
+ }
1743
1780
 
1744
- if (lanPointer != null) {
1745
- log('Info', `LAN transport created.`)
1746
- } else {
1747
- log('Error', `Can't create LAN transport (${errorType} error).`)
1781
+ return lanPointer
1748
1782
  }
1749
-
1750
- return lanPointer
1751
1783
  }
1752
1784
 
1753
1785
  /** @internal */
@@ -1755,18 +1787,16 @@ export function transportsLANDestroy(lan: Pointer<TransportLAN>): boolean | void
1755
1787
  trace()
1756
1788
  ensureInitialized()
1757
1789
 
1758
- const ffiError = dittoCore.ditto_sdk_transports_error_new()
1759
- const wasDestroyed = dittoCore.ditto_sdk_transports_lan_destroy(lan, ffiError)
1760
-
1761
- const error: TransportsError = dittoCore.ditto_sdk_transports_error_value(ffiError)
1762
- dittoCore.ditto_sdk_transports_error_free(ffiError)
1790
+ if (Environment.isNodeBuild) {
1791
+ const { output: wasDestroyed, errorType } = withTransportsError(dittoCore.ditto_sdk_transports_lan_destroy, lan)
1763
1792
 
1764
- if (wasDestroyed === true) {
1765
- log('Info', 'LAN transport disabled.')
1766
- } else {
1767
- log('Error', `LAN transport could not be disabled (${error} error).`)
1793
+ if (wasDestroyed === true) {
1794
+ log('Info', 'LAN transport disabled.')
1795
+ } else {
1796
+ log('Error', `LAN transport could not be disabled (${errorType} error).`)
1797
+ }
1798
+ return wasDestroyed
1768
1799
  }
1769
- return wasDestroyed
1770
1800
  }
1771
1801
 
1772
1802
  /** @internal */
@@ -1774,7 +1804,10 @@ export function transportsAWDLIsAvailable(ditto: Pointer<FFIDitto>): boolean {
1774
1804
  trace()
1775
1805
  ensureInitialized()
1776
1806
 
1777
- return dittoCore.ditto_sdk_transports_awdl_is_available(ditto)
1807
+ if (Environment.isNodeBuild) {
1808
+ return dittoCore.ditto_sdk_transports_awdl_is_available(ditto)
1809
+ }
1810
+ return false
1778
1811
  }
1779
1812
 
1780
1813
  /** @internal */
@@ -1782,19 +1815,17 @@ export function transportsAWDLCreate(ditto: Pointer<FFIDitto>): Pointer<Transpor
1782
1815
  trace()
1783
1816
  ensureInitialized()
1784
1817
 
1785
- const transportsErrorPointer = dittoCore.ditto_sdk_transports_error_new()
1786
- const awdlPointer: Pointer<TransportAWDL> = dittoCore.ditto_sdk_transports_awdl_create(ditto, transportsErrorPointer)
1818
+ if (Environment.isNodeBuild) {
1819
+ const { output: awdlPointer, errorType } = withTransportsError(dittoCore.ditto_sdk_transports_awdl_create, ditto)
1787
1820
 
1788
- const errorType: TransportsError = dittoCore.ditto_sdk_transports_error_value(transportsErrorPointer)
1789
- dittoCore.ditto_sdk_transports_error_free(transportsErrorPointer)
1821
+ if (awdlPointer != null) {
1822
+ log('Info', `AWDL transport created.`)
1823
+ } else {
1824
+ log('Error', `Can't create AWDL transport (${errorType} error).`)
1825
+ }
1790
1826
 
1791
- if (awdlPointer != null) {
1792
- log('Info', `AWDL transport created.`)
1793
- } else {
1794
- log('Error', `Can't create AWDL transport (${errorType} error).`)
1827
+ return awdlPointer
1795
1828
  }
1796
-
1797
- return awdlPointer
1798
1829
  }
1799
1830
 
1800
1831
  /** @internal */
@@ -1802,18 +1833,48 @@ export function transportsAWDLDestroy(awdl: Pointer<TransportAWDL>): boolean | v
1802
1833
  trace()
1803
1834
  ensureInitialized()
1804
1835
 
1805
- const ffiError = dittoCore.ditto_sdk_transports_error_new()
1806
- const wasDestroyed = dittoCore.ditto_sdk_transports_awdl_destroy(awdl, ffiError)
1807
-
1808
- const errorType: TransportsError = dittoCore.ditto_sdk_transports_error_value(ffiError)
1809
- dittoCore.ditto_sdk_transports_error_free(ffiError)
1836
+ if (Environment.isNodeBuild) {
1837
+ const { output: wasDestroyed, errorType } = withTransportsError(dittoCore.ditto_sdk_transports_awdl_destroy, awdl)
1810
1838
 
1811
- if (wasDestroyed === true) {
1812
- log('Info', 'AWDL transport disabled.')
1813
- } else {
1814
- log('Error', `AWDL transport could not be disabled (${errorType} error).`)
1839
+ if (wasDestroyed === true) {
1840
+ log('Info', 'AWDL transport disabled.')
1841
+ } else {
1842
+ log('Error', `AWDL transport could not be disabled (${errorType} error).`)
1843
+ }
1844
+ return wasDestroyed
1815
1845
  }
1816
- return wasDestroyed
1846
+ }
1847
+
1848
+ /**
1849
+ * Helper type that extracts all but the last parameter from a function type.
1850
+ *
1851
+ * NOTE: will accept any parameters for functions that don't have typed parameters.
1852
+ */
1853
+ // prettier-ignore
1854
+ type ParametersExceptLast<F extends (...args: any[]) => any> = Parameters<F> extends [...infer U, any]
1855
+ ? U
1856
+ : any // FIXME: this should really be `never` but we don't have proper types for the FFI functions yet.
1857
+
1858
+ /**
1859
+ * Calls the given FFI function with the passed in arguments and a newly
1860
+ * allocated `transportsErrorPointer` as the last argument, then returns its
1861
+ * result.
1862
+ *
1863
+ * The given function MUST take a `transportsErrorPointer` as its last argument.
1864
+ *
1865
+ * @param ffiFunction The FFI function to wrap.
1866
+ * @param args The arguments to pass to the FFI function, excluding the
1867
+ * `transportsErrorPointer`.
1868
+ * @returns An object with two properties: `output` and `errorType`. `output` is
1869
+ * the return value of the FFI function, and `errorType` is the value of the
1870
+ * `transportsErrorPointer` after the FFI function has been called.
1871
+ */
1872
+ function withTransportsError<T extends (...args: any[]) => any>(ffiFunction: T, ...args: ParametersExceptLast<T>): { output: ReturnType<T>; errorType: TransportsError } {
1873
+ const transportsErrorPointer = dittoCore.ditto_sdk_transports_error_new()
1874
+ const output = ffiFunction(...args, transportsErrorPointer)
1875
+ const errorType: TransportsError = dittoCore.ditto_sdk_transports_error_value(transportsErrorPointer)
1876
+ dittoCore.ditto_sdk_transports_error_free(transportsErrorPointer)
1877
+ return { output, errorType }
1817
1878
  }
1818
1879
 
1819
1880
  // ---------------------------------------------------------------- Other ------
@@ -1902,8 +1963,25 @@ export function createDirectory(path: string): string | void {
1902
1963
  }
1903
1964
  }
1904
1965
 
1966
+ /** @internal */
1967
+ export function readFile(path: string): string {
1968
+ if (Environment.isReactNativeBuild) {
1969
+ // @ts-expect-error Throws method not-found on non-RN envs.
1970
+ return dittoCore.readFile(path)
1971
+ }
1972
+ }
1973
+
1974
+ /** @internal */
1975
+ export function copyFile(source: string, destination: string, dittoPath: string): string {
1976
+ if (Environment.isReactNativeBuild) {
1977
+ // @ts-expect-error Throws method not-found on non-RN envs.
1978
+ return dittoCore.copyFile(source, destination, dittoPath)
1979
+ }
1980
+ }
1981
+
1905
1982
  // -------------------------------------------------------------- Private ------
1906
1983
 
1984
+
1907
1985
  /** @internal */
1908
1986
  const NOT_FOUND_ERROR_CODE = -30798
1909
1987
 
@@ -82,14 +82,32 @@ export function validateQuery(query, options = {}): string {
82
82
  export function generateEphemeralToken(): string {
83
83
 
84
84
  let data = new Uint16Array(16)
85
- // Note: Replacing conditional with polymorphism. (#10731)
86
-
87
- data = FFI.getRandomValues(data) as Uint16Array
85
+ if (Environment.isReactNativeBuild) {
86
+ data = FFI.getRandomValues(data) as Uint16Array
87
+ } else {
88
+ throw new Error('Internal inconsistency, incorrect environment to run getRandomValues()')
89
+ }
88
90
 
89
91
  const doublets = Array.from(data)
90
92
  return doublets.map((doublet) => doublet.toString(16)).join('')
91
93
  }
92
94
 
95
+ /**
96
+ * Can be used to implement a custom Node.js `inspect` representation for
97
+ * objects that have a `value` property.
98
+ *
99
+ * Node.js < 16.14.0 and Electron < 19.0.0 do not provide the `inspect`
100
+ * parameter used below, which is when we fall back to `JSON.stringify`.
101
+ *
102
+ * See https://nodejs.org/api/util.html#custom-inspection-functions-on-objects
103
+ */
104
+ export function customInspectRepresentation(documentLike: { value: any }, inspect?: any): string {
105
+ if (inspect === undefined) {
106
+ return `${documentLike.constructor.name} ${JSON.stringify({ value: documentLike.value }, null, 2)}`
107
+ }
108
+ return `${documentLike.constructor.name} ${inspect({ value: documentLike.value })}`
109
+ }
110
+
93
111
  // --------------------------------------------------------------- System ------
94
112
 
95
113
  /** @internal */
@@ -106,4 +124,20 @@ export async function step<T>(closure: () => T): Promise<T> {
106
124
  }
107
125
 
108
126
  /** @internal */
127
+ // WORKAROUND: the corresponding FFI function(s) is not async at the
128
+ // moment, we therefore artificially make it async via step() until an
129
+ // async variant becomes available.
130
+ //
131
+ // Why? Any function marked as async that isn't under the hood appears to be
132
+ // yield-ing on the use site but isn't. This may lead to blocking the
133
+ // event loop for a long time, which in turn can lead to excessive memory
134
+ // consumption and other side-effects. Plus, WebAssembly limitations may
135
+ // lead to deadlocks and other crashes.
136
+ //
137
+ // The function alias here is to be able to easily see that its use is
138
+ // a workaround, plus easily finding all of those workarounds to fix
139
+ // them all once the FFI API is properly asyncified.
140
+ //
141
+ // See PR #4833 for details:
142
+ // https://github.com/getditto/ditto/pull/4833
109
143
  export const performAsyncToWorkaroundNonAsyncFFIAPI = step
@@ -26,7 +26,16 @@ export class LiveQueryManager {
26
26
  startLiveQuery(liveQuery: LiveQuery) {
27
27
  const ditto = this.ditto
28
28
  const dittoHandle = Bridge.ditto.handleFor(ditto)
29
-
29
+
30
+ // REFACTOR: the starting closure runs detached from here which is a smell.
31
+ // Can we make this whole starting mechanism non-async? The culprit is
32
+ // the workaround for a zalgo with `ditto_live_query_start()` FFI function:
33
+ // It immediately triggers the live query handler making it run "in-line"
34
+ // when creating a live query, while subsequent invocations are async
35
+ // (by definition). This is a classic zalgo case. Fix by making
36
+ // `ditto_live_query_start()` trigger the first live query callback `async`,
37
+ // just like the subsequent ones.
38
+
30
39
  ditto.deferCloseAsync(async () => {
31
40
  const liveQueryID = liveQuery.liveQueryID
32
41
  if (!liveQueryID) {
@@ -5,11 +5,11 @@
5
5
  import * as FFI from './ffi'
6
6
 
7
7
  import { Bridge } from './bridge'
8
- import { Collection } from './collection'
9
8
 
10
9
  import { LiveQueryEventInitial } from './live-query-event'
11
10
  import { LiveQueryEventUpdate } from './live-query-event'
12
11
 
12
+ import type { Collection } from './collection'
13
13
  import type { QueryArguments } from './essentials'
14
14
  import type { LiveQueryEvent } from './live-query-event'
15
15
  import type { LiveQueryManager } from './live-query-manager'