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:
┌─ 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:
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.