What problem are you trying to solve?
ClientSession.snapshot() is the way to read of a session's durable log. #1051 was closed NOT_PLANNED as its bounded-read request was superseded by #1219 and #1404: "session.stream({ follow: false }) and ClientSession.snapshot() now pin the durable tail and return a cursor for exact live resumption."
However It only pins the tail:
snapshot(e) {
e?.signal?.throwIfAborted();
let t = [];
for await (let n of this.#o({ follow: false, signal: e?.signal, startIndex: 0 })) t.push(n);
return { events: t, session: { sessionId: ..., streamIndex: t.length } };
}
Always startIndex: 0, accumulate every event, no byte or event limit. The only control is signal, which bounds duration, not memory consumed within it. Memory consumption arguably matters even more than the event count, because of the amplification #1051 documented: every message.appended carries the whole message so far and each one is persisted, so a message's stored size is quadratic in its appends. #1051's repro measured a session stream at "100-1000× the transcript size". #944 reports a downstream session with 17,194 message.appended events.
So a caller reading a snapshot has no way to say "give up rather than pull more than N events or bytes". The failure mode is a server-side render pulling and retaining a multi-hundred-megabyte transcript, with no signal until it happens.
Proposed solution
An optional bound on snapshot(), rejecting once exceeded such as
await session.snapshot({ signal, maxBytes: 5 * 1024 * 1024 });
A distinct error type (SnapshotTooLargeError, or ClientError with a discriminant) would let callers fall back to a partial transcript, or render without history
Alternatives considered
What problem are you trying to solve?
ClientSession.snapshot()is the way to read of a session's durable log. #1051 was closed NOT_PLANNED as its bounded-read request was superseded by #1219 and #1404: "session.stream({ follow: false })andClientSession.snapshot()now pin the durable tail and return a cursor for exact live resumption."However It only pins the tail:
Always
startIndex: 0, accumulate every event, no byte or event limit. The only control issignal, which bounds duration, not memory consumed within it. Memory consumption arguably matters even more than the event count, because of the amplification #1051 documented: everymessage.appendedcarries the whole message so far and each one is persisted, so a message's stored size is quadratic in its appends. #1051's repro measured a session stream at "100-1000× the transcript size". #944 reports a downstream session with 17,194message.appendedevents.So a caller reading a snapshot has no way to say "give up rather than pull more than N events or bytes". The failure mode is a server-side render pulling and retaining a multi-hundred-megabyte transcript, with no signal until it happens.
Proposed solution
An optional bound on
snapshot(), rejecting once exceeded such asA distinct error type (
SnapshotTooLargeError, orClientErrorwith a discriminant) would let callers fall back to a partial transcript, or render without historyAlternatives considered
snapshot()as astream({ follow: false, startIndex: 0 })loop with a counter. This works and stays on the public API, but it is a hand-written copy of a published method that will drift, and it counts re-serialised JSON rather than wire bytes.signalwith a short timeout. Bounds duration only; a fast link delivers more, not less./historyendpoint from No way to fetch session history without re-downloading every partial copy of streamed text and waiting on stream endpoint semantics #1051. Already declined, and the reasoning given there ("would reduce response bytes while preserving the O(n²) writes") is about a different concern — this request is a client-side ceiling, not a new endpoint or storage format.