@react-native-oh-tpl/realm
Version:
Realm by MongoDB is an offline-first mobile database: an alternative to SQLite and key-value stores
243 lines • 10.8 kB
JavaScript
"use strict";
////////////////////////////////////////////////////////////////////////////
//
// Copyright 2022 Realm Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
////////////////////////////////////////////////////////////////////////////
Object.defineProperty(exports, "__esModule", { value: true });
exports.ProgressRealmPromise = void 0;
const binding_1 = require("./binding");
const assert_1 = require("./assert");
const errors_1 = require("./errors");
const flags_1 = require("./flags");
const indirect_1 = require("./indirect");
const Configuration_1 = require("./Configuration");
const SyncConfiguration_1 = require("./app-services/SyncConfiguration");
const BaseSubscriptionSet_1 = require("./app-services/BaseSubscriptionSet");
const SyncSession_1 = require("./app-services/SyncSession");
const PromiseHandle_1 = require("./PromiseHandle");
const TimeoutPromise_1 = require("./TimeoutPromise");
function determineBehavior(config, realmExists) {
const { sync, openSyncedRealmLocally } = config;
if (!sync || openSyncedRealmLocally) {
return { openBehavior: SyncConfiguration_1.OpenRealmBehaviorType.OpenImmediately };
}
else {
const configProperty = realmExists ? "existingRealmFileBehavior" : "newRealmFileBehavior";
const configBehavior = sync[configProperty];
if (configBehavior) {
const { type, timeOut, timeOutBehavior } = configBehavior;
if (typeof timeOut !== "undefined") {
assert_1.assert.number(timeOut, "timeOut");
}
return { openBehavior: type, timeOut, timeOutBehavior };
}
else {
return {
openBehavior: SyncConfiguration_1.OpenRealmBehaviorType.DownloadBeforeOpen,
timeOut: 30 * 1000,
timeOutBehavior: SyncConfiguration_1.OpenRealmTimeOutBehavior.ThrowException,
};
}
}
}
class ProgressRealmPromise {
/** @internal */
static instances = new Set();
/**
* Cancels all unresolved `ProgressRealmPromise` instances.
* @internal
*/
static cancelAll() {
for (const promiseRef of ProgressRealmPromise.instances) {
promiseRef.deref()?.cancel();
}
ProgressRealmPromise.instances.clear();
}
/** @internal */
task = null;
/** @internal */
listeners = new Set();
/** @internal */
handle = new PromiseHandle_1.PromiseHandle();
/** @internal */
timeoutPromise = null;
/**
* Token used for unregistering the progress notifier.
* @internal
*/
notifierToken = null;
/** @internal */
constructor(config) {
if (flags_1.flags.ALLOW_CLEAR_TEST_STATE) {
ProgressRealmPromise.instances.add(new binding_1.binding.WeakRef(this));
}
try {
(0, Configuration_1.validateConfiguration)(config);
// Calling `Realm.exists()` before `binding.Realm.getSynchronizedRealm()` is necessary to capture
// the correct value when this constructor was called since `binding.Realm.getSynchronizedRealm()`
// will open the realm. This is needed when calling the Realm constructor.
const realmExists = indirect_1.indirect.Realm.exists(config);
const { openBehavior, timeOut, timeOutBehavior } = determineBehavior(config, realmExists);
if (openBehavior === SyncConfiguration_1.OpenRealmBehaviorType.OpenImmediately) {
const realm = new indirect_1.indirect.Realm(config);
this.handle.resolve(realm);
}
else if (openBehavior === SyncConfiguration_1.OpenRealmBehaviorType.DownloadBeforeOpen) {
const { bindingConfig } = indirect_1.indirect.Realm.transformConfig(config);
// Construct an async open task
this.task = binding_1.binding.Realm.getSynchronizedRealm(bindingConfig);
// If the promise handle gets rejected, we should cancel the open task
// to avoid consuming a thread safe reference which is no longer registered
this.handle.promise.catch(() => this.task?.cancel());
this.createTimeoutPromise(config, { openBehavior, timeOut, timeOutBehavior });
this.task
.start()
.then(async (tsr) => {
const realm = new indirect_1.indirect.Realm(config, {
internal: binding_1.binding.Helpers.consumeThreadSafeReferenceToSharedRealm(tsr),
// Do not call `Realm.exists()` here in case the realm has been opened by this point in time.
realmExists,
});
if (config.sync?.flexible && !config.openSyncedRealmLocally) {
const { subscriptions } = realm;
if (subscriptions.state === BaseSubscriptionSet_1.SubscriptionSetState.Pending) {
await subscriptions.waitForSynchronization();
}
}
return realm;
})
.then(this.handle.resolve, (err) => {
assert_1.assert.undefined(err.code, "Update this to use the error code instead of matching on message");
if (err instanceof Error && err.message === "Sync session became inactive") {
// This can happen when two async tasks are opened for the same Realm and one gets canceled
this.rejectAsCanceled();
}
else {
this.handle.reject(err);
}
});
this.notifierToken = this.task.registerDownloadProgressNotifier(this.emitProgress.bind(this));
}
else {
throw new Error(`Unexpected open behavior '${openBehavior}'`);
}
}
catch (err) {
if (this.notifierToken !== null) {
this.task?.unregisterDownloadProgressNotifier(this.notifierToken);
this.notifierToken = null;
}
this.handle.reject(err);
}
}
/**
* Cancels the download of the Realm
* If multiple `ProgressRealmPromise` instances are in progress for the same Realm, then canceling one of them
* will cancel all of them.
*/
cancel() {
this.cancelAndResetTask();
this.timeoutPromise?.cancel();
if (this.notifierToken !== null) {
this.task?.unregisterDownloadProgressNotifier(this.notifierToken);
this.notifierToken = null;
}
// Clearing all listeners to avoid accidental progress notifications
this.listeners.clear();
// Tell anything awaiting the promise
this.rejectAsCanceled();
}
/**
* Register to receive progress notifications while the download is in progress.
* @param callback Called multiple times as the client receives data.
*/
progress(callback) {
this.listeners.add(callback);
// TODO: Is the manual triggering necessary? It was meant to mimic the
// same behavior experienced prior to having the estimate notifier.
if ((0, SyncSession_1.isEstimateProgressNotificationCallback)(callback)) {
callback(1.0);
}
else {
callback(0.0, 0.0);
}
return this;
}
then = this.handle.promise.then.bind(this.handle.promise);
catch = this.handle.promise.catch.bind(this.handle.promise);
finally = this.handle.promise.finally.bind(this.handle.promise);
/** @internal */
emitProgress(transferredArg, transferableArg, progressEstimate) {
const transferred = binding_1.binding.Int64.intToNum(transferredArg);
const transferable = binding_1.binding.Int64.intToNum(transferableArg);
for (const listener of this.listeners) {
if ((0, SyncSession_1.isEstimateProgressNotificationCallback)(listener)) {
listener(progressEstimate);
}
else {
listener(transferred, transferable);
}
}
}
/** @internal */
createTimeoutPromise(config, { timeOut, timeOutBehavior }) {
if (typeof timeOut === "number") {
this.timeoutPromise = new TimeoutPromise_1.TimeoutPromise(this.handle.promise, // Ensures the timeout gets cancelled when the realm opens
{
ms: timeOut,
message: `Realm could not be downloaded in the allocated time: ${timeOut} ms.`,
});
if (timeOutBehavior === SyncConfiguration_1.OpenRealmTimeOutBehavior.ThrowException) {
// Make failing the timeout, reject the promise
this.timeoutPromise.catch(this.handle.reject);
}
else if (timeOutBehavior === SyncConfiguration_1.OpenRealmTimeOutBehavior.OpenLocalRealm) {
// Make failing the timeout, resolve the promise
this.timeoutPromise.catch((err) => {
if (err instanceof errors_1.TimeoutError) {
this.cancelAndResetTask();
const realm = new indirect_1.indirect.Realm(config);
this.handle.resolve(realm);
}
else {
this.handle.reject(err);
}
});
}
else {
throw new Error(`Invalid 'timeOutBehavior': '${timeOutBehavior}'. Only 'throwException' and 'openLocalRealm' is allowed.`);
}
}
}
/** @internal */
cancelAndResetTask() {
if (this.task) {
this.task.cancel();
this.task.$resetSharedPtr();
this.task = null;
}
}
/** @internal */
rejectAsCanceled() {
const err = new Error("Async open canceled");
this.handle.reject(err);
}
get [Symbol.toStringTag]() {
return ProgressRealmPromise.name;
}
}
exports.ProgressRealmPromise = ProgressRealmPromise;
//# sourceMappingURL=ProgressRealmPromise.js.map