{"id":20834204,"url":"https://github.com/marcus-sa/deepkit-restate","last_synced_at":"2025-10-14T17:42:09.187Z","repository":{"id":226242366,"uuid":"768062163","full_name":"marcus-sa/deepkit-restate","owner":"marcus-sa","description":"Build resilient enterprise applications using Deepkit and Restate","archived":false,"fork":false,"pushed_at":"2025-10-07T14:33:02.000Z","size":983,"stargazers_count":10,"open_issues_count":3,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-10-07T16:29:49.092Z","etag":null,"topics":["async-await","deepkit","distributed-systems","durable-execution","event-driven","kafka","microservices","restate","saga","saga-orchestration","typescript"],"latest_commit_sha":null,"homepage":"https://deepkit-restate.js.org","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/marcus-sa.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,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2024-03-06T11:58:32.000Z","updated_at":"2025-10-07T14:33:06.000Z","dependencies_parsed_at":"2024-03-06T17:02:39.752Z","dependency_job_id":"698de59c-0dc8-48f4-a601-15f390f50173","html_url":"https://github.com/marcus-sa/deepkit-restate","commit_stats":null,"previous_names":["marcus-sa/deepkit-restate","marcus-sa/deepstate"],"tags_count":6,"template":false,"template_full_name":null,"purl":"pkg:github/marcus-sa/deepkit-restate","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/marcus-sa%2Fdeepkit-restate","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/marcus-sa%2Fdeepkit-restate/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/marcus-sa%2Fdeepkit-restate/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/marcus-sa%2Fdeepkit-restate/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/marcus-sa","download_url":"https://codeload.github.com/marcus-sa/deepkit-restate/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/marcus-sa%2Fdeepkit-restate/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":279020080,"owners_count":26086806,"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","status":"online","status_checked_at":"2025-10-14T02:00:06.444Z","response_time":60,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"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":["async-await","deepkit","distributed-systems","durable-execution","event-driven","kafka","microservices","restate","saga","saga-orchestration","typescript"],"created_at":"2024-11-18T00:18:29.612Z","updated_at":"2025-10-14T17:42:09.181Z","avatar_url":"https://github.com/marcus-sa.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Deepkit Restate\n\n**Deepkit Restate** is a seamless [Restate](https://restate.dev) integration for [Deepkit](https://deepkit.io). It enables effortless communication between distributed services using durable invocations, service interfaces, and event-driven architecture.\n\n\u003e This documentation assumes familiarity with Deepkit **and** Restate's concepts and lifecycle.\n\n---\n\n## Installation\n\n```bash\nnpm add deepkit-restate\n```\n\n---\n\n## Module Setup\n\nTo use Deepkit Restate, import the `RestateModule` and provide configuration for the components you need:\n\n```ts\nimport { FrameworkModule } from '@deepkit/framework';\nimport { RestateModule } from 'deepkit-restate';\nimport { App } from '@deepkit/app';\n\nconst app = new App({\n  imports: [\n    new FrameworkModule(),\n    new RestateModule({\n      server: {\n        host: 'http://localhost',\n        port: 9080,\n        propagateIncomingHeaders: true, // Forward all incoming headers to service calls\n      },\n      ingress: {\n        url: 'http://localhost:8080',\n      },\n      pubsub: {\n        cluster: 'default',\n        defaultStream: 'all',\n        sse: {\n          url: 'http://localhost:3000',\n        },\n      },\n      admin: {\n        url: 'http://0.0.0.0:9070',\n        deployOnStartup: true,\n      },\n    }),\n  ],\n});\n```\n\nYou can configure any combination of the following:\n\n- **server**: Starts a Restate server\n- **ingress**: Enables outbound service calls\n- **pubsub**: Enables pub/sub event system\n- **admin**: Registers deployments with the admin interface\n\n\u003e If a section is not configured, that functionality will not be available.\n\n## Server Configuration\n\nThe `server` configuration section supports the following options:\n\n| Option | Type | Default | Description |\n|--------|------|---------|-------------|\n| `host` | `string` | - | The host address for the Restate server |\n| `port` | `number` | `9080` | The port number for the Restate server |\n| `propagateIncomingHeaders` | `true \\| string[]` | `undefined` | Controls header propagation to downstream service calls |\n\n### Header Propagation\n\nThe `propagateIncomingHeaders` option controls whether incoming request headers are forwarded when making service-to-service calls:\n\n```ts\n// Forward all incoming headers\nserver: {\n  propagateIncomingHeaders: true\n}\n\n// Forward only specific headers\nserver: {\n  propagateIncomingHeaders: ['authorization', 'x-correlation-id', 'x-tenant-id']\n}\n\n// No header propagation (default)\nserver: {\n  // propagateIncomingHeaders not specified\n}\n```\n\nThis is particularly useful for:\n- **Authentication**: Forwarding authorization tokens through the service call chain\n- **Tracing**: Propagating correlation IDs for distributed tracing\n- **Multi-tenancy**: Passing tenant identifiers to downstream services\n- **Custom context**: Forwarding application-specific headers\n\n\u003e **Note**: When `propagateIncomingHeaders` is enabled, the incoming headers are merged with any explicitly provided headers in the service call options. Explicitly provided headers take precedence over incoming headers.\n\n---\n\n## Serialization (Serde) and Error Handling\n\nAll serialization and deserialization in Deepkit Restate is handled via **BSON** by default.\n\nThis means you can **return** and **accept** any types in your service handlers or saga steps, including:\n\n- Primitives (`string`, `number`, `boolean`, etc.)\n- Plain objects (`{ name: string; age: number }`)\n- Class instances (with properties and methods)\n- Complex nested types and arrays\n- Custom types supported by BSON serialization\n\nThe serialization system preserves type fidelity and structure when encoding and decoding data across the network.\n\n### Automatic Error Forwarding and Serialization\n\n- If an error is **thrown** inside a handler or saga step, it is automatically serialized and forwarded to the caller.\n- This allows errors to be **caught** remotely, preserving the error information.\n- **Custom errors with type information** are supported and **will not be retried** automatically by the system, enabling precise control over error handling and retries.\n\n\u003e We are actively working on an adapter to support JSON serialization as an alternative to BSON.\n\n---\n\n## Calling Services\n\n### `RestateClient`\n\nThe `RestateClient` handles communication between services and objects. It behaves differently depending on whether it is used within or outside an invocation context.\n\nYou can create an ingress client manually:\n\n```ts\nimport { RestateIngressClient } from 'deepkit-restate';\n\nconst client = new RestateIngressClient({ url: 'http://localhost:9080' });\n```\n\nOr retrieve the configured instance via DI:\n\n```ts\nconst client = app.get\u003cRestateClient\u003e();\n```\n\n### Using the Client\n\nTo create a proxy to a **service**:\n\n```ts\nconst user = client.service\u003cUserServiceApi\u003e();\n```\n\nTo create a proxy to an **object**:\n\n```ts\nconst user = client.object\u003cUserObjectApi\u003e();\n```\n\n### Invoking Methods\n\nDurable request (waits for a result):\n\n```ts\nawait client.call(user.create());\n```\n\nFire-and-forget (does not wait for result):\n\n```ts\nawait client.send(user.create());\n```\n\nYou can configure delivery options:\n\n```ts\nawait client.send(user.create(), { delay: '10s' });\n```\n\nFor object calls, specify the key:\n\n```ts\nawait client.call('user-key', user.create());\nawait client.send('user-key', user.create());\n```\n\n---\n\n## Defining Services and Objects\n\n### Services\n\n```ts\ninterface UserServiceHandlers {\n  create(username: string): Promise\u003cUser\u003e;\n}\n\ntype UserServiceApi = RestateService\u003c'user', UserServiceHandlers\u003e;\n\n@restate.service\u003cUserServiceApi\u003e()\nclass UserService implements UserServiceHandlers {\n  constructor(private readonly ctx: RestateServiceContext) {}\n\n  @restate.handler()\n  async create(username: string): Promise\u003cUser\u003e {\n    return User.create(this.ctx, username);\n  }\n}\n```\n\n- Use `@restate.service()` to define a service.\n- Use `@restate.handler()` define handlers.\n- The context (`RestateServiceContext`) provides durable execution helpers.\n\n### Objects\n\n```ts\ninterface UserObjectHandlers {}\n\ntype UserObjectApi = RestateObject\u003c'user', UserObjectHandlers\u003e;\n\n@restate.object\u003cUserObjectApi\u003e()\nclass UserObject implements UserObjectHandlers {}\n```\n\nUse `@restate.object()` to define virtual objects.\n\n\u003e Shared handlers can be declared using `@restate.shared().handler()`.\n\u003e **Note:** Shared handlers use the object context, which is not type-safe. Avoid using `ctx.set()` at runtime in shared handlers.\n\n---\n\n## Middleware\n\nMiddleware provides a way to execute code before handlers are invoked, enabling cross-cutting concerns like authentication, logging, validation, and request preprocessing.\n\n### Defining Middleware\n\nCreate a middleware class that implements the `RestateMiddleware` interface:\n\n```ts\nimport {\n  RestateMiddleware,\n  RestateSharedContext,\n  RestateClassMetadata,\n  RestateHandlerMetadata\n} from 'deepkit-restate';\n\nclass AuthenticationMiddleware implements RestateMiddleware {\n  async execute(\n    ctx: RestateSharedContext,\n    classMetadata: RestateClassMetadata,\n    handlerMetadata?: RestateHandlerMetadata,\n  ): Promise\u003cvoid\u003e {\n    // Access context properties like headers, request data, etc.\n    const headers = ctx.request().headers;\n\n    // Access metadata about the service/object and handler\n    console.log(`Executing ${classMetadata.name}.${handlerMetadata?.name}`);\n    console.log(`Service class: ${classMetadata.classType.name}`);\n\n    // Perform authentication logic\n    if (!headers?.authorization) {\n      throw new Error('Authentication required');\n    }\n\n    // Middleware can modify context or perform side effects\n    console.log('Request authenticated');\n  }\n}\n```\n\n### Applying Middleware\n\n#### Service-Level Middleware\n\nApply middleware to all handlers in a service:\n\n```ts\n@restate.service\u003cUserServiceApi\u003e().middleware(AuthenticationMiddleware)\nclass UserService implements UserServiceHandlers {\n  @restate.handler()\n  async create(username: string): Promise\u003cUser\u003e {\n    // AuthenticationMiddleware runs before this handler\n    return new User(username);\n  }\n}\n```\n\n#### Handler-Level Middleware\n\nApply middleware to specific handlers:\n\n```ts\n@restate.service\u003cUserServiceApi\u003e()\nclass UserService implements UserServiceHandlers {\n  @restate.handler().middleware(ValidationMiddleware)\n  async create(username: string): Promise\u003cUser\u003e {\n    // ValidationMiddleware runs before this handler\n    return new User(username);\n  }\n}\n```\n\n#### Object Middleware\n\nMiddleware works the same way for objects:\n\n```ts\n@restate.object\u003cUserObjectApi\u003e().middleware(LoggingMiddleware)\nclass UserObject implements UserObjectHandlers {\n  @restate.handler()\n  async update(data: UserData): Promise\u003cvoid\u003e {\n    // LoggingMiddleware runs before this handler\n  }\n}\n```\n\n#### Global Middleware\n\nApply middleware to all services and objects:\n\n```ts\nnew RestateModule({\n  // ... other config\n}).addGlobalMiddleware(LoggingMiddleware, MetricsMiddleware);\n```\n\n### Middleware Execution Order\n\nMiddleware executes in the following order:\n\n1. **Global middleware** (in registration order)\n2. **Service/Object-level middleware** (in registration order)\n3. **Handler-level middleware** (in registration order)\n4. **Handler execution**\n\n### Middleware Context\n\nMiddleware receives three parameters providing comprehensive execution context:\n\n#### 1. `RestateSharedContext`\nProvides access to:\n- **Request information**: Headers, method name, service name\n- **Execution context**: Invocation ID, retry information\n- **Restate utilities**: Random number generation, timing functions\n\n#### 2. `RestateClassMetadata`\nProvides information about the service/object being executed:\n- **Service/Object name**: The registered name\n- **Class type**: The actual TypeScript class\n- **Handlers**: All handlers defined on the service/object\n- **Applied middleware**: Middleware configured at the class level\n\n#### 3. `RestateHandlerMetadata` (optional)\nProvides information about the specific handler being executed:\n- **Handler name**: The method name being invoked\n- **Return type**: TypeScript type information for the return value\n- **Arguments type**: TypeScript type information for the parameters\n- **Handler options**: Configuration options for the handler\n- **Applied middleware**: Middleware configured at the handler level\n\n```ts\nclass RequestLoggingMiddleware implements RestateMiddleware {\n  async execute(\n    ctx: RestateSharedContext,\n    classMetadata: RestateClassMetadata,\n    handlerMetadata?: RestateHandlerMetadata,\n  ): Promise\u003cvoid\u003e {\n    console.log(`Executing ${classMetadata.name}.${handlerMetadata?.name || 'unknown'}`);\n    console.log(`Service class: ${classMetadata.classType.name}`);\n    console.log(`Invocation ID: ${ctx.invocationId}`);\n    console.log(`Headers:`, ctx.request?.headers);\n\n    // Access handler-specific information\n    if (handlerMetadata) {\n      console.log(`Handler return type: ${handlerMetadata.returnType.kind}`);\n      console.log(`Handler middleware count: ${handlerMetadata.middlewares.length}`);\n    }\n\n    // Access class-level information\n    console.log(`Service middleware count: ${classMetadata.middlewares.length}`);\n    console.log(`Total handlers: ${classMetadata.handlers.size}`);\n  }\n}\n```\n\n### Error Handling in Middleware\n\nIf middleware throws an error, the handler will not execute and the error will be propagated to the caller:\n\n```ts\nclass ValidationMiddleware implements RestateMiddleware {\n  async execute(\n    ctx: RestateSharedContext,\n    classMetadata: RestateClassMetadata,\n    handlerMetadata?: RestateHandlerMetadata,\n  ): Promise\u003cvoid\u003e {\n    // This error will prevent handler execution\n    if (!this.isValidRequest(ctx, handlerMetadata)) {\n      throw new Error(`Invalid request format for ${classMetadata.name}.${handlerMetadata?.name}`);\n    }\n  }\n\n  private isValidRequest(ctx: RestateSharedContext, handlerMetadata?: RestateHandlerMetadata): boolean {\n    // Validation logic can use both context and metadata\n    return true; // Simplified example\n  }\n}\n```\n\n### Dependency Injection\n\nMiddleware classes support dependency injection like any other service:\n\n```ts\nclass DatabaseMiddleware implements RestateMiddleware {\n  constructor(private readonly database: Database) {}\n\n  async execute(\n    ctx: RestateSharedContext,\n    classMetadata: RestateClassMetadata,\n    handlerMetadata?: RestateHandlerMetadata,\n  ): Promise\u003cvoid\u003e {\n    // Use injected dependencies and metadata\n    await this.database.logRequest({\n      invocationId: ctx.invocationId,\n      serviceName: classMetadata.name,\n      handlerName: handlerMetadata?.name,\n      serviceClass: classMetadata.classType.name,\n    });\n  }\n}\n```\n\nMiddleware classes are automatically resolved by the dependency injection system when applied to services, objects, or handlers. No manual registration in the providers array is required.\n\n### Using Metadata in Middleware\n\nThe metadata parameters enable powerful middleware capabilities:\n\n#### Service-Specific Logic\n```ts\nclass ServiceSpecificMiddleware implements RestateMiddleware {\n  async execute(\n    ctx: RestateSharedContext,\n    classMetadata: RestateClassMetadata,\n    handlerMetadata?: RestateHandlerMetadata,\n  ): Promise\u003cvoid\u003e {\n    // Apply different logic based on service name\n    if (classMetadata.name === 'payment') {\n      await this.validatePaymentSecurity(ctx);\n    } else if (classMetadata.name === 'user') {\n      await this.validateUserPermissions(ctx);\n    }\n  }\n}\n```\n\n#### Handler-Specific Behavior\n```ts\nclass HandlerSpecificMiddleware implements RestateMiddleware {\n  async execute(\n    ctx: RestateSharedContext,\n    classMetadata: RestateClassMetadata,\n    handlerMetadata?: RestateHandlerMetadata,\n  ): Promise\u003cvoid\u003e {\n    // Skip validation for read-only operations\n    if (handlerMetadata?.name?.startsWith('get') || handlerMetadata?.name?.startsWith('list')) {\n      return; // Skip middleware for read operations\n    }\n\n    // Apply strict validation for write operations\n    await this.validateWritePermissions(ctx, classMetadata.name);\n  }\n}\n```\n\n#### Dynamic Configuration\n```ts\nclass ConfigurableMiddleware implements RestateMiddleware {\n  async execute(\n    ctx: RestateSharedContext,\n    classMetadata: RestateClassMetadata,\n    handlerMetadata?: RestateHandlerMetadata,\n  ): Promise\u003cvoid\u003e {\n    // Use handler options for configuration\n    const timeout = handlerMetadata?.options?.timeout || 30000;\n    const retries = handlerMetadata?.options?.retries || 3;\n\n    // Apply configuration-based logic\n    await this.setupTimeoutAndRetries(ctx, timeout, retries);\n  }\n}\n```\n\n---\n\n## Dependency Injection: Calling Other Services\n\nYou can inject the client and proxy APIs into a service:\n\n```ts\n@restate.service\u003cUserServiceApi\u003e()\nclass UserService {\n  constructor(\n    private readonly client: RestateClient,\n    private readonly payment: PaymentServiceApi,\n  ) {}\n\n  @restate.handler()\n  async create(user: User): Promise\u003cvoid\u003e {\n    await this.client.call(this.payment.create('Test', user));\n  }\n}\n```\n\nFor objects, remember to provide a key:\n\n```ts\nawait this.client.call('payment-id', this.payment.create('Test'));\n```\n\n---\n\n## Durable Helpers\n\n### `run` blocks\n\nThe `ctx.run()` helper ensures a block is executed durably:\n\n```ts\nconst user = await this.ctx.run\u003cUser\u003e('create user', () =\u003e new User(username));\n```\n\nWithout a type argument, the return value is ignored:\n\n```ts\nconst none = await this.ctx.run('create user', () =\u003e new User(username));\n```\n\n### Awakeables\n\nUsed to pause and resume execution:\n\n```ts\nconst awakeable = this.ctx.awakeable\u003cUser\u003e();\n```\n\nTo resume:\n\n```ts\nthis.ctx.resolveAwakeable\u003cUser\u003e();\n```\n\n### Durable State\n\nStore and retrieve durable state using the context:\n\n```ts\nawait this.ctx.set\u003cUser\u003e('user', user);\n```\n\n```ts\nconst user = await this.ctx.get\u003cUser\u003e('user');\n```\n\n---\n\n## Pub/Sub\n\n### Server Setup\n\nSet up a dedicated application for handling events.\n\n```ts\nimport { App } from '@deepkit/app';\nimport { FrameworkModule } from '@deepkit/framework';\nimport { RestateModule } from 'deepkit-restate';\nimport { RestatePubsubServerModule } from 'deepkit-restate/pubsub-server';\n\nawait new App({\n  imports: [\n    new FrameworkModule({ port: 9090 }),\n    new RestateModule({ server: { port: 9080 } }),\n    new RestatePubSubServerModule({\n      sse: {\n        all: true,\n        autoDiscover: true,\n        nodes: ['localhost:9090'],\n      },\n    }),\n  ],\n}).run();\n```\n\n### Publishing Events\n\nInside a service handler (durable):\n\n```ts\nconstructor(private readonly publisher: RestateEventPublisher) {}\n\nawait this.publisher.publish([new UserCreatedEvent(user)]);\n```\n\nOutside of invocation (non-durable):\n\n```ts\nconst publisher = app.get\u003cRestateEventPublisher\u003e();\nawait publisher.publish([new UserCreatedEvent(user)]);\n```\n\n\u003e Only classes are supported as events.\n\n\u003e Events are versioned by hashing their structure.\n\n### Handling Events\n\nOnly services can define event handlers:\n\n```ts\n@restate.service\u003cUserServiceApi\u003e()\nclass UserService {\n  @(restate.event\u003cUserCreatedEvent\u003e().handler())\n  async onUserCreated(event: UserCreatedEvent): Promise\u003cvoid\u003e {\n    // handle event\n  }\n}\n```\n\n### SSE Delivery\n\nServer-Sent Events (SSE) allow real-time delivery of events to connected subscribers.\n\n#### Subscribing to Events Outside of Services\n\nSubscribe to events from contexts like HTTP or RPC controllers:\n\n```ts\nconst subscriber = app.get\u003cRestateEventSubscriber\u003e();\n\nconst unsubscribe = await subscriber.subscribe\u003cUserCreatedEvent\u003e(event =\u003e {\n  // handle event\n});\n\nawait unsubscribe();\n```\n\nYou can also use union types to subscribe to multiple events.\n\n#### Configuration (Global)\n\nYou can configure global SSE delivery behavior in `RestatePubSubServerModule`:\n\n```ts\nnew RestatePubSubServerModule({\n  sse: {\n    all: true,\n    autoDiscover: true,\n    nodes: ['events-1.internal:9090', 'events-2.internal:9090'],\n  },\n});\n```\n\n| Option             | Type       | Description                                                                   |\n|--------------------| ---------- |-------------------------------------------------------------------------------|\n| `sse.all`          | `boolean`  | If `true`, all published events will be delivered via SSE by default.         |\n| `sse.autoDiscover` | `boolean`  | When enabled, resolves peer IPs via DNS to fan out SSE events to other nodes. |\n| `sse.nodes`        | `string[]` | List of peer server URLs for fan-out.                                         |\n\n\u003e SSE fan-out is stateless and opportunistic. Each node will attempt to push matching events to other known nodes.\n\n#### Overriding per Publish\n\nYou can override the global SSE setting by passing `{ sse: true }` in the publish options:\n\n```ts\nawait publisher.publish([new UserCreatedEvent(user)], {\n  sse: true,\n});\n```\n\nBehavior summary:\n\n- If `sse.all` is **true**, SSE is used by default unless explicitly disabled.\n- If `sse.all` is **false**, SSE is off by default — but you can still enable it by passing `sse: true`.\n\n\u003e Only events published with SSE enabled will be streamed to subscribers.\n\n# Sagas\n\nSagas provide a powerful way to orchestrate complex, long-running workflows that involve multiple services. They support **stepwise execution**, **compensation (rollback)**, **reply handling**, and **waiting for external events** (via awakeables).\n\n---\n\n## What is a Saga?\n\nA **Saga** is a workflow pattern that manages distributed transactions and side effects in a coordinated way, including compensations for failures. In Deepkit Restate, you define sagas by extending the `Saga\u003cT\u003e` class and using the `@restate.saga\u003cApi\u003e()` decorator.\n\n---\n\n## Defining a Saga Workflow\n\nSagas are defined using a fluent builder pattern in the `definition` property:\n\n- `step()`: Defines a new step in the saga.\n- `invoke(handler)`: Calls a method in your saga class to perform an action or service call.\n- `compensate(handler)`: Defines a rollback method if the step fails or the saga is aborted.\n- `onReply\u003cEventType\u003e(handler)`: Registers an event handler for replies to invoked actions.\n- `build()`: Finalizes the saga definition.\n\n---\n\n## Awakeables\n\nAwakeables are special constructs to **wait for asynchronous external events**. They provide a promise you can `await` to pause saga execution until an event occurs.\n\nCreate awakeables with the saga context inside your saga methods:\n\n```ts\nthis.confirmTicketAwakeable = this.ctx.awakeable\u003cTicketConfirmed\u003e();\n```\n\n---\n\n## Using the Saga Context\n\nThe `RestateSagaContext` (`this.ctx`) provides utilities like:\n\n- `awakeable\u003cT\u003e()`: Creates an awakeable to wait for events.\n- `set\u003cT\u003e(key, value)`: Persist state data during saga execution.\n- `get\u003cT\u003e(key)`: Retrieve persisted state.\n\n---\n\n## Calling Other Services\n\nAll service calls inside invocation handlers automatically use the underlying `client.call`. This means:\n\n- You **do not need to manually call `client.call`** within your saga handlers.\n- Only **service calls** are supported currently (no direct calls to objects).\n- The framework handles communication and reply handling.\n\n---\n\n## Example: Simplified CreateOrderSaga\n\n```ts\nimport {\n  restate,\n  Saga,\n  RestateSagaContext,\n  RestateAwakeable,\n} from 'deepkit-restate';\n\n@restate.saga\u003cCreateOrderSagaApi\u003e()\nexport class CreateOrderSaga extends Saga\u003cCreateOrderSagaData\u003e {\n  confirmTicketAwakeable?: RestateAwakeable\u003cTicketConfirmed\u003e;\n\n  readonly definition = this.step()\n    .invoke(this.create)\n    .compensate(this.reject)\n    .step()\n    .invoke(this.createTicket)\n    .onReply\u003cTicketCreated\u003e(this.handleTicketCreated)\n    .step()\n    .invoke(this.waitForTicketConfirmation)\n    .build();\n\n  constructor(\n    private readonly order: OrderServiceApi,\n    private readonly kitchen: KitchenServiceApi,\n    private readonly ctx: RestateSagaContext,\n  ) {\n    super();\n  }\n\n  create(data: CreateOrderSagaData) {\n    return this.order.create(data.orderId, data.orderDetails);\n  }\n\n  reject(data: CreateOrderSagaData) {\n    return this.order.reject(data.orderId);\n  }\n\n  createTicket(data: CreateOrderSagaData) {\n    this.confirmTicketAwakeable = this.ctx.awakeable\u003cTicketConfirmed\u003e();\n    return this.kitchen.createTicket(\n      data.orderDetails.restaurantId,\n      data.orderId,\n      data.orderDetails.lineItems,\n      this.confirmTicketAwakeable.id,\n    );\n  }\n\n  handleTicketCreated(data: CreateOrderSagaData, event: TicketCreated) {\n    data.ticketId = event.ticketId;\n  }\n\n  async waitForTicketConfirmation(data: CreateOrderSagaData) {\n    await this.confirmTicketAwakeable!.promise;\n  }\n}\n```\n\n## Starting a Saga and Retrieving Its State\n\nAfter defining your saga, you typically want to **start** an instance of it and later **query its state** to track progress or outcome.\n\n### Creating a Saga Client\n\nUse the client to create a saga proxy:\n\n```ts\nconst createOrderSaga = client.saga\u003cCreateOrderSagaApi\u003e();\n```\n\nThis creates a handle to interact with the saga.\n\n---\n\n### Starting a Saga Instance\n\nTo start a saga, call `start` with the saga’s unique ID and initial input data:\n\n```ts\nconst startStatus = await createOrderSaga.start(orderId, {\n  id: orderId,\n  orderTotal: 10.5,\n  customerId,\n});\n```\n\n- `orderId` uniquely identifies the saga instance.\n- The second argument is the initial data payload to pass to the saga.\n- `start` returns the initial status of saga execution.\n\n---\n\n### Querying the Saga State\n\nAt any time, you can query the current state of the saga instance by its ID using `state`:\n\n```ts\nconst state = await createOrderSaga.state(orderId);\n```\n\nThis returns the persisted saga data reflecting its current progress, e.g., which step it is on, and any state variables updated along the way.\n\n---\n\n### Notes\n\n- The saga `start` call triggers the first step of your saga workflow.\n- The saga state reflects all persisted data and progress, useful for monitoring or troubleshooting.\n- You can invoke `start` only once per unique saga instance ID.\n- Subsequent state changes happen asynchronously as the saga progresses.\n\n### Summary\n\n- Sagas manage multi-step distributed workflows with clear compensation.\n- Steps can invoke service calls, wait for replies, or wait for external events.\n- Awakeables let you asynchronously wait inside sagas for external confirmations.\n- Saga state can be persisted and retrieved with the saga context.\n- Invocation handlers automatically handle calling services; no manual client calls needed.\n- Currently, only service calls are supported, no direct object calls with keys.\n- Compensation methods help rollback on failure or abort scenarios.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmarcus-sa%2Fdeepkit-restate","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmarcus-sa%2Fdeepkit-restate","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmarcus-sa%2Fdeepkit-restate/lists"}