HarperFast / HarperFast/harper

Forward Last-Event-ID into Resource methods for replay-capable SSE streams

Open
#515 0 comments 0 reactions 0 assignees View on GitHub
enhancement
Dominant language
JavaScript
Stars
89
Forks
10
Avg merge
2d 2h
Merged PRs (30d)
205

Description

## Context

SSE clients reconnecting after a network blip send `Last-Event-ID` (per the SSE spec, also automatic in `EventSource`). Replay-aware streams should resume from that cursor — Harper's existing `transactionBroadcast` / `replayLogs` already supports replay-from-timestamp, so the underlying capability is there. The missing piece is plumbing the header into Resource methods.

Grep confirms no `last-event-id` / `lastEventId` handling in `core/`.

## Proposed change

1. When the HTTP entry detects `Accept: text/event-stream`, read `Last-Event-ID` and stash it on the request as `request.lastEventId` (or surface as `request.cursor` for protocol-neutral consumption).
2. Resource methods that produce replay-capable streams read it and pass to the underlying subscription:

```ts
class ConversationFeed extends Resource {
async *get(target, opts, request) {
for await (const turn of conversations.subscribe(target.id, {
cursor: request.lastEventId,
})) {
yield { event: 'turn', data: turn, id: turn.id };
}
}
}
```

The existing SSE serializer in `contentTypes.ts` already emits `id:` lines when messages carry an `id` field — so closing the loop only requires forwarding the inbound header.

## Acceptance

- [ ] `request.lastEventId` populated from the `Last-Event-ID` header when present on SSE requests.
- [ ] Documented pattern for using it with `Resource.subscribe()` / `transactionBroadcast`.
- [ ] Example: a `ConversationFeed`-style resource demonstrates resume-after-disconnect against a real client (e.g., browser `EventSource`).
- [ ] No behavior change for non-SSE requests or for methods that don't read the field.

## Related

- Useful for the live-update story behind the "real-time collaborative agents" workload (PDF page 14).
- Pairs with #511 (ConversationResource) — turn streams should be naturally replay-able.

## Out of scope

- WebSocket equivalent (would use an open-frame cursor instead — file separately if/when needed).
- Server-side cursor invalidation / GC policy beyond what `transactionBroadcast` already provides.

Contributor guide

Open the contributing guide

Research direction

Start at the HTTP entry that detects Accept: text/event-stream and trace how requests reach Resource methods. Read contentTypes.ts, Resource.subscribe(), transactionBroadcast, and replayLogs to understand the existing SSE and replay paths. Done means the Last-Event-ID value reaches replay-capable resources without affecting non-SSE requests, with the documented ConversationFeed/EventSource example described in the acceptance criteria.

Written by the indexing model from the issue text.

Assessment

Tech stack
javascript, node.js
Domain
api, backend
Issue type
Feature
Difficulty
4/5
Estimated time
3-5 days
Activity status
Quiet
Clarity
Mostly clear
Newbie friendliness
48/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.