vapor-chamber 0.1.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
@@ -1,13 +1,58 @@
1
- # Vapor Chamber
1
+ <p align="center">
2
+ <img src="assets/vapor-chamber.png" alt="Vapor Chamber">
3
+ </p>
2
4
 
3
- A lightweight command bus designed for [Vue Vapor](https://github.com/vuejs/vue-vapor). ~1KB core.
5
+ <p align="center">
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
+ </p>
4
8
 
5
9
  ## What is Vue Vapor?
6
10
 
7
- 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.
8
14
 
9
15
  **Vapor Chamber** embraces this philosophy: minimal abstraction, direct updates, signal-native reactivity.
10
16
 
17
+ ## Migrating from Vue 3 emitters
18
+
19
+ If you're already using Vue 3's `emit` / `eventBus` pattern, here's the before and after:
20
+
21
+ ```
22
+ // Before — Vue 3 emitter
23
+ // cart.vue
24
+ emit('cart:add', product);
25
+
26
+ // App.vue
27
+ bus.on('cart:add', (product) => {
28
+ cart.items.push(product);
29
+ analytics.track('add');
30
+ validate(product); // where does this live?
31
+ });
32
+
33
+ // ProductList.vue — also listens?
34
+ bus.on('cart:add', updateBadge); // now two handlers, hard to trace
35
+ ```
36
+
37
+ ```
38
+ // After — Vapor Chamber
39
+ // Anywhere in the app
40
+ bus.dispatch('cart_add', product, { quantity: 1 });
41
+
42
+ // One place, once:
43
+ bus.register('cart_add', (cmd) => {
44
+ cart.items.push(cmd.target);
45
+ return cart.items;
46
+ });
47
+
48
+ // Cross-cutting concerns as plugins, not scattered listeners:
49
+ bus.use(logger());
50
+ bus.use(validator({ 'cart_add': (cmd) => cmd.target.id ? null : 'Missing ID' }));
51
+ bus.use(analyticsPlugin);
52
+ ```
53
+
54
+ **The key difference:** `emit` is fire-and-forget with many listeners. `dispatch` has one handler and a composable plugin pipeline — one place to look, debug, and test.
55
+
11
56
  ## Why a Command Bus?
12
57
 
13
58
  Traditional event systems scatter logic across components. A command bus centralizes it:
@@ -15,7 +60,7 @@ Traditional event systems scatter logic across components. A command bus central
15
60
  ```
16
61
  Event-driven (scattered) Command bus (centralized)
17
62
  ───────────────────────── ─────────────────────────
18
- Component A emits 'add' → dispatch('cart.add', product)
63
+ Component A emits 'add' → dispatch('cart_add', product)
19
64
  Component B listens... ↓
20
65
  Component C also listens... Handler executes once
21
66
  Who handles what? When? Plugins observe/modify
@@ -23,10 +68,10 @@ Who handles what? When? Plugins observe/modify
23
68
  ```
24
69
 
25
70
  **Benefits:**
26
- - **Semantic actions** - `cart.add` is clearer than `emit('add')`
27
- - **Single handler** - One place to look, debug, test
28
- - **Plugin pipeline** - Cross-cutting concerns (logging, validation, analytics) without cluttering handlers
29
- - **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
30
75
 
31
76
  ## Install
32
77
 
@@ -34,6 +79,8 @@ Who handles what? When? Plugins observe/modify
34
79
  npm install vapor-chamber
35
80
  ```
36
81
 
82
+ **Requirements:** Node.js ≥20.19.0 | Vue ≥3.5.0 (optional peer dep) | Vite 7/8 compatible
83
+
37
84
  ## Quick Start
38
85
 
39
86
  ```typescript
@@ -44,17 +91,17 @@ const bus = createCommandBus();
44
91
  // Add plugins
45
92
  bus.use(logger());
46
93
  bus.use(validator({
47
- 'cart.add': (cmd) => cmd.payload?.quantity > 0 ? null : 'Quantity required'
94
+ 'cart_add': (cmd) => cmd.payload?.quantity > 0 ? null : 'Quantity required'
48
95
  }));
49
96
 
50
97
  // Register handler
51
- bus.register('cart.add', (cmd) => {
98
+ bus.register('cart_add', (cmd) => {
52
99
  cart.items.push({ ...cmd.target, quantity: cmd.payload.quantity });
53
100
  return cart.items;
54
101
  });
55
102
 
56
103
  // Dispatch
57
- const result = bus.dispatch('cart.add', product, { quantity: 2 });
104
+ const result = bus.dispatch('cart_add', product, { quantity: 2 });
58
105
  if (result.ok) {
59
106
  console.log('Added:', result.value);
60
107
  } else {
@@ -62,6 +109,56 @@ if (result.ok) {
62
109
  }
63
110
  ```
64
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
+
65
162
  ## Core Concepts
66
163
 
67
164
  ### Commands
@@ -70,27 +167,48 @@ A command has three parts:
70
167
 
71
168
  ```typescript
72
169
  bus.dispatch(
73
- 'cart.add', // action - what to do
170
+ 'cart_add', // action - what to do
74
171
  product, // target - what to act on
75
172
  { quantity: 2 } // payload - additional data (optional)
76
173
  );
77
174
  ```
78
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
+
79
192
  ### Handlers
80
193
 
81
194
  One handler per action. Returns a value or throws:
82
195
 
83
196
  ```typescript
84
- bus.register('cart.add', (cmd) => {
85
- // cmd.action = 'cart.add'
86
- // cmd.target = product
87
- // cmd.payload = { quantity: 2 }
88
-
197
+ bus.register('cart_add', (cmd) => {
89
198
  cart.items.push(cmd.target);
90
199
  return cart.items; // becomes result.value
91
200
  });
92
201
  ```
93
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
+
94
212
  ### Results
95
213
 
96
214
  Every dispatch returns a result:
@@ -118,7 +236,45 @@ const timingPlugin: Plugin = (cmd, next) => {
118
236
  bus.use(timingPlugin);
119
237
  ```
120
238
 
121
- Plugins execute in order: first added = outermost wrapper.
239
+ Plugins execute by priority (highest first), then registration order for equal priorities:
240
+
241
+ ```typescript
242
+ bus.use(validatorPlugin, { priority: 10 }); // runs first
243
+ bus.use(analyticsPlugin, { priority: 1 }); // runs after validation
244
+ bus.use(loggerPlugin); // priority 0 (default, runs last)
245
+ ```
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.
122
278
 
123
279
  ## Built-in Plugins
124
280
 
@@ -129,18 +285,20 @@ Plugins execute in order: first added = outermost wrapper.
129
285
  | `history(options?)` | Track command history for undo/redo |
130
286
  | `debounce(actions, wait)` | Delay execution until activity stops |
131
287
  | `throttle(actions, wait)` | Limit execution frequency |
288
+ | `authGuard(options)` | Block protected commands when unauthenticated |
289
+ | `optimistic(handlers)` | Apply optimistic updates, rollback on failure |
132
290
 
133
291
  ### logger
134
292
 
135
293
  ```typescript
136
- bus.use(logger({ collapsed: true, filter: (cmd) => cmd.action.startsWith('cart.') }));
294
+ bus.use(logger({ collapsed: true, filter: (cmd) => cmd.action.startsWith('cart_') }));
137
295
  ```
138
296
 
139
297
  ### validator
140
298
 
141
299
  ```typescript
142
300
  bus.use(validator({
143
- 'cart.add': (cmd) => {
301
+ 'cart_add': (cmd) => {
144
302
  if (!cmd.target?.id) return 'Product must have an ID';
145
303
  return null; // null = valid
146
304
  }
@@ -158,16 +316,78 @@ historyPlugin.redo();
158
316
  historyPlugin.getState(); // { past, future, canUndo, canRedo }
159
317
  ```
160
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
+
161
329
  ### debounce
162
330
 
163
331
  ```typescript
164
- bus.use(debounce(['search.query'], 300)); // wait 300ms after last call
332
+ bus.use(debounce(['search_query'], 300)); // wait 300ms after last call
165
333
  ```
166
334
 
167
335
  ### throttle
168
336
 
169
337
  ```typescript
170
- 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
+ }));
362
+ ```
363
+
364
+ ## Batch Dispatch
365
+
366
+ Dispatch multiple commands as a unit. Stops on the first failure:
367
+
368
+ ```typescript
369
+ const result = bus.dispatchBatch([
370
+ { action: 'cart_add', target: cart, payload: item },
371
+ { action: 'totals_update', target: cart },
372
+ { action: 'analytics_track', target: session, payload: item },
373
+ ]);
374
+
375
+ if (result.ok) {
376
+ console.log('All succeeded:', result.results);
377
+ } else {
378
+ console.error('Stopped at failure:', result.error);
379
+ }
380
+ ```
381
+
382
+ ## Dead Letter Handling
383
+
384
+ Configure what happens when a command has no registered handler:
385
+
386
+ ```typescript
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
171
391
  ```
172
392
 
173
393
  ## Async Command Bus
@@ -179,22 +399,22 @@ import { createAsyncCommandBus } from 'vapor-chamber';
179
399
 
180
400
  const bus = createAsyncCommandBus();
181
401
 
182
- bus.register('user.fetch', async (cmd) => {
402
+ bus.register('user_fetch', async (cmd) => {
183
403
  const response = await fetch(`/api/users/${cmd.target.id}`);
184
404
  return response.json();
185
405
  });
186
406
 
187
- const result = await bus.dispatch('user.fetch', { id: 123 });
407
+ const result = await bus.dispatch('user_fetch', { id: 123 });
188
408
  ```
189
409
 
190
410
  ## Vapor Composables
191
411
 
192
- For Vue Vapor components:
193
-
194
412
  ### useCommand
195
413
 
414
+ Dispatch commands with reactive loading/error state:
415
+
196
416
  ```vue
197
- <script setup>
417
+ <script setup vapor>
198
418
  import { useCommand } from 'vapor-chamber';
199
419
 
200
420
  const { dispatch, loading, lastError } = useCommand();
@@ -206,16 +426,36 @@ const { dispatch, loading, lastError } = useCommand();
206
426
  </template>
207
427
  ```
208
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
+
209
447
  ### useCommandState
210
448
 
449
+ State managed by commands:
450
+
211
451
  ```vue
212
- <script setup>
452
+ <script setup vapor>
213
453
  import { useCommandState } from 'vapor-chamber';
214
454
 
215
455
  const { state: cart } = useCommandState(
216
456
  { items: [], total: 0 },
217
457
  {
218
- 'cart.add': (state, cmd) => ({
458
+ 'cart_add': (state, cmd) => ({
219
459
  items: [...state.items, cmd.target],
220
460
  total: state.total + cmd.target.price
221
461
  })
@@ -226,16 +466,84 @@ const { state: cart } = useCommandState(
226
466
 
227
467
  ### useCommandHistory
228
468
 
469
+ Reactive undo/redo:
470
+
229
471
  ```vue
230
- <script setup>
472
+ <script setup vapor>
231
473
  import { useCommandHistory } from 'vapor-chamber';
232
474
 
233
475
  const { canUndo, canRedo, undo, redo } = useCommandHistory({
234
- filter: (cmd) => cmd.action.startsWith('editor.')
476
+ filter: (cmd) => cmd.action.startsWith('editor_')
235
477
  });
236
478
  </script>
237
479
  ```
238
480
 
481
+ ### useCommandBus
482
+
483
+ Lightweight access to the shared bus — tree-shakeable:
484
+
485
+ ```typescript
486
+ import { useCommandBus } from 'vapor-chamber';
487
+
488
+ const bus = useCommandBus();
489
+ bus.dispatch('cart_add', product, { quantity: 1 });
490
+ ```
491
+
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.
493
+
494
+ ### configureSignal
495
+
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:
497
+
498
+ ```typescript
499
+ import { ref } from 'vue';
500
+ import { configureSignal } from 'vapor-chamber';
501
+
502
+ configureSignal(ref); // explicit — usually auto-detected
503
+ ```
504
+
505
+ ### Testing
506
+
507
+ `createTestBus()` records all dispatched commands without executing real handlers:
508
+
509
+ ```typescript
510
+ import { createTestBus, setCommandBus } from 'vapor-chamber';
511
+ import { describe, it, expect, beforeEach, afterEach } from 'vitest';
512
+ import { resetCommandBus } from 'vapor-chamber';
513
+
514
+ describe('CartButton', () => {
515
+ let bus: TestBus;
516
+
517
+ beforeEach(() => {
518
+ bus = createTestBus();
519
+ setCommandBus(bus);
520
+ });
521
+
522
+ afterEach(() => {
523
+ resetCommandBus();
524
+ });
525
+
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
+ });
531
+ });
532
+ ```
533
+
534
+ ### setupDevtools
535
+
536
+ Connect a bus to Vue DevTools. Adds a **Commands** timeline layer and a **Vapor Chamber** inspector panel. Requires `@vue/devtools-api` — silently no-ops if not installed:
537
+
538
+ ```typescript
539
+ import { createApp } from 'vue';
540
+ import { getCommandBus, setupDevtools } from 'vapor-chamber';
541
+
542
+ const app = createApp(App);
543
+ setupDevtools(getCommandBus(), app);
544
+ app.mount('#app');
545
+ ```
546
+
239
547
  ## Examples
240
548
 
241
549
  See the [`examples/`](./examples) folder for complete, runnable examples:
@@ -249,52 +557,93 @@ See the [`examples/`](./examples) folder for complete, runnable examples:
249
557
  | [`custom-plugins.ts`](./examples/custom-plugins.ts) | Analytics, auth guard, rate limiter plugins |
250
558
  | [`vue-vapor-component.vue`](./examples/vue-vapor-component.vue) | Full Vue Vapor todo app |
251
559
 
252
- Run TypeScript examples with:
253
- ```bash
254
- npx ts-node examples/shopping-cart.ts
255
- ```
256
-
257
560
  ## API Reference
258
561
 
259
562
  ### Core
260
563
 
261
564
  | Function | Description |
262
565
  |----------|-------------|
263
- | `createCommandBus()` | Create a synchronous command bus |
264
- | `createAsyncCommandBus()` | Create an async command bus |
566
+ | `createCommandBus(options?)` | Create a synchronous command bus |
567
+ | `createAsyncCommandBus(options?)` | Create an async command bus |
568
+ | `createTestBus(options?)` | Create a test bus that records dispatches |
569
+
570
+ **`CommandBusOptions`**
571
+
572
+ | Option | Type | Default | Description |
573
+ |--------|------|---------|-------------|
574
+ | `onMissing` | `'error' \| 'throw' \| 'ignore' \| fn` | `'error'` | Behavior when no handler is registered |
575
+ | `naming` | `{ pattern: RegExp, onViolation?: string }` | — | Enforce naming convention on actions |
265
576
 
266
577
  ### Command Bus Methods
267
578
 
268
579
  | Method | Description |
269
580
  |--------|-------------|
270
581
  | `dispatch(action, target, payload?)` | Execute a command |
271
- | `register(action, handler)` | Register a handler (returns unregister fn) |
272
- | `use(plugin)` | Add a plugin (returns unsubscribe fn) |
582
+ | `dispatchBatch(commands[])` | Execute multiple commands; stops on first failure |
583
+ | `register(action, handler, options?)` | Register a handler. Options: `{ undo?, throttle?, debounce? }` |
584
+ | `use(plugin, options?)` | Add a plugin. `options.priority` controls order |
273
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 |
274
590
 
275
591
  ### Composables
276
592
 
277
593
  | Composable | Description |
278
594
  |------------|-------------|
279
595
  | `useCommand()` | Dispatch with reactive loading/error state |
596
+ | `defineVaporCommand(action, handler, options?)` | Zero-overhead dispatch for hot paths |
280
597
  | `useCommandState(initial, handlers)` | State managed by commands |
281
598
  | `useCommandHistory(options?)` | Reactive undo/redo |
282
- | `getCommandBus()` | Get shared bus instance |
599
+ | `useCommandBus()` | Get shared bus instance |
600
+ | `getCommandBus()` | Get shared bus instance (non-composable) |
283
601
  | `setCommandBus(bus)` | Set shared bus instance |
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 |
608
+
609
+ ## Roadmap
610
+
611
+ | Feature | Status |
612
+ |---------|--------|
613
+ | DevTools integration | ✅ Done |
614
+ | DevTools production strip (0KB in prod) | ✅ Done |
615
+ | Command batching (`dispatchBatch`) | ✅ Done |
616
+ | Middleware priority/ordering | ✅ Done |
617
+ | Dead letter handling (`onMissing`) | ✅ Done |
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) |
628
+ | Persistence plugin (localStorage / IndexedDB) | Planned |
629
+ | SSR support | Planned (pending Vue Vapor stabilization) |
284
630
 
285
631
  ## Documentation
286
632
 
287
633
  See the [`docs/`](./docs) folder for detailed documentation:
288
634
 
289
- - [Whitepaper](./docs/whitepaper.md) - Design philosophy and architecture
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
290
638
 
291
639
  ## Design Goals
292
640
 
293
- 1. **Minimal** - ~1KB core, no dependencies
294
- 2. **Vapor-native** - Built for signals, not VDOM
295
- 3. **Composable** - Plugins for everything
296
- 4. **Type-safe** - Full TypeScript support
297
- 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
298
647
 
299
648
  ## License
300
649