Skip to main content
This page builds a complete small application. A signed-in person creates documents, lists them page by page, renames them and deletes them. Nobody else can read or change those documents unless their owner shares them. The functions are ordinary queries and mutations over ctx.db. Protection is declared once, on the table, and Bijection enforces it on every read and write, from any function.

Choose a starter

bijection access init writes a starter policy into a project that has no schema yet. Preview it first; the preview signs in to nothing and writes nothing:
Each starter is ordinary TypeScript that you edit afterwards. None of them grants a role at runtime by itself: The team starter declares each team as a tenant boundary and binds every document to its team through a parent field and index. Membership is an explicit grant on one team; signing in grants none. Without --dry-run, setup reports each step separately:
  1. Source declarations written: access.ts, schema.ts and accessApi.ts.
  2. Application deployed: the deployment activated the authorization program.
  3. Application identity verified: the deployment verified your application sign-in. A management login is never used as an application identity.
  4. Initial authority established: the verified identity holds root:owner, the declared recovery role. Set BIJECTION_INITIAL_OWNER, or pass --initial-owner, to require a specific identity. The first caller never becomes an owner implicitly.
  5. Example check: whether that owner may create a document on the root, decided by the installed policy. The check writes nothing; a refusal is reported rather than hidden.
A rerun resumes an interrupted setup. Once you edit the generated model, setup keeps it and no starter replaces it.

The access model

The personal starter writes this model:
bijection/access.ts
  • A permission names one operation on a resource type, such as document:edit.
  • A role groups permissions. An assignment gives a holder a role on one resource.
  • audience: { create: "public" } declares that every caller holds document:create. Creating a document also assigns document:owner to its creator, so a caller without an application identity cannot create one.

The schema

The schema binds the document resource to a table. Every field names its policy, through small constants you reuse:
bijection/schema.ts
The file begins with imports of defineSchema, defineTable, v and the access model. assignments stores the application’s role assignments.

Queries and mutations

bijection/documents.ts
None of these handlers checks a permission. The table policy decides:
  • create needs document:create, which the model makes public. The server sets createdBy; a caller cannot choose it.
  • mine is an ordinary query and returns the complete requested range: if any document in it is not readable, for example after its ownership was revoked, the query fails rather than returning a shorter page.
  • mineVisible adds .visible() and returns only the documents the caller can read, chosen before pagination. Use it when the feature is to discover readable documents.
  • rename calls renameDocument, a plain TypeScript helper. It needs no registration, runs in the same transaction, and its ctx.db.patch is checked against document:edit at commit, as it would be from any public or internal mutation.
  • remove revokes the document’s assignments, then deletes it with document:delete.
select(["_id", "title"]) returns only those fields, and the result type says so.

Call it from React

Generated references keep argument and result types:
results[0]?.title is a string; results[0]?.body is a type error because the query does not select it.

Share a document

The owner shares a document by granting a role on it. From the command line, signed in as the owner:
The viewer can now get the document; rename stays refused. It does not appear in the viewer’s mine lists, which select by creator. Listing documents shared with you uses a declared access path; see read rules. The generated accessApi.grant and accessApi.revoke functions offer the same operations to the application. Revoking the assignment affects the viewer’s next reads and live subscriptions.

Add a sensitive field

Adding a field is a classification decision. Suppose owners keep private notes that shared viewers must not read. Declare a permission for them in the model:
bijection/access.ts
Then classify the field with its own policy:
bijection/schema.ts
Without the privateNotes entry, the table fails type checking and activation refuses it. document:owner holds document:*, so owners read the notes; document:viewer and document:editor do not. Reads that viewers make should select the fields they may read, as mine does.

Who can do what

The initial owner holds root:owner, which covers every document. Grant it only to the application’s administrators.

Next steps