Skip to content

TypeScript codegen

@syntarie/tracking/generated/events is a generated file: the codegen tool reads your tracking plan and emits a typed track() whose name argument is narrowed to your registered event names and whose props argument is narrowed to the per-event interface.

// Without codegen — string literals, untyped props:
import { track } from '@syntarie/tracking';
track('checkout_compleded', { plan: 'pro' });
// ^^^^^^^^^^^^^^^^^^ typo, accepted at compile time, drops at the collector
// With codegen — names and props checked at compile time:
import { track } from '@syntarie/tracking/generated/events';
track('checkout_compleded', { plan: 'pro' });
// ~~~~~~~~~~~~~~~~~~~~ Type error: argument of type "checkout_compleded"
// is not assignable to parameter of type
// TrackedEventName.

Behind the scenes the runtime function is the same untyped track. The generated subpath only adds compile-time overloads — there is no runtime overhead and no extra bytes shipped if you import only from the generated path.

The codegen CLI lives in tooling/codegen/. From the repo root:

Terminal window
pnpm codegen

This:

  1. Reads shared/tracking-plan/schema.yaml (or whatever path you configure).
  2. Validates the plan against the canonical tracking-plan schema.
  3. Emits sdk/src/generated/events.ts with one overload per event.

You should run codegen as a prebuild step in CI so generated output never drifts from the source plan.

For a plan event like:

checkout_completed:
description: User finished checkout and a payment intent succeeded.
owner: ecommerce
classification: revenue
props:
type: object
required: [order_id, total_cents, currency]
additionalProperties: false
properties:
order_id: { type: string, minLength: 1 }
total_cents: { type: integer, minimum: 0 }
currency: { type: string, pattern: "^[A-Z]{3}$" }
coupon_code: { type: string }

The generator emits roughly:

export interface CheckoutCompletedProps {
readonly order_id: string;
readonly total_cents: number;
readonly currency: string;
readonly coupon_code?: string;
}
export interface TrackedEvents {
// ...other events...
readonly checkout_completed: CheckoutCompletedProps;
}
export type TrackedEventName = keyof TrackedEvents;
export function track<N extends TrackedEventName>(
name: N,
props: TrackedEvents[N],
): void;

You can extend TrackedEvents via TypeScript declaration merging if you need to track an event before it lands in the central plan:

declare module '@syntarie/tracking/generated/events' {
interface TrackedEvents {
readonly experimental_event: { readonly variant: string };
}
}

The runtime path is unchanged — the merge only adds a compile-time narrowing for that name.

Adding a new event to the plan and re-running codegen is a minor bump of @syntarie/tracking (a new key on TrackedEvents). Removing an event inside a major is forbidden by the semver policy. See Versioning §1.2.