4.0

These are the constructs that Cerebral consists of:

import { Controller, Module, Provider, Compute, CerebralError } from 'cerebral'

Module 

The module is how you structure your application.

const app = Module({
  state: {},
  signals: {},
  providers: {},
  modules: {},
  catch: []
})

// Or callback
const app = Module(({ name, path, controller }) => ({
  state: {}
}))
  1. Providers are now an object instead of an array. This makes more sense as providers needs a name and we do not need this provide factory:
const app = Module({
  state: {},
  providers: {
    http: HttpProvider({})
  }
})
  1. The only way to catch an error now is at the module level. Errors propagate up to parent modules if not caught. Meaning there is no signal specific error catching or “global”. “Global” would be your root level module.
const app = Module({
  state: {},
  catch: [[HttpProviderError, sequences.catchHttpError]]
})

Documentation

Controller 

The controller takes a module as first argument, and other options as a second argument.

const controller = Controller(rootModule, {
  devtools: null
})

The same goes for server side controller.

Documentation

Provider 

Providers will now have a stronger concept. The concept is being the API between your application and your tools of choice. It can only be defined in one way, an object with methods. This makes things consistent and also enforces the idea that you think of a provider as a “bridge” between your app and the tools your are using.

const myProvider = Provider({
  foo() {}
})

You can point to the existing context using this.context. It works this way to simplify with just one API surface and we can do prototypal optimizations under the hood.

const api = Provider({
  getUser() {
    return this.context.http.get('/user')
  }
})

All providers defined this way is automatically optimized and wrapped for debugger purposes. If you just want to use a 3rd party tool directly, you can still do that by just attaching it:

Module({
  providers: { uuid }
})

Documentation

Compute 

This is just a change to be consistent with the other APIs:

const myComputed = Compute(state`foo`, (foo) => ())

Documentation

CerebralError 

To more easily create different error types you can now define your errors by extending the CerebralError:

import { CerebralError } from 'cerebral'

export class AppError extends CerebralError {}
export class ApiError extends CerebralError {}

Documentation

Module state changes 

You can point to the module to make state changes on module executing the signal:

function myAction({ module }) {
  module.set('foo', 'bar')
}

You can now use a module tag to point to the state of the module running the signal:

;[set(module`foo`, 'bar')]

Documentation tags Documentation state

Testing 

The CerebralTest API now takes a Module as first argument.

import { CerebralTest } from 'cerebral/test'
import app from './src/app' // A Module

CerebralTest(app)

Routing 

Since you now use a root Module, the router will need to be added there.

import { Controller, Module } from 'cerebral'
import router from './router' // Instance of the router module

const rootModule = Module({
  modules: {
    router
  }
})

const controller = Controller(rootModule)

Migration 

  1. Change controller configuration to take in a top level module
  2. Wrap all modules in Module constructor
  3. Change configuration of providers to use object
  4. Change any custom providers to use the Provider factory
  5. Rename use of compute to Compute
  6. Any error catching now needs to be at module level without new Map(...)
  7. Recommended to extend any errors that can be caught from CerebralError, as you get serialization out of the box
  8. Change out any CerebralTest argument with a Module