Metadata-Version: 2.4
Name: accept-ch-parse
Version: 0.1.2
Summary: Zero-dependency RFC 8942 Accept-CH (Client Hints) HTTP header parser and serializer for Node.js (≥18, ESM+CJS) and Python (≥3.11)
Author: repo-factory
License: MIT
Keywords: http,client-hints,accept-ch,rfc8942,parser,header
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: License :: OSI Approved :: MIT License
Classifier: Operating System :: OS Independent
Classifier: Topic :: Internet :: WWW/HTTP
Requires-Python: >=3.11
Description-Content-Type: text/markdown
License-File: LICENSE
Provides-Extra: test
Requires-Dist: pytest>=7.0; extra == "test"
Dynamic: license-file

# accept-ch-parse

Zero-dependency parser for the HTTP **`Accept-CH`** Client Hints header (RFC 8942) — for **Node.js** and **Python**.

- Parses `Accept-CH` into a list of tokens with quality (`q`) values
- Sorts output by descending `q` (stable: ties keep input order)
- Token names are case-sensitive per RFC 8942
- Forward-compatible: unregistered tokens are still parsed
- Empty / whitespace-only headers yield `[]` (not an error)
- Zero runtime dependencies on both platforms

## Why

HTTP Client Hints (RFC 8942) let servers request device / connection information via the `Accept-CH` response header. Example:

```
Accept-CH: Width, Viewport-Width, Content-DPR;q=0.8
```

When this library was first published (2026-08-08), no zero-dependency
parser for `Accept-CH` existed on either registry — the names were free:

- npm `accept-ch-parse` — confirmed 404 at build time (2026-08-08)
- PyPI `accept-ch-parse` — confirmed 404 at build time (2026-08-08)

This library fills that gap. It is small, dual-language, has zero runtime
deps, and gets out of your way. If you are reading this README after
2026-08-08 and the names are no longer 404, someone has published a
typosquat or fork — please verify the package you install matches the
GitHub repo at `prasad-a-abhishek/accept-ch-parse` before depending on it.

## Install

### Node.js (≥18)

```bash
npm install accept-ch-parse
```

ESM:

```javascript
import { parseAcceptCH, serializeAcceptCH, getAcceptCHTokens } from 'accept-ch-parse';
```

CJS:

```javascript
const { parseAcceptCH, serializeAcceptCH, getAcceptCHTokens } = require('accept-ch-parse');
```

### Python (≥3.11)

```bash
pip install accept-ch-parse
```

```python
from accept_ch_parse import parse_accept_ch, serialize_accept_ch, get_accept_ch_tokens
```

## Usage

### Basic parse

```javascript
import { parseAcceptCH } from 'accept-ch-parse';

parseAcceptCH('Viewport-Width, Width, Content-DPR');
// → [
//     { token: 'Viewport-Width', q: 1.0, raw: 'Viewport-Width' },
//     { token: 'Width',          q: 1.0, raw: 'Width' },
//     { token: 'Content-DPR',    q: 1.0, raw: 'Content-DPR' }
//   ]
```

```python
from accept_ch_parse import parse_accept_ch

parse_accept_ch('Viewport-Width, Width, Content-DPR')
# → [AcceptCHToken(token='Viewport-Width', q=1.0, raw='Viewport-Width'),
#     AcceptCHToken(token='Width',          q=1.0, raw='Width'),
#     AcceptCHToken(token='Content-DPR',    q=1.0, raw='Content-DPR')]
```

### Quality weights — sorted by descending `q`

```javascript
parseAcceptCH('Width;q=0.5, Viewport-Width, Content-DPR;q=0.8');
// → [
//     { token: 'Viewport-Width', q: 1.0, raw: 'Viewport-Width' },
//     { token: 'Content-DPR',    q: 0.8, raw: 'Content-DPR;q=0.8' },
//     { token: 'Width',          q: 0.5, raw: 'Width;q=0.5' }
//   ]
```

### Serialize

```javascript
import { serializeAcceptCH } from 'accept-ch-parse';

serializeAcceptCH([
  { token: 'Viewport-Width', q: 1.0, raw: 'Viewport-Width' },
  { token: 'Width',          q: 0.5, raw: 'Width' }
]);
// → 'Viewport-Width, Width;q=0.5'
```

### Just the token names

```javascript
import { getAcceptCHTokens } from 'accept-ch-parse';

getAcceptCHTokens('Width;q=0.5, Viewport-Width, Content-DPR;q=0.8');
// → ['Viewport-Width', 'Content-DPR', 'Width']   (already q-sorted)
```

## Recognized Client Hints

The parser is forward-compatible — **any** syntactically valid token is accepted. The eight registered per RFC 8942 are:

| Token | Meaning |
|---|---|
| `Viewport-Width` | Layout viewport width in CSS pixels |
| `Width` | Resource width in CSS pixels |
| `Content-DPR` | Resource's intrinsic DPR (paired with `Content-Length`) |
| `DPR` | Current device pixel ratio |
| `Device-Memory` | Approximate device RAM in GiB |
| `ECT` | Effective connection type (`slow-2g`, `2g`, `3g`, `4g`) |
| `RTT` | Round-trip time in ms |
| `Downlink` | Downlink speed in Mbps |

## API

### Node.js

```typescript
parseAcceptCH(header: string): AcceptCHToken[]
serializeAcceptCH(tokens: AcceptCHToken[]): string
getAcceptCHTokens(header: string): string[]

interface AcceptCHToken {
  token: string;   // case-sensitive
  q: number;       // 0..1, default 1.0
  raw: string;     // exact substring parsed (trimmed)
}
```

Throws `TypeError` if `header` is not a string (including `null` / `undefined`).

### Python

```python
parse_accept_ch(header: str) -> list[AcceptCHToken]
serialize_accept_ch(tokens: list[AcceptCHToken]) -> str
get_accept_ch_tokens(header: str) -> list[str]

class AcceptCHToken(NamedTuple):
    token: str
    q: float
    raw: str
```

Raises `TypeError` if `header` is not a string (including `None`).

## Errors

| Input | Behaviour |
|---|---|
| `''` / `'   '` | Returns `[]` |
| `null` / `undefined` / non-string (Node) | `TypeError` |
| `None` / non-`str` (Python) | `TypeError` |
| `Width;q=0.5` | Parsed; `q = 0.5` |
| `Width;Q=0.5` | Parsed; param names are case-insensitive (RFC 7230) |
| `Width;q=0, X` | `Width` sorts last; `q` clamped to `[0, 1]` |
| `Width;q=2.5` | Clamped to `1.0` |
| `Width;q=-0.5` | Clamped to `0.0` |
| `Width;q=` (malformed) | Treated as `q = 1.0` (forgiving) |
| `Width,,X` | Empty entry dropped; `Width` and `X` parsed |
| `viewport-width` | Parsed as-is (case-sensitive, NOT folded to `Viewport-Width`) |

## Run the tests

```bash
# Node
npm install
npm test

# Python
pip install -e .[test]
python -m pytest
```

The full suite is **60 Node.js tests** + **61 Python tests**, all green.

## Limitations / Non-goals

Out of scope for this library (intentionally, per spec):

- **Sending** `Accept-CH` or any other HTTP header — this library **parses** only.
- `Save-Data` header parsing — different header, different spec.
- `Vary` header parsing — different header, different semantics.
- A token registry — unregistered tokens are still accepted (forward compatibility). If you need to reject unknown tokens, do that at a higher layer.
- Quality value arithmetic (sum, threshold checks, etc.) — just parse and sort.
- Browser or HTTP client implementation.

## References

- [RFC 8942 — HTTP Client Hints](https://datatracker.ietf.org/doc/html/rfc8942)
- [MDN — Accept-CH](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Accept-CH)
- [MDN — Client Hints infrastructure](https://developer.mozilla.org/en-US/docs/Web/HTTP/Client_hints_infrastructure)
- [Chrome Platform Status — Client Hints](https://chromestatus.com/feature/6378878493753344)
- [Web.dev — Responsive images with Client Hints](https://web.dev/articles/responsive-images-client-hints)

## License

MIT — see `LICENSE`.
