Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

optimism

Package Overview
Dependencies
Maintainers
1
Versions
72
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

optimism - npm Package Compare versions

Comparing version 0.5.1 to 0.5.2

316

lib/entry.js
"use strict";
var assert = require("assert");
var getLocal = require("./local.js").get;
var UNKNOWN_VALUE = Object.create(null);
var emptySetPool = [];
var entryPool = [];
function Entry(fn, key, args) {
assert(this instanceof Entry);
// 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");
}
}
this.fn = fn;
this.key = key;
this.args = args;
this.value = UNKNOWN_VALUE;
this.dirty = true;
function Entry(fn, args) {
this.parents = new Set;

@@ -24,6 +25,30 @@ this.childValues = new Map;

this.subscribe = null;
this.unsubscribe = null;
reset(this, fn, args);
}
function reset(entry, fn, args) {
entry.fn = fn;
entry.args = args;
entry.value = UNKNOWN_VALUE;
entry.dirty = true;
entry.subscribe = null;
entry.unsubscribe = null;
entry.recomputing = false;
}
Entry.acquire = function (fn, args) {
var entry = entryPool.pop();
if (entry) {
reset(entry, fn, args);
return entry;
}
return new Entry(fn, args);
};
function release(entry) {
assert(entry.parents.size === 0);
forgetChildren(entry);
entryPool.push(entry);
}
exports.Entry = Entry;

@@ -33,58 +58,52 @@

// The public API of Entry objects consists of the Entry constructor,
// along with the recompute, setDirty, and dispose methods.
Ep.recompute = function recompute() {
rememberParent(this);
return recomputeIfDirty(this);
};
Ep.setDirty = function setDirty() {
if (this.dirty) return;
this.dirty = true;
this.parents.forEach(reportDirty, this);
this.value = UNKNOWN_VALUE;
// Since we're explicitly setting this.dirty = true, we won't need to
// examine any of our children in recomputeIfDirty, so we can go ahead
// and forget them.
forgetChildren(this);
reportDirty(this);
};
Ep.mightBeDirty = function mightBeDirty() {
return this.dirty ||
(this.dirtyChildren &&
this.dirtyChildren.size > 0);
};
Ep.recompute = function recompute() {
var parent = this._rememberParent();
var value = this._recomputeIfDirty();
if (parent) {
parent.childValues.set(this, this.value);
parent._removeDirtyChild(this);
}
return value;
};
Ep.dispose = function dispose() {
// If we're no longer going to be subscribed to changes affecting this
// Entry, then we'd better inform its parents that it needs to be
// recomputed.
this.setDirty();
this._forgetChildren();
unsubscribe(this);
};
var entry = this;
forgetChildren(entry);
unsubscribe(entry);
Ep._rememberChild = function _rememberChild(child) {
child.parents.add(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.
entry.parents.forEach(function (parent) {
// Causes parent to forget all of its children, including entry.
parent.setDirty();
});
if (! this.childValues.has(child)) {
this.childValues.set(child, UNKNOWN_VALUE);
}
if (child.mightBeDirty()) {
this._reportDirtyChild(child);
} else {
this._reportCleanChild(child);
}
// Since this entry has no parents and no children anymore, and the
// caller of Entry#dispose has indicated that entry.value no longer
// matters, we can safely recycle this Entry object for later use.
release(entry);
};
Ep._forgetChild = function _forgetChild(child) {
child.parents.delete(this);
this._removeDirtyChild(child);
this.childValues.delete(child);
};
function setClean(entry) {
entry.dirty = false;
Ep._setClean = function _setClean() {
this.dirty = false;
if (this.dirtyChildren &&
this.dirtyChildren.size > 0) {
if (mightBeDirty(entry)) {
// This Entry may still have dirty children, in which case we can't

@@ -95,26 +114,34 @@ // let our parents know we're clean just yet.

this.parents.forEach(reportClean, this);
};
reportClean(entry);
}
function reportDirty(parent) {
parent._reportDirtyChild(this);
function reportDirty(entry) {
entry.parents.forEach(function (parent) {
reportDirtyChild(parent, entry);
});
}
function reportClean(parent) {
parent._reportCleanChild(this);
function reportClean(entry) {
entry.parents.forEach(function (parent) {
reportCleanChild(parent, entry);
});
}
function mightBeDirty(entry) {
return entry.dirty ||
(entry.dirtyChildren &&
entry.dirtyChildren.size);
}
// Let a parent Entry know that one of its children may be dirty.
Ep._reportDirtyChild = function _reportDirtyChild(child) {
// Must have called this._rememberChild(child) before calling
// this._reportDirtyChild(child).
assert(this.childValues.has(child));
assert(child.mightBeDirty());
function reportDirtyChild(entry, child) {
// Must have called rememberParent(child) before calling
// reportDirtyChild(parent, child).
assert(entry.childValues.has(child));
assert(mightBeDirty(child));
if (! this.dirtyChildren) {
// Initialize this.dirtyChildren with an empty set drawn from the
// emptySetPool if possible.
this.dirtyChildren = emptySetPool.pop() || new Set;
if (! entry.dirtyChildren) {
entry.dirtyChildren = emptySetPool.pop() || new Set;
} else if (this.dirtyChildren.has(child)) {
} else if (entry.dirtyChildren.has(child)) {
// If we already know this child is dirty, then we must have already

@@ -126,24 +153,28 @@ // informed our own parents that we are dirty, so we can terminate

this.dirtyChildren.add(child);
this.parents.forEach(reportDirty, this);
};
entry.dirtyChildren.add(child);
reportDirty(entry);
}
// Let a parent Entry know that one of its children is no longer dirty.
Ep._reportCleanChild = function _reportCleanChild(child) {
// Must have called this._rememberChild(child) before calling
// this._reportCleanChild(child).
assert(this.childValues.has(child));
assert(! child.mightBeDirty());
if (this.childValues.get(child) !== child.value) {
this.setDirty();
function reportCleanChild(entry, child) {
// Must have called rememberChild(child) before calling
// reportCleanChild(parent, child).
assert(entry.childValues.has(child));
assert(! mightBeDirty(child));
if (entry.childValues.get(child) !== child.value) {
entry.setDirty();
}
this._removeDirtyChild(child);
};
// Often we are removing a child because it is no longer dirty, so
// child.dirty is not a precondition for this method. Also note that the
// child may remain in this.childValues, so we definitely do not want to
// call child.parents.delete(this) here.
Ep._removeDirtyChild = function _removeDirtyChild(child) {
var dc = this.dirtyChildren;
removeDirtyChild(entry, child);
if (mightBeDirty(entry)) {
return;
}
reportClean(entry);
}
function removeDirtyChild(entry, child) {
var dc = entry.dirtyChildren;
if (dc) {

@@ -153,21 +184,26 @@ dc.delete(child);

emptySetPool.push(dc);
dc = this.dirtyChildren = null;
entry.dirtyChildren = null;
}
}
}
if (this.dirty || dc) {
return;
}
this.parents.forEach(reportClean, this);
};
Ep._rememberParent = function _rememberParent() {
function rememberParent(entry) {
var local = getLocal();
var parent = local.currentParentEntry;
if (parent) {
parent._rememberChild(this);
entry.parents.add(parent);
if (! parent.childValues.has(entry)) {
parent.childValues.set(entry, UNKNOWN_VALUE);
}
if (mightBeDirty(entry)) {
reportDirtyChild(parent, entry);
} else {
reportCleanChild(parent, entry);
}
return parent;
}
};
}

@@ -180,76 +216,86 @@ // This is the most important method of the Entry API, because it

// (3) this.value is usally returned very quickly, without recomputation.
Ep._recomputeIfDirty = function _recomputeIfDirty() {
if (this.dirty) {
function recomputeIfDirty(entry) {
if (entry.dirty) {
// If this Entry is explicitly dirty because someone called
// entry.setDirty(), recompute.
return this._reallyRecompute();
return reallyRecompute(entry);
}
if (this.dirtyChildren &&
this.dirtyChildren.size > 0) {
if (mightBeDirty(entry)) {
// Get fresh values for any dirty children, and if those values
// disagree with this.childValues, mark this Entry explicitly dirty.
this.dirtyChildren.forEach(function (child) {
assert(this.childValues.has(child));
entry.dirtyChildren.forEach(function (child) {
assert(entry.childValues.has(child));
try {
child._recomputeIfDirty();
recomputeIfDirty(child);
} catch (e) {
this.setDirty();
entry.setDirty();
}
}, this);
});
if (this.dirty) {
if (entry.dirty) {
// If this Entry has become explicitly dirty after comparing the fresh
// values of its dirty children against this.childValues, recompute.
return this._reallyRecompute();
return reallyRecompute(entry);
}
}
assert.notStrictEqual(this.value, UNKNOWN_VALUE);
assert(entry.value !== UNKNOWN_VALUE);
return this.value;
};
return entry.value;
}
Ep._reallyRecompute = function _reallyRecompute() {
this._forgetChildren();
function reallyRecompute(entry) {
assert(! entry.recomputing, "already recomputing");
entry.recomputing = true;
forgetChildren(entry);
var local = getLocal();
var parent = local.currentParentEntry;
local.currentParentEntry = this;
local.currentParentEntry = entry;
var threw = true;
try {
this.value = this.fn.apply(null, this.args);
entry.value = entry.fn.apply(null, entry.args);
threw = false;
} finally {
assert.strictEqual(local.currentParentEntry, this);
entry.recomputing = false;
assert(local.currentParentEntry === entry);
local.currentParentEntry = parent;
if (threw || ! subscribe(this)) {
// Mark this Entry dirty if this.fn threw or we failed to
if (threw || ! subscribe(entry)) {
// Mark this Entry dirty if entry.fn threw or we failed to
// resubscribe. This is important because, if we have a subscribe
// function and it failed, then we're going to miss important
// notifications about the potential dirtiness of this.value.
this.setDirty();
// notifications about the potential dirtiness of entry.value.
entry.setDirty();
} else {
// If we successfully recomputed this.value and did not fail to
// If we successfully recomputed entry.value and did not fail to
// (re)subscribe, then this Entry is no longer explicitly dirty.
this._setClean();
setClean(entry);
}
}
return this.value;
};
return entry.value;
}
Ep._forgetChildren = function _forgetChildren() {
this.childValues.forEach(function (value, child) {
this._forgetChild(child);
}, this);
function forgetChildren(entry) {
entry.childValues.forEach(function (value, child) {
forgetChild(entry, child);
});
// After we forget all our children, this.dirtyChildren must be empty
// and thus have been reset to null.
assert.strictEqual(this.dirtyChildren, null);
};
// and therefor must have been reset to null.
assert(entry.dirtyChildren === null);
}
function forgetChild(entry, child) {
child.parents.delete(entry);
entry.childValues.delete(child);
removeDirtyChild(entry, child);
}
function subscribe(entry) {

@@ -256,0 +302,0 @@ if (typeof entry.subscribe === "function") {

@@ -52,3 +52,3 @@ "use strict";

} else {
cache.set(key, entry = new Entry(fn, key, args));
cache.set(key, entry = Entry.acquire(fn, args));
entry.subscribe = options.subscribe;

@@ -55,0 +55,0 @@ }

{
"name": "optimism",
"version": "0.5.1",
"version": "0.5.2",
"author": "Ben Newman <ben@benjamn.com>",

@@ -5,0 +5,0 @@ "description": "Composable reactive caching with efficient invalidation.",

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc