@dittolive/ditto
Version:
Ditto is a cross-platform SDK that allows apps to sync with and even without internet connectivity.
1,027 lines (1,018 loc) • 458 kB
JavaScript
'use strict';
//
// Copyright © 2021 DittoLive Incorporated. All rights reserved.
//
/** @internal */
class KeepAlive {
/** @internal */
get isActive() {
return this.intervalID !== null;
}
/** @internal */
constructor() {
this.countsByID = {};
this.intervalID = null;
}
/** @internal */
retain(id) {
if (typeof this.countsByID[id] === 'undefined')
this.countsByID[id] = 0;
this.countsByID[id] += 1;
if (this.intervalID === null) {
// Keep the process alive as long as there is at least one ID being
// tracked by setting a time interval with the maximum delay. This will
// prevent the process from exiting.
const maxDelay = 2147483647; // Signed 32 bit integer, see docs: https://developer.mozilla.org/en-US/docs/Web/API/setInterval
this.intervalID = setInterval(() => {
/* no-op */
}, maxDelay);
KeepAlive.finalizationRegistry.register(this, this.intervalID, this);
}
}
/** @internal */
release(id) {
if (typeof this.countsByID[id] === 'undefined') {
throw new Error(`Internal inconsistency, trying to release a keep-alive ID that hasn't been retained before or isn't tracked anymore: ${id}`);
}
this.countsByID[id] -= 1;
if (this.countsByID[id] === 0)
delete this.countsByID[id];
if (Object.keys(this.countsByID).length === 0) {
// Nothing is tracked anymore, it's safe to clear the interval
// and let the process do what it wants.
KeepAlive.finalizationRegistry.unregister(this);
clearInterval(this.intervalID);
this.intervalID = null;
}
}
/** @internal */
currentIDs() {
return Object.keys(this.countsByID);
}
/** @internal */
countForID(id) {
var _a;
return (_a = this.countsByID[id]) !== null && _a !== void 0 ? _a : null;
}
}
KeepAlive.finalizationRegistry = new FinalizationRegistry(clearInterval);
//
// Copyright © 2021 DittoLive Incorporated. All rights reserved.
//
/**
* Generic observer handle returned by various observation APIs. The observation
* remains active until the {@link stop | stop()} method is called explicitly or
* the observer instance is garbage collected. Therefore, to keep the observation
* alive, you have to keep a reference to the corresponding observer.
*/
class Observer {
/** @internal */
get token() {
return this._token;
}
/** @internal */
constructor(observerManager, token, options = {}) {
this.observerManager = observerManager;
this._token = token;
this.options = options;
if (options.stopsWhenFinalized) {
Observer.finalizationRegistry.register(this, { observerManager, token }, this);
}
}
/**
* Returns `true` if the observer has been explicitly stopped via the `stop()`
* method. Otherwise returns `false`.
*/
get isStopped() {
return (this.token !== undefined && this.observerManager.hasObserver(this.token));
}
/**
* Stops the observation. Calling this method multiple times has no effect.
*/
stop() {
const token = this.token;
if (token) {
delete this._token;
Observer.finalizationRegistry.unregister(this);
this.observerManager.removeObserver(token);
}
}
static finalize(observerManagerAndToken) {
const { observerManager, token } = observerManagerAndToken;
observerManager.removeObserver(token);
}
}
Observer.finalizationRegistry = new FinalizationRegistry(Observer.finalize);
//
// Copyright © 2021 DittoLive Incorporated. All rights reserved.
//
// NOTE: we use a token to detect private invocation of the constructor. This is
// not secure and just to prevent accidental private invocation on the client
// side.
const privateToken$1 = Symbol('privateConstructorToken');
/**
* Represents a CRDT counter that can be upserted as part of a document or
* assigned to a property during an update of a document.
*
* Not available in React Native environments.
*/
class Counter {
/** The value of the counter. */
get value() {
return this._value;
}
/**
* Creates a new counter that can be used as part of a document's content.
*/
constructor() {
this._value = 0.0;
}
/** @internal */
static '@ditto.create'(mutDoc, path, value) {
// @ts-expect-error - using hidden argument
const counter = mutDoc ? new MutableCounter(privateToken$1) : new Counter();
counter.mutDoc = mutDoc;
counter.path = path;
counter._value = value;
return counter;
}
}
// -----------------------------------------------------------------------------
/**
* Represents a mutable CRDT counter that can be incremented by a specific
* amount while updating a document.
*
* This class can't be instantiated directly, it's returned automatically for
* any counter property within an update block via {@link MutableDocumentPath.counter}.
*/
class MutableCounter extends Counter {
/**
* Increments the counter by `amount`, which can be any valid number.
*
* Only valid within the `update` closure of
* {@link PendingCursorOperation.update | PendingCursorOperation.update()} and
* {@link PendingIDSpecificOperation.update | PendingIDSpecificOperation.update()},
* otherwise an exception is thrown.
*
* @throws {Error} when called in a React Native environment.
*/
increment(amount) {
const mutDoc = this.mutDoc;
const path = this.path;
if (!mutDoc) {
throw new Error(`Can't increment counter, only possible within the closure of a collection's update() method.`);
}
mutDoc.at(path)['@ditto.increment'](amount);
// We also increment the local value to make sure that the change is
// reflected locally as well as in the underlying document
this._value += amount;
}
/** @internal */
constructor() {
if (arguments[0] === privateToken$1)
super();
else
throw new Error(`MutableCounter constructor is for internal use only.`);
}
}
//
// Copyright © 2022 DittoLive Incorporated. All rights reserved.
//
// NOTE: we use a token to detect private invocation of the constructor. This is
// not secure and just to prevent accidental private invocation on the client
// side.
const privateToken = '@ditto.ff82dae89821c5ab822a8b539056bce4';
/**
* Represents a CRDT register that can be upserted as part of a document or
* assigned to a property during an update of a document.
*
* Not available in React Native environments.
*/
class Register {
/** Returns the value of the register. */
get value() {
return this['@ditto.value'];
}
/**
* Creates a new Register that can be used as part of a document's content.
*/
constructor(value) {
this['@ditto.value'] = value;
}
/** @internal */
static '@ditto.create'(mutableDocument, path, value) {
const register = mutableDocument
? new MutableRegister(value, privateToken)
: new Register(value);
register['@ditto.mutableDocument'] = mutableDocument;
register['@ditto.path'] = path;
register['@ditto.value'] = value;
return register;
}
}
// -----------------------------------------------------------------------------
/**
* Represents a mutable CRDT register that can be set to a specific value when
* updating a document.
*
* This class can't be instantiated directly, it's returned automatically for
* any register property of a document within an update block via
* {@link MutableDocumentPath.register}.
*
* Not available in React Native environments.
*/
class MutableRegister extends Register {
/**
* Returns the value of the register.
*
* Not available in React Native environments.
*/
get value() {
return super.value;
}
/**
* Convenience setter, equivalent to {@link set | set()}.
*
* Not available in React Native environments.
*/
set value(value) {
this.set(value);
}
/**
* Sets the register to the provided value.
*
* Only valid within the `update` closure of
* {@link PendingCursorOperation.update | PendingCursorOperation.update()} and
* {@link PendingIDSpecificOperation.update | PendingIDSpecificOperation.update()},
* otherwise an exception is thrown.
*
* Not available in React Native environments.
*/
set(value) {
const mutableDocument = this['@ditto.mutableDocument'];
const path = this['@ditto.path'];
mutableDocument.at(path)['@ditto.set'](value);
// We also set the local value to make sure that the change is
// reflected locally as well as in the underlying document.
this['@ditto.value'] = value;
}
/** @internal */
constructor(value) {
if (arguments[1] === privateToken)
super(value);
else
throw new Error(`MutableRegister constructor is for internal use only.`);
}
}
const ditto = (function () {
// IMPORTANT: most packagers perform _static_ analysis to identity native
// Node modules to copy them into the right directory within the
// final package. This only works if we use constant strings with
// require(). Therefore, we use an if for all supported targets and
// explicitly require each instead of dynamically building the require
// path.
const target = process.platform + '-' + process.arch;
try {
if (target === 'darwin-x64') return require('./ditto.darwin-x64.node')
if (target === 'darwin-arm64') return require('./ditto.darwin-arm64.node')
if (target === 'linux-x64') return require('./ditto.linux-x64.node')
if (target === 'linux-arm64') return require('./ditto.linux-arm64.node')
if (target === 'win32-x64') return require('./ditto.win32-x64.node')
} catch (error) {
throw new Error("Couldn't load native module 'ditto." + target + ".node' due to error:" + error.toString())
}
throw new Error("No native module 'ditto." + target + ".node' found. Please check the Ditto documentation for supported platforms.")
})();
function boxCBytesIntoBuffer(...args) { return ditto.boxCBytesIntoBuffer(...args) }
function boxCStringIntoString(...args) { return ditto.boxCStringIntoString(...args) }
function cStringVecToStringArray(...args) { return ditto.cStringVecToStringArray(...args) }
function ditto_add_subscription(...args) { return ditto.ditto_add_subscription(...args) }
function ditto_auth_client_get_app_id(...args) { return ditto.ditto_auth_client_get_app_id(...args) }
function ditto_auth_client_get_site_id(...args) { return ditto.ditto_auth_client_get_site_id(...args) }
function ditto_auth_client_is_web_valid(...args) { return ditto.ditto_auth_client_is_web_valid(...args) }
function ditto_auth_client_login_with_credentials(...args) { return ditto.ditto_auth_client_login_with_credentials(...args) }
function ditto_auth_client_login_with_token(...args) { return ditto.ditto_auth_client_login_with_token(...args) }
function ditto_auth_client_login_with_token_and_feedback(...args) { return ditto.ditto_auth_client_login_with_token_and_feedback(...args) }
function ditto_auth_client_logout(...args) { return ditto.ditto_auth_client_logout(...args) }
function ditto_auth_client_make_login_provider(...args) { return ditto.ditto_auth_client_make_login_provider(...args) }
function ditto_auth_client_user_id(...args) { return ditto.ditto_auth_client_user_id(...args) }
function ditto_auth_set_login_provider(...args) { return ditto.ditto_auth_set_login_provider(...args) }
function ditto_cancel_resolve_attachment(...args) { return ditto.ditto_cancel_resolve_attachment(...args) }
function ditto_clear_presence_callback(...args) { return ditto.ditto_clear_presence_callback(...args) }
function ditto_clear_presence_v3_callback(...args) { return ditto.ditto_clear_presence_v3_callback(...args) }
function ditto_collection_evict(...args) { return ditto.ditto_collection_evict(...args) }
function ditto_collection_evict_query_str(...args) { return ditto.ditto_collection_evict_query_str(...args) }
function ditto_collection_exec_query_str(...args) { return ditto.ditto_collection_exec_query_str(...args) }
function ditto_collection_get(...args) { return ditto.ditto_collection_get(...args) }
function ditto_collection_get_with_write_transaction(...args) { return ditto.ditto_collection_get_with_write_transaction(...args) }
function ditto_collection_insert_value(...args) { return ditto.ditto_collection_insert_value(...args) }
function ditto_collection_remove(...args) { return ditto.ditto_collection_remove(...args) }
function ditto_collection_remove_query_str(...args) { return ditto.ditto_collection_remove_query_str(...args) }
function ditto_collection_update(...args) { return ditto.ditto_collection_update(...args) }
function ditto_collection_update_multiple(...args) { return ditto.ditto_collection_update_multiple(...args) }
function ditto_disable_sync_with_v3(...args) { return ditto.ditto_disable_sync_with_v3(...args) }
function ditto_document_free(...args) { return ditto.ditto_document_free(...args) }
function ditto_document_get_cbor_with_path_type(...args) { return ditto.ditto_document_get_cbor_with_path_type(...args) }
function ditto_document_id(...args) { return ditto.ditto_document_id(...args) }
function ditto_document_id_query_compatible(...args) { return ditto.ditto_document_id_query_compatible(...args) }
function ditto_document_increment_counter(...args) { return ditto.ditto_document_increment_counter(...args) }
function ditto_document_remove(...args) { return ditto.ditto_document_remove(...args) }
function ditto_document_set_cbor(...args) { return ditto.ditto_document_set_cbor(...args) }
function ditto_document_set_cbor_with_timestamp(...args) { return ditto.ditto_document_set_cbor_with_timestamp(...args) }
function ditto_documents_hash(...args) { return ditto.ditto_documents_hash(...args) }
function ditto_documents_hash_mnemonic(...args) { return ditto.ditto_documents_hash_mnemonic(...args) }
function ditto_error_message(...args) { return ditto.ditto_error_message(...args) }
function ditto_free(...args) { return ditto.ditto_free(...args) }
function ditto_free_attachment_handle(...args) { return ditto.ditto_free_attachment_handle(...args) }
function ditto_get_collection_names(...args) { return ditto.ditto_get_collection_names(...args) }
function ditto_get_complete_attachment_path(...args) { return ditto.ditto_get_complete_attachment_path(...args) }
function ditto_get_sdk_version(...args) { return ditto.ditto_get_sdk_version(...args) }
function ditto_identity_config_make_manual_v0(...args) { return ditto.ditto_identity_config_make_manual_v0(...args) }
function ditto_identity_config_make_offline_playground(...args) { return ditto.ditto_identity_config_make_offline_playground(...args) }
function ditto_identity_config_make_online_playground(...args) { return ditto.ditto_identity_config_make_online_playground(...args) }
function ditto_identity_config_make_online_with_authentication(...args) { return ditto.ditto_identity_config_make_online_with_authentication(...args) }
function ditto_identity_config_make_shared_key(...args) { return ditto.ditto_identity_config_make_shared_key(...args) }
function ditto_init_sdk_version(...args) { return ditto.ditto_init_sdk_version(...args) }
function ditto_live_query_register_str_detached(...args) { return ditto.ditto_live_query_register_str_detached(...args) }
function ditto_live_query_signal_available_next(...args) { return ditto.ditto_live_query_signal_available_next(...args) }
function ditto_live_query_start(...args) { return ditto.ditto_live_query_start(...args) }
function ditto_live_query_stop(...args) { return ditto.ditto_live_query_stop(...args) }
function ditto_log(...args) { return ditto.ditto_log(...args) }
function ditto_logger_emoji_headings_enabled(...args) { return ditto.ditto_logger_emoji_headings_enabled(...args) }
function ditto_logger_emoji_headings_enabled_get(...args) { return ditto.ditto_logger_emoji_headings_enabled_get(...args) }
function ditto_logger_enabled(...args) { return ditto.ditto_logger_enabled(...args) }
function ditto_logger_enabled_get(...args) { return ditto.ditto_logger_enabled_get(...args) }
function ditto_logger_init(...args) { return ditto.ditto_logger_init(...args) }
function ditto_logger_minimum_log_level(...args) { return ditto.ditto_logger_minimum_log_level(...args) }
function ditto_logger_minimum_log_level_get(...args) { return ditto.ditto_logger_minimum_log_level_get(...args) }
function ditto_logger_set_custom_log_cb(...args) { return ditto.ditto_logger_set_custom_log_cb(...args) }
function ditto_logger_set_log_file(...args) { return ditto.ditto_logger_set_log_file(...args) }
function ditto_new_attachment_from_bytes(...args) { return ditto.ditto_new_attachment_from_bytes(...args) }
function ditto_new_attachment_from_file(...args) { return ditto.ditto_new_attachment_from_file(...args) }
function ditto_presence_v1(...args) { return ditto.ditto_presence_v1(...args) }
function ditto_presence_v3(...args) { return ditto.ditto_presence_v3(...args) }
function ditto_read_transaction(...args) { return ditto.ditto_read_transaction(...args) }
function ditto_read_transaction_free(...args) { return ditto.ditto_read_transaction_free(...args) }
function ditto_register_presence_v1_callback(...args) { return ditto.ditto_register_presence_v1_callback(...args) }
function ditto_register_presence_v3_callback(...args) { return ditto.ditto_register_presence_v3_callback(...args) }
function ditto_register_transport_condition_changed_callback(...args) { return ditto.ditto_register_transport_condition_changed_callback(...args) }
function ditto_remove_subscription(...args) { return ditto.ditto_remove_subscription(...args) }
function ditto_resolve_attachment(...args) { return ditto.ditto_resolve_attachment(...args) }
function ditto_run_garbage_collection(...args) { return ditto.ditto_run_garbage_collection(...args) }
function ditto_sdk_transports_error_free(...args) { return ditto.ditto_sdk_transports_error_free(...args) }
function ditto_sdk_transports_error_new(...args) { return ditto.ditto_sdk_transports_error_new(...args) }
function ditto_sdk_transports_error_value(...args) { return ditto.ditto_sdk_transports_error_value(...args) }
function ditto_sdk_transports_init(...args) { return ditto.ditto_sdk_transports_init(...args) }
function ditto_set_device_name(...args) { return ditto.ditto_set_device_name(...args) }
function ditto_shutdown(...args) { return ditto.ditto_shutdown(...args) }
function ditto_small_peer_info_get_is_enabled(...args) { return ditto.ditto_small_peer_info_get_is_enabled(...args) }
function ditto_small_peer_info_get_metadata(...args) { return ditto.ditto_small_peer_info_get_metadata(...args) }
function ditto_small_peer_info_get_sync_scope(...args) { return ditto.ditto_small_peer_info_get_sync_scope(...args) }
function ditto_small_peer_info_set_enabled(...args) { return ditto.ditto_small_peer_info_set_enabled(...args) }
function ditto_small_peer_info_set_metadata(...args) { return ditto.ditto_small_peer_info_set_metadata(...args) }
function ditto_small_peer_info_set_sync_scope(...args) { return ditto.ditto_small_peer_info_set_sync_scope(...args) }
function ditto_validate_document_id(...args) { return ditto.ditto_validate_document_id(...args) }
function ditto_write_transaction(...args) { return ditto.ditto_write_transaction(...args) }
function ditto_write_transaction_commit(...args) { return ditto.ditto_write_transaction_commit(...args) }
function ditto_write_transaction_rollback(...args) { return ditto.ditto_write_transaction_rollback(...args) }
function dittoffi_authentication_status_free(...args) { return ditto.dittoffi_authentication_status_free(...args) }
function dittoffi_authentication_status_is_authenticated(...args) { return ditto.dittoffi_authentication_status_is_authenticated(...args) }
function dittoffi_authentication_status_user_id(...args) { return ditto.dittoffi_authentication_status_user_id(...args) }
function dittoffi_base64_encode(...args) { return ditto.dittoffi_base64_encode(...args) }
function dittoffi_connection_request_authorize(...args) { return ditto.dittoffi_connection_request_authorize(...args) }
function dittoffi_connection_request_connection_type(...args) { return ditto.dittoffi_connection_request_connection_type(...args) }
function dittoffi_connection_request_free(...args) { return ditto.dittoffi_connection_request_free(...args) }
function dittoffi_connection_request_identity_service_metadata_json(...args) { return ditto.dittoffi_connection_request_identity_service_metadata_json(...args) }
function dittoffi_connection_request_peer_key_string(...args) { return ditto.dittoffi_connection_request_peer_key_string(...args) }
function dittoffi_connection_request_peer_metadata_json(...args) { return ditto.dittoffi_connection_request_peer_metadata_json(...args) }
function dittoffi_crypto_generate_secure_random_token(...args) { return ditto.dittoffi_crypto_generate_secure_random_token(...args) }
function dittoffi_differ_diff(...args) { return ditto.dittoffi_differ_diff(...args) }
function dittoffi_differ_free(...args) { return ditto.dittoffi_differ_free(...args) }
function dittoffi_differ_new(...args) { return ditto.dittoffi_differ_new(...args) }
function dittoffi_ditto_is_activated(...args) { return ditto.dittoffi_ditto_is_activated(...args) }
function dittoffi_ditto_is_sync_active(...args) { return ditto.dittoffi_ditto_is_sync_active(...args) }
function dittoffi_ditto_set_authentication_status_handler(...args) { return ditto.dittoffi_ditto_set_authentication_status_handler(...args) }
function dittoffi_ditto_set_cloud_sync_enabled(...args) { return ditto.dittoffi_ditto_set_cloud_sync_enabled(...args) }
function dittoffi_ditto_stop_sync(...args) { return ditto.dittoffi_ditto_stop_sync(...args) }
function dittoffi_ditto_transport_config(...args) { return ditto.dittoffi_ditto_transport_config(...args) }
function dittoffi_ditto_try_new_blocking(...args) { return ditto.dittoffi_ditto_try_new_blocking(...args) }
function dittoffi_ditto_try_set_transport_config(...args) { return ditto.dittoffi_ditto_try_set_transport_config(...args) }
function dittoffi_ditto_try_start_sync(...args) { return ditto.dittoffi_ditto_try_start_sync(...args) }
function dittoffi_error_code(...args) { return ditto.dittoffi_error_code(...args) }
function dittoffi_error_description(...args) { return ditto.dittoffi_error_description(...args) }
function dittoffi_error_free(...args) { return ditto.dittoffi_error_free(...args) }
function dittoffi_get_sdk_semver(...args) { return ditto.dittoffi_get_sdk_semver(...args) }
function dittoffi_logger_try_export_to_file_async(...args) { return ditto.dittoffi_logger_try_export_to_file_async(...args) }
function dittoffi_presence_peer_metadata_json(...args) { return ditto.dittoffi_presence_peer_metadata_json(...args) }
function dittoffi_presence_set_connection_request_handler(...args) { return ditto.dittoffi_presence_set_connection_request_handler(...args) }
function dittoffi_presence_try_set_peer_metadata_json(...args) { return ditto.dittoffi_presence_try_set_peer_metadata_json(...args) }
function dittoffi_query_result_free(...args) { return ditto.dittoffi_query_result_free(...args) }
function dittoffi_query_result_item_at(...args) { return ditto.dittoffi_query_result_item_at(...args) }
function dittoffi_query_result_item_cbor(...args) { return ditto.dittoffi_query_result_item_cbor(...args) }
function dittoffi_query_result_item_count(...args) { return ditto.dittoffi_query_result_item_count(...args) }
function dittoffi_query_result_item_free(...args) { return ditto.dittoffi_query_result_item_free(...args) }
function dittoffi_query_result_item_json(...args) { return ditto.dittoffi_query_result_item_json(...args) }
function dittoffi_query_result_item_new(...args) { return ditto.dittoffi_query_result_item_new(...args) }
function dittoffi_query_result_mutated_document_id_at(...args) { return ditto.dittoffi_query_result_mutated_document_id_at(...args) }
function dittoffi_query_result_mutated_document_id_count(...args) { return ditto.dittoffi_query_result_mutated_document_id_count(...args) }
function dittoffi_store_begin_transaction_async_throws(...args) { return ditto.dittoffi_store_begin_transaction_async_throws(...args) }
function dittoffi_store_transactions(...args) { return ditto.dittoffi_store_transactions(...args) }
function dittoffi_transaction_complete_async_throws(...args) { return ditto.dittoffi_transaction_complete_async_throws(...args) }
function dittoffi_transaction_execute_async_throws(...args) { return ditto.dittoffi_transaction_execute_async_throws(...args) }
function dittoffi_transaction_free(...args) { return ditto.dittoffi_transaction_free(...args) }
function dittoffi_transaction_info(...args) { return ditto.dittoffi_transaction_info(...args) }
function dittoffi_try_add_sync_subscription(...args) { return ditto.dittoffi_try_add_sync_subscription(...args) }
function dittoffi_try_base64_decode(...args) { return ditto.dittoffi_try_base64_decode(...args) }
function dittoffi_try_exec_statement(...args) { return ditto.dittoffi_try_exec_statement(...args) }
function dittoffi_try_experimental_register_change_observer_str_detached(...args) { return ditto.dittoffi_try_experimental_register_change_observer_str_detached(...args) }
function dittoffi_try_remove_sync_subscription(...args) { return ditto.dittoffi_try_remove_sync_subscription(...args) }
function dittoffi_try_verify_license(...args) { return ditto.dittoffi_try_verify_license(...args) }
function getDeadlockTimeout$1(...args) { return ditto.getDeadlockTimeout(...args) }
function jsDocsToCDocs(...args) { return ditto.jsDocsToCDocs(...args) }
function refCBytesIntoBuffer(...args) { return ditto.refCBytesIntoBuffer(...args) }
function refCStringToString(...args) { return ditto.refCStringToString(...args) }
function setDeadlockTimeout$1(...args) { return ditto.setDeadlockTimeout(...args) }
function withOutBoxCBytes(...args) { return ditto.withOutBoxCBytes(...args) }
//
// Copyright © 2023 DittoLive Incorporated. All rights reserved.
//
// This internal module contains the Ditto FFI error type and helper functions
// for working with it. Except for the feature flag helper, this module should
// not depend on any other parts of the Ditto Javascript SDK.
/** Matches a `<...>` prefix in an FFI result error message, e.g. `<dql> ...`. **/
const PREFIX_REGEX = new RegExp(/^<.*?>\s*/);
/**
* Represents an exception that occurred during a call into the Ditto FFI.
*
* Use the {@link throwOnErrorStatus | throwOnErrorStatus()} helper to
* automatically throw this error when an FFI call returns with a non-zero
* return value.
*
* @internal
*/
class DittoFFIError extends Error {
/**
* Only call this constructor after having called `FFI.ensureInitialized()`
* and `FFI.trace()`.
*
* @param code numerical status code returned by an FFI call or an
* {@link FFIResultErrorCode} for errors returned on FFI result objects
* @param messageOverride overrides the thread-local error message set in
* Ditto core
* @param messageFallback fallback message to use if the thread-local error
* message is empty
*/
constructor(code, messageOverride, messageFallback) {
// Call `ffiErrorMessage()` even when an override is provided to ensure that
// the thread-local error message is cleared.
const threadLocalErrorMessage = ffiErrorMessage();
super(messageOverride || threadLocalErrorMessage || messageFallback);
this.code = code;
}
}
/**
* Throws a {@link DittoFFIError} if the given `ffiError` is not `null`.
*
* Removes the thread-local error message from Ditto core. If an error description
* is available on the given `ffiError`, it is used as the error message of the
* thrown {@link DittoFFIError}. A prefix in the error message is removed, e.g.
* `<dql> ...`.
*
* If no error description is available, the given `ffiFunctionName` is used to
* produce a fallback error message.
*
* @internal
*/
function throwOnErrorResult(ffiError, ffiFunctionName) {
if (ffiError !== null) {
let errorCode;
let errorMsg;
try {
errorCode = dittoffi_error_code(ffiError);
errorMsg = boxCStringIntoString(dittoffi_error_description(ffiError));
dittoffi_error_free(ffiError);
}
catch (err) {
throw new DittoFFIError(-1, `Failed to retrieve Ditto core error message: ${err.message}`);
}
if (errorMsg == null) {
errorMsg = `${ffiFunctionName}() failed with error code: ${errorCode}`;
}
else {
// Remove prefix from error message, e.g. `<dql> ...`.
errorMsg = errorMsg.replace(PREFIX_REGEX, '');
}
throw new DittoFFIError(errorCode, errorMsg);
}
}
/**
* Retrieves last thread-local error message and removes it.
*
* Subsequent call to this function (if no new error message has been set) will
* always return `null`.
*
* This function is not calling `FFI.ensureInitialized()` to avoid a circular
* dependency. This is okay as long as this function is only called from a
* context where `FFI.ensureInitialized()` has already been called.
*
* @internal
*/
function ffiErrorMessage() {
const errorMessageCString = ditto_error_message();
return boxCStringIntoString(errorMessageCString);
}
//
// Copyright © 2023 DittoLive Incorporated. All rights reserved.
//
// NOTE: This is a temporary *hand-written* shim file for glue code for the
// Ditto native Node module. Mid to long term, we'll either move all of this
// code to the native side or generate this file via a companion CLI tool or
// something equivalent.
//
// IMPORTANT: please leave this file self-contained and do not import any
// sources from the JS SDK.
/** @internal */
const DittoCRDTTypeKey = '_ditto_internal_type_jkb12973t4b';
/** @internal */
const DittoCRDTValueKey = '_value';
/** @internal */
var DittoCRDTType;
(function (DittoCRDTType) {
DittoCRDTType[DittoCRDTType["counter"] = 0] = "counter";
DittoCRDTType[DittoCRDTType["register"] = 1] = "register";
DittoCRDTType[DittoCRDTType["attachment"] = 2] = "attachment";
DittoCRDTType[DittoCRDTType["rga"] = 3] = "rga";
DittoCRDTType[DittoCRDTType["rwMap"] = 4] = "rwMap";
})(DittoCRDTType || (DittoCRDTType = {}));
// ------------------------------------------------------------- Differ --------
function differNew() {
ensureInitialized();
return dittoffi_differ_new();
}
function differDiff(differ, items) {
ensureInitialized();
return dittoffi_differ_diff(differ, items);
}
function differFree(differ) {
ensureInitialized();
dittoffi_differ_free(differ);
}
// HACK: underlying safer-ffi doesn't equivalent a CDitto with dittoffi_store_t
// although they are C aliases so we need to force it's type.
/** @internal */
function dittoPointerToStorePointer(dittoHandle) {
return {
addr: dittoHandle.addr,
type: 'dittoffi_store_t const *',
};
}
// ------------------------------------------------------------- Document ------
/** @internal */
function documentSetCBORWithTimestamp(document, path, cbor, timestamp) {
ensureInitialized();
const pathX = bytesFromString(path);
const errorCode = ditto_document_set_cbor_with_timestamp(document, pathX, cbor, timestamp);
if (errorCode !== 0) {
throw new Error(errorMessage() ||
`ditto_document_set_cbor_with_timestamp() failed with error code: ${errorCode}`);
}
}
/** @internal */
function documentSetCBOR(document, path, cbor) {
ensureInitialized();
// NOTE: not sure if this should be async or not.
const pathX = bytesFromString(path);
const errorCode = ditto_document_set_cbor(document, pathX, cbor);
if (errorCode !== 0) {
throw new Error(errorMessage() ||
`ditto_document_set_cbor() failed with error code: ${errorCode}`);
}
}
/** @internal */
function documentID(self) {
ensureInitialized();
// REFACTOR: add proper error handling.
const documentIDX = ditto_document_id(self);
return boxCBytesIntoBuffer(documentIDX);
}
/** @internal */
function documentGetCBORWithPathType(document, path, pathType) {
ensureInitialized();
const pathBytes = bytesFromString(path);
const cborPathResultRaw = ditto_document_get_cbor_with_path_type(document, pathBytes, pathType);
const cborPathResult = {
statusCode: cborPathResultRaw.status_code,
cbor: boxCBytesIntoBuffer(cborPathResultRaw.cbor),
};
return cborPathResult;
}
/** @internal */
function documentRemove(document, path) {
ensureInitialized();
const pathBytes = bytesFromString(path);
const errorCode = ditto_document_remove(document, pathBytes);
if (errorCode !== 0) {
throw new Error(errorMessage() ||
`ditto_document_remove() failed with error code: ${errorCode}`);
}
}
/** @internal */
function documentIncrementCounter(document, path, amount) {
ensureInitialized();
const pathBytes = bytesFromString(path);
const errorCode = ditto_document_increment_counter(document, pathBytes, amount);
if (errorCode !== 0) {
throw new Error(errorMessage() ||
`ditto_document_increment_counter() failed with error code: ${errorCode}`);
}
}
/** @internal */
function documentFree(self) {
ensureInitialized();
// REFACTOR: add proper error handling.
ditto_document_free(self);
}
// ----------------------------------------------------------- DocumentID ------
/** @internal */
function documentIDQueryCompatible(docID, stringPrimitiveFormat) {
ensureInitialized();
const docIDString = ditto_document_id_query_compatible(docID, stringPrimitiveFormat);
return boxCStringIntoString(docIDString);
}
/** @internal */
function validateDocumentID(docID) {
ensureInitialized();
const cborCBytes = withOutBoxCBytes((outCBOR) => {
const errorCode = ditto_validate_document_id(docID, outCBOR);
if (errorCode !== 0) {
throw new Error(errorMessage() ||
`ditto_validate_document_id() failed with error code: ${errorCode}`);
}
return outCBOR;
});
return boxCBytesIntoBuffer(cborCBytes);
}
// ----------------------------------------------------------- Collection ------
/** @internal */
async function collectionGet(ditto, collectionName, documentID, readTransaction) {
ensureInitialized();
// REFACTOR: add proper error handling.
const collectionNamePointer = bytesFromString(collectionName);
const { status_code: errorCode, document } = await ditto_collection_get(ditto, collectionNamePointer, documentID, readTransaction);
if (errorCode === NOT_FOUND_ERROR_CODE)
return null;
if (errorCode !== 0) {
throw new Error(errorMessage() ||
`ditto_collection_get() failed with error code: ${errorCode}`);
}
return document;
}
/** @internal */
async function collectionGetWithWriteTransaction(ditto, collectionName, documentID, writeTransaction) {
ensureInitialized();
const collectionNamePointer = bytesFromString(collectionName);
const { status_code: errorCode, document } = await ditto_collection_get_with_write_transaction(ditto, collectionNamePointer, documentID, writeTransaction);
if (errorCode === NOT_FOUND_ERROR_CODE)
return null;
if (errorCode !== 0) {
throw new Error(errorMessage() ||
`ditto_collection_get_with_write_transaction() failed with error code: ${errorCode}`);
}
return document;
}
/** @internal */
async function collectionInsertValue(ditto, collectionName, doc_cbor, writeStrategy, writeTransaction) {
ensureInitialized();
// REFACTOR: add proper error handling.
const collectionNameX = bytesFromString(collectionName);
let strategy;
switch (writeStrategy) {
case 'merge':
strategy = 'Merge';
break;
case 'insertIfAbsent':
strategy = 'InsertIfAbsent';
break;
case 'insertDefaultIfAbsent':
strategy = 'InsertDefaultIfAbsent';
break;
case 'updateDifferentValues':
strategy = 'UpdateDifferentValues';
break;
default:
throw new Error(`Unsupported write strategy '${writeStrategy}' provided.`);
}
const { status_code: errorCode, id } = await ditto_collection_insert_value(ditto, collectionNameX, doc_cbor, strategy, null, writeTransaction !== null && writeTransaction !== void 0 ? writeTransaction : null);
if (errorCode !== 0) {
throw new Error(errorMessage() ||
`ditto_collection_insert_value() failed with error code: ${errorCode}`);
}
return boxCBytesIntoBuffer(id);
}
/** @internal */
async function collectionRemove(ditto, collectionName, writeTransaction, documentID) {
ensureInitialized();
// REFACTOR: add proper error handling.
const collectionNameX = bytesFromString(collectionName);
const { status_code: errorCode, bool_value: didRemove } = await ditto_collection_remove(ditto, collectionNameX, writeTransaction, documentID);
if (errorCode !== 0) {
throw new Error(errorMessage() ||
`ditto_collection_remove() failed with error code: ${errorCode}`);
}
return didRemove;
}
/** @internal */
async function collectionEvict(ditto, collectionName, writeTransaction, documentID) {
ensureInitialized();
// REFACTOR: add proper error handling.
const collectionNameX = bytesFromString(collectionName);
const { status_code: errorCode, bool_value: didEvict } = await ditto_collection_evict(ditto, collectionNameX, writeTransaction, documentID);
if (errorCode !== 0) {
throw new Error(errorMessage() ||
`ditto_collection_evict() failed with error code: ${errorCode}`);
}
return didEvict;
}
/** @internal */
async function collectionUpdate(ditto, collectionName, writeTransaction, document) {
ensureInitialized();
const collectionNameX = bytesFromString(collectionName);
const errorCode = await ditto_collection_update(ditto, collectionNameX, writeTransaction, document);
if (errorCode !== 0) {
throw new Error(errorMessage() ||
`ditto_collection_update() failed with error code: ${errorCode}`);
}
}
/** @internal */
async function collectionUpdateMultiple(ditto, collectionName, writeTransaction, documents) {
ensureInitialized();
const collectionNameX = bytesFromString(collectionName);
const cDocuments = jsDocsToCDocs(documents);
const errorCode = await ditto_collection_update_multiple(ditto, collectionNameX, writeTransaction, cDocuments);
if (errorCode !== 0) {
throw new Error(errorMessage() ||
`ditto_collection_update_multiple() failed with error code: ${errorCode}`);
}
}
/** @internal */
async function collectionExecQueryStr(ditto, collectionName, writeTransaction, query, queryArgsCBOR, orderBy, limit, offset) {
ensureInitialized();
const collectionNameX = bytesFromString(collectionName);
const queryX = bytesFromString(query);
return await ditto_collection_exec_query_str(ditto, collectionNameX, writeTransaction, queryX, queryArgsCBOR, orderBy, limit, offset);
}
/** @internal */
async function collectionRemoveQueryStr(ditto, collectionName, writeTransaction, query, queryArgsCBOR, orderBy, limit, offset) {
ensureInitialized();
const collectionNameX = bytesFromString(collectionName);
const queryX = bytesFromString(query);
return await ditto_collection_remove_query_str(ditto, collectionNameX, writeTransaction, queryX, queryArgsCBOR, orderBy, limit, offset);
}
/** @internal */
async function collectionEvictQueryStr(ditto, collectionName, writeTransaction, query, queryArgsCBOR, orderBy, limit, offset) {
ensureInitialized();
const collectionNameX = bytesFromString(collectionName);
const queryX = bytesFromString(query);
return await ditto_collection_evict_query_str(ditto, collectionNameX, writeTransaction, queryX, queryArgsCBOR, orderBy, limit, offset);
}
/**
* This FFI can error:
* - DQL parser error
* - Incorrect arguments to query parameters
* - Collection is not found.
*
* @internal
*/
async function tryExecStatement(ditto, query, queryArgsCBOR) {
ensureInitialized();
const queryBytesPointer = bytesFromString(query);
const result = await dittoffi_try_exec_statement(ditto, queryBytesPointer, queryArgsCBOR);
throwOnErrorResult(result.error, 'dittoffi_try_exec_statement');
return result.success;
}
/** @internal */
function addSubscription(ditto, collectionName, query, queryArgsCBOR, orderBy, limit, offset) {
ensureInitialized();
const collectionNameX = bytesFromString(collectionName);
const queryX = bytesFromString(query);
const statusCode = ditto_add_subscription(ditto, collectionNameX, queryX, queryArgsCBOR, orderBy, limit, offset);
if (statusCode !== 0) {
throw new Error(errorMessage() ||
`ditto_add_subscription() failed with error code: ${statusCode}`);
}
}
/** @internal */
function removeSubscription(ditto, collectionName, query, queryArgsCBOR, orderBy, limit, offset) {
ensureInitialized();
const collectionNameX = bytesFromString(collectionName);
const queryX = bytesFromString(query);
const statusCode = ditto_remove_subscription(ditto, collectionNameX, queryX, queryArgsCBOR, orderBy, limit, offset);
if (statusCode !== 0) {
throw new Error(errorMessage() ||
`ditto_remove_subscription() failed with error code: ${statusCode}`);
}
}
/** @internal */
function tryAddSyncSubscription(dittoPointer, query, queryArgsCBOR) {
ensureInitialized();
const queryBuffer = bytesFromString(query);
const result = dittoffi_try_add_sync_subscription(dittoPointer, queryBuffer, queryArgsCBOR);
throwOnErrorResult(result.error, 'dittoffi_try_add_sync_subscription');
}
/** @internal */
function tryRemoveSyncSubscription(dittoPointer, query, queryArgsCBOR) {
ensureInitialized();
const queryBuffer = bytesFromString(query);
const result = dittoffi_try_remove_sync_subscription(dittoPointer, queryBuffer, queryArgsCBOR);
throwOnErrorResult(result.error, 'dittoffi_try_remove_sync_subscription');
}
// ----------------------------------------------------------- QueryResult ------
/**
* Doesn't error
*
* @internal
*/
function queryResultFree(queryResultPointer) {
ensureInitialized();
dittoffi_query_result_free(queryResultPointer);
}
/**
* Doesn't error
*
* @internal
*/
function queryResultItemFree(queryResultItemPointer) {
ensureInitialized();
dittoffi_query_result_item_free(queryResultItemPointer);
}
/**
* Can error only on internal bug.
*
* @internal */
function queryResultItems(queryResultPointer) {
ensureInitialized();
const rv = [];
const resultCount = dittoffi_query_result_item_count(queryResultPointer);
for (let i = 0; i < resultCount; i++)
rv.push(dittoffi_query_result_item_at(queryResultPointer, i));
return rv;
}
/**
* Doesn't error
*
* @internal
*/
function queryResultMutatedDocumentIDs(queryResultPointer) {
ensureInitialized();
const rv = [];
const resultCount = dittoffi_query_result_mutated_document_id_count(queryResultPointer);
for (let i = 0; i < resultCount; i++) {
const cborBytes = dittoffi_query_result_mutated_document_id_at(queryResultPointer, i);
rv.push(boxCBytesIntoBuffer(cborBytes));
}
return rv;
}
/**
* The result CBOR contains a map/object with fields and values. No CRDTs are
* present there as they are not needed. By default only values from registers
* are returned and non-register fields are ignored.
*
* Doesn't error
*
* @internal
*/
function queryResultItemCBOR(queryResultItemPointer) {
ensureInitialized();
const cborBytes = dittoffi_query_result_item_cbor(queryResultItemPointer);
return boxCBytesIntoBuffer(cborBytes);
}
/**
* Returns JSON-encoded results given a DQL result item pointer.
*
* Compare for {@link queryResultItemCBOR} above.
*
* Doesn't error
*
* @internal
*/
function queryResultItemJSON(queryResultItemPointer) {
ensureInitialized();
const jsonBytes = dittoffi_query_result_item_json(queryResultItemPointer);
return boxCStringIntoString(jsonBytes);
}
function queryResultItemNew(jsonData) {
ensureInitialized();
const result = dittoffi_query_result_item_new(jsonData);
throwOnErrorResult(result.error, 'dittoffi_query_result_item_new');
return result.success;
}
// ------------------------------------------------------------ LiveQuery ------
/** @internal */
function liveQueryRegister(ditto, collectionName, query, queryArgsCBOR, orderBy, limit, offset, eventHandler,
// Cb may be called in parallel at any point, so let's use
// an optional error handler (which defaults to the ditto logger at 'Error' level).
onError) {
ensureInitialized();
const collectionNameBuffer = bytesFromString(collectionName);
const queryBuffer = bytesFromString(query);
// Note(Daniel): the callback is now registered to be called in a detached
// manner: if the FFI / Rust does `cb()`, then when that call returns, the js
// callback itself may not have completed. This is fine, since `signalNext()`
// shall be the proper way to let the Rust core know the FFI call completed.
const { status_code: errorCode, i64: id } = ditto_live_query_register_str_detached(ditto, collectionNameBuffer, queryBuffer, queryArgsCBOR, orderBy, limit, offset, wrapBackgroundCbForFFI(onError, eventHandler));
if (errorCode !== 0) {
throw new Error(errorMessage() ||
`\`ditto_live_query_register_str()\` failed with error code: ${errorCode}`);
}
return id;
}
/** @internal */
function tryExperimentalRegisterChangeObserver(ditto, query, queryArgsCBOR, changeHandler) {
ensureInitialized();
const errorHandler = (err) => log('Error', `The registered store observer callback failed with ${err}`);
const wrappedCallback = wrapBackgroundCbForFFI(errorHandler, changeHandler);
const queryBuffer = bytesFromString(query);
// Note(Daniel): the callback is registered to be called in a detached manner:
// if the FFI / Rust does `cb()`, then when that call returns, the js callback
// itself may not have completed. This is fine, since `signalNext()` shall be
// the proper way to let the Rust core know the FFI call completed.
const result = dittoffi_try_experimental_register_change_observer_str_detached(ditto, queryBuffer, queryArgsCBOR, wrappedCallback);
throwOnErrorResult(result.error, 'dittoffi_try_experimental_register_change_observer_str_detached');
return result.success;
}
/** @internal */
async function liveQueryStart(ditto, liveQueryID) {
ensureInitialized();
const errorCode = await ditto_live_query_start(ditto, liveQueryID);
if (errorCode !== 0) {
throw new Error(errorMessage() ||
`\`ditto_live_query_start()\` failed with error code: ${errorCode}`);
}
}
/** @internal */
function liveQueryStop(ditto, liveQueryID) {
ensureInitialized();
ditto_live_query_stop(ditto, liveQueryID);
}
/** @internal */
async function liveQuerySignalAvailableNext(ditto, liveQueryID) {
ensureInitialized();
await ditto_live_query_signal_available_next(ditto, liveQueryID);
}
// ------------------------------------------------------ ReadTransaction ------
/** @internal */
async function readTransaction(ditto) {
ensureInitialized();
// REFACTOR: add proper error handling.
const { status_code: errorCode, txn: readTransaction } = await ditto_read_transaction(ditto);
if (errorCode !== 0) {
throw new Error(errorMessage() ||
`\`ditto_read_transaction()\` failed with error code: ${errorCode}`);
}
return readTransaction;
}
/** @internal */
function readTransactionFree(self) {
ensureInitialized();
// REFACTOR: add proper error handling.
return ditto_read_transaction_free(self);
}
// ----------------------------------------------------- WriteTransaction ------
/** @internal */
async function writeTransaction(ditto) {
ensureInitialized();
// REFACTOR: add proper error handling.
const { status_code: errorCode, txn: writeTransaction } = await ditto_write_transaction(ditto, null);
if (errorCode !== 0) {
throw new Error(errorMessage() ||
`ditto_write_transaction() failed with error code: ${errorCode}`);
}
return writeTransaction;
}
/** @internal */
async function writeTransactionCommit(ditto, self) {
ensureInitialized();
const errorCode = await ditto_write_transaction_commit(ditto, self);
if (errorCode !== 0) {
throw new Error(errorMessage() ||
`ditto_write_transaction_commit() failed with error code: ${errorCode}`);
}
}
/** @internal */
async function writeTransactionRollback(ditto, transaction) {
ensureInitialized();
const errorCode = await ditto_write_transaction_rollback(ditto, transaction);
if (errorCode !== 0) {
throw new Error(errorMessage() ||
`ditto_write_transaction_rollback() failed with error code: ${errorCode}`);
}
}
// --------------------------------------------------------------- Logger ------
/** @internal */
function loggerInit() {
ensureInitialized();
ditto_logger_init();
}
/** @internal */
async function loggerSetCustomLogCb(cb) {
ensureInitialize