MDC Syntax Logo
API Reference

Auto-Close API

The auto-close API provides utilities for handling incomplete or partial MDC content, which is especially useful for streaming scenarios where content arrives incrementally.

Overview

When parsing streaming content (like AI-generated text), the content is often incomplete with unclosed markdown syntax or MDC components. The auto-close utilities automatically detect and close these unclosed elements, ensuring valid parsing at every chunk.

Functions

autoCloseMarkdown(markdown: string): string

Automatically closes unclosed markdown inline syntax and MDC components.

Parameters:

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

Returns: The markdown with auto-closed syntax

Example:

import { autoCloseMarkdown } from 'mdc-syntax'

// Auto-close bold syntax
const partial = '**bold text'
const closed = autoCloseMarkdown(partial)
console.log(closed) // '**bold text**'

// Auto-close italic syntax
const italic = '*italic text'
const closedItalic = autoCloseMarkdown(italic)
console.log(closedItalic) // '*italic text*'

// Auto-close MDC component
const component = '::alert{type="info"}\nImportant message'
const closedComponent = autoCloseMarkdown(component)
console.log(closedComponent)
// '::alert{type="info"}\nImportant message\n::'

// Auto-close nested components
const nested = ':::parent\n::child\nContent'
const closedNested = autoCloseMarkdown(nested)
console.log(closedNested)
// ':::parent\n::child\nContent\n::\n:::'

Streaming Integration

The auto-close utilities are automatically integrated into parseStreamIncremental, so you don't need to call them manually:

import { parseStreamIncremental } from 'mdc-syntax/stream'

// Auto-close is automatically applied!
for await (const result of parseStreamIncremental(stream)) {
  if (!result.isComplete) {
    // Each intermediate result has auto-closed syntax
    renderContent(result.body)
  }
}

However, you can use them manually if needed:

import { autoCloseMarkdown, parse } from 'mdc-syntax'

async function* parseStreamWithManualAutoClose(stream) {
  let accumulated = ''

  for await (const chunk of stream) {
    accumulated += chunk.toString()

    // Manually apply auto-close
    const closed = autoCloseMarkdown(accumulated)
    const parsed = parse(closed)

    yield parsed
  }
}

Supported Syntax

Inline Markdown

The auto-close API handles these inline markdown syntaxes:

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

Example:

const examples = [
  '**bold',
  '*italic',
  '`code',
  '~~strike',
  '[link](url',
  '![image](url'
]

examples.forEach((text) => {
  console.log(autoCloseMarkdown(text))
})
// **bold**
// *italic*
// `code`
// ~~strike~~
// [link](url)
// ![image](url)

MDC Components

Block components are automatically closed based on their marker count:

// Single marker (not a block component, no closing needed)
autoCloseMarkdown(':inline-component')
// ':inline-component'

// 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:

// Props
autoCloseMarkdown('::alert{type="info" title="Note"}')
// '::alert{type="info" title="Note"}\n::'

// Content with props
autoCloseMarkdown('::card{class="primary"}\nContent here')
// '::card{class="primary"}\nContent here\n::'

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

Use Cases

1. AI Streaming Content

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

import { autoCloseMarkdown, parse } from 'mdc-syntax'

let accumulated = ''

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

  // Auto-close before parsing to ensure valid AST
  const closed = autoCloseMarkdown(accumulated)
  const parsed = parse(closed)

  // Update UI with valid parsed content
  renderContent(parsed.body)
})

socket.on('end', () => {
  // Final parse with complete content
  const final = parse(accumulated)
  renderFinalContent(final.body)
})

2. Real-time Markdown Editor

Show live preview while user types:

import { autoCloseMarkdown, parse } from 'mdc-syntax'

const editor = document.querySelector('#markdown-editor')
const preview = document.querySelector('#preview')

editor.addEventListener('input', (e) => {
  const content = e.target.value

  // Auto-close for valid preview even with incomplete syntax
  const closed = autoCloseMarkdown(content)
  const parsed = parse(closed)

  // Render preview
  preview.innerHTML = renderToHTML(parsed.body)
})

3. Incremental File Upload

Parse content as file uploads:

import { autoCloseMarkdown, parse } from 'mdc-syntax'

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

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

    // Parse with auto-close for progress display
    const closed = autoCloseMarkdown(accumulated)
    const parsed = parse(closed)

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

    offset += chunkSize
  }

  // Final parse
  return parse(accumulated)
}

Advanced Examples

Performance Considerations

  1. Auto-close is fast: The algorithm is optimized and runs in O(n) time where n is the length of the content.
  2. Call once per chunk: In streaming scenarios, call autoCloseMarkdown once per chunk, not for every character.
// Good: Call once per chunk
for await (const chunk of stream) {
  accumulated += chunk
  const closed = autoCloseMarkdown(accumulated)
  render(parse(closed))
}

// Avoid: Calling for every character
for (const char of text) {
  accumulated += char
  const closed = autoCloseMarkdown(accumulated) // Too frequent!
  render(parse(closed))
}

Limitations

  1. Complex nested structures: Very deeply nested components (>10 levels) may have edge cases.
  2. Custom syntax: Only standard MDC and markdown syntax is supported. Custom extensions need separate handling.
  3. Ambiguous cases: Some ambiguous syntax may be closed in unexpected ways:
    // This could be interpreted multiple ways
    autoCloseMarkdown('**bold *italic')
    // Currently closes as: '**bold *italic***'
    

See Also