@react-native-ohos/realm
Version:
Realm by MongoDB is an offline-first mobile database: an alternative to SQLite and key-value stores
1,024 lines • 45.6 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.
//
////////////////////////////////////////////////////////////////////////////
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
__setModuleDefault(result, mod);
return result;
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.Realm = void 0;
const binding_1 = require("./binding");
const assert_1 = require("./assert");
const errors_1 = require("./errors");
const debug_1 = require("./debug");
const flags_1 = require("./flags");
const indirect_1 = require("./indirect");
const platform_1 = require("./platform");
const Object_1 = require("./Object");
const Results_1 = require("./Results");
const schema_1 = require("./schema");
const ClassMap_1 = require("./ClassMap");
const Configuration_1 = require("./Configuration");
const SyncConfiguration_1 = require("./app-services/SyncConfiguration");
const Logger_1 = require("./Logger");
const List_1 = require("./List");
const ProgressRealmPromise_1 = require("./ProgressRealmPromise");
const Object_2 = require("./Object");
const RealmListeners_1 = require("./RealmListeners");
const SubscriptionSet_1 = require("./app-services/SubscriptionSet");
const SyncSession_1 = require("./app-services/SyncSession");
const array_buffer_1 = require("./type-helpers/array-buffer");
const symbols_1 = require("./symbols");
const Results_2 = require("./collection-accessors/Results");
const debug = (0, debug_1.extendDebug)("Realm");
/**
* Asserts the event passed as string is a valid RealmEvent value.
* @throws A {@link TypeAssertionError} if an unexpected name is passed via {@link name}.
* @param name - The name of the event.
* @internal
*/
function assertRealmEvent(name) {
const values = Object.values(RealmListeners_1.RealmEvent);
if (!values.includes(name)) {
throw new errors_1.TypeAssertionError("One of " + values.join(", "), name);
}
}
/**
* The Realm database.
*/
class Realm {
static defaultPath = Realm.normalizePath("default.realm");
static internals = new Set();
/**
* Sets the log level.
* @param level - The log level to be used by the logger. The default value is `info`.
* @param category - The category to set the log level for. If omitted, the log level is set for all categories (`"Realm"`).
* @note The log level can be changed during the lifetime of the application.
* @since 12.0.0
* @example
* Realm.setLogLevel("all");
*/
static setLogLevel(level, category = "Realm") {
(0, assert_1.assert)(Logger_1.LOG_CATEGORIES.includes(category), `Unexpected log category: '${category}'`);
const categoryRef = binding_1.binding.LogCategoryRef.getCategory(category);
categoryRef.setDefaultLevelThreshold((0, Logger_1.toBindingLoggerLevel)(level));
}
static setLogger(loggerCallback) {
assert_1.assert.function(loggerCallback);
binding_1.binding.Logger.setDefaultLogger((0, Logger_1.toBindingLogger)(loggerCallback));
}
/**
* Closes all Realms, cancels all pending {@link Realm.open} calls, clears internal caches, resets the logger and collects garbage.
* Call this method to free up the event loop and allow Node.js to perform a graceful exit.
*/
static shutdown() {
// Close any realms not already closed
for (const realmRef of Realm.internals) {
const realm = realmRef.deref();
if (realm && !realm.isClosed) {
realm.close();
}
}
Realm.internals.clear();
binding_1.binding.RealmCoordinator.clearAllCaches();
binding_1.binding.App.clearCachedApps();
ProgressRealmPromise_1.ProgressRealmPromise.cancelAll();
binding_1.binding.Logger.setDefaultLogger(null);
platform_1.garbageCollection.collect();
}
/**
* Clears the state by closing and deleting any Realm in the default directory and logout all users.
* NOTE: Not a part of the public API and it's primarily used from the library's tests.
* @private
*/
static clearTestState() {
(0, assert_1.assert)(flags_1.flags.ALLOW_CLEAR_TEST_STATE, "Set the flags.ALLOW_CLEAR_TEST_STATE = true before calling this.");
Realm.shutdown();
// Delete all Realm files in the default directory
const defaultDirectoryPath = platform_1.fs.getDefaultDirectoryPath();
platform_1.fs.removeRealmFilesFromDirectory(defaultDirectoryPath);
}
/**
* Delete the Realm file for the given configuration.
* @param config - The configuration for the Realm being deleted.
* @throws An {@link Error} if anything in the provided {@link config} is invalid.
*/
static deleteFile(config) {
(0, Configuration_1.validateConfiguration)(config);
const path = Realm.determinePath(config);
platform_1.fs.removeFile(path);
platform_1.fs.removeFile(path + ".lock");
platform_1.fs.removeFile(path + ".fresh.lock");
platform_1.fs.removeFile(path + ".note");
platform_1.fs.removeDirectory(path + ".management");
}
static exists(arg = {}) {
const config = typeof arg === "string" ? { path: arg } : arg;
(0, Configuration_1.validateConfiguration)(config);
const path = Realm.determinePath(config);
return platform_1.fs.exists(path);
}
static open(arg = {}) {
const config = typeof arg === "string" ? { path: arg } : arg;
return new ProgressRealmPromise_1.ProgressRealmPromise(config);
}
/**
* Get the current schema version of the Realm at the given path.
* @param path - The path to the file where the Realm database is stored.
* @param encryptionKey - Required only when accessing encrypted Realms.
* @throws An {@link Error} if passing an invalid or non-matching encryption key.
* @returns Version of the schema as an integer, or `-1` if no Realm exists at {@link path}.
* @since 0.11.0
*/
static schemaVersion(path, encryptionKey) {
const notFound = "18446744073709551615"; // std::numeric_limit<uint64_t>::max() = 0xffffffffffffffff as string
const config = { path };
const absolutePath = Realm.determinePath(config);
const schemaVersion = binding_1.binding.Realm.getSchemaVersion({
path: absolutePath,
encryptionKey: Realm.determineEncryptionKey(encryptionKey),
});
// no easy way to compare uint64_t in TypeScript
return notFound === schemaVersion.toString() ? -1 : binding_1.binding.Int64.intToNum(schemaVersion);
}
/**
* Creates a template object for a Realm model class where all optional fields are undefined
* and all required fields have the default value for the given data type, either the value
* set by the default property in the schema or the default value for the datatype if the schema
* doesn't specify one, i.e. 0, false and "".
* @param objectSchema - Schema describing the object that should be created.
*/
static createTemplateObject(objectSchema) {
(0, schema_1.validateObjectSchema)(objectSchema);
const normalizedSchema = (0, schema_1.normalizeObjectSchema)(objectSchema);
const result = {};
for (const [key, property] of Object.entries(normalizedSchema.properties)) {
// If a default value is explicitly set, always set the property
if (typeof property.default !== "undefined") {
result[key] = property.default;
continue;
}
// if optional is set, it wil take precedence over any `?` set on the type parameter
if (property.optional) {
continue;
}
// Set the default value for all required primitive types.
// Lists are always treated as empty if not specified and references to objects are always optional
switch (property.type) {
case "bool":
result[key] = false;
break;
case "int":
result[key] = 0;
break;
case "float":
result[key] = 0.0;
break;
case "double":
result[key] = 0.0;
break;
case "string":
result[key] = "";
break;
case "data":
result[key] = new ArrayBuffer(0);
break;
case "date":
result[key] = new Date(0);
break;
}
}
return result;
}
/**
* Copy any Realm files (i.e. `*.realm`) bundled with the application from the application
* directory into the application's documents directory, so that they can be opened and used
* by Realm. If the file already exists in the documents directory, it will not be
* overwritten, so this can safely be called multiple times.
*
* This should be called before opening the Realm, in order to move the bundled Realm
* files into a place where they can be written to.
* @example
* ```
* // Given a bundled file, example.realm, this will copy example.realm (and any other .realm files)
* // from the app bundle into the app's documents directory. If the file already exists, it will
* // not be overwritten, so it is safe to call this every time the app starts.
* Realm.copyBundledRealmFiles();
*
* const realm = await Realm.open({
* // This will open example.realm from the documents directory, with the bundled data in.
* path: "example.realm"
* });
* ```
*
* This is only implemented for React Native.
* @throws an {@link Error} If an I/O error occurred or method is not implemented.
*/
static copyBundledRealmFiles() {
platform_1.fs.copyBundledRealmFiles();
}
/**
* TODO: Consider breaking this by ensuring a ".realm" suffix (coordinating with other SDK teams in the process)
*/
static normalizePath(path) {
if (typeof path === "undefined") {
return Realm.defaultPath;
}
else if (path.length === 0) {
throw new Error("Unexpected empty path");
}
else if (platform_1.fs.isAbsolutePath(path)) {
return path;
}
else {
return platform_1.fs.joinPaths(platform_1.fs.getDefaultDirectoryPath(), path);
}
}
/**
* @note When the path is relative and the config contains a sync object, Core will replace any existing file extension
* or add the ".realm" suffix.
*/
static determinePath(config) {
if (config.sync && !config.openSyncedRealmLocally) {
if (config.path && platform_1.fs.isAbsolutePath(config.path)) {
return Realm.normalizePath(config.path);
}
else {
const bindingSyncConfig = (0, SyncConfiguration_1.toBindingSyncConfig)(config.sync);
return config.sync.user.internal.pathForRealm(bindingSyncConfig, config.path);
}
}
else {
return Realm.normalizePath(config.path);
}
}
static determineEncryptionKey(encryptionKey) {
if (typeof encryptionKey === "undefined") {
return encryptionKey;
}
else {
return (0, array_buffer_1.toArrayBuffer)(encryptionKey, false);
}
}
static extractRealmSchemaExtras(schemas) {
const extras = {};
for (const schema of schemas) {
extras[schema.name] = this.extractObjectSchemaExtras(schema);
}
return extras;
}
/** @internal */
static extractObjectSchemaExtras(schema) {
const defaults = {};
const presentations = {};
for (const [name, propertySchema] of Object.entries(schema.properties)) {
defaults[name] = propertySchema.default;
presentations[name] = propertySchema.presentation;
}
return { constructor: schema.ctor, defaults, presentations };
}
/** @internal */
static transformConfig(config) {
const normalizedSchema = config.schema && (0, schema_1.normalizeRealmSchema)(config.schema);
const schemaExtras = Realm.extractRealmSchemaExtras(normalizedSchema || []);
const path = Realm.determinePath(config);
const { fifoFilesFallbackPath, shouldCompact, inMemory } = config;
const bindingSchema = normalizedSchema && (0, schema_1.toBindingSchema)(normalizedSchema);
return {
schemaExtras,
bindingConfig: {
path,
cache: true,
fifoFilesFallbackPath,
schema: bindingSchema,
inMemory: inMemory === true,
schemaMode: Realm.determineSchemaMode(config),
schemaVersion: config.schema
? binding_1.binding.Int64.numToInt(typeof config.schemaVersion === "number" ? config.schemaVersion : 0)
: undefined,
migrationFunction: config.onMigration ? Realm.wrapMigration(schemaExtras, config.onMigration) : undefined,
shouldCompactOnLaunchFunction: shouldCompact
? (totalBytes, usedBytes) => {
return shouldCompact(Number(totalBytes), Number(usedBytes));
}
: undefined,
disableFormatUpgrade: config.disableFormatUpgrade,
encryptionKey: Realm.determineEncryptionKey(config.encryptionKey),
syncConfig: config.sync ? (0, SyncConfiguration_1.toBindingSyncConfig)(config.sync) : undefined,
forceSyncHistory: config.openSyncedRealmLocally,
automaticallyHandleBacklinksInMigrations: config.migrationOptions?.resolveEmbeddedConstraints ?? false,
},
};
}
static determineSchemaMode(config) {
const { readOnly, deleteRealmIfMigrationNeeded, onMigration, sync } = config;
(0, assert_1.assert)(!readOnly || !deleteRealmIfMigrationNeeded, "Cannot set 'deleteRealmIfMigrationNeeded' when 'readOnly' is set.");
(0, assert_1.assert)(!onMigration || !deleteRealmIfMigrationNeeded, "Cannot set 'deleteRealmIfMigrationNeeded' when 'onMigration' is set.");
if (readOnly) {
return 1 /* binding.SchemaMode.Immutable */;
}
else if (deleteRealmIfMigrationNeeded) {
return 3 /* binding.SchemaMode.SoftResetFile */;
}
else if (sync) {
return 6 /* binding.SchemaMode.AdditiveExplicit */;
}
else {
return undefined;
}
}
static wrapMigration(schemaExtras, onMigration) {
return (oldRealmInternal, newRealmInternal) => {
try {
const oldRealm = new Realm(null, { internal: oldRealmInternal, schemaExtras });
const newRealm = new Realm(null, { internal: newRealmInternal, schemaExtras });
onMigration(oldRealm, newRealm);
}
finally {
oldRealmInternal.close();
oldRealmInternal.$resetSharedPtr();
newRealmInternal.$resetSharedPtr();
}
};
}
/**
* The Realms's representation in the binding.
* @internal
*/
internal;
/**
* The sync session if this is a synced Realm
*/
syncSession;
schemaExtras = {};
classes;
changeListeners = new RealmListeners_1.RealmListeners(this, RealmListeners_1.RealmEvent.Change);
beforeNotifyListeners = new RealmListeners_1.RealmListeners(this, RealmListeners_1.RealmEvent.BeforeNotify);
schemaListeners = new RealmListeners_1.RealmListeners(this, RealmListeners_1.RealmEvent.Schema);
/** @internal */
currentUpdateMode;
constructor(arg, internalConfig = {}) {
const config = typeof arg === "string" ? { path: arg } : arg || {};
// Calling `Realm.exists()` before `binding.Realm.getSharedRealm()` is necessary to capture
// the correct value when this constructor was called since `binding.Realm.getSharedRealm()`
// will open the realm. This is needed when deciding whether to update initial subscriptions.
const realmExists = internalConfig.realmExists ?? Realm.exists(config);
if (arg !== null) {
(0, assert_1.assert)(!internalConfig.schemaExtras, "Expected either a configuration or schemaExtras");
(0, Configuration_1.validateConfiguration)(config);
const { bindingConfig, schemaExtras } = Realm.transformConfig(config);
debug("open", bindingConfig);
this.schemaExtras = schemaExtras;
platform_1.fs.ensureDirectoryForFile(bindingConfig.path);
this.internal = internalConfig.internal ?? binding_1.binding.Realm.getSharedRealm(bindingConfig);
if (flags_1.flags.ALLOW_CLEAR_TEST_STATE) {
Realm.internals.add(new binding_1.binding.WeakRef(this.internal));
}
binding_1.binding.Helpers.setBindingContext(this.internal, {
didChange: (r) => {
r.verifyOpen();
this.changeListeners.notify();
},
schemaDidChange: (r) => {
r.verifyOpen();
this.classes = new ClassMap_1.ClassMap(this, this.internal.schema, this.schema);
this.schemaListeners.notify(this.schema);
},
beforeNotify: (r) => {
r.verifyOpen();
this.beforeNotifyListeners.notify();
},
});
}
else {
const { internal, schemaExtras } = internalConfig;
assert_1.assert.instanceOf(internal, binding_1.binding.Realm, "internal");
this.internal = internal;
this.schemaExtras = schemaExtras || {};
}
Object.defineProperty(this, "classes", {
enumerable: false,
configurable: false,
writable: true,
});
Object.defineProperty(this, "internal", {
enumerable: false,
configurable: false,
writable: false,
});
this.classes = new ClassMap_1.ClassMap(this, this.internal.schema, this.schema);
const syncSession = this.internal.syncSession;
this.syncSession = syncSession ? new SyncSession_1.SyncSession(syncSession) : null;
const initialSubscriptions = config.sync?.initialSubscriptions;
if (initialSubscriptions && !config.openSyncedRealmLocally) {
// Do not call `Realm.exists()` here in case the realm has been opened by this point in time.
this.handleInitialSubscriptions(initialSubscriptions, realmExists);
}
}
/**
* Indicates if this Realm contains any objects.
* @returns `true` if empty, `false` otherwise.
* @readonly
* @since 1.10.0
*/
get isEmpty() {
return this.internal.isEmpty;
}
/**
* The path to the file where this Realm is stored.
* @returns A string containing the path to the file where this Realm is stored.
* @readonly
* @since 0.12.0
*/
get path() {
return this.internal.config.path;
}
/**
* Indicates if this Realm was opened as read-only.
* @returns `true` if this Realm is read-only, `false` otherwise.
* @readonly
* @since 0.12.0
*/
get isReadOnly() {
return this.internal.config.schemaMode === 1 /* binding.SchemaMode.Immutable */;
}
/**
* Indicates if this Realm was opened in-memory.
* @returns `true` if this Realm is in-memory, `false` otherwise.
* @readonly
*/
get isInMemory() {
return this.internal.config.inMemory;
}
/**
* A normalized representation of the schema provided in the {@link Configuration} when this Realm was constructed.
* @returns An array of {@link CanonicalObjectSchema} describing all objects in this Realm.
* @readonly
* @since 0.12.0
*/
get schema() {
const schemas = (0, schema_1.fromBindingRealmSchema)(this.internal.schema);
// Stitch in the constructors and defaults stored in this.schemaExtras
for (const objectSchema of schemas) {
const extras = this.schemaExtras[objectSchema.name];
if (extras) {
objectSchema.ctor = extras.constructor;
}
for (const property of Object.values(objectSchema.properties)) {
property.default = extras ? extras.defaults[property.name] : undefined;
property.presentation = extras ? extras.presentations[property.name] : undefined;
}
}
return schemas;
}
/**
* The current schema version of the Realm.
* @returns The schema version of this Realm, as a `number`.
* @readonly
* @since 0.12.0
*/
get schemaVersion() {
return Number(this.internal.schemaVersion);
}
/**
* Indicates if this Realm is in a write transaction.
* @returns `true` if in a write transaction, `false` otherwise.
* @readonly
* @since 1.10.3
*/
get isInTransaction() {
// TODO: Consider keeping a local state in JS for this
return this.internal.isInTransaction;
}
/**
* Indicates if this Realm is in migration.
* @returns `true` if in migration, `false` otherwise
* @readonly
* @since 12.3.0
*/
get isInMigration() {
// TODO: Consider keeping a local state in JS for this
return this.internal.isInMigration;
}
/**
* Indicates if this Realm has been closed.
* @returns `true` if closed, `false` otherwise.
* @readonly
* @since 2.1.0
*/
get isClosed() {
// TODO: Consider keeping a local state in JS for this
return this.internal.isClosed;
}
/**
* The latest set of flexible sync subscriptions.
* @returns A {@link SubscriptionSet} object.
* @throws An {@link Error} if flexible sync is not enabled for this app.
*/
get subscriptions() {
const { syncConfig } = this.internal.config;
(0, assert_1.assert)(syncConfig, "`subscriptions` can only be accessed if flexible sync is enabled, but sync is " +
"currently disabled for your app. Add a flexible sync config when opening the " +
"Realm, for example: { sync: { user, flexible: true } }.");
(0, assert_1.assert)(syncConfig.flxSyncRequested, "`subscriptions` can only be accessed if flexible sync is enabled, but partition " +
"based sync is currently enabled for your Realm. Modify your sync config to remove any `partitionValue` " +
"and enable flexible sync, for example: { sync: { user, flexible: true } }");
return new SubscriptionSet_1.SubscriptionSet(this, this.internal.latestSubscriptionSet);
}
/**
* Closes this Realm so it may be re-opened with a newer schema version.
* All objects and collections from this Realm are no longer valid after calling this method.
* The method is idempotent.
*/
close() {
this.internal.close();
}
create(type, values, mode = Object_2.UpdateMode.Never) {
// Supporting a boolean overload for mode
if (mode === true) {
mode = Object_2.UpdateMode.All;
}
else if (mode === false) {
mode = Object_2.UpdateMode.Never;
}
// Implements https://github.com/realm/realm-js/blob/v11/src/js_realm.hpp#L1260-L1321
if (values instanceof Object_1.RealmObject && !values[symbols_1.OBJECT_INTERNAL]) {
throw new Error("Cannot create an object from a detached RealmObject instance");
}
if (!Object.values(Object_2.UpdateMode).includes(mode)) {
throw new Error(`Unsupported 'updateMode'. Only '${Object_2.UpdateMode.Never}', '${Object_2.UpdateMode.Modified}' or '${Object_2.UpdateMode.All}' is supported.`);
}
this.internal.verifyOpen();
const helpers = this.classes.getHelpers(type);
this.currentUpdateMode = mode;
let realmObject;
try {
realmObject = Object_1.RealmObject.create(this, values, mode, { helpers });
}
finally {
this.currentUpdateMode = undefined;
}
return isAsymmetric(helpers.objectSchema) ? undefined : realmObject;
}
//FIXME: any should not be used, but we are staying compatible with previous versions
/**
* Deletes the provided Realm object, or each one inside the provided collection.
* @param subject - The Realm object to delete, or a collection containing multiple Realm objects to delete.
*/
// eslint-disable-next-line @typescript-eslint/no-explicit-any
delete(subject) {
assert_1.assert.inTransaction(this, "Can only delete objects within a transaction.");
assert_1.assert.object(subject, "subject");
if (subject instanceof Object_1.RealmObject) {
assert_1.assert.isSameRealm(subject[symbols_1.OBJECT_REALM].internal, this.internal, "Can't delete an object from another Realm");
const { objectSchema } = this.classes.getHelpers(subject);
const obj = subject[symbols_1.OBJECT_INTERNAL];
assert_1.assert.isValid(obj, "Object is invalid. Either it has been previously deleted or the Realm it belongs to has been closed.");
const table = binding_1.binding.Helpers.getTable(this.internal, objectSchema.tableKey);
table.removeObject(obj.key);
}
else if (subject instanceof List_1.List) {
subject.internal.deleteAll();
}
else if (subject instanceof Results_1.Results) {
subject.internal.clear();
}
else if (Array.isArray(subject) || Symbol.iterator in subject) {
//@ts-expect-error the above check is good enough
for (const object of subject) {
assert_1.assert.instanceOf(object, Object_1.RealmObject);
assert_1.assert.isSameRealm(object[symbols_1.OBJECT_REALM].internal, this.internal, "Can't delete an object from another Realm");
const { objectSchema } = this.classes.getHelpers(object);
const table = binding_1.binding.Helpers.getTable(this.internal, objectSchema.tableKey);
table.removeObject(object[symbols_1.OBJECT_INTERNAL].key);
}
}
else {
throw new Error("Can only delete objects, lists and results.");
}
}
/**
* Deletes a Realm model, including all of its objects.
* If called outside a migration function, {@link schema} and {@link schemaVersion} are updated.
* @param name - The model name.
*/
deleteModel(name) {
assert_1.assert.inTransaction(this, "Can only delete objects within a transaction.");
binding_1.binding.Helpers.deleteDataForObject(this.internal, name);
if (!this.internal.isInMigration) {
const newSchema = this.internal.schema.filter((objectSchema) => objectSchema.name !== name);
this.internal.updateSchema(newSchema, binding_1.binding.Int64.add(this.internal.schemaVersion, binding_1.binding.Int64.numToInt(1)), null, null, true);
}
}
/**
* **WARNING:** This will delete **all** objects in the Realm!
*/
deleteAll() {
assert_1.assert.inTransaction(this, "Can only delete objects within a transaction.");
for (const objectSchema of this.internal.schema) {
const table = binding_1.binding.Helpers.getTable(this.internal, objectSchema.tableKey);
table.clear();
}
}
objectForPrimaryKey(type, primaryKey) {
// Implements https://github.com/realm/realm-js/blob/v11/src/js_realm.hpp#L1240-L1258
const { objectSchema, properties, wrapObject } = this.classes.getHelpers(type);
if (!objectSchema.primaryKey) {
throw new Error(`Expected a primary key on '${objectSchema.name}'`);
}
if (isAsymmetric(objectSchema)) {
throw new Error("You cannot query an asymmetric object.");
}
const table = binding_1.binding.Helpers.getTable(this.internal, objectSchema.tableKey);
const value = properties.get(objectSchema.primaryKey).toBinding(primaryKey);
try {
const objKey = table.findPrimaryKey(value);
if (binding_1.binding.isEmptyObjKey(objKey)) {
return null;
}
else {
const obj = table.getObject(objKey);
return wrapObject(obj);
}
}
catch (err) {
// TODO: Match on something else than the error message, when exposed by the binding
if (err instanceof Error && err.message.startsWith("No object with key")) {
throw new Error(`No '${objectSchema.name}' with key '${primaryKey}'`);
}
else {
throw err;
}
}
}
_objectForObjectKey(type, objectKey) {
const { objectSchema, wrapObject } = this.classes.getHelpers(type);
if (isEmbedded(objectSchema)) {
throw new Error("You cannot query an embedded object.");
}
else if (isAsymmetric(objectSchema)) {
throw new Error("You cannot query an asymmetric object.");
}
assert_1.assert.numericString(objectKey);
const table = binding_1.binding.Helpers.getTable(this.internal, objectSchema.tableKey);
try {
const objKey = binding_1.binding.stringToObjKey(objectKey);
const obj = table.tryGetObject(objKey);
const result = obj && wrapObject(obj);
return result === null ? undefined : result;
}
catch (err) {
if (err instanceof binding_1.binding.InvalidObjKey) {
return undefined;
}
else {
throw err;
}
}
}
objects(type) {
const { internal, classes } = this;
const { objectSchema, wrapObject } = classes.getHelpers(type);
if (isEmbedded(objectSchema)) {
throw new Error("You cannot query an embedded object.");
}
else if (isAsymmetric(objectSchema)) {
throw new Error("You cannot query an asymmetric object.");
}
const table = binding_1.binding.Helpers.getTable(internal, objectSchema.tableKey);
const results = binding_1.binding.Results.fromTable(internal, table);
const typeHelpers = {
fromBinding(value) {
return wrapObject(value);
},
toBinding(value) {
assert_1.assert.instanceOf(value, Object_1.RealmObject);
return value[symbols_1.OBJECT_INTERNAL];
},
};
const accessor = (0, Results_2.createResultsAccessor)({ realm: this, typeHelpers, itemType: 7 /* binding.PropertyType.Object */ });
return new Results_1.Results(this, results, accessor, typeHelpers);
}
/**
* Add a listener {@link callback} for the specified {@link eventName}.
* @param eventName - The name of event that should cause the callback to be called.
* @param callback - Function to be called when a change event occurs.
* Each callback will only be called once per event, regardless of the number of times
* it was added.
* @throws An {@link Error} if an invalid event {@link eventName} is supplied, if Realm is closed or if {@link callback} is not a function.
*/
addListener(eventName, callback) {
assert_1.assert.open(this);
assert_1.assert.function(callback);
if (eventName === "change") {
this.changeListeners.add(callback);
}
else if (eventName === "schema") {
this.schemaListeners.add(callback);
}
else if (eventName === "beforenotify") {
this.beforeNotifyListeners.add(callback);
}
else {
throw new Error(`Unknown event name '${eventName}': only 'change', 'schema' and 'beforenotify' are supported.`);
}
}
/**
* Remove the listener {@link callback} for the specified event {@link eventName}.
* @param eventName - The event name.
* @param callback - Function that was previously added as a listener for this event through the {@link addListener} method.
* @throws an {@link Error} If an invalid event {@link eventName} is supplied, if Realm is closed or if {@link callback} is not a function.
*/
removeListener(eventName, callback) {
assert_1.assert.open(this);
assert_1.assert.function(callback);
assertRealmEvent(eventName);
if (eventName === RealmListeners_1.RealmEvent.Change) {
this.changeListeners.remove(callback);
}
else if (eventName === RealmListeners_1.RealmEvent.Schema) {
this.schemaListeners.remove(callback);
}
else if (eventName === RealmListeners_1.RealmEvent.BeforeNotify) {
this.beforeNotifyListeners.remove(callback);
}
else {
assert_1.assert.never(eventName, "eventName");
}
}
/**
* Remove all event listeners (restricted to the event {@link eventName}, if provided).
* @param eventName - The name of the event whose listeners should be removed.
* @throws An {@link Error} when invalid event {@link eventName} is supplied.
*/
removeAllListeners(eventName) {
assert_1.assert.open(this);
if (typeof eventName === "undefined") {
this.changeListeners.removeAll();
this.schemaListeners.removeAll();
this.beforeNotifyListeners.removeAll();
}
else {
assert_1.assert.string(eventName, "eventName");
assertRealmEvent(eventName);
if (eventName === RealmListeners_1.RealmEvent.Change) {
this.changeListeners.removeAll();
}
else if (eventName === RealmListeners_1.RealmEvent.Schema) {
this.schemaListeners.removeAll();
}
else if (eventName === RealmListeners_1.RealmEvent.BeforeNotify) {
this.beforeNotifyListeners.removeAll();
}
else {
assert_1.assert.never(eventName, "eventName");
}
}
}
/**
* Synchronously call the provided {@link callback} inside a write transaction. If an exception happens inside a transaction,
* you’ll lose the changes in that transaction, but the Realm itself won’t be affected (or corrupted).
* More precisely, {@link beginTransaction} and {@link commitTransaction} will be called
* automatically. If any exception is thrown during the transaction {@link cancelTransaction} will
* be called instead of {@link commitTransaction} and the exception will be re-thrown to the caller of {@link write}.
*
* Nested transactions (calling {@link write} within {@link write}) is not possible.
* @param callback - Function to be called inside a write transaction.
* @returns Returned value from the callback.
*/
write(callback) {
let result = undefined;
this.internal.beginTransaction();
try {
result = callback();
}
catch (err) {
this.internal.cancelTransaction();
throw err;
}
this.internal.commitTransaction();
return result;
}
/**
* Initiate a write transaction.
*
* When doing a transaction, it is highly recommended to do error handling.
* If you don't handle errors, your data might become inconsistent. Error handling
* will often involve canceling the transaction.
* @throws An {@link Error} if already in write transaction
* @see {@link cancelTransaction}
* @see {@link commitTransaction}
* @example
* realm.beginTransaction();
* try {
* realm.create('Person', { name: 'Arthur Dent', origin: 'Earth' });
* realm.create('Person', { name: 'Ford Prefect', origin: 'Betelgeuse Five' });
* realm.commitTransaction();
* } catch (e) {
* realm.cancelTransaction();
* throw e;
* }
*/
beginTransaction() {
this.internal.beginTransaction();
}
/**
* Commit a write transaction.
* @see {@link beginTransaction}
*/
commitTransaction() {
this.internal.commitTransaction();
}
/**
* Cancel a write transaction.
* @see {@link beginTransaction}
*/
cancelTransaction() {
this.internal.cancelTransaction();
}
/**
* Replaces all string columns in this Realm with a string enumeration column and compacts the
* database file.
*
* Cannot be called from a write transaction.
*
* Compaction will not occur if other {@link Realm} instances exist.
*
* While compaction is in progress, attempts by other threads or processes to open the database will
* wait.
*
* Be warned that resource requirements for compaction is proportional to the amount of live data in
* the database. Compaction works by writing the database contents to a temporary database file and
* then replacing the database with the temporary one.
* @returns `true` if compaction succeeds, `false` if not.
*/
compact() {
assert_1.assert.outTransaction(this, "Cannot compact a Realm within a transaction.");
return this.internal.compact();
}
/**
* Writes a compacted copy of the Realm with the given configuration.
*
* The destination file cannot already exist.
* All conversions between synced and non-synced Realms are supported, and will be
* performed according to the {@link config} parameter, which describes the desired output.
*
* Note that if this method is called from within a write transaction, the current data is written,
* not the data from the point when the previous write transaction was committed.
* @param config - Realm configuration that describes the output realm.
*/
writeCopyTo(config) {
assert_1.assert.outTransaction(this, "Can only convert Realms outside a transaction.");
(0, Configuration_1.validateConfiguration)(config);
const { bindingConfig } = Realm.transformConfig(config);
this.internal.convert(bindingConfig);
}
/**
* Update the schema of the Realm.
* @param schema The schema which the Realm should be updated to use.
* @internal
*/
_updateSchema(schema) {
(0, schema_1.validateRealmSchema)(schema);
const normalizedSchema = (0, schema_1.normalizeRealmSchema)(schema);
const bindingSchema = (0, schema_1.toBindingSchema)(normalizedSchema);
if (!this.isInTransaction) {
throw new Error("Can only create object schema within a transaction.");
}
this.internal.updateSchema(bindingSchema, binding_1.binding.Int64.add(this.internal.schemaVersion, binding_1.binding.Int64.numToInt(1)), null, null, true);
// Note: The schema change listener is fired immediately after the call to
// `this.internal.updateSchema()` (thus before `_updateSchema()` has
// returned). Therefore, `this.classes` is updated in the `schemaDidChange`
// callback and not here.
}
/** @internal */
getClassHelpers(arg) {
return this.classes.getHelpers(arg);
}
/**
* Update subscriptions with the initial subscriptions if needed.
* @param initialSubscriptions The initial subscriptions.
* @param realmExists Whether the realm already exists.
*/
handleInitialSubscriptions(initialSubscriptions, realmExists) {
const shouldUpdateSubscriptions = initialSubscriptions.rerunOnOpen || !realmExists;
if (shouldUpdateSubscriptions) {
debug("handling initial subscriptions, %O", { rerunOnOpen: initialSubscriptions.rerunOnOpen, realmExists });
this.subscriptions.updateNoWait(initialSubscriptions.update);
}
}
}
exports.Realm = Realm;
(0, indirect_1.injectIndirect)("Realm", Realm);
/**
* @param objectSchema - The schema of the object.
* @returns `true` if the object is marked for asymmetric sync, otherwise `false`.
*/
function isAsymmetric(objectSchema) {
return objectSchema.tableType === 2 /* binding.TableType.TopLevelAsymmetric */;
}
/**
* @param objectSchema - The schema of the object.
* @returns `true` if the object is marked as embedded, otherwise `false`.
*/
function isEmbedded(objectSchema) {
return objectSchema.tableType === 1 /* binding.TableType.Embedded */;
}
// Declare the Realm namespace for backwards compatibility
// This declaration needs to happen in the same file which declares "Realm"
// @see https://www.typescriptlang.org/docs/handbook/declaration-merging.html#merging-namespaces-with-classes-functions-and-enums
const ns = __importStar(require("./namespace"));
// Needed to avoid complaints about a self-reference
var RealmItself = Realm;
// eslint-disable-next-line @typescript-eslint/no-namespace
(function (Realm) {
Realm.Realm = RealmItself;
Realm.flags = ns.flags;
Realm.Object = ns.RealmObject;
Realm.App = ns.App;
Realm.Auth = ns.Auth;
Realm.BSON = ns.BSON;
Realm.Types = ns.Types;
Realm.Services = ns.Services;
Realm.index = ns.index;
Realm.mapTo = ns.mapTo;
Realm.kmToRadians = ns.kmToRadians;
Realm.miToRadians = ns.miToRadians;
Realm.AssertionError = ns.AssertionError;
Realm.ClientResetMode = ns.ClientResetMode;
Realm.Collection = ns.Collection;
Realm.CompensatingWriteError = ns.CompensatingWriteError;
Realm.ConnectionState = ns.ConnectionState;
Realm.Counter = ns.Counter;
Realm.Credentials = ns.Credentials;
Realm.Dictionary = ns.Dictionary;
Realm.List = ns.List;
Realm.MetadataMode = ns.MetadataMode;
Realm.MongoDB = ns.MongoDB;
Realm.NumericLogLevel = ns.NumericLogLevel;
Realm.OpenRealmBehaviorType = ns.OpenRealmBehaviorType;
Realm.OpenRealmTimeOutBehavior = ns.OpenRealmTimeOutBehavior;
Realm.OrderedCollection = ns.OrderedCollection;
Realm.ProgressDirection = ns.ProgressDirection;
Realm.ProgressMode = ns.ProgressMode;
Realm.ProgressRealmPromise = ns.ProgressRealmPromise;
Realm.PropertySchemaParseError = ns.PropertySchemaParseError;
Realm.ProviderType = ns.ProviderType;
Realm.ProxyType = ns.ProxyType;
Realm.RealmEvent = ns.RealmEvent;
Realm.Results = ns.Results;
Realm.SchemaParseError = ns.SchemaParseError;
Realm.SessionState = ns.SessionState;
Realm.SessionStopPolicy = ns.SessionStopPolicy;
Realm.Set = ns.RealmSet;
Realm.SubscriptionSetState = ns.SubscriptionSetState;
Realm.SyncError = ns.SyncError;
Realm.TypeAssertionError = ns.TypeAssertionError;
Realm.UpdateMode = ns.UpdateMode;
Realm.User = ns.User;
Realm.UserState = ns.UserState;
Realm.WaitForSync = ns.WaitForSync;
/** @deprecated Use another {@link ns.ClientResetMode | ClientResetMode} than {@link ns.ClientResetMode.Manual | ClientResetMode.Manual}. */
Realm.ClientResetError = ns.ClientResetError;
/** @deprecated See https://www.mongodb.com/docs/atlas/app-services/reference/push-notifications/ */
Realm.PushClient = ns.PushClient;
})(Realm = exports.Realm || (exports.Realm = {}));
exports.Realm = Realm;
// Set default logger and log level.
Realm.setLogger(Logger_1.defaultLogger);
Realm.setLogLevel(Logger_1.defaultLoggerLevel);
//# sourceMappingURL=Realm.js.map