first commit
This commit is contained in:
111
docs/media/audio/voice/src/voices/voices.md
Normal file
111
docs/media/audio/voice/src/voices/voices.md
Normal file
@@ -0,0 +1,111 @@
|
||||
# `voices/voices`
|
||||
|
||||
The voice repository interface for `@suaveplan/voice` — read-only listing and
|
||||
lookup of voices available to the active provider. Defines the
|
||||
`VoiceRepository` contract every voice provider adapter must implement, plus
|
||||
the supporting Zod schemas and a runtime type guard.
|
||||
|
||||
<!-- begin:auto-docs -->
|
||||
_(no submodules)_
|
||||
<!-- end:auto-docs -->
|
||||
|
||||
## Purpose
|
||||
|
||||
Provider voice catalogues differ in pagination scheme (cursor / page-token /
|
||||
offset), filter taxonomy, and "shared" vs "owned" voice splits. This module
|
||||
locks the canonical surface — `list(options?)` returning a `VoiceList`
|
||||
envelope and `get(voiceId)` returning `Voice | null`. Provider adapters
|
||||
collapse their native responses onto these shapes, so consumers can browse
|
||||
voices without learning each backend's quirks.
|
||||
|
||||
## Features
|
||||
|
||||
- `VoiceRepository` interface with `providerName`, `list`, and `get`.
|
||||
- `list(options?)` returns a `VoiceList` envelope (`voices`, `nextPageToken`,
|
||||
`hasMore`).
|
||||
- `get(voiceId)` resolves to `Voice | null` — adapters MUST map upstream
|
||||
not-found codes to `null` rather than throwing.
|
||||
- Zod schemas re-exporting `ListVoicesOptionsSchema` (as
|
||||
`VoiceRepositoryListOptionsSchema`), `VoiceListSchema` (as
|
||||
`VoiceRepositoryListResultSchema`), and a `Voice | null` union for the
|
||||
`get` result.
|
||||
- Runtime type guard `isVoiceRepository`.
|
||||
|
||||
## Usage: Basic Example
|
||||
|
||||
```ts
|
||||
import type { VoiceRepository } from "@suaveplan/voice/voices/voices";
|
||||
|
||||
async function findVoiceByName(
|
||||
voices: VoiceRepository,
|
||||
name: string,
|
||||
) {
|
||||
const page = await voices.list({ search: name, pageSize: 25 });
|
||||
return page.voices[0] ?? null;
|
||||
}
|
||||
```
|
||||
|
||||
## Usage: Advanced Example
|
||||
|
||||
Async-iterate every voice across all pages, with a guarded language filter:
|
||||
|
||||
```ts
|
||||
import type {
|
||||
ListVoicesOptions,
|
||||
Voice,
|
||||
VoiceRepository,
|
||||
} from "@suaveplan/voice";
|
||||
|
||||
async function* allEnglishVoices(
|
||||
voices: VoiceRepository,
|
||||
): AsyncIterable<Voice> {
|
||||
let token: string | undefined;
|
||||
while (true) {
|
||||
const opts: ListVoicesOptions = { languageCode: "en", pageSize: 100 };
|
||||
if (token !== undefined) opts.pageToken = token;
|
||||
const page = await voices.list(opts);
|
||||
for (const v of page.voices) yield v;
|
||||
if (!page.hasMore || page.nextPageToken === undefined) break;
|
||||
token = page.nextPageToken;
|
||||
}
|
||||
}
|
||||
|
||||
async function lookupOrFail(voices: VoiceRepository, id: string): Promise<Voice> {
|
||||
const v = await voices.get(id);
|
||||
if (v === null) throw new Error(`voice ${id} not found`);
|
||||
return v;
|
||||
}
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
- `VoiceRepository` — interface with:
|
||||
- `providerName: string` — stable identifier (e.g. `'elevenlabs'`).
|
||||
- `list(options?: ListVoicesOptions): Promise<VoiceList>`.
|
||||
- `get(voiceId: string): Promise<Voice | null>`.
|
||||
- `VoiceRepositoryListOptionsSchema` — re-export of `ListVoicesOptionsSchema`
|
||||
for ergonomic discovery.
|
||||
- `VoiceRepositoryListResultSchema` — alias of `VoiceListSchema`.
|
||||
- `VoiceRepositoryGetResultSchema` — Zod union of `VoiceSchema` and
|
||||
`z.null()`.
|
||||
- `isVoiceRepository(value): value is VoiceRepository` — duck-type guard.
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
- The `null`-on-not-found contract for `get` is deliberate: callers commonly
|
||||
check existence before falling back to a default voice. Throwing would
|
||||
force a try/catch around every probe. Adapters that receive a 404 from
|
||||
upstream MUST map it to `null`; any other error is propagated as a
|
||||
`VoiceError` subclass.
|
||||
- Voice cloning is intentionally absent. ElevenLabs supports instant /
|
||||
professional cloning; OpenAI does not at all; Azure has its own
|
||||
"Custom Neural Voice" workflow with a multi-week verification process.
|
||||
A future `VoiceCloner` interface will land in a dedicated subpath when the
|
||||
design is settled and ≥ 2 providers are mapped onto it.
|
||||
- The schemas in `voices.schemas.ts` are re-exports / aliases of the
|
||||
authoritative schemas in `types/types.ts`. They exist to give callers
|
||||
ergonomic discovery without forcing them to know that `ListVoicesOptions`
|
||||
and `VoiceList` are shared with non-repository consumers.
|
||||
- The runtime guard checks the three required members but does not enforce
|
||||
argument shapes on the methods. Callers needing stronger validation should
|
||||
call `await voices.list({})` against a fixture and assert the response.
|
||||
Reference in New Issue
Block a user