5.4 KiB
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(defaulthttp://localhost:8000) andtimeoutin ms (default30000). healthCheck()—GET /, returns the typedHealthCheckResponse.fingerprint(req)—POST /fingerprintwithfileplus optionalpeak_threshold/fan_value.match(req)—POST /matchwithquery_file,reference_file, plus optionalpeak_threshold/fan_value/min_matches.AbortController-based timeouts; on expiry the client rejects withError("Request timeout after Nms: <endpoint>").- Error parsing: HTTP-non-OK responses are parsed as JSON; if the body is
{detail: "..."}, that string becomes theError.message. Otherwise the client falls back toHTTP <status>: <statusText>. - Non-
Errorthrown values fromfetchare normalised toError("Unknown error occurred: <string>").
Usage: Basic
import { PythonAudioService } from "@suaveplan/audio-fingerprint/client";
const client = new PythonAudioService();
const health = await client.healthCheck();
// { status: "ok", service: "audio-fingerprinting" }
Usage: Advanced
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<MatchResponse> {
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
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<HealthCheckResponse> |
Error on HTTP error, timeout, or network failure. |
fingerprint |
(req: FingerprintRequest) => Promise<FingerprintResponse> |
Error on HTTP error, timeout, or network failure. |
match |
(req: MatchRequest) => Promise<MatchResponse> |
Error on HTTP error, timeout, or network failure. |
Error envelope
| Cause | Error.message |
|---|---|
| Timeout | Request timeout after <Nms>: <endpoint> |
HTTP non-OK with parseable {detail: string} body |
<detail> |
HTTP non-OK with non-JSON or detail-missing body |
HTTP <status>: <statusText> |
fetch throws a non-Error value |
Unknown error occurred: <stringified value> |
fetch throws a non-Abort Error |
the original error is re-raised verbatim |
Implementation Notes
- The timeout is implemented with a
setTimeoutthat callscontroller.abort(); the timer is cleared both on success and on failure (try/catch + redundantclearTimeoutafter theawait). This avoids leaking timers in long-lived browsers tabs. AbortErroris detected byerror.name === "AbortError"rather than byinstanceof DOMExceptionto 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.jsare 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-fingerprintpackage'spython-backendsubpath is the canonical React adapter.