@rendobar/sdk 2.1.2 → 3.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/README.md +77 -3
- package/dist/index.cjs +186 -11
- package/dist/index.d.cts +602 -63
- package/dist/index.d.mts +602 -63
- package/dist/index.mjs +185 -12
- package/package.json +1 -1
package/dist/index.mjs
CHANGED
|
@@ -302,13 +302,130 @@ function createBillingResource(request) {
|
|
|
302
302
|
}
|
|
303
303
|
//#endregion
|
|
304
304
|
//#region src/resources/uploads.ts
|
|
305
|
+
function toBlob(file) {
|
|
306
|
+
return file instanceof Blob ? file : new Blob([file]);
|
|
307
|
+
}
|
|
308
|
+
/**
|
|
309
|
+
* PUT a body to a presigned R2 url with bounded retry.
|
|
310
|
+
*
|
|
311
|
+
* Returns the ETag string when the bucket exposes it, or `null` when the
|
|
312
|
+
* response header is absent (e.g. bucket CORS not exposing ETag). Only
|
|
313
|
+
* network/HTTP failures trigger retries; a missing ETag is not retried because
|
|
314
|
+
* it is a deterministic misconfiguration, not a transient error.
|
|
315
|
+
*/
|
|
316
|
+
async function putWithRetry(url, body, signal, attempts = 3) {
|
|
317
|
+
let lastErr;
|
|
318
|
+
for (let i = 0; i < attempts; i++) {
|
|
319
|
+
if (signal?.aborted) throw new Error("Upload aborted");
|
|
320
|
+
try {
|
|
321
|
+
const res = await fetch(url, {
|
|
322
|
+
method: "PUT",
|
|
323
|
+
body,
|
|
324
|
+
signal
|
|
325
|
+
});
|
|
326
|
+
if (!res.ok) throw new Error(`Upload PUT failed: ${res.status}`);
|
|
327
|
+
return res.headers.get("etag");
|
|
328
|
+
} catch (e) {
|
|
329
|
+
lastErr = e;
|
|
330
|
+
if (signal?.aborted) throw e;
|
|
331
|
+
if (i < attempts - 1) await new Promise((r) => setTimeout(r, 200 * 2 ** i));
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
throw lastErr;
|
|
335
|
+
}
|
|
336
|
+
/**
|
|
337
|
+
* Run `fn` over `items` with at most `limit` in flight; preserves order.
|
|
338
|
+
* Workers stop pulling new items once `signal` is aborted, enabling fast
|
|
339
|
+
* cancellation when one part fails.
|
|
340
|
+
*/
|
|
341
|
+
async function mapLimit(items, limit, fn, signal) {
|
|
342
|
+
const out = new Array(items.length);
|
|
343
|
+
let next = 0;
|
|
344
|
+
const worker = async () => {
|
|
345
|
+
while (next < items.length) {
|
|
346
|
+
if (signal?.aborted) break;
|
|
347
|
+
const i = next++;
|
|
348
|
+
out[i] = await fn(items[i]);
|
|
349
|
+
}
|
|
350
|
+
};
|
|
351
|
+
const workers = Math.min(Math.max(1, limit), items.length);
|
|
352
|
+
await Promise.all(Array.from({ length: workers }, worker));
|
|
353
|
+
return out;
|
|
354
|
+
}
|
|
305
355
|
function createUploadsResource(request) {
|
|
306
|
-
return { async
|
|
307
|
-
|
|
356
|
+
return { async create(file, options) {
|
|
357
|
+
const blob = toBlob(file);
|
|
358
|
+
const size = options.size ?? blob.size;
|
|
359
|
+
if (options.signal?.aborted) throw new Error("Upload aborted");
|
|
360
|
+
const init = await (await request("/assets", {
|
|
308
361
|
method: "POST",
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
362
|
+
body: {
|
|
363
|
+
filename: options.filename,
|
|
364
|
+
size,
|
|
365
|
+
contentType: options.contentType,
|
|
366
|
+
checksum: options.checksum,
|
|
367
|
+
lifecycle: options.persist ? "persisted" : "ephemeral"
|
|
368
|
+
},
|
|
369
|
+
raw: true,
|
|
370
|
+
signal: options.signal
|
|
371
|
+
})).json();
|
|
372
|
+
if (init.status === "deduplicated") {
|
|
373
|
+
options.onProgress?.({
|
|
374
|
+
loaded: size,
|
|
375
|
+
total: size
|
|
376
|
+
});
|
|
377
|
+
return init.data;
|
|
378
|
+
}
|
|
379
|
+
if (init.status === "presigned") {
|
|
380
|
+
await putWithRetry(init.upload.url, blob, options.signal);
|
|
381
|
+
options.onProgress?.({
|
|
382
|
+
loaded: size,
|
|
383
|
+
total: size
|
|
384
|
+
});
|
|
385
|
+
return request(`/assets/${init.data.id}/complete`, {
|
|
386
|
+
method: "POST",
|
|
387
|
+
body: {},
|
|
388
|
+
signal: options.signal
|
|
389
|
+
});
|
|
390
|
+
}
|
|
391
|
+
const internal = new AbortController();
|
|
392
|
+
const combinedSignal = options.signal ? AbortSignal.any([options.signal, internal.signal]) : internal.signal;
|
|
393
|
+
const { partSize, parts } = init.upload;
|
|
394
|
+
let loaded = 0;
|
|
395
|
+
let partError = null;
|
|
396
|
+
const completed = await mapLimit(parts, options.concurrency ?? 5, async (p) => {
|
|
397
|
+
const start = (p.partNumber - 1) * partSize;
|
|
398
|
+
const chunk = blob.slice(start, Math.min(start + partSize, size));
|
|
399
|
+
let etag;
|
|
400
|
+
try {
|
|
401
|
+
etag = await putWithRetry(p.url, chunk, combinedSignal);
|
|
402
|
+
} catch (e) {
|
|
403
|
+
internal.abort();
|
|
404
|
+
partError = e instanceof Error ? e : new Error(String(e));
|
|
405
|
+
throw partError;
|
|
406
|
+
}
|
|
407
|
+
if (etag === null) {
|
|
408
|
+
internal.abort();
|
|
409
|
+
const corsErr = /* @__PURE__ */ new Error("Multipart upload requires the ETag response header. Configure the R2 bucket CORS to expose 'ETag'.");
|
|
410
|
+
partError = corsErr;
|
|
411
|
+
throw corsErr;
|
|
412
|
+
}
|
|
413
|
+
loaded += chunk.size;
|
|
414
|
+
options.onProgress?.({
|
|
415
|
+
loaded,
|
|
416
|
+
total: size
|
|
417
|
+
});
|
|
418
|
+
return {
|
|
419
|
+
partNumber: p.partNumber,
|
|
420
|
+
etag
|
|
421
|
+
};
|
|
422
|
+
}, combinedSignal);
|
|
423
|
+
if (partError) throw partError;
|
|
424
|
+
completed.sort((a, b) => a.partNumber - b.partNumber);
|
|
425
|
+
return request(`/assets/${init.data.id}/complete`, {
|
|
426
|
+
method: "POST",
|
|
427
|
+
body: { parts: completed },
|
|
428
|
+
signal: options.signal
|
|
312
429
|
});
|
|
313
430
|
} };
|
|
314
431
|
}
|
|
@@ -448,12 +565,32 @@ function createBatchesResource(request) {
|
|
|
448
565
|
//#endregion
|
|
449
566
|
//#region src/resources/assets.ts
|
|
450
567
|
function createAssetsResource(request) {
|
|
451
|
-
return {
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
568
|
+
return {
|
|
569
|
+
async list(params, options) {
|
|
570
|
+
return request("/assets", {
|
|
571
|
+
query: params,
|
|
572
|
+
signal: options?.signal
|
|
573
|
+
});
|
|
574
|
+
},
|
|
575
|
+
async get(id, options) {
|
|
576
|
+
return request(`/assets/${id}`, { signal: options?.signal });
|
|
577
|
+
},
|
|
578
|
+
async delete(id, options) {
|
|
579
|
+
return request(`/assets/${id}`, {
|
|
580
|
+
method: "DELETE",
|
|
581
|
+
signal: options?.signal
|
|
582
|
+
});
|
|
583
|
+
},
|
|
584
|
+
async download(id, options) {
|
|
585
|
+
return request(`/assets/${id}/download`, { signal: options?.signal });
|
|
586
|
+
},
|
|
587
|
+
async templates(params, options) {
|
|
588
|
+
return request("/assets/templates", {
|
|
589
|
+
query: params,
|
|
590
|
+
signal: options?.signal
|
|
591
|
+
});
|
|
592
|
+
}
|
|
593
|
+
};
|
|
457
594
|
}
|
|
458
595
|
//#endregion
|
|
459
596
|
//#region src/resources/team.ts
|
|
@@ -645,4 +782,40 @@ function createClient(config = {}) {
|
|
|
645
782
|
};
|
|
646
783
|
}
|
|
647
784
|
//#endregion
|
|
648
|
-
|
|
785
|
+
//#region src/types.ts
|
|
786
|
+
/**
|
|
787
|
+
* Primary playable/download URL for a completed job, or `undefined`.
|
|
788
|
+
*
|
|
789
|
+
* Returns `output.file.url` — the single download for a file job or the playlist
|
|
790
|
+
* URL for a stream. A pure set (multiple files, no headline) or a data-only job
|
|
791
|
+
* has no single primary URL: inspect `job.output.files` / `job.output.data`.
|
|
792
|
+
*/
|
|
793
|
+
function outputUrl(job) {
|
|
794
|
+
if (job.status !== "complete") return void 0;
|
|
795
|
+
return job.output.file?.url;
|
|
796
|
+
}
|
|
797
|
+
/**
|
|
798
|
+
* Typed accessor for a completed job's structured `data`, or `null`.
|
|
799
|
+
*
|
|
800
|
+
* `output.data` is job-type-specific (probe result, detections, transcript) and
|
|
801
|
+
* is `unknown` at the contract level — its shape is documented per job type, not
|
|
802
|
+
* by the API schema. This helper applies the caller-supplied type `T` so you can
|
|
803
|
+
* read `data` without sprinkling assertions at each call site. `T` is your claim
|
|
804
|
+
* about the shape for the job type you submitted; validate it yourself (e.g. with
|
|
805
|
+
* Zod) if the input is untrusted.
|
|
806
|
+
*
|
|
807
|
+
* Returns `null` for non-complete jobs and for jobs with no structured data
|
|
808
|
+
* (pure file transforms).
|
|
809
|
+
*
|
|
810
|
+
* @example
|
|
811
|
+
* type Metadata = { format: string; durationMs: number };
|
|
812
|
+
* const meta = jobData<Metadata>(await client.jobs.wait(id));
|
|
813
|
+
* if (meta) console.log(meta.durationMs);
|
|
814
|
+
*/
|
|
815
|
+
function jobData(job) {
|
|
816
|
+
if (job.status !== "complete") return null;
|
|
817
|
+
if (job.output.data == null) return null;
|
|
818
|
+
return job.output.data;
|
|
819
|
+
}
|
|
820
|
+
//#endregion
|
|
821
|
+
export { ApiError, createClient, isApiError, jobData, outputUrl };
|