Browse all documentation

Transactions and errors

How Local API calls share a transaction inside hooks, what happens after commit, and how to handle a structured OperationError.

Calls inside hooks share a transaction

A Local API call made while an operation is still saving, from a BeforeChange hook for example, joins that operation’s database transaction. If either fails, both roll back:

A post save that updates its author
┌─ transaction ─────────────────────────────────────────────┐
│ 1. BeforeChange hook: ctx.Local.Update("users", authorID) │
│      runs the user's access rules, validation and hooks   │
│ 2. save the post                                          │
│ 3. commit both, or roll both back                         │
└───────────────────────────────────────────────────────────┘

The nested call still runs its own access rules, validation, hooks and version snapshot. If it fails, the whole transaction is rolled back even when the hook ignores the error.

After-commit and after-error hooks run outside the transaction, so their calls start a new one. An error there means the original change is already saved: don’t retry it blindly. For work outside the database, such as email or webhooks, use an after-commit hook or a durable task.

Ridu stops calls that loop back into the same hooked operation too many times, with operation_recursion. Treat that as a safety net, not a way to end a loop. There is no manual begin, commit or rollback; put related writes in hooks. See Transactions and errors in hooks.

Handle an error

Failed calls return a *ridu.OperationError. Branch on its Code, not its Message:

go
post, err := app.Local().Update(ctx, "posts", postID, values,
  ridu.MutationOptions{Actor: user},
)
if err != nil {
  var failure *ridu.OperationError
  if !errors.As(err, &failure) {
    return err
  }
  switch failure.Code {
  case "validation":
    return showFieldIssues(failure.Issues)
  case "conflict":
    return reloadAndAskTheAuthor(failure)
  case "access_denied", "not_found":
    return hideUnavailableDocument()
  }
  return err
}
Field Holds
Code the stable code, such as validation, conflict or not_found
Status the matching HTTP status
Issues one entry per invalid field, with its Path, Code and Message
Committed the change was saved, and the error came from work after the commit
CommitAttempted the commit was attempted but its outcome is unknown, so check before retrying

not_found can also mean the caller’s access rules hide the document. Writing data lists the codes a write returns, and Filters the ones a query returns.