npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@liquefy/cascade.reactive

v2.1.0

Published

Temporal signals: reactive programming with ES6 proxies, where readers and writers of the same data are ordered in time. The reactive engine of Cascade.

Readme

cascade.reactive

Temporal signals - reactive programming with ES6 proxies, where the readers and writers of the same data are ordered in time. The reactive engine of Cascade.

Signals usually give you one value per object and property, so every intermediate result needs a signal of its own, and an explicit dependency graph between them. But rendering, like any document transformation, is a sequence of steps transforming the same objects. With temporal signals, each reader and writer operates within its own time frame: a reader sees the value as of its place in the pipeline, and a change invalidates only the readers downstream of it.

npm install @liquefy/cascade.reactive

Temporal signals in one example

import getWorld from "@liquefy/cascade.reactive";
const { observable, repeat } = getWorld({ name: "example" });

// The same object, read and written along a pipeline of repeaters.
const layout = observable({ width: 1000 });

repeat(() => {
  repeat(() => console.log("Header sees width", layout.width));  // before the write
  repeat(() => { layout.width = layout.width - 200; });          // the sidebar takes 200
  repeat(() => console.log("Content sees width", layout.width)); // after it
});
// Header sees width 1000
// Content sees width 800

layout.width = 1200;
// Header sees width 1200
// Content sees width 1000

This is what lets a Cascade component render before its children, measure the room it has, and hand that down to them - in the same pass.

cascade.reactive grew out of causality (the reactive engine of Flow), and shares its API: the reference below still calls it causality in places.

Usage

It is possible to create several instances of causality, called worlds, each with its separate config and dependencies. As the name implies, you typically just create one single causality world for your app to use. But you can sometimes benefit from created several isolated worlds where causality isolates observation and event propagation within each world.

import getWorld from "@liquefy/cascade.reactive";
let myWorld = getWorld({name: "myWorld", ...moreOptions}); // Create an instance with possibility to configure it.
let { observable, repeat } = myWorld;

Calling getWorld multiple times with the same unique name given in the configuration, will return the same world object. Only the options given the first time will be considered. For consequtive calls with the same world name, the options will be ignored.

Quick Example

This is just to show a simple example of what causality is all about, using the simple repeat primitive.

import getWorld from "@liquefy/cascade.reactive";
let { observable, repeat } = getWorld();

let x = observable({propA: 11});
let y = observable({propB: 11, propC: 100});
let z;

repeat(function(){
    z = x.propA + y.propB;  // Sets up a reactive setting of variable z
});
// z is now 22

y.propB = 2;      // Setting of propB will cause reevaluation of z
// z is now 13

x.propA = 2;
// z is now 4

However, there are a lot more interesting features to try out.

Features

observable

With observable you simply create a causality object. The observable function takes any other Javascript object as input. Example usage:

observable({a: 1, b: 2, c: 3});
observable([1, 2, 3]);
observable(new MyClass());

A causality object is an object that can be observed by causality. This means that changes in causality objects will be detected automatically by the causality framework. While it is possible to mix the use of causality object and plain Javascript object, it is not recommended to do so, as changes in plain Javascript objects will go undetected by causality.

In every other aspect, a causality object behaves just as an ordinary Javascript object would. So you could for example write:

var x = observable({a: 1, b: 2});
var y = x.a + x.b; // 3

var l = observable([]);
l.push("item1");
l.push("item2");
l.pop();
console.log(l); // should print out ["item1"];

If you find it too cumbersome to write "observable" upon every object creation, you can create an alias "o":

o({a: 1, b: 2});

To access the inner workings of a causality object, you can access it by the causality property. For example write object.causality.id to get a system wide unique id for that particular object.

repeat (MobX autorun)

Repeat is typically used to enforce some certain data constraint. Such as reactive validation of a form, or calculation of some. In its basic form, it is simply a function that is reevaluated every time any of the data it read changes. For example:

repeat(function() {  x.value = y.value + z.value; });

This will cause x.value to be assigned to y.value + z.value any time either y.value or z.value changes. The good part is that you can write any kind of code inside the function. There can be loops, function calls, recursive functions. Anything. And no matter what code is there, causality will always keep track of what data has been read by the repeater function at any given moment.

It will however not detect changes in local variables, so for example if local or global variable y is assigned in this example, there will be no reevaluation of x.value. In practice however, this is in general not a limitation as application code typically reacts to changes to a specific model, rather than changes in local variables.

Using repeat with rebuilding keys

repeat is equipped with some special features when it comes to creating data structures. Assume that you have an algorithm that constructs a data structure. For example a balanced index structure of some sort. However, when the input data is changed and the algorithm needs to repeat once more, you do not want a completely new output data structure. Instead, you want to execute the algorithm once more, and at the end of it, compare the difference to the result of the last run, and update the previous result in a minimal way. This is something that is supported with causality!

React users might notice that this is actually a generalization of how React works with the synthetic dom. But instead of being limited to just dom structures, causality can do this trick for any data structure!

For example:

repeat(() => {
    for(let input of inputArray) {
        addToIndex(observable({value: input.value},"buildId" + input.id))
    }
})

Note that the only difference is that we added a unique build id for each created object in the data structure. This way causality will know what previously created object correspond to the objects created at each repetition.

Using repeat with time

When building a framework, such as a front end framework, it becomes apparent that some repeats should run at a later time than others. For example, if we have a rendering pipeline, that starts with model, view model and then ends with view. It would be highly innefficient if for every change in the model, we would update the view. Because say perhaps that there are 3 changes on the model level, that causes 9 changes in the view model level, that again causes 18 changes on the view itself. We want to finish all changes on the model level, before we start to work on the view model level, and we want to finish all changes on the view model, before we start to update the view. To do this, we need to stage repeaters into different times.

You want the update to propagate through your dependency network breadth first for minimal re-work.

For example:

repeat(() => updateViewModel(), {
    time: 1
})

To do this, causality has a limited number of time levels to divide repeaters into. When you execute a repeat operation, you can set the time of it: an integer from 0 up to, but not including, the world's timeLevels (4 by default - getWorld({ timeLevels }) sets another). A time outside that range is an error. A repeater created inside another's run is part of that one's pipeline, and its own time is ignored - it runs where its creator places it.

Also, in addition to using time levels. causality internally uses a queue for repeater updates, and this by itself is a heuristic that makes it more likley for changes to propagate breadth first. But depending on your dependencies, reevaluation at some stage could potentially occur, so time levels helps to enforce some degree of breadth first.

Separate cause and effect (MobX reaction)

A repeater can mix cause and effect, and there are some preventive measures in place to prevent a repeater from activating itself. So, in many cases it is safe to use a repeater with a single action (comparable to autorun in MobX). However, there could be situations where you would like to distinguish more between cause and effect. To do that, a second argument for the repeater is an action that is not recorded.

repeat([description for debug], recordedAction, nonRecordedAction, options)

The options object contains further configurations for the repeater (time, independent, ...).

The return value of the recorded action will be sent as an argument to the nonRecorded action, similar to how reaction works in MobX.

invalidateOnChange

There is a famous quote from programmer Jeff Atwood (author of blog Coding Horrors):

There are two hard things in computer science: cache invalidation, naming things, and off-by-one errors.

Well, at least cache invalidation just got much more simple thanks to invalidateOnChange. While you could use a repeat to invalidate a cache, invalidateOnChange is a more basic primitive that is optimized for this specific purpose. For example, if a cache is filled up using lazy evaluation, it is convenient to just clear the cache without any direct reevaluation.

Here is an example:

invalidateOnChange(
    function() {
        x.value = y.value + z.value; xIsValid = true;
    },
    function() {
        xIsValid = false;
    }
);

In this case, if y.value is changed for instance, it will only mean that the second function is run, setting xIsValid to false. invalidateOnChange is in particularly useful when integrating causality with other frameworks. For example, rendering code could be run using invalidateOnChange, and the second function could simply invalidate a certain view-component.

withoutRecording and withoutReactions

When working with causality it could be useful to sometimes break the rules. Reading data without creating a dependency could for example be useful for debug printouts. In the following code, the debug printout itself would create a false dependency on z.value if it wasnt for the withoutRecording clause.

let x = null
let y = observable({ value: false });
let z = observable({ value: 10});

repeat(function() {
    withoutRecording(function() {
        console.log("Repeating with these values:");
        console.log(y.value);
        console.log(z.value);
    })
    if (y.value) {
        x = z.value;
    } else {
        x = 42;
    }
}

// No one is going to notice!
withoutReactions(function() {
    y.value = true;
});

There are probably less use cases for being able to change data without triggering any reactions, using withoutReactions, it is available nevertheless.

Emiting Events

Somtimes you just need to observe objects, and record events. For this purpose you can set an onEventGlobal callback in the causality configuration. This callback can then monitor any change that happens in the world from a birds eye perspective.

Activating sendEventsToObjects will also cause causality to try to send events directly to your observable objects. If your objects implement the following callbacks, causality will then try to call them.

  • onChange
  • onEstablish
  • onDispose

onChange will receive a message, containing information about any change that happened to the object - including, when doing data structure rebuilding, events of type create, reCreate and dispose.

onEstablish and onDispose are called on an object built in a rebuild when it is first established, and when a rebuild no longer creates it. They correspond to the React concepts of componentDidMount and componentWillUnmount, but for a generalized data structure rebuilding framework.

causality.rebuildTwin

The re building mechanism internally uses a forwarding mechanism that can be used directly. To use it, simply type myObject.causality.rebuildTwin = otherObject. This will cause myObject have the identity of myObject, but the state of otherObject. Quite useful for some very special scenarios. Note that the forwarding disregards the meta data of the object that is reached by myObject.causality. As a consequence of this, object.causality.id cannot be relied upon when forwarding is used, such as in a rebuilding scenario. Also, forwarding takes place before any onReadGlobal and onWriteGlobal event can be fired, those events will be fired, but for the object that the object was forwarding to.

transaction

It is possible to do many changes at once, before causality has a chance to responde to any of the changes. This can be done by the use of "transaction". Here is an example:

transaction(function() {
   x.a = 12;
   x.b = 30;
})

Warning: A transaction should typically only write data, as reading data inside a transaction might result in reading non-updated data since all repeaters are frozen inside the transaction.

Comparison to MobX

Causality is comparable to MobX.

Early, non-conclusive experiments indicated that causality, the library this one grew from, could be almost twice as fast as MobX; the temporal signals added since have not been benchmarked. Causality also takes full advantage of ES6/proxies which makes it comparable to MobX version 5 and above.

Causality also offers some experimental features that can not be found in MobX, such as data structure rebuilding using rebuild keys. On the other hand, MobX is a more mature library with a large supporting community and better integration with other libraries.

Context

Causality is to be a foundation to an isomorphic application framework called "liquefy" that will feature full stack data binding and reactive programming (https://github.com/erobwen/liquid). It is useful to anyone that would like to do reactive programming in Javascript (https://en.wikipedia.org/wiki/Reactive_programming).