Files
2026-05-27 23:29:53 +01:00

8.8 KiB

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 <meta> 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<string, string> }
  • 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<DetectResult> — 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<StreamDetectionResult> — 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