@wildpeaks/actions-worker

ActionsWorker class

Usage no npm install needed!

<script type="module">
  import wildpeaksActionsWorker from 'https://cdn.skypack.dev/@wildpeaks/actions-worker';
</script>

README

ActionsWorker

Build Status

Typescript class to store an immutable state that can be edited using actions, and emits JSON props on state change.

The EntryLoader uses it to generate a Web Worker automatically as part of the JSON Entries system for Webpack, but this package can also be used on its own, even without a Web Worker.

Install:

npm install @wildpeaks/actions-worker

Example:

import {ActionsWorker, IDispatcher} from '@wildpeaks/actions-worker';


// The immutable state could be a simple frozen object, class instance, etc.
// It's up to you.
type State = {
    readonly count: number;
};


// Props must be a JSON-compatible frozen object.
// This way, it could be forwarded from a Web Worker to the main thread
// for rendering with React or Preact, for example.
// Technically it doesn't have to be frozen, but it is assumed to be immutable.
type Props = {
    readonly text: string;
};


// String values are easier to debug.
enum Actions {
    ADD = 'add',
    SUBTRACT = 'subtract'
}

// But you can use a classic integer enum as well.
// enum Actions {
// 	ADD,
// 	SUBTRACT
// }


// Each action has a matching data message.
// The only requirement is property `action`.
type AddMessage = {
    action: Actions.ADD;
    delta: number;
};
type SubtractMessage = {
    action: Actions.SUBTRACT;
    delta: number;
};
type Messages = AddMessage | SubtractMessage;
type Dispatcher = IDispatcher<State, Messages>;


// Actions are simple functions with two arguments:
// - the data message
// - a reference to read/write the new state & scheduled additional actions.
//
// Note that package `@wildpeaks/frozen` makes it simpler to manipulate
// frozen objects, if the following is too verbose.
function add(message: AddMessage, dispatcher: Dispatcher): void {
    const oldState: State = dispatcher.state;
    const newState: State = {
        count: oldState.count + message.delta
    };
    dispatcher.state = Object.freeze(newState);

    // Actions are only allowed ONE immediate change to the state.
    // It must schedule any delayed or additional changes using `dispatcher.schedule`.
}

function subtract(message: SubtractMessage, dispatcher: Dispatcher): void {
    const oldState: State = dispatcher.state;
    const newState: State = {
        count: oldState.count - message.delta
    };
    dispatcher.state = Object.freeze(newState);
}


// Subclass the ActionsWorker class to specify the list of actions
// and the way to extract render Props from State (using `serialize`).
class Storage extends ActionsWorker<Props, State, Messages> {
    constructor() {
        super();
        this.actions[Actions.ADD] = add;
        this.actions[Actions.SUBTRACT] = subtract;
    }
    protected serialize(state: State): Props {
        const props: Props = {
            text: `Count is ${state.count}`
        };
        Object.freeze(props);
        return props;
    }
}


const store = new Storage();

// Receives the results of serialize(state),
// ready to be rendered by React or Preact for example.
store.onprops = props => {
    console.log(props);
};

// Initial state
store.state = {
    count: 0
};

// Trigger actions by pushing to the queue.
// The dispatcher that actions receive also has the `schedule` method.
// Messages are JSON-compatible, so the storage could be in a WebWorker
// while the messages are sent from the main thread.
// They could also be recorded for playback.
store.schedule({
    action: Actions.ADD,
    delta: 1
});
store.schedule({
    action: Actions.ADD,
    delta: 10
});
store.schedule({
    action: Actions.SUBTRACT,
    delta: 200
});