01Source changecounters["visits"] = 1
→02Derived valuedoubled("visits") = 2
→03One revisionSources + results + receipt
Keep a value current
Call ctx.materialize(value, args) in a mutation to store a derived value and keep it current.
- An instance is its name plus its arguments. Omitted arguments mean
null. ctx.unmaterializeremoves it, with dependencies nothing else needs.- Queries can also read unmaterialized values; they're computed on the spot.
- A mutation sees its own earlier writes, including through derived values.
- Record changes, updated values and the result commit together.
Keep dependencies narrow
A value recomputes when anything it read changes, so read only what you need.
- Reading a missing record counts; the value updates when it appears.
scandepends on the whole collection.- List indexed collections in
define({ collections: [orders], ... })so lookups depend only on matching entries. - Split unrelated work into separate derived values, so a tip doesn't recompute every order total.
Maintain totals cheaply
Use aggregate for sums and counts. Give it pure initial, add and remove functions; Flower applies only changed rows. Use it like any derived value.
removemust exactly undoadd. Integer sums and counts work well.- A deploy builds new aggregates in the background. If a write lands meanwhile you get
DEPLOYMENT_CONFLICT: retry with the same request ID, or passpreparation: "blocking"toclient.deploy. - For large collections, use a staged deployment.
The indexes and aggregates guide has a full example.
How errors behave
- If a derived value throws, its readers get the error and may catch it.
- A mutation that throws commits nothing.
- Cycles and exhausted transaction budgets always abort, even if your code catches the error.
- Returning an error-shaped object is a success, not an error.
Callbacks are sandboxed and synchronous. Return JSON. There is no network, filesystem, current date (use ctx.now()) or async work. Globals don't persist between calls. Outside facts come in as method arguments.