@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/carrierTyping 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-typesThat 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 --checkNote 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.
