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 🙏

© 2024 – Pkg Stats / Ryan Hefner

rp

v0.2.0

Published

value containers + computed values + automatic dependency tracking

Downloads

518

Readme

rp (ReactiveProperty)

rp = value containers + computed values + automatic dependency tracking

  • works well with objects and arrays
  • including common computations

Basics

var rp = require("rp");

// Creating RPs
var x = rp.variable(1); // read/writable
var y = rp.const(2); // only readable

// Reading values
x() === 1
y() === 2
y.get() === 2 // y() is a shortcut for y.get()

// Computed RPs
var xy = rp.computed(function() {
	return x() + y(); // Dependencies
});

xy() === 3

// Writing values
x.set(5);
x() === 5

// Computed RPs are updated
xy() === 6

Reading operations

Shortcuts for common computations. They all return a RP so they are chainable.

var x = rp.variable(Math.PI);
var y = rp.variable(123);

x.computed(function(x) {
	return x * x + 2
}); // => 11.869...

x.inversed() // => false

x.asBool() // => true

x.asNumber() // => 3.14...

x.asString() // => "3.14..."

x.rounded() // => 3
x.floored() // => 3
x.ceiled() // => 4

x.plus(y) // => 126.14...
y.minus(x) // => 119.86...
y.dividedBy(x) // => 39.15...
x.multipliedBy(y) // => 386.41...
x.equals(y) // false
x.comparedTo(y) // -1
x.comparedTo(y, function(a,b) {...}) // -1
rp.variable("test123TEST").split(y.asString()) // => ["test", "TEST"]
rp.variable("[1,2]").parsed() // => [1,2]
rp.variable([1,2]).stringified() // => "[1,2]"
// Array operations
var z = rp.variable([1,2,3,4]);
var a = rp.variable(1);

z.map(function(item) {
	return item * (a() + 1);
}) // => [2, 4, 6, 8]

z.filter(function(item) {
	return item % 2 === a();
}) // => [1, 3]

z.reduce(function(a, b) {
	return a + b;
}) // => 10

z.slice(a, 2) // => [2, 3]

z.indexOf(a) // => 0

z.size() // => 4

Writing operations

var x = rp.variable(1);
var y = rp.variable(2);
var z = rp.variable([1,2,3]);

x.increment(); // x = x + 1
x.decrement(); // x = x - 1
x.add(3); // x = x + 3;
x.subtract(6); // x = x - 6;
x.multiply(10); // x = x * 10;
x.divide(y()); // x = x / y;

z.push(y());
z.unshift(y());
z.shift(); // => 2
z.pop(); // => 2
z.splice(1, 2, "test");
z(2).remove();

Helpers

var z = rp.variable([1,2,3]);

z.forEach(function(item, idx) {
	console.log("at " + idx + " is " + item());
});

z.log("z"); // logs all changes with the name "z"

z.readonly(); // readonly version of anything

z.fixed(); // cannot be updated, but elements and properties can be changed

z.fixedArray(); // cannot be updated, neither can elements be added or removed
				// But values can be changed

z.isConst() === false
rp.const([1,2,3]).map(function(i) { return i * i; }).isConst() === true
	// Returns true is the value is constant

Objects and Arrays

var o = rp.variable({a: {b: [{c: 1}, {d: 2}]}});

// Getting RP of properties and elements
var a = o("a");
var b = o("a", "b");
var c = a("b", 0, "c");
var d = o("a", "b", 1, "d");
var p = o("p", "p", "p");

c() === 1
d() === 2

// not existing properties are undefined
p() === undefined

// RP move with array modifications
b.shift();

//  d is now o("a", "b", 0, "d")
d() === 2

// removed elements become undefined
c() === undefined

// you get the same instance for a property or element
o("a", "b", 0, "d") === d

Memory management

// Every RP is only valid until the next tick
var x = rp.variable(1);
x() // fine
process.nextTick(function() {
	x() // :(
});

// To make them longer valid put them into a scope
var x = rp.variable(2).scope(); // into the global scope

// or into a private scope
var s = rp.scope(function() {
	var x = rp.variable(4).scope();
});

// continue to run code in this scope
s.run(function() {
	var y = rp.variable(6).scope();
	// nest scopes
	rp.scope(function() {
		var zz = rp.variable(8);
		var z = zz.plus(2).multipliedBy(7);

		// scope computed RPs,
		//  dependencies will stay too
		z.scope();
	});
});
s.scope(function() {
	var a = rp.const(1).plus(1).scope();
});

// Scope[ x, y, Scope[ z ], Scope[ a ] ]

// unref the scope anytime you want
// nested scopes are also removed
s.unref();

// Computed forms a scope too
//  but it's not part of the parent scope
// It's handled by the normal RP lifetime
var x = rp.computed(function() {
	var y = rp.variable().scope();
});
// x is valid until the next tick, so is y

Handled get

Read the value, but handle changes by some function.

rp.prototype.getHandled(changedHandler(value))

rp.prototype.getHandled(
	updatedHandler(value),
	addedHandler(idx, item),
	removedHandler(idx, item)
)

A handled get is active as long as the current scope is active.

Example: exchange the HTML code of a HTMLElement:

var x = rp.variable("Text");
var tag = rp.variable("div");
var html = x.computed(function(x) {
	return "<h1>" + x + "</h1>";
});
var element = rp.computed(function() {
	var element = document.createElement(tag());
	element.innerHTML = html.getHandled(function(html) {
		// Changes of html are handled here
		element.innerHTML = html;
	});
	return element;
});
var el1 = element();
x.set("New text");
element() === el1;
tag.set("pre");
element() !== el1;

Atomic

var x = rp.variable("test");
var counter = 0;
var y = x.computed(function(x) {
	return x + (++counter);
});
y() === "test1"

// atomic delay updates to the end of the block
// can boost performance for many changes
rp.atomic(function() {
	x.set("a");
	x.set("b");
	x.set("c");
});
y() === "c2"

Delegated

var a = rp.variable(1);
var b = rp.variable(2);
var which = rp.variable(false);

// while computed RPs provide only read access
var y = which.computed(function(which) {
	return which ? a() : b()
});

// delegated RPs can provide read/write access
//  to a dynamically choosen RP
var ref = which.computed(function(which) {
	return which ? a : b
});
var x = rp.delegated(ref);
// or: var x = ref.delegated();
// or: var x = rp.delegated(function() { return which() ? a : b; });

x() === 2
x.set(4);
b() === 4
which.set(true);
x() === 1
s.set(9);
a() === 9

Two-way computions

var x = rp.variable(1);
var y = x.computed(
	function(x) { return x * 2; },
	function(y) { return y / 2; }
);
y() === 2
y.set(5);
x() === 2.5
x.set(2);
y() === 4

Two-way operations

var x = rp.variable(123);
var str1 = x.asIntString();
var str2 = x.asFloatString();
str1() === "123"
str1.set("456");
x() === 456
x.set(789);
str1() === "789"
str2() === "789"
str2.set("1.23");
x() === 1.23

Attributes

Every RP can have attributes. One can use them to exchange user data. Each attribute is a rp.variable.

var x = rp.variable({ a: 1 });
function validate() {
	var a = x("a");
	a.attr("valid").set(a() % 2 === 0);
}
validate();
var aValid = x("a").attr("valid");
aValid() === false;
x("a").set(2);
aValid() === true;
x("a").hasAttr("valid") === true;
x.hasAttr("valid") === false;

Internals

All works with 5 events:

  • changed: The value changed
  • updated: The primitive value changed or the reference changed
  • added: An item is added to the array
  • removed: An item is removed from the array
  • nested: A event occured on nested stuff
var a = rp.variable(/test/);
var b = rp.variable([1,2]);
var c = rp.variable({a: 1});
var d = rp.variable({a: {b: [1]}});

a.set(123);
// a updated(123, /test/)
// a changed()

a.set(123);
// no events

b.push(3);
// b added(2/*idx*/, 3/*value*/)
// b changed()

b.set([]);
// b updated([], [1,2,3])
// b changed

c("a").set(2);
// c nested("updated", ["a"], 2, 1);
// c changed

c.set({a: 2});
// c updated({a: 2}, {a: 2});
// c changed

d("a", "b").push(2);
// c nested("added", ["a", "b"], 2);
// d changed

d("a", "b", 1).set(3);
// c nested("updated", ["a", "b", 1], 3);
// d changed

The nested event is not used internally, can be used to capture all updates on objects and arrays.

Computed values listen on "changed", "updated", "added" and "removed" and update their values.

By design they could do this very clever:

var x = rp.variable([1,2,3,4]);
var y = x.filter(function(item) {
	return item % 2 === 0
}).map(function(item) {
	return item * item
});

// y() is now [4, 16]

x.push(5);
// no events for y

x.push(6)
// y added(2, 36)

But it's work on progress for some function. (They currently update the whole array, where a bunch of "added" or "removed" events would do it better.)

// You can listen on the events
x.onceChanged(function() {});
x.onChanged(function() {});
x.onUpdated(function() {});
x.onAdded(function() {});
x.onRemoved(function() {});
x.onceDisposed(function() {});

x.removeOnceChangedListener(fn);
x.removeChangedListener(fn);
// ...
x.removeDisposedListener(fn);

Internally each RP uses reference counting to know when to dispose itself. At creation they get one reference that is removed at nextTick. Disposing means to remove event listener from RPs it depend on.

With x.ref() and x.unref() you can add/remove references, but there is no need to do this if you don't want to write your own RP. Better use x.scope() and let the scope manage your RPs.

TODO

Test status

Build Status

License

MIT