closeout
API reference

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;
ParameterType
streamOutputStream
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;
ParameterType
msnumber

Returns number

createRegistry

function createRegistry(options?: RegistryOptions): Registry;
ParameterType
options (optional)RegistryOptions

Returns Registry

hideCursor

Hide the cursor and register its restore; the returned function shows it again.

function hideCursor(stream: OutputStream): () => void;
ParameterType
streamOutputStream

Returns () => void

install

Wire a registry to a process, and return the handle a program registers through.

function install(options?: InstallOptions): Closeout;
ParameterType
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;
ParameterType
handlerExitHandler
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;
ParameterType
inputInputStream
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;
ParameterType
reportShutdownReport

Returns ExitEvent

reportToJson

The --json rendering: one line, machine-first, error flattened to text.

function reportToJson(report: ShutdownReport): string;
ParameterType
reportShutdownReport

Returns string

showCursor

Show the cursor. Idempotent, and a no-op on a non-TTY.

function showCursor(stream: OutputStream): void;
ParameterType
streamOutputStream

Returns void

timeoutMessage

The line the deadline prints when it fires.

function timeoutMessage(report: ShutdownReport, deadline: number): string;
ParameterType
reportShutdownReport
deadlinenumber

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.

ExportKindDocumented in
oncefunctioncloseout/once
ENTER_ALTERNATE_SCREENconstcloseout/cursor
HIDE_CURSORconstcloseout/cursor
LEAVE_ALTERNATE_SCREENconstcloseout/cursor
SHOW_CURSORconstcloseout/cursor
InputStreaminterfacecloseout/cursor
OutputStreaminterfacecloseout/cursor

On this page