Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
99 changes: 76 additions & 23 deletions dist/kasper.js

Large diffs are not rendered by default.

565 changes: 304 additions & 261 deletions dist/kasper.min.js

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions dist/types/component.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ export declare class Component<TArgs extends Record<string, any> = Record<string
onRender(): void;
onChanges(): void;
onDestroy(): void;
onError?(error: Error, phase: string): void;
render(): void;
}
export type KasperEntity = Component | Record<string, any> | null | undefined;
Expand Down
7 changes: 7 additions & 0 deletions dist/types/error-handler.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
export type ErrorPhase = 'render' | 'watcher';
export type ErrorHandlerFn = (error: Error, context: {
component?: any;
phase: ErrorPhase;
}) => void;
export declare function setErrorHandler(handler: ErrorHandlerFn | undefined): void;
export declare function handleError(error: unknown, phase: ErrorPhase, component?: any): void;
3 changes: 2 additions & 1 deletion dist/types/expression-parser.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@ import { Token } from "./types/token";
export declare class ExpressionParser {
private current;
private tokens;
parse(tokens: Token[]): Expr.Expr[];
private source;
parse(tokens: Token[], source?: string): Expr.Expr[];
private match;
private advance;
private peek;
Expand Down
1 change: 1 addition & 0 deletions dist/types/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,4 @@ import { signal, effect, computed, batch, watch } from "./signal";
import { nextTick } from "./scheduler";
export { ExpressionParser, Interpreter, Scanner, TemplateParser, Transpiler, signal, effect, computed, batch, watch, nextTick };
export { execute, transpile, bootstrap as App, lazy, Component, navigate, Router };
export type { ErrorHandlerFn } from "./error-handler";
2 changes: 2 additions & 0 deletions dist/types/kasper.d.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { ComponentClass, ComponentRegistry } from "./component";
import { ErrorHandlerFn } from "./error-handler";
export declare function lazy(importer: () => Promise<Record<string, ComponentClass>>): {
component: () => Promise<ComponentClass>;
lazy: true;
Expand All @@ -12,5 +13,6 @@ export interface KasperConfig {
entry?: string;
registry: ComponentRegistry;
mode?: "development" | "production";
onError?: ErrorHandlerFn;
}
export declare function bootstrap(config: KasperConfig): import("./component").Component<Record<string, any>>;
1 change: 1 addition & 0 deletions dist/types/scheduler.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ export declare function queueUpdate(instance: Component, task: Task): void;
* Executes a function with batching disabled.
* Used for initial mount and manual renders.
*/
export declare function isBatching(): boolean;
export declare function flushSync(fn: () => void): void;
/**
* Returns a promise that resolves after the next framework update cycle.
Expand Down
8 changes: 7 additions & 1 deletion dist/types/types/error.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,12 +29,18 @@ export declare const KErrorCode: {
};
export type KErrorCodeType = (typeof KErrorCode)[keyof typeof KErrorCode];
export declare const ErrorTemplates: Record<string, (args: any) => string>;
export interface KasperErrorOptions {
line?: number;
col?: number;
tag?: string;
source?: string;
}
export declare class KasperError extends Error {
code: KErrorCodeType;
args: any;
line?: number;
col?: number;
tagName?: string;
constructor(code: KErrorCodeType, args?: any, line?: number, col?: number, tagName?: string);
constructor(code: KErrorCodeType, args?: any, options?: KasperErrorOptions);
withTag(tagName: string): this;
}
1 change: 1 addition & 0 deletions docs-web/astro.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ export default defineConfig({
{ label: 'Pipes', link: '/guides/pipes/' },
{ label: 'Routing', link: '/guides/routing/' },
{ label: 'Lazy Loading', link: '/guides/lazy-loading/' },
{ label: 'Error Handling', link: '/guides/error-handling/' },
{ label: 'AI-Driven Development', link: '/guides/agents/' },
{ label: 'Vite Integration', link: '/guides/vite/' }
]
Expand Down
27 changes: 27 additions & 0 deletions docs-web/public/llms.txt
Original file line number Diff line number Diff line change
Expand Up @@ -527,6 +527,33 @@ registry: {

---

## Error handling

Errors during reactive updates are caught by the framework. Configure handlers to surface them:

```ts
// Global handler — receives all unhandled errors
App({
onError(error, { component, phase }) {
// phase: 'render' | 'watcher'
Sentry.captureException(error);
},
});

// Component-level — handle locally, optionally show fallback UI
export class MyComponent extends Component {
onError(error: Error, phase: string) {
this.hasError.value = true;
// re-throw to also reach the global handler
}
}
```

Propagation order: component `onError` → global `onError` → `console.error`.
Component `onError` suppresses the global handler unless it re-throws.

---

## Key rules

- One component per `.kasper` file
Expand Down
104 changes: 104 additions & 0 deletions docs-web/src/content/docs/guides/error-handling.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
---
title: Error Handling
description: Catching and reporting render errors with component and global error handlers.
sidebar:
order: 10
---

By default, errors that occur during a reactive update (signal change triggering a re-render) are caught by the framework to prevent a single component from crashing the whole app. Without error handlers configured those errors are only logged to the console — your application code never sees them.

Kasper provides two levels of error handling: a **component-level `onError` hook** and a **global `onError` handler** on the app config.

## Global error handler

Pass `onError` to `App()` to intercept all unhandled render and watcher errors. Use this for error reporting services like Sentry:

```ts
import { App } from 'kasper-js';
import { AppRoot } from './components/AppRoot.kasper';

App({
root: document.body,
entry: 'app-root',
registry: {
'app-root': { component: AppRoot },
},
onError(error, { component, phase }) {
console.error(`[${phase}] in`, component?.constructor?.name, error);
// e.g. Sentry.captureException(error, { extra: { phase } });
},
});
```

`phase` is either `'render'` (reactive DOM update) or `'watcher'` (a `watch()` callback threw).

## Component-level `onError`

Define `onError` on any component to handle errors locally — for example to show a fallback UI instead of leaving a broken element on screen:

```html
<!-- SafeWidget.kasper -->
<template>
<div @if="!hasError.value">
{{ riskyValue |> expensiveFormat }}
</div>
<div @else class="error-fallback">
Something went wrong.
</div>
</template>

<script>
import { Component, signal } from 'kasper-js';

export class SafeWidget extends Component {
hasError = signal(false);

onError(error, phase) {
this.hasError.value = true;
// optionally re-throw to also reach the global handler:
// throw error;
}
}
</script>
```

If `onError` handles the error (does not re-throw), the global handler is **not** called. Re-throw if you want both — local recovery AND global reporting.

## Error propagation order

```
Error during render / watcher
├─ component.onError defined?
│ ├─ yes → call it
│ │ ├─ returns normally → done (global handler skipped)
│ │ └─ throws → fall through with original error
│ └─ no → fall through
├─ global onError configured?
│ ├─ yes → call it → done
│ └─ no → fall through
└─ console.error (final fallback)
```

## TypeScript

`ErrorHandlerFn` is exported for typing the global handler:

```ts
import type { ErrorHandlerFn } from 'kasper-js';

const handler: ErrorHandlerFn = (error, { component, phase }) => {
// ...
};
```

## What is and isn't caught

| Scenario | Caught |
|---|---|
| Error in template expression during reactive update | ✓ |
| Error in a `watch()` callback | ✓ |
| Error thrown during initial mount (`App()`) | ✗ — propagates to caller |
| Error in `onMount` / `onDestroy` | ✗ — propagates normally |
Loading
Loading