///|
/// Raised by `Txn::retry` to abort the current attempt because it cannot make
/// progress yet. The payload is the commit version at which the transaction's
/// read set was last validated (its snapshot clock), which may be older than
/// the current global clock when an asynchronous body suspended.
///
/// A blocking runner (such as `@transaction/runner.atomically_async`) waits
/// until `commit_version()` differs from the payload, then runs the body again
/// from the beginning. If a commit happened after that snapshot — for example
/// while the body was suspended — the runner restarts immediately instead of
/// waiting for a future commit, so the commit is never missed. Compare with
/// `!=` rather than `>`; the 64-bit counter will not wrap in practice. Under
/// `atomically` the error propagates to the caller.
pub suberror Retry {
  Retry(Int64)
}

///|
/// Raised when a TVar read earlier by the transaction has been committed by
/// another transaction, so the transaction can no longer return a consistent
/// result.
///
/// The payload is the global commit version at which the conflict was
/// detected. `atomically` and `atomically_async` restart the body
/// automatically; only custom runners see this error.
pub suberror Conflict {
  Conflict(Int64)
}

///|
/// Whether `err` is the `Retry` raised by `Txn::retry` or `Txn::check`.
///
/// `atomically` declares an abstract `raise` (the body can raise anything),
/// so a caller's `catch` binds an abstract `Error`. Use this predicate to
/// detect the blocking condition without depending on the constructor:
///
/// # Example
/// ```mbt check
/// test {
///   let cell : @transaction.TVar[Int32] = @transaction.TVar::new(0)
///   try
///     @transaction.atomically(txn => {
///       txn.check(txn.read(cell) > 0)
///       txn.read(cell)
///     })
///   catch {
///     err => assert_true(@transaction.is_retry(err))
///   } noraise {
///     _ => fail("expected the transaction to block")
///   }
/// }
/// ```
pub fn is_retry(err : Error) -> Bool {
  err is Retry(_)
}

///|
/// Whether `err` is the `Conflict` raised when a transaction's snapshot went
/// stale.
///
/// `atomically` and `atomically_async` restart the body automatically on
/// conflict, so only custom runners need this predicate.
pub fn is_conflict(err : Error) -> Bool {
  err is Conflict(_)
}