recoil
Version:
Recoil - A state management library for React
110 lines (99 loc) • 2.87 kB
Flow
/**
* 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.
*
* Implements (a subset of) the interface of built-in Map but supports arrays as
* keys. Two keys are equal if corresponding elements are equal according to the
* equality semantics of built-in Map. Operations are at worst O(n*b) where n is
* the array length and b is the complexity of the built-in operation.
*
* @emails oncall+recoil
* @flow
* @format
*/
;
const LEAF = {};
const emptyMap = new Map();
class ArrayKeyedMap<V> {
_base: Map<any, any> = new Map();
constructor(
// $FlowFixMe[incompatible-type]
existing?: ArrayKeyedMap<V> | Iterable<[mixed, V]>,
): ArrayKeyedMap<V> {
if (existing instanceof ArrayKeyedMap) {
for (const [k, v] of existing.entries()) {
this.set(k, v);
}
} else if (existing) {
for (const [k, v] of existing) {
this.set(k, v);
}
}
return this;
}
get(key: mixed): V | void {
const ks = Array.isArray(key) ? key : [key];
let map = this._base;
ks.forEach(k => {
map = map.get(k) ?? emptyMap;
});
return map === undefined ? undefined : map.get(LEAF);
}
set(key: mixed, value: V): any {
const ks = Array.isArray(key) ? key : [key];
let map: ?(any | Map<mixed, mixed> | Map<any, any>) = this._base;
let next: ?(any | Map<mixed, mixed> | Map<any, any>) = map;
ks.forEach(k => {
// $FlowFixMe[incompatible-use]
next = map.get(k);
if (!next) {
next = new Map();
// $FlowFixMe[incompatible-use]
map.set(k, next);
}
map = next;
});
// $FlowFixMe[incompatible-use]
next.set(LEAF, value);
return this;
}
delete(key: mixed): any {
const ks = Array.isArray(key) ? key : [key];
let map: ?(any | Map<mixed, mixed> | Map<any, any>) = this._base;
let next: ?(any | Map<mixed, mixed> | Map<any, any>) = map;
ks.forEach(k => {
// $FlowFixMe[incompatible-use]
next = map.get(k);
if (!next) {
next = new Map();
// $FlowFixMe[incompatible-use]
map.set(k, next);
}
map = next;
});
// $FlowFixMe[incompatible-use]
next.delete(LEAF);
// TODO We could cleanup empty maps
return this;
}
entries(): Iterator<[$ReadOnlyArray<mixed>, V]> {
const answer = [];
function recurse(level, prefix) {
level.forEach((v, k) => {
if (k === LEAF) {
answer.push([prefix, v]);
} else {
recurse(v, prefix.concat(k));
}
});
}
recurse(this._base, []);
return answer.values();
}
toBuiltInMap(): Map<$ReadOnlyArray<mixed>, V> {
return new Map(this.entries());
}
}
module.exports = {ArrayKeyedMap};