Defining Events

Create typed event registries with IgniterTelemetryEvents — namespaces, groups, Zod schemas, and registry descriptors for full TypeScript inference.

Defining Events

IgniterTelemetryEvents is the typed event registry builder. It creates namespaced, schema-validated event definitions that feed into the telemetry builder — giving you autocompletion on emit() calls and optional runtime validation.

Typed events are optional. You can emit any string event name without a registry. Registries give you autocompletion, documentation, and validation when you need it.


Basic Structure

Every event registry starts with a namespace and contains events or groups:

import { IgniterTelemetryEvents } from "@igniter-js/telemetry";
import { z } from "zod";

const MyEvents = IgniterTelemetryEvents
  .namespace("myapp.feature")     // 1. Define namespace
  .event("action.done", schema)   // 2. Add flat events
  .group("subfeature", (g) =>     // 3. Add nested groups
    g.event("start", schema)
     .event("complete", schema)
  )
  .build();                       // 4. Build the descriptor

Flat Events

Define individual, non-grouped events under a namespace:

import { z } from "zod";
import { IgniterTelemetryEvents } from "@igniter-js/telemetry";

const AuthEvents = IgniterTelemetryEvents
  .namespace("igniter.auth")
  .event("login.succeeded", z.object({
    "ctx.user.id": z.string(),
    "ctx.login.method": z.enum(["password", "oauth", "magic_link"]),
    "ctx.login.ip": z.string(),
  }))
  .event("login.failed", z.object({
    "ctx.user.id": z.string().optional(),
    "ctx.login.method": z.string(),
    "ctx.login.reason": z.string(),
  }))
  .event("logout", z.object({
    "ctx.user.id": z.string(),
    "ctx.session.duration_seconds": z.number(),
  }))
  .build();

Emitting these events gives autocompletion:

telemetry.emit("igniter.auth.login.succeeded", {
  attributes: {
    "ctx.user.id": "usr_123",
    "ctx.login.method": "oauth",
    "ctx.login.ip": "192.168.1.1",
  },
});

Grouped Events

Organize related events into groups for logical hierarchy and better autocompletion:

const OrderEvents = IgniterTelemetryEvents
  .namespace("igniter.orders")
  .group("checkout", (g) =>
    g
      .event("started", z.object({
        "ctx.cart.id": z.string(),
        "ctx.cart.item_count": z.number(),
      }))
      .event("completed", z.object({
        "ctx.order.id": z.string(),
        "ctx.order.total": z.number(),
      }))
      .event("abandoned", z.object({
        "ctx.cart.id": z.string(),
        "ctx.cart.elapsed_seconds": z.number(),
      }))
  )
  .group("fulfillment", (g) =>
    g
      .event("picked", z.object({
        "ctx.order.id": z.string(),
        "ctx.warehouse.id": z.string(),
      }))
      .event("shipped", z.object({
        "ctx.order.id": z.string(),
        "ctx.shipment.tracking": z.string(),
      }))
      .event("delivered", z.object({
        "ctx.order.id": z.string(),
        "ctx.delivery.timestamp": z.string(),
      }))
  )
  .build();

Grouped events use dot-separated names:

telemetry.emit("igniter.orders.checkout.completed", {
  attributes: {
    "ctx.order.id": "ord_456",
    "ctx.order.total": 8999,
  },
});

Schema Libraries

The event builder accepts Zod schemas, StandardSchemaV1 schemas, or any schema object with parse/safeParse methods.

Zod is used in all examples but is not bundled. You control which schema library to use — just make sure it implements parse() or safeParse().

// Zod (recommended)
import { z } from "zod";

const schema = z.object({
  "ctx.user.id": z.string(),
});

Adding Events to the Builder

Use .addEvents() to register event descriptors with the telemetry builder:

const telemetry = IgniterTelemetry.create()
  .withService("api")
  .addEvents(AuthEvents)            // First registry
  .addEvents(OrderEvents)           // Second registry
  .addEvents(BillingEvents, {       // With validation options
    mode: "always",
    strict: true,
  })
  .addTransport(loggerAdapter)
  .build();

Validation Options

addEvents() accepts optional validation options per registry:

interface IgniterTelemetryEventsValidationOptions {
  mode?: "development" | "always" | "none";  // default: "development"
  strict?: boolean;                            // default: false
}
OptionDescription
mode: "development"Validate only when NODE_ENV !== "production"
mode: "always"Always validate — throw on schema mismatch even in production
mode: "none"Never validate — events pass through regardless
strict: trueThrow on unknown event names (events not in any registry)

Using the Descriptor Helpers

The built descriptor exposes helpers for getting typed keys and schemas:

const BillingEvents = IgniterTelemetryEvents
  .namespace("igniter.billing")
  .event("cycle.started", z.object({ "ctx.cycle.id": z.string() }))
  .build();

// Get the full event key with namespace
const key = BillingEvents.get.key("cycle.started");
// => "igniter.billing.cycle.started"

// Get the schema for a specific event
const schema = BillingEvents.get.schema("cycle.started");
// => z.ZodObject<{ "ctx.cycle.id": z.ZodString }>

// Type inference
type BillingRegistry = typeof BillingEvents.$Infer.registry;
type BillingKeys = typeof BillingEvents.$Infer.keys;
// => "cycle.started"

Multiple Namespaces

You can register multiple namespaces on the same telemetry instance:

const telemetry = IgniterTelemetry.create()
  .withService("platform")
  .addEvents(AuthEvents)       // namespace: "igniter.auth"
  .addEvents(OrderEvents)      // namespace: "igniter.orders"
  .addEvents(BillingEvents)    // namespace: "igniter.billing"
  .addTransport(loggerAdapter)
  .build();

// All namespaces are available on emit()
telemetry.emit("igniter.auth.login.succeeded", { ... });
telemetry.emit("igniter.orders.checkout.completed", { ... });
telemetry.emit("igniter.billing.invoice.paid", { ... });

Duplicate namespaces throw an error. If you try to register two descriptors with the same namespace, TELEMETRY_DUPLICATE_NAMESPACE is thrown. Use multiple events/groups within a single namespace instead.


Naming Conventions

Recommended event naming patterns:

PatternExampleUse Case
namespace.group.actionigniter.jobs.job.completedGrouped domain events
namespace.verb.past_tenseigniter.auth.login.succeededLifecycle events
namespace.verb.presentigniter.billing.cycle.startedProcess start events
namespace.noun.actionigniter.mail.recipient.addedEntity events

Key Guidelines

  • Use dot-separated names for hierarchy (igniter.jobs.worker.started)
  • Use past tense for completed actions (succeeded, failed, completed)
  • Use ctx.* prefix for attribute keys (ctx.user.id, ctx.order.total)
  • Use lowercase for all event names and attribute keys
  • Keep event names descriptive — prefer payment.intent.created over pay_created

Next Steps