Skip to main content
A write rule decides whether the changes a transaction makes to a table may commit. You attach it with .govern, and Bijection runs it at commit for every transaction that inserts, patches, replaces or deletes documents in that table, whichever mutation made the change.
bijection/schema.ts
The rule is a query that receives the changes and returns one result per change:
bijection/access.ts
A write rule is not a mutation wrapper. It applies to every writer, public and internal mutations alike, and to mutations called from other functions.

What the rule receives

The rule receives changes, the net changes the transaction made to the table, one per document: A document inserted and then patched in the same transaction is one change with before: null. The TableChange type from bijection/server describes one change. The rule runs on the committing transaction and sees its complete tentative state. By default its reads see the database as it will be after the transaction.

What the rule returns

The rule returns one result per change, in order, and [] when there are no changes. Each result is null, { validUntil }, { reason } or { validUntil, reason }, as for a read rule. Throw to refuse: a refusal fails the whole transaction, and nothing it wrote commits. A missing result, an extra one, or a bare value instead of an array refuses. The rule’s returns must be readAccessResults; a deployment whose write rule declares anything else is refused. Every push of your code runs each write rule once with no changes, which checks this before any real write depends on it. When the rule reads the clock, each permission must carry validUntil, at most 366 days ahead. The transaction must then commit before that instant.

Private inputs and the original state

Pass an object instead of a bare reference to give the rule private inputs or to choose which state it decides against:
bijection/schema.ts
  • reads lists the tables the rule may read privately, at most 32 ordinary tables of your schema, exactly like a read rule’s policy inputs. With reads, the rule may read those tables and nothing else.
  • basis chooses the state the rule’s reads see. "final", the default, is the state at commit. "original" is the state before the transaction started.
Use basis: "original" on tables that hold authority, such as memberships or grants. Deciding a new membership against the original state means the authority to add it must already exist before the transaction: a caller can’t grant themselves a role and use it to approve their own grant in the same commit.

Errors

When a write rule refuses, the mutation fails with a GoverningRuleRejected error. What the caller sees depends on whether the rule declares inputs: A rule with private inputs could mention them in its message, so Bijection keeps its message private. To record why such a rule refused, audit the table with .access({ audit: true }), which records the why of a thrown BijectionError({ kind: "AccessRefused", why }). See Auditing decisions.

Where write rules apply

  • Only on tables your app owns. Synced tables hold a provider’s data, and views are computed, so neither accepts .govern.
  • A table has at most one write rule. To combine several conditions, check them all inside that rule.
  • A write rule checks the changes of each transaction. It isn’t a standing constraint: a rule on tasks that looks at a project doesn’t run when the project changes.

Options