117 lines
5.1 KiB
Markdown
117 lines
5.1 KiB
Markdown
# 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
|
|
|
|
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<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:
|
|
|
|
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<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.
|