{"id":28289812,"url":"https://github.com/dillonstreator/txob","last_synced_at":"2026-03-05T22:31:45.583Z","repository":{"id":212956432,"uuid":"732572899","full_name":"dillonstreator/txob","owner":"dillonstreator","description":"Transactional outbox event processor with graceful shutdown and horizontal scalability","archived":false,"fork":false,"pushed_at":"2026-03-02T18:46:17.000Z","size":707,"stargazers_count":4,"open_issues_count":3,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2026-03-02T20:48:56.243Z","etag":null,"topics":["ddd","events","graceful-shutdown","horizontal-scalable","microservices","outbox-pattern","transactional-outbox","typescript"],"latest_commit_sha":null,"homepage":"https://www.npmjs.com/package/txob","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/dillonstreator.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,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2023-12-17T05:51:07.000Z","updated_at":"2026-03-02T17:14:31.000Z","dependencies_parsed_at":"2023-12-21T04:55:16.062Z","dependency_job_id":"c2275a72-bda9-4be2-8a44-ac60ec90e3c7","html_url":"https://github.com/dillonstreator/txob","commit_stats":null,"previous_names":["dillonstreator/txob"],"tags_count":1,"template":false,"template_full_name":null,"purl":"pkg:github/dillonstreator/txob","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dillonstreator%2Ftxob","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dillonstreator%2Ftxob/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dillonstreator%2Ftxob/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dillonstreator%2Ftxob/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/dillonstreator","download_url":"https://codeload.github.com/dillonstreator/txob/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dillonstreator%2Ftxob/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":30152854,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-03-05T21:15:50.531Z","status":"ssl_error","status_checked_at":"2026-03-05T21:15:11.173Z","response_time":93,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.6:443 state=error: 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":["ddd","events","graceful-shutdown","horizontal-scalable","microservices","outbox-pattern","transactional-outbox","typescript"],"created_at":"2025-05-22T02:11:25.655Z","updated_at":"2026-03-05T22:31:45.545Z","avatar_url":"https://github.com/dillonstreator.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"\u003ch1 align=\"center\"\u003etxob\u003c/h1\u003e\n\u003cp align=\"center\"\u003eA generic \u003ca href=\"https://microservices.io/patterns/data/transactional-outbox.html\"\u003etransactional outbox\u003c/a\u003e event processor with graceful shutdown and horizontal scalability\u003c/p\u003e\n\u003cp align=\"center\"\u003e\n  \u003ca href=\"https://codecov.io/gh/dillonstreator/txob\" \u003e\n    \u003cimg src=\"https://codecov.io/gh/dillonstreator/txob/graph/badge.svg?token=E9M7G67VLL\"/\u003e\n  \u003c/a\u003e\n  \u003ca aria-label=\"NPM version\" href=\"https://www.npmjs.com/package/txob\"\u003e\n    \u003cimg alt=\"\" src=\"https://badgen.net/npm/v/txob?v=0.0.21\"\u003e\n  \u003c/a\u003e\n  \u003ca aria-label=\"License\" href=\"https://github.com/dillonstreator/txob/blob/main/LICENSE\"\u003e\n    \u003cimg alt=\"\" src=\"https://badgen.net/npm/license/txob\"\u003e\n  \u003c/a\u003e\n  \u003ca aria-label=\"Typescript\" href=\"https://github.com/dillonstreator/txob/blob/main/src/cache.ts\"\u003e\n    \u003cimg alt=\"\" src=\"https://badgen.net/npm/types/txob\"\u003e\n  \u003c/a\u003e\n  \u003ca aria-label=\"CodeFactor\" href=\"https://www.codefactor.io/repository/github/dillonstreator/txob\"\u003e\n    \u003cimg alt=\"\" src=\"https://www.codefactor.io/repository/github/dillonstreator/txob/badge\"\u003e\n  \u003c/a\u003e\n\u003c/p\u003e\n\n## Description\n\n`txob` _does not_ prescribe a storage layer implementation.\\\n`txob` _does_ prescribe a base event storage data model that enables a high level of visibility into event handler processing outcomes.\n\n- `id` string\n- `timestamp` Date\n- `type` (enum/string)\n- `data` json\n- `correlation_id` string\n- `handler_results` json\n- `errors` number\n- `backoff_until` Date nullable\n- `processed_at` Date nullable\n\n`txob` exposes an optionally configurable interface into event processing with control over maximum allowed errors, backoff calculation on error, event update retrying, and logging.\n\nAs per the 'transactional outbox specification', you should ensure your events are transactionally persisted alongside their related data mutations.\n\nThe processor handles graceful shutdown and is horizontally scalable by default with the native client implementatations for [`pg`](./src/pg/client.ts) and [`mongodb`](./src/mongodb/client.ts).\n\n## Installation\n\n```sh\n(npm|yarn) (install|add) txob\n```\n\n### Examples\n\nLet's look at an example of an HTTP API that allows a user to be invited where an SMTP request must be sent as a side-effect of the user creation / invite.\n\n```ts\nimport http from \"node:http\";\nimport { randomUUID } from \"node:crypto\";\nimport { Client } from \"pg\";\nimport gracefulShutdown from \"http-graceful-shutdown\";\nimport { EventProcessor } from \"txob\";\nimport { createProcessorClient } from \"txob/pg\";\n\nconst eventTypes = {\n  UserCreated: \"UserCreated\",\n  // other event types\n} as const;\n\ntype EventType = keyof typeof eventTypes;\n\nconst client = new Client({\n  user: process.env.POSTGRES_USER,\n  password: process.env.POSTGRES_PASSWORD,\n  database: process.env.POSTGRES_DB,\n});\nawait client.connect();\n\nconst HTTP_PORT = process.env.PORT || 3000;\n\nconst processor = EventProcessor(\n  createProcessorClient\u003cEventType\u003e(client),\n  {\n    UserCreated: {\n      sendEmail: async (event, { signal }) =\u003e {\n        // find user by event.data.userId to use relevant user data in email sending\n\n        // email sending logic\n\n        // use the AbortSignal `signal` (aborted when EventProcessor#stop is called) to perform quick cleanup\n        // during graceful shutdown enabling the processor to\n        // save handler result updates to the event ASAP\n      },\n      publish: async (event) =\u003e {\n        // publish to event bus\n      },\n      // other handler that should be executed when a `UserCreated` event is saved\n    },\n    // other event types\n  }\n)\nprocessor.start();\n\nconst server = http.createServer(async (req, res) =\u003e {\n  if (req.url  !== \"/invite\") return;\n\n  // invite user endpoint\n\n  const correlationId = randomUUID(); // or some value on the incoming request such as a request id / trace id\n\n  try {\n    await client.query(\"BEGIN\");\n\n    const userId = randomUUID();\n    // save user with userId\n    await client.query(`INSERT INTO users (id, email) VALUES ($1, $2)`, [userId, req.body.email]);\n\n    // save event to `events` table\n    await client.query(\n      `INSERT INTO events (id, type, data, correlation_id) VALUES ($1, $2, $3, $4)`,\n      [\n        randomUUID(),\n        eventTypes.UserCreated,\n        { userId }, // other relevant data\n        correlationId,\n      ],\n    );\n\n    await client.query(\"COMMIT\");\n  } catch (error) {\n    await client.query(\"ROLLBACK\").catch(() =\u003e {});\n  }\n}).listen(HTTP_PORT, () =\u003e console.log(`listening on port ${HTTP_PORT}`));\n\ngracefulShutdown(server, {\n  onShutdown: async () =\u003e {\n    // allow any actively running event handlers to finish\n    // and the event processor to save the results\n    await processor.stop();\n  }\n});\n```\n\n[other examples](./examples)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdillonstreator%2Ftxob","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fdillonstreator%2Ftxob","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdillonstreator%2Ftxob/lists"}