3.9 KiB
3.9 KiB
name, description
| name | description |
|---|---|
| sp-readline | Terminal I/O primitive — AsyncGenerator line reader and interactive prompt interface for Node.js streams and Web Streams. |
Readline for Suaveplan
AsyncGenerator line reader and interactive prompt interface for Node.js
streams, file paths, and Web Streams. readLines yields lines from any source
with optional empty-line skipping, max-line-length guards, and AbortSignal
support. createInterface builds a REPL-like prompt object with question()
and async-iterator semantics.
Architecture
| Component | Responsibility |
|---|---|
createScanner |
Low-level byte scanner over a Readable stream; yields lines, enforces maxLineLength, respects AbortSignal. |
readLines |
Public AsyncGenerator that adapts file paths, Node Readable, or Web ReadableStream to createScanner. |
createInterface |
Interactive prompt interface with question(prompt) and [Symbol.asyncIterator] for piped input. |
ReadlineError |
Typed error carrying READLINE_CODES.SCANNER.*, READLINE_CODES.INTERFACE.*, or READLINE_CODES.READER.*. |
File Structure
devtools/tooling/readline/
src/scanner/ # createScanner — byte-level line scanner
src/reader/ # readLines — multi-source AsyncGenerator
src/interface/ # createInterface — interactive prompt / REPL
src/errors/ # READLINE_CODES registry + ReadlineError
Sibling packages:
@suaveplan/linter— unified markdown + biome linting pipeline@suaveplan/profiler— runtime performance profiling toolkit@suaveplan/sandbox— sandboxed code execution@suaveplan/test-vite— Vite + Vitest configuration factories
Patterns
Quick Start — iterate lines from a file
import { readLines } from "@suaveplan/readline/reader/reader";
for await (const line of readLines("/var/log/app.log")) {
console.log(line);
}
From a Node.js Readable with options
import { createReadStream } from "node:fs";
import { readLines } from "@suaveplan/readline/reader/reader";
for await (const line of readLines(createReadStream("data.csv"), { skipEmpty: true })) {
process(line);
}
Abort support and line-length guard
import { readLines } from "@suaveplan/readline/reader/reader";
const ac = new AbortController();
setTimeout(() => ac.abort(), 5_000);
for await (const line of readLines("huge.txt", { maxLineLength: 4096, signal: ac.signal })) {
console.log(line);
}
Interactive prompt
import { createInterface } from "@suaveplan/readline/interface/interface";
const rl = createInterface({ input: process.stdin, output: process.stdout });
const name = await rl.question("Your name: ");
console.log(`Hello, ${name}!`);
for await (const line of rl) {
if (line === "quit") break;
console.log(`echo: ${line}`);
}
rl.close();
Constraints
- Node ≥ 20 — relies on
node:fs/createReadStreamandnode:stream. - Scanner default
maxLineLengthis 4096 — exceeding it throwsReadlineError(READLINE_CODES.SCANNER.LINE_TOO_LONG). - CR characters are stripped —
\ris ignored; only\nterminates a line. - AbortSignal unblocks the generator cleanly — the scanner pushes a
doneitem when the signal fires, so the loop exits without throwing. - Interface operations throw after
close()—question()andnext()on a closed interface raiseReadlineError(READLINE_CODES.INTERFACE.CLOSED). - Error codes go through
defineCodes—READLINE_CODESgroupsREADLINE.SCANNER.*,READLINE.INTERFACE.*, andREADLINE.READER.*; never hand-write the string literal.
Checklist
- Use
readLinesfor batch line processing; usecreateInterfacefor interactive prompts. - Always pass
signalwhen reading from potentially unbounded streams. - Set
maxLineLengthwhen reading untrusted input to prevent memory exhaustion. - Close the interface with
rl.close()when done to end the OTel span.