Skip to content

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.

eel
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, @eelgrass/client exposes an immutable Money class over bigint minors. Use Money.minor(4999n, "USD") or Money.parse("49.99", "USD"). Accidental + with a number is structurally hard.

Enums ​

eel
status: enum { trial, paid, suspended }

users |> filter status == .paid

The 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.

eel
events |> filter metadata.amount is Money
events |> select { cents: metadata.amount as Money<usd> }
FormMeaning
expr is TypeBool predicate: runtime tag test
expr as TypeCast: fails per @strict / @loose

Rejected assertions: is ref X, is enum { … }, is unknown.

Time and duration ​

eel
orders |> filter placed_at > now() - 7d

now() 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.

Pre-alpha. Local-first. Stdlib-only Rust engine. Tenant concerns shifted left into the database.