bijection/schema.ts
customer_invoices document is one link between a customer and an
invoice. A customer can have any number of invoices, and an invoice belongs to
at most one customer.
Declaring a link type
Call.link on a table definition with an object that has exactly two entries.
Each key is an endpoint field and each value is its cardinality,
[minimum, maximum]:
- The endpoint fields must be required, top-level
v.id(...)fields of the table. System tables can’t be endpoints. - The other fields of the table, such as
noteabove, are ordinary data about the link. An ID field that isn’t named in.linkisn’t an endpoint. - A table can have one
.linkdeclaration.
.link can’t be used on a view, on a table filled by an
integration, or on a table with a staged validator.
Cardinality
An endpoint’s bounds count the live links that each document of the endpoint’s table takes part in. In the example,invoice_id: [0, 1] means every invoice
is in zero or one customer_invoices link.
"many" means no upper bound. Bounds are non-negative integers with the
minimum no greater than the maximum.
Put a bound on the endpoint it constrains. “An invoice has at most one customer”
is a bound on invoice_id, not on customer_id.
Minimums
A minimum applies to every document in the endpoint’s table, including documents that have no links yet. Withinvoice_id: [1, 1], an invoice can’t
exist without its link, so a mutation that inserts an invoice must also insert
its link:
bijection/invoices.ts
0 when records can legitimately exist before they’re
associated, and track unassigned records with an ordinary query.
Creating, changing and ending links
Links are written with ordinary mutations:- Create a link with
db.insert. - Change its data with
db.patchordb.replace, as long as both endpoint fields keep their values. - End a link with
db.delete.
bijection/invoices.ts
_id and
_creationTime.
Checked on the final state
Bijection checks links against the state the whole transaction leaves behind, not after each write. That’s whyreassign can briefly leave the invoice
without a link, and why a mutation can delete a customer together with all its
links. It also covers writes that don’t touch the link table: deleting a
customer that still has live links fails, even though no link was written.
A transaction that breaks a link rule fails with one of these errors, and none
of its writes are committed:
Declaring a link type, or making its bounds stricter, also checks the documents
already in your tables when you deploy.
Indexes
To check links efficiently, a link type needs two indexes. Taking the endpoint fields in alphabetical order, one index begins with both fields and the other begins with the second field. Bijection adds these asby_link_pair and
by_link_endpoint_2 unless you’ve declared indexes that begin with the same
fields, in which case it reuses yours.
Declare the indexes you query yourself, so that they have meaningful names and
appear in your generated types. In the example at the top of this page,
by_customer_invoice begins with both endpoints and by_invoice begins with
invoice_id, so Bijection adds no indexes of its own.
Reading links
A link type is a table, so you read links with ordinary indexed queries, in either direction:bijection/invoices.ts
.unique() is safe in customerOf because invoice_id has a maximum of 1.
Links in views
You can also traverse links inside a view. Because an invoice has at most one link, a view keyed by invoices can join it with the defaultcardinality: "one":
bijection/schema.ts
bijection/schema.ts
Links in the console
The Schema page of the console draws each declared link as edges from the link table to its two endpoint tables. Selecting a table lists the links it takes part in, the table that declares each one, and each endpoint’s bounds, written like0..1, 1 or 0..*. For a table that
declares a link, the diagram shows the declared endpoints instead of guessing
edges from its ID fields.