first commit
This commit is contained in:
112
docs/foundation/data/media/README.md
Normal file
112
docs/foundation/data/media/README.md
Normal file
@@ -0,0 +1,112 @@
|
||||
# @suaveplan/media - Universal media detection, metadata extraction, and constraint validation. Magic-byte signature registry (pluggable), MIME-keyed type detection via @suaveplan/mime, Zod-validated constraints, typed MediaError hierarchy with stable MEDIA.* codes, OTel counters + histogram, injectable observability bundle, and an async validation pipeline supporting custom predicates.
|
||||
|
||||
Universal media detection, metadata extraction, and constraint
|
||||
validation. Combines a pluggable magic-byte `SignatureRegistry` with the
|
||||
MIME database from `@suaveplan/mime` to identify image, video, and audio
|
||||
artefacts from raw bytes, filenames, or both. Ships typed errors via
|
||||
`@suaveplan/error`, OpenTelemetry instrumentation, Zod-validated
|
||||
constraints, and an async validation pipeline that supports custom
|
||||
predicates and format-specific metadata extractors.
|
||||
|
||||
<!-- begin:auto-docs -->
|
||||
## Documentation tree
|
||||
|
||||
- [detection](./src/detection/detection.md) — Byte-level and filename-level media type detection. The module pairs the pluggable `SignatureRegistry` from `signatures…
|
||||
- [detection](./src/detection/detection.md) — Byte-level and filename-level media type detection. The module pairs the pluggable `SignatureRegistry` from `signatures…
|
||||
- [errors](./src/errors/errors.md) — Owns the stable error-code registry, per-code metadata table, and the `MediaError` class that every public entrypoint t…
|
||||
- [errors](./src/errors/errors.md) — Owns the stable error-code registry, per-code metadata table, and the `MediaError` class that every public entrypoint t…
|
||||
- [metadata](./src/metadata/metadata.md) — Builds a `MediaMetadata` bundle (type, MIME, size, matched signature label) from raw bytes. The sync helper produces th…
|
||||
- [metadata](./src/metadata/metadata.md) — Builds a `MediaMetadata` bundle (type, MIME, size, matched signature label) from raw bytes. The sync helper produces th…
|
||||
- [observability](./src/observability/observability.md) — Constructs the OpenTelemetry instrument bundle threaded through every public entrypoint in `@suaveplan/media`. Each det…
|
||||
- [observability](./src/observability/observability.md) — Constructs the OpenTelemetry instrument bundle threaded through every public entrypoint in `@suaveplan/media`. Each det…
|
||||
- [signatures](./src/signatures/signatures.md) — Owns the magic-byte signature registry that powers byte-level detection. The module ships a frozen `BUILTIN_SIGNATURES`…
|
||||
- [signatures](./src/signatures/signatures.md) — Owns the magic-byte signature registry that powers byte-level detection. The module ships a frozen `BUILTIN_SIGNATURES`…
|
||||
- [validation](./src/validation/validation.md) — Validates media bytes against a `MediaConstraints` rule set covering size floors and ceilings, type allowlists, MIME al…
|
||||
- [validation](./src/validation/validation.md) — Validates media bytes against a `MediaConstraints` rule set covering size floors and ceilings, type allowlists, MIME al…
|
||||
<!-- end:auto-docs -->
|
||||
|
||||
## Install
|
||||
|
||||
```bash
|
||||
bun add @suaveplan/media
|
||||
```
|
||||
|
||||
## Quick start
|
||||
|
||||
```ts
|
||||
import {
|
||||
detectMediaType,
|
||||
getMetadata,
|
||||
validateMedia,
|
||||
} from "@suaveplan/media";
|
||||
|
||||
const png = new Uint8Array([0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a]);
|
||||
|
||||
detectMediaType(png); // "image"
|
||||
|
||||
const meta = getMetadata(png, { filename: "logo.png" });
|
||||
// { type: "image", mimeType: "image/png", size: 6, signatureLabel: "PNG" }
|
||||
|
||||
const result = validateMedia(png, {
|
||||
maxSize: 1_000_000,
|
||||
allowedTypes: ["image"],
|
||||
});
|
||||
if (!result.valid) console.warn(result.errors);
|
||||
```
|
||||
|
||||
## Modules
|
||||
|
||||
- [`detection/detection.md`](./src/detection/detection.md) — byte +
|
||||
filename detection helpers.
|
||||
- [`metadata/metadata.md`](./src/metadata/metadata.md) — sync + async
|
||||
metadata extraction.
|
||||
- [`validation/validation.md`](./src/validation/validation.md) —
|
||||
constraint checking, async predicate support, assert helper.
|
||||
- [`signatures/signatures.md`](./src/signatures/signatures.md) —
|
||||
pluggable magic-byte registry.
|
||||
- [`errors/errors.md`](./src/errors/errors.md) — `MEDIA_CODES`,
|
||||
`MediaError`, metadata table.
|
||||
- [`observability/observability.md`](./src/observability/observability.md)
|
||||
— OpenTelemetry bundle factory.
|
||||
- [`types/media.types.md`](./src/types/media.types.md) — public type
|
||||
contracts + Zod schemas.
|
||||
|
||||
## Features
|
||||
|
||||
- Magic-byte detection covering PNG, JPEG, GIF, BMP, WEBP/RIFF, MP4/MOV,
|
||||
WebM/MKV, MP3 (ID3 + sync), FLAC, OGG.
|
||||
- Pluggable `SignatureRegistry` with `register` / `unregister` / `list`
|
||||
/ `match` / `detect`; duplicate labels rejected with
|
||||
`MEDIA.DUPLICATE_SIGNATURE`.
|
||||
- MIME-keyed filename detection via `@suaveplan/mime`.
|
||||
- Zod-validated `MediaConstraints` (`minSize`, `maxSize`, `allowedTypes`,
|
||||
`allowedMimeTypes`, `minWidth`/`maxWidth`, `minHeight`/`maxHeight`,
|
||||
`maxDuration`, custom predicate).
|
||||
- Async metadata extractor hook for layering width / height / duration
|
||||
/ codec onto the base bundle.
|
||||
- `assertValidMedia` throwing variant for guard sites.
|
||||
- Eight stable `MEDIA.*` error codes with per-code HTTP / severity /
|
||||
retryable metadata.
|
||||
- Four counters + two histograms emitted from every public entrypoint.
|
||||
|
||||
## Error codes
|
||||
|
||||
| Code | HTTP | Retryable | Severity |
|
||||
| --------------------------------- | ---- | --------- | -------- |
|
||||
| `MEDIA.EMPTY_INPUT` | 400 | no | warning |
|
||||
| `MEDIA.INVALID_CONSTRAINTS` | 400 | no | error |
|
||||
| `MEDIA.VALIDATION_FAILED` | 422 | no | warning |
|
||||
| `MEDIA.UNSUPPORTED_TYPE` | 415 | no | warning |
|
||||
| `MEDIA.PREDICATE_FAILED` | 500 | no | error |
|
||||
| `MEDIA.DUPLICATE_SIGNATURE` | 409 | no | error |
|
||||
| `MEDIA.METADATA_EXTRACTOR_FAILED` | 500 | no | error |
|
||||
| `MEDIA.INVALID_SIGNATURE` | 400 | no | error |
|
||||
|
||||
## Observability
|
||||
|
||||
Four counters: `media_detect_total`, `media_metadata_total`,
|
||||
`media_validate_total`, `media_error_total`. Two histograms:
|
||||
`media_op_latency_ms`, `media_input_size_bytes`. Wire your meter via
|
||||
`createMediaObservability({ meter })` and thread the bundle into every
|
||||
call.
|
||||
|
||||
70
docs/foundation/data/media/src/detection/detection.md
Normal file
70
docs/foundation/data/media/src/detection/detection.md
Normal file
@@ -0,0 +1,70 @@
|
||||
# Detection
|
||||
|
||||
<!-- begin:auto-docs -->
|
||||
_(no submodules)_
|
||||
<!-- end:auto-docs -->
|
||||
|
||||
## Purpose
|
||||
|
||||
Byte-level and filename-level media type detection. The module pairs the
|
||||
pluggable `SignatureRegistry` from `signatures/` with the MIME database
|
||||
exposed by `@suaveplan/mime` so consumers can identify image, video, and
|
||||
audio artefacts whether they have the bytes, the filename, or both.
|
||||
|
||||
## Features
|
||||
|
||||
- `detectMediaType(input, opts?)` — magic-byte detection via the registry.
|
||||
- `detectSignature(input, opts?)` — return the matched `MediaSignature`
|
||||
record, not just its `type`.
|
||||
- `detectMediaTypeFromName(filename, opts?)` — MIME-database lookup.
|
||||
- `inferMimeFromSignature(sig)` — canonical MIME for a matched signature
|
||||
with a sane fallback when no signature matched.
|
||||
- `defaultMimeForType(type)` — coarse fallback when only the
|
||||
category is known.
|
||||
- Counters and latency histograms emitted on every invocation.
|
||||
|
||||
## Usage: Basic
|
||||
|
||||
```ts
|
||||
import { detectMediaType } from "@suaveplan/media";
|
||||
|
||||
const png = new Uint8Array([0x89, 0x50, 0x4e, 0x47, 0x0d]);
|
||||
detectMediaType(png); // "image"
|
||||
```
|
||||
|
||||
## Usage: Advanced
|
||||
|
||||
```ts
|
||||
import {
|
||||
detectSignature,
|
||||
inferMimeFromSignature,
|
||||
SignatureRegistry,
|
||||
} from "@suaveplan/media";
|
||||
|
||||
const reg = new SignatureRegistry();
|
||||
reg.register({
|
||||
type: "image",
|
||||
label: "AVIF",
|
||||
bytes: [0x66, 0x74, 0x79, 0x70, 0x61, 0x76, 0x69, 0x66],
|
||||
offset: 4,
|
||||
});
|
||||
const sig = detectSignature(myBytes, { registry: reg });
|
||||
const mime = inferMimeFromSignature(sig); // canonical MIME
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
- `DetectOptions` — `{ registry?, observability? }`.
|
||||
- `detectMediaType(input, opts?)` — `MediaType`.
|
||||
- `detectSignature(input, opts?)` — `MediaSignature | null`.
|
||||
- `detectMediaTypeFromName(filename, opts?)` — `MediaType`.
|
||||
- `inferMimeFromSignature(sig)` — canonical MIME string.
|
||||
- `defaultMimeForType(type)` — coarse fallback MIME.
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
`detectMediaType` and `detectMediaTypeFromName` both record an
|
||||
`op_latency_ms` histogram entry in a `finally` block so the cost of a
|
||||
detection call is always measured, even when the registry throws. The
|
||||
filename pathway uses `@suaveplan/mime`'s `getMimeType` so the database
|
||||
behind detection stays in lock-step with the rest of the project.
|
||||
67
docs/foundation/data/media/src/errors/errors.md
Normal file
67
docs/foundation/data/media/src/errors/errors.md
Normal file
@@ -0,0 +1,67 @@
|
||||
# Errors
|
||||
|
||||
<!-- begin:auto-docs -->
|
||||
_(no submodules)_
|
||||
<!-- end:auto-docs -->
|
||||
|
||||
## Purpose
|
||||
|
||||
Owns the stable error-code registry, per-code metadata table, and the
|
||||
`MediaError` class that every public entrypoint throws or wraps. Every
|
||||
failure path in `@suaveplan/media` surfaces one of eight `MEDIA.*` codes
|
||||
so consumers (loggers, retry policies, telemetry pipelines, UI surfaces)
|
||||
can classify failures programmatically without parsing message strings.
|
||||
|
||||
## Features
|
||||
|
||||
- Frozen `MEDIA_CODES` registry created via `defineCodes("MEDIA", […])`.
|
||||
- Per-code `MediaCodeMetadata` record (`httpStatus`, `retryable`,
|
||||
`severity`, `description`) frozen at module load.
|
||||
- `MediaError` extending `SuaveplanError<MediaErrorCode>` — cause chain,
|
||||
redaction, OTel span-event adapter inherited.
|
||||
- `asMediaError(code, cause, fallback)` lift helper for `catch` sites.
|
||||
|
||||
## Usage: Basic
|
||||
|
||||
```ts
|
||||
import { MediaError, MEDIA_CODES } from "@suaveplan/media";
|
||||
|
||||
throw new MediaError(MEDIA_CODES.EMPTY_INPUT, "input is empty");
|
||||
```
|
||||
|
||||
## Usage: Advanced
|
||||
|
||||
```ts
|
||||
import { asMediaError, MEDIA_CODES, MEDIA_ERROR_METADATA } from "@suaveplan/media";
|
||||
|
||||
try {
|
||||
/* boundary work */
|
||||
} catch (cause) {
|
||||
const err = asMediaError(
|
||||
MEDIA_CODES.PREDICATE_FAILED,
|
||||
cause,
|
||||
"predicate threw"
|
||||
);
|
||||
const meta = MEDIA_ERROR_METADATA[err.code];
|
||||
if (meta.retryable) {
|
||||
// schedule retry
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
- `MEDIA_CODES` — frozen code registry with eight namespaced keys.
|
||||
- `MediaErrorCode` — union of registry values.
|
||||
- `MEDIA_ERROR_METADATA` — frozen record keyed by code.
|
||||
- `MediaCodeMetadata` — per-code metadata shape.
|
||||
- `MediaError` — concrete `SuaveplanError` subclass with `.metadata` getter.
|
||||
- `asMediaError(code, cause, fallback)` — lift helper.
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
`defineCodes` produces namespaced keys (`MEDIA.EMPTY_INPUT`, …). The
|
||||
metadata table is `Object.freeze`'d at module top level so consumers
|
||||
cannot tamper at runtime. `asMediaError` preserves the original error as
|
||||
`.cause` when it's an `Error` instance, otherwise it wraps the stringified
|
||||
value so the cause chain is always populated.
|
||||
65
docs/foundation/data/media/src/metadata/metadata.md
Normal file
65
docs/foundation/data/media/src/metadata/metadata.md
Normal file
@@ -0,0 +1,65 @@
|
||||
# Metadata
|
||||
|
||||
<!-- begin:auto-docs -->
|
||||
_(no submodules)_
|
||||
<!-- end:auto-docs -->
|
||||
|
||||
## Purpose
|
||||
|
||||
Builds a `MediaMetadata` bundle (type, MIME, size, matched signature
|
||||
label) from raw bytes. The sync helper produces the base bundle in a
|
||||
single pass; the async helper layers a user-supplied
|
||||
`MediaMetadataExtractor` over the base so callers can fill in
|
||||
format-specific fields (width, height, duration) without forking the
|
||||
package.
|
||||
|
||||
## Features
|
||||
|
||||
- `getMetadata(input, opts?)` — synchronous, no extractor.
|
||||
- `getMetadataAsync(input, opts?)` — awaits the optional extractor and
|
||||
merges its return into the base bundle.
|
||||
- MIME resolution prefers the explicit `filename` field; otherwise it
|
||||
falls back to `inferMimeFromSignature`.
|
||||
- Signature label exposed via `metadata.signatureLabel` for downstream
|
||||
diagnostics.
|
||||
- Extractor errors are wrapped in `MediaError(MEDIA.METADATA_EXTRACTOR_FAILED)`
|
||||
with the original error chained as `.cause`.
|
||||
|
||||
## Usage: Basic
|
||||
|
||||
```ts
|
||||
import { getMetadata } from "@suaveplan/media";
|
||||
|
||||
const meta = getMetadata(new Uint8Array([0x89, 0x50, 0x4e, 0x47]), {
|
||||
filename: "logo.png",
|
||||
});
|
||||
console.log(meta.type, meta.mimeType, meta.size, meta.signatureLabel);
|
||||
```
|
||||
|
||||
## Usage: Advanced
|
||||
|
||||
```ts
|
||||
import { getMetadataAsync } from "@suaveplan/media";
|
||||
|
||||
const meta = await getMetadataAsync(buf, {
|
||||
filename: "clip.mp4",
|
||||
extractor: async (_bytes, base) => {
|
||||
const { width, height, duration } = await probe(buf);
|
||||
return { ...base, width, height, duration };
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
- `MetadataOptions` — `{ filename?, registry?, observability?, extractor? }`.
|
||||
- `getMetadata(input, opts?)` — synchronous `MediaMetadata`.
|
||||
- `getMetadataAsync(input, opts?)` — `Promise<MediaMetadata>`.
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
The base bundle is built inside a private helper so both entrypoints
|
||||
share signature detection + MIME resolution. The async variant only
|
||||
forks off the base when an extractor is supplied, so unused await paths
|
||||
incur near-zero overhead. Latency histograms are recorded in a
|
||||
`finally` block so failed extractor calls still produce telemetry.
|
||||
@@ -0,0 +1,60 @@
|
||||
# Observability
|
||||
|
||||
<!-- begin:auto-docs -->
|
||||
_(no submodules)_
|
||||
<!-- end:auto-docs -->
|
||||
|
||||
## Purpose
|
||||
|
||||
Constructs the OpenTelemetry instrument bundle threaded through every
|
||||
public entrypoint in `@suaveplan/media`. Each detection, metadata, and
|
||||
validation call increments a counter, records a latency histogram, and
|
||||
optionally emits a span. Consumers can inject their own meter / tracer
|
||||
to wire instruments into a shared telemetry pipeline.
|
||||
|
||||
## Features
|
||||
|
||||
- Four counters: `media_detect_total`, `media_metadata_total`,
|
||||
`media_validate_total`, `media_error_total`.
|
||||
- Two histograms: `media_op_latency_ms`, `media_input_size_bytes`.
|
||||
- Injectable meter / tracer or a pre-built `MediaObservability`.
|
||||
- Falls back to the global no-op providers when nothing is supplied,
|
||||
keeping the bundle safe to construct from library code.
|
||||
|
||||
## Usage: Basic
|
||||
|
||||
```ts
|
||||
import { createMediaObservability } from "@suaveplan/media";
|
||||
|
||||
const obs = createMediaObservability();
|
||||
obs.counters.detectTotal.add(1, { kind: "magic-bytes" });
|
||||
```
|
||||
|
||||
## Usage: Advanced
|
||||
|
||||
```ts
|
||||
import { metrics } from "@opentelemetry/api";
|
||||
import { createMediaObservability } from "@suaveplan/media";
|
||||
|
||||
const meter = metrics.getMeter("my-app");
|
||||
const obs = createMediaObservability({ meter });
|
||||
obs.histograms.opLatencyMs.record(12.4, { op: "validateMedia" });
|
||||
obs.counters.errorTotal.add(1, { code: "MEDIA.VALIDATION_FAILED" });
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
- `MediaCounters` — counter set.
|
||||
- `MediaHistograms` — histogram set.
|
||||
- `MediaObservability` — bundle.
|
||||
- `MediaObservabilityOptions` — `{ meter?, tracer?, observability? }`.
|
||||
- `createMediaObservability(opts?)` — bundle factory.
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
When `opts.observability` is supplied, it short-circuits before any
|
||||
instrument creation so injected bundles round-trip without mutation.
|
||||
Otherwise the function resolves a meter and tracer (defaulting to the
|
||||
global providers) and creates instruments via the helpers in
|
||||
`@suaveplan/opentelemetry` so naming + unit conventions stay consistent
|
||||
across the project.
|
||||
71
docs/foundation/data/media/src/signatures/signatures.md
Normal file
71
docs/foundation/data/media/src/signatures/signatures.md
Normal file
@@ -0,0 +1,71 @@
|
||||
# Signatures
|
||||
|
||||
<!-- begin:auto-docs -->
|
||||
_(no submodules)_
|
||||
<!-- end:auto-docs -->
|
||||
|
||||
## Purpose
|
||||
|
||||
Owns the magic-byte signature registry that powers byte-level detection.
|
||||
The module ships a frozen `BUILTIN_SIGNATURES` list covering common image
|
||||
(PNG, JPEG, GIF, BMP, WEBP, RIFF), video (MP4/MOV, WebM/MKV), and audio
|
||||
(MP3, FLAC, OGG) formats, plus a mutable `SignatureRegistry` class that
|
||||
lets downstream consumers add or remove entries without forking the
|
||||
package.
|
||||
|
||||
## Features
|
||||
|
||||
- Frozen built-in registry covering twelve canonical formats.
|
||||
- `SignatureRegistry` class with `register`, `unregister`, `list`,
|
||||
`match`, and `detect` methods.
|
||||
- Per-entry `offset` so ftyp-style signatures (MP4) and RIFF chunk markers
|
||||
(WebP) can match at non-zero positions.
|
||||
- Duplicate-label detection: `register` throws
|
||||
`MEDIA.DUPLICATE_SIGNATURE` if a second entry uses the same label.
|
||||
- Byte validation: registration rejects empty byte arrays and non-byte
|
||||
numbers with `MEDIA.INVALID_SIGNATURE`.
|
||||
- Process-wide `defaultSignatureRegistry` seeded with the built-ins.
|
||||
|
||||
## Usage: Basic
|
||||
|
||||
```ts
|
||||
import { defaultSignatureRegistry } from "@suaveplan/media";
|
||||
|
||||
const png = new Uint8Array([0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a]);
|
||||
const sig = defaultSignatureRegistry.match(png);
|
||||
console.log(sig?.label); // "PNG"
|
||||
```
|
||||
|
||||
## Usage: Advanced
|
||||
|
||||
```ts
|
||||
import { SignatureRegistry } from "@suaveplan/media";
|
||||
|
||||
const reg = new SignatureRegistry();
|
||||
reg.register({
|
||||
type: "image",
|
||||
label: "AVIF",
|
||||
bytes: [0x66, 0x74, 0x79, 0x70, 0x61, 0x76, 0x69, 0x66],
|
||||
offset: 4,
|
||||
});
|
||||
const type = reg.detect(myBytes);
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
- `BUILTIN_SIGNATURES` — readonly array of twelve canonical signatures.
|
||||
- `SignatureRegistry` — mutable registry class.
|
||||
- `register(sig)` — add entry; throws on duplicate label / invalid bytes.
|
||||
- `unregister(label)` — remove by label; returns whether removed.
|
||||
- `list()` — frozen snapshot.
|
||||
- `match(input)` — first matching `MediaSignature` or `null`.
|
||||
- `detect(input)` — `MediaType`, defaulting to `"unknown"`.
|
||||
- `defaultSignatureRegistry` — process-wide default.
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
`match` honours each signature's `offset` so non-zero-anchored formats
|
||||
work correctly. The built-in list orders longer / more-specific
|
||||
signatures before shorter ones (RIFF is listed after WEBP because the
|
||||
WEBP marker lives inside a RIFF container at offset 8). The registry's
|
||||
internal array is private so external mutation cannot break ordering.
|
||||
69
docs/foundation/data/media/src/types/media.types.md
Normal file
69
docs/foundation/data/media/src/types/media.types.md
Normal file
@@ -0,0 +1,69 @@
|
||||
# Media Types
|
||||
|
||||
## Purpose
|
||||
|
||||
Defines every public type and Zod schema exported by `@suaveplan/media`.
|
||||
These contracts pin the package's surface so downstream consumers can write
|
||||
runtime-validated boundaries and compile against stable shapes. Types
|
||||
covered include `MediaType`, `MediaSignature`, `MediaMetadata`,
|
||||
`MediaConstraints`, `MediaResult`, the constraint predicate, and the async
|
||||
metadata-extractor hook.
|
||||
|
||||
## Features
|
||||
|
||||
- `MediaType` discriminated union (`image | video | audio | unknown`).
|
||||
- `MediaTypeSchema` and `MediaConstraintsSchema` Zod parsers for boundary
|
||||
validation.
|
||||
- `MediaSignature` model for magic-byte registry entries with optional
|
||||
non-zero offset.
|
||||
- Extended `MediaConstraints` (size floor + ceiling, MIME allowlist,
|
||||
duration ceiling, custom predicate).
|
||||
- `MediaMetadataExtractor` hook used by the metadata pipeline.
|
||||
|
||||
## Usage: Basic
|
||||
|
||||
```ts
|
||||
import { MediaTypeSchema, type MediaMetadata } from "@suaveplan/media";
|
||||
|
||||
const parsed = MediaTypeSchema.parse("image");
|
||||
const meta: MediaMetadata = {
|
||||
type: parsed,
|
||||
mimeType: "image/png",
|
||||
size: 1024,
|
||||
};
|
||||
```
|
||||
|
||||
## Usage: Advanced
|
||||
|
||||
```ts
|
||||
import {
|
||||
MediaConstraintsSchema,
|
||||
type MediaConstraints,
|
||||
} from "@suaveplan/media";
|
||||
|
||||
const constraints: MediaConstraints = MediaConstraintsSchema.parse({
|
||||
maxSize: 2_000_000,
|
||||
allowedTypes: ["image"],
|
||||
allowedMimeTypes: ["image/png", "image/jpeg"],
|
||||
predicate: (m) => (m.size > 0 ? null : "empty"),
|
||||
});
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
- `MediaType` — recognised media categories.
|
||||
- `MediaTypeSchema` — Zod enum for `MediaType`.
|
||||
- `MediaSignature` — magic-byte registry entry.
|
||||
- `MediaMetadata` — extracted metadata bundle.
|
||||
- `MediaConstraints` / `MediaConstraintsSchema` — validation rule set.
|
||||
- `MediaResult` — validation result with errors array.
|
||||
- `MediaConstraintPredicate` — custom validation function.
|
||||
- `MediaMetadataExtractor` — async metadata extension hook.
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
All schemas use `z` default-imported per Bun-safety. The predicate field
|
||||
in `MediaConstraintsSchema` uses `z.custom` because Zod cannot statically
|
||||
describe arbitrary function shapes. Arrays surface as `readonly` arrays in
|
||||
TypeScript so consumers cannot mutate constraint collections after
|
||||
construction.
|
||||
78
docs/foundation/data/media/src/validation/validation.md
Normal file
78
docs/foundation/data/media/src/validation/validation.md
Normal file
@@ -0,0 +1,78 @@
|
||||
# Validation
|
||||
|
||||
<!-- begin:auto-docs -->
|
||||
_(no submodules)_
|
||||
<!-- end:auto-docs -->
|
||||
|
||||
## 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<MediaResult>`.
|
||||
- `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.
|
||||
Reference in New Issue
Block a user