first commit
This commit is contained in:
290
docs/foundation/data/mime/README.md
Normal file
290
docs/foundation/data/mime/README.md
Normal file
@@ -0,0 +1,290 @@
|
||||
# @suaveplan/mime - Structured MIME-type detection, parsing, and registry: magic-byte sniffing, RFC 7231 Content-Type parsing, IANA-sourced type database.
|
||||
|
||||
## Summary
|
||||
|
||||
Structured MIME-type detection, parsing, and registry for the Suaveplan platform. Provides RFC 7231 Content-Type parsing, magic-byte file signature detection, charset sniffing, and a 150+ entry IANA-sourced type database — all in a zero-dependency, tree-shakeable package.
|
||||
|
||||
<!-- begin:auto-docs -->
|
||||
## Documentation tree
|
||||
|
||||
- [sniff-bom](./src/charset/sniff-bom.md) — Detects character encoding from a Byte Order Mark (BOM) at the start of a binary buffer. BOM detection is the highest-c…
|
||||
- [sniff-html-meta](./src/charset/sniff-html-meta.md) — Extracts a declared character encoding from an HTML `<meta>` tag by scanning the first 1,024 characters of an HTML docu…
|
||||
- [sniff-xml-decl](./src/charset/sniff-xml-decl.md) — Extracts the declared encoding from an XML or SVG document's processing instruction (`<?xml version="1.0" encoding="UTF…
|
||||
- [content-type-equals](./src/content-type/content-type-equals.md) — Provides two utility functions for comparing Content-Type values without caring about parameters: `contentTypeEquals` c…
|
||||
- [format-content-type](./src/content-type/format-content-type.md) — Serializes a `ParsedContentType` object back into a standards-compliant `Content-Type` header string. Handles quoting o…
|
||||
- [parse-content-type](./src/content-type/parse-content-type.md) — Parses an RFC 7231 / RFC 9110 `Content-Type` header value into a structured `ParsedContentType` object. Handles quoted…
|
||||
- [builtin-db](./src/db/builtin-db.md) — The built-in MIME type database for `@suaveplan/mime`. Contains over 150 `MimeEntry` records covering the most common w…
|
||||
- [registry](./src/db/registry.md) — The central in-memory MIME type database for `@suaveplan/mime`. Maintains two indexes over the built-in `MimeEntry` lis…
|
||||
- [from-magic-bytes](./src/detection/from-magic-bytes/from-magic-bytes.md) — <!-- begin:auto-docs --> _(no submodules)_ <!-- end:auto-docs --> - helpers — Low-level byte-inspection utilities used……
|
||||
- [archives](./src/detection/from-magic-bytes/signatures/archives.md) — Registers magic-byte signatures for archive and compressed file formats with the global signature registry. Covers all…
|
||||
- [audio](./src/detection/from-magic-bytes/signatures/audio.md) — Registers magic-byte signatures for audio formats with the global signature registry. Covers all major audio container…
|
||||
- [data](./src/detection/from-magic-bytes/signatures/data.md) — Registers magic-byte signatures for binary data and structured-data formats with the global signature registry. Covers…
|
||||
- [documents](./src/detection/from-magic-bytes/signatures/documents.md) — Registers magic-byte signatures for document and data format files with the global signature registry. Covers office do…
|
||||
- [executables](./src/detection/from-magic-bytes/signatures/executables.md) — Registers magic-byte signatures for executable binary formats with the global signature registry. Covers native executa…
|
||||
- [fonts](./src/detection/from-magic-bytes/signatures/fonts.md) — Registers magic-byte signatures for font file formats with the global signature registry. Covers all major web font and…
|
||||
- [images](./src/detection/from-magic-bytes/signatures/images.md) — Registers magic-byte signatures for image formats with the global signature registry. Covers all major raster and vecto…
|
||||
- [model3d](./src/detection/from-magic-bytes/signatures/model3d.md) — Registers magic-byte signatures for 3D model file formats with the global signature registry. Covers formats used in CA…
|
||||
- [video](./src/detection/from-magic-bytes/signatures/video.md) — Registers magic-byte signatures for video container formats with the global signature registry. Covers all major video…
|
||||
- [from-magic-bytes](./src/detection/from-magic-bytes/from-magic-bytes.md) — <!-- begin:auto-docs --> _(no submodules)_ <!-- end:auto-docs --> - helpers — Low-level byte-inspection utilities used……
|
||||
- [helpers](./src/detection/from-magic-bytes/helpers.md) — Low-level byte-inspection utilities used by the magic-byte signature matchers. Provides helpers for common patterns: pr…
|
||||
- [registry](./src/detection/from-magic-bytes/registry.md) — The mutable signature registry for magic-byte detection. Stores all registered byte-pattern matchers and provides CRUD-…
|
||||
- [detect](./src/detection/detect.md) — The unified MIME detection entry point for `@suaveplan/mime`. Merges evidence from three independent sources — magic by…
|
||||
- [from-content-type](./src/detection/from-content-type.md) — Detects the MIME type from a raw `Content-Type` header string. Parses the header using the full RFC 7231 parser and wra…
|
||||
- [from-extension](./src/detection/from-extension.md) — Detects the MIME type from a file extension or full filename. Strips path components and leading dots, normalizes to lo…
|
||||
- [from-stream-reader](./src/detection/from-stream-reader.md) — Internal module providing the `toAsyncIterable` helper and the `drainViaReader` legacy fallback for `ReadableStream` so…
|
||||
- [from-stream](./src/detection/from-stream.md) — Provides `detectFromStream`, which reads the first N bytes from any async byte source (`ReadableStream<Uint8Array>` or…
|
||||
- [error-codes](./src/error-codes/error-codes.md) — Defines the stable numeric error-code registry for every error class in `@suaveplan/mime`. Each code is a non-overlappi…
|
||||
- [error-codes](./src/error-codes/error-codes.md) — Defines the stable numeric error-code registry for every error class in `@suaveplan/mime`. Each code is a non-overlappi…
|
||||
- [insufficient-bytes-error](./src/errors/insufficient-bytes-error.md) — Thrown when a magic-byte detection operation receives fewer bytes than the minimum required by the signature. Carries b…
|
||||
- [invalid-content-type-error](./src/errors/invalid-content-type-error.md) — Thrown when a `Content-Type` header or media type string fails RFC 7231 validation. Carries the raw input string and op…
|
||||
- [mime-error](./src/errors/mime-error.md) — The base error class for all `@suaveplan/mime` errors. Extends `SuaveplanError` from `@suaveplan/error` with the `MIME_…
|
||||
- [parse-error](./src/errors/parse-error.md) — Thrown when a general parsing operation fails — for example, XML declaration parsing or BOM detection failures. Unlike…
|
||||
- [unknown-mime-type-error](./src/errors/unknown-mime-type-error.md) — Thrown when a MIME type string is not found in the built-in database and no fallback is available. Carries the unrecogn…
|
||||
- [unknown-signature-error](./src/errors/unknown-signature-error.md) — Thrown when a byte signature identifier is referenced but is not present in the signature registry. Used as a sentinel…
|
||||
- [legacy](./src/legacy/get-mime-type.md) — <!-- begin:auto-docs --> _(no submodules)_ <!-- end:auto-docs -->
|
||||
- [get-mime-type](./src/legacy/get-mime-type.md) — <!-- begin:auto-docs --> _(no submodules)_ <!-- end:auto-docs -->
|
||||
- [reverse](./src/reverse/reverse.md) — <!-- begin:auto-docs --> _(no submodules)_ <!-- end:auto-docs -->
|
||||
- [reverse](./src/reverse/reverse.md) — <!-- begin:auto-docs --> _(no submodules)_ <!-- end:auto-docs -->
|
||||
- [telemetry](./src/telemetry/meters.md) — <!-- begin:auto-docs --> _(no submodules)_ <!-- end:auto-docs -->
|
||||
- [meters](./src/telemetry/meters.md) — <!-- begin:auto-docs --> _(no submodules)_ <!-- end:auto-docs -->
|
||||
<!-- end:auto-docs --> - helpers — Low-level byte-inspection utilities used……
|
||||
- [archives](./src/detection/from-magic-bytes/signatures/archives.md) — Registers magic-byte signatures for archive and compressed file formats with the global signature registry. Covers all…
|
||||
- [audio](./src/detection/from-magic-bytes/signatures/audio.md) — Registers magic-byte signatures for audio formats with the global signature registry. Covers all major audio container…
|
||||
- [data](./src/detection/from-magic-bytes/signatures/data.md) — Registers magic-byte signatures for binary data and structured-data formats with the global signature registry. Covers…
|
||||
- [documents](./src/detection/from-magic-bytes/signatures/documents.md) — Registers magic-byte signatures for document and data format files with the global signature registry. Covers office do…
|
||||
- [executables](./src/detection/from-magic-bytes/signatures/executables.md) — Registers magic-byte signatures for executable binary formats with the global signature registry. Covers native executa…
|
||||
- [fonts](./src/detection/from-magic-bytes/signatures/fonts.md) — Registers magic-byte signatures for font file formats with the global signature registry. Covers all major web font and…
|
||||
- [images](./src/detection/from-magic-bytes/signatures/images.md) — Registers magic-byte signatures for image formats with the global signature registry. Covers all major raster and vecto…
|
||||
- [model3d](./src/detection/from-magic-bytes/signatures/model3d.md) — Registers magic-byte signatures for 3D model file formats with the global signature registry. Covers formats used in CA…
|
||||
- [video](./src/detection/from-magic-bytes/signatures/video.md) — Registers magic-byte signatures for video container formats with the global signature registry. Covers all major video…
|
||||
- [from-magic-bytes](./src/detection/from-magic-bytes/from-magic-bytes.md) — <!-- begin:auto-docs --> _(no submodules)_- helpers — Low-level byte-inspection utilities used……
|
||||
- [helpers](./src/detection/from-magic-bytes/helpers.md) — Low-level byte-inspection utilities used by the magic-byte signature matchers. Provides helpers for common patterns: pr…
|
||||
- [registry](./src/detection/from-magic-bytes/registry.md) — The mutable signature registry for magic-byte detection. Stores all registered byte-pattern matchers and provides CRUD-…
|
||||
- [detect](./src/detection/detect.md) — The unified MIME detection entry point for `@suaveplan/mime`. Merges evidence from three independent sources — magic by…
|
||||
- [from-content-type](./src/detection/from-content-type.md) — Detects the MIME type from a raw `Content-Type` header string. Parses the header using the full RFC 7231 parser and wra…
|
||||
- [from-extension](./src/detection/from-extension.md) — Detects the MIME type from a file extension or full filename. Strips path components and leading dots, normalizes to lo…
|
||||
- [from-stream-reader](./src/detection/from-stream-reader.md) — Internal module providing the `toAsyncIterable` helper and the `drainViaReader` legacy fallback for `ReadableStream` so…
|
||||
- [from-stream](./src/detection/from-stream.md) — Provides `detectFromStream`, which reads the first N bytes from any async byte source (`ReadableStream<Uint8Array>` or…
|
||||
- [insufficient-bytes-error](./src/errors/insufficient-bytes-error.md) — Thrown when a magic-byte detection operation receives fewer bytes than the minimum required by the signature. Carries b…
|
||||
- [invalid-content-type-error](./src/errors/invalid-content-type-error.md) — Thrown when a `Content-Type` header or media type string fails RFC 7231 validation. Carries the raw input string and op…
|
||||
- [mime-error](./src/errors/mime-error.md) — The base error class for all `@suaveplan/mime` errors. Extends `SuaveplanError` from `@suaveplan/error` with the `MIME_…
|
||||
- [parse-error](./src/errors/parse-error.md) — Thrown when a general parsing operation fails — for example, XML declaration parsing or BOM detection failures. Unlike…
|
||||
- [unknown-mime-type-error](./src/errors/unknown-mime-type-error.md) — Thrown when a MIME type string is not found in the built-in database and no fallback is available. Carries the unrecogn…
|
||||
- [unknown-signature-error](./src/errors/unknown-signature-error.md) — Thrown when a byte signature identifier is referenced but is not present in the signature registry. Used as a sentinel…
|
||||
- [legacy](./src/legacy/get-mime-type.md) — <!-- begin:auto-docs --> _(no submodules)_- [get-mime-type](./src/legacy/get-mime-type.md) — <!-- begin:auto-docs --> _(no submodules)_- [reverse](./src/reverse/reverse.md) — <!-- begin:auto-docs --> _(no submodules)_- [reverse](./src/reverse/reverse.md) — <!-- begin:auto-docs --> _(no submodules)_- [telemetry](./src/telemetry/meters.md) — <!-- begin:auto-docs --> _(no submodules)_- [meters](./src/telemetry/meters.md) — <!-- begin:auto-docs --> _(no submodules)_- [error-codes](./src/error-codes.md) — Defines the stable numeric error-code registry for every error class in `@suaveplan/mime`. Each code is a non-overlappi…
|
||||
|
||||
- helpers — Low-level byte-inspection utilities used……
|
||||
|
||||
- [archives](./src/detection/from-magic-bytes/signatures/archives.md) — Registers magic-byte signatures for archive and compressed file formats with the global signature registry. Covers all…
|
||||
- [audio](./src/detection/from-magic-bytes/signatures/audio.md) — Registers magic-byte signatures for audio formats with the global signature registry. Covers all major audio container…
|
||||
- [data](./src/detection/from-magic-bytes/signatures/data.md) — Registers magic-byte signatures for binary data and structured-data formats with the global signature registry. Covers…
|
||||
- [documents](./src/detection/from-magic-bytes/signatures/documents.md) — Registers magic-byte signatures for document and data format files with the global signature registry. Covers office do…
|
||||
- [executables](./src/detection/from-magic-bytes/signatures/executables.md) — Registers magic-byte signatures for executable binary formats with the global signature registry. Covers native executa…
|
||||
- [fonts](./src/detection/from-magic-bytes/signatures/fonts.md) — Registers magic-byte signatures for font file formats with the global signature registry. Covers all major web font and…
|
||||
- [images](./src/detection/from-magic-bytes/signatures/images.md) — Registers magic-byte signatures for image formats with the global signature registry. Covers all major raster and vecto…
|
||||
- [model3d](./src/detection/from-magic-bytes/signatures/model3d.md) — Registers magic-byte signatures for 3D model file formats with the global signature registry. Covers formats used in CA…
|
||||
- [video](./src/detection/from-magic-bytes/signatures/video.md) — Registers magic-byte signatures for video container formats with the global signature registry. Covers all major video…
|
||||
- [from-magic-bytes](./src/detection/from-magic-bytes/from-magic-bytes.md) — <!-- begin:auto-docs --> _(no submodules)_- helpers — Low-level byte-inspection utilities used……
|
||||
- [helpers](./src/detection/from-magic-bytes/helpers.md) — Low-level byte-inspection utilities used by the magic-byte signature matchers. Provides helpers for common patterns: pr…
|
||||
- [registry](./src/detection/from-magic-bytes/registry.md) — The mutable signature registry for magic-byte detection. Stores all registered byte-pattern matchers and provides CRUD-…
|
||||
- [detect](./src/detection/detect.md) — The unified MIME detection entry point for `@suaveplan/mime`. Merges evidence from three independent sources — magic by…
|
||||
- [from-content-type](./src/detection/from-content-type.md) — Detects the MIME type from a raw `Content-Type` header string. Parses the header using the full RFC 7231 parser and wra…
|
||||
- [from-extension](./src/detection/from-extension.md) — Detects the MIME type from a file extension or full filename. Strips path components and leading dots, normalizes to lo…
|
||||
- [from-stream-reader](./src/detection/from-stream-reader.md) — Internal module providing the `toAsyncIterable` helper and the `drainViaReader` legacy fallback for `ReadableStream` so…
|
||||
- [from-stream](./src/detection/from-stream.md) — Provides `detectFromStream`, which reads the first N bytes from any async byte source (`ReadableStream<Uint8Array>` or…
|
||||
- [insufficient-bytes-error](./src/errors/insufficient-bytes-error.md) — Thrown when a magic-byte detection operation receives fewer bytes than the minimum required by the signature. Carries b…
|
||||
- [invalid-content-type-error](./src/errors/invalid-content-type-error.md) — Thrown when a `Content-Type` header or media type string fails RFC 7231 validation. Carries the raw input string and op…
|
||||
- [mime-error](./src/errors/mime-error.md) — The base error class for all `@suaveplan/mime` errors. Extends `SuaveplanError` from `@suaveplan/error` with the `MIME_…
|
||||
- [parse-error](./src/errors/parse-error.md) — Thrown when a general parsing operation fails — for example, XML declaration parsing or BOM detection failures. Unlike…
|
||||
- [unknown-mime-type-error](./src/errors/unknown-mime-type-error.md) — Thrown when a MIME type string is not found in the built-in database and no fallback is available. Carries the unrecogn…
|
||||
- [unknown-signature-error](./src/errors/unknown-signature-error.md) — Thrown when a byte signature identifier is referenced but is not present in the signature registry. Used as a sentinel…
|
||||
- [legacy](./src/legacy/get-mime-type.md) — <!-- begin:auto-docs --> _(no submodules)_- [get-mime-type](./src/legacy/get-mime-type.md) — <!-- begin:auto-docs --> _(no submodules)_- [reverse](./src/reverse/reverse.md) — <!-- begin:auto-docs --> _(no submodules)_- [reverse](./src/reverse/reverse.md) — <!-- begin:auto-docs --> _(no submodules)_- [telemetry](./src/telemetry/meters.md) — <!-- begin:auto-docs --> _(no submodules)_- [meters](./src/telemetry/meters.md) — <!-- begin:auto-docs --> _(no submodules)_- [error-codes](./src/error-codes.md) — Defines the stable numeric error-code registry for every error class in `@suaveplan/mime`. Each code is a non-overlappi…
|
||||
|
||||
- helpers — Low-level byte-inspection utilities used…
|
||||
|
||||
- [archives](./src/detection/from-magic-bytes/signatures/archives.md) — Registers magic-byte signatures for archive and compressed file formats with the global signature registry. Covers all…
|
||||
- [audio](./src/detection/from-magic-bytes/signatures/audio.md) — Registers magic-byte signatures for audio formats with the global signature registry. Covers all major audio container…
|
||||
- [data](./src/detection/from-magic-bytes/signatures/data.md) — Registers magic-byte signatures for binary data and structured-data formats with the global signature registry. Covers…
|
||||
- [documents](./src/detection/from-magic-bytes/signatures/documents.md) — Registers magic-byte signatures for document and data format files with the global signature registry. Covers office do…
|
||||
- [executables](./src/detection/from-magic-bytes/signatures/executables.md) — Registers magic-byte signatures for executable binary formats with the global signature registry. Covers native executa…
|
||||
- [fonts](./src/detection/from-magic-bytes/signatures/fonts.md) — Registers magic-byte signatures for font file formats with the global signature registry. Covers all major web font and…
|
||||
- [images](./src/detection/from-magic-bytes/signatures/images.md) — Registers magic-byte signatures for image formats with the global signature registry. Covers all major raster and vecto…
|
||||
- [model3d](./src/detection/from-magic-bytes/signatures/model3d.md) — Registers magic-byte signatures for 3D model file formats with the global signature registry. Covers formats used in CA…
|
||||
- [video](./src/detection/from-magic-bytes/signatures/video.md) — Registers magic-byte signatures for video container formats with the global signature registry. Covers all major video…
|
||||
- [from-magic-bytes](./src/detection/from-magic-bytes/from-magic-bytes.md) — <!-- begin:auto-docs --> _(no submodules)_- helpers — Low-level byte-inspection utilities used…
|
||||
- [helpers](./src/detection/from-magic-bytes/helpers.md) — Low-level byte-inspection utilities used by the magic-byte signature matchers. Provides helpers for common patterns: pr…
|
||||
- [registry](./src/detection/from-magic-bytes/registry.md) — The mutable signature registry for magic-byte detection. Stores all registered byte-pattern matchers and provides CRUD-…
|
||||
- [detect](./src/detection/detect.md) — The unified MIME detection entry point for `@suaveplan/mime`. Merges evidence from three independent sources — magic by…
|
||||
- [from-content-type](./src/detection/from-content-type.md) — Detects the MIME type from a raw `Content-Type` header string. Parses the header using the full RFC 7231 parser and wra…
|
||||
- [from-extension](./src/detection/from-extension.md) — Detects the MIME type from a file extension or full filename. Strips path components and leading dots, normalizes to lo…
|
||||
- [from-stream-reader](./src/detection/from-stream-reader.md) — Internal module providing the `toAsyncIterable` helper and the `drainViaReader` legacy fallback for `ReadableStream` so…
|
||||
- [from-stream](./src/detection/from-stream.md) — Provides `detectFromStream`, which reads the first N bytes from any async byte source (`ReadableStream<Uint8Array>` or…
|
||||
- [insufficient-bytes-error](./src/errors/insufficient-bytes-error.md) — Thrown when a magic-byte detection operation receives fewer bytes than the minimum required by the signature. Carries b…
|
||||
- [invalid-content-type-error](./src/errors/invalid-content-type-error.md) — Thrown when a `Content-Type` header or media type string fails RFC 7231 validation. Carries the raw input string and op…
|
||||
- [mime-error](./src/errors/mime-error.md) — The base error class for all `@suaveplan/mime` errors. Extends `SuaveplanError` from `@suaveplan/error` with the `MIME_…
|
||||
- [parse-error](./src/errors/parse-error.md) — Thrown when a general parsing operation fails — for example, XML declaration parsing or BOM detection failures. Unlike…
|
||||
- [unknown-mime-type-error](./src/errors/unknown-mime-type-error.md) — Thrown when a MIME type string is not found in the built-in database and no fallback is available. Carries the unrecogn…
|
||||
- [unknown-signature-error](./src/errors/unknown-signature-error.md) — Thrown when a byte signature identifier is referenced but is not present in the signature registry. Used as a sentinel…
|
||||
- [legacy](./src/legacy/get-mime-type.md) — <!-- begin:auto-docs --> _(no submodules)_- [get-mime-type](./src/legacy/get-mime-type.md) — <!-- begin:auto-docs --> _(no submodules)_- [reverse](./src/reverse/reverse.md) — <!-- begin:auto-docs --> _(no submodules)_- [reverse](./src/reverse/reverse.md) — <!-- begin:auto-docs --> _(no submodules)_- [telemetry](./src/telemetry/meters.md) — <!-- begin:auto-docs --> _(no submodules)_- [meters](./src/telemetry/meters.md) — <!-- begin:auto-docs --> _(no submodules)_- [error-codes](./src/error-codes.md) — Defines the stable numeric error-code registry for every error class in `@suaveplan/mime`. Each code is a non-overlappi…
|
||||
- [archives](./src/detection/from-magic-bytes/signatures/archives.md) — Registers magic-byte signatures for archive and compressed file formats with the global signature registry. Covers all…
|
||||
- [audio](./src/detection/from-magic-bytes/signatures/audio.md) — Registers magic-byte signatures for audio formats with the global signature registry. Covers all major audio container…
|
||||
- [data](./src/detection/from-magic-bytes/signatures/data.md) — Registers magic-byte signatures for binary data and structured-data formats with the global signature registry. Covers…
|
||||
- [documents](./src/detection/from-magic-bytes/signatures/documents.md) — Registers magic-byte signatures for document and data format files with the global signature registry. Covers office do…
|
||||
- [executables](./src/detection/from-magic-bytes/signatures/executables.md) — Registers magic-byte signatures for executable binary formats with the global signature registry. Covers native executa…
|
||||
- [fonts](./src/detection/from-magic-bytes/signatures/fonts.md) — Registers magic-byte signatures for font file formats with the global signature registry. Covers all major web font and…
|
||||
- [images](./src/detection/from-magic-bytes/signatures/images.md) — Registers magic-byte signatures for image formats with the global signature registry. Covers all major raster and vecto…
|
||||
- [model3d](./src/detection/from-magic-bytes/signatures/model3d.md) — Registers magic-byte signatures for 3D model file formats with the global signature registry. Covers formats used in CA…
|
||||
- [video](./src/detection/from-magic-bytes/signatures/video.md) — Registers magic-byte signatures for video container formats with the global signature registry. Covers all major video…
|
||||
- [from-magic-bytes](./src/detection/from-magic-bytes/from-magic-bytes.md) — <!-- begin:auto-docs --> _(no submodules)_- [helpers](./src/detection/from-magic-bytes/helpers.md) — Low-level byte-inspection utilities used by the magic-byte signature matchers. Provides helpers for common patterns: pr…
|
||||
- [registry](./src/detection/from-magic-bytes/registry.md) — The mutable signature registry for magic-byte detection. Stores all registered byte-pattern matchers and provides CRUD-…
|
||||
- [detect](./src/detection/detect.md) — The unified MIME detection entry point for `@suaveplan/mime`. Merges evidence from three independent sources — magic by…
|
||||
- [from-content-type](./src/detection/from-content-type.md) — Detects the MIME type from a raw `Content-Type` header string. Parses the header using the full RFC 7231 parser and wra…
|
||||
- [from-extension](./src/detection/from-extension.md) — Detects the MIME type from a file extension or full filename. Strips path components and leading dots, normalizes to lo…
|
||||
- [from-stream-reader](./src/detection/from-stream-reader.md) — Internal module providing the `toAsyncIterable` helper and the `drainViaReader` legacy fallback for `ReadableStream` so…
|
||||
- [from-stream](./src/detection/from-stream.md) — Provides `detectFromStream`, which reads the first N bytes from any async byte source (`ReadableStream<Uint8Array>` or…
|
||||
- [insufficient-bytes-error](./src/errors/insufficient-bytes-error.md) — Thrown when a magic-byte detection operation receives fewer bytes than the minimum required by the signature. Carries b…
|
||||
- [invalid-content-type-error](./src/errors/invalid-content-type-error.md) — Thrown when a `Content-Type` header or media type string fails RFC 7231 validation. Carries the raw input string and op…
|
||||
- [mime-error](./src/errors/mime-error.md) — The base error class for all `@suaveplan/mime` errors. Extends `SuaveplanError` from `@suaveplan/error` with the `MIME_…
|
||||
- [parse-error](./src/errors/parse-error.md) — Thrown when a general parsing operation fails — for example, XML declaration parsing or BOM detection failures. Unlike…
|
||||
- [unknown-mime-type-error](./src/errors/unknown-mime-type-error.md) — Thrown when a MIME type string is not found in the built-in database and no fallback is available. Carries the unrecogn…
|
||||
- [unknown-signature-error](./src/errors/unknown-signature-error.md) — Thrown when a byte signature identifier is referenced but is not present in the signature registry. Used as a sentinel…
|
||||
- [legacy](./src/legacy/get-mime-type.md) — <!-- begin:auto-docs --> _(no submodules)_ - [get-mime-type](./src/legacy/get-mime-type.md) — <!-- begin:auto-docs --> _(no submodules)_- [reverse](./src/reverse/reverse.md) — <!-- begin:auto-docs --> _(no submodules)_ - [reverse](./src/reverse/reverse.md) — <!-- begin:auto-docs --> _(no submodules)_- [telemetry](./src/telemetry/meters.md) — <!-- begin:auto-docs --> _(no submodules)_ - [meters](./src/telemetry/meters.md) — <!-- begin:auto-docs --> _(no submodules)_- [error-codes](./src/error-codes.md) — Defines the stable numeric error-code registry for every error class in `@suaveplan/mime`. Each code is a non-overlappi…
|
||||
|
||||
## Features
|
||||
|
||||
- **RFC 7231 Content-Type parsing** — full quoted-string and OWS support
|
||||
- **Magic-byte detection** — 150+ signatures across images, audio, video, archives, documents, executables, and fonts
|
||||
- **Stream detection** — peek bytes from a `ReadableStream` or `AsyncIterable` without losing the original stream
|
||||
- **Charset sniffing** — BOM, HTML `<meta>` tag, and XML declaration detection
|
||||
- **Multi-source confidence scoring** — merge bytes + extension + Content-Type with agreement boost
|
||||
- **Reverse lookup** — MIME type → canonical file extension
|
||||
- **OpenTelemetry metrics** — optional OTel instrumentation
|
||||
- **100% test coverage** — every branch and statement verified
|
||||
|
||||
## Installation
|
||||
|
||||
```bash
|
||||
bun add @suaveplan/mime
|
||||
```
|
||||
|
||||
## Quick Start
|
||||
|
||||
```typescript
|
||||
import { detect } from "@suaveplan/mime";
|
||||
|
||||
// Multi-source detection with confidence scoring
|
||||
const result = detect({
|
||||
bytes: fileBuffer,
|
||||
filename: "photo.jpg",
|
||||
contentType: "image/jpeg",
|
||||
});
|
||||
console.log(result.mime); // "image/jpeg"
|
||||
console.log(result.confidence); // 1.0 (all three sources agree)
|
||||
```
|
||||
|
||||
## Module Reference
|
||||
|
||||
| Module | Description | Docs |
|
||||
|--------|-------------|------|
|
||||
| `@suaveplan/mime` | Main entry: `detect()`, `parseContentType()`, `formatContentType()` | [src/index.ts](src/index.ts) |
|
||||
| `@suaveplan/mime/content-type` | RFC 7231 parser, formatter, equality/match utilities | [parse-content-type.md](src/content-type/parse-content-type.md) |
|
||||
| `@suaveplan/mime/detection/detect` | Unified multi-source detector | [detect.md](src/detection/detect.md) |
|
||||
| `@suaveplan/mime/detection/from-stream` | Stream-based detection with head/tail replay | [from-stream.md](src/detection/from-stream.md) |
|
||||
| `@suaveplan/mime/detection/from-magic-bytes` | Raw magic-byte detection API | [from-magic-bytes.md](src/detection/from-magic-bytes/from-magic-bytes.md) |
|
||||
| `@suaveplan/mime/charset/sniff-bom` | BOM detection from byte buffers | [sniff-bom.md](src/charset/sniff-bom.md) |
|
||||
| `@suaveplan/mime/charset/sniff-html-meta` | HTML `<meta charset>` detection | [sniff-html-meta.md](src/charset/sniff-html-meta.md) |
|
||||
| `@suaveplan/mime/charset/sniff-xml-decl` | XML encoding declaration detection | [sniff-xml-decl.md](src/charset/sniff-xml-decl.md) |
|
||||
| `@suaveplan/mime/db/registry` | MIME database: forward and reverse lookup | [registry.md](src/db/registry.md) |
|
||||
| `@suaveplan/mime/reverse` | MIME type → file extension reverse lookup | [reverse.md](src/reverse/reverse.md) |
|
||||
| `@suaveplan/mime/errors` | Typed error classes | [mime-error.md](src/errors/mime-error.md) |
|
||||
| `@suaveplan/mime/telemetry/meters` | Optional OTel metrics instrumentation | [meters.md](src/telemetry/meters.md) |
|
||||
| `@suaveplan/mime/legacy` | `getMimeType()` compat shim | [get-mime-type.md](src/legacy/get-mime-type.md) |
|
||||
|
||||
## API Examples
|
||||
|
||||
### Content-Type Parsing
|
||||
|
||||
```typescript
|
||||
import { parseContentType, formatContentType } from "@suaveplan/mime/content-type";
|
||||
|
||||
const ct = parseContentType("text/html; charset=UTF-8; format=flowed");
|
||||
console.log(ct.type); // "text"
|
||||
console.log(ct.subtype); // "html"
|
||||
console.log(ct.essence); // "text/html"
|
||||
console.log(ct.parameters.charset); // "utf-8" (lowercased)
|
||||
|
||||
console.log(formatContentType(ct)); // "text/html; charset=utf-8; format=flowed"
|
||||
```
|
||||
|
||||
### Magic-Byte Detection
|
||||
|
||||
```typescript
|
||||
import { detectFromMagicBytes } from "@suaveplan/mime/detection/from-magic-bytes/from-magic-bytes";
|
||||
|
||||
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
|
||||
```
|
||||
|
||||
### Stream Detection
|
||||
|
||||
```typescript
|
||||
import { detectFromStream } from "@suaveplan/mime/detection/from-stream";
|
||||
|
||||
const { mime, head, tail } = await detectFromStream(uploadStream);
|
||||
console.log(mime); // e.g. "video/mp4"
|
||||
|
||||
// Replay the full stream (head + tail)
|
||||
const chunks: Uint8Array[] = [head];
|
||||
for await (const chunk of tail) chunks.push(chunk);
|
||||
```
|
||||
|
||||
### Charset Sniffing
|
||||
|
||||
```typescript
|
||||
import { sniffBom } from "@suaveplan/mime/charset/sniff-bom";
|
||||
import { sniffHtmlMeta } from "@suaveplan/mime/charset/sniff-html-meta";
|
||||
|
||||
const bom = sniffBom(fileBytes); // "utf-8" | "utf-16le" | null
|
||||
const meta = sniffHtmlMeta(htmlText); // "iso-8859-1" | null
|
||||
```
|
||||
|
||||
### Error Handling
|
||||
|
||||
```typescript
|
||||
import { InvalidContentTypeError, UnknownMimeTypeError } from "@suaveplan/mime/errors";
|
||||
import { parseContentType } from "@suaveplan/mime/content-type";
|
||||
|
||||
try {
|
||||
parseContentType("not valid");
|
||||
} catch (err) {
|
||||
if (err instanceof InvalidContentTypeError) {
|
||||
console.error("Bad header:", err.context?.raw);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Registering Custom Signatures
|
||||
|
||||
```typescript
|
||||
import { registerSignature } from "@suaveplan/mime/detection/from-magic-bytes/registry";
|
||||
|
||||
registerSignature({
|
||||
mime: "application/x-my-format",
|
||||
priority: 75,
|
||||
minBytes: 4,
|
||||
match: (bytes) =>
|
||||
bytes[0] === 0x4d && bytes[1] === 0x59 &&
|
||||
bytes[2] === 0x46 && bytes[3] === 0x4d,
|
||||
});
|
||||
```
|
||||
|
||||
## Coverage
|
||||
|
||||
This package maintains **100% statement, branch, function, and line coverage** — a requirement for Layer 0/1 foundation packages in Suaveplan.
|
||||
|
||||
## License
|
||||
|
||||
MIT
|
||||
|
||||
59
docs/foundation/data/mime/src/charset/sniff-bom.md
Normal file
59
docs/foundation/data/mime/src/charset/sniff-bom.md
Normal file
@@ -0,0 +1,59 @@
|
||||
# sniff-bom
|
||||
|
||||
## Purpose
|
||||
|
||||
Detects character encoding from a Byte Order Mark (BOM) at the start of a binary buffer. BOM detection is the highest-confidence method of charset identification — when present, a BOM unambiguously identifies the encoding without relying on declared metadata or heuristics.
|
||||
|
||||
## Features
|
||||
|
||||
- Detects UTF-8 BOM (`EF BB BF`)
|
||||
- Detects UTF-16 BE BOM (`FE FF`)
|
||||
- Detects UTF-16 LE BOM (`FF FE`)
|
||||
- Detects UTF-32 BE BOM (`00 00 FE FF`)
|
||||
- Detects UTF-32 LE BOM (`FF FE 00 00`)
|
||||
- Accepts any `ArrayBuffer`, `SharedArrayBuffer`, or `ArrayBufferView` (`Uint8Array`, `DataView`, etc.)
|
||||
- Returns the IANA charset name in lowercase, or `null` when no BOM is present
|
||||
- Zero allocation path for no-BOM case
|
||||
|
||||
## Usage: Basic Example
|
||||
|
||||
```typescript
|
||||
import { sniffBom } from "@suaveplan/mime/charset/sniff-bom";
|
||||
|
||||
const bytes = new Uint8Array([0xef, 0xbb, 0xbf, 0x3c, 0x68, 0x74, 0x6d, 0x6c]);
|
||||
const charset = sniffBom(bytes);
|
||||
console.log(charset); // "utf-8"
|
||||
```
|
||||
|
||||
## Usage: Advanced Example
|
||||
|
||||
```typescript
|
||||
import { sniffBom } from "@suaveplan/mime/charset/sniff-bom";
|
||||
import { sniffHtmlMeta } from "@suaveplan/mime/charset/sniff-html-meta";
|
||||
|
||||
async function detectEncoding(buffer: Uint8Array): Promise<string> {
|
||||
// BOM takes precedence over meta-tag declarations
|
||||
const bomCharset = sniffBom(buffer);
|
||||
if (bomCharset) return bomCharset;
|
||||
|
||||
// Fall back to meta-tag detection
|
||||
const text = new TextDecoder("ascii", { fatal: false }).decode(buffer.slice(0, 1024));
|
||||
return sniffHtmlMeta(text) ?? "utf-8";
|
||||
}
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
### `sniffBom(buffer: ArrayBufferView | ArrayBuffer): string | null`
|
||||
|
||||
| Parameter | Type | Description |
|
||||
|-----------|------|-------------|
|
||||
| `buffer` | `ArrayBufferView \| ArrayBuffer` | The byte buffer to inspect (any view or raw buffer). |
|
||||
|
||||
**Returns:** The IANA charset name in lowercase, or `null` when no BOM is detected.
|
||||
|
||||
**Throws:** Never — all buffer accesses are bounds-checked.
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
UTF-32 BOMs (both BE and LE) must be tested before UTF-16 LE because the UTF-32 LE BOM (`FF FE 00 00`) starts with the same two bytes as the UTF-16 LE BOM (`FF FE`). The implementation checks the 4-byte sequences first to avoid misidentification. The function uses direct `Uint8Array` byte comparisons for maximum performance — no string allocations or regex matching.
|
||||
65
docs/foundation/data/mime/src/charset/sniff-html-meta.md
Normal file
65
docs/foundation/data/mime/src/charset/sniff-html-meta.md
Normal file
@@ -0,0 +1,65 @@
|
||||
# sniff-html-meta
|
||||
|
||||
## Purpose
|
||||
|
||||
Extracts a declared character encoding from an HTML `<meta>` tag by scanning the first 1,024 characters of an HTML document. Handles both the modern HTML5 `<meta charset="...">` form and the legacy HTML4 `<meta http-equiv="Content-Type" content="...; charset=...">` form using a single regex that matches `charset=` in any attribute context.
|
||||
|
||||
## Features
|
||||
|
||||
- Scans up to 1,024 bytes (configurable via the slice parameter)
|
||||
- Matches HTML5 `<meta charset="...">` tags
|
||||
- Matches HTML4 `<meta http-equiv="Content-Type" content="...; charset=...">` tags
|
||||
- Case-insensitive matching for both tag names and attribute values
|
||||
- Returns the charset name in lowercase for canonical comparison
|
||||
- Returns `null` when no charset declaration is found
|
||||
- Zero allocation on the no-match path
|
||||
|
||||
## Usage: Basic Example
|
||||
|
||||
```typescript
|
||||
import { sniffHtmlMeta } from "@suaveplan/mime/charset/sniff-html-meta";
|
||||
|
||||
const html = '<html><head><meta charset="UTF-8"><title>Hello</title>';
|
||||
const charset = sniffHtmlMeta(html);
|
||||
console.log(charset); // "utf-8"
|
||||
```
|
||||
|
||||
## Usage: Advanced Example
|
||||
|
||||
```typescript
|
||||
import { sniffHtmlMeta } from "@suaveplan/mime/charset/sniff-html-meta";
|
||||
import { sniffBom } from "@suaveplan/mime/charset/sniff-bom";
|
||||
|
||||
function detectHtmlEncoding(bytes: Uint8Array): string {
|
||||
// BOM has highest priority
|
||||
const bom = sniffBom(bytes);
|
||||
if (bom) return bom;
|
||||
|
||||
// Decode as ASCII/Latin-1 to scan for meta tags
|
||||
const head = new TextDecoder("latin1").decode(bytes.slice(0, 1024));
|
||||
const meta = sniffHtmlMeta(head);
|
||||
|
||||
// HTML5 spec default is utf-8 when no declaration is found
|
||||
return meta ?? "utf-8";
|
||||
}
|
||||
|
||||
// Legacy HTML4 http-equiv form
|
||||
const legacyHtml = '<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1">';
|
||||
console.log(sniffHtmlMeta(legacyHtml)); // "iso-8859-1"
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
### `sniffHtmlMeta(source: string): string | null`
|
||||
|
||||
| Parameter | Type | Description |
|
||||
|-----------|------|-------------|
|
||||
| `source` | `string` | HTML content (or the first chunk thereof). |
|
||||
|
||||
**Returns:** The lowercase IANA charset name, or `null` when no charset declaration is found in the first 1,024 characters.
|
||||
|
||||
**Throws:** Never.
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
A single regex (`/<meta[^>]+charset\s*=\s*["']?([A-Za-z0-9_\-:]+)/i`) covers both the HTML5 and HTML4 forms because `charset=` appears literally in the `content` attribute of the `http-equiv` tag. A separate legacy regex for the full `http-equiv` pattern is unnecessary — the simpler pattern matches earlier and always wins. The 1,024-character scan window follows the HTML5 parsing spec for prescan byte-sequence limits.
|
||||
64
docs/foundation/data/mime/src/charset/sniff-xml-decl.md
Normal file
64
docs/foundation/data/mime/src/charset/sniff-xml-decl.md
Normal file
@@ -0,0 +1,64 @@
|
||||
# sniff-xml-decl
|
||||
|
||||
## Purpose
|
||||
|
||||
Extracts the declared encoding from an XML or SVG document's processing instruction (`<?xml version="1.0" encoding="UTF-8"?>`). XML documents commonly declare their encoding in the prolog, and this declaration must be trusted before any BOM or HTTP header when present, per the XML specification (RFC 7303).
|
||||
|
||||
## Features
|
||||
|
||||
- Matches the `encoding` attribute in `<?xml ... encoding="..."?>` declarations
|
||||
- Supports both double-quoted and single-quoted attribute values
|
||||
- Case-insensitive matching — `ENCODING` and `encoding` both match
|
||||
- Scans only the first 256 characters (the XML prolog is always at the start)
|
||||
- Returns the encoding name in lowercase for canonical comparison
|
||||
- Returns `null` when no XML declaration or encoding attribute is found
|
||||
- Handles whitespace variations between attributes
|
||||
|
||||
## Usage: Basic Example
|
||||
|
||||
```typescript
|
||||
import { sniffXmlDecl } from "@suaveplan/mime/charset/sniff-xml-decl";
|
||||
|
||||
const xml = '<?xml version="1.0" encoding="UTF-8"?><root/>';
|
||||
const encoding = sniffXmlDecl(xml);
|
||||
console.log(encoding); // "utf-8"
|
||||
```
|
||||
|
||||
## Usage: Advanced Example
|
||||
|
||||
```typescript
|
||||
import { sniffXmlDecl } from "@suaveplan/mime/charset/sniff-xml-decl";
|
||||
import { sniffBom } from "@suaveplan/mime/charset/sniff-bom";
|
||||
|
||||
function detectXmlEncoding(bytes: Uint8Array): string {
|
||||
// BOM overrides XML declaration per RFC 7303 §4.3
|
||||
const bom = sniffBom(bytes);
|
||||
if (bom) return bom;
|
||||
|
||||
// Use latin-1 to safely read the ASCII-range prolog
|
||||
const prolog = new TextDecoder("latin1").decode(bytes.slice(0, 256));
|
||||
const declared = sniffXmlDecl(prolog);
|
||||
|
||||
return declared ?? "utf-8"; // XML default encoding
|
||||
}
|
||||
|
||||
// Single-quoted attribute
|
||||
const svgWithSingleQuotes = "<?xml version='1.0' encoding='iso-8859-1'?><svg/>";
|
||||
console.log(sniffXmlDecl(svgWithSingleQuotes)); // "iso-8859-1"
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
### `sniffXmlDecl(source: string): string | null`
|
||||
|
||||
| Parameter | Type | Description |
|
||||
|-----------|------|-------------|
|
||||
| `source` | `string` | XML/SVG content (or the first chunk thereof). |
|
||||
|
||||
**Returns:** The lowercase IANA encoding name, or `null` when no encoding attribute is found.
|
||||
|
||||
**Throws:** Never.
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
The regex `/<\?xml[^?]*encoding\s*=\s*["']([^"']+)["']/i` intentionally uses `[^?]*` (non-greedy via exclusion) rather than `.*?` to avoid backtracking across large documents when no `<?xml` declaration is present. The 256-character scan window is sufficient because the XML specification mandates that the encoding declaration, if present, must appear in the document prolog before any other content.
|
||||
@@ -0,0 +1,79 @@
|
||||
# content-type-equals
|
||||
|
||||
## Purpose
|
||||
|
||||
Provides two utility functions for comparing Content-Type values without caring about parameters: `contentTypeEquals` checks exact `type/subtype` equality (case-insensitive), and `contentTypeMatches` supports wildcard patterns (`*/*`, `image/*`). Both functions accept either raw header strings or pre-parsed `ParsedContentType` objects for zero-overhead comparisons.
|
||||
|
||||
## Features
|
||||
|
||||
- `contentTypeEquals`: exact essence comparison, ignoring parameters
|
||||
- `contentTypeMatches`: glob-style pattern matching (`*/*`, `image/*`, `text/plain`)
|
||||
- Accepts raw strings or `ParsedContentType` objects for both arguments
|
||||
- Case-insensitive for raw string inputs
|
||||
- Malformed patterns return `false` instead of throwing
|
||||
- Zero-cost fast path when a `ParsedContentType` is passed directly (no re-parse)
|
||||
|
||||
## Usage: Basic Example
|
||||
|
||||
```typescript
|
||||
import {
|
||||
contentTypeEquals,
|
||||
contentTypeMatches,
|
||||
} from "@suaveplan/mime/content-type/content-type-equals";
|
||||
|
||||
// Exact equality (parameters ignored)
|
||||
contentTypeEquals("text/html; charset=utf-8", "text/html"); // true
|
||||
contentTypeEquals("Text/HTML", "text/html"); // true (case-insensitive)
|
||||
contentTypeEquals("text/html", "text/plain"); // false
|
||||
|
||||
// Wildcard matching
|
||||
contentTypeMatches("image/png", "image/*"); // true
|
||||
contentTypeMatches("image/png", "*/*"); // true
|
||||
contentTypeMatches("text/html", "image/*"); // false
|
||||
```
|
||||
|
||||
## Usage: Advanced Example
|
||||
|
||||
```typescript
|
||||
import {
|
||||
contentTypeEquals,
|
||||
contentTypeMatches,
|
||||
} from "@suaveplan/mime/content-type/content-type-equals";
|
||||
import { parseContentType } from "@suaveplan/mime/content-type/parse-content-type";
|
||||
|
||||
// Pre-parse once and compare many times
|
||||
const incoming = parseContentType(request.headers.get("content-type") ?? "");
|
||||
|
||||
// Exact match against known types
|
||||
const isJson = contentTypeEquals(incoming, "application/json");
|
||||
|
||||
// Accept any image type
|
||||
const isImage = contentTypeMatches(incoming, "image/*");
|
||||
|
||||
// Build a router
|
||||
const handlers: Array<{ pattern: string; fn: (req: Request) => Response }> = [
|
||||
{ pattern: "application/json", fn: handleJson },
|
||||
{ pattern: "multipart/form-data", fn: handleMultipart },
|
||||
{ pattern: "text/*", fn: handleText },
|
||||
];
|
||||
|
||||
for (const { pattern, fn } of handlers) {
|
||||
if (contentTypeMatches(incoming, pattern)) {
|
||||
return fn(request);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
### `contentTypeEquals(a: string | ParsedContentType, b: string | ParsedContentType): boolean`
|
||||
|
||||
Returns `true` when both values have the same `type/subtype` essence, ignoring all parameters and case differences.
|
||||
|
||||
### `contentTypeMatches(value: string | ParsedContentType, pattern: string): boolean`
|
||||
|
||||
Returns `true` when the value's essence matches the pattern. The pattern may use `*` as a wildcard for either type or subtype. Returns `false` if the pattern is malformed (no slash).
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
Both functions normalize inputs through `parseContentType` when a raw string is provided. The pattern parser in `contentTypeMatches` uses a simple string split on `/` rather than re-using the full parser, which avoids the overhead of parameter parsing and error handling for patterns. Wildcard matching is implemented with direct string comparison after split — there is no full glob engine, only the two valid HTTP wildcard forms (`*/*` and `type/*`).
|
||||
@@ -0,0 +1,71 @@
|
||||
# format-content-type
|
||||
|
||||
## Purpose
|
||||
|
||||
Serializes a `ParsedContentType` object back into a standards-compliant `Content-Type` header string. Handles quoting of parameter values that contain non-token characters, ensuring the output is always valid for use in HTTP headers and is round-trip safe with `parseContentType`.
|
||||
|
||||
## Features
|
||||
|
||||
- Produces the canonical `type/subtype` essence string
|
||||
- Appends parameters as `; name=value` pairs in insertion order
|
||||
- Automatically quotes values containing non-token characters (spaces, special chars)
|
||||
- Does not quote simple token values (no unnecessary quotes)
|
||||
- Round-trip safe: `parseContentType(formatContentType(x)).essence === x.essence`
|
||||
- Accepts both `ParsedContentType` objects and ad-hoc objects with the same shape
|
||||
|
||||
## Usage: Basic Example
|
||||
|
||||
```typescript
|
||||
import { parseContentType } from "@suaveplan/mime/content-type/parse-content-type";
|
||||
import { formatContentType } from "@suaveplan/mime/content-type/format-content-type";
|
||||
|
||||
const ct = parseContentType("text/html; charset=utf-8");
|
||||
const formatted = formatContentType(ct);
|
||||
console.log(formatted); // "text/html; charset=utf-8"
|
||||
```
|
||||
|
||||
## Usage: Advanced Example
|
||||
|
||||
```typescript
|
||||
import { formatContentType } from "@suaveplan/mime/content-type/format-content-type";
|
||||
|
||||
// Construct a content type programmatically
|
||||
const ct = {
|
||||
type: "multipart",
|
||||
subtype: "form-data",
|
||||
essence: "multipart/form-data",
|
||||
parameters: Object.freeze({ boundary: "--my boundary with spaces" }),
|
||||
};
|
||||
console.log(formatContentType(ct));
|
||||
// 'multipart/form-data; boundary="--my boundary with spaces"'
|
||||
|
||||
// Values needing quoting are detected automatically
|
||||
const withSpecial = {
|
||||
type: "text",
|
||||
subtype: "plain",
|
||||
essence: "text/plain",
|
||||
parameters: Object.freeze({ name: 'file with "quotes".txt' }),
|
||||
};
|
||||
console.log(formatContentType(withSpecial));
|
||||
// 'text/plain; name="file with \\"quotes\\".txt"'
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
### `formatContentType(ct: ParsedContentType): string`
|
||||
|
||||
| Parameter | Type | Description |
|
||||
|-----------|------|-------------|
|
||||
| `ct` | `ParsedContentType` | A parsed content-type object. |
|
||||
|
||||
**Returns:** An RFC 7231-compliant `Content-Type` header string.
|
||||
|
||||
**Throws:** Never — all inputs that satisfy `ParsedContentType` are formattable.
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
A value is quoted only when it contains characters outside the RFC 7230 token character set (`!#$%&'*+-.^_\`|~0-9A-Za-z`). Characters within the quoted string that are not`token` characters are escaped with a backslash. This ensures the output is always valid even when parameter values contain double-quote characters themselves. The function uses `Object.keys` to enumerate parameters, which preserves insertion order in all modern JavaScript engines.
|
||||
|
||||
### Quoting Rules
|
||||
|
||||
Parameter values that contain characters outside the RFC 7230 `token` grammar (whitespace, `;`, `,`, non-ASCII) are wrapped in double quotes with backslash-escaping; values that already look like a valid token are emitted unquoted. Charset parameter values are lowercased at format time so output is canonical and hashes stably for content-based caches.
|
||||
@@ -0,0 +1,82 @@
|
||||
# parse-content-type
|
||||
|
||||
## Purpose
|
||||
|
||||
Parses an RFC 7231 / RFC 9110 `Content-Type` header value into a structured `ParsedContentType` object. Handles quoted strings, escape sequences, optional whitespace (OWS), and case normalization. The parsed object is immutable (frozen parameters map) and provides a canonical `essence` field (`type/subtype`) for efficient comparison.
|
||||
|
||||
## Features
|
||||
|
||||
- Parses `type/subtype` with OWS trimming and lowercase normalization
|
||||
- Parses parameters: `name=value` pairs, `;`-separated
|
||||
- Supports quoted-string parameter values with `\"` escape sequences
|
||||
- Normalizes `charset`, `boundary`, and `format` parameter values to lowercase
|
||||
- Handles OWS around `=` and between parameters
|
||||
- Validates type, subtype, and parameter names as RFC token characters
|
||||
- Guards against non-string inputs (JS interop safety)
|
||||
- Returns a frozen `parameters` object for safe sharing
|
||||
- Throws `InvalidContentTypeError` for all malformed inputs
|
||||
|
||||
## Usage: Basic Example
|
||||
|
||||
```typescript
|
||||
import { parseContentType } from "@suaveplan/mime/content-type/parse-content-type";
|
||||
|
||||
const ct = parseContentType("text/html; charset=utf-8");
|
||||
console.log(ct.type); // "text"
|
||||
console.log(ct.subtype); // "html"
|
||||
console.log(ct.essence); // "text/html"
|
||||
console.log(ct.parameters); // { charset: "utf-8" }
|
||||
```
|
||||
|
||||
## Usage: Advanced Example
|
||||
|
||||
```typescript
|
||||
import { parseContentType } from "@suaveplan/mime/content-type/parse-content-type";
|
||||
import { InvalidContentTypeError } from "@suaveplan/mime/errors";
|
||||
|
||||
// Quoted boundary with spaces
|
||||
const multipart = parseContentType('multipart/form-data; boundary="--boundary 123"');
|
||||
console.log(multipart.parameters.boundary); // "--boundary 123"
|
||||
|
||||
// Multiple parameters with OWS
|
||||
const complex = parseContentType('text/html; charset= utf-8; format=flowed');
|
||||
console.log(complex.parameters.charset); // "utf-8"
|
||||
console.log(complex.parameters.format); // "flowed"
|
||||
|
||||
// Error handling
|
||||
try {
|
||||
parseContentType("not/valid token!");
|
||||
} catch (err) {
|
||||
if (err instanceof InvalidContentTypeError) {
|
||||
console.error("Malformed Content-Type:", err.message);
|
||||
}
|
||||
}
|
||||
|
||||
// Round-trip safe (parse → format → parse)
|
||||
const reparsed = parseContentType(formatContentType(multipart));
|
||||
console.log(reparsed.essence); // "multipart/form-data"
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
### `parseContentType(input: string): ParsedContentType`
|
||||
|
||||
| Parameter | Type | Description |
|
||||
|-----------|------|-------------|
|
||||
| `input` | `string` | Raw `Content-Type` header value. |
|
||||
|
||||
**Returns:** A `ParsedContentType` with `type`, `subtype`, `essence`, and frozen `parameters`.
|
||||
|
||||
**Throws:** `InvalidContentTypeError` when:
|
||||
|
||||
- Input is not a string
|
||||
- Input is empty or whitespace-only
|
||||
- Missing slash between type and subtype
|
||||
- Type or subtype is empty
|
||||
- Type or subtype contains non-token characters
|
||||
- Parameter name contains non-token characters
|
||||
- Parameter is missing the `=` separator
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
The parser is a hand-written state machine operating on the raw string. This avoids regex backtracking issues with complex quoted-string values and ensures O(n) time complexity. The `parameters` object is frozen via `Object.freeze` before return to prevent accidental mutation by callers. The `charset`, `boundary`, and `format` parameter values are lowercased per the RFC — all other parameter values preserve their original casing. The leading `trim()` on the full input removes any surrounding whitespace, which means trailing spaces after a final semicolon are eliminated before parameter parsing begins.
|
||||
56
docs/foundation/data/mime/src/db/builtin-db.md
Normal file
56
docs/foundation/data/mime/src/db/builtin-db.md
Normal file
@@ -0,0 +1,56 @@
|
||||
# db/builtin-db
|
||||
|
||||
## Purpose
|
||||
|
||||
The built-in MIME type database for `@suaveplan/mime`. Contains over 150 `MimeEntry` records covering the most common web, media, document, archive, font, and application MIME types. This is a static data file — it contains no runtime logic, only the `BUILTIN_DB` constant array consumed by the registry.
|
||||
|
||||
## Features
|
||||
|
||||
- 150+ MIME type entries across all major categories
|
||||
- Each entry includes: `mime` (canonical type string), `extensions` (array of associated file extensions), and `encoding` (`"binary"`, `"text"`, or `"base64"`)
|
||||
- Comprehensive coverage: images, audio, video, documents, archives, fonts, executables, data formats
|
||||
- Extensions are lowercase and without leading dots
|
||||
- Canonical extension (for `Content-Disposition` filename guessing) is always the first element
|
||||
|
||||
## Usage: Basic Example
|
||||
|
||||
```typescript
|
||||
import { BUILTIN_DB } from "@suaveplan/mime/db/builtin-db";
|
||||
|
||||
// Inspect all text-type entries
|
||||
const textTypes = BUILTIN_DB.filter(e => e.mime.startsWith("text/"));
|
||||
console.log(textTypes.map(e => e.mime));
|
||||
// ["text/plain", "text/html", "text/css", ...]
|
||||
```
|
||||
|
||||
## Usage: Advanced Example
|
||||
|
||||
```typescript
|
||||
import { BUILTIN_DB } from "@suaveplan/mime/db/builtin-db";
|
||||
import { getEntryByMime } from "@suaveplan/mime/db/registry";
|
||||
|
||||
// Combine built-in with runtime lookup
|
||||
const entry = getEntryByMime("application/pdf");
|
||||
console.log(entry?.extensions); // ["pdf"]
|
||||
console.log(entry?.encoding); // "binary"
|
||||
|
||||
// Find all binary MIME types
|
||||
const binaryTypes = BUILTIN_DB.filter(e => e.encoding === "binary");
|
||||
console.log(binaryTypes.length); // 100+
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
### `BUILTIN_DB: readonly MimeEntry[]`
|
||||
|
||||
A frozen array of `MimeEntry` objects. Each entry has:
|
||||
|
||||
| Field | Type | Description |
|
||||
|-------|------|-------------|
|
||||
| `mime` | `string` | Canonical MIME type string (e.g., `"image/png"`) |
|
||||
| `extensions` | `string[]` | Associated file extensions without dots (e.g., `["png"]`) |
|
||||
| `encoding` | `"binary" \| "text" \| "base64"` | Transfer encoding hint |
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
The database is intentionally a plain array rather than a pre-built map so that the registry can apply its first-write-wins semantics during initialization. Keeping data and behavior separate also makes it straightforward to swap the database at build time (e.g., for a minimal embedded build that includes only a subset of MIME types). The entries are ordered roughly by frequency of use, which has no algorithmic impact but improves grep readability.
|
||||
70
docs/foundation/data/mime/src/db/registry.md
Normal file
70
docs/foundation/data/mime/src/db/registry.md
Normal file
@@ -0,0 +1,70 @@
|
||||
# db/registry
|
||||
|
||||
## Purpose
|
||||
|
||||
The central in-memory MIME type database for `@suaveplan/mime`. Maintains two indexes over the built-in `MimeEntry` list: a `type → extensions` forward map and an `extension → entry` reverse map. Provides O(1) lookup in both directions and supports registering additional entries at runtime for extensibility.
|
||||
|
||||
## Features
|
||||
|
||||
- `getEntryByMime(mime)` — forward lookup: MIME type string → `MimeEntry`
|
||||
- `getEntryByExtension(ext)` — reverse lookup: file extension → `MimeEntry`
|
||||
- `registerEntry(entry)` — register a custom MIME type at runtime
|
||||
- First-write-wins semantics: built-in entries take precedence over later registrations for the same extension
|
||||
- Extension keys are normalized to lowercase with no leading dot
|
||||
- Both indexes are populated lazily on first access (module load)
|
||||
- `_indexEntryForTesting` — exported only for internal test instrumentation
|
||||
|
||||
## Usage: Basic Example
|
||||
|
||||
```typescript
|
||||
import { getEntryByMime, getEntryByExtension } from "@suaveplan/mime/db/registry";
|
||||
|
||||
// Forward lookup
|
||||
const pngEntry = getEntryByMime("image/png");
|
||||
console.log(pngEntry?.extensions); // ["png"]
|
||||
|
||||
// Reverse lookup
|
||||
const entry = getEntryByExtension("jpg");
|
||||
console.log(entry?.mime); // "image/jpeg"
|
||||
```
|
||||
|
||||
## Usage: Advanced Example
|
||||
|
||||
```typescript
|
||||
import { getEntryByMime, registerEntry } from "@suaveplan/mime/db/registry";
|
||||
|
||||
// Register a custom MIME type
|
||||
registerEntry({
|
||||
mime: "application/x-my-format",
|
||||
extensions: ["myf", "myfmt"],
|
||||
encoding: "binary",
|
||||
});
|
||||
|
||||
// Immediately available via lookup
|
||||
const custom = getEntryByMime("application/x-my-format");
|
||||
console.log(custom?.extensions); // ["myf", "myfmt"]
|
||||
|
||||
// Built-in extensions are protected (first-write-wins)
|
||||
// Registering a second entry for ".png" will not override image/png
|
||||
registerEntry({ mime: "image/x-fake-png", extensions: ["png"] });
|
||||
const png = getEntryByExtension("png");
|
||||
console.log(png?.mime); // still "image/png"
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
### `getEntryByMime(mime: string): MimeEntry | undefined`
|
||||
|
||||
Looks up a `MimeEntry` by its MIME type string. Returns `undefined` if not found.
|
||||
|
||||
### `getEntryByExtension(ext: string): MimeEntry | undefined`
|
||||
|
||||
Looks up a `MimeEntry` by file extension (with or without leading dot, case-insensitive). Returns `undefined` if not found.
|
||||
|
||||
### `registerEntry(entry: MimeEntry): void`
|
||||
|
||||
Registers a new MIME type entry. First-write-wins for extensions: if an extension is already in the index, the new entry does not replace it.
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
The registry is initialized at module load by iterating `BUILTIN_DB` once and calling the private `index()` function for each entry. The `index()` function implements the first-write-wins extension guard: `if (!byExtension.has(key)) byExtension.set(key, entry)`. This guard prevents downstream `registerEntry` calls from overriding the canonical extension-to-type mapping established by the built-in database, which is important for stable behavior across multiple `registerEntry` calls in plugin-style usage.
|
||||
90
docs/foundation/data/mime/src/detection/detect.md
Normal file
90
docs/foundation/data/mime/src/detection/detect.md
Normal 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.
|
||||
56
docs/foundation/data/mime/src/detection/from-content-type.md
Normal file
56
docs/foundation/data/mime/src/detection/from-content-type.md
Normal 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.
|
||||
62
docs/foundation/data/mime/src/detection/from-extension.md
Normal file
62
docs/foundation/data/mime/src/detection/from-extension.md
Normal 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.
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
@@ -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`.
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
79
docs/foundation/data/mime/src/detection/from-stream.md
Normal file
79
docs/foundation/data/mime/src/detection/from-stream.md
Normal 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.
|
||||
63
docs/foundation/data/mime/src/error-codes/error-codes.md
Normal file
63
docs/foundation/data/mime/src/error-codes/error-codes.md
Normal file
@@ -0,0 +1,63 @@
|
||||
# error-codes
|
||||
|
||||
<!-- begin:auto-docs -->
|
||||
_(no submodules)_
|
||||
<!-- end:auto-docs -->
|
||||
|
||||
## Purpose
|
||||
|
||||
Defines the stable numeric error-code registry for every error class in `@suaveplan/mime`. Each code is a non-overlapping integer in the `5000–5099` range, allocated via `defineCodes` from `@suaveplan/error`. Stable codes enable error identification across serialization, transport, and version upgrades without relying on class names or message strings.
|
||||
|
||||
## Features
|
||||
|
||||
- Five distinct error codes: `INVALID_CONTENT_TYPE`, `UNKNOWN_MIME_TYPE`, `INSUFFICIENT_BYTES`, `PARSE_ERROR`, `UNKNOWN_SIGNATURE`
|
||||
- Allocated via `@suaveplan/error`'s `defineCodes` for namespace isolation
|
||||
- Zero runtime overhead — values are plain numbers resolved at import time
|
||||
- Deterministic: the same integer is guaranteed across package versions
|
||||
|
||||
## Usage: Basic Example
|
||||
|
||||
```typescript
|
||||
import { MIME_ERROR_CODES } from "@suaveplan/mime/errors/codes";
|
||||
|
||||
// Identify any MIME error by numeric code
|
||||
if (error.code === MIME_ERROR_CODES.INVALID_CONTENT_TYPE) {
|
||||
console.error("Malformed Content-Type header");
|
||||
}
|
||||
```
|
||||
|
||||
## Usage: Advanced Example
|
||||
|
||||
```typescript
|
||||
import { MIME_ERROR_CODES } from "@suaveplan/mime/errors/codes";
|
||||
import { InvalidContentTypeError } from "@suaveplan/mime/errors";
|
||||
|
||||
// The code on each thrown error matches the registry
|
||||
try {
|
||||
parseContentType("not-a-content-type");
|
||||
} catch (err) {
|
||||
if (err instanceof InvalidContentTypeError) {
|
||||
console.log(err.code === MIME_ERROR_CODES.INVALID_CONTENT_TYPE); // true
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
### `MIME_ERROR_CODES`
|
||||
|
||||
| Name | Description |
|
||||
|------|-------------|
|
||||
| `INVALID_CONTENT_TYPE` | Malformed or unparseable Content-Type header |
|
||||
| `UNKNOWN_MIME_TYPE` | MIME type not found in the built-in database |
|
||||
| `INSUFFICIENT_BYTES` | Too few bytes provided for magic-byte detection |
|
||||
| `PARSE_ERROR` | General parse failure (XML/HTML charset sniffing) |
|
||||
| `UNKNOWN_SIGNATURE` | Byte signature pattern not recognized |
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
The range `5000–5099` is reserved for `@suaveplan/mime`. Adjacent packages use non-overlapping ranges so that a project-wide error router can dispatch on code alone. The `defineCodes` call enforces uniqueness and throws at import time if a range collision is detected during development.
|
||||
|
||||
### Registry Layout
|
||||
|
||||
Every code lives under the `MIME` namespace and is produced by `defineCodes` from `@suaveplan/error`: `MIME.PARSE.INVALID`, `MIME.CONTENT_TYPE.INVALID`, `MIME.TYPE.UNKNOWN`, `MIME.SIGNATURE.UNKNOWN`, `MIME.BYTES.INSUFFICIENT`. The `MimeErrorCode` union is used by the `MimeError<TCode>` generic so subclasses advertise exactly which codes they can carry.
|
||||
@@ -0,0 +1,65 @@
|
||||
# errors/insufficient-bytes-error
|
||||
|
||||
## Purpose
|
||||
|
||||
Thrown when a magic-byte detection operation receives fewer bytes than the minimum required by the signature. Carries both the number of bytes received and the number required in `context` for precise error reporting.
|
||||
|
||||
## Features
|
||||
|
||||
- Error code `MIME_ERROR_CODES.INSUFFICIENT_BYTES`
|
||||
- `context.received` — number of bytes provided
|
||||
- `context.required` — minimum bytes needed for detection
|
||||
- `name` set to `"InsufficientBytesError"`
|
||||
- Full `SuaveplanError` serialization
|
||||
|
||||
## Usage: Basic Example
|
||||
|
||||
```typescript
|
||||
import { InsufficientBytesError } from "@suaveplan/mime/errors";
|
||||
|
||||
function requireMinBytes(bytes: Uint8Array, minBytes: number): void {
|
||||
if (bytes.length < minBytes) {
|
||||
throw new InsufficientBytesError({
|
||||
context: { received: bytes.length, required: minBytes },
|
||||
});
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Usage: Advanced Example
|
||||
|
||||
```typescript
|
||||
import { InsufficientBytesError } from "@suaveplan/mime/errors";
|
||||
|
||||
try {
|
||||
detectFromBytes(tinyBuffer);
|
||||
} catch (err) {
|
||||
if (err instanceof InsufficientBytesError) {
|
||||
console.warn(
|
||||
`Need ${err.context?.required} bytes, got ${err.context?.received}. ` +
|
||||
"Read more bytes and retry."
|
||||
);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
### `class InsufficientBytesError extends MimeError`
|
||||
|
||||
**Constructor options:**
|
||||
|
||||
| Field | Type | Description |
|
||||
|-------|------|-------------|
|
||||
| `message?` | `string` | Human-readable message |
|
||||
| `context?.received` | `number` | Bytes actually received |
|
||||
| `context?.required` | `number` | Minimum bytes required |
|
||||
| `cause?` | `unknown` | Underlying cause |
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
This error is not currently thrown by the public `detectFromMagicBytes` API (which returns `undefined` for insufficient bytes rather than throwing). It is available for use by higher-level wrappers that prefer a fail-fast behavior over returning a nullable result. Matching the `received`/`required` context fields against monitoring thresholds enables proactive detection of truncated upload streams.
|
||||
|
||||
### Callers
|
||||
|
||||
Thrown by stream-oriented detection paths (`detectFromStream`) when the upstream `ReadableStream<Uint8Array>` yields fewer bytes than any candidate signature's `minBytes` requires. Callers that do their own buffering should catch this specifically and request more bytes rather than giving up — most signatures need only 4–16 bytes, so a single additional chunk is usually enough. The error carries the `requested` and `available` byte counts for log context.
|
||||
@@ -0,0 +1,72 @@
|
||||
# errors/invalid-content-type-error
|
||||
|
||||
## Purpose
|
||||
|
||||
Thrown when a `Content-Type` header or media type string fails RFC 7231 validation. Carries the raw input string and optional byte position in the `context` field to help callers produce useful error messages for end users and debugging.
|
||||
|
||||
## Features
|
||||
|
||||
- Error code `MIME_ERROR_CODES.INVALID_CONTENT_TYPE`
|
||||
- Optional `context.raw` field with the offending input string
|
||||
- Optional `context.position` field with the byte offset where parsing failed
|
||||
- `name` set to `"InvalidContentTypeError"`
|
||||
- Full `SuaveplanError` serialization (JSON + binary wire format, redaction, OTel)
|
||||
|
||||
## Usage: Basic Example
|
||||
|
||||
```typescript
|
||||
import { parseContentType } from "@suaveplan/mime/content-type/parse-content-type";
|
||||
import { InvalidContentTypeError } from "@suaveplan/mime/errors";
|
||||
|
||||
try {
|
||||
parseContentType("not a valid content type");
|
||||
} catch (err) {
|
||||
if (err instanceof InvalidContentTypeError) {
|
||||
console.error("Invalid input:", err.context?.raw);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Usage: Advanced Example
|
||||
|
||||
```typescript
|
||||
import { InvalidContentTypeError } from "@suaveplan/mime/errors";
|
||||
|
||||
// Throw directly with full context
|
||||
function assertValidContentType(header: string): void {
|
||||
if (!header.includes("/")) {
|
||||
throw new InvalidContentTypeError({
|
||||
message: `Content-Type must contain a slash: "${header}"`,
|
||||
context: { raw: header, position: 0 },
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Wire-format serialization is inherited from SuaveplanError
|
||||
const err = new InvalidContentTypeError({
|
||||
message: "bad input",
|
||||
context: { raw: "text" },
|
||||
});
|
||||
const json = err.toJSON(); // { code, message, context, name, ... }
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
### `class InvalidContentTypeError extends MimeError`
|
||||
|
||||
**Constructor options:**
|
||||
|
||||
| Field | Type | Description |
|
||||
|-------|------|-------------|
|
||||
| `message` | `string` | Human-readable error description |
|
||||
| `context?.raw` | `string` | The raw input that failed parsing |
|
||||
| `context?.position` | `number` | Byte offset where the error occurred |
|
||||
| `cause?` | `unknown` | Underlying cause (for cause-chain traversal) |
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
The `context` field uses `exactOptionalPropertyTypes` semantics — the field is entirely absent when not provided, not set to `undefined`. This matches `@suaveplan/error`'s strict typing conventions and avoids false positives when checking `err.context?.raw`.
|
||||
|
||||
### Callers
|
||||
|
||||
Thrown by `parseContentType` when the RFC 7231 grammar is violated: missing `/`, invalid token characters in type or subtype, malformed parameter syntax, or an unterminated quoted-string. The error's `input` field carries the raw header value so API logs can show exactly what the client sent. Consumers generally respond with HTTP 400 — the request is unparseable, not merely unsupported.
|
||||
59
docs/foundation/data/mime/src/errors/mime-error.md
Normal file
59
docs/foundation/data/mime/src/errors/mime-error.md
Normal file
@@ -0,0 +1,59 @@
|
||||
# errors/mime-error
|
||||
|
||||
## Purpose
|
||||
|
||||
The base error class for all `@suaveplan/mime` errors. Extends `SuaveplanError` from `@suaveplan/error` with the `MIME_ERROR_CODES` namespace and provides a common ancestor for `instanceof` checks across all MIME-specific error types.
|
||||
|
||||
## Features
|
||||
|
||||
- Extends `SuaveplanError` for consistent error serialization and OTel integration
|
||||
- `code` field set to `MIME_ERROR_CODES.INVALID_CONTENT_TYPE` by default (overridden by subclasses)
|
||||
- `name` property set to `"MimeError"` for easy identification in logs
|
||||
- Compatible with `SuaveplanError`'s cause-chain traversal and redaction APIs
|
||||
- Used as the base for `instanceof MimeError` checks when callers don't need the specific subtype
|
||||
|
||||
## Usage: Basic Example
|
||||
|
||||
```typescript
|
||||
import { MimeError } from "@suaveplan/mime/errors";
|
||||
|
||||
try {
|
||||
// Any MIME operation
|
||||
} catch (err) {
|
||||
if (err instanceof MimeError) {
|
||||
console.error("MIME error:", err.message, "code:", err.code);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Usage: Advanced Example
|
||||
|
||||
```typescript
|
||||
import { MimeError } from "@suaveplan/mime/errors";
|
||||
import { InvalidContentTypeError, UnknownMimeTypeError } from "@suaveplan/mime/errors";
|
||||
|
||||
// Route by specific subtype
|
||||
function handleMimeError(err: MimeError) {
|
||||
if (err instanceof InvalidContentTypeError) {
|
||||
return Response.json({ error: "bad_content_type" }, { status: 400 });
|
||||
}
|
||||
if (err instanceof UnknownMimeTypeError) {
|
||||
return Response.json({ error: "unsupported_type" }, { status: 415 });
|
||||
}
|
||||
throw err; // Unexpected subtype — re-throw
|
||||
}
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
### `class MimeError extends SuaveplanError`
|
||||
|
||||
The base class. All `@suaveplan/mime` error classes extend this.
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
`MimeError` exists primarily to provide a stable `instanceof` target for callers who want to catch any MIME-related error without caring about the specific subtype. All subclasses set their own `code` from `MIME_ERROR_CODES` and override `name` with their class name. The inheritance chain is: `SuaveplanError → MimeError → {subtype}`.
|
||||
|
||||
### Subclass Index
|
||||
|
||||
Direct subclasses are `InvalidContentTypeError`, `UnknownMimeTypeError`, `UnknownSignatureError`, `InsufficientBytesError`, and `ParseError`. Every subclass sets its own `code` from the stable `MIME_ERROR_CODES` registry, overrides `name`, and inherits the wire-serialisation, OTel span-event adapter, and i18n resolver behaviour from `SuaveplanError`. Tests typically assert `err instanceof MimeError` first for broad catches, then narrow with `instanceof` on the specific subclass.
|
||||
69
docs/foundation/data/mime/src/errors/parse-error.md
Normal file
69
docs/foundation/data/mime/src/errors/parse-error.md
Normal file
@@ -0,0 +1,69 @@
|
||||
# errors/parse-error
|
||||
|
||||
## Purpose
|
||||
|
||||
Thrown when a general parsing operation fails — for example, XML declaration parsing or BOM detection failures. Unlike `InvalidContentTypeError`, which is specific to RFC 7231 Content-Type headers, `ParseError` is the catch-all for any other structured-text parsing failure within the `@suaveplan/mime` package.
|
||||
|
||||
## Features
|
||||
|
||||
- Error code `MIME_ERROR_CODES.PARSE_ERROR`
|
||||
- `name` set to `"ParseError"`
|
||||
- Optional context fields for the offending input
|
||||
- Full `SuaveplanError` serialization
|
||||
|
||||
## Usage: Basic Example
|
||||
|
||||
```typescript
|
||||
import { ParseError } from "@suaveplan/mime/errors";
|
||||
|
||||
function parseXmlEncoding(text: string): string {
|
||||
const match = /encoding=['"]([^'"]+)/.exec(text);
|
||||
if (!match) {
|
||||
throw new ParseError({
|
||||
message: "No encoding declaration found in XML prolog",
|
||||
context: { raw: text.slice(0, 100) },
|
||||
});
|
||||
}
|
||||
return match[1];
|
||||
}
|
||||
```
|
||||
|
||||
## Usage: Advanced Example
|
||||
|
||||
```typescript
|
||||
import { MimeError, ParseError } from "@suaveplan/mime/errors";
|
||||
|
||||
// Catch any MIME parse error generically
|
||||
try {
|
||||
processDocument(buffer);
|
||||
} catch (err) {
|
||||
if (err instanceof ParseError) {
|
||||
console.error("Document parse failed:", err.message);
|
||||
return { error: "parse_failed", details: err.context };
|
||||
}
|
||||
if (err instanceof MimeError) {
|
||||
// Other MIME errors
|
||||
}
|
||||
throw err;
|
||||
}
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
### `class ParseError extends MimeError`
|
||||
|
||||
**Constructor options:**
|
||||
|
||||
| Field | Type | Description |
|
||||
|-------|------|-------------|
|
||||
| `message` | `string` | Human-readable error description |
|
||||
| `context?` | `object` | Arbitrary structured context for debugging |
|
||||
| `cause?` | `unknown` | Underlying cause |
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
`ParseError` is intentionally generic so that future parsing features (XML namespace detection, HTTP range header parsing, etc.) can reuse it without creating a proliferation of narrow error classes. If a specific context shape becomes widely used, a dedicated subclass can be introduced with a more precise `context` type.
|
||||
|
||||
### Callers
|
||||
|
||||
Thrown by lower-level parser stages when a structurally-recognised format cannot be fully parsed — for example a truncated multipart boundary, an invalid base64 parameter value, or a charset declaration that doesn't round-trip through `TextDecoder`. Distinct from `InvalidContentTypeError`, which is reserved for the RFC 7231 grammar checker. The error captures both the `stage` that failed and the raw `input` for log replay.
|
||||
@@ -0,0 +1,63 @@
|
||||
# errors/unknown-mime-type-error
|
||||
|
||||
## Purpose
|
||||
|
||||
Thrown when a MIME type string is not found in the built-in database and no fallback is available. Carries the unrecognized MIME type string in `context.mime` so callers can log, redirect, or prompt users to provide additional information.
|
||||
|
||||
## Features
|
||||
|
||||
- Error code `MIME_ERROR_CODES.UNKNOWN_MIME_TYPE`
|
||||
- Optional `context.mime` field with the unrecognized MIME type string
|
||||
- `name` set to `"UnknownMimeTypeError"`
|
||||
- Full `SuaveplanError` serialization
|
||||
|
||||
## Usage: Basic Example
|
||||
|
||||
```typescript
|
||||
import { getEntryByMime } from "@suaveplan/mime/db/registry";
|
||||
import { UnknownMimeTypeError } from "@suaveplan/mime/errors";
|
||||
|
||||
function requireMimeEntry(mime: string) {
|
||||
const entry = getEntryByMime(mime);
|
||||
if (!entry) {
|
||||
throw new UnknownMimeTypeError({
|
||||
context: { mime },
|
||||
});
|
||||
}
|
||||
return entry;
|
||||
}
|
||||
```
|
||||
|
||||
## Usage: Advanced Example
|
||||
|
||||
```typescript
|
||||
import { UnknownMimeTypeError } from "@suaveplan/mime/errors";
|
||||
|
||||
// Auto-generated message includes the MIME type
|
||||
const err = new UnknownMimeTypeError({ context: { mime: "application/x-exotic" } });
|
||||
console.log(err.message); // "Unknown MIME type: application/x-exotic"
|
||||
|
||||
// Without context — uses "unknown" fallback in message
|
||||
const err2 = new UnknownMimeTypeError({ message: "MIME type not recognized" });
|
||||
console.log(err2.context); // undefined
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
### `class UnknownMimeTypeError extends MimeError`
|
||||
|
||||
**Constructor options:**
|
||||
|
||||
| Field | Type | Description |
|
||||
|-------|------|-------------|
|
||||
| `message?` | `string` | Human-readable message (auto-generated from context.mime if omitted) |
|
||||
| `context?.mime` | `string` | The unrecognized MIME type string |
|
||||
| `cause?` | `unknown` | Underlying cause |
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
The error message is auto-generated as `"Unknown MIME type: {mime}"` when `context.mime` is provided and no explicit `message` is supplied. This convention reduces boilerplate for the common case of constructing the error from a lookup failure.
|
||||
|
||||
### Callers
|
||||
|
||||
Thrown by the strict variants of registry lookups (`mimeTypeOrThrow`, `mimeCategoryOrThrow`) when the supplied MIME string has no entry in the curated IANA database. Non-strict variants return `undefined` instead. The error carries the requested MIME string so callers can log, suggest the nearest registered type, or fall back to a default. Usually handled at an API boundary with HTTP 415.
|
||||
@@ -0,0 +1,67 @@
|
||||
# errors/unknown-signature-error
|
||||
|
||||
## Purpose
|
||||
|
||||
Thrown when a byte signature identifier is referenced but is not present in the signature registry. Used as a sentinel for development-time validation when code expects a specific signature to be registered (for example, in plugin systems that verify their registration succeeded).
|
||||
|
||||
## Features
|
||||
|
||||
- Error code `MIME_ERROR_CODES.UNKNOWN_SIGNATURE`
|
||||
- `context.signature` — the expected signature identifier or MIME type
|
||||
- `name` set to `"UnknownSignatureError"`
|
||||
- Full `SuaveplanError` serialization
|
||||
|
||||
## Usage: Basic Example
|
||||
|
||||
```typescript
|
||||
import { UnknownSignatureError } from "@suaveplan/mime/errors";
|
||||
import { getSignatures } from "@suaveplan/mime/detection/from-magic-bytes/registry";
|
||||
|
||||
function requireSignature(mime: string): void {
|
||||
const found = getSignatures().some(s => s.mime === mime);
|
||||
if (!found) {
|
||||
throw new UnknownSignatureError({
|
||||
context: { signature: mime },
|
||||
});
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Usage: Advanced Example
|
||||
|
||||
```typescript
|
||||
import { UnknownSignatureError } from "@suaveplan/mime/errors";
|
||||
|
||||
// In a plugin registration system
|
||||
function verifySignaturePlugin(pluginMime: string): void {
|
||||
const isRegistered = getSignatures().some(s => s.mime === pluginMime);
|
||||
if (!isRegistered) {
|
||||
throw new UnknownSignatureError({
|
||||
message: `Plugin signature for "${pluginMime}" was not registered`,
|
||||
context: { signature: pluginMime },
|
||||
});
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
### `class UnknownSignatureError extends MimeError`
|
||||
|
||||
**Constructor options:**
|
||||
|
||||
| Field | Type | Description |
|
||||
|-------|------|-------------|
|
||||
| `message?` | `string` | Human-readable error description |
|
||||
| `context?.signature` | `string` | The expected signature identifier |
|
||||
| `cause?` | `unknown` | Underlying cause |
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
This error is not thrown by any built-in `@suaveplan/mime` API — it is provided for consumers who build plugin-style extension systems on top of the signature registry and want explicit registration verification. The `detectFromMagicBytes` function returns `undefined` for unrecognized bytes rather than throwing, keeping the detection path exception-free.
|
||||
|
||||
### Callers
|
||||
|
||||
Thrown by strict magic-byte detection (`detectFromMagicBytesOrThrow`) when the supplied bytes match no registered `Signature`. Non-strict `detectFromMagicBytes` returns `undefined`. The error carries the first few bytes as a hex string in `prefix` so logs capture enough context to diagnose missing signatures without dumping the full payload.
|
||||
|
||||
The `prefix` field is capped at the first 16 bytes of the input so log aggregators never see enormous hex blobs even when the caller passed a multi-megabyte buffer. That cap keeps logs readable and stops a surprising memory amplification path at the telemetry layer.
|
||||
65
docs/foundation/data/mime/src/legacy/get-mime-type.md
Normal file
65
docs/foundation/data/mime/src/legacy/get-mime-type.md
Normal file
@@ -0,0 +1,65 @@
|
||||
# legacy/get-mime-type
|
||||
|
||||
<!-- begin:auto-docs -->
|
||||
_(no submodules)_
|
||||
<!-- end:auto-docs -->
|
||||
## Purpose
|
||||
|
||||
Provides a simplified, backwards-compatible `getMimeType` function that accepts a filename or extension and returns the MIME type string, or a configurable default when no match is found. Designed for codebases migrating from `mime` or `mime-types` npm packages that used a similar single-function API.
|
||||
|
||||
## Features
|
||||
|
||||
- Simple `getMimeType(filename, fallback?)` interface
|
||||
- Returns the MIME type string directly (not wrapped in a result object)
|
||||
- Configurable fallback (default: `"application/octet-stream"`)
|
||||
- Strips path components and handles extensions with or without dots
|
||||
- Zero dependencies beyond the built-in registry
|
||||
|
||||
## Usage: Basic Example
|
||||
|
||||
```typescript
|
||||
import { getMimeType } from "@suaveplan/mime/legacy/get-mime-type";
|
||||
|
||||
console.log(getMimeType("document.pdf")); // "application/pdf"
|
||||
console.log(getMimeType(".ts")); // "video/mp2t" (TypeScript)
|
||||
console.log(getMimeType("unknown.xyz")); // "application/octet-stream"
|
||||
console.log(getMimeType("unknown.xyz", "text/plain")); // "text/plain"
|
||||
```
|
||||
|
||||
## Usage: Advanced Example
|
||||
|
||||
```typescript
|
||||
import { getMimeType } from "@suaveplan/mime/legacy/get-mime-type";
|
||||
|
||||
// Migrate from the 'mime' npm package
|
||||
// Before: mime.getType("photo.jpg") → "image/jpeg"
|
||||
// After: getMimeType("photo.jpg") → "image/jpeg"
|
||||
|
||||
function serveFile(filePath: string, buffer: Uint8Array): Response {
|
||||
const mimeType = getMimeType(filePath, "application/octet-stream");
|
||||
return new Response(buffer, {
|
||||
headers: { "Content-Type": mimeType },
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
### `getMimeType(filename: string, fallback?: string): string`
|
||||
|
||||
| Parameter | Type | Description |
|
||||
|-----------|------|-------------|
|
||||
| `filename` | `string` | Filename, path, or extension (with or without dot). |
|
||||
| `fallback?` | `string` | Returned when no match found (default: `"application/octet-stream"`). |
|
||||
|
||||
**Returns:** MIME type string.
|
||||
|
||||
**Throws:** Never.
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
`getMimeType` is a thin convenience wrapper over `detectFromExtension`. It is intentionally limited to extension-based detection — for production code requiring high-confidence detection (including magic-byte analysis), use the `detect()` function from `@suaveplan/mime/detection/detect` instead. This function is in the `legacy/` directory to signal that new code should prefer the more explicit multi-source API.
|
||||
|
||||
### Deprecation Plan
|
||||
|
||||
`getMimeType` is retained for callers migrating from the pre-`@suaveplan/mime` helpers; new code should call `mimeFromExtension` / `detectFromMagicBytes` directly. A future major release may remove this wrapper — it currently forwards to the real extension-to-MIME map and returns `undefined` when nothing matches, which is the exact contract the old helper advertised.
|
||||
70
docs/foundation/data/mime/src/reverse/reverse.md
Normal file
70
docs/foundation/data/mime/src/reverse/reverse.md
Normal file
@@ -0,0 +1,70 @@
|
||||
# reverse/reverse
|
||||
|
||||
<!-- begin:auto-docs -->
|
||||
_(no submodules)_
|
||||
<!-- end:auto-docs -->
|
||||
## Purpose
|
||||
|
||||
Provides `getMimeExtension` for reverse lookup: given a MIME type string, returns the canonical file extension associated with that type. This is the inverse of extension-based detection — useful for generating filenames, setting `Content-Disposition` headers, and file download naming.
|
||||
|
||||
## Features
|
||||
|
||||
- Returns the canonical (first) extension for any registered MIME type
|
||||
- Returns `undefined` for unknown MIME types (no throws)
|
||||
- Case-insensitive MIME type matching (delegates to registry)
|
||||
- Returns extensions without a leading dot
|
||||
- O(1) lookup via the registry's forward map
|
||||
|
||||
## Usage: Basic Example
|
||||
|
||||
```typescript
|
||||
import { getMimeExtension } from "@suaveplan/mime/reverse/reverse";
|
||||
|
||||
console.log(getMimeExtension("image/png")); // "png"
|
||||
console.log(getMimeExtension("application/pdf")); // "pdf"
|
||||
console.log(getMimeExtension("text/javascript")); // "js"
|
||||
console.log(getMimeExtension("unknown/type")); // undefined
|
||||
```
|
||||
|
||||
## Usage: Advanced Example
|
||||
|
||||
```typescript
|
||||
import { getMimeExtension } from "@suaveplan/mime/reverse/reverse";
|
||||
|
||||
function buildFilename(baseName: string, mimeType: string): string {
|
||||
const ext = getMimeExtension(mimeType);
|
||||
if (!ext) return baseName; // Unknown type — keep original name
|
||||
// Avoid double extension
|
||||
return baseName.endsWith(`.${ext}`) ? baseName : `${baseName}.${ext}`;
|
||||
}
|
||||
|
||||
// Content-Disposition header generation
|
||||
function contentDisposition(mimeType: string, name: string): string {
|
||||
const ext = getMimeExtension(mimeType);
|
||||
const filename = ext ? `${name}.${ext}` : name;
|
||||
return `attachment; filename="${filename}"`;
|
||||
}
|
||||
|
||||
console.log(contentDisposition("application/pdf", "report"));
|
||||
// 'attachment; filename="report.pdf"'
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
### `getMimeExtension(mime: string): string | undefined`
|
||||
|
||||
| Parameter | Type | Description |
|
||||
|-----------|------|-------------|
|
||||
| `mime` | `string` | MIME type string (e.g., `"image/png"`). |
|
||||
|
||||
**Returns:** The canonical file extension string without a leading dot, or `undefined` if the MIME type is not in the database.
|
||||
|
||||
**Throws:** Never.
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
The "canonical" extension is the first element of the `extensions` array in the `MimeEntry`. The built-in database places the most common extension first by convention (e.g., `"jpeg"` before `"jpg"` for `image/jpeg`). Callers that need all extensions for a type should use `getEntryByMime` directly from `@suaveplan/mime/db/registry` and access `entry.extensions`.
|
||||
|
||||
### Lookup Tables
|
||||
|
||||
The reverse index maps MIME string → preferred extension, MIME string → alias list, and alias → canonical MIME. Tables are precomputed at module load from the same curated IANA database the forward lookup uses, so the two directions can never drift. Lookups are O(1) against frozen `Map` instances with zero runtime cost after first import.
|
||||
61
docs/foundation/data/mime/src/telemetry/meters.md
Normal file
61
docs/foundation/data/mime/src/telemetry/meters.md
Normal file
@@ -0,0 +1,61 @@
|
||||
# telemetry/meters
|
||||
|
||||
<!-- begin:auto-docs -->
|
||||
_(no submodules)_
|
||||
<!-- end:auto-docs -->
|
||||
## Purpose
|
||||
|
||||
Initializes OpenTelemetry metrics instruments for `@suaveplan/mime`. Provides counters and histograms for tracking MIME detection operations, cache hits, and processing durations. Callers supply their own `Meter` instance so this module has no direct dependency on any specific OTel SDK implementation.
|
||||
|
||||
## Features
|
||||
|
||||
- Lazy initialization — instruments are created once on first `initMeters` call
|
||||
- Idempotent — subsequent `initMeters` calls after the first are no-ops
|
||||
- Counters: detection calls by source, detection failures, charset sniff operations
|
||||
- Histograms: detection latency, stream read bytes
|
||||
- No OTel SDK dependency — accepts any `Meter`-compatible interface
|
||||
- Zero-cost no-op path when telemetry is not initialized
|
||||
|
||||
## Usage: Basic Example
|
||||
|
||||
```typescript
|
||||
import { initMeters } from "@suaveplan/mime/telemetry/meters";
|
||||
import { metrics } from "@opentelemetry/api";
|
||||
|
||||
// Call once at application startup
|
||||
const meter = metrics.getMeter("@suaveplan/mime");
|
||||
initMeters(meter);
|
||||
|
||||
// All subsequent MIME operations will record metrics automatically
|
||||
```
|
||||
|
||||
## Usage: Advanced Example
|
||||
|
||||
```typescript
|
||||
import { initMeters, getMimeMeters } from "@suaveplan/mime/telemetry/meters";
|
||||
import { metrics } from "@opentelemetry/api";
|
||||
|
||||
// Initialize with a named meter for better grouping in dashboards
|
||||
initMeters(metrics.getMeter("@suaveplan/mime", "1.0.0"));
|
||||
|
||||
// Access instruments directly for custom recording
|
||||
const meters = getMimeMeters();
|
||||
meters?.detectionCounter.add(1, {
|
||||
source: "magic-bytes",
|
||||
mime: "image/png",
|
||||
});
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
### `initMeters(meter: Meter): void`
|
||||
|
||||
Initializes the metric instruments using the provided OTel `Meter`. Idempotent — only the first call has effect.
|
||||
|
||||
### `getMimeMeters(): MimeMeters | undefined`
|
||||
|
||||
Returns the initialized meters object, or `undefined` if `initMeters` has not been called.
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
The idempotency guard uses a module-level `let meters: MimeMeters | undefined` variable checked at the top of `initMeters`. This avoids re-creating instruments if `initMeters` is accidentally called multiple times (e.g., from multiple entry points in a large application). The no-op fallback (`getMimeMeters()` returning `undefined`) means callers can use optional chaining without any conditional initialization checks: `getMimeMeters()?.detectionCounter.add(1, attrs)`.
|
||||
Reference in New Issue
Block a user