@giveitsmaller/sdk 0.9.0 → 0.11.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 -12
- 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/README.md
CHANGED
|
@@ -40,6 +40,14 @@ const dls = await client.getWorkflowDownloads(workflow.workflowId);
|
|
|
40
40
|
console.log('Compressed:', dls.downloads[0].files[0].downloadUrl);
|
|
41
41
|
```
|
|
42
42
|
|
|
43
|
+
> **Reusing an upload id across clients?** An upload created by an
|
|
44
|
+
> authenticated caller is owned by that caller. If you persist a `fileId` and
|
|
45
|
+
> later reference it (via `fileInput.uploadId(id)`) from a client configured
|
|
46
|
+
> with a *different* `apiKey`/session, workflow-create returns
|
|
47
|
+
> `404 upload_not_found` — the server enforces ownership. Reference an upload id
|
|
48
|
+
> only under the same auth that created it; the upload-then-create flow above is
|
|
49
|
+
> consistent by construction. Anonymous-intake uploads are unaffected.
|
|
50
|
+
|
|
43
51
|
## Full documentation
|
|
44
52
|
|
|
45
53
|
Docs are published in the [giveitsmaller-sdks](https://github.com/AntonioCS/giveitsmaller-sdks) repository — they are **not** shipped in the npm tarball (only `dist/` is published).
|
package/dist/_audit.js
CHANGED
package/dist/builder.js
CHANGED
|
@@ -26,7 +26,7 @@
|
|
|
26
26
|
*/
|
|
27
27
|
import { SseEventType, SseOperationProgressDataFromJSON, } from '@giveitsmaller/contracts/openapi';
|
|
28
28
|
import { uploadSource } from './types.js';
|
|
29
|
-
import { GislTimeoutError } from './errors.js';
|
|
29
|
+
import { GislTimeoutError, GislNetworkError, SseEndedWithoutTerminal } from './errors.js';
|
|
30
30
|
// Deferred-usage-only import: `Handle` is constructed inside submit() at call
|
|
31
31
|
// time, not at module load, so the builder.ts <-> handle.ts cycle is safe
|
|
32
32
|
// under ESM (handle.ts imports the await-primitives from this module).
|
|
@@ -245,6 +245,12 @@ export class OperationBuilder {
|
|
|
245
245
|
throw new GislTimeoutError(`Workflow ${created.workflowId} reached terminal status but maxWait elapsed before downloads could be fetched`);
|
|
246
246
|
}
|
|
247
247
|
const downloads = await this.client.getWorkflowDownloads(created.workflowId);
|
|
248
|
+
// TDqmkWpX: the maxWait deadline also covers the downloads fetch itself — a
|
|
249
|
+
// slow getWorkflowDownloads must not return a success after the advertised
|
|
250
|
+
// whole-run deadline. Re-check AFTER the call (the check above is BEFORE).
|
|
251
|
+
if (Date.now() >= deadline) {
|
|
252
|
+
throw new GislTimeoutError(`Workflow ${created.workflowId} downloads fetch completed after maxWait elapsed`);
|
|
253
|
+
}
|
|
248
254
|
return _projectResult(finalStatus, downloads.downloads, resolved.wireOptions, resolved.resolvedOptions);
|
|
249
255
|
}
|
|
250
256
|
/**
|
|
@@ -311,14 +317,15 @@ export class OperationBuilder {
|
|
|
311
317
|
return await _consumeSseToTerminal(this.client, args);
|
|
312
318
|
}
|
|
313
319
|
catch (err) {
|
|
314
|
-
//
|
|
315
|
-
//
|
|
316
|
-
//
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
if (err instanceof
|
|
320
|
+
// TDqmkWpX: poll-fallback ONLY on a clean SSE stream-end
|
|
321
|
+
// (SseEndedWithoutTerminal) or a typed transport error (GislNetworkError).
|
|
322
|
+
// Everything else — timeout, abort, API error, an onProgress callback
|
|
323
|
+
// throw, anything unexpected — MUST propagate; re-issuing the same doomed
|
|
324
|
+
// request via poll would mask the real failure.
|
|
325
|
+
if (!(err instanceof SseEndedWithoutTerminal || err instanceof GislNetworkError)) {
|
|
320
326
|
throw err;
|
|
321
|
-
|
|
327
|
+
}
|
|
328
|
+
// Genuine SSE stream-end / transport error — fall through to poll fallback.
|
|
322
329
|
}
|
|
323
330
|
}
|
|
324
331
|
return await _pollToTerminal(this.client, args);
|
|
@@ -425,6 +432,20 @@ const TERMINAL_STATUS = new Set([
|
|
|
425
432
|
'expired',
|
|
426
433
|
'paused_insufficient_credits',
|
|
427
434
|
]);
|
|
435
|
+
/**
|
|
436
|
+
* Internal marker (TDqmkWpX): tags an error thrown by the caller's `onProgress`
|
|
437
|
+
* callback so the mid-stream transport-error wrap in {@link _consumeSseToTerminal}
|
|
438
|
+
* cannot mistake it for a transport failure (a callback that throws a `TypeError`
|
|
439
|
+
* would otherwise be wrapped as `GislNetworkError` → masked by poll-fallback).
|
|
440
|
+
* The inner catch unwraps it and rethrows the ORIGINAL `cause`, so the caller
|
|
441
|
+
* sees their own error and the run never silently succeeds.
|
|
442
|
+
*/
|
|
443
|
+
class _OnProgressThrew {
|
|
444
|
+
cause;
|
|
445
|
+
constructor(cause) {
|
|
446
|
+
this.cause = cause;
|
|
447
|
+
}
|
|
448
|
+
}
|
|
428
449
|
/** @internal — exported for reuse by `merge.ts` (T3) and future builders. */
|
|
429
450
|
export async function _consumeSseToTerminal(client, args) {
|
|
430
451
|
const remainingMs = args.deadline - Date.now();
|
|
@@ -460,6 +481,13 @@ export async function _consumeSseToTerminal(client, args) {
|
|
|
460
481
|
if (deadlineExpired && err instanceof DOMException && err.name === 'AbortError') {
|
|
461
482
|
throw new GislTimeoutError(`Workflow ${args.workflowId} did not complete before maxWait deadline`);
|
|
462
483
|
}
|
|
484
|
+
// TDqmkWpX: a genuine connect-phase TRANSPORT failure surfaces as a raw
|
|
485
|
+
// `TypeError` from `fetch` (DNS/TCP/TLS) — wrap it as a typed
|
|
486
|
+
// GislNetworkError so the await-terminal callers poll-fallback on it
|
|
487
|
+
// (and ONLY on it / a clean stream-end), never on an onProgress throw.
|
|
488
|
+
if (err instanceof TypeError) {
|
|
489
|
+
throw new GislNetworkError(`SSE connect to workflow ${args.workflowId} events failed: ${err.message}`);
|
|
490
|
+
}
|
|
463
491
|
throw err;
|
|
464
492
|
}
|
|
465
493
|
// for-await also throws if the iterator's .next() rejects (e.g. the
|
|
@@ -490,7 +518,15 @@ export async function _consumeSseToTerminal(client, args) {
|
|
|
490
518
|
? { phaseTotalInputs: data.phaseTotalInputs }
|
|
491
519
|
: {}),
|
|
492
520
|
};
|
|
493
|
-
|
|
521
|
+
// TDqmkWpX: an onProgress callback throw (ANY type, incl. TypeError)
|
|
522
|
+
// must propagate, never be mistaken for a transport failure. Tag it so
|
|
523
|
+
// the mid-stream TypeError wrap in the catch below skips it.
|
|
524
|
+
try {
|
|
525
|
+
args.onProgress(proj);
|
|
526
|
+
}
|
|
527
|
+
catch (cbErr) {
|
|
528
|
+
throw new _OnProgressThrew(cbErr);
|
|
529
|
+
}
|
|
494
530
|
}
|
|
495
531
|
if (event.event === SseEventType.workflow_completed ||
|
|
496
532
|
event.event === SseEventType.workflow_failed ||
|
|
@@ -511,17 +547,33 @@ export async function _consumeSseToTerminal(client, args) {
|
|
|
511
547
|
if (deadlineExpired) {
|
|
512
548
|
throw new GislTimeoutError(`Workflow ${args.workflowId} did not complete before maxWait deadline`);
|
|
513
549
|
}
|
|
514
|
-
// Otherwise it was a clean server-side close — fall back to poll.
|
|
515
|
-
|
|
550
|
+
// Otherwise it was a clean server-side close — fall back to poll. TDqmkWpX:
|
|
551
|
+
// a sealed marker (not a bare Error) so callers poll ONLY on this + a typed
|
|
552
|
+
// transport error, never on an onProgress callback throw.
|
|
553
|
+
throw new SseEndedWithoutTerminal();
|
|
516
554
|
}
|
|
517
555
|
catch (innerErr) {
|
|
518
|
-
//
|
|
519
|
-
//
|
|
556
|
+
// TDqmkWpX: an onProgress callback throw was tagged so it is NEVER treated
|
|
557
|
+
// as a transport failure — unwrap and rethrow the ORIGINAL cause so it
|
|
558
|
+
// propagates to the caller (never masked by a poll retry), even when the
|
|
559
|
+
// callback threw a TypeError.
|
|
560
|
+
if (innerErr instanceof _OnProgressThrew) {
|
|
561
|
+
throw innerErr.cause;
|
|
562
|
+
}
|
|
563
|
+
// If deadline expired and the iterator rejected with AbortError, surface
|
|
564
|
+
// as GislTimeoutError.
|
|
520
565
|
if (deadlineExpired &&
|
|
521
566
|
innerErr instanceof DOMException &&
|
|
522
567
|
innerErr.name === 'AbortError') {
|
|
523
568
|
throw new GislTimeoutError(`Workflow ${args.workflowId} did not complete before maxWait deadline`);
|
|
524
569
|
}
|
|
570
|
+
// A genuine mid-stream TRANSPORT failure (reader disconnect) surfaces as a
|
|
571
|
+
// raw `TypeError` from the iterator — wrap as GislNetworkError so callers
|
|
572
|
+
// poll-fallback. (An onProgress throw was already handled above, so a
|
|
573
|
+
// TypeError here is unambiguously transport.)
|
|
574
|
+
if (innerErr instanceof TypeError) {
|
|
575
|
+
throw new GislNetworkError(`SSE stream for workflow ${args.workflowId} failed mid-stream: ${innerErr.message}`);
|
|
576
|
+
}
|
|
525
577
|
throw innerErr;
|
|
526
578
|
}
|
|
527
579
|
}
|
package/dist/client.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import type { AudioWatermarkDecodeRequest, AudioWatermarkDecodeResponse, ExternalImportCreatedResponse, ExternalImportRequest, LoginUserRequest, LoginUser200ResponseData, ContactRequest, CreditsBalanceResponse, CreditsUsageResponse, UploadResponse, UploadProbeResponse, WorkflowCancelResponse, WorkflowCreateResponse, WorkflowResumeResponse, WorkflowStatusResponse, WorkflowDownloadResponse, MetadataResponse, RetryResponse } from '@giveitsmaller/contracts/openapi';
|
|
2
|
-
import type { CreditsUsageOptions, GetSchemaOptions, GetSchemaResult, GislClientConfig, GislSseEvent, PreflightClipsResult, UploadOptions, WaitOptions, WorkflowCreatePayload, _Sdk3HandCodedKeepaliveResult, _Sdk3HandCodedMultipartStatusResult, _Sdk3HandCodedPresignPartsResult } from './types.js';
|
|
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';
|
|
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 {
|
|
@@ -277,6 +277,12 @@ export declare class GislClient {
|
|
|
277
277
|
* until the external-import infrastructure ships. The method
|
|
278
278
|
* exists today so consumers can write the integration ahead of
|
|
279
279
|
* time.
|
|
280
|
+
*
|
|
281
|
+
* Auth-ownership: the import id this returns is owned by the
|
|
282
|
+
* authenticated caller that created it. Referencing it from a client
|
|
283
|
+
* with a different auth context 404s `upload_not_found` at
|
|
284
|
+
* workflow-create — same ownership rule as `fileInput.uploadId` (api
|
|
285
|
+
* PqpD9ySv).
|
|
280
286
|
*/
|
|
281
287
|
createExternalImport(payload: ExternalImportRequest): Promise<ExternalImportCreatedResponse>;
|
|
282
288
|
/**
|
|
@@ -324,4 +330,34 @@ export declare class GislClient {
|
|
|
324
330
|
* Server defaults: `limit=20`, `offset=0`. Most-recent-first.
|
|
325
331
|
*/
|
|
326
332
|
getCreditsUsage(options?: CreditsUsageOptions): Promise<CreditsUsageResponse>;
|
|
333
|
+
/**
|
|
334
|
+
* List the caller's workflows — a cursor-paginated, user-scoped summary
|
|
335
|
+
* list, most-recent-first. Each row is a lightweight {@link WorkflowSummary}
|
|
336
|
+
* (id / status / created_at + per-job type+status + a deliverable-output
|
|
337
|
+
* count); it does NOT inline per-op `result_metadata` or output details —
|
|
338
|
+
* drill in via {@link getWorkflowStatus} / {@link getWorkflowDownloads}.
|
|
339
|
+
*
|
|
340
|
+
* Auth is REQUIRED (the list is user-scoped; an anonymous caller gets a 401
|
|
341
|
+
* → `GislAuthError`). Walk pages by passing each response's `nextCursor` as
|
|
342
|
+
* the next call's `cursor` until `isTruncated` is false, or use
|
|
343
|
+
* {@link workflows} to auto-paginate. Mirrors the PHP
|
|
344
|
+
* `GislClient::listWorkflows`.
|
|
345
|
+
*/
|
|
346
|
+
listWorkflows(options?: ListWorkflowsOptions): Promise<WorkflowListResponse>;
|
|
347
|
+
/**
|
|
348
|
+
* Auto-paginating async iterator over ALL of the caller's workflows,
|
|
349
|
+
* yielding each {@link WorkflowSummary} most-recent-first across page
|
|
350
|
+
* boundaries — the ergonomic companion to {@link listWorkflows}. Walks
|
|
351
|
+
* `nextCursor` until the server reports `isTruncated: false`. Mirrors the
|
|
352
|
+
* PHP `workflows()` generator.
|
|
353
|
+
*
|
|
354
|
+
* ```ts
|
|
355
|
+
* for await (const wf of client.workflows()) {
|
|
356
|
+
* console.log(wf.workflowId, wf.status);
|
|
357
|
+
* }
|
|
358
|
+
* ```
|
|
359
|
+
*/
|
|
360
|
+
workflows(options?: {
|
|
361
|
+
limit?: number;
|
|
362
|
+
}): AsyncGenerator<WorkflowSummary, void, undefined>;
|
|
327
363
|
}
|
package/dist/client.js
CHANGED
|
@@ -1,7 +1,11 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
1
|
+
// node:fs/promises + node:path are reached only through `./node-fs.js`, which
|
|
2
|
+
// the package.json `browser` field swaps for a stub in browser bundles — so the
|
|
3
|
+
// SDK entry graph stays browser-safe (a browser caller uploads a Blob via
|
|
4
|
+
// blobByteSource, which never touches these). Kept as a STATIC import (not a
|
|
5
|
+
// dynamic one) so `vi.mock('node:fs/promises')` still intercepts it in tests.
|
|
6
|
+
import { open, stat, basename } from './node-fs.js';
|
|
7
|
+
import { AudioWatermarkDecodeRequestToJSON, AudioWatermarkDecodeResponseFromJSON, ExternalImportCreatedResponseFromJSON, ExternalImportRequestToJSON, LoginUser200ResponseDataFromJSON, CreditsBalanceResponseFromJSON, CreditsUsageResponseFromJSON, UploadResponseFromJSON, UploadProbeResponseFromJSON, MultipartInitiateResponseFromJSON, MultipartInitiateRequestMetadataHintToJSON, MultipartCompleteResponseFromJSON, MultipartCompleteRequestToJSON, WorkflowCancelResponseFromJSON, WorkflowCreateResponseFromJSON, WorkflowResumeResponseFromJSON, WorkflowStatusResponseFromJSON, WorkflowListResponseFromJSON, WorkflowDownloadResponseFromJSON, MetadataResponseFromJSON, OperationsSchemaResponseFromJSON, RetryResponseFromJSON, WorkflowStatus, AuthErrorResponseFromJSON, AuthErrorType, AuthRejectionEnvelopeFromJSON, AuthRejectionEnvelopeErrorTypeEnum, BalanceExhaustedResponseFromJSON, BalanceExhaustedResponseRequiredActionEnum, FeatureNotAvailableResponseFromJSON, FeatureTierRestrictedResponseFromJSON, TierRestrictionKind, TierRestrictionResponseFromJSON, UserTier, WorkflowExpiredResponseFromJSON, ProbePendingResponseFromJSON, UploadSizeExceedsTierResponseFromJSON, UploadDurationExceedsTierResponseFromJSON, UploadConstraintsAppliedProcessingClassPreAssignmentEnum, UploadThresholdsSingleShotMaxBytesEnum, UploadThresholdsMultipartChunkSizeEnum, UploadThresholdsMultipartConcurrencyDefaultEnum, } from '@giveitsmaller/contracts/openapi';
|
|
8
|
+
import { GislAbortError, GislApiError, GislAuthError, GislAuthRejectionError, GislBalanceExhaustedError, GislError, GislFeatureNotAvailableError, GislFeatureTierRestrictedError, GislMultipartPartCountError, GislMultipartPartError, GislMultipartSessionNotFoundError, GislMultipartSessionOwnershipError, GislMultipartSessionAuthRequiredError, GislTierRestrictedError, GislTimeoutError, GislProbePendingError, GislUploadCapExceededError, GislValidationError, GislWorkflowExpiredError, } from './errors.js';
|
|
5
9
|
import { parseSseStream } from './sse.js';
|
|
6
10
|
const DEFAULT_TIMEOUT_MS = 30_000;
|
|
7
11
|
// SDK-internal aliases derived from the contract-pinned UploadThresholds enums
|
|
@@ -66,6 +70,19 @@ const TERMINAL_STATUSES = new Set([
|
|
|
66
70
|
WorkflowStatus.expired,
|
|
67
71
|
WorkflowStatus.paused_insufficient_credits,
|
|
68
72
|
]);
|
|
73
|
+
// Flatten a `Headers` object into a plain `Record<string,string>` for
|
|
74
|
+
// attaching to a thrown `GislApiError`. `Headers.forEach` yields LOWERCASED
|
|
75
|
+
// keys (HTTP header names are case-insensitive per RFC 9110) and collapses
|
|
76
|
+
// any multi-value header (e.g. `set-cookie`) into a single comma-joined
|
|
77
|
+
// value — the record is for content-language / vary / x-request-id reads,
|
|
78
|
+
// not cookies.
|
|
79
|
+
function headersToRecord(headers) {
|
|
80
|
+
const r = {};
|
|
81
|
+
headers.forEach((v, k) => {
|
|
82
|
+
r[k] = v;
|
|
83
|
+
});
|
|
84
|
+
return r;
|
|
85
|
+
}
|
|
69
86
|
function isValidationDetails(value) {
|
|
70
87
|
return (Array.isArray(value) &&
|
|
71
88
|
value.length > 0 &&
|
|
@@ -294,6 +311,18 @@ export class GislClient {
|
|
|
294
311
|
if (config.apiKey) {
|
|
295
312
|
this.headers['Authorization'] = `Bearer ${config.apiKey}`;
|
|
296
313
|
}
|
|
314
|
+
// The dedicated `locale` option wins over any `Accept-Language` the caller
|
|
315
|
+
// passed via `headers` (mirrors apiKey -> Authorization). Drop any caller
|
|
316
|
+
// variant case-insensitively first so the request never carries two keys
|
|
317
|
+
// (e.g. `accept-language` + `Accept-Language`) with undefined precedence.
|
|
318
|
+
if (config.locale) {
|
|
319
|
+
for (const key of Object.keys(this.headers)) {
|
|
320
|
+
if (key.toLowerCase() === 'accept-language') {
|
|
321
|
+
delete this.headers[key];
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
this.headers['Accept-Language'] = config.locale;
|
|
325
|
+
}
|
|
297
326
|
}
|
|
298
327
|
// -----------------------------------------------------------------------
|
|
299
328
|
// Internal HTTP
|
|
@@ -369,11 +398,18 @@ export class GislClient {
|
|
|
369
398
|
return this.handleResponse(response, path, opts.deserialize);
|
|
370
399
|
}
|
|
371
400
|
async handleResponse(response, path, deserialize) {
|
|
401
|
+
// Surface the response headers (lowercased Record) and the resolved
|
|
402
|
+
// `Content-Language` on every GislApiError thrown from this handler.
|
|
403
|
+
const responseHeaders = headersToRecord(response.headers);
|
|
404
|
+
const contentLanguage = response.headers.get('content-language') ?? undefined;
|
|
372
405
|
const contentType = (response.headers.get('content-type') ?? '').toLowerCase();
|
|
373
406
|
const isJsonContent = contentType.includes('application/json') || contentType.includes('+json');
|
|
374
407
|
if (!isJsonContent) {
|
|
375
408
|
if (!response.ok) {
|
|
376
|
-
throw new GislApiError(response.status, 'Non-JSON response', path
|
|
409
|
+
throw new GislApiError(response.status, 'Non-JSON response', path, undefined, {
|
|
410
|
+
responseHeaders,
|
|
411
|
+
contentLanguage,
|
|
412
|
+
});
|
|
377
413
|
}
|
|
378
414
|
return undefined;
|
|
379
415
|
}
|
|
@@ -386,7 +422,10 @@ export class GislClient {
|
|
|
386
422
|
json = await response.json();
|
|
387
423
|
}
|
|
388
424
|
catch {
|
|
389
|
-
throw new GislApiError(response.status, 'Invalid JSON response', path
|
|
425
|
+
throw new GislApiError(response.status, 'Invalid JSON response', path, undefined, {
|
|
426
|
+
responseHeaders,
|
|
427
|
+
contentLanguage,
|
|
428
|
+
});
|
|
390
429
|
}
|
|
391
430
|
// Standard envelope: { success, data } or { success, error, details }
|
|
392
431
|
if (!response.ok || json.success === false) {
|
|
@@ -398,6 +437,8 @@ export class GislClient {
|
|
|
398
437
|
messageKey: json.message_key,
|
|
399
438
|
locale: json.locale,
|
|
400
439
|
messageParams: json.message_params,
|
|
440
|
+
responseHeaders,
|
|
441
|
+
contentLanguage,
|
|
401
442
|
};
|
|
402
443
|
// Human-readable text comes from `message` (the I26 localised field).
|
|
403
444
|
// `error` is the stable, never-localised SCREAMING_SNAKE machine code —
|
|
@@ -543,6 +584,18 @@ export class GislClient {
|
|
|
543
584
|
if (status === 422 && errorType === 'FILE_TOO_LARGE_FOR_MULTIPART') {
|
|
544
585
|
throw new GislUploadCapExceededError(status, errorMessage, 'cap_v2_multipart', undefined, path, i18n);
|
|
545
586
|
}
|
|
587
|
+
// 422 auth-side-effect domain rejection (per contracts ADR-0019).
|
|
588
|
+
// Flat AuthRejectionEnvelope — NO `details[]` — on register /
|
|
589
|
+
// verify-email / api-keys (`error_type: unprocessable_entity`) and
|
|
590
|
+
// profile PATCH email-unchanged (`error_type: email_same`). The
|
|
591
|
+
// `validation_error` branch of the same auth-422 `oneOf` carries
|
|
592
|
+
// `details[]` and is already routed to GislValidationError by the
|
|
593
|
+
// shape-based branch above. Mirrors
|
|
594
|
+
// `packages/php/src/GislClient.php` GislAuthRejectionError branch.
|
|
595
|
+
if (status === 422 &&
|
|
596
|
+
isInEnum(errorType, AuthRejectionEnvelopeErrorTypeEnum)) {
|
|
597
|
+
tryThrowStructured(AuthRejectionEnvelopeFromJSON, GislAuthRejectionError, (p) => isInEnum(p.errorType, AuthRejectionEnvelopeErrorTypeEnum));
|
|
598
|
+
}
|
|
546
599
|
throw new GislApiError(status, errorMessage, path, json.details, { ...i18n, payload: json });
|
|
547
600
|
}
|
|
548
601
|
const data = json.data ?? json;
|
|
@@ -1756,7 +1809,12 @@ export class GislClient {
|
|
|
1756
1809
|
catch {
|
|
1757
1810
|
// Non-JSON body — keep generic message.
|
|
1758
1811
|
}
|
|
1759
|
-
throw
|
|
1812
|
+
// This throw is OUTSIDE handleResponse (rawResponse:true / 304 path), so
|
|
1813
|
+
// build the response-header surface from the in-scope `response` here.
|
|
1814
|
+
throw new GislApiError(response.status, errorMessage, path, undefined, {
|
|
1815
|
+
responseHeaders: headersToRecord(response.headers),
|
|
1816
|
+
contentLanguage: response.headers.get('content-language') ?? undefined,
|
|
1817
|
+
});
|
|
1760
1818
|
}
|
|
1761
1819
|
const raw = await response.json();
|
|
1762
1820
|
const data = OperationsSchemaResponseFromJSON(raw);
|
|
@@ -1874,6 +1932,12 @@ export class GislClient {
|
|
|
1874
1932
|
* until the external-import infrastructure ships. The method
|
|
1875
1933
|
* exists today so consumers can write the integration ahead of
|
|
1876
1934
|
* time.
|
|
1935
|
+
*
|
|
1936
|
+
* Auth-ownership: the import id this returns is owned by the
|
|
1937
|
+
* authenticated caller that created it. Referencing it from a client
|
|
1938
|
+
* with a different auth context 404s `upload_not_found` at
|
|
1939
|
+
* workflow-create — same ownership rule as `fileInput.uploadId` (api
|
|
1940
|
+
* PqpD9ySv).
|
|
1877
1941
|
*/
|
|
1878
1942
|
async createExternalImport(payload) {
|
|
1879
1943
|
return this.request('POST', '/api/external-imports', {
|
|
@@ -1979,4 +2043,64 @@ export class GislClient {
|
|
|
1979
2043
|
deserialize: CreditsUsageResponseFromJSON,
|
|
1980
2044
|
});
|
|
1981
2045
|
}
|
|
2046
|
+
/**
|
|
2047
|
+
* List the caller's workflows — a cursor-paginated, user-scoped summary
|
|
2048
|
+
* list, most-recent-first. Each row is a lightweight {@link WorkflowSummary}
|
|
2049
|
+
* (id / status / created_at + per-job type+status + a deliverable-output
|
|
2050
|
+
* count); it does NOT inline per-op `result_metadata` or output details —
|
|
2051
|
+
* drill in via {@link getWorkflowStatus} / {@link getWorkflowDownloads}.
|
|
2052
|
+
*
|
|
2053
|
+
* Auth is REQUIRED (the list is user-scoped; an anonymous caller gets a 401
|
|
2054
|
+
* → `GislAuthError`). Walk pages by passing each response's `nextCursor` as
|
|
2055
|
+
* the next call's `cursor` until `isTruncated` is false, or use
|
|
2056
|
+
* {@link workflows} to auto-paginate. Mirrors the PHP
|
|
2057
|
+
* `GislClient::listWorkflows`.
|
|
2058
|
+
*/
|
|
2059
|
+
async listWorkflows(options = {}) {
|
|
2060
|
+
const params = new URLSearchParams();
|
|
2061
|
+
// Guard `null` too — runtime (untyped) callers may pass `cursor: null`,
|
|
2062
|
+
// which must be omitted, not serialised as the literal string "null".
|
|
2063
|
+
if (options.cursor !== undefined && options.cursor !== null && options.cursor !== '') {
|
|
2064
|
+
params.set('cursor', options.cursor);
|
|
2065
|
+
}
|
|
2066
|
+
if (options.limit !== undefined)
|
|
2067
|
+
params.set('limit', String(options.limit));
|
|
2068
|
+
const query = params.toString();
|
|
2069
|
+
// String concatenation (not template) so the contract-drift path scanner
|
|
2070
|
+
// picks up the literal path. See getCreditsUsage for the same pattern.
|
|
2071
|
+
const path = query.length > 0 ? '/api/workflows' + '?' + query : '/api/workflows';
|
|
2072
|
+
return this.request('GET', path, {
|
|
2073
|
+
deserialize: WorkflowListResponseFromJSON,
|
|
2074
|
+
});
|
|
2075
|
+
}
|
|
2076
|
+
/**
|
|
2077
|
+
* Auto-paginating async iterator over ALL of the caller's workflows,
|
|
2078
|
+
* yielding each {@link WorkflowSummary} most-recent-first across page
|
|
2079
|
+
* boundaries — the ergonomic companion to {@link listWorkflows}. Walks
|
|
2080
|
+
* `nextCursor` until the server reports `isTruncated: false`. Mirrors the
|
|
2081
|
+
* PHP `workflows()` generator.
|
|
2082
|
+
*
|
|
2083
|
+
* ```ts
|
|
2084
|
+
* for await (const wf of client.workflows()) {
|
|
2085
|
+
* console.log(wf.workflowId, wf.status);
|
|
2086
|
+
* }
|
|
2087
|
+
* ```
|
|
2088
|
+
*/
|
|
2089
|
+
async *workflows(options = {}) {
|
|
2090
|
+
let cursor;
|
|
2091
|
+
for (;;) {
|
|
2092
|
+
const page = await this.listWorkflows({ cursor, limit: options.limit });
|
|
2093
|
+
for (const summary of page.workflows) {
|
|
2094
|
+
yield summary;
|
|
2095
|
+
}
|
|
2096
|
+
const next = page.nextCursor;
|
|
2097
|
+
// Stop on a final page, an empty cursor, OR a non-advancing cursor — a
|
|
2098
|
+
// server that repeats the same cursor on a truncated page would
|
|
2099
|
+
// otherwise loop forever, refetching + re-yielding the same rows.
|
|
2100
|
+
if (!page.isTruncated || next === undefined || next === null || next === '' || next === cursor) {
|
|
2101
|
+
return;
|
|
2102
|
+
}
|
|
2103
|
+
cursor = next;
|
|
2104
|
+
}
|
|
2105
|
+
}
|
|
1982
2106
|
}
|
package/dist/credentials.js
CHANGED
|
@@ -137,8 +137,10 @@ function defaultProfilePath() {
|
|
|
137
137
|
async function readProfile(path, profileName) {
|
|
138
138
|
let raw;
|
|
139
139
|
try {
|
|
140
|
-
// Lazy import keeps `node:fs` out of browser bundles.
|
|
141
|
-
|
|
140
|
+
// Lazy import keeps `node:fs` out of browser bundles. webpackIgnore stops
|
|
141
|
+
// webpack bundling it for browser targets; Vite externalises node: builtins
|
|
142
|
+
// itself (no @vite-ignore — that would also bypass test mocks of node:fs).
|
|
143
|
+
const fs = await import(/* webpackIgnore: true */ 'node:fs/promises');
|
|
142
144
|
raw = await fs.readFile(path, 'utf8');
|
|
143
145
|
}
|
|
144
146
|
catch (err) {
|
|
@@ -34,7 +34,7 @@
|
|
|
34
34
|
// plan's canonical example and the contract minimum.
|
|
35
35
|
// On a parse, the resolver also writes `encoding_mode='target_size'`
|
|
36
36
|
// to the wire; conversely if `crf` is explicit, `encoding_mode='crf'`.
|
|
37
|
-
import {
|
|
37
|
+
import { sha256Hex } from '../sha256.js';
|
|
38
38
|
import { GislConfigError } from '../errors.js';
|
|
39
39
|
import { ImageCompressPresetOptions, AudioCompressPresetOptions, VideoCompressPresetOptions, DocumentPdfCompressPresetOptions, DocumentOfficeCompressPresetOptions, DocumentOdfCompressPresetOptions, DocumentEpubCompressPresetOptions, } from './presets/index.js';
|
|
40
40
|
/** Bumped on any change to a `*PresetOptions.shippedDefaultsFor(...)` cell value. */
|
|
@@ -52,7 +52,6 @@ export const PRESET_VERSION = '1.0';
|
|
|
52
52
|
// safe path.
|
|
53
53
|
const WIRE_ALIASES = Object.freeze({
|
|
54
54
|
iccProfile: 'icc_profile',
|
|
55
|
-
autoOrient: 'auto_orient',
|
|
56
55
|
outputFormat: 'output_format',
|
|
57
56
|
sampleRate: 'sample_rate',
|
|
58
57
|
audioCodec: 'audio_codec',
|
|
@@ -236,7 +235,7 @@ function presetDefaultsCellRecord(defaults, media, op, optimize) {
|
|
|
236
235
|
// for clearly-typed plain objects whose key set only intersects with a
|
|
237
236
|
// non-matching media.
|
|
238
237
|
const MEDIA_FIELDS = Object.freeze({
|
|
239
|
-
image: new Set(['mode', 'quality', '
|
|
238
|
+
image: new Set(['mode', 'quality', 'metadata', 'iccProfile', 'progressive', 'outputFormat']),
|
|
240
239
|
audio: new Set(['bitrate', 'channels', 'sampleRate', 'normalize']),
|
|
241
240
|
video: new Set(['codec', 'targetSize', 'crf', 'preset', 'width', 'height', 'fit', 'fps', 'faststart', 'audioCodec', 'audioBitrate']),
|
|
242
241
|
document_pdf: new Set(['profile', 'colorspace', 'flattenForms']),
|
|
@@ -302,7 +301,7 @@ function mergeLayer(acc, layer, source) {
|
|
|
302
301
|
// Validations on the merged wire payload
|
|
303
302
|
// ---------------------------------------------------------------------------
|
|
304
303
|
const KNOWN_WIRE_FIELDS = Object.freeze({
|
|
305
|
-
image: new Set(['mode', 'quality', '
|
|
304
|
+
image: new Set(['mode', 'quality', 'metadata', 'icc_profile', 'progressive', 'output_format']),
|
|
306
305
|
audio: new Set(['bitrate', 'channels', 'sample_rate', 'normalize', 'trim_start', 'trim_end']),
|
|
307
306
|
video: new Set(['codec', 'encoding_mode', 'crf', 'target_size_bytes', 'preset', 'width', 'height', 'fit', 'fps', 'faststart', 'audio_codec', 'audio_bitrate', 'trim_start', 'trim_end']),
|
|
308
307
|
document_pdf: new Set(['profile', 'colorspace', 'pages', 'flatten_forms']),
|
|
@@ -417,7 +416,7 @@ function computePresetConfigHash(clientDefault, scopedDefault, callPresetOverrid
|
|
|
417
416
|
scopedDefault: scopedDefault ?? null,
|
|
418
417
|
callPresetOverride: callPresetOverride ?? null,
|
|
419
418
|
});
|
|
420
|
-
return `sha256:${
|
|
419
|
+
return `sha256:${sha256Hex(canonical)}`;
|
|
421
420
|
}
|
|
422
421
|
// ---------------------------------------------------------------------------
|
|
423
422
|
// Main entry point
|
|
@@ -1,25 +1,17 @@
|
|
|
1
|
-
import { ImageMode,
|
|
1
|
+
import { ImageMode, ImageMetadataPolicy, IccProfilePolicy, ImageFormat, OptimizeFor } from '../../generated/sdk_spec/enums.js';
|
|
2
2
|
export interface ImageCompressPresetOptionsInput {
|
|
3
3
|
readonly mode?: ImageMode;
|
|
4
4
|
readonly quality?: number;
|
|
5
|
-
readonly width?: number;
|
|
6
|
-
readonly height?: number;
|
|
7
|
-
readonly fit?: ImageFit;
|
|
8
5
|
readonly metadata?: ImageMetadataPolicy;
|
|
9
6
|
readonly iccProfile?: IccProfilePolicy;
|
|
10
|
-
readonly autoOrient?: boolean;
|
|
11
7
|
readonly progressive?: boolean;
|
|
12
8
|
readonly outputFormat?: ImageFormat;
|
|
13
9
|
}
|
|
14
10
|
export declare class ImageCompressPresetOptions {
|
|
15
11
|
readonly mode?: ImageMode;
|
|
16
12
|
readonly quality?: number;
|
|
17
|
-
readonly width?: number;
|
|
18
|
-
readonly height?: number;
|
|
19
|
-
readonly fit?: ImageFit;
|
|
20
13
|
readonly metadata?: ImageMetadataPolicy;
|
|
21
14
|
readonly iccProfile?: IccProfilePolicy;
|
|
22
|
-
readonly autoOrient?: boolean;
|
|
23
15
|
readonly progressive?: boolean;
|
|
24
16
|
readonly outputFormat?: ImageFormat;
|
|
25
17
|
private constructor();
|
|
@@ -7,22 +7,19 @@
|
|
|
7
7
|
// values ARE the wire backing values — so the leaf DTO is wire-compatible
|
|
8
8
|
// once the resolver (T4b) snake_cases the property names.
|
|
9
9
|
//
|
|
10
|
-
// Field set per
|
|
11
|
-
// (mode, quality,
|
|
12
|
-
//
|
|
13
|
-
//
|
|
14
|
-
// per-call
|
|
10
|
+
// Field set (6) per EsD1hs5u / contracts v2.60.0: image =
|
|
11
|
+
// (mode, quality, metadata, iccProfile, progressive, outputFormat).
|
|
12
|
+
// `width`/`height`/`fit`/`autoOrient` were REMOVED — the image-compress
|
|
13
|
+
// worker never resized (resize-fit lives on thumbnail/convert; video keeps
|
|
14
|
+
// its own fit). Trim / per-call knobs are deliberately excluded — they
|
|
15
|
+
// belong on the per-call argument shape, not the preset cell.
|
|
15
16
|
import { shippedDefaultsFor as f3ShippedDefaultsFor } from '../../generated/sdk_spec/presets.js';
|
|
16
17
|
import { translateEnum } from './_translate.js';
|
|
17
18
|
export class ImageCompressPresetOptions {
|
|
18
19
|
mode;
|
|
19
20
|
quality;
|
|
20
|
-
width;
|
|
21
|
-
height;
|
|
22
|
-
fit;
|
|
23
21
|
metadata;
|
|
24
22
|
iccProfile;
|
|
25
|
-
autoOrient;
|
|
26
23
|
progressive;
|
|
27
24
|
outputFormat;
|
|
28
25
|
constructor(input) {
|
|
@@ -30,18 +27,10 @@ export class ImageCompressPresetOptions {
|
|
|
30
27
|
this.mode = input.mode;
|
|
31
28
|
if (input.quality !== undefined)
|
|
32
29
|
this.quality = input.quality;
|
|
33
|
-
if (input.width !== undefined)
|
|
34
|
-
this.width = input.width;
|
|
35
|
-
if (input.height !== undefined)
|
|
36
|
-
this.height = input.height;
|
|
37
|
-
if (input.fit !== undefined)
|
|
38
|
-
this.fit = input.fit;
|
|
39
30
|
if (input.metadata !== undefined)
|
|
40
31
|
this.metadata = input.metadata;
|
|
41
32
|
if (input.iccProfile !== undefined)
|
|
42
33
|
this.iccProfile = input.iccProfile;
|
|
43
|
-
if (input.autoOrient !== undefined)
|
|
44
|
-
this.autoOrient = input.autoOrient;
|
|
45
34
|
if (input.progressive !== undefined)
|
|
46
35
|
this.progressive = input.progressive;
|
|
47
36
|
if (input.outputFormat !== undefined)
|
|
@@ -74,18 +63,10 @@ export class ImageCompressPresetOptions {
|
|
|
74
63
|
mut.mode = translateEnum('ImageMode', cell.mode);
|
|
75
64
|
if ('quality' in cell)
|
|
76
65
|
mut.quality = cell.quality;
|
|
77
|
-
if ('width' in cell)
|
|
78
|
-
mut.width = cell.width;
|
|
79
|
-
if ('height' in cell)
|
|
80
|
-
mut.height = cell.height;
|
|
81
|
-
if ('fit' in cell)
|
|
82
|
-
mut.fit = translateEnum('ImageFit', cell.fit);
|
|
83
66
|
if ('metadata' in cell)
|
|
84
67
|
mut.metadata = translateEnum('ImageMetadataPolicy', cell.metadata);
|
|
85
68
|
if ('iccProfile' in cell)
|
|
86
69
|
mut.iccProfile = translateEnum('IccProfilePolicy', cell.iccProfile);
|
|
87
|
-
if ('autoOrient' in cell)
|
|
88
|
-
mut.autoOrient = cell.autoOrient;
|
|
89
70
|
if ('progressive' in cell)
|
|
90
71
|
mut.progressive = cell.progressive;
|
|
91
72
|
if ('outputFormat' in cell)
|
|
@@ -13,7 +13,7 @@ export { DocumentPdfCompressPresetOptions, type DocumentPdfCompressPresetOptions
|
|
|
13
13
|
export { DocumentOfficeCompressPresetOptions, type DocumentOfficeCompressPresetOptionsInput, } from './document_office_compress.js';
|
|
14
14
|
export { DocumentOdfCompressPresetOptions, type DocumentOdfCompressPresetOptionsInput, } from './document_odf_compress.js';
|
|
15
15
|
export { DocumentEpubCompressPresetOptions, type DocumentEpubCompressPresetOptionsInput, } from './document_epub_compress.js';
|
|
16
|
-
export { OptimizeFor, ImageMode,
|
|
16
|
+
export { OptimizeFor, ImageMode, ImageMetadataPolicy, IccProfilePolicy, ImageFormat, VideoCodec, VideoPreset, VideoFit, AudioBitrate, AudioCodec, AudioSampleRate, PdfProfile, PdfColorspace, } from '../../generated/sdk_spec/enums.js';
|
|
17
17
|
/** Supported media×op pairs for preset cells in T4a. Compress-only. */
|
|
18
18
|
export type PresetMedia = 'image' | 'audio' | 'video' | 'document_pdf' | 'document_office' | 'document_odf' | 'document_epub';
|
|
19
19
|
export type PresetOp = 'compress';
|
|
@@ -41,7 +41,7 @@ export { DocumentOfficeCompressPresetOptions, } from './document_office_compress
|
|
|
41
41
|
export { DocumentOdfCompressPresetOptions, } from './document_odf_compress.js';
|
|
42
42
|
export { DocumentEpubCompressPresetOptions, } from './document_epub_compress.js';
|
|
43
43
|
// Re-export ergonomic enums for callers (single canonical path).
|
|
44
|
-
export { OptimizeFor, ImageMode,
|
|
44
|
+
export { OptimizeFor, ImageMode, ImageMetadataPolicy, IccProfilePolicy, ImageFormat, VideoCodec, VideoPreset, VideoFit, AudioBitrate, AudioCodec, AudioSampleRate, PdfProfile, PdfColorspace, } from '../../generated/sdk_spec/enums.js';
|
|
45
45
|
function cellKeyOf(media, op) {
|
|
46
46
|
return `${media}_${op}`;
|
|
47
47
|
}
|