@dittolive/ditto 4.10.2 → 4.11.0-preview.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.
Files changed (44) hide show
  1. package/DittoReactNative.podspec +1 -17
  2. package/README.md +4 -80
  3. package/node/ditto.cjs.js +444 -81
  4. package/node/ditto.darwin-arm64.node +0 -0
  5. package/node/ditto.darwin-x64.node +0 -0
  6. package/node/ditto.linux-arm64.node +0 -0
  7. package/node/ditto.linux-x64.node +0 -0
  8. package/node/ditto.win32-x64.node +0 -0
  9. package/package.json +1 -1
  10. package/react-native/android/build.gradle +29 -34
  11. package/react-native/android/cpp-adapter.cpp +10 -11
  12. package/react-native/android/dittoffi/src/ditto_transaction.cpp +1 -0
  13. package/react-native/android/dittoffi/src/ditto_transaction.h +91 -0
  14. package/react-native/android/dittoffi/src/dittoffi.h +587 -390
  15. package/react-native/android/dittoffi/src/dittoffi_java.cpp +3360 -1358
  16. package/react-native/android/dittoffi/src/dittoffi_java.h +59 -10
  17. package/react-native/android/dittoffi/src/dittoffi_java.i +2 -0
  18. package/react-native/android/dittoffi/src/dittostore_java.i +48 -0
  19. package/react-native/android/dittoffi/src/mesh_java_interfaces.h +81 -109
  20. package/react-native/android/gradle.properties +6 -5
  21. package/react-native/android/src/main/java/com/dittolive/rnsdk/DittoRNSDKModule.kt +23 -15
  22. package/react-native/cpp/include/DQL.h +1 -0
  23. package/react-native/cpp/include/Differ.h +14 -0
  24. package/react-native/cpp/include/Lifecycle.h +1 -1
  25. package/react-native/cpp/include/Transaction.h +18 -0
  26. package/react-native/cpp/include/Transports.h +1 -0
  27. package/react-native/cpp/include/Utils.h +27 -3
  28. package/react-native/cpp/include/main.h +2 -0
  29. package/react-native/cpp/src/DQL.cpp +21 -0
  30. package/react-native/cpp/src/Differ.cpp +57 -0
  31. package/react-native/cpp/src/Lifecycle.cpp +17 -7
  32. package/react-native/cpp/src/Misc.cpp +50 -32
  33. package/react-native/cpp/src/Transaction.cpp +195 -0
  34. package/react-native/cpp/src/Transports.cpp +77 -1
  35. package/react-native/cpp/src/Utils.cpp +11 -0
  36. package/react-native/cpp/src/main.cpp +18 -1
  37. package/react-native/ditto.es6.js +1 -1
  38. package/react-native/ios/DittoRNSDK.mm +2 -4
  39. package/types/ditto.d.ts +271 -33
  40. package/web/ditto.es6.js +1 -1
  41. package/web/ditto.umd.js +1 -1
  42. package/web/ditto.wasm +0 -0
  43. package/react-native/ios/YeetJSIUtils.h +0 -60
  44. package/react-native/ios/YeetJSIUtils.mm +0 -196
package/node/ditto.cjs.js CHANGED
@@ -344,7 +344,6 @@ function ditto_live_query_register_str_detached(...args) { return ditto.ditto_li
344
344
  function ditto_live_query_signal_available_next(...args) { return ditto.ditto_live_query_signal_available_next(...args) }
345
345
  function ditto_live_query_start(...args) { return ditto.ditto_live_query_start(...args) }
346
346
  function ditto_live_query_stop(...args) { return ditto.ditto_live_query_stop(...args) }
347
- function ditto_live_query_webhook_register_str(...args) { return ditto.ditto_live_query_webhook_register_str(...args) }
348
347
  function ditto_log(...args) { return ditto.ditto_log(...args) }
349
348
  function ditto_logger_emoji_headings_enabled(...args) { return ditto.ditto_logger_emoji_headings_enabled(...args) }
350
349
  function ditto_logger_emoji_headings_enabled_get(...args) { return ditto.ditto_logger_emoji_headings_enabled_get(...args) }
@@ -394,12 +393,16 @@ function dittoffi_connection_request_identity_service_metadata_json(...args) { r
394
393
  function dittoffi_connection_request_peer_key_string(...args) { return ditto.dittoffi_connection_request_peer_key_string(...args) }
395
394
  function dittoffi_connection_request_peer_metadata_json(...args) { return ditto.dittoffi_connection_request_peer_metadata_json(...args) }
396
395
  function dittoffi_crypto_generate_secure_random_token(...args) { return ditto.dittoffi_crypto_generate_secure_random_token(...args) }
396
+ function dittoffi_differ_diff(...args) { return ditto.dittoffi_differ_diff(...args) }
397
+ function dittoffi_differ_free(...args) { return ditto.dittoffi_differ_free(...args) }
398
+ function dittoffi_differ_new(...args) { return ditto.dittoffi_differ_new(...args) }
397
399
  function dittoffi_ditto_is_activated(...args) { return ditto.dittoffi_ditto_is_activated(...args) }
398
400
  function dittoffi_ditto_is_sync_active(...args) { return ditto.dittoffi_ditto_is_sync_active(...args) }
399
401
  function dittoffi_ditto_set_authentication_status_handler(...args) { return ditto.dittoffi_ditto_set_authentication_status_handler(...args) }
400
402
  function dittoffi_ditto_set_cloud_sync_enabled(...args) { return ditto.dittoffi_ditto_set_cloud_sync_enabled(...args) }
401
403
  function dittoffi_ditto_stop_sync(...args) { return ditto.dittoffi_ditto_stop_sync(...args) }
402
404
  function dittoffi_ditto_transport_config(...args) { return ditto.dittoffi_ditto_transport_config(...args) }
405
+ function dittoffi_ditto_try_new_blocking(...args) { return ditto.dittoffi_ditto_try_new_blocking(...args) }
403
406
  function dittoffi_ditto_try_set_transport_config(...args) { return ditto.dittoffi_ditto_try_set_transport_config(...args) }
404
407
  function dittoffi_ditto_try_start_sync(...args) { return ditto.dittoffi_ditto_try_start_sync(...args) }
405
408
  function dittoffi_error_code(...args) { return ditto.dittoffi_error_code(...args) }
@@ -407,7 +410,6 @@ function dittoffi_error_description(...args) { return ditto.dittoffi_error_descr
407
410
  function dittoffi_error_free(...args) { return ditto.dittoffi_error_free(...args) }
408
411
  function dittoffi_get_sdk_semver(...args) { return ditto.dittoffi_get_sdk_semver(...args) }
409
412
  function dittoffi_logger_try_export_to_file_async(...args) { return ditto.dittoffi_logger_try_export_to_file_async(...args) }
410
- function dittoffi_make_with_transport_config_mode(...args) { return ditto.dittoffi_make_with_transport_config_mode(...args) }
411
413
  function dittoffi_presence_peer_metadata_json(...args) { return ditto.dittoffi_presence_peer_metadata_json(...args) }
412
414
  function dittoffi_presence_set_connection_request_handler(...args) { return ditto.dittoffi_presence_set_connection_request_handler(...args) }
413
415
  function dittoffi_presence_try_set_peer_metadata_json(...args) { return ditto.dittoffi_presence_try_set_peer_metadata_json(...args) }
@@ -417,8 +419,15 @@ function dittoffi_query_result_item_cbor(...args) { return ditto.dittoffi_query_
417
419
  function dittoffi_query_result_item_count(...args) { return ditto.dittoffi_query_result_item_count(...args) }
418
420
  function dittoffi_query_result_item_free(...args) { return ditto.dittoffi_query_result_item_free(...args) }
419
421
  function dittoffi_query_result_item_json(...args) { return ditto.dittoffi_query_result_item_json(...args) }
422
+ function dittoffi_query_result_item_new(...args) { return ditto.dittoffi_query_result_item_new(...args) }
420
423
  function dittoffi_query_result_mutated_document_id_at(...args) { return ditto.dittoffi_query_result_mutated_document_id_at(...args) }
421
424
  function dittoffi_query_result_mutated_document_id_count(...args) { return ditto.dittoffi_query_result_mutated_document_id_count(...args) }
425
+ function dittoffi_store_begin_transaction_async_throws(...args) { return ditto.dittoffi_store_begin_transaction_async_throws(...args) }
426
+ function dittoffi_store_transactions(...args) { return ditto.dittoffi_store_transactions(...args) }
427
+ function dittoffi_transaction_complete_async_throws(...args) { return ditto.dittoffi_transaction_complete_async_throws(...args) }
428
+ function dittoffi_transaction_execute_async_throws(...args) { return ditto.dittoffi_transaction_execute_async_throws(...args) }
429
+ function dittoffi_transaction_free(...args) { return ditto.dittoffi_transaction_free(...args) }
430
+ function dittoffi_transaction_info(...args) { return ditto.dittoffi_transaction_info(...args) }
422
431
  function dittoffi_try_add_sync_subscription(...args) { return ditto.dittoffi_try_add_sync_subscription(...args) }
423
432
  function dittoffi_try_base64_decode(...args) { return ditto.dittoffi_try_base64_decode(...args) }
424
433
  function dittoffi_try_exec_statement(...args) { return ditto.dittoffi_try_exec_statement(...args) }
@@ -544,6 +553,28 @@ var DittoCRDTType;
544
553
  DittoCRDTType[DittoCRDTType["rga"] = 3] = "rga";
545
554
  DittoCRDTType[DittoCRDTType["rwMap"] = 4] = "rwMap";
546
555
  })(DittoCRDTType || (DittoCRDTType = {}));
556
+ // ------------------------------------------------------------- Differ --------
557
+ function differNew() {
558
+ ensureInitialized();
559
+ return dittoffi_differ_new();
560
+ }
561
+ function differDiff(differ, items) {
562
+ ensureInitialized();
563
+ return dittoffi_differ_diff(differ, items);
564
+ }
565
+ function differFree(differ) {
566
+ ensureInitialized();
567
+ dittoffi_differ_free(differ);
568
+ }
569
+ // HACK: underlying safer-ffi doesn't equivalent a CDitto with dittoffi_store_t
570
+ // although they are C aliases so we need to force it's type.
571
+ /** @internal */
572
+ function dittoPointerToStorePointer(dittoHandle) {
573
+ return {
574
+ addr: dittoHandle.addr,
575
+ type: 'dittoffi_store_t const *',
576
+ };
577
+ }
547
578
  // ------------------------------------------------------------- Document ------
548
579
  /** @internal */
549
580
  function documentSetCBORWithTimestamp(document, path, cbor, timestamp) {
@@ -878,6 +909,12 @@ function queryResultItemJSON(queryResultItemPointer) {
878
909
  const jsonBytes = dittoffi_query_result_item_json(queryResultItemPointer);
879
910
  return boxCStringIntoString(jsonBytes);
880
911
  }
912
+ function queryResultItemNew(jsonData) {
913
+ ensureInitialized();
914
+ const result = dittoffi_query_result_item_new(jsonData);
915
+ throwOnErrorResult(result.error, 'dittoffi_query_result_item_new');
916
+ return result.success;
917
+ }
881
918
  // ------------------------------------------------------------ LiveQuery ------
882
919
  /** @internal */
883
920
  function liveQueryRegister(ditto, collectionName, query, queryArgsCBOR, orderBy, limit, offset, eventHandler,
@@ -931,20 +968,6 @@ async function liveQuerySignalAvailableNext(ditto, liveQueryID) {
931
968
  ensureInitialized();
932
969
  await ditto_live_query_signal_available_next(ditto, liveQueryID);
933
970
  }
934
- // ------------------------------------------------------------ Webhook ------
935
- /** @internal */
936
- async function liveQueryWebhookRegister(ditto, collectionName, query, orderBy, limit, offset, url) {
937
- ensureInitialized();
938
- const collectionNameBuffer = bytesFromString(collectionName);
939
- const queryBuffer = bytesFromString(query);
940
- const urlBuffer = bytesFromString(url);
941
- const { status_code: errorCode, id } = await ditto_live_query_webhook_register_str(ditto, collectionNameBuffer, queryBuffer, orderBy, limit, offset, urlBuffer);
942
- if (errorCode !== 0) {
943
- throw new Error(errorMessage() ||
944
- `\`ditto_live_query_webhook_register_str()\` failed with error code: ${errorCode}`);
945
- }
946
- return boxCBytesIntoBuffer(id);
947
- }
948
971
  // ------------------------------------------------------ ReadTransaction ------
949
972
  /** @internal */
950
973
  async function readTransaction(ditto) {
@@ -1225,12 +1248,68 @@ function authenticationStatusFree(ffiAuthenticationStatus) {
1225
1248
  ensureInitialized();
1226
1249
  dittoffi_authentication_status_free(ffiAuthenticationStatus);
1227
1250
  }
1251
+ // --------------------------------------------------------- Transactions ------
1252
+ function storeTransactions(store) {
1253
+ ensureInitialized();
1254
+ const cborBytes = dittoffi_store_transactions(store);
1255
+ return boxCBytesIntoBuffer(cborBytes);
1256
+ }
1257
+ async function storeBeginTransaction(store, options) {
1258
+ ensureInitialized();
1259
+ const ffiOptions = {
1260
+ is_read_only: options.isReadOnly,
1261
+ hint: bytesFromString(options.hint),
1262
+ };
1263
+ return new Promise((resolve, reject) => {
1264
+ const callback = wrapBackgroundCbForFFI(reject, (resultObj) => {
1265
+ throwOnErrorResult(resultObj.error, 'dittoffi_store_begin_transaction_async_throws');
1266
+ resolve(resultObj.success);
1267
+ });
1268
+ dittoffi_store_begin_transaction_async_throws(store, ffiOptions, callback);
1269
+ });
1270
+ }
1271
+ async function transactionCompleteAsync(transaction, action) {
1272
+ ensureInitialized();
1273
+ return new Promise((resolve, reject) => {
1274
+ const callback = wrapBackgroundCbForFFI(reject, (resultObj) => {
1275
+ throwOnErrorResult(resultObj.error, 'dittoffi_transaction_complete_async_throws');
1276
+ const action = resultObj.success;
1277
+ resolve(action);
1278
+ });
1279
+ dittoffi_transaction_complete_async_throws(transaction, action, callback);
1280
+ });
1281
+ }
1282
+ async function transactionExecuteAsync(transaction, query, queryArgsCbor) {
1283
+ ensureInitialized();
1284
+ return new Promise((resolve, reject) => {
1285
+ const callback = wrapBackgroundCbForFFI(reject, (resultObj) => {
1286
+ throwOnErrorResult(resultObj.error, 'dittoffi_transaction_execute_async_throws');
1287
+ resolve(resultObj.success);
1288
+ });
1289
+ const queryBytes = bytesFromString(query);
1290
+ dittoffi_transaction_execute_async_throws(transaction, queryBytes, queryArgsCbor, callback);
1291
+ });
1292
+ }
1293
+ function transactionInfo(transaction) {
1294
+ ensureInitialized();
1295
+ const cborBytes = dittoffi_transaction_info(transaction);
1296
+ return boxCBytesIntoBuffer(cborBytes);
1297
+ }
1298
+ function transactionFree(transaction) {
1299
+ ensureInitialized();
1300
+ dittoffi_transaction_free(transaction);
1301
+ }
1228
1302
  // ---------------------------------------------------------------- Ditto ------
1229
1303
  /** @internal */
1230
- function dittoMakeWithTransportConfigMode(path, identityConfig, historyTracking, transportConfigMode) {
1304
+ function dittoTryNewBlocking(path, identityConfig, historyTracking, transportConfigMode) {
1231
1305
  ensureInitialized();
1306
+ // Encryption is not supported by the JS SDK.
1307
+ const experimentalPassphrase = null;
1232
1308
  const pathPointer = bytesFromString(path);
1233
- return dittoffi_make_with_transport_config_mode(pathPointer, identityConfig, historyTracking, transportConfigMode);
1309
+ const experimentalPassphrasePointer = bytesFromString(experimentalPassphrase);
1310
+ const result = dittoffi_ditto_try_new_blocking(pathPointer, identityConfig, historyTracking, experimentalPassphrasePointer, transportConfigMode);
1311
+ throwOnErrorResult(result.error, 'dittoffi_ditto_try_new_blocking');
1312
+ return result.success;
1234
1313
  }
1235
1314
  /** @internal */
1236
1315
  async function dittoGetCollectionNames(self) {
@@ -1403,17 +1482,17 @@ function dittoSmallPeerInfoGetIsEnabled(dittoPointer) {
1403
1482
  return ditto_small_peer_info_get_is_enabled(dittoPointer);
1404
1483
  }
1405
1484
  /** @internal */
1406
- async function dittoSmallPeerInfoSetEnabled(dittoPointer, isEnabled) {
1485
+ function dittoSmallPeerInfoSetEnabled(dittoPointer, isEnabled) {
1407
1486
  ensureInitialized();
1408
- ditto_small_peer_info_set_enabled(dittoPointer, isEnabled);
1487
+ return ditto_small_peer_info_set_enabled(dittoPointer, isEnabled);
1409
1488
  }
1410
1489
  /** @internal */
1411
- async function dittoSmallPeerInfoGetSyncScope(dittoPointer) {
1490
+ function dittoSmallPeerInfoGetSyncScope(dittoPointer) {
1412
1491
  ensureInitialized();
1413
1492
  return ditto_small_peer_info_get_sync_scope(dittoPointer);
1414
1493
  }
1415
1494
  /** @internal */
1416
- async function dittoSmallPeerInfoSetSyncScope(dittoPointer, syncScope) {
1495
+ function dittoSmallPeerInfoSetSyncScope(dittoPointer, syncScope) {
1417
1496
  ensureInitialized();
1418
1497
  return ditto_small_peer_info_set_sync_scope(dittoPointer, syncScope);
1419
1498
  }
@@ -1448,7 +1527,6 @@ function dittoSmallPeerInfoSetMetadata(dittoPointer, metadata) {
1448
1527
  function dittoRegisterTransportConditionChangedCallback(self, cb) {
1449
1528
  ensureInitialized();
1450
1529
  if (!cb) {
1451
- // @Konstantin: do we do this?
1452
1530
  ditto_register_transport_condition_changed_callback(self, null);
1453
1531
  }
1454
1532
  else {
@@ -1813,8 +1891,12 @@ const ERROR_CODES = {
1813
1891
  'store/crdt': 'An error occurred processing a CRDT.',
1814
1892
  /** Error for a document not found. */
1815
1893
  'store/document-not-found': 'The document with the provided ID could not be found.',
1894
+ /** Error for writing to a read-only transaction. */
1895
+ 'store/transaction-read-only': 'A mutating DQL query was attempted using a read-only transaction.',
1816
1896
  /** Error for an invalid document ID. */
1817
1897
  'store/document-id': 'The document ID is invalid.',
1898
+ /** Error when the chosen persistence directory is already in use by another Ditto instance. */
1899
+ 'store/persistence-directory-locked': 'The chosen persistence directory is already in use by another Ditto instance.',
1818
1900
  /** Permission has been denied for a file operation when working with attachments. */
1819
1901
  'store/attachment-file-permission-denied': 'Permission has been denied for a file operation when working with attachments.',
1820
1902
  /** The source file for an attachment does not exist. */
@@ -1854,6 +1936,21 @@ const ERROR_CODES = {
1854
1936
  'validation/not-json-compatible': 'Value is not serializable as JSON.',
1855
1937
  /** A validation error where a size limit was exceeded. */
1856
1938
  'validation/size-limit-exceeded': 'The size limit has been exceeded.',
1939
+ //
1940
+ // Encryption errors
1941
+ //
1942
+ /**
1943
+ * Error when a passphrase was provided but the store is not encrypted.
1944
+ *
1945
+ * This error is not in use for the JavaScript SDK, which currently does not
1946
+ * support encrypted stores.
1947
+ */
1948
+ 'encryption/extraneous-passphrase-given': 'Unexpected passphrase provided for the currently unencrypted store.',
1949
+ //
1950
+ // Differ errors
1951
+ //
1952
+ /** */
1953
+ 'differ/identity-key-path-invalid': 'A provided identity key path is invalid.',
1857
1954
  };
1858
1955
 
1859
1956
  //
@@ -1897,12 +1994,20 @@ const DEFAULT_STATUS_CODE_MAPPING = {
1897
1994
  StoreQuery: ['query/execution'],
1898
1995
  ParameterQuery: ['query/parameter'],
1899
1996
  //
1997
+ // Encryption errors
1998
+ //
1999
+ EncryptionExtraneousPassphraseGiven: [
2000
+ 'encryption/extraneous-passphrase-given',
2001
+ ],
2002
+ //
1900
2003
  // Store errors
1901
2004
  //
1902
2005
  StoreDatabase: ['store/backend'],
1903
2006
  StoreDocumentId: ['store/document-id'],
1904
2007
  StoreDocumentNotFound: ['store/document-not-found'],
2008
+ StoreTransactionReadOnly: ['store/transaction-read-only'],
1905
2009
  Crdt: ['store/crdt'],
2010
+ LockedDittoWorkingDirectory: ['store/persistence-directory-locked'],
1906
2011
  //
1907
2012
  // Validation errors
1908
2013
  //
@@ -1918,17 +2023,12 @@ const DEFAULT_STATUS_CODE_MAPPING = {
1918
2023
  ValidationNotAMap: ['validation/not-an-object'],
1919
2024
  ValidationSizeLimitExceeded: ['validation/size-limit-exceeded'],
1920
2025
  //
2026
+ // Differ errors
2027
+ //
2028
+ DifferIdentityKeyPathInvalid: ['differ/identity-key-path-invalid'],
2029
+ //
1921
2030
  // Lifecycle errors
1922
2031
  //
1923
- // FIXME: A locked Ditto working directory should eventually not result in an
1924
- // `internal` error. Here it is mapped to that until
1925
- // https://github.com/getditto/ditto/issues/12096 is fixed, and this kind of
1926
- // error is actually thrown instead of the SDK panicking when the persistence
1927
- // directory is locked.
1928
- LockedDittoWorkingDirectory: [
1929
- 'internal',
1930
- 'Ditto working directory is locked.',
1931
- ],
1932
2032
  Transport: ['internal', 'Transport error.'],
1933
2033
  Unsupported: ['sdk/unsupported'],
1934
2034
  Unknown: ['internal/unknown-error'], // May be updated in v5, c.f. #12755
@@ -2174,7 +2274,7 @@ class AttachmentToken {
2174
2274
 
2175
2275
  // NOTE: this is patched up with the actual build version by Jake task
2176
2276
  // build:package and has to be a valid semantic version as defined here: https://semver.org.
2177
- const fullBuildVersionString = '4.10.2';
2277
+ const fullBuildVersionString = '4.11.0-preview.1';
2178
2278
 
2179
2279
  //
2180
2280
  // Copyright © 2021 DittoLive Incorporated. All rights reserved.
@@ -4057,6 +4157,10 @@ Bridge.queryResult = new _a(queryResultFree);
4057
4157
  /** @internal */
4058
4158
  Bridge.queryResultItem = new _a(queryResultItemFree);
4059
4159
  /** @internal */
4160
+ Bridge.transaction = new _a(transactionFree);
4161
+ /** @internal */
4162
+ Bridge.differ = new _a(differFree);
4163
+ /** @internal */
4060
4164
  Bridge.ditto = new _a(async (dittoPointer) => {
4061
4165
  // HACK: quick and dirty, clear all presence callbacks. This covers presence
4062
4166
  // v1 and v2 callbacks. v3 should be cleared properly by the `Presence`
@@ -4407,6 +4511,9 @@ async function step(closure) {
4407
4511
  // See PR #4833 for details:
4408
4512
  // https://github.com/getditto/ditto/pull/4833
4409
4513
  const performAsyncToWorkaroundNonAsyncFFIAPI = step;
4514
+ function capitalize(str) {
4515
+ return str.charAt(0).toUpperCase() + str.slice(1);
4516
+ }
4410
4517
 
4411
4518
  //
4412
4519
  // Copyright © 2021 DittoLive Incorporated. All rights reserved.
@@ -7914,6 +8021,69 @@ class WriteTransaction {
7914
8021
  }
7915
8022
  }
7916
8023
 
8024
+ /** Encapsulates information about a transaction.
8025
+ *
8026
+ * @see {@link Store.transaction | ditto.store.transaction()}
8027
+ */
8028
+ class TransactionInfo {
8029
+ constructor(id, isReadOnly, hint) {
8030
+ this.id = id;
8031
+ this.hint = hint;
8032
+ this.isReadOnly = isReadOnly;
8033
+ }
8034
+ }
8035
+ /**
8036
+ * Represents a transaction in the Ditto store.
8037
+ *
8038
+ * A `Transaction` groups multiple operations into a single atomic unit,
8039
+ * ensuring that all operations within the transaction are either fully applied
8040
+ * or not applied at all, thereby maintaining data integrity.
8041
+ *
8042
+ * For more information on creating and using transactions, refer to the
8043
+ * {@link Store.transaction | ditto.store.transaction()} method. For a comprehensive guide on
8044
+ * transactions, please visit the
8045
+ * [Ditto documentation](https://docs.ditto.live/sdk/latest/crud/transactions).
8046
+ */
8047
+ class Transaction {
8048
+ constructor(store) {
8049
+ this.store = store;
8050
+ }
8051
+ /** Provides information about the current transaction. */
8052
+ get info() {
8053
+ const transactionHandle = Bridge.transaction.handleFor(this);
8054
+ const cborData = transactionInfo(transactionHandle.deref());
8055
+ const options = CBOR.decode(cborData);
8056
+ return new TransactionInfo(options.id, options.is_read_only, options.hint);
8057
+ }
8058
+ async execute(query, queryArguments) {
8059
+ if (typeof query !== 'string') {
8060
+ throw new DittoError('query/invalid', `Expected parameter 'query' to be of type 'string', found: ${typeof query}`);
8061
+ }
8062
+ return this.store.ditto.deferCloseAsync(async () => {
8063
+ let queryArgumentsCBOR = null;
8064
+ if (queryArguments) {
8065
+ try {
8066
+ const queryArgumentsJSON = desugarJSObject(queryArguments);
8067
+ queryArgumentsCBOR = CBOR.encode(queryArgumentsJSON);
8068
+ }
8069
+ catch (error) {
8070
+ throw new DittoError('query/arguments-invalid', `Unable to encode query arguments: ${error.message}`);
8071
+ }
8072
+ }
8073
+ const transactionHandle = Bridge.transaction.handleFor(this);
8074
+ const queryResultPointer = await mapFFIErrorsAsync(async () => transactionExecuteAsync(transactionHandle.deref(), query, queryArgumentsCBOR));
8075
+ return Bridge.queryResult.bridge(queryResultPointer, () => new QueryResult(queryResultPointer));
8076
+ });
8077
+ }
8078
+ // ----------------------------------------------------------- Internal ------
8079
+ /** @internal */
8080
+ async complete(action) {
8081
+ const transactionHandle = Bridge.transaction.handleFor(this);
8082
+ const result = await mapFFIErrorsAsync(async () => await transactionCompleteAsync(transactionHandle.deref(), capitalize(action)));
8083
+ return result.toLowerCase();
8084
+ }
8085
+ }
8086
+
7917
8087
  //
7918
8088
  // Copyright © 2021 DittoLive Incorporated. All rights reserved.
7919
8089
  //
@@ -7925,6 +8095,22 @@ class WriteTransaction {
7925
8095
  * {@link Ditto} instance via its {@link Ditto.store | store} property.
7926
8096
  */
7927
8097
  class Store {
8098
+ /** @internal */
8099
+ // NOTE: currently, this property will only return the currently _running_
8100
+ // transactions rather than all pending. This means it's at most a single
8101
+ // read-write transaction, and potentially multiple read-only transactions.
8102
+ // Ideally, we'd want to have all _enqueued_ transactions appear here but
8103
+ // we don't have the infrastructure in the Rust Core for this yet.
8104
+ // Therefore, we'll keep this property internal for the time being,
8105
+ // which is still useful for some tests.
8106
+ get transactions() {
8107
+ return this.ditto.deferClose((dittoHandle) => {
8108
+ const storePointer = dittoPointerToStorePointer(dittoHandle.deref());
8109
+ const cborData = storeTransactions(storePointer);
8110
+ const data = CBOR.decode(cborData);
8111
+ return data.map((t) => new TransactionInfo(t.id, t.is_read_only, t.hint));
8112
+ });
8113
+ }
7928
8114
  /**
7929
8115
  * Register a handler to be called whenever a query's results change in the
7930
8116
  * local store.
@@ -8072,31 +8258,6 @@ class Store {
8072
8258
  return mapFFIErrors(() => dittoGetCollectionNames(dittoHandle.deref()));
8073
8259
  });
8074
8260
  }
8075
- /**
8076
- * Executes a DQL query and returns matching items as a query result.
8077
- *
8078
- * **Note:** only returns results from the local store without waiting for any
8079
- * {@link SyncSubscription | sync subscriptions} to have caught up with the
8080
- * latest changes. Only use this method if your program must proceed with
8081
- * immediate results. Use a {@link StoreObserver | store observer} to receive
8082
- * updates to query results as soon as they have been synced to this peer.
8083
- *
8084
- * @param query A string containing a valid query expressed in DQL.
8085
- * @param queryArguments An object of values keyed by the placeholder name
8086
- * without the leading `:`. Example: `{ "name": "John" }` for a query like
8087
- * `SELECT * FROM people WHERE name = :name`.
8088
- * @template T The type of items returned by the query. This is a convenience
8089
- * type that is neither inferred from the `query` parameter nor validated
8090
- * against it.
8091
- * @template U The type of the query arguments
8092
- * @returns A promise for a {@link QueryResult} containing a
8093
- * {@link QueryResultItem} for each match.
8094
- * @throws {@link DittoError} `query/invalid`: if `query` argument is not a
8095
- * string or not valid DQL.
8096
- * @throws {@link DittoError} `query/arguments-invalid`: if `queryArguments`
8097
- * argument is invalid (e.g. contains unsupported types).
8098
- * @throws {@link DittoError} may throw other errors.
8099
- */
8100
8261
  async execute(query, queryArguments) {
8101
8262
  if (typeof query !== 'string') {
8102
8263
  throw new DittoError('query/invalid', `Expected parameter 'query' to be of type 'string', found: ${typeof query}`);
@@ -8408,6 +8569,131 @@ class Store {
8408
8569
  this.attachmentFetchers = Object.freeze(newAttachmentFetchers);
8409
8570
  return true;
8410
8571
  }
8572
+ // ------------------------------------------ Working with Transactions ------
8573
+ /**
8574
+ * Executes multiple DQL queries within a single atomic transaction.
8575
+ *
8576
+ * This ensures that either all statements are executed successfully, or none
8577
+ * are executed at all, providing strong consistency guarantees. Certain mesh
8578
+ * configurations may impose limitations on these guarantees. For more
8579
+ * details, refer to the [Ditto
8580
+ * documentation](https://docs.ditto.live/sdk/latest/crud/transactions).
8581
+ *
8582
+ * Transactions are initiated as read-write by default, and only one
8583
+ * read-write transaction can be executed at any given time. Any other
8584
+ * read-write transaction started concurrently will wait until the current
8585
+ * transaction has been committed or rolled back. Therefore, it is crucial to
8586
+ * ensure a transaction finishes as early as possible to prevent blocking
8587
+ * other read-write transactions.
8588
+ *
8589
+ * A transaction can also be configured to be read-only using the `isReadOnly`
8590
+ * parameter. Multiple read-only transactions can be executed concurrently.
8591
+ * However, executing a mutating DQL statement in a read-only transaction will
8592
+ * throw an error.
8593
+ *
8594
+ * If errors occur in an `execute()` call within a transaction block and the
8595
+ * error is caught and handled within the block, the transaction will continue
8596
+ * to run and not be rolled back. When an error is thrown at any point inside
8597
+ * the transaction block or while committing the transaction, the transaction
8598
+ * is implicitly rolled back, and the error is propagated to the caller.
8599
+ *
8600
+ * When a Ditto instance goes out of scope, it will drive all pending
8601
+ * transactions to completion before being shut down.
8602
+ *
8603
+ * **Warning:** Calling `ditto.store.execute()` or creating a nested
8604
+ * transaction within a transaction may lead to a deadlock.
8605
+ *
8606
+ * The transaction closure provided here can either return a
8607
+ * {@link TransactionCompletionAction} or an arbitrary value, either of which
8608
+ * will then also be returned by the call to this method itself. If one of the
8609
+ * {@link TransactionCompletionAction} values `'commit'` or `'rollback'` is
8610
+ * returned from the closure, that action is applied. If any other value,
8611
+ * including `null`, is returned, the transaction is committed unless an error
8612
+ * is thrown from the closure.
8613
+ *
8614
+ * Example usage (explicit completion):
8615
+ *
8616
+ * ```ts
8617
+ * await store.transaction(async (transaction) => {
8618
+ * // ...
8619
+ * return 'commit'
8620
+ * })
8621
+ * ```
8622
+ *
8623
+ * Example usage (returning a custom type):
8624
+ *
8625
+ * ```ts
8626
+ * interface UserData {
8627
+ * id: string
8628
+ * name: string
8629
+ * }
8630
+ *
8631
+ * const user: UserData = await store.transaction<UserData>(async (transaction) => {
8632
+ * // ...
8633
+ * return { id: 'u1', name: 'Alice' }
8634
+ * })
8635
+ * ```
8636
+ *
8637
+ * @template T The type of the value returned from the `scope` function.
8638
+ * Defaults to `TransactionCompletionAction`.
8639
+ * @param {Function} scope A function that provides access to a transaction
8640
+ * object to execute DQL queries.
8641
+ * @param {TransactionOptions} [options] Optional settings for the
8642
+ * transaction.
8643
+ * @returns {Promise<T>} A promise that resolves to the value returned by the
8644
+ * scope function. If that value is a {@link TransactionCompletionAction}, the
8645
+ * transaction is completed with the same action.
8646
+ * @throws {DittoError} Throws `DittoError` of `store/transaction-read-only`
8647
+ * if a mutating query is executed in a read-only transaction.
8648
+ * @throws {DittoError} May throw other `DittoError`s.
8649
+ * @throws Will rethrow any error thrown within the `scope` function.
8650
+ * @see {@link Transaction}
8651
+ * @see {@link TransactionOptions}
8652
+ */
8653
+ async transaction(scope, options = {}) {
8654
+ return this.ditto.deferCloseAsync(async () => {
8655
+ if ((options === null || options === void 0 ? void 0 : options.isReadOnly) && typeof options.isReadOnly !== 'boolean')
8656
+ throw new TypeError("Expected 'options.isReadOnly' to be a boolean");
8657
+ if ((options === null || options === void 0 ? void 0 : options.hint) && typeof options.hint !== 'string')
8658
+ throw new TypeError("Expected 'options.hint' to be a string");
8659
+ const transaction = await this.beginTransaction(options);
8660
+ // REFACTOR: completing with action `rollback` should never fail even
8661
+ // though the API is fallible (because `commit` _can_ fail). The Rust Core
8662
+ // will log an error if anything goes wrong here, so we simply ignore any
8663
+ // `transaction.complete()` errors.
8664
+ let result;
8665
+ try {
8666
+ result = await scope(transaction);
8667
+ }
8668
+ catch (error) {
8669
+ await transaction.complete('rollback');
8670
+ throw error;
8671
+ }
8672
+ if (result === 'rollback' || result === 'commit') {
8673
+ return (await transaction.complete(result));
8674
+ }
8675
+ await transaction.complete('commit');
8676
+ return result;
8677
+ });
8678
+ }
8679
+ // ----------------------------------------------------------- Internal ------
8680
+ /** @internal */
8681
+ async beginTransaction(options = {}) {
8682
+ return this.ditto.deferCloseAsync(async (dittoHandle) => {
8683
+ var _a, _b;
8684
+ // We explicitely set the default value here to maintain consistency with
8685
+ // other API that we already have, while acknowledging that default values
8686
+ // are generally better to be dictated from Core (via a factory method).
8687
+ const isReadOnly = (_a = options.isReadOnly) !== null && _a !== void 0 ? _a : false;
8688
+ const hint = (_b = options.hint) !== null && _b !== void 0 ? _b : null;
8689
+ const storePointer = dittoPointerToStorePointer(dittoHandle.deref());
8690
+ const transactionPointer = await storeBeginTransaction(storePointer, {
8691
+ isReadOnly,
8692
+ hint,
8693
+ });
8694
+ return Bridge.transaction.bridge(transactionPointer, () => new Transaction(this));
8695
+ });
8696
+ }
8411
8697
  /** @internal */
8412
8698
  close() {
8413
8699
  for (const observer of this.observers)
@@ -8417,17 +8703,6 @@ class Store {
8417
8703
  // NOTE: live query webhook is taken care of by the FFI's
8418
8704
  // `ditto_shutdown()`, no need to unregister it here.
8419
8705
  }
8420
- /**
8421
- * Private method, used only by the Portal https://github.com/getditto/ditto/pull/3652
8422
- * @internal
8423
- */
8424
- async registerLiveQueryWebhook(collectionName, query, url) {
8425
- return this.ditto.deferCloseAsync(async (dittoHandle) => {
8426
- const validatedQuery = validateQuery(query);
8427
- const idCBOR = await liveQueryWebhookRegister(dittoHandle.deref(), collectionName, validatedQuery, [], 0, 0, url);
8428
- return new DocumentID(idCBOR, true);
8429
- });
8430
- }
8431
8706
  }
8432
8707
 
8433
8708
  //
@@ -9323,8 +9598,8 @@ class SmallPeerInfo {
9323
9598
  set isEnabled(newValue) {
9324
9599
  if (typeof newValue !== 'boolean')
9325
9600
  throw new TypeError(`Expected boolean, got ${typeof newValue}`);
9326
- void this.ditto.deferCloseAsync(async (dittoHandle) => {
9327
- return dittoSmallPeerInfoSetEnabled(dittoHandle.deref(), newValue);
9601
+ this.ditto.deferClose((dittoHandle) => {
9602
+ dittoSmallPeerInfoSetEnabled(dittoHandle.deref(), newValue);
9328
9603
  });
9329
9604
  }
9330
9605
  /**
@@ -9385,15 +9660,35 @@ class SmallPeerInfo {
9385
9660
  dittoSmallPeerInfoSetMetadata(dittoHandle.deref(), metadata);
9386
9661
  });
9387
9662
  }
9663
+ /**
9664
+ * Determines which "kind" of peers the small peer info will be replicated to.
9665
+ *
9666
+ * Defaults to `LocalPeerOnly`, which means no replication. Set this to
9667
+ * `BigPeerOnly` to replicate collected info to the Big Peer.
9668
+ *
9669
+ * @throws when set to a value other than `BigPeerOnly` or `LocalPeerOnly`.
9670
+ */
9671
+ get syncScope() {
9672
+ return this.ditto.deferClose((dittoHandle) => {
9673
+ return dittoSmallPeerInfoGetSyncScope(dittoHandle.deref());
9674
+ });
9675
+ }
9676
+ set syncScope(syncScope) {
9677
+ this.ditto.deferClose((dittoHandle) => {
9678
+ return dittoSmallPeerInfoSetSyncScope(dittoHandle.deref(), syncScope);
9679
+ });
9680
+ }
9388
9681
  /**
9389
9682
  * Determines which "kind" of peers the small peer info will be
9390
9683
  * replicated to.
9391
9684
  *
9392
9685
  * Defaults to `LocalPeerOnly`, which means no replication. Set this to
9393
9686
  * `BigPeerOnly` to replicate collected info to the Big Peer.
9687
+ *
9688
+ * @deprecated use {@link SmallPeerInfo.syncScope} instead.
9394
9689
  */
9395
9690
  async getSyncScope() {
9396
- return this.ditto.deferCloseAsync(async (dittoHandle) => {
9691
+ return this.ditto.deferClose((dittoHandle) => {
9397
9692
  return dittoSmallPeerInfoGetSyncScope(dittoHandle.deref());
9398
9693
  });
9399
9694
  }
@@ -9404,9 +9699,10 @@ class SmallPeerInfo {
9404
9699
  *
9405
9700
  * @param syncScope the new sync scope.
9406
9701
  * @throws when set to a value other than `BigPeerOnly` or `LocalPeerOnly`.
9702
+ * @deprecated use {@link SmallPeerInfo.syncScope} instead.
9407
9703
  */
9408
9704
  async setSyncScope(syncScope) {
9409
- return this.ditto.deferCloseAsync(async (dittoHandle) => {
9705
+ return this.ditto.deferClose((dittoHandle) => {
9410
9706
  return dittoSmallPeerInfoSetSyncScope(dittoHandle.deref(), syncScope);
9411
9707
  });
9412
9708
  }
@@ -9581,7 +9877,7 @@ class Ditto {
9581
9877
  // determined for P2P transports based on the environment. We specify this here by setting
9582
9878
  // `transportConfigMode` to 'PlatformDependent'.
9583
9879
  const transportConfigMode = 'PlatformDependent';
9584
- const dittoPointer = dittoMakeWithTransportConfigMode(this.persistenceDirectory, identityConfig, historyTracking, transportConfigMode);
9880
+ const dittoPointer = mapFFIErrors(() => dittoTryNewBlocking(this.persistenceDirectory, identityConfig, historyTracking, transportConfigMode));
9585
9881
  const appID = dittoAuthClientGetAppID(dittoPointer);
9586
9882
  const siteID = dittoAuthClientGetSiteID(dittoPointer);
9587
9883
  Bridge.ditto.bridge(dittoPointer, this);
@@ -9753,11 +10049,13 @@ class Ditto {
9753
10049
  const path = require('path');
9754
10050
  if (process.platform === 'win32') {
9755
10051
  // Normalize the path on Windows to prevent issues with its max path
9756
- // length. Windows has a hard limit at 260 characters for file paths
9757
- // [1]. When this limit is exceeded reads and writes may fail.
10052
+ // length (if needed). Windows has a hard limit at 260 characters
10053
+ // for file paths [1]. When this limit is exceeded reads and writes
10054
+ // may fail.
9758
10055
  //
9759
10056
  // [1]: https://docs.microsoft.com/en-us/windows/win32/fileio/naming-a-file#maximum-path-length-limitation
9760
- validatedPath = `\\\\?\\${path.resolve(validatedPath)}`;
10057
+ if (!validatedPath.startsWith('\\\\?\\'))
10058
+ validatedPath = `\\\\?\\${path.resolve(validatedPath)}`;
9761
10059
  }
9762
10060
  const absolutePath = path.resolve(validatedPath);
9763
10061
  // We check if the directory exists before creating it to be able to
@@ -10227,6 +10525,58 @@ const isDirectoryWritable = (directoryPath) => {
10227
10525
  return true;
10228
10526
  };
10229
10527
 
10528
+ //
10529
+ // Copyright © 2025 DittoLive Incorporated. All rights reserved.
10530
+ //
10531
+ /**
10532
+ * Represents a diff between two arrays.
10533
+ *
10534
+ * Create a diff between arrays of {@link QueryResultItem} using a {@link Differ}.
10535
+ */
10536
+ class Diff {
10537
+ constructor(cborData) {
10538
+ const object = CBOR.decode(cborData);
10539
+ this.insertions = object.insertions;
10540
+ this.deletions = object.deletions;
10541
+ this.updates = object.updates;
10542
+ this.moves = object.moves.map(([from, to]) => ({ from, to }));
10543
+ }
10544
+ }
10545
+ /**
10546
+ * Calculates diffs between arrays of {@link QueryResultItem}.
10547
+ *
10548
+ * Use a {@link Differ} with a {@link StoreObserver} to get the diff between
10549
+ * subsequent query results delivered by the store observer.
10550
+ */
10551
+ class Differ {
10552
+ /** Create a new differ. */
10553
+ constructor() {
10554
+ const ffiDiffer = differNew();
10555
+ return Bridge.differ.bridge(ffiDiffer, this);
10556
+ }
10557
+ /**
10558
+ * Calculate the diff of the provided items against the last set of items that
10559
+ * were passed to this differ.
10560
+ *
10561
+ * The returned {@link Diff} identifies changes from the old array of items
10562
+ * to the new array of items using indices into both arrays.
10563
+ *
10564
+ * Initially, the differ has no items, so the first call to this method will
10565
+ * always return a diff showing all items as insertions.
10566
+ *
10567
+ * The identity of items is determined by their `_id` field.
10568
+ */
10569
+ diff(items) {
10570
+ const pointers = items.map((item) => item.deref());
10571
+ const cborData = differDiff(this.deref(), pointers);
10572
+ return new Diff(cborData);
10573
+ }
10574
+ /** @internal */
10575
+ deref() {
10576
+ return Bridge.differ.handleFor(this).deref();
10577
+ }
10578
+ }
10579
+
10230
10580
  //
10231
10581
  // Copyright © 2023 DittoLive Incorporated. All rights reserved.
10232
10582
  //
@@ -10314,8 +10664,7 @@ class QueryResultItem {
10314
10664
  * method once and keep it for as long as needed.
10315
10665
  */
10316
10666
  cborData() {
10317
- const resultItemHandle = Bridge.queryResultItem.handleFor(this);
10318
- return queryResultItemCBOR(resultItemHandle.deref());
10667
+ return queryResultItemCBOR(this.deref());
10319
10668
  }
10320
10669
  /**
10321
10670
  * Returns the content of the item as a JSON string.
@@ -10324,8 +10673,7 @@ class QueryResultItem {
10324
10673
  * method once and keep it for as long as needed.
10325
10674
  */
10326
10675
  jsonString() {
10327
- const resultItemHandle = Bridge.queryResultItem.handleFor(this);
10328
- return queryResultItemJSON(resultItemHandle.deref());
10676
+ return queryResultItemJSON(this.deref());
10329
10677
  }
10330
10678
  // ----------------------------------------------------------- Internal ------
10331
10679
  /**
@@ -10339,6 +10687,15 @@ class QueryResultItem {
10339
10687
  }
10340
10688
  /** @internal */
10341
10689
  constructor() { }
10690
+ /** @internal */
10691
+ static fromJSON(jsonData) {
10692
+ const encodedData = new TextEncoder().encode(jsonData);
10693
+ return Bridge.queryResultItem.bridge(queryResultItemNew(encodedData));
10694
+ }
10695
+ /** @internal */
10696
+ deref() {
10697
+ return Bridge.queryResultItem.handleFor(this).deref();
10698
+ }
10342
10699
  }
10343
10700
 
10344
10701
  /**
@@ -10375,6 +10732,8 @@ Bridge.attachment.registerType(Attachment);
10375
10732
  Bridge.connectionRequest.registerType(ConnectionRequest);
10376
10733
  Bridge.document.registerType(Document);
10377
10734
  Bridge.queryResultItem.registerType(QueryResultItem);
10735
+ Bridge.differ.registerType(Differ);
10736
+ Bridge.transaction.registerType(Transaction);
10378
10737
  Bridge.queryResult.registerType(QueryResult);
10379
10738
  Bridge.mutableDocument.registerType(MutableDocument);
10380
10739
  Bridge.ditto.registerType(Ditto);
@@ -10391,6 +10750,8 @@ exports.Collection = Collection;
10391
10750
  exports.CollectionsEvent = CollectionsEvent;
10392
10751
  exports.ConnectionRequest = ConnectionRequest;
10393
10752
  exports.Counter = Counter;
10753
+ exports.Diff = Diff;
10754
+ exports.Differ = Differ;
10394
10755
  exports.Ditto = Ditto;
10395
10756
  exports.DittoError = DittoError;
10396
10757
  exports.Document = Document;
@@ -10424,6 +10785,8 @@ exports.StoreObserver = StoreObserver;
10424
10785
  exports.Subscription = Subscription;
10425
10786
  exports.Sync = Sync;
10426
10787
  exports.SyncSubscription = SyncSubscription;
10788
+ exports.Transaction = Transaction;
10789
+ exports.TransactionInfo = TransactionInfo;
10427
10790
  exports.TransportConfig = TransportConfig;
10428
10791
  exports.UpdateResult = UpdateResult;
10429
10792
  exports.UpdateResultsMap = UpdateResultsMap;