@fotovid/sdk 0.1.0 → 0.2.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 CHANGED
@@ -1,6 +1,8 @@
1
1
  # @fotovid/sdk
2
2
 
3
- Thin, typed TypeScript/Node client for the [Fotovid](https://fotovid.co) media API. POST a URL, await the finished file — no ffmpeg binary in your bundle.
3
+ [![npm](https://img.shields.io/npm/v/@fotovid/sdk)](https://www.npmjs.com/package/@fotovid/sdk)
4
+
5
+ Thin, typed Node.js / TypeScript SDK for the [Fotovid](https://fotovid.co) media API — a serverless ffmpeg API for watermarking video and images, trimming video and audio, extracting audio from video, generating video thumbnails, and probing video metadata. POST a source URL, await the finished file over one HTTPS call. No ffmpeg binary, no native dependencies, nothing to install beyond this package.
4
6
 
5
7
  ## Install
6
8
 
@@ -42,13 +44,53 @@ Parameter names match the API 1:1 (`source_url`, `watermark_image_url`, …). Ev
42
44
  media operation returns `{ id, type, url, expires_at, duration? }`; `probe`
43
45
  returns video metadata.
44
46
 
47
+ ## Async (large or long video)
48
+
49
+ The sync methods above reject video over ~720p or 15s with a 400 asking you to
50
+ use the async endpoint (a hard limit — the sync API has a short time budget).
51
+ For a large video watermark, a long trim, or anything you don't need back in a
52
+ couple of seconds, submit a task instead and poll for the result:
53
+
54
+ ```ts
55
+ let task = await fotovid.tasks.video.watermark({
56
+ source_url: "https://cdn.example.com/1080x1920.mp4", // vertical / large video, rejected by sync
57
+ watermark_type: "image",
58
+ watermark_image_url: "https://cdn.example.com/logo.png",
59
+ });
60
+
61
+ while (task.status === "starting" || task.status === "processing") {
62
+ await new Promise((r) => setTimeout(r, 2000));
63
+ task = await fotovid.tasks.get(task.id);
64
+ }
65
+
66
+ if (task.status === "succeeded") {
67
+ console.log(task.outputs); // [{ kind: "video", url: "..." }, ...] — read by kind, order not guaranteed
68
+ } else {
69
+ console.error(task.error); // { code, message, request_id, detail? } — a failed task, not an exception
70
+ }
71
+ ```
72
+
73
+ `tasks.*` mirrors the sync methods 1:1 (`tasks.video.watermark`, `tasks.video.trim`,
74
+ `tasks.video.extractAudio`, `tasks.video.thumbnail`, `tasks.image.watermark`,
75
+ `tasks.audio.trim`) plus the low-level `tasks.create`/`tasks.get`. There's no
76
+ built-in polling helper — the loop above is the whole pattern. `probe` has no
77
+ async form; it's sync-only.
78
+
79
+ ## Sync vs async
80
+
81
+ | | Sync (`fotovid.video.*`, …) | Async (`fotovid.tasks.*`) |
82
+ | --- | --- | --- |
83
+ | Returns | Finished result, same call | A `Task` — poll `tasks.get` until terminal |
84
+ | Limits | ~720p / 15s | 4K / 600s |
85
+ | Use for | Small/short media, need the result now | Large video, long clips, batch/background jobs |
86
+
45
87
  ## Config
46
88
 
47
89
  ```ts
48
90
  new Fotovid({
49
91
  apiKey: "p6_<key_id>:<secret>", // or set FOTOVID_API_KEY
50
92
  baseUrl: "https://api.fotovid.co", // optional
51
- fetch: customFetch, // optional, defaults to global fetch (Node 18+)
93
+ fetch: customFetch, // optional, defaults to global fetch (Node 20+)
52
94
  });
53
95
  ```
54
96
 
@@ -67,6 +109,15 @@ await fotovid.video.watermark(input, opts);
67
109
  await fotovid.video.watermark(input, opts);
68
110
  ```
69
111
 
112
+ The same applies to `fotovid.tasks.*` — pass `idempotencyKey` there too (it travels
113
+ in the request body, not a header, but the SDK handles that difference for you).
114
+
115
+ ## Errors
116
+
117
+ A non-2xx response throws `FotovidError` (`status`, `detail`, `retryAfter`).
118
+ For `fotovid.tasks.*`, that's the only thing that throws — a task that finishes
119
+ as `"failed"` is a normal return value, not an exception; check `task.error`.
120
+
70
121
  ## License
71
122
 
72
123
  MIT
package/dist/index.cjs CHANGED
@@ -22,6 +22,7 @@ var index_exports = {};
22
22
  __export(index_exports, {
23
23
  Fotovid: () => Fotovid,
24
24
  FotovidError: () => FotovidError,
25
+ Tasks: () => Tasks,
25
26
  default: () => Fotovid
26
27
  });
27
28
  module.exports = __toCommonJS(index_exports);
@@ -54,6 +55,82 @@ var FotovidError = class extends Error {
54
55
  }
55
56
  };
56
57
 
58
+ // src/http.ts
59
+ async function requestJson(ctx, path, init) {
60
+ const response = await ctx.fetch(new URL(path, ctx.baseUrl), {
61
+ method: init.method,
62
+ headers: {
63
+ authorization: `Bearer ${ctx.apiKey}`,
64
+ // An explicit UA identifies the SDK to the edge and to server-side
65
+ // observability. (The Python SDK's default urllib UA was blocked by
66
+ // Cloudflare with a 403 — Node's fetch UA is not, but be explicit.)
67
+ "user-agent": `fotovid-sdk/${"0.2.0"}`,
68
+ ...init.body !== void 0 ? { "content-type": "application/json" } : {},
69
+ ...init.headers
70
+ },
71
+ body: init.body !== void 0 ? JSON.stringify(init.body) : void 0,
72
+ signal: init.signal
73
+ });
74
+ if (!response.ok) {
75
+ let detail;
76
+ try {
77
+ detail = await response.json();
78
+ } catch {
79
+ }
80
+ const ra = response.headers.get("retry-after");
81
+ const retryAfter = ra ? Number(ra) : void 0;
82
+ throw new FotovidError(response.status, detail, retryAfter);
83
+ }
84
+ return await response.json();
85
+ }
86
+
87
+ // src/tasks.ts
88
+ function taskBody(input, options) {
89
+ return {
90
+ type: input.type,
91
+ source_url: input.source_url,
92
+ params: input.params,
93
+ idempotency_key: options?.idempotencyKey ?? globalThis.crypto.randomUUID(),
94
+ webhook: options?.webhook,
95
+ webhook_events_filter: options?.webhookEventsFilter
96
+ };
97
+ }
98
+ var Tasks = class {
99
+ #ctx;
100
+ constructor(ctx) {
101
+ this.#ctx = ctx;
102
+ }
103
+ create(input, options) {
104
+ return requestJson(this.#ctx, "/v1/tasks", {
105
+ method: "POST",
106
+ body: taskBody(input, options)
107
+ });
108
+ }
109
+ /** `failed` is not thrown — check `task.error`. Only a transport failure throws `FotovidError`. */
110
+ get(id, options) {
111
+ return requestJson(this.#ctx, `/v1/tasks/${encodeURIComponent(id)}`, {
112
+ method: "GET",
113
+ signal: options?.signal
114
+ });
115
+ }
116
+ video = {
117
+ watermark: (input, options) => this.create({ type: "video.watermark", ...splitInput(input) }, options),
118
+ trim: (input, options) => this.create({ type: "video.trim", ...splitInput(input) }, options),
119
+ extractAudio: (input, options) => this.create({ type: "video.audio", ...splitInput(input) }, options),
120
+ thumbnail: (input, options) => this.create({ type: "video.cover", ...splitInput(input) }, options)
121
+ };
122
+ image = {
123
+ watermark: (input, options) => this.create({ type: "image.watermark", ...splitInput(input) }, options)
124
+ };
125
+ audio = {
126
+ trim: (input, options) => this.create({ type: "audio.trim", ...splitInput(input) }, options)
127
+ };
128
+ };
129
+ function splitInput(input) {
130
+ const { source_url, ...params } = input;
131
+ return { source_url, params };
132
+ }
133
+
57
134
  // src/client.ts
58
135
  var DEFAULT_BASE_URL = "https://api.fotovid.co";
59
136
  function resolveApiKey(explicit) {
@@ -69,35 +146,33 @@ var Fotovid = class {
69
146
  #apiKey;
70
147
  #baseUrl;
71
148
  #fetch;
149
+ /** Async task surface — submit larger jobs than sync accepts, then poll for the result. */
150
+ tasks;
72
151
  constructor(options = {}) {
73
152
  this.#apiKey = resolveApiKey(options.apiKey);
74
153
  this.#baseUrl = options.baseUrl ?? DEFAULT_BASE_URL;
75
154
  this.#fetch = options.fetch ?? globalThis.fetch;
155
+ this.tasks = new Tasks({
156
+ apiKey: this.#apiKey,
157
+ baseUrl: this.#baseUrl,
158
+ fetch: this.#fetch
159
+ });
76
160
  }
77
- async #post(path, input, options) {
161
+ #post(path, input, options) {
78
162
  const { source_url, ...params } = input;
79
- const response = await this.#fetch(new URL(path, this.#baseUrl), {
80
- method: "POST",
81
- headers: {
82
- "content-type": "application/json",
83
- authorization: `Bearer ${this.#apiKey}`,
84
- // Billed endpoints require an idempotency key; default to a fresh
85
- // UUID per call, overridable to make a retry replay (not re-charge).
86
- "idempotency-key": options?.idempotencyKey ?? globalThis.crypto.randomUUID()
87
- },
88
- body: JSON.stringify({ source_url, params })
89
- });
90
- if (!response.ok) {
91
- let detail;
92
- try {
93
- detail = await response.json();
94
- } catch {
163
+ return requestJson(
164
+ { apiKey: this.#apiKey, baseUrl: this.#baseUrl, fetch: this.#fetch },
165
+ path,
166
+ {
167
+ method: "POST",
168
+ body: { source_url, params },
169
+ headers: {
170
+ // Billed endpoints require an idempotency key; default to a fresh
171
+ // UUID per call, overridable to make a retry replay (not re-charge).
172
+ "idempotency-key": options?.idempotencyKey ?? globalThis.crypto.randomUUID()
173
+ }
95
174
  }
96
- const ra = response.headers.get("retry-after");
97
- const retryAfter = ra ? Number(ra) : void 0;
98
- throw new FotovidError(response.status, detail, retryAfter);
99
- }
100
- return await response.json();
175
+ );
101
176
  }
102
177
  video = {
103
178
  watermark: (input, options) => this.#post("/v1/video/watermark", input, options),
@@ -116,6 +191,7 @@ var Fotovid = class {
116
191
  // Annotate the CommonJS export names for ESM import in node:
117
192
  0 && (module.exports = {
118
193
  Fotovid,
119
- FotovidError
194
+ FotovidError,
195
+ Tasks
120
196
  });
121
197
  //# sourceMappingURL=index.cjs.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts","../src/error.ts","../src/client.ts"],"sourcesContent":["export { Fotovid, Fotovid as default } from \"./client.js\";\nexport { FotovidError } from \"./error.js\";\nexport type {\n\tExtractAudioInput,\n\tFotovidOptions,\n\tImageFormat,\n\tImageWatermarkInput,\n\tMediaResult,\n\tProbeInput,\n\tProbeResult,\n\tRequestOptions,\n\tThumbnailInput,\n\tTrimAudioInput,\n\tTrimInput,\n\tVideoWatermarkInput,\n\tWatermarkPosition,\n\tWatermarkType,\n\tX264Preset,\n} from \"./types.js\";\n","function isRecord(value: unknown): value is Record<string, unknown> {\n\treturn typeof value === \"object\" && value !== null;\n}\n\n/** Thrown when the Fotovid API returns a non-2xx response. */\nexport class FotovidError extends Error {\n\t/** HTTP status code of the failed response. */\n\treadonly status: number;\n\t/** Parsed error body, when the response carried one. */\n\treadonly detail: unknown;\n\t/** Seconds to wait before retrying, from the `Retry-After` header (e.g. 429/503). */\n\treadonly retryAfter?: number;\n\n\tconstructor(status: number, detail: unknown, retryAfter?: number) {\n\t\tlet message: string | undefined;\n\t\tif (isRecord(detail)) {\n\t\t\tif (typeof detail.detail === \"string\") {\n\t\t\t\tmessage = detail.detail;\n\t\t\t} else if (typeof detail.message === \"string\") {\n\t\t\t\tmessage = detail.message;\n\t\t\t}\n\t\t}\n\t\tsuper(message ?? `Fotovid API request failed with status ${status}`);\n\t\tthis.name = \"FotovidError\";\n\t\tthis.status = status;\n\t\tthis.detail = detail;\n\t\tthis.retryAfter = retryAfter;\n\t}\n}\n","import { FotovidError } from \"./error.js\";\nimport type {\n\tExtractAudioInput,\n\tFotovidOptions,\n\tImageWatermarkInput,\n\tMediaResult,\n\tProbeInput,\n\tProbeResult,\n\tRequestOptions,\n\tThumbnailInput,\n\tTrimAudioInput,\n\tTrimInput,\n\tVideoWatermarkInput,\n} from \"./types.js\";\n\nconst DEFAULT_BASE_URL = \"https://api.fotovid.co\";\n\nfunction resolveApiKey(explicit: string | undefined): string {\n\tconst key =\n\t\texplicit ??\n\t\t(typeof process !== \"undefined\" ? process.env?.FOTOVID_API_KEY : undefined);\n\tif (!key) {\n\t\tthrow new Error(\n\t\t\t\"Fotovid: missing API key. Pass { apiKey } or set FOTOVID_API_KEY.\",\n\t\t);\n\t}\n\treturn key;\n}\n\n/**\n * Typed client for the Fotovid media API. Methods mirror the REST resources\n * (`client.video.*`, `client.image.*`, `client.audio.*`); parameter names\n * match the API 1:1 (`source_url`, `watermark_image_url`, …).\n */\nexport class Fotovid {\n\treadonly #apiKey: string;\n\treadonly #baseUrl: string;\n\treadonly #fetch: typeof globalThis.fetch;\n\n\tconstructor(options: FotovidOptions = {}) {\n\t\tthis.#apiKey = resolveApiKey(options.apiKey);\n\t\tthis.#baseUrl = options.baseUrl ?? DEFAULT_BASE_URL;\n\t\tthis.#fetch = options.fetch ?? globalThis.fetch;\n\t}\n\n\tasync #post<T>(\n\t\tpath: string,\n\t\tinput: { source_url: string },\n\t\toptions?: RequestOptions,\n\t): Promise<T> {\n\t\tconst { source_url, ...params } = input;\n\t\tconst response = await this.#fetch(new URL(path, this.#baseUrl), {\n\t\t\tmethod: \"POST\",\n\t\t\theaders: {\n\t\t\t\t\"content-type\": \"application/json\",\n\t\t\t\tauthorization: `Bearer ${this.#apiKey}`,\n\t\t\t\t// Billed endpoints require an idempotency key; default to a fresh\n\t\t\t\t// UUID per call, overridable to make a retry replay (not re-charge).\n\t\t\t\t\"idempotency-key\":\n\t\t\t\t\toptions?.idempotencyKey ?? globalThis.crypto.randomUUID(),\n\t\t\t},\n\t\t\tbody: JSON.stringify({ source_url, params }),\n\t\t});\n\t\tif (!response.ok) {\n\t\t\tlet detail: unknown;\n\t\t\ttry {\n\t\t\t\tdetail = await response.json();\n\t\t\t} catch {\n\t\t\t\t// non-JSON error body — leave detail undefined\n\t\t\t}\n\t\t\tconst ra = response.headers.get(\"retry-after\");\n\t\t\tconst retryAfter = ra ? Number(ra) : undefined;\n\t\t\tthrow new FotovidError(response.status, detail, retryAfter);\n\t\t}\n\t\treturn (await response.json()) as T;\n\t}\n\n\treadonly video = {\n\t\twatermark: (\n\t\t\tinput: VideoWatermarkInput,\n\t\t\toptions?: RequestOptions,\n\t\t): Promise<MediaResult> =>\n\t\t\tthis.#post<MediaResult>(\"/v1/video/watermark\", input, options),\n\t\ttrim: (input: TrimInput, options?: RequestOptions): Promise<MediaResult> =>\n\t\t\tthis.#post<MediaResult>(\"/v1/video/trim\", input, options),\n\t\textractAudio: (\n\t\t\tinput: ExtractAudioInput,\n\t\t\toptions?: RequestOptions,\n\t\t): Promise<MediaResult> =>\n\t\t\tthis.#post<MediaResult>(\"/v1/video/extract-audio\", input, options),\n\t\tthumbnail: (\n\t\t\tinput: ThumbnailInput,\n\t\t\toptions?: RequestOptions,\n\t\t): Promise<MediaResult> =>\n\t\t\tthis.#post<MediaResult>(\"/v1/video/extract-cover\", input, options),\n\t\tprobe: (\n\t\t\tinput: ProbeInput,\n\t\t\toptions?: RequestOptions,\n\t\t): Promise<ProbeResult> =>\n\t\t\tthis.#post<ProbeResult>(\"/v1/video/probe\", input, options),\n\t};\n\n\treadonly image = {\n\t\twatermark: (\n\t\t\tinput: ImageWatermarkInput,\n\t\t\toptions?: RequestOptions,\n\t\t): Promise<MediaResult> =>\n\t\t\tthis.#post<MediaResult>(\"/v1/image/watermark\", input, options),\n\t};\n\n\treadonly audio = {\n\t\ttrim: (\n\t\t\tinput: TrimAudioInput,\n\t\t\toptions?: RequestOptions,\n\t\t): Promise<MediaResult> =>\n\t\t\tthis.#post<MediaResult>(\"/v1/audio/trim\", input, options),\n\t};\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACAA,SAAS,SAAS,OAAkD;AACnE,SAAO,OAAO,UAAU,YAAY,UAAU;AAC/C;AAGO,IAAM,eAAN,cAA2B,MAAM;AAAA;AAAA,EAE9B;AAAA;AAAA,EAEA;AAAA;AAAA,EAEA;AAAA,EAET,YAAY,QAAgB,QAAiB,YAAqB;AACjE,QAAI;AACJ,QAAI,SAAS,MAAM,GAAG;AACrB,UAAI,OAAO,OAAO,WAAW,UAAU;AACtC,kBAAU,OAAO;AAAA,MAClB,WAAW,OAAO,OAAO,YAAY,UAAU;AAC9C,kBAAU,OAAO;AAAA,MAClB;AAAA,IACD;AACA,UAAM,WAAW,0CAA0C,MAAM,EAAE;AACnE,SAAK,OAAO;AACZ,SAAK,SAAS;AACd,SAAK,SAAS;AACd,SAAK,aAAa;AAAA,EACnB;AACD;;;ACbA,IAAM,mBAAmB;AAEzB,SAAS,cAAc,UAAsC;AAC5D,QAAM,MACL,aACC,OAAO,YAAY,cAAc,QAAQ,KAAK,kBAAkB;AAClE,MAAI,CAAC,KAAK;AACT,UAAM,IAAI;AAAA,MACT;AAAA,IACD;AAAA,EACD;AACA,SAAO;AACR;AAOO,IAAM,UAAN,MAAc;AAAA,EACX;AAAA,EACA;AAAA,EACA;AAAA,EAET,YAAY,UAA0B,CAAC,GAAG;AACzC,SAAK,UAAU,cAAc,QAAQ,MAAM;AAC3C,SAAK,WAAW,QAAQ,WAAW;AACnC,SAAK,SAAS,QAAQ,SAAS,WAAW;AAAA,EAC3C;AAAA,EAEA,MAAM,MACL,MACA,OACA,SACa;AACb,UAAM,EAAE,YAAY,GAAG,OAAO,IAAI;AAClC,UAAM,WAAW,MAAM,KAAK,OAAO,IAAI,IAAI,MAAM,KAAK,QAAQ,GAAG;AAAA,MAChE,QAAQ;AAAA,MACR,SAAS;AAAA,QACR,gBAAgB;AAAA,QAChB,eAAe,UAAU,KAAK,OAAO;AAAA;AAAA;AAAA,QAGrC,mBACC,SAAS,kBAAkB,WAAW,OAAO,WAAW;AAAA,MAC1D;AAAA,MACA,MAAM,KAAK,UAAU,EAAE,YAAY,OAAO,CAAC;AAAA,IAC5C,CAAC;AACD,QAAI,CAAC,SAAS,IAAI;AACjB,UAAI;AACJ,UAAI;AACH,iBAAS,MAAM,SAAS,KAAK;AAAA,MAC9B,QAAQ;AAAA,MAER;AACA,YAAM,KAAK,SAAS,QAAQ,IAAI,aAAa;AAC7C,YAAM,aAAa,KAAK,OAAO,EAAE,IAAI;AACrC,YAAM,IAAI,aAAa,SAAS,QAAQ,QAAQ,UAAU;AAAA,IAC3D;AACA,WAAQ,MAAM,SAAS,KAAK;AAAA,EAC7B;AAAA,EAES,QAAQ;AAAA,IAChB,WAAW,CACV,OACA,YAEA,KAAK,MAAmB,uBAAuB,OAAO,OAAO;AAAA,IAC9D,MAAM,CAAC,OAAkB,YACxB,KAAK,MAAmB,kBAAkB,OAAO,OAAO;AAAA,IACzD,cAAc,CACb,OACA,YAEA,KAAK,MAAmB,2BAA2B,OAAO,OAAO;AAAA,IAClE,WAAW,CACV,OACA,YAEA,KAAK,MAAmB,2BAA2B,OAAO,OAAO;AAAA,IAClE,OAAO,CACN,OACA,YAEA,KAAK,MAAmB,mBAAmB,OAAO,OAAO;AAAA,EAC3D;AAAA,EAES,QAAQ;AAAA,IAChB,WAAW,CACV,OACA,YAEA,KAAK,MAAmB,uBAAuB,OAAO,OAAO;AAAA,EAC/D;AAAA,EAES,QAAQ;AAAA,IAChB,MAAM,CACL,OACA,YAEA,KAAK,MAAmB,kBAAkB,OAAO,OAAO;AAAA,EAC1D;AACD;","names":[]}
1
+ {"version":3,"sources":["../src/index.ts","../src/error.ts","../src/http.ts","../src/tasks.ts","../src/client.ts"],"sourcesContent":["export { Fotovid, Fotovid as default } from \"./client.js\";\nexport { FotovidError } from \"./error.js\";\nexport type {\n\tCreateTaskInput,\n\tTask,\n\tTaskError,\n\tTaskErrorCode,\n\tTaskOutput,\n\tTaskRequestOptions,\n\tTaskStatus,\n\tTaskType,\n\tWebhookEvent,\n} from \"./tasks.js\";\nexport { Tasks } from \"./tasks.js\";\nexport type {\n\tExtractAudioInput,\n\tFotovidOptions,\n\tImageFormat,\n\tImageWatermarkInput,\n\tMediaResult,\n\tProbeInput,\n\tProbeResult,\n\tRequestOptions,\n\tThumbnailInput,\n\tTrimAudioInput,\n\tTrimInput,\n\tVideoWatermarkInput,\n\tWatermarkPosition,\n\tWatermarkType,\n\tX264Preset,\n} from \"./types.js\";\n","function isRecord(value: unknown): value is Record<string, unknown> {\n\treturn typeof value === \"object\" && value !== null;\n}\n\n/** Thrown when the Fotovid API returns a non-2xx response. */\nexport class FotovidError extends Error {\n\t/** HTTP status code of the failed response. */\n\treadonly status: number;\n\t/** Parsed error body, when the response carried one. */\n\treadonly detail: unknown;\n\t/** Seconds to wait before retrying, from the `Retry-After` header (e.g. 429/503). */\n\treadonly retryAfter?: number;\n\n\tconstructor(status: number, detail: unknown, retryAfter?: number) {\n\t\tlet message: string | undefined;\n\t\tif (isRecord(detail)) {\n\t\t\tif (typeof detail.detail === \"string\") {\n\t\t\t\tmessage = detail.detail;\n\t\t\t} else if (typeof detail.message === \"string\") {\n\t\t\t\tmessage = detail.message;\n\t\t\t}\n\t\t}\n\t\tsuper(message ?? `Fotovid API request failed with status ${status}`);\n\t\tthis.name = \"FotovidError\";\n\t\tthis.status = status;\n\t\tthis.detail = detail;\n\t\tthis.retryAfter = retryAfter;\n\t}\n}\n","import { FotovidError } from \"./error.js\";\n\n// Injected at build time from package.json by tsup (see tsup.config.ts).\ndeclare const __SDK_VERSION__: string;\n\nexport interface RequestContext {\n\tapiKey: string;\n\tbaseUrl: string;\n\tfetch: typeof globalThis.fetch;\n}\n\nexport interface JsonRequestInit {\n\tmethod: \"GET\" | \"POST\";\n\tbody?: unknown;\n\theaders?: Record<string, string>;\n\tsignal?: AbortSignal;\n}\n\n/**\n * Shared low-level request used by both the sync (client.ts) and the async\n * (tasks.ts) surfaces — same auth, UA, and error handling either way.\n */\nexport async function requestJson<T>(\n\tctx: RequestContext,\n\tpath: string,\n\tinit: JsonRequestInit,\n): Promise<T> {\n\tconst response = await ctx.fetch(new URL(path, ctx.baseUrl), {\n\t\tmethod: init.method,\n\t\theaders: {\n\t\t\tauthorization: `Bearer ${ctx.apiKey}`,\n\t\t\t// An explicit UA identifies the SDK to the edge and to server-side\n\t\t\t// observability. (The Python SDK's default urllib UA was blocked by\n\t\t\t// Cloudflare with a 403 — Node's fetch UA is not, but be explicit.)\n\t\t\t\"user-agent\": `fotovid-sdk/${__SDK_VERSION__}`,\n\t\t\t...(init.body !== undefined\n\t\t\t\t? { \"content-type\": \"application/json\" }\n\t\t\t\t: {}),\n\t\t\t...init.headers,\n\t\t},\n\t\tbody: init.body !== undefined ? JSON.stringify(init.body) : undefined,\n\t\tsignal: init.signal,\n\t});\n\tif (!response.ok) {\n\t\tlet detail: unknown;\n\t\ttry {\n\t\t\tdetail = await response.json();\n\t\t} catch {\n\t\t\t// non-JSON error body — leave detail undefined\n\t\t}\n\t\tconst ra = response.headers.get(\"retry-after\");\n\t\tconst retryAfter = ra ? Number(ra) : undefined;\n\t\tthrow new FotovidError(response.status, detail, retryAfter);\n\t}\n\treturn (await response.json()) as T;\n}\n","import { type RequestContext, requestJson } from \"./http.js\";\nimport type {\n\tExtractAudioInput,\n\tImageWatermarkInput,\n\tThumbnailInput,\n\tTrimAudioInput,\n\tTrimInput,\n\tVideoWatermarkInput,\n} from \"./types.js\";\n\n/**\n * Async task type. Deliberately excludes `\"video.meta\"` — the async worker\n * always fails it (probe has no artifact to produce); use the sync\n * `client.video.probe` instead.\n */\nexport type TaskType =\n\t| \"image.watermark\"\n\t| \"video.watermark\"\n\t| \"video.cover\"\n\t| \"video.trim\"\n\t| \"video.audio\"\n\t| \"audio.trim\";\n\nexport type TaskStatus =\n\t| \"starting\"\n\t| \"processing\"\n\t| \"succeeded\"\n\t| \"failed\"\n\t| \"canceled\";\n\nexport type WebhookEvent = \"completed\" | \"failed\";\n\n/** Stable, closed set of task-level error codes (`taskerr.ExternalCode`). */\nexport type TaskErrorCode =\n\t| \"invalid_input\"\n\t| \"unsupported_media\"\n\t| \"source_unreachable\"\n\t| \"source_too_large\"\n\t| \"unknown_task_type\"\n\t| \"insufficient_credits\"\n\t| \"processing_failed\"\n\t| \"internal_error\";\n\n/** One produced artifact. `kind` is `\"video\" | \"cover\" | \"image\" | \"audio\"`. */\nexport interface TaskOutput {\n\tkind: string;\n\turl: string;\n}\n\n/**\n * Task-level failure, carried in `Task.error` on a `\"failed\"` task. Distinct\n * from `FotovidError`, which is thrown for transport-layer failures (a\n * non-2xx response to create/get itself).\n */\nexport interface TaskError {\n\tcode: TaskErrorCode;\n\tmessage: string;\n\trequest_id: string;\n\tdetail?: string;\n}\n\nexport interface Task {\n\tid: string;\n\tstatus: TaskStatus;\n\ttask_type?: string;\n\t/** Produced artifacts, once `status` is `\"succeeded\"`. Order is not guaranteed — read by `kind`. */\n\toutputs?: TaskOutput[];\n\t/** Present when `status` is `\"failed\"`. A failed task is not an exception — check this field. */\n\terror: TaskError | null;\n\tsource: string;\n\tdata_removed: boolean;\n\tcreated_at: string;\n\tstarted_at?: string;\n\tcompleted_at?: string;\n\t/** RFC 3339, empty until the task succeeds. Output artifacts expire 7 days after completion. */\n\texpires_at?: string;\n\tmetrics?: { total_time?: number };\n\t/** `get`: this task's own polling URL — not a download link. */\n\turls: { get: string };\n}\n\nexport interface CreateTaskInput {\n\ttype: TaskType;\n\tsource_url: string;\n\tparams?: Record<string, unknown>;\n}\n\nexport interface TaskRequestOptions {\n\t/**\n\t * Idempotency key (≤64 chars). Defaults to a fresh UUID per call, like the\n\t * sync surface — unlike sync, this is a JSON body field, not a header.\n\t */\n\tidempotencyKey?: string;\n\t/** URL notified (HMAC-signed) when the task reaches a terminal state. */\n\twebhook?: string;\n\t/** Restrict which terminal states trigger the webhook. Default: all. */\n\twebhookEventsFilter?: WebhookEvent[];\n\tsignal?: AbortSignal;\n}\n\nfunction taskBody(\n\tinput: CreateTaskInput,\n\toptions: TaskRequestOptions | undefined,\n) {\n\treturn {\n\t\ttype: input.type,\n\t\tsource_url: input.source_url,\n\t\tparams: input.params,\n\t\tidempotency_key: options?.idempotencyKey ?? globalThis.crypto.randomUUID(),\n\t\twebhook: options?.webhook,\n\t\twebhook_events_filter: options?.webhookEventsFilter,\n\t};\n}\n\n/**\n * Async task surface: submit larger jobs than the sync endpoints accept, then\n * poll for the result. There is no built-in `wait` — poll `get` yourself:\n *\n * ```ts\n * let task = await client.tasks.video.watermark({ source_url, ...params });\n * while (task.status === \"starting\" || task.status === \"processing\") {\n * await new Promise((r) => setTimeout(r, 2000));\n * task = await client.tasks.get(task.id);\n * }\n * ```\n */\nexport class Tasks {\n\treadonly #ctx: RequestContext;\n\n\tconstructor(ctx: RequestContext) {\n\t\tthis.#ctx = ctx;\n\t}\n\n\tcreate(input: CreateTaskInput, options?: TaskRequestOptions): Promise<Task> {\n\t\treturn requestJson<Task>(this.#ctx, \"/v1/tasks\", {\n\t\t\tmethod: \"POST\",\n\t\t\tbody: taskBody(input, options),\n\t\t});\n\t}\n\n\t/** `failed` is not thrown — check `task.error`. Only a transport failure throws `FotovidError`. */\n\tget(id: string, options?: { signal?: AbortSignal }): Promise<Task> {\n\t\treturn requestJson<Task>(this.#ctx, `/v1/tasks/${encodeURIComponent(id)}`, {\n\t\t\tmethod: \"GET\",\n\t\t\tsignal: options?.signal,\n\t\t});\n\t}\n\n\treadonly video = {\n\t\twatermark: (input: VideoWatermarkInput, options?: TaskRequestOptions) =>\n\t\t\tthis.create({ type: \"video.watermark\", ...splitInput(input) }, options),\n\t\ttrim: (input: TrimInput, options?: TaskRequestOptions) =>\n\t\t\tthis.create({ type: \"video.trim\", ...splitInput(input) }, options),\n\t\textractAudio: (input: ExtractAudioInput, options?: TaskRequestOptions) =>\n\t\t\tthis.create({ type: \"video.audio\", ...splitInput(input) }, options),\n\t\tthumbnail: (input: ThumbnailInput, options?: TaskRequestOptions) =>\n\t\t\tthis.create({ type: \"video.cover\", ...splitInput(input) }, options),\n\t};\n\n\treadonly image = {\n\t\twatermark: (input: ImageWatermarkInput, options?: TaskRequestOptions) =>\n\t\t\tthis.create({ type: \"image.watermark\", ...splitInput(input) }, options),\n\t};\n\n\treadonly audio = {\n\t\ttrim: (input: TrimAudioInput, options?: TaskRequestOptions) =>\n\t\t\tthis.create({ type: \"audio.trim\", ...splitInput(input) }, options),\n\t};\n}\n\n/** Splits a sync-style `{ source_url, ...params }` input into the task envelope shape. */\nfunction splitInput(input: { source_url: string }): {\n\tsource_url: string;\n\tparams: Record<string, unknown>;\n} {\n\tconst { source_url, ...params } = input;\n\treturn { source_url, params };\n}\n","import { requestJson } from \"./http.js\";\nimport { Tasks } from \"./tasks.js\";\nimport type {\n\tExtractAudioInput,\n\tFotovidOptions,\n\tImageWatermarkInput,\n\tMediaResult,\n\tProbeInput,\n\tProbeResult,\n\tRequestOptions,\n\tThumbnailInput,\n\tTrimAudioInput,\n\tTrimInput,\n\tVideoWatermarkInput,\n} from \"./types.js\";\n\nconst DEFAULT_BASE_URL = \"https://api.fotovid.co\";\n\nfunction resolveApiKey(explicit: string | undefined): string {\n\tconst key =\n\t\texplicit ??\n\t\t(typeof process !== \"undefined\" ? process.env?.FOTOVID_API_KEY : undefined);\n\tif (!key) {\n\t\tthrow new Error(\n\t\t\t\"Fotovid: missing API key. Pass { apiKey } or set FOTOVID_API_KEY.\",\n\t\t);\n\t}\n\treturn key;\n}\n\n/**\n * Typed client for the Fotovid media API. Methods mirror the REST resources\n * (`client.video.*`, `client.image.*`, `client.audio.*`); parameter names\n * match the API 1:1 (`source_url`, `watermark_image_url`, …).\n */\nexport class Fotovid {\n\treadonly #apiKey: string;\n\treadonly #baseUrl: string;\n\treadonly #fetch: typeof globalThis.fetch;\n\n\t/** Async task surface — submit larger jobs than sync accepts, then poll for the result. */\n\treadonly tasks: Tasks;\n\n\tconstructor(options: FotovidOptions = {}) {\n\t\tthis.#apiKey = resolveApiKey(options.apiKey);\n\t\tthis.#baseUrl = options.baseUrl ?? DEFAULT_BASE_URL;\n\t\tthis.#fetch = options.fetch ?? globalThis.fetch;\n\t\tthis.tasks = new Tasks({\n\t\t\tapiKey: this.#apiKey,\n\t\t\tbaseUrl: this.#baseUrl,\n\t\t\tfetch: this.#fetch,\n\t\t});\n\t}\n\n\t#post<T>(\n\t\tpath: string,\n\t\tinput: { source_url: string },\n\t\toptions?: RequestOptions,\n\t): Promise<T> {\n\t\tconst { source_url, ...params } = input;\n\t\treturn requestJson<T>(\n\t\t\t{ apiKey: this.#apiKey, baseUrl: this.#baseUrl, fetch: this.#fetch },\n\t\t\tpath,\n\t\t\t{\n\t\t\t\tmethod: \"POST\",\n\t\t\t\tbody: { source_url, params },\n\t\t\t\theaders: {\n\t\t\t\t\t// Billed endpoints require an idempotency key; default to a fresh\n\t\t\t\t\t// UUID per call, overridable to make a retry replay (not re-charge).\n\t\t\t\t\t\"idempotency-key\":\n\t\t\t\t\t\toptions?.idempotencyKey ?? globalThis.crypto.randomUUID(),\n\t\t\t\t},\n\t\t\t},\n\t\t);\n\t}\n\n\treadonly video = {\n\t\twatermark: (\n\t\t\tinput: VideoWatermarkInput,\n\t\t\toptions?: RequestOptions,\n\t\t): Promise<MediaResult> =>\n\t\t\tthis.#post<MediaResult>(\"/v1/video/watermark\", input, options),\n\t\ttrim: (input: TrimInput, options?: RequestOptions): Promise<MediaResult> =>\n\t\t\tthis.#post<MediaResult>(\"/v1/video/trim\", input, options),\n\t\textractAudio: (\n\t\t\tinput: ExtractAudioInput,\n\t\t\toptions?: RequestOptions,\n\t\t): Promise<MediaResult> =>\n\t\t\tthis.#post<MediaResult>(\"/v1/video/extract-audio\", input, options),\n\t\tthumbnail: (\n\t\t\tinput: ThumbnailInput,\n\t\t\toptions?: RequestOptions,\n\t\t): Promise<MediaResult> =>\n\t\t\tthis.#post<MediaResult>(\"/v1/video/extract-cover\", input, options),\n\t\tprobe: (\n\t\t\tinput: ProbeInput,\n\t\t\toptions?: RequestOptions,\n\t\t): Promise<ProbeResult> =>\n\t\t\tthis.#post<ProbeResult>(\"/v1/video/probe\", input, options),\n\t};\n\n\treadonly image = {\n\t\twatermark: (\n\t\t\tinput: ImageWatermarkInput,\n\t\t\toptions?: RequestOptions,\n\t\t): Promise<MediaResult> =>\n\t\t\tthis.#post<MediaResult>(\"/v1/image/watermark\", input, options),\n\t};\n\n\treadonly audio = {\n\t\ttrim: (\n\t\t\tinput: TrimAudioInput,\n\t\t\toptions?: RequestOptions,\n\t\t): Promise<MediaResult> =>\n\t\t\tthis.#post<MediaResult>(\"/v1/audio/trim\", input, options),\n\t};\n}\n"],"mappings":"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","names":[]}
package/dist/index.d.cts CHANGED
@@ -1,3 +1,9 @@
1
+ interface RequestContext {
2
+ apiKey: string;
3
+ baseUrl: string;
4
+ fetch: typeof globalThis.fetch;
5
+ }
6
+
1
7
  /** Anchor position for a watermark overlay. */
2
8
  type WatermarkPosition = "top-left" | "top-right" | "bottom-left" | "bottom-right" | "center";
3
9
  /** Output image format for image-producing operations. */
@@ -100,6 +106,106 @@ interface RequestOptions {
100
106
  idempotencyKey?: string;
101
107
  }
102
108
 
109
+ /**
110
+ * Async task type. Deliberately excludes `"video.meta"` — the async worker
111
+ * always fails it (probe has no artifact to produce); use the sync
112
+ * `client.video.probe` instead.
113
+ */
114
+ type TaskType = "image.watermark" | "video.watermark" | "video.cover" | "video.trim" | "video.audio" | "audio.trim";
115
+ type TaskStatus = "starting" | "processing" | "succeeded" | "failed" | "canceled";
116
+ type WebhookEvent = "completed" | "failed";
117
+ /** Stable, closed set of task-level error codes (`taskerr.ExternalCode`). */
118
+ type TaskErrorCode = "invalid_input" | "unsupported_media" | "source_unreachable" | "source_too_large" | "unknown_task_type" | "insufficient_credits" | "processing_failed" | "internal_error";
119
+ /** One produced artifact. `kind` is `"video" | "cover" | "image" | "audio"`. */
120
+ interface TaskOutput {
121
+ kind: string;
122
+ url: string;
123
+ }
124
+ /**
125
+ * Task-level failure, carried in `Task.error` on a `"failed"` task. Distinct
126
+ * from `FotovidError`, which is thrown for transport-layer failures (a
127
+ * non-2xx response to create/get itself).
128
+ */
129
+ interface TaskError {
130
+ code: TaskErrorCode;
131
+ message: string;
132
+ request_id: string;
133
+ detail?: string;
134
+ }
135
+ interface Task {
136
+ id: string;
137
+ status: TaskStatus;
138
+ task_type?: string;
139
+ /** Produced artifacts, once `status` is `"succeeded"`. Order is not guaranteed — read by `kind`. */
140
+ outputs?: TaskOutput[];
141
+ /** Present when `status` is `"failed"`. A failed task is not an exception — check this field. */
142
+ error: TaskError | null;
143
+ source: string;
144
+ data_removed: boolean;
145
+ created_at: string;
146
+ started_at?: string;
147
+ completed_at?: string;
148
+ /** RFC 3339, empty until the task succeeds. Output artifacts expire 7 days after completion. */
149
+ expires_at?: string;
150
+ metrics?: {
151
+ total_time?: number;
152
+ };
153
+ /** `get`: this task's own polling URL — not a download link. */
154
+ urls: {
155
+ get: string;
156
+ };
157
+ }
158
+ interface CreateTaskInput {
159
+ type: TaskType;
160
+ source_url: string;
161
+ params?: Record<string, unknown>;
162
+ }
163
+ interface TaskRequestOptions {
164
+ /**
165
+ * Idempotency key (≤64 chars). Defaults to a fresh UUID per call, like the
166
+ * sync surface — unlike sync, this is a JSON body field, not a header.
167
+ */
168
+ idempotencyKey?: string;
169
+ /** URL notified (HMAC-signed) when the task reaches a terminal state. */
170
+ webhook?: string;
171
+ /** Restrict which terminal states trigger the webhook. Default: all. */
172
+ webhookEventsFilter?: WebhookEvent[];
173
+ signal?: AbortSignal;
174
+ }
175
+ /**
176
+ * Async task surface: submit larger jobs than the sync endpoints accept, then
177
+ * poll for the result. There is no built-in `wait` — poll `get` yourself:
178
+ *
179
+ * ```ts
180
+ * let task = await client.tasks.video.watermark({ source_url, ...params });
181
+ * while (task.status === "starting" || task.status === "processing") {
182
+ * await new Promise((r) => setTimeout(r, 2000));
183
+ * task = await client.tasks.get(task.id);
184
+ * }
185
+ * ```
186
+ */
187
+ declare class Tasks {
188
+ #private;
189
+ constructor(ctx: RequestContext);
190
+ create(input: CreateTaskInput, options?: TaskRequestOptions): Promise<Task>;
191
+ /** `failed` is not thrown — check `task.error`. Only a transport failure throws `FotovidError`. */
192
+ get(id: string, options?: {
193
+ signal?: AbortSignal;
194
+ }): Promise<Task>;
195
+ readonly video: {
196
+ watermark: (input: VideoWatermarkInput, options?: TaskRequestOptions) => Promise<Task>;
197
+ trim: (input: TrimInput, options?: TaskRequestOptions) => Promise<Task>;
198
+ extractAudio: (input: ExtractAudioInput, options?: TaskRequestOptions) => Promise<Task>;
199
+ thumbnail: (input: ThumbnailInput, options?: TaskRequestOptions) => Promise<Task>;
200
+ };
201
+ readonly image: {
202
+ watermark: (input: ImageWatermarkInput, options?: TaskRequestOptions) => Promise<Task>;
203
+ };
204
+ readonly audio: {
205
+ trim: (input: TrimAudioInput, options?: TaskRequestOptions) => Promise<Task>;
206
+ };
207
+ }
208
+
103
209
  /**
104
210
  * Typed client for the Fotovid media API. Methods mirror the REST resources
105
211
  * (`client.video.*`, `client.image.*`, `client.audio.*`); parameter names
@@ -107,6 +213,8 @@ interface RequestOptions {
107
213
  */
108
214
  declare class Fotovid {
109
215
  #private;
216
+ /** Async task surface — submit larger jobs than sync accepts, then poll for the result. */
217
+ readonly tasks: Tasks;
110
218
  constructor(options?: FotovidOptions);
111
219
  readonly video: {
112
220
  watermark: (input: VideoWatermarkInput, options?: RequestOptions) => Promise<MediaResult>;
@@ -134,4 +242,4 @@ declare class FotovidError extends Error {
134
242
  constructor(status: number, detail: unknown, retryAfter?: number);
135
243
  }
136
244
 
137
- export { type ExtractAudioInput, Fotovid, FotovidError, type FotovidOptions, type ImageFormat, type ImageWatermarkInput, type MediaResult, type ProbeInput, type ProbeResult, type RequestOptions, type ThumbnailInput, type TrimAudioInput, type TrimInput, type VideoWatermarkInput, type WatermarkPosition, type WatermarkType, type X264Preset, Fotovid as default };
245
+ export { type CreateTaskInput, type ExtractAudioInput, Fotovid, FotovidError, type FotovidOptions, type ImageFormat, type ImageWatermarkInput, type MediaResult, type ProbeInput, type ProbeResult, type RequestOptions, type Task, type TaskError, type TaskErrorCode, type TaskOutput, type TaskRequestOptions, type TaskStatus, type TaskType, Tasks, type ThumbnailInput, type TrimAudioInput, type TrimInput, type VideoWatermarkInput, type WatermarkPosition, type WatermarkType, type WebhookEvent, type X264Preset, Fotovid as default };
package/dist/index.d.ts CHANGED
@@ -1,3 +1,9 @@
1
+ interface RequestContext {
2
+ apiKey: string;
3
+ baseUrl: string;
4
+ fetch: typeof globalThis.fetch;
5
+ }
6
+
1
7
  /** Anchor position for a watermark overlay. */
2
8
  type WatermarkPosition = "top-left" | "top-right" | "bottom-left" | "bottom-right" | "center";
3
9
  /** Output image format for image-producing operations. */
@@ -100,6 +106,106 @@ interface RequestOptions {
100
106
  idempotencyKey?: string;
101
107
  }
102
108
 
109
+ /**
110
+ * Async task type. Deliberately excludes `"video.meta"` — the async worker
111
+ * always fails it (probe has no artifact to produce); use the sync
112
+ * `client.video.probe` instead.
113
+ */
114
+ type TaskType = "image.watermark" | "video.watermark" | "video.cover" | "video.trim" | "video.audio" | "audio.trim";
115
+ type TaskStatus = "starting" | "processing" | "succeeded" | "failed" | "canceled";
116
+ type WebhookEvent = "completed" | "failed";
117
+ /** Stable, closed set of task-level error codes (`taskerr.ExternalCode`). */
118
+ type TaskErrorCode = "invalid_input" | "unsupported_media" | "source_unreachable" | "source_too_large" | "unknown_task_type" | "insufficient_credits" | "processing_failed" | "internal_error";
119
+ /** One produced artifact. `kind` is `"video" | "cover" | "image" | "audio"`. */
120
+ interface TaskOutput {
121
+ kind: string;
122
+ url: string;
123
+ }
124
+ /**
125
+ * Task-level failure, carried in `Task.error` on a `"failed"` task. Distinct
126
+ * from `FotovidError`, which is thrown for transport-layer failures (a
127
+ * non-2xx response to create/get itself).
128
+ */
129
+ interface TaskError {
130
+ code: TaskErrorCode;
131
+ message: string;
132
+ request_id: string;
133
+ detail?: string;
134
+ }
135
+ interface Task {
136
+ id: string;
137
+ status: TaskStatus;
138
+ task_type?: string;
139
+ /** Produced artifacts, once `status` is `"succeeded"`. Order is not guaranteed — read by `kind`. */
140
+ outputs?: TaskOutput[];
141
+ /** Present when `status` is `"failed"`. A failed task is not an exception — check this field. */
142
+ error: TaskError | null;
143
+ source: string;
144
+ data_removed: boolean;
145
+ created_at: string;
146
+ started_at?: string;
147
+ completed_at?: string;
148
+ /** RFC 3339, empty until the task succeeds. Output artifacts expire 7 days after completion. */
149
+ expires_at?: string;
150
+ metrics?: {
151
+ total_time?: number;
152
+ };
153
+ /** `get`: this task's own polling URL — not a download link. */
154
+ urls: {
155
+ get: string;
156
+ };
157
+ }
158
+ interface CreateTaskInput {
159
+ type: TaskType;
160
+ source_url: string;
161
+ params?: Record<string, unknown>;
162
+ }
163
+ interface TaskRequestOptions {
164
+ /**
165
+ * Idempotency key (≤64 chars). Defaults to a fresh UUID per call, like the
166
+ * sync surface — unlike sync, this is a JSON body field, not a header.
167
+ */
168
+ idempotencyKey?: string;
169
+ /** URL notified (HMAC-signed) when the task reaches a terminal state. */
170
+ webhook?: string;
171
+ /** Restrict which terminal states trigger the webhook. Default: all. */
172
+ webhookEventsFilter?: WebhookEvent[];
173
+ signal?: AbortSignal;
174
+ }
175
+ /**
176
+ * Async task surface: submit larger jobs than the sync endpoints accept, then
177
+ * poll for the result. There is no built-in `wait` — poll `get` yourself:
178
+ *
179
+ * ```ts
180
+ * let task = await client.tasks.video.watermark({ source_url, ...params });
181
+ * while (task.status === "starting" || task.status === "processing") {
182
+ * await new Promise((r) => setTimeout(r, 2000));
183
+ * task = await client.tasks.get(task.id);
184
+ * }
185
+ * ```
186
+ */
187
+ declare class Tasks {
188
+ #private;
189
+ constructor(ctx: RequestContext);
190
+ create(input: CreateTaskInput, options?: TaskRequestOptions): Promise<Task>;
191
+ /** `failed` is not thrown — check `task.error`. Only a transport failure throws `FotovidError`. */
192
+ get(id: string, options?: {
193
+ signal?: AbortSignal;
194
+ }): Promise<Task>;
195
+ readonly video: {
196
+ watermark: (input: VideoWatermarkInput, options?: TaskRequestOptions) => Promise<Task>;
197
+ trim: (input: TrimInput, options?: TaskRequestOptions) => Promise<Task>;
198
+ extractAudio: (input: ExtractAudioInput, options?: TaskRequestOptions) => Promise<Task>;
199
+ thumbnail: (input: ThumbnailInput, options?: TaskRequestOptions) => Promise<Task>;
200
+ };
201
+ readonly image: {
202
+ watermark: (input: ImageWatermarkInput, options?: TaskRequestOptions) => Promise<Task>;
203
+ };
204
+ readonly audio: {
205
+ trim: (input: TrimAudioInput, options?: TaskRequestOptions) => Promise<Task>;
206
+ };
207
+ }
208
+
103
209
  /**
104
210
  * Typed client for the Fotovid media API. Methods mirror the REST resources
105
211
  * (`client.video.*`, `client.image.*`, `client.audio.*`); parameter names
@@ -107,6 +213,8 @@ interface RequestOptions {
107
213
  */
108
214
  declare class Fotovid {
109
215
  #private;
216
+ /** Async task surface — submit larger jobs than sync accepts, then poll for the result. */
217
+ readonly tasks: Tasks;
110
218
  constructor(options?: FotovidOptions);
111
219
  readonly video: {
112
220
  watermark: (input: VideoWatermarkInput, options?: RequestOptions) => Promise<MediaResult>;
@@ -134,4 +242,4 @@ declare class FotovidError extends Error {
134
242
  constructor(status: number, detail: unknown, retryAfter?: number);
135
243
  }
136
244
 
137
- export { type ExtractAudioInput, Fotovid, FotovidError, type FotovidOptions, type ImageFormat, type ImageWatermarkInput, type MediaResult, type ProbeInput, type ProbeResult, type RequestOptions, type ThumbnailInput, type TrimAudioInput, type TrimInput, type VideoWatermarkInput, type WatermarkPosition, type WatermarkType, type X264Preset, Fotovid as default };
245
+ export { type CreateTaskInput, type ExtractAudioInput, Fotovid, FotovidError, type FotovidOptions, type ImageFormat, type ImageWatermarkInput, type MediaResult, type ProbeInput, type ProbeResult, type RequestOptions, type Task, type TaskError, type TaskErrorCode, type TaskOutput, type TaskRequestOptions, type TaskStatus, type TaskType, Tasks, type ThumbnailInput, type TrimAudioInput, type TrimInput, type VideoWatermarkInput, type WatermarkPosition, type WatermarkType, type WebhookEvent, type X264Preset, Fotovid as default };
package/dist/index.js CHANGED
@@ -26,6 +26,82 @@ var FotovidError = class extends Error {
26
26
  }
27
27
  };
28
28
 
29
+ // src/http.ts
30
+ async function requestJson(ctx, path, init) {
31
+ const response = await ctx.fetch(new URL(path, ctx.baseUrl), {
32
+ method: init.method,
33
+ headers: {
34
+ authorization: `Bearer ${ctx.apiKey}`,
35
+ // An explicit UA identifies the SDK to the edge and to server-side
36
+ // observability. (The Python SDK's default urllib UA was blocked by
37
+ // Cloudflare with a 403 — Node's fetch UA is not, but be explicit.)
38
+ "user-agent": `fotovid-sdk/${"0.2.0"}`,
39
+ ...init.body !== void 0 ? { "content-type": "application/json" } : {},
40
+ ...init.headers
41
+ },
42
+ body: init.body !== void 0 ? JSON.stringify(init.body) : void 0,
43
+ signal: init.signal
44
+ });
45
+ if (!response.ok) {
46
+ let detail;
47
+ try {
48
+ detail = await response.json();
49
+ } catch {
50
+ }
51
+ const ra = response.headers.get("retry-after");
52
+ const retryAfter = ra ? Number(ra) : void 0;
53
+ throw new FotovidError(response.status, detail, retryAfter);
54
+ }
55
+ return await response.json();
56
+ }
57
+
58
+ // src/tasks.ts
59
+ function taskBody(input, options) {
60
+ return {
61
+ type: input.type,
62
+ source_url: input.source_url,
63
+ params: input.params,
64
+ idempotency_key: options?.idempotencyKey ?? globalThis.crypto.randomUUID(),
65
+ webhook: options?.webhook,
66
+ webhook_events_filter: options?.webhookEventsFilter
67
+ };
68
+ }
69
+ var Tasks = class {
70
+ #ctx;
71
+ constructor(ctx) {
72
+ this.#ctx = ctx;
73
+ }
74
+ create(input, options) {
75
+ return requestJson(this.#ctx, "/v1/tasks", {
76
+ method: "POST",
77
+ body: taskBody(input, options)
78
+ });
79
+ }
80
+ /** `failed` is not thrown — check `task.error`. Only a transport failure throws `FotovidError`. */
81
+ get(id, options) {
82
+ return requestJson(this.#ctx, `/v1/tasks/${encodeURIComponent(id)}`, {
83
+ method: "GET",
84
+ signal: options?.signal
85
+ });
86
+ }
87
+ video = {
88
+ watermark: (input, options) => this.create({ type: "video.watermark", ...splitInput(input) }, options),
89
+ trim: (input, options) => this.create({ type: "video.trim", ...splitInput(input) }, options),
90
+ extractAudio: (input, options) => this.create({ type: "video.audio", ...splitInput(input) }, options),
91
+ thumbnail: (input, options) => this.create({ type: "video.cover", ...splitInput(input) }, options)
92
+ };
93
+ image = {
94
+ watermark: (input, options) => this.create({ type: "image.watermark", ...splitInput(input) }, options)
95
+ };
96
+ audio = {
97
+ trim: (input, options) => this.create({ type: "audio.trim", ...splitInput(input) }, options)
98
+ };
99
+ };
100
+ function splitInput(input) {
101
+ const { source_url, ...params } = input;
102
+ return { source_url, params };
103
+ }
104
+
29
105
  // src/client.ts
30
106
  var DEFAULT_BASE_URL = "https://api.fotovid.co";
31
107
  function resolveApiKey(explicit) {
@@ -41,35 +117,33 @@ var Fotovid = class {
41
117
  #apiKey;
42
118
  #baseUrl;
43
119
  #fetch;
120
+ /** Async task surface — submit larger jobs than sync accepts, then poll for the result. */
121
+ tasks;
44
122
  constructor(options = {}) {
45
123
  this.#apiKey = resolveApiKey(options.apiKey);
46
124
  this.#baseUrl = options.baseUrl ?? DEFAULT_BASE_URL;
47
125
  this.#fetch = options.fetch ?? globalThis.fetch;
126
+ this.tasks = new Tasks({
127
+ apiKey: this.#apiKey,
128
+ baseUrl: this.#baseUrl,
129
+ fetch: this.#fetch
130
+ });
48
131
  }
49
- async #post(path, input, options) {
132
+ #post(path, input, options) {
50
133
  const { source_url, ...params } = input;
51
- const response = await this.#fetch(new URL(path, this.#baseUrl), {
52
- method: "POST",
53
- headers: {
54
- "content-type": "application/json",
55
- authorization: `Bearer ${this.#apiKey}`,
56
- // Billed endpoints require an idempotency key; default to a fresh
57
- // UUID per call, overridable to make a retry replay (not re-charge).
58
- "idempotency-key": options?.idempotencyKey ?? globalThis.crypto.randomUUID()
59
- },
60
- body: JSON.stringify({ source_url, params })
61
- });
62
- if (!response.ok) {
63
- let detail;
64
- try {
65
- detail = await response.json();
66
- } catch {
134
+ return requestJson(
135
+ { apiKey: this.#apiKey, baseUrl: this.#baseUrl, fetch: this.#fetch },
136
+ path,
137
+ {
138
+ method: "POST",
139
+ body: { source_url, params },
140
+ headers: {
141
+ // Billed endpoints require an idempotency key; default to a fresh
142
+ // UUID per call, overridable to make a retry replay (not re-charge).
143
+ "idempotency-key": options?.idempotencyKey ?? globalThis.crypto.randomUUID()
144
+ }
67
145
  }
68
- const ra = response.headers.get("retry-after");
69
- const retryAfter = ra ? Number(ra) : void 0;
70
- throw new FotovidError(response.status, detail, retryAfter);
71
- }
72
- return await response.json();
146
+ );
73
147
  }
74
148
  video = {
75
149
  watermark: (input, options) => this.#post("/v1/video/watermark", input, options),
@@ -88,6 +162,7 @@ var Fotovid = class {
88
162
  export {
89
163
  Fotovid,
90
164
  FotovidError,
165
+ Tasks,
91
166
  Fotovid as default
92
167
  };
93
168
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/error.ts","../src/client.ts"],"sourcesContent":["function isRecord(value: unknown): value is Record<string, unknown> {\n\treturn typeof value === \"object\" && value !== null;\n}\n\n/** Thrown when the Fotovid API returns a non-2xx response. */\nexport class FotovidError extends Error {\n\t/** HTTP status code of the failed response. */\n\treadonly status: number;\n\t/** Parsed error body, when the response carried one. */\n\treadonly detail: unknown;\n\t/** Seconds to wait before retrying, from the `Retry-After` header (e.g. 429/503). */\n\treadonly retryAfter?: number;\n\n\tconstructor(status: number, detail: unknown, retryAfter?: number) {\n\t\tlet message: string | undefined;\n\t\tif (isRecord(detail)) {\n\t\t\tif (typeof detail.detail === \"string\") {\n\t\t\t\tmessage = detail.detail;\n\t\t\t} else if (typeof detail.message === \"string\") {\n\t\t\t\tmessage = detail.message;\n\t\t\t}\n\t\t}\n\t\tsuper(message ?? `Fotovid API request failed with status ${status}`);\n\t\tthis.name = \"FotovidError\";\n\t\tthis.status = status;\n\t\tthis.detail = detail;\n\t\tthis.retryAfter = retryAfter;\n\t}\n}\n","import { FotovidError } from \"./error.js\";\nimport type {\n\tExtractAudioInput,\n\tFotovidOptions,\n\tImageWatermarkInput,\n\tMediaResult,\n\tProbeInput,\n\tProbeResult,\n\tRequestOptions,\n\tThumbnailInput,\n\tTrimAudioInput,\n\tTrimInput,\n\tVideoWatermarkInput,\n} from \"./types.js\";\n\nconst DEFAULT_BASE_URL = \"https://api.fotovid.co\";\n\nfunction resolveApiKey(explicit: string | undefined): string {\n\tconst key =\n\t\texplicit ??\n\t\t(typeof process !== \"undefined\" ? process.env?.FOTOVID_API_KEY : undefined);\n\tif (!key) {\n\t\tthrow new Error(\n\t\t\t\"Fotovid: missing API key. Pass { apiKey } or set FOTOVID_API_KEY.\",\n\t\t);\n\t}\n\treturn key;\n}\n\n/**\n * Typed client for the Fotovid media API. Methods mirror the REST resources\n * (`client.video.*`, `client.image.*`, `client.audio.*`); parameter names\n * match the API 1:1 (`source_url`, `watermark_image_url`, …).\n */\nexport class Fotovid {\n\treadonly #apiKey: string;\n\treadonly #baseUrl: string;\n\treadonly #fetch: typeof globalThis.fetch;\n\n\tconstructor(options: FotovidOptions = {}) {\n\t\tthis.#apiKey = resolveApiKey(options.apiKey);\n\t\tthis.#baseUrl = options.baseUrl ?? DEFAULT_BASE_URL;\n\t\tthis.#fetch = options.fetch ?? globalThis.fetch;\n\t}\n\n\tasync #post<T>(\n\t\tpath: string,\n\t\tinput: { source_url: string },\n\t\toptions?: RequestOptions,\n\t): Promise<T> {\n\t\tconst { source_url, ...params } = input;\n\t\tconst response = await this.#fetch(new URL(path, this.#baseUrl), {\n\t\t\tmethod: \"POST\",\n\t\t\theaders: {\n\t\t\t\t\"content-type\": \"application/json\",\n\t\t\t\tauthorization: `Bearer ${this.#apiKey}`,\n\t\t\t\t// Billed endpoints require an idempotency key; default to a fresh\n\t\t\t\t// UUID per call, overridable to make a retry replay (not re-charge).\n\t\t\t\t\"idempotency-key\":\n\t\t\t\t\toptions?.idempotencyKey ?? globalThis.crypto.randomUUID(),\n\t\t\t},\n\t\t\tbody: JSON.stringify({ source_url, params }),\n\t\t});\n\t\tif (!response.ok) {\n\t\t\tlet detail: unknown;\n\t\t\ttry {\n\t\t\t\tdetail = await response.json();\n\t\t\t} catch {\n\t\t\t\t// non-JSON error body — leave detail undefined\n\t\t\t}\n\t\t\tconst ra = response.headers.get(\"retry-after\");\n\t\t\tconst retryAfter = ra ? Number(ra) : undefined;\n\t\t\tthrow new FotovidError(response.status, detail, retryAfter);\n\t\t}\n\t\treturn (await response.json()) as T;\n\t}\n\n\treadonly video = {\n\t\twatermark: (\n\t\t\tinput: VideoWatermarkInput,\n\t\t\toptions?: RequestOptions,\n\t\t): Promise<MediaResult> =>\n\t\t\tthis.#post<MediaResult>(\"/v1/video/watermark\", input, options),\n\t\ttrim: (input: TrimInput, options?: RequestOptions): Promise<MediaResult> =>\n\t\t\tthis.#post<MediaResult>(\"/v1/video/trim\", input, options),\n\t\textractAudio: (\n\t\t\tinput: ExtractAudioInput,\n\t\t\toptions?: RequestOptions,\n\t\t): Promise<MediaResult> =>\n\t\t\tthis.#post<MediaResult>(\"/v1/video/extract-audio\", input, options),\n\t\tthumbnail: (\n\t\t\tinput: ThumbnailInput,\n\t\t\toptions?: RequestOptions,\n\t\t): Promise<MediaResult> =>\n\t\t\tthis.#post<MediaResult>(\"/v1/video/extract-cover\", input, options),\n\t\tprobe: (\n\t\t\tinput: ProbeInput,\n\t\t\toptions?: RequestOptions,\n\t\t): Promise<ProbeResult> =>\n\t\t\tthis.#post<ProbeResult>(\"/v1/video/probe\", input, options),\n\t};\n\n\treadonly image = {\n\t\twatermark: (\n\t\t\tinput: ImageWatermarkInput,\n\t\t\toptions?: RequestOptions,\n\t\t): Promise<MediaResult> =>\n\t\t\tthis.#post<MediaResult>(\"/v1/image/watermark\", input, options),\n\t};\n\n\treadonly audio = {\n\t\ttrim: (\n\t\t\tinput: TrimAudioInput,\n\t\t\toptions?: RequestOptions,\n\t\t): Promise<MediaResult> =>\n\t\t\tthis.#post<MediaResult>(\"/v1/audio/trim\", input, options),\n\t};\n}\n"],"mappings":";AAAA,SAAS,SAAS,OAAkD;AACnE,SAAO,OAAO,UAAU,YAAY,UAAU;AAC/C;AAGO,IAAM,eAAN,cAA2B,MAAM;AAAA;AAAA,EAE9B;AAAA;AAAA,EAEA;AAAA;AAAA,EAEA;AAAA,EAET,YAAY,QAAgB,QAAiB,YAAqB;AACjE,QAAI;AACJ,QAAI,SAAS,MAAM,GAAG;AACrB,UAAI,OAAO,OAAO,WAAW,UAAU;AACtC,kBAAU,OAAO;AAAA,MAClB,WAAW,OAAO,OAAO,YAAY,UAAU;AAC9C,kBAAU,OAAO;AAAA,MAClB;AAAA,IACD;AACA,UAAM,WAAW,0CAA0C,MAAM,EAAE;AACnE,SAAK,OAAO;AACZ,SAAK,SAAS;AACd,SAAK,SAAS;AACd,SAAK,aAAa;AAAA,EACnB;AACD;;;ACbA,IAAM,mBAAmB;AAEzB,SAAS,cAAc,UAAsC;AAC5D,QAAM,MACL,aACC,OAAO,YAAY,cAAc,QAAQ,KAAK,kBAAkB;AAClE,MAAI,CAAC,KAAK;AACT,UAAM,IAAI;AAAA,MACT;AAAA,IACD;AAAA,EACD;AACA,SAAO;AACR;AAOO,IAAM,UAAN,MAAc;AAAA,EACX;AAAA,EACA;AAAA,EACA;AAAA,EAET,YAAY,UAA0B,CAAC,GAAG;AACzC,SAAK,UAAU,cAAc,QAAQ,MAAM;AAC3C,SAAK,WAAW,QAAQ,WAAW;AACnC,SAAK,SAAS,QAAQ,SAAS,WAAW;AAAA,EAC3C;AAAA,EAEA,MAAM,MACL,MACA,OACA,SACa;AACb,UAAM,EAAE,YAAY,GAAG,OAAO,IAAI;AAClC,UAAM,WAAW,MAAM,KAAK,OAAO,IAAI,IAAI,MAAM,KAAK,QAAQ,GAAG;AAAA,MAChE,QAAQ;AAAA,MACR,SAAS;AAAA,QACR,gBAAgB;AAAA,QAChB,eAAe,UAAU,KAAK,OAAO;AAAA;AAAA;AAAA,QAGrC,mBACC,SAAS,kBAAkB,WAAW,OAAO,WAAW;AAAA,MAC1D;AAAA,MACA,MAAM,KAAK,UAAU,EAAE,YAAY,OAAO,CAAC;AAAA,IAC5C,CAAC;AACD,QAAI,CAAC,SAAS,IAAI;AACjB,UAAI;AACJ,UAAI;AACH,iBAAS,MAAM,SAAS,KAAK;AAAA,MAC9B,QAAQ;AAAA,MAER;AACA,YAAM,KAAK,SAAS,QAAQ,IAAI,aAAa;AAC7C,YAAM,aAAa,KAAK,OAAO,EAAE,IAAI;AACrC,YAAM,IAAI,aAAa,SAAS,QAAQ,QAAQ,UAAU;AAAA,IAC3D;AACA,WAAQ,MAAM,SAAS,KAAK;AAAA,EAC7B;AAAA,EAES,QAAQ;AAAA,IAChB,WAAW,CACV,OACA,YAEA,KAAK,MAAmB,uBAAuB,OAAO,OAAO;AAAA,IAC9D,MAAM,CAAC,OAAkB,YACxB,KAAK,MAAmB,kBAAkB,OAAO,OAAO;AAAA,IACzD,cAAc,CACb,OACA,YAEA,KAAK,MAAmB,2BAA2B,OAAO,OAAO;AAAA,IAClE,WAAW,CACV,OACA,YAEA,KAAK,MAAmB,2BAA2B,OAAO,OAAO;AAAA,IAClE,OAAO,CACN,OACA,YAEA,KAAK,MAAmB,mBAAmB,OAAO,OAAO;AAAA,EAC3D;AAAA,EAES,QAAQ;AAAA,IAChB,WAAW,CACV,OACA,YAEA,KAAK,MAAmB,uBAAuB,OAAO,OAAO;AAAA,EAC/D;AAAA,EAES,QAAQ;AAAA,IAChB,MAAM,CACL,OACA,YAEA,KAAK,MAAmB,kBAAkB,OAAO,OAAO;AAAA,EAC1D;AACD;","names":[]}
1
+ {"version":3,"sources":["../src/error.ts","../src/http.ts","../src/tasks.ts","../src/client.ts"],"sourcesContent":["function isRecord(value: unknown): value is Record<string, unknown> {\n\treturn typeof value === \"object\" && value !== null;\n}\n\n/** Thrown when the Fotovid API returns a non-2xx response. */\nexport class FotovidError extends Error {\n\t/** HTTP status code of the failed response. */\n\treadonly status: number;\n\t/** Parsed error body, when the response carried one. */\n\treadonly detail: unknown;\n\t/** Seconds to wait before retrying, from the `Retry-After` header (e.g. 429/503). */\n\treadonly retryAfter?: number;\n\n\tconstructor(status: number, detail: unknown, retryAfter?: number) {\n\t\tlet message: string | undefined;\n\t\tif (isRecord(detail)) {\n\t\t\tif (typeof detail.detail === \"string\") {\n\t\t\t\tmessage = detail.detail;\n\t\t\t} else if (typeof detail.message === \"string\") {\n\t\t\t\tmessage = detail.message;\n\t\t\t}\n\t\t}\n\t\tsuper(message ?? `Fotovid API request failed with status ${status}`);\n\t\tthis.name = \"FotovidError\";\n\t\tthis.status = status;\n\t\tthis.detail = detail;\n\t\tthis.retryAfter = retryAfter;\n\t}\n}\n","import { FotovidError } from \"./error.js\";\n\n// Injected at build time from package.json by tsup (see tsup.config.ts).\ndeclare const __SDK_VERSION__: string;\n\nexport interface RequestContext {\n\tapiKey: string;\n\tbaseUrl: string;\n\tfetch: typeof globalThis.fetch;\n}\n\nexport interface JsonRequestInit {\n\tmethod: \"GET\" | \"POST\";\n\tbody?: unknown;\n\theaders?: Record<string, string>;\n\tsignal?: AbortSignal;\n}\n\n/**\n * Shared low-level request used by both the sync (client.ts) and the async\n * (tasks.ts) surfaces — same auth, UA, and error handling either way.\n */\nexport async function requestJson<T>(\n\tctx: RequestContext,\n\tpath: string,\n\tinit: JsonRequestInit,\n): Promise<T> {\n\tconst response = await ctx.fetch(new URL(path, ctx.baseUrl), {\n\t\tmethod: init.method,\n\t\theaders: {\n\t\t\tauthorization: `Bearer ${ctx.apiKey}`,\n\t\t\t// An explicit UA identifies the SDK to the edge and to server-side\n\t\t\t// observability. (The Python SDK's default urllib UA was blocked by\n\t\t\t// Cloudflare with a 403 — Node's fetch UA is not, but be explicit.)\n\t\t\t\"user-agent\": `fotovid-sdk/${__SDK_VERSION__}`,\n\t\t\t...(init.body !== undefined\n\t\t\t\t? { \"content-type\": \"application/json\" }\n\t\t\t\t: {}),\n\t\t\t...init.headers,\n\t\t},\n\t\tbody: init.body !== undefined ? JSON.stringify(init.body) : undefined,\n\t\tsignal: init.signal,\n\t});\n\tif (!response.ok) {\n\t\tlet detail: unknown;\n\t\ttry {\n\t\t\tdetail = await response.json();\n\t\t} catch {\n\t\t\t// non-JSON error body — leave detail undefined\n\t\t}\n\t\tconst ra = response.headers.get(\"retry-after\");\n\t\tconst retryAfter = ra ? Number(ra) : undefined;\n\t\tthrow new FotovidError(response.status, detail, retryAfter);\n\t}\n\treturn (await response.json()) as T;\n}\n","import { type RequestContext, requestJson } from \"./http.js\";\nimport type {\n\tExtractAudioInput,\n\tImageWatermarkInput,\n\tThumbnailInput,\n\tTrimAudioInput,\n\tTrimInput,\n\tVideoWatermarkInput,\n} from \"./types.js\";\n\n/**\n * Async task type. Deliberately excludes `\"video.meta\"` — the async worker\n * always fails it (probe has no artifact to produce); use the sync\n * `client.video.probe` instead.\n */\nexport type TaskType =\n\t| \"image.watermark\"\n\t| \"video.watermark\"\n\t| \"video.cover\"\n\t| \"video.trim\"\n\t| \"video.audio\"\n\t| \"audio.trim\";\n\nexport type TaskStatus =\n\t| \"starting\"\n\t| \"processing\"\n\t| \"succeeded\"\n\t| \"failed\"\n\t| \"canceled\";\n\nexport type WebhookEvent = \"completed\" | \"failed\";\n\n/** Stable, closed set of task-level error codes (`taskerr.ExternalCode`). */\nexport type TaskErrorCode =\n\t| \"invalid_input\"\n\t| \"unsupported_media\"\n\t| \"source_unreachable\"\n\t| \"source_too_large\"\n\t| \"unknown_task_type\"\n\t| \"insufficient_credits\"\n\t| \"processing_failed\"\n\t| \"internal_error\";\n\n/** One produced artifact. `kind` is `\"video\" | \"cover\" | \"image\" | \"audio\"`. */\nexport interface TaskOutput {\n\tkind: string;\n\turl: string;\n}\n\n/**\n * Task-level failure, carried in `Task.error` on a `\"failed\"` task. Distinct\n * from `FotovidError`, which is thrown for transport-layer failures (a\n * non-2xx response to create/get itself).\n */\nexport interface TaskError {\n\tcode: TaskErrorCode;\n\tmessage: string;\n\trequest_id: string;\n\tdetail?: string;\n}\n\nexport interface Task {\n\tid: string;\n\tstatus: TaskStatus;\n\ttask_type?: string;\n\t/** Produced artifacts, once `status` is `\"succeeded\"`. Order is not guaranteed — read by `kind`. */\n\toutputs?: TaskOutput[];\n\t/** Present when `status` is `\"failed\"`. A failed task is not an exception — check this field. */\n\terror: TaskError | null;\n\tsource: string;\n\tdata_removed: boolean;\n\tcreated_at: string;\n\tstarted_at?: string;\n\tcompleted_at?: string;\n\t/** RFC 3339, empty until the task succeeds. Output artifacts expire 7 days after completion. */\n\texpires_at?: string;\n\tmetrics?: { total_time?: number };\n\t/** `get`: this task's own polling URL — not a download link. */\n\turls: { get: string };\n}\n\nexport interface CreateTaskInput {\n\ttype: TaskType;\n\tsource_url: string;\n\tparams?: Record<string, unknown>;\n}\n\nexport interface TaskRequestOptions {\n\t/**\n\t * Idempotency key (≤64 chars). Defaults to a fresh UUID per call, like the\n\t * sync surface — unlike sync, this is a JSON body field, not a header.\n\t */\n\tidempotencyKey?: string;\n\t/** URL notified (HMAC-signed) when the task reaches a terminal state. */\n\twebhook?: string;\n\t/** Restrict which terminal states trigger the webhook. Default: all. */\n\twebhookEventsFilter?: WebhookEvent[];\n\tsignal?: AbortSignal;\n}\n\nfunction taskBody(\n\tinput: CreateTaskInput,\n\toptions: TaskRequestOptions | undefined,\n) {\n\treturn {\n\t\ttype: input.type,\n\t\tsource_url: input.source_url,\n\t\tparams: input.params,\n\t\tidempotency_key: options?.idempotencyKey ?? globalThis.crypto.randomUUID(),\n\t\twebhook: options?.webhook,\n\t\twebhook_events_filter: options?.webhookEventsFilter,\n\t};\n}\n\n/**\n * Async task surface: submit larger jobs than the sync endpoints accept, then\n * poll for the result. There is no built-in `wait` — poll `get` yourself:\n *\n * ```ts\n * let task = await client.tasks.video.watermark({ source_url, ...params });\n * while (task.status === \"starting\" || task.status === \"processing\") {\n * await new Promise((r) => setTimeout(r, 2000));\n * task = await client.tasks.get(task.id);\n * }\n * ```\n */\nexport class Tasks {\n\treadonly #ctx: RequestContext;\n\n\tconstructor(ctx: RequestContext) {\n\t\tthis.#ctx = ctx;\n\t}\n\n\tcreate(input: CreateTaskInput, options?: TaskRequestOptions): Promise<Task> {\n\t\treturn requestJson<Task>(this.#ctx, \"/v1/tasks\", {\n\t\t\tmethod: \"POST\",\n\t\t\tbody: taskBody(input, options),\n\t\t});\n\t}\n\n\t/** `failed` is not thrown — check `task.error`. Only a transport failure throws `FotovidError`. */\n\tget(id: string, options?: { signal?: AbortSignal }): Promise<Task> {\n\t\treturn requestJson<Task>(this.#ctx, `/v1/tasks/${encodeURIComponent(id)}`, {\n\t\t\tmethod: \"GET\",\n\t\t\tsignal: options?.signal,\n\t\t});\n\t}\n\n\treadonly video = {\n\t\twatermark: (input: VideoWatermarkInput, options?: TaskRequestOptions) =>\n\t\t\tthis.create({ type: \"video.watermark\", ...splitInput(input) }, options),\n\t\ttrim: (input: TrimInput, options?: TaskRequestOptions) =>\n\t\t\tthis.create({ type: \"video.trim\", ...splitInput(input) }, options),\n\t\textractAudio: (input: ExtractAudioInput, options?: TaskRequestOptions) =>\n\t\t\tthis.create({ type: \"video.audio\", ...splitInput(input) }, options),\n\t\tthumbnail: (input: ThumbnailInput, options?: TaskRequestOptions) =>\n\t\t\tthis.create({ type: \"video.cover\", ...splitInput(input) }, options),\n\t};\n\n\treadonly image = {\n\t\twatermark: (input: ImageWatermarkInput, options?: TaskRequestOptions) =>\n\t\t\tthis.create({ type: \"image.watermark\", ...splitInput(input) }, options),\n\t};\n\n\treadonly audio = {\n\t\ttrim: (input: TrimAudioInput, options?: TaskRequestOptions) =>\n\t\t\tthis.create({ type: \"audio.trim\", ...splitInput(input) }, options),\n\t};\n}\n\n/** Splits a sync-style `{ source_url, ...params }` input into the task envelope shape. */\nfunction splitInput(input: { source_url: string }): {\n\tsource_url: string;\n\tparams: Record<string, unknown>;\n} {\n\tconst { source_url, ...params } = input;\n\treturn { source_url, params };\n}\n","import { requestJson } from \"./http.js\";\nimport { Tasks } from \"./tasks.js\";\nimport type {\n\tExtractAudioInput,\n\tFotovidOptions,\n\tImageWatermarkInput,\n\tMediaResult,\n\tProbeInput,\n\tProbeResult,\n\tRequestOptions,\n\tThumbnailInput,\n\tTrimAudioInput,\n\tTrimInput,\n\tVideoWatermarkInput,\n} from \"./types.js\";\n\nconst DEFAULT_BASE_URL = \"https://api.fotovid.co\";\n\nfunction resolveApiKey(explicit: string | undefined): string {\n\tconst key =\n\t\texplicit ??\n\t\t(typeof process !== \"undefined\" ? process.env?.FOTOVID_API_KEY : undefined);\n\tif (!key) {\n\t\tthrow new Error(\n\t\t\t\"Fotovid: missing API key. Pass { apiKey } or set FOTOVID_API_KEY.\",\n\t\t);\n\t}\n\treturn key;\n}\n\n/**\n * Typed client for the Fotovid media API. Methods mirror the REST resources\n * (`client.video.*`, `client.image.*`, `client.audio.*`); parameter names\n * match the API 1:1 (`source_url`, `watermark_image_url`, …).\n */\nexport class Fotovid {\n\treadonly #apiKey: string;\n\treadonly #baseUrl: string;\n\treadonly #fetch: typeof globalThis.fetch;\n\n\t/** Async task surface — submit larger jobs than sync accepts, then poll for the result. */\n\treadonly tasks: Tasks;\n\n\tconstructor(options: FotovidOptions = {}) {\n\t\tthis.#apiKey = resolveApiKey(options.apiKey);\n\t\tthis.#baseUrl = options.baseUrl ?? DEFAULT_BASE_URL;\n\t\tthis.#fetch = options.fetch ?? globalThis.fetch;\n\t\tthis.tasks = new Tasks({\n\t\t\tapiKey: this.#apiKey,\n\t\t\tbaseUrl: this.#baseUrl,\n\t\t\tfetch: this.#fetch,\n\t\t});\n\t}\n\n\t#post<T>(\n\t\tpath: string,\n\t\tinput: { source_url: string },\n\t\toptions?: RequestOptions,\n\t): Promise<T> {\n\t\tconst { source_url, ...params } = input;\n\t\treturn requestJson<T>(\n\t\t\t{ apiKey: this.#apiKey, baseUrl: this.#baseUrl, fetch: this.#fetch },\n\t\t\tpath,\n\t\t\t{\n\t\t\t\tmethod: \"POST\",\n\t\t\t\tbody: { source_url, params },\n\t\t\t\theaders: {\n\t\t\t\t\t// Billed endpoints require an idempotency key; default to a fresh\n\t\t\t\t\t// UUID per call, overridable to make a retry replay (not re-charge).\n\t\t\t\t\t\"idempotency-key\":\n\t\t\t\t\t\toptions?.idempotencyKey ?? globalThis.crypto.randomUUID(),\n\t\t\t\t},\n\t\t\t},\n\t\t);\n\t}\n\n\treadonly video = {\n\t\twatermark: (\n\t\t\tinput: VideoWatermarkInput,\n\t\t\toptions?: RequestOptions,\n\t\t): Promise<MediaResult> =>\n\t\t\tthis.#post<MediaResult>(\"/v1/video/watermark\", input, options),\n\t\ttrim: (input: TrimInput, options?: RequestOptions): Promise<MediaResult> =>\n\t\t\tthis.#post<MediaResult>(\"/v1/video/trim\", input, options),\n\t\textractAudio: (\n\t\t\tinput: ExtractAudioInput,\n\t\t\toptions?: RequestOptions,\n\t\t): Promise<MediaResult> =>\n\t\t\tthis.#post<MediaResult>(\"/v1/video/extract-audio\", input, options),\n\t\tthumbnail: (\n\t\t\tinput: ThumbnailInput,\n\t\t\toptions?: RequestOptions,\n\t\t): Promise<MediaResult> =>\n\t\t\tthis.#post<MediaResult>(\"/v1/video/extract-cover\", input, options),\n\t\tprobe: (\n\t\t\tinput: ProbeInput,\n\t\t\toptions?: RequestOptions,\n\t\t): Promise<ProbeResult> =>\n\t\t\tthis.#post<ProbeResult>(\"/v1/video/probe\", input, options),\n\t};\n\n\treadonly image = {\n\t\twatermark: (\n\t\t\tinput: ImageWatermarkInput,\n\t\t\toptions?: RequestOptions,\n\t\t): Promise<MediaResult> =>\n\t\t\tthis.#post<MediaResult>(\"/v1/image/watermark\", input, options),\n\t};\n\n\treadonly audio = {\n\t\ttrim: (\n\t\t\tinput: TrimAudioInput,\n\t\t\toptions?: RequestOptions,\n\t\t): Promise<MediaResult> =>\n\t\t\tthis.#post<MediaResult>(\"/v1/audio/trim\", input, options),\n\t};\n}\n"],"mappings":"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","names":[]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fotovid/sdk",
3
- "version": "0.1.0",
3
+ "version": "0.2.0",
4
4
  "description": "Typed TypeScript/Node client for the Fotovid media API",
5
5
  "license": "MIT",
6
6
  "type": "module",