README.md

# ServerSentEvents

[![CI](https://github.com/benjreinhart/server_sent_events/actions/workflows/ci.yml/badge.svg)](https://github.com/benjreinhart/server_sent_events/actions/workflows/ci.yml)
[![License](https://img.shields.io/hexpm/l/server_sent_events.svg)](https://github.com/benjreinhart/server_sent_events/blob/main/LICENSE.md)
[![Version](https://img.shields.io/hexpm/v/server_sent_events.svg)](https://hex.pm/benjreinhart/server_sent_events)

Lightweight, ultra-fast Server Sent Event parser for Elixir.

This module fully conforms to the official [Server Sent Events specification](https://html.spec.whatwg.org/multipage/server-sent-events.html#parsing-an-event-stream) with a comprehensive [test suite](https://github.com/benjreinhart/server_sent_events/blob/main/test/server_sent_events_test.exs).

## Usage

```elixir
{events, rest} = ServerSentEvents.parse("event: event\ndata: {\"complete\":true}\n\n")
IO.inspect(events)   # [%{event: "event", data: "{\"complete\":true}\n"}]
IO.inspect(rest)     # ""
```

Parsing a chunk containing zero or more events followed by an incomplete event returns the incomplete data.

```elixir
{events, buffer} = ServerSentEvents.parse("event: event\ndata: {\"complete\":")
IO.inspect(events)   # []
IO.inspect(buffer)   # "event: event\ndata: {\"complete\":"

{events, buffer} = ServerSentEvents.parse(buffer <> "true}\n\nevent: event\ndata: {")
IO.inspect(events)   # [%{event: "event", data: "{\"complete\":true}\n"}]
IO.inspect(buffer)   # "event: event\ndata: {"

{events, rest} = ServerSentEvents.parse(buffer <> "\"key\":\"value\"}\n\n")
IO.inspect(events)   # [%{event: "event", data: "{\"key\":\"value\"}\n"}]
IO.inspect(rest)     # ""
```

This can be useful for streaming environments where a single event may not reliably arrive in one chunk.

## Real world example

AI providers like OpenAI and Anthropic stream AI generated messages using Server Sent Events.
This module can handle parsing the server sent events, returning a list of maps. For example,
we can parse a streaming response from Anthropic:

```elixir
Req.post("https://api.anthropic.com/v1/messages",
  json: request,
  into: fn {:data, data}, {req, res} ->
    buffer = Request.get_private(req, :sse_buffer, "")
    {events, buffer} = ServerSentEvents.parse(buffer <> data)
    Request.put_private(req, :sse_buffer, buffer)

    if events != [] do
      # Do something with events, e.g., send to a process consuming them.
      send(pid, {:events, events})
    end

    {:cont, {req, res}}
  end,
  headers: %{
    "x-api-key" => api_key(),
    "anthropic-version" => "2023-06-01"
  }
)
```

The first chunk from Anthropic tends to contain a couple of messages that look something like the following when parsed by this module:

```elixir
# Parsing first chunk from Anthropic
{events, ""} = ServerSentEvents.parse(chunk)
IO.inspect(events)
# [
#   %{
#     data: "{\"type\":\"message_start\",\"message\":{\"id\":\"msg_01LAFhYgKvtBB5ac5n41oyDn\",\"type\":\"message\",\"role\":\"assistant\",\"model\":\"claude-3-5-sonnet-20241022\",\"content\":[],\"stop_reason\":null,\"stop_sequence\":null,\"usage\":{\"input_tokens\":12,\"output_tokens\":2}}        }",
#     event: "message_start"
#   },
#   %{
#     data: "{\"type\":\"content_block_start\",\"index\":0,\"content_block\":{\"type\":\"text\",\"text\":\"\"}         }",
#     event: "content_block_start"
#   },
#   %{data: "{\"type\": \"ping\"}", event: "ping"},
#   %{
#     data: "{\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"text_delta\",\"text\":\"Here's\"}          }",
#     event: "content_block_delta"
#   }
# ]
```

This is typically followed by filtering out unwanted events and JSON parsing the `data` field of meaningful events.

## Installation

The package can be installed by adding `server_sent_events` to your list of dependencies in `mix.exs`:

```elixir
def deps do
  [
    {:server_sent_events, "~> 0.2.0"}
  ]
end
```