Version 1.0.2 mit node_modules Verzeichnis

This commit is contained in:
ISA
2024-10-02 07:58:24 +02:00
parent f353a06b1b
commit 62b6e55a0a
68228 changed files with 4548477 additions and 651 deletions

View File

@@ -0,0 +1,19 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow strict
* @format
* @oncall recoil
*/
'use strict';
export interface CacheImplementation<K, V> {
get(K): ?V;
set(K, V): void;
delete(K): void;
clear(): void;
size(): number;
}

View File

@@ -0,0 +1,22 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow strict
* @format
* @oncall recoil
*/
'use strict';
export type EqualityPolicy = 'reference' | 'value';
export type EvictionPolicy = 'lru' | 'keep-all' | 'most-recent';
export type CachePolicy =
| {eviction: 'lru', maxSize: number, equality?: EqualityPolicy}
| {eviction: 'keep-all', equality?: EqualityPolicy}
| {eviction: 'most-recent', equality?: EqualityPolicy}
| {equality: EqualityPolicy};
export type CachePolicyWithoutEviction = {equality: EqualityPolicy};

159
node_modules/recoil/cjs/caches/Recoil_LRUCache.js.flow generated vendored Normal file
View File

@@ -0,0 +1,159 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow strict
* @format
* @oncall recoil
*/
'use strict';
const nullthrows = require('recoil-shared/util/Recoil_nullthrows');
type CacheNode<K, V> = {
key: K,
value: V,
left: ?CacheNode<K, V>,
right: ?CacheNode<K, V>,
};
type Options<K> = {
maxSize: number,
mapKey?: K => mixed,
};
class LRUCache<K = mixed, V = mixed> {
_maxSize: number;
_size: number;
_head: ?CacheNode<K, V>;
_tail: ?CacheNode<K, V>;
_map: Map<mixed, CacheNode<K, V>>;
_keyMapper: K => mixed;
constructor(options: Options<K>) {
this._maxSize = options.maxSize;
this._size = 0;
this._head = null;
this._tail = null;
this._map = new Map<mixed, CacheNode<K, V>>();
this._keyMapper = options.mapKey ?? (v => v);
}
head(): ?CacheNode<K, V> {
return this._head;
}
tail(): ?CacheNode<K, V> {
return this._tail;
}
size(): number {
return this._size;
}
maxSize(): number {
return this._maxSize;
}
has(key: K): boolean {
return this._map.has(this._keyMapper(key));
}
get(key: K): ?V {
const mappedKey = this._keyMapper(key);
const node = this._map.get(mappedKey);
if (!node) {
return undefined;
}
this.set(key, node.value);
return node.value;
}
set(key: K, val: V): void {
const mappedKey = this._keyMapper(key);
const existingNode = this._map.get(mappedKey);
if (existingNode) {
this.delete(key);
}
const head = this.head();
const node = {
key,
right: head,
left: null,
value: val,
};
if (head) {
head.left = node;
} else {
this._tail = node;
}
this._map.set(mappedKey, node);
this._head = node;
this._size++;
this._maybeDeleteLRU();
}
_maybeDeleteLRU() {
if (this.size() > this.maxSize()) {
this.deleteLru();
}
}
deleteLru(): void {
const tail = this.tail();
if (tail) {
this.delete(tail.key);
}
}
delete(key: K): void {
const mappedKey = this._keyMapper(key);
if (!this._size || !this._map.has(mappedKey)) {
return;
}
const node = nullthrows(this._map.get(mappedKey));
const right = node.right;
const left = node.left;
if (right) {
right.left = node.left;
}
if (left) {
left.right = node.right;
}
if (node === this.head()) {
this._head = right;
}
if (node === this.tail()) {
this._tail = left;
}
this._map.delete(mappedKey);
this._size--;
}
clear(): void {
this._size = 0;
this._head = null;
this._tail = null;
this._map = new Map<mixed, CacheNode<K, V>>();
}
}
module.exports = {LRUCache};

51
node_modules/recoil/cjs/caches/Recoil_MapCache.js.flow generated vendored Normal file
View File

@@ -0,0 +1,51 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow strict
* @format
* @oncall recoil
*/
'use strict';
type Options<K> = {
mapKey: K => mixed,
};
class MapCache<K, V> {
_map: Map<mixed, V>;
_keyMapper: K => mixed;
constructor(options?: Options<K>) {
this._map = new Map<mixed, V>();
this._keyMapper = options?.mapKey ?? (v => v);
}
size(): number {
return this._map.size;
}
has(key: K): boolean {
return this._map.has(this._keyMapper(key));
}
get(key: K): ?V {
return this._map.get(this._keyMapper(key));
}
set(key: K, val: V): void {
this._map.set(this._keyMapper(key), val);
}
delete(key: K): void {
this._map.delete(this._keyMapper(key));
}
clear(): void {
this._map.clear();
}
}
module.exports = {MapCache};

252
node_modules/recoil/cjs/caches/Recoil_TreeCache.js.flow generated vendored Normal file
View File

@@ -0,0 +1,252 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow strict-local
* @format
* @oncall recoil
*/
'use strict';
import type {
GetHandlers,
NodeCacheRoute,
NodeValueGet,
SetHandlers,
TreeCacheBranch,
TreeCacheLeaf,
TreeCacheNode,
} from './Recoil_TreeCacheImplementationType';
const {isFastRefreshEnabled} = require('../core/Recoil_ReactMode');
const recoverableViolation = require('recoil-shared/util/Recoil_recoverableViolation');
export type Options<T> = {
name?: string,
mapNodeValue?: (value: mixed) => mixed,
onHit?: (node: TreeCacheLeaf<T>) => void,
onSet?: (node: TreeCacheLeaf<T>) => void,
};
class ChangedPathError extends Error {}
class TreeCache<T = mixed> {
_name: ?string;
_numLeafs: number;
// $FlowIssue[unclear-type]
_root: TreeCacheNode<any> | null;
_onHit: $NonMaybeType<Options<T>['onHit']>;
_onSet: $NonMaybeType<Options<T>['onSet']>;
_mapNodeValue: $NonMaybeType<Options<T>['mapNodeValue']>;
constructor(options?: Options<T>) {
this._name = options?.name;
this._numLeafs = 0;
this._root = null;
this._onHit = options?.onHit ?? (() => {});
this._onSet = options?.onSet ?? (() => {});
this._mapNodeValue = options?.mapNodeValue ?? (val => val);
}
size(): number {
return this._numLeafs;
}
// $FlowIssue[unclear-type]
root(): TreeCacheNode<any> | null {
return this._root;
}
get(getNodeValue: NodeValueGet, handlers?: GetHandlers<T>): ?T {
return this.getLeafNode(getNodeValue, handlers)?.value;
}
getLeafNode(
getNodeValue: NodeValueGet,
handlers?: GetHandlers<T>,
): ?TreeCacheLeaf<T> {
if (this._root == null) {
return undefined;
}
// Iterate down the tree based on the current node values until we hit a leaf
// $FlowIssue[unclear-type]
let node: ?TreeCacheNode<any> = this._root;
while (node) {
handlers?.onNodeVisit(node);
if (node.type === 'leaf') {
this._onHit(node);
return node;
}
const nodeValue = this._mapNodeValue(getNodeValue(node.nodeKey));
node = node.branches.get(nodeValue);
}
return undefined;
}
set(route: NodeCacheRoute, value: T, handlers?: SetHandlers<T>): void {
const addLeaf = () => {
// First, setup the branch nodes for the route:
// Iterate down the tree to find or add branch nodes following the route
let node: ?TreeCacheBranch<T>;
let branchKey;
for (const [nodeKey, nodeValue] of route) {
// If the previous root was a leaf, while we not have a get(), it means
// the selector has inconsistent values or implementation changed.
const root = this._root;
if (root?.type === 'leaf') {
throw this.invalidCacheError();
}
// node now refers to the next node down in the tree
const parent = node;
// $FlowFixMe[prop-missing]
// $FlowFixMe[incompatible-type]
node = parent ? parent.branches.get(branchKey) : root;
// $FlowFixMe[prop-missing]
// $FlowFixMe[incompatible-type]
node = node ?? {
type: 'branch',
nodeKey,
parent,
branches: new Map(),
branchKey,
};
// If we found an existing node, confirm it has a consistent value
if (node.type !== 'branch' || node.nodeKey !== nodeKey) {
throw this.invalidCacheError();
}
// Add the branch node to the tree
parent?.branches.set(branchKey, node);
handlers?.onNodeVisit?.(node);
// Prepare for next iteration and install root if it is new.
branchKey = this._mapNodeValue(nodeValue);
this._root = this._root ?? node;
}
// Second, setup the leaf node:
// If there is an existing leaf for this route confirm it is consistent
const oldLeaf: ?TreeCacheNode<T> = node
? node?.branches.get(branchKey)
: this._root;
if (
oldLeaf != null &&
(oldLeaf.type !== 'leaf' || oldLeaf.branchKey !== branchKey)
) {
throw this.invalidCacheError();
}
// Create a new or replacement leaf.
const leafNode = {
type: 'leaf',
value,
parent: node,
branchKey,
};
// Install the leaf and call handlers
node?.branches.set(branchKey, leafNode);
this._root = this._root ?? leafNode;
this._numLeafs++;
this._onSet(leafNode);
handlers?.onNodeVisit?.(leafNode);
};
try {
addLeaf();
} catch (error) {
// If the cache was stale or observed inconsistent values, such as with
// Fast Refresh, then clear it and rebuild with the new values.
if (error instanceof ChangedPathError) {
this.clear();
addLeaf();
} else {
throw error;
}
}
}
// Returns true if leaf was actually deleted from the tree
delete(leaf: TreeCacheLeaf<T>): boolean {
const root = this.root();
if (!root) {
return false;
}
if (leaf === root) {
this._root = null;
this._numLeafs = 0;
return true;
}
// Iterate up from the leaf deleteing it from it's parent's branches.
let node = leaf.parent;
let branchKey = leaf.branchKey;
while (node) {
node.branches.delete(branchKey);
// Stop iterating if we hit the root.
if (node === root) {
if (node.branches.size === 0) {
this._root = null;
this._numLeafs = 0;
} else {
this._numLeafs--;
}
return true;
}
// Stop iterating if there are other branches since we don't need to
// remove any more nodes.
if (node.branches.size > 0) {
break;
}
// Iterate up to our parent
branchKey = node?.branchKey;
node = node.parent;
}
// Confirm that the leaf we are deleting is actually attached to our tree
for (; node !== root; node = node.parent) {
if (node == null) {
return false;
}
}
this._numLeafs--;
return true;
}
clear(): void {
this._numLeafs = 0;
this._root = null;
}
invalidCacheError(): ChangedPathError {
const CHANGED_PATH_ERROR_MESSAGE = isFastRefreshEnabled()
? 'Possible Fast Refresh module reload detected. ' +
'This may also be caused by an selector returning inconsistent values. ' +
'Resetting cache.'
: 'Invalid cache values. This happens when selectors do not return ' +
'consistent values for the same input dependency values. That may also ' +
'be caused when using Fast Refresh to change a selector implementation. ' +
'Resetting cache.';
recoverableViolation(
CHANGED_PATH_ERROR_MESSAGE +
(this._name != null ? ` - ${this._name}` : ''),
'recoil',
);
throw new ChangedPathError();
}
}
module.exports = {TreeCache};

View File

@@ -0,0 +1,71 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow strict
* @format
* @oncall recoil
*/
'use strict';
import type {NodeKey} from '../core/Recoil_Keys';
export type NodeCacheRoute = Array<[NodeKey, mixed]>;
export type TreeCacheNode<T> = TreeCacheLeaf<T> | TreeCacheBranch<T>;
export type TreeCacheLeaf<T> = {
type: 'leaf',
value: T,
branchKey?: mixed,
parent: ?TreeCacheBranch<T>,
};
export type TreeCacheBranch<T> = {
type: 'branch',
nodeKey: NodeKey,
branches: Map<mixed, TreeCacheNode<T>>,
branchKey?: mixed,
parent: ?TreeCacheBranch<T>,
};
export type NodeValueGet = (nodeKey: NodeKey) => mixed;
type NodeVisitHandler<T> = (node: TreeCacheNode<T>) => void;
export type GetHandlers<T> = {
onNodeVisit: NodeVisitHandler<T>,
};
export type SetHandlers<T> = {
onNodeVisit: NodeVisitHandler<T>,
};
/**
* This is an opinionated tree cache that conforms to the requirements needed
* by Recoil selectors.
*
* Unlike a conventional cache, the tree cache does not store key-value pairs,
* but "routes" that point to values. In the context of selectors these routes
* represent dependencies that a selector has to other atoms and selectors.
*
* In order to retrieve a value from the cache, a function is passed to the
* cache's `get()` method, and the tree cache will use that function to traverse
* itself, passing the provided function a "key" (the first part of the route tuple),
* reconstructing the route to some value (or undefined).
*
* The handlers are necessary for the selector to be able to capture the
* incremental nodes in the tree that are traversed while looking for a cache
* hit as these incremental nodes represent dependencies to the selector, which
* are used internally by the selector.
*/
export interface TreeCacheImplementation<T> {
get(NodeValueGet, handlers?: GetHandlers<T>): ?T;
set(NodeCacheRoute, T, handlers?: SetHandlers<T>): void;
delete(TreeCacheLeaf<T>): boolean;
clear(): void;
root(): ?TreeCacheNode<T>;
size(): number;
}

View File

@@ -0,0 +1,76 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow strict
* @format
* @oncall recoil
*/
'use strict';
import type {CacheImplementation} from './Recoil_CacheImplementationType';
import type {
CachePolicy,
EqualityPolicy,
EvictionPolicy,
} from './Recoil_CachePolicy';
const {LRUCache} = require('./Recoil_LRUCache');
const {MapCache} = require('./Recoil_MapCache');
const err = require('recoil-shared/util/Recoil_err');
const nullthrows = require('recoil-shared/util/Recoil_nullthrows');
const stableStringify = require('recoil-shared/util/Recoil_stableStringify');
const defaultPolicy: {
equality: 'reference',
eviction: 'none',
maxSize: number,
} = {
equality: 'reference',
eviction: 'none',
maxSize: Infinity,
};
function cacheFromPolicy<K, V>({
equality = defaultPolicy.equality,
eviction = defaultPolicy.eviction,
maxSize = defaultPolicy.maxSize,
}: // $FlowFixMe[incompatible-type]
CachePolicy = defaultPolicy): CacheImplementation<K, V> {
const valueMapper = getValueMapper(equality);
const cache = getCache<K, V>(eviction, maxSize, valueMapper);
return cache;
}
function getValueMapper(equality: EqualityPolicy): mixed => mixed {
switch (equality) {
case 'reference':
return val => val;
case 'value':
return val => stableStringify(val);
}
throw err(`Unrecognized equality policy ${equality}`);
}
function getCache<K, V>(
eviction: EvictionPolicy,
maxSize: ?number,
mapKey: mixed => mixed,
): CacheImplementation<K, V> {
switch (eviction) {
case 'keep-all':
return new MapCache<K, V>({mapKey});
case 'lru':
return new LRUCache<K, V>({mapKey, maxSize: nullthrows(maxSize)});
case 'most-recent':
return new LRUCache<K, V>({mapKey, maxSize: 1});
}
throw err(`Unrecognized eviction policy ${eviction}`);
}
module.exports = cacheFromPolicy;

View File

@@ -0,0 +1,81 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow strict-local
* @format
* @oncall recoil
*/
'use strict';
import type {
CachePolicy,
EqualityPolicy,
EvictionPolicy,
} from './Recoil_CachePolicy';
import type {TreeCacheImplementation} from './Recoil_TreeCacheImplementationType';
const {TreeCache} = require('./Recoil_TreeCache');
const treeCacheLRU = require('./Recoil_treeCacheLRU');
const err = require('recoil-shared/util/Recoil_err');
const nullthrows = require('recoil-shared/util/Recoil_nullthrows');
const stableStringify = require('recoil-shared/util/Recoil_stableStringify');
const defaultPolicy: {
equality: 'reference',
eviction: 'keep-all',
maxSize: number,
} = {
equality: 'reference',
eviction: 'keep-all',
maxSize: Infinity,
};
function treeCacheFromPolicy<T>(
{
equality = defaultPolicy.equality,
eviction = defaultPolicy.eviction,
maxSize = defaultPolicy.maxSize,
}: // $FlowFixMe[incompatible-type]
CachePolicy = defaultPolicy,
name?: string,
): TreeCacheImplementation<T> {
const valueMapper = getValueMapper(equality);
return getTreeCache(eviction, maxSize, valueMapper, name);
}
function getValueMapper(equality: EqualityPolicy): mixed => mixed {
switch (equality) {
case 'reference':
return val => val;
case 'value':
return val => stableStringify(val);
}
throw err(`Unrecognized equality policy ${equality}`);
}
function getTreeCache<T>(
eviction: EvictionPolicy,
maxSize: ?number,
mapNodeValue: mixed => mixed,
name?: string,
): TreeCacheImplementation<T> {
switch (eviction) {
case 'keep-all':
return new TreeCache<T>({name, mapNodeValue});
case 'lru':
return treeCacheLRU<T>({
name,
maxSize: nullthrows(maxSize),
mapNodeValue,
});
case 'most-recent':
return treeCacheLRU<T>({name, maxSize: 1, mapNodeValue});
}
throw err(`Unrecognized eviction policy ${eviction}`);
}
module.exports = treeCacheFromPolicy;

View File

@@ -0,0 +1,51 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow strict-local
* @format
* @oncall recoil
*/
'use strict';
import type {TreeCacheImplementation} from './Recoil_TreeCacheImplementationType';
const {LRUCache} = require('./Recoil_LRUCache');
const {TreeCache} = require('./Recoil_TreeCache');
function treeCacheLRU<T>({
name,
maxSize,
mapNodeValue = (v: mixed) => v,
}: {
name?: string,
maxSize: number,
mapNodeValue?: mixed => mixed,
}): TreeCacheImplementation<T> {
const lruCache = new LRUCache({maxSize});
const cache: TreeCache<T> = new TreeCache({
name,
mapNodeValue,
onHit: node => {
lruCache.set(node, true);
},
onSet: node => {
const lruNode = lruCache.tail();
lruCache.set(node, true);
if (lruNode && cache.size() > maxSize) {
// $FlowFixMe[incompatible-call]
cache.delete(lruNode.key);
}
},
});
return cache;
}
module.exports = treeCacheLRU;

View File

@@ -0,0 +1,133 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow strict-local
* @format
* @oncall recoil
*/
'use strict';
const {
getRecoilTestFn,
} = require('recoil-shared/__test_utils__/Recoil_TestingUtils');
let LRUCache;
const testRecoil = getRecoilTestFn(() => {
({LRUCache} = require('../Recoil_LRUCache'));
});
describe('LRUCache', () => {
testRecoil('setting and getting (without hitting max size)', () => {
const cache = new LRUCache({
maxSize: 10,
});
cache.set('a', 1);
cache.set('b', 2);
cache.set('c', 3);
expect(cache.size()).toBe(3);
expect(cache.get('a')).toBe(1);
expect(cache.get('b')).toBe(2);
expect(cache.get('c')).toBe(3);
cache.delete('a');
cache.delete('b');
expect(cache.size()).toBe(1);
});
testRecoil('setting and getting (hitting max size)', () => {
const cache = new LRUCache({
maxSize: 2,
});
cache.set('a', 1);
cache.set('b', 2);
cache.set('c', 3);
expect(cache.size()).toBe(2);
expect(cache.get('a')).toBe(undefined);
expect(cache.get('b')).toBe(2);
expect(cache.get('c')).toBe(3);
cache.delete('a');
cache.delete('b');
expect(cache.size()).toBe(1);
cache.set('d', 4);
cache.set('e', 5);
expect(cache.size()).toBe(2);
expect(cache.get('b')).toBe(undefined);
expect(cache.get('c')).toBe(undefined);
});
testRecoil('manually deleting LRU', () => {
const cache = new LRUCache({
maxSize: 10,
});
cache.set('a', 1);
cache.set('b', 2);
cache.set('c', 3);
expect(cache.size()).toBe(3);
expect(cache.get('a')).toBe(1);
expect(cache.get('b')).toBe(2);
expect(cache.get('c')).toBe(3);
cache.deleteLru(); // delete 'a'
expect(cache.get('a')).toBe(undefined);
expect(cache.size()).toBe(2);
cache.deleteLru(); // delete 'b'
expect(cache.get('b')).toBe(undefined);
expect(cache.size()).toBe(1);
});
testRecoil('head() and tail()', () => {
const cache = new LRUCache({
maxSize: 10,
});
cache.set('a', 1);
cache.set('b', 2);
cache.set('c', 3);
expect(cache.size()).toBe(3);
expect(cache.tail()).toBeDefined();
expect(cache.tail()?.value).toBe(1);
expect(cache.head()?.value).toBe(3);
expect(cache.get('c')).toBe(3);
expect(cache.get('b')).toBe(2);
expect(cache.get('a')).toBe(1);
expect(cache.tail()?.value).toBe(3);
expect(cache.head()?.value).toBe(1);
cache.delete('a');
cache.delete('b');
expect(cache.tail()?.value).toBe(3);
expect(cache.head()?.value).toBe(3);
expect(cache.size()).toBe(1);
});
});

View File

@@ -0,0 +1,53 @@
/**
* (c) Meta Platforms, Inc. and affiliates. Confidential and proprietary.
*
* @flow strict-local
* @format
* @oncall recoil
*/
'use strict';
const {
getRecoilTestFn,
} = require('recoil-shared/__test_utils__/Recoil_TestingUtils');
let MapCache;
const testRecoil = getRecoilTestFn(() => {
({MapCache} = require('../Recoil_MapCache'));
});
describe('MapCache', () => {
testRecoil('setting and getting', () => {
const cache = new MapCache<string, number>();
cache.set('a', 1);
cache.set('b', 2);
cache.set('c', 3);
expect(cache.size()).toBe(3);
expect(cache.get('a')).toBe(1);
expect(cache.get('b')).toBe(2);
expect(cache.get('c')).toBe(3);
});
testRecoil('deleting', () => {
const cache = new MapCache<string, number>();
cache.set('a', 1);
cache.set('b', 2);
cache.set('c', 3);
expect(cache.size()).toBe(3);
cache.delete('a');
expect(cache.size()).toBe(2);
expect(cache.get('a')).toBe(undefined);
expect(cache.get('b')).toBe(2);
expect(cache.has('a')).toBe(false);
});
});

View File

@@ -0,0 +1,264 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow strict-local
* @format
* @oncall recoil
*/
'use strict';
import type {NodeKey} from 'Recoil_Keys';
const {
getRecoilTestFn,
} = require('recoil-shared/__test_utils__/Recoil_TestingUtils');
let TreeCache, loadableWithValue, nullthrows;
const testRecoil = getRecoilTestFn(() => {
({TreeCache} = require('../Recoil_TreeCache'));
nullthrows = require('recoil-shared/util/Recoil_nullthrows');
({loadableWithValue} = require('../../adt/Recoil_Loadable'));
});
describe('TreeCache', () => {
testRecoil('setting and getting values', () => {
const cache = new TreeCache();
const [route1, loadable1] = [
[
['a', 2],
['b', 3],
],
loadableWithValue('value1'),
];
const [route2, loadable2] = [
[
['a', 3],
['b', 4],
],
loadableWithValue('value2'),
];
const [route3, loadable3] = [[['a', 4]], loadableWithValue('value3')];
cache.set(route1, loadable1);
cache.set(route2, loadable2);
cache.set(route3, loadable3);
expect(
cache.get(nodeKey => route1.find(([key]) => key === nodeKey)?.[1]),
).toBe(loadable1);
expect(
cache.get(nodeKey => route2.find(([key]) => key === nodeKey)?.[1]),
).toBe(loadable2);
expect(
cache.get(nodeKey => route3.find(([key]) => key === nodeKey)?.[1]),
).toBe(loadable3);
expect(cache.size()).toBe(3);
});
testRecoil('deleting values', () => {
const cache = new TreeCache();
const [route1, loadable1] = [
[
['a', 2],
['b', 3],
],
loadableWithValue('value1'),
];
const [route2, loadable2] = [
[
['a', 2],
['b', 4],
['c', 5],
],
loadableWithValue('value2'),
];
const [route3, loadable3] = [[['a', 6]], loadableWithValue('value3')];
cache.set(route1, loadable1);
cache.set(route2, loadable2);
cache.set(route3, loadable3);
const leaf1 = cache.getLeafNode(
nodeKey => route1.find(([key]) => key === nodeKey)?.[1],
);
const leaf2 = cache.getLeafNode(
nodeKey => route2.find(([key]) => key === nodeKey)?.[1],
);
const leaf3 = cache.getLeafNode(
nodeKey => route3.find(([key]) => key === nodeKey)?.[1],
);
expect(leaf1).toBeDefined();
expect(leaf2).toBeDefined();
expect(leaf3).toBeDefined();
const leaf1Node = nullthrows(leaf1);
const leaf2Node = nullthrows(leaf2);
const leaf3Node = nullthrows(leaf3);
expect(cache.size()).toBe(3);
const deleted1 = cache.delete(leaf1Node);
expect(deleted1).toBe(true);
expect(cache.size()).toBe(2);
const deleted2 = cache.delete(leaf2Node);
expect(deleted2).toBe(true);
expect(cache.size()).toBe(1);
const deleted3 = cache.delete(leaf3Node);
expect(deleted3).toBe(true);
expect(cache.size()).toBe(0);
expect(cache.root()).toBeNull();
const deletedAgain = cache.delete(leaf1Node);
expect(deletedAgain).toBe(false);
});
testRecoil('onHit() handler', () => {
const [route1, loadable1] = [
[
['a', 2],
['b', 3],
],
loadableWithValue('value1'),
];
const onHit = jest.fn();
const cache = new TreeCache({
onHit,
});
const getter = (nodeKey: NodeKey) =>
route1.find(([key]) => key === nodeKey)?.[1];
cache.set(route1, loadable1);
// hit
cache.get(getter);
// miss
cache.get(() => {});
// hit
cache.get(getter);
expect(onHit).toHaveBeenCalledTimes(2);
});
testRecoil('onSet() handler', () => {
const onSet = jest.fn();
const cache = new TreeCache({
onSet,
});
const [route1, loadable1] = [
[
['a', 2],
['b', 3],
],
loadableWithValue('value1'),
];
const [route2, loadable2] = [
[
['a', 3],
['b', 4],
],
loadableWithValue('value2'),
];
const [route3, loadable3] = [[['a', 4]], loadableWithValue('value3')];
cache.set(route1, loadable1);
cache.set(route2, loadable2);
cache.set(route3, loadable3);
expect(onSet).toHaveBeenCalledTimes(3);
});
testRecoil('default key generation uses reference equality', () => {
const [route1, loadable1] = [
[
['a', [2]],
['b', [3]],
],
loadableWithValue('value1'),
];
const cache = new TreeCache();
cache.set(route1, loadable1);
const resultWithKeyCopy = cache.get(nodeKey => [
...(route1.find(([key]) => key === nodeKey)?.[1] ?? []),
]);
expect(resultWithKeyCopy).toBeUndefined();
const result = cache.get(
nodeKey => route1.find(([key]) => key === nodeKey)?.[1],
);
expect(result).toBe(loadable1);
});
testRecoil('mapNodeValue() to implement value equality keys', () => {
const cache = new TreeCache({
mapNodeValue: value => JSON.stringify(value),
});
const [route1, loadable1] = [
[
['a', [2]],
['b', [3]],
],
loadableWithValue('value1'),
];
cache.set(route1, loadable1);
const resultWithKeyCopy = cache.get(nodeKey => [
...(route1.find(([key]) => key === nodeKey)?.[1] ?? []),
]);
expect(resultWithKeyCopy).toBe(loadable1);
});
// Test ability to scale cache to large number of entries.
// Use more dependencies than the JavaScript callstack depth limit to ensure
// we are not using a recursive algorithm.
testRecoil('Scalability', () => {
const cache = new TreeCache();
const route = Array.from(Array(10000).keys()).map(i => [
String(i),
String(i),
]);
// $FlowFixMe[incompatible-call]
cache.set(route, 'VALUE');
expect(cache.get(x => x)).toBe('VALUE');
const leafNode = cache.getLeafNode(x => x);
expect(cache.delete(nullthrows(leafNode))).toBe(true);
});
});

View File

@@ -0,0 +1,203 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow strict-local
* @format
* @oncall recoil
*/
'use strict';
const {
getRecoilTestFn,
} = require('recoil-shared/__test_utils__/Recoil_TestingUtils');
let cacheFromPolicy;
const testRecoil = getRecoilTestFn(() => {
cacheFromPolicy = require('../Recoil_cacheFromPolicy');
});
describe('cacheFromPolicy()', () => {
testRecoil('equality: reference, eviction: keep-all', () => {
const policy = {equality: 'reference', eviction: 'keep-all'};
const cache = cacheFromPolicy<{[string]: number}, boolean>(policy);
const obj1 = {a: 1};
const obj2 = {b: 2};
const obj3 = {c: 3};
cache.set(obj1, true);
cache.set(obj2, true);
cache.set(obj3, true);
expect(cache.size()).toBe(3);
expect(cache.get(obj1)).toBe(true);
expect(cache.get(obj2)).toBe(true);
expect(cache.get(obj3)).toBe(true);
expect(cache.get({...obj1})).toBe(undefined);
expect(cache.get({...obj2})).toBe(undefined);
expect(cache.get({...obj3})).toBe(undefined);
});
testRecoil('equality: value, eviction: keep-all', () => {
const policy = {equality: 'value', eviction: 'keep-all'};
const cache = cacheFromPolicy<{[string]: number}, boolean>(policy);
const obj1 = {a: 1};
const obj2 = {b: 2};
const obj3 = {c: 3};
cache.set(obj1, true);
cache.set(obj2, true);
cache.set(obj3, true);
expect(cache.size()).toBe(3);
expect(cache.get(obj1)).toBe(true);
expect(cache.get(obj2)).toBe(true);
expect(cache.get(obj3)).toBe(true);
expect(cache.get({...obj1})).toBe(true);
expect(cache.get({...obj2})).toBe(true);
expect(cache.get({...obj3})).toBe(true);
});
testRecoil('equality: reference, eviction: lru', () => {
const policy = {equality: 'reference', eviction: 'lru', maxSize: 2};
const cache = cacheFromPolicy<{[string]: number}, boolean>(policy);
const obj1 = {a: 1};
const obj2 = {b: 2};
const obj3 = {c: 3};
cache.set(obj1, true);
cache.set(obj2, true);
cache.set(obj3, true);
expect(cache.size()).toBe(2);
expect(cache.get(obj1)).toBe(undefined);
expect(cache.get(obj2)).toBe(true);
expect(cache.get(obj3)).toBe(true);
cache.set(obj1, true);
expect(cache.size()).toBe(2);
expect(cache.get(obj2)).toBe(undefined);
expect(cache.get(obj1)).toBe(true);
expect(cache.get(obj3)).toBe(true);
expect(cache.get({...obj1})).toBe(undefined);
expect(cache.get({...obj3})).toBe(undefined);
});
testRecoil('equality: value, eviction: lru', () => {
const policy = {equality: 'value', eviction: 'lru', maxSize: 2};
const cache = cacheFromPolicy<{[string]: number}, boolean>(policy);
const obj1 = {a: 1};
const obj2 = {b: 2};
const obj3 = {c: 3};
cache.set(obj1, true);
cache.set(obj2, true);
cache.set(obj3, true);
expect(cache.size()).toBe(2);
expect(cache.get(obj1)).toBe(undefined);
expect(cache.get(obj2)).toBe(true);
expect(cache.get(obj3)).toBe(true);
cache.set(obj1, true);
expect(cache.size()).toBe(2);
expect(cache.get(obj2)).toBe(undefined);
expect(cache.get(obj1)).toBe(true);
expect(cache.get(obj3)).toBe(true);
expect(cache.get({...obj2})).toBe(undefined);
expect(cache.get({...obj1})).toBe(true);
expect(cache.get({...obj3})).toBe(true);
});
testRecoil('equality: reference, eviction: most-recent', () => {
const policy = {equality: 'reference', eviction: 'most-recent'};
const cache = cacheFromPolicy<{[string]: number}, boolean>(policy);
const obj1 = {a: 1};
const obj2 = {b: 2};
const obj3 = {c: 3};
cache.set(obj1, true);
cache.set(obj2, true);
cache.set(obj3, true);
expect(cache.size()).toBe(1);
expect(cache.get(obj1)).toBe(undefined);
expect(cache.get(obj2)).toBe(undefined);
expect(cache.get(obj3)).toBe(true);
cache.set(obj1, true);
expect(cache.size()).toBe(1);
expect(cache.get(obj2)).toBe(undefined);
expect(cache.get(obj3)).toBe(undefined);
expect(cache.get(obj1)).toBe(true);
expect(cache.get({...obj2})).toBe(undefined);
expect(cache.get({...obj1})).toBe(undefined);
expect(cache.get({...obj3})).toBe(undefined);
});
testRecoil('equality: value, eviction: most-recent', () => {
const policy = {equality: 'value', eviction: 'most-recent'};
const cache = cacheFromPolicy<{[string]: number}, boolean>(policy);
const obj1 = {a: 1};
const obj2 = {b: 2};
const obj3 = {c: 3};
cache.set(obj1, true);
cache.set(obj2, true);
cache.set(obj3, true);
expect(cache.size()).toBe(1);
expect(cache.get(obj1)).toBe(undefined);
expect(cache.get(obj2)).toBe(undefined);
expect(cache.get(obj3)).toBe(true);
cache.set(obj1, true);
expect(cache.size()).toBe(1);
expect(cache.get(obj2)).toBe(undefined);
expect(cache.get(obj3)).toBe(undefined);
expect(cache.get(obj1)).toBe(true);
expect(cache.get({...obj2})).toBe(undefined);
expect(cache.get({...obj3})).toBe(undefined);
expect(cache.get({...obj1})).toBe(true);
});
});

View File

@@ -0,0 +1,276 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow strict-local
* @format
* @oncall recoil
*/
'use strict';
import type {NodeKey} from 'Recoil_Keys';
const {
getRecoilTestFn,
} = require('recoil-shared/__test_utils__/Recoil_TestingUtils');
let treeCacheFromPolicy;
const testRecoil = getRecoilTestFn(() => {
treeCacheFromPolicy = require('../Recoil_treeCacheFromPolicy');
});
/* $FlowFixMe[missing-local-annot] The type annotation(s) required by Flow's
* LTI update could not be added via codemod */
const valGetterFromPath = path => (nodeKey: NodeKey) =>
path.find(([k]) => k === nodeKey)?.[1];
/* $FlowFixMe[missing-local-annot] The type annotation(s) required by Flow's
* LTI update could not be added via codemod */
const clonePath = path => JSON.parse(JSON.stringify(path));
describe('treeCacheFromPolicy()', () => {
testRecoil('equality: reference, eviction: keep-all', () => {
const policy = {equality: 'reference', eviction: 'keep-all'};
const cache = treeCacheFromPolicy<{[string]: number}>(policy);
const path1 = [
['a', [1]],
['b', [2]],
];
const obj1 = {a: 1};
const path2 = [['a', [2]]];
const obj2 = {b: 2};
const path3 = [
['a', [3]],
['c', [4]],
];
const obj3 = {c: 3};
cache.set(path1, obj1);
cache.set(path2, obj2);
cache.set(path3, obj3);
expect(cache.size()).toBe(3);
expect(cache.get(valGetterFromPath(path1))).toBe(obj1);
expect(cache.get(valGetterFromPath(path2))).toBe(obj2);
expect(cache.get(valGetterFromPath(path3))).toBe(obj3);
expect(cache.get(valGetterFromPath(clonePath(path1)))).toBe(undefined);
expect(cache.get(valGetterFromPath(clonePath(path2)))).toBe(undefined);
expect(cache.get(valGetterFromPath(clonePath(path3)))).toBe(undefined);
});
testRecoil('equality: value, eviction: keep-all', () => {
const policy = {equality: 'value', eviction: 'keep-all'};
const cache = treeCacheFromPolicy<{[string]: number}>(policy);
const path1 = [
['a', [1]],
['b', [2]],
];
const obj1 = {a: 1};
const path2 = [['a', [2]]];
const obj2 = {b: 2};
const path3 = [
['a', [3]],
['c', [4]],
];
const obj3 = {c: 3};
cache.set(path1, obj1);
cache.set(path2, obj2);
cache.set(path3, obj3);
expect(cache.size()).toBe(3);
expect(cache.get(valGetterFromPath(path1))).toBe(obj1);
expect(cache.get(valGetterFromPath(path2))).toBe(obj2);
expect(cache.get(valGetterFromPath(path3))).toBe(obj3);
expect(cache.get(valGetterFromPath(clonePath(path1)))).toBe(obj1);
expect(cache.get(valGetterFromPath(clonePath(path2)))).toBe(obj2);
expect(cache.get(valGetterFromPath(clonePath(path3)))).toBe(obj3);
});
testRecoil('equality: reference, eviction: lru', () => {
const policy = {equality: 'reference', eviction: 'lru', maxSize: 2};
const cache = treeCacheFromPolicy<{[string]: number}>(policy);
const path1 = [
['a', [1]],
['b', [2]],
];
const obj1 = {a: 1};
const path2 = [['a', [2]]];
const obj2 = {b: 2};
const path3 = [
['a', [3]],
['c', [4]],
];
const obj3 = {c: 3};
cache.set(path1, obj1);
cache.set(path2, obj2);
cache.set(path3, obj3);
expect(cache.size()).toBe(2);
expect(cache.get(valGetterFromPath(path1))).toBe(undefined);
expect(cache.get(valGetterFromPath(path2))).toBe(obj2);
expect(cache.get(valGetterFromPath(path3))).toBe(obj3);
cache.set(path1, obj1);
expect(cache.size()).toBe(2);
expect(cache.get(valGetterFromPath(path2))).toBe(undefined);
expect(cache.get(valGetterFromPath(path1))).toBe(obj1);
expect(cache.get(valGetterFromPath(path3))).toBe(obj3);
expect(cache.get(valGetterFromPath(clonePath(path1)))).toBe(undefined);
expect(cache.get(valGetterFromPath(clonePath(path3)))).toBe(undefined);
});
testRecoil('equality: value, eviction: lru', () => {
const policy = {equality: 'value', eviction: 'lru', maxSize: 2};
const cache = treeCacheFromPolicy<{[string]: number}>(policy);
const path1 = [
['a', [1]],
['b', [2]],
];
const obj1 = {a: 1};
const path2 = [['a', [2]]];
const obj2 = {b: 2};
const path3 = [
['a', [3]],
['c', [4]],
];
const obj3 = {c: 3};
cache.set(path1, obj1);
cache.set(path2, obj2);
cache.set(path3, obj3);
expect(cache.size()).toBe(2);
expect(cache.get(valGetterFromPath(path1))).toBe(undefined);
expect(cache.get(valGetterFromPath(path2))).toBe(obj2);
expect(cache.get(valGetterFromPath(path3))).toBe(obj3);
cache.set(path1, obj1);
expect(cache.size()).toBe(2);
expect(cache.get(valGetterFromPath(path2))).toBe(undefined);
expect(cache.get(valGetterFromPath(path1))).toBe(obj1);
expect(cache.get(valGetterFromPath(path3))).toBe(obj3);
expect(cache.get(valGetterFromPath(clonePath(path1)))).toBe(obj1);
expect(cache.get(valGetterFromPath(clonePath(path3)))).toBe(obj3);
});
testRecoil('equality: reference, eviction: most-recent', () => {
const policy = {equality: 'reference', eviction: 'most-recent'};
const cache = treeCacheFromPolicy<{[string]: number}>(policy);
const path1 = [
['a', [1]],
['b', [2]],
];
const obj1 = {a: 1};
const path2 = [['a', [2]]];
const obj2 = {b: 2};
const path3 = [
['a', [3]],
['c', [4]],
];
const obj3 = {c: 3};
cache.set(path1, obj1);
cache.set(path2, obj2);
cache.set(path3, obj3);
expect(cache.size()).toBe(1);
expect(cache.get(valGetterFromPath(path1))).toBe(undefined);
expect(cache.get(valGetterFromPath(path2))).toBe(undefined);
expect(cache.get(valGetterFromPath(path3))).toBe(obj3);
cache.set(path1, obj1);
expect(cache.size()).toBe(1);
expect(cache.get(valGetterFromPath(path2))).toBe(undefined);
expect(cache.get(valGetterFromPath(path3))).toBe(undefined);
expect(cache.get(valGetterFromPath(path1))).toBe(obj1);
expect(cache.get(valGetterFromPath(clonePath(path1)))).toBe(undefined);
expect(cache.get(valGetterFromPath(clonePath(path2)))).toBe(undefined);
expect(cache.get(valGetterFromPath(clonePath(path3)))).toBe(undefined);
});
testRecoil('equality: value, eviction: most-recent', () => {
const policy = {equality: 'value', eviction: 'most-recent'};
const cache = treeCacheFromPolicy<{[string]: number}>(policy);
const path1 = [
['a', [1]],
['b', [2]],
];
const obj1 = {a: 1};
const path2 = [['a', [2]]];
const obj2 = {b: 2};
const path3 = [
['a', [3]],
['c', [4]],
];
const obj3 = {c: 3};
cache.set(path1, obj1);
cache.set(path2, obj2);
cache.set(path3, obj3);
expect(cache.size()).toBe(1);
expect(cache.get(valGetterFromPath(path1))).toBe(undefined);
expect(cache.get(valGetterFromPath(path2))).toBe(undefined);
expect(cache.get(valGetterFromPath(path3))).toBe(obj3);
cache.set(path1, obj1);
expect(cache.size()).toBe(1);
expect(cache.get(valGetterFromPath(path2))).toBe(undefined);
expect(cache.get(valGetterFromPath(path3))).toBe(undefined);
expect(cache.get(valGetterFromPath(path1))).toBe(obj1);
expect(cache.get(valGetterFromPath(clonePath(path1)))).toBe(obj1);
expect(cache.get(valGetterFromPath(clonePath(path2)))).toBe(undefined);
expect(cache.get(valGetterFromPath(clonePath(path3)))).toBe(undefined);
});
});

View File

@@ -0,0 +1,117 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow strict-local
* @format
* @oncall recoil
*/
'use strict';
const {
getRecoilTestFn,
} = require('recoil-shared/__test_utils__/Recoil_TestingUtils');
let treeCacheLRU, loadableWithValue;
const testRecoil = getRecoilTestFn(() => {
treeCacheLRU = require('../Recoil_treeCacheLRU');
({loadableWithValue} = require('../../adt/Recoil_Loadable'));
});
describe('treeCacheLRU()', () => {
testRecoil('getting and setting cache', () => {
const cache = treeCacheLRU<$FlowFixMe>({maxSize: 10});
const [route1, loadable1] = [
[
['a', 2],
['b', 3],
],
loadableWithValue('value1'),
];
const [route2, loadable2] = [
[
['a', 3],
['b', 4],
],
loadableWithValue('value2'),
];
const [route3, loadable3] = [[['a', 4]], loadableWithValue('value3')];
cache.set(route1, loadable1);
cache.set(route2, loadable2);
cache.set(route3, loadable3);
expect(
cache.get(nodeKey => route1.find(([key]) => key === nodeKey)?.[1]),
).toBe(loadable1);
expect(
cache.get(nodeKey => route2.find(([key]) => key === nodeKey)?.[1]),
).toBe(loadable2);
expect(
cache.get(nodeKey => route3.find(([key]) => key === nodeKey)?.[1]),
).toBe(loadable3);
expect(cache.size()).toBe(3);
});
testRecoil('getting and setting cache (hitting max size)', () => {
const cache = treeCacheLRU<$FlowFixMe>({maxSize: 2});
const [route1, loadable1] = [
[
['a', 2],
['b', 3],
],
loadableWithValue('value1'),
];
const [route2, loadable2] = [
[
['a', 3],
['b', 4],
],
loadableWithValue('value2'),
];
const [route3, loadable3] = [[['a', 4]], loadableWithValue('value3')];
cache.set(route1, loadable1);
cache.set(route2, loadable2);
cache.set(route3, loadable3);
expect(
cache.get(nodeKey => route1.find(([key]) => key === nodeKey)?.[1]),
).toBe(undefined);
expect(
cache.get(nodeKey => route2.find(([key]) => key === nodeKey)?.[1]),
).toBe(loadable2);
expect(
cache.get(nodeKey => route3.find(([key]) => key === nodeKey)?.[1]),
).toBe(loadable3);
expect(cache.size()).toBe(2);
cache.set(route1, loadable1);
expect(
cache.get(nodeKey => route1.find(([key]) => key === nodeKey)?.[1]),
).toBe(loadable1);
expect(
cache.get(nodeKey => route2.find(([key]) => key === nodeKey)?.[1]),
).toBe(undefined);
expect(cache.size()).toBe(2);
});
});