{"id":16837694,"url":"https://github.com/jeffijoe/libx","last_synced_at":"2025-05-08T21:23:20.402Z","repository":{"id":17683621,"uuid":"82486359","full_name":"jeffijoe/libx","owner":"jeffijoe","description":"Collection + Model infrastructure for MobX applications.","archived":false,"fork":false,"pushed_at":"2023-02-04T13:12:31.000Z","size":722,"stargazers_count":103,"open_issues_count":7,"forks_count":5,"subscribers_count":7,"default_branch":"master","last_synced_at":"2024-04-14T12:10:11.125Z","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/jeffijoe.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE.md","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2017-02-19T20:11:46.000Z","updated_at":"2024-04-09T20:35:21.000Z","dependencies_parsed_at":"2023-02-18T16:35:30.825Z","dependency_job_id":null,"html_url":"https://github.com/jeffijoe/libx","commit_stats":null,"previous_names":[],"tags_count":22,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jeffijoe%2Flibx","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jeffijoe%2Flibx/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jeffijoe%2Flibx/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jeffijoe%2Flibx/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/jeffijoe","download_url":"https://codeload.github.com/jeffijoe/libx/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":238044093,"owners_count":19407128,"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-10-13T12:18:33.464Z","updated_at":"2025-02-10T02:09:06.730Z","avatar_url":"https://github.com/jeffijoe.png","language":"TypeScript","funding_links":[],"categories":["Awesome MobX"],"sub_categories":["Related projects and utilities"],"readme":"# libx\n\n[![npm](https://img.shields.io/npm/v/libx.svg?maxAge=1000)](https://www.npmjs.com/package/libx)\n[![dependency Status](https://img.shields.io/david/jeffijoe/libx.svg?maxAge=1000)](https://david-dm.org/jeffijoe/libx)\n[![devDependency Status](https://img.shields.io/david/dev/jeffijoe/libx.svg?maxAge=1000)](https://david-dm.org/jeffijoe/libx)\n[![Build Status](https://img.shields.io/travis/jeffijoe/libx.svg?maxAge=1000)](https://travis-ci.org/jeffijoe/libx)\n[![Coveralls](https://img.shields.io/coveralls/jeffijoe/libx.svg?maxAge=1000)](https://coveralls.io/github/jeffijoe/libx)\n[![npm](https://img.shields.io/npm/dt/libx.svg?maxAge=1000)](https://www.npmjs.com/package/libx)\n[![npm](https://img.shields.io/npm/l/libx.svg?maxAge=1000)](https://github.com/jeffijoe/libx/blob/master/LICENSE.md)\n[![node](https://img.shields.io/node/v/libx.svg?maxAge=1000)](https://www.npmjs.com/package/libx)\n\nCollection + Model infrastructure for [MobX](https://github.com/mobxjs/mobx) applications. Written in [TypeScript](https://github.com/Microsoft/TypeScript).\n\n## Install\n\n```bash\n# Depends on MobX, it's BYOD (Bring Your Own Dependencies)\nnpm install --save libx mobx\n```\n\n# Table of Contents\n\n- [libx](#libx)\n  - [Install](#install)\n- [Table of Contents](#table-of-contents)\n- [Why?](#why)\n- [Examples](#examples)\n- [Concepts](#concepts)\n  - [The Root Store](#the-root-store)\n  - [Store](#store)\n  - [Collection](#collection)\n  - [Model](#model)\n- [Let's build an app!](#lets-build-an-app)\n  - [Step 1: the Root Store](#step-1-the-root-store)\n  - [Step 2: the `TodoStore` and `Todo` model](#step-2-the-todostore-and-todo-model)\n  - [Step 3: the `UserStore` and `User` model](#step-3-the-userstore-and-user-model)\n  - [Step 4: the UI](#step-4-the-ui)\n- [API documentation](#api-documentation)\n  - [`collection([opts])`](#collectionopts)\n    - [Collection object](#collection-object)\n    - [`collection.items`](#collectionitems)\n    - [`collection.length`](#collectionlength)\n    - [`collection.add(models)`](#collectionaddmodels)\n    - [`collection.create(data, [opts])`](#collectioncreatedata-opts)\n    - [`collection.get(id)`](#collectiongetid)\n    - [`collection.set(data, [opts])`](#collectionsetdata-opts)\n    - [`collection.clear()`](#collectionclear)\n    - [`collection.remove(modelOrId)`](#collectionremovemodelorid)\n    - [`collection.move(fromIndex, toIndex)`](#collectionmovefromindex-toindex)\n    - [`collection.referenceOne(ids, field?)`](#collectionreferenceoneids-field)\n    - [`collection.referenceMany(ids, field)`](#collectionreferencemanyids-field)\n  - [The `Model` class](#the-model-class)\n    - [`constructor (attributes, opts)`](#constructor-attributes-opts)\n    - [`.rootStore`](#rootstore)\n    - [`.set (attributes, opts)`](#set-attributes-opts)\n    - [`.parse (attributes, opts)`](#parse-attributes-opts)\n      - [Parent -\\\u003e Child -\\\u003e Parent parsing](#parent---child---parent-parsing)\n  - [The `Store` class](#the-store-class)\n    - [`store.collection(opts)`](#storecollectionopts)\n  - [`createRootStore(obj)`](#createrootstoreobj)\n- [Projects using LibX](#projects-using-libx)\n- [See Also](#see-also)\n- [Author](#author)\n\n# Why?\n\nMaintaining large application state is hard. Maintaining single references to entities for a single source of truth is\nhard. But it doesn't have to be.\n\n**LibX** is inspired by [Backbone](https://github.com/jashkenas/backbone)'s notion of Collections and Models, and makes it sexy by using [MobX](https://github.com/mobxjs/mobx) to manage state,\ninstead of using events.\n\n**TL;DR:** Maintaining only a single instance of a model is a chore. With LibX, it's not.\n\n# Examples\n\nSee the [TypeScript example][ts-example] and [Babel example][babel-example] for runnable examples (in Node).\n\n# Concepts\n\nLibX concepts are similar to those of Backbone: **Models** and **Collections** are used to represent your domain data. LibX also adds the Flux concept of **Stores**.\n\n## The Root Store\n\nJust a fancy name for an object (or instance of a class) that references all your stores. Yes, it will work with Server Side Rendering, too. A root\nstore is by no means required to build apps with LibX - it's just convenient.\n\n**Example:**\n\n```js\nimport TodoStore from './TodoStore'\nimport UserStore from './UserStore'\n\nclass RootStore {\n  constructor() {\n    this.userStore = new UserStore({ rootStore: this })\n    this.todoStore = new TodoStore({ rootStore: this })\n  }\n}\n```\n\n## Store\n\nA store maintains \"top-level\" state, like collections, and _whether or not some sidebar is visible_.\n\nIt also contains **actions**: a means of mutating state. While not required, it is recommended that all actions and logic is implemented in stores.\n\n**Example:**\n\n```js\nimport { action } from 'mobx'\nimport { Store } from 'libx'\nimport Todo from '../models/Todo'\n\nexport default class TodoStore extends Store {\n  todos = this.collection({\n    model: Todo,\n  })\n\n  @action\n  createTodo(text) {\n    return http\n      .post('/todos', {\n        text: text,\n      })\n      .then((data) =\u003e {\n        this.todos.set(data)\n      })\n  }\n\n  @action\n  toggle(todo) {\n    const completed = !todo.completed\n    todo.set({ completed: completed })\n    return (\n      http\n        .patch('/todos/' + todo.id, {\n          completed: todo.completed,\n        })\n        // this will update the todo model, because it's already\n        // in the collection with the same ID.\n        .then(this.todos.set)\n    )\n  }\n}\n```\n\n## Collection\n\nMaintains a collection of models, making sure we only have a single instance of an entity in memory. That way, updates to an entity will propagate to the entire system without us having to do anything at all.\n\n**Example:** see the [`Store`](#store) example.\n\n## Model\n\nRepresents a concept of your own domain, like a `User`, a `Todo`, a `Product` or whatever your domain deals with.\n\nModels usually have observable and computed properties, and no actions other than the built-in `set()`. If you want to, you can give your models actions, but it is recommended to keep all actions in the stores.\n\n**Example:**\n\n```js\nimport { observable } from 'mobx'\nimport { Model } from 'libx'\n\nexport default class Todo extends Model {\n  @observable text = ''\n  @observable completed = false\n}\n```\n\nIf you are not a fan of using classes, you can also use the `model` builder pattern.\n\n**Example:**\n\n```js\nimport { model } from 'libx'\n\nexport const Todo = (attrs, opts) =\u003e {\n  return model()\n    .extendObservable({\n      text: '',\n      completed: false,\n    })\n    .set(attrs, opts)\n}\n```\n\n# Let's build an app!\n\nThis section is mostly going to break down the Babel example for easier digestion. :smile:\n\nWe are building a Todo app - _how original_ - but there's a twist:\nEach `Todo` has a `creator`, which is a `User`. It's like erhh, let's say _\"Todos for Teams\"_, hehe...\n\nSo that means we have 2 entities in our system: `Todo` and `User`. But more on those later.\n\n**Note:** basic knowledge of MobX and Promises will help.\n\n## Step 1: the Root Store\n\nTo make it easier for your application parts to communicate, we need some glue. In LibX,\nthat glue is called the **Root Store**.\n\n_The root store is by no means required to use parts of LibX (Models, Collections), but it greatly\nsimplifies writing applications._\n\nFeatures of the root store:\n\n- References all the other stores\n- Only a single instance per session.\n\nBy using the root store pattern instead of singleton stores, you can do server-side rendering\npretty easily.\n\nLet's create our root store.\n\n```js\nclass RootStore {\n  constructor() {\n    // We're gonna need 3 stores.\n    // Each store wants a reference to the root store\n    // so they can talk to each other, as well as pass it\n    // along to our `Todo` and `User` instances.\n    this.todoStore = new TodoStore({ rootStore: this })\n    this.userStore = new UserStore({ rootStore: this })\n    // used for UI state\n    this.todosScreenStore = new TodosScreenStore({ rootStore: this })\n  }\n}\n\n// And we're ready!\nconst rootStore = new RootStore()\n```\n\nThere's no rocket science there, so if you want to type less, you can use `createRootStore`.\n\n```js\nimport { createRootStore } from 'libx'\n\n// if using Server Side Rendering, do this once per request.\nconst rootStore = createRootStore({\n  todoStore: TodoStore,\n  userStore: UserStore,\n  // used for UI state\n  todosScreenStore: TodosScreenStore,\n})\n```\n\nThat was the root store. Now let's implement the `TodoStore`.\n\n## Step 2: the `TodoStore` and `Todo` model\n\nA Store in LibX is just a glorified state container. It can contain your data, UI state, whatever. However the\nmost common case is to store a collection of models.\n\nLet's implement the `TodoStore`.\n\n```js\nimport { Store } from 'libx'\n\nclass TodoStore extends Store {\n  // This is the most powerful part of LibX: Models and Collections.\n  // Whenever a `Todo` model is created, the `rootStore` is passed to it.\n  todos = this.collection({\n    // Whenever we try to add stuff to this collection,\n    // transform it to a Todo model.\n    model: Todo,\n  })\n\n  // Fetch todos from our server.\n  fetchTodos() {\n    return (\n      fetch('/api/todos')\n        .then((r) =\u003e r.json())\n        // `set` is a MobX action, no need to wrap it.\n        // Also, it does not care about `this` context.\n        .then(this.todos.set)\n    )\n  }\n\n  addNewTodo(text) {\n    return fetch('/api/todos', {\n      method: 'post',\n      body: JSON.stringify({\n        text,\n      }),\n    })\n      .then((r) =\u003e r.json())\n      .then(this.todos.set) // resolves to a Todo model.\n  }\n\n  toggleCompleted(id, completed) {\n    return (\n      fetch(`/api/todos/${id}`, {\n        method: 'patch',\n        body: JSON.stringify({\n          completed,\n        }),\n      })\n        .then((r) =\u003e r.json())\n        // resolves to our existing Todo model, because\n        // when `set` is called, it recognizes the `id` and sets\n        // the new values on the existing todo. Magical!\n        .then(this.todos.set)\n    )\n  }\n}\n```\n\nNotice that whenever we needed to update our todos state, we just\ncalled `this.todos.set(someObjectOrArray)` and LibX automagically adds and updates\nthe models.\n\nNow to the `Todo` model.\n\n```js\nimport { Model } from 'libx'\nimport { observable, computed } from 'mobx'\nimport moment from 'moment' // just to show off, hehe\n\nclass Todo extends Model {\n  @observable id\n  @observable text = ''\n  @observable completed = false\n  @observable creatorId = null // the ID of the user that created this todo\n  @observable createdAt\n\n  @computed\n  get creator() {\n    // References the creator by ID. If you change the `creatorId`\n    // this will automagically update.\n    return this.rootStore.userStore.users.referenceOne(this.creatorId)\n  }\n\n  parse(json) {\n    // Let's imagine the API response looks like\n    // {\n    //   \"id\": 1,\n    //   \"text\": \"Buy milk\"\n    //   \"completed\": false,\n    //   \"createdAt\": \"2017-20-02T14:45:12Z\",\n    //   \"creator\": {\n    //     \"_id\": \"abcd\",\n    //     \"name\": \"Jeff Hansen\"\n    //   }\n    // }\n\n    // We want to set the `creator` ourselves, so slice it out of\n    // the `json`.\n    const { creator, ...rest } = json\n    // Set the user in the user store, get a User (or undefined) back.\n    // When created through a Store collection, models have access\n    // to the Root Store.\n    this.rootStore.userStore.users.set(creator)\n    return {\n      // All fields except `creator`.\n      ...rest,\n      // Assign the creator ID so we can look it up.\n      creatorId: creator._id,\n      // We want all our dates as `Moment`s.\n      createdAt: moment(json.createdAt),\n    }\n  }\n}\n```\n\nWe leveraged the fact that we can communicate with the user store, and\n`parse` is called whenever we try to `set` some JSON in the collection.\n\nUnder the hood, the collection does this for new items:\n\n```js\n// call the parse function before setting on the model\nnew Todo(data, { parse: true, rootStore: rootStore })\n```\n\nAnd for existing items:\n\n```js\n// call the parse function before setting on the model\ntodo.set(data, { parse: true })\n```\n\n## Step 3: the `UserStore` and `User` model\n\nSame as the Todo store, except if you paid attention to the JSON example,\nyou might have noticed that users have an `_id` attribute instead of the conventional `id`.\nThis is not a problem at all, we just need to tell the collection what ID to look at.\n\n```js\nimport { Store } from 'libx'\n\nclass UserStore extends Store {\n  users = this.collection({\n    model: User,\n    idAttribute: '_id', // easy - to the pub!\n  })\n}\n```\n\nSince we don't deal with any user API, that's all we need. But even if we did,\nit would follow the same formula as the Todo store.\n\nOn to the `User` model!\n\n```js\nimport { Model } from 'libx'\n\nclass User extends Model {\n  @observable _id\n  @observable name\n\n  // Let's create a getter for the user's created todos.\n  // .. cause we can!\n  @computed\n  get todos() {\n    return this.rootStore.todoStore.todos.referenceMany(this._id, 'creatorId')\n  }\n}\n```\n\nSince we don't do any fancy parsing, that's all we need!\n\n## Step 4: the UI\n\nLike with MobX, you can use any UI library you want. I like React, so I'll use that.\n\nFirstly, I want to implement the `TodosScreenStore` - our UI state.\n\n```js\nimport { Store } from 'libx'\nimport { observable, action, computed } from 'mobx'\n\n// Most of this is plain MobX.\nclass TodosScreenStore extends Store {\n  @observable loading = false\n  @observable filter = 'ALL'\n  @observable text = ''\n\n  @computed\n  get todos() {\n    // Stores have access to the root store.\n    const { todoStore } = this.rootStore\n    const { todos } = todoStore\n    // Fun fact: collections implement a few collection functions,\n    // like `filter`, `map` and more.\n    switch (this.filter) {\n      case 'COMPLETED':\n        return todos.filter((x) =\u003e x.completed)\n      case 'INCOMPLETE':\n        return todos.filter((x) =\u003e !x.completed)\n      default:\n        return todos.slice() // coerce to array\n    }\n  }\n\n  @action\n  setText(text) {\n    this.text = text\n  }\n\n  @action\n  setLoading(loading) {\n    this.loading = loading\n  }\n\n  @action\n  setFilter(filter) {\n    this.filter = filter\n  }\n\n  // Called by the UI whenever it wants to activate\n  // this state from scratch.\n  activate() {\n    this.setLoading(true)\n    this.setText('') // clear the text on activate.\n    this.rootStore.todoStore.fetchTodos().then(() =\u003e this.setLoading(false))\n  }\n\n  addTodo() {\n    const text = this.text\n    this.setText('') // clear the text\n    return this.rootStore.todoStore.addNewTodo(text)\n  }\n\n  toggle(todo) {\n    this.rootStore.todoStore.toggleCompleted(todo.id, !todo.completed)\n  }\n}\n```\n\nThat's it for the `TodosScreenStore` - our UI state.\n\nNow for the React part.\n\n```jsx\nimport React from 'react'\nimport { render } from 'react-dom'\nimport { observer } from 'mobx-react'\n\n@observer\nclass TodosApp extends React.Component {\n  get store() {\n    return this.props.rootStore.todosScreenStore\n  }\n\n  componentWillMount() {\n    this.store.activate()\n  }\n\n  render() {\n    if (this.store.loading) {\n      return \u003cdiv\u003eLoading todos, please hold...\u003c/div\u003e\n    }\n\n    return (\n      \u003cdiv\u003e\n        \u003cinput\n          type=\"text\"\n          value={this.store.text}\n          placeholder=\"What needs to be done?\"\n          onChange={(e) =\u003e this.store.setText(e.target.value)}\n        /\u003e\n        \u003cbutton onClick={() =\u003e this.store.addTodo()}\u003eAdd\u003c/button\u003e\n        \u003cul\u003e\n          {this.store.todos.map((todo) =\u003e (\n            \u003cli key={todo.id} onClick={() =\u003e this.store.toggle(todo)}\u003e\n              \u003cp\u003e\n                {todo.text}\n                {todo.completed \u0026\u0026 \u003cspan\u003e (COMPLETED)\u003c/span\u003e}\n              \u003c/p\u003e\n              \u003cp\u003e- created by {todo.creator.name}\u003c/p\u003e\n            \u003c/li\u003e\n          ))}\n        \u003c/ul\u003e\n        \u003cselect\n          value={this.store.filter}\n          onChange={(e) =\u003e this.store.setFilter(e.target.value)}\n        \u003e\n          \u003coption value=\"ALL\"\u003eAll\u003c/option\u003e\n          \u003coption value=\"COMPLETED\"\u003eCompleted\u003c/option\u003e\n          \u003coption value=\"INCOMPLETE\"\u003eIncomplete\u003c/option\u003e\n        \u003c/select\u003e\n      \u003c/div\u003e\n    )\n  }\n}\n\nrender(\n  // Get the root store reference from somewhere.\n  \u003cTodosApp rootStore={rootStore} /\u003e,\n  document.body,\n)\n```\n\nAnd that concludes our guide. All that's left is to slap some Stripe integration on it\nand make millions. You're welcome.\n\n# API documentation\n\nThis is documentation for the exported modules.\n\n```js\nimport { collection, Model, Store, createRootStore } from 'libx'\n```\n\n## `collection([opts])`\n\nCreates a collection with the given options. The collection ensures no duplicate objects\nare inserted based on an identification field and a bit of config.\n\nThe `collection` function is used internally by `Store.collection` (which provides\naforementioned config), and can be used with plain objects. If you wish to use it\nstand-alone, go right ahead.\n\n**Params:**\n\n- `opts.idAttribute` - defaults to `\"id\"`, the property used to determine whether to insert or update an item using the default `getModelId` and `getDataId`.\n- `opts.create` - function used to transform the input object to something else. Defaults to a function that just returns the input.\n  - Signature: `(data, opts) =\u003e stuffToAddToCollection`, with `opts` being the collection options.\n- `opts.update` - function used to merge input onto an existing object. Defaults to `(existing, input) =\u003e Object.assign(existing, input)`\n  - Signature: `(existing, data, opts) =\u003e void`, with `opts` being the collection options.\n- `opts.getModelId` - function used to get the ID from an existing object in the collection. Defaults to `(model) =\u003e model[idAttribute]`.\n- `opts.getDataId` - function used to get the ID from a raw object wanting to join the collection. Defaults to `(data) =\u003e data[idAttribute]`.\n\n**Example:**\n\n```js\n// Example todo \"model\" not using LibX's Model class.\nfunction todo(props) {\n  const self = observable({\n    text: '',\n    completed: false,\n    ...props,\n    set: (data) =\u003e Object.assign(self, data),\n    toggle: () =\u003e {\n      self.completed = !self.completed\n    },\n  })\n\n  return self\n}\n\nconst todos = collection({\n  create: todo,\n  update: (existing, data) =\u003e existing.set(data),\n})\n\n// Add an item\nconst todo1 = todos.set({\n  // id = the magic sauce that makes it work\n  id: 1,\n  text: 'Install LibX',\n  completed: false,\n})\n\n// Add the same item again, just updates the current one\nconst todo1Instance2 = todos.set({\n  id: 1,\n  text: 'Install LibX and follow @jeffijoe on Twitter',\n})\n\nconsole.log(todo1 === todo1Instance2) // true\nconsole.log(todo1.text) // \"Install LibX and follow @jeffijoe on Twitter\"\nconsole.log(todo1.completed) // false\ntodo1Instance2.toggle() // it's the same instance!\nconsole.log(todo1.completed) // true\n\n// Add multiple items\ntodos.set([\n  {\n    id: 1,\n  },\n  {\n    id: 2,\n    text: 'Build a great app',\n  },\n  {\n    id: 3,\n    text: 'Profit',\n  },\n])\n\nconsole.log(todos.length) // 3, because the first was updated\n```\n\n### Collection object\n\nThe object returned from `collection()` has the following properties and functions.\n\n**Note:** All functions accepting an `opts` will have the collection's options merged into them when calling functions like `create`, `update`, `getModelId` and `getDataId`.\n\n### `collection.items`\n\nA MobX observable array of items.\n\n### `collection.length`\n\nGetter that returns the length of the `items` array.\n\n### `collection.add(models)`\n\nAdds one or more models to the end of collection (**does not call `create`**). No updating is\ndone here, existing models (based on referential equality) are not added again.\n\nSupports 2 variants: `add(model)` and `add([model1, model2])`.\n\n**Returns:** the collection.\n\n### `collection.create(data, [opts])`\n\nLike `set`, but will add regardless of whether an id is present or not.\nThis has the added risk of resulting multiple model instances if you don't make sure\nto update the existing model once you do have an id. The model id is what makes the whole\none-instance-per-entity work.\n\nSupports 2 variants: `create(obj)` and `create([obj1, obj2])`.\n\n### `collection.get(id)`\n\nGets items by ids. Supports 2 variants:\n\n- `collection.get(id)` - returns the found model, or undefined.\n- `collection.get([id1, id2])` - returns an array of found models. If a model isn't found, it is set to `undefined` in the result array (as in `[model1, undefined, model3]`).\n\nInternally uses `getModelId`.\n\n### `collection.set(data, [opts])`\n\nGiven an object or an array of objects, intelligently adds or updates models.\n\nIf a model representing the given input exists in the collection\n(_based on `getDataId` and `getModelId`_), the `update` is called. If not, the\n`create` function is called and the result is added to the internal `items` array.\n\n**Returns:** the added/existing model(s), same style as `collection.get`.\n\n### `collection.clear()`\n\nClears the internal `items` array.\n\n### `collection.remove(modelOrId)`\n\nRemoves a model based on ID or the model itself.\n\n### `collection.move(fromIndex, toIndex)`\n\nMoves an item from one index to another. Delegates to the inner Observable Array's `move` function.\n\n**Returns:** the collection.\n\n### `collection.referenceOne(ids, field?)`\n\nGiven a single or list of ids and a collection with models, returns the model(s) the IDs represent.\nIf `field` is specified, it will be used instead of the source collection model ID.\n\nOnly the first matching model per ID is returned.\nFor \"one/many-to-many\" type references, use `referenceMany`.\n\n**Returns:** the found item or null when called with a single ID, or an array when called with multiple.\n\n**Example**: bi-directional relationships using `referenceOne` and `referenceMany`.\n\n```ts\nclass Member {\n  @observable name = null\n  @observable houseId = null\n\n  @computed\n  get house() {\n    return families.referenceOne(this.houseId)\n  }\n}\n\nclass House {\n  @observable name = null\n  @observable words = null\n\n  @computed\n  get members() {\n    return members.referenceMany(this.id, 'houseId')\n  }\n}\n\nconst houses = collection({\n  model: House,\n})\n\nhouses.set([\n  {\n    id: 'lannister',\n    name: 'House Lannister',\n    words: 'A Lannister Always Pays His Debts',\n  },\n  { id: 'stark', name: 'House Stark', words: 'Winter Is Coming' },\n])\n\nconst members = collection({\n  model: Member,\n})\n\nmembers.set([\n  { id: 1, name: 'Tyrion Lannister', houseId: 'lannister' },\n  { id: 2, name: 'Jamie Lannister', houseId: 'lannister' },\n  { id: 3, name: 'Eddard Stark', houseId: 'stark' },\n])\n\nconst lannister = houses.get('lannister')\nconst stark = houses.get('lannister')\n\nconst tyrion = members.get(1)\nconst jamie = members.get(2)\nconst eddard = members.get(3)\n\nexpect(tyrion.house).toBe(lannister)\nexpect(jamie.house).toBe(lannister)\nexpect(eddard.house).toBe(stark)\n\nexpect(lannister.members).toContain(tyrion)\nexpect(lannister.members).toContain(jamie)\nexpect(lannister.members).not.toContain(eddard)\n```\n\n### `collection.referenceMany(ids, field)`\n\nGiven a single or list of ids and a collection with models, returns the models that match `field`.\n\nAll matching models are returned and flattened.\n\nFor \"one-to-one\" type references, use `referenceOne`.\n\n**Returns:** an array with found items.\n\n## The `Model` class\n\nIf you don't mind using ES6 classes, extend from this. It provides some nice things, such as...\n\n### `constructor (attributes, opts)`\n\nCalls `this.set` with the attributes and options, and also sets the `rootStore` on the model if it was passed in.\n\n**Params:**\n\n- `attributes` - an object that will get assigned onto the model using ´set`.\n- `opts` - model options. These are passed to the initial `set` as well.\n- `opts.parse` - if true, calls `this.parse(attributes, opts)` and assigns the result to the model.\n- `opts.stripUndefined` - if true, strips out any undefined values before assigning to the model.\n- `opts.rootStore` - if set, will be assigned to the model.\n\n### `.rootStore`\n\nA convenient reference to the root store (if it was passed to the constructor opts).\n\n### `.set (attributes, opts)`\n\nAssigns the attributes to the model instance.\n\nIf `opts.parse` is `true`, calls `this.parse(attributes, opts)` and assigns the result onto the object.\n\nIf `opts.stripUndefined` is `true`, removes all undefined values from the result.\n\n**Params:** same as `constructor`.\n\n**Returns:** the model instance.\n\n**Example:** transform data before assignment using `parse`\n\n```js\nclass Todo extends Model {\n  parse(attributes, opts) {\n    return {\n      ...attributes,\n      completedAt: moment(attributes.completedAt),\n    }\n  }\n}\n\nconst todo = new Todo({ text: 'Install LibX' })\n\ntodo.set(\n  {\n    completed: true,\n    completedAt: '2017-02-29T12:00:00Z',\n  },\n  {\n    parse: true,\n  },\n)\n```\n\n**Example:** strip out undefined values\n\n```js\nconst todo = new Todo({\n  text: 'Install LibX',\n})\n\ntodo.set(\n  {\n    text: undefined,\n  },\n  {\n    stripUndefined: true,\n  },\n)\n\nconsole.log(todo.text) // \"Install LibX\"\n```\n\n### `.parse (attributes, opts)`\n\nCalled by `set` when `parse: true` is passed to it. Gives the model a chance to massage the data into something it wants to work it. Commonly used to transform embedded data (denormalized) into references (normalized).\n\nThe `parse` function is responsible for 2 things:\n\n- Update any other data stores in case of embedded data\n- Return props to be merged onto the new/existing in-memory model instance.\n\n**Example:** normalization\n\nImagine there being a root store + a user store.\n\n```js\nclass Todo extends Model {\n  parse(attributes, opts) {\n    // The attributes has a `creator` object, we want to normalize it.\n    const creator = this.rootStore.userStore.users.set(attributes.creator)\n    return {\n      ...attributes,\n      creator,\n    }\n  }\n}\n\nconst todo = new Todo(\n  {\n    text: 'Install MobX',\n    creator: {\n      id: 1,\n      name: 'Jeff Hansen',\n    },\n  },\n  {\n    parse: true,\n  },\n)\n```\n\n#### Parent -\u003e Child -\u003e Parent parsing\n\nLet's say you have the following input JSON:\n\n```json\n{\n  \"id\": \"post123\",\n  \"title\": \"Upgrading your JS Life\",\n  \"author\": \"Abraham Lincoln\",\n  \"category\": {\n    \"id\": \"category123\",\n    \"name\": \"Developer Tips\",\n    \"latestPost\": {\n      \"id\": \"post123\",\n      \"title\": \"Upgrading your JS Life\"\n    }\n  }\n}\n```\n\nAnd a set of models and stores for those 2 entities (stores left out for brevity):\n\n```js\nclass Post extends Model {\n  parse({ category, ...json }) {\n    return {\n      ...json,\n      category: this.rootStore.categoryStore.categories.set(category),\n    }\n  }\n}\n\nclass Category extends Model {\n  parse({ latestPost, ...json }) {\n    return {\n      ...json,\n      latestPost: this.rootStore.postStore.posts.set(latestPost),\n    }\n  }\n}\n```\n\nIn LibX 0.1.x, this would wrongfully result in 2 `Post` instances, because:\n\n- Check if we have a post with id `post123`\n  - We don't, parse the data into a new `Post` instance\n    - Check if we have a category with id `category123`\n      - We don't, parse the data into a new `Category` instance\n        - Check if we have a post with id `post123`\n          - We don't, parse the data into a new `Post` instance\n          - Add the created `Post` to the collection\n      - Add the created `Category` to the collection\n  - Add the created `Post` to the collection\n\nAs of version 0.2.0, parsing a 3+ level deep parent-\u003echild-\u003eparent structure no longer results in duplicate models.\nThis works by checking the collection _after parsing_ to see if a model with the same ID was added to the collection.\nIf it was, parse the data _again_ but while _updating the existing model_.\n\n## The `Store` class\n\nIf you don't mind using ES6 classes, you'll get a lot by using the `Store` as a base class for your stores:\n\n- it sets a reference to the `rootStore`\n- it has a nice `collection()` function.\n\n### `store.collection(opts)`\n\nCreates a [`collection`](#collection-object), but configured for use with\na [`Model`](#the-model-class). It also passes in the store's `rootStore` reference to any created models.\n\n**Params:**\n\n- `opts` - options passed to `collection(opts)`\n- `opts.model` - class to instantiate for new models\n- `opts.stripUndefined` - default is now `true`\n- `opts.parse` - default is now `true`\n\n**Returns:** a collection.\n\n**Example:**\n\n```js\nclass TodoStore extends Store {\n  // ES7 property initializer\n  todos = this.collection({\n    model: Todo,\n    stripUndefined: false,\n  })\n}\n\nconst root = { awesome: true }\nconst store = new TodoStore({ rootStore: root })\nconst todo = store.todos.set({ id: 1, text: 'Install LibX' })\n\nconsole.log(todo instanceof Todo) // true\nconsole.log(todo.rootStore.awesome) // true\n```\n\n## `createRootStore(obj)`\n\nEasiest way to create a simple root store. A root store, as described earlier, isn't that magical; it's just an object that holds references to all other stores.\n\n**Params:**\n\n- `obj` - example: `{ userStore: UserStore }`\n\n**Returns:** the root store object.\n\n**Example:**\n\n```js\nclass UserStore extends Store { ... }\n\nclass TodoStore extends Store { ... }\n\nconst rootStore = createRootStore({\n  userStore: UserStore,\n  todoStore: TodoStore\n})\n\nconsole.log(rootStore.userStore instanceof UserStore) // true\n```\n\n# Projects using LibX\n\n- [Posish](https://posish.now.sh) ([GitHub repo](https://github.com/jeffijoe/posish)) — a tool to generate code based on string positions\n\nAre you using LibX in your project and want it listed here? Submit a PR! :rocket:\n\n# See Also\n\n- [validx][validx] - MobX validation library\n- [mobx-task][mobx-task] - Async operation state management\n\n# Author\n\nJeff Hansen - [@Jeffijoe](https://twitter.com/Jeffijoe)\n\n[ts-example]: /examples/typescript\n[babel-example]: /examples/babel\n[validx]: https://github.com/jeffijoe/validx\n[mobx-task]: https://github.com/jeffijoe/mobx-task\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjeffijoe%2Flibx","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fjeffijoe%2Flibx","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjeffijoe%2Flibx/lists"}