Dotyc
API Reference

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

ParameterDescription
nameThe wire name of the event (e.g. 'feature_used'). This is the key you pass to track(). Must be unique across the catalog.
definition.tagsStatic 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.propertiesThe property schema. Any Standard Schema validator works: Zod, Valibot, ArkType, ... Dotyc only depends on the ~standard interface.
definition.computedOptional. 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's store) 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 through onError.

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 the direct(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's track() type — calling track() with a trusted event name on an http-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 a schemaHash identifying 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.
  • computed runs at ingest. The emitter stays dumb: derived fields are evaluated server-side after validation, before dispatch.
Draft — subject to change: the exact context object passed to computed functions is not finalized (timestamp is available today), as are the default retry policy and dead-letter shape for durable events.

See also

On this page