DEV Community

Cover image for Vapor Chamber: A Command Bus Built for Vue Vapor
Luciano Federico Pereira
Luciano Federico Pereira

Posted on

Vapor Chamber: A Command Bus Built for Vue Vapor

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 })
Enter fullscreen mode Exit fullscreen mode

One semantic action. One handler. One result.

Installation

npm install vapor-chamber
Enter fullscreen mode Exit fullscreen mode

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 }
Enter fullscreen mode Exit fullscreen mode

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)
}
Enter fullscreen mode Exit fullscreen mode

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())
Enter fullscreen mode Exit fullscreen mode

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 })
Enter fullscreen mode Exit fullscreen mode

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 }
])
Enter fullscreen mode Exit fullscreen mode

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()
Enter fullscreen mode Exit fullscreen mode

Dead Letter Handling

Configure what happens when an unregistered action is dispatched:

const bus = createCommandBus({
  onMissing: 'error'    // 'error' | 'throw' | 'ignore' | (cmd) => {}
})
Enter fullscreen mode Exit fullscreen mode

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)
Enter fullscreen mode Exit fullscreen mode

DevTools Integration

import { setupDevtools } from 'vapor-chamber'

setupDevtools(app, bus)
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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)