# Design: enterprise-media ## Module layout ``` packages/foundation/data/media/src/ detection/ detectMediaType, detectSignature, detectMediaTypeFromName, inferMimeFromSignature, defaultMimeForType metadata/ getMetadata, getMetadataAsync, MetadataOptions validation/ validateMedia, validateMediaAsync, assertValidMedia, ValidateOptions signatures/ BUILTIN_SIGNATURES, SignatureRegistry, defaultSignatureRegistry errors/ MEDIA_CODES, MEDIA_ERROR_METADATA, MediaError, asMediaError observability/ createMediaObservability + counter / histogram types types/ MediaType, MediaSignature, MediaMetadata, MediaConstraints, MediaResult, MediaMetadataExtractor + Zod schemas index.ts root barrel — explicit named re-exports ``` Every feature folder is the canonical four-file pattern (`.ts`, `.test.ts`, `.md`, `index.ts`). Co-located `.md` docs cover purpose, features, basic + advanced usage, API reference, and implementation notes. ## Detection pipeline 1. `detectMediaType(input, opts?)` resolves `opts.registry ?? defaultSignatureRegistry` and delegates to `registry.detect(input)`. 2. `detectSignature(input, opts?)` returns the matched `MediaSignature | null` so consumers can route on the label (PNG / JPEG / MP4 / …) and not just the coarse `MediaType`. 3. `detectMediaTypeFromName(filename, opts?)` calls `@suaveplan/mime.getMimeType` and buckets the result by top-level type (`image/`, `video/`, `audio/`). 4. `inferMimeFromSignature(sig)` maps the matched signature label to a canonical MIME, falling back to `defaultMimeForType(sig.type)` when no per-label mapping exists. `SignatureRegistry` exposes `register`, `unregister`, `list`, `match`, and `detect`. `register` rejects empty byte arrays (`MEDIA.INVALID_SIGNATURE`), non-byte values (`MEDIA.INVALID_SIGNATURE`), and duplicate labels (`MEDIA.DUPLICATE_SIGNATURE`). The built-in list orders longer / more- specific signatures first so the first match wins. ## Metadata pipeline `getMetadata` builds the base bundle (`{ type, mimeType, size, signatureLabel }`) in a private helper. `getMetadataAsync` accepts an optional `MediaMetadataExtractor` that receives `(input, base)` and returns `Partial`; the helper merges the partial over the base. Extractor exceptions are wrapped in `MediaError(MEDIA.METADATA_EXTRACTOR_FAILED)` with the original error chained. MIME resolution prefers the explicit `filename` field (via `getMimeType`) and falls back to `inferMimeFromSignature`. The base bundle exposes `signatureLabel` so downstream extractors can route on the exact format. ## Validation pipeline `validateMedia` runs: 1. `MediaConstraintsSchema.safeParse(constraints)` (unless caller passes `skipConstraintValidation: true`). Failure raises `MediaError(MEDIA.INVALID_CONSTRAINTS)`. 2. Empty-input guard: zero-byte input raises `MediaError(MEDIA.EMPTY_INPUT)`. 3. `getMetadata(input, opts)` to build the bundle. 4. `checkConstraints(metadata, constraints)` to populate the error array (size floors / ceilings, type / MIME allowlists, width / height ceilings + floors, duration ceiling). `validateMediaAsync` runs the same pipeline but calls `getMetadataAsync` and, after constraint checks, runs `constraints.predicate(metadata, input)`. Predicate return values: - `null` / `undefined` — pass. - `string` — push onto the errors array. - thrown — wrapped in `MediaError(MEDIA.PREDICATE_FAILED)`. `assertValidMedia` calls the sync variant and throws `MediaError(MEDIA.VALIDATION_FAILED)` if any error is reported, incrementing the `media_error_total` counter with `{ code }`. ## Observability `createMediaObservability(opts?)` builds the bundle. If `opts.observability` is supplied it short-circuits. Otherwise the function resolves `meter` and `tracer` from `opts` or the global `@opentelemetry/api` providers and constructs counters / histograms via the helpers in `@suaveplan/opentelemetry`. Every public entrypoint constructs a default bundle when none is supplied so calls always emit telemetry without requiring caller-side wiring. ## Error model `MEDIA_CODES` is built via `defineCodes("MEDIA", [...] as const)` so the namespace and value strings line up with the rest of the monorepo. `MEDIA_ERROR_METADATA` is `Object.freeze`'d at module load. `MediaError` extends `SuaveplanError` and exposes a `.metadata` getter that pulls the matching record. `asMediaError(code, cause, fallback)` lifts unknown thrown values, preserving the original error as `.cause` when it's already an `Error` instance. ## Why split into seven feature folders The package is small (~700 LOC after the enterprise rewrite) but each module owns a distinct concern: detection / metadata / validation are end-user surfaces; signatures / errors / observability are infrastructure shared across the three; types holds the data contracts. The split keeps `.ts` under the 300-line hard limit and lets tree-shake-sensitive consumers deep-import via the subpath exports without dragging in the full bundle.