Auto-Close API
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:
| Syntax | Example | Auto-closed |
|---|---|---|
| Bold | **text | **text** |
| Italic | *text | *text* |
| Code | `code | `code` |
| Strikethrough | ~~text | ~~text~~ |
| Link | [text](url | [text](url) |
| Image |  |
Example:
const examples = [
'**bold',
'*italic',
'`code',
'~~strike',
'[link](url',
' => {
console.log(autoCloseMarkdown(text))
})
// **bold**
// *italic*
// `code`
// ~~strike~~
// [link](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
- Auto-close is fast: The algorithm is optimized and runs in O(n) time where n is the length of the content.
- Call once per chunk: In streaming scenarios, call
autoCloseMarkdownonce 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
- Complex nested structures: Very deeply nested components (>10 levels) may have edge cases.
- Custom syntax: Only standard MDC and markdown syntax is supported. Custom extensions need separate handling.
- 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
- Parse API - Main parsing functions
- Vue Renderer - Vue integration
- Examples - Real-world usage patterns