@renderingvideo/sdk 1.0.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,570 @@
1
+ // src/errors.ts
2
+ var RenderingVideoError = class extends Error {
3
+ code;
4
+ details;
5
+ constructor(message, code, details = {}) {
6
+ super(message);
7
+ this.name = "RenderingVideoError";
8
+ this.code = code;
9
+ this.details = details;
10
+ }
11
+ };
12
+ var AuthenticationError = class extends RenderingVideoError {
13
+ constructor(message, details = {}) {
14
+ super(message, "AUTHENTICATION_ERROR", details);
15
+ this.name = "AuthenticationError";
16
+ }
17
+ };
18
+ var InvalidApiKeyError = class extends RenderingVideoError {
19
+ constructor(message, details = {}) {
20
+ super(message, "INVALID_API_KEY", details);
21
+ this.name = "InvalidApiKeyError";
22
+ }
23
+ };
24
+ var InsufficientCreditsError = class extends RenderingVideoError {
25
+ constructor(message, details = {}) {
26
+ super(message, "INSUFFICIENT_CREDITS", details);
27
+ this.name = "InsufficientCreditsError";
28
+ }
29
+ };
30
+ var ValidationError = class extends RenderingVideoError {
31
+ constructor(message, details = {}) {
32
+ super(message, "VALIDATION_ERROR", details);
33
+ this.name = "ValidationError";
34
+ }
35
+ };
36
+ var NotFoundError = class extends RenderingVideoError {
37
+ constructor(message, details = {}) {
38
+ super(message, "NOT_FOUND", details);
39
+ this.name = "NotFoundError";
40
+ }
41
+ };
42
+ var RateLimitError = class extends RenderingVideoError {
43
+ constructor(message, details = {}) {
44
+ super(message, "RATE_LIMITED", details);
45
+ this.name = "RateLimitError";
46
+ }
47
+ };
48
+ var AlreadyRenderingError = class extends RenderingVideoError {
49
+ constructor(message, details = {}) {
50
+ super(message, "ALREADY_RENDERING", details);
51
+ this.name = "AlreadyRenderingError";
52
+ }
53
+ };
54
+ var UploadError = class extends RenderingVideoError {
55
+ constructor(message, details = {}) {
56
+ super(message, "UPLOAD_FAILED", details);
57
+ this.name = "UploadError";
58
+ }
59
+ };
60
+ var StorageLimitError = class extends RenderingVideoError {
61
+ constructor(message, details = {}) {
62
+ super(message, "STORAGE_LIMIT_EXCEEDED", details);
63
+ this.name = "StorageLimitError";
64
+ }
65
+ };
66
+ var RemoteError = class extends RenderingVideoError {
67
+ constructor(message, details = {}) {
68
+ super(message, "REMOTE_ERROR", details);
69
+ this.name = "RemoteError";
70
+ }
71
+ };
72
+ function handleApiError(statusCode, message, code, details = {}) {
73
+ switch (code) {
74
+ case "MISSING_API_KEY":
75
+ case "INVALID_API_KEY":
76
+ case "INVALID_API_KEY_FORMAT":
77
+ case "API_KEY_INACTIVE":
78
+ case "USER_NOT_FOUND":
79
+ return new AuthenticationError(message, { ...details, code, statusCode });
80
+ case "INSUFFICIENT_CREDITS":
81
+ return new InsufficientCreditsError(message, { ...details, code, statusCode });
82
+ case "INVALID_REQUEST":
83
+ case "INVALID_CONFIG":
84
+ case "NO_FILES":
85
+ case "UNSUPPORTED_FILE_TYPE":
86
+ return new ValidationError(message, { ...details, code, statusCode });
87
+ case "NOT_FOUND":
88
+ return new NotFoundError(message, { ...details, code, statusCode });
89
+ case "ALREADY_RENDERING":
90
+ return new AlreadyRenderingError(message, { ...details, code, statusCode });
91
+ case "STORAGE_LIMIT_EXCEEDED":
92
+ return new StorageLimitError(message, { ...details, code, statusCode });
93
+ case "UPLOAD_FAILED":
94
+ return new UploadError(message, { ...details, code, statusCode });
95
+ case "REMOTE_ERROR":
96
+ case "RENDER_TRIGGER_FAILED":
97
+ return new RemoteError(message, { ...details, code, statusCode });
98
+ }
99
+ switch (statusCode) {
100
+ case 401:
101
+ return new AuthenticationError(message, { ...details, code, statusCode });
102
+ case 402:
103
+ return new InsufficientCreditsError(message, { ...details, code, statusCode });
104
+ case 400:
105
+ return new ValidationError(message, { ...details, code, statusCode });
106
+ case 404:
107
+ return new NotFoundError(message, { ...details, code, statusCode });
108
+ case 429:
109
+ return new RateLimitError(message, { ...details, code, statusCode });
110
+ default:
111
+ return new RenderingVideoError(message, code, { ...details, statusCode });
112
+ }
113
+ }
114
+
115
+ // src/client.ts
116
+ var DEFAULT_BASE_URL = "https://renderingvideo.com";
117
+ var DEFAULT_TIMEOUT = 3e4;
118
+ async function request(baseUrl, apiKey, timeout, method, endpoint, body, params) {
119
+ const url = new URL(`${baseUrl}${endpoint}`);
120
+ if (params) {
121
+ Object.entries(params).forEach(([key, value]) => {
122
+ if (value !== void 0) {
123
+ url.searchParams.append(key, String(value));
124
+ }
125
+ });
126
+ }
127
+ const controller = new AbortController();
128
+ const timeoutId = setTimeout(() => controller.abort(), timeout);
129
+ try {
130
+ const response = await fetch(url.toString(), {
131
+ method,
132
+ headers: {
133
+ Authorization: `Bearer ${apiKey}`,
134
+ "Content-Type": "application/json"
135
+ },
136
+ body: body ? JSON.stringify(body) : void 0,
137
+ signal: controller.signal
138
+ });
139
+ const data = await response.json();
140
+ if (!response.ok) {
141
+ throw handleApiError(
142
+ response.status,
143
+ data.error || "Unknown error",
144
+ data.code || "UNKNOWN_ERROR",
145
+ data.details || data
146
+ );
147
+ }
148
+ return data;
149
+ } catch (error) {
150
+ if (error instanceof RenderingVideoError) {
151
+ throw error;
152
+ }
153
+ if (error instanceof Error && error.name === "AbortError") {
154
+ throw new Error(`Request timed out after ${timeout}ms`);
155
+ }
156
+ throw error;
157
+ } finally {
158
+ clearTimeout(timeoutId);
159
+ }
160
+ }
161
+ var VideoClient = class {
162
+ constructor(baseUrl, apiKey, timeout) {
163
+ this.baseUrl = baseUrl;
164
+ this.apiKey = apiKey;
165
+ this.timeout = timeout;
166
+ }
167
+ async request(method, endpoint, body, params) {
168
+ return request(this.baseUrl, this.apiKey, this.timeout, method, endpoint, body, params);
169
+ }
170
+ /**
171
+ * Create a new video task (does not start rendering)
172
+ * @example
173
+ * ```typescript
174
+ * const task = await client.video.create({
175
+ * config: {
176
+ * meta: { version: '2.0.0', width: 1920, height: 1080, fps: 30 },
177
+ * tracks: [{ clips: [{ type: 'text', text: 'Hello World', start: 0, duration: 5 }] }]
178
+ * },
179
+ * metadata: { projectId: 'proj_123' }
180
+ * });
181
+ * ```
182
+ */
183
+ async create(options) {
184
+ const body = { config: options.config };
185
+ if (options.metadata) body.metadata = options.metadata;
186
+ return this.request("POST", "/api/v1/video", body);
187
+ }
188
+ /**
189
+ * List video tasks
190
+ * @example
191
+ * ```typescript
192
+ * const { tasks, pagination } = await client.video.list({ page: 1, limit: 20, status: 'completed' });
193
+ * ```
194
+ */
195
+ async list(options) {
196
+ return this.request("GET", "/api/v1/video", void 0, {
197
+ page: options?.page,
198
+ limit: options?.limit,
199
+ status: options?.status
200
+ });
201
+ }
202
+ /**
203
+ * Get task details by ID
204
+ * @example
205
+ * ```typescript
206
+ * const task = await client.video.get('abc123def456');
207
+ * console.log(task.status, task.videoUrl);
208
+ * ```
209
+ */
210
+ async get(taskId) {
211
+ return this.request("GET", `/api/v1/video/${taskId}`);
212
+ }
213
+ /**
214
+ * Delete a video task permanently
215
+ * @example
216
+ * ```typescript
217
+ * const result = await client.video.delete('abc123def456');
218
+ * console.log(result.deleted, result.remoteDeleted);
219
+ * ```
220
+ */
221
+ async delete(taskId) {
222
+ return this.request("DELETE", `/api/v1/video/${taskId}`);
223
+ }
224
+ /**
225
+ * Trigger rendering for a task
226
+ * @example
227
+ * ```typescript
228
+ * const result = await client.video.render('abc123def456', {
229
+ * webhookUrl: 'https://example.com/webhook',
230
+ * numWorkers: 5
231
+ * });
232
+ * ```
233
+ */
234
+ async render(taskId, options) {
235
+ const body = {};
236
+ if (options?.webhookUrl) body.webhook_url = options.webhookUrl;
237
+ if (options?.numWorkers) body.num_workers = options.numWorkers;
238
+ return this.request("POST", `/api/v1/video/${taskId}/render`, body);
239
+ }
240
+ /**
241
+ * Create task and immediately start rendering (convenience method)
242
+ * @example
243
+ * ```typescript
244
+ * const task = await client.video.createAndRender({
245
+ * config: { ... },
246
+ * webhookUrl: 'https://example.com/webhook'
247
+ * });
248
+ * ```
249
+ */
250
+ async createAndRender(options) {
251
+ const task = await this.create(options);
252
+ return this.render(task.taskId, { webhookUrl: options.webhookUrl, numWorkers: options.numWorkers });
253
+ }
254
+ };
255
+ var FileClient = class {
256
+ constructor(baseUrl, apiKey, timeout) {
257
+ this.baseUrl = baseUrl;
258
+ this.apiKey = apiKey;
259
+ this.timeout = timeout;
260
+ }
261
+ async request(method, endpoint, body, params) {
262
+ return request(this.baseUrl, this.apiKey, this.timeout, method, endpoint, body, params);
263
+ }
264
+ /**
265
+ * Upload files (images, videos, audio)
266
+ * @example
267
+ * ```typescript
268
+ * // In Node.js with FormData
269
+ * const formData = new FormData();
270
+ * formData.append('file', fileBlob, 'image.png');
271
+ * const result = await client.files.upload(formData);
272
+ *
273
+ * // Multiple files
274
+ * formData.append('files', file1Blob, 'video.mp4');
275
+ * formData.append('files', file2Blob, 'audio.mp3');
276
+ * const result = await client.files.upload(formData);
277
+ * ```
278
+ */
279
+ async upload(formData) {
280
+ const controller = new AbortController();
281
+ const timeoutId = setTimeout(() => controller.abort(), this.timeout);
282
+ try {
283
+ const response = await fetch(`${this.baseUrl}/api/v1/upload`, {
284
+ method: "POST",
285
+ headers: {
286
+ Authorization: `Bearer ${this.apiKey}`
287
+ },
288
+ body: formData,
289
+ signal: controller.signal
290
+ });
291
+ const data = await response.json();
292
+ if (!response.ok) {
293
+ throw handleApiError(
294
+ response.status,
295
+ data.error || "Unknown error",
296
+ data.code || "UNKNOWN_ERROR",
297
+ data.details || data
298
+ );
299
+ }
300
+ return data;
301
+ } catch (error) {
302
+ if (error instanceof RenderingVideoError) {
303
+ throw error;
304
+ }
305
+ if (error instanceof Error && error.name === "AbortError") {
306
+ throw new Error(`Upload timed out after ${this.timeout}ms`);
307
+ }
308
+ throw error;
309
+ } finally {
310
+ clearTimeout(timeoutId);
311
+ }
312
+ }
313
+ /**
314
+ * Upload a single file from buffer (Node.js)
315
+ * @example
316
+ * ```typescript
317
+ * const buffer = fs.readFileSync('./image.png');
318
+ * const result = await client.files.uploadBuffer(buffer, 'image.png', 'image/png');
319
+ * ```
320
+ */
321
+ async uploadBuffer(buffer, filename, mimeType) {
322
+ const formData = new FormData();
323
+ const blob = new Blob([buffer], { type: mimeType });
324
+ formData.append("file", blob, filename);
325
+ return this.upload(formData);
326
+ }
327
+ /**
328
+ * Upload a single file from Blob/File
329
+ * @example
330
+ * ```typescript
331
+ * const blob = new Blob([data], { type: 'image/png' });
332
+ * const result = await client.files.uploadFile(blob, 'image.png');
333
+ * ```
334
+ */
335
+ async uploadFile(file, filename) {
336
+ const formData = new FormData();
337
+ formData.append("file", file, filename || (file instanceof File ? file.name : "file"));
338
+ return this.upload(formData);
339
+ }
340
+ /**
341
+ * List uploaded files
342
+ * @example
343
+ * ```typescript
344
+ * const { files, pagination } = await client.files.list({ type: 'image', limit: 50 });
345
+ * ```
346
+ */
347
+ async list(options) {
348
+ return this.request("GET", "/api/v1/files", void 0, {
349
+ page: options?.page,
350
+ limit: options?.limit,
351
+ type: options?.type
352
+ });
353
+ }
354
+ /**
355
+ * Delete a file
356
+ * @example
357
+ * ```typescript
358
+ * const result = await client.files.delete('asset_001');
359
+ * ```
360
+ */
361
+ async delete(fileId) {
362
+ return this.request("DELETE", `/api/v1/files/${fileId}`);
363
+ }
364
+ /**
365
+ * Get file by ID (convenience method - searches through list)
366
+ * @example
367
+ * ```typescript
368
+ * const file = await client.files.get('asset_001');
369
+ * ```
370
+ */
371
+ async get(fileId) {
372
+ const { files } = await this.list({ limit: 100 });
373
+ return files.find((f) => f.id === fileId) || null;
374
+ }
375
+ };
376
+ var PreviewClient = class {
377
+ constructor(baseUrl, apiKey, timeout) {
378
+ this.baseUrl = baseUrl;
379
+ this.apiKey = apiKey;
380
+ this.timeout = timeout;
381
+ }
382
+ async request(method, endpoint, body, params) {
383
+ return request(this.baseUrl, this.apiKey, this.timeout, method, endpoint, body, params);
384
+ }
385
+ /**
386
+ * Create a temporary preview link (7 days validity, no credits consumed)
387
+ * @example
388
+ * ```typescript
389
+ * const preview = await client.preview.create({
390
+ * meta: { version: '2.0.0', width: 1920, height: 1080 },
391
+ * tracks: [{ clips: [{ type: 'text', text: 'Preview', start: 0, duration: 5 }] }]
392
+ * });
393
+ * console.log(preview.previewUrl, preview.tempId);
394
+ * ```
395
+ */
396
+ async create(config) {
397
+ return this.request("POST", "/api/v1/preview", { config });
398
+ }
399
+ /**
400
+ * Get preview configuration
401
+ * @example
402
+ * ```typescript
403
+ * const { config } = await client.preview.get('temp_abc123');
404
+ * ```
405
+ */
406
+ async get(tempId) {
407
+ return this.request("GET", `/api/v1/preview/${tempId}`);
408
+ }
409
+ /**
410
+ * Delete a preview link
411
+ * @example
412
+ * ```typescript
413
+ * const result = await client.preview.delete('temp_abc123');
414
+ * ```
415
+ */
416
+ async delete(tempId) {
417
+ return this.request("DELETE", `/api/v1/preview/${tempId}`);
418
+ }
419
+ /**
420
+ * Convert preview to permanent task (does not start rendering)
421
+ * @example
422
+ * ```typescript
423
+ * const result = await client.preview.convert('temp_abc123', { category: 'marketing' });
424
+ * console.log(result.taskId);
425
+ * ```
426
+ */
427
+ async convert(tempId, options) {
428
+ const body = {};
429
+ if (options?.category) body.category = options.category;
430
+ return this.request("POST", `/api/v1/preview/${tempId}/convert`, body);
431
+ }
432
+ /**
433
+ * Convert preview to permanent task and immediately start rendering
434
+ * @example
435
+ * ```typescript
436
+ * const result = await client.preview.render('temp_abc123', {
437
+ * webhookUrl: 'https://example.com/webhook'
438
+ * });
439
+ * console.log(result.taskId, result.status);
440
+ * ```
441
+ */
442
+ async render(tempId, options) {
443
+ const body = {};
444
+ if (options?.category) body.category = options.category;
445
+ if (options?.webhookUrl) body.webhook_url = options.webhookUrl;
446
+ if (options?.numWorkers) body.num_workers = options.numWorkers;
447
+ return this.request("POST", `/api/v1/preview/${tempId}/render`, body);
448
+ }
449
+ };
450
+ var RenderingVideo = class {
451
+ apiKey;
452
+ baseUrl;
453
+ timeout;
454
+ _video = null;
455
+ _files = null;
456
+ _preview = null;
457
+ constructor(apiKeyOrOptions, options) {
458
+ if (typeof apiKeyOrOptions === "string") {
459
+ this.apiKey = apiKeyOrOptions;
460
+ this.baseUrl = options?.baseUrl ?? DEFAULT_BASE_URL;
461
+ this.timeout = options?.timeout ?? DEFAULT_TIMEOUT;
462
+ } else {
463
+ this.apiKey = apiKeyOrOptions.apiKey;
464
+ this.baseUrl = apiKeyOrOptions.baseUrl ?? DEFAULT_BASE_URL;
465
+ this.timeout = apiKeyOrOptions.timeout ?? DEFAULT_TIMEOUT;
466
+ }
467
+ if (!this.apiKey) {
468
+ throw new Error("API key is required");
469
+ }
470
+ if (!this.apiKey.startsWith("sk-")) {
471
+ throw new Error('Invalid API key. API key should start with "sk-"');
472
+ }
473
+ }
474
+ /**
475
+ * Video API operations
476
+ */
477
+ get video() {
478
+ if (!this._video) {
479
+ this._video = new VideoClient(this.baseUrl, this.apiKey, this.timeout);
480
+ }
481
+ return this._video;
482
+ }
483
+ /**
484
+ * File API operations (upload, list, delete)
485
+ */
486
+ get files() {
487
+ if (!this._files) {
488
+ this._files = new FileClient(this.baseUrl, this.apiKey, this.timeout);
489
+ }
490
+ return this._files;
491
+ }
492
+ /**
493
+ * Preview API operations
494
+ */
495
+ get preview() {
496
+ if (!this._preview) {
497
+ this._preview = new PreviewClient(this.baseUrl, this.apiKey, this.timeout);
498
+ }
499
+ return this._preview;
500
+ }
501
+ /**
502
+ * Credits API operations
503
+ */
504
+ get credits() {
505
+ return new CreditsClient(this.baseUrl, this.apiKey, this.timeout);
506
+ }
507
+ /**
508
+ * Get masked API key for logging
509
+ */
510
+ get apiKeyPreview() {
511
+ return this.apiKey.length > 12 ? `${this.apiKey.slice(0, 8)}...${this.apiKey.slice(-4)}` : "***";
512
+ }
513
+ /**
514
+ * Get the base URL being used
515
+ */
516
+ get baseURL() {
517
+ return this.baseUrl;
518
+ }
519
+ };
520
+ var CreditsClient = class {
521
+ constructor(baseUrl, apiKey, timeout) {
522
+ this.baseUrl = baseUrl;
523
+ this.apiKey = apiKey;
524
+ this.timeout = timeout;
525
+ }
526
+ /**
527
+ * Get current credit balance
528
+ * @example
529
+ * ```typescript
530
+ * const { credits } = await client.credits.get();
531
+ * console.log(`You have ${credits} credits remaining`);
532
+ * ```
533
+ */
534
+ async get() {
535
+ return request(this.baseUrl, this.apiKey, this.timeout, "GET", "/api/v1/credits");
536
+ }
537
+ /**
538
+ * Check if user has enough credits for a render
539
+ * @example
540
+ * ```typescript
541
+ * const hasCredits = await client.credits.hasEnough(100);
542
+ * ```
543
+ */
544
+ async hasEnough(required) {
545
+ const { credits } = await this.get();
546
+ return credits >= required;
547
+ }
548
+ };
549
+
550
+ // src/index.ts
551
+ var index_default = RenderingVideo;
552
+ export {
553
+ AlreadyRenderingError,
554
+ AuthenticationError,
555
+ CreditsClient,
556
+ FileClient,
557
+ InsufficientCreditsError,
558
+ InvalidApiKeyError,
559
+ NotFoundError,
560
+ PreviewClient,
561
+ RateLimitError,
562
+ RemoteError,
563
+ RenderingVideo,
564
+ RenderingVideoError,
565
+ StorageLimitError,
566
+ UploadError,
567
+ ValidationError,
568
+ VideoClient,
569
+ index_default as default
570
+ };
package/package.json ADDED
@@ -0,0 +1,71 @@
1
+ {
2
+ "name": "@renderingvideo/sdk",
3
+ "version": "1.0.0",
4
+ "description": "Official Node.js SDK for the RenderingVideo API - Create videos programmatically",
5
+ "main": "dist/index.cjs",
6
+ "module": "dist/index.js",
7
+ "types": "dist/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "types": "./dist/index.d.ts",
11
+ "import": "./dist/index.js",
12
+ "require": "./dist/index.cjs"
13
+ }
14
+ },
15
+ "files": [
16
+ "dist",
17
+ "README.md",
18
+ "LICENSE"
19
+ ],
20
+ "scripts": {
21
+ "build": "tsup src/index.ts --format cjs,esm --dts --clean",
22
+ "build:watch": "tsup src/index.ts --format cjs,esm --dts --watch",
23
+ "test": "vitest run",
24
+ "test:watch": "vitest",
25
+ "test:coverage": "vitest run --coverage",
26
+ "lint": "eslint src --ext .ts",
27
+ "typecheck": "tsc --noEmit",
28
+ "prepublishOnly": "npm run build"
29
+ },
30
+ "keywords": [
31
+ "renderingvideo",
32
+ "video",
33
+ "api",
34
+ "sdk",
35
+ "video-rendering",
36
+ "programmatic-video",
37
+ "video-generation",
38
+ "video-api",
39
+ "automated-video",
40
+ "video-automation"
41
+ ],
42
+ "author": "RenderingVideo",
43
+ "license": "MIT",
44
+ "repository": {
45
+ "type": "git",
46
+ "url": "https://github.com/renderingvideo/nodejs-sdk"
47
+ },
48
+ "bugs": {
49
+ "url": "https://github.com/renderingvideo/nodejs-sdk/issues"
50
+ },
51
+ "homepage": "https://docs.renderingvideo.com",
52
+ "engines": {
53
+ "node": ">=18.0.0"
54
+ },
55
+ "devDependencies": {
56
+ "@types/node": "^20.0.0",
57
+ "tsup": "^8.0.0",
58
+ "typescript": "^5.0.0",
59
+ "vitest": "^1.0.0"
60
+ },
61
+ "peerDependencies": {
62
+ "typescript": ">=4.7.0"
63
+ },
64
+ "peerDependenciesMeta": {
65
+ "typescript": {
66
+ "optional": true
67
+ }
68
+ },
69
+ "sideEffects": false,
70
+ "type": "module"
71
+ }