Skip to content

Rules

Rules are event-driven automation components that react to system events in Hantera. They enable you to validate state changes, enforce business logic, and trigger automated workflows.

What are Rules?

A rule is a component that executes when specific events occur in the system. Rules can:

  • Validate operations before they complete (prevent invalid states)
  • Automate workflows when entities change
  • Enforce business logic across the platform
  • Integrate with external systems via effects

Unlike Jobs which are time-triggered, rules are event-triggered - they run automatically when specific system events occur.

How Rules Work

Rules follow a three-step pattern:

  1. Hook Triggers: A system event occurs (e.g., order created)
  2. Component Evaluates: Rule component receives event data and evaluates conditions
  3. Effects Execute: Component returns effects to execute (commands, validations, integrations)
graph LR
A[Actor Event] --> B[Rule Hook Triggers]
B --> C[Component Evaluates]
C --> D[Return Effects]
D --> E[Effects Execute]

Simple Example: Order Confirmation Email

Here’s a complete example that sends an email when an order is created:

  1. Create the rule component:

    order-confirmation.hrule
    import 'resources'
    // 'input' is a special parameter and the designated type determines when/if the rule will evaluate:
    param input: OnOrderCreated
    let customerEmail = input.order.invoiceAddress.email
    from customerEmail match
    /@/ |>
    {
    effect = 'sendEmail'
    to = email
    subject = $'Order #{input.order.orderNumber} Confirmed'
    body = {
    html = $'<h1>Thank you for your order!</h1><p>Order number: {input.order.orderNumber}</p>'
    }
    category = 'order_confirmation'
    dynamic = {
    orderId = input.order.orderId
    }
    }
    ]
  2. Deploy via manifest:

    uri: /resources/rules/order-confirmation
    spec:
    codeFile: order-confirmation.hrule
  3. Apply the manifest:

    Terminal window
    h_ manage apply h_manifest.yaml

Now every time an order is created, the customer receives a confirmation email automatically.

Rule Hooks

Rules respond to actor lifecycle events called hooks. Hooks are categorized by timing and actor type:

Hook Timing

Before Hooks (OnXxxBeforeCreated, OnXxxBeforeDeleted):

  • Execute before the operation completes
  • Can prevent the operation with validation errors
  • Changes aren’t yet persisted

After Hooks (OnXxxCreated, OnXxxDeleted):

  • Execute after the operation completes
  • Changes are persisted
  • Can trigger side effects and automation

Command Hooks (OnXxxCommands):

  • Execute when commands are applied
  • Can add additional commands
  • Useful for cascading logic

Available Hooks

Currently available hooks focus on actor lifecycle events:

  • Order: 6 hooks (BeforeCreated, Created, BeforeDeleted, Deleted, Commands, Journal)
  • Payment: 7 hooks (BeforeCreated, Created, BeforeDeleted, Deleted, Commands, Capture, Journal)
  • Ticket: 8 hooks (BeforeCreated, Created, BeforeDeleted, Deleted, Commands, Complete, Rejected)
  • Sku: 5 hooks (BeforeCreated, Created, BeforeDeleted, Deleted, Commands)
  • Asset: 5 hooks (BeforeCreated, Created, BeforeDeleted, Deleted, Commands)

Additional hooks for other resource types (files, registry, etc.) may be added in the future.

See all hooks →

Rule Effects

Rules return effects - instructions for what should happen.

Effects include:

Actor Commands

Apply commands to actors:

  • orderCommand - Modify orders
  • paymentCommand - Modify payments
  • ticketCommand - Modify tickets
  • skuCommand - Modify SKUs
  • assetCommand - Modify assets

System Operations

  • messageActor - Send messages to other actors
  • scheduleJob - Schedule background jobs

Validation

  • validationError - Prevent operation and return error

See all effects →

Common Use Cases

Validation Rules

Prevent invalid state changes:

import 'iterators'
param input: OnOrderBeforeCreated
let hasItems =
input.order.deliveries
select d => d.orderLines
flatten
count > 0
from hasItems match
false |> {
effect = 'validationError'
code = 'EMPTY_ORDER'
message = 'Orders must have at least one item'
}

Automation Rules

Trigger workflows automatically:

param input: OnPaymentCapture
from input.paymentCapture.capturedAmount > 10000 match
true |> {
effect = 'messageActor'
actorType = 'ticket'
actorId = 'new'
messages = [{
type = 'create'
commands = [{
type = 'setDynamicFields',
fields = {
title = 'Large Payment Review'
description = $'Payment {input.payment.paymentNumber} for {input.paymentCapture.capturedAmount} needs review'
paymentId = input.payment.paymentId
}
}]
}]
}

Integration Rules

Sync with external systems:

param input: OnOrderCreated
from [{
effect = 'scheduleJob'
definition = 'sync-to-erp'
at = datetime.now addMinutes 5
parameters = {
orderId = order.id
}
}]

Multiple Effects

Rules can return multiple effects as an array:

param input: OnOrderCreated
from {
effect = 'orderCommand'
type = 'createComputedOrderDiscount'
componentId = 'free-shipping'
description = 'Free Shipping'
parameters = { threshold = '500' }
}
from {
effect = 'sendEmail'
to = order.customer.email
subject = 'Order Confirmed'
body = { html = '<h1>Thank you!</h1>' }
dynamic = {}
}

All effects execute in the order specified.

Access Control

Managing rule resources requires standard permissions:

rules:read # View rule configurations
rules:write # Create, update, and delete rules

Rules inherit the execution permissions of the system - they run with elevated privileges to enforce business logic across the platform.

Best Practices

1. Use Before Hooks for Validation

Prevent invalid operations with before hooks:

hook OnOrderBeforeCreated
from validateOrder(order) match
Error |> [{ effect = 'validationError', code = error.code, message = error.message }]
|> []

2. Use After Hooks for Automation

Trigger workflows after successful operations:

hook OnOrderCreated
from [
{ effect = 'sendEmail', ... },
{ effect = 'scheduleJob', ... }
]

3. Handle Missing Data Gracefully

Always handle optional data:

from input.order.invoiceAddress.email match
email |> { effect = 'sendEmail', to = email, ... }
// Default case returns nothing

See Also