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

@archival/carrier

v0.17.0

Published

TypeScript types for archival carriers

Readme

@archival/carrier

TypeScript types for archival carriers — server side functions deployed alongside your site, one per directory under carriers/.

cd carriers/my-carrier
npm install --save-dev @archival/carrier

Typing a carrier

A carrier default-exports a function of (params, body, objects):

import type { Carrier } from "@archival/carrier";

const carrier: Carrier = async (params, body, objects) => ({
  hello: params.get("name"),
  site: objects.SITE_URL,
});

export default carrier;

Return an object to send JSON, or a string to send text/plain. A string prefixed with redirect: becomes a 302 to the rest of the string. Return a Response to send anything else.

Reading uploads

objects.UPLOADS reads the files uploaded to your site. Reads are scoped to your site, and there is no way to write.

list() gives every upload, as the name it was uploaded with and the content hash it is stored under:

await objects.UPLOADS.list();
// [{ sha: "0c1f…", filename: "menu.pdf" }, …]

get() reads one, by name:

const upload = await objects.UPLOADS.get("menu.pdf");

It resolves to null when nothing matches. Names are not unique — the same name can be uploaded more than once, under different hashes — so looking one up by name alone throws when it is ambiguous. Pass the hash to say which you meant, or pass a file straight off objects, which carries its own:

await objects.UPLOADS.get("menu.pdf", "0c1f…");
await objects.UPLOADS.get(objects.settings.menu);

Serving one back is a Response, which is what lets a carrier put a file behind a check the site itself can't make:

const carrier: Carrier = async (params, body, objects) => {
  if (params.get("password") !== objects.settings.download_password) {
    return "redirect:/login";
  }
  const upload = await objects.UPLOADS.get("private-menu.pdf");
  if (!upload) {
    return "redirect:/404";
  }
  const headers = new Headers();
  upload.writeHttpMetadata(headers);
  headers.set("etag", upload.httpEtag);
  return new Response(upload.body, { headers });
};

Typing your site's objects

objects is your site's archival objects merged with the vars archival injects (SITE_URL, UPLOADS). Every site's objects are different, so the types for them are generated from your objects.toml:

npx archival-carrier-types

That runs archival types and writes an archival-objects.d.ts next to each carrier. Commit those files — they contain no object values, only your schema, and committing them means your editor and CI work on a fresh clone with no extra setup.

After generating, objects is fully typed:

const carrier: Carrier = async (params, body, objects) => ({
  titles: objects.posts.map((post) => post.title), // (string | null)[]
  contact: objects.settings.contact,
  hero: objects.posts[0].hero?.url,
});

Objects backed by a directory (objects/posts/*.toml) are arrays; objects backed by a single file (objects/settings.toml) are not. Unset fields are null. Child objects default to []. Every object read from its own file also carries path and order.

Until you generate, reading anything but the injected vars off objects is a compile error — deliberately, so a carrier can't silently read an object that isn't there.

You need the archival binary on your PATH or in your node_modules (npm install --save-dev archival, or cargo install archival).

Options

| Flag | | | ----------------------- | -------------------------------------------------------------- | | --check | Don't write; exit non-zero if anything is out of date. For CI. | | --carrier <name> | Only generate for one carrier. | | --carriers-dir <path> | Carriers directory, if not carriers. | | --site <path> | Site root, if not an ancestor of the working directory. | | --out <path> | Write a single file here instead of one per carrier. | | --archival <path> | Path to the archival binary. |

To keep generated types honest in CI:

npx archival-carrier-types --check

Note on secret fields

Fields declared secret are hidden from templates, but carriers run on the server and do receive their values. They are typed as string | null like any other string.