toon-format/toon · error · ToonDecodeError
Top-level document must start with a key-value or array-head
Error message
Top-level document must start with a key-value or array-header line
What it means
TOON documents must begin with either a single primitive value or a top-level `key: value` / `[N]:` array-header line. decodeDocument throws this when the first line is neither a parseable primitive nor a key-value line while the stream is not a one-line primitive document, so no valid root can be established. It is a structural error in the input text, not a runtime state problem.
Source
Thrown at packages/toon/src/decode/decoders.ts:79
const headerInfo = withLine(first, () => resolveArrayHeader(parseArrayHeaderLine(first.content, DEFAULT_DELIMITER), options.strict))
if (headerInfo) {
yield* readLine(reader)
yield* decodeArrayFromHeader(headerInfo.header, headerInfo.inlineValues, reader, 0, options, first)
yield* assertFullyConsumed(reader, options.strict)
return
}
}
yield* readLine(reader)
const following = yield* peekLine(reader)
const hasMore = following !== undefined
if (!hasMore && !isKeyValueLine(first)) {
yield { type: 'primitive', value: withLine(first, () => parsePrimitiveToken(first.content)) }
return
}
if (!isKeyValueLine(first) && following?.depth === 0) {
throw new ToonDecodeError(
'Top-level document must start with a key-value or array-header line',
{ line: first.lineNumber, source: first.raw },
)
}
const rootSeenKeys = options.strict ? new Set<string>() : undefined
yield { type: 'startObject' }
yield* decodeKeyValue(first, reader, 0, options, rootSeenKeys)
while (true) {
const line = yield* peekLine(reader)
if (!line) {
break
}
if (line.depth !== 0) {
if (options.strict) {
throw overIndentedLineError(line, 0)View on GitHub (pinned to 604eac266e)
Solutions
- Start the document with a `key: value` line or an `[N]{...}:` array header at depth 0
- If the input is a single bare value, ensure it is the only line so the primitive-root path is taken
- Check for missing/renamed root key or accidental leading blank/garbage lines
- Wrap the fragment in a root key before decoding
Example fix
// before "just a bare value" another line // after root: "just a bare value" nested: another: line
Defensive patterns
Strategy: validation
Validate before calling
function looksLikeToonDocument(text: string): boolean {
const first = text.split('\n', 1)[0]?.trim() ?? ''
if (!first) return false
if (first.startsWith('- ')) return false
return /^[^:\s][^:]*:/.test(first) || /^\[\d+\]/.test(first)
}
if (!looksLikeToonDocument(input)) throw new Error('Input is not a rooted TOON document') Type guard
function isRootedToon(text: string): boolean {
const first = text.split('\n', 1)[0] ?? ''
return /:\s*/.test(first) || /^\[\d+\]/.test(first)
} Try / catch
try {
const doc = decode(input, { strict: true })
} catch (e) {
if (e instanceof ToonDecodeError && /Top-level document/.test(e.message)) {
// recover: wrap fragment or surface a user-facing parse error
} else throw e
} Prevention
- Always emit documents through the TOON encoder rather than string concatenation
- Validate the first line starts with `key:` or `[N]:` before decoding
- Never paste bare object fragments without wrapping in a root key
When it happens
Trigger: Calling decodeStreamSync/decodeStream (or decode) on input whose first line is a bare token that contains no unquoted colon and is not a `- ` list item, and where a following line exists at depth 0 (so it cannot be treated as a lone root primitive).
Common situations: Hand-written TOON files that start with a stray value or comment-like text; truncated files where the opening key was lost; pasting only a nested fragment (e.g. the body of an object) instead of the whole document; tooling that emits YAML with the root key stripped.
Related errors
- Unexpected bare token line outside root primitive position
- Indentation depth jump: expected depth ${parentDepth + 1}, b
- Unexpected content after the document root
- Duplicate sibling key "${key}"
- Unexpected indentation inside keyed tabular object
AI-assisted analysis of toon-format/toon@604eac266e (2026-08-31).
Data as JSON: /api/errors/4363a4d9fe141d9d.
Report an issue: GitHub.