# Tasks: enterprise-mime ## 1. Scaffold - [x] 1.1 Create `packages/universal/mime/` with `package.json`, `tsconfig.json`, `tsconfig.lib.json`, `vite.config.ts`, `vitest.config.ts`, `README.md` skeleton - [x] 1.2 Register coverage thresholds at 100/100/100/100 in `vitest.config.ts`; provider = istanbul - [x] 1.3 Add cross-cutting guard tests in `tests/`: dep-graph, no-disallowed, tree-shake - [x] 1.4 Add package to root `CLAUDE.md`, `README.md`, and `STRUCTURE.yml` ## 2. Error types - [x] 2.1 Implement `src/errors/mime-error.ts` — `MimeError` base extends `SuaveplanError` - [x] 2.2 Implement error code constants in `src/error-codes.ts` — `MIME_ERROR_METADATA`, `MimeErrorCode`, `registerMimeErrorCodes()` using `defineCodes` for `MIME.PARSE_ERROR`, `MIME.INSUFFICIENT_BYTES`, `MIME.UNKNOWN_SIGNATURE`, `MIME.UNKNOWN_TYPE`, `MIME.INVALID_CONTENT_TYPE` - [x] 2.3 Implement `src/errors/insufficient-bytes-error.ts` — `InsufficientBytesError` extends `MimeError`; `InsufficientBytesErrorCode`, `InsufficientBytesErrorContext` (`{ needed: number; got: number }`), `InsufficientBytesErrorOptions` - [x] 2.4 Implement `src/errors/invalid-content-type-error.ts` — `InvalidContentTypeError` extends `MimeError`; context `{ raw: string }` - [x] 2.5 Implement `src/errors/unknown-mime-type-error.ts` — `UnknownMimeTypeError` extends `MimeError`; context `{ mimeType: string }` - [x] 2.6 Implement `src/errors/parse-error.ts` — `ParseError` extends `MimeError` (code `MIME.PARSE_ERROR`) - [x] 2.7 Implement `src/errors/unknown-signature-error.ts` — `UnknownSignatureError` extends `MimeError` (code `MIME.UNKNOWN_SIGNATURE`) - [x] 2.8 Tests: construction, code narrowing, context field shapes, `instanceof MimeError` - [x] 2.9 Co-located `.md` for each error file ## 3. Zod schemas - [x] 3.1 Implement `src/schemas/content-type.schemas.ts` — `ParsedContentTypeSchema`, `MediaTypeSchema` - [x] 3.2 Implement `src/schemas/db.schemas.ts` — `MimeCategorySchema`, `MimeEntrySchema` - [x] 3.3 Implement `src/schemas/detection.schemas.ts` — `SignatureSchema`, `DetectInputSchema`, `MagicBytesResultSchema`, `ExtensionDetectionResultSchema`, `ContentTypeDetectionResultSchema`, `StreamDetectionResultSchema`, `DetectResultSchema` (discriminated union) - [x] 3.4 Tests: valid and invalid inputs for every schema; discriminated union branches - [x] 3.5 Co-located `.md` for each schemas file ## 4. Content-type parser - [x] 4.1 Implement `src/content-type/parse-content-type.ts` — `parseContentType(value)` → `ParsedContentType`; RFC 7231 compliant; handles quoted-string parameters, case-insensitive type/subtype, OWS trimming; throws `InvalidContentTypeError` on parse failure - [x] 4.2 Implement `src/content-type/format-content-type.ts` — `formatContentType(parsed)` → `string`; canonical round-trip serialisation; quotes parameter values that require it - [x] 4.3 Implement `src/content-type/content-type-equals.ts` — `contentTypeEquals(a, b)` → `boolean`; structural equality ignoring parameter order and case - [x] 4.4 Implement `src/content-type/content-type-matches.ts` — `contentTypeMatches(actual, pattern)` → `boolean`; wildcard `*` subtype matching - [x] 4.5 Define `src/content-type/content-type.types.ts` — `MediaType`, `ParsedContentType` - [x] 4.6 Unit tests: RFC 7231 edge cases (quoted strings, OWS, empty params, case), equality, matching, round-trip - [x] 4.7 Property-based tests: `formatContentType(parseContentType(s))` round-trip invariant - [x] 4.8 Co-located `.md` for each content-type file ## 5. MIME database - [x] 5.1 Implement `src/db/mime-entry.types.ts` — `MimeCategory`, `MimeEntry` - [x] 5.2 Implement `src/db/builtin-db.ts` — `BUILTIN_DB`: curated, IANA-sourced, comprehensive set of `MimeEntry` records covering all major categories - [x] 5.3 Implement `src/db/registry.ts` — mutable runtime registry layered over `BUILTIN_DB`; `getMimeEntry(mimeType)`, `getEntryByExtension(ext)`, `listMimeEntries()`, `registerMimeEntry(entry)` — throws `UnknownMimeTypeError` on missing lookup when strict mode requested - [x] 5.4 Implement `src/reverse/reverse.ts` — `toAllExtensions(mimeType)`, `toCategory(mimeType)`, `toDefaultExtension(mimeType)` - [x] 5.5 Implement `src/legacy/get-mime-type.ts` — `getMimeType(ext)` legacy shim delegating to `getEntryByExtension` - [x] 5.6 Unit tests: lookups hit BUILTIN_DB; `registerMimeEntry` overrides; `listMimeEntries` returns sorted copy; reverse lookups; legacy shim - [x] 5.7 Property-based tests: `getMimeEntry(registerMimeEntry(e).mimeType)` round-trip - [x] 5.8 Co-located `.md` for each db file ## 6. Magic-byte detection - [x] 6.1 Define `src/detection/signature.types.ts` — `Signature` (`{ mimeType: string; bytes: Uint8Array; offset?: number; mask?: Uint8Array }`); `MagicBytesResult` - [x] 6.2 Implement `src/detection/trie.ts` — compiled byte trie built from the signature table; supports offset-aware matching and bitmask application; exported as `buildTrie`, `matchTrie` - [x] 6.3 Implement `src/detection/builtin-signatures.ts` — built-in signature table covering common formats: PNG, JPEG, GIF, WebP, AVIF, PDF, ZIP, GZIP, BZIP2, XZ, WASM, ELF, MP4/ISO-BMFF, OGG, FLAC, MP3, WAV, DOCX/XLSX/PPTX (ZIP-based), 7z, RAR, TAR, SQLite, ICO, BMP, TIFF, PSD, SWF, RTF, XML, HTML, and others - [x] 6.4 Implement `src/detection/signature-registry.ts` — `registerSignature(sig)`, `getSignatures()`, `resetSignatures()` (test-only); validates via `SignatureSchema` before insertion; throws `UnknownSignatureError` on duplicate with conflict - [x] 6.5 Implement `src/detection/detect-from-magic-bytes.ts` — `detectFromMagicBytes(bytes)` → `MagicBytesResult`; uses compiled trie; throws `InsufficientBytesError` if buffer is shorter than the shortest registered signature - [x] 6.6 Implement `src/detection/detect-all-from-magic-bytes.ts` — `detectAllFromMagicBytes(bytes)` → `MagicBytesResult[]`; returns all matching signatures, sorted by specificity (longest match first) - [x] 6.7 Unit tests: known-format buffers match expected MIME types; insufficient buffer throws `InsufficientBytesError`; masked signatures; offset signatures - [x] 6.8 Property-based tests: `detectFromMagicBytes(bytes)` is always a result or `InsufficientBytesError` — never another error class - [x] 6.9 Co-located `.md` for each detection file in this step ## 7. Charset detection - [x] 7.1 Implement `src/charset/sniff-bom.ts` — `sniffBom(bytes)` → `BomDetection | null`; recognises UTF-8 BOM (EF BB BF), UTF-16 BE (FE FF), UTF-16 LE (FF FE), UTF-32 BE/LE (00 00 FE FF / FF FE 00 00) - [x] 7.2 Implement `src/charset/sniff-html-meta.ts` — `sniffHtmlMeta(bytes)` → `string | null`; scans up to 1 024 bytes for `` and `` patterns (case-insensitive) - [x] 7.3 Implement `src/charset/sniff-xml-decl.ts` — `sniffXmlDecl(bytes)` → `string | null`; extracts `encoding="…"` from the XML declaration if present within the first 128 bytes - [x] 7.4 Define `src/charset/charset.types.ts` — `BomDetection` (`{ charset: string; bomLength: number }`) - [x] 7.5 Unit tests: known BOM sequences; HTML meta with various attribute orderings; XML declaration with and without encoding; non-matching inputs return null - [x] 7.6 Co-located `.md` for each charset file ## 8. Extension + content-type detection - [x] 8.1 Implement `src/detection/detect-from-extension.ts` — `detectFromExtension(ext)` → `ExtensionDetectionResult`; delegates to `getEntryByExtension`; normalises leading dot; throws `UnknownMimeTypeError` on unknown extension - [x] 8.2 Implement `src/detection/detect-from-content-type.ts` — `detectFromContentType(contentType)` → `ContentTypeDetectionResult`; calls `parseContentType` then `getMimeEntry`; throws `InvalidContentTypeError` or `UnknownMimeTypeError` - [x] 8.3 Define `src/detection/detection.types.ts` — `DetectSource`, `DetectInput`, `DetectResult`, `ExtensionDetectionResult`, `ContentTypeDetectionResult`, `StreamDetectionResult` - [x] 8.4 Unit tests: known extensions map to correct MIME types; unknown extension throws; invalid Content-Type throws; round-trip through `formatContentType` - [x] 8.5 Co-located `.md` for each file ## 9. Unified detect() and detectFromStream() - [x] 9.1 Implement `src/detection/detect.ts` — `detect(input)` → `Promise`; priority order: magic bytes → extension → content-type header; respects `input.signal`; validates input via `DetectInputSchema` - [x] 9.2 Implement `src/detection/detect-from-stream.ts` — `detectFromStream(stream, opts?)` → `Promise`; reads the first chunk from an async iterable or `ReadableStream`, then delegates to `detectFromMagicBytes`; honours `AbortSignal`; documents deferred-streaming intent - [x] 9.3 Unit tests: all three detection paths exercised; signal cancellation; empty input handled gracefully - [x] 9.4 Co-located `.md` for each file ## 10. OTel instrumentation - [x] 10.1 Implement `src/telemetry/meters.ts` — create meter `@suaveplan/mime`; define counter `mime.detect.total` (attribute: `source`), histogram `mime.detect.duration_ms`, counter `mime.parse.total` (attribute: `outcome`) - [x] 10.2 Wire instrumentation into `detect`, `detectFromMagicBytes`, `detectFromExtension`, `detectFromContentType`, `detectFromStream`, `parseContentType` - [x] 10.3 Tests: confirm no-op behaviour when no OTel exporter is registered (no throw, no side-effects visible) - [x] 10.4 Co-located `src/telemetry/meters.md` ## 11. Property tests + benchmarks - [x] 11.1 `src/__benchmarks__/parse-content-type.bench.ts` — `parseContentType` 10 000 iterations (mitata) - [x] 11.2 `src/__benchmarks__/detect.bench.ts` — `detect` from 4 KB `Uint8Array` 10 000 iterations - [x] 11.3 `src/__benchmarks__/get-mime-entry.bench.ts` — `getMimeEntry` lookup 100 000 iterations - [x] 11.4 Publish baseline numbers in `README.md` ## 12. Docs - [x] 12.1 Co-located `.md` for every implementation file (≥200 words, simple + advanced examples, API reference, implementation notes) - [x] 12.2 Rewrite `README.md` with full public API table, detection priority diagram, database category table, charset detection guide, consumer quick-start, and benchmark results - [x] 12.3 `bun run docs:check --filter=@suaveplan/mime` ≥ 90% ## 13. Verify and publish - [x] 13.1 `bunx turbo lint typecheck test --filter=@suaveplan/mime` green - [x] 13.2 `bunx turbo test:coverage --filter=@suaveplan/mime` at 100/100/100/100 - [x] 13.3 `bunx turbo build --filter=@suaveplan/mime` green - [x] 13.4 `bun scripts/publish.ts --package @suaveplan/mime` to Verdaccio at 0.0.0 - [x] 13.5 Archive OpenSpec change to `openspec/archive/2026-04-19-enterprise-mime/`