Atomic State Commitment — Why Components Never See Partial Updates
In Redux, a selector can read intermediate state in the middle of a dispatch, and middleware can dispatch while another dispatch is still running. SDuX Vault removes that entire category of bugs by separating computing the next state from committing it. The pipeline either commits one complete snapshot or changes nothing at all — your components never see torn state. And because there is no global store to funnel through, many FeatureCells can be resolving updates at the same moment while each one stays perfectly atomic on its own.
Redux Angle: Redux routes every update through a single global store and dispatch system, so intermediate state can be visible during dispatch and middleware re-dispatch.
SDuX Vault Angle: SDuX Vault scopes state to isolated FeatureCells and defers commitment to a microtask boundary, guaranteeing atomic snapshots with no partial visibility.
Partial State Visibility in Redux
Redux dispatch is synchronous and eager. When you call dispatch(action), the action travels through middleware and then through the reducer tree, and the store's state is replaced as that work unfolds. This creates two well-known hazards.
First, intermediate visibility. If middleware or a subscriber reads the store while a dispatch is still in flight, it can observe a state that is neither the fully previous value nor the fully next value — a partially updated tree.
Second, re-dispatch during dispatch. Middleware that dispatches a new action while the current one is still being processed interleaves two updates. The ordering of what each subscriber sees becomes dependent on middleware composition and call timing.
Both hazards share a root cause: computing the next state and making it visible are the same operation. There is no boundary between them.
There is a second, quieter cost to the single global store: every dispatch in the entire application funnels through one reducer tree. Atomicity — to whatever degree Redux provides it — is coupled to a single global serialization point. Unrelated slices of state cannot settle independently because they all share the same dispatch channel.
How Atomic Commitment Works
SDuX Vault executes every state update in two distinct phases that are never interleaved: pipeline computation and state commitment. Pipeline computation determines what the next state should be. State commitment determines when and how that result becomes visible to synchronous getters, reactive observers, callbacks, and DevTools.
During computation, the pipeline performs all of its work without mutating state — interceptor evaluation, resolve behavior execution, operator, filter, and reducer processing, error normalization, and the final outcome decision. Only after the pipeline has fully determined a final outcome does SDuX Vault proceed to commit.
Key takeaway: Partial results, intermediate reducer output, and transient values are never observable outside the pipeline. Either the entire snapshot is committed, or no state change is visible at all.
State ownership lives in independent FeatureCells, each with its own isolated state and execution lifecycle. Here is how you create one:
// main.ts
import { Vault } from '@sdux-vault/core';
Vault({ logLevel: 'off' });
// cart.cell.ts
import { FeatureCell } from '@sdux-vault/core';
export const cartCell = FeatureCell({
key: 'cart',
initialState: { items: [], total: 0 }
});
cartCell.initialize();
The Microtask Boundary
Once pipeline computation completes, SDuX Vault schedules state commitment on a microtask boundary. Signal updates, state callbacks, DevTools notifications, and controller notifications are all executed inside a queueMicrotask callback.
This boundary guarantees three things:
| Guarantee | What it prevents |
|---|---|
| The current pipeline run completes fully before any observer is notified | Partial snapshot visibility |
| No observer can trigger a new pipeline run while a commit is in progress | Interleaved updates and re-entrant writes |
| All observers see the same finalized snapshot | Timing-dependent inconsistencies between subscribers |
The result is an atomic snapshot: fully resolved, fully filtered, fully reduced, and fully normalized. Observers never see intermediate states or partially reduced values. This atomicity applies equally to synchronous inputs, promises, observables, and streams.
Atomicity Without a Global Bottleneck
Atomic commitment in SDuX Vault is not enforced by a single global lock. There is no global store, no global dispatch channel, and no shared selector tree that every update must pass through. Instead, each FeatureCell owns its own execution boundary — its own Conductor and queue — and serialization is scoped to that cell alone.
The practical consequence is worth sitting with: a cart cell, a user-profile cell, and a notifications cell can all be resolving updates in the same tick. Each runs its own compute-then-commit cycle and finalizes on its own microtask boundary. None of them waits on the others, and none of them can leak a half-finished value into another. Atomicity is preserved per cell, in parallel, rather than rationed through one global chokepoint.
Key takeaway: Because serialization is scoped to a single FeatureCell, N cells can update concurrently while every cell still commits one complete, atomic snapshot. Independence is the default — not something you coordinate.
| Concern | Redux global store | SDuX Vault FeatureCells |
|---|---|---|
| Serialization scope | One global dispatch channel | Per-cell — one Conductor queue each |
| Independent settlement | No — all slices share the reducer tree | Yes — each cell finalizes on its own microtask |
| Cross-slice interference | Possible via shared dispatch and subscribers | Structurally impossible — no shared state tree |
This is why atomicity in SDuX Vault scales with the number of features rather than contending against it. Adding a new FeatureCell adds an independent execution boundary — it never widens a global surface that every other update has to negotiate.
Reentrancy Is Structurally Impossible
Because state commitment is deferred to a microtask, any attempt to trigger a new state update from within a reducer, a state callback, a DevTools hook, or an error handler always occurs after the current commit has completed. There is no window in which a new update can wedge itself into an in-progress one.
This eliminates entire classes of bugs common in Redux-style systems:
- Dispatching during reducer execution
- Promise resolution interleaving with state writes
- Observer-triggered infinite loops
Warning: Because commitment is deferred, reading a FeatureCell's state synchronously on the same tick you triggered an update returns the previous committed snapshot — not the pending one. In tests, use the settlement API to wait for the pipeline before asserting.
it('commits a complete snapshot', async () => {
// act — trigger a state update
cartCell.mergeState({ value: mockItems });
// settle — wait for the pipeline to commit
await vaultSettled('cart');
// assert — verify the committed snapshot
expect(cartCell.state.total).toBe(42);
});
Video
Watch the Pipeline Isolation walkthrough for a visual explanation of how each FeatureCell keeps its execution and state fully isolated: https://www.youtube.com/watch?v=TRlvCmluBcE
Deeper Dive
Atomic commitment is not an incidental optimization — it is a deliberate architectural boundary that separates computation from visibility. Read the Pipeline Execution Guarantees for the full execution contract, and the Redux migration guide to see how your existing reducer and selector knowledge carries directly into SDuX Vault.
Top comments (0)