perf(core): speed up event stream decoding on JavaScriptCore - #134
Conversation
The message delimiter regex used a quantified group, `(?:...){2,}`, which
JavaScriptCore (Bun, Safari) scans about 100x slower than V8. Replace it with
an equivalent pattern built from character classes, drop the redundant
lookahead from the line ending regex, and skip two per-feed costs that every
single-message chunk paid: the leading line ending regex and joining a
one-element pending buffer.
A report claimed the old regex also splits messages on a single CR under
JavaScriptCore; that did not reproduce on Bun 1.4.1/1.4.2. A Bun test now
asserts CR, LF and CRLF decoding on JavaScriptCore to guard it.
@standard-server/aws-lambda
@standard-server/core
@standard-server/fastify
@standard-server/fetch
@standard-server/node
@standard-server/peer
@standard-server/shared
commit: |
Merging this PR will not alter performance
Comparing Footnotes
|
Codecov Report✅ All modified and coverable lines are covered by tests. 📢 Thoughts on this report? Let us know! |
There was a problem hiding this comment.
✅ No new issues found.
Reviewed changes
- Delimiter regex rewritten for JavaScriptCore —
MESSAGE_DELIMITER_REGEXmoves from the quantified group(?:\r\n|\r(?!\n)|\n){2,}to/[\r\n]{3,}|\r\r|\n[\r\n]/g, andLINE_ENDING_REGEXsimplifies to/\r\n?|\n/. I verified both are behaviorally identical to the originals by brute-forcing every string over{a, \r, \n}up to length 7 (matchAllindex/length for the delimiter,splitboundaries for line endings): zero mismatches. A lone\r\n(one line ending) is still correctly excluded, and\r\r/\n\n/\n\rremain delimiters. - Decoder fast paths in
feed()— the leading[\r\n]+strip now runs only when the chunk's first code unit is CR/LF, andpending.join('')is skipped for a single buffered chunk.pending.length === 1impliespending[0] === chunk, so thebufferedsubstitution preserves the existing offset mapping. - Tests — a new Node case covers line endings on either side of a chunk with no line ending; a new
tests/bunsuite repeats the single-CR and every-delimiter-split cases on JavaScriptCore.tests/*is a workspace package withtest: bun testand CI pins Bun before the recursive test run, so the new suite executes in CI.
pnpm exec vitest run packages/core/src/event-stream/decoder.test.ts (32 passed), pnpm --filter @standard-server/core run type:check, and eslint on the changed files all pass.
DeepSeek Flash (default — pick a model for stronger reviews) | 𝕏

Event stream decoding is now much faster on Bun and Safari, where the message delimiter regex hit a slow path in JavaScriptCore's regex engine. Small messages decode about 3x faster on Bun and large ones up to 22x faster, while Node gets about 20% faster for streams that deliver one message per chunk. Decoded output is unchanged.
Performance
(?:…){2,}, not the lookahead: JavaScriptCore scans any such group ~70–100x slower than a character classCorrectness
mainon both Node and BunTesting