UNPKG

realm

Version:

Realm by MongoDB is an offline-first mobile database: an alternative to SQLite and key-value stores

208 lines 9.55 kB
"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