npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

vapor-chamber

v0.1.0

Published

Lightweight command bus for Vue Vapor - plugins, hooks, undo/redo

Readme

Vapor Chamber

A lightweight command bus designed for Vue Vapor. ~1KB core.

What is Vue Vapor?

Vue Vapor is Vue's upcoming compilation strategy that eliminates the Virtual DOM. Instead of diffing virtual trees, Vapor compiles templates to direct DOM operations using signals - reactive primitives that update only what changed.

Vapor Chamber embraces this philosophy: minimal abstraction, direct updates, signal-native reactivity.

Why a Command Bus?

Traditional event systems scatter logic across components. A command bus centralizes it:

Event-driven (scattered)          Command bus (centralized)
─────────────────────────         ─────────────────────────
Component A emits 'add'    →      dispatch('cart.add', product)
Component B listens...            ↓
Component C also listens...       Handler executes once
Who handles what? When?           Plugins observe/modify
                                  Result returned

Benefits:

  • Semantic actions - cart.add is clearer than emit('add')
  • Single handler - One place to look, debug, test
  • Plugin pipeline - Cross-cutting concerns (logging, validation, analytics) without cluttering handlers
  • Undo/redo - Command history is natural when actions are explicit

Install

npm install vapor-chamber

Quick Start

import { createCommandBus, logger, validator } from 'vapor-chamber';

const bus = createCommandBus();

// Add plugins
bus.use(logger());
bus.use(validator({
  'cart.add': (cmd) => cmd.payload?.quantity > 0 ? null : 'Quantity required'
}));

// Register handler
bus.register('cart.add', (cmd) => {
  cart.items.push({ ...cmd.target, quantity: cmd.payload.quantity });
  return cart.items;
});

// Dispatch
const result = bus.dispatch('cart.add', product, { quantity: 2 });
if (result.ok) {
  console.log('Added:', result.value);
} else {
  console.error('Failed:', result.error);
}

Core Concepts

Commands

A command has three parts:

bus.dispatch(
  'cart.add',      // action - what to do
  product,         // target - what to act on
  { quantity: 2 }  // payload - additional data (optional)
);

Handlers

One handler per action. Returns a value or throws:

bus.register('cart.add', (cmd) => {
  // cmd.action  = 'cart.add'
  // cmd.target  = product
  // cmd.payload = { quantity: 2 }

  cart.items.push(cmd.target);
  return cart.items; // becomes result.value
});

Results

Every dispatch returns a result:

type CommandResult = {
  ok: boolean;     // success or failure
  value?: any;     // handler return value (if ok)
  error?: Error;   // error thrown (if not ok)
};

Plugins

Plugins wrap handlers. They can modify commands, short-circuit execution, observe results, or transform output:

const timingPlugin: Plugin = (cmd, next) => {
  const start = Date.now();
  const result = next();  // call next plugin or handler
  console.log(`${cmd.action} took ${Date.now() - start}ms`);
  return result;
};

bus.use(timingPlugin);

Plugins execute in order: first added = outermost wrapper.

Built-in Plugins

| Plugin | Description | |--------|-------------| | logger(options?) | Log commands to console | | validator(rules) | Validate commands before execution | | history(options?) | Track command history for undo/redo | | debounce(actions, wait) | Delay execution until activity stops | | throttle(actions, wait) | Limit execution frequency |

logger

bus.use(logger({ collapsed: true, filter: (cmd) => cmd.action.startsWith('cart.') }));

validator

bus.use(validator({
  'cart.add': (cmd) => {
    if (!cmd.target?.id) return 'Product must have an ID';
    return null;  // null = valid
  }
}));

history

const historyPlugin = history({ maxSize: 100 });
bus.use(historyPlugin);

historyPlugin.undo();
historyPlugin.redo();
historyPlugin.getState(); // { past, future, canUndo, canRedo }

debounce

bus.use(debounce(['search.query'], 300)); // wait 300ms after last call

throttle

bus.use(throttle(['ui.scroll'], 100)); // max once per 100ms

Async Command Bus

For async handlers (API calls, IndexedDB, etc.):

import { createAsyncCommandBus } from 'vapor-chamber';

const bus = createAsyncCommandBus();

bus.register('user.fetch', async (cmd) => {
  const response = await fetch(`/api/users/${cmd.target.id}`);
  return response.json();
});

const result = await bus.dispatch('user.fetch', { id: 123 });

Vapor Composables

For Vue Vapor components:

useCommand

<script setup>
import { useCommand } from 'vapor-chamber';

const { dispatch, loading, lastError } = useCommand();
</script>

<template>
  <button @click="dispatch('save', doc)" :disabled="loading.value">Save</button>
  <p v-if="lastError.value">{{ lastError.value.message }}</p>
</template>

useCommandState

<script setup>
import { useCommandState } from 'vapor-chamber';

const { state: cart } = useCommandState(
  { items: [], total: 0 },
  {
    'cart.add': (state, cmd) => ({
      items: [...state.items, cmd.target],
      total: state.total + cmd.target.price
    })
  }
);
</script>

useCommandHistory

<script setup>
import { useCommandHistory } from 'vapor-chamber';

const { canUndo, canRedo, undo, redo } = useCommandHistory({
  filter: (cmd) => cmd.action.startsWith('editor.')
});
</script>

Examples

See the examples/ folder for complete, runnable examples:

| Example | Description | |---------|-------------| | shopping-cart.ts | Cart with validation, history, and undo/redo | | form-validation.ts | Form validation with error handling | | async-api.ts | Async handlers with retry plugin | | realtime-search.ts | Debounced search queries | | custom-plugins.ts | Analytics, auth guard, rate limiter plugins | | vue-vapor-component.vue | Full Vue Vapor todo app |

Run TypeScript examples with:

npx ts-node examples/shopping-cart.ts

API Reference

Core

| Function | Description | |----------|-------------| | createCommandBus() | Create a synchronous command bus | | createAsyncCommandBus() | Create an async command bus |

Command Bus Methods

| Method | Description | |--------|-------------| | dispatch(action, target, payload?) | Execute a command | | register(action, handler) | Register a handler (returns unregister fn) | | use(plugin) | Add a plugin (returns unsubscribe fn) | | onAfter(hook) | Run callback after every command |

Composables

| Composable | Description | |------------|-------------| | useCommand() | Dispatch with reactive loading/error state | | useCommandState(initial, handlers) | State managed by commands | | useCommandHistory(options?) | Reactive undo/redo | | getCommandBus() | Get shared bus instance | | setCommandBus(bus) | Set shared bus instance |

Documentation

See the docs/ folder for detailed documentation:

Design Goals

  1. Minimal - ~1KB core, no dependencies
  2. Vapor-native - Built for signals, not VDOM
  3. Composable - Plugins for everything
  4. Type-safe - Full TypeScript support
  5. Predictable - Sync by default, explicit async

License

GNU Lesser General Public License v2.1