Back to KB
Extending
Difficulty
Intermediate
Read Time
7 min
## [](#what-is-an-atomic-transaction)What Is an Atomic Transaction?
By Codcompass Team··7 min read
Atomic Transactions with Rollback Semantics in Reactive Signal Systems
Current Situation Analysis
Traditional reactive batching mechanisms (batch / transaction) only coalesce effect reruns to reduce computational overhead. They lack rollback semantics, meaning that if an operation fails mid-execution—especially across await boundaries—intermediate state mutations leak to downstream subscribers. This results in:
- Partial State Commitment: Effects run with inconsistent snapshots, causing UI flicker or invalid application states.
- Manual Rollover Boilerplate: Developers must manually track pre-operation values and restore them on error, which is error-prone and breaks composability.
- Async Boundary Fragility: Standard batching doesn't natively handle Promise resolution/rejection, making it impossible to guarantee atomicity across asynchronous workflows.
- Nested Isolation Failure: Without a dedicated write-log stack, inner transaction failures either corrupt outer state or require complex manual cleanup.
WOW Moment: Key Findings
| Approach | Flush Frequency | Rollback Capability | Async Boundary Safety | Intermediate State Leakage | Implementation Complexity |
|---|---|---|---|---|---|
Regular batch/transaction | 1 on exit | ❌ None | ⚠️ Manual handling required | 🔴 High (leaks on failure) | 🟢 Low |
| Manual Pre-Value Tracking | 1 on exit | ✅ Yes (manual) | ⚠️ Error-prone across await | 🟡 Medium (depends on dev) | 🔴 High |
| Atomic Transaction | 1 on success | ✅ Automatic | 🟢 Native Promise support | 🟢 Zero (strict isolation) | 🟡 Medium |
Key Findings:
- Atomic transactions guarantee single-flush commitment on success and complete state restoration on failure.
- The write-log stack architecture enables safe nested transactions without cross-contamination.
- Lazy recomputation of
computednodes post-rollback eliminates unnecessary synchronous work while maintaining consistency.
Core Solution
The implementation extends the scheduler with a depth-tracked write log, a muted scheduling flag, and explicit commit/rollback pathways. The signal.set() method hooks into the atomic context to record pre-write values only when equality checks pass.
Extending scheduler.ts
import { markStale } from "./computed.js";
import type { Node } from "./graph.js";
export interface Schedulable { run(): void; disposed?: boolean }
// Internal node shape used by signal/computed
export type InternalNode<T = unknown> = { value: T };
// Write log for atomic transactions
type WriteLog = Map<(Node & InternalNode<unknown>), unknown>;
const queue = new Set<Schedulable>();
let scheduled = false;
// > 0 means we are inside batch/transaction mode (delay microtask flushing)
let batchDepth = 0;
// Atomic transaction depth and log stack
let atomicDepth = 0;
const atomicLogs: WriteLog[] = [];
// Mute scheduling during rollback to prevent scheduleJob from creating new work
let muted = 0;
expor
🎉 Mid-Year Sale — Unlock Full Article
Base plan from just $4.99/mo or $49/yr
Sign in to read the full article and unlock all 635+ tutorials.
Sign In / Register — Start Free Trial7-day free trial · Cancel anytime · 30-day money-back
