Guide · 03

Derived values stay up to date.

Flower remembers what each derived value reads. When those records change, it recomputes the value in the same commit.

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.unmaterialize removes 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.
  • scan depends 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.

  • remove must exactly undo add. 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 pass preparation: "blocking" to client.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.