closeout
Every export of closeout, with its signature and doc comment: alternateScreen, assertDeadline, createRegistry, DEADLINE_ERROR_CODE, DeadlineError, DEFAULT_DEADLINE and 17 more, plus 16 types.
closeout — close everything out.
import { alternateScreen, assertDeadline, createRegistry, … } from 'closeout';Functions
alternateScreen
Enter the alternate screen and register leaving it in restore; the returned function
leaves it. Registers on the process-wide instance unless closeout names another.
function alternateScreen(stream: OutputStream, closeout?: Closeout): () => void;| Parameter | Type |
|---|---|
stream | OutputStream |
closeout (optional) | Closeout |
Returns () => void
assertDeadline
Check a deadline at the moment it is registered, not at the moment it would have fired.
function assertDeadline(ms: number): number;| Parameter | Type |
|---|---|
ms | number |
Returns number
createRegistry
function createRegistry(options?: RegistryOptions): Registry;| Parameter | Type |
|---|---|
options (optional) | RegistryOptions |
Returns Registry
hideCursor
Hide the cursor and register its restore; the returned function shows it again.
function hideCursor(stream: OutputStream): () => void;| Parameter | Type |
|---|---|
stream | OutputStream |
Returns () => void
install
Wire a registry to a process, and return the handle a program registers through.
function install(options?: InstallOptions): Closeout;| Parameter | Type |
|---|---|
options (optional) | InstallOptions |
Returns Closeout
onExit
Register a handler that runs exactly once, on every path out of the program.
function onExit(handler: ExitHandler, spec?: HandlerSpec): () => void;| Parameter | Type |
|---|---|
handler | ExitHandler |
spec (optional) | HandlerSpec |
Returns () => void
rawMode
Turn raw mode on and register turning it off in restore; the returned function turns it
off. An input that was already raw belongs to somebody else and is left alone, now and at
exit. Registers on the process-wide instance unless closeout names another.
function rawMode(input: InputStream, closeout?: Closeout): () => void;| Parameter | Type |
|---|---|
input | InputStream |
closeout (optional) | Closeout |
Returns () => void
reportToEvent
The agent rendering. Same values, one key added, still a projection of the one record.
function reportToEvent(report: ShutdownReport): ExitEvent;| Parameter | Type |
|---|---|
report | ShutdownReport |
Returns ExitEvent
reportToJson
The --json rendering: one line, machine-first, error flattened to text.
function reportToJson(report: ShutdownReport): string;| Parameter | Type |
|---|---|
report | ShutdownReport |
Returns string
showCursor
Show the cursor. Idempotent, and a no-op on a non-TTY.
function showCursor(stream: OutputStream): void;| Parameter | Type |
|---|---|
stream | OutputStream |
Returns void
timeoutMessage
The line the deadline prints when it fires.
function timeoutMessage(report: ShutdownReport, deadline: number): string;| Parameter | Type |
|---|---|
report | ShutdownReport |
deadline | number |
Returns string
Classes
DeadlineError
class DeadlineError extends TypeError {
readonly code = "USAGE";
/** What to do instead. Always present, always actionable — contract R8's shape. */
readonly fix: string;
constructor(message: string, fix: string);
}Constants
DEADLINE_ERROR_CODE
A caller error, in the family's one vocabulary: a code that classifies it and a fix
that says what to do instead.
const DEADLINE_ERROR_CODE = "USAGE";DEFAULT_DEADLINE
Two seconds: long enough to flush a file, short enough that nobody reaches for the keyboard.
const DEFAULT_DEADLINE = 2000;DEFAULT_PHASE
Where a handler goes when its author did not say.
const DEFAULT_PHASE: Phase;EXIT_PATHS
Every path, in the order install() wires them. Exported so a matrix can iterate it.
const EXIT_PATHS: readonly ["exit", "beforeExit", "signal", "uncaught", "rejection"];PHASES
The order shutdown happens in, as data rather than as registration order.
const PHASES: readonly ["flush", "release", "restore"];SIGNALS
The signals a CLI is expected to survive politely (design R1).
const SIGNALS: readonly ["SIGINT", "SIGTERM", "SIGHUP", "SIGQUIT", "SIGBREAK"];Interfaces
Closeout
interface Closeout {
/**
* Register a handler. Returns the function that unregisters it.
*/
onExit(handler: ExitHandler, spec?: HandlerSpec): () => void;
/** Hide the cursor and register its restore; the returned function shows it again. */
hideCursor(stream: OutputStream): () => void;
/** Show the cursor now. Idempotent, and a no-op on a non-TTY. */
showCursor(stream: OutputStream): void;
/** The registry, for a caller that wants to drive shutdown itself. */
readonly registry: Registry;
}ExitEvent
The shape an agent event carries: the same values, under the family's event key (Y6).
interface ExitEvent {
type: 'closeout.shutdown';
path: ExitPath;
signal: string | null;
code: number | null;
error: string | null;
timedOut: boolean;
unfinished: string[];
}ExitInfo
What a trigger supplies. The path is inferred from the signal when it is not stated.
interface ExitInfo {
code: number | null;
signal: string | null;
path?: ExitPath;
error?: unknown;
}ExitReport
What a handler is handed. The same record --json and an agent event project from.
interface ExitReport {
/** Which door: a normal exit, an emptied loop, a signal, a throw, a rejected promise. */
path: ExitPath;
/** The signal that ended it, when one did. */
signal: string | null;
/** The code the process is leaving with, when it is leaving by a code. */
code: number | null;
/** What was thrown or rejected, on the two paths that have one. `null` on the others. */
error: unknown;
}HandlerOptions
How a handler is registered when a bare phase is not enough to say it.
interface HandlerOptions {
/** Which phase it runs in. Defaults to {@link DEFAULT_PHASE}. */
phase?: Phase;
/**
* What to call it in a report.
*/
label?: string;
}InstallOptions
interface InstallOptions extends RegistryOptions {
/** Defaults to the real `process`. */
process?: ProcessLike;
}ProcessLike
The half of process this package needs, so the wiring can be tested without one.
interface ProcessLike {
on(event: string, listener: (...args: never[]) => void): unknown;
removeListener(event: string, listener: (...args: never[]) => void): unknown;
listenerCount(event: string): number;
exit(code?: number): never;
/**
* Send a signal to a process. closeout passes exactly one pid — {@link ProcessLike.pid},
* its own — because the only thing it ever does with this is re-raise the signal it just
* handled, so that a process killed by SIGINT *dies of SIGINT* instead of reporting 130.
*/
kill(pid: number, signal: string): unknown;
/** This process's own id — the only one {@link ProcessLike.kill} is ever given. */
pid: number;
stderr: OutputStream;
/**
* Optional because the signal wiring has never needed it and its tests do not supply one:
* only the terminal-restore façade picks between the two streams.
*/
stdout?: OutputStream;
/**
* The code the process will leave with, which is a property of the process and not of any
* handler. `exit-hook`'s contract is written in terms of it — a hook is handed the code the
* program is about to exit with, and a signal overrules whatever was set — so the drop-in
* path reads and writes it here. Typed as Node types it (`string` is legal and coerced).
*/
exitCode?: number | string | undefined;
}Registry
interface Registry {
/**
* Register a handler in a phase. Returns the function that unregisters it.
*/
add(handler: ExitHandler, spec?: HandlerSpec): () => void;
/**
* Run every handler, once, phase by phase, inside the deadline. A later call runs nothing
* and resolves with the first: it waits on a shutdown still in flight.
*/
run(info: ExitInfo): Promise<ShutdownReport>;
/** Run every handler synchronously, in phase order; a returned promise is abandoned. */
runSync(info: ExitInfo): ShutdownReport;
/** How many handlers are registered — for a caller that wants to know if any are. */
readonly size: number;
/** How many are registered in one phase. */
count(phase: Phase): number;
/** Whether shutdown has already happened. */
readonly settled: boolean;
/**
* The report of the shutdown that happened, or `undefined` before one has.
*/
readonly report: ShutdownReport | undefined;
}RegistryOptions
interface RegistryOptions {
/**
* How long shutdown may take before it stops waiting, in milliseconds.
*/
deadline?: number;
/** Where a handler's own failure is reported. Defaults to stderr. */
onError?: (error: unknown) => void;
/**
* Where a breached deadline is reported. Defaults to stderr, naming every handler that
* had not returned. Takes the whole report, so a caller can project it as JSON or as an
* agent event instead of a line of prose.
*/
onTimeout?: (report: ShutdownReport) => void;
}ShutdownReport
What shutdown was, once it has run: the record plus what the deadline found.
interface ShutdownReport extends ExitReport {
/** Whether the deadline expired before the handlers were done. */
timedOut: boolean;
/**
* The handlers that had not returned when shutdown stopped waiting — by the label their
* caller gave them, or by the function's own `name`.
*/
unfinished: readonly string[];
}Types
ExitHandler
A handler is handed the one record: { path, signal, code, error } (design R2).
type ExitHandler = (report: ExitReport) => void | Promise<void>;ExitPath
Which door the process left by.
type ExitPath = 'exit' | 'beforeExit' | 'signal' | 'uncaught' | 'rejection';HandlerSpec
A phase, or the options object. The bare phase is the common case and stays spellable.
type HandlerSpec = Phase | HandlerOptions;Phase
One of {@link PHASES}.
type Phase = (typeof PHASES)[number];Re-exported
Documented on the page of the entry point that declares them.
| Export | Kind | Documented in |
|---|---|---|
once | function | closeout/once |
ENTER_ALTERNATE_SCREEN | const | closeout/cursor |
HIDE_CURSOR | const | closeout/cursor |
LEAVE_ALTERNATE_SCREEN | const | closeout/cursor |
SHOW_CURSOR | const | closeout/cursor |
InputStream | interface | closeout/cursor |
OutputStream | interface | closeout/cursor |