fix(peer): reject event-stream meta that withEventMeta would throw on - #97
Conversation
isPeerEventStreamMessage accepted any finite retry and any id/comment string, but the consumer re-validates those fields via withEventMeta with stricter rules (non-negative integer retry, no CR/LF). Messages that passed the gate could then throw EventStreamEncoderError mid-stream. Core now exports isEventStreamMessageId/Retry/Comment predicates, which the encoder asserts and the peer validator both use, so the rules can't drift.
@standard-server/aws-lambda
@standard-server/core
@standard-server/fastify
@standard-server/fetch
@standard-server/node
@standard-server/peer
@standard-server/shared
commit: |
Codecov Report✅ All modified and coverable lines are covered by tests. 📢 Thoughts on this report? Let us know! |
Merging this PR will not alter performance
Comparing Footnotes
|
There was a problem hiding this comment.
ℹ️ No critical issues — one minor documentation gap.
Reviewed changes
- Shared predicates in core —
isEventStreamMessageId,isEventStreamMessageRetry(integer>= 0), andisEventStreamMessageCommentare exported, and the matchingassertEventStreamMessage*functions now delegate to them instead of duplicating the checks. - Peer validator synced with
withEventMeta—isPeerEventStreamMessagenow rejectsid/comments containing CR/LF and non-integer or negativeretry(previouslyNumber.isFinite), so malformed meta is dropped at the transport boundary instead of throwingEventStreamEncoderErrorinside the receivingfor await. - Tests — predicate cases in core plus peer rejection cases for negative/fractional
retryand line-brokenid/comment; the peer cases fail against the old validator.
I traced the consumer (packages/peer/src/event-stream.ts:20,38) and the SSE decoder path (packages/core/src/event-stream/decoder.ts:55) — the latter can only produce non-negative integer retry and line-break-free id/comments, so the tightened predicates don't create false rejections there. pnpm exec vitest run packages/core/src/event-stream packages/peer/src/validators.test.ts passes (117 tests).
ℹ️ Nitpicks
- The "Errors and low-level assertions" export list at
packages/core/README.md:446still names only the fourassertEventStreamMessage*helpers; the three new public predicates could be added there so the list stays current.
DeepSeek Flash (default — pick a model for stronger reviews) | 𝕏

isPeerEventStreamMessageaccepted event-stream messages that the peer consumer then rejected. Aretryof-1or1.5, or anid/comment containing CR/LF, passed the validator, then threwEventStreamEncoderErrorfromnext()when the consumer attached meta to an object payload. That ended the receiver'sfor awaitloop. Such messages are now rejected up front byisClientPeerSendMessage/isServerPeerSendMessage, so callers drop them instead of losing the stream.Fixes
retry,idandcommentson received event-stream messages no longer crash the consuming iterator.isEventStreamMessageId/isEventStreamMessageRetry/isEventStreamMessageComment), so the validator can't drift from the encoder asserts again.Notes for reviewers
@standard-server/core.assertEventStreamMessageId/assertEventStreamMessageCommentnow also throw on non-string input (only reachable from untyped JS callers; previously the value was coerced to a string).withEventMeta.Testing
tscand eslint clean.