{"id":25731903,"url":"https://github.com/codeship/modelist","last_synced_at":"2025-05-07T18:41:22.137Z","repository":{"id":57103157,"uuid":"115017497","full_name":"codeship/modelist","owner":"codeship","description":"Flexible \u0026 Customizable Modelstructure for awesome data management.","archived":false,"fork":false,"pushed_at":"2018-12-07T11:37:33.000Z","size":291,"stargazers_count":67,"open_issues_count":0,"forks_count":5,"subscribers_count":1,"default_branch":"master","last_synced_at":"2025-04-16T21:24:39.267Z","etag":null,"topics":["collection","data-structure","functional-programming","js","model","modelist","react","reactive","redux","vue","vuex"],"latest_commit_sha":null,"homepage":"https://www.npmjs.com/package/@codeship/modelist","language":"JavaScript","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/codeship.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":"CODE_OF_CONDUCT.md","threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2017-12-21T15:00:23.000Z","updated_at":"2025-02-28T03:01:50.000Z","dependencies_parsed_at":"2022-08-21T00:30:16.345Z","dependency_job_id":null,"html_url":"https://github.com/codeship/modelist","commit_stats":null,"previous_names":[],"tags_count":15,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/codeship%2Fmodelist","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/codeship%2Fmodelist/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/codeship%2Fmodelist/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/codeship%2Fmodelist/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/codeship","download_url":"https://codeload.github.com/codeship/modelist/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":252936125,"owners_count":21828084,"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":["collection","data-structure","functional-programming","js","model","modelist","react","reactive","redux","vue","vuex"],"created_at":"2025-02-26T03:38:23.653Z","updated_at":"2025-05-07T18:41:22.112Z","avatar_url":"https://github.com/codeship.png","language":"JavaScript","readme":"# Modelist\n\nFlexible \u0026 Customizable Modelstructure for awesome data management.\n\n## What is Modelist\n\nModelist is a client based collection model that makes handling data surprisingly convenient.\nIt works great in reactive systems like `Vue` and `React` or flux stores like `Vuex` or `Redux`.\nCommon tasks like recording, updating, deleting and selecting are made easy.\n\nFor Real-life examples checkout the [Examples](#examples) section.\n\n## Contributing\n\nContributions to this project are very welcome. Please read the [Contributing.md](CONTRIBUTING.md) document first.\n\n## Table of Contents:\n\n1.  [Installation](#installation)\n2.  [Basic Usage](#usage)\n3.  [The Entry Object](#entry)\n4.  [Customize a Model](#customize)\n    * [Add custom filters](#customize-filters)\n    * [Customize the Entry Object](#customize-entry)\n    * [Record standard Arrays](#arrays)\n    * [Use Schemas](#schema)\n5.  [Examples](#examples)\n    * [Using Modelist with Vuex](#example-vuex)\n6.  [Meta](#meta)\n\n## Installation \u003ca name=\"installation\"\u003e\u003c/a\u003e\n\n`Modelist` can be added to your project with ease.\n\n```sh\nnpm install -D @codeship/modelist\n\n# or\n\nyarn add @codeship/modelist\n```\n\n## Basic Usage \u003ca name=\"usage\"\u003e\u003c/a\u003e\n\nAfter adding `modelist` to your project, usage is quite simple.\n\n```js\nimport Model from \"@codeship/modelist\";\n```\n\nThis gives you access to the Model contructor. That one can be used as is to have direct access to the everything `Modelist` brings to the table. Create a new instance, and start using it right away.\n\n```js\nconst users = new Model();\n```\n\n**That's it**. Nothing more that needs to be done to get access to a new collection and all the default methods that come with it.\n\n**Add entries to the collection**\n\n```js\nusers.record({ id: 1, name: \"Jane Doe\" });\n```\n\nYou can also record singe values like a `Number` or a `String`.\n`Modelist` will convert those into objects for you. [-\u003e Record standard Arrays](#arrays)\n\n**Get all entries of the collection**\n\n```js\nusers.all();\n```\n\n**Retrieve the first element of the collection**\n\n```js\nusers.first();\n```\n\n**Retrieve the last element of the collection**\n\n```js\nusers.last();\n```\n\n**Check if the collection has a record based on the primary key**_(default: id)_\n\n```js\nusers.has(1);\n```\n\n**Retrieve one specific element of the collection based on the primary key**_(default: id)_\n\n```js\nusers.find(1);\n```\n\n**Retrieve one specific element of the collection based on a custom key**\n\n```js\nusers.findBy(\"name\", \"Jane Doe\");\n```\n\n**Update an entry based on the primary key**\nOnly the values that should be changed need to get passed in\n\n```js\nusers.update(1, { name: \"Jane Goodall\" });\n```\n\n**Replace entries based on the primary key**\nIf you want to update multiple entries at once you can leverage `replace`.\nNotice though that this will really replace the element and not merge the existing values.\n\n```js\nusers.replace(1, { name: \"Jane Goodall\" });\n```\n\nShould replace be called on an element that can't be matched it will be recorded instead.\nIf you want to record elements though, `record` is recommended as it's faster.\n\n**Remove an entry from the collection**\n\n```js\nusers.destroy(1);\n```\n\n**Get the number of elements in the collection**\n\n```js\nusers.size;\n```\n\n**$$reset**\nThis method will reset the Model and drop every data that's currently stored in the collection.\n\n```js\nusers.$$reset();\n```\n\nInternally all collection operations `destroy`, `record` and `update` use default Array methods, so reactive systems can track changes to the collection.\n\n## Single Entries \u003ca name=\"entry\"\u003e\u003c/a\u003e\n\nUsually there is something that needs to be done with an entry of the collection when it gets retrieved on it's own.\nTherfore `Modelist` will wrap that result in an `Entry1 object.\nSingle entries aim to support functional composition. Entries come with default functionality that can be used to alter the value you received.\nLet's see this in action right away:\n\n```js\nconst fruits = new Model();\nfruits.record({ id: 1, name: \"Banana\" });\n\n// This will return the Entry object\nconst entry = fruits.find(1);\n\n// Inspect a entry by using .toString() for easy debugging\nentry.toString(); //= Entry({ id: 1, name: 'Banana' })\n\n// By default an Entry comes with two functions\n// map() to map over the current value and wrap the output in another Entry\n// fold() to get the value out of the entry\n\nentry.fold(); //= {id: 1, name: 'Banana'}\n\n// Let's say you want only the name\nentry.fold(e =\u003e e.name); //= 'Banana'\n\n// What if you want to change the name before folding\nentry.map(e =\u003e e.name.toUpperCase()).fold(); //= 'Banana'\n\n// What happened here is, that we actually only took one value of the object\n// and transformed it. We can still fold it afterwards as we receive a wrapped Entry.\n// This gives you full control over the flow of what's happening with your entries\n```\n\nYou can even add custom methods to your entries, so let's see how to configure the model.\n\n## Customize a Model \u003ca name=\"customize\"\u003e\u003c/a\u003e\n\nThe Model comes with a bunch of preconfigured values that should make live easy and get you started quickly without making you jump through a bunch of hoops. But sometimes those values are not right, and that's why you can configure a model instance to your liking\nLet's stary easy by changing the primaryKey.\n\n```js\nconst products = new Model({\n  primaryKey: \"secret_id\"\n});\n```\n\nNow products will use `secret_id` as main identification for all the `find`, `update` and `destroy` methods.\nHere is a full list of available params:\n\n```js\n{\n  // Change the primaryKey\n  primaryKey: 'custom',\n\n  // Enforce a primary key to be set on each record by Modelist (uuid/v4)\n  // default: false\n  setPrimaryKey: true\n\n  // Enforce that every entry is matched against the schema\n  // default: false\n  validate: false,\n\n  // Schema can be defined where each entry will be matched against\n  schema: {\n    custom: 'String'\n  },\n\n  // Pass in data by default\n  data: [...],\n\n  // Add custom filters to the collection that will be made available\n  // as getters on the root of the model.\n  filters: {\n    // i.e.: return all completed tasks of a task list\n    // This will also give you access to the subset of the collection as a fresh Model instance\n    completed: (collection) =\u003e collection.filter( item =\u003e item.done === true)\n  },\n\n  // Add methods to entries\n  methods: {\n    customMethod: (e) =\u003e {\n      e.amount--\n      return e\n    },\n  }\n}\n```\n\n### Extending the model with custom filters \u003ca name=\"customize-filters\"\u003e\u003c/a\u003e\n\nSometimes you want specific subsets of all the stored data of your Model.\nThat's a perfect opportunity to add filters to your instance.\n_Filters_ are custom methods that allow you to retrieve a subset of the passed in collection.\nThe result is than passed down for you as a fresh `Model` instance.\n\n**Some caveat**:\nThe new instance gives you access to all core functions but without the filters.\nSo it's not possible to call a filter method and chain another custom filter.\n\n```js\nconst tasks = new Model({\n  data: [\n    { task: \"Go for a walk\", done: false },\n    { task: \"Buy Milk\", done: true },\n    { taks: \"Feed the cat\", done: true }\n  ],\n  filters: {\n    completed: c =\u003e c.filter(t =\u003e t.done === true)\n  }\n});\n\nconsole.log(tasks.completed.size); //= 2\nconsole.log(tasks.completed.all()); //= [{task: 'Buy Milk', done: true}, {taks: 'Feed the cat', done: true}]\n```\n\n### Using custom methods for entries \u003ca name=\"customize-entry\"\u003e\u003c/a\u003e\n\nIf you passed in methods to the Model, they're made available for you on the Entry object.\nBy default those methods will be applied as folds so they just return whatever the function returns.\n\n```js\nconst fruits = new Model({\n  primaryKey: \"name\",\n\n  data: [{ name: \"Apple\" }],\n\n  methods: {\n    getSaladName: e =\u003e `${e.name} Salad`\n  }\n});\n\nfruits.find(\"Apple\").getSaladName(); //= Apple Salad\n```\n\nBut you can actually also map over the value and get another Entry should you want to do something else or chain various methods. In this case the method name needs to be passed as a String.\n\n```js\nfruits\n  .find(\"Apple\")\n  .map(\"getSaladName\")\n  .toString(); //= Entry('Apple Salad')\n```\n\n## Record standard Arrays \u003ca name=\"arrays\"\u003e\u003c/a\u003e\n\nIn some instances the data is not prepared as an array of objects, but simple values. `Modelist` allows you to record simple values alongside of objects. If you pass in an simple value, it will be transformed into an object with an unique ID based on the `primaryKey` and the `value` stored alongside it.\n\n```js\nmodel.record(...[1, 'fred'])\n\nmodel.all() // [{ id: uuid(), value: 1 }, { id: uuid(), value: 'fred' }]\n```\n\n\n### Schemas \u003ca name=\"schemas\"\u003e\u003c/a\u003e\n\nSchemas allow defining how the root data structure of each data sent into the collection should look like.\nIt support primiteves like `Number`, `String`, `Boolean`, `Object`, `Array` or event custom types.\n\nTo leverage the schema validation, just add the `validation` key to your Model.\nFor example you can successfully match this data:\n\n```js\nconst data = {\n  id: 1,\n  name: \"Jane\",\n  favs: [\"a\", \"b\"],\n  settings: {},\n  custom: new Custom()\n};\n```\n\nagainst this schema\n\n```js\nconst schema = {\n  id: Number,\n  name: String,\n  favs: Array,\n  settings: Object,\n  custom: Custom\n};\n```\n\nIf something is not fitting that schema it will print out a warning, but still try to process the data.\n\n## Examples \u003ca name=\"examples\"\u003e\u003c/a\u003e\n\n### Use Modelist within in Vuex \u003ca name=\"example-vuex\"\u003e\u003c/a\u003e\n\nLet's see a simple example how to use Modelist within in Vuex.\nThis makes mutations, and actions way more convenient, but can also make your components a little more structured.\n\n```js\nimport Model from \"@codeship/modelist\";\nimport uuid from \"uuid/v1\";\n\nexport default {\n  state: {\n    tasks: new Model()\n  },\n\n  mutations: {\n    recordTask(state, entry) {\n      state.tasks.record(entry);\n    },\n\n    updateTask(state, id, data) {\n      state.tasks.update(id, data);\n    }\n  },\n\n  actions: {\n    addTask({ commit }, task) {\n      const newTask = { id: uuid(), body: task, done: false };\n      commit(\"recordTask\", newTask);\n    },\n\n    completeTask({ commit }, task) {\n      commit(\"updateTask\", task.id, { done: true });\n    },\n\n    changeTask({ commit }, task, body) {\n      commit(\"updateTask\", task.id, { body });\n    }\n  },\n\n  getters: {\n    tasks: state =\u003e state.tasks\n  }\n};\n```\n\nEventually you can structure the store in a very convenient way and, see that the `updateTask` mutation can actually be used for two different actions.\nFor the getter, it's very easy to just return the whole collection and give the components access to it's internal functions if you like.\nOn the client this could look like this:\n\n```html\n\u003cul\u003e\n  \u003ctask v-for=\"task in tasks.all()\" :task=\"task\"\u003e\n\u003c/ul\u003e\n```\n\nThe important piece here is, that by using the whole task collection, `task` will be the plain object for your convenience.\n\n## Meta \u003ca name=\"meta\"\u003e\u003c/a\u003e\n\nThis Project is managed by [Roman Kuba \u003crkuba@cloudbees.com\u003e](mailto:rkuba@cloudbees.com) from the Codeship Team.\nYou can best reach me on Twitter [@Codebryo](https://twitter.com/Codebryo).\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcodeship%2Fmodelist","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fcodeship%2Fmodelist","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcodeship%2Fmodelist/lists"}