@dittolive/ditto 4.0.3-alpha.linux-ble-fixes-2 → 4.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/node/ditto.cjs.js CHANGED
@@ -84,7 +84,7 @@ class Observer {
84
84
  * method. Otherwise returns `false`.
85
85
  */
86
86
  get isStopped() {
87
- return typeof this.token === 'undefined';
87
+ return this.observerManager.hasObserver(this.token);
88
88
  }
89
89
  /**
90
90
  * Stops the observation. Calling this method multiple times has no effect.
@@ -292,21 +292,16 @@ function ditto_add_multicast_transport(...args) { return ditto.ditto_add_multica
292
292
  function ditto_add_static_tcp_client(...args) { return ditto.ditto_add_static_tcp_client(...args) }
293
293
  function ditto_add_subscription(...args) { return ditto.ditto_add_subscription(...args) }
294
294
  function ditto_add_websocket_client(...args) { return ditto.ditto_add_websocket_client(...args) }
295
- function ditto_auth_client_free(...args) { return ditto.ditto_auth_client_free(...args) }
296
295
  function ditto_auth_client_get_site_id(...args) { return ditto.ditto_auth_client_get_site_id(...args) }
297
296
  function ditto_auth_client_is_web_valid(...args) { return ditto.ditto_auth_client_is_web_valid(...args) }
298
297
  function ditto_auth_client_is_x509_valid(...args) { return ditto.ditto_auth_client_is_x509_valid(...args) }
299
298
  function ditto_auth_client_login_with_credentials(...args) { return ditto.ditto_auth_client_login_with_credentials(...args) }
300
299
  function ditto_auth_client_login_with_token(...args) { return ditto.ditto_auth_client_login_with_token(...args) }
301
300
  function ditto_auth_client_logout(...args) { return ditto.ditto_auth_client_logout(...args) }
302
- function ditto_auth_client_make_anonymous_client(...args) { return ditto.ditto_auth_client_make_anonymous_client(...args) }
303
- function ditto_auth_client_make_for_development(...args) { return ditto.ditto_auth_client_make_for_development(...args) }
304
301
  function ditto_auth_client_make_login_provider(...args) { return ditto.ditto_auth_client_make_login_provider(...args) }
305
- function ditto_auth_client_make_with_shared_key(...args) { return ditto.ditto_auth_client_make_with_shared_key(...args) }
306
- function ditto_auth_client_make_with_static_x509(...args) { return ditto.ditto_auth_client_make_with_static_x509(...args) }
307
- function ditto_auth_client_make_with_web(...args) { return ditto.ditto_auth_client_make_with_web(...args) }
308
302
  function ditto_auth_client_set_validity_listener(...args) { return ditto.ditto_auth_client_set_validity_listener(...args) }
309
303
  function ditto_auth_client_user_id(...args) { return ditto.ditto_auth_client_user_id(...args) }
304
+ function ditto_auth_set_login_provider(...args) { return ditto.ditto_auth_set_login_provider(...args) }
310
305
  function ditto_cancel_resolve_attachment(...args) { return ditto.ditto_cancel_resolve_attachment(...args) }
311
306
  function ditto_clear_presence_callback(...args) { return ditto.ditto_clear_presence_callback(...args) }
312
307
  function ditto_clear_presence_v3_callback(...args) { return ditto.ditto_clear_presence_v3_callback(...args) }
@@ -337,6 +332,11 @@ function ditto_free_attachment_handle(...args) { return ditto.ditto_free_attachm
337
332
  function ditto_get_collection_names(...args) { return ditto.ditto_get_collection_names(...args) }
338
333
  function ditto_get_complete_attachment_path(...args) { return ditto.ditto_get_complete_attachment_path(...args) }
339
334
  function ditto_get_sdk_version(...args) { return ditto.ditto_get_sdk_version(...args) }
335
+ function ditto_identity_config_make_manual_v0(...args) { return ditto.ditto_identity_config_make_manual_v0(...args) }
336
+ function ditto_identity_config_make_offline_playground(...args) { return ditto.ditto_identity_config_make_offline_playground(...args) }
337
+ function ditto_identity_config_make_online_playground(...args) { return ditto.ditto_identity_config_make_online_playground(...args) }
338
+ function ditto_identity_config_make_online_with_authentication(...args) { return ditto.ditto_identity_config_make_online_with_authentication(...args) }
339
+ function ditto_identity_config_make_shared_key(...args) { return ditto.ditto_identity_config_make_shared_key(...args) }
340
340
  function ditto_init_sdk_version(...args) { return ditto.ditto_init_sdk_version(...args) }
341
341
  function ditto_live_query_register_str_detached(...args) { return ditto.ditto_live_query_register_str_detached(...args) }
342
342
  function ditto_live_query_signal_available_next(...args) { return ditto.ditto_live_query_signal_available_next(...args) }
@@ -370,6 +370,7 @@ function ditto_run_garbage_collection(...args) { return ditto.ditto_run_garbage_
370
370
  function ditto_set_connect_retry_interval(...args) { return ditto.ditto_set_connect_retry_interval(...args) }
371
371
  function ditto_set_device_name(...args) { return ditto.ditto_set_device_name(...args) }
372
372
  function ditto_set_sync_group(...args) { return ditto.ditto_set_sync_group(...args) }
373
+ function ditto_shutdown(...args) { return ditto.ditto_shutdown(...args) }
373
374
  function ditto_start_http_server(...args) { return ditto.ditto_start_http_server(...args) }
374
375
  function ditto_start_tcp_server(...args) { return ditto.ditto_start_tcp_server(...args) }
375
376
  function ditto_stop_http_server(...args) { return ditto.ditto_stop_http_server(...args) }
@@ -378,10 +379,12 @@ function ditto_validate_document_id(...args) { return ditto.ditto_validate_docum
378
379
  function ditto_write_transaction(...args) { return ditto.ditto_write_transaction(...args) }
379
380
  function ditto_write_transaction_commit(...args) { return ditto.ditto_write_transaction_commit(...args) }
380
381
  function ditto_write_transaction_rollback(...args) { return ditto.ditto_write_transaction_rollback(...args) }
382
+ function getDeadlockTimeout$1(...args) { return ditto.getDeadlockTimeout(...args) }
381
383
  function jsDocsToCDocs(...args) { return ditto.jsDocsToCDocs(...args) }
382
384
  function mdns_client_free_handle(...args) { return ditto.mdns_client_free_handle(...args) }
383
385
  function mdns_server_free_handle(...args) { return ditto.mdns_server_free_handle(...args) }
384
386
  function refCStringToString(...args) { return ditto.refCStringToString(...args) }
387
+ function setDeadlockTimeout$1(...args) { return ditto.setDeadlockTimeout(...args) }
385
388
  function static_tcp_client_free_handle(...args) { return ditto.static_tcp_client_free_handle(...args) }
386
389
  function uninitialized_ditto_make(...args) { return ditto.uninitialized_ditto_make(...args) }
387
390
  function verify_license(...args) { return ditto.verify_license(...args) }
@@ -537,7 +540,7 @@ async function collectionGet(ditto, collectionName, documentID, readTransaction)
537
540
  return document;
538
541
  }
539
542
  /** @internal */
540
- async function collectionInsertValue(ditto, collectionName, doc_cbor, doc_id, writeStrategy, writeTransaction) {
543
+ async function collectionInsertValue(ditto, collectionName, doc_cbor, writeStrategy, writeTransaction) {
541
544
  ensureInitialized();
542
545
  // REFACTOR: add proper error handling.
543
546
  const collectionNameX = bytesFromString(collectionName);
@@ -555,7 +558,7 @@ async function collectionInsertValue(ditto, collectionName, doc_cbor, doc_id, wr
555
558
  default:
556
559
  throw new Error('Invalid write strategy provided');
557
560
  }
558
- const { status_code: errorCode, id } = await ditto_collection_insert_value(ditto, collectionNameX, doc_cbor, doc_id, strategy, null, writeTransaction !== null && writeTransaction !== void 0 ? writeTransaction : null);
561
+ const { status_code: errorCode, id } = await ditto_collection_insert_value(ditto, collectionNameX, doc_cbor, strategy, null, writeTransaction !== null && writeTransaction !== void 0 ? writeTransaction : null);
559
562
  if (errorCode !== 0)
560
563
  throw new Error(errorMessage() || `ditto_collection_insert_value() failed with error code: ${errorCode}`);
561
564
  return boxCBytesIntoBuffer(id);
@@ -649,9 +652,10 @@ onError) {
649
652
  ensureInitialized();
650
653
  const collectionNameBuffer = bytesFromString(collectionName);
651
654
  const queryBuffer = bytesFromString(query);
652
- // Note(Daniel): the callback is now registered to be called in a detached manner:
653
- // if the FFI / Rust does `cb()`, then when that call returns, the js callback itself may not
654
- // have completed. This is fine, since `signalNext()` shall be the proper way to achieve this.
655
+ // Note(Daniel): the callback is now registered to be called in a detached
656
+ // manner: if the FFI / Rust does `cb()`, then when that call returns, the js
657
+ // callback itself may not have completed. This is fine, since `signalNext()`
658
+ // shall be the proper way to achieve this.
655
659
  const { status_code: errorCode, i64: id } = ditto_live_query_register_str_detached(ditto, collectionNameBuffer, queryBuffer, queryArgsCBOR, orderBy, limit, offset, wrapBackgroundCbForFFI(onError, eventHandler));
656
660
  if (errorCode !== 0)
657
661
  throw new Error(errorMessage() || `\`ditto_live_query_register_str()\` failed with error code: ${errorCode}`);
@@ -823,103 +827,94 @@ function log(level, message) {
823
827
  }
824
828
  // ----------------------------------------------------------- AuthClient ------
825
829
  /** @internal */
826
- function dittoAuthClientMakeAnonymousClient(path, appID, sharedToken, baseURL) {
830
+ function dittoIdentityConfigMakeOnlinePlayground(appID, sharedToken, baseURL) {
827
831
  ensureInitialized();
828
- const pathX = bytesFromString(path);
829
832
  const appIDX = bytesFromString(appID);
830
833
  const sharedTokenX = bytesFromString(sharedToken);
831
834
  const baseURLX = bytesFromString(baseURL);
832
- const { status_code: errorCode, auth_client: authClient } = ditto_auth_client_make_anonymous_client(pathX, appIDX, sharedTokenX, baseURLX);
835
+ const { status_code: errorCode, identity_config: identityConfig } = ditto_identity_config_make_online_playground(appIDX, sharedTokenX, baseURLX);
833
836
  if (errorCode !== 0)
834
- throw new Error(errorMessage() || `ditto_auth_client_make_anonymous_client() failed with error code: ${errorCode}`);
835
- return authClient;
837
+ throw new Error(errorMessage() || `ditto_identity_config_make_online_playground() failed with error code: ${errorCode}`);
838
+ return identityConfig;
836
839
  }
837
840
  /** @internal */
838
- function dittoAuthClientMakeWithWeb(path, appID, baseURL, loginProvider) {
841
+ function dittoIdentityConfigMakeOnlineWithAuthentication(appID, baseURL) {
839
842
  ensureInitialized();
840
- const pathX = bytesFromString(path);
841
843
  const appIDX = bytesFromString(appID);
842
844
  const baseURLX = bytesFromString(baseURL);
843
- const { status_code: errorCode, auth_client: authClient } = ditto_auth_client_make_with_web(pathX, appIDX, baseURLX, loginProvider);
845
+ const { status_code: errorCode, identity_config: identityConfig } = ditto_identity_config_make_online_with_authentication(appIDX, baseURLX);
844
846
  if (errorCode !== 0)
845
- throw new Error(errorMessage() || `ditto_auth_client_make_with_web() failed with error code: ${errorCode}`);
846
- return authClient;
847
+ throw new Error(errorMessage() || `ditto_identity_config_make_online_with_authentication() failed with error code: ${errorCode}`);
848
+ return identityConfig;
847
849
  }
848
850
  /** @internal */
849
- function dittoAuthClientMakeForDevelopment(path, appId, siteID) {
851
+ function dittoIdentityConfigMakeOfflinePlayground(appId, siteID) {
850
852
  ensureInitialized();
851
- const pathX = bytesFromString(path);
852
853
  const appIdX = bytesFromString(appId);
853
854
  const siteIDX = Number(siteID);
854
- const { status_code: errorCode, auth_client: authClient } = ditto_auth_client_make_for_development(pathX, appIdX, siteIDX);
855
+ const { status_code: errorCode, identity_config: identityConfig } = ditto_identity_config_make_offline_playground(appIdX, siteIDX);
855
856
  if (errorCode !== 0)
856
- throw new Error(errorMessage() || `ditto_auth_client_make_for_development() failed with error code: ${errorCode}`);
857
- return authClient;
857
+ throw new Error(errorMessage() || `ditto_identity_config_make_offline_playground() failed with error code: ${errorCode}`);
858
+ return identityConfig;
858
859
  }
859
860
  /** @internal */
860
- function dittoAuthClientMakeWithSharedKey(path, appId, sharedKey, siteID) {
861
+ function dittoIdentityConfigMakeSharedKey(appId, sharedKey, siteID) {
861
862
  ensureInitialized();
862
- const pathX = bytesFromString(path);
863
863
  const appIdX = bytesFromString(appId);
864
864
  const sharedKeyX = bytesFromString(sharedKey);
865
865
  const siteIDX = Number(siteID);
866
- const { status_code: errorCode, auth_client: authClient } = ditto_auth_client_make_with_shared_key(pathX, appIdX, sharedKeyX, siteIDX);
866
+ const { status_code: errorCode, identity_config: identityConfig } = ditto_identity_config_make_shared_key(appIdX, sharedKeyX, siteIDX);
867
867
  if (errorCode !== 0)
868
- throw new Error(errorMessage() || `ditto_auth_client_make_with_shared_key() failed with error code: ${errorCode}`);
869
- return authClient;
868
+ throw new Error(errorMessage() || `ditto_identity_config_make_shared_key() failed with error code: ${errorCode}`);
869
+ return identityConfig;
870
870
  }
871
871
  /** @internal */
872
- function dittoAuthClientMakeWithStaticX509(configCBORBase64) {
872
+ function dittoIdentityConfigMakeManual(configCBORBase64) {
873
873
  ensureInitialized();
874
874
  const configCBORBase64X = bytesFromString(configCBORBase64);
875
- const { status_code: errorCode, auth_client: authClient } = ditto_auth_client_make_with_static_x509(configCBORBase64X);
875
+ const { status_code: errorCode, identity_config: identityConfig } = ditto_identity_config_make_manual_v0(configCBORBase64X);
876
876
  if (errorCode !== 0)
877
- throw new Error(errorMessage() || `ditto_auth_client_make_with_static_x509() failed with error code: ${errorCode}`);
878
- return authClient;
877
+ throw new Error(errorMessage() || `ditto_identity_config_make_manual_v0() failed with error code: ${errorCode}`);
878
+ return identityConfig;
879
879
  }
880
880
  /** @internal */
881
- function dittoAuthClientGetSiteID(authClient) {
881
+ function dittoAuthClientGetSiteID(ditto) {
882
882
  ensureInitialized();
883
- return ditto_auth_client_get_site_id(authClient);
883
+ return ditto_auth_client_get_site_id(ditto);
884
884
  }
885
- /** @internal */
886
- function dittoAuthClientFree(authClient) {
887
- ensureInitialized();
888
- return ditto_auth_client_free(authClient);
889
- }
890
- function dittoAuthClientIsWebValid(authClient) {
885
+ function dittoAuthClientIsWebValid(ditto) {
891
886
  ensureInitialized();
892
- return ditto_auth_client_is_web_valid(authClient) !== 0;
887
+ return ditto_auth_client_is_web_valid(ditto) !== 0;
893
888
  }
894
- function dittoAuthClientUserID(authClient) {
889
+ function dittoAuthClientUserID(ditto) {
895
890
  ensureInitialized();
896
- const cStr = ditto_auth_client_user_id(authClient);
891
+ const cStr = ditto_auth_client_user_id(ditto);
897
892
  return boxCStringIntoString(cStr);
898
893
  }
899
- function dittoAuthClientIsX509Valid(authClient) {
894
+ function dittoAuthClientIsX509Valid(ditto) {
900
895
  ensureInitialized();
901
- return ditto_auth_client_is_x509_valid(authClient) !== 0;
896
+ return ditto_auth_client_is_x509_valid(ditto) !== 0;
902
897
  }
903
- async function dittoAuthClientLoginWithToken(authClient, token, provider) {
898
+ async function dittoAuthClientLoginWithToken(ditto, token, provider) {
904
899
  ensureInitialized();
905
900
  const tokenBytes = bytesFromString(token);
906
901
  const providerBytes = bytesFromString(provider);
907
- const errorCode = await ditto_auth_client_login_with_token(authClient, tokenBytes, providerBytes);
902
+ const errorCode = await ditto_auth_client_login_with_token(ditto, tokenBytes, providerBytes);
908
903
  if (errorCode !== 0)
909
904
  throw new Error(errorMessage() || `Ditto failed to authenticate (error code: ${errorCode}).`);
910
905
  }
911
- async function dittoAuthClientLoginWithUsernameAndPassword(authClient, username, password, provider) {
906
+ async function dittoAuthClientLoginWithUsernameAndPassword(ditto, username, password, provider) {
912
907
  ensureInitialized();
913
908
  const usernameBytes = bytesFromString(username);
914
909
  const passwordBytes = bytesFromString(password);
915
910
  const providerBytes = bytesFromString(provider);
916
- const errorCode = await ditto_auth_client_login_with_credentials(authClient, usernameBytes, passwordBytes, providerBytes);
911
+ const errorCode = await ditto_auth_client_login_with_credentials(ditto, usernameBytes, passwordBytes, providerBytes);
917
912
  if (errorCode !== 0)
918
913
  throw new Error(errorMessage() || `Ditto failed to authenticate (error code: ${errorCode}).`);
919
914
  }
920
- async function dittoAuthClientLogout(authClient) {
915
+ async function dittoAuthClientLogout(ditto) {
921
916
  ensureInitialized();
922
- const errorCode = await ditto_auth_client_logout(authClient);
917
+ const errorCode = await ditto_auth_client_logout(ditto);
923
918
  if (errorCode !== 0)
924
919
  throw new Error(errorMessage() || `Ditto failed to logout (error code: ${errorCode}).`);
925
920
  }
@@ -931,9 +926,9 @@ function uninitializedDittoMake(path) {
931
926
  return uninitialized_ditto_make(pathX);
932
927
  }
933
928
  /** @internal */
934
- function dittoMake(uninitializedDitto, authClient) {
929
+ function dittoMake(uninitializedDitto, identityConfig) {
935
930
  ensureInitialized();
936
- return ditto_make(uninitializedDitto, authClient, 'Disabled');
931
+ return ditto_make(uninitializedDitto, identityConfig, 'Disabled');
937
932
  }
938
933
  /** @internal */
939
934
  async function dittoGetCollectionNames(self) {
@@ -956,17 +951,27 @@ function dittoFree(self) {
956
951
  return ditto_free(self);
957
952
  }
958
953
  /** @internal */
954
+ function getDeadlockTimeout() {
955
+ ensureInitialized();
956
+ return getDeadlockTimeout$1();
957
+ }
958
+ /** @internal */
959
+ function setDeadlockTimeout(duration) {
960
+ ensureInitialized();
961
+ setDeadlockTimeout$1(duration);
962
+ }
963
+ /** @internal */
959
964
  async function dittoRegisterPresenceV1Callback(self, cb) {
960
965
  ensureInitialized();
961
- if (cb) {
962
- ditto_register_presence_v1_callback(self, wrapBackgroundCbForFFI((err) => console.error(`The registered presence callback errored with ${err}`), (cJsonStr) => {
963
- const jsonStr = refCStringToString(cJsonStr);
964
- cb(jsonStr);
965
- }));
966
- }
967
- else {
968
- await ditto_clear_presence_callback(self);
969
- }
966
+ ditto_register_presence_v1_callback(self, wrapBackgroundCbForFFI((err) => console.error(`The registered presence callback v1 errored with ${err}`), (cJsonStr) => {
967
+ const jsonStr = refCStringToString(cJsonStr);
968
+ cb(jsonStr);
969
+ }));
970
+ }
971
+ /** @internal */
972
+ async function dittoClearPresenceCallback(self) {
973
+ ensureInitialized();
974
+ await ditto_clear_presence_callback(self);
970
975
  }
971
976
  /** @internal */
972
977
  async function dittoRegisterPresenceV3Callback(self, cb) {
@@ -982,11 +987,6 @@ async function dittoClearPresenceV3Callback(self) {
982
987
  ditto_clear_presence_v3_callback(self);
983
988
  }
984
989
  /** @internal */
985
- async function dittoClearPresenceCallback(self) {
986
- ensureInitialized();
987
- await ditto_clear_presence_callback(self);
988
- }
989
- /** @internal */
990
990
  function dittoRegisterTransportConditionChangedCallback(self, cb) {
991
991
  ensureInitialized();
992
992
  if (!cb) {
@@ -1100,6 +1100,11 @@ function dittoStopTCPServer(dittoPointer) {
1100
1100
  return ditto_stop_tcp_server(dittoPointer);
1101
1101
  }
1102
1102
  /** @internal */
1103
+ async function dittoShutdown(dittoPointer) {
1104
+ ensureInitialized();
1105
+ return await ditto_shutdown(dittoPointer);
1106
+ }
1107
+ /** @internal */
1103
1108
  function dittoAddMulticastTransport(dittoPointer) {
1104
1109
  ensureInitialized();
1105
1110
  return ditto_add_multicast_transport(dittoPointer);
@@ -1154,6 +1159,11 @@ function documentsHashMnemonic(documents) {
1154
1159
  return boxCStringIntoString(c_string);
1155
1160
  }
1156
1161
  /** @internal */
1162
+ async function dittoAuthSetLoginProvider(ditto, loginProvider) {
1163
+ ensureInitialized();
1164
+ return await ditto_auth_set_login_provider(ditto, loginProvider);
1165
+ }
1166
+ /** @internal */
1157
1167
  function dittoAuthClientMakeLoginProvider(expiringCb,
1158
1168
  // Cb may be called in parallel at any point, so let's use an optional error handler
1159
1169
  onError) {
@@ -1161,14 +1171,14 @@ onError) {
1161
1171
  return ditto_auth_client_make_login_provider(wrapBackgroundCbForFFI(onError, expiringCb));
1162
1172
  }
1163
1173
  /** @internal */
1164
- function dittoAuthClientSetValidityListener(authClient, validityUpdateCb,
1174
+ function dittoAuthClientSetValidityListener(ditto, validityUpdateCb,
1165
1175
  // Cb may be called in parallel at any point, so let's use an optional error handler
1166
1176
  onError) {
1167
1177
  ensureInitialized();
1168
1178
  const validityUpdateRawCb = wrapBackgroundCbForFFI(onError, function (isWebValidInt, isX509ValidInt) {
1169
1179
  return validityUpdateCb(isWebValidInt === 1, isX509ValidInt === 1);
1170
1180
  });
1171
- return ditto_auth_client_set_validity_listener(authClient, validityUpdateRawCb);
1181
+ return ditto_auth_client_set_validity_listener(ditto, validityUpdateRawCb);
1172
1182
  }
1173
1183
  // ---------------------------------------------------------------- Other ------
1174
1184
  /** @internal */
@@ -1442,7 +1452,7 @@ function awdlDestroy(awdl) {
1442
1452
 
1443
1453
  // NOTE: this is patched up with the actual build version by Jake task
1444
1454
  // build:package and has to be a valid semantic version as defined here: https://semver.org.
1445
- const fullBuildVersionString = '4.0.3-alpha.linux-ble-fixes-2';
1455
+ const fullBuildVersionString = '4.1.1';
1446
1456
 
1447
1457
  //
1448
1458
  /**
@@ -2220,19 +2230,111 @@ function validateDocumentIDCBOR(idCBOR) {
2220
2230
  const DEBUG_TYPE_NAMES = [];
2221
2231
  const DEBUG_ALL_TYPES = false;
2222
2232
  /**
2223
- * For every object registered in a bridge, a {@link Meta} object is stored in
2224
- * the bridge instance to help with debugging and testing.
2233
+ * A handle serves as a safe wrapper around a pointer to a native object.
2234
+ *
2235
+ * A bridge keeps track of all handles that have been created on its
2236
+ * {@link Bridge.handlesByAddress | `handlesByAddress`} property, which allows
2237
+ * enumerating all objects that are currently managed by the bridge.
2225
2238
  *
2226
2239
  * @internal */
2227
- class Meta {
2228
- /** @internal */
2229
- constructor(type, object, pointer) {
2230
- this.type = type;
2231
- this.object = object;
2232
- this.pointer = pointer;
2240
+ class Handle {
2241
+ /**
2242
+ * Warning: Do not call this constructor directly. Use
2243
+ * {@link Bridge.handleFor | `Bridge.<type>.handleFor()`} instead.
2244
+ *
2245
+ * @internal */
2246
+ constructor(bridge, object) {
2247
+ this.isClosed = false;
2248
+ this.bridge = bridge;
2249
+ this.objectWeakRef = new WeakRef(object);
2250
+ }
2251
+ /** The type of this handle's bridge */
2252
+ get type() {
2253
+ return this.bridge.type;
2254
+ }
2255
+ /**
2256
+ * Returns the pointer associated with this handle.
2257
+ *
2258
+ * @throws {Error} if the object has already been closed, garbage collected,
2259
+ * or unregistered from the bridge.
2260
+ */
2261
+ deref() {
2262
+ const object = this.objectWeakRef.deref();
2263
+ if (object == null) {
2264
+ throw new Error(`Bridging error: can't get pointer for an object that has been garbage collected.`);
2265
+ }
2266
+ if (this.isClosed) {
2267
+ throw new Error(`Bridging error: can't get pointer for an object that has been closed.`);
2268
+ }
2269
+ // Instead of storing the pointer in the handle, we look it up in the
2270
+ // bridge's pointer map to avoid storing the pointer twice.
2271
+ const pointer = this.bridge.rawPointerFor(object);
2272
+ if (pointer == null) {
2273
+ throw new Error(`Bridging error: ${this.type.name} object is not currently registered in this bridge.`);
2274
+ }
2275
+ return pointer;
2276
+ }
2277
+ /**
2278
+ * Returns the pointer associated with this handle or `null` if the object
2279
+ * has been closed, garbage collected, or unregistered.
2280
+ */
2281
+ derefOrNull() {
2282
+ const object = this.objectWeakRef.deref();
2283
+ if (object == null) {
2284
+ return null;
2285
+ }
2286
+ if (this.isClosed) {
2287
+ return null;
2288
+ }
2289
+ const pointer = this.bridge.rawPointerFor(object);
2290
+ return pointer !== null && pointer !== void 0 ? pointer : null;
2291
+ }
2292
+ /**
2293
+ * Returns the object associated with this handle.
2294
+ *
2295
+ * @throws {Error} if the object has already been garbage collected,
2296
+ * closed or unregistered.
2297
+ */
2298
+ object() {
2299
+ const object = this.objectWeakRef.deref();
2300
+ if (object == null) {
2301
+ throw new Error(`Bridging error: ${this.bridge.type.name} object has already been garbage collected.`);
2302
+ }
2303
+ if (this.isClosed) {
2304
+ throw new Error(`Bridging error: ${this.bridge.type.name} object has already been closed.`);
2305
+ }
2306
+ return object;
2307
+ }
2308
+ /**
2309
+ * Returns the object associated with this handle or `null` if the object
2310
+ * has already been garbage collected.
2311
+ */
2312
+ objectOrNull() {
2313
+ var _b;
2314
+ if (this.isClosed) {
2315
+ throw new Error(`Bridging error: ${this.bridge.type.name} object has already been closed.`);
2316
+ }
2317
+ return (_b = this.objectWeakRef.deref()) !== null && _b !== void 0 ? _b : null;
2233
2318
  }
2234
2319
  toString() {
2235
- return `{ Meta | type: ${this.type.name}, object: ${this.object.deref()}, FFI address: ${this.pointer.addr}, FFI type: ${this.pointer.type} }`;
2320
+ const pointer = this.derefOrNull();
2321
+ return `{ Handle | type: ${this.bridge.type}, object: ${this.objectWeakRef.deref()}, FFI address: ${pointer === null || pointer === void 0 ? void 0 : pointer.addr}, FFI type: ${pointer === null || pointer === void 0 ? void 0 : pointer.type} }`;
2322
+ }
2323
+ }
2324
+ /**
2325
+ * Use this for passing arrays of pointers to the FFI.
2326
+ */
2327
+ // REFACTOR: Use an iterator instead of an array to deref handles lazily.
2328
+ class Handles {
2329
+ /**
2330
+ * @throws {Error} if any of the objects are not registered in the bridge.
2331
+ * @throws {Error} if any of the objects have already been garbage collected.
2332
+ */
2333
+ constructor(bridge, objects) {
2334
+ this.handles = objects.map((object) => bridge.handleFor(object));
2335
+ }
2336
+ deref() {
2337
+ return this.handles.map((handle) => handle.deref());
2236
2338
  }
2237
2339
  }
2238
2340
  /**
@@ -2254,9 +2356,9 @@ class Meta {
2254
2356
  * - {@link StaticTCPClient}: `Bridge.staticTCPClient`
2255
2357
  * - {@link WebsocketClient}: `Bridge.websocketClient`
2256
2358
  *
2257
- * Use `Bridge.<type>.pointerFor()` to lookup the pointer for a given object and
2258
- * `Bridge.<type>.bridge()` to get or create the matching object for a memory
2259
- * address.
2359
+ * Use `Bridge.<type>.handleFor()` to obtain a handle, which is a wrapper around
2360
+ * the raw pointer, and `Bridge.<type>.bridge()` to get or create the matching
2361
+ * object for a memory address.
2260
2362
  *
2261
2363
  * @internal */
2262
2364
  class Bridge {
@@ -2279,12 +2381,12 @@ class Bridge {
2279
2381
  /**
2280
2382
  * Look up a pointer given its object.
2281
2383
  *
2282
- * As `WeakMap` does not allow for enumeration, use `this.metasByAddress` to
2384
+ * As `WeakMap` does not allow for enumeration, use `this.handlesByAddress` to
2283
2385
  * iterate over all pointers.
2284
2386
  */
2285
2387
  this.pointerByObject = new WeakMap();
2286
2388
  this.release = release;
2287
- this.metasByAddress = {};
2389
+ this.handlesByAddress = {};
2288
2390
  this.finalizationRegistry = new FinalizationRegistry(this.finalize.bind(this));
2289
2391
  Bridge.all.push(new WeakRef(this));
2290
2392
  }
@@ -2314,20 +2416,32 @@ class Bridge {
2314
2416
  this.internalType = value;
2315
2417
  }
2316
2418
  /**
2317
- * Returns the FFI pointer for `object`.
2419
+ * Returns the handle for a bridged object.
2318
2420
  *
2319
- * Throws an exception if object is currently not registered, which can be the
2320
- * case when object has never been registered or when it has already been
2321
- * unregistered.
2421
+ * Use `handle.deref()` to get the pointer for the object at the time of use.
2422
+ *
2423
+ * @throws {Error} if the object is not registered.
2322
2424
  *
2323
2425
  * @internal
2324
2426
  */
2325
- pointerFor(object) {
2326
- const val = this.pointerByObject.get(object);
2327
- if (val == null) {
2427
+ handleFor(object) {
2428
+ const pointer = this.pointerByObject.get(object);
2429
+ if (pointer == null) {
2328
2430
  throw new Error(`Bridging error: ${this.type.name} object is not currently registered in this bridge.`);
2329
2431
  }
2330
- return val;
2432
+ const handle = this.handlesByAddress[pointer.addr];
2433
+ if (handle == null) {
2434
+ throw new Error(`Internal inconsistency, found a pointer for an object whose handle is undefined: ${pointer}`);
2435
+ }
2436
+ return handle;
2437
+ }
2438
+ /**
2439
+ * Returns a `Handles` instance for an array of objects.
2440
+ *
2441
+ * @internal
2442
+ */
2443
+ handlesFor(objects) {
2444
+ return new Handles(this, objects);
2331
2445
  }
2332
2446
  /**
2333
2447
  * Convenience method, returns the object for the FFI `pointer` if registered,
@@ -2337,15 +2451,14 @@ class Bridge {
2337
2451
  * @internal
2338
2452
  */
2339
2453
  objectFor(pointer) {
2340
- const meta = this.metasByAddress[pointer.addr];
2341
- if (!meta)
2454
+ const handle = this.handlesByAddress[pointer.addr];
2455
+ if (!handle)
2342
2456
  return undefined;
2343
- if (meta.type !== this.type)
2344
- throw new Error(`Can't return object for pointer, pointer is associated with an object of type ${meta.type} but this bridge is configured for ${this.type}`);
2345
- const object = meta.object.deref();
2346
- if (!object)
2347
- throw new Error(`Internal inconsistency, found a meta entry for an object whose object is undefined (garbage collected): ${pointer}`);
2348
- return object;
2457
+ if (handle.type !== this.type)
2458
+ throw new Error(`Can't return object for pointer, pointer is associated with an object of type ${handle.type} but this bridge is configured for ${this.type}`);
2459
+ // This throws an error if the object has been garbage collected but the
2460
+ // finalizer has not been called yet.
2461
+ return handle.object();
2349
2462
  }
2350
2463
  /**
2351
2464
  * Returns the object for the FFI `pointer` if registered. Otherwise, calls
@@ -2356,6 +2469,7 @@ class Bridge {
2356
2469
  *
2357
2470
  * @param pointer reference to the FFi instance for the object
2358
2471
  * @param objectOrCreate can either be the JS object, or a function that returns the instance when called. If undefined, an object is created based on the Bridge type.
2472
+ * @throws {Error} if `objectOrCreate` is a function that returns an object that is not an instance of the bridge's type.
2359
2473
  * @internal
2360
2474
  */
2361
2475
  bridge(pointer, objectOrCreate) {
@@ -2393,26 +2507,49 @@ class Bridge {
2393
2507
  * @private */
2394
2508
  register(object, pointer) {
2395
2509
  const objectType = object.constructor;
2396
- const bridgeType = this.type;
2397
- if (object.constructor !== this.type)
2398
- throw new Error(`Can't register, bridge is configured for type ${bridgeType.name} but passed in object is of type ${objectType.name}`);
2510
+ if (objectType !== this.type)
2511
+ throw new Error(`Can't register, bridge is configured for type ${this.type.name} but passed in object is of type ${objectType.name}`);
2399
2512
  const existingPointer = this.pointerByObject.get(object);
2400
- const existingMeta = this.metasByAddress[pointer.addr];
2401
- // Check that both pointer and meta are undefined at this point.
2402
- if (existingPointer != null && existingMeta != null)
2403
- throw new Error(`Can't register, an object for the passed in pointer has previously been registered: ${existingMeta.pointer}`);
2404
- if (existingPointer != null && existingMeta == null)
2405
- throw new Error(`Internal inconsistency, trying to register an object which has an associated pointer but no meta entry: ${objectType.name} at ${existingPointer.type} ${existingPointer.addr}`);
2406
- if (existingPointer == null && existingMeta != null)
2407
- throw new Error(`Internal inconsistency, trying to register an object which has a meta entry but no associated pointer: ${objectType.name} ${object}`);
2408
- const meta = new Meta(this.type, new WeakRef(object), pointer);
2409
- this.metasByAddress[meta.pointer.addr] = meta;
2513
+ const existingHandle = this.handlesByAddress[pointer.addr];
2514
+ // Check that both pointer and handle are undefined at this point.
2515
+ if (existingPointer != null && existingHandle != null)
2516
+ throw new Error(`Can't register, an object for the passed in pointer has previously been registered: ${existingHandle.object()}`);
2517
+ if (existingPointer != null && existingHandle == null)
2518
+ throw new Error(`Internal inconsistency, trying to register an object which has an associated pointer but no handle entry: ${objectType.name} at ${existingPointer.type} ${existingPointer.addr}`);
2519
+ if (existingPointer == null && existingHandle != null)
2520
+ throw new Error(`Internal inconsistency, trying to register an object which has a handle entry but no associated pointer: ${objectType.name} ${object}`);
2521
+ const handle = new Handle(this, object);
2522
+ this.handlesByAddress[pointer.addr] = handle;
2410
2523
  this.pointerByObject.set(object, pointer);
2411
2524
  this.finalizationRegistry.register(object, pointer, object);
2412
2525
  if (DEBUG_TYPE_NAMES.includes(this.type.name) || DEBUG_ALL_TYPES) {
2413
- console.log(`[VERBOSE] Bridge REGISTERED a new instance of ${this.type.name}, current count: ${Object.keys(this.metasByAddress).length}`);
2526
+ console.log(`[VERBOSE] Bridge REGISTERED a new instance of ${this.type.name}, current count: ${Object.keys(this.handlesByAddress).length}`);
2414
2527
  }
2415
2528
  }
2529
+ /**
2530
+ * Marks the object as closed while still keeping it registered in the
2531
+ * {@link FinalizationRegistry}, such that it still gets finalized by GC.
2532
+ * Otherwise, the behavior is exactly as if the object would've been
2533
+ * unregistered.
2534
+ *
2535
+ * @internal */
2536
+ markAsClosed(object) {
2537
+ const objectType = object.constructor;
2538
+ const bridgeType = this.type;
2539
+ if (objectType !== bridgeType)
2540
+ throw new Error(`Can't unregister, bridge is configured for type ${bridgeType.name} but passed in object is of type ${objectType.name}`);
2541
+ const pointer = this.pointerByObject.get(object);
2542
+ if (pointer == null)
2543
+ throw new Error(`Can't unregister, object that has not been registered before: ${object}`);
2544
+ const handle = this.handlesByAddress[pointer.addr];
2545
+ if (handle == null)
2546
+ throw new Error(`Internal inconsistency, trying to unregister an object which has an associated pointer but no handle: ${object}`);
2547
+ if (handle.type !== bridgeType)
2548
+ throw new Error(`Internal inconsistency, trying to unregister an object that has a handle with a different type than that of the bridge: ${handle}`);
2549
+ if (handle.objectOrNull() !== object)
2550
+ throw new Error(`Internal inconsistency, trying to unregister an object whose associated handle holds a different object: ${handle}`);
2551
+ handle['isClosed'] = true;
2552
+ }
2416
2553
  /**
2417
2554
  * Removes an instance from this bridge's {@link FinalizationRegistry}.
2418
2555
  *
@@ -2429,20 +2566,18 @@ class Bridge {
2429
2566
  const pointer = this.pointerByObject.get(object);
2430
2567
  if (pointer == null)
2431
2568
  throw new Error(`Can't unregister, object that has not been registered before: ${object}`);
2432
- const meta = this.metasByAddress[pointer.addr];
2433
- if (meta == null)
2434
- throw new Error(`Internal inconsistency, trying to unregister an object which has an associated pointer but no meta entry: ${object}`);
2435
- if (meta.type !== bridgeType)
2436
- throw new Error(`Internal inconsistency, trying to unregister an object that has a meta entry with a different type than that of the bridge: ${meta}`);
2437
- if (meta.object.deref() == null)
2438
- throw new Error(`Internal inconsistency, trying to unregister an object that has a meta entry whose object is undfined (garbage collected): ${meta}`);
2439
- if (meta.object.deref() !== object)
2440
- throw new Error(`Internal inconsistency, trying to unregister an object that has a meta entry holding a different object: ${meta}`);
2569
+ const handle = this.handlesByAddress[pointer.addr];
2570
+ if (handle == null)
2571
+ throw new Error(`Internal inconsistency, trying to unregister an object which has an associated pointer but no handle: ${object}`);
2572
+ if (handle.type !== bridgeType)
2573
+ throw new Error(`Internal inconsistency, trying to unregister an object that has a handle with a different type than that of the bridge: ${handle}`);
2574
+ if (handle.objectOrNull() !== object)
2575
+ throw new Error(`Internal inconsistency, trying to unregister an object whose associated handle holds a different object: ${handle}`);
2441
2576
  this.finalizationRegistry.unregister(object);
2442
- delete this.metasByAddress[pointer.addr];
2577
+ delete this.handlesByAddress[pointer.addr];
2443
2578
  this.pointerByObject.delete(object);
2444
2579
  if (DEBUG_TYPE_NAMES.includes(this.type.name) || DEBUG_ALL_TYPES) {
2445
- console.log(`[VERBOSE] Bridge UNREGISTERED an instance of ${this.type.name}, current count: ${Object.keys(this.metasByAddress).length}`);
2580
+ console.log(`[VERBOSE] Bridge UNREGISTERED an instance of ${this.type.name}, current count: ${Object.keys(this.handlesByAddress).length}`);
2446
2581
  }
2447
2582
  }
2448
2583
  /** @internal */
@@ -2450,8 +2585,8 @@ class Bridge {
2450
2585
  if (DEBUG_TYPE_NAMES.includes(this.type.name) || DEBUG_ALL_TYPES) {
2451
2586
  console.log(`[VERBOSE] Unregistering ALL bridged instances of type ${this.type.name}.`);
2452
2587
  }
2453
- for (const meta of Object.values(this.metasByAddress)) {
2454
- const object = meta.object.deref();
2588
+ for (const handle of Object.values(this.handlesByAddress)) {
2589
+ const object = handle.object();
2455
2590
  if (object) {
2456
2591
  this.unregister(object);
2457
2592
  }
@@ -2459,16 +2594,20 @@ class Bridge {
2459
2594
  }
2460
2595
  /** @internal */
2461
2596
  get count() {
2462
- return Object.keys(this.metasByAddress).length;
2597
+ return Object.keys(this.handlesByAddress).length;
2598
+ }
2599
+ /* @internal */
2600
+ rawPointerFor(object) {
2601
+ return this.pointerByObject.get(object);
2463
2602
  }
2464
2603
  finalize(pointer) {
2465
- const meta = this.metasByAddress[pointer.addr];
2466
- if (!meta)
2467
- throw new Error(`Internal inconsistency, tried to finalize an instance for a pointer, that has no meta entry: ${pointer}`);
2468
- delete this.metasByAddress[pointer.addr];
2469
- this.release(pointer);
2604
+ const handle = this.handlesByAddress[pointer.addr];
2605
+ if (!handle)
2606
+ throw new Error(`Internal inconsistency, tried to finalize an instance for a pointer, that has no handle: ${pointer}`);
2607
+ delete this.handlesByAddress[pointer.addr];
2608
+ this.release(pointer, handle);
2470
2609
  if (DEBUG_TYPE_NAMES.includes(this.type.name) || DEBUG_ALL_TYPES) {
2471
- console.log(`[VERBOSE] Bridge FINALIZED an instance of ${this.type.name}, current count: ${Object.keys(this.metasByAddress).length}`);
2610
+ console.log(`[VERBOSE] Bridge FINALIZED an instance of ${this.type.name}, current count: ${Object.keys(this.handlesByAddress).length}`);
2472
2611
  }
2473
2612
  }
2474
2613
  }
@@ -2490,11 +2629,14 @@ Bridge.staticTCPClient = new Bridge(staticTCPClientFreeHandle);
2490
2629
  /** @internal */
2491
2630
  Bridge.websocketClient = new Bridge(websocketClientFreeHandle);
2492
2631
  /** @internal */
2493
- Bridge.ditto = new Bridge((dittoPointer) => {
2632
+ Bridge.ditto = new Bridge(async (dittoPointer, handle) => {
2494
2633
  // HACK: quick and dirty, clear all presence callbacks. This covers presence
2495
2634
  // v1 and v2 callbacks. v3 should be cleared properly by the `Presence`
2496
2635
  // class itself.
2497
2636
  dittoClearPresenceCallback(dittoPointer);
2637
+ if (!handle.isClosed) {
2638
+ await dittoShutdown(dittoPointer);
2639
+ }
2498
2640
  dittoFree(dittoPointer);
2499
2641
  });
2500
2642
 
@@ -2551,13 +2693,16 @@ class Attachment {
2551
2693
  * Returns the attachment's data.
2552
2694
  */
2553
2695
  getData() {
2554
- {
2555
- const dittoPointer = Bridge.ditto.pointerFor(this.ditto);
2556
- const attachmentHandlePointer = Bridge.attachment.pointerFor(this);
2557
- const attachmentPath = dittoGetCompleteAttachmentPath(dittoPointer, attachmentHandlePointer);
2558
- const fs = require('fs/promises');
2559
- return fs.readFile(attachmentPath);
2560
- }
2696
+ const ditto = this.ditto;
2697
+ const dittoHandle = Bridge.ditto.handleFor(ditto);
2698
+ return this.ditto.deferCloseAsync(async () => {
2699
+ {
2700
+ const attachmentHandle = Bridge.attachment.handleFor(this);
2701
+ const attachmentPath = dittoGetCompleteAttachmentPath(dittoHandle.deref(), attachmentHandle.deref());
2702
+ const fs = require('fs/promises');
2703
+ return await fs.readFile(attachmentPath);
2704
+ }
2705
+ });
2561
2706
  }
2562
2707
  /**
2563
2708
  * Copies the attachment to the specified file path. Node-only,
@@ -2566,15 +2711,18 @@ class Attachment {
2566
2711
  * @param path The path that the attachment should be copied to.
2567
2712
  */
2568
2713
  copyToPath(path) {
2569
- {
2570
- const dittoPointer = Bridge.ditto.pointerFor(this.ditto);
2571
- const attachmentHandlePointer = Bridge.attachment.pointerFor(this);
2572
- const attachmentPath = dittoGetCompleteAttachmentPath(dittoPointer, attachmentHandlePointer);
2573
- const fs = require('fs/promises');
2574
- // If the file already exists, we fail. This is the same behavior as
2575
- // for the Swift/ObjC SDK.
2576
- return fs.copyFile(attachmentPath, path, fs.COPYFILE_EXCL);
2577
- }
2714
+ const ditto = this.ditto;
2715
+ const dittoHandle = Bridge.ditto.handleFor(ditto);
2716
+ return this.ditto.deferCloseAsync(async () => {
2717
+ {
2718
+ const attachmentHandle = Bridge.attachment.handleFor(this);
2719
+ const attachmentPath = dittoGetCompleteAttachmentPath(dittoHandle.deref(), attachmentHandle.deref());
2720
+ const fs = require('fs/promises');
2721
+ // If the file already exists, we fail. This is the same behavior as
2722
+ // for the Swift/ObjC SDK.
2723
+ return await fs.copyFile(attachmentPath, path, fs.COPYFILE_EXCL);
2724
+ }
2725
+ });
2578
2726
  }
2579
2727
  /** @internal */
2580
2728
  constructor(ditto, token) {
@@ -2614,6 +2762,11 @@ function augmentJSONValue(json, mutDoc, workingPath) {
2614
2762
  return json;
2615
2763
  }
2616
2764
  }
2765
+ /**
2766
+ * Converts objects that may contain instances of classes of this SDK, i.e.
2767
+ * `DocumentID`, `Counter`, `Register` and `Attachment`, into plain JS objects
2768
+ * that can be passed to the FFI layer.
2769
+ */
2617
2770
  function desugarJSObject(jsObj, atRoot = false) {
2618
2771
  if (jsObj && typeof jsObj === 'object') {
2619
2772
  if (Array.isArray(jsObj)) {
@@ -2651,8 +2804,25 @@ function desugarJSObject(jsObj, atRoot = false) {
2651
2804
  }
2652
2805
  }
2653
2806
  else {
2807
+ checkForUnsupportedValues(jsObj);
2654
2808
  return jsObj;
2655
2809
  }
2810
+ }
2811
+ /**
2812
+ * Throws an error if input is a non-finite float value.
2813
+ *
2814
+ * Workaround while we don't have a reliable way of receiving error messages
2815
+ * from `dittoCore.ditto_collection_insert_value()`.
2816
+ *
2817
+ * See https://github.com/getditto/ditto/issues/8657 for details.
2818
+ *
2819
+ * @param jsObj The object to check.
2820
+ * @throws {Error} If `jsObj` is a non-finite float value.
2821
+ */
2822
+ function checkForUnsupportedValues(jsObj) {
2823
+ if (Number.isNaN(jsObj) || jsObj === Infinity || jsObj === -Infinity) {
2824
+ throw new Error('Non-finite float values are not supported');
2825
+ }
2656
2826
  }
2657
2827
 
2658
2828
  //
@@ -2719,7 +2889,10 @@ function validateQuery(query, options = {}) {
2719
2889
  return validatedQuery;
2720
2890
  }
2721
2891
  // -------------------------------------------------------------- Helpers ------
2722
- /** @internal */
2892
+ /**
2893
+ * Generate a random hex-encoded token using the WebCrypto API.
2894
+ *
2895
+ * @internal */
2723
2896
  function generateEphemeralToken() {
2724
2897
  let webcrypto = undefined;
2725
2898
  {
@@ -2738,9 +2911,9 @@ function sleep(milliseconds) {
2738
2911
  });
2739
2912
  }
2740
2913
  /** @internal use this to asyncify chunks of code. */
2741
- async function step(block) {
2914
+ async function step(closure) {
2742
2915
  await sleep(0);
2743
- return block();
2916
+ return closure();
2744
2917
  }
2745
2918
  /** @internal */
2746
2919
  // WORKAROUND: the corresponding FFI function(s) is not async at the
@@ -2999,8 +3172,8 @@ class DocumentPath {
2999
3172
  underlyingValueForPathType(pathType) {
3000
3173
  const path = this.path;
3001
3174
  const document = this.document;
3002
- const documentX = Bridge.document.pointerFor(document);
3003
- const cborPathResult = documentGetCBORWithPathType(documentX, path, pathType);
3175
+ const documentHandle = Bridge.document.handleFor(document);
3176
+ const cborPathResult = documentGetCBORWithPathType(documentHandle.deref(), path, pathType);
3004
3177
  return cborPathResult.cbor !== null ? CBOR.decode(cborPathResult.cbor) : undefined;
3005
3178
  }
3006
3179
  }
@@ -3103,8 +3276,8 @@ class MutableDocumentPath {
3103
3276
  underlyingValueForPathType(pathType) {
3104
3277
  const path = this.path;
3105
3278
  const document = this.mutableDocument;
3106
- const documentX = Bridge.mutableDocument.pointerFor(document);
3107
- const cborPathResult = documentGetCBORWithPathType(documentX, path, pathType);
3279
+ const documentHandle = Bridge.mutableDocument.handleFor(document);
3280
+ const cborPathResult = documentGetCBORWithPathType(documentHandle.deref(), path, pathType);
3108
3281
  return cborPathResult.cbor !== null ? CBOR.decode(cborPathResult.cbor) : undefined;
3109
3282
  }
3110
3283
  /** @internal */
@@ -3112,21 +3285,21 @@ class MutableDocumentPath {
3112
3285
  // REFACTOR: this body should probably move to
3113
3286
  // `MutableCounter.incrementBy()`. Keeping as-is for now until we implement
3114
3287
  // more explicit CRDT types.
3115
- const documentPointer = Bridge.mutableDocument.pointerFor(this.mutableDocument);
3116
- documentIncrementCounter(documentPointer, this.path, amount);
3288
+ const documentHandle = Bridge.mutableDocument.handleFor(this.mutableDocument);
3289
+ documentIncrementCounter(documentHandle.deref(), this.path, amount);
3117
3290
  const updateResult = UpdateResult.incremented(this.mutableDocument.id, this.path, amount);
3118
3291
  this.recordUpdateResult(updateResult);
3119
3292
  }
3120
3293
  /** @internal */
3121
3294
  '@ditto.set'(value, isDefault) {
3122
- const documentX = Bridge.mutableDocument.pointerFor(this.mutableDocument);
3295
+ const documentHandle = Bridge.mutableDocument.handleFor(this.mutableDocument);
3123
3296
  const valueJSON = desugarJSObject(value, false);
3124
3297
  const valueCBOR = CBOR.encode(valueJSON);
3125
3298
  if (isDefault) {
3126
- documentSetCBORWithTimestamp(documentX, this.path, valueCBOR, 0);
3299
+ documentSetCBORWithTimestamp(documentHandle.deref(), this.path, valueCBOR, 0);
3127
3300
  }
3128
3301
  else {
3129
- documentSetCBOR(documentX, this.path, valueCBOR);
3302
+ documentSetCBOR(documentHandle.deref(), this.path, valueCBOR);
3130
3303
  }
3131
3304
  // HACK: we need a copy of value because the original value can be mutated
3132
3305
  // later on and an update result needs the state of the value at this
@@ -3139,8 +3312,8 @@ class MutableDocumentPath {
3139
3312
  }
3140
3313
  /** @internal */
3141
3314
  '@ditto.remove'() {
3142
- const documentPointer = Bridge.mutableDocument.pointerFor(this.mutableDocument);
3143
- documentRemove(documentPointer, this.path);
3315
+ const documentHandle = Bridge.mutableDocument.handleFor(this.mutableDocument);
3316
+ documentRemove(documentHandle.deref(), this.path);
3144
3317
  this.updateInMemory((container, lastPathComponent) => {
3145
3318
  if (Array.isArray(container) && typeof lastPathComponent === 'number') {
3146
3319
  container.splice(lastPathComponent, 1);
@@ -3178,8 +3351,8 @@ class Document {
3178
3351
  */
3179
3352
  static hash(documentOrMany) {
3180
3353
  const documents = documentsFrom(documentOrMany);
3181
- const documentPointers = documents.map((doc) => Bridge.document.pointerFor(doc));
3182
- return documentsHash(documentPointers);
3354
+ const documentHandles = documents.map((doc) => Bridge.document.handleFor(doc));
3355
+ return documentsHash(documentHandles.map((handle) => handle.deref()));
3183
3356
  }
3184
3357
  /**
3185
3358
  * Returns a pattern of words that together create a mnemonic, which
@@ -3187,8 +3360,8 @@ class Document {
3187
3360
  */
3188
3361
  static hashMnemonic(documentOrMany) {
3189
3362
  const documents = documentsFrom(documentOrMany);
3190
- const documentPointers = documents.map((doc) => Bridge.document.pointerFor(doc));
3191
- return documentsHashMnemonic(documentPointers);
3363
+ const documentHandles = Bridge.document.handlesFor(documents);
3364
+ return documentsHashMnemonic(documentHandles.deref());
3192
3365
  }
3193
3366
  /**
3194
3367
  * Returns the document ID.
@@ -3196,8 +3369,8 @@ class Document {
3196
3369
  get id() {
3197
3370
  let id = this['@ditto.id'];
3198
3371
  if (typeof id === 'undefined') {
3199
- const documentX = Bridge.document.pointerFor(this);
3200
- const documentIDCBOR = documentID(documentX);
3372
+ const documentHandle = Bridge.document.handleFor(this);
3373
+ const documentIDCBOR = documentID(documentHandle.deref());
3201
3374
  id = new DocumentID(documentIDCBOR, true);
3202
3375
  this['@ditto.id'] = id;
3203
3376
  }
@@ -3233,8 +3406,8 @@ class Document {
3233
3406
  // TEMPORARY: helpers to deal with non-canonical IDs.
3234
3407
  /** @internal */
3235
3408
  static idCBOR(document) {
3236
- const documentX = Bridge.document.pointerFor(document);
3237
- return documentID(documentX);
3409
+ const documentHandle = Bridge.document.handleFor(document);
3410
+ return documentID(documentHandle.deref());
3238
3411
  }
3239
3412
  /** @internal */
3240
3413
  static canonicalizedIDCBOR(idCBOR) {
@@ -3269,8 +3442,8 @@ class MutableDocument {
3269
3442
  get id() {
3270
3443
  let id = this['@ditto.id'];
3271
3444
  if (typeof id === 'undefined') {
3272
- const documentX = Bridge.mutableDocument.pointerFor(this);
3273
- const documentIDCBOR = documentID(documentX);
3445
+ const documentHandle = Bridge.mutableDocument.handleFor(this);
3446
+ const documentIDCBOR = documentID(documentHandle.deref());
3274
3447
  id = new DocumentID(documentIDCBOR, true);
3275
3448
  this['@ditto.id'] = id;
3276
3449
  }
@@ -3302,8 +3475,8 @@ class MutableDocument {
3302
3475
  // TEMPORARY: helpers to deal with non-canonical IDs.
3303
3476
  /** @internal */
3304
3477
  static idCBOR(mutableDocument) {
3305
- const documentX = Bridge.mutableDocument.pointerFor(mutableDocument);
3306
- return documentID(documentX);
3478
+ const documentHandle = Bridge.mutableDocument.handleFor(mutableDocument);
3479
+ return documentID(documentHandle.deref());
3307
3480
  }
3308
3481
  }
3309
3482
  /** @internal */
@@ -3454,13 +3627,16 @@ class BasePendingCursorOperation {
3454
3627
  * the query generated by the preceding function chaining.
3455
3628
  */
3456
3629
  async exec() {
3457
- const query = this.query;
3458
- const dittoX = Bridge.ditto.pointerFor(this.collection.store.ditto);
3459
- const documentsX = await performAsyncToWorkaroundNonAsyncFFIAPI(async () => {
3460
- return await collectionExecQueryStr(dittoX, this.collection.name, null, query, this.queryArgsCBOR, this.orderBys, this.currentLimit, this.currentOffset);
3461
- });
3462
- return documentsX.map((documentX) => {
3463
- return Bridge.document.bridge(documentX);
3630
+ const ditto = this.collection.store.ditto;
3631
+ const dittoHandle = Bridge.ditto.handleFor(ditto);
3632
+ return ditto.deferCloseAsync(async () => {
3633
+ const query = this.query;
3634
+ const documentPointers = await performAsyncToWorkaroundNonAsyncFFIAPI(async () => {
3635
+ return await collectionExecQueryStr(dittoHandle.deref(), this.collection.name, null, query, this.queryArgsCBOR, this.orderBys, this.currentLimit, this.currentOffset);
3636
+ });
3637
+ return documentPointers.map((documentPointer) => {
3638
+ return Bridge.document.bridge(documentPointer);
3639
+ });
3464
3640
  });
3465
3641
  }
3466
3642
  // ----------------------------------------------------------- Internal ------
@@ -3479,37 +3655,40 @@ class BasePendingCursorOperation {
3479
3655
  * @internal
3480
3656
  */
3481
3657
  async updateWithTransaction(closure, writeTransactionX) {
3482
- return await performAsyncToWorkaroundNonAsyncFFIAPI(async () => {
3483
- const query = this.query;
3484
- const dittoX = Bridge.ditto.pointerFor(this.collection.store.ditto);
3485
- const documentsX = await collectionExecQueryStr(dittoX, this.collection.name, writeTransactionX, query, this.queryArgsCBOR, this.orderBys, this.currentLimit, this.currentOffset);
3486
- const mutableDocuments = documentsX.map((documentX) => {
3487
- return Bridge.mutableDocument.bridge(documentX, () => new MutableDocument());
3488
- });
3489
- closure(mutableDocuments);
3490
- const updateResultsDocumentIDs = [];
3491
- const updateResultsByDocumentIDString = {};
3492
- for (const mutableDocument of mutableDocuments) {
3493
- const documentID = mutableDocument.id;
3494
- const documentIDString = documentID.toString();
3495
- const updateResults = mutableDocument['@ditto.updateResults'];
3496
- if (updateResultsByDocumentIDString[documentIDString]) {
3497
- // HACK: in theory, 2 different document IDs can have the exact
3498
- // same string representation at the time of this writing. There is
3499
- // already a REFACTOR comment in `UpdateResultsMap` to implement a
3500
- // proper and correct data structure for holding these update results.
3501
- // Until then, we'll leave this check here to see how often we hit
3502
- // this edge case.
3503
- throw new Error(`Internal inconsistency, update results for document ID as string already exist: ${documentIDString}`);
3658
+ const ditto = this.collection.store.ditto;
3659
+ const dittoHandle = Bridge.ditto.handleFor(ditto);
3660
+ return ditto.deferCloseAsync(async () => {
3661
+ return await performAsyncToWorkaroundNonAsyncFFIAPI(async () => {
3662
+ const query = this.query;
3663
+ const documentsX = await collectionExecQueryStr(dittoHandle.deref(), this.collection.name, writeTransactionX, query, this.queryArgsCBOR, this.orderBys, this.currentLimit, this.currentOffset);
3664
+ const mutableDocuments = documentsX.map((documentX) => {
3665
+ return Bridge.mutableDocument.bridge(documentX, () => new MutableDocument());
3666
+ });
3667
+ closure(mutableDocuments);
3668
+ const updateResultsDocumentIDs = [];
3669
+ const updateResultsByDocumentIDString = {};
3670
+ for (const mutableDocument of mutableDocuments) {
3671
+ const documentID = mutableDocument.id;
3672
+ const documentIDString = documentID.toString();
3673
+ const updateResults = mutableDocument['@ditto.updateResults'];
3674
+ if (updateResultsByDocumentIDString[documentIDString]) {
3675
+ // HACK: in theory, 2 different document IDs can have the exact
3676
+ // same string representation at the time of this writing. There is
3677
+ // already a REFACTOR comment in `UpdateResultsMap` to implement a
3678
+ // proper and correct data structure for holding these update results.
3679
+ // Until then, we'll leave this check here to see how often we hit
3680
+ // this edge case.
3681
+ throw new Error(`Internal inconsistency, update results for document ID as string already exist: ${documentIDString}`);
3682
+ }
3683
+ updateResultsDocumentIDs.push(documentID);
3684
+ updateResultsByDocumentIDString[documentIDString] = updateResults;
3685
+ Bridge.mutableDocument.unregister(mutableDocument);
3504
3686
  }
3505
- updateResultsDocumentIDs.push(documentID);
3506
- updateResultsByDocumentIDString[documentIDString] = updateResults;
3507
- Bridge.mutableDocument.unregister(mutableDocument);
3508
- }
3509
- // NOTE: ownership of documentsX (and contained documents)
3510
- // is transferred to Rust at this point.
3511
- await collectionUpdateMultiple(dittoX, this.collection.name, writeTransactionX, documentsX);
3512
- return new UpdateResultsMap(updateResultsDocumentIDs, updateResultsByDocumentIDString);
3687
+ // NOTE: ownership of documentsX (and contained documents)
3688
+ // is transferred to Rust at this point.
3689
+ await collectionUpdateMultiple(dittoHandle.deref(), this.collection.name, writeTransactionX, documentsX);
3690
+ return new UpdateResultsMap(updateResultsDocumentIDs, updateResultsByDocumentIDString);
3691
+ });
3513
3692
  });
3514
3693
  }
3515
3694
  /** @internal */
@@ -3551,15 +3730,18 @@ class BasePendingIDSpecificOperation {
3551
3730
  * not found.
3552
3731
  */
3553
3732
  async exec() {
3554
- const dittoX = Bridge.ditto.pointerFor(this.collection.store.ditto);
3555
- return await performAsyncToWorkaroundNonAsyncFFIAPI(async () => {
3556
- const readTransactionX = await readTransaction(dittoX);
3557
- const documentX = await collectionGet(dittoX, this.collection.name, this.documentIDCBOR, readTransactionX);
3558
- let document = undefined;
3559
- if (documentX)
3560
- document = Bridge.document.bridge(documentX);
3561
- readTransactionFree(readTransactionX);
3562
- return document;
3733
+ const ditto = this.collection.store.ditto;
3734
+ const dittoHandle = Bridge.ditto.handleFor(ditto);
3735
+ return ditto.deferCloseAsync(async () => {
3736
+ return await performAsyncToWorkaroundNonAsyncFFIAPI(async () => {
3737
+ const readTransactionX = await readTransaction(dittoHandle.deref());
3738
+ const documentX = await collectionGet(dittoHandle.deref(), this.collection.name, this.documentIDCBOR, readTransactionX);
3739
+ let document = undefined;
3740
+ if (documentX)
3741
+ document = Bridge.document.bridge(documentX);
3742
+ readTransactionFree(readTransactionX);
3743
+ return document;
3744
+ });
3563
3745
  });
3564
3746
  }
3565
3747
  // ----------------------------------------------------------- Internal ------
@@ -3591,6 +3773,7 @@ class ObserverManager {
3591
3773
  const process = (_d = options.process) !== null && _d !== void 0 ? _d : null;
3592
3774
  this.id = id;
3593
3775
  this.keepAlive = keepAlive;
3776
+ this.isClosed = false;
3594
3777
  this.isRegistered = false;
3595
3778
  this.callbacksByToken = {};
3596
3779
  if (register !== null) {
@@ -3606,6 +3789,11 @@ class ObserverManager {
3606
3789
  /** @internal */
3607
3790
  addObserver(callback) {
3608
3791
  var _a;
3792
+ if (this.isClosed) {
3793
+ // REFACTOR: throw a catchable error here, such that calling code
3794
+ // can be more specific when forwarding it to the user.
3795
+ throw new Error(`Internal inconsistency, can't add '${this.id}' observer, observer mananger close()-ed.`);
3796
+ }
3609
3797
  this.registerIfNeeded();
3610
3798
  const token = generateEphemeralToken();
3611
3799
  this.callbacksByToken[token] = callback;
@@ -3615,18 +3803,45 @@ class ObserverManager {
3615
3803
  /** @internal */
3616
3804
  removeObserver(token) {
3617
3805
  var _a;
3618
- delete this.callbacksByToken[token];
3806
+ const callback = this.callbacksByToken[token];
3807
+ if (typeof callback === 'undefined') {
3808
+ throw new Error(`Can't remove '${this.id}' observer, token '${token}' has never been registered before.`);
3809
+ }
3810
+ if (callback === null) {
3811
+ // Observer has already been removed, no-op.
3812
+ return;
3813
+ }
3814
+ // REFACTOR: not deleting the token here will keep eating up
3815
+ // memory over long periods of time. We actually need to track
3816
+ // the observer objects themselves and remove it from the table
3817
+ // as soon as the observer object is garbage collected.
3818
+ this.callbacksByToken[token] = null;
3619
3819
  (_a = this.keepAlive) === null || _a === void 0 ? void 0 : _a.release(`${this.id}.${token}`);
3620
3820
  this.unregisterIfNeeded();
3621
3821
  }
3822
+ hasObserver(token) {
3823
+ return typeof this.callbacksByToken[token] != 'undefined';
3824
+ }
3622
3825
  /** @internal */
3623
3826
  notify(...args) {
3827
+ if (this.isClosed) {
3828
+ // NOTE: we don't notify observers after closing and just swallow
3829
+ // the event.
3830
+ return;
3831
+ }
3624
3832
  const processedArgs = this.process(...args);
3625
3833
  for (const token in this.callbacksByToken) {
3626
3834
  const callback = this.callbacksByToken[token];
3627
3835
  callback(...processedArgs);
3628
3836
  }
3629
3837
  }
3838
+ /** @internal */
3839
+ close() {
3840
+ this.isClosed = true;
3841
+ for (const token in this.callbacksByToken) {
3842
+ this.removeObserver(token);
3843
+ }
3844
+ }
3630
3845
  /**
3631
3846
  * Can be injected and replaced via constructor options.
3632
3847
  *
@@ -3676,9 +3891,6 @@ class ObserverManager {
3676
3891
  this.unregister();
3677
3892
  }
3678
3893
  }
3679
- async finalize(token) {
3680
- await this.removeObserver(token);
3681
- }
3682
3894
  }
3683
3895
 
3684
3896
  //
@@ -3748,39 +3960,65 @@ class Authenticator {
3748
3960
  '@ditto.authClientValidityChanged'(isWebValid, isX509Valid) {
3749
3961
  throw new Error(`Authenticator['@ditto.authClientValidityChanged']() is abstract and must be implemented by subclasses.`);
3750
3962
  }
3963
+ /** @internal */
3964
+ close() {
3965
+ this.observerManager.close();
3966
+ }
3751
3967
  }
3752
3968
  // -----------------------------------------------------------------------------
3753
3969
  /** @internal */
3754
3970
  class OnlineAuthenticator extends Authenticator {
3755
3971
  async loginWithToken(token, provider) {
3756
- await dittoAuthClientLoginWithToken(this.authClientPointer, token, provider);
3972
+ const ditto = this.ditto.deref();
3973
+ if (!ditto) {
3974
+ return;
3975
+ }
3976
+ const dittoHandle = Bridge.ditto.handleFor(ditto);
3977
+ const dittoPointer = dittoHandle.derefOrNull();
3978
+ if (!dittoPointer) {
3979
+ return;
3980
+ }
3981
+ return ditto.deferCloseAsync(async () => {
3982
+ await dittoAuthClientLoginWithToken(dittoPointer, token, provider);
3983
+ });
3757
3984
  }
3758
3985
  async loginWithUsernameAndPassword(username, password, provider) {
3759
- await dittoAuthClientLoginWithUsernameAndPassword(this.authClientPointer, username, password, provider);
3986
+ const ditto = this.ditto.deref();
3987
+ if (!ditto) {
3988
+ return;
3989
+ }
3990
+ const dittoHandle = Bridge.ditto.handleFor(ditto);
3991
+ const dittoPointer = dittoHandle.derefOrNull();
3992
+ if (!dittoPointer) {
3993
+ return;
3994
+ }
3995
+ return ditto.deferCloseAsync(async () => {
3996
+ await dittoAuthClientLoginWithUsernameAndPassword(dittoPointer, username, password, provider);
3997
+ });
3760
3998
  }
3761
3999
  async logout(cleanupFn) {
3762
4000
  const ditto = this.ditto.deref();
3763
- if (ditto) {
3764
- await dittoAuthClientLogout(this.authClientPointer);
3765
- ditto.stopSync();
3766
- cleanupFn === null || cleanupFn === void 0 ? void 0 : cleanupFn(this.ditto);
4001
+ if (!ditto) {
4002
+ return;
3767
4003
  }
3768
- else {
3769
- Logger.warning('Unable to logout, related Ditto object does not exist anymore.');
4004
+ const dittoHandle = Bridge.ditto.handleFor(ditto);
4005
+ const dittoPointer = dittoHandle.derefOrNull();
4006
+ if (!dittoPointer) {
4007
+ return;
3770
4008
  }
4009
+ return ditto.deferCloseAsync(async () => {
4010
+ await dittoAuthClientLogout(dittoPointer);
4011
+ ditto.stopSync();
4012
+ cleanupFn === null || cleanupFn === void 0 ? void 0 : cleanupFn(this.ditto);
4013
+ });
3771
4014
  }
3772
- constructor(keepAlive, authClientPointer, ditto, authenticationHandler) {
4015
+ constructor(keepAlive, ditto, authenticationHandler) {
3773
4016
  super(keepAlive);
3774
4017
  this['loginSupported'] = true;
3775
4018
  this['status'] = { isAuthenticated: false, userID: null };
3776
- this.authClientPointer = authClientPointer;
3777
4019
  this.ditto = new WeakRef(ditto);
3778
4020
  this.authenticationHandler = authenticationHandler;
3779
4021
  this.updateAndNotify(false);
3780
- // NOTE: The ownership of the auth client is transferred to us (from Ditto),
3781
- // so we have to free the auth client once this instance is garbage
3782
- // collected.
3783
- OnlineAuthenticator.finalizationRegistry.register(this, authClientPointer);
3784
4022
  }
3785
4023
  '@ditto.authenticationExpiring'(secondsRemaining) {
3786
4024
  const authenticationHandler = this.authenticationHandler;
@@ -3796,10 +4034,21 @@ class OnlineAuthenticator extends Authenticator {
3796
4034
  }
3797
4035
  updateAndNotify(shouldNotify) {
3798
4036
  var _a;
4037
+ const ditto = this.ditto.deref();
4038
+ if (!ditto) {
4039
+ Logger.debug('Unable to update auth status and notify, related Ditto object does not exist anymore.');
4040
+ return;
4041
+ }
4042
+ const dittoHandle = Bridge.ditto.handleFor(ditto);
4043
+ const dittoPointer = dittoHandle.derefOrNull();
4044
+ if (!dittoPointer) {
4045
+ Logger.debug('Unable to update auth status and notify, related Ditto object does not exist anymore.');
4046
+ return;
4047
+ }
3799
4048
  const wasAuthenticated = this.status.isAuthenticated;
3800
4049
  const previousUserID = this.status.userID;
3801
- const isAuthenticated = dittoAuthClientIsWebValid(this.authClientPointer);
3802
- const userID = dittoAuthClientUserID(this.authClientPointer);
4050
+ const isAuthenticated = dittoAuthClientIsWebValid(dittoPointer);
4051
+ const userID = dittoAuthClientUserID(dittoPointer);
3803
4052
  const status = { isAuthenticated, userID };
3804
4053
  this['status'] = status;
3805
4054
  if (shouldNotify) {
@@ -3810,11 +4059,7 @@ class OnlineAuthenticator extends Authenticator {
3810
4059
  }
3811
4060
  }
3812
4061
  }
3813
- static finalize(authClientPointer) {
3814
- dittoAuthClientFree(authClientPointer);
3815
- }
3816
4062
  }
3817
- OnlineAuthenticator.finalizationRegistry = new FinalizationRegistry(OnlineAuthenticator.finalize);
3818
4063
  // -----------------------------------------------------------------------------
3819
4064
  /** @internal */
3820
4065
  class NotAvailableAuthenticator extends Authenticator {
@@ -3980,94 +4225,6 @@ class TransportConfig {
3980
4225
  }
3981
4226
  }
3982
4227
 
3983
- //
3984
- /**
3985
- * These objects are returned by calls to
3986
- * {@link Collection.fetchAttachment | fetchAttachment()} on {@link Collection}
3987
- * and allow you to stop an in-flight attachment fetch.
3988
- */
3989
- class AttachmentFetcher {
3990
- /**
3991
- * Stops fetching the associated attachment and cleans up any associated
3992
- * resources.
3993
- *
3994
- * Note that you are not required to call `stop()` once your attachment fetch
3995
- * operation has finished. The method primarily exists to allow you to cancel
3996
- * an attachment fetch request while it is ongoing if you no longer wish for
3997
- * the attachment to be made available locally to the device.
3998
- */
3999
- stop() {
4000
- AttachmentFetcher.stopWithContextInfo({
4001
- ditto: this.ditto,
4002
- attachmentTokenID: this.token.id,
4003
- cancelTokenPromise: this.cancelTokenPromise,
4004
- });
4005
- }
4006
- /** @internal */
4007
- then(onfulfilled, onrejected) {
4008
- return this.attachment.then(onfulfilled, onrejected);
4009
- }
4010
- /** @internal */
4011
- constructor(ditto, token, eventHandler) {
4012
- this.ditto = ditto;
4013
- this.token = token;
4014
- this.eventHandler = eventHandler || null;
4015
- this.cancelTokenPromise = null;
4016
- const eventHandlerOrNoOp = eventHandler || function () { };
4017
- this.attachment = new Promise((resolve, reject) => {
4018
- const dittoPointer = Bridge.ditto.pointerFor(ditto);
4019
- // not awaited yet; only needs to be fully resolved (and `await`ed over)
4020
- // when using the actual `cancelTokenPromise`, which only happens in `stop()`.
4021
- this.cancelTokenPromise = dittoResolveAttachment(dittoPointer, token.id, {
4022
- onComplete: (attachmentHandlePointer) => {
4023
- this['eventHandler'] = null;
4024
- this['cancelTokenPromise'] = null;
4025
- const attachment = new Attachment(this.ditto, this.token);
4026
- Bridge.attachment.bridge(attachmentHandlePointer, () => attachment);
4027
- eventHandlerOrNoOp({ type: 'Completed', attachment });
4028
- resolve(attachment);
4029
- },
4030
- onProgress: (downloaded, toDownload) => {
4031
- eventHandlerOrNoOp({ type: 'Progress', totalBytes: toDownload, downloadedBytes: downloaded });
4032
- },
4033
- onDelete: () => {
4034
- this['eventHandler'] = null;
4035
- this['cancelTokenPromise'] = null;
4036
- eventHandlerOrNoOp({ type: 'Deleted' });
4037
- resolve(null);
4038
- },
4039
- },
4040
- /* onError: */ (err) => {
4041
- this['eventHandler'] = null;
4042
- this['cancelTokenPromise'] = null;
4043
- // REFACTOR: We simply throw this error at the moment. Maybe we should
4044
- // bubble it up?
4045
- reject(err);
4046
- });
4047
- });
4048
- const contextInfo = {
4049
- ditto: ditto,
4050
- attachmentTokenID: token.id,
4051
- cancelTokenPromise: this.cancelTokenPromise,
4052
- };
4053
- AttachmentFetcher.finalizationRegistry.register(this, contextInfo, contextInfo);
4054
- }
4055
- static stopWithContextInfo(contextInfo) {
4056
- // No need for synchronicity, here: we let the "stop promise", float / run
4057
- // in a detached fashion since there is no point in awaiting the
4058
- // "stop signaling" itself.
4059
- step(async () => {
4060
- // await the initial resolution of `FFI.dittoResolveAttachment` below.
4061
- const cancelToken = await contextInfo.cancelTokenPromise;
4062
- if (cancelToken) {
4063
- const dittoPointer = Bridge.ditto.pointerFor(contextInfo.ditto);
4064
- dittoCancelResolveAttachment(dittoPointer, contextInfo.attachmentTokenID, cancelToken);
4065
- }
4066
- });
4067
- }
4068
- }
4069
- AttachmentFetcher.finalizationRegistry = new FinalizationRegistry(AttachmentFetcher.stopWithContextInfo);
4070
-
4071
4228
  //
4072
4229
  /**
4073
4230
  * Used to subscribe to receive updates from remote peers about matching
@@ -4090,7 +4247,7 @@ class Subscription {
4090
4247
  cancel() {
4091
4248
  if (!this.isCancelled) {
4092
4249
  this['isCancelled'] = true;
4093
- Subscription.remove(this, this.contextInfo);
4250
+ this.manager.remove(this);
4094
4251
  }
4095
4252
  }
4096
4253
  // ----------------------------------------------------------- Internal ------
@@ -4106,7 +4263,7 @@ class Subscription {
4106
4263
  this.queryArgsCBOR = queryArgsCBOR;
4107
4264
  this.collection = collection;
4108
4265
  this.contextInfo = {
4109
- ditto: collection.store.ditto,
4266
+ id: generateEphemeralToken(),
4110
4267
  collectionName: collection.name,
4111
4268
  query,
4112
4269
  queryArgsCBOR,
@@ -4114,23 +4271,10 @@ class Subscription {
4114
4271
  limit,
4115
4272
  offset,
4116
4273
  };
4117
- Subscription.add(this, this.contextInfo);
4118
- }
4119
- static add(subscription, contextInfo) {
4120
- const dittoX = Bridge.ditto.pointerFor(contextInfo.ditto);
4121
- addSubscription(dittoX, contextInfo.collectionName, contextInfo.query, contextInfo.queryArgsCBOR, contextInfo.orderBys, contextInfo.limit, contextInfo.offset);
4122
- this.finalizationRegistry.register(subscription, contextInfo, contextInfo);
4123
- }
4124
- static remove(subscription, contextInfo) {
4125
- const dittoX = Bridge.ditto.pointerFor(contextInfo.ditto);
4126
- removeSubscription(dittoX, contextInfo.collectionName, contextInfo.query, contextInfo.queryArgsCBOR, contextInfo.orderBys, contextInfo.limit, contextInfo.offset);
4127
- if (subscription)
4128
- this.finalizationRegistry.unregister(contextInfo);
4274
+ this.manager = collection.store.ditto.subscriptionManager;
4275
+ this.manager.add(this);
4129
4276
  }
4130
- }
4131
- Subscription.finalizationRegistry = new FinalizationRegistry((contextInfo) => {
4132
- Subscription.remove(null, contextInfo);
4133
- });
4277
+ }
4134
4278
 
4135
4279
  //
4136
4280
  // -----------------------------------------------------------------------------
@@ -4150,14 +4294,16 @@ class LiveQueryEventInitial {
4150
4294
  * Returns a hash that represents the set of matching documents.
4151
4295
  */
4152
4296
  hash(documents) {
4153
- return documentsHash(documents.map((doc) => Bridge.document.pointerFor(doc)));
4297
+ const documentHandles = Bridge.document.handlesFor(documents);
4298
+ return documentsHash(documentHandles.deref());
4154
4299
  }
4155
4300
  /**
4156
4301
  * Returns a pattern of words that together create a mnemonic, which
4157
4302
  * represents the set of matching documents.
4158
4303
  */
4159
4304
  hashMnemonic(documents) {
4160
- return documentsHashMnemonic(documents.map((doc) => Bridge.document.pointerFor(doc)));
4305
+ const documentHandles = Bridge.document.handlesFor(documents);
4306
+ return documentsHashMnemonic(documentHandles.deref());
4161
4307
  }
4162
4308
  }
4163
4309
  // -----------------------------------------------------------------------------
@@ -4170,14 +4316,16 @@ class LiveQueryEventUpdate {
4170
4316
  * Returns a hash that represents the set of matching documents.
4171
4317
  */
4172
4318
  hash(documents) {
4173
- return documentsHash(documents.map((doc) => Bridge.document.pointerFor(doc)));
4319
+ const documentHandles = Bridge.document.handlesFor(documents);
4320
+ return documentsHash(documentHandles.deref());
4174
4321
  }
4175
4322
  /**
4176
4323
  * Returns a pattern of words that together create a mnemonic, which
4177
4324
  * represents the set of matching documents.
4178
4325
  */
4179
4326
  hashMnemonic(documents) {
4180
- return documentsHashMnemonic(documents.map((doc) => Bridge.document.pointerFor(doc)));
4327
+ const documentHandles = Bridge.document.handlesFor(documents);
4328
+ return documentsHashMnemonic(documentHandles.deref());
4181
4329
  }
4182
4330
  /** @internal */
4183
4331
  constructor(params) {
@@ -4203,14 +4351,16 @@ class SingleDocumentLiveQueryEvent {
4203
4351
  * Returns a hash that represents the set of matching documents.
4204
4352
  */
4205
4353
  hash(document) {
4206
- return documentsHash(document === null ? [] : [Bridge.document.pointerFor(document)]);
4354
+ const documentHandles = Bridge.document.handlesFor(document == null ? [] : [document]);
4355
+ return documentsHash(documentHandles.deref());
4207
4356
  }
4208
4357
  /**
4209
4358
  * Returns a pattern of words that together create a mnemonic, which
4210
4359
  * represents the set of matching documents.
4211
4360
  */
4212
4361
  hashMnemonic(document) {
4213
- return documentsHashMnemonic(document === null ? [] : [Bridge.document.pointerFor(document)]);
4362
+ const documentHandles = Bridge.document.handlesFor(document == null ? [] : [document]);
4363
+ return documentsHashMnemonic(documentHandles.deref());
4214
4364
  }
4215
4365
  /** @internal */
4216
4366
  constructor(isInitial, oldDocument) {
@@ -4241,24 +4391,18 @@ class LiveQuery {
4241
4391
  }
4242
4392
  /** Returns true if the receiver has been stopped. */
4243
4393
  get isStopped() {
4244
- return this.liveQueryID === null;
4394
+ return !this.liveQueryManager;
4245
4395
  }
4246
4396
  /**
4247
4397
  * Stop the live query from delivering updates.
4248
4398
  */
4249
4399
  stop() {
4250
- var _a;
4251
- const liveQueryID = this.liveQueryID;
4252
- if (liveQueryID !== null) {
4253
- this.collection.store.ditto.keepAlive.release(`LiveQuery.${liveQueryID}`);
4254
- this.liveQueryID = null;
4255
- const dittoPointer = Bridge.ditto.pointerFor(this.collection.store.ditto);
4256
- (_a = this.subscription) === null || _a === void 0 ? void 0 : _a.cancel();
4257
- liveQueryStop(dittoPointer, liveQueryID);
4400
+ if (!this.isStopped) {
4401
+ this.liveQueryManager.stopLiveQuery(this);
4258
4402
  }
4259
4403
  }
4260
4404
  /** @internal */
4261
- constructor(query, queryArgs, queryArgsCBOR, orderBys, limit, offset, collection, subscription, handler) {
4405
+ constructor(query, queryArgs, queryArgsCBOR, orderBys, limit, offset, collection, handler) {
4262
4406
  // Query should be validated at this point.
4263
4407
  this.query = query;
4264
4408
  this.queryArgs = queryArgs ? Object.freeze({ ...queryArgs }) : null;
@@ -4268,52 +4412,59 @@ class LiveQuery {
4268
4412
  this.offset = offset;
4269
4413
  this.collection = collection;
4270
4414
  this.handler = handler;
4271
- if (subscription) {
4272
- this.subscription = subscription;
4273
- }
4415
+ this.liveQueryManager = null;
4274
4416
  const collectionName = collection.name;
4275
4417
  const weakDitto = new WeakRef(collection.store.ditto);
4276
4418
  let liveQueryID = undefined;
4277
4419
  const signalNext = async () => {
4278
4420
  const ditto = weakDitto.deref();
4279
- if (ditto) {
4280
- const dittoPointer = Bridge.ditto.pointerFor(ditto);
4421
+ if (!ditto)
4422
+ return;
4423
+ const dittoHandle = Bridge.ditto.handleFor(ditto);
4424
+ const dittoPointer = dittoHandle.derefOrNull();
4425
+ if (!dittoPointer)
4426
+ return;
4427
+ return ditto.deferCloseAsync(async () => {
4281
4428
  await liveQuerySignalAvailableNext(dittoPointer, liveQueryID);
4282
- }
4429
+ });
4283
4430
  };
4284
- const dittoPointer = Bridge.ditto.pointerFor(collection.store.ditto);
4285
- liveQueryID = liveQueryRegister(dittoPointer, collectionName, query, queryArgsCBOR, this.orderBys, limit, offset, (cCBParams) => {
4286
- const documents = cCBParams.documents.map((ptr) => Bridge.document.bridge(ptr));
4287
- let event;
4288
- if (cCBParams.is_initial) {
4289
- event = new LiveQueryEventInitial();
4290
- }
4291
- else {
4292
- event = new LiveQueryEventUpdate({
4293
- oldDocuments: cCBParams.old_documents.map((ptr) => Bridge.document.bridge(ptr)),
4294
- insertions: cCBParams.insertions,
4295
- deletions: cCBParams.deletions,
4296
- updates: cCBParams.updates,
4297
- moves: cCBParams.moves.map((move) => ({ from: move[0], to: move[1] })),
4298
- });
4431
+ const ditto = collection.store.ditto;
4432
+ const dittoHandle = Bridge.ditto.handleFor(ditto);
4433
+ ditto.deferClose(() => {
4434
+ liveQueryID = liveQueryRegister(dittoHandle.deref(), collectionName, query, queryArgsCBOR, this.orderBys, limit, offset, (cCBParams) => {
4435
+ const documents = cCBParams.documents.map((ptr) => Bridge.document.bridge(ptr));
4436
+ let event;
4437
+ if (cCBParams.is_initial) {
4438
+ event = new LiveQueryEventInitial();
4439
+ }
4440
+ else {
4441
+ event = new LiveQueryEventUpdate({
4442
+ oldDocuments: cCBParams.old_documents.map((ptr) => Bridge.document.bridge(ptr)),
4443
+ insertions: cCBParams.insertions,
4444
+ deletions: cCBParams.deletions,
4445
+ updates: cCBParams.updates,
4446
+ moves: cCBParams.moves.map((move) => ({ from: move[0], to: move[1] })),
4447
+ });
4448
+ }
4449
+ handler(documents, event, signalNext);
4450
+ });
4451
+ if (!liveQueryID) {
4452
+ throw new Error("Internal inconsistency, couldn't create a valid live query ID.");
4299
4453
  }
4300
- handler(documents, event, signalNext);
4454
+ this['liveQueryID'] = liveQueryID;
4301
4455
  });
4302
- if (!liveQueryID) {
4303
- throw new Error("Internal inconsistency, couldn't create a valid live query ID.");
4304
- }
4305
- this.liveQueryID = liveQueryID;
4306
- // not awaited on purpose; let the invocation of the initial observation
4307
- // happen in a "fire-and-forget" / detached / escaping fashion, to be
4308
- // consistent with the subsequent invocations.
4309
- step(async () => await liveQueryStart(dittoPointer, liveQueryID));
4310
- collection.store.ditto.keepAlive.retain(`LiveQuery.${liveQueryID}`);
4311
4456
  }
4312
4457
  /** @internal */
4313
4458
  async signalNext() {
4314
4459
  // IDEA: make this public?
4315
- const dittoPointer = Bridge.ditto.pointerFor(this.collection.store.ditto);
4316
- await liveQuerySignalAvailableNext(dittoPointer, this.liveQueryID);
4460
+ const ditto = this.collection.store.ditto;
4461
+ const dittoHandle = Bridge.ditto.handleFor(ditto);
4462
+ const dittoPointer = dittoHandle.derefOrNull();
4463
+ if (!dittoPointer)
4464
+ return;
4465
+ return ditto.deferCloseAsync(async () => {
4466
+ await liveQuerySignalAvailableNext(dittoHandle.deref(), this.liveQueryID);
4467
+ });
4317
4468
  }
4318
4469
  }
4319
4470
 
@@ -4355,40 +4506,48 @@ class PendingCursorOperation extends BasePendingCursorOperation {
4355
4506
  return super.limit(limit);
4356
4507
  }
4357
4508
  async remove() {
4358
- const query = this.query;
4359
- const dittoX = Bridge.ditto.pointerFor(this.collection.store.ditto);
4360
- const documentsX = await performAsyncToWorkaroundNonAsyncFFIAPI(async () => {
4361
- const writeTransactionX = await writeTransaction(dittoX);
4362
- const results = await collectionRemoveQueryStr(dittoX, this.collection.name, writeTransactionX, query, this.queryArgsCBOR, this.orderBys, this.currentLimit, this.currentOffset);
4363
- await writeTransactionCommit(dittoX, writeTransactionX);
4364
- return results;
4365
- });
4366
- return documentsX.map((idCBOR) => {
4367
- return new DocumentID(idCBOR, true);
4509
+ const ditto = this.collection.store.ditto;
4510
+ const dittoHandle = Bridge.ditto.handleFor(ditto);
4511
+ return ditto.deferCloseAsync(async () => {
4512
+ const query = this.query;
4513
+ const documentsX = await performAsyncToWorkaroundNonAsyncFFIAPI(async () => {
4514
+ const writeTransactionX = await writeTransaction(dittoHandle.deref());
4515
+ const results = await collectionRemoveQueryStr(dittoHandle.deref(), this.collection.name, writeTransactionX, query, this.queryArgsCBOR, this.orderBys, this.currentLimit, this.currentOffset);
4516
+ await writeTransactionCommit(dittoHandle.deref(), writeTransactionX);
4517
+ return results;
4518
+ });
4519
+ return documentsX.map((idCBOR) => {
4520
+ return new DocumentID(idCBOR, true);
4521
+ });
4368
4522
  });
4369
4523
  }
4370
4524
  async evict() {
4371
- const query = this.query;
4372
- const dittoX = Bridge.ditto.pointerFor(this.collection.store.ditto);
4373
- const documentsX = await performAsyncToWorkaroundNonAsyncFFIAPI(async () => {
4374
- const writeTransactionX = await writeTransaction(dittoX);
4375
- const results = await collectionEvictQueryStr(dittoX, this.collection.name, writeTransactionX, query, this.queryArgsCBOR, this.orderBys, this.currentLimit, this.currentOffset);
4376
- await writeTransactionCommit(dittoX, writeTransactionX);
4377
- return results;
4378
- });
4379
- return documentsX.map((idCBOR) => {
4380
- return new DocumentID(idCBOR, true);
4525
+ const ditto = this.collection.store.ditto;
4526
+ const dittoHandle = Bridge.ditto.handleFor(ditto);
4527
+ return ditto.deferCloseAsync(async () => {
4528
+ const query = this.query;
4529
+ const documentsX = await performAsyncToWorkaroundNonAsyncFFIAPI(async () => {
4530
+ const writeTransactionX = await writeTransaction(dittoHandle.deref());
4531
+ const results = await collectionEvictQueryStr(dittoHandle.deref(), this.collection.name, writeTransactionX, query, this.queryArgsCBOR, this.orderBys, this.currentLimit, this.currentOffset);
4532
+ await writeTransactionCommit(dittoHandle.deref(), writeTransactionX);
4533
+ return results;
4534
+ });
4535
+ return documentsX.map((idCBOR) => {
4536
+ return new DocumentID(idCBOR, true);
4537
+ });
4381
4538
  });
4382
4539
  }
4383
4540
  async update(closure) {
4384
- const dittoX = Bridge.ditto.pointerFor(this.collection.store.ditto);
4385
- const results = await performAsyncToWorkaroundNonAsyncFFIAPI(async () => {
4386
- const writeTransactionX = await writeTransaction(dittoX);
4387
- const results = await super.updateWithTransaction(closure, writeTransactionX);
4388
- await writeTransactionCommit(dittoX, writeTransactionX);
4389
- return results;
4541
+ const ditto = this.collection.store.ditto;
4542
+ const dittoHandle = Bridge.ditto.handleFor(ditto);
4543
+ return ditto.deferCloseAsync(async () => {
4544
+ return await performAsyncToWorkaroundNonAsyncFFIAPI(async () => {
4545
+ const writeTransactionX = await writeTransaction(dittoHandle.deref());
4546
+ const results = await super.updateWithTransaction(closure, writeTransactionX);
4547
+ await writeTransactionCommit(dittoHandle.deref(), writeTransactionX);
4548
+ return results;
4549
+ });
4390
4550
  });
4391
- return results;
4392
4551
  }
4393
4552
  /**
4394
4553
  * Enables you to subscribe to changes that occur in a collection remotely.
@@ -4406,7 +4565,9 @@ class PendingCursorOperation extends BasePendingCursorOperation {
4406
4565
  * query specified in the preceding chain.
4407
4566
  */
4408
4567
  subscribe() {
4409
- return new Subscription(this.collection, this.query, this.queryArgsCBOR, this.orderBys, this.currentLimit, this.currentOffset);
4568
+ const subscription = new Subscription(this.collection, this.query, this.queryArgsCBOR, this.orderBys, this.currentLimit, this.currentOffset);
4569
+ this.collection.store.ditto.subscriptionManager.add(subscription);
4570
+ return subscription;
4410
4571
  }
4411
4572
  /**
4412
4573
  * Enables you to listen for changes that occur in a collection locally.
@@ -4433,7 +4594,7 @@ class PendingCursorOperation extends BasePendingCursorOperation {
4433
4594
  * as you want to keep receiving updates.
4434
4595
  */
4435
4596
  observeLocal(handler) {
4436
- return this._observe(handler, false, false);
4597
+ return this._observe(handler, false);
4437
4598
  }
4438
4599
  /**
4439
4600
  * Enables you to listen for changes that occur in a collection locally and
@@ -4462,7 +4623,7 @@ class PendingCursorOperation extends BasePendingCursorOperation {
4462
4623
  * as you want to keep receiving updates.
4463
4624
  */
4464
4625
  observeLocalWithNextSignal(handler) {
4465
- return this._observe(handler, false, true);
4626
+ return this._observe(handler, true);
4466
4627
  }
4467
4628
  // ----------------------------------------------------------- Internal ------
4468
4629
  /** @internal */
@@ -4470,8 +4631,7 @@ class PendingCursorOperation extends BasePendingCursorOperation {
4470
4631
  super(query, queryArgs, collection);
4471
4632
  }
4472
4633
  /** @internal */
4473
- _observe(handler, createSubscription, waitForNextSignal) {
4474
- const subscription = createSubscription ? this.subscribe() : null;
4634
+ _observe(handler, waitForNextSignal) {
4475
4635
  function wrappedHandler(documents, event, nextSignal) {
4476
4636
  try {
4477
4637
  return handler.call(this, documents, event);
@@ -4481,7 +4641,9 @@ class PendingCursorOperation extends BasePendingCursorOperation {
4481
4641
  }
4482
4642
  }
4483
4643
  const handlerOrWrapped = waitForNextSignal ? handler : wrappedHandler;
4484
- return new LiveQuery(this.query, this.queryArgs, this.queryArgsCBOR, this.orderBys, this.currentLimit, this.currentOffset, this.collection, subscription, handlerOrWrapped);
4644
+ const liveQuery = new LiveQuery(this.query, this.queryArgs, this.queryArgsCBOR, this.orderBys, this.currentLimit, this.currentOffset, this.collection, handlerOrWrapped);
4645
+ this.collection.store.ditto.liveQueryManager.startLiveQuery(liveQuery);
4646
+ return liveQuery;
4485
4647
  }
4486
4648
  }
4487
4649
 
@@ -4509,39 +4671,48 @@ class PendingCursorOperation extends BasePendingCursorOperation {
4509
4671
  */
4510
4672
  class PendingIDSpecificOperation extends BasePendingIDSpecificOperation {
4511
4673
  async remove() {
4512
- const dittoX = Bridge.ditto.pointerFor(this.collection.store.ditto);
4513
- return await performAsyncToWorkaroundNonAsyncFFIAPI(async () => {
4514
- const writeTransactionX = await writeTransaction(dittoX);
4515
- const didRemove = await collectionRemove(dittoX, this.collection.name, writeTransactionX, this.documentIDCBOR);
4516
- await writeTransactionCommit(dittoX, writeTransactionX);
4517
- return didRemove;
4674
+ const ditto = this.collection.store.ditto;
4675
+ const dittoHandle = Bridge.ditto.handleFor(ditto);
4676
+ return ditto.deferCloseAsync(async () => {
4677
+ return await performAsyncToWorkaroundNonAsyncFFIAPI(async () => {
4678
+ const writeTransactionX = await writeTransaction(dittoHandle.deref());
4679
+ const didRemove = await collectionRemove(dittoHandle.deref(), this.collection.name, writeTransactionX, this.documentIDCBOR);
4680
+ await writeTransactionCommit(dittoHandle.deref(), writeTransactionX);
4681
+ return didRemove;
4682
+ });
4518
4683
  });
4519
4684
  }
4520
4685
  async evict() {
4521
- const dittoX = Bridge.ditto.pointerFor(this.collection.store.ditto);
4522
- return await performAsyncToWorkaroundNonAsyncFFIAPI(async () => {
4523
- const writeTransactionX = await writeTransaction(dittoX);
4524
- const didEvict = await collectionEvict(dittoX, this.collection.name, writeTransactionX, this.documentIDCBOR);
4525
- await writeTransactionCommit(dittoX, writeTransactionX);
4526
- return didEvict;
4686
+ const ditto = this.collection.store.ditto;
4687
+ const dittoHandle = Bridge.ditto.handleFor(ditto);
4688
+ return ditto.deferCloseAsync(async () => {
4689
+ return await performAsyncToWorkaroundNonAsyncFFIAPI(async () => {
4690
+ const writeTransactionX = await writeTransaction(dittoHandle.deref());
4691
+ const didEvict = await collectionEvict(dittoHandle.deref(), this.collection.name, writeTransactionX, this.documentIDCBOR);
4692
+ await writeTransactionCommit(dittoHandle.deref(), writeTransactionX);
4693
+ return didEvict;
4694
+ });
4527
4695
  });
4528
4696
  }
4529
4697
  async update(closure) {
4530
- const dittoX = Bridge.ditto.pointerFor(this.collection.store.ditto);
4531
- const readTransactionX = await readTransaction(dittoX);
4532
- const documentX = await collectionGet(dittoX, this.collection.name, this.documentIDCBOR, readTransactionX);
4533
- readTransactionFree(readTransactionX);
4534
- if (!documentX)
4535
- throw new Error(`Can't update, document with ID '${this.documentID.toString()}' not found in collection named '${this.collection.name}'`);
4536
- const mutableDocument = Bridge.mutableDocument.bridge(documentX, () => new MutableDocument());
4537
- closure(mutableDocument);
4538
- // Ownership is transferred back to the FFI layer via collectionUpdate(),
4539
- // we therefore need to explicitly unregister the instance.
4540
- Bridge.mutableDocument.unregister(mutableDocument);
4541
- const writeTransactionX = await writeTransaction(dittoX);
4542
- await collectionUpdate(dittoX, this.collection.name, writeTransactionX, documentX);
4543
- await writeTransactionCommit(dittoX, writeTransactionX);
4544
- return mutableDocument['@ditto.updateResults'].slice();
4698
+ const ditto = this.collection.store.ditto;
4699
+ const dittoHandle = Bridge.ditto.handleFor(ditto);
4700
+ return ditto.deferCloseAsync(async () => {
4701
+ const readTransactionX = await readTransaction(dittoHandle.deref());
4702
+ const documentX = await collectionGet(dittoHandle.deref(), this.collection.name, this.documentIDCBOR, readTransactionX);
4703
+ readTransactionFree(readTransactionX);
4704
+ if (!documentX)
4705
+ throw new Error(`Can't update, document with ID '${this.documentID.toString()}' not found in collection named '${this.collection.name}'`);
4706
+ const mutableDocument = Bridge.mutableDocument.bridge(documentX, () => new MutableDocument());
4707
+ closure(mutableDocument);
4708
+ // Ownership is transferred back to the FFI layer via collectionUpdate(),
4709
+ // we therefore need to explicitly unregister the instance.
4710
+ Bridge.mutableDocument.unregister(mutableDocument);
4711
+ const writeTransactionX = await writeTransaction(dittoHandle.deref());
4712
+ await collectionUpdate(dittoHandle.deref(), this.collection.name, writeTransactionX, documentX);
4713
+ await writeTransactionCommit(dittoHandle.deref(), writeTransactionX);
4714
+ return mutableDocument['@ditto.updateResults'].slice();
4715
+ });
4545
4716
  }
4546
4717
  /**
4547
4718
  * Enables you to subscribe to changes that occur in relation to a document
@@ -4558,7 +4729,9 @@ class PendingIDSpecificOperation extends BasePendingIDSpecificOperation {
4558
4729
  * long as you want to keep receiving updates for the document.
4559
4730
  */
4560
4731
  subscribe() {
4561
- return new Subscription(this.collection, this.query, null, [], -1, 0);
4732
+ const subscription = new Subscription(this.collection, this.query, null, [], -1, 0);
4733
+ this.collection.store.ditto.subscriptionManager.add(subscription);
4734
+ return subscription;
4562
4735
  }
4563
4736
  /**
4564
4737
  * Enables you to listen for changes that occur in relation to a document
@@ -4583,7 +4756,7 @@ class PendingIDSpecificOperation extends BasePendingIDSpecificOperation {
4583
4756
  * as you want to keep receiving updates.
4584
4757
  */
4585
4758
  observeLocal(handler) {
4586
- return this._observe(handler, false, false);
4759
+ return this._observe(handler, false);
4587
4760
  }
4588
4761
  /**
4589
4762
  * Enables you to listen for changes that occur in relation to a document
@@ -4609,16 +4782,15 @@ class PendingIDSpecificOperation extends BasePendingIDSpecificOperation {
4609
4782
  * as you want to keep receiving updates.
4610
4783
  */
4611
4784
  observeLocalWithNextSignal(handler) {
4612
- return this._observe(handler, false, true);
4785
+ return this._observe(handler, true);
4613
4786
  }
4614
4787
  /** @internal */
4615
4788
  constructor(documentID, collection) {
4616
4789
  super(documentID, collection);
4617
4790
  }
4618
4791
  /** @internal */
4619
- _observe(handler, createSubscription, waitForNextSignal) {
4620
- const subscription = createSubscription ? this.subscribe() : null;
4621
- return new LiveQuery(this.query, null, null, [], -1, 0, this.collection, subscription, (documents, event, signalNext) => {
4792
+ _observe(handler, waitForNextSignal) {
4793
+ const liveQuery = new LiveQuery(this.query, null, null, [], -1, 0, this.collection, (documents, event, signalNext) => {
4622
4794
  if (documents.length > 1) {
4623
4795
  const documentIDsAsBase64Strings = documents.map((document) => document.id.toBase64String());
4624
4796
  throw new Error(`Internal inconsistency, single document live query returned more than one document. Query: ${this.query}, documentIDs: ${documentIDsAsBase64Strings.join(', ')}.`);
@@ -4653,6 +4825,8 @@ class PendingIDSpecificOperation extends BasePendingIDSpecificOperation {
4653
4825
  }
4654
4826
  }
4655
4827
  });
4828
+ this.collection.store.ditto.liveQueryManager.startLiveQuery(liveQuery);
4829
+ return liveQuery;
4656
4830
  }
4657
4831
  }
4658
4832
 
@@ -4712,28 +4886,18 @@ class Collection {
4712
4886
  return new PendingIDSpecificOperation(documentID, this);
4713
4887
  }
4714
4888
  async upsert(value, options = {}) {
4715
- var _a;
4716
- const writeStrategy = (_a = options.writeStrategy) !== null && _a !== void 0 ? _a : 'merge';
4717
4889
  const ditto = this.store.ditto;
4718
- const dittoPointer = Bridge.ditto.pointerFor(ditto);
4719
- const id = value._id;
4720
- let documentID;
4721
- if (typeof id === 'undefined') {
4722
- documentID = undefined;
4723
- }
4724
- else if (id instanceof DocumentID) {
4725
- documentID = id;
4726
- }
4727
- else {
4728
- documentID = new DocumentID(id);
4729
- }
4730
- const documentIDCBOR = typeof documentID === 'undefined' ? null : documentID.toCBOR();
4731
- const documentValueJSON = desugarJSObject(value, true);
4732
- const documentValueCBOR = CBOR.encode(documentValueJSON);
4733
- const idCBOR = await performAsyncToWorkaroundNonAsyncFFIAPI(async () => {
4734
- return await collectionInsertValue(dittoPointer, this.name, documentValueCBOR, documentIDCBOR, writeStrategy, undefined);
4890
+ const dittoHandle = Bridge.ditto.handleFor(ditto);
4891
+ return ditto.deferCloseAsync(async () => {
4892
+ var _a;
4893
+ const writeStrategy = (_a = options.writeStrategy) !== null && _a !== void 0 ? _a : 'merge';
4894
+ const documentValueJSON = desugarJSObject(value, true);
4895
+ const documentValueCBOR = CBOR.encode(documentValueJSON);
4896
+ const idCBOR = await performAsyncToWorkaroundNonAsyncFFIAPI(async () => {
4897
+ return await collectionInsertValue(dittoHandle.deref(), this.name, documentValueCBOR, writeStrategy, undefined);
4898
+ });
4899
+ return new DocumentID(idCBOR, true);
4735
4900
  });
4736
- return new DocumentID(idCBOR, true);
4737
4901
  }
4738
4902
  /**
4739
4903
  * Creates a new {@link Attachment} object, which can then be inserted into a
@@ -4763,23 +4927,25 @@ class Collection {
4763
4927
  */
4764
4928
  async newAttachment(pathOrData, metadata = {}) {
4765
4929
  const ditto = this.store.ditto;
4766
- const dittoPointer = Bridge.ditto.pointerFor(ditto);
4767
- const { id, len, handle } = await (async () => {
4768
- if (typeof pathOrData === 'string') {
4769
- {
4770
- return dittoNewAttachmentFromFile(dittoPointer, pathOrData, 'Copy');
4930
+ const dittoHandle = Bridge.ditto.handleFor(ditto);
4931
+ return ditto.deferCloseAsync(async () => {
4932
+ const { id, len, handle } = await (async () => {
4933
+ if (typeof pathOrData === 'string') {
4934
+ {
4935
+ return dittoNewAttachmentFromFile(dittoHandle.deref(), pathOrData, 'Copy');
4936
+ }
4771
4937
  }
4772
- }
4773
- if (pathOrData instanceof Uint8Array) {
4774
- return await dittoNewAttachmentFromBytes(dittoPointer, pathOrData);
4775
- }
4776
- throw new Error(`Can't create new attachment, only file path as string or raw data as Uint8Array are supported, but got: ${typeof pathOrData}, ${pathOrData}`);
4777
- })();
4778
- const attachmentTokenJSON = { _id: id, _len: len, _meta: { ...metadata } };
4779
- attachmentTokenJSON[DittoCRDTTypeKey] = DittoCRDTType.attachment;
4780
- const attachmentToken = new AttachmentToken(attachmentTokenJSON);
4781
- const attachment = new Attachment(ditto, attachmentToken);
4782
- return Bridge.attachment.bridge(handle, () => attachment);
4938
+ if (pathOrData instanceof Uint8Array) {
4939
+ return await dittoNewAttachmentFromBytes(dittoHandle.deref(), pathOrData);
4940
+ }
4941
+ throw new Error(`Can't create new attachment, only file path as string or raw data as Uint8Array are supported, but got: ${typeof pathOrData}, ${pathOrData}`);
4942
+ })();
4943
+ const attachmentTokenJSON = { _id: id, _len: len, _meta: { ...metadata } };
4944
+ attachmentTokenJSON[DittoCRDTTypeKey] = DittoCRDTType.attachment;
4945
+ const attachmentToken = new AttachmentToken(attachmentTokenJSON);
4946
+ const attachment = new Attachment(ditto, attachmentToken);
4947
+ return Bridge.attachment.bridge(handle, () => attachment);
4948
+ });
4783
4949
  }
4784
4950
  /**
4785
4951
  * Trigger an attachment to be downloaded locally to the device and observe
@@ -4806,8 +4972,13 @@ class Collection {
4806
4972
  * fetch status changes.
4807
4973
  */
4808
4974
  fetchAttachment(token, eventHandler) {
4975
+ if (token == null || !(token instanceof AttachmentToken)) {
4976
+ throw new Error(`Invalid attachment token: ${token}`);
4977
+ }
4809
4978
  const ditto = this.store.ditto;
4810
- return new AttachmentFetcher(ditto, token, eventHandler);
4979
+ return ditto.deferClose(() => {
4980
+ return ditto.attachmentFetcherManager.startAttachmentFetcher(token, eventHandler);
4981
+ });
4811
4982
  }
4812
4983
  /** @internal */
4813
4984
  constructor(name, store) {
@@ -4959,7 +5130,7 @@ class PendingCollectionsOperation {
4959
5130
  * as you want to keep receiving updates.
4960
5131
  */
4961
5132
  observeLocal(handler) {
4962
- return this._observe(handler, false, false);
5133
+ return this._observe(handler, false);
4963
5134
  }
4964
5135
  /**
4965
5136
  * Enables you to listen for changes that occur in relation to the collections
@@ -4980,7 +5151,7 @@ class PendingCollectionsOperation {
4980
5151
  * as you want to keep receiving updates.
4981
5152
  */
4982
5153
  observeLocalWithNextSignal(handler) {
4983
- return this._observe(handler, false, true);
5154
+ return this._observe(handler, true);
4984
5155
  }
4985
5156
  /**
4986
5157
  * Return the list of collections requested based on the preceding function
@@ -5004,7 +5175,7 @@ class PendingCollectionsOperation {
5004
5175
  }
5005
5176
  // ----------------------------------------------------------- Internal ------
5006
5177
  /** @internal */
5007
- _observe(handler, createSubscription, waitForNextSignal) {
5178
+ _observe(handler, waitForNextSignal) {
5008
5179
  const weakStore = new WeakRef(this.store);
5009
5180
  const collectionsObservationHandler = function (documents, event, nextSignal) {
5010
5181
  const strongStore = weakStore.deref();
@@ -5035,7 +5206,7 @@ class PendingCollectionsOperation {
5035
5206
  handler(collEvent);
5036
5207
  }
5037
5208
  };
5038
- return this.pendingCursorOperation._observe(collectionsObservationHandler, createSubscription, waitForNextSignal);
5209
+ return this.pendingCursorOperation._observe(collectionsObservationHandler, waitForNextSignal);
5039
5210
  }
5040
5211
  }
5041
5212
  /** @private */
@@ -5076,16 +5247,23 @@ class ExperimentalStore {
5076
5247
  * @returns a promise for an array of Ditto documents matching the query
5077
5248
  **/
5078
5249
  async execute(query) {
5079
- const dittoPointer = Bridge.ditto.pointerFor(this.ditto);
5080
- // A one-off query execution uses a transaction internally but a transaction
5081
- // API is not implemented yet.
5082
- const writeTransaction = null;
5083
- // Not implemented yet.
5084
- const queryParameters = null;
5085
- const documentPointers = await performAsyncToWorkaroundNonAsyncFFIAPI(async () => {
5086
- return await experimentalExecQueryStr(dittoPointer, writeTransaction, query, queryParameters);
5250
+ const ditto = this.ditto;
5251
+ const dittoHandle = Bridge.ditto.handleFor(ditto);
5252
+ return ditto.deferCloseAsync(async () => {
5253
+ // A one-off query execution uses a transaction internally but a
5254
+ // transaction API is not implemented yet.
5255
+ const writeTransaction = null;
5256
+ // Not implemented yet.
5257
+ const queryParameters = null;
5258
+ const documentPointers = await performAsyncToWorkaroundNonAsyncFFIAPI(async () => {
5259
+ return await experimentalExecQueryStr(dittoHandle.deref(), writeTransaction, query, queryParameters);
5260
+ });
5261
+ return documentPointers.map((documentPointer) => Bridge.document.bridge(documentPointer));
5087
5262
  });
5088
- return documentPointers.map((documentPointer) => Bridge.document.bridge(documentPointer));
5263
+ }
5264
+ /** @internal */
5265
+ close() {
5266
+ // Nothing to do yet.
5089
5267
  }
5090
5268
  }
5091
5269
 
@@ -5115,30 +5293,36 @@ class WriteTransactionPendingCursorOperation extends BasePendingCursorOperation
5115
5293
  return super.limit(limit);
5116
5294
  }
5117
5295
  async remove() {
5118
- const query = this.query;
5119
- const dittoX = Bridge.ditto.pointerFor(this.collection.store.ditto);
5120
- const transaction = this.collection.writeTransaction;
5121
- const documentsX = await performAsyncToWorkaroundNonAsyncFFIAPI(async () => collectionRemoveQueryStr(dittoX, this.collection.name, transaction.writeTransactionPointer, query, this.queryArgsCBOR, this.orderBys, this.currentLimit, this.currentOffset));
5122
- const results = documentsX.map((idCBOR) => {
5123
- return new DocumentID(idCBOR, true);
5124
- });
5125
- results.forEach((documentId) => {
5126
- transaction.addResult('removed', documentId, this.collection.name);
5296
+ const ditto = this.collection.store.ditto;
5297
+ const dittoHandle = Bridge.ditto.handleFor(ditto);
5298
+ return ditto.deferCloseAsync(async () => {
5299
+ const query = this.query;
5300
+ const transaction = this.collection.writeTransaction;
5301
+ const documentsX = await performAsyncToWorkaroundNonAsyncFFIAPI(async () => collectionRemoveQueryStr(dittoHandle.deref(), this.collection.name, transaction.writeTransactionPointer, query, this.queryArgsCBOR, this.orderBys, this.currentLimit, this.currentOffset));
5302
+ const results = documentsX.map((idCBOR) => {
5303
+ return new DocumentID(idCBOR, true);
5304
+ });
5305
+ results.forEach((documentId) => {
5306
+ transaction.addResult('removed', documentId, this.collection.name);
5307
+ });
5308
+ return results;
5127
5309
  });
5128
- return results;
5129
5310
  }
5130
5311
  async evict() {
5131
- const query = this.query;
5132
- const dittoX = Bridge.ditto.pointerFor(this.collection.store.ditto);
5133
- const transaction = this.collection.writeTransaction;
5134
- const documentsX = await performAsyncToWorkaroundNonAsyncFFIAPI(async () => collectionEvictQueryStr(dittoX, this.collection.name, transaction.writeTransactionPointer, query, this.queryArgsCBOR, this.orderBys, this.currentLimit, this.currentOffset));
5135
- const results = documentsX.map((idCBOR) => {
5136
- return new DocumentID(idCBOR, true);
5137
- });
5138
- results.forEach((documentId) => {
5139
- transaction.addResult('evicted', documentId, this.collection.name);
5312
+ const ditto = this.collection.store.ditto;
5313
+ const dittoHandle = Bridge.ditto.handleFor(ditto);
5314
+ return ditto.deferCloseAsync(async () => {
5315
+ const query = this.query;
5316
+ const transaction = this.collection.writeTransaction;
5317
+ const documentsX = await performAsyncToWorkaroundNonAsyncFFIAPI(async () => collectionEvictQueryStr(dittoHandle.deref(), this.collection.name, transaction.writeTransactionPointer, query, this.queryArgsCBOR, this.orderBys, this.currentLimit, this.currentOffset));
5318
+ const results = documentsX.map((idCBOR) => {
5319
+ return new DocumentID(idCBOR, true);
5320
+ });
5321
+ results.forEach((documentId) => {
5322
+ transaction.addResult('evicted', documentId, this.collection.name);
5323
+ });
5324
+ return results;
5140
5325
  });
5141
- return results;
5142
5326
  }
5143
5327
  async update(closure) {
5144
5328
  const transaction = this.collection.writeTransaction;
@@ -5158,35 +5342,44 @@ class WriteTransactionPendingCursorOperation extends BasePendingCursorOperation
5158
5342
  //
5159
5343
  class WriteTransactionPendingIDSpecificOperation extends BasePendingIDSpecificOperation {
5160
5344
  async remove() {
5161
- const dittoPointer = Bridge.ditto.pointerFor(this.collection.store.ditto);
5162
- const transaction = this.collection.writeTransaction;
5163
- const result = await performAsyncToWorkaroundNonAsyncFFIAPI(async () => collectionRemove(dittoPointer, this.collection.name, transaction.writeTransactionPointer, this.documentIDCBOR));
5164
- transaction.addResult('removed', this.documentID, this.collection.name);
5165
- return result;
5345
+ const ditto = this.collection.store.ditto;
5346
+ const dittoHandle = Bridge.ditto.handleFor(ditto);
5347
+ return ditto.deferCloseAsync(async () => {
5348
+ const transaction = this.collection.writeTransaction;
5349
+ const result = await performAsyncToWorkaroundNonAsyncFFIAPI(async () => collectionRemove(dittoHandle.deref(), this.collection.name, transaction.writeTransactionPointer, this.documentIDCBOR));
5350
+ transaction.addResult('removed', this.documentID, this.collection.name);
5351
+ return result;
5352
+ });
5166
5353
  }
5167
5354
  async evict() {
5168
- const dittoPointer = Bridge.ditto.pointerFor(this.collection.store.ditto);
5169
- const transaction = this.collection.writeTransaction;
5170
- const result = await performAsyncToWorkaroundNonAsyncFFIAPI(async () => await collectionEvict(dittoPointer, this.collection.name, transaction.writeTransactionPointer, this.documentIDCBOR));
5171
- transaction.addResult('evicted', this.documentID, this.collection.name);
5172
- return result;
5355
+ const ditto = this.collection.store.ditto;
5356
+ const dittoHandle = Bridge.ditto.handleFor(ditto);
5357
+ return ditto.deferCloseAsync(async () => {
5358
+ const transaction = this.collection.writeTransaction;
5359
+ const result = await performAsyncToWorkaroundNonAsyncFFIAPI(async () => await collectionEvict(dittoHandle.deref(), this.collection.name, transaction.writeTransactionPointer, this.documentIDCBOR));
5360
+ transaction.addResult('evicted', this.documentID, this.collection.name);
5361
+ return result;
5362
+ });
5173
5363
  }
5174
5364
  async update(closure) {
5175
- const dittoPointer = Bridge.ditto.pointerFor(this.collection.store.ditto);
5176
- const transaction = this.collection.writeTransaction;
5177
- const readTransactionX = await readTransaction(dittoPointer);
5178
- const documentX = await collectionGet(dittoPointer, this.collection.name, this.documentIDCBOR, readTransactionX);
5179
- readTransactionFree(readTransactionX);
5180
- if (!documentX)
5181
- throw new Error(`Can't update, document with ID '${this.documentID.toString()}' not found in collection named '${this.collection.name}'`);
5182
- const mutableDocument = Bridge.mutableDocument.bridge(documentX, () => new MutableDocument());
5183
- closure(mutableDocument);
5184
- // Ownership is transferred back to the FFI layer via collectionUpdate(),
5185
- // we therefore need to explicitly unregister the instance.
5186
- Bridge.mutableDocument.unregister(mutableDocument);
5187
- await collectionUpdate(dittoPointer, this.collection.name, transaction.writeTransactionPointer, documentX);
5188
- transaction.addResult('updated', this.documentID, this.collection.name);
5189
- return mutableDocument['@ditto.updateResults'].slice();
5365
+ const ditto = this.collection.store.ditto;
5366
+ const dittoHandle = Bridge.ditto.handleFor(ditto);
5367
+ return ditto.deferCloseAsync(async () => {
5368
+ const transaction = this.collection.writeTransaction;
5369
+ const readTransactionX = await readTransaction(dittoHandle.deref());
5370
+ const documentX = await collectionGet(dittoHandle.deref(), this.collection.name, this.documentIDCBOR, readTransactionX);
5371
+ readTransactionFree(readTransactionX);
5372
+ if (!documentX)
5373
+ throw new Error(`Can't update, document with ID '${this.documentID.toString()}' not found in collection named '${this.collection.name}'`);
5374
+ const mutableDocument = Bridge.mutableDocument.bridge(documentX, () => new MutableDocument());
5375
+ closure(mutableDocument);
5376
+ // Ownership is transferred back to the FFI layer via collectionUpdate(),
5377
+ // we therefore need to explicitly unregister the instance.
5378
+ Bridge.mutableDocument.unregister(mutableDocument);
5379
+ await collectionUpdate(dittoHandle.deref(), this.collection.name, transaction.writeTransactionPointer, documentX);
5380
+ transaction.addResult('updated', this.documentID, this.collection.name);
5381
+ return mutableDocument['@ditto.updateResults'].slice();
5382
+ });
5190
5383
  }
5191
5384
  // ----------------------------------------------------------- Internal ------
5192
5385
  /** @internal */
@@ -5241,30 +5434,20 @@ class WriteTransactionCollection {
5241
5434
  return new WriteTransactionPendingIDSpecificOperation(documentID, this);
5242
5435
  }
5243
5436
  async upsert(value, options = {}) {
5244
- var _a;
5245
- const writeStrategy = (_a = options.writeStrategy) !== null && _a !== void 0 ? _a : 'merge';
5246
- const dittoPointer = Bridge.ditto.pointerFor(this.store.ditto);
5247
- const id = value._id;
5248
- let documentID;
5249
- if (typeof id === 'undefined') {
5250
- documentID = undefined;
5251
- }
5252
- else if (id instanceof DocumentID) {
5253
- documentID = id;
5254
- }
5255
- else {
5256
- documentID = new DocumentID(id);
5257
- }
5258
- const documentIDCBOR = typeof documentID === 'undefined' ? null : documentID.toCBOR();
5259
- const documentValueJSON = desugarJSObject(value, true);
5260
- const documentValueCBOR = CBOR.encode(documentValueJSON);
5261
- const idCBOR = await performAsyncToWorkaroundNonAsyncFFIAPI(async () => {
5262
- // @ts-ignore
5263
- return await collectionInsertValue(dittoPointer, this.name, documentValueCBOR, documentIDCBOR, writeStrategy, this.writeTransaction.writeTransactionPointer);
5437
+ const ditto = this.store.ditto;
5438
+ const dittoHandle = Bridge.ditto.handleFor(ditto);
5439
+ return ditto.deferCloseAsync(async () => {
5440
+ var _a;
5441
+ const writeStrategy = (_a = options.writeStrategy) !== null && _a !== void 0 ? _a : 'merge';
5442
+ const documentValueJSON = desugarJSObject(value, true);
5443
+ const documentValueCBOR = CBOR.encode(documentValueJSON);
5444
+ const idCBOR = await performAsyncToWorkaroundNonAsyncFFIAPI(async () => {
5445
+ return await collectionInsertValue(dittoHandle.deref(), this.name, documentValueCBOR, writeStrategy, this.writeTransaction.writeTransactionPointer);
5446
+ });
5447
+ const insertedDocumentId = new DocumentID(idCBOR, true);
5448
+ this.writeTransaction.addResult('inserted', insertedDocumentId, this.name);
5449
+ return new DocumentID(idCBOR, true);
5264
5450
  });
5265
- const insertedDocumentId = new DocumentID(idCBOR, true);
5266
- this.writeTransaction.addResult('inserted', insertedDocumentId, this.name);
5267
- return new DocumentID(idCBOR, true);
5268
5451
  }
5269
5452
  /**
5270
5453
  * See comment in {@link CollectionInterface.findByIDCBOR()}
@@ -5303,9 +5486,11 @@ class WriteTransaction {
5303
5486
  * @internal
5304
5487
  */
5305
5488
  static async init(ditto) {
5306
- const dittoPointer = Bridge.ditto.pointerFor(ditto);
5307
- const writeTransactionPointer = await writeTransaction(dittoPointer);
5308
- return new WriteTransaction(ditto, writeTransactionPointer);
5489
+ return ditto.deferCloseAsync(async () => {
5490
+ const dittoHandle = Bridge.ditto.handleFor(ditto);
5491
+ const writeTransactionPointer = await writeTransaction(dittoHandle.deref());
5492
+ return new WriteTransaction(ditto, writeTransactionPointer);
5493
+ });
5309
5494
  }
5310
5495
  /**
5311
5496
  * Creates a transaction-specific
@@ -5324,13 +5509,19 @@ class WriteTransaction {
5324
5509
  // ----------------------------------------------------------- Internal ------
5325
5510
  /** @internal */
5326
5511
  async commit() {
5327
- const dittoPointer = Bridge.ditto.pointerFor(this.ditto);
5328
- return writeTransactionCommit(dittoPointer, this.writeTransactionPointer);
5512
+ const ditto = this.ditto;
5513
+ const dittoHandle = Bridge.ditto.handleFor(ditto);
5514
+ return ditto.deferCloseAsync(async () => {
5515
+ return writeTransactionCommit(dittoHandle.deref(), this.writeTransactionPointer);
5516
+ });
5329
5517
  }
5330
5518
  /** @internal */
5331
5519
  async rollback() {
5332
- const dittoPointer = Bridge.ditto.pointerFor(this.ditto);
5333
- return writeTransactionRollback(dittoPointer, this.writeTransactionPointer);
5520
+ const ditto = this.ditto;
5521
+ const dittoHandle = Bridge.ditto.handleFor(ditto);
5522
+ return ditto.deferCloseAsync(async () => {
5523
+ return writeTransactionRollback(dittoHandle.deref(), this.writeTransactionPointer);
5524
+ });
5334
5525
  }
5335
5526
  /**
5336
5527
  * Adds an entry to the list of results that is returned at the end of a
@@ -5377,14 +5568,18 @@ class Store {
5377
5568
  * fetch or observe the collections in the store
5378
5569
  */
5379
5570
  collections() {
5380
- return new PendingCollectionsOperation(this.ditto.store);
5571
+ return new PendingCollectionsOperation(this);
5381
5572
  }
5382
5573
  /**
5383
5574
  * Returns the names of all available collections in the store of the
5384
5575
  * related {@link Ditto} instance.
5385
5576
  */
5386
5577
  collectionNames() {
5387
- return dittoGetCollectionNames(Bridge.ditto.pointerFor(this.ditto));
5578
+ const ditto = this.ditto;
5579
+ const dittoHandle = Bridge.ditto.handleFor(ditto);
5580
+ return ditto.deferClose(() => {
5581
+ return dittoGetCollectionNames(dittoHandle.deref());
5582
+ });
5388
5583
  }
5389
5584
  /**
5390
5585
  * Initiate a write transaction in a callback.
@@ -5395,26 +5590,20 @@ class Store {
5395
5590
  * @returns a list of `WriteTransactionResult`s. There is a result for each operation performed as part of the write transaction.
5396
5591
  */
5397
5592
  async write(callback) {
5398
- const transaction = await WriteTransaction.init(this.ditto);
5399
5593
  // Run caller's callback, rolling back if needed.
5400
- try {
5401
- await callback(transaction);
5402
- }
5403
- catch (error) {
5404
- await transaction.rollback();
5405
- Logger.warning(`Transaction rolled back due to an error: ${error === null || error === void 0 ? void 0 : error.message}`);
5406
- throw error;
5407
- }
5408
- // Commit transaction, rolling back if needed.
5409
- try {
5594
+ return this.ditto.deferCloseAsync(async () => {
5595
+ const transaction = await WriteTransaction.init(this.ditto);
5596
+ try {
5597
+ await callback(transaction);
5598
+ }
5599
+ catch (error) {
5600
+ await transaction.rollback();
5601
+ Logger.warning(`Transaction rolled back due to an error: ${error === null || error === void 0 ? void 0 : error.message}`);
5602
+ throw error;
5603
+ }
5410
5604
  await transaction.commit();
5411
- }
5412
- catch (error) {
5413
- await transaction.rollback();
5414
- Logger.warning(`Transaction rolled back due to an error: ${error === null || error === void 0 ? void 0 : error.message}`);
5415
- throw error;
5416
- }
5417
- return transaction.results;
5605
+ return transaction.results;
5606
+ });
5418
5607
  }
5419
5608
  // ----------------------------------------------------------- Internal ------
5420
5609
  /** @internal */
@@ -5422,14 +5611,24 @@ class Store {
5422
5611
  this.ditto = ditto;
5423
5612
  this.experimental = new ExperimentalStore(ditto);
5424
5613
  }
5614
+ /** @internal */
5615
+ close() {
5616
+ this.experimental.close();
5617
+ // NOTE: live query webhook is taken care of by the FFI's
5618
+ // `ditto_shutdown()`, no need to unregister it here.
5619
+ }
5425
5620
  /**
5426
5621
  * Private method, used only by the Portal https://github.com/getditto/ditto/pull/3652
5427
5622
  * @internal
5428
5623
  */
5429
5624
  async registerLiveQueryWebhook(collectionName, query, url) {
5430
- const validatedQuery = validateQuery(query);
5431
- const idCBOR = await liveQueryWebhookRegister(Bridge.ditto.pointerFor(this.ditto), collectionName, validatedQuery, [], 0, 0, url);
5432
- return new DocumentID(idCBOR, true);
5625
+ const ditto = this.ditto;
5626
+ const dittoHandle = Bridge.ditto.handleFor(ditto);
5627
+ return ditto.deferCloseAsync(async () => {
5628
+ const validatedQuery = validateQuery(query);
5629
+ const idCBOR = await liveQueryWebhookRegister(dittoHandle.deref(), collectionName, validatedQuery, [], 0, 0, url);
5630
+ return new DocumentID(idCBOR, true);
5631
+ });
5433
5632
  }
5434
5633
  }
5435
5634
 
@@ -5457,9 +5656,12 @@ class Presence {
5457
5656
  * connections between them.
5458
5657
  */
5459
5658
  get graph() {
5460
- const dittoPointer = Bridge.ditto.pointerFor(this.ditto);
5461
- const graphJSONString = dittoPresenceV3(dittoPointer);
5462
- return JSON.parse(graphJSONString);
5659
+ const ditto = this.ditto;
5660
+ const dittoHandle = Bridge.ditto.handleFor(ditto);
5661
+ return ditto.deferClose(() => {
5662
+ const graphJSONString = dittoPresenceV3(dittoHandle.deref());
5663
+ return JSON.parse(graphJSONString);
5664
+ });
5463
5665
  }
5464
5666
  /**
5465
5667
  * Request information about Ditto peers in range of this device.
@@ -5486,12 +5688,18 @@ class Presence {
5486
5688
  this.observerManager = new ObserverManager('PresenceObservation', {
5487
5689
  keepAlive: ditto.keepAlive,
5488
5690
  register: (callback) => {
5489
- const dittoPointer = Bridge.ditto.pointerFor(this.ditto);
5490
- dittoRegisterPresenceV3Callback(dittoPointer, callback);
5691
+ const ditto = this.ditto;
5692
+ const dittoHandle = Bridge.ditto.handleFor(ditto);
5693
+ ditto.deferClose(() => {
5694
+ dittoRegisterPresenceV3Callback(dittoHandle.deref(), callback);
5695
+ });
5491
5696
  },
5492
5697
  unregister: () => {
5493
- const dittoPointer = Bridge.ditto.pointerFor(this.ditto);
5494
- dittoClearPresenceV3Callback(dittoPointer);
5698
+ const ditto = this.ditto;
5699
+ const dittoHandle = Bridge.ditto.handleFor(ditto);
5700
+ ditto.deferClose(() => {
5701
+ dittoClearPresenceV3Callback(dittoHandle.deref());
5702
+ });
5495
5703
  },
5496
5704
  process: (presenceGraphJSONString) => {
5497
5705
  const presenceGraph = JSON.parse(presenceGraphJSONString);
@@ -5499,22 +5707,114 @@ class Presence {
5499
5707
  },
5500
5708
  });
5501
5709
  }
5710
+ /** @internal */
5711
+ close() {
5712
+ this.observerManager.close();
5713
+ }
5714
+ }
5715
+
5716
+ //
5717
+ /** @internal */
5718
+ class LiveQueryManager {
5719
+ /** @internal */
5720
+ constructor(ditto, keepAlive) {
5721
+ this.finalizationRegistry = new FinalizationRegistry(this.finalize);
5722
+ this.ditto = ditto;
5723
+ this.keepAlive = keepAlive;
5724
+ this.liveQueriesByID = {};
5725
+ }
5726
+ /** @internal */
5727
+ startLiveQuery(liveQuery) {
5728
+ const ditto = this.ditto;
5729
+ const dittoHandle = Bridge.ditto.handleFor(ditto);
5730
+ // REFACTOR: the starting closure runs detached from here which is a smell.
5731
+ // Can we make this whole starting mechanism non-async? The culprit is
5732
+ // the workaround for a zalgo with `ditto_live_query_start()` FFI function:
5733
+ // It immediately triggers the live query handler making it run "in-line"
5734
+ // when creating a live query, while subsequent invocations are async
5735
+ // (by definition). This is a classic zalgo case. Fix by making
5736
+ // `ditto_live_query_start()` trigger the first live query callback `async`,
5737
+ // just like the subsequent ones.
5738
+ ditto.deferCloseAsync(async () => {
5739
+ const liveQueryID = liveQuery.liveQueryID;
5740
+ if (!liveQueryID) {
5741
+ throw new Error("Internal inconsistency, tried to add a live query that doesn't have a live query ID (probably stopped).");
5742
+ }
5743
+ const existingLiveQuery = this.liveQueriesByID[liveQueryID];
5744
+ if (existingLiveQuery) {
5745
+ throw new Error('Internal inconsistency, tried to add a live query with an ID that has already been added.');
5746
+ }
5747
+ const weakLiveQuery = new WeakRef(liveQuery);
5748
+ this.liveQueriesByID[liveQueryID] = weakLiveQuery;
5749
+ this.finalizationRegistry.register(liveQuery, liveQueryID, this.finalize);
5750
+ liveQuery.liveQueryManager = this;
5751
+ ditto.keepAlive.retain(`LiveQuery.${liveQueryID}`);
5752
+ return new Promise((resolve, reject) => {
5753
+ // not awaited on purpose; let the invocation of the initial observation
5754
+ // happen in a "fire-and-forget" / detached / escaping fashion, to be
5755
+ // consistent with the subsequent invocations.
5756
+ step(async () => {
5757
+ await liveQueryStart(dittoHandle.deref(), liveQueryID);
5758
+ // @ts-ignore
5759
+ resolve();
5760
+ });
5761
+ });
5762
+ });
5763
+ }
5764
+ /** @internal */
5765
+ stopLiveQuery(liveQuery) {
5766
+ this.finalizationRegistry.unregister(liveQuery);
5767
+ const liveQueryID = liveQuery.liveQueryID;
5768
+ if (!liveQueryID) {
5769
+ throw new Error("Internal inconsistency, tried to remove a live query that doesn't have a live query ID (probably stopped).");
5770
+ }
5771
+ liveQuery.liveQueryManager = null;
5772
+ this.stopLiveQueryWithID(liveQueryID);
5773
+ }
5774
+ /** @internal */
5775
+ close() {
5776
+ for (const liveQueryID in this.liveQueriesByID) {
5777
+ const weakLiveQuery = this.liveQueriesByID[liveQueryID];
5778
+ const liveQuery = weakLiveQuery.deref();
5779
+ if (liveQuery) {
5780
+ this.stopLiveQuery(liveQuery);
5781
+ }
5782
+ }
5783
+ }
5784
+ stopLiveQueryWithID(liveQueryID) {
5785
+ const ditto = this.ditto;
5786
+ const dittoHandle = Bridge.ditto.handleFor(ditto);
5787
+ ditto.deferClose(() => {
5788
+ liveQueryStop(dittoHandle.deref(), liveQueryID);
5789
+ this.keepAlive.release(`LiveQuery.${liveQueryID}`);
5790
+ delete this.liveQueriesByID[liveQueryID];
5791
+ });
5792
+ }
5793
+ finalize(liveQueryID) {
5794
+ this.stopLiveQueryWithID(liveQueryID);
5795
+ }
5502
5796
  }
5503
5797
 
5504
5798
  //
5505
5799
  /**
5506
- @internal
5507
- @deprecated Replaced by `Presence`.
5508
- */
5800
+ * @internal
5801
+ * @deprecated Replaced by `Presence`.
5802
+ */
5509
5803
  class PresenceManager {
5510
5804
  constructor(ditto) {
5511
5805
  this.ditto = ditto;
5806
+ this.isClosed = false;
5512
5807
  this.isRegistered = false;
5513
5808
  this.currentRemotePeers = [];
5514
5809
  this.callbacksByPresenceToken = {};
5515
5810
  }
5516
5811
  /** @internal */
5517
5812
  addObserver(callback) {
5813
+ if (this.isClosed) {
5814
+ // REFACTOR: throw a catchable error here, such that calling code
5815
+ // can be more specific when forwarding it to the user.
5816
+ throw new Error(`Internal inconsistency, can't add presence observer, observer mananger close()-ed.`);
5817
+ }
5518
5818
  this.registerIfNeeded();
5519
5819
  const token = generateEphemeralToken();
5520
5820
  this.callbacksByPresenceToken[token] = callback;
@@ -5529,31 +5829,62 @@ class PresenceManager {
5529
5829
  }
5530
5830
  /** @internal */
5531
5831
  removeObserver(token) {
5532
- this.ditto.keepAlive.release(`PresenceObservation.${token}`);
5533
- delete this.callbacksByPresenceToken[token];
5534
- this.unregisterIfNeeded();
5832
+ const callback = this.callbacksByPresenceToken[token];
5833
+ if (typeof callback === 'undefined') {
5834
+ throw new Error(`Can't remove presence observer, token '${token}' has never been registered before.`);
5835
+ }
5836
+ if (callback === null) {
5837
+ // Observer has already been removed, no-op.
5838
+ return;
5839
+ }
5840
+ if (typeof this.callbacksByPresenceToken[token] != 'undefined') {
5841
+ this.ditto.keepAlive.release(`PresenceObservation.${token}`);
5842
+ // REFACTOR: not deleting the token here will keep eating up
5843
+ // memory over long periods of time. We actually need to track
5844
+ // the observer objects themselves and remove it from the table
5845
+ // as soon as the observer object is garbage collected.
5846
+ this.callbacksByPresenceToken[token] = null;
5847
+ this.unregisterIfNeeded();
5848
+ }
5849
+ }
5850
+ /** @internal */
5851
+ hasObserver(token) {
5852
+ return typeof this.callbacksByPresenceToken[token] != 'undefined';
5853
+ }
5854
+ /** @internal */
5855
+ close() {
5856
+ this.isClosed = true;
5857
+ for (const token in this.callbacksByPresenceToken) {
5858
+ this.removeObserver(token);
5859
+ }
5535
5860
  }
5536
5861
  hasObservers() {
5537
5862
  return Object.keys(this.callbacksByPresenceToken).length > 0;
5538
5863
  }
5539
5864
  registerIfNeeded() {
5540
- const needsToRegister = !this.isRegistered;
5541
- if (needsToRegister) {
5542
- this.isRegistered = true;
5543
- const dittoPointer = Bridge.ditto.pointerFor(this.ditto);
5544
- const remotePeersJSONString = dittoPresenceV1(dittoPointer);
5545
- this.currentRemotePeers = this.decode(remotePeersJSONString).sort(this.compareRemotePeers);
5546
- dittoRegisterPresenceV1Callback(dittoPointer, this.handlePresenceV1Callback.bind(this));
5547
- }
5865
+ const ditto = this.ditto;
5866
+ const dittoHandle = Bridge.ditto.handleFor(this.ditto);
5867
+ ditto.deferClose(() => {
5868
+ const needsToRegister = !this.isRegistered;
5869
+ if (needsToRegister) {
5870
+ this.isRegistered = true;
5871
+ const remotePeersJSONString = dittoPresenceV1(dittoHandle.deref());
5872
+ this.currentRemotePeers = this.decode(remotePeersJSONString).sort(this.compareRemotePeers);
5873
+ dittoRegisterPresenceV1Callback(dittoHandle.deref(), this.handlePresenceV1Callback.bind(this));
5874
+ }
5875
+ });
5548
5876
  }
5549
5877
  unregisterIfNeeded() {
5550
- const needsToUnregister = !this.hasObservers() && this.isRegistered;
5551
- if (needsToUnregister) {
5552
- this.isRegistered = false;
5553
- const dittoPointer = Bridge.ditto.pointerFor(this.ditto);
5554
- dittoRegisterPresenceV1Callback(dittoPointer, null);
5555
- this.currentRemotePeers = [];
5556
- }
5878
+ const ditto = this.ditto;
5879
+ const dittoHandle = Bridge.ditto.handleFor(ditto);
5880
+ ditto.deferClose(() => {
5881
+ const needsToUnregister = !this.hasObservers() && this.isRegistered;
5882
+ if (needsToUnregister) {
5883
+ this.isRegistered = false;
5884
+ dittoClearPresenceCallback(dittoHandle.deref());
5885
+ this.currentRemotePeers = [];
5886
+ }
5887
+ });
5557
5888
  }
5558
5889
  handlePresenceV1Callback(remotePeersJSONString) {
5559
5890
  const remotePeers = this.decode(remotePeersJSONString).sort(this.compareRemotePeers);
@@ -5561,6 +5892,10 @@ class PresenceManager {
5561
5892
  this.notify();
5562
5893
  }
5563
5894
  notify() {
5895
+ if (this.isClosed) {
5896
+ // NOTE: we don't notify observers after closing.
5897
+ return;
5898
+ }
5564
5899
  for (const token in this.callbacksByPresenceToken) {
5565
5900
  const callback = this.callbacksByPresenceToken[token];
5566
5901
  callback(this.currentRemotePeers);
@@ -5579,9 +5914,6 @@ class PresenceManager {
5579
5914
  };
5580
5915
  });
5581
5916
  }
5582
- finalize(token) {
5583
- this.removeObserver(token);
5584
- }
5585
5917
  compareRemotePeers(left, right) {
5586
5918
  // NOTE: we use the exact same sort order here as in the ObjC version.
5587
5919
  if (left.connections.length === 0 && right.connections.length > 0)
@@ -5605,12 +5937,18 @@ class TransportConditionsManager extends ObserverManager {
5605
5937
  this.ditto = ditto;
5606
5938
  }
5607
5939
  register(callback) {
5608
- const dittoPointer = Bridge.ditto.pointerFor(this.ditto);
5609
- return dittoRegisterTransportConditionChangedCallback(dittoPointer, callback);
5940
+ const ditto = this.ditto;
5941
+ const dittoHandle = Bridge.ditto.handleFor(ditto);
5942
+ return ditto.deferClose(() => {
5943
+ return dittoRegisterTransportConditionChangedCallback(dittoHandle.deref(), callback);
5944
+ });
5610
5945
  }
5611
5946
  unregister() {
5612
- const dittoPointer = Bridge.ditto.pointerFor(this.ditto);
5613
- return dittoRegisterTransportConditionChangedCallback(dittoPointer, null);
5947
+ const ditto = this.ditto;
5948
+ const dittoHandle = Bridge.ditto.handleFor(ditto);
5949
+ return ditto.deferClose(() => {
5950
+ return dittoRegisterTransportConditionChangedCallback(dittoHandle.deref(), null);
5951
+ });
5614
5952
  }
5615
5953
  process(conditionSource, transportCondition) {
5616
5954
  /* eslint-disable */
@@ -5717,277 +6055,582 @@ class Sync {
5717
6055
  this.updateConnectRetryInterval(stateOld, stateNew);
5718
6056
  this.state = stateNew;
5719
6057
  }
6058
+ /** @internal */
6059
+ close() {
6060
+ if (this.parameters.isSyncActive) {
6061
+ throw new Error(`Internal inconsistency, can't close sync object while sync is active, please 'stopSync()' first.`);
6062
+ }
6063
+ // Nothing to do, when sync is stopped, this object should be
6064
+ // already be cleaned up properly.
6065
+ }
5720
6066
  updatePeerToPeerBluetoothLE(stateOld, stateNew) {
5721
- const dittoPointer = Bridge.ditto.pointerFor(this.ditto);
5722
- const bluetoothLEOld = stateOld.effectiveTransportConfig.peerToPeer.bluetoothLE;
5723
- const bluetoothLENew = stateNew.effectiveTransportConfig.peerToPeer.bluetoothLE;
5724
- const shouldStart = !bluetoothLEOld.isEnabled && bluetoothLENew.isEnabled;
5725
- const shouldStop = bluetoothLEOld.isEnabled && !bluetoothLENew.isEnabled;
5726
- if (shouldStart && this.bluetoothLETransportPointer)
5727
- throw new Error(`Internal inconsistency, when starting BLE transport, no BLE transport pointer should exist.`);
5728
- if (shouldStop && !this.bluetoothLETransportPointer)
5729
- throw new Error(`Internal inconsistency, when stopping BLE transport, a BLE transport pointer should exist.`);
5730
- {
5731
- // HACK: quick & dirty Linux & Windows hack. A proper implementation
5732
- // should encapsulate everything behind the transports module. To undo,
5733
- // remove the whole if block.
5734
- if (process.platform === 'linux' || process.platform === 'win32') {
5735
- if (shouldStart) {
5736
- const clientTransport = dittoAddInternalBLEClientTransport(dittoPointer);
5737
- const serverTransport = dittoAddInternalBLEServerTransport(dittoPointer);
5738
- const blePlatform = { clientTransport, serverTransport };
5739
- this.bluetoothLETransportPointer = blePlatform;
5740
- }
5741
- if (shouldStop) {
5742
- const blePlatform = this.bluetoothLETransportPointer;
5743
- const { clientTransport, serverTransport } = blePlatform;
5744
- bleServerFreeHandle(serverTransport);
5745
- bleClientFreeHandle(clientTransport);
5746
- this.bluetoothLETransportPointer = null;
6067
+ const ditto = this.ditto;
6068
+ const dittoHandle = Bridge.ditto.handleFor(ditto);
6069
+ ditto.deferClose(() => {
6070
+ const bluetoothLEOld = stateOld.effectiveTransportConfig.peerToPeer.bluetoothLE;
6071
+ const bluetoothLENew = stateNew.effectiveTransportConfig.peerToPeer.bluetoothLE;
6072
+ const shouldStart = !bluetoothLEOld.isEnabled && bluetoothLENew.isEnabled;
6073
+ const shouldStop = bluetoothLEOld.isEnabled && !bluetoothLENew.isEnabled;
6074
+ if (shouldStart && this.bluetoothLETransportPointer)
6075
+ throw new Error(`Internal inconsistency, when starting BLE transport, no BLE transport pointer should exist.`);
6076
+ if (shouldStop && !this.bluetoothLETransportPointer)
6077
+ throw new Error(`Internal inconsistency, when stopping BLE transport, a BLE transport pointer should exist.`);
6078
+ {
6079
+ // HACK: quick & dirty Linux & Windows hack. A proper implementation
6080
+ // should encapsulate everything behind the transports module. To undo,
6081
+ // remove the whole if block.
6082
+ if (process.platform === 'linux' || process.platform === 'win32') {
6083
+ if (shouldStart) {
6084
+ const clientTransport = dittoAddInternalBLEClientTransport(dittoHandle.deref());
6085
+ const serverTransport = dittoAddInternalBLEServerTransport(dittoHandle.deref());
6086
+ const blePlatform = { clientTransport, serverTransport };
6087
+ this.bluetoothLETransportPointer = blePlatform;
6088
+ }
6089
+ if (shouldStop) {
6090
+ const blePlatform = this.bluetoothLETransportPointer;
6091
+ const { clientTransport, serverTransport } = blePlatform;
6092
+ bleServerFreeHandle(serverTransport);
6093
+ bleClientFreeHandle(clientTransport);
6094
+ this.bluetoothLETransportPointer = null;
6095
+ }
6096
+ return;
5747
6097
  }
5748
- return;
5749
6098
  }
5750
- }
5751
- if (shouldStart) {
5752
- this.bluetoothLETransportPointer = bleCreate(dittoPointer);
5753
- }
5754
- if (shouldStop) {
5755
- bleDestroy(this.bluetoothLETransportPointer);
5756
- delete this.bluetoothLETransportPointer;
5757
- }
6099
+ if (shouldStart) {
6100
+ this.bluetoothLETransportPointer = bleCreate(dittoHandle.deref());
6101
+ }
6102
+ if (shouldStop) {
6103
+ bleDestroy(this.bluetoothLETransportPointer);
6104
+ delete this.bluetoothLETransportPointer;
6105
+ }
6106
+ });
5758
6107
  }
5759
6108
  updatePeerToPeerAWDL(stateOld, stateNew) {
5760
- const dittoPointer = Bridge.ditto.pointerFor(this.ditto);
5761
- const awdlOld = stateOld.effectiveTransportConfig.peerToPeer.awdl;
5762
- const awdlNew = stateNew.effectiveTransportConfig.peerToPeer.awdl;
5763
- const shouldStart = !awdlOld.isEnabled && awdlNew.isEnabled;
5764
- const shouldStop = awdlOld.isEnabled && !awdlNew.isEnabled;
5765
- if (shouldStart && this.awdlTransportPointer)
5766
- throw new Error(`Internal inconsistency, when starting AWDL transport, no AWDL transport pointer should exist.`);
5767
- if (shouldStop && !this.awdlTransportPointer)
5768
- throw new Error(`Internal inconsistency, when stopping AWDL transport, an AWDL transport pointer should exist.`);
5769
- if (shouldStart) {
5770
- this.awdlTransportPointer = awdlCreate(dittoPointer);
5771
- }
5772
- if (shouldStop) {
5773
- awdlDestroy(this.awdlTransportPointer);
5774
- this.awdlTransportPointer = null;
5775
- }
6109
+ const ditto = this.ditto;
6110
+ const dittoHandle = Bridge.ditto.handleFor(ditto);
6111
+ ditto.deferClose(() => {
6112
+ const awdlOld = stateOld.effectiveTransportConfig.peerToPeer.awdl;
6113
+ const awdlNew = stateNew.effectiveTransportConfig.peerToPeer.awdl;
6114
+ const shouldStart = !awdlOld.isEnabled && awdlNew.isEnabled;
6115
+ const shouldStop = awdlOld.isEnabled && !awdlNew.isEnabled;
6116
+ if (shouldStart && this.awdlTransportPointer)
6117
+ throw new Error(`Internal inconsistency, when starting AWDL transport, no AWDL transport pointer should exist.`);
6118
+ if (shouldStop && !this.awdlTransportPointer)
6119
+ throw new Error(`Internal inconsistency, when stopping AWDL transport, an AWDL transport pointer should exist.`);
6120
+ if (shouldStart) {
6121
+ this.awdlTransportPointer = awdlCreate(dittoHandle.deref());
6122
+ }
6123
+ if (shouldStop) {
6124
+ awdlDestroy(this.awdlTransportPointer);
6125
+ this.awdlTransportPointer = null;
6126
+ }
6127
+ });
5776
6128
  }
5777
6129
  updatePeerToPeerLAN(stateOld, stateNew) {
5778
- const dittoPointer = Bridge.ditto.pointerFor(this.ditto);
5779
- const lanOld = stateOld.effectiveTransportConfig.peerToPeer.lan;
5780
- const lanNew = stateNew.effectiveTransportConfig.peerToPeer.lan;
5781
- {
5782
- // HACK: quick & dirty Linux & Windows hack. A proper implementation
5783
- // should encapsulate everything behind the transports module. To undo,
5784
- // remove the whole if block.
5785
- if (process.platform === 'win32' || process.platform === 'linux') {
5786
- if (lanOld.isEnabled) {
5787
- if (lanOld.isMdnsEnabled) {
5788
- mdnsClientFreeHandle(this.mdnsClientTransportPointer);
5789
- this.mdnsClientTransportPointer = null;
5790
- mdnsServerFreeHandle(this.mdnsServerAdvertiserPointer);
5791
- this.mdnsServerAdvertiserPointer = null;
5792
- }
5793
- if (lanOld.isMulticastEnabled) {
5794
- dittoRemoveMulticastTransport(dittoPointer);
6130
+ const ditto = this.ditto;
6131
+ const dittoHandle = Bridge.ditto.handleFor(ditto);
6132
+ ditto.deferClose(() => {
6133
+ const lanOld = stateOld.effectiveTransportConfig.peerToPeer.lan;
6134
+ const lanNew = stateNew.effectiveTransportConfig.peerToPeer.lan;
6135
+ {
6136
+ // HACK: quick & dirty Linux & Windows hack. A proper implementation
6137
+ // should encapsulate everything behind the transports module. To undo,
6138
+ // remove the whole if block.
6139
+ if (process.platform === 'win32' || process.platform === 'linux') {
6140
+ if (lanOld.isEnabled) {
6141
+ if (lanOld.isMdnsEnabled) {
6142
+ mdnsClientFreeHandle(this.mdnsClientTransportPointer);
6143
+ this.mdnsClientTransportPointer = null;
6144
+ mdnsServerFreeHandle(this.mdnsServerAdvertiserPointer);
6145
+ this.mdnsServerAdvertiserPointer = null;
6146
+ }
6147
+ if (lanOld.isMulticastEnabled) {
6148
+ dittoRemoveMulticastTransport(dittoHandle.deref());
6149
+ }
5795
6150
  }
5796
- }
5797
- if (lanNew.isEnabled) {
5798
- if (lanNew.isMdnsEnabled) {
5799
- this.mdnsClientTransportPointer = dittoAddInternalMdnsTransport(dittoPointer);
5800
- this.mdnsServerAdvertiserPointer = dittoAddInternalMdnsAdvertiser(dittoPointer);
5801
- }
5802
- if (lanNew.isMulticastEnabled) {
5803
- dittoAddMulticastTransport(dittoPointer);
6151
+ if (lanNew.isEnabled) {
6152
+ if (lanNew.isMdnsEnabled) {
6153
+ this.mdnsClientTransportPointer = dittoAddInternalMdnsTransport(dittoHandle.deref());
6154
+ this.mdnsServerAdvertiserPointer = dittoAddInternalMdnsAdvertiser(dittoHandle.deref());
6155
+ }
6156
+ if (lanNew.isMulticastEnabled) {
6157
+ dittoAddMulticastTransport(dittoHandle.deref());
6158
+ }
5804
6159
  }
6160
+ return;
5805
6161
  }
5806
- return;
5807
- }
5808
- }
5809
- // IDEA: move the logic for this dance into stateFrom() signal
5810
- // via some additional state signaling the actions here.
5811
- if (lanOld.isEnabled) {
5812
- if (lanOld.isMdnsEnabled) {
5813
- lanDestroy(this.lanTransportPointer);
5814
- delete this.lanTransportPointer;
5815
- }
5816
- if (lanOld.isMulticastEnabled) {
5817
- dittoRemoveMulticastTransport(dittoPointer);
5818
6162
  }
5819
- }
5820
- if (lanNew.isEnabled) {
5821
- if (lanNew.isMdnsEnabled) {
5822
- this.lanTransportPointer = lanCreate(dittoPointer);
6163
+ // IDEA: move the logic for this dance into stateFrom() signal
6164
+ // via some additional state signaling the actions here.
6165
+ if (lanOld.isEnabled) {
6166
+ if (lanOld.isMdnsEnabled) {
6167
+ lanDestroy(this.lanTransportPointer);
6168
+ delete this.lanTransportPointer;
6169
+ }
6170
+ if (lanOld.isMulticastEnabled) {
6171
+ dittoRemoveMulticastTransport(dittoHandle.deref());
6172
+ }
5823
6173
  }
5824
- if (lanNew.isMulticastEnabled) {
5825
- dittoAddMulticastTransport(dittoPointer);
6174
+ if (lanNew.isEnabled) {
6175
+ if (lanNew.isMdnsEnabled) {
6176
+ this.lanTransportPointer = lanCreate(dittoHandle.deref());
6177
+ }
6178
+ if (lanNew.isMulticastEnabled) {
6179
+ dittoAddMulticastTransport(dittoHandle.deref());
6180
+ }
5826
6181
  }
5827
- }
6182
+ });
5828
6183
  }
5829
6184
  updateListenTCP(stateOld, stateNew) {
5830
- const tcpOld = stateOld.effectiveTransportConfig.listen.tcp;
5831
- const tcpNew = stateNew.effectiveTransportConfig.listen.tcp;
5832
- if (TransportConfig.areListenTCPsEqual(tcpNew, tcpOld))
5833
- return;
5834
- const dittoPointer = Bridge.ditto.pointerFor(this.ditto);
5835
- if (tcpOld.isEnabled)
5836
- dittoStopTCPServer(dittoPointer);
5837
- if (tcpNew.isEnabled)
5838
- dittoStartTCPServer(dittoPointer, `${tcpNew.interfaceIP}:${tcpNew.port}`);
6185
+ const ditto = this.ditto;
6186
+ const dittoHandle = Bridge.ditto.handleFor(ditto);
6187
+ ditto.deferClose(() => {
6188
+ const tcpOld = stateOld.effectiveTransportConfig.listen.tcp;
6189
+ const tcpNew = stateNew.effectiveTransportConfig.listen.tcp;
6190
+ if (TransportConfig.areListenTCPsEqual(tcpNew, tcpOld))
6191
+ return;
6192
+ if (tcpOld.isEnabled)
6193
+ dittoStopTCPServer(dittoHandle.deref());
6194
+ if (tcpNew.isEnabled)
6195
+ dittoStartTCPServer(dittoHandle.deref(), `${tcpNew.interfaceIP}:${tcpNew.port}`);
6196
+ });
5839
6197
  }
5840
6198
  updateListenHTTP(stateOld, stateNew) {
5841
- const httpOld = stateOld.effectiveTransportConfig.listen.http;
5842
- const httpNew = stateNew.effectiveTransportConfig.listen.http;
5843
- if (TransportConfig.areListenHTTPsEqual(httpOld, httpNew))
5844
- return;
5845
- const dittoPointer = Bridge.ditto.pointerFor(this.ditto);
5846
- if (httpOld.isEnabled)
5847
- dittoStopHTTPServer(dittoPointer);
5848
- /* eslint-disable */
5849
- if (httpNew.isEnabled) {
5850
- dittoStartHTTPServer(dittoPointer, `${httpNew.interfaceIP}:${httpNew.port}`, httpNew.staticContentPath || null, httpNew.websocketSync ? 'Enabled' : 'Disabled', httpNew.tlsCertificatePath || null, httpNew.tlsKeyPath || null);
5851
- }
5852
- /* eslint-enable */
6199
+ const ditto = this.ditto;
6200
+ const dittoHandle = Bridge.ditto.handleFor(ditto);
6201
+ ditto.deferClose(() => {
6202
+ const httpOld = stateOld.effectiveTransportConfig.listen.http;
6203
+ const httpNew = stateNew.effectiveTransportConfig.listen.http;
6204
+ if (TransportConfig.areListenHTTPsEqual(httpOld, httpNew))
6205
+ return;
6206
+ if (httpOld.isEnabled)
6207
+ dittoStopHTTPServer(dittoHandle.deref());
6208
+ /* eslint-disable */
6209
+ if (httpNew.isEnabled) {
6210
+ dittoStartHTTPServer(dittoHandle.deref(), `${httpNew.interfaceIP}:${httpNew.port}`, httpNew.staticContentPath || null, httpNew.websocketSync ? 'Enabled' : 'Disabled', httpNew.tlsCertificatePath || null, httpNew.tlsKeyPath || null);
6211
+ }
6212
+ /* eslint-enable */
6213
+ });
5853
6214
  }
5854
6215
  updateConnectTCPServers(stateOld, stateNew) {
5855
- // REFACTOR: same "algorithm" as for Websockets below, DRY up.
5856
- const currentTCPServers = Object.getOwnPropertyNames(this.staticTCPClientsByAddress);
5857
- const desiredTCPServers = stateNew.effectiveTransportConfig.connect.tcpServers;
5858
- const tcpServersToConnectToSet = new Set(desiredTCPServers);
5859
- for (const tcpServer of currentTCPServers)
5860
- tcpServersToConnectToSet.delete(tcpServer);
5861
- const tcpServersToDisconnectFromSet = new Set(currentTCPServers);
5862
- for (const tcpServer of desiredTCPServers)
5863
- tcpServersToDisconnectFromSet.delete(tcpServer);
5864
- const tcpServersToConnectTo = tcpServersToConnectToSet.values();
5865
- const tcpServersToDisconnectFrom = tcpServersToDisconnectFromSet.values();
5866
- for (const tcpServer of tcpServersToConnectTo) {
5867
- const staticTCPClientPointer = addStaticTCPClient(Bridge.ditto.pointerFor(this.ditto), tcpServer);
5868
- const staticTCPClient = Bridge.staticTCPClient.bridge(staticTCPClientPointer);
5869
- this.staticTCPClientsByAddress[tcpServer] = staticTCPClient;
5870
- }
5871
- for (const tcpServer of tcpServersToDisconnectFrom) {
5872
- // REFACTOR: we have to make sure freeing the tcpServer handle is done
5873
- // BEFORE the Ditto instance itself is freed.
5874
- const staticTCPClient = this.staticTCPClientsByAddress[tcpServer];
5875
- if (!staticTCPClient)
5876
- throw new Error(`Internal inconsistency, can't disconnect from TCP address '${tcpServer}', no staticTCPClient found.`);
5877
- const staticTCPClientPointer = Bridge.staticTCPClient.pointerFor(staticTCPClient);
5878
- // Unregister to avoid double-free for this pointer, then free manually:
5879
- Bridge.staticTCPClient.unregister(staticTCPClient);
5880
- staticTCPClientFreeHandle(staticTCPClientPointer);
5881
- delete this.staticTCPClientsByAddress[tcpServer];
5882
- }
6216
+ const ditto = this.ditto;
6217
+ const dittoHandle = Bridge.ditto.handleFor(ditto);
6218
+ ditto.deferClose(() => {
6219
+ // REFACTOR: same "algorithm" as for Websockets below, DRY up.
6220
+ const currentTCPServers = Object.getOwnPropertyNames(this.staticTCPClientsByAddress);
6221
+ const desiredTCPServers = stateNew.effectiveTransportConfig.connect.tcpServers;
6222
+ const tcpServersToConnectToSet = new Set(desiredTCPServers);
6223
+ for (const tcpServer of currentTCPServers)
6224
+ tcpServersToConnectToSet.delete(tcpServer);
6225
+ const tcpServersToDisconnectFromSet = new Set(currentTCPServers);
6226
+ for (const tcpServer of desiredTCPServers)
6227
+ tcpServersToDisconnectFromSet.delete(tcpServer);
6228
+ const tcpServersToConnectTo = tcpServersToConnectToSet.values();
6229
+ const tcpServersToDisconnectFrom = tcpServersToDisconnectFromSet.values();
6230
+ for (const tcpServer of tcpServersToConnectTo) {
6231
+ const staticTCPClientPointer = addStaticTCPClient(dittoHandle.deref(), tcpServer);
6232
+ const staticTCPClient = Bridge.staticTCPClient.bridge(staticTCPClientPointer);
6233
+ this.staticTCPClientsByAddress[tcpServer] = staticTCPClient;
6234
+ }
6235
+ for (const tcpServer of tcpServersToDisconnectFrom) {
6236
+ // REFACTOR: we have to make sure freeing the tcpServer handle is done
6237
+ // BEFORE the Ditto instance itself is freed.
6238
+ const staticTCPClient = this.staticTCPClientsByAddress[tcpServer];
6239
+ if (!staticTCPClient)
6240
+ throw new Error(`Internal inconsistency, can't disconnect from TCP address '${tcpServer}', no staticTCPClient found.`);
6241
+ const staticTCPClientPointer = Bridge.staticTCPClient.handleFor(staticTCPClient).deref();
6242
+ // Unregister to avoid double-free for this pointer, then free manually:
6243
+ Bridge.staticTCPClient.unregister(staticTCPClient);
6244
+ staticTCPClientFreeHandle(staticTCPClientPointer);
6245
+ delete this.staticTCPClientsByAddress[tcpServer];
6246
+ }
6247
+ });
5883
6248
  }
5884
6249
  updateConnectWebsocketURLs(stateOld, stateNew) {
5885
- // REFACTOR: same "algorithm" as for TCP servers above, DRY up.
5886
- // IDEA: normalize URLs so that we don't connect to the same URL twice?
5887
- const currentWebsocketURLs = Object.getOwnPropertyNames(this.websocketClientsByURL);
5888
- const desiredWebsocketURLs = stateNew.effectiveTransportConfig.connect.websocketURLs.slice();
5889
- const websocketURLsToConnectToSet = new Set(desiredWebsocketURLs);
5890
- for (const websocketURL of currentWebsocketURLs)
5891
- websocketURLsToConnectToSet.delete(websocketURL);
5892
- const websocketURLsToDisconnectFromSet = new Set(currentWebsocketURLs);
5893
- for (const websocketURL of desiredWebsocketURLs)
5894
- websocketURLsToDisconnectFromSet.delete(websocketURL);
5895
- const websocketURLsToConnectTo = websocketURLsToConnectToSet.values();
5896
- const websocketURLsToDisconnectFrom = websocketURLsToDisconnectFromSet.values();
5897
- const routingHint = stateNew.effectiveTransportConfig.global.routingHint;
5898
- for (const websocketURL of websocketURLsToConnectTo) {
5899
- const websocketClientPointer = addWebsocketClient(Bridge.ditto.pointerFor(this.ditto), websocketURL, routingHint);
5900
- const websocketClient = Bridge.websocketClient.bridge(websocketClientPointer);
5901
- this.websocketClientsByURL[websocketURL] = websocketClient;
5902
- }
5903
- for (const websocketURL of websocketURLsToDisconnectFrom) {
5904
- // REFACTOR: we have to make sure freeing the websocket handle is done
5905
- // BEFORE the Ditto instance itself is freed.
5906
- const websocketClient = this.websocketClientsByURL[websocketURL];
5907
- if (!websocketClient)
5908
- throw new Error(`Internal inconsistency, can't disconnect from websocket URL '${websocketURL}', no websocketClient found.`);
5909
- const websocketClientPointer = Bridge.websocketClient.pointerFor(websocketClient);
5910
- // Unregister to avoid double-free for this pointer, then free manually:
5911
- Bridge.websocketClient.unregister(websocketClient);
5912
- websocketClientFreeHandle(websocketClientPointer);
5913
- delete this.websocketClientsByURL[websocketURL];
5914
- }
6250
+ const ditto = this.ditto;
6251
+ const dittoHandle = Bridge.ditto.handleFor(ditto);
6252
+ ditto.deferClose(() => {
6253
+ // REFACTOR: same "algorithm" as for TCP servers above, DRY up.
6254
+ // IDEA: normalize URLs so that we don't connect to the same URL twice?
6255
+ const currentWebsocketURLs = Object.getOwnPropertyNames(this.websocketClientsByURL);
6256
+ const desiredWebsocketURLs = stateNew.effectiveTransportConfig.connect.websocketURLs.slice();
6257
+ const websocketURLsToConnectToSet = new Set(desiredWebsocketURLs);
6258
+ for (const websocketURL of currentWebsocketURLs)
6259
+ websocketURLsToConnectToSet.delete(websocketURL);
6260
+ const websocketURLsToDisconnectFromSet = new Set(currentWebsocketURLs);
6261
+ for (const websocketURL of desiredWebsocketURLs)
6262
+ websocketURLsToDisconnectFromSet.delete(websocketURL);
6263
+ const websocketURLsToConnectTo = websocketURLsToConnectToSet.values();
6264
+ const websocketURLsToDisconnectFrom = websocketURLsToDisconnectFromSet.values();
6265
+ const routingHint = stateNew.effectiveTransportConfig.global.routingHint;
6266
+ for (const websocketURL of websocketURLsToConnectTo) {
6267
+ const websocketClientPointer = addWebsocketClient(dittoHandle.deref(), websocketURL, routingHint);
6268
+ const websocketClient = Bridge.websocketClient.bridge(websocketClientPointer);
6269
+ this.websocketClientsByURL[websocketURL] = websocketClient;
6270
+ }
6271
+ for (const websocketURL of websocketURLsToDisconnectFrom) {
6272
+ // REFACTOR: we have to make sure freeing the websocket handle is done
6273
+ // BEFORE the Ditto instance itself is freed.
6274
+ const websocketClient = this.websocketClientsByURL[websocketURL];
6275
+ if (!websocketClient)
6276
+ throw new Error(`Internal inconsistency, can't disconnect from websocket URL '${websocketURL}', no websocketClient found.`);
6277
+ const websocketClientPointer = Bridge.websocketClient.handleFor(websocketClient).deref();
6278
+ // Unregister to avoid double-free for this pointer, then free manually:
6279
+ Bridge.websocketClient.unregister(websocketClient);
6280
+ websocketClientFreeHandle(websocketClientPointer);
6281
+ delete this.websocketClientsByURL[websocketURL];
6282
+ }
6283
+ });
5915
6284
  }
5916
6285
  updateGlobal(stateOld, stateNew) {
5917
- if (stateOld.effectiveTransportConfig.global.syncGroup !== stateNew.effectiveTransportConfig.global.syncGroup) {
5918
- dittoSetSyncGroup(Bridge.ditto.pointerFor(this.ditto), stateNew.effectiveTransportConfig.global.syncGroup);
5919
- }
6286
+ const ditto = this.ditto;
6287
+ const dittoHandle = Bridge.ditto.handleFor(ditto);
6288
+ ditto.deferClose(() => {
6289
+ if (stateOld.effectiveTransportConfig.global.syncGroup !== stateNew.effectiveTransportConfig.global.syncGroup) {
6290
+ dittoSetSyncGroup(dittoHandle.deref(), stateNew.effectiveTransportConfig.global.syncGroup);
6291
+ }
6292
+ });
5920
6293
  }
5921
6294
  updateConnectRetryInterval(stateOld, stateNew) {
5922
- dittoSetConnectRetryInterval(Bridge.ditto.pointerFor(this.ditto), stateNew.effectiveTransportConfig.connect.retryInterval);
6295
+ const ditto = this.ditto;
6296
+ const dittoHandle = Bridge.ditto.handleFor(ditto);
6297
+ ditto.deferClose(() => {
6298
+ dittoSetConnectRetryInterval(dittoHandle.deref(), stateNew.effectiveTransportConfig.connect.retryInterval);
6299
+ });
5923
6300
  }
5924
6301
  }
5925
6302
  /**
5926
6303
  * @private
5927
6304
  */
5928
6305
  function stateFrom(parameters) {
5929
- // IMPORTANT: this function maps a set of sync parameters to an effective
5930
- // transport config plus some extra state where need be. Make sure
5931
- // to keep this function free of side-effects.
5932
- var _a;
5933
- const transportConfig = parameters.transportConfig.copy();
5934
- const identity = parameters.identity;
5935
6306
  const ditto = parameters.ditto;
5936
- const isSyncActive = parameters.isSyncActive;
5937
- const isX509Valid = parameters.isX509Valid;
5938
- const isWebValid = parameters.isWebValid;
5939
- transportConfig.connect.tcpServers;
5940
- transportConfig.connect.websocketURLs;
5941
- let appID;
5942
- let customDittoCloudURL;
5943
- let isDittoCloudSyncEnabled = false;
5944
- if (identity.type === 'onlinePlayground' || identity.type === 'onlineWithAuthentication') {
5945
- // NOTE: enableDittoCloudSync is true by default for any online identity.
5946
- appID = identity.appID;
5947
- customDittoCloudURL = (_a = identity.customDittoCloudURL) !== null && _a !== void 0 ? _a : null;
5948
- isDittoCloudSyncEnabled = identity.hasOwnProperty('enableDittoCloudSync') ? identity.enableDittoCloudSync : true;
5949
- }
5950
- const dittoPointer = Bridge.ditto.pointerFor(ditto);
5951
- // Not all P2P transports are always supported.
5952
- const unavailableButEnabledP2PTransports = [];
5953
- const isBLEAvailable = bleIsAvailable(dittoPointer);
5954
- const isAWDLAvailable = awdlIsAvailable(dittoPointer);
5955
- const isLANAvailable = lanIsAvailable(dittoPointer);
5956
- if (transportConfig.peerToPeer.bluetoothLE.isEnabled && !isBLEAvailable) {
5957
- unavailableButEnabledP2PTransports.push('BluetoothLE');
5958
- }
5959
- if (transportConfig.peerToPeer.awdl.isEnabled && !isAWDLAvailable) {
5960
- unavailableButEnabledP2PTransports.push('AWDL');
5961
- }
5962
- if (transportConfig.peerToPeer.lan.isEnabled && !isLANAvailable) {
5963
- unavailableButEnabledP2PTransports.push('LAN');
5964
- }
5965
- if (unavailableButEnabledP2PTransports.length > 0) {
5966
- throw new Error(`The following P2P transports are enabled in the transport config but are not supported in the current environment: ${unavailableButEnabledP2PTransports.join(', ')}`);
5967
- }
5968
- if (!isSyncActive || !isX509Valid) {
5969
- transportConfig.peerToPeer.bluetoothLE.isEnabled = false;
5970
- transportConfig.peerToPeer.awdl.isEnabled = false;
5971
- transportConfig.peerToPeer.lan.isEnabled = false;
5972
- transportConfig.listen.tcp.isEnabled = false;
5973
- transportConfig.connect.tcpServers = [];
5974
- }
5975
- if (!isSyncActive || !isWebValid) {
5976
- transportConfig.connect.websocketURLs = [];
5977
- }
5978
- if (!isSyncActive) {
5979
- transportConfig.listen.http.isEnabled = false;
5980
- }
5981
- // NOTE: we have to smuggle in an additional Ditto Cloud websocket URL to
5982
- // connect to if cloud sync is enabled.
5983
- if (isSyncActive && isWebValid && isDittoCloudSyncEnabled) {
5984
- const dittoCloudURL = customDittoCloudURL !== null && customDittoCloudURL !== void 0 ? customDittoCloudURL : defaultDittoCloudURL(appID);
5985
- transportConfig.connect.websocketURLs.push(dittoCloudURL);
5986
- }
5987
- return {
5988
- underlyingSyncParameters: parameters,
5989
- effectiveTransportConfig: transportConfig.freeze(),
5990
- };
6307
+ const dittoHandle = Bridge.ditto.handleFor(ditto);
6308
+ return ditto.deferClose(() => {
6309
+ // IMPORTANT: this function maps a set of sync parameters to an effective
6310
+ // transport config plus some extra state where need be. Make sure
6311
+ // to keep this function free of side-effects.
6312
+ var _a;
6313
+ const transportConfig = parameters.transportConfig.copy();
6314
+ const identity = parameters.identity;
6315
+ const isSyncActive = parameters.isSyncActive;
6316
+ const isX509Valid = parameters.isX509Valid;
6317
+ const isWebValid = parameters.isWebValid;
6318
+ transportConfig.connect.tcpServers;
6319
+ transportConfig.connect.websocketURLs;
6320
+ let appID;
6321
+ let customDittoCloudURL;
6322
+ let isDittoCloudSyncEnabled = false;
6323
+ if (identity.type === 'onlinePlayground' || identity.type === 'onlineWithAuthentication') {
6324
+ // NOTE: enableDittoCloudSync is true by default for any online identity.
6325
+ appID = identity.appID;
6326
+ customDittoCloudURL = (_a = identity.customDittoCloudURL) !== null && _a !== void 0 ? _a : null;
6327
+ isDittoCloudSyncEnabled = identity.hasOwnProperty('enableDittoCloudSync') ? identity.enableDittoCloudSync : true;
6328
+ }
6329
+ // Not all P2P transports are always supported.
6330
+ const unavailableButEnabledP2PTransports = [];
6331
+ const isBLEAvailable = bleIsAvailable(dittoHandle.deref());
6332
+ const isAWDLAvailable = awdlIsAvailable(dittoHandle.deref());
6333
+ const isLANAvailable = lanIsAvailable(dittoHandle.deref());
6334
+ if (transportConfig.peerToPeer.bluetoothLE.isEnabled && !isBLEAvailable) {
6335
+ unavailableButEnabledP2PTransports.push('BluetoothLE');
6336
+ }
6337
+ if (transportConfig.peerToPeer.awdl.isEnabled && !isAWDLAvailable) {
6338
+ unavailableButEnabledP2PTransports.push('AWDL');
6339
+ }
6340
+ if (transportConfig.peerToPeer.lan.isEnabled && !isLANAvailable) {
6341
+ unavailableButEnabledP2PTransports.push('LAN');
6342
+ }
6343
+ if (unavailableButEnabledP2PTransports.length > 0) {
6344
+ throw new Error(`The following P2P transports are enabled in the transport config but are not supported in the current environment: ${unavailableButEnabledP2PTransports.join(', ')}`);
6345
+ }
6346
+ if (!isSyncActive || !isX509Valid) {
6347
+ transportConfig.peerToPeer.bluetoothLE.isEnabled = false;
6348
+ transportConfig.peerToPeer.awdl.isEnabled = false;
6349
+ transportConfig.peerToPeer.lan.isEnabled = false;
6350
+ transportConfig.listen.tcp.isEnabled = false;
6351
+ transportConfig.connect.tcpServers = [];
6352
+ }
6353
+ if (!isSyncActive || !isWebValid) {
6354
+ transportConfig.connect.websocketURLs = [];
6355
+ }
6356
+ if (!isSyncActive) {
6357
+ transportConfig.listen.http.isEnabled = false;
6358
+ }
6359
+ // NOTE: we have to smuggle in an additional Ditto Cloud websocket URL to
6360
+ // connect to if cloud sync is enabled.
6361
+ if (isSyncActive && isWebValid && isDittoCloudSyncEnabled) {
6362
+ const dittoCloudURL = customDittoCloudURL !== null && customDittoCloudURL !== void 0 ? customDittoCloudURL : defaultDittoCloudURL(appID);
6363
+ transportConfig.connect.websocketURLs.push(dittoCloudURL);
6364
+ }
6365
+ return {
6366
+ underlyingSyncParameters: parameters,
6367
+ effectiveTransportConfig: transportConfig.freeze(),
6368
+ };
6369
+ });
6370
+ }
6371
+
6372
+ //
6373
+ /**
6374
+ * Tracks `Subscription` instances in order to remove them when Ditto is
6375
+ * closed.
6376
+ *
6377
+ * @internal
6378
+ */
6379
+ class SubscriptionManager {
6380
+ /** @internal */
6381
+ constructor(ditto) {
6382
+ this.ditto = ditto;
6383
+ this.subscriptions = {};
6384
+ this.finalizationRegistry = new FinalizationRegistry(this.removeWithContextinfo.bind(this));
6385
+ }
6386
+ /**
6387
+ * Begin tracking a subscription instance and start it.
6388
+ *
6389
+ * @internal */
6390
+ add(subscription) {
6391
+ const ditto = this.ditto;
6392
+ const dittoHandle = Bridge.ditto.handleFor(ditto);
6393
+ const contextInfo = subscription.contextInfo;
6394
+ ditto.deferClose(async () => {
6395
+ this.subscriptions[contextInfo.id] = new WeakRef(subscription);
6396
+ this.finalizationRegistry.register(subscription, subscription.contextInfo, subscription);
6397
+ addSubscription(dittoHandle.deref(), contextInfo.collectionName, contextInfo.query, contextInfo.queryArgsCBOR, contextInfo.orderBys, contextInfo.limit, contextInfo.offset);
6398
+ });
6399
+ }
6400
+ /**
6401
+ * Stop tracking a subscription instance and cancel it.
6402
+ *
6403
+ * @internal */
6404
+ remove(subscription) {
6405
+ if (this.subscriptions[subscription.contextInfo.id] == null) {
6406
+ throw new Error(`Internal inconsistency, tried to remove a subscription that is not tracked: ${subscription.contextInfo.id}`);
6407
+ }
6408
+ this.finalizationRegistry.unregister(subscription);
6409
+ this.removeWithContextinfo(subscription.contextInfo);
6410
+ }
6411
+ /**
6412
+ * Stop tracking all subscriptions and cancel them.
6413
+ *
6414
+ * @internal */
6415
+ close() {
6416
+ this.ditto.deferClose(async () => {
6417
+ for (const subscriptionID in this.subscriptions) {
6418
+ const subscription = this.subscriptions[subscriptionID].deref();
6419
+ if (subscription != null) {
6420
+ // This doesn't call `Subscription.cancel()` because that is not
6421
+ // async and we want to wait for all subscriptions to be removed.
6422
+ this.remove(subscription);
6423
+ }
6424
+ }
6425
+ });
6426
+ }
6427
+ /**
6428
+ * Remove tracked subscription without unregistering from finalization
6429
+ * registry.
6430
+ *
6431
+ * @internal */
6432
+ removeWithContextinfo(contextInfo) {
6433
+ const ditto = this.ditto;
6434
+ const dittoHandle = Bridge.ditto.handleFor(ditto);
6435
+ ditto.deferClose(() => {
6436
+ delete this.subscriptions[contextInfo.id];
6437
+ removeSubscription(dittoHandle.deref(), contextInfo.collectionName, contextInfo.query, contextInfo.queryArgsCBOR, contextInfo.orderBys, contextInfo.limit, contextInfo.offset);
6438
+ });
6439
+ }
6440
+ }
6441
+
6442
+ //
6443
+ /**
6444
+ * These objects are returned by calls to
6445
+ * {@link Collection.fetchAttachment | fetchAttachment()} on {@link Collection}
6446
+ * and allow you to stop an in-flight attachment fetch.
6447
+ */
6448
+ class AttachmentFetcher {
6449
+ /**
6450
+ * Stops fetching the associated attachment and cleans up any associated
6451
+ * resources.
6452
+ *
6453
+ * Note that you are not required to call `stop()` once your attachment fetch
6454
+ * operation has finished. The method primarily exists to allow you to cancel
6455
+ * an attachment fetch request while it is ongoing if you no longer wish for
6456
+ * the attachment to be made available locally to the device.
6457
+ */
6458
+ stop() {
6459
+ // No need for synchronicity here: we let the "stop promise" float / run in
6460
+ // a detached fashion since there is no point in awaiting the "stop
6461
+ // signaling" itself.
6462
+ step(async () => {
6463
+ await this.manager.stopAttachmentFetcher(this);
6464
+ if (this.rejectPendingFetch != null) {
6465
+ this.rejectPendingFetch();
6466
+ }
6467
+ });
6468
+ }
6469
+ /** @internal */
6470
+ then(onfulfilled, onrejected) {
6471
+ return this.attachment.then(onfulfilled, onrejected);
6472
+ }
6473
+ /** @internal */
6474
+ constructor(ditto, token, manager, eventHandler) {
6475
+ // --------------------------------------- Internal ------------------------
6476
+ /** @internal */
6477
+ this.cancelTokenPromise = null;
6478
+ /**
6479
+ * This function is defined while a fetch is in progress and is used to reject
6480
+ * the promise `this.attachment` when the fetch is canceled.
6481
+ *
6482
+ * @internal
6483
+ */
6484
+ this.rejectPendingFetch = null;
6485
+ this.ditto = ditto;
6486
+ this.token = token;
6487
+ this.manager = manager;
6488
+ this.id = generateEphemeralToken();
6489
+ const eventHandlerOrNoOp = eventHandler || function () { };
6490
+ const dittoHandle = Bridge.ditto.handleFor(ditto);
6491
+ this.attachment = new Promise((resolve, reject) => {
6492
+ // REFACTOR: The callbacks hold quite a bunch of objects with most
6493
+ // probably lead to retain cycles and therefore memory leaks. This needs
6494
+ // to be
6495
+ // fixed.
6496
+ const onComplete = (attachmentHandlePointer) => {
6497
+ const attachment = new Attachment(this.ditto, this.token);
6498
+ Bridge.attachment.bridge(attachmentHandlePointer, () => attachment);
6499
+ eventHandlerOrNoOp({ type: 'Completed', attachment });
6500
+ this.rejectPendingFetch = null;
6501
+ resolve(attachment);
6502
+ };
6503
+ const onProgress = (downloaded, toDownload) => {
6504
+ eventHandlerOrNoOp({ type: 'Progress', totalBytes: toDownload, downloadedBytes: downloaded });
6505
+ };
6506
+ const onDelete = () => {
6507
+ eventHandlerOrNoOp({ type: 'Deleted' });
6508
+ this.rejectPendingFetch = null;
6509
+ resolve(null);
6510
+ };
6511
+ const onError = () => {
6512
+ };
6513
+ // The core doesn't call any of the handlers defined above when a fetch is
6514
+ // cancelled through `this.stop()` so we use this function to reject the
6515
+ // promise from the outside.
6516
+ this.rejectPendingFetch = () => {
6517
+ reject(new Error('Attachment fetch was canceled'));
6518
+ };
6519
+ // Not awaited yet; only needs to be fully resolved (and `await`ed over)
6520
+ // when using the actual `cancelTokenPromise`, which only happens in
6521
+ // `AttachmentFetcherManager.stopWithContextInfo()`.
6522
+ // @ts-expect-error setting readonly property
6523
+ this.cancelTokenPromise = dittoResolveAttachment(dittoHandle.deref(), token.id, { onComplete, onProgress, onDelete }, onError);
6524
+ });
6525
+ }
6526
+ }
6527
+
6528
+ //
6529
+ /**
6530
+ * Manages attachment fetchers to make sure we free all resources when the
6531
+ * fetcher is garbage collected and to allow us to wait for freeing of
6532
+ * ressources to be finished before the ditto instance is closed.
6533
+ *
6534
+ * @internal */
6535
+ class AttachmentFetcherManager {
6536
+ /** @internal */
6537
+ constructor(ditto) {
6538
+ // --- Private ---
6539
+ this.contextInfoByID = {};
6540
+ this.finalizationRegistry = new FinalizationRegistry(this.stopWithContextInfo);
6541
+ this.ditto = ditto;
6542
+ }
6543
+ /**
6544
+ * Start an attachment fetcher.
6545
+ *
6546
+ * @internal */
6547
+ startAttachmentFetcher(token, eventHandler) {
6548
+ return this.ditto.deferClose(() => {
6549
+ const attachmentFetcher = new AttachmentFetcher(this.ditto, token, this, eventHandler);
6550
+ // Register in finalization registry.
6551
+ const contextInfo = {
6552
+ id: attachmentFetcher.id,
6553
+ attachmentTokenID: token.id,
6554
+ cancelTokenPromise: attachmentFetcher.cancelTokenPromise,
6555
+ attachmentFetcher: new WeakRef(attachmentFetcher),
6556
+ };
6557
+ this.finalizationRegistry.register(attachmentFetcher, contextInfo, attachmentFetcher);
6558
+ // Keep a reference to the context info so that we can stop the fetcher.
6559
+ this.contextInfoByID[attachmentFetcher.id] = contextInfo;
6560
+ // Prevent cancellation of the fetch once it was fulfilled or rejected.
6561
+ const resetCancelToken = () => {
6562
+ if (this.contextInfoByID[attachmentFetcher.id] != null) {
6563
+ this.contextInfoByID[attachmentFetcher.id].cancelTokenPromise = null;
6564
+ }
6565
+ };
6566
+ attachmentFetcher.attachment.then((value) => {
6567
+ resetCancelToken();
6568
+ return value;
6569
+ }, (reason) => {
6570
+ resetCancelToken();
6571
+ return reason;
6572
+ });
6573
+ // Keep the attachment fetcher alive until it is stopped.
6574
+ this.ditto.keepAlive.retain(`AttachmentFetcher.${attachmentFetcher.id})`);
6575
+ return attachmentFetcher;
6576
+ });
6577
+ }
6578
+ /**
6579
+ * Stop an attachment fetcher and wait for it to be stopped.
6580
+ *
6581
+ * @internal */
6582
+ async stopAttachmentFetcher(attachmentFetcher) {
6583
+ this.finalizationRegistry.unregister(attachmentFetcher);
6584
+ const contextInfo = this.contextInfoByID[attachmentFetcher.id];
6585
+ if (contextInfo == null) {
6586
+ throw new Error(`Internal inconsistency: cannot stop attachment fetcher ${attachmentFetcher.id}, which is not registered.`);
6587
+ }
6588
+ await this.stopWithContextInfo(contextInfo);
6589
+ }
6590
+ /**
6591
+ * Closing the manager will cancel all attachment fetchers.
6592
+ *
6593
+ * @internal
6594
+ */
6595
+ close() {
6596
+ // REFACTOR: the closing closure runs detached from here which is a smell.
6597
+ // Can we make this whole closing mechanism non-async? The culprit is
6598
+ // the cancelTokenPromise that _have to_ `await` on close. The reason
6599
+ // for this is that the FFI function `ditto_resolve_attachment()` is
6600
+ // async but should be non-async. Refactor and make that non-async, then
6601
+ // de-async-ify the closing mechanism.
6602
+ this.ditto.deferCloseAsync(async () => {
6603
+ const contextInfos = Object.values(this.contextInfoByID);
6604
+ const stopped = contextInfos.map(async (contextInfo) => {
6605
+ const attachmentFetcher = contextInfo.attachmentFetcher.deref();
6606
+ if (attachmentFetcher != null) {
6607
+ await this.stopAttachmentFetcher(attachmentFetcher);
6608
+ }
6609
+ });
6610
+ await Promise.all(stopped);
6611
+ });
6612
+ }
6613
+ /**
6614
+ * Stop the attachment fetcher without unregistering it from the finalization
6615
+ * registry.
6616
+ */
6617
+ stopWithContextInfo(contextInfo) {
6618
+ const dittoHandle = Bridge.ditto.handleFor(this.ditto);
6619
+ return this.ditto.deferCloseAsync(async () => {
6620
+ // Remove the manager's own record of the context info.
6621
+ if (this.contextInfoByID[contextInfo.id] == null) {
6622
+ throw new Error(`Internal inconsistency: attachment fetcher ${contextInfo.id} not found in active attachment fetchers.`);
6623
+ }
6624
+ delete this.contextInfoByID[contextInfo.id];
6625
+ // Release the keep-alive.
6626
+ this.ditto.keepAlive.release(`AttachmentFetcher.${contextInfo.id})`);
6627
+ // Cancel the fetcher if it is still running.
6628
+ const cancelToken = await contextInfo.cancelTokenPromise;
6629
+ if (cancelToken) {
6630
+ dittoCancelResolveAttachment(dittoHandle.deref(), contextInfo.attachmentTokenID, cancelToken);
6631
+ }
6632
+ });
6633
+ }
5991
6634
  }
5992
6635
 
5993
6636
  //
@@ -5998,8 +6641,10 @@ function stateFrom(parameters) {
5998
6641
  class Ditto {
5999
6642
  /** Returns a string identifying the version of the Ditto SDK. */
6000
6643
  get sdkVersion() {
6001
- const dittoPointer = Bridge.ditto.pointerFor(this);
6002
- return dittoGetSDKVersion(dittoPointer);
6644
+ const dittoHandle = Bridge.ditto.handleFor(this);
6645
+ return this.deferClose(() => {
6646
+ return dittoGetSDKVersion(dittoHandle.deref());
6647
+ });
6003
6648
  }
6004
6649
  /**
6005
6650
  * Initializes a new `Ditto` instance.
@@ -6020,8 +6665,16 @@ class Ditto {
6020
6665
  * @see {@link Ditto.path}
6021
6666
  */
6022
6667
  constructor(identity, path) {
6668
+ /**
6669
+ * `true` once {@link close | Ditto.close()} has been called, otherwise
6670
+ * `false`.
6671
+ */
6672
+ this.isClosed = false;
6673
+ this.deferCloseAllowed = true;
6023
6674
  this.isWebValid = false;
6024
6675
  this.isX509Valid = false;
6676
+ /** Set of pending operations that need to complete before the Ditto instance can be closed in a safe manner. */
6677
+ this.pendingOperations = new Set();
6025
6678
  if (!Ditto.isEnvironmentSupported()) {
6026
6679
  throw new Error('Ditto does not support this JavaScript environment. Please consult the Ditto JavaScript documentation for a list of supported environments and browsers. You can use `Ditto.isEnvironmentSupported()` to run this check anytime.');
6027
6680
  }
@@ -6056,50 +6709,35 @@ class Ditto {
6056
6709
  // it after all pieces are in place.
6057
6710
  let secondsRemainingUntilAuthenticationExpires = null;
6058
6711
  const weakThis = new WeakRef(this);
6059
- const authClientX = (() => {
6712
+ const identityConfig = (() => {
6060
6713
  var _a, _b, _c;
6061
6714
  if (validIdentity.type === 'offlinePlayground') {
6062
- return dittoAuthClientMakeForDevelopment(pathOrDefault, validIdentity.appID, (_a = validIdentity.siteID) !== null && _a !== void 0 ? _a : 0);
6715
+ return dittoIdentityConfigMakeOfflinePlayground(validIdentity.appID, (_a = validIdentity.siteID) !== null && _a !== void 0 ? _a : 0);
6063
6716
  }
6064
6717
  if (validIdentity.type === 'manual') {
6065
- return dittoAuthClientMakeWithStaticX509(validIdentity.certificate);
6718
+ return dittoIdentityConfigMakeManual(validIdentity.certificate);
6066
6719
  }
6067
6720
  if (validIdentity.type === 'sharedKey') {
6068
- return dittoAuthClientMakeWithSharedKey(pathOrDefault, validIdentity.appID, validIdentity.sharedKey, validIdentity.siteID);
6721
+ return dittoIdentityConfigMakeSharedKey(validIdentity.appID, validIdentity.sharedKey, validIdentity.siteID);
6069
6722
  }
6070
6723
  if (validIdentity.type === 'onlinePlayground') {
6071
6724
  const authURL = (_b = validIdentity.customAuthURL) !== null && _b !== void 0 ? _b : defaultAuthURL(validIdentity.appID);
6072
- return dittoAuthClientMakeAnonymousClient(pathOrDefault, validIdentity.appID, validIdentity.token, authURL);
6725
+ return dittoIdentityConfigMakeOnlinePlayground(validIdentity.appID, validIdentity.token, authURL);
6073
6726
  }
6074
6727
  if (validIdentity.type === 'onlineWithAuthentication') {
6075
6728
  const authURL = (_c = validIdentity.customAuthURL) !== null && _c !== void 0 ? _c : defaultAuthURL(validIdentity.appID);
6076
- const loginProviderX = dittoAuthClientMakeLoginProvider(function (secondsRemaining) {
6077
- const strongThis = weakThis.deref();
6078
- if (!strongThis) {
6079
- Logger.warning(`Internal inconsistency, LoginProvider callback fired after the corresponding Ditto instance has been deallocated.`);
6080
- return;
6081
- }
6082
- if (strongThis.auth) {
6083
- strongThis.auth['@ditto.authenticationExpiring'](secondsRemaining);
6084
- }
6085
- else {
6086
- // WORKAROUND: see description above where the
6087
- // secondsRemainingUntilAuthenticationExpires variable is declared.
6088
- secondsRemainingUntilAuthenticationExpires = secondsRemaining;
6089
- }
6090
- });
6091
- return dittoAuthClientMakeWithWeb(pathOrDefault, validIdentity.appID, authURL, loginProviderX);
6729
+ return dittoIdentityConfigMakeOnlineWithAuthentication(validIdentity.appID, authURL);
6092
6730
  }
6093
6731
  throw new Error(`Can't create Ditto, unsupported identity type: ${validIdentity}`);
6094
6732
  })();
6095
- dittoAuthClientSetValidityListener(authClientX, function (...args) {
6733
+ const dittoPointer = dittoMake(uninitializedDittoX, identityConfig);
6734
+ dittoAuthClientSetValidityListener(dittoPointer, function (...args) {
6096
6735
  var _a;
6097
6736
  (_a = weakThis.deref()) === null || _a === void 0 ? void 0 : _a.authClientValidityChanged(...args);
6098
6737
  });
6099
- const isWebValid = dittoAuthClientIsWebValid(authClientX);
6100
- const isX509Valid = dittoAuthClientIsX509Valid(authClientX);
6101
- const siteID = dittoAuthClientGetSiteID(authClientX);
6102
- const dittoPointer = dittoMake(uninitializedDittoX, authClientX);
6738
+ const isWebValid = dittoAuthClientIsWebValid(dittoPointer);
6739
+ const isX509Valid = dittoAuthClientIsX509Valid(dittoPointer);
6740
+ const siteID = dittoAuthClientGetSiteID(dittoPointer);
6103
6741
  Bridge.ditto.bridge(dittoPointer, this);
6104
6742
  // IMPORTANT: Keeping the auth client around accumulates run-times and
6105
6743
  // resources which becomes a problem specifically in tests (where we use one
@@ -6107,10 +6745,30 @@ class Ditto {
6107
6745
  // _only_ for the onlineWithAuthentication and online identities and
6108
6746
  // transfer ownership of it to the authenticator.
6109
6747
  if (validIdentity.type === 'onlineWithAuthentication') {
6110
- this.auth = new OnlineAuthenticator(this.keepAlive, authClientX, this, validIdentity.authHandler);
6748
+ this.auth = new OnlineAuthenticator(this.keepAlive, this, validIdentity.authHandler);
6749
+ const loginProviderX = dittoAuthClientMakeLoginProvider(function (secondsRemaining) {
6750
+ const strongThis = weakThis.deref();
6751
+ if (!strongThis) {
6752
+ Logger.warning(`Internal inconsistency, LoginProvider callback fired after the corresponding Ditto instance has been deallocated.`);
6753
+ return;
6754
+ }
6755
+ if (strongThis.auth) {
6756
+ strongThis.auth['@ditto.authenticationExpiring'](secondsRemaining);
6757
+ }
6758
+ else {
6759
+ // WORKAROUND: see description above where the
6760
+ // secondsRemainingUntilAuthenticationExpires variable is declared.
6761
+ secondsRemainingUntilAuthenticationExpires = secondsRemaining;
6762
+ }
6763
+ });
6764
+ // We don't need to worry about awaiting the result of this call because
6765
+ // auth all happens in the background and so there are no guarantees we
6766
+ // need to uphold by making sure things are in a certain state before the
6767
+ // constructor finishes
6768
+ dittoAuthSetLoginProvider(dittoPointer, loginProviderX);
6111
6769
  }
6112
6770
  else if (validIdentity.type === 'onlinePlayground') {
6113
- this.auth = new OnlineAuthenticator(this.keepAlive, authClientX, this, {
6771
+ this.auth = new OnlineAuthenticator(this.keepAlive, this, {
6114
6772
  authenticationRequired: function (authenticator) {
6115
6773
  // No-op.
6116
6774
  },
@@ -6120,7 +6778,6 @@ class Ditto {
6120
6778
  });
6121
6779
  }
6122
6780
  else {
6123
- dittoAuthClientFree(authClientX);
6124
6781
  this.auth = new NotAvailableAuthenticator(this.keepAlive);
6125
6782
  }
6126
6783
  const transportConfig = this.makeDefaultTransportConfig();
@@ -6136,13 +6793,63 @@ class Ditto {
6136
6793
  this.store = new Store(this);
6137
6794
  this.presence = new Presence(this);
6138
6795
  this.presenceManager = new PresenceManager(this);
6796
+ this.liveQueryManager = new LiveQueryManager(this, this.keepAlive);
6797
+ this.attachmentFetcherManager = new AttachmentFetcherManager(this);
6139
6798
  this.transportConditionsManager = new TransportConditionsManager(this);
6799
+ this.subscriptionManager = new SubscriptionManager(this);
6800
+ disableDeadlockTimeoutWhenDebugging();
6140
6801
  // WORKAROUND: see description above where the
6141
6802
  // secondsRemainingUntilAuthenticationExpires variable is declared.
6142
6803
  if (secondsRemainingUntilAuthenticationExpires !== null) {
6143
6804
  this.auth['@ditto.authenticationExpiring'](secondsRemainingUntilAuthenticationExpires);
6144
6805
  }
6145
6806
  }
6807
+ /**
6808
+ * Don't terminate the process when callbacks are pending for a long time.
6809
+ *
6810
+ * Some methods in the Ditto library accept asynchronous functions as callback
6811
+ * parameters. If these asynchronous functions do not resolve within a certain
6812
+ * period of time after having been invoked by Ditto, deadlock detection gets
6813
+ * triggered, resulting in the termination of the process.
6814
+ *
6815
+ * When Ditto is executed in a Node.js environment with an interactive
6816
+ * debugger attached, this deadlock detection might get activated upon
6817
+ * encountering a breakpoint. Calling `Ditto.disableDeadlockDetection()`
6818
+ * disables this behavior, thus allowing the use of an interactive debugger
6819
+ * without triggering the deadlock detection.
6820
+ *
6821
+ * This feature is not available in the browser.
6822
+ */
6823
+ static disableDeadlockDetection() {
6824
+ {
6825
+ const current = getDeadlockTimeout();
6826
+ if (current !== 0) {
6827
+ try {
6828
+ setDeadlockTimeout(0);
6829
+ }
6830
+ catch (e) {
6831
+ throw new Error(`Failed to disable deadlock detection: ${e === null || e === void 0 ? void 0 : e.message}`);
6832
+ }
6833
+ }
6834
+ }
6835
+ }
6836
+ /**
6837
+ * Returns `true` if deadlock detection is enabled.
6838
+ *
6839
+ * See
6840
+ * {@link Ditto.disableDeadlockDetection | Ditto.disableDeadlockDetection()}
6841
+ * for more information.
6842
+ *
6843
+ * This method always returns `false` in the browser where deadlock detection
6844
+ * is not available.
6845
+ *
6846
+ * @returns `true` if deadlock detection is enabled
6847
+ */
6848
+ static hasDeadlockDetection() {
6849
+ {
6850
+ return getDeadlockTimeout() !== 0;
6851
+ }
6852
+ }
6146
6853
  /**
6147
6854
  * Check if the current environment supports running Ditto.
6148
6855
  *
@@ -6154,7 +6861,7 @@ class Ditto {
6154
6861
  *
6155
6862
  * Internet Explorer is not supported.
6156
6863
  *
6157
- * @returns true if the environment is supported
6864
+ * @returns `true` if the environment is supported
6158
6865
  */
6159
6866
  static isEnvironmentSupported() {
6160
6867
  // From https://stackoverflow.com/questions/21825157/internet-explorer-11-detection
@@ -6305,8 +7012,10 @@ class Ditto {
6305
7012
  * Only available in Node environments at the moment, no-op in the browser.
6306
7013
  */
6307
7014
  runGarbageCollection() {
6308
- const dittoPointer = Bridge.ditto.pointerFor(this);
6309
- dittoRunGarbageCollection(dittoPointer);
7015
+ const dittoHandle = Bridge.ditto.handleFor(this);
7016
+ return this.deferClose(() => {
7017
+ dittoRunGarbageCollection(dittoHandle.deref());
7018
+ });
6310
7019
  }
6311
7020
  /**
6312
7021
  * Explicitly opt-in to disabling the ability to sync with Ditto peers running
@@ -6318,8 +7027,99 @@ class Ditto {
6318
7027
  * or has (transitively) had disabled, syncing with v3 SDK peers.
6319
7028
  */
6320
7029
  disableSyncWithV3() {
6321
- const dittoPointer = Bridge.ditto.pointerFor(this);
6322
- dittoDisableSyncWithV3(dittoPointer);
7030
+ const dittoHandle = Bridge.ditto.handleFor(this);
7031
+ return this.deferClose(() => {
7032
+ dittoDisableSyncWithV3(dittoHandle.deref());
7033
+ });
7034
+ }
7035
+ /**
7036
+ * Shut down Ditto and release all resources.
7037
+ *
7038
+ * Must be called before recreating a Ditto instance that uses the same
7039
+ * persistence directory.
7040
+ */
7041
+ async close() {
7042
+ if (this.isClosed) {
7043
+ return;
7044
+ }
7045
+ this['isClosed'] = true;
7046
+ this.stopSync();
7047
+ this.store.close();
7048
+ this.presence.close();
7049
+ this.auth.close();
7050
+ this.sync.close();
7051
+ this.presenceManager.close();
7052
+ this.liveQueryManager.close();
7053
+ this.attachmentFetcherManager.close();
7054
+ this.transportConditionsManager.close();
7055
+ this.subscriptionManager.close();
7056
+ if (this.keepAlive.isActive) {
7057
+ throw new Error('Internal inconsistency, still kept alive after the Ditto object has been close()-ed. ');
7058
+ }
7059
+ // Await all pending operations before closing. Rejected promises are
7060
+ // ignored because they are handled at the original call site.
7061
+ do {
7062
+ await Promise.allSettled(this.pendingOperations);
7063
+ // REFACTOR: in theory, we could end up in an endless loop here if for
7064
+ // some reason a resolved or rejected promise isn't removed from
7065
+ // `pendingOperations`. AFAICS, this is not possible atm due to the
7066
+ // way `deferClose` and `deferCloseAsync` is implemented. Would be
7067
+ // great to rework this and make it more solid if possible.
7068
+ } while (this.pendingOperations.size > 0);
7069
+ this.deferCloseAllowed = false;
7070
+ const dittoHandle = Bridge.ditto.handleFor(this);
7071
+ await dittoShutdown(dittoHandle.deref());
7072
+ Bridge.ditto.markAsClosed(this);
7073
+ }
7074
+ /**
7075
+ * The number of operations pending before the Ditto instance can be closed.
7076
+ *
7077
+ * For testing purposes only.
7078
+ * @internal */
7079
+ get numPendingOperations() {
7080
+ return this.pendingOperations.size;
7081
+ }
7082
+ /**
7083
+ * Makes sure that the closure is executed only if the Ditto instance hasn't
7084
+ * been closed yet.
7085
+ *
7086
+ * @param closure the synchronous closure to execute.
7087
+ * @returns the result of the closure.
7088
+ * @throws if the Ditto instance was closed before calling this method.
7089
+ * @internal */
7090
+ deferClose(closure) {
7091
+ if (!this.deferCloseAllowed) {
7092
+ throw new Error(`Can't perform operation using a Ditto instance that has been closed.`);
7093
+ }
7094
+ return closure();
7095
+ }
7096
+ /**
7097
+ * Makes sure that the closure is executed to completion before the Ditto
7098
+ * instance is closed.
7099
+ *
7100
+ * Any calls to {@link close | `Ditto.close()`} will wait until the closure
7101
+ * has completed before closing the Ditto instance.
7102
+ *
7103
+ * @param closure the asynchronous closure to execute.
7104
+ * @returns the result of the closure.
7105
+ * @throws if the Ditto instance was closed before calling this method.
7106
+ * @internal */
7107
+ async deferCloseAsync(closure) {
7108
+ if (!this.deferCloseAllowed) {
7109
+ throw new Error(`Can't perform operation using a Ditto instance that has been closed.`);
7110
+ }
7111
+ const pendingOperation = closure();
7112
+ this.pendingOperations.add(pendingOperation);
7113
+ let result;
7114
+ try {
7115
+ result = await pendingOperation;
7116
+ }
7117
+ finally {
7118
+ // Remove the promise from the set of pending operations even if it
7119
+ // rejected.
7120
+ this.pendingOperations.delete(pendingOperation);
7121
+ }
7122
+ return result;
6323
7123
  }
6324
7124
  authClientValidityChanged(isWebValid, isX509Valid) {
6325
7125
  const transportConfig = this.transportConfig;
@@ -6370,49 +7170,41 @@ class Ditto {
6370
7170
  return validIdentity;
6371
7171
  }
6372
7172
  setSyncActive(flag) {
6373
- if (flag && IdentityTypesRequiringOfflineLicenseToken.includes(this.identity.type) && !this.isActivated) {
6374
- throw new Error('Sync could not be started because Ditto has not yet been activated. This can be achieved with a successful call to `setOfflineOnlyLicenseToken`. If you need to obtain a license token then please visit https://portal.ditto.live.');
6375
- }
6376
- if (!this.isSyncActive && flag) {
6377
- this.keepAlive.retain('sync');
6378
- }
6379
- if (this.isSyncActive && !flag) {
6380
- this.keepAlive.release('sync');
6381
- }
6382
- const dittoPointer = Bridge.ditto.pointerFor(this);
6383
- this['isSyncActive'] = flag;
6384
- const isWebValid = this.isWebValid;
6385
- const isX509Valid = this.isX509Valid;
6386
- const identity = this.identity;
6387
- const transportConfig = this.transportConfig;
6388
- dittoSetDeviceName(dittoPointer, this.deviceName);
6389
- this.sync.update({ identity, transportConfig, isWebValid, isX509Valid, isSyncActive: !!flag, ditto: this });
7173
+ const dittoHandle = Bridge.ditto.handleFor(this);
7174
+ this.deferClose(() => {
7175
+ if (flag && IdentityTypesRequiringOfflineLicenseToken.includes(this.identity.type) && !this.isActivated) {
7176
+ throw new Error('Sync could not be started because Ditto has not yet been activated. This can be achieved with a successful call to `setOfflineOnlyLicenseToken`. If you need to obtain a license token then please visit https://portal.ditto.live.');
7177
+ }
7178
+ if (!this.isSyncActive && flag) {
7179
+ this.keepAlive.retain('sync');
7180
+ }
7181
+ if (this.isSyncActive && !flag) {
7182
+ this.keepAlive.release('sync');
7183
+ }
7184
+ this['isSyncActive'] = flag;
7185
+ const isWebValid = this.isWebValid;
7186
+ const isX509Valid = this.isX509Valid;
7187
+ const identity = this.identity;
7188
+ const transportConfig = this.transportConfig;
7189
+ dittoSetDeviceName(dittoHandle.deref(), this.deviceName);
7190
+ this.sync.update({ identity, transportConfig, isWebValid, isX509Valid, isSyncActive: !!flag, ditto: this });
7191
+ });
6390
7192
  }
6391
7193
  makeDefaultTransportConfig() {
6392
- const dittoPointer = Bridge.ditto.pointerFor(this);
6393
- const transportConfig = new TransportConfig();
6394
- if (bleIsAvailable(dittoPointer)) {
6395
- transportConfig.peerToPeer.bluetoothLE.isEnabled = true;
6396
- }
6397
- if (awdlIsAvailable(dittoPointer)) {
6398
- transportConfig.peerToPeer.awdl.isEnabled = true;
6399
- }
6400
- if (lanIsAvailable(dittoPointer)) {
6401
- transportConfig.peerToPeer.lan.isEnabled = true;
6402
- }
6403
- // WORKAROUND: the Linux implementation panics if no BLE hardware is
6404
- // available but should report `NoBleHardware` via transport condition
6405
- // observation instead. Until this is fixed, we'll disable the BLE transport
6406
- // by default on Linux. To undo, remove the following block of code.
6407
- //
6408
- // See issue #5686 for details:
6409
- // https://github.com/getditto/ditto/pull/5608
6410
- {
6411
- if (process.platform === 'linux') {
6412
- transportConfig.peerToPeer.bluetoothLE.isEnabled = false;
7194
+ const dittoHandle = Bridge.ditto.handleFor(this);
7195
+ return this.deferClose(() => {
7196
+ const transportConfig = new TransportConfig();
7197
+ if (bleIsAvailable(dittoHandle.deref())) {
7198
+ transportConfig.peerToPeer.bluetoothLE.isEnabled = true;
6413
7199
  }
6414
- }
6415
- return transportConfig.freeze();
7200
+ if (awdlIsAvailable(dittoHandle.deref())) {
7201
+ transportConfig.peerToPeer.awdl.isEnabled = true;
7202
+ }
7203
+ if (lanIsAvailable(dittoHandle.deref())) {
7204
+ transportConfig.peerToPeer.lan.isEnabled = true;
7205
+ }
7206
+ return transportConfig.freeze();
7207
+ });
6416
7208
  }
6417
7209
  }
6418
7210
  /** @internal */
@@ -6420,6 +7212,31 @@ const checkAPIs = (_globalObject) => {
6420
7212
  const requiredBrowserAPIs = ['BigInt', 'WeakRef', 'FinalizationRegistry'];
6421
7213
  const globalObject = _globalObject || globalThis || global || window;
6422
7214
  return requiredBrowserAPIs.every((apiName) => !!globalObject[apiName]);
7215
+ };
7216
+ /**
7217
+ * Disable deadlock timeout when Node.js is running with `--inspect` parameter.
7218
+ *
7219
+ * This heuristic is not failsafe as debugging mode can also be enabled by
7220
+ * sending a `SIGUSR1` signal to the process.
7221
+ *
7222
+ * @internal
7223
+ */
7224
+ const disableDeadlockTimeoutWhenDebugging = () => {
7225
+ var _a, _b;
7226
+ {
7227
+ const hasInspector = process && ((_a = process === null || process === void 0 ? void 0 : process.execArgv) === null || _a === void 0 ? void 0 : _a.some((arg) => arg.includes('--inspect') || arg.includes('--debug')));
7228
+ // @ts-ignore - v8debug may be undefined
7229
+ const hasLegacyDebugMode = (process && ((_b = process === null || process === void 0 ? void 0 : process.execArgv) === null || _b === void 0 ? void 0 : _b.some((arg) => arg.includes('--debug')))) || typeof v8debug === 'object';
7230
+ if (hasInspector || hasLegacyDebugMode) {
7231
+ try {
7232
+ setDeadlockTimeout(0);
7233
+ Logger.warning('Detected Node running with inspector enabled, disabling deadlock timeout.');
7234
+ }
7235
+ catch (e) {
7236
+ Logger.error(`Detected Node running with inspector enabled but failed to disable deadlock timeout: ${e === null || e === void 0 ? void 0 : e.message}`);
7237
+ }
7238
+ }
7239
+ }
6423
7240
  };
6424
7241
 
6425
7242
  /**
@@ -6514,6 +7331,7 @@ exports.WriteTransactionPendingCursorOperation = WriteTransactionPendingCursorOp
6514
7331
  exports.WriteTransactionPendingIDSpecificOperation = WriteTransactionPendingIDSpecificOperation;
6515
7332
  exports.addressToString = addressToString;
6516
7333
  exports.checkAPIs = checkAPIs;
7334
+ exports.disableDeadlockTimeoutWhenDebugging = disableDeadlockTimeoutWhenDebugging;
6517
7335
  exports.getBridgeLoad = getBridgeLoad;
6518
7336
  exports.init = init;
6519
7337
  exports.validateDocumentIDCBOR = validateDocumentIDCBOR;