Rollback Scope
The Rollback Scope utility (from v0.13.0) provides transactional semantics for state mutations. It allows you to wrap multiple state changes in an atomic operation that can be automatically rolled back on failure.
Overview
createRollbackScope creates a transactional wrapper around any BaseOriginator or DiffOriginator instance. The begin() method executes a callback within a transactional boundary:
- On success: All changes are committed
- On rollback/error: All changes are reverted to the original state
import { createRollbackScope, rollback } from '@dxbox/use-less-react/classes';
const scope = createRollbackScope(myOriginator)
.withBatchedNotifications();
await scope.begin(async () => {
myOriginator.value = 'new value';
if (someCondition) {
rollback(); // Revert all changes
}
});
API Reference
createRollbackScope(originator)
Factory function that creates a RollbackScope instance.
| Parameter | Type | Description |
|---|---|---|
originator | BaseOriginator | DiffOriginator | The originator instance to wrap |
Returns: RollbackScope<TOriginator>
The factory automatically detects the originator type and uses the appropriate caretaker:
DiffOriginator→DiffCaretakerBaseOriginator→BaseCaretaker
// With BaseOriginator
const baseScope = createRollbackScope(myBaseOriginator);
// With DiffOriginator
const diffScope = createRollbackScope(myDiffOriginator);
Fluent API for Notification Configuration
After creating a scope, you must configure how notifications are handled using one of these methods:
.withBatchedNotifications(options?)
Configures the scope to batch all notifications during transaction execution. Useful for optimistic updates.
const scope = createRollbackScope(myOriginator)
.withBatchedNotifications();
await scope.begin(async () => {
// All notifications are batched - single render
myOriginator.field1 = 'a';
myOriginator.field2 = 'b';
}); // Single notification sent here
.withStandardNotifications()
Configures the scope to send notifications immediately as changes occur during the transaction.
const scope = createRollbackScope(myOriginator)
.withStandardNotifications();
await scope.begin(async () => {
// Each mutation triggers immediate notification
myOriginator.field1 = 'a'; // Notification
myOriginator.field2 = 'b'; // Notification
});
Note: You must call one of these methods before calling begin(). The fluent API ensures type-safe configuration.
RollbackScope.begin(callback)
Executes the callback within a transactional boundary.
| Parameter | Type | Description |
|---|---|---|
callback | () => Promise<void> | void | The operations to execute transactionally |
Returns: Promise<void>
Behavior:
- Captures a memento (state snapshot) before execution
- Handles notifications according to the configured mode:
.withBatchedNotifications(): Batches all notifications, sends single notification on success.withStandardNotifications(): Sends notifications immediately as changes occur
- On success: commits changes
- On
RollbackErroror any exception: restores state, re-throws error
await scope.begin(async () => {
// Multiple mutations...
originator.field1 = 'value1';
originator.field2 = 'value2';
await someAsyncOperation();
originator.field3 = 'value3';
}); // All changes committed atomically
rollback()
Helper function that throws a RollbackError to trigger a rollback.
import { rollback } from '@dxbox/use-less-react/classes';
await scope.begin(async () => {
originator.status = 'processing';
const result = await apiCall();
if (!result.success) {
rollback(); // Throws RollbackError
}
});
Note: rollback() throws an exception, so code after the rollback() call will not execute.
RollbackError
Custom error class used to signal rollback operations.
import { RollbackError } from '@dxbox/use-less-react/classes';
try {
await scope.begin(async () => {
// ...
rollback();
});
} catch (error) {
if (error instanceof RollbackError) {
console.log('Transaction was rolled back');
}
}
Rollback Triggers
There are two ways to trigger a rollback:
1. Explicit Rollback
Use the rollback() helper when a condition requires aborting:
await scope.begin(async () => {
order.status = 'confirmed';
if (!validateOrder(order)) {
rollback();
}
});
2. Automatic Rollback on Exceptions
Any thrown exception triggers an automatic rollback:
await scope.begin(async () => {
account.balance -= amount;
// If this throws, balance is restored
await paymentService.charge(amount);
});
Nested Rollback Scopes
RollbackScope supports nested rollback scopes with independent rollback behavior:
await outerScope.begin(async () => {
parent.value = 'outer';
try {
await innerScope.begin(async () => {
child.value = 'inner';
rollback(); // Only inner rolls back
});
} catch {
// Inner rolled back, outer continues
}
parent.status = 'done';
}); // Outer commits
Nested Transaction Rules
| Scenario | Result |
|---|---|
| Inner rollback | Inner changes reverted, outer continues |
| Outer rollback | All changes reverted (including committed inner) |
| Inner exception (uncaught) | Propagates to outer, both roll back |
Notification Handling
Notification behavior during rollback scopes is controlled by the fluent API configuration:
With Batched Notifications
When using .withBatchedNotifications(), all mutations are wrapped in batchNotifications():
- No intermediate renders during the transaction
- Single notification when the transaction commits
- Zero notifications on rollback
const scope = createRollbackScope(originator)
.withBatchedNotifications();
await scope.begin(async () => {
// These don't trigger individual notifications
originator.field1 = 'a';
originator.field2 = 'b';
originator.field3 = 'c';
}); // Single notification here
With Standard Notifications
When using .withStandardNotifications(), notifications are sent immediately for each mutation:
const scope = createRollbackScope(originator)
.withStandardNotifications();
await scope.begin(async () => {
originator.field1 = 'a'; // Notification sent
originator.field2 = 'b'; // Notification sent
originator.field3 = 'c'; // Notification sent
});
Best Practices
-
Keep Rollback Scopes focused — Include only related mutations in a single transaction
-
Combine with async/await — The API is designed for async operations
-
Consider nested scopes carefully — Understand the rollback propagation behavior
Related
- Memento Pattern — Foundation for rollback functionality
- Batch Notifications — Notification batching details