Skip to content

Add SSE transport + Built with eth.zig section#48

Merged
koko1123 merged 8 commits intomainfrom
feat/sse-transport-built-with
Mar 15, 2026
Merged

Add SSE transport + Built with eth.zig section#48
koko1123 merged 8 commits intomainfrom
feat/sse-transport-built-with

Conversation

@koko1123
Copy link
Contributor

@koko1123 koko1123 commented Mar 15, 2026

Summary

  • Adds src/sse_transport.zig — a spec-compliant Server-Sent Events transport, upstreamed from production use in gator-liquidators
  • Exports eth.sse_transport from root.zig
  • Adds "Built with eth.zig" section to README showing the perpcity-zig-sdk → gator-liquidators ecosystem

SSE Transport

The SseParser and subscribe/subscribeWithReconnect functions were extracted from gator-liquidators' sse_client.zig where they've been running in production. The app-specific wake logic stays in gator-liquidators; the general parsing and connection handling lives here.

Closes #33.

Test plan

  • zig build test passes (all 13 SSE parser tests included)
  • eth.sse_transport.SseParser, SseEvent, subscribe, subscribeWithReconnect are accessible via @import("eth")

Summary by CodeRabbit

  • New Features

    • Added a Server‑Sent Events (SSE) transport with streaming event parsing, last‑event ID persistence, and reconnect support with configurable exponential backoff and hooks.
    • Added reconnect support for WebSocket connections with configurable backoff and reconnect hook.
  • Documentation

    • README: added a "Built with eth.zig" section and updated transport docs to reference HTTP, WebSocket, and SSE transports.
  • Tests

    • Added extensive tests for SSE parsing, multi‑line data, id/retry handling, reconnect behavior, and WebSocket backoff logic.

@vercel
Copy link

vercel bot commented Mar 15, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
eth-zig Ready Ready Preview, Comment Mar 15, 2026 6:26pm

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Mar 15, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Added a Server‑Sent Events (SSE) transport implementation and exports, updated README, and added reconnect/backoff options to the WebSocket transport. New SSE parser, subscription APIs (with reconnect), tests, and a ws reconnect helper were introduced.

Changes

Cohort / File(s) Summary
Documentation
README.md
Inserted "Built with eth.zig" entries and updated the Modules table to list sse_transport alongside HTTP and WebSocket transports.
Module Exports
src/root.zig
Exported sse_transport (pub const sse_transport = @import("sse_transport.zig");) and added the import in the test imports.
SSE Transport
src/sse_transport.zig
New public SSE implementation: SseEvent, SseError, SseParser (line-oriented parser handling data/id/event/retry and comments), subscribe() HTTP streaming API, ReconnectOpts, subscribeWithReconnect() with exponential backoff and preserved last-event-id; extensive unit tests.
WebSocket Reconnect
src/ws_transport.zig
Added ReconnectOpts and connectWithReconnect() implementing exponential backoff, on_reconnect callback, clamping/overflow guards, and tests validating defaults and backoff behavior.

Sequence Diagram(s)

sequenceDiagram
    participant Caller as Caller
    participant Sub as subscribe()
    participant HTTP as HTTP Client
    participant Server as SSE Server
    participant Parser as SseParser
    participant CB as Callback

    Caller->>Sub: subscribe(allocator, url, extra_headers, parser, callback)
    Sub->>HTTP: Prepare headers (Accept: text/event-stream, Cache-Control: no-cache, Last-Event-ID?)
    Sub->>HTTP: GET request to url
    HTTP->>Server: Send request
    Server-->>HTTP: 200 OK + stream

    loop For each line in stream
        HTTP-->>Sub: Read line
        Sub->>Parser: feedLine(line)
        alt Event boundary (empty line)
            Parser-->>Sub: ?SseEvent
            Sub->>CB: callback(event)
        else Accumulating
            Parser-->>Sub: null
        end
    end

    HTTP-->>Sub: Connection closed / error
    Sub-->>Caller: return or error
Loading
sequenceDiagram
    participant Caller as Caller
    participant Reconnect as subscribeWithReconnect()
    participant Sub as subscribe()
    participant Backoff as Backoff Handler

    Caller->>Reconnect: subscribeWithReconnect(allocator, url, headers, opts, callback)

    loop Infinite reconnection loop
        Reconnect->>Sub: subscribe(...)
        alt Connection succeeds (clean close)
            Sub-->>Reconnect: stream closed normally
            Reconnect->>Reconnect: reset backoff to initial_backoff_ms
        else Connection fails / disconnected
            Sub-->>Reconnect: error / disconnected
        end

        Reconnect->>Backoff: on_reconnect(current_backoff_ms) [if set]
        Reconnect->>Backoff: sleep current_backoff_ms
        Reconnect->>Reconnect: double backoff (capped by max_backoff_ms or server retry)
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Poem

🐇 I nibbled bytes and stitched each line with care,

I hold the last id close while networks catch their air.
When streams go quiet I wait and backoff with a hop,
then dance through events again until the server stops.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the two main changes: adding SSE transport functionality and a 'Built with eth.zig' section to the README.
Linked Issues check ✅ Passed All core requirements from issue #33 are met: SSE parser with correct wire format handling, SseEvent struct with required fields, subscribe and subscribeWithReconnect functions with reconnection semantics and Last-Event-ID support.
Out of Scope Changes check ✅ Passed All changes are directly related to PR objectives: new sse_transport.zig implementation, root.zig exports, README updates with Built with section, and supporting ws_transport.zig enhancements for consistency.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/sse-transport-built-with
📝 Coding Plan
  • Generate coding plan for human review comments

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@README.md`:
- Around line 135-139: The fenced code block containing the tree snippet (lines
showing "eth.zig", "perpcity-zig-sdk", "gator-liquidators") needs a language tag
to satisfy MD040; update the triple-backtick opening to include a tag such as
text (e.g., change ``` to ```text) so the block is explicitly fenced with a
language identifier.

In `@src/sse_transport.zig`:
- Around line 69-71: The handler for SSE `data` lines currently overwrites
self.current_data instead of appending; change the logic where you check if
field == "data" (the block that sets self.current_data = value and the similar
block around lines 271-278) to append newline-separated values: if
self.current_data is empty set it to value, otherwise set it to
self.current_data ++ "\n" ++ value (or the Zig equivalent for concatenation), so
multiple `data:` lines are joined with '\n' into a single event payload.
- Line 72: The SSE implementation currently ignores the "id" and "retry" fields
and reconnects without sending Last-Event-ID or adjusting the backoff; update
the SSE parser (the code that parses event fields such as "id" and "retry") to
store the last seen event id and the server-specified retry value, include
Last-Event-ID in the HTTP reconnect header when present, and use the parsed
retry value to control reconnect delay in the reconnect loop (where
disconnection/reconnect is handled). Ensure the stored last-event-id is
persisted across reconnect attempts and that the reconnect/backoff code consults
the server-provided retry before falling back to a client default.
- Around line 10-15: The SseEvent struct is missing the required id field and
its fields don't match the stated contract (id, event, data); update the struct
SseEvent to include an optional id field (e.g., id: ?[]const u8 = null) and
align field names to the contract (use event instead of event_type and keep
data: ?[]const u8 = null), then update any code that constructs or reads
SseEvent to use the new id and event names so SSE can be handled losslessly and
support reconnect continuity.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: e637759e-a156-4b88-b448-06a2164ccd5d

📥 Commits

Reviewing files that changed from the base of the PR and between d369bd0 and b4ed9bb.

📒 Files selected for processing (3)
  • README.md
  • src/root.zig
  • src/sse_transport.zig

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/sse_transport.zig`:
- Around line 76-83: The SSE parser currently emits callbacks for frames that
contain only `id:` or `event:` and only updates `last_event_id` when `id_len >
0`, which prevents `id:` from clearing the last ID and causes spurious
dispatched events; modify the logic in the parsing function that checks
`self.event_len`, `self.data_len`, and `self.id_len` so that you do NOT dispatch
any callbacks when `self.data_len == 0` (i.e., non-data frames should not be
emitted), but still apply the `id:` update unconditionally (update
`self.last_event_id_buf`/`self.last_event_id_len` even when `self.id_len == 0`
to clear the prior ID); apply the same change in the corresponding code path
around the other occurrence (the block referenced at lines ~124-127) to ensure
consistent SSE resume semantics.
- Around line 103-135: The parser currently ignores lines without a colon
because it only executes the "field:value" branch when std.mem.indexOf finds a
colon; change the logic so lines lacking ":" are treated as a valid field with
an empty value (i.e., set field = trimmed and value = an empty slice) before the
existing handling; update the colon-trimming and optional-leading-space handling
to only strip a leading space when value.len > 0, and keep the rest of the
existing branches that copy into self.event_buf/self.data_buf/self.id_buf or
parse self.retry_ms unchanged so lines like "data" are preserved and appended
correctly.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 83e777b0-5623-45be-84df-1e4cd7091d1f

📥 Commits

Reviewing files that changed from the base of the PR and between b4ed9bb and 13993c6.

📒 Files selected for processing (2)
  • README.md
  • src/sse_transport.zig
🚧 Files skipped from review as they are similar to previous changes (1)
  • README.md

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (4)
src/sse_transport.zig (4)

194-195: Remove dead code.

prefix is declared and immediately suppressed with _ = prefix. This appears to be leftover from refactoring.

🧹 Proposed fix
     if (parser.lastEventId()) |last_id| {
-        const prefix = ""; // value is the id itself; name is the header name
-        _ = prefix;
         `@memcpy`(last_id_header_buf[0..last_id.len], last_id);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/sse_transport.zig` around lines 194 - 195, The local constant prefix and
its suppression (_ = prefix) are dead code; remove the declaration "const prefix
= \"\";" and the suppression line "_ = prefix;" from sse_transport.zig (around
the code that sets the header name/id) so there is no unused variable; ensure no
other code relies on the symbol prefix before committing.

22-25: ConnectionFailed error variant appears unused.

subscribe() returns error.BadStatus for non-OK responses but propagates underlying connection errors directly rather than wrapping them as ConnectionFailed. Consider either using this variant or removing it to avoid confusion.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/sse_transport.zig` around lines 22 - 25, SseError.ConnectionFailed is
defined but never used; update the subscribe() implementation to wrap underlying
connection errors as error.SseError.ConnectionFailed (preserving original error
info) instead of propagating raw errors, or remove the ConnectionFailed variant
from the SseError declaration and adjust any callers expecting that variant;
look for the SseError type and the subscribe() function to apply the change and
ensure error.BadStatus remains used for non-OK HTTP responses.

46-56: Silent truncation when event data exceeds 64KB buffer.

If SSE data exceeds data_buf.len (65536 bytes), lines 128-131 silently truncate without any indication to the caller. For MEV-Share events this is likely sufficient, but consider either documenting this limit prominently or returning an error/flag when truncation occurs.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/sse_transport.zig` around lines 46 - 56, The SSE parser currently
silently truncates event payloads when data_buf (65536) is exceeded; update the
parsing logic that writes into data_buf/data_len (and any code paths using
has_id/event_buf/id_buf) to detect buffer overflow and signal it instead of
truncating silently: add a boolean flag (e.g., data_truncated) or return an
explicit error from the parse/consume function when data_len would exceed
data_buf.len, set that flag/return the error at the point of overflow detection,
and propagate/handle it in callers so callers can log/handle oversized events or
fallback to a documented behavior; ensure the new flag/error is
initialized/cleared when starting a new event block alongside has_id and
data_len.

286-288: Potential integer overflow if initial_backoff_ms is very large.

backoff_ms * 2 is computed before @min, so if a caller provides an initial_backoff_ms near u64 max, multiplication overflows before clamping. With defaults this is safe, but consider defensive ordering:

🛡️ Safer ordering
         if (parser.retry_ms == null) {
-            backoff_ms = `@min`(backoff_ms * 2, opts.max_backoff_ms);
+            backoff_ms = if (backoff_ms > opts.max_backoff_ms / 2)
+                opts.max_backoff_ms
+            else
+                backoff_ms * 2;
         }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/sse_transport.zig` around lines 286 - 288, The multiplication backoff_ms
* 2 can overflow before `@min` clamps it; change the logic in the block that
checks parser.retry_ms to avoid multiplying first: compute a safe doubled value
by comparing backoff_ms to opts.max_backoff_ms/2 (or checking against the u64
half) and if backoff_ms > opts.max_backoff_ms/2 set backoff_ms =
opts.max_backoff_ms else set backoff_ms = backoff_ms * 2; reference variables:
parser.retry_ms, backoff_ms, opts.max_backoff_ms.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/sse_transport.zig`:
- Around line 368-374: The test "SseParser handles event with no data" is wrong:
per SSE spec SseParser.feedLine returns null when no data is present; update the
test to expect null instead of unwrapping—modify the assertions in the test to
assert parser.feedLine("") == null (or use std.testing.expectNull) and remove
the subsequent unwraps/expectEqualStrings/expect for evt.data; this keeps
SseParser and feedLine unchanged.
- Around line 173-239: The subscribe function's streaming read loop can block
indefinitely because reader.takeDelimiterInclusive is called in a tight
while(true) with no timeout; update subscribe to guard streaming reads with a
read timeout (e.g., use std.posix.select or a socket deadline wrapper) around
calls to response.reader()/reader.takeDelimiterInclusive so a stalled connection
returns a distinct error (or closes/retries) instead of hanging; ensure you
reference the same symbols (subscribe, client.request/.sendBodiless,
response.reader, reader.takeDelimiterInclusive) and either accept a timeout
parameter or use a configured constant, and propagate/handle the timeout error
where the current EndOfStream/err switch is done.

---

Nitpick comments:
In `@src/sse_transport.zig`:
- Around line 194-195: The local constant prefix and its suppression (_ =
prefix) are dead code; remove the declaration "const prefix = \"\";" and the
suppression line "_ = prefix;" from sse_transport.zig (around the code that sets
the header name/id) so there is no unused variable; ensure no other code relies
on the symbol prefix before committing.
- Around line 22-25: SseError.ConnectionFailed is defined but never used; update
the subscribe() implementation to wrap underlying connection errors as
error.SseError.ConnectionFailed (preserving original error info) instead of
propagating raw errors, or remove the ConnectionFailed variant from the SseError
declaration and adjust any callers expecting that variant; look for the SseError
type and the subscribe() function to apply the change and ensure error.BadStatus
remains used for non-OK HTTP responses.
- Around line 46-56: The SSE parser currently silently truncates event payloads
when data_buf (65536) is exceeded; update the parsing logic that writes into
data_buf/data_len (and any code paths using has_id/event_buf/id_buf) to detect
buffer overflow and signal it instead of truncating silently: add a boolean flag
(e.g., data_truncated) or return an explicit error from the parse/consume
function when data_len would exceed data_buf.len, set that flag/return the error
at the point of overflow detection, and propagate/handle it in callers so
callers can log/handle oversized events or fallback to a documented behavior;
ensure the new flag/error is initialized/cleared when starting a new event block
alongside has_id and data_len.
- Around line 286-288: The multiplication backoff_ms * 2 can overflow before
`@min` clamps it; change the logic in the block that checks parser.retry_ms to
avoid multiplying first: compute a safe doubled value by comparing backoff_ms to
opts.max_backoff_ms/2 (or checking against the u64 half) and if backoff_ms >
opts.max_backoff_ms/2 set backoff_ms = opts.max_backoff_ms else set backoff_ms =
backoff_ms * 2; reference variables: parser.retry_ms, backoff_ms,
opts.max_backoff_ms.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 6d8cd559-be60-46ec-a458-6bec41e9c51a

📥 Commits

Reviewing files that changed from the base of the PR and between 13993c6 and 6c315ab.

📒 Files selected for processing (1)
  • src/sse_transport.zig

Comment on lines +173 to +239
pub fn subscribe(
allocator: std.mem.Allocator,
url: []const u8,
extra_headers: []const std.http.Header,
parser: *SseParser,
callback: *const fn (event: SseEvent) void,
) !void {
var client = std.http.Client{ .allocator = allocator };
defer client.deinit();

const uri = try std.Uri.parse(url);

// Build header list: base SSE headers + Last-Event-ID (if any) + caller extras.
const base_headers: []const std.http.Header = &.{
.{ .name = "Accept", .value = "text/event-stream" },
.{ .name = "Cache-Control", .value = "no-cache" },
};

var last_id_header_buf: [512 + 20]u8 = undefined; // "Last-Event-ID: " + id
var id_headers: []const std.http.Header = &.{};
if (parser.lastEventId()) |last_id| {
const prefix = ""; // value is the id itself; name is the header name
_ = prefix;
@memcpy(last_id_header_buf[0..last_id.len], last_id);
id_headers = &.{.{
.name = "Last-Event-ID",
.value = last_id_header_buf[0..last_id.len],
}};
}

const all_headers = try std.mem.concat(
allocator,
std.http.Header,
&.{ base_headers, id_headers, extra_headers },
);
defer allocator.free(all_headers);

var req = try client.request(.GET, uri, .{ .extra_headers = all_headers });
defer req.deinit();

try req.sendBodiless();

var redirect_buf: [4096]u8 = undefined;
var response = try req.receiveHead(&redirect_buf);

if (response.head.status != .ok) {
return error.BadStatus;
}

// Reset per-event state but preserve last_event_id and retry_ms.
parser.reset();

var transfer_buf: [8192]u8 = undefined;
const reader = response.reader(&transfer_buf);

while (true) {
const line_with_nl = reader.takeDelimiterInclusive('\n') catch |err| switch (err) {
error.EndOfStream => return, // normal close
else => return err,
};
const line = line_with_nl[0 .. line_with_nl.len - 1];

if (parser.feedLine(line)) |evt| {
callback(evt);
}
}
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check Zig version in build.zig or any version constraints
fd -t f 'build.zig' --exec cat {} \; 2>/dev/null | head -50

# Search for any existing timeout handling or std.http usage patterns
rg -n 'std\.http' --type zig -C 3

Repository: StrobeLabs/eth.zig

Length of output: 4193


🌐 Web query:

Zig std.http.Client API request sendBodiless receiveHead reader 0.12 0.13

💡 Result:

In Zig 0.12.x / 0.13.x, std.http.Client.Request does not use the newer “split” API (sendBodiless, receiveHead). Instead, the flow is:

  1. req.send()
  2. (optional) req.writeAll(payload)
  3. req.finish()
  4. req.wait()
  5. read body via req.reader() (after wait) (raw.githubusercontent.com)

That’s why code/examples mentioning sendBodiless / receiveHead won’t match 0.12/0.13—they correspond to the newer request API where you can explicitly do sendBodiless() and then receiveHead(redirect_buffer) (git.medv.io).

0.12 / 0.13 minimal GET example (no request body)

var req = try client.open(.GET, uri, .{
    .server_header_buffer = &server_header_buf,
});
defer req.deinit();

try req.send();
try req.finish();
try req.wait();

var r = req.reader();
const n = try r.readAll(&body_buf);

(For POST/PUT: set the request transfer info as needed, send(), writeAll(payload), finish(), wait(), then read with reader().) (raw.githubusercontent.com)

Citations:


🏁 Script executed:

# View the http_transport.zig file to see actual std.http.Client usage
wc -l src/http_transport.zig && echo "---" && sed -n '1,100p' src/http_transport.zig

# Search for timeout-related code or patterns
rg -i 'timeout|deadline|sleep|std\.time' --type zig -B 2 -A 2

Repository: StrobeLabs/eth.zig

Length of output: 5757


🏁 Script executed:

# Check if response.reader() or similar patterns are used elsewhere
rg -n 'response\.reader|takeDelimiter|receiveHead|sendBodiless' --type zig -B 2 -A 2

Repository: StrobeLabs/eth.zig

Length of output: 970


Consider adding timeout protection to the SSE stream reader.

The HTTP Client API calls (sendBodiless(), receiveHead(), response.reader(), takeDelimiterInclusive()) are compatible with the target Zig 0.15.2. However, the while (true) loop at line 228 has no timeout on stream reads. If the server hangs or the network partition persists, this will block indefinitely. For production MEV-Share usage, consider adding a read timeout (e.g., via std.posix.select or a wrapper) or documenting this limitation.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/sse_transport.zig` around lines 173 - 239, The subscribe function's
streaming read loop can block indefinitely because reader.takeDelimiterInclusive
is called in a tight while(true) with no timeout; update subscribe to guard
streaming reads with a read timeout (e.g., use std.posix.select or a socket
deadline wrapper) around calls to
response.reader()/reader.takeDelimiterInclusive so a stalled connection returns
a distinct error (or closes/retries) instead of hanging; ensure you reference
the same symbols (subscribe, client.request/.sendBodiless, response.reader,
reader.takeDelimiterInclusive) and either accept a timeout parameter or use a
configured constant, and propagate/handle the timeout error where the current
EndOfStream/err switch is done.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

♻️ Duplicate comments (1)
src/sse_transport.zig (1)

12-20: ⚠️ Potential issue | 🟠 Major

Align SseEvent.data with the promised public API shape.

SseEvent.data is optional at Line 19, but the issue objective defines it as required (data: []const u8). This creates avoidable nullable handling at call sites and diverges from the expected contract.

Suggested API alignment
 pub const SseEvent = struct {
@@
-    data: ?[]const u8 = null,
+    data: []const u8 = "",
 };
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/sse_transport.zig` around lines 12 - 20, SseEvent.data is declared
nullable but the public API promises a non-nullable array; change the field in
the SseEvent struct from "data: ?[]const u8 = null" to a required "data: []const
u8" and update any constructors/initializations that create SseEvent (search for
uses of SseEvent literal or field assignments) to supply an empty slice (e.g.,
"") or the appropriate data slice so callers no longer need nullable handling;
ensure any code that merged multiple data lines still produces a []const u8
result before constructing or assigning to SseEvent.data.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/sse_transport.zig`:
- Around line 93-97: The code currently gates dispatching an SseEvent on
self.data_len > 0 which drops valid events that have an explicitly empty data:
line; change the logic to track presence of any data: field instead of byte
length (e.g., add or reuse a boolean like self.has_data or set a flag when
parsing a data: line), set that flag when a data: line is seen even if zero
bytes, and use that flag in the creation of evt (SseEvent .data should be
self.data_buf[0..self.data_len] which may be an empty slice) so empty-data
events are dispatched; update parsing code paths that previously only
incremented data_len (where data lines are handled) to set the presence flag as
well and replace checks of self.data_len > 0 with the presence flag when
deciding to construct evt.
- Around line 285-288: The code multiplies delay by std.time.ns_per_ms which can
overflow for very large parser.retry_ms/backoff_ms; update the reconnect path in
src/sse_transport.zig to perform an overflow-safe conversion before calling
std.Thread.sleep: compute ns_per_ms = std.time.ns_per_ms and use a checked
multiplication (e.g., std.math.mulWithOverflow or explicit bound check against
(`@as`(u64).max / ns_per_ms)) to produce a capped or error-handled ns_delay, call
opts.on_reconnect with the original delay value as before, and pass the safe
ns_delay to std.Thread.sleep; reference symbols: delay, parser.retry_ms,
backoff_ms, opts.on_reconnect, std.time.ns_per_ms, std.Thread.sleep.

---

Duplicate comments:
In `@src/sse_transport.zig`:
- Around line 12-20: SseEvent.data is declared nullable but the public API
promises a non-nullable array; change the field in the SseEvent struct from
"data: ?[]const u8 = null" to a required "data: []const u8" and update any
constructors/initializations that create SseEvent (search for uses of SseEvent
literal or field assignments) to supply an empty slice (e.g., "") or the
appropriate data slice so callers no longer need nullable handling; ensure any
code that merged multiple data lines still produces a []const u8 result before
constructing or assigning to SseEvent.data.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 47d9881d-3ff5-4fa5-81d4-7f1f99899122

📥 Commits

Reviewing files that changed from the base of the PR and between 6c315ab and 4896fb6.

📒 Files selected for processing (1)
  • src/sse_transport.zig

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/ws_transport.zig`:
- Around line 681-684: The backoff is only reset when callback(&transport)
returns success, which leaves backoff high if the session later errors; change
the logic to reset backoff when a connection/session is successfully established
rather than only on a clean callback return: move or add the assignment
backoff_ms = opts.initial_backoff_ms to the code path immediately after the
successful connection/setup and before invoking callback(&transport) (or
immediately after confirming the transport became healthy), referencing the
existing symbols callback(&transport), backoff_ms, and opts.initial_backoff_ms;
apply the same change across the surrounding block covering the callback/session
loop (lines ~677-695) so any successful session establishment clears the
backoff.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 8410b091-8742-4600-94ae-5f22be0f7ab5

📥 Commits

Reviewing files that changed from the base of the PR and between 4896fb6 and a7c3659.

📒 Files selected for processing (1)
  • src/ws_transport.zig

@koko1123
Copy link
Contributor Author

@coderabbitai review

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Mar 15, 2026

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/sse_transport.zig`:
- Around line 455-458: The test attempts to optional-unwrap ev2.data but
SseEvent.data is a non-optional []const u8; remove the optional unwrap and
compare directly (use ev2.data instead of ev2.data.?) in the assertions that
follow parser.feedLine("") so the code compiles; locate the test lines
referencing parser.feedLine, ev2.event and ev2.data and update the
expectEqualStrings call to pass ev2.data as a non-optional value.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 7194fa08-6e67-4b97-ba13-8c7c0f0f87e0

📥 Commits

Reviewing files that changed from the base of the PR and between 4896fb6 and c4493ef.

📒 Files selected for processing (4)
  • README.md
  • src/root.zig
  • src/sse_transport.zig
  • src/ws_transport.zig
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/root.zig

@koko1123 koko1123 merged commit 7670612 into main Mar 15, 2026
10 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

SSE transport for MEV-Share event streaming

1 participant