5.0 KiB
Design — @suaveplan/caddy-imgproxy
File layout
packages/media/image/caddy-imgproxy/
package.json # 0.1.0, exports per feature module
vite.config.ts # multi-entry build mirroring exports
vitest.config.ts # provider: istanbul, thresholds 100/100/100/100
tsconfig.lib.json
README.md
tests/setup/polyfills.ts
src/
index.ts # explicit named re-exports
parse/
parse-url.ts # parseImgproxyUrl
parse-url.test.ts
parse-url.md
index.ts # explicit named barrel
dimensions/
calculate-dimensions.ts # calculateDimensions
calculate-dimensions.test.ts
calculate-dimensions.md
index.ts
gravity/
gravity.ts # gravityToImgproxy + GRAVITY_MAP
gravity.test.ts
gravity.md
index.ts
operation/
operation.ts # operationToImgproxy
operation.test.ts
operation.md
index.ts
signing/
sign-path.ts # signImgproxyPath, HashLike injection
sign-path.test.ts
sign-path.md
index.ts
url-builder/
url-builder.ts # buildImgproxyUrl orchestrator
url-builder.test.ts
url-builder.md
index.ts
domain/
extract-domain.ts
extract-domain.test.ts
extract-domain.md
index.ts
handler/
handler.ts # ImgproxyHandler orchestrator
handler.test.ts
handler.md
index.ts
config/
config.ts # ImgproxyConfigSchema, parseConfig
config.test.ts
config.md
index.ts
error-codes/
error-codes.ts # CADDY_IMGPROXY_CODES + CaddyImgproxyError
error-codes.test.ts
error-codes.md
index.ts
telemetry/
telemetry.ts # counters + histogram
telemetry.test.ts
telemetry.md
index.ts
types/
types.ts # ParsedImgproxyUrl, ImgproxyConfig, RewriteResult
index.ts
URL grammar (verbatim port of Go regex)
^/
([su]) # 1 signed | unsigned
([vcfsr])? # 2 op (v/c/f/s/r) opt
(tl|tr|bl|br|[tblrc])? # 3 gravity opt
(?:
([1-9]\d*):([1-9]\d*) # 4,5 aspect W:H
| ([1-9]\d*)x(\d+) # 6,7 pixel WxH (W>0)
| (0)x([1-9]\d*) # 8,9 pixel 0xH (auto-width)
| ([1-9]\d*) # 10 square shorthand
)?
((?:[wh][1-9]\d*|q(?:100|[1-9]\d?))*) # 11 modifiers
/(.+)\.([a-z]+)$ # 12 path / 13 format
Modifier sub-regex: ([whq])(\d+) — extract w<int>, h<int>, q<int> in any order.
Dimensions algorithm
if (!hasDims) return { width: 0, height: 0 };
if (!isAspect) return { width: pixelW, height: pixelH };
// aspect path
if (hintType === "h" && hintValue > 0) {
height = hintValue;
width = Math.trunc((height * aspectW) / aspectH);
} else {
width = hintValue > 0 ? hintValue : defaultWidth;
height = Math.trunc((width * aspectH) / aspectW);
}
(Math.trunc matches Go integer division.)
Operation → imgproxy table
| op | both W&H | only W | only H | none |
|---|---|---|---|---|
| cover | g:<g>/rs:fill:W:H |
g:<g>/w:W |
g:<g>/h:H |
"" |
| fit | rs:fit:W:H |
w:W |
h:H |
"" |
| crop | g:<g>/c:W:H |
"" |
"" |
"" |
| stretch | rs:force:W:H |
"" |
"" |
"" |
| resize | — | w:W |
h:H |
"" |
"" (passthrough) |
"" |
"" |
"" |
"" |
Signing
HMAC-SHA256(keyBytes, saltBytes || pathBytes) → base64url, no padding. Implementation uses hmac from @suaveplan/crypto-hash with Uint8Array inputs. Hex key/salt decoded via fromHex.
Telemetry
| Instrument | Kind | Tags | Where |
|---|---|---|---|
caddy_imgproxy_parse_total |
counter | outcome ∈ `ok |
invalid |
caddy_imgproxy_rewrite_total |
counter | outcome ∈ `rewritten |
passthrough |
caddy_imgproxy_sign_total |
counter | mode ∈ `signed |
unsafe` |
caddy_imgproxy_rewrite_duration_ms |
histogram | — | ImgproxyHandler.rewrite |
Tier & deps
Universal-tier. Deps: @suaveplan/error, @suaveplan/types, @suaveplan/crypto-hash, @suaveplan/opentelemetry, @opentelemetry/api, tslib, zod. DevDeps: @suaveplan/testing, @vitest/coverage-istanbul, typescript, vite, vite-plugin-dts, vitest, @types/node.
Test plan
Re-port every imgproxy_test.go case to parse-url.test.ts + dimensions/...test.ts + operation/...test.ts + gravity/...test.ts + domain/...test.ts + url-builder/...test.ts. Plus genesis-only tests: Zod boundary failures, error-code reachability, telemetry emission via a real MeterProvider, signing KAT against a fixed key/salt pair (mirrors Go signPath output).