initial update
This commit is contained in:
9
node_modules/optimism/.github/dependabot.yml
generated
vendored
Normal file
9
node_modules/optimism/.github/dependabot.yml
generated
vendored
Normal file
@@ -0,0 +1,9 @@
|
||||
# Please see the documentation for all configuration options:
|
||||
# https://help.github.com/github/administering-a-repository/configuration-options-for-dependency-updates
|
||||
|
||||
version: 2
|
||||
updates:
|
||||
- package-ecosystem: "npm"
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: "daily"
|
||||
29
node_modules/optimism/.github/workflows/main.yml
generated
vendored
Normal file
29
node_modules/optimism/.github/workflows/main.yml
generated
vendored
Normal file
@@ -0,0 +1,29 @@
|
||||
name: CI
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ main ]
|
||||
pull_request:
|
||||
branches: [ main ]
|
||||
|
||||
jobs:
|
||||
test:
|
||||
name: Test on node ${{ matrix.node_version }} and ${{ matrix.os }}
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
matrix:
|
||||
node_version: ['16', '18', '19', '20', '21']
|
||||
os: [ubuntu-latest]
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Use Node.js ${{ matrix.node_version }}
|
||||
uses: actions/setup-node@v1
|
||||
with:
|
||||
node-version: ${{ matrix.node_version }}
|
||||
|
||||
- name: npm install, build and test
|
||||
run: |
|
||||
npm install
|
||||
npm run build --if-present
|
||||
npm test
|
||||
21
node_modules/optimism/LICENSE
generated
vendored
Normal file
21
node_modules/optimism/LICENSE
generated
vendored
Normal file
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2016 Ben Newman
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
3
node_modules/optimism/README.md
generated
vendored
Normal file
3
node_modules/optimism/README.md
generated
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
# optimism [](https://github.com/benjamn/optimism/actions)
|
||||
|
||||
Composable reactive caching with efficient invalidation.
|
||||
492
node_modules/optimism/lib/bundle.cjs
generated
vendored
Normal file
492
node_modules/optimism/lib/bundle.cjs
generated
vendored
Normal file
@@ -0,0 +1,492 @@
|
||||
'use strict';
|
||||
|
||||
var trie = require('@wry/trie');
|
||||
var caches$1 = require('@wry/caches');
|
||||
var context = require('@wry/context');
|
||||
|
||||
var parentEntrySlot = new context.Slot();
|
||||
function nonReactive(fn) {
|
||||
return parentEntrySlot.withValue(void 0, fn);
|
||||
}
|
||||
|
||||
var hasOwnProperty = Object.prototype.hasOwnProperty;
|
||||
var arrayFromSet = Array.from ||
|
||||
function (set) {
|
||||
var array = [];
|
||||
set.forEach(function (item) { return array.push(item); });
|
||||
return array;
|
||||
};
|
||||
function maybeUnsubscribe(entryOrDep) {
|
||||
var unsubscribe = entryOrDep.unsubscribe;
|
||||
if (typeof unsubscribe === "function") {
|
||||
entryOrDep.unsubscribe = void 0;
|
||||
unsubscribe();
|
||||
}
|
||||
}
|
||||
|
||||
var emptySetPool = [];
|
||||
var POOL_TARGET_SIZE = 100;
|
||||
// Since this package might be used browsers, we should avoid using the
|
||||
// Node built-in assert module.
|
||||
function assert(condition, optionalMessage) {
|
||||
if (!condition) {
|
||||
throw new Error(optionalMessage || "assertion failure");
|
||||
}
|
||||
}
|
||||
function valueIs(a, b) {
|
||||
var len = a.length;
|
||||
return (
|
||||
// Unknown values are not equal to each other.
|
||||
len > 0 &&
|
||||
// Both values must be ordinary (or both exceptional) to be equal.
|
||||
len === b.length &&
|
||||
// The underlying value or exception must be the same.
|
||||
a[len - 1] === b[len - 1]);
|
||||
}
|
||||
function valueGet(value) {
|
||||
switch (value.length) {
|
||||
case 0: throw new Error("unknown value");
|
||||
case 1: return value[0];
|
||||
case 2: throw value[1];
|
||||
}
|
||||
}
|
||||
function valueCopy(value) {
|
||||
return value.slice(0);
|
||||
}
|
||||
var Entry = /** @class */ (function () {
|
||||
function Entry(fn) {
|
||||
this.fn = fn;
|
||||
this.parents = new Set();
|
||||
this.childValues = new Map();
|
||||
// When this Entry has children that are dirty, this property becomes
|
||||
// a Set containing other Entry objects, borrowed from emptySetPool.
|
||||
// When the set becomes empty, it gets recycled back to emptySetPool.
|
||||
this.dirtyChildren = null;
|
||||
this.dirty = true;
|
||||
this.recomputing = false;
|
||||
this.value = [];
|
||||
this.deps = null;
|
||||
++Entry.count;
|
||||
}
|
||||
Entry.prototype.peek = function () {
|
||||
if (this.value.length === 1 && !mightBeDirty(this)) {
|
||||
rememberParent(this);
|
||||
return this.value[0];
|
||||
}
|
||||
};
|
||||
// This is the most important method of the Entry API, because it
|
||||
// determines whether the cached this.value can be returned immediately,
|
||||
// or must be recomputed. The overall performance of the caching system
|
||||
// depends on the truth of the following observations: (1) this.dirty is
|
||||
// usually false, (2) this.dirtyChildren is usually null/empty, and thus
|
||||
// (3) valueGet(this.value) is usually returned without recomputation.
|
||||
Entry.prototype.recompute = function (args) {
|
||||
assert(!this.recomputing, "already recomputing");
|
||||
rememberParent(this);
|
||||
return mightBeDirty(this)
|
||||
? reallyRecompute(this, args)
|
||||
: valueGet(this.value);
|
||||
};
|
||||
Entry.prototype.setDirty = function () {
|
||||
if (this.dirty)
|
||||
return;
|
||||
this.dirty = true;
|
||||
reportDirty(this);
|
||||
// We can go ahead and unsubscribe here, since any further dirty
|
||||
// notifications we receive will be redundant, and unsubscribing may
|
||||
// free up some resources, e.g. file watchers.
|
||||
maybeUnsubscribe(this);
|
||||
};
|
||||
Entry.prototype.dispose = function () {
|
||||
var _this = this;
|
||||
this.setDirty();
|
||||
// Sever any dependency relationships with our own children, so those
|
||||
// children don't retain this parent Entry in their child.parents sets,
|
||||
// thereby preventing it from being fully garbage collected.
|
||||
forgetChildren(this);
|
||||
// Because this entry has been kicked out of the cache (in index.js),
|
||||
// we've lost the ability to find out if/when this entry becomes dirty,
|
||||
// whether that happens through a subscription, because of a direct call
|
||||
// to entry.setDirty(), or because one of its children becomes dirty.
|
||||
// Because of this loss of future information, we have to assume the
|
||||
// worst (that this entry might have become dirty very soon), so we must
|
||||
// immediately mark this entry's parents as dirty. Normally we could
|
||||
// just call entry.setDirty() rather than calling parent.setDirty() for
|
||||
// each parent, but that would leave this entry in parent.childValues
|
||||
// and parent.dirtyChildren, which would prevent the child from being
|
||||
// truly forgotten.
|
||||
eachParent(this, function (parent, child) {
|
||||
parent.setDirty();
|
||||
forgetChild(parent, _this);
|
||||
});
|
||||
};
|
||||
Entry.prototype.forget = function () {
|
||||
// The code that creates Entry objects in index.ts will replace this method
|
||||
// with one that actually removes the Entry from the cache, which will also
|
||||
// trigger the entry.dispose method.
|
||||
this.dispose();
|
||||
};
|
||||
Entry.prototype.dependOn = function (dep) {
|
||||
dep.add(this);
|
||||
if (!this.deps) {
|
||||
this.deps = emptySetPool.pop() || new Set();
|
||||
}
|
||||
this.deps.add(dep);
|
||||
};
|
||||
Entry.prototype.forgetDeps = function () {
|
||||
var _this = this;
|
||||
if (this.deps) {
|
||||
arrayFromSet(this.deps).forEach(function (dep) { return dep.delete(_this); });
|
||||
this.deps.clear();
|
||||
emptySetPool.push(this.deps);
|
||||
this.deps = null;
|
||||
}
|
||||
};
|
||||
Entry.count = 0;
|
||||
return Entry;
|
||||
}());
|
||||
function rememberParent(child) {
|
||||
var parent = parentEntrySlot.getValue();
|
||||
if (parent) {
|
||||
child.parents.add(parent);
|
||||
if (!parent.childValues.has(child)) {
|
||||
parent.childValues.set(child, []);
|
||||
}
|
||||
if (mightBeDirty(child)) {
|
||||
reportDirtyChild(parent, child);
|
||||
}
|
||||
else {
|
||||
reportCleanChild(parent, child);
|
||||
}
|
||||
return parent;
|
||||
}
|
||||
}
|
||||
function reallyRecompute(entry, args) {
|
||||
forgetChildren(entry);
|
||||
// Set entry as the parent entry while calling recomputeNewValue(entry).
|
||||
parentEntrySlot.withValue(entry, recomputeNewValue, [entry, args]);
|
||||
if (maybeSubscribe(entry, args)) {
|
||||
// If we successfully recomputed entry.value and did not fail to
|
||||
// (re)subscribe, then this Entry is no longer explicitly dirty.
|
||||
setClean(entry);
|
||||
}
|
||||
return valueGet(entry.value);
|
||||
}
|
||||
function recomputeNewValue(entry, args) {
|
||||
entry.recomputing = true;
|
||||
var normalizeResult = entry.normalizeResult;
|
||||
var oldValueCopy;
|
||||
if (normalizeResult && entry.value.length === 1) {
|
||||
oldValueCopy = valueCopy(entry.value);
|
||||
}
|
||||
// Make entry.value an empty array, representing an unknown value.
|
||||
entry.value.length = 0;
|
||||
try {
|
||||
// If entry.fn succeeds, entry.value will become a normal Value.
|
||||
entry.value[0] = entry.fn.apply(null, args);
|
||||
// If we have a viable oldValueCopy to compare with the (successfully
|
||||
// recomputed) new entry.value, and they are not already === identical, give
|
||||
// normalizeResult a chance to pick/choose/reuse parts of oldValueCopy[0]
|
||||
// and/or entry.value[0] to determine the final cached entry.value.
|
||||
if (normalizeResult && oldValueCopy && !valueIs(oldValueCopy, entry.value)) {
|
||||
try {
|
||||
entry.value[0] = normalizeResult(entry.value[0], oldValueCopy[0]);
|
||||
}
|
||||
catch (_a) {
|
||||
// If normalizeResult throws, just use the newer value, rather than
|
||||
// saving the exception as entry.value[1].
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (e) {
|
||||
// If entry.fn throws, entry.value will hold that exception.
|
||||
entry.value[1] = e;
|
||||
}
|
||||
// Either way, this line is always reached.
|
||||
entry.recomputing = false;
|
||||
}
|
||||
function mightBeDirty(entry) {
|
||||
return entry.dirty || !!(entry.dirtyChildren && entry.dirtyChildren.size);
|
||||
}
|
||||
function setClean(entry) {
|
||||
entry.dirty = false;
|
||||
if (mightBeDirty(entry)) {
|
||||
// This Entry may still have dirty children, in which case we can't
|
||||
// let our parents know we're clean just yet.
|
||||
return;
|
||||
}
|
||||
reportClean(entry);
|
||||
}
|
||||
function reportDirty(child) {
|
||||
eachParent(child, reportDirtyChild);
|
||||
}
|
||||
function reportClean(child) {
|
||||
eachParent(child, reportCleanChild);
|
||||
}
|
||||
function eachParent(child, callback) {
|
||||
var parentCount = child.parents.size;
|
||||
if (parentCount) {
|
||||
var parents = arrayFromSet(child.parents);
|
||||
for (var i = 0; i < parentCount; ++i) {
|
||||
callback(parents[i], child);
|
||||
}
|
||||
}
|
||||
}
|
||||
// Let a parent Entry know that one of its children may be dirty.
|
||||
function reportDirtyChild(parent, child) {
|
||||
// Must have called rememberParent(child) before calling
|
||||
// reportDirtyChild(parent, child).
|
||||
assert(parent.childValues.has(child));
|
||||
assert(mightBeDirty(child));
|
||||
var parentWasClean = !mightBeDirty(parent);
|
||||
if (!parent.dirtyChildren) {
|
||||
parent.dirtyChildren = emptySetPool.pop() || new Set;
|
||||
}
|
||||
else if (parent.dirtyChildren.has(child)) {
|
||||
// If we already know this child is dirty, then we must have already
|
||||
// informed our own parents that we are dirty, so we can terminate
|
||||
// the recursion early.
|
||||
return;
|
||||
}
|
||||
parent.dirtyChildren.add(child);
|
||||
// If parent was clean before, it just became (possibly) dirty (according to
|
||||
// mightBeDirty), since we just added child to parent.dirtyChildren.
|
||||
if (parentWasClean) {
|
||||
reportDirty(parent);
|
||||
}
|
||||
}
|
||||
// Let a parent Entry know that one of its children is no longer dirty.
|
||||
function reportCleanChild(parent, child) {
|
||||
// Must have called rememberChild(child) before calling
|
||||
// reportCleanChild(parent, child).
|
||||
assert(parent.childValues.has(child));
|
||||
assert(!mightBeDirty(child));
|
||||
var childValue = parent.childValues.get(child);
|
||||
if (childValue.length === 0) {
|
||||
parent.childValues.set(child, valueCopy(child.value));
|
||||
}
|
||||
else if (!valueIs(childValue, child.value)) {
|
||||
parent.setDirty();
|
||||
}
|
||||
removeDirtyChild(parent, child);
|
||||
if (mightBeDirty(parent)) {
|
||||
return;
|
||||
}
|
||||
reportClean(parent);
|
||||
}
|
||||
function removeDirtyChild(parent, child) {
|
||||
var dc = parent.dirtyChildren;
|
||||
if (dc) {
|
||||
dc.delete(child);
|
||||
if (dc.size === 0) {
|
||||
if (emptySetPool.length < POOL_TARGET_SIZE) {
|
||||
emptySetPool.push(dc);
|
||||
}
|
||||
parent.dirtyChildren = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
// Removes all children from this entry and returns an array of the
|
||||
// removed children.
|
||||
function forgetChildren(parent) {
|
||||
if (parent.childValues.size > 0) {
|
||||
parent.childValues.forEach(function (_value, child) {
|
||||
forgetChild(parent, child);
|
||||
});
|
||||
}
|
||||
// Remove this parent Entry from any sets to which it was added by the
|
||||
// addToSet method.
|
||||
parent.forgetDeps();
|
||||
// After we forget all our children, this.dirtyChildren must be empty
|
||||
// and therefore must have been reset to null.
|
||||
assert(parent.dirtyChildren === null);
|
||||
}
|
||||
function forgetChild(parent, child) {
|
||||
child.parents.delete(parent);
|
||||
parent.childValues.delete(child);
|
||||
removeDirtyChild(parent, child);
|
||||
}
|
||||
function maybeSubscribe(entry, args) {
|
||||
if (typeof entry.subscribe === "function") {
|
||||
try {
|
||||
maybeUnsubscribe(entry); // Prevent double subscriptions.
|
||||
entry.unsubscribe = entry.subscribe.apply(null, args);
|
||||
}
|
||||
catch (e) {
|
||||
// If this Entry has a subscribe function and it threw an exception
|
||||
// (or an unsubscribe function it previously returned now throws),
|
||||
// return false to indicate that we were not able to subscribe (or
|
||||
// unsubscribe), and this Entry should remain dirty.
|
||||
entry.setDirty();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
// Returning true indicates either that there was no entry.subscribe
|
||||
// function or that it succeeded.
|
||||
return true;
|
||||
}
|
||||
|
||||
var EntryMethods = {
|
||||
setDirty: true,
|
||||
dispose: true,
|
||||
forget: true, // Fully remove parent Entry from LRU cache and computation graph
|
||||
};
|
||||
function dep(options) {
|
||||
var depsByKey = new Map();
|
||||
var subscribe = options && options.subscribe;
|
||||
function depend(key) {
|
||||
var parent = parentEntrySlot.getValue();
|
||||
if (parent) {
|
||||
var dep_1 = depsByKey.get(key);
|
||||
if (!dep_1) {
|
||||
depsByKey.set(key, dep_1 = new Set);
|
||||
}
|
||||
parent.dependOn(dep_1);
|
||||
if (typeof subscribe === "function") {
|
||||
maybeUnsubscribe(dep_1);
|
||||
dep_1.unsubscribe = subscribe(key);
|
||||
}
|
||||
}
|
||||
}
|
||||
depend.dirty = function dirty(key, entryMethodName) {
|
||||
var dep = depsByKey.get(key);
|
||||
if (dep) {
|
||||
var m_1 = (entryMethodName &&
|
||||
hasOwnProperty.call(EntryMethods, entryMethodName)) ? entryMethodName : "setDirty";
|
||||
// We have to use arrayFromSet(dep).forEach instead of dep.forEach,
|
||||
// because modifying a Set while iterating over it can cause elements in
|
||||
// the Set to be removed from the Set before they've been iterated over.
|
||||
arrayFromSet(dep).forEach(function (entry) { return entry[m_1](); });
|
||||
depsByKey.delete(key);
|
||||
maybeUnsubscribe(dep);
|
||||
}
|
||||
};
|
||||
return depend;
|
||||
}
|
||||
|
||||
// The defaultMakeCacheKey function is remarkably powerful, because it gives
|
||||
// a unique object for any shallow-identical list of arguments. If you need
|
||||
// to implement a custom makeCacheKey function, you may find it helpful to
|
||||
// delegate the final work to defaultMakeCacheKey, which is why we export it
|
||||
// here. However, you may want to avoid defaultMakeCacheKey if your runtime
|
||||
// does not support WeakMap, or you have the ability to return a string key.
|
||||
// In those cases, just write your own custom makeCacheKey functions.
|
||||
var defaultKeyTrie;
|
||||
function defaultMakeCacheKey() {
|
||||
var args = [];
|
||||
for (var _i = 0; _i < arguments.length; _i++) {
|
||||
args[_i] = arguments[_i];
|
||||
}
|
||||
var trie$1 = defaultKeyTrie || (defaultKeyTrie = new trie.Trie(typeof WeakMap === "function"));
|
||||
return trie$1.lookupArray(args);
|
||||
}
|
||||
var caches = new Set();
|
||||
function wrap(originalFunction, _a) {
|
||||
var _b = _a === void 0 ? Object.create(null) : _a, _c = _b.max, max = _c === void 0 ? Math.pow(2, 16) : _c, keyArgs = _b.keyArgs, _d = _b.makeCacheKey, makeCacheKey = _d === void 0 ? defaultMakeCacheKey : _d, normalizeResult = _b.normalizeResult, subscribe = _b.subscribe, _e = _b.cache, cacheOption = _e === void 0 ? caches$1.StrongCache : _e;
|
||||
var cache = typeof cacheOption === "function"
|
||||
? new cacheOption(max, function (entry) { return entry.dispose(); })
|
||||
: cacheOption;
|
||||
var optimistic = function () {
|
||||
var key = makeCacheKey.apply(null, keyArgs ? keyArgs.apply(null, arguments) : arguments);
|
||||
if (key === void 0) {
|
||||
return originalFunction.apply(null, arguments);
|
||||
}
|
||||
var entry = cache.get(key);
|
||||
if (!entry) {
|
||||
cache.set(key, entry = new Entry(originalFunction));
|
||||
entry.normalizeResult = normalizeResult;
|
||||
entry.subscribe = subscribe;
|
||||
// Give the Entry the ability to trigger cache.delete(key), even though
|
||||
// the Entry itself does not know about key or cache.
|
||||
entry.forget = function () { return cache.delete(key); };
|
||||
}
|
||||
var value = entry.recompute(Array.prototype.slice.call(arguments));
|
||||
// Move this entry to the front of the least-recently used queue,
|
||||
// since we just finished computing its value.
|
||||
cache.set(key, entry);
|
||||
caches.add(cache);
|
||||
// Clean up any excess entries in the cache, but only if there is no
|
||||
// active parent entry, meaning we're not in the middle of a larger
|
||||
// computation that might be flummoxed by the cleaning.
|
||||
if (!parentEntrySlot.hasValue()) {
|
||||
caches.forEach(function (cache) { return cache.clean(); });
|
||||
caches.clear();
|
||||
}
|
||||
return value;
|
||||
};
|
||||
Object.defineProperty(optimistic, "size", {
|
||||
get: function () { return cache.size; },
|
||||
configurable: false,
|
||||
enumerable: false,
|
||||
});
|
||||
Object.freeze(optimistic.options = {
|
||||
max: max,
|
||||
keyArgs: keyArgs,
|
||||
makeCacheKey: makeCacheKey,
|
||||
normalizeResult: normalizeResult,
|
||||
subscribe: subscribe,
|
||||
cache: cache,
|
||||
});
|
||||
function dirtyKey(key) {
|
||||
var entry = key && cache.get(key);
|
||||
if (entry) {
|
||||
entry.setDirty();
|
||||
}
|
||||
}
|
||||
optimistic.dirtyKey = dirtyKey;
|
||||
optimistic.dirty = function dirty() {
|
||||
dirtyKey(makeCacheKey.apply(null, arguments));
|
||||
};
|
||||
function peekKey(key) {
|
||||
var entry = key && cache.get(key);
|
||||
if (entry) {
|
||||
return entry.peek();
|
||||
}
|
||||
}
|
||||
optimistic.peekKey = peekKey;
|
||||
optimistic.peek = function peek() {
|
||||
return peekKey(makeCacheKey.apply(null, arguments));
|
||||
};
|
||||
function forgetKey(key) {
|
||||
return key ? cache.delete(key) : false;
|
||||
}
|
||||
optimistic.forgetKey = forgetKey;
|
||||
optimistic.forget = function forget() {
|
||||
return forgetKey(makeCacheKey.apply(null, arguments));
|
||||
};
|
||||
optimistic.makeCacheKey = makeCacheKey;
|
||||
optimistic.getKey = keyArgs ? function getKey() {
|
||||
return makeCacheKey.apply(null, keyArgs.apply(null, arguments));
|
||||
} : makeCacheKey;
|
||||
return Object.freeze(optimistic);
|
||||
}
|
||||
|
||||
Object.defineProperty(exports, 'KeyTrie', {
|
||||
enumerable: true,
|
||||
get: function () { return trie.Trie; }
|
||||
});
|
||||
Object.defineProperty(exports, 'Slot', {
|
||||
enumerable: true,
|
||||
get: function () { return context.Slot; }
|
||||
});
|
||||
Object.defineProperty(exports, 'asyncFromGen', {
|
||||
enumerable: true,
|
||||
get: function () { return context.asyncFromGen; }
|
||||
});
|
||||
Object.defineProperty(exports, 'bindContext', {
|
||||
enumerable: true,
|
||||
get: function () { return context.bind; }
|
||||
});
|
||||
Object.defineProperty(exports, 'noContext', {
|
||||
enumerable: true,
|
||||
get: function () { return context.noContext; }
|
||||
});
|
||||
Object.defineProperty(exports, 'setTimeout', {
|
||||
enumerable: true,
|
||||
get: function () { return context.setTimeout; }
|
||||
});
|
||||
exports.defaultMakeCacheKey = defaultMakeCacheKey;
|
||||
exports.dep = dep;
|
||||
exports.nonReactive = nonReactive;
|
||||
exports.wrap = wrap;
|
||||
//# sourceMappingURL=bundle.cjs.map
|
||||
1
node_modules/optimism/lib/bundle.cjs.map
generated
vendored
Normal file
1
node_modules/optimism/lib/bundle.cjs.map
generated
vendored
Normal file
File diff suppressed because one or more lines are too long
492
node_modules/optimism/lib/bundle.cjs.native.js
generated
vendored
Normal file
492
node_modules/optimism/lib/bundle.cjs.native.js
generated
vendored
Normal file
@@ -0,0 +1,492 @@
|
||||
'use strict';
|
||||
|
||||
var trie = require('@wry/trie');
|
||||
var caches$1 = require('@wry/caches');
|
||||
var context = require('@wry/context');
|
||||
|
||||
var parentEntrySlot = new context.Slot();
|
||||
function nonReactive(fn) {
|
||||
return parentEntrySlot.withValue(void 0, fn);
|
||||
}
|
||||
|
||||
var hasOwnProperty = Object.prototype.hasOwnProperty;
|
||||
var arrayFromSet = Array.from ||
|
||||
function (set) {
|
||||
var array = [];
|
||||
set.forEach(function (item) { return array.push(item); });
|
||||
return array;
|
||||
};
|
||||
function maybeUnsubscribe(entryOrDep) {
|
||||
var unsubscribe = entryOrDep.unsubscribe;
|
||||
if (typeof unsubscribe === "function") {
|
||||
entryOrDep.unsubscribe = void 0;
|
||||
unsubscribe();
|
||||
}
|
||||
}
|
||||
|
||||
var emptySetPool = [];
|
||||
var POOL_TARGET_SIZE = 100;
|
||||
// Since this package might be used browsers, we should avoid using the
|
||||
// Node built-in assert module.
|
||||
function assert(condition, optionalMessage) {
|
||||
if (!condition) {
|
||||
throw new Error(optionalMessage || "assertion failure");
|
||||
}
|
||||
}
|
||||
function valueIs(a, b) {
|
||||
var len = a.length;
|
||||
return (
|
||||
// Unknown values are not equal to each other.
|
||||
len > 0 &&
|
||||
// Both values must be ordinary (or both exceptional) to be equal.
|
||||
len === b.length &&
|
||||
// The underlying value or exception must be the same.
|
||||
a[len - 1] === b[len - 1]);
|
||||
}
|
||||
function valueGet(value) {
|
||||
switch (value.length) {
|
||||
case 0: throw new Error("unknown value");
|
||||
case 1: return value[0];
|
||||
case 2: throw value[1];
|
||||
}
|
||||
}
|
||||
function valueCopy(value) {
|
||||
return value.slice(0);
|
||||
}
|
||||
var Entry = /** @class */ (function () {
|
||||
function Entry(fn) {
|
||||
this.fn = fn;
|
||||
this.parents = new Set();
|
||||
this.childValues = new Map();
|
||||
// When this Entry has children that are dirty, this property becomes
|
||||
// a Set containing other Entry objects, borrowed from emptySetPool.
|
||||
// When the set becomes empty, it gets recycled back to emptySetPool.
|
||||
this.dirtyChildren = null;
|
||||
this.dirty = true;
|
||||
this.recomputing = false;
|
||||
this.value = [];
|
||||
this.deps = null;
|
||||
++Entry.count;
|
||||
}
|
||||
Entry.prototype.peek = function () {
|
||||
if (this.value.length === 1 && !mightBeDirty(this)) {
|
||||
rememberParent(this);
|
||||
return this.value[0];
|
||||
}
|
||||
};
|
||||
// This is the most important method of the Entry API, because it
|
||||
// determines whether the cached this.value can be returned immediately,
|
||||
// or must be recomputed. The overall performance of the caching system
|
||||
// depends on the truth of the following observations: (1) this.dirty is
|
||||
// usually false, (2) this.dirtyChildren is usually null/empty, and thus
|
||||
// (3) valueGet(this.value) is usually returned without recomputation.
|
||||
Entry.prototype.recompute = function (args) {
|
||||
assert(!this.recomputing, "already recomputing");
|
||||
rememberParent(this);
|
||||
return mightBeDirty(this)
|
||||
? reallyRecompute(this, args)
|
||||
: valueGet(this.value);
|
||||
};
|
||||
Entry.prototype.setDirty = function () {
|
||||
if (this.dirty)
|
||||
return;
|
||||
this.dirty = true;
|
||||
reportDirty(this);
|
||||
// We can go ahead and unsubscribe here, since any further dirty
|
||||
// notifications we receive will be redundant, and unsubscribing may
|
||||
// free up some resources, e.g. file watchers.
|
||||
maybeUnsubscribe(this);
|
||||
};
|
||||
Entry.prototype.dispose = function () {
|
||||
var _this = this;
|
||||
this.setDirty();
|
||||
// Sever any dependency relationships with our own children, so those
|
||||
// children don't retain this parent Entry in their child.parents sets,
|
||||
// thereby preventing it from being fully garbage collected.
|
||||
forgetChildren(this);
|
||||
// Because this entry has been kicked out of the cache (in index.js),
|
||||
// we've lost the ability to find out if/when this entry becomes dirty,
|
||||
// whether that happens through a subscription, because of a direct call
|
||||
// to entry.setDirty(), or because one of its children becomes dirty.
|
||||
// Because of this loss of future information, we have to assume the
|
||||
// worst (that this entry might have become dirty very soon), so we must
|
||||
// immediately mark this entry's parents as dirty. Normally we could
|
||||
// just call entry.setDirty() rather than calling parent.setDirty() for
|
||||
// each parent, but that would leave this entry in parent.childValues
|
||||
// and parent.dirtyChildren, which would prevent the child from being
|
||||
// truly forgotten.
|
||||
eachParent(this, function (parent, child) {
|
||||
parent.setDirty();
|
||||
forgetChild(parent, _this);
|
||||
});
|
||||
};
|
||||
Entry.prototype.forget = function () {
|
||||
// The code that creates Entry objects in index.ts will replace this method
|
||||
// with one that actually removes the Entry from the cache, which will also
|
||||
// trigger the entry.dispose method.
|
||||
this.dispose();
|
||||
};
|
||||
Entry.prototype.dependOn = function (dep) {
|
||||
dep.add(this);
|
||||
if (!this.deps) {
|
||||
this.deps = emptySetPool.pop() || new Set();
|
||||
}
|
||||
this.deps.add(dep);
|
||||
};
|
||||
Entry.prototype.forgetDeps = function () {
|
||||
var _this = this;
|
||||
if (this.deps) {
|
||||
arrayFromSet(this.deps).forEach(function (dep) { return dep.delete(_this); });
|
||||
this.deps.clear();
|
||||
emptySetPool.push(this.deps);
|
||||
this.deps = null;
|
||||
}
|
||||
};
|
||||
Entry.count = 0;
|
||||
return Entry;
|
||||
}());
|
||||
function rememberParent(child) {
|
||||
var parent = parentEntrySlot.getValue();
|
||||
if (parent) {
|
||||
child.parents.add(parent);
|
||||
if (!parent.childValues.has(child)) {
|
||||
parent.childValues.set(child, []);
|
||||
}
|
||||
if (mightBeDirty(child)) {
|
||||
reportDirtyChild(parent, child);
|
||||
}
|
||||
else {
|
||||
reportCleanChild(parent, child);
|
||||
}
|
||||
return parent;
|
||||
}
|
||||
}
|
||||
function reallyRecompute(entry, args) {
|
||||
forgetChildren(entry);
|
||||
// Set entry as the parent entry while calling recomputeNewValue(entry).
|
||||
parentEntrySlot.withValue(entry, recomputeNewValue, [entry, args]);
|
||||
if (maybeSubscribe(entry, args)) {
|
||||
// If we successfully recomputed entry.value and did not fail to
|
||||
// (re)subscribe, then this Entry is no longer explicitly dirty.
|
||||
setClean(entry);
|
||||
}
|
||||
return valueGet(entry.value);
|
||||
}
|
||||
function recomputeNewValue(entry, args) {
|
||||
entry.recomputing = true;
|
||||
var normalizeResult = entry.normalizeResult;
|
||||
var oldValueCopy;
|
||||
if (normalizeResult && entry.value.length === 1) {
|
||||
oldValueCopy = valueCopy(entry.value);
|
||||
}
|
||||
// Make entry.value an empty array, representing an unknown value.
|
||||
entry.value.length = 0;
|
||||
try {
|
||||
// If entry.fn succeeds, entry.value will become a normal Value.
|
||||
entry.value[0] = entry.fn.apply(null, args);
|
||||
// If we have a viable oldValueCopy to compare with the (successfully
|
||||
// recomputed) new entry.value, and they are not already === identical, give
|
||||
// normalizeResult a chance to pick/choose/reuse parts of oldValueCopy[0]
|
||||
// and/or entry.value[0] to determine the final cached entry.value.
|
||||
if (normalizeResult && oldValueCopy && !valueIs(oldValueCopy, entry.value)) {
|
||||
try {
|
||||
entry.value[0] = normalizeResult(entry.value[0], oldValueCopy[0]);
|
||||
}
|
||||
catch (_a) {
|
||||
// If normalizeResult throws, just use the newer value, rather than
|
||||
// saving the exception as entry.value[1].
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (e) {
|
||||
// If entry.fn throws, entry.value will hold that exception.
|
||||
entry.value[1] = e;
|
||||
}
|
||||
// Either way, this line is always reached.
|
||||
entry.recomputing = false;
|
||||
}
|
||||
function mightBeDirty(entry) {
|
||||
return entry.dirty || !!(entry.dirtyChildren && entry.dirtyChildren.size);
|
||||
}
|
||||
function setClean(entry) {
|
||||
entry.dirty = false;
|
||||
if (mightBeDirty(entry)) {
|
||||
// This Entry may still have dirty children, in which case we can't
|
||||
// let our parents know we're clean just yet.
|
||||
return;
|
||||
}
|
||||
reportClean(entry);
|
||||
}
|
||||
function reportDirty(child) {
|
||||
eachParent(child, reportDirtyChild);
|
||||
}
|
||||
function reportClean(child) {
|
||||
eachParent(child, reportCleanChild);
|
||||
}
|
||||
function eachParent(child, callback) {
|
||||
var parentCount = child.parents.size;
|
||||
if (parentCount) {
|
||||
var parents = arrayFromSet(child.parents);
|
||||
for (var i = 0; i < parentCount; ++i) {
|
||||
callback(parents[i], child);
|
||||
}
|
||||
}
|
||||
}
|
||||
// Let a parent Entry know that one of its children may be dirty.
|
||||
function reportDirtyChild(parent, child) {
|
||||
// Must have called rememberParent(child) before calling
|
||||
// reportDirtyChild(parent, child).
|
||||
assert(parent.childValues.has(child));
|
||||
assert(mightBeDirty(child));
|
||||
var parentWasClean = !mightBeDirty(parent);
|
||||
if (!parent.dirtyChildren) {
|
||||
parent.dirtyChildren = emptySetPool.pop() || new Set;
|
||||
}
|
||||
else if (parent.dirtyChildren.has(child)) {
|
||||
// If we already know this child is dirty, then we must have already
|
||||
// informed our own parents that we are dirty, so we can terminate
|
||||
// the recursion early.
|
||||
return;
|
||||
}
|
||||
parent.dirtyChildren.add(child);
|
||||
// If parent was clean before, it just became (possibly) dirty (according to
|
||||
// mightBeDirty), since we just added child to parent.dirtyChildren.
|
||||
if (parentWasClean) {
|
||||
reportDirty(parent);
|
||||
}
|
||||
}
|
||||
// Let a parent Entry know that one of its children is no longer dirty.
|
||||
function reportCleanChild(parent, child) {
|
||||
// Must have called rememberChild(child) before calling
|
||||
// reportCleanChild(parent, child).
|
||||
assert(parent.childValues.has(child));
|
||||
assert(!mightBeDirty(child));
|
||||
var childValue = parent.childValues.get(child);
|
||||
if (childValue.length === 0) {
|
||||
parent.childValues.set(child, valueCopy(child.value));
|
||||
}
|
||||
else if (!valueIs(childValue, child.value)) {
|
||||
parent.setDirty();
|
||||
}
|
||||
removeDirtyChild(parent, child);
|
||||
if (mightBeDirty(parent)) {
|
||||
return;
|
||||
}
|
||||
reportClean(parent);
|
||||
}
|
||||
function removeDirtyChild(parent, child) {
|
||||
var dc = parent.dirtyChildren;
|
||||
if (dc) {
|
||||
dc.delete(child);
|
||||
if (dc.size === 0) {
|
||||
if (emptySetPool.length < POOL_TARGET_SIZE) {
|
||||
emptySetPool.push(dc);
|
||||
}
|
||||
parent.dirtyChildren = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
// Removes all children from this entry and returns an array of the
|
||||
// removed children.
|
||||
function forgetChildren(parent) {
|
||||
if (parent.childValues.size > 0) {
|
||||
parent.childValues.forEach(function (_value, child) {
|
||||
forgetChild(parent, child);
|
||||
});
|
||||
}
|
||||
// Remove this parent Entry from any sets to which it was added by the
|
||||
// addToSet method.
|
||||
parent.forgetDeps();
|
||||
// After we forget all our children, this.dirtyChildren must be empty
|
||||
// and therefore must have been reset to null.
|
||||
assert(parent.dirtyChildren === null);
|
||||
}
|
||||
function forgetChild(parent, child) {
|
||||
child.parents.delete(parent);
|
||||
parent.childValues.delete(child);
|
||||
removeDirtyChild(parent, child);
|
||||
}
|
||||
function maybeSubscribe(entry, args) {
|
||||
if (typeof entry.subscribe === "function") {
|
||||
try {
|
||||
maybeUnsubscribe(entry); // Prevent double subscriptions.
|
||||
entry.unsubscribe = entry.subscribe.apply(null, args);
|
||||
}
|
||||
catch (e) {
|
||||
// If this Entry has a subscribe function and it threw an exception
|
||||
// (or an unsubscribe function it previously returned now throws),
|
||||
// return false to indicate that we were not able to subscribe (or
|
||||
// unsubscribe), and this Entry should remain dirty.
|
||||
entry.setDirty();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
// Returning true indicates either that there was no entry.subscribe
|
||||
// function or that it succeeded.
|
||||
return true;
|
||||
}
|
||||
|
||||
var EntryMethods = {
|
||||
setDirty: true,
|
||||
dispose: true,
|
||||
forget: true, // Fully remove parent Entry from LRU cache and computation graph
|
||||
};
|
||||
function dep(options) {
|
||||
var depsByKey = new Map();
|
||||
var subscribe = options && options.subscribe;
|
||||
function depend(key) {
|
||||
var parent = parentEntrySlot.getValue();
|
||||
if (parent) {
|
||||
var dep_1 = depsByKey.get(key);
|
||||
if (!dep_1) {
|
||||
depsByKey.set(key, dep_1 = new Set);
|
||||
}
|
||||
parent.dependOn(dep_1);
|
||||
if (typeof subscribe === "function") {
|
||||
maybeUnsubscribe(dep_1);
|
||||
dep_1.unsubscribe = subscribe(key);
|
||||
}
|
||||
}
|
||||
}
|
||||
depend.dirty = function dirty(key, entryMethodName) {
|
||||
var dep = depsByKey.get(key);
|
||||
if (dep) {
|
||||
var m_1 = (entryMethodName &&
|
||||
hasOwnProperty.call(EntryMethods, entryMethodName)) ? entryMethodName : "setDirty";
|
||||
// We have to use arrayFromSet(dep).forEach instead of dep.forEach,
|
||||
// because modifying a Set while iterating over it can cause elements in
|
||||
// the Set to be removed from the Set before they've been iterated over.
|
||||
arrayFromSet(dep).forEach(function (entry) { return entry[m_1](); });
|
||||
depsByKey.delete(key);
|
||||
maybeUnsubscribe(dep);
|
||||
}
|
||||
};
|
||||
return depend;
|
||||
}
|
||||
|
||||
// The defaultMakeCacheKey function is remarkably powerful, because it gives
|
||||
// a unique object for any shallow-identical list of arguments. If you need
|
||||
// to implement a custom makeCacheKey function, you may find it helpful to
|
||||
// delegate the final work to defaultMakeCacheKey, which is why we export it
|
||||
// here. However, you may want to avoid defaultMakeCacheKey if your runtime
|
||||
// does not support WeakMap, or you have the ability to return a string key.
|
||||
// In those cases, just write your own custom makeCacheKey functions.
|
||||
var defaultKeyTrie;
|
||||
function defaultMakeCacheKey() {
|
||||
var args = [];
|
||||
for (var _i = 0; _i < arguments.length; _i++) {
|
||||
args[_i] = arguments[_i];
|
||||
}
|
||||
var trie$1 = defaultKeyTrie || (defaultKeyTrie = new trie.Trie(typeof WeakMap === "function"));
|
||||
return trie$1.lookupArray(args);
|
||||
}
|
||||
var caches = new Set();
|
||||
function wrap(originalFunction, _a) {
|
||||
var _b = _a === void 0 ? Object.create(null) : _a, _c = _b.max, max = _c === void 0 ? Math.pow(2, 16) : _c, keyArgs = _b.keyArgs, _d = _b.makeCacheKey, makeCacheKey = _d === void 0 ? defaultMakeCacheKey : _d, normalizeResult = _b.normalizeResult, subscribe = _b.subscribe, _e = _b.cache, cacheOption = _e === void 0 ? caches$1.StrongCache : _e;
|
||||
var cache = typeof cacheOption === "function"
|
||||
? new cacheOption(max, function (entry) { return entry.dispose(); })
|
||||
: cacheOption;
|
||||
var optimistic = function () {
|
||||
var key = makeCacheKey.apply(null, keyArgs ? keyArgs.apply(null, arguments) : arguments);
|
||||
if (key === void 0) {
|
||||
return originalFunction.apply(null, arguments);
|
||||
}
|
||||
var entry = cache.get(key);
|
||||
if (!entry) {
|
||||
cache.set(key, entry = new Entry(originalFunction));
|
||||
entry.normalizeResult = normalizeResult;
|
||||
entry.subscribe = subscribe;
|
||||
// Give the Entry the ability to trigger cache.delete(key), even though
|
||||
// the Entry itself does not know about key or cache.
|
||||
entry.forget = function () { return cache.delete(key); };
|
||||
}
|
||||
var value = entry.recompute(Array.prototype.slice.call(arguments));
|
||||
// Move this entry to the front of the least-recently used queue,
|
||||
// since we just finished computing its value.
|
||||
cache.set(key, entry);
|
||||
caches.add(cache);
|
||||
// Clean up any excess entries in the cache, but only if there is no
|
||||
// active parent entry, meaning we're not in the middle of a larger
|
||||
// computation that might be flummoxed by the cleaning.
|
||||
if (!parentEntrySlot.hasValue()) {
|
||||
caches.forEach(function (cache) { return cache.clean(); });
|
||||
caches.clear();
|
||||
}
|
||||
return value;
|
||||
};
|
||||
Object.defineProperty(optimistic, "size", {
|
||||
get: function () { return cache.size; },
|
||||
configurable: false,
|
||||
enumerable: false,
|
||||
});
|
||||
Object.freeze(optimistic.options = {
|
||||
max: max,
|
||||
keyArgs: keyArgs,
|
||||
makeCacheKey: makeCacheKey,
|
||||
normalizeResult: normalizeResult,
|
||||
subscribe: subscribe,
|
||||
cache: cache,
|
||||
});
|
||||
function dirtyKey(key) {
|
||||
var entry = key && cache.get(key);
|
||||
if (entry) {
|
||||
entry.setDirty();
|
||||
}
|
||||
}
|
||||
optimistic.dirtyKey = dirtyKey;
|
||||
optimistic.dirty = function dirty() {
|
||||
dirtyKey(makeCacheKey.apply(null, arguments));
|
||||
};
|
||||
function peekKey(key) {
|
||||
var entry = key && cache.get(key);
|
||||
if (entry) {
|
||||
return entry.peek();
|
||||
}
|
||||
}
|
||||
optimistic.peekKey = peekKey;
|
||||
optimistic.peek = function peek() {
|
||||
return peekKey(makeCacheKey.apply(null, arguments));
|
||||
};
|
||||
function forgetKey(key) {
|
||||
return key ? cache.delete(key) : false;
|
||||
}
|
||||
optimistic.forgetKey = forgetKey;
|
||||
optimistic.forget = function forget() {
|
||||
return forgetKey(makeCacheKey.apply(null, arguments));
|
||||
};
|
||||
optimistic.makeCacheKey = makeCacheKey;
|
||||
optimistic.getKey = keyArgs ? function getKey() {
|
||||
return makeCacheKey.apply(null, keyArgs.apply(null, arguments));
|
||||
} : makeCacheKey;
|
||||
return Object.freeze(optimistic);
|
||||
}
|
||||
|
||||
Object.defineProperty(exports, 'KeyTrie', {
|
||||
enumerable: true,
|
||||
get: function () { return trie.Trie; }
|
||||
});
|
||||
Object.defineProperty(exports, 'Slot', {
|
||||
enumerable: true,
|
||||
get: function () { return context.Slot; }
|
||||
});
|
||||
Object.defineProperty(exports, 'asyncFromGen', {
|
||||
enumerable: true,
|
||||
get: function () { return context.asyncFromGen; }
|
||||
});
|
||||
Object.defineProperty(exports, 'bindContext', {
|
||||
enumerable: true,
|
||||
get: function () { return context.bind; }
|
||||
});
|
||||
Object.defineProperty(exports, 'noContext', {
|
||||
enumerable: true,
|
||||
get: function () { return context.noContext; }
|
||||
});
|
||||
Object.defineProperty(exports, 'setTimeout', {
|
||||
enumerable: true,
|
||||
get: function () { return context.setTimeout; }
|
||||
});
|
||||
exports.defaultMakeCacheKey = defaultMakeCacheKey;
|
||||
exports.dep = dep;
|
||||
exports.nonReactive = nonReactive;
|
||||
exports.wrap = wrap;
|
||||
//# sourceMappingURL=bundle.cjs.map
|
||||
11
node_modules/optimism/lib/context.d.ts
generated
vendored
Normal file
11
node_modules/optimism/lib/context.d.ts
generated
vendored
Normal file
@@ -0,0 +1,11 @@
|
||||
import { Slot } from "@wry/context";
|
||||
import { AnyEntry } from "./entry.js";
|
||||
export declare const parentEntrySlot: {
|
||||
readonly id: string;
|
||||
hasValue(): boolean;
|
||||
getValue(): AnyEntry | undefined;
|
||||
withValue<TResult, TArgs extends any[], TThis = any>(value: AnyEntry | undefined, callback: (this: TThis, ...args: TArgs) => TResult, args?: TArgs | undefined, thisArg?: TThis | undefined): TResult;
|
||||
};
|
||||
export declare function nonReactive<R>(fn: () => R): R;
|
||||
export { Slot };
|
||||
export { bind as bindContext, noContext, setTimeout, asyncFromGen, } from "@wry/context";
|
||||
8
node_modules/optimism/lib/context.js
generated
vendored
Normal file
8
node_modules/optimism/lib/context.js
generated
vendored
Normal file
@@ -0,0 +1,8 @@
|
||||
import { Slot } from "@wry/context";
|
||||
export const parentEntrySlot = new Slot();
|
||||
export function nonReactive(fn) {
|
||||
return parentEntrySlot.withValue(void 0, fn);
|
||||
}
|
||||
export { Slot };
|
||||
export { bind as bindContext, noContext, setTimeout, asyncFromGen, } from "@wry/context";
|
||||
//# sourceMappingURL=context.js.map
|
||||
1
node_modules/optimism/lib/context.js.map
generated
vendored
Normal file
1
node_modules/optimism/lib/context.js.map
generated
vendored
Normal file
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"context.js","sourceRoot":"","sources":["../src/context.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,cAAc,CAAC;AAGpC,MAAM,CAAC,MAAM,eAAe,GAAG,IAAI,IAAI,EAAwB,CAAC;AAEhE,MAAM,UAAU,WAAW,CAAI,EAAW;IACxC,OAAO,eAAe,CAAC,SAAS,CAAC,KAAK,CAAC,EAAE,EAAE,CAAC,CAAC;AAC/C,CAAC;AAED,OAAO,EAAE,IAAI,EAAE,CAAA;AACf,OAAO,EACL,IAAI,IAAI,WAAW,EACnB,SAAS,EACT,UAAU,EACV,YAAY,GACb,MAAM,cAAc,CAAC"}
|
||||
19
node_modules/optimism/lib/dep.d.ts
generated
vendored
Normal file
19
node_modules/optimism/lib/dep.d.ts
generated
vendored
Normal file
@@ -0,0 +1,19 @@
|
||||
import { AnyEntry } from "./entry.js";
|
||||
import { OptimisticWrapOptions } from "./index.js";
|
||||
import { Unsubscribable } from "./helpers.js";
|
||||
type EntryMethodName = keyof typeof EntryMethods;
|
||||
declare const EntryMethods: {
|
||||
setDirty: boolean;
|
||||
dispose: boolean;
|
||||
forget: boolean;
|
||||
};
|
||||
export type OptimisticDependencyFunction<TKey> = ((key: TKey) => void) & {
|
||||
dirty: (key: TKey, entryMethodName?: EntryMethodName) => void;
|
||||
};
|
||||
export type Dep<TKey> = Set<AnyEntry> & {
|
||||
subscribe: OptimisticWrapOptions<[TKey]>["subscribe"];
|
||||
} & Unsubscribable;
|
||||
export declare function dep<TKey>(options?: {
|
||||
subscribe: Dep<TKey>["subscribe"];
|
||||
}): OptimisticDependencyFunction<TKey>;
|
||||
export {};
|
||||
40
node_modules/optimism/lib/dep.js
generated
vendored
Normal file
40
node_modules/optimism/lib/dep.js
generated
vendored
Normal file
@@ -0,0 +1,40 @@
|
||||
import { parentEntrySlot } from "./context.js";
|
||||
import { hasOwnProperty, maybeUnsubscribe, arrayFromSet, } from "./helpers.js";
|
||||
const EntryMethods = {
|
||||
setDirty: true,
|
||||
dispose: true,
|
||||
forget: true, // Fully remove parent Entry from LRU cache and computation graph
|
||||
};
|
||||
export function dep(options) {
|
||||
const depsByKey = new Map();
|
||||
const subscribe = options && options.subscribe;
|
||||
function depend(key) {
|
||||
const parent = parentEntrySlot.getValue();
|
||||
if (parent) {
|
||||
let dep = depsByKey.get(key);
|
||||
if (!dep) {
|
||||
depsByKey.set(key, dep = new Set);
|
||||
}
|
||||
parent.dependOn(dep);
|
||||
if (typeof subscribe === "function") {
|
||||
maybeUnsubscribe(dep);
|
||||
dep.unsubscribe = subscribe(key);
|
||||
}
|
||||
}
|
||||
}
|
||||
depend.dirty = function dirty(key, entryMethodName) {
|
||||
const dep = depsByKey.get(key);
|
||||
if (dep) {
|
||||
const m = (entryMethodName &&
|
||||
hasOwnProperty.call(EntryMethods, entryMethodName)) ? entryMethodName : "setDirty";
|
||||
// We have to use arrayFromSet(dep).forEach instead of dep.forEach,
|
||||
// because modifying a Set while iterating over it can cause elements in
|
||||
// the Set to be removed from the Set before they've been iterated over.
|
||||
arrayFromSet(dep).forEach(entry => entry[m]());
|
||||
depsByKey.delete(key);
|
||||
maybeUnsubscribe(dep);
|
||||
}
|
||||
};
|
||||
return depend;
|
||||
}
|
||||
//# sourceMappingURL=dep.js.map
|
||||
1
node_modules/optimism/lib/dep.js.map
generated
vendored
Normal file
1
node_modules/optimism/lib/dep.js.map
generated
vendored
Normal file
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"dep.js","sourceRoot":"","sources":["../src/dep.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC;AAC/C,OAAO,EACL,cAAc,EAEd,gBAAgB,EAChB,YAAY,GACZ,MAAM,cAAc,CAAC;AAGvB,MAAM,YAAY,GAAG;IACnB,QAAQ,EAAE,IAAI;IACd,OAAO,EAAE,IAAI;IACb,MAAM,EAAE,IAAI,EAAI,iEAAiE;CAClF,CAAC;AAWF,MAAM,UAAU,GAAG,CAAO,OAEzB;IACC,MAAM,SAAS,GAAG,IAAI,GAAG,EAAmB,CAAC;IAC7C,MAAM,SAAS,GAAG,OAAO,IAAI,OAAO,CAAC,SAAS,CAAC;IAE/C,SAAS,MAAM,CAAC,GAAS;QACvB,MAAM,MAAM,GAAG,eAAe,CAAC,QAAQ,EAAE,CAAC;QAC1C,IAAI,MAAM,EAAE;YACV,IAAI,GAAG,GAAG,SAAS,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;YAC7B,IAAI,CAAC,GAAG,EAAE;gBACR,SAAS,CAAC,GAAG,CAAC,GAAG,EAAE,GAAG,GAAG,IAAI,GAAgB,CAAC,CAAC;aAChD;YACD,MAAM,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC;YACrB,IAAI,OAAO,SAAS,KAAK,UAAU,EAAE;gBACnC,gBAAgB,CAAC,GAAG,CAAC,CAAC;gBACtB,GAAG,CAAC,WAAW,GAAG,SAAS,CAAC,GAAG,CAAC,CAAC;aAClC;SACF;IACH,CAAC;IAED,MAAM,CAAC,KAAK,GAAG,SAAS,KAAK,CAC3B,GAAS,EACT,eAAiC;QAEjC,MAAM,GAAG,GAAG,SAAS,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;QAC/B,IAAI,GAAG,EAAE;YACP,MAAM,CAAC,GAAoB,CACzB,eAAe;gBACf,cAAc,CAAC,IAAI,CAAC,YAAY,EAAE,eAAe,CAAC,CACnD,CAAC,CAAC,CAAC,eAAe,CAAC,CAAC,CAAC,UAAU,CAAC;YACjC,mEAAmE;YACnE,wEAAwE;YACxE,wEAAwE;YACxE,YAAY,CAAC,GAAG,CAAC,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;YAC/C,SAAS,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;YACtB,gBAAgB,CAAC,GAAG,CAAC,CAAC;SACvB;IACH,CAAC,CAAC;IAEF,OAAO,MAA4C,CAAC;AACtD,CAAC"}
|
||||
28
node_modules/optimism/lib/entry.d.ts
generated
vendored
Normal file
28
node_modules/optimism/lib/entry.d.ts
generated
vendored
Normal file
@@ -0,0 +1,28 @@
|
||||
import { OptimisticWrapOptions } from "./index.js";
|
||||
import { Dep } from "./dep.js";
|
||||
import { Unsubscribable } from "./helpers.js";
|
||||
type Value<T> = [] | [T] | [void, any];
|
||||
export type AnyEntry = Entry<any, any>;
|
||||
export declare class Entry<TArgs extends any[], TValue> {
|
||||
readonly fn: (...args: TArgs) => TValue;
|
||||
static count: number;
|
||||
normalizeResult: OptimisticWrapOptions<TArgs, any, any, TValue>["normalizeResult"];
|
||||
subscribe: OptimisticWrapOptions<TArgs>["subscribe"];
|
||||
unsubscribe: Unsubscribable["unsubscribe"];
|
||||
readonly parents: Set<AnyEntry>;
|
||||
readonly childValues: Map<AnyEntry, Value<any>>;
|
||||
dirtyChildren: Set<AnyEntry> | null;
|
||||
dirty: boolean;
|
||||
recomputing: boolean;
|
||||
readonly value: Value<TValue>;
|
||||
constructor(fn: (...args: TArgs) => TValue);
|
||||
peek(): TValue | undefined;
|
||||
recompute(args: TArgs): TValue;
|
||||
setDirty(): void;
|
||||
dispose(): void;
|
||||
forget(): void;
|
||||
private deps;
|
||||
dependOn(dep: Dep<any>): void;
|
||||
forgetDeps(): void;
|
||||
}
|
||||
export {};
|
||||
301
node_modules/optimism/lib/entry.js
generated
vendored
Normal file
301
node_modules/optimism/lib/entry.js
generated
vendored
Normal file
@@ -0,0 +1,301 @@
|
||||
import { parentEntrySlot } from "./context.js";
|
||||
import { maybeUnsubscribe, arrayFromSet } from "./helpers.js";
|
||||
const emptySetPool = [];
|
||||
const POOL_TARGET_SIZE = 100;
|
||||
// Since this package might be used browsers, we should avoid using the
|
||||
// Node built-in assert module.
|
||||
function assert(condition, optionalMessage) {
|
||||
if (!condition) {
|
||||
throw new Error(optionalMessage || "assertion failure");
|
||||
}
|
||||
}
|
||||
function valueIs(a, b) {
|
||||
const len = a.length;
|
||||
return (
|
||||
// Unknown values are not equal to each other.
|
||||
len > 0 &&
|
||||
// Both values must be ordinary (or both exceptional) to be equal.
|
||||
len === b.length &&
|
||||
// The underlying value or exception must be the same.
|
||||
a[len - 1] === b[len - 1]);
|
||||
}
|
||||
function valueGet(value) {
|
||||
switch (value.length) {
|
||||
case 0: throw new Error("unknown value");
|
||||
case 1: return value[0];
|
||||
case 2: throw value[1];
|
||||
}
|
||||
}
|
||||
function valueCopy(value) {
|
||||
return value.slice(0);
|
||||
}
|
||||
export class Entry {
|
||||
constructor(fn) {
|
||||
this.fn = fn;
|
||||
this.parents = new Set();
|
||||
this.childValues = new Map();
|
||||
// When this Entry has children that are dirty, this property becomes
|
||||
// a Set containing other Entry objects, borrowed from emptySetPool.
|
||||
// When the set becomes empty, it gets recycled back to emptySetPool.
|
||||
this.dirtyChildren = null;
|
||||
this.dirty = true;
|
||||
this.recomputing = false;
|
||||
this.value = [];
|
||||
this.deps = null;
|
||||
++Entry.count;
|
||||
}
|
||||
peek() {
|
||||
if (this.value.length === 1 && !mightBeDirty(this)) {
|
||||
rememberParent(this);
|
||||
return this.value[0];
|
||||
}
|
||||
}
|
||||
// This is the most important method of the Entry API, because it
|
||||
// determines whether the cached this.value can be returned immediately,
|
||||
// or must be recomputed. The overall performance of the caching system
|
||||
// depends on the truth of the following observations: (1) this.dirty is
|
||||
// usually false, (2) this.dirtyChildren is usually null/empty, and thus
|
||||
// (3) valueGet(this.value) is usually returned without recomputation.
|
||||
recompute(args) {
|
||||
assert(!this.recomputing, "already recomputing");
|
||||
rememberParent(this);
|
||||
return mightBeDirty(this)
|
||||
? reallyRecompute(this, args)
|
||||
: valueGet(this.value);
|
||||
}
|
||||
setDirty() {
|
||||
if (this.dirty)
|
||||
return;
|
||||
this.dirty = true;
|
||||
reportDirty(this);
|
||||
// We can go ahead and unsubscribe here, since any further dirty
|
||||
// notifications we receive will be redundant, and unsubscribing may
|
||||
// free up some resources, e.g. file watchers.
|
||||
maybeUnsubscribe(this);
|
||||
}
|
||||
dispose() {
|
||||
this.setDirty();
|
||||
// Sever any dependency relationships with our own children, so those
|
||||
// children don't retain this parent Entry in their child.parents sets,
|
||||
// thereby preventing it from being fully garbage collected.
|
||||
forgetChildren(this);
|
||||
// Because this entry has been kicked out of the cache (in index.js),
|
||||
// we've lost the ability to find out if/when this entry becomes dirty,
|
||||
// whether that happens through a subscription, because of a direct call
|
||||
// to entry.setDirty(), or because one of its children becomes dirty.
|
||||
// Because of this loss of future information, we have to assume the
|
||||
// worst (that this entry might have become dirty very soon), so we must
|
||||
// immediately mark this entry's parents as dirty. Normally we could
|
||||
// just call entry.setDirty() rather than calling parent.setDirty() for
|
||||
// each parent, but that would leave this entry in parent.childValues
|
||||
// and parent.dirtyChildren, which would prevent the child from being
|
||||
// truly forgotten.
|
||||
eachParent(this, (parent, child) => {
|
||||
parent.setDirty();
|
||||
forgetChild(parent, this);
|
||||
});
|
||||
}
|
||||
forget() {
|
||||
// The code that creates Entry objects in index.ts will replace this method
|
||||
// with one that actually removes the Entry from the cache, which will also
|
||||
// trigger the entry.dispose method.
|
||||
this.dispose();
|
||||
}
|
||||
dependOn(dep) {
|
||||
dep.add(this);
|
||||
if (!this.deps) {
|
||||
this.deps = emptySetPool.pop() || new Set();
|
||||
}
|
||||
this.deps.add(dep);
|
||||
}
|
||||
forgetDeps() {
|
||||
if (this.deps) {
|
||||
arrayFromSet(this.deps).forEach(dep => dep.delete(this));
|
||||
this.deps.clear();
|
||||
emptySetPool.push(this.deps);
|
||||
this.deps = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
Entry.count = 0;
|
||||
function rememberParent(child) {
|
||||
const parent = parentEntrySlot.getValue();
|
||||
if (parent) {
|
||||
child.parents.add(parent);
|
||||
if (!parent.childValues.has(child)) {
|
||||
parent.childValues.set(child, []);
|
||||
}
|
||||
if (mightBeDirty(child)) {
|
||||
reportDirtyChild(parent, child);
|
||||
}
|
||||
else {
|
||||
reportCleanChild(parent, child);
|
||||
}
|
||||
return parent;
|
||||
}
|
||||
}
|
||||
function reallyRecompute(entry, args) {
|
||||
forgetChildren(entry);
|
||||
// Set entry as the parent entry while calling recomputeNewValue(entry).
|
||||
parentEntrySlot.withValue(entry, recomputeNewValue, [entry, args]);
|
||||
if (maybeSubscribe(entry, args)) {
|
||||
// If we successfully recomputed entry.value and did not fail to
|
||||
// (re)subscribe, then this Entry is no longer explicitly dirty.
|
||||
setClean(entry);
|
||||
}
|
||||
return valueGet(entry.value);
|
||||
}
|
||||
function recomputeNewValue(entry, args) {
|
||||
entry.recomputing = true;
|
||||
const { normalizeResult } = entry;
|
||||
let oldValueCopy;
|
||||
if (normalizeResult && entry.value.length === 1) {
|
||||
oldValueCopy = valueCopy(entry.value);
|
||||
}
|
||||
// Make entry.value an empty array, representing an unknown value.
|
||||
entry.value.length = 0;
|
||||
try {
|
||||
// If entry.fn succeeds, entry.value will become a normal Value.
|
||||
entry.value[0] = entry.fn.apply(null, args);
|
||||
// If we have a viable oldValueCopy to compare with the (successfully
|
||||
// recomputed) new entry.value, and they are not already === identical, give
|
||||
// normalizeResult a chance to pick/choose/reuse parts of oldValueCopy[0]
|
||||
// and/or entry.value[0] to determine the final cached entry.value.
|
||||
if (normalizeResult && oldValueCopy && !valueIs(oldValueCopy, entry.value)) {
|
||||
try {
|
||||
entry.value[0] = normalizeResult(entry.value[0], oldValueCopy[0]);
|
||||
}
|
||||
catch (_a) {
|
||||
// If normalizeResult throws, just use the newer value, rather than
|
||||
// saving the exception as entry.value[1].
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (e) {
|
||||
// If entry.fn throws, entry.value will hold that exception.
|
||||
entry.value[1] = e;
|
||||
}
|
||||
// Either way, this line is always reached.
|
||||
entry.recomputing = false;
|
||||
}
|
||||
function mightBeDirty(entry) {
|
||||
return entry.dirty || !!(entry.dirtyChildren && entry.dirtyChildren.size);
|
||||
}
|
||||
function setClean(entry) {
|
||||
entry.dirty = false;
|
||||
if (mightBeDirty(entry)) {
|
||||
// This Entry may still have dirty children, in which case we can't
|
||||
// let our parents know we're clean just yet.
|
||||
return;
|
||||
}
|
||||
reportClean(entry);
|
||||
}
|
||||
function reportDirty(child) {
|
||||
eachParent(child, reportDirtyChild);
|
||||
}
|
||||
function reportClean(child) {
|
||||
eachParent(child, reportCleanChild);
|
||||
}
|
||||
function eachParent(child, callback) {
|
||||
const parentCount = child.parents.size;
|
||||
if (parentCount) {
|
||||
const parents = arrayFromSet(child.parents);
|
||||
for (let i = 0; i < parentCount; ++i) {
|
||||
callback(parents[i], child);
|
||||
}
|
||||
}
|
||||
}
|
||||
// Let a parent Entry know that one of its children may be dirty.
|
||||
function reportDirtyChild(parent, child) {
|
||||
// Must have called rememberParent(child) before calling
|
||||
// reportDirtyChild(parent, child).
|
||||
assert(parent.childValues.has(child));
|
||||
assert(mightBeDirty(child));
|
||||
const parentWasClean = !mightBeDirty(parent);
|
||||
if (!parent.dirtyChildren) {
|
||||
parent.dirtyChildren = emptySetPool.pop() || new Set;
|
||||
}
|
||||
else if (parent.dirtyChildren.has(child)) {
|
||||
// If we already know this child is dirty, then we must have already
|
||||
// informed our own parents that we are dirty, so we can terminate
|
||||
// the recursion early.
|
||||
return;
|
||||
}
|
||||
parent.dirtyChildren.add(child);
|
||||
// If parent was clean before, it just became (possibly) dirty (according to
|
||||
// mightBeDirty), since we just added child to parent.dirtyChildren.
|
||||
if (parentWasClean) {
|
||||
reportDirty(parent);
|
||||
}
|
||||
}
|
||||
// Let a parent Entry know that one of its children is no longer dirty.
|
||||
function reportCleanChild(parent, child) {
|
||||
// Must have called rememberChild(child) before calling
|
||||
// reportCleanChild(parent, child).
|
||||
assert(parent.childValues.has(child));
|
||||
assert(!mightBeDirty(child));
|
||||
const childValue = parent.childValues.get(child);
|
||||
if (childValue.length === 0) {
|
||||
parent.childValues.set(child, valueCopy(child.value));
|
||||
}
|
||||
else if (!valueIs(childValue, child.value)) {
|
||||
parent.setDirty();
|
||||
}
|
||||
removeDirtyChild(parent, child);
|
||||
if (mightBeDirty(parent)) {
|
||||
return;
|
||||
}
|
||||
reportClean(parent);
|
||||
}
|
||||
function removeDirtyChild(parent, child) {
|
||||
const dc = parent.dirtyChildren;
|
||||
if (dc) {
|
||||
dc.delete(child);
|
||||
if (dc.size === 0) {
|
||||
if (emptySetPool.length < POOL_TARGET_SIZE) {
|
||||
emptySetPool.push(dc);
|
||||
}
|
||||
parent.dirtyChildren = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
// Removes all children from this entry and returns an array of the
|
||||
// removed children.
|
||||
function forgetChildren(parent) {
|
||||
if (parent.childValues.size > 0) {
|
||||
parent.childValues.forEach((_value, child) => {
|
||||
forgetChild(parent, child);
|
||||
});
|
||||
}
|
||||
// Remove this parent Entry from any sets to which it was added by the
|
||||
// addToSet method.
|
||||
parent.forgetDeps();
|
||||
// After we forget all our children, this.dirtyChildren must be empty
|
||||
// and therefore must have been reset to null.
|
||||
assert(parent.dirtyChildren === null);
|
||||
}
|
||||
function forgetChild(parent, child) {
|
||||
child.parents.delete(parent);
|
||||
parent.childValues.delete(child);
|
||||
removeDirtyChild(parent, child);
|
||||
}
|
||||
function maybeSubscribe(entry, args) {
|
||||
if (typeof entry.subscribe === "function") {
|
||||
try {
|
||||
maybeUnsubscribe(entry); // Prevent double subscriptions.
|
||||
entry.unsubscribe = entry.subscribe.apply(null, args);
|
||||
}
|
||||
catch (e) {
|
||||
// If this Entry has a subscribe function and it threw an exception
|
||||
// (or an unsubscribe function it previously returned now throws),
|
||||
// return false to indicate that we were not able to subscribe (or
|
||||
// unsubscribe), and this Entry should remain dirty.
|
||||
entry.setDirty();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
// Returning true indicates either that there was no entry.subscribe
|
||||
// function or that it succeeded.
|
||||
return true;
|
||||
}
|
||||
//# sourceMappingURL=entry.js.map
|
||||
1
node_modules/optimism/lib/entry.js.map
generated
vendored
Normal file
1
node_modules/optimism/lib/entry.js.map
generated
vendored
Normal file
File diff suppressed because one or more lines are too long
7
node_modules/optimism/lib/helpers.d.ts
generated
vendored
Normal file
7
node_modules/optimism/lib/helpers.d.ts
generated
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
export type NoInfer<T> = [T][T extends any ? 0 : never];
|
||||
export declare const hasOwnProperty: (v: PropertyKey) => boolean;
|
||||
export declare const arrayFromSet: <T>(set: Set<T>) => T[];
|
||||
export type Unsubscribable = {
|
||||
unsubscribe?: void | (() => any);
|
||||
};
|
||||
export declare function maybeUnsubscribe(entryOrDep: Unsubscribable): void;
|
||||
15
node_modules/optimism/lib/helpers.js
generated
vendored
Normal file
15
node_modules/optimism/lib/helpers.js
generated
vendored
Normal file
@@ -0,0 +1,15 @@
|
||||
export const { hasOwnProperty, } = Object.prototype;
|
||||
export const arrayFromSet = Array.from ||
|
||||
function (set) {
|
||||
const array = [];
|
||||
set.forEach(item => array.push(item));
|
||||
return array;
|
||||
};
|
||||
export function maybeUnsubscribe(entryOrDep) {
|
||||
const { unsubscribe } = entryOrDep;
|
||||
if (typeof unsubscribe === "function") {
|
||||
entryOrDep.unsubscribe = void 0;
|
||||
unsubscribe();
|
||||
}
|
||||
}
|
||||
//# sourceMappingURL=helpers.js.map
|
||||
1
node_modules/optimism/lib/helpers.js.map
generated
vendored
Normal file
1
node_modules/optimism/lib/helpers.js.map
generated
vendored
Normal file
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"helpers.js","sourceRoot":"","sources":["../src/helpers.ts"],"names":[],"mappings":"AAEA,MAAM,CAAC,MAAM,EACX,cAAc,GACf,GAAG,MAAM,CAAC,SAAS,CAAC;AAErB,MAAM,CAAC,MAAM,YAAY,GACvB,KAAK,CAAC,IAAI;IACV,UAAU,GAAG;QACX,MAAM,KAAK,GAAU,EAAE,CAAC;QACxB,GAAG,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC;QACtC,OAAO,KAAK,CAAC;IACf,CAAC,CAAC;AAMJ,MAAM,UAAU,gBAAgB,CAAC,UAA0B;IACzD,MAAM,EAAE,WAAW,EAAE,GAAG,UAAU,CAAC;IACnC,IAAI,OAAO,WAAW,KAAK,UAAU,EAAE;QACrC,UAAU,CAAC,WAAW,GAAG,KAAK,CAAC,CAAC;QAChC,WAAW,EAAE,CAAC;KACf;AACH,CAAC"}
|
||||
36
node_modules/optimism/lib/index.d.ts
generated
vendored
Normal file
36
node_modules/optimism/lib/index.d.ts
generated
vendored
Normal file
@@ -0,0 +1,36 @@
|
||||
import { Trie } from "@wry/trie";
|
||||
import { CommonCache } from "@wry/caches";
|
||||
import { Entry } from "./entry.js";
|
||||
import type { NoInfer } from "./helpers.js";
|
||||
export { bindContext, noContext, nonReactive, setTimeout, asyncFromGen, Slot, } from "./context.js";
|
||||
export { dep, OptimisticDependencyFunction } from "./dep.js";
|
||||
export declare function defaultMakeCacheKey(...args: any[]): object;
|
||||
export { Trie as KeyTrie };
|
||||
export type OptimisticWrapperFunction<TArgs extends any[], TResult, TKeyArgs extends any[] = TArgs, TCacheKey = any> = ((...args: TArgs) => TResult) & {
|
||||
readonly size: number;
|
||||
options: OptionsWithCacheInstance<TArgs, TKeyArgs, TCacheKey>;
|
||||
dirty: (...args: TKeyArgs) => void;
|
||||
dirtyKey: (key: TCacheKey | undefined) => void;
|
||||
peek: (...args: TKeyArgs) => TResult | undefined;
|
||||
peekKey: (key: TCacheKey | undefined) => TResult | undefined;
|
||||
forget: (...args: TKeyArgs) => boolean;
|
||||
forgetKey: (key: TCacheKey | undefined) => boolean;
|
||||
getKey: (...args: TArgs) => TCacheKey | undefined;
|
||||
makeCacheKey: (...args: TKeyArgs) => TCacheKey | undefined;
|
||||
};
|
||||
export { CommonCache };
|
||||
export interface CommonCacheConstructor<TCacheKey, TResult, TArgs extends any[]> extends Function {
|
||||
new <K extends TCacheKey, V extends Entry<TArgs, TResult>>(max?: number, dispose?: (value: V, key?: K) => void): CommonCache<K, V>;
|
||||
}
|
||||
export type OptimisticWrapOptions<TArgs extends any[], TKeyArgs extends any[] = TArgs, TCacheKey = any, TResult = any> = {
|
||||
max?: number;
|
||||
keyArgs?: (...args: TArgs) => TKeyArgs;
|
||||
makeCacheKey?: (...args: NoInfer<TKeyArgs>) => TCacheKey | undefined;
|
||||
normalizeResult?: (newer: TResult, older: TResult) => TResult;
|
||||
subscribe?: (...args: TArgs) => void | (() => any);
|
||||
cache?: CommonCache<NoInfer<TCacheKey>, Entry<NoInfer<TArgs>, NoInfer<TResult>>> | CommonCacheConstructor<NoInfer<TCacheKey>, NoInfer<TResult>, NoInfer<TArgs>>;
|
||||
};
|
||||
export interface OptionsWithCacheInstance<TArgs extends any[], TKeyArgs extends any[] = TArgs, TCacheKey = any, TResult = any> extends OptimisticWrapOptions<TArgs, TKeyArgs, TCacheKey, TResult> {
|
||||
cache: CommonCache<NoInfer<TCacheKey>, Entry<NoInfer<TArgs>, NoInfer<TResult>>>;
|
||||
}
|
||||
export declare function wrap<TArgs extends any[], TResult, TKeyArgs extends any[] = TArgs, TCacheKey = any>(originalFunction: (...args: TArgs) => TResult, { max, keyArgs, makeCacheKey, normalizeResult, subscribe, cache: cacheOption, }?: OptimisticWrapOptions<TArgs, TKeyArgs, TCacheKey, TResult>): OptimisticWrapperFunction<TArgs, TResult, TKeyArgs, TCacheKey>;
|
||||
113
node_modules/optimism/lib/index.js
generated
vendored
Normal file
113
node_modules/optimism/lib/index.js
generated
vendored
Normal file
@@ -0,0 +1,113 @@
|
||||
import { Trie } from "@wry/trie";
|
||||
import { StrongCache } from "@wry/caches";
|
||||
import { Entry } from "./entry.js";
|
||||
import { parentEntrySlot } from "./context.js";
|
||||
// These helper functions are important for making optimism work with
|
||||
// asynchronous code. In order to register parent-child dependencies,
|
||||
// optimism needs to know about any currently active parent computations.
|
||||
// In ordinary synchronous code, the parent context is implicit in the
|
||||
// execution stack, but asynchronous code requires some extra guidance in
|
||||
// order to propagate context from one async task segment to the next.
|
||||
export { bindContext, noContext, nonReactive, setTimeout, asyncFromGen, Slot, } from "./context.js";
|
||||
// A lighter-weight dependency, similar to OptimisticWrapperFunction, except
|
||||
// with only one argument, no makeCacheKey, no wrapped function to recompute,
|
||||
// and no result value. Useful for representing dependency leaves in the graph
|
||||
// of computation. Subscriptions are supported.
|
||||
export { dep } from "./dep.js";
|
||||
// The defaultMakeCacheKey function is remarkably powerful, because it gives
|
||||
// a unique object for any shallow-identical list of arguments. If you need
|
||||
// to implement a custom makeCacheKey function, you may find it helpful to
|
||||
// delegate the final work to defaultMakeCacheKey, which is why we export it
|
||||
// here. However, you may want to avoid defaultMakeCacheKey if your runtime
|
||||
// does not support WeakMap, or you have the ability to return a string key.
|
||||
// In those cases, just write your own custom makeCacheKey functions.
|
||||
let defaultKeyTrie;
|
||||
export function defaultMakeCacheKey(...args) {
|
||||
const trie = defaultKeyTrie || (defaultKeyTrie = new Trie(typeof WeakMap === "function"));
|
||||
return trie.lookupArray(args);
|
||||
}
|
||||
// If you're paranoid about memory leaks, or you want to avoid using WeakMap
|
||||
// under the hood, but you still need the behavior of defaultMakeCacheKey,
|
||||
// import this constructor to create your own tries.
|
||||
export { Trie as KeyTrie };
|
||||
;
|
||||
const caches = new Set();
|
||||
export function wrap(originalFunction, { max = Math.pow(2, 16), keyArgs, makeCacheKey = defaultMakeCacheKey, normalizeResult, subscribe, cache: cacheOption = StrongCache, } = Object.create(null)) {
|
||||
const cache = typeof cacheOption === "function"
|
||||
? new cacheOption(max, entry => entry.dispose())
|
||||
: cacheOption;
|
||||
const optimistic = function () {
|
||||
const key = makeCacheKey.apply(null, keyArgs ? keyArgs.apply(null, arguments) : arguments);
|
||||
if (key === void 0) {
|
||||
return originalFunction.apply(null, arguments);
|
||||
}
|
||||
let entry = cache.get(key);
|
||||
if (!entry) {
|
||||
cache.set(key, entry = new Entry(originalFunction));
|
||||
entry.normalizeResult = normalizeResult;
|
||||
entry.subscribe = subscribe;
|
||||
// Give the Entry the ability to trigger cache.delete(key), even though
|
||||
// the Entry itself does not know about key or cache.
|
||||
entry.forget = () => cache.delete(key);
|
||||
}
|
||||
const value = entry.recompute(Array.prototype.slice.call(arguments));
|
||||
// Move this entry to the front of the least-recently used queue,
|
||||
// since we just finished computing its value.
|
||||
cache.set(key, entry);
|
||||
caches.add(cache);
|
||||
// Clean up any excess entries in the cache, but only if there is no
|
||||
// active parent entry, meaning we're not in the middle of a larger
|
||||
// computation that might be flummoxed by the cleaning.
|
||||
if (!parentEntrySlot.hasValue()) {
|
||||
caches.forEach(cache => cache.clean());
|
||||
caches.clear();
|
||||
}
|
||||
return value;
|
||||
};
|
||||
Object.defineProperty(optimistic, "size", {
|
||||
get: () => cache.size,
|
||||
configurable: false,
|
||||
enumerable: false,
|
||||
});
|
||||
Object.freeze(optimistic.options = {
|
||||
max,
|
||||
keyArgs,
|
||||
makeCacheKey,
|
||||
normalizeResult,
|
||||
subscribe,
|
||||
cache,
|
||||
});
|
||||
function dirtyKey(key) {
|
||||
const entry = key && cache.get(key);
|
||||
if (entry) {
|
||||
entry.setDirty();
|
||||
}
|
||||
}
|
||||
optimistic.dirtyKey = dirtyKey;
|
||||
optimistic.dirty = function dirty() {
|
||||
dirtyKey(makeCacheKey.apply(null, arguments));
|
||||
};
|
||||
function peekKey(key) {
|
||||
const entry = key && cache.get(key);
|
||||
if (entry) {
|
||||
return entry.peek();
|
||||
}
|
||||
}
|
||||
optimistic.peekKey = peekKey;
|
||||
optimistic.peek = function peek() {
|
||||
return peekKey(makeCacheKey.apply(null, arguments));
|
||||
};
|
||||
function forgetKey(key) {
|
||||
return key ? cache.delete(key) : false;
|
||||
}
|
||||
optimistic.forgetKey = forgetKey;
|
||||
optimistic.forget = function forget() {
|
||||
return forgetKey(makeCacheKey.apply(null, arguments));
|
||||
};
|
||||
optimistic.makeCacheKey = makeCacheKey;
|
||||
optimistic.getKey = keyArgs ? function getKey() {
|
||||
return makeCacheKey.apply(null, keyArgs.apply(null, arguments));
|
||||
} : makeCacheKey;
|
||||
return Object.freeze(optimistic);
|
||||
}
|
||||
//# sourceMappingURL=index.js.map
|
||||
1
node_modules/optimism/lib/index.js.map
generated
vendored
Normal file
1
node_modules/optimism/lib/index.js.map
generated
vendored
Normal file
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAEjC,OAAO,EAAE,WAAW,EAAe,MAAM,aAAa,CAAC;AACvD,OAAO,EAAE,KAAK,EAAY,MAAM,YAAY,CAAC;AAC7C,OAAO,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC;AAG/C,qEAAqE;AACrE,qEAAqE;AACrE,yEAAyE;AACzE,sEAAsE;AACtE,yEAAyE;AACzE,sEAAsE;AACtE,OAAO,EACL,WAAW,EACX,SAAS,EACT,WAAW,EACX,UAAU,EACV,YAAY,EACZ,IAAI,GACL,MAAM,cAAc,CAAC;AAEtB,4EAA4E;AAC5E,6EAA6E;AAC7E,8EAA8E;AAC9E,+CAA+C;AAC/C,OAAO,EAAE,GAAG,EAAgC,MAAM,UAAU,CAAC;AAE7D,4EAA4E;AAC5E,2EAA2E;AAC3E,0EAA0E;AAC1E,4EAA4E;AAC5E,2EAA2E;AAC3E,4EAA4E;AAC5E,qEAAqE;AACrE,IAAI,cAAwC,CAAC;AAC7C,MAAM,UAAU,mBAAmB,CAAC,GAAG,IAAW;IAChD,MAAM,IAAI,GAAG,cAAc,IAAI,CAC7B,cAAc,GAAG,IAAI,IAAI,CAAC,OAAO,OAAO,KAAK,UAAU,CAAC,CACzD,CAAC;IACF,OAAO,IAAI,CAAC,WAAW,CAAC,IAAI,CAAC,CAAC;AAChC,CAAC;AAED,4EAA4E;AAC5E,0EAA0E;AAC1E,oDAAoD;AACpD,OAAO,EAAE,IAAI,IAAI,OAAO,EAAE,CAAA;AAqFzB,CAAC;AAEF,MAAM,MAAM,GAAG,IAAI,GAAG,EAA8B,CAAC;AAErD,MAAM,UAAU,IAAI,CAKlB,gBAA6C,EAAE,EAC/C,GAAG,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,EAAE,CAAC,EACrB,OAAO,EACP,YAAY,GAAI,mBAAuC,EACvD,eAAe,EACf,SAAS,EACT,KAAK,EAAE,WAAW,GAAG,WAAW,MAC8B,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC;IACjF,MAAM,KAAK,GACT,OAAO,WAAW,KAAK,UAAU;QAC/B,CAAC,CAAC,IAAI,WAAW,CAAC,GAAG,EAAE,KAAK,CAAC,EAAE,CAAC,KAAK,CAAC,OAAO,EAAE,CAAC;QAChD,CAAC,CAAC,WAAW,CAAC;IAElB,MAAM,UAAU,GAAG;QACjB,MAAM,GAAG,GAAG,YAAY,CAAC,KAAK,CAC5B,IAAI,EACJ,OAAO,CAAC,CAAC,CAAC,OAAO,CAAC,KAAK,CAAC,IAAI,EAAE,SAAgB,CAAC,CAAC,CAAC,CAAC,SAAgB,CACnE,CAAC;QAEF,IAAI,GAAG,KAAK,KAAK,CAAC,EAAE;YAClB,OAAO,gBAAgB,CAAC,KAAK,CAAC,IAAI,EAAE,SAAgB,CAAC,CAAC;SACvD;QAED,IAAI,KAAK,GAAG,KAAK,CAAC,GAAG,CAAC,GAAG,CAAE,CAAC;QAC5B,IAAI,CAAC,KAAK,EAAE;YACV,KAAK,CAAC,GAAG,CAAC,GAAG,EAAE,KAAK,GAAG,IAAI,KAAK,CAAC,gBAAgB,CAAC,CAAC,CAAC;YACpD,KAAK,CAAC,eAAe,GAAG,eAAe,CAAC;YACxC,KAAK,CAAC,SAAS,GAAG,SAAS,CAAC;YAC5B,uEAAuE;YACvE,qDAAqD;YACrD,KAAK,CAAC,MAAM,GAAG,GAAG,EAAE,CAAC,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;SACxC;QAED,MAAM,KAAK,GAAG,KAAK,CAAC,SAAS,CAC3B,KAAK,CAAC,SAAS,CAAC,KAAK,CAAC,IAAI,CAAC,SAAS,CAAU,CAC/C,CAAC;QAEF,iEAAiE;QACjE,8CAA8C;QAC9C,KAAK,CAAC,GAAG,CAAC,GAAG,EAAE,KAAK,CAAC,CAAC;QAEtB,MAAM,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;QAElB,oEAAoE;QACpE,mEAAmE;QACnE,uDAAuD;QACvD,IAAI,CAAE,eAAe,CAAC,QAAQ,EAAE,EAAE;YAChC,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC,KAAK,CAAC,KAAK,EAAE,CAAC,CAAC;YACvC,MAAM,CAAC,KAAK,EAAE,CAAC;SAChB;QAED,OAAO,KAAK,CAAC;IACf,CAAmE,CAAC;IAEpE,MAAM,CAAC,cAAc,CAAC,UAAU,EAAE,MAAM,EAAE;QACxC,GAAG,EAAE,GAAG,EAAE,CAAC,KAAK,CAAC,IAAI;QACrB,YAAY,EAAE,KAAK;QACnB,UAAU,EAAE,KAAK;KAClB,CAAC,CAAC;IAEH,MAAM,CAAC,MAAM,CAAC,UAAU,CAAC,OAAO,GAAG;QACjC,GAAG;QACH,OAAO;QACP,YAAY;QACZ,eAAe;QACf,SAAS;QACT,KAAK;KACN,CAAC,CAAC;IAEH,SAAS,QAAQ,CAAC,GAA0B;QAC1C,MAAM,KAAK,GAAG,GAAG,IAAI,KAAK,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;QACpC,IAAI,KAAK,EAAE;YACT,KAAK,CAAC,QAAQ,EAAE,CAAC;SAClB;IACH,CAAC;IACD,UAAU,CAAC,QAAQ,GAAG,QAAQ,CAAC;IAC/B,UAAU,CAAC,KAAK,GAAG,SAAS,KAAK;QAC/B,QAAQ,CAAC,YAAY,CAAC,KAAK,CAAC,IAAI,EAAE,SAAgB,CAAC,CAAC,CAAC;IACvD,CAAC,CAAC;IAEF,SAAS,OAAO,CAAC,GAA0B;QACzC,MAAM,KAAK,GAAG,GAAG,IAAI,KAAK,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;QACpC,IAAI,KAAK,EAAE;YACT,OAAO,KAAK,CAAC,IAAI,EAAE,CAAC;SACrB;IACH,CAAC;IACD,UAAU,CAAC,OAAO,GAAG,OAAO,CAAC;IAC7B,UAAU,CAAC,IAAI,GAAG,SAAS,IAAI;QAC7B,OAAO,OAAO,CAAC,YAAY,CAAC,KAAK,CAAC,IAAI,EAAE,SAAgB,CAAC,CAAC,CAAC;IAC7D,CAAC,CAAC;IAEF,SAAS,SAAS,CAAC,GAA0B;QAC3C,OAAO,GAAG,CAAC,CAAC,CAAC,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC;IACzC,CAAC;IACD,UAAU,CAAC,SAAS,GAAG,SAAS,CAAC;IACjC,UAAU,CAAC,MAAM,GAAG,SAAS,MAAM;QACjC,OAAO,SAAS,CAAC,YAAY,CAAC,KAAK,CAAC,IAAI,EAAE,SAAgB,CAAC,CAAC,CAAC;IAC/D,CAAC,CAAC;IAEF,UAAU,CAAC,YAAY,GAAG,YAAY,CAAC;IACvC,UAAU,CAAC,MAAM,GAAG,OAAO,CAAC,CAAC,CAAC,SAAS,MAAM;QAC3C,OAAO,YAAY,CAAC,KAAK,CAAC,IAAI,EAAE,OAAO,CAAC,KAAK,CAAC,IAAI,EAAE,SAAgB,CAAC,CAAC,CAAC;IACzE,CAAC,CAAC,CAAC,CAAC,YAAyD,CAAC;IAE9D,OAAO,MAAM,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC;AACnC,CAAC"}
|
||||
56
node_modules/optimism/package.json
generated
vendored
Normal file
56
node_modules/optimism/package.json
generated
vendored
Normal file
@@ -0,0 +1,56 @@
|
||||
{
|
||||
"name": "optimism",
|
||||
"version": "0.18.1",
|
||||
"author": "Ben Newman <ben@benjamn.com>",
|
||||
"description": "Composable reactive caching with efficient invalidation.",
|
||||
"keywords": [
|
||||
"caching",
|
||||
"cache",
|
||||
"invalidation",
|
||||
"reactive",
|
||||
"reactivity",
|
||||
"dependency",
|
||||
"tracking",
|
||||
"tracker",
|
||||
"memoization"
|
||||
],
|
||||
"type": "module",
|
||||
"main": "lib/bundle.cjs",
|
||||
"module": "lib/index.js",
|
||||
"types": "lib/index.d.ts",
|
||||
"license": "MIT",
|
||||
"homepage": "https://github.com/benjamn/optimism#readme",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/benjamn/optimism.git"
|
||||
},
|
||||
"bugs": {
|
||||
"url": "https://github.com/benjamn/optimism/issues"
|
||||
},
|
||||
"scripts": {
|
||||
"build": "npm run clean && npm run tsc:es5 && tsc && rollup -c && rimraf lib/es5",
|
||||
"tsc:es5": "tsc -p tsconfig.es5.json",
|
||||
"clean": "rimraf lib",
|
||||
"prepare": "npm run build",
|
||||
"mocha": "mocha --require source-map-support/register --reporter spec --full-trace",
|
||||
"test:cjs": "npm run mocha -- lib/tests/bundle.cjs",
|
||||
"test:esm": "npm run mocha -- lib/tests/bundle.js",
|
||||
"test": "npm run test:esm && npm run test:cjs"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/mocha": "^10.0.1",
|
||||
"@types/node": "^20.2.5",
|
||||
"@wry/equality": "^0.5.7",
|
||||
"mocha": "^10.2.0",
|
||||
"rimraf": "^5.0.0",
|
||||
"rollup": "^3.20.0",
|
||||
"source-map-support": "^0.5.19",
|
||||
"typescript": "^5.0.2"
|
||||
},
|
||||
"dependencies": {
|
||||
"@wry/caches": "^1.0.0",
|
||||
"@wry/context": "^0.7.0",
|
||||
"@wry/trie": "^0.5.0",
|
||||
"tslib": "^2.3.0"
|
||||
}
|
||||
}
|
||||
16
node_modules/optimism/src/context.ts
generated
vendored
Normal file
16
node_modules/optimism/src/context.ts
generated
vendored
Normal file
@@ -0,0 +1,16 @@
|
||||
import { Slot } from "@wry/context";
|
||||
import { AnyEntry } from "./entry.js";
|
||||
|
||||
export const parentEntrySlot = new Slot<AnyEntry | undefined>();
|
||||
|
||||
export function nonReactive<R>(fn: () => R): R {
|
||||
return parentEntrySlot.withValue(void 0, fn);
|
||||
}
|
||||
|
||||
export { Slot }
|
||||
export {
|
||||
bind as bindContext,
|
||||
noContext,
|
||||
setTimeout,
|
||||
asyncFromGen,
|
||||
} from "@wry/context";
|
||||
68
node_modules/optimism/src/dep.ts
generated
vendored
Normal file
68
node_modules/optimism/src/dep.ts
generated
vendored
Normal file
@@ -0,0 +1,68 @@
|
||||
import { AnyEntry } from "./entry.js";
|
||||
import { OptimisticWrapOptions } from "./index.js";
|
||||
import { parentEntrySlot } from "./context.js";
|
||||
import {
|
||||
hasOwnProperty,
|
||||
Unsubscribable,
|
||||
maybeUnsubscribe,
|
||||
arrayFromSet,
|
||||
} from "./helpers.js";
|
||||
|
||||
type EntryMethodName = keyof typeof EntryMethods;
|
||||
const EntryMethods = {
|
||||
setDirty: true, // Mark parent Entry as needing to be recomputed (default)
|
||||
dispose: true, // Detach parent Entry from parents and children, but leave in LRU cache
|
||||
forget: true, // Fully remove parent Entry from LRU cache and computation graph
|
||||
};
|
||||
|
||||
export type OptimisticDependencyFunction<TKey> =
|
||||
((key: TKey) => void) & {
|
||||
dirty: (key: TKey, entryMethodName?: EntryMethodName) => void;
|
||||
};
|
||||
|
||||
export type Dep<TKey> = Set<AnyEntry> & {
|
||||
subscribe: OptimisticWrapOptions<[TKey]>["subscribe"];
|
||||
} & Unsubscribable;
|
||||
|
||||
export function dep<TKey>(options?: {
|
||||
subscribe: Dep<TKey>["subscribe"];
|
||||
}) {
|
||||
const depsByKey = new Map<TKey, Dep<TKey>>();
|
||||
const subscribe = options && options.subscribe;
|
||||
|
||||
function depend(key: TKey) {
|
||||
const parent = parentEntrySlot.getValue();
|
||||
if (parent) {
|
||||
let dep = depsByKey.get(key);
|
||||
if (!dep) {
|
||||
depsByKey.set(key, dep = new Set as Dep<TKey>);
|
||||
}
|
||||
parent.dependOn(dep);
|
||||
if (typeof subscribe === "function") {
|
||||
maybeUnsubscribe(dep);
|
||||
dep.unsubscribe = subscribe(key);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
depend.dirty = function dirty(
|
||||
key: TKey,
|
||||
entryMethodName?: EntryMethodName,
|
||||
) {
|
||||
const dep = depsByKey.get(key);
|
||||
if (dep) {
|
||||
const m: EntryMethodName = (
|
||||
entryMethodName &&
|
||||
hasOwnProperty.call(EntryMethods, entryMethodName)
|
||||
) ? entryMethodName : "setDirty";
|
||||
// We have to use arrayFromSet(dep).forEach instead of dep.forEach,
|
||||
// because modifying a Set while iterating over it can cause elements in
|
||||
// the Set to be removed from the Set before they've been iterated over.
|
||||
arrayFromSet(dep).forEach(entry => entry[m]());
|
||||
depsByKey.delete(key);
|
||||
maybeUnsubscribe(dep);
|
||||
}
|
||||
};
|
||||
|
||||
return depend as OptimisticDependencyFunction<TKey>;
|
||||
}
|
||||
372
node_modules/optimism/src/entry.ts
generated
vendored
Normal file
372
node_modules/optimism/src/entry.ts
generated
vendored
Normal file
@@ -0,0 +1,372 @@
|
||||
import { parentEntrySlot } from "./context.js";
|
||||
import { OptimisticWrapOptions } from "./index.js";
|
||||
import { Dep } from "./dep.js";
|
||||
import { maybeUnsubscribe, arrayFromSet, Unsubscribable } from "./helpers.js";
|
||||
|
||||
const emptySetPool: Set<any>[] = [];
|
||||
const POOL_TARGET_SIZE = 100;
|
||||
|
||||
// Since this package might be used browsers, we should avoid using the
|
||||
// Node built-in assert module.
|
||||
function assert(condition: any, optionalMessage?: string) {
|
||||
if (! condition) {
|
||||
throw new Error(optionalMessage || "assertion failure");
|
||||
}
|
||||
}
|
||||
|
||||
// Since exceptions are cached just like normal values, we need an efficient
|
||||
// way of representing unknown, ordinary, and exceptional values.
|
||||
type Value<T> =
|
||||
| [] // unknown
|
||||
| [T] // known value
|
||||
| [void, any]; // known exception
|
||||
|
||||
function valueIs(a: Value<any>, b: Value<any>) {
|
||||
const len = a.length;
|
||||
return (
|
||||
// Unknown values are not equal to each other.
|
||||
len > 0 &&
|
||||
// Both values must be ordinary (or both exceptional) to be equal.
|
||||
len === b.length &&
|
||||
// The underlying value or exception must be the same.
|
||||
a[len - 1] === b[len - 1]
|
||||
);
|
||||
}
|
||||
|
||||
function valueGet<T>(value: Value<T>): T {
|
||||
switch (value.length) {
|
||||
case 0: throw new Error("unknown value");
|
||||
case 1: return value[0];
|
||||
case 2: throw value[1];
|
||||
}
|
||||
}
|
||||
|
||||
function valueCopy<T>(value: Value<T>): Value<T> {
|
||||
return value.slice(0) as Value<T>;
|
||||
}
|
||||
|
||||
export type AnyEntry = Entry<any, any>;
|
||||
|
||||
export class Entry<TArgs extends any[], TValue> {
|
||||
public static count = 0;
|
||||
|
||||
public normalizeResult: OptimisticWrapOptions<TArgs, any, any, TValue>["normalizeResult"];
|
||||
public subscribe: OptimisticWrapOptions<TArgs>["subscribe"];
|
||||
public unsubscribe: Unsubscribable["unsubscribe"];
|
||||
|
||||
public readonly parents = new Set<AnyEntry>();
|
||||
public readonly childValues = new Map<AnyEntry, Value<any>>();
|
||||
|
||||
// When this Entry has children that are dirty, this property becomes
|
||||
// a Set containing other Entry objects, borrowed from emptySetPool.
|
||||
// When the set becomes empty, it gets recycled back to emptySetPool.
|
||||
public dirtyChildren: Set<AnyEntry> | null = null;
|
||||
|
||||
public dirty = true;
|
||||
public recomputing = false;
|
||||
public readonly value: Value<TValue> = [];
|
||||
|
||||
constructor(
|
||||
public readonly fn: (...args: TArgs) => TValue,
|
||||
) {
|
||||
++Entry.count;
|
||||
}
|
||||
|
||||
public peek(): TValue | undefined {
|
||||
if (this.value.length === 1 && !mightBeDirty(this)) {
|
||||
rememberParent(this);
|
||||
return this.value[0];
|
||||
}
|
||||
}
|
||||
|
||||
// This is the most important method of the Entry API, because it
|
||||
// determines whether the cached this.value can be returned immediately,
|
||||
// or must be recomputed. The overall performance of the caching system
|
||||
// depends on the truth of the following observations: (1) this.dirty is
|
||||
// usually false, (2) this.dirtyChildren is usually null/empty, and thus
|
||||
// (3) valueGet(this.value) is usually returned without recomputation.
|
||||
public recompute(args: TArgs): TValue {
|
||||
assert(! this.recomputing, "already recomputing");
|
||||
rememberParent(this);
|
||||
return mightBeDirty(this)
|
||||
? reallyRecompute(this, args)
|
||||
: valueGet(this.value);
|
||||
}
|
||||
|
||||
public setDirty() {
|
||||
if (this.dirty) return;
|
||||
this.dirty = true;
|
||||
reportDirty(this);
|
||||
// We can go ahead and unsubscribe here, since any further dirty
|
||||
// notifications we receive will be redundant, and unsubscribing may
|
||||
// free up some resources, e.g. file watchers.
|
||||
maybeUnsubscribe(this);
|
||||
}
|
||||
|
||||
public dispose() {
|
||||
this.setDirty();
|
||||
|
||||
// Sever any dependency relationships with our own children, so those
|
||||
// children don't retain this parent Entry in their child.parents sets,
|
||||
// thereby preventing it from being fully garbage collected.
|
||||
forgetChildren(this);
|
||||
|
||||
// Because this entry has been kicked out of the cache (in index.js),
|
||||
// we've lost the ability to find out if/when this entry becomes dirty,
|
||||
// whether that happens through a subscription, because of a direct call
|
||||
// to entry.setDirty(), or because one of its children becomes dirty.
|
||||
// Because of this loss of future information, we have to assume the
|
||||
// worst (that this entry might have become dirty very soon), so we must
|
||||
// immediately mark this entry's parents as dirty. Normally we could
|
||||
// just call entry.setDirty() rather than calling parent.setDirty() for
|
||||
// each parent, but that would leave this entry in parent.childValues
|
||||
// and parent.dirtyChildren, which would prevent the child from being
|
||||
// truly forgotten.
|
||||
eachParent(this, (parent, child) => {
|
||||
parent.setDirty();
|
||||
forgetChild(parent, this);
|
||||
});
|
||||
}
|
||||
|
||||
public forget() {
|
||||
// The code that creates Entry objects in index.ts will replace this method
|
||||
// with one that actually removes the Entry from the cache, which will also
|
||||
// trigger the entry.dispose method.
|
||||
this.dispose();
|
||||
}
|
||||
|
||||
private deps: Set<Dep<any>> | null = null;
|
||||
|
||||
public dependOn(dep: Dep<any>) {
|
||||
dep.add(this);
|
||||
if (! this.deps) {
|
||||
this.deps = emptySetPool.pop() || new Set<Set<AnyEntry>>();
|
||||
}
|
||||
this.deps.add(dep);
|
||||
}
|
||||
|
||||
public forgetDeps() {
|
||||
if (this.deps) {
|
||||
arrayFromSet(this.deps).forEach(dep => dep.delete(this));
|
||||
this.deps.clear();
|
||||
emptySetPool.push(this.deps);
|
||||
this.deps = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function rememberParent(child: AnyEntry) {
|
||||
const parent = parentEntrySlot.getValue();
|
||||
if (parent) {
|
||||
child.parents.add(parent);
|
||||
|
||||
if (! parent.childValues.has(child)) {
|
||||
parent.childValues.set(child, []);
|
||||
}
|
||||
|
||||
if (mightBeDirty(child)) {
|
||||
reportDirtyChild(parent, child);
|
||||
} else {
|
||||
reportCleanChild(parent, child);
|
||||
}
|
||||
|
||||
return parent;
|
||||
}
|
||||
}
|
||||
|
||||
function reallyRecompute(entry: AnyEntry, args: any[]) {
|
||||
forgetChildren(entry);
|
||||
|
||||
// Set entry as the parent entry while calling recomputeNewValue(entry).
|
||||
parentEntrySlot.withValue(entry, recomputeNewValue, [entry, args]);
|
||||
|
||||
if (maybeSubscribe(entry, args)) {
|
||||
// If we successfully recomputed entry.value and did not fail to
|
||||
// (re)subscribe, then this Entry is no longer explicitly dirty.
|
||||
setClean(entry);
|
||||
}
|
||||
|
||||
return valueGet(entry.value);
|
||||
}
|
||||
|
||||
function recomputeNewValue(entry: AnyEntry, args: any[]) {
|
||||
entry.recomputing = true;
|
||||
|
||||
const { normalizeResult } = entry;
|
||||
let oldValueCopy: Value<any> | undefined;
|
||||
if (normalizeResult && entry.value.length === 1) {
|
||||
oldValueCopy = valueCopy(entry.value);
|
||||
}
|
||||
|
||||
// Make entry.value an empty array, representing an unknown value.
|
||||
entry.value.length = 0;
|
||||
|
||||
try {
|
||||
// If entry.fn succeeds, entry.value will become a normal Value.
|
||||
entry.value[0] = entry.fn.apply(null, args);
|
||||
|
||||
// If we have a viable oldValueCopy to compare with the (successfully
|
||||
// recomputed) new entry.value, and they are not already === identical, give
|
||||
// normalizeResult a chance to pick/choose/reuse parts of oldValueCopy[0]
|
||||
// and/or entry.value[0] to determine the final cached entry.value.
|
||||
if (normalizeResult && oldValueCopy && !valueIs(oldValueCopy, entry.value)) {
|
||||
try {
|
||||
entry.value[0] = normalizeResult(entry.value[0], oldValueCopy[0]);
|
||||
} catch {
|
||||
// If normalizeResult throws, just use the newer value, rather than
|
||||
// saving the exception as entry.value[1].
|
||||
}
|
||||
}
|
||||
|
||||
} catch (e) {
|
||||
// If entry.fn throws, entry.value will hold that exception.
|
||||
entry.value[1] = e;
|
||||
}
|
||||
|
||||
// Either way, this line is always reached.
|
||||
entry.recomputing = false;
|
||||
}
|
||||
|
||||
function mightBeDirty(entry: AnyEntry) {
|
||||
return entry.dirty || !!(entry.dirtyChildren && entry.dirtyChildren.size);
|
||||
}
|
||||
|
||||
function setClean(entry: AnyEntry) {
|
||||
entry.dirty = false;
|
||||
|
||||
if (mightBeDirty(entry)) {
|
||||
// This Entry may still have dirty children, in which case we can't
|
||||
// let our parents know we're clean just yet.
|
||||
return;
|
||||
}
|
||||
|
||||
reportClean(entry);
|
||||
}
|
||||
|
||||
function reportDirty(child: AnyEntry) {
|
||||
eachParent(child, reportDirtyChild);
|
||||
}
|
||||
|
||||
function reportClean(child: AnyEntry) {
|
||||
eachParent(child, reportCleanChild);
|
||||
}
|
||||
|
||||
function eachParent(
|
||||
child: AnyEntry,
|
||||
callback: (parent: AnyEntry, child: AnyEntry) => any,
|
||||
) {
|
||||
const parentCount = child.parents.size;
|
||||
if (parentCount) {
|
||||
const parents = arrayFromSet(child.parents);
|
||||
for (let i = 0; i < parentCount; ++i) {
|
||||
callback(parents[i], child);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Let a parent Entry know that one of its children may be dirty.
|
||||
function reportDirtyChild(parent: AnyEntry, child: AnyEntry) {
|
||||
// Must have called rememberParent(child) before calling
|
||||
// reportDirtyChild(parent, child).
|
||||
assert(parent.childValues.has(child));
|
||||
assert(mightBeDirty(child));
|
||||
const parentWasClean = !mightBeDirty(parent);
|
||||
|
||||
if (! parent.dirtyChildren) {
|
||||
parent.dirtyChildren = emptySetPool.pop() || new Set;
|
||||
|
||||
} else if (parent.dirtyChildren.has(child)) {
|
||||
// If we already know this child is dirty, then we must have already
|
||||
// informed our own parents that we are dirty, so we can terminate
|
||||
// the recursion early.
|
||||
return;
|
||||
}
|
||||
|
||||
parent.dirtyChildren.add(child);
|
||||
|
||||
// If parent was clean before, it just became (possibly) dirty (according to
|
||||
// mightBeDirty), since we just added child to parent.dirtyChildren.
|
||||
if (parentWasClean) {
|
||||
reportDirty(parent);
|
||||
}
|
||||
}
|
||||
|
||||
// Let a parent Entry know that one of its children is no longer dirty.
|
||||
function reportCleanChild(parent: AnyEntry, child: AnyEntry) {
|
||||
// Must have called rememberChild(child) before calling
|
||||
// reportCleanChild(parent, child).
|
||||
assert(parent.childValues.has(child));
|
||||
assert(! mightBeDirty(child));
|
||||
|
||||
const childValue = parent.childValues.get(child)!;
|
||||
if (childValue.length === 0) {
|
||||
parent.childValues.set(child, valueCopy(child.value));
|
||||
} else if (! valueIs(childValue, child.value)) {
|
||||
parent.setDirty();
|
||||
}
|
||||
|
||||
removeDirtyChild(parent, child);
|
||||
|
||||
if (mightBeDirty(parent)) {
|
||||
return;
|
||||
}
|
||||
|
||||
reportClean(parent);
|
||||
}
|
||||
|
||||
function removeDirtyChild(parent: AnyEntry, child: AnyEntry) {
|
||||
const dc = parent.dirtyChildren;
|
||||
if (dc) {
|
||||
dc.delete(child);
|
||||
if (dc.size === 0) {
|
||||
if (emptySetPool.length < POOL_TARGET_SIZE) {
|
||||
emptySetPool.push(dc);
|
||||
}
|
||||
parent.dirtyChildren = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Removes all children from this entry and returns an array of the
|
||||
// removed children.
|
||||
function forgetChildren(parent: AnyEntry) {
|
||||
if (parent.childValues.size > 0) {
|
||||
parent.childValues.forEach((_value, child) => {
|
||||
forgetChild(parent, child);
|
||||
});
|
||||
}
|
||||
|
||||
// Remove this parent Entry from any sets to which it was added by the
|
||||
// addToSet method.
|
||||
parent.forgetDeps();
|
||||
|
||||
// After we forget all our children, this.dirtyChildren must be empty
|
||||
// and therefore must have been reset to null.
|
||||
assert(parent.dirtyChildren === null);
|
||||
}
|
||||
|
||||
function forgetChild(parent: AnyEntry, child: AnyEntry) {
|
||||
child.parents.delete(parent);
|
||||
parent.childValues.delete(child);
|
||||
removeDirtyChild(parent, child);
|
||||
}
|
||||
|
||||
function maybeSubscribe(entry: AnyEntry, args: any[]) {
|
||||
if (typeof entry.subscribe === "function") {
|
||||
try {
|
||||
maybeUnsubscribe(entry); // Prevent double subscriptions.
|
||||
entry.unsubscribe = entry.subscribe.apply(null, args);
|
||||
} catch (e) {
|
||||
// If this Entry has a subscribe function and it threw an exception
|
||||
// (or an unsubscribe function it previously returned now throws),
|
||||
// return false to indicate that we were not able to subscribe (or
|
||||
// unsubscribe), and this Entry should remain dirty.
|
||||
entry.setDirty();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Returning true indicates either that there was no entry.subscribe
|
||||
// function or that it succeeded.
|
||||
return true;
|
||||
}
|
||||
25
node_modules/optimism/src/helpers.ts
generated
vendored
Normal file
25
node_modules/optimism/src/helpers.ts
generated
vendored
Normal file
@@ -0,0 +1,25 @@
|
||||
export type NoInfer<T> = [T][T extends any ? 0 : never];
|
||||
|
||||
export const {
|
||||
hasOwnProperty,
|
||||
} = Object.prototype;
|
||||
|
||||
export const arrayFromSet: <T>(set: Set<T>) => T[] =
|
||||
Array.from ||
|
||||
function (set) {
|
||||
const array: any[] = [];
|
||||
set.forEach(item => array.push(item));
|
||||
return array;
|
||||
};
|
||||
|
||||
export type Unsubscribable = {
|
||||
unsubscribe?: void | (() => any);
|
||||
}
|
||||
|
||||
export function maybeUnsubscribe(entryOrDep: Unsubscribable) {
|
||||
const { unsubscribe } = entryOrDep;
|
||||
if (typeof unsubscribe === "function") {
|
||||
entryOrDep.unsubscribe = void 0;
|
||||
unsubscribe();
|
||||
}
|
||||
}
|
||||
246
node_modules/optimism/src/index.ts
generated
vendored
Normal file
246
node_modules/optimism/src/index.ts
generated
vendored
Normal file
@@ -0,0 +1,246 @@
|
||||
import { Trie } from "@wry/trie";
|
||||
|
||||
import { StrongCache, CommonCache } from "@wry/caches";
|
||||
import { Entry, AnyEntry } from "./entry.js";
|
||||
import { parentEntrySlot } from "./context.js";
|
||||
import type { NoInfer } from "./helpers.js";
|
||||
|
||||
// These helper functions are important for making optimism work with
|
||||
// asynchronous code. In order to register parent-child dependencies,
|
||||
// optimism needs to know about any currently active parent computations.
|
||||
// In ordinary synchronous code, the parent context is implicit in the
|
||||
// execution stack, but asynchronous code requires some extra guidance in
|
||||
// order to propagate context from one async task segment to the next.
|
||||
export {
|
||||
bindContext,
|
||||
noContext,
|
||||
nonReactive,
|
||||
setTimeout,
|
||||
asyncFromGen,
|
||||
Slot,
|
||||
} from "./context.js";
|
||||
|
||||
// A lighter-weight dependency, similar to OptimisticWrapperFunction, except
|
||||
// with only one argument, no makeCacheKey, no wrapped function to recompute,
|
||||
// and no result value. Useful for representing dependency leaves in the graph
|
||||
// of computation. Subscriptions are supported.
|
||||
export { dep, OptimisticDependencyFunction } from "./dep.js";
|
||||
|
||||
// The defaultMakeCacheKey function is remarkably powerful, because it gives
|
||||
// a unique object for any shallow-identical list of arguments. If you need
|
||||
// to implement a custom makeCacheKey function, you may find it helpful to
|
||||
// delegate the final work to defaultMakeCacheKey, which is why we export it
|
||||
// here. However, you may want to avoid defaultMakeCacheKey if your runtime
|
||||
// does not support WeakMap, or you have the ability to return a string key.
|
||||
// In those cases, just write your own custom makeCacheKey functions.
|
||||
let defaultKeyTrie: Trie<object> | undefined;
|
||||
export function defaultMakeCacheKey(...args: any[]): object {
|
||||
const trie = defaultKeyTrie || (
|
||||
defaultKeyTrie = new Trie(typeof WeakMap === "function")
|
||||
);
|
||||
return trie.lookupArray(args);
|
||||
}
|
||||
|
||||
// If you're paranoid about memory leaks, or you want to avoid using WeakMap
|
||||
// under the hood, but you still need the behavior of defaultMakeCacheKey,
|
||||
// import this constructor to create your own tries.
|
||||
export { Trie as KeyTrie }
|
||||
|
||||
export type OptimisticWrapperFunction<
|
||||
TArgs extends any[],
|
||||
TResult,
|
||||
TKeyArgs extends any[] = TArgs,
|
||||
TCacheKey = any,
|
||||
> = ((...args: TArgs) => TResult) & {
|
||||
// Get the current number of Entry objects in the LRU cache.
|
||||
readonly size: number;
|
||||
|
||||
// Snapshot of wrap options used to create this wrapper function.
|
||||
options: OptionsWithCacheInstance<TArgs, TKeyArgs, TCacheKey>;
|
||||
|
||||
// "Dirty" any cached Entry stored for the given arguments, marking that Entry
|
||||
// and its ancestors as potentially needing to be recomputed. The .dirty(...)
|
||||
// method of an optimistic function takes the same parameter types as the
|
||||
// original function by default, unless a keyArgs function is configured, and
|
||||
// then it matters that .dirty takes TKeyArgs instead of TArgs.
|
||||
dirty: (...args: TKeyArgs) => void;
|
||||
// A version of .dirty that accepts a key returned by .getKey.
|
||||
dirtyKey: (key: TCacheKey | undefined) => void;
|
||||
|
||||
// Examine the current value without recomputing it.
|
||||
peek: (...args: TKeyArgs) => TResult | undefined;
|
||||
// A version of .peek that accepts a key returned by .getKey.
|
||||
peekKey: (key: TCacheKey | undefined) => TResult | undefined;
|
||||
|
||||
// Completely remove the entry from the cache, dirtying any parent entries.
|
||||
forget: (...args: TKeyArgs) => boolean;
|
||||
// A version of .forget that accepts a key returned by .getKey.
|
||||
forgetKey: (key: TCacheKey | undefined) => boolean;
|
||||
|
||||
// In order to use the -Key version of the above functions, you need a key
|
||||
// rather than the arguments used to compute the key. These two functions take
|
||||
// TArgs or TKeyArgs and return the corresponding TCacheKey. If no keyArgs
|
||||
// function has been configured, TArgs will be the same as TKeyArgs, and thus
|
||||
// getKey and makeCacheKey will be synonymous.
|
||||
getKey: (...args: TArgs) => TCacheKey | undefined;
|
||||
|
||||
// This property is equivalent to the makeCacheKey function provided in the
|
||||
// OptimisticWrapOptions, or (if no options.makeCacheKey function is provided)
|
||||
// a default implementation of makeCacheKey. This function is also exposed as
|
||||
// optimistic.options.makeCacheKey, somewhat redundantly.
|
||||
makeCacheKey: (...args: TKeyArgs) => TCacheKey | undefined;
|
||||
};
|
||||
|
||||
export { CommonCache }
|
||||
export interface CommonCacheConstructor<TCacheKey, TResult, TArgs extends any[]> extends Function {
|
||||
new <K extends TCacheKey, V extends Entry<TArgs, TResult>>(max?: number, dispose?: (value: V, key?: K) => void): CommonCache<K,V>;
|
||||
}
|
||||
|
||||
export type OptimisticWrapOptions<
|
||||
TArgs extends any[],
|
||||
TKeyArgs extends any[] = TArgs,
|
||||
TCacheKey = any,
|
||||
TResult = any,
|
||||
> = {
|
||||
// The maximum number of cache entries that should be retained before the
|
||||
// cache begins evicting the oldest ones.
|
||||
max?: number;
|
||||
// Transform the raw arguments to some other type of array, which will then
|
||||
// be passed to makeCacheKey.
|
||||
keyArgs?: (...args: TArgs) => TKeyArgs;
|
||||
// The makeCacheKey function takes the same arguments that were passed to
|
||||
// the wrapper function and returns a single value that can be used as a key
|
||||
// in a Map to identify the cached result.
|
||||
makeCacheKey?: (...args: NoInfer<TKeyArgs>) => TCacheKey | undefined;
|
||||
// Called when a new value is computed to allow efficient normalization of
|
||||
// results over time, for example by returning older if equal(newer, older).
|
||||
normalizeResult?: (newer: TResult, older: TResult) => TResult;
|
||||
// If provided, the subscribe function should either return an unsubscribe
|
||||
// function or return nothing.
|
||||
subscribe?: (...args: TArgs) => void | (() => any);
|
||||
cache?: CommonCache<NoInfer<TCacheKey>, Entry<NoInfer<TArgs>, NoInfer<TResult>>>
|
||||
| CommonCacheConstructor<NoInfer<TCacheKey>, NoInfer<TResult>, NoInfer<TArgs>>;
|
||||
};
|
||||
|
||||
export interface OptionsWithCacheInstance<
|
||||
TArgs extends any[],
|
||||
TKeyArgs extends any[] = TArgs,
|
||||
TCacheKey = any,
|
||||
TResult = any,
|
||||
> extends OptimisticWrapOptions<TArgs, TKeyArgs, TCacheKey, TResult> {
|
||||
cache: CommonCache<NoInfer<TCacheKey>, Entry<NoInfer<TArgs>, NoInfer<TResult>>>;
|
||||
};
|
||||
|
||||
const caches = new Set<CommonCache<any, AnyEntry>>();
|
||||
|
||||
export function wrap<
|
||||
TArgs extends any[],
|
||||
TResult,
|
||||
TKeyArgs extends any[] = TArgs,
|
||||
TCacheKey = any,
|
||||
>(originalFunction: (...args: TArgs) => TResult, {
|
||||
max = Math.pow(2, 16),
|
||||
keyArgs,
|
||||
makeCacheKey = (defaultMakeCacheKey as () => TCacheKey),
|
||||
normalizeResult,
|
||||
subscribe,
|
||||
cache: cacheOption = StrongCache,
|
||||
}: OptimisticWrapOptions<TArgs, TKeyArgs, TCacheKey, TResult> = Object.create(null)) {
|
||||
const cache: CommonCache<TCacheKey, Entry<TArgs, TResult>> =
|
||||
typeof cacheOption === "function"
|
||||
? new cacheOption(max, entry => entry.dispose())
|
||||
: cacheOption;
|
||||
|
||||
const optimistic = function (): TResult {
|
||||
const key = makeCacheKey.apply(
|
||||
null,
|
||||
keyArgs ? keyArgs.apply(null, arguments as any) : arguments as any
|
||||
);
|
||||
|
||||
if (key === void 0) {
|
||||
return originalFunction.apply(null, arguments as any);
|
||||
}
|
||||
|
||||
let entry = cache.get(key)!;
|
||||
if (!entry) {
|
||||
cache.set(key, entry = new Entry(originalFunction));
|
||||
entry.normalizeResult = normalizeResult;
|
||||
entry.subscribe = subscribe;
|
||||
// Give the Entry the ability to trigger cache.delete(key), even though
|
||||
// the Entry itself does not know about key or cache.
|
||||
entry.forget = () => cache.delete(key);
|
||||
}
|
||||
|
||||
const value = entry.recompute(
|
||||
Array.prototype.slice.call(arguments) as TArgs,
|
||||
);
|
||||
|
||||
// Move this entry to the front of the least-recently used queue,
|
||||
// since we just finished computing its value.
|
||||
cache.set(key, entry);
|
||||
|
||||
caches.add(cache);
|
||||
|
||||
// Clean up any excess entries in the cache, but only if there is no
|
||||
// active parent entry, meaning we're not in the middle of a larger
|
||||
// computation that might be flummoxed by the cleaning.
|
||||
if (! parentEntrySlot.hasValue()) {
|
||||
caches.forEach(cache => cache.clean());
|
||||
caches.clear();
|
||||
}
|
||||
|
||||
return value;
|
||||
} as OptimisticWrapperFunction<TArgs, TResult, TKeyArgs, TCacheKey>;
|
||||
|
||||
Object.defineProperty(optimistic, "size", {
|
||||
get: () => cache.size,
|
||||
configurable: false,
|
||||
enumerable: false,
|
||||
});
|
||||
|
||||
Object.freeze(optimistic.options = {
|
||||
max,
|
||||
keyArgs,
|
||||
makeCacheKey,
|
||||
normalizeResult,
|
||||
subscribe,
|
||||
cache,
|
||||
});
|
||||
|
||||
function dirtyKey(key: TCacheKey | undefined) {
|
||||
const entry = key && cache.get(key);
|
||||
if (entry) {
|
||||
entry.setDirty();
|
||||
}
|
||||
}
|
||||
optimistic.dirtyKey = dirtyKey;
|
||||
optimistic.dirty = function dirty() {
|
||||
dirtyKey(makeCacheKey.apply(null, arguments as any));
|
||||
};
|
||||
|
||||
function peekKey(key: TCacheKey | undefined) {
|
||||
const entry = key && cache.get(key);
|
||||
if (entry) {
|
||||
return entry.peek();
|
||||
}
|
||||
}
|
||||
optimistic.peekKey = peekKey;
|
||||
optimistic.peek = function peek() {
|
||||
return peekKey(makeCacheKey.apply(null, arguments as any));
|
||||
};
|
||||
|
||||
function forgetKey(key: TCacheKey | undefined) {
|
||||
return key ? cache.delete(key) : false;
|
||||
}
|
||||
optimistic.forgetKey = forgetKey;
|
||||
optimistic.forget = function forget() {
|
||||
return forgetKey(makeCacheKey.apply(null, arguments as any));
|
||||
};
|
||||
|
||||
optimistic.makeCacheKey = makeCacheKey;
|
||||
optimistic.getKey = keyArgs ? function getKey() {
|
||||
return makeCacheKey.apply(null, keyArgs.apply(null, arguments as any));
|
||||
} : makeCacheKey as (...args: any[]) => TCacheKey | undefined;
|
||||
|
||||
return Object.freeze(optimistic);
|
||||
}
|
||||
968
node_modules/optimism/src/tests/api.ts
generated
vendored
Normal file
968
node_modules/optimism/src/tests/api.ts
generated
vendored
Normal file
@@ -0,0 +1,968 @@
|
||||
import * as assert from "assert";
|
||||
import { createHash } from "crypto";
|
||||
import {
|
||||
wrap,
|
||||
defaultMakeCacheKey,
|
||||
OptimisticWrapperFunction,
|
||||
CommonCache,
|
||||
} from "../index";
|
||||
import { equal } from '@wry/equality';
|
||||
import { wrapYieldingFiberMethods } from '@wry/context';
|
||||
import { dep } from "../dep";
|
||||
import { permutations } from "./test-utils";
|
||||
|
||||
type NumThunk = OptimisticWrapperFunction<[], number>;
|
||||
|
||||
describe("optimism", function () {
|
||||
it("sanity", function () {
|
||||
assert.strictEqual(typeof wrap, "function");
|
||||
assert.strictEqual(typeof defaultMakeCacheKey, "function");
|
||||
});
|
||||
|
||||
it("works with single functions", function () {
|
||||
const test = wrap(function (x: string) {
|
||||
return x + salt;
|
||||
}, {
|
||||
makeCacheKey: function (x: string) {
|
||||
return x;
|
||||
}
|
||||
});
|
||||
|
||||
let salt = "salt";
|
||||
assert.strictEqual(test("a"), "asalt");
|
||||
|
||||
salt = "NaCl";
|
||||
assert.strictEqual(test("a"), "asalt");
|
||||
assert.strictEqual(test("b"), "bNaCl");
|
||||
|
||||
test.dirty("a");
|
||||
assert.strictEqual(test("a"), "aNaCl");
|
||||
});
|
||||
|
||||
it("can manually specify a cache instance", () => {
|
||||
class Cache<K, V> implements CommonCache<K, V> {
|
||||
private _cache = new Map<K, V>()
|
||||
has = this._cache.has.bind(this._cache);
|
||||
get = this._cache.get.bind(this._cache);
|
||||
delete = this._cache.delete.bind(this._cache);
|
||||
get size(){ return this._cache.size }
|
||||
set(key: K, value: V): V {
|
||||
this._cache.set(key, value);
|
||||
return value;
|
||||
}
|
||||
clean(){};
|
||||
}
|
||||
|
||||
const cache = new Cache<String, any>();
|
||||
|
||||
const wrapped = wrap(
|
||||
(obj: { value: string }) => obj.value + " transformed",
|
||||
{
|
||||
cache,
|
||||
makeCacheKey(obj) {
|
||||
return obj.value;
|
||||
},
|
||||
}
|
||||
);
|
||||
assert.ok(cache instanceof Cache);
|
||||
assert.strictEqual(wrapped({ value: "test" }), "test transformed");
|
||||
assert.strictEqual(wrapped({ value: "test" }), "test transformed");
|
||||
cache.get("test").value[0] = "test modified";
|
||||
assert.strictEqual(wrapped({ value: "test" }), "test modified");
|
||||
});
|
||||
|
||||
it("can manually specify a cache constructor", () => {
|
||||
class Cache<K, V> implements CommonCache<K, V> {
|
||||
private _cache = new Map<K, V>()
|
||||
has = this._cache.has.bind(this._cache);
|
||||
get = this._cache.get.bind(this._cache);
|
||||
delete = this._cache.delete.bind(this._cache);
|
||||
get size(){ return this._cache.size }
|
||||
set(key: K, value: V): V {
|
||||
this._cache.set(key, value);
|
||||
return value;
|
||||
}
|
||||
clean(){};
|
||||
}
|
||||
|
||||
const wrapped = wrap(
|
||||
(obj: { value: string }) => obj.value + " transformed",
|
||||
{
|
||||
cache: Cache,
|
||||
makeCacheKey(obj) {
|
||||
return obj.value;
|
||||
},
|
||||
}
|
||||
);
|
||||
assert.ok(wrapped.options.cache instanceof Cache);
|
||||
assert.strictEqual(wrapped({ value: "test" }), "test transformed");
|
||||
assert.strictEqual(wrapped({ value: "test" }), "test transformed");
|
||||
wrapped.options.cache.get("test").value[0] = "test modified";
|
||||
assert.strictEqual(wrapped({ value: "test" }), "test modified");
|
||||
});
|
||||
|
||||
it("works with two layers of functions", function () {
|
||||
const files: { [key: string]: string } = {
|
||||
"a.js": "a",
|
||||
"b.js": "b"
|
||||
};
|
||||
|
||||
const fileNames = Object.keys(files);
|
||||
|
||||
const read = wrap(function (path: string) {
|
||||
return files[path];
|
||||
});
|
||||
|
||||
const hash = wrap(function (paths: string[]) {
|
||||
const h = createHash("sha1");
|
||||
paths.forEach(function (path) {
|
||||
h.update(read(path));
|
||||
});
|
||||
return h.digest("hex");
|
||||
});
|
||||
|
||||
const hash1 = hash(fileNames);
|
||||
files["a.js"] += "yy";
|
||||
const hash2 = hash(fileNames);
|
||||
read.dirty("a.js");
|
||||
const hash3 = hash(fileNames);
|
||||
files["b.js"] += "ee";
|
||||
read.dirty("b.js");
|
||||
const hash4 = hash(fileNames);
|
||||
|
||||
assert.strictEqual(hash1, hash2);
|
||||
assert.notStrictEqual(hash1, hash3);
|
||||
assert.notStrictEqual(hash1, hash4);
|
||||
assert.notStrictEqual(hash3, hash4);
|
||||
});
|
||||
|
||||
it("works with subscription functions", function () {
|
||||
let dirty: () => void;
|
||||
let sep = ",";
|
||||
const unsubscribed = Object.create(null);
|
||||
const test = wrap(function (x: string) {
|
||||
return [x, x, x].join(sep);
|
||||
}, {
|
||||
max: 1,
|
||||
subscribe: function (x: string) {
|
||||
dirty = function () {
|
||||
test.dirty(x);
|
||||
};
|
||||
|
||||
delete unsubscribed[x];
|
||||
|
||||
return function () {
|
||||
unsubscribed[x] = true;
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
assert.strictEqual(test("a"), "a,a,a");
|
||||
|
||||
assert.strictEqual(test("b"), "b,b,b");
|
||||
assert.deepEqual(unsubscribed, { a: true });
|
||||
|
||||
assert.strictEqual(test("c"), "c,c,c");
|
||||
assert.deepEqual(unsubscribed, {
|
||||
a: true,
|
||||
b: true
|
||||
});
|
||||
|
||||
sep = ":";
|
||||
|
||||
assert.strictEqual(test("c"), "c,c,c");
|
||||
assert.deepEqual(unsubscribed, {
|
||||
a: true,
|
||||
b: true
|
||||
});
|
||||
|
||||
dirty!();
|
||||
|
||||
assert.strictEqual(test("c"), "c:c:c");
|
||||
assert.deepEqual(unsubscribed, {
|
||||
a: true,
|
||||
b: true
|
||||
});
|
||||
|
||||
assert.strictEqual(test("d"), "d:d:d");
|
||||
assert.deepEqual(unsubscribed, {
|
||||
a: true,
|
||||
b: true,
|
||||
c: true
|
||||
});
|
||||
});
|
||||
|
||||
// The fibers coroutine library no longer works with Node.js v16.
|
||||
it.skip("is not confused by fibers", function () {
|
||||
const Fiber = wrapYieldingFiberMethods(require("fibers"));
|
||||
|
||||
const order = [];
|
||||
let result1 = "one";
|
||||
let result2 = "two";
|
||||
|
||||
const f1 = new Fiber(function () {
|
||||
order.push(1);
|
||||
|
||||
const o1 = wrap(function () {
|
||||
Fiber.yield();
|
||||
return result1;
|
||||
});
|
||||
|
||||
order.push(2);
|
||||
assert.strictEqual(o1(), "one");
|
||||
order.push(3);
|
||||
result1 += ":dirty";
|
||||
assert.strictEqual(o1(), "one");
|
||||
order.push(4);
|
||||
Fiber.yield();
|
||||
order.push(5);
|
||||
assert.strictEqual(o1(), "one");
|
||||
order.push(6);
|
||||
o1.dirty();
|
||||
order.push(7);
|
||||
assert.strictEqual(o1(), "one:dirty");
|
||||
order.push(8);
|
||||
assert.strictEqual(o2(), "two:dirty");
|
||||
order.push(9);
|
||||
});
|
||||
|
||||
result2 = "two"
|
||||
const o2 = wrap(function () {
|
||||
return result2;
|
||||
});
|
||||
|
||||
order.push(0);
|
||||
|
||||
f1.run();
|
||||
assert.deepEqual(order, [0, 1, 2]);
|
||||
|
||||
// The primary goal of this test is to make sure this call to o2()
|
||||
// does not register a dirty-chain dependency for o1.
|
||||
assert.strictEqual(o2(), "two");
|
||||
|
||||
f1.run();
|
||||
assert.deepEqual(order, [0, 1, 2, 3, 4]);
|
||||
|
||||
// If the call to o2() captured o1() as a parent, then this o2.dirty()
|
||||
// call will report the o1() call dirty, which is not what we want.
|
||||
result2 += ":dirty";
|
||||
o2.dirty();
|
||||
|
||||
f1.run();
|
||||
// The call to o1() between order.push(5) and order.push(6) should not
|
||||
// yield, because it should still be cached, because it should not be
|
||||
// dirty. However, the call to o1() between order.push(7) and
|
||||
// order.push(8) should yield, because we call o1.dirty() explicitly,
|
||||
// which is why this assertion stops at 7.
|
||||
assert.deepEqual(order, [0, 1, 2, 3, 4, 5, 6, 7]);
|
||||
|
||||
f1.run();
|
||||
assert.deepEqual(order, [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]);
|
||||
});
|
||||
|
||||
it("marks evicted cache entries dirty", function () {
|
||||
let childSalt = "*";
|
||||
let child = wrap(function (x: string) {
|
||||
return x + childSalt;
|
||||
}, { max: 1 });
|
||||
|
||||
let parentSalt = "^";
|
||||
const parent = wrap(function (x: string) {
|
||||
return child(x) + parentSalt;
|
||||
});
|
||||
|
||||
assert.strictEqual(parent("asdf"), "asdf*^");
|
||||
|
||||
childSalt = "&";
|
||||
parentSalt = "%";
|
||||
|
||||
assert.strictEqual(parent("asdf"), "asdf*^");
|
||||
assert.strictEqual(child("zxcv"), "zxcv&");
|
||||
assert.strictEqual(parent("asdf"), "asdf&%");
|
||||
});
|
||||
|
||||
it("handles children throwing exceptions", function () {
|
||||
const expected = new Error("oyez");
|
||||
|
||||
const child = wrap(function () {
|
||||
throw expected;
|
||||
});
|
||||
|
||||
const parent = wrap(function () {
|
||||
try {
|
||||
child();
|
||||
} catch (e) {
|
||||
return e;
|
||||
}
|
||||
});
|
||||
|
||||
assert.strictEqual(parent(), expected);
|
||||
assert.strictEqual(parent(), expected);
|
||||
|
||||
child.dirty();
|
||||
assert.strictEqual(parent(), expected);
|
||||
|
||||
parent.dirty();
|
||||
assert.strictEqual(parent(), expected);
|
||||
});
|
||||
|
||||
it("reports clean children to correct parents", function () {
|
||||
let childResult = "a";
|
||||
const child = wrap(function () {
|
||||
return childResult;
|
||||
});
|
||||
|
||||
const parent = wrap(function (x: any) {
|
||||
return child() + x;
|
||||
});
|
||||
|
||||
assert.strictEqual(parent(1), "a1");
|
||||
assert.strictEqual(parent(2), "a2");
|
||||
|
||||
childResult = "b";
|
||||
child.dirty();
|
||||
|
||||
// If this call to parent(1) mistakenly reports child() as clean to
|
||||
// parent(2), then the second assertion will fail by returning "a2".
|
||||
assert.strictEqual(parent(1), "b1");
|
||||
assert.strictEqual(parent(2), "b2");
|
||||
});
|
||||
|
||||
it("supports object cache keys", function () {
|
||||
let counter = 0;
|
||||
const wrapped = wrap(function (a: any, b: any) {
|
||||
return counter++;
|
||||
});
|
||||
|
||||
const a = {};
|
||||
const b = {};
|
||||
|
||||
// Different combinations of distinct object references should
|
||||
// increment the counter.
|
||||
assert.strictEqual(wrapped(a, a), 0);
|
||||
assert.strictEqual(wrapped(a, b), 1);
|
||||
assert.strictEqual(wrapped(b, a), 2);
|
||||
assert.strictEqual(wrapped(b, b), 3);
|
||||
|
||||
// But the same combinations of arguments should return the same
|
||||
// cached values when passed again.
|
||||
assert.strictEqual(wrapped(a, a), 0);
|
||||
assert.strictEqual(wrapped(a, b), 1);
|
||||
assert.strictEqual(wrapped(b, a), 2);
|
||||
assert.strictEqual(wrapped(b, b), 3);
|
||||
});
|
||||
|
||||
it("supports falsy non-void cache keys", function () {
|
||||
let callCount = 0;
|
||||
const wrapped = wrap((key: number | string | null | boolean | undefined) => {
|
||||
++callCount;
|
||||
return key;
|
||||
}, {
|
||||
makeCacheKey(key) {
|
||||
return key;
|
||||
},
|
||||
});
|
||||
|
||||
assert.strictEqual(wrapped(0), 0);
|
||||
assert.strictEqual(callCount, 1);
|
||||
assert.strictEqual(wrapped(0), 0);
|
||||
assert.strictEqual(callCount, 1);
|
||||
|
||||
assert.strictEqual(wrapped(""), "");
|
||||
assert.strictEqual(callCount, 2);
|
||||
assert.strictEqual(wrapped(""), "");
|
||||
assert.strictEqual(callCount, 2);
|
||||
|
||||
assert.strictEqual(wrapped(null), null);
|
||||
assert.strictEqual(callCount, 3);
|
||||
assert.strictEqual(wrapped(null), null);
|
||||
assert.strictEqual(callCount, 3);
|
||||
|
||||
assert.strictEqual(wrapped(false), false);
|
||||
assert.strictEqual(callCount, 4);
|
||||
assert.strictEqual(wrapped(false), false);
|
||||
assert.strictEqual(callCount, 4);
|
||||
|
||||
assert.strictEqual(wrapped(0), 0);
|
||||
assert.strictEqual(wrapped(""), "");
|
||||
assert.strictEqual(wrapped(null), null);
|
||||
assert.strictEqual(wrapped(false), false);
|
||||
assert.strictEqual(callCount, 4);
|
||||
|
||||
assert.strictEqual(wrapped(1), 1);
|
||||
assert.strictEqual(wrapped("oyez"), "oyez");
|
||||
assert.strictEqual(wrapped(true), true);
|
||||
assert.strictEqual(callCount, 7);
|
||||
|
||||
assert.strictEqual(wrapped(void 0), void 0);
|
||||
assert.strictEqual(wrapped(void 0), void 0);
|
||||
assert.strictEqual(wrapped(void 0), void 0);
|
||||
assert.strictEqual(callCount, 10);
|
||||
});
|
||||
|
||||
it("detects problematic cycles", function () {
|
||||
const self: NumThunk = wrap(function () {
|
||||
return self() + 1;
|
||||
});
|
||||
|
||||
const mutualA: NumThunk = wrap(function () {
|
||||
return mutualB() + 1;
|
||||
});
|
||||
|
||||
const mutualB: NumThunk = wrap(function () {
|
||||
return mutualA() + 1;
|
||||
});
|
||||
|
||||
function check(fn: typeof self) {
|
||||
try {
|
||||
fn();
|
||||
throw new Error("should not get here");
|
||||
} catch (e: any) {
|
||||
assert.strictEqual(e.message, "already recomputing");
|
||||
}
|
||||
|
||||
// Try dirtying the function, now that there's a cycle in the Entry
|
||||
// graph. This should succeed.
|
||||
fn.dirty();
|
||||
}
|
||||
|
||||
check(self);
|
||||
check(mutualA);
|
||||
check(mutualB);
|
||||
|
||||
let returnZero = true;
|
||||
const fn: NumThunk = wrap(function () {
|
||||
if (returnZero) {
|
||||
returnZero = false;
|
||||
return 0;
|
||||
}
|
||||
returnZero = true;
|
||||
return fn() + 1;
|
||||
});
|
||||
|
||||
assert.strictEqual(fn(), 0);
|
||||
assert.strictEqual(returnZero, false);
|
||||
|
||||
returnZero = true;
|
||||
assert.strictEqual(fn(), 0);
|
||||
assert.strictEqual(returnZero, true);
|
||||
|
||||
fn.dirty();
|
||||
|
||||
returnZero = false;
|
||||
check(fn);
|
||||
});
|
||||
|
||||
it("tolerates misbehaving makeCacheKey functions", function () {
|
||||
type NumNum = OptimisticWrapperFunction<[number], number>;
|
||||
|
||||
let chaos = false;
|
||||
let counter = 0;
|
||||
const allOddsDep = wrap(() => ++counter);
|
||||
|
||||
const sumOdd: NumNum = wrap((n: number) => {
|
||||
allOddsDep();
|
||||
if (n < 1) return 0;
|
||||
if (n % 2 === 1) {
|
||||
return n + sumEven(n - 1);
|
||||
}
|
||||
return sumEven(n);
|
||||
}, {
|
||||
makeCacheKey(n) {
|
||||
// Even though the computation completes, returning "constant" causes
|
||||
// cycles in the Entry graph.
|
||||
return chaos ? "constant" : n;
|
||||
}
|
||||
});
|
||||
|
||||
const sumEven: NumNum = wrap((n: number) => {
|
||||
if (n < 1) return 0;
|
||||
if (n % 2 === 0) {
|
||||
return n + sumOdd(n - 1);
|
||||
}
|
||||
return sumOdd(n);
|
||||
});
|
||||
|
||||
function check() {
|
||||
sumEven.dirty(10);
|
||||
sumOdd.dirty(10);
|
||||
if (chaos) {
|
||||
try {
|
||||
sumOdd(10);
|
||||
} catch (e: any) {
|
||||
assert.strictEqual(e.message, "already recomputing");
|
||||
}
|
||||
try {
|
||||
sumEven(10);
|
||||
} catch (e: any) {
|
||||
assert.strictEqual(e.message, "already recomputing");
|
||||
}
|
||||
} else {
|
||||
assert.strictEqual(sumEven(10), 55);
|
||||
assert.strictEqual(sumOdd(10), 55);
|
||||
}
|
||||
}
|
||||
|
||||
check();
|
||||
|
||||
allOddsDep.dirty();
|
||||
sumEven.dirty(10);
|
||||
check();
|
||||
|
||||
allOddsDep.dirty();
|
||||
allOddsDep();
|
||||
check();
|
||||
|
||||
chaos = true;
|
||||
check();
|
||||
|
||||
allOddsDep.dirty();
|
||||
allOddsDep();
|
||||
check();
|
||||
|
||||
allOddsDep.dirty();
|
||||
check();
|
||||
|
||||
chaos = false;
|
||||
allOddsDep.dirty();
|
||||
check();
|
||||
|
||||
chaos = true;
|
||||
sumOdd.dirty(9);
|
||||
sumOdd.dirty(7);
|
||||
sumOdd.dirty(5);
|
||||
check();
|
||||
|
||||
chaos = false;
|
||||
check();
|
||||
});
|
||||
|
||||
it("supports options.keyArgs", function () {
|
||||
const sumNums = wrap((...args: any[]) => ({
|
||||
sum: args.reduce(
|
||||
(sum, arg) => typeof arg === "number" ? arg + sum : sum,
|
||||
0,
|
||||
) as number,
|
||||
}), {
|
||||
keyArgs(...args) {
|
||||
return args.filter(arg => typeof arg === "number");
|
||||
},
|
||||
});
|
||||
|
||||
assert.strictEqual(sumNums().sum, 0);
|
||||
assert.strictEqual(sumNums("asdf", true, sumNums).sum, 0);
|
||||
|
||||
const sumObj1 = sumNums(1, "zxcv", true, 2, false, 3);
|
||||
assert.strictEqual(sumObj1.sum, 6);
|
||||
// These results are === sumObj1 because the numbers involved are identical.
|
||||
assert.strictEqual(sumNums(1, 2, 3), sumObj1);
|
||||
assert.strictEqual(sumNums("qwer", 1, 2, true, 3, [3]), sumObj1);
|
||||
assert.strictEqual(sumNums("backwards", 3, 2, 1).sum, 6);
|
||||
assert.notStrictEqual(sumNums("backwards", 3, 2, 1), sumObj1);
|
||||
|
||||
sumNums.dirty(1, 2, 3);
|
||||
const sumObj2 = sumNums(1, 2, 3);
|
||||
assert.strictEqual(sumObj2.sum, 6);
|
||||
assert.notStrictEqual(sumObj2, sumObj1);
|
||||
assert.strictEqual(sumNums("a", 1, "b", 2, "c", 3), sumObj2);
|
||||
});
|
||||
|
||||
it("supports wrap(fn, {...}).options to reflect input options", function () {
|
||||
const keyArgs: () => [] = () => [];
|
||||
function makeCacheKey() { return "constant"; }
|
||||
function subscribe() {}
|
||||
let normalizeCalls: [number, number][] = [];
|
||||
function normalizeResult(newer: number, older: number) {
|
||||
normalizeCalls.push([newer, older]);
|
||||
return newer;
|
||||
}
|
||||
|
||||
let counter1 = 0;
|
||||
const wrapped = wrap(() => ++counter1, {
|
||||
max: 10,
|
||||
keyArgs,
|
||||
makeCacheKey,
|
||||
normalizeResult,
|
||||
subscribe,
|
||||
});
|
||||
assert.strictEqual(wrapped.options.max, 10);
|
||||
assert.strictEqual(wrapped.options.keyArgs, keyArgs);
|
||||
assert.strictEqual(wrapped.options.makeCacheKey, makeCacheKey);
|
||||
assert.strictEqual(wrapped.options.normalizeResult, normalizeResult);
|
||||
assert.strictEqual(wrapped.options.subscribe, subscribe);
|
||||
|
||||
assert.deepEqual(normalizeCalls, []);
|
||||
assert.strictEqual(wrapped(), 1);
|
||||
assert.deepEqual(normalizeCalls, []);
|
||||
assert.strictEqual(wrapped(), 1);
|
||||
assert.deepEqual(normalizeCalls, []);
|
||||
wrapped.dirty();
|
||||
assert.deepEqual(normalizeCalls, []);
|
||||
assert.strictEqual(wrapped(), 2);
|
||||
assert.deepEqual(normalizeCalls, [[2, 1]]);
|
||||
assert.strictEqual(wrapped(), 2);
|
||||
wrapped.dirty();
|
||||
assert.strictEqual(wrapped(), 3);
|
||||
assert.deepEqual(normalizeCalls, [[2, 1], [3, 2]]);
|
||||
assert.strictEqual(wrapped(), 3);
|
||||
assert.deepEqual(normalizeCalls, [[2, 1], [3, 2]]);
|
||||
assert.strictEqual(wrapped(), 3);
|
||||
|
||||
let counter2 = 0;
|
||||
const wrappedWithDefaults = wrap(() => ++counter2);
|
||||
assert.strictEqual(wrappedWithDefaults.options.max, Math.pow(2, 16));
|
||||
assert.strictEqual(wrappedWithDefaults.options.keyArgs, void 0);
|
||||
assert.strictEqual(typeof wrappedWithDefaults.options.makeCacheKey, "function");
|
||||
assert.strictEqual(wrappedWithDefaults.options.normalizeResult, void 0);
|
||||
assert.strictEqual(wrappedWithDefaults.options.subscribe, void 0);
|
||||
});
|
||||
|
||||
it("tolerates cycles when propagating dirty/clean signals", function () {
|
||||
let counter = 0;
|
||||
const dep = wrap(() => ++counter);
|
||||
|
||||
const callChild = () => child();
|
||||
let parentBody = callChild;
|
||||
const parent = wrap(() => {
|
||||
dep();
|
||||
return parentBody();
|
||||
});
|
||||
|
||||
const callParent = () => parent();
|
||||
let childBody = () => "child";
|
||||
const child = wrap(() => {
|
||||
dep();
|
||||
return childBody();
|
||||
});
|
||||
|
||||
assert.strictEqual(parent(), "child");
|
||||
|
||||
childBody = callParent;
|
||||
parentBody = () => "parent";
|
||||
child.dirty();
|
||||
assert.strictEqual(child(), "parent");
|
||||
dep.dirty();
|
||||
assert.strictEqual(child(), "parent");
|
||||
});
|
||||
|
||||
it("is not confused by eviction during recomputation", function () {
|
||||
const fib: OptimisticWrapperFunction<[number], number> =
|
||||
wrap(function (n: number) {
|
||||
if (n > 1) {
|
||||
return fib(n - 1) + fib(n - 2);
|
||||
}
|
||||
return n;
|
||||
}, {
|
||||
max: 10
|
||||
});
|
||||
|
||||
assert.strictEqual(fib.options.max, 10);
|
||||
|
||||
assert.strictEqual(fib(78), 8944394323791464);
|
||||
assert.strictEqual(fib(68), 72723460248141);
|
||||
assert.strictEqual(fib(58), 591286729879);
|
||||
assert.strictEqual(fib(48), 4807526976);
|
||||
assert.strictEqual(fib(38), 39088169);
|
||||
assert.strictEqual(fib(28), 317811);
|
||||
assert.strictEqual(fib(18), 2584);
|
||||
assert.strictEqual(fib(8), 21);
|
||||
});
|
||||
|
||||
it("allows peeking the current value", function () {
|
||||
const sumFirst = wrap(function (n: number): number {
|
||||
return n < 1 ? 0 : n + sumFirst(n - 1);
|
||||
});
|
||||
|
||||
assert.strictEqual(sumFirst.peek(3), void 0);
|
||||
assert.strictEqual(sumFirst.peek(2), void 0);
|
||||
assert.strictEqual(sumFirst.peek(1), void 0);
|
||||
assert.strictEqual(sumFirst.peek(0), void 0);
|
||||
assert.strictEqual(sumFirst(3), 6);
|
||||
assert.strictEqual(sumFirst.peek(3), 6);
|
||||
assert.strictEqual(sumFirst.peek(2), 3);
|
||||
assert.strictEqual(sumFirst.peek(1), 1);
|
||||
assert.strictEqual(sumFirst.peek(0), 0);
|
||||
|
||||
assert.strictEqual(sumFirst.peek(7), void 0);
|
||||
assert.strictEqual(sumFirst(10), 55);
|
||||
assert.strictEqual(sumFirst.peek(9), 55 - 10);
|
||||
assert.strictEqual(sumFirst.peek(8), 55 - 10 - 9);
|
||||
assert.strictEqual(sumFirst.peek(7), 55 - 10 - 9 - 8);
|
||||
|
||||
sumFirst.dirty(7);
|
||||
// Everything from 7 and above is now unpeekable.
|
||||
assert.strictEqual(sumFirst.peek(10), void 0);
|
||||
assert.strictEqual(sumFirst.peek(9), void 0);
|
||||
assert.strictEqual(sumFirst.peek(8), void 0);
|
||||
assert.strictEqual(sumFirst.peek(7), void 0);
|
||||
// Since 6 < 7, its value is still cached.
|
||||
assert.strictEqual(sumFirst.peek(6), 6 * 7 / 2);
|
||||
});
|
||||
|
||||
it("allows forgetting entries", function () {
|
||||
const ns: number[] = [];
|
||||
const sumFirst = wrap(function (n: number): number {
|
||||
ns.push(n);
|
||||
return n < 1 ? 0 : n + sumFirst(n - 1);
|
||||
});
|
||||
|
||||
function inclusiveDescendingRange(n: number, limit = 0) {
|
||||
const range: number[] = [];
|
||||
while (n >= limit) range.push(n--);
|
||||
return range;
|
||||
}
|
||||
|
||||
assert.strictEqual(sumFirst(10), 55);
|
||||
assert.deepStrictEqual(ns, inclusiveDescendingRange(10));
|
||||
|
||||
assert.strictEqual(sumFirst.forget(6), true);
|
||||
assert.strictEqual(sumFirst(4), 10);
|
||||
assert.deepStrictEqual(ns, inclusiveDescendingRange(10));
|
||||
|
||||
assert.strictEqual(sumFirst(11), 66);
|
||||
assert.deepStrictEqual(ns, [
|
||||
...inclusiveDescendingRange(10),
|
||||
...inclusiveDescendingRange(11, 6),
|
||||
]);
|
||||
|
||||
assert.strictEqual(sumFirst.forget(3), true);
|
||||
assert.strictEqual(sumFirst(7), 28);
|
||||
assert.deepStrictEqual(ns, [
|
||||
...inclusiveDescendingRange(10),
|
||||
...inclusiveDescendingRange(11, 6),
|
||||
...inclusiveDescendingRange(7, 3),
|
||||
]);
|
||||
|
||||
assert.strictEqual(sumFirst.forget(123), false);
|
||||
assert.strictEqual(sumFirst.forget(-1), false);
|
||||
assert.strictEqual(sumFirst.forget("7" as any), false);
|
||||
assert.strictEqual((sumFirst.forget as any)(6, 4), false);
|
||||
});
|
||||
|
||||
it("allows forgetting entries by key", function () {
|
||||
const ns: number[] = [];
|
||||
const sumFirst = wrap(function (n: number): number {
|
||||
ns.push(n);
|
||||
return n < 1 ? 0 : n + sumFirst(n - 1);
|
||||
}, {
|
||||
makeCacheKey: function (x: number) {
|
||||
return x * 2;
|
||||
}
|
||||
});
|
||||
|
||||
assert.strictEqual(sumFirst.options.makeCacheKey!(7), 14);
|
||||
assert.strictEqual(sumFirst(10), 55);
|
||||
|
||||
/*
|
||||
* Verify:
|
||||
* 1- Calling forgetKey will remove the entry.
|
||||
* 2- Calling forgetKey again will return false.
|
||||
* 3- Callling forget on the same entry will return false.
|
||||
*/
|
||||
assert.strictEqual(sumFirst.forgetKey(6 * 2), true);
|
||||
assert.strictEqual(sumFirst.forgetKey(6 * 2), false);
|
||||
assert.strictEqual(sumFirst.forget(6), false);
|
||||
|
||||
/*
|
||||
* Verify:
|
||||
* 1- Calling forget will remove the entry.
|
||||
* 2- Calling forget again will return false.
|
||||
* 3- Callling forgetKey on the same entry will return false.
|
||||
*/
|
||||
assert.strictEqual(sumFirst.forget(7), true);
|
||||
assert.strictEqual(sumFirst.forget(7), false);
|
||||
assert.strictEqual(sumFirst.forgetKey(7 * 2), false);
|
||||
|
||||
/*
|
||||
* Verify you can query an entry key.
|
||||
*/
|
||||
assert.strictEqual(sumFirst.getKey(9), 18);
|
||||
assert.strictEqual(sumFirst.forgetKey(sumFirst.getKey(9)), true);
|
||||
assert.strictEqual(sumFirst.forgetKey(sumFirst.getKey(9)), false);
|
||||
assert.strictEqual(sumFirst.forget(9), false);
|
||||
});
|
||||
|
||||
it("exposes optimistic.{size,options.cache.size} properties", function () {
|
||||
const d = dep<string>();
|
||||
const fib = wrap((n: number): number => {
|
||||
d("shared");
|
||||
return n > 1 ? fib(n - 1) + fib(n - 2) : n;
|
||||
}, {
|
||||
makeCacheKey(n) {
|
||||
return n;
|
||||
},
|
||||
});
|
||||
|
||||
function size() {
|
||||
assert.strictEqual(fib.options.cache.size, fib.size);
|
||||
return fib.size;
|
||||
}
|
||||
|
||||
assert.strictEqual(size(), 0);
|
||||
|
||||
assert.strictEqual(fib(0), 0);
|
||||
assert.strictEqual(fib(1), 1);
|
||||
assert.strictEqual(fib(2), 1);
|
||||
assert.strictEqual(fib(3), 2);
|
||||
assert.strictEqual(fib(4), 3);
|
||||
assert.strictEqual(fib(5), 5);
|
||||
assert.strictEqual(fib(6), 8);
|
||||
assert.strictEqual(fib(7), 13);
|
||||
assert.strictEqual(fib(8), 21);
|
||||
|
||||
assert.strictEqual(size(), 9);
|
||||
|
||||
fib.dirty(6);
|
||||
// Merely dirtying an Entry does not remove it from the LRU cache.
|
||||
assert.strictEqual(size(), 9);
|
||||
|
||||
fib.forget(6);
|
||||
// Forgetting an Entry both dirties it and removes it from the LRU cache.
|
||||
assert.strictEqual(size(), 8);
|
||||
|
||||
fib.forget(4);
|
||||
assert.strictEqual(size(), 7);
|
||||
|
||||
// This way of calling d.dirty causes any parent Entry objects to be
|
||||
// forgotten (removed from the LRU cache).
|
||||
d.dirty("shared", "forget");
|
||||
assert.strictEqual(size(), 0);
|
||||
});
|
||||
|
||||
describe("wrapOptions.normalizeResult", function () {
|
||||
it("can normalize array results", function () {
|
||||
const normalizeArgs: [number[], number[]][] = [];
|
||||
const range = wrap((n: number) => {
|
||||
let result = [];
|
||||
for (let i = 0; i < n; ++i) {
|
||||
result[i] = i;
|
||||
}
|
||||
return result;
|
||||
}, {
|
||||
normalizeResult(newer, older) {
|
||||
normalizeArgs.push([newer, older]);
|
||||
return equal(newer, older) ? older : newer;
|
||||
},
|
||||
});
|
||||
|
||||
const r3a = range(3);
|
||||
assert.deepStrictEqual(r3a, [0, 1, 2]);
|
||||
// Nothing surprising, just regular caching.
|
||||
assert.strictEqual(r3a, range(3));
|
||||
|
||||
// Force range(3) to be recomputed below.
|
||||
range.dirty(3);
|
||||
|
||||
const r3b = range(3);
|
||||
assert.deepStrictEqual(r3b, [0, 1, 2]);
|
||||
|
||||
assert.strictEqual(r3a, r3b);
|
||||
|
||||
assert.deepStrictEqual(normalizeArgs, [
|
||||
[r3b, r3a],
|
||||
]);
|
||||
// Though r3a and r3b ended up ===, the normalizeResult callback should
|
||||
// have been called with two !== arrays.
|
||||
assert.notStrictEqual(
|
||||
normalizeArgs[0][0],
|
||||
normalizeArgs[0][1],
|
||||
);
|
||||
});
|
||||
|
||||
it("can normalize recursive array results", function () {
|
||||
const range = wrap((n: number): number[] => {
|
||||
if (n <= 0) return [];
|
||||
return range(n - 1).concat(n - 1);
|
||||
}, {
|
||||
normalizeResult: (newer, older) => equal(newer, older) ? older : newer,
|
||||
});
|
||||
|
||||
const ranges = [
|
||||
range(0),
|
||||
range(1),
|
||||
range(2),
|
||||
range(3),
|
||||
range(4),
|
||||
];
|
||||
|
||||
assert.deepStrictEqual(ranges[0], []);
|
||||
assert.deepStrictEqual(ranges[1], [0]);
|
||||
assert.deepStrictEqual(ranges[2], [0, 1]);
|
||||
assert.deepStrictEqual(ranges[3], [0, 1, 2]);
|
||||
assert.deepStrictEqual(ranges[4], [0, 1, 2, 3]);
|
||||
|
||||
const perms = permutations(ranges[4]);
|
||||
assert.strictEqual(perms.length, 4 * 3 * 2 * 1);
|
||||
|
||||
// For each permutation of the range sizes, check that strict equality
|
||||
// holds for r[i] and range(i) for all i after dirtying each number.
|
||||
let count = 0;
|
||||
perms.forEach(perm => {
|
||||
perm.forEach(toDirty => {
|
||||
range.dirty(toDirty);
|
||||
perm.forEach(i => {
|
||||
assert.strictEqual(ranges[i], range(i));
|
||||
++count;
|
||||
});
|
||||
})
|
||||
});
|
||||
assert.strictEqual(count, perms.length * 4 * 4);
|
||||
});
|
||||
|
||||
it("exceptions thrown by normalizeResult are ignored", function () {
|
||||
const normalizeCalls: [string | number, string | number][] = [];
|
||||
|
||||
const maybeThrow = wrap((value: string | number, shouldThrow: boolean) => {
|
||||
if (shouldThrow) throw value;
|
||||
return value;
|
||||
}, {
|
||||
makeCacheKey(value, shouldThrow) {
|
||||
return JSON.stringify({
|
||||
// Coerce the value to a string so we can trigger normalizeResult
|
||||
// using either 2 or "2" below.
|
||||
value: String(value),
|
||||
shouldThrow,
|
||||
});
|
||||
},
|
||||
normalizeResult(a, b) {
|
||||
normalizeCalls.push([a, b]);
|
||||
throw new Error("from normalizeResult (expected)");
|
||||
},
|
||||
});
|
||||
|
||||
assert.strictEqual(maybeThrow(1, false), 1);
|
||||
assert.strictEqual(maybeThrow(2, false), 2);
|
||||
|
||||
maybeThrow.dirty(2, false);
|
||||
assert.strictEqual(maybeThrow("2", false), "2");
|
||||
assert.strictEqual(maybeThrow(2, false), "2");
|
||||
maybeThrow.dirty(2, false);
|
||||
assert.strictEqual(maybeThrow(2, false), 2);
|
||||
assert.strictEqual(maybeThrow("2", false), 2);
|
||||
|
||||
assert.throws(
|
||||
() => maybeThrow(3, true),
|
||||
error => error === 3,
|
||||
);
|
||||
|
||||
assert.throws(
|
||||
() => maybeThrow("3", true),
|
||||
// Still 3 because the previous maybeThrow(3, true) exception is cached.
|
||||
error => error === 3,
|
||||
);
|
||||
|
||||
maybeThrow.dirty(3, true);
|
||||
assert.throws(
|
||||
() => maybeThrow("3", true),
|
||||
error => error === "3",
|
||||
);
|
||||
|
||||
// Even though the exception thrown by normalizeResult was ignored, check
|
||||
// that it was in fact called (twice).
|
||||
assert.deepStrictEqual(normalizeCalls, [
|
||||
["2", 2],
|
||||
[2, "2"],
|
||||
]);
|
||||
});
|
||||
});
|
||||
});
|
||||
110
node_modules/optimism/src/tests/cache.ts
generated
vendored
Normal file
110
node_modules/optimism/src/tests/cache.ts
generated
vendored
Normal file
@@ -0,0 +1,110 @@
|
||||
import * as assert from "assert";
|
||||
import { StrongCache as Cache } from "@wry/caches";
|
||||
|
||||
describe("least-recently-used cache", function () {
|
||||
it("can hold lots of elements", function () {
|
||||
const cache = new Cache();
|
||||
const count = 1000000;
|
||||
|
||||
for (let i = 0; i < count; ++i) {
|
||||
cache.set(i, String(i));
|
||||
}
|
||||
|
||||
cache.clean();
|
||||
|
||||
assert.strictEqual((cache as any).map.size, count);
|
||||
assert.ok(cache.has(0));
|
||||
assert.ok(cache.has(count - 1));
|
||||
assert.strictEqual(cache.get(43), "43");
|
||||
});
|
||||
|
||||
it("evicts excess old elements", function () {
|
||||
const max = 10;
|
||||
const evicted = [];
|
||||
const cache = new Cache(max, (value, key) => {
|
||||
assert.strictEqual(String(key), value);
|
||||
evicted.push(key);
|
||||
});
|
||||
|
||||
const count = 100;
|
||||
const keys = [];
|
||||
for (let i = 0; i < count; ++i) {
|
||||
cache.set(i, String(i));
|
||||
keys.push(i);
|
||||
}
|
||||
|
||||
cache.clean();
|
||||
|
||||
assert.strictEqual((cache as any).map.size, max);
|
||||
assert.strictEqual(evicted.length, count - max);
|
||||
|
||||
for (let i = count - max; i < count; ++i) {
|
||||
assert.ok(cache.has(i));
|
||||
}
|
||||
});
|
||||
|
||||
it("can cope with small max values", function () {
|
||||
const cache = new Cache(2);
|
||||
|
||||
function check(...sequence: number[]) {
|
||||
cache.clean();
|
||||
|
||||
let entry = (cache as any).newest;
|
||||
const forwards = [];
|
||||
while (entry) {
|
||||
forwards.push(entry.key);
|
||||
entry = entry.older;
|
||||
}
|
||||
assert.deepEqual(forwards, sequence);
|
||||
|
||||
const backwards = [];
|
||||
entry = (cache as any).oldest;
|
||||
while (entry) {
|
||||
backwards.push(entry.key);
|
||||
entry = entry.newer;
|
||||
}
|
||||
backwards.reverse();
|
||||
assert.deepEqual(backwards, sequence);
|
||||
|
||||
sequence.forEach(function (n) {
|
||||
assert.strictEqual((cache as any).map.get(n).value, n + 1);
|
||||
});
|
||||
|
||||
if (sequence.length > 0) {
|
||||
assert.strictEqual((cache as any).newest.key, sequence[0]);
|
||||
assert.strictEqual(
|
||||
(cache as any).oldest.key,
|
||||
sequence[sequence.length - 1]
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
cache.set(1, 2);
|
||||
check(1);
|
||||
|
||||
cache.set(2, 3);
|
||||
check(2, 1);
|
||||
|
||||
cache.set(3, 4);
|
||||
check(3, 2);
|
||||
|
||||
cache.get(2);
|
||||
check(2, 3);
|
||||
|
||||
cache.set(4, 5);
|
||||
check(4, 2);
|
||||
|
||||
assert.strictEqual(cache.has(1), false);
|
||||
assert.strictEqual(cache.get(2), 3);
|
||||
assert.strictEqual(cache.has(3), false);
|
||||
assert.strictEqual(cache.get(4), 5);
|
||||
|
||||
cache.delete(2);
|
||||
check(4);
|
||||
cache.delete(4);
|
||||
check();
|
||||
|
||||
assert.strictEqual((cache as any).newest, null);
|
||||
assert.strictEqual((cache as any).oldest, null);
|
||||
});
|
||||
});
|
||||
208
node_modules/optimism/src/tests/context.ts
generated
vendored
Normal file
208
node_modules/optimism/src/tests/context.ts
generated
vendored
Normal file
@@ -0,0 +1,208 @@
|
||||
import * as assert from "assert";
|
||||
import {
|
||||
wrap,
|
||||
setTimeout,
|
||||
asyncFromGen,
|
||||
noContext,
|
||||
nonReactive,
|
||||
Slot,
|
||||
} from '../index.js';
|
||||
|
||||
describe("asyncFromGen", function () {
|
||||
it("is importable", function () {
|
||||
assert.strictEqual(typeof asyncFromGen, "function");
|
||||
});
|
||||
|
||||
it("works like an async function", asyncFromGen(function*(): Generator<
|
||||
number | Promise<number>,
|
||||
Promise<string>,
|
||||
number
|
||||
> {
|
||||
let sum = 0;
|
||||
const limit = yield new Promise<number>(resolve => {
|
||||
setTimeout(() => resolve(10), 10);
|
||||
});
|
||||
for (let i = 0; i < limit; ++i) {
|
||||
sum += yield i + 1;
|
||||
}
|
||||
assert.strictEqual(sum, 55);
|
||||
return Promise.resolve("ok");
|
||||
}));
|
||||
|
||||
it("properly handles exceptions", async function () {
|
||||
const fn = asyncFromGen(function*(throwee?: object): Generator<
|
||||
Promise<string> | object,
|
||||
string,
|
||||
string
|
||||
> {
|
||||
const result = yield Promise.resolve("ok");
|
||||
if (throwee) {
|
||||
throw yield throwee;
|
||||
}
|
||||
return result;
|
||||
});
|
||||
|
||||
const okPromise = fn();
|
||||
const expected = {};
|
||||
const koPromise = fn(expected);
|
||||
|
||||
assert.strictEqual(await okPromise, "ok");
|
||||
|
||||
try {
|
||||
await koPromise;
|
||||
throw new Error("not reached");
|
||||
} catch (error) {
|
||||
assert.strictEqual(error, expected);
|
||||
}
|
||||
|
||||
try {
|
||||
await fn(Promise.resolve("oyez"));
|
||||
throw new Error("not reached");
|
||||
} catch (thrown) {
|
||||
assert.strictEqual(thrown, "oyez");
|
||||
}
|
||||
|
||||
const catcher = asyncFromGen(function*() {
|
||||
try {
|
||||
yield Promise.reject(new Error("expected"));
|
||||
throw new Error("not reached");
|
||||
} catch (error: any) {
|
||||
assert.strictEqual(error.message, "expected");
|
||||
}
|
||||
return "ok";
|
||||
});
|
||||
|
||||
return catcher().then(result => {
|
||||
assert.strictEqual(result, "ok");
|
||||
});
|
||||
});
|
||||
|
||||
it("can be cached", async function () {
|
||||
let parentCounter = 0;
|
||||
const parent = wrap(asyncFromGen(function*(x: number): Generator<
|
||||
Promise<number>,
|
||||
number,
|
||||
number
|
||||
> {
|
||||
++parentCounter;
|
||||
const a = yield new Promise<number>(resolve => setTimeout(() => {
|
||||
resolve(child(x));
|
||||
}, 10));
|
||||
const b = yield new Promise<number>(resolve => setTimeout(() => {
|
||||
resolve(child(x + 1));
|
||||
}, 20));
|
||||
return a * b;
|
||||
}));
|
||||
|
||||
let childCounter = 0;
|
||||
const child = wrap((x: number) => {
|
||||
return ++childCounter;
|
||||
});
|
||||
|
||||
assert.strictEqual(parentCounter, 0);
|
||||
assert.strictEqual(childCounter, 0);
|
||||
const parentPromise = parent(123);
|
||||
assert.strictEqual(parentCounter, 1);
|
||||
assert.strictEqual(await parentPromise, 2);
|
||||
assert.strictEqual(childCounter, 2);
|
||||
|
||||
assert.strictEqual(parent(123), parentPromise);
|
||||
assert.strictEqual(parentCounter, 1);
|
||||
assert.strictEqual(childCounter, 2);
|
||||
|
||||
child.dirty(123);
|
||||
|
||||
assert.strictEqual(await parent(123), 3 * 2);
|
||||
assert.strictEqual(parentCounter, 2);
|
||||
assert.strictEqual(childCounter, 3);
|
||||
|
||||
assert.strictEqual(await parent(456), 4 * 5);
|
||||
assert.strictEqual(parentCounter, 3);
|
||||
assert.strictEqual(childCounter, 5);
|
||||
|
||||
assert.strictEqual(parent(666), parent(666));
|
||||
assert.strictEqual(await parent(666), await parent(666));
|
||||
assert.strictEqual(parentCounter, 4);
|
||||
assert.strictEqual(childCounter, 7);
|
||||
|
||||
child.dirty(667);
|
||||
|
||||
assert.strictEqual(await parent(667), 8 * 9);
|
||||
assert.strictEqual(await parent(667), 8 * 9);
|
||||
assert.strictEqual(parentCounter, 5);
|
||||
assert.strictEqual(childCounter, 9);
|
||||
|
||||
assert.strictEqual(await parent(123), 3 * 2);
|
||||
assert.strictEqual(parentCounter, 5);
|
||||
assert.strictEqual(childCounter, 9);
|
||||
});
|
||||
});
|
||||
|
||||
describe("noContext", function () {
|
||||
it("prevents registering dependencies", function () {
|
||||
let parentCounter = 0;
|
||||
const parent = wrap(() => {
|
||||
return [++parentCounter, noContext(child)];
|
||||
});
|
||||
|
||||
let childCounter = 0;
|
||||
const child = wrap(() => ++childCounter);
|
||||
|
||||
assert.deepEqual(parent(), [1, 1]);
|
||||
assert.deepEqual(parent(), [1, 1]);
|
||||
parent.dirty();
|
||||
assert.deepEqual(parent(), [2, 1]);
|
||||
// Calling child.dirty() does not dirty the parent:
|
||||
child.dirty();
|
||||
assert.deepEqual(parent(), [2, 1]);
|
||||
parent.dirty();
|
||||
assert.deepEqual(parent(), [3, 2]);
|
||||
assert.deepEqual(parent(), [3, 2]);
|
||||
parent.dirty();
|
||||
assert.deepEqual(parent(), [4, 2]);
|
||||
});
|
||||
});
|
||||
|
||||
describe("nonReactive", function () {
|
||||
const otherSlot = new Slot<string>();
|
||||
|
||||
it("censors only optimism-related context", function () {
|
||||
let innerCounter = 0;
|
||||
const inner = wrap(() => ++innerCounter);
|
||||
const outer = wrap(() => ({
|
||||
fromInner: nonReactive(() => inner()),
|
||||
fromOther: nonReactive(() => otherSlot.getValue()),
|
||||
}));
|
||||
assert.strictEqual(otherSlot.getValue(), undefined);
|
||||
otherSlot.withValue("preserved", () => {
|
||||
assert.deepEqual(outer(), { fromInner: 1, fromOther: "preserved" });
|
||||
assert.deepEqual(outer(), { fromInner: 1, fromOther: "preserved" });
|
||||
inner.dirty();
|
||||
assert.deepEqual(outer(), { fromInner: 1, fromOther: "preserved" });
|
||||
assert.strictEqual(inner(), 2);
|
||||
outer.dirty();
|
||||
assert.deepEqual(outer(), { fromInner: 2, fromOther: "preserved" });
|
||||
});
|
||||
assert.strictEqual(otherSlot.getValue(), undefined);
|
||||
});
|
||||
|
||||
it("same test using noContext, for comparison", function () {
|
||||
let innerCounter = 0;
|
||||
const inner = wrap(() => ++innerCounter);
|
||||
const outer = wrap(() => ({
|
||||
fromInner: noContext(inner),
|
||||
fromOther: noContext(() => otherSlot.getValue()),
|
||||
}));
|
||||
assert.strictEqual(otherSlot.getValue(), undefined);
|
||||
otherSlot.withValue("preserved", () => {
|
||||
assert.deepEqual(outer(), { fromInner: 1, fromOther: void 0 });
|
||||
assert.deepEqual(outer(), { fromInner: 1, fromOther: void 0 });
|
||||
inner.dirty();
|
||||
assert.deepEqual(outer(), { fromInner: 1, fromOther: void 0 });
|
||||
assert.strictEqual(inner(), 2);
|
||||
outer.dirty();
|
||||
assert.deepEqual(outer(), { fromInner: 2, fromOther: void 0 });
|
||||
});
|
||||
assert.strictEqual(otherSlot.getValue(), undefined);
|
||||
});
|
||||
});
|
||||
183
node_modules/optimism/src/tests/deps.ts
generated
vendored
Normal file
183
node_modules/optimism/src/tests/deps.ts
generated
vendored
Normal file
@@ -0,0 +1,183 @@
|
||||
import * as assert from "assert";
|
||||
import { wrap, dep } from "../index";
|
||||
|
||||
describe("OptimisticDependencyFunction<TKey>", () => {
|
||||
it("can dirty OptimisticWrapperFunctions", () => {
|
||||
const numberDep = dep<number>();
|
||||
const stringDep = dep<string>();
|
||||
let callCount = 0;
|
||||
|
||||
const fn = wrap((n: number, s: string) => {
|
||||
numberDep(n);
|
||||
stringDep(s);
|
||||
++callCount;
|
||||
return s.repeat(n);
|
||||
});
|
||||
|
||||
assert.strictEqual(fn(0, "oyez"), "");
|
||||
assert.strictEqual(callCount, 1);
|
||||
assert.strictEqual(fn(1, "oyez"), "oyez");
|
||||
assert.strictEqual(callCount, 2);
|
||||
assert.strictEqual(fn(2, "oyez"), "oyezoyez");
|
||||
assert.strictEqual(callCount, 3);
|
||||
|
||||
assert.strictEqual(fn(0, "oyez"), "");
|
||||
assert.strictEqual(fn(1, "oyez"), "oyez");
|
||||
assert.strictEqual(fn(2, "oyez"), "oyezoyez");
|
||||
assert.strictEqual(callCount, 3);
|
||||
|
||||
numberDep.dirty(0);
|
||||
assert.strictEqual(fn(0, "oyez"), "");
|
||||
assert.strictEqual(callCount, 4);
|
||||
assert.strictEqual(fn(1, "oyez"), "oyez");
|
||||
assert.strictEqual(callCount, 4);
|
||||
assert.strictEqual(fn(2, "oyez"), "oyezoyez");
|
||||
assert.strictEqual(callCount, 4);
|
||||
|
||||
stringDep.dirty("mlem");
|
||||
assert.strictEqual(fn(0, "oyez"), "");
|
||||
assert.strictEqual(callCount, 4);
|
||||
|
||||
stringDep.dirty("oyez");
|
||||
assert.strictEqual(fn(2, "oyez"), "oyezoyez");
|
||||
assert.strictEqual(callCount, 5);
|
||||
assert.strictEqual(fn(1, "oyez"), "oyez");
|
||||
assert.strictEqual(callCount, 6);
|
||||
assert.strictEqual(fn(0, "oyez"), "");
|
||||
assert.strictEqual(callCount, 7);
|
||||
|
||||
assert.strictEqual(fn(0, "oyez"), "");
|
||||
assert.strictEqual(fn(1, "oyez"), "oyez");
|
||||
assert.strictEqual(fn(2, "oyez"), "oyezoyez");
|
||||
assert.strictEqual(callCount, 7);
|
||||
});
|
||||
|
||||
it("should be forgotten when parent is recomputed", () => {
|
||||
const d = dep<string>();
|
||||
let callCount = 0;
|
||||
let shouldDepend = true;
|
||||
|
||||
const parent = wrap((id: string) => {
|
||||
if (shouldDepend) d(id);
|
||||
return ++callCount;
|
||||
});
|
||||
|
||||
assert.strictEqual(parent("oyez"), 1);
|
||||
assert.strictEqual(parent("oyez"), 1);
|
||||
assert.strictEqual(parent("mlem"), 2);
|
||||
assert.strictEqual(parent("mlem"), 2);
|
||||
|
||||
d.dirty("mlem");
|
||||
assert.strictEqual(parent("oyez"), 1);
|
||||
assert.strictEqual(parent("mlem"), 3);
|
||||
|
||||
d.dirty("oyez");
|
||||
assert.strictEqual(parent("oyez"), 4);
|
||||
assert.strictEqual(parent("mlem"), 3);
|
||||
|
||||
parent.dirty("oyez");
|
||||
shouldDepend = false;
|
||||
assert.strictEqual(parent("oyez"), 5);
|
||||
assert.strictEqual(parent("mlem"), 3);
|
||||
d.dirty("oyez");
|
||||
shouldDepend = true;
|
||||
assert.strictEqual(parent("oyez"), 5);
|
||||
assert.strictEqual(parent("mlem"), 3);
|
||||
// This still has no effect because the previous call to parent("oyez")
|
||||
// was cached.
|
||||
d.dirty("oyez");
|
||||
assert.strictEqual(parent("oyez"), 5);
|
||||
assert.strictEqual(parent("mlem"), 3);
|
||||
parent.dirty("oyez");
|
||||
assert.strictEqual(parent("oyez"), 6);
|
||||
assert.strictEqual(parent("mlem"), 3);
|
||||
d.dirty("oyez");
|
||||
assert.strictEqual(parent("oyez"), 7);
|
||||
assert.strictEqual(parent("mlem"), 3);
|
||||
|
||||
parent.dirty("mlem");
|
||||
shouldDepend = false;
|
||||
assert.strictEqual(parent("oyez"), 7);
|
||||
assert.strictEqual(parent("mlem"), 8);
|
||||
d.dirty("oyez");
|
||||
d.dirty("mlem");
|
||||
assert.strictEqual(parent("oyez"), 9);
|
||||
assert.strictEqual(parent("mlem"), 8);
|
||||
d.dirty("oyez");
|
||||
d.dirty("mlem");
|
||||
assert.strictEqual(parent("oyez"), 9);
|
||||
assert.strictEqual(parent("mlem"), 8);
|
||||
shouldDepend = true;
|
||||
parent.dirty("mlem");
|
||||
assert.strictEqual(parent("oyez"), 9);
|
||||
assert.strictEqual(parent("mlem"), 10);
|
||||
d.dirty("oyez");
|
||||
d.dirty("mlem");
|
||||
assert.strictEqual(parent("oyez"), 9);
|
||||
assert.strictEqual(parent("mlem"), 11);
|
||||
});
|
||||
|
||||
it("supports subscribing and unsubscribing", function () {
|
||||
let subscribeCallCount = 0;
|
||||
let unsubscribeCallCount = 0;
|
||||
let parentCallCount = 0;
|
||||
|
||||
function check(counts: {
|
||||
subscribe: number;
|
||||
unsubscribe: number;
|
||||
parent: number;
|
||||
}) {
|
||||
assert.strictEqual(counts.subscribe, subscribeCallCount);
|
||||
assert.strictEqual(counts.unsubscribe, unsubscribeCallCount);
|
||||
assert.strictEqual(counts.parent, parentCallCount);
|
||||
}
|
||||
|
||||
const d = dep({
|
||||
subscribe(key: string) {
|
||||
++subscribeCallCount;
|
||||
return () => {
|
||||
++unsubscribeCallCount;
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
assert.strictEqual(subscribeCallCount, 0);
|
||||
assert.strictEqual(unsubscribeCallCount, 0);
|
||||
|
||||
const parent = wrap((key: string) => {
|
||||
d(key);
|
||||
return ++parentCallCount;
|
||||
});
|
||||
|
||||
assert.strictEqual(parent("rawr"), 1);
|
||||
check({ subscribe: 1, unsubscribe: 0, parent: 1 });
|
||||
assert.strictEqual(parent("rawr"), 1);
|
||||
check({ subscribe: 1, unsubscribe: 0, parent: 1 });
|
||||
assert.strictEqual(parent("blep"), 2);
|
||||
check({ subscribe: 2, unsubscribe: 0, parent: 2 });
|
||||
assert.strictEqual(parent("rawr"), 1);
|
||||
check({ subscribe: 2, unsubscribe: 0, parent: 2 });
|
||||
assert.strictEqual(parent("blep"), 2);
|
||||
check({ subscribe: 2, unsubscribe: 0, parent: 2 });
|
||||
|
||||
d.dirty("blep");
|
||||
check({ subscribe: 2, unsubscribe: 1, parent: 2 });
|
||||
assert.strictEqual(parent("rawr"), 1);
|
||||
check({ subscribe: 2, unsubscribe: 1, parent: 2 });
|
||||
d.dirty("blep"); // intentionally redundant
|
||||
check({ subscribe: 2, unsubscribe: 1, parent: 2 });
|
||||
assert.strictEqual(parent("blep"), 3);
|
||||
check({ subscribe: 3, unsubscribe: 1, parent: 3 });
|
||||
assert.strictEqual(parent("blep"), 3);
|
||||
check({ subscribe: 3, unsubscribe: 1, parent: 3 });
|
||||
|
||||
d.dirty("rawr");
|
||||
check({ subscribe: 3, unsubscribe: 2, parent: 3 });
|
||||
assert.strictEqual(parent("blep"), 3);
|
||||
check({ subscribe: 3, unsubscribe: 2, parent: 3 });
|
||||
assert.strictEqual(parent("rawr"), 4);
|
||||
check({ subscribe: 4, unsubscribe: 2, parent: 4 });
|
||||
assert.strictEqual(parent("blep"), 3);
|
||||
check({ subscribe: 4, unsubscribe: 2, parent: 4 });
|
||||
});
|
||||
});
|
||||
76
node_modules/optimism/src/tests/exceptions.ts
generated
vendored
Normal file
76
node_modules/optimism/src/tests/exceptions.ts
generated
vendored
Normal file
@@ -0,0 +1,76 @@
|
||||
import * as assert from "assert";
|
||||
import { wrap } from "../index.js";
|
||||
|
||||
describe("exceptions", function () {
|
||||
it("should be cached", function () {
|
||||
const error = new Error("expected");
|
||||
let threw = false;
|
||||
function throwOnce() {
|
||||
if (!threw) {
|
||||
threw = true;
|
||||
throw error;
|
||||
}
|
||||
return "already threw";
|
||||
}
|
||||
|
||||
const wrapper = wrap(throwOnce);
|
||||
|
||||
try {
|
||||
wrapper();
|
||||
throw new Error("unreached");
|
||||
} catch (e) {
|
||||
assert.strictEqual(e, error);
|
||||
}
|
||||
|
||||
try {
|
||||
wrapper();
|
||||
throw new Error("unreached");
|
||||
} catch (e) {
|
||||
assert.strictEqual(e, error);
|
||||
}
|
||||
|
||||
wrapper.dirty();
|
||||
assert.strictEqual(wrapper(), "already threw");
|
||||
assert.strictEqual(wrapper(), "already threw");
|
||||
wrapper.dirty();
|
||||
assert.strictEqual(wrapper(), "already threw");
|
||||
});
|
||||
|
||||
it("should memoize a throwing fibonacci function", function () {
|
||||
const fib = wrap((n: number) => {
|
||||
if (n < 2) throw n;
|
||||
try {
|
||||
fib(n - 1);
|
||||
} catch (minusOne: any) {
|
||||
try {
|
||||
fib(n - 2);
|
||||
} catch (minusTwo: any) {
|
||||
throw minusOne + minusTwo;
|
||||
}
|
||||
}
|
||||
throw new Error("unreached");
|
||||
});
|
||||
|
||||
function check(n: number, expected: number) {
|
||||
try {
|
||||
fib(n);
|
||||
throw new Error("unreached");
|
||||
} catch (result) {
|
||||
assert.strictEqual(result, expected);
|
||||
}
|
||||
}
|
||||
|
||||
check(78, 8944394323791464);
|
||||
check(68, 72723460248141);
|
||||
check(58, 591286729879);
|
||||
check(48, 4807526976);
|
||||
fib.dirty(28);
|
||||
check(38, 39088169);
|
||||
check(28, 317811);
|
||||
check(18, 2584);
|
||||
check(8, 21);
|
||||
fib.dirty(20);
|
||||
check(78, 8944394323791464);
|
||||
check(10, 55);
|
||||
});
|
||||
});
|
||||
90
node_modules/optimism/src/tests/key-trie.ts
generated
vendored
Normal file
90
node_modules/optimism/src/tests/key-trie.ts
generated
vendored
Normal file
@@ -0,0 +1,90 @@
|
||||
import * as assert from "assert";
|
||||
import { KeyTrie } from "../index";
|
||||
|
||||
describe("KeyTrie", function () {
|
||||
it("can be imported", function () {
|
||||
assert.strictEqual(typeof KeyTrie, "function");
|
||||
});
|
||||
|
||||
it("can hold objects weakly", function () {
|
||||
const trie = new KeyTrie<object>(true);
|
||||
assert.strictEqual((trie as any).weakness, true);
|
||||
const obj1 = {};
|
||||
assert.strictEqual(
|
||||
trie.lookup(obj1, 2, 3),
|
||||
trie.lookup(obj1, 2, 3),
|
||||
);
|
||||
const obj2 = {};
|
||||
assert.notStrictEqual(
|
||||
trie.lookup(1, obj2),
|
||||
trie.lookup(1, obj2, 3),
|
||||
);
|
||||
assert.strictEqual((trie as any).weak.has(obj1), true);
|
||||
assert.strictEqual((trie as any).strong.has(obj1), false);
|
||||
assert.strictEqual((trie as any).strong.get(1).weak.has(obj2), true);
|
||||
assert.strictEqual((trie as any).strong.get(1).weak.get(obj2).strong.has(3), true);
|
||||
});
|
||||
|
||||
it("can disable WeakMap", function () {
|
||||
const trie = new KeyTrie<object>(false);
|
||||
assert.strictEqual((trie as any).weakness, false);
|
||||
const obj1 = {};
|
||||
assert.strictEqual(
|
||||
trie.lookup(obj1, 2, 3),
|
||||
trie.lookup(obj1, 2, 3),
|
||||
);
|
||||
const obj2 = {};
|
||||
assert.notStrictEqual(
|
||||
trie.lookup(1, obj2),
|
||||
trie.lookup(1, obj2, 3),
|
||||
);
|
||||
assert.strictEqual(typeof (trie as any).weak, "undefined");
|
||||
assert.strictEqual((trie as any).strong.has(obj1), true);
|
||||
assert.strictEqual((trie as any).strong.has(1), true);
|
||||
assert.strictEqual((trie as any).strong.get(1).strong.has(obj2), true);
|
||||
assert.strictEqual((trie as any).strong.get(1).strong.get(obj2).strong.has(3), true);
|
||||
});
|
||||
|
||||
it("can produce data types other than Object", function () {
|
||||
const symbolTrie = new KeyTrie(true, args => Symbol.for(args.join(".")));
|
||||
const s123 = symbolTrie.lookup(1, 2, 3);
|
||||
assert.strictEqual(s123.toString(), "Symbol(1.2.3)");
|
||||
assert.strictEqual(s123, symbolTrie.lookup(1, 2, 3));
|
||||
assert.strictEqual(s123, symbolTrie.lookupArray([1, 2, 3]));
|
||||
const sNull = symbolTrie.lookup();
|
||||
assert.strictEqual(sNull.toString(), "Symbol()");
|
||||
|
||||
const regExpTrie = new KeyTrie(true, args => new RegExp("^(" + args.join("|") + ")$"));
|
||||
const rXYZ = regExpTrie.lookup("x", "y", "z");
|
||||
assert.strictEqual(rXYZ.test("w"), false);
|
||||
assert.strictEqual(rXYZ.test("x"), true);
|
||||
assert.strictEqual(rXYZ.test("y"), true);
|
||||
assert.strictEqual(rXYZ.test("z"), true);
|
||||
assert.strictEqual(String(rXYZ), "/^(x|y|z)$/");
|
||||
|
||||
class Data {
|
||||
constructor(public readonly args: any[]) {}
|
||||
}
|
||||
const dataTrie = new KeyTrie(true, args => new Data(args));
|
||||
function checkData(...args: any[]) {
|
||||
const data = dataTrie.lookupArray(args);
|
||||
assert.strictEqual(data instanceof Data, true);
|
||||
assert.notStrictEqual(data.args, args);
|
||||
assert.deepEqual(data.args, args);
|
||||
assert.strictEqual(data, dataTrie.lookup(...args));
|
||||
assert.strictEqual(data, dataTrie.lookupArray(arguments));
|
||||
return data;
|
||||
}
|
||||
const datas = [
|
||||
checkData(),
|
||||
checkData(1),
|
||||
checkData(1, 2),
|
||||
checkData(2),
|
||||
checkData(2, 3),
|
||||
checkData(true, "a"),
|
||||
checkData(/asdf/i, "b", function oyez() {}),
|
||||
];
|
||||
// Verify that all Data objects are distinct.
|
||||
assert.strictEqual(new Set(datas).size, datas.length);
|
||||
});
|
||||
});
|
||||
7
node_modules/optimism/src/tests/main.ts
generated
vendored
Normal file
7
node_modules/optimism/src/tests/main.ts
generated
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
import "./api";
|
||||
import "./deps";
|
||||
import "./cache";
|
||||
import "./key-trie";
|
||||
import "./context";
|
||||
import "./exceptions";
|
||||
import "./performance";
|
||||
86
node_modules/optimism/src/tests/performance.ts
generated
vendored
Normal file
86
node_modules/optimism/src/tests/performance.ts
generated
vendored
Normal file
@@ -0,0 +1,86 @@
|
||||
import * as assert from "assert";
|
||||
import { wrap, dep, KeyTrie } from "../index";
|
||||
|
||||
describe("performance", function () {
|
||||
this.timeout(30000);
|
||||
|
||||
it("should be able to tolerate lots of Entry objects", function () {
|
||||
let counter = 0;
|
||||
const child = wrap((a: any, b: any) => counter++);
|
||||
const parent = wrap((obj1: object, num: number, obj2: object) => {
|
||||
child(obj1, counter);
|
||||
child(counter, obj2);
|
||||
return counter++;
|
||||
});
|
||||
for (let i = 0; i < 100000; ++i) {
|
||||
parent({}, i, {});
|
||||
}
|
||||
});
|
||||
|
||||
const keys: object[] = [];
|
||||
for (let i = 0; i < 100000; ++i) {
|
||||
keys.push({ i });
|
||||
}
|
||||
|
||||
it("should be able to tolerate lots of deps", function () {
|
||||
const d = dep<object>();
|
||||
const parent = wrap((id: number) => {
|
||||
keys.forEach(d);
|
||||
return id;
|
||||
});
|
||||
parent(1);
|
||||
parent(2);
|
||||
parent(3);
|
||||
keys.forEach(key => d.dirty(key));
|
||||
});
|
||||
|
||||
it("can speed up sorting with O(array.length) cache lookup", function () {
|
||||
let counter = 0;
|
||||
const trie = new KeyTrie(false);
|
||||
const sort = wrap((array: number[]) => {
|
||||
++counter;
|
||||
return array.slice(0).sort();
|
||||
}, {
|
||||
makeCacheKey(array) {
|
||||
return trie.lookupArray(array);
|
||||
}
|
||||
});
|
||||
|
||||
assert.deepEqual(sort([2, 1, 5, 4]), [1, 2, 4, 5]);
|
||||
assert.strictEqual(counter, 1);
|
||||
assert.strictEqual(
|
||||
sort([2, 1, 5, 4]),
|
||||
sort([2, 1, 5, 4]),
|
||||
);
|
||||
assert.strictEqual(counter, 1);
|
||||
|
||||
assert.deepEqual(sort([3, 2, 1]), [1, 2, 3]);
|
||||
assert.strictEqual(counter, 2);
|
||||
|
||||
const bigArray: number[] = [];
|
||||
for (let i = 0; i < 100000; ++i) {
|
||||
bigArray.push(Math.round(Math.random() * 100));
|
||||
}
|
||||
|
||||
const bigArrayCopy = bigArray.slice(0);
|
||||
const rawSortStartTime = Date.now();
|
||||
bigArrayCopy.sort();
|
||||
const rawSortTime = Date.now() - rawSortStartTime;
|
||||
|
||||
assert.deepEqual(
|
||||
sort(bigArray),
|
||||
bigArrayCopy,
|
||||
);
|
||||
|
||||
const cachedSortStartTime = Date.now();
|
||||
const cached = sort(bigArray);
|
||||
const cachedSortTime = Date.now() - cachedSortStartTime;
|
||||
|
||||
assert.deepEqual(cached, bigArrayCopy);
|
||||
assert.ok(
|
||||
cachedSortTime <= rawSortTime,
|
||||
`cached: ${cachedSortTime}ms, raw: ${rawSortTime}ms`,
|
||||
);
|
||||
assert.strictEqual(counter, 3);
|
||||
});
|
||||
});
|
||||
14
node_modules/optimism/src/tests/test-utils.ts
generated
vendored
Normal file
14
node_modules/optimism/src/tests/test-utils.ts
generated
vendored
Normal file
@@ -0,0 +1,14 @@
|
||||
export function permutations<T>(array: T[], start = 0): T[][] {
|
||||
if (start === array.length) return [[]];
|
||||
const item = array[start];
|
||||
const results: T[][] = [];
|
||||
permutations<T>(array, start + 1).forEach(perm => {
|
||||
perm.forEach((_, i) => {
|
||||
const copy = perm.slice(0);
|
||||
copy.splice(i, 0, item);
|
||||
results.push(copy);
|
||||
});
|
||||
results.push(perm.concat(item));
|
||||
});
|
||||
return results;
|
||||
}
|
||||
Reference in New Issue
Block a user