{"id":20252457,"url":"https://github.com/ivandotv/fuerte","last_synced_at":"2025-04-10T23:21:15.404Z","repository":{"id":41416069,"uuid":"369639616","full_name":"ivandotv/fuerte","owner":"ivandotv","description":"Mobx powered library inspired by the best parts of Backbone.js and Ember.js","archived":false,"fork":false,"pushed_at":"2025-03-15T16:59:49.000Z","size":1006,"stargazers_count":3,"open_issues_count":18,"forks_count":0,"subscribers_count":1,"default_branch":"master","last_synced_at":"2025-03-24T20:11:12.448Z","etag":null,"topics":["ddd","ddd-architecture","domain-driven-design","mobx","mobx-react"],"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/ivandotv.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,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2021-05-21T19:49:37.000Z","updated_at":"2022-10-03T13:25:21.000Z","dependencies_parsed_at":"2025-03-15T17:38:24.460Z","dependency_job_id":null,"html_url":"https://github.com/ivandotv/fuerte","commit_stats":{"total_commits":177,"total_committers":5,"mean_commits":35.4,"dds":"0.10169491525423724","last_synced_commit":"fb237e1f6cf76edb47e5d426eae9d946b35a8f61"},"previous_names":[],"tags_count":22,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ivandotv%2Ffuerte","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ivandotv%2Ffuerte/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ivandotv%2Ffuerte/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ivandotv%2Ffuerte/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/ivandotv","download_url":"https://codeload.github.com/ivandotv/fuerte/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248313059,"owners_count":21082796,"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":["ddd","ddd-architecture","domain-driven-design","mobx","mobx-react"],"created_at":"2024-11-14T10:16:55.135Z","updated_at":"2025-04-10T23:21:15.375Z","avatar_url":"https://github.com/ivandotv.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Fuerte\n\n[![Test](https://github.com/ivandotv/fuerte/actions/workflows/CI.yml/badge.svg)](https://github.com/ivandotv/fuerte/actions/workflows/CI.yml)\n[![Codecov](https://img.shields.io/codecov/c/gh/ivandotv/fuerte)](https://app.codecov.io/gh/ivandotv/fuerte)\n[![GitHub license](https://img.shields.io/github/license/ivandotv/fuerte)](https://github.com/ivandotv/fuerte/blob/main/LICENSE)\n\nMobx powered library inspired by the best parts of Backbone.js and Ember.js\n\n\u003c!-- toc --\u003e\n\n- [Motivation](#motivation)\n- [Installation](#installation)\n- [Usage](#usage)\n  - [Model](#model)\n  - [Factory](#factory)\n  - [Transport](#transport)\n  - [Collection](#collection)\n  - [Putting it all together](#putting-it-all-together)\n- [Model](#model-1)\n  - [Properties](#properties)\n  - [Methods](#methods)\n  - [Callbacks](#callbacks)\n- [Collection](#collection-1)\n  - [Collection concepts](#collection-concepts)\n    - [Data loading](#data-loading)\n    - [Reset](#reset)\n  - [Callbacks](#callbacks-1)\n- [Autosave Collection](#autosave-collection)\n  - [Callbacks](#callbacks-2)\n- [Transport](#transport-1)\n- [Recipes](#recipes)\n  - [Use composition instead of inheritance](#use-composition-instead-of-inheritance)\n  - [Restufl transport](#restufl-transport)\n\n\u003c!-- tocstop --\u003e\n\n## Motivation\n\nIt started as an experiment in using domain-driven design and separation of concerns in the frontend.\nThe idea was to separate the Model, Model Collection, and Persistence (Transport) into three distinct parts.\n\n- Model should be an object that mostly just carries data\n- Collection should implement business logic, and act as aggregation root for the Model\n- Transport should only be concerned with persisting data.\n\nAfter some trial and error, I've ended up with a library that I think, satisfies these concerns.\n\nThe `Collection` part of the library is inspired by [Backbone.js](https://backbonejs.org/#Collection) while the `Model` part of the library is inspired by [Ember.js](https://guides.emberjs.com/release/models/#toc_what-are-ember-data-models)\n\n[Mobx](mobx.js.org/) is used for the reactivity so we don't concern ourselves with integrating the library with different frontend frameworks.\n\nThis abstraction on top of the Mobx will cost you an additional [~4.3KB](https://bundlephobia.com/package/@fuerte/core), and I think it's worth it.\n\n## Installation\n\n```sh\nnpm i @fuerte/core\n```\n\n## Usage\n\nTo showcase the basic usage and capabilities of the library, we are going to create a simple **todo** app.\n\n- Todo `Model` (todo item)\n- Model collection (`Collection` of todo items)\n- Factory function for creating new models\n- Transport for persisting the models ( local storage, REST API, etc..)\n\n### Model\n\nFirst, we will create a `Model`.\nModels usually carry the data, but they can also have their methods to manipulate the data or perform other tasks.\n\nThe `model` will have an `id`, `done`, and a `task` properties.\n\nEvery `model` needs to have a `serialize` method which will be used to serialize the model to storage, and track if the model is _dirty_ (has changed properties).\n\n```ts\nimport {Model} from '@fuerte/core`\nimport { makeObservable, action, observable } from 'mobx'\n\nclass Todo extends Model {\n  public done: boolean\n\n  constructor(public task: string, public id?: number) {\n    super()\n\n    this.done = false\n\n    makeObservable(this, {\n      task: observable,\n      done: observable,\n      setTask: action,\n    })\n  }\n\n  //data to be saved when the model is persisted\n  serialize() {\n    return {\n      id: this.id,\n      task: this.task,\n      done: this.done,\n    }\n  }\n\n  setTask(task: string) {\n    this.task = task\n  }\n}\n\n```\n\n### Factory\n\nNext, we need a `model factory`. This is a function whose purpose is to create the new `Model` instance, the function can be asynchronous.\n\n```ts\nimport type { FactoryFn } from '@fuerte/core'\n\nexport type ModelDTO = { task: string; id?: number }\n\nconst modelFactory: FactoryFn = (data: ModelDTO) =\u003e {\n  return new Todo(data.task, data.id ?? Math.random())\n}\n```\n\n### Transport\n\nNext, we create the transport layer. The transport layer is used for transporting (persisting) the data. Usually, the transport layer will use `fetch`, `localStorage`, or `IndexedDB`. In this simple example, we are going to save the data _in memory_.\n\nTransport class needs to implement `Transport` interface, which requires three methods:\n\n- `load`: loads the data from somewhere, this is usually used when the app first starts. This method **does not create the model instances**, it just returns the raw data (`ModelDTO` in the model section) that will later be used by the factory function to construct the models.\n- `save`: for saving or updating the model\n- `delete`: for deleting the model\n\n```ts\nimport type { Transport } from '@fuerte/core'\n\n//sample data\nconst firstTodo = { id: 1, task: 'Remember the milk' }\nconst secondTodo = { id: 2, task: 'Return books to the library' }\n\n//simple in memory storage\nconst storage: Map\u003cstring, ModelDTO\u003e = new Map()\n\n//populate the storage\nstorage.set(firstTodo.id, firstTodo)\nstorage.set(secondTodo.id, secondTodo)\n\n//transport implementation\nexport class MemoryTransport implements Transport {\n  load(): Promise\u003c{ data: ModelDTO[] }\u003e {\n    // return everything from the storage\n    return Promise.resolve({ data: [...storage.values()] })\n  }\n\n  save(model: Todo) {\n    storage.set(model.id, model.payload)\n  }\n\n  delete(model: Todo) {\n    storage.delete(model.id)\n  }\n}\n```\n\n### Collection\n\nOne final missing piece and also the most important one is the `Collection`.\n\nBusiness logic should generally be set on the collection. You can think of the collection as the [`Mobx store`](https://mobx.js.org/defining-data-stores.html#defining-data-stores).\n\nTo construct the `Collection` class instance we need both the `factory` and the `transport`.\n\n```ts\nimport { Collection } from '@fuerte/core'\nimport { makeObservable computed } from 'mobx'\nimport {modelFactory} from './modelFactory'\nimport {MemoryTransport} from './memoryTransport'\n\nclass TodoCollection extends Collection\u003c\n  Todo,\n  typeof modelFactory,\n  MemoryTransport\n\u003e {\n  constructor(factory: typeof modelFactory, transport: MemoryTransport) {\n    super(factory, transport)\n\n    makeObservable(this, {\n      doneTasks: computed\n    })\n  }\n\n  get doneTasks() {\n    return this.models.filter((model) =\u003e model.done === true)\n  }\n}\n```\n\n### Putting it all together\n\nNow that we have all the pieces that we need. We can build our Todo app.\n\n```ts\nimport { modelFactory } from './modelFactory'\nimport { MemoryTransport } from './memoryTransport'\nimport { TodoCollection } from './todoCollection'\n\n// create the collection instance\nconst collection = new TodoCollection(modelFactory, MemoryTransport)\n\n//load all the models (calls MemoryTransport.load under the hood)\nconst loadResult = await collection.load()\n\n//get first task from the collection\nconst todoOne = collection.models[0]\ntodoOne.task // 'remember the milk'\n\ntodoOne.isDirty // false (model hasn't changed)\n\ntodoOne.setTask('remember the icecream instead')\ntodoOne.isDirty //true (model.task has changed)\n\n//save the model to the storage (MemoryTransport)\nconst result = await todoOne.save()\n\n//\"done\" tasks\ncollection.doneTasks // []\n\ntodoOne.done = true\ntodoOne.isDirty // true - model is dirty again\n\ncollection.doneTasks // [todoOne]\n\n//Let's create a completely new todo\nconst newTodo = collection.create({ id: '3', task: 'buy cat food' })\nnewTodo.isNew //true\nnewTodo.isDirty // false Note new models are not dirty!\n\n//just add the model to the collection without saving it (Transport.save is not called)\ncollection.add(newTodo)\n\n//save the model (this will not add the model a second time, it will just save it via Transport.save)\nawait collection.save(newTodo)\n```\n\nAnd that is the gist of it. It's important to note that both the `collection` and the `model` have properties that are **reactive** (via Mobx) so you can use them directly in your React components, and the components will be rendered when the data is changed. All standard Mobx rules apply.\nSome of the react properties are\n\n```ts\nmodel.isSaving // true - if the model is in the process of saving\nmodel.isSyncing //true - if model is saving or deleting\n\ncollection.isSyncing // true - the collection has models that are currently saving or deleting\n\ncollection.saving // [model] - returns all the models that are currently in the process of saving\n\ncollection.syncing //[model] - returns all the models that are currently deleting or saving\n\nmodel.isDirty // when model data differs from the last saved data\n```\n\nAll these properties are documented at the [Model properties](#properties) and [Collection properties](#properties) sections of the readme.\n\n## Model\n\nModel is generally used to carry data, although there is no reason not to carry its own logic in form of custom methods.\n\nThe two most important pieces of the Model class are the `serialize` method and the `indentityKey` static property.\n\n`serialize` method is used to determine what properties of the object will be used by the transport for saving the object and for determining if the model `isDirty`(has changed properties). This method **must** be implemented by every class that extends the `Model` class.\n\ncollection. `Identity key` should be heavily used by the `transport` layer, for persisting the models.\n\n`indentityKey` is a static property that holds the `name` of the property that will be **unique** for every model. This unique property is used by the collection to check if the model with the same value is already present, remember `Collection` can only have models with unique values for `identityKey`.\n\n`Transport` layer can also use this key to persist data. In case you have some kind of transport that adheres to REST principles the transport can then construct API endpoints by utilizing the `identityKey`.\nFor example:\n\n```sh\nPOST /todos/${todo.identity}\nDELETE /todos/${todo.identity}\n```\n\n\u003e Check out the [Fetch transport recepie](#fetch-transport)\n\nIn the next example, `Book` model has the `isbn` property set as the `indentityKey`\n\n```ts\nclass Book extends Model {\n  static indentityKey = 'isbn'\n\n  constructor(public isbn: string) {}\n}\n\nconst book = new Book('123')\n\ncollection.add(book)\ncollection.getByIdentity(book.isbn) // book\n\nbook.getIdentityKey() // isbn\nbook.identity // 123\n```\n\n### Properties\n\nAll model _getter_ properties are reactive (via Mobx). And they reflect the state of the model.\n\n- `isSaving`: true when the model is in the process of saving.\n- `isDeleting`: true when the model is in the process of deleting.\n- `isSyncing`: true if the model is either saving or deleting.\n- `isDeleted`: if the model is deleted by transport.\n- `isDestroyed`: true if the model `destroy` method has been called.\n- `identity`: model identity value.\n- `indentityKey`: model identity key.\n- `cid`: model client identity (used internally by collection).\n- `payload`: data that is returned by the `serialize` method. The transport layer should use this property to save the model.\n- `isDirty`: true when the current model data is different than the last model data that has been saved.\n- `saveError`: error when the `Transport.save` method fails to save the model.\n- `deleteError`: error `Transport.delete` method fails to delete the model.\n- `hasErrors`: true if either `saveError` or `deleteError` is truthy.\n- `lastSavedData`: last successfully saved data.\n\n### Methods\n\n- `setIdentity(newValue:string)`: set new identity value (`indentityKey` value will be changed)\n- `setIsNew(isNew:boolean)`: Set `isNew` property on the model. This method should generally not be used by the client code. The transport layer can check this property to determine if it should use `POST` or `PATCH` methods for persistence\n- `getCollection` - returns the `Collection` that the model is part of. This could be **undefined** if the model is created but not yet added to the collection.\n- `destroy()`: stops model internal processes. This method should be used when you want to completely remove the model from the app and release the memory used by the model.\n\n### Callbacks\n\nThe model supports various callbacks in the form of methods on a class. It's important to note that you **should not call** `super[method name]` on any of the callbacks. They are all fire and forget.\n\n```ts\nimport type {\n  ModelDeleteErrorCallback,\n  ModelDeleteStartCallback,\n  ModelDeleteSuccessCallback,\n  ModelSaveErrorCallback,\n  ModelSaveStartCallback,\n  ModelSaveSuccessCallback\n} from '@fuerte/core'\n\nimport { TodoCollection } from './TodoCollection'\nimport { TodoTransport } from './TodoTransport'\n\nexport class TodoModel extends Model\u003cTodoCollection\u003e {\n  // called when the model is added to the collection.\n  override onAdded(collection: TodoCollection): void {}\n\n  // called when the model is removed from the collection\n  override onRemoved(collection: TodoCollection): void {}\n\n  // called when model is about to be saved by the collection\n  override onSaveStart(data: ModelSaveStartCallback\u003cTodoTransport\u003e): void {}\n\n  //called when model is successfully saved by the collection\n  override onSaveSuccess(data: ModelSaveSuccessCallback\u003cTodoTransport\u003e): void {}\n\n  //called when collection has failed to save the model\n  override onSaveError(\n    data: ModelSaveErrorCallback\u003cTodoModel, TodoTransport\u003e\n  ): void {}\n\n  // called when collection is about to delete the model\n  override onDeleteStart(data: ModelDeleteStartCallback\u003cTodoTransport\u003e): void {}\n\n  // called when collection has successfully deleted the model\n  override onDeleteSuccess(\n    data: ModelDeleteSuccessCallback\u003cTodoTransport\u003e\n  ): void {}\n\n  // called when collecton has failed to delete the model\n  override onDeleteError(data: ModelDeleteErrorCallback\u003cTodoTransport\u003e): void {}\n\n  // called when the model is destroyed. `Model.destroy() method has been called\n  override onDestroy(): void {}\n}\n```\n\nFor more info check out the [Model API docs](./packages/core/docs/api/classes/Model.md)\n\n## Collection\n\nCollection class as the name suggests is used for collecting (manipulating) the models and acting as an aggregation root for the models. It has methods like `add`, `remove`, `save` etc...\n\nBusiness logic that concerns the models in the collection should generally be set on the collection. You can think of the collection as the [Mobx store](https://mobx.js.org/defining-data-stores.html#defining-data-stores).\n\nThe collection constructor has three dependencies.\n\n- factory: used for creating the models\n- transport: used for persisting the models\n- configuration: an optional configuration that determines the default behavior for the collection\n\n```ts\nimport { Collection } from '@fuerte/core'\n\nimport { todoFactory } from './todoFactory'\nimport { TodoModel } from './TodoModel'\nimport { TodoTransport } from './TodoTransport'\n\nexport class TodoCollection extends Collection\u003c\n  TodoModel,\n  typeof todoFactory,\n  TodoTransport\n\u003e {}\n\nconst todoCollection = new TodoCollection(todoFactory, new TodoTransport())\n\n//create uses the factory function internally\nconst newTodo = todoCollection.create({ task: 'Buy milk' })\n\n//save uses the transport  class internally\ntodoCollection.save(newTodo)\n```\n\n### Collection concepts\n\nThere are a few key concepts for working with the `Collection`.\n\n#### Data loading\n\nWhen you first start your app, the collection will probably be empty. You need a way to immediately populate the collection when the app starts. You can use the `Collection.load` method for that.\n\nWhen you call the `Collection.load` method, under the hood it will call the `Transport.load` method.\n\n`Transport.load` method should return **raw** model data (that will be directly passed to the `modelFactory` function), then that data is iterated over and passed to the `modelFactory` function, which in turn creates `Model` instances.\n\n`Collection.load` method also returns a Promise that resolves to all of the models that were created and added.\n\n```ts\nconst todoCollection = new TodoCollection(todoFactory, new TodoTransport())\n\nconst result = await todoCollection.load()\n//result.added - new models\n```\n\nIn the case of calling `load` multiple times and loading the same model data (models with the same value for the `indentityKey` property), there will be an issue when the model with the same value is already present in the collection and you will need to decide what to do with that model when that happens, you can _keep the old_ model, _keep the new_ model, or _keep both_.\n\n```ts\nimport { DuplicateModelStrategy, ModelCompareResult } from '@fuerte/core'\n\ncollection.load({\n  //keep the new models\n  duplicateModelStrategy: DuplicateModelStrategy.KEEP_NEW\n})\n\ncollection.load({\n  //keep the model that is already present in the collection\n  duplicateModelStrategy: DuplicateModelStrategy.KEEP_OLD\n})\n\ncollection.load({\n  // compare new and old model and then decide\n  duplicateModelStrategy: DuplicateModelStrategy.COMPARE,\n  compareFn: (newModel: TodoModel, oldModel: TodoModel) =\u003e {\n    //here you have access to the new and old models\n\n    // keep the new model\n    return ModelCompareResult.KEEP_NEW\n\n    // keep the old model\n    return ModelCompareResult.KEEP_OLD\n\n    // keep both models!\n    return ModelCompareResult.KEEP_BOTH\n  }\n})\n```\n\nIn the case of the `ModelCompareResult.KEEP_BOTH` you need to make sure to change the identity of one of the models, otherwise `Collection.load` will throw an error. As mentioned earlier, there can't be two models in the collection with the same value for `identity` property.\n\nThere is also an option to empty the collection (reset) before adding new models. After the `load` method completes, only newly loaded models will be present in the collection.\n\n```ts\nconst result = todoCollection.load({ reset: true })\n```\n\n#### Reset\n\nAny time in the lifecycle of the app you can empty the collection (reset) and optionally add new models by providing data for the factory.\n\n```ts\n//empty the collection\ncollection.reset()\n\n//empty the collection and add new models\ncollection.reset([{ task: 'Buy crypto' }, { task: 'by cinema tickets' }])\n```\n\n### Callbacks\n\nThere are various callbacks in form of methods on a class. It it's important to note that you **should not call** `super[method name]` on any of the callbacks. They are all fire and forget.\n\n```ts\nimport {\n  DeleteErrorCallback,\n  DeleteStartCallback,\n  DeleteSuccessCallback,\n  FactoryData,\n  LoadErrorCallback,\n  LoadStartCallback,\n  LoadSuccessCallback,\n  SaveErrorCallback,\n  SaveStartCallback,\n  SaveSuccessCallback\n  Collection\n} from '@fuerte/core'\nimport { todoFactory } from './todoFactory'\nimport { TodoModel } from './TodoModel'\nimport { TodoTransport } from './TodoTransport'\n\nexport class TestCollection extends Collection\u003c\n  TodoModel,\n  typeof todoFactory,\n  TodoTransport\n\u003e {\n\n  //called when collecton is reset\n  override onReset(\n    added: TodoModel[],\n    removed: TodoModel[],\n    fromLoad = false\n  ): void {}\n\n  //called when the model is removed from the collection\n  override onRemoved(model: TodoModel): void {}\n\n  //called when the model is added to the collection\n  override onAdded(model: TodoModel): void {}\n\n  //called when model save process is about to start\n  override onSaveStart(\n    data: SaveStartCallback\u003cTodoModel, TodoTransport\u003e\n  ): void {}\n\n  //called when the model is saved successfully\n  override onSaveSuccess(\n    data: SaveSuccessCallback\u003cTodoModel, TodoTransport\u003e\n  ): void {}\n\n  //called when model save process fails\n  override onSaveError(\n    data: SaveErrorCallback\u003cTodoModel, TodoTransport\u003e\n  ): void {}\n\n  //called when model delete process starts\n  override onDeleteStart(\n    data: DeleteStartCallback\u003cTodoModel, TodoTransport\u003e\n  ): void {}\n\n  //called when model delete process completes successfully\n  override onDeleteSuccess(\n    data: DeleteSuccessCallback\u003cTodoModel, TodoTransport\u003e\n  ): void {}\n\n  //called when model delete process fails\n  override onDeleteError(\n    data: DeleteErrorCallback\u003cTodoModel, TodoTransport\u003e\n  ): void {}\n\n  //called when collection load process starts\n  override onLoadStart(\n    data: LoadStartCallback\u003cTodoModel, TodoTransport\u003e\n  ): void {}\n\n  //called when collection load process completes successfully\n  override onLoadSuccess(\n    data: LoadSuccessCallback\u003cTodoModel, TodoTransport\u003e\n  ): void {}\n\n  //called when collection load process fails\n  override onLoadError(\n    data: LoadErrorCallback\u003cTodoModel, TodoTransport\u003e\n  ): void {}\n\n  /**\n   * called when `Collection.serialize` method executes\n   * If you want to add additional data to the serialization, return the data from the callback, and it will be\n   * added to the serialized object.\n   */\n  override onSerialize():Record\u003cstring, any\u003e | void {}\n\n  //called when collection `destroy` method is called\n  override onDestroy():void {}\n\n  /**\n   * Callback for when the collection is about to create a new model.\n   * This callback fires only on `Collection.reset` and `Collection.load` methods.\n   * It will not fire when the model is created via `Collection.create`\n   * Here, you can return modified data for model creation.\n   * If `undefined` is returned model creation will be skipped\n   */\n  protected override onModelCreateData(\n    data: FactoryData\u003ctypeof todoFactory\u003e\n  ): void | {\n    foo?: string | undefined\n    bar?: string | undefined\n    id?: string | undefined\n  } {}\n}\n```\n\nFor more info check out the [Collection API docs](./packages/core/docs/api/classes/Collection.md)\n\n## Autosave Collection\n\nAutosave collection inherits from the `Collection`. Its main differentiator is that it can automatically save the model whenever the `model payload` changes.\n\n```ts\nimport { AutosaveCollection } from '@fuerte/core'\nimport { todoFactory } from './todoFactory'\nimport { TodoModel } from './TodoModel'\nimport { TodoTransport } from './TodoTransport'\n\nconst collection = new AutosaveCollection(todoFactory, new TodoTransport(), {\n  autoSave: {\n    enabled: true //immediately enable autosave\n  }\n})\n\nconst model = collection.add(collection.create({ task: 'buy milk' }))\n\nmodel.setTask('Buy orange juice') // autosave triggers automatically\n```\n\nYou can enable or disable autosave only for certain models or for all of the collection at once.\n\n```ts\ncollection.startAutosave(modelOne)\ncollection.startAutosave([modelTwo, modelThree])\ncollection.startAutosave() //start for all the models in the collection\n\ncollection.stopAutosave(modelOne)\ncollection.stopAutosave([modelTwo, modelThree])\ncollection.stopAutosave() //start for all the models in the collection\n```\n\n### Callbacks\n\n- In addition to all the [callbacks](#callbacks-1) of the `Collection` class, `AutosaveCollection` has it's own callbacks.\n\n```ts\nimport { AutosaveCollection } from '@fuerte/core'\nimport { testModelFactory } from './TodoFactory'\nimport { TestModel } from './TestModel'\nimport { TestTransport } from './TestTransport'\n\nexport class TodoAutosaveCollection extends AutosaveCollection\u003c\n  TestModel,\n  typeof testModelFactory,\n  TestTransport\n\u003e {\n  /**\n   * Callback for when {@link AutosaveCollection.stopAutoSave} method has been executed.\n   * @param models - the array of models for which the auto-save process has been stopped.\n   */\n  onStartAutoSave(models: TestModel[]): void {}\n\n  /**\n   * Callback for when {@link AutosaveCollection.startAutoSave} method is executed.\n   * @param models - the array of models for which the auto-save process has been started\n   */\n  onStopAutoSave(models: TestModel[]): void {}\n}\n```\n\n## Transport\n\nThere is no `Transport` class in `@fuerte/core` there is only an `Interface` that classes that want to act as `transport` layer need to implement. The current transport interface consists of these methods:\n\n```ts\nexport interface Transport\u003cTModel extends Model = Model, TDTO = any\u003e {\n  /**\n   * Loads the model data. This method should just return the data that the factory requires to construct\n   * the models.\n   * @param config - transport config\n   * @returns array of model data for model construction\n   */\n  load(config?: any): Promise\u003c{ data: TDTO[] }\u003e\n\n  /**\n   * Saves the model\n   * @param model - model to save\n   * @param config - save the configuration\n   * @returns Object with optional \"data\" property to pass back to collection\n   */\n  save(model: TModel, config?: any): Promise\u003c{ data?: any } | void\u003e\n\n  /**\n   * Deletes the model\n   * @param model - model to delete\n   * @param config - delete configuration\n   * @returns Object with optional \"data\" property to pass back to collection\n   */\n  delete(model: TModel, config?: any): Promise\u003c{ data?: any } | void\u003e\n}\n```\n\nThis simplest transport, which saves the data **in-memory** could look like this:\n\n```ts\nimport type { Transport } from '@fuerte/core'\n\n//sample data\nconst firstTodo = { id: 1, task: 'Remember the milk' }\nconst secondTodo = { id: 2, task: 'Return books to library' }\n\n//simple in memory storage\nconst storage: Map\u003cstring, ModelDTO\u003e = new Map()\n\n//populate the storage\nstorage.set(firstTodo.id, firstTodo)\nstorage.set(secondTodo.id, secondTodo)\n\nexport class MemoryTransport implements Transport {\n  load(): Promise\u003c{ data: ModelDTO[] }\u003e {\n    // return everything from the storage (load the collection)\n    return Promise.resolve({ data: [...storage.values()] })\n  }\n\n  save(model: Todo) {\n    storage.set(model.id, model.payload)\n  }\n\n  delete(model: Todo) {\n    storage.delete(model.id)\n  }\n}\n```\n\nHead over to the recipes section to see more examples of [`Transport` implementations](#fetch-transport).\n\n## Recipes\n\n### Use composition instead of inheritance\n\nSometimes you don't want to expose all the methods of the `Collection` to the client code. Or do you want to have\nmethod names that better reflect your business logic, so instead of using `Collection.add` and `Collection.create` you would like to have `MyTodoStore.addTodo()`.\n\n[Composition](https://en.wikipedia.org/wiki/Composition_over_inheritance) is a perfect candidate for something like this.\n\nIn the next example, we are going to create a class that instead of inheriting from the `Collection` will use the `Collection` as its _protected_ property.\n\n```ts\nimport { todoFactory } from './TodoFactory'\nimport { TodoModel } from './TodoModel'\nimport { TodoTransport } from './TodoTransport'\nimport { FactoryData, Collection } from '@fuerte/core'\n\nexport class MyTodoStore {\n  protected collection = new Collection(todoFactory, new TodoTransport())\n\n  addTodo(data: FactoryData\u003ctypeof todoFactory\u003e): TodoModel {\n    const todo = this.collection.create(data)\n\n    this.collection.add(todo)\n\n    return todo\n  }\n\n  get todos() {\n    return this.collection.models\n  }\n}\n```\n\nBonus:\nIf you are into Dependency injection (and you should be), then you can modify the previous example so that the `MyTodoStore` class\naccepts already created collections.\n\n```ts\nimport { todoFactory } from './TodoFactory'\nimport { TodoModel } from './TodoModel'\nimport { TodoTransport } from './TodoTransport'\nimport { FactoryData, Collection, Transport } from '@fuerte/core'\n\ntype TodoFactory = typeof todoFactory\n\nexport class MyTodoStore {\n  constructor(\n    protected collection: Collection\u003cTodoModel, TodoFactory, Transport\u003e\n  ) {}\n}\n```\n\n### Restufl transport\n\nThis transport recipe uses `fetch` to communicate with the restful API.\n\n```ts\nimport type { Transport } from '@fuerte/core'\n\nexport class FetchTransport implements Transport {\n  async load(data?: { page?: string }): Promise\u003c{ data: ModelDTO[] }\u003e {\n    //maybe we have pagination enabled, check to see if we are using pagination\n    const query = data?.page ? `?query=${data.page}` : ''\n\n    const response = await fetch(`/some/api/todos${query}`)\n\n    const data = await response.json()\n\n    return data\n  }\n\n  async save(model: Todo) {\n    // if the model is new use POST otherwise use PUT\n    const method = model.isNew ? 'POST' : 'PUT'\n\n    const response = await fetch(`/some/api/todos/${model.identity}`, {\n      method,\n      headers: {\n        'Content-Type': 'application/json;charset=utf-8'\n      },\n      body: JSON.stringify(model)\n    })\n  }\n\n  async delete(model: Todo) {\n    const response = await fetch(`/some/api/todos/${model.identity}`, {\n      method: 'DELETE'\n    })\n  }\n}\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fivandotv%2Ffuerte","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fivandotv%2Ffuerte","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fivandotv%2Ffuerte/lists"}