## Note for 0.3.x
Should be mostly non-breaking, the generic variant of `EventStream` has been replaced by the `Bytes` specific version which now has a bound of `Into<Bytes>`. If you define your own types you may need to add an `Into<Bytes>` implementation, most relevant `std` types already implement this. I would recommend the upgrade as it fixes a performance issue.

# `sseer` (sse - er)

## What?
A collection of utilities for getting Events out of your SSE streams

## Why?
As a bit of a learning project, in making a 'real' rust crate. Also got to learn a bit about the async ecosystem and hopefully make some improvements over the original code `reqwest-eventsource` and `eventsource-stream` by Julian Popescu

## How?
With `reqwest`:
```rust
let request = client.get("https://example.com/events");
let mut event_source = EventSource::new(request)?;
while let Some(result) = event_source.next().await { 
    // your code here :3
}
```
Make an EventSource stream over your `RequestBuilder` and it turns into a `Stream`. Or if you want to handle it yourself then hit up the `event_stream` module for a `Stream` that parses bytes into `Event`s. Or use the `parser` module if you want to parse it yourself.
```rust
let mut event_stream = EventStream::new(stream);
```

## No `std`?
Yes, without the `reqwest` feature it's all no_std. 

## Benches
Benches were run on my personal computer with 96GB of 6000Mhz DDR5 + Ryzen 9 9950X3D. We benchmark against eventsource-stream as a baseline since that's the main competitor and the inspiration I hoped to do better than, and the ratios are compared to it.

### parse_line
For these benches we just run the parser on a single line of different types. The main difference we get is on long lines such as the "big 1024-byte line" benchmark, since we use `memchr` instead of `nom` for the parser any benchmarks involving long lines are weighted in our favour.

| Line type | eventsource-stream | sseer |
|---|---|---|
| data field | 31.8ns | 5.2ns (**6.1x**) |
| comment | 18.9ns | 5.0ns (**3.8x**) |
| event field | 28.4ns | 7.3ns (**3.9x**) |
| id field | 23.1ns | 5.5ns (**4.2x**) |
| empty line | 17.1ns | 4.1ns (**4.1x**) |
| no value | 21.0ns | 5.2ns (**4.0x**) |
| no space | 25.8ns | 6.5ns (**3.9x**) |
| big 1024-byte line | 617.6ns | 12.0ns (**51x**) |

### event_stream
These benchmarks run the full stream implementation across some sample events in two conditions: events aligned to line boundaries (thus more individual stream items) and 'dumb' 128 byte chunks.
- mixed is just a sort of random mixed set of different line types, with no particularly long data lines. 512 events.
- ai_stream has its line lengths and ratios based on some responses I captured from OpenRouter, so is almost entirely made of data lines with some being quite long and some quite short. 512 events.
- evenish_distribution just takes our data, comment, event and id field lines we use in the parse_line benchmark and stacks them end to end 128 times and also splits into 128 byte chunks.

| Workload | Chunking | eventsource-stream | sseer |
|---|---|---|---|
| mixed | unaligned | 188.1µs | 103.2µs (**1.8x**) |
| mixed | line-aligned | 231.1µs | 112.9µs (**2.0x**) |
| ai_stream | unaligned | 342.9µs | 72.3µs (**4.7x**) |
| ai_stream | line-aligned | 201.2µs | 64.0µs (**3.1x**) |
| evenish_distribution | unaligned | 51.5µs | 32.8µs (**1.6x**) |

### Memory
This is available under the example with `cargo run --example memory_usage --features std`. I just use a global allocator that tracks calls to alloc and stores some stats, it's probably not perfectly accurate but hopefully it lets you get the gist. The main advantage `sseer` has over `eventsource-stream` is that we use `bytes::Bytes` as much as possible to reduce allocation, and we also avoid allocating a buffer for the data line in cases where there's only one data line. Because the stream accepts anything `Into<Bytes>` (so `bytes::Bytes` and `Vec<u8>` convert for free), we slice events straight out of the incoming chunk and avoid allocating any time a stream item completes a line, hence why the line-aligned case looks so good for us

| Workload | Chunking | Metric | eventsource-stream | sseer |
|---|---|---|---|---|
| mixed | unaligned (128B) | alloc calls | 4,753 | 535 (**8.9x**) |
| mixed | unaligned (128B) | total bytes | 188.1 KiB | 34.2 KiB (**5.5x**) |
| mixed | unaligned (128B) | peak live | 488 B | 739 B (**0.7x**) |
| mixed | line-aligned | alloc calls | 6,034 | 306 (**19.7x**) |
| mixed | line-aligned | total bytes | 92.8 KiB | 11.5 KiB (**8.1x**) |
| mixed | line-aligned | peak live | 171 B | 93 B (**1.8x**) |
| ai_stream | unaligned (128B) | alloc calls | 4,094 | 7 (**584.9x**) |
| ai_stream | unaligned (128B) | total bytes | 669.2 KiB | 7.9 KiB (**84.6x**) |
| ai_stream | unaligned (128B) | peak live | 6.7 KiB | 6.0 KiB (**1.1x**) |
| ai_stream | line-aligned | alloc calls | 3,576 | 0 (**∞**) |
| ai_stream | line-aligned | total bytes | 515.3 KiB | 0 B (**∞**) |
| ai_stream | line-aligned | peak live | 7.3 KiB | 0 B (**∞**) |