Files
2026-05-27 23:47:53 +01:00

3.3 KiB

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

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:

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.