@dittolive/ditto 4.4.0 → 4.4.2-alpha1

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
@@ -70,10 +70,14 @@ KeepAlive.finalizationRegistry = new FinalizationRegistry(clearInterval);
70
70
  * alive, you have to keep a reference to the corresponding observer.
71
71
  */
72
72
  class Observer {
73
+ /** @internal */
74
+ get token() {
75
+ return this._token;
76
+ }
73
77
  /** @internal */
74
78
  constructor(observerManager, token, options = {}) {
75
79
  this.observerManager = observerManager;
76
- this.token = token;
80
+ this._token = token;
77
81
  this.options = options;
78
82
  if (options.stopsWhenFinalized) {
79
83
  Observer.finalizationRegistry.register(this, { observerManager, token }, this);
@@ -84,7 +88,7 @@ class Observer {
84
88
  * method. Otherwise returns `false`.
85
89
  */
86
90
  get isStopped() {
87
- return this.observerManager.hasObserver(this.token);
91
+ return this.token !== undefined && this.observerManager.hasObserver(this.token);
88
92
  }
89
93
  /**
90
94
  * Stops the observation. Calling this method multiple times has no effect.
@@ -92,7 +96,7 @@ class Observer {
92
96
  stop() {
93
97
  const token = this.token;
94
98
  if (token) {
95
- delete this['token'];
99
+ delete this._token;
96
100
  Observer.finalizationRegistry.unregister(this);
97
101
  this.observerManager.removeObserver(token);
98
102
  }
@@ -121,24 +125,29 @@ class Value {
121
125
  // NOTE: we use a token to detect private invocation of the constructor. This is
122
126
  // not secure and just to prevent accidental private invocation on the client
123
127
  // side.
124
- const privateToken$1 = '@ditto.64d129224a5377b63e9727479ec987d9';
128
+ const privateToken$1 = Symbol('privateConstructorToken');
125
129
  /**
126
130
  * Represents a CRDT counter that can be upserted as part of a document or
127
131
  * assigned to a property during an update of a document.
128
132
  */
129
133
  class Counter {
134
+ /** The value of the counter. */
135
+ get value() {
136
+ return this._value;
137
+ }
130
138
  /**
131
139
  * Creates a new counter that can be used as part of a document's content.
132
140
  */
133
141
  constructor() {
134
- this.value = 0.0;
142
+ this._value = 0.0;
135
143
  }
136
144
  /** @internal */
137
145
  static '@ditto.create'(mutDoc, path, value) {
146
+ // @ts-expect-error - using hidden argument
138
147
  const counter = mutDoc ? new MutableCounter(privateToken$1) : new Counter();
139
148
  counter.mutDoc = mutDoc;
140
149
  counter.path = path;
141
- counter['value'] = value;
150
+ counter._value = value;
142
151
  return counter;
143
152
  }
144
153
  }
@@ -160,15 +169,15 @@ class MutableCounter extends Counter {
160
169
  * otherwise an exception is thrown.
161
170
  */
162
171
  increment(amount) {
163
- const mutDoc = this['mutDoc'];
164
- const path = this['path'];
172
+ const mutDoc = this.mutDoc;
173
+ const path = this.path;
165
174
  if (!mutDoc) {
166
175
  throw new Error(`Can't increment counter, only possible within the closure of a collection's update() method.`);
167
176
  }
168
177
  mutDoc.at(path)['@ditto.increment'](amount);
169
178
  // We also increment the local value to make sure that the change is
170
179
  // reflected locally as well as in the underlying document
171
- this['value'] += amount;
180
+ this._value += amount;
172
181
  }
173
182
  /** @internal */
174
183
  constructor() {
@@ -324,9 +333,11 @@ function ditto_document_set_cbor(...args) { return ditto.ditto_document_set_cbor
324
333
  function ditto_document_set_cbor_with_timestamp(...args) { return ditto.ditto_document_set_cbor_with_timestamp(...args) }
325
334
  function ditto_documents_hash(...args) { return ditto.ditto_documents_hash(...args) }
326
335
  function ditto_documents_hash_mnemonic(...args) { return ditto.ditto_documents_hash_mnemonic(...args) }
336
+ function ditto_dql_response_free(...args) { return ditto.ditto_dql_response_free(...args) }
337
+ function ditto_dql_response_results(...args) { return ditto.ditto_dql_response_results(...args) }
338
+ function ditto_dql_result_free(...args) { return ditto.ditto_dql_result_free(...args) }
327
339
  function ditto_error_message(...args) { return ditto.ditto_error_message(...args) }
328
340
  function ditto_experimental_add_dql_subscription(...args) { return ditto.ditto_experimental_add_dql_subscription(...args) }
329
- function ditto_experimental_exec_statement_str(...args) { return ditto.ditto_experimental_exec_statement_str(...args) }
330
341
  function ditto_experimental_register_dql_live_query_str_detached(...args) { return ditto.ditto_experimental_register_dql_live_query_str_detached(...args) }
331
342
  function ditto_experimental_remove_dql_subscription(...args) { return ditto.ditto_experimental_remove_dql_subscription(...args) }
332
343
  function ditto_experimental_webhook_register_dql_live_query_str(...args) { return ditto.ditto_experimental_webhook_register_dql_live_query_str(...args) }
@@ -369,6 +380,7 @@ function ditto_register_transport_condition_changed_callback(...args) { return d
369
380
  function ditto_remove_multicast_transport(...args) { return ditto.ditto_remove_multicast_transport(...args) }
370
381
  function ditto_remove_subscription(...args) { return ditto.ditto_remove_subscription(...args) }
371
382
  function ditto_resolve_attachment(...args) { return ditto.ditto_resolve_attachment(...args) }
383
+ function ditto_result_cbor(...args) { return ditto.ditto_result_cbor(...args) }
372
384
  function ditto_run_garbage_collection(...args) { return ditto.ditto_run_garbage_collection(...args) }
373
385
  function ditto_set_connect_retry_interval(...args) { return ditto.ditto_set_connect_retry_interval(...args) }
374
386
  function ditto_set_device_name(...args) { return ditto.ditto_set_device_name(...args) }
@@ -385,6 +397,9 @@ function ditto_validate_document_id(...args) { return ditto.ditto_validate_docum
385
397
  function ditto_write_transaction(...args) { return ditto.ditto_write_transaction(...args) }
386
398
  function ditto_write_transaction_commit(...args) { return ditto.ditto_write_transaction_commit(...args) }
387
399
  function ditto_write_transaction_rollback(...args) { return ditto.ditto_write_transaction_rollback(...args) }
400
+ function dittoffi_error_description(...args) { return ditto.dittoffi_error_description(...args) }
401
+ function dittoffi_error_free(...args) { return ditto.dittoffi_error_free(...args) }
402
+ function dittoffi_try_experimental_exec_statement_str(...args) { return ditto.dittoffi_try_experimental_exec_statement_str(...args) }
388
403
  function getDeadlockTimeout$1(...args) { return ditto.getDeadlockTimeout(...args) }
389
404
  function jsDocsToCDocs(...args) { return ditto.jsDocsToCDocs(...args) }
390
405
  function mdns_client_free_handle(...args) { return ditto.mdns_client_free_handle(...args) }
@@ -636,13 +651,27 @@ async function collectionEvictQueryStr(ditto, collectionName, writeTransaction,
636
651
  const queryX = bytesFromString(query);
637
652
  return await ditto_collection_evict_query_str(ditto, collectionNameX, writeTransaction, queryX, queryArgsCBOR, orderBy, limit, offset);
638
653
  }
639
- /** @internal */
654
+ /**
655
+ * This FFI can error:
656
+ * - DQL parser error
657
+ * - Incorrect arguments to query parameters
658
+ * - Collection is not found.
659
+ *
660
+ * @internal
661
+ */
640
662
  async function experimentalExecQueryStr(ditto, writeTransaction, query, queryArgsCBOR) {
641
663
  ensureInitialized();
642
664
  const queryBytesPointer = bytesFromString(query);
643
665
  // This doesn't need to convert error return codes into thrown js errors
644
666
  // because the ffi implementation already does that.
645
- return await ditto_experimental_exec_statement_str(ditto, writeTransaction, queryBytesPointer, queryArgsCBOR);
667
+ const result = await dittoffi_try_experimental_exec_statement_str(ditto, writeTransaction, queryBytesPointer, queryArgsCBOR);
668
+ if (result.error !== null) {
669
+ // TODO: idiomatic js error class shim around `Pointer<FFIError>`.
670
+ const errorMsg = errorMessage(result.error);
671
+ dittoffi_error_free(result.error);
672
+ throw new Error(errorMsg);
673
+ }
674
+ return result.success;
646
675
  }
647
676
  /** @internal */
648
677
  function addSubscription(ditto, collectionName, query, queryArgsCBOR, orderBy, limit, offset) {
@@ -674,6 +703,49 @@ function experimentalRemoveDqlSubscription(ditto, query, queryArgsCBOR) {
674
703
  if (statusCode !== 0)
675
704
  throw new Error(errorMessage() || `ditto_experimental_remove_dql_subscription() failed with error code: ${statusCode}`);
676
705
  }
706
+ // ----------------------------------------------------------- DqlResponse ------
707
+ /**
708
+ * Doesn't error
709
+ *
710
+ * @internal
711
+ */
712
+ function experimentalDqlResponseFree(self) {
713
+ ensureInitialized();
714
+ ditto_dql_response_free(self);
715
+ }
716
+ /**
717
+ * Doesn't error
718
+ *
719
+ * @internal
720
+ */
721
+ function experimentalDqlResultFree(self) {
722
+ ensureInitialized();
723
+ ditto_dql_result_free(self);
724
+ }
725
+ /**
726
+ * Can error only on internal bug.
727
+ *
728
+ * @internal */
729
+ function experimentalDqlResponseResults(self) {
730
+ ensureInitialized();
731
+ return ditto_dql_response_results(self);
732
+ }
733
+ /**
734
+ * The result CBOR contains a map/object with fields and values.
735
+ * No CRDTs are present there as they are not needed. Currently
736
+ * only values from registers are returned and non-register fields are
737
+ * ignored. Reading values from other CRDTs will be supported with
738
+ * definition support.
739
+ *
740
+ * Doesn't error
741
+ *
742
+ * @internal
743
+ */
744
+ function experimentalDqlResultCBOR(self) {
745
+ ensureInitialized();
746
+ const cborBytes = ditto_result_cbor(self);
747
+ return boxCBytesIntoBuffer(cborBytes);
748
+ }
677
749
  // ------------------------------------------------------------ LiveQuery ------
678
750
  /** @internal */
679
751
  function liveQueryRegister(ditto, collectionName, query, queryArgsCBOR, orderBy, limit, offset, eventHandler,
@@ -1342,9 +1414,12 @@ function bytesFromString(jsString) {
1342
1414
  return textEncoder.encode(`${jsString}\0`);
1343
1415
  }
1344
1416
  /** @internal */
1345
- function errorMessage() {
1417
+ function errorMessage(ffiError) {
1346
1418
  ensureInitialized();
1347
- const errorMessageCString = ditto_error_message();
1419
+ // eslint-disable-next-line
1420
+ const errorMessageCString = (typeof ffiError === 'undefined'
1421
+ ? ditto_error_message()
1422
+ : dittoffi_error_description(ffiError));
1348
1423
  return boxCStringIntoString(errorMessageCString);
1349
1424
  }
1350
1425
  /** @internal */
@@ -1521,7 +1596,7 @@ function awdlDestroy(awdl) {
1521
1596
 
1522
1597
  // NOTE: this is patched up with the actual build version by Jake task
1523
1598
  // build:package and has to be a valid semantic version as defined here: https://semver.org.
1524
- const fullBuildVersionString = '4.4.0';
1599
+ const fullBuildVersionString = '4.4.2-alpha1';
1525
1600
 
1526
1601
  //
1527
1602
  // Copyright © 2021 DittoLive Incorporated. All rights reserved.
@@ -1563,6 +1638,13 @@ async function init(options = {}) {
1563
1638
  * log messages with the Ditto logging infrastructure.
1564
1639
  */
1565
1640
  class Logger {
1641
+ /**
1642
+ * Registers a file path where logs will be written to, whenever Ditto wants
1643
+ * to issue a log (on _top_ of emitting the log to the console).
1644
+ */
1645
+ static get logFile() {
1646
+ return this._logFile;
1647
+ }
1566
1648
  /**
1567
1649
  * On Node, registers a file path where logs will be written to, whenever
1568
1650
  * Ditto wants to issue a log (on _top_ of emitting the log to the console).
@@ -1575,11 +1657,11 @@ class Logger {
1575
1657
  static setLogFile(path) {
1576
1658
  if (path) {
1577
1659
  loggerSetLogFile(path);
1578
- this['logFile'] = path;
1660
+ this._logFile = path;
1579
1661
  }
1580
1662
  else {
1581
- loggerSetLogFile(null);
1582
- delete this['logFile'];
1663
+ loggerSetLogFile(undefined);
1664
+ delete this._logFile;
1583
1665
  }
1584
1666
  }
1585
1667
  /**
@@ -1587,7 +1669,8 @@ class Logger {
1587
1669
  * {@link setLogFile | setLogFile()} with it.
1588
1670
  */
1589
1671
  static setLogFileURL(url) {
1590
- this.setLogFile(url === null || url === void 0 ? void 0 : url.pathname);
1672
+ var _a;
1673
+ this.setLogFile((_a = url === null || url === void 0 ? void 0 : url.pathname) !== null && _a !== void 0 ? _a : null);
1591
1674
  }
1592
1675
  /** Whether the logger is currently enabled. */
1593
1676
  static get enabled() {
@@ -1629,6 +1712,14 @@ class Logger {
1629
1712
  static set minimumLogLevel(minimumLogLevel) {
1630
1713
  loggerMinimumLogLevel(minimumLogLevel);
1631
1714
  }
1715
+ /**
1716
+ * Returns the current custom log callback, `undefined` by default. See
1717
+ * {@link setCustomLogCallback | setCustomLogCallback()} for a detailed
1718
+ * description.
1719
+ */
1720
+ static get customLogCallback() {
1721
+ return this._customLogCallback;
1722
+ }
1632
1723
  /**
1633
1724
  * Registers a custom callback that will be called to report each log entry.
1634
1725
  *
@@ -1639,11 +1730,11 @@ class Logger {
1639
1730
  static async setCustomLogCallback(callback) {
1640
1731
  if (callback) {
1641
1732
  await loggerSetCustomLogCb(callback);
1642
- this['customLogCallback'] = callback;
1733
+ this._customLogCallback = callback;
1643
1734
  }
1644
1735
  else {
1645
1736
  await loggerSetCustomLogCb(null);
1646
- delete this['customLogCallback'];
1737
+ delete this._customLogCallback;
1647
1738
  }
1648
1739
  }
1649
1740
  /**
@@ -1693,7 +1784,6 @@ class Logger {
1693
1784
  static verbose(message) {
1694
1785
  this.log('Verbose', message);
1695
1786
  }
1696
- // ------------------------------------------------------------ Private ------
1697
1787
  constructor() {
1698
1788
  throw new Error("Logger can't be instantiated, use its static properties & methods directly instead.");
1699
1789
  }
@@ -2839,7 +2929,7 @@ function validateDocumentIDCBOR(idCBOR) {
2839
2929
  }
2840
2930
 
2841
2931
  //
2842
- // Copyright © 2021 DittoLive Incorporated. All rights reserved.
2932
+ // Copyright © 2023 DittoLive Incorporated. All rights reserved.
2843
2933
  //
2844
2934
  // REFACTOR: tweak the API to use Rust concepts of ownership. For example,
2845
2935
  // register() could be named something like yieldOwnership() while unregister()
@@ -3284,6 +3374,10 @@ Bridge.document = new Bridge(documentFree);
3284
3374
  /** @internal */
3285
3375
  Bridge.mutableDocument = new Bridge(documentFree);
3286
3376
  /** @internal */
3377
+ Bridge.dqlResponse = new Bridge(experimentalDqlResponseFree);
3378
+ /** @internal */
3379
+ Bridge.dqlResult = new Bridge(experimentalDqlResultFree);
3380
+ /** @internal */
3287
3381
  Bridge.staticTCPClient = new Bridge(staticTCPClientFreeHandle);
3288
3382
  /** @internal */
3289
3383
  Bridge.websocketClient = new Bridge(websocketClientFreeHandle);
@@ -4584,6 +4678,12 @@ class ObserverManager {
4584
4678
  * `OnlineWithAuthentication` or an `Online` identity.
4585
4679
  */
4586
4680
  class Authenticator {
4681
+ /**
4682
+ Returns the current authentication status.
4683
+ */
4684
+ get status() {
4685
+ return this._status;
4686
+ }
4587
4687
  /**
4588
4688
  * Log in to Ditto with a third-party token. Throws if authentication is not
4589
4689
  * available, which can be checked with {@link loginSupported}.
@@ -4632,7 +4732,7 @@ class Authenticator {
4632
4732
  /** @internal */
4633
4733
  constructor(keepAlive) {
4634
4734
  this.keepAlive = keepAlive;
4635
- this.status = { isAuthenticated: false, userID: null };
4735
+ this._status = { isAuthenticated: false, userID: null };
4636
4736
  this.loginSupported = false;
4637
4737
  this.observerManager = new ObserverManager('AuthenticationStatusObservation', { keepAlive });
4638
4738
  }
@@ -4693,13 +4793,13 @@ class OnlineAuthenticator extends Authenticator {
4693
4793
  return ditto.deferCloseAsync(async () => {
4694
4794
  await dittoAuthClientLogout(dittoPointer);
4695
4795
  ditto.stopSync();
4696
- cleanupFn === null || cleanupFn === void 0 ? void 0 : cleanupFn(this.ditto);
4796
+ cleanupFn === null || cleanupFn === void 0 ? void 0 : cleanupFn(ditto);
4697
4797
  });
4698
4798
  }
4699
4799
  constructor(keepAlive, ditto, authenticationHandler) {
4700
4800
  super(keepAlive);
4701
- this['loginSupported'] = true;
4702
- this['status'] = { isAuthenticated: false, userID: null };
4801
+ this.loginSupported = true;
4802
+ this._status = { isAuthenticated: false, userID: null };
4703
4803
  this.ditto = new WeakRef(ditto);
4704
4804
  this.authenticationHandler = authenticationHandler;
4705
4805
  this.updateAndNotify(false);
@@ -4734,7 +4834,7 @@ class OnlineAuthenticator extends Authenticator {
4734
4834
  const isAuthenticated = dittoAuthClientIsWebValid(dittoPointer);
4735
4835
  const userID = dittoAuthClientUserID(dittoPointer);
4736
4836
  const status = { isAuthenticated, userID };
4737
- this['status'] = status;
4837
+ this._status = status;
4738
4838
  if (shouldNotify) {
4739
4839
  const sameStatus = !!wasAuthenticated === !!isAuthenticated && previousUserID === userID;
4740
4840
  if (!sameStatus) {
@@ -4927,6 +5027,7 @@ class TransportConfig {
4927
5027
  * Create a new transport config initialized with the default settings.
4928
5028
  */
4929
5029
  constructor() {
5030
+ this._isFrozen = false;
4930
5031
  this.peerToPeer = {
4931
5032
  bluetoothLE: { isEnabled: false },
4932
5033
  awdl: { isEnabled: false },
@@ -4963,13 +5064,20 @@ class TransportConfig {
4963
5064
  this.peerToPeer.lan.isEnabled = enabled;
4964
5065
  this.peerToPeer.awdl.isEnabled = enabled;
4965
5066
  }
5067
+ /**
5068
+ * Returns `true` if the transport configuration is frozen, otherwise
5069
+ * returns `false`.
5070
+ */
5071
+ get isFrozen() {
5072
+ return this._isFrozen;
5073
+ }
4966
5074
  /**
4967
5075
  * (Deep) freezes the receiver such that it can't be modified anymore.
4968
5076
  */
4969
5077
  freeze() {
4970
5078
  if (this.isFrozen)
4971
5079
  return this;
4972
- this['isFrozen'] = true;
5080
+ this._isFrozen = true;
4973
5081
  Object.freeze(this.peerToPeer.bluetoothLE);
4974
5082
  Object.freeze(this.peerToPeer.awdl);
4975
5083
  Object.freeze(this.peerToPeer.lan);
@@ -4996,8 +5104,8 @@ class TransportConfig {
4996
5104
  copy.connect.tcpServers = this.connect.tcpServers.slice();
4997
5105
  copy.connect.websocketURLs = this.connect.websocketURLs.slice();
4998
5106
  copy.connect.retryInterval = this.connect.retryInterval;
4999
- copy.listen['tcp'] = { ...this.listen.tcp };
5000
- copy.listen['http'] = { ...this.listen.http };
5107
+ copy.listen.tcp = { ...this.listen.tcp };
5108
+ copy.listen.http = { ...this.listen.http };
5001
5109
  copy.global.syncGroup = this.global.syncGroup;
5002
5110
  copy.global.routingHint = this.global.routingHint;
5003
5111
  return copy;
@@ -5008,6 +5116,7 @@ class TransportConfig {
5008
5116
  */
5009
5117
  static areListenTCPsEqual(left, right) {
5010
5118
  /* eslint-disable */
5119
+ // prettier-ignore
5011
5120
  return (left.isEnabled === right.isEnabled &&
5012
5121
  left.interfaceIP === right.interfaceIP &&
5013
5122
  left.port === right.port);
@@ -5019,6 +5128,7 @@ class TransportConfig {
5019
5128
  */
5020
5129
  static areListenHTTPsEqual(left, right) {
5021
5130
  /* eslint-disable */
5131
+ // prettier-ignore
5022
5132
  return (left.isEnabled === right.isEnabled &&
5023
5133
  left.interfaceIP === right.interfaceIP &&
5024
5134
  left.port === right.port &&
@@ -5042,6 +5152,13 @@ class TransportConfig {
5042
5152
  * try to be kept up-to-date with the latest changes from remote peers.
5043
5153
  */
5044
5154
  class Subscription {
5155
+ /**
5156
+ * Returns `true` if subscription has been explicitly cancelled, `false`
5157
+ * otherwise.
5158
+ */
5159
+ get isCancelled() {
5160
+ return this._isCancelled;
5161
+ }
5045
5162
  /**
5046
5163
  * The name of the collection that the subscription is based on.
5047
5164
  */
@@ -5053,18 +5170,14 @@ class Subscription {
5053
5170
  */
5054
5171
  cancel() {
5055
5172
  if (!this.isCancelled) {
5056
- this['isCancelled'] = true;
5173
+ this._isCancelled = true;
5057
5174
  this.manager.remove(this);
5058
5175
  }
5059
5176
  }
5060
5177
  // ----------------------------------------------------------- Internal ------
5061
5178
  /** @internal */
5062
5179
  constructor(collection, query, queryArgsCBOR, orderBys, limit, offset) {
5063
- /**
5064
- * Returns `true` if subscription has been explicitly cancelled, `false`
5065
- * otherwise.
5066
- */
5067
- this.isCancelled = false;
5180
+ this._isCancelled = false;
5068
5181
  // Query should be validated at this point.
5069
5182
  this.query = query;
5070
5183
  this.queryArgsCBOR = queryArgsCBOR;
@@ -5230,6 +5343,9 @@ class LiveQuery {
5230
5343
  this.liveQueryManager.stopLiveQuery(this);
5231
5344
  }
5232
5345
  }
5346
+ get liveQueryID() {
5347
+ return this._liveQueryID;
5348
+ }
5233
5349
  /** @internal */
5234
5350
  constructor(query, queryArgs, queryArgsCBOR, orderBys, limit, offset, collection, handler) {
5235
5351
  // Query should be validated at this point.
@@ -5277,11 +5393,11 @@ class LiveQuery {
5277
5393
  }
5278
5394
  handler(documents, event, signalNext);
5279
5395
  });
5280
- if (!liveQueryID) {
5281
- throw new Error("Internal inconsistency, couldn't create a valid live query ID.");
5282
- }
5283
- this['liveQueryID'] = liveQueryID;
5284
5396
  });
5397
+ if (!liveQueryID) {
5398
+ throw new Error("Internal inconsistency, couldn't create a valid live query ID.");
5399
+ }
5400
+ this._liveQueryID = liveQueryID;
5285
5401
  }
5286
5402
  /** @internal */
5287
5403
  async signalNext() {
@@ -6360,26 +6476,29 @@ class ExperimentalReplicationSubscriptionManager {
6360
6476
  * @internal
6361
6477
  */
6362
6478
  class ExperimentalReplicationSubscription {
6479
+ /**
6480
+ * `true` when {@link cancel | cancel()} has been called or the Ditto instance
6481
+ * managing this replication subscription has been closed.
6482
+ */
6483
+ // Recording this information on the replication subscription instance itself
6484
+ // allows working with it even after Ditto has been closed and the replication
6485
+ // subscription manager can not be accessed anymore.
6486
+ get isCancelled() {
6487
+ return this._isCancelled;
6488
+ }
6363
6489
  /**
6364
6490
  * Cancels a replication subscription and releases all associated resources.
6365
6491
  */
6366
6492
  cancel() {
6367
6493
  if (!this.isCancelled) {
6368
- this['isCancelled'] = true;
6494
+ this._isCancelled = true;
6369
6495
  this.manager.removeSubscription(this);
6370
6496
  }
6371
6497
  }
6372
6498
  // --------------------------- Internal --------------------------------------
6373
6499
  /** @internal */
6374
6500
  constructor(manager, query, queryArgsCBOR) {
6375
- /**
6376
- * `true` when {@link cancel | cancel()} has been called or the Ditto instance
6377
- * managing this replication subscription has been closed.
6378
- */
6379
- // Recording this information on the replication subscription instance itself
6380
- // allows working with it even after Ditto has been closed and the replication
6381
- // subscription manager can not be accessed anymore.
6382
- this.isCancelled = false;
6501
+ this._isCancelled = false;
6383
6502
  this.query = query;
6384
6503
  this.queryArgsCBOR = queryArgsCBOR;
6385
6504
  this.contextInfo = {
@@ -6480,14 +6599,23 @@ class ExperimentalStore {
6480
6599
  * The returned {@link ExperimentalReplicationSubscription} object must be kept in scope
6481
6600
  * for as long as you want to keep receiving updates.
6482
6601
  *
6602
+ * Use placeholders to incorporate values from the optional `queryArguments`
6603
+ * parameter into the query. The keys of the `queryArguments` object must
6604
+ * match the placeholders used within the query. You can not use placeholders
6605
+ * in the `FROM` clause.
6606
+ *
6483
6607
  * @internal
6484
6608
  * @param query a Ditto Query Language query string of the form SELECT * FROM
6485
6609
  * collection WHERE expression
6610
+ * @param queryArguments an object with values to be incorporated into a
6611
+ * query. The keys of the object must match the placeholders used within the
6612
+ * query.
6486
6613
  * @returns {@link ExperimentalReplicationSubscription} object
6487
6614
  */
6488
- addReplicationSubscription(query) {
6615
+ addReplicationSubscription(query, queryArguments) {
6489
6616
  Logger.debug(`ExperimentalStore.subscribe(query = ${query})`);
6490
- return new ExperimentalReplicationSubscription(this.replicationSubscriptionManager, query, null);
6617
+ const queryArgumentsCBOR = queryArguments ? CBOR.encode(queryArguments) : null;
6618
+ return new ExperimentalReplicationSubscription(this.replicationSubscriptionManager, query, queryArgumentsCBOR);
6491
6619
  }
6492
6620
  /** @internal */
6493
6621
  close() {
@@ -6497,18 +6625,25 @@ class ExperimentalStore {
6497
6625
  /**
6498
6626
  * Executes the given query in the local store and returns the result.
6499
6627
  *
6628
+ * Use placeholders to incorporate values from the optional `queryArguments`
6629
+ * parameter into the query. The keys of the `queryArguments` object must
6630
+ * match the placeholders used within the query. You can not use placeholders
6631
+ * in the `FROM` clause.
6632
+ *
6500
6633
  * Limitations:
6501
6634
  *
6502
6635
  * - Supports `SELECT * FROM <collection name>` with optional `WHERE
6503
6636
  * <expression>`
6504
- * - No query parameters as function arguments yet
6505
6637
  * - No transactions
6506
6638
  *
6507
6639
  * @internal
6508
6640
  * @param query a Ditto Query Language query string
6641
+ * @param queryArguments an object with values to be incorporated into a
6642
+ * query. The keys of the object must match the placeholders used within the
6643
+ * query.
6509
6644
  * @returns a promise for an array of Ditto documents matching the query
6510
6645
  */
6511
- async execute(query) {
6646
+ async execute(query, queryArguments) {
6512
6647
  Logger.debug(`ExperimentalStore.execute(query = ${query})`);
6513
6648
  const ditto = this.ditto;
6514
6649
  const dittoHandle = Bridge.ditto.handleFor(ditto);
@@ -6516,12 +6651,11 @@ class ExperimentalStore {
6516
6651
  // A one-off query execution uses a transaction internally but a
6517
6652
  // transaction API is not implemented yet.
6518
6653
  const writeTransaction = null;
6519
- // Not implemented yet.
6520
- const queryParameters = null;
6521
- const documentPointers = await performAsyncToWorkaroundNonAsyncFFIAPI(async () => {
6522
- return await experimentalExecQueryStr(dittoHandle.deref(), writeTransaction, query, queryParameters);
6654
+ const queryArgumentsCBOR = queryArguments ? CBOR.encode(queryArguments) : null;
6655
+ const responsePointer = await performAsyncToWorkaroundNonAsyncFFIAPI(async () => {
6656
+ return await experimentalExecQueryStr(dittoHandle.deref(), writeTransaction, query, queryArgumentsCBOR);
6523
6657
  });
6524
- return documentPointers.map((documentPointer) => Bridge.document.bridge(documentPointer));
6658
+ return Bridge.dqlResponse.bridge(responsePointer);
6525
6659
  });
6526
6660
  }
6527
6661
  }
@@ -7303,6 +7437,9 @@ class TransportConditionsManager extends ObserverManager {
7303
7437
  //
7304
7438
  /** @internal */
7305
7439
  class Sync {
7440
+ get parameters() {
7441
+ return this._parameters;
7442
+ }
7306
7443
  constructor(ditto) {
7307
7444
  this.bluetoothLETransportPointer = null;
7308
7445
  this.awdlTransportPointer = null;
@@ -7313,11 +7450,11 @@ class Sync {
7313
7450
  const transportConfig = new TransportConfig();
7314
7451
  const parameters = { identity, transportConfig, ditto, isWebValid: false, isX509Valid: false, isSyncActive: false };
7315
7452
  this.ditto = ditto;
7316
- this.parameters = parameters;
7453
+ this._parameters = parameters;
7317
7454
  this.state = stateFrom(parameters);
7318
7455
  }
7319
7456
  update(parameters) {
7320
- this['parameters'] = { ...parameters };
7457
+ this._parameters = { ...parameters };
7321
7458
  // NOTE: updating is a two-step process. Given all parameters, we
7322
7459
  // first compute the final "state" we want the transports stuff to
7323
7460
  // be in via the `stateFrom()` function. We then take that "desired" state
@@ -7897,6 +8034,32 @@ class Ditto {
7897
8034
  Logger.warning("⚠️ Deprecation Warning: The 'Ditto.path' property is deprecated. Please update your code to use the new 'Ditto.persistenceDirectory' property instead.");
7898
8035
  return this.persistenceDirectory;
7899
8036
  }
8037
+ /**
8038
+ * Returns `true` if an offline license token has been set, otherwise returns `false`.
8039
+ *
8040
+ * @see {@link setOfflineOnlyLicenseToken | setOfflineOnlyLicenseToken()}
8041
+ */
8042
+ get isActivated() {
8043
+ var _a;
8044
+ return (_a = this._isActivated) !== null && _a !== void 0 ? _a : false;
8045
+ }
8046
+ /**
8047
+ * `true` once {@link close | Ditto.close()} has been called, otherwise
8048
+ * `false`.
8049
+ */
8050
+ get isClosed() {
8051
+ var _a;
8052
+ return (_a = this._isClosed) !== null && _a !== void 0 ? _a : false;
8053
+ }
8054
+ /**
8055
+ * Returns `true` if sync is active, otherwise returns `false`. Use
8056
+ * {@link startSync | startSync()} to activate and
8057
+ * {@link stopSync | stopSync()} to deactivate sync.
8058
+ */
8059
+ get isSyncActive() {
8060
+ var _a;
8061
+ return (_a = this._isSyncActive) !== null && _a !== void 0 ? _a : false;
8062
+ }
7900
8063
  /**
7901
8064
  * Initializes a new `Ditto` instance.
7902
8065
  *
@@ -7908,20 +8071,18 @@ class Ditto {
7908
8071
  * `offlinePlayground` with `appID` being the empty string `''`.
7909
8072
  *
7910
8073
  * @param persistenceDirectory optional string containing a directory path
7911
- * that Ditto will use for persistence. Defaults to `"ditto"`.
8074
+ * that Ditto will use for persistence. Defaults to `"ditto"`. On Windows,
8075
+ * the path will be automatically normalized.
7912
8076
  *
7913
8077
  * @see {@link Ditto.identity}
7914
8078
  * @see {@link Ditto.persistenceDirectory}
7915
8079
  */
7916
8080
  constructor(identity, persistenceDirectory) {
7917
- /**
7918
- * `true` once {@link close | Ditto.close()} has been called, otherwise
7919
- * `false`.
7920
- */
7921
- this.isClosed = false;
7922
8081
  this.deferCloseAllowed = true;
7923
8082
  this.isWebValid = false;
7924
8083
  this.isX509Valid = false;
8084
+ this._isSyncActive = false;
8085
+ this._isClosed = false;
7925
8086
  /** Set of pending operations that need to complete before the Ditto instance can be closed in a safe manner. */
7926
8087
  this.pendingOperations = new Set();
7927
8088
  if (!Ditto.isEnvironmentSupported()) {
@@ -8035,14 +8196,14 @@ class Ditto {
8035
8196
  //
8036
8197
  this.appID = appID;
8037
8198
  this.siteID = siteID;
8038
- this.transportConfig = transportConfig.copy().freeze();
8199
+ this._transportConfig = transportConfig.copy().freeze();
8039
8200
  this.isX509Valid = isX509Valid;
8040
8201
  this.isWebValid = isWebValid;
8041
8202
  this.sync = new Sync(this);
8042
8203
  this.sync.update({ isSyncActive: false, isX509Valid, isWebValid, identity: validIdentity, ditto: this, transportConfig });
8043
8204
  // We don't need a license when running in the browser, so we
8044
8205
  // mark the instance as activated from the get-go in that case.
8045
- this.isActivated = !IdentityTypesRequiringOfflineLicenseToken.includes(validIdentity.type);
8206
+ this._isActivated = !IdentityTypesRequiringOfflineLicenseToken.includes(validIdentity.type);
8046
8207
  this.store = new Store(this);
8047
8208
  this.presence = new Presence(this);
8048
8209
  this.presenceManager = new PresenceManager(this);
@@ -8152,11 +8313,42 @@ class Ditto {
8152
8313
  }
8153
8314
  {
8154
8315
  const fs = require('fs');
8316
+ const path = require('path');
8317
+ if (process.platform === 'win32') {
8318
+ // Normalize the path on Windows to prevent issues with its max path
8319
+ // length. Windows has a hard limit at 260 characters for file paths
8320
+ // [1]. When this limit is exceeded reads and writes may fail.
8321
+ //
8322
+ // [1]: https://docs.microsoft.com/en-us/windows/win32/fileio/naming-a-file#maximum-path-length-limitation
8323
+ validatedPath = `\\\\?\\${path.resolve(validatedPath)}`;
8324
+ }
8325
+ const absolutePath = path.resolve(validatedPath);
8326
+ // We check if the directory exists before creating it to be able to
8327
+ // provide a more helpful error message in case the directory is not
8328
+ // writable.
8329
+ let isDirectoryExisting = false;
8155
8330
  try {
8156
- fs.mkdirSync(validatedPath, { recursive: true });
8331
+ fs.statSync(absolutePath);
8332
+ isDirectoryExisting = true;
8157
8333
  }
8158
8334
  catch (error) {
8159
- throw new Error(`Failed to create persistence directory at '${validatedPath}'`);
8335
+ // On Windows, permissions can prevent us from checking if the directory
8336
+ // exists: https://github.com/nodejs/node/issues/35853
8337
+ if (error.code === 'EPERM') {
8338
+ throw new Error(`Missing read or write permissions for the persistence directory path '${absolutePath}'. Please update the permissions or use a different path.`);
8339
+ }
8340
+ }
8341
+ if (!isDirectoryExisting) {
8342
+ try {
8343
+ fs.mkdirSync(absolutePath, { recursive: true });
8344
+ }
8345
+ catch (error) {
8346
+ throw new Error(`Failed to create persistence directory at path '${absolutePath}'.\n\n${error}`);
8347
+ }
8348
+ }
8349
+ // Caveat: It is still possible that these permissions are revoked during runtime.
8350
+ if (!isDirectoryWritable(absolutePath)) {
8351
+ throw new Error(`Missing read or write permissions for the persistence directory path '${absolutePath}'. Please update the permissions or use a different path.`);
8160
8352
  }
8161
8353
  }
8162
8354
  return validatedPath;
@@ -8175,11 +8367,11 @@ class Ditto {
8175
8367
  if (IdentityTypesRequiringOfflineLicenseToken.includes(this.identity.type)) {
8176
8368
  const { result, errorMessage } = verifyLicense(licenseToken);
8177
8369
  if (result !== 'LicenseOk') {
8178
- this['isActivated'] = false;
8370
+ this._isActivated = false;
8179
8371
  throw new Error(errorMessage);
8180
8372
  }
8181
8373
  else {
8182
- this['isActivated'] = true;
8374
+ this._isActivated = true;
8183
8375
  }
8184
8376
  }
8185
8377
  else {
@@ -8187,6 +8379,20 @@ class Ditto {
8187
8379
  }
8188
8380
  }
8189
8381
  }
8382
+ /**
8383
+ * Returns the current transport configuration, frozen. If you want to modify
8384
+ * the transport config, make a {@link TransportConfig.copy | copy} first. Or
8385
+ * use the {@link updateTransportConfig | updateTransportConfig()}
8386
+ * convenience method. By default peer-to-peer transports (Bluetooth, WiFi,
8387
+ * and AWDL) are enabled if available in the current environment
8388
+ * (Web, Node, OS, etc.).
8389
+ *
8390
+ * @see {@link setTransportConfig | setTransportConfig()}
8391
+ * @see {@link updateTransportConfig | updateTransportConfig()}
8392
+ */
8393
+ get transportConfig() {
8394
+ return this._transportConfig;
8395
+ }
8190
8396
  /**
8191
8397
  * Assigns a new transports configuration. By default peer-to-peer transports
8192
8398
  * (Bluetooth, WiFi, and AWDL) are enabled. You may use this method to alter
@@ -8197,7 +8403,7 @@ class Ditto {
8197
8403
  * @see {@link updateTransportConfig | updateTransportConfig()}
8198
8404
  */
8199
8405
  setTransportConfig(transportConfig) {
8200
- this['transportConfig'] = transportConfig.copy().freeze();
8406
+ this._transportConfig = transportConfig.copy().freeze();
8201
8407
  const transportConfigNew = this.transportConfig;
8202
8408
  const identity = this.identity;
8203
8409
  const isWebValid = this.isWebValid;
@@ -8334,7 +8540,7 @@ class Ditto {
8334
8540
  if (this.isClosed) {
8335
8541
  return;
8336
8542
  }
8337
- this['isClosed'] = true;
8543
+ this._isClosed = true;
8338
8544
  this.stopSync();
8339
8545
  this.store.close();
8340
8546
  this.presence.close();
@@ -8424,8 +8630,8 @@ class Ditto {
8424
8630
  }
8425
8631
  validateIdentity(identity) {
8426
8632
  const validIdentity = { ...identity };
8427
- identity['appName'];
8428
- const appID = identity['appID'];
8633
+ // @ts-expect-error we validate the existence of the value below.
8634
+ const appID = identity.appID;
8429
8635
  if (!['offlinePlayground', 'sharedKey', 'manual', 'onlinePlayground', 'onlineWithAuthentication'].includes(identity.type)) {
8430
8636
  throw new Error(`Can't create Ditto instance, unknown identity type: ${identity.type}`);
8431
8637
  }
@@ -8471,7 +8677,7 @@ class Ditto {
8471
8677
  if (this.isSyncActive && !flag) {
8472
8678
  this.keepAlive.release('sync');
8473
8679
  }
8474
- this['isSyncActive'] = flag;
8680
+ this._isSyncActive = flag;
8475
8681
  const isWebValid = this.isWebValid;
8476
8682
  const isX509Valid = this.isX509Valid;
8477
8683
  const identity = this.identity;
@@ -8535,6 +8741,54 @@ const disableDeadlockTimeoutWhenDebugging = () => {
8535
8741
  }
8536
8742
  }
8537
8743
  };
8744
+ /**
8745
+ * Return true if we have read and write permissions for the given directory.
8746
+ *
8747
+ * Always returns `true` in the browser.
8748
+ *
8749
+ * Uses `fs.accessSync()` on all platforms except Windows, where ACLs are not
8750
+ * checked by that method [1]. On Windows, we try writing and removing a temp
8751
+ * file to the given path instead.
8752
+ *
8753
+ * [1]:
8754
+ * https://nodejs.org/docs/latest-v18.x/api/fs.html#fsaccesspath-mode-callback
8755
+ *
8756
+ * @internal
8757
+ */
8758
+ const isDirectoryWritable = (directoryPath) => {
8759
+ {
8760
+ const fs = require('fs');
8761
+ const path = require('path');
8762
+ if (process.platform === 'win32') {
8763
+ const persistenceDirectory = path.resolve(directoryPath);
8764
+ const testFilePath = path.join(persistenceDirectory, 'ditto-permissions-test.txt');
8765
+ const testFileContents = 'permissions test';
8766
+ try {
8767
+ fs.writeFileSync(testFilePath, testFileContents);
8768
+ const fileContents = fs.readFileSync(testFilePath, 'utf8');
8769
+ if (fileContents !== testFileContents) {
8770
+ Logger.debug(`Failed to read back test file contents, expected '${testFileContents}' but got '${fileContents}'`);
8771
+ return false;
8772
+ }
8773
+ fs.unlinkSync(testFilePath);
8774
+ }
8775
+ catch (error) {
8776
+ Logger.debug(`Failed to access persistence directory: ${error === null || error === void 0 ? void 0 : error.message}`);
8777
+ return false;
8778
+ }
8779
+ }
8780
+ else {
8781
+ try {
8782
+ fs.accessSync(directoryPath, fs.constants.W_OK | fs.constants.R_OK);
8783
+ }
8784
+ catch (error) {
8785
+ Logger.debug(`Failed to access persistence directory: ${error === null || error === void 0 ? void 0 : error.message}`);
8786
+ return false;
8787
+ }
8788
+ }
8789
+ }
8790
+ return true;
8791
+ };
8538
8792
 
8539
8793
  /**
8540
8794
  * Get a count of bridged objects binned by bridge type.
@@ -8575,6 +8829,67 @@ class StaticTCPClient {
8575
8829
  class WebsocketClient {
8576
8830
  }
8577
8831
 
8832
+ //
8833
+ // Copyright © 2023 DittoLive Incorporated. All rights reserved.
8834
+ //
8835
+ /**
8836
+ * Represents single result object.
8837
+ * This API needs to be completed to navigate in the result object.
8838
+ * The result object is retrieved via FFI.dqlResultCBOR(Pointer<FFIDqlResult>).
8839
+ *
8840
+ * Currently experimental.
8841
+ *
8842
+ * @internal
8843
+ */
8844
+ class ExperimentalDqlResult {
8845
+ get value() {
8846
+ if (this.storedValue === undefined) {
8847
+ const result = Bridge.dqlResult.handleFor(this);
8848
+ const cbor = experimentalDqlResultCBOR(result.deref());
8849
+ this.storedValue = CBOR.decode(cbor);
8850
+ }
8851
+ return this.storedValue;
8852
+ }
8853
+ /**
8854
+ * FIXME: warn users who try to instantiate this class themselves
8855
+ *
8856
+ * @internal
8857
+ */
8858
+ constructor() { }
8859
+ }
8860
+
8861
+ //
8862
+ // Copyright © 2023 DittoLive Incorporated. All rights reserved.
8863
+ //
8864
+ /**
8865
+ * Represents the response from executing DQL statements.
8866
+ * Currently experimental.
8867
+ *
8868
+ * The memory management is quite complex. Every time when Rust is called
8869
+ * it will clone the result object. Rust mirror of DqlResponse contains
8870
+ * the collection of result objects. During the getting results call
8871
+ * Rust will copy DqlResult obejcts, thus JS here gets complete control
8872
+ * of their lifetimes.
8873
+ *
8874
+ * @internal
8875
+ */
8876
+ class ExperimentalDqlResponse {
8877
+ get results() {
8878
+ if (this.storedResults === undefined) {
8879
+ const response = Bridge.dqlResponse.handleFor(this);
8880
+ const results = experimentalDqlResponseResults(response.deref());
8881
+ this.storedResults = results.map((r) => Bridge.dqlResult.bridge(r));
8882
+ }
8883
+ return this.storedResults;
8884
+ }
8885
+ /**
8886
+ * FIXME: warn users who try to instantiate this class themselves
8887
+ *
8888
+ * @internal
8889
+ */
8890
+ constructor() { }
8891
+ }
8892
+
8578
8893
  //
8579
8894
  // Copyright © 2021 DittoLive Incorporated. All rights reserved.
8580
8895
  //
@@ -8586,6 +8901,8 @@ class WebsocketClient {
8586
8901
  // immediately require the bridged type, oftentimes leading to an import cycle.
8587
8902
  Bridge.attachment.registerType(Attachment);
8588
8903
  Bridge.document.registerType(Document);
8904
+ Bridge.dqlResult.registerType(ExperimentalDqlResult);
8905
+ Bridge.dqlResponse.registerType(ExperimentalDqlResponse);
8589
8906
  Bridge.mutableDocument.registerType(MutableDocument);
8590
8907
  Bridge.staticTCPClient.registerType(StaticTCPClient);
8591
8908
  Bridge.websocketClient.registerType(WebsocketClient);