@realm/react
Version:
React specific hooks and implementation helpers for Realm
1,384 lines (1,287 loc) • 96.7 kB
JavaScript
'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(null);
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(null);
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 `