Hermolaos.Transport.MessageBuffer (Hermolaos v0.3.0)
View SourceBuffer for handling newline-delimited JSON messages.
MCP stdio transport uses newline-delimited JSON, where each JSON-RPC message is on its own line. This buffer handles:
- Accumulating partial data chunks
- Splitting on newlines to extract complete messages
- Decoding JSON messages
- Preserving incomplete data for the next chunk
Design Notes
This module is designed for high performance:
- Uses binary operations for efficient string handling
- Minimizes copying by using binary references where possible
- Handles edge cases like empty lines and partial JSON
Examples
iex> buffer = Hermolaos.Transport.MessageBuffer.new()
iex> {messages, buffer} = Hermolaos.Transport.MessageBuffer.append(buffer, ~s({"id":1}\n))
iex> messages
[%{"id" => 1}]
# Partial messages are buffered
iex> buffer = Hermolaos.Transport.MessageBuffer.new()
iex> {[], buffer} = Hermolaos.Transport.MessageBuffer.append(buffer, ~s({"id":))
iex> {[%{"id" => 1}], _} = Hermolaos.Transport.MessageBuffer.append(buffer, ~s(1}\n))
Summary
Functions
Appends data to the buffer and extracts any complete messages.
Returns the current buffer size in bytes.
Checks if the buffer has pending (incomplete) data.
Creates a new empty message buffer.
Resets the buffer, discarding any buffered data.
Returns buffer statistics.
Types
@type stats() :: %{ messages_received: non_neg_integer(), bytes_received: non_neg_integer(), parse_errors: non_neg_integer() }
Functions
Appends data to the buffer and extracts any complete messages.
Returns a tuple of {messages, new_buffer} where:
messagesis a list of decoded JSON maps (may be empty)new_bufferis the updated buffer with any incomplete data preserved
Parameters
buffer- The current buffer statedata- Binary data to append
Examples
# Single complete message
iex> buffer = Hermolaos.Transport.MessageBuffer.new()
iex> {msgs, _} = Hermolaos.Transport.MessageBuffer.append(buffer, ~s({"jsonrpc":"2.0"}\n))
iex> msgs
[%{"jsonrpc" => "2.0"}]
# Multiple messages in one chunk
iex> buffer = Hermolaos.Transport.MessageBuffer.new()
iex> {msgs, _} = Hermolaos.Transport.MessageBuffer.append(buffer, ~s({"id":1}\n{"id":2}\n))
iex> length(msgs)
2
# Partial message preserved
iex> buffer = Hermolaos.Transport.MessageBuffer.new()
iex> {[], buffer} = Hermolaos.Transport.MessageBuffer.append(buffer, ~s({"partial":))
iex> buffer.buffer
~s({"partial":)
@spec buffer_size(t()) :: non_neg_integer()
Returns the current buffer size in bytes.
Examples
iex> buffer = %Hermolaos.Transport.MessageBuffer{buffer: "12345"}
iex> Hermolaos.Transport.MessageBuffer.buffer_size(buffer)
5
Checks if the buffer has pending (incomplete) data.
Examples
iex> buffer = Hermolaos.Transport.MessageBuffer.new()
iex> Hermolaos.Transport.MessageBuffer.has_pending?(buffer)
false
iex> buffer = %Hermolaos.Transport.MessageBuffer{buffer: "pending"}
iex> Hermolaos.Transport.MessageBuffer.has_pending?(buffer)
true
@spec new() :: t()
Creates a new empty message buffer.
Examples
iex> Hermolaos.Transport.MessageBuffer.new()
%Hermolaos.Transport.MessageBuffer{buffer: "", stats: %{messages_received: 0, bytes_received: 0, parse_errors: 0}}
Resets the buffer, discarding any buffered data.
Returns any messages that could be extracted from the remaining buffer before clearing it.
Examples
iex> buffer = %Hermolaos.Transport.MessageBuffer{buffer: "partial data"}
iex> {[], new_buffer} = Hermolaos.Transport.MessageBuffer.reset(buffer)
iex> new_buffer.buffer
""
Returns buffer statistics.
Examples
iex> buffer = Hermolaos.Transport.MessageBuffer.new()
iex> Hermolaos.Transport.MessageBuffer.stats(buffer)
%{messages_received: 0, bytes_received: 0, parse_errors: 0}