@playstack/events
Typed in-process domain events for dependency inversion between Playstack packages.
Free. MIT licensed. Check preview availability before installing. See package access.
@playstack/events is the typed seam through which a capability describes what happened without selecting a framework, broker, or event store.
Install
npm install @playstack/core @playstack/eventsDefine and compose registries
Payload validators use Standard Schema v1, so Zod and other conforming schemas work directly.
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
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.
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. For API lookup and partial-example conventions, see Reading the reference. Provider failures, lifecycle requirements and application responsibilities remain described in the guide above; types alone do not establish production safety.