# Node API

> The shutdown-check Node API: run a check from a test with checkShutdown, validate configs, build a custom runner with runCheck and write JUnit XML.

Source: https://shutdown.jscrate.dev/docs/node-api
Last updated: 2026-09-23

The shutdown-check Node API is useful when the check belongs inside a test
suite or custom script. It runs the same engine as the CLI, but it returns data
instead of printing output or exiting your process.

## Import the package

ES modules:

```ts
import { checkShutdown, defineConfig } from "shutdown-check";
```

CommonJS, supported since 1.0.1:

```js
const { checkShutdown, defineConfig } = require("shutdown-check");
```

TypeScript declarations are included for both entry points.

## Exports

| Export          | Use it when                                                  |
| --------------- | ------------------------------------------------------------ |
| `checkShutdown` | You have an input object and want validation plus a full run |
| `defineConfig`  | You want type checking and completion while writing config   |
| `parseConfig`   | You want to validate an object and apply defaults            |
| `loadConfig`    | You want to read and validate a JSON file                    |
| `runCheck`      | You already have a validated `CheckConfig`                   |
| `junitXml`      | You want to turn a `CheckResult` into JUnit XML              |

The package also exports the config, result, diagnostic, and timeline types.

## Run a check with checkShutdown

`checkShutdown()` is the recommended high-level function. It validates the
input, fills in defaults, runs the check, performs cleanup, and resolves to a
`CheckResult`.

```ts title="test/shutdown.test.ts"
import assert from "node:assert/strict";
import { test } from "node:test";
import { checkShutdown } from "shutdown-check";

test("finishes active requests on SIGTERM", { timeout: 30_000 }, async () => {
  const result = await checkShutdown({
    command: ["node", "dist/server.js"],
    env: { PORT: "3510" },
    baseUrl: "http://127.0.0.1:3510",
    readiness: { path: "/health" },
    workload: {
      path: "/slow",
      bodyIncludes: "work complete",
      started: { type: "response-headers" },
    },
    shutdown: {
      deadlineMs: 10_000,
      readinessWithdrawal: true,
    },
  });

  assert.equal(result.pass, true, `${result.code}: ${result.message}`);
});
```

A normal shutdown failure does not throw. It resolves with `pass: false` and a
diagnostic code. Invalid config rejects with a validation `Error`.

```ts
function checkShutdown(input: CheckConfigInput, baseDirectory?: string): Promise<CheckResult>
```

Validates `input` with `parseConfig()` and runs the check. Relative paths resolve from `baseDirectory` (default `process.cwd()`). It starts and cleans up its own process, and returns `pass: false` for a failed check rather than throwing; it throws only for an invalid config.

### Resolve relative paths

The second argument is the base directory used for relative `cwd` values. It
defaults to `process.cwd()`.

```ts
const result = await checkShutdown(config, import.meta.dirname);
```

Pass the test file's directory when the test runner may use another working
directory.

## Type a config with defineConfig

`defineConfig()` returns the object unchanged. It exists to provide editor
completion and catch invalid types before the test runs.

```ts title="shutdown.config.ts"
import { defineConfig } from "shutdown-check";

export default defineConfig({
  command: ["node", "dist/server.js"],
  baseUrl: "http://127.0.0.1:3510",
  readiness: { path: "/health", timeoutMs: 30_000 },
  workload: {
    path: "/slow",
    concurrent: 2,
    started: { type: "response-headers" },
  },
  shutdown: { deadlineMs: 15_000 },
});
```

Import the object and pass it to `checkShutdown()`. The CLI does not load
TypeScript config files.

```ts
function defineConfig<T extends CheckConfigInput>(input: T): T
```

Returns `input` unchanged. Use it for editor completion and type checking when you write a config in TypeScript.

## Validate input with parseConfig

`parseConfig()` validates an unknown object and returns a complete
`CheckConfig` with defaults applied.

```ts
import { parseConfig } from "shutdown-check";

const config = parseConfig(
  {
    command: ["node", "dist/server.js"],
    baseUrl: "http://127.0.0.1:3510",
    readiness: { path: "/health" },
    workload: {
      path: "/slow",
      started: { type: "response-headers" },
    },
  },
  process.cwd()
);

console.log(config.shutdown.deadlineMs); // 10000
```

The optional second argument controls how relative `cwd` is resolved.
Validation throws an `Error` naming the first invalid field.

## Read a JSON config with loadConfig

`loadConfig()` reads a file, parses its JSON, validates it, and resolves `cwd`
from the file's folder.

```ts
import { loadConfig } from "shutdown-check";

const config = await loadConfig("config/shutdown-check.json");
```

File errors, JSON syntax errors, and validation errors are thrown. The messages
match the ones used by the CLI.

```ts
function parseConfig(raw: unknown, baseDirectory?: string): CheckConfig
```

Validates a config object and fills in every default, resolving `cwd` from `baseDirectory` (default `process.cwd()`). Throws an `Error` naming the first invalid field.

```ts
function loadConfig(file: string): Promise<CheckConfig>
```

Reads and validates a JSON config file. Relative paths in it resolve from the file's folder.

## Build a custom runner

Use `runCheck()` after `parseConfig()` or `loadConfig()` when you need custom
logging or report handling.

```ts title="scripts/check-shutdown.ts"
import { mkdir, writeFile } from "node:fs/promises";
import { junitXml, loadConfig, runCheck } from "shutdown-check";

const config = await loadConfig("shutdown-check.json");
const result = await runCheck(config);

for (const item of result.timeline) {
  const detail = item.detail ? ` — ${item.detail}` : "";
  console.log(`+${item.ms} ms ${item.event}${detail}`);
}

await mkdir("reports", { recursive: true });
await writeFile(
  "reports/shutdown.xml",
  junitXml(result, "api-server shutdown")
);

if (!result.pass) {
  console.error(`${result.code}: ${result.message}`);
  if (result.stderr) console.error(result.stderr);
  process.exitCode = 1;
}
```

`runCheck()` expects a valid `CheckConfig`; it does not validate a hand-built
object again. Bypassing validation can lead to [SC002](https://shutdown.jscrate.dev/docs/codes/sc002) or
[SC999](https://shutdown.jscrate.dev/docs/codes/sc999).

```ts
function runCheck(config: CheckConfig): Promise<CheckResult>
```

Runs the check with an already validated config, from `parseConfig()` or `loadConfig()`.

```ts
function junitXml(result: CheckResult, suiteName?: string): string
```

Turns a result into JUnit XML: one suite (`suiteName`, default `"shutdown-check"`) with one test case, "graceful shutdown". The timeline goes in `system-out`, stderr in `system-err`, and a failed check adds a `failure` element typed with its code.

The optional second argument to `junitXml()` names the test suite. It defaults
to `shutdown-check`.

## Understand CheckResult

Every completed check resolves to the same object printed by CLI `--json`.

| Option | Type | Default | Description |
| --- | --- | --- | --- |
| `pass` (required) | `boolean` | — | `true` only for `SC000`. |
| `code` (required) | `string` | — | The [diagnostic code](https://shutdown.jscrate.dev/docs/codes): `SC000` on a pass, the first failure otherwise. |
| `message` (required) | `string` | — | What happened, in one sentence, with the values observed. |
| `timeline` (required) | `TimelineEvent[]` | — | Every step of the check in order, with milliseconds since it started. |
| `stdout` (required) | `string` | — | The last 8 KiB the service wrote to stdout. |
| `stderr` (required) | `string` | — | The last 8 KiB the service wrote to stderr. |

Important fields:

- `pass` is the value to assert in a test.
- `code` is `SC000` on a pass or the first failure code.
- `message` is the human-readable result.
- `timeline` records each observed step.
- `stdout` and `stderr` contain the captured service output tails.
- `startedAt` and `durationMs` describe the run.

Each item in `timeline` has this shape:

| Option | Type | Default | Description |
| --- | --- | --- | --- |
| `ms` (required) | `number` | — | Milliseconds since the check started, rounded. |
| `event` (required) | `string` | — | What happened: `"process launched"`, `"service ready"`, `"signal sent"`, `"work request finished"`, `"process exited"` and so on. |
| `detail` | `string` | — | The values behind it, such as `"HTTP 503"` or `"code=0, signal=none"`. |

The [output reference](https://shutdown.jscrate.dev/docs/output) lists every event name and its detail.

## What throws?

| Situation                       | `checkShutdown()`           | `runCheck()`                |
| ------------------------------- | --------------------------- | --------------------------- |
| Invalid input config            | Rejects with `Error`        | Not validated               |
| Expected check failure          | Resolves with `pass: false` | Resolves with `pass: false` |
| Unexpected error during the run | Resolves with `SC999`       | Resolves with `SC999`       |

Wrap calls in `try/catch` when configuration may be invalid. For an ordinary
test failure, inspect the returned code and timeline.

## Cleanup and parallel tests

Before resolving, the API closes its client requests, clears timers, and
force-kills the service process group when necessary. A passing service has
already exited and closed its port by itself.

Parallel checks must use separate ports and isolated test data. Otherwise one
run may fail with [SC001](https://shutdown.jscrate.dev/docs/codes/sc001) or affect another run's workload.

## Related

- [Tests with node:test and Vitest](https://shutdown.jscrate.dev/docs/guides/test-runners)
- [Configuration reference](https://shutdown.jscrate.dev/docs/configuration)
- [Output and reports](https://shutdown.jscrate.dev/docs/output)
- [Diagnostic codes](https://shutdown.jscrate.dev/docs/codes)
- [CLI reference](https://shutdown.jscrate.dev/docs/cli)
