realm
Version:
Realm by MongoDB is an offline-first mobile database: an alternative to SQLite and key-value stores
208 lines • 9.55 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 internal_1 = require("./internal");
function determineBehavior(config, realmExists) {
const { sync, openSyncedRealmLocally } = config;
if (!sync || openSyncedRealmLocally) {
return { openBehavior: internal_1.OpenRealmBehaviorType.OpenImmediately };
}
else {
const configProperty = realmExists ? "existingRealmFileBehavior" : "newRealmFileBehavior";
const configBehavior = sync[configProperty];
if (configBehavior) {
const { type, timeOut, timeOutBehavior } = configBehavior;
if (typeof timeOut !== "undefined") {
internal_1.assert.number(timeOut, "timeOut");
}
return { openBehavior: type, timeOut, timeOutBehavior };
}
else {
return {
openBehavior: internal_1.OpenRealmBehaviorType.DownloadBeforeOpen,
timeOut: 30 * 1000,
timeOutBehavior: internal_1.OpenRealmTimeOutBehavior.ThrowException,
};
}
}
}
class ProgressRealmPromise {
/** @internal */
static instances = new Set();
/**
* Cancels all unresolved `ProgressRealmPromise` instances.
* @internal
*/
static cancelAll() {
(0, internal_1.assert)(internal_1.flags.ALLOW_CLEAR_TEST_STATE, "Set the flags.ALLOW_CLEAR_TEST_STATE = true before calling this.");
for (const promiseRef of ProgressRealmPromise.instances) {
promiseRef.deref()?.cancel();
}
ProgressRealmPromise.instances.clear();
}
/** @internal */
task = null;
/** @internal */
listeners = new Set();
/** @internal */
handle = new internal_1.PromiseHandle();
/** @internal */
timeoutPromise = null;
/** @internal */
constructor(config) {
if (internal_1.flags.ALLOW_CLEAR_TEST_STATE) {
ProgressRealmPromise.instances.add(new internal_1.binding.WeakRef(this));
}
try {
(0, internal_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 = internal_1.Realm.exists(config);
const { openBehavior, timeOut, timeOutBehavior } = determineBehavior(config, realmExists);
if (openBehavior === internal_1.OpenRealmBehaviorType.OpenImmediately) {
const realm = new internal_1.Realm(config);
this.handle.resolve(realm);
}
else if (openBehavior === internal_1.OpenRealmBehaviorType.DownloadBeforeOpen) {
const { bindingConfig } = internal_1.Realm.transformConfig(config);
// Construct an async open task
this.task = internal_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 internal_1.Realm(config, {
internal: internal_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 === internal_1.SubscriptionSetState.Pending) {
await subscriptions.waitForSynchronization();
}
}
return realm;
})
.then(this.handle.resolve, (err) => {
internal_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);
}
});
// TODO: Consider storing the token returned here to unregister when the task gets cancelled,
// if for some reason, that doesn't happen internally
this.task.registerDownloadProgressNotifier(this.emitProgress);
}
else {
throw new Error(`Unexpected open behavior '${openBehavior}'`);
}
}
catch (err) {
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();
// 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, with two arguments:
* 1. `transferred` The current number of bytes already transferred
* 2. `transferable` The total number of transferable bytes (i.e. the number of bytes already transferred plus the number of bytes pending transfer)
*/
progress(callback) {
this.listeners.add(callback);
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);
emitProgress = (transferredArg, transferableArg) => {
const transferred = internal_1.binding.Int64.intToNum(transferredArg);
const transferable = internal_1.binding.Int64.intToNum(transferableArg);
for (const listener of this.listeners) {
listener(transferred, transferable);
}
};
createTimeoutPromise(config, { timeOut, timeOutBehavior }) {
if (typeof timeOut === "number") {
this.timeoutPromise = new internal_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 === internal_1.OpenRealmTimeOutBehavior.ThrowException) {
// Make failing the timeout, reject the promise
this.timeoutPromise.catch(this.handle.reject);
}
else if (timeOutBehavior === internal_1.OpenRealmTimeOutBehavior.OpenLocalRealm) {
// Make failing the timeout, resolve the promise
this.timeoutPromise.catch((err) => {
if (err instanceof internal_1.TimeoutError) {
this.cancelAndResetTask();
const realm = new internal_1.Realm(config);
this.handle.resolve(realm);
}
else {
this.handle.reject(err);
}
});
}
else {
throw new Error(`Invalid 'timeOutBehavior': '${timeOutBehavior}'. Only 'throwException' and 'openLocalRealm' is allowed.`);
}
}
}
cancelAndResetTask() {
if (this.task) {
this.task.cancel();
this.task.$resetSharedPtr();
this.task = null;
}
}
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