{"_id":"@blade47/saga-ts","_rev":"5-a1514ff357ae096179b72c7dfa592d33","name":"@blade47/saga-ts","dist-tags":{"latest":"1.0.4"},"versions":{"1.0.0":{"name":"@blade47/saga-ts","version":"1.0.0","keywords":["saga","typescript","transaction","rollback","compensation","distributed-transactions","saga-pattern"],"author":{"name":"Alessandro Afloarei"},"license":"MIT","_id":"@blade47/saga-ts@1.0.0","maintainers":[{"name":"blade47","email":"alessandro.afloarei@outlook.it"}],"dist":{"shasum":"69c89b8fc17f237c9981d324aa1a45d28e02389f","tarball":"https://registry.npmjs.org/@blade47/saga-ts/-/saga-ts-1.0.0.tgz","fileCount":7,"integrity":"sha512-1cjyQCP9uvQUzbTipNp4Ux3np6a4apweApKJpDce7tbuwztVLCmLTUEsEYAWhLe0IJgZHg/kzrK/VetX4rUL6A==","signatures":[{"sig":"MEUCIDbyw8n/5fWCN+/c51Iq5ZdHg+60yd9EWguYQcRMrWH3AiEAol7MEQ7Vb3mXBfZW8wS5pdHgnMmxD9sgKJh26BXX34o=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":16424},"main":"dist/index.js","types":"dist/index.d.ts","engines":{"node":">=16.0.0"},"scripts":{"test":"jest","build":"tsc","clean":"rm -rf dist","test:watch":"jest --watch","test:coverage":"jest --coverage","prepublishOnly":"npm run build && npm test"},"_npmUser":{"name":"blade47","email":"alessandro.afloarei@outlook.it"},"repository":{"url":"","type":"git"},"_npmVersion":"10.8.2","description":"A lightweight, type-safe saga pattern implementation for TypeScript with automatic rollback support","directories":{},"_nodeVersion":"20.19.0","publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^29.7.0","ts-jest":"^29.1.2","typescript":"^5.3.3","@types/jest":"^29.5.12","@types/node":"^20.11.19"},"_npmOperationalInternal":{"tmp":"tmp/saga-ts_1.0.0_1763455924553_0.3273245647065379","host":"s3://npm-registry-packages-npm-production"}},"1.0.1":{"name":"@blade47/saga-ts","version":"1.0.1","keywords":["saga","typescript","transaction","rollback","compensation","distributed-transactions","saga-pattern"],"author":{"name":"Alessandro Afloarei"},"license":"MIT","_id":"@blade47/saga-ts@1.0.1","maintainers":[{"name":"blade47","email":"alessandro.afloarei@outlook.it"}],"dist":{"shasum":"6474abd335545f56effc4aa9ec48c7bdd70af9fa","tarball":"https://registry.npmjs.org/@blade47/saga-ts/-/saga-ts-1.0.1.tgz","fileCount":7,"integrity":"sha512-MThKkpisASi8NVh65E8rbIwcNISMzKXYB2wyhPONDjIsv0BgEcZQHCMrHP7jxWCnfpJs0uqANV0lDI8l+SDzmg==","signatures":[{"sig":"MEYCIQD6N3zqAij+v1lvEN9pCB0QxPHaSOfSnWMVYHD0aCDBkwIhAOp+UhzK9ALni4kit4da30KrYCdvaMMLs2PhkNGzzwMr","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":19390},"main":"dist/index.js","types":"dist/index.d.ts","engines":{"node":">=16.0.0"},"gitHead":"b8266dbac2032a4531b1d0c770036536c91aaf59","scripts":{"test":"jest","build":"tsc","clean":"rm -rf dist","test:watch":"jest --watch","test:coverage":"jest --coverage","prepublishOnly":"npm run build && npm test"},"_npmUser":{"name":"blade47","email":"alessandro.afloarei@outlook.it"},"repository":{"url":"","type":"git"},"_npmVersion":"10.8.2","description":"A lightweight, type-safe saga pattern implementation for TypeScript with automatic rollback support","directories":{},"_nodeVersion":"20.19.0","publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^29.7.0","ts-jest":"^29.1.2","typescript":"^5.3.3","@types/jest":"^29.5.12","@types/node":"^20.11.19"},"_npmOperationalInternal":{"tmp":"tmp/saga-ts_1.0.1_1763459290465_0.1961154619280192","host":"s3://npm-registry-packages-npm-production"}},"1.0.2":{"name":"@blade47/saga-ts","version":"1.0.2","keywords":["saga","typescript","transaction","rollback","compensation","distributed-transactions","saga-pattern"],"author":{"name":"Alessandro Afloarei"},"license":"MIT","_id":"@blade47/saga-ts@1.0.2","maintainers":[{"name":"blade47","email":"alessandro.afloarei@outlook.it"}],"dist":{"shasum":"1aa266559f2e8321aef5425d8a399da90ac09463","tarball":"https://registry.npmjs.org/@blade47/saga-ts/-/saga-ts-1.0.2.tgz","fileCount":7,"integrity":"sha512-HdmkRwFcK/9VHP1G011wuwsCc57+tLgIvmODKjP6+pWEIRvq36LkD1seF87q1DgBXY/zvNRhB8BNp+q86WeHrg==","signatures":[{"sig":"MEUCIA0nd+239Vkty1jt60ACUaQZrHiVPkksa6U+0zQHSsS3AiEApDVA1q82GFCxxomp5JM13PFy/hddkSbpQESq7J5pysU=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":19143},"main":"dist/index.js","types":"dist/index.d.ts","engines":{"node":">=16.0.0"},"gitHead":"b8266dbac2032a4531b1d0c770036536c91aaf59","scripts":{"test":"jest","build":"tsc","clean":"rm -rf dist","test:watch":"jest --watch","test:coverage":"jest --coverage","prepublishOnly":"npm run build && npm test"},"_npmUser":{"name":"blade47","email":"alessandro.afloarei@outlook.it"},"repository":{"url":"","type":"git"},"_npmVersion":"10.8.2","description":"A lightweight, type-safe saga pattern implementation for TypeScript with automatic rollback support","directories":{},"_nodeVersion":"20.19.0","publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^29.7.0","ts-jest":"^29.1.2","typescript":"^5.3.3","@types/jest":"^29.5.12","@types/node":"^20.11.19"},"_npmOperationalInternal":{"tmp":"tmp/saga-ts_1.0.2_1763459651893_0.5362779889466329","host":"s3://npm-registry-packages-npm-production"}},"1.0.3":{"name":"@blade47/saga-ts","version":"1.0.3","keywords":["saga","typescript","transaction","rollback","compensation","distributed-transactions","saga-pattern"],"author":{"name":"Alessandro Afloarei"},"license":"MIT","_id":"@blade47/saga-ts@1.0.3","maintainers":[{"name":"blade47","email":"alessandro.afloarei@outlook.it"}],"dist":{"shasum":"aa95c0d4af4c08bcfec4ce32df127783da3a65bd","tarball":"https://registry.npmjs.org/@blade47/saga-ts/-/saga-ts-1.0.3.tgz","fileCount":7,"integrity":"sha512-GyJOL7m5MsH5s6VnXPM58IeQ0EkDG+FO84ljffCVUISuaTBXiyDbGODthM+OILDhgxUBSAL6UcXqob3CNF7a8A==","signatures":[{"sig":"MEQCIGOd3+JJF46MfmzwRUM005AnN6cs2zMJHXKIqM3m9PnzAiBZYHwL3dZ6q0l8CJj9k+iScx3RnyzM8bDIyyXgs6C9qA==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":19428},"main":"dist/index.js","types":"dist/index.d.ts","engines":{"node":">=16.0.0"},"gitHead":"b8266dbac2032a4531b1d0c770036536c91aaf59","scripts":{"test":"jest","build":"tsc","clean":"rm -rf dist","test:watch":"jest --watch","test:coverage":"jest --coverage","prepublishOnly":"npm run build && npm test"},"_npmUser":{"name":"blade47","email":"alessandro.afloarei@outlook.it"},"repository":{"url":"","type":"git"},"_npmVersion":"10.8.2","description":"A lightweight, type-safe saga pattern implementation for TypeScript with automatic rollback support","directories":{},"_nodeVersion":"20.19.0","publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^29.7.0","ts-jest":"^29.1.2","typescript":"^5.3.3","@types/jest":"^29.5.12","@types/node":"^20.11.19"},"_npmOperationalInternal":{"tmp":"tmp/saga-ts_1.0.3_1763461092127_0.15478451923120828","host":"s3://npm-registry-packages-npm-production"}},"1.0.4":{"name":"@blade47/saga-ts","version":"1.0.4","description":"A lightweight, type-safe saga pattern implementation for TypeScript with automatic rollback support","main":"dist/index.js","types":"dist/index.d.ts","scripts":{"build":"tsc","test":"jest","test:watch":"jest --watch","test:coverage":"jest --coverage","prepublishOnly":"npm run build && npm test","clean":"rm -rf dist"},"keywords":["saga","typescript","transaction","rollback","compensation","distributed-transactions","saga-pattern"],"author":{"name":"Alessandro Afloarei"},"license":"MIT","devDependencies":{"@types/jest":"^29.5.12","@types/node":"^20.11.19","jest":"^29.7.0","ts-jest":"^29.1.2","typescript":"^5.3.3"},"repository":{"type":"git","url":""},"engines":{"node":">=16.0.0"},"publishConfig":{"access":"public"},"_id":"@blade47/saga-ts@1.0.4","gitHead":"860fd9a4c7454d14e179fb6a1f911cc8670a3f26","_nodeVersion":"22.21.1","_npmVersion":"10.9.4","dist":{"integrity":"sha512-jCAA99ek0wTmOG8RSJb2ta1fklEm5DQa/smaduYOEp2uiIa2e4Jx5DZ/3LGsJG4I5GtPSlhaIbQiKBT9oKC2GQ==","shasum":"6ae49f10be6b771a725324ecd1fde25fa2f9deb9","tarball":"https://registry.npmjs.org/@blade47/saga-ts/-/saga-ts-1.0.4.tgz","fileCount":7,"unpackedSize":20235,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEUCIDlFyhELdaUvnPA2kZs6Lhpr0n2ZtUo3O+Qm2/iTVOjlAiEArus7XV/5pueulZWMsXAczjaUBap4XSo1f9gCMUGFlyY="}]},"_npmUser":{"name":"blade47","email":"alessandro.afloarei@outlook.it"},"directories":{},"maintainers":[{"name":"blade47","email":"alessandro.afloarei@outlook.it"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/saga-ts_1.0.4_1765293497455_0.5092677044148168"},"_hasShrinkwrap":false}},"time":{"created":"2025-11-18T08:52:04.500Z","modified":"2025-12-09T15:18:18.220Z","1.0.0":"2025-11-18T08:52:04.739Z","1.0.1":"2025-11-18T09:48:10.663Z","1.0.2":"2025-11-18T09:54:12.131Z","1.0.3":"2025-11-18T10:18:12.356Z","1.0.4":"2025-12-09T15:18:17.597Z"},"author":{"name":"Alessandro Afloarei"},"license":"MIT","keywords":["saga","typescript","transaction","rollback","compensation","distributed-transactions","saga-pattern"],"repository":{"type":"git","url":""},"description":"A lightweight, type-safe saga pattern implementation for TypeScript with automatic rollback support","maintainers":[{"name":"blade47","email":"alessandro.afloarei@outlook.it"}],"readme":"# saga-ts\n\nA lightweight, type-safe saga pattern implementation for TypeScript with automatic rollback support.\n\n## Features\n\n- **Type-safe**: Full TypeScript support with intelligent type inference\n- **Automatic rollback**: Failed steps trigger automatic compensation in reverse order\n- **Zero dependencies**: Lightweight and production-ready\n- **Simple API**: Intuitive builder pattern for defining sagas\n- **Flexible**: Works with any async operations (database, API calls, etc.)\n\n## Installation\n\n```bash\nnpm install saga-ts\n```\n\n## Quick Start\n\n```typescript\nimport { createSaga } from 'saga-ts';\n\nconst result = await createSaga()\n  .step(\n    'createUser',\n    { email: 'test@test.com', name: 'John' },\n    async (input, ctx) => {\n      const user = await db.users.create(input);\n      return { userId: user.id, email: input.email };\n    },\n    async (output) => {\n      // Rollback: delete the user if something fails later\n      await db.users.delete(output.userId);\n    }\n  )\n  .step(\n    'sendEmail',\n    { template: 'welcome' },\n    async (input, ctx) => {\n      // Access previous step results with full type safety\n      const { email } = ctx.results.createUser;\n\n      const emailId = await emailService.send({\n        to: email,\n        template: input.template,\n      });\n      return { emailId, sentAt: new Date() };\n    },\n    async (output) => {\n      await emailService.cancel(output.emailId);\n    }\n  )\n  .run();\n\nif (result.status === 'success') {\n  console.log('User created:', result.results.createUser.userId);\n  console.log('Email sent:', result.results.sendEmail.emailId);\n} else {\n  console.error('Failed at step:', result.failedAt);\n  console.error('Error:', result.error);\n}\n```\n\n## API Reference\n\n### `createSaga()`\n\nCreates a new saga builder instance.\n\n### `.step(name, input, execute, rollback?)`\n\nAdds a step to the saga.\n\n- **name**: Unique identifier for the step (used for accessing results)\n- **input**: Input data for the step\n- **execute**: Async function that performs the step's action\n  - Receives `input` and `ctx` (containing results from previous steps)\n  - Must return the step's output\n- **rollback** (optional): Async function to undo the step's action\n  - Receives the step's output\n  - Called automatically if a later step fails\n\n### `.run()`\n\nExecutes the saga and returns a promise with the result.\n\n**Success result:**\n```typescript\n{\n  status: 'success',\n  data: TLastOutput,      // Output of the last step\n  results: TResults       // All step results by name\n}\n```\n\n**Failure result:**\n```typescript\n{\n  status: 'failed',\n  error: Error,           // The error that occurred\n  failedAt: string        // Name of the step that failed\n}\n```\n\n## Examples\n\n### Payment Processing with Rollback\n\n```typescript\nconst result = await createSaga()\n  .step(\n    'validateCard',\n    { cardToken: 'tok_visa' },\n    async (input) => {\n      const card = await stripe.tokens.retrieve(input.cardToken);\n      return { cardId: card.id, last4: card.card.last4 };\n    }\n  )\n  .step(\n    'calculateTotal',\n    { items: [{ id: '1', price: 100 }, { id: '2', price: 50 }] },\n    async (input) => {\n      const total = input.items.reduce((sum, item) => sum + item.price, 0);\n      return { total, items: input.items };\n    }\n  )\n  .step(\n    'createCharge',\n    { currency: 'usd' },\n    async (input, ctx) => {\n      const charge = await stripe.charges.create({\n        amount: ctx.results.calculateTotal.total,\n        currency: input.currency,\n        source: ctx.results.validateCard.cardId,\n      });\n      return { chargeId: charge.id };\n    },\n    async (output) => {\n      // Rollback: refund the charge\n      await stripe.refunds.create({ charge: output.chargeId });\n    }\n  )\n  .step(\n    'sendReceipt',\n    { recipientEmail: 'user@example.com' },\n    async (input, ctx) => {\n      await emailService.send({\n        to: input.recipientEmail,\n        template: 'receipt',\n        data: {\n          chargeId: ctx.results.createCharge.chargeId,\n          amount: ctx.results.calculateTotal.total,\n        },\n      });\n      return { sent: true };\n    }\n  )\n  .run();\n\n// If sendReceipt fails, the charge will be automatically refunded\n```\n\n### Complex User Onboarding\n\n```typescript\nconst result = await createSaga()\n  .step(\n    'createAccount',\n    { email: 'user@example.com', password: 'secure123' },\n    async (input) => {\n      const account = await db.accounts.create(input);\n      return { accountId: account.id };\n    },\n    async (output) => {\n      await db.accounts.delete(output.accountId);\n    }\n  )\n  .step(\n    'createProfile',\n    { name: 'John Doe', avatar: 'default.png' },\n    async (input, ctx) => {\n      const profile = await db.profiles.create({\n        ...input,\n        accountId: ctx.results.createAccount.accountId,\n      });\n      return { profileId: profile.id };\n    },\n    async (output) => {\n      await db.profiles.delete(output.profileId);\n    }\n  )\n  .step(\n    'assignRole',\n    { role: 'user' },\n    async (input, ctx) => {\n      await db.roles.assign({\n        accountId: ctx.results.createAccount.accountId,\n        role: input.role,\n      });\n      return { role: input.role };\n    },\n    async (output, ctx) => {\n      await db.roles.revoke({\n        accountId: ctx.results.createAccount.accountId,\n        role: output.role,\n      });\n    }\n  )\n  .step(\n    'sendWelcomeEmail',\n    { template: 'onboarding' },\n    async (input, ctx) => {\n      const emailId = await emailService.send({\n        to: ctx.results.createAccount.email,\n        template: input.template,\n      });\n      return { emailId };\n    }\n  )\n  .run();\n```\n\n## How It Works\n\n1. **Step Execution**: Steps are executed sequentially in the order they're defined\n2. **Context Propagation**: Each step receives results from all previous steps via `ctx.results`\n3. **Type Safety**: TypeScript automatically infers and validates the types of step inputs and outputs\n4. **Error Handling**: If any step throws an error, execution stops immediately\n5. **Automatic Rollback**: Compensation functions are called in reverse order for all successfully executed steps\n6. **Rollback Resilience**: If a rollback fails, an error is logged but other rollbacks continue\n\n## Type Safety\n\nsaga-ts provides full type inference:\n\n```typescript\nconst result = await createSaga()\n  .step('step1', { value: 10 }, async (input) => {\n    return { doubled: input.value * 2 };\n  })\n  .step('step2', { multiplier: 3 }, async (input, ctx) => {\n    // TypeScript knows ctx.results.step1.doubled is a number\n    const result = ctx.results.step1.doubled * input.multiplier;\n    return { final: result };\n  })\n  .run();\n\nif (result.status === 'success') {\n  // TypeScript knows the exact shape of results\n  const doubled: number = result.results.step1.doubled;\n  const final: number = result.results.step2.final;\n}\n```\n\n## Error Handling Best Practices\n\n1. **Always provide rollback functions** for steps that modify state\n2. **Keep rollbacks idempotent** - they may be called multiple times\n3. **Log rollback failures** - the library logs them but continues\n4. **Test your rollback logic** - ensure compensations work correctly\n\n## Known Limitations\n\nThis library is designed for simplicity and type safety. For more complex orchestration needs, consider these limitations:\n\n### Sequential Execution Only\nSteps execute one at a time in order. If you have independent steps that could run in parallel, they will still wait for each other to complete.\n\n```typescript\n// These steps run sequentially even though they're independent\n.step('fetchUserProfile', {}, async () => { /* ... */ })\n.step('fetchUserPreferences', {}, async () => { /* ... */ })\n```\n\n**Workaround**: Run independent sagas concurrently using `Promise.all()`.\n\n### No Built-in Retry Logic\nIf a step fails due to transient issues (network timeout, rate limiting), the entire saga fails and rolls back. There's no automatic retry with exponential backoff.\n\n**Workaround**: Implement retry logic inside your step functions or wrap the saga execution in a retry handler.\n\n### Limited Rollback Observability\nWhen rollback functions fail, errors are logged to `console.error` but not included in the saga result. You won't know if compensation partially failed.\n\n**Workaround**: Implement your own error tracking inside rollback functions if you need detailed compensation audit logs.\n\n### In-Memory Only\nAll step results are stored in memory during execution. For long-running sagas or steps that return large payloads, this could cause memory issues. There's no persistence layer.\n\n**Implication**: If your process crashes mid-saga, there's no way to resume. For critical workflows, consider workflow engines like Temporal or Conductor.\n\n### No Conditional Logic\nEvery step runs unless a previous step fails. You can't skip steps based on conditions.\n\n```typescript\n// Can't do: \"if user is premium, skip payment step\"\n```\n\n**Workaround**: Use conditional logic inside step functions to return early, or split into separate sagas.\n\n### No Timeout or Cancellation\nSteps can run indefinitely. There's no built-in timeout mechanism or AbortSignal support.\n\n**Workaround**: Implement timeouts within your step functions using `Promise.race()` or AbortController.\n\n## When to Use Sagas\n\nSagas are ideal for:\n- Multi-step business processes that need to be atomic\n- Distributed transactions across services\n- Complex workflows with compensation logic\n- Operations that need to maintain consistency across failures\n\n## License\n\nMIT\n\n## Contributing\n\nContributions are welcome! Please feel free to submit a Pull Request.\n","readmeFilename":"README.md"}