UNPKG

@realm/react

Version:

React specific hooks and implementation helpers for Realm

1,384 lines (1,287 loc) 117 kB
'use strict'; var Realm = require('realm'); var React = require('react'); //////////////////////////////////////////////////////////////////////////// // // 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 numericRegEx = /^-?\d+$/; function getCacheKey(id) { return `${id}`; } /** * Creates a proxy around a {@link Realm.Collection} that will create new {@link Realm.Object} * references on any relevant change (update, insert, deletion) and return the same * object reference if no changes have occurred since the last access. * * This makes the {@link Realm.Collection} behaves in an immutable way, as React expects, so * that a {@link Realm.Object} can be wrapped in {@link React.memo} to prevent unnecessary * rendering (see {@link useQuery} hook). * @param args {@link CachedCollectionArgs} object arguments * @returns Proxy object wrapping the collection */ function createCachedCollection({ collection, realm, updateCallback, updatedRef, objectCache = new Map(), isDerived = false, keyPaths, }) { const cachedCollectionHandler = { get: function (target, key, receiver) { // Pass functions through const value = Reflect.get(target, key, receiver); if (typeof value === "function") { if (key === "sorted" || key === "filtered") { return (...args) => { const col = Reflect.apply(value, target, args); const { collection: newCol } = createCachedCollection({ collection: col, realm, updateCallback, updatedRef, objectCache, isDerived: true, keyPaths, }); return newCol; }; } return value; } // If the key is not numeric, pass it through if (typeof key === "symbol" || !numericRegEx.test(key)) { return value; } // If the key is numeric, check if we have a cached object for this key const index = Number(key); const object = target[index]; // If the collection is modeled in a way that objects can be null // then we should return null instead of undefined to stay semantically // correct if (object === null) { return null; } else if (typeof object === "undefined") { // If there is no object at this index, return undefined return undefined; } const objectId = object._objectKey(); const cacheKey = getCacheKey(objectId); // If we do, return it... if (objectCache.get(cacheKey)) { return objectCache.get(cacheKey); } // If not then this index has either not been accessed before, or has been invalidated due // to a modification. Fetch it from the collection and store it in the cache objectCache.set(cacheKey, object); return object; }, }; const cachedCollectionResult = new Proxy(collection, cachedCollectionHandler); const listenerCallback = (listenerCollection, changes) => { if (changes.deletions.length > 0 || changes.insertions.length > 0 || changes.newModifications.length > 0) { // TODO: There is currently no way to rebuild the cache key from the changes array for deleted object. // Until it is possible, we clear the cache on deletions. // Blocking issue: https://github.com/realm/realm-core/issues/5220 // Possible solutions: // a. the listenerCollection is a frozen copy of the collection before the deletion, // allowing accessing the _objectKey() using listenerCollection[index]._objectKey() // b. the callback provides an array of changed objectIds if (changes.deletions.length > 0) { objectCache.clear(); } // Item(s) were modified, just clear them from the cache so that we return new instances for them changes.newModifications.forEach((index) => { const objectId = listenerCollection[index]._objectKey(); if (objectId) { const cacheKey = getCacheKey(objectId); if (objectCache.has(cacheKey)) { objectCache.delete(cacheKey); } } }); updatedRef.current = true; updateCallback(); } }; let setImmediateId = undefined; if (!isDerived) { // If we are in a transaction, then push adding the listener to the event loop. This will allow the write transaction to finish. // see https://github.com/realm/realm-js/issues/4375 if (realm.isInTransaction) { setImmediateId = setImmediate(() => { collection.addListener(listenerCallback, keyPaths); }); } else { collection.addListener(listenerCallback, keyPaths); } } const tearDown = () => { if (!isDerived) { if (setImmediateId) { clearImmediate(setImmediateId); setImmediateId = undefined; } collection.removeListener(listenerCallback); objectCache.clear(); } }; return { collection: cachedCollectionResult, tearDown }; } //////////////////////////////////////////////////////////////////////////// // // 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. // //////////////////////////////////////////////////////////////////////////// /** * Creates a proxy around a {@link Realm.Object} that will return a new reference * on any relevant update to the object itself. It also wraps accesses to {@link Realm.List} * attributes into a {@link cachedCollection}, so that any update, insert or deletion to the * list will also return a new object reference. * * See {@link cachedCollection} and the `useObject` hook for description of how this * helps prevent unnecessary rendering. * @param args - {@link CachedObjectArgs} object arguments * @returns Proxy object wrapping the {@link Realm.Object} */ function createCachedObject({ object, realm, updateCallback, updatedRef, keyPaths, }) { const listCaches = new Map(); const listTearDowns = []; // If the object doesn't exist, just return it with an noop tearDown // if (object === null) { return { object, tearDown: () => undefined }; } // Create a cache for any Realm.List properties on the object for (const key of object.keys()) { //@ts-expect-error - TS doesn't know that the key is a valid property const value = object[key]; if (value instanceof Realm.List && value.type === "object") { const updatedRef = { current: true }; const { collection, tearDown } = createCachedCollection({ collection: value, realm, updateCallback, updatedRef }); listCaches.set(key, { collection, updatedRef }); listTearDowns.push(tearDown); } } // This Proxy handler intercepts any accesses into properties of the cached object // of type `Realm.List`, and returns a `cachedCollection` wrapping those properties // to allow changes in the list to trigger re-renders const cachedObjectHandler = { get: function (target, key, receiver) { const value = Reflect.get(target, key, receiver); // If its a Realm.List we need to add a proxy cache around it if (value instanceof Realm.List && value.type === "object") { if (listCaches.has(key)) { // Return a new proxy wrapping the cachedCollection so that its reference gets updated, // otherwise the list component will not re-render. The cachedCollection then ensures that // only the modified children of the list component actually re-render. const { collection, updatedRef } = listCaches.get(key); if (updatedRef.current) { updatedRef.current = false; const proxyCollection = new Proxy(collection, {}); listCaches.set(key, { collection: proxyCollection, updatedRef }); return proxyCollection; } return collection; } } return value; }, }; const cachedObjectResult = new Proxy(object, cachedObjectHandler); const listenerCallback = (obj, changes) => { updatedRef.current = true; if (changes.deleted) { updateCallback(); } else if (changes.changedProperties.length > 0) { // Don't force a second re-render if any of the changed properties is a Realm.List, // as the List's cachedCollection will force a re-render itself const anyListPropertyModified = changes.changedProperties.some((property) => { return obj[property] instanceof Realm.List && obj[property].type === "object"; }); const shouldRerender = !anyListPropertyModified; if (shouldRerender) { updateCallback(); } } }; // We cannot add a listener to an invalid object if (object.isValid()) { // If we are in a transaction, then push adding the listener to the event loop. This will allow the write transaction to finish. // see https://github.com/realm/realm-js/issues/4375 if (realm.isInTransaction) { setImmediate(() => { object.addListener(listenerCallback, keyPaths); }); } else { object.addListener(listenerCallback, keyPaths); } } const tearDown = () => { object.removeListener(listenerCallback); for (const listTearDown of listTearDowns) { listTearDown(); } }; return { object: cachedObjectResult, tearDown }; } //////////////////////////////////////////////////////////////////////////// // // Copyright 2023 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. // //////////////////////////////////////////////////////////////////////////// // Convenience function that returns the correct type for the objectForPrimaryKey function // Since we don't have a combined declaration for this function, typescript needs to know // which return to use based on the typeof the type argument function getObjectForPrimaryKey(realm, type, primaryKey) { return typeof type === "string" ? realm.objectForPrimaryKey(type, primaryKey) : realm.objectForPrimaryKey(type, primaryKey); } // Convenience function that returns the correct type for the objects function // Since we don't have a combined declaration for this function, typescript needs to know // which return to use based on the typeof the type argument function getObjects(realm, type) { return (typeof type === "string" ? realm.objects(type) : realm.objects(type)); } function isClassModelConstructor(value) { return Object.getPrototypeOf(value) === Realm.Object; } //////////////////////////////////////////////////////////////////////////// // // Copyright 2021 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. // //////////////////////////////////////////////////////////////////////////// /** * Generates the `useObject` hook from a given `useRealm` hook. * @param useRealm - Hook that returns an open Realm instance * @returns useObject - Hook that is used to gain access to a single Realm object from a primary key */ function createUseObject(useRealm) { function useObject(type, primaryKey, keyPaths) { const realm = useRealm(); // Create a forceRerender function for the cachedObject to use as its updateCallback, so that // the cachedObject can force the component using this hook to re-render when a change occurs. const [, forceRerender] = React.useReducer((x) => x + 1, 0); // Get the original object from the realm, so we can check if it exists const originalObject = getObjectForPrimaryKey(realm, type, primaryKey); // Store the primaryKey as a ref, since when it is an objectId or UUID, it will be a new instance on every render const primaryKeyRef = React.useRef(primaryKey); const collectionRef = React.useRef(getObjects(realm, type)); const objectRef = React.useRef(); const updatedRef = React.useRef(true); // Initializing references with a function call or class constructor will // cause the function or constructor to be called on ever render. // Even though this value is thrown away on subsequent renders, `createCachedObject` will end up registering a listener. // Therefore, we initialize the references with null, and only create the object if it is null // Ref: https://github.com/facebook/react/issues/14490 const cachedObjectRef = React.useRef(null); const memoizedKeyPaths = React.useMemo(() => (typeof keyPaths === "string" ? [keyPaths] : keyPaths), /* eslint-disable-next-line react-hooks/exhaustive-deps -- Memoizing the keyPaths to avoid renders */ [JSON.stringify(keyPaths)]); if (!cachedObjectRef.current) { cachedObjectRef.current = createCachedObject({ object: originalObject ?? null, realm, updateCallback: forceRerender, updatedRef, keyPaths: memoizedKeyPaths, }); } // Create a ref, since the object returned from `objectForPrimaryKey` is always going to have a different reference const originalObjectRef = React.useRef(originalObject); // Wrap the cachedObject in useMemo, so we only replace it with a new instance if `primaryKey` or `type` change const { object, tearDown } = React.useMemo( // TODO: There will be an upcoming breaking change that makes objectForPrimaryKey return null // When this is implemented, remove `?? null` () => { // This should never happen, but if it does, we want to return a null result if (!cachedObjectRef.current) { return { object: null, tearDown: () => undefined }; } // Re-instantiate the cachedObject if the primaryKey has changed or the originalObject has gone from null to not null if (!arePrimaryKeysIdentical(primaryKey, primaryKeyRef.current) || (!originalObjectRef.current && originalObject)) { cachedObjectRef.current = createCachedObject({ object: originalObject ?? null, realm, updateCallback: forceRerender, updatedRef, keyPaths: memoizedKeyPaths, }); originalObjectRef.current = originalObject; // Primary key has updated, so update the reference primaryKeyRef.current = primaryKey; // Signal that the object reference needs to be updated updatedRef.current = true; } return cachedObjectRef.current; }, [realm, originalObject, primaryKey, memoizedKeyPaths]); // Invoke the tearDown of the cachedObject when useObject is unmounted React.useEffect(() => { return tearDown; }, [tearDown]); // If the object doesn't exist, listen for insertions to the collection and force a rerender if the inserted object has the correct primary key React.useEffect(() => { const collection = collectionRef.current; const collectionListener = (_, changes) => { const primaryKeyProperty = collection?.[0]?.objectSchema()?.primaryKey; for (const index of changes.insertions) { const object = collection[index]; if (primaryKeyProperty) { //@ts-expect-error - if the primaryKeyProperty exists, then it is indexable. However, we don't allow it when we don't actually know the type of the object const insertedPrimaryKey = object[primaryKeyProperty]; if (arePrimaryKeysIdentical(insertedPrimaryKey, primaryKeyRef.current)) { forceRerender(); collection.removeListener(collectionListener); break; } } } }; if (!originalObjectRef.current) { collection.addListener(collectionListener); } return () => { // If the app is closing, the realm will be closed and the listener does not need to be removed if if (!realm.isClosed && collection) { collection.removeListener(collectionListener); } }; }, [realm, type, forceRerender]); // If the object has been deleted or doesn't exist for the given primary key, just return null if (!object?.isValid()) { return null; } if (updatedRef.current) { // Wrap object in a proxy to update the reference on rerender ( should only rerender when something has changed ) objectRef.current = new Proxy(object, {}); updatedRef.current = false; } // This will never be undefined, but the type system doesn't know that return objectRef.current; } return function useObjectOverload(typeOrOptions, primaryKey, keyPaths) { if (typeof typeOrOptions === "string" || isClassModelConstructor(typeOrOptions)) { if (typeof primaryKey === "undefined") { throw new Error("Expected a primary key"); } /* eslint-disable-next-line react-hooks/rules-of-hooks -- We're calling `useQuery` once in any of the brances */ return useObject(typeOrOptions, primaryKey, keyPaths); } else { const { type, primaryKey, keyPaths } = typeOrOptions; /* eslint-disable-next-line react-hooks/rules-of-hooks -- We're calling `useQuery` once in any of the brances */ return useObject(type, primaryKey, keyPaths); } }; } // This is a helper function that determines if two primary keys are equal. It will also handle the case where the primary key is an ObjectId or UUID function arePrimaryKeysIdentical(a, b) { if (typeof a !== typeof b) { return false; } if (typeof a === "string" || typeof a === "number") { return a === b; } if (a instanceof Realm.BSON.ObjectId && b instanceof Realm.BSON.ObjectId) { return a.toHexString() === b.toHexString(); } if (a instanceof Realm.BSON.UUID && b instanceof Realm.BSON.UUID) { return a.toHexString() === b.toHexString(); } return false; } //////////////////////////////////////////////////////////////////////////// // // Copyright 2021 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. // //////////////////////////////////////////////////////////////////////////// /** * Maps a value to itself */ function identity(value) { return value; } /** * Generates the `useQuery` hook from a given `useRealm` hook. * @param useRealm - Hook that returns an open Realm instance * @returns useObject - Hook that is used to gain access to a {@link Realm.Collection} */ function createUseQuery(useRealm) { function useQuery({ type, query = identity, keyPaths }, deps = []) { const realm = useRealm(); // We need to add the type to the deps, so that if the type changes, the query will be re-run. // This will be saved in an array which will be spread into the provided deps. const requiredDeps = [type]; // Create a forceRerender function for the cachedCollection to use as its updateCallback, so that // the cachedCollection can force the component using this hook to re-render when a change occurs. const [, forceRerender] = React.useReducer((x) => x + 1, 0); const collectionRef = React.useRef(); const updatedRef = React.useRef(true); const queryCallbackRef = React.useRef(null); /* eslint-disable-next-line react-hooks/exhaustive-deps -- We want the user of this hook to be able pass in the `query` function inline (without the need to `useCallback` on it) This means that the query function is unstable and will be a redefined on each render of the component where `useQuery` is used Therefore we use the `deps` array to memoize the query function internally, and only use the returned `queryCallback` */ const queryCallback = React.useCallback(query, [...deps, ...requiredDeps]); // If the query function changes, we need to update the cachedCollection if (queryCallbackRef.current !== queryCallback) { queryCallbackRef.current = queryCallback; updatedRef.current = true; } const queryResult = React.useMemo(() => { return queryCallback(getObjects(realm, type)); }, [type, realm, queryCallback]); const memoizedKeyPaths = React.useMemo(() => (typeof keyPaths === "string" ? [keyPaths] : keyPaths), /* eslint-disable-next-line react-hooks/exhaustive-deps -- Memoizing the keyPaths to avoid renders */ [JSON.stringify(keyPaths)]); // Wrap the cachedObject in useMemo, so we only replace it with a new instance if `realm` or `queryResult` change const { collection, tearDown } = React.useMemo(() => { return createCachedCollection({ collection: queryResult, realm, updateCallback: forceRerender, updatedRef, keyPaths: memoizedKeyPaths, }); }, [realm, queryResult, memoizedKeyPaths]); // Invoke the tearDown of the cachedCollection when useQuery is unmounted React.useEffect(() => { return tearDown; }, [tearDown]); // This makes sure the collection has a different reference on a rerender // Also we are ensuring the type returned is Realm.Results, as this is known in this context if (updatedRef.current) { updatedRef.current = false; collectionRef.current = new Proxy(collection, {}); } // This will never not be defined, but the type system doesn't know that return collectionRef.current; } return function useQueryOverload(typeOrOptionsOrQuery, queryOrDeps = identity, depsOrPartialOptions = []) { const args = { typeOrOptionsOrQuery, queryOrDeps, depsOrPartialOptions }; /* eslint-disable react-hooks/rules-of-hooks -- We're calling `useQuery` once in any of the brances */ if (isTypeFunctionDeps(args)) { return useQuery({ type: args.typeOrOptionsOrQuery, query: args.queryOrDeps }, args.depsOrPartialOptions); } if (isOptionsDepsNone(args)) { return useQuery(args.typeOrOptionsOrQuery, Array.isArray(args.queryOrDeps) ? args.queryOrDeps : []); } if (isFunctionDepsOptions(args)) { return useQuery({ ...args.depsOrPartialOptions, query: args.typeOrOptionsOrQuery }, args.queryOrDeps); } /* eslint-enable react-hooks/rules-of-hooks */ throw new Error("Unexpected arguments passed to useQuery"); }; } function isTypeFunctionDeps(args) { const { typeOrOptionsOrQuery, queryOrDeps, depsOrPartialOptions } = args; return ((typeof typeOrOptionsOrQuery === "string" || isClassModelConstructor(typeOrOptionsOrQuery)) && typeof queryOrDeps === "function" && Array.isArray(depsOrPartialOptions)); } function isOptionsDepsNone(args) { const { typeOrOptionsOrQuery, queryOrDeps } = args; return (typeof typeOrOptionsOrQuery === "object" && typeOrOptionsOrQuery !== null && (Array.isArray(queryOrDeps) || queryOrDeps === identity)); } function isFunctionDepsOptions(args) { const { typeOrOptionsOrQuery, queryOrDeps, depsOrPartialOptions } = args; return (typeof typeOrOptionsOrQuery === "function" && Array.isArray(queryOrDeps) && typeof depsOrPartialOptions === "object" && depsOrPartialOptions !== null); } //////////////////////////////////////////////////////////////////////////// // // Copyright 2021 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. // //////////////////////////////////////////////////////////////////////////// /** * Generates a `useRealm` hook given a RealmContext. This allows access to the {@link Realm} * instance anywhere within the RealmProvider. * @param RealmContext - The context containing the {@link Realm} instance * @returns useRealm - Hook that is used to gain access to the {@link Realm} instance */ const createUseRealm = (RealmContext) => { return function useRealm() { // This is the context setup by `createRealmContext` const context = React.useContext(RealmContext); if (context === null) { throw new Error("Realm context not found. Did you call useRealm() within a <RealmProvider/>?"); } return context; }; }; var commonjsGlobal = typeof globalThis !== 'undefined' ? globalThis : typeof window !== 'undefined' ? window : typeof global !== 'undefined' ? global : typeof self !== 'undefined' ? self : {}; function getDefaultExportFromCjs (x) { return x && x.__esModule && Object.prototype.hasOwnProperty.call(x, 'default') ? x['default'] : x; } var lodash_isequal = {exports: {}}; /** * Lodash (Custom Build) <https://lodash.com/> * Build: `lodash modularize exports="npm" -o ./` * Copyright JS Foundation and other contributors <https://js.foundation/> * Released under MIT license <https://lodash.com/license> * Based on Underscore.js 1.8.3 <http://underscorejs.org/LICENSE> * Copyright Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors */ lodash_isequal.exports; (function (module, exports) { /** Used as the size to enable large array optimizations. */ var LARGE_ARRAY_SIZE = 200; /** Used to stand-in for `undefined` hash values. */ var HASH_UNDEFINED = '__lodash_hash_undefined__'; /** Used to compose bitmasks for value comparisons. */ var COMPARE_PARTIAL_FLAG = 1, COMPARE_UNORDERED_FLAG = 2; /** Used as references for various `Number` constants. */ var MAX_SAFE_INTEGER = 9007199254740991; /** `Object#toString` result references. */ var argsTag = '[object Arguments]', arrayTag = '[object Array]', asyncTag = '[object AsyncFunction]', boolTag = '[object Boolean]', dateTag = '[object Date]', errorTag = '[object Error]', funcTag = '[object Function]', genTag = '[object GeneratorFunction]', mapTag = '[object Map]', numberTag = '[object Number]', nullTag = '[object Null]', objectTag = '[object Object]', promiseTag = '[object Promise]', proxyTag = '[object Proxy]', regexpTag = '[object RegExp]', setTag = '[object Set]', stringTag = '[object String]', symbolTag = '[object Symbol]', undefinedTag = '[object Undefined]', weakMapTag = '[object WeakMap]'; var arrayBufferTag = '[object ArrayBuffer]', dataViewTag = '[object DataView]', float32Tag = '[object Float32Array]', float64Tag = '[object Float64Array]', int8Tag = '[object Int8Array]', int16Tag = '[object Int16Array]', int32Tag = '[object Int32Array]', uint8Tag = '[object Uint8Array]', uint8ClampedTag = '[object Uint8ClampedArray]', uint16Tag = '[object Uint16Array]', uint32Tag = '[object Uint32Array]'; /** * Used to match `RegExp` * [syntax characters](http://ecma-international.org/ecma-262/7.0/#sec-patterns). */ var reRegExpChar = /[\\^$.*+?()[\]{}|]/g; /** Used to detect host constructors (Safari). */ var reIsHostCtor = /^\[object .+?Constructor\]$/; /** Used to detect unsigned integer values. */ var reIsUint = /^(?:0|[1-9]\d*)$/; /** Used to identify `toStringTag` values of typed arrays. */ var typedArrayTags = {}; typedArrayTags[float32Tag] = typedArrayTags[float64Tag] = typedArrayTags[int8Tag] = typedArrayTags[int16Tag] = typedArrayTags[int32Tag] = typedArrayTags[uint8Tag] = typedArrayTags[uint8ClampedTag] = typedArrayTags[uint16Tag] = typedArrayTags[uint32Tag] = true; typedArrayTags[argsTag] = typedArrayTags[arrayTag] = typedArrayTags[arrayBufferTag] = typedArrayTags[boolTag] = typedArrayTags[dataViewTag] = typedArrayTags[dateTag] = typedArrayTags[errorTag] = typedArrayTags[funcTag] = typedArrayTags[mapTag] = typedArrayTags[numberTag] = typedArrayTags[objectTag] = typedArrayTags[regexpTag] = typedArrayTags[setTag] = typedArrayTags[stringTag] = typedArrayTags[weakMapTag] = false; /** Detect free variable `global` from Node.js. */ var freeGlobal = typeof commonjsGlobal == 'object' && commonjsGlobal && commonjsGlobal.Object === Object && commonjsGlobal; /** Detect free variable `self`. */ var freeSelf = typeof self == 'object' && self && self.Object === Object && self; /** Used as a reference to the global object. */ var root = freeGlobal || freeSelf || Function('return this')(); /** Detect free variable `exports`. */ var freeExports = exports && !exports.nodeType && exports; /** Detect free variable `module`. */ var freeModule = freeExports && 'object' == 'object' && module && !module.nodeType && module; /** Detect the popular CommonJS extension `module.exports`. */ var moduleExports = freeModule && freeModule.exports === freeExports; /** Detect free variable `process` from Node.js. */ var freeProcess = moduleExports && freeGlobal.process; /** Used to access faster Node.js helpers. */ var nodeUtil = (function() { try { return freeProcess && freeProcess.binding && freeProcess.binding('util'); } catch (e) {} }()); /* Node.js helper references. */ var nodeIsTypedArray = nodeUtil && nodeUtil.isTypedArray; /** * A specialized version of `_.filter` for arrays without support for * iteratee shorthands. * * @private * @param {Array} [array] The array to iterate over. * @param {Function} predicate The function invoked per iteration. * @returns {Array} Returns the new filtered array. */ function arrayFilter(array, predicate) { var index = -1, length = array == null ? 0 : array.length, resIndex = 0, result = []; while (++index < length) { var value = array[index]; if (predicate(value, index, array)) { result[resIndex++] = value; } } return result; } /** * Appends the elements of `values` to `array`. * * @private * @param {Array} array The array to modify. * @param {Array} values The values to append. * @returns {Array} Returns `array`. */ function arrayPush(array, values) { var index = -1, length = values.length, offset = array.length; while (++index < length) { array[offset + index] = values[index]; } return array; } /** * A specialized version of `_.some` for arrays without support for iteratee * shorthands. * * @private * @param {Array} [array] The array to iterate over. * @param {Function} predicate The function invoked per iteration. * @returns {boolean} Returns `true` if any element passes the predicate check, * else `false`. */ function arraySome(array, predicate) { var index = -1, length = array == null ? 0 : array.length; while (++index < length) { if (predicate(array[index], index, array)) { return true; } } return false; } /** * The base implementation of `_.times` without support for iteratee shorthands * or max array length checks. * * @private * @param {number} n The number of times to invoke `iteratee`. * @param {Function} iteratee The function invoked per iteration. * @returns {Array} Returns the array of results. */ function baseTimes(n, iteratee) { var index = -1, result = Array(n); while (++index < n) { result[index] = iteratee(index); } return result; } /** * The base implementation of `_.unary` without support for storing metadata. * * @private * @param {Function} func The function to cap arguments for. * @returns {Function} Returns the new capped function. */ function baseUnary(func) { return function(value) { return func(value); }; } /** * Checks if a `cache` value for `key` exists. * * @private * @param {Object} cache The cache to query. * @param {string} key The key of the entry to check. * @returns {boolean} Returns `true` if an entry for `key` exists, else `false`. */ function cacheHas(cache, key) { return cache.has(key); } /** * Gets the value at `key` of `object`. * * @private * @param {Object} [object] The object to query. * @param {string} key The key of the property to get. * @returns {*} Returns the property value. */ function getValue(object, key) { return object == null ? undefined : object[key]; } /** * Converts `map` to its key-value pairs. * * @private * @param {Object} map The map to convert. * @returns {Array} Returns the key-value pairs. */ function mapToArray(map) { var index = -1, result = Array(map.size); map.forEach(function(value, key) { result[++index] = [key, value]; }); return result; } /** * Creates a unary function that invokes `func` with its argument transformed. * * @private * @param {Function} func The function to wrap. * @param {Function} transform The argument transform. * @returns {Function} Returns the new function. */ function overArg(func, transform) { return function(arg) { return func(transform(arg)); }; } /** * Converts `set` to an array of its values. * * @private * @param {Object} set The set to convert. * @returns {Array} Returns the values. */ function setToArray(set) { var index = -1, result = Array(set.size); set.forEach(function(value) { result[++index] = value; }); return result; } /** Used for built-in method references. */ var arrayProto = Array.prototype, funcProto = Function.prototype, objectProto = Object.prototype; /** Used to detect overreaching core-js shims. */ var coreJsData = root['__core-js_shared__']; /** Used to resolve the decompiled source of functions. */ var funcToString = funcProto.toString; /** Used to check objects for own properties. */ var hasOwnProperty = objectProto.hasOwnProperty; /** Used to detect methods masquerading as native. */ var maskSrcKey = (function() { var uid = /[^.]+$/.exec(coreJsData && coreJsData.keys && coreJsData.keys.IE_PROTO || ''); return uid ? ('Symbol(src)_1.' + uid) : ''; }()); /** * Used to resolve the * [`toStringTag`](http://ecma-international.org/ecma-262/7.0/#sec-object.prototype.tostring) * of values. */ var nativeObjectToString = objectProto.toString; /** Used to detect if a method is native. */ var reIsNative = RegExp('^' + funcToString.call(hasOwnProperty).replace(reRegExpChar, '\\$&') .replace(/hasOwnProperty|(function).*?(?=\\\()| for .+?(?=\\\])/g, '$1.*?') + '$' ); /** Built-in value references. */ var Buffer = moduleExports ? root.Buffer : undefined, Symbol = root.Symbol, Uint8Array = root.Uint8Array, propertyIsEnumerable = objectProto.propertyIsEnumerable, splice = arrayProto.splice, symToStringTag = Symbol ? Symbol.toStringTag : undefined; /* Built-in method references for those with the same name as other `lodash` methods. */ var nativeGetSymbols = Object.getOwnPropertySymbols, nativeIsBuffer = Buffer ? Buffer.isBuffer : undefined, nativeKeys = overArg(Object.keys, Object); /* Built-in method references that are verified to be native. */ var DataView = getNative(root, 'DataView'), Map = getNative(root, 'Map'), Promise = getNative(root, 'Promise'), Set = getNative(root, 'Set'), WeakMap = getNative(root, 'WeakMap'), nativeCreate = getNative(Object, 'create'); /** Used to detect maps, sets, and weakmaps. */ var dataViewCtorString = toSource(DataView), mapCtorString = toSource(Map), promiseCtorString = toSource(Promise), setCtorString = toSource(Set), weakMapCtorString = toSource(WeakMap); /** Used to convert symbols to primitives and strings. */ var symbolProto = Symbol ? Symbol.prototype : undefined, symbolValueOf = symbolProto ? symbolProto.valueOf : undefined; /** * Creates a hash object. * * @private * @constructor * @param {Array} [entries] The key-value pairs to cache. */ function Hash(entries) { var index = -1, length = entries == null ? 0 : entries.length; this.clear(); while (++index < length) { var entry = entries[index]; this.set(entry[0], entry[1]); } } /** * Removes all key-value entries from the hash. * * @private * @name clear * @memberOf Hash */ function hashClear() { this.__data__ = nativeCreate ? nativeCreate(null) : {}; this.size = 0; } /** * Removes `key` and its value from the hash. * * @private * @name delete * @memberOf Hash * @param {Object} hash The hash to modify. * @param {string} key The key of the value to remove. * @returns {boolean} Returns `true` if the entry was removed, else `false`. */ function hashDelete(key) { var result = this.has(key) && delete this.__data__[key]; this.size -= result ? 1 : 0; return result; } /** * Gets the hash value for `key`. * * @private * @name get * @memberOf Hash * @param {string} key The key of the value to get. * @returns {*} Returns the entry value. */ function hashGet(key) { var data = this.__data__; if (nativeCreate) { var result = data[key]; return result === HASH_UNDEFINED ? undefined : result; } return hasOwnProperty.call(data, key) ? data[key] : undefined; } /** * Checks if a hash value for `key` exists. * * @private * @name has * @memberOf Hash * @param {string} key The key of the entry to check. * @returns {boolean} Returns `true` if an entry for `key` exists, else `false`. */ function hashHas(key) { var data = this.__data__; return nativeCreate ? (data[key] !== undefined) : hasOwnProperty.call(data, key); } /** * Sets the hash `key` to `value`. * * @private * @name set * @memberOf Hash * @param {string} key The key of the value to set. * @param {*} value The value to set. * @returns {Object} Returns the hash instance. */ function hashSet(key, value) { var data = this.__data__; this.size += this.has(key) ? 0 : 1; data[key] = (nativeCreate && value === undefined) ? HASH_UNDEFINED : value; return this; } // Add methods to `Hash`. Hash.prototype.clear = hashClear; Hash.prototype['delete'] = hashDelete; Hash.prototype.get = hashGet; Hash.prototype.has = hashHas; Hash.prototype.set = hashSet; /** * Creates an list cache object. * * @private * @constructor * @param {Array} [entries] The key-value pairs to cache. */ function ListCache(entries) { var index = -1, length = entries == null ? 0 : entries.length; this.clear(); while (++index < length) { var entry = entries[index]; this.set(entry[0], entry[1]); } } /** * Removes all key-value entries from the list cache. * * @private * @name clear * @memberOf ListCache */ function listCacheClear() { this.__data__ = []; this.size = 0; } /** * Removes `key` and its value from the list cache. * * @private * @name delete * @memberOf ListCache * @param {string} key The key of the value to remove. * @returns {boolean} Returns `true` if the entry was removed, else `false`. */ function listCacheDelete(key) { var data = this.__data__, index = assocIndexOf(data, key); if (index < 0) { return false; } var lastIndex = data.length - 1; if (index == lastIndex) { data.pop(); } else { splice.call(data, index, 1); } --this.size; return true; } /** * Gets the list cache value for `key`. * * @private * @name get * @memberOf ListCache * @param {string} key The key of the value to get. * @returns {*} Returns the entry value. */ function listCacheGet(key) { var data = this.__data__, index = assocIndexOf(data, key); return index < 0 ? undefined : data[index][1]; } /** * Checks if a list cache value for `key` exists. * * @private * @name has * @memberOf ListCache * @param {string} key The key of the entry to check. * @returns {boolean} Returns `true` if an entry for `key` exists, else `false`. */ function listCacheHas(key) { return assocIndexOf(this.__data__, key) > -1; } /** * Sets the list cache `key` to `value`. * * @private * @name set * @memberOf ListCache * @param {string} key The key of the value to set. * @param {*} value The value to set. * @returns {Object} Returns the list cache instance. */ function listCacheSet(key, value) { var data = this.__data__, index = assocIndexOf(data, key); if (index < 0) { ++this.size; data.push([key, value]); } else { data[index][1] = value; } return this; } // Add methods to `ListCache`. ListCache.prototype.clear = listCacheClear; ListCache.prototype['delete'] = listCacheDelete; ListCache.prototype.get = listCacheGet; ListCache.prototype.has = listCacheHas; ListCache.prototype.set = listCacheSet; /** * Creates a map cache object to store key-value pairs. * * @private * @constructor * @param {Array} [entries] The key-value pairs to cache. */ function MapCache(entries) { var index = -1, length = entries == null ? 0 : entries.length; this.clear(); while (++index < length) { var entry = entries[index]; this.set(entry[0], entry[1]); } } /** * Removes all key-value entries from the map. * * @private * @name clear * @memberOf MapCache */ function mapCacheClear() { this.size = 0; this.__data__ = { 'hash': new Hash, 'map': new (Map || ListCache), 'string': new Hash }; } /** * Removes `key` and its value from the map. * * @private * @name delete * @memberOf MapCache * @param {string} key The key of the value to remove. * @returns {boolean} Returns `true` if the entry was removed, else `false`. */ function mapCacheDelete(key) { var result = getMapData(this, key)['delete'](key); this.size -= result ? 1 : 0; return result; } /** * Gets the map value for `key`. * * @private * @name get * @memberOf MapCache * @param {string} key The key of the value to get. * @returns {*} Returns the entry value. */ function mapCacheGet(key) { return getMapData(this, key).get(key); } /** * Checks if a map value for `key` exists. * * @private * @name has * @memberOf MapCache * @param {string} key The key of the entry to check. * @returns {boolean} Returns `true` if an entry for `key` exists, else `false`. */ function mapCacheHas(key) { return getMapData(this, key).has(key); } /** * Sets the map `key` to `value`. * * @private * @name set * @memberOf MapCache * @param {string} key The key of the value to set. * @param {*} value The value to set. * @returns {Object} Returns the map cache instance. */ function mapCacheSet(key, value) { var data = getMapData(this, key), size = data.size; data.set(key, value); this.size += data.size == size ? 0 : 1; return this; } // Add methods to `MapCache`. MapCache.prototype.clear = mapCacheClear; MapCache.prototype['delete'] = mapCacheDelete; MapCache.prototype.get = mapCacheGet; MapCache.prototype.has = mapCacheHas; MapCache.prototype.set = mapCacheSet; /** * * Creates an array cache object to store unique values. * * @private * @constructor * @param {Array} [values] The values to cache. */ function SetCache(values) { var index = -1, length = values == null ? 0 : values.length; this.__data__ = new MapCache; while (++index < length) { this.add(values[index]); } } /** * Adds `value` to the array cache. * * @private * @name add * @memberOf SetCache * @alias push * @param {*} value The value to cache. * @returns {Object} Returns the cache instance. */ function setCacheAdd(value) { this.__data__.set(value, HASH_UNDEFINED); return this; } /** * Checks if `value` is in the array cache. * * @private * @name has * @memberOf SetCache * @param {*} value The value to search for. * @returns {number} Returns `true` if `value` is found, else `false`. */ function setCacheHas(value) { return this.__data__.has(value); } // Add methods to `SetCache`. SetCache.prototype.add = SetCache.prototype.push = setCacheAdd; SetCache.prototype.has = setCacheHas; /** * Creates a stack cache object to store key-value pairs. * * @private * @constructor * @param {Array} [entries] The key-value pairs to cache. */ function Stack(entries) { var data = this.__data__ = new ListCache(entries); this.size = data.size; } /** * Removes all key-value entries from the stack. * * @private * @name clear * @memberOf Stack */ function stackClear() { this.__data__ = new ListCache; this.size = 0; } /** * Removes `key` and its value from the stack. * * @private * @name delete * @memberOf Stack * @param {string} key The key of the value to remove. * @returns {boolean} Returns `true` if the entry was removed, else `false`. */ function stackDelete(key) { var data = this.__data__, result = data['delete'](key); this.size = data.size; return result; } /** * Gets the stack value for `key`. * * @private * @name get * @memberOf Stack * @param {string} key The key of the value to get. * @returns {*} Returns the entry value. */ function stackGet(key) { return this.__data__.get(key); } /** * Checks if a stack value for `key` exists. * * @private * @name has * @memberOf Stack * @param {string} key The key of the entry to check. * @returns {boolean} Returns `true` if an entry for `key` exi