vapor-chamber 0.2.0 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -3,12 +3,14 @@
3
3
  </p>
4
4
 
5
5
  <p align="center">
6
- A lightweight command bus designed for <a href="https://github.com/vuejs/vue-vapor">Vue Vapor</a>. ~2KB gzipped. Optional DevTools integration.
6
+ A lightweight command bus designed for <a href="https://github.com/vuejs/vue-vapor">Vue Vapor</a>. ~2KB gzipped. Vue 3.6 Vapor aligned. Optional DevTools integration.
7
7
  </p>
8
8
 
9
9
  ## What is Vue Vapor?
10
10
 
11
- 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.
11
+ Vue Vapor is Vue's 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.
12
+
13
+ **As of Vue 3.6 beta**, Vapor mode is feature-complete for all stable APIs. The reactivity engine has been rewritten atop [alien-signals](https://github.com/stackblitz/alien-signals), delivering ~14% less memory and faster dependency tracking. `ref()` is now a signal internally.
12
14
 
13
15
  **Vapor Chamber** embraces this philosophy: minimal abstraction, direct updates, signal-native reactivity.
14
16
 
@@ -35,17 +37,17 @@ bus.on('cart:add', updateBadge); // now two handlers, hard to trace
35
37
  ```
36
38
  // After — Vapor Chamber
37
39
  // Anywhere in the app
38
- bus.dispatch('cart.add', product, { quantity: 1 });
40
+ bus.dispatch('cart_add', product, { quantity: 1 });
39
41
 
40
42
  // One place, once:
41
- bus.register('cart.add', (cmd) => {
43
+ bus.register('cart_add', (cmd) => {
42
44
  cart.items.push(cmd.target);
43
45
  return cart.items;
44
46
  });
45
47
 
46
48
  // Cross-cutting concerns as plugins, not scattered listeners:
47
49
  bus.use(logger());
48
- bus.use(validator({ 'cart.add': (cmd) => cmd.target.id ? null : 'Missing ID' }));
50
+ bus.use(validator({ 'cart_add': (cmd) => cmd.target.id ? null : 'Missing ID' }));
49
51
  bus.use(analyticsPlugin);
50
52
  ```
51
53
 
@@ -58,7 +60,7 @@ Traditional event systems scatter logic across components. A command bus central
58
60
  ```
59
61
  Event-driven (scattered) Command bus (centralized)
60
62
  ───────────────────────── ─────────────────────────
61
- Component A emits 'add' → dispatch('cart.add', product)
63
+ Component A emits 'add' → dispatch('cart_add', product)
62
64
  Component B listens... ↓
63
65
  Component C also listens... Handler executes once
64
66
  Who handles what? When? Plugins observe/modify
@@ -66,10 +68,10 @@ Who handles what? When? Plugins observe/modify
66
68
  ```
67
69
 
68
70
  **Benefits:**
69
- - **Semantic actions** - `cart.add` is clearer than `emit('add')`
70
- - **Single handler** - One place to look, debug, test
71
- - **Plugin pipeline** - Cross-cutting concerns (logging, validation, analytics) without cluttering handlers
72
- - **Undo/redo** - Command history is natural when actions are explicit
71
+ - **Semantic actions** `cart_add` is clearer than `emit('add')`
72
+ - **Single handler** One place to look, debug, test
73
+ - **Plugin pipeline** Cross-cutting concerns (logging, validation, analytics) without cluttering handlers
74
+ - **Undo/redo** Command history is natural when actions are explicit
73
75
 
74
76
  ## Install
75
77
 
@@ -77,6 +79,8 @@ Who handles what? When? Plugins observe/modify
77
79
  npm install vapor-chamber
78
80
  ```
79
81
 
82
+ **Requirements:** Node.js ≥20.19.0 | Vue ≥3.5.0 (optional peer dep) | Vite 7/8 compatible
83
+
80
84
  ## Quick Start
81
85
 
82
86
  ```typescript
@@ -87,17 +91,17 @@ const bus = createCommandBus();
87
91
  // Add plugins
88
92
  bus.use(logger());
89
93
  bus.use(validator({
90
- 'cart.add': (cmd) => cmd.payload?.quantity > 0 ? null : 'Quantity required'
94
+ 'cart_add': (cmd) => cmd.payload?.quantity > 0 ? null : 'Quantity required'
91
95
  }));
92
96
 
93
97
  // Register handler
94
- bus.register('cart.add', (cmd) => {
98
+ bus.register('cart_add', (cmd) => {
95
99
  cart.items.push({ ...cmd.target, quantity: cmd.payload.quantity });
96
100
  return cart.items;
97
101
  });
98
102
 
99
103
  // Dispatch
100
- const result = bus.dispatch('cart.add', product, { quantity: 2 });
104
+ const result = bus.dispatch('cart_add', product, { quantity: 2 });
101
105
  if (result.ok) {
102
106
  console.log('Added:', result.value);
103
107
  } else {
@@ -105,6 +109,56 @@ if (result.ok) {
105
109
  }
106
110
  ```
107
111
 
112
+ ## Vue 3.6 Vapor Mode
113
+
114
+ Vapor Chamber v0.4.0 is aligned with Vue 3.6 beta. It works in three contexts:
115
+
116
+ ### 1. Pure Vapor App (smallest bundle)
117
+
118
+ ```typescript
119
+ import { createVaporChamberApp, getCommandBus } from 'vapor-chamber';
120
+ import App from './App.vue';
121
+
122
+ // No VDOM runtime — ~10KB baseline
123
+ createVaporChamberApp(App).mount('#app');
124
+ ```
125
+
126
+ ```vue
127
+ <script setup vapor>
128
+ import { useCommand } from 'vapor-chamber';
129
+
130
+ const { dispatch, loading } = useCommand();
131
+ </script>
132
+ ```
133
+
134
+ ### 2. Mixed VDOM + Vapor (gradual migration)
135
+
136
+ ```typescript
137
+ import { createApp } from 'vue';
138
+ import { getVaporInteropPlugin } from 'vapor-chamber';
139
+
140
+ const app = createApp(App);
141
+ const interop = getVaporInteropPlugin();
142
+ if (interop) app.use(interop);
143
+ app.mount('#app');
144
+ ```
145
+
146
+ Now Vapor and VDOM components can nest inside each other. Useful for incremental migration.
147
+
148
+ ### 3. Standard Vue 3 (no Vapor)
149
+
150
+ Everything works without Vapor. The signal shim auto-detects Vue's `ref()` for reactivity. In Vue 3.6+ this is alien-signals backed.
151
+
152
+ ### Vapor Detection
153
+
154
+ ```typescript
155
+ import { isVaporAvailable } from 'vapor-chamber';
156
+
157
+ if (isVaporAvailable()) {
158
+ // Vue 3.6+ with createVaporApp available
159
+ }
160
+ ```
161
+
108
162
  ## Core Concepts
109
163
 
110
164
  ### Commands
@@ -113,27 +167,48 @@ A command has three parts:
113
167
 
114
168
  ```typescript
115
169
  bus.dispatch(
116
- 'cart.add', // action - what to do
170
+ 'cart_add', // action - what to do
117
171
  product, // target - what to act on
118
172
  { quantity: 2 } // payload - additional data (optional)
119
173
  );
120
174
  ```
121
175
 
176
+ ### Naming Convention
177
+
178
+ Enforce consistent action names at register and dispatch time:
179
+
180
+ ```typescript
181
+ const bus = createCommandBus({
182
+ naming: {
183
+ pattern: /^[a-z][a-z0-9]*(_[a-z][a-z0-9]*)+$/, // snake_case
184
+ onViolation: 'throw' // or 'warn' or 'ignore'
185
+ }
186
+ });
187
+
188
+ bus.register('cart_add', handler); // ✓ passes
189
+ bus.register('cartAdd', handler); // ✗ throws
190
+ ```
191
+
122
192
  ### Handlers
123
193
 
124
194
  One handler per action. Returns a value or throws:
125
195
 
126
196
  ```typescript
127
- bus.register('cart.add', (cmd) => {
128
- // cmd.action = 'cart.add'
129
- // cmd.target = product
130
- // cmd.payload = { quantity: 2 }
131
-
197
+ bus.register('cart_add', (cmd) => {
132
198
  cart.items.push(cmd.target);
133
199
  return cart.items; // becomes result.value
134
200
  });
135
201
  ```
136
202
 
203
+ Register with options for undo support and per-command throttling:
204
+
205
+ ```typescript
206
+ bus.register('cart_add', addHandler, {
207
+ undo: (cmd) => { cart.items.pop(); },
208
+ throttle: 300, // max once per 300ms per target
209
+ });
210
+ ```
211
+
137
212
  ### Results
138
213
 
139
214
  Every dispatch returns a result:
@@ -169,6 +244,38 @@ bus.use(analyticsPlugin, { priority: 1 }); // runs after validation
169
244
  bus.use(loggerPlugin); // priority 0 (default, runs last)
170
245
  ```
171
246
 
247
+ ### Wildcard Listeners
248
+
249
+ Subscribe to command patterns without being a handler:
250
+
251
+ ```typescript
252
+ // All commands
253
+ bus.on('*', (cmd, result) => analytics.track(cmd.action));
254
+
255
+ // Prefix matching
256
+ bus.on('shop_*', (cmd, result) => console.log('Shop event:', cmd.action));
257
+
258
+ // Exact match
259
+ bus.on('cart_add', (cmd, result) => updateBadge());
260
+ ```
261
+
262
+ ### Request / Response
263
+
264
+ Async request/response pattern with timeout:
265
+
266
+ ```typescript
267
+ // Register a responder
268
+ bus.respond('get_auth_token', async (cmd) => {
269
+ const response = await fetch('/api/token');
270
+ return response.json();
271
+ });
272
+
273
+ // Request with timeout
274
+ const result = await bus.request('get_auth_token', { userId: 42 }, { timeout: 3000 });
275
+ ```
276
+
277
+ Falls back to normal `dispatch()` if no responder is registered.
278
+
172
279
  ## Built-in Plugins
173
280
 
174
281
  | Plugin | Description |
@@ -178,18 +285,20 @@ bus.use(loggerPlugin); // priority 0 (default, runs last)
178
285
  | `history(options?)` | Track command history for undo/redo |
179
286
  | `debounce(actions, wait)` | Delay execution until activity stops |
180
287
  | `throttle(actions, wait)` | Limit execution frequency |
288
+ | `authGuard(options)` | Block protected commands when unauthenticated |
289
+ | `optimistic(handlers)` | Apply optimistic updates, rollback on failure |
181
290
 
182
291
  ### logger
183
292
 
184
293
  ```typescript
185
- bus.use(logger({ collapsed: true, filter: (cmd) => cmd.action.startsWith('cart.') }));
294
+ bus.use(logger({ collapsed: true, filter: (cmd) => cmd.action.startsWith('cart_') }));
186
295
  ```
187
296
 
188
297
  ### validator
189
298
 
190
299
  ```typescript
191
300
  bus.use(validator({
192
- 'cart.add': (cmd) => {
301
+ 'cart_add': (cmd) => {
193
302
  if (!cmd.target?.id) return 'Product must have an ID';
194
303
  return null; // null = valid
195
304
  }
@@ -207,16 +316,49 @@ historyPlugin.redo();
207
316
  historyPlugin.getState(); // { past, future, canUndo, canRedo }
208
317
  ```
209
318
 
319
+ With bus-backed undo (executes inverse handlers):
320
+
321
+ ```typescript
322
+ const historyPlugin = history({ maxSize: 100, bus });
323
+ bus.use(historyPlugin);
324
+
325
+ // If cart_add was registered with { undo: fn }, calling undo() executes it
326
+ historyPlugin.undo();
327
+ ```
328
+
210
329
  ### debounce
211
330
 
212
331
  ```typescript
213
- bus.use(debounce(['search.query'], 300)); // wait 300ms after last call
332
+ bus.use(debounce(['search_query'], 300)); // wait 300ms after last call
214
333
  ```
215
334
 
216
335
  ### throttle
217
336
 
218
337
  ```typescript
219
- bus.use(throttle(['ui.scroll'], 100)); // max once per 100ms
338
+ bus.use(throttle(['ui_scroll'], 100)); // max once per 100ms
339
+ ```
340
+
341
+ ### authGuard
342
+
343
+ ```typescript
344
+ bus.use(authGuard({
345
+ isAuthenticated: () => !!user.value,
346
+ protected: ['shop_cart_', 'shop_wishlist_'],
347
+ onUnauthenticated: (cmd) => router.push('/login'),
348
+ }));
349
+ ```
350
+
351
+ ### optimistic
352
+
353
+ ```typescript
354
+ bus.use(optimistic({
355
+ 'cart_add': {
356
+ apply: (cmd) => {
357
+ cartCount.value++;
358
+ return () => { cartCount.value--; }; // rollback function
359
+ }
360
+ }
361
+ }));
220
362
  ```
221
363
 
222
364
  ## Batch Dispatch
@@ -225,42 +367,27 @@ Dispatch multiple commands as a unit. Stops on the first failure:
225
367
 
226
368
  ```typescript
227
369
  const result = bus.dispatchBatch([
228
- { action: 'cart.add', target: cart, payload: item },
229
- { action: 'totals.update', target: cart },
230
- { action: 'analytics.track', target: session, payload: item },
370
+ { action: 'cart_add', target: cart, payload: item },
371
+ { action: 'totals_update', target: cart },
372
+ { action: 'analytics_track', target: session, payload: item },
231
373
  ]);
232
374
 
233
375
  if (result.ok) {
234
376
  console.log('All succeeded:', result.results);
235
377
  } else {
236
378
  console.error('Stopped at failure:', result.error);
237
- console.log('Partial results:', result.results);
238
379
  }
239
380
  ```
240
381
 
241
- Works on both sync and async buses.
242
-
243
382
  ## Dead Letter Handling
244
383
 
245
384
  Configure what happens when a command has no registered handler:
246
385
 
247
386
  ```typescript
248
- // Default: returns { ok: false, error }
249
- createCommandBus()
250
-
251
- // Throw instead of returning an error result
252
- createCommandBus({ onMissing: 'throw' })
253
-
254
- // Silently succeed (useful for optional commands)
255
- createCommandBus({ onMissing: 'ignore' })
256
-
257
- // Custom fallback
258
- createCommandBus({
259
- onMissing: (cmd) => {
260
- console.warn(`Unhandled: ${cmd.action}`);
261
- return { ok: true, value: null };
262
- }
263
- })
387
+ createCommandBus() // default: returns { ok: false, error }
388
+ createCommandBus({ onMissing: 'throw' }) // throws the error
389
+ createCommandBus({ onMissing: 'ignore' }) // returns { ok: true, value: undefined }
390
+ createCommandBus({ onMissing: (cmd) => { ... } }) // custom fallback
264
391
  ```
265
392
 
266
393
  ## Async Command Bus
@@ -272,22 +399,22 @@ import { createAsyncCommandBus } from 'vapor-chamber';
272
399
 
273
400
  const bus = createAsyncCommandBus();
274
401
 
275
- bus.register('user.fetch', async (cmd) => {
402
+ bus.register('user_fetch', async (cmd) => {
276
403
  const response = await fetch(`/api/users/${cmd.target.id}`);
277
404
  return response.json();
278
405
  });
279
406
 
280
- const result = await bus.dispatch('user.fetch', { id: 123 });
407
+ const result = await bus.dispatch('user_fetch', { id: 123 });
281
408
  ```
282
409
 
283
410
  ## Vapor Composables
284
411
 
285
- For Vue Vapor components:
286
-
287
412
  ### useCommand
288
413
 
414
+ Dispatch commands with reactive loading/error state:
415
+
289
416
  ```vue
290
- <script setup>
417
+ <script setup vapor>
291
418
  import { useCommand } from 'vapor-chamber';
292
419
 
293
420
  const { dispatch, loading, lastError } = useCommand();
@@ -299,16 +426,36 @@ const { dispatch, loading, lastError } = useCommand();
299
426
  </template>
300
427
  ```
301
428
 
429
+ ### defineVaporCommand
430
+
431
+ Zero-overhead dispatch for hot paths — no reactive `loading`/`lastError` signals created.
432
+ Ideal for GA4 tracking, scroll events, debounced search, fire-and-forget patterns:
433
+
434
+ ```vue
435
+ <script setup vapor>
436
+ import { defineVaporCommand } from 'vapor-chamber';
437
+
438
+ const { dispatch } = defineVaporCommand('analytics_track', (cmd) => {
439
+ gtag('event', cmd.target.event, cmd.target.params);
440
+ });
441
+
442
+ // Fire-and-forget — no reactive overhead in the alien-signals graph
443
+ dispatch({ event: 'page_view', params: { page: '/shop' } });
444
+ </script>
445
+ ```
446
+
302
447
  ### useCommandState
303
448
 
449
+ State managed by commands:
450
+
304
451
  ```vue
305
- <script setup>
452
+ <script setup vapor>
306
453
  import { useCommandState } from 'vapor-chamber';
307
454
 
308
455
  const { state: cart } = useCommandState(
309
456
  { items: [], total: 0 },
310
457
  {
311
- 'cart.add': (state, cmd) => ({
458
+ 'cart_add': (state, cmd) => ({
312
459
  items: [...state.items, cmd.target],
313
460
  total: state.total + cmd.target.price
314
461
  })
@@ -319,47 +466,50 @@ const { state: cart } = useCommandState(
319
466
 
320
467
  ### useCommandHistory
321
468
 
469
+ Reactive undo/redo:
470
+
322
471
  ```vue
323
- <script setup>
472
+ <script setup vapor>
324
473
  import { useCommandHistory } from 'vapor-chamber';
325
474
 
326
475
  const { canUndo, canRedo, undo, redo } = useCommandHistory({
327
- filter: (cmd) => cmd.action.startsWith('editor.')
476
+ filter: (cmd) => cmd.action.startsWith('editor_')
328
477
  });
329
478
  </script>
330
479
  ```
331
480
 
332
481
  ### useCommandBus
333
482
 
334
- Lightweight composable for the "toolbox" patternimport only when needed, tree-shaken out of builds that don't use it. Returns the shared bus directly:
483
+ Lightweight access to the shared bus — tree-shakeable:
335
484
 
336
485
  ```typescript
337
486
  import { useCommandBus } from 'vapor-chamber';
338
487
 
339
488
  const bus = useCommandBus();
340
- bus.dispatch('cart.add', product, { quantity: 1 });
489
+ bus.dispatch('cart_add', product, { quantity: 1 });
341
490
  ```
342
491
 
343
- Use `useCommand()` when you need reactive `loading`/`lastError` signals. Use `useCommandBus()` when you just need to dispatch.
492
+ Use `useCommand()` when you need reactive `loading`/`lastError` signals. Use `defineVaporCommand()` for zero-overhead hot paths. Use `useCommandBus()` when you just need to dispatch.
344
493
 
345
494
  ### configureSignal
346
495
 
347
- Inject Vue Vapor's native signal factory once at app setup. Falls back to a built-in shim automatically in non-Vapor environments (standard Vue 3, tests, SSR):
496
+ Inject a custom signal factory. In Vue 3.6+, `ref()` is auto-detected and backed by alien-signals calling `configureSignal` is only needed for custom signal implementations:
348
497
 
349
498
  ```typescript
350
- import { signal } from 'vue-vapor';
499
+ import { ref } from 'vue';
351
500
  import { configureSignal } from 'vapor-chamber';
352
501
 
353
- configureSignal(signal);
502
+ configureSignal(ref); // explicit — usually auto-detected
354
503
  ```
355
504
 
356
505
  ### Testing
357
506
 
358
- `createTestBus()` records all dispatched commands without executing real handlers. Use it to test components that call `dispatch` without wiring up the full application:
507
+ `createTestBus()` records all dispatched commands without executing real handlers:
359
508
 
360
509
  ```typescript
361
510
  import { createTestBus, setCommandBus } from 'vapor-chamber';
362
- import { describe, it, expect, beforeEach } from 'vitest';
511
+ import { describe, it, expect, beforeEach, afterEach } from 'vitest';
512
+ import { resetCommandBus } from 'vapor-chamber';
363
513
 
364
514
  describe('CartButton', () => {
365
515
  let bus: TestBus;
@@ -369,19 +519,15 @@ describe('CartButton', () => {
369
519
  setCommandBus(bus);
370
520
  });
371
521
 
372
- it('dispatches cart.add on click', () => {
373
- // ... render component, click button ...
374
- expect(bus.wasDispatched('cart.add')).toBe(true);
375
- expect(bus.getDispatched('cart.add')[0].cmd.payload).toEqual({ quantity: 1 });
522
+ afterEach(() => {
523
+ resetCommandBus();
376
524
  });
377
- });
378
- ```
379
-
380
- Register real handlers for actions you want to test deeply:
381
525
 
382
- ```typescript
383
- bus.register('cart.add', (cmd) => {
384
- // real handler logic
526
+ it('dispatches cart_add on click', () => {
527
+ // ... render component, click button ...
528
+ expect(bus.wasDispatched('cart_add')).toBe(true);
529
+ expect(bus.getDispatched('cart_add')[0].cmd.payload).toEqual({ quantity: 1 });
530
+ });
385
531
  });
386
532
  ```
387
533
 
@@ -398,12 +544,6 @@ setupDevtools(getCommandBus(), app);
398
544
  app.mount('#app');
399
545
  ```
400
546
 
401
- The inspector shows:
402
- - Every dispatched command with its action, target, payload, and result
403
- - Green `ok` / red `error` tags at a glance
404
- - Full detail (value or error message) when a command is selected
405
- - Filterable tree by action name
406
-
407
547
  ## Examples
408
548
 
409
549
  See the [`examples/`](./examples) folder for complete, runnable examples:
@@ -417,11 +557,6 @@ See the [`examples/`](./examples) folder for complete, runnable examples:
417
557
  | [`custom-plugins.ts`](./examples/custom-plugins.ts) | Analytics, auth guard, rate limiter plugins |
418
558
  | [`vue-vapor-component.vue`](./examples/vue-vapor-component.vue) | Full Vue Vapor todo app |
419
559
 
420
- Run TypeScript examples with:
421
- ```bash
422
- npx ts-node examples/shopping-cart.ts
423
- ```
424
-
425
560
  ## API Reference
426
561
 
427
562
  ### Core
@@ -430,13 +565,14 @@ npx ts-node examples/shopping-cart.ts
430
565
  |----------|-------------|
431
566
  | `createCommandBus(options?)` | Create a synchronous command bus |
432
567
  | `createAsyncCommandBus(options?)` | Create an async command bus |
433
- | `createTestBus(options?)` | Create a test bus that records dispatches (see [Testing](#testing)) |
568
+ | `createTestBus(options?)` | Create a test bus that records dispatches |
434
569
 
435
570
  **`CommandBusOptions`**
436
571
 
437
572
  | Option | Type | Default | Description |
438
573
  |--------|------|---------|-------------|
439
- | `onMissing` | `'error' \| 'throw' \| 'ignore' \| fn` | `'error'` | Behavior when no handler is registered for an action |
574
+ | `onMissing` | `'error' \| 'throw' \| 'ignore' \| fn` | `'error'` | Behavior when no handler is registered |
575
+ | `naming` | `{ pattern: RegExp, onViolation?: string }` | — | Enforce naming convention on actions |
440
576
 
441
577
  ### Command Bus Methods
442
578
 
@@ -444,22 +580,31 @@ npx ts-node examples/shopping-cart.ts
444
580
  |--------|-------------|
445
581
  | `dispatch(action, target, payload?)` | Execute a command |
446
582
  | `dispatchBatch(commands[])` | Execute multiple commands; stops on first failure |
447
- | `register(action, handler)` | Register a handler (returns unregister fn) |
448
- | `use(plugin, options?)` | Add a plugin (returns unsubscribe fn). `options.priority` controls order — higher runs first |
583
+ | `register(action, handler, options?)` | Register a handler. Options: `{ undo?, throttle?, debounce? }` |
584
+ | `use(plugin, options?)` | Add a plugin. `options.priority` controls order |
449
585
  | `onAfter(hook)` | Run callback after every command |
586
+ | `on(pattern, listener)` | Subscribe to commands matching a pattern (`*`, `prefix_*`, exact) |
587
+ | `request(action, target, options?)` | Async request/response with timeout |
588
+ | `respond(action, handler)` | Register a responder for `request()` calls |
589
+ | `getUndoHandler(action)` | Get the undo handler for an action |
450
590
 
451
591
  ### Composables
452
592
 
453
593
  | Composable | Description |
454
594
  |------------|-------------|
455
- | `useCommandBus()` | Get the shared bus lightweight, tree-shakeable |
456
- | `useCommand()` | Dispatch with reactive loading/error state. Returns `dispose()` to clean up registered handlers/plugins |
457
- | `useCommandState(initial, handlers)` | State managed by commands. Returns `dispose()` to unregister handlers |
458
- | `useCommandHistory(options?)` | Reactive undo/redo. Returns `dispose()` to unsubscribe |
459
- | `getCommandBus()` | Get shared bus instance |
595
+ | `useCommand()` | Dispatch with reactive loading/error state |
596
+ | `defineVaporCommand(action, handler, options?)` | Zero-overhead dispatch for hot paths |
597
+ | `useCommandState(initial, handlers)` | State managed by commands |
598
+ | `useCommandHistory(options?)` | Reactive undo/redo |
599
+ | `useCommandBus()` | Get shared bus instance |
600
+ | `getCommandBus()` | Get shared bus instance (non-composable) |
460
601
  | `setCommandBus(bus)` | Set shared bus instance |
461
- | `configureSignal(fn)` | Inject a custom signal factory (e.g. Vue Vapor's native `signal`) |
462
- | `setupDevtools(bus, app)` | Connect bus to Vue DevTools. No-ops automatically in production builds |
602
+ | `resetCommandBus()` | Reset shared bus to null (useful in tests) |
603
+ | `configureSignal(fn)` | Inject a custom signal factory |
604
+ | `isVaporAvailable()` | Returns true if Vue 3.6+ Vapor mode is detected |
605
+ | `createVaporChamberApp(component, props?)` | Create a Vapor app instance (requires Vue 3.6+) |
606
+ | `getVaporInteropPlugin()` | Returns `vaporInteropPlugin` for mixed trees |
607
+ | `setupDevtools(bus, app)` | Connect bus to Vue DevTools |
463
608
 
464
609
  ## Roadmap
465
610
 
@@ -471,6 +616,15 @@ npx ts-node examples/shopping-cart.ts
471
616
  | Middleware priority/ordering | ✅ Done |
472
617
  | Dead letter handling (`onMissing`) | ✅ Done |
473
618
  | Testing utilities (`createTestBus`) | ✅ Done |
619
+ | Naming convention enforcement | ✅ Done (v0.3.0) |
620
+ | Wildcard listeners (`on`) | ✅ Done (v0.3.0) |
621
+ | Request/response pattern | ✅ Done (v0.3.0) |
622
+ | Per-command throttle/undo at register | ✅ Done (v0.3.0) |
623
+ | Auth guard plugin | ✅ Done (v0.3.0) |
624
+ | Optimistic update plugin | ✅ Done (v0.3.0) |
625
+ | Vue 3.6 Vapor alignment | ✅ Done (v0.4.0) |
626
+ | `defineVaporCommand` zero-overhead composable | ✅ Done (v0.4.0) |
627
+ | `onScopeDispose` lifecycle alignment | ✅ Done (v0.4.0) |
474
628
  | Persistence plugin (localStorage / IndexedDB) | Planned |
475
629
  | SSR support | Planned (pending Vue Vapor stabilization) |
476
630
 
@@ -478,16 +632,18 @@ npx ts-node examples/shopping-cart.ts
478
632
 
479
633
  See the [`docs/`](./docs) folder for detailed documentation:
480
634
 
481
- - [Whitepaper](./docs/whitepaper.md) - Design philosophy and architecture
482
- - [SSR Guide](./docs/ssr.md) - Server-side rendering and hydration
635
+ - [Whitepaper](./docs/whitepaper.md) Design philosophy and architecture
636
+ - [Vue 3.6 Vapor Alignment](./docs/whitepaper-vue36.md) Alien-signals, Vapor mode, and migration strategy
637
+ - [SSR Guide](./docs/ssr.md) — Server-side rendering and hydration
483
638
 
484
639
  ## Design Goals
485
640
 
486
- 1. **Minimal** - ~1KB core, no dependencies
487
- 2. **Vapor-native** - Built for signals, not VDOM
488
- 3. **Composable** - Plugins for everything
489
- 4. **Type-safe** - Full TypeScript support
490
- 5. **Predictable** - Sync by default, explicit async
641
+ 1. **Minimal** ~1KB core, no dependencies
642
+ 2. **Vapor-native** Built for signals, not VDOM
643
+ 3. **Composable** Plugins for everything
644
+ 4. **Type-safe** Full TypeScript support
645
+ 5. **Predictable** Sync by default, explicit async
646
+ 6. **Progressive** — Works in VDOM, Vapor, and mixed trees
491
647
 
492
648
  ## License
493
649