143 lines
5.0 KiB
Markdown
143 lines
5.0 KiB
Markdown
# Design — @suaveplan/caddy-imgproxy
|
|
|
|
## File layout
|
|
|
|
```text
|
|
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)
|
|
|
|
```text
|
|
^/
|
|
([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
|
|
|
|
```ts
|
|
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|unknown_op|missing_dims` | `parseImgproxyUrl` |
|
|
| `caddy_imgproxy_rewrite_total` | counter | `outcome` ∈ `rewritten|passthrough|invalid`, `operation` | `ImgproxyHandler.rewrite` |
|
|
| `caddy_imgproxy_sign_total` | counter | `mode` ∈ `signed|unsafe` | `buildImgproxyUrl` |
|
|
| `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).
|