# OpenSpec Change: enterprise-mime
- **Change ID**: enterprise-mime
- **Package**: `@suaveplan/mime`
- **Category**: universal
- **Target path**: `packages/universal/mime`
- **Status**: PROPOSED
- **Date**: 2026-04-19
- **Reference spec**: `/mnt/s/web/suaveplan/enterprise/mime.md`
## Summary
Greenfield implementation of `@suaveplan/mime` for the genesis branch. This package delivers structured MIME-type detection, parsing, and registry at enterprise quality: magic-byte sniffing via a compiled trie over a signature table, RFC 7231 `Content-Type` parsing (quoted-string parameters, charset normalisation, round-trip serialisation), a curated IANA-sourced type database with extension↔type↔category lookups, charset sniffing (BOM, HTML meta, XML declaration), an extensible signature registry, structured errors, Zod schemas, OTel instrumentation, property-based tests, and benchmarks. Zero browser/Node/React dependencies — pure TypeScript runnable in Bun, Node, and browser.
## Current-state audit (genesis)
The package does not exist on genesis yet — this is a fresh build.
## Available workspace deps on genesis
| Package | Available |
|---------|-----------|
| `@suaveplan/error` | ✅ |
| `@suaveplan/types` | ✅ |
| `@suaveplan/logger` | ✅ |
| `@suaveplan/opentelemetry` | ❌ not yet built — use `@opentelemetry/api` directly |
| `@suaveplan/schema-registry` | ❌ not yet built — export Zod schemas directly |
| `@suaveplan/streams` | ❌ not yet built — `detectFromStream` is a thin wrapper over `detectFromMagicBytes` with `AbortSignal` |
External deps (via catalog):
- `zod` → `catalog:forms`
- `@opentelemetry/api` → `catalog:observability`
- `tslib` → `catalog:build`
Dev deps (via catalog):
- `vitest`, `@vitest/coverage-istanbul` → `catalog:testing`
- `fast-check` → `catalog:testing`
- `mitata` → `catalog:testing`
## Preserved public API surface (from main branch)
### Charset
- `type BomDetection` — detected charset from a BOM prefix
- `sniffBom(bytes)` → `BomDetection | null` — detect charset from BOM
- `sniffHtmlMeta(bytes)` → `string | null` — detect charset from HTML `` tag
- `sniffXmlDecl(bytes)` → `string | null` — detect charset from XML declaration
### Content-type
- `contentTypeEquals(a, b)` → `boolean` — structural equality ignoring parameter order
- `contentTypeMatches(actual, pattern)` → `boolean` — wildcard `*` subtype matching
- `formatContentType(parsed)` → `string` — canonical round-trip serialisation
- `type MediaType` — `{ type: string; subtype: string }`
- `type ParsedContentType` — `{ mediaType: MediaType; parameters: Record }`
- `parseContentType(value)` → `ParsedContentType` — RFC 7231 parser with quoted-string support
### Database
- `BUILTIN_DB` — the built-in curated IANA-sourced registry (read-only reference)
- `getEntryByExtension(ext)` → `MimeEntry | undefined`
- `getMimeEntry(mimeType)` → `MimeEntry | undefined`
- `listMimeEntries()` → `MimeEntry[]`
- `type MimeCategory` — `'image' | 'audio' | 'video' | 'text' | 'application' | 'font' | 'model' | 'message' | 'multipart' | 'chemical'`
- `type MimeEntry` — `{ mimeType: string; category: MimeCategory; extensions: string[]; compressible?: boolean; charset?: string }`
- `registerMimeEntry(entry)` → `void` — add or override an entry at runtime
### Detection
- `type ContentTypeDetectionResult` — detection result sourced from a Content-Type header
- `type DetectInput` — `{ bytes?: Uint8Array; extension?: string; contentType?: string; signal?: AbortSignal }`
- `type DetectResult` — union of `MagicBytesResult | ExtensionDetectionResult | ContentTypeDetectionResult | StreamDetectionResult`
- `type DetectSource` — `'magic' | 'extension' | 'content-type' | 'stream'`
- `detect(input)` → `Promise` — unified detection: tries magic bytes first, then extension, then Content-Type header
- `detectAllFromMagicBytes(bytes)` → `MagicBytesResult[]` — all matching signatures for a buffer
- `detectFromContentType(contentType)` → `ContentTypeDetectionResult` — detection from parsed header alone
- `detectFromExtension(ext)` → `ExtensionDetectionResult` — detection via extension lookup
- `detectFromMagicBytes(bytes)` → `MagicBytesResult` — best-match detection from buffer
- `detectFromStream(stream, opts?)` → `Promise` — thin AbortSignal wrapper over `detectFromMagicBytes`; defers full streaming integration until `@suaveplan/streams` ships
- `type ExtensionDetectionResult` — detection result sourced from a file extension
- `type MagicBytesResult` — detection result sourced from magic bytes
- `registerSignature(sig)` → `void` — add a custom signature to the trie at runtime
- `type Signature` — `{ mimeType: string; bytes: Uint8Array; offset?: number; mask?: Uint8Array }`
- `type StreamDetectionResult` — detection result sourced from a readable stream
### Errors
- `MIME_ERROR_METADATA` — error code metadata map for `@suaveplan/error` `defineCodes`
- `type MimeErrorCode` — union of all `MIME.*` code strings
- `registerMimeErrorCodes()` — registers all MIME error codes with the process-wide registry
- `InsufficientBytesError` extends `MimeError` — thrown when the buffer is too short for magic-byte detection
- `type InsufficientBytesErrorCode`
- `type InsufficientBytesErrorContext` — `{ needed: number; got: number }`
- `type InsufficientBytesErrorOptions`
- `InvalidContentTypeError` extends `MimeError` — thrown when a Content-Type string fails to parse
- `type InvalidContentTypeErrorCode`
- `type InvalidContentTypeErrorContext` — `{ raw: string }`
- `type InvalidContentTypeErrorOptions`
- `UnknownMimeTypeError` extends `MimeError` — thrown when a MIME type string has no registry entry
- `type UnknownMimeTypeErrorCode`
- `type UnknownMimeTypeErrorContext` — `{ mimeType: string }`
- `type UnknownMimeTypeErrorOptions`
### Reverse lookups (main branch additions to preserve)
- `toAllExtensions(mimeType)` → `string[]` — all extensions mapped to a MIME type
- `toCategory(mimeType)` → `MimeCategory | undefined` — category lookup by MIME type string
- `toDefaultExtension(mimeType)` → `string | undefined` — canonical primary extension
### Legacy
- `getMimeType(ext)` → `string | undefined` — simple extension-to-mime-type lookup (legacy shim; retained for API surface completeness)
## New additions for genesis
### Structured errors
- `MimeError` base class extends `SuaveplanError` with codes under the `MIME.*` namespace
- Error codes: `MIME.PARSE_ERROR`, `MIME.INSUFFICIENT_BYTES`, `MIME.UNKNOWN_SIGNATURE`, `MIME.UNKNOWN_TYPE`, `MIME.INVALID_CONTENT_TYPE`
- `InsufficientBytesError`, `InvalidContentTypeError`, `UnknownMimeTypeError`, `ParseError`, `UnknownSignatureError` all extend `MimeError`
- Error codes registered via side-effect `import "./error-codes.js"` in `src/index.ts`
### Zod schemas
- `ParsedContentTypeSchema` — validates `ParsedContentType` at the boundary
- `MimeEntrySchema` — validates `MimeEntry` and `MimeCategory`
- `SignatureSchema` — validates a `Signature` before insertion into the trie
- `DetectResultSchema` — discriminated union validator for `DetectResult`
- `DetectInputSchema` — validates `DetectInput` options
### OTel instrumentation (via `@opentelemetry/api`, no-op without exporter)
- Counter `mime.detect.total` — by `source` label (`magic` / `extension` / `content-type` / `stream`)
- Histogram `mime.detect.duration_ms` — per `detect()` call
- Counter `mime.parse.total` — by `outcome` label (`success` / `error`)
### detectFromStream — deferred streaming, interim shim
`detectFromStream` is exported and typed as in the main branch but is implemented as a thin wrapper that reads the first chunk from the provided async iterable (or `ReadableStream`) with an `AbortSignal` and delegates to `detectFromMagicBytes`. Full streaming integration is deferred until `@suaveplan/streams` ships.
### Property-based tests (fast-check)
- `parseContentType → formatContentType` round-trip: for any valid Content-Type string, `formatContentType(parseContentType(s))` parses back to the same `ParsedContentType`
- Magic-byte detection: `detectFromMagicBytes(bytes)` always returns a result or throws `InsufficientBytesError` — never throws any other error class
- Registry round-trip: any entry registered via `registerMimeEntry(e)` is immediately retrievable via `getMimeEntry(e.mimeType)` with identical shape
### Benchmarks (mitata)
- `parseContentType` — 10 000 iterations over a representative set of realistic Content-Type strings
- `detect` from `Uint8Array` — 10 000 iterations with a 4 KB buffer covering a variety of magic-byte signatures
- `getMimeEntry` lookup — 100 000 iterations over the built-in database
## Impact
- Adds one new package: `packages/universal/mime`
- Adds one new spec capability: `mime`
- No existing package consumers on genesis yet