UNPKG

realm

Version:

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

253 lines 10.6 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.Dictionary = void 0; //////////////////////////////////////////////////////////////////////////// // // 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. // //////////////////////////////////////////////////////////////////////////// const internal_1 = require("./internal"); /* eslint-disable jsdoc/multiline-blocks -- We need this to have @ts-expect-error located correctly in the .d.ts bundle */ const REALM = Symbol("Dictionary#realm"); const INTERNAL = Symbol("Dictionary#internal"); const HELPERS = Symbol("Dictionary#helpers"); const DEFAULT_PROPERTY_DESCRIPTOR = { configurable: true, enumerable: true }; const PROXY_HANDLER = { get(target, prop, receiver) { const value = Reflect.get(target, prop, receiver); if (typeof value === "undefined" && typeof prop === "string") { const internal = target[INTERNAL]; const fromBinding = target[HELPERS].fromBinding; return fromBinding(internal.tryGetAny(prop)); } else { return value; } }, set(target, prop, value) { if (typeof prop === "string") { const internal = target[INTERNAL]; const toBinding = target[HELPERS].toBinding; internal.insertAny(prop, toBinding(value)); return true; } else { (0, internal_1.assert)(typeof prop !== "symbol", "Symbols cannot be used as keys of a dictionary"); return false; } }, deleteProperty(target, prop) { // We're intentionally not checking !Reflect.has(target, prop) below to allow deletes to propagate for any key if (typeof prop === "string") { const internal = target[INTERNAL]; internal.tryErase(prop); // We consider any key without a value as "deletable", the same way `const foo = {}; delete foo.bar;` returns true return true; } else { return false; } }, ownKeys(target) { const internal = target[INTERNAL]; const result = Reflect.ownKeys(target); const keys = internal.keys.snapshot(); for (let i = 0; i < keys.size(); i++) { const key = keys.getAny(i); internal_1.assert.string(key, "dictionary key"); result.push(key); } return result; }, getOwnPropertyDescriptor(target, prop) { const internal = target[INTERNAL]; if (typeof prop === "string" && internal.contains(prop)) { return { ...DEFAULT_PROPERTY_DESCRIPTOR, get: PROXY_HANDLER.get?.bind(null, target, prop, null), set: PROXY_HANDLER.set?.bind(null, target, prop, null), }; } else { return Reflect.getOwnPropertyDescriptor(target, prop); } }, }; /** * Instances of this class are returned when accessing object properties whose type is `"Dictionary"` * * Dictionaries behave mostly like a JavaScript object i.e., as a key/value pair * where the key is a string. */ class Dictionary extends internal_1.Collection { /** * Create a `Results` wrapping a set of query `Results` from the binding. * @internal */ constructor(realm, internal, helpers) { if (arguments.length === 0 || !(internal instanceof internal_1.binding.Dictionary)) { throw new internal_1.IllegalConstructorError("Dictionary"); } super((listener, keyPaths) => { return this[INTERNAL].addKeyBasedNotificationCallback(({ deletions, insertions, modifications }) => { try { listener(proxied, { deletions: deletions.map((value) => { internal_1.assert.string(value); return value; }), insertions: insertions.map((value) => { internal_1.assert.string(value); return value; }), modifications: modifications.map((value) => { internal_1.assert.string(value); return value; }), }); } catch (err) { // Scheduling a throw on the event loop, // since throwing synchronously here would result in an abort in the calling C++ setImmediate(() => { throw err; }); } }, keyPaths ? realm.internal.createKeyPathArray(internal.objectSchema.name, keyPaths) : keyPaths); }); const proxied = new Proxy(this, PROXY_HANDLER); Object.defineProperty(this, REALM, { enumerable: false, configurable: false, writable: false, value: realm, }); Object.defineProperty(this, INTERNAL, { enumerable: false, configurable: false, writable: false, value: internal, }); Object.defineProperty(this, HELPERS, { enumerable: false, configurable: false, writable: false, value: helpers, }); return proxied; } /** * @see {@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/entries Array.prototype.entries} * @returns An iterator with all entries in the dictionary. */ *[Symbol.iterator]() { yield* this.entries(); } /** * @see {@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/keys Array.prototype.keys} * @returns An iterator with all values in the dictionary. * @since 10.5.0 * @ts-expect-error We're exposing methods in the end-users namespace of keys */ *keys() { const snapshot = this[INTERNAL].keys.snapshot(); const size = snapshot.size(); for (let i = 0; i < size; i++) { const key = snapshot.getAny(i); internal_1.assert.string(key); yield key; } } /** * @see {@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/values Array.prototype.values} * @returns An iterator with all values in the dictionary. * @since 10.5.0 * @ts-expect-error We're exposing methods in the end-users namespace of values */ *values() { const { fromBinding } = this[HELPERS]; const snapshot = this[INTERNAL].values.snapshot(); const size = snapshot.size(); for (let i = 0; i < size; i++) { const value = snapshot.getAny(i); yield fromBinding(value); } } /** * @see {@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/entries Array.prototype.entries} * @returns An iterator with all key/value pairs in the dictionary. * @since 10.5.0 * @ts-expect-error We're exposing methods in the end-users namespace of entries */ *entries() { const { fromBinding } = this[HELPERS]; const keys = this[INTERNAL].keys.snapshot(); const values = this[INTERNAL].values.snapshot(); const size = keys.size(); (0, internal_1.assert)(size === values.size(), "Expected keys and values to equal in size"); for (let i = 0; i < size; i++) { const key = keys.getAny(i); const value = values.getAny(i); yield [key, fromBinding(value)]; } } /** * Checks if this dictionary has not been deleted and is part of a valid Realm. * @returns `true` if the dictionary can be safely accessed. * @since 0.14.0 * @ts-expect-error We're exposing methods in the end-users namespace of keys */ isValid() { return this[INTERNAL].isValid; } /** * Adds one or more elements with the specified key and value to the dictionary or updates value if key exists. * @param elementsOrKey - The element to add or the key of the element to add. * @param value - The value of the element to add. * @throws An {@link AssertionError} if not inside a write transaction, if using symbol as keys, or if any value violates type constraints. * @returns The dictionary. * @since 10.6.0 */ set(elementsOrKey, value) { const elements = typeof elementsOrKey === "object" ? elementsOrKey : { [elementsOrKey]: value }; (0, internal_1.assert)(Object.getOwnPropertySymbols(elements).length === 0, "Symbols cannot be used as keys of a dictionary"); internal_1.assert.inTransaction(this[REALM]); const internal = this[INTERNAL]; const toBinding = this[HELPERS].toBinding; for (const [key, val] of Object.entries(elements)) { internal.insertAny(key, toBinding(val)); } return this; } /** * Removes elements from the dictionary, with the keys provided. * This does not throw if the keys are already missing from the dictionary. * @param key - The key to be removed. * @throws An {@link AssertionError} if not inside a write transaction. * @returns The dictionary * @since 10.6.0 * @ts-expect-error We're exposing methods in the end-users namespace of keys */ remove(key) { internal_1.assert.inTransaction(this[REALM]); const internal = this[INTERNAL]; const keys = typeof key === "string" ? [key] : key; for (const k of keys) { internal.tryErase(k); } return this; } /** @internal */ toJSON(_, cache = new internal_1.JSONCacheMap()) { return Object.fromEntries(Object.entries(this).map(([k, v]) => [k, v instanceof internal_1.RealmObject ? v.toJSON(k, cache) : v])); } } exports.Dictionary = Dictionary; //# sourceMappingURL=Dictionary.js.map