Files
suaveplan/openspec/archive/2026-04-19-enterprise-mime/tasks.md
2026-05-27 23:29:53 +01:00

10 KiB

Tasks: enterprise-mime

1. Scaffold

  • 1.1 Create packages/universal/mime/ with package.json, tsconfig.json, tsconfig.lib.json, vite.config.ts, vitest.config.ts, README.md skeleton
  • 1.2 Register coverage thresholds at 100/100/100/100 in vitest.config.ts; provider = istanbul
  • 1.3 Add cross-cutting guard tests in tests/: dep-graph, no-disallowed, tree-shake
  • 1.4 Add package to root CLAUDE.md, README.md, and STRUCTURE.yml

2. Error types

  • 2.1 Implement src/errors/mime-error.ts — MimeError base extends SuaveplanError
  • 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
  • 2.3 Implement src/errors/insufficient-bytes-error.ts — InsufficientBytesError extends MimeError; InsufficientBytesErrorCode, InsufficientBytesErrorContext ({ needed: number; got: number }), InsufficientBytesErrorOptions
  • 2.4 Implement src/errors/invalid-content-type-error.ts — InvalidContentTypeError extends MimeError; context { raw: string }
  • 2.5 Implement src/errors/unknown-mime-type-error.ts — UnknownMimeTypeError extends MimeError; context { mimeType: string }
  • 2.6 Implement src/errors/parse-error.ts — ParseError extends MimeError (code MIME.PARSE_ERROR)
  • 2.7 Implement src/errors/unknown-signature-error.ts — UnknownSignatureError extends MimeError (code MIME.UNKNOWN_SIGNATURE)
  • 2.8 Tests: construction, code narrowing, context field shapes, instanceof MimeError
  • 2.9 Co-located .md for each error file

3. Zod schemas

  • 3.1 Implement src/schemas/content-type.schemas.ts — ParsedContentTypeSchema, MediaTypeSchema
  • 3.2 Implement src/schemas/db.schemas.ts — MimeCategorySchema, MimeEntrySchema
  • 3.3 Implement src/schemas/detection.schemas.ts — SignatureSchema, DetectInputSchema, MagicBytesResultSchema, ExtensionDetectionResultSchema, ContentTypeDetectionResultSchema, StreamDetectionResultSchema, DetectResultSchema (discriminated union)
  • 3.4 Tests: valid and invalid inputs for every schema; discriminated union branches
  • 3.5 Co-located .md for each schemas file

4. Content-type parser

  • 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
  • 4.2 Implement src/content-type/format-content-type.ts — formatContentType(parsed) → string; canonical round-trip serialisation; quotes parameter values that require it
  • 4.3 Implement src/content-type/content-type-equals.ts — contentTypeEquals(a, b) → boolean; structural equality ignoring parameter order and case
  • 4.4 Implement src/content-type/content-type-matches.ts — contentTypeMatches(actual, pattern) → boolean; wildcard * subtype matching
  • 4.5 Define src/content-type/content-type.types.ts — MediaType, ParsedContentType
  • 4.6 Unit tests: RFC 7231 edge cases (quoted strings, OWS, empty params, case), equality, matching, round-trip
  • 4.7 Property-based tests: formatContentType(parseContentType(s)) round-trip invariant
  • 4.8 Co-located .md for each content-type file

5. MIME database

  • 5.1 Implement src/db/mime-entry.types.ts — MimeCategory, MimeEntry
  • 5.2 Implement src/db/builtin-db.ts — BUILTIN_DB: curated, IANA-sourced, comprehensive set of MimeEntry records covering all major categories
  • 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
  • 5.4 Implement src/reverse/reverse.ts — toAllExtensions(mimeType), toCategory(mimeType), toDefaultExtension(mimeType)
  • 5.5 Implement src/legacy/get-mime-type.ts — getMimeType(ext) legacy shim delegating to getEntryByExtension
  • 5.6 Unit tests: lookups hit BUILTIN_DB; registerMimeEntry overrides; listMimeEntries returns sorted copy; reverse lookups; legacy shim
  • 5.7 Property-based tests: getMimeEntry(registerMimeEntry(e).mimeType) round-trip
  • 5.8 Co-located .md for each db file

6. Magic-byte detection

  • 6.1 Define src/detection/signature.types.ts — Signature ({ mimeType: string; bytes: Uint8Array; offset?: number; mask?: Uint8Array }); MagicBytesResult
  • 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
  • 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
  • 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
  • 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
  • 6.6 Implement src/detection/detect-all-from-magic-bytes.ts — detectAllFromMagicBytes(bytes) → MagicBytesResult[]; returns all matching signatures, sorted by specificity (longest match first)
  • 6.7 Unit tests: known-format buffers match expected MIME types; insufficient buffer throws InsufficientBytesError; masked signatures; offset signatures
  • 6.8 Property-based tests: detectFromMagicBytes(bytes) is always a result or InsufficientBytesError — never another error class
  • 6.9 Co-located .md for each detection file in this step

7. Charset detection

  • 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)
  • 7.2 Implement src/charset/sniff-html-meta.ts — sniffHtmlMeta(bytes) → string | null; scans up to 1 024 bytes for <meta charset="…"> and <meta http-equiv="Content-Type" content="…; charset=…"> patterns (case-insensitive)
  • 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
  • 7.4 Define src/charset/charset.types.ts — BomDetection ({ charset: string; bomLength: number })
  • 7.5 Unit tests: known BOM sequences; HTML meta with various attribute orderings; XML declaration with and without encoding; non-matching inputs return null
  • 7.6 Co-located .md for each charset file

8. Extension + content-type detection

  • 8.1 Implement src/detection/detect-from-extension.ts — detectFromExtension(ext) → ExtensionDetectionResult; delegates to getEntryByExtension; normalises leading dot; throws UnknownMimeTypeError on unknown extension
  • 8.2 Implement src/detection/detect-from-content-type.ts — detectFromContentType(contentType) → ContentTypeDetectionResult; calls parseContentType then getMimeEntry; throws InvalidContentTypeError or UnknownMimeTypeError
  • 8.3 Define src/detection/detection.types.ts — DetectSource, DetectInput, DetectResult, ExtensionDetectionResult, ContentTypeDetectionResult, StreamDetectionResult
  • 8.4 Unit tests: known extensions map to correct MIME types; unknown extension throws; invalid Content-Type throws; round-trip through formatContentType
  • 8.5 Co-located .md for each file

9. Unified detect() and detectFromStream()

  • 9.1 Implement src/detection/detect.ts — detect(input) → Promise<DetectResult>; priority order: magic bytes → extension → content-type header; respects input.signal; validates input via DetectInputSchema
  • 9.2 Implement src/detection/detect-from-stream.ts — detectFromStream(stream, opts?) → Promise<StreamDetectionResult>; reads the first chunk from an async iterable or ReadableStream, then delegates to detectFromMagicBytes; honours AbortSignal; documents deferred-streaming intent
  • 9.3 Unit tests: all three detection paths exercised; signal cancellation; empty input handled gracefully
  • 9.4 Co-located .md for each file

10. OTel instrumentation

  • 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)
  • 10.2 Wire instrumentation into detect, detectFromMagicBytes, detectFromExtension, detectFromContentType, detectFromStream, parseContentType
  • 10.3 Tests: confirm no-op behaviour when no OTel exporter is registered (no throw, no side-effects visible)
  • 10.4 Co-located src/telemetry/meters.md

11. Property tests + benchmarks

  • 11.1 src/__benchmarks__/parse-content-type.bench.ts — parseContentType 10 000 iterations (mitata)
  • 11.2 src/__benchmarks__/detect.bench.ts — detect from 4 KB Uint8Array 10 000 iterations
  • 11.3 src/__benchmarks__/get-mime-entry.bench.ts — getMimeEntry lookup 100 000 iterations
  • 11.4 Publish baseline numbers in README.md

12. Docs

  • 12.1 Co-located .md for every implementation file (≥200 words, simple + advanced examples, API reference, implementation notes)
  • 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
  • 12.3 bun run docs:check --filter=@suaveplan/mime ≥ 90%

13. Verify and publish

  • 13.1 bunx turbo lint typecheck test --filter=@suaveplan/mime green
  • 13.2 bunx turbo test:coverage --filter=@suaveplan/mime at 100/100/100/100
  • 13.3 bunx turbo build --filter=@suaveplan/mime green
  • 13.4 bun scripts/publish.ts --package @suaveplan/mime to Verdaccio at 0.0.0
  • 13.5 Archive OpenSpec change to openspec/archive/2026-04-19-enterprise-mime/