Initial Commit
This commit is contained in:
@@ -0,0 +1,90 @@
|
||||
# `extension.ts` — URI → file-extension extraction
|
||||
|
||||
## Purpose
|
||||
|
||||
The loader dispatches decoders by file extension. This module owns the
|
||||
canonicalisation step: given a URI string (which may be `bundle://`,
|
||||
`https://`, `drive://`, plain path-based, or `fallback://?u=…&u=…`),
|
||||
return the lowercase extension that decides which `AudioDecoder` runs.
|
||||
Cross-format `fallback://` URIs are explicitly rejected with
|
||||
`DECODER_UNAVAILABLE` — a single load must pick a single decoder.
|
||||
|
||||
## Features
|
||||
|
||||
- Universal across `bundle://`, `https://`, `drive://`, and arbitrary
|
||||
path-based URIs.
|
||||
- Recursive handling for `fallback://` URIs — every candidate is
|
||||
re-extracted and the result must agree across candidates.
|
||||
- Query and fragment components are stripped before extension extraction.
|
||||
- Throws `AUDIO_IR_LOADER.DECODER_UNAVAILABLE` on every failure mode
|
||||
(missing extension, parse error, candidate disagreement).
|
||||
|
||||
## Usage: Basic
|
||||
|
||||
```ts
|
||||
import { extractExtension } from "@suaveplan/audio-ir-loader";
|
||||
|
||||
extractExtension("bundle://x/foo.wav"); // → "wav"
|
||||
extractExtension("https://cdn.example.com/path/clip.WAV?v=2"); // → "wav"
|
||||
extractExtension("fallback://?u=bundle%3A%2F%2Fx%2Fa.wav&u=https%3A%2F%2Fh%2Fa.wav");
|
||||
// → "wav"
|
||||
```
|
||||
|
||||
## Usage: Advanced
|
||||
|
||||
The loader uses this helper at the start of every `load(id)` to pick the
|
||||
right decoder before any I/O happens:
|
||||
|
||||
```ts
|
||||
import {
|
||||
AUDIO_IR_LOADER_CODES,
|
||||
AudioIrLoaderError,
|
||||
extractExtension,
|
||||
} from "@suaveplan/audio-ir-loader";
|
||||
|
||||
function pickDecoder<D>(decoders: Record<string, D>, uri: string, id: string): D {
|
||||
const ext = extractExtension(uri, id);
|
||||
const decoder = decoders[ext];
|
||||
if (decoder === undefined) {
|
||||
throw new AudioIrLoaderError(
|
||||
AUDIO_IR_LOADER_CODES.DECODER_UNAVAILABLE,
|
||||
`no decoder registered for '.${ext}'`,
|
||||
{ id, extension: ext }
|
||||
);
|
||||
}
|
||||
return decoder;
|
||||
}
|
||||
```
|
||||
|
||||
Cross-format fallback URIs surface as a single `DECODER_UNAVAILABLE`
|
||||
throw with the first extension preserved on the error's `extension`
|
||||
discriminant, which makes the diagnostic easy to act on in the manifest
|
||||
authoring step.
|
||||
|
||||
## API Reference
|
||||
|
||||
### `extractExtension(uri: string, id?: string): string`
|
||||
|
||||
Returns the lowercase extension. The optional `id` is forwarded to the
|
||||
`AudioIrLoaderError.id` discriminant on every throw so observers can
|
||||
filter by IR identifier.
|
||||
|
||||
Throws `AUDIO_IR_LOADER.DECODER_UNAVAILABLE` when:
|
||||
|
||||
- the URI carries no `.<ext>` segment after the last `/`,
|
||||
- the URI is `fallback://` and is not parseable as a URL,
|
||||
- the URI is `fallback://` with zero `u=` candidates,
|
||||
- the URI is `fallback://` and candidates disagree on extension.
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
The `bundle://x/y.wav` URI form is technically not a valid RFC 3986
|
||||
authority-less URI but every consumer of `audio-ir-loader` already deals
|
||||
with it through `storage-uri-plugin-bundle`'s own parser; the
|
||||
path-stripping in this module is intentionally permissive about authority
|
||||
shape so callers get a consistent dispatch result regardless of whether
|
||||
the path-component is `//host/key.wav` or `:/path/key.wav`. The
|
||||
`fallback://` walk delegates to `extractExtension` recursively, so any
|
||||
future URI scheme that itself wraps candidates (e.g. a hypothetical
|
||||
`mirror://`) would only need to extend `FALLBACK_PREFIX` matching and
|
||||
fall through to the same recursion.
|
||||
Reference in New Issue
Block a user