@giveitsmaller/sdk 0.12.1 → 0.13.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 CHANGED
@@ -12,34 +12,42 @@ Node.js 18+ required.
12
12
 
13
13
  ## Quickstart
14
14
 
15
+ The SDK is **file-first**: you start from a file (`.file(path)` for one,
16
+ `.files([paths])` for many) and call operations *on* it — upload, workflow
17
+ creation, and waiting all happen for you.
18
+
15
19
  ```ts
16
- import { GislClient, uploadSource, OperationType } from '@giveitsmaller/sdk';
17
-
18
- const client = new GislClient({
19
- baseUrl: 'https://api.giveitsmaller.com',
20
- apiKey: 'REPLACE_ME_API_KEY',
21
- });
22
-
23
- const upload = await client.uploadFile('./photo.jpg');
24
-
25
- const workflow = await client.createWorkflow({
26
- jobs: [
27
- {
28
- id: 'compressed',
29
- source: uploadSource(upload.fileId),
30
- operations: [
31
- { type: OperationType.compress, options: { mode: 'lossy', quality: 80 } },
32
- ],
33
- },
34
- ],
35
- });
36
-
37
- await client.waitForWorkflow(workflow.workflowId);
38
-
39
- const dls = await client.getWorkflowDownloads(workflow.workflowId);
40
- console.log('Compressed:', dls.downloads[0].files[0].downloadUrl);
20
+ import { gisl, OptimizeFor } from '@giveitsmaller/sdk';
21
+
22
+ // baseUrl defaults to https://api.giveitsmaller.com; the key can also come
23
+ // from GISL_API_KEY or ~/.gisl/credentials.
24
+ const client = await gisl.create({ apiKey: 'sk_...' });
25
+
26
+ // One file:
27
+ const result = await client
28
+ .file('./photo.jpg')
29
+ .compress(OptimizeFor.Balanced)
30
+ .run({ maxWait: '5m' });
31
+
32
+ console.log(result.url); // pre-signed download URL
33
+
34
+ // Many files (fan-out) — the same chain:
35
+ const many = await client
36
+ .files(['./a.jpg', './b.png'])
37
+ .compress(OptimizeFor.Balanced)
38
+ .run();
39
+
40
+ for (const artifact of many.artifacts) console.log(artifact.url);
41
41
  ```
42
42
 
43
+ > **Operation-first / low-level also ships.** A lower-level
44
+ > `client.compress(path, { ... }).run()` form (and `thumbnail` / `convert`) is
45
+ > available, and the raw wire client — `client.createWorkflow({ jobs: [...] })`
46
+ > with `uploadSource` / `OperationType` for hand-built job DAGs — is the advanced
47
+ > escape hatch. File-first is the recommended direction the
48
+ > [examples](https://github.com/AntonioCS/giveitsmaller-sdks/tree/main/docs/typescript/examples)
49
+ > build on.
50
+
43
51
  > **Reusing an upload id across clients?** An upload created by an
44
52
  > authenticated caller is owned by that caller. If you persist a `fileId` and
45
53
  > later reference it (via `fileInput.uploadId(id)`) from a client configured
package/dist/client.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import type { AudioWatermarkDecodeRequest, AudioWatermarkDecodeResponse, ExternalImportCreatedResponse, ExternalImportRequest, LoginUserRequest, LoginUser200ResponseData, ContactRequest, CreditsBalanceResponse, CreditsUsageResponse, UploadResponse, UploadProbeResponse, WorkflowCancelResponse, WorkflowCreateResponse, WorkflowResumeResponse, WorkflowStatusResponse, WorkflowListResponse, WorkflowSummary, WorkflowDownloadResponse, MetadataResponse, RetryResponse } from '@giveitsmaller/contracts/openapi';
2
- import type { CreditsUsageOptions, ListWorkflowsOptions, GetSchemaOptions, GetSchemaResult, GislClientConfig, GislSseEvent, PreflightClipsResult, UploadOptions, WaitOptions, WorkflowCreatePayload, _Sdk3HandCodedKeepaliveResult, _Sdk3HandCodedMultipartStatusResult, _Sdk3HandCodedPresignPartsResult } from './types.js';
2
+ import type { CreditsUsageOptions, ListWorkflowsOptions, GetSchemaOptions, GetSchemaResult, GislClientConfig, GislSseEvent, PreflightClipsResult, ProbeWaitOptions, ProbeWaitResult, UploadOptions, WaitOptions, WorkflowCreatePayload, _Sdk3HandCodedKeepaliveResult, _Sdk3HandCodedMultipartStatusResult, _Sdk3HandCodedPresignPartsResult } from './types.js';
3
3
  export declare const MULTIPART_CONCURRENCY_DEFAULT: 4;
4
4
  export declare const DEFAULT_MULTIPART_FIRST_CHUNK_SIZE: number;
5
5
  export interface ValidationDetail {
@@ -306,15 +306,52 @@ export declare class GislClient {
306
306
  /**
307
307
  * Probe an uploaded file for workflow-readiness — detects corruption,
308
308
  * unsupported codecs, and pre-assigns the processing class the server
309
- * would route the file to. Designed for the long-form merge edge case
310
- * where a single bad input would fail the whole workflow.
309
+ * would route the file to. For video uploads the probe also lands the
310
+ * codec + duration the server needs to admit the parallel split, so
311
+ * calling this (or {@link waitForProbe}) before workflow-create is the
312
+ * structural unlock for the fast video path on the multipart flow.
311
313
  *
312
- * Currently `availability: planned` calls return
313
- * `GislFeatureNotAvailableError` (422) until the cross-repo Lambda
314
- * support ships. Idempotent: probing the same `fileId` twice returns
315
- * the cached result.
314
+ * Endpoint availability is `stable`. The probe runs asynchronously after
315
+ * upload: until the result has landed, this returns `422`
316
+ * `feature_not_available` (surfaced as {@link GislFeatureNotAvailableError})
317
+ * i.e. that 422 means "probe not landed yet", NOT "not implemented". Once
318
+ * landed it returns a `200` with any `probeStatus`. Idempotent: probing the
319
+ * same `fileId` twice returns the cached result. See {@link waitForProbe}
320
+ * for a bounded poll that turns this into a single ready/gave-up answer.
321
+ */
322
+ probeUpload(fileId: string, options?: {
323
+ signal?: AbortSignal;
324
+ }): Promise<UploadProbeResponse>;
325
+ /**
326
+ * Bounded poll of {@link probeUpload} until the probe lands — the helper
327
+ * frontends call between upload-complete and workflow-create so the server
328
+ * sees the video's codec + duration and admits the ~3× parallel split.
329
+ *
330
+ * Loop (per the API wire contract):
331
+ * - `422 feature_not_available` → probe not landed yet → keep polling
332
+ * (exponential full-jitter backoff, honouring a `Retry-After` header when
333
+ * present, clamped to the remaining budget).
334
+ * - any `200` → STOP. Resolves `{ landed: true, probe }` regardless of
335
+ * `probeStatus` (ok / corrupt / unsupported_codec / missing_metadata) —
336
+ * the server + fan-out gate decide split-vs-single from the landed
337
+ * metadata; the SDK does not interpret it.
338
+ * - `5xx` (prober crash) → retry a couple of times, then give up.
339
+ * - timeout → give up.
340
+ *
341
+ * **Never bounces:** on give-up (timeout / repeated 5xx / transport) it
342
+ * resolves `{ landed: false, reason }` rather than throwing, so the caller
343
+ * proceeds to create the workflow anyway (the server's size heuristic routes
344
+ * it; worst case = today's single-task behaviour). Genuine failures —
345
+ * `404 upload_not_found`, auth errors, or caller abort — DO propagate (they
346
+ * are not "probe not ready"), so a real problem is never silently swallowed.
347
+ *
348
+ * `timeoutMs` bounds the OVERALL poll, checked between attempts; each
349
+ * in-flight probe request is bounded by the client's own per-request timeout
350
+ * (a hung request surfaces as a transient and the next deadline check gives
351
+ * up). So a single slow probe may run up to one client-request-timeout before
352
+ * the wait returns.
316
353
  */
317
- probeUpload(fileId: string): Promise<UploadProbeResponse>;
354
+ waitForProbe(fileId: string, options?: ProbeWaitOptions): Promise<ProbeWaitResult>;
318
355
  /**
319
356
  * Probe N uploaded files in parallel and partition the results by
320
357
  * outcome. Returns `{ ok, rejected, errors }` so the caller can
package/dist/client.js CHANGED
@@ -112,6 +112,51 @@ function isRetryableStatus(status) {
112
112
  function isRetryableNetworkError(err) {
113
113
  return err instanceof TypeError;
114
114
  }
115
+ // Parse an HTTP `Retry-After` header into milliseconds. Accepts the two RFC
116
+ // 9110 forms: delta-seconds (e.g. "5") or an HTTP-date. Returns `undefined`
117
+ // for an absent / unparseable / negative value (caller falls back to its own
118
+ // backoff). A past HTTP-date clamps to 0.
119
+ function parseRetryAfterMs(headerValue) {
120
+ if (headerValue === undefined)
121
+ return undefined;
122
+ const trimmed = headerValue.trim();
123
+ if (trimmed === '')
124
+ return undefined;
125
+ let ms;
126
+ if (/^\d+$/.test(trimmed)) {
127
+ ms = Number(trimmed) * 1000;
128
+ }
129
+ else {
130
+ const when = Date.parse(trimmed);
131
+ if (Number.isNaN(when))
132
+ return undefined;
133
+ ms = when - Date.now();
134
+ }
135
+ // A non-positive Retry-After (e.g. "0" or a past HTTP-date) must NOT short-
136
+ // circuit the backoff to zero — treat it as absent so the caller falls back
137
+ // to jitter and the loop can't busy-poll until timeout.
138
+ return ms > 0 ? ms : undefined;
139
+ }
140
+ // Cancellable sleep for poll loops. Resolves after `ms`, or rejects with
141
+ // `GislAbortError` if `signal` aborts. Resolves immediately for ms <= 0.
142
+ function cancellableSleep(ms, signal) {
143
+ if (signal?.aborted)
144
+ return Promise.reject(new GislAbortError('waitForProbe aborted'));
145
+ if (ms <= 0)
146
+ return Promise.resolve();
147
+ return new Promise((resolve, reject) => {
148
+ const onAbort = () => {
149
+ clearTimeout(timer);
150
+ signal?.removeEventListener('abort', onAbort);
151
+ reject(new GislAbortError('waitForProbe aborted'));
152
+ };
153
+ const timer = setTimeout(() => {
154
+ signal?.removeEventListener('abort', onAbort);
155
+ resolve();
156
+ }, ms);
157
+ signal?.addEventListener('abort', onAbort, { once: true });
158
+ });
159
+ }
115
160
  // Full-jitter exponential backoff: delay = random(0, base * 2^attemptIndex).
116
161
  // AWS SDK guidance for shared-throttling sources like S3 — keeps competing
117
162
  // clients from synchronising their retries.
@@ -1979,19 +2024,118 @@ export class GislClient {
1979
2024
  /**
1980
2025
  * Probe an uploaded file for workflow-readiness — detects corruption,
1981
2026
  * unsupported codecs, and pre-assigns the processing class the server
1982
- * would route the file to. Designed for the long-form merge edge case
1983
- * where a single bad input would fail the whole workflow.
2027
+ * would route the file to. For video uploads the probe also lands the
2028
+ * codec + duration the server needs to admit the parallel split, so
2029
+ * calling this (or {@link waitForProbe}) before workflow-create is the
2030
+ * structural unlock for the fast video path on the multipart flow.
1984
2031
  *
1985
- * Currently `availability: planned` calls return
1986
- * `GislFeatureNotAvailableError` (422) until the cross-repo Lambda
1987
- * support ships. Idempotent: probing the same `fileId` twice returns
1988
- * the cached result.
2032
+ * Endpoint availability is `stable`. The probe runs asynchronously after
2033
+ * upload: until the result has landed, this returns `422`
2034
+ * `feature_not_available` (surfaced as {@link GislFeatureNotAvailableError})
2035
+ * i.e. that 422 means "probe not landed yet", NOT "not implemented". Once
2036
+ * landed it returns a `200` with any `probeStatus`. Idempotent: probing the
2037
+ * same `fileId` twice returns the cached result. See {@link waitForProbe}
2038
+ * for a bounded poll that turns this into a single ready/gave-up answer.
1989
2039
  */
1990
- async probeUpload(fileId) {
2040
+ async probeUpload(fileId, options = {}) {
1991
2041
  return this.request('POST', `/api/uploads/${encodeURIComponent(fileId)}/probe`, {
1992
2042
  deserialize: UploadProbeResponseFromJSON,
2043
+ signal: options.signal,
1993
2044
  });
1994
2045
  }
2046
+ /**
2047
+ * Bounded poll of {@link probeUpload} until the probe lands — the helper
2048
+ * frontends call between upload-complete and workflow-create so the server
2049
+ * sees the video's codec + duration and admits the ~3× parallel split.
2050
+ *
2051
+ * Loop (per the API wire contract):
2052
+ * - `422 feature_not_available` → probe not landed yet → keep polling
2053
+ * (exponential full-jitter backoff, honouring a `Retry-After` header when
2054
+ * present, clamped to the remaining budget).
2055
+ * - any `200` → STOP. Resolves `{ landed: true, probe }` regardless of
2056
+ * `probeStatus` (ok / corrupt / unsupported_codec / missing_metadata) —
2057
+ * the server + fan-out gate decide split-vs-single from the landed
2058
+ * metadata; the SDK does not interpret it.
2059
+ * - `5xx` (prober crash) → retry a couple of times, then give up.
2060
+ * - timeout → give up.
2061
+ *
2062
+ * **Never bounces:** on give-up (timeout / repeated 5xx / transport) it
2063
+ * resolves `{ landed: false, reason }` rather than throwing, so the caller
2064
+ * proceeds to create the workflow anyway (the server's size heuristic routes
2065
+ * it; worst case = today's single-task behaviour). Genuine failures —
2066
+ * `404 upload_not_found`, auth errors, or caller abort — DO propagate (they
2067
+ * are not "probe not ready"), so a real problem is never silently swallowed.
2068
+ *
2069
+ * `timeoutMs` bounds the OVERALL poll, checked between attempts; each
2070
+ * in-flight probe request is bounded by the client's own per-request timeout
2071
+ * (a hung request surfaces as a transient and the next deadline check gives
2072
+ * up). So a single slow probe may run up to one client-request-timeout before
2073
+ * the wait returns.
2074
+ */
2075
+ async waitForProbe(fileId, options = {}) {
2076
+ const timeoutMs = sanitiseBaseMs(options.timeoutMs, 30_000);
2077
+ const signal = options.signal;
2078
+ const start = Date.now();
2079
+ const deadline = start + timeoutMs;
2080
+ const BASE_BACKOFF_MS = 250;
2081
+ const MAX_PROBER_RETRIES = 2;
2082
+ let attempt = 0;
2083
+ // Counts transient probe-call failures (5xx + transport + per-request
2084
+ // timeout) — repeated transients give up so the caller creates anyway
2085
+ // (never-bounce).
2086
+ let transientFailures = 0;
2087
+ for (;;) {
2088
+ if (signal?.aborted)
2089
+ throw new GislAbortError('waitForProbe aborted');
2090
+ const remainingBeforeAttempt = deadline - Date.now();
2091
+ if (remainingBeforeAttempt <= 0)
2092
+ return { landed: false, reason: 'timeout' };
2093
+ attempt += 1;
2094
+ options.onPoll?.({ attempt, elapsedMs: Date.now() - start });
2095
+ let retryAfterMs;
2096
+ try {
2097
+ const probe = await this.probeUpload(fileId, { signal });
2098
+ return { landed: true, probe };
2099
+ }
2100
+ catch (err) {
2101
+ // A CALLER abort always propagates (it is not "probe not ready").
2102
+ if (signal?.aborted) {
2103
+ throw err instanceof GislAbortError ? err : new GislAbortError('waitForProbe aborted');
2104
+ }
2105
+ if (err instanceof GislFeatureNotAvailableError) {
2106
+ // Not landed yet — keep polling.
2107
+ retryAfterMs = parseRetryAfterMs(err.responseHeaders?.['retry-after']);
2108
+ }
2109
+ else if ((err instanceof GislApiError && err.statusCode >= 500) ||
2110
+ err instanceof GislTimeoutError ||
2111
+ isRetryableNetworkError(err)) {
2112
+ // Transient probe-call failure — a 5xx, the client's own per-request
2113
+ // timeout, or a transport error. Retry a couple of times, then give
2114
+ // up (never-bounce: the caller creates anyway).
2115
+ transientFailures += 1;
2116
+ if (transientFailures > MAX_PROBER_RETRIES) {
2117
+ return { landed: false, reason: 'prober_error' };
2118
+ }
2119
+ retryAfterMs =
2120
+ err instanceof GislApiError
2121
+ ? parseRetryAfterMs(err.responseHeaders?.['retry-after'])
2122
+ : undefined;
2123
+ }
2124
+ else {
2125
+ // 404 upload_not_found, auth, validation, or any other typed/unexpected
2126
+ // error is a real failure, not "probe not ready" — propagate.
2127
+ throw err;
2128
+ }
2129
+ }
2130
+ const remaining = deadline - Date.now();
2131
+ if (remaining <= 0)
2132
+ return { landed: false, reason: 'timeout' };
2133
+ const backoff = retryAfterMs ?? fullJitterDelay(BASE_BACKOFF_MS, attempt - 1);
2134
+ await cancellableSleep(Math.min(backoff, remaining), signal);
2135
+ if (Date.now() >= deadline)
2136
+ return { landed: false, reason: 'timeout' };
2137
+ }
2138
+ }
1995
2139
  /**
1996
2140
  * Probe N uploaded files in parallel and partition the results by
1997
2141
  * outcome. Returns `{ ok, rejected, errors }` so the caller can
@@ -68,6 +68,7 @@ export interface ResolveCompressOptionsOutput {
68
68
  * @internal — exported for unit tests.
69
69
  */
70
70
  export declare function _parseTargetSize(value: unknown): number;
71
+ export declare const KNOWN_WIRE_FIELDS: Readonly<Record<PresetMedia, ReadonlySet<string>>>;
71
72
  /**
72
73
  * Resolve the wire payload + introspection projection for a compress
73
74
  * operation call. Throws {@link GislConfigError} before any network
@@ -300,7 +300,10 @@ function mergeLayer(acc, layer, source) {
300
300
  // ---------------------------------------------------------------------------
301
301
  // Validations on the merged wire payload
302
302
  // ---------------------------------------------------------------------------
303
- const KNOWN_WIRE_FIELDS = Object.freeze({
303
+ // Exported so the wire-key conformance guard (tests/unit/wire-key-conformance.test.ts)
304
+ // can pin this hand-maintained allowlist to the generated contract metadata: every
305
+ // field the resolver may emit MUST be a real contract option key for `compress`.
306
+ export const KNOWN_WIRE_FIELDS = Object.freeze({
304
307
  image: new Set(['mode', 'quality', 'metadata', 'icc_profile', 'progressive', 'output_format']),
305
308
  audio: new Set(['bitrate', 'channels', 'sample_rate', 'normalize', 'trim_start', 'trim_end']),
306
309
  video: new Set(['codec', 'encoding_mode', 'crf', 'target_size_bytes', 'preset', 'width', 'height', 'fit', 'fps', 'faststart', 'audio_codec', 'audio_bitrate', 'trim_start', 'trim_end']),
@@ -466,7 +466,7 @@ export declare class FilesRecipe {
466
466
  * lowering builds an internal Recipe that throws the same `GislConfigError`.
467
467
  */
468
468
  compress(optimize?: OptimizeFor, options?: Record<string, unknown>): FilesRecipe;
469
- /** Change every input's format. `format` lowers verbatim to the `format` option. */
469
+ /** Change every input's format. `format` lowers to the contract `output_format` wire key (via {@link Recipe.convert}), NOT `format`. */
470
470
  convert(format: string, options?: Record<string, unknown>): FilesRecipe;
471
471
  /** Generate a preview of every input. Omitted dimensions are dropped from the wire options. */
472
472
  thumbnail(options?: {
@@ -785,7 +785,7 @@ export class FilesRecipe {
785
785
  compress(optimize, options = {}) {
786
786
  return this.withStep(this.baseRecipe().compress(optimize, options));
787
787
  }
788
- /** Change every input's format. `format` lowers verbatim to the `format` option. */
788
+ /** Change every input's format. `format` lowers to the contract `output_format` wire key (via {@link Recipe.convert}), NOT `format`. */
789
789
  convert(format, options = {}) {
790
790
  return this.withStep(this.baseRecipe().convert(format, options));
791
791
  }
@@ -1,6 +1,6 @@
1
1
  export { GislClient, DEFAULT_MULTIPART_FIRST_CHUNK_SIZE } from './client.js';
2
2
  export { parseSseStream } from './sse.js';
3
- export type { CreditsUsageOptions, ListWorkflowsOptions, GetSchemaOptions, GetSchemaResult, PreflightClipError, PreflightClipsResult, GislClientConfig, GislSseEvent, UploadOptions, WaitOptions, WorkflowCreatePayload, OperationDef, WorkflowSourcePayload, MultiInputSourcePayload, UploadSourcePayload, JobOutputSourcePayload, ExternalImportSourcePayload, ConnectionSourcePayload, JobInputV2Payload, JobDefinitionPayload, ExternalDestinationPayload, DeliveryPayload, DeliveryModePayload, DeliveryBundleFormatPayload, DeliverySelectionPayload, DeliverySelectionTypePayload, DeliveryOutputRefPayload, WorkflowProcessingPayload, ProcessingClassHintPayload, MultipartCheckpointState, _Sdk3HandCodedUploadedPart, _Sdk3HandCodedMultipartStatusResult, _Sdk3HandCodedPresignedPart, _Sdk3HandCodedPresignPartsResult, _Sdk3HandCodedKeepaliveResult, } from './types.js';
3
+ export type { CreditsUsageOptions, ListWorkflowsOptions, GetSchemaOptions, GetSchemaResult, PreflightClipError, PreflightClipsResult, ProbeWaitOptions, ProbeWaitResult, GislClientConfig, GislSseEvent, UploadOptions, WaitOptions, WorkflowCreatePayload, OperationDef, WorkflowSourcePayload, MultiInputSourcePayload, UploadSourcePayload, JobOutputSourcePayload, ExternalImportSourcePayload, ConnectionSourcePayload, JobInputV2Payload, JobDefinitionPayload, ExternalDestinationPayload, DeliveryPayload, DeliveryModePayload, DeliveryBundleFormatPayload, DeliverySelectionPayload, DeliverySelectionTypePayload, DeliveryOutputRefPayload, WorkflowProcessingPayload, ProcessingClassHintPayload, MultipartCheckpointState, _Sdk3HandCodedUploadedPart, _Sdk3HandCodedMultipartStatusResult, _Sdk3HandCodedPresignedPart, _Sdk3HandCodedPresignPartsResult, _Sdk3HandCodedKeepaliveResult, } from './types.js';
4
4
  export { uploadSource, jobOutputSource, externalImportSource, connectionSource, } from './types.js';
5
5
  export type { GislConfigErrorMetadata } from './errors.js';
6
6
  export { GislError, GislApiError, GislValidationError, GislBalanceExhaustedError, GislTierRestrictedError, GislFeatureTierRestrictedError, GislFeatureNotAvailableError, GislWorkflowExpiredError, GislProbePendingError, GislAuthError, GislUploadCapExceededError, GislMultipartPartError, GislMultipartPartCountError, GislMultipartSessionNotFoundError, GislMultipartSessionOwnershipError, GislMultipartSessionAuthRequiredError, GislTimeoutError, GislAbortError, GislNetworkError, GislConfigError, GislMissingCredentialsError, GislFeatureRequiresAuthError, GislUndeclaredAssetError, GislUnusedAssetError, GislPerInputOptionsNotSupportedError, GislChainCardinalityMismatchError, GislBundleAlreadyArchivedError, GislNoSuchKeyError, GislSinkError, GislResultNotReadyError, } from './errors.js';
package/dist/types.d.ts CHANGED
@@ -287,6 +287,43 @@ export interface WaitOptions {
287
287
  /** Called after each poll with current status */
288
288
  onPoll?: (status: string) => void;
289
289
  }
290
+ export interface ProbeWaitOptions {
291
+ /**
292
+ * Overall wall-clock bound for the poll loop in ms (default: 30000). On
293
+ * elapse the loop gives up and resolves `{ landed: false, reason: 'timeout' }`
294
+ * — it never throws for a slow probe (the caller then creates anyway).
295
+ */
296
+ timeoutMs?: number;
297
+ /** Abort the wait early; aborting rejects the promise with `GislAbortError`. */
298
+ signal?: AbortSignal;
299
+ /**
300
+ * Fires once per poll attempt — drive an "analysing video…" UI in the gap
301
+ * between upload-complete and workflow-create. `attempt` is 1-based;
302
+ * `elapsedMs` is wall-clock since the wait started.
303
+ */
304
+ onPoll?: (info: {
305
+ attempt: number;
306
+ elapsedMs: number;
307
+ }) => void;
308
+ }
309
+ export interface ProbeWaitResult {
310
+ /**
311
+ * True iff the probe landed — i.e. `POST /api/uploads/{id}/probe` returned
312
+ * 200 (ANY `probeStatus`: ok / corrupt / unsupported_codec / missing_metadata).
313
+ * The caller proceeds to create the workflow either way; a landed probe lets
314
+ * the server admit the parallel video split.
315
+ */
316
+ landed: boolean;
317
+ /** The landed probe response — present iff `landed`. */
318
+ probe?: UploadProbeResponse;
319
+ /**
320
+ * Why the wait gave up WITHOUT a landed probe — present iff `!landed`.
321
+ * `timeout` = the bound elapsed; `prober_error` = repeated 5xx from the
322
+ * prober. In both cases the caller should create anyway (never-bounce); the
323
+ * server's size heuristic routes the job (single-task worst case).
324
+ */
325
+ reason?: 'timeout' | 'prober_error';
326
+ }
290
327
  export type GislSseEvent = {
291
328
  event: typeof SseEventType.operation_progress;
292
329
  data: SseOperationProgressData;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@giveitsmaller/sdk",
3
- "version": "0.12.1",
3
+ "version": "0.13.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",
@@ -31,7 +31,7 @@
31
31
  "node": ">=18"
32
32
  },
33
33
  "dependencies": {
34
- "@giveitsmaller/contracts": "^0.17.0"
34
+ "@giveitsmaller/contracts": "^0.18.0"
35
35
  },
36
36
  "devDependencies": {
37
37
  "@types/node": "^22",