> ## 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.

# Calling Operations

> Submit, preview, recover and follow operation requests

Calling an operation is different from calling a mutation in one important way:
every request carries a **request key**, a stable identity for that business
request. The key is what lets you retry safely after a lost response without
applying the change twice.

```ts src/cancelOrder.ts theme={"theme":{"light":"github-light-default","dark":"github-dark-default"}}
import {
  operationFunctionReference,
  operationInvocationArgs,
} from "bijection/server";
import { api } from "../bijection/_generated/api";

// Choose the key once, when the user decides, and keep it with the request.
const requestKey = crypto.randomUUID();
const request = operationInvocationArgs(api.orders.cancel, requestKey, {
  order_id: orderId,
  invoice_id: invoiceId,
});

const receipt = await client.mutation(
  operationFunctionReference(api.orders.cancel, "invoke"),
  request,
);
console.log(receipt.invocation_id, receipt.local_result);
```

Here `client` is any Bijection client, such as a `BijectionHttpClient` or a
`BijectionReactClient`, and `api.orders.cancel` is the generated reference for
the operation.

## Operation references

In the generated [`api`](/generated-api/api) object, an operation is an
*operation reference* rather than an ordinary function reference. It carries the
operation's contract: its object type, target, arguments and result.

Each operation is served by companion functions that the backend generates from
your one declaration:

| Companion | Kind | What it does |
| - | - | - |
| `invoke` | Mutation | Submits a request and returns its acceptance receipt |
| `preview` | Mutation | Evaluates a request without accepting it |
| `recover` | Query | Looks up what an earlier request key accepted |
| `status` | Query | Reports an accepted request's external calls and review state |
| `revise` | Mutation | Re-admits an accepted request's never-held external calls |

`operationFunctionReference(operation, companion)` returns the function
reference for one companion. The argument helpers build the matching request
envelope and include the contract the reference was generated with:

* `operationInvocationArgs(operation, requestKey, args)` for `invoke` and `recover`
* `operationPreviewArgs(operation, args)` for `preview`
* `operationStatusArgs(operation, invocationId)` for `status`

The tools you already use present an operation as one entry, not five
functions. See [CLI and console](/operations/cli-and-dashboard).

## Request keys

A request key identifies one business request. Its rules are simple:

* **Use a new key for a new request.** Two requests with different keys are two
  different business requests, even if their arguments are equal.
* **Reuse the key to retry.** If a response is lost, submit the same key and the
  same arguments again. The backend returns the original acceptance instead of
  running preparation a second time.
* **Keep the arguments with the key.** Submitting a key that was already
  accepted with different arguments is refused with
  `OperationRequestConflict`.

A key is scoped to the caller, the component, the operation and the deployment.

<Warning>
  A lost or unreadable response does not mean the request failed. Never replace
  the key because you did not receive an answer: recover it instead.
</Warning>

## The acceptance receipt

`invoke` returns once the request's local changes have committed:

| Field | Meaning |
| - | - |
| `invocation_id` | The accepted invocation's identity. It grants no access. |
| `accepted_revision` | The database revision the request committed at |
| `target` | The targeted object, when the operation declares a `target` |
| `local_result` | The value preparation returned, checked against `returns` |
| `definition_digest` | The admitted definition the request ran under |

A receipt is evidence of local acceptance only. The external calls the request
submitted are delivered afterwards; follow them with
[`status`](#following-a-request).

## Previewing an operation

A preview runs the operation's preparation on the current data, validates the
result the same way acceptance does, reports what it found, and discards
everything. It uses no request key and accepts, reserves and sends nothing.

```ts src/previewCancel.ts theme={"theme":{"light":"github-light-default","dark":"github-dark-default"}}
import {
  operationFunctionReference,
  operationPreviewArgs,
} from "bijection/server";
import { api } from "../bijection/_generated/api";

const preview = await client.mutation(
  operationFunctionReference(api.orders.cancel, "preview"),
  operationPreviewArgs(api.orders.cancel, {
    order_id: orderId,
    invoice_id: invoiceId,
  }),
);
if (preview.refusal) console.log(preview.refusal.message);
```

The report contains:

| Field | Meaning |
| - | - |
| `basis_revision` | The revision the preview read |
| `checks` | `preparation`, `constraints`, `governing` and `invocation`, each `satisfied`, `denied` or `unevaluated` |
| `is_complete` | Every check reached `satisfied` or `denied` |
| `is_executable` | Every check was satisfied at that revision |
| `changes` | The rows the request would change, before and after |
| `external_calls` | The external calls it would submit; each outcome is `unevaluated` |
| `local_result` | The value preparation returned |
| `is_disclosure_complete`, `is_result_complete` | Whether everything could be shown |
| `refusal` | `{ code, message }` when preparation threw a `BijectionError` with those fields, otherwise `null` |

`is_executable` is advice, not a promise. When the request is actually
submitted, every check runs again on the data as it is then.

A preview needs the operation's `read` and `preview` grants. Submitting needs
`invoke` as well.

<Warning>
  Previewing an operation that submits external calls is in beta.
</Warning>

## Recovering a request

If you do not know whether a request was accepted, ask `recover` with the same
key and arguments. It is a query, and it never runs preparation or contacts an
external system.

```ts src/recoverCancel.ts theme={"theme":{"light":"github-light-default","dark":"github-dark-default"}}
const recovery = await client.query(
  operationFunctionReference(api.orders.cancel, "recover"),
  request, // the same envelope you submitted to invoke
);
if (recovery.kind === "accepted") {
  console.log("Accepted as", recovery.invocation_id);
}
```

`recover` returns either `{ kind: "accepted", ...receipt }` or
`{ kind: "absent" }`. An absent answer does not authorize a new key: the
original request may still be in flight. Submitting the same key again is always
safe.

## Following a request

`status` reports where an accepted request's external calls stand. It is a
query, so you can subscribe to it and it updates as outcomes are recorded:

```tsx src/OrderStatus.tsx theme={"theme":{"light":"github-light-default","dark":"github-dark-default"}}
import { useQuery } from "bijection/react";
import {
  operationFunctionReference,
  operationStatusArgs,
  type OperationInvocationId,
} from "bijection/server";
import { api } from "../bijection/_generated/api";

export function CancelStatus({
  invocationId,
}: {
  invocationId: OperationInvocationId;
}) {
  const status = useQuery(
    operationFunctionReference(api.orders.cancel, "status"),
    operationStatusArgs(api.orders.cancel, invocationId),
  );
  if (status === undefined) return <p>Loading…</p>;
  return (
    <ul>
      {status.external_calls.map((call) => (
        <li key={call.id}>{call.delivery}</li>
      ))}
    </ul>
  );
}
```

The status contains the receipt fields except `local_result`, and:

* `external_calls`: for each call, its `delivery` outcome and its `publication`
  (whether the confirmed result has been published back into your tables). See
  [External calls](/operations/external-calls#delivery-outcomes).
* `summary`: counts of calls by outcome, including `pending`, `unknown` and
  `publication_pending`.
* `review`: whether the request is held for a person, described below.

You can also look up a status by the original request key and arguments instead
of the invocation ID.

### Review state

A request can be held until someone acts. `review` says why, and who must act:

| `kind` | Meaning |
| - | - |
| `none` | Nothing is held |
| `awaiting_review` | A person must decide before an external call proceeds |
| `waiting` | An external call is waiting on a condition that someone owns |
| `approved` | An approval was granted for this request |
| `awaiting_approval` | An approval was issued and is still undecided |
| `rejected` | The approval was revoked |

The state is derived from the request's retained records each time it is read.
It is never inferred from how long a request has been waiting. A decision itself
is made by your own review operation, through [access rules and
approvals](/access/overview).

## Calling operations from server code

From an action or a mutation, call the companions with `ctx.runMutation` and
`ctx.runQuery`, exactly as a client would:

```ts bijection/assistant.ts theme={"theme":{"light":"github-light-default","dark":"github-dark-default"}}
import { internalAction } from "./_generated/server";
import {
  operationFunctionReference,
  operationInvocationArgs,
} from "bijection/server";
import { api } from "./_generated/api";
import { v } from "bijection/values";

export const applySuggestion = internalAction({
  args: { suggestion: v.string(), orderId: v.id("order_records") },
  handler: async (ctx, args) => {
    const receipt = await ctx.runMutation(
      operationFunctionReference(api.orders.ship, "invoke"),
      operationInvocationArgs(api.orders.ship, `suggestion:${args.suggestion}`, {
        order_id: args.orderId,
        expected_revision: 3,
      }),
    );
    return receipt.local_result;
  },
});
```

Deriving the key from something stable, such as the suggestion being applied,
means a retried action submits the same request instead of a new one.

When one operation is invoked inside another transaction, its acceptance is
tentative until the outer transaction commits.

## Revising a blocked request

<Warning>Revising a request is in beta.</Warning>

An accepted external call stays bound to the integration command definition it
was accepted under. If that command's definition or its governing rule changes
before the call was ever sent, the call is blocked instead of being delivered
under a contract nobody accepted.

The `revise` companion re-admits such calls under the definitions the
destination publishes now. It takes `{ invocation_id, expected_contract }` and
requires the `invoke` grant. It only applies to calls that were never held for
delivery; if any call of the request was, the whole revision is refused with
`OperationWasHeld`. It reports which calls moved (`restamped`) and which already
matched (`unchanged`).

## Errors

Operation companions refuse with a `BijectionError` whose data is
`{ kind: "operation_error", code }`. The most common codes:

| Code | Meaning |
| - | - |
| `OperationAccess` | The caller lacks the grant this call needs |
| `OperationArguments` | The request envelope or its arguments are malformed |
| `OperationDefinitionChanged` | The deployed contract differs from the caller's generated reference |
| `OperationRequestConflict` | The request key was accepted with different arguments |
| `OperationTarget` | The target argument is missing or belongs to the wrong table |
| `OperationInvocationNotFound` | No accepted invocation matches the status lookup |
| `OperationRetired` | The operation no longer accepts new requests |
| `OperationResultExpired` | The retained result expired; this request cannot run again |

A refusal from your own preparation, such as a stale revision, is your own
`BijectionError` and reaches the caller unchanged.
