Templates

Templates are reusable code patterns with typed slots. Define them once, use them anywhere — the compiler expands them into full TypeScript.

Defining a template

A template has a name, typed slots, optional imports, and a body with {{ }} placeholders:

template name=zustand-store
  slot name=storeName type=identifier
  slot name=stateType type=identifier
  import from=zustand names=create
  body <<<
    export const use{{storeName}}Store = create<{{stateType}}>((set, get) => ({
      {{CHILDREN}}
    }));
  >>>

Slot types

Each slot has a typed constraint that the compiler validates:

Slot typeValidatesExample
identifierMust match [A-Za-z_][A-Za-z0-9_]*storeName, hookName
typeNon-empty TypeScript typeatomType, returnType
exprNon-empty expressioncacheKey, queryFn
blockAny value (can be empty)body content

Slots can be optional with default values:

slot name=fetcher type=expr optional=true default=defaultFetcher

Using a template

Use a registered template by its name as a node type, passing slot values as props:

zustand-store storeName=Toast stateType=ToastState
  handler <<<
    toasts: [],
    addToast: (msg) => set(s => ({
      toasts: [...s.toasts, { id: Date.now().toString(), message: msg, type: 'info' }]
    })),
    removeToast: (id) => set(s => ({
      toasts: s.toasts.filter(t => t.id !== id)
    })),
  >>>

The handler <<< >>> block becomes the {{CHILDREN}} placeholder in the template body. The compiler:

  1. Looks up the template definition
  2. Validates required slots against the node's props
  3. Replaces {{slotName}} placeholders with provided values
  4. Replaces {{CHILDREN}} with child node output
  5. Prepends import statements

Built-in template catalog

kern init-templates scans your package.json and scaffolds .kern template files for detected libraries:

kern init-templates    # Scans package.json -> scaffolds templates

Library templates

LibraryTemplateWhat it generates
Zustandzustand-storeTyped store with create<T>()
Zustandzustand-selectorTyped selector function
SWRswr-hookData fetching hook with useSWR
TanStack Queryquery-hookQuery hook with useQuery
TanStack Querymutation-hookMutation hook with useMutation
tRPCtrpc-querytRPC route query hook
XStatexstate-machineState machine with setup()
Jotaijotai-atomTyped atom with atom<T>()
Jotaijotai-derivedDerived atom with get()

Common templates (always included)

Two framework-agnostic templates are always available:

// arrow-fn: named arrow function export
arrow-fn name=formatArticle params="a: Article" returnType=": string"
  handler <<<
    return `[${a.status}] ${a.title}`;
  >>>

// window-event: browser event hook with cleanup
window-event hookName=useResize eventName="'resize'"
  handler <<<
    setWidth(window.innerWidth);
  >>>

Self-extending: kern evolve

Don't have a template for your library? KERN can learn it from your codebase:

kern evolve src/ --recursive           # Detect patterns -> propose templates
kern evolve:review --list              # Review proposals with split-view
kern evolve:review --approve <id>      # Approve a proposal
kern evolve:review --promote --local   # Write to templates/

Evolve scans your TypeScript, finds patterns from library families (react-hook-form, redux-toolkit, framer-motion, axios, yup, valibot, and more), and generates .kern template proposals with typed slots.

Each proposal is validated through a 5-step pipeline: parse, register, expand, golden-diff, typecheck.

Template expansion depth

Templates can reference other templates (nested expansion). The compiler enforces a maximum expansion depth of 10 to prevent infinite recursion.