Construct and validate typed data
This tutorial runs locally, without a model or external service. It builds an invoice, compares two dates and returns a copy flagged for review.
1. Write the program
Save the following program as invoice.tb:
invoice.tb
module examples.invoice_data
type Invoice
reference is Text length 1..40
issued_on is Date
order_date is Date
currency is Text length 3 default "CAD"
total_cents is Int min 0
flags is List of Text length 0..5 default []
function Review
takes invoice as Invoice
gives Invoice
do
if invoice.issued_on < invoice.order_date
give copy invoice { flags as ["invoice_before_order"] }
give invoice
strategy Demo
gives Invoice
do
bind draft as Invoice = record Invoice { reference as "INV-42", issued_on as date("2026-10-20"), order_date as date("2026-10-21"), total_cents as 12500 }
Review
with invoice as draft
into reviewed
give reviewed
Defaults fill currency and flags during construction. copy keeps the
other fields and leaves the original invoice unchanged.
2. Run
touba compile invoice.tb --out invoice.air.json
touba run invoice.air.json --entry strategy:examples.invoice_data/Demo --output result
The result contains currency: "CAD" and
flags: ["invoice_before_order"]. The date rule belongs to your business
logic: the next step can route this invoice to a human approval.
3. Check the boundaries
- Change the date to
date("2026-02-30"): the value is refused. - Set
total_centsto0 - 1: the numeric contract is refused. - Refer to
invoice.missing_field: the compiler reports the unknown field.
Handlers and models supply complete records. Source defaults apply to
record construction, not to external input. Both SDKs use the same ISO
dates, JSON lists and constraints.
See Typed data for the contract and Approvals with roles for the next step.