{"id":17036132,"url":"https://github.com/digaev/interactor-js","last_synced_at":"2026-02-16T09:02:00.537Z","repository":{"id":100739620,"uuid":"608529177","full_name":"digaev/interactor-js","owner":"digaev","description":"Interactor pattern implementation","archived":false,"fork":false,"pushed_at":"2024-06-24T15:55:31.000Z","size":101,"stargazers_count":1,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"master","last_synced_at":"2025-10-19T17:06:00.583Z","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/digaev.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,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2023-03-02T07:51:47.000Z","updated_at":"2024-06-24T15:55:34.000Z","dependencies_parsed_at":null,"dependency_job_id":"c8ca7c65-91a9-43b7-ad04-e12ad4f57971","html_url":"https://github.com/digaev/interactor-js","commit_stats":{"total_commits":57,"total_committers":2,"mean_commits":28.5,"dds":0.1578947368421053,"last_synced_commit":"bb7f6df96c5f825212339948f3241705708525e8"},"previous_names":[],"tags_count":1,"template":false,"template_full_name":null,"purl":"pkg:github/digaev/interactor-js","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/digaev%2Finteractor-js","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/digaev%2Finteractor-js/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/digaev%2Finteractor-js/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/digaev%2Finteractor-js/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/digaev","download_url":"https://codeload.github.com/digaev/interactor-js/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/digaev%2Finteractor-js/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":29504684,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-02-16T08:14:25.707Z","status":"ssl_error","status_checked_at":"2026-02-16T08:14:25.334Z","response_time":115,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"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-14T08:49:27.373Z","updated_at":"2026-02-16T09:02:00.505Z","avatar_url":"https://github.com/digaev.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# interactor-organizer\n\n[![CircleCI](https://dl.circleci.com/status-badge/img/gh/digaev/interactor-js/tree/master.svg?style=svg)](https://dl.circleci.com/status-badge/redirect/gh/digaev/interactor-js/tree/master)\n[![Coverage Status](https://coveralls.io/repos/github/digaev/interactor-js/badge.svg?branch=master)](https://coveralls.io/github/digaev/interactor-js?branch=master)\n[![npm](https://img.shields.io/npm/v/interactor-organizer)](https://www.npmjs.com/package/interactor-organizer)\n\nInteractor pattern implementation, inspired by Ruby gem [interactor](https://github.com/collectiveidea/interactor).\n\n___\n\n* [Getting started](#getting-started)\n* [Interactors](#interactors)\n* [Organizers](#organizers)\n* [Usage](#usage)\n\n## Getting started\n\n```bash\nnpm i interactor-organizer\n```\n\n```ts\nimport { Interactor } from 'interactor-organizer';\n\nclass DoSomething extends Interactor {\n  async after() {\n    console.log('after');\n  }\n\n  async before() {\n    console.log('before');\n  }\n\n  // Your business logic goes here\n  async perform() {\n    console.log('perform', this.context);\n\n    try {\n      this.context.bar = 'baz';\n    } catch (error) {\n      this.fail({ error });\n    }\n  }\n}\n\nasync function main() {\n  // Perform the interactor\n  const interactor = await DoSomething.perform({ foo: 'bar' });\n\n  console.log(interactor.failure, interactor.success, interactor.context);\n}\n\nmain();\n\n// output\n/**\nbefore\nperform { foo: 'bar' }\nafter\nfalse true { foo: 'bar', bar: 'baz' }\n*/\n```\n\n## Interactors\n\nEvery interactor has `after`, `before`, `fail`, `perform` and `rollback` methods, they are very similar to the Ruby gem methods, the only \"new\" method is `perform` (which is used here instead of `call`).\n\nThere are two classes of interactors:\n\n* `Interactor`\n* `SafeInteractor`\n\nThe only difference between them is that `SafeInteractor` will never reject, instead, it calls `fail({ error })`, while `Interactor` will reject unless you catch and handle errors yourself.\n\n### constructor\n\n`constructor(context?: any)`\n\nAnything you want to pass to the interactor or return from it should be stored in `context`. Expected an object, default `{}`.\n\n### after\n\n`after(): Promise\u003cany\u003e`\n\nIs called after `perform` only if the interactor didn't `fail`.\n\n### before\n\n`before(): Promise\u003cany\u003e`\n\nIs always called before `perform`.\n\n### fail\n\n`fail(context?: any): void`\n\nIf something went wrong use this method. It sets the interactor's property `failure` to `true` (which is also used by Organizers).\n\n`context` is appended to the current context. Expected an object.\n\n### perform\n\n`perform(): Promise\u003cany\u003e`\n\nYour business logic goes here. Under the hood, this method is modified so that it calls the `after` and `before` hooks.\n\n### rollback\n\n`rollback(): Promise\u003cany\u003e`\n\nThis method is only used by Organizers to allow successfully resolved interactors in the chain to undo the changes made by `perform`.\n\n### static perform\n\n`static perform(context?: any): Promise\u003cInteractor\u003e`\n\nA shortcut to the instance method.\n\n### context\n\n`context: any`\n\nCurrent context. An object.\n\n### failure\n\n`failure: boolean`\n\nIndicates if the interactor failed.\n\n### success\n\n`success: boolean`\n\nThe opposite of `failure`.\n\n## Organizers\n\nOrganizers sequentially `perform` interactors, if any interactor in the chain fails all the previous interactors will `rollback` (from the last resolved to the first). If any `rollback` rejects the organizer will reject as well (any further interactors won't `rollback`)!\n\n## Usage\n\nInteractors example:\n\n```ts\nimport { Interactor } from \"interactor-organizer\";\n\nclass PlaceOrder extends Interactor {\n  get order() {\n    return this.context.order;\n  }\n\n  get user() {\n    return this.context.user;\n  }\n\n  async perform() {\n    this.order.user = { _id: this.user._id };\n\n    return client.db().collection('orders').insertOne(this.order)\n      .then((result) =\u003e {\n        this.order._id = result.insertedId;\n      })\n      // We could inherit PlaceOrder from SafeInteractor to let it catch errors for us\n      .catch((error) =\u003e {\n        this.fail({ error });\n      });\n  }\n\n  async rollback() {\n    // Delete the order if ChargeCard fails\n    return client.db().collection('orders').deleteOne({ _id: this.order._id })\n  }\n}\n\nclass ChargeCard extends Interactor {\n  async perform() {\n    // API call to the payment system\n  }\n}\n```\n\nThere are helper functions to create an Interactor class runtime:\n\n```ts\nimport { createInteractor } from \"interactor-organizer\";\n\n// Do not use arrow/anonymous functions if you want to access `this`\nconst FirstInteractor = createInteractor(function perform() { console.log('first'); });\nconst SecondInteractor = createInteractor(function perform() { console.log('second'); });\n```\n\nOrganizers example:\n\n```ts\n// The easiest way is to use the `organize` function\nimport { organize } from \"interactor-organizer\";\n\norganize({}, [FirstInteractor, SecondInteractor]).then(console.log);\n```\n\n```ts\n// A more elegant way is to create an Organizer\nimport { Organizer } from \"interactor-organizer\";\n\nclass CreateOrder extends Organizer {\n  static organize() {\n    return [PlaceOrder, ChargeCard];\n  }\n}\n```\n\n```ts\n// orders.controller.ts\n\nfunction createOrder(req, res, next) {\n  CreateOrder.perform({ order: ...req.body, user: req.user })\n    .then((result) =\u003e {\n      if (result.failure) {\n        throw result.context.error;\n      }\n\n      res.status(201).json({ _id: result.context.order._id });\n    })\n    .catch(next);\n}\n```\n\nChecking for `failure` every time may not always can be convenient, instead, you can throw errors from the organizer:\n\n```ts\nclass StrictOrganizer extends Organizer {\n  static async perform(context: any = {}) {\n    return super.perform(context)\n      .then((result) =\u003e {\n        if (result.failure) {\n          throw result.context.error || new Error(`${this.name} failed`);\n        }\n        return result;\n      });\n  }\n}\n\n// Inherit your organizers from StrictOrganizer\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdigaev%2Finteractor-js","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fdigaev%2Finteractor-js","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdigaev%2Finteractor-js/lists"}