5.1 KiB
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 (<name>.ts,
<name>.test.ts, <name>.md, index.ts). Co-located .md docs cover
purpose, features, basic + advanced usage, API reference, and
implementation notes.
Detection pipeline
detectMediaType(input, opts?)resolvesopts.registry ?? defaultSignatureRegistryand delegates toregistry.detect(input).detectSignature(input, opts?)returns the matchedMediaSignature | nullso consumers can route on the label (PNG / JPEG / MP4 / …) and not just the coarseMediaType.detectMediaTypeFromName(filename, opts?)calls@suaveplan/mime.getMimeTypeand buckets the result by top-level type (image/,video/,audio/).inferMimeFromSignature(sig)maps the matched signature label to a canonical MIME, falling back todefaultMimeForType(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<MediaMetadata>; 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:
MediaConstraintsSchema.safeParse(constraints)(unless caller passesskipConstraintValidation: true). Failure raisesMediaError(MEDIA.INVALID_CONSTRAINTS).- Empty-input guard: zero-byte input raises
MediaError(MEDIA.EMPTY_INPUT). getMetadata(input, opts)to build the bundle.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<MediaErrorCode> 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 <file>.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.