Events
KERN has three event systems: typed custom events, DOM event handlers, and WebSocket events. All are target-agnostic — the same .kern compiles to React, Vue, Express, or FastAPI.
Typed custom events
The event node defines a typed event system with a union type, event map, and callback type:
event name=ForgeEvent
type name="baseline:start"
type name="baseline:done" data="{ passes: boolean }"
type name="stage1:dispatch" data="{ engineId: string }"
type name="stage1:accepted" data="{ engineId: string, score: number }"
type name="winner:determined" data="{ winner: string, bestScore: number }"Compiles to:
export type ForgeEventType =
| 'baseline:start'
| 'baseline:done'
| 'stage1:dispatch'
| 'stage1:accepted'
| 'winner:determined';
export interface ForgeEvent {
type: ForgeEventType;
data?: Record<string, unknown>;
}
export interface ForgeEventMap {
'baseline:start': Record<string, unknown>;
'baseline:done': { passes: boolean };
'stage1:dispatch': { engineId: string };
'stage1:accepted': { engineId: string; score: number };
'winner:determined': { winner: string; bestScore: number };
}
export type ForgeEventCallback = (event: ForgeEvent) => void;Event type children
name— event type string (e.g."baseline:done")data— optional payload shape (defaults toRecord<string, unknown>)
The generated ForgeEventMap gives you strict typing when listening to specific event types — TypeScript enforces that event data matches the declared shape.
DOM event handlers
The on node binds event handlers. KERN maps the event type to the correct native type:
on event=click
handler <<<
handleClick();
>>>
on event=submit async=true
handler <<<
e.preventDefault();
await submitForm(data);
>>>
on event=key key=Enter
handler <<<
processInput(buffer);
>>>Event type mapping
| KERN event | TypeScript type | React prop | Vue directive |
|---|---|---|---|
click | MouseEvent | onClick | @click |
change | Event | onChange | @change |
submit | SubmitEvent | onSubmit | @submit |
key / keydown | KeyboardEvent | onKeyDown | @keydown |
focus | FocusEvent | onFocus | @focus |
blur | FocusEvent | onBlur | @blur |
scroll | Event | onScroll | @scroll |
message | MessageEvent | (listener) | (listener) |
resize | UIEvent | useEffect listener | onMounted listener |
on properties
event— event type: click, submit, key, change, message, resize, etc.key— optional key filter for keyboard events (e.g.key=Enter)async=true— wrap handler as async functionhandler— reference to an external handler function name
React compilation
DOM events on interactive elements become React props. Global events (resize, keydown) become useEffect listeners with automatic cleanup:
// Button click -> onClick prop
<button onClick={handleClick}>Save</button>
// Input binding -> value + onChange
<input value={query} onChange={(e) => setQuery(e.target.value)} />
// Global keydown -> useEffect listener
useEffect(() => {
const listener = (e: KeyboardEvent) => {
if (e.key === 'Enter') processInput(buffer);
};
window.addEventListener('keydown', listener);
return () => window.removeEventListener('keydown', listener);
}, []);Handlers are wrapped in useCallback with proper dependency arrays. Event types are fully qualified: React.MouseEvent, React.KeyboardEvent, etc.
Vue compilation
The same .kern compiles to Vue template directives and Composition API:
// Button -> @click directive
<button @click="handleClick">Save</button>
// Global keydown -> onMounted listener
onMounted(() => {
const listener = (e: KeyboardEvent) => handleKey(e);
window.addEventListener('keydown', listener);
return () => window.removeEventListener('keydown', listener);
});WebSocket events
The websocket node defines bidirectional server-client communication:
websocket path=/ws/chat
on event=connect
handler <<<
ws.send(JSON.stringify({ type: 'hello' }));
>>>
on event=message
handler <<<
const data = JSON.parse(event.data);
broadcast(data);
>>>
on event=disconnect
handler <<<
console.log('client disconnected');
>>>Compiles to Express:
const wsServer = new WebSocketServer({ server, path: '/ws/chat' });
wsServer.on('connection', (ws: WebSocket) => {
ws.send(JSON.stringify({ type: 'hello' }));
ws.on('message', (raw: Buffer) => {
const data = JSON.parse(raw.toString());
broadcast(data);
});
ws.on('close', () => {
console.log('client disconnected');
});
});Compiles to FastAPI:
@app.websocket("/ws/chat")
async def websocket_endpoint(websocket: WebSocket):
await websocket.accept()
await websocket.send_json({"type": "hello"})
try:
while True:
data = await websocket.receive_json()
await broadcast(data)
except WebSocketDisconnect:
print("client disconnected")WebSocket event types
connect— fired when a client connectsmessage— fired on incoming datadisconnect/close— fired when client disconnectserror— fired on connection error
How target compilation works
Events are target-agnostic at the IR level. The parser produces a unified event representation, which each target transpiler compiles to native syntax:
React
onClick, onChange, useEffect listeners
Vue
@click, @change, onMounted listeners
Express
.on() listeners, WebSocketServer
FastAPI
@app.websocket, async def handlers
Define event handlers once — the same on event=click works whether you're building a React app or a Vue app.
Back to: Types & Interfaces | Functions | State Machines