Types & Money
Storage is dynamically typed. The language layer is not. Declared shapes drive the typechecker, LSP, and executor drift handling.
Scalars
Id, Text, Int, Float, Bool, Money, Time, Duration, Bytes, Json, plus enum { … }, ref Other, and unknown.
Money (never float)
A money value is integer minor units + ISO 4217 currency — never a bare decimal float.
49.99$usd // canonical
49.99$ // bare form — only where schema currency is known (e.g. Money<usd>)
500$jpy- Cross-currency arithmetic and comparison are type errors. Convert explicitly.
- In TypeScript,
@eelden/clientexposes an immutableMoneyclass overbigintminors — useMoney.minor(4999n, "USD")orMoney.parse("49.99", "USD"). Accidental+with a number is structurally hard.
Enums
status: enum { trial, paid, suspended }
users |> filter status == .paidThe lexer is context-free: . is always a token. The parser reads .ident as an enum literal in primary position and as field access in postfix position.
unknown, is, and as
Drifted or Json-derived values surface as unknown. They are never silently coerced.
events |> filter metadata.amount is Money
events |> select { cents: metadata.amount as Money<usd> }| Form | Meaning |
|---|---|
expr is Type | Bool predicate — runtime tag test |
expr as Type | Cast — fails per @strict / @loose |
Rejected assertions: is ref X, is enum { … }, is unknown.
Time and duration
orders |> filter placed_at > now() - 7dnow() is wall time from the injected clock (injectable in tests and wasm). Duration literals use d / h / m / s (e.g. 7d, 12h, 90m, 30s) and participate in arithmetic with Time.