Skip to content

Automattic/EventHorizon

Repository files navigation

Event Horizon

A multi-language code generation tool for type-safe event tracking. The system takes YAML schema definitions as input and generates equivalent tracking implementations for Kotlin, Swift, TypeScript, and JSON schema formats.

Input Schema

# Required key defining schema's format version.
schemaVersion: 1

# List of platforms that are available for code generation.
platforms:
  - android
  - ios
  - web
  - desktop

# List of events
events:
  user_signup:
    # Optional key.
    # '_metadata' is a reserved keyword and cannot be used as a property
    _metadata:
      # Optional key.
      description: Some description
      # Optional key.
      # List of platforms for which event should not be generated. Must use predeclared platforms.
      # If key is not present event will be generated for all platforms.
      excludedPlatforms:
        - android
        - web
    # Optional properties used with an event
    user_id:
      # Required. Type of the property for the generated code.
      # Must be one of [text, boolean, number, <predeclared enum reference>].
      type: text
      # Optional key.
      description: Some description
      # Optional key.
      # Defines if a property can be null. Must be either a boolean or a list of predeclared platforms.
      # If key is not present property is assumed to be not null.
      optional: true
    signup_provider:
      type: signup_type
      description: Some description
      optional:
        - android
        - ios

# List of enums used for property types
enums:
  signup_type:
    - google
    - facebook
    - apple

CLI

The CLI supports two primary operation modes:

  • Verification Mode: Validates input YAML schema without generating code.
  • Generation Mode: Parses input and generates code using the specified format and platform.
Option Short Description Required
--input-file -i Input schema file Yes
--output-path -o Output path used for generated files Yes (for generation)
--output-platform -p Output platform for code generation Conditional*
--output-format -f Format: kotlin, swift, ts, json Yes (for generation)
--namespace -n Namespace used for generated code No
--verify -v Only run input file verification No
--help -h Show help message and exit No

*Required for generation when schema declares availablePlatforms and format is not json.

Generated code

Event Horizon generates compact code that can be integrated with external analytics tools.

Kotlin

Generated code:

class EventHorizon(
  private val eventSink: (String, Map<String, Any>) -> Unit,
) {
  fun track(event: Trackable) {
    eventSink(event.trackableName, event.trackableProperties)
  }
}

interface Trackable {
  val trackableName: String
  val trackableProperties: Map<String, Any>
}

data class UpNextQueueReorderedEvent(
  val direction: QueueDirection,
  /**
   * The number of slots the episode was moved
   */
  val slots: Number?,
  /**
   * Whether the episode was moved to the next item that will play
   */
  val isNext: Boolean,
  val source: String,
) : Trackable {
  override val trackableName: String
    get() = "up_next_queue_reordered"

  override val trackableProperties: Map<String, Any>
    get() = buildMap<String, Any> {
      put("direction", direction)
      if (slots != null) {
        put("slots", slots)
      }
      put("is_next", isNext)
      put("source", source)
    }
}

enum class QueueDirection {
  Up {
    override fun toString(): String = "up"
  },
  Down {
    override fun toString(): String = "down"
  },
}

Integration and usage:

val tracker: AnalyticsTracker = TODO()
val eventHorizon = EventHorizon { eventName, eventProperties ->
  // delegation to analytics tracker
}


val event = UpNextQueueReorderedEvent(
  direction = QueueDirection.Up,
  slots = 2,
  isNext = false,
  episodeUuid = episode.uuid,
)
eventHorizon.track(event)

Swift

Generated code:

class EventHorizon {
  private let eventSink: (String, [AnyHashable : Any]) -> Void

  init(eventSink: @escaping (String, [AnyHashable : Any]) -> Void) {
    self.eventSink = eventSink
  }

  func track(_ event: Trackable) {
    eventSink(event.trackableName, event.trackableProperties)
  }
}

protocol Trackable {
  var trackableName: String { get }
  var trackableProperties: [AnyHashable : Any] { get }
}

/**
 * When the user moves (up or down) one of the episodes
 */
struct UpNextQueueReorderedEvent: Trackable {
  let direction: QueueDirection
  /**
   * The number of slots the episode was moved
   */
  let slots: (any Numeric)?
  /**
   * Whether the episode was moved to the next item that will play
   */
  let isNext: Bool
  let episodeUuid: String

  var trackableName: String {
    return "up_next_queue_reordered"
  }

  var trackableProperties: [AnyHashable : Any] {
    var props: [AnyHashable : Any] = [:]
    props["direction"] = direction.analyticsValue
    if let slots = slots {
      props["slots"] = slots
    }
    props["is_next"] = isNext
    props["episode_uuid"] = episodeUuid
    return props
  }

  init(
    direction: QueueDirection,
    slots: (any Numeric)?,
    isNext: Bool,
    episodeUuid: String
  ) {
    self.direction = direction
    self.slots = slots
    self.isNext = isNext
    self.episodeUuid = episodeUuid
  }
}

enum QueueDirection: String {
  case up = "up"
  case down = "down"

  var analyticsValue: String {
    return rawValue
  }
}

Integration and usage:

let tracker: AnalyticsTracker = TODO()
let eventHorizon = EventHorizon { eventName, eventProperties ->
   // delegation to analytics tracker
}


let event = UpNextQueueReorderedEvent(
  direction: .up,
  slots: 2,
  isNext: false,
  episodeUuid: episode.uuid,
)
eventHorizon.track(event)

TypeScript

Generated code:

export type Trackable = {
  // When the user moves (up or down) one of the episodes
  "up_next_queue_reordered": {
    direction: QueueDirection;
    // The number of slots the episode was moved
    slots?: number;
    // Whether the episode was moved to the next item that will play
    is_next: boolean;
    episode_uuid: string;
  };
};

export type QueueDirection =
  | "up"
  | "down";

Integration and usage:

const tracker = TODO()

function trackEvent<K extends keyof Trackable>(
  event: K,
  props: Trackable[K] extends undefined ? never : Trackable[K],
): void;

function trackEvent<K extends keyof Trackable>(event: K): void;

function trackEvent<K extends keyof Trackable>(event: K, props?: Trackable[K]): void {
  // delegation to analytics tracker
}


trackEvent(
  "up_next_queue_reordered",
  {
    direction: "up",
    slots: 2,
    is_next: false,
    episode_uuid: episode.uuid,
  }
)

About

A multi-language code generation tool for type-safe event tracking.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Contributors 2

  •  
  •