Pipelines
A pipeline starts at a collection and threads stages with |>.
users
|> filter active == true
|> select { name, email, balance }Studio tip: type | after a complete segment. It expands to |> and opens the stage menu (you rarely type the digraph by hand).
Stages
filter
Keep rows where the predicate is Bool.
users |> filter status == .paid && balance >= 50.00$usdselect
Project fields or paths. After a join, nested paths work:
orders |> join listing |> select { listing.title, total, placed_at }join
Follow a ref field (inner join). There is no SQL-style multi-collection join syntax. The right-hand side is the ref name on the left row.
orders |> join listingsort
Stable sort by a field. Optional asc (default) or desc.
users |> sort balance desctake / skip
users |> sort balance desc |> take 10 |> skip 0take / skip expect Int.
group … aggregate
users |> group status aggregate { n: count(), balance: sum(balance) }Multiple keys are allowed:
orders |> group status, listing aggregate { n: count(), total: sum(total) }Aggregates: count(), sum(expr), min(expr), max(expr), avg(expr).
distinct
users |> select { status } |> distinctasof
Pin reads to the nearest named snapshot at or before the given time. Create snapshots first (Studio or POST …/ops).
listings |> asof now() - 2d |> select { title, stock }Raw page-version history (without a named snapshot) is not wired yet. See Status.
insert
users |> insert { name: "alice", email: "a@x.com", active: true, balance: 49.99$usd, status: .paid }Missing id → executor assigns a ULID.
update
users |> filter email == "a@x.com" |> update { status: .paid }delete
users |> filter active == false |> deletedelete tombstones the row. Mutations return affected rows and can compose with later stages.
atomic
Tenant-scoped transactions. See Transactions.
atomic {
listings |> filter id == ${lid} && stock > 0 |> update { stock: stock - 1 }
orders |> insert { listing: ${lid}, buyer_ref: ${bref}, total: 129.00$, placed_at: now(), status: .pending }
}Parameters
Typed holes ${name} are filled by the host (named-query params or executor bindings):
users |> filter balance >= ${min_balance} |> select { name, balance }Sources
| Source | Status |
|---|---|
collection | Executes: current tenant |
across tenants(pred) collection | Parses only: not executed |
Operators
- Equality:
==!= - Ordering:
<<=>>=(Int, Float, Text, Id, Time, Duration, Money) - Boolean:
&&||! - Arithmetic:
+/-(Time ± Duration → Time; Time − Time → Duration; Money ± Money, same currency) - Multiply / divide:
*/(Int / Float only)
Cross-currency Money arithmetic and comparison are type errors.
Builtins
| Call | Result |
|---|---|
now() | Time: wall clock (injectable in tests / wasm) |
count() | Int: aggregate |
sum(expr) | Same type as expr |
min(expr) / max(expr) | Same type as expr |
avg(expr) | Money stays Money; Int/Float → Float |
Worked scene
examples/storefront.eel shows checkout atomics, seller dashboard pipelines, asof, and a tenant fork in one file.