# `detect/detect-format` Format detection and source-resolution helpers — the runtime-gated entry point that turns any `Source` (string path, URL, ArrayBuffer, Uint8Array, ReadableStream) into a header slice plus an optional detected format name. _(no submodules)_ ## Purpose The package handles five concrete `Source` shapes; each needs a different platform-specific resolution strategy. Without a single helper, every public entry point (`decodeSource`, `encodeTo`, `progressiveDecode`) would re-implement the same `string | URL | ArrayBuffer | Uint8Array | ReadableStream` switch — duplicating wiring and risking divergent behaviour for the edge cases. This module centralises it. The detection delegates magic-byte sniffing to `@suaveplan/audio-metadata`'s `detectAudioFormat` — the same function used by every other package that needs to identify audio bytes — so the registry's `detect` and the entry-point `detectSource` always agree. ## Features - `Source` type union — `string | URL | ArrayBuffer | Uint8Array | ReadableStream`. - `DetectResult` shape — `{ format: string | undefined; bytes?: Uint8Array; path?: string; reader?: ReadableStreamDefaultReader }`. The optional fields tell the caller what kind of resolution happened so they can continue draining the source efficiently. - `detectSource(source)` — reads at most `HEADER_SIZE` (256 KiB) bytes from the source head, sniffs the format, and returns either the detected format name or `undefined` for unknown content. Never throws for unknown content — only throws `INVALID_SOURCE` for a source value that is not a recognised variant or a fetch that returned no body. - `resolveSource(source)` — drains the source completely into a single `Uint8Array`. Used by `decodeSource` to materialise the full payload before dispatch. - `HEADER_SIZE` — exported constant set to `256 * 1024`. Reading exactly this many bytes keeps detection cheap even for multi-GB files while being large enough to cover every realistic header layout (ID3v2 with embedded artwork, AIFF with large COMM-adjacent comment chunks, etc). ## Resolution rules - `string` starting with `http://` / `https://` / `data:` / `blob:`, or any `URL` instance → resolved via `fetch()`. Header read via the response stream reader, which is returned to the caller for continued draining. - `string` starting with `file://` → resolved via dynamic `import('node:fs/promises')`. Returns the resolved file path so the caller can perform random-access reads. - Other bare `string` → on a server runtime (process.versions.node is a string) resolves via `fs/promises.readFile`; in a browser runtime resolves via `fetch()`. - `ArrayBuffer` / `Uint8Array` → header is the first 256 KiB slice; no platform call. - `ReadableStream` → reader drains until 256 KiB observed; the original reader is returned for continued draining. ## Usage: Basic Example ```ts import { detectSource } from "@suaveplan/audio-codecs/detect/detect-format"; const result = await detectSource(bytes); if (result.format === "wav") { decodeWav(result.bytes!); } ``` ## Usage: Advanced Example Resolve a remote URL once and reuse the detection result + stream reader without re-fetching: ```ts import { detectSource, resolveSource } from "@suaveplan/audio-codecs/detect/detect-format"; const result = await detectSource("https://cdn.example/audio.flac"); if (result.format === "flac") { // We have the first 256 KiB cached on result.bytes, and the rest // is available via result.reader. const rest = await drainReader(result.reader!); const full = concat(result.bytes!, rest); return decodeFlac(full); } // resolveSource fully drains regardless of source shape. const buf = await resolveSource("file:///srv/audio/in.wav"); ``` ## API Reference - `type Source = string | URL | ArrayBuffer | Uint8Array | ReadableStream` - `interface DetectResult { format: string | undefined; bytes?: Uint8Array; path?: string; reader?: ReadableStreamDefaultReader }` - `const HEADER_SIZE: number` — `256 * 1024` - `async function detectSource(source: Source): Promise` — throws `AUDIO_CODECS.INVALID_SOURCE` for an unrecognised source value or when `fetch()` returns a body-less response. - `async function resolveSource(source: Source): Promise` — throws `AUDIO_CODECS.INVALID_SOURCE` for an unrecognised source. ## Implementation Notes - `isServerRuntime()` is a closure over `typeof process !== "undefined" && typeof process.versions?.node === "string"`. The function exists so the runtime branch is not evaluated at module-load time — the package stays universal with no static `node:fs` import. - The `node:fs/promises` import is **always** inside a runtime-gated dynamic `import()` — never a top-level import. This keeps the package importable from browser bundles without bundler config tweaks. - The browser-only `fetch()` fallback paths (lines 154-156 in `detectSource`, 214-216 in `resolveSource`) are unreachable under Node tests; structural coverage of that branch requires a happy-dom or browser-target test harness (a future integration suite). - The `URL` instance branch and the `http://` / `https://` / `data:` / `blob:` string branch both route through `fetchHeaderViaFetch` — no logic is duplicated. - `readHeaderFromStream` keeps reading until at least `HEADER_SIZE` bytes are observed OR the stream ends. The resulting buffer may be larger than `HEADER_SIZE` because chunks arrive whole; that's intentional — over-read is cheap and avoids fragmenting the caller's understanding of what bytes were consumed.