One File, Three Outputs
Write once in natural language. Get executable behavior, test scenarios, and live diagrams from the same source.
Your .flow file is the executable specification. One source generates runtime behavior, tests, and diagrams. No drift. No sync. Just truth.
machine: @order
scenario: checkout
given:
@customer is logged in
cart has items
on :checkout from @customer (api)
? cart is valid
$order_id becomes uuid()
order moves to #awaiting_payment
emit :payment_request to @payment with:
| field | value | validation |
| order_id | $order_id | required, string, valid uuid |
| amount | $total | required, number, greater than 0 |
reply 201 with:
| id | $order_id |
| status | current_state |
otherwise
reply 400 bad request with:
| error | "CART_INVALID" |
on :validation_failed
reply 400 with:
| errors | $errors |
on :payment_success from @payment
order moves to #confirmed
emit async :send_confirmation to @notification
expect:
= order is in #confirmed
= reply status is 201test: @order
for scenario: checkout
for :checkout:
empty cart rejected:
with scenario:
cart is empty
= order is not in #awaiting_payment
invalid amount rejected:
when:
@customer sends :checkout with:
| amount | -10 | required, number, greater than 0 |
= :validation_failed was emitted
payment fails then succeeds:
after :checkout
receive :payment_failed from @payment
then :payment_success from @payment
= order is in #confirmed#[Guard('cart is valid')]
class CartIsValidGuard extends GuardBehavior
{
public function __invoke(array $context): bool
{
return count($context['cart']['items'] ?? []) > 0;
}
}#[Action('send confirmation')]
class SendConfirmationAction extends ActionBehavior
{
public function __invoke(array $context): void
{
Mail::to($context['customer']['email'])
->send(new OrderConfirmation($context['order_id']));
}
}#[ValidationRule('valid uuid')]
class ValidUuidRule implements Rule
{
public function validate(mixed $value, Context $ctx): bool
{
return Uuid::isValid($value);
}
}