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 +21 -0
- package/README.md +269 -0
- package/dist/client.d.ts +154 -0
- package/dist/client.d.ts.map +1 -0
- package/dist/client.js +1134 -0
- package/dist/client.js.map +1 -0
- package/dist/compress.d.ts +24 -0
- package/dist/compress.d.ts.map +1 -0
- package/dist/compress.js +83 -0
- package/dist/compress.js.map +1 -0
- package/dist/errors.d.ts +235 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +293 -0
- package/dist/errors.js.map +1 -0
- package/dist/index.d.ts +24 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +21 -0
- package/dist/index.js.map +1 -0
- package/dist/limits.d.ts +125 -0
- package/dist/limits.d.ts.map +1 -0
- package/dist/limits.js +177 -0
- package/dist/limits.js.map +1 -0
- package/dist/types.d.ts +229 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +256 -0
- package/dist/types.js.map +1 -0
- package/dist/version.d.ts +11 -0
- package/dist/version.d.ts.map +1 -0
- package/dist/version.js +11 -0
- package/dist/version.js.map +1 -0
- package/package.json +59 -0
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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"}
|
package/dist/index.d.ts
ADDED
|
@@ -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"}
|
package/dist/limits.d.ts
ADDED
|
@@ -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
|