Skip to main content
The @ooneex/analytics component tracks product events, user behavior, and metrics. It provides an IAnalytics interface and a @decorator.analytics() decorator so you can build typed, purpose-specific tracking classes, register them with the container, and send events to whatever backend you choose.

Why this component

  • One method to learn. capture() records an event from a typed options object.
  • Type-safe events. Each analytics class defines its own capture options type instead of an untyped bag of fields.
  • Container-managed. Register with a decorator and resolve from the container — no manual wiring.
  • Backend agnostic. Implement IAnalytics to target any sink (a data warehouse, an HTTP endpoint, a queue) while keeping the same call sites.

How it works

You define an analytics class, implement capture() with your tracking logic, and register it with @decorator.analytics(). The container manages its lifecycle; callers resolve the class and call capture() with a typed options object.
container.get(MyAnalytics)        // resolve the registered class
  → analytics.capture(options)    // your implementation forwards the event
  → your backend                  // warehouse, HTTP endpoint, queue, ...
Because the interface is the only contract, you can swap or stub the backend without touching any call site.

Decorator and usage

@decorator.analytics()

Registers an analytics class with the container. It accepts an optional scope (defaults to singleton).
import { decorator } from "@ooneex/analytics";
import type { IAnalytics } from "@ooneex/analytics";

type PageViewOptionsType = {
  userId: string;
  page: string;
};

@decorator.analytics()
export class PageViewAnalytics implements IAnalytics<PageViewOptionsType> {
  public capture(options: PageViewOptionsType): void {
    // Tracking logic for page views.
  }
}
Resolve it from the container and call capture():
import { container } from "@ooneex/container";

const analytics = container.get(PageViewAnalytics);
analytics.capture({ userId: "user-123", page: "/pricing" });
Choose a non-default scope when you need a fresh instance per request:
import { EContainerScope } from "@ooneex/container";

@decorator.analytics(EContainerScope.Request)
export class RequestAnalytics implements IAnalytics {
  public capture(options: Record<string, unknown>): void {
    // New instance per HTTP request.
  }
}

Capturing events

capture() takes the typed options object your class defines — model it on the event you track. Keep one class per concern so each has a precise shape.
type PurchaseOptionsType = {
  userId: string;
  productId: string;
  price: number;
  currency: string;
};

@decorator.analytics()
export class PurchaseAnalytics implements IAnalytics<PurchaseOptionsType> {
  public capture(options: PurchaseOptionsType): void {
    // Forward the event to your backend.
  }
}
const analytics = container.get(PurchaseAnalytics);
analytics.capture({
  userId: "user-123",
  productId: "prod-456",
  price: 99.99,
  currency: "USD",
});

Exceptions

The component ships AnalyticsException for signaling analytics failures from your implementation. It carries a machine-readable key, a human-readable message, and a data object, so callers can branch on the key.
import { AnalyticsException } from "@ooneex/analytics";

@decorator.analytics()
export class PurchaseAnalytics implements IAnalytics<PurchaseOptionsType> {
  public capture(options: PurchaseOptionsType): void {
    if (!options.userId) {
      throw new AnalyticsException("Missing user id", "USER_ID_REQUIRED", {
        event: "purchase_completed",
      });
    }
    // Forward the event to your backend.
  }
}
Catch it at the call site to handle analytics errors without breaking the request:
import { AnalyticsException } from "@ooneex/analytics";

try {
  analytics.capture(options);
} catch (error) {
  if (error instanceof AnalyticsException) {
    logger.error(`Analytics error [${error.key}]: ${error.message}`, error.data);
  } else {
    throw error;
  }
}

Best practices

  • Type your capture options. Replace Record<string, unknown> with a precise options type per class so events stay consistent.
  • One class per tracked concern. A PageViewAnalytics, a CheckoutAnalytics, etc. — small, focused classes are easier to test and reason about.
  • Name events consistently. Use a stable convention like snake_case verbs (purchase_completed, button_clicked) so dashboards stay clean.
  • Keep backend credentials in the environment. Load any keys your implementation needs from .env; never hard-code them.
  • capture() is fire-and-forget. It returns void — keep it lightweight and off the request’s critical path.
  • Throw AnalyticsException with a stable key. Callers branch on the key; keep keys constant and put variable detail in data.

CLI command

Scaffold an analytics class and its test file with the generator. It writes the class under modules/<module>/src/analytics/<Name>Analytics.ts and installs @ooneex/analytics if it is missing.
# Interactive: prompts for the name
ooneex analytics:create

# Provide the name
ooneex analytics:create --name=PageView

# Target a module and overwrite
ooneex analytics:create --name=PageView --module=tracking --override
OptionDescriptionDefault
--nameAnalytics class name. The Analytics suffix is appended automatically.Prompted if omitted
--moduleTarget module the class is generated into.shared
--overrideOverwrite an existing class without prompting.false
The generated class starts from this shape, ready for you to type the options and implement capture():
import { decorator } from "@ooneex/analytics";
import type { IAnalytics } from "@ooneex/analytics";

type CaptureOptionsType = Record<string, unknown>;

@decorator.analytics()
export class PageViewAnalytics<T extends CaptureOptionsType = CaptureOptionsType>
  implements IAnalytics<T>
{
  public capture(options: T): void {
    // console.log("Analytics captured:", options);
  }
}
See analytics:create for the full command reference.

Use with Claude and Codex

The generator ships a matching analytics:create skill. It runs the scaffold and then guides your AI agent through completing the class — defining a proper capture options type and implementing the tracking logic in capture(). Initialize the skills once for your agent:
ooneex claude:init
Then ask Claude in natural language — it maps the request to the generator, runs it, and fills in the implementation:
Prompt
Add analytics for page views that captures the user id and page path.
For example, the prompt above maps to analytics:create --name=PageView, then types the capture options and implements capture().