Initial Commit
This commit is contained in:
159
openspec/archive/2026-04-19-enterprise-mime/proposal.md
Normal file
159
openspec/archive/2026-04-19-enterprise-mime/proposal.md
Normal file
@@ -0,0 +1,159 @@
|
||||
# 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
|
||||
Reference in New Issue
Block a user