npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@sdxc/digest-fields

v2026.10.25

Published

RFC 9530 Content-Digest and Repr-Digest, plus the RFC 3230 Digest header

Readme

@sdxc/digest-fields

RFC 9530 Content-Digest and Repr-Digest, plus the RFC 3230 Digest header.

Compute, write, read and verify the HTTP digest fields, including the Want-Content-Digest and Want-Repr-Digest preferences, Unencoded-Digest and Want-Unencoded-Digest from HTTP Unencoded Digest, and the legacy Digest header that draft-cavage HTTP signatures cover.

Installation

npm add @sdxc/digest-fields

Every function answers a Result from @sdxc/result, which installs with this package. Digests run on the Web Crypto API.

Usage

Digest a body

import { digest } from "@sdxc/digest-fields";
import { unwrap } from "@sdxc/result";

let body = new TextEncoder().encode(JSON.stringify({ message: "Hello" }));
let bytes = unwrap(await digest(body, "sha-256")); // Uint8Array(32)

Write the field

import { stringify } from "@sdxc/digest-fields";

stringify({ "sha-256": bytes }, "content-digest"); // success("sha-256=:…:")
stringify({ "sha-256": bytes }, "digest"); // success("SHA-256=…")
stringify({ "sha-512": 3, "sha-256": 10 }, "want-content-digest"); // success("sha-512=3, sha-256=10")

Read a field

import { parse } from "@sdxc/digest-fields";

parse("sha-256=:X48E9qOokqqrvdts8nOJRJN3OWDUoyWxBf7kbu9DBPE=:", "content-digest");
// success({ "sha-256": Uint8Array(32) })

parse("SHA-256=X48E9qOokqqrvdts8nOJRJN3OWDUoyWxBf7kbu9DBPE=", "digest");
// success({ "sha-256": Uint8Array(32) })

parse("sha-512=3, sha-256=10", "want-content-digest");
// success({ "sha-512": 3, "sha-256": 10 })

Verify a received body

import { verify } from "@sdxc/digest-fields";
import { isFailure } from "@sdxc/result";

let checked = await verify(request.headers, await request.bytes(), { field: "content-digest" });
if (isFailure(checked)) return new Response(checked.error.message, { status: 400 });

Unencoded-Digest covers the representation before content coding. Pass decode: true when the body is still coded as Content-Encoding says (gzip, x-gzip, deflate, identity), and leave it off for a body fetch already decoded:

await verify(request.headers, await request.bytes(), { field: "unencoded-digest", decode: true });

API

digest(bytes: BinaryLike, algorithm: DigestAlgorithm): Promise<Result<Uint8Array, DigestError>>

Hashes a body with sha-256 or sha-512. BinaryLike is string | Uint8Array | ArrayBuffer; text is read as UTF-8, so pass the exact bytes sent when the body is not UTF-8 text.

let bytes = await digest(body, "sha-256");
// same as
let bytes = new Uint8Array(await crypto.subtle.digest("SHA-256", body));

parse(value: string, field: DigestField): Result<FieldValues[field], DigestError>

Reads a field into digest bytes (content-digest, repr-digest, digest) or preference weights from 0 to 10 (want-content-digest, want-repr-digest), keyed by lowercase algorithm name. The RFC 9530 fields follow the Structured Field grammar of @sdxc/structured-fields. Digest accepts algorithm names in any case, fails on a repeated algorithm, and skips an entry for another algorithm whose value is not base64, such as UNIXsum=30637.

stringify(value: FieldValues[field], field: DigestField): Result<string, DigestError>

Writes a field. Digest uppercases algorithm names (SHA-256=…), the form draft-cavage verifiers read. An empty value answers "", meaning "do not send the field".

verify(headers: Headers, body: BinaryLike, options: VerifyOptions): Promise<Result<void, DigestError>>

Checks a body against options.field: content-digest, repr-digest, unencoded-digest or digest. options.decode removes the Content-Encoding codings before checking unencoded-digest; decodeContent(body, contentEncoding) does the same on its own. Every sha-256 and sha-512 entry must match, so a forged entry next to a valid one still fails; entries for other algorithms are skipped.

DIGEST_ALGORITHMS

["sha-256", "sha-512"], the algorithms digest and verify compute.

DigestError

The failure every function answers with; code says which check failed:

| code | When | | ----------------------- | -------------------------------------------------------------- | | missing | verify found no such field | | malformed | The field text does not fit its grammar | | invalid | stringify got a name the field cannot carry or a bad weight | | unsupported-algorithm | No sha-256/sha-512 entry to check, or digest got another | | mismatch | The body differs from a digest | | coding | decode met an unknown content coding or a corrupt body | | crypto | The runtime refused the hash |

Types

DigestAlgorithm

"sha-256" | "sha-512".

DigestField and DigestValueField

"content-digest" | "repr-digest" | "unencoded-digest" | "want-content-digest" | "want-repr-digest" | "want-unencoded-digest" | "digest". DigestValueField is the four that carry digests.

Digests, Preferences and FieldValues

Record<string, Uint8Array> and Record<string, number>, keyed by lowercase algorithm name. FieldValues maps each field to the one it carries.

DigestErrorCode

The union of DigestError codes in the table above.

VerifyOptions

{ field: DigestValueField; decode?: boolean }.

Pattern: Signing-Ready Outbound Request

Attach both fields so RFC 9421 and draft-cavage verifiers each find the one their signature covers.

import { digest, stringify } from "@sdxc/digest-fields";
import { unwrap } from "@sdxc/result";

let body = new TextEncoder().encode(JSON.stringify({ type: "Create" }));
let digests = { "sha-256": unwrap(await digest(body, "sha-256")) };

let request = new Request("https://inbox.example.com/", {
	method: "POST",
	body,
	headers: {
		"content-type": "application/json",
		"content-digest": unwrap(stringify(digests, "content-digest")),
		digest: unwrap(stringify(digests, "digest")),
	},
});

Pattern: Verifying Whichever Field the Sender Used

Check Content-Digest first, fall back to Digest, and map each failure to a status.

import { verify } from "@sdxc/digest-fields";
import { isFailure } from "@sdxc/result";

async function checkBody(request: Request) {
	let body = await request.bytes();
	let field = request.headers.has("content-digest") ? "content-digest" : "digest";
	let checked = await verify(request.headers, body, { field });
	if (!isFailure(checked)) return body;

	switch (checked.error.code) {
		case "mismatch":
			return new Response("Body does not match its digest", { status: 422 });
		case "crypto":
			return new Response(null, { status: 500 });
		default:
			return new Response(checked.error.message, { status: 400 });
	}
}

Pattern: Answering Want-Content-Digest

Send the algorithm the client weighs highest, falling back to sha-256.

import { DIGEST_ALGORITHMS, digest, parse, stringify } from "@sdxc/digest-fields";
import { isSuccess, unwrap } from "@sdxc/result";

function preferred(request: Request) {
	let wanted = parse(request.headers.get("want-content-digest") ?? "", "want-content-digest");
	if (!isSuccess(wanted)) return "sha-256";
	let weights = wanted.data;
	let ranked = DIGEST_ALGORITHMS.filter((name) => (weights[name] ?? 0) > 0);
	ranked.sort((a, b) => (weights[b] ?? 0) - (weights[a] ?? 0));
	return ranked[0] ?? "sha-256";
}

async function respond(request: Request, data: unknown) {
	let body = new TextEncoder().encode(JSON.stringify(data));
	let algorithm = preferred(request);
	let bytes = unwrap(await digest(body, algorithm));

	return new Response(body, {
		headers: { "content-digest": unwrap(stringify({ [algorithm]: bytes }, "content-digest")) },
	});
}

Versioning

Releases are dated rather than semantic. A version is the UTC date it was published, written YYYY.M.D, so 2026.9.4 is the release from 4 September 2026. At most one release goes out per day.

Those numbers say when, not what: a later date means a later release and carries no compatibility promise. Any release may change or remove an export.

Depend on one exact date, and move it when you are ready to take the change:

{
	"dependencies": {
		"@sdxc/digest-fields": "2026.9.4"
	}
}

A caret or tilde range reads the date as major, minor and patch, so it accepts every later release in the same year. An exact version keeps the upgrade yours to schedule.

License

MIT

Author

Sergio Xalambrí