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

68 lines
6.2 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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).