Hydration Proof

Search documentation

Find a page or section

Everything the CLI does, from your own script.

The hydration-proof Node API runs the same test as the CLI from your own script. run() resolves with the report and the exit code and never calls process.exit, so you can start a server, seed data or publish the report around it. Everything on this page is exported from the hydration-proof package.

Run a test with run()

run-from-node.ts
import { run } from "hydration-proof";
 
const { exitCode, report } = await run({
  overrides: { url: "http://localhost:3000", reporters: ["json"] },
  write: (text) => process.stdout.write(text),
});
 
// The report is the same object the JSON reporter writes.
for (const issue of report.issues) {
  if (issue.severity !== "error") continue;
  console.log(
    `${issue.route.pattern} ${issue.code} ${issue.source?.file ?? "(no source)"} — ${issue.cause?.title ?? "unknown cause"}`
  );
}
 
process.exit(exitCode);

run() reads the config the way the CLI does, from cwd or the file you name in config. overrides are the same overrides the command line applies, such as url, routes, reporters, failOn, probes or shard. write receives the progress and the terminal report (standard output by default), and reporters adds your own reporters to the configured ones.

function run(options?: RunOptions): Promise<RunResult>

Runs a test the way hydration-proof test does and resolves with the report and exit code. It never calls process.exit.

OptionTypeDefaultDescription
cwdstringprocess.cwd()Directory the config is looked up from.
configstringConfig file path; found automatically when omitted.
overridesCliOverridesThe same overrides the command line applies, e.g. { url, routes, reporters, failOn }.
write(text: string) => voidWhere progress and the terminal report are written.
reportersReporter[]Extra reporters (in addition to the configured ones).
signalAbortSignalAbort the run (the app and browser are shut down).
OptionTypeDefaultDescription
report*ReportThe full report, the same object report.json holds.
exitCode*ExitCodeThe exit code the CLI would use. See ExitCode.
failures*string[]Human-readable reasons the run failed.
files*string[]Report files that were written.
notes*string[]Informational notes printed at the end of the run.
outputDir*stringWhere the reports were written.

To stop a run early, pass an AbortSignal. When it aborts, the app and the browser are shut down:

run-with-timeout.ts
import { run } from "hydration-proof";
 
// Stop the run, the app and the browser after ten minutes.
await run({ signal: AbortSignal.timeout(10 * 60_000) });

Handle errors with RunError

Problems found on pages never throw: they are in the report, and exitCode says whether the policy failed. run() throws a RunError for failures that stop a run before any page is tested, such as an invalid config, a failing setup hook or login, a browser that cannot launch, or an app that cannot be started. Its exitCode is the one the CLI would use:

hydration.ts
import { run, RunError } from "hydration-proof";
 
try {
  const result = await run();
  process.exitCode = result.exitCode;
} catch (error) {
  if (!(error instanceof RunError)) throw error;
  console.error(error.message);
  process.exitCode = error.exitCode;
}
class RunError extends Error { override name: string; readonly exitCode: ExitCode; readonly details: string | undefined; constructor(message: string, exitCode: ExitCode, details?: string); }

Thrown by run() for failures that stop a run before any page is tested, with the exit code the CLI would use.

Exit codes

ExitCode names the codes the CLI exits with. They are stable:

NameCodeMeaning
Ok0Every page passed the policy
Failed1Findings, a budget or an expired ignore rule failed the policy
Usage2Invalid configuration or command-line usage
Server3The app could not be built, started or reached
Browser4The browser is missing or failed to launch
Internal70A bug in hydration-proof
Interrupted130Interrupted (Ctrl+C)
const ExitCode: { /** Every page passed the policy. */ readonly Ok: 0; /** Issues, budget or expired ignore rules failed the policy. */ readonly Failed: 1; /** Invalid configuration or command-line usage. */ readonly Usage: 2; /** The app could not be built, started or reached. */ readonly Server: 3; /** The browser is missing or failed to launch. */ readonly Browser: 4; /** A bug in hydration-proof. */ readonly Internal: 70; /** Interrupted (Ctrl+C). */ readonly Interrupted: 130; }

Exit codes. They are stable: 0 ok, 1 failed, 2 usage or config error, 3 server, 4 browser, 70 internal error. An interrupted run exits 130.

Read and merge reports

readReport() reads report.json from a report folder or a file path. mergeReports() combines the reports of sharded or per-project runs into one report, removing duplicates. The CLI's merge-reports command wraps it, and also applies the CI policy and runs the reporters.

merge.ts
import { writeFileSync } from "node:fs";
import { mergeReports, readReport } from "hydration-proof";
 
const report = mergeReports(
  ["shards/shard-1", "shards/shard-2"].map((dir) => readReport(dir))
);
 
writeFileSync("merged-report.json", JSON.stringify(report, null, 2));
console.log(`${report.summary.failed} pages failed`);
function mergeReports(inputs: readonly MergeInput[]): Report

Combines the reports of sharded or per-project runs into one, removing duplicates. The CLI's merge-reports command wraps it.

function readReport(path: string): MergeInput

Read report.json from a folder or a file path.

Configs, plugins and adapters

defineConfig(), definePlugin() and defineAdapter() give the config file, a plugin and an adapter type checking and completion. ADAPTERS lists the built-in adapters in detection order, most specific first. See plugins for normalizers, cause detectors, route providers and reporters, and adapters for frameworks hydration-proof does not know.

function defineConfig(config: HydrationProofConfig): HydrationProofConfig

Identity helper that gives hydration-proof.config.ts type checking and completion.

function definePlugin(plugin: HydrationProofPlugin): HydrationProofPlugin

Identity helper with type checking for plugins.

function defineAdapter(definition: AdapterDefinition): Adapter

Create an adapter for a framework hydration-proof does not know.

ISSUES maps every issue code to its name, title, default severity and description. docsUrl() returns the documentation page of a code:

list-codes.ts
import { docsUrl, ISSUES } from "hydration-proof";
 
for (const [code, issue] of ISSUES) {
  console.log(`${code} ${issue.severity} ${issue.title} ${docsUrl(code)}`);
}
const ISSUES: ReadonlyMap<IssueCode, IssueDefinition>

Every issue code with its name, title, default severity and description. The issue code reference is generated from it.

function docsUrl(code: IssueCode): string

The documentation URL for an issue code, e.g. https://hydration.jscrate.dev/docs/issues/hp1001.

Schemas and versions

configJsonSchema() returns the JSON Schema of the config file, the same one published at /schema/config.json and shipped as node_modules/hydration-proof/schema/config.json. VERSION is the installed version. REPORT_SCHEMA_VERSION is the schemaVersion written into report.json, 1, and it changes only in a major release.

function configJsonSchema(): JsonSchema

The JSON Schema of the config file, the same one published at /schema/config.json.

const VERSION: string

The installed version of hydration-proof.

const REPORT_SCHEMA_VERSION = 1

The schemaVersion written into report.json. It changes only in a major release.

What is stable in the hydration-proof Node API

From 1.0 on, the stable parts follow semver: they change only in a major release, with a migration path.

  • Stable: run(), defineConfig, definePlugin, defineAdapter, mergeReports, ISSUES and the types they use; the exit codes; and report.json with schemaVersion: 1, where fields are added but never removed or repurposed.
  • Not stable: anything the package does not export; the HTML report's markup and embedded data; the wording of messages, the set of evidence entries and confidence scores; and which cause a finding is given. Treat a cause as an explanation for a person, not something to assert on.

A code's default severity can change in a minor release. If your script depends on what fails, set failOn or a budget explicitly. The compatibility page has the full list.