# `factory/create-elevenlabs-voice` Composition root for the `@suaveplan/voice` engine adapters that target ElevenLabs. One call, one shared transport, five voice engines wired up. _(no submodules)_ ## Purpose `@suaveplan/voice-elevenlabs` exposes five domain adapters (TTS, STT, voices, sound-effects, voice-isolation). Each needs an `ElevenLabsClient` from `@suaveplan/third-party-elevenlabs`. Without a factory, every consumer would manually construct each adapter and remember to share the same client between them — duplicating the same boilerplate over and over and leaving room for subtle bugs (one adapter using a different timeout, one created on every request and leaking event listeners). `createElevenLabsVoice` makes the right thing easy: either supply a pre-constructed client (so the same transport can be shared with `ElevenLabsModelCatalog` or `ElevenLabsSpeechToSpeech` from `@suaveplan/third-party-elevenlabs`), or pass the same `ElevenLabsClientOptions` a fresh client would accept, and get all five voice engines back as a typed bundle. ## Features - Single function: `createElevenLabsVoice(options)`. - Returns `ElevenLabsVoiceBundle` — `{ client, tts, stt, voices, effects, isolation }`. - Every engine shares the same `client`, which means they share the same retry policy, timeout, event bus, logger, and observability context. - The `client` field is exposed so consumers can issue ad-hoc requests (e.g. account-info endpoints not covered by the engines) against the same transport, or wire it into a sibling provider catalogue. - `options.client` overrides every transport-construction field; pass a client explicitly to compose with surfaces outside the voice domain. ## Usage: Basic Example ```ts import { createElevenLabsVoice } from "@suaveplan/voice-elevenlabs"; const { tts, voices } = createElevenLabsVoice({ apiKey: process.env.ELEVENLABS_KEY!, }); const all = await voices.list(); const result = await tts.synthesize({ text: "hello", voiceId: all.voices[0]!.id, }); ``` ## Usage: Advanced Example ```ts import { ElevenLabsClient, ElevenLabsModelCatalog, } from "@suaveplan/third-party-elevenlabs"; import { createElevenLabsVoice } from "@suaveplan/voice-elevenlabs"; // One transport shared by both the voice engines AND the model catalogue. const client = new ElevenLabsClient({ apiKey: process.env.ELEVENLABS_KEY!, baseUrl: "https://eu.api.elevenlabs.io", timeoutMs: 60_000, maxRetries: 5, initialRetryDelayMs: 250, fetchImpl: instrumentedFetch, webSocketImpl: WS, // 'ws' on Node events: appEventBus, logger: appLogger, userAgent: "my-app/1.4.2", defaultHeaders: { "x-tenant": "acme" }, }); const voice = createElevenLabsVoice({ client }); const models = new ElevenLabsModelCatalog(client); const [voiceList, modelList] = await Promise.all([ voice.voices.list(), models.list(), ]); ``` ## API Reference - `interface ElevenLabsVoiceBundle { client, tts, stt, voices, effects, isolation }` - `type CreateElevenLabsVoiceOptions = { client: ElevenLabsClient } | (ElevenLabsClientOptions & { client?: undefined })` - `function createElevenLabsVoice(options: CreateElevenLabsVoiceOptions): ElevenLabsVoiceBundle` Each field is the corresponding `@suaveplan/voice` engine implementation instantiated against the shared `client`. ## Implementation Notes - When `options.client` is present it is reused as-is; every other field on `options` is ignored. This is by design so consumers can wire their own transport (e.g. an `ElevenLabsClient` already configured for an EU shard, a private base URL, or a shared event bus) and the voice adapters drop straight in. - When `options.client` is absent the factory delegates to `new ElevenLabsClient(options)` so every `ElevenLabsClientOptions` field is honoured. Invalid options surface as the same construction errors a direct `new ElevenLabsClient(opts)` would raise. - The factory does not validate `apiKey` — passing an empty string surfaces as a 401 / `VOICE.PROVIDER.NOT_CONFIGURED` on the first call rather than a synchronous throw. - The bundle is a plain object literal; consumers can destructure freely (`const { tts } = createElevenLabsVoice(...)`) without losing the shared client reference. - Stateful adapters (none currently) would still be instantiated per call. Call `createElevenLabsVoice` once per process / per long-lived consumer, not once per request.