defineEvent
Define a single catalog event: name, typed properties, tags, delivery guarantee, trust source, and optional computed fields.
Defines one event of your catalog: its wire name, its property schema (any Standard Schema validator), its tags, its delivery guarantee, its trust source, and optional computed fields applied at ingest.
import { defineEvent } from 'dotyc'Signature
function defineEvent<Name extends string, Schema extends StandardSchema>(
name: Name,
definition: {
tags: readonly string[]
delivery?: 'best-effort' | 'durable'
source?: 'anonymous' | 'trusted'
properties: Schema
computed?: Record<string, (ctx: ComputedContext) => unknown>
}
): EventDefinition<Name, Schema>Parameters
| Parameter | Description |
|---|---|
name | The wire name of the event (e.g. 'feature_used'). This is the key you pass to track(). Must be unique across the catalog. |
definition.tags | Static metadata: free-form category strings ('feature', 'marketing', 'trace', ...). Tags are the primary routing dimension for the wiring. |
definition.delivery | 'best-effort' (default) or 'durable'. See below. |
definition.source | 'anonymous' (default) or 'trusted'. See below. |
definition.properties | The property schema. Any Standard Schema validator works: Zod, Valibot, ArkType, ... Dotyc only depends on the ~standard interface. |
definition.computed | Optional. Derived fields computed at ingest (never on the emitter) and merged into the validated properties. |
delivery — the delivery guarantee
Two classes of traffic, declared per event:
'best-effort'(default) — logs, marketing, product traces. Fire-and-forget through the standard pipeline with isolated fan-out. Losing one is acceptable, and that contract is what keeps this class zero-overhead: a catalog with no durable events pays no durability cost at all.'durable'— business facts: usage, billing, quotas. At-least-once delivery: the event is persisted to an outbox (via the dispatcher'sstore) before dispatch, acknowledged to the transport, then retried with backoff per consumer until each one succeeds. A consumer that exhausts its retries moves the event to a dead-letter status and reports throughonError.
Durable delivery implies your adapters may see the same event more than once — deduplicate on eventId (see DotycAdapter). A dispatcher whose catalog contains any durable event requires a store.
source — where the event may come from
'anonymous'(default) — emittable from anywhere: browser, HTTP transport, server.'trusted'— emittable only in-process via thedirect(dispatcher)transport. If a trusted event arrives over the HTTP transport, ingest rejects it. And it never gets that far from your own code: trusted events are excluded from the client emitter'strack()type — callingtrack()with a trusted event name on anhttp-transport emitter is a compile error, not a runtime error.
The intent: a critical fact is emitted where it happens. The API route that performs the action emits the event; the server never has to verify a client's claim about it.
Rule of thumb: any event with a monetary consequence is delivery: 'durable', source: 'trusted'. Usage that feeds billing, subscription changes, credit consumption — durable so it cannot be lost, trusted so it cannot be forged.
Return value
An event definition object. You never use it directly — you pass it to defineCatalog, which anchors type inference for track() and the wiring matchers.
Example
import { defineEvent } from 'dotyc'
import * as v from 'valibot' // or zod, arktype, ... (Standard Schema)
// Product analytics: defaults are fine
export const featureUsed = defineEvent('feature_used', {
tags: ['feature'],
properties: v.object({
feature: v.string(),
}),
computed: {
day: ({ timestamp }) => new Date(timestamp).toISOString().slice(0, 10),
},
})
// Monetary consequence: durable + trusted
export const subscriptionUpgraded = defineEvent('subscription_upgraded', {
tags: ['billing', 'audit'],
delivery: 'durable',
source: 'trusted',
properties: v.object({
plan: v.string(),
}),
})Notes
- Validation is strict for current-schema events. At ingest, an event whose properties do not conform to the schema is rejected, reported through the dispatcher's
onError, and never dispatched. Events emitted by out-of-date clients are handled by schema resolution instead of being dropped. - Every emitted event carries an
eventId(a ULID generated at emission) and aschemaHashidentifying the exact version of this definition it was validated against. See the adapter interface for how they surface. - Tags are free strings here, typed everywhere else. You can invent any tag at definition time. Dotyc infers the literal union of all tags in the catalog, so wiring matchers only accept tags that actually exist — a typo is a compile error.
computedruns at ingest. The emitter stays dumb: derived fields are evaluated server-side after validation, before dispatch.
computed functions is not finalized (timestamp is available today), as are the default retry policy and dead-letter shape for durable events.See also
- defineCatalog — assemble events into a catalog
- createDispatcher — the
storeoption and the durable pipeline - Catalog concepts