{"id":18831278,"url":"https://github.com/acrazing/mobx-sync","last_synced_at":"2025-04-06T22:12:09.676Z","repository":{"id":40803308,"uuid":"112314438","full_name":"acrazing/mobx-sync","owner":"acrazing","description":"A library to persist mobx store automatically","archived":false,"fork":false,"pushed_at":"2023-01-06T01:51:06.000Z","size":1087,"stargazers_count":148,"open_issues_count":20,"forks_count":11,"subscribers_count":1,"default_branch":"master","last_synced_at":"2025-03-30T21:07:40.259Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"","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/acrazing.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-11-28T09:27:23.000Z","updated_at":"2024-08-31T07:57:02.000Z","dependencies_parsed_at":"2023-02-05T02:00:54.779Z","dependency_job_id":null,"html_url":"https://github.com/acrazing/mobx-sync","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/acrazing%2Fmobx-sync","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/acrazing%2Fmobx-sync/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/acrazing%2Fmobx-sync/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/acrazing%2Fmobx-sync/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/acrazing","download_url":"https://codeload.github.com/acrazing/mobx-sync/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247557770,"owners_count":20958047,"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-08T01:52:52.015Z","updated_at":"2025-04-06T22:12:09.654Z","avatar_url":"https://github.com/acrazing.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# mobx-sync\n\n[![FOSSA Status](https://app.fossa.io/api/projects/git%2Bgithub.com%2Facrazing%2Fmobx-sync.svg?type=shield)](https://app.fossa.io/projects/git%2Bgithub.com%2Facrazing%2Fmobx-sync?ref=badge_shield)\n\nA library use JSON to persist your MobX stores with version control.\n\n## Features\n\n- use `JSON.stringify/JSON.parse` as the deserialize/serialize method\n- version control by using `@version` decorator\n- ignore any store node by using `@ignore` decorator\n- support React Native\n- support server side rendering (SSR)\n\n## Install\n\n```bash\n# by yarn\nyarn add mobx-sync\n\n# OR by npm\nnpm i -S mobx-sync\n```\n\n## Quick Start\n\n```typescript jsx\nimport { AsyncTrunk, date } from 'mobx-sync';\nimport { observable } from 'mobx';\n\nclass Store {\n  @observable\n  foo = 'bar';\n\n  @date\n  @observable\n  date = new Date();\n}\n\nconst store = new Store();\n\n// create a mobx-sync instance, it will:\n// 1. load your state from localStorage \u0026 ssr rendered state\n// 2. persist your store to localStorage automatically\n// NOTE: you do not need to call `trunk.updateStore` to persist\n// your store, it is persisted automatically!\nconst trunk = new AsyncTrunk(store, { storage: localStorage });\n\n// init the state and auto persist watcher(use MobX's autorun)\n// NOTE: it will load the persisted state first(and must), and\n// then load the state from ssr, if you pass it as the first\n// argument of `init`, just like trunk.init(__INITIAL_STATE__)\ntrunk.init().then(() =\u003e {\n  // you can do any staff now, just like:\n\n  // 1. render app with initial state:\n  ReactDOM.render(\u003cApp store={store} /\u003e);\n\n  // 2. update store, the update of the store will be persisted\n  // automatically:\n  store.foo = 'foo bar';\n});\n```\n\n## Full Example\n\nYou can see it at [example](example/index.tsx)\n\n## API Reference\n\n- [version control](#version-control)\n- [ignore control](#ignore-control)\n- [custom formatter](#custom-formatter)\n- [async trunk](#async-trunk)\n- [sync trunk](#sync-trunk)\n- [ssr](#ssr)\n\n### version control\n\nSometimes, if your store's data structure has been changed, which means\nthe persisted data is illegal to use, you can use `@version` decorator\nto mark the store node with a `version`, if the persisted version is different\nfrom the declared node's version, the persisted version will be ignored.\n\nFor example, we publish an application like the [Quick Start](#quick-start) at first,\nand then we want to change the type of `Store#foo` from `string` to `number`.\nThe persisted string value of `foo` thus become illegal, and should be ignored.\nIt is necessary to use `@version` to mark the `foo` field with a new version to omit it:\n\n```typescript jsx\nimport { version } from 'mobx-sync';\nimport { observable } from 'mobx';\n\nclass Store {\n  @version(1)\n  @observable\n  foo = 1;\n\n  @date\n  @observable\n  date = new Date();\n}\n\n// ...\n```\n\nWhen application with the new version is executed, the persisted\nvalue of `foo` will be ignored, while `date` keeps the persisted value.\nIt means, after calling `trunk.init()`,the `foo` becomes `1`,\nand `date` still stores the previous value.\n\n**NOTE: if the new version is strictly different with the persisted version,\nit will be ignored, or else it will be loaded as normal, so if you use it,\nwe recommend you use an progressive increasing integer to mark it, because\nyou couldn't know the version of persisted in client.**\n\n`@version` also supports class decorator, that means any instance of the\nclass will be ignored if its version is different. For example:\n\n```typescript jsx\nimport { version } from 'mobx-sync';\nimport { observable } from 'mobx';\n\n@version(1)\nclass C1 {\n  p1 = 1;\n}\n\nclass C2 {\n  p2 = 2;\n}\n\nclass Store {\n  c1 = new C1();\n  c2 = new C2();\n  c1_1 = new C1();\n}\n```\n\nIf the persisted version of store's `c1` \u0026\u0026 `c1_1` has different version with\n`1`, they will be ignored.\n\n**NOTE: if you use a non-pure object as the store field, you must initialize it\nbefore you call `trunk.init`, just like `custom store class`(`C1`, `C2` upon),\n`observable.map`, `observable.array`, etc. And it must be iterable by `for..in`\ngrammar, if not, you may need to use a custom formatter(see\n[custom formatter](#custom-formatter) bellow) to serialize/de-serialize it.**\n\nSignature:\n\n```typescript jsx\nfunction version(id: number): PropertyDecorator \u0026 ClassDecorator;\n```\n\n### ignore control\n\nIf you hope some fields of your store to skip persisting, just like an article\nwith big size of detailed content. you can use `@ignore` decorator to mark it,\nthose fields will not be loaded (even if it is persisted in previous version) in the initial,\nand also the subsequent change will not trigger the action of persisting.\n\nFor example: if we want to ignore the `date` field in Quick Start, we just need to\nuse `@ignore` to decorate it:\n\n```typescript jsx\nimport { date, ignore } from 'mobx-sync';\nimport { observable } from 'mobx';\n\nclass Store {\n  @observable\n  foo = 'bar';\n\n  @ignore\n  @date\n  @observable\n  date = new Date();\n}\n```\n\n`@ignore` only supports decorating property.\n\nSignature:\n\n```typescript jsx\n/**\n * works in web environment only\n */\nfunction ignore(target: any, key: string): void;\nnamespace ignore {\n  /**\n   * works in both web and ssr environment\n   */\n  function ssr(target: any, key: string): void;\n\n  /**\n   * works in ssr environment only\n   */\n  function ssrOnly(target: any, key: string): void;\n}\n```\n\n### custom formatter\n\nSometimes, your store node is not a pure object, just like `Set`, `Map`,\n`observable.map\u003cnumber, Date\u003e`, etc, you may need to use custom formatter\n(`@format`) to parse/stringify the data/value.\n\nFor example, we use `Set\u003cDate\u003e` as a field:\n\n```typescript jsx\nimport { format } from 'mobx-sync';\nimport { observable } from 'mobx';\n\nclass Store {\n  @format(\n    (data: string[]) =\u003e new Set(data.map((d) =\u003e new Date(d))),\n    (value: Set\u003cDate\u003e) =\u003e Array.from(value, (v) =\u003e v.toISOString()),\n  )\n  @observable\n  allowDates = new Set\u003cDate\u003e();\n}\n```\n\nBuilt-in formatters:\n\n- `@date`: parse/stringify date\n- `@regexp`: parse/stringify regexp\n\nSignature:\n\n```typescript jsx\n/**\n * define a custom stringify/parse function for a field, it is useful for\n * builtin objects, just like Date, TypedArray, etc.\n *\n * @example\n *\n * // this example shows how to format a date to timestamp,\n * // and load it from serialized string,\n * // if the date is invalid, it will not be persisted.\n * class SomeStore {\n *   @format\u003cDate, number\u003e(\n *      (timestamp) =\u003e new Date(timestamp),\n *      (date) =\u003e date ? +date : void 0,\n *   )\n *   dateField = new Date()\n * }\n *\n * @param deserializer - the function to parse the serialized data to\n *      custom object, the first argument is the data serialized by\n *      `serializer`, and the second is the current value of the field.\n * @param serializer - the function to serialize the object to pure js\n *      object or any else could be stringify safely by `JSON.stringify`.\n */\nfunction format\u003cI, O = I\u003e(\n  deserializer: (persistedValue: O, currentValue: I) =\u003e I,\n  serializer?: (value: I) =\u003e O,\n): PropertyDecorator;\n\nfunction date(target: any, key: string): void;\n\nfunction regexp(target: any, key: string): void;\n```\n\n### SSR\n\nSometimes, we hope to use MobX in SSR(Server-Side Rendering), there is no\nstandard way to stringify/load mobx store to/from html template, mobx-sync\nmaybe one.\n\nAt first, you need to call `config({ ssr: true })` before call any decorator\nof mobx-sync. And then, you can use `JSON.stringify` to stringify your state\nto html template, and then use `trunk.init` or `parseStore` to load it to\nyour store.\n\nFor example:\n\n```typescript jsx\n// store.ts\nimport { ignore } from 'mobx-sync'\nimport { observable } from 'mobx'\n\nexport Store {\n  @observable userId = 0\n\n  @ignore.ssr\n  users = observable.map()\n}\n```\n\n```typescript jsx\n// server.ts\nimport { config } from 'mobx-sync';\n\nconfig({ ssr: true });\n\nimport { Store } from './store';\n\napp.get('/', (_, res) =\u003e {\n  const store = new Store();\n\n  res.end(`\u003c!DOCTYPE html\u003e\n  \u003chtml\u003e\n  \u003cbody\u003e\n  \u003cdiv id=root\u003e${renderToString(\u003cApp store={store} /\u003e)}\u003c/div\u003e\n  \u003cscript\u003evar __INITIAL_STATE__ = ${JSON.stringify(store).replace(\n    /\u003c/g,\n    '\\\\u003c',\n  )}\u003c/script\u003e\n  \u003c/body\u003e\n  \u003c/html\u003e`);\n});\n```\n\n```typescript jsx\n// client.ts\nimport { AsyncTrunk } from 'mobx-sync';\nimport { Store } from './store';\n\nconst store = new Store();\nconst trunk = new AsyncTrunk(store);\n\ntrunk.init(__INITIAL_STATE__).then(() =\u003e {\n  ReactDOM.render(\u003cApp store={store} /\u003e, document.querySelector('#root'));\n});\n```\n\n**NOTE: if you do not want to use a trunk to persist/load state from\nlocalStorage, just want to use mobx-sync to load SSR state, you can use\n`parseStore(store, state, true)` to load it.**\n\nFor example:\n\n```typescript jsx\n// client.ts\nimport { parseStore } from 'mobx-sync';\nimport { Store } from './store';\n\nconst store = new Store();\n\nparseStore(store, __INITIAL_STATE__, true);\n\nReactDOM.render(\u003cApp /\u003e, document.querySelector('#root'));\n```\n\nSignature:\n\n```typescript jsx\ninterface Options {\n  ssr: boolean;\n}\n\nfunction config(options: Partial\u003cOptions\u003e): void;\n\nfunction parseStore(store: any, data: any, isFromServer: boolean): void;\n```\n\n### async trunk\n\n### sync trunk\n\nBoth of `AsyncTrunk` and `SyncTrunk` is the class to auto load/persist\nstore to storage, the difference between them is the `AsyncTrunk` runs\nasynchronously and `SyncTrunk` runs synchronously.\n\nSignature:\n\n```typescript jsx\n// this is a subset of `Storage`\ninterface SyncStorage {\n  getItem(key: string): string | null;\n  setItem(key: string, value: string): void;\n  removeItem(key: string): void;\n}\n\n// this is a subset of `ReactNative.AsyncStorage`\ninterface AsyncStorage {\n  getItem(key: string): Promise\u003cstring | null\u003e;\n  setItem(key: string, value: string): Promise\u003cvoid\u003e;\n  removeItem(key: string): Promise\u003cvoid\u003e;\n}\n\n/**\n * sync trunk initial options\n */\nexport interface SyncTrunkOptions {\n  /**\n   * storage, SyncStorage only\n   * default is localStorage\n   */\n  storage?: SyncStorage;\n  /**\n   * the storage key, default is KeyDefaultKey\n   */\n  storageKey?: string;\n  /**\n   * the delay time, default is 0\n   */\n  delay?: number;\n\n  /**\n   * error callback\n   * @param error\n   */\n  onError?: (error: any) =\u003e void;\n}\n\n/**\n * the async trunk initial options\n */\nexport interface AsyncTrunkOptions {\n  /**\n   * storage, both AsyncStorage and SyncStorage is supported,\n   * default is localStorage\n   */\n  storage?: AsyncStorage | SyncStorage;\n  /**\n   * the custom persisted key in storage,\n   * default is KeyDefaultKey\n   */\n  storageKey?: string;\n  /**\n   * delay milliseconds for run the reaction for mobx,\n   * default is 0\n   */\n  delay?: number;\n\n  /**\n   * error callback\n   * @param error\n   */\n  onError?: (error: any) =\u003e void;\n}\n\nclass AsyncTrunk {\n  disposer: () =\u003e void;\n  constructor(store: any, options?: AsyncTrunkOptions);\n  init(initialState?: any): Promise\u003cvoid\u003e;\n  // call persist manually\n  persist(): Promise\u003cvoid\u003e;\n  // clear persisted state in storage\n  clear(): Promise\u003cvoid\u003e;\n  // change the store instance\n  updateStore(): Promise\u003cvoid\u003e;\n}\n\nclass SyncTrunk {\n  disposer: () =\u003e void;\n  constructor(store: any, options?: SyncTrunkOptions);\n  init(initialState?: any): void;\n  // call persist manually\n  persist(): void;\n  // clear persisted state in storage\n  clear(): void;\n  // change the store instance\n  updateStore(): void;\n}\n```\n\n## License\n\n```markdown\nThe MIT License (MIT)\n\nCopyright (c) 2016 acrazing\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n```\n\n[![FOSSA Status](https://app.fossa.io/api/projects/git%2Bgithub.com%2Facrazing%2Fmobx-sync.svg?type=large)](https://app.fossa.io/projects/git%2Bgithub.com%2Facrazing%2Fmobx-sync?ref=badge_large)","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Facrazing%2Fmobx-sync","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Facrazing%2Fmobx-sync","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Facrazing%2Fmobx-sync/lists"}