toon-format/toon · error · Error
Incomplete event stream: unclosed objects or arrays
Error message
Incomplete event stream: unclosed objects or arrays
What it means
After consuming all events, the builder's container stack still holds entries, meaning some `startObject`/`startArray` events never got matching `endObject`/`endArray` events. The library requires a complete, balanced event stream to produce a JSON value and refuses to return a partially built structure.
Source
Thrown at packages/toon/src/decode/event-builder.ts:176
if (parent.currentKey === undefined) {
throw new Error('Primitive event without preceding key in object')
}
setOwnProperty(parent.obj, parent.currentKey, event.value)
parent.currentKey = undefined
}
else if (parent.type === 'array') {
parent.arr.push(event.value)
}
}
break
}
}
}
function finalizeState(state: BuildState): JsonValue {
if (state.stack.length !== 0) {
throw new Error('Incomplete event stream: unclosed objects or arrays')
}
if (state.root === undefined) {
throw new Error('No root value built from events')
}
return state.root
}
// #endregion
View on GitHub (pinned to 604eac266e)
Solutions
- Ensure the producer emits matching endObject/endArray events for every start event
- Verify the input source is fully consumed (don't truncate the TOON document or stream)
- If handling partial data, wrap the builder call in try/catch and treat it as incomplete input
- Regenerate the events from the full input rather than hand-splicing event sequences
Example fix
// before: events = [{type:'startObject'},{type:'key',key:'a'},{type:'primitive',value:1}] // no endObject
// after: events = [{type:'startObject'},{type:'key',key:'a'},{type:'primitive',value:1},{type:'endObject'}] Defensive patterns
Strategy: validation
Validate before calling
function isBalanced(events) {
let depth = 0
for (const e of events) {
if (e.type === 'startObject' || e.type === 'startArray') depth++
if (e.type === 'endObject' || e.type === 'endArray') depth--
if (depth < 0) return false
}
return depth === 0
} Try / catch
try {
const value = buildValueFromEvents(events)
} catch (err) {
if (err instanceof Error && err.message.includes('Incomplete event stream')) {
// treat as partial data; request/re-read the full source
} else throw err
} Prevention
- Consume the entire event iterable; never break early
- Ensure producers emit end events even on error paths (try/finally)
- Verify network streams are fully received before decoding
- Don't hand-splice event arrays for testing; regenerate from full input
When it happens
Trigger: Feeding buildValueFromEvents or buildValueFromEventsAsync an event iterable that ends while objects/arrays are still open — e.g. truncation during streaming, a producer that omits end events on error paths, or breaking out of the source iteration early so end events are lost.
Common situations: Streaming decode from a network that drops the tail of the data; custom event pipelines that swallow end events on exceptions; manually slicing an event array for debugging and feeding the slice to the builder.
Related errors
- Primitive event without preceding key in object
- No root value built from events
- Top-level document must start with a key-value or array-head
- Indentation depth jump: expected depth ${parentDepth + 1}, b
- Unexpected bare token line outside root primitive position
AI-assisted analysis of toon-format/toon@604eac266e (2026-08-31).
Data as JSON: /api/errors/a329287e00c774c3.
Report an issue: GitHub.