# Purifai
> A fixed-policy HTML-to-text converter for Node.js, Bun, Deno, Cloudflare Workers and browsers. It converts untrusted, malformed or hostile HTML into readable plain text with deterministic resource limits, using no DOM, no document tree and no runtime dependencies. It is not an HTML sanitizer: it returns text, never safe markup.
Purifai targets a narrow intersection — reader-friendly output, non-reader bodies dropped, hostile-input bounds enforced during scanning, chunk-invariant streaming, and portability across every JavaScript runtime. The fixed scope is the reason to choose it.
## Install
```sh
npm install purifai
```
Requires Node.js 22 or newer when used in Node. Ships ESM and CommonJS exports. Zero runtime dependencies. For the current published version see the npm page linked below.
## API
- [`toText(html, options?) → string`](https://github.com/moji2002/purifai#api-reference): Converts one HTML string to readable text. Throws `TypeError` for invalid input or options, and `PurifaiLimitError` for a breached limit. Never truncates.
- [`convert(html, options?) → ConversionResult`](https://github.com/moji2002/purifai#api-reference): Text plus a frozen report — `truncatedBy`, `scanComplete`, `consumedInputCodeUnits`, `outputCodeUnits`, `droppedContainers`. The only API that can return a deliberately truncated prefix, and only with `overflow: 'truncate'`.
- [`createTextTransform(options?) → TextTransform`](https://github.com/moji2002/purifai#streaming): A native `TransformStream` with a `result` promise for the frozen report. Output is invariant across chunk boundaries. Limit failures reject both the stream and the promise with the same error object.
- [`escapeHtmlText(text) → string`](https://github.com/moji2002/purifai#safe-output): Losslessly encodes `&`, `<`, `>`, `"` and `'` for an HTML **text-node** context only.
- [`PurifaiLimitError`](https://github.com/moji2002/purifai#api-reference): Extends `RangeError`; exposes `kind`, `limit` and `observed`.
## Options
Unknown keys and invalid values throw `TypeError`. Purifai does not guess around configuration mistakes, so do not invent option names.
- `layout`: `'readable'` (default) or `'compact'`.
- `links`: `'label'` (default), `'label-and-url'`, or `'drop'`.
- `images`: `'alt'` (default) or `'drop'`.
- `baseUrl`: `string | URL` — resolves relative display URLs against a credential-free HTTP(S) base.
- `overflow`: `'throw'` (default) or `'truncate'`. Honoured by `convert` only; every other API always throws.
- `limits.input`: default `1_000_000` — maximum input UTF-16 code units consumed.
- `limits.output`: default `250_000` — maximum output UTF-16 code units emitted.
- `limits.depth`: default `64` — maximum live structural nesting.
- `limits.token`: default `65_536` — maximum aggregate retained token and attribute code units.
All limits measure UTF-16 code units, not encoded bytes, and are enforced before unbounded caller-controlled state can accumulate.
## Notes for code generation
- **The return value is a string, not safe HTML.** Assigning it to `innerHTML` unescaped is a bug. Prefer a text sink: `element.textContent = toText(untrustedHtml)`.
- If an HTML text node is the only available sink, escape explicitly: `element.innerHTML = escapeHtmlText(toText(untrustedHtml))`.
- `escapeHtmlText` is for an HTML text context **only**. It does not make a value safe for an attribute, a URL, JavaScript, CSS or template source. A displayed URL is still text; moving it into `href` requires a separate URL-policy decision.
- Do not reach for `convert` when a bounded prefix is unacceptable — use `toText` and let it throw.
- Do not import from `purifai/dist` or any deep path. The package exposes a single `.` export.
- There is no DOM requirement, no `jsdom` peer, and no Node built-in dependency. Do not add one.
- `label-and-url` emits destinations as display text, never as active links. It accepts absolute `http:`, `https:` and `mailto:` URLs; relative URLs require a validated HTTP(S) `baseUrl`.
## Extraction policy
Drops source and non-reader bodies including `script`, `style`, `template`, `iframe`, `noscript`, `noembed`, `noframes`, `svg` and `math`. Preserves headings, paragraphs, lists, quotes, code, simple tables, link labels and image alternatives, plus selected fallback and form text. Decodes the complete pinned WHATWG character-reference set, preserves literal `xmp`, and treats `plaintext` as text through end of input.
This is a bounded extraction grammar, not browser tree construction. It does **not** recreate CSS layout, browser `innerText`, complex `rowspan`/`colspan` tables, SVG or MathML semantics, selector rules, custom formatters, or browser-equivalent malformed-markup recovery. If a task needs selector-driven formatting, complex table layout, or allow-listed safe HTML, Purifai is the wrong tool.
## Docs
- [README — full documentation](https://github.com/moji2002/purifai#readme): install, quick start, safe output, streaming, bounded conversion, options, extraction policy, API reference.
- [npm package](https://www.npmjs.com/package/purifai): current published version and release provenance.
- [Project notes](https://worksonmy.dev/projects/purifai): the design rationale and engineering trade-offs.
## Optional
- [Benchmark methodology and raw results](https://github.com/moji2002/purifai/blob/main/docs/benchmarks/v3.md): pinned against `striptags@3.2.0` and `html-to-text@10.0.0`. Results are machine-, runtime- and corpus-specific. `striptags` remains faster on some flat-strip cases; that is not Purifai's claim.
- [v3 migration guide](https://github.com/moji2002/purifai/blob/main/docs/migration-v3.md)
- [Issues](https://github.com/moji2002/purifai/issues)