SessionChunkReader class final

Backward/forward byte-scan reader over one JSONL session file.

All reads are record-atomic: a window is grown until its oldest boundary sits on a record start, so a record straddling the initial read window is never truncated (issue #135 E1) — the window doubles until the whole record is inside, bounded only by the file length.

The parse cache (issue #1498) assumes the session JSONL is APPEND-ONLY: entries survive observed growth and are dropped on any observed shrink or mtime anomaly (see _noteParseCacheStamp). A writer that rewrites earlier bytes in place while keeping size AND mtime is unobservable at that granularity — the same signal the windowed storage itself relies on — and would serve stale records.

Constructors

SessionChunkReader({required FileSystem fs, required String path, int startWindowBytes = 128 << 10, SessionParseExecutor? parseExecutor, int parseCacheEntries = defaultParseCacheEntries, int parseCacheBytes = defaultParseCacheBytes})
Creates a reader over path. startWindowBytes is the first backward read size; it doubles up to the file length as needed. The parse cache (parseCacheEntries × parseCacheBytes, issue #1498) remembers parsed lines across scans — 0 entries disables it.

Properties

canReadRanges → bool
Whether the backing filesystem supports byte-range reads. Hosts without it keep the whole-file load path.
no setter
fs → FileSystem
The store backing path.
final
hashCode → int
The hash code for this object.
no setterinherited
locatePassCount → int
Blocks read by locateRecord this open (AC6 instrumentation: a jump through the sparse offset map adds none).
no setter
parseCacheBytes → int
Remembered raw-line bytes across all cached lines (issue #1498). 0 means no byte budget (the entry cap still applies); combined with parseCacheEntries 0 the cache is fully disabled.
final
parseCacheEntries → int
Remembered distinct lines (issue #1498).
final
parseCacheEntryCount → int
no setter
parseCacheHitCount → int
Lines the cache saved from decode+parse (issue #1498 instrumentation, the locatePassCount precedent): a repeat scan must add misses, not parse work.
no setter
parseCacheMissCount → int
no setter
parseExecutor → SessionParseExecutor?
Where record parsing runs — null keeps the inline batched path (web); IO hosts inject the isolate executor (issue #199).
final
path → String
final
runtimeType → Type
A representation of the runtime type of the object.
no setterinherited
startWindowBytes → int
final

Methods

countRecords() → Future<int>
Streams the file counting newlines — no JSON decode (~1 s per 300 MB). Returns the number of RECORDS (lines minus the header line).
locateRecord(String recordId) → Future<int?>
Streaming id seek (issue #135 AC6): scans the file block-wise for the line whose record id is recordId and returns its byte offset; null when absent. Cost is one pass — callers jump through the sparse offset map instead whenever the record was read before (locatePassCount instruments the difference).
noSuchMethod(Invocation invocation) → dynamic
Invoked when a nonexistent method or property is accessed.
inherited
readAround(int byteOffset, {int maxRecords = defaultChunkRecords, int maxBytes = defaultChunkBytes}) → Future<SessionChunk>
Reads a window centered on the record at byteOffset (a jump target): the record itself, the newer records below it, and older records above it, bounded by the same dual cap.
readBefore(int anchorOffset, {int maxRecords = defaultChunkRecords, int maxBytes = defaultChunkBytes}) → Future<SessionChunk>
Reads up to maxRecords records immediately above anchorOffset (the byte offset of a record start — typically a previous chunk's SessionChunk.firstOffset).
readBlockBefore(int anchorOffset, {int maxRecords = defaultChunkRecords, int maxBytes = defaultChunkBytes, bool shallowGiantCustoms = false}) → Future<SessionChunk>
Boundary-walk block read (issue #503): ONE strip of up to maxBytes immediately above anchorOffset, trimmed to the newest maxRecords records. Unlike the capped readBefore — whose doubling window re-splits and re-decodes its whole buffer on every pass — every byte here is read, decoded and parsed exactly once, so a deep resume walk costs one linear pass over the span instead of ~5x the parse work (a 400 MB tail-after-compaction walked 10s+ through readBefore).
readForward(int fromOffset, {int? maxRecords, int? maxBytes}) → Future<SessionChunk>
Reads records from the line boundary fromOffset to EOF. With maxRecords/maxBytes (the jump and page-down paths) at most that much is kept, scanned block-wise — the below-range of a deep-paged window is never read whole; without caps (the live-tail ingest path — an external CLI appended while the app held the window) everything is returned, so a burst of external appends is never silently dropped. The chunk's SessionChunk.limitOffset is the end of the last kept record, so a capped read resumes exactly after it.
readHeader() → Future<SessionHeader>
Reads the session header (first line) only — the metadata an open needs without touching the record body. The prefix read doubles from 4 KiB so a normal open moves a few KiB, never the file.
readNewestSessionInfo() → Future<SessionInfoRecord?>
The newest session_info record of the file (null when it holds none) — the record form of readNewestSessionInfoName, so callers probing several segments can tell "no record" apart from an empty (name-clearing) one.
readNewestSessionInfoName() → Future<String?>
The newest session_info record's name via two bounded probes (issue #369). The HEAD window is read first: a named-at-creation session keeps its session_info among the first records, so the 400 MB giant case resolves from the file start without any backward walk (AC2). The TAIL window's verdict wins whenever it sees a record — bytes after the head are newer, so a rename-at-end still beats the header name (AC5, newest within the cap) — and on a head miss it is the bounded-cap fallback AC1 names. The body between the windows is never read. Returns the name, null when the probes see no session_info or the newest probed record clears the name (empty/whitespace — the caller's contract). Throws SessionException like readTail when the file is missing.
readRecordsByIds(Set<String> ids) → Future<Map<String, SessionRecord>>
Hidden-range drill-in (issue #385 F4): one forward block scan collecting the records whose ids appear in ids — the records a HiddenRangeRecord covers. They sit OFF the kept branch (the compaction evicted them from the context), so the branch readers never surface them; this reads the raw file instead. Serves what exists: a range still open at the file tail resolves the records already written, honestly; ids absent from the file stay absent from the result. Cost is one pass, bounded like locateRecord.
readTail({int maxRecords = defaultChunkRecords, int maxBytes = defaultChunkBytes}) → Future<SessionChunk>
Reads up to the newest maxRecords records (bounded by maxBytes).
stat() → Future<({int mtimeMs, int size})?>
File facts for staleness guards; null when the file is gone.
toString() → String
A string representation of this object.
inherited

Operators

operator ==(Object other) → bool
The equality operator.
inherited