first commit
This commit is contained in:
86
docs/media/image/caddy-imgproxy/src/config/config.md
Normal file
86
docs/media/image/caddy-imgproxy/src/config/config.md
Normal file
@@ -0,0 +1,86 @@
|
||||
# config
|
||||
|
||||
<!-- begin:auto-docs -->
|
||||
_(no submodules)_
|
||||
<!-- end:auto-docs -->
|
||||
|
||||
## Purpose
|
||||
|
||||
The `config` module exposes the Zod schema and the validating
|
||||
`parseConfig` function for the package's public `ImgproxyConfig`. It
|
||||
is the single boundary where a raw object — typically loaded from
|
||||
environment variables, JSON, or a Caddyfile shim — is converted into
|
||||
the immutable, defaulted `ImgproxyConfig` that the URL builder and
|
||||
the handler consume.
|
||||
|
||||
`parseConfig` raises stable, typed `CaddyImgproxyError` instances on
|
||||
every failure path: `INVALID_OPTIONS` for general schema failures,
|
||||
`INVALID_KEY` for a malformed `key`, and `INVALID_SALT` for a
|
||||
malformed `salt`. The schema applies the same defaults the upstream
|
||||
Go module ships with (`localhost:8080`, `images`, `800`) so a bare
|
||||
`parseConfig({})` returns a usable production configuration.
|
||||
|
||||
## Features
|
||||
|
||||
- Zod schema is exported so callers can compose it into larger
|
||||
configs.
|
||||
- Defaults match the Go reference exactly.
|
||||
- Hex validation built into the schema — even-length, lower-case
|
||||
only.
|
||||
- Distinct error codes for key vs salt failures so consumers can
|
||||
surface targeted remediation in their UI.
|
||||
- The returned `ImgproxyConfig` is frozen for safe sharing.
|
||||
|
||||
## Usage: Basic
|
||||
|
||||
```ts
|
||||
import { parseConfig } from "@suaveplan/caddy-imgproxy";
|
||||
|
||||
const cfg = parseConfig({});
|
||||
cfg.upstream; // "localhost:8080"
|
||||
cfg.s3Bucket; // "images"
|
||||
cfg.defaultWidth; // 800
|
||||
```
|
||||
|
||||
## Usage: Advanced
|
||||
|
||||
```ts
|
||||
import { parseConfig, ImgproxyConfigSchema } from "@suaveplan/caddy-imgproxy";
|
||||
import z from "zod";
|
||||
|
||||
// Compose into a larger application schema:
|
||||
const AppEnvSchema = z.object({
|
||||
NODE_ENV: z.enum(["development", "production"]),
|
||||
IMGPROXY: ImgproxyConfigSchema,
|
||||
});
|
||||
|
||||
const env = AppEnvSchema.parse(process.env);
|
||||
const cfg = parseConfig(env.IMGPROXY);
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
### `ImgproxyConfigSchema`
|
||||
|
||||
A Zod object schema with the following fields:
|
||||
|
||||
- `upstream: string` — default `"localhost:8080"`, non-empty.
|
||||
- `s3Bucket: string` — default `"images"`, non-empty.
|
||||
- `defaultWidth: number` — positive integer, default `800`.
|
||||
- `key?: string` — optional even-length lower-hex string.
|
||||
- `salt?: string` — optional even-length lower-hex string.
|
||||
|
||||
### `parseConfig(raw: unknown): ImgproxyConfig`
|
||||
|
||||
- **Returns:** a frozen `ImgproxyConfig`.
|
||||
- **Throws:** `CaddyImgproxyError` with one of `INVALID_OPTIONS`,
|
||||
`INVALID_KEY`, `INVALID_SALT`.
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
`safeParse` is used instead of `parse` so the function can branch on
|
||||
the first issue's `path` to choose the appropriate error code. The
|
||||
schema lifts the hex check into a `refine` so the Zod issue path is
|
||||
exactly `["key"]` or `["salt"]` when the hex format fails — that
|
||||
makes the `parseConfig` dispatch one-liner reliable. The returned
|
||||
config is frozen so downstream code cannot mutate it accidentally.
|
||||
@@ -0,0 +1,77 @@
|
||||
# calculateDimensions
|
||||
|
||||
<!-- begin:auto-docs -->
|
||||
_(no submodules)_
|
||||
<!-- end:auto-docs -->
|
||||
|
||||
## Purpose
|
||||
|
||||
`calculateDimensions` resolves a `ParsedImgproxyUrl` plus the
|
||||
configured `defaultWidth` into a concrete `{ width, height }` pair in
|
||||
integer pixels. It is the single source of truth for how the compact
|
||||
grammar's aspect-ratio + hint syntax becomes pixel values; every
|
||||
processing string emitted by `operationToImgproxy` is derived from
|
||||
this result.
|
||||
|
||||
Semantics mirror the upstream Go `calculateDimensions` byte-for-byte:
|
||||
no dimensions → `{0, 0}` (passthrough); pixel dimensions return
|
||||
verbatim; aspect-ratio with an `h` hint drives height first; otherwise
|
||||
width drives (from the `w` hint or the configured default). All
|
||||
divisions truncate toward zero via `Math.trunc` so the TypeScript and
|
||||
Go implementations agree on every fixture.
|
||||
|
||||
## Features
|
||||
|
||||
- Total function — never throws, always returns concrete numbers.
|
||||
- Honors the same precedence as the Go reference: hint axis wins over
|
||||
default width.
|
||||
- Integer truncation matches Go integer division exactly.
|
||||
- Zero runtime cost beyond two multiplications and one divide on the
|
||||
aspect path.
|
||||
|
||||
## Usage: Basic
|
||||
|
||||
```ts
|
||||
import { calculateDimensions, parseImgproxyUrl } from "@suaveplan/caddy-imgproxy";
|
||||
|
||||
const parsed = parseImgproxyUrl("/uv16:9/avatar.png");
|
||||
const dims = calculateDimensions(parsed, 800);
|
||||
dims; // { width: 800, height: 450 }
|
||||
```
|
||||
|
||||
## Usage: Advanced
|
||||
|
||||
```ts
|
||||
import { calculateDimensions, parseImgproxyUrl } from "@suaveplan/caddy-imgproxy";
|
||||
|
||||
// Height hint with portrait aspect ratio
|
||||
const portrait = parseImgproxyUrl("/uv9:16h1080/photo.jpg");
|
||||
calculateDimensions(portrait, 800);
|
||||
// → { width: 607, height: 1080 }
|
||||
|
||||
// Pixel passthrough — the default-width parameter is ignored
|
||||
const pixel = parseImgproxyUrl("/uf800x600/photo.jpg");
|
||||
calculateDimensions(pixel, 9999);
|
||||
// → { width: 800, height: 600 }
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
### `calculateDimensions(parsed: ParsedImgproxyUrl, defaultWidth: number): ImgproxyDimensions`
|
||||
|
||||
- **Parameters:**
|
||||
- `parsed` — output of `parseImgproxyUrl`.
|
||||
- `defaultWidth` — width used when the URL is in aspect-ratio mode
|
||||
without a `w`/`h` hint.
|
||||
- **Returns:** `{ width, height }`, both non-negative integers. Either
|
||||
axis may be `0` (meaning "auto / unspecified").
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
The function is intentionally a pure expression — it never touches
|
||||
the clock, the meter, or the logger so it composes cleanly under
|
||||
property-based tests. The default-width branch is reached when the
|
||||
parsed URL specifies an aspect ratio but neither a `w` nor an `h`
|
||||
hint, or when `hintType === "h"` but `hintValue === 0` (the
|
||||
zero-value clause is unreachable from the public regex but defended
|
||||
for callers who construct `ParsedImgproxyUrl` values directly).
|
||||
67
docs/media/image/caddy-imgproxy/src/domain/extract-domain.md
Normal file
67
docs/media/image/caddy-imgproxy/src/domain/extract-domain.md
Normal file
@@ -0,0 +1,67 @@
|
||||
# extractDomain
|
||||
|
||||
<!-- begin:auto-docs -->
|
||||
_(no submodules)_
|
||||
<!-- end:auto-docs -->
|
||||
|
||||
## Purpose
|
||||
|
||||
`extractDomain` translates an incoming HTTP `Host` header into the
|
||||
S3 sub-path segment that the URL builder interpolates into the
|
||||
`s3://<bucket>/<domain>/<path>@<format>` template. It is the single
|
||||
place where the multi-tenant routing convention "the `images.` host
|
||||
prefix maps to the bare apex domain" is enforced.
|
||||
|
||||
The function is total — it never throws — so callers may apply it
|
||||
to any string. Hosts shorter than two labels (such as `localhost`)
|
||||
are returned unchanged because the heuristic only makes sense for
|
||||
fully-qualified domain names. Port suffixes are removed before
|
||||
splitting so `images.example.com:8080` and `images.example.com` map
|
||||
to the same value.
|
||||
|
||||
## Features
|
||||
|
||||
- Total function over arbitrary input strings.
|
||||
- Port-aware: strips `:<port>` before label splitting.
|
||||
- Single-label hosts (`localhost`) pass through unchanged.
|
||||
- Mirrors the upstream Go heuristic exactly.
|
||||
|
||||
## Usage: Basic
|
||||
|
||||
```ts
|
||||
import { extractDomain } from "@suaveplan/caddy-imgproxy";
|
||||
|
||||
extractDomain("images.example.com:8080"); // "example.com"
|
||||
extractDomain("cdn.example.com"); // "cdn.example.com"
|
||||
extractDomain("localhost"); // "localhost"
|
||||
```
|
||||
|
||||
## Usage: Advanced
|
||||
|
||||
```ts
|
||||
import { extractDomain } from "@suaveplan/caddy-imgproxy";
|
||||
|
||||
// Apply per-request to build a tenant key:
|
||||
function tenantKey(host: string): string {
|
||||
return extractDomain(host).replace(/\./g, "-");
|
||||
}
|
||||
tenantKey("images.acme.example.com"); // "acme-example-com"
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
### `extractDomain(host: string): string`
|
||||
|
||||
- **Parameters:** `host` — the raw `Host` header value (with or
|
||||
without a port suffix).
|
||||
- **Returns:** the apex domain when the prefix is `images.`,
|
||||
otherwise the host unchanged (minus any port suffix).
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
Implemented as a two-step string scan: `indexOf(":")` to locate the
|
||||
optional port boundary, then `String.prototype.split(".")` to break
|
||||
the host into labels. The label check requires at least two segments
|
||||
because dropping the first label of a one-label host would yield the
|
||||
empty string. The function does not validate the host against any
|
||||
DNS spec — it accepts whatever the caller passes through.
|
||||
101
docs/media/image/caddy-imgproxy/src/error-codes/error-codes.md
Normal file
101
docs/media/image/caddy-imgproxy/src/error-codes/error-codes.md
Normal file
@@ -0,0 +1,101 @@
|
||||
# error-codes
|
||||
|
||||
<!-- begin:auto-docs -->
|
||||
_(no submodules)_
|
||||
<!-- end:auto-docs -->
|
||||
|
||||
## Purpose
|
||||
|
||||
The `error-codes` module is the canonical home of the package's
|
||||
stable error-code registry and the typed `CaddyImgproxyError` class
|
||||
that carries those codes. Every failure path in the package raises a
|
||||
`CaddyImgproxyError` whose `code` is drawn from this registry;
|
||||
consumers discriminate on `error.code` rather than `instanceof` so
|
||||
the namespace survives module boundaries and runtime serialisation.
|
||||
|
||||
Codes are built with `@suaveplan/error`'s `defineCodes` helper, which
|
||||
emits the canonical `NAMESPACE.REASON` form (e.g.
|
||||
`"CADDY_IMGPROXY.INVALID_URL"`). The `CaddyImgproxyError` class
|
||||
extends `SuaveplanError<CaddyImgproxyCode>` so `code` narrows
|
||||
against the literal union at compile time.
|
||||
|
||||
## Features
|
||||
|
||||
- Built via `defineCodes` so the namespace + reason form is uniform.
|
||||
- `CaddyImgproxyError` carries `code`, `message`, `cause`, `context`,
|
||||
`timestamp`, and the standard `SuaveplanError` envelope.
|
||||
- Discriminating union literal (`CaddyImgproxyCode`) for exhaustive
|
||||
`switch` checks.
|
||||
- Every code in the registry is reachable from at least one call
|
||||
site exercised by the tests.
|
||||
|
||||
## Usage: Basic
|
||||
|
||||
```ts
|
||||
import {
|
||||
CADDY_IMGPROXY_CODES,
|
||||
CaddyImgproxyError,
|
||||
} from "@suaveplan/caddy-imgproxy";
|
||||
|
||||
try {
|
||||
// … call into the package
|
||||
} catch (err) {
|
||||
if (
|
||||
err instanceof CaddyImgproxyError &&
|
||||
err.code === CADDY_IMGPROXY_CODES.INVALID_URL
|
||||
) {
|
||||
// handle parse failure
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Usage: Advanced
|
||||
|
||||
```ts
|
||||
import {
|
||||
CADDY_IMGPROXY_CODES,
|
||||
CaddyImgproxyError,
|
||||
} from "@suaveplan/caddy-imgproxy";
|
||||
|
||||
function classify(err: unknown): "parse" | "config" | "host" | "other" {
|
||||
if (!(err instanceof CaddyImgproxyError)) return "other";
|
||||
switch (err.code) {
|
||||
case CADDY_IMGPROXY_CODES.INVALID_URL:
|
||||
case CADDY_IMGPROXY_CODES.MISSING_DIMENSIONS:
|
||||
return "parse";
|
||||
case CADDY_IMGPROXY_CODES.INVALID_OPTIONS:
|
||||
case CADDY_IMGPROXY_CODES.INVALID_KEY:
|
||||
case CADDY_IMGPROXY_CODES.INVALID_SALT:
|
||||
return "config";
|
||||
case CADDY_IMGPROXY_CODES.INVALID_HOST:
|
||||
return "host";
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
### `CADDY_IMGPROXY_CODES`
|
||||
|
||||
A frozen object mapping reason names to their `CADDY_IMGPROXY.REASON`
|
||||
code strings. Reasons: `INVALID_URL`, `MISSING_DIMENSIONS`,
|
||||
`INVALID_OPTIONS`, `INVALID_KEY`, `INVALID_SALT`, `INVALID_HOST`.
|
||||
|
||||
### `class CaddyImgproxyError extends SuaveplanError<CaddyImgproxyCode>`
|
||||
|
||||
Public constructor: `(code, message, options?)`. Options carry
|
||||
`cause` and `context`.
|
||||
|
||||
### `type CaddyImgproxyCode`
|
||||
|
||||
Discriminating union literal extracted from the registry values.
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
`defineCodes` returns a frozen object indexed by reason name, with
|
||||
each value typed as `${Namespace}.${Reason}` for compile-time literal
|
||||
narrowing. The class deliberately exposes a public constructor —
|
||||
`SuaveplanError`'s own constructor is `protected` so subclasses must
|
||||
provide a concrete entry point. The `name = "CaddyImgproxyError"`
|
||||
assignment matters for stack-trace clarity; without it `new.target`
|
||||
would still set it correctly but explicit is clearer.
|
||||
74
docs/media/image/caddy-imgproxy/src/gravity/gravity.md
Normal file
74
docs/media/image/caddy-imgproxy/src/gravity/gravity.md
Normal file
@@ -0,0 +1,74 @@
|
||||
# gravity
|
||||
|
||||
<!-- begin:auto-docs -->
|
||||
_(no submodules)_
|
||||
<!-- end:auto-docs -->
|
||||
|
||||
## Purpose
|
||||
|
||||
The `gravity` module exposes the bidirectional translation between the
|
||||
compact one- or two-character grammar codes (`t`, `tl`, `bl`, …) and
|
||||
the two-/four-letter compass tokens that imgproxy itself consumes
|
||||
(`no`, `nowe`, `sowe`, …). Every operation that takes a gravity
|
||||
parameter — `cover`, `crop` — routes through this module so the
|
||||
mapping lives in exactly one place.
|
||||
|
||||
The map is exported as a frozen `Readonly<Record<GravityCode, string>>`
|
||||
so consumers may inspect or build their own translation tables on top
|
||||
of it. The `gravityToImgproxy` helper handles unknown / empty input by
|
||||
falling back to `"ce"` (center), matching the Go default branch
|
||||
exactly.
|
||||
|
||||
## Features
|
||||
|
||||
- Total function — never throws.
|
||||
- Single source of truth for the gravity grammar.
|
||||
- Frozen map for safe sharing.
|
||||
- Center default for forward-compatibility with grammar extensions.
|
||||
|
||||
## Usage: Basic
|
||||
|
||||
```ts
|
||||
import { gravityToImgproxy } from "@suaveplan/caddy-imgproxy";
|
||||
|
||||
gravityToImgproxy("t"); // "no"
|
||||
gravityToImgproxy("br"); // "soea"
|
||||
gravityToImgproxy(""); // "ce" (default)
|
||||
```
|
||||
|
||||
## Usage: Advanced
|
||||
|
||||
```ts
|
||||
import { GRAVITY_MAP } from "@suaveplan/caddy-imgproxy";
|
||||
|
||||
// Build a reverse lookup for parsing imgproxy URLs back into the
|
||||
// compact grammar.
|
||||
const reverse = Object.fromEntries(
|
||||
Object.entries(GRAVITY_MAP).map(([k, v]) => [v, k] as const)
|
||||
);
|
||||
reverse["sowe"]; // "bl"
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
### `gravityToImgproxy(code: string): string`
|
||||
|
||||
- **Parameters:** `code` — one of `t|b|l|r|c|tl|tr|bl|br`, or any
|
||||
unknown value.
|
||||
- **Returns:** the imgproxy compass token, or `"ce"` when input is
|
||||
unknown or empty.
|
||||
|
||||
### `GRAVITY_MAP: Readonly<Record<GravityCode, string>>`
|
||||
|
||||
The frozen lookup table.
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
The function uses an indexed object lookup rather than a `switch`
|
||||
statement because the map is exposed as a frozen value; the default
|
||||
branch handles both the empty-string and the unknown-code cases in a
|
||||
single line. Coverage for the unknown branch is exercised by the test
|
||||
`"defaults unknown codes to ce"`. The cast to `Record<string, string>`
|
||||
inside the function is necessary because the formal type uses the
|
||||
`GravityCode` union; at runtime any string may arrive (e.g. from a
|
||||
caller-constructed `ParsedImgproxyUrl`).
|
||||
121
docs/media/image/caddy-imgproxy/src/handler/handler.md
Normal file
121
docs/media/image/caddy-imgproxy/src/handler/handler.md
Normal file
@@ -0,0 +1,121 @@
|
||||
# ImgproxyHandler
|
||||
|
||||
<!-- begin:auto-docs -->
|
||||
_(no submodules)_
|
||||
<!-- end:auto-docs -->
|
||||
|
||||
## Purpose
|
||||
|
||||
`ImgproxyHandler` is the high-level orchestrator that wraps
|
||||
`parseImgproxyUrl`, `extractDomain`, and `buildImgproxyUrl` into a
|
||||
single `rewrite(request)` call suitable for use inside a CDN worker,
|
||||
an edge reverse proxy, or a build-time tooling pass. It owns the
|
||||
telemetry envelope around every rewrite, the structured logging on
|
||||
parse failures, and the discriminated `RewriteResult` that lets
|
||||
callers decide whether to forward to imgproxy, fall back, or return
|
||||
an error to the client.
|
||||
|
||||
The handler exposes three observable outcomes: `rewritten` (the path
|
||||
matched the grammar and a target imgproxy path was produced),
|
||||
`passthrough` (the path is `/unsafe/...` or `/health*`, meaning the
|
||||
caller should forward to imgproxy unchanged), and `invalid` (the
|
||||
path is not an imgproxy URL and the caller should fall back to the
|
||||
next handler in the chain).
|
||||
|
||||
## Features
|
||||
|
||||
- Injectable `Clock` for deterministic duration histograms.
|
||||
- Injectable `LoggerLike` for structured parse-failure warnings.
|
||||
- Injectable `SignerHashLike` for native / HSM / WebCrypto signing.
|
||||
- Discriminated `RewriteResult` — no exceptions on the happy path.
|
||||
- Telemetry: counter (`caddy_imgproxy_rewrite_total{outcome,
|
||||
operation}`) + histogram (`caddy_imgproxy_rewrite_duration_ms`).
|
||||
|
||||
## Usage: Basic
|
||||
|
||||
```ts
|
||||
import { ImgproxyHandler, parseConfig } from "@suaveplan/caddy-imgproxy";
|
||||
|
||||
const handler = new ImgproxyHandler({
|
||||
config: parseConfig({
|
||||
defaultWidth: 800,
|
||||
key: "aa11bbcc",
|
||||
salt: "deadbeef",
|
||||
}),
|
||||
});
|
||||
|
||||
const result = handler.rewrite({
|
||||
path: "/uv16:9/avatar.png",
|
||||
host: "images.example.com",
|
||||
});
|
||||
|
||||
if (result.rewritten) {
|
||||
forwardToImgproxy(result.targetPath);
|
||||
} else if (result.reason === "passthrough") {
|
||||
forwardToImgproxy(originalPath);
|
||||
} else {
|
||||
fallbackToNextHandler();
|
||||
}
|
||||
```
|
||||
|
||||
## Usage: Advanced
|
||||
|
||||
```ts
|
||||
import type { Clock, LoggerLike } from "@suaveplan/types";
|
||||
import { ImgproxyHandler, parseConfig } from "@suaveplan/caddy-imgproxy";
|
||||
|
||||
const tick = createMonotonicTick();
|
||||
const clock: Clock = () => tick.nowMs();
|
||||
const logger: LoggerLike = {
|
||||
debug: (m, c) => log("debug", m, c),
|
||||
info: (m, c) => log("info", m, c),
|
||||
warn: (m, c) => log("warn", m, c),
|
||||
error: (m, c) => log("error", m, c),
|
||||
};
|
||||
|
||||
const handler = new ImgproxyHandler({
|
||||
config: parseConfig({}),
|
||||
clock,
|
||||
logger,
|
||||
});
|
||||
|
||||
const result = handler.rewrite({
|
||||
path: "/uvtr9:16h1080/hero.webp",
|
||||
host: "images.example.com",
|
||||
});
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
### `class ImgproxyHandler`
|
||||
|
||||
#### `new ImgproxyHandler(options: ImgproxyHandlerOptions)`
|
||||
|
||||
- `options.config` — validated `ImgproxyConfig`.
|
||||
- `options.clock` — optional wall-clock; defaults to `Date.now`.
|
||||
- `options.logger` — optional `LoggerLike`; defaults to a silent
|
||||
no-op.
|
||||
- `options.hash` — optional signer hash override.
|
||||
|
||||
#### `rewrite(request: RewriteRequest): RewriteResult`
|
||||
|
||||
Returns one of `{ rewritten: true; targetPath; operation }`,
|
||||
`{ rewritten: false; reason: "passthrough" }`, or
|
||||
`{ rewritten: false; reason: "invalid"; errorCode; errorMessage }`.
|
||||
|
||||
### `RewriteRequest`
|
||||
|
||||
- `path` — incoming URL path including leading `/`.
|
||||
- `host` — incoming `Host` header.
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
Empty host is treated as a hard error (`INVALID_HOST`) rather than a
|
||||
silent passthrough because the S3 path interpolation depends on the
|
||||
host. `/unsafe/...` and `/health*` are detected only when
|
||||
`parseImgproxyUrl` throws — the grammar deliberately does not match
|
||||
these paths, so we treat the parse failure as a signal that this is a
|
||||
direct imgproxy passthrough rather than a user-facing rewrite. Both
|
||||
the clock and the logger are captured into instance fields at
|
||||
construction time so a single handler is safe to share across many
|
||||
requests in a long-lived server.
|
||||
76
docs/media/image/caddy-imgproxy/src/operation/operation.md
Normal file
76
docs/media/image/caddy-imgproxy/src/operation/operation.md
Normal file
@@ -0,0 +1,76 @@
|
||||
# operationToImgproxy
|
||||
|
||||
<!-- begin:auto-docs -->
|
||||
_(no submodules)_
|
||||
<!-- end:auto-docs -->
|
||||
|
||||
## Purpose
|
||||
|
||||
`operationToImgproxy` converts a resolved operation + concrete pixel
|
||||
dimensions + gravity into the imgproxy processing-string segment that
|
||||
sits between the `/<signature>/` prefix and the `/plain/s3://…@<fmt>`
|
||||
suffix. It is the bridge between the compact grammar's abstract
|
||||
operations and imgproxy's concrete URL DSL.
|
||||
|
||||
The five operations behave exactly as the upstream Go module: `cover`
|
||||
and `crop` require a gravity prefix so anchoring takes effect during
|
||||
cropping; `fit` ignores gravity (it always preserves aspect ratio);
|
||||
`stretch` always emits `rs:force:W:H`; `resize` is the single-axis
|
||||
variant of `fit`. Passthrough (`""`) and unknown operations emit an
|
||||
empty string so the URL builder skips the processing segment entirely.
|
||||
|
||||
## Features
|
||||
|
||||
- Total function — every input returns a defined string, never
|
||||
throws.
|
||||
- Mirrors the Go switch table byte-for-byte.
|
||||
- Single-axis fallbacks (`g:<g>/w:W`, `w:W`, `h:H`) so callers can
|
||||
produce auto-axis resizes without re-implementing the table.
|
||||
- Empty-string fallback drives the URL builder to skip the segment.
|
||||
|
||||
## Usage: Basic
|
||||
|
||||
```ts
|
||||
import { operationToImgproxy } from "@suaveplan/caddy-imgproxy";
|
||||
|
||||
operationToImgproxy("cover", 1920, 1080, "t");
|
||||
// → "g:no/rs:fill:1920:1080"
|
||||
|
||||
operationToImgproxy("fit", 800, 0, "c");
|
||||
// → "w:800"
|
||||
```
|
||||
|
||||
## Usage: Advanced
|
||||
|
||||
```ts
|
||||
import {
|
||||
calculateDimensions,
|
||||
operationToImgproxy,
|
||||
parseImgproxyUrl,
|
||||
} from "@suaveplan/caddy-imgproxy";
|
||||
|
||||
const parsed = parseImgproxyUrl("/ucbl4:3w1920/photo.png");
|
||||
const { width, height } = calculateDimensions(parsed, 800);
|
||||
operationToImgproxy(parsed.operation, width, height, parsed.gravity);
|
||||
// → "g:sowe/c:1920:1440"
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
### `operationToImgproxy(op: ImgproxyOperation, width: number, height: number, gravity: string): string`
|
||||
|
||||
- **Parameters:**
|
||||
- `op` — one of `""|cover|crop|fit|stretch|resize`.
|
||||
- `width`, `height` — non-negative integers; either may be zero.
|
||||
- `gravity` — a grammar code accepted by `gravityToImgproxy`.
|
||||
- **Returns:** the imgproxy processing string, or `""` when the
|
||||
operation cannot be expressed (e.g. `crop` without both dims).
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
The switch is exhaustive over the `ImgproxyOperation` union; the
|
||||
TypeScript compiler verifies no case escapes. Gravity is resolved
|
||||
once at the top of the function so the `cover`/`crop` branches share
|
||||
a single translation step. The empty-operation case returns `""`
|
||||
explicitly rather than falling through so the exhaustiveness check
|
||||
stays clean.
|
||||
98
docs/media/image/caddy-imgproxy/src/parse/parse-url.md
Normal file
98
docs/media/image/caddy-imgproxy/src/parse/parse-url.md
Normal file
@@ -0,0 +1,98 @@
|
||||
# parseImgproxyUrl
|
||||
|
||||
<!-- begin:auto-docs -->
|
||||
_(no submodules)_
|
||||
<!-- end:auto-docs -->
|
||||
|
||||
## Purpose
|
||||
|
||||
`parseImgproxyUrl` parses the compact imgproxy URL grammar used by the
|
||||
Caddy module — `/[s|u]<op><gravity><dims><modifiers>/<path>.<format>` —
|
||||
into a typed, frozen `ParsedImgproxyUrl`. It is the canonical entry
|
||||
point for every other helper in the package; the URL builder, the
|
||||
handler, and the dimensions calculator all consume the value it
|
||||
returns.
|
||||
|
||||
Behaviour mirrors the upstream Go implementation byte-for-byte so the
|
||||
TypeScript port and the Go module can be tested against a shared
|
||||
fixture corpus. The function never partially succeeds: invalid input
|
||||
throws a `CaddyImgproxyError` carrying a stable code from
|
||||
`CADDY_IMGPROXY_CODES`, and the parse-total counter ticks once per
|
||||
call with the matching outcome tag.
|
||||
|
||||
## Features
|
||||
|
||||
- Single-pass regex grammar covering signed / unsigned prefix, five
|
||||
operations (`cover`, `crop`, `fit`, `stretch`, `resize`), nine
|
||||
gravities, aspect / pixel / square dimensions, and width / height /
|
||||
quality modifiers in arbitrary order.
|
||||
- Quality validation built into the grammar (`q1..q100`) — out-of-band
|
||||
values raise `INVALID_URL`.
|
||||
- Operation + dimension constraint enforcement (`MISSING_DIMENSIONS`).
|
||||
- Frozen, immutable return value safe to memoise.
|
||||
- OTel counter on every call (`caddy_imgproxy_parse_total`).
|
||||
|
||||
## Usage: Basic
|
||||
|
||||
```ts
|
||||
import { parseImgproxyUrl } from "@suaveplan/caddy-imgproxy";
|
||||
|
||||
const parsed = parseImgproxyUrl("/uv16:9/avatar.png");
|
||||
parsed.operation; // "cover"
|
||||
parsed.aspectW; // 16
|
||||
parsed.path; // "avatar"
|
||||
parsed.format; // "png"
|
||||
```
|
||||
|
||||
## Usage: Advanced
|
||||
|
||||
```ts
|
||||
import {
|
||||
CADDY_IMGPROXY_CODES,
|
||||
CaddyImgproxyError,
|
||||
parseImgproxyUrl,
|
||||
} from "@suaveplan/caddy-imgproxy";
|
||||
|
||||
function tryParse(path: string): string | null {
|
||||
try {
|
||||
const parsed = parseImgproxyUrl(path);
|
||||
return `${parsed.operation || "passthrough"} → ${parsed.path}.${parsed.format}`;
|
||||
} catch (err) {
|
||||
if (
|
||||
err instanceof CaddyImgproxyError &&
|
||||
err.code === CADDY_IMGPROXY_CODES.MISSING_DIMENSIONS
|
||||
) {
|
||||
return "needs dims";
|
||||
}
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
### `parseImgproxyUrl(path: string): ParsedImgproxyUrl`
|
||||
|
||||
- **Parameters:** `path` — the URL path including leading `/`.
|
||||
- **Returns:** a frozen `ParsedImgproxyUrl` (see `types/types.ts`).
|
||||
- **Throws:**
|
||||
- `CaddyImgproxyError(INVALID_URL)` when the regex rejects the
|
||||
input — bad prefix, malformed dimensions, quality outside
|
||||
`1..100`.
|
||||
- `CaddyImgproxyError(MISSING_DIMENSIONS)` when an operation
|
||||
(`v|c|f|s|r`) is present without any dimension grammar.
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
The grammar regex is a single anchored expression with thirteen
|
||||
capture groups that mirror the field order of the Go regex
|
||||
exactly. The modifier sub-grammar (`w<int>`, `h<int>`, `q1..100`) is
|
||||
extracted via a global secondary regex so order is irrelevant — the
|
||||
last `w`/`h` wins (matching Go), and `q` always wins for quality.
|
||||
|
||||
`Number.parseInt` with radix 10 is used for every numeric extraction
|
||||
to avoid the legacy octal-from-leading-zero behaviour. Defensive
|
||||
defaults are deliberately omitted from branches the regex can never
|
||||
reach so coverage stays honest; the only fallbacks in the function
|
||||
body protect against future grammar evolution (e.g. an empty gravity
|
||||
capture defaulting to `"c"`).
|
||||
95
docs/media/image/caddy-imgproxy/src/signing/sign-path.md
Normal file
95
docs/media/image/caddy-imgproxy/src/signing/sign-path.md
Normal file
@@ -0,0 +1,95 @@
|
||||
# signImgproxyPath
|
||||
|
||||
<!-- begin:auto-docs -->
|
||||
_(no submodules)_
|
||||
<!-- end:auto-docs -->
|
||||
|
||||
## Purpose
|
||||
|
||||
`signImgproxyPath` computes the HMAC-SHA256 URL signature that
|
||||
imgproxy requires when it is configured with a key + salt. It is the
|
||||
single source of truth for how the TypeScript port and the upstream
|
||||
Go module agree on bytes: any URL signed here verifies against the Go
|
||||
implementation and vice-versa.
|
||||
|
||||
The function decodes the hex-encoded `key` and `salt` into raw bytes,
|
||||
concatenates the salt with the UTF-8 encoded `path`, runs HMAC-SHA256
|
||||
under the key, and base64url-encodes the digest without padding. Bad
|
||||
hex on either input raises a typed `CaddyImgproxyError`
|
||||
(`INVALID_KEY` / `INVALID_SALT`) so misconfiguration surfaces
|
||||
immediately rather than at the imgproxy boundary.
|
||||
|
||||
## Features
|
||||
|
||||
- Verbatim byte-level port of the Go `signPath` function.
|
||||
- Pluggable hash via the optional `hash` parameter — supply a native
|
||||
/ HSM / WebCrypto-backed implementation when needed.
|
||||
- Hex validation stricter than `@suaveplan/crypto-hash`'s `fromHex`
|
||||
(lower-case only) to keep wire-format round-trips deterministic.
|
||||
- Throws typed `CaddyImgproxyError` on every failure path.
|
||||
|
||||
## Usage: Basic
|
||||
|
||||
```ts
|
||||
import { signImgproxyPath } from "@suaveplan/caddy-imgproxy";
|
||||
|
||||
const signature = signImgproxyPath({
|
||||
key: "aa11bbcc",
|
||||
salt: "deadbeef",
|
||||
path: "/g:ce/rs:fill:800:450/plain/s3://images/example.com/avatar@png",
|
||||
});
|
||||
signature; // "E3ytdnTM6THWihFdTWqY4YGQIV-6K969S-8jb4AaE4U"
|
||||
```
|
||||
|
||||
## Usage: Advanced
|
||||
|
||||
```ts
|
||||
import { signImgproxyPath, type SignerHashLike } from "@suaveplan/caddy-imgproxy";
|
||||
|
||||
// Plug a WebCrypto-backed signer in a Cloudflare Worker:
|
||||
const wcHash: SignerHashLike = {
|
||||
hmac(_algo, key, data) {
|
||||
// ... call into crypto.subtle.sign synchronously via a vendored
|
||||
// implementation, return the digest as Uint8Array.
|
||||
return data;
|
||||
},
|
||||
};
|
||||
|
||||
const sig = signImgproxyPath({
|
||||
key: "deadbeef",
|
||||
salt: "feedface",
|
||||
path: "/plain/s3://images/example.com/photo@webp",
|
||||
hash: wcHash,
|
||||
});
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
### `signImgproxyPath(options: SignImgproxyPathOptions): string`
|
||||
|
||||
- **Parameters:**
|
||||
- `options.key` — lower-hex HMAC key, even length, non-empty.
|
||||
- `options.salt` — lower-hex salt, even length, non-empty.
|
||||
- `options.path` — the imgproxy path being signed.
|
||||
- `options.hash` — optional `SignerHashLike` injection seam.
|
||||
- **Returns:** base64url-encoded signature, no padding.
|
||||
- **Throws:**
|
||||
- `CaddyImgproxyError(INVALID_KEY)` for malformed / empty / odd-length key.
|
||||
- `CaddyImgproxyError(INVALID_SALT)` for malformed / empty / odd-length salt.
|
||||
|
||||
### `SignerHashLike`
|
||||
|
||||
Structural type with a single `hmac(algorithm, key, data)` method
|
||||
returning a `Uint8Array`. Lets callers swap the default Noble-backed
|
||||
implementation for a native, HSM, or WebCrypto-backed alternative.
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
The function pre-allocates the `salt || path` buffer in one
|
||||
`Uint8Array(saltBytes.length + pathBytes.length)` allocation so the
|
||||
hot path never reallocates. UTF-8 encoding goes through
|
||||
`TextEncoder`, which is available in every modern JS runtime
|
||||
(Node ≥ 14, all browsers, Cloudflare Workers, Deno, Bun). The Go
|
||||
reference uses `[]byte(path)` which is identical for ASCII paths and
|
||||
spec-compliant for arbitrary UTF-8 (the imgproxy upstream uses the
|
||||
same convention).
|
||||
89
docs/media/image/caddy-imgproxy/src/telemetry/telemetry.md
Normal file
89
docs/media/image/caddy-imgproxy/src/telemetry/telemetry.md
Normal file
@@ -0,0 +1,89 @@
|
||||
# telemetry
|
||||
|
||||
<!-- begin:auto-docs -->
|
||||
_(no submodules)_
|
||||
<!-- end:auto-docs -->
|
||||
|
||||
## Purpose
|
||||
|
||||
The `telemetry` module owns the OTel instruments emitted by every
|
||||
public entry point in `@suaveplan/caddy-imgproxy`. It declares three
|
||||
counters and one histogram, registers them against a single meter
|
||||
named `@suaveplan/caddy-imgproxy`, and exposes thin `record*`
|
||||
helpers so the consuming feature modules never need to import the
|
||||
raw OTel API.
|
||||
|
||||
When no `MeterProvider` is registered (the default in unit tests and
|
||||
production code that has not opted into telemetry), the OTel API
|
||||
returns a no-op meter and the helpers become no-ops. This makes the
|
||||
instruments safe to import unconditionally — the package never
|
||||
forces a runtime cost on consumers who do not want telemetry.
|
||||
|
||||
## Features
|
||||
|
||||
- Three counters: parse outcomes, rewrite outcomes (tagged with
|
||||
operation), sign mode.
|
||||
- One histogram: rewrite duration in milliseconds.
|
||||
- Helpers (`recordParseOutcome`, `recordRewriteOutcome`,
|
||||
`recordSignMode`, `recordRewriteDurationMs`) so feature code stays
|
||||
free of OTel boilerplate.
|
||||
- Discriminated tag literals (`ParseOutcome`, `RewriteOutcome`,
|
||||
`SignMode`) for compile-time safety on tag values.
|
||||
|
||||
## Usage: Basic
|
||||
|
||||
```ts
|
||||
import { recordParseOutcome, recordSignMode } from "@suaveplan/caddy-imgproxy";
|
||||
|
||||
recordParseOutcome("ok");
|
||||
recordSignMode("signed");
|
||||
```
|
||||
|
||||
## Usage: Advanced
|
||||
|
||||
```ts
|
||||
import { metrics } from "@opentelemetry/api";
|
||||
import { MeterProvider } from "@opentelemetry/sdk-metrics";
|
||||
import { rewriteDurationHistogram } from "@suaveplan/caddy-imgproxy";
|
||||
|
||||
// Wire a real MeterProvider before importing the rest of the package
|
||||
// so the histogram is bound to an active exporter.
|
||||
const provider = new MeterProvider();
|
||||
metrics.setGlobalMeterProvider(provider);
|
||||
|
||||
// rewriteDurationHistogram now records into the live provider.
|
||||
expect(rewriteDurationHistogram).toBeDefined();
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
### Counters
|
||||
|
||||
- `parseTotalCounter` — name `caddy_imgproxy_parse_total`, tagged
|
||||
with `outcome` ∈ `ok | invalid | missing_dims`.
|
||||
- `rewriteTotalCounter` — `caddy_imgproxy_rewrite_total`, tagged
|
||||
with `outcome` ∈ `rewritten | passthrough | invalid` and
|
||||
`operation`.
|
||||
- `signTotalCounter` — `caddy_imgproxy_sign_total`, tagged with
|
||||
`mode` ∈ `signed | unsafe`.
|
||||
|
||||
### Histogram
|
||||
|
||||
- `rewriteDurationHistogram` — `caddy_imgproxy_rewrite_duration_ms`,
|
||||
unit `ms`.
|
||||
|
||||
### Helpers
|
||||
|
||||
- `recordParseOutcome(outcome)`
|
||||
- `recordRewriteOutcome(outcome, operation)`
|
||||
- `recordSignMode(mode)`
|
||||
- `recordRewriteDurationMs(durationMs)`
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
The meter is bound at module import time via `metrics.getMeter(...)`.
|
||||
When no provider is registered the OTel API returns a no-op meter,
|
||||
so the instrument constructors do not throw. Helpers funnel through
|
||||
the counters/histogram via the standard `add(value, tags)` /
|
||||
`record(value)` API; the tag object literal is constructed inline
|
||||
each call so V8 can inline it into the call site under hot loops.
|
||||
98
docs/media/image/caddy-imgproxy/src/types/types.md
Normal file
98
docs/media/image/caddy-imgproxy/src/types/types.md
Normal file
@@ -0,0 +1,98 @@
|
||||
# types
|
||||
|
||||
<!-- begin:auto-docs -->
|
||||
_(no submodules)_
|
||||
<!-- end:auto-docs -->
|
||||
|
||||
## Purpose
|
||||
|
||||
The `types` module declares every cross-feature type that flows
|
||||
between the package's runtime modules: `ParsedImgproxyUrl` (the
|
||||
output of the parser), `ImgproxyConfig` (the public configuration
|
||||
shape), `ImgproxyDimensions` (the resolved pixel pair), and the
|
||||
discriminated `RewriteResult` union that `ImgproxyHandler.rewrite`
|
||||
returns. Centralising them here keeps the package's contract in one
|
||||
place and prevents circular imports between feature modules.
|
||||
|
||||
These types are runtime-free — `types.ts` has no executable code and
|
||||
is excluded from coverage by the vitest config. Consumers that only
|
||||
want the type contract can import from `@suaveplan/caddy-imgproxy/types`
|
||||
without pulling in any runtime dependency.
|
||||
|
||||
## Features
|
||||
|
||||
- Single source of truth for the package's data contracts.
|
||||
- Discriminated union (`RewriteResult`) for branchless handling of
|
||||
the three rewrite outcomes.
|
||||
- Frozen-by-convention return values throughout the package — the
|
||||
types use `readonly` modifiers so accidental mutation is a
|
||||
compile-time error.
|
||||
- Closed unions (`ImgproxyOperation`, `GravityCode`, `HintType`)
|
||||
give the rest of the package exhaustive `switch` coverage.
|
||||
|
||||
## Usage: Basic
|
||||
|
||||
```ts
|
||||
import type { ImgproxyConfig, ParsedImgproxyUrl } from "@suaveplan/caddy-imgproxy";
|
||||
|
||||
function describe(parsed: ParsedImgproxyUrl, config: ImgproxyConfig): string {
|
||||
return `${parsed.operation || "passthrough"} for ${config.s3Bucket}`;
|
||||
}
|
||||
```
|
||||
|
||||
## Usage: Advanced
|
||||
|
||||
```ts
|
||||
import type { RewriteResult } from "@suaveplan/caddy-imgproxy";
|
||||
|
||||
function summarise(result: RewriteResult): string {
|
||||
if (result.rewritten) return `→ ${result.targetPath}`;
|
||||
if (result.reason === "passthrough") return "passthrough";
|
||||
return `invalid (${result.errorCode}): ${result.errorMessage}`;
|
||||
}
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
### `ImgproxyOperation`
|
||||
|
||||
Union of `""|cover|crop|fit|stretch|resize`.
|
||||
|
||||
### `GravityCode`
|
||||
|
||||
Union of `t|b|l|r|c|tl|tr|bl|br`.
|
||||
|
||||
### `HintType`
|
||||
|
||||
Union of `""|w|h`.
|
||||
|
||||
### `ParsedImgproxyUrl`
|
||||
|
||||
Frozen, immutable parser output. Carries `signed`, `operation`,
|
||||
`gravity`, `aspectW`, `aspectH`, `pixelW`, `pixelH`, `hintType`,
|
||||
`hintValue`, `quality`, `path`, `format`, `isAspect`, `hasDims`.
|
||||
|
||||
### `ImgproxyConfig`
|
||||
|
||||
Validated configuration carrying `upstream`, `s3Bucket`,
|
||||
`defaultWidth`, and optional `key` / `salt`.
|
||||
|
||||
### `ImgproxyDimensions`
|
||||
|
||||
`{ width: number; height: number }` — output of
|
||||
`calculateDimensions`.
|
||||
|
||||
### `RewriteResult`
|
||||
|
||||
Discriminated union of `RewriteRewrittenResult`,
|
||||
`RewritePassthroughResult`, and `RewriteInvalidResult`.
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
Every field uses `readonly` to communicate immutability through the
|
||||
type. The discriminated `RewriteResult` shape is deliberately tagged
|
||||
twice (`rewritten` for the boolean discriminator and `reason` for
|
||||
the failure sub-discriminator) so consumers can branch on either
|
||||
side without losing TypeScript narrowing. The optional `key` / `salt`
|
||||
on `ImgproxyConfig` carry an explicit `| undefined` because the
|
||||
package is built under `exactOptionalPropertyTypes`.
|
||||
@@ -0,0 +1,92 @@
|
||||
# buildImgproxyUrl
|
||||
|
||||
<!-- begin:auto-docs -->
|
||||
_(no submodules)_
|
||||
<!-- end:auto-docs -->
|
||||
|
||||
## Purpose
|
||||
|
||||
`buildImgproxyUrl` composes the final imgproxy URL path from a parsed
|
||||
grammar representation. It is the orchestrator that ties together
|
||||
`calculateDimensions`, `operationToImgproxy`, and `signImgproxyPath`
|
||||
into the single string a downstream proxy or CDN edge needs to
|
||||
forward an image-rewrite request to imgproxy.
|
||||
|
||||
The function mirrors the upstream Go `buildImgproxyURL` semantics in
|
||||
every detail: processing string first, optional `q:<quality>` next,
|
||||
then the `plain/s3://<bucket>/<domain>/<path>@<format>` payload, and
|
||||
finally either a `/<base64url-signature>` prefix (when the config
|
||||
carries both a key and a salt) or `/unsafe`. Every call records a
|
||||
single `caddy_imgproxy_sign_total` sample tagged with the mode.
|
||||
|
||||
## Features
|
||||
|
||||
- Single source of truth for the URL composition order.
|
||||
- Telemetry on every call (sign-mode counter).
|
||||
- Honours the empty-string fallbacks from `operationToImgproxy` —
|
||||
passthrough URLs produce a clean `/unsafe/plain/...` shape.
|
||||
- Supports the injectable signer hash from `signImgproxyPath`.
|
||||
|
||||
## Usage: Basic
|
||||
|
||||
```ts
|
||||
import {
|
||||
buildImgproxyUrl,
|
||||
parseConfig,
|
||||
parseImgproxyUrl,
|
||||
} from "@suaveplan/caddy-imgproxy";
|
||||
|
||||
const parsed = parseImgproxyUrl("/uv16:9/avatar.png");
|
||||
const config = parseConfig({ defaultWidth: 800 });
|
||||
|
||||
buildImgproxyUrl({ parsed, domain: "example.com", config });
|
||||
// → "/unsafe/g:ce/rs:fill:800:450/plain/s3://images/example.com/avatar@png"
|
||||
```
|
||||
|
||||
## Usage: Advanced
|
||||
|
||||
```ts
|
||||
import {
|
||||
buildImgproxyUrl,
|
||||
parseConfig,
|
||||
parseImgproxyUrl,
|
||||
} from "@suaveplan/caddy-imgproxy";
|
||||
|
||||
const config = parseConfig({
|
||||
s3Bucket: "media",
|
||||
defaultWidth: 1200,
|
||||
key: "aa11bbcc",
|
||||
salt: "deadbeef",
|
||||
});
|
||||
|
||||
// Quality + width hint + signed
|
||||
const parsed = parseImgproxyUrl("/uv16:9q90w1920/photo.png");
|
||||
const url = buildImgproxyUrl({ parsed, domain: "example.com", config });
|
||||
// → "/<signature>/g:ce/rs:fill:1920:1080/q:90/plain/s3://media/example.com/photo@png"
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
### `buildImgproxyUrl(options: BuildImgproxyUrlOptions): string`
|
||||
|
||||
- **Parameters:**
|
||||
- `options.parsed` — output of `parseImgproxyUrl`.
|
||||
- `options.domain` — virtual-host segment for the S3 path.
|
||||
- `options.config` — validated `ImgproxyConfig`.
|
||||
- `options.hash` — optional signer hash override.
|
||||
- **Returns:** the full imgproxy path including the signature prefix.
|
||||
|
||||
### `BuildImgproxyUrlOptions`
|
||||
|
||||
Inputs interface — see above.
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
Signature mode is selected only when BOTH `config.key` and
|
||||
`config.salt` are non-empty strings. An empty string in either slot
|
||||
falls back to `/unsafe` so callers can disable signing at runtime
|
||||
without re-parsing the config. The `caddy_imgproxy_sign_total`
|
||||
counter is incremented exactly once per call — either as `signed` or
|
||||
as `unsafe` — even when the upstream signer throws (the throw
|
||||
propagates after the counter increments via the signer's own error
|
||||
path, since `signImgproxyPath` raises before this function records).
|
||||
Reference in New Issue
Block a user