recoil
Version:
Recoil - A state management library for React
1,948 lines (1,556 loc) • 227 kB
JavaScript
import reactNative from 'react-native';
import react from 'react';
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @emails oncall+recoil
*
* @format
*/
// Split declaration and implementation to allow this function to pretend to
// check for actual instance of Promise instead of something with a `then`
// method.
// eslint-disable-next-line no-redeclare
function isPromise(p) {
return !!p && typeof p.then === 'function';
}
var Recoil_isPromise = isPromise;
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @emails oncall+recoil
*
* @format
*/
function nullthrows(x, message) {
if (x != null) {
return x;
}
throw new Error(message !== null && message !== void 0 ? message : 'Got unexpected null or undefined');
}
var Recoil_nullthrows = nullthrows;
// TODO Convert Loadable to a Class to allow for runtime type detection.
// Containing static factories of withValue(), withError(), withPromise(), and all()
class Canceled {}
const CANCELED = new Canceled();
const loadableAccessors = {
valueMaybe() {
return undefined;
},
valueOrThrow() {
const error = new Error(`Loadable expected value, but in "${this.state}" state`); // V8 keeps closures alive until stack is accessed, this prevents a memory leak
throw error;
},
errorMaybe() {
return undefined;
},
errorOrThrow() {
const error = new Error(`Loadable expected error, but in "${this.state}" state`); // V8 keeps closures alive until stack is accessed, this prevents a memory leak
throw error;
},
promiseMaybe() {
return undefined;
},
promiseOrThrow() {
const error = new Error(`Loadable expected promise, but in "${this.state}" state`); // V8 keeps closures alive until stack is accessed, this prevents a memory leak
throw error;
},
is(other) {
return other.state === this.state && other.contents === this.contents;
},
// TODO Unit tests
// TODO Convert Loadable to a Class to better support chaining
// by returning a Loadable from a map function
map(map) {
if (this.state === 'hasError') {
return this;
}
if (this.state === 'hasValue') {
try {
const next = map(this.contents); // TODO if next instanceof Loadable, then return next
return Recoil_isPromise(next) ? loadableWithPromise(next) : loadableWithValue(next);
} catch (e) {
return Recoil_isPromise(e) ? // If we "suspended", then try again.
// errors and subsequent retries will be handled in 'loading' case
loadableWithPromise(e.next(() => map(this.contents))) : loadableWithError(e);
}
}
if (this.state === 'loading') {
return loadableWithPromise(this.contents // TODO if map returns a loadable, then return the value or promise or throw the error
.then(map).catch(e => {
if (Recoil_isPromise(e)) {
// we were "suspended," try again
return e.then(() => map(this.contents));
}
throw e;
}));
}
const error = new Error('Invalid Loadable state'); // V8 keeps closures alive until stack is accessed, this prevents a memory leak
throw error;
}
};
function loadableWithValue(value) {
// Build objects this way since Flow doesn't support disjoint unions for class properties
return Object.freeze({
state: 'hasValue',
contents: value,
...loadableAccessors,
getValue() {
return this.contents;
},
toPromise() {
return Promise.resolve(this.contents);
},
valueMaybe() {
return this.contents;
},
valueOrThrow() {
return this.contents;
}
});
}
function loadableWithError(error) {
return Object.freeze({
state: 'hasError',
contents: error,
...loadableAccessors,
getValue() {
throw this.contents;
},
toPromise() {
return Promise.reject(this.contents);
},
errorMaybe() {
return this.contents;
},
errorOrThrow() {
return this.contents;
}
});
}
function loadableWithPromise(promise) {
return Object.freeze({
state: 'loading',
contents: promise,
...loadableAccessors,
getValue() {
throw this.contents.then(({
__value
}) => __value);
},
toPromise() {
return this.contents.then(({
__value
}) => __value);
},
promiseMaybe() {
return this.contents.then(({
__value
}) => __value);
},
promiseOrThrow() {
return this.contents.then(({
__value
}) => __value);
}
});
}
function loadableLoading() {
return loadableWithPromise(new Promise(() => {}));
}
function loadableAll(inputs) {
return inputs.every(i => i.state === 'hasValue') ? loadableWithValue(inputs.map(i => i.contents)) : inputs.some(i => i.state === 'hasError') ? loadableWithError(Recoil_nullthrows(inputs.find(i => i.state === 'hasError'), 'Invalid loadable passed to loadableAll').contents) : loadableWithPromise(Promise.all(inputs.map(i => i.contents)).then(value => ({
__value: value
})));
}
var Recoil_Loadable = {
loadableWithValue,
loadableWithError,
loadableWithPromise,
loadableLoading,
loadableAll,
Canceled,
CANCELED
};
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @emails oncall+recoil
*
* @format
*/
function recoverableViolation(message, projectName, {
error
} = {}) {
if (process.env.NODE_ENV !== "production") {
console.error(message, error);
}
return null;
}
var recoverableViolation_1 = recoverableViolation;
// @oss-only
var Recoil_recoverableViolation = recoverableViolation_1;
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* Utilities for working with built-in Maps and Sets without mutating them.
*
* @emails oncall+recoil
*
* @format
*/
function setByAddingToSet(set, v) {
const next = new Set(set);
next.add(v);
return next;
}
function setByDeletingFromSet(set, v) {
const next = new Set(set);
next.delete(v);
return next;
}
function mapBySettingInMap(map, k, v) {
const next = new Map(map);
next.set(k, v);
return next;
}
function mapByUpdatingInMap(map, k, updater) {
const next = new Map(map);
next.set(k, updater(next.get(k)));
return next;
}
function mapByDeletingFromMap(map, k) {
const next = new Map(map);
next.delete(k);
return next;
}
function mapByDeletingMultipleFromMap(map, ks) {
const next = new Map(map);
ks.forEach(k => next.delete(k));
return next;
}
var Recoil_CopyOnWrite = {
setByAddingToSet,
setByDeletingFromSet,
mapBySettingInMap,
mapByUpdatingInMap,
mapByDeletingFromMap,
mapByDeletingMultipleFromMap
};
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @emails oncall+recoil
*
* @format
*/
/**
* Creates a new iterable whose output is generated by passing the input
* iterable's values through the filter function.
*/
function* filterIterable(iterable, predicate) {
// Use generator to create iterable/iterator
let index = 0;
for (const value of iterable) {
if (predicate(value, index++)) {
yield value;
}
}
}
var Recoil_filterIterable = filterIterable;
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @emails oncall+recoil
*
* @format
*/
const gks = new Map().set('recoil_hamt_2020', true).set('recoil_memory_managament_2020', true);
function Recoil_gkx(gk) {
var _gks$get;
return (_gks$get = gks.get(gk)) !== null && _gks$get !== void 0 ? _gks$get : false;
}
Recoil_gkx.setPass = gk => {
gks.set(gk, true);
};
Recoil_gkx.setFail = gk => {
gks.set(gk, false);
};
var Recoil_gkx_1 = Recoil_gkx; // @oss-only
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @emails oncall+recoil
*
* @format
*/
/**
* Creates a new iterable whose output is generated by passing the input
* iterable's values through the mapper function.
*/
function mapIterable(iterable, callback) {
// Use generator to create iterable/iterator
return function* () {
let index = 0;
for (const value of iterable) {
yield callback(value, index++);
}
}();
}
var Recoil_mapIterable = mapIterable;
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @emails oncall+recoil
*
* @format
*/
function sprintf(format, ...args) {
let index = 0;
return format.replace(/%s/g, () => String(args[index++]));
}
var sprintf_1 = sprintf;
function expectationViolation(format, ...args) {
if (process.env.NODE_ENV !== "production") {
const message = sprintf_1.call(null, format, ...args);
const error = new Error(message);
error.name = 'Expectation Violation';
console.error(error);
}
}
var expectationViolation_1 = expectationViolation;
// @oss-only
var Recoil_expectationViolation = expectationViolation_1;
function _defineProperty(obj, key, value) {
if (key in obj) {
Object.defineProperty(obj, key, {
value: value,
enumerable: true,
configurable: true,
writable: true
});
} else {
obj[key] = value;
}
return obj;
}
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @emails oncall+recoil
*
* @format
*/
// eslint-disable-next-line no-unused-vars
class AbstractRecoilValue {
constructor(newKey) {
_defineProperty(this, "key", void 0);
this.key = newKey;
}
}
class RecoilState extends AbstractRecoilValue {}
class RecoilValueReadOnly extends AbstractRecoilValue {}
function isRecoilValue(x) {
return x instanceof RecoilState || x instanceof RecoilValueReadOnly;
}
var Recoil_RecoilValue = {
AbstractRecoilValue,
RecoilState,
RecoilValueReadOnly,
isRecoilValue
};
var Recoil_RecoilValue_1 = Recoil_RecoilValue.AbstractRecoilValue;
var Recoil_RecoilValue_2 = Recoil_RecoilValue.RecoilState;
var Recoil_RecoilValue_3 = Recoil_RecoilValue.RecoilValueReadOnly;
var Recoil_RecoilValue_4 = Recoil_RecoilValue.isRecoilValue;
var Recoil_RecoilValue$1 = /*#__PURE__*/Object.freeze({
__proto__: null,
AbstractRecoilValue: Recoil_RecoilValue_1,
RecoilState: Recoil_RecoilValue_2,
RecoilValueReadOnly: Recoil_RecoilValue_3,
isRecoilValue: Recoil_RecoilValue_4
});
class DefaultValue {}
const DEFAULT_VALUE = new DefaultValue();
class RecoilValueNotReady extends Error {
constructor(key) {
super(`Tried to set the value of Recoil selector ${key} using an updater function, but it is an async selector in a pending or error state; this is not supported.`);
}
}
// flowlint-next-line unclear-type:off
const nodes = new Map(); // flowlint-next-line unclear-type:off
const recoilValues = new Map();
/* eslint-disable no-redeclare */
function recoilValuesForKeys(keys) {
return Recoil_mapIterable(keys, key => Recoil_nullthrows(recoilValues.get(key)));
}
function registerNode(node) {
if (nodes.has(node.key)) {
const message = `Duplicate atom key "${node.key}". This is a FATAL ERROR in
production. But it is safe to ignore this warning if it occurred because of
hot module replacement.`; // TODO Need to figure out if there is a standard/open-source equivalent to see if hot module replacement is happening:
// prettier-ignore
// @fb-only: if (__DEV__) {
// @fb-only: const isAcceptingUpdate = require('__debug').isAcceptingUpdate;
// prettier-ignore
// @fb-only: if (typeof isAcceptingUpdate !== 'function' || !isAcceptingUpdate()) {
// @fb-only: expectationViolation(message, 'recoil');
// @fb-only: }
// prettier-ignore
// @fb-only: } else {
// @fb-only: recoverableViolation(message, 'recoil');
// @fb-only: }
console.warn(message); // @oss-only
}
nodes.set(node.key, node);
const recoilValue = node.set == null ? new Recoil_RecoilValue$1.RecoilValueReadOnly(node.key) : new Recoil_RecoilValue$1.RecoilState(node.key);
recoilValues.set(node.key, recoilValue);
return recoilValue;
}
/* eslint-enable no-redeclare */
class NodeMissingError extends Error {} // flowlint-next-line unclear-type:off
function getNode(key) {
const node = nodes.get(key);
if (node == null) {
throw new NodeMissingError(`Missing definition for RecoilValue: "${key}""`);
}
return node;
} // flowlint-next-line unclear-type:off
function getNodeMaybe(key) {
return nodes.get(key);
}
const configDeletionHandlers = new Map();
function deleteNodeConfigIfPossible(key) {
var _node$shouldDeleteCon;
if (!Recoil_gkx_1('recoil_memory_managament_2020')) {
return;
}
const node = nodes.get(key);
if (node === null || node === void 0 ? void 0 : (_node$shouldDeleteCon = node.shouldDeleteConfigOnRelease) === null || _node$shouldDeleteCon === void 0 ? void 0 : _node$shouldDeleteCon.call(node)) {
var _getConfigDeletionHan;
nodes.delete(key);
(_getConfigDeletionHan = getConfigDeletionHandler(key)) === null || _getConfigDeletionHan === void 0 ? void 0 : _getConfigDeletionHan();
configDeletionHandlers.delete(key);
}
}
function setConfigDeletionHandler(key, fn) {
if (!Recoil_gkx_1('recoil_memory_managament_2020')) {
return;
}
if (fn === undefined) {
configDeletionHandlers.delete(key);
} else {
configDeletionHandlers.set(key, fn);
}
}
function getConfigDeletionHandler(key) {
return configDeletionHandlers.get(key);
}
var Recoil_Node = {
nodes,
recoilValues,
registerNode,
getNode,
getNodeMaybe,
deleteNodeConfigIfPossible,
setConfigDeletionHandler,
getConfigDeletionHandler,
recoilValuesForKeys,
NodeMissingError,
DefaultValue,
DEFAULT_VALUE,
RecoilValueNotReady
};
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @emails oncall+recoil
*
* @format
*/
class RetentionZone {}
function retentionZone() {
return new RetentionZone();
}
var Recoil_RetentionZone = {
RetentionZone,
retentionZone
};
const {
setByAddingToSet: setByAddingToSet$1
} = Recoil_CopyOnWrite;
const {
getNode: getNode$1,
getNodeMaybe: getNodeMaybe$1,
recoilValuesForKeys: recoilValuesForKeys$1
} = Recoil_Node;
const {
RetentionZone: RetentionZone$1
} = Recoil_RetentionZone; // flowlint-next-line unclear-type:off
const emptySet = Object.freeze(new Set());
class ReadOnlyRecoilValueError extends Error {}
function initializeRetentionForNode(store, nodeKey, retainedBy) {
if (!Recoil_gkx_1('recoil_memory_managament_2020')) {
return () => undefined;
}
const {
nodesRetainedByZone
} = store.getState().retention;
function addToZone(zone) {
let set = nodesRetainedByZone.get(zone);
if (!set) {
nodesRetainedByZone.set(zone, set = new Set());
}
set.add(nodeKey);
}
if (retainedBy instanceof RetentionZone$1) {
addToZone(retainedBy);
} else if (Array.isArray(retainedBy)) {
for (const zone of retainedBy) {
addToZone(zone);
}
}
return () => {
if (!Recoil_gkx_1('recoil_memory_managament_2020')) {
return;
}
const nodesRetainedByZone = store.getState().retention.nodesRetainedByZone;
function deleteFromZone(zone) {
const set = nodesRetainedByZone.get(zone);
if (set) {
set.delete(nodeKey);
}
if (set && set.size === 0) {
nodesRetainedByZone.delete(zone);
}
}
if (retainedBy instanceof RetentionZone$1) {
deleteFromZone(retainedBy);
} else if (Array.isArray(retainedBy)) {
for (const zone of retainedBy) {
deleteFromZone(zone);
}
}
};
}
function initializeNodeIfNewToStore(store, treeState, key, trigger) {
const storeState = store.getState();
if (storeState.nodeCleanupFunctions.has(key)) {
return;
}
const config = getNode$1(key);
const retentionCleanup = initializeRetentionForNode(store, key, config.retainedBy);
const nodeCleanup = config.init(store, treeState, trigger);
storeState.nodeCleanupFunctions.set(key, () => {
nodeCleanup();
retentionCleanup();
});
}
function cleanUpNode(store, key) {
var _state$nodeCleanupFun;
const state = store.getState();
(_state$nodeCleanupFun = state.nodeCleanupFunctions.get(key)) === null || _state$nodeCleanupFun === void 0 ? void 0 : _state$nodeCleanupFun();
state.nodeCleanupFunctions.delete(key);
} // Get the current value loadable of a node and update the state.
// Update dependencies and subscriptions for selectors.
// Update saved value validation for atoms.
function getNodeLoadable(store, state, key) {
initializeNodeIfNewToStore(store, state, key, 'get');
return getNode$1(key).get(store, state);
} // Peek at the current value loadable for a node without any evaluation or state change
function peekNodeLoadable(store, state, key) {
return getNode$1(key).peek(store, state);
} // Write value directly to state bypassing the Node interface as the node
// definitions may not have been loaded yet when processing the initial snapshot.
function setUnvalidatedAtomValue_DEPRECATED(state, key, newValue) {
var _node$invalidate;
const node = getNodeMaybe$1(key);
node === null || node === void 0 ? void 0 : (_node$invalidate = node.invalidate) === null || _node$invalidate === void 0 ? void 0 : _node$invalidate.call(node, state);
return { ...state,
atomValues: state.atomValues.clone().delete(key),
nonvalidatedAtoms: state.nonvalidatedAtoms.clone().set(key, newValue),
dirtyAtoms: setByAddingToSet$1(state.dirtyAtoms, key)
};
} // Return the discovered dependencies and values to be written by setting
// a node value. (Multiple values may be written due to selectors getting to
// set upstreams; deps may be discovered because of reads in updater functions.)
function setNodeValue(store, state, key, newValue) {
const node = getNode$1(key);
if (node.set == null) {
throw new ReadOnlyRecoilValueError(`Attempt to set read-only RecoilValue: ${key}`);
}
const set = node.set; // so flow doesn't lose the above refinement.
initializeNodeIfNewToStore(store, state, key, 'set');
return set(store, state, newValue);
}
function peekNodeInfo(store, state, key) {
var _graph$nodeDeps$get, _storeState$nodeToCom, _storeState$nodeToCom2;
const storeState = store.getState();
const graph = store.getGraph(state.version);
const type = storeState.knownAtoms.has(key) ? 'atom' : storeState.knownSelectors.has(key) ? 'selector' : undefined;
const downstreamNodes = Recoil_filterIterable(getDownstreamNodes(store, state, new Set([key])), nodeKey => nodeKey !== key);
return {
loadable: peekNodeLoadable(store, state, key),
isActive: storeState.knownAtoms.has(key) || storeState.knownSelectors.has(key),
isSet: type === 'selector' ? false : state.atomValues.has(key),
isModified: state.dirtyAtoms.has(key),
type,
// Report current dependencies. If the node hasn't been evaluated, then
// dependencies may be missing based on the current state.
deps: recoilValuesForKeys$1((_graph$nodeDeps$get = graph.nodeDeps.get(key)) !== null && _graph$nodeDeps$get !== void 0 ? _graph$nodeDeps$get : []),
// Reportsall "current" subscribers. Evaluating other nodes or
// previous in-progress async evaluations may introduce new subscribers.
subscribers: {
nodes: recoilValuesForKeys$1(downstreamNodes),
components: Recoil_mapIterable((_storeState$nodeToCom = (_storeState$nodeToCom2 = storeState.nodeToComponentSubscriptions.get(key)) === null || _storeState$nodeToCom2 === void 0 ? void 0 : _storeState$nodeToCom2.values()) !== null && _storeState$nodeToCom !== void 0 ? _storeState$nodeToCom : [], ([name]) => ({
name
}))
}
};
} // Find all of the recursively dependent nodes
function getDownstreamNodes(store, state, keys) {
const visitedNodes = new Set();
const visitingNodes = Array.from(keys);
const graph = store.getGraph(state.version);
for (let key = visitingNodes.pop(); key; key = visitingNodes.pop()) {
var _graph$nodeToNodeSubs;
visitedNodes.add(key);
const subscribedNodes = (_graph$nodeToNodeSubs = graph.nodeToNodeSubscriptions.get(key)) !== null && _graph$nodeToNodeSubs !== void 0 ? _graph$nodeToNodeSubs : emptySet;
for (const downstreamNode of subscribedNodes) {
if (!visitedNodes.has(downstreamNode)) {
visitingNodes.push(downstreamNode);
}
}
}
return visitedNodes;
}
var Recoil_FunctionalCore = {
getNodeLoadable,
peekNodeLoadable,
setNodeValue,
cleanUpNode,
setUnvalidatedAtomValue_DEPRECATED,
peekNodeInfo,
getDownstreamNodes,
initializeNodeIfNewToStore
};
const {
CANCELED: CANCELED$1
} = Recoil_Loadable;
const {
getDownstreamNodes: getDownstreamNodes$1,
getNodeLoadable: getNodeLoadable$1,
setNodeValue: setNodeValue$1
} = Recoil_FunctionalCore;
const {
getNodeMaybe: getNodeMaybe$2
} = Recoil_Node;
const {
DefaultValue: DefaultValue$1,
RecoilValueNotReady: RecoilValueNotReady$1
} = Recoil_Node;
const {
AbstractRecoilValue: AbstractRecoilValue$1,
RecoilState: RecoilState$1,
RecoilValueReadOnly: RecoilValueReadOnly$1,
isRecoilValue: isRecoilValue$1
} = Recoil_RecoilValue$1;
function getRecoilValueAsLoadable(store, {
key
}, treeState = store.getState().currentTree) {
var _storeState$nextTree, _storeState$previousT;
// Reading from an older tree can cause bugs because the dependencies that we
// discover during the read are lost.
const storeState = store.getState();
if (!(treeState.version === storeState.currentTree.version || treeState.version === ((_storeState$nextTree = storeState.nextTree) === null || _storeState$nextTree === void 0 ? void 0 : _storeState$nextTree.version) || treeState.version === ((_storeState$previousT = storeState.previousTree) === null || _storeState$previousT === void 0 ? void 0 : _storeState$previousT.version))) {
Recoil_recoverableViolation('Tried to read from a discarded tree');
}
const loadable = getNodeLoadable$1(store, treeState, key);
if (loadable.state === 'loading') {
loadable.contents.catch(() => {
/**
* HACK: intercept thrown error here to prevent an uncaught promise exception. Ideally this would happen closer to selector
* execution (perhaps introducing a new ERROR class to be resolved by async selectors that are in an error state)
*/
return CANCELED$1;
});
}
return loadable;
}
function applyAtomValueWrites(atomValues, writes) {
const result = atomValues.clone();
writes.forEach((v, k) => {
if (v.state === 'hasValue' && v.contents instanceof DefaultValue$1) {
result.delete(k);
} else {
result.set(k, v);
}
});
return result;
}
function valueFromValueOrUpdater(store, state, {
key
}, valueOrUpdater) {
if (typeof valueOrUpdater === 'function') {
// Updater form: pass in the current value. Throw if the current value
// is unavailable (namely when updating an async selector that's
// pending or errored):
const current = getNodeLoadable$1(store, state, key);
if (current.state === 'loading') {
throw new RecoilValueNotReady$1(key);
} else if (current.state === 'hasError') {
throw current.contents;
} // T itself may be a function, so our refinement is not sufficient:
return valueOrUpdater(current.contents); // flowlint-line unclear-type:off
} else {
return valueOrUpdater;
}
}
function applyAction(store, state, action) {
if (action.type === 'set') {
const {
recoilValue,
valueOrUpdater
} = action;
const newValue = valueFromValueOrUpdater(store, state, recoilValue, valueOrUpdater);
const writes = setNodeValue$1(store, state, recoilValue.key, newValue);
for (const [key, loadable] of writes.entries()) {
writeLoadableToTreeState(state, key, loadable);
}
} else if (action.type === 'setLoadable') {
const {
recoilValue: {
key
},
loadable
} = action;
writeLoadableToTreeState(state, key, loadable);
} else if (action.type === 'markModified') {
const {
recoilValue: {
key
}
} = action;
state.dirtyAtoms.add(key);
} else if (action.type === 'setUnvalidated') {
var _node$invalidate;
// Write value directly to state bypassing the Node interface as the node
// definitions may not have been loaded yet when processing the initial snapshot.
const {
recoilValue: {
key
},
unvalidatedValue
} = action;
const node = getNodeMaybe$2(key);
node === null || node === void 0 ? void 0 : (_node$invalidate = node.invalidate) === null || _node$invalidate === void 0 ? void 0 : _node$invalidate.call(node, state);
state.atomValues.delete(key);
state.nonvalidatedAtoms.set(key, unvalidatedValue);
state.dirtyAtoms.add(key);
} else {
Recoil_recoverableViolation(`Unknown action ${action.type}`);
}
}
function writeLoadableToTreeState(state, key, loadable) {
if (loadable.state === 'hasValue' && loadable.contents instanceof DefaultValue$1) {
state.atomValues.delete(key);
} else {
state.atomValues.set(key, loadable);
}
state.dirtyAtoms.add(key);
state.nonvalidatedAtoms.delete(key);
}
function applyActionsToStore(store, actions) {
store.replaceState(state => {
const newState = copyTreeState(state);
for (const action of actions) {
applyAction(store, newState, action);
}
invalidateDownstreams(store, newState);
return newState;
});
}
function queueOrPerformStateUpdate(store, action) {
if (batchStack.length) {
const actionsByStore = batchStack[batchStack.length - 1];
let actions = actionsByStore.get(store);
if (!actions) {
actionsByStore.set(store, actions = []);
}
actions.push(action);
} else {
applyActionsToStore(store, [action]);
}
}
const batchStack = [];
function batchStart() {
const actionsByStore = new Map();
batchStack.push(actionsByStore);
return () => {
for (const [store, actions] of actionsByStore) {
applyActionsToStore(store, actions);
}
const popped = batchStack.pop();
if (popped !== actionsByStore) {
Recoil_recoverableViolation('Incorrect order of batch popping');
}
};
}
function copyTreeState(state) {
return { ...state,
atomValues: state.atomValues.clone(),
nonvalidatedAtoms: state.nonvalidatedAtoms.clone(),
dirtyAtoms: new Set(state.dirtyAtoms)
};
}
function invalidateDownstreams(store, state) {
// Inform any nodes that were changed or downstream of changes so that they
// can clear out any caches as needed due to the update:
const downstreams = getDownstreamNodes$1(store, state, state.dirtyAtoms);
for (const key of downstreams) {
var _getNodeMaybe, _getNodeMaybe$invalid;
(_getNodeMaybe = getNodeMaybe$2(key)) === null || _getNodeMaybe === void 0 ? void 0 : (_getNodeMaybe$invalid = _getNodeMaybe.invalidate) === null || _getNodeMaybe$invalid === void 0 ? void 0 : _getNodeMaybe$invalid.call(_getNodeMaybe, state);
}
}
function setRecoilValue(store, recoilValue, valueOrUpdater) {
queueOrPerformStateUpdate(store, {
type: 'set',
recoilValue,
valueOrUpdater
});
}
function setRecoilValueLoadable(store, recoilValue, loadable) {
if (loadable instanceof DefaultValue$1) {
return setRecoilValue(store, recoilValue, loadable);
}
queueOrPerformStateUpdate(store, {
type: 'setLoadable',
recoilValue,
loadable
});
}
function markRecoilValueModified(store, recoilValue) {
queueOrPerformStateUpdate(store, {
type: 'markModified',
recoilValue
});
}
function setUnvalidatedRecoilValue(store, recoilValue, unvalidatedValue) {
queueOrPerformStateUpdate(store, {
type: 'setUnvalidated',
recoilValue,
unvalidatedValue
});
}
let subscriptionID = 0;
function subscribeToRecoilValue(store, {
key
}, callback, componentDebugName = null) {
const subID = subscriptionID++;
const storeState = store.getState();
if (!storeState.nodeToComponentSubscriptions.has(key)) {
storeState.nodeToComponentSubscriptions.set(key, new Map());
}
Recoil_nullthrows(storeState.nodeToComponentSubscriptions.get(key)).set(subID, [componentDebugName !== null && componentDebugName !== void 0 ? componentDebugName : '<not captured>', callback]);
return {
release: () => {
const storeState = store.getState();
const subs = storeState.nodeToComponentSubscriptions.get(key);
if (subs === undefined || !subs.has(subID)) {
Recoil_recoverableViolation(`Subscription missing at release time for atom ${key}. This is a bug in Recoil.`);
return;
}
subs.delete(subID);
if (subs.size === 0) {
storeState.nodeToComponentSubscriptions.delete(key);
}
}
};
}
var Recoil_RecoilValueInterface = {
RecoilValueReadOnly: RecoilValueReadOnly$1,
AbstractRecoilValue: AbstractRecoilValue$1,
RecoilState: RecoilState$1,
getRecoilValueAsLoadable,
setRecoilValue,
setRecoilValueLoadable,
markRecoilValueModified,
setUnvalidatedRecoilValue,
subscribeToRecoilValue,
isRecoilValue: isRecoilValue$1,
applyAtomValueWrites,
// TODO Remove export when deprecating initialStoreState_DEPRECATED in RecoilRoot
batchStart,
invalidateDownstreams_FOR_TESTING: invalidateDownstreams
};
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @emails oncall+recoil
*
* @format
*
* This is to export esstiential functions from react-dom
* for our react-native build
*/
// $FlowExpectedError[cannot-resolve-module]
const {
unstable_batchedUpdates
} = reactNative;
var ReactBatchedUpdates_native = {
unstable_batchedUpdates
};
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @emails oncall+recoil
*
* @format
*
* This is to export esstiential functions from react-dom
* for our web build
*/
// @fb-only: const {unstable_batchedUpdates} = require('ReactDOMComet');
const {
unstable_batchedUpdates: unstable_batchedUpdates$1
} = ReactBatchedUpdates_native; // @oss-only
var Recoil_ReactBatchedUpdates = {
unstable_batchedUpdates: unstable_batchedUpdates$1
};
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @emails oncall+recoil
*
* @format
*/
const {
batchStart: batchStart$1
} = Recoil_RecoilValueInterface;
const {
unstable_batchedUpdates: unstable_batchedUpdates$2
} = Recoil_ReactBatchedUpdates;
let batcher = unstable_batchedUpdates$2; // flowlint-next-line unclear-type:off
/**
* Sets the provided batcher function as the batcher function used by Recoil.
*
* Set the batcher to a custom batcher for your renderer,
* if you use a renderer other than React DOM or React Native.
*/
const setBatcher = newBatcher => {
batcher = newBatcher;
};
/**
* Returns the current batcher function.
*/
const getBatcher = () => batcher;
/**
* Calls the current batcher function and passes the
* provided callback function.
*/
const batchUpdates = callback => {
batcher(() => {
let batchEnd = () => undefined;
try {
batchEnd = batchStart$1();
callback();
} finally {
batchEnd();
}
});
};
var Recoil_Batching = {
getBatcher,
setBatcher,
batchUpdates
};
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @emails oncall+recoil
*
* @format
*/
function enqueueExecution(s, f) {
f();
}
var Recoil_Queue = {
enqueueExecution
};
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @emails oncall+recoil
*
* @format
*/
/**
* Returns a set containing all of the values from the first set that are not
* present in any of the subsequent sets.
*
* Note: this is written procedurally (i.e., without filterSet) for performant
* use in tight loops.
*/
function differenceSets(set, ...setsWithValuesToRemove) {
const ret = new Set();
FIRST: for (const value of set) {
for (const otherSet of setsWithValuesToRemove) {
if (otherSet.has(value)) {
continue FIRST;
}
}
ret.add(value);
}
return ret;
}
var Recoil_differenceSets = differenceSets;
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @emails oncall+recoil
*
* @format
*/
/**
* Returns a new Map object with the same keys as the original, but with the
* values replaced with the output of the given callback function.
*/
function mapMap(map, callback) {
const result = new Map();
map.forEach((value, key) => {
result.set(key, callback(value, key));
});
return result;
}
var Recoil_mapMap = mapMap;
function graph() {
return {
nodeDeps: new Map(),
nodeToNodeSubscriptions: new Map()
};
}
function cloneGraph(graph) {
return {
nodeDeps: Recoil_mapMap(graph.nodeDeps, s => new Set(s)),
nodeToNodeSubscriptions: Recoil_mapMap(graph.nodeToNodeSubscriptions, s => new Set(s))
};
} // Note that this overwrites the deps of existing nodes, rather than unioning
// the new deps with the old deps.
function mergeDependencyMapIntoGraph(deps, graph, // If olderGraph is given then we will not overwrite changes made to the given
// graph compared with olderGraph:
olderGraph) {
const {
nodeDeps,
nodeToNodeSubscriptions
} = graph;
deps.forEach((upstreams, downstream) => {
const existingUpstreams = nodeDeps.get(downstream);
if (existingUpstreams && olderGraph && existingUpstreams !== olderGraph.nodeDeps.get(downstream)) {
return;
} // Update nodeDeps:
nodeDeps.set(downstream, new Set(upstreams)); // Add new deps to nodeToNodeSubscriptions:
const addedUpstreams = existingUpstreams == null ? upstreams : Recoil_differenceSets(upstreams, existingUpstreams);
addedUpstreams.forEach(upstream => {
if (!nodeToNodeSubscriptions.has(upstream)) {
nodeToNodeSubscriptions.set(upstream, new Set());
}
const existing = Recoil_nullthrows(nodeToNodeSubscriptions.get(upstream));
existing.add(downstream);
}); // Remove removed deps from nodeToNodeSubscriptions:
if (existingUpstreams) {
const removedUpstreams = Recoil_differenceSets(existingUpstreams, upstreams);
removedUpstreams.forEach(upstream => {
if (!nodeToNodeSubscriptions.has(upstream)) {
return;
}
const existing = Recoil_nullthrows(nodeToNodeSubscriptions.get(upstream));
existing.delete(downstream);
if (existing.size === 0) {
nodeToNodeSubscriptions.delete(upstream);
}
});
}
});
}
function saveDependencyMapToStore(dependencyMap, store, version) {
var _storeState$nextTree, _storeState$previousT, _storeState$previousT2, _storeState$previousT3;
const storeState = store.getState();
if (!(version === storeState.currentTree.version || version === ((_storeState$nextTree = storeState.nextTree) === null || _storeState$nextTree === void 0 ? void 0 : _storeState$nextTree.version) || version === ((_storeState$previousT = storeState.previousTree) === null || _storeState$previousT === void 0 ? void 0 : _storeState$previousT.version))) {
Recoil_recoverableViolation('Tried to save dependencies to a discarded tree');
} // Merge the dependencies discovered into the store's dependency map
// for the version that was read:
const graph = store.getGraph(version);
mergeDependencyMapIntoGraph(dependencyMap, graph); // If this version is not the latest version, also write these dependencies
// into later versions if they don't already have their own:
if (version === ((_storeState$previousT2 = storeState.previousTree) === null || _storeState$previousT2 === void 0 ? void 0 : _storeState$previousT2.version)) {
const currentGraph = store.getGraph(storeState.currentTree.version);
mergeDependencyMapIntoGraph(dependencyMap, currentGraph, graph);
}
if (version === ((_storeState$previousT3 = storeState.previousTree) === null || _storeState$previousT3 === void 0 ? void 0 : _storeState$previousT3.version) || version === storeState.currentTree.version) {
var _storeState$nextTree2;
const nextVersion = (_storeState$nextTree2 = storeState.nextTree) === null || _storeState$nextTree2 === void 0 ? void 0 : _storeState$nextTree2.version;
if (nextVersion !== undefined) {
const nextGraph = store.getGraph(nextVersion);
mergeDependencyMapIntoGraph(dependencyMap, nextGraph, graph);
}
}
}
function mergeDepsIntoDependencyMap(from, into) {
from.forEach((upstreamDeps, downstreamNode) => {
if (!into.has(downstreamNode)) {
into.set(downstreamNode, new Set());
}
const deps = Recoil_nullthrows(into.get(downstreamNode));
upstreamDeps.forEach(dep => deps.add(dep));
});
}
function addToDependencyMap(downstream, upstream, dependencyMap) {
if (!dependencyMap.has(downstream)) {
dependencyMap.set(downstream, new Set());
}
Recoil_nullthrows(dependencyMap.get(downstream)).add(upstream);
}
var Recoil_Graph = {
addToDependencyMap,
cloneGraph,
graph,
mergeDepsIntoDependencyMap,
saveDependencyMapToStore
};
function createCommonjsModule(fn, module) {
return module = { exports: {} }, fn(module, module.exports), module.exports;
}
var hamt_1 = createCommonjsModule(function (module) {
var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) {
return typeof obj;
} : function (obj) {
return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj;
};
/**
@fileOverview Hash Array Mapped Trie.
Code based on: https://github.com/exclipy/pdata
*/
var hamt = {}; // export
/* Configuration
******************************************************************************/
var SIZE = 5;
var BUCKET_SIZE = Math.pow(2, SIZE);
var MASK = BUCKET_SIZE - 1;
var MAX_INDEX_NODE = BUCKET_SIZE / 2;
var MIN_ARRAY_NODE = BUCKET_SIZE / 4;
/*
******************************************************************************/
var nothing = {};
var constant = function constant(x) {
return function () {
return x;
};
};
/**
Get 32 bit hash of string.
Based on:
http://stackoverflow.com/questions/7616461/generate-a-hash-from-string-in-javascript-jquery
*/
var hash = hamt.hash = function (str) {
var type = typeof str === 'undefined' ? 'undefined' : _typeof(str);
if (type === 'number') return str;
if (type !== 'string') str += '';
var hash = 0;
for (var i = 0, len = str.length; i < len; ++i) {
var c = str.charCodeAt(i);
hash = (hash << 5) - hash + c | 0;
}
return hash;
};
/* Bit Ops
******************************************************************************/
/**
Hamming weight.
Taken from: http://jsperf.com/hamming-weight
*/
var popcount = function popcount(x) {
x -= x >> 1 & 0x55555555;
x = (x & 0x33333333) + (x >> 2 & 0x33333333);
x = x + (x >> 4) & 0x0f0f0f0f;
x += x >> 8;
x += x >> 16;
return x & 0x7f;
};
var hashFragment = function hashFragment(shift, h) {
return h >>> shift & MASK;
};
var toBitmap = function toBitmap(x) {
return 1 << x;
};
var fromBitmap = function fromBitmap(bitmap, bit) {
return popcount(bitmap & bit - 1);
};
/* Array Ops
******************************************************************************/
/**
Set a value in an array.
@param mutate Should the input array be mutated?
@param at Index to change.
@param v New value
@param arr Array.
*/
var arrayUpdate = function arrayUpdate(mutate, at, v, arr) {
var out = arr;
if (!mutate) {
var len = arr.length;
out = new Array(len);
for (var i = 0; i < len; ++i) {
out[i] = arr[i];
}
}
out[at] = v;
return out;
};
/**
Remove a value from an array.
@param mutate Should the input array be mutated?
@param at Index to remove.
@param arr Array.
*/
var arraySpliceOut = function arraySpliceOut(mutate, at, arr) {
var newLen = arr.length - 1;
var i = 0;
var g = 0;
var out = arr;
if (mutate) {
i = g = at;
} else {
out = new Array(newLen);
while (i < at) {
out[g++] = arr[i++];
}
}
++i;
while (i <= newLen) {
out[g++] = arr[i++];
}
if (mutate) {
out.length = newLen;
}
return out;
};
/**
Insert a value into an array.
@param mutate Should the input array be mutated?
@param at Index to insert at.
@param v Value to insert,
@param arr Array.
*/
var arraySpliceIn = function arraySpliceIn(mutate, at, v, arr) {
var len = arr.length;
if (mutate) {
var _i = len;
while (_i >= at) {
arr[_i--] = arr[_i];
}
arr[at] = v;
return arr;
}
var i = 0,
g = 0;
var out = new Array(len + 1);
while (i < at) {
out[g++] = arr[i++];
}
out[at] = v;
while (i < len) {
out[++g] = arr[i++];
}
return out;
};
/* Node Structures
******************************************************************************/
var LEAF = 1;
var COLLISION = 2;
var INDEX = 3;
var ARRAY = 4;
/**
Empty node.
*/
var empty = {
__hamt_isEmpty: true
};
var isEmptyNode = function isEmptyNode(x) {
return x === empty || x && x.__hamt_isEmpty;
};
/**
Leaf holding a value.
@member edit Edit of the node.
@member hash Hash of key.
@member key Key.
@member value Value stored.
*/
var Leaf = function Leaf(edit, hash, key, value) {
return {
type: LEAF,
edit: edit,
hash: hash,
key: key,
value: value,
_modify: Leaf__modify
};
};
/**
Leaf holding multiple values with the same hash but different keys.
@member edit Edit of the node.
@member hash Hash of key.
@member children Array of collision children node.
*/
var Collision = function Collision(edit, hash, children) {
return {
type: COLLISION,
edit: edit,
hash: hash,
children: children,
_modify: Collision__modify
};
};
/**
Internal node with a sparse set of children.
Uses a bitmap and array to pack children.
@member edit Edit of the node.
@member mask Bitmap that encode the positions of children in the array.
@member children Array of child nodes.
*/
var IndexedNode = function IndexedNode(edit, mask, children) {
return {
type: INDEX,
edit: edit,
mask: mask,
children: children,
_modify: IndexedNode__modify
};
};
/**
Internal node with many children.
@member edit Edit of the node.
@member size Number of children.
@member children Array of child nodes.
*/
var ArrayNode = function ArrayNode(edit, size, children) {
return {
type: ARRAY,
edit: edit,
size: size,
children: children,
_modify: ArrayNode__modify
};
};
/**
Is `node` a leaf node?
*/
var isLeaf = function isLeaf(node) {
return node === empty || node.type === LEAF || node.type === COLLISION;
};
/* Internal node operations.
******************************************************************************/
/**
Expand an indexed node into an array node.
@param edit Current edit.
@param frag Index of added child.
@param child Added child.
@param mask Index node mask before child added.
@param subNodes Index node children before child added.
*/
var expand = function expand(edit, frag, child, bitmap, subNodes) {
var arr = [];
var bit = bitmap;
var count = 0;
for (var i = 0; bit; ++i) {
if (bit & 1) arr[i] = subNodes[count++];
bit >>>= 1;
}
arr[frag] = child;
return ArrayNode(edit, count + 1, arr);
};
/**
Collapse an array node into a indexed node.
@param edit Current edit.
@param count Number of elements in new array.
@param removed Index of removed element.
@param elements Array node children before remove.
*/
var pack = function pack(edit, count, removed, elements) {
var children = new Array(count - 1);
var g = 0;
var bitmap = 0;
for (var i = 0, len = elements.length; i < len; ++i) {
if (i !== removed) {
var elem = elements[i];
if (elem && !isEmptyNode(elem)) {
children[g++] = elem;
bitmap |= 1 << i;
}
}
}
return IndexedNode(edit, bitmap, children);
};
/**
Merge two leaf nodes.
@param shift Current shift.
@param h1 Node 1 hash.
@param n1 Node 1.
@param h2 Node 2 hash.
@param n2 Node 2.
*/
var mergeLeaves = function mergeLeaves(edit, shift, h1, n1, h2, n2) {
if (h1 === h2) return Collision(edit, h1, [n2, n1]);
var subH1 = hashFragment(shift, h1);
var subH2 = hashFragment(shift, h2);
return IndexedNode(edit, toBitmap(subH1) | toBitmap(subH2), subH1 === subH2 ? [mergeLeaves(edit, shift + SIZE, h1, n1, h2, n2)] : subH1 < subH2 ? [n1, n2] : [n2, n1]);
};
/**
Update an entry in a collision list.
@param mutate Should mutation be used?
@param edit Current edit.
@param keyEq Key compare function.
@param hash Hash of collision.
@param list Collision list.
@param f Update function.
@param k Key to update.
@param size Size ref.
*/
var updateCollisionList = function updateCollisionList(mutate, edit, keyEq, h, list, f, k, size) {
var len = list.length;
for (var i = 0; i < len; ++i) {
var child = list[i];
if (keyEq(k, child.key)) {
var value = child.value;
var _newValue = f(value);
if (_newValue === value) return list;
if (_newValue === nothing) {
--size.value;
return arraySpliceOut(mutate, i, list);
}
return arrayUpdate(mutate, i, Leaf(edit, h, k, _newValue), list);
}
}
var newValue = f();
if (newValue === nothing) return list;
++size.value;
return arrayUpdate(mutate, len, Leaf(edit, h, k, newValue), list);
};
var canEditNode = function canEditNode(edit, node) {
return edit === node.edit;
};
/* Editing
******************************************************************************/
var Leaf__modify = function Leaf__modify(edit, keyEq, shift, f, h, k, size) {
if (keyEq(k, this.key)) {
var _v = f(this.value);
if (_v === this.value) return this;else if (_v === nothing) {
--size.value;
return empty;
}
if (canEditNode(edit, this)) {
this.value = _v;
return this;
}
return Leaf(edit, h, k, _v);
}
var v = f();
if (v === nothing) return this;
++size.value;
return mergeLeaves(edit, shift, this.hash, this, h, Leaf(edit, h, k, v));
};
var Collision__modify = function Collision__modify(edit, keyEq, shift, f, h, k, size) {
if (h === this.hash) {
var canEdit = canEditNode(edit, this);
var list = updateCollisionList(canEdit, edit, keyEq, this.hash, this.children, f, k, size);
if (list === this.children) return this;
return list.length > 1 ? Collision(edit, this.hash, list) : list[0]; // collapse single element collision list
}
var v = f();
if (v === nothing) return this;
++size.value;
return mergeLeaves(edit, shift, this.hash, this, h, Leaf(edit, h, k, v));
};
var IndexedNode__modify = function IndexedNode__modify(edit, keyEq, shift, f, h, k, size) {
var mask = this.mask;
var children = this.children;
var frag = hashFragment(shift, h);
var bit = toBitmap(frag);
var indx = fromBitmap(mask, bit);
var exists = mask & bit;
var current = exists ? children[indx] : empty;
var child = current._modify(edit, keyEq, shift + SIZE, f, h, k, size);
if (current === child) return this;
var canEdit = canEditNode(edit, this);
var bitmap = mask;
var newChildren = void 0;
if (exists && isEmptyNode(child)) {
// remove
bitmap &= ~bit;
if (!bitmap) return empty;
if (children.length <= 2 && isLeaf(children[indx ^ 1])) return children[indx ^ 1]; // collapse
newChildren = arraySpliceOut(canEdit, indx, children);
} else if (!exists && !isEmptyNode(child)) {
// add
if (children.length >= MAX_INDEX_NODE) return expand(edit, frag, child, mask, children);
bitmap |= bit;
newChildren = arraySpliceIn(canEdit, indx, child, children);
} else {
// modify
newChildren = arrayUpdate(canEdit, indx, child, children);
}
if (canEdit) {
this.mask = bitmap;
this.children = newChildren;
return this;
}
return IndexedNode(edit, bitmap, newChildren);
};
var ArrayNode__modify = function ArrayN