# `client/python-audio-service.ts` — `PythonAudioService` _(no submodules)_ ## Purpose `PythonAudioService` is the typed HTTP client TypeScript and browser callers use to talk to the Python-tier audio fingerprinting service. It wraps `fetch`, handles multipart `FormData` uploads, propagates `AbortController`-based timeouts, and translates the FastAPI error envelope (`{detail: string}`) into well-typed JavaScript `Error` instances with informative messages. The class is the only runtime export of the `@suaveplan/audio-fingerprint/client` subpath, and it is also re-exported from the package root. The client deliberately depends on nothing beyond the platform `fetch`, `FormData`, and `AbortController` APIs. Browsers (Chrome, Firefox, Safari 14+, Edge) and modern server runtimes (Node 18+, Bun, Deno) all expose these, so the same code runs unchanged in every consumer tier. There is no axios, no isomorphic-fetch polyfill, and no transitive dependency closure to audit. ## Features - Configurable `baseUrl` (default `http://localhost:8000`) and `timeout` in ms (default `30000`). - `healthCheck()` — `GET /`, returns the typed `HealthCheckResponse`. - `fingerprint(req)` — `POST /fingerprint` with `file` plus optional `peak_threshold` / `fan_value`. - `match(req)` — `POST /match` with `query_file`, `reference_file`, plus optional `peak_threshold` / `fan_value` / `min_matches`. - `AbortController`-based timeouts; on expiry the client rejects with `Error("Request timeout after Nms: ")`. - Error parsing: HTTP-non-OK responses are parsed as JSON; if the body is `{detail: "..."}`, that string becomes the `Error.message`. Otherwise the client falls back to `HTTP : `. - Non-`Error` thrown values from `fetch` are normalised to `Error("Unknown error occurred: ")`. ## Usage: Basic ```ts import { PythonAudioService } from "@suaveplan/audio-fingerprint/client"; const client = new PythonAudioService(); const health = await client.healthCheck(); // { status: "ok", service: "audio-fingerprinting" } ``` ## Usage: Advanced ```ts import { PythonAudioService } from "@suaveplan/audio-fingerprint"; import type { FingerprintResponse, MatchResponse, } from "@suaveplan/audio-fingerprint/types"; const client = new PythonAudioService({ baseUrl: "https://audio-fp.internal.example.com", timeout: 60_000, }); async function identify(query: File, reference: File): Promise { try { const queryFingerprint: FingerprintResponse = await client.fingerprint({ file: query, peak_threshold: 0.55, fan_value: 18, }); // ... persist queryFingerprint to corpus if needed return await client.match({ query_file: query, reference_file: reference, peak_threshold: 0.55, fan_value: 18, min_matches: 25, }); } catch (error) { // The thrown message is either the FastAPI `detail`, a status-text // fallback, or the timeout / unknown-error envelope. console.error("identification failed:", (error as Error).message); throw error; } } ``` ## API Reference ### Constructor ```ts constructor(config?: AudioFingerprintConfig) ``` | Field | Type | Default | Description | |---|---|---|---| | `config.baseUrl` | `string` | `"http://localhost:8000"` | Base URL of the FastAPI service. | | `config.timeout` | `number` | `30000` | Per-request timeout in milliseconds. | ### Methods | Method | Signature | Throws | |---|---|---| | `healthCheck` | `() => Promise` | `Error` on HTTP error, timeout, or network failure. | | `fingerprint` | `(req: FingerprintRequest) => Promise` | `Error` on HTTP error, timeout, or network failure. | | `match` | `(req: MatchRequest) => Promise` | `Error` on HTTP error, timeout, or network failure. | ### Error envelope | Cause | `Error.message` | |---|---| | Timeout | `Request timeout after : ` | | HTTP non-OK with parseable `{detail: string}` body | `` | | HTTP non-OK with non-JSON or `detail`-missing body | `HTTP : ` | | `fetch` throws a non-`Error` value | `Unknown error occurred: ` | | `fetch` throws a non-Abort `Error` | the original error is re-raised verbatim | ## Implementation Notes - The timeout is implemented with a `setTimeout` that calls `controller.abort()`; the timer is cleared both on success and on failure (try/catch + redundant `clearTimeout` after the `await`). This avoids leaking timers in long-lived browsers tabs. - `AbortError` is detected by `error.name === "AbortError"` rather than by `instanceof DOMException` to remain runtime-agnostic (DOMException is browser-only). - Optional form fields are only appended when the caller supplies them; the FastAPI defaults remain authoritative. - The error-body JSON parse is wrapped in a try/catch with no rethrow inside the catch — a parse failure should NOT mask the original HTTP error, only fall through to the status-text fallback. - Type-only imports from `../types/audio-fingerprint.types.js` are used so the client carries zero runtime cost from the type definitions. - The client has no peer dependency on any framework; consumers wrap it in their own React/Vue/Svelte hooks as appropriate. The sibling `@suaveplan/react-audio-fingerprint` package's `python-backend` subpath is the canonical React adapter.