first commit
This commit is contained in:
@@ -0,0 +1,83 @@
|
||||
# FileSourceAdapter
|
||||
|
||||
<!-- begin:auto-docs -->
|
||||
_(no submodules)_
|
||||
<!-- end:auto-docs -->
|
||||
|
||||
## Purpose
|
||||
|
||||
`FileSourceAdapter` is the server-tier entry point for reading audio files
|
||||
into the universal `AudioDocument` model. It owns the path-sanitisation
|
||||
boundary, dispatches to `@suaveplan/audio-codecs` `decodeSource` for
|
||||
format detection and decode, and exposes both buffered (`load`,
|
||||
`loadMany`) and streaming (`createReadStream`) modes through a single
|
||||
injectable `FsLike` seam. The adapter implements no DSP itself — every
|
||||
byte ends up routed through the universal codec layer.
|
||||
|
||||
## Features
|
||||
|
||||
- Buffered `load(path, opts?)` returns an `AudioDocument` from a local
|
||||
path or an `http`/`https` URL.
|
||||
- `loadMany(paths, opts?)` resolves a `Promise.all` over `load`,
|
||||
preserving input order regardless of decode completion order.
|
||||
- `createReadStream(path)` returns a WHATWG `ReadableStream<Uint8Array>`
|
||||
backed by the injected `FsLike` (or `node:fs.createReadStream`).
|
||||
- Zod-validated options at every entry point; failures throw
|
||||
`AUDIO_ENGINE_SERVER.INVALID_OPTIONS`.
|
||||
- `sanitizePath` rejects empty / null-byte / `..`-traversal paths against
|
||||
the optional `rootDir` boundary before any filesystem call is issued.
|
||||
- `AbortSignal` propagation — pre-aborted signals short-circuit with
|
||||
`AUDIO_ENGINE_SERVER.ABORTED`; mid-load aborts surface the same code via
|
||||
the codec layer.
|
||||
- OpenTelemetry instrumentation — span `file.load`, counter
|
||||
`audio_engine_bytes_read_total`, histogram
|
||||
`audio_engine_load_duration_ms`. Inert without an exporter.
|
||||
|
||||
## Usage: Basic
|
||||
|
||||
```ts
|
||||
import { FileSourceAdapter } from "@suaveplan/audio-engine-server";
|
||||
|
||||
const source = new FileSourceAdapter();
|
||||
const doc = await source.load("/tmp/sample.wav");
|
||||
console.log(doc.sampleRate, doc.channels, doc.sourceLength);
|
||||
```
|
||||
|
||||
## Usage: Advanced
|
||||
|
||||
```ts
|
||||
import { FileSourceAdapter, defaultFs } from "@suaveplan/audio-engine-server";
|
||||
|
||||
const source = new FileSourceAdapter({
|
||||
fs: defaultFs,
|
||||
rootDir: "/var/audio",
|
||||
registry: customCodecRegistry,
|
||||
});
|
||||
|
||||
const ctrl = new AbortController();
|
||||
setTimeout(() => ctrl.abort(), 5000);
|
||||
const docs = await source.loadMany(
|
||||
["a.wav", "b.flac", "c.mp3"],
|
||||
{ signal: ctrl.signal }
|
||||
);
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
- `new FileSourceAdapter(options?: FileSourceOptions)` — `fs`, `fetch`,
|
||||
`registry`, `rootDir` all optional.
|
||||
- `load(path, opts?): Promise<AudioDocument>`
|
||||
- `loadMany(paths, opts?): Promise<AudioDocument[]>`
|
||||
- `createReadStream(path): ReadableStream<Uint8Array>`
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
`load` distinguishes URL inputs via `isHttpUrl`; URL inputs are fetched
|
||||
through the injected `fetch` and skip filesystem sanitisation. Local
|
||||
paths pass through `sanitizePath(path, rootDir)` before any `FsLike`
|
||||
call, so a path-traversal attempt throws `INVALID_PATH` before the
|
||||
filesystem is touched. `ENOENT` from the underlying `readFile` is mapped
|
||||
to `FILE_NOT_FOUND`; any other read error becomes `IO_FAILED`. Decode
|
||||
failures from the codec layer are re-thrown as `DECODE_FAILED` unless the
|
||||
inner error reports a stream abort, in which case the engine maps it to
|
||||
`AUDIO_ENGINE_SERVER.ABORTED` to preserve the signal-aborted contract.
|
||||
Reference in New Issue
Block a user