WindowedSessionStorage class final
Append-only session storage over a byte-scanned window of the file.
Segment boundary (gh-1077): this view is bound to the ACTIVE segment
(the primary file). Records archived into <path>.part-NN siblings
by segment rotation are NOT reachable through loadOlder — paging
stops at the primary's header copy and hasOlder goes false there,
by design: the chat path only ever renders recent history. Full-chain
reads go through JsonlSessionStorage.open (which loads parts +
primary as one chain) or JsonlSessionRepo.readCustomRecordsOfType.
The in-memory index holds the LOADED records only; every structure
(_entries, _byId, _labelsById, offsets) is pruned in the same
pass when eviction drops records, so no strong reference outlives
residency (issue #135 round-2 review). The branch walk
(getPathToRoot) stops silently at the window edge — records
outside are reachable through loadOlder/loadNewer/
jumpToOffset. Side-branch records that share a chunk with the
active branch enter the index for id lookups but never render: the
branch joins (_joinBranchUpward) walk the parent chain only.
- Implemented types
Properties
- cachedMetadata → SessionMetadata
-
The header metadata (parsed at open/creation).
no setteroverride
- cachedTotalRecords → int?
-
The exact record count once countRecords completed;
nullbefore — the UI shows "more above" until then.no setter - countBelow → int?
-
File records below the window — the "Load newer" banner count.
nullwhile unknown (after a jumpToOffset);0at the tail.no setter - hashCode → int
-
The hash code for this object.
no setterinherited
- hasNewer → bool
-
Whether records sit below the loaded window (the "Load newer"
signal — the page-down path back to the live tail).
no setter
- hasOlder → bool
-
Whether records may exist above the loaded window (the "more above"
signal while cachedTotalRecords is still unknown).
no setter
- reader → SessionChunkReader
-
The SessionChunkReader backing this window (jump support).
no setter
- residentCount → int
-
Resident-window instrumentation (issue #135 AC1: the bound is
asserted by tests, not by user behavior).
no setter
- residentWindowBytes → int
-
no setter
- runtimeType → Type
-
A representation of the runtime type of the object.
no setterinherited
Methods
-
appendEntry(
SessionRecord record) → Future< void> -
Appends
recordto the file and the in-memory index.override -
countAbove(
) → Future< int?> -
File records above the window — the "Load earlier" banner count.
nullwhile unknown (background count running, or right after a jumpToOffset until an edge is walked);0at the file top. -
countRecords(
) → Future< int> - Background record count (streams newlines, no JSON decode). The first call scans; later calls return the memo.
-
createEntryId(
) → Future< String> -
Generates a record id that is unique within this storage.
override
-
currentBranch(
) → Future< List< SessionRecord> > - The active branch as currently RESIDENT, root-first — the view baseline. Bounded by the residency cap by construction (issue #135 round 4: the app layer must not re-accumulate what the storage evicted).
-
findEntries(
String type) → Future< List< SessionRecord> > -
Returns all records of the given
type, in file order.override -
getEntries(
) → Future< List< SessionRecord> > -
Returns all records in file order.
override
-
getEntry(
String id) → Future< SessionRecord?> -
Looks up a record by id.
override
-
getLabel(
String id) → Future< String?> -
Returns the current label attached to the record
id, if any.override -
getLeafId(
) → Future< String?> -
Returns the id of the active leaf record, or
nullat the tree root.override -
getMetadata(
) → Future< SessionMetadata> -
Returns the session metadata (from the file header).
override
-
getPathToRoot(
String? leafId) → Future< List< SessionRecord> > -
Walks from
leafIdto the tree root, returning records root-first.override -
growOlderUntil(
bool found(SessionRecord record), {int maxPages = 512, int? tokenBudget}) → Future< bool> -
Pages older chunks until
foundmatches a record on the active branch, the file top is reached, ormaxPagespass — WITHOUT sliding the newest side out: the residency cap is suspended for the walk, so a resume keeps the tail anchored while reaching the compaction boundary (the CLI resume walks to the newest compaction — the old ensure path let loadOlder evict the leaf on the first chunk, the branch read empty, and every deep-boundary resume fell back to a full open: 10s+ on a marathon file). -
ingestAppended(
) → Future< ({List< SessionRecord> delta, bool reanchored})> - Streams externally-appended records (a running fa CLI) into the window. Returns the records that joined the active branch at the tail, oldest-first — the transcript delta to append — and whether the window was RE-ANCHORED (the file shrank: truncation or rotation; issue #135 E5): the delta is then the new window's whole branch and the caller must replace, not append. While the user is deep-paged (window bottom above the file tail) appends only advance the below-count — they page in through loadNewer.
-
jumpToOffset(
int byteOffset, {int maxRecords = defaultChunkRecords, int maxBytes = defaultChunkBytes}) → Future< List< SessionRecord> > -
Re-centers the window on the record at
byteOffset(issue #135 AC6): the window becomes the chunk around the target, the leaf stays the true file leaf, and both edge counts reset to unknown until an edge is walked. Returns the branch records inside the new window, root-first — the transcript replacement delta. Offsets for repeat jumps come from offsetOf without a re-read. -
jumpToRecord(
String recordId, {int maxRecords = defaultChunkRecords, int maxBytes = defaultChunkBytes}) → Future< List< SessionRecord> > - Jump-to-record (issue #135 AC6, the mechanism the review asked for): the offset comes from the sparse map when the record was ever read this open — SessionChunkReader.locatePassCount then stays flat, the "instant" AC6 jump — and only unexplored records pay the one-pass SessionChunkReader.locateRecord seek. Returns the re-centered branch (root-first) or an empty list on a miss.
-
jumpToTail(
) → Future< List< SessionRecord> > - Re-centers the window on the live tail (issue #1159 E2): a deep-paged view rejoins the newest records in a bounded number of reads — one tail re-anchor plus at most two chunk fills back up to the resident cap (never the chunk-by-chunk page-down crawl) — and hasNewer clears. Returns the re-centered branch (root-first) — the transcript delta the caller re-projects. A window already at the tail re-reads the same range (idempotent).
-
loadAllOlder(
{int maxBytes = maxWalkBlockBytes, int maxWholeLedgerBytes = defaultMaxFullOpenBytes}) → Future< void> - Brings the whole file into residency with a sequential forward scan (gh-1555 W1 — the read-everything trajectory surfaces): strips read upward from the file head in bounded chunks, every byte read and parsed exactly once, spliced into the window in ONE prepend. growOlderUntil cannot do this cheaply: its doubling strips answer "where is the NEARBY boundary", and its per-strip record cap truncates each strip — a whole-ledger walk under that cap measured ~13x read+parse amplification on a 30k-record ledger (each 8 MiB strip kept only its newest few hundred records). A no-op when the window already reaches the file head.
-
loadNewer(
{int maxRecords = defaultChunkRecords, int maxBytes = defaultChunkBytes}) → Future< List< SessionRecord> > - Pages one chunk of records back in BELOW the window — the page-down path after deep paging slid the newest side out. Returns the records that rejoin the active branch at the bottom, oldest-first (the transcript delta to append).
-
loadOlder(
{int maxRecords = defaultChunkRecords, int maxBytes = defaultChunkBytes, int maxChunks = 8}) → Future< List< SessionRecord> > -
Loads one chunk of records above the window. Returns the records
that JOIN THE ACTIVE BRANCH, root-first — the transcript delta to
prepend. Side-branch records in the chunk enter the index (id
lookups) but are not returned. Reads chunks until a branch record
lands (a chunk can be entirely foreign-branch), the branch root or
file top is reached, or
maxChunkspasses. When the residency cap is exceeded the NEWEST side slides out — deep paging moves the window up; loadNewer (or jumpToOffset) brings the tail side back. -
noSuchMethod(
Invocation invocation) → dynamic -
Invoked when a nonexistent method or property is accessed.
inherited
-
offsetOf(
String recordId) → int? -
The byte offset of
recordIdif its chunk was ever read this open (the sparse offset map);nullfor unexplored records. -
setLeafId(
String? leafId) → Future< void> -
Persists a leaf record that moves the active leaf to
leafId.override -
toString(
) → String -
A string representation of this object.
inherited
Operators
-
operator ==(
Object other) → bool -
The equality operator.
inherited
Static Methods
-
open(
FileSystem fs, String filePath, {int chunkRecords = defaultChunkRecords, int chunkBytes = defaultChunkBytes, int? residentRecords, int? residentBytes, SessionParseExecutor? parseExecutor, SessionIoRetryConfig ioRetry = const SessionIoRetryConfig(), SessionTimingLogger? timingLog}) → Future< WindowedSessionStorage> - Opens a session windowed: header + newest chunk only. Small sessions load completely (window == file) and behave exactly like JsonlSessionStorage.open.
Constants
- defaultResidentBytes → const int
- defaultResidentRecords → const int
- Resident-window bounds (issue #135 AC1): memory is bounded by the cache, not the file. Defaults keep ~3 chunks alive.
- maxWalkBlockBytes → const int
- maxWalkBlockRecords → const int
- Largest single block of the growOlderUntil walk — the per-page doubling ceiling (64 MB / 4096 records keeps one page at ~300 ms and bounds the transient strip buffer).