subformer 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js ADDED
@@ -0,0 +1,442 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/index.ts
21
+ var index_exports = {};
22
+ __export(index_exports, {
23
+ AuthenticationError: () => AuthenticationError,
24
+ NotFoundError: () => NotFoundError,
25
+ RateLimitError: () => RateLimitError,
26
+ Subformer: () => Subformer,
27
+ SubformerError: () => SubformerError,
28
+ ValidationError: () => ValidationError
29
+ });
30
+ module.exports = __toCommonJS(index_exports);
31
+
32
+ // src/errors.ts
33
+ var SubformerError = class extends Error {
34
+ constructor(message, options) {
35
+ super(message);
36
+ this.name = "SubformerError";
37
+ this.statusCode = options?.statusCode;
38
+ this.code = options?.code;
39
+ this.data = options?.data;
40
+ }
41
+ };
42
+ var AuthenticationError = class extends SubformerError {
43
+ constructor(message = "Invalid or missing API key") {
44
+ super(message, { statusCode: 401, code: "UNAUTHORIZED" });
45
+ this.name = "AuthenticationError";
46
+ }
47
+ };
48
+ var NotFoundError = class extends SubformerError {
49
+ constructor(message = "Resource not found") {
50
+ super(message, { statusCode: 404, code: "NOT_FOUND" });
51
+ this.name = "NotFoundError";
52
+ }
53
+ };
54
+ var RateLimitError = class extends SubformerError {
55
+ constructor(message = "Rate limit exceeded") {
56
+ super(message, { statusCode: 429, code: "RATE_LIMIT_EXCEEDED" });
57
+ this.name = "RateLimitError";
58
+ }
59
+ };
60
+ var ValidationError = class extends SubformerError {
61
+ constructor(message, data) {
62
+ super(message, { statusCode: 400, code: "BAD_REQUEST", data });
63
+ this.name = "ValidationError";
64
+ }
65
+ };
66
+
67
+ // src/client.ts
68
+ var DEFAULT_BASE_URL = "https://api.subformer.com/v1";
69
+ var DEFAULT_TIMEOUT = 3e4;
70
+ var Subformer = class {
71
+ constructor(options) {
72
+ this.apiKey = options.apiKey;
73
+ this.baseUrl = (options.baseUrl ?? DEFAULT_BASE_URL).replace(/\/$/, "");
74
+ this.timeout = options.timeout ?? DEFAULT_TIMEOUT;
75
+ }
76
+ async request(method, path, options) {
77
+ const url = new URL(`${this.baseUrl}${path}`);
78
+ if (options?.params) {
79
+ for (const [key, value] of Object.entries(options.params)) {
80
+ if (value !== void 0) {
81
+ url.searchParams.set(key, String(value));
82
+ }
83
+ }
84
+ }
85
+ const controller = new AbortController();
86
+ const timeoutId = setTimeout(() => controller.abort(), this.timeout);
87
+ try {
88
+ const response = await fetch(url.toString(), {
89
+ method,
90
+ headers: {
91
+ "x-api-key": this.apiKey,
92
+ "Content-Type": "application/json"
93
+ },
94
+ body: options?.body ? JSON.stringify(options.body) : void 0,
95
+ signal: controller.signal
96
+ });
97
+ clearTimeout(timeoutId);
98
+ if (!response.ok) {
99
+ await this.handleError(response);
100
+ }
101
+ if (response.status === 204) {
102
+ return void 0;
103
+ }
104
+ const data = await response.json();
105
+ return this.transformDates(data);
106
+ } catch (error) {
107
+ clearTimeout(timeoutId);
108
+ if (error instanceof SubformerError) {
109
+ throw error;
110
+ }
111
+ if (error instanceof Error && error.name === "AbortError") {
112
+ throw new SubformerError("Request timeout");
113
+ }
114
+ throw new SubformerError(
115
+ error instanceof Error ? error.message : "Unknown error"
116
+ );
117
+ }
118
+ }
119
+ async handleError(response) {
120
+ let message;
121
+ let code;
122
+ let data;
123
+ try {
124
+ const json = await response.json();
125
+ message = json.message ?? response.statusText;
126
+ code = json.code;
127
+ data = json.data;
128
+ } catch {
129
+ message = response.statusText;
130
+ }
131
+ switch (response.status) {
132
+ case 401:
133
+ throw new AuthenticationError(message);
134
+ case 404:
135
+ throw new NotFoundError(message);
136
+ case 429:
137
+ throw new RateLimitError(message);
138
+ case 400:
139
+ throw new ValidationError(message, data);
140
+ default:
141
+ throw new SubformerError(message, {
142
+ statusCode: response.status,
143
+ code,
144
+ data
145
+ });
146
+ }
147
+ }
148
+ transformDates(obj) {
149
+ if (obj === null || obj === void 0) {
150
+ return obj;
151
+ }
152
+ if (Array.isArray(obj)) {
153
+ return obj.map((item) => this.transformDates(item));
154
+ }
155
+ if (typeof obj === "object") {
156
+ const result = {};
157
+ for (const [key, value] of Object.entries(obj)) {
158
+ if (typeof value === "string" && (key.endsWith("At") || key.endsWith("On")) && /^\d{4}-\d{2}-\d{2}T/.test(value)) {
159
+ result[key] = new Date(value);
160
+ } else {
161
+ result[key] = this.transformDates(value);
162
+ }
163
+ }
164
+ return result;
165
+ }
166
+ return obj;
167
+ }
168
+ // ==================== Dubbing ====================
169
+ /**
170
+ * Create a video dubbing job.
171
+ *
172
+ * @param options - Dubbing options
173
+ * @returns The created job
174
+ *
175
+ * @example
176
+ * ```typescript
177
+ * const job = await client.dub({
178
+ * source: 'youtube',
179
+ * url: 'https://youtube.com/watch?v=dQw4w9WgXcQ',
180
+ * language: 'es-ES'
181
+ * });
182
+ * ```
183
+ */
184
+ async dub(options) {
185
+ const response = await this.request("POST", "/dub", {
186
+ body: {
187
+ type: options.source,
188
+ url: options.url,
189
+ toLanguage: options.language,
190
+ disableWatermark: options.disableWatermark
191
+ }
192
+ });
193
+ return response.job;
194
+ }
195
+ /**
196
+ * Get list of supported languages for dubbing.
197
+ *
198
+ * @returns List of language codes
199
+ */
200
+ async getLanguages() {
201
+ return this.request("GET", "/metadata/dub/languages");
202
+ }
203
+ // ==================== Jobs ====================
204
+ /**
205
+ * Get a job by ID.
206
+ *
207
+ * @param jobId - The job ID
208
+ * @returns The job
209
+ */
210
+ async getJob(jobId) {
211
+ return this.request("GET", `/jobs/${jobId}`);
212
+ }
213
+ /**
214
+ * List jobs for the authenticated user.
215
+ *
216
+ * @param options - List options
217
+ * @returns Paginated list of jobs
218
+ */
219
+ async listJobs(options = {}) {
220
+ return this.request("GET", "/jobs", {
221
+ params: {
222
+ offset: options.offset,
223
+ limit: options.limit,
224
+ type: options.type
225
+ }
226
+ });
227
+ }
228
+ /**
229
+ * Delete jobs by IDs.
230
+ *
231
+ * @param jobIds - List of job IDs to delete (max 50)
232
+ * @returns True if successful
233
+ */
234
+ async deleteJobs(jobIds) {
235
+ const response = await this.request(
236
+ "DELETE",
237
+ "/jobs",
238
+ {
239
+ body: { jobIds }
240
+ }
241
+ );
242
+ return response.success;
243
+ }
244
+ /**
245
+ * Wait for a job to complete.
246
+ *
247
+ * @param jobId - The job ID
248
+ * @param options - Wait options
249
+ * @returns The completed job
250
+ * @throws {Error} If the job doesn't complete within the timeout
251
+ */
252
+ async waitForJob(jobId, options = {}) {
253
+ const pollInterval = (options.pollInterval ?? 2) * 1e3;
254
+ const timeout = options.timeout ? options.timeout * 1e3 : void 0;
255
+ const startTime = Date.now();
256
+ while (true) {
257
+ const job = await this.getJob(jobId);
258
+ if (job.state === "completed" || job.state === "failed" || job.state === "cancelled") {
259
+ return job;
260
+ }
261
+ if (timeout && Date.now() - startTime > timeout) {
262
+ throw new Error(
263
+ `Job ${jobId} did not complete within ${options.timeout} seconds`
264
+ );
265
+ }
266
+ await new Promise((resolve) => setTimeout(resolve, pollInterval));
267
+ }
268
+ }
269
+ // ==================== Voice Cloning ====================
270
+ /**
271
+ * Create a voice cloning job.
272
+ *
273
+ * @param options - Voice cloning options
274
+ * @returns The created job
275
+ */
276
+ async cloneVoice(options) {
277
+ const response = await this.request("POST", "/voice/clone", {
278
+ body: {
279
+ sourceAudioUrl: options.sourceAudioUrl,
280
+ targetVoice: options.targetVoice.mode === "preset" ? { mode: "preset", presetVoiceId: options.targetVoice.presetVoiceId } : { mode: "upload", targetAudioUrl: options.targetVoice.targetAudioUrl }
281
+ }
282
+ });
283
+ return response.job;
284
+ }
285
+ /**
286
+ * Create a voice synthesis (text-to-speech) job.
287
+ *
288
+ * @param options - Voice synthesis options
289
+ * @returns The created job
290
+ */
291
+ async synthesizeVoice(options) {
292
+ const response = await this.request(
293
+ "POST",
294
+ "/voice/synthesize",
295
+ {
296
+ body: {
297
+ text: options.text,
298
+ targetVoice: options.targetVoice.mode === "preset" ? { mode: "preset", presetVoiceId: options.targetVoice.presetVoiceId } : { mode: "upload", targetAudioUrl: options.targetVoice.targetAudioUrl }
299
+ }
300
+ }
301
+ );
302
+ return response.job;
303
+ }
304
+ // ==================== Voice Library ====================
305
+ /**
306
+ * List all voices in the user's voice library.
307
+ *
308
+ * @returns List of voices
309
+ */
310
+ async listVoices() {
311
+ return this.request("GET", "/voices");
312
+ }
313
+ /**
314
+ * Get a voice by ID.
315
+ *
316
+ * @param voiceId - The voice ID
317
+ * @returns The voice
318
+ */
319
+ async getVoice(voiceId) {
320
+ return this.request("GET", `/voices/${voiceId}`);
321
+ }
322
+ /**
323
+ * Create a new voice in the voice library.
324
+ *
325
+ * @param options - Create voice options
326
+ * @returns The created voice
327
+ */
328
+ async createVoice(options) {
329
+ return this.request("POST", "/voices", {
330
+ body: {
331
+ name: options.name,
332
+ audioUrl: options.audioUrl,
333
+ gender: options.gender,
334
+ duration: options.duration
335
+ }
336
+ });
337
+ }
338
+ /**
339
+ * Update a voice in the voice library.
340
+ *
341
+ * @param options - Update voice options
342
+ * @returns The updated voice
343
+ */
344
+ async updateVoice(options) {
345
+ return this.request("PUT", `/voices/${options.voiceId}`, {
346
+ body: {
347
+ voiceId: options.voiceId,
348
+ name: options.name,
349
+ gender: options.gender
350
+ }
351
+ });
352
+ }
353
+ /**
354
+ * Delete a voice from the voice library.
355
+ *
356
+ * @param voiceId - The voice ID
357
+ * @returns True if successful
358
+ */
359
+ async deleteVoice(voiceId) {
360
+ const response = await this.request(
361
+ "DELETE",
362
+ `/voices/${voiceId}`,
363
+ {
364
+ body: { voiceId }
365
+ }
366
+ );
367
+ return response.success;
368
+ }
369
+ /**
370
+ * Generate a presigned URL for uploading voice audio.
371
+ *
372
+ * @param options - Upload URL options
373
+ * @returns Upload URL details
374
+ */
375
+ async generateVoiceUploadUrl(options) {
376
+ return this.request("POST", "/voices/upload-url", {
377
+ body: {
378
+ fileName: options.fileName,
379
+ contentType: options.contentType
380
+ }
381
+ });
382
+ }
383
+ // ==================== Billing ====================
384
+ /**
385
+ * Get current billing period usage statistics.
386
+ *
387
+ * @returns Current usage including credits, plan limits, and subscription details
388
+ */
389
+ async getUsage() {
390
+ return this.request("GET", "/billing/usage");
391
+ }
392
+ /**
393
+ * Get daily usage statistics for the past 30 days.
394
+ *
395
+ * @returns List of daily usage records grouped by task type
396
+ */
397
+ async getUsageHistory() {
398
+ return this.request("GET", "/billing/usage-history");
399
+ }
400
+ // ==================== Users ====================
401
+ /**
402
+ * Get the currently authenticated user's profile.
403
+ *
404
+ * @returns User profile information
405
+ */
406
+ async getMe() {
407
+ return this.request("GET", "/users/me");
408
+ }
409
+ /**
410
+ * Update the currently authenticated user's profile.
411
+ *
412
+ * @param options - Update user options
413
+ * @returns Updated user profile
414
+ */
415
+ async updateMe(options) {
416
+ const response = await this.request("PUT", "/users/me", {
417
+ body: {
418
+ name: options.name,
419
+ email: options.email
420
+ }
421
+ });
422
+ return response.user;
423
+ }
424
+ /**
425
+ * Get the current rate limit status for creating dubbing jobs.
426
+ *
427
+ * @returns Rate limit status including remaining count and limit
428
+ */
429
+ async getRateLimit() {
430
+ return this.request("GET", "/users/me/rate-limit");
431
+ }
432
+ };
433
+ // Annotate the CommonJS export names for ESM import in node:
434
+ 0 && (module.exports = {
435
+ AuthenticationError,
436
+ NotFoundError,
437
+ RateLimitError,
438
+ Subformer,
439
+ SubformerError,
440
+ ValidationError
441
+ });
442
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/index.ts","../src/errors.ts","../src/client.ts"],"sourcesContent":["/**\n * Subformer TypeScript/JavaScript SDK\n * AI-powered video dubbing and voice cloning\n */\n\nexport { Subformer } from \"./client\";\nexport type { SubformerOptions } from \"./client\";\n\nexport {\n SubformerError,\n AuthenticationError,\n NotFoundError,\n RateLimitError,\n ValidationError,\n} from \"./errors\";\n\nexport type {\n CloneVoiceOptions,\n CreateVoiceOptions,\n DailyUsage,\n DubOptions,\n DubSource,\n GenerateVoiceUploadUrlOptions,\n Job,\n JobMetadata,\n JobProgress,\n JobState,\n JobType,\n Language,\n ListJobsOptions,\n PaginatedJobs,\n PresetVoice,\n RateLimit,\n SynthesizeVoiceOptions,\n TargetVoice,\n UpdateUserOptions,\n UpdateVoiceOptions,\n UploadedVoice,\n UploadUrl,\n Usage,\n UsageData,\n User,\n Voice,\n WaitForJobOptions,\n} from \"./types\";\n","/**\n * Exceptions for Subformer SDK\n */\n\n/** Base error for Subformer SDK */\nexport class SubformerError extends Error {\n readonly statusCode?: number;\n readonly code?: string;\n readonly data?: unknown;\n\n constructor(\n message: string,\n options?: {\n statusCode?: number;\n code?: string;\n data?: unknown;\n }\n ) {\n super(message);\n this.name = \"SubformerError\";\n this.statusCode = options?.statusCode;\n this.code = options?.code;\n this.data = options?.data;\n }\n}\n\n/** Raised when API authentication fails */\nexport class AuthenticationError extends SubformerError {\n constructor(message = \"Invalid or missing API key\") {\n super(message, { statusCode: 401, code: \"UNAUTHORIZED\" });\n this.name = \"AuthenticationError\";\n }\n}\n\n/** Raised when a resource is not found */\nexport class NotFoundError extends SubformerError {\n constructor(message = \"Resource not found\") {\n super(message, { statusCode: 404, code: \"NOT_FOUND\" });\n this.name = \"NotFoundError\";\n }\n}\n\n/** Raised when rate limit is exceeded */\nexport class RateLimitError extends SubformerError {\n constructor(message = \"Rate limit exceeded\") {\n super(message, { statusCode: 429, code: \"RATE_LIMIT_EXCEEDED\" });\n this.name = \"RateLimitError\";\n }\n}\n\n/** Raised when request validation fails */\nexport class ValidationError extends SubformerError {\n constructor(message: string, data?: unknown) {\n super(message, { statusCode: 400, code: \"BAD_REQUEST\", data });\n this.name = \"ValidationError\";\n }\n}\n","/**\n * Subformer API client\n */\n\nimport {\n AuthenticationError,\n NotFoundError,\n RateLimitError,\n SubformerError,\n ValidationError,\n} from \"./errors\";\nimport type {\n CloneVoiceOptions,\n CreateVoiceOptions,\n DailyUsage,\n DubOptions,\n GenerateVoiceUploadUrlOptions,\n Job,\n Language,\n ListJobsOptions,\n PaginatedJobs,\n RateLimit,\n SynthesizeVoiceOptions,\n UpdateUserOptions,\n UpdateVoiceOptions,\n UploadUrl,\n Usage,\n User,\n Voice,\n WaitForJobOptions,\n} from \"./types\";\n\nconst DEFAULT_BASE_URL = \"https://api.subformer.com/v1\";\nconst DEFAULT_TIMEOUT = 30000; // 30 seconds\n\nexport interface SubformerOptions {\n /** Your Subformer API key */\n apiKey: string;\n /** Base URL for the API (default: https://api.subformer.com/v1) */\n baseUrl?: string;\n /** Request timeout in milliseconds (default: 30000) */\n timeout?: number;\n}\n\n/**\n * Subformer API client for video dubbing, voice cloning, and text-to-speech.\n *\n * @example\n * ```typescript\n * import { Subformer } from 'subformer';\n *\n * const client = new Subformer({ apiKey: 'sk_subformer_...' });\n *\n * // Create a dubbing job\n * const job = await client.dub({\n * source: 'youtube',\n * url: 'https://youtube.com/watch?v=VIDEO_ID',\n * language: 'es-ES'\n * });\n *\n * // Wait for completion\n * const result = await client.waitForJob(job.id);\n * console.log('Done!', result.output);\n * ```\n */\nexport class Subformer {\n private readonly apiKey: string;\n private readonly baseUrl: string;\n private readonly timeout: number;\n\n constructor(options: SubformerOptions) {\n this.apiKey = options.apiKey;\n this.baseUrl = (options.baseUrl ?? DEFAULT_BASE_URL).replace(/\\/$/, \"\");\n this.timeout = options.timeout ?? DEFAULT_TIMEOUT;\n }\n\n private async request<T>(\n method: string,\n path: string,\n options?: {\n body?: unknown;\n params?: Record<string, string | number | boolean | undefined>;\n }\n ): Promise<T> {\n const url = new URL(`${this.baseUrl}${path}`);\n\n if (options?.params) {\n for (const [key, value] of Object.entries(options.params)) {\n if (value !== undefined) {\n url.searchParams.set(key, String(value));\n }\n }\n }\n\n const controller = new AbortController();\n const timeoutId = setTimeout(() => controller.abort(), this.timeout);\n\n try {\n const response = await fetch(url.toString(), {\n method,\n headers: {\n \"x-api-key\": this.apiKey,\n \"Content-Type\": \"application/json\",\n },\n body: options?.body ? JSON.stringify(options.body) : undefined,\n signal: controller.signal,\n });\n\n clearTimeout(timeoutId);\n\n if (!response.ok) {\n await this.handleError(response);\n }\n\n if (response.status === 204) {\n return undefined as T;\n }\n\n const data = await response.json();\n return this.transformDates(data) as T;\n } catch (error) {\n clearTimeout(timeoutId);\n if (error instanceof SubformerError) {\n throw error;\n }\n if (error instanceof Error && error.name === \"AbortError\") {\n throw new SubformerError(\"Request timeout\");\n }\n throw new SubformerError(\n error instanceof Error ? error.message : \"Unknown error\"\n );\n }\n }\n\n private async handleError(response: Response): Promise<never> {\n let message: string;\n let code: string | undefined;\n let data: unknown;\n\n try {\n const json = (await response.json()) as Record<string, unknown>;\n message = (json.message as string) ?? response.statusText;\n code = json.code as string | undefined;\n data = json.data;\n } catch {\n message = response.statusText;\n }\n\n switch (response.status) {\n case 401:\n throw new AuthenticationError(message);\n case 404:\n throw new NotFoundError(message);\n case 429:\n throw new RateLimitError(message);\n case 400:\n throw new ValidationError(message, data);\n default:\n throw new SubformerError(message, {\n statusCode: response.status,\n code,\n data,\n });\n }\n }\n\n private transformDates(obj: unknown): unknown {\n if (obj === null || obj === undefined) {\n return obj;\n }\n\n if (Array.isArray(obj)) {\n return obj.map((item) => this.transformDates(item));\n }\n\n if (typeof obj === \"object\") {\n const result: Record<string, unknown> = {};\n for (const [key, value] of Object.entries(obj as Record<string, unknown>)) {\n if (\n typeof value === \"string\" &&\n (key.endsWith(\"At\") || key.endsWith(\"On\")) &&\n /^\\d{4}-\\d{2}-\\d{2}T/.test(value)\n ) {\n result[key] = new Date(value);\n } else {\n result[key] = this.transformDates(value);\n }\n }\n return result;\n }\n\n return obj;\n }\n\n // ==================== Dubbing ====================\n\n /**\n * Create a video dubbing job.\n *\n * @param options - Dubbing options\n * @returns The created job\n *\n * @example\n * ```typescript\n * const job = await client.dub({\n * source: 'youtube',\n * url: 'https://youtube.com/watch?v=dQw4w9WgXcQ',\n * language: 'es-ES'\n * });\n * ```\n */\n async dub(options: DubOptions): Promise<Job> {\n const response = await this.request<{ job: Job }>(\"POST\", \"/dub\", {\n body: {\n type: options.source,\n url: options.url,\n toLanguage: options.language,\n disableWatermark: options.disableWatermark,\n },\n });\n return response.job;\n }\n\n /**\n * Get list of supported languages for dubbing.\n *\n * @returns List of language codes\n */\n async getLanguages(): Promise<Language[]> {\n return this.request<Language[]>(\"GET\", \"/metadata/dub/languages\");\n }\n\n // ==================== Jobs ====================\n\n /**\n * Get a job by ID.\n *\n * @param jobId - The job ID\n * @returns The job\n */\n async getJob(jobId: string): Promise<Job> {\n return this.request<Job>(\"GET\", `/jobs/${jobId}`);\n }\n\n /**\n * List jobs for the authenticated user.\n *\n * @param options - List options\n * @returns Paginated list of jobs\n */\n async listJobs(options: ListJobsOptions = {}): Promise<PaginatedJobs> {\n return this.request<PaginatedJobs>(\"GET\", \"/jobs\", {\n params: {\n offset: options.offset,\n limit: options.limit,\n type: options.type,\n },\n });\n }\n\n /**\n * Delete jobs by IDs.\n *\n * @param jobIds - List of job IDs to delete (max 50)\n * @returns True if successful\n */\n async deleteJobs(jobIds: string[]): Promise<boolean> {\n const response = await this.request<{ success: boolean }>(\n \"DELETE\",\n \"/jobs\",\n {\n body: { jobIds },\n }\n );\n return response.success;\n }\n\n /**\n * Wait for a job to complete.\n *\n * @param jobId - The job ID\n * @param options - Wait options\n * @returns The completed job\n * @throws {Error} If the job doesn't complete within the timeout\n */\n async waitForJob(jobId: string, options: WaitForJobOptions = {}): Promise<Job> {\n const pollInterval = (options.pollInterval ?? 2) * 1000;\n const timeout = options.timeout ? options.timeout * 1000 : undefined;\n const startTime = Date.now();\n\n while (true) {\n const job = await this.getJob(jobId);\n\n if (\n job.state === \"completed\" ||\n job.state === \"failed\" ||\n job.state === \"cancelled\"\n ) {\n return job;\n }\n\n if (timeout && Date.now() - startTime > timeout) {\n throw new Error(\n `Job ${jobId} did not complete within ${options.timeout} seconds`\n );\n }\n\n await new Promise((resolve) => setTimeout(resolve, pollInterval));\n }\n }\n\n // ==================== Voice Cloning ====================\n\n /**\n * Create a voice cloning job.\n *\n * @param options - Voice cloning options\n * @returns The created job\n */\n async cloneVoice(options: CloneVoiceOptions): Promise<Job> {\n const response = await this.request<{ job: Job }>(\"POST\", \"/voice/clone\", {\n body: {\n sourceAudioUrl: options.sourceAudioUrl,\n targetVoice:\n options.targetVoice.mode === \"preset\"\n ? { mode: \"preset\", presetVoiceId: options.targetVoice.presetVoiceId }\n : { mode: \"upload\", targetAudioUrl: options.targetVoice.targetAudioUrl },\n },\n });\n return response.job;\n }\n\n /**\n * Create a voice synthesis (text-to-speech) job.\n *\n * @param options - Voice synthesis options\n * @returns The created job\n */\n async synthesizeVoice(options: SynthesizeVoiceOptions): Promise<Job> {\n const response = await this.request<{ job: Job }>(\n \"POST\",\n \"/voice/synthesize\",\n {\n body: {\n text: options.text,\n targetVoice:\n options.targetVoice.mode === \"preset\"\n ? { mode: \"preset\", presetVoiceId: options.targetVoice.presetVoiceId }\n : { mode: \"upload\", targetAudioUrl: options.targetVoice.targetAudioUrl },\n },\n }\n );\n return response.job;\n }\n\n // ==================== Voice Library ====================\n\n /**\n * List all voices in the user's voice library.\n *\n * @returns List of voices\n */\n async listVoices(): Promise<Voice[]> {\n return this.request<Voice[]>(\"GET\", \"/voices\");\n }\n\n /**\n * Get a voice by ID.\n *\n * @param voiceId - The voice ID\n * @returns The voice\n */\n async getVoice(voiceId: string): Promise<Voice> {\n return this.request<Voice>(\"GET\", `/voices/${voiceId}`);\n }\n\n /**\n * Create a new voice in the voice library.\n *\n * @param options - Create voice options\n * @returns The created voice\n */\n async createVoice(options: CreateVoiceOptions): Promise<Voice> {\n return this.request<Voice>(\"POST\", \"/voices\", {\n body: {\n name: options.name,\n audioUrl: options.audioUrl,\n gender: options.gender,\n duration: options.duration,\n },\n });\n }\n\n /**\n * Update a voice in the voice library.\n *\n * @param options - Update voice options\n * @returns The updated voice\n */\n async updateVoice(options: UpdateVoiceOptions): Promise<Voice> {\n return this.request<Voice>(\"PUT\", `/voices/${options.voiceId}`, {\n body: {\n voiceId: options.voiceId,\n name: options.name,\n gender: options.gender,\n },\n });\n }\n\n /**\n * Delete a voice from the voice library.\n *\n * @param voiceId - The voice ID\n * @returns True if successful\n */\n async deleteVoice(voiceId: string): Promise<boolean> {\n const response = await this.request<{ success: boolean }>(\n \"DELETE\",\n `/voices/${voiceId}`,\n {\n body: { voiceId },\n }\n );\n return response.success;\n }\n\n /**\n * Generate a presigned URL for uploading voice audio.\n *\n * @param options - Upload URL options\n * @returns Upload URL details\n */\n async generateVoiceUploadUrl(options: GenerateVoiceUploadUrlOptions): Promise<UploadUrl> {\n return this.request<UploadUrl>(\"POST\", \"/voices/upload-url\", {\n body: {\n fileName: options.fileName,\n contentType: options.contentType,\n },\n });\n }\n\n // ==================== Billing ====================\n\n /**\n * Get current billing period usage statistics.\n *\n * @returns Current usage including credits, plan limits, and subscription details\n */\n async getUsage(): Promise<Usage> {\n return this.request<Usage>(\"GET\", \"/billing/usage\");\n }\n\n /**\n * Get daily usage statistics for the past 30 days.\n *\n * @returns List of daily usage records grouped by task type\n */\n async getUsageHistory(): Promise<DailyUsage[]> {\n return this.request<DailyUsage[]>(\"GET\", \"/billing/usage-history\");\n }\n\n // ==================== Users ====================\n\n /**\n * Get the currently authenticated user's profile.\n *\n * @returns User profile information\n */\n async getMe(): Promise<User> {\n return this.request<User>(\"GET\", \"/users/me\");\n }\n\n /**\n * Update the currently authenticated user's profile.\n *\n * @param options - Update user options\n * @returns Updated user profile\n */\n async updateMe(options: UpdateUserOptions): Promise<User> {\n const response = await this.request<{ user: User }>(\"PUT\", \"/users/me\", {\n body: {\n name: options.name,\n email: options.email,\n },\n });\n return response.user;\n }\n\n /**\n * Get the current rate limit status for creating dubbing jobs.\n *\n * @returns Rate limit status including remaining count and limit\n */\n async getRateLimit(): Promise<RateLimit> {\n return this.request<RateLimit>(\"GET\", \"/users/me/rate-limit\");\n }\n}\n"],"mappings":"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","names":[]}