MobX-State-Tree Prebuilt Middlewares

raw JSON →
6.1.0 verified Thu Apr 23 auth: no javascript

mst-middlewares provides a collection of prebuilt middlewares for MobX-State-Tree (MST), primarily serving as examples and starting points for developers to create their own custom MST middlewares. Originally part of the `mobx-state-tree` monorepo, it was extracted into its own package to keep the core MST library small. The package is currently at version 6.1.0 and appears to have a release cadence driven by contributions and fixes, with a recent major bump to v6.0.0. Key differentiators include its direct lineage from the official MST repository, offering basic but functional examples like action logging and transactional rollbacks, and encouraging direct modification or copy-pasting of its source for specific project needs rather than relying on them as fully-fledged, heavily-supported features. Developers are encouraged to read the source code to understand how to implement custom middleware for MST's event system.

error TypeError: Cannot read properties of undefined (reading 'addMiddleware')
cause Attempting to use `addMiddleware` or `decorate` without `mobx-state-tree` being correctly installed as a peer dependency, or trying to apply middleware to an object that isn't a valid MST instance.
fix
Ensure mobx-state-tree is installed (npm install mobx-state-tree or yarn add mobx-state-tree) and that addMiddleware or decorate are being called on a valid MST model instance created with types.model.create().
error SyntaxError: Cannot use import statement outside a module
cause Attempting to use `import` syntax in a CommonJS (CJS) environment without proper transpilation or configuration (e.g., in an older Node.js project without `"type": "module"` in `package.json`).
fix
For CommonJS projects, either configure your project to use ESM (e.g., set "type": "module" in package.json and adjust file extensions to .js or .mjs) or use a transpiler like Babel or TypeScript to convert ESM imports to CJS require statements during build. Modern Node.js versions support ESM directly when "type": "module" is set.
breaking Version 6.0.0 included a fix where `recorder.undo()` actions no longer register as an `UndoState` in the middleware. This might affect custom middleware logic that relies on inspecting the type of state produced by `recorder.undo()` operations.
fix Review any custom middleware that inspects the type of state returned by `recorder.undo()`. Adjust logic to account for this change, as the undo operation will no longer be seen as an `UndoState`.
gotcha The provided middlewares are explicitly described as examples and are supported on a 'best effort' basis. They are not intended as fully robust, production-ready solutions for all use cases.
fix For critical systems, it is strongly recommended to copy and paste the source code of the desired middleware and tailor it to specific needs, rather than relying solely on the package's default implementations. This allows for full control and custom error handling/feature additions.
gotcha The package originated from an undo of the MobX-State-Tree monorepo setup. This implies a historical context where these middlewares were internal examples, and their design reflects that, prioritizing clarity and demonstration over extreme optimization or comprehensive feature sets.
fix Always review the source code of any middleware you plan to use in production to understand its limitations and potential areas for improvement or customization for your specific application.
npm install mst-middlewares
yarn add mst-middlewares
pnpm add mst-middlewares

This quickstart demonstrates the `atomic` middleware, which ensures that any modifications made within a synchronous or asynchronous action are rolled back if the action fails. It shows how to use `addMiddleware` on a model instance and verifies that the state (`m.z`) returns to its initial value after a `flow` action throws an error.

import { types, addMiddleware, flow } from 'mobx-state-tree';
import { atomic } from 'mst-middlewares';

const delay = (ms: number) => new Promise(res => setTimeout(res, ms));

const TestModel = types
  .model({
    z: types.number,
  })
  .actions((self) => {
    // Example of adding middleware directly
    addMiddleware(self, atomic);

    return {
      inc: flow(function* (x: number) {
        console.log(`Initial z: ${self.z}`);
        yield delay(2);
        self.z += x;
        console.log(`z after first increment: ${self.z}`);
        yield delay(2);
        self.z += x;
        console.log(`z after second increment: ${self.z}`);
        throw new Error("Oops, something went wrong!");
      }),
    };
  });

const m = TestModel.create({ z: 1 });

m.inc(3).catch((error: Error) => {
  console.error(`Caught error: ${error.message}`);
  console.log(`z after failed action (should be rolled back): ${m.z}`); // Expected: 1
});