Installation

Install JetSet via NPM; it's about 0.6k with no dependencies, so you shouldn't have to wait long.

npm install --save jet-set

Then, import it normally.

import jetSet from 'jet-set';

Usage

First, create a new JetSet object.

let superObj = jetSet();

When you create your JetSet you can set some default values, too.

let duperObj = jetSet({
    apples: 5,
    oranges: 3,
    bananas: 6
});

That's it! superObj and duperObj now behave as normal JavaScript objects, with a few major enhancements.

Setting Values

Think of JetSets as enhanced JavaScript objects. You set values on them just as you would on a vanilla object.

let superObj = jetSet();

superObj.someProp = 'Some value';
superObj.anotherProp = true;

let prop = 'yetAnotherProp';
superObj[prop] = 12;

Setting Dynamic Values

A JetSet object lets you define dynamic or derived properties, too. On the getting-side, these will behave as static values (more on this in the Getting Dynamic Properties section).

superObj.apples = 5;
superObj.oranges = 3;
superObj.totalFruit = () => superObj.apples + superObj.oranges;

Getting Values

You retrieve values just as you would with normal JS objects.

let superObj = jetSet({
    someBool: true,
    someStr: 'Hello, world!'
});

if (superObj.someBool) {
    console.log(superObj.someStr); // Hello, world!
}

Getting Dynamic Properties

Access dynamic properties as if they were static.

superObj.apples = 5;
superObj.oranges = 3;
superObj.totalFruit = () => superObj.apples + superObj.oranges

// outputs 8
console.log(superObj.totalFruit);

// throws an exception, as .totalFruit is not a function
console.log(superObj.totalFruit());

Getting a dynamic property returns the result of an assigned function, not the function signature. This makes JetSet great for holding state; derived values can be treated as static, stateful properties. This means they can be observed, as well.

Getting Values via the Usual Object Suspects

Given that JetSets get and set like objects, you can do all this stuff, too. (Assume we've created a JetSet and named it superObj.)

let {someProp, anotherProp, yetAnotherProp} = superObj;
console.log(someProp, anotherProp, yetAnotherProp);

let keys = Object.keys(superObj);
keys.map((key) => console.log(superObj[key]));

for (let prop in superObj) {
    if (superObj.hasOwnProperty(prop)) {
        console.log(superObj[prop]);
    }
}

Watching Values

You can attach handlers to any property in a JetSet object - be it static or dynamic - to execute when a value changes.

Do this via .onChange(prop, changeHandler).

let superObj = jetSet({
    apples: 5,
    oranges: 3,
    bananas: 2,
    totalFruit: () => superObj.apples + superObj.oranges + superObj.bananas
});

superObj.onChange('apples', (newVal, oldVal) => {
    console.log(`Apples was ${oldVal} and is now ${newVal}`);
});

superObj.apples = 10; // console out, "Apples was 5 and is now 10"

// no change, so no output
superObj.apples = 10;

Handlers/callbacks attached to changes via .onChange() receive the updated value and the old value as params, respectively. If the assigned value does not change, the handler is not executed.

Change handlers stack, so you can attach more than one to the same property.

...

superObj.onChange('oranges', () => console.log('Oranges'));
superObj.onChange('oranges', () => console.log('... just updated!'));

superObj.oranges = 2;
// console out, "Oranges"
// console out, "... just updated!"

Watching Dynamic Properties

Groovier still, this works on dynamic/derived properties. Continuing the above code block:

...

superObj.onChange('totalFruit', (totalFruit) => {
    console.log(`Total fruit: ${totalFruit}`);
});

superObj.bananas = 5; // console out, "Total fruit: 17"

Unwatching Values

As handlers are attached to changes via .onChange(), we can logically remove them via .offChange().

let superObj = jetSet({
    apples: 3,
    oranges: 2
});

let handler1 = () => console.log('Called!');
let handler2 = () => console.log('Also called!');

superObj.onChange('apples', handler1);
superObj.onChange('apples', handler2);

superObj.apples = 4;
// console out, "Called!"
// console out, "Also called!"

superObj.offChange('apples', handler2);

superObj.apples = 1;
// console out, "Called!"

Change handlers are matched by reference; you can attach anonymous functions easily enough via .onChange() but that can make them painful to remove. Using named functions is recommended if you're going to attach or detach them regularly.

About JetSet

This library aims to be three things: simple, tiny, and stateful.

Simple

Interacting with a JetSet object should feel familiar, mirroring how you set and get properties on a vanilla JavaScript object. The .onChange() and .offChange() methods (on JetSet's prototype) are the only things you need to remember.

To keep it simple (and small), JetSet doesn't provide many conveniences ... like a .set({ ... }) method that you see on many state objects/models. This was by design. It's intended to be a micro-library with a light API for use as a component in larger projects, not a full-featured library to cover all use cases.

Since it behaves so similarly to a JS object, however, creating your own wrappers or facades to automate tasks should be straight-forward.

Tiny

I wrote JetSet's ugly, quick, and dirty progenitor for the JS13K Games competition in 2017. JS13K is a friendly competition where participants create an HTML/JS/CSS game in under 13k, so every byte counts.

JetSet retains its roots and tries to be as small a package as possible for the functionality it provides.

Currently, it's < 500 bytes (compressed, of course) and striving to shrink further.

Stateful

You can use a JetSet object for a number of applications, though its primary purpose is for creating stateful objects. That's why even dynamic properties are treated as static - a JetSet represents "right now" even if properties are derived from other values.

"It depends" is not stateful. A function that returns X + Y is not stateful; the result of that function is. JetSets try to emulate a static snapshot of state even if the under-the-hood workings are dynamic.

Issues/Bugs

Issues, bugs, and feature requests are tracked on GitHub in full public view.