├── test-results └── .gitkeep ├── .dockerignore ├── bun.lockb ├── docs ├── .gitattributes ├── API_header.md ├── concepts │ ├── reconciliation.md │ ├── react.md │ ├── dependency-injection.md │ ├── patches.md │ ├── listeners.md │ ├── views.md │ ├── snapshots.md │ └── actions.md ├── intro │ ├── examples.md │ └── installation.md ├── API │ └── interfaces │ │ ├── referenceoptionsoninvalidated.md │ │ ├── imodelreflectionpropertiesdata.md │ │ ├── ivalidationcontextentry.md │ │ ├── ijsonpatch.md │ │ ├── iserializedactioncall.md │ │ ├── iactiontrackingmiddleware2call.md │ │ ├── unionoptions.md │ │ ├── ivalidationerror.md │ │ ├── ihooks.md │ │ ├── ireversiblejsonpatch.md │ │ ├── referenceoptionsgetset.md │ │ ├── isnapshotprocessors.md │ │ ├── iactionrecorder.md │ │ ├── iactiontrackingmiddleware2hooks.md │ │ ├── iactioncontext.md │ │ ├── imodelreflectiondata.md │ │ ├── customtypeoptions.md │ │ ├── ipatchrecorder.md │ │ ├── itype.md │ │ ├── iactiontrackingmiddlewarehooks.md │ │ ├── ianytype.md │ │ ├── ianycomplextype.md │ │ └── isimpletype.md └── tips │ ├── circular-deps.md │ ├── inheritance.md │ ├── more-tips.md │ ├── snapshots-as-values.md │ ├── faq.md │ └── resources.md ├── bunfig.toml ├── website ├── bun.lockb ├── static │ ├── img │ │ ├── logo.png │ │ ├── favicon.ico │ │ ├── patches.png │ │ ├── tserror.png │ │ ├── oss_logo.png │ │ ├── reduxdevtools.png │ │ ├── mobx-state-tree-logo.png │ │ └── mobx-state-tree-logo-gradient.png │ ├── css │ │ └── custom.css │ └── index.html ├── package.json ├── sidebars.json └── siteConfig.js ├── .husky └── pre-commit ├── .gitattributes ├── ISSUE_TEMPLATE.md ├── Dockerfile ├── SECURITY.md ├── .gitignore ├── .vscode ├── extensions.json ├── launch.json ├── tasks.json └── settings.json ├── __tests__ ├── tsconfig.json ├── setup.ts ├── core │ ├── name.test.ts │ ├── circular1.test.ts │ ├── circular2.test.ts │ ├── 1525.test.ts │ ├── optimizations.test.ts │ ├── 1664.test.ts │ ├── literal.test.ts │ ├── deprecated.test.ts │ ├── __snapshots__ │ │ ├── custom-type.test.ts.snap │ │ └── reference-custom.test.ts.snap │ ├── refinement.test.ts │ ├── this.test.ts │ ├── pointer.test.ts │ ├── frozen.test.ts │ ├── enum.test.ts │ ├── lazy.test.ts │ └── primitives.test.ts ├── perf │ ├── timer.ts │ ├── perf.skip.ts │ ├── fixture-data.skip.ts │ ├── fixture-models.skip.ts │ ├── scenarios.ts │ ├── fixtures │ │ ├── fixture-models.ts │ │ └── fixture-data.ts │ └── report.ts └── utils.test.ts ├── typedocconfig.js ├── jest.config.js ├── src ├── core │ ├── node │ │ ├── Hook.ts │ │ ├── livelinessChecking.ts │ │ ├── create-node.ts │ │ └── scalar-node.ts │ ├── actionContext.ts │ └── process.ts ├── types │ ├── index.ts │ └── utility-types │ │ ├── maybe.ts │ │ ├── enumeration.ts │ │ ├── literal.ts │ │ ├── refinement.ts │ │ ├── lazy.ts │ │ └── frozen.ts ├── internal.ts └── middlewares │ └── create-action-tracking-middleware.ts ├── docker-compose.yml ├── .github ├── pull_request_template.md ├── ISSUE_TEMPLATE │ └── bug-report.md ├── lock.yml └── stale.yml ├── tsconfig.json ├── tslint.json ├── LICENSE ├── scripts ├── generate-compose-type.js └── generate-shared.js ├── rollup.config.js └── .circleci └── config.yml /test-results/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.dockerignore: -------------------------------------------------------------------------------- 1 | */node_modules 2 | *.log 3 | -------------------------------------------------------------------------------- /bun.lockb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mobxjs/mobx-state-tree/HEAD/bun.lockb -------------------------------------------------------------------------------- /docs/.gitattributes: -------------------------------------------------------------------------------- 1 | /API/**/* -diff -merge 2 | /API/**/* linguist-generated 3 | -------------------------------------------------------------------------------- /bunfig.toml: -------------------------------------------------------------------------------- 1 | [test] 2 | root = "./__tests__" 3 | preload = ["./__tests__/setup.ts"] 4 | -------------------------------------------------------------------------------- /website/bun.lockb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mobxjs/mobx-state-tree/HEAD/website/bun.lockb -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | . "$(dirname "$0")/_/husky.sh" 3 | 4 | bun run lint-staged 5 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto 2 | 3 | *.ts text eol=lf 4 | *.json text eol=lf 5 | *.md text eol=lf 6 | -------------------------------------------------------------------------------- /website/static/img/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mobxjs/mobx-state-tree/HEAD/website/static/img/logo.png -------------------------------------------------------------------------------- /website/static/img/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mobxjs/mobx-state-tree/HEAD/website/static/img/favicon.ico -------------------------------------------------------------------------------- /website/static/img/patches.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mobxjs/mobx-state-tree/HEAD/website/static/img/patches.png -------------------------------------------------------------------------------- /website/static/img/tserror.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mobxjs/mobx-state-tree/HEAD/website/static/img/tserror.png -------------------------------------------------------------------------------- /website/static/img/oss_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mobxjs/mobx-state-tree/HEAD/website/static/img/oss_logo.png -------------------------------------------------------------------------------- /website/static/img/reduxdevtools.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mobxjs/mobx-state-tree/HEAD/website/static/img/reduxdevtools.png -------------------------------------------------------------------------------- /website/static/img/mobx-state-tree-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mobxjs/mobx-state-tree/HEAD/website/static/img/mobx-state-tree-logo.png -------------------------------------------------------------------------------- /website/static/img/mobx-state-tree-logo-gradient.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mobxjs/mobx-state-tree/HEAD/website/static/img/mobx-state-tree-logo-gradient.png -------------------------------------------------------------------------------- /ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | **If your issue isn't a bug report, please consider using discussion threads instead of opening an issue: https://github.com/mobxjs/mobx-state-tree/discussions** 2 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:8.11.4 2 | 3 | WORKDIR /app/website 4 | 5 | EXPOSE 3000 35729 6 | COPY ./docs /app/docs 7 | COPY ./website /app/website 8 | RUN yarn install 9 | 10 | CMD ["yarn", "start"] 11 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | ## Security contact information 2 | 3 | To report a security vulnerability, please use the 4 | [Tidelift security contact](https://tidelift.com/security). 5 | Tidelift will coordinate the fix and disclosure. 6 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | *.log 3 | lib 4 | dist 5 | coverage 6 | .nyc_output 7 | .idea 8 | package-lock.json 9 | .prettierignore 10 | .vscode 11 | .editorconfig 12 | /test-results/**/*.xml 13 | /website/build 14 | .DS_Store 15 | junit.xml -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | // See http://go.microsoft.com/fwlink/?LinkId=827846 3 | // for the documentation about the extensions.json format 4 | "recommendations": [ 5 | // Extension identifier format: ${publisher}.${name}. Example: vscode.csharp 6 | "esbenp.prettier-vscode" 7 | ] 8 | } -------------------------------------------------------------------------------- /__tests__/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.json", 3 | "compilerOptions": { 4 | "rootDir": "../", 5 | "module": "commonjs", 6 | "lib": ["es2017", "dom"], 7 | "target": "es6", 8 | "sourceMap": false, 9 | "noEmit": true 10 | }, 11 | "include": ["**/*.ts"] 12 | } 13 | -------------------------------------------------------------------------------- /typedocconfig.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | src: ["src/index.ts"], 3 | module: "commonjs", 4 | excludeNotExported: true, 5 | excludePrivate: true, 6 | excludeProtected: true, 7 | mode: "file", 8 | readme: "none", 9 | out: "./docs/API", 10 | theme: "docusaurus", 11 | tsconfig: "tsconfig.json", 12 | listInvalidSymbolLinks: true, 13 | mdHideSources: true 14 | } 15 | -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | displayName: "test", 3 | testEnvironment: "node", 4 | transform: { 5 | "^.+\\.tsx?$": "ts-jest" 6 | }, 7 | testRegex: ".*\\.test\\.tsx?$", 8 | moduleFileExtensions: ["ts", "tsx", "js"], 9 | globals: { 10 | "ts-jest": { 11 | tsConfig: "__tests__/tsconfig.json" 12 | } 13 | }, 14 | reporters: ["default", "jest-junit"] 15 | } 16 | -------------------------------------------------------------------------------- /website/static/css/custom.css: -------------------------------------------------------------------------------- 1 | /* your custom css */ 2 | @media only screen and (min-device-width: 360px) and (max-device-width: 736px) { 3 | } 4 | 5 | @media only screen and (min-width: 1024px) { 6 | } 7 | 8 | @media only screen and (max-width: 1023px) { 9 | } 10 | 11 | @media only screen and (min-width: 1400px) { 12 | } 13 | 14 | @media only screen and (min-width: 1500px) { 15 | } 16 | 17 | a { 18 | color: #ff7000; 19 | } 20 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.2.0", 3 | "configurations": [ 4 | { 5 | "type": "node", 6 | "request": "launch", 7 | "cwd": "${workspaceRoot}", 8 | "name": "debug unit test", 9 | "program": "${workspaceRoot}/node_modules/jest/bin/jest.js", 10 | "args": ["-i", "${fileBasename}"], 11 | "skipFiles": ["/**/*", "${workspaceRoot}/node_modules/**/*"] 12 | } 13 | ] 14 | } 15 | -------------------------------------------------------------------------------- /website/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "scripts": { 3 | "examples": "docusaurus-examples", 4 | "start": "docusaurus-start", 5 | "build": "docusaurus-build", 6 | "publish-gh-pages": "docusaurus-publish", 7 | "write-translations": "docusaurus-write-translations", 8 | "version": "docusaurus-version", 9 | "rename-version": "docusaurus-rename-version" 10 | }, 11 | "devDependencies": { 12 | "docusaurus": "^1.14.0" 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /docs/API_header.md: -------------------------------------------------------------------------------- 1 | # Mobx-State-Tree API reference guide 2 | 3 | _This reference guide lists all methods exposed by MST. Contributions like linguistic improvements, adding more details to the descriptions or additional examples are highly appreciated! Please note that the docs are generated from source. Most methods are declared in the [mst-operations.ts](https://github.com/mobxjs/mobx-state-tree/blob/master/packages/mobx-state-tree/src/core/mst-operations.ts) file._ 4 | -------------------------------------------------------------------------------- /website/static/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 9 | MobX-state-tree 10 | 11 | 12 | If you are not redirected automatically, follow this 13 | link. 14 | 15 | 16 | -------------------------------------------------------------------------------- /__tests__/setup.ts: -------------------------------------------------------------------------------- 1 | import { beforeEach, afterEach, mock } from "bun:test" 2 | import { resetNextActionId, setLivelinessChecking } from "../src/internal" 3 | import { configure } from "mobx" 4 | 5 | beforeEach(() => { 6 | setLivelinessChecking("warn") 7 | resetNextActionId() 8 | }) 9 | 10 | afterEach(() => { 11 | mock.restore() 12 | 13 | // Some tests turn off proxy support, so ensure it's always turned back on 14 | configure({ 15 | useProxies: "always" 16 | }) 17 | }) 18 | -------------------------------------------------------------------------------- /__tests__/core/name.test.ts: -------------------------------------------------------------------------------- 1 | import { types } from "../../src" 2 | import { getDebugName } from "mobx" 3 | import { expect, test } from "bun:test" 4 | 5 | test("it should have a debug name", () => { 6 | const Model = types.model("Name") 7 | 8 | const model = Model.create() 9 | const array = types.array(Model).create() 10 | const map = types.map(Model).create() 11 | 12 | expect(getDebugName(model)).toBe("Name") 13 | expect(getDebugName(array)).toBe("Name[]") 14 | expect(getDebugName(map)).toBe("Map") 15 | }) 16 | -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | // See https://go.microsoft.com/fwlink/?LinkId=733558 3 | // for the documentation about the tasks.json format 4 | "version": "2.0.0", 5 | "command": "npm", 6 | "tasks": [ 7 | { 8 | "label": "build", 9 | "type": "shell", 10 | "args": [ 11 | "tsc" 12 | ], 13 | "problemMatcher": [], 14 | "group": { 15 | "_id": "build", 16 | "isDefault": false 17 | } 18 | } 19 | ] 20 | } -------------------------------------------------------------------------------- /src/core/node/Hook.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @hidden 3 | */ 4 | export enum Hook { 5 | afterCreate = "afterCreate", 6 | afterAttach = "afterAttach", 7 | afterCreationFinalization = "afterCreationFinalization", 8 | beforeDetach = "beforeDetach", 9 | beforeDestroy = "beforeDestroy" 10 | } 11 | 12 | export interface IHooks { 13 | [Hook.afterCreate]?: () => void 14 | [Hook.afterAttach]?: () => void 15 | [Hook.beforeDetach]?: () => void 16 | [Hook.beforeDestroy]?: () => void 17 | } 18 | 19 | export type IHooksGetter = (self: T) => IHooks 20 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: "3" 2 | 3 | services: 4 | docusaurus: 5 | build: . 6 | ports: 7 | - 3000:3000 8 | - 35729:35729 9 | volumes: 10 | - ./docs:/app/docs 11 | - ./website/blog:/app/website/blog 12 | - ./website/core:/app/website/core 13 | - ./website/i18n:/app/website/i18n 14 | - ./website/pages:/app/website/pages 15 | - ./website/static:/app/website/static 16 | - ./website/sidebars.json:/app/website/sidebars.json 17 | - ./website/siteConfig.js:/app/website/siteConfig.js 18 | working_dir: /app/website 19 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | // Place your settings in this file to overwrite default and user settings. 2 | { 3 | "editor.insertSpaces": true, 4 | "editor.trimAutoWhitespace": true, 5 | "tslint.configFile": "tslint.json", 6 | "typescript.tsdk": "node_modules/typescript/lib", 7 | "[typescript]": { 8 | "editor.formatOnSave": true, 9 | "editor.formatOnPaste": true 10 | }, 11 | "[javascript]": { 12 | "editor.formatOnSave": true, 13 | "editor.formatOnPaste": true 14 | }, 15 | "npm.packageManager": "yarn", 16 | "tslint.packageManager": "yarn", 17 | "eslint.packageManager": "yarn" 18 | } 19 | -------------------------------------------------------------------------------- /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | ## What does this PR do and why? 2 | 3 | 9 | 10 | ## Steps to validate locally 11 | 12 | 20 | -------------------------------------------------------------------------------- /__tests__/perf/timer.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Start a timer which return a function, which when called show the 3 | * number of milliseconds since it started. 4 | * 5 | * Passing true will give the current lap time. 6 | * 7 | * Example: 8 | * ```ts 9 | * const time = start() 10 | * // 1 second later 11 | * time() // 1.00 12 | * // 1 more second later 13 | * time() // 2.00 14 | * time(true) // 1.00 15 | * ``` 16 | */ 17 | export const start = () => { 18 | const started = process.hrtime() 19 | let last: [number, number] = [started[0], started[1]] 20 | return (lapTime = false) => { 21 | const final = process.hrtime(lapTime ? last : started) 22 | return Math.round((final[0] * 1e9 + final[1]) / 1e6) 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /docs/concepts/reconciliation.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: reconciliation 3 | title: Reconciliation 4 | --- 5 | 6 |
7 | 8 | - When applying snapshots, MST will always try to reuse existing object instances for snapshots with the same identifier (see `types.identifier`). 9 | - If no identifier is specified, but the type of the snapshot is correct, MST will reconcile objects as well if they are stored in a specific model property or under the same map key. 10 | - In arrays, items without an identifier are never reconciled. 11 | 12 | If an object is reconciled, the consequence is that localState is preserved and `afterCreate` / `attach` life-cycle hooks are not fired because applying a snapshot results just in an existing tree node being updated. 13 | -------------------------------------------------------------------------------- /docs/intro/examples.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: examples 3 | title: Examples 4 | --- 5 | 6 |
7 | 8 | - [Bookshop](https://github.com/coolsoftwaretyler/mst-example-bookshop/) Example webshop application with references, identifiers, routing, testing, etc. 9 | - [Boxes](https://github.com/coolsoftwaretyler/mst-example-boxes) Example app where one can draw, drag, and drop boxes. With time-travelling and multi-client synchronization over websockets. 10 | - [TodoMVC](https://github.com/coolsoftwaretyler/mst-example-todomvc) Classic example app using React and MST. 11 | - [Redux TodoMVC](https://github.com/coolsoftwaretyler/mst-example-redux-todomvc) Redux TodoMVC application, except that the reducers are replaced with a MST. Tip: open the Redux devtools; they will work! 12 | -------------------------------------------------------------------------------- /__tests__/utils.test.ts: -------------------------------------------------------------------------------- 1 | import { MstError } from "../src/utils" 2 | import { describe, expect, test } from "bun:test" 3 | 4 | describe("MstError custom error class", () => { 5 | test("with default message", () => { 6 | const error = new MstError() 7 | expect(error.message).toBe("[mobx-state-tree] Illegal state") 8 | }) 9 | 10 | test("with custom message", () => { 11 | const customMessage = "custom error message" 12 | const error = new MstError(customMessage) 13 | expect(error.message).toBe(`[mobx-state-tree] ${customMessage}`) 14 | }) 15 | 16 | test("instance of MstError", () => { 17 | const error = new MstError() 18 | expect(error).toBeInstanceOf(MstError) 19 | expect(error).toBeInstanceOf(Error) 20 | }) 21 | }) 22 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "outDir": "lib/", 4 | "target": "es5", 5 | "sourceMap": false, 6 | "declaration": true, 7 | "module": "es2015", 8 | "removeComments": false, 9 | "moduleResolution": "node", 10 | "experimentalDecorators": true, 11 | "strict": true, 12 | "strictNullChecks": true, 13 | "strictFunctionTypes": true, 14 | "noImplicitAny": true, 15 | "noFallthroughCasesInSwitch": true, 16 | "noImplicitReturns": true, 17 | "noImplicitThis": true, 18 | "importHelpers": true, 19 | "stripInternal": true, 20 | "downlevelIteration": true, 21 | "lib": ["es6"], 22 | "useDefineForClassFields": true, 23 | "isolatedModules": true, 24 | "skipLibCheck": true 25 | }, 26 | "include": ["**/*.js", "**/*.ts"] 27 | } 28 | -------------------------------------------------------------------------------- /docs/API/interfaces/referenceoptionsoninvalidated.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: "referenceoptionsoninvalidated" 3 | title: "ReferenceOptionsOnInvalidated" 4 | sidebar_label: "ReferenceOptionsOnInvalidated" 5 | --- 6 | 7 | [mobx-state-tree - v7.0.2](../index.md) › [ReferenceOptionsOnInvalidated](referenceoptionsoninvalidated.md) 8 | 9 | ## Type parameters 10 | 11 | ▪ **IT**: *[IAnyComplexType](ianycomplextype.md)* 12 | 13 | ## Hierarchy 14 | 15 | * **ReferenceOptionsOnInvalidated** 16 | 17 | ## Index 18 | 19 | ### Properties 20 | 21 | * [onInvalidated](referenceoptionsoninvalidated.md#oninvalidated) 22 | 23 | ## Properties 24 | 25 | ### onInvalidated 26 | 27 | • **onInvalidated**: *[OnReferenceInvalidated](../index.md#onreferenceinvalidated)‹ReferenceT‹IT››* 28 | 29 | *Defined in [src/types/utility-types/reference.ts:478](https://github.com/mobxjs/mobx-state-tree/blob/1be40a3e/src/types/utility-types/reference.ts#L478)* 30 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug-report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: I think something is not working as it should 4 | --- 5 | 6 | 7 | 8 | **_Bug report_** 9 | 10 | - [ ] I've checked documentation and searched for existing issues [and discussions](https://github.com/mobxjs/mobx-state-tree/discussions) 11 | - [ ] I've made sure my project is based on the latest MST version 12 | - [ ] Fork [this](https://codesandbox.io/p/sandbox/mobx-state-tree-todolist-forked-pj732k) code sandbox or another minimal reproduction. 13 | 14 | **Sandbox link or minimal reproduction code** 15 | 16 | 17 | 18 | **Describe the expected behavior** 19 | 20 | 21 | 22 | **Describe the observed behavior** 23 | 24 | 25 | -------------------------------------------------------------------------------- /tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["tslint-config-prettier"], 3 | "rules": { 4 | "class-name": true, 5 | "comment-format": [true, "check-space"], 6 | "curly": false, 7 | "indent": [true, "spaces"], 8 | "interface-name": false, 9 | "jsdoc-format": true, 10 | "no-consecutive-blank-lines": true, 11 | "no-debugger": true, 12 | "no-duplicate-variable": true, 13 | "no-eval": true, 14 | "no-internal-module": true, 15 | "no-shadowed-variable": true, 16 | "no-switch-case-fall-through": true, 17 | "no-unused-expression": true, 18 | "no-use-before-declare": false, 19 | "no-var-keyword": true, 20 | "one-line": [true, "check-open-brace", "check-whitespace", "check-catch"], 21 | "trailing-comma": false, 22 | "triple-equals": [true, "allow-null-check"], 23 | "variable-name": [true, "ban-keywords"] 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /.github/lock.yml: -------------------------------------------------------------------------------- 1 | # Configuration for Lock Threads - https://github.com/dessant/lock-threads 2 | 3 | # Number of days of inactivity before a closed issue or pull request is locked 4 | daysUntilLock: 60 5 | 6 | # Skip issues and pull requests created before a given timestamp. Timestamp must 7 | # follow ISO 8601 (`YYYY-MM-DD`). Set to `false` to disable 8 | skipCreatedBefore: 2019-01-01 9 | 10 | # Issues and pull requests with these labels will be ignored. Set to `[]` to disable 11 | exemptLabels: [] 12 | 13 | # Label to add before locking, such as `outdated`. Set to `false` to disable 14 | lockLabel: false 15 | 16 | # Comment to post before locking. Set to `false` to disable 17 | lockComment: > 18 | This thread has been automatically locked since there has not been 19 | any recent activity after it was closed. Please open a new issue for 20 | related bugs or questions. 21 | 22 | # Assign `resolved` as the reason for locking. Set to `false` to disable 23 | setLockReason: true 24 | 25 | # Limit to only `issues` or `pulls` 26 | only: issues 27 | -------------------------------------------------------------------------------- /__tests__/core/circular1.test.ts: -------------------------------------------------------------------------------- 1 | import { types } from "../../src" 2 | import { LateTodo2, LateStore2 } from "./circular2.test" 3 | import { expect, test } from "bun:test" 4 | // combine function hosting with types.late to support circular refs between files! 5 | export function LateStore1() { 6 | return types.model({ 7 | todo: types.late(LateTodo2) 8 | }) 9 | } 10 | export function LateTodo1() { 11 | return types.model({ 12 | done: types.boolean 13 | }) 14 | } 15 | 16 | test("circular test 1 should work", () => { 17 | const Store1 = types.late(LateStore1) 18 | const Store2 = types.late(LateStore2) 19 | expect(Store1.is({})).toBe(false) 20 | expect(Store1.is({ todo: { done: true } })).toBe(true) 21 | const s1 = Store1.create({ todo: { done: true } }) 22 | expect(s1.todo.done).toBe(true) 23 | expect(Store2.is({})).toBe(false) 24 | expect(Store2.is({ todo: { done: true } })).toBe(true) 25 | const s2 = Store2.create({ todo: { done: true } }) 26 | expect(s2.todo.done).toBe(true) 27 | }) 28 | -------------------------------------------------------------------------------- /__tests__/core/circular2.test.ts: -------------------------------------------------------------------------------- 1 | import { types } from "../../src" 2 | import { LateTodo1, LateStore1 } from "./circular1.test" 3 | import { expect, test } from "bun:test" 4 | // combine function hosting with types.late to support circular refs between files! 5 | export function LateTodo2() { 6 | return types.model({ 7 | done: types.boolean 8 | }) 9 | } 10 | export function LateStore2() { 11 | return types.model({ 12 | todo: types.late(LateTodo1) 13 | }) 14 | } 15 | 16 | test("circular test 2 should work", () => { 17 | const Store1 = types.late(LateStore1) 18 | const Store2 = types.late(LateStore2) 19 | expect(Store1.is({})).toBe(false) 20 | expect(Store1.is({ todo: { done: true } })).toBe(true) 21 | const s1 = Store1.create({ todo: { done: true } }) 22 | expect(s1.todo.done).toBe(true) 23 | expect(Store2.is({})).toBe(false) 24 | expect(Store2.is({ todo: { done: true } })).toBe(true) 25 | const s2 = Store2.create({ todo: { done: true } }) 26 | expect(s2.todo.done).toBe(true) 27 | }) 28 | -------------------------------------------------------------------------------- /docs/API/interfaces/imodelreflectionpropertiesdata.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: "imodelreflectionpropertiesdata" 3 | title: "IModelReflectionPropertiesData" 4 | sidebar_label: "IModelReflectionPropertiesData" 5 | --- 6 | 7 | [mobx-state-tree - v7.0.2](../index.md) › [IModelReflectionPropertiesData](imodelreflectionpropertiesdata.md) 8 | 9 | ## Hierarchy 10 | 11 | * **IModelReflectionPropertiesData** 12 | 13 | ↳ [IModelReflectionData](imodelreflectiondata.md) 14 | 15 | ## Index 16 | 17 | ### Properties 18 | 19 | * [name](imodelreflectionpropertiesdata.md#name) 20 | * [properties](imodelreflectionpropertiesdata.md#properties) 21 | 22 | ## Properties 23 | 24 | ### name 25 | 26 | • **name**: *string* 27 | 28 | *Defined in [src/core/mst-operations.ts:825](https://github.com/mobxjs/mobx-state-tree/blob/1be40a3e/src/core/mst-operations.ts#L825)* 29 | 30 | ___ 31 | 32 | ### properties 33 | 34 | • **properties**: *object* 35 | 36 | *Defined in [src/core/mst-operations.ts:826](https://github.com/mobxjs/mobx-state-tree/blob/1be40a3e/src/core/mst-operations.ts#L826)* 37 | 38 | #### Type declaration: 39 | 40 | * \[ **K**: *string*\]: [IAnyType](ianytype.md) 41 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2016 Michel Weststrate 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /docs/API/interfaces/ivalidationcontextentry.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: "ivalidationcontextentry" 3 | title: "IValidationContextEntry" 4 | sidebar_label: "IValidationContextEntry" 5 | --- 6 | 7 | [mobx-state-tree - v7.0.2](../index.md) › [IValidationContextEntry](ivalidationcontextentry.md) 8 | 9 | Validation context entry, this is, where the validation should run against which type 10 | 11 | ## Hierarchy 12 | 13 | * **IValidationContextEntry** 14 | 15 | ## Index 16 | 17 | ### Properties 18 | 19 | * [path](ivalidationcontextentry.md#path) 20 | * [type](ivalidationcontextentry.md#type) 21 | 22 | ## Properties 23 | 24 | ### path 25 | 26 | • **path**: *string* 27 | 28 | *Defined in [src/core/type/type-checker.ts:17](https://github.com/mobxjs/mobx-state-tree/blob/1be40a3e/src/core/type/type-checker.ts#L17)* 29 | 30 | Subpath where the validation should be run, or an empty string to validate it all 31 | 32 | ___ 33 | 34 | ### type 35 | 36 | • **type**: *[IAnyType](ianytype.md)* 37 | 38 | *Defined in [src/core/type/type-checker.ts:19](https://github.com/mobxjs/mobx-state-tree/blob/1be40a3e/src/core/type/type-checker.ts#L19)* 39 | 40 | Type to validate the subpath against 41 | -------------------------------------------------------------------------------- /docs/intro/installation.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: installation 3 | title: Installation 4 | --- 5 | 6 |
7 | 8 | - NPM: `npm install mobx mobx-state-tree --save` 9 | - Yarn: `yarn add mobx mobx-state-tree` 10 | - CDN: https://unpkg.com/mobx-state-tree/dist/mobx-state-tree.umd.js (exposed as `window.mobxStateTree`) 11 | - CodeSandbox [TodoList demo](https://codesandbox.io/s/y64pzxj01) fork for testing and bug reporting 12 | 13 | TypeScript typings are included in the packages. Use `module: "commonjs"` or `moduleResolution: "node"` to make sure they are picked up automatically in any consuming project. 14 | 15 | Supported environments: 16 | 17 | - MobX-State-Tree 4+ runs in any JavaScript environment, including browsers, Node, React Native (including Hermes), and more 18 | 19 | Supported devtools: 20 | 21 | - [Reactotron](https://github.com/infinitered/reactotron) 22 | - [MobX DevTools](https://chrome.google.com/webstore/detail/mobx-developer-tools/pfgnfdagidkfgccljigdamigbcnndkod) 23 | - The Redux DevTools can be connected as well as demonstrated [here](https://github.com/coolsoftwaretyler/mst-example-redux-todomvc/blob/main/src/index.js#L6) 24 | -------------------------------------------------------------------------------- /docs/API/interfaces/ijsonpatch.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: "ijsonpatch" 3 | title: "IJsonPatch" 4 | sidebar_label: "IJsonPatch" 5 | --- 6 | 7 | [mobx-state-tree - v7.0.2](../index.md) › [IJsonPatch](ijsonpatch.md) 8 | 9 | https://tools.ietf.org/html/rfc6902 10 | http://jsonpatch.com/ 11 | 12 | ## Hierarchy 13 | 14 | * **IJsonPatch** 15 | 16 | ↳ [IReversibleJsonPatch](ireversiblejsonpatch.md) 17 | 18 | ## Index 19 | 20 | ### Properties 21 | 22 | * [op](ijsonpatch.md#op) 23 | * [path](ijsonpatch.md#path) 24 | * [value](ijsonpatch.md#optional-value) 25 | 26 | ## Properties 27 | 28 | ### op 29 | 30 | • **op**: *"replace" | "add" | "remove"* 31 | 32 | *Defined in [src/core/json-patch.ts:8](https://github.com/mobxjs/mobx-state-tree/blob/1be40a3e/src/core/json-patch.ts#L8)* 33 | 34 | ___ 35 | 36 | ### path 37 | 38 | • **path**: *string* 39 | 40 | *Defined in [src/core/json-patch.ts:9](https://github.com/mobxjs/mobx-state-tree/blob/1be40a3e/src/core/json-patch.ts#L9)* 41 | 42 | ___ 43 | 44 | ### `Optional` value 45 | 46 | • **value**? : *any* 47 | 48 | *Defined in [src/core/json-patch.ts:10](https://github.com/mobxjs/mobx-state-tree/blob/1be40a3e/src/core/json-patch.ts#L10)* 49 | -------------------------------------------------------------------------------- /docs/API/interfaces/iserializedactioncall.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: "iserializedactioncall" 3 | title: "ISerializedActionCall" 4 | sidebar_label: "ISerializedActionCall" 5 | --- 6 | 7 | [mobx-state-tree - v7.0.2](../index.md) › [ISerializedActionCall](iserializedactioncall.md) 8 | 9 | ## Hierarchy 10 | 11 | * **ISerializedActionCall** 12 | 13 | ## Index 14 | 15 | ### Properties 16 | 17 | * [args](iserializedactioncall.md#optional-args) 18 | * [name](iserializedactioncall.md#name) 19 | * [path](iserializedactioncall.md#optional-path) 20 | 21 | ## Properties 22 | 23 | ### `Optional` args 24 | 25 | • **args**? : *any[]* 26 | 27 | *Defined in [src/middlewares/on-action.ts:33](https://github.com/mobxjs/mobx-state-tree/blob/1be40a3e/src/middlewares/on-action.ts#L33)* 28 | 29 | ___ 30 | 31 | ### name 32 | 33 | • **name**: *string* 34 | 35 | *Defined in [src/middlewares/on-action.ts:31](https://github.com/mobxjs/mobx-state-tree/blob/1be40a3e/src/middlewares/on-action.ts#L31)* 36 | 37 | ___ 38 | 39 | ### `Optional` path 40 | 41 | • **path**? : *undefined | string* 42 | 43 | *Defined in [src/middlewares/on-action.ts:32](https://github.com/mobxjs/mobx-state-tree/blob/1be40a3e/src/middlewares/on-action.ts#L32)* 44 | -------------------------------------------------------------------------------- /.github/stale.yml: -------------------------------------------------------------------------------- 1 | # Number of days of inactivity before an issue becomes stale 2 | daysUntilStale: 10 3 | # Number of days of inactivity before a stale issue is closed 4 | daysUntilClose: 4 5 | # Issues with these labels will never be considered stale 6 | exemptLabels: 7 | - brainstorming/wild idea 8 | - breaking change 9 | - bug 10 | - docs or examples 11 | - enhancement 12 | - has PR 13 | - help/PR welcome 14 | - require('@mweststrate') 15 | - never-stale 16 | # Label to use when marking an issue as stale 17 | staleLabel: stale 18 | # Comment to post when marking an issue as stale. Set to `false` to disable 19 | markComment: > 20 | This issue has been automatically marked as stale because it has not had 21 | recent activity in the last 10 days. It will be closed in 4 days if no further 22 | activity occurs. Thank you for your contributions. 23 | # Comment to post when closing a stale issue. Set to `false` to disable 24 | closeComment: false 25 | # Limit to only `issues` or `pulls` 26 | only: issues 27 | # Comment to post when removing the stale label. 28 | unmarkComment: > 29 | This issue has been automatically unmarked as stale. Please disregard previous warnings. 30 | -------------------------------------------------------------------------------- /docs/API/interfaces/iactiontrackingmiddleware2call.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: "iactiontrackingmiddleware2call" 3 | title: "IActionTrackingMiddleware2Call" 4 | sidebar_label: "IActionTrackingMiddleware2Call" 5 | --- 6 | 7 | [mobx-state-tree - v7.0.2](../index.md) › [IActionTrackingMiddleware2Call](iactiontrackingmiddleware2call.md) 8 | 9 | ## Type parameters 10 | 11 | ▪ **TEnv** 12 | 13 | ## Hierarchy 14 | 15 | * object 16 | 17 | ↳ **IActionTrackingMiddleware2Call** 18 | 19 | ## Index 20 | 21 | ### Properties 22 | 23 | * [env](iactiontrackingmiddleware2call.md#env) 24 | * [parentCall](iactiontrackingmiddleware2call.md#optional-parentcall) 25 | 26 | ## Properties 27 | 28 | ### env 29 | 30 | • **env**: *TEnv | undefined* 31 | 32 | *Defined in [src/middlewares/createActionTrackingMiddleware2.ts:4](https://github.com/mobxjs/mobx-state-tree/blob/1be40a3e/src/middlewares/createActionTrackingMiddleware2.ts#L4)* 33 | 34 | ___ 35 | 36 | ### `Optional` parentCall 37 | 38 | • **parentCall**? : *[IActionTrackingMiddleware2Call](iactiontrackingmiddleware2call.md)‹TEnv›* 39 | 40 | *Defined in [src/middlewares/createActionTrackingMiddleware2.ts:5](https://github.com/mobxjs/mobx-state-tree/blob/1be40a3e/src/middlewares/createActionTrackingMiddleware2.ts#L5)* 41 | -------------------------------------------------------------------------------- /src/types/index.ts: -------------------------------------------------------------------------------- 1 | // we import the types to re-export them inside types. 2 | import { 3 | enumeration, 4 | model, 5 | compose, 6 | custom, 7 | reference, 8 | safeReference, 9 | union, 10 | optional, 11 | literal, 12 | maybe, 13 | maybeNull, 14 | refinement, 15 | string, 16 | boolean, 17 | number, 18 | integer, 19 | float, 20 | finite, 21 | DatePrimitive, 22 | map, 23 | array, 24 | frozen, 25 | identifier, 26 | identifierNumber, 27 | late, 28 | lazy, 29 | undefinedType, 30 | nullType, 31 | snapshotProcessor 32 | } from "../internal" 33 | 34 | export const types = { 35 | enumeration, 36 | model, 37 | compose, 38 | custom, 39 | reference, 40 | safeReference, 41 | union, 42 | optional, 43 | literal, 44 | maybe, 45 | maybeNull, 46 | refinement, 47 | string, 48 | boolean, 49 | number, 50 | integer, 51 | float, 52 | finite, 53 | Date: DatePrimitive, 54 | map, 55 | array, 56 | frozen, 57 | identifier, 58 | identifierNumber, 59 | late, 60 | lazy, 61 | undefined: undefinedType, 62 | null: nullType, 63 | snapshotProcessor 64 | } 65 | -------------------------------------------------------------------------------- /__tests__/perf/perf.skip.ts: -------------------------------------------------------------------------------- 1 | import { smallScenario, mediumScenario, largeScenario } from "./scenarios" 2 | import { start } from "./timer" 3 | import { expect, test } from "bun:test" 4 | 5 | // TODO: Not sure how this should work. This feels super fragile. 6 | const TOO_SLOW_MS = 10000 7 | 8 | test("performs well on small scenario", () => { 9 | expect(smallScenario(10).elapsed < TOO_SLOW_MS).toBe(true) 10 | }) 11 | 12 | test("performs well on medium scenario", () => { 13 | expect(mediumScenario(10).elapsed < TOO_SLOW_MS).toBe(true) 14 | }) 15 | 16 | test("performs well on large scenario", () => { 17 | expect(largeScenario(10, 0, 0).elapsed < TOO_SLOW_MS).toBe(true) 18 | expect(largeScenario(10, 10, 0).elapsed < TOO_SLOW_MS).toBe(true) 19 | expect(largeScenario(10, 0, 10).elapsed < TOO_SLOW_MS).toBe(true) 20 | expect(largeScenario(10, 10, 10).elapsed < TOO_SLOW_MS).toBe(true) 21 | }) 22 | 23 | test("timer", () => async () => { 24 | const go = start() 25 | 26 | await new Promise(resolve => setTimeout(resolve, 2)) 27 | const lap = go(true) 28 | 29 | await new Promise(resolve => setTimeout(resolve, 2)) 30 | const d = go() 31 | expect(lap).not.toBe(0) 32 | expect(d).not.toBe(0) 33 | expect(lap).not.toBe(d) 34 | }) 35 | -------------------------------------------------------------------------------- /docs/tips/circular-deps.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: circular-deps 3 | sidebar_label: Circular dependencies 4 | title: Handle circular dependencies between files and types using `late` 5 | --- 6 | 7 |
8 | 9 | 10 | In the exporting file: 11 | 12 | ```javascript 13 | export function LateStore() { 14 | return types.model({ 15 | title: types.string 16 | }) 17 | } 18 | ``` 19 | 20 | In the importing file: 21 | 22 | ```javascript 23 | import { LateStore } from "./circular-dep" 24 | 25 | const Store = types.late(() => LateStore) 26 | ``` 27 | 28 | Thanks to function hoisting in combination with `types.late`, this lets you have circular dependencies between types, across files. 29 | 30 | If you are using TypeScript and you get errors about circular or self-referencing types then you can partially fix it by doing: 31 | 32 | ```ts 33 | const Node = types.model({ 34 | x: 5, // as an example 35 | me: types.maybe(types.late((): IAnyModelType => Node)) 36 | }) 37 | ``` 38 | 39 | In this case, while "me" will become any, any other properties (such as x) will be strongly typed, so you can typecast the self referencing properties (me in this case) once more to get typings. For example: 40 | 41 | ```ts 42 | node.((me) as Instance).x // x here will be number 43 | ``` 44 | -------------------------------------------------------------------------------- /docs/API/interfaces/unionoptions.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: "unionoptions" 3 | title: "UnionOptions" 4 | sidebar_label: "UnionOptions" 5 | --- 6 | 7 | [mobx-state-tree - v7.0.2](../index.md) › [UnionOptions](unionoptions.md) 8 | 9 | ## Type parameters 10 | 11 | ▪ **Types**: *[IAnyType](ianytype.md)[]* 12 | 13 | ## Hierarchy 14 | 15 | * **UnionOptions** 16 | 17 | ## Index 18 | 19 | ### Properties 20 | 21 | * [dispatcher](unionoptions.md#optional-dispatcher) 22 | * [eager](unionoptions.md#optional-eager) 23 | 24 | ## Properties 25 | 26 | ### `Optional` dispatcher 27 | 28 | • **dispatcher**? : *[ITypeDispatcher](../index.md#itypedispatcher)‹Types›* 29 | 30 | *Defined in [src/types/utility-types/union.ts:38](https://github.com/mobxjs/mobx-state-tree/blob/1be40a3e/src/types/utility-types/union.ts#L38)* 31 | 32 | A function that returns the type to be used given an input snapshot. 33 | 34 | ___ 35 | 36 | ### `Optional` eager 37 | 38 | • **eager**? : *undefined | false | true* 39 | 40 | *Defined in [src/types/utility-types/union.ts:33](https://github.com/mobxjs/mobx-state-tree/blob/1be40a3e/src/types/utility-types/union.ts#L33)* 41 | 42 | Whether or not to use eager validation. 43 | 44 | When `true`, the first matching type will be used. Otherwise, all types will be checked and the 45 | validation will pass if and only if a single type matches. 46 | -------------------------------------------------------------------------------- /docs/API/interfaces/ivalidationerror.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: "ivalidationerror" 3 | title: "IValidationError" 4 | sidebar_label: "IValidationError" 5 | --- 6 | 7 | [mobx-state-tree - v7.0.2](../index.md) › [IValidationError](ivalidationerror.md) 8 | 9 | Type validation error 10 | 11 | ## Hierarchy 12 | 13 | * **IValidationError** 14 | 15 | ## Index 16 | 17 | ### Properties 18 | 19 | * [context](ivalidationerror.md#context) 20 | * [message](ivalidationerror.md#optional-message) 21 | * [value](ivalidationerror.md#value) 22 | 23 | ## Properties 24 | 25 | ### context 26 | 27 | • **context**: *[IValidationContext](../index.md#ivalidationcontext)* 28 | 29 | *Defined in [src/core/type/type-checker.ts:28](https://github.com/mobxjs/mobx-state-tree/blob/1be40a3e/src/core/type/type-checker.ts#L28)* 30 | 31 | Validation context 32 | 33 | ___ 34 | 35 | ### `Optional` message 36 | 37 | • **message**? : *undefined | string* 38 | 39 | *Defined in [src/core/type/type-checker.ts:32](https://github.com/mobxjs/mobx-state-tree/blob/1be40a3e/src/core/type/type-checker.ts#L32)* 40 | 41 | Error message 42 | 43 | ___ 44 | 45 | ### value 46 | 47 | • **value**: *any* 48 | 49 | *Defined in [src/core/type/type-checker.ts:30](https://github.com/mobxjs/mobx-state-tree/blob/1be40a3e/src/core/type/type-checker.ts#L30)* 50 | 51 | Value that was being validated, either a snapshot or an instance 52 | -------------------------------------------------------------------------------- /__tests__/core/1525.test.ts: -------------------------------------------------------------------------------- 1 | import { types, Instance } from "../../src/index" 2 | import { describe, it } from "bun:test" 3 | 4 | describe("1525. Model instance maybe fields becoming TypeScript optional fields when included in a types.union", () => { 5 | it("does not throw a typescript error", () => { 6 | const Model = types.model("myModel", { 7 | foo: types.string, 8 | bar: types.maybe(types.integer) 9 | }) 10 | 11 | const Store = types.model("store", { 12 | itemWithoutIssue: Model, 13 | itemWithIssue: types.union(types.literal("anotherValue"), Model) 14 | }) 15 | 16 | interface IModel extends Instance {} 17 | 18 | interface FunctionArgs { 19 | model1: IModel 20 | model2: IModel 21 | } 22 | 23 | const store = Store.create({ 24 | itemWithoutIssue: { foo: "works" }, 25 | itemWithIssue: { foo: "has ts error in a regression" } 26 | }) 27 | 28 | const f = (props: FunctionArgs) => {} 29 | 30 | const itemWithoutIssueModel = store.itemWithoutIssue 31 | const itemWithIssueModel = 32 | store.itemWithIssue === "anotherValue" ? null : store.itemWithIssue 33 | itemWithIssueModel && f({ model1: itemWithoutIssueModel, model2: itemWithIssueModel }) 34 | }) 35 | }) 36 | -------------------------------------------------------------------------------- /docs/concepts/react.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: using-react 3 | title: React and MST 4 | --- 5 | 6 |
7 | 8 |
9 | egghead.io lesson 5: Render mobx-state-tree Models in React 10 |
11 |
12 | 13 |
14 | Hosted on egghead.io 15 |
16 | 17 | ### Can I use React and MST together? 18 | 19 | Yep, that works perfectly fine, everything that applies to MobX and React applies to MST and React as well. `observer`, `autorun`, etc. will work as expected. 20 | To share MST trees between components we recommend to use `React.createContext`. 21 | 22 | In the examples folder several examples of React and MST can be found, or check this [example](https://github.com/impulse/react-hooks-mobx-state-tree) which uses hooks (recommended). 23 | 24 | ### Tips 25 | 26 | When passing models in to a component **do not** use the spread syntax, e.g. ``. See [here](https://github.com/mobxjs/mobx-state-tree/issues/726). 27 | -------------------------------------------------------------------------------- /scripts/generate-compose-type.js: -------------------------------------------------------------------------------- 1 | const { getDeclaration } = require("./generate-shared") 2 | 3 | let str = `// generated with ${__filename}\n` 4 | 5 | const minArgs = 2 6 | const maxArgs = 10 7 | const preParam = "name: string, " 8 | 9 | const returnTypeTransform = (rt) => { 10 | // [['PA', 'PB', 'PC'], ['OA', 'OB', 'OC'], ['FCA', 'FCB', 'FCC'], ['FSA', 'FSB', 'FSC']] 11 | // -> 12 | // [['PA', 'PB', 'PC'], no change 13 | // ['OA', 'OB', 'OC'], no change 14 | // ['_CustomJoin>'] 15 | // ['_CustomJoin>']] 16 | 17 | const [props, others, fixedC, fixedS] = rt 18 | 19 | function customJoin(left) { 20 | if (left.length === 1) { 21 | return left[0] 22 | } 23 | const [a, ...rest] = left 24 | return `_CustomJoin<${a}, ${customJoin(rest)}>` 25 | } 26 | 27 | return [props, others, [customJoin(fixedC)], [customJoin(fixedS)]] 28 | } 29 | 30 | for (let i = minArgs; i < maxArgs; i++) { 31 | str += getDeclaration( 32 | "compose", 33 | "IModelType", 34 | ["P", "O", "FC", "FS"], 35 | i, 36 | preParam, 37 | "&", 38 | "IModelType", 39 | returnTypeTransform 40 | ) 41 | str += getDeclaration( 42 | "compose", 43 | "IModelType", 44 | ["P", "O", "FC", "FS"], 45 | i, 46 | null, 47 | "&", 48 | "IModelType", 49 | returnTypeTransform 50 | ) 51 | } 52 | 53 | console.log(str) 54 | -------------------------------------------------------------------------------- /src/types/utility-types/maybe.ts: -------------------------------------------------------------------------------- 1 | import { 2 | union, 3 | optional, 4 | IType, 5 | undefinedType, 6 | nullType, 7 | IAnyType, 8 | assertIsType 9 | } from "../../internal" 10 | 11 | const optionalUndefinedType = optional(undefinedType, undefined) 12 | const optionalNullType = optional(nullType, null) 13 | 14 | /** @hidden */ 15 | export interface IMaybeIType 16 | extends IType {} 17 | 18 | /** @hidden */ 19 | export interface IMaybe extends IMaybeIType {} 20 | 21 | /** @hidden */ 22 | export interface IMaybeNull extends IMaybeIType {} 23 | 24 | /** 25 | * `types.maybe` - Maybe will make a type nullable, and also optional. 26 | * The value `undefined` will be used to represent nullability. 27 | * 28 | * @param type 29 | * @returns 30 | */ 31 | export function maybe(type: IT): IMaybe { 32 | assertIsType(type, 1) 33 | 34 | return union(type, optionalUndefinedType) 35 | } 36 | 37 | /** 38 | * `types.maybeNull` - Maybe will make a type nullable, and also optional. 39 | * The value `null` will be used to represent no value. 40 | * 41 | * @param type 42 | * @returns 43 | */ 44 | export function maybeNull(type: IT): IMaybeNull { 45 | assertIsType(type, 1) 46 | 47 | return union(type, optionalNullType) 48 | } 49 | -------------------------------------------------------------------------------- /rollup.config.js: -------------------------------------------------------------------------------- 1 | import * as path from "path" 2 | import filesize from "rollup-plugin-filesize" 3 | import resolve from "rollup-plugin-node-resolve" 4 | import { terser } from "rollup-plugin-terser" 5 | import replace from "rollup-plugin-replace" 6 | 7 | const devPlugins = () => [resolve(), filesize()] 8 | 9 | // For umd builds, set process.env.NODE_ENV to "development" since 'process' is not available in the browser 10 | const devPluginsUmd = () => [ 11 | resolve(), 12 | replace({ "process.env.NODE_ENV": JSON.stringify("development") }), 13 | filesize() 14 | ] 15 | 16 | const prodPlugins = () => [ 17 | resolve(), 18 | replace({ "process.env.NODE_ENV": JSON.stringify("production") }), 19 | terser(), 20 | filesize() 21 | ] 22 | 23 | const config = (outFile, format, mode) => ({ 24 | input: "./lib/src/index.js", 25 | output: { 26 | file: path.join("./dist", outFile), 27 | format: format, 28 | globals: { 29 | mobx: "mobx" 30 | }, 31 | name: format === "umd" ? "mobxStateTree" : undefined 32 | }, 33 | external: ["mobx"], 34 | plugins: mode === "production" ? prodPlugins() : format === "umd" ? devPluginsUmd() : devPlugins() 35 | }) 36 | 37 | export default [ 38 | config("mobx-state-tree.js", "cjs", "development"), 39 | config("mobx-state-tree.min.js", "cjs", "production"), 40 | config("mobx-state-tree.umd.js", "umd", "development"), 41 | config("mobx-state-tree.umd.min.js", "umd", "production"), 42 | config("mobx-state-tree.module.js", "es", "development") 43 | ] 44 | -------------------------------------------------------------------------------- /docs/API/interfaces/ihooks.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: "ihooks" 3 | title: "IHooks" 4 | sidebar_label: "IHooks" 5 | --- 6 | 7 | [mobx-state-tree - v7.0.2](../index.md) › [IHooks](ihooks.md) 8 | 9 | ## Hierarchy 10 | 11 | * **IHooks** 12 | 13 | ## Index 14 | 15 | ### Properties 16 | 17 | * [[Hook.afterAttach]](ihooks.md#optional-[hook.afterattach]) 18 | * [[Hook.afterCreate]](ihooks.md#optional-[hook.aftercreate]) 19 | * [[Hook.beforeDestroy]](ihooks.md#optional-[hook.beforedestroy]) 20 | * [[Hook.beforeDetach]](ihooks.md#optional-[hook.beforedetach]) 21 | 22 | ## Properties 23 | 24 | ### `Optional` [Hook.afterAttach] 25 | 26 | • **[Hook.afterAttach]**? : *undefined | function* 27 | 28 | *Defined in [src/core/node/Hook.ts:14](https://github.com/mobxjs/mobx-state-tree/blob/1be40a3e/src/core/node/Hook.ts#L14)* 29 | 30 | ___ 31 | 32 | ### `Optional` [Hook.afterCreate] 33 | 34 | • **[Hook.afterCreate]**? : *undefined | function* 35 | 36 | *Defined in [src/core/node/Hook.ts:13](https://github.com/mobxjs/mobx-state-tree/blob/1be40a3e/src/core/node/Hook.ts#L13)* 37 | 38 | ___ 39 | 40 | ### `Optional` [Hook.beforeDestroy] 41 | 42 | • **[Hook.beforeDestroy]**? : *undefined | function* 43 | 44 | *Defined in [src/core/node/Hook.ts:16](https://github.com/mobxjs/mobx-state-tree/blob/1be40a3e/src/core/node/Hook.ts#L16)* 45 | 46 | ___ 47 | 48 | ### `Optional` [Hook.beforeDetach] 49 | 50 | • **[Hook.beforeDetach]**? : *undefined | function* 51 | 52 | *Defined in [src/core/node/Hook.ts:15](https://github.com/mobxjs/mobx-state-tree/blob/1be40a3e/src/core/node/Hook.ts#L15)* 53 | -------------------------------------------------------------------------------- /__tests__/perf/fixture-data.skip.ts: -------------------------------------------------------------------------------- 1 | import { rando, createHeros, createMonsters, createTreasure } from "./fixtures/fixture-data" 2 | import { Hero, Monster, Treasure } from "./fixtures/fixture-models" 3 | import { expect, test } from "bun:test" 4 | 5 | test("createHeros", () => { 6 | const data = createHeros(10) 7 | expect(data.length).toBe(10) 8 | const hero = Hero.create(data[0]) 9 | expect(hero.descriptionLength > 1).toBe(true) 10 | }) 11 | test("createMonsters", () => { 12 | const data = createMonsters(10, 10, 10) 13 | expect(data.length).toBe(10) 14 | expect(data[1].treasures.length).toBe(10) 15 | expect(data[0].eatenHeroes.length).toBe(10) 16 | const monster = Monster.create(data[0]) 17 | expect(monster.eatenHeroes && monster.eatenHeroes.length === 10).toBe(true) 18 | expect(monster.treasures.length === 10).toBe(true) 19 | }) 20 | test("createTreasure", () => { 21 | const data = createTreasure(10) 22 | expect(data.length).toBe(10) 23 | const treasure = Treasure.create(data[1]) 24 | expect(treasure.gold > 0).toBe(true) 25 | }) 26 | test("rando sorting", () => { 27 | // i'm going straight to hell for this test... must get coverage to 100%.... no matter the cost. 28 | let foundTrue = false 29 | let foundFalse = false 30 | let result 31 | do { 32 | result = rando() 33 | if (result) { 34 | foundTrue = true 35 | } else { 36 | foundFalse = true 37 | } 38 | } while (!foundTrue || !foundFalse) 39 | expect(foundTrue).toBe(true) 40 | expect(foundFalse).toBe(true) 41 | }) 42 | -------------------------------------------------------------------------------- /__tests__/core/optimizations.test.ts: -------------------------------------------------------------------------------- 1 | import { getSnapshot, applySnapshot, unprotect, types } from "../../src" 2 | import { expect, test } from "bun:test" 3 | 4 | test("it should avoid processing patch if is exactly the current one in applySnapshot", () => { 5 | const Model = types.model({ 6 | a: types.number, 7 | b: types.string 8 | }) 9 | const store = Model.create({ a: 1, b: "hello" }) 10 | const snapshot = getSnapshot(store) 11 | applySnapshot(store, snapshot) 12 | expect(getSnapshot(store)).toBe(snapshot) // no new snapshot emitted 13 | }) 14 | test("it should avoid processing patch if is exactly the current one in reconcile", () => { 15 | const Model = types.model({ 16 | a: types.number, 17 | b: types.string 18 | }) 19 | const RootModel = types.model({ 20 | a: Model 21 | }) 22 | const store = RootModel.create({ a: { a: 1, b: "hello" } }) 23 | unprotect(store) 24 | // NOTE: snapshots are not equal after property access anymore, 25 | // so we test initial and actual ones separately 26 | const snapshot = getSnapshot(store) 27 | expect(getSnapshot(store)).toEqual(snapshot) 28 | 29 | store.a = snapshot.a 30 | // check whether reconciliation works on initial values 31 | expect(getSnapshot(store)).toEqual(snapshot) 32 | 33 | // access property to initialize observable instance 34 | expect(getSnapshot(store.a)).toEqual(snapshot.a) 35 | 36 | // check whether initializing instance does not cause snapshot invalidation 37 | const actualSnapshot = getSnapshot(store) 38 | expect(actualSnapshot.a).toBe(snapshot.a) 39 | }) 40 | -------------------------------------------------------------------------------- /__tests__/core/1664.test.ts: -------------------------------------------------------------------------------- 1 | import { types as t } from "../../src/index" 2 | import { describe, test } from "bun:test" 3 | 4 | describe("1664. Array and model types are not inferred correctly when broken down into their components", () => { 5 | test("should not throw a typescript error", () => { 6 | // Simple concrete type with a creation type different than its instance type 7 | const date = t.custom({ 8 | name: "Date", 9 | fromSnapshot: snapshot => new Date(snapshot), 10 | toSnapshot: dt => dt.toISOString(), 11 | isTargetType: (val: unknown) => val instanceof Date, 12 | getValidationMessage: (snapshot: unknown) => 13 | typeof snapshot !== "string" || isNaN(Date.parse(snapshot)) 14 | ? `${snapshot} is not a valid Date string` 15 | : "" 16 | }) 17 | 18 | //Wrap the date type in an array type. IArrayType is a sub-interface of IType. 19 | const DateArray = t.array(date) 20 | 21 | //Pass the array type to t.union, which infers the component types as 22 | const LoadableDateArray = t.union(t.literal("loading"), DateArray) 23 | 24 | //Instantiate the type 25 | const lda = LoadableDateArray.create([]) 26 | 27 | //Try to use the array type as an instance 28 | if (lda !== "loading") { 29 | //Error: type of lda is essentially `(string | Date)[] | undefined` 30 | //The creation type has been mixed together with the instance type 31 | const dateArray: Date[] = lda 32 | } 33 | }) 34 | }) 35 | -------------------------------------------------------------------------------- /docs/API/interfaces/ireversiblejsonpatch.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: "ireversiblejsonpatch" 3 | title: "IReversibleJsonPatch" 4 | sidebar_label: "IReversibleJsonPatch" 5 | --- 6 | 7 | [mobx-state-tree - v7.0.2](../index.md) › [IReversibleJsonPatch](ireversiblejsonpatch.md) 8 | 9 | ## Hierarchy 10 | 11 | * [IJsonPatch](ijsonpatch.md) 12 | 13 | ↳ **IReversibleJsonPatch** 14 | 15 | ## Index 16 | 17 | ### Properties 18 | 19 | * [oldValue](ireversiblejsonpatch.md#oldvalue) 20 | * [op](ireversiblejsonpatch.md#op) 21 | * [path](ireversiblejsonpatch.md#path) 22 | * [value](ireversiblejsonpatch.md#optional-value) 23 | 24 | ## Properties 25 | 26 | ### oldValue 27 | 28 | • **oldValue**: *any* 29 | 30 | *Defined in [src/core/json-patch.ts:14](https://github.com/mobxjs/mobx-state-tree/blob/1be40a3e/src/core/json-patch.ts#L14)* 31 | 32 | ___ 33 | 34 | ### op 35 | 36 | • **op**: *"replace" | "add" | "remove"* 37 | 38 | *Inherited from [IJsonPatch](ijsonpatch.md).[op](ijsonpatch.md#op)* 39 | 40 | *Defined in [src/core/json-patch.ts:8](https://github.com/mobxjs/mobx-state-tree/blob/1be40a3e/src/core/json-patch.ts#L8)* 41 | 42 | ___ 43 | 44 | ### path 45 | 46 | • **path**: *string* 47 | 48 | *Inherited from [IJsonPatch](ijsonpatch.md).[path](ijsonpatch.md#path)* 49 | 50 | *Defined in [src/core/json-patch.ts:9](https://github.com/mobxjs/mobx-state-tree/blob/1be40a3e/src/core/json-patch.ts#L9)* 51 | 52 | ___ 53 | 54 | ### `Optional` value 55 | 56 | • **value**? : *any* 57 | 58 | *Inherited from [IJsonPatch](ijsonpatch.md).[value](ijsonpatch.md#optional-value)* 59 | 60 | *Defined in [src/core/json-patch.ts:10](https://github.com/mobxjs/mobx-state-tree/blob/1be40a3e/src/core/json-patch.ts#L10)* 61 | -------------------------------------------------------------------------------- /docs/API/interfaces/referenceoptionsgetset.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: "referenceoptionsgetset" 3 | title: "ReferenceOptionsGetSet" 4 | sidebar_label: "ReferenceOptionsGetSet" 5 | --- 6 | 7 | [mobx-state-tree - v7.0.2](../index.md) › [ReferenceOptionsGetSet](referenceoptionsgetset.md) 8 | 9 | ## Type parameters 10 | 11 | ▪ **IT**: *[IAnyComplexType](ianycomplextype.md)* 12 | 13 | ## Hierarchy 14 | 15 | * **ReferenceOptionsGetSet** 16 | 17 | ## Index 18 | 19 | ### Methods 20 | 21 | * [get](referenceoptionsgetset.md#get) 22 | * [set](referenceoptionsgetset.md#set) 23 | 24 | ## Methods 25 | 26 | ### get 27 | 28 | ▸ **get**(`identifier`: [ReferenceIdentifier](../index.md#referenceidentifier), `parent`: IAnyStateTreeNode | null): *ReferenceT‹IT›* 29 | 30 | *Defined in [src/types/utility-types/reference.ts:472](https://github.com/mobxjs/mobx-state-tree/blob/1be40a3e/src/types/utility-types/reference.ts#L472)* 31 | 32 | **Parameters:** 33 | 34 | Name | Type | 35 | ------ | ------ | 36 | `identifier` | [ReferenceIdentifier](../index.md#referenceidentifier) | 37 | `parent` | IAnyStateTreeNode | null | 38 | 39 | **Returns:** *ReferenceT‹IT›* 40 | 41 | ___ 42 | 43 | ### set 44 | 45 | ▸ **set**(`value`: ReferenceT‹IT›, `parent`: IAnyStateTreeNode | null): *[ReferenceIdentifier](../index.md#referenceidentifier)* 46 | 47 | *Defined in [src/types/utility-types/reference.ts:473](https://github.com/mobxjs/mobx-state-tree/blob/1be40a3e/src/types/utility-types/reference.ts#L473)* 48 | 49 | **Parameters:** 50 | 51 | Name | Type | 52 | ------ | ------ | 53 | `value` | ReferenceT‹IT› | 54 | `parent` | IAnyStateTreeNode | null | 55 | 56 | **Returns:** *[ReferenceIdentifier](../index.md#referenceidentifier)* 57 | -------------------------------------------------------------------------------- /scripts/generate-shared.js: -------------------------------------------------------------------------------- 1 | const alfa = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" 2 | 3 | function getTemplateVar(templateVar, argNumber) { 4 | return `${templateVar}${alfa[argNumber]}` 5 | } 6 | 7 | function getTemplateVars(templateVars, argNumber) { 8 | return templateVars.map((tv) => getTemplateVar(tv, argNumber)) 9 | } 10 | 11 | exports.getDeclaration = function getDeclaration( 12 | funcName, 13 | type, 14 | templateVars, 15 | args, 16 | preParam, 17 | operationChar, 18 | outType = type, 19 | allReturnTypesTransform = (x) => x 20 | ) { 21 | let str = "// prettier-ignore\n" 22 | 23 | let allTemplateVars = [] 24 | for (let i = 0; i < args; i++) { 25 | allTemplateVars = allTemplateVars.concat(getTemplateVars(templateVars, i)) 26 | } 27 | allTemplateVars = allTemplateVars.map((tv) => 28 | tv.startsWith("P") ? `${tv} extends ModelProperties` : tv 29 | ) 30 | str += `export function ${funcName}<${allTemplateVars.join(", ")}>(` 31 | 32 | if (preParam) { 33 | str += preParam 34 | } 35 | 36 | const allParams = [] 37 | for (let i = 0; i < args; i++) { 38 | allParams.push(`${alfa[i]}: ${type}<${getTemplateVars(templateVars, i).join(", ")}>`) 39 | } 40 | str += `${allParams.join(", ")}` 41 | str += ")" 42 | 43 | let allReturnTypes = [] 44 | for (const templateVar of templateVars) { 45 | let union = [] 46 | for (let i = 0; i < args; i++) { 47 | union.push(getTemplateVar(templateVar, i)) 48 | } 49 | allReturnTypes.push(union) 50 | } 51 | allReturnTypes = allReturnTypesTransform(allReturnTypes) 52 | str += `: ${outType}<${allReturnTypes.map((u) => u.join(` ${operationChar} `)).join(", ")}>` 53 | 54 | return str + "\n" 55 | } 56 | -------------------------------------------------------------------------------- /__tests__/perf/fixture-models.skip.ts: -------------------------------------------------------------------------------- 1 | import { Hero, Monster, Treasure } from "./fixtures/fixture-models" 2 | import { expect, test } from "bun:test" 3 | const mst = require("../../dist/mobx-state-tree.umd") 4 | const { unprotect } = mst 5 | 6 | const SAMPLE_HERO = { 7 | id: 1, 8 | name: "jimmy", 9 | level: 1, 10 | role: "cleric", 11 | description: "hi" 12 | } 13 | test("Hero computed fields", () => { 14 | const hero = Hero.create(SAMPLE_HERO) 15 | expect(hero.descriptionLength).toBe(2) 16 | }) 17 | test("Tresure", () => { 18 | const treasure = Treasure.create({ gold: 1, trapped: true }) 19 | expect(treasure.trapped).toBe(true) 20 | expect(treasure.gold).toBe(1) 21 | }) 22 | test("Monster computed fields", () => { 23 | const monster = Monster.create({ 24 | id: "foo", 25 | level: 1, 26 | maxHp: 3, 27 | hp: 1, 28 | warning: "boo!", 29 | createdAt: new Date(), 30 | treasures: [ 31 | { gold: 2, trapped: true }, 32 | { gold: 3, trapped: true } 33 | ], 34 | eatenHeroes: [SAMPLE_HERO], 35 | hasFangs: true, 36 | hasClaws: true, 37 | hasWings: true, 38 | hasGrowl: true, 39 | freestyle: null 40 | }) 41 | expect(monster.isAlive).toBe(true) 42 | expect(monster.isFlashingRed).toBe(true) 43 | unprotect(monster) 44 | expect(monster.weight).toBe(2) 45 | monster.level = 0 46 | monster.hasFangs = false 47 | monster.hasWings = false 48 | monster.eatenHeroes = null 49 | expect(monster.weight).toBe(1) 50 | monster.hp = 0 51 | expect(monster.isFlashingRed).toBe(false) 52 | expect(monster.isAlive).toBe(false) 53 | }) 54 | -------------------------------------------------------------------------------- /src/core/node/livelinessChecking.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Defines what MST should do when running into reads / writes to objects that have died. 3 | * - `"warn"`: Print a warning (default). 4 | * - `"error"`: Throw an exception. 5 | * - "`ignore`": Do nothing. 6 | */ 7 | export type LivelinessMode = "warn" | "error" | "ignore" 8 | 9 | let livelinessChecking: LivelinessMode = "warn" 10 | 11 | /** 12 | * Defines what MST should do when running into reads / writes to objects that have died. 13 | * By default it will print a warning. 14 | * Use the `"error"` option to easy debugging to see where the error was thrown and when the offending read / write took place 15 | * 16 | * @param mode `"warn"`, `"error"` or `"ignore"` 17 | */ 18 | export function setLivelinessChecking(mode: LivelinessMode) { 19 | livelinessChecking = mode 20 | } 21 | 22 | /** 23 | * Returns the current liveliness checking mode. 24 | * 25 | * @returns `"warn"`, `"error"` or `"ignore"` 26 | */ 27 | export function getLivelinessChecking(): LivelinessMode { 28 | return livelinessChecking 29 | } 30 | 31 | /** 32 | * @deprecated use LivelinessMode instead 33 | * @hidden 34 | */ 35 | export type LivelynessMode = LivelinessMode 36 | 37 | /** 38 | * @deprecated use setLivelinessChecking instead 39 | * @hidden 40 | * 41 | * Defines what MST should do when running into reads / writes to objects that have died. 42 | * By default it will print a warning. 43 | * Use the `"error"` option to easy debugging to see where the error was thrown and when the offending read / write took place 44 | * 45 | * @param mode `"warn"`, `"error"` or `"ignore"` 46 | */ 47 | export function setLivelynessChecking(mode: LivelinessMode) { 48 | setLivelinessChecking(mode) 49 | } 50 | -------------------------------------------------------------------------------- /docs/concepts/dependency-injection.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: dependency-injection 3 | title: Dependency Injection 4 | --- 5 | 6 |
7 | 8 | When creating a new state tree it is possible to pass in environment specific data by passing an object as the second argument to a `.create` call. 9 | This object should be (shallowly) immutable and can be accessed by any model in the tree by calling `getEnv(self)`. 10 | 11 | This is useful to inject environment or test-specific utilities like a transport layer, loggers, etc. This is also very useful to mock behavior in unit tests or provide instantiated utilities to models without requiring singleton modules. 12 | See also the [bookshop example](https://github.com/mobxjs/mobx-state-tree/blob/a4f25de0c88acf0e239acb85e690e91147a8f0f0/examples/bookshop/src/stores/ShopStore.test.js#L9) for inspiration. 13 | 14 | ```javascript 15 | import { types, getEnv } from "mobx-state-tree" 16 | 17 | const Todo = types 18 | .model({ 19 | title: "" 20 | }) 21 | .actions(self => ({ 22 | setTitle(newTitle) { 23 | // grab injected logger and log 24 | getEnv(self).logger.log("Changed title to: " + newTitle) 25 | self.title = newTitle 26 | } 27 | })) 28 | 29 | const Store = types.model({ 30 | todos: types.array(Todo) 31 | }) 32 | 33 | // setup logger and inject it when the store is created 34 | const logger = { 35 | log(msg) { 36 | console.log(msg) 37 | } 38 | } 39 | 40 | const store = Store.create( 41 | { 42 | todos: [{ title: "Grab tea" }] 43 | }, 44 | { 45 | logger: logger // inject logger to the tree 46 | } 47 | ) 48 | 49 | store.todos[0].setTitle("Grab coffee") 50 | // prints: Changed title to: Grab coffee 51 | ``` 52 | -------------------------------------------------------------------------------- /__tests__/core/literal.test.ts: -------------------------------------------------------------------------------- 1 | import { types } from "../../src" 2 | import { expect, test } from "bun:test" 3 | 4 | if (process.env.NODE_ENV !== "production") { 5 | test("it should allow only primitives", () => { 6 | const error = expect(() => { 7 | types.model({ 8 | complexArg: types.literal({ a: 1 } as any) 9 | }) 10 | }).toThrow("expected primitive as argument") 11 | }) 12 | test("it should fail if not optional and no default provided", () => { 13 | const Factory = types.literal("hello") 14 | expect(() => { 15 | ;(Factory.create as any)() 16 | }).toThrow(/is not assignable to type/) 17 | }) 18 | test("it should throw if a different type is given", () => { 19 | const Factory = types.model("TestFactory", { 20 | shouldBeOne: types.literal(1) 21 | }) 22 | expect(() => { 23 | Factory.create({ shouldBeOne: 2 as any }) 24 | }).toThrow(/is not assignable to type/) 25 | }) 26 | } 27 | test("it should support null type", () => { 28 | const M = types.model({ 29 | nullish: types.null 30 | }) 31 | expect( 32 | M.is({ 33 | nullish: null 34 | }) 35 | ).toBe(true) 36 | expect(M.is({ nullish: undefined })).toBe(false) 37 | expect(M.is({ nullish: 17 })).toBe(false) 38 | }) 39 | test("it should support undefined type", () => { 40 | const M = types.model({ 41 | undefinedish: types.undefined 42 | }) 43 | expect( 44 | M.is({ 45 | undefinedish: undefined 46 | }) 47 | ).toBe(true) 48 | expect(M.is({})).toBe(true) // MWE: disputable, should be false? 49 | expect(M.is({ undefinedish: null })).toBe(false) 50 | expect(M.is({ undefinedish: 17 })).toBe(false) 51 | }) 52 | -------------------------------------------------------------------------------- /__tests__/perf/scenarios.ts: -------------------------------------------------------------------------------- 1 | import { start } from "./timer" 2 | import { Treasure, Hero, Monster } from "./fixtures/fixture-models" 3 | import { createTreasure, createHeros, createMonsters } from "./fixtures/fixture-data" 4 | 5 | /** 6 | * Covers models with a trivial number of fields. 7 | * 8 | * @param count The number of records to create. 9 | */ 10 | export function smallScenario(count: number) { 11 | const data = createTreasure(count) // ready? 12 | const time = start() 13 | const converted = data.map(d => Treasure.create(d)) // go 14 | const elapsed = time() 15 | const sanity = converted.length === count 16 | return { count, elapsed, sanity } 17 | } 18 | /** 19 | * Covers models with a moderate number of fields + 1 computed field. 20 | * 21 | * @param count The number of records to create. 22 | */ 23 | export function mediumScenario(count: number) { 24 | const data = createHeros(count) // ready? 25 | const time = start() 26 | const converted = data.map(d => Hero.create(d)) // go 27 | const elapsed = time() 28 | const sanity = converted.length === count 29 | return { count, elapsed, sanity } 30 | } 31 | /** 32 | * Covers models with a large number of fields. 33 | * 34 | * @param count The number of records to create. 35 | * @param smallChildren The number of small children contained within. 36 | * @param mediumChildren The number of medium children contained within. 37 | */ 38 | export function largeScenario(count: number, smallChildren: number, mediumChildren: number) { 39 | const data = createMonsters(count, smallChildren, mediumChildren) // ready? 40 | const time = start() 41 | const converted = data.map(d => Monster.create(d)) // go 42 | const elapsed = time() 43 | const sanity = converted.length === count 44 | return { count, elapsed, sanity } 45 | } 46 | -------------------------------------------------------------------------------- /__tests__/core/deprecated.test.ts: -------------------------------------------------------------------------------- 1 | import { deprecated } from "../../src/utils" 2 | import { flow, createFlowSpawner } from "../../src/core/flow" 3 | import { process as mstProcess, createProcessSpawner } from "../../src/core/process" 4 | import { expect, jest, test } from "bun:test" 5 | 6 | function createDeprecationListener() { 7 | // clear previous deprecation dedupe keys 8 | deprecated.ids = {} 9 | // save console.warn native implementation 10 | const originalWarn = console.warn 11 | // create spy to track warning call 12 | const spyWarn = (console.warn = jest.fn()) 13 | // return callback to check if warn was called properly 14 | return function isDeprecated() { 15 | // replace original implementation 16 | console.warn = originalWarn 17 | // test for correct log message, if in development 18 | if (process.env.NODE_ENV !== "production") { 19 | expect(spyWarn).toHaveBeenCalledTimes(1) 20 | expect(spyWarn.mock.calls[0][0].message).toMatch(/Deprecation warning:/) 21 | } 22 | } 23 | } 24 | test("`process` should mirror `flow`", () => { 25 | const isDeprecated = createDeprecationListener() 26 | const generator = function* () {} 27 | const flowResult = flow(generator) 28 | const processResult = mstProcess(generator) 29 | expect(processResult.name).toBe(flowResult.name) 30 | isDeprecated() 31 | }) 32 | test("`createProcessSpawner` should mirror `createFlowSpawner`", () => { 33 | const isDeprecated = createDeprecationListener() 34 | const alias = "generatorAlias" 35 | const generator = function* (): IterableIterator {} 36 | const flowSpawnerResult = createFlowSpawner(alias, generator) 37 | const processSpawnerResult = createProcessSpawner(alias, generator) 38 | expect(processSpawnerResult.name).toBe(flowSpawnerResult.name) 39 | isDeprecated() 40 | }) 41 | -------------------------------------------------------------------------------- /docs/API/interfaces/isnapshotprocessors.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: "isnapshotprocessors" 3 | title: "ISnapshotProcessors" 4 | sidebar_label: "ISnapshotProcessors" 5 | --- 6 | 7 | [mobx-state-tree - v7.0.2](../index.md) › [ISnapshotProcessors](isnapshotprocessors.md) 8 | 9 | Snapshot processors. 10 | 11 | ## Type parameters 12 | 13 | ▪ **IT**: *[IAnyType](ianytype.md)* 14 | 15 | ▪ **CustomC** 16 | 17 | ▪ **CustomS** 18 | 19 | ## Hierarchy 20 | 21 | * **ISnapshotProcessors** 22 | 23 | ## Index 24 | 25 | ### Methods 26 | 27 | * [postProcessor](isnapshotprocessors.md#optional-postprocessor) 28 | * [preProcessor](isnapshotprocessors.md#optional-preprocessor) 29 | 30 | ## Methods 31 | 32 | ### `Optional` postProcessor 33 | 34 | ▸ **postProcessor**(`snapshot`: IT["SnapshotType"], `node`: [Instance](../index.md#instance)‹IT›): *_CustomOrOther‹CustomS, IT["SnapshotType"]›* 35 | 36 | *Defined in [src/types/utility-types/snapshotProcessor.ts:230](https://github.com/mobxjs/mobx-state-tree/blob/1be40a3e/src/types/utility-types/snapshotProcessor.ts#L230)* 37 | 38 | Function that transforms an output snapshot. 39 | 40 | **Parameters:** 41 | 42 | Name | Type | Description | 43 | ------ | ------ | ------ | 44 | `snapshot` | IT["SnapshotType"] | | 45 | `node` | [Instance](../index.md#instance)‹IT› | - | 46 | 47 | **Returns:** *_CustomOrOther‹CustomS, IT["SnapshotType"]›* 48 | 49 | ___ 50 | 51 | ### `Optional` preProcessor 52 | 53 | ▸ **preProcessor**(`snapshot`: _CustomOrOther‹CustomC, IT["CreationType"]›): *IT["CreationType"]* 54 | 55 | *Defined in [src/types/utility-types/snapshotProcessor.ts:224](https://github.com/mobxjs/mobx-state-tree/blob/1be40a3e/src/types/utility-types/snapshotProcessor.ts#L224)* 56 | 57 | Function that transforms an input snapshot. 58 | 59 | **Parameters:** 60 | 61 | Name | Type | 62 | ------ | ------ | 63 | `snapshot` | _CustomOrOther‹CustomC, IT["CreationType"]› | 64 | 65 | **Returns:** *IT["CreationType"]* 66 | -------------------------------------------------------------------------------- /src/internal.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * All imports / exports should be proxied through this file. 3 | * Why? It gives us full control over the module load order, preventing circular dependency isses 4 | */ 5 | 6 | export * from "./core/node/livelinessChecking" 7 | export * from "./core/node/Hook" 8 | export * from "./core/mst-operations" 9 | export * from "./core/node/BaseNode" 10 | export * from "./core/node/scalar-node" 11 | export * from "./core/node/object-node" 12 | export * from "./core/type/type" 13 | export * from "./middlewares/create-action-tracking-middleware" 14 | export * from "./middlewares/createActionTrackingMiddleware2" 15 | export * from "./middlewares/on-action" 16 | export * from "./core/action" 17 | export * from "./core/actionContext" 18 | export * from "./core/type/type-checker" 19 | export * from "./core/node/identifier-cache" 20 | export * from "./core/node/create-node" 21 | export * from "./core/node/node-utils" 22 | export * from "./core/process" 23 | export * from "./core/flow" 24 | export * from "./core/json-patch" 25 | export * from "./utils" 26 | export * from "./types/utility-types/snapshotProcessor" 27 | export * from "./types/complex-types/map" 28 | export * from "./types/complex-types/array" 29 | export * from "./types/complex-types/model" 30 | export * from "./types/primitives" 31 | export * from "./types/utility-types/literal" 32 | export * from "./types/utility-types/refinement" 33 | export * from "./types/utility-types/enumeration" 34 | export * from "./types/utility-types/union" 35 | export * from "./types/utility-types/optional" 36 | export * from "./types/utility-types/maybe" 37 | export * from "./types/utility-types/late" 38 | export * from "./types/utility-types/lazy" 39 | export * from "./types/utility-types/frozen" 40 | export * from "./types/utility-types/reference" 41 | export * from "./types/utility-types/identifier" 42 | export * from "./types/utility-types/custom" 43 | export * from "./types" 44 | -------------------------------------------------------------------------------- /docs/API/interfaces/iactionrecorder.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: "iactionrecorder" 3 | title: "IActionRecorder" 4 | sidebar_label: "IActionRecorder" 5 | --- 6 | 7 | [mobx-state-tree - v7.0.2](../index.md) › [IActionRecorder](iactionrecorder.md) 8 | 9 | ## Hierarchy 10 | 11 | * **IActionRecorder** 12 | 13 | ## Index 14 | 15 | ### Properties 16 | 17 | * [actions](iactionrecorder.md#actions) 18 | * [recording](iactionrecorder.md#recording) 19 | 20 | ### Methods 21 | 22 | * [replay](iactionrecorder.md#replay) 23 | * [resume](iactionrecorder.md#resume) 24 | * [stop](iactionrecorder.md#stop) 25 | 26 | ## Properties 27 | 28 | ### actions 29 | 30 | • **actions**: *ReadonlyArray‹[ISerializedActionCall](iserializedactioncall.md)›* 31 | 32 | *Defined in [src/middlewares/on-action.ts:37](https://github.com/mobxjs/mobx-state-tree/blob/1be40a3e/src/middlewares/on-action.ts#L37)* 33 | 34 | ___ 35 | 36 | ### recording 37 | 38 | • **recording**: *boolean* 39 | 40 | *Defined in [src/middlewares/on-action.ts:38](https://github.com/mobxjs/mobx-state-tree/blob/1be40a3e/src/middlewares/on-action.ts#L38)* 41 | 42 | ## Methods 43 | 44 | ### replay 45 | 46 | ▸ **replay**(`target`: IAnyStateTreeNode): *void* 47 | 48 | *Defined in [src/middlewares/on-action.ts:41](https://github.com/mobxjs/mobx-state-tree/blob/1be40a3e/src/middlewares/on-action.ts#L41)* 49 | 50 | **Parameters:** 51 | 52 | Name | Type | 53 | ------ | ------ | 54 | `target` | IAnyStateTreeNode | 55 | 56 | **Returns:** *void* 57 | 58 | ___ 59 | 60 | ### resume 61 | 62 | ▸ **resume**(): *void* 63 | 64 | *Defined in [src/middlewares/on-action.ts:40](https://github.com/mobxjs/mobx-state-tree/blob/1be40a3e/src/middlewares/on-action.ts#L40)* 65 | 66 | **Returns:** *void* 67 | 68 | ___ 69 | 70 | ### stop 71 | 72 | ▸ **stop**(): *void* 73 | 74 | *Defined in [src/middlewares/on-action.ts:39](https://github.com/mobxjs/mobx-state-tree/blob/1be40a3e/src/middlewares/on-action.ts#L39)* 75 | 76 | **Returns:** *void* 77 | -------------------------------------------------------------------------------- /docs/concepts/patches.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: patches 3 | title: Patches 4 | --- 5 | 6 |
7 | 8 |
9 | egghead.io lesson 3: Test mobx-state-tree Models by Recording Snapshots or Patches 10 |
11 |
12 | 13 |
14 | Hosted on egghead.io 15 |
16 | 17 | Modifying a model does not only result in a new snapshot, but also in a stream of [JSON-patches](http://jsonpatch.com/) describing which modifications were made. 18 | Patches have the following signature: 19 | 20 | export interface IJsonPatch { 21 | op: "replace" | "add" | "remove" 22 | path: string 23 | value?: any 24 | } 25 | 26 | - Patches are constructed according to JSON-Patch, RFC 6902 27 | - Patches are emitted immediately when a mutation is made and don't respect transaction boundaries (like snapshots) 28 | - Patch listeners can be used to achieve deep observing 29 | - The `path` attribute of a patch contains the path of the event relative to the place where the event listener is attached 30 | - A single mutation can result in multiple patches, for example when splicing an array 31 | - Patches can be reverse applied, which enables many powerful patterns like undo / redo 32 | 33 | Useful methods: 34 | 35 | - `onPatch(model, listener)` attaches a patch listener to the provided model, which will be invoked whenever the model or any of its descendants is mutated 36 | - `applyPatch(model, patch)` applies a patch (or array of patches) to the provided model 37 | -------------------------------------------------------------------------------- /src/core/node/create-node.ts: -------------------------------------------------------------------------------- 1 | import { 2 | MstError, 3 | ObjectNode, 4 | ScalarNode, 5 | AnyNode, 6 | getStateTreeNodeSafe, 7 | AnyObjectNode, 8 | ComplexType, 9 | SimpleType 10 | } from "../../internal" 11 | 12 | /** 13 | * @internal 14 | * @hidden 15 | */ 16 | export function createObjectNode( 17 | type: ComplexType, 18 | parent: AnyObjectNode | null, 19 | subpath: string, 20 | environment: any, 21 | initialValue: C | T 22 | ): ObjectNode { 23 | const existingNode = getStateTreeNodeSafe(initialValue) 24 | if (existingNode) { 25 | if (existingNode.parent) { 26 | // istanbul ignore next 27 | throw new MstError( 28 | `Cannot add an object to a state tree if it is already part of the same or another state tree. Tried to assign an object to '${ 29 | parent ? parent.path : "" 30 | }/${subpath}', but it lives already at '${existingNode.path}'` 31 | ) 32 | } 33 | 34 | if (parent) { 35 | existingNode.setParent(parent, subpath) 36 | } 37 | // else it already has no parent since it is a pre-requisite 38 | 39 | return existingNode 40 | } 41 | 42 | // not a node, a snapshot 43 | return new ObjectNode(type, parent, subpath, environment, initialValue as C) 44 | } 45 | 46 | /** 47 | * @internal 48 | * @hidden 49 | */ 50 | export function createScalarNode( 51 | type: SimpleType, 52 | parent: AnyObjectNode | null, 53 | subpath: string, 54 | environment: any, 55 | initialValue: C 56 | ): ScalarNode { 57 | return new ScalarNode(type, parent, subpath, environment, initialValue) 58 | } 59 | 60 | /** 61 | * @internal 62 | * @hidden 63 | */ 64 | export function isNode(value: any): value is AnyNode { 65 | return value instanceof ScalarNode || value instanceof ObjectNode 66 | } 67 | -------------------------------------------------------------------------------- /docs/API/interfaces/iactiontrackingmiddleware2hooks.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: "iactiontrackingmiddleware2hooks" 3 | title: "IActionTrackingMiddleware2Hooks" 4 | sidebar_label: "IActionTrackingMiddleware2Hooks" 5 | --- 6 | 7 | [mobx-state-tree - v7.0.2](../index.md) › [IActionTrackingMiddleware2Hooks](iactiontrackingmiddleware2hooks.md) 8 | 9 | ## Type parameters 10 | 11 | ▪ **TEnv** 12 | 13 | ## Hierarchy 14 | 15 | * **IActionTrackingMiddleware2Hooks** 16 | 17 | ## Index 18 | 19 | ### Properties 20 | 21 | * [filter](iactiontrackingmiddleware2hooks.md#optional-filter) 22 | * [onFinish](iactiontrackingmiddleware2hooks.md#onfinish) 23 | * [onStart](iactiontrackingmiddleware2hooks.md#onstart) 24 | 25 | ## Properties 26 | 27 | ### `Optional` filter 28 | 29 | • **filter**? : *undefined | function* 30 | 31 | *Defined in [src/middlewares/createActionTrackingMiddleware2.ts:9](https://github.com/mobxjs/mobx-state-tree/blob/1be40a3e/src/middlewares/createActionTrackingMiddleware2.ts#L9)* 32 | 33 | ___ 34 | 35 | ### onFinish 36 | 37 | • **onFinish**: *function* 38 | 39 | *Defined in [src/middlewares/createActionTrackingMiddleware2.ts:11](https://github.com/mobxjs/mobx-state-tree/blob/1be40a3e/src/middlewares/createActionTrackingMiddleware2.ts#L11)* 40 | 41 | #### Type declaration: 42 | 43 | ▸ (`call`: [IActionTrackingMiddleware2Call](iactiontrackingmiddleware2call.md)‹TEnv›, `error?`: any): *void* 44 | 45 | **Parameters:** 46 | 47 | Name | Type | 48 | ------ | ------ | 49 | `call` | [IActionTrackingMiddleware2Call](iactiontrackingmiddleware2call.md)‹TEnv› | 50 | `error?` | any | 51 | 52 | ___ 53 | 54 | ### onStart 55 | 56 | • **onStart**: *function* 57 | 58 | *Defined in [src/middlewares/createActionTrackingMiddleware2.ts:10](https://github.com/mobxjs/mobx-state-tree/blob/1be40a3e/src/middlewares/createActionTrackingMiddleware2.ts#L10)* 59 | 60 | #### Type declaration: 61 | 62 | ▸ (`call`: [IActionTrackingMiddleware2Call](iactiontrackingmiddleware2call.md)‹TEnv›): *void* 63 | 64 | **Parameters:** 65 | 66 | Name | Type | 67 | ------ | ------ | 68 | `call` | [IActionTrackingMiddleware2Call](iactiontrackingmiddleware2call.md)‹TEnv› | 69 | -------------------------------------------------------------------------------- /src/types/utility-types/enumeration.ts: -------------------------------------------------------------------------------- 1 | import { ISimpleType, union, literal, assertIsString, devMode } from "../../internal" 2 | 3 | /** @hidden */ 4 | export type UnionStringArray = T[number] 5 | 6 | // strongly typed enumeration forms for plain and readonly string arrays (when passed directly to the function). 7 | // with these overloads, we get correct typing for native TS string enums when we use Object.values(Enum) as Enum[] as options. 8 | // these overloads also allow both mutable and immutable arrays, making types.enumeration(Object.values(Enum)) possible. 9 | // the only case where this doesn't work is when passing to the function an array variable with a mutable type constraint; 10 | // for these cases, it will just fallback and assume the type is a generic string. 11 | export function enumeration( 12 | options: readonly T[] 13 | ): ISimpleType> 14 | export function enumeration( 15 | name: string, 16 | options: readonly T[] 17 | ): ISimpleType> 18 | /** 19 | * `types.enumeration` - Can be used to create an string based enumeration. 20 | * (note: this methods is just sugar for a union of string literals) 21 | * 22 | * Example: 23 | * ```ts 24 | * const TrafficLight = types.model({ 25 | * color: types.enumeration("Color", ["Red", "Orange", "Green"]) 26 | * }) 27 | * ``` 28 | * 29 | * @param name descriptive name of the enumeration (optional) 30 | * @param options possible values this enumeration can have 31 | * @returns 32 | */ 33 | export function enumeration( 34 | name: string | readonly T[], 35 | options?: readonly T[] 36 | ): ISimpleType { 37 | const realOptions: readonly T[] = typeof name === "string" ? options! : name 38 | // check all options 39 | if (devMode()) { 40 | realOptions.forEach((option, i) => { 41 | assertIsString(option, i + 1) 42 | }) 43 | } 44 | const type = union(...realOptions.map(option => literal("" + option))) 45 | if (typeof name === "string") type.name = name 46 | return type as ISimpleType 47 | } 48 | -------------------------------------------------------------------------------- /__tests__/core/__snapshots__/custom-type.test.ts.snap: -------------------------------------------------------------------------------- 1 | // Bun Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`reassignments will work 1`] = ` 4 | [ 5 | { 6 | "balance": "2.5", 7 | "lastTransaction": null, 8 | }, 9 | { 10 | "balance": "3.5", 11 | "lastTransaction": null, 12 | }, 13 | { 14 | "balance": "4.5", 15 | "lastTransaction": null, 16 | }, 17 | { 18 | "balance": "4.5", 19 | "lastTransaction": "2.5", 20 | }, 21 | { 22 | "balance": "4.5", 23 | "lastTransaction": null, 24 | }, 25 | ] 26 | `; 27 | 28 | exports[`reassignments will work 2`] = ` 29 | [ 30 | { 31 | "op": "replace", 32 | "path": "/balance", 33 | "value": "2.5", 34 | }, 35 | { 36 | "op": "replace", 37 | "path": "/balance", 38 | "value": "3.5", 39 | }, 40 | { 41 | "op": "replace", 42 | "path": "/balance", 43 | "value": "4.5", 44 | }, 45 | { 46 | "op": "replace", 47 | "path": "/lastTransaction", 48 | "value": "2.5", 49 | }, 50 | { 51 | "op": "replace", 52 | "path": "/lastTransaction", 53 | "value": null, 54 | }, 55 | ] 56 | `; 57 | 58 | exports[`complex reassignments will work 1`] = ` 59 | [ 60 | { 61 | "balance": [ 62 | 2, 63 | 5, 64 | ], 65 | }, 66 | { 67 | "balance": [ 68 | 2, 69 | 5, 70 | ], 71 | }, 72 | { 73 | "balance": [ 74 | 3, 75 | 5, 76 | ], 77 | }, 78 | { 79 | "balance": [ 80 | 4, 81 | 5, 82 | ], 83 | }, 84 | ] 85 | `; 86 | 87 | exports[`complex reassignments will work 2`] = ` 88 | [ 89 | { 90 | "op": "replace", 91 | "path": "/balance", 92 | "value": [ 93 | 2, 94 | 5, 95 | ], 96 | }, 97 | { 98 | "op": "replace", 99 | "path": "/balance", 100 | "value": [ 101 | 2, 102 | 5, 103 | ], 104 | }, 105 | { 106 | "op": "replace", 107 | "path": "/balance", 108 | "value": [ 109 | 3, 110 | 5, 111 | ], 112 | }, 113 | { 114 | "op": "replace", 115 | "path": "/balance", 116 | "value": [ 117 | 4, 118 | 5, 119 | ], 120 | }, 121 | ] 122 | `; 123 | -------------------------------------------------------------------------------- /docs/API/interfaces/iactioncontext.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: "iactioncontext" 3 | title: "IActionContext" 4 | sidebar_label: "IActionContext" 5 | --- 6 | 7 | [mobx-state-tree - v7.0.2](../index.md) › [IActionContext](iactioncontext.md) 8 | 9 | ## Hierarchy 10 | 11 | * **IActionContext** 12 | 13 | ↳ [IMiddlewareEvent](imiddlewareevent.md) 14 | 15 | ## Index 16 | 17 | ### Properties 18 | 19 | * [args](iactioncontext.md#args) 20 | * [context](iactioncontext.md#context) 21 | * [id](iactioncontext.md#id) 22 | * [name](iactioncontext.md#name) 23 | * [parentActionEvent](iactioncontext.md#parentactionevent) 24 | * [tree](iactioncontext.md#tree) 25 | 26 | ## Properties 27 | 28 | ### args 29 | 30 | • **args**: *any[]* 31 | 32 | *Defined in [src/core/actionContext.ts:20](https://github.com/mobxjs/mobx-state-tree/blob/1be40a3e/src/core/actionContext.ts#L20)* 33 | 34 | Event arguments in an array (action arguments for actions) 35 | 36 | ___ 37 | 38 | ### context 39 | 40 | • **context**: *IAnyStateTreeNode* 41 | 42 | *Defined in [src/core/actionContext.ts:15](https://github.com/mobxjs/mobx-state-tree/blob/1be40a3e/src/core/actionContext.ts#L15)* 43 | 44 | Event context (node where the action was invoked) 45 | 46 | ___ 47 | 48 | ### id 49 | 50 | • **id**: *number* 51 | 52 | *Defined in [src/core/actionContext.ts:9](https://github.com/mobxjs/mobx-state-tree/blob/1be40a3e/src/core/actionContext.ts#L9)* 53 | 54 | Event unique id 55 | 56 | ___ 57 | 58 | ### name 59 | 60 | • **name**: *string* 61 | 62 | *Defined in [src/core/actionContext.ts:6](https://github.com/mobxjs/mobx-state-tree/blob/1be40a3e/src/core/actionContext.ts#L6)* 63 | 64 | Event name (action name for actions) 65 | 66 | ___ 67 | 68 | ### parentActionEvent 69 | 70 | • **parentActionEvent**: *[IMiddlewareEvent](imiddlewareevent.md) | undefined* 71 | 72 | *Defined in [src/core/actionContext.ts:12](https://github.com/mobxjs/mobx-state-tree/blob/1be40a3e/src/core/actionContext.ts#L12)* 73 | 74 | Parent action event object 75 | 76 | ___ 77 | 78 | ### tree 79 | 80 | • **tree**: *IAnyStateTreeNode* 81 | 82 | *Defined in [src/core/actionContext.ts:17](https://github.com/mobxjs/mobx-state-tree/blob/1be40a3e/src/core/actionContext.ts#L17)* 83 | 84 | Event tree (root node of the node where the action was invoked) 85 | -------------------------------------------------------------------------------- /docs/concepts/listeners.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: listeners 3 | title: Listening to observables, snapshots, patches and actions 4 | sidebar_label: Listening to changes 5 | --- 6 | 7 |
8 | 9 | MST is powered by MobX. This means that it is immediately compatible with `observer` components or reactions like `autorun`: 10 | 11 | ```javascript 12 | import { autorun } from "mobx" 13 | 14 | autorun(() => { 15 | console.log(storeInstance.selectedTodo.title) 16 | }) 17 | ``` 18 | 19 | Because MST keeps immutable snapshots in the background, it is also possible to be notified when a new snapshot of the tree is available. This is similar to `.subscribe` on a redux store: 20 | 21 | ```javascript 22 | onSnapshot(storeInstance, (newSnapshot) => { 23 | console.info("Got new snapshot:", newSnapshot) 24 | }) 25 | ``` 26 | 27 | However, sometimes it is more useful to precisely know what has changed rather than just receiving a complete new snapshot. 28 | For that, MST supports json-patches out of the box. 29 | 30 | ```javascript 31 | onPatch(storeInstance, patch => { 32 | console.info("Got change: ", patch) 33 | }) 34 | 35 | storeInstance.todos[0].setTitle("Add milk") 36 | // prints: 37 | { 38 | path: "/todos/0", 39 | op: "replace", 40 | value: "Add milk" 41 | } 42 | ``` 43 | 44 | Similarly, you can be notified whenever an action is invoked by using `onAction`. 45 | 46 | ```javascript 47 | onAction(storeInstance, call => { 48 | console.info("Action was called:", call) 49 | }) 50 | 51 | storeInstance.todos[0].setTitle("Add milk") 52 | // prints: 53 | { 54 | path: "/todos/0", 55 | name: "setTitle", 56 | args: ["Add milk"] 57 | } 58 | ``` 59 | 60 | It is even possible to intercept actions before they are applied by adding middleware using `addMiddleware`: 61 | 62 | ```javascript 63 | addMiddleware(storeInstance, (call, next) => { 64 | call.args[0] = call.args[0].replace(/tea/gi, "Coffee") 65 | return next(call) 66 | }) 67 | ``` 68 | 69 | A more extensive middleware example can be found in this [code sandbox](https://codesandbox.io/s/0yt72). 70 | For more details on creating middleware and the exact specification of middleware events, see the [docs](middleware). 71 | 72 | Finally, it is not only possible to be notified about snapshots, patches or actions. It is also possible to re-apply them by using `applySnapshot`, `applyPatch` or `applyAction`! 73 | -------------------------------------------------------------------------------- /docs/concepts/views.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: views 3 | title: Derived values 4 | --- 5 | 6 |
7 | 8 |
9 | egghead.io lesson 4: Derive Information from Models Using Views 10 |
11 |
12 | 13 |
14 | Hosted on egghead.io 15 |
16 | 17 | Any fact that can be derived from your state is called a "view" or "derivation". 18 | See [The gist of MobX](https://mobx.js.org/the-gist-of-mobx.html) for some background. 19 | 20 | Views come in two flavors: views with arguments and views without arguments. The latter are called computed values, based on the [computed](https://mobx.js.org/computeds.html) concept in MobX. The main difference between the two is that computed properties create an explicit caching point, but later they work the same and any other computed value or MobX based reaction like [`@observer`](https://mobx.js.org/react-integration.html) components can react to them. Computed values are defined using _getter_ functions. 21 | 22 | Example: 23 | 24 | ```javascript 25 | import { autorun } from "mobx" 26 | 27 | const UserStore = types 28 | .model({ 29 | users: types.array(User) 30 | }) 31 | .views(self => ({ 32 | get numberOfChildren() { 33 | return self.users.filter(user => user.age < 18).length 34 | }, 35 | numberOfPeopleOlderThan(age) { 36 | return self.users.filter(user => user.age > age).length 37 | } 38 | })) 39 | 40 | const userStore = UserStore.create(/* */) 41 | 42 | // Every time the userStore is updated in a relevant way, log messages will be printed 43 | autorun(() => { 44 | console.log("There are now ", userStore.numberOfChildren, " children") 45 | }) 46 | autorun(() => { 47 | console.log("There are now ", userStore.numberOfPeopleOlderThan(75), " pretty old people") 48 | }) 49 | ``` 50 | 51 | If you want to share volatile state between views and actions, use `.extend` instead of `.views` + `.actions`. See the [volatile state](volatiles) section. 52 | -------------------------------------------------------------------------------- /docs/API/interfaces/imodelreflectiondata.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: "imodelreflectiondata" 3 | title: "IModelReflectionData" 4 | sidebar_label: "IModelReflectionData" 5 | --- 6 | 7 | [mobx-state-tree - v7.0.2](../index.md) › [IModelReflectionData](imodelreflectiondata.md) 8 | 9 | ## Hierarchy 10 | 11 | * [IModelReflectionPropertiesData](imodelreflectionpropertiesdata.md) 12 | 13 | ↳ **IModelReflectionData** 14 | 15 | ## Index 16 | 17 | ### Properties 18 | 19 | * [actions](imodelreflectiondata.md#actions) 20 | * [flowActions](imodelreflectiondata.md#flowactions) 21 | * [name](imodelreflectiondata.md#name) 22 | * [properties](imodelreflectiondata.md#properties) 23 | * [views](imodelreflectiondata.md#views) 24 | * [volatile](imodelreflectiondata.md#volatile) 25 | 26 | ## Properties 27 | 28 | ### actions 29 | 30 | • **actions**: *string[]* 31 | 32 | *Defined in [src/core/mst-operations.ts:855](https://github.com/mobxjs/mobx-state-tree/blob/1be40a3e/src/core/mst-operations.ts#L855)* 33 | 34 | ___ 35 | 36 | ### flowActions 37 | 38 | • **flowActions**: *string[]* 39 | 40 | *Defined in [src/core/mst-operations.ts:858](https://github.com/mobxjs/mobx-state-tree/blob/1be40a3e/src/core/mst-operations.ts#L858)* 41 | 42 | ___ 43 | 44 | ### name 45 | 46 | • **name**: *string* 47 | 48 | *Inherited from [IModelReflectionPropertiesData](imodelreflectionpropertiesdata.md).[name](imodelreflectionpropertiesdata.md#name)* 49 | 50 | *Defined in [src/core/mst-operations.ts:825](https://github.com/mobxjs/mobx-state-tree/blob/1be40a3e/src/core/mst-operations.ts#L825)* 51 | 52 | ___ 53 | 54 | ### properties 55 | 56 | • **properties**: *object* 57 | 58 | *Inherited from [IModelReflectionPropertiesData](imodelreflectionpropertiesdata.md).[properties](imodelreflectionpropertiesdata.md#properties)* 59 | 60 | *Defined in [src/core/mst-operations.ts:826](https://github.com/mobxjs/mobx-state-tree/blob/1be40a3e/src/core/mst-operations.ts#L826)* 61 | 62 | #### Type declaration: 63 | 64 | * \[ **K**: *string*\]: [IAnyType](ianytype.md) 65 | 66 | ___ 67 | 68 | ### views 69 | 70 | • **views**: *string[]* 71 | 72 | *Defined in [src/core/mst-operations.ts:856](https://github.com/mobxjs/mobx-state-tree/blob/1be40a3e/src/core/mst-operations.ts#L856)* 73 | 74 | ___ 75 | 76 | ### volatile 77 | 78 | • **volatile**: *string[]* 79 | 80 | *Defined in [src/core/mst-operations.ts:857](https://github.com/mobxjs/mobx-state-tree/blob/1be40a3e/src/core/mst-operations.ts#L857)* 81 | -------------------------------------------------------------------------------- /src/core/actionContext.ts: -------------------------------------------------------------------------------- 1 | import { IAnyStateTreeNode, IMiddlewareEvent } from "../internal" 2 | import { getCurrentActionContext } from "./action" 3 | 4 | export interface IActionContext { 5 | /** Event name (action name for actions) */ 6 | readonly name: string 7 | 8 | /** Event unique id */ 9 | readonly id: number 10 | 11 | /** Parent action event object */ 12 | readonly parentActionEvent: IMiddlewareEvent | undefined 13 | 14 | /** Event context (node where the action was invoked) */ 15 | readonly context: IAnyStateTreeNode 16 | /** Event tree (root node of the node where the action was invoked) */ 17 | readonly tree: IAnyStateTreeNode 18 | 19 | /** Event arguments in an array (action arguments for actions) */ 20 | readonly args: any[] 21 | } 22 | 23 | /** 24 | * Returns the currently executing MST action context, or undefined if none. 25 | */ 26 | export function getRunningActionContext(): IActionContext | undefined { 27 | let current = getCurrentActionContext() 28 | while (current && current.type !== "action") { 29 | current = current.parentActionEvent 30 | } 31 | return current 32 | } 33 | 34 | function _isActionContextThisOrChildOf( 35 | actionContext: IActionContext, 36 | sameOrParent: number | IActionContext | IMiddlewareEvent, 37 | includeSame: boolean 38 | ) { 39 | const parentId = typeof sameOrParent === "number" ? sameOrParent : sameOrParent.id 40 | 41 | let current: IActionContext | IMiddlewareEvent | undefined = includeSame 42 | ? actionContext 43 | : actionContext.parentActionEvent 44 | while (current) { 45 | if (current.id === parentId) { 46 | return true 47 | } 48 | current = current.parentActionEvent 49 | } 50 | return false 51 | } 52 | 53 | /** 54 | * Returns if the given action context is a parent of this action context. 55 | */ 56 | export function isActionContextChildOf( 57 | actionContext: IActionContext, 58 | parent: number | IActionContext | IMiddlewareEvent 59 | ) { 60 | return _isActionContextThisOrChildOf(actionContext, parent, false) 61 | } 62 | 63 | /** 64 | * Returns if the given action context is this or a parent of this action context. 65 | */ 66 | export function isActionContextThisOrChildOf( 67 | actionContext: IActionContext, 68 | parentOrThis: number | IActionContext | IMiddlewareEvent 69 | ) { 70 | return _isActionContextThisOrChildOf(actionContext, parentOrThis, true) 71 | } 72 | -------------------------------------------------------------------------------- /src/types/utility-types/literal.ts: -------------------------------------------------------------------------------- 1 | import { 2 | isPrimitive, 3 | createScalarNode, 4 | ISimpleType, 5 | TypeFlags, 6 | IValidationContext, 7 | IValidationResult, 8 | typeCheckSuccess, 9 | typeCheckFailure, 10 | isType, 11 | Primitives, 12 | AnyObjectNode, 13 | SimpleType, 14 | devMode 15 | } from "../../internal" 16 | import { assertArg } from "../../utils" 17 | 18 | /** 19 | * @internal 20 | * @hidden 21 | */ 22 | export class Literal extends SimpleType { 23 | readonly value: T 24 | readonly flags = TypeFlags.Literal 25 | 26 | constructor(value: T) { 27 | super(JSON.stringify(value)) 28 | this.value = value 29 | } 30 | 31 | instantiate( 32 | parent: AnyObjectNode | null, 33 | subpath: string, 34 | environment: any, 35 | initialValue: this["C"] 36 | ): this["N"] { 37 | return createScalarNode(this, parent, subpath, environment, initialValue) 38 | } 39 | 40 | describe() { 41 | return JSON.stringify(this.value) 42 | } 43 | 44 | isValidSnapshot(value: this["C"], context: IValidationContext): IValidationResult { 45 | if (isPrimitive(value) && value === this.value) { 46 | return typeCheckSuccess() 47 | } 48 | return typeCheckFailure( 49 | context, 50 | value, 51 | `Value is not a literal ${JSON.stringify(this.value)}` 52 | ) 53 | } 54 | } 55 | 56 | /** 57 | * `types.literal` - The literal type will return a type that will match only the exact given type. 58 | * The given value must be a primitive, in order to be serialized to a snapshot correctly. 59 | * You can use literal to match exact strings for example the exact male or female string. 60 | * 61 | * Example: 62 | * ```ts 63 | * const Person = types.model({ 64 | * name: types.string, 65 | * gender: types.union(types.literal('male'), types.literal('female')) 66 | * }) 67 | * ``` 68 | * 69 | * @param value The value to use in the strict equal check 70 | * @returns 71 | */ 72 | export function literal(value: S): ISimpleType { 73 | // check that the given value is a primitive 74 | assertArg(value, isPrimitive, "primitive", 1) 75 | 76 | return new Literal(value) 77 | } 78 | 79 | /** 80 | * Returns if a given value represents a literal type. 81 | * 82 | * @param type 83 | * @returns 84 | */ 85 | export function isLiteralType(type: unknown): type is ISimpleType { 86 | return isType(type) && (type.flags & TypeFlags.Literal) > 0 87 | } 88 | -------------------------------------------------------------------------------- /__tests__/perf/fixtures/fixture-models.ts: -------------------------------------------------------------------------------- 1 | const mst = require("../../../dist/mobx-state-tree.umd") 2 | const { types } = mst 3 | 4 | // tiny 5 | export const Treasure = types.model("Treasure", { 6 | trapped: types.boolean, 7 | gold: types.optional(types.number, 0) 8 | }) 9 | // medium 10 | export const HeroRoles = ["warrior", "wizard", "cleric", "thief"] 11 | export const Hero = types 12 | .model("Hero", { 13 | id: types.identifierNumber, 14 | name: types.string, 15 | description: types.string, 16 | level: types.optional(types.number, 1), 17 | role: types.union(...exports.HeroRoles.map(types.literal)) 18 | }) 19 | .views((self: any) => ({ 20 | get descriptionLength() { 21 | return self.description.length 22 | } 23 | })) 24 | // large 25 | export const Monster = types 26 | .model("Monster", { 27 | id: types.identifier, 28 | freestyle: types.frozen(), 29 | level: types.number, 30 | maxHp: types.number, 31 | hp: types.number, 32 | warning: types.maybeNull(types.string), 33 | createdAt: types.maybeNull(types.Date), 34 | treasures: types.optional(types.array(exports.Treasure), []), 35 | eatenHeroes: types.maybeNull(types.array(exports.Hero)), 36 | hasFangs: types.optional(types.boolean, false), 37 | hasClaws: types.optional(types.boolean, false), 38 | hasWings: types.optional(types.boolean, false), 39 | hasGrowl: types.optional(types.boolean, false), 40 | stenchLevel: types.optional(types.number, 0), 41 | fearsFire: types.optional(types.boolean, false), 42 | fearsWater: types.optional(types.boolean, false), 43 | fearsWarriors: types.optional(types.boolean, false), 44 | fearsClerics: types.optional(types.boolean, false), 45 | fearsMages: types.optional(types.boolean, false), 46 | fearsThieves: types.optional(types.boolean, false), 47 | fearsProgrammers: types.optional(types.boolean, true) 48 | }) 49 | .views((self: any) => ({ 50 | get isAlive() { 51 | return self.hp > 0 52 | }, 53 | get isFlashingRed() { 54 | return self.hp > 0 && self.hp < self.maxHp && self.hp === 1 55 | }, 56 | get weight() { 57 | const victimWeight = self.eatenHeroes ? self.eatenHeroes.length : 0 58 | const fangWeight = self.hasFangs ? 10 : 5 59 | const wingWeight = self.hasWings ? 12 : 4 60 | return (victimWeight + fangWeight + wingWeight) * self.level > 5 ? 2 : 1 61 | } 62 | })) 63 | -------------------------------------------------------------------------------- /docs/tips/inheritance.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: inheritance 3 | sidebar_label: Simulating inheritance 4 | title: Simulate inheritance by using type composition 5 | --- 6 | 7 |
8 | 9 | There is no notion of inheritance in MST. The recommended approach is to keep references to the original configuration of a model in order to compose it into a new one, for example by using `types.compose` (which combines two types) or producing fresh types using `.props|.views|.actions`. An example of classical inheritance could be expressed using composition as follows: 10 | 11 | ```javascript 12 | const Square = types 13 | .model( 14 | "Square", 15 | { 16 | width: types.number 17 | } 18 | ) 19 | .views(self => ({ 20 | // note: this is not a getter! this is just a function that is evaluated 21 | surface() { 22 | return self.width * self.width 23 | } 24 | })) 25 | 26 | // create a new type, based on Square 27 | const Box = Square 28 | .named("Box") 29 | .views(self => { 30 | // save the base implementation of surface, again, this is a function. 31 | // if it was a getter, the getter would be evaluated only once here 32 | // instead of being able to evaluate dynamically at time-of-use 33 | const superSurface = self.surface 34 | 35 | return { 36 | // super contrived override example! 37 | surface() { 38 | return superSurface() * 1 39 | }, 40 | volume() { 41 | return self.surface() * self.width 42 | } 43 | } 44 | })) 45 | 46 | // no inheritance, but, union types and code reuse 47 | const Shape = types.union(Box, Square) 48 | 49 | const instance = Shape.create({type:"Box",width:4}) 50 | console.log(instance.width) 51 | console.log(instance.surface()) // calls Box.surface() 52 | console.log(instance.volume()) // calls Box.volume() 53 | ``` 54 | 55 | Similarly, compose can be used to simply mix in types: 56 | 57 | ```javascript 58 | const CreationLogger = types.model().actions((self) => ({ 59 | afterCreate() { 60 | console.log("Instantiated " + getType(self).name) 61 | } 62 | })) 63 | 64 | const BaseSquare = types 65 | .model({ 66 | width: types.number 67 | }) 68 | .views((self) => ({ 69 | surface() { 70 | return self.width * self.width 71 | } 72 | })) 73 | 74 | export const LoggingSquare = types 75 | .compose( 76 | // combine a simple square model... 77 | BaseSquare, 78 | // ... with the logger type 79 | CreationLogger 80 | ) 81 | // ..and give it a nice name 82 | .named("LoggingSquare") 83 | ``` 84 | -------------------------------------------------------------------------------- /__tests__/core/refinement.test.ts: -------------------------------------------------------------------------------- 1 | import { getSnapshot, types } from "../../src" 2 | import { expect, test } from "bun:test" 3 | 4 | test("it should allow if type and predicate is correct", () => { 5 | const Factory = types.model({ 6 | number: types.refinement( 7 | "positive number", 8 | types.optional(types.number, 0), 9 | s => typeof s === "number" && s >= 0 10 | ) 11 | }) 12 | const doc = Factory.create({ number: 42 }) 13 | expect(getSnapshot(doc)).toEqual({ number: 42 }) 14 | }) 15 | if (process.env.NODE_ENV !== "production") { 16 | test("it should throw if a correct type with failing predicate is given", () => { 17 | const Factory = types.model({ 18 | number: types.refinement( 19 | "positive number", 20 | types.optional(types.number, 0), 21 | s => typeof s === "number" && s >= 0 22 | ) 23 | }) 24 | expect(() => { 25 | Factory.create({ number: "givenStringInstead" } as any) 26 | }).toThrow( 27 | `[mobx-state-tree] Error while converting \`{\"number\":\"givenStringInstead\"}\` to \`AnonymousModel\`:\n\n at path \"/number\" value \`\"givenStringInstead\"\` is not assignable to type: \`positive number\` (Value is not a number).` 28 | ) 29 | expect(() => { 30 | Factory.create({ number: -4 }) 31 | }).toThrow( 32 | `[mobx-state-tree] Error while converting \`{\"number\":-4}\` to \`AnonymousModel\`:\n\n at path \"/number\" value \`-4\` is not assignable to type: \`positive number\` (Value does not respect the refinement predicate).` 33 | ) 34 | }) 35 | test("it should throw custom error message with failing predicate is given", () => { 36 | const Factory = types.model({ 37 | number: types.refinement( 38 | types.optional(types.number, 0), 39 | s => typeof s === "number" && s >= 0, 40 | s => "A positive number was expected" 41 | ) 42 | }) 43 | expect(() => { 44 | Factory.create({ number: "givenStringInstead" } as any) 45 | }).toThrow( 46 | `[mobx-state-tree] Error while converting \`{\"number\":\"givenStringInstead\"}\` to \`AnonymousModel\`:\n\n at path \"/number\" value \`\"givenStringInstead\"\` is not assignable to type: \`number\` (Value is not a number).` 47 | ) 48 | expect(() => { 49 | Factory.create({ number: -4 }) 50 | }).toThrow( 51 | `[mobx-state-tree] Error while converting \`{\"number\":-4}\` to \`AnonymousModel\`:\n\n at path "/number" value \`-4\` is not assignable to type: \`number\` (A positive number was expected).` 52 | ) 53 | }) 54 | } 55 | -------------------------------------------------------------------------------- /website/sidebars.json: -------------------------------------------------------------------------------- 1 | { 2 | "docs": { 3 | "Introduction": [ 4 | "intro/welcome", 5 | "intro/installation", 6 | "intro/getting-started", 7 | "intro/examples", 8 | "intro/philosophy" 9 | ], 10 | "Basic Concepts": [ 11 | "concepts/trees", 12 | "concepts/actions", 13 | "concepts/views", 14 | "concepts/using-react", 15 | "concepts/snapshots", 16 | "concepts/references", 17 | "concepts/async-actions" 18 | ], 19 | "Advanced Concepts": [ 20 | "concepts/patches", 21 | "concepts/listeners", 22 | "concepts/dependency-injection", 23 | "concepts/middleware", 24 | "concepts/reconciliation", 25 | "concepts/volatiles" 26 | ], 27 | "API Overview": [ 28 | "overview/types", 29 | "overview/api", 30 | "overview/hooks" 31 | ], 32 | "Tips": [ 33 | "tips/resources", 34 | "tips/contributing", 35 | "tips/faq", 36 | "tips/typescript", 37 | "tips/circular-deps", 38 | "tips/inheritance", 39 | "tips/snapshots-as-values", 40 | "tips/more-tips" 41 | ], 42 | "Compare": [ 43 | "compare/context-reducer-vs-mobx-state-tree" 44 | ], 45 | "Recipes": [ 46 | "recipes/auto-generated-property-setter-actions", 47 | "recipes/pre-built-form-types-with-mst-form-type", 48 | "recipes/mst-query" 49 | ] 50 | }, 51 | "mobx-state-tree": { 52 | "Introduction": [ 53 | "API/index" 54 | ], 55 | "Interfaces": [ 56 | "API/interfaces/customtypeoptions", 57 | "API/interfaces/functionwithflag", 58 | "API/interfaces/iactioncontext", 59 | "API/interfaces/iactionrecorder", 60 | "API/interfaces/iactiontrackingmiddleware2call", 61 | "API/interfaces/iactiontrackingmiddleware2hooks", 62 | "API/interfaces/iactiontrackingmiddlewarehooks", 63 | "API/interfaces/ianycomplextype", 64 | "API/interfaces/ianymodeltype", 65 | "API/interfaces/ianytype", 66 | "API/interfaces/ihooks", 67 | "API/interfaces/ijsonpatch", 68 | "API/interfaces/imiddlewareevent", 69 | "API/interfaces/imodelreflectiondata", 70 | "API/interfaces/imodelreflectionpropertiesdata", 71 | "API/interfaces/imodeltype", 72 | "API/interfaces/ipatchrecorder", 73 | "API/interfaces/ireversiblejsonpatch", 74 | "API/interfaces/iserializedactioncall", 75 | "API/interfaces/isimpletype", 76 | "API/interfaces/isnapshotprocessor", 77 | "API/interfaces/isnapshotprocessors", 78 | "API/interfaces/itype", 79 | "API/interfaces/ivalidationcontextentry", 80 | "API/interfaces/ivalidationerror", 81 | "API/interfaces/referenceoptionsgetset", 82 | "API/interfaces/referenceoptionsoninvalidated", 83 | "API/interfaces/unionoptions" 84 | ] 85 | } 86 | } -------------------------------------------------------------------------------- /__tests__/perf/report.ts: -------------------------------------------------------------------------------- 1 | import { smallScenario, mediumScenario, largeScenario } from "./scenarios" 2 | 3 | // here's what we'll be testing 4 | const plan = [ 5 | "-----------", 6 | "Small Model", 7 | "-----------", 8 | () => smallScenario(100), 9 | () => smallScenario(1000), 10 | () => smallScenario(10000), 11 | () => smallScenario(1000), 12 | () => smallScenario(100), 13 | "", 14 | "------------", 15 | "Medium Model", 16 | "------------", 17 | () => mediumScenario(100), 18 | () => mediumScenario(1000), 19 | () => mediumScenario(10000), 20 | () => mediumScenario(1000), 21 | () => mediumScenario(100), 22 | "", 23 | "------------------------", 24 | "Large Model - 0 children", 25 | "------------------------", 26 | () => largeScenario(100, 0, 0), 27 | () => largeScenario(1000, 0, 0), 28 | () => largeScenario(100, 0, 0), 29 | "", 30 | "-------------------------------------------", 31 | "Large Model - 10 small & 10 medium children", 32 | "-------------------------------------------", 33 | () => largeScenario(50, 10, 10), 34 | () => largeScenario(250, 10, 10), 35 | () => largeScenario(50, 10, 10), 36 | "", 37 | "-------------------------------------------", 38 | "Large Model - 100 small & 0 medium children", 39 | "-------------------------------------------", 40 | () => largeScenario(50, 100, 0), 41 | () => largeScenario(250, 100, 0), 42 | () => largeScenario(50, 100, 0), 43 | "", 44 | "-------------------------------------------", 45 | "Large Model - 0 small & 100 medium children", 46 | "-------------------------------------------", 47 | () => largeScenario(50, 0, 100), 48 | () => largeScenario(250, 0, 100), 49 | () => largeScenario(50, 0, 100) 50 | ] 51 | // burn a few to get the juices flowing 52 | smallScenario(1000) 53 | mediumScenario(500) 54 | largeScenario(100, 10, 10) 55 | // remember when this broke the internet? 56 | function leftPad(value: string, length: number, char = " "): string { 57 | return value.toString().length < length ? leftPad(char + value, length) : value 58 | } 59 | // let's start 60 | plan.forEach(fn => { 61 | // strings get printed, i guess. 62 | if (typeof fn === "string") { 63 | console.log(fn) 64 | return 65 | } 66 | // trigger awkward gc up front if we can 67 | if (global.gc) { 68 | global.gc() 69 | } 70 | // run the report 71 | const result = fn() 72 | // calculate some fields 73 | const seconds = leftPad((result.elapsed / 1.0).toLocaleString(), 8) 74 | const times = leftPad(`x ${result.count.toLocaleString()}`, 10) 75 | const avg = leftPad((result.elapsed / result.count).toFixed(1), 4) 76 | // print 77 | console.log(`${seconds}ms | ${times} | ${avg}ms avg`) 78 | }) 79 | console.log("") 80 | -------------------------------------------------------------------------------- /__tests__/core/__snapshots__/reference-custom.test.ts.snap: -------------------------------------------------------------------------------- 1 | // Bun Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`it should support custom references - adv 1`] = ` 4 | [ 5 | "1", 6 | "2", 7 | "1", 8 | null, 9 | "3", 10 | ] 11 | `; 12 | 13 | exports[`it should support custom references - adv 2`] = ` 14 | [ 15 | { 16 | "selection": "Michel", 17 | "users": { 18 | "1": { 19 | "id": "1", 20 | "name": "Michel", 21 | }, 22 | "2": { 23 | "id": "2", 24 | "name": "Mattia", 25 | }, 26 | }, 27 | }, 28 | { 29 | "selection": "Mattia", 30 | "users": { 31 | "1": { 32 | "id": "1", 33 | "name": "Michel", 34 | }, 35 | "2": { 36 | "id": "2", 37 | "name": "Mattia", 38 | }, 39 | }, 40 | }, 41 | { 42 | "selection": "Michel", 43 | "users": { 44 | "1": { 45 | "id": "1", 46 | "name": "Michel", 47 | }, 48 | "2": { 49 | "id": "2", 50 | "name": "Mattia", 51 | }, 52 | }, 53 | }, 54 | { 55 | "selection": "Michel", 56 | "users": { 57 | "2": { 58 | "id": "2", 59 | "name": "Mattia", 60 | }, 61 | }, 62 | }, 63 | { 64 | "selection": "Michel", 65 | "users": { 66 | "2": { 67 | "id": "2", 68 | "name": "Mattia", 69 | }, 70 | "3": { 71 | "id": "3", 72 | "name": "Michel", 73 | }, 74 | }, 75 | }, 76 | ] 77 | `; 78 | 79 | exports[`it should support custom references - adv 3`] = ` 80 | [ 81 | { 82 | "op": "replace", 83 | "path": "/selection", 84 | "value": "Michel", 85 | }, 86 | { 87 | "op": "replace", 88 | "path": "/selection", 89 | "value": "Mattia", 90 | }, 91 | { 92 | "op": "replace", 93 | "path": "/selection", 94 | "value": "Michel", 95 | }, 96 | { 97 | "op": "remove", 98 | "path": "/users/1", 99 | }, 100 | { 101 | "op": "add", 102 | "path": "/users/3", 103 | "value": { 104 | "id": "3", 105 | "name": "Michel", 106 | }, 107 | }, 108 | ] 109 | `; 110 | 111 | exports[`it should support custom references - adv 4`] = ` 112 | [ 113 | { 114 | "op": "replace", 115 | "path": "/selection", 116 | "value": "Mattia", 117 | }, 118 | { 119 | "op": "replace", 120 | "path": "/selection", 121 | "value": "Michel", 122 | }, 123 | { 124 | "op": "replace", 125 | "path": "/selection", 126 | "value": "Mattia", 127 | }, 128 | { 129 | "op": "add", 130 | "path": "/users/1", 131 | "value": { 132 | "id": "1", 133 | "name": "Michel", 134 | }, 135 | }, 136 | { 137 | "op": "remove", 138 | "path": "/users/3", 139 | }, 140 | ] 141 | `; 142 | -------------------------------------------------------------------------------- /docs/API/interfaces/customtypeoptions.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: "customtypeoptions" 3 | title: "CustomTypeOptions" 4 | sidebar_label: "CustomTypeOptions" 5 | --- 6 | 7 | [mobx-state-tree - v7.0.2](../index.md) › [CustomTypeOptions](customtypeoptions.md) 8 | 9 | ## Type parameters 10 | 11 | ▪ **S** 12 | 13 | ▪ **T** 14 | 15 | ## Hierarchy 16 | 17 | * **CustomTypeOptions** 18 | 19 | ## Index 20 | 21 | ### Properties 22 | 23 | * [name](customtypeoptions.md#name) 24 | 25 | ### Methods 26 | 27 | * [fromSnapshot](customtypeoptions.md#fromsnapshot) 28 | * [getValidationMessage](customtypeoptions.md#getvalidationmessage) 29 | * [isTargetType](customtypeoptions.md#istargettype) 30 | * [toSnapshot](customtypeoptions.md#tosnapshot) 31 | 32 | ## Properties 33 | 34 | ### name 35 | 36 | • **name**: *string* 37 | 38 | *Defined in [src/types/utility-types/custom.ts:15](https://github.com/mobxjs/mobx-state-tree/blob/1be40a3e/src/types/utility-types/custom.ts#L15)* 39 | 40 | Friendly name 41 | 42 | ## Methods 43 | 44 | ### fromSnapshot 45 | 46 | ▸ **fromSnapshot**(`snapshot`: S, `env?`: any): *T* 47 | 48 | *Defined in [src/types/utility-types/custom.ts:17](https://github.com/mobxjs/mobx-state-tree/blob/1be40a3e/src/types/utility-types/custom.ts#L17)* 49 | 50 | given a serialized value and environment, how to turn it into the target type 51 | 52 | **Parameters:** 53 | 54 | Name | Type | 55 | ------ | ------ | 56 | `snapshot` | S | 57 | `env?` | any | 58 | 59 | **Returns:** *T* 60 | 61 | ___ 62 | 63 | ### getValidationMessage 64 | 65 | ▸ **getValidationMessage**(`snapshot`: S): *string* 66 | 67 | *Defined in [src/types/utility-types/custom.ts:23](https://github.com/mobxjs/mobx-state-tree/blob/1be40a3e/src/types/utility-types/custom.ts#L23)* 68 | 69 | a non empty string is assumed to be a validation error 70 | 71 | **Parameters:** 72 | 73 | Name | Type | 74 | ------ | ------ | 75 | `snapshot` | S | 76 | 77 | **Returns:** *string* 78 | 79 | ___ 80 | 81 | ### isTargetType 82 | 83 | ▸ **isTargetType**(`value`: T | S): *boolean* 84 | 85 | *Defined in [src/types/utility-types/custom.ts:21](https://github.com/mobxjs/mobx-state-tree/blob/1be40a3e/src/types/utility-types/custom.ts#L21)* 86 | 87 | if true, this is a converted value, if false, it's a snapshot 88 | 89 | **Parameters:** 90 | 91 | Name | Type | 92 | ------ | ------ | 93 | `value` | T | S | 94 | 95 | **Returns:** *boolean* 96 | 97 | ___ 98 | 99 | ### toSnapshot 100 | 101 | ▸ **toSnapshot**(`value`: T): *S* 102 | 103 | *Defined in [src/types/utility-types/custom.ts:19](https://github.com/mobxjs/mobx-state-tree/blob/1be40a3e/src/types/utility-types/custom.ts#L19)* 104 | 105 | return the serialization of the current value 106 | 107 | **Parameters:** 108 | 109 | Name | Type | 110 | ------ | ------ | 111 | `value` | T | 112 | 113 | **Returns:** *S* 114 | -------------------------------------------------------------------------------- /__tests__/core/this.test.ts: -------------------------------------------------------------------------------- 1 | import { types } from "../../src" 2 | import { isObservableProp, isComputedProp } from "mobx" 3 | import { expect, test } from "bun:test" 4 | 5 | // MWE: disabled test, `this` isn't supposed to work, and afaik nowhere advertised 6 | test.skip("this support", () => { 7 | const M = types 8 | .model({ x: 5 }) 9 | .views(self => ({ 10 | get x2() { 11 | return self.x * 2 12 | }, 13 | get x4() { 14 | return this.x2 * 2 15 | }, 16 | boundTo() { 17 | return this 18 | }, 19 | innerBoundTo() { 20 | return () => this 21 | }, 22 | isThisObservable() { 23 | return ( 24 | isObservableProp(this, "x2") && 25 | isObservableProp(this, "x4") && 26 | isObservableProp(this, "localState") && 27 | isComputedProp(this, "x2") 28 | ) 29 | } 30 | })) 31 | .volatile(self => ({ 32 | localState: 3, 33 | getLocalState() { 34 | return this.localState 35 | }, 36 | getLocalState2() { 37 | return this.getLocalState() * 2 38 | } 39 | })) 40 | 41 | .actions(self => { 42 | return { 43 | xBy(by: number) { 44 | return self.x * by 45 | }, 46 | setX(x: number) { 47 | self.x = x 48 | }, 49 | setThisX(x: number) { 50 | ;(this as any).x = x // this should not affect self.x 51 | }, 52 | setXBy(x: number) { 53 | this.setX(this.xBy(x)) 54 | }, 55 | setLocalState(x: number) { 56 | self.localState = x 57 | } 58 | } 59 | }) 60 | 61 | const mi = M.create() 62 | 63 | expect(mi.isThisObservable()).toBe(true) 64 | 65 | expect(mi.boundTo()).toBe(mi) 66 | expect(mi.innerBoundTo()()).toBe(mi) 67 | 68 | expect(mi.x).toBe(5) 69 | 70 | mi.setX(6) 71 | expect(mi.x).toBe(6) 72 | 73 | mi.setXBy(2) 74 | expect(mi.x).toBe(12) 75 | expect(mi.x2).toBe(12 * 2) 76 | expect(mi.x4).toBe(12 * 4) 77 | expect(mi.xBy(2)).toBe(24) 78 | 79 | expect(mi.localState).toBe(3) 80 | expect(mi.getLocalState()).toBe(3) 81 | expect(mi.getLocalState2()).toBe(3 * 2) 82 | 83 | mi.setLocalState(6) 84 | expect(mi.localState).toBe(6) 85 | expect(mi.getLocalState()).toBe(6) 86 | expect(mi.getLocalState2()).toBe(6 * 2) 87 | 88 | mi.setLocalState(7) 89 | expect(mi.localState).toBe(7) 90 | 91 | // make sure attempts to modify this (as long as it is not an action) doesn't affect self 92 | const oldX = mi.x 93 | mi.setThisX(oldX + 1) 94 | expect(mi.x).toBe(oldX) 95 | }) 96 | -------------------------------------------------------------------------------- /docs/API/interfaces/ipatchrecorder.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: "ipatchrecorder" 3 | title: "IPatchRecorder" 4 | sidebar_label: "IPatchRecorder" 5 | --- 6 | 7 | [mobx-state-tree - v7.0.2](../index.md) › [IPatchRecorder](ipatchrecorder.md) 8 | 9 | ## Hierarchy 10 | 11 | * **IPatchRecorder** 12 | 13 | ## Index 14 | 15 | ### Properties 16 | 17 | * [inversePatches](ipatchrecorder.md#inversepatches) 18 | * [patches](ipatchrecorder.md#patches) 19 | * [recording](ipatchrecorder.md#recording) 20 | * [reversedInversePatches](ipatchrecorder.md#reversedinversepatches) 21 | 22 | ### Methods 23 | 24 | * [replay](ipatchrecorder.md#replay) 25 | * [resume](ipatchrecorder.md#resume) 26 | * [stop](ipatchrecorder.md#stop) 27 | * [undo](ipatchrecorder.md#undo) 28 | 29 | ## Properties 30 | 31 | ### inversePatches 32 | 33 | • **inversePatches**: *ReadonlyArray‹[IJsonPatch](ijsonpatch.md)›* 34 | 35 | *Defined in [src/core/mst-operations.ts:137](https://github.com/mobxjs/mobx-state-tree/blob/1be40a3e/src/core/mst-operations.ts#L137)* 36 | 37 | ___ 38 | 39 | ### patches 40 | 41 | • **patches**: *ReadonlyArray‹[IJsonPatch](ijsonpatch.md)›* 42 | 43 | *Defined in [src/core/mst-operations.ts:136](https://github.com/mobxjs/mobx-state-tree/blob/1be40a3e/src/core/mst-operations.ts#L136)* 44 | 45 | ___ 46 | 47 | ### recording 48 | 49 | • **recording**: *boolean* 50 | 51 | *Defined in [src/core/mst-operations.ts:139](https://github.com/mobxjs/mobx-state-tree/blob/1be40a3e/src/core/mst-operations.ts#L139)* 52 | 53 | ___ 54 | 55 | ### reversedInversePatches 56 | 57 | • **reversedInversePatches**: *ReadonlyArray‹[IJsonPatch](ijsonpatch.md)›* 58 | 59 | *Defined in [src/core/mst-operations.ts:138](https://github.com/mobxjs/mobx-state-tree/blob/1be40a3e/src/core/mst-operations.ts#L138)* 60 | 61 | ## Methods 62 | 63 | ### replay 64 | 65 | ▸ **replay**(`target?`: IAnyStateTreeNode): *void* 66 | 67 | *Defined in [src/core/mst-operations.ts:142](https://github.com/mobxjs/mobx-state-tree/blob/1be40a3e/src/core/mst-operations.ts#L142)* 68 | 69 | **Parameters:** 70 | 71 | Name | Type | 72 | ------ | ------ | 73 | `target?` | IAnyStateTreeNode | 74 | 75 | **Returns:** *void* 76 | 77 | ___ 78 | 79 | ### resume 80 | 81 | ▸ **resume**(): *void* 82 | 83 | *Defined in [src/core/mst-operations.ts:141](https://github.com/mobxjs/mobx-state-tree/blob/1be40a3e/src/core/mst-operations.ts#L141)* 84 | 85 | **Returns:** *void* 86 | 87 | ___ 88 | 89 | ### stop 90 | 91 | ▸ **stop**(): *void* 92 | 93 | *Defined in [src/core/mst-operations.ts:140](https://github.com/mobxjs/mobx-state-tree/blob/1be40a3e/src/core/mst-operations.ts#L140)* 94 | 95 | **Returns:** *void* 96 | 97 | ___ 98 | 99 | ### undo 100 | 101 | ▸ **undo**(`target?`: IAnyStateTreeNode): *void* 102 | 103 | *Defined in [src/core/mst-operations.ts:143](https://github.com/mobxjs/mobx-state-tree/blob/1be40a3e/src/core/mst-operations.ts#L143)* 104 | 105 | **Parameters:** 106 | 107 | Name | Type | 108 | ------ | ------ | 109 | `target?` | IAnyStateTreeNode | 110 | 111 | **Returns:** *void* 112 | -------------------------------------------------------------------------------- /__tests__/core/pointer.test.ts: -------------------------------------------------------------------------------- 1 | import { types, unprotect, IAnyModelType, castToReferenceSnapshot } from "../../src" 2 | import { expect, test } from "bun:test" 3 | 4 | function Pointer(Model: IT) { 5 | return types.model("PointerOf" + Model.name, { 6 | value: types.maybe(types.reference(Model)) 7 | }) 8 | } 9 | const Todo = types.model("Todo", { 10 | id: types.identifier, 11 | name: types.string 12 | }) 13 | test("it should allow array of pointer objects", () => { 14 | const TodoPointer = Pointer(Todo) 15 | const AppStore = types.model("AppStore", { 16 | todos: types.array(Todo), 17 | selected: types.optional(types.array(TodoPointer), []) 18 | }) 19 | const store = AppStore.create({ 20 | todos: [ 21 | { id: "1", name: "Hello" }, 22 | { id: "2", name: "World" } 23 | ], 24 | selected: [] 25 | }) 26 | unprotect(store) 27 | const ref = TodoPointer.create({ value: castToReferenceSnapshot(store.todos[0]) }) // Fails because store.todos does not belongs to the same tree 28 | store.selected.push(ref) 29 | expect(store.selected[0].value).toBe(store.todos[0]) 30 | }) 31 | test("it should allow array of pointer objects - 2", () => { 32 | const TodoPointer = Pointer(Todo) 33 | const AppStore = types.model({ 34 | todos: types.array(Todo), 35 | selected: types.optional(types.array(TodoPointer), []) 36 | }) 37 | const store = AppStore.create({ 38 | todos: [ 39 | { id: "1", name: "Hello" }, 40 | { id: "2", name: "World" } 41 | ], 42 | selected: [] 43 | }) 44 | unprotect(store) 45 | const ref = TodoPointer.create() 46 | store.selected.push(ref) 47 | ref.value = store.todos[0] 48 | expect(store.selected[0].value).toBe(store.todos[0]) 49 | }) 50 | test("it should allow array of pointer objects - 3", () => { 51 | const TodoPointer = Pointer(Todo) 52 | const AppStore = types.model({ 53 | todos: types.array(Todo), 54 | selected: types.optional(types.array(TodoPointer), []) 55 | }) 56 | const store = AppStore.create({ 57 | todos: [ 58 | { id: "1", name: "Hello" }, 59 | { id: "2", name: "World" } 60 | ], 61 | selected: [] 62 | }) 63 | unprotect(store) 64 | const ref = TodoPointer.create({ value: castToReferenceSnapshot(store.todos[0]) }) 65 | store.selected.push(ref) 66 | expect(store.selected[0].value).toBe(store.todos[0]) 67 | }) 68 | test("it should allow array of pointer objects - 4", () => { 69 | const TodoPointer = Pointer(Todo) 70 | const AppStore = types.model({ 71 | todos: types.array(Todo), 72 | selected: types.optional(types.array(TodoPointer), []) 73 | }) 74 | const store = AppStore.create({ 75 | todos: [ 76 | { id: "1", name: "Hello" }, 77 | { id: "2", name: "World" } 78 | ], 79 | selected: [] 80 | }) 81 | unprotect(store) 82 | const ref = TodoPointer.create() // Fails because ref is required 83 | store.selected.push(ref) 84 | ref.value = store.todos[0] 85 | expect(ref.value).toBe(store.todos[0]) 86 | }) 87 | -------------------------------------------------------------------------------- /__tests__/core/frozen.test.ts: -------------------------------------------------------------------------------- 1 | import { getSnapshot, types, unprotect } from "../../src" 2 | import { expect, test } from "bun:test" 3 | 4 | test("it should accept any serializable value", () => { 5 | const Factory = types.model({ 6 | value: types.frozen<{ a: number; b: number } | undefined>() 7 | }) 8 | const doc = Factory.create() 9 | unprotect(doc) 10 | doc.value = { a: 1, b: 2 } 11 | expect(getSnapshot(doc)).toEqual({ value: { a: 1, b: 2 } }) 12 | }) 13 | 14 | if (process.env.NODE_ENV !== "production") { 15 | test("it should throw if value is not serializable", () => { 16 | const Factory = types.model({ 17 | value: types.frozen() 18 | }) 19 | const doc = Factory.create() 20 | unprotect(doc) 21 | expect(() => { 22 | doc.value = function IAmUnserializable() {} 23 | }).toThrow(/Error while converting to `frozen`/) 24 | }) 25 | } 26 | 27 | test("it should accept any default value value", () => { 28 | const Factory = types.model({ 29 | value: types.frozen(3) 30 | }) 31 | const doc = Factory.create() 32 | expect(Factory.is({})).toBeTruthy() 33 | expect(getSnapshot(doc)).toEqual({ value: 3 }) 34 | }) 35 | 36 | test("it should type strongly", () => { 37 | type Point = { x: number; y: number } 38 | const Mouse = types 39 | .model({ 40 | loc: types.frozen() 41 | }) 42 | .actions(self => ({ 43 | moveABit() { 44 | // self.loc.x += 1; // compile error, x is readonly! 45 | ;(self.loc as any).x += 1 // throws, frozen! 46 | } 47 | })) 48 | 49 | expect(Mouse.is({})).toBeTruthy() // any value is acceptable to frozen, even undefined... 50 | 51 | const m = Mouse.create({ 52 | // loc: 3 // type error! 53 | loc: { x: 2, y: 3 } 54 | }) 55 | 56 | if (process.env.NODE_ENV !== "production") { 57 | expect(() => { 58 | m.moveABit() 59 | }).toThrow("Attempted to assign to readonly property.") 60 | } 61 | }) 62 | 63 | if (process.env.NODE_ENV !== "production") { 64 | test("it should be capable of using another MST type", () => { 65 | const Point = types.model("Point", { x: types.number, y: types.number }) 66 | const Mouse = types.model({ 67 | loc: types.frozen(Point) 68 | }) 69 | 70 | expect(Mouse.is({})).toBeFalsy() 71 | expect(Mouse.is({ loc: {} })).toBeFalsy() 72 | expect(Mouse.is({ loc: { x: 3, y: 2 } })).toBeTruthy() 73 | 74 | expect(() => { 75 | ;(Mouse.create as any)() 76 | }).toThrow( 77 | 'at path "/loc" value `undefined` is not assignable to type: `frozen(Point)` (Value is not a plain object)' 78 | ) 79 | expect(() => { 80 | Mouse.create({ loc: { x: 4 } } as any) 81 | }).toThrow( 82 | 'at path "/loc/y" value `undefined` is not assignable to type: `number` (Value is not a number)' 83 | ) 84 | 85 | const m = Mouse.create({ 86 | loc: { x: 3, y: 2 } 87 | }) 88 | 89 | const x = m.loc.x 90 | expect(x).toBe(3) 91 | }) 92 | } 93 | -------------------------------------------------------------------------------- /.circleci/config.yml: -------------------------------------------------------------------------------- 1 | version: 2.1 2 | 3 | executors: 4 | my-executor: 5 | docker: 6 | - image: cimg/node:14.18.1 7 | environment: 8 | CI: true 9 | 10 | orbs: 11 | node: circleci/node@4.7.0 12 | 13 | jobs: 14 | # mobx-state-tree build 15 | build: 16 | executor: my-executor 17 | steps: 18 | - checkout 19 | 20 | - run: 21 | name: Install the latest version of bun 22 | command: curl -fsSL https://bun.sh/install | bash 23 | - run: 24 | name: Link bun 25 | command: sudo ln -s ~/.bun/bin/bun /usr/local/bin/ 26 | - run: 27 | name: Install dependencies 28 | command: bun install 29 | - run: 30 | name: Build MST 31 | command: bun run build 32 | 33 | - persist_to_workspace: 34 | root: . 35 | paths: 36 | - ./* 37 | 38 | # Add new prettier check job 39 | check-prettier: 40 | executor: my-executor 41 | steps: 42 | - attach_workspace: 43 | at: . 44 | - run: 45 | name: Install the latest version of bun 46 | command: curl -fsSL https://bun.sh/install | bash 47 | - run: 48 | name: Link bun 49 | command: sudo ln -s ~/.bun/bin/bun /usr/local/bin/ 50 | - run: 51 | name: Check code formatting 52 | command: bun run prettier:check 53 | 54 | # mobx-state-tree tests 55 | test-mst-dev: 56 | executor: my-executor 57 | steps: 58 | - attach_workspace: 59 | at: . 60 | 61 | - run: 62 | name: Install the latest version of bun 63 | command: curl -fsSL https://bun.sh/install | bash 64 | - run: 65 | name: Link bun 66 | command: sudo ln -s ~/.bun/bin/bun /usr/local/bin/ 67 | - run: bun test:all 68 | 69 | test-mst-prod: 70 | executor: my-executor 71 | steps: 72 | - attach_workspace: 73 | at: . 74 | - run: 75 | name: Install the latest version of bun 76 | command: curl -fsSL https://bun.sh/install | bash 77 | - run: 78 | name: Link bun 79 | command: sudo ln -s ~/.bun/bin/bun /usr/local/bin/ 80 | - run: bun test:prod 81 | 82 | test-size: 83 | executor: my-executor 84 | steps: 85 | - attach_workspace: 86 | at: . 87 | - run: 88 | name: Install the latest version of bun 89 | command: curl -fsSL https://bun.sh/install | bash 90 | - run: 91 | name: Link bun 92 | command: sudo ln -s ~/.bun/bin/bun /usr/local/bin/ 93 | - run: bun run size 94 | 95 | workflows: 96 | version: 2 97 | build-and-test: 98 | jobs: 99 | - build 100 | 101 | # Add prettier check to workflow 102 | - check-prettier: 103 | requires: 104 | - build 105 | 106 | - test-mst-dev: 107 | requires: 108 | - build 109 | - test-mst-prod: 110 | requires: 111 | - build 112 | # Temporarily disabled while we work to implement Bun testing 113 | # - test-size: 114 | # requires: 115 | # - build 116 | -------------------------------------------------------------------------------- /__tests__/core/enum.test.ts: -------------------------------------------------------------------------------- 1 | import { types, unprotect } from "../../src" 2 | import { expect, test } from "bun:test" 3 | 4 | enum ColorEnum { 5 | Red = "Red", 6 | Orange = "Orange", 7 | Green = "Green" 8 | } 9 | const colorEnumValues = Object.values(ColorEnum) as ColorEnum[] 10 | 11 | test("should support enums", () => { 12 | const TrafficLight = types.model({ color: types.enumeration("Color", colorEnumValues) }) 13 | expect(TrafficLight.is({ color: ColorEnum.Green })).toBe(true) 14 | expect(TrafficLight.is({ color: "Blue" })).toBe(false) 15 | expect(TrafficLight.is({ color: undefined })).toBe(false) 16 | const l = TrafficLight.create({ color: ColorEnum.Orange }) 17 | unprotect(l) 18 | l.color = ColorEnum.Red 19 | expect(TrafficLight.describe()).toBe('{ color: ("Red" | "Orange" | "Green") }') 20 | if (process.env.NODE_ENV !== "production") { 21 | expect(() => (l.color = "Blue" as any)).toThrow( 22 | /Error while converting `"Blue"` to `Color`/ 23 | ) 24 | } 25 | }) 26 | test("should support anonymous enums", () => { 27 | const TrafficLight = types.model({ color: types.enumeration(colorEnumValues) }) 28 | const l = TrafficLight.create({ color: ColorEnum.Orange }) 29 | unprotect(l) 30 | l.color = ColorEnum.Red 31 | expect(TrafficLight.describe()).toBe('{ color: ("Red" | "Orange" | "Green") }') 32 | if (process.env.NODE_ENV !== "production") { 33 | expect(() => (l.color = "Blue" as any)).toThrow( 34 | /Error while converting `"Blue"` to `"Red" | "Orange" | "Green"`/ 35 | ) 36 | } 37 | }) 38 | test("should support optional enums", () => { 39 | const TrafficLight = types.optional(types.enumeration(colorEnumValues), ColorEnum.Orange) 40 | const l = TrafficLight.create() 41 | expect(l).toBe(ColorEnum.Orange) 42 | }) 43 | test("should support optional enums inside a model", () => { 44 | const TrafficLight = types.model({ 45 | color: types.optional(types.enumeration(colorEnumValues), ColorEnum.Orange) 46 | }) 47 | const l = TrafficLight.create({}) 48 | expect(l.color).toBe(ColorEnum.Orange) 49 | }) 50 | test("should support plain string[] arrays", () => { 51 | const colorOptions: string[] = ["Red", "Orange", "Green"] 52 | const TrafficLight = types.model({ color: types.enumeration(colorOptions) }) 53 | const l = TrafficLight.create({ color: "Orange" }) 54 | unprotect(l) 55 | l.color = "Red" 56 | expect(TrafficLight.describe()).toBe('{ color: ("Red" | "Orange" | "Green") }') 57 | if (process.env.NODE_ENV !== "production") { 58 | expect(() => (l.color = "Blue" as any)).toThrow( 59 | /Error while converting `"Blue"` to `"Red" | "Orange" | "Green"`/ 60 | ) 61 | } 62 | }) 63 | test("should support readonly enums as const", () => { 64 | const colorOptions = ["Red", "Orange", "Green"] as const 65 | const TrafficLight = types.model({ color: types.enumeration(colorOptions) }) 66 | const l = TrafficLight.create({ color: "Orange" }) 67 | unprotect(l) 68 | l.color = "Red" 69 | expect(TrafficLight.describe()).toBe('{ color: ("Red" | "Orange" | "Green") }') 70 | if (process.env.NODE_ENV !== "production") { 71 | expect(() => (l.color = "Blue" as any)).toThrow( 72 | /Error while converting `"Blue"` to `"Red" | "Orange" | "Green"`/ 73 | ) 74 | } 75 | }) 76 | -------------------------------------------------------------------------------- /__tests__/core/lazy.test.ts: -------------------------------------------------------------------------------- 1 | import { when } from "mobx" 2 | import { getRoot, types } from "../../src" 3 | import { expect, test } from "bun:test" 4 | 5 | interface IRootModel { 6 | shouldLoad: boolean 7 | } 8 | 9 | test("it should load the correct type", async () => { 10 | const LazyModel = types 11 | .model("LazyModel", { 12 | width: types.number, 13 | height: types.number 14 | }) 15 | .views(self => ({ 16 | get area() { 17 | return self.height * self.width 18 | } 19 | })) 20 | 21 | const Root = types 22 | .model("Root", { 23 | shouldLoad: types.optional(types.boolean, false), 24 | lazyModel: types.lazy("lazy", { 25 | loadType: () => Promise.resolve(LazyModel), 26 | shouldLoadPredicate: parent => parent.shouldLoad == true 27 | }) 28 | }) 29 | .actions(self => ({ 30 | load: () => { 31 | self.shouldLoad = true 32 | } 33 | })) 34 | 35 | const store = Root.create({ 36 | lazyModel: { 37 | width: 3, 38 | height: 2 39 | } 40 | }) 41 | 42 | expect(store.lazyModel.width).toBe(3) 43 | expect(store.lazyModel.height).toBe(2) 44 | expect(store.lazyModel.area).toBeUndefined() 45 | store.load() 46 | 47 | await when(() => store.lazyModel && store.lazyModel.area !== undefined, { timeout: 2000 }) 48 | await expect(store.lazyModel.area).toBe(6) 49 | }) 50 | 51 | test("maintains the tree structure when loaded", async () => { 52 | const LazyModel = types 53 | .model("LazyModel", { 54 | width: types.number, 55 | height: types.number 56 | }) 57 | .views(self => ({ 58 | get area() { 59 | const root = getRoot<{ rootValue: number }>(self) 60 | return self.height * self.width * root.rootValue 61 | } 62 | })) 63 | 64 | const Root = types 65 | .model("Root", { 66 | shouldLoad: types.optional(types.boolean, false), 67 | lazyModel: types.lazy("lazy", { 68 | loadType: () => Promise.resolve(LazyModel), 69 | shouldLoadPredicate: parent => parent.shouldLoad == true 70 | }) 71 | }) 72 | .views(() => ({ 73 | get rootValue() { 74 | return 5 75 | } 76 | })) 77 | .actions(self => ({ 78 | load: () => { 79 | self.shouldLoad = true 80 | } 81 | })) 82 | 83 | const store = Root.create({ 84 | lazyModel: { 85 | width: 3, 86 | height: 2 87 | } 88 | }) 89 | 90 | expect(store.lazyModel.width).toBe(3) 91 | expect(store.lazyModel.height).toBe(2) 92 | expect(store.rootValue).toEqual(5) 93 | expect(store.lazyModel.area).toBeUndefined() 94 | store.load() 95 | const promise = new Promise((resolve, reject) => { 96 | when( 97 | () => store.lazyModel && store.lazyModel.area !== undefined, 98 | () => resolve(store.lazyModel.area) 99 | ) 100 | 101 | setTimeout(reject, 2000) 102 | }) 103 | 104 | await expect(promise).resolves.toBe(30) 105 | }) 106 | -------------------------------------------------------------------------------- /__tests__/perf/fixtures/fixture-data.ts: -------------------------------------------------------------------------------- 1 | import { HeroRoles } from "./fixture-models" 2 | 3 | /** 4 | * Creates data containing very few fields. 5 | * 6 | * @param count The number of items to create. 7 | */ 8 | export function createTreasure(count: number) { 9 | const data = [] 10 | let i = 0 11 | do { 12 | data.push({ 13 | trapped: i % 2 === 0, 14 | gold: ((count % 10) + 1) * 10 15 | }) 16 | i++ 17 | } while (i < count) 18 | return data 19 | } 20 | 21 | // why yes i DID graduate high school, why do you ask? 22 | export const rando = () => (Math.random() > 0.5 ? 1 : 0) 23 | 24 | const titles = ["Sir", "Lady", "Baron von", "Baroness", "Captain", "Dread", "Fancy"].sort(rando) 25 | const givenNames = ["Abe", "Beth", "Chuck", "Dora", "Ernie", "Fran", "Gary", "Haily"].sort(rando) 26 | const epicNames = ["Amazing", "Brauny", "Chafed", "Dapper", "Egomaniac", "Foul"].sort(rando) 27 | const wtf = `Daenerys Stormborn of the House Targaryen, First of Her Name, the Unburnt, 28 | Queen of the Andals and the First Men, Khaleesi of the Great Grass Sea, Breaker of Chains, 29 | and Mother of Dragons. ` 30 | /** 31 | * Creates data with a medium number of fields and data. 32 | * 33 | * @param count The number of items to create. 34 | */ 35 | export function createHeros(count: number) { 36 | const data = [] 37 | let i = 0 38 | let even = true 39 | let n1 40 | let n2 41 | let n3 42 | do { 43 | n1 = titles[i % titles.length] 44 | n2 = givenNames[i % givenNames.length] 45 | n3 = epicNames[i % epicNames.length] 46 | data.push({ 47 | id: i, 48 | name: `${n1} ${n2} the ${n3}`, 49 | level: (count % 100) + 1, 50 | role: HeroRoles[i % HeroRoles.length], 51 | description: `${wtf} ${wtf} ${wtf}` 52 | }) 53 | even = !even 54 | i++ 55 | } while (i < count) 56 | return data 57 | } 58 | 59 | /** 60 | * Creates data with a large number of fields and data. 61 | * 62 | * @param count The number of items to create. 63 | * @param treasureCount The number of small children to create. 64 | * @param heroCount The number of medium children to create. 65 | */ 66 | export function createMonsters(count: number, treasureCount: number, heroCount: number) { 67 | const data = [] 68 | let i = 0 69 | let even = true 70 | do { 71 | const treasures = createTreasure(treasureCount) 72 | const eatenHeroes = createHeros(heroCount) 73 | data.push({ 74 | id: `omg-${i}-run!`, 75 | freestyle: `${wtf} ${wtf} ${wtf}${wtf} ${wtf} ${wtf}`, 76 | level: (count % 100) + 1, 77 | hp: i % 2 === 0 ? 1 : 5 * i, 78 | maxHp: 5 * i, 79 | warning: "!!!!!!", 80 | createdAt: new Date(), 81 | hasFangs: even, 82 | hasClaws: even, 83 | hasWings: !even, 84 | hasGrowl: !even, 85 | fearsFire: even, 86 | fearsWater: !even, 87 | fearsWarriors: even, 88 | fearsClerics: !even, 89 | fearsMages: even, 90 | fearsThieves: !even, 91 | stenchLevel: i % 5, 92 | treasures, 93 | eatenHeroes 94 | }) 95 | even = !even 96 | i++ 97 | } while (i < count) 98 | return data 99 | } 100 | -------------------------------------------------------------------------------- /docs/tips/more-tips.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: more-tips 3 | title: Miscellaneous Tips 4 | --- 5 | 6 |
7 | 8 | ### Generate MST models from JSON 9 | 10 | The following service can generate MST models based on JSON: https://transform.now.sh/json-to-mobx-state-tree 11 | 12 | ### `optionals` and default value functions 13 | 14 | `types.optional` can take an optional function parameter which will be invoked each time a default value is needed. This is useful to generate timestamps, identifiers or even complex objects, for example: 15 | 16 | `createdDate: types.optional(types.Date, () => new Date())` 17 | 18 | ### `toJSON()` for debugging 19 | 20 | For debugging you might want to use `getSnapshot(model, applyPostProcess)` to print the state of a model. If you didn't import `getSnapshot` while debugging in some debugger, don't worry, `model.toJSON()` will produce the same snapshot. (For API consistency, this feature is not part of the typed API). 21 | 22 | #### Optional/empty maps/arrays 23 | 24 | Since v3, maps and arrays are optional by default, this is: 25 | 26 | ```javascript 27 | types.map(OtherType) 28 | // is the same as 29 | types.optional(types.map(OtherType), {}) 30 | 31 | types.array(OtherType) 32 | // is the same as 33 | types.optional(types.array(OtherType), []) 34 | ``` 35 | 36 | #### Complex union types 37 | 38 | Union types works well when the types are primitives. Unions might cause bugs when complex types are involved. For Example 39 | 40 | ```javascript 41 | const Foo = types.model({ foo: types.array(types.string) }) 42 | const Bar = types.model({ bar: types.array(types.number) }) 43 | const FooBar = types.union(Foo, Bar) 44 | 45 | const test_foo = { foo: ["test"] } 46 | const test_bar = { bar: [200] } 47 | 48 | const unionStore = Store.create({ 49 | foobars: [test_foo, test_bar] 50 | }) 51 | 52 | const foo = unionStore.foobars[0] 53 | const bar = unionStore.foobars[1] 54 | console.log(foo, bar) 55 | 56 | // Expected: { foo: ["test"], bar: [200] } 57 | // Actual: { foo: ["test"], foo: [] } 58 | ``` 59 | 60 | This can be solved in two ways 61 | 62 | **Using Dispatcher:** 63 | 64 | You can provide first arg with a _dispatcher_ that provides _snapshot_ which can be used to explicitly return the `type` of the model 65 | 66 | ```javascript 67 | const FooBar = types.union( 68 | { 69 | dispatcher: (snapshot) => { 70 | console.log({ snapshot }) 71 | if (snapshot.foo) { 72 | return Foo 73 | } 74 | return Bar 75 | } 76 | }, 77 | Foo, 78 | Bar 79 | ) 80 | ``` 81 | 82 | **Using Literals:** 83 | 84 | Adding type literal to the Base Models to identify the type 85 | 86 | ```javascript 87 | const Foo = types.model({ 88 | foo: types.array(types.string), 89 | type: types.literal("foo") 90 | }) 91 | 92 | const Bar = types.model({ 93 | bar: types.array(types.number), 94 | type: types.literal("bar") 95 | }) 96 | ``` 97 | 98 | ### Building with production environment 99 | 100 | MobX-state-tree provides a lot of dev-only checks. They check the correctness of function calls and perform runtime type-checks over your models. It is recommended to disable them in production builds. To do so, you should use webpack's DefinePlugin to set environment as production and remove them. More information could be found in the [official webpack guides](https://webpack.js.org/plugins/environment-plugin/#usage). 101 | -------------------------------------------------------------------------------- /docs/tips/snapshots-as-values.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: snapshots-as-values 3 | title: Using snapshots as values 4 | --- 5 | 6 |
7 | 8 | Everywhere where you can modify your state tree and assign a model instance, you can also 9 | just assign a snapshot, and MST will convert it to a model instance for you. 10 | However, that is simply not expressible in static type systems atm (as the type written to a value differs to the type read from it). 11 | As a workaround MST offers a `cast` function, which will try to fool the typesystem into thinking that an snapshot type (and instance as well) 12 | is of the related instance type. 13 | 14 | ```typescript 15 | const Task = types.model({ 16 | done: false 17 | }) 18 | const Store = types.model({ 19 | tasks: types.array(Task), 20 | selection: types.maybe(Task) 21 | }) 22 | 23 | const s = Store.create({ tasks: [] }) 24 | // `{}` is a valid snapshot of Task, and hence a valid task, MST allows this, but TS doesn't, so we need to use 'cast' 25 | s.tasks.push(cast({})) 26 | s.selection = cast({}) 27 | ``` 28 | 29 | Additionally, for function parameters, MST offers a `SnapshotOrInstance` type, where T can either be a `typeof TYPE` or a 30 | `typeof VARIABLE`. In both cases it will resolve to the union of the input (creation) snapshot and instance type of that TYPE or VARIABLE. 31 | 32 | Using both at the same time we can express property assignation of complex properties in this form: 33 | 34 | ```typescript 35 | const Task = types.model({ 36 | done: false 37 | }) 38 | const Store = types 39 | .model({ 40 | tasks: types.array(Task) 41 | }) 42 | .actions(self => ({ 43 | addTask(task: SnapshotOrInstance) { 44 | self.tasks.push(cast(task)) 45 | }, 46 | replaceTasks(tasks: SnapshotOrInstance) { 47 | self.tasks = cast(tasks) 48 | } 49 | })) 50 | 51 | const s = Store.create({ tasks: [] }) 52 | 53 | s.addTask({}) 54 | // or 55 | s.addTask(Task.create({})) 56 | 57 | s.replaceTasks([{ done: true }]) 58 | // or 59 | s.replaceTasks(types.array(Task).create([{ done: true }])) 60 | ``` 61 | 62 | Additionally, the `castToSnapshot` function can be also used in the inverse case, this is when you want to use an instance inside an snapshot. 63 | In this case MST will internally convert the instance to a snapshot before using it, but we need once more to fool TypeScript into 64 | thinking that this instance is actually a snapshot. 65 | 66 | ```typescript 67 | const task = Task.create({ done: true }) 68 | const Store = types.model({ 69 | tasks: types.array(Task) 70 | }) 71 | 72 | // we cast the task instance to a snapshot so it can be used as part of another snapshot without typing errors 73 | const s = Store.create({ tasks: [castToSnapshot(task)] }) 74 | ``` 75 | 76 | Finally, the `castToReferenceSnapshot` can be used when we want to use an instance to actually use a reference snapshot (a string or number). 77 | In this case MST will internally convert the instance to a reference snapshot before using it, but we need once more to fool TypeScript into 78 | thinking that this instance is actually a snapshot of a reference. 79 | 80 | ```typescript 81 | const task = Task.create({ id: types.identifier, done: true }) 82 | const Store = types.model({ 83 | tasks: types.array(types.reference(Task)) 84 | }) 85 | 86 | // we cast the task instance to a reference snapshot so it can be used as part of another snapshot without typing errors 87 | const s = Store.create({ tasks: [castToReferenceSnapshot(task)] }) 88 | ``` 89 | 90 | -------------------------------------------------------------------------------- /docs/concepts/snapshots.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: snapshots 3 | title: Snapshots 4 | --- 5 | 6 |
7 | 8 |
9 | egghead.io lesson 3: Test mobx-state-tree Models by Recording Snapshots or Patches 10 |
11 |
12 | 13 |
14 | Hosted on egghead.io 15 |
16 | 17 |
18 | egghead.io lesson 9: Store Store in Local Storage 19 |
20 |
21 | 22 |
23 | Hosted on egghead.io 24 |
25 | 26 |
27 | egghead.io lesson 16: Automatically Send Changes to the Server by Using onSnapshot 28 |
29 |
30 | 31 |
32 | Hosted on egghead.io 33 |
34 | 35 | Snapshots are the immutable serialization, in plain objects, of a tree at a specific point in time. 36 | Snapshots can be inspected through `getSnapshot(node, applyPostProcess)`. 37 | Snapshots don't contain any type information and are stripped from all actions, etc., so they are perfectly suitable for transportation. 38 | Requesting a snapshot is cheap as MST always maintains a snapshot of each node in the background and uses structural sharing. 39 | 40 | ```javascript 41 | coffeeTodo.setTitle("Tea instead plz") 42 | 43 | console.dir(getSnapshot(coffeeTodo)) 44 | // prints `{ title: "Tea instead plz" }` 45 | ``` 46 | 47 | Some interesting properties of snapshots: 48 | 49 | - Snapshots are immutable 50 | - Snapshots can be transported 51 | - Snapshots can be used to update models or restore them to a particular state 52 | - Snapshots are automatically converted to models when needed. So, the two following statements are equivalent: `store.todos.push(Todo.create({ title: "test" }))` and `store.todos.push({ title: "test" })`. 53 | 54 | Useful methods: 55 | 56 | - `getSnapshot(model, applyPostProcess)`: returns a snapshot representing the current state of the model 57 | - `onSnapshot(model, callback)`: creates a listener that fires whenever a new snapshot is available (but only one per MobX transaction). 58 | - `applySnapshot(model, snapshot)`: updates the state of the model and all its descendants to the state represented by the snapshot 59 | 60 | `mobx-state-tree` also supports customizing snapshots when they are generated or when they are applied with [`types.snapshotProcessor`](/overview/hooks). -------------------------------------------------------------------------------- /__tests__/core/primitives.test.ts: -------------------------------------------------------------------------------- 1 | import { isFinite, isFloat, isInteger } from "../../src/utils" 2 | import { types, applySnapshot, getSnapshot } from "../../src" 3 | import { expect, test } from "bun:test" 4 | 5 | test("Date instance can be reused", () => { 6 | const Model = types.model({ 7 | a: types.model({ 8 | b: types.string 9 | }), 10 | c: types.Date // types.string -> types.Date 11 | }) 12 | const Store = types 13 | .model({ 14 | one: Model, 15 | index: types.array(Model) 16 | }) 17 | .actions(self => { 18 | function set(one: typeof Model.Type) { 19 | self.one = one 20 | } 21 | function push(model: typeof Model.Type) { 22 | self.index.push(model) 23 | } 24 | return { 25 | set, 26 | push 27 | } 28 | }) 29 | const object = { a: { b: "string" }, c: new Date() } // string -> date (number) 30 | const instance = Store.create({ 31 | one: object, 32 | index: [object] 33 | }) 34 | instance.set(object) 35 | expect(() => instance.push(object)).not.toThrow() 36 | expect(instance.one.c).toBe(object.c) 37 | expect(instance.index[0].c).toBe(object.c) 38 | }) 39 | test("Date can be rehydrated using unix timestamp", () => { 40 | const time = new Date() 41 | const newTime = 6813823163 42 | const Factory = types.model({ 43 | date: types.optional(types.Date, () => time) 44 | }) 45 | const store = Factory.create() 46 | expect(store.date.getTime()).toBe(time.getTime()) 47 | applySnapshot(store, { date: newTime }) 48 | expect(store.date.getTime()).toBe(newTime) 49 | expect(getSnapshot(store).date).toBe(newTime) 50 | }) 51 | 52 | test("check isInteger", () => { 53 | expect(isInteger(5)).toBe(true) 54 | expect(isInteger(-5)).toBe(true) 55 | expect(isInteger(5.2)).toBe(false) 56 | }) 57 | 58 | test("Default inference for integers is 'number'", () => { 59 | const A = types.model({ 60 | x: 3 61 | }) 62 | expect( 63 | A.is({ 64 | x: 2.5 65 | }) 66 | ).toBe(true) 67 | }) 68 | 69 | test("check isFloat", () => { 70 | expect(isFloat(3.14)).toBe(true) 71 | expect(isFloat(-2.5)).toBe(true) 72 | expect(isFloat(Infinity)).toBe(true) 73 | expect(isFloat(10)).toBe(false) 74 | expect(isFloat(0)).toBe(false) 75 | expect(isFloat("3.14")).toBe(false) 76 | expect(isFloat(null)).toBe(false) 77 | expect(isFloat(undefined)).toBe(false) 78 | expect(isFloat(NaN)).toBe(false) 79 | }) 80 | 81 | test("check isFinite", () => { 82 | expect(isFinite(3.14)).toBe(true) 83 | expect(isFinite(-2.5)).toBe(true) 84 | expect(isFinite(10)).toBe(true) 85 | expect(isFinite(0)).toBe(true) 86 | expect(isFinite("3.14")).toBe(false) 87 | expect(isFinite(null)).toBe(false) 88 | expect(isFinite(undefined)).toBe(false) 89 | expect(isFinite(NaN)).toBe(false) 90 | expect(isFinite(Infinity)).toBe(false) 91 | }) 92 | 93 | if (process.env.NODE_ENV !== "production") { 94 | test("Passing non integer to types.integer", () => { 95 | const Size = types.model({ 96 | width: types.integer, 97 | height: 20 98 | }) 99 | 100 | expect(() => { 101 | const size = Size.create({ width: 10 }) 102 | }).not.toThrow() 103 | 104 | expect(() => { 105 | const size = Size.create({ width: 10.5 }) 106 | }).toThrow() 107 | }) 108 | } 109 | -------------------------------------------------------------------------------- /docs/concepts/actions.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: actions 3 | title: Actions 4 | --- 5 | 6 |
7 | 8 |
9 | egghead.io lesson 2: Attach Behavior to mobx-state-tree Models Using Actions 10 |
11 |
12 | 13 |
14 | Hosted on egghead.io 15 |
16 | 17 | By default, nodes can only be modified by one of their actions, or by actions higher up in the tree. 18 | Actions can be defined by returning an object from the action initializer function that was passed to `actions`. 19 | The initializer function is executed for each instance, so that `self` is always bound to the current instance. 20 | Also, the closure of that function can be used to store so called _volatile_ state for the instance or to create private functions that can only 21 | be invoked from the actions, but not from the outside. 22 | 23 | ```javascript 24 | const Todo = types 25 | .model({ 26 | title: types.string 27 | }) 28 | .actions(self => { 29 | function setTitle(newTitle) { 30 | self.title = newTitle 31 | } 32 | 33 | return { 34 | setTitle 35 | } 36 | }) 37 | ``` 38 | 39 | Shorter form if no local state or private functions are involved: 40 | 41 | ```javascript 42 | const Todo = types 43 | .model({ 44 | title: types.string 45 | }) 46 | .actions(self => ({ 47 | // note the `({`, we are returning an object literal 48 | setTitle(newTitle) { 49 | self.title = newTitle 50 | } 51 | })) 52 | ``` 53 | 54 | Actions are replayable and are therefore constrained in several ways: 55 | 56 | - Trying to modify a node without using an action will throw an exception. 57 | - It's recommended to make sure action arguments are serializable. Some arguments can be serialized automatically such as relative paths to other nodes 58 | - Actions can only modify models that belong to the (sub)tree on which they are invoked 59 | - You cannot use `this` inside actions. Instead, use `self`. This makes it safe to pass actions around without binding them or wrapping them in arrow functions. 60 | 61 | Useful methods: 62 | 63 | - [`onAction`](/API/#onaction) listens to any action that is invoked on the model or any of its descendants. 64 | - [`addMiddleware`](/API/#addmiddleware) adds an interceptor function to any action invoked on the subtree. 65 | - [`applyAction`](/API/#applyaction) invokes an action on the model according to the given action description 66 | 67 | 68 | #### Action listeners versus middleware 69 | 70 | The difference between action listeners and middleware is: middleware can intercept the action that is about to be invoked, modify arguments, return types, etc. Action listeners cannot intercept and are only notified. Action listeners receive the action arguments in a serializable format, while middleware receives the raw arguments. (`onAction` is actually just a built-in middleware). 71 | 72 | For more details on creating middleware, see the [docs](/concepts/middleware). 73 | 74 | #### Disabling protected mode 75 | 76 | This may be desired if the default protection of `mobx-state-tree` doesn't fit your use case. For example, if you are not interested in replayable actions or hate the effort of writing actions to modify any field, `unprotect(tree)` will disable the protected mode of a tree allowing anyone to directly modify the tree. 77 | -------------------------------------------------------------------------------- /docs/API/interfaces/itype.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: "itype" 3 | title: "IType" 4 | sidebar_label: "IType" 5 | --- 6 | 7 | [mobx-state-tree - v7.0.2](../index.md) › [IType](itype.md) 8 | 9 | A type, either complex or simple. 10 | 11 | ## Type parameters 12 | 13 | ▪ **C** 14 | 15 | ▪ **S** 16 | 17 | ▪ **T** 18 | 19 | ## Hierarchy 20 | 21 | * **IType** 22 | 23 | ↳ [IAnyType](ianytype.md) 24 | 25 | ↳ [ISimpleType](isimpletype.md) 26 | 27 | ↳ [IAnyComplexType](ianycomplextype.md) 28 | 29 | ↳ [ISnapshotProcessor](isnapshotprocessor.md) 30 | 31 | ↳ [IModelType](imodeltype.md) 32 | 33 | ## Index 34 | 35 | ### Properties 36 | 37 | * [identifierAttribute](itype.md#optional-identifierattribute) 38 | * [name](itype.md#name) 39 | 40 | ### Methods 41 | 42 | * [create](itype.md#create) 43 | * [describe](itype.md#describe) 44 | * [is](itype.md#is) 45 | * [validate](itype.md#validate) 46 | 47 | ## Properties 48 | 49 | ### `Optional` identifierAttribute 50 | 51 | • **identifierAttribute**? : *undefined | string* 52 | 53 | *Defined in [src/core/type/type.ts:92](https://github.com/mobxjs/mobx-state-tree/blob/1be40a3e/src/core/type/type.ts#L92)* 54 | 55 | Name of the identifier attribute or null if none. 56 | 57 | ___ 58 | 59 | ### name 60 | 61 | • **name**: *string* 62 | 63 | *Defined in [src/core/type/type.ts:87](https://github.com/mobxjs/mobx-state-tree/blob/1be40a3e/src/core/type/type.ts#L87)* 64 | 65 | Friendly type name. 66 | 67 | ## Methods 68 | 69 | ### create 70 | 71 | ▸ **create**(`snapshot?`: C | ExcludeReadonly‹T›, `env?`: any): *this["Type"]* 72 | 73 | *Defined in [src/core/type/type.ts:99](https://github.com/mobxjs/mobx-state-tree/blob/1be40a3e/src/core/type/type.ts#L99)* 74 | 75 | Creates an instance for the type given an snapshot input. 76 | 77 | **Parameters:** 78 | 79 | Name | Type | 80 | ------ | ------ | 81 | `snapshot?` | C | ExcludeReadonly‹T› | 82 | `env?` | any | 83 | 84 | **Returns:** *this["Type"]* 85 | 86 | An instance of that type. 87 | 88 | ___ 89 | 90 | ### describe 91 | 92 | ▸ **describe**(): *string* 93 | 94 | *Defined in [src/core/type/type.ts:121](https://github.com/mobxjs/mobx-state-tree/blob/1be40a3e/src/core/type/type.ts#L121)* 95 | 96 | Gets the textual representation of the type as a string. 97 | 98 | **Returns:** *string* 99 | 100 | ___ 101 | 102 | ### is 103 | 104 | ▸ **is**(`thing`: any): *thing is C | this["Type"]* 105 | 106 | *Defined in [src/core/type/type.ts:107](https://github.com/mobxjs/mobx-state-tree/blob/1be40a3e/src/core/type/type.ts#L107)* 107 | 108 | Checks if a given snapshot / instance is of the given type. 109 | 110 | **Parameters:** 111 | 112 | Name | Type | Description | 113 | ------ | ------ | ------ | 114 | `thing` | any | Snapshot or instance to be checked. | 115 | 116 | **Returns:** *thing is C | this["Type"]* 117 | 118 | true if the value is of the current type, false otherwise. 119 | 120 | ___ 121 | 122 | ### validate 123 | 124 | ▸ **validate**(`thing`: C | T, `context`: [IValidationContext](../index.md#ivalidationcontext)): *[IValidationResult](../index.md#ivalidationresult)* 125 | 126 | *Defined in [src/core/type/type.ts:116](https://github.com/mobxjs/mobx-state-tree/blob/1be40a3e/src/core/type/type.ts#L116)* 127 | 128 | Run's the type's typechecker on the given value with the given validation context. 129 | 130 | **Parameters:** 131 | 132 | Name | Type | Description | 133 | ------ | ------ | ------ | 134 | `thing` | C | T | Value to be checked, either a snapshot or an instance. | 135 | `context` | [IValidationContext](../index.md#ivalidationcontext) | Validation context, an array of { subpaths, subtypes } that should be validated | 136 | 137 | **Returns:** *[IValidationResult](../index.md#ivalidationresult)* 138 | 139 | The validation result, an array with the list of validation errors. 140 | -------------------------------------------------------------------------------- /docs/tips/faq.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: faq 3 | title: Frequently Asked Questions 4 | --- 5 | 6 |
7 | 8 | ### Should all state of my app be stored in `mobx-state-tree`? 9 | 10 | No, or not necessarily. An application can use both state trees and vanilla MobX observables at the same time. 11 | State trees are primarily designed to store your domain data as this kind of state is often distributed and not very local. 12 | For local component state, for example, vanilla MobX observables might often be simpler to use. 13 | 14 | ### When not to use MST? 15 | 16 | MST provides access to snapshots, patches and interceptable actions. Also, it fixes the `this` problem. 17 | All these features have a downside as they incur a little runtime overhead. 18 | Although in many places the MST core can still be optimized significantly, there will always be a constant overhead. 19 | If you have a performance critical application that handles a huge amount of mutable data, you will probably be better 20 | off by using 'raw' MobX, which has a predictable and well-known performance and much less overhead. 21 | 22 | Likewise, if your application mainly processes stateless information (such as a logging system), MST won't add much value. 23 | 24 | ### Can I use Hot Module Reloading? 25 | 26 |
27 | egghead.io lesson 10: Restore the Model Tree State using Hot Module Reloading when Model Definitions Change 28 |
29 |
30 | 31 |
32 | Hosted on egghead.io 33 |
34 | 35 | Yes, with MST it is pretty straight forward to setup hot reloading for your store definitions while preserving state. See the [todomvc example](https://github.com/coolsoftwaretyler/mst-example-todomvc/blob/main/src/index.js#L59C1-L64C1). 36 | 37 | ### How does MST compare to Redux 38 | 39 | So far this might look a lot like an immutable state tree as found for example in Redux apps, but there're are only so many reasons to use Redux as per [article linked at the very top of Redux guide](https://medium.com/@dan_abramov/you-might-not-need-redux-be46360cf367) that MST covers too, meanwhile: 40 | 41 | - Like Redux, and unlike MobX, MST prescribes a very specific state architecture. 42 | - mobx-state-tree allows direct modification of any value in the tree. It is not necessary to construct a new tree in your actions. 43 | - mobx-state-tree allows for fine-grained and efficient observation of any point in the state tree. 44 | - mobx-state-tree generates JSON patches for any modification that is made. 45 | - mobx-state-tree provides utilities to turn any MST tree into a valid Redux store. 46 | - Having multiple MSTs in a single application is perfectly fine. 47 | 48 | ### Where is the `any` type? 49 | 50 | MST doesn't offer an any type because it can't reason about it. For example, given a snapshot and a field with `any`, how should MST know how to deserialize it or apply patches to it, etc.? If you need `any`, there are following options: 51 | 52 | 1. Use `types.frozen()`. Frozen values need to be immutable and serializable (so MST can treat them verbatim) 53 | 2. Use volatile state. Volatile state can store anything, but won't appear in snapshots, patches etc. 54 | 3. If your type is regular, and you just are too lazy to type the model, you could also consider generating the type at runtime once (after all, MST types are just JS...). However, you will lose static typing, and any confusion it causes is up to you to handle :-). 55 | -------------------------------------------------------------------------------- /docs/API/interfaces/iactiontrackingmiddlewarehooks.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: "iactiontrackingmiddlewarehooks" 3 | title: "IActionTrackingMiddlewareHooks" 4 | sidebar_label: "IActionTrackingMiddlewareHooks" 5 | --- 6 | 7 | [mobx-state-tree - v7.0.2](../index.md) › [IActionTrackingMiddlewareHooks](iactiontrackingmiddlewarehooks.md) 8 | 9 | ## Type parameters 10 | 11 | ▪ **T** 12 | 13 | ## Hierarchy 14 | 15 | * **IActionTrackingMiddlewareHooks** 16 | 17 | ## Index 18 | 19 | ### Properties 20 | 21 | * [filter](iactiontrackingmiddlewarehooks.md#optional-filter) 22 | * [onFail](iactiontrackingmiddlewarehooks.md#onfail) 23 | * [onResume](iactiontrackingmiddlewarehooks.md#onresume) 24 | * [onStart](iactiontrackingmiddlewarehooks.md#onstart) 25 | * [onSuccess](iactiontrackingmiddlewarehooks.md#onsuccess) 26 | * [onSuspend](iactiontrackingmiddlewarehooks.md#onsuspend) 27 | 28 | ## Properties 29 | 30 | ### `Optional` filter 31 | 32 | • **filter**? : *undefined | function* 33 | 34 | *Defined in [src/middlewares/create-action-tracking-middleware.ts:6](https://github.com/mobxjs/mobx-state-tree/blob/1be40a3e/src/middlewares/create-action-tracking-middleware.ts#L6)* 35 | 36 | ___ 37 | 38 | ### onFail 39 | 40 | • **onFail**: *function* 41 | 42 | *Defined in [src/middlewares/create-action-tracking-middleware.ts:11](https://github.com/mobxjs/mobx-state-tree/blob/1be40a3e/src/middlewares/create-action-tracking-middleware.ts#L11)* 43 | 44 | #### Type declaration: 45 | 46 | ▸ (`call`: [IMiddlewareEvent](imiddlewareevent.md), `context`: T, `error`: any): *void* 47 | 48 | **Parameters:** 49 | 50 | Name | Type | 51 | ------ | ------ | 52 | `call` | [IMiddlewareEvent](imiddlewareevent.md) | 53 | `context` | T | 54 | `error` | any | 55 | 56 | ___ 57 | 58 | ### onResume 59 | 60 | • **onResume**: *function* 61 | 62 | *Defined in [src/middlewares/create-action-tracking-middleware.ts:8](https://github.com/mobxjs/mobx-state-tree/blob/1be40a3e/src/middlewares/create-action-tracking-middleware.ts#L8)* 63 | 64 | #### Type declaration: 65 | 66 | ▸ (`call`: [IMiddlewareEvent](imiddlewareevent.md), `context`: T): *void* 67 | 68 | **Parameters:** 69 | 70 | Name | Type | 71 | ------ | ------ | 72 | `call` | [IMiddlewareEvent](imiddlewareevent.md) | 73 | `context` | T | 74 | 75 | ___ 76 | 77 | ### onStart 78 | 79 | • **onStart**: *function* 80 | 81 | *Defined in [src/middlewares/create-action-tracking-middleware.ts:7](https://github.com/mobxjs/mobx-state-tree/blob/1be40a3e/src/middlewares/create-action-tracking-middleware.ts#L7)* 82 | 83 | #### Type declaration: 84 | 85 | ▸ (`call`: [IMiddlewareEvent](imiddlewareevent.md)): *T* 86 | 87 | **Parameters:** 88 | 89 | Name | Type | 90 | ------ | ------ | 91 | `call` | [IMiddlewareEvent](imiddlewareevent.md) | 92 | 93 | ___ 94 | 95 | ### onSuccess 96 | 97 | • **onSuccess**: *function* 98 | 99 | *Defined in [src/middlewares/create-action-tracking-middleware.ts:10](https://github.com/mobxjs/mobx-state-tree/blob/1be40a3e/src/middlewares/create-action-tracking-middleware.ts#L10)* 100 | 101 | #### Type declaration: 102 | 103 | ▸ (`call`: [IMiddlewareEvent](imiddlewareevent.md), `context`: T, `result`: any): *void* 104 | 105 | **Parameters:** 106 | 107 | Name | Type | 108 | ------ | ------ | 109 | `call` | [IMiddlewareEvent](imiddlewareevent.md) | 110 | `context` | T | 111 | `result` | any | 112 | 113 | ___ 114 | 115 | ### onSuspend 116 | 117 | • **onSuspend**: *function* 118 | 119 | *Defined in [src/middlewares/create-action-tracking-middleware.ts:9](https://github.com/mobxjs/mobx-state-tree/blob/1be40a3e/src/middlewares/create-action-tracking-middleware.ts#L9)* 120 | 121 | #### Type declaration: 122 | 123 | ▸ (`call`: [IMiddlewareEvent](imiddlewareevent.md), `context`: T): *void* 124 | 125 | **Parameters:** 126 | 127 | Name | Type | 128 | ------ | ------ | 129 | `call` | [IMiddlewareEvent](imiddlewareevent.md) | 130 | `context` | T | 131 | -------------------------------------------------------------------------------- /src/core/process.ts: -------------------------------------------------------------------------------- 1 | import { deprecated, flow, createFlowSpawner } from "../internal" 2 | 3 | // based on: https://github.com/mobxjs/mobx-utils/blob/master/src/async-action.ts 4 | /* 5 | All contents of this file are deprecated. 6 | 7 | The term `process` has been replaced with `flow` to avoid conflicts with the 8 | global `process` object. 9 | 10 | Refer to `flow.ts` for any further changes to this implementation. 11 | */ 12 | 13 | const DEPRECATION_MESSAGE = 14 | "See https://github.com/mobxjs/mobx-state-tree/issues/399 for more information. " + 15 | "Note that the middleware event types starting with `process` now start with `flow`." 16 | 17 | /** 18 | * @deprecated has been renamed to `flow()`. 19 | * @hidden 20 | */ 21 | export function process(generator: () => IterableIterator): () => Promise 22 | /** 23 | * @deprecated has been renamed to `flow()`. 24 | * @hidden 25 | */ 26 | export function process(generator: (a1: A1) => IterableIterator): (a1: A1) => Promise 27 | /** 28 | * @deprecated has been renamed to `flow()`. 29 | * @hidden 30 | */ 31 | export function process( 32 | generator: (a1: A1, a2: A2) => IterableIterator 33 | ): (a1: A1, a2: A2) => Promise 34 | /** 35 | * @deprecated has been renamed to `flow()`. 36 | * @hidden 37 | */ 38 | export function process( 39 | generator: (a1: A1, a2: A2, a3: A3) => IterableIterator 40 | ): (a1: A1, a2: A2, a3: A3) => Promise 41 | /** 42 | * @deprecated has been renamed to `flow()`. 43 | * @hidden 44 | */ 45 | export function process( 46 | generator: (a1: A1, a2: A2, a3: A3, a4: A4) => IterableIterator 47 | ): (a1: A1, a2: A2, a3: A3, a4: A4) => Promise 48 | /** 49 | * @deprecated has been renamed to `flow()`. 50 | * @hidden 51 | */ 52 | export function process( 53 | generator: (a1: A1, a2: A2, a3: A3, a4: A4, a5: A5) => IterableIterator 54 | ): (a1: A1, a2: A2, a3: A3, a4: A4, a5: A5) => Promise 55 | /** 56 | * @deprecated has been renamed to `flow()`. 57 | * @hidden 58 | */ 59 | export function process( 60 | generator: (a1: A1, a2: A2, a3: A3, a4: A4, a5: A5, a6: A6) => IterableIterator 61 | ): (a1: A1, a2: A2, a3: A3, a4: A4, a5: A5, a6: A6) => Promise 62 | /** 63 | * @deprecated has been renamed to `flow()`. 64 | * @hidden 65 | */ 66 | export function process( 67 | generator: (a1: A1, a2: A2, a3: A3, a4: A4, a5: A5, a6: A6, a7: A7) => IterableIterator 68 | ): (a1: A1, a2: A2, a3: A3, a4: A4, a5: A5, a6: A6, a7: A7) => Promise 69 | /** 70 | * @deprecated has been renamed to `flow()`. 71 | * @hidden 72 | */ 73 | export function process( 74 | generator: ( 75 | a1: A1, 76 | a2: A2, 77 | a3: A3, 78 | a4: A4, 79 | a5: A5, 80 | a6: A6, 81 | a7: A7, 82 | a8: A8 83 | ) => IterableIterator 84 | ): (a1: A1, a2: A2, a3: A3, a4: A4, a5: A5, a6: A6, a7: A7, a8: A8) => Promise 85 | /** 86 | * @hidden 87 | * 88 | * @deprecated has been renamed to `flow()`. 89 | * See https://github.com/mobxjs/mobx-state-tree/issues/399 for more information. 90 | * Note that the middleware event types starting with `process` now start with `flow`. 91 | * 92 | * @returns {Promise} 93 | */ 94 | export function process(asyncAction: any): any { 95 | deprecated("process", "`process()` has been renamed to `flow()`. " + DEPRECATION_MESSAGE) 96 | return flow(asyncAction) 97 | } 98 | 99 | /** 100 | * @internal 101 | * @hidden 102 | */ 103 | export function createProcessSpawner(name: string, generator: Function) { 104 | deprecated( 105 | "process", 106 | "`createProcessSpawner()` has been renamed to `createFlowSpawner()`. " + DEPRECATION_MESSAGE 107 | ) 108 | return createFlowSpawner(name, generator) 109 | } 110 | -------------------------------------------------------------------------------- /src/middlewares/create-action-tracking-middleware.ts: -------------------------------------------------------------------------------- 1 | import { IMiddlewareEvent, IMiddlewareHandler } from "../internal" 2 | 3 | const runningActions = new Map() 4 | 5 | export interface IActionTrackingMiddlewareHooks { 6 | filter?: (call: IMiddlewareEvent) => boolean 7 | onStart: (call: IMiddlewareEvent) => T 8 | onResume: (call: IMiddlewareEvent, context: T) => void 9 | onSuspend: (call: IMiddlewareEvent, context: T) => void 10 | onSuccess: (call: IMiddlewareEvent, context: T, result: any) => void 11 | onFail: (call: IMiddlewareEvent, context: T, error: any) => void 12 | } 13 | 14 | /** 15 | * Note: Consider migrating to `createActionTrackingMiddleware2`, it is easier to use. 16 | * 17 | * Convenience utility to create action based middleware that supports async processes more easily. 18 | * All hooks are called for both synchronous and asynchronous actions. Except that either `onSuccess` or `onFail` is called 19 | * 20 | * The create middleware tracks the process of an action (assuming it passes the `filter`). 21 | * `onResume` can return any value, which will be passed as second argument to any other hook. This makes it possible to keep state during a process. 22 | * 23 | * See the `atomic` middleware for an example 24 | * 25 | * @param hooks 26 | * @returns 27 | */ 28 | export function createActionTrackingMiddleware( 29 | hooks: IActionTrackingMiddlewareHooks 30 | ): IMiddlewareHandler { 31 | return function actionTrackingMiddleware( 32 | call: IMiddlewareEvent, 33 | next: (actionCall: IMiddlewareEvent) => any, 34 | abort: (value: any) => any 35 | ) { 36 | switch (call.type) { 37 | case "action": { 38 | if (!hooks.filter || hooks.filter(call) === true) { 39 | const context = hooks.onStart(call) 40 | hooks.onResume(call, context) 41 | runningActions.set(call.id, { 42 | call, 43 | context, 44 | async: false 45 | }) 46 | try { 47 | const res = next(call) 48 | hooks.onSuspend(call, context) 49 | if (runningActions.get(call.id)!.async === false) { 50 | runningActions.delete(call.id) 51 | hooks.onSuccess(call, context, res) 52 | } 53 | return res 54 | } catch (e) { 55 | runningActions.delete(call.id) 56 | hooks.onFail(call, context, e) 57 | throw e 58 | } 59 | } else { 60 | return next(call) 61 | } 62 | } 63 | case "flow_spawn": { 64 | const root = runningActions.get(call.rootId)! 65 | root.async = true 66 | return next(call) 67 | } 68 | case "flow_resume": 69 | case "flow_resume_error": { 70 | const root = runningActions.get(call.rootId)! 71 | hooks.onResume(call, root.context) 72 | try { 73 | return next(call) 74 | } finally { 75 | hooks.onSuspend(call, root.context) 76 | } 77 | } 78 | case "flow_throw": { 79 | const root = runningActions.get(call.rootId)! 80 | runningActions.delete(call.rootId) 81 | hooks.onFail(call, root.context, call.args[0]) 82 | return next(call) 83 | } 84 | case "flow_return": { 85 | const root = runningActions.get(call.rootId)! 86 | runningActions.delete(call.rootId) 87 | hooks.onSuccess(call, root.context, call.args[0]) 88 | return next(call) 89 | } 90 | } 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /src/core/node/scalar-node.ts: -------------------------------------------------------------------------------- 1 | import { 2 | MstError, 3 | freeze, 4 | NodeLifeCycle, 5 | Hook, 6 | BaseNode, 7 | AnyObjectNode, 8 | SimpleType, 9 | devMode 10 | } from "../../internal" 11 | import { action } from "mobx" 12 | 13 | /** 14 | * @internal 15 | * @hidden 16 | */ 17 | export class ScalarNode extends BaseNode { 18 | // note about hooks: 19 | // - afterCreate is not emmited in scalar nodes, since it would be emitted in the 20 | // constructor, before it can be subscribed by anybody 21 | // - afterCreationFinalization could be emitted, but there's no need for it right now 22 | // - beforeDetach is never emitted for scalar nodes, since they cannot be detached 23 | 24 | declare readonly type: SimpleType 25 | 26 | constructor( 27 | simpleType: SimpleType, 28 | parent: AnyObjectNode | null, 29 | subpath: string, 30 | environment: any, 31 | initialSnapshot: C 32 | ) { 33 | super(simpleType, parent, subpath, environment) 34 | try { 35 | this.storedValue = simpleType.createNewInstance(initialSnapshot) 36 | } catch (e) { 37 | // short-cut to die the instance, to avoid the snapshot computed starting to throw... 38 | this.state = NodeLifeCycle.DEAD 39 | throw e 40 | } 41 | 42 | this.state = NodeLifeCycle.CREATED 43 | // for scalar nodes there's no point in firing this event since it would fire on the constructor, before 44 | // anybody can actually register for/listen to it 45 | // this.fireHook(Hook.AfterCreate) 46 | 47 | this.finalizeCreation() 48 | } 49 | 50 | get root(): AnyObjectNode { 51 | // future optimization: store root ref in the node and maintain it 52 | if (!this.parent) throw new MstError(`This scalar node is not part of a tree`) 53 | return this.parent.root 54 | } 55 | 56 | setParent(newParent: AnyObjectNode, subpath: string): void { 57 | const parentChanged = this.parent !== newParent 58 | const subpathChanged = this.subpath !== subpath 59 | 60 | if (!parentChanged && !subpathChanged) { 61 | return 62 | } 63 | 64 | if (devMode()) { 65 | if (!subpath) { 66 | // istanbul ignore next 67 | throw new MstError("assertion failed: subpath expected") 68 | } 69 | if (!newParent) { 70 | // istanbul ignore next 71 | throw new MstError("assertion failed: parent expected") 72 | } 73 | if (parentChanged) { 74 | // istanbul ignore next 75 | throw new MstError("assertion failed: scalar nodes cannot change their parent") 76 | } 77 | } 78 | 79 | this.environment = undefined // use parent's 80 | this.baseSetParent(this.parent, subpath) 81 | } 82 | 83 | get snapshot(): S { 84 | return freeze(this.getSnapshot()) 85 | } 86 | 87 | getSnapshot(): S { 88 | return this.type.getSnapshot(this) 89 | } 90 | 91 | toString(): string { 92 | const path = (this.isAlive ? this.path : this.pathUponDeath) || "" 93 | return `${this.type.name}@${path}${this.isAlive ? "" : " [dead]"}` 94 | } 95 | 96 | die(): void { 97 | if (!this.isAlive || this.state === NodeLifeCycle.DETACHING) return 98 | this.aboutToDie() 99 | this.finalizeDeath() 100 | } 101 | 102 | finalizeCreation(): void { 103 | this.baseFinalizeCreation() 104 | } 105 | 106 | aboutToDie(): void { 107 | this.baseAboutToDie() 108 | } 109 | 110 | finalizeDeath(): void { 111 | this.baseFinalizeDeath() 112 | } 113 | 114 | protected fireHook(name: Hook): void { 115 | this.fireInternalHook(name) 116 | } 117 | } 118 | ScalarNode.prototype.die = action(ScalarNode.prototype.die) 119 | -------------------------------------------------------------------------------- /docs/API/interfaces/ianytype.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: "ianytype" 3 | title: "IAnyType" 4 | sidebar_label: "IAnyType" 5 | --- 6 | 7 | [mobx-state-tree - v7.0.2](../index.md) › [IAnyType](ianytype.md) 8 | 9 | Any kind of type. 10 | 11 | ## Hierarchy 12 | 13 | * [IType](itype.md)‹any, any, any› 14 | 15 | ↳ **IAnyType** 16 | 17 | ## Index 18 | 19 | ### Properties 20 | 21 | * [identifierAttribute](ianytype.md#optional-identifierattribute) 22 | * [name](ianytype.md#name) 23 | 24 | ### Methods 25 | 26 | * [create](ianytype.md#create) 27 | * [describe](ianytype.md#describe) 28 | * [is](ianytype.md#is) 29 | * [validate](ianytype.md#validate) 30 | 31 | ## Properties 32 | 33 | ### `Optional` identifierAttribute 34 | 35 | • **identifierAttribute**? : *undefined | string* 36 | 37 | *Inherited from [IType](itype.md).[identifierAttribute](itype.md#optional-identifierattribute)* 38 | 39 | *Defined in [src/core/type/type.ts:92](https://github.com/mobxjs/mobx-state-tree/blob/1be40a3e/src/core/type/type.ts#L92)* 40 | 41 | Name of the identifier attribute or null if none. 42 | 43 | ___ 44 | 45 | ### name 46 | 47 | • **name**: *string* 48 | 49 | *Inherited from [IType](itype.md).[name](itype.md#name)* 50 | 51 | *Defined in [src/core/type/type.ts:87](https://github.com/mobxjs/mobx-state-tree/blob/1be40a3e/src/core/type/type.ts#L87)* 52 | 53 | Friendly type name. 54 | 55 | ## Methods 56 | 57 | ### create 58 | 59 | ▸ **create**(`snapshot?`: any | ExcludeReadonly‹any›, `env?`: any): *this["Type"]* 60 | 61 | *Inherited from [IType](itype.md).[create](itype.md#create)* 62 | 63 | *Defined in [src/core/type/type.ts:99](https://github.com/mobxjs/mobx-state-tree/blob/1be40a3e/src/core/type/type.ts#L99)* 64 | 65 | Creates an instance for the type given an snapshot input. 66 | 67 | **Parameters:** 68 | 69 | Name | Type | 70 | ------ | ------ | 71 | `snapshot?` | any | ExcludeReadonly‹any› | 72 | `env?` | any | 73 | 74 | **Returns:** *this["Type"]* 75 | 76 | An instance of that type. 77 | 78 | ___ 79 | 80 | ### describe 81 | 82 | ▸ **describe**(): *string* 83 | 84 | *Inherited from [IType](itype.md).[describe](itype.md#describe)* 85 | 86 | *Defined in [src/core/type/type.ts:121](https://github.com/mobxjs/mobx-state-tree/blob/1be40a3e/src/core/type/type.ts#L121)* 87 | 88 | Gets the textual representation of the type as a string. 89 | 90 | **Returns:** *string* 91 | 92 | ___ 93 | 94 | ### is 95 | 96 | ▸ **is**(`thing`: any): *thing is any | this["Type"]* 97 | 98 | *Inherited from [IType](itype.md).[is](itype.md#is)* 99 | 100 | *Defined in [src/core/type/type.ts:107](https://github.com/mobxjs/mobx-state-tree/blob/1be40a3e/src/core/type/type.ts#L107)* 101 | 102 | Checks if a given snapshot / instance is of the given type. 103 | 104 | **Parameters:** 105 | 106 | Name | Type | Description | 107 | ------ | ------ | ------ | 108 | `thing` | any | Snapshot or instance to be checked. | 109 | 110 | **Returns:** *thing is any | this["Type"]* 111 | 112 | true if the value is of the current type, false otherwise. 113 | 114 | ___ 115 | 116 | ### validate 117 | 118 | ▸ **validate**(`thing`: any | any, `context`: [IValidationContext](../index.md#ivalidationcontext)): *[IValidationResult](../index.md#ivalidationresult)* 119 | 120 | *Inherited from [IType](itype.md).[validate](itype.md#validate)* 121 | 122 | *Defined in [src/core/type/type.ts:116](https://github.com/mobxjs/mobx-state-tree/blob/1be40a3e/src/core/type/type.ts#L116)* 123 | 124 | Run's the type's typechecker on the given value with the given validation context. 125 | 126 | **Parameters:** 127 | 128 | Name | Type | Description | 129 | ------ | ------ | ------ | 130 | `thing` | any | any | Value to be checked, either a snapshot or an instance. | 131 | `context` | [IValidationContext](../index.md#ivalidationcontext) | Validation context, an array of { subpaths, subtypes } that should be validated | 132 | 133 | **Returns:** *[IValidationResult](../index.md#ivalidationresult)* 134 | 135 | The validation result, an array with the list of validation errors. 136 | -------------------------------------------------------------------------------- /docs/API/interfaces/ianycomplextype.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: "ianycomplextype" 3 | title: "IAnyComplexType" 4 | sidebar_label: "IAnyComplexType" 5 | --- 6 | 7 | [mobx-state-tree - v7.0.2](../index.md) › [IAnyComplexType](ianycomplextype.md) 8 | 9 | Any kind of complex type. 10 | 11 | ## Hierarchy 12 | 13 | * [IType](itype.md)‹any, any, object› 14 | 15 | ↳ **IAnyComplexType** 16 | 17 | ## Index 18 | 19 | ### Properties 20 | 21 | * [identifierAttribute](ianycomplextype.md#optional-identifierattribute) 22 | * [name](ianycomplextype.md#name) 23 | 24 | ### Methods 25 | 26 | * [create](ianycomplextype.md#create) 27 | * [describe](ianycomplextype.md#describe) 28 | * [is](ianycomplextype.md#is) 29 | * [validate](ianycomplextype.md#validate) 30 | 31 | ## Properties 32 | 33 | ### `Optional` identifierAttribute 34 | 35 | • **identifierAttribute**? : *undefined | string* 36 | 37 | *Inherited from [IType](itype.md).[identifierAttribute](itype.md#optional-identifierattribute)* 38 | 39 | *Defined in [src/core/type/type.ts:92](https://github.com/mobxjs/mobx-state-tree/blob/1be40a3e/src/core/type/type.ts#L92)* 40 | 41 | Name of the identifier attribute or null if none. 42 | 43 | ___ 44 | 45 | ### name 46 | 47 | • **name**: *string* 48 | 49 | *Inherited from [IType](itype.md).[name](itype.md#name)* 50 | 51 | *Defined in [src/core/type/type.ts:87](https://github.com/mobxjs/mobx-state-tree/blob/1be40a3e/src/core/type/type.ts#L87)* 52 | 53 | Friendly type name. 54 | 55 | ## Methods 56 | 57 | ### create 58 | 59 | ▸ **create**(`snapshot?`: any | ExcludeReadonly‹object›, `env?`: any): *this["Type"]* 60 | 61 | *Inherited from [IType](itype.md).[create](itype.md#create)* 62 | 63 | *Defined in [src/core/type/type.ts:99](https://github.com/mobxjs/mobx-state-tree/blob/1be40a3e/src/core/type/type.ts#L99)* 64 | 65 | Creates an instance for the type given an snapshot input. 66 | 67 | **Parameters:** 68 | 69 | Name | Type | 70 | ------ | ------ | 71 | `snapshot?` | any | ExcludeReadonly‹object› | 72 | `env?` | any | 73 | 74 | **Returns:** *this["Type"]* 75 | 76 | An instance of that type. 77 | 78 | ___ 79 | 80 | ### describe 81 | 82 | ▸ **describe**(): *string* 83 | 84 | *Inherited from [IType](itype.md).[describe](itype.md#describe)* 85 | 86 | *Defined in [src/core/type/type.ts:121](https://github.com/mobxjs/mobx-state-tree/blob/1be40a3e/src/core/type/type.ts#L121)* 87 | 88 | Gets the textual representation of the type as a string. 89 | 90 | **Returns:** *string* 91 | 92 | ___ 93 | 94 | ### is 95 | 96 | ▸ **is**(`thing`: any): *thing is any | this["Type"]* 97 | 98 | *Inherited from [IType](itype.md).[is](itype.md#is)* 99 | 100 | *Defined in [src/core/type/type.ts:107](https://github.com/mobxjs/mobx-state-tree/blob/1be40a3e/src/core/type/type.ts#L107)* 101 | 102 | Checks if a given snapshot / instance is of the given type. 103 | 104 | **Parameters:** 105 | 106 | Name | Type | Description | 107 | ------ | ------ | ------ | 108 | `thing` | any | Snapshot or instance to be checked. | 109 | 110 | **Returns:** *thing is any | this["Type"]* 111 | 112 | true if the value is of the current type, false otherwise. 113 | 114 | ___ 115 | 116 | ### validate 117 | 118 | ▸ **validate**(`thing`: any | object, `context`: [IValidationContext](../index.md#ivalidationcontext)): *[IValidationResult](../index.md#ivalidationresult)* 119 | 120 | *Inherited from [IType](itype.md).[validate](itype.md#validate)* 121 | 122 | *Defined in [src/core/type/type.ts:116](https://github.com/mobxjs/mobx-state-tree/blob/1be40a3e/src/core/type/type.ts#L116)* 123 | 124 | Run's the type's typechecker on the given value with the given validation context. 125 | 126 | **Parameters:** 127 | 128 | Name | Type | Description | 129 | ------ | ------ | ------ | 130 | `thing` | any | object | Value to be checked, either a snapshot or an instance. | 131 | `context` | [IValidationContext](../index.md#ivalidationcontext) | Validation context, an array of { subpaths, subtypes } that should be validated | 132 | 133 | **Returns:** *[IValidationResult](../index.md#ivalidationresult)* 134 | 135 | The validation result, an array with the list of validation errors. 136 | -------------------------------------------------------------------------------- /src/types/utility-types/refinement.ts: -------------------------------------------------------------------------------- 1 | import { 2 | isStateTreeNode, 3 | getStateTreeNode, 4 | IValidationContext, 5 | IValidationResult, 6 | typeCheckSuccess, 7 | typeCheckFailure, 8 | isType, 9 | TypeFlags, 10 | IAnyType, 11 | AnyObjectNode, 12 | BaseType, 13 | ExtractNodeType, 14 | assertIsType, 15 | devMode 16 | } from "../../internal" 17 | import { assertIsString, assertIsFunction } from "../../utils" 18 | 19 | class Refinement extends BaseType< 20 | IT["CreationType"], 21 | IT["SnapshotType"], 22 | IT["TypeWithoutSTN"], 23 | ExtractNodeType 24 | > { 25 | get flags() { 26 | return this._subtype.flags | TypeFlags.Refinement 27 | } 28 | 29 | constructor( 30 | name: string, 31 | private readonly _subtype: IT, 32 | private readonly _predicate: (v: IT["CreationType"]) => boolean, 33 | private readonly _message: (v: IT["CreationType"]) => string 34 | ) { 35 | super(name) 36 | } 37 | 38 | describe() { 39 | return this.name 40 | } 41 | 42 | instantiate( 43 | parent: AnyObjectNode | null, 44 | subpath: string, 45 | environment: any, 46 | initialValue: this["C"] | this["T"] 47 | ): this["N"] { 48 | // create the child type 49 | return this._subtype.instantiate(parent, subpath, environment, initialValue) as any 50 | } 51 | 52 | isAssignableFrom(type: IAnyType) { 53 | return this._subtype.isAssignableFrom(type) 54 | } 55 | 56 | isValidSnapshot(value: this["C"], context: IValidationContext): IValidationResult { 57 | const subtypeErrors = this._subtype.validate(value, context) 58 | if (subtypeErrors.length > 0) return subtypeErrors 59 | 60 | const snapshot = isStateTreeNode(value) ? getStateTreeNode(value).snapshot : value 61 | 62 | if (!this._predicate(snapshot)) { 63 | return typeCheckFailure(context, value, this._message(value)) 64 | } 65 | 66 | return typeCheckSuccess() 67 | } 68 | 69 | reconcile( 70 | current: this["N"], 71 | newValue: this["C"] | this["T"], 72 | parent: AnyObjectNode, 73 | subpath: string 74 | ): this["N"] { 75 | return this._subtype.reconcile(current, newValue, parent, subpath) as any 76 | } 77 | 78 | getSubTypes() { 79 | return this._subtype 80 | } 81 | } 82 | 83 | export function refinement( 84 | name: string, 85 | type: IT, 86 | predicate: (snapshot: IT["CreationType"]) => boolean, 87 | message?: string | ((v: IT["CreationType"]) => string) 88 | ): IT 89 | export function refinement( 90 | type: IT, 91 | predicate: (snapshot: IT["CreationType"]) => boolean, 92 | message?: string | ((v: IT["CreationType"]) => string) 93 | ): IT 94 | 95 | /** 96 | * `types.refinement` - Creates a type that is more specific than the base type, e.g. `types.refinement(types.string, value => value.length > 5)` to create a type of strings that can only be longer then 5. 97 | * 98 | * @param name 99 | * @param type 100 | * @param predicate 101 | * @returns 102 | */ 103 | export function refinement(...args: any[]): IAnyType { 104 | const name = typeof args[0] === "string" ? args.shift() : isType(args[0]) ? args[0].name : null 105 | const type = args[0] 106 | const predicate = args[1] 107 | const message = args[2] 108 | ? args[2] 109 | : (v: any) => "Value does not respect the refinement predicate" 110 | // ensures all parameters are correct 111 | assertIsType(type, [1, 2]) 112 | assertIsString(name, 1) 113 | assertIsFunction(predicate, [2, 3]) 114 | assertIsFunction(message, [3, 4]) 115 | 116 | return new Refinement(name, type, predicate, message) 117 | } 118 | 119 | /** 120 | * Returns if a given value is a refinement type. 121 | * 122 | * @param type 123 | * @returns 124 | */ 125 | export function isRefinementType(type: unknown): type is IAnyType { 126 | return isType(type) && (type.flags & TypeFlags.Refinement) > 0 127 | } 128 | -------------------------------------------------------------------------------- /docs/API/interfaces/isimpletype.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: "isimpletype" 3 | title: "ISimpleType" 4 | sidebar_label: "ISimpleType" 5 | --- 6 | 7 | [mobx-state-tree - v7.0.2](../index.md) › [ISimpleType](isimpletype.md) 8 | 9 | A simple type, this is, a type where the instance and the snapshot representation are the same. 10 | 11 | ## Type parameters 12 | 13 | ▪ **T** 14 | 15 | ## Hierarchy 16 | 17 | * [IType](itype.md)‹T, T, T› 18 | 19 | ↳ **ISimpleType** 20 | 21 | ## Index 22 | 23 | ### Properties 24 | 25 | * [identifierAttribute](isimpletype.md#optional-identifierattribute) 26 | * [name](isimpletype.md#name) 27 | 28 | ### Methods 29 | 30 | * [create](isimpletype.md#create) 31 | * [describe](isimpletype.md#describe) 32 | * [is](isimpletype.md#is) 33 | * [validate](isimpletype.md#validate) 34 | 35 | ## Properties 36 | 37 | ### `Optional` identifierAttribute 38 | 39 | • **identifierAttribute**? : *undefined | string* 40 | 41 | *Inherited from [IType](itype.md).[identifierAttribute](itype.md#optional-identifierattribute)* 42 | 43 | *Defined in [src/core/type/type.ts:92](https://github.com/mobxjs/mobx-state-tree/blob/1be40a3e/src/core/type/type.ts#L92)* 44 | 45 | Name of the identifier attribute or null if none. 46 | 47 | ___ 48 | 49 | ### name 50 | 51 | • **name**: *string* 52 | 53 | *Inherited from [IType](itype.md).[name](itype.md#name)* 54 | 55 | *Defined in [src/core/type/type.ts:87](https://github.com/mobxjs/mobx-state-tree/blob/1be40a3e/src/core/type/type.ts#L87)* 56 | 57 | Friendly type name. 58 | 59 | ## Methods 60 | 61 | ### create 62 | 63 | ▸ **create**(`snapshot?`: T | ExcludeReadonly‹T›, `env?`: any): *this["Type"]* 64 | 65 | *Inherited from [IType](itype.md).[create](itype.md#create)* 66 | 67 | *Defined in [src/core/type/type.ts:99](https://github.com/mobxjs/mobx-state-tree/blob/1be40a3e/src/core/type/type.ts#L99)* 68 | 69 | Creates an instance for the type given an snapshot input. 70 | 71 | **Parameters:** 72 | 73 | Name | Type | 74 | ------ | ------ | 75 | `snapshot?` | T | ExcludeReadonly‹T› | 76 | `env?` | any | 77 | 78 | **Returns:** *this["Type"]* 79 | 80 | An instance of that type. 81 | 82 | ___ 83 | 84 | ### describe 85 | 86 | ▸ **describe**(): *string* 87 | 88 | *Inherited from [IType](itype.md).[describe](itype.md#describe)* 89 | 90 | *Defined in [src/core/type/type.ts:121](https://github.com/mobxjs/mobx-state-tree/blob/1be40a3e/src/core/type/type.ts#L121)* 91 | 92 | Gets the textual representation of the type as a string. 93 | 94 | **Returns:** *string* 95 | 96 | ___ 97 | 98 | ### is 99 | 100 | ▸ **is**(`thing`: any): *thing is T | this["Type"]* 101 | 102 | *Inherited from [IType](itype.md).[is](itype.md#is)* 103 | 104 | *Defined in [src/core/type/type.ts:107](https://github.com/mobxjs/mobx-state-tree/blob/1be40a3e/src/core/type/type.ts#L107)* 105 | 106 | Checks if a given snapshot / instance is of the given type. 107 | 108 | **Parameters:** 109 | 110 | Name | Type | Description | 111 | ------ | ------ | ------ | 112 | `thing` | any | Snapshot or instance to be checked. | 113 | 114 | **Returns:** *thing is T | this["Type"]* 115 | 116 | true if the value is of the current type, false otherwise. 117 | 118 | ___ 119 | 120 | ### validate 121 | 122 | ▸ **validate**(`thing`: T | T, `context`: [IValidationContext](../index.md#ivalidationcontext)): *[IValidationResult](../index.md#ivalidationresult)* 123 | 124 | *Inherited from [IType](itype.md).[validate](itype.md#validate)* 125 | 126 | *Defined in [src/core/type/type.ts:116](https://github.com/mobxjs/mobx-state-tree/blob/1be40a3e/src/core/type/type.ts#L116)* 127 | 128 | Run's the type's typechecker on the given value with the given validation context. 129 | 130 | **Parameters:** 131 | 132 | Name | Type | Description | 133 | ------ | ------ | ------ | 134 | `thing` | T | T | Value to be checked, either a snapshot or an instance. | 135 | `context` | [IValidationContext](../index.md#ivalidationcontext) | Validation context, an array of { subpaths, subtypes } that should be validated | 136 | 137 | **Returns:** *[IValidationResult](../index.md#ivalidationresult)* 138 | 139 | The validation result, an array with the list of validation errors. 140 | -------------------------------------------------------------------------------- /website/siteConfig.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2017-present, Facebook, Inc. 3 | * 4 | * This source code is licensed under the MIT license found in the 5 | * LICENSE file in the root directory of this source tree. 6 | */ 7 | 8 | // See https://docusaurus.io/docs/site-config for all the possible 9 | // site configuration options. 10 | 11 | const siteConfig = { 12 | algolia: { 13 | apiKey: "b7b0cfe7d1c8fa6db6089df94a3128f1", 14 | indexName: "mobx-state-tree" 15 | }, 16 | title: "MobX-state-tree", // Title for your website. 17 | tagline: 18 | "Opinionated, transactional, MobX powered state container combining the best features of the immutable and mutable world for an optimal DX", 19 | url: "https://mobx-state-tree.js.org/", // Your website URL 20 | // baseUrl: "/mobx-state-tree/", 21 | baseUrl: "/", // Base URL for your project */ 22 | docsUrl: "", 23 | cname: "mobx-state-tree.js.org", 24 | editUrl: "https://github.com/mobxjs/mobx-state-tree/edit/master/docs/", 25 | // For github.io type URLs, you would set the url and baseUrl like: 26 | // url: 'https://facebook.github.io', 27 | // baseUrl: '/test-site/', 28 | 29 | // Used for publishing and more 30 | projectName: "mobx-state-tree", 31 | organizationName: "mobxjs", 32 | gaTrackingId: "UA-65632006-4", 33 | // For top-level user or org sites, the organization is still the same. 34 | // e.g., for the https://JoelMarcey.github.io site, it would be set like... 35 | // organizationName: 'JoelMarcey' 36 | 37 | // For no header links in the top nav bar -> headerLinks: [], 38 | headerLinks: [ 39 | { 40 | doc: "intro/welcome", 41 | label: "Documentation" 42 | }, 43 | { 44 | doc: "API/index", 45 | label: "TypeDocs" 46 | }, 47 | { href: "https://opencollective.com/mobx", label: "Sponsor" }, 48 | 49 | { href: "https://github.com/mobxjs/mobx-state-tree", label: "GitHub" } 50 | // {doc: "support", label: "Support mobx-state-tree"} 51 | ], 52 | 53 | /* path to images for header/footer */ 54 | headerIcon: "img/favicon.ico", 55 | footerIcon: "img/favicon.ico", 56 | favicon: "img/favicon.ico", 57 | 58 | /* Colors for website */ 59 | colors: { 60 | primaryColor: "#000", 61 | secondaryColor: "#ff7000" 62 | }, 63 | 64 | /* Custom fonts for website */ 65 | /* 66 | fonts: { 67 | myFont: [ 68 | "Times New Roman", 69 | "Serif" 70 | ], 71 | myOtherFont: [ 72 | "-apple-system", 73 | "system-ui" 74 | ] 75 | }, 76 | */ 77 | 78 | // This copyright info is used in /core/Footer.js and blog RSS/Atom feeds. 79 | copyright: `Copyright © ${new Date().getFullYear()} Michel Weststrate`, 80 | 81 | highlight: { 82 | // Highlight.js theme to use for syntax highlighting in code blocks. 83 | theme: "dracula" 84 | }, 85 | 86 | // Add custom scripts here that would be placed in