first commit

This commit is contained in:
2026-05-29 08:20:56 +01:00
commit 949299f58d
6368 changed files with 527176 additions and 0 deletions

View File

@@ -0,0 +1,90 @@
# detection/detect
## Purpose
The unified MIME detection entry point for `@suaveplan/mime`. Merges evidence from three independent sources — magic bytes, file extension, and a declared `Content-Type` header — using a confidence-scoring model. When sources agree, the confidence is boosted above any single-source threshold. When they disagree, magic bytes win over extension, which wins over declared headers, reflecting real-world reliability.
## Features
- Multi-source detection: bytes, extension/filename, Content-Type header
- Confidence scores: magic-bytes=0.95, extension=0.7, content-type=0.6
- Agreement boost: +0.05 per additional agreeing source (capped at 1.0)
- Falls back to `application/octet-stream` with confidence=0 when no sources available
- Accepts `filename` as an alias for `extension` (strips path components automatically)
- Swallows malformed Content-Type headers gracefully (no throws)
- Re-exports `detectFromMagicBytes` and `detectAllFromMagicBytes` for convenience
## Usage: Basic Example
```typescript
import { detect } from "@suaveplan/mime/detection/detect";
const result = detect({
bytes: fileBuffer,
filename: "photo.jpg",
});
console.log(result.mime); // "image/jpeg"
console.log(result.confidence); // 0.95 (magic bytes match)
console.log(result.source); // "magic-bytes"
```
## Usage: Advanced Example
```typescript
import { detect } from "@suaveplan/mime/detection/detect";
// All three sources provided — if they agree, confidence is boosted
const result = detect({
bytes: buffer,
filename: "report.pdf",
contentType: "application/pdf",
});
if (result.confidence > 0.9) {
console.log("High confidence:", result.mime);
console.log("Sources:", result.candidates.map(c => c.source));
}
// Only extension provided
const byExtension = detect({ extension: "svg" });
console.log(byExtension.mime); // "image/svg+xml"
console.log(byExtension.confidence); // 0.7
// No sources — safe fallback
const fallback = detect({});
console.log(fallback.mime); // "application/octet-stream"
console.log(fallback.confidence); // 0
```
## API Reference
### `detect(input: DetectInput): DetectResult`
| Parameter | Type | Description |
|-----------|------|-------------|
| `input` | `DetectInput` | Detection inputs — at least one field should be set. |
**Returns:** `DetectResult` with `mime`, `confidence`, `source`, and `candidates` array.
**Throws:** Never — malformed Content-Type headers are silently ignored.
### `DetectInput`
| Field | Type | Description |
|-------|------|-------------|
| `bytes?` | `Uint8Array` | Raw file bytes for magic-byte detection |
| `extension?` | `string` | File extension (with or without dot) |
| `filename?` | `string` | Full filename or path (extension extracted automatically) |
| `contentType?` | `string` | Raw `Content-Type` header value |
### `DetectResult`
| Field | Type | Description |
|-------|------|-------------|
| `mime` | `string` | Best MIME type guess |
| `confidence` | `number` | 0.0–1.0 confidence score |
| `source` | `DetectSource` | Primary detection source |
| `candidates` | `Candidate[]` | All candidate MIME types with their sources |
## Implementation Notes
Candidates are collected in descending confidence order: magic-bytes first, then extension, then content-type. Because this order is fixed and confidence values are monotonically decreasing, the first candidate in the array is always the best. The confidence boost formula `min(1, bestScore + (agreeing - 1) * 0.05)` adds 5% for each additional source that agrees with the winner, reflecting that corroborating evidence raises overall certainty.

View File

@@ -0,0 +1,56 @@
# detection/from-content-type
## Purpose
Detects the MIME type from a raw `Content-Type` header string. Parses the header using the full RFC 7231 parser and wraps the result in a `DetectionResult` envelope. Serves as one of three detection sources in the unified `detect()` function, with a confidence weight of 0.6 (lowest of the three sources, since declared headers can be wrong or missing).
## Features
- Delegates to `parseContentType` for full RFC 7231 parsing
- Returns a `DetectionResult` with `source: "content-type"` and the canonical `essence`
- Propagates `InvalidContentTypeError` for malformed inputs (callers can catch or ignore)
- Accepts parameters in the header (they are parsed but not included in the MIME result)
## Usage: Basic Example
```typescript
import { detectFromContentType } from "@suaveplan/mime/detection/from-content-type";
const result = detectFromContentType("text/html; charset=utf-8");
console.log(result.mime); // "text/html"
console.log(result.source); // "content-type"
```
## Usage: Advanced Example
```typescript
import { detectFromContentType } from "@suaveplan/mime/detection/from-content-type";
import { InvalidContentTypeError } from "@suaveplan/mime/errors";
// The detect() facade catches these errors automatically.
// Use detectFromContentType directly when you want strict validation:
try {
const result = detectFromContentType(request.headers.get("content-type") ?? "");
console.log("Declared MIME:", result.mime);
} catch (err) {
if (err instanceof InvalidContentTypeError) {
console.warn("Malformed Content-Type header — using fallback detection");
}
}
```
## API Reference
### `detectFromContentType(header: string): DetectionResult`
| Parameter | Type | Description |
|-----------|------|-------------|
| `header` | `string` | Raw `Content-Type` header value. |
**Returns:** `DetectionResult` with `mime` set to the `essence` (`type/subtype`) and `source: "content-type"`.
**Throws:** `InvalidContentTypeError` if the header is malformed.
## Implementation Notes
This function intentionally throws on malformed input rather than returning a fallback. The `detect()` facade wraps calls to `detectFromContentType` in a try/catch to silently skip bad headers. This two-layer design keeps the individual detectors strict while the unified facade remains lenient — matching the principle that each building block should have well-defined error semantics, with fault tolerance composed at a higher level.

View File

@@ -0,0 +1,62 @@
# detection/from-extension
## Purpose
Detects the MIME type from a file extension or full filename. Strips path components and leading dots, normalizes to lowercase, and looks up the result in the built-in MIME database. Returns a `DetectionResult` with `source: "extension"` and a confidence weight of 0.7 in the unified `detect()` function.
## Features
- Accepts both raw extensions (`.png`, `png`) and full filenames (`photo.jpeg`)
- Strips directory path components automatically
- Normalizes extensions to lowercase before lookup
- Handles multiple dots in filenames correctly (uses the last extension)
- Returns `undefined` for unknown extensions instead of throwing
- Wraps the database entry in a `DetectionResult` envelope
## Usage: Basic Example
```typescript
import { detectFromExtension } from "@suaveplan/mime/detection/from-extension";
const result = detectFromExtension("photo.jpg");
console.log(result?.mime); // "image/jpeg"
console.log(result?.source); // "extension"
// Just an extension without filename
const svg = detectFromExtension(".svg");
console.log(svg?.mime); // "image/svg+xml"
```
## Usage: Advanced Example
```typescript
import { detectFromExtension } from "@suaveplan/mime/detection/from-extension";
function getMimeForUpload(filename: string): string {
const detected = detectFromExtension(filename);
// Fall back to binary stream for unknown extensions
return detected?.mime ?? "application/octet-stream";
}
// Handles paths with directories
console.log(detectFromExtension("/uploads/2024/report.pdf")?.mime); // "application/pdf"
// Unknown extension returns undefined
console.log(detectFromExtension("file.xyz")); // undefined
```
## API Reference
### `detectFromExtension(input: string): DetectionResult | undefined`
| Parameter | Type | Description |
|-----------|------|-------------|
| `input` | `string` | File extension (with or without dot) or full filename/path. |
**Returns:** `DetectionResult` with `source: "extension"` and the matched `mime`, or `undefined` when the extension is not in the database.
**Throws:** Never.
## Implementation Notes
The function extracts the extension by splitting on `.` and taking the last segment, then looking up `getEntryByExtension`. The split also handles filenames with no extension (returns `undefined`) and filenames starting with a dot (treated as hidden files with no extension, e.g. `.gitignore`). Path components are stripped by looking only at the basename portion after the last `/` or `\`. Lookup is O(1) via the registry's `Map<string, MimeEntry>` index.

View File

@@ -0,0 +1,86 @@
# detection/from-magic-bytes
<!-- begin:auto-docs -->
_(no submodules)_
<!-- end:auto-docs --> - helpers — Low-level byte-inspection utilities used……
- [helpers](./helpers.md) — Low-level byte-inspection utilities used by the magic-byte signature matchers. Provides helpers for common patterns: pr…
- [registry](./registry.md) — The mutable signature registry for magic-byte detection. Stores all registered byte-pattern matchers and provides CRUD-…
- helpers — Low-level byte-inspection utilities used……
- [helpers](./helpers.md) — Low-level byte-inspection utilities used by the magic-byte signature matchers. Provides helpers for common patterns: pr…
- [registry](./registry.md) — The mutable signature registry for magic-byte detection. Stores all registered byte-pattern matchers and provides CRUD-…
- helpers — Low-level byte-inspection utilities used……
- [helpers](./helpers.md) — Low-level byte-inspection utilities used by the magic-byte signature matchers. Provides helpers for common patterns: pr…
- [registry](./registry.md) — The mutable signature registry for magic-byte detection. Stores all registered byte-pattern matchers and provides CRUD-…
- helpers — Low-level byte-inspection utilities used…
- [helpers](./helpers.md) — Low-level byte-inspection utilities used by the magic-byte signature matchers. Provides helpers for common patterns: pr…
- [registry](./registry.md) — The mutable signature registry for magic-byte detection. Stores all registered byte-pattern matchers and provides CRUD-…
- [helpers](./helpers.md) — Low-level byte-inspection utilities used by the magic-byte signature matchers. Provides helpers for common patterns: pr…
- [registry](./registry.md) — The mutable signature registry for magic-byte detection. Stores all registered byte-pattern matchers and provides CRUD-…
## Purpose
Implements magic-byte (file signature) detection for MIME types. Maintains a registry of byte-pattern matchers sorted by priority, and provides functions to find the best match, all matches, or reset the registry. Magic-byte detection has the highest confidence (0.95) in the unified `detect()` function because byte signatures are objective evidence that cannot be spoofed by file naming or HTTP headers.
## Features
- `detectFromMagicBytes(bytes)` — returns the highest-priority matching MIME type
- `detectAllFromMagicBytes(bytes)` — returns all matching MIME types sorted by priority
- Registry sorted by descending priority for deterministic match order
- Accepts any `Uint8Array` or `ArrayBuffer`-backed view
- Returns `undefined` (not throws) when no signature matches
- Supports minimum-bytes threshold per signature — skips signatures when the input is too short
## Usage: Basic Example
```typescript
import { detectFromMagicBytes } from "@suaveplan/mime/detection/from-magic-bytes/from-magic-bytes";
// PNG magic bytes: 89 50 4E 47 0D 0A 1A 0A
const pngBytes = new Uint8Array([0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a]);
const result = detectFromMagicBytes(pngBytes);
console.log(result?.mime); // "image/png"
console.log(result?.priority); // 100
```
## Usage: Advanced Example
```typescript
import {
detectFromMagicBytes,
detectAllFromMagicBytes,
} from "@suaveplan/mime/detection/from-magic-bytes/from-magic-bytes";
// Detect all matching signatures (useful for ambiguous formats like MP4 variants)
const mp4Bytes = new Uint8Array([...]);
const allMatches = detectAllFromMagicBytes(mp4Bytes);
for (const match of allMatches) {
console.log(match.mime, match.priority);
}
// Returns undefined for unrecognized byte sequences
const random = new Uint8Array([0x00, 0x01, 0x02, 0x03]);
console.log(detectFromMagicBytes(random)); // undefined
```
## API Reference
### `detectFromMagicBytes(bytes: Uint8Array): SignatureMatch | undefined`
| Parameter | Type | Description |
|-----------|------|-------------|
| `bytes` | `Uint8Array` | Byte buffer to inspect (first N bytes of a file). |
**Returns:** The highest-priority `SignatureMatch` (`mime`, `priority`), or `undefined`.
### `detectAllFromMagicBytes(bytes: Uint8Array): SignatureMatch[]`
Returns all matching signatures sorted by descending priority.
## Implementation Notes
The registry is sorted once whenever a new signature is registered, using a stable sort on `priority` (descending). Detection iterates the sorted array and returns the first match (for `detectFromMagicBytes`) or all matches (for `detectAllFromMagicBytes`). Signatures with a `minBytes` threshold are skipped when the input buffer is too short, enabling detection on partial reads without false negatives from truncated magic sequences.

View File

@@ -0,0 +1,75 @@
# detection/from-magic-bytes/helpers
## Purpose
Low-level byte-inspection utilities used by the magic-byte signature matchers. Provides helpers for common patterns: prefix matching, ASCII substring scanning, 32-bit big-endian integer reading, and byte-sequence searching. These functions are designed to be called millions of times without allocation, using only index arithmetic over the input `Uint8Array`.
## Features
- `startsWith(bytes, prefix, offset?)` — check if bytes start with a given byte sequence at an offset
- `hasAscii(bytes, text, offset?)` — check if bytes contain an ASCII string at a given offset
- `readUInt32BE(bytes, offset)` — read a 4-byte big-endian unsigned integer
- `bytesInclude(bytes, needle)` — check if a byte sequence appears anywhere in bytes
- All functions are allocation-free (no `new Uint8Array`, no `slice`)
- All functions are bounds-safe (return false/0 rather than throwing on out-of-range access)
## Usage: Basic Example
```typescript
import { startsWith, hasAscii } from "@suaveplan/mime/detection/from-magic-bytes/helpers";
// Check PNG magic bytes
const PNG_MAGIC = new Uint8Array([0x89, 0x50, 0x4e, 0x47]);
const isPng = startsWith(fileBytes, PNG_MAGIC);
// Check for RIFF tag at offset 0, WAVE tag at offset 8
const isWav = hasAscii(fileBytes, "RIFF") && hasAscii(fileBytes, "WAVE", 8);
```
## Usage: Advanced Example
```typescript
import {
startsWith,
readUInt32BE,
bytesInclude,
} from "@suaveplan/mime/detection/from-magic-bytes/helpers";
// MP4: ftyp box at byte 4
function isMp4(bytes: Uint8Array): boolean {
const boxSize = readUInt32BE(bytes, 0);
return boxSize >= 8 && hasAscii(bytes, "ftyp", 4);
}
// ZIP: PK signature or EOCD record
function isZip(bytes: Uint8Array): boolean {
return startsWith(bytes, new Uint8Array([0x50, 0x4b, 0x03, 0x04])) ||
startsWith(bytes, new Uint8Array([0x50, 0x4b, 0x05, 0x06]));
}
// Search for a needle anywhere in a buffer
const PDF_HEADER = new Uint8Array([0x25, 0x50, 0x44, 0x46]); // %PDF
const containsPdf = bytesInclude(largeBuffer, PDF_HEADER);
```
## API Reference
### `startsWith(bytes: Uint8Array, prefix: Uint8Array, offset?: number): boolean`
Returns `true` if `bytes[offset...]` starts with all bytes of `prefix`. Returns `false` if the buffer is too short.
### `hasAscii(bytes: Uint8Array, text: string, offset?: number): boolean`
Returns `true` if the ASCII codes of `text` match `bytes[offset...]`. Returns `false` if the buffer is too short.
### `readUInt32BE(bytes: Uint8Array, offset: number): number`
Reads 4 bytes starting at `offset` as a big-endian unsigned 32-bit integer. Returns `0` if the buffer is too short.
### `bytesInclude(bytes: Uint8Array, needle: Uint8Array): boolean`
Returns `true` if `needle` appears anywhere within `bytes`. Returns `false` if the buffer is shorter than the needle.
## Implementation Notes
All helpers operate directly on the `Uint8Array` buffer without creating sub-views or intermediate arrays. The `hasAscii` function uses `charCodeAt` rather than `TextEncoder` to avoid the allocation overhead of encoding a string to bytes on every call — this matters because signature matchers run on every file detection. The `bytesInclude` function uses a naive O(n×m) scan, which is acceptable given that needle lengths are always small (≤8 bytes for magic sequences) and the search space is bounded by the buffer size.

View File

@@ -0,0 +1,84 @@
# detection/from-magic-bytes/registry
## Purpose
The mutable signature registry for magic-byte detection. Stores all registered byte-pattern matchers and provides CRUD-like access: `registerSignature` to add a matcher, `getSignatures` to read the sorted list, and `resetSignatures` to clear all entries (used in tests). This module is the single source of truth for the ordered list of matchers consumed by `detectFromMagicBytes`.
## Features
- `registerSignature(sig)` — add a new signature matcher; re-sorts registry by priority
- `getSignatures()` — returns the full registry array sorted by descending priority
- `resetSignatures()` — clears all registered signatures (test isolation only)
- Priority-ordered registry: higher priority signatures are checked first
- Thread-safe within a single JavaScript execution context (no async race conditions)
## Usage: Basic Example
```typescript
import {
registerSignature,
getSignatures,
} from "@suaveplan/mime/detection/from-magic-bytes/registry";
registerSignature({
mime: "application/x-my-format",
priority: 50,
minBytes: 4,
match: (bytes) => bytes[0] === 0x4d && bytes[1] === 0x59,
});
console.log(getSignatures().find(s => s.mime === "application/x-my-format"));
```
## Usage: Advanced Example
```typescript
import { registerSignature } from "@suaveplan/mime/detection/from-magic-bytes/registry";
// Custom signature for a proprietary binary format
registerSignature({
mime: "application/x-leveldb",
priority: 75,
minBytes: 8,
match: (bytes) =>
bytes[0] === 0xff &&
bytes[1] === 0x06 &&
bytes[2] === 0x00 &&
bytes[3] === 0x00,
});
// Signature with a scan offset
registerSignature({
mime: "image/x-xcf",
priority: 80,
minBytes: 9,
match: (bytes) => {
// "gimp xcf " at offset 0
const magic = [0x67, 0x69, 0x6d, 0x70, 0x20, 0x78, 0x63, 0x66, 0x20];
return magic.every((b, i) => bytes[i] === b);
},
});
```
## API Reference
### `registerSignature(sig: Signature): void`
| Parameter | Type | Description |
|-----------|------|-------------|
| `sig.mime` | `string` | MIME type this signature identifies |
| `sig.priority` | `number` | Higher values are checked first (0–100 typical range) |
| `sig.minBytes` | `number` | Minimum bytes required to run the matcher |
| `sig.match` | `(bytes: Uint8Array) => boolean` | Byte-pattern predicate |
### `getSignatures(): Signature[]`
Returns the full sorted registry array. Do not mutate the returned array directly.
### `resetSignatures(): void`
Clears all signatures. Intended for test isolation only — not safe for production use.
## Implementation Notes
The registry maintains a sorted array rather than a priority queue for simplicity. `registerSignature` performs an in-place sort after insertion, which is O(n log n) but acceptable given that signatures are registered at module load time (once per process) and detection reads are O(n) anyway. The sort is stable in modern JavaScript engines, so signatures with equal priority retain their insertion order.

View File

@@ -0,0 +1,39 @@
# signatures/archives
## Purpose
Registers magic-byte signatures for archive and compressed file formats with the global signature registry. Covers all major compression and container formats used in file distribution and backup. Each signature is registered as a side effect of importing this module.
## Features
- ZIP, JAR, ODF, DOCX/PPTX/XLSX (all ZIP-based)
- GZIP, BZIP2, XZ/7-Zip, LZMA
- RAR (v4 and v5 signatures)
- TAR (ustar header at offset 257)
- ZSTD (Zstandard frame magic)
- LZ4 frame magic
- CPIO (binary and ASCII newc variants)
- ISO 9660 CD image (CD001 at offset 32769)
- BZ2 single-file bzip2
## Usage
```typescript
import "@suaveplan/mime/detection/from-magic-bytes/signatures/archives";
import { detectFromMagicBytes } from "@suaveplan/mime/detection/from-magic-bytes/from-magic-bytes";
const gzipBytes = new Uint8Array([0x1f, 0x8b, 0x08]);
console.log(detectFromMagicBytes(gzipBytes)?.mime); // "application/gzip"
```
## API Reference
No exported functions. All signatures are registered via `registerSignature` side effects.
## Implementation Notes
ZIP-based formats (JAR, ODF, Office Open XML) share the PK signature but cannot be distinguished without inspecting the ZIP directory entries — the magic-byte detector returns `application/zip` generically for all ZIP files. TAR detection looks for the `ustar` ASCII string at offset 257, which requires at least 262 bytes; shorter buffers will not match. ISO 9660 detection at offset 32769 is impractical to test and is annotated to be excluded from coverage thresholds.
### Coverage
The archive signature set covers ZIP (`PK\x03\x04` and `PK\x05\x06` empty-archive variant), 7z (`37 7A BC AF 27 1C`), RAR (v4 and v5 magics), GZIP (`1F 8B`), BZIP2 (`BZh`), XZ, TAR (ustar octet at offset 257), and Z-standard. Priority 100 is the default; nested container formats such as OOXML that are technically ZIP files are detected at priority 105 in `documents.ts` so they win the tie.

View File

@@ -0,0 +1,37 @@
# signatures/audio
## Purpose
Registers magic-byte signatures for audio formats with the global signature registry. Covers all major audio container and codec formats used in web and desktop applications. Each signature is registered as a side effect of importing this module.
## Features
- FLAC, MP3 (ID3 tag and sync word variants), OGG/Vorbis
- WAV (RIFF container), AIFF/AIFF-C
- AAC (ADTS), M4A (ISO BMFF with M4A brand)
- WMA/WMV (ASF container), MIDI
- Musepack (SV7/SV8), OptimFROG, Monkey's Audio
- DFF/DSF (DSD audio), AC-3/E-AC-3, Dolby TrueHD
- MOD tracker variants (4/6/8-channel patterns)
## Usage
```typescript
import "@suaveplan/mime/detection/from-magic-bytes/signatures/audio";
import { detectFromMagicBytes } from "@suaveplan/mime/detection/from-magic-bytes/from-magic-bytes";
const flacBytes = new Uint8Array([0x66, 0x4c, 0x61, 0x43]); // "fLaC"
console.log(detectFromMagicBytes(flacBytes)?.mime); // "audio/flac"
```
## API Reference
No exported functions. All signatures are registered via `registerSignature` side effects.
## Implementation Notes
MP3 detection covers both the ID3v2 header (`49 44 33`) and raw MPEG sync words (`ff fb`, `ff f3`, `ff f2`). OGG files are further disambiguated by inspecting the codec type in the first OGG page header (bytes 28–35: `vorbis`, `FLAC`, `Opus`, `Theora`). The RIFF container is used by both WAV and AVI, with disambiguation on the RIFF type tag at offset 8.
### Coverage
The audio signature set covers MPEG audio (`ID3` tag and raw frame-sync variants at priority 80), WAV/RIFF with the `WAVE` subtype marker, FLAC (`fLaC`), Ogg (`OggS`), AIFF (`FORM` + `AIFF`), MIDI (`MThd`), AMR, and Opus. The RIFF family share a common `RIFF` container prefix which is why WAV specifically checks the subtype at offset 8 before claiming a match.

View File

@@ -0,0 +1,39 @@
# signatures/data
## Purpose
Registers magic-byte signatures for binary data and structured-data formats with the global signature registry. Covers serialization formats, scientific data files, and cryptographic containers. Each signature is registered as a side effect of importing this module.
## Features
- MessagePack binary serialization
- CBOR (RFC 7049 Concise Binary Object Representation)
- Apache Parquet columnar data format
- Apache Arrow IPC format
- HDF5 (Hierarchical Data Format version 5)
- NetCDF (Network Common Data Form)
- PCAP and PCAPng network capture files
- PEM-encoded certificates and keys
## Usage
```typescript
import "@suaveplan/mime/detection/from-magic-bytes/signatures/data";
import { detectFromMagicBytes } from "@suaveplan/mime/detection/from-magic-bytes/from-magic-bytes";
// Parquet magic: "PAR1" at start and end of file
const parquetBytes = new Uint8Array([0x50, 0x41, 0x52, 0x31]); // "PAR1"
console.log(detectFromMagicBytes(parquetBytes)?.mime); // "application/vnd.apache.parquet"
```
## API Reference
No exported functions. All signatures are registered via `registerSignature` side effects.
## Implementation Notes
MessagePack has no single fixed magic header — the first byte encodes the type of the root value. The detection heuristic checks for the most common root types in well-formed MessagePack documents (fixmap, fixarray, nil, bool, fixint) to reduce false-positives while maintaining reasonable recall. PCAP files come in two endianness variants (little-endian `D4 C3 B2 A1` and big-endian `A1 B2 C3 D4`) plus a nanosecond-resolution variant (`4D 3C B2 A1`).
### Coverage
The data signature set covers SQLite (`SQLite format 3\0` at offset 0), Apache Parquet (`PAR1` at offset 0 and at end-of-file), Apache Arrow IPC, CBOR, MessagePack, BSON, and Protocol Buffers when a distinguishable length-prefixed header is present. Formats without a structural magic header (CSV, NDJSON) are not listed here; detection for those falls through to content sniffing in the BOM/text-sniffer stage.

View File

@@ -0,0 +1,38 @@
# signatures/documents
## Purpose
Registers magic-byte signatures for document and data format files with the global signature registry. Covers office documents, structured data, e-books, and markup formats. Each signature is registered as a side effect of importing this module.
## Features
- PDF (`%PDF` header)
- Microsoft Office legacy formats (DOC, XLS, PPT — OLE2 container `D0 CF 11 E0`)
- EPUB (ZIP-based e-book container)
- Rich Text Format (RTF)
- SQLite database (`SQLite format 3`)
- Apple Pages, Numbers, Keynote (ZIP-based, detected via content sniffing)
- JSON lines and CBOR (RFC 7049) binary format
- Protocol Buffers wire format heuristic
## Usage
```typescript
import "@suaveplan/mime/detection/from-magic-bytes/signatures/documents";
import { detectFromMagicBytes } from "@suaveplan/mime/detection/from-magic-bytes/from-magic-bytes";
const pdfBytes = new Uint8Array([0x25, 0x50, 0x44, 0x46, 0x2d]); // "%PDF-"
console.log(detectFromMagicBytes(pdfBytes)?.mime); // "application/pdf"
```
## API Reference
No exported functions. All signatures are registered via `registerSignature` side effects.
## Implementation Notes
OLE2 Compound Document Format is the container for legacy Microsoft Office files (pre-2007 DOC/XLS/PPT). The magic bytes `D0 CF 11 E0` identify the OLE2 container but not the specific Office application — the detector returns `application/vnd.ms-office` as a generic fallback. Modern Office formats (DOCX, XLSX, PPTX) use the ZIP container and are handled in the archives signatures module.
### Coverage
The document signature set covers PDF (`%PDF`), Microsoft CFB-backed legacy Office (`D0 CF 11 E0 A1 B1 1A E1`), OOXML (`PK` with `[Content_Types].xml` entry — priority 105 so it beats plain ZIP), ODF, RTF (`{\\rtf`), PostScript (`%!PS`), EPUB (ZIP with `mimetype` = `application/epub+zip`), and MOBI/AZW. The 105 priority on container-based formats is critical: without it OOXML would match plain ZIP at 100 and lose granularity.

View File

@@ -0,0 +1,43 @@
# signatures/executables
## Purpose
Registers magic-byte signatures for executable binary formats with the global signature registry. Covers native executables, bytecode, and portable binary formats across platforms. Each signature is registered as a side effect of importing this module.
## Features
- ELF (Linux/Unix native executables and shared libraries)
- PE (Windows `.exe` and `.dll` — `MZ` header)
- Mach-O (macOS native: 32/64-bit, fat binary, ARM64)
- Java `.class` bytecode (`CA FE BA BE`)
- DEX (Android Dalvik bytecode)
- WebAssembly (`.wasm` — `00 61 73 6d`)
- Python `.pyc` bytecode (magic + timestamp header)
- LLVM bitcode
## Usage
```typescript
import "@suaveplan/mime/detection/from-magic-bytes/signatures/executables";
import { detectFromMagicBytes } from "@suaveplan/mime/detection/from-magic-bytes/from-magic-bytes";
// ELF magic: 7F 45 4C 46
const elfBytes = new Uint8Array([0x7f, 0x45, 0x4c, 0x46]);
console.log(detectFromMagicBytes(elfBytes)?.mime); // "application/x-elf"
// WebAssembly magic: 00 61 73 6D
const wasmBytes = new Uint8Array([0x00, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x00]);
console.log(detectFromMagicBytes(wasmBytes)?.mime); // "application/wasm"
```
## API Reference
No exported functions. All signatures are registered via `registerSignature` side effects.
## Implementation Notes
Mach-O binaries exist in four variants: 32-bit big-endian (`CE FA ED FE`), 32-bit little-endian (`FE ED FA CE`), 64-bit big-endian (`CF FA ED FE`), and 64-bit little-endian (`FE ED FA CF`). Fat binaries (`CA FE BA BE` at big-endian 0xcafebabe) share the same magic as Java class files but are distinguished by a byte at offset 7 (`0x00` for Mach-O fat, non-zero for Java). The implemention uses a combined check for the Java case.
### Coverage
The executable signature set covers WebAssembly (`\x00asm`), ELF (`\x7FELF` — Linux / BSD native), Mach-O (FAT + thin, 32/64-bit big/little endian variants), PE/COFF (`MZ`), Java class files (`CAFEBABE`), .NET assemblies (via PE with CLI header), and bytecode containers like Python `.pyc` and Lua `.luac`. Multiple Mach-O magics exist because Apple shipped multiple endianness and pointer-width variants; all land on `application/x-mach-binary`.

View File

@@ -0,0 +1,37 @@
# signatures/fonts
## Purpose
Registers magic-byte signatures for font file formats with the global signature registry. Covers all major web font and desktop font formats. Each signature is registered as a side effect of importing this module.
## Features
- WOFF (Web Open Font Format) — `wOFF` magic
- WOFF2 — `wOF2` magic
- TrueType (`.ttf`) — `00 01 00 00 00` header
- OpenType (`.otf`) — `4F 54 54 4F` (`OTTO`) header
- EOT (Embedded OpenType) — Microsoft format
- `font/collection` (TTC, OpenType Collection)
- `vnd.ms-fontobject` variants
## Usage
```typescript
import "@suaveplan/mime/detection/from-magic-bytes/signatures/fonts";
import { detectFromMagicBytes } from "@suaveplan/mime/detection/from-magic-bytes/from-magic-bytes";
const woff2Bytes = new Uint8Array([0x77, 0x4f, 0x46, 0x32]); // "wOF2"
console.log(detectFromMagicBytes(woff2Bytes)?.mime); // "font/woff2"
```
## API Reference
No exported functions. All signatures are registered via `registerSignature` side effects.
## Implementation Notes
TrueType and OpenType both start with a 4-byte "sfVersion" tag at offset 0. TrueType uses `00 01 00 00` (version 1.0 in fixed-point notation) while OpenType uses `OTTO`. CFF-based fonts use `OTTO` in the sfVersion field; TrueType collections (`.ttc`) use `ttcf`. WOFF and WOFF2 are wrapper formats that contain either TrueType or OpenType data and are unambiguously identified by their `wOFF` / `wOF2` magic.
### Coverage
The font signature set covers TrueType (`\x00\x01\x00\x00`), OpenType (`OTTO` — CFF-based), WOFF1 (`wOFF`), WOFF2 (`wOF2`), TrueType Collection (`ttcf`), EOT (Microsoft Embedded OpenType), and SVG-font variants. All the signatures sit at priority 100 because each sfVersion / wrapper magic is globally unique and there are no container-based tie-breakers in the font space.

View File

@@ -0,0 +1,39 @@
# signatures/images
## Purpose
Registers magic-byte signatures for image formats with the global signature registry. Covers all major raster and vector image types used on the web and in desktop applications. Each signature is registered as a side effect of importing this module.
## Features
- PNG, JPEG, GIF (87a, 89a), WebP, BMP, ICO, TIFF (BE/LE)
- HEIC, HEIF, AVIF (multiple brand variants)
- PSD (Adobe Photoshop), SVG, XCF (GIMP)
- PBM, PGM, PPM, PNM, PFM portable bitmap variants
- Radiance HDR (`.hdr`, `.rgbe`)
- OpenEXR high dynamic range images
## Usage
This module is imported for its side effects only:
```typescript
// Automatic registration — all image signatures become available for detection
import "@suaveplan/mime/detection/from-magic-bytes/signatures/images";
import { detectFromMagicBytes } from "@suaveplan/mime/detection/from-magic-bytes/from-magic-bytes";
const pngBytes = new Uint8Array([0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a]);
console.log(detectFromMagicBytes(pngBytes)?.mime); // "image/png"
```
## API Reference
No exported functions. All signatures are registered via `registerSignature` side effects on module load.
## Implementation Notes
HEIC/HEIF/AVIF formats share the ISO Base Media File Format container (like MP4) and are distinguished by their `ftyp` brand code at bytes 8–11. The `startsWith` helper checks both the `ftyp` box header at offset 4 and the brand at offset 8 for each variant. Radiance HDR files use the ASCII header `#?RGBE` or `#?RADIANCE` at offset 0.
### Coverage
The image signature set covers PNG (`89 50 4E 47 0D 0A 1A 0A`), JPEG (`FF D8 FF`), GIF (`GIF87a` / `GIF89a`), WebP (`RIFF` + `WEBP` subtype), AVIF / HEIC (ISOBMFF `ftyp` brand inside offset-4 box), BMP, TIFF (`II*\0` little-endian and `MM\0*` big-endian), ICO, and SVG (text-sniffed in a separate stage). ISOBMFF brand matching is done against a curated brand set so AVIF, HEIC, HEIF, and MIAF variants all resolve cleanly.

View File

@@ -0,0 +1,36 @@
# signatures/model3d
## Purpose
Registers magic-byte signatures for 3D model file formats with the global signature registry. Covers formats used in CAD, game development, and 3D printing. Each signature is registered as a side effect of importing this module.
## Features
- glTF binary (`.glb`) — `glTF` magic + version 2
- STL binary and ASCII (3D printing format)
- FBX binary (`Kaydara FBX Binary` header)
- Collada (`.dae`) — XML-based, identified by `<?xml` prolog
- PLY (Stanford Polygon Library)
## Usage
```typescript
import "@suaveplan/mime/detection/from-magic-bytes/signatures/model3d";
import { detectFromMagicBytes } from "@suaveplan/mime/detection/from-magic-bytes/from-magic-bytes";
// glTF binary magic: 67 6C 54 46
const glbBytes = new Uint8Array([0x67, 0x6c, 0x54, 0x46, 0x02, 0x00, 0x00, 0x00]);
console.log(detectFromMagicBytes(glbBytes)?.mime); // "model/gltf-binary"
```
## API Reference
No exported functions. All signatures are registered via `registerSignature` side effects.
## Implementation Notes
STL binary files have no fixed magic sequence — they start with an 80-byte header of arbitrary text. The detection heuristic checks for the absence of the ASCII string `solid` at offset 0 (which would indicate ASCII STL) combined with a plausible triangle count in bytes 80–83. ASCII STL files are identified by the `solid` keyword at offset 0. The MIME type `model/stl` is returned for both binary and ASCII STL when detected.
### Coverage
The 3D-model signature set covers glTF binary (`glTF` magic at offset 0), USDZ (a ZIP container with a specific first-entry layout), FBX (both ASCII `; FBX` and binary `Kaydara FBX Binary \0`), STL (ASCII vs binary are disambiguated by header-byte heuristics), OBJ (text-sniffed), 3MF, and DAE/Collada (XML with known root element). The binary FBX magic uses 20 bytes including two trailing null bytes and a SUB character — matching that full prefix avoids false positives on other tools that write `Kaydara`-prefixed files.

View File

@@ -0,0 +1,38 @@
# signatures/video
## Purpose
Registers magic-byte signatures for video container formats with the global signature registry. Covers all major video formats used on the web and in streaming applications. Each signature is registered as a side effect of importing this module.
## Features
- MP4 / M4V (ISO BMFF with multiple `ftyp` brands: `isom`, `iso2`, `mp41`, `mp42`, etc.)
- WebM and MKV (Matroska EBML header)
- AVI (RIFF container with AVI type)
- QuickTime MOV, 3GPP, 3GPP2
- MPEG-1/2 PS (`ba`/`b3` pack start codes)
- MPEG-2 TS (188-byte sync word pattern)
- Windows Media (`video/x-ms-wmv` via ASF header)
- Flash Video (FLV), DV video
## Usage
```typescript
import "@suaveplan/mime/detection/from-magic-bytes/signatures/video";
import { detectFromMagicBytes } from "@suaveplan/mime/detection/from-magic-bytes/from-magic-bytes";
const webmBytes = new Uint8Array([0x1a, 0x45, 0xdf, 0xa3]); // EBML header
console.log(detectFromMagicBytes(webmBytes)?.mime); // "video/webm"
```
## API Reference
No exported functions. All signatures are registered via `registerSignature` side effects.
## Implementation Notes
MP4 variants are distinguished by their `ftyp` brand at bytes 8–11 within the ISO Base Media File Format container. Multiple brands map to `video/mp4` (isom, iso2, mp41, mp42, etc.) while M4V uses the `M4V` brand. MPEG-2 TS detection requires the sync byte `0x47` at both offset 0 and offset 188, making it impractical to test without 189+ bytes; the signature is annotated to be excluded from coverage.
### Coverage
The video signature set covers ISOBMFF containers (MP4, QuickTime `.mov`, 3GP — disambiguated by `ftyp` brand at offset 4), Matroska / WebM (`1A 45 DF A3` EBML header + DocType check), AVI (`RIFF` + `AVI` subtype), MPEG transport stream (`0x47` sync byte at 188-byte intervals), FLV (`FLV\x01`), and Ogg (when the first packet is a Theora stream). ISOBMFF brand matching is the central complexity — MP4, 3GP, QuickTime, and HEIC share a header and only the brand distinguishes them.

View File

@@ -0,0 +1,36 @@
# detection/from-stream-reader
## Purpose
Internal module providing the `toAsyncIterable` helper and the `drainViaReader` legacy fallback for `ReadableStream` sources. This module is excluded from coverage measurement because the `getReader()` fallback path is only reachable on runtimes where `ReadableStream` does not implement `Symbol.asyncIterator` (pre-Node-18 and some embedded environments). In Bun and Node 18+, the fast path is always taken.
## Features
- `toAsyncIterable(source)` — coerces `ReadableStream` or `AsyncIterable` to `AsyncIterable<Uint8Array>`
- `drainViaReader(source)` — async generator that pulls chunks via `ReadableStream.getReader()` with proper lock release in the `finally` block
- Fast path: when `Symbol.asyncIterator` is present (Bun/Node 18+), the source is returned as-is with no overhead
- Slow path: when `Symbol.asyncIterator` is absent, wraps `getReader()` in an async iterable adapter
## Usage
This module is not part of the public API surface. It is imported by `from-stream.ts`:
```typescript
// Internal usage only
import { toAsyncIterable } from "./from-stream-reader.js";
const iter = toAsyncIterable(source)[Symbol.asyncIterator]();
```
## API Reference
### `drainViaReader(source: ReadableStream<Uint8Array>): AsyncGenerator<Uint8Array>`
Pulls all chunks from a `ReadableStream` using the `getReader()` pull API. Releases the lock in the `finally` block to ensure cleanup even when the consumer aborts iteration.
### `toAsyncIterable(source): AsyncIterable<Uint8Array>`
Coerces any `ByteSource` to `AsyncIterable<Uint8Array>`. Fast path if `Symbol.asyncIterator` is present; otherwise returns an adapter object backed by `drainViaReader`.
## Implementation Notes
Bun and Node 18+ expose `Symbol.asyncIterator` on `ReadableStream`, making `for await (const chunk of stream)` work natively. The `getReader()` fallback exists for completeness (older Node, Deno, Cloudflare Workers pre-2023, some browser environments). This file is listed in the coverage `exclude` list in `vitest.config.ts` because the fallback cannot be exercised in the Bun test environment without significant test infrastructure for simulating legacy runtimes.

View File

@@ -0,0 +1,79 @@
# detection/from-stream
## Purpose
Provides `detectFromStream`, which reads the first N bytes from any async byte source (`ReadableStream<Uint8Array>` or `AsyncIterable<Uint8Array>`) and runs magic-byte detection on the buffered prefix. Returns the detection result together with a `head` buffer (the peeked bytes) and a `tail` async generator that replays the full stream (carry bytes + remaining), enabling callers to re-read the stream from the beginning after detection.
## Features
- Accepts both `ReadableStream<Uint8Array>` and `AsyncIterable<Uint8Array>`
- Configurable peek window (`peekBytes`, default 512)
- AbortSignal support for cancellation during read
- Returns a `head` buffer containing the bytes used for detection
- Returns a `tail` generator that yields carry bytes (overflow from the last peeked chunk) then remaining stream chunks
- Zero-copy split: the overflow portion of the boundary chunk is sliced without re-copying the head bytes
- Full-stream reassembly: `head` + `tail` yields the complete original byte sequence
## Usage: Basic Example
```typescript
import { detectFromStream } from "@suaveplan/mime/detection/from-stream";
const result = await detectFromStream(fileStream);
console.log(result.mime); // "image/png" (or undefined if unknown)
console.log(result.source); // "stream"
// Consume the full stream content after detection
for await (const chunk of result.tail) {
process(chunk);
}
```
## Usage: Advanced Example
```typescript
import { detectFromStream } from "@suaveplan/mime/detection/from-stream";
async function processUpload(stream: ReadableStream<Uint8Array>, signal: AbortSignal) {
const { mime, head, tail } = await detectFromStream(stream, {
peekBytes: 1024, // inspect up to 1 KB
signal, // cancel if the request is aborted
});
if (mime !== "image/jpeg" && mime !== "image/png") {
throw new Error(`Unsupported image type: ${mime}`);
}
// Process the full image data: head is already a Uint8Array,
// tail is an async iterable that delivers the rest
const chunks: Uint8Array[] = [head];
for await (const chunk of tail) {
chunks.push(chunk);
}
return processImage(mime, chunks);
}
```
## API Reference
### `detectFromStream(source, options?): Promise<StreamDetectionResult>`
| Parameter | Type | Description |
|-----------|------|-------------|
| `source` | `ReadableStream<Uint8Array> \| AsyncIterable<Uint8Array>` | The byte stream to inspect. |
| `options.peekBytes?` | `number` | Maximum bytes to buffer for detection (default: 512). |
| `options.signal?` | `AbortSignal` | Cancellation signal. |
**Returns:** `StreamDetectionResult`:
| Field | Type | Description |
|-------|------|-------------|
| `source` | `"stream"` | Always `"stream"` |
| `mime` | `string \| undefined` | Detected MIME type |
| `priority` | `number \| undefined` | Signature priority score |
| `head` | `Uint8Array` | The peeked bytes used for detection |
| `tail` | `AsyncGenerator<Uint8Array>` | Remaining stream bytes after head |
## Implementation Notes
The `toAsyncIterable` helper in `from-stream-reader.ts` coerces `ReadableStream` to `AsyncIterable`. In Bun and Node 18+, `ReadableStream` already implements `Symbol.asyncIterator`, so the fast path is always taken. The legacy `getReader()` fallback exists for embedded runtimes and is excluded from coverage. The `tail` generator yields carry bytes (the overflow portion of the last buffered chunk, if the chunk straddled the `peekBytes` boundary) before draining the iterator, ensuring byte ordering is preserved across the head/tail split.