/** * Value Object: EventName * Validates and encapsulates a telemetry event name. * Event names must be non-empty, max 64 characters, and follow snake_case convention. */ export class EventName { private static readonly MAX_LENGTH = 64; private static readonly PATTERN = /^[a-z][a-z0-9_]*$/; private constructor(private readonly value: string) {} static create(name: string): EventName { if (!name || name.trim().length === 0) { throw new Error('Event name cannot be empty'); } if (name.length > EventName.MAX_LENGTH) { throw new Error(`Event name must be at most ${EventName.MAX_LENGTH} characters, got ${name.length}`); } if (!EventName.PATTERN.test(name)) { throw new Error(`Event name must be snake_case (lowercase letters, digits, underscores): "${name}"`); } return new EventName(name); } /** * Create an EventName without strict pattern validation (for legacy/external events). * Still enforces max length. */ static createLenient(name: string): EventName { if (!name || name.trim().length === 0) { throw new Error('Event name cannot be empty'); } if (name.length > EventName.MAX_LENGTH) { throw new Error(`Event name must be at most ${EventName.MAX_LENGTH} characters, got ${name.length}`); } return new EventName(name); } toString(): string { return this.value; } equals(other: EventName): boolean { return this.value === other.value; } /** Check if this is a session-related event */ isSessionEvent(): boolean { return this.value === 'app_session_start' || this.value === 'app_session_end'; } /** Check if this is a heartbeat-related event */ isHeartbeatEvent(): boolean { return this.value === 'heartbeat' || this.value === 'app_heartbeat'; } }