vapor-chamber 0.1.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 ADDED
@@ -0,0 +1,301 @@
1
+ # Vapor Chamber
2
+
3
+ A lightweight command bus designed for [Vue Vapor](https://github.com/vuejs/vue-vapor). ~1KB core.
4
+
5
+ ## What is Vue Vapor?
6
+
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.
8
+
9
+ **Vapor Chamber** embraces this philosophy: minimal abstraction, direct updates, signal-native reactivity.
10
+
11
+ ## Why a Command Bus?
12
+
13
+ Traditional event systems scatter logic across components. A command bus centralizes it:
14
+
15
+ ```
16
+ Event-driven (scattered) Command bus (centralized)
17
+ ───────────────────────── ─────────────────────────
18
+ Component A emits 'add' → dispatch('cart.add', product)
19
+ Component B listens... ↓
20
+ Component C also listens... Handler executes once
21
+ Who handles what? When? Plugins observe/modify
22
+ Result returned
23
+ ```
24
+
25
+ **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
30
+
31
+ ## Install
32
+
33
+ ```bash
34
+ npm install vapor-chamber
35
+ ```
36
+
37
+ ## Quick Start
38
+
39
+ ```typescript
40
+ import { createCommandBus, logger, validator } from 'vapor-chamber';
41
+
42
+ const bus = createCommandBus();
43
+
44
+ // Add plugins
45
+ bus.use(logger());
46
+ bus.use(validator({
47
+ 'cart.add': (cmd) => cmd.payload?.quantity > 0 ? null : 'Quantity required'
48
+ }));
49
+
50
+ // Register handler
51
+ bus.register('cart.add', (cmd) => {
52
+ cart.items.push({ ...cmd.target, quantity: cmd.payload.quantity });
53
+ return cart.items;
54
+ });
55
+
56
+ // Dispatch
57
+ const result = bus.dispatch('cart.add', product, { quantity: 2 });
58
+ if (result.ok) {
59
+ console.log('Added:', result.value);
60
+ } else {
61
+ console.error('Failed:', result.error);
62
+ }
63
+ ```
64
+
65
+ ## Core Concepts
66
+
67
+ ### Commands
68
+
69
+ A command has three parts:
70
+
71
+ ```typescript
72
+ bus.dispatch(
73
+ 'cart.add', // action - what to do
74
+ product, // target - what to act on
75
+ { quantity: 2 } // payload - additional data (optional)
76
+ );
77
+ ```
78
+
79
+ ### Handlers
80
+
81
+ One handler per action. Returns a value or throws:
82
+
83
+ ```typescript
84
+ bus.register('cart.add', (cmd) => {
85
+ // cmd.action = 'cart.add'
86
+ // cmd.target = product
87
+ // cmd.payload = { quantity: 2 }
88
+
89
+ cart.items.push(cmd.target);
90
+ return cart.items; // becomes result.value
91
+ });
92
+ ```
93
+
94
+ ### Results
95
+
96
+ Every dispatch returns a result:
97
+
98
+ ```typescript
99
+ type CommandResult = {
100
+ ok: boolean; // success or failure
101
+ value?: any; // handler return value (if ok)
102
+ error?: Error; // error thrown (if not ok)
103
+ };
104
+ ```
105
+
106
+ ### Plugins
107
+
108
+ Plugins wrap handlers. They can modify commands, short-circuit execution, observe results, or transform output:
109
+
110
+ ```typescript
111
+ const timingPlugin: Plugin = (cmd, next) => {
112
+ const start = Date.now();
113
+ const result = next(); // call next plugin or handler
114
+ console.log(`${cmd.action} took ${Date.now() - start}ms`);
115
+ return result;
116
+ };
117
+
118
+ bus.use(timingPlugin);
119
+ ```
120
+
121
+ Plugins execute in order: first added = outermost wrapper.
122
+
123
+ ## Built-in Plugins
124
+
125
+ | Plugin | Description |
126
+ |--------|-------------|
127
+ | `logger(options?)` | Log commands to console |
128
+ | `validator(rules)` | Validate commands before execution |
129
+ | `history(options?)` | Track command history for undo/redo |
130
+ | `debounce(actions, wait)` | Delay execution until activity stops |
131
+ | `throttle(actions, wait)` | Limit execution frequency |
132
+
133
+ ### logger
134
+
135
+ ```typescript
136
+ bus.use(logger({ collapsed: true, filter: (cmd) => cmd.action.startsWith('cart.') }));
137
+ ```
138
+
139
+ ### validator
140
+
141
+ ```typescript
142
+ bus.use(validator({
143
+ 'cart.add': (cmd) => {
144
+ if (!cmd.target?.id) return 'Product must have an ID';
145
+ return null; // null = valid
146
+ }
147
+ }));
148
+ ```
149
+
150
+ ### history
151
+
152
+ ```typescript
153
+ const historyPlugin = history({ maxSize: 100 });
154
+ bus.use(historyPlugin);
155
+
156
+ historyPlugin.undo();
157
+ historyPlugin.redo();
158
+ historyPlugin.getState(); // { past, future, canUndo, canRedo }
159
+ ```
160
+
161
+ ### debounce
162
+
163
+ ```typescript
164
+ bus.use(debounce(['search.query'], 300)); // wait 300ms after last call
165
+ ```
166
+
167
+ ### throttle
168
+
169
+ ```typescript
170
+ bus.use(throttle(['ui.scroll'], 100)); // max once per 100ms
171
+ ```
172
+
173
+ ## Async Command Bus
174
+
175
+ For async handlers (API calls, IndexedDB, etc.):
176
+
177
+ ```typescript
178
+ import { createAsyncCommandBus } from 'vapor-chamber';
179
+
180
+ const bus = createAsyncCommandBus();
181
+
182
+ bus.register('user.fetch', async (cmd) => {
183
+ const response = await fetch(`/api/users/${cmd.target.id}`);
184
+ return response.json();
185
+ });
186
+
187
+ const result = await bus.dispatch('user.fetch', { id: 123 });
188
+ ```
189
+
190
+ ## Vapor Composables
191
+
192
+ For Vue Vapor components:
193
+
194
+ ### useCommand
195
+
196
+ ```vue
197
+ <script setup>
198
+ import { useCommand } from 'vapor-chamber';
199
+
200
+ const { dispatch, loading, lastError } = useCommand();
201
+ </script>
202
+
203
+ <template>
204
+ <button @click="dispatch('save', doc)" :disabled="loading.value">Save</button>
205
+ <p v-if="lastError.value">{{ lastError.value.message }}</p>
206
+ </template>
207
+ ```
208
+
209
+ ### useCommandState
210
+
211
+ ```vue
212
+ <script setup>
213
+ import { useCommandState } from 'vapor-chamber';
214
+
215
+ const { state: cart } = useCommandState(
216
+ { items: [], total: 0 },
217
+ {
218
+ 'cart.add': (state, cmd) => ({
219
+ items: [...state.items, cmd.target],
220
+ total: state.total + cmd.target.price
221
+ })
222
+ }
223
+ );
224
+ </script>
225
+ ```
226
+
227
+ ### useCommandHistory
228
+
229
+ ```vue
230
+ <script setup>
231
+ import { useCommandHistory } from 'vapor-chamber';
232
+
233
+ const { canUndo, canRedo, undo, redo } = useCommandHistory({
234
+ filter: (cmd) => cmd.action.startsWith('editor.')
235
+ });
236
+ </script>
237
+ ```
238
+
239
+ ## Examples
240
+
241
+ See the [`examples/`](./examples) folder for complete, runnable examples:
242
+
243
+ | Example | Description |
244
+ |---------|-------------|
245
+ | [`shopping-cart.ts`](./examples/shopping-cart.ts) | Cart with validation, history, and undo/redo |
246
+ | [`form-validation.ts`](./examples/form-validation.ts) | Form validation with error handling |
247
+ | [`async-api.ts`](./examples/async-api.ts) | Async handlers with retry plugin |
248
+ | [`realtime-search.ts`](./examples/realtime-search.ts) | Debounced search queries |
249
+ | [`custom-plugins.ts`](./examples/custom-plugins.ts) | Analytics, auth guard, rate limiter plugins |
250
+ | [`vue-vapor-component.vue`](./examples/vue-vapor-component.vue) | Full Vue Vapor todo app |
251
+
252
+ Run TypeScript examples with:
253
+ ```bash
254
+ npx ts-node examples/shopping-cart.ts
255
+ ```
256
+
257
+ ## API Reference
258
+
259
+ ### Core
260
+
261
+ | Function | Description |
262
+ |----------|-------------|
263
+ | `createCommandBus()` | Create a synchronous command bus |
264
+ | `createAsyncCommandBus()` | Create an async command bus |
265
+
266
+ ### Command Bus Methods
267
+
268
+ | Method | Description |
269
+ |--------|-------------|
270
+ | `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) |
273
+ | `onAfter(hook)` | Run callback after every command |
274
+
275
+ ### Composables
276
+
277
+ | Composable | Description |
278
+ |------------|-------------|
279
+ | `useCommand()` | Dispatch with reactive loading/error state |
280
+ | `useCommandState(initial, handlers)` | State managed by commands |
281
+ | `useCommandHistory(options?)` | Reactive undo/redo |
282
+ | `getCommandBus()` | Get shared bus instance |
283
+ | `setCommandBus(bus)` | Set shared bus instance |
284
+
285
+ ## Documentation
286
+
287
+ See the [`docs/`](./docs) folder for detailed documentation:
288
+
289
+ - [Whitepaper](./docs/whitepaper.md) - Design philosophy and architecture
290
+
291
+ ## Design Goals
292
+
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
298
+
299
+ ## License
300
+
301
+ [GNU Lesser General Public License v2.1](https://www.gnu.org/licenses/old-licenses/lgpl-2.1.en.html)
@@ -0,0 +1,49 @@
1
+ /**
2
+ * vapor-chamber - Vue Vapor integration
3
+ *
4
+ * Note: Vue Vapor is still in development. This integration uses
5
+ * the expected signal-based API. Update imports when Vapor stabilizes.
6
+ */
7
+ import { type CommandBus, type Command, type CommandResult, type Handler, type Plugin } from './command-bus';
8
+ type Signal<T> = {
9
+ value: T;
10
+ };
11
+ export declare function getCommandBus(): CommandBus;
12
+ export declare function setCommandBus(bus: CommandBus): void;
13
+ /**
14
+ * useCommand - dispatch commands with reactive loading/error state
15
+ */
16
+ export declare function useCommand(): {
17
+ dispatch: (action: string, target: any, payload?: any) => CommandResult;
18
+ loading: Signal<boolean>;
19
+ lastError: Signal<Error | null>;
20
+ register: (action: string, handler: Handler) => () => void;
21
+ use: (plugin: Plugin) => () => void;
22
+ };
23
+ /**
24
+ * useCommandState - create reactive state that updates via commands
25
+ */
26
+ export declare function useCommandState<T>(initial: T, handlers: {
27
+ [action: string]: (state: T, cmd: Command) => T;
28
+ }): {
29
+ state: Signal<T>;
30
+ dispose: () => void;
31
+ };
32
+ /**
33
+ * useCommandHistory - undo/redo with reactive state
34
+ */
35
+ export declare function useCommandHistory(options?: {
36
+ maxSize?: number;
37
+ filter?: (cmd: Command) => boolean;
38
+ }): {
39
+ past: Signal<Command[]>;
40
+ future: Signal<Command[]>;
41
+ canUndo: Signal<boolean>;
42
+ canRedo: Signal<boolean>;
43
+ undo: () => Command | undefined;
44
+ redo: () => Command | undefined;
45
+ clear: () => void;
46
+ dispose: () => void;
47
+ };
48
+ export {};
49
+ //# sourceMappingURL=chamber.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"chamber.d.ts","sourceRoot":"","sources":["../src/chamber.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAoB,KAAK,UAAU,EAAE,KAAK,OAAO,EAAE,KAAK,aAAa,EAAE,KAAK,OAAO,EAAE,KAAK,MAAM,EAAE,MAAM,eAAe,CAAC;AAI/H,KAAK,MAAM,CAAC,CAAC,IAAI;IAAE,KAAK,EAAE,CAAC,CAAA;CAAE,CAAC;AAkC9B,wBAAgB,aAAa,IAAI,UAAU,CAK1C;AAED,wBAAgB,aAAa,CAAC,GAAG,EAAE,UAAU,GAAG,IAAI,CAEnD;AAED;;GAEG;AACH,wBAAgB,UAAU;uBAKE,MAAM,UAAU,GAAG,YAAY,GAAG,KAAG,aAAa;;;;;EAqB7E;AAED;;GAEG;AACH,wBAAgB,eAAe,CAAC,CAAC,EAC/B,OAAO,EAAE,CAAC,EACV,QAAQ,EAAE;IACR,CAAC,MAAM,EAAE,MAAM,GAAG,CAAC,KAAK,EAAE,CAAC,EAAE,GAAG,EAAE,OAAO,KAAK,CAAC,CAAC;CACjD;;;EAsBF;AAED;;GAEG;AACH,wBAAgB,iBAAiB,CAAC,OAAO,GAAE;IACzC,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,MAAM,CAAC,EAAE,CAAC,GAAG,EAAE,OAAO,KAAK,OAAO,CAAC;CAC/B;;;;;gBAqBa,OAAO,GAAG,SAAS;gBAYnB,OAAO,GAAG,SAAS;;;EA6BrC"}
@@ -0,0 +1,149 @@
1
+ /**
2
+ * vapor-chamber - Vue Vapor integration
3
+ *
4
+ * Note: Vue Vapor is still in development. This integration uses
5
+ * the expected signal-based API. Update imports when Vapor stabilizes.
6
+ */
7
+ import { createCommandBus } from './command-bus';
8
+ // Detect environment and get signal function
9
+ function getSignalFn() {
10
+ // Try Vapor's signal first (when available)
11
+ // @ts-ignore - Vapor not yet typed
12
+ if (typeof window !== 'undefined' && window.__VUE_VAPOR__?.signal) {
13
+ // @ts-ignore
14
+ return window.__VUE_VAPOR__.signal;
15
+ }
16
+ // Fallback: simple signal implementation for testing/non-Vapor
17
+ return (initial) => {
18
+ let _value = initial;
19
+ const listeners = [];
20
+ return {
21
+ get value() { return _value; },
22
+ set value(v) {
23
+ _value = v;
24
+ listeners.forEach(fn => fn(v));
25
+ }
26
+ };
27
+ };
28
+ }
29
+ const signal = getSignalFn();
30
+ /**
31
+ * Shared command bus instance
32
+ */
33
+ let sharedBus = null;
34
+ export function getCommandBus() {
35
+ if (!sharedBus) {
36
+ sharedBus = createCommandBus();
37
+ }
38
+ return sharedBus;
39
+ }
40
+ export function setCommandBus(bus) {
41
+ sharedBus = bus;
42
+ }
43
+ /**
44
+ * useCommand - dispatch commands with reactive loading/error state
45
+ */
46
+ export function useCommand() {
47
+ const bus = getCommandBus();
48
+ const loading = signal(false);
49
+ const lastError = signal(null);
50
+ function dispatch(action, target, payload) {
51
+ loading.value = true;
52
+ lastError.value = null;
53
+ const result = bus.dispatch(action, target, payload);
54
+ loading.value = false;
55
+ if (!result.ok) {
56
+ lastError.value = result.error ?? null;
57
+ }
58
+ return result;
59
+ }
60
+ return {
61
+ dispatch,
62
+ loading,
63
+ lastError,
64
+ register: bus.register,
65
+ use: bus.use,
66
+ };
67
+ }
68
+ /**
69
+ * useCommandState - create reactive state that updates via commands
70
+ */
71
+ export function useCommandState(initial, handlers) {
72
+ const bus = getCommandBus();
73
+ const state = signal(initial);
74
+ // Register handlers that update state
75
+ const unregisters = [];
76
+ for (const [action, handler] of Object.entries(handlers)) {
77
+ const unregister = bus.register(action, (cmd) => {
78
+ state.value = handler(state.value, cmd);
79
+ return state.value;
80
+ });
81
+ unregisters.push(unregister);
82
+ }
83
+ // Cleanup function
84
+ const dispose = () => {
85
+ unregisters.forEach(fn => fn());
86
+ };
87
+ return { state, dispose };
88
+ }
89
+ /**
90
+ * useCommandHistory - undo/redo with reactive state
91
+ */
92
+ export function useCommandHistory(options = {}) {
93
+ const { maxSize = 50, filter } = options;
94
+ const bus = getCommandBus();
95
+ const past = signal([]);
96
+ const future = signal([]);
97
+ const canUndo = signal(false);
98
+ const canRedo = signal(false);
99
+ // Track commands via afterHook
100
+ const unsubscribe = bus.onAfter((cmd, result) => {
101
+ if (result.ok && (!filter || filter(cmd))) {
102
+ const newPast = [...past.value, cmd];
103
+ if (newPast.length > maxSize)
104
+ newPast.shift();
105
+ past.value = newPast;
106
+ future.value = [];
107
+ canUndo.value = true;
108
+ canRedo.value = false;
109
+ }
110
+ });
111
+ function undo() {
112
+ const p = [...past.value];
113
+ const cmd = p.pop();
114
+ if (cmd) {
115
+ past.value = p;
116
+ future.value = [...future.value, cmd];
117
+ canUndo.value = p.length > 0;
118
+ canRedo.value = true;
119
+ }
120
+ return cmd;
121
+ }
122
+ function redo() {
123
+ const f = [...future.value];
124
+ const cmd = f.pop();
125
+ if (cmd) {
126
+ future.value = f;
127
+ past.value = [...past.value, cmd];
128
+ canUndo.value = true;
129
+ canRedo.value = f.length > 0;
130
+ }
131
+ return cmd;
132
+ }
133
+ function clear() {
134
+ past.value = [];
135
+ future.value = [];
136
+ canUndo.value = false;
137
+ canRedo.value = false;
138
+ }
139
+ return {
140
+ past,
141
+ future,
142
+ canUndo,
143
+ canRedo,
144
+ undo,
145
+ redo,
146
+ clear,
147
+ dispose: unsubscribe,
148
+ };
149
+ }
@@ -0,0 +1,38 @@
1
+ /**
2
+ * vapor-chamber - Command Bus for Vue Vapor
3
+ * ~1KB - Commands + Plugins + Hooks
4
+ */
5
+ export type Command = {
6
+ action: string;
7
+ target: any;
8
+ payload?: any;
9
+ };
10
+ export type CommandResult = {
11
+ ok: boolean;
12
+ value?: any;
13
+ error?: Error;
14
+ };
15
+ export type Handler = (cmd: Command) => any;
16
+ export type AsyncHandler = (cmd: Command) => any | Promise<any>;
17
+ export type Plugin = (cmd: Command, next: () => CommandResult) => CommandResult;
18
+ export type AsyncPlugin = (cmd: Command, next: () => CommandResult | Promise<CommandResult>) => CommandResult | Promise<CommandResult>;
19
+ export type Hook = (cmd: Command, result: CommandResult) => void;
20
+ export type AsyncHook = (cmd: Command, result: CommandResult) => void | Promise<void>;
21
+ export interface CommandBus {
22
+ dispatch: (action: string, target: any, payload?: any) => CommandResult;
23
+ register: (action: string, handler: Handler) => () => void;
24
+ use: (plugin: Plugin) => () => void;
25
+ onAfter: (hook: Hook) => () => void;
26
+ }
27
+ export interface AsyncCommandBus {
28
+ dispatch: (action: string, target: any, payload?: any) => Promise<CommandResult>;
29
+ register: (action: string, handler: AsyncHandler) => () => void;
30
+ use: (plugin: AsyncPlugin) => () => void;
31
+ onAfter: (hook: AsyncHook) => () => void;
32
+ }
33
+ export declare function createCommandBus(): CommandBus;
34
+ /**
35
+ * Async command bus - supports async handlers, plugins, and hooks
36
+ */
37
+ export declare function createAsyncCommandBus(): AsyncCommandBus;
38
+ //# sourceMappingURL=command-bus.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"command-bus.d.ts","sourceRoot":"","sources":["../src/command-bus.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,MAAM,MAAM,OAAO,GAAG;IACpB,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,GAAG,CAAC;IACZ,OAAO,CAAC,EAAE,GAAG,CAAC;CACf,CAAC;AAEF,MAAM,MAAM,aAAa,GAAG;IAC1B,EAAE,EAAE,OAAO,CAAC;IACZ,KAAK,CAAC,EAAE,GAAG,CAAC;IACZ,KAAK,CAAC,EAAE,KAAK,CAAC;CACf,CAAC;AAEF,MAAM,MAAM,OAAO,GAAG,CAAC,GAAG,EAAE,OAAO,KAAK,GAAG,CAAC;AAC5C,MAAM,MAAM,YAAY,GAAG,CAAC,GAAG,EAAE,OAAO,KAAK,GAAG,GAAG,OAAO,CAAC,GAAG,CAAC,CAAC;AAChE,MAAM,MAAM,MAAM,GAAG,CAAC,GAAG,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,aAAa,KAAK,aAAa,CAAC;AAChF,MAAM,MAAM,WAAW,GAAG,CAAC,GAAG,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,aAAa,GAAG,OAAO,CAAC,aAAa,CAAC,KAAK,aAAa,GAAG,OAAO,CAAC,aAAa,CAAC,CAAC;AACvI,MAAM,MAAM,IAAI,GAAG,CAAC,GAAG,EAAE,OAAO,EAAE,MAAM,EAAE,aAAa,KAAK,IAAI,CAAC;AACjE,MAAM,MAAM,SAAS,GAAG,CAAC,GAAG,EAAE,OAAO,EAAE,MAAM,EAAE,aAAa,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;AAEtF,MAAM,WAAW,UAAU;IACzB,QAAQ,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,GAAG,EAAE,OAAO,CAAC,EAAE,GAAG,KAAK,aAAa,CAAC;IACxE,QAAQ,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,KAAK,MAAM,IAAI,CAAC;IAC3D,GAAG,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,MAAM,IAAI,CAAC;IACpC,OAAO,EAAE,CAAC,IAAI,EAAE,IAAI,KAAK,MAAM,IAAI,CAAC;CACrC;AAED,MAAM,WAAW,eAAe;IAC9B,QAAQ,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,GAAG,EAAE,OAAO,CAAC,EAAE,GAAG,KAAK,OAAO,CAAC,aAAa,CAAC,CAAC;IACjF,QAAQ,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,YAAY,KAAK,MAAM,IAAI,CAAC;IAChE,GAAG,EAAE,CAAC,MAAM,EAAE,WAAW,KAAK,MAAM,IAAI,CAAC;IACzC,OAAO,EAAE,CAAC,IAAI,EAAE,SAAS,KAAK,MAAM,IAAI,CAAC;CAC1C;AAED,wBAAgB,gBAAgB,IAAI,UAAU,CAgE7C;AAED;;GAEG;AACH,wBAAgB,qBAAqB,IAAI,eAAe,CAgEvD"}
@@ -0,0 +1,119 @@
1
+ /**
2
+ * vapor-chamber - Command Bus for Vue Vapor
3
+ * ~1KB - Commands + Plugins + Hooks
4
+ */
5
+ export function createCommandBus() {
6
+ const handlers = new Map();
7
+ const plugins = [];
8
+ const afterHooks = [];
9
+ function dispatch(action, target, payload) {
10
+ const cmd = { action, target, payload };
11
+ const handler = handlers.get(action);
12
+ // Build execution chain: plugins wrap the handler
13
+ const execute = () => {
14
+ if (!handler) {
15
+ return { ok: false, error: new Error(`No handler: ${action}`) };
16
+ }
17
+ try {
18
+ const value = handler(cmd);
19
+ return { ok: true, value };
20
+ }
21
+ catch (e) {
22
+ return { ok: false, error: e };
23
+ }
24
+ };
25
+ // Apply plugins (right to left, so first plugin is outermost)
26
+ const chain = plugins.reduceRight((next, plugin) => () => plugin(cmd, next), execute);
27
+ const result = chain();
28
+ // Run after hooks
29
+ for (const hook of afterHooks) {
30
+ try {
31
+ hook(cmd, result);
32
+ }
33
+ catch (e) {
34
+ console.error('[vapor-chamber] Hook error:', e);
35
+ }
36
+ }
37
+ return result;
38
+ }
39
+ function register(action, handler) {
40
+ handlers.set(action, handler);
41
+ return () => handlers.delete(action);
42
+ }
43
+ function use(plugin) {
44
+ plugins.push(plugin);
45
+ return () => {
46
+ const i = plugins.indexOf(plugin);
47
+ if (i !== -1)
48
+ plugins.splice(i, 1);
49
+ };
50
+ }
51
+ function onAfter(hook) {
52
+ afterHooks.push(hook);
53
+ return () => {
54
+ const i = afterHooks.indexOf(hook);
55
+ if (i !== -1)
56
+ afterHooks.splice(i, 1);
57
+ };
58
+ }
59
+ return { dispatch, register, use, onAfter };
60
+ }
61
+ /**
62
+ * Async command bus - supports async handlers, plugins, and hooks
63
+ */
64
+ export function createAsyncCommandBus() {
65
+ const handlers = new Map();
66
+ const plugins = [];
67
+ const afterHooks = [];
68
+ async function dispatch(action, target, payload) {
69
+ const cmd = { action, target, payload };
70
+ const handler = handlers.get(action);
71
+ // Build execution chain: plugins wrap the handler
72
+ const execute = async () => {
73
+ if (!handler) {
74
+ return { ok: false, error: new Error(`No handler: ${action}`) };
75
+ }
76
+ try {
77
+ const value = await handler(cmd);
78
+ return { ok: true, value };
79
+ }
80
+ catch (e) {
81
+ return { ok: false, error: e };
82
+ }
83
+ };
84
+ // Apply plugins (right to left, so first plugin is outermost)
85
+ const chain = plugins.reduceRight((next, plugin) => () => plugin(cmd, next), execute);
86
+ const result = await chain();
87
+ // Run after hooks
88
+ for (const hook of afterHooks) {
89
+ try {
90
+ await hook(cmd, result);
91
+ }
92
+ catch (e) {
93
+ console.error('[vapor-chamber] Hook error:', e);
94
+ }
95
+ }
96
+ return result;
97
+ }
98
+ function register(action, handler) {
99
+ handlers.set(action, handler);
100
+ return () => handlers.delete(action);
101
+ }
102
+ function use(plugin) {
103
+ plugins.push(plugin);
104
+ return () => {
105
+ const i = plugins.indexOf(plugin);
106
+ if (i !== -1)
107
+ plugins.splice(i, 1);
108
+ };
109
+ }
110
+ function onAfter(hook) {
111
+ afterHooks.push(hook);
112
+ return () => {
113
+ const i = afterHooks.indexOf(hook);
114
+ if (i !== -1)
115
+ afterHooks.splice(i, 1);
116
+ };
117
+ }
118
+ return { dispatch, register, use, onAfter };
119
+ }
@@ -0,0 +1,9 @@
1
+ /**
2
+ * vapor-chamber - Lightweight command bus for Vue Vapor
3
+ *
4
+ * A ~1KB command bus with plugins, hooks, and Vapor-native reactivity.
5
+ */
6
+ export { createCommandBus, createAsyncCommandBus, type Command, type CommandResult, type CommandBus, type AsyncCommandBus, type Handler, type AsyncHandler, type Plugin, type AsyncPlugin, type Hook, type AsyncHook, } from './command-bus';
7
+ export { logger, validator, history, debounce, throttle, type HistoryState, } from './plugins';
8
+ export { getCommandBus, setCommandBus, useCommand, useCommandState, useCommandHistory, } from './chamber';
9
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAGH,OAAO,EACL,gBAAgB,EAChB,qBAAqB,EACrB,KAAK,OAAO,EACZ,KAAK,aAAa,EAClB,KAAK,UAAU,EACf,KAAK,eAAe,EACpB,KAAK,OAAO,EACZ,KAAK,YAAY,EACjB,KAAK,MAAM,EACX,KAAK,WAAW,EAChB,KAAK,IAAI,EACT,KAAK,SAAS,GACf,MAAM,eAAe,CAAC;AAGvB,OAAO,EACL,MAAM,EACN,SAAS,EACT,OAAO,EACP,QAAQ,EACR,QAAQ,EACR,KAAK,YAAY,GAClB,MAAM,WAAW,CAAC;AAGnB,OAAO,EACL,aAAa,EACb,aAAa,EACb,UAAU,EACV,eAAe,EACf,iBAAiB,GAClB,MAAM,WAAW,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,11 @@
1
+ /**
2
+ * vapor-chamber - Lightweight command bus for Vue Vapor
3
+ *
4
+ * A ~1KB command bus with plugins, hooks, and Vapor-native reactivity.
5
+ */
6
+ // Core
7
+ export { createCommandBus, createAsyncCommandBus, } from './command-bus';
8
+ // Plugins
9
+ export { logger, validator, history, debounce, throttle, } from './plugins';
10
+ // Vapor integration
11
+ export { getCommandBus, setCommandBus, useCommand, useCommandState, useCommandHistory, } from './chamber';