@giveitsmaller/sdk 0.9.0 → 0.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +8 -0
- package/dist/_audit.js +0 -1
- package/dist/builder.js +65 -13
- package/dist/client.d.ts +38 -2
- package/dist/client.js +131 -7
- package/dist/credentials.js +4 -2
- package/dist/ergonomic/preset_resolver.js +4 -5
- package/dist/ergonomic/presets/image_compress.d.ts +1 -9
- package/dist/ergonomic/presets/image_compress.js +6 -25
- package/dist/ergonomic/presets/index.d.ts +1 -1
- package/dist/ergonomic/presets/index.js +1 -1
- package/dist/errors.d.ts +59 -1
- package/dist/errors.js +51 -0
- package/dist/file-first.d.ts +233 -0
- package/dist/file-first.js +585 -21
- package/dist/generated/sdk_spec/enums.d.ts +0 -11
- package/dist/generated/sdk_spec/enums.js +0 -7
- package/dist/generated/sdk_spec/errors.d.ts +1 -1
- package/dist/generated/sdk_spec/errors.js +26 -0
- package/dist/generated/sdk_spec/presets.js +0 -3
- package/dist/generated/sdk_spec/version.d.ts +2 -2
- package/dist/generated/sdk_spec/version.js +2 -2
- package/dist/handle.js +34 -14
- package/dist/index.browser.d.ts +1 -0
- package/dist/index.browser.js +14 -0
- package/dist/index.core.d.ts +35 -0
- package/dist/index.core.js +102 -0
- package/dist/index.d.ts +1 -32
- package/dist/index.js +9 -89
- package/dist/lazy-downloader.d.ts +19 -0
- package/dist/lazy-downloader.js +19 -0
- package/dist/merge.d.ts +11 -0
- package/dist/merge.js +144 -45
- package/dist/node-fs.browser.d.ts +17 -0
- package/dist/node-fs.browser.js +7 -0
- package/dist/node-fs.d.ts +14 -0
- package/dist/node-fs.js +14 -0
- package/dist/sha256.d.ts +20 -0
- package/dist/sha256.js +108 -0
- package/dist/types.d.ts +54 -2
- package/dist/types.js +2 -0
- package/package.json +15 -2
package/dist/merge.js
CHANGED
|
@@ -26,7 +26,7 @@
|
|
|
26
26
|
* assets both fail fast so the caller saves bandwidth on typo'd composes.
|
|
27
27
|
*/
|
|
28
28
|
import { uploadSource, jobOutputSource } from './types.js';
|
|
29
|
-
import { GislConfigError, GislPerInputOptionsNotSupportedError, GislTimeoutError, GislUndeclaredAssetError, GislUnusedAssetError, } from './errors.js';
|
|
29
|
+
import { GislConfigError, GislNetworkError, GislPerInputOptionsNotSupportedError, GislTimeoutError, GislUndeclaredAssetError, GislUnusedAssetError, SseEndedWithoutTerminal, } from './errors.js';
|
|
30
30
|
import { _checkAborted, _consumeSseToTerminal, _parseMaxWait, _pollToTerminal, _projectResult, } from './builder.js';
|
|
31
31
|
import { Handle } from './handle.js';
|
|
32
32
|
/**
|
|
@@ -118,6 +118,12 @@ export class MergeBuilder {
|
|
|
118
118
|
throw new GislTimeoutError(`Merge workflow ${created.workflowId} reached terminal status but maxWait elapsed before downloads could be fetched`);
|
|
119
119
|
}
|
|
120
120
|
const downloads = await this.client.getWorkflowDownloads(created.workflowId);
|
|
121
|
+
// TDqmkWpX: the maxWait deadline also covers the downloads fetch itself —
|
|
122
|
+
// re-check AFTER the call so a slow getWorkflowDownloads cannot return a
|
|
123
|
+
// success past the advertised whole-run deadline.
|
|
124
|
+
if (Date.now() >= deadline) {
|
|
125
|
+
throw new GislTimeoutError(`Merge workflow ${created.workflowId} downloads fetch completed after maxWait elapsed`);
|
|
126
|
+
}
|
|
121
127
|
// p0SuJEeK — project ONLY the merge job's output. getWorkflowDownloads
|
|
122
128
|
// returns a download group per terminal job, which now INCLUDES the
|
|
123
129
|
// `passthrough` source jobs (their output is the unchanged upload). Those
|
|
@@ -125,7 +131,7 @@ export class MergeBuilder {
|
|
|
125
131
|
// would pollute the Result with the raw inputs. The merge job's ref is
|
|
126
132
|
// 'merge' (see buildPayload); the source jobs are 'src_N'.
|
|
127
133
|
const mergeDownloads = downloads.downloads.filter((d) => d.ref === 'merge');
|
|
128
|
-
return _projectResult(finalStatus, mergeDownloads, this.opOptionsForResolved());
|
|
134
|
+
return _projectResult(finalStatus, mergeDownloads, this.opOptionsForResolved(plan.mediaKind));
|
|
129
135
|
}
|
|
130
136
|
async submit(options) {
|
|
131
137
|
const plan = this.planSequence();
|
|
@@ -202,6 +208,32 @@ export class MergeBuilder {
|
|
|
202
208
|
if (positions.length > 10) {
|
|
203
209
|
throw new GislConfigError(`merge accepts at most 10 inputs (got ${positions.length}). Reduce the sequence or split the merge.`);
|
|
204
210
|
}
|
|
211
|
+
// Validate merge-level options BEFORE upload (parity with PHP MergeBuilder).
|
|
212
|
+
// A `targetSize: 'garbage'` typo must fail locally rather than burning N
|
|
213
|
+
// uploads before parseSizeString fires from wireMergeOptions().
|
|
214
|
+
//
|
|
215
|
+
// Gated to video (codex #176 r3 DCJUvvfA) — `target_size_bytes` only
|
|
216
|
+
// crosses the wire for video merges (see wireMergeOptions); for image/audio
|
|
217
|
+
// the field is silently dropped, so validating its string form would reject
|
|
218
|
+
// a merge over a value that never leaves the SDK.
|
|
219
|
+
if (mediaKind === 'video' && typeof this.opOptions.targetSize === 'string') {
|
|
220
|
+
try {
|
|
221
|
+
parseSizeString(this.opOptions.targetSize);
|
|
222
|
+
}
|
|
223
|
+
catch {
|
|
224
|
+
throw new GislConfigError(`Invalid targetSize string '${this.opOptions.targetSize}' — expected '<num>[B|KB|MB|GB]'.`);
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
// Image merges require an `output_type` (the generated merge schema marks
|
|
228
|
+
// `output_type` required for image kind). Without this check the SDK would
|
|
229
|
+
// upload all assets then receive a server-side validation failure instead
|
|
230
|
+
// of a free local one. Parity with PHP MergeBuilder.
|
|
231
|
+
if (mediaKind === 'image' &&
|
|
232
|
+
this.opOptions.output == null &&
|
|
233
|
+
this.opOptions.outputType == null) {
|
|
234
|
+
throw new GislConfigError('image merges require an explicit output_type — set MergeOptions(output: "video"|"gif") or ' +
|
|
235
|
+
'MergeOptions(outputType: ...). The server rejects image merge requests with no output_type.');
|
|
236
|
+
}
|
|
205
237
|
return { mediaKind, positions, uniqueAssets: uploadSet };
|
|
206
238
|
}
|
|
207
239
|
inferMediaKind() {
|
|
@@ -308,12 +340,55 @@ export class MergeBuilder {
|
|
|
308
340
|
};
|
|
309
341
|
return { jobs: [...sourceJobs, mergeJob] };
|
|
310
342
|
}
|
|
311
|
-
opOptionsForResolved() {
|
|
312
|
-
//
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
343
|
+
opOptionsForResolved(mediaKind) {
|
|
344
|
+
// Mirror wireMergeOptions's per-media allowlist (and PHP
|
|
345
|
+
// opOptionsForResolved) so resolvedOptions.applied reports ONLY the
|
|
346
|
+
// options that actually crossed the wire for this media kind — not the
|
|
347
|
+
// raw option bag (which would falsely claim dropped fields were applied).
|
|
348
|
+
const o = this.opOptions;
|
|
349
|
+
const out = {};
|
|
350
|
+
// All media kinds.
|
|
351
|
+
if (o.output !== undefined)
|
|
352
|
+
out.output = o.output;
|
|
353
|
+
if (o.outputType !== undefined)
|
|
354
|
+
out.outputType = o.outputType;
|
|
355
|
+
if (o.transition !== undefined)
|
|
356
|
+
out.transition = o.transition;
|
|
357
|
+
// Video + audio.
|
|
358
|
+
if (mediaKind === 'video' || mediaKind === 'audio') {
|
|
359
|
+
if (o.crossfadeDuration !== undefined)
|
|
360
|
+
out.crossfadeDuration = o.crossfadeDuration;
|
|
361
|
+
if (o.normalizeAudio !== undefined)
|
|
362
|
+
out.normalizeAudio = o.normalizeAudio;
|
|
363
|
+
}
|
|
364
|
+
// Audio only.
|
|
365
|
+
if (mediaKind === 'audio' && o.gapDuration !== undefined)
|
|
366
|
+
out.gapDuration = o.gapDuration;
|
|
367
|
+
// Video only.
|
|
368
|
+
if (mediaKind === 'video') {
|
|
369
|
+
if (o.codec !== undefined)
|
|
370
|
+
out.codec = o.codec;
|
|
371
|
+
if (o.crf !== undefined)
|
|
372
|
+
out.crf = o.crf;
|
|
373
|
+
if (o.preset !== undefined)
|
|
374
|
+
out.preset = o.preset;
|
|
375
|
+
if (o.targetSize !== undefined)
|
|
376
|
+
out.targetSize = o.targetSize;
|
|
377
|
+
}
|
|
378
|
+
// Image only.
|
|
379
|
+
if (mediaKind === 'image') {
|
|
380
|
+
if (o.transitionDuration !== undefined)
|
|
381
|
+
out.transitionDuration = o.transitionDuration;
|
|
382
|
+
if (o.fps !== undefined)
|
|
383
|
+
out.fps = o.fps;
|
|
384
|
+
if (o.durationPerImage !== undefined)
|
|
385
|
+
out.durationPerImage = o.durationPerImage;
|
|
386
|
+
if (o.loopCount !== undefined)
|
|
387
|
+
out.loopCount = o.loopCount;
|
|
388
|
+
if (o.videoFormat !== undefined)
|
|
389
|
+
out.videoFormat = o.videoFormat;
|
|
390
|
+
}
|
|
391
|
+
return out;
|
|
317
392
|
}
|
|
318
393
|
async awaitTerminal(args) {
|
|
319
394
|
if (args.useSSE) {
|
|
@@ -321,10 +396,12 @@ export class MergeBuilder {
|
|
|
321
396
|
return await _consumeSseToTerminal(this.client, args);
|
|
322
397
|
}
|
|
323
398
|
catch (err) {
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
399
|
+
// TDqmkWpX: poll-fallback ONLY on a clean SSE stream-end or a typed
|
|
400
|
+
// transport error; rethrow everything else (timeout, abort, API, an
|
|
401
|
+
// onProgress callback throw, anything unexpected) so it isn't masked.
|
|
402
|
+
if (!(err instanceof SseEndedWithoutTerminal || err instanceof GislNetworkError)) {
|
|
327
403
|
throw err;
|
|
404
|
+
}
|
|
328
405
|
}
|
|
329
406
|
}
|
|
330
407
|
return await _pollToTerminal(this.client, args);
|
|
@@ -367,45 +444,67 @@ function assetIdentity(a) {
|
|
|
367
444
|
// anything else is a separate upload" — predictable.
|
|
368
445
|
return `path:${a.path}`;
|
|
369
446
|
}
|
|
370
|
-
|
|
447
|
+
/**
|
|
448
|
+
* Project the merge-level {@link MergeOptions} into the per-media wire
|
|
449
|
+
* allowlist. Exported so the file-first `MergedRecipe`
|
|
450
|
+
* (`files([...]).merge(...)`) lowers identically to this operation-first
|
|
451
|
+
* `client.merge(...)` builder — one allowlist, no drift. Mirrors the PHP
|
|
452
|
+
* `MergeBuilder::wireMergeOptions()` public-static seam.
|
|
453
|
+
*
|
|
454
|
+
* @internal Not re-exported from `index.ts`; shared between the two merge
|
|
455
|
+
* surfaces only.
|
|
456
|
+
*/
|
|
457
|
+
export function wireMergeOptions(opts, mediaKind) {
|
|
371
458
|
const out = {};
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
// Codex r2 medium ab2422e56ea0 — merge-level `gap_duration` is on
|
|
377
|
-
// MergeAudioOptions only (not MergeVideoOptions or MergeImageOptions).
|
|
378
|
-
// Drop it for non-audio merges instead of shipping an invalid payload.
|
|
379
|
-
if (opts.gapDuration !== undefined && mediaKind === 'audio')
|
|
380
|
-
out.gap_duration = opts.gapDuration;
|
|
381
|
-
if (opts.normalizeAudio !== undefined)
|
|
382
|
-
out.normalize_audio = opts.normalizeAudio;
|
|
383
|
-
if (opts.codec !== undefined)
|
|
384
|
-
out.codec = opts.codec;
|
|
385
|
-
if (opts.crf !== undefined)
|
|
386
|
-
out.crf = opts.crf;
|
|
387
|
-
if (opts.preset !== undefined)
|
|
388
|
-
out.preset = opts.preset;
|
|
389
|
-
if (opts.targetSize !== undefined) {
|
|
390
|
-
out.target_size_bytes = typeof opts.targetSize === 'number'
|
|
391
|
-
? opts.targetSize
|
|
392
|
-
: parseSizeString(opts.targetSize);
|
|
393
|
-
out.encoding_mode = 'target_size';
|
|
394
|
-
}
|
|
395
|
-
if (opts.transitionDuration !== undefined)
|
|
396
|
-
out.transition_duration = opts.transitionDuration;
|
|
397
|
-
if (opts.fps !== undefined)
|
|
398
|
-
out.fps = opts.fps;
|
|
399
|
-
if (opts.durationPerImage !== undefined)
|
|
400
|
-
out.duration_per_image = opts.durationPerImage;
|
|
401
|
-
if (opts.loopCount !== undefined)
|
|
402
|
-
out.loop_count = opts.loopCount;
|
|
459
|
+
// Per-media wire allowlist — mirror of PHP MergeBuilder::wireMergeOptions
|
|
460
|
+
// (codex 30d…/parity): a field set on the wrong media kind is DROPPED
|
|
461
|
+
// locally rather than shipped as an invalid payload the server 422s.
|
|
462
|
+
// All media kinds.
|
|
403
463
|
if (opts.output !== undefined)
|
|
404
464
|
out.output_type = opts.output;
|
|
405
465
|
if (opts.outputType !== undefined)
|
|
406
466
|
out.output_type = opts.outputType;
|
|
407
|
-
if (opts.
|
|
408
|
-
out.
|
|
467
|
+
if (opts.transition !== undefined)
|
|
468
|
+
out.transition = opts.transition;
|
|
469
|
+
// Video + audio.
|
|
470
|
+
if (mediaKind === 'video' || mediaKind === 'audio') {
|
|
471
|
+
if (opts.crossfadeDuration !== undefined)
|
|
472
|
+
out.crossfade_duration = opts.crossfadeDuration;
|
|
473
|
+
if (opts.normalizeAudio !== undefined)
|
|
474
|
+
out.normalize_audio = opts.normalizeAudio;
|
|
475
|
+
}
|
|
476
|
+
// Audio only — merge-level `gap_duration` is on MergeAudioOptions only
|
|
477
|
+
// (codex r2 medium ab2422e56ea0).
|
|
478
|
+
if (mediaKind === 'audio' && opts.gapDuration !== undefined)
|
|
479
|
+
out.gap_duration = opts.gapDuration;
|
|
480
|
+
// Video only.
|
|
481
|
+
if (mediaKind === 'video') {
|
|
482
|
+
if (opts.codec !== undefined)
|
|
483
|
+
out.codec = opts.codec;
|
|
484
|
+
if (opts.crf !== undefined)
|
|
485
|
+
out.crf = opts.crf;
|
|
486
|
+
if (opts.preset !== undefined)
|
|
487
|
+
out.preset = opts.preset;
|
|
488
|
+
if (opts.targetSize !== undefined) {
|
|
489
|
+
out.target_size_bytes = typeof opts.targetSize === 'number'
|
|
490
|
+
? opts.targetSize
|
|
491
|
+
: parseSizeString(opts.targetSize);
|
|
492
|
+
out.encoding_mode = 'target_size';
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
// Image only.
|
|
496
|
+
if (mediaKind === 'image') {
|
|
497
|
+
if (opts.transitionDuration !== undefined)
|
|
498
|
+
out.transition_duration = opts.transitionDuration;
|
|
499
|
+
if (opts.fps !== undefined)
|
|
500
|
+
out.fps = opts.fps;
|
|
501
|
+
if (opts.durationPerImage !== undefined)
|
|
502
|
+
out.duration_per_image = opts.durationPerImage;
|
|
503
|
+
if (opts.loopCount !== undefined)
|
|
504
|
+
out.loop_count = opts.loopCount;
|
|
505
|
+
if (opts.videoFormat !== undefined)
|
|
506
|
+
out.video_format = opts.videoFormat;
|
|
507
|
+
}
|
|
409
508
|
return out;
|
|
410
509
|
}
|
|
411
510
|
/**
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Browser stub for `node-fs.ts` — selected via the package.json `browser` field
|
|
3
|
+
* file-remap so browser bundlers (vite/webpack/esbuild) never pull `node:fs` /
|
|
4
|
+
* `node:path`. These are only reached by path-string uploads, which are
|
|
5
|
+
* Node-only; a browser caller passes a `Blob`/`File` (handled by
|
|
6
|
+
* `blobByteSource`, no filesystem) and never invokes these. If somehow called
|
|
7
|
+
* in a browser, they throw a clear error rather than a missing-module crash.
|
|
8
|
+
*
|
|
9
|
+
* The signatures intentionally mirror the `node-fs.ts` re-exports
|
|
10
|
+
* (`node:fs/promises` `open`/`stat`, `node:path` `basename`) so `client.ts`
|
|
11
|
+
* type-checks identically against either module.
|
|
12
|
+
*/
|
|
13
|
+
import type { open as NodeOpen, stat as NodeStat } from 'node:fs/promises';
|
|
14
|
+
import type { basename as NodeBasename } from 'node:path';
|
|
15
|
+
export declare const open: typeof NodeOpen;
|
|
16
|
+
export declare const stat: typeof NodeStat;
|
|
17
|
+
export declare const basename: typeof NodeBasename;
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
const pathUploadsAreNodeOnly = () => {
|
|
2
|
+
throw new Error('Path-string file uploads require Node.js (node:fs). In a browser, pass a ' +
|
|
3
|
+
'Blob/File to uploadFile()/file() instead.');
|
|
4
|
+
};
|
|
5
|
+
export const open = pathUploadsAreNodeOnly;
|
|
6
|
+
export const stat = pathUploadsAreNodeOnly;
|
|
7
|
+
export const basename = pathUploadsAreNodeOnly;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Node-only filesystem/path access used by path-string uploads in `client.ts`.
|
|
3
|
+
*
|
|
4
|
+
* @internal — isolated in its own module so the browser build can swap it for
|
|
5
|
+
* `node-fs.browser.ts` via the package.json `browser` field, keeping `node:fs`
|
|
6
|
+
* out of browser bundles. `client.ts` imports these STATICALLY (not via dynamic
|
|
7
|
+
* `import()`), which is deliberate: a static re-export chain stays interceptable
|
|
8
|
+
* by `vi.mock('node:fs/promises')` in the test suite, whereas a dynamic import
|
|
9
|
+
* of a `node:` builtin is not mocked by vitest. Browser callers upload a
|
|
10
|
+
* `Blob`/`File` (which never reaches this module); a browser bundle resolves the
|
|
11
|
+
* `.browser` stub instead, so these symbols are never loaded there.
|
|
12
|
+
*/
|
|
13
|
+
export { open, stat } from 'node:fs/promises';
|
|
14
|
+
export { basename } from 'node:path';
|
package/dist/node-fs.js
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Node-only filesystem/path access used by path-string uploads in `client.ts`.
|
|
3
|
+
*
|
|
4
|
+
* @internal — isolated in its own module so the browser build can swap it for
|
|
5
|
+
* `node-fs.browser.ts` via the package.json `browser` field, keeping `node:fs`
|
|
6
|
+
* out of browser bundles. `client.ts` imports these STATICALLY (not via dynamic
|
|
7
|
+
* `import()`), which is deliberate: a static re-export chain stays interceptable
|
|
8
|
+
* by `vi.mock('node:fs/promises')` in the test suite, whereas a dynamic import
|
|
9
|
+
* of a `node:` builtin is not mocked by vitest. Browser callers upload a
|
|
10
|
+
* `Blob`/`File` (which never reaches this module); a browser bundle resolves the
|
|
11
|
+
* `.browser` stub instead, so these symbols are never loaded there.
|
|
12
|
+
*/
|
|
13
|
+
export { open, stat } from 'node:fs/promises';
|
|
14
|
+
export { basename } from 'node:path';
|
package/dist/sha256.d.ts
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Dependency-free, **synchronous** SHA-256 — browser- and Node-safe.
|
|
3
|
+
*
|
|
4
|
+
* @internal — NOT re-exported from `index.ts`. Exists so the ergonomic
|
|
5
|
+
* preset-resolver fingerprint (`preset_resolver.ts`) can hash without importing
|
|
6
|
+
* `node:crypto`, which would drag a Node-only built-in into the browser entry
|
|
7
|
+
* graph. Output is byte-identical to Node's
|
|
8
|
+
* `createHash('sha256').update(input, 'utf8').digest('hex')` and to PHP's
|
|
9
|
+
* `hash('sha256', $input)` — the resolver fingerprint is parity-pinned
|
|
10
|
+
* (`tests/unit/preset-resolver.test.ts` asserts exact digests; "do NOT relax to
|
|
11
|
+
* a regex"). Web Crypto's `subtle.digest` is async and would force the resolver
|
|
12
|
+
* sync→async, so a small sync implementation is used instead.
|
|
13
|
+
*
|
|
14
|
+
* Standard FIPS 180-4 SHA-256. Input is hashed as UTF-8 bytes.
|
|
15
|
+
*/
|
|
16
|
+
/**
|
|
17
|
+
* Compute the SHA-256 of `input` (hashed as its UTF-8 byte encoding) and return
|
|
18
|
+
* the lowercase 64-character hex digest.
|
|
19
|
+
*/
|
|
20
|
+
export declare function sha256Hex(input: string): string;
|
package/dist/sha256.js
ADDED
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Dependency-free, **synchronous** SHA-256 — browser- and Node-safe.
|
|
3
|
+
*
|
|
4
|
+
* @internal — NOT re-exported from `index.ts`. Exists so the ergonomic
|
|
5
|
+
* preset-resolver fingerprint (`preset_resolver.ts`) can hash without importing
|
|
6
|
+
* `node:crypto`, which would drag a Node-only built-in into the browser entry
|
|
7
|
+
* graph. Output is byte-identical to Node's
|
|
8
|
+
* `createHash('sha256').update(input, 'utf8').digest('hex')` and to PHP's
|
|
9
|
+
* `hash('sha256', $input)` — the resolver fingerprint is parity-pinned
|
|
10
|
+
* (`tests/unit/preset-resolver.test.ts` asserts exact digests; "do NOT relax to
|
|
11
|
+
* a regex"). Web Crypto's `subtle.digest` is async and would force the resolver
|
|
12
|
+
* sync→async, so a small sync implementation is used instead.
|
|
13
|
+
*
|
|
14
|
+
* Standard FIPS 180-4 SHA-256. Input is hashed as UTF-8 bytes.
|
|
15
|
+
*/
|
|
16
|
+
// First 32 bits of the fractional parts of the cube roots of the first 64 primes.
|
|
17
|
+
const K = new Uint32Array([
|
|
18
|
+
0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5,
|
|
19
|
+
0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174,
|
|
20
|
+
0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da,
|
|
21
|
+
0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967,
|
|
22
|
+
0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85,
|
|
23
|
+
0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070,
|
|
24
|
+
0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3,
|
|
25
|
+
0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2,
|
|
26
|
+
]);
|
|
27
|
+
function rotr(value, bits) {
|
|
28
|
+
return (value >>> bits) | (value << (32 - bits));
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Compute the SHA-256 of `input` (hashed as its UTF-8 byte encoding) and return
|
|
32
|
+
* the lowercase 64-character hex digest.
|
|
33
|
+
*/
|
|
34
|
+
export function sha256Hex(input) {
|
|
35
|
+
const bytes = new TextEncoder().encode(input); // UTF-8, matching Node `.update(str)`
|
|
36
|
+
// --- Padding (FIPS 180-4 §5.1.1) ---
|
|
37
|
+
const bitLength = bytes.length * 8;
|
|
38
|
+
// message + 0x80 + zero-pad to 56 mod 64 + 8-byte big-endian length
|
|
39
|
+
const paddedLength = ((bytes.length + 8) >> 6 << 6) + 64;
|
|
40
|
+
const buffer = new Uint8Array(paddedLength);
|
|
41
|
+
buffer.set(bytes);
|
|
42
|
+
buffer[bytes.length] = 0x80;
|
|
43
|
+
// 64-bit big-endian bit length in the final 8 bytes. Both words are written:
|
|
44
|
+
// the high word via Math.floor (exact for the Number range, well beyond any
|
|
45
|
+
// realistic input) and the low word via `>>> 0`. Fingerprint inputs are tiny,
|
|
46
|
+
// so the high word is effectively always zero, but it is set for correctness.
|
|
47
|
+
const view = new DataView(buffer.buffer);
|
|
48
|
+
view.setUint32(paddedLength - 4, bitLength >>> 0, false);
|
|
49
|
+
view.setUint32(paddedLength - 8, Math.floor(bitLength / 0x100000000), false);
|
|
50
|
+
// --- Initial hash values: fractional parts of the square roots of the first 8 primes. ---
|
|
51
|
+
let h0 = 0x6a09e667;
|
|
52
|
+
let h1 = 0xbb67ae85;
|
|
53
|
+
let h2 = 0x3c6ef372;
|
|
54
|
+
let h3 = 0xa54ff53a;
|
|
55
|
+
let h4 = 0x510e527f;
|
|
56
|
+
let h5 = 0x9b05688c;
|
|
57
|
+
let h6 = 0x1f83d9ab;
|
|
58
|
+
let h7 = 0x5be0cd19;
|
|
59
|
+
const w = new Uint32Array(64);
|
|
60
|
+
for (let offset = 0; offset < paddedLength; offset += 64) {
|
|
61
|
+
for (let i = 0; i < 16; i++) {
|
|
62
|
+
w[i] = view.getUint32(offset + i * 4, false);
|
|
63
|
+
}
|
|
64
|
+
for (let i = 16; i < 64; i++) {
|
|
65
|
+
const s0 = rotr(w[i - 15], 7) ^ rotr(w[i - 15], 18) ^ (w[i - 15] >>> 3);
|
|
66
|
+
const s1 = rotr(w[i - 2], 17) ^ rotr(w[i - 2], 19) ^ (w[i - 2] >>> 10);
|
|
67
|
+
w[i] = (w[i - 16] + s0 + w[i - 7] + s1) | 0;
|
|
68
|
+
}
|
|
69
|
+
let a = h0;
|
|
70
|
+
let b = h1;
|
|
71
|
+
let c = h2;
|
|
72
|
+
let d = h3;
|
|
73
|
+
let e = h4;
|
|
74
|
+
let f = h5;
|
|
75
|
+
let g = h6;
|
|
76
|
+
let h = h7;
|
|
77
|
+
for (let i = 0; i < 64; i++) {
|
|
78
|
+
const S1 = rotr(e, 6) ^ rotr(e, 11) ^ rotr(e, 25);
|
|
79
|
+
const ch = (e & f) ^ (~e & g);
|
|
80
|
+
const temp1 = (h + S1 + ch + K[i] + w[i]) | 0;
|
|
81
|
+
const S0 = rotr(a, 2) ^ rotr(a, 13) ^ rotr(a, 22);
|
|
82
|
+
const maj = (a & b) ^ (a & c) ^ (b & c);
|
|
83
|
+
const temp2 = (S0 + maj) | 0;
|
|
84
|
+
h = g;
|
|
85
|
+
g = f;
|
|
86
|
+
f = e;
|
|
87
|
+
e = (d + temp1) | 0;
|
|
88
|
+
d = c;
|
|
89
|
+
c = b;
|
|
90
|
+
b = a;
|
|
91
|
+
a = (temp1 + temp2) | 0;
|
|
92
|
+
}
|
|
93
|
+
h0 = (h0 + a) | 0;
|
|
94
|
+
h1 = (h1 + b) | 0;
|
|
95
|
+
h2 = (h2 + c) | 0;
|
|
96
|
+
h3 = (h3 + d) | 0;
|
|
97
|
+
h4 = (h4 + e) | 0;
|
|
98
|
+
h5 = (h5 + f) | 0;
|
|
99
|
+
h6 = (h6 + g) | 0;
|
|
100
|
+
h7 = (h7 + h) | 0;
|
|
101
|
+
}
|
|
102
|
+
const out = [h0, h1, h2, h3, h4, h5, h6, h7];
|
|
103
|
+
let hex = '';
|
|
104
|
+
for (const value of out) {
|
|
105
|
+
hex += (value >>> 0).toString(16).padStart(8, '0');
|
|
106
|
+
}
|
|
107
|
+
return hex;
|
|
108
|
+
}
|
package/dist/types.d.ts
CHANGED
|
@@ -18,6 +18,20 @@ export interface GislClientConfig {
|
|
|
18
18
|
* environment's responsibility.
|
|
19
19
|
*/
|
|
20
20
|
useSessionCookie?: boolean;
|
|
21
|
+
/**
|
|
22
|
+
* Preferred response language. When set, the SDK sends
|
|
23
|
+
* `Accept-Language: <locale>` on every GISL-API request (e.g. `'fr-FR'`,
|
|
24
|
+
* `'de'`). The server echoes the language it actually resolved via the
|
|
25
|
+
* `Content-Language` response header, surfaced on
|
|
26
|
+
* `GislApiError.contentLanguage`. When no supported language matches, the
|
|
27
|
+
* server falls back to its default (typically `en-GB`).
|
|
28
|
+
*
|
|
29
|
+
* A dedicated `locale` wins over any `Accept-Language` passed through
|
|
30
|
+
* `headers` (mirrors how `apiKey` wins over a caller-supplied
|
|
31
|
+
* `Authorization` header); the conflicting `headers` entry is dropped
|
|
32
|
+
* case-insensitively so the request never carries two variants.
|
|
33
|
+
*/
|
|
34
|
+
locale?: string;
|
|
21
35
|
/** Threshold in bytes above which multipart upload is used (default: 10MB) */
|
|
22
36
|
multipartThreshold?: number;
|
|
23
37
|
/**
|
|
@@ -70,12 +84,27 @@ export interface ConnectionSourcePayload {
|
|
|
70
84
|
path: string;
|
|
71
85
|
}
|
|
72
86
|
export type WorkflowSourcePayload = UploadSourcePayload | JobOutputSourcePayload | ExternalImportSourcePayload | ConnectionSourcePayload;
|
|
87
|
+
/**
|
|
88
|
+
* The source leaves a multi-input `JobInputV2Payload.source` may take —
|
|
89
|
+
* `WorkflowSourcePayload` minus the `upload` leaf. Per the contract
|
|
90
|
+
* (`MultiInputSource` in `openapi/api.yaml`), multi-input operations
|
|
91
|
+
* (merge / archive / image_watermark / custom_luma / audio_overlay) do NOT
|
|
92
|
+
* accept upload-direct: an uploaded file enters a multi-input job via a
|
|
93
|
+
* `passthrough` source job referenced downstream by `{ type: 'job_output' }`.
|
|
94
|
+
* Single-input `JobDefinitionPayload.source` keeps the wider
|
|
95
|
+
* `WorkflowSourcePayload` (upload-direct is valid there).
|
|
96
|
+
*
|
|
97
|
+
* Named with the SDK's `*Payload` suffix to match every sibling source type
|
|
98
|
+
* and to avoid colliding with the generated `MultiInputSource` model (a
|
|
99
|
+
* different, deep-import-only type family).
|
|
100
|
+
*/
|
|
101
|
+
export type MultiInputSourcePayload = JobOutputSourcePayload | ExternalImportSourcePayload | ConnectionSourcePayload;
|
|
73
102
|
export declare function uploadSource(fileId: string): UploadSourcePayload;
|
|
74
103
|
export declare function jobOutputSource(from: string, operation?: string): JobOutputSourcePayload;
|
|
75
104
|
export declare function externalImportSource(externalSourceId: string): ExternalImportSourcePayload;
|
|
76
105
|
export declare function connectionSource(connectionId: string, path: string): ConnectionSourcePayload;
|
|
77
106
|
export interface JobInputV2Payload {
|
|
78
|
-
source:
|
|
107
|
+
source: MultiInputSourcePayload;
|
|
79
108
|
role?: JobInputV2RoleEnum;
|
|
80
109
|
per_input_options?: Record<string, unknown>;
|
|
81
110
|
}
|
|
@@ -139,6 +168,17 @@ export interface WorkflowProcessingPayload {
|
|
|
139
168
|
}
|
|
140
169
|
export interface WorkflowCreatePayload {
|
|
141
170
|
jobs: JobDefinitionPayload[];
|
|
171
|
+
/**
|
|
172
|
+
* Flat single-job form (with `operations`): top-level input source, exactly
|
|
173
|
+
* equivalent to `jobs: [{ source, operations }]` (contracts D0Gsri8V, v2.64.0).
|
|
174
|
+
* The spec's `oneOf` makes `jobs` and `source`+`operations` mutually exclusive;
|
|
175
|
+
* the SDK builders always emit the explicit `jobs[]` form, so these are typed
|
|
176
|
+
* optional for spec-completeness (a consumer hand-building the flat form omits
|
|
177
|
+
* `jobs`). Builder adoption of the flat form is a follow-up.
|
|
178
|
+
*/
|
|
179
|
+
source?: WorkflowSourcePayload;
|
|
180
|
+
/** Flat-form operation set (with `source`); equivalent to one job's `operations`. */
|
|
181
|
+
operations?: OperationDef[];
|
|
142
182
|
workflow_edges?: Array<{
|
|
143
183
|
from: string;
|
|
144
184
|
to: string;
|
|
@@ -156,7 +196,7 @@ export interface WorkflowCreatePayload {
|
|
|
156
196
|
* only via deep imports and should not be treated as public API.
|
|
157
197
|
* @internal
|
|
158
198
|
*/
|
|
159
|
-
export declare const WORKFLOW_CREATE_PAYLOAD_KEYS: readonly ["jobs", "workflow_edges", "callback_url", "callback_events", "export", "delivery", "processing"];
|
|
199
|
+
export declare const WORKFLOW_CREATE_PAYLOAD_KEYS: readonly ["jobs", "source", "operations", "workflow_edges", "callback_url", "callback_events", "export", "delivery", "processing"];
|
|
160
200
|
export interface GetSchemaOptions {
|
|
161
201
|
/** Filter the schema to operations that accept this MIME type (e.g. `image/jpeg`). */
|
|
162
202
|
mimeType?: string;
|
|
@@ -196,6 +236,18 @@ export interface CreditsUsageOptions {
|
|
|
196
236
|
/** Page offset (zero-based). Server default is 0. */
|
|
197
237
|
offset?: number;
|
|
198
238
|
}
|
|
239
|
+
export interface ListWorkflowsOptions {
|
|
240
|
+
/**
|
|
241
|
+
* Opaque pagination cursor from a previous page's `nextCursor`. Omit for
|
|
242
|
+
* the first page; treat the value as opaque (do not parse).
|
|
243
|
+
*/
|
|
244
|
+
cursor?: string;
|
|
245
|
+
/**
|
|
246
|
+
* Rows per page. Server defaults to 20 and rejects values outside `[1, 100]`
|
|
247
|
+
* with a 400 validation envelope.
|
|
248
|
+
*/
|
|
249
|
+
limit?: number;
|
|
250
|
+
}
|
|
199
251
|
/**
|
|
200
252
|
* Aggregated result of a `preflightClips()` batch probe — N parallel calls
|
|
201
253
|
* to `POST /api/uploads/{id}/probe`, partitioned by outcome so the caller
|
package/dist/types.js
CHANGED
package/package.json
CHANGED
|
@@ -1,16 +1,28 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@giveitsmaller/sdk",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.10.0",
|
|
4
4
|
"description": "Node.js SDK for the GISL (Give It Smaller) file compression and processing API",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"main": "./dist/index.js",
|
|
8
8
|
"exports": {
|
|
9
9
|
".": {
|
|
10
|
+
"browser": {
|
|
11
|
+
"types": "./dist/index.browser.d.ts",
|
|
12
|
+
"default": "./dist/index.browser.js"
|
|
13
|
+
},
|
|
10
14
|
"types": "./dist/index.d.ts",
|
|
11
15
|
"default": "./dist/index.js"
|
|
16
|
+
},
|
|
17
|
+
"./browser": {
|
|
18
|
+
"types": "./dist/index.browser.d.ts",
|
|
19
|
+
"default": "./dist/index.browser.js"
|
|
12
20
|
}
|
|
13
21
|
},
|
|
22
|
+
"browser": {
|
|
23
|
+
"./dist/index.js": "./dist/index.browser.js",
|
|
24
|
+
"./dist/node-fs.js": "./dist/node-fs.browser.js"
|
|
25
|
+
},
|
|
14
26
|
"types": "./dist/index.d.ts",
|
|
15
27
|
"files": [
|
|
16
28
|
"dist/"
|
|
@@ -19,10 +31,11 @@
|
|
|
19
31
|
"node": ">=18"
|
|
20
32
|
},
|
|
21
33
|
"dependencies": {
|
|
22
|
-
"@giveitsmaller/contracts": "^0.
|
|
34
|
+
"@giveitsmaller/contracts": "^0.16.0"
|
|
23
35
|
},
|
|
24
36
|
"devDependencies": {
|
|
25
37
|
"@types/node": "^22",
|
|
38
|
+
"esbuild": "^0.27",
|
|
26
39
|
"typescript": "^5.7",
|
|
27
40
|
"vitest": "^3.1 <3.2.5",
|
|
28
41
|
"yaml": "^2.6"
|