Nighttime Possum Meandering
    Have ideas to improve npm?Join in the discussion! »

    raid

    6.1.0 • Public • Published

    Raid

    De/Centralised state container

    npm License Build Status Coverage Status js-standard-style

    Documentation

    Getting Started

    Install with yarn or npm

    yarn add raid
    npm i -S raid

    Raid manages the state layer by providing an observable that supplies the current state of the application.

    import { Signal } from 'raid'
     
    const signal = Signal.of({
      count: 0
    })
     
    signal.observe(state => {
      // Current signal state, typically a side effect
      console.log(state)
    })

    State is held within the signal and changes can be triggered by emitting action objects and using update functions to mutate the state.

    import { Signal } from 'raid'
     
    const signal = Signal.of({
      count: 0
    })
     
    // Update function
    signal.register(state => {
      // Mutations occur here
      // Up to you if you want to mutate or return new objects
      state.count++
      return state
    })
     
    // Observable
    signal.observe(state => {
      // Current signal state
      console.log(state)
    })
     
    // Emit action
    signal.emit({
      type: 'ADD'
    })

    Raid works great with immutable state objects to ensure that all mutations occur within the update functions, although this is not enforced, it’ll work great with regular javascript structures too.

    Further reading exists in the documentation.

    Additionally there are a number of examples and test cases.

    See the changelog for details regarding changes between major versions. Raid adheres to semantic versionin and strives to keep breaking changes to a minimum and provide upgrade instructions (or codemods) where necessary.

    Applying updates

    Update functions are applied to the signal using the register method, which accepts an update function.

    signal.register((state, event) => {
      return state
    })

    An update function is always of the form:

    (state: Object<any>, event: EventObject) => state: Object<any>
    
    // EventObject
    {
      type: String,
      payload: Object<any>
    }
    

    Note that whilst type is assigned as a string here, which is usual, it needn't be and any type would work. See the actions example for a differing implementation.

    Update functions must always return the new (or mutated) state object, or the next update function in the chain (or, if the last or only, the observers will get it) will receive a void state object.

    See safe from @raid/addons for a higher order function to ensure state is returned from updates.

    signal.register can additionally take an options object, which is currently only used to assign a key to the update function, and returns a function which can be used to remove (dispose) the update function from the signal:

    // signal.register
    (update: Function(<state>, <event>), options: Object<RegisterOptions>) => Function(<void>)
    
    // RegisterOptions
    {
      key: String
    }
    

    Note that whilst the type of key is assigned as a string here, which is usual, anything would work so long as it can be used a key for map.

    Further reading exists in the documentation.

    Attaching observers

    Observers are usually where your side effects will live (although nothing in Raid mandates this) and receive the current state object moving through the signal when attached and on every emit through the stream.

    Note that pre-version 6 signal observers attached after the signal is created would not receive an immediate execution. This change is to allow a reactive model where observer side effects can run immediately. As observers typically perform updates elsewhere in the system (a GUI or TUI, for example), this is usually what you want and avoids potentially costly re-renders to work around.

    signal.observe(state => {
      console.log(state)
    })

    Observer functions should always take the form:

    (state: Object<any>) => void
    

    The signal.observe function itself accepts a next and an error observer (which will fire if an error is detected) and an options object:

    // signal.observe
    (next: Function<state>, error: Function<Error>, options: Object<ObserveOptions>) => void
    
    // ObserveOptions
    {
      key: String,
      subscription: {
        next: Function<state>,
        error: Function<error>,
        complete: Function<state>
      }
    }
    

    Note that key is declared as a String here, which is usual, but anything that can be used as a key within a map would work.

    If a subscription object is supplied as an option then it will take precedence over next and/or error parameters and be used as outputs of the stream. The complete function is mentioned above for completeness, Raid signals typically never complete as they are the stream form of event emitters.

    signal.subscribe exists as an alias to signal.observe.

    Managing the signal lifecycle

    Signals have a clean and minimal API and each function that creates resources will return a function to remove them, i.e.

    const dispose = signal.register(updateFn)
    const detach = signal.observe(observeFn)

    Signals will keep track of updates and observers and provides methods to clean up when (if?) you want to destroy a signal:

    signal.detachAll()
    signal.disposeAll()

    Using keys to keep track of resources

    Raid will manage resources for you and provide functions when you do want to perform clean-up, however, if you’d prefer to supply a key then you can:

    signal.register(fn, {
      key: 'uid for an update function'
    })
     
    signal.observe(fn, {
      key: 'uid for an observer'
    })

    Both register and observe/subscribe will return functions to clean up, but you can use the key to remove them:

    // Dispose is to register
    signal.dispose('uid for an update function')
     
    // Detach is to observe
    signal.detach('uid for an update function')

    Applying functions to updates

    Raid Signals can additionally keep a stack of functions to apply to every update passing through the stream. They are applied lazily so can be added and removed and will execute for every update on every emit.

    signal.apply(fn => (state, event) => fn(state, event))

    Applicator functions are higher-order functions that accept an update function to decorate.

    An example is using safe from @raid/addons to ensure that updates within the signal always return a state.

    import { safe } from '@raid/addons'
     
    signal.apply(safe)

    Mounting other streams

    It is often very useful to create streams which emit action objects and then mount those streams on to a Signal.

    import { actions, keyStream } from '@raid/streams'
     
    const signal = Signal.of()
    signal.update((state, event) => {
      if (event.type === actions.keydown) {
        // Respond to the keydown event here
      }
      return state
    })
     
    const subscription = signal.mount(keyStream())

    Mount attempts to use the subscribe method of the passed-in stream and will pass the return value back. For streams which implement the ES Observable proposal the returned value will be a Subscription object which can be used to unmount the stream.

    subscription.unsubscibe()

    Mount can also mount another Signal, whereby the source Signal will receive any events that pass through the mounted signal (but not its state). When mounting a Signal the return value will be a function that can be invoked to unmount.

    const signal = Signal.of({})
    const mounted = Signal.of({})
     
    const unmount = signal.mount(mounted)
    mounted.emit({ type: 'action', payload: 'I ❤️ Raid'})
     
    unmount()

    Running tests

    yarn
    yarn test

    Contributing

    Pull requests are always welcome, the project uses the standard code style. Please run yarn test to ensure all tests are passing and add tests for any new features or updates.

    For bugs and feature requests, please create an issue.

    See the root readme for more information about how the repository is structured.

    License

    MIT

    Install

    npm i raid

    DownloadsWeekly Downloads

    13

    Version

    6.1.0

    License

    MIT

    Unpacked Size

    166 kB

    Total Files

    24

    Last publish

    Collaborators

    • avatar