inital upload

This commit is contained in:
jackbeeby
2025-05-15 13:35:49 +10:00
commit 8c53ff1000
9092 changed files with 1833300 additions and 0 deletions

9
node_modules/optimism/.github/dependabot.yml generated vendored Normal file
View 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
View 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
View 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
View File

@@ -0,0 +1,3 @@
# optimism [![Build Status](https://github.com/benjamn/optimism/workflows/CI/badge.svg)](https://github.com/benjamn/optimism/actions)
Composable reactive caching with efficient invalidation.

492
node_modules/optimism/lib/bundle.cjs generated vendored Normal file
View 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

File diff suppressed because one or more lines are too long

492
node_modules/optimism/lib/bundle.cjs.native.js generated vendored Normal file
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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

File diff suppressed because one or more lines are too long

7
node_modules/optimism/lib/helpers.d.ts generated vendored Normal file
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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;
}