image2ppt 0.5.3

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 image2ppt
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,269 @@
1
+ # image2ppt — Node.js / TypeScript client
2
+
3
+ Official Node.js client for the [image2ppt](https://image2ppt.com) API. Turn a batch of images or PDF pages into one **editable** PowerPoint (`.pptx`).
4
+
5
+ Uses Node's built-in `fetch`, plus `sharp` for client-side image preparation. Requires Node 18.17+, 20.3+, or 21+ — the range `sharp` ships native binaries for.
6
+
7
+ ## Install
8
+
9
+ ```bash
10
+ npm install image2ppt
11
+ ```
12
+
13
+ Fully typed. Works from JavaScript or TypeScript, ESM or CommonJS interop.
14
+
15
+ ## Get an API key
16
+
17
+ Sign in at [image2ppt.com](https://image2ppt.com), open **Developer / API** from the account menu, and create a key (looks like `i2p_live_xxxx`). It's shown in full **once** — save it. API access is available to accounts with credits.
18
+
19
+ > **Server-side only.** This SDK reads files from disk and holds your API key — run it on your backend, never in a browser or any client a user can inspect.
20
+
21
+ ## Quick start
22
+
23
+ One shot — submit, wait, download:
24
+
25
+ ```ts
26
+ import { Image2PPTClient, JobCancelledError } from "image2ppt";
27
+
28
+ const client = new Image2PPTClient({ apiKey: process.env.IMAGE2PPT_API_KEY! });
29
+
30
+ const job = await client.convert(
31
+ ["slide1.png", "slide2.png", "report.pdf"],
32
+ "out.pptx",
33
+ { locale: "zh-CN", aspectRatio: "16:9" }, // both optional
34
+ );
35
+ console.log(`done — ${job.slideCount} pages, ${job.creditsUsed} credits used`);
36
+ ```
37
+
38
+ Step by step, if you want to control polling:
39
+
40
+ ```ts
41
+ const job = await client.submit(["slide1.png"], { aspectRatio: "4:3" });
42
+ console.log("job:", job.jobId, "reserved:", job.creditsReserved);
43
+
44
+ const done = await client.wait(job.jobId, { pollIntervalMs: 5000, timeoutMs: 1_800_000 });
45
+ await client.download(done.jobId, "out.pptx");
46
+ ```
47
+
48
+ Cancel a job you no longer need:
49
+
50
+ ```ts
51
+ const result = await client.cancel(job.jobId);
52
+ if (result.finalizing) {
53
+ console.log("cancellation accepted; running pages are still winding down");
54
+ }
55
+
56
+ try {
57
+ const done = await client.wait(job.jobId);
58
+ // At least one page completed: the partial deck remains downloadable.
59
+ await client.download(done.jobId, "partial.pptx");
60
+ } catch (error) {
61
+ if (!(error instanceof JobCancelledError)) throw error;
62
+ // No page completed, so the reservation was refunded and there is no deck.
63
+ }
64
+ ```
65
+
66
+ Cancellation is graceful: pages already running finish and are billed if successful;
67
+ pages that have not started are skipped and refunded. A page being dispatched at the
68
+ very moment the request arrives may still run to completion and be billed. The call
69
+ is idempotent. A job with retained pages finishes as `completed`; without any
70
+ deliverable it finishes as `failed`, and `wait()` throws `JobCancelledError` (a
71
+ subclass of `JobFailedError`). `JobAlreadyFinishedError` covers both a job that
72
+ finished on its own and one already past the point where cancelling could change the
73
+ outcome.
74
+
75
+ ## Which pages made it
76
+
77
+ Once a job is terminal, `job.pageResults` says what happened to each page, in page
78
+ order, one entry per page:
79
+
80
+ ```ts
81
+ for (const page of done.pageResults ?? []) {
82
+ if (page.status === "converted") continue;
83
+ if (page.error?.code === "PAGE_NOT_ATTEMPTED") {
84
+ // This page never started. It is NOT in the deck at all.
85
+ console.log(`page ${page.pageNumber} is missing from the deck`);
86
+ } else {
87
+ // Attempted and failed. The page IS in the deck — as the original image,
88
+ // not as editable content.
89
+ console.log(`page ${page.pageNumber} came through as a flat image`);
90
+ }
91
+ if (page.error?.retryable) console.log(" worth submitting again");
92
+ }
93
+ ```
94
+
95
+ - **`null` is not an empty list.** `pageResults` is `null` when the service did not
96
+ send the field at all — while the job is still running, and for an early job with
97
+ no per-page record. An empty array would mean the job had no pages. Check for
98
+ `null` rather than assuming every terminal job carries a ledger.
99
+ - **The two kinds of failed page call for different things.** `PAGE_NOT_ATTEMPTED`
100
+ means the page is absent from the deck; every other code means it is present as the
101
+ original image. `creditsRefunded` only tells you *how many* pages did not convert —
102
+ this tells you which, and what became of them.
103
+ - **Codes**: `CONVERSION_FAILED`, `CONVERSION_TIMEOUT`, `PAGE_NOT_ATTEMPTED`. Treat
104
+ anything else as `CONVERSION_FAILED`. The job-level `error.code` is deliberately
105
+ coarser and still has only its two values — the finer reasons live here.
106
+ - **Read `retryable`, don't assume it.** Every code today says `true`; a code added
107
+ later may not.
108
+
109
+ ## Error messages in your language
110
+
111
+ Error `message` text follows the request's `Accept-Language` header. This client sends
112
+ none by default, so messages come back in English. To change that:
113
+
114
+ ```ts
115
+ const client = new Image2PPTClient({
116
+ apiKey: process.env.IMAGE2PPT_API_KEY!,
117
+ acceptLanguage: "zh-CN",
118
+ });
119
+ ```
120
+
121
+ > **`acceptLanguage` is not `locale`, and the two are easy to mix up.** The
122
+ > per-submission `locale` decides what language the **generated deck** is written in.
123
+ > The client-level `acceptLanguage` decides what language a **failure is explained to
124
+ > you** in. They are unrelated — an English deck with Chinese error messages is a
125
+ > perfectly sensible combination. `acceptLanguage` is a free-form HTTP header value
126
+ > (`"fr, en;q=0.8"` is fine), not one of the two deck languages.
127
+
128
+ Either way, branch on `code`, never on `message`.
129
+
130
+ Check your balance:
131
+
132
+ ```ts
133
+ const { email, credits } = await client.account();
134
+ console.log(email, "credits:", credits);
135
+ ```
136
+
137
+ ## How it works
138
+
139
+ - **Async.** `submit` resolves with a job id immediately; conversion runs in the background. A single page typically takes ~2 minutes; 90% of jobs finish within 3.
140
+ - **One job = one PPTX.** All files in a submission are merged into a single deck, in upload order.
141
+ - **Billed per page.** 1 page = 1 credit, reserved at submit and settled on completion. If some pages fail but others succeed, the job still completes with the good pages and the failed pages' credits are refunded (`creditsRefunded`).
142
+ - **Limits.** Each file ≤ 35MB; **the files in one request ≤ 45MB in total**; ≤ 50 pages per job (images count as 1, PDFs as their page count). All three are checked locally before upload — note the per-file limit is the *stricter* one, so a 40MB PDF is refused even though it fits a request. **The sizes counted are the ones that actually go on the wire**: image preparation happens first, then the final payload sizes drive pre-flight and batching. PDFs keep their on-disk size.
143
+ - **The check is never stricter than the documented limit.** 45MB of file content is meant to be usable, so a submission sitting exactly on it goes through. Auto-batching is the one place that is deliberately conservative — it fills a batch only to 40MB, because starting one more batch costs nothing while refusing something the server would have accepted does not.
144
+ - **Only the formats the API accepts.** `png`, `jpg`/`jpeg`, `webp`, `gif`, `pdf`. Anything else throws `InvalidFileError` locally — the batch calls check every file before submitting the first one, so an unsupported file at the end of the pile cannot leave you paying for the batches ahead of it.
145
+ - **The local page check is a lower bound.** The client does not parse PDFs, so it counts each one as *at least* 1 page. That is enough to refuse combinations that can never work (50 images plus any PDF is already 51 pages), but a submission that passes locally can still come back `TOO_MANY_SLIDES` — a 30-page PDF counts as 1 here and 30 on the server.
146
+ - **Going over the request limit is not a polite error.** Past that the connection is cut before the API can answer, so the caller sees a write timeout or a broken pipe instead of a status code. The client therefore checks locally *before* uploading and throws `InvalidFileError` (`code: "PAYLOAD_TOO_LARGE"`) without sending a byte.
147
+ - **A failed submission is never retried automatically.** A network error only tells you the exchange broke — not whether the request body arrived. The job may not exist, or it may exist with credits already reserved and only the response lost. Retrying the second case charges you twice, and there is no idempotency key to tell them apart, so the error is thrown as-is. Check `account()` or your job list before resending. (Rate limits *are* retried by `submitAll()` / `convertAll()`: a 429 is the server saying it did not take the submission.)
148
+ - **Downloads are all-or-nothing.** `download()` writes to a temporary file next to the destination and renames it into place at the end, so a dropped connection cannot leave a truncated `.pptx` behind — or destroy a good deck already sitting at that path.
149
+ - **Every request identifies the client** with a `User-Agent` of `image2ppt-node/<version>`. The service uses this to tell SDK versions apart — it is not part of authentication and never changes a request's outcome.
150
+ - **A deprecated SDK version logs one warning.** If this version is below the lowest the service still supports, the response carries a `Deprecation` header and the client warns once (`console.warn`). Pass `warnOnDeprecated: false` to `Image2PPTClient` to silence it.
151
+ - **Time units.** `pollIntervalMs` and `timeoutMs` are in **milliseconds** (idiomatic for Node's timers).
152
+ - **The 60-second request timeout is idle time, not total time.** `timeoutMs` (default 60000) is how long one request may go with **no data moving in either direction** — it is not a cap on how long a request may take. A 40MB upload or a large PPTX download that keeps making progress runs as long as it needs to; only a transfer that actually stalls is given up on, as `APITimeoutError`. A request that never gets a response at all is covered by the same clock. This matches the Python client's read timeout, so the two SDKs behave the same way on a slow link.
153
+ - **Every failure of the request is an `Image2PPTError`.** A refused connection, a reset mid-download, a 2xx that comes back as a proxy's HTML login page, a job body missing its own id — all of them arrive as an SDK error with the original kept on `.cause`, never as a raw `TypeError: fetch failed` or `SyntaxError`. **Your own filesystem is the exception, deliberately:** if `download` cannot write where you asked it to, you get the operating system's error — `ENOSPC`, `EACCES`, `ENOENT` — because that names the thing you have to go and fix, and no error of ours would say it better. The Python client draws the same line.
154
+
155
+ > Both the Node and Python SDKs pre-compress images that need processing before upload. PNG/JPEG files already at most 1MiB with a longest edge at most 2000px upload byte-for-byte unchanged. Other PNG/JPEG files, and all WebP/GIF files, may be resized, flattened onto white, and sent as JPEG; PDFs are never compressed or decoded and are streamed unchanged.
156
+
157
+ ## More files than one request can hold
158
+
159
+ `convert()` is one job, one PPTX. For a pile too big for a single request, `convertAll()` splits it and writes **one PPTX per batch** (no server-side merge — N batches means N decks):
160
+
161
+ ```ts
162
+ const files = await client.convertAll(imagePaths, "decks/");
163
+ console.log(files); // ['decks/part-01.pptx', 'decks/part-02.pptx']
164
+ ```
165
+
166
+ Batches hold at most 40MB of file content and at most 50 images; every PDF goes in a batch of its own, because the client does not parse PDFs and only the server knows their page count. `submitAll()` does the same splitting and hands back the jobs if you want to drive polling yourself. To see the plan without uploading anything, use `planBatches()`.
167
+
168
+ **Rate limits are waited out, not thrown.** A pile big enough to need batching will hit the account's per-minute page quota (and its cap on concurrently active jobs). Both arrive as a `429` with a `Retry-After`; both are handled the same way — sleep that long, retry the same batch. Retrying a 429 is free: the server is saying it did *not* take the submission, so nothing was created and nothing was charged. Total waiting is capped by `rateLimitMaxWaitMs` (default 30 min) — and **only waiting counts against it**, not the time the uploads themselves take, so a slow link cannot quietly turn the cap into "do not wait at all". A single batch is also retried at most 10 times, whatever the budget says: every retry re-uploads the whole batch, and a service still refusing after ten tries will not be talked round by more of them.
169
+
170
+ If a batch call does fail partway, **the jobs it already created come back on the error**:
171
+
172
+ ```ts
173
+ import { Image2PPTError } from "image2ppt";
174
+
175
+ try {
176
+ const files = await client.convertAll(imagePaths, "decks/");
177
+ } catch (e) {
178
+ if (e instanceof Image2PPTError) {
179
+ // Already running with credits reserved — collect them, don't resubmit.
180
+ for (const job of e.submittedJobs) console.log("still running:", job.jobId);
181
+ }
182
+ throw e;
183
+ }
184
+ ```
185
+
186
+ ## Rate limits
187
+
188
+ Per account (all keys share the budget): ≤ 10 concurrent jobs, ≤ 60 pages/minute submitted. Over the limit returns `429` with a `Retry-After` hint. **Only submissions are rate limited — polling job status is not.**
189
+
190
+ `submitAll()` / `convertAll()` wait these out for you: a pile big enough to need batching is a pile big enough to hit the quota, so a 429 mid-pile is the normal path, not an error. `submit()` and `convert()` do not — they submit exactly once, so catch `RateLimitedError` and honor `retryAfter` (seconds) yourself:
191
+
192
+ ```ts
193
+ import { RateLimitedError } from "image2ppt";
194
+
195
+ for (;;) {
196
+ try {
197
+ job = await client.submit(paths);
198
+ break;
199
+ } catch (e) {
200
+ if (e instanceof RateLimitedError) {
201
+ await new Promise((r) => setTimeout(r, (e.retryAfter ?? 5) * 1000));
202
+ } else throw e;
203
+ }
204
+ }
205
+ ```
206
+
207
+ ## Errors
208
+
209
+ Every error this client raises about a *request* subclasses `Image2PPTError` and carries `statusCode`, `code`, and `message`. Branch on `code`, not `message`. The one thing that reaches you unwrapped is a failure of your own disk while `download` is writing — see the note above.
210
+
211
+ Every `Image2PPTError` also carries **`isTransient`** — whether **repeating this exact read** later could plausibly succeed. (A raw filesystem error from `download` has no such field; that is the same exception as above, and it is never transient — free the space or fix the permission first.) It is what `wait()` uses to decide whether a failed status poll should be backed off and retried or should end the wait: `true` for a 5xx, a dropped connection and a stalled request, `false` for a bad key, a job that does not exist, or a response this client cannot parse.
212
+
213
+ **It says nothing about submitting.** `submit()` is never retried on this signal, and neither should your code be: a lost response cannot be told apart from a submission the server accepted, and there is no idempotency key, so resending the same files can create the same job twice and **charge you twice**. Only a `RateLimitedError` is retried on the submit path — a 429 is the service explicitly saying it took nothing.
214
+
215
+ | Class | HTTP | code |
216
+ |---|---|---|
217
+ | `AuthenticationError` | 401 / 403 | `INVALID_API_KEY`, `API_KEY_REQUIRED`, `ACCOUNT_DELETED` |
218
+ | `InvalidFileError` | 400 / 413 | `INVALID_FILE`, `INVALID_PDF`, `PAYLOAD_TOO_LARGE` (the size checks also fire locally, before upload) |
219
+ | `UploadAbortedError` | 400 | `UPLOAD_ABORTED` — the body never finished arriving and the server took nothing, so **resending the same files is safe** |
220
+ | `MalformedUploadError` | 400 | `MALFORMED_UPLOAD` — the body was not valid `multipart/form-data`; **resending identical bytes will not help** |
221
+ | `NoFilesError` | 400 | `NO_FILES` — no files reached the server |
222
+ | `InvalidAspectRatioError` | 400 | `INVALID_ASPECT_RATIO` — use `auto`, `16:9`, or `4:3` |
223
+ | `TooManySlidesError` | 400 | `TOO_MANY_SLIDES` |
224
+ | `PageRateExceededError` | 400 | `PAGE_RATE_EXCEEDED` — this one submission has more pages than a minute's quota, so waiting will not help; split it |
225
+ | `InsufficientCreditsError` | 402 | `INSUFFICIENT_CREDITS` |
226
+ | `RateLimitedError` | 429 | `RATE_LIMITED` (has `retryAfter`) |
227
+ | `JobNotFoundError` | 404 | `JOB_NOT_FOUND` |
228
+ | `JobAlreadyFinishedError` | 409 | `JOB_ALREADY_FINISHED` — the job already finished, **or is past the point where cancelling could change the outcome** |
229
+ | `NotReadyError` | 409 | `NOT_READY` |
230
+ | `OutputExpiredError` | 410 | `OUTPUT_EXPIRED` |
231
+ | `JobCancelledError` | — | `JOB_CANCELLED` — cancellation settled with no deliverable; subclasses `JobFailedError` |
232
+ | `JobFailedError` | — | job's `error.code` (thrown by `wait()`; `.job` is the snapshot) |
233
+ | `ServerError` | 5xx | `JOB_CANCEL_FAILED`, `STORAGE_FAILED`, … — the service failed on its own side; **retrying later is reasonable** (`isTransient` is true). Branch on `.code`. |
234
+ | `APIConnectionError` | — | — (the request never completed: connection refused or reset, DNS or TLS failure, a body that stopped arriving; the underlying error is on `.cause`) |
235
+ | `APITimeoutError` | — | `REQUEST_TIMEOUT` — one request went `timeoutMs` with **no data moving**; subclasses `APIConnectionError` |
236
+ | `MalformedResponseError` | — | — (the server answered with something this client cannot read: a 2xx that is not JSON, or a body missing a field the contract guarantees) |
237
+ | `Image2PPTTimeoutError` | — | — (`wait()` exceeded its own `timeoutMs`; job may still be running — **not** a transport failure, and not the same as `APITimeoutError`) |
238
+
239
+ > **5xx now lands on `ServerError` rather than the base class.** It still subclasses `Image2PPTError`, so `catch (e) { if (e instanceof Image2PPTError) }` is unaffected — only code matching on the exact class or on `e.name` needs updating.
240
+
241
+ ```ts
242
+ import { APIConnectionError, Image2PPTError, JobFailedError } from "image2ppt";
243
+
244
+ try {
245
+ await client.convert(paths, "out.pptx");
246
+ } catch (e) {
247
+ if (e instanceof JobFailedError) console.error("failed:", e.code, e.message);
248
+ // Covers APITimeoutError too — it's a subclass. `.cause` has the real reason.
249
+ else if (e instanceof APIConnectionError) console.error("network:", e.message);
250
+ else if (e instanceof Image2PPTError) console.error("request error:", e.statusCode, e.code);
251
+ else throw e;
252
+ }
253
+ ```
254
+
255
+ ## Full API reference
256
+
257
+ See <https://image2ppt.com/en/docs/api> for the complete HTTP contract (endpoints, fields, error codes). 中文版:<https://image2ppt.com/docs/api>。
258
+
259
+ ## Develop
260
+
261
+ ```bash
262
+ npm install
263
+ npm run build # tsc -> dist/
264
+ npm test # vitest
265
+ ```
266
+
267
+ ## License
268
+
269
+ [MIT](./LICENSE)
@@ -0,0 +1,154 @@
1
+ /** The image2ppt API client. */
2
+ import type { Account, CancellationResult, ClientOptions, ConvertOptions, SubmitOptions, WaitOptions } from "./types.js";
3
+ import { Job } from "./types.js";
4
+ export declare const DEFAULT_BASE_URL = "https://image2ppt.com";
5
+ /**
6
+ * Client for the image2ppt API. Server-side only — keep your key off the browser.
7
+ *
8
+ * ```ts
9
+ * const client = new Image2PPTClient({ apiKey: "i2p_live_..." });
10
+ * const job = await client.convert(["slide.png", "report.pdf"], "out.pptx");
11
+ * ```
12
+ */
13
+ export declare class Image2PPTClient {
14
+ #private;
15
+ readonly baseUrl: string;
16
+ readonly timeoutMs: number;
17
+ readonly rateLimitMaxWaitMs: number;
18
+ readonly warnOnDeprecated: boolean;
19
+ /**
20
+ * `Accept-Language` sent on every request, or undefined to send none.
21
+ *
22
+ * Sets the language of the service's error **messages** only — not the language
23
+ * of the deck, which is `SubmitOptions.locale`.
24
+ */
25
+ readonly acceptLanguage?: string;
26
+ constructor(options: ClientOptions);
27
+ /**
28
+ * Submit a batch of files and create a conversion job.
29
+ *
30
+ * Checked locally before anything is uploaded: the files must add up to at most
31
+ * 45MB and at most 50 pages. Over either limit this throws without opening a
32
+ * connection — going over the size cap on the wire does not come back as a clean
33
+ * error, it comes back as a dead connection.
34
+ *
35
+ * **A failed submission is never retried automatically.** A network error does
36
+ * not tell you whether the request body made it: the job may not exist, or it
37
+ * may exist with credits already reserved and only the response lost. Retrying
38
+ * the second case charges twice, and without an idempotency key there is no way
39
+ * to tell them apart — so the error is thrown as-is. Check `account()` or your
40
+ * job list before resending.
41
+ *
42
+ * @param paths Local file paths (one or more). png/jpeg/webp/gif/pdf, each ≤ 35MB,
43
+ * and ≤ 45MB of file content per request. An image is 1 page, a PDF is its page
44
+ * count; the total must be ≤ 50 pages. For more files than one request can hold,
45
+ * use `submitAll` / `convertAll`.
46
+ * @returns A `Job` with status `pending`, plus `slideCount` and `creditsReserved`.
47
+ * @throws InvalidFileError Including the local `PAYLOAD_TOO_LARGE` pre-flight failure.
48
+ */
49
+ submit(paths: string[], options?: SubmitOptions): Promise<Job>;
50
+ /**
51
+ * Split files into submittable batches and create **one job per batch**.
52
+ *
53
+ * For a pile of files too big or too numerous for a single request. Batching
54
+ * rules live in `planBatches`: at most 40MB of file content and at most 50
55
+ * images per batch, and every PDF in a batch of its own (the SDK does not parse
56
+ * PDFs, so only the server knows their page count). Input order is preserved.
57
+ *
58
+ * **Each returned job produces its own PPTX.** There is no server-side merge —
59
+ * N batches means N decks. If you need exactly one deck, keep the submission
60
+ * inside one request's limits and use `convert`.
61
+ *
62
+ * **Rate limits are waited out, not thrown.** A pile big enough to need
63
+ * batching is a pile big enough to hit the account's per-minute page quota (and
64
+ * its cap on concurrently active jobs). Both arrive as a 429 with a
65
+ * `Retry-After`, and both are handled the same way: sleep that long, then try
66
+ * the same batch again. Waiting is the normal path here. Total waiting is capped
67
+ * by the client's `rateLimitMaxWaitMs`.
68
+ *
69
+ * **Network errors are not retried** — see `submit`. Only a 429 is, because only
70
+ * a 429 proves the server did not take the submission.
71
+ *
72
+ * **If it does give up, the jobs already created are handed back on the error**,
73
+ * in `err.submittedJobs`. Those jobs are running on the server with credits
74
+ * already reserved — they are not lost and not refunded. Wait on them or fetch
75
+ * them later; do not resubmit those files.
76
+ *
77
+ * @returns One pending `Job` per batch, in batch order.
78
+ * @throws InvalidFileError A single file is over the 35MB per-file limit, so no
79
+ * batching can carry it.
80
+ * @throws RateLimitedError Still rate limited after `rateLimitMaxWaitMs`.
81
+ */
82
+ submitAll(paths: string[], options?: SubmitOptions): Promise<Job[]>;
83
+ /** Fetch the current job state as a `Job` snapshot. Throws JobNotFoundError. */
84
+ getJob(jobId: string): Promise<Job>;
85
+ /**
86
+ * Request graceful cancellation of a conversion job.
87
+ *
88
+ * Pages already running finish and remain in the deliverable; pages that have
89
+ * not started are skipped and refunded. **A page being dispatched at the very
90
+ * moment the request arrives may still run to completion and be billed.**
91
+ * Repeating the call is safe. When `finalizing` is true, keep polling with
92
+ * `getJob` until the job is terminal.
93
+ *
94
+ * @throws JobAlreadyFinishedError The job already finished, **or is past the
95
+ * point where cancelling could change the outcome** (409 `JOB_ALREADY_FINISHED`).
96
+ */
97
+ cancel(jobId: string): Promise<CancellationResult>;
98
+ /**
99
+ * Poll until the job reaches a terminal state; return the completed `Job`.
100
+ *
101
+ * Backs off from `pollIntervalMs` to 15s. On a 429 it waits the `Retry-After`
102
+ * seconds. A failed job throws JobFailedError; exceeding `timeoutMs` throws
103
+ * Image2PPTTimeoutError (the job itself may still be running).
104
+ */
105
+ wait(jobId: string, options?: WaitOptions): Promise<Job>;
106
+ /**
107
+ * Download a completed job's PPTX to `destPath`; return that path. Throws
108
+ * NotReadyError (409), JobNotFoundError (404), or OutputExpiredError (410).
109
+ *
110
+ * **`destPath` either holds a complete deck or is not written at all.** The bytes
111
+ * go to a temporary file beside it and are renamed into place once the last one
112
+ * arrives, so a connection dropped mid-download cannot leave a truncated `.pptx`
113
+ * that opens in a file listing and nowhere else. That matters most for
114
+ * `convertAll`, whose contract is "the decks already downloaded stay on disk" — a
115
+ * half-written `part-02.pptx` would be indexed as one of them.
116
+ */
117
+ download(jobId: string, destPath: string): Promise<string>;
118
+ /**
119
+ * One-shot: submit → wait for completion → download to `destPath`.
120
+ *
121
+ * One job, one PPTX — the files must fit in a single submission (45MB of file
122
+ * content, 50 pages). For more than that, `convertAll` splits the pile and writes
123
+ * one PPTX per batch.
124
+ */
125
+ convert(paths: string[], destPath: string, options?: ConvertOptions): Promise<Job>;
126
+ /**
127
+ * Batch version of `convert`: submit everything, wait, download each deck.
128
+ *
129
+ * Files are split with `submitAll`, every batch is submitted first (so the
130
+ * server works on them in parallel), then each job is waited on and downloaded
131
+ * in order.
132
+ *
133
+ * **This writes one PPTX per batch, not one merged deck.** Output files are
134
+ * named `part-01.pptx`, `part-02.pptx`, ... inside `destDir` — stable for the
135
+ * same input, and never overwriting each other. Existing files with those names
136
+ * are overwritten. `convert` is unchanged: one job, one PPTX.
137
+ *
138
+ * `timeoutMs` is the wait cap **per job**, not for the whole pile. If a job
139
+ * fails or runs past it, earlier batches that already downloaded stay on disk
140
+ * and later ones are not waited on. Every job created so far is on the thrown
141
+ * error as `err.submittedJobs` — those are still running with credits reserved,
142
+ * so wait on them rather than resubmitting.
143
+ *
144
+ * Rate limits during submission are waited out — see `submitAll`.
145
+ *
146
+ * @param destDir Directory for the PPTX files. Created **and proven writable
147
+ * before anything is submitted**, so an unusable destination costs nothing.
148
+ * @returns The written file paths, in batch order.
149
+ */
150
+ convertAll(paths: string[], destDir: string, options?: ConvertOptions): Promise<string[]>;
151
+ /** Return account info: `{ email, credits }` (available credits). */
152
+ account(): Promise<Account>;
153
+ }
154
+ //# sourceMappingURL=client.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AAAA,gCAAgC;AA8BhC,OAAO,KAAK,EACV,OAAO,EACP,kBAAkB,EAClB,aAAa,EACb,cAAc,EACd,aAAa,EACb,WAAW,EACZ,MAAM,YAAY,CAAC;AACpB,OAAO,EAAE,GAAG,EAA2B,MAAM,YAAY,CAAC;AAG1D,eAAO,MAAM,gBAAgB,0BAA0B,CAAC;AAgbxD;;;;;;;GAOG;AACH,qBAAa,eAAe;;IAC1B,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,kBAAkB,EAAE,MAAM,CAAC;IACpC,QAAQ,CAAC,gBAAgB,EAAE,OAAO,CAAC;IACnC;;;;;OAKG;IACH,QAAQ,CAAC,cAAc,CAAC,EAAE,MAAM,CAAC;gBAKrB,OAAO,EAAE,aAAa;IAiBlC;;;;;;;;;;;;;;;;;;;;;OAqBG;IACG,MAAM,CAAC,KAAK,EAAE,MAAM,EAAE,EAAE,OAAO,GAAE,aAAkB,GAAG,OAAO,CAAC,GAAG,CAAC;IAOxE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OA+BG;IACG,SAAS,CAAC,KAAK,EAAE,MAAM,EAAE,EAAE,OAAO,GAAE,aAAkB,GAAG,OAAO,CAAC,GAAG,EAAE,CAAC;IAmC7E,gFAAgF;IAC1E,MAAM,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,GAAG,CAAC;IAQzC;;;;;;;;;;;OAWG;IACG,MAAM,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,kBAAkB,CAAC;IAQxD;;;;;;OAMG;IACG,IAAI,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,GAAE,WAAgB,GAAG,OAAO,CAAC,GAAG,CAAC;IAmDlE;;;;;;;;;;OAUG;IACG,QAAQ,CAAC,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IAoEhE;;;;;;OAMG;IACG,OAAO,CACX,KAAK,EAAE,MAAM,EAAE,EACf,QAAQ,EAAE,MAAM,EAChB,OAAO,GAAE,cAAmB,GAC3B,OAAO,CAAC,GAAG,CAAC;IAOf;;;;;;;;;;;;;;;;;;;;;;;OAuBG;IACG,UAAU,CACd,KAAK,EAAE,MAAM,EAAE,EACf,OAAO,EAAE,MAAM,EACf,OAAO,GAAE,cAAmB,GAC3B,OAAO,CAAC,MAAM,EAAE,CAAC;IAyBpB,qEAAqE;IAC/D,OAAO,IAAI,OAAO,CAAC,OAAO,CAAC;CAuXlC"}