# 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