stateworks

3.1.1 • Public • Published

Node Stateworks

Stateworks is a library to compose distinct objects into a single stateful.

Stateworks leverages Proxy objects to conduct its magic. Accordingly, the constructor does not return a Stateful object, but a Proxy around a StatefulCore object. The resulting object is more or less a composition of a total of three objects: The StatefulCore (which is also an event emitter), the actual state, and the common properties object.

Update v3

Stateworks has been slightly redesigned to use closures instead. Its use is now much like Promises. The enter method has effectively been removed from the Stateful object. The stateful function no longer takes the initial state as single argument, but an initializer function. See below for details.

Simple Example

const stateful = require('stateworks');

const mystateful = stateful((proxy, common, enter, active) => {
    const state1 = {
        foo() {
            active() === state1; // currently active state, non-proxy. accessing this value circumvents common props
            enter(state2);
            return 'state1.foo';
        }
    };
    const state2 = {
        foo() {
            enter(state3);
            return 'state2.foo';
        }
    };
    const state3 = {
        foo() {
            enter({}); // enter empty/terminal state - foo is no longer defined here
            return 'state3.foo';
        }
    };
    return state1;
});

console.log(mystateful.foo(), mystateful.foo(), mystateful.foo());
// output: state1.foo  state2.foo  state3.foo
mystateful.foo(); // throws because 'undefined' is not callable

2-Layer System

The Stateful object is composed of two objects: the active state object and the common properties. When transitioning with enter the active state changes, and thus dynamically the exposed properties along with it.

When getting properties, the common properties take precedence. If the property does not exist on the common properties object, it is read from the active state object. If it doesn't exist there either, undefined is returned.

Setting properties follows a very similar logic: if the property exists on the common properties object, its property is overridden; otherwise, the active state's property is overridden.

Any method on either the common properties or the active state are bound to your Stateful object.

Note that due to the dynamic nature of these stateful objects, it is impossible to standardize a stateful object's interface for TypeScript as a different state may expose entirely different properties. Stateworks is too generic for TypeScript's typing system, though you may define interfaces to describe the current state of the stateful.

Common Properties

Common properties persist across state transitions. When assigning a value to a Stateful's property, if this property is common, it will be stored in the common object passed to the initializer. Otherwise, it will be stored in the active state. Thus, when transitioning into another state, this property will be rerouted by the Stateful proxy.

Example

const stateful = require('stateworks');

const mystateful = stateful((proxy, common, enter) => {
    Object.assign(common, {
        foo: 'bar',
        answer: 42,
    })
    
    const state1 = {
        state: 1,
        ask() {
            console.log(this.foo === 'bar'); // true
            const answer = this.answer += 1;
            enter(state2); // Note that this call already changes the active state
            return answer;
        }
    };
    const state2 = {
        state: 2,
        ask() {
            console.log(this.foo === 'bar'); // true
            const answer = this.answer += 2
            enter(state3);
            return answer;
        }
    };
    const state3 = {
        state: 3,
        ask() {
            console.log(this.foo === 'bar'); // true
            return this.answer += 1;
        }
    }
    
    return state1;
});

console.log(mystateful.answer, mystateful.state); // 42 1
console.log(mystateful.ask(), mystateful.state);  // 43 2
console.log(mystateful.ask(), mystateful.state);  // 45 3
console.log(mystateful.ask(), mystateful.state);  // 46 3

Callbacks

If your active state implements onStateEnter and onStateLeave methods, they will be called accordingly upon transitioning states. this will be bound to the Stateful proxy object.

Both callbacks will receive oldState, newState arguments in this order.

Example

const stateful = require('stateworks');

const mystateful = stateful((proxy, common, enter) => {
    Object.assign(common, {
        state: undefined,
    });
    
    const state1 = {
        onEnterState(oldState, newState) {
            newState === state1; // true
            this.state = 1;
        },
        onLeaveState(oldState, newState) {
            oldState === state1; // true
            newState === state2; // true
            console.log('bye bye state 1');
        },
        next() {
            enter(state2);
        }
    };
    const state2 = {
        onEnterState() {
            this.state = 2;
        },
        next() {
            enter(state1);
        }
    };
    
    return state1;
});

console.log(mystateful.state); // 1
mystateful.next();
console.log(mystateful.state); // 2
mystateful.next();
console.log(mystateful.state); // 1

Note that the state returned from the initializer will also trigger onEnterState.

Readme

Keywords

Package Sidebar

Install

npm i stateworks

Weekly Downloads

1

Version

3.1.1

License

MIT

Unpacked Size

12.4 kB

Total Files

5

Last publish

Collaborators

  • kiruse