On this page
  1. Config
  2. normalizeAssets
  3. The committed manifest
  4. readCommittedManifest
  5. The media: reference codec
  6. parseMediaToken
  7. mediaToken
  8. The render resolver
  9. makeMediaResolver
  10. Types

Media (@glw907/cairn-cms/media)

This subpath holds the node-safe media surface a site actually reaches into: the config normalizer, reading the committed manifest, the media: reference codec, and the render-time resolver. All of it is pure projection. Nothing here pulls @sveltejs/kit or @cloudflare/workers-types into the module graph, so a plain-Node tool, a Vite build step, or a site’s render path can import it. The R2-touching pieces (the object store and the delivery bucket seam), the requireBucket helper, and the createMediaRoute delivery factory live on /sveltekit instead, off this surface, so the public type for /media names no kit or workers-types type. The manifest CRUD, the content-hash naming helpers, and the Cloudflare Images transform-URL builders back the engine’s own upload and preview pipeline; every real caller reaches them by relative import, so they stay unexported internals rather than public surface.

import { makeMediaResolver, normalizeAssets } from '@glw907/cairn-cms/media';

A site reaches into this subpath to back its public render path with a media resolver: it reads the committed media.json, normalizes its adapter assets block, and threads the resolver through render, so a media: reference in published content rewrites to its delivery URL. The TypeScript types in src/lib/media and src/lib/render/resolve-media.ts are the source of truth, and the export-coverage gate checks every name here against them.

import { normalizeAssets, makeMediaResolver } from '@glw907/cairn-cms/media';
import mediaManifest from '../content/.cairn/media.json';

const resolveMedia = makeMediaResolver(mediaManifest, normalizeAssets({ bucketBinding: 'MEDIA_BUCKET' }));

Config

normalizeAssets

Stability tier: Extension API.

declare function normalizeAssets(assets: AssetConfig | undefined): ResolvedAssetConfig;

Validate a site’s AssetConfig and resolve it into the engine-internal ResolvedAssetConfig the upload, storage, delivery, and render paths read. An absent block leaves media off and returns { enabled: false } rather than throwing. A declared block must name its R2 bucket binding and carry a known urlForm and valid variant fit and gravity values; each failure throws a cairn:-prefixed error. The named variants merge over the built-in presets.


The committed manifest

The media manifest is a small git-committed record, one row per stored asset, keyed by the 16-hex content-hash prefix. It carries the human layer the bytes cannot (display name, alt text, original filename) and is the dedup lookup an ingest checks before storing.

readCommittedManifest

Stability tier: Extension API.

declare function readCommittedManifest(globResult: Record<string, unknown>): MediaManifest;

Read the committed manifest from an import.meta.glob eager result, degrading a missing file to an empty manifest. A static import of an absent media.json fails the Vite build before any runtime degrade can run, so a fresh site can’t build. A glob with no match returns {} instead of throwing. The consumer passes import.meta.glob('<path-to-media.json>', { eager: true, import: 'default' }), and this helper extracts the single matched value and parses it.


The media: reference codec

A media reference is the logical handle content commits to git, keyed to a content-hash prefix so the same bytes resolve no matter where they are stored or what they are named. The canonical form is media:<slug>.<hash>, with the bare media:<hash> form also valid.

parseMediaToken

Stability tier: Extension API.

declare function parseMediaToken(href: string): MediaRef | null;

Parse a media:<slug>.<hash> href (or the bare media:<hash> form), or null for any other href or a malformed token.

mediaToken

Stability tier: Extension API.

declare function mediaToken(ref: MediaRef): string;

Write the canonical media: token for a ref, the inverse of parseMediaToken, so a parse then write round trip is stable.


The render resolver

makeMediaResolver

Stability tier: Extension API.

declare function makeMediaResolver(
  manifest: MediaManifest,
  resolved: ResolvedAssetConfig,
  opts?: { preset?: string },
): MediaResolve;

Build the per-call media resolver, closing over the committed manifest and the resolved config. The resolver looks a ref’s content hash up in the manifest and builds the canonical delivery path from the manifest entry’s slug and ext, not the token’s, so a rename never breaks the reference. With a preset and zone transformations on it returns the variant URL; otherwise it returns the bare full-size path. It returns undefined when media is off or no entry carries the hash. The undefined return is the preview-miss backstop. A site threads the resolver through render via the resolveMedia option.

The resolved image also carries whatever layout and responsive-delivery detail the manifest and config can honestly derive, with no new option to wire: a manifest entry’s recorded width/height land as intrinsic width/height attributes on the rendered <img> (reserving its aspect ratio and avoiding a layout shift on load), and with the site’s assets.transformations on and the width known, the image also gets a srcset built from a small fixed width ladder through the same variant-URL mechanism a preset uses, plus a sizes hint derived from the image’s enclosing :::figure placement role (center, wide, full; a bare image or an unplaced figure falls back to 100vw). An asset whose dimensions are unknown (the upload’s client did not report them) or whose width is too small to offer more than one honest srcset candidate gets none of that. The engine never substitutes a guessed value. A raw external image (not a media: reference) is untouched either way; reserving its layout is a template-level concern, since the engine has no dimensions to derive it from.


Types

NameStabilitySignatureMeaning
ResolvedAssetConfigExtension APItype ResolvedAssetConfig = { enabled: false } | { enabled: true; bucketBinding; publicBase; urlForm; maxUploadBytes; allowedTypes; variants; transformations }The resolved media config the engine serves from. An absent assets block yields the { enabled: false } variant; otherwise every field is filled from the AssetConfig or its default.
MediaEntryExtension APIinterface MediaEntry { hash; sha256; slug; displayName; originalFilename; alt; ext; contentType; bytes; width; height; createdAt }One stored asset’s row: its content hash, its human layer, and its byte and pixel facts. width and height are null when no dimensions are known.
MediaManifestExtension APItype MediaManifest = Record<string, MediaEntry>The whole stored-asset record, keyed by the 16-hex content-hash prefix.
VariantSpecExtension APIinterface VariantSpec { width?; height?; quality?; fit?; gravity?; format? }A single image variant: the resize and format directives Cloudflare Images applies to the original bytes.
MediaRefExtension APIinterface MediaRef { slug: string | null; hash: string }A resolved reference to a media asset by its content-hash prefix, with an optional display slug.
MediaResolveExtension APItype MediaResolve = (ref: MediaRef) => string | undefinedThe per-call resolver render reads under resolveMedia. undefined is a preview miss; a resolver that throws is the build backstop.

Edit this page on GitHub(opens in a new tab)