gengen/docs

API reference

All exports from @moeki0/gengen. React components and hooks are in @moeki0/gengen/react.

g.block(name)

Defines a block. .schema() returns a SchemaDefinition — server-safe, no React dependency, suitable for g.prompt(). Calling .component() on that returns a RendererDefinition for <Gengen>. Keep the two in separate files so the schema stays importable on the server.

import { g } from '@moeki0/gengen'

// card.schema.ts — server-safe
export const cardSchema = g.block('card')
  .describe('A key insight card')
  .schema({ title: g.text(), body: g.text() })
// → SchemaDefinition — pass to g.prompt()

// card.tsx — needs React
import { cardSchema } from './card.schema'
export const Card = cardSchema
  .component(({ title, body }) => <div>{title}: {body}</div>)
// → RendererDefinition — pass to <Gengen>

The name is used for routing: a ### card heading or a ```card fenced block in the LLM output will match this renderer.

g.inline(name)

Defines an inline renderer — custom markers within prose text.

import { useInlineText } from '@moeki0/gengen/react'

const Highlight = g.inline('highlight')
  .marker('==', '==')
  .describe('emphasized term')
  .component(() => {
    const text = useInlineText()
    return <mark>{text}</mark>
  })

// LLM writes: "The ==photosynthesis== process..."
// Renders: <mark>photosynthesis</mark>
Prop / methodTypeDescription
.marker(open, close)[string, string]The opening and closing delimiters.
.describe(desc)stringDescription used in g.prompt() output.
.component(C)ComponentType<{ text: string }>React component. Use useInlineText() to access the matched text.

Schema types

Schema types describe what the LLM should write for each field.

g.text(until?, label?)

A plain text paragraph, or a key: value line when label is true.

schema: {
  summary: g.text(),           // plain paragraph
  status: g.text(undefined, true), // writes "status: ..."
  note: g.text().optional(),   // may be absent
}

g.list()

A bullet list (- item). Chain modifiers to constrain the items.

schema: {
  items: g.list(),
  items3plus: g.list().min(3),
  urls: g.list().all(g.url()),
  kvPairs: g.list().all(g.split(':', g.str('key'), g.str('value'))),
}
Prop / methodTypeDescription
.min(n)numberRequire at least n items.
.some(constraint)LabeledConstraintAt least one item must satisfy a labeled constraint.
.all(constraint)KeyValueConstraint | FormatConstraintAll items must satisfy the constraint. Changes the inferred type.
.optional()Field may be absent.

g.codeblock(lang?)

A fenced code block.

schema: {
  code: g.codeblock('tsx'),    // ```tsx ... ```
  sql:  g.codeblock('sql').optional(),
}

g.bool()

A single line containing true or false.

schema: {
  isRecommended: g.bool(),
}

g.heading(level?)

A Markdown heading. Supports structured metadata via .split() or exact-match via .content().

// Simple heading (any ##)
schema: { section: g.heading(2) }

// Heading with embedded metadata
const h = g.heading(2)
  .split(' | ', 'color', g.hex())
  .split(' (', 'year', 'number')
// Matches: "## Section title | #1a2b3c (2024)"
// Parsed as: { text: "Section title", color: "#1a2b3c", year: "2024" }

// Parse a heading-structured markdown directly
const { intro, sections } = h.parse(markdown)

// Exact-match heading
schema: { title: g.heading(1).content('Summary') }

g.table()

A GFM Markdown table. Inferred type: { headers: string[]; rows: string[][] }.

schema: {
  data: g.table(),
}
// Component receives: { headers: ['Name', 'Value'], rows: [['a', '1'], ...] }

g.blockquote()

A blockquote (> ...).

schema: {
  quote: g.blockquote(),
}

List constraints

g.split(sep, key, value)

Constrains list items to a key–value format. All items must match. Returns a typed object array.

// List items like: "Albert Einstein — physicist"
const people = g.list().all(
  g.split('', g.str('name'), g.str('role'))
)
// Inferred type: { name: string; role: string }[]

// Numeric values
const scores = g.list().all(
  g.split(':', g.str('label'), g.integer('score'))
)
// { label: string; score: number }[]

g.url() / g.image()

Format constraints — all list items must be a URL or image URL.

schema: {
  links:  g.list().all(g.url()),
  photos: g.list().all(g.image()),
}

g.endsWith(suffix) / g.startsWith(prefix)

Labeled constraints for .some(). Mark a subset of list items.

// Some items end with "★" to mark them as favorites
const isFavorite = g.endsWith('').is('favorite')
schema: {
  options: g.list().some(isFavorite),
}
// LLM writes:
// - Regular option
// - This is a favorite ★
// Props: { options: string[]; favorite: string }

g.matches(pattern)

Items matching a regex pattern.

const hasEmoji = g.matches(/^[🌀-🧿]/u).is('emoji')
schema: {
  reactions: g.list().some(hasEmoji),
}

Item types

Used as key/value arguments to g.split().

g.str('fieldName')      // → string
g.integer('fieldName')  // → number (parseInt)
g.number('fieldName')   // → number (parseFloat)
g.yearStr('fieldName')  // → string, must contain a digit sequence

Meta types

Used as the third argument to g.heading().split(). Describe the format of embedded metadata in headings.

g.hex()                  // "#1a2b3c" — HEX color
g.oneOf('a', 'b', 'c')   // one of the given values
g.gridSpan()             // "2x1" — columns × rows

Flow API

Flow nodes shape how g.prompt() describes the response structure. They are hints for the LLM — g.route() always uses schema specificity, not flow order.

import { g } from '@moeki0/gengen'

const systemPrompt = g.prompt([
  g.prose('Start with a brief introduction'),
  g.loop([
    g.pick(Card, Quote),   // repeat: one of Card or Quote
    g.prose(),             // followed by prose
  ]),
])
Prop / methodTypeDescription
g.prose(hint?)ProseNodeA plain prose paragraph. Optional hint for the LLM.
g.loop([...nodes])LoopNodeRepeat the child nodes as many times as needed.
g.pick(...schemas)OneOfNodeChoose one of the given schemas.
g.flow([...nodes])FlowWrap nodes into a Flow object (optional convenience).

g.prompt(input)

Generates a system prompt string from renderer definitions or flow nodes.

import { g } from '@moeki0/gengen'

// Flat list
const p1 = g.prompt([Card, Quote])

// Flow
const p2 = g.prompt([
  g.prose('Introduction'),
  g.loop([Card]),
])

// Single renderer
const p3 = g.prompt([Card])

The output is a plain string you can use as a system message in any LLM SDK.

g.route(markdown, renderers)

Parses markdown and returns an array of RenderedBlock objects, each matched to the most specific renderer (or null for unmatched blocks).

import { g } from '@moeki0/gengen'

const blocks = g.route(markdown, [Card, Quote])
// blocks: Array<{ renderer: RendererDefinition | null; markdown: string }>

// Used internally by <Gengen>. Call directly for server-side extraction:
for (const block of blocks) {
  if (block.renderer?.name === 'card') {
    const data = g.parseSchema(block.markdown, Card.schema)
    // data: { title: string; body: string }
  }
}

g.parseSchema(markdown, schema)

Parses a markdown string according to a schema, returning a typed object.

import { g } from '@moeki0/gengen'

const schema = { title: g.text(), tags: g.list() }
const data = g.parseSchema(markdownBlock, schema)
// data: { title: string; tags: string[] }

Use this on the server when you need to extract structured data without rendering.

<Gengen>

The main rendering component. Import from @moeki0/gengen/react.

import { Gengen } from '@moeki0/gengen/react'

<Gengen
  markdown={response}
  renderers={[Card, Quote, Term]}
  context={{ onAction: handleAction }}
  fallback={MyMarkdownRenderer}
/>
Prop / methodTypeDescription
markdownstringThe LLM response to render.
renderers(RendererDefinition | InlineRendererDefinition)[]Block and inline renderers.
context?Record<string, any>Arbitrary data passed to useGengenContext() inside renderer components.
fallback?ComponentType<{ markdown: string }>Custom fallback for unmatched blocks. Defaults to a styled ReactMarkdown.

Hooks

Import from @moeki0/gengen/react.

useGengenContext()

Returns the context object passed to the parent <Gengen>.

import { useGengenContext } from '@moeki0/gengen/react'

function CardComponent({ title }: { title: string }) {
  const { onAction } = useGengenContext<{ onAction: (a: Action) => void }>()
  return <button onClick={() => onAction({ type: 'select', payload: title })}>{title}</button>
}

useInlineText()

Returns the matched text inside an inline renderer component.

import { useInlineText } from '@moeki0/gengen/react'

const Tooltip = g.inline('tooltip')
  .marker('[', ']')
  .component(() => {
    const text = useInlineText()
    return <abbr title="...">{text}</abbr>
  })