Files
2026-05-27 23:29:53 +01:00

6.2 KiB
Raw Permalink Blame History

Enterprise migration: @suaveplan/caddy-imgproxy

Package: @suaveplan/caddy-imgproxy Status: PROPOSED Type: new-package (migration from upstream) Date: 2026-05-25 Design: ./design.md Spec delta: ./specs/caddy-imgproxy/spec.md

Why

Upstream ../suaveplan/packages/server/caddy-imgproxy/ ships a Go Caddy module that rewrites compact URL grammars (/u<op><gravity><dims><modifiers>/<path>.<format>) into signed imgproxy URLs. The TypeScript side of the package is a 4-line placeholder. Genesis needs a real, universal-tier TypeScript port so the same grammar can be consumed from any genesis runtime (CDN workers, edge gateways, build-time tooling, integration tests for Caddy stacks) without leaving the monorepo's enterprise quality bar.

The migration re-implements the Go semantics exactly — same regex grammar, same gravity/operation mapping, same HMAC-SHA256 signing, same s3://<bucket>/<domain>/<path>@<format> layout — and adds the standard genesis layer: stable error codes via defineCodes, Zod-validated config, injectable Clock for deterministic spans, OTel counters + a duration histogram, and full 100% test coverage. The resulting package is the canonical reference for the URL grammar; Caddy hosts can either delegate to it via a sidecar or treat the Go module as a parallel implementation tested against the same fixtures.

What changes

New package @suaveplan/caddy-imgproxy at packages/media/image/caddy-imgproxy/. Public surface:

  • ADDED: parseImgproxyUrl(path) — parses the compact grammar (/u, /s, v|c|f|s|r, tl|tr|bl|br|t|b|l|r|c, aspect / pixel / square dims, w|h|q modifiers) into a typed ParsedImgproxyUrl. Mirrors the Go parseURL semantics byte-for-byte; raises CaddyImgproxyError(INVALID_URL | UNKNOWN_OPERATION | MISSING_DIMENSIONS).
  • ADDED: calculateDimensions(parsed, defaultWidth) — resolves aspect-ratio + hint into concrete (width, height) pixels with the same precedence as Go (h hint takes height-first, w hint or default takes width-first).
  • ADDED: gravityToImgproxy(code) — translates t|b|l|r|c|tl|tr|bl|br to imgproxy's no|so|we|ea|ce|nowe|noea|sowe|soea.
  • ADDED: operationToImgproxy(op, width, height, gravity) — emits the imgproxy processing string (g:<g>/rs:fill:W:H for cover, rs:fit:W:H for fit, g:<g>/c:W:H for crop, rs:force:W:H for stretch, w:W/h:H for resize).
  • ADDED: signImgproxyPath({ key, salt, path, hash? }) — HMAC-SHA256 over salt || path (raw bytes), base64url no-padding encoded. Uses @suaveplan/crypto-hash's hmac + toBase64Url; injectable hash for offline / native swaps.
  • ADDED: buildImgproxyUrl({ parsed, domain, config }) — composes the full imgproxy path including signature prefix (or /unsafe when no key/salt). Inserts q:<quality> segment when present.
  • ADDED: extractDomain(host) — strips port, drops the images. subdomain.
  • ADDED: ImgproxyHandler — orchestrator with rewrite(request): returns { rewritten: true; targetPath } | { rewritten: false; reason } for downstream proxy clients. Accepts injectable Clock, LoggerLike, IdGenerator for tracing; tagged spans + counters emitted on every call.
  • ADDED: ImgproxyConfigSchema (Zod) and parseConfig(raw) — validates upstream, s3Bucket, defaultWidth >= 1, hex-encoded key/salt (even-length, lower-hex).
  • ADDED: CADDY_IMGPROXY_CODES + CaddyImgproxyError via defineCodes("CADDY_IMGPROXY", [...]) — codes INVALID_URL, UNKNOWN_OPERATION, MISSING_DIMENSIONS, INVALID_OPTIONS, INVALID_KEY, INVALID_SALT, INVALID_HOST.
  • ADDED: OTel instruments at meter @suaveplan/caddy-imgproxy — counters caddy_imgproxy_parse_total{outcome}, caddy_imgproxy_rewrite_total{outcome,operation}, caddy_imgproxy_sign_total{mode}; histogram caddy_imgproxy_rewrite_duration_ms.

Parity Checklist

Capability Upstream Migrated Notes
URL grammar parsing (/u, /s, ops, gravity, dims, modifiers) ✅ (Go) ✅ TS port mirrors regex + state machine verbatim
Aspect-ratio → pixel calculation with w/h hint ✅ ✅
Square-shorthand dims (e.g. /uv128/avatar.png) ✅ ✅
Quality modifier q1..q100 ✅ ✅
Gravity codes (9 directions) ✅ ✅
Operation → imgproxy processing string ✅ ✅
HMAC-SHA256 URL signing, base64url no-pad ✅ ✅ Via @suaveplan/crypto-hash
/unsafe fallback when no key/salt ✅ ✅
S3 path s3://bucket/domain/path@format ✅ ✅
Domain extraction (images.example.com → example.com) ✅ ✅
Caddyfile directive parsing ✅ (Go-only) 🚫 Caddyfile lives in the Go module; TS package targets the URL-grammar contract. Justified: the Caddyfile is a Caddy-runtime artefact with no TypeScript equivalent; consumers wanting Caddy hosting use the Go module, the TS package serves edge/CDN/test consumers.
HTTP reverse proxy execution ✅ (Go) 🚫 Same justification: handler runs inside Caddy. TS package returns a rewrite decision; the caller owns the network round trip.
Structured logging (zap) ✅ ⬆️ Replaced with injectable LoggerLike from @suaveplan/types
Defaults (localhost:8080, images, 800) ✅ ✅ Encoded in ImgproxyConfigSchema
Stable error codes via defineCodes 🚫 ⬆️ Genesis-only: typed CaddyImgproxyError
Zod-validated public config 🚫 ⬆️ Genesis-only
OTel counters + histogram 🚫 ⬆️ Genesis-only
100% test coverage ✅ (Go) ✅ TS port re-implements every Go test case

Impact

Affected dependents

Run bunx turbo ls --filter=...@suaveplan/caddy-imgproxy to enumerate. No existing dependents — fresh package landing.

  • No dependents (verified by the package being at – in STATUS.md and absent from any workspace:^ consumer).

Out of scope

  • The Go Caddy module itself is untouched. The Go source remains in ../suaveplan/packages/server/caddy-imgproxy/. A future change MAY align the two via a shared test-vector corpus, but that work is not in this proposal.
  • HTTP reverse-proxy delivery (TS package returns rewrite decisions only).
  • Caddyfile directive parsing (Caddy runtime concern).