{"id":18355420,"url":"https://github.com/charto/classy-mst","last_synced_at":"2025-04-06T12:32:01.858Z","repository":{"id":56228555,"uuid":"108137818","full_name":"charto/classy-mst","owner":"charto","description":"ES6-like syntax for mobx-state-tree","archived":false,"fork":false,"pushed_at":"2020-11-19T12:02:06.000Z","size":54,"stargazers_count":91,"open_issues_count":4,"forks_count":4,"subscribers_count":5,"default_branch":"master","last_synced_at":"2025-03-17T13:07:54.563Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/charto.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2017-10-24T14:22:44.000Z","updated_at":"2024-01-24T21:55:23.000Z","dependencies_parsed_at":"2022-08-15T15:01:02.513Z","dependency_job_id":null,"html_url":"https://github.com/charto/classy-mst","commit_stats":null,"previous_names":[],"tags_count":18,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/charto%2Fclassy-mst","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/charto%2Fclassy-mst/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/charto%2Fclassy-mst/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/charto%2Fclassy-mst/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/charto","download_url":"https://codeload.github.com/charto/classy-mst/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247484352,"owners_count":20946384,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-11-05T22:06:51.884Z","updated_at":"2025-04-06T12:32:01.571Z","avatar_url":"https://github.com/charto.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"classy-mst\n==========\n\n[![build status](https://travis-ci.org/charto/classy-mst.svg?branch=master)](http://travis-ci.org/charto/classy-mst)\n[![npm version](https://img.shields.io/npm/v/classy-mst.svg)](https://www.npmjs.com/package/classy-mst)\n[![dependency status](https://david-dm.org/charto/classy-mst.svg)](https://david-dm.org/charto/classy-mst)\n[![install size](https://img.shields.io/bundlephobia/min/classy-mst.svg)](https://bundlephobia.com/result?p=classy-mst)\n[![license](https://img.shields.io/npm/l/classy-mst.svg)](https://raw.githubusercontent.com/charto/classy-mst/master/LICENSE)\n\n`classy-mst` is the ultimate state management solution for TypeScript and JavaScript apps: a light wrapper around the amazing\n[mobx-state-tree](https://github.com/mobxjs/mobx-state-tree) to allow standard ES6 syntax.\n\nES6 class methods become \"views\" or \"actions\" (when decorated with `@action`\nto indicate they have side effects). Then:\n\n- Changes automatically propagate through views.\n- State is protected from modification outside actions.\n- State and state diffs (patches) are serializable to JSON and replayable for undo / redo.\n- Redux DevTools are supported for working with the state.\n  - The underlying technology is still [MobX](https://mobx.js.org/).\n\n[mobx-state-tree](https://github.com/mobxjs/mobx-state-tree) provides the\nstate management, `classy-mst` adds the benefits of standard ES6 syntax:\n\n- Less boilerplate.\n- `this`, `super` and inheritance work as you would expect.\n- No lock-in, easier to switch to other technology if needed.\n\n**Note**: Old versions 1.x work with mobx-state-tree 2.x.\nNow the major versions are kept in sync.\n\nContents\n========\n\n- [Usage](#usage)\n- [Inheritance](#inheritance)\n- [Polymorphism](#polymorphism)\n- [Getters and setters](#getters-and-setters)\n- [Volatile state](#volatile-state)\n- [Asynchronous actions](#asynchronous-actions)\n- [Recursive types](#recursive-types)\n- [License](#license)\n\nUsage\n-----\n\nInstall:\n\n```bash\nnpm install --save mobx mobx-state-tree classy-mst\n```\n\nUse in your code:\n\n```TypeScript\nimport { types } from 'mobx-state-tree';\nimport { mst, shim, action } from 'classy-mst';\n\nconst TodoData = types.model({\n\n\ttitle: types.string,\n\tdone: false\n\n});\n\nclass TodoCode extends shim(TodoData) {\n\n\t@action\n\ttoggle() {\n\t\tthis.done = !this.done;\n\t}\n\n}\n\nconst Todo = mst(TodoCode, TodoData, 'Todo');\n```\n\nES6 methods become views (assumed to have no side-effects) unless decorated\nwith `@action`, which turns them into actions.\n\nAfterwards, `Todo` is a regular MST type. Here, `TodoData` is an MST type\nholding the properties with MobX state tracking magic, and `TodoCode` is only\na block of code with methods (views and actions) to use with the type.\n\nThe `mst` function binds the two together (producing a new type \"inheriting\"\n`TodoData`), and the `TodoCode` class should not be used directly.\nA third, optional parameter gives the resulting model a name.\nNames are required for polymorphism to work correctly, when serializing\nmodels to JSON containing fields with types that have further subclasses.\n\nThe `shim` function is a tiny wrapper that makes TypeScript accept MST types\nas superclasses. It must be used in the `extends` clause of the ES6 class\ndefining the views and actions.\n\nThe major differences compared to ordinary ES6 classes are:\n\n- `this instanceof Class` is false inside `Class`, because `this` refers to a MobX state tree node.\n- Class properties must be declared using MST type syntax in a separate block before the class.\n- MST has no static properties.\n\nYou can look at the [tests](https://github.com/charto/classy-mst/blob/master/test/test.ts)\nfor fully working examples, or run them like this:\n\n```bash\ngit clone https://github.com/charto/classy-mst.git\ncd classy-mst\nnpm install\nnpm test\n```\n\nInheritance\n-----------\n\nYou can inherit from and extend other classes wrapped in MST types as follows:\n\n```TypeScript\n// Inherit Todo and add new count property.\n\nconst SpecialTodoData = Todo.props({\n\tcount: 0\n});\n\n// Original MST type \"Todo\" containing the wrapped methods\n// is needed by shim for binding references to \"super\".\n\nclass SpecialTodoCode extends shim(SpecialTodoData, Todo) {\n\n\t@action\n\ttoggle() {\n\t\tconsole.log('Toggled ' + (++this.count) + ' times!');\n\t\tsuper.toggle();\n\t}\n\n}\n\nconst SpecialTodo = mst(SpecialTodoCode, SpecialTodoData, 'SpecialTodo');\n```\n\nIf adding new properties to the superclass, it's important to pass the\nunmodified superclass as the second parameter to `shim` so that\n`super` is initialized correctly.\n\nInheritance support combined with hot reload or late types can cause strange\nerrors such as stack overflow. To fix them, a class can be sealed to disable\ninheritance entirely:\n\n```TypeScript\nconst Todo = mst(TodoCode, TodoData, 'Todo', { sealed: true });\n```\n\nPolymorphism\n------------\n\nInstances of subclasses can be used in place of their parent classes inside models.\nDue to `mobx-state-tree` implementation internals, both classes must have been defined\nbefore the first parent class instance has been created anywhere in the program.\n\nSnapshots containing polymorphic types require type names in the serialized JSON,\nto identify the correct subclass when applying the snapshot.\nA special key `$` is automatically added in snapshots when an object in the tree\nbelongs to a subclass of the class actually specified in the model.\n\nThe default key `$` for types can be changed by passing a different string to the\n`setTypeTag` function before creating any model instances. For example:\n\n```TypeScript\nimport { getSnapshot } from 'mobx-state-tree';\nimport { setTypeTag } from 'classy-mst';\n\nsetTypeTag('type');\n\nconst Store = types.model({\n\ttodos: types.array(Todo)\n});\n\nconst store = Store.create({\n\ttodos: [\n\t\tSpecialTodo.create({ title: 'Baz' })\n\t]\n});\n\nconsole.log(getSnapshot(store));\n```\n\nThe above prints:\n\n```\n{ todos: [ { title: 'Baz', done: false, count: 0, type: 'SpecialTodo' } ] }\n```\n\nGetters and setters\n-------------------\n\nClass members with getters become MobX computed properties.\nSetters are not considered actions themselves, so they're only allowed to\nmodify internal state by calling other methods decorated with `@action`.\n\nFor example:\n\n```TypeScript\nclass TodoCode extends shim(TodoData) {\n\n        @action\n        toggle() {\n                this.done = !this.done;\n        }\n\n        get pending() {\n                return(!this.done);\n        }\n\n        set pending(flag: boolean) {\n                if(this.done == flag) this.toggle();\n        }\n\n}\n```\n\nVolatile state\n--------------\n\nYou can create a model with volatile state directly using `mobx-state-tree` syntax:\n\n```TypeScript\nconst VolatileData = types.model({}).volatile(\n\t(self) =\u003e ({ a: 1 })\n);\n```\n\nAlternatively, for most types of volatile members (not functions, however)\nyou can define and initialize them inside the ES6 class:\n\n```TypeScript\nclass VolatileCode extends shim(VolatileData) {\n\n\tb = 2;\n\n}\n```\n\nNote that the member must be initialized, or it gets compiled away and `classy-mst`\nnever sees it.\n\nAsynchronous actions\n--------------------\n\nAsynchronous actions return a promise. The actual method needs to define a\ngenerator, pass it to `flow` from `mobx-state-tree`, call the returned\nfunction and return its result, like this:\n\n```TypeScript\nimport { types, flow } from 'mobx-state-tree';\nimport { mst, shim, action } from 'classy-mst';\n\nconst AsyncData = types.model({});\n\nclass AsyncCode extends shim(AsyncData) {\n\n\t@action\n\trun() {\n\t\tfunction* generate() {\n\t\t\tyield Promise.resolve('This gets lost');\n\t\t\treturn('Returned value');\n\t\t}\n\n\t\treturn(flow(generate)());\n\t}\n\n}\n\nconst Async = mst(AsyncCode, AsyncData);\n\nAsync.create().run().then(\n\t(result) =\u003e console.log(result)\n);\n```\n\nRecursive types\n---------------\n\nFully typed recursive types require some tricky syntax to avoid these TypeScript compiler errors:\n\n- `error TS2456: Type alias 'Type' circularly references itself.`\n- `error TS2502: 'member' is referenced directly or indirectly in its own type annotation.`\n- `error TS2506: 'Type' is referenced directly or indirectly in its own base expression.`\n- `error TS7022: 'Type' implicitly has type 'any' because it does not have a type annotation and is referenced directly or indirectly in its own initializer.`\n\nIf your model has a `children` property containing an array of the same model\nas their parent, the easiest solution is to add the `children` property only\nin the ES6 class and use `mstWithChildren` instead of `mst` when defining the\nmodel. It handles adding the property to the `mobx-state-tree` type.\n\nThe function `mstWithChildren` returns an object with the members:\n\n- `Model`, the model with views, actions and a `children` property attached.\n- `Children`, the correct `mobx-state-tree` type for the `children` property.\n\nYou should call it just after your class defining the views and actions\n(replacing Todo with your own class name) like this:\n\n```TypeScript\nconst { Model: Todo, Children } = mstWithChildren(TodoCode, TodoData, 'Todo');\n```\n\nYou can use the `Children` type inside the class methods thanks to declaration\nhoisting. Without the type, it's difficult to initialize an unset `children`\nproperty correctly.\n\nThe `children` property should be declared in your class as\n`(this | \u003cclass name\u003e)[]` to allow further inheritance, like this:\n\n```TypeScript\nimport { IObservableArray } from 'mobx';\nimport { types, isStateTreeNode, IModelType } from 'mobx-state-tree';\nimport { mst, mstWithChildren, shim, action, ModelInterface } from 'classy-mst';\n\nexport const NodeData = T.model({ value: 42 });\nexport class NodeCode extends shim(NodeData) {\n\n\t@action\n\taddChild(child: Node | typeof Node.SnapshotType) {\n\t\tif(!this.children) this.children = Children.create();\n\t\tthis.children.push(isStateTreeNode(child) ? child : Node.create(child));\n\n\t\treturn(this);\n\t}\n\n\tchildren?: (this | NodeCode)[];\n}\n\nconst { Model: Node, Children } = mstWithChildren(NodeCode, NodeData, 'Node');\nexport type Node = typeof Node.Type;\n```\n\nIf you want to use some other name than `children` for the property, easiest is\nto copy, paste and customize the `mstWithChildren` function from\n[classy-mst.ts](https://github.com/charto/classy-mst/blob/master/src/classy-mst.ts).\nWithout macro support in the TypeScript compiler, the name cannot be\nparameterized while keeping the code fully typed.\n\nLicense\n=======\n\n[The MIT License](https://raw.githubusercontent.com/charto/classy-mst/master/LICENSE)\n\nCopyright (c) 2017- BusFaster Ltd\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcharto%2Fclassy-mst","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fcharto%2Fclassy-mst","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcharto%2Fclassy-mst/lists"}