1.0.0 • Public • Published

sublet Build Status

Reactive leases for data subscriptions

With sublet you can directly attach functions to the data they care about.

You marry a function with its relevant data. They become bound to one another, a "reactive" pair.
When the data changes, the function automatically gets triggered, receiving the latest snapshot of that data.

Their "reactive marriage" is also exposed to the outside world in the form of a proxied data object.
This allows external reads and writes into data object. Because the function is bound, it will still always be triggered, regardless of who/what caused those updates!

Whenever the binding updates, the original data source receives the updates, too.
This means that when new reactive pairs source their data from other bindings (or pieces of them), any updates to the "child" binding propagate to the "parent" binding. In turn, both of their partner functions are triggered.

So, sublet sort of behaves like a store, but its dispatchers are normal variable assignments!
Instead, sublet allows you to get back to JavaScript's basic building blocks while keeping your sanity.


There are two "versions" of sublet, accommodating different browser support targets:


Size (gzip): 194 bytes
Availability: UMD, CommonJS, ES Module
Requires: Proxy

The more modern build of sublet as it relies on the Proxy class.
Because of this, the "modern" version of sublet can work with Objects and Arrays, neither of which require the input to have a predefined shape. This means that your Objects can receive new keys at any point, or your Arrays can mutate in any which way, and your callback subscriber will always be made aware of those changes.


Size (gzip): 263 bytes
Availability: UMD, ES Module
Requires: Array.isArray, Object.defineProperty

The "legacy" version is bound by Object.defineProperty's limitations:

  1. the input must be an Object (no Arrays);
  2. the input Object must have its keys defined ahead of time, before sublet instantiation.

Unlike the Proxy-based solution, any new, dynamically added keys are unknown and uninitialized, which means they cannot and will never be able to trigger the callback when they show up or change.


$ npm install --save sublet

Also available on

<!-- Mode: "proxy" -->
<script src=""></script>

<!-- Mode: "legacy" -->
<script src=""></script>


// Mode: "proxy"
import sublet from 'sublet';
// Mode: "legacy"
import sublet from 'sublet/legacy';

const user = {}; // <~ "proxy" can be lazy
const user = { firstName:null, lastName:null }; // <~ "legacy" is explicit

const view = sublet(user, state => {
  if (state.firstName) {
    console.log(`Hello, ${state.firstName} ${state.lastName}`);
  } else {
    console.log('Howdy stranger~!');
//=> "Howdy stranger~!"

view.firstName = 'Nicolas';
view.lastName = 'Cage';
//=> "Hello, Nicolas Cage"


sublet(input, callback)

Returns: T<input>

A wrapped (aka, proxied) form of your input is returned.
This is the reactive interface and should be used to update state.
This interface is also passed to callback as its only argument.

Any updates through this T interface will propagate new value(s) to the original input, too.
Similarly, all read operations pull from the current input object. This means that any updates to input directly (outside of the T interface) will not trigger the callback subscription; however, those values will appear when read through T and/or in the next callback invocation.

Note: In "proxy" mode, the T is a Proxy instance and in "legacy" mode, it is an Object.
However, these are functionally equivalent since a Proxy can't be detected anwyay.


Type: Object

The original state data to observe.

Important: Modes operate differently!

Mode: "proxy"

  • Array types are permitted
  • When an Object, the keys do not need to be declared upfront

Mode: "legacy"

  • Array types are not permitted
    The original input is immediately returned, without callback attachment.
  • The Object must have predefined keys!
    Reactivity can only be established for known keys.
    You must define your default or empty state.


Type: Function

The callback to run whenever the paired reactive data updates.
This function receives the reactive data (T<input>) as its only argument.

The callback will run immediately when setting up a sublet instance.
This means that your callback should be capable of handling "setup" vs "update" usage. Alternatively, you can separate those actions and attach the "update" function as your sublet subscriber.

After initializing, the callback will only be queued to run if an updated value was not strictly equal to its previous value.
Similarly, any updates to the T<input> argument will enqueue a new update cycle.

Finally, the callback is debounced. This means that multiple T<input> updates will re-run the callback once.

import sublet from 'sublet';

let num = 0;
const view = sublet({}, () => console.log(`Render #${++num}`));
//=> Render #1 = 123;
// Render #2

await sleep(10); //~> 10ms later = 123;
// (no render, value identical)

await sleep(10); //~> 10ms later = 1; = 2;
view.baz = 3;
//=> Render #3

Browser Support

The "legacy" version works just about everywhere.
The "proxy" version works anywhere that was considered "modern" in the last 4 years.

Chrome Safari Firefox Edge Opera IE Node.js
"proxy" 49 10 18 12 36 6.0.0
"legacy" 5 5.1 4 12 11.6 9

Prior Art

There are 101 observable/reactive libraries and patterns out there – it would be impossible to name them all.

However, the reactivity of Svelte 3.x, specifically, inspired me.


MIT © Luke Edwards

Package Sidebar


npm i sublet

Weekly Downloads






Unpacked Size

10.9 kB

Total Files


Last publish


  • lukeed