UNPKG

stateworks

Version:
150 lines (124 loc) 5.52 kB
# 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 `Promise`s. 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 ```javascript 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** ```javascript 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** ```javascript 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`.