@giveitsmaller/sdk 0.6.0 → 0.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (52) hide show
  1. package/dist/_audit.js +71 -0
  2. package/dist/builder.d.ts +406 -0
  3. package/dist/builder.js +706 -0
  4. package/dist/client.d.ts +10 -0
  5. package/dist/client.js +42 -6
  6. package/dist/credentials.d.ts +61 -0
  7. package/dist/credentials.js +200 -0
  8. package/dist/ergonomic/preset_resolver.d.ts +75 -0
  9. package/dist/ergonomic/preset_resolver.js +568 -0
  10. package/dist/ergonomic/presets/_translate.d.ts +11 -0
  11. package/dist/ergonomic/presets/_translate.js +35 -0
  12. package/dist/ergonomic/presets/audio_compress.d.ts +16 -0
  13. package/dist/ergonomic/presets/audio_compress.js +45 -0
  14. package/dist/ergonomic/presets/document_epub_compress.d.ts +14 -0
  15. package/dist/ergonomic/presets/document_epub_compress.js +34 -0
  16. package/dist/ergonomic/presets/document_odf_compress.d.ts +14 -0
  17. package/dist/ergonomic/presets/document_odf_compress.js +34 -0
  18. package/dist/ergonomic/presets/document_office_compress.d.ts +16 -0
  19. package/dist/ergonomic/presets/document_office_compress.js +40 -0
  20. package/dist/ergonomic/presets/document_pdf_compress.d.ts +14 -0
  21. package/dist/ergonomic/presets/document_pdf_compress.js +35 -0
  22. package/dist/ergonomic/presets/image_compress.d.ts +43 -0
  23. package/dist/ergonomic/presets/image_compress.js +95 -0
  24. package/dist/ergonomic/presets/index.d.ts +77 -0
  25. package/dist/ergonomic/presets/index.js +216 -0
  26. package/dist/ergonomic/presets/video_compress.d.ts +30 -0
  27. package/dist/ergonomic/presets/video_compress.js +83 -0
  28. package/dist/errors.d.ts +196 -1
  29. package/dist/errors.js +216 -0
  30. package/dist/file-first.d.ts +284 -0
  31. package/dist/file-first.js +445 -0
  32. package/dist/generated/sdk_spec/enums.d.ts +195 -0
  33. package/dist/generated/sdk_spec/enums.js +127 -0
  34. package/dist/generated/sdk_spec/errors.d.ts +16 -0
  35. package/dist/generated/sdk_spec/errors.js +523 -0
  36. package/dist/generated/sdk_spec/index.d.ts +4 -0
  37. package/dist/generated/sdk_spec/index.js +7 -0
  38. package/dist/generated/sdk_spec/presets.d.ts +6 -0
  39. package/dist/generated/sdk_spec/presets.js +157 -0
  40. package/dist/generated/sdk_spec/version.d.ts +3 -0
  41. package/dist/generated/sdk_spec/version.js +6 -0
  42. package/dist/gisl.d.ts +122 -0
  43. package/dist/gisl.js +283 -0
  44. package/dist/http-downloader.d.ts +9 -0
  45. package/dist/http-downloader.js +55 -0
  46. package/dist/index.d.ts +20 -5
  47. package/dist/index.js +45 -4
  48. package/dist/merge.d.ts +142 -0
  49. package/dist/merge.js +411 -0
  50. package/dist/types.d.ts +12 -14
  51. package/dist/types.js +18 -0
  52. package/package.json +3 -3
@@ -0,0 +1,445 @@
1
+ /**
2
+ * File-first result surface — the value the file-first layer's `run()` /
3
+ * `Handle.wait()` / `Handle.result()` return (producers land in FF2b/FF5).
4
+ *
5
+ * Coexists with the operation-first `Result`/`Artifact` (in `builder.ts`)
6
+ * until the operation-first layer is removed (FF6). The file-first shape is
7
+ * flatter and adds an always-present per-input partition (`succeeded` /
8
+ * `failed`) so one bad input in a multi-input run doesn't sink the rest.
9
+ *
10
+ * Mirrors `packages/php/src/FileFirst/*`.
11
+ */
12
+ import { GislApiError, GislConfigError, GislNoSuchKeyError, GislSinkError, GislTimeoutError } from './errors.js';
13
+ import { _detectCompressMedia, _consumeSseToTerminal, _pollToTerminal, _parseMaxWait, _checkAborted, } from './builder.js';
14
+ import { HttpDownloader } from './http-downloader.js';
15
+ import { resolveCompressOptions, } from './ergonomic/preset_resolver.js';
16
+ import { OptimizeFor } from './generated/sdk_spec/enums.js';
17
+ import { uploadSource } from './types.js';
18
+ /**
19
+ * Result of a file-first run. Coexists with the operation-first `Result`
20
+ * (in `builder.ts`) until FF6.
21
+ *
22
+ * Mirrors the PHP `RunResult` class. A class (not a bare interface) because
23
+ * it carries the `byKey()`/`toFile()`/`downloadTo()` behaviour; the data
24
+ * fields stay public + readonly so `toArray()` round-trips.
25
+ *
26
+ * Field notes:
27
+ * - `url`: single-output sugar — the lone artifact's URL when exactly one
28
+ * output exists, else undefined.
29
+ * - `ok`: true iff `failed` is empty. (A boolean — the partition lists are
30
+ * `succeeded`/`failed`; resolves the design doc's `ok` bool-vs-list
31
+ * contradiction.)
32
+ * - `state`: lifecycle state (`completed` | `failed` | ...). Named `state`,
33
+ * NOT `status`, matching the file-first `StatusSnapshot.state`.
34
+ * - sinks fetch via the injected {@link Downloader}; a result with no
35
+ * downloader throws {@link GislSinkError} (reason `downloader_unavailable`).
36
+ */
37
+ export class RunResult {
38
+ workflowId;
39
+ state;
40
+ artifacts;
41
+ succeeded;
42
+ failed;
43
+ downloader;
44
+ /** Single-output sugar: the lone artifact's URL, or undefined for 0 / >1. */
45
+ url;
46
+ /** True iff {@link failed} is empty. */
47
+ ok;
48
+ constructor(workflowId, state, artifacts, succeeded, failed, downloader) {
49
+ this.workflowId = workflowId;
50
+ this.state = state;
51
+ this.artifacts = artifacts;
52
+ this.succeeded = succeeded;
53
+ this.failed = failed;
54
+ this.downloader = downloader;
55
+ this.url = artifacts.length === 1 ? artifacts[0].url : undefined;
56
+ this.ok = failed.length === 0;
57
+ }
58
+ /**
59
+ * Address a succeeded input by the `key:` given to `file()`. Duplicate keys
60
+ * are not valid input — the producer enforces key uniqueness (a later
61
+ * ticket); the first match is returned.
62
+ * @throws {GislNoSuchKeyError} when no succeeded entry has that key (a
63
+ * keyless run always throws — it is positionally addressable only).
64
+ */
65
+ byKey(key) {
66
+ const item = this.succeeded.find((i) => i.key === key);
67
+ if (item === undefined) {
68
+ throw new GislNoSuchKeyError(`No result for key '${key}'.`);
69
+ }
70
+ return item;
71
+ }
72
+ /**
73
+ * Write the single output to `path`. Requires EXACTLY ONE artifact.
74
+ * @throws {GislSinkError} reason `not_single_output` for 0/>1 outputs;
75
+ * reason `downloader_unavailable` when no downloader is bound.
76
+ */
77
+ async toFile(path) {
78
+ if (this.artifacts.length !== 1) {
79
+ throw new GislSinkError(`toFile() requires exactly one output; this run produced ${this.artifacts.length}. ` +
80
+ 'Use downloadTo() for multi-output runs.', { reason: 'not_single_output' });
81
+ }
82
+ await this.requireDownloader().downloadTo(this.artifacts[0].url, path);
83
+ }
84
+ /**
85
+ * Download every output into `dir` (filename per output), in output order.
86
+ * Returns the {@link Manifest} of local paths written.
87
+ * @throws {GislSinkError} reason `partial_failure` when `failOnPartial` and
88
+ * the run had failed inputs; reason `downloader_unavailable` when no
89
+ * downloader is bound.
90
+ */
91
+ async downloadTo(dir, options) {
92
+ if (options?.failOnPartial && this.failed.length > 0) {
93
+ throw new GislSinkError(`downloadTo({ failOnPartial: true }) but the run had ${this.failed.length} failed input(s).`, { reason: 'partial_failure' });
94
+ }
95
+ if (dir === '') {
96
+ throw new GislSinkError("downloadTo(): the directory argument is empty. Pass a target directory (use '.' for the current directory).", { reason: 'invalid_directory' });
97
+ }
98
+ const downloader = this.requireDownloader();
99
+ const sep = dir.endsWith('/') || dir.endsWith('\\') ? '' : '/';
100
+ // Resolve destinations first so a basename collision fails loudly BEFORE any
101
+ // byte is written — silently overwriting an earlier output is data loss.
102
+ const names = this.artifacts.map(
103
+ // Strip any directory component from a server-supplied filename so a value
104
+ // like "../x" or "a/b" cannot escape `dir` (mirrors PHP basename()).
105
+ (a) => a.filename.split(/[/\\]/).pop() ?? a.filename);
106
+ // Collision key is case-folded: many destination filesystems (macOS, NTFS)
107
+ // are case-insensitive, so `a.jpg` and `A.jpg` would target the same file.
108
+ const seen = new Set();
109
+ for (const name of names) {
110
+ const key = name.toLowerCase();
111
+ if (seen.has(key)) {
112
+ throw new GislSinkError(`downloadTo(): two outputs resolve to the same filename '${name}' in '${dir}' ` +
113
+ '(case-insensitively). Download them to separate directories.', { reason: 'duplicate_filename' });
114
+ }
115
+ seen.add(key);
116
+ }
117
+ const paths = [];
118
+ for (let i = 0; i < this.artifacts.length; i++) {
119
+ const dest = `${dir}${sep}${names[i]}`;
120
+ await downloader.downloadTo(this.artifacts[i].url, dest);
121
+ paths.push(dest);
122
+ }
123
+ return { paths };
124
+ }
125
+ /**
126
+ * Plain-object projection. Field ORDER (workflowId, state, ok, url?,
127
+ * artifacts, succeeded, failed) is fixed to match the PHP `toArray()`
128
+ * reference so JSON-string parity holds (FF1 shape assertion + FF2b harness
129
+ * fixture). `url` is omitted entirely when undefined — `JSON.stringify`
130
+ * then produces the identical shape to PHP's omit-when-null `toArray()`.
131
+ */
132
+ toJSON() {
133
+ // Re-project each OutputFile to exactly its four fields so structurally
134
+ // compatible inputs carrying extra properties can't leak into the JSON.
135
+ const file = (o) => ({
136
+ url: o.url,
137
+ filename: o.filename,
138
+ sizeBytes: o.sizeBytes,
139
+ operation: o.operation,
140
+ });
141
+ const rest = {
142
+ artifacts: this.artifacts.map(file),
143
+ succeeded: this.succeeded.map((i) => ({ key: i.key, outputs: i.outputs.map(file) })),
144
+ failed: this.failed.map((f) => ({
145
+ key: f.key,
146
+ error: f.error instanceof Error ? f.error.message : String(f.error),
147
+ })),
148
+ };
149
+ const head = { workflowId: this.workflowId, state: this.state, ok: this.ok };
150
+ // Insert `url` BETWEEN ok and artifacts when present, matching the PHP
151
+ // toArray() field order (workflowId, state, ok, url?, artifacts, ...) so
152
+ // JSON-string parity holds. Omitted entirely when undefined (PHP omits
153
+ // null), so `JSON.stringify` produces the identical shape.
154
+ return this.url === undefined
155
+ ? { ...head, ...rest }
156
+ : { ...head, url: this.url, ...rest };
157
+ }
158
+ requireDownloader() {
159
+ if (this.downloader === undefined) {
160
+ throw new GislSinkError('This result has no downloader bound, so its outputs cannot be written to disk here ' +
161
+ '(e.g. a browser / no-I/O context). Fetch each output from its URL instead.', { reason: 'downloader_unavailable' });
162
+ }
163
+ return this.downloader;
164
+ }
165
+ }
166
+ /** Named constructors for {@link FileInput} — mirror the PHP static factories. */
167
+ export const fileInput = {
168
+ path(path) {
169
+ return { kind: 'path', path };
170
+ },
171
+ blob(blob) {
172
+ return { kind: 'blob', blob };
173
+ },
174
+ uploadId(fileId) {
175
+ return { kind: 'uploadId', fileId };
176
+ },
177
+ };
178
+ /**
179
+ * The file-first builder value. `client.file(path)` returns a `Recipe`;
180
+ * single-input operations called on it (`compress`, `convert`, `thumbnail`,
181
+ * `textWatermark`) chain SEQUENTIALLY — each op feeds the next, and the chain
182
+ * lowers to ONE workflow job with an ordered `operations[]` (per ADR-0004:
183
+ * operations execute sequentially, each consuming the previous output). A
184
+ * chain yields the TERMINAL output only; intermediates are consumed (surfaced
185
+ * by FF2b's `run()`/{@link RunResult}).
186
+ *
187
+ * **Immutable / clone-on-write.** Every op returns a NEW `Recipe` carrying the
188
+ * appended step — `this` is never mutated. A Recipe is therefore a reusable
189
+ * value: branching the same base recipe two different ways cannot let one
190
+ * branch observe the other's steps (the aliasing trap mutable builders fall
191
+ * into).
192
+ *
193
+ * FF2a is network-free: there is NO `run()` here (that is FF2b). The lowering
194
+ * seam {@link toWorkflowPayload} takes the resolved upload id as a parameter
195
+ * so it stays pure — FF2b's `run()` calls the SAME method after uploading, and
196
+ * the parity harness calls it with a fixed id to assert the lowered shape.
197
+ *
198
+ * Mirrors the PHP `Recipe`.
199
+ */
200
+ export class Recipe {
201
+ input;
202
+ recipeKey;
203
+ steps;
204
+ presetDefaults;
205
+ scopedPresetDefaults;
206
+ client;
207
+ constructor(input, recipeKey = undefined, steps = [], presetDefaults, scopedPresetDefaults, client) {
208
+ this.input = input;
209
+ this.recipeKey = recipeKey;
210
+ this.steps = steps;
211
+ this.presetDefaults = presetDefaults;
212
+ this.scopedPresetDefaults = scopedPresetDefaults;
213
+ this.client = client;
214
+ }
215
+ /**
216
+ * Reduce file size. `optimize` selects a per-media preset (resolved to
217
+ * concrete wire fields at lower-time, exactly as `client.compress()` does).
218
+ */
219
+ compress(optimize) {
220
+ if (optimize !== undefined && !Object.values(OptimizeFor).includes(optimize)) {
221
+ const allowed = Object.values(OptimizeFor).join(', ');
222
+ throw new GislConfigError(`compress 'optimize' must be one of ${allowed}; got '${String(optimize)}'.`, { reason: 'invalid_optimize', conflictingFields: ['optimize'] });
223
+ }
224
+ return this.withStep({ opType: 'compress', options: optimize === undefined ? {} : { optimize } });
225
+ }
226
+ /** Change format. `format` is lowered verbatim to the `format` wire option. */
227
+ convert(format) {
228
+ return this.withStep({ opType: 'convert', options: { format } });
229
+ }
230
+ /**
231
+ * Generate a preview. Width and/or height in pixels; an omitted dimension is
232
+ * dropped from the wire options (not sent as `undefined`).
233
+ */
234
+ thumbnail(options = {}) {
235
+ const wire = {};
236
+ if (options.width !== undefined)
237
+ wire.width = options.width;
238
+ if (options.height !== undefined)
239
+ wire.height = options.height;
240
+ return this.withStep({ opType: 'thumbnail', options: wire });
241
+ }
242
+ /**
243
+ * Apply a text watermark. Single-input (the text is an option, not a
244
+ * secondary file) — lowers to the `text_watermark` op with a `text` option.
245
+ */
246
+ textWatermark(text) {
247
+ return this.withStep({ opType: 'text_watermark', options: { text } });
248
+ }
249
+ /**
250
+ * Lower this recipe to a workflow-create payload against a resolved upload
251
+ * id. Single-input chain → ONE job, `source: upload(fileId)`, ordered
252
+ * `operations[]`; the job `id` is omitted (a single job referenced by
253
+ * nothing — the server auto-assigns `job_N`).
254
+ *
255
+ * @internal Consumed by FF2b's `run()` (after a real upload) and by the
256
+ * cross-language parity harness (with a fixed id). Not part of the
257
+ * caller-facing fluent surface.
258
+ */
259
+ toWorkflowPayload(fileId) {
260
+ const operations = this.steps.map((step) => this.lowerStep(step));
261
+ // Key order (source, operations) matches the PHP `toWire()` so the
262
+ // JSON-string serialisation is byte-identical across languages.
263
+ const job = { source: uploadSource(fileId), operations };
264
+ return { jobs: [job] };
265
+ }
266
+ /** The result-addressing key passed to `file()`, or undefined. */
267
+ key() {
268
+ return this.recipeKey;
269
+ }
270
+ /** The number of operations chained so far (introspection / tests). */
271
+ get stepCount() {
272
+ return this.steps.length;
273
+ }
274
+ /**
275
+ * Execute the recipe end-to-end: upload the input (when required), create
276
+ * the workflow, await a terminal state (SSE with poll fallback), then
277
+ * resolve the produced downloads into a flat {@link RunResult}. Throws
278
+ * {@link GislTimeoutError} if `maxWait` elapses before terminal status.
279
+ *
280
+ * Mirrors the operation-first `OperationBuilder.run` (in `builder.ts`).
281
+ * Requires a client bound at construction time — `gisl().file(...)` wires
282
+ * it; a directly-constructed `Recipe` (e.g. in a lowering-only test) has no
283
+ * client and throws {@link GislConfigError}.
284
+ */
285
+ async run(options = {}) {
286
+ const signal = options.signal;
287
+ const onProgress = options.onProgress;
288
+ if (this.client === undefined) {
289
+ throw new GislConfigError('Recipe.run() requires a client; build the recipe via gisl().file(...) rather than constructing Recipe directly.', { reason: 'no_client' });
290
+ }
291
+ const deadline = Date.now() + _parseMaxWait(options.maxWait ?? 300_000);
292
+ // 1. Resolve the upload id. A pre-uploaded id skips the upload entirely;
293
+ // a path / blob is uploaded now, emitting {phase:'upload'} progress.
294
+ let fileId;
295
+ if (this.input.kind === 'uploadId') {
296
+ fileId = this.input.fileId;
297
+ }
298
+ else {
299
+ const source = this.input.kind === 'path' ? this.input.path : this.input.blob;
300
+ const up = await this.client.uploadFile(source, {
301
+ signal,
302
+ ...(onProgress !== undefined
303
+ ? {
304
+ onProgress: (uploadedBytes, totalBytes) => {
305
+ onProgress({ phase: 'upload', uploadedBytes, totalBytes });
306
+ },
307
+ }
308
+ : {}),
309
+ });
310
+ fileId = up.fileId;
311
+ }
312
+ _checkAborted(signal);
313
+ if (Date.now() >= deadline) {
314
+ throw new GislTimeoutError('Upload completed but maxWait elapsed before workflow could be created');
315
+ }
316
+ // 2. Create the workflow from the lowered payload.
317
+ const payload = this.toWorkflowPayload(fileId);
318
+ const created = await this.client.createWorkflow(payload);
319
+ _checkAborted(signal);
320
+ // 3. Wait to terminal status — SSE first, poll on a genuine SSE error.
321
+ // Caller-aborted + deadline-elapsed errors MUST propagate (not transient).
322
+ let finalStatus;
323
+ try {
324
+ finalStatus = await _consumeSseToTerminal(this.client, {
325
+ workflowId: created.workflowId,
326
+ deadline,
327
+ signal,
328
+ onProgress,
329
+ });
330
+ }
331
+ catch (err) {
332
+ // Only genuine SSE transport / clean-stream-end failures fall through to
333
+ // poll. Caller-deadline, abort, and API errors (a 401/402/etc. from
334
+ // /events, or an onProgress callback throw surfacing as GislApiError)
335
+ // MUST propagate — re-issuing the same doomed request via poll would mask
336
+ // them. Mirrors the PHP BuilderInternals::awaitTerminal sealed-marker
337
+ // discipline (codex review medium).
338
+ if (err instanceof GislTimeoutError)
339
+ throw err;
340
+ if (err instanceof DOMException && err.name === 'AbortError')
341
+ throw err;
342
+ if (err instanceof GislApiError)
343
+ throw err;
344
+ finalStatus = await _pollToTerminal(this.client, {
345
+ workflowId: created.workflowId,
346
+ deadline,
347
+ signal,
348
+ pollIntervalMs: options.pollIntervalMs,
349
+ });
350
+ }
351
+ // 4. Fetch downloads. The maxWait deadline covers upload + create + wait +
352
+ // downloads, so check before issuing the request (mirrors builder.ts).
353
+ if (Date.now() >= deadline) {
354
+ throw new GislTimeoutError(`Workflow ${created.workflowId} reached terminal status but maxWait elapsed before downloads could be fetched`);
355
+ }
356
+ const downloads = await this.client.getWorkflowDownloads(created.workflowId);
357
+ // Flatten to the lean OutputFile[] (the four file-first fields only).
358
+ const artifacts = [];
359
+ for (const job of downloads.downloads) {
360
+ for (const f of job.files) {
361
+ artifacts.push({
362
+ url: f.downloadUrl,
363
+ filename: f.filename,
364
+ sizeBytes: f.sizeBytes,
365
+ operation: f.operation,
366
+ });
367
+ }
368
+ }
369
+ // Partition the single input into succeeded / failed by terminal state.
370
+ // Success is ONLY `completed`: every other terminal state — `failed`,
371
+ // `partially_failed`, `cancelled`, `expired`,
372
+ // `paused_insufficient_credits` — is a non-success and partitions into
373
+ // `failed` so a caller's `ok`/`succeeded` check can never treat a
374
+ // cancelled/expired/paused run as a clean result (codex review high).
375
+ const state = finalStatus.status;
376
+ const key = this.recipeKey ?? null;
377
+ let succeeded;
378
+ let failed;
379
+ if (state === 'completed') {
380
+ succeeded = [{ key, outputs: artifacts }];
381
+ failed = [];
382
+ }
383
+ else {
384
+ const firstError = (finalStatus.jobs ?? [])
385
+ .flatMap((j) => j.operations ?? [])
386
+ .map((op) => op.errorMessage)
387
+ .find((m) => m !== undefined);
388
+ succeeded = [];
389
+ failed = [
390
+ { key, error: new Error(firstError !== undefined ? `${state}: ${firstError}` : state) },
391
+ ];
392
+ }
393
+ // Download URLs from getWorkflowDownloads are pre-signed and require no SDK
394
+ // auth, so the downloader issues a plain unauthenticated fetch.
395
+ const downloader = new HttpDownloader();
396
+ return new RunResult(created.workflowId, state, artifacts, succeeded, failed, downloader);
397
+ }
398
+ withStep(step) {
399
+ return new Recipe(this.input, this.recipeKey, [...this.steps, step], this.presetDefaults, this.scopedPresetDefaults, this.client);
400
+ }
401
+ lowerStep(step) {
402
+ const options = step.opType === 'compress' ? this.lowerCompressOptions(step.options) : { ...step.options };
403
+ // Empty options omit the `options` wire key entirely, so TS (undefined →
404
+ // absent) and PHP (null → absent) serialise byte-identically.
405
+ return Object.keys(options).length === 0
406
+ ? { type: step.opType }
407
+ : { type: step.opType, options };
408
+ }
409
+ lowerCompressOptions(options) {
410
+ const optimize = options.optimize;
411
+ const media = this.compressMediaHint();
412
+ if (media === undefined) {
413
+ // Cannot infer a media class (a Blob without a recognised name, or a
414
+ // bare upload id) → preset resolution is impossible. Fail FAST rather
415
+ // than silently dropping an explicit `optimize`; bare compress() is fine.
416
+ if (optimize !== undefined) {
417
+ throw new GislConfigError(`compress(optimize: ${String(optimize)}) needs a media type to resolve the preset, but the ` +
418
+ 'input has no inferable media (a pre-uploaded file id or unnamed Blob carries no extension). ' +
419
+ 'Use a path with a file extension, or call compress() without optimize.', { reason: 'media_unknown', conflictingFields: ['optimize'] });
420
+ }
421
+ return {};
422
+ }
423
+ const input = { media, op: 'compress', explicitOptions: {} };
424
+ if (this.presetDefaults !== undefined) {
425
+ input.presetDefaults = this.presetDefaults;
426
+ }
427
+ if (this.scopedPresetDefaults !== undefined) {
428
+ input.scopedPresetDefaults =
429
+ this.scopedPresetDefaults;
430
+ }
431
+ if (optimize !== undefined) {
432
+ input.optimize = optimize;
433
+ }
434
+ return { ...resolveCompressOptions(input).wireOptions };
435
+ }
436
+ compressMediaHint() {
437
+ if (this.input.kind === 'path') {
438
+ return _detectCompressMedia(this.input.path);
439
+ }
440
+ if (this.input.kind === 'blob') {
441
+ return _detectCompressMedia(this.input.blob);
442
+ }
443
+ return undefined;
444
+ }
445
+ }
@@ -0,0 +1,195 @@
1
+ export declare const OptimizeFor: {
2
+ readonly Size: "Size";
3
+ readonly Balanced: "Balanced";
4
+ readonly Quality: "Quality";
5
+ };
6
+ export type OptimizeFor = typeof OptimizeFor[keyof typeof OptimizeFor];
7
+ export declare const ImageMode: {
8
+ readonly Lossy: "lossy";
9
+ readonly Lossless: "lossless";
10
+ readonly Auto: "auto";
11
+ };
12
+ export type ImageMode = typeof ImageMode[keyof typeof ImageMode];
13
+ export declare const ImageFormat: {
14
+ readonly Original: "original";
15
+ readonly Auto: "auto";
16
+ readonly Smallest: "smallest";
17
+ readonly Jpeg: "jpeg";
18
+ readonly Png: "png";
19
+ readonly Webp: "webp";
20
+ readonly Avif: "avif";
21
+ };
22
+ export type ImageFormat = typeof ImageFormat[keyof typeof ImageFormat];
23
+ export declare const ImageFit: {
24
+ readonly Max: "max";
25
+ readonly Crop: "crop";
26
+ readonly Scale: "scale";
27
+ };
28
+ export type ImageFit = typeof ImageFit[keyof typeof ImageFit];
29
+ export declare const ImageMetadataPolicy: {
30
+ readonly All: "all";
31
+ readonly None: "none";
32
+ readonly Copyright: "copyright";
33
+ readonly Sensitive: "sensitive";
34
+ };
35
+ export type ImageMetadataPolicy = typeof ImageMetadataPolicy[keyof typeof ImageMetadataPolicy];
36
+ export declare const IccProfilePolicy: {
37
+ readonly Preserve: "preserve";
38
+ readonly Strip: "strip";
39
+ readonly Srgb: "srgb";
40
+ };
41
+ export type IccProfilePolicy = typeof IccProfilePolicy[keyof typeof IccProfilePolicy];
42
+ export declare const VideoCodec: {
43
+ readonly H264: "h264";
44
+ readonly H265: "h265";
45
+ readonly Vp9: "vp9";
46
+ readonly Av1: "av1";
47
+ };
48
+ export type VideoCodec = typeof VideoCodec[keyof typeof VideoCodec];
49
+ export declare const VideoPreset: {
50
+ readonly Ultrafast: "ultrafast";
51
+ readonly Superfast: "superfast";
52
+ readonly Veryfast: "veryfast";
53
+ readonly Faster: "faster";
54
+ readonly Fast: "fast";
55
+ readonly Medium: "medium";
56
+ readonly Slow: "slow";
57
+ readonly Slower: "slower";
58
+ readonly Veryslow: "veryslow";
59
+ };
60
+ export type VideoPreset = typeof VideoPreset[keyof typeof VideoPreset];
61
+ export declare const VideoFit: {
62
+ readonly Max: "max";
63
+ readonly Crop: "crop";
64
+ readonly Scale: "scale";
65
+ readonly Pad: "pad";
66
+ };
67
+ export type VideoFit = typeof VideoFit[keyof typeof VideoFit];
68
+ export declare const AudioBitrate: {
69
+ readonly _64: 64;
70
+ readonly _96: 96;
71
+ readonly _128: 128;
72
+ readonly _192: 192;
73
+ readonly _256: 256;
74
+ readonly _320: 320;
75
+ };
76
+ export type AudioBitrate = typeof AudioBitrate[keyof typeof AudioBitrate];
77
+ export declare const AudioCodec: {
78
+ readonly Aac: "aac";
79
+ readonly Opus: "opus";
80
+ readonly Vorbis: "vorbis";
81
+ readonly Copy: "copy";
82
+ };
83
+ export type AudioCodec = typeof AudioCodec[keyof typeof AudioCodec];
84
+ export declare const AudioSampleRate: {
85
+ readonly _22050: 22050;
86
+ readonly _44100: 44100;
87
+ readonly _48000: 48000;
88
+ };
89
+ export type AudioSampleRate = typeof AudioSampleRate[keyof typeof AudioSampleRate];
90
+ export declare const PdfProfile: {
91
+ readonly Web: "web";
92
+ readonly Print: "print";
93
+ readonly Archive: "archive";
94
+ readonly Max: "max";
95
+ };
96
+ export type PdfProfile = typeof PdfProfile[keyof typeof PdfProfile];
97
+ export declare const PdfColorspace: {
98
+ readonly Unchanged: "unchanged";
99
+ readonly Rgb: "rgb";
100
+ readonly Cmyk: "cmyk";
101
+ readonly Grayscale: "grayscale";
102
+ };
103
+ export type PdfColorspace = typeof PdfColorspace[keyof typeof PdfColorspace];
104
+ /** Catalog of every ergonomic enum (canonicalName → wire). */
105
+ export declare const ERGONOMIC_ENUMS: {
106
+ readonly OptimizeFor: {
107
+ readonly Size: "Size";
108
+ readonly Balanced: "Balanced";
109
+ readonly Quality: "Quality";
110
+ };
111
+ readonly ImageMode: {
112
+ readonly Lossy: "lossy";
113
+ readonly Lossless: "lossless";
114
+ readonly Auto: "auto";
115
+ };
116
+ readonly ImageFormat: {
117
+ readonly Original: "original";
118
+ readonly Auto: "auto";
119
+ readonly Smallest: "smallest";
120
+ readonly Jpeg: "jpeg";
121
+ readonly Png: "png";
122
+ readonly Webp: "webp";
123
+ readonly Avif: "avif";
124
+ };
125
+ readonly ImageFit: {
126
+ readonly Max: "max";
127
+ readonly Crop: "crop";
128
+ readonly Scale: "scale";
129
+ };
130
+ readonly ImageMetadataPolicy: {
131
+ readonly All: "all";
132
+ readonly None: "none";
133
+ readonly Copyright: "copyright";
134
+ readonly Sensitive: "sensitive";
135
+ };
136
+ readonly IccProfilePolicy: {
137
+ readonly Preserve: "preserve";
138
+ readonly Strip: "strip";
139
+ readonly Srgb: "srgb";
140
+ };
141
+ readonly VideoCodec: {
142
+ readonly H264: "h264";
143
+ readonly H265: "h265";
144
+ readonly Vp9: "vp9";
145
+ readonly Av1: "av1";
146
+ };
147
+ readonly VideoPreset: {
148
+ readonly Ultrafast: "ultrafast";
149
+ readonly Superfast: "superfast";
150
+ readonly Veryfast: "veryfast";
151
+ readonly Faster: "faster";
152
+ readonly Fast: "fast";
153
+ readonly Medium: "medium";
154
+ readonly Slow: "slow";
155
+ readonly Slower: "slower";
156
+ readonly Veryslow: "veryslow";
157
+ };
158
+ readonly VideoFit: {
159
+ readonly Max: "max";
160
+ readonly Crop: "crop";
161
+ readonly Scale: "scale";
162
+ readonly Pad: "pad";
163
+ };
164
+ readonly AudioBitrate: {
165
+ readonly _64: 64;
166
+ readonly _96: 96;
167
+ readonly _128: 128;
168
+ readonly _192: 192;
169
+ readonly _256: 256;
170
+ readonly _320: 320;
171
+ };
172
+ readonly AudioCodec: {
173
+ readonly Aac: "aac";
174
+ readonly Opus: "opus";
175
+ readonly Vorbis: "vorbis";
176
+ readonly Copy: "copy";
177
+ };
178
+ readonly AudioSampleRate: {
179
+ readonly _22050: 22050;
180
+ readonly _44100: 44100;
181
+ readonly _48000: 48000;
182
+ };
183
+ readonly PdfProfile: {
184
+ readonly Web: "web";
185
+ readonly Print: "print";
186
+ readonly Archive: "archive";
187
+ readonly Max: "max";
188
+ };
189
+ readonly PdfColorspace: {
190
+ readonly Unchanged: "unchanged";
191
+ readonly Rgb: "rgb";
192
+ readonly Cmyk: "cmyk";
193
+ readonly Grayscale: "grayscale";
194
+ };
195
+ };