Skip to main content
Views are virtual by default: Bijection evaluates the expression each time you read the view. Calling .materialize() on a view asks Bijection to store its output and maintain it instead:
bijection/schema.ts
The definition, the results and the way you read the view stay the same. Only the execution strategy changes.

Virtual and materialized

A materialized view is never stale. There’s no refresh to schedule and no lag to monitor: a read always returns the same result the virtual view would return at the same snapshot, including the pending writes of the current mutation.

How materialized views stay current

When a mutation writes to a table that a materialized view reads, Bijection recomputes the affected view rows as part of that mutation’s transaction. The change to the table and the change to the view commit together, or not at all. For customer_summaries, inserting an order recomputes that one customer’s row. Moving an order from one customer to another recomputes both customers, using indexes to find them. A small change doesn’t rescan every customer. That work is bounded by the view’s work limits. If a write would require more maintenance than the limits allow, the mutation fails. Bijection doesn’t fall back to a stale result or to finishing the work later.
A mutation that changes many inputs of a materialized view at once, such as one that updates every order, does all the matching view maintenance in the same transaction. Keep such mutations small, or split them across several mutations, the same way you would to stay within transaction limits.

Index requirements

A materialized view needs indexes that let Bijection find affected rows in both directions:
  • Joins need the index on the right side, as for any view, and also an index on the left table that begins with the left fields. When a joined row changes, that index finds the view rows that referenced it. A join on the left table’s _id uses the built-in by_id index.
  • Grouped inputs need an index that begins with the grouping fields, as for any view.
In the example above, the join is on customer._id, and orders has an index on customer_id for orderTotals, so no extra index is needed. Bijection refuses to maintain a materialized join whose left fields have no such index, with an error naming the table and fields. Views over views can be materialized too. A materialized view can read a virtual view, and the other way around.

Choosing between them

Start with a virtual view. It has no storage and no write-time cost, and it’s a good fit when:
  • Reads are narrow, such as db.get on one key or an index range over fields the view passes through from its source table.
  • The view is small enough to evaluate within its limits on every read.
Materialize a view when:
  • You order or filter by a computed or joined field, such as a total, across many rows. The stored output can be read by index instead of evaluated and sorted.
  • Many clients read the view much more often than its inputs change.
Because both forms return the same results, the functions that read a view are the same whether or not it’s materialized.
Materializing a view stores its output as a derived copy of its inputs. It doesn’t create a new source of truth: the tables it reads remain the only place its data is written, and access to the view is still checked on every read. See Access rules.