Skip to content

Document a public symbol

API reference pages on this site are generated from the exports of every source file listed in that package’s entryPoints configuration in apps/docs/astro.config.ts. JSDoc on those exports is public documentation, not an internal code comment: no reference Markdown is hand-written between a code change and a docs rebuild.

src/index.ts is the conventional package-root entry point, but it is not the only supported entry point. A published subpath can have its own source entry point, such as:

  • @maelstrom-co/realtime-protocol/openai → src/openai.ts.
  • @maelstrom-co/realtime-protocol/livekit-wire → src/livekit-wire.ts.
  • @maelstrom-co/realtime-vad/assets → src/vad-assets.ts.
  • Check the package’s exports map. The symbol must be exported from a published package root or subpath rather than only from an internal source file.
  • Check apps/docs/astro.config.ts. The public root or subpath source file must be listed explicitly in the matching TypeDoc plugin’s entryPoints array.
  • Check the package’s tsconfig.lib.json. TypeDoc’s resolve strategy expects each configured entry point to belong to that TypeScript project, so the entry-point file and the declaration it exports must be included and not excluded.
  1. Write a JSDoc block above the export. Lead with what the symbol does and any load-bearing invariant; skip what the type already shows.

    packages/engine/src/lib/engine.ts
    /**
    * Construct an Engine from the given config.
    *
    * Validates `config` synchronously and **throws** on invalid input.
    * Invalid config is a programmer error, not a runtime condition.
    */
    export function createEngine(config: EngineConfig): Engine {
    // ...
    }
  2. Cross-reference other symbols with {@link Name}. Bare names render as plain text; {@link} resolves to an intra-site link, including member references like {@link Engine.handle}.

    /**
    * Routes on `message.type`. See {@link createEngine} for construction
    * and {@link EngineError} for the failure channel.
    */
    handle(message: EngineMessage): Promise<Result<EngineOutput, EngineError>>;
  3. Mark public-entry-point exports that aren’t user-facing with @internal. If a symbol is exported from a configured root or subpath only because a sibling @maelstrom-co package needs it, tag it @internal. The generator uses excludeInternal: true, so the symbol remains importable but does not appear on the docs site. A helper that is not reachable from any configured entry point is already absent from the generated reference; @internal is unnecessary there.

    /**
    * Wire helper for cross-package consumers.
    *
    * @internal
    */
    export function someBarrelExportedHelper(): SomeShape {
    // ...
    }

Use @example followed by a fenced code block. The plugin renders it as an ## Example section under the symbol.

/**
* Build the system + user prompt halves.
*
* @example
* ```ts
* const { systemPrompt, userPrompt } = buildPrompt(request, {
* vars: { assistantName: 'Aria' },
* });
* ```
*/
export function buildPrompt(
request: ChartRequest,
options?: PromptOptions,
): PromptResult;

@deprecated renders inline with a strikethrough badge; pair it with a pointer to the replacement.

/**
* @deprecated Use the disposer returned by {@link registerRunner} instead.
*/
unregisterRunner(instanceId: InstanceId): void;

@throws renders as its own section. Keep the body in prose — the leading {TypeName} is parsed by TypeDoc and stripped from the rendered text.

/**
* @throws If `options` is invalid, or if a system-prompt customizer is
* misconfigured.
*/
export function llmPlanner(options: LlmPlannerOptions): Stage<LlmPlannerInput, PlannerCandidate>;

A weak JSDoc block repeats the type signature without adding behavior:

/**
* Processes an intent.
*
* @param intent The intent string.
* @returns A promise.
*/
processIntent(intent: string): Promise<void>;

A useful block explains the contract the type cannot show:

/**
* Sends a user intent to the engine and updates Maelstrom state with the result.
*
* The promise resolves even when processing fails; render failures from
* `lastError` instead of relying on `try/catch` around this call.
*/
processIntent(intent: string): Promise<void>;
  • Don’t restate the type signature in prose. The reference page already shows it. If hovering the symbol gives the reader the same information, the prose is filler.
  • Don’t put TODO(...) notes inside JSDoc blocks. They render verbatim on the public page. Use regular // comments next to the implementation instead.
  • Don’t use ## or deeper Markdown headings inside JSDoc. TypeDoc emits its own ## sections (Parameters, Returns, Throws, Example). Authoring sibling ## headings inside a comment block produces a jumbled page hierarchy. Stick to prose paragraphs and bullet lists.
  • Don’t document an internal source export expecting it to appear. The symbol must be reachable from an explicitly configured package root or subpath entry point.
  • Don’t configure an internal file as an entry point. First publish a root or subpath in the package exports map, then configure its source file and confirm that tsconfig.lib.json includes it.
  • Don’t guess a generated URL from a symbol name or source filename. Multi-entry packages can include source-entry-point segments in their route hierarchy. Build the site and record the route that Astro actually emits.

Before you call a public symbol documented, reread the rendered prose and check:

  • Accuracy: the text matches actual runtime behavior.
  • Completeness: edge cases, no-ops, throw conditions, and invariants are documented where they matter.
  • Contrast: related APIs that behave differently are distinguished explicitly.
  • Examples: examples compile conceptually and do not rely on hidden setup.
  • Surface: each symbol is reachable from the intended published root or subpath, and exports meant only for package internals are marked @internal.

Build the docs, inventory the configured output, and open the generated page for the symbol you changed:

Terminal window
bunx nx run docs:build
rg --files apps/docs/dist/reference/<configured-output>

Treat the files under apps/docs/dist/reference/<configured-output> as the route authority. Do not assume a fixed <kind>/<symbol> pattern: the generated path depends on TypeDoc’s project model, and multi-entry packages can render additional module or source-entry-point segments. Open the observed page and confirm the prose, parameters, returns, @internal exclusions, and any {@link} cross-references. For an iterative loop, bunx nx run docs:dev regenerates on startup; restart the dev server after editing JSDoc.