{"id":15379291,"url":"https://github.com/andreasbm/boomerang","last_synced_at":"2026-01-12T13:30:18.470Z","repository":{"id":57098371,"uuid":"127041588","full_name":"andreasbm/boomerang","owner":"andreasbm","description":"A simple flux-inspired state management library","archived":false,"fork":false,"pushed_at":"2018-10-31T16:35:36.000Z","size":28,"stargazers_count":1,"open_issues_count":0,"forks_count":0,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-01-30T01:37:07.942Z","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":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/andreasbm.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2018-03-27T20:21:45.000Z","updated_at":"2020-03-25T18:15:29.000Z","dependencies_parsed_at":"2022-08-20T16:51:06.517Z","dependency_job_id":null,"html_url":"https://github.com/andreasbm/boomerang","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/andreasbm%2Fboomerang","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/andreasbm%2Fboomerang/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/andreasbm%2Fboomerang/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/andreasbm%2Fboomerang/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/andreasbm","download_url":"https://codeload.github.com/andreasbm/boomerang/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":239748300,"owners_count":19690237,"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-01T14:18:43.932Z","updated_at":"2025-02-19T23:19:44.802Z","avatar_url":"https://github.com/andreasbm.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# @appnest/boomerang\n\n\u003ca href=\"https://npmcharts.com/compare/@appnest/boomerang?minimal=true\"\u003e\u003cimg alt=\"Downloads per month\" src=\"https://img.shields.io/npm/dm/@appnest/boomerang.svg\" height=\"20\"\u003e\u003c/img\u003e\u003c/a\u003e\n\u003ca href=\"https://david-dm.org/andreasbm/boomerang\"\u003e\u003cimg alt=\"Dependencies\" src=\"https://img.shields.io/david/andreasbm/boomerang.svg\" height=\"20\"\u003e\u003c/img\u003e\u003c/a\u003e\n\u003ca href=\"https://www.npmjs.com/package/@appnest/boomerang\"\u003e\u003cimg alt=\"NPM Version\" src=\"https://img.shields.io/npm/v/@appnest/boomerang.svg\" height=\"20\"\u003e\u003c/img\u003e\u003c/a\u003e\n\u003ca href=\"https://github.com/andreasbm/boomerang/graphs/contributors\"\u003e\u003cimg alt=\"Contributors\" src=\"https://img.shields.io/github/contributors/andreasbm/boomerang.svg\" height=\"20\"\u003e\u003c/img\u003e\u003c/a\u003e\n\u003ca href=\"https://opensource.org/licenses/MIT\"\u003e\u003cimg alt=\"MIT License\" src=\"https://img.shields.io/badge/License-MIT-yellow.svg\" height=\"20\"\u003e\u003c/img\u003e\u003c/a\u003e\n\n## 🧐 What is this?\n\nA simple flux-inspired state management library.\n\nIf you want to brush up your flux skills I can recommend [this](https://blog.andrewray.me/flux-for-stupid-people/) article.\n\n## Benefits\n\n- Typesafe\n- `started`, `success`, `failed`, `invalidated` and `done` actions are dispatched along side your own actions! **This is super convenient for making stuff as for example loading and error handling**.\n\n## 👌 Step 1 - Install the dependency\n\n```javascript\nnpm i @appnest/boomerang --save\n```\n\n## 👊 Step 2 - Create the actions\n\nThe first step towards your glorious state management is to define the actions. An action is of the type `IAction` that contains a `kind`, `status` and an optional `data` and `metadata`. The way you usually want to create actions is by using the `mkDefaultAction` function. Here you need to specify the `data` and the `metadata` types of the `IAction`.\n\nHere's an example on how to create actions. The below example defines two actions. The first action, `getEntities` has the type `IEntity[]` as data and the type `string` as metadata. The other action `createEntity` has the type `IEntity` as data and does not contain any metadata.\n\n```javascript\nexport const EntityAction = {\n  getEntities: mkDefaultAction\u003cIEntity[], string\u003e(),\n  createEntity: mkDefaultAction\u003cIEntity\u003e()\n};\n```\n\n## 👏 Step 3 - Dispatch the actions\n\nThe next step is to dispatch the actions. You could dispatch the actions through the `Dispatcher` class like this.\n\n```javascript\n// Get entities\nconst data = await this.api.getEntities();\nconst metadata = \"Hello World\";\nDispatcher.instance.dispatch(EntityAction.getEntities.success(data, metadata));\n\n// Create entity\nconst data = await this.api.createEntity();\nDispatcher.instance.dispatch(EntityAction.getEntities.success(data, null));\n\n```\n\nThis works, but it would quickly get very repetitious to type if many of your views need to dispatch the same actions. Also, all of your views would need to know the specific actions which is not optimal. In flux we are recommended to use an abstraction, called action creators, which abstracts the above into functions.\n\nAction creators should extend the `ActionCreator` class for easier dispatching of events. Here's an example of the above turned into an action creator.\n\n```javascript\nexport class EntityActionCreator extends ActionCreator {\n\n  constructor (private api: API) {\n    super();\n  }\n\n  getEntities () {\n    this.tryCatch(EntityAction.getEntities, async () =\u003e {\n      return await this.api.getEntities();\n    }, \"Hello World\"});\n  }\n\n  createEntity () {\n    this.tryCatch(EntityAction.createEntity, async () =\u003e {\n      return await this.api.createEntity();\n    });\n  }\n}\n```\n\nYou might be wondering what the `tryCatch` function does. This method is really clever. It ensures that the `started`, `success`, `failed`, `invalidated` and `done` actions are dispatched along side your own actions! **This is super convenient for making stuff as for example loading and error handling**. What happens inside the `tryCatch` function is really simple as shown in the below code.\n\n```javascript\nprotected tryCatch\u003cData, Metadata\u003e (actionFactory: IDefaultAsyncActionFactory\u003cData, Metadata\u003e, bodyFunction: () =\u003e Promise\u003cData\u003e, metadata?: Metadata): void {\n  (async () =\u003e {\n    this.started(actionFactory, undefined, metadata);\n\n    try {\n      const data = await bodyFunction();\n      this.success(actionFactory, data, metadata);\n\n    } catch (e) {\n      this.failed(actionFactory, e, metadata);\n    }\n\n    this.done(actionFactory, undefined, metadata);\n  })();\n}\n```\n\n## 💪 Step 4 - Handle the actions\n\nSoo.. Now you have some actions and you are dispatching them. You now need a store that can handle them. To create a store, you will need to extend the `Store` class that provides the store behavior by subscribing to the `Dispatcher`. The only thing you will need to do now is to implement the `protected abstract handler (action: IAction): void;` and handle the relevant actions. To handle an action you are encouraged to use the `isAction` method. Here's an example on how a store could look.\n\n```javascript\nexport class EntityStore extends Store\u003cIEntityStoreEvent\u003e {\n  protected handler (action: IAction) {\n    if (isAction(action, EntityAction.createEntity.success)) {\n      // TODO: Add the new entity to the list\n\n    } else if (isAction(action, EntityAction.getEntities.success)) {\n      // TODO: Handle the new entities (can be cound in the action.data)\n\n    } else if (isAction(action, EntityAction.createEntity.failed)) {\n      // TODO: Handle that the creation failed\n    }\n\n    // Loading related stuff\n    if (isAction(action, EntityAction.createEntity.started) || isAction(action, EntityAction.getEntities.started)) {\n      // TODO: Handle the loading started event\n\n    } else if (isAction(action, EntityAction.createEntity.done) || isAction(action, EntityAction.getEntities.done)) {\n      // TODO: Handle the loading ended event\n    }\n  }\n}\n```\n\n## 👍 Step 5 - Update the view\n\nWe can now handle the actions! The last step is to update the view. The view needs to know of the store and is able to subscribe to the store since it extends the `Subject` class. It is therefore possible for the store to dispatch events and for the view to listen to them. Here's an example on how that could look.\n\n```javascript\nexport enum EntityStoreEventKind {\n  entityAdded,\n  entitiesChanged,\n  entityAddedError,\n  loadingStarted,\n  loadingEnded\n}\n\nexport interface IEntityStoreEvent {\n  kind: EntityStoreEventKind;\n  data?: Json;\n}\nexport class EntityStore extends Store\u003cIEntityStoreEvent\u003e {\n\n  private _entities: IEntity[] = [];\n  get entities () {\n    return this._entities;\n  }\n\n  protected handler (action: IAction) {\n    console.log(action);\n\n    if (isAction(action, EntityAction.createEntity.success)) {\n      this._entities.push(action.data);\n      this.dispatch({kind: EntityStoreEventKind.entityAdded});\n\n    } else if (isAction(action, EntityAction.getEntities.success)) {\n      this._entities = action.data;\n      this.dispatch({kind: EntityStoreEventKind.entitiesChanged});\n\n    } else if (isAction(action, EntityAction.createEntity.failed)) {\n      this.dispatch({kind: EntityStoreEventKind.entityAddedError, data: \"Sometimes it goes wrong..\"});\n    }\n\n    // Loading related stuff\n    if (isAction(action, EntityAction.createEntity.started) || isAction(action, EntityAction.getEntities.started)) {\n      this.dispatch({kind: EntityStoreEventKind.loadingStarted});\n\n    } else if (isAction(action, EntityAction.createEntity.done) || isAction(action, EntityAction.getEntities.done)) {\n      this.dispatch({kind: EntityStoreEventKind.loadingEnded});\n    }\n  }\n}\n```\n\nAnd here's the view that listens to changes in the `EntityStore` and dispatches actions through the `EntityActionCreator`.\n\n```javascript\nclass OverviewComponent {\n\n  private isLoading = false;\n\n  constructor (private entityActionCreator = new EntityActionCreator(new API()),\n               private entityStore = new EntityStore()) {\n    this.entityStoreListener = this.entityStoreListener.bind(this);\n  }\n\n  connectedCallback () {\n    this.entityStore.addListener(this.entityStoreListener);\n    this.entityActionCreator.getEntities();\n  }\n\n  private entityStoreListener (e: IEntityStoreEvent) {\n    switch (e.kind) {\n      case EntityStoreEventKind.entitiesChanged:\n        break;\n      case EntityStoreEventKind.entityAdded:\n        break;\n      case EntityStoreEventKind.entityAddedError:\n        alert(e.data);\n        return;\n      case EntityStoreEventKind.loadingStarted:\n        this.isLoading = true;\n        break;\n      case EntityStoreEventKind.loadingEnded:\n        this.isLoading = false;\n        break;\n    }\n\n    this.invalidate();\n  }\n\n  disconnectedCallback () {\n    this.entityStore.removeListener(this.entityStoreListener);\n  }\n\n  private createEntity () {\n    this.entityActionCreator.createEntity();\n  }\n\n  ...\n}\n\n```\n\n## 🎉 License\n\nLicensed under [MIT](https://opensource.org/licenses/MIT).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fandreasbm%2Fboomerang","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fandreasbm%2Fboomerang","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fandreasbm%2Fboomerang/lists"}