Vapor Chamber: A Command Bus Built for Vue Vapor
Vue Vapor is changing how Vue apps compile. By ditching the Virtual DOM in favor of direct DOM updates using signals, Vapor opens the door for leaner, faster reactivity. Vapor Chamber is a ~2KB command bus designed to match that philosophy: minimal abstraction, predictable data flow, one place to look, debug, and test.
The Problem With Events
The traditional Vue pattern — emit, v-on, scattered handlers — works fine for small apps. But as complexity grows, logic fragments across components and tracing a single user action becomes a hunt.
With events you ask: where did this get handled?
With a command bus you ask: what does this do? — and the answer is always one function.
bus.dispatch('cart.add', product, { quantity: 2 })
One semantic action. One handler. One result.
Installation
npm install vapor-chamber
Requires Node >=18 and Vue >=3.5 (peer dependency, optional).
Core Concepts
Every command has three parts:
| Part | Role | Example |
|---|---|---|
action |
What to do | 'cart.add' |
target |
What to act on |
product object |
payload |
Optional extra data | { quantity: 2 } |
Results always return a consistent shape:
{ ok: boolean, value?: any, error?: Error }
No exceptions leaking out. No ambiguous returns.
Registering Handlers
One action → one handler. That's the contract.
import { createCommandBus } from 'vapor-chamber'
const bus = createCommandBus()
bus.register('cart.add', (cmd) => {
cart.items.push(cmd.target)
return cart.items
})
const result = bus.dispatch('cart.add', product, { quantity: 2 })
if (result.ok) {
console.log('Cart updated:', result.value)
}
Plugin System
Plugins are middleware that wrap every dispatch. They run in priority order (highest first), making it easy to compose cross-cutting concerns without touching handler logic.
import { validator, logger, history } from 'vapor-chamber'
// Validate before execution
bus.use(validator({
'cart.add': (cmd) => cmd.target?.id ? null : 'Missing product ID'
}))
// Log everything (lowest priority, runs last)
bus.use(logger(), { priority: 0 })
// Track undo/redo history
bus.use(history())
Built-in Plugins
| Plugin | What it does |
|---|---|
logger |
Console logging with action filtering |
validator |
Pre-execution validation rules |
history |
Undo/redo tracking |
debounce |
Delay until activity stops |
throttle |
Limit execution frequency |
Async Support
Need to hit an API? Use the async bus:
import { createAsyncCommandBus } from 'vapor-chamber'
const bus = createAsyncCommandBus()
bus.register('user.fetch', async (cmd) => {
const data = await fetch(`/api/users/${cmd.target.id}`)
return data.json()
})
const result = await bus.dispatch('user.fetch', { id: 42 })
Batch Dispatching
Run multiple commands as a unit. Stops on the first failure.
const result = await bus.dispatchBatch([
{ action: 'cart.clear', target: cart },
{ action: 'order.create', target: orderData },
{ action: 'email.send', target: user }
])
Vue Vapor Composables
Vapor Chamber ships with composables built for Vue Vapor's signal-based reactivity:
import { useCommandBus, useCommand, useCommandState, useCommandHistory } from 'vapor-chamber'
// Access the shared bus (tree-shakeable)
const bus = useCommandBus()
// Dispatch with reactive loading/error signals
const { dispatch, loading, error } = useCommand('cart.add')
// State management via commands
const { state } = useCommandState('cart', initialState)
// Undo/redo
const { undo, redo, canUndo, canRedo } = useCommandHistory()
Dead Letter Handling
Configure what happens when an unregistered action is dispatched:
const bus = createCommandBus({
onMissing: 'error' // 'error' | 'throw' | 'ignore' | (cmd) => {}
})
Testing
createTestBus() records dispatches without executing handlers — perfect for unit tests.
import { createTestBus } from 'vapor-chamber'
const bus = createTestBus()
bus.dispatch('cart.add', product)
expect(bus.dispatched[0].action).toBe('cart.add')
expect(bus.dispatched[0].target).toEqual(product)
DevTools Integration
import { setupDevtools } from 'vapor-chamber'
setupDevtools(app, bus)
Adds a command timeline and inspector panel to Vue DevTools.
Why Vapor Chamber?
- ~2KB gzipped — practically free
- Full TypeScript support out of the box
- Framework-optional — works without Vue too
- Predictable — consistent result shape everywhere
- Testable — first-class testing utilities
Get Started
npm install vapor-chamber
If you're building with Vue Vapor and want your data flow as direct as your DOM updates, give Vapor Chamber a try.
Top comments (0)