-
Notifications
You must be signed in to change notification settings - Fork 3
Expand file tree
/
Copy pathdocs-text.js
More file actions
1 lines (1 loc) · 212 KB
/
docs-text.js
File metadata and controls
1 lines (1 loc) · 212 KB
1
{"introduction":{"index":{"raw":"# Get Started with Cerebral\n\nCerebral is a state management tool that helps you structure your application logic and debug with ease. Watch the introduction video to learn the core concepts:\n\n```marksy\n<Youtube url=\"https://www.youtube.com/embed/mYkM8CiVsXw\" />\n```\n\n## Installation\n\n### Prerequisites\n\n1. Install [Node.js](https://nodejs.org/en/) (version 10 or later)\n2. A JavaScript project set up with npm or yarn\n\n### Install Cerebral\n\nAdd Cerebral to your project:\n\n```bash\n# Using npm\nnpm install cerebral\n\n# Using yarn\nyarn add cerebral\n```\n\nYou also need to install a [view integration](docs/introduction/views.md).\n\n## Create Your First Cerebral App\n\nHere's a minimal example to get started with Cerebral and React:\n\n```js\n// index.js\nimport React from 'react'\nimport { createRoot } from 'react-dom/client'\nimport App from 'cerebral'\nimport { Container } from '@cerebral/react'\nimport Devtools from 'cerebral/devtools'\n\n// Define your main module with state and sequences\nconst main = {\n state: {\n count: 0\n },\n sequences: {\n increment: ({ store }) => store.set('count', store.get('count') + 1),\n decrement: ({ store }) => store.set('count', store.get('count') - 1)\n }\n}\n\n// Initialize the app with optional devtools\nconst app = App(main, {\n devtools:\n process.env.NODE_ENV === 'production'\n ? null\n : Devtools({ host: 'localhost:8585' })\n})\n\n// Create a simple counter component\nfunction Counter({ get, sequences }) {\n const count = get(state`count`)\n\n return (\n <div>\n <h1>Count: {count}</h1>\n <button onClick={sequences.increment}>+</button>\n <button onClick={sequences.decrement}>-</button>\n </div>\n )\n}\n\n// Connect the component to Cerebral\nconst ConnectedCounter = connect(Counter)\n\n// Render the app\nconst root = createRoot(document.getElementById('root'))\nroot.render(\n <Container app={app}>\n <ConnectedCounter />\n </Container>\n)\n```\n\n## Debugger Installation\n\nThe Cerebral Debugger is a standalone application that connects to your app and provides visualization of state, sequences, and component updates.\n\n### Download Options\n\nDownload the [Cerebral Debugger](https://github.com/cerebral/cerebral-debugger/releases) for your system:\n\n- **Mac**: cerebral-debugger-x.x.x.dmg\n- **Windows**: cerebral-debugger-setup-x.x.x.exe\n- **Linux**: cerebral-debugger_x.x.x_amd64.deb or snap package\n\nAfter installation, open the debugger application and create a new debugger instance. The debugger automatically notifies you of updates.\n\n```marksy\n<Image src=\"/images/debugger_connect.png\" style={{ width: 400 }} />\n```\n\n## Next Steps\n\n1. Check out the [tutorial](/docs/introduction/tutorial.html) for a more comprehensive introduction\n2. Learn how to [organize your application logic](/docs/introduction/organize.html)\n3. Explore more advanced concepts in the [in-depth guides](/docs/advanced/index.html)\n","title":"Get Started with Cerebral"},"architecture":{"raw":"# The architecture\n\n## Vision\n\nThe architecture of Cerebral is driven by the goal to give you insight. Creating a Cerebral application allows you and your team members to never question what actually happens when a page loads, a button is clicked, etc.\n\nThe Cerebral debugger is what gives you this insight and answers your questions on what state changes and side effects have been run in any given situation. It is a powerful tool that makes it easier to reason about the application and increases productivity in planning out logic and implementation.\n\nCerebral is based on the simple concept of three things your application does in order to function. **Store state**, **render state** and **update state**.\n\n## Store state\n\nWhere to store the state of an application is a highly debated subject. Should we allow the components to store state? Should we have multiple models? Stores? Reducers? Services? There are many concepts that can store state. In Cerebral you store all your state in \"a single state tree\". That means you do not create classes or other abstractions around state, it is all basically one big object of plain JavaScript types. Objects, arrays, strings, numbers and booleans:\n\n```js\n{\n auth: {\n isLoggedIn: false,\n user: {\n prefs: {\n style: 'light'\n },\n friends: [],\n info: {\n email: '',\n name: ''\n }\n }\n },\n posts: {\n list: [],\n selectedPostIndex: 0\n }\n}\n```\n\nWith this approach we get some benefits not possible with other approaches.\n\n1. **Simple and consistent API** - Typically a state update changes a value, pushes an item in a list,\n or merges an object into another. With a single state tree we can create an API that does exactly that:\n\n ```js\n store.set('auth.user.prefs.style', 'dark')\n store.push('auth.user.friends', 'Joe')\n store.merge('auth.user.info', {\n email: 'cheetah@jungle.com',\n name: 'Cheetah'\n })\n ```\n\n With a single state tree we can point to parts of the state using paths (the first argument).\n\n2. Cerebral does not look at the updates in your application as \"value updates\", but as \"path updates\". This allows Cerebral to make optimizations not possible in other frameworks.\n\n3. There is no need for immutability in Cerebral because a change to a path means that any component depending on that path should render (no value comparison). In applications with large data structures, immutability has a high cost. There is no need to hack objects and arrays to observe changes to them either. There is nothing special about the state you put into Cerebral's state tree.\n\n4. Since there is no value comparison in Cerebral it uses what we call **strict render**. This allows us to do render optimizations not possible with other solutions. For example you can say that a component that depends on a list is only interested in added/removed items of the list or whether the list itself is being replaced.\n\n5. When the state of the application is a single object, we can use an object inspector to visualize the whole state of your application. With the Cerebral debugger it is easy to build a mental image of application state. You can even make changes directly to state to see how it affects the view layer.\n\n## Render state\n\nSince Cerebral stores all the state of the application in a single state tree, we need a way to expose that state to the components. In some frameworks this is done by passing the whole model or collection of models/stores from the top of the application and down from one component to the next. This can become very tedious and fragile as all nested components completely depend on their parent. In Cerebral, the state of the application is directly connected to each component (here shown with **React**):\n\n```js\nconnect({\n userName: state`auth.user.info.name`\n})(function User(props) {\n props.userName // \"some name\" (value stored in 'app.user.name')\n})\n```\n\n**connect** tells Cerebral that the component is interested in a path where the user name happens to be. When that path changes, then the component will be rendered. The component is now completely independent of other components. You can move it to wherever you desire in the component tree and it will still work.\n\n## Update state\n\nThis is where Cerebral differs most from other approaches to application development. Updating the state of an application can be anything from:\n\n- flipping a **true** to a **false**\n- setting some value, like a filter\n- reading (and storing) something in local storage\n- requesting data from the server and, depending on the status code of the response, doing something with that response which might lead to new requests and/or setting up a listener for data on the server\n- etc...\n\nThe point is, updating state can be a very complex flow of operations. This is often where spaghetti code comes from, and we use abstractions to hide it. The problem with abstractions hiding too much logic is that it becomes rigid, making it difficult to reuse logic and compose existing logic together in new ways.\n\nTo handle everything from a simple toggle to very complex operations, Cerebral has the concept of **sequences**. Sequences allows you to compose functions together into a **flow**. You can define different execution paths based on whatever you want (a status code, an error, some state, etc). This allows you to write decoupled code, while still bringing everything together in the form of a higher abstraction which helps developers understand how things relate to one another (in what order they will run, when they will run, etc). Under the hood, sequences are based on [function-tree](https://github.com/cerebral/cerebral/tree/master/packages/node_modules/function-tree), a project that came out of the initial experimentations in the first version of Cerebral.\n","title":"The architecture"},"tutorial":{"raw":"# Tutorial\n\n## Introduction\n\nWelcome to the Cerebral tutorial! This guide will walk you through the core concepts of Cerebral and help you build your first application. By the end of this tutorial, you'll understand how Cerebral's state management works and how to structure your application logic.\n\n```marksy\n<Info>\nIf you are interested in learning about the concepts of Cerebral first, you can read [an introduction to the architecture](/docs/introduction/architecture) or [dig into the details](/docs/advanced/index).\n</Info>\n```\n\n## Getting Started\n\nTo follow along with this tutorial:\n\n1. Make sure you have completed the [get started](/docs/introduction/get_started.md) guide\n2. Download and install the Cerebral debugger\n3. Create a new project or use the provided Codesandbox examples\n\nAs you work through the documentation, you'll see special information boxes like these:\n\n```marksy\n<Info>\nIt means you get some general information or a tip about the topic at hand.\n</Info>\n```\n\nAnd when you see this:\n\n```marksy\n<Warning>\nIt is important that you read it to avoid being confused by something.\n</Warning>\n```\n\n## Example Application\n\nTo help you understand how Cerebral's concepts come together in a real application, take a look at this TodoMVC implementation:\n\n```marksy\n<Codesandbox id=\"cerebral-todomvc-7ke4u\" />\n```\n","title":"Tutorial"},"modules":{"raw":"# Modules\n\nModules are the fundamental building blocks of a Cerebral application. They help you organize your application into logical units with their own state and behavior.\n\n## Basic Module Structure\n\nA module is an object with properties that define its behavior:\n\n```js\nimport App from 'cerebral'\n\nconst app = App({\n state: {\n title: 'My Project',\n isLoading: false\n },\n sequences: {\n loadData: [\n // Actions to load data\n ]\n },\n providers: {\n // Custom functionality\n }\n})\n```\n\nThe most common module properties are:\n\n- **state**: The module's local state\n- **sequences**: Functions that change state and run side effects\n- **providers**: Tools that actions can use\n- **modules**: Child modules for further organization\n\n## Organizing with Modules\n\nAs your application grows, you can organize it into nested modules:\n\n```js\nimport App from 'cerebral'\nimport userModule from './modules/user'\nimport postsModule from './modules/posts'\n\nconst app = App({\n state: {\n title: 'My Project'\n },\n modules: {\n user: userModule,\n posts: postsModule\n }\n})\n```\n\nEach module can have its own state, sequences, and providers that will be namespaced under the module's path.\n\n## Creating a Module\n\nLet's create a simple module for handling users:\n\n```js\n// modules/user/index.js\nimport * as sequences from './sequences'\nimport * as providers from './providers'\n\nexport default {\n state: {\n currentUser: null,\n isLoading: false,\n error: null\n },\n sequences,\n providers\n}\n```\n\n```js\n// modules/user/sequences.js\nimport { set } from 'cerebral/factories'\nimport { state, props } from 'cerebral'\nimport * as actions from './actions'\n\nexport const fetchUser = [\n set(state`user.isLoading`, true),\n actions.fetchUser,\n {\n success: [\n set(state`user.currentUser`, props`user`),\n set(state`user.isLoading`, false)\n ],\n error: [\n set(state`user.error`, props`error`),\n set(state`user.isLoading`, false)\n ]\n }\n]\n```\n\n## State and Path Resolution\n\nThe state from all modules is merged into one big state tree:\n\n```js\n// From root module:\n{\n title: 'My Project',\n user: {\n currentUser: null,\n isLoading: false,\n error: null\n },\n posts: {\n items: [],\n isLoading: false\n }\n}\n```\n\nYou access state using paths:\n\n```js\n// In components\nconnect({\n title: state`title`,\n user: state`user.currentUser`,\n isLoadingUser: state`user.isLoading`\n})\n\n// In actions\nfunction myAction({ store }) {\n store.set(state`user.isLoading`, true)\n}\n```\n\n## Module-Relative State with moduleState\n\nYou can use `moduleState` to refer to state relative to the current module:\n\n```js\nimport { moduleState } from 'cerebral'\n\n// In a sequence in the user module:\n;[\n set(moduleState`isLoading`, true), // Sets user.isLoading\n actions.fetchUser,\n set(moduleState`isLoading`, false) // Sets user.isLoading\n]\n```\n\nThis makes your modules more portable as they don't need to know their exact path in the state tree.\n\n## Dynamic Modules\n\nFor more advanced organization, you can create module factories that generate the module based on its context:\n\n```js\nconst userModule = ({ name, path, app }) => ({\n state: {\n users: {},\n currentPage: 1\n },\n sequences: {\n initialize: [\n ({ store }) => {\n console.log(`Module \"${name}\" initialized at path \"${path}\"`)\n store.set(moduleState`initialized`, true)\n }\n ]\n }\n})\n```\n\n## Summary\n\nModules allow you to:\n\n1. Organize your application into manageable pieces\n2. Encapsulate related state and behavior\n3. Create reusable modules that can be shared between applications\n4. Scale your application as it grows in complexity\n\nIn the next sections, we'll explore other core Cerebral concepts that work with modules to help you build sophisticated applications.\n","title":"Modules"},"devtools":{"raw":"# Devtools\n\nCerebral comes with a powerful debugger that helps you understand your application state and how it changes over time.\n\n## Installing the Debugger\n\n[Download the Cerebral Debugger](https://github.com/cerebral/cerebral-debugger/releases) for your operating system:\n\n- **Mac**: cerebral-debugger-x.x.x.dmg\n- **Windows**: cerebral-debugger-setup-x.x.x.exe\n- **Linux**: cerebral-debugger_x.x.x_amd64.deb\n\nAfter installation, open the application and create a new debugger instance.\n\n```marksy\n<Image src=\"/images/add_app.png\" style={{ width: 400 }} />\n```\n\n## Connecting Your App\n\nTo connect your application to the debugger, add the devtools to your app configuration:\n\n```js\nimport { App } from 'cerebral'\nimport Devtools from 'cerebral/devtools'\n\nconst app = App(\n {\n state: {\n title: 'My Project'\n }\n },\n {\n devtools:\n process.env.NODE_ENV === 'production'\n ? null\n : Devtools({\n host: 'localhost:8585' // Default debugger address\n })\n }\n)\n```\n\n```marksy\n<Info>\nFor native applications or when developing on a different machine than the debugger, use your actual IP address instead of \"localhost\".\n</Info>\n```\n\n## Using the Debugger\n\nOnce connected, the debugger provides several powerful features:\n\n### State Tab\n\nThe State tab shows your current application state tree. You can:\n\n- Explore the complete state tree\n- See which components are watching specific state paths\n- Edit state values in real time to test UI changes\n- Search for specific state paths or values\n\n```marksy\n<Image src=\"/images/state_tab.png\" style={{ width: 600 }} />\n```\n\n### Sequences Tab\n\nThe Sequences tab lets you monitor sequence executions:\n\n- See a list of executed sequences\n- View the full execution path of each sequence\n- Inspect input and output values for each action\n- See timing information for performance analysis\n\n```marksy\n<Image src=\"/images/sequences_tab.png\" style={{ width: 600 }} />\n```\n\n### History & Time Travel\n\nThe debugger automatically records state changes, enabling time travel debugging:\n\n- Scrub through your application's history\n- Jump to any previous state\n- See what sequences and actions led to specific state changes\n\n```marksy\n<Info>\nTime travel temporarily freezes your app from executing new sequences when you're viewing past state. Return to the present to resume normal operation.\n</Info>\n```\n\n### Components Tab\n\nThe Components tab helps you understand component relationships:\n\n- See all components watching state\n- View component render count for optimization\n- Identify components with too many dependencies\n\n## Debugging Tips\n\n### 1. Use Named Actions\n\nNamed actions make it easier to understand sequence execution:\n\n```js\n// Instead of:\n;[\n ({ store }) => store.set(state`isLoading`, true),\n\n // Use:\n function setLoadingTrue({ store }) {\n store.set(state`isLoading`, true)\n }\n]\n```\n\nYou can also use the `sequence` factory to create named sequences for better debugging:\n\n```js\nimport { sequence } from 'cerebral/factories'\n\n// Named sequence appears in debugger\nexport const fetchItems = sequence('Items - Fetch', [\n setLoadingTrue,\n httpGet('/api/items'),\n set(state`items`, props`result`),\n setLoadingFalse\n])\n```\n\n### 2. Structure Your State Logically\n\nWell-organized state makes debugging easier:\n\n```js\n// Instead of flat state:\n{\n userFirstName: 'John',\n userLastName: 'Doe',\n userAge: 30\n}\n\n// Group related data:\n{\n user: {\n firstName: 'John',\n lastName: 'Doe',\n age: 30\n }\n}\n```\n\n### 3. Use Development-Only Debugging\n\nAdd debugging-specific code that only runs in development:\n\n```js\nfunction debugAction({ props, store }) {\n if (process.env.NODE_ENV !== 'production') {\n console.log('Props received:', props)\n }\n\n // Normal action logic\n store.set(state`user`, props.user)\n}\n```\n\n## Production Considerations\n\nAlways disable devtools in production by using environment checks:\n\n```js\ndevtools: process.env.NODE_ENV === 'production'\n ? null\n : Devtools({\n host: 'localhost:8585'\n })\n```\n\nFor more advanced configuration options, see the [Devtools API documentation](/docs/api/devtools.html).\n","title":"Devtools"},"state":{"raw":"# State\n\nCerebral uses a single state tree to store all the state of your application. Even though you split up your state into modules, at the end of the day it will look like one big tree:\n\n```js\n{\n title: 'My Project',\n someOtherModule: {\n foo: 'bar'\n }\n}\n```\n\n## State management principles\n\nCerebral's state management is built on several key principles:\n\n1. **Simple data types**: Store only plain objects, arrays, strings, numbers, booleans, and a few special types like File and Blob\n2. **Serializable state**: The entire state tree can be serialized to JSON (with a few exceptions like File objects)\n3. **Path-based access**: All state is accessed via paths, making it easy to understand data relationships\n4. **Controlled mutations**: State can only be changed through the store API, ensuring all changes are tracked\n\nThese principles make your application more predictable and easier to debug.\n\n## Adding state to your app\n\nLet's expand our application with more state to manage posts and users:\n\n```js\nimport { App } from 'cerebral'\nimport Devtools from 'cerebral/devtools'\n\nconst app = App({\n state: {\n title: 'My Project',\n posts: [],\n users: {},\n userModal: {\n show: false,\n id: null\n },\n isLoadingPosts: false,\n isLoadingUser: false,\n error: null\n }\n}, {...})\n```\n\n## Data modeling strategies\n\nWhen modeling your state, you'll need to decide how to structure your data. There are two main approaches:\n\n### Arrays vs Objects for collections\n\nFor our posts and users, we've chosen different storage approaches:\n\n```js\n{\n posts: [\n { id: 1, title: 'Post 1', userId: 1 },\n { id: 2, title: 'Post 2', userId: 2 }\n ],\n users: {\n '1': { id: 1, name: 'User 1' },\n '2': { id: 2, name: 'User 2' }\n }\n}\n```\n\nThe decision depends on how you'll access the data:\n\n- **Arrays** are best when you primarily need to iterate through items (like rendering a list of posts)\n- **Objects with ID keys** are better for lookups by ID (like finding a user by ID)\n\nFor example, to display a user in our modal:\n\n```js\n// With object storage - O(1) constant time lookup\nconst user = users[userModal.id]\n\n// With array storage - O(n) linear time search\nconst user = users.find((user) => user.id === userModal.id)\n```\n\nObjects also naturally prevent duplicate entries with the same ID, since object keys must be unique.\n\n## Accessing state\n\nIn Cerebral, you access state using the `state` tag:\n\n```js\nimport { state } from 'cerebral'\n\n// In a component\nconnect({\n title: state`title`,\n posts: state`posts`\n})\n\n// In an action\nfunction myAction({ get }) {\n const title = get(state`title`)\n const posts = get(state`posts`)\n}\n```\n\nYou can also use a cleaner object notation syntax if you configure the [@cerebral/babel-plugin](/docs/api/proxy.html) in your project:\n\n```js\nimport { state } from 'cerebral'\n\n// In a component\nconnect({\n title: state.title,\n posts: state.posts\n})\n\n// In an action\nfunction myAction({ get }) {\n const title = get(state.title)\n const posts = get(state.posts)\n}\n```\n\n## Updating state\n\nState can only be updated in actions using the store provider:\n\n```js\nfunction setTitle({ store }) {\n store.set(state`title`, 'New Title')\n}\n\nfunction addPost({ store, props }) {\n store.push(state`posts`, props.post)\n}\n```\n\nWe'll explore more about state operations in later sections.\n","title":"State"},"providers":{"raw":"# Providers\n\n## What are Providers?\n\nIn Cerebral, providers are the bridge between your application and the outside world. They handle all the side effects in your application - everything from API calls to browser APIs, local storage, or any external system.\n\nProviders offer several key benefits:\n\n1. They create an **explicit API** for side effects in your application\n2. They are automatically **tracked by the debugger**, giving you insight into their usage\n3. They're **easy to mock** during testing, making your tests more reliable\n\n## Creating a Simple Provider\n\nLet's create a provider to communicate with the [JSONPlaceholder](https://jsonplaceholder.typicode.com/) API:\n\n```js\nimport App from 'cerebral'\nimport Devtools from 'cerebral/devtools'\n\nconst API_URL = 'https://jsonplaceholder.typicode.com'\n\nconst app = App({\n state: {\n title: 'My Project',\n posts: [],\n users: {},\n userModal: {\n show: false,\n id: null\n },\n isLoadingPosts: false,\n isLoadingUser: false,\n error: null\n },\n providers: {\n api: {\n getPosts() {\n return fetch(`${API_URL}/posts`).then((response) => response.json())\n },\n getUser(id) {\n return fetch(`${API_URL}/users/${id}`).then((response) =>\n response.json()\n )\n }\n }\n }\n})\n```\n\nInstead of creating a generic HTTP provider, we've built a specific API provider for JSONPlaceholder. This approach is recommended as it makes your application code more readable and focused.\n\n## Using Providers in Actions\n\nOnce defined, providers are available in the context object passed to your actions:\n\n```js\nexport function getPosts({ api, state }) {\n state.set('isLoadingPosts', true)\n\n return api\n .getPosts()\n .then((posts) => ({ posts }))\n .catch((error) => ({ error: error.message }))\n}\n```\n\n## Provider Best Practices\n\n### 1. Create Domain-Specific Providers\n\nInstead of generic providers, create providers that speak the language of your domain:\n\n```js\n// GOOD: Domain-specific provider\nproviders: {\n usersApi: {\n getUsers() { /* ... */ },\n getUserById(id) { /* ... */ }\n }\n}\n\n// AVOID: Generic provider that needs configuration in actions\nproviders: {\n http: {\n get(url) { /* ... */ },\n post(url, data) { /* ... */ }\n }\n}\n```\n\n### 2. Handle Errors Appropriately\n\nMake your providers handle errors in a consistent way:\n\n```js\nuserApi: {\n getUser(id) {\n return fetch(`/api/users/${id}`)\n .then(response => {\n if (!response.ok) {\n throw new Error(`Failed to fetch user: ${response.status}`)\n }\n return response.json()\n })\n }\n}\n```\n\n### 3. Compose Providers\n\nProviders can use other providers through `this.context`:\n\n```js\nproviders: {\n logger: {\n log(message) {\n console.log(`[${new Date().toISOString()}]`, message)\n }\n },\n userApi: {\n getUser(id) {\n this.context.logger.log(`Fetching user with ID: ${id}`)\n return fetch(`/api/users/${id}`).then(response => response.json())\n }\n }\n}\n```\n\n## Common Provider Examples\n\n### Local Storage Provider\n\n```js\nconst storageProvider = {\n get(key) {\n return JSON.parse(localStorage.getItem(key))\n },\n set(key, value) {\n localStorage.setItem(key, JSON.stringify(value))\n },\n remove(key) {\n localStorage.removeItem(key)\n }\n}\n```\n\n### Authentication Provider\n\n```js\nconst authProvider = {\n login(credentials) {\n return fetch('/api/login', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify(credentials)\n }).then((response) => response.json())\n },\n logout() {\n return fetch('/api/logout', { method: 'POST' })\n },\n getToken() {\n return localStorage.getItem('authToken')\n }\n}\n```\n\nWith providers, you can build a clean separation between your application logic and external systems, making your code more testable, understandable, and easier to maintain.\n","title":"Providers"},"sequences":{"raw":"# Sequences\n\nApplication development is about handling events to run side effects and produce changes to the state. Cerebral uses **sequences** to define these flows in a declarative, functional way.\n\n## From Imperative to Declarative\n\nLet's compare a traditional imperative approach to Cerebral's declarative sequence approach.\n\n### Traditional Imperative Approach\n\n```js\nimport { App } from 'cerebral'\n\nconst app = App({\n state: {\n title: 'My Project',\n posts: [],\n users: {},\n userModal: {\n show: false,\n id: null\n },\n isLoadingPosts: false,\n isLoadingUser: false,\n error: null\n },\n methods: {\n openPostsPage() {\n this.state.isLoadingPosts = true\n this.providers.api.getPosts().then((posts) => {\n this.state.posts = posts\n this.state.isLoadingPosts = false\n })\n },\n openUserModal(id) {\n this.state.isLoadingUser = true\n this.providers.api.getUser(id).then((user) => {\n this.state.users[id] = user\n this.state.userModal.id = id\n this.state.isLoadingUser = false\n })\n }\n },\n providers: {\n api: {\n /*...*/\n }\n }\n})\n```\n\n### Cerebral's Declarative Approach\n\nIn Cerebral, we think about **what** should happen before we define **how** it happens. Let's define our sequences first:\n\n```js\nimport { App } from 'cerebral'\nimport { set } from 'cerebral/factories'\nimport { state, props } from 'cerebral'\n\nconst app = App({\n state: {\n title: 'My Project',\n posts: [],\n users: {},\n userModal: {\n show: false,\n id: null\n },\n isLoadingPosts: false,\n isLoadingUser: false,\n error: null\n },\n sequences: {\n openPostsPage: [\n set(state`isLoadingPosts`, true),\n getPosts,\n set(state`posts`, props`posts`),\n set(state`isLoadingPosts`, false)\n ],\n openUserModal: [\n set(state`userModal.id`, props`id`),\n set(state`userModal.show`, true),\n set(state`isLoadingUser`, true),\n getUser,\n set(state`users.${props`id`}`, props`user`),\n set(state`isLoadingUser`, false)\n ]\n },\n providers: {\n api: {\n /*...*/\n }\n }\n})\n```\n\nNow we need to implement the actions used in our sequences:\n\n```js\n// Action to get posts\nfunction getPosts({ api }) {\n return api.getPosts().then((posts) => ({ posts }))\n}\n\n// Action to get user\nfunction getUser({ api, props }) {\n return api.getUser(props.id).then((user) => ({ user }))\n}\n```\n\n## Actions\n\nEvery function in a sequence is called an **action**. Actions receive a context object with access to:\n\n1. **store** - For changing state\n2. **props** - Values passed to the sequence or from previous actions\n3. **path** - When using paths for branching\n4. Your custom **providers** (like the api provider in our example)\n\nActions can be synchronous or asynchronous:\n\n```js\n// Synchronous action\nfunction syncAction({ store }) {\n store.set('some.path', 'some value')\n}\n\n// Asynchronous action with Promise\nfunction asyncAction({ api }) {\n return api.getSomething().then((data) => ({ data }))\n}\n\n// Asynchronous action with async/await\nasync function modernAction({ api }) {\n const data = await api.getSomething()\n return { data }\n}\n```\n\n## Props Flow\n\nWhen a sequence triggers, you can pass it an object of props. These props are available to all actions in the sequence:\n\n```js\n// Trigger a sequence with props\napp.getSequence('openUserModal')({ id: 123 })\n```\n\nWhen an action returns an object, its properties are merged with the existing props and passed to the next action:\n\n```js\n;[\n // Props: { id: 123 }\n function firstAction({ props }) {\n return { foo: 'bar' } // Adds to props\n },\n // Props: { id: 123, foo: 'bar' }\n function secondAction({ props }) {\n console.log(props) // { id: 123, foo: 'bar' }\n }\n]\n```\n\n## Factories\n\nCerebral includes **factories** - functions that create actions. They help you write more concise and readable code:\n\n```js\nimport { set, push, toggle, when } from 'cerebral/factories'\nimport { state, props } from 'cerebral'\n\nexport const mySequence = [\n // Set a value in state\n set(state`user.name`, props`name`),\n\n // Push to an array\n push(state`items`, props`newItem`),\n\n // Toggle a boolean\n toggle(state`menu.isOpen`),\n\n // Conditional logic\n when(state`user.isAdmin`),\n {\n true: [set(state`adminTools.visible`, true)],\n false: [set(state`adminTools.visible`, false)]\n }\n]\n```\n\n```marksy\n<Info>\nYou can also use object notation (like `state.user.name`) with the [@cerebral/babel-plugin](/docs/api/proxy.html) in your project.\n</Info>\n```\n\nThis approach makes your sequences more declarative and easier to understand.\n\n## Using the Debugger\n\nYou can visualize sequence execution with the Cerebral debugger. To try it out, add this to your entry file:\n\n```js\n// Get a reference to a sequence\nconst openPostsPage = app.getSequence('openPostsPage')\n\n// Run it\nopenPostsPage()\n```\n\nWhen you refresh your application, you should see the sequence execution in the debugger. Experiment with the checkboxes at the top of the execution window to adjust the level of detail shown.\n\n## Summary\n\nSequences provide a structured way to define your application logic by:\n\n1. Declaring the exact flow of operations\n2. Breaking down complex logic into simple actions\n3. Using factories to handle common operations\n4. Providing great debugging through the Cerebral debugger\n\nIn the next sections, we'll explore more advanced features like branching paths and error handling.\n","title":"Sequences"},"organize":{"raw":"# Organize\n\nWhen starting with Cerebral, you might begin with all your code in a single file. As your application grows, organizing your code becomes essential for maintenance and scalability.\n\n## The Evolution of a Cerebral App\n\nLet's walk through how to transform a single-file app into a well-organized structure.\n\n### Starting Point\n\nYour initial app might look something like this:\n\n```js\nimport App from 'cerebral'\nimport { state, props } from 'cerebral'\nimport { set } from 'cerebral/factories'\nimport Devtools from 'cerebral/devtools'\n\n// Define actions\nconst getPosts = ({ api }) => api.getPosts().then((posts) => ({ posts }))\nconst getUser = ({ api, props }) =>\n api.getUser(props.id).then((user) => ({ user }))\n\n// Define sequences\nconst openPostsPage = [\n set(state`isLoadingPosts`, true),\n getPosts,\n set(state`posts`, props`posts`),\n set(state`isLoadingPosts`, false)\n]\n\nconst openUserModal = [\n set(state`userModal.show`, true),\n set(state`userModal.id`, props`id`),\n set(state`isLoadingUser`, true),\n getUser,\n set(state`users.${props`id`}`, props`user`),\n set(state`isLoadingUser`, false)\n]\n\n// Define API provider\nconst api = {\n getPosts() {\n return fetch('https://jsonplaceholder.typicode.com/posts').then(\n (response) => response.json()\n )\n },\n getUser(id) {\n return fetch(`https://jsonplaceholder.typicode.com/users/${id}`).then(\n (response) => response.json()\n )\n }\n}\n\n// Define main module\nconst main = {\n state: {\n title: 'My Project',\n posts: [],\n users: {},\n userModal: {\n show: false,\n id: null\n },\n isLoadingPosts: false,\n isLoadingUser: false,\n error: null\n },\n sequences: {\n openPostsPage,\n openUserModal\n },\n providers: {\n api\n }\n}\n\n// Create the app\nconst app = App(main, {\n devtools:\n process.env.NODE_ENV === 'production'\n ? null\n : Devtools({ host: 'localhost:8585' })\n})\n\n// Use the app\napp.getSequence('openPostsPage')()\n```\n\nAs your application grows, this approach becomes difficult to maintain. Let's break it down into separate files.\n\n## First Step: Separate Files by Type\n\nThe first step in organizing your code is to separate it by type into different files.\n\n### Main Module (src/main/index.js)\n\n```js\nimport * as sequences from './sequences'\nimport * as providers from './providers'\n\nexport default {\n state: {\n title: 'My Project',\n posts: [],\n users: {},\n userModal: {\n show: false,\n id: null\n },\n isLoadingPosts: false,\n isLoadingUser: false,\n error: null\n },\n sequences,\n providers\n}\n```\n\n### Actions (src/main/actions.js)\n\n```js\nexport const getPosts = ({ api }) => api.getPosts().then((posts) => ({ posts }))\n\nexport const getUser = ({ api, props }) =>\n api.getUser(props.id).then((user) => ({ user }))\n```\n\n### Sequences (src/main/sequences.js)\n\n```js\nimport { set } from 'cerebral/factories'\nimport { state, props } from 'cerebral'\nimport * as actions from './actions'\n\nexport const openPostsPage = [\n set(state`isLoadingPosts`, true),\n actions.getPosts,\n set(state`posts`, props`posts`),\n set(state`isLoadingPosts`, false)\n]\n\nexport const openUserModal = [\n set(state`userModal.show`, true),\n set(state`userModal.id`, props`id`),\n set(state`isLoadingUser`, true),\n actions.getUser,\n set(state`users.${props`id`}`, props`user`),\n set(state`isLoadingUser`, false)\n]\n```\n\n### Providers (src/main/providers.js)\n\n```js\nconst API_URL = 'https://jsonplaceholder.typicode.com'\n\nexport const api = {\n getPosts() {\n return fetch(`${API_URL}/posts`).then((response) => response.json())\n },\n getUser(id) {\n return fetch(`${API_URL}/users/${id}`).then((response) => response.json())\n }\n}\n```\n\n### App Entry Point (src/index.js)\n\n```js\nimport App from 'cerebral'\nimport Devtools from 'cerebral/devtools'\nimport main from './main'\n\nconst app = App(main, {\n devtools:\n process.env.NODE_ENV === 'production'\n ? null\n : Devtools({ host: 'localhost:8585' })\n})\n\n// Get a sequence directly from the app\nconst openPostsPage = app.getSequence('openPostsPage')\n\n// Run it\nopenPostsPage()\n```\n\n## Second Step: Add Feature Modules\n\nAs your application grows further, you'll want to organize code by features or domains:\n\n```js\nsrc /\n main / // Root module\n modules / // Feature modules\n auth / // Authentication feature\n posts / // Posts feature\n users / // Users feature\n actions.js // Shared actions\nsequences.js // Shared sequences\nproviders.js // Shared providers\nindex.js // Main module definition\nindex.js // App entry point\n```\n\nEach feature module can follow the same structure as the main module:\n\n```js\nsrc / main / modules / posts / actions.js // Post-specific actions\nsequences.js // Post-specific sequences\ncomputeds.js // Post-specific computed values\nindex.js // Module definition\n```\n\n### Feature Module Example (src/main/modules/posts/index.js)\n\n```js\nimport * as sequences from './sequences'\n\nexport default {\n state: {\n items: [],\n currentId: null,\n isLoading: false\n },\n sequences\n}\n```\n\n### Including Modules (src/main/index.js)\n\n```js\nimport * as sequences from './sequences'\nimport * as providers from './providers'\nimport auth from './modules/auth'\nimport posts from './modules/posts'\nimport users from './modules/users'\n\nexport default {\n state: {\n title: 'My Project'\n },\n sequences,\n providers,\n modules: {\n auth,\n posts,\n users\n }\n}\n```\n\n## Benefits of This Approach\n\nOrganizing your code this way provides several benefits:\n\n1. **Maintainability**: Each file has a clear purpose\n2. **Collaboration**: Team members can work on different modules\n3. **Testability**: Easier to write focused tests for actions and sequences\n4. **Scalability**: Structure supports growth of application complexity\n5. **Reusability**: Actions and sequences can be shared between modules\n\nFor more advanced organization techniques and best practices as your application grows larger, see the [Structure guide](/docs/advanced/structure.html).\n","title":"Organize"},"views":{"raw":"# View Integrations\n\nCerebral can be connected to various view libraries, allowing you to use your preferred UI framework while leveraging Cerebral's state management capabilities. This separation of concerns means Cerebral handles your application state and side effects while the view library handles rendering.\n\n## Available View Integrations\n\nCerebral provides official integrations with several popular view libraries:\n\n- **React** - `@cerebral/react`\n- **Vue** - `@cerebral/vue`\n- **Angular** - `@cerebral/angular`\n- **Preact** - `@cerebral/preact`\n- **Inferno** - `@cerebral/inferno`\n\nAll integrations follow a similar pattern for connecting components to Cerebral state and sequences.\n\n## Basic Integration Pattern\n\nRegardless of which view library you choose, the integration follows these steps:\n\n1. Install Cerebral and the view integration package\n2. Create a Cerebral app with your main module\n3. Connect the app to your view using a Container component\n4. Connect individual components to Cerebral state and sequences\n\n## Using Vue\n\nVue is one of the supported view libraries for Cerebral.\n\n### Installation of @cerebral/vue\n\n```bash\nnpm install vue @cerebral/vue\n```\n\n### Basic Setup with Vue 3\n\n```js\nimport { createApp } from 'vue'\nimport App from 'cerebral'\nimport { Container, connect } from '@cerebral/vue'\nimport AppComponent from './components/App'\nimport main from './main'\n\nconst app = App(main, {\n devtools:\n process.env.NODE_ENV === 'production'\n ? null\n : Devtools({ host: 'localhost:8585' })\n})\n\nconst loadItemsPage = app.get(sequences.loadItemsPage)\nloadItemsPage()\n\ncreateApp({\n components: {\n Container: Container(app),\n AppComponent\n },\n template: '<Container><AppComponent /></Container>'\n}).mount('#app')\n```\n\n### Defining Components\n\n```js\nimport { connect } from '@cerebral/vue'\nimport { state, sequences } from 'cerebral'\n\nexport default connect(\n {\n posts: state`posts`,\n isLoadingItems: state`isLoadingItems`,\n openUserModal: sequences`openUserModal`\n },\n {\n template: `\n <div v-if=\"isLoadingItems\" class=\"content\">\n <h4>Loading posts...</h4>\n </div>\n <div v-else class=\"content\">\n <div class=\"posts\">\n <div\n v-for=\"post in posts\"\n class=\"post\"\n @click=\"openUserModal({ id: post.userId })\"\n >\n {{ post.title }}\n </div>\n </div>\n </div>\n`\n }\n)\n```\n\nYou can also use **.vue** files for your components. Read more about that in [@cerebral/vue](/views/vue.html).\n\n## Using React\n\nReact is one of the most popular integrations for Cerebral.\n\n### Installation of @cerebral/react\n\n```bash\nnpm install react react-dom @cerebral/react\n```\n\n### Basic Setup\n\n```js\nimport React from 'react'\nimport { createRoot } from 'react-dom/client'\nimport App from 'cerebral'\nimport { Container } from '@cerebral/react'\nimport Devtools from 'cerebral/devtools'\nimport AppComponent from './components/App'\nimport main from './main'\n\nconst app = App(main, {\n devtools: Devtools({\n host: 'localhost:8585'\n })\n})\n\nconst root = createRoot(document.querySelector('#app'))\nroot.render(\n <Container app={app}>\n <AppComponent />\n </Container>\n)\n```\n\n### Defining Components in React\n\n```js\nimport React from 'react'\nimport { state, sequences } from 'cerebral'\nimport { connect } from '@cerebral/react'\n\n// Function component (recommended)\nexport default connect(\n {\n posts: state`posts`,\n openUserModal: sequences`openUserModal`\n },\n function App({ posts, openUserModal }) {\n return (\n <div className=\"posts\">\n {posts.map((post) => (\n <div\n key={post.id}\n className=\"post\"\n onClick={() => openUserModal({ id: post.userId })}\n >\n {post.title}\n </div>\n ))}\n </div>\n )\n }\n)\n\n// Dynamic dependencies\nexport const DynamicComponent = connect(function ({ get }) {\n const posts = get(state`posts`)\n const openUserModal = get(sequences`openUserModal`)\n\n return (\n <div className=\"posts\">\n {posts.map((post) => (\n <div\n key={post.id}\n className=\"post\"\n onClick={() => openUserModal({ id: post.userId })}\n >\n {post.title}\n </div>\n ))}\n </div>\n )\n})\n```\n\nThe point is that for Cerebral it does not really matter. Cerebral is responsible for your state, state changes and side effects. You can use whatever to convert your state into a UI for your users.\n\nFrom this point you are hopefully able to start exploring on your own. Continue looking into more advanced concepts and guides.\n","title":"View Integrations"}},"advanced":{"index":{"raw":"# Computed\n\nComputed values let you derive state from your application state tree. Instead of putting calculation logic in components or actions, computed values give you a dedicated place for this logic with automatic optimization.\n\n## When to Use Computed Values\n\nUse computed values when you need to:\n\n- Filter or transform lists\n- Combine multiple state values\n- Calculate derived properties\n- Cache expensive calculations\n- Share derived state across components\n\n## Basic Computed\n\nLet's look at a simple example that filters a list based on criteria in state:\n\n```js\nimport { state } from 'cerebral'\n\nexport const filteredList = (get) => {\n const items = get(state`items`)\n const filter = get(state`filter`)\n\n return items.filter((item) => item[filter.property] === filter.value)\n}\n```\n\nAdd it to your state tree:\n\n```js\nimport { filteredList } from './computed'\n\nexport default {\n state: {\n items: [\n { id: 1, title: 'Item 1', isCompleted: false },\n { id: 2, title: 'Item 2', isCompleted: true }\n ],\n filter: {\n property: 'isCompleted',\n value: false\n },\n filteredList\n }\n}\n```\n\n## Performance Benefits\n\nWhen you use a computed value:\n\n1. **Automatic dependency tracking**: The `get` function tracks every state path you access\n2. **Smart caching**: The computed value only recalculates when its dependencies change\n3. **Composition optimization**: When computed values use other computed values, the dependency tree is maintained\n\nThis makes computed values significantly more efficient than calculating derived state in components or actions.\n\n## Composition\n\nYou can compose computed values by using other computed values:\n\n```js\nimport { state } from 'cerebral'\n\n// First computed\nexport const activeUsers = (get) => {\n const users = get(state`users`)\n return users.filter((user) => user.isActive)\n}\n\n// Second computed using the first\nexport const activeAdmins = (get) => {\n const active = get(state`activeUsers`)\n return active.filter((user) => user.isAdmin)\n}\n```\n\nIn your state tree:\n\n```js\nimport { activeUsers, activeAdmins } from './computed'\n\nexport default {\n state: {\n users: [\n /* user objects */\n ],\n activeUsers,\n activeAdmins\n }\n}\n```\n\n## Dynamic Computed with Props\n\nComputed values can access component props, making them dynamic:\n\n```js\nimport { state, props } from 'cerebral'\n\nexport const userItems = (get) => {\n const userId = get(props`userId`)\n const items = get(state`items`)\n\n return items.filter((item) => item.userId === userId)\n}\n```\n\nUse it in a component:\n\n```js\n// React example\nconnect(\n {\n items: state`userItems`\n },\n function UserItems({ items }) {\n return (\n <ul>\n {items.map(item => (\n <li key={item.id}>{item.title}</li>\n ))}\n </ul>\n )\n }\n)\n\n// When using the component\n<UserItems userId=\"user-123\" />\n```\n\nBehind the scenes, Cerebral clones the computed for each component instance to maintain individual caching.\n\n## Creating Computed Factories\n\nSometimes you'll want to create reusable computed patterns. You can create a computed factory:\n\n```js\nimport { state } from 'cerebral'\n\nexport const itemsByStatus = (statusKey) => (get) => {\n const items = get(state`items`)\n const status = get(state`${statusKey}`)\n\n return items.filter((item) => item.status === status)\n}\n\n// Usage in state\nexport default {\n state: {\n items: [],\n activeStatus: 'pending',\n archivedStatus: 'archived',\n\n // Created from factory\n pendingItems: itemsByStatus('activeStatus'),\n archivedItems: itemsByStatus('archivedStatus')\n }\n}\n```\n\n## Using in Actions\n\nYou can use computed values in actions just like any other state:\n\n```js\nfunction myAction({ get, store }) {\n const filteredItems = get(state`filteredList`)\n\n if (filteredItems.length === 0) {\n store.set(state`noResults`, true)\n }\n}\n```\n\nFor computed values that require props, pass them as the second argument:\n\n```js\nfunction myAction({ get }) {\n const userItems = get(state`userItems`, { userId: 'user-123' })\n // Do something with userItems\n}\n```\n\n## Best Practices\n\n1. **Keep computed values pure**: Don't cause side effects in computed functions\n2. **Move complex logic out of components**: Extract calculations into computed values\n3. **Be specific with dependencies**: Only access the state you need\n4. **Compose for clarity**: Break complex computations into smaller, composable pieces\n5. **Watch for expensive operations**: Even computed values will run when dependencies change\n\nBy following these patterns, computed values can significantly improve your application's performance and maintainability.\n","title":"Computed"},"errors":{"raw":"# Error Handling\n\nCerebral provides a structured approach to error handling through the `catch` property of modules. This allows you to handle errors without cluttering your sequences with try/catch patterns.\n\n## Basic Error Handling\n\nInstead of creating nested structures for error handling, you can use the `catch` property to handle errors in a cleaner way:\n\n```js\nimport { set } from 'cerebral/factories'\nimport { state, props } from 'cerebral'\n\nconst getUser = ({ jsonPlaceholder, props }) =>\n jsonPlaceholder.getUser(props.id).then((user) => ({ user }))\n\nexport default {\n state: {\n title: 'My Project',\n users: {},\n currentUserId: null,\n isLoadingUser: false,\n error: null\n },\n sequences: {\n loadUser: [\n set(state`isLoadingUser`, true),\n getUser,\n set(state`users.${props`id`}`, props`user`),\n set(state`currentUserId`, props`id`),\n set(state`isLoadingUser`, false)\n ]\n },\n catch: [[Error, set(state`error`, props`error.message`)]]\n}\n```\n\nThe `catch` property takes an array of error handlers. Each handler is an array with two elements:\n\n1. The error type to catch\n2. The sequence to run when the error is caught\n\n```marksy\n<Warning>\nNotice that the catch handler is an array of arrays. Each item in the array is an array of two items. The type of error to handle and what sequence should handle it.\n</Warning>\n```\n\n## Custom Error Types\n\nCreating custom error types gives you more control over error handling. This allows you to catch specific errors in different ways:\n\n```js\nimport { CerebralError } from 'cerebral'\nimport { set } from 'cerebral/factories'\nimport { state, props } from 'cerebral'\n\n// Create a custom error type\nexport class JsonPlaceholderError extends CerebralError {\n constructor(message, statusCode) {\n super(message)\n this.name = 'JsonPlaceholderError'\n this.statusCode = statusCode\n }\n}\n\nexport default {\n // ...module definition\n catch: [[JsonPlaceholderError, set(state`error`, props`error.message`)]]\n}\n```\n\n## Throwing Custom Errors\n\nYou can throw custom errors from actions or providers:\n\n```js\nimport { Provider } from 'cerebral'\nimport { JsonPlaceholderError } from './errors'\n\nexport const jsonPlaceholder = Provider({\n getUser(id) {\n return fetch(`https://jsonplaceholder.typicode.com/users/${id}`)\n .then((response) => {\n if (response.status >= 200 && response.status < 300) {\n return response.json()\n } else {\n return response.text().then((message) => {\n throw new JsonPlaceholderError(message, response.status)\n })\n }\n })\n .then((user) => ({ user }))\n .catch((error) => {\n throw new JsonPlaceholderError(error.message, 0)\n })\n }\n})\n```\n\n## Error Handler Sequences\n\nFor better organization, create dedicated sequences for error handling:\n\n```js\n// sequences.js\nimport { set } from 'cerebral/factories'\nimport { state, props } from 'cerebral'\n\nexport const handleError = [\n set(state`error`, props`error.message`),\n set(state`isLoadingUser`, false)\n]\n\nexport const loadUser = [\n // ...loadUser sequence\n]\n\n// In your module\nexport default {\n // ...module definition\n catch: [[JsonPlaceholderError, sequences.handleError]]\n}\n```\n\n## Error Debugging\n\nBy default, Cerebral will show caught errors in the debugger but won't throw them to the console. You can change this behavior with the `throwToConsole` option:\n\n```js\nimport App from 'cerebral'\nimport main from './main'\nimport Devtools from 'cerebral/devtools'\n\nconst app = App(main, {\n throwToConsole: true, // Also log caught errors to console\n devtools: Devtools({\n host: 'localhost:8585'\n })\n})\n```\n\n## Error Propagation\n\nErrors propagate up the module hierarchy. If a module doesn't have a matching error handler, the error will bubble up to its parent module, and so on until it reaches the root module.\n\nThis allows you to:\n\n- Handle common errors at the root level\n- Handle specific errors close to where they occur\n- Create specialized error handling for different parts of your application\n","title":"Error Handling"},"factories":{"raw":"# Custom Factories\n\n## What is a Factory?\n\nIn Cerebral, factories are functions that create actions. This pattern gives you power to create reusable, configurable actions:\n\n```js\nfunction createAction(config) {\n return function action(context) {\n // Use config and context here\n }\n}\n```\n\n## Why Use Factories?\n\nFactories help you:\n\n- Reuse similar logic with different configurations\n- Make your sequences more declarative and readable\n- Configure actions before they run\n\n## Basic Factory Example\n\nHere's a simple factory that creates a message:\n\n```js\nfunction createMessager(name) {\n function message({ store }) {\n store.set('message', `Hello ${name}!`)\n }\n return message\n}\n\n// Use it in a sequence\nexport const sayHello = [\n createMessager('Bob')\n // Result: sets state.message to \"Hello Bob!\"\n]\n```\n\n## Creating HTTP Request Factories\n\nOne common use case is creating factories for API requests:\n\n```js\nfunction httpGetFactory(url) {\n function httpGetAction({ http, props }) {\n return http.get(url).then((response) => ({ response: response.data }))\n }\n return httpGetAction\n}\n\n// Usage\nexport const getUsers = [\n httpGetFactory('/api/users'),\n set(state`users`, props`response`)\n]\n```\n\n## Using Dynamic Values with Tags\n\nLet's improve our factory to handle dynamic values using Cerebral's tag system:\n\n```js\nimport { string, props } from 'cerebral'\n\nfunction httpGetFactory(url) {\n function httpGetAction({ http, get }) {\n // Resolve the URL if it's a tag\n const resolvedUrl = get(url)\n return http\n .get(resolvedUrl)\n .then((response) => ({ response: response.data }))\n }\n return httpGetAction\n}\n\n// Usage with dynamic URL\nexport const getUser = [\n httpGetFactory(string`/api/users/${props`userId`}`),\n set(state`currentUser`, props`response`)\n]\n```\n\n## Supporting Path Divergence\n\nFactories can support branching execution paths:\n\n```js\nfunction httpGetFactory(url) {\n function httpGetAction({ http, get, path }) {\n const resolvedUrl = get(url)\n\n // Check if this action is used with paths\n if (path) {\n return http\n .get(resolvedUrl)\n .then((response) => path.success({ response: response.data }))\n .catch((error) => path.error({ error: error.response }))\n } else {\n // Regular promise return when not using paths\n return http\n .get(resolvedUrl)\n .then((response) => ({ response: response.data }))\n }\n }\n return httpGetAction\n}\n\n// Usage with paths\nexport const getUsers = [\n httpGetFactory('/api/users'),\n {\n success: set(state`users`, props`response`),\n error: set(state`error`, props`error`)\n }\n]\n```\n\n## Status-Based Path Selection\n\nYou can enhance factories to choose paths based on status codes:\n\n```js\nfunction httpGetFactory(url) {\n function httpGetAction({ http, get, path }) {\n const resolvedUrl = get(url)\n\n if (path) {\n return http\n .get(resolvedUrl)\n .then((response) => {\n // Choose path based on status code or use default success\n return path[response.status]\n ? path[response.status]({ response: response.data })\n : path.success({ response: response.data })\n })\n .catch((error) => {\n return path[error.response?.status]\n ? path[error.response?.status]({ error: error.response })\n : path.error({ error: error.response })\n })\n } else {\n // Regular promise return\n return http\n .get(resolvedUrl)\n .then((response) => ({ response: response.data }))\n }\n }\n return httpGetAction\n}\n\n// Usage with status paths\nexport const getUsers = [\n httpGetFactory('/api/users'),\n {\n success: set(state`users`, props`response`),\n 404: set(state`error`, 'Users not found'),\n error: set(state`error`, props`error.message`)\n }\n]\n```\n\n## Working with Tag Paths\n\nSometimes you need to resolve not just the value of a tag, but its path:\n\n```js\nfunction setFactory(target, value) {\n function setAction({ store, resolve }) {\n // Check if target is a state tag\n if (resolve.isTag(target, 'state')) {\n // Get the path from the tag (e.g., \"users.list\")\n const path = resolve.path(target)\n // Get the value to set, which might also be a tag\n const resolvedValue = resolve.value(value)\n\n // Update state at the resolved path\n store.set(path, resolvedValue)\n } else {\n throw new Error('Target must be a state tag')\n }\n }\n return setAction\n}\n\n// Usage\nexport const updateUser = [\n setFactory(state`users.${props`userId`}`, props`userData`)\n]\n```\n\n## Simple Factory Examples\n\nHere are some useful custom factories you can create:\n\n```js\n// Notification factory\nfunction notifyFactory(message) {\n return function notify({ store, get }) {\n store.set('notifications.message', get(message))\n store.set('notifications.visible', true)\n }\n}\n\n// Timeout factory\nfunction delayFactory(ms) {\n return function delay() {\n return new Promise((resolve) => setTimeout(resolve, ms))\n }\n}\n\n// Usage\nexport const showNotification = [\n notifyFactory('Operation successful!'),\n delayFactory(3000),\n set(state`notifications.visible`, false)\n]\n```\n\n## Best Practices\n\n1. **Name your actions**: Give the returned function a name for better debugging\n2. **Keep factories focused**: Each factory should do one thing well\n3. **Handle errors**: Always consider how errors will be handled\n4. **Document parameters**: Make it clear what each factory expects\n5. **Use tag resolution**: Take advantage of Cerebral's tag system for dynamic values\n\nCustom factories are a powerful way to create reusable logic in your Cerebral application while keeping your sequences clean and declarative.\n","title":"Custom Factories"},"reactions":{"raw":"# Reactions\n\nReactions allow you to respond to state changes outside the normal rendering flow. This guide covers common usage patterns for reactions in different scenarios.\n\n## When to Use Reactions\n\nReactions are useful for:\n\n- Running side effects when state changes\n- Triggering sequences in response to state updates\n- Managing focus, scroll position, or other DOM operations\n- Syncing state changes with external systems\n\n## Reactions in Modules\n\nWhen creating a module, you can attach reactions that respond to state changes:\n\n```js\n// modules/admin.js\nimport { Reaction } from 'cerebral'\nimport { state, sequences } from 'cerebral'\n\n// Define reactions in a separate file\nexport const watchPermissions = Reaction(\n { permissions: state`user.permissions` },\n ({ permissions, get }) => {\n if (!permissions.includes('admin')) {\n get(sequences`redirectToHome`)()\n }\n }\n)\n```\n\nThis pattern allows you to define side effects that run automatically when specific state changes.\n\n## Reactions in Components\n\nComponents often need to react to state changes for side effects like focus management:\n\n```js\nimport * as React from 'react'\nimport { connect } from '@cerebral/react'\nimport { state, sequences } from 'cerebral'\n\nexport default connect(\n {\n inputValue: state`form.inputValue`,\n changeInputValue: sequences`changeInputValue`\n },\n function FormInput({ inputValue, changeInputValue, reaction }) {\n const inputRef = React.useRef(null)\n\n // The reaction prop is provided by the connect HOC\n reaction(\n 'focusOnError', // Name for debugging\n { error: state`form.error` },\n ({ error }) => {\n if (error && inputRef.current) {\n inputRef.current.focus()\n }\n }\n )\n\n return (\n <input\n ref={inputRef}\n value={inputValue}\n onChange={(event) => changeInputValue({ value: event.target.value })}\n />\n )\n }\n)\n```\n\n## Advanced Usage Patterns\n\n### Computed-Based Reactions\n\nReactions can depend on computed values:\n\n```js\nimport { Reaction } from 'cerebral'\nimport { state } from 'cerebral'\n\n// Define a computed value\nexport const filteredItems = (get) => {\n const items = get(state`items`)\n const filter = get(state`filter`)\n return items.filter((item) => item.type === filter)\n}\n\n// Create app state with the computed\nconst appState = {\n items: [],\n filter: 'all',\n filteredItems\n}\n\n// Create a reaction to the computed\nexport const onFilteredItemsChange = Reaction(\n { filteredItems: state`filteredItems` },\n ({ filteredItems }) => {\n console.log('Filtered items changed:', filteredItems.length)\n }\n)\n```\n\n### Accessing Additional State\n\nReactions provide a `get` function to access state not declared in dependencies:\n\n```js\nReaction({ isLoggedIn: state`user.isLoggedIn` }, ({ isLoggedIn, get }) => {\n if (isLoggedIn) {\n // Only access these values when needed\n const username = get(state`user.name`)\n const permissions = get(state`user.permissions`)\n\n // Use the dynamically accessed values\n analytics.identify(username, { permissions })\n }\n})\n```\n\n### Reacting to Multiple State Changes\n\nA single reaction can depend on multiple state paths:\n\n```js\nReaction(\n {\n filter: state`items.filter`,\n sort: state`items.sort`,\n items: state`items.list`\n },\n ({ filter, sort, items }) => {\n // This runs when filter, sort, or items change\n const processed = processItems(items, filter, sort)\n localStorage.setItem('processedItems', JSON.stringify(processed))\n }\n)\n```\n","title":"Reactions"},"structure":{"raw":"# Structure\n\nAs your application grows, a consistent structure becomes essential. This guide builds on the organization concepts introduced in the [Organize guide](/docs/introduction/organize.html) and provides recommended patterns for large-scale applications.\n\n## Recommended File Structure\n\n```js\nsrc/\n main/ // Root module\n modules/ // Feature modules\n feature1/ // Each feature is its own module\n feature2/ // with the same structure\n ...\n actions.js // Shared actions\n factories.js // Custom action factories\n sequences.js // Shared sequences\n providers.js // Custom providers\n computeds.js // Computed state values\n reactions.js // Side effect handlers\n errors.js // Custom error types\n index.js // Main module definition\n controller.js // App initialization\n```\n\nThis structure organizes code primarily by type rather than by feature at the root level, with features encapsulated as modules. This approach balances cohesion and separation of concerns.\n\n## Module Structure\n\nEach module (including the main module and feature modules) follows the same structure pattern:\n\n### index.js\n\nThe index file defines the module and imports all its components:\n\n```js\nimport { Module } from 'cerebral'\nimport * as sequences from './sequences'\nimport * as providers from './providers'\nimport * as reactions from './reactions'\nimport * as errors from './errors'\nimport featureA from './modules/featureA'\nimport featureB from './modules/featureB'\n\nexport default Module({\n state: {\n // Module state\n isLoading: false\n },\n sequences,\n providers,\n signals: {}, // Legacy API, prefer sequences\n reactions,\n catch: [[errors.AuthError, sequences.handleAuthError]],\n modules: {\n featureA,\n featureB\n }\n})\n```\n\n### actions.js\n\nActions are pure functions that do a specific job:\n\n```js\n// Named function style\nexport function setUserData({ store, props }) {\n store.set('users.current', props.user)\n}\n\n// Arrow function style\nexport const fetchUserData = ({ http, props }) =>\n http\n .get(`/api/users/${props.userId}`)\n .then((response) => ({ user: response.data }))\n```\n\nGroup related actions in the same file. If you have many actions, consider creating subdirectories by domain:\n\n```js\nactions / auth.js // Authentication-related actions\nusers.js // User-related actions\nposts.js // Post-related actions\n```\n\n### sequences.js\n\nSequences combine actions and factories to create logical flows:\n\n```js\nimport { set, when } from 'cerebral/factories'\nimport { state, props } from 'cerebral'\nimport * as actions from './actions'\n\nexport const fetchUser = [\n set(state`users.isLoading`, true),\n actions.fetchUserData,\n set(state`users.current`, props`user`),\n set(state`users.isLoading`, false)\n]\n\nexport const loginUser = [\n set(state`auth.isAuthenticating`, true),\n actions.authenticateUser,\n when(props`success`),\n {\n true: [set(state`auth.isLoggedIn`, true), fetchUser],\n false: [set(state`auth.error`, props`error`)]\n },\n set(state`auth.isAuthenticating`, false)\n]\n```\n\nFor complex applications, consider organizing sequences by feature domain similar to actions.\n\n### factories.js\n\nCustom factories create reusable, configurable actions:\n\n```js\n// Debounce input changes\nexport const debounceInput = (time) => {\n return function debounceInput({ debounce, props, path }) {\n return debounce\n .shared('inputChange', time)\n .then(path.continue)\n .catch(path.discard)\n }\n}\n\n// Mark form fields as touched\nexport const touchField = (field) => {\n return function touchField({ store }) {\n store.set(`form.fields.${field}.touched`, true)\n }\n}\n```\n\n### providers.js\n\nDefine custom providers that expose services to your actions:\n\n```js\nexport const api = {\n get(url) {\n return fetch(url).then((response) => response.json())\n },\n post(url, data) {\n return fetch(url, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify(data)\n }).then((response) => response.json())\n }\n}\n\nexport const logger = (context) => {\n context.logger = {\n info(message) {\n console.log(`[INFO] ${message}`)\n },\n error(message, error) {\n console.error(`[ERROR] ${message}`, error)\n }\n }\n\n return context\n}\n```\n\n### reactions.js\n\nReactions respond to state changes with side effects:\n\n```js\nexport const saveUserSettingsToLocalStorage = ({ watch, get, controller }) => {\n // Trigger when user settings change\n watch(state`user.settings.**`, () => {\n const settings = get(state`user.settings`)\n localStorage.setItem('userSettings', JSON.stringify(settings))\n })\n\n // Ensure reaction is triggered once during initialization\n controller.on('initialized', () => {\n const storedSettings = localStorage.getItem('userSettings')\n if (storedSettings) {\n controller.getSequence('user.initializeSettings')({\n settings: JSON.parse(storedSettings)\n })\n }\n })\n}\n```\n\n### errors.js\n\nCustom error types for better error handling:\n\n```js\nimport { CerebralError } from 'cerebral'\n\nexport class AuthenticationError extends CerebralError {\n constructor(message, details) {\n super(message, details)\n this.name = 'AuthenticationError'\n }\n}\n\nexport class ValidationError extends CerebralError {\n constructor(message, fields) {\n super(message, { fields })\n this.name = 'ValidationError'\n }\n}\n```\n\n## Module Dependencies\n\nModules can access each other's state but should avoid directly calling sequences from other modules. Instead:\n\n1. **Use sequences from parent modules** to coordinate between child modules\n2. **Use state changes** to trigger reactions in other modules\n3. **Pass props between sequences** to share data\n\n```js\n// Parent module sequence\nexport const userSelectedPost = [\n set(state`posts.currentId`, props`postId`),\n set(state`posts.showDetails`, true),\n ...when(state`users.isLoaded`),\n {\n true: [],\n false: [\n // Load user data needed by posts module\n actions.loadUsers\n ]\n }\n]\n```\n\n## Scaling Tips\n\nAs your application grows:\n\n1. **Create deeper module hierarchies** for related features\n2. **Extract reusable logic** into shared modules\n3. **Standardize naming conventions** for actions, sequences, etc.\n4. **Document public module APIs** - which state and sequences are meant to be used by other modules\n5. **Use computed props** to derive complex state rather than duplicating logic\n6. **Split large files** when they exceed 300-400 lines\n7. **Consider code generation** for repetitive patterns\n\n## Example: Complete E-commerce Module\n\nHere's an example of a complete e-commerce feature module structure:\n\n```js\nsrc /\n main /\n modules /\n ecommerce /\n modules /\n cart / // Shopping cart submodule\n catalog / // Product catalog submodule\n checkout / // Checkout process submodule\n actions.js // Shared e-commerce actions\nsequences.js // Shared e-commerce sequences\nproviders.js // E-commerce specific API client\ncomputeds.js // E-commerce derived state\nerrors.js // E-commerce specific errors\nindex.js // E-commerce module definition\n```\n\nThis structure allows the e-commerce module to be self-contained while enabling interaction with other application features like authentication or user profiles.\n","title":"Structure"},"test":{"raw":"# Testing Cerebral Applications\n\nTesting is an essential part of building maintainable applications. Cerebral provides powerful tools for testing your application logic at different levels.\n\n## Snapshot Testing\n\nSnapshot testing allows you to create tests that verify the execution of sequences including state changes, provider calls, and action outputs. This approach is particularly useful for testing complex sequences with multiple actions and side effects.\n\n### Creating Snapshot Tests with the Debugger\n\nThe simplest way to create snapshot tests is directly from the Cerebral debugger:\n\n1. Run your application with the Cerebral debugger connected\n2. Trigger the sequence you want to test in your application\n3. Select the sequence in the debugger sidebar\n4. Click \"Create sequence test\" button\n5. Paste the generated code into your test file\n\nThis generates a test that will verify the exact execution flow of your sequence, including:\n\n- Actions executed\n- State mutations\n- Provider method calls\n- Paths taken\n\n### Writing Snapshot Tests Manually\n\nYou can also write snapshot tests manually to have more control over the test setup:\n\n```js\nimport { Snapshot } from 'cerebral/test'\nimport main from './main'\n\ndescribe('User authentication', () => {\n it('should authenticate user with valid credentials', () => {\n return (\n Snapshot(main)\n // Set up the initial state\n .mutate('set', 'auth.isLoading', false)\n .mutate('set', 'auth.error', null)\n\n // Mock API responses\n .mockResolvedPromise('http.post', {\n token: 'valid-jwt-token',\n user: { id: '123', name: 'Test User' }\n })\n\n // Run the sequence with test data\n .run('auth.login', {\n username: 'testuser',\n password: 'password123'\n })\n .then((snapshot) => {\n // Verify execution against stored snapshot\n expect(snapshot.get()).toMatchSnapshot()\n })\n )\n })\n\n it('should handle authentication failures', () => {\n return (\n Snapshot(main)\n .mutate('set', 'auth.isLoading', false)\n .mutate('set', 'auth.error', null)\n\n // Mock API error response\n .mockRejectedPromise('http.post', {\n statusCode: 401,\n message: 'Invalid credentials'\n })\n\n .run('auth.login', {\n username: 'testuser',\n password: 'wrongpassword'\n })\n .then((snapshot) => {\n expect(snapshot.get()).toMatchSnapshot()\n })\n )\n })\n})\n```\n\n### Understanding Snapshot Tests\n\nWhen a snapshot test runs, it:\n\n1. Creates a clean instance of your application\n2. Sets up the initial state with any mutations you specify\n3. Prepares mock implementations for provider methods\n4. Runs the specified sequence with your provided props\n5. Records all state mutations, provider calls, and action executions\n6. Compares this execution record against the stored snapshot\n\nIf your sequence implementation changes in a way that affects the execution flow, the test will fail, prompting you to review the changes.\n\n## Unit Testing\n\nWhile snapshot tests verify the entire execution flow, unit tests focus on testing individual parts of your application.\n\n### Testing Actions\n\nActions are pure functions that can be tested in isolation:\n\n```js\nimport { runAction } from 'cerebral/test'\nimport { submitForm } from './actions'\n\ndescribe('Form actions', () => {\n it('should validate form data', async () => {\n const { state, output } = await runAction(submitForm, {\n state: {\n form: {\n name: '',\n email: 'invalid',\n terms: false\n }\n }\n })\n\n expect(output.isValid).toBe(false)\n expect(state.form.errors.name).toBe('Name is required')\n expect(state.form.errors.email).toBe('Invalid email format')\n expect(state.form.errors.terms).toBe('You must accept the terms')\n })\n})\n```\n\n### Testing Computed Values\n\nComputed values can be tested with the `runCompute` helper:\n\n```js\nimport { runCompute } from 'cerebral/test'\nimport { filteredUsers } from './computed'\n\ndescribe('User filtering', () => {\n it('should filter active users', () => {\n const users = runCompute(filteredUsers, {\n state: {\n users: [\n { id: 1, name: 'User 1', active: true },\n { id: 2, name: 'User 2', active: false },\n { id: 3, name: 'User 3', active: true }\n ],\n filters: {\n showActive: true\n }\n }\n })\n\n expect(users.length).toBe(2)\n expect(users[0].id).toBe(1)\n expect(users[1].id).toBe(3)\n })\n\n it('should filter by search term', () => {\n const users = runCompute(filteredUsers, {\n state: {\n users: [\n { id: 1, name: 'John Doe', active: true },\n { id: 2, name: 'Jane Smith', active: true },\n { id: 3, name: 'Bob Johnson', active: true }\n ],\n filters: {\n searchTerm: 'jo'\n }\n }\n })\n\n expect(users.length).toBe(2)\n expect(users[0].name).toBe('John Doe')\n expect(users[1].name).toBe('Bob Johnson')\n })\n})\n```\n\n### Testing Sequences\n\nTest complete sequences with the `runSequence` helper:\n\n```js\nimport { runSequence } from 'cerebral/test'\nimport { fetchUsers } from './sequences'\n\ndescribe('User sequences', () => {\n it('should load and filter users', async () => {\n // Mock providers if needed\n const mockHttp = {\n get: () =>\n Promise.resolve({\n users: [\n { id: 1, name: 'User 1', role: 'admin' },\n { id: 2, name: 'User 2', role: 'user' }\n ]\n })\n }\n\n const { state } = await runSequence(fetchUsers, {\n state: {\n users: {\n list: [],\n isLoading: false,\n filter: 'admin'\n }\n },\n props: { pageSize: 10 },\n providers: { http: mockHttp }\n })\n\n expect(state.users.isLoading).toBe(false)\n expect(state.users.list.length).toBe(1)\n expect(state.users.list[0].role).toBe('admin')\n })\n})\n```\n\n### Testing Multiple Sequences\n\nThe `CerebralTest` factory is useful for testing sequences that build upon each other:\n\n```js\nimport { CerebralTest } from 'cerebral/test'\nimport app from './app'\n\ndescribe('Shopping cart', () => {\n let cerebral\n\n beforeEach(() => {\n // Create fresh test environment for each test\n cerebral = CerebralTest(app, {\n recordActions: 'byName'\n })\n\n // Set initial state\n cerebral.setState('cart', { items: [], total: 0 })\n })\n\n it('should manage shopping cart operations', async () => {\n // Add first item\n await cerebral.runSequence('cart.addItem', {\n product: { id: 1, name: 'Product 1', price: 10 },\n quantity: 2\n })\n\n expect(cerebral.getState('cart.items').length).toBe(1)\n expect(cerebral.getState('cart.total')).toBe(20)\n\n // Add second item\n await cerebral.runSequence('cart.addItem', {\n product: { id: 2, name: 'Product 2', price: 15 },\n quantity: 1\n })\n\n expect(cerebral.getState('cart.items').length).toBe(2)\n expect(cerebral.getState('cart.total')).toBe(35)\n\n // Remove item\n await cerebral.runSequence('cart.removeItem', { productId: 1 })\n\n expect(cerebral.getState('cart.items').length).toBe(1)\n expect(cerebral.getState('cart.total')).toBe(15)\n })\n})\n```\n\n## Testing Components\n\nWhen testing components that use Cerebral, you'll often want to verify that:\n\n1. Components correctly access state and computed values\n2. Components trigger the right sequences with the right props\n\n### React Components\n\n```js\nimport React from 'react'\nimport { mount } from 'enzyme'\nimport App from 'cerebral'\nimport { Container } from '@cerebral/react'\nimport UserList from './UserList'\n\ndescribe('UserList component', () => {\n it('should render users and handle pagination', () => {\n // Create test module with necessary state and mocked sequences\n const testModule = {\n state: {\n users: [\n { id: 1, name: 'User 1' },\n { id: 2, name: 'User 2' }\n ],\n currentPage: 1,\n totalPages: 3\n },\n sequences: {\n pageChanged: jest.fn()\n }\n }\n\n const app = App(testModule)\n\n const wrapper = mount(\n <Container app={app}>\n <UserList />\n </Container>\n )\n\n // Verify rendering\n expect(wrapper.find('.user-item').length).toBe(2)\n expect(wrapper.find('.pagination').exists()).toBe(true)\n\n // Trigger sequence\n wrapper.find('.next-page').simulate('click')\n\n // Verify sequence was called with correct props\n expect(testModule.sequences.pageChanged).toHaveBeenCalledWith(\n expect.objectContaining({ page: 2 })\n )\n })\n})\n```\n\n## Best Practices\n\n1. **Use snapshot tests for complex flows** - Snapshot tests are ideal for testing integration points and complex sequences.\n\n2. **Use unit tests for logic** - Test individual actions and computed values with unit tests for faster feedback.\n\n3. **Mock providers consistently** - Establish patterns for mocking provider methods to ensure consistent test behavior.\n\n4. **Test both success and error paths** - Ensure your tests cover error scenarios as well as happy paths.\n\n5. **Test state transitions** - Verify that your application moves correctly between states, especially for features like authentication, forms, and wizards.\n\n6. **Isolate tests** - Make sure tests are isolated and don't depend on each other's state changes.\n\n7. **Consider using factories for test data** - Create factory functions that generate test data to keep tests DRY and maintainable.\n\nFor more details on the testing API, see the [Test API reference](/docs/api/test.html).\n","title":"Testing Cerebral Applications"},"typescript":{"raw":"# TypeScript with Cerebral\n\n```marksy\n<Warning>\n**IMPORTANT**: Even though Cerebral fully supports TypeScript, you might also want to consider Cerebral's successor, [Overmind](https://overmindjs.org), which was built from the ground up with TypeScript support and modern JavaScript features.\n</Warning>\n```\n\nCerebral provides comprehensive type safety for your application. You can add typing gradually, so let's take this step by step. You can stop at any level when you feel you have enough type safety for your needs.\n\n## Required: Setting Up Proxy Support\n\nCerebral uses proxies for type-safe state and sequences access. To set up these typed proxies, create a file called **app.cerebral.ts**:\n\n```marksy\n<Info>\nYou MUST use the [@cerebral/babel-plugin](https://www.npmjs.com/package/@cerebral/babel-plugin) package. This plugin transforms the typed proxies into template literal tags behind the scenes.\n</Info>\n```\n\n```ts\nimport * as cerebral from 'cerebral'\n\ntype State = {}\n\nexport const props = cerebral.props\nexport const state = cerebral.state as State\nexport const sequences = cerebral.sequences\nexport const moduleState = cerebral.moduleState\nexport const moduleSequences = cerebral.moduleSequences\n```\n\nIn your **tsconfig.json**, add paths to make importing this file easier:\n\n```json\n{\n \"compilerOptions\": {\n \"module\": \"es2015\",\n \"target\": \"es5\",\n \"jsx\": \"react\",\n \"lib\": [\"es6\", \"dom\"],\n \"baseUrl\": \"./src\",\n \"paths\": {\n \"app.cerebral\": [\"app.cerebral.ts\"]\n }\n },\n \"exclude\": [\"node_modules\"]\n}\n```\n\n## Step 1: Typing State\n\nCreate a **types.ts** file next to your modules to define your types:\n\n```ts\n// main/types.ts\nexport type State = {\n title: string\n isAwesome: boolean\n}\n```\n\n```marksy\n<Info>\nWhen you type your **sequences** and **computed** values this way, they automatically incorporate any new additions, making your type system self-updating.\n</Info>\n```\n\nUse this type in your module definition:\n\n```ts\n// main/index.ts\nimport { ModuleDefinition } from 'cerebral'\nimport { State } from './types'\nimport * as sequences from './sequences'\nimport * as computeds from './computeds'\nimport * as providers from './providers'\n\nconst state: State = {\n title: 'My project',\n isAwesome: computeds.isAwesome\n}\n\nconst module: ModuleDefinition = {\n state,\n sequences,\n computeds,\n providers\n}\n\nexport default module\n```\n\nFor your computed values:\n\n```ts\nimport { state } from 'cerebral'\n\n// Computed are just functions that receive a 'get' parameter\nexport const isAwesome = (get) => {\n const value = get(state.isAwesome)\n return value + '!!!'\n}\n```\n\nIn your **app.cerebral.ts**, compose state from all modules:\n\n```ts\nimport * as cerebral from 'cerebral'\nimport * as Main from './main/types'\nimport * as ModuleA from './main/modules/moduleA/types'\n\ntype State = Main.State & {\n moduleA: ModuleA.State\n}\n\nexport const props = cerebral.props\nexport const state = cerebral.state as State\nexport const sequences = cerebral.sequences\nexport const moduleState = cerebral.moduleState\nexport const moduleSequences = cerebral.moduleSequences\n```\n\nWhen using module-specific proxies, cast them to the appropriate type:\n\n```ts\n// main/sequences.ts\nimport { moduleState as moduleStateProxy } from 'app.cerebral'\nimport { State } from './types'\n\nconst moduleState = moduleStateProxy as State\n```\n\n## Step 2: Typing Sequences (Declarative Approach)\n\nThe simplest way to type sequences is with the `sequence` factory, which provides typing for execution:\n\n```ts\nimport { sequence } from 'cerebral/factories'\n\n// Simple sequence\nexport const mySequence = sequence(actions.myAction)\n\n// Sequence with multiple actions\nexport const myOtherSequence = sequence([\n actions.someAction,\n actions.someOtherAction\n])\n\n// With typed props\nexport const sequenceWithProps = sequence<{ foo: string }>(actions.myAction)\n```\n\n```marksy\n<Warning>\nThis approach doesn't provide complete type checking for props passed between actions. However, it maintains declarative syntax while providing input typing and debugger support, which is often the best balance.\n</Warning>\n```\n\nAdd sequence types to your module's types file:\n\n```ts\n// main/types.ts\nimport * as sequences from './sequences'\n\nexport type State = {\n title: string\n isAwesome: boolean\n}\n\nexport type Sequences = {\n [key in keyof typeof sequences]: (typeof sequences)[key]\n}\n```\n\n## Step 3: Typing React Components\n\nWhen using React with Cerebral, there are several ways to type your components:\n\n### Function Components with Dependencies\n\n```ts\nimport { state, sequences } from 'app.cerebral'\nimport { connect, ConnectedProps } from '@cerebral/react'\n\nconst deps = {\n foo: state.foo,\n onClick: sequences.onClick\n}\n\nexport const MyComponent: React.FC<typeof deps & ConnectedProps> = ({\n foo,\n bar,\n onClick\n}) => {\n return <div onClick={() => onClick()}>{foo}</div>\n}\n\nexport default connect(deps, MyComponent)\n```\n\n### Class Components with Dependencies\n\n```ts\nimport { state, sequences } from 'app.cerebral'\nimport { connect, ConnectedProps } from '@cerebral/react'\n\nconst deps = {\n foo: state.foo,\n onClick: sequences.onClick\n}\n\nclass MyComponent extends React.Component<typeof deps & ConnectedProps> {\n render() {\n const { foo, bar, onClick } = this.props\n return <div onClick={() => onClick()}>{foo}</div>\n }\n}\n\nexport default connect(deps, MyComponent)\n```\n\n### Components with External Props\n\n```ts\nimport { state, sequences } from 'app.cerebral'\nimport { connect, ConnectedProps } from '@cerebral/react'\n\ntype Props = {\n external: string\n}\n\nconst deps = {\n foo: state.foo,\n onClick: sequences.onClick\n}\n\nexport const MyComponent: React.FC<Props & typeof deps & ConnectedProps> = ({\n external,\n foo,\n bar,\n onClick\n}) => {\n return <div onClick={() => onClick()}>{external}: {foo}</div>\n}\n\nexport default connect<Props>(deps, MyComponent)\n```\n\n### Dynamic Dependencies\n\nIf you need more flexibility, you can use dynamic dependencies:\n\n```ts\nimport { state, sequences } from 'app.cerebral'\nimport { connect, ConnectedProps } from '@cerebral/react'\n\nconst MyComponent: React.FC<ConnectedProps> = ({ get }) => {\n const foo = get(state.foo)\n const onClick = get(sequences.onClick)\n\n return <div onClick={() => onClick()}>{foo}</div>\n}\n\nexport default connect(MyComponent)\n```\n\n## Step 4: Typing Actions and Providers\n\nType your custom providers and add them to your context:\n\n```ts\n// main/providers.ts\nexport const myProvider = {\n get(value: string) {\n return value\n }\n}\n```\n\n```ts\n// main/types.ts\nimport * as providers from './providers'\n\nexport type Providers = {\n [key in keyof typeof providers]: (typeof providers)[key]\n}\n```\n\nUpdate your app.cerebral.ts:\n\n```ts\nimport * as cerebral from 'cerebral'\nimport * as Main from './main/types'\n\ntype State = Main.State\n\ntype Providers = Main.Providers\n\nexport type Context<Props = {}> = cerebral.IContext<Props> & Providers\n\nexport const props = cerebral.props\nexport const state = cerebral.state as State\n// ...other exports\n```\n\nNow you can type actions with your context:\n\n```ts\nimport { Context } from 'app.cerebral'\n\nexport function myAction({ store, myProvider }: Context) {\n // Fully typed context\n}\n\n// With props\nexport function actionWithProps({ store, props }: Context<{ foo: string }>) {\n // Typed props\n}\n```\n\n## Step 5: Advanced Sequence Typing (Chain API)\n\nFor complete type safety in sequences, you can use the chaining API. While less declarative, it provides full type checking:\n\n```ts\n// In app.cerebral.ts\nexport type Context<Props = {}> = cerebral.IContext<Props> & Providers\nexport type BranchContext<Paths, Props = {}> = cerebral.IBranchContext<\n Paths,\n Props\n> &\n Providers\n\nexport const Sequence = cerebral.ChainSequenceFactory<Context>()\nexport const SequenceWithProps =\n cerebral.ChainSequenceWithPropsFactory<Context>()\n```\n\nDefine sequences with full type safety:\n\n```ts\nimport { Sequence, SequenceWithProps, state } from 'app.cerebral'\nimport * as actions from './actions'\n\nexport const doThis = Sequence((sequence) =>\n sequence\n .action(actions.doSomething)\n .action('doSomethingElse', ({ store }) => store.set(state.foo, 'bar'))\n)\n\nexport const doThat = SequenceWithProps<{ foo: string }>((sequence) =>\n sequence.action('doThisThing', ({ store, props }) =>\n store.set(state.foo, props.foo)\n )\n)\n```\n\n```marksy\n<Info>\nThis approach automatically infers props as they're passed between actions, ensuring type safety throughout the entire sequence flow, even when composing sequences together.\n</Info>\n```\n\nFor conditional logic, use branch:\n\n```ts\nexport const conditionalSequence = Sequence((sequence) =>\n sequence.branch(actions.checkCondition).paths({\n success: (sequence) => sequence.action(actions.onSuccess),\n error: (sequence) => sequence.action(actions.onError)\n })\n)\n```\n\nCompose sequences with:\n\n```ts\nexport const composedSequence = Sequence((sequence) =>\n sequence\n .sequence(sequences.someOtherSequence)\n .parallel([sequences.sequenceA, sequences.sequenceB])\n)\n```\n\nThe flow factories are integrated into the chaining API:\n\n```ts\nexport const delayedSequence = Sequence((sequence) =>\n sequence\n .delay(1000)\n .when(state.foo)\n .paths({\n true: (sequence) => sequence.action(actions.onTrue),\n false: (sequence) => sequence.action(actions.onFalse)\n })\n)\n```\n\nYou can improve action typing for paths:\n\n```ts\nimport { BranchContext } from 'app.cerebral'\n\nexport function checkCondition({\n path\n}: BranchContext<{\n success: { result: string }\n error: { error: Error }\n}>) {\n // Type-safe path execution\n}\n```\n\n## Summary\n\nTypeScript integration can be added incrementally to your Cerebral app:\n\n1. Set up the babel plugin and create your app.cerebral.ts file\n2. Define and compose state types\n3. Use the sequence factory for simple sequence typing\n4. Add connect with proper types to your components\n5. Type your context for actions and providers\n6. (Optional) Use the chain API for maximum type safety\n\nChoose the level of type safety that best fits your project's needs - you can start simple and add more as you go.\n","title":"TypeScript with Cerebral"}},"views":{"index":{"raw":"# @cerebral/angular\n\n[Angular](https://angular.dev) integration for Cerebral state management.\n\n## Installation\n\n```sh\nnpm install @cerebral/angular @angular/core @angular/platform-browser\n```\n\n## Usage\n\n### AppService\n\n`AppService` is the core service that creates and exposes the Cerebral controller to your Angular application.\n\n#### Setup in Angular App\n\n```ts\n// app.config.ts\nimport { ApplicationConfig } from '@angular/core'\nimport { provideAnimations } from '@angular/platform-browser/animations'\nimport { provideRouter } from '@angular/router'\nimport { AppService } from '@cerebral/angular'\nimport { SomeService } from './some.service'\nimport { routes } from './app.routes'\n\n// Create a Cerebral app factory\nexport function createCerebralApp(someService: SomeService) {\n return new AppService({\n state: {\n count: 0,\n items: []\n },\n sequences: {\n increment: [\n ({ store, get }) => store.set('count', get(state`count`) + 1)\n ],\n decrement: [({ store, get }) => store.set('count', get(state`count`) - 1)]\n },\n providers: {\n // Provide Angular services to Cerebral\n someService\n }\n })\n}\n\n// Provide AppService in your app config\nexport const appConfig: ApplicationConfig = {\n providers: [\n provideRouter(routes),\n provideAnimations(),\n {\n provide: AppService,\n useFactory: createCerebralApp,\n deps: [SomeService]\n }\n ]\n}\n```\n\n### Components\n\n#### Using the `connect` decorator\n\nThe `connect` decorator connects Cerebral state and sequences to your components:\n\n```ts\nimport {\n Component,\n ChangeDetectionStrategy,\n ChangeDetectorRef,\n Inject\n} from '@angular/core'\nimport { state, sequences } from 'cerebral'\nimport { connect, AppService, CerebralComponent } from '@cerebral/angular'\n\n@Component({\n selector: 'app-counter',\n standalone: true, // In Angular 19+ is this the default\n template: `\n <div>\n <h2>Count: {{ count }}</h2>\n <button (click)=\"increment()\">+</button>\n <button (click)=\"decrement()\">-</button>\n </div>\n `,\n // OnPush change detection is required for optimal performance\n changeDetection: ChangeDetectionStrategy.OnPush\n})\n@connect({\n count: state`count`,\n increment: sequences`increment`,\n decrement: sequences`decrement`\n})\nexport class CounterComponent extends CerebralComponent {\n // Properly declare connected properties with TypeScript\n count!: number\n increment!: () => void\n decrement!: () => void\n\n constructor(\n @Inject(ChangeDetectorRef) cdr: ChangeDetectorRef,\n @Inject(AppService) app: AppService\n ) {\n super(cdr, app)\n }\n}\n```\n\n#### Working with Parent/Child Components\n\nFor parent-child component relationships, ensure child components are properly imported:\n\n```ts\nimport {\n Component,\n ChangeDetectionStrategy,\n ChangeDetectorRef,\n Inject\n} from '@angular/core'\nimport { state, sequences } from 'cerebral'\nimport { connect, AppService, CerebralComponent } from '@cerebral/angular'\nimport { ChildComponent } from './child.component'\n\n@Component({\n selector: 'app-parent',\n standalone: true,\n imports: [ChildComponent],\n template: `\n <div>\n <h1>Parent</h1>\n <app-child [data]=\"items\"></app-child>\n <button (click)=\"addItem()\">Add Item</button>\n </div>\n `,\n // Change detection needs to be set to OnPush\n changeDetection: ChangeDetectionStrategy.OnPush\n})\n@connect({\n items: state`items`,\n addItem: sequences`addItem`\n})\nexport class ParentComponent extends CerebralComponent {\n items!: any[]\n addItem!: () => void\n\n constructor(\n @Inject(ChangeDetectorRef) cdr: ChangeDetectorRef,\n @Inject(AppService) app: AppService\n ) {\n super(cdr, app)\n }\n}\n```\n\n### Advanced Features\n\n#### Using Angular Services in Cerebral Sequences\n\nYou can inject Angular services into your Cerebral sequences using providers:\n\n```ts\n// app.config.ts\nimport { HttpClient } from '@angular/common/http'\nimport { ApplicationConfig } from '@angular/core'\nimport { AppService } from '@cerebral/angular'\n\nexport function createCerebralApp(httpClient: HttpClient) {\n return new AppService({\n state: {\n data: null,\n loading: false,\n error: null\n },\n sequences: {\n fetchData: [\n ({ store }) => store.set('loading', true),\n async ({ http }) => {\n try {\n const response = await http.get('/api/data').toPromise()\n return { response }\n } catch (error) {\n return { error }\n }\n },\n ({ store, props }) => {\n if (props.error) {\n store.set('error', props.error)\n } else {\n store.set('data', props.response)\n }\n store.set('loading', false)\n }\n ]\n },\n providers: {\n http: httpClient\n }\n })\n}\n\nexport const appConfig: ApplicationConfig = {\n providers: [\n {\n provide: AppService,\n useFactory: createCerebralApp,\n deps: [HttpClient]\n }\n ]\n}\n```\n\n#### Using Computed Values\n\nCerebral uses functions to compute derived state values:\n\n```ts\nimport { state } from 'cerebral'\nimport { AppService } from '@cerebral/angular'\n\n// 1. Define types for better type safety\ntype Item = { id: number; name: string }\ntype AppState = {\n items: Item[]\n filter: string\n filteredItems?: Item[]\n}\n\n// 2. Define the computed function\nconst filteredItems = (get: any) => {\n const items = get(state`items`)\n const filter = get(state`filter`)\n return items.filter((item: Item) =>\n filter ? item.name.includes(filter) : true\n )\n}\n\n// 3. Create your app with computed properties\nconst app = new AppService({\n state: {\n items: [],\n filter: '',\n filteredItems // Attach computed to state\n } as AppState,\n sequences: {\n // Define your sequences here\n }\n})\n\n// 4. Use in component\n@Component({\n standalone: true\n // ...component config\n})\n@connect({\n filteredItems: state`filteredItems` // Access via state tag or proxy\n})\nexport class ListComponent extends CerebralComponent {\n // Declare with proper type\n filteredItems!: Item[]\n}\n```\n\nComputed values automatically track their dependencies and only recalculate when necessary, improving performance for derived state.\n\n## Troubleshooting\n\n### Common Issues\n\n1. **State updates not reflected in components**\n - Make sure your component uses `ChangeDetectionStrategy.OnPush`\n - Check that you've extended `CerebralComponent`\n - Verify you're using `@Inject()` for dependency injection\n\n2. **TypeScript errors with connected properties**\n - Always declare connected properties with the correct type and `!` assertion\n - Example: `count!: number;`\n\n3. **Angular directives not working**\n - When using standalone components, remember to import needed directives\n - Example: `imports: [NgIf, NgFor, CommonModule]`\n\n4. **Component not updating after state change**\n - Check if you need to call `app.flush()` after sequence execution in tests\n - In components, sequences are automatically flushed\n","title":"@cerebral/angular"},"inferno":{"raw":"# @cerebral/inferno\n\n[Inferno](http://infernojs.org) view for Cerebral.\n\n## Install\n\n`npm install @cerebral/inferno inferno babel-plugin-inferno`\n\n## Container\n\n```js\nimport { render } from 'inferno'\nimport App from 'cerebral'\nimport { Container } from '@cerebral/inferno'\nimport AppComponent from './components/AppComponent'\nimport main from './main'\n\nconst app = App(main)\n\nrender(\n <Container app={app}>\n <AppComponent />\n </Container>,\n document.querySelector('#app')\n)\n```\n\n## connect\n\n### Function Component (Recommended)\n\n```js\nimport { connect } from '@cerebral/inferno'\nimport { state, sequences } from 'cerebral'\n\nexport default connect(\n {\n foo: state`foo`,\n onClick: sequences`onClick`\n },\n function MyComponent({ foo, onClick }) {\n return <div onClick={() => onClick()}>{foo}</div>\n }\n)\n```\n\n### Class Component\n\n```js\nimport { Component } from 'inferno'\nimport { connect } from '@cerebral/inferno'\nimport { state, sequences } from 'cerebral'\n\nexport default connect(\n {\n foo: state`foo`,\n onClick: sequences`onClick`\n },\n class MyComponent extends Component {\n render() {\n const { foo, onClick } = this.props\n return <div onClick={() => onClick()}>{foo}</div>\n }\n }\n)\n```\n\n### With Tag Properties\n\n```js\nimport { connect } from '@cerebral/inferno'\nimport { state, sequences } from 'cerebral'\n\nexport default connect(\n {\n foo: state`some.path.${state`someOtherPath`}`,\n bar: state`some.path.${props`propKey`}`,\n onClick: sequences`onClick`\n },\n function MyComponent({ foo, bar, onClick }) {\n return (\n <div onClick={() => onClick()}>\n {foo} - {bar}\n </div>\n )\n }\n)\n```\n","title":"@cerebral/inferno"},"preact":{"raw":"# @cerebral/preact\n\n[Preact](https://preactjs.com) view for Cerebral.\n\n## Install\n\n`npm install @cerebral/preact preact`\n\n## Container\n\n```js\nimport { h, render } from 'preact'\nimport App from 'cerebral'\nimport { Container } from '@cerebral/preact'\nimport AppComponent from './components/AppComponent'\nimport main from './main'\n\nconst app = App(main)\n\nrender(\n <Container app={app}>\n <AppComponent />\n </Container>,\n document.querySelector('#app')\n)\n```\n\n## connect\n\n### Function Component (Recommended)\n\n```js\nimport { h } from 'preact'\nimport { connect } from '@cerebral/preact'\nimport { state, sequences } from 'cerebral'\n\nexport default connect(\n {\n foo: state`foo`,\n onClick: sequences`onClick`\n },\n function MyComponent({ foo, onClick }) {\n return <div onClick={() => onClick()}>{foo}</div>\n }\n)\n```\n\n### Class Component\n\n```js\nimport { h, Component } from 'preact'\nimport { connect } from '@cerebral/preact'\nimport { state, sequences } from 'cerebral'\n\nexport default connect(\n {\n foo: state`foo`,\n onClick: sequences`onClick`\n },\n class MyComponent extends Component {\n render({ foo, onClick }) {\n return <div onClick={() => onClick()}>{foo}</div>\n }\n }\n)\n```\n","title":"@cerebral/preact"},"react":{"raw":"# @cerebral/react\n\n[React](https://react.dev) view integration for Cerebral state management.\n\n## Install\n\n```sh\nnpm install @cerebral/react react react-dom\n```\n\n## Container\n\nThe Container component connects your Cerebral app to your React components:\n\n```js\nimport React from 'react'\nimport { createRoot } from 'react-dom/client'\nimport App from 'cerebral'\nimport { Container } from '@cerebral/react'\nimport AppComponent from './components/App'\nimport main from './main'\n\nconst app = App(main)\n\nconst root = createRoot(document.querySelector('#app'))\nroot.render(\n <Container app={app}>\n <AppComponent />\n </Container>\n)\n```\n\n## connect\n\nThe `connect` function binds Cerebral state and sequences to your React components:\n\n### Function Components (Recommended)\n\n```js\nimport React from 'react'\nimport { state, sequences } from 'cerebral'\nimport { connect } from '@cerebral/react'\n\nexport default connect(\n {\n foo: state`foo`,\n onClick: sequences`onClick`\n },\n function MyComponent({ foo, onClick }) {\n return <div onClick={() => onClick()}>{foo}</div>\n }\n)\n```\n\n### Class Components\n\n```js\nimport React from 'react'\nimport { state, sequences } from 'cerebral'\nimport { connect } from '@cerebral/react'\n\nexport default connect(\n {\n foo: state`foo`,\n onClick: sequences`onClick`\n },\n class MyComponent extends React.Component {\n render() {\n const { foo, onClick } = this.props\n return <div onClick={() => onClick()}>{foo}</div>\n }\n }\n)\n```\n\n### Dynamic Dependencies\n\nYou can dynamically access cerebral state and sequences inside components without pre-declaring them:\n\n```js\nimport React from 'react'\nimport { state, sequences } from 'cerebral'\nimport { connect } from '@cerebral/react'\n\nexport default connect(function MyComponent({ get }) {\n const foo = get(state`foo`)\n const onClick = get(sequences`onClick`)\n\n return <div onClick={() => onClick()}>{foo}</div>\n})\n```\n\n### Props Transformer\n\nYou can transform props before they reach your component by adding a transformer function:\n\n```js\nimport React from 'react'\nimport { sequences, state } from 'cerebral'\nimport { connect } from '@cerebral/react'\n\nexport default connect(\n // 1. Define cerebral dependencies\n {\n foo: state`app.foo`,\n onClick: sequences`app.onClick`\n },\n // 2. Transform props before they reach the component\n (cerebralProps, componentProps, get) => {\n return {\n foo: `Label: ${cerebralProps.foo}`,\n onClick: () => cerebralProps.onClick({ id: componentProps.id })\n }\n },\n // 3. Your component receives the transformed props\n function App({ foo, onClick }) {\n return <div onClick={onClick}>{foo}</div>\n }\n)\n```\n\nThe transformer function takes three arguments:\n\n- **cerebralProps**: Values from your Cerebral state and sequences\n- **componentProps**: Props passed from parent components\n- **get**: Function to resolve Cerebral tags and computed values\n\n## TypeScript Support\n\nFor TypeScript users, you can add static typing to your connected components. See the [TypeScript documentation](/docs/advanced/typescript) for details.\n","title":"@cerebral/react"},"vue":{"raw":"# @cerebral/vue\n\n[Vue.js](https://vuejs.org) view for Cerebral.\n\n## Install\n\n`npm install @cerebral/vue vue`\n\n## Container\n\n```js\nimport { createApp } from 'vue'\nimport App from 'cerebral'\nimport { Container, connect } from '@cerebral/vue'\nimport main from './main'\nimport AppComponent from './AppComponent'\n\nconst app = App(main)\n\ncreateApp({\n components: {\n Container: Container(app),\n AppComponent\n },\n template: '<Container><AppComponent /></Container>'\n}).mount('#app')\n```\n\n## connect\n\n### _MyComponent.js_\n\n```js\nimport { connect } from '@cerebral/vue'\nimport { state, sequence } from 'cerebral'\n\nexport default connect(\n {\n foo: state`foo`,\n click: sequence`clicked`\n },\n {\n template: '<div @click=\"click\">{{foo}}</div>'\n }\n)\n```\n\n### _main.js_\n\n```js\nimport { createApp } from 'vue'\nimport App from 'cerebral'\nimport { Container, connect } from '@cerebral/vue'\nimport MyComponent from './MyComponent'\nimport main from './main'\n\nconst app = App(main)\n\ncreateApp({\n components: {\n Container: Container(app),\n 'my-component': MyComponent\n },\n template: '<Container><my-component /></Container>'\n}).mount('#app')\n```\n\n## Vue 2 Support\n\nFor Vue 2, you need to use the complete Vue build that includes the template compiler:\n\n```js\nimport Vue from 'vue/dist/vue'\nimport App from 'cerebral'\nimport { Container, connect } from '@cerebral/vue'\nimport main from './main'\nimport AppComponent from './AppComponent'\n\nconst app = App(main)\n\nnew Vue({\n render: (h) =>\n h({\n components: {\n Container: Container(app),\n AppComponent: AppComponent\n },\n template: '<Container><AppComponent /></Container>'\n })\n}).$mount('#app')\n```\n\nFor projects created with `vue-cli`, add this to your `vue.config.js`:\n\n```js\nmodule.exports = { runtimeCompiler: true }\n```\n","title":"@cerebral/vue"}},"guides":{"index":{"raw":"# HTTP Requests in Cerebral\n\nMost applications need to make HTTP requests to a server. Cerebral provides a flexible approach where you create custom providers using your preferred HTTP library. This gives you complete control over how HTTP requests are handled in your application.\n\n## Creating an HTTP Provider\n\n### Using Axios\n\n[Axios](https://github.com/axios/axios) is a popular HTTP client that works in both browser and Node.js environments.\n\n```js\nimport axios from 'axios'\n\nexport const http = {\n get: axios.get,\n post: axios.post,\n put: axios.put,\n patch: axios.patch,\n delete: axios.delete\n}\n```\n\nThis simple implementation exposes the axios methods directly. You can register it in your module:\n\n```js\nimport * as providers from './providers'\n\nexport default {\n // ...module definition\n providers\n}\n```\n\n### Using Fetch API\n\nThe native [Fetch API](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API) is available in all modern browsers and Node.js:\n\n```js\nexport const http = {\n async get(url, options = {}) {\n const response = await fetch(url, {\n method: 'GET',\n ...options\n })\n\n if (!response.ok) {\n throw new Error(`HTTP Error: ${response.status}`)\n }\n\n return response.json()\n },\n\n async post(url, data, options = {}) {\n const response = await fetch(url, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n ...options.headers\n },\n body: JSON.stringify(data),\n ...options\n })\n\n if (!response.ok) {\n throw new Error(`HTTP Error: ${response.status}`)\n }\n\n return response.json()\n }\n\n // Add other methods (put, patch, delete) similarly\n}\n```\n\n## Adding Default Configuration\n\nOne advantage of creating your own provider is the ability to add defaults and customizations:\n\n```js\nimport axios from 'axios'\n\n// Create axios instance with defaults\nconst client = axios.create({\n baseURL: 'https://api.example.com',\n timeout: 10000,\n headers: {\n 'Content-Type': 'application/json'\n }\n})\n\nexport const http = {\n get: client.get,\n post: client.post,\n put: client.put,\n patch: client.patch,\n delete: client.delete\n}\n```\n\n## Authentication and Tokens\n\nA common need is handling authentication tokens:\n\n```js\nexport const http = (() => {\n // Store token in closure\n let token = null\n\n // Create methods with token handling\n return {\n setToken(newToken) {\n token = newToken\n },\n\n async get(url, options = {}) {\n const headers = {\n ...(token ? { Authorization: `Bearer ${token}` } : {}),\n ...options.headers\n }\n\n return fetch(url, {\n method: 'GET',\n headers,\n ...options\n }).then((response) => {\n if (!response.ok) throw new Error(`HTTP Error: ${response.status}`)\n return response.json()\n })\n }\n\n // Implement other methods similarly\n }\n})()\n```\n\n## Using Local Storage for Tokens\n\nYou can combine providers to create more powerful abstractions:\n\n```js\nexport const localStorage = {\n get(key) {\n try {\n return JSON.parse(window.localStorage.getItem(key))\n } catch (e) {\n return null\n }\n },\n set(key, value) {\n window.localStorage.setItem(key, JSON.stringify(value))\n }\n}\n\nexport const http = {\n async get(url, options = {}) {\n // Access localStorage provider through context\n const token = this.context.localStorage.get('token')\n\n const headers = {\n ...(token ? { Authorization: `Bearer ${token}` } : {}),\n ...options.headers\n }\n\n // Continue with request...\n }\n\n // Other methods...\n}\n```\n\n## Error Handling\n\nRobust error handling is crucial for HTTP requests:\n\n```js\nimport { CerebralError } from 'cerebral'\n\n// Custom HTTP error class\nexport class HttpError extends CerebralError {\n constructor(message, status, data) {\n super(message)\n this.name = 'HttpError'\n this.status = status\n this.data = data\n }\n}\n\nexport const http = {\n async get(url, options = {}) {\n try {\n const response = await fetch(url, {\n method: 'GET',\n ...options\n })\n\n if (!response.ok) {\n const errorData = await response.json().catch(() => ({}))\n throw new HttpError(\n `Request failed with status ${response.status}`,\n response.status,\n errorData\n )\n }\n\n return response.json()\n } catch (error) {\n if (error instanceof HttpError) throw error\n\n throw new HttpError('Network error', 0, { originalError: error.message })\n }\n }\n\n // Other methods with similar error handling\n}\n```\n\nThen in your module:\n\n```js\nimport { HttpError } from './errors'\n\nexport default {\n // ...module definition\n catch: [[HttpError, sequences.handleHttpError]]\n}\n```\n\n## Domain-Specific API Providers\n\nFor larger applications, you might want to create domain-specific API providers instead of a generic HTTP provider:\n\n```js\nexport const usersApi = {\n async getUser(id) {\n const response = await fetch(`/api/users/${id}`)\n if (!response.ok) throw new Error('Failed to fetch user')\n return response.json()\n },\n\n async updateUser(id, data) {\n const response = await fetch(`/api/users/${id}`, {\n method: 'PUT',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify(data)\n })\n if (!response.ok) throw new Error('Failed to update user')\n return response.json()\n }\n\n // Other user-related operations\n}\n\nexport const postsApi = {\n async getPosts(filters = {}) {\n const queryString = new URLSearchParams(filters).toString()\n const response = await fetch(`/api/posts?${queryString}`)\n if (!response.ok) throw new Error('Failed to fetch posts')\n return response.json()\n }\n\n // Other post-related operations\n}\n```\n\nThis approach:\n\n1. Makes sequences more readable with explicit method names\n2. Centralizes API endpoint knowledge\n3. Allows for specialized error handling per domain\n4. Makes testing easier with more focused mocking\n\n## Testing HTTP Providers\n\nOne major advantage of providers is easy mocking during tests:\n\n```js\nimport main from './main'\nimport App from 'cerebral'\n\ndescribe('My sequence', () => {\n it('should handle successful response', () => {\n const mockHttp = {\n get: jest.fn().mockResolvedValue({ id: '123', name: 'Test User' })\n }\n\n const app = App(main, {\n providers: {\n http: mockHttp\n }\n })\n\n return app\n .getSequence('fetchUser')({ id: '123' })\n .then(() => {\n expect(mockHttp.get).toHaveBeenCalledWith('/api/users/123')\n expect(app.getState('currentUser.name')).toBe('Test User')\n })\n })\n\n it('should handle errors', () => {\n const mockHttp = {\n get: jest.fn().mockRejectedValue(new Error('Network failure'))\n }\n\n const app = App(main, {\n providers: {\n http: mockHttp\n }\n })\n\n return app\n .getSequence('fetchUser')({ id: '123' })\n .then(() => {\n expect(app.getState('error')).toBe('Network failure')\n })\n })\n})\n```\n\n## TypeScript Support\n\nIf you're using TypeScript, you can define interfaces for your HTTP provider:\n\n```typescript\n// HTTP provider types\ninterface HttpOptions {\n headers?: Record<string, string>\n timeout?: number\n signal?: AbortSignal\n [key: string]: any\n}\n\ninterface HttpProvider {\n get<T>(url: string, options?: HttpOptions): Promise<T>\n post<T>(url: string, data: any, options?: HttpOptions): Promise<T>\n put<T>(url: string, data: any, options?: HttpOptions): Promise<T>\n patch<T>(url: string, data: any, options?: HttpOptions): Promise<T>\n delete<T>(url: string, options?: HttpOptions): Promise<T>\n setToken(token: string | null): void\n}\n\n// Implementation\nexport const http: HttpProvider = {\n // Implementation...\n}\n```\n\n## Summary\n\nCreating custom HTTP providers in Cerebral 5 gives you:\n\n1. Complete control over HTTP request handling\n2. Flexibility to use your preferred HTTP library\n3. Ability to add application-specific functionality\n4. Better testability through easier mocking\n\nBy treating HTTP requests as side effects handled by providers, Cerebral maintains its clean separation of concerns while giving you the freedom to implement HTTP functionality in the way that best suits your application's needs.\n","title":"HTTP Requests in Cerebral"},"routing":{"raw":"# Routing\n\nThere are two different ways to approach routing in Cerebral applications. You can either handle routing in your view layer or treat URL changes as events that trigger sequences in your application. In this guide, we'll explore the \"Cerebral first\" approach to routing using the browser's native History API.\n\n## Cerebral First Router\n\nUsing the browser's built-in History API provides a reliable and future-proof approach to routing that doesn't depend on third-party libraries:\n\n```js\nimport { routeToRoot, routeToItems, routeToItem } from './sequences'\n\n// Route pattern matching utility\nfunction matchRoute(pattern, path) {\n const patternParts = pattern.split('/').filter(Boolean)\n const pathParts = path.split('/').filter(Boolean)\n\n if (patternParts.length !== pathParts.length) return false\n\n const params = {}\n\n for (let i = 0; i < patternParts.length; i++) {\n const patternPart = patternParts[i]\n const pathPart = pathParts[i]\n\n // Check for parameters (starting with :)\n if (patternPart.startsWith(':')) {\n const paramName = patternPart.slice(1)\n params[paramName] = pathPart\n } else if (patternPart !== pathPart) {\n return false\n }\n }\n\n return { match: true, params }\n}\n\nexport default ({ app }) => {\n // Store routes for matching\n const routes = new Map()\n\n // We create a route factory which takes the route url and\n // the sequences that should run when it triggers\n function route(url, sequence) {\n // Store our routes for the matcher function\n routes.set(url, (params, query) => {\n app.runSequence(url, sequence, { params, query })\n })\n\n // Return an action usable in components\n return ({ props }) => {\n const urlWithReplacedParams = Object.keys(props.params || {}).reduce(\n (currentUrl, param) => {\n return currentUrl.replace(`:${param}`, props.params[param])\n },\n url\n )\n\n // Add query string if provided\n let queryString = ''\n if (props.query) {\n const searchParams = new URLSearchParams()\n Object.entries(props.query).forEach(([key, value]) => {\n searchParams.append(key, value)\n })\n queryString = '?' + searchParams.toString()\n }\n\n // Navigate using History API\n const fullUrl = urlWithReplacedParams + queryString\n window.history.pushState(null, '', fullUrl)\n\n // Manually trigger route handling since pushState doesn't fire events\n handleRouteChange()\n }\n }\n\n // Function to handle route changes\n function handleRouteChange() {\n const path = window.location.pathname\n const query = {}\n\n // Parse query string\n const searchParams = new URLSearchParams(window.location.search)\n for (const [key, value] of searchParams.entries()) {\n query[key] = value\n }\n\n // Find matching route\n for (const [pattern, handler] of routes.entries()) {\n const result = matchRoute(pattern, path)\n\n if (result.match) {\n handler(result.params, query)\n break\n }\n }\n }\n\n // Set up route handling\n app.on('initialized', () => {\n // Handle initial route\n handleRouteChange()\n\n // Listen for popstate (back/forward navigation)\n window.addEventListener('popstate', handleRouteChange)\n })\n\n return {\n sequences: {\n routeToRoot: route('/', routeToRoot),\n routeToItems: route('/items', routeToItems),\n routeToItem: route('/items/:id', routeToItem)\n }\n }\n}\n```\n\nThis approach uses the browser's native History API and implements a simple pattern matching system. With this setup, you can trigger URL changes from components:\n\n```js\nimport { connect } from '@cerebral/react'\nimport { sequences } from 'cerebral'\n\nexport default connect(function MyComponent({ get }) {\n const routeToItem = get(sequences`routeToItem`)\n\n return (\n <button onClick={() => routeToItem({ params: { id: 123 } })}>\n Go to 123\n </button>\n )\n})\n```\n\n## Query Strings\n\nMany applications need query string support. You can easily add this:\n\n```js\nimport { routeToRoot, routeToItems, routeToItem } from './sequences'\n\n// Route pattern matching utility\nfunction matchRoute(pattern, path) {\n const patternParts = pattern.split('/').filter(Boolean)\n const pathParts = path.split('/').filter(Boolean)\n\n if (patternParts.length !== pathParts.length) return false\n\n const params = {}\n\n for (let i = 0; i < patternParts.length; i++) {\n const patternPart = patternParts[i]\n const pathPart = pathParts[i]\n\n // Check for parameters (starting with :)\n if (patternPart.startsWith(':')) {\n const paramName = patternPart.slice(1)\n params[paramName] = pathPart\n } else if (patternPart !== pathPart) {\n return false\n }\n }\n\n return { match: true, params }\n}\n\nexport default ({ app }) => {\n // Store routes for matching\n const routes = new Map()\n\n // We create a route factory which takes the route url and\n // the sequences that should run when it triggers\n function route(url, sequence) {\n // Store our routes for the matcher function\n routes.set(url, (params, query) => {\n app.runSequence(url, sequence, { params, query })\n })\n\n // Return an action usable in components\n return ({ props }) => {\n const urlWithReplacedParams = Object.keys(props.params || {}).reduce(\n (currentUrl, param) => {\n return currentUrl.replace(`:${param}`, props.params[param])\n },\n url\n )\n\n // Add query string if provided\n let queryString = ''\n if (props.query) {\n const searchParams = new URLSearchParams()\n Object.entries(props.query).forEach(([key, value]) => {\n searchParams.append(key, value)\n })\n queryString = '?' + searchParams.toString()\n }\n\n // Navigate using History API\n const fullUrl = urlWithReplacedParams + queryString\n window.history.pushState(null, '', fullUrl)\n\n // Manually trigger route handling since pushState doesn't fire events\n handleRouteChange()\n }\n }\n\n // Function to handle route changes\n function handleRouteChange() {\n const path = window.location.pathname\n const query = {}\n\n // Parse query string\n const searchParams = new URLSearchParams(window.location.search)\n for (const [key, value] of searchParams.entries()) {\n query[key] = value\n }\n\n // Find matching route\n for (const [pattern, handler] of routes.entries()) {\n const result = matchRoute(pattern, path)\n\n if (result.match) {\n handler(result.params, query)\n break\n }\n }\n }\n\n // Set up route handling\n app.on('initialized', () => {\n // Handle initial route\n handleRouteChange()\n\n // Listen for popstate (back/forward navigation)\n window.addEventListener('popstate', handleRouteChange)\n })\n\n return {\n sequences: {\n routeToRoot: route('/', routeToRoot),\n routeToItems: route('/items', routeToItems),\n routeToItem: route('/items/:id', routeToItem)\n }\n }\n}\n```\n\nYou can then use the `query` property in components:\n\n```js\nimport { connect } from '@cerebral/react'\nimport { sequences } from 'cerebral'\n\nexport default connect(function MyComponent({ get }) {\n const routeToItems = get(sequences`routeToItems`)\n\n return (\n <button onClick={() => routeToItems({ query: { limit: 10 } })}>\n Go to items with limit\n </button>\n )\n})\n```\n\n## Managing Nested Routes\n\nWhen you have two routes such as `/items` and `/items/:id`, you might want to run the logic for the first route when the second is triggered. This is often useful when you show a page with items and the ID indicates which modal should be displayed on top of the list.\n\nThe two routes are defined separately:\n\n```js\n{\n sequences: {\n routeToRoot: route('/', routeToRoot),\n routeToItems: route('/items', routeToItems),\n routeToItem: route('/items/:id', routeToItem)\n }\n}\n```\n\nTo ensure that the items logic runs when `/items/:id` is triggered, you can compose the sequences:\n\n```js\nimport { set } from 'cerebral/factories'\nimport { state, props } from 'cerebral'\nimport * as actions from './actions'\n\nexport const routeToRoot = set(state`page`, 'home')\n\nexport const routeToItems = [\n set(state`page`, 'items'),\n actions.getItems,\n set(state`items`, props`items`)\n]\n\nexport const routeToItem = [\n routeToItems, // We include the parent route sequence\n actions.getItemDetails,\n set(state`itemDetails.${props`params.id`}`, props`itemDetails`),\n set(state`currentItemId`, props`params.id`)\n]\n```\n\nThis approach will first get the items and then get the specific item details. You can optimize this by running these operations in parallel:\n\n```js\nimport { parallel, set } from 'cerebral/factories'\nimport { state, props } from 'cerebral'\nimport * as actions from './actions'\n\nexport const routeToRoot = set(state`page`, 'home')\n\nexport const routeToItems = [\n set(state`page`, 'items'),\n actions.getItems,\n set(state`items`, props`items`)\n]\n\nexport const routeToItem = parallel([\n routeToItems,\n [\n actions.getItemDetails,\n set(state`itemDetails.${props`params.id`}`, props`itemDetails`),\n set(state`currentItemId`, props`params.id`)\n ]\n])\n```\n\nNow the app will immediately set the page, and both the items list and the item details will load in parallel. How you compose this depends on your data requirements and the user experience you want to create.\n\n## Alternative Routing Approaches\n\nIf you prefer a different approach to routing, here are some options:\n\n### 1. View-Library Specific Routers\n\nMost view libraries have their own routing solutions that remain actively maintained:\n\n- React: [React Router](https://reactrouter.com/)\n- Vue: [Vue Router](https://router.vuejs.org/)\n- Angular: [Angular Router](https://angular.io/guide/router)\n\nThese can be integrated with Cerebral by triggering sequences when routes change.\n\n### 2. Enhanced History API Router\n\nFor a more robust solution, you can extend the basic History API approach with additional features:\n\n```js\nexport default ({ app }) => {\n // Route configuration\n const routes = [\n {\n path: '/',\n sequence: 'routeToRoot'\n },\n {\n path: '/items',\n sequence: 'routeToItems'\n },\n {\n path: '/items/:id',\n sequence: 'routeToItem'\n }\n ]\n\n // Process route configuration\n const processedRoutes = routes.map((route) => ({\n ...route,\n parts: route.path.split('/').filter(Boolean),\n params: route.path\n .split('/')\n .filter((part) => part.startsWith(':'))\n .map((part) => part.slice(1))\n }))\n\n // Find matching route\n function findMatchingRoute(path) {\n const pathParts = path.split('/').filter(Boolean)\n\n for (const route of processedRoutes) {\n if (route.parts.length !== pathParts.length) continue\n\n let isMatch = true\n const params = {}\n\n for (let i = 0; i < route.parts.length; i++) {\n const routePart = route.parts[i]\n const pathPart = pathParts[i]\n\n if (routePart.startsWith(':')) {\n params[routePart.slice(1)] = pathPart\n } else if (routePart !== pathPart) {\n isMatch = false\n break\n }\n }\n\n if (isMatch) {\n return {\n route,\n params\n }\n }\n }\n\n return null\n }\n\n // Set up URL handling\n app.on('initialized', () => {\n // Handle route changes\n function handleRouteChange() {\n const path = window.location.pathname\n const query = Object.fromEntries(\n new URLSearchParams(window.location.search)\n )\n\n const match = findMatchingRoute(path)\n\n if (match) {\n app.getSequence(match.route.sequence)({\n params: match.params,\n query\n })\n } else {\n // Handle 404\n app.getSequence('routeNotFound')({ path })\n }\n }\n\n // Initial route\n handleRouteChange()\n\n // Listen for browser navigation\n window.addEventListener('popstate', handleRouteChange)\n })\n\n // Return module definition\n return {\n state: {\n currentPath: window.location.pathname,\n currentParams: {},\n currentQuery: {}\n },\n sequences: {\n navigateTo: ({ props, store }) => {\n const { path, params = {}, query = {} } = props\n\n // Build URL\n let resolvedPath = path\n Object.entries(params).forEach(([key, value]) => {\n resolvedPath = resolvedPath.replace(`:${key}`, value)\n })\n\n // Add query string\n const queryString =\n Object.keys(query).length > 0\n ? '?' + new URLSearchParams(query).toString()\n : ''\n\n // Update state\n store.set(state`router.currentPath`, resolvedPath)\n store.set(state`router.currentParams`, params)\n store.set(state`router.currentQuery`, query)\n\n // Update URL\n window.history.pushState(null, '', resolvedPath + queryString)\n }\n }\n }\n}\n```\n\n## Server-Side Rendering Integration\n\nThe route matching utilities we've created are usable in both browser and server environments, making them ideal for SSR. Here's how to integrate with the SSR approach described in the [SSR guide](/docs/guides/ssr.md):\n\n```js\n// Shared route matching utility (used on both client and server)\nexport function matchRoute(pattern, path) {\n const patternParts = pattern.split('/').filter(Boolean)\n const pathParts = path.split('/').filter(Boolean)\n\n if (patternParts.length !== pathParts.length) return false\n\n const params = {}\n\n for (let i = 0; i < patternParts.length; i++) {\n const patternPart = patternParts[i]\n const pathPart = pathParts[i]\n\n if (patternPart.startsWith(':')) {\n const paramName = patternPart.slice(1)\n params[paramName] = pathPart\n } else if (patternPart !== pathPart) {\n return false\n }\n }\n\n return { match: true, params }\n}\n\n// Server-side sequence to set up routing state\nexport const initializeRouteState = [\n ({ store, props }) => {\n // Store URL information from the request\n store.set(state`router.currentPath`, props.url)\n\n // Parse and store query parameters\n const query = {}\n if (props.query) {\n Object.entries(props.query).forEach(([key, value]) => {\n query[key] = value\n })\n }\n store.set(state`router.query`, query)\n\n // Match route and extract parameters\n const routes = [\n { path: '/', name: 'home' },\n { path: '/items', name: 'items' },\n { path: '/items/:id', name: 'item' }\n ]\n\n let matchedRoute = null\n let routeParams = {}\n\n for (const route of routes) {\n const result = matchRoute(route.path, props.url)\n if (result.match) {\n matchedRoute = route.name\n routeParams = result.params\n break\n }\n }\n\n // Store extracted parameters\n store.set(state`router.params`, routeParams)\n store.set(state`router.currentRoute`, matchedRoute)\n\n return {\n route: matchedRoute,\n params: routeParams\n }\n }\n // Next actions can load data based on route\n]\n```\n\nOn the server, in your Express route handler:\n\n```js\napp.get('*', async (req, res) => {\n // Create a fresh app instance\n const cerebral = UniversalApp(main)\n\n // Initialize routing state using the request URL\n await cerebral.runSequence(initializeRouteState, {\n url: req.path,\n query: req.query\n })\n\n // Load data based on route\n await cerebral.runSequence('loadRouteData')\n\n // Render...\n})\n```\n\nOn the client, this state will be hydrated automatically, and then your client-side router can work with the same state structure. This ensures a seamless handoff from server to client.\n\n## Advanced Routing Patterns\n\n### Route Guards\n\nYou can implement route guards by adding conditions to your route sequences:\n\n```js\nexport const routeToAdminArea = [\n ({ store, get, path }) => {\n const isAdmin = get(state`user.isAdmin`)\n\n return isAdmin ? path.authorized() : path.unauthorized()\n },\n {\n authorized: [set(state`page`, 'admin'), actions.loadAdminData],\n unauthorized: [\n set(state`page`, 'unauthorized'),\n set(state`redirectAfterLogin`, '/admin')\n ]\n }\n]\n```\n\n### Route Transitions\n\nFor smooth route transitions:\n\n```js\nexport const routeToItems = [\n // Start transition\n set(state`isTransitioning`, true),\n\n // Load data in parallel with transition\n parallel([\n [actions.getItems, set(state`items`, props`items`)],\n [wait(300)] // Minimum transition time\n ]),\n\n // Update page and finish transition\n set(state`page`, 'items'),\n set(state`isTransitioning`, false)\n]\n```\n\nRemember that Cerebral gives you complete control over these flows, making complex routing behavior manageable and declarative.\n","title":"Routing"},"ssr":{"raw":"# Server Side Rendering\n\nServer-side rendering (SSR) allows you to deliver pre-rendered HTML to users for faster initial page loads and better SEO. Cerebral provides robust support for SSR through the `UniversalApp` API.\n\n## Understanding the SSR Flow\n\nWhen a user visits your application, the typical SSR flow follows these steps:\n\n1. **Server receives request** - User requests a URL like `www.example.com`\n2. **Server initializes app state** - A Cerebral app instance is created with initial state\n3. **Server fetches necessary data** - Data required for the current route is fetched\n4. **Server renders HTML** - Components are rendered to HTML string using the populated state\n5. **Server returns HTML response** - HTML is sent to the browser with embedded state\n6. **Client hydrates the app** - Client-side JavaScript takes over without refetching data\n\n## Setting Up SSR with Cerebral\n\n### 1. Create a Universal App Instance\n\nOn your server, create a fresh `UniversalApp` instance for each request:\n\n```js\nimport { UniversalApp } from 'cerebral'\nimport main from '../client/main' // Your main module\n\napp.get('/', (req, res) => {\n // Create a new app instance for each request to prevent state leakage\n const cerebral = UniversalApp(main)\n\n // Continue with rendering...\n})\n```\n\n### 2. Populate Initial State\n\nFetch any necessary data and update the app state:\n\n```js\n// Run a sequence to fetch data and update state\nawait cerebral.runSequence(\n [\n // Fetch data based on the current route\n ({ http, props }) => {\n return http.get('/api/data').then((response) => ({ data: response.data }))\n },\n // Update state with the fetched data\n ({ store, props }) => {\n store.set(state`pageData`, props.data)\n }\n ],\n {\n // Initial props for the sequence\n route: req.path\n }\n)\n```\n\n### 3. Render Components to HTML\n\nUse your view library's server rendering method:\n\n```js\n// With React\nimport { renderToString } from 'react-dom/server'\nimport { Container } from '@cerebral/react'\nimport App from '../client/components/App'\n\n// Render the app to a string\nconst appHtml = renderToString(\n <Container app={cerebral}>\n <App />\n </Container>\n)\n```\n\n### 4. Generate State Hydration Script\n\nCreate a script tag containing the state changes to hydrate the client app:\n\n```js\n// Generate script tag with serialized state\nconst stateScript = cerebral.getScript()\n```\n\n### 5. Return Complete HTML Response\n\nCombine everything into a complete HTML response:\n\n```js\nres.send(`<!DOCTYPE html>\n<html>\n <head>\n <title>My Cerebral App</title>\n ${stateScript}\n </head>\n <body>\n <div id=\"root\">${appHtml}</div>\n <script src=\"/static/bundle.js\"></script>\n </body>\n</html>`)\n```\n\n### 6. Client-Side Hydration\n\nOn the client, use a regular `App` instance that will automatically pick up the state:\n\n```js\n// client.js\nimport React from 'react'\nimport { createRoot } from 'react-dom/client'\nimport App from 'cerebral'\nimport { Container } from '@cerebral/react'\nimport main from './main'\nimport AppComponent from './components/App'\n\n// Standard app - automatically picks up window.CEREBRAL_STATE\nconst app = App(main)\n\n// Hydrate the app\nconst root = createRoot(document.getElementById('root'))\nroot.render(\n <Container app={app}>\n <AppComponent />\n </Container>\n)\n```\n\n## Complete SSR Example\n\nThis example shows a complete Express server setup for SSR:\n\n```js\nimport express from 'express'\nimport path from 'path'\nimport React from 'react'\nimport { renderToString } from 'react-dom/server'\nimport { UniversalApp, state } from 'cerebral'\nimport { Container } from '@cerebral/react'\n\nimport main from '../client/main'\nimport App from '../client/components/App'\n\nconst server = express()\nconst PORT = process.env.PORT || 3000\n\n// Serve static files\nserver.use('/static', express.static(path.resolve(__dirname, '../dist')))\n\n// Handle all routes\nserver.get('*', async (req, res) => {\n try {\n // 1. Create fresh app instance\n const cerebral = UniversalApp(main)\n\n // 2. Run initialization sequence based on route\n await cerebral.runSequence(\n [\n ({ store, props }) => {\n // Store current URL\n store.set(state`currentPage`, props.url)\n\n // We could fetch data based on the route here\n // For example, if url is /users/123, fetch user with id 123\n if (props.url.startsWith('/users/')) {\n const userId = props.url.split('/')[2]\n return { userId }\n }\n },\n // Conditionally fetch data based on previous action's return value\n ({ http, props }) => {\n if (props.userId) {\n return http\n .get(`/api/users/${props.userId}`)\n .then((response) => ({ user: response.data }))\n }\n },\n // Store fetched data\n ({ store, props }) => {\n if (props.user) {\n store.set(state`currentUser`, props.user)\n }\n }\n ],\n {\n url: req.path\n }\n )\n\n // 3. Render the app\n const appHtml = renderToString(\n <Container app={cerebral}>\n <App />\n </Container>\n )\n\n // 4. Get state script for hydration\n const stateScript = cerebral.getScript()\n\n // 5. Send the response\n res.send(`<!DOCTYPE html>\n<html>\n <head>\n <title>Cerebral SSR Example</title>\n ${stateScript}\n </head>\n <body>\n <div id=\"root\">${appHtml}</div>\n <script src=\"/static/bundle.js\"></script>\n </body>\n</html>`)\n } catch (error) {\n console.error('SSR Error:', error)\n res.status(500).send('Server Error')\n }\n})\n\nserver.listen(PORT, () => {\n console.log(`Server running on port ${PORT}`)\n})\n```\n\n## Router Integration\n\nWhen implementing SSR with routing, you need to synchronize the server-side state with the current route:\n\n```js\nawait cerebral.runSequence(\n [\n ({ store, props }) => {\n // Initialize routing state based on the request URL\n store.set(state`router.currentPath`, props.url)\n store.set(state`router.query`, props.query)\n\n // Extract route parameters if needed\n const params = extractParamsFromUrl(props.url)\n store.set(state`router.params`, params)\n\n // Determine which data to load based on the route\n return {\n route: determineRoute(props.url),\n params\n }\n },\n // Load data based on the route\n ({ http, props }) => {\n switch (props.route) {\n case 'home':\n return http\n .get('/api/home-data')\n .then((response) => ({ pageData: response.data }))\n case 'user':\n return http\n .get(`/api/users/${props.params.id}`)\n .then((response) => ({ userData: response.data }))\n default:\n return { pageData: null }\n }\n },\n // Store loaded data\n ({ store, props }) => {\n if (props.pageData) {\n store.set(state`pageData`, props.pageData)\n }\n if (props.userData) {\n store.set(state`currentUser`, props.userData)\n }\n }\n ],\n {\n url: req.path,\n query: req.query\n }\n)\n```\n\nYour client-side router should be configured to read from and update this same state structure to ensure consistency between server and client rendering.\n\n## Performance Optimizations\n\nTo optimize your SSR implementation:\n\n1. **Use streaming where possible** - For large pages, consider streaming HTML response\n2. **Cache rendered content** - For static content, implement server-side caching\n3. **Selective hydration** - Only hydrate interactive parts of your page\n4. **Defer non-critical data loading** - Load non-essential data on the client\n\n## Common Challenges\n\n### 1. Browser-only APIs\n\nHandle APIs that only exist in the browser:\n\n```js\nfunction safelyUseWindow({ props }) {\n // Check if we're in a browser environment\n if (typeof window !== 'undefined') {\n window.localStorage.setItem('key', props.value)\n }\n}\n```\n\n### 2. Different environment configurations\n\nCreate environment-specific providers:\n\n```js\nconst localStorageProvider =\n typeof window !== 'undefined'\n ? {\n get(key) {\n return window.localStorage.getItem(key)\n },\n set(key, value) {\n window.localStorage.setItem(key, value)\n }\n }\n : {\n get() {\n return null\n },\n set() {}\n }\n\n// In your module\nexport default {\n providers: {\n storage: localStorageProvider\n }\n}\n```\n\n## Best Practices\n\n1. **Create a fresh app instance per request** - Prevents state leakage between users\n2. **Error handling** - Implement proper error boundaries and fallbacks\n3. **Track performance** - Measure and optimize render times\n4. **Selective SSR** - Consider which routes benefit most from SSR\n5. **Progressive enhancement** - Ensure your app works without JavaScript\n\n## Conclusion\n\nServer-side rendering with Cerebral gives you the benefits of fast initial page loads and SEO while maintaining the power and organization of a Cerebral application. By using the `UniversalApp` API, you can seamlessly share state between server and client.\n\nFor more detailed API information, refer to the [UniversalApp API documentation](/docs/api/universalapp.html).\n","title":"Server Side Rendering"}},"api":{"index":{"raw":"# Action\n\nActions are the building blocks of sequences in Cerebral. When actions run they receive a context object with tools and values to interact with your app.\n\n```js\nfunction myAction(context) {\n // Use context to access state, props, etc.\n}\n```\n\nThe context is populated by Cerebral and can be extended with custom **providers**. By default, Cerebral includes the following on the context:\n\n## Props\n\nWhen you trigger a sequence you can pass it a payload. This payload is the starting point of the props for the sequence:\n\n```js\n// Define a sequence with multiple actions\nconst mySequence = [actionA, actionB]\n\n// Trigger the sequence with initial props\nmySequence({\n foo: 'bar'\n})\n```\n\nThe first action will receive the payload passed to the sequence:\n\n```js\nfunction actionA({ props }) {\n props // {foo: \"bar\"}\n\n return {\n bar: 'baz'\n }\n}\n```\n\nBy returning an object, you extend the props for the next action:\n\n```js\nfunction actionB({ props }) {\n props // {foo: \"bar\", bar: \"baz\"}\n}\n```\n\nReturn values from actions, either directly or from a promise, are merged with existing props for subsequent actions to use.\n\n## Store\n\nTo change the state of your application, use the store API:\n\n```js\nfunction setSomething({ store }) {\n store.set('some.path.foo', 'bar')\n}\n```\n\nCerebral uses a direct mutation approach instead of relying on immutability patterns. Instead of first accessing a value and then operating on it, you specify the operation first and then the path to the value:\n\n```js\n// Traditional approach\nsomeArray.push('newItem')\n\n// With Cerebral\nstore.push('path.to.array', 'newItem')\n```\n\nThis approach enables Cerebral to:\n\n1. Track mutations for debugging\n2. Efficiently update components based on state changes\n3. Avoid the complexity of immutability patterns\n\nAvailable store methods include:\n\n- **set** - Set a value at a specific path\n- **concat** - Concatenate arrays\n- **increment** - Increase a number by a specified value\n- **merge** - Merge objects\n- **pop** - Remove the last item in an array\n- **push** - Add items to the end of an array\n- **shift** - Remove the first item in an array\n- **splice** - Remove or replace elements in an array\n- **toggle** - Toggle a boolean value\n- **unset** - Remove a property from an object\n- **unshift** - Add elements to the beginning of an array\n\n## Path\n\nThe path property is only available when your action is followed by a paths object:\n\n```js\nimport * as actions from './actions'\n\nexport default [\n actions.actionA,\n actions.actionB,\n {\n success: actions.actionC,\n error: actions.actionD\n }\n]\n```\n\nIn this example, only `actionB` has access to the path property, allowing it to choose between the \"success\" and \"error\" paths:\n\n```js\nfunction actionB({ path }) {\n // Choose which path to take\n if (someCondition) {\n return path.success()\n } else {\n return path.error()\n }\n}\n```\n\n## Get\n\nYou can access state, props, computed values, and sequences using the `get` function:\n\n```js\nimport { state, props } from 'cerebral'\n\nfunction someAction({ get }) {\n const foo = get(state`foo`) // Get state value\n const user = get(state`users.${props`userId`}`) // Dynamic path\n}\n```\n\n```marksy\n<Info>\nYou can also use object notation (like `state.foo`) with the [@cerebral/babel-plugin](/docs/api/proxy.html).\n</Info>\n```\n\n## Resolve\n\nThe `resolve` utility allows you to work with tags and values at a lower level. `get` is actually a wrapper around `resolve`:\n\n```js\nfunction someActionFactory(someArgument) {\n function someAction({ resolve }) {\n // Resolve any value, including tags\n const value = resolve.value(someArgument)\n\n // Check if an argument is a tag\n if (resolve.isTag(someArgument)) {\n // Get the path string of a tag\n const path = resolve.path(someArgument)\n }\n }\n\n return someAction\n}\n```\n\nThis is particularly useful when creating factories that accept a mix of static values and tags.\n","title":"Action"},"app":{"raw":"# App\n\nThe `App` function creates the main Cerebral controller instance that manages your application state and logic.\n\n## Initialization\n\n```js\nimport App from 'cerebral'\nimport main from './main' // The root module\n\nconst app = App(main, {\n // Options (all optional)\n devtools: null, // Debugger integration\n throwToConsole: true, // Log errors to console\n noRethrow: false, // Prevent rethrow of errors after handling\n stateChanges: {}, // Initial state changes\n returnSequencePromise: false, // Make sequences return promises\n hotReloading: false // Handle hot reloading\n})\n```\n\n### Options\n\n| Option | Type | Default | Description |\n| ----------------------- | ----------- | ------- | ---------------------------------------------------------- |\n| `devtools` | Object/null | null | [Debugger](/docs/introduction/debugger.html) configuration |\n| `throwToConsole` | Boolean | true | Log caught errors to console |\n| `noRethrow` | Boolean | false | Prevent rethrowing errors after handling them |\n| `stateChanges` | Object | {} | Initial state changes to apply |\n| `returnSequencePromise` | Boolean | false | Make sequence executions return promises |\n| `hotReloading` | Boolean | false | Enable smart merging for hot reloading |\n\n## State Management\n\n### getState\n\nGets state from a specific path.\n\n```js\n// Get value at path\nconst user = app.getState('user')\n\n// Get nested value\nconst username = app.getState('user.details.name')\n\n// Get full state tree\nconst fullState = app.getState()\n```\n\n### get\n\nGets a value using a tag.\n\n```js\nimport { state } from 'cerebral'\n\n// Get state using tag\nconst user = app.get(state`user`)\n\n// With dynamic path\nconst item = app.get(state`items.${itemId}`)\n```\n\n## Sequences\n\n### getSequence\n\nReturns a callable sequence from a specific path.\n\n```js\n// Get a sequence\nconst authenticate = app.getSequence('auth.authenticate')\n\n// Run the sequence with optional payload\nauthenticate({ username: 'user', password: 'pass' })\n```\n\n### getSequences\n\nReturns all sequences from a module.\n\n```js\n// Get all sequences from a module\nconst authSequences = app.getSequences('auth')\n\n// Run a sequence from the collection\nauthSequences.authenticate({ username: 'user' })\n```\n\n### runSequence\n\nRun an arbitrary sequence definition.\n\n```js\n// Run an inline sequence\napp.runSequence(\n 'myInlineSequence',\n [setLoading(true), fetchData, setLoading(false)],\n { id: 123 }\n)\n\n// Run a predefined sequence by path\napp.runSequence('auth.authenticate', {\n username: 'user',\n password: 'pass'\n})\n```\n\n## Module Management\n\n### addModule\n\nAdd a module after app initialization.\n\n```js\nimport analyticsModule from './modules/analytics'\n\n// Add a module at a specific path\napp.addModule('analytics', analyticsModule)\n\n// Add a nested module\napp.addModule('settings.theme', themeModule)\n```\n\n### removeModule\n\nRemove a module from the app.\n\n```js\n// Remove a module\napp.removeModule('analytics')\n\n// Remove a nested module\napp.removeModule('settings.theme')\n```\n\n## Other Methods\n\n### getModel\n\nReturns the model (state tree) of the app.\n\n```js\nconst model = app.getModel()\n```\n\n### flush\n\nTriggers UI updates based on state changes.\n\n```js\n// Standard flush\napp.flush()\n\n// Force flush regardless of changes\napp.flush(true)\n```\n\n## Events\n\nThe app instance is an event emitter that you can subscribe to:\n\n```js\n// Listen for a specific event\napp.on('flush', (changes) => {\n console.log('State changes:', changes)\n})\n\n// Remove an event listener\napp.off('flush', myListener)\n\n// Listen once for an event\napp.once('initialized', () => {\n console.log('App is ready!')\n})\n```\n\n### Lifecycle Events\n\n| Event | Arguments | Description |\n| ------------------- | -------------- | ------------------------------------- |\n| `initialized:model` | - | Model has initialized |\n| `initialized` | - | App has fully initialized |\n| `moduleAdded` | `path, module` | A module has been added |\n| `moduleRemoved` | `path, module` | A module has been removed |\n| `flush` | `changes` | State changes have been applied to UI |\n\n### Sequence Execution Events\n\n| Event | Arguments | Description |\n| ------------------ | -------------------------------------------------- | ---------------------------------- |\n| `start` | `execution, payload` | Sequence execution started |\n| `end` | `execution, payload` | Sequence execution ended |\n| `pathStart` | `execution, payload` | Path execution started |\n| `pathEnd` | `execution, payload` | Path execution ended |\n| `functionStart` | `execution, functionDetails, payload` | Action execution started |\n| `functionEnd` | `execution, functionDetails, payload, result` | Action execution ended |\n| `asyncFunction` | `execution, functionDetails, payload` | Async action executed |\n| `parallelStart` | `execution, payload, functionsToResolveCount` | Parallel execution started |\n| `parallelProgress` | `execution, payload, functionsStillResolvingCount` | Parallel execution progress update |\n| `parallelEnd` | `execution, payload, functionsExecutedCount` | Parallel execution ended |\n| `mutation` | `mutation` | State mutation occurred |\n| `remember` | `datetime` | Time travel occurred (debugging) |\n\n## Example\n\n```js\nimport App from 'cerebral'\nimport main from './main'\n\nconst app = App(main, {\n devtools: process.env.NODE_ENV === 'development' && {\n host: 'localhost:8585',\n reconnect: true\n }\n})\n\n// Wait for initialization\napp.once('initialized', () => {\n // App is ready to use\n app.getSequence('app.initialize')()\n\n // Listen for state changes\n app.on('flush', (changes) => {\n console.log('Changes:', changes)\n })\n})\n```\n\nSee [UniversalApp](/docs/api/universalapp.html) for server-side rendering support.\n","title":"App"},"computed":{"raw":"# Computed\n\nComputed values allow you to derive state from your application state. They calculate and cache derived values based on dependencies from the state tree.\n\n## API Reference\n\n### Creating Computed\n\n```js\nimport { state } from 'cerebral'\n\n// Basic computed function\nexport const fullName = (get) => {\n const firstName = get(state`user.firstName`)\n const lastName = get(state`user.lastName`)\n\n return `${firstName} ${lastName}`\n}\n\n// With conditional logic\nexport const userStatus = (get) => {\n const isLoggedIn = get(state`user.isLoggedIn`)\n\n if (isLoggedIn) {\n const lastActive = get(state`user.lastActive`)\n return lastActive > Date.now() - 3600000 ? 'active' : 'away'\n }\n\n return 'offline'\n}\n```\n\nThe computed function receives a `get` parameter which is used to retrieve values from the state tree. Using `get` automatically creates dependency tracking.\n\n### Using in State Tree\n\nComputed values are added directly to your state tree:\n\n```js\nimport { fullName, userStatus } from './computed'\n\nexport default {\n state: {\n user: {\n firstName: 'John',\n lastName: 'Doe',\n isLoggedIn: true,\n lastActive: Date.now()\n },\n fullName, // <-- Computed added to state\n userStatus // <-- Another computed\n }\n}\n```\n\n### Accessing Computed Values\n\nYou can access computed values just like regular state:\n\n```js\n// In an action\nfunction myAction({ get }) {\n const name = get(state`fullName`)\n // Do something with name\n}\n\n// In a component (React example)\nconnect(\n {\n name: state`fullName`,\n status: state`userStatus`\n },\n function User({ name, status }) {\n return (\n <div>\n <h1>{name}</h1>\n <span>Status: {status}</span>\n </div>\n )\n }\n)\n```\n\n### Using Props\n\nComputed values can access props when used in components:\n\n```js\nimport { state, props } from 'cerebral'\n\nexport const itemDetails = (get) => {\n const itemId = get(props`itemId`)\n const item = get(state`items.${itemId}`)\n\n return item || { name: 'Unknown item' }\n}\n```\n\nWhen used in a component:\n\n```js\n// React component\nconnect(\n {\n item: state`itemDetails`\n },\n function ItemDetails({ item }) {\n return <div>{item.name}</div>\n }\n)\n\n// Usage\n<ItemDetails itemId=\"123\" />\n```\n\nWhen used in an action, props must be passed explicitly:\n\n```js\nfunction myAction({ get }) {\n const item = get(state`itemDetails`, { itemId: '123' })\n}\n```\n\n### The `get` Function\n\nThe `get` function has these capabilities:\n\n1. **Retrieve state values**:\n\n ```js\n const user = get(state`user`)\n ```\n\n2. **Access paths**:\n\n ```js\n const path = get.path(state`user.settings`)\n // Returns 'user.settings'\n ```\n\n3. **Access other computed values**:\n\n ```js\n const name = get(state`fullName`)\n ```\n\n## Behavior\n\n```marksy\n<Info>\n- **Caching**: Computed values are cached and only recalculate when their dependencies change\n- **Composition**: Computed values can use other computed values\n- **Cloning**: When a computed uses props and is connected to a component, it's cloned for that instance\n- **Optimization**: The dependency tracking ensures minimal recalculations\n</Info>\n```\n\nFor more examples and advanced usage patterns, see the [advanced computed documentation](/docs/advanced/computed.html).\n","title":"Computed"},"devtools":{"raw":"# Devtools\n\nCerebral's devtools provide powerful debugging capabilities through integration with the Cerebral debugger application.\n\n## Configuration\n\n```js\nimport App from 'cerebral'\nimport Devtools from 'cerebral/devtools'\nimport main from './main'\n\nconst app = App(main, {\n devtools:\n process.env.NODE_ENV === 'production'\n ? null\n : Devtools({\n // Options here\n host: 'localhost:8585'\n })\n})\n```\n\n## Options\n\n| Option | Type | Default | Description |\n| -------------------------- | ------- | ------- | ------------------------------------------------------------------------------- |\n| `host` | String | `null` | **Required**. Host and port to connect to the debugger (e.g., 'localhost:8585') |\n| `https` | Boolean | `false` | Whether to use secure WebSockets (wss://) for connecting |\n| `reconnect` | Boolean | `true` | Whether to attempt reconnection if connection is lost |\n| `reconnectInterval` | Number | `5000` | Milliseconds to wait between reconnection attempts |\n| `storeMutations` | Boolean | `true` | Whether to store mutations for time travel debugging |\n| `preventExternalMutations` | Boolean | `true` | Warn when mutations occur outside of Cerebral actions |\n| `preventPropsReplacement` | Boolean | `false` | Prevent props with the same key being replaced in sequence |\n| `warnStateProps` | Boolean | `true` | Warn when passing objects/arrays as props to components |\n| `bigComponentsWarning` | Number | `10` | Show warning when components have more dependencies than this number |\n| `allowedTypes` | Array | `[]` | Additional types allowed to be stored in state (beyond basic JS types) |\n| `disableDebounce` | Boolean | `false` | Disable debouncing updates to the debugger |\n\n## Basic Setup\n\n```js\nimport App from 'cerebral'\nimport Devtools from 'cerebral/devtools'\nimport main from './main'\n\nlet devtools = null\n\nif (process.env.NODE_ENV !== 'production') {\n devtools = Devtools({\n host: 'localhost:8585'\n })\n}\n\nconst app = App(main, {\n devtools\n})\n```\n\n## Advanced Configuration\n\n### Type Safety\n\nBy default, Cerebral allows these types to be stored in state: Object, Array, String, Number, Boolean, File, FileList, Blob, ImageData, and RegExp. You can add additional types:\n\n```js\ndevtools = Devtools({\n host: 'localhost:8585',\n allowedTypes: [MyCustomType, AnotherType]\n})\n```\n\n### Securing Connections\n\nFor HTTPS sites, configure secure WebSocket connections:\n\n```js\ndevtools = Devtools({\n host: 'localhost:8585',\n https: true // Use wss:// instead of ws://\n})\n```\n\n### Debugging Large Applications\n\nFor large applications, you might want to adjust performance settings:\n\n```js\ndevtools = Devtools({\n host: 'localhost:8585',\n\n // Increase threshold for component complexity warnings\n bigComponentsWarning: 20,\n\n // Disable storing mutations for time travel if memory usage is a concern\n storeMutations: false\n})\n```\n\n## Debugger Features\n\n### Time Travel\n\nThe devtools enable time travel debugging by recording mutations when `storeMutations` is `true`. This allows you to:\n\n- Travel backward and forward in your application's history\n- See the exact state at any point in time\n- Understand the sequence of changes that led to a particular state\n\n### State Tree Visualization\n\nThe debugger provides a visual representation of your state tree, showing:\n\n- Current state values\n- Components watching each state path\n- Mutations in real-time\n\n### Sequence Tracing\n\nTrack sequence executions with:\n\n- Full action execution path\n- Input and output values for each action\n- Timing information\n\n### Component Debugging\n\nMonitor your components with:\n\n- State dependency mapping\n- Re-render tracking\n- Performance optimization suggestions\n\n## Events\n\nThe devtools emit and listen to several events:\n\n- `remember`: Emitted when time traveling to a specific point\n- `reset`: Emitted when the debugger state is reset\n- `changeModel`: Emitted when state is changed via the debugger\n\n## Environment Variables\n\nYou can control the devtools using environment variables:\n\n```js\ndevtools =\n process.env.NODE_ENV === 'production'\n ? null\n : process.env.CEREBRAL_DEBUGGER_OFF === 'true'\n ? null\n : Devtools({ host: 'localhost:8585' })\n```\n","title":"Devtools"},"error":{"raw":"# Error\n\nThe `CerebralError` class allows you to create custom error types that integrate with Cerebral's error handling system. Custom errors can be caught specifically in the module's `catch` configuration.\n\n## Creating Custom Errors\n\nExtend the `CerebralError` class to create custom error types:\n\n```js\nimport { CerebralError } from 'cerebral'\n\nexport class AuthenticationError extends CerebralError {\n constructor(message, details) {\n super(message, details)\n this.name = 'AuthenticationError' // Override the default name\n }\n}\n\n// With additional properties\nexport class ApiError extends CerebralError {\n constructor(message, statusCode, responseData) {\n super(message, { responseData }) // Details passed to parent\n this.name = 'ApiError'\n this.statusCode = statusCode // Custom property\n }\n}\n```\n\n## Error Properties\n\nThe `CerebralError` includes these properties:\n\n- `name`: Error type name, defaults to 'CerebralError' (should be overridden)\n- `message`: The error message\n- `details`: Optional object with additional error details\n- `stack`: Stack trace (automatically generated)\n\n## Throwing Errors\n\nThrow custom errors in actions or providers:\n\n```js\nimport { AuthenticationError } from './errors'\n\nfunction authenticate({ props }) {\n if (!props.token) {\n throw new AuthenticationError('Invalid token', {\n attempted: true,\n userId: props.userId\n })\n }\n\n // Continue with authentication\n}\n```\n\n## Catching Errors\n\nErrors are caught in the module's `catch` configuration:\n\n```js\nimport { AuthenticationError, ApiError } from './errors'\nimport * as sequences from './sequences'\n\nexport default {\n // ...module definition\n catch: [\n // Catch specific error types\n [AuthenticationError, sequences.handleAuthError],\n [ApiError, sequences.handleApiError],\n\n // Generic fallback (catches any Error)\n [Error, sequences.handleGenericError]\n ]\n}\n```\n\n## Error Handling Propagation\n\n```marksy\n<Info>\nIf a module doesn't catch an error, it propagates up to parent modules until caught or reaching the root. This enables centralized error handling for common errors while allowing specialized handling in specific modules.\n</Info>\n```\n\n## Serialization\n\n`CerebralError` instances can be serialized to JSON, which includes all properties except 'toJSON', 'execution', and 'functionDetails':\n\n```js\ntry {\n throw new ApiError('Failed to fetch data', 404, { error: 'Not found' })\n} catch (error) {\n console.log(JSON.stringify(error))\n // Output includes: name, message, details, statusCode, stack\n}\n```\n","title":"Error"},"factories":{"raw":"# Factories\n\nFactories are functions that create actions for you. They help you manipulate state and control execution flow with a declarative API.\n\n## State Manipulation Factories\n\nCerebral includes a set of factories for common state operations, all imported from 'cerebral/factories':\n\n```js\nimport { set, push, toggle } from 'cerebral/factories'\n```\n\n### concat\n\nConcatenate values to an array:\n\n```js\nconcat(state`list`, ['foo', 'bar'])\n```\n\n### increment\n\nIncrement a number by a specific value (default is 1):\n\n```js\n// Basic increment by 1\nincrement(state`counter`)\n\n// Increment by specific value\nincrement(state`counter`, 5)\n\n// Decrement using negative values\nincrement(state`counter`, -1)\n\n// Use values from state or props\nincrement(state`counter`, state`incrementBy`)\nincrement(state`counter`, props`amount`)\n```\n\n### merge\n\nMerge objects into existing values:\n\n```js\n// Simple merge\nmerge(state`user`, { name: 'John', age: 30 })\n\n// Merge with props\nmerge(state`user`, props`userData`)\n\n// Merge with dynamic keys\nmerge(state`clients.$draft`, {\n name: props`name`,\n email: props`email`\n})\n```\n\n### pop\n\nRemove and return last element from an array:\n\n```js\npop(state`items`)\n```\n\n### push\n\nAdd items to the end of an array:\n\n```js\npush(state`items`, 'new item')\npush(state`items`, props`newItem`)\n```\n\n### set\n\nSet a value in state or props:\n\n```js\n// Basic usage\nset(state`user.name`, 'John')\nset(props`redirect`, true)\n\n// Transform value before setting\nset(state`count`, props`count`, (value) => value * 2)\n```\n\n### shift\n\nRemove and return the first element from an array:\n\n```js\nshift(state`items`)\n```\n\n### splice\n\nModify an array by removing or replacing elements:\n\n```js\n// Remove 2 elements starting at index 0\nsplice(state`items`, 0, 2)\n\n// Remove 1 element at index 2 and insert new elements\nsplice(state`items`, 2, 1, 'new item', props`item2`)\n```\n\n### toggle\n\nToggle a boolean value:\n\n```js\ntoggle(state`menu.isOpen`)\n```\n\n### unset\n\nRemove a property from an object:\n\n```js\nunset(state`user.temporaryData`)\nunset(state`items.${props`itemKey`}`)\n```\n\n### unshift\n\nAdd elements to the beginning of an array:\n\n```js\nunshift(state`items`, 'new first item')\n```\n\n## Flow Control Factories\n\nThese factories help control the execution flow of your sequences.\n\n### debounce\n\nDelay execution of a path until a specified time has passed without another call:\n\n```js\nimport { debounce } from 'cerebral/factories'\n\n// Basic usage\n;[\n debounce(500), // Wait 500ms without new calls\n {\n continue: [actions.search], // Run when debounce completes\n discard: [] // Must be present - run when debounce is abandoned\n }\n]\n\n// Shared debounce across multiple sequences\nconst sharedDebounce = debounce.shared()\n\nexport const notifyUser = [\n set(state`notification`, props`message`),\n sharedDebounce(2000),\n {\n continue: [unset(state`notification`)],\n discard: []\n }\n]\n```\n\n```marksy\n<Warning>\nThe `discard` path must always be present when using debounce, even if you don't need to run any actions when debounce is abandoned.\n</Warning>\n```\n\n### equals\n\nBranch execution based on a value comparison:\n\n```js\n;(equals(state`user.role`),\n {\n admin: [actions.loadAdminPage],\n user: [actions.loadUserPage],\n otherwise: [actions.redirectToLogin]\n })\n```\n\n### wait\n\nPause execution for a specified time:\n\n```js\n// Simple waiting\n;[wait(500), actions.afterWaiting]\n\n// Within parallel execution\nparallel([\n [\n wait(500),\n {\n continue: [actions.afterWaiting]\n }\n ],\n actions.runInParallel\n])\n```\n\n### when\n\nConditionally choose a path based on a value or predicate:\n\n```js\n// With direct value\n;(when(state`user.isLoggedIn`),\n {\n true: [actions.redirectToDashboard],\n false: [actions.showLoginForm]\n })\n\n// With custom predicate\n;(when(state`user.role`, (role) => role === 'admin'),\n {\n true: [actions.showAdminTools],\n false: []\n })\n\n// With multiple arguments\n;(when(\n state`inputValue`,\n state`minLength`,\n (value, minLength) => value.length >= minLength\n),\n {\n true: [set(state`isValid`, true)],\n false: [set(state`isValid`, false)]\n })\n```\n\n## Sequence and Parallel\n\nThese factories help compose sequences and run actions in parallel:\n\n```js\nimport { sequence, parallel } from 'cerebral/factories'\n\n// Create a named sequence\nexport const mySequence = sequence('My Sequence', [\n actions.doSomething,\n set(state`foo`, 'bar')\n])\n\n// Run actions in parallel\nexport const loadData = [\n parallel('Load Data', [\n actions.loadUsers,\n actions.loadPosts,\n actions.loadSettings\n ]),\n set(state`isLoaded`, true)\n]\n```\n\nFor more detailed usage examples and advanced patterns, see the [factories guide](/docs/advanced/factories.html).\n","title":"Factories"},"module":{"raw":"# Module\n\nThe module is how you structure your application, it holds:\n\n```js\n{\n state,\n sequences,\n reactions,\n providers,\n catch,\n modules,\n}\n```\n\nYou instantiate your application with a root module:\n\n```js\nconst app = App({\n state: {}\n})\n```\n\nAnd you extend this root module with nested modules:\n\n```js\nconst app = App({\n state: {},\n modules: {\n moduleA,\n moduleB\n }\n})\n```\n\n## Module Properties\n\n### state\n\nThe state object contains the module's local state:\n\n```js\n{\n state: {\n users: [],\n currentUserId: null,\n isLoading: false\n }\n}\n```\n\nStates from child modules are merged into a single state tree.\n\n### sequences\n\nSequences define the logic flows of your application:\n\n```js\n{\n sequences: {\n loadUsers: [\n set(state`isLoading`, true),\n getUsers,\n set(state`users`, props`users`),\n set(state`isLoading`, false)\n ]\n }\n}\n```\n\n### reactions\n\nReactions let you respond to state changes:\n\n```js\nimport { Reaction } from 'cerebral'\n\n{\n reactions: {\n watchAuthentication: Reaction(\n { isAuthenticated: state`user.isAuthenticated` },\n ({ isAuthenticated, get }) => {\n if (isAuthenticated) {\n get(sequences`loadDashboard`)()\n }\n }\n )\n }\n}\n```\n\n### providers\n\nProviders expose functionality to your actions:\n\n```js\n{\n providers: {\n api: {\n getUsers() {\n return fetch('/api/users').then(res => res.json())\n }\n },\n logger: {\n log(message) {\n console.log(`[APP]: ${message}`)\n }\n }\n }\n}\n```\n\n### catch\n\nThe `catch` property handles errors that occur in the module's sequences:\n\n```js\nimport { ApiError, ValidationError } from './errors'\n\n{\n catch: [\n [ApiError, sequences.handleApiError],\n [ValidationError, sequences.handleValidationError],\n [Error, sequences.handleGenericError]\n ]\n}\n```\n\nEach handler is an array with:\n\n1. The error type to catch\n2. The sequence to run when that error type is caught\n\nErrors not caught by a module will propagate to parent modules.\n\n### modules\n\nChild modules are registered using the `modules` property:\n\n```js\nimport adminModule from './modules/admin'\nimport settingsModule from './modules/settings'\n\n{\n modules: {\n admin: adminModule,\n settings: settingsModule\n }\n}\n```\n\n## Creating Modules\n\nYou can define modules in two ways:\n\n### Object Definition\n\n```js\nconst myModule = {\n state: {\n isLoading: false,\n items: []\n },\n sequences: {\n loadItems: [\n set(state`isLoading`, true),\n getItems,\n set(state`items`, props`items`),\n set(state`isLoading`, false)\n ]\n }\n}\n```\n\n### Factory Function\n\nYou can also create modules using a function that receives information about the module:\n\n```js\nconst myModule = ({ name, path, app }) => ({\n state: {\n moduleName: name,\n modulePath: path\n },\n sequences: {\n initialize: [\n ({ store }) => {\n store.set(state`initialized`, true)\n }\n ]\n }\n})\n```\n\nThe module factory receives:\n\n- `name`: The module name\n- `path`: The full path to the module (e.g., \"app.settings\")\n- `app`: Reference to the app instance\n\n## Path Resolution\n\nModules create a hierarchy that affects how paths are resolved:\n\n```js\n// Root module\n{\n state: { title: 'My App' },\n modules: {\n users: {\n state: { list: [] }\n }\n }\n}\n```\n\nThis creates a state tree:\n\n```js\n{\n title: 'My App',\n users: {\n list: []\n }\n}\n```\n\nTo access the users list:\n\n```js\n// In components or actions\nget(state`users.list`)\n```\n\n## Module State Tag\n\nThe `moduleState` tag lets you refer to state within the current module:\n\n```js\nimport { moduleState } from 'cerebral'\n\nfunction toggleLoading({ store }) {\n // If in \"users\" module, this will toggle \"users.isLoading\"\n store.toggle(moduleState`isLoading`)\n}\n```\n\n## Error Handling\n\nErrors thrown in a module's sequences can be caught with the `catch` property:\n\n```js\ncatch: [\n [HttpError, sequences.handleHttpError],\n [ValidationError, sequences.handleValidationError],\n [Error, sequences.handleGenericError]\n]\n```\n\nIf an error isn't caught by the current module, it propagates up to parent modules until handled or reaching the root module.\n","title":"Module"},"providers":{"raw":"# Providers\n\nProviders expose functionality to your actions, typically for handling side effects. They are a core concept in Cerebral's architecture that helps you manage interactions with external systems in a structured way.\n\n## Creating Providers\n\nA provider is simply an object with methods:\n\n```js\nexport const myProvider = {\n doSomething() {\n return 'hello'\n },\n\n doSomethingAsync() {\n return fetch('/api/data').then((response) => response.json())\n }\n}\n```\n\n## Using the Provider Factory\n\nYou can use the `Provider` factory from Cerebral to create providers with additional options:\n\n```js\nimport { Provider } from 'cerebral'\n\nexport const myProvider = Provider({\n doSomething() {\n return 'hello'\n }\n})\n```\n\n## Adding Providers to Your App\n\nProviders are added to your application in your module definition:\n\n```js\nimport * as providers from './providers'\n\nexport default {\n state: {\n // Your state\n },\n providers\n}\n```\n\n## Accessing Context\n\nInside provider methods, you can access the full context using `this.context`:\n\n```js\nexport const myProvider = {\n triggerSequenceOnEvent(event, sequencePath) {\n // Access other providers or utilities through context\n const sequence = this.context.get(sequencePath)\n\n window.addEventListener(event, () => {\n sequence()\n })\n },\n\n getDataForUser(userId) {\n // Use another provider from within your provider\n return this.context.http.get(`/api/users/${userId}`)\n }\n}\n```\n\nThis approach keeps the API concise while allowing providers to work together.\n\n## Provider Options\n\nWhen using the Provider factory, you can pass options to control its behavior:\n\n```js\nexport const myProvider = Provider(\n {\n // Provider methods\n doSomething() {\n /* ... */\n }\n },\n {\n // Options\n wrap: false // Disable debugger wrapping for this provider\n }\n)\n```\n\n### Available Options\n\n- **wrap** (boolean): When `false`, disables debugger wrapping for this provider\n- **ignoreDefinition** (boolean): When `true`, skips verification of the provider definition\n\n## Function-Based Providers\n\nYou can also define providers as functions that receive the context and return an object:\n\n```js\nexport const myDynamicProvider = (context) => {\n // Create provider based on context\n return {\n doSomething() {\n return context.state.get('someValue')\n }\n }\n}\n```\n\nWhile this approach is more flexible, it prevents certain optimizations that are possible with object-based providers.\n\n## Debugger Integration\n\nBy default, all provider methods are automatically tracked by Cerebral's debugger, showing method calls, arguments, and return values. This helps you understand how side effects flow through your application.\n\n## Testing\n\nProviders are easy to mock in tests:\n\n```js\n// Mock provider for testing\nconst mockHttpProvider = {\n get: jest.fn().mockResolvedValue({ result: 'mocked data' }),\n post: jest.fn().mockResolvedValue({ success: true })\n}\n\nconst app = App(rootModule, {\n providers: {\n http: mockHttpProvider\n }\n})\n```\n\n## Using Providers in Actions\n\n```js\nexport const api = {\n getPosts() {\n return fetch('https://jsonplaceholder.typicode.com/posts').then(\n (response) => response.json()\n )\n },\n getUser(id) {\n return fetch(`https://jsonplaceholder.typicode.com/users/${id}`).then(\n (response) => response.json()\n )\n }\n}\n```\n\n```marksy\n<Info>\nInstead of creating a generic HTTP provider, we've built a specific API provider for JSONPlaceholder. This approach is recommended as it makes your application code more readable and focused.\n</Info>\n```\n","title":"Providers"},"proxy":{"raw":"# Proxy vs Tags\n\nCerebral offers two ways to target state, sequences, and props in your application:\n\n1. **Proxies**: A cleaner syntax that looks like normal object access (`state.foo`)\n2. **Tags**: Traditional template literals (`` state`foo` ``) that work without additional configuration\n\n## Proxies\n\nProxies provide a more natural syntax for accessing paths in your state and sequences. They require the [@cerebral/babel-plugin](https://www.npmjs.com/package/@cerebral/babel-plugin) which transforms them into template literal tags behind the scenes.\n\n```marksy\n<Info>\nThe proxy syntax is recommended for most applications as it's more readable and integrates better with TypeScript. Tags are still supported for compatibility and for projects that don't use Babel.\n</Info>\n```\n\n### Setup for Proxies\n\n1. Install the babel plugin:\n\n ```sh\n npm install --save-dev @cerebral/babel-plugin\n ```\n\n2. Add it to your Babel configuration:\n\n ```json\n {\n \"plugins\": [\"@cerebral\"]\n }\n ```\n\n## When to Use Which Approach\n\n- **Use Tags**: When you want to quickly get started without additional configuration or if you're not using Babel\n- **Use Proxies**: When you prefer the cleaner syntax or need full TypeScript support\n\n## State\n\n```js\nimport { state } from 'cerebral'\n\n// In action\nfunction myAction ({ store }) {\n store.set(state.foo, 'bar')\n}\n\n// Using tags (alternative without babel plugin)\nfunction myAction ({ store }) {\n store.set(state`foo`, 'bar')\n}\n\n// In factories\n[\n set(state.foo, 'bar'),\n when(state.isAwesome)\n]\n\n// In reaction\nReaction({\n foo: state.foo\n}, () => {})\n\n// In connect\nconnect({\n foo: state.foo\n}, ...)\n```\n\n## Sequences\n\n```js\nimport { sequences } from 'cerebral'\n\n// In action\nfunction myAction ({ get }) {\n const mySequence = get(sequences.mySequence)\n}\n\n// Using tags (alternative without babel plugin)\nfunction myAction ({ get }) {\n const mySequence = get(sequences`mySequence`)\n}\n\n// In factories\n[\n onMessage('some_channel', sequences.onMessage)\n]\n\n// In reaction\nReaction({\n foo: state.foo\n}, ({ foo, get }) => {\n get(sequences.mySequence)({ foo })\n})\n\n// In connect\nconnect({\n foo: state.foo,\n onClick: sequences.doThis\n}, ...)\n```\n\n## Computed\n\n```js\nimport { state } from 'cerebral'\n\n// Define a computed\nexport const filteredItems = (get) => {\n const items = get(state.items)\n const filter = get(state.filter)\n\n return items.filter(item => item[filter.property] === filter.value)\n}\n\n// In action\nfunction myAction({ get }) {\n const filteredResult = get(state.filteredItems)\n}\n\n// In factories\n[\n when(state.filteredItems)\n]\n\n// In reaction\nReaction({\n filteredItems: state.filteredItems\n}, ({ filteredItems }) => {})\n\n// Using tags (alternative without babel plugin)\nexport const filteredItems = (get) => {\n const items = get(state`items`)\n const filter = get(state`filter`)\n\n return items.filter(item => item[filter.property] === filter.value)\n}\n\nfunction myAction({ get }) {\n const filteredResult = get(state`filteredItems`)\n}\n\n// In components\nconnect({\n items: state.filteredItems\n}, ...)\n```\n\n## Props\n\n```js\nimport { props } from 'cerebral'\n\n// In factories\n[\n when(props.isCool)\n]\n\n// Using tags (alternative without babel plugin)\n[\n when(props`isCool`)\n]\n\n// In connect\nconnect({\n item: state.items[props.index]\n}, ...)\n```\n\n## ModuleState\n\n```js\nimport { moduleState } from 'cerebral'\n\n// In action\nfunction myAction({ store }) {\n store.set(moduleState.foo, 'bar')\n}\n\n// Using tags (alternative without babel plugin)\nfunction myAction({ store }) {\n store.set(moduleState`foo`, 'bar')\n}\n\n// In factories\n;[set(moduleState.foo, 'bar'), when(moduleState.isAwesome)]\n\n// In reaction\nReaction(\n {\n foo: moduleState.foo\n },\n () => {}\n)\n```\n\n## ModuleSequences\n\n```js\nimport { moduleSequences } from 'cerebral'\n\n// In action\nfunction myAction({ get }) {\n const mySequence = get(moduleSequences.mySequence)\n}\n\n// Using tags (alternative without babel plugin)\nfunction myAction({ get }) {\n const mySequence = get(moduleSequences`mySequence`)\n}\n\n// In factories\n;[onMessage('some_channel', moduleSequences.onMessage)]\n\n// In reaction\nReaction(\n {\n foo: state.foo\n },\n ({ foo, get }) => {\n get(moduleSequences.mySequence)({ foo })\n }\n)\n```\n\n## Reaction\n\n```js\nimport { state } from 'cerebral'\n\n// Create a reaction with proxies\nconst dispose = Reaction(\n {\n items: state.items,\n filter: state.filter\n },\n ({ items, filter, get }) => {\n console.log(\n 'Filtered items:',\n items.filter((item) => item.type === filter)\n )\n\n // Use get to access additional state\n const count = get(state.itemCount)\n }\n)\n\n// Using tags (alternative without babel plugin)\nconst dispose = Reaction(\n {\n items: state`items`,\n filter: state`filter`\n },\n ({ items, filter, get }) => {\n console.log(\n 'Filtered items:',\n items.filter((item) => item.type === filter)\n )\n\n // Use get to access additional state\n const count = get(state`itemCount`)\n }\n)\n\n// In components - no change needed as props.reaction is provided\n```\n\n## String\n\n```js\nimport { state, string } from 'cerebral'\n\n// In factories\n;[httpGet(string`/items/${state.currentItemId}`)][\n // Using tags (alternative without babel plugin)\n httpGet(string`/items/${state`currentItemId`}`)\n]\n```\n\n## TypeScript Support\n\nFor complete TypeScript support, you need to use proxies. The tags approach doesn't provide the same level of type checking. See [TypeScript documentation](/docs/advanced/typescript) for details on setting up static typing with Cerebral.\n","title":"Proxy vs Tags"},"reaction":{"raw":"# Reaction\n\nThe `Reaction` function creates a declarative way to react to state changes outside the normal component rendering flow.\n\n## API Reference\n\n### Signature\n\n```js\nReaction(dependencies, callback)\n```\n\n### Parameters\n\n- **dependencies** (Object): An object where keys define the property names in the callback's argument, and values are tags pointing to state paths. Optional - if omitted, the first argument is treated as the callback.\n\n- **callback** (Function): A function called when tracked state changes. Receives an object containing:\n - All dependencies defined in the first argument\n - A `get` function to access additional state values\n\n### Returns\n\nA reaction object with the following methods:\n\n- **create(controller, modulePath, name)**: Binds the reaction to a controller/app and associates it with a module.\n - `controller`: The Cerebral app/controller\n - `modulePath`: Array specifying the module path (e.g., `['users']`)\n - `name`: String identifier for debugging\n - Returns: The reaction instance\n\n- **initialize()**: Registers the reaction with the dependency system.\n - Returns: The reaction instance\n- **onUpdate()**: Manually triggers the reaction callback.\n\n- **destroy()**: Cleans up the reaction by unregistering it from the dependency system.\n\n### Examples\n\nBasic usage:\n\n```js\nimport { Reaction, state } from 'cerebral'\n\nconst logUser = Reaction(\n {\n user: state`user`,\n count: state`count`\n },\n ({ user, count, get }) => {\n console.log(`User: ${user.name}, Count: ${count}`)\n\n // Access additional state using get\n const otherValue = get(state`otherPath`)\n }\n)\n```\n\n## Component API\n\nWhen using components with Cerebral, a `reaction` prop is provided to create and manage reactions:\n\n```js\n// Class components\nthis.props.reaction(\n 'debugName', // Optional name for debugging\n { error: state`usernameError` }, // Dependencies\n ({ error }) => {\n /* callback */\n } // Callback\n)\n\n// Function components\nreaction('debugName', { error: state`usernameError` }, ({ error }) => {\n /* callback */\n})\n```\n\nComponent reactions are automatically disposed when the component unmounts.\n\nFor usage patterns and examples, see the [reactions guide](/docs/advanced/reactions.html).\n","title":"Reaction"},"sequence":{"raw":"# Sequence\n\nSequences are the primary way to define logic flows in Cerebral. They allow you to compose actions in a declarative way.\n\n## Basic Sequences\n\nSequences can be expressed with a simple array:\n\n```js\nexport const mySequence = [actions.doSomething, actions.doSomethingElse]\n```\n\nYou attach sequences to modules:\n\n```js\nimport * as sequences from './sequences'\n\nexport default {\n sequences\n}\n```\n\n## Async Actions\n\nBy default sequences run synchronously, but an action might run asynchronously, making the sequence async. When an action returns a promise, the sequence waits for it to resolve before continuing:\n\n```js\nfunction myAsyncAction() {\n return Promise.resolve({ result: 'data' })\n}\n\n// Or using async/await\nasync function myAsyncAction() {\n const data = await fetchSomething()\n return { result: data }\n}\n\nexport const mySequence = [\n myAsyncAction, // Sequence will wait for this to resolve\n actions.useTheResult\n]\n```\n\n## Sequence Factory\n\nWhile using array literals is the simplest approach, you can also be explicit by using the sequence factory:\n\n```js\nimport { sequence } from 'cerebral/factories'\nimport * as actions from './actions'\n\n// Unnamed sequence\nexport const mySequence = sequence([actions.someAction, actions.anotherAction])\n\n// Named sequence (useful for debugging)\nexport const namedSequence = sequence('My Important Flow', [\n actions.someAction,\n actions.anotherAction\n])\n```\n\n## Composition\n\nSequences can be composed within other sequences:\n\n```js\nimport * as actions from './actions'\n\n// Define a reusable sequence\nexport const authenticateSequence = [\n actions.validateCredentials,\n actions.requestToken,\n actions.storeToken\n]\n\n// Use it within another sequence\nexport const loginSequence = [\n actions.showLoadingIndicator,\n ...authenticateSequence,\n actions.redirectToDashboard,\n actions.hideLoadingIndicator\n]\n\n// Or\nexport const loginSequence = [\n actions.showLoadingIndicator,\n authenticateSequence,\n actions.redirectToDashboard,\n actions.hideLoadingIndicator\n]\n```\n\n## Parallel\n\nWhile JavaScript is single-threaded, Cerebral can run multiple asynchronous actions concurrently using `parallel`:\n\n```js\nimport { parallel } from 'cerebral/factories'\nimport * as actions from './actions'\n\nexport const loadDataSequence = [\n actions.setLoading,\n parallel([actions.loadUsers, actions.loadPosts, actions.loadSettings]),\n actions.unsetLoading\n]\n\n// Named parallel for debugging\nexport const loadDataSequence = [\n actions.setLoading,\n parallel('Load Application Data', [\n actions.loadUsers,\n actions.loadPosts,\n actions.loadSettings\n ]),\n actions.unsetLoading\n]\n```\n\nThe sequence continues only when all parallel actions have completed.\n\n## Paths\n\nPaths allow you to create branches in your sequences based on the result of an action.\n\n### Basic Path Usage\n\n```js\nimport * as actions from './actions'\n\nexport const submitForm = [\n actions.validateForm,\n {\n valid: [actions.submitForm, actions.showSuccessMessage],\n invalid: [actions.showValidationErrors]\n }\n]\n```\n\nThe action before the paths object decides which path to take:\n\n```js\nfunction validateForm({ path }) {\n const isValid = /* validation logic */\n\n if (isValid) {\n return path.valid()\n } else {\n return path.invalid()\n }\n}\n```\n\n### Passing Data to Paths\n\nYou can pass data when taking a path:\n\n```js\nfunction validateForm({ path, props }) {\n if (props.form.isValid) {\n return path.valid({\n validatedData: props.form.data\n })\n } else {\n return path.invalid({\n validationErrors: getErrors(props.form)\n })\n }\n}\n```\n\n### Async Path Selection\n\nPaths work with promises too:\n\n```js\nfunction checkUserPermission({ api, path, props }) {\n return api\n .checkPermission(props.userId)\n .then((response) => {\n if (response.hasPermission) {\n return path.allowed({ permissions: response.permissions })\n } else {\n return path.denied({ reason: response.reason })\n }\n })\n .catch((error) => path.error({ error }))\n}\n\nexport const userSequence = [\n actions.checkUserPermission,\n {\n allowed: [actions.grantAccess],\n denied: [actions.redirectToUnauthorized],\n error: [actions.showError]\n }\n]\n```\n\n### Status-Based Paths\n\nYou can create paths for specific scenarios like HTTP status codes:\n\n```js\nfunction getUser({ http, path, props }) {\n return http\n .get(`/users/${props.id}`)\n .then((response) => path.success({ user: response.data }))\n .catch((error) => {\n if (error.status === 404) {\n return path.notFound()\n } else {\n return path.error({ error })\n }\n })\n}\n\nexport const loadUser = [\n actions.getUser,\n {\n success: [actions.setUser],\n notFound: [actions.redirectToUserNotFound],\n error: [actions.showErrorMessage]\n }\n]\n```\n\n### Optional Paths\n\nNot all defined paths need to be used. Actions can choose which paths to include:\n\n```js\n// The action might take any of these paths, but isn't required to use all\nexport const userSequence = [\n actions.processUser,\n {\n admin: [actions.loadAdminTools],\n regular: [actions.loadRegularDashboard],\n guest: [actions.redirectToLogin],\n error: [actions.showError]\n }\n]\n```\n\n### Nesting Paths\n\nPaths can be nested to create complex conditional flows:\n\n```js\nexport const checkoutSequence = [\n actions.validateCart,\n {\n valid: [\n actions.processPayment,\n {\n success: [actions.createOrder, actions.showReceipt],\n declined: [actions.showPaymentError],\n error: [actions.logPaymentError]\n }\n ],\n invalid: [actions.showCartError]\n }\n]\n```\n\n## Running Sequences\n\nThere are several ways to run sequences:\n\n```js\nimport { sequences, state } from 'cerebral'\n\n// From an action\nfunction myAction({ get, props }) {\n // Get a sequence and run it\n const mySequence = get(sequences`mySequence`)\n mySequence({ someData: props.data })\n}\n\n// From a component\nconnect(\n {\n buttonClicked: sequences`mySequence`\n },\n ({ buttonClicked }) => {\n return <button onClick={() => buttonClicked({ id: 123 })}>Click me</button>\n }\n)\n\n// From a reaction\nReaction(\n {\n isLoggedIn: state`user.isLoggedIn`\n },\n ({ isLoggedIn, get }) => {\n if (isLoggedIn) {\n get(sequences`loadDashboard`)()\n }\n }\n)\n```\n\n```marksy\n<Info>\nYou can also use object notation (like `sequences.mySequence`) with the [@cerebral/babel-plugin](/docs/api/proxy.html).\n</Info>\n```\n","title":"Sequence"},"state":{"raw":"# State\n\n## Get state\n\nState in Cerebral is accessed using the `get` provider with tags.\n\n```js\nimport { state, moduleState } from 'cerebral'\n\nfunction someAction({ get }) {\n // Get state using path tag\n const stateAtSomePath = get(state`some.path`)\n\n // Get state using object notation (proxy)\n const sameState = get(state.some.path)\n\n // Get state from current module\n const stateAtModulePath = get(moduleState`isLoading`)\n\n // Also works with object notation\n const sameModuleState = get(moduleState.isLoading)\n\n // Access computed values the same way\n const computedValue = get(state`someComputed`)\n}\n```\n\n```marksy\n<Info>\nThe object notation (proxy) syntax requires the [@cerebral/babel-plugin](/docs/api/proxy.html) to be configured in your project. The template literal tag syntax (`` state`path` ``) works without additional configuration.\n</Info>\n```\n\n## Updating state\n\nState updates must be performed in actions using the `store` provider:\n\n```js\nimport { state, moduleState } from 'cerebral'\n\nfunction someAction({ store }) {\n // Set a value\n store.set(state`some.path`, 'someValue')\n\n // Set using object notation\n store.set(state.some.path, 'someValue')\n\n // Set value in current module\n store.set(moduleState`isLoading`, true)\n}\n```\n\n### Store API Methods\n\n```js\nfunction storeExample({ store }) {\n // Set or replace a value\n store.set(state`some.path`, 'someValue')\n\n // Set to undefined is the same as unset\n store.set(state`some.path`, undefined)\n\n // Toggle a boolean value\n store.toggle(state`isActive`)\n\n // Increment number (default increment is 1)\n store.increment(state`count`)\n store.increment(state`count`, 5)\n\n // Array operations\n store.push(state`items`, 'newItem') // Add to end of array\n store.unshift(state`items`, 'newItem') // Add to beginning of array\n store.pop(state`items`) // Remove last item\n store.shift(state`items`) // Remove first item\n store.splice(state`items`, 2, 1, 'newItem') // Remove/insert items\n store.concat(state`items`, ['a', 'b']) // Concatenate arrays\n\n // Object operations\n store.merge(state`user`, { name: 'John', age: 25 }) // Merge object properties\n store.unset(state`user.temporaryData`) // Remove property\n}\n```\n\n```marksy\n<Warning>\n**Important:** Never mutate state values directly in your actions or components. Always use the store API to ensure changes are tracked by Cerebral's state system and debugger.\n</Warning>\n```\n\n## Using moduleState\n\nThe `moduleState` tag allows you to operate on state relative to the current module:\n\n```js\nfunction someAction({ store, get }) {\n // If running in a module at \"app.dashboard\", this points to \"app.dashboard.isLoading\"\n store.set(moduleState`isLoading`, true)\n\n const isLoading = get(moduleState`isLoading`)\n}\n```\n\n## Path resolution\n\nYou can use dynamic paths with template literals:\n\n```js\nfunction selectUser({ store, props }) {\n store.set(state`users.${props.userId}.isSelected`, true)\n}\n```\n\n## Special values support\n\nCerebral supports these special value types that can be stored in state:\n\n- **File**\n- **FileList**\n- **Blob**\n- **ImageData**\n- **RegExp**\n\nThese values are treated as immutable - they will never be cloned or modified directly.\n\n```js\nfunction uploadFile({ store, props }) {\n // Store a file object directly in state\n store.set(state`uploadedFiles.${props.id}`, props.file)\n}\n```\n\nIf you need to support additional special types, you can configure this in the devtools options.\n","title":"State"},"test":{"raw":"# Test API\n\nCerebral provides testing utilities to help you test your application logic. There are two main approaches to testing in Cerebral:\n\n1. **Snapshot testing** - Test sequences by capturing and verifying their execution flow\n2. **Unit testing** - Test individual components, actions, and computed values\n\n## Snapshot Testing API\n\n### Snapshot\n\nCreates a snapshot test environment for your application. Pass your main module:\n\n```js\nimport { Snapshot } from 'cerebral/test'\nimport main from './main'\n\nconst snapshot = Snapshot(main)\n```\n\n### run\n\nRuns a sequence with an optional payload, returning a promise:\n\n```js\nSnapshot(main)\n .run('app.sequences.submitForm', { username: 'test' })\n .then((snapshot) => {\n expect(snapshot.get()).toMatchSnapshot()\n })\n```\n\n### mutate\n\nModifies state before running the sequence:\n\n```js\nSnapshot(main)\n .mutate('set', 'users.isLoading', true)\n .run('app.sequences.loadUsers')\n```\n\nAvailable mutations: `set`, `toggle`, `push`, `concat`, `pop`, `shift`, `unshift`, `splice`, `merge`, `unset`\n\n### mock\n\nMocks provider methods that will be called during sequence execution:\n\n```js\n// Mock with return value\nSnapshot(main).mock('http.get', { users: [] }).run('app.sequences.loadUsers')\n\n// Mock with function\nSnapshot(main)\n .mock('http.get', (context, url) => {\n expect(url).toBe('/api/users')\n return { users: [{ id: 1, name: 'Test' }] }\n })\n .run('app.sequences.loadUsers')\n```\n\n### mockResolvedPromise / mockRejectedPromise\n\nMock asynchronous provider methods that return promises:\n\n```js\n// Mock resolved promise\nSnapshot(main)\n .mockResolvedPromise('http.get', { users: [] })\n .run('app.sequences.loadUsers')\n\n// Mock rejected promise\nSnapshot(main)\n .mockRejectedPromise('http.get', { error: 'Network error' })\n .run('app.sequences.loadUsers')\n```\n\n## Unit Testing API\n\n### runCompute\n\nTest computed values:\n\n```js\nimport { runCompute } from 'cerebral/test'\nimport { multiply } from './computed'\n\nit('should multiply values', () => {\n const result = runCompute(multiply, {\n state: { value: 5 },\n props: { multiplier: 2 }\n })\n expect(result).toBe(10)\n})\n```\n\n### runAction\n\nTest individual actions:\n\n```js\nimport { runAction } from 'cerebral/test'\nimport { increment } from './actions'\n\nit('should increment counter', async () => {\n const { state } = await runAction(increment, {\n state: { count: 1 }\n })\n expect(state.count).toBe(2)\n})\n```\n\nThe promise resolves with an object containing:\n\n- `state`: Updated state\n- `props`: Props passed to the action\n- `output`: Action's output\n\n### runSequence\n\nTest sequences:\n\n```js\nimport { runSequence } from 'cerebral/test'\nimport { submitForm } from './sequences'\n\nit('should validate form', async () => {\n const { state } = await runSequence(submitForm, {\n state: { form: { username: '' } },\n props: { submitted: true }\n })\n expect(state.form.isValid).toBe(false)\n})\n```\n\n### CerebralTest\n\nCreate a test environment for running multiple sequences with the same controller:\n\n```js\nimport { CerebralTest } from 'cerebral/test'\nimport app from './app'\n\nit('should manage user sessions', async () => {\n const test = CerebralTest(app)\n\n // Set initial state\n test.setState('user.isLoggedIn', false)\n\n // Run first sequence\n await test.runSequence('user.login', {\n username: 'test',\n password: 'password'\n })\n\n // Check state between sequences\n expect(test.getState('user.isLoggedIn')).toBe(true)\n\n // Run another sequence\n await test.runSequence('user.logout')\n expect(test.getState('user.isLoggedIn')).toBe(false)\n})\n```\n\n#### Options\n\nCerebralTest accepts an options object as second parameter:\n\n```js\nconst test = CerebralTest(app, {\n throwToConsole: true, // Log errors to console\n recordActions: 'byName' // Record actions by name instead of index\n})\n```\n\nWith `recordActions: 'byName'`, action results are accessible by action name:\n\n```js\nconst { validateForm } = await test.runSequence('form.submit')\nexpect(validateForm.output.isValid).toBe(true)\n```\n\n## Component Testing\n\nTest components with Cerebral containers:\n\n```js\nimport { mount } from 'enzyme'\nimport App from 'cerebral'\nimport { Container } from '@cerebral/react'\nimport UserComponent from './UserComponent'\n\nit('should render user data', () => {\n const app = App({\n state: {\n user: { name: 'Test' }\n }\n })\n\n const wrapper = mount(\n <Container app={app}>\n <UserComponent />\n </Container>\n )\n\n expect(wrapper.find('.user-name').text()).toBe('Test')\n})\n```\n","title":"Test API"},"universalapp":{"raw":"# UniversalApp\n\nThe `UniversalApp` creates a special version of the Cerebral controller for server-side rendering (SSR). It allows you to:\n\n1. Initialize your app's state on the server\n2. Render the app with the populated state\n3. Produce a script tag containing state changes for client hydration\n\n## Initialization\n\n```js\nimport { UniversalApp } from 'cerebral'\nimport main from './main'\n\nconst app = UniversalApp(main, {\n // Same options as App\n devtools: null,\n throwToConsole: true\n})\n```\n\n```marksy\n<Info>\n`UniversalApp` accepts the same options as the standard [App](/docs/api/app.html).\n</Info>\n```\n\n## Methods\n\n`UniversalApp` includes all methods from the standard [App](/docs/api/app.html), plus the following:\n\n### run / runSequence\n\nExecute sequences to set up the initial state.\n\n```js\n// Run an inline sequence\napp.run(\n [\n ({ store, props }) => {\n store.set(state`user`, props.user)\n }\n ],\n {\n user: fetchedUserData\n }\n)\n\n// Run a named module sequence\napp.runSequence('app.initialize', {\n user: fetchedUserData\n})\n```\n\nBoth methods return a Promise that resolves when the sequence completes:\n\n```js\napp.runSequence('app.initialize', { user }).then(() => {\n // State is now populated\n renderApp()\n})\n```\n\n### setState\n\nDirectly set state at a specific path (synchronous operation).\n\n```js\n// Set a value by path\napp.setState('user.isLoggedIn', true)\n\n// Set a nested object\napp.setState('user', {\n id: '123',\n name: 'John',\n isLoggedIn: true\n})\n```\n\n### getChanges\n\nReturns a map of all state changes made since initialization.\n\n```js\n// After running sequences to set state\nconst stateChanges = app.getChanges()\n// { \"user.isLoggedIn\": true, \"user.id\": \"123\" }\n```\n\n### getScript\n\nGenerates a script tag containing all state changes, which the client app will use for hydration.\n\n```js\n// Get the script tag HTML\nconst scriptTag = app.getScript()\n// <script>window.CEREBRAL_STATE = {\"user.isLoggedIn\":true,\"user.id\":\"123\"}</script>\n```\n\nThis should be included in the HTML response, typically in the `<head>` section.\n\n## Complete SSR Example\n\nThis example shows a complete server-side rendering setup with React:\n\n```js\nimport express from 'express'\nimport React from 'react'\nimport { renderToString } from 'react-dom/server'\nimport { UniversalApp, state } from 'cerebral'\nimport { Container } from '@cerebral/react'\nimport App from '../client/components/App'\nimport main from '../client/main'\n\nconst server = express()\n\nserver.get('/', async (req, res) => {\n // Create a fresh app instance for each request\n const app = UniversalApp(main)\n\n // Run initialization sequence with request data\n await app.runSequence([fetchUser, setInitialState], {\n query: req.query,\n cookies: req.cookies\n })\n\n // Render the app to string\n const appHtml = renderToString(\n <Container app={app}>\n <App />\n </Container>\n )\n\n // Get the state hydration script\n const stateScript = app.getScript()\n\n // Return the complete HTML\n res.send(`<!DOCTYPE html>\n<html>\n <head>\n <title>My App</title>\n ${stateScript}\n </head>\n <body>\n <div id=\"root\">${appHtml}</div>\n <script src=\"/static/bundle.js\"></script>\n </body>\n</html>`)\n})\n\n// Example actions for initialization\nfunction fetchUser({ http, props }) {\n // Fetch user data based on cookie\n return http\n .get(`/api/users/me`, {\n headers: {\n Cookie: props.cookies\n }\n })\n .then((response) => ({ user: response.data }))\n .catch(() => ({ user: null }))\n}\n\nfunction setInitialState({ store, props }) {\n // Set the user in state\n store.set(state`user`, props.user)\n\n // Set initial query parameters\n store.set(state`query`, props.query)\n}\n\nserver.listen(3000, () => {\n console.log('Server running on port 3000')\n})\n```\n\n## Client-Side Hydration\n\nOn the client side, the standard App automatically picks up the state changes:\n\n```js\nimport React from 'react'\nimport { createRoot } from 'react-dom/client'\nimport App from 'cerebral'\nimport { Container } from '@cerebral/react'\nimport main from './main'\nimport AppComponent from './components/App'\n\n// Standard app picks up CEREBRAL_STATE automatically\nconst app = App(main)\n\n// Render and hydrate the app\nconst root = createRoot(document.getElementById('root'))\nroot.render(\n <Container app={app}>\n <AppComponent />\n </Container>\n)\n```\n\n## Caveats and Best Practices\n\n1. **Create fresh instances**: Create a new `UniversalApp` instance for each request to prevent state leakage between users.\n\n2. **Async operations**: Ensure all async operations complete before rendering.\n\n3. **Environment differences**: Be mindful of APIs that may exist only in browser or server environments.\n\n4. **Error handling**: Add proper error handling for server-side sequences.\n\n5. **Transpilation**: When using JSX on the server, ensure you're properly transpiling your server code.\n\nFor more detailed information on server-side rendering, see the [SSR guide](/docs/guides/ssr.html).\n","title":"UniversalApp"}},"migration":{"index":{"raw":"# 4.0\n\nThese are the constructs that Cerebral consists of:\n\n```js\nimport { Controller, Module, Provider, Compute, CerebralError } from 'cerebral'\n```\n\n## Module\n\nThe module is how you structure your application.\n\n```js\nconst app = Module({\n state: {},\n signals: {},\n providers: {},\n modules: {},\n catch: []\n})\n\n// Or callback\nconst app = Module(({ name, path, controller }) => ({\n state: {}\n}))\n```\n\n1. 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:\n\n```js\nconst app = Module({\n state: {},\n providers: {\n http: HttpProvider({})\n }\n})\n```\n\n2. 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.\n\n```js\nconst app = Module({\n state: {},\n catch: [[HttpProviderError, sequences.catchHttpError]]\n})\n```\n\n[Documentation](/docs/api/module)\n\n## Controller\n\nThe controller takes a module as first argument, and other options as a second argument.\n\n```js\nconst controller = Controller(rootModule, {\n devtools: null\n})\n```\n\nThe same goes for server side controller.\n\n[Documentation](/docs/api/controller)\n\n## Provider\n\nProviders 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.\n\n```js\nconst myProvider = Provider({\n foo() {}\n})\n```\n\nYou 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.\n\n```js\nconst api = Provider({\n getUser() {\n return this.context.http.get('/user')\n }\n})\n```\n\nAll 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:\n\n```js\nModule({\n providers: { uuid }\n})\n```\n\n[Documentation](/docs/api/providers)\n\n## Compute\n\nThis is just a change to be consistent with the other APIs:\n\n```js\nconst myComputed = Compute(state`foo`, (foo) => ())\n```\n\n[Documentation](/docs/api/compute)\n\n## CerebralError\n\nTo more easily create different error types you can now define your errors by extending the `CerebralError`:\n\n```js\nimport { CerebralError } from 'cerebral'\n\nexport class AppError extends CerebralError {}\nexport class ApiError extends CerebralError {}\n```\n\n[Documentation](/docs/api/error)\n\n## Module state changes\n\nYou can point to the module to make state changes on module executing the signal:\n\n```js\nfunction myAction({ module }) {\n module.set('foo', 'bar')\n}\n```\n\nYou can now use a `module` tag to point to the state of the module running the signal:\n\n```js\n;[set(module`foo`, 'bar')]\n```\n\n[Documentation tags](/docs/api/tags)\n[Documentation state](/docs/api/state)\n\n## Testing\n\nThe `CerebralTest` API now takes a Module as first argument.\n\n```js\nimport { CerebralTest } from 'cerebral/test'\nimport app from './src/app' // A Module\n\nCerebralTest(app)\n```\n\n## Routing\n\nSince you now use a root `Module`, the router will need to be added there.\n\n```js\nimport { Controller, Module } from 'cerebral'\nimport router from './router' // Instance of the router module\n\nconst rootModule = Module({\n modules: {\n router\n }\n})\n\nconst controller = Controller(rootModule)\n```\n\n## Migration\n\n1. Change controller configuration to take in a top level module\n2. Wrap all modules in Module constructor\n3. Change configuration of providers to use object\n4. Change any custom providers to use the Provider factory\n5. Rename use of `compute` to `Compute`\n6. Any error catching now needs to be at module level without `new Map(...)`\n7. Recommended to extend any errors that can be caught from `CerebralError`, as you get serialization out of the box\n8. Change out any `CerebralTest` argument with a `Module`\n","title":"4.0"},"5_0":{"raw":"# Migrating to Cerebral 5.0\n\nCerebral 5.0 focuses on four key improvements:\n\n- **Simplicity**: Streamlined API with fewer concepts to learn\n- **Consistency**: Unified patterns across the entire codebase\n- **Approachability**: Easier for new developers to get started\n- **Scope reduction**: Core functionality with less maintenance overhead\n\nWatch this introduction video to understand the changes:\n\n```marksy\n<Youtube url=\"https://www.youtube.com/embed/5TaFHDir82w\" />\n```\n\n## Key Changes\n\nOne significant change is the **removal of all addons**. This was done to reduce maintenance burden and encourage a more flexible approach where you can build custom providers exactly the way you need them. For example, you can now [create your own router](/docs/guides/routing.html) tailored to your application's needs.\n\n## Breaking Changes\n\nYour application should work with minimal changes, with these exceptions:\n\n### 1. Compute API\n\nThe `Compute` API has a new signature. [Read more about it here](/docs/api/computed.html).\n\n### 2. StateContainer Replaced\n\n```js\n// REMOVED\nimport { StateContainer } from '@cerebral/_view_'\n\n// INSTEAD\nimport App from 'cerebral'\nimport { Container } from '@cerebral/_view_'\nimport main from 'path/to/main/module'\n\nconst app = App(main)\n\n<Container app={app}>\n {/* Your app */}\n</Container>\n```\n\n## Deprecations\n\nThe following changes will trigger deprecation warnings but won't break your app:\n\n### 1. Controller → App\n\n```js\n// DEPRECATED\nimport { Controller } from 'cerebral'\nconst controller = Controller(...)\n\n// NEW\nimport App from 'cerebral'\nconst app = App(...)\n```\n\n### 2. UniversalController → UniversalApp\n\n```js\n// DEPRECATED\nimport { UniversalController } from 'cerebral'\nconst controller = UniversalController(...)\n\n// NEW\nimport { UniversalApp } from 'cerebral'\nconst app = UniversalApp(...)\n```\n\n### 3. Container prop renamed\n\n```js\n// DEPRECATED\nimport { Container } from '@cerebral/react'\nrender(<Container controller={controller}></Container>)\n\n// NEW\nimport { Container } from '@cerebral/react'\nrender(<Container app={app}></Container>)\n```\n\n### 4. Module factory removed\n\n```js\n// DEPRECATED\nimport { Module } from 'cerebral'\nexport default Module({})\n// or\nexport default Module(() => {})\n\n// NEW\nexport default {}\n// or\nexport default () => {}\n```\n\nFor TypeScript users:\n\n```ts\nimport { ModuleDefinition } from 'cerebral'\nconst module: ModuleDefinition = {}\nexport default module\n```\n\n### 5. Tag names updated\n\n```js\n// DEPRECATED\nimport { signal, signals, module } from 'cerebral/tags'\n\n// NEW\nimport { sequences, moduleState } from 'cerebral'\n```\n\n```marksy\n<Info>\n**sequences** now handles both single sequences and modules with sequences, replacing both the previous **signal** and **signals** tags.\n</Info>\n```\n\n### 6. Tags → Proxies\n\n```js\n// DEPRECATED\nimport * as tags from 'cerebral/tags'\n\n// NEW\nimport {\n string,\n props,\n state,\n sequences,\n moduleState,\n moduleSequences\n} from 'cerebral'\n```\n\n[Learn more about using proxies](/docs/api/proxy)\n\n### 7. Operators → Factories\n\nWe've renamed \"operators\" to \"factories\" to better reflect their purpose:\n\n```js\n// DEPRECATED\nimport { set, push, merge } from 'cerebral/operators'\n\n// NEW\nimport { set, push, merge } from 'cerebral/factories'\n```\n\n## Replacing Addons\n\nAddons have been removed from the core library. Here's how to replace them:\n\n### Forms\n\nTreat forms as \"complex inputs\" that manage their own state with integration points to your Cerebral state. Libraries like [formsy-react](https://github.com/formsy/formsy-react) work well with this approach.\n\n### HTTP\n\nUse any HTTP library and expose its methods through a provider. See [the HTTP guide](/docs/guides/index.html) for examples.\n\n### Routing\n\nYou can choose from several approaches:\n\n- Component-based routing from your view library\n- A dedicated routing library\n- A \"Cerebral-first\" routing approach as shown in [the routing guide](/docs/guides/routing.html)\n\n### Local Storage\n\nCreate a simple provider that exposes localStorage methods, or use a more comprehensive solution like [local-storage](https://www.npmjs.com/package/local-storage).\n\n### Firebase\n\nFirebase's API has improved significantly and is now easier to use directly. Create a provider that exposes the Firebase methods you need for your application.\n","title":"Migrating to Cerebral 5.0"}},"contribute":{"index":{"raw":"# Introduction\n\nOpen source would never work without contributions, but it is difficult to get a sense of where you can put in an effort to help out on a project. Where is help needed? How does the project work? How do I create a pull request? On this page you will hopefully get the information you need to contribute to the Cerebral project.\n\n## Contribute to documentation\n\nContributing with missing explanations and wrong documentation is the **most important** contribution you can make to any project.\n\n```marksy\n<Youtube url=\"https://www.youtube.com/embed/5UXmyClZkjU\" />\n```\n\n## Contribute to website\n\nNew documentation like guides is a really great way to contribute as it will help others with specific use cases.\n\n```marksy\n<Youtube url=\"https://www.youtube.com/embed/yhDTzXSOD7E\" />\n```\n\n## Contribute to codebase\n\nBug fixes, refactors, typescript typings and new features is also a really great way to contribute. Mostly because you get insight into the code and get more ownership.\n\n```marksy\n<Youtube url=\"https://www.youtube.com/embed/TYkylI7Aado\" />\n```\n","title":"Introduction"},"issues":{"raw":"# Issues\n\nThis is an overview of current issues that requires a **pull request**. They have been commented with a recommended approach.\n","title":"Issues"}},"resources":{"index":{"raw":"# Utilities\n\nThis is a growing list of packages that may help you in the development of your Cerebral Project\n\n## state-forms\n\nA computed form - originally @cerebral/forms\n\n### Description\n\nForms are one of the most complex state management challenges out there. Before Cerebral was created I spent a lot of time developing formsy-react, which is a library that tries to solve forms with internal state. With the release of Cerebral we got a chance to explore the space of solving forms complexity with external state instead. To this day I have a hard time recommending a solution and you should not see this lib as \"the official way of managing forms with Cerebral\". There is nothing wrong thinking of a form as a very complex input where you only pass data into Cerebral on the submit of the form.\n\n[GitHub](https://github.com/garth/state-forms) |\n[npm](https://www.npmjs.com/package/state-forms)\n\n## @cerebral/storage\n\n### Description (storage)\n\nThis module exposes local storage or session storage as a provider, where it by default parses and serializes to JSON.\n\n```marksy\n<Info>\nThis one is not in npm yet, so you need to add the github url in `package.json`\n</Info>\n```\n\n[GitHub](https://github.com/psypersky/cerebral-local-storage)\n","title":"Utilities"}}}