Skip to content

API ​

Everything is typed in types/index.d.ts. There is no any.

Costs are in the guide. Short version: no index means a prefix scan; buildIndex is one full read; find is a JS line scan; disk edits copy the whole file.

createSeeker(source, options?) ​

Returns a LineSeeker. source may be a path (string or file: URL), - for stdin, a Uint8Array / Buffer, { text }, or { bytes }. Buffer / Uint8Array sources keep their backing ArrayBuffer zero-copy instead of doubling it.

OptionDefaultMeaning
encoding'utf8'Line decoding
highWaterMark256 * 1024Stream / pread chunk size
maxLineLength8 * 1024 * 1024Cap on a single line (LineTooLongError)
maxRange100000Cap for getRange / getLast / getLines
indexPathfilePath + '.lseek'Sidecar index location
cacheIndexfalseStore the index under ~/.cache/line-seeker
cacheDiros cacheOverride cache directory
stride64Index density for buildIndex()
inputprocess.stdinByte source when filePath is -

Reads ​

getLine(n) → Promise<string | null> ​

n may be negative. Returns null if the line does not exist. Without an index, scans from byte 0 or from the forward cursor. After buildIndex, jumps to the nearest stored offset, then walks at most stride lines.

locate(n) → Promise<LineHit | null> ​

Same seek as getLine, plus offset and nextOffset in bytes.

getAround(n, { before, after }) → Promise<LineRecord[]> ​

The hit and its neighbours. Default window is only that line.

getRange(start, end) → Promise<string[]> ​

Inclusive. getRange(-50, -1) is the last 50 lines. Throws RangeTooLargeError above maxRange.

getLast(n) → Promise<string[]> ​

Walks backward from EOF when no index covers the whole file.

getLines([n1, n2, n3]) → Promise<Array<string | null>> ​

One forward pass. Result order matches the input. Negative indexes resolve against one line count instead of triggering a scan per negative.

streamLines({ onLine, start, end, signal }) ​

Return false from onLine to stop.

lines({ start, end, signal }) ​

js
for await (const { line, lineNumber } of seeker.lines({ start: 100 })) {
  // ...
}

find(pattern, { start, end, limit, before, after, signal }) ​

pattern is a string, a RegExp, or (line, lineNumber) => boolean. before / after emit neighbouring lines once (grep -B / -A). Linear scan of decoded lines — not ripgrep.

countMatches(pattern, opts) → Promise<number> ​

Same scan as find, without collecting lines.

copyRange(start, end, dest) → Promise<number> ​

Streams an inclusive range to a file path or a Writable. No maxRange cap. Returns how many lines were written.

bytes() / text() ​

In-memory seekers return the current buffer / decoded string. File and stdin seekers return null.

replaceLine(n, text) / insertLines(n, lines) / deleteLines(start, end?) ​

Does not load the file as a string. On disk, copies through a temp path then renames — I/O is the whole file, RAM stays a chunk. If a sidecar existed, it is rebuilt after the rename. An in-memory seeker splices the buffer; read it back with text() / bytes(). Edits are serialized, so concurrent replaceLine / insertLines / deleteLines calls cannot corrupt each other.

Insert before line n. Use count + 1 to append. insertLines(-1) inserts before the last line.

One-shot helpers (open, run, close — best with a path): readLine, readRange, readLast, readAround, locateLine, countFileLines, countFileMatches, setLine, insertLines, removeLines.

follow({ pollMs, fromStart, signal, watch, onRotate }) ​

Yields newly appended lines (tail -f). Survives inode replacement and truncate. watch defaults to true (fs.watch plus poll). onRotate({ reason }) fires before the reread. Throws on an in-memory source. An appended line over maxLineLength throws LineTooLongError.

countLines() → Promise<number> ​

Uses the index prefix when present; counts only the appended tail if the file grew.

Index ​

buildIndex({ stride, indexPath }) ​

One full read of the file, then a sparse sidecar. Returns { lineCount, entryCount, stride, bytes, appended, rebuilt }. Skip this unless you will seek more than once.

refreshIndex({ stride, indexPath }) ​

Extends an existing index after append. Rebuilds if fingerprints no longer match.

hasIndex() → Promise<boolean> ​

close() → Promise<void> ​

Always close when you are done. Waits out in-flight edits and file-opens, then releases the source file handle and the index handle.

cursorLineNumber ​

Last line consumed by the sequential cursor. 0 before the first read.

Errors ​

All errors are one line. No stack dumps from the CLI.

ClasscodeWhen
LineSeekerErrorLINE_SEEKER_ERRORGeneric / EINVAL / ENOENT via formatError
LineTooLongErrorLINE_TOO_LONGA line exceeds maxLineLength
RangeTooLargeErrorRANGE_TOO_LARGEgetRange / getLast / getLines over maxRange

formatError(err, file) and formatDiagnostic(diagnostic) print a single diagnostic line:

error[ENOENT]: File not found --> /tmp/missing.log
match: ERROR --> app.log:9:1 ERROR boom

Diagnostics helpers ​

js
import {
  formatError,
  formatDiagnostic,
  locateMatch,
  diagnosticFromError,
  oneLine,
} from "line-seeker";

locateMatch(line, pattern) returns { column, length } for a string or RegExp hit (1-based column).

Zero dependencies. Node.js 18+.