HarperFast / HarperFast/harper
Forward Last-Event-ID into Resource methods for replay-capable SSE streams
- 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
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