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 to Record<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 eventTypeScript typeReact propVue directive
clickMouseEventonClick@click
changeEventonChange@change
submitSubmitEventonSubmit@submit
key / keydownKeyboardEventonKeyDown@keydown
focusFocusEventonFocus@focus
blurFocusEventonBlur@blur
scrollEventonScroll@scroll
messageMessageEvent(listener)(listener)
resizeUIEventuseEffect listeneronMounted 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 function
  • handler — 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 connects
  • message — fired on incoming data
  • disconnect / close — fired when client disconnects
  • error — 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