Skip to content

Sidecar index ​

The index is a sparse table of byte offsets. It is not a copy of the file. Building it is a full sequential read. Skip it if you will seek once.

A 50 million line file with stride: 64 needs about 6 MB on disk. A later getLine reads 8 bytes from the sidecar, preads that offset, then walks at most stride lines. It does not make find() / --grep sublinear.

0..3    magic   "LSK1"
4       version 2
5       flags   bit0 = ended with newline
6..9    stride  uint32 LE
10..41  lineCount, fileSize, mtimeMs, entryCount   uint64 LE
42..49  lastLineOffset uint64 LE
50..57  prefix/suffix checksums of the indexed bytes
64..    offsets  uint64 LE  (line 1, 1+stride, 1+2*stride, ...)

Default path: {file}.lseek. Override with indexPath, or set cacheIndex: true to write under ~/.cache/line-seeker.

Incremental refresh ​

refreshIndex() compares prefix and suffix fingerprints.

  • Append — only the new bytes are scanned; existing offsets stay valid.
  • Truncate or rewrite — the sidecar is discarded and rebuilt.
  • LineSeeker edit (replaceLine / insert / delete) — the sidecar is rebuilt if one existed.
  • Read-only volume — buildIndex() falls back to the cache dir on EACCES.
  • Failed build — a failed index build removes its .tmp partial; no half-written sidecar survives.

Low-level helpers ​

These are exported if you want the index without a full seeker:

js
import {
  LineIndex,
  buildIndexFile,
  refreshIndexFile,
  indexPathFor,
  cacheIndexPath,
} from "line-seeker";

LineIndex.locate(lineNumber) returns { offset, indexedLine } or null. Only the 8-byte entry needed for that lookup is read into memory.

Zero dependencies. Node.js 18+.