# Validation _(no submodules)_ ## Purpose Validates media bytes against a `MediaConstraints` rule set covering size floors and ceilings, type allowlists, MIME allowlists, width / height / duration limits, and an optional custom predicate. The module exposes a synchronous helper for cheap byte-level checks and an async helper that also honours the constraint predicate plus the metadata extractor hook. ## Features - `validateMedia(input, constraints, opts?)` — sync validation. - `validateMediaAsync(input, constraints, opts?)` — async validation that runs the predicate hook. - `assertValidMedia(...)` — throwing variant that yields `MediaError(MEDIA.VALIDATION_FAILED)` on failure. - Zod-validated constraints — malformed input throws `MediaError(MEDIA.INVALID_CONSTRAINTS)`; use `skipConstraintValidation` to opt out on hot paths. - Predicate failure wrapping — caller exceptions surface as `MediaError(MEDIA.PREDICATE_FAILED)` with the original error chained. - OTel `media_validate_total` counter + `media_op_latency_ms` histogram emitted on every invocation. ## Usage: Basic ```ts import { validateMedia } from "@suaveplan/media"; const result = validateMedia(myBytes, { maxSize: 1_000_000, allowedTypes: ["image"], }); if (!result.valid) { console.warn(result.errors); } ``` ## Usage: Advanced ```ts import { validateMediaAsync } from "@suaveplan/media"; const result = await validateMediaAsync(myBytes, { maxSize: 5_000_000, allowedTypes: ["image", "video"], allowedMimeTypes: ["image/png", "video/mp4"], maxWidth: 4096, maxHeight: 4096, maxDuration: 60, predicate: async (meta) => meta.size === 0 ? "empty payload" : null, }); ``` ## API Reference - `ValidateOptions` — extends `MetadataOptions` with `skipConstraintValidation`. - `validateMedia(input, constraints, opts?)` — sync `MediaResult`. - `validateMediaAsync(input, constraints, opts?)` — `Promise`. - `assertValidMedia(input, constraints, opts?)` — `MediaMetadata`, throws on validation failure. ## Implementation Notes `checkConstraints` is shared between the sync and async paths so the rule semantics are identical. Empty input is rejected up-front with `MEDIA.EMPTY_INPUT` before any metadata work happens. The predicate runs after every other check so callers can rely on the base errors being populated first.