+105
-90
ROADMAP.md
+105
-90
ROADMAP.md
···
20
| | ✓ | [Refactor](#evaluator--binder-hardening) |
21
| v0.5.1 | ✓ | [Error Handling & Diagnostics](#error-handling--diagnostics) (partial) |
22
| v0.6.0 | | [Error Handling & Diagnostics](#error-handling--diagnostics) |
23
-
| v0.7.0 | | [Bundle Size Optimization](#bundle-size-optimization) |
24
-
| v0.8.0 | | [CSP Compatibility](#csp-compatibility) |
25
-
| v0.9.0 | | [DOM Morphing & Streaming](#dom-morphing--streaming) |
26
-
| v0.10.0 | | [Scope Inheritance & State Management](#scope-inheritance--state-management) |
27
-
| v0.11.0 | | [Background Requests & Reactive Polling](#background-requests--reactive-polling) |
28
-
| v0.12.0 | | [Attribute Prefix Support](#attribute-prefix-support) |
29
-
| v0.13.0 | | [Persistence & Offline](#persistence--offline) (advanced features) |
30
-
| v0.14.0 | | [Inspector & Developer Tools](#inspector--developer-tools) |
31
| v1.0.0 | | [Stable Release](#stable-release) |
32
33
## Completed
···
120
- ✓ v0.5.1: Centralized error boundary system for directives and effects
121
- ✓ v0.5.1: Sandbox error wrapping with contextual hints (directive name, expression, element)
122
- ✓ v0.5.1: `$volt.report(error, context)` API for plugin and app-level reporting
123
-
- v0.6.0: Enhanced console error messages with directive context
124
-
- v0.6.0: Differentiated error levels: warn, error, fatal
125
-
- v0.6.0: Documentation: "Understanding VoltX Errors" guide
126
- v0.6.0: Add error handling examples to demo
127
-
- v0.14.0: Visual in-DOM error overlays for development mode
128
-
- v0.14.0: Runtime health monitor tracking failures
129
-
- v0.14.0: Configurable global error policy
130
131
### Persistence & Offline
132
···
135
**Deliverables:**
136
- ✓ Persistent signals (localStorage, sessionStorage, indexedDb)
137
- ✓ Storage plugin (`data-volt-persist`)
138
-
- v0.13.0: Storage modifiers on signals (`.local`, `.session`, `.ifmissing`)
139
-
- v0.13.0: Sync strategy API (merge, overwrite, patch) for conflict resolution
140
-
- v0.13.0: Cache invalidation strategies
141
-
- v0.13.0: Offline queue for deferred stream events and HTTP requests
142
-
- v0.13.0: Service Worker integration for offline-first apps
143
-
- v0.13.0: Background sync for deferred requests
144
-
- v0.13.0: Cross-tab synchronization via `BroadcastChannel`
145
146
### Bundle Size Optimization
147
148
**Goal:** Reduce bundle size to <15KB gzipped while maintaining full feature set.
149
**Outcome:** Lightweight runtime footprint with comprehensive declarative capabilities.
150
**Deliverables:**
151
-
- v0.7.0: Audit and tree-shake unused code paths
152
-
- v0.7.0: Optimize evaluator and binder implementations
153
-
- v0.7.0: Minimize plugin footprint, ensure lazy loading
154
-
- v0.7.0: Refactor expression compiler for smaller output
155
-
- v0.7.0: Compress constant strings and reduce runtime helpers
156
-
- v0.7.0: Optimize signal subscription management
157
-
- v0.7.0: Production mode stripping (remove dev-only error messages)
158
-
- v0.7.0: Aggressive minification pipeline tuning
159
-
- v0.7.0: Target: <15KB gzipped sustained
160
161
### CSP Compatibility
162
163
**Goal:** Make VoltX.js Content Security Policy compliant without 'unsafe-eval'.
164
**Outcome:** VoltX.js can run in strict CSP environments (no Function constructor).
165
**Deliverables:**
166
-
- v0.8.0: Research and design CSP-safe evaluator architecture
167
-
- v0.8.0: Evaluate trade-offs: AST interpreter vs limited expression subset
168
-
- v0.8.0: Implement CSP-safe expression evaluator (AST-based or restricted syntax)
169
-
- v0.8.0: Maintain expression feature parity where possible
170
-
- v0.8.0: Fallback mode detection for environments requiring CSP
171
-
- v0.8.0: Full test coverage for CSP mode
172
-
- v0.8.0: Documentation on CSP limitations and alternatives
173
-
- v0.8.0: Bundle split: standard build vs CSP build
174
175
### DOM Morphing & Streaming
176
177
**Goal:** Add intelligent DOM morphing and Server-Sent Events for real-time updates.
178
**Outcome:** Built-in morphing and SSE streaming for seamless server-driven UI updates.
179
**Deliverables:**
180
-
- v0.9.0: Integrate Idiomorph or implement lightweight morphing algorithm
181
-
- v0.9.0: `data-volt-morph` attribute for morphing-based swaps
182
-
- v0.9.0: Preserve focus, scroll, and input state during morphs
183
-
- v0.9.0: Server-Sent Events (SSE) integration
184
-
- v0.9.0: `data-volt-stream` attribute for SSE endpoints
185
-
- v0.9.0: Automatic reconnection with exponential backoff
186
-
- v0.9.0: Signal patching from backend SSE events
187
-
- v0.9.0: JSON Patch support for partial updates
188
-
- v0.9.0: `data-volt-ignore-morph` for selective exclusion
189
-
- v0.9.0: WebSocket as alternative to SSE
190
-
- v0.9.0: Unified streaming API across SSE/WebSocket
191
192
### Scope Inheritance & State Management
193
194
**Goal:** Improve data scoping with optional inheritance for ergonomic nested components.
195
**Outcome:** Flexible scoping patterns for complex component hierarchies.
196
**Deliverables:**
197
-
- v0.10.0: Optional scope inheritance via `data-volt-scope="inherit"`
198
-
- v0.10.0: Child scopes inherit parent signals with override capability
199
-
- v0.10.0: $parent accessor for explicit parent scope access
200
-
- v0.10.0: Scoped context providers for dependency injection
201
-
- v0.10.0: Enhanced $store with namespacing and modules
202
-
- v0.10.0: Cross-scope signal sharing patterns
203
204
### Background Requests & Reactive Polling
205
206
**Goal:** Enable declarative background data fetching and periodic updates.
207
**Outcome:** VoltX.js elements can fetch or refresh data automatically based on time, visibility, or reactive conditions.
208
**Deliverables:**
209
-
- v0.11.0: `data-volt-visible` for fetching when element enters viewport (IntersectionObserver)
210
-
- v0.11.0: `data-volt-poll` attribute for periodic background requests
211
-
- v0.11.0: Configurable intervals, delays, and signal-based triggers
212
-
- v0.11.0: Automatic cancellation when elements unmount
213
-
- v0.11.0: Conditional polling tied to reactive signals
214
-
- v0.11.0: Background task scheduler with priority management
215
216
### Attribute Prefix Support
217
218
**Goal:** Support multiple attribute prefix options for developer preference.
219
**Outcome:** VoltX.js supports `voltx-`, `vx-`, and `data-volt-` prefixes.
220
**Deliverables:**
221
-
- v0.12.0: Add support for `voltx-*` and `vx-*` attribute prefixes
222
-
- v0.12.0: Recommend `vx-*` as primary in documentation
223
-
- v0.12.0: Maintain backward compatibility with `data-volt-*`
224
-
- v0.12.0: Update demo to use recommended prefix
225
226
### Inspector & Developer Tools
227
228
**Goal:** Improve developer experience and runtime introspection.
229
**Outcome:** First-class developer ergonomics; VoltX.js is enjoyable to debug and extend.
230
**Deliverables:**
231
-
- v0.14.0: Visual in-DOM error overlays for development mode
232
-
- v0.14.0: Runtime health monitor tracking failures
233
-
- v0.14.0: Configurable global error policy (silent, overlay, throw)
234
-
- v0.14.0: Developer overlay for inspecting signals, subscriptions, and effects
235
-
- v0.14.0: Time-travel debugging for signal history
236
-
- v0.14.0: Signal dependency graph visualization
237
-
- v0.14.0: Performance profiling tools
238
-
- v0.14.0: Browser console integration (`window.$volt.inspect()`)
239
-
- v0.14.0: Dev logging toggle (`Volt.debug = true`)
240
-
- v0.14.0: Request/response debugging (HTTP actions, SSE streams)
241
-
- v0.14.0: Browser DevTools extension with full integration
242
243
### Stable Release
244
···
254
- Community contribution guide & governance doc
255
256
## Parking Lot
257
-
258
-
### IIFE Build Support
259
-
260
-
Provide an IIFE (Immediately Invoked Function Expression) build target for VoltX.js to support direct `<script>` tag usage without module systems.
261
-
262
-
**Deliverables:**
263
-
264
-
- IIFE build output (voltx.iife.js) alongside ESM build
265
-
- Global `Volt` namespace for browser environments
266
-
- CDN-friendly distribution (unpkg, jsdelivr)
267
-
- Update build pipeline to generate IIFE bundle
268
-
- Document usage: `<script src="voltx.iife.min.js"></script>`
269
-
- Ensure plugins work with IIFE build
270
-
- Add IIFE examples to documentation
271
272
### Evaluator & Binder Hardening
273
···
20
| | ✓ | [Refactor](#evaluator--binder-hardening) |
21
| v0.5.1 | ✓ | [Error Handling & Diagnostics](#error-handling--diagnostics) (partial) |
22
| v0.6.0 | | [Error Handling & Diagnostics](#error-handling--diagnostics) |
23
+
| v0.6.0 | | [Bundle Size Optimization](#bundle-size-optimization) |
24
+
| v0.6.0 | | [IIFE Build Support](#iife-build-support) |
25
+
| v0.7.0 | | [Testing & Benchmarking](#testing--benchmarking) |
26
+
| v0.7.0 | | [CSP Compatibility](#csp-compatibility) |
27
+
| v0.8.0 | | [DOM Morphing & Streaming](#dom-morphing--streaming) |
28
+
| v0.9.0 | | [Scope Inheritance & State Management](#scope-inheritance--state-management) |
29
+
| v0.10.0 | | [Background Requests & Reactive Polling](#background-requests--reactive-polling) |
30
+
| v0.11.0 | | [Attribute Prefix Support](#attribute-prefix-support) |
31
+
| v0.12.0 | | [Persistence & Offline](#persistence--offline) (advanced features) |
32
+
| v0.13.0 | | [Inspector & Developer Tools](#inspector--developer-tools) |
33
| v1.0.0 | | [Stable Release](#stable-release) |
34
35
## Completed
···
122
- ✓ v0.5.1: Centralized error boundary system for directives and effects
123
- ✓ v0.5.1: Sandbox error wrapping with contextual hints (directive name, expression, element)
124
- ✓ v0.5.1: `$volt.report(error, context)` API for plugin and app-level reporting
125
+
- ✓ v0.6.0: Enhanced console error messages with directive context
126
+
- ✓ v0.6.0: Differentiated error levels: warn, error, fatal
127
+
- ✓ v0.6.0: Documentation: "Understanding VoltX Errors" guide
128
- v0.6.0: Add error handling examples to demo
129
+
- v0.13.0: Visual in-DOM error overlays for development mode
130
+
- v0.13.0: Runtime health monitor tracking failures
131
+
- v0.13.0: Configurable global error policy
132
133
### Persistence & Offline
134
···
137
**Deliverables:**
138
- ✓ Persistent signals (localStorage, sessionStorage, indexedDb)
139
- ✓ Storage plugin (`data-volt-persist`)
140
+
- v0.12.0: Storage modifiers on signals (`.local`, `.session`, `.ifmissing`)
141
+
- v0.12.0: Sync strategy API (merge, overwrite, patch) for conflict resolution
142
+
- v0.12.0: Cache invalidation strategies
143
+
- v0.12.0: Offline queue for deferred stream events and HTTP requests
144
+
- v0.12.0: Service Worker integration for offline-first apps
145
+
- v0.12.0: Background sync for deferred requests
146
+
- v0.12.0: Cross-tab synchronization via `BroadcastChannel`
147
148
### Bundle Size Optimization
149
150
**Goal:** Reduce bundle size to <15KB gzipped while maintaining full feature set.
151
**Outcome:** Lightweight runtime footprint with comprehensive declarative capabilities.
152
**Deliverables:**
153
+
- v0.6.0: Audit and tree-shake unused code paths
154
+
- v0.6.0: Optimize evaluator and binder implementations
155
+
- v0.6.0: Minimize plugin footprint, ensure lazy loading
156
+
- v0.6.0: Refactor expression compiler for smaller output
157
+
- v0.6.0: Compress constant strings and reduce runtime helpers
158
+
- v0.6.0: Optimize signal subscription management
159
+
- v0.6.0: Production mode stripping (remove dev-only error messages)
160
+
- v0.6.0: Aggressive minification pipeline tuning
161
+
- v0.6.0: Target: <15KB gzipped sustained
162
+
163
+
### IIFE Build Support
164
+
165
+
**Goal:** Provide an IIFE build target for VoltX.js to support direct `<script>` tag usage without module systems.
166
+
**Outcome:** VoltX.js can be used via CDN without build tools or module bundlers.
167
+
**Deliverables:**
168
+
- v0.6.0: IIFE build output (voltx.iife.js) alongside ESM build
169
+
- v0.6.0: Global `Volt` namespace for browser environments
170
+
- v0.6.0: CDN-friendly distribution (unpkg, jsdelivr)
171
+
- v0.6.0: Update build pipeline to generate IIFE bundle
172
+
- v0.6.0: Document usage: `<script src="voltx.iife.min.js"></script>`
173
+
- v0.6.0: Ensure plugins work with IIFE build
174
+
- v0.6.0: Add IIFE examples to documentation
175
+
176
+
### Testing & Benchmarking
177
+
178
+
**Goal:** Establish comprehensive testing infrastructure and performance benchmarking.
179
+
**Outcome:** VoltX.js has rigorous end-to-end testing and quantifiable performance metrics against competing frameworks.
180
+
**Deliverables:**
181
+
- v0.7.0: Playwright-based integration test suite for real browser testing
182
+
- v0.7.0: End-to-end tests for all core directives and plugins
183
+
- v0.7.0: Cross-browser compatibility tests (Chrome, Firefox, Safari)
184
+
- v0.7.0: Memory usage and leak detection benchmarks
185
+
- v0.7.0: Bundle size tracking and regression detection
186
+
- v0.7.0: Reactivity performance benchmarks (signal updates, computed chains, effect execution)
187
+
- v0.7.0: DOM update performance benchmarks
188
+
- v0.7.0: CI integration for automated benchmark runs and regression alerts
189
190
### CSP Compatibility
191
192
**Goal:** Make VoltX.js Content Security Policy compliant without 'unsafe-eval'.
193
**Outcome:** VoltX.js can run in strict CSP environments (no Function constructor).
194
**Deliverables:**
195
+
- v0.7.0: Research and design CSP-safe evaluator architecture
196
+
- v0.7.0: Evaluate trade-offs: AST interpreter vs limited expression subset
197
+
- v0.7.0: Implement CSP-safe expression evaluator (AST-based or restricted syntax)
198
+
- v0.7.0: Maintain expression feature parity where possible
199
+
- v0.7.0: Fallback mode detection for environments requiring CSP
200
+
- v0.7.0: Full test coverage for CSP mode
201
+
- v0.7.0: Documentation on CSP limitations and alternatives
202
+
- v0.7.0: Bundle split: standard build vs CSP build
203
204
### DOM Morphing & Streaming
205
206
**Goal:** Add intelligent DOM morphing and Server-Sent Events for real-time updates.
207
**Outcome:** Built-in morphing and SSE streaming for seamless server-driven UI updates.
208
**Deliverables:**
209
+
- v0.8.0: Integrate Idiomorph or implement lightweight morphing algorithm
210
+
- v0.8.0: `data-volt-morph` attribute for morphing-based swaps
211
+
- v0.8.0: Preserve focus, scroll, and input state during morphs
212
+
- v0.8.0: Server-Sent Events (SSE) integration
213
+
- v0.8.0: `data-volt-stream` attribute for SSE endpoints
214
+
- v0.8.0: Automatic reconnection with exponential backoff
215
+
- v0.8.0: Signal patching from backend SSE events
216
+
- v0.8.0: JSON Patch support for partial updates
217
+
- v0.8.0: `data-volt-ignore-morph` for selective exclusion
218
+
- v0.8.0: WebSocket as alternative to SSE
219
+
- v0.8.0: Unified streaming API across SSE/WebSocket
220
221
### Scope Inheritance & State Management
222
223
**Goal:** Improve data scoping with optional inheritance for ergonomic nested components.
224
**Outcome:** Flexible scoping patterns for complex component hierarchies.
225
**Deliverables:**
226
+
- v0.9.0: Optional scope inheritance via `data-volt-scope="inherit"`
227
+
- v0.9.0: Child scopes inherit parent signals with override capability
228
+
- v0.9.0: $parent accessor for explicit parent scope access
229
+
- v0.9.0: Scoped context providers for dependency injection
230
+
- v0.9.0: Enhanced $store with namespacing and modules
231
+
- v0.9.0: Cross-scope signal sharing patterns
232
233
### Background Requests & Reactive Polling
234
235
**Goal:** Enable declarative background data fetching and periodic updates.
236
**Outcome:** VoltX.js elements can fetch or refresh data automatically based on time, visibility, or reactive conditions.
237
**Deliverables:**
238
+
- v0.10.0: `data-volt-visible` for fetching when element enters viewport (IntersectionObserver)
239
+
- v0.10.0: `data-volt-poll` attribute for periodic background requests
240
+
- v0.10.0: Configurable intervals, delays, and signal-based triggers
241
+
- v0.10.0: Automatic cancellation when elements unmount
242
+
- v0.10.0: Conditional polling tied to reactive signals
243
+
- v0.10.0: Background task scheduler with priority management
244
245
### Attribute Prefix Support
246
247
**Goal:** Support multiple attribute prefix options for developer preference.
248
**Outcome:** VoltX.js supports `voltx-`, `vx-`, and `data-volt-` prefixes.
249
**Deliverables:**
250
+
- v0.11.0: Add support for `voltx-*` and `vx-*` attribute prefixes
251
+
- v0.11.0: Recommend `vx-*` as primary in documentation
252
+
- v0.11.0: Maintain backward compatibility with `data-volt-*`
253
+
- v0.11.0: Update demo to use recommended prefix
254
255
### Inspector & Developer Tools
256
257
**Goal:** Improve developer experience and runtime introspection.
258
**Outcome:** First-class developer ergonomics; VoltX.js is enjoyable to debug and extend.
259
**Deliverables:**
260
+
- v0.13.0: Visual in-DOM error overlays for development mode
261
+
- v0.13.0: Runtime health monitor tracking failures
262
+
- v0.13.0: Configurable global error policy (silent, overlay, throw)
263
+
- v0.13.0: Developer overlay for inspecting signals, subscriptions, and effects
264
+
- v0.13.0: Time-travel debugging for signal history
265
+
- v0.13.0: Signal dependency graph visualization
266
+
- v0.13.0: Performance profiling tools
267
+
- v0.13.0: Browser console integration (`window.$volt.inspect()`)
268
+
- v0.13.0: Dev logging toggle (`Volt.debug = true`)
269
+
- v0.13.0: Request/response debugging (HTTP actions, SSE streams)
270
+
- v0.13.0: Browser DevTools extension with full integration
271
272
### Stable Release
273
···
283
- Community contribution guide & governance doc
284
285
## Parking Lot
286
287
### Evaluator & Binder Hardening
288
+1
-45
docs/cli.md
+1
-45
docs/cli.md
···
9
The CLI is available as `create-voltx` on npm:
10
11
```bash
12
-
# Use with pnpm (recommended)
13
pnpm create voltx my-app
14
15
# Use with npm
···
242
- Event handlers
243
- Counter example
244
245
-
Best for: Learning VoltX.js basics, simple interactive pages.
246
-
247
### With Router
248
249
A multi-page application featuring:
···
252
- Multiple routes (home, about, contact, 404)
253
- Navigation with `data-volt-navigate`
254
- Route matching with `data-volt-url`
255
-
256
-
Best for: Multi-page applications, documentation sites, dashboards.
257
258
### With Plugins
259
···
274
- VoltX.js CSS utilities
275
- Semantic HTML
276
- No JavaScript required
277
-
278
-
Best for: Static sites, progressively enhanced pages, CSS-only projects.
279
280
## Configuration
281
···
287
import { defineConfig } from 'vite';
288
289
export default defineConfig({
290
-
// Custom Vite configuration
291
server: {
292
port: 3000,
293
},
···
298
```
299
300
See the [Vite documentation](https://vitejs.dev/config/) for all available options.
301
-
302
-
## Troubleshooting
303
-
304
-
### Dev Server Won't Start
305
-
306
-
Ensure you're in a VoltX.js project directory with an `index.html` file:
307
-
308
-
```bash
309
-
ls index.html
310
-
```
311
-
312
-
If `index.html` is missing, you may not be in a VoltX.js project.
313
-
314
-
### Download Fails
315
-
316
-
Check your internet connection and try again. The CLI downloads assets from:
317
-
318
-
```text
319
-
https://cdn.jsdelivr.net/npm/voltx.js@{version}/dist/
320
-
```
321
-
322
-
If jsDelivr is blocked, manually download from the [npm package](https://www.npmjs.com/package/voltx.js).
323
-
324
-
### Build Fails
325
-
326
-
Ensure all dependencies are installed:
327
-
328
-
```bash
329
-
pnpm install
330
-
```
331
-
332
-
Check for syntax errors in your HTML, CSS, or JavaScript files.
333
-
334
-
## Next Steps
335
-
336
-
- Read the [Installation Guide](./installation) for framework setup
337
-
- Explore [Usage Patterns](./usage/state) for state management
···
9
The CLI is available as `create-voltx` on npm:
10
11
```bash
12
+
# Use with pnpm
13
pnpm create voltx my-app
14
15
# Use with npm
···
242
- Event handlers
243
- Counter example
244
245
### With Router
246
247
A multi-page application featuring:
···
250
- Multiple routes (home, about, contact, 404)
251
- Navigation with `data-volt-navigate`
252
- Route matching with `data-volt-url`
253
254
### With Plugins
255
···
270
- VoltX.js CSS utilities
271
- Semantic HTML
272
- No JavaScript required
273
274
## Configuration
275
···
281
import { defineConfig } from 'vite';
282
283
export default defineConfig({
284
server: {
285
port: 3000,
286
},
···
291
```
292
293
See the [Vite documentation](https://vitejs.dev/config/) for all available options.
+201
docs/usage/error-handling.md
+201
docs/usage/error-handling.md
···
···
1
+
# Error Handling
2
+
3
+
⚠️ Named error classes and enhanced error handling are unreleased as of writing. This documentation describes features planned for v0.6.0.
4
+
5
+
VoltX categorizes all errors by source and severity, wrapping them in named error classes with rich debugging context. This system enables precise error identification, flexible handling strategies, and seamless integration with logging services.
6
+
7
+
## Error Types
8
+
9
+
Each error source maps to a specific error class:
10
+
11
+
| Identifier | Source | Cause |
12
+
| ---------------- | ----------- | ----------------------------------------------------------------------------------------------------------------------------------- |
13
+
| `EvaluatorError` | `evaluator` | Expression evaluation fails in directives (`data-volt-text`, `data-volt-if`, etc.) or computed values |
14
+
| `BindingError` | `binding` | Directive setup or execution fails (`data-volt-model` with missing signal, invalid `data-volt-for` syntax, missing parent elements) |
15
+
| `EffectError` | `effect` | Effect callbacks, computed signals, or async effects fail during execution or cleanup |
16
+
| `HttpError` | `http` | HTTP directives encounter network errors, invalid swap strategies, missing target elements, or parsing failures |
17
+
| `PluginError` | `plugin` | Custom plugin handlers fail during initialization or execution |
18
+
| `LifecycleError` | `lifecycle` | Lifecycle hooks (`beforeMount`, `afterMount`, `onMount`, etc.) fail during execution |
19
+
| `ChargeError` | `charge` | `charge()` encounters invalid `data-volt-state` JSON, malformed configuration, or initialization errors |
20
+
| `UserError` | `user` | User code explicitly reports errors via `report()` |
21
+
22
+
All error classes extend `VoltError` and set their `name` property accordingly (e.g., `error.name === "HttpError"`).
23
+
24
+
## Severity Levels
25
+
26
+
Errors have three severity levels that control console output and execution flow:
27
+
28
+
**warn** — Non-critical issues logged via `console.warn`. Execution continues. Use for deprecations, missing optional features, or recoverable configuration issues.
29
+
30
+
**error** (default) — Recoverable errors logged via `console.error`. Execution continues but the specific operation fails. Most runtime errors use this level.
31
+
32
+
**fatal** — Unrecoverable errors logged via `console.error` and then thrown, halting execution. Reserve for critical initialization failures or corrupted state.
33
+
34
+
## Error Context
35
+
36
+
Every VoltX error includes contextual metadata for debugging:
37
+
38
+
```ts
39
+
interface VoltError {
40
+
name: string; // Error class name
41
+
source: ErrorSource; // Error category
42
+
level: ErrorLevel; // Severity level
43
+
directive?: string; // Failed directive (e.g., "data-volt-text")
44
+
expression?: string; // Failed expression
45
+
element?: HTMLElement; // DOM element where error occurred
46
+
cause: Error; // Original wrapped error
47
+
timestamp: number; // Unix timestamp (ms)
48
+
context: ErrorContext; // Full context including custom properties
49
+
50
+
// HTTP errors only
51
+
httpMethod?: string;
52
+
httpUrl?: string;
53
+
httpStatus?: number;
54
+
55
+
// Plugin errors only
56
+
pluginName?: string;
57
+
58
+
// Lifecycle errors only
59
+
hookName?: string;
60
+
}
61
+
```
62
+
63
+
## Handling Errors
64
+
65
+
### Registration
66
+
67
+
Register global error handlers with `onError()`. Handlers execute in registration order and receive all errors:
68
+
69
+
```ts
70
+
import { onError } from "voltx.js";
71
+
72
+
const cleanup = onError((error) => {
73
+
analytics.track("error", error.toJSON());
74
+
75
+
if (error instanceof HttpError) {
76
+
showToast(`Request failed: ${error.cause.message}`);
77
+
}
78
+
});
79
+
80
+
// Cleanup when done
81
+
cleanup();
82
+
```
83
+
84
+
### Propagation Control
85
+
86
+
Call `error.stopPropagation()` to prevent subsequent handlers from running:
87
+
88
+
```ts
89
+
onError((error) => {
90
+
if (error.source === "http") {
91
+
handleHttpError(error);
92
+
error.stopPropagation();
93
+
}
94
+
});
95
+
96
+
// This handler won't run for HTTP errors
97
+
onError((error) => logToConsole(error));
98
+
```
99
+
100
+
### Cleanup
101
+
102
+
Remove handlers individually via their cleanup function or clear all handlers with `clearErrorHandlers()`.
103
+
104
+
## Console Fallback
105
+
106
+
When no handlers are registered, errors log to console based on severity (`console.warn` for warn, `console.error` for error/fatal) with formatted context:
107
+
108
+
```text
109
+
[ERROR] [evaluator] Cannot read property 'foo' of undefined | Directive: data-volt-text | Expression: user.foo | Element: <div#app>
110
+
Caused by: TypeError: Cannot read property 'foo' of undefined
111
+
Element: <div id="app">...</div>
112
+
```
113
+
114
+
Fatal errors throw after logging.
115
+
116
+
## Reporting Errors
117
+
118
+
Report errors from user code using `report(error, context)`:
119
+
120
+
```ts
121
+
import { report } from "voltx.js";
122
+
123
+
try {
124
+
processForm(formElement);
125
+
} catch (error) {
126
+
report(error as Error, {
127
+
source: "user",
128
+
level: "warn",
129
+
element: formElement as HTMLElement,
130
+
formId: formElement.id,
131
+
});
132
+
}
133
+
```
134
+
135
+
The `context` object accepts any custom properties beyond the standard fields.
136
+
137
+
## Serialization
138
+
139
+
All VoltX errors implement `toJSON()` for serialization to logging services or error tracking systems.
140
+
141
+
### Schema
142
+
143
+
```ts
144
+
interface SerializedVoltError {
145
+
/** Error class name (e.g., "EvaluatorError", "HttpError") */
146
+
name: string;
147
+
/** Full formatted error message with context */
148
+
message: string;
149
+
/** Error source category */
150
+
source:
151
+
| "evaluator"
152
+
| "binding"
153
+
| "effect"
154
+
| "http"
155
+
| "plugin"
156
+
| "lifecycle"
157
+
| "charge"
158
+
| "user";
159
+
160
+
/** Severity level */
161
+
level: "warn" | "error" | "fatal";
162
+
/** Directive name (e.g., "data-volt-text") */
163
+
directive?: string;
164
+
/** Expression that failed */
165
+
expression?: string;
166
+
/** Unix timestamp in milliseconds */
167
+
timestamp: number;
168
+
/** Full error context including custom properties */
169
+
context: {
170
+
source: string;
171
+
level?: string;
172
+
element?: HTMLElement;
173
+
directive?: string;
174
+
expression?: string;
175
+
pluginName?: string;
176
+
httpMethod?: string;
177
+
httpUrl?: string;
178
+
httpStatus?: number;
179
+
hookName?: string;
180
+
[key: string]: unknown;
181
+
};
182
+
/** Original error that was wrapped */
183
+
cause: { name: string; message: string; stack?: string; };
184
+
/** VoltX error stack trace */
185
+
stack?: string;
186
+
}
187
+
```
188
+
189
+
### Usage
190
+
191
+
```ts
192
+
import { onError } from "voltx.js";
193
+
194
+
onError((error) => {
195
+
fetch("/api/errors", {
196
+
method: "POST",
197
+
headers: { "Content-Type": "application/json" },
198
+
body: JSON.stringify(error.toJSON()),
199
+
});
200
+
});
201
+
```
+9
-1
lib/src/core/charge.ts
+9
-1
lib/src/core/charge.ts
···
88
if (typeof stateData !== "object" || isNil(stateData) || Array.isArray(stateData)) {
89
report(new Error(`data-volt-state must be a JSON object, got ${typeof stateData}`), {
90
source: "charge",
91
element: el as HTMLElement,
92
directive: "data-volt-state",
93
expression: stateAttr,
···
100
} catch (error) {
101
report(error as Error, {
102
source: "charge",
103
element: el as HTMLElement,
104
directive: "data-volt-state",
105
expression: stateAttr,
···
143
if (typeof data !== "object" || isNil(data) || Array.isArray(data)) {
144
report(new Error(`data-volt-store script must contain a JSON object, got: ${typeof data}`), {
145
source: "charge",
146
element: script as HTMLElement,
147
directive: "data-volt-store",
148
});
···
151
152
registerStore(data);
153
} catch (error) {
154
-
report(error as Error, { source: "charge", element: script as HTMLElement, directive: "data-volt-store" });
155
}
156
}
157
}
···
88
if (typeof stateData !== "object" || isNil(stateData) || Array.isArray(stateData)) {
89
report(new Error(`data-volt-state must be a JSON object, got ${typeof stateData}`), {
90
source: "charge",
91
+
level: "fatal",
92
element: el as HTMLElement,
93
directive: "data-volt-state",
94
expression: stateAttr,
···
101
} catch (error) {
102
report(error as Error, {
103
source: "charge",
104
+
level: "fatal",
105
element: el as HTMLElement,
106
directive: "data-volt-state",
107
expression: stateAttr,
···
145
if (typeof data !== "object" || isNil(data) || Array.isArray(data)) {
146
report(new Error(`data-volt-store script must contain a JSON object, got: ${typeof data}`), {
147
source: "charge",
148
+
level: "fatal",
149
element: script as HTMLElement,
150
directive: "data-volt-store",
151
});
···
154
155
registerStore(data);
156
} catch (error) {
157
+
report(error as Error, {
158
+
source: "charge",
159
+
level: "fatal",
160
+
element: script as HTMLElement,
161
+
directive: "data-volt-store",
162
+
});
163
}
164
}
165
}
+183
-32
lib/src/core/error.ts
+183
-32
lib/src/core/error.ts
···
6
*
7
* @module core/error
8
*/
9
-
import type { ErrorContext, ErrorHandler, ErrorSource } from "$types/volt";
10
11
/**
12
-
* Enhanced error class with VoltX context
13
*
14
-
* Wraps original errors with rich debugging information including
15
-
* source, element, directive, and expression details.
16
*/
17
export class VoltError extends Error {
18
/** Error source category */
19
public readonly source: ErrorSource;
20
/** DOM element where error occurred */
21
public readonly element?: HTMLElement;
22
/** Directive name */
···
38
this.name = "VoltError";
39
this.cause = cause;
40
this.source = context.source;
41
this.element = context.element;
42
this.directive = context.directive;
43
this.expression = context.expression;
···
47
// V8-specific feature
48
// See: https://github.com/microsoft/TypeScript/issues/3926
49
if ((Error as any).captureStackTrace) {
50
-
(Error as any).captureStackTrace(this, VoltError);
51
}
52
}
53
···
67
68
private static buildMessage(cause: Error, context: ErrorContext): string {
69
const parts: string[] = [];
70
71
-
parts.push(`[${context.source}] ${cause.message}`);
72
73
if (context.directive) {
74
parts.push(`Directive: ${context.directive}`);
···
112
name: this.name,
113
message: this.message,
114
source: this.source,
115
directive: this.directive,
116
expression: this.expression,
117
timestamp: this.timestamp,
···
123
}
124
125
/**
126
* Global error handler registry
127
*/
128
let errorHandlers: ErrorHandler[] = [];
···
131
* Register an error handler
132
*
133
* Multiple handlers can be registered and will be called in registration order.
134
-
* Handlers can call `error.stopPropagation()` to prevent subsequent handlers
135
-
* from being called.
136
*
137
* @param handler - Error handler function
138
* @returns Cleanup function to unregister the handler
···
183
* If no error handlers are registered, errors are logged to console as fallback.
184
* Once handlers are registered, console logging is disabled.
185
*
186
* @param error - Error to report (can be Error, unknown, or string)
187
* @param context - Error context with source and additional details
188
*
189
* @example
190
* ```ts
191
-
* // Internal usage (by VoltX)
192
-
* try {
193
-
* evaluate(expression, scope);
194
-
* } catch (err) {
195
-
* report(err, {
196
-
* source: ErrorSource.Evaluator,
197
-
* element: ctx.element,
198
-
* directive: 'data-volt-text',
199
-
* expression: expression
200
-
* });
201
-
* }
202
*
203
-
* // External usage (by plugins/apps)
204
-
* try {
205
-
* myCustomLogic();
206
-
* } catch (err) {
207
-
* report(err, {
208
-
* source: ErrorSource.User,
209
-
* customContext: 'My feature failed'
210
-
* });
211
-
* }
212
* ```
213
*/
214
export function report(error: unknown, context: ErrorContext): void {
215
const errorObj = error instanceof Error ? error : new Error(String(error));
216
-
const voltError = new VoltError(errorObj, context);
217
218
if (errorHandlers.length === 0) {
219
-
console.error(voltError.message);
220
-
console.error("Caused by:", voltError.cause);
221
if (voltError.element) {
222
-
console.error("Element:", voltError.element);
223
}
224
return;
225
}
···
232
}
233
} catch (handlerError) {
234
console.error("Error in error handler:", handlerError);
235
}
236
}
237
}
···
6
*
7
* @module core/error
8
*/
9
+
import type { ErrorContext, ErrorHandler, ErrorLevel, ErrorSource } from "$types/volt";
10
11
/**
12
+
* Base error class with VoltX context
13
*
14
+
* Wraps original errors with rich debugging information including ource, element, directive, and expression details.
15
*/
16
export class VoltError extends Error {
17
/** Error source category */
18
public readonly source: ErrorSource;
19
+
/** Error severity level */
20
+
public readonly level: ErrorLevel;
21
/** DOM element where error occurred */
22
public readonly element?: HTMLElement;
23
/** Directive name */
···
39
this.name = "VoltError";
40
this.cause = cause;
41
this.source = context.source;
42
+
this.level = context.level ?? "error";
43
this.element = context.element;
44
this.directive = context.directive;
45
this.expression = context.expression;
···
49
// V8-specific feature
50
// See: https://github.com/microsoft/TypeScript/issues/3926
51
if ((Error as any).captureStackTrace) {
52
+
(Error as any).captureStackTrace(this, this.constructor);
53
}
54
}
55
···
69
70
private static buildMessage(cause: Error, context: ErrorContext): string {
71
const parts: string[] = [];
72
+
const level = context.level ?? "error";
73
74
+
parts.push(`[${level.toUpperCase()}] [${context.source}] ${cause.message}`);
75
76
if (context.directive) {
77
parts.push(`Directive: ${context.directive}`);
···
115
name: this.name,
116
message: this.message,
117
source: this.source,
118
+
level: this.level,
119
directive: this.directive,
120
expression: this.expression,
121
timestamp: this.timestamp,
···
127
}
128
129
/**
130
+
* Error during expression evaluation
131
+
*
132
+
* Thrown when evaluating expressions in directives like data-volt-text, data-volt-if, or any other binding that uses the expression evaluator.
133
+
*/
134
+
export class EvaluatorError extends VoltError {
135
+
constructor(cause: Error, context: ErrorContext) {
136
+
super(cause, { ...context, source: "evaluator" });
137
+
this.name = "EvaluatorError";
138
+
}
139
+
}
140
+
141
+
/**
142
+
* Error during directive binding
143
+
*
144
+
* Thrown when setting up or executing DOM bindings like data-volt-text, data-volt-class, data-volt-model, etc.
145
+
*/
146
+
export class BindingError extends VoltError {
147
+
constructor(cause: Error, context: ErrorContext) {
148
+
super(cause, { ...context, source: "binding" });
149
+
this.name = "BindingError";
150
+
}
151
+
}
152
+
153
+
/**
154
+
* Error during effect execution
155
+
*
156
+
* Thrown when effects, computed signals, or async effects fail during execution or cleanup.
157
+
*/
158
+
export class EffectError extends VoltError {
159
+
constructor(cause: Error, context: ErrorContext) {
160
+
super(cause, { ...context, source: "effect" });
161
+
this.name = "EffectError";
162
+
}
163
+
}
164
+
165
+
/**
166
+
* Error during HTTP operations
167
+
*
168
+
* Thrown when HTTP directives (data-volt-get, data-volt-post, etc.) encounter network errors, parsing failures, or swap strategy issues.
169
+
*/
170
+
export class HttpError extends VoltError {
171
+
constructor(cause: Error, context: ErrorContext) {
172
+
super(cause, { ...context, source: "http" });
173
+
this.name = "HttpError";
174
+
}
175
+
}
176
+
177
+
/**
178
+
* Error in plugin execution
179
+
*
180
+
* Thrown when custom plugins registered via registerPlugin fail during initialization or execution.
181
+
*/
182
+
export class PluginError extends VoltError {
183
+
constructor(cause: Error, context: ErrorContext) {
184
+
super(cause, { ...context, source: "plugin" });
185
+
this.name = "PluginError";
186
+
}
187
+
}
188
+
189
+
/**
190
+
* Error in lifecycle hooks
191
+
*
192
+
* Thrown when lifecycle hooks (beforeMount, afterMount, onMount, etc.) fail during execution.
193
+
*/
194
+
export class LifecycleError extends VoltError {
195
+
constructor(cause: Error, context: ErrorContext) {
196
+
super(cause, { ...context, source: "lifecycle" });
197
+
this.name = "LifecycleError";
198
+
}
199
+
}
200
+
201
+
/**
202
+
* Error during charge/initialization
203
+
*
204
+
* Thrown when charge() encounters errors during auto-discovery and mounting of [data-volt] elements, or when parsing data-volt-state.
205
+
*/
206
+
export class ChargeError extends VoltError {
207
+
constructor(cause: Error, context: ErrorContext) {
208
+
super(cause, { ...context, source: "charge" });
209
+
this.name = "ChargeError";
210
+
}
211
+
}
212
+
213
+
/**
214
+
* User-triggered error
215
+
*
216
+
* Errors explicitly reported by user code via the report() function
217
+
* with source: "user".
218
+
*/
219
+
export class UserError extends VoltError {
220
+
constructor(cause: Error, context: ErrorContext) {
221
+
super(cause, { ...context, source: "user" });
222
+
this.name = "UserError";
223
+
}
224
+
}
225
+
226
+
/**
227
* Global error handler registry
228
*/
229
let errorHandlers: ErrorHandler[] = [];
···
232
* Register an error handler
233
*
234
* Multiple handlers can be registered and will be called in registration order.
235
+
* Handlers can call `error.stopPropagation()` to prevent subsequent handlers from being called.
236
*
237
* @param handler - Error handler function
238
* @returns Cleanup function to unregister the handler
···
283
* If no error handlers are registered, errors are logged to console as fallback.
284
* Once handlers are registered, console logging is disabled.
285
*
286
+
* Error levels determine console output and behavior:
287
+
* - warn: Non-critical issues logged with console.warn
288
+
* - error: Recoverable errors logged with console.error (default)
289
+
* - fatal: Unrecoverable errors logged with console.error and thrown to halt execution
290
+
*
291
* @param error - Error to report (can be Error, unknown, or string)
292
* @param context - Error context with source and additional details
293
*
294
* @example
295
* ```ts
296
+
* // Warning for non-critical issues
297
+
* report(err, {
298
+
* source: "binding",
299
+
* level: "warn",
300
+
* directive: "data-volt-deprecated"
301
+
* });
302
*
303
+
* // Error for recoverable issues (default)
304
+
* report(err, {
305
+
* source: "evaluator",
306
+
* level: "error",
307
+
* directive: "data-volt-text",
308
+
* expression: expression
309
+
* });
310
+
*
311
+
* // Fatal error that halts execution
312
+
* report(err, {
313
+
* source: "charge",
314
+
* level: "fatal",
315
+
* directive: "data-volt-state"
316
+
* });
317
* ```
318
*/
319
export function report(error: unknown, context: ErrorContext): void {
320
const errorObj = error instanceof Error ? error : new Error(String(error));
321
+
322
+
const voltError = createErrorBySource(errorObj, context);
323
324
if (errorHandlers.length === 0) {
325
+
const logFn = voltError.level === "warn" ? console.warn : console.error;
326
+
327
+
logFn(voltError.message);
328
+
logFn("Caused by:", voltError.cause);
329
if (voltError.element) {
330
+
logFn("Element:", voltError.element);
331
+
}
332
+
333
+
if (voltError.level === "fatal") {
334
+
throw voltError;
335
}
336
return;
337
}
···
344
}
345
} catch (handlerError) {
346
console.error("Error in error handler:", handlerError);
347
+
}
348
+
}
349
+
350
+
if (voltError.level === "fatal") {
351
+
throw voltError;
352
+
}
353
+
}
354
+
355
+
/**
356
+
* Create the appropriate error type based on the source
357
+
*/
358
+
function createErrorBySource(cause: Error, context: ErrorContext): VoltError {
359
+
switch (context.source) {
360
+
case "evaluator": {
361
+
return new EvaluatorError(cause, context);
362
+
}
363
+
case "binding": {
364
+
return new BindingError(cause, context);
365
+
}
366
+
case "effect": {
367
+
return new EffectError(cause, context);
368
+
}
369
+
case "http": {
370
+
return new HttpError(cause, context);
371
+
}
372
+
case "plugin": {
373
+
return new PluginError(cause, context);
374
+
}
375
+
case "lifecycle": {
376
+
return new LifecycleError(cause, context);
377
+
}
378
+
case "charge": {
379
+
return new ChargeError(cause, context);
380
+
}
381
+
case "user": {
382
+
return new UserError(cause, context);
383
+
}
384
+
default: {
385
+
return new VoltError(cause, context);
386
}
387
}
388
}
+2
lib/src/core/http.ts
+2
lib/src/core/http.ts
···
231
default: {
232
report(new Error(`Unknown swap strategy: ${strategy as string}`), {
233
source: "http",
234
element: target as HTMLElement,
235
});
236
}
···
510
if (!target) {
511
report(new Error(`Target element not found: ${targetConf}`), {
512
source: "http",
513
element: defaultEl as HTMLElement,
514
directive: "data-volt-target",
515
});
···
231
default: {
232
report(new Error(`Unknown swap strategy: ${strategy as string}`), {
233
source: "http",
234
+
level: "warn",
235
element: target as HTMLElement,
236
});
237
}
···
511
if (!target) {
512
report(new Error(`Target element not found: ${targetConf}`), {
513
source: "http",
514
+
level: "warn",
515
element: defaultEl as HTMLElement,
516
directive: "data-volt-target",
517
});
+15
-2
lib/src/index.ts
+15
-2
lib/src/index.ts
···
7
export { asyncEffect } from "$core/async-effect";
8
export { mount } from "$core/binder";
9
export { charge } from "$core/charge";
10
-
export { clearErrorHandlers, onError, report } from "$core/error";
11
-
export type { VoltError } from "$core/error";
12
export { parseHttpConfig, request, serializeForm, serializeFormToJSON, swap } from "$core/http";
13
export {
14
clearAllGlobalHooks,
···
86
ComputedSignal,
87
ErrorContext,
88
ErrorHandler,
89
ErrorSource,
90
GlobalHookName,
91
GlobalStore,
···
7
export { asyncEffect } from "$core/async-effect";
8
export { mount } from "$core/binder";
9
export { charge } from "$core/charge";
10
+
export {
11
+
BindingError,
12
+
ChargeError,
13
+
clearErrorHandlers,
14
+
EffectError,
15
+
EvaluatorError,
16
+
HttpError,
17
+
LifecycleError,
18
+
onError,
19
+
PluginError,
20
+
report,
21
+
UserError,
22
+
VoltError,
23
+
} from "$core/error";
24
export { parseHttpConfig, request, serializeForm, serializeFormToJSON, swap } from "$core/http";
25
export {
26
clearAllGlobalHooks,
···
98
ComputedSignal,
99
ErrorContext,
100
ErrorHandler,
101
+
ErrorLevel,
102
ErrorSource,
103
GlobalHookName,
104
GlobalStore,
+11
lib/src/types/volt.d.ts
+11
lib/src/types/volt.d.ts
···
559
export type ErrorSource = "evaluator" | "binding" | "effect" | "http" | "plugin" | "lifecycle" | "charge" | "user";
560
561
/**
562
* Context information for error reporting
563
*/
564
export type ErrorContext = {
565
/** Error source category */
566
source: ErrorSource;
567
/** DOM element where error occurred */
568
element?: HTMLElement;
569
/** Directive name (e.g., "data-volt-text", "data-volt-on-click") */
···
559
export type ErrorSource = "evaluator" | "binding" | "effect" | "http" | "plugin" | "lifecycle" | "charge" | "user";
560
561
/**
562
+
* Error severity level
563
+
*
564
+
* - `warn`: Non-critical issues that don't prevent operation (e.g., deprecated usage, missing optional features)
565
+
* - `error`: Recoverable errors that prevent specific operations (e.g., failed evaluations, missing elements)
566
+
* - `fatal`: Unrecoverable errors that should halt execution (e.g., critical initialization failures)
567
+
*/
568
+
export type ErrorLevel = "warn" | "error" | "fatal";
569
+
570
+
/**
571
* Context information for error reporting
572
*/
573
export type ErrorContext = {
574
/** Error source category */
575
source: ErrorSource;
576
+
/** Error severity level (defaults to "error") */
577
+
level?: ErrorLevel;
578
/** DOM element where error occurred */
579
element?: HTMLElement;
580
/** Directive name (e.g., "data-volt-text", "data-volt-on-click") */
+186
-2
lib/test/core/error.test.ts
+186
-2
lib/test/core/error.test.ts
···
1
-
import { clearErrorHandlers, getErrorHandlerCount, onError, report, VoltError } from "$core/error";
2
-
import type { ErrorContext, ErrorSource } from "$types/volt";
3
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
4
5
describe("VoltError", () => {
···
295
for (const [i, source] of sources.entries()) {
296
const voltError: VoltError = handler.mock.calls[i][0];
297
expect(voltError.source).toBe(source);
298
}
299
});
300
});
···
1
+
import {
2
+
BindingError,
3
+
ChargeError,
4
+
clearErrorHandlers,
5
+
EffectError,
6
+
EvaluatorError,
7
+
getErrorHandlerCount,
8
+
HttpError,
9
+
LifecycleError,
10
+
onError,
11
+
PluginError,
12
+
report,
13
+
UserError,
14
+
VoltError,
15
+
} from "$core/error";
16
+
import type { ErrorContext, ErrorLevel, ErrorSource } from "$types/volt";
17
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
18
19
describe("VoltError", () => {
···
309
for (const [i, source] of sources.entries()) {
310
const voltError: VoltError = handler.mock.calls[i][0];
311
expect(voltError.source).toBe(source);
312
+
}
313
+
});
314
+
315
+
it("creates correct error types based on source", () => {
316
+
const handler = vi.fn();
317
+
onError(handler);
318
+
319
+
const testCases: Array<{ source: ErrorSource; errorType: typeof VoltError; name: string }> = [
320
+
{ source: "evaluator", errorType: EvaluatorError, name: "EvaluatorError" },
321
+
{ source: "binding", errorType: BindingError, name: "BindingError" },
322
+
{ source: "effect", errorType: EffectError, name: "EffectError" },
323
+
{ source: "http", errorType: HttpError, name: "HttpError" },
324
+
{ source: "plugin", errorType: PluginError, name: "PluginError" },
325
+
{ source: "lifecycle", errorType: LifecycleError, name: "LifecycleError" },
326
+
{ source: "charge", errorType: ChargeError, name: "ChargeError" },
327
+
{ source: "user", errorType: UserError, name: "UserError" },
328
+
];
329
+
330
+
for (const { source } of testCases) {
331
+
report(new Error(`Test ${source}`), { source });
332
+
}
333
+
334
+
expect(handler).toHaveBeenCalledTimes(testCases.length);
335
+
336
+
for (const [i, { errorType, name }] of testCases.entries()) {
337
+
const voltError = handler.mock.calls[i][0];
338
+
expect(voltError).toBeInstanceOf(errorType);
339
+
expect(voltError).toBeInstanceOf(VoltError);
340
+
expect(voltError.name).toBe(name);
341
+
}
342
+
});
343
+
});
344
+
345
+
describe("Error Levels", () => {
346
+
beforeEach(() => {
347
+
clearErrorHandlers();
348
+
vi.spyOn(console, "error").mockImplementation(() => {});
349
+
vi.spyOn(console, "warn").mockImplementation(() => {});
350
+
});
351
+
352
+
afterEach(() => {
353
+
clearErrorHandlers();
354
+
vi.restoreAllMocks();
355
+
});
356
+
357
+
it("defaults to error level when not specified", () => {
358
+
const cause = new Error("Test error");
359
+
const context: ErrorContext = { source: "binding" };
360
+
361
+
const voltError = new VoltError(cause, context);
362
+
363
+
expect(voltError.level).toBe("error");
364
+
});
365
+
366
+
it("includes error level in VoltError", () => {
367
+
const levels: Array<ErrorLevel> = ["warn", "error", "fatal"];
368
+
369
+
for (const level of levels) {
370
+
const cause = new Error(`Test ${level}`);
371
+
const context: ErrorContext = { source: "binding", level };
372
+
373
+
const voltError = new VoltError(cause, context);
374
+
375
+
expect(voltError.level).toBe(level);
376
+
}
377
+
});
378
+
379
+
it("includes error level in message", () => {
380
+
const levels: Array<ErrorLevel> = ["warn", "error", "fatal"];
381
+
382
+
for (const level of levels) {
383
+
const cause = new Error(`Test ${level}`);
384
+
const context: ErrorContext = { source: "binding", level };
385
+
386
+
const voltError = new VoltError(cause, context);
387
+
388
+
expect(voltError.message).toContain(`[${level.toUpperCase()}]`);
389
+
}
390
+
});
391
+
392
+
it("includes error level in JSON serialization", () => {
393
+
const cause = new Error("Test error");
394
+
const context: ErrorContext = { source: "binding", level: "warn" };
395
+
396
+
const voltError = new VoltError(cause, context);
397
+
const json = voltError.toJSON();
398
+
399
+
expect(json.level).toBe("warn");
400
+
});
401
+
402
+
it("uses console.warn for warn level without handlers", () => {
403
+
const error = new Error("Warning message");
404
+
const context: ErrorContext = { source: "binding", level: "warn" };
405
+
406
+
report(error, context);
407
+
408
+
expect(console.warn).toHaveBeenCalledTimes(2);
409
+
expect(console.warn).toHaveBeenCalledWith(expect.stringContaining("[WARN]"));
410
+
expect(console.warn).toHaveBeenCalledWith("Caused by:", error);
411
+
expect(console.error).not.toHaveBeenCalled();
412
+
});
413
+
414
+
it("uses console.error for error level without handlers", () => {
415
+
const error = new Error("Error message");
416
+
const context: ErrorContext = { source: "binding", level: "error" };
417
+
418
+
report(error, context);
419
+
420
+
expect(console.error).toHaveBeenCalledTimes(2);
421
+
expect(console.error).toHaveBeenCalledWith(expect.stringContaining("[ERROR]"));
422
+
expect(console.error).toHaveBeenCalledWith("Caused by:", error);
423
+
expect(console.warn).not.toHaveBeenCalled();
424
+
});
425
+
426
+
it("uses console.error for fatal level without handlers", () => {
427
+
const error = new Error("Fatal error");
428
+
const context: ErrorContext = { source: "charge", level: "fatal" };
429
+
430
+
expect(() => report(error, context)).toThrow(VoltError);
431
+
432
+
expect(console.error).toHaveBeenCalledTimes(2);
433
+
expect(console.error).toHaveBeenCalledWith(expect.stringContaining("[FATAL]"));
434
+
expect(console.error).toHaveBeenCalledWith("Caused by:", error);
435
+
});
436
+
437
+
it("throws error for fatal level after handlers", () => {
438
+
const handler = vi.fn();
439
+
onError(handler);
440
+
441
+
const error = new Error("Fatal error");
442
+
const context: ErrorContext = { source: "charge", level: "fatal" };
443
+
444
+
expect(() => report(error, context)).toThrow(VoltError);
445
+
446
+
expect(handler).toHaveBeenCalledTimes(1);
447
+
const voltError = handler.mock.calls[0][0];
448
+
expect(voltError.level).toBe("fatal");
449
+
});
450
+
451
+
it("does not throw for warn level", () => {
452
+
const error = new Error("Warning");
453
+
const context: ErrorContext = { source: "http", level: "warn" };
454
+
455
+
expect(() => report(error, context)).not.toThrow();
456
+
});
457
+
458
+
it("does not throw for error level", () => {
459
+
const error = new Error("Error");
460
+
const context: ErrorContext = { source: "binding", level: "error" };
461
+
462
+
expect(() => report(error, context)).not.toThrow();
463
+
});
464
+
465
+
it("passes error level to handlers", () => {
466
+
const handler = vi.fn();
467
+
onError(handler);
468
+
469
+
const levels: Array<ErrorLevel> = ["warn", "error", "fatal"];
470
+
471
+
for (const level of levels) {
472
+
try {
473
+
report(new Error(`Test ${level}`), { source: "binding", level });
474
+
} catch { /* No-op */ }
475
+
}
476
+
477
+
expect(handler).toHaveBeenCalledTimes(3);
478
+
479
+
for (const [i, level] of levels.entries()) {
480
+
const voltError: VoltError = handler.mock.calls[i][0];
481
+
expect(voltError.level).toBe(level);
482
}
483
});
484
});