API Reference

Streaming API

Handle incomplete or partial Comark content with utilities for streaming scenarios where content arrives incrementally.

autoCloseMarkdown(source)

Automatically closes unclosed markdown inline syntax and Comark components. Built for streaming scenarios where content arrives incrementally and may be incomplete at any point.

Parameters:

  • source - The markdown content (potentially partial/incomplete)

Returns: string — the source with all unclosed syntax closed

Example:

import { autoCloseMarkdown } from 'comark'

autoCloseMarkdown('**bold text')
autoCloseMarkdown is also available as a parse option — set autoClose: true (default) in parse() or createParse() to apply it automatically.

Parser Integration

autoClose is enabled by default in parse() and createParse(). You can disable it if you want to handle incomplete syntax yourself:

import { parse } from 'comark'

const result = await parse(content, {
  autoClose: true // default
})

Supported Syntax

Inline Markdown

SyntaxExampleAuto-closed
Bold**text**text**
Italic*text*text*
Code`code`code`
Strikethrough~~text~~text~~
Link[text](url[text](url)
Image![alt](url![alt](url)

Comark Components

Block components are closed based on their marker count:

auto-close.ts
// Double marker (block component)
autoCloseMarkdown('::alert\nContent')
// '::alert\nContent\n::'

// Triple marker (nested component)
autoCloseMarkdown(':::card\nContent')
// ':::card\nContent\n:::'

// Nested components
autoCloseMarkdown('::::outer\n:::inner\n::component')
// '::::outer\n:::inner\n::component\n::\n:::\n::::'

Props and Attributes

Components with props are handled correctly:

auto-close.ts
// Inline props
autoCloseMarkdown('::alert{type="info" title="Note"}')
// '::alert{type="info" title="Note"}\n::'

// YAML props
autoCloseMarkdown(`::component\n---\nkey: value\n---\nContent`)
// '::component\n---\nkey: value\n---\nContent\n::'

Use Cases

AI Streaming

When streaming AI-generated markdown, content arrives in chunks and may be incomplete at any point:

chat.ts
import { autoCloseMarkdown, parse } from 'comark'

let accumulated = ''

socket.on('chunk', async (chunk) => {
  accumulated += chunk

  // Auto-close before parsing to ensure valid AST at every chunk
  const closed = autoCloseMarkdown(accumulated)
  const result = await parse(closed)

  renderContent(result.nodes)
})

socket.on('end', async () => {
  const result = await parse(accumulated)
  renderFinalContent(result.nodes)
})

Real-time Editor

Show a live preview while the user types:

editor.ts
import { autoCloseMarkdown, parse } from 'comark'
import { renderHTML } from '@comark/html'

editor.addEventListener('input', async (e) => {
  const closed = autoCloseMarkdown(e.target.value)
  const result = await parse(closed)
  preview.innerHTML = await renderHTML(result)
})

Incremental File Upload

Parse content progressively as a file uploads:

upload.ts
import { autoCloseMarkdown, parse } from 'comark'

async function uploadAndParse(file: File) {
  const chunkSize = 64 * 1024 // 64KB
  let offset = 0
  let accumulated = ''

  while (offset < file.size) {
    const chunk = file.slice(offset, offset + chunkSize)
    accumulated += await chunk.text()

    const closed = autoCloseMarkdown(accumulated)
    const result = await parse(closed)

    updateProgress({
      percent: (offset / file.size) * 100,
      preview: result.nodes
    })

    offset += chunkSize
  }

  return parse(accumulated)
}

Performance

Call autoCloseMarkdown once per chunk on the accumulated content — not on every character:

stream.ts
// ✅ Good: call once per chunk
for await (const chunk of stream) {
  accumulated += chunk
  const closed = autoCloseMarkdown(accumulated)
  render(await parse(closed))
}

// ❌ Avoid: calling for every character
for (const char of text) {
  accumulated += char
  const closed = autoCloseMarkdown(accumulated) // too frequent
  render(await parse(closed))
}