Functions

KERN has a rich function system: standalone functions, class methods, React hooks, route handlers, guards, and more. All compile to idiomatic TypeScript.

Standalone functions

The fn node declares a function with typed parameters and return type:

fn name=createPlan params="action:PlanAction,ws:WorkspaceSnapshot" returns=Plan
  handler <<<
    return { id: crypto.randomUUID(), action, workspace: ws, state: 'draft' };
  >>>

Compiles to:

export function createPlan(
  action: PlanAction,
  ws: WorkspaceSnapshot,
): Plan {
  return { id: crypto.randomUUID(), action, workspace: ws, state: 'draft' };
}

Parameters

Parameter syntax is name:Type or name:Type=default, comma-separated:

fn name=search params="query:string,limit:number=20,offset:number=0" returns="SearchResult[]"
  handler <<<
    return db.search(query, { limit, offset });
  >>>

Complex types work too — the parser is depth-aware for generics, arrow functions, and nested brackets:

params="fetcher:(key:string)=>Promise<Data>,cache:Record<string,Data>"

Async functions

Add async=true to generate an async function:

fn name=fetchUser params="id:string" returns="Promise<User>" async=true
  handler <<<
    const res = await fetch(`/api/users/${id}`);
    return res.json();
  >>>

Streaming functions

Add stream=true to generate an async generator:

fn name=streamChunks params="prompt:string" returns=StreamChunk async=true stream=true
  handler <<<
    for await (const chunk of client.stream(prompt)) {
      yield chunk;
    }
  >>>

Compiles to:

export async function* streamChunks(
  prompt: string,
): AsyncGenerator<StreamChunk> {
  for await (const chunk of client.stream(prompt)) {
    yield chunk;
  }
}

Signal & cleanup

The signal child creates an AbortController. The cleanup child wraps the handler in try/finally:

fn name=handleChat params="input:string" returns="Promise<void>" async=true
  signal name=abort
  handler <<<
    const res = await fetch('/api/chat', {
      signal: abort.signal, body: input,
    });
    return res.json();
  >>>
  cleanup <<<
    abort.abort();
  >>>

Compiles to:

export async function handleChat(input: string): Promise<void> {
  const abort = new AbortController();
  try {
    const res = await fetch('/api/chat', {
      signal: abort.signal, body: input,
    });
    return res.json();
  } finally {
    abort.abort();
  }
}

Services (classes)

The service node generates a TypeScript class with fields, constructor, and methods:

service name=TokenTracker
  field name=entries type="TokenUsage[]" default="[]" private=true readonly=true

  method name=record params="usage:TokenUsage" returns=void
    handler <<<
      this.entries.push(usage);
    >>>

  method name=getStats returns=SessionStats
    handler <<<
      const total = this.entries.reduce((sum, e) => sum + e.tokens, 0);
      return { total, count: this.entries.length };
    >>>

  singleton name=tracker type=TokenTracker

Compiles to:

export class TokenTracker {
  private readonly entries: TokenUsage[] = [];

  record(usage: TokenUsage): void {
    this.entries.push(usage);
  }

  getStats(): SessionStats {
    const total = this.entries.reduce((sum, e) => sum + e.tokens, 0);
    return { total, count: this.entries.length };
  }
}

export const tracker = new TokenTracker();

Method modifiers

  • private=true — private visibility
  • static=true — static method
  • async=true — async method
  • stream=true — async generator (async *)

Singletons

The singleton child creates a module-level instance: export const tracker = new TokenTracker();

React hooks

The hook node generates a complete React custom hook with state, refs, effects, memos, and callbacks:

hook name=useSearch params="initialQuery:string" returns=UseSearchResult
  state name=query type=string init="initialQuery"
  ref name=controller type=AbortController init="new AbortController()"
  context name=env type=EnvContext source=EnvContext

  memo name=cacheKey deps="query"
    handler <<<
      return buildCacheKey(query);
    >>>

  callback name=handleFilter params="field:string,value:string" deps="query"
    handler <<<
      setQuery(prev => updateFilter(prev, field, value));
    >>>

  effect deps="query"
    handler <<<
      trackSearch(query);
    >>>

  returns names="query,handleFilter,cacheKey"

Compiles to:

import { useState, useRef, useContext, useMemo, useCallback, useEffect } from 'react';

export function useSearch(initialQuery: string): UseSearchResult {
  const [query, setQuery] = useState<string>(initialQuery);
  const controller = useRef<AbortController>(new AbortController());
  const env = useContext(EnvContext);

  const cacheKey = useMemo(() => {
    return buildCacheKey(query);
  }, [query]);

  const handleFilter = useCallback((field: string, value: string) => {
    setQuery(prev => updateFilter(prev, field, value));
  }, [query]);

  useEffect(() => {
    trackSearch(query);
  }, [query]);

  return { query, handleFilter, cacheKey } as const;
}

Hook children

ChildReact outputProps
stateuseState<T>(init)name, type, init
refuseRef<T>(init)name, type, init
contextuseContext(source)name, type, source
memouseMemo(() => {...}, deps)name, deps, handler
callbackuseCallback((p) => {...}, deps)name, params, deps, handler
effectuseEffect(() => {...}, deps)deps, handler, cleanup
returnsreturn { ... } as constnames

Guards

The guard node enforces conditions inside route handlers. If the expression is falsy, it returns an error response:

route GET /api/users/:id
  derive user expr={{await db.users.findById(params.id)}}
  guard name=exists expr={{user}} else=404
  respond 200 json=user
  error 404 "Not found"

Compiles to Express:

app.get('/api/users/:id', async (req, res) => {
  const user = await db.users.findById(req.params.id);
  if (!(user)) {
    return res.status(404).json({ error: 'exists guard failed' });
  }
  res.json(user);
});

Function taxonomy

NodeContextOutput
fnTop-levelStandalone function
methodInside serviceClass method
constructorInside serviceClass constructor
hookTop-levelReact custom hook
callbackInside hookuseCallback()
memoInside hookuseMemo()
effectInside hookuseEffect()
guardInside routeConditional check
transitionInside machineState transition fn
commandInside cliCLI handler

Next: State Machines — type-safe state transitions from 12 lines of KERN.