Extract the parsing logic into SSEParser, a synchronous, value-semantic incremental parser, and rebuild the async APIs as thin wrappers over it. Everything that exists in 0.1.0 keeps compiling and behaving identically; the new surface is small and non-throwing.
SSEParser, aSendablestruct (value semantics, no locking):mutating func consume(_ byte: UInt8) -> ServerSentEvent?mutating func consume(_ bytes: some Sequence<UInt8>) -> [ServerSentEvent]mutating func finish()— end of stream: applies an unterminated trailing field line (so a trailingretrystill counts, matching 0.1.0), discards the incomplete block per spec, and resets for a new stream (BOM stripped again) while retaining reconnect state.private(set) var lastEventId: String?andretryInterval: Int?— the committed reconnect state.static func parse(_ bytes: some Sequence<UInt8>) -> [ServerSentEvent]for fully buffered input.- Nothing throws: per spec, invalid UTF-8 decodes with replacement characters and malformed lines are ignored, so parsing is total.
- Generic
AsyncSequence where Element == UInt8extension providing.sse(), replacing (source-compatibly) the concreteSSEByteStreamand Darwin-onlyURLSession.AsyncBytesextensions.
ServerSentEventBlock— itsdata == nilsentinel duplicatedServerSentEventawkwardly; block-level state consumers just readlastEventId/retryIntervaloff the parser.SSEParser.Limitsand throwingconsume— speculative; can be added additively in a later release if ever needed..sseBlocks()adapters — no consumer.
- New
SSEParser.swift: internalSSELineSplitter(byte → line state machine, CR/LF/CRLF, leading BOM) +SSEParser(field parsing directly from bytes, decoding only recognized values; noString.split). AsyncServerSentEvents.AsyncIteratorbecomes a loop feeding bytes to anSSEParser, mirroring committed state into the existing publicSSEStateactor only when it changes.EventSourcereads reconnect state synchronously from its iterator's parser instead of awaiting theSSEStateactor.- Behavioral compatibility pinned: id commits only at blank line; retry applies per line (including an unterminated final line at EOF); state-only blocks observable; comment-only blocks silent; per-stream last-event-ID buffer resets across reconnects while the committed value persists.
- All 63 existing tests pass unchanged (line-splitting tests port to the sync splitter, same assertions).
- New
SSEParserTests: every existing fixture parsed sync vs async with identical events and final state; chunk-boundary invariance (sizes 1/2/3/7/16/1024); emission timing; state-only blocks;finish()reuse incl. BOM re-strip; trailing unterminatedretryline. - Clean
swift buildandswift test.