> ## Documentation Index
> Fetch the complete documentation index at: https://docs.bijection.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Defining Operations

> Declare a business operation with defineOperation

An operation declares one business change: which kind of object it is about,
which arguments it takes, what local result it returns, and how it prepares the
change. Preparation runs in an ordinary mutation transaction.

```ts bijection/invoices.ts theme={"theme":{"light":"github-light-default","dark":"github-dark-default"}}
import { defineOperation } from "bijection/server";
import { v } from "bijection/values";
import { invoices } from "./schema";

export const addNote = defineOperation({
  on: invoices,
  target: { argument: "invoice_id" },
  args: { invoice_id: v.id("invoice_records"), note: v.string() },
  returns: v.null(),
  prepare: async (ctx, args) => {
    await ctx.db.patch(args.invoice_id, { note: args.note });
    return null;
  },
});
```

Read on to understand each part of the declaration.

## Operation names

Operations follow the same naming rules as queries, see
[Query names](/functions/query-functions#query-names). An operation exported as
`addNote` from `bijection/invoices.ts` is addressed as `invoices:addNote`.

Operations can be defined in the same file as queries, mutations and actions.

## The `defineOperation` constructor

Import `defineOperation` from `bijection/server` and pass it an object with
these fields:

| Field | Required | Meaning |
| - | - | - |
| `on` | Yes | The object type the operation is about: a view registered in your schema, or a locally owned retained table |
| `target` | No | `{ argument }`: the argument that names the one existing object the operation changes |
| `implements` | No | The [operation interfaces](/operations/operation-interfaces) this operation implements |
| `args` | Yes | [Validators](/functions/validation) for the operation's arguments |
| `returns` | Yes | A validator for the local result of preparation |
| `prepare` | Yes | The handler that prepares the change in a mutation transaction |

`defineOperation` refuses a declaration that is missing `on`, `args`, `returns`
or `prepare`.

<Tip>
  Once your deployment declares an operation, the generated
  `./_generated/server` also exports a `defineOperation` typed with your data
  model, just like `mutation`.
</Tip>

### The object type: `on`

`on` associates the operation with a business object type. Usually this is a
[view](/views/overview) registered in your schema:

```ts bijection/schema.ts theme={"theme":{"light":"github-light-default","dark":"github-dark-default"}}
import { defineSchema, defineTable, defineView, q } from "bijection/server";
import { v } from "bijection/values";

export const invoices = defineView({
  key: { from: "invoice_records", field: "_id" },
  expression: q.table("invoice_records"),
});

export default defineSchema({
  invoice_records: defineTable({ number: v.string(), note: v.string() }),
  invoices,
});
```

`on` can also name a locally owned table declared with `.retain(...)`. In that
case the operation must declare a `target`.

The association is used for discovery, for example to list the operations
available on an invoice. It grants no permission by itself.

### The target: `target`

`target: { argument }` names the argument that selects the one existing object
the operation changes. That argument must be a required, top-level `v.id(...)`
argument. For a view, the ID belongs to the view's key table; for a retained
table, to that table.

For a retained table, the target row is read through the ordinary tracked
reader before preparation runs, so the operation depends on it even if `prepare`
does not read it.

Leave `target` out for an operation that creates a new object, or that is not
about one particular existing object.

### Arguments and result

`args` and `returns` use the same [validators](/functions/validation) as
queries and mutations. Both are required.

`returns` describes the operation's *local* result: what preparation produced
in this transaction. It says nothing about what an external system later does.
Preparation must return a value that matches it.

### Preparation: `prepare`

`prepare` receives a mutation context and the validated arguments. It runs as
one transaction, exactly like a [mutation](/functions/mutation-functions#transactions):

* Use `ctx.db` to read and write your tables.
* Use `ctx.externalCalls.submit` to request work in an external system. Read on
  about [External calls](/operations/external-calls).
* Use `ctx.auth` to check the caller, and `ctx.scheduler` to schedule functions.

Like any mutation, preparation cannot call third-party APIs itself. If it throws,
nothing it wrote and no external call it submitted is kept.

```ts bijection/orders.ts {11-17} theme={"theme":{"light":"github-light-default","dark":"github-dark-default"}}
import { defineOperation } from "bijection/server";
import { BijectionError, v } from "bijection/values";
import { orders } from "./schema";

export const ship = defineOperation({
  on: orders,
  target: { argument: "order_id" },
  args: { order_id: v.id("order_records"), expected_revision: v.number() },
  returns: v.object({ revision: v.number() }),
  prepare: async (ctx, args) => {
    const order = await ctx.db.get(args.order_id);
    if (!order || order.revision !== args.expected_revision) {
      throw new BijectionError({
        code: "STALE",
        message: "This order changed. Reload it and try again.",
      });
    }
    const revision = order.revision + 1;
    await ctx.db.patch(args.order_id, { status: "shipped", revision });
    return { revision };
  },
});
```

To refuse a request with a reason the caller can show, throw a
[`BijectionError`](/functions/error-handling/application-errors) whose data has
a string `code` and `message`. A [preview](/operations/calling-operations#previewing-an-operation)
reports exactly those two fields, so a person sees why before they submit.

## Internal operations

`defineInternalOperation` takes the same fields and declares an operation that
can only be called from your own server code, like an
[internal function](/functions/internal-functions). Internal visibility does not
grant permission to call it: the caller still needs a grant.

```ts bijection/orders.ts theme={"theme":{"light":"github-light-default","dark":"github-dark-default"}}
import { defineInternalOperation } from "bijection/server";
```

## Granting access

Every caller of an operation, including a deployment administrator, needs an
explicit grant on that operation. The permissions are `read`, `preview` and
`invoke`. A deployment administrator grants them with `bijection permissions`:

```sh theme={"theme":{"light":"github-light-default","dark":"github-dark-default"}}
bijection permissions set 'issuer|subject' read true --operation orders:ship
bijection permissions set 'issuer|subject' invoke true --operation orders:ship
```

The subject is a user token identifier, the `issuer|subject` pair. Read on
about [access rules and grants](/access/overview).

## Operations after a deploy

An accepted request keeps the meaning it was accepted with. Deploying new code
does not rerun preparation for requests that were already accepted, and does not
recalculate their external calls.

Each generated reference carries the operation's contract. If the deployed
operation's contract (its object type, target, arguments or result) no longer
matches the reference a caller was built with, the call is refused with `OperationDefinitionChanged`. Regenerate your
code and call it again.
