---
title: "@playstack/events"
description: "Typed in-process domain events for dependency inversion between Playstack packages."
tags: ["package","events","bridge","free"]
---

{/* package-access:start */}

> **Free.** MIT licensed. Check preview availability before installing. See [package access](/docs/packages#access-policy).

{/* package-access:end */}

`@playstack/events` is the typed seam through which a capability describes what happened without selecting a framework, broker, or event store.

## Install

```sh
npm install @playstack/core @playstack/events
```

## Define and compose registries

Payload validators use Standard Schema v1, so Zod and other conforming schemas work directly.

```ts
import { createEventBridge, defineEvents } from '@playstack/events'
import { z } from 'zod'

const catalogEvents = defineEvents({
  'catalog.product.published': {
    payload: z.object({ productId: z.string(), slug: z.string() }),
    audit: { tier: 'transactional', fields: ['productId'] },
  },
})

export const eventBridge = createEventBridge({
  registries: [catalogEvents, accountsEvents, authEvents],
  clock,
  ids,
  outbox,
  supportedTransactionKinds: ['prisma'],
  routes: [
    { match: 'catalog.product.published', to: [searchIndexer], retry: 3 },
  ],
  failureRecorder,
})

eventBridge.assertReady()
```

Event names require at least three lowercase dot-separated segments. `defineEventSchema(parse)` adapts a small hand-written parser when a schema library is unnecessary.

## Bridge configuration

| `EventBridgeOptions` property | Required | Purpose |
| --- | --- | --- |
| `registries` | Yes | Package and application registries merged into one typed contract. |
| `clock`, `ids` | Yes | Envelope timestamps and identifiers. |
| `outbox` | For durable routes | Transactional observational delivery bridge. |
| `routes` | No | Maps event names to observational handlers and retry counts. |
| `failureRecorder` | No | Captures isolated observational dispatch failures. |
| `supportedTransactionKinds` | No | Allowlist of transaction handles accepted by internal handlers/outbox. |
| `maximumDepth` | No | Limits recursive caused-event chains. |

The returned bridge exposes the merged `registry`, typed `events` emitter, and `assertReady()` completeness check. It owns no application lifecycle.

## Emit options and envelope properties

```ts
await events.emit(
  'catalog.product.published',
  () => ({ productId, slug }),
  { transaction, context: operationContext },
)
```

| Emit option | Meaning |
| --- | --- |
| `transaction` | Application transaction used by same-kind internal handlers and the outbox. |
| `cause` | Source envelope; inherits correlation, actor, scope, and trace and sets causation. |
| `context` | Root `OperationContext` for a new chain. |
| `bestEffort` | Allows explicitly non-critical observational work to fail without rejecting emission. |

Every immutable `EventEnvelope` carries `id`, `name`, validated JSON payload, `occurredAt`, `correlationId`, and optional causation, actor, scope, and W3C trace properties.

## Handler and transport bridges

| Contract | Responsibility |
| --- | --- |
| `InternalEventHandler` | Declares `transactionKind` and handles an event inside the emitting transaction. |
| `ObservationalEventHandler` | Named handler for isolated, retryable application reactions. |
| `OutboxWriter` | Declares `transactionKind` and enqueues the envelope atomically. |
| `DispatchFailureRecorder` | Records event, handler, error, and attempt count. |

Internal handlers implement package invariants and may fail the originating mutation. Observational handlers belong behind the outbox when they cross process or infrastructure boundaries.

`emit` is a no-op that returns `null` when nothing is registered for the event: no internal handler, no observational handler, and no route. An outbox row is written only when an observational handler or route will consume it, so a host that wants every event of a package in the outbox, for a projection or an external consumer, registers an observational handler for it. Transactional audit handlers bound with `@playstack/audit` write audit rows on the emitting transaction and do not by themselves produce outbox rows.

## Boundary

Events is not an event-sourced aggregate log, replay system, or general broker abstraction. Stateful packages expose their persistence seams and publish integration events rather than importing one another; the application chooses durable transport and consumer lifecycle.

{/* package-reference:start */}

## API entry points and requirements

Reference snapshot: `@playstack/events@0.1.0-beta.1`. Import only the entry point your runtime needs. Paths below are relative to the installed package; use **Go to Definition** in your editor to inspect exact parameters, return types and overloads. Do not import the declaration-file paths directly.

| Public entry point | Declaration file |
| --- | --- |
| `@playstack/events` | `./dist/index.d.ts` |
| `@playstack/events/errors` | `./dist/errors.d.ts` |
| `@playstack/events/testing` | `./dist/testing.d.ts` |
| `@playstack/events/playstack.integration.json` | No TypeScript declaration (asset or metadata export). |
| `@playstack/events/package.json` | No TypeScript declaration (asset or metadata export). |

Node.js engine requirement: `>=20`. This is not a claim that every entry point works in browsers or Workers.

### Peer dependencies

This package declares no peer dependencies. Its ordinary dependencies are resolved by the package manager.

For a complete first program, start with [Getting started](/docs/getting-started). For API lookup and partial-example conventions, see [Reading the reference](/docs/packages#reading-the-reference). Provider failures, lifecycle requirements and application responsibilities remain described in the guide above; types alone do not establish production safety.

{/* package-reference:end */}
