Triggers

Status: [Designed] -- Area 2 of the Grove language. Syntax and semantics are defined; implementation is in progress.

Triggers connect Area 1 (aggregates) to Area 2 (workflows). When a domain event is emitted by a module, a trigger can automatically start a workflow, mapping fields from the event into the workflow's input. This is the primary mechanism for event-driven workflow orchestration in Grove.

Prerequisites: Concepts Overview, Workflows Overview. What you'll learn: How to declare triggers, map event fields to workflow inputs, and understand trigger execution semantics.

Declaring a Trigger

A trigger watches for a specific event from a module and starts a workflow when that event occurs:

trigger on_order_placed from orders.order_placed {
  start fulfill_order v1 {
    order_id: event.id
    customer_id: event.customer_id
    items: event.items
    total: event.total
  }
}

The declaration has three parts:

  1. Name -- on_order_placed. A unique name within the module for this trigger.
  2. Source event -- from orders.order_placed. The qualified module and event name to watch.
  3. Start block -- start fulfill_order v1 { ... }. The workflow to start and the field mappings.

Source Event

The from clause references a domain event using dot notation: module.event_name.

trigger on_payment_received from payments.payment_received {
  start reconcile_payment v1 {
    payment_id: event.payment_id
    amount: event.amount
  }
}

The trigger activates every time the specified event is emitted, regardless of which action caused it. If multiple actions can emit the same event, the trigger fires for all of them.

Cross-Module Triggers

Triggers can watch events from any module in the project, not just the module where the trigger is declared:

// In the fulfillment module, watching an event from the orders module
trigger on_order_placed from orders.order_placed {
  start fulfill_order v1 {
    order_id: event.id
    items: event.items
  }
}

This is the standard pattern for decoupling modules: the orders module emits events without knowing who consumes them, and the fulfillment module reacts to those events without knowing how they were produced.

Versioned Sources

Events evolve. When an event grows new fields or changes shape, the old form is versioned and a new version is introduced alongside it. Sources and triggers can both reference a specific event version using a .vN suffix:

// Source declared with an explicit version.
source deploy_started v1 { ... }
source deploy_started v2 { ... }

// Trigger subscribing to exactly v1.
trigger on_deploy_v1 from StudioEdge.deploy_started.v1 {
  start handle_v1_deploy v1 { ... }
}

// Trigger subscribing to exactly v2.
trigger on_deploy_v2 from StudioEdge.deploy_started.v2 {
  start handle_v2_deploy v1 { ... }
}

Unversioned references (from orders.order_placed without a .vN suffix) remain supported for modules that have not yet forked their event schema — the resolver treats an unversioned reference as "the single available version." When multiple versions exist for an event and a trigger references it without a version suffix, the resolver raises an ambiguity error at compile time. Add an explicit .vN to disambiguate.

Use this mechanism to:

See Versioning and Migration for the full versioning model across records, events, and actions.

Field Mappings

The field mappings inside the start block connect event fields to workflow input fields:

start process_refund v1 {
  order_id: event.order_id
  refund_amount: event.amount
  reason: event.reason
  requested_by: meta.by
}

Available Root Objects

RootDescription
eventThe emitted event's fields
metaExecution metadata: meta.id, meta.by, meta.timestamp, meta.action_id

Field mappings are not general-purpose apply blocks. They are direct assignments -- you cannot use let bindings, if/else, for loops, or complex expressions. Each mapping is workflow_field: source_expression.

Execution Semantics

Multiple Triggers

A module can have multiple triggers watching different events:

trigger on_order_placed from orders.order_placed {
  start fulfill_order v1 {
    order_id: event.id
    items: event.items
  }
}

trigger on_order_cancelled from orders.order_cancelled {
  start cancel_fulfillment v1 {
    order_id: event.id
    reason: event.reason
  }
}

trigger on_payment_failed from payments.payment_failed {
  start handle_payment_failure v1 {
    order_id: event.order_id
    error_code: event.error_code
  }
}

Multiple triggers can also watch the same event, starting different workflows:

trigger on_signup_fulfillment from accounts.account_created {
  start onboarding_workflow v1 {
    account_id: event.id
  }
}

trigger on_signup_notification from accounts.account_created {
  start send_welcome_email v1 {
    email: event.email
    name: event.name
  }
}

See Also