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.
| Option | Default | Meaning |
|---|---|---|
encoding | 'utf8' | Line decoding |
highWaterMark | 256 * 1024 | Stream / pread chunk size |
maxLineLength | 8 * 1024 * 1024 | Cap on a single line (LineTooLongError) |
maxRange | 100000 | Cap for getRange / getLast / getLines |
indexPath | filePath + '.lseek' | Sidecar index location |
cacheIndex | false | Store the index under ~/.cache/line-seeker |
cacheDir | os cache | Override cache directory |
stride | 64 | Index density for buildIndex() |
input | process.stdin | Byte 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 })
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.
| Class | code | When |
|---|---|---|
LineSeekerError | LINE_SEEKER_ERROR | Generic / EINVAL / ENOENT via formatError |
LineTooLongError | LINE_TOO_LONG | A line exceeds maxLineLength |
RangeTooLargeError | RANGE_TOO_LARGE | getRange / 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 boomDiagnostics helpers
import {
formatError,
formatDiagnostic,
locateMatch,
diagnosticFromError,
oneLine,
} from "line-seeker";locateMatch(line, pattern) returns { column, length } for a string or RegExp hit (1-based column).