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/dist/errors.js ADDED
@@ -0,0 +1,293 @@
1
+ /**
2
+ * Exception hierarchy for the image2ppt client.
3
+ *
4
+ * Every error carries the HTTP `statusCode`, the server error `code` (from the
5
+ * `{ error: { code, message } }` envelope), and a human-readable `message`.
6
+ * Branch on `code`, not `message` — messages may be reworded.
7
+ */
8
+ /** Base class for all client errors. */
9
+ export class Image2PPTError extends Error {
10
+ statusCode;
11
+ code;
12
+ /**
13
+ * Jobs already created when this error was thrown out of `submitAll` /
14
+ * `convertAll`. They are **running on the server with credits already
15
+ * reserved** — not lost, not refunded by the failure. Wait on them
16
+ * (`wait`/`download`) or come back to them later. Empty for any error not
17
+ * thrown out of a batch call.
18
+ */
19
+ submittedJobs = [];
20
+ constructor(message, init = {}) {
21
+ super(message, "cause" in init ? { cause: init.cause } : undefined);
22
+ // new.target gives the concrete subclass, so subclasses get the right name
23
+ // without each redefining it.
24
+ this.name = new.target.name;
25
+ this.statusCode = init.statusCode;
26
+ this.code = init.code;
27
+ }
28
+ /**
29
+ * Whether retrying the same call later could plausibly succeed.
30
+ *
31
+ * `wait()` polls a job for as long as half an hour, and this is the flag it
32
+ * consults to decide whether one failed poll should be backed off and retried
33
+ * or should end the wait. It used to ask a different question — "did this error
34
+ * come out of this SDK?" — and got both halves wrong: an unexpected bug in this
35
+ * client was retried for the whole deadline, while a single slow status poll
36
+ * (which this SDK reports as its own error) aborted the wait outright.
37
+ *
38
+ * The default is the HTTP rule: a 5xx is the server's own problem and may clear
39
+ * on its own, everything else — a bad key, a job that does not exist, a
40
+ * rejected file — will still be wrong in fifteen seconds. Subclasses that know
41
+ * better override it.
42
+ */
43
+ get isTransient() {
44
+ return this.statusCode != null && this.statusCode >= 500;
45
+ }
46
+ }
47
+ /**
48
+ * The request never completed at the transport level.
49
+ *
50
+ * A connection refused or reset, a DNS or TLS failure, a response body that
51
+ * stopped arriving. The underlying error is kept as `cause`.
52
+ *
53
+ * **This does not tell you whether the server acted on the request.** For a
54
+ * submission it is genuinely ambiguous — the job may not exist, or it may exist
55
+ * with credits reserved and only the reply lost — which is why `submit` never
56
+ * retries one for you. Polling a job's status has no such cost, so `wait()` does
57
+ * back these off and retry (`isTransient` is true).
58
+ */
59
+ export class APIConnectionError extends Image2PPTError {
60
+ get isTransient() {
61
+ return true;
62
+ }
63
+ }
64
+ /**
65
+ * A single HTTP request went `timeoutMs` with no data moving in either
66
+ * direction (`code: "REQUEST_TIMEOUT"`).
67
+ *
68
+ * `timeoutMs` is an **idle** timeout, not a cap on how long a request may take:
69
+ * a large upload or download that keeps making progress is never cut off by it,
70
+ * however long it runs in total.
71
+ *
72
+ * **Not the same as `Image2PPTTimeoutError`**, and the two are deliberately kept
73
+ * apart. This one is a transport failure — one request stalled — so it is
74
+ * transient and `wait()` retries it. `Image2PPTTimeoutError` means `wait()` ran
75
+ * out of its own overall deadline while the job was still running perfectly
76
+ * well; nothing failed, and there is nothing to retry.
77
+ */
78
+ export class APITimeoutError extends APIConnectionError {
79
+ /**
80
+ * `code` defaults here rather than at each `throw`, the way the Python class
81
+ * does it. Every construction site meant the same thing by it, and one that
82
+ * forgot would have produced an `APITimeoutError` with no code at all for a
83
+ * caller branching on codes.
84
+ */
85
+ constructor(message, init = {}) {
86
+ super(message, { code: "REQUEST_TIMEOUT", ...init });
87
+ }
88
+ }
89
+ /**
90
+ * The server answered, but this client cannot make sense of the answer.
91
+ *
92
+ * Either a 2xx body that is not JSON at all — a captive-portal login page, a CDN
93
+ * error page, a proxy that replaced the response — or a JSON body missing a
94
+ * field the API contract guarantees.
95
+ *
96
+ * **Never transient**, on purpose. A response that cannot be parsed is a sign
97
+ * that something between this client and the API is rewriting traffic, and no
98
+ * amount of waiting fixes it. Retrying would mean `wait()` silently swallowing
99
+ * the evidence for half an hour and then reporting a timeout instead.
100
+ */
101
+ export class MalformedResponseError extends Image2PPTError {
102
+ get isTransient() {
103
+ return false;
104
+ }
105
+ }
106
+ /**
107
+ * The service failed on its own side (any 5xx).
108
+ *
109
+ * The contract's advice for these is to retry later, so `isTransient` is true and
110
+ * `wait()` backs off and polls again rather than giving up. Subclasses
111
+ * `Image2PPTError` like every other error here, so existing
112
+ * `catch (e) { if (e instanceof Image2PPTError) ... }` code is unaffected.
113
+ */
114
+ export class ServerError extends Image2PPTError {
115
+ }
116
+ /** API key is missing, invalid, or the account is gone (401 / 403). */
117
+ export class AuthenticationError extends Image2PPTError {
118
+ }
119
+ /**
120
+ * A file was rejected (400), or the request carried too much file content.
121
+ *
122
+ * Raised for an unsupported format, a single file over the 35MB per-file limit, or
123
+ * a request whose files add up to more than the 45MB per-request limit
124
+ * (413 `PAYLOAD_TOO_LARGE`). The client raises the `PAYLOAD_TOO_LARGE` case
125
+ * locally, before uploading anything.
126
+ */
127
+ export class InvalidFileError extends Image2PPTError {
128
+ }
129
+ /**
130
+ * The upload was cut off before the body finished arriving (400 `UPLOAD_ABORTED`).
131
+ *
132
+ * The server is telling you it did **not** take the submission — no job was created
133
+ * and no credits were reserved — so **resending the same files is safe**. That makes
134
+ * this different from a transport-level fetch failure, which cannot rule out that the
135
+ * job was created and only the response was lost; the client never retries that one
136
+ * for you.
137
+ *
138
+ * If it keeps happening the submission is probably too large for the link. Send fewer
139
+ * files per request, or use `submitAll` / `convertAll` to split.
140
+ */
141
+ export class UploadAbortedError extends Image2PPTError {
142
+ }
143
+ /**
144
+ * The body was not valid `multipart/form-data` (400 `MALFORMED_UPLOAD`). A
145
+ * client-side framing problem: **retrying the identical payload will not help**.
146
+ * Using this SDK unmodified you should never see it; if you do, please report it.
147
+ */
148
+ export class MalformedUploadError extends Image2PPTError {
149
+ }
150
+ /**
151
+ * The request carried no files at all (400 `NO_FILES`). Using this SDK you should
152
+ * never see it — `submit` refuses an empty `paths` before opening a connection.
153
+ */
154
+ export class NoFilesError extends Image2PPTError {
155
+ }
156
+ /**
157
+ * `aspectRatio` was not one of the accepted values (400 `INVALID_ASPECT_RATIO`).
158
+ * Accepted: `auto` (default), `16:9`, `4:3`. Nothing was created and nothing was
159
+ * charged — fix the value and submit again.
160
+ */
161
+ export class InvalidAspectRatioError extends Image2PPTError {
162
+ }
163
+ /**
164
+ * One submission holds more pages than the per-minute quota allows (400
165
+ * `PAGE_RATE_EXCEEDED`).
166
+ *
167
+ * Distinct from `RateLimitedError`: a 429 means "not right now, try again in N
168
+ * seconds" and the same submission will eventually go through. `PAGE_RATE_EXCEEDED`
169
+ * means this submission can *never* fit the window whole, so waiting does not help —
170
+ * split it, e.g. with `submitAll` / `convertAll`.
171
+ */
172
+ export class PageRateExceededError extends Image2PPTError {
173
+ }
174
+ /** The submission exceeds the 50-page-per-job limit (400 TOO_MANY_SLIDES). */
175
+ export class TooManySlidesError extends Image2PPTError {
176
+ }
177
+ /** Not enough available credits to cover the submission (402). */
178
+ export class InsufficientCreditsError extends Image2PPTError {
179
+ }
180
+ /** The job id doesn't exist, or isn't owned by this key's account (404). */
181
+ export class JobNotFoundError extends Image2PPTError {
182
+ }
183
+ /** The job finished naturally before a cancellation request was accepted (409). */
184
+ export class JobAlreadyFinishedError extends Image2PPTError {
185
+ }
186
+ /** The job hasn't finished yet, so the deliverable can't be downloaded (409). */
187
+ export class NotReadyError extends Image2PPTError {
188
+ }
189
+ /** The job finished, but its PPTX passed the retention window and was reaped (410). */
190
+ export class OutputExpiredError extends Image2PPTError {
191
+ }
192
+ /**
193
+ * Rate limited (429 RATE_LIMITED). `retryAfter` is the server-suggested wait in
194
+ * seconds (from the `Retry-After` header); retry after that long.
195
+ *
196
+ * Both kinds of 429 land here — the per-minute page quota and the cap on
197
+ * concurrently active jobs — and both are handled the same way: wait, then try the
198
+ * same submission again. `submitAll` / `convertAll` do that for you; if one of
199
+ * them gives up, `submittedJobs` holds the jobs already created.
200
+ */
201
+ export class RateLimitedError extends Image2PPTError {
202
+ retryAfter;
203
+ constructor(message, init = {}) {
204
+ super(message, init);
205
+ this.retryAfter = init.retryAfter;
206
+ }
207
+ /** "Not right now" is the one server answer that says to come back and ask again. */
208
+ get isTransient() {
209
+ return true;
210
+ }
211
+ }
212
+ /**
213
+ * The job ended in failure (raised by `wait` when it polls status=failed). `job`
214
+ * is the failure snapshot; `code` / `message` come from its `error` field.
215
+ */
216
+ export class JobFailedError extends Image2PPTError {
217
+ job;
218
+ constructor(message, init = {}) {
219
+ super(message, { code: init.code });
220
+ this.job = init.job;
221
+ }
222
+ }
223
+ /** A graceful cancellation settled without any deliverable pages. */
224
+ export class JobCancelledError extends JobFailedError {
225
+ }
226
+ /**
227
+ * `wait` exceeded its `timeout` before the job reached a terminal state. This does
228
+ * not mean the job failed — it may still be running. Re-`wait` on the `jobId` later.
229
+ *
230
+ * **Not the same as `APITimeoutError`**, which means one HTTP request stalled with
231
+ * no data moving for `timeoutMs`. That one is a transport failure and `wait()`
232
+ * retries it internally; this one is `wait()` itself running out of the deadline
233
+ * the caller gave it, with nothing having gone wrong at all.
234
+ */
235
+ export class Image2PPTTimeoutError extends Image2PPTError {
236
+ jobId;
237
+ constructor(message, jobId) {
238
+ super(message);
239
+ this.jobId = jobId;
240
+ }
241
+ }
242
+ // Server error code -> exception class. Unlisted codes fall back to the status-code
243
+ // map, then to `ServerError` for any 5xx, then to the base class.
244
+ const CODE_TO_CLASS = {
245
+ INVALID_API_KEY: AuthenticationError,
246
+ API_KEY_REQUIRED: AuthenticationError,
247
+ ACCOUNT_DELETED: AuthenticationError,
248
+ INVALID_FILE: InvalidFileError,
249
+ INVALID_PDF: InvalidFileError,
250
+ PAYLOAD_TOO_LARGE: InvalidFileError,
251
+ UPLOAD_ABORTED: UploadAbortedError,
252
+ MALFORMED_UPLOAD: MalformedUploadError,
253
+ NO_FILES: NoFilesError,
254
+ INVALID_ASPECT_RATIO: InvalidAspectRatioError,
255
+ PAGE_RATE_EXCEEDED: PageRateExceededError,
256
+ TOO_MANY_SLIDES: TooManySlidesError,
257
+ INSUFFICIENT_CREDITS: InsufficientCreditsError,
258
+ RATE_LIMITED: RateLimitedError,
259
+ JOB_NOT_FOUND: JobNotFoundError,
260
+ JOB_ALREADY_FINISHED: JobAlreadyFinishedError,
261
+ NOT_READY: NotReadyError,
262
+ OUTPUT_EXPIRED: OutputExpiredError,
263
+ };
264
+ const STATUS_TO_CLASS = {
265
+ 401: AuthenticationError,
266
+ 403: AuthenticationError, // API_KEY_REQUIRED / ACCOUNT_DELETED (fallback if code absent)
267
+ 402: InsufficientCreditsError,
268
+ 404: JobNotFoundError,
269
+ 409: NotReadyError,
270
+ 410: OutputExpiredError,
271
+ 413: InvalidFileError,
272
+ 429: RateLimitedError,
273
+ };
274
+ /** Build the mapped exception for an error envelope. */
275
+ export function exceptionFor(args) {
276
+ const { statusCode, code, message, retryAfter } = args;
277
+ if (statusCode === 429) {
278
+ return new RateLimitedError(message, {
279
+ statusCode: 429,
280
+ code: code ?? "RATE_LIMITED",
281
+ retryAfter,
282
+ });
283
+ }
284
+ // A 5xx the maps do not claim is the service's own failure: retrying later is
285
+ // the contract's advice, so it gets a class that says so. Codes that already map
286
+ // to a specific class keep doing so, whatever status they arrive with — callers
287
+ // branch on `code`, and that must not change with the status line.
288
+ const cls = (code && CODE_TO_CLASS[code]) ||
289
+ STATUS_TO_CLASS[statusCode] ||
290
+ (statusCode >= 500 ? ServerError : Image2PPTError);
291
+ return new cls(message, { statusCode, code });
292
+ }
293
+ //# sourceMappingURL=errors.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"errors.js","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,24 @@
1
+ /**
2
+ * Official Node.js / TypeScript client for the image2ppt API.
3
+ * Convert images and PDFs into editable PowerPoint (.pptx) decks.
4
+ *
5
+ * ```ts
6
+ * import { Image2PPTClient } from "image2ppt";
7
+ *
8
+ * const client = new Image2PPTClient({ apiKey: "i2p_live_..." });
9
+ * const job = await client.convert(["slide.png", "report.pdf"], "out.pptx");
10
+ * console.log("credits used:", job.creditsUsed);
11
+ * ```
12
+ *
13
+ * Server-side only — keep your API key off the browser.
14
+ * See https://github.com/image2ppt/image2ppt-sdk for docs and examples.
15
+ */
16
+ export { DEFAULT_BASE_URL, Image2PPTClient } from "./client.js";
17
+ export { VERSION } from "./version.js";
18
+ export { APIConnectionError, APITimeoutError, AuthenticationError, Image2PPTError, Image2PPTTimeoutError, InsufficientCreditsError, InvalidAspectRatioError, InvalidFileError, JobAlreadyFinishedError, JobCancelledError, JobFailedError, JobNotFoundError, MalformedResponseError, MalformedUploadError, NoFilesError, NotReadyError, OutputExpiredError, PageRateExceededError, RateLimitedError, ServerError, TooManySlidesError, UploadAbortedError, } from "./errors.js";
19
+ export type { ErrorInit } from "./errors.js";
20
+ export { BATCH_TARGET_BYTES, MAX_FILE_BYTES, MAX_PAGES_PER_JOB, MAX_UPLOAD_BYTES, checkFileSize, checkSubmission, planBatches, } from "./limits.js";
21
+ export type { UploadItem } from "./limits.js";
22
+ export { Job, PageError, PageResult } from "./types.js";
23
+ export type { Account, AspectRatio, CancellationResult, ClientOptions, ConvertOptions, JobError, JobStatus, Locale, PageStatus, SubmitOptions, WaitOptions, } from "./types.js";
24
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,OAAO,EAAE,gBAAgB,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAChE,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AACvC,OAAO,EACL,kBAAkB,EAClB,eAAe,EACf,mBAAmB,EACnB,cAAc,EACd,qBAAqB,EACrB,wBAAwB,EACxB,uBAAuB,EACvB,gBAAgB,EAChB,uBAAuB,EACvB,iBAAiB,EACjB,cAAc,EACd,gBAAgB,EAChB,sBAAsB,EACtB,oBAAoB,EACpB,YAAY,EACZ,aAAa,EACb,kBAAkB,EAClB,qBAAqB,EACrB,gBAAgB,EAChB,WAAW,EACX,kBAAkB,EAClB,kBAAkB,GACnB,MAAM,aAAa,CAAC;AACrB,YAAY,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAC7C,OAAO,EACL,kBAAkB,EAClB,cAAc,EACd,iBAAiB,EACjB,gBAAgB,EAChB,aAAa,EACb,eAAe,EACf,WAAW,GACZ,MAAM,aAAa,CAAC;AACrB,YAAY,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAC9C,OAAO,EAAE,GAAG,EAAE,SAAS,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AACxD,YAAY,EACV,OAAO,EACP,WAAW,EACX,kBAAkB,EAClB,aAAa,EACb,cAAc,EACd,QAAQ,EACR,SAAS,EACT,MAAM,EACN,UAAU,EACV,aAAa,EACb,WAAW,GACZ,MAAM,YAAY,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,21 @@
1
+ /**
2
+ * Official Node.js / TypeScript client for the image2ppt API.
3
+ * Convert images and PDFs into editable PowerPoint (.pptx) decks.
4
+ *
5
+ * ```ts
6
+ * import { Image2PPTClient } from "image2ppt";
7
+ *
8
+ * const client = new Image2PPTClient({ apiKey: "i2p_live_..." });
9
+ * const job = await client.convert(["slide.png", "report.pdf"], "out.pptx");
10
+ * console.log("credits used:", job.creditsUsed);
11
+ * ```
12
+ *
13
+ * Server-side only — keep your API key off the browser.
14
+ * See https://github.com/image2ppt/image2ppt-sdk for docs and examples.
15
+ */
16
+ export { DEFAULT_BASE_URL, Image2PPTClient } from "./client.js";
17
+ export { VERSION } from "./version.js";
18
+ export { APIConnectionError, APITimeoutError, AuthenticationError, Image2PPTError, Image2PPTTimeoutError, InsufficientCreditsError, InvalidAspectRatioError, InvalidFileError, JobAlreadyFinishedError, JobCancelledError, JobFailedError, JobNotFoundError, MalformedResponseError, MalformedUploadError, NoFilesError, NotReadyError, OutputExpiredError, PageRateExceededError, RateLimitedError, ServerError, TooManySlidesError, UploadAbortedError, } from "./errors.js";
19
+ export { BATCH_TARGET_BYTES, MAX_FILE_BYTES, MAX_PAGES_PER_JOB, MAX_UPLOAD_BYTES, checkFileSize, checkSubmission, planBatches, } from "./limits.js";
20
+ export { Job, PageError, PageResult } from "./types.js";
21
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,OAAO,EAAE,gBAAgB,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAChE,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AACvC,OAAO,EACL,kBAAkB,EAClB,eAAe,EACf,mBAAmB,EACnB,cAAc,EACd,qBAAqB,EACrB,wBAAwB,EACxB,uBAAuB,EACvB,gBAAgB,EAChB,uBAAuB,EACvB,iBAAiB,EACjB,cAAc,EACd,gBAAgB,EAChB,sBAAsB,EACtB,oBAAoB,EACpB,YAAY,EACZ,aAAa,EACb,kBAAkB,EAClB,qBAAqB,EACrB,gBAAgB,EAChB,WAAW,EACX,kBAAkB,EAClB,kBAAkB,GACnB,MAAM,aAAa,CAAC;AAErB,OAAO,EACL,kBAAkB,EAClB,cAAc,EACd,iBAAiB,EACjB,gBAAgB,EAChB,aAAa,EACb,eAAe,EACf,WAAW,GACZ,MAAM,aAAa,CAAC;AAErB,OAAO,EAAE,GAAG,EAAE,SAAS,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC"}
@@ -0,0 +1,125 @@
1
+ /**
2
+ * Upload size limits and batch planning.
3
+ *
4
+ * The API caps the **file content of a single request** at 45MB. Going over that
5
+ * is not a friendly failure: the check can only run after the whole body has been
6
+ * received, and the network layer in front of the API gives up on an oversized
7
+ * request before it ever gets there — so a client that sends too much sees the
8
+ * connection die (a write timeout on a slow uplink, a broken pipe on a fast one)
9
+ * with no error code and no explanation.
10
+ *
11
+ * The fix is to never send too much. These limits, the pre-flight checks, and
12
+ * `planBatches` let a client know a submission is too big *before* opening a
13
+ * connection, and split a large pile of files into submittable batches.
14
+ *
15
+ * There are three separate caps, and they fail in different ways: one file
16
+ * (`MAX_FILE_BYTES`), the file content of one request (`MAX_UPLOAD_BYTES`), and
17
+ * pages per job (`MAX_PAGES_PER_JOB`). `BATCH_TARGET_BYTES` is not a fourth cap — it
18
+ * is how much `planBatches` puts in one batch, deliberately under the real one.
19
+ */
20
+ /**
21
+ * Server cap on **one** file. A file over this is rejected with `INVALID_FILE`
22
+ * however it is submitted, so no amount of batching helps. Keep in sync with the
23
+ * documented API contract.
24
+ */
25
+ export declare const MAX_FILE_BYTES: number;
26
+ /**
27
+ * Server cap on the **file content** of one request — boundaries, per-part headers
28
+ * and filenames do not count towards it. Over this, the request is rejected
29
+ * (413 `PAYLOAD_TOO_LARGE`) — or, further up, cut off outright before the server can
30
+ * answer at all. Keep in sync with the documented API contract.
31
+ */
32
+ export declare const MAX_UPLOAD_BYTES: number;
33
+ /**
34
+ * Byte budget for one auto-planned batch. **A splitting budget, not a cap** — the cap
35
+ * is `MAX_UPLOAD_BYTES`, and `checkSubmission` compares against that exactly.
36
+ *
37
+ * Deliberately below the cap, and the asymmetry is the point: starting one more batch
38
+ * costs nothing, while refusing a submission the server would have accepted is a bug in
39
+ * the guard. Nothing here needs the headroom — the planner measures the exact bytes it
40
+ * will send — so this is margin, not correction. **Do not turn it into a cap.**
41
+ * `MAX_UPLOAD_BYTES` is file content only; the multipart framing around it does not
42
+ * count against the published limit, and a pre-flight that "leaves room" for framing
43
+ * just makes the documented maximum unreachable.
44
+ */
45
+ export declare const BATCH_TARGET_BYTES: number;
46
+ /**
47
+ * Server cap on pages per job. An image is 1 page; a PDF counts as its own page
48
+ * count. Keep in sync with the documented API contract.
49
+ */
50
+ export declare const MAX_PAGES_PER_JOB = 50;
51
+ /**
52
+ * One file to upload, with the size it will occupy in the request.
53
+ *
54
+ * `size` is the final file-content byte count on the wire. Images may have been
55
+ * client-compressed already; PDFs retain their on-disk size.
56
+ *
57
+ * `isPdf` marks a file whose page count is unknown to the client — the SDK does
58
+ * not parse PDFs. Such a file is never mixed into a batch with others.
59
+ */
60
+ export interface UploadItem {
61
+ path: string;
62
+ size: number;
63
+ isPdf: boolean;
64
+ }
65
+ /** Format a byte count as MB, for human-readable error messages. */
66
+ export declare function formatBytes(size: number): string;
67
+ /**
68
+ * Throw if one file is over the per-file cap, whatever else it travels with.
69
+ *
70
+ * Separate from `checkSubmission` because it is a property of the file, not of
71
+ * the request: a 40MB PDF fits under the 45MB request cap and would sail through
72
+ * batch planning, then be rejected by the server every single time. Fail on it
73
+ * locally instead of building a batch that can never succeed.
74
+ *
75
+ * @param path The file, named in the error so the caller knows which one.
76
+ * @param size Its size as it will be uploaded.
77
+ * @throws InvalidFileError Over `MAX_FILE_BYTES` (`code: "INVALID_FILE"`, the same
78
+ * code the server would answer with).
79
+ */
80
+ export declare function checkFileSize(path: string, size: number): void;
81
+ /**
82
+ * Throw if a submission cannot succeed, before any bytes go on the wire.
83
+ *
84
+ * **The page check is a lower bound, not the server's verdict.** An image is
85
+ * exactly 1 page, but a PDF is however many pages it holds and the SDK does not
86
+ * parse PDFs (zero dependencies), so each one can only be counted as *at least* 1.
87
+ * That is enough to catch the combinations that are certain to fail — 50 images
88
+ * plus any PDF is at least 51 pages, so it never had a chance — but a submission
89
+ * that passes here can still come back `TOO_MANY_SLIDES` from the server, because
90
+ * a 30-page PDF counted as 1 here and as 30 there. Passing this check means "not
91
+ * obviously doomed", not "will be accepted".
92
+ *
93
+ * @param totalBytes Sum of the file sizes that will be sent in this request.
94
+ * @param imagePages Number of image files. Each is exactly 1 page.
95
+ * @param pdfFiles Number of PDFs (or other files whose page count is unknown to
96
+ * the client). Each counts as at least 1 page.
97
+ * @throws TooManySlidesError The minimum page count already exceeds what one job
98
+ * can hold.
99
+ * @throws InvalidFileError File content over the per-request cap
100
+ * (`code = "PAYLOAD_TOO_LARGE"`).
101
+ */
102
+ export declare function checkSubmission(totalBytes: number, imagePages: number, pdfFiles?: number): void;
103
+ /**
104
+ * Split files into batches that each fit in one submission.
105
+ *
106
+ * Pure function: no file system, no network. Same input, same output.
107
+ *
108
+ * Rules:
109
+ * - a batch holds at most `BATCH_TARGET_BYTES` of file content;
110
+ * - a batch holds at most `MAX_PAGES_PER_JOB` images. That count is exact, not a
111
+ * lower bound like `checkSubmission`'s: a PDF always flushes the current batch
112
+ * and takes one of its own, so a batch being filled here only ever holds images,
113
+ * and an image is always exactly 1 page;
114
+ * - **every PDF gets a batch to itself.** The SDK does not parse PDFs (zero
115
+ * dependencies), so the client cannot know how many pages one holds. Mixed into
116
+ * a batch, an unknown page count could push the job over the page limit with no
117
+ * way to predict it. Alone, the job is exactly that one PDF and the server's own
118
+ * count decides;
119
+ * - input order is preserved, so the same files always plan the same way.
120
+ *
121
+ * @throws InvalidFileError A single file is over `MAX_FILE_BYTES`. No batching can
122
+ * help — the server rejects that file however it is submitted.
123
+ */
124
+ export declare function planBatches(items: Iterable<UploadItem>): UploadItem[][];
125
+ //# sourceMappingURL=limits.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"limits.d.ts","sourceRoot":"","sources":["../src/limits.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAIH;;;;GAIG;AACH,eAAO,MAAM,cAAc,QAAmB,CAAC;AAE/C;;;;;GAKG;AACH,eAAO,MAAM,gBAAgB,QAAmB,CAAC;AAEjD;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,kBAAkB,QAAmB,CAAC;AAEnD;;;GAGG;AACH,eAAO,MAAM,iBAAiB,KAAK,CAAC;AAEpC;;;;;;;;GAQG;AACH,MAAM,WAAW,UAAU;IACzB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,OAAO,CAAC;CAChB;AAED,oEAAoE;AACpE,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAOhD;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,IAAI,CAS9D;AAED;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAgB,eAAe,CAC7B,UAAU,EAAE,MAAM,EAClB,UAAU,EAAE,MAAM,EAClB,QAAQ,SAAI,GACX,IAAI,CAuBN;AAED;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAgB,WAAW,CAAC,KAAK,EAAE,QAAQ,CAAC,UAAU,CAAC,GAAG,UAAU,EAAE,EAAE,CAsCvE"}
package/dist/limits.js ADDED
@@ -0,0 +1,177 @@
1
+ /**
2
+ * Upload size limits and batch planning.
3
+ *
4
+ * The API caps the **file content of a single request** at 45MB. Going over that
5
+ * is not a friendly failure: the check can only run after the whole body has been
6
+ * received, and the network layer in front of the API gives up on an oversized
7
+ * request before it ever gets there — so a client that sends too much sees the
8
+ * connection die (a write timeout on a slow uplink, a broken pipe on a fast one)
9
+ * with no error code and no explanation.
10
+ *
11
+ * The fix is to never send too much. These limits, the pre-flight checks, and
12
+ * `planBatches` let a client know a submission is too big *before* opening a
13
+ * connection, and split a large pile of files into submittable batches.
14
+ *
15
+ * There are three separate caps, and they fail in different ways: one file
16
+ * (`MAX_FILE_BYTES`), the file content of one request (`MAX_UPLOAD_BYTES`), and
17
+ * pages per job (`MAX_PAGES_PER_JOB`). `BATCH_TARGET_BYTES` is not a fourth cap — it
18
+ * is how much `planBatches` puts in one batch, deliberately under the real one.
19
+ */
20
+ import { InvalidFileError, TooManySlidesError } from "./errors.js";
21
+ /**
22
+ * Server cap on **one** file. A file over this is rejected with `INVALID_FILE`
23
+ * however it is submitted, so no amount of batching helps. Keep in sync with the
24
+ * documented API contract.
25
+ */
26
+ export const MAX_FILE_BYTES = 35 * 1024 * 1024;
27
+ /**
28
+ * Server cap on the **file content** of one request — boundaries, per-part headers
29
+ * and filenames do not count towards it. Over this, the request is rejected
30
+ * (413 `PAYLOAD_TOO_LARGE`) — or, further up, cut off outright before the server can
31
+ * answer at all. Keep in sync with the documented API contract.
32
+ */
33
+ export const MAX_UPLOAD_BYTES = 45 * 1024 * 1024;
34
+ /**
35
+ * Byte budget for one auto-planned batch. **A splitting budget, not a cap** — the cap
36
+ * is `MAX_UPLOAD_BYTES`, and `checkSubmission` compares against that exactly.
37
+ *
38
+ * Deliberately below the cap, and the asymmetry is the point: starting one more batch
39
+ * costs nothing, while refusing a submission the server would have accepted is a bug in
40
+ * the guard. Nothing here needs the headroom — the planner measures the exact bytes it
41
+ * will send — so this is margin, not correction. **Do not turn it into a cap.**
42
+ * `MAX_UPLOAD_BYTES` is file content only; the multipart framing around it does not
43
+ * count against the published limit, and a pre-flight that "leaves room" for framing
44
+ * just makes the documented maximum unreachable.
45
+ */
46
+ export const BATCH_TARGET_BYTES = 40 * 1024 * 1024;
47
+ /**
48
+ * Server cap on pages per job. An image is 1 page; a PDF counts as its own page
49
+ * count. Keep in sync with the documented API contract.
50
+ */
51
+ export const MAX_PAGES_PER_JOB = 50;
52
+ /** Format a byte count as MB, for human-readable error messages. */
53
+ export function formatBytes(size) {
54
+ // Stays honest below a megabyte. Rounding everything to MB makes a submission one
55
+ // byte over the cap read as "45.0MB, over the 45.0MB limit (0.0MB too much)" — a
56
+ // message that contradicts itself and looks like the check is broken.
57
+ if (size < 1024)
58
+ return `${size}B`;
59
+ if (size < 1024 * 1024)
60
+ return `${(size / 1024).toFixed(1)}KB`;
61
+ return `${(size / (1024 * 1024)).toFixed(1)}MB`;
62
+ }
63
+ /**
64
+ * Throw if one file is over the per-file cap, whatever else it travels with.
65
+ *
66
+ * Separate from `checkSubmission` because it is a property of the file, not of
67
+ * the request: a 40MB PDF fits under the 45MB request cap and would sail through
68
+ * batch planning, then be rejected by the server every single time. Fail on it
69
+ * locally instead of building a batch that can never succeed.
70
+ *
71
+ * @param path The file, named in the error so the caller knows which one.
72
+ * @param size Its size as it will be uploaded.
73
+ * @throws InvalidFileError Over `MAX_FILE_BYTES` (`code: "INVALID_FILE"`, the same
74
+ * code the server would answer with).
75
+ */
76
+ export function checkFileSize(path, size) {
77
+ if (size > MAX_FILE_BYTES) {
78
+ throw new InvalidFileError(`"${path}" is ${formatBytes(size)}, over the ${formatBytes(MAX_FILE_BYTES)} ` +
79
+ "per-file limit; the server rejects it however it is submitted, so " +
80
+ "splitting into batches will not help", { code: "INVALID_FILE" });
81
+ }
82
+ }
83
+ /**
84
+ * Throw if a submission cannot succeed, before any bytes go on the wire.
85
+ *
86
+ * **The page check is a lower bound, not the server's verdict.** An image is
87
+ * exactly 1 page, but a PDF is however many pages it holds and the SDK does not
88
+ * parse PDFs (zero dependencies), so each one can only be counted as *at least* 1.
89
+ * That is enough to catch the combinations that are certain to fail — 50 images
90
+ * plus any PDF is at least 51 pages, so it never had a chance — but a submission
91
+ * that passes here can still come back `TOO_MANY_SLIDES` from the server, because
92
+ * a 30-page PDF counted as 1 here and as 30 there. Passing this check means "not
93
+ * obviously doomed", not "will be accepted".
94
+ *
95
+ * @param totalBytes Sum of the file sizes that will be sent in this request.
96
+ * @param imagePages Number of image files. Each is exactly 1 page.
97
+ * @param pdfFiles Number of PDFs (or other files whose page count is unknown to
98
+ * the client). Each counts as at least 1 page.
99
+ * @throws TooManySlidesError The minimum page count already exceeds what one job
100
+ * can hold.
101
+ * @throws InvalidFileError File content over the per-request cap
102
+ * (`code = "PAYLOAD_TOO_LARGE"`).
103
+ */
104
+ export function checkSubmission(totalBytes, imagePages, pdfFiles = 0) {
105
+ const minPages = imagePages + pdfFiles;
106
+ if (minPages > MAX_PAGES_PER_JOB) {
107
+ const counted = pdfFiles
108
+ ? `${imagePages} images plus ${pdfFiles} ${pdfFiles === 1 ? "PDF" : "PDFs"} ` +
109
+ `(at least 1 page each) is at least ${minPages} pages`
110
+ : `${imagePages} images is ${minPages} pages`;
111
+ throw new TooManySlidesError(`${counted} in one submission, over the ${MAX_PAGES_PER_JOB}-page-per-job ` +
112
+ "limit; use submitAll() or convertAll() to split them into jobs automatically", { code: "TOO_MANY_SLIDES" });
113
+ }
114
+ if (totalBytes > MAX_UPLOAD_BYTES) {
115
+ throw new InvalidFileError(`these files add up to ${formatBytes(totalBytes)}, over the ` +
116
+ `${formatBytes(MAX_UPLOAD_BYTES)} of file content one request may carry ` +
117
+ `(${formatBytes(totalBytes - MAX_UPLOAD_BYTES)} too much). ` +
118
+ "Send fewer files per call, or use submitAll() / convertAll() to split them " +
119
+ "into batches automatically", { code: "PAYLOAD_TOO_LARGE" });
120
+ }
121
+ }
122
+ /**
123
+ * Split files into batches that each fit in one submission.
124
+ *
125
+ * Pure function: no file system, no network. Same input, same output.
126
+ *
127
+ * Rules:
128
+ * - a batch holds at most `BATCH_TARGET_BYTES` of file content;
129
+ * - a batch holds at most `MAX_PAGES_PER_JOB` images. That count is exact, not a
130
+ * lower bound like `checkSubmission`'s: a PDF always flushes the current batch
131
+ * and takes one of its own, so a batch being filled here only ever holds images,
132
+ * and an image is always exactly 1 page;
133
+ * - **every PDF gets a batch to itself.** The SDK does not parse PDFs (zero
134
+ * dependencies), so the client cannot know how many pages one holds. Mixed into
135
+ * a batch, an unknown page count could push the job over the page limit with no
136
+ * way to predict it. Alone, the job is exactly that one PDF and the server's own
137
+ * count decides;
138
+ * - input order is preserved, so the same files always plan the same way.
139
+ *
140
+ * @throws InvalidFileError A single file is over `MAX_FILE_BYTES`. No batching can
141
+ * help — the server rejects that file however it is submitted.
142
+ */
143
+ export function planBatches(items) {
144
+ const batches = [];
145
+ let current = [];
146
+ let currentBytes = 0;
147
+ const flush = () => {
148
+ if (current.length) {
149
+ batches.push(current);
150
+ current = [];
151
+ currentBytes = 0;
152
+ }
153
+ };
154
+ for (const item of items) {
155
+ // Stricter than the request cap and checked first: a file over the per-file
156
+ // limit is unsubmittable, not merely unbatchable.
157
+ checkFileSize(item.path, item.size);
158
+ if (item.isPdf) {
159
+ flush();
160
+ batches.push([item]);
161
+ continue;
162
+ }
163
+ // A file bigger than the batch target still gets uploaded — alone. The
164
+ // `current.length` guard means we only start a new batch to make room, never
165
+ // refuse to place an item.
166
+ if (current.length &&
167
+ (currentBytes + item.size > BATCH_TARGET_BYTES ||
168
+ current.length + 1 > MAX_PAGES_PER_JOB)) {
169
+ flush();
170
+ }
171
+ current.push(item);
172
+ currentBytes += item.size;
173
+ }
174
+ flush();
175
+ return batches;
176
+ }
177
+ //# sourceMappingURL=limits.js.map