@giveitsmaller/sdk 0.20.0 → 0.21.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/LICENSE +202 -0
- package/README.md +8 -15
- package/dist/_audit.js +2 -4
- package/dist/builder.d.ts +4 -4
- package/dist/builder.js +16 -16
- package/dist/client.d.ts +9 -6
- package/dist/client.js +90 -31
- package/dist/ergonomic/image_output_routes.d.ts +30 -0
- package/dist/ergonomic/image_output_routes.js +82 -11
- package/dist/ergonomic/preset_resolver.d.ts +2 -2
- package/dist/ergonomic/preset_resolver.js +6 -10
- package/dist/ergonomic/presets/index.d.ts +9 -8
- package/dist/ergonomic/presets/index.js +1 -9
- package/dist/errors.d.ts +48 -2
- package/dist/errors.js +54 -1
- package/dist/file-first.d.ts +81 -13
- package/dist/file-first.js +279 -64
- package/dist/generated/sdk_spec/enums.d.ts +0 -26
- package/dist/generated/sdk_spec/enums.js +0 -16
- package/dist/generated/sdk_spec/errors.d.ts +1 -1
- package/dist/generated/sdk_spec/errors.js +12 -0
- package/dist/generated/sdk_spec/presets.js +0 -14
- package/dist/generated/sdk_spec/version.d.ts +2 -2
- package/dist/generated/sdk_spec/version.js +2 -2
- package/dist/gisl.d.ts +21 -2
- package/dist/handle.d.ts +6 -1
- package/dist/handle.js +42 -13
- package/dist/index.core.d.ts +4 -4
- package/dist/index.core.js +2 -2
- package/dist/merge.js +2 -2
- package/dist/types.d.ts +11 -3
- package/dist/types.js +1 -0
- package/package.json +3 -3
- package/dist/ergonomic/presets/document_pdf_compress.d.ts +0 -12
- package/dist/ergonomic/presets/document_pdf_compress.js +0 -33
package/dist/client.js
CHANGED
|
@@ -4,8 +4,8 @@
|
|
|
4
4
|
// blobByteSource, which never touches these). Kept as a STATIC import (not a
|
|
5
5
|
// dynamic one) so `vi.mock('node:fs/promises')` still intercepts it in tests.
|
|
6
6
|
import { open, stat, basename } from './node-fs.js';
|
|
7
|
-
import { AudioWatermarkDecodeRequestToJSON, AudioWatermarkDecodeResponseFromJSON, ExternalImportCreatedResponseFromJSON, ExternalImportRequestToJSON, LoginUser200ResponseDataFromJSON, AccountLimitsFromJSON, 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';
|
|
7
|
+
import { AudioWatermarkDecodeRequestToJSON, AudioWatermarkDecodeResponseFromJSON, ExternalImportCreatedResponseFromJSON, ExternalImportRequestToJSON, LoginUser200ResponseDataFromJSON, AccountLimitsFromJSON, 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, LongFormConcurrencyLimitResponseFromJSON, 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, GislLongFormConcurrencyError, GislMultipartPartCountError, GislMultipartPartError, GislMultipartSessionNotFoundError, GislMultipartSessionOwnershipError, GislMultipartSessionAuthRequiredError, GislTierRestrictedError, GislTimeoutError, GislProbePendingError, GislUploadCapExceededError, GislValidationError, GislWorkflowExpiredError, } from './errors.js';
|
|
9
9
|
// The `Retry-After` millisecond parser lives in the shared retry-metadata
|
|
10
10
|
// module (extracted to break the client ↔ errors circular import); re-imported
|
|
11
11
|
// here so the retry-loop timing stays byte-identical.
|
|
@@ -59,7 +59,25 @@ const S3_MAX_MULTIPART_PARTS = 10_000;
|
|
|
59
59
|
// exists to prevent). codex review (high).
|
|
60
60
|
const RECOMMENDED_CHUNK_SIZE_MAX_BYTES = 104_857_600; // 100 MiB
|
|
61
61
|
const DEFAULT_POLL_INTERVAL_MS = 2_000;
|
|
62
|
-
const DEFAULT_POLL_TIMEOUT_MS =
|
|
62
|
+
const DEFAULT_POLL_TIMEOUT_MS = 600_000; // 10 min
|
|
63
|
+
/**
|
|
64
|
+
* Re-tag a bare per-request transport {@link GislTimeoutError} with the workflow
|
|
65
|
+
* it was scoped to, so a timed-out workflow read (status / downloads) stays
|
|
66
|
+
* recoverable. No-op for any other error and for a timeout that already carries
|
|
67
|
+
* an id. See `oYumKo6y`. (PHP transport failures surface as `GislNetworkError`,
|
|
68
|
+
* so this enrichment is TS-only.)
|
|
69
|
+
*/
|
|
70
|
+
function withWorkflowIdOnTimeout(err, workflowId) {
|
|
71
|
+
if (!(err instanceof GislTimeoutError) || err.workflowId !== undefined)
|
|
72
|
+
return err;
|
|
73
|
+
const enriched = new GislTimeoutError(err.message, workflowId);
|
|
74
|
+
// Preserve the original throw site + chain the cause — the enriched error is
|
|
75
|
+
// a re-tag, not a new failure, so diagnostics should still point at the
|
|
76
|
+
// transport timeout.
|
|
77
|
+
enriched.stack = err.stack;
|
|
78
|
+
enriched.cause = err;
|
|
79
|
+
return enriched;
|
|
80
|
+
}
|
|
63
81
|
// Anonymous-read capability header. An anonymous (null-owner) workflow create
|
|
64
82
|
// returns a one-time `cap` token (WorkflowCreateResponse.cap); the session-less
|
|
65
83
|
// caller passes it back on status/downloads/events reads via this header so the
|
|
@@ -98,6 +116,18 @@ function headersToRecord(headers) {
|
|
|
98
116
|
});
|
|
99
117
|
return r;
|
|
100
118
|
}
|
|
119
|
+
// Canonical human-readable fallback when a failure envelope carries NO `message`
|
|
120
|
+
// field. Kept IDENTICAL to the PHP SDK (`GislClient::fallbackErrorMessage`) so a
|
|
121
|
+
// message-absent error surfaces byte-identical `.message` text across SDKs
|
|
122
|
+
// (card U7MACpOj). Used by BOTH error-construction paths (`handleResponse` and
|
|
123
|
+
// the `getSchema` raw-response path) so they cannot drift. The machine code
|
|
124
|
+
// stays on `.errorCode` — this string is human DISPLAY text only; never parse
|
|
125
|
+
// it. `error` absent → `unknown_error` (PHP parity). Historically TS leaked the
|
|
126
|
+
// SCREAMING_SNAKE `error` code as `.message`, diverging from PHP's synthetic
|
|
127
|
+
// sentence; this unifies them on the human-shaped form.
|
|
128
|
+
function fallbackErrorMessage(status, errorCode) {
|
|
129
|
+
return `Request failed with status ${status} (${errorCode ?? 'unknown_error'}).`;
|
|
130
|
+
}
|
|
101
131
|
function isValidationDetails(value) {
|
|
102
132
|
return (Array.isArray(value) &&
|
|
103
133
|
value.length > 0 &&
|
|
@@ -484,13 +514,16 @@ export class GislClient {
|
|
|
484
514
|
};
|
|
485
515
|
// Human-readable text comes from `message` (the I26 localised field).
|
|
486
516
|
// `error` is the stable, never-localised SCREAMING_SNAKE machine code —
|
|
487
|
-
// NOT display text
|
|
488
|
-
//
|
|
489
|
-
//
|
|
490
|
-
//
|
|
491
|
-
//
|
|
517
|
+
// NOT display text (surfacing it as `.message` regressed consumers that
|
|
518
|
+
// render the human string, x9Lbf6uy). When `message` is absent (the
|
|
519
|
+
// deployed contract guarantees it on conforming error envelopes), fall
|
|
520
|
+
// back to the canonical synthetic sentence — byte-identical to the PHP
|
|
521
|
+
// SDK — instead of leaking the machine code (card U7MACpOj). The code is
|
|
522
|
+
// still carried on `.errorCode`. Machine dispatch keys off `error_type`
|
|
523
|
+
// (below), unchanged.
|
|
492
524
|
const status = response.status;
|
|
493
|
-
const errorMessage = json.message ??
|
|
525
|
+
const errorMessage = json.message ??
|
|
526
|
+
fallbackErrorMessage(status, typeof json.error === 'string' ? json.error : undefined);
|
|
494
527
|
// Validation-details branch first — preserve existing shape so callers
|
|
495
528
|
// matching on `instanceof GislValidationError` keep working.
|
|
496
529
|
if (isValidationDetails(json.details)) {
|
|
@@ -595,6 +628,15 @@ export class GislClient {
|
|
|
595
628
|
tryThrowCap(UploadDurationExceedsTierResponseFromJSON, 'duration_tier', (p) => isInEnum(p.currentTier, UserTier) &&
|
|
596
629
|
typeof p.maxDurationSeconds === 'number');
|
|
597
630
|
}
|
|
631
|
+
// Long-form concurrency limit (429) — a TIER quota, DISTINCT from a
|
|
632
|
+
// generic infra rate-limit 429. Dispatched on the machine `error` CODE
|
|
633
|
+
// (this envelope carries NO `error_type`); a generic rate-limit 429 has a
|
|
634
|
+
// different/absent code, so it falls through to the base GislApiError
|
|
635
|
+
// where `retryAfterSeconds` applies. The validator re-asserts the code so
|
|
636
|
+
// a malformed envelope falls through rather than mis-typing.
|
|
637
|
+
if (status === 429 && json.error === 'LONG_FORM_CONCURRENCY_LIMIT_EXCEEDED') {
|
|
638
|
+
tryThrowStructured(LongFormConcurrencyLimitResponseFromJSON, GislLongFormConcurrencyError, (p) => p.error === 'LONG_FORM_CONCURRENCY_LIMIT_EXCEEDED');
|
|
639
|
+
}
|
|
598
640
|
// 413 = the absolute across-tier cap. The contract models 413 as a
|
|
599
641
|
// plain `ErrorEnvelope` (no `error_type` discriminator, no typed
|
|
600
642
|
// payload — api.yaml), so dispatch purely on status with no FromJSON
|
|
@@ -1603,10 +1645,15 @@ export class GislClient {
|
|
|
1603
1645
|
* authenticated reads. A wrong/missing cap on a null-owner workflow is a 404.
|
|
1604
1646
|
*/
|
|
1605
1647
|
async getWorkflowStatus(workflowId, opts = {}) {
|
|
1606
|
-
|
|
1607
|
-
|
|
1608
|
-
|
|
1609
|
-
|
|
1648
|
+
try {
|
|
1649
|
+
return await this.request('GET', `/api/workflows/${encodeURIComponent(workflowId)}/status`, {
|
|
1650
|
+
deserialize: WorkflowStatusResponseFromJSON,
|
|
1651
|
+
headers: workflowCapabilityHeaders(opts.capability),
|
|
1652
|
+
});
|
|
1653
|
+
}
|
|
1654
|
+
catch (err) {
|
|
1655
|
+
throw withWorkflowIdOnTimeout(err, workflowId);
|
|
1656
|
+
}
|
|
1610
1657
|
}
|
|
1611
1658
|
/**
|
|
1612
1659
|
* Poll until the workflow reaches a terminal status.
|
|
@@ -1624,7 +1671,7 @@ export class GislClient {
|
|
|
1624
1671
|
return status;
|
|
1625
1672
|
}
|
|
1626
1673
|
if (Date.now() + intervalMs > deadline) {
|
|
1627
|
-
throw new GislTimeoutError(`Workflow ${workflowId} did not complete within ${timeoutMs}ms
|
|
1674
|
+
throw new GislTimeoutError(`Workflow ${workflowId} did not complete within ${timeoutMs}ms`, workflowId);
|
|
1628
1675
|
}
|
|
1629
1676
|
await new Promise((resolve) => setTimeout(resolve, intervalMs));
|
|
1630
1677
|
}
|
|
@@ -1681,10 +1728,15 @@ export class GislClient {
|
|
|
1681
1728
|
* authenticated reads. A wrong/missing cap on a null-owner workflow is a 404.
|
|
1682
1729
|
*/
|
|
1683
1730
|
async getWorkflowDownloads(workflowId, opts = {}) {
|
|
1684
|
-
|
|
1685
|
-
|
|
1686
|
-
|
|
1687
|
-
|
|
1731
|
+
try {
|
|
1732
|
+
return await this.request('GET', `/api/workflows/${encodeURIComponent(workflowId)}/downloads`, {
|
|
1733
|
+
deserialize: WorkflowDownloadResponseFromJSON,
|
|
1734
|
+
headers: workflowCapabilityHeaders(opts.capability),
|
|
1735
|
+
});
|
|
1736
|
+
}
|
|
1737
|
+
catch (err) {
|
|
1738
|
+
throw withWorkflowIdOnTimeout(err, workflowId);
|
|
1739
|
+
}
|
|
1688
1740
|
}
|
|
1689
1741
|
/**
|
|
1690
1742
|
* Stream SSE events for a workflow. Returns an async iterable.
|
|
@@ -1862,22 +1914,26 @@ export class GislClient {
|
|
|
1862
1914
|
return { notModified: true, etag, lastModified };
|
|
1863
1915
|
}
|
|
1864
1916
|
if (!response.ok) {
|
|
1865
|
-
let errorMessage = 'Unknown error';
|
|
1866
1917
|
let errorCode;
|
|
1918
|
+
// Seed with the canonical synthetic sentence (unknown_error) so the
|
|
1919
|
+
// message-absent JSON branch below reuses the SAME helper as
|
|
1920
|
+
// handleResponse + PHP — that message-absent JSON envelope is the
|
|
1921
|
+
// U7MACpOj parity target. A non-JSON body is a rare edge that keeps
|
|
1922
|
+
// this seed (it does NOT claim byte-parity with PHP's non-JSON path,
|
|
1923
|
+
// which throws a distinct GislError).
|
|
1924
|
+
let errorMessage = fallbackErrorMessage(response.status, undefined);
|
|
1867
1925
|
try {
|
|
1868
1926
|
const errJson = (await response.json());
|
|
1869
|
-
// Prefer the human `message`; `error` is the machine code (x9Lbf6uy).
|
|
1870
|
-
if (errJson.message)
|
|
1871
|
-
errorMessage = errJson.message;
|
|
1872
|
-
else if (errJson.error)
|
|
1873
|
-
errorMessage = errJson.error;
|
|
1874
1927
|
// Surface the machine code as errorCode too (parity with handleResponse
|
|
1875
1928
|
// + PHP), even when `message` supplied the human text.
|
|
1876
1929
|
if (typeof errJson.error === 'string')
|
|
1877
1930
|
errorCode = errJson.error;
|
|
1931
|
+
// Prefer the human `message`; else the canonical synthetic sentence —
|
|
1932
|
+
// NOT the raw machine code (x9Lbf6uy / U7MACpOj).
|
|
1933
|
+
errorMessage = errJson.message ?? fallbackErrorMessage(response.status, errorCode);
|
|
1878
1934
|
}
|
|
1879
1935
|
catch {
|
|
1880
|
-
// Non-JSON body — keep generic message, no machine code.
|
|
1936
|
+
// Non-JSON body — keep the generic synthetic message, no machine code.
|
|
1881
1937
|
}
|
|
1882
1938
|
// This throw is OUTSIDE handleResponse (rawResponse:true / 304 path), so
|
|
1883
1939
|
// build the response-header surface from the in-scope `response` here.
|
|
@@ -1949,15 +2005,18 @@ export class GislClient {
|
|
|
1949
2005
|
* via that cookie when the client is configured with
|
|
1950
2006
|
* `useSessionCookie: true`.
|
|
1951
2007
|
*
|
|
1952
|
-
* Failure modes per ticket FX6mbTJD
|
|
1953
|
-
*
|
|
1954
|
-
*
|
|
1955
|
-
*
|
|
1956
|
-
* `
|
|
1957
|
-
* `account_deletion_expired`) → `GislAuthError`.
|
|
2008
|
+
* Failure modes per ticket FX6mbTJD (login narrowed at contracts
|
|
2009
|
+
* v2.166.0 authsec — no 403 account-state branch on login):
|
|
2010
|
+
* - **401** `invalid_credentials` (wrong password, unverified, OR
|
|
2011
|
+
* unknown account — all collapsed for anti-enumeration) →
|
|
2012
|
+
* `GislAuthError`.
|
|
1958
2013
|
* - **429** infrastructure rate-limit → `GislApiError` with
|
|
1959
2014
|
* the `Retry-After` header echoed on the response.
|
|
1960
2015
|
*
|
|
2016
|
+
* The account-status error types (`account_locked` / `account_disabled`
|
|
2017
|
+
* / `account_deleted` / `account_deletion_expired`) still exist but are
|
|
2018
|
+
* emitted on the API-key path + live-session enforcement, not on login.
|
|
2019
|
+
*
|
|
1961
2020
|
* Node session persistence (cookie-jar across processes) is out of
|
|
1962
2021
|
* scope — this method only touches the request side.
|
|
1963
2022
|
*/
|
|
@@ -87,4 +87,34 @@ export declare function resolveOutputRoute(inputToken: string, outputFormat: str
|
|
|
87
87
|
* option / value / group is unknown (no gate).
|
|
88
88
|
*/
|
|
89
89
|
export declare function isPlannedValue(inputToken: string, optionKey: string, value: unknown): boolean;
|
|
90
|
+
/**
|
|
91
|
+
* Compress-route enum members per image mime-group, mirroring the shipped
|
|
92
|
+
* `availability/availability.json` `operations.compress.mime_groups.<group>.
|
|
93
|
+
* options.<opt>.values`. Kept as a hand table (NOT a runtime read of the ~238KB
|
|
94
|
+
* availability sidecar) so the enum-membership gate stays browser-safe, exactly
|
|
95
|
+
* like {@link IMAGE_OUTPUT_ROUTES} — and, crucially, so the gate has NO
|
|
96
|
+
* dependency on a contracts version that carries the enum in a compact form (a
|
|
97
|
+
* generated-metadata `values` field would fail open on an older published
|
|
98
|
+
* `@giveitsmaller/contracts`). PINNED to `availability.json` by
|
|
99
|
+
* `output-route-conformance.test.ts`; a contract regen that adds/changes an
|
|
100
|
+
* enum member fails there. Mirrored by PHP `ImageOutputRoutes::COMPRESS_OPTION_VALUES`.
|
|
101
|
+
*
|
|
102
|
+
* `image_svg`/`image_avif` carry the NARROW `metadata: ['strip','all']` (no
|
|
103
|
+
* `keep`) — the reason a value gate that consulted only the generic `image`
|
|
104
|
+
* group (`['strip','keep','all']`) let `metadata: 'keep'` reach a server 422 on
|
|
105
|
+
* those bases (rtkzl9gr). `output_format` is listed for a faithful projection
|
|
106
|
+
* mirror but is never gated here (the Output lowering owns it positionally).
|
|
107
|
+
*/
|
|
108
|
+
export declare const COMPRESS_OPTION_VALUES: Readonly<Record<string, Readonly<Record<string, readonly string[]>>>>;
|
|
109
|
+
/**
|
|
110
|
+
* Whether a VALUE lies OUTSIDE the option's compress-route enum for the given
|
|
111
|
+
* input format — the pre-upload enum-membership gate (rtkzl9gr). Reads the hand
|
|
112
|
+
* {@link COMPRESS_OPTION_VALUES} table. Returns false when the option is not an
|
|
113
|
+
* enum on this group (no entry), so a non-enum option (e.g. integer `quality`)
|
|
114
|
+
* is never gated. Meaningful only on the same_format (compress) route, where
|
|
115
|
+
* the compress option enums definitionally apply. Membership is STRICT: a value
|
|
116
|
+
* whose type differs from the string enum members (e.g. numeric `420`) is
|
|
117
|
+
* treated as unknown rather than coerced to a match.
|
|
118
|
+
*/
|
|
119
|
+
export declare function isUnknownEnumValue(inputToken: string, optionKey: string, value: unknown): boolean;
|
|
90
120
|
export {};
|
|
@@ -29,8 +29,18 @@
|
|
|
29
29
|
import { compressMetadata } from '@giveitsmaller/contracts/operations';
|
|
30
30
|
/** The resize option keys — input-keyed, raster-only (see module doc). */
|
|
31
31
|
export const RESIZE_KEYS = ['width', 'height', 'fit'];
|
|
32
|
-
/**
|
|
33
|
-
|
|
32
|
+
/**
|
|
33
|
+
* Options whose availability follows the INPUT format's raster capability, not
|
|
34
|
+
* the output format — resize (`width`/`height`/`fit`) plus `auto_orient`. The
|
|
35
|
+
* projection lists them on every `format_change` cell (keyed by OUTPUT), so on
|
|
36
|
+
* a format change they must be re-gated against the INPUT's `same_format` cell:
|
|
37
|
+
* a raster input carries them, an SVG (vector) input does not. Before rtkzl9gr
|
|
38
|
+
* only the resize keys were input-gated, so `auto_orient` leaked onto the
|
|
39
|
+
* `svg → raster` route and was rejected server-side.
|
|
40
|
+
*/
|
|
41
|
+
const INPUT_GATED_KEYS = [...RESIZE_KEYS, 'auto_orient'];
|
|
42
|
+
/** Set form of {@link INPUT_GATED_KEYS} for `string`-keyed membership tests. */
|
|
43
|
+
const INPUT_GATED_KEY_SET = new Set(INPUT_GATED_KEYS);
|
|
34
44
|
/** Image area cap shared by every resizable route (projection `max_output_pixels`). */
|
|
35
45
|
export const MAX_OUTPUT_PIXELS = 16_000_000;
|
|
36
46
|
/**
|
|
@@ -117,21 +127,22 @@ export function resolveOutputRoute(inputToken, outputFormat) {
|
|
|
117
127
|
const cell = IMAGE_OUTPUT_ROUTES.format_change[outToken];
|
|
118
128
|
if (cell === undefined)
|
|
119
129
|
return undefined;
|
|
120
|
-
// Resize
|
|
121
|
-
//
|
|
122
|
-
//
|
|
123
|
-
//
|
|
124
|
-
//
|
|
125
|
-
//
|
|
126
|
-
|
|
130
|
+
// Resize + auto_orient are INPUT-gated (see {@link INPUT_GATED_KEYS}). Since
|
|
131
|
+
// v2.103.0 convert is the resize engine, so the projection lists width/height/
|
|
132
|
+
// fit AND auto_orient on EVERY format_change cell — but an SVG INPUT cannot be
|
|
133
|
+
// raster-resized or auto-oriented (the convert worker rejects it). So strip
|
|
134
|
+
// the cell's input-gated keys and re-add only those the INPUT's same_format
|
|
135
|
+
// cell honors: raster inputs carry them, svg does not. The transcoder options
|
|
136
|
+
// (output_format/quality/background/color_profile) ride the cell directly.
|
|
137
|
+
const transcoderHonored = cell.honored.filter((k) => !INPUT_GATED_KEY_SET.has(k));
|
|
127
138
|
const inCell = IMAGE_OUTPUT_ROUTES.same_format[inputToken];
|
|
128
|
-
const
|
|
139
|
+
const inputGated = inCell ? INPUT_GATED_KEYS.filter((k) => inCell.honored.includes(k)) : [];
|
|
129
140
|
return {
|
|
130
141
|
route: 'format_change',
|
|
131
142
|
sourceOp: 'convert',
|
|
132
143
|
outputFormatWire: outToken,
|
|
133
144
|
inputToken,
|
|
134
|
-
honored: new Set([...transcoderHonored, ...
|
|
145
|
+
honored: new Set([...transcoderHonored, ...inputGated]),
|
|
135
146
|
planned: new Set(cell.planned),
|
|
136
147
|
};
|
|
137
148
|
}
|
|
@@ -161,3 +172,63 @@ export function isPlannedValue(inputToken, optionKey, value) {
|
|
|
161
172
|
const entry = opt.per_value_availability[String(value)];
|
|
162
173
|
return entry?.availability === 'planned';
|
|
163
174
|
}
|
|
175
|
+
/**
|
|
176
|
+
* Compress-route enum members per image mime-group, mirroring the shipped
|
|
177
|
+
* `availability/availability.json` `operations.compress.mime_groups.<group>.
|
|
178
|
+
* options.<opt>.values`. Kept as a hand table (NOT a runtime read of the ~238KB
|
|
179
|
+
* availability sidecar) so the enum-membership gate stays browser-safe, exactly
|
|
180
|
+
* like {@link IMAGE_OUTPUT_ROUTES} — and, crucially, so the gate has NO
|
|
181
|
+
* dependency on a contracts version that carries the enum in a compact form (a
|
|
182
|
+
* generated-metadata `values` field would fail open on an older published
|
|
183
|
+
* `@giveitsmaller/contracts`). PINNED to `availability.json` by
|
|
184
|
+
* `output-route-conformance.test.ts`; a contract regen that adds/changes an
|
|
185
|
+
* enum member fails there. Mirrored by PHP `ImageOutputRoutes::COMPRESS_OPTION_VALUES`.
|
|
186
|
+
*
|
|
187
|
+
* `image_svg`/`image_avif` carry the NARROW `metadata: ['strip','all']` (no
|
|
188
|
+
* `keep`) — the reason a value gate that consulted only the generic `image`
|
|
189
|
+
* group (`['strip','keep','all']`) let `metadata: 'keep'` reach a server 422 on
|
|
190
|
+
* those bases (rtkzl9gr). `output_format` is listed for a faithful projection
|
|
191
|
+
* mirror but is never gated here (the Output lowering owns it positionally).
|
|
192
|
+
*/
|
|
193
|
+
export const COMPRESS_OPTION_VALUES = {
|
|
194
|
+
image: { color_profile: ['keep', 'srgb', 'strip'], fit: ['max', 'crop', 'scale'], metadata: ['strip', 'keep', 'all'], output_format: ['original', 'webp', 'auto', 'smallest'] },
|
|
195
|
+
image_jpeg: { chroma_subsampling: ['420', '422', '444'], color_profile: ['keep', 'srgb', 'strip'], encoding_mode: ['quality', 'target_size', 'auto_quality'], fit: ['max', 'crop', 'scale'], metadata: ['strip', 'keep', 'all'], output_format: ['original', 'webp', 'auto', 'smallest'], quality_preset: ['best', 'good', 'fair', 'low'] },
|
|
196
|
+
image_png: { color_profile: ['keep', 'srgb', 'strip'], fit: ['max', 'crop', 'scale'], metadata: ['strip', 'keep', 'all'], output_format: ['original', 'webp', 'auto', 'smallest'] },
|
|
197
|
+
image_avif: { color_profile: ['keep', 'srgb', 'strip'], encoding_mode: ['quality', 'target_size', 'auto_quality'], fit: ['max', 'crop', 'scale'], metadata: ['strip', 'all'], output_format: ['original', 'webp', 'auto', 'smallest'], quality_preset: ['best', 'good', 'fair', 'low'] },
|
|
198
|
+
image_svg: { metadata: ['strip', 'all'], output_format: ['original', 'webp', 'auto', 'smallest'] },
|
|
199
|
+
image_webp: { color_profile: ['keep', 'srgb', 'strip'], encoding_mode: ['quality', 'target_size', 'auto_quality'], fit: ['max', 'crop', 'scale'], metadata: ['strip', 'keep', 'all'], output_format: ['original', 'webp', 'auto', 'smallest'], quality_preset: ['best', 'good', 'fair', 'low'] },
|
|
200
|
+
};
|
|
201
|
+
/**
|
|
202
|
+
* The compress mime-group whose enum members are authoritative for an image
|
|
203
|
+
* token's SAME_FORMAT route — the exact `image_<token>` group when
|
|
204
|
+
* {@link COMPRESS_OPTION_VALUES} carries one, else the generic `image` group
|
|
205
|
+
* (gif/tiff).
|
|
206
|
+
*
|
|
207
|
+
* Deliberately DISTINCT from {@link compressGroupForToken} (which the planned
|
|
208
|
+
* gate uses). The planned gate routes webp/gif/svg/tiff through the generic
|
|
209
|
+
* `image` group, where cross-format `planned` markers live (e.g. `srgb`).
|
|
210
|
+
* Enum MEMBERSHIP is the opposite: it needs the format-specific enum, because
|
|
211
|
+
* `image_svg`'s `metadata` enum is the narrow `[strip, all]` while the generic
|
|
212
|
+
* group's is `[strip, keep, all]` — so only the specific group rejects
|
|
213
|
+
* `metadata: 'keep'` on SVG (and AVIF, which already maps specifically).
|
|
214
|
+
*/
|
|
215
|
+
function enumGroupForToken(token) {
|
|
216
|
+
const specific = `image_${token}`;
|
|
217
|
+
return COMPRESS_OPTION_VALUES[specific] !== undefined ? specific : 'image';
|
|
218
|
+
}
|
|
219
|
+
/**
|
|
220
|
+
* Whether a VALUE lies OUTSIDE the option's compress-route enum for the given
|
|
221
|
+
* input format — the pre-upload enum-membership gate (rtkzl9gr). Reads the hand
|
|
222
|
+
* {@link COMPRESS_OPTION_VALUES} table. Returns false when the option is not an
|
|
223
|
+
* enum on this group (no entry), so a non-enum option (e.g. integer `quality`)
|
|
224
|
+
* is never gated. Meaningful only on the same_format (compress) route, where
|
|
225
|
+
* the compress option enums definitionally apply. Membership is STRICT: a value
|
|
226
|
+
* whose type differs from the string enum members (e.g. numeric `420`) is
|
|
227
|
+
* treated as unknown rather than coerced to a match.
|
|
228
|
+
*/
|
|
229
|
+
export function isUnknownEnumValue(inputToken, optionKey, value) {
|
|
230
|
+
const members = COMPRESS_OPTION_VALUES[enumGroupForToken(inputToken)]?.[optionKey];
|
|
231
|
+
if (members === undefined)
|
|
232
|
+
return false;
|
|
233
|
+
return !(typeof value === 'string' && members.includes(value));
|
|
234
|
+
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { ResolvedOptions } from '../builder.js';
|
|
2
2
|
import type { OptimizeFor } from '../generated/sdk_spec/enums.js';
|
|
3
|
-
import { type PresetDefaults, type PresetMedia, type PresetOp } from './presets/index.js';
|
|
3
|
+
import { type PresetDefaults, type PresetMedia, type DetectedMedia, type PresetOp } from './presets/index.js';
|
|
4
4
|
/**
|
|
5
5
|
* The preset matrix version emitted on every resolve. Re-exported from the
|
|
6
6
|
* GENERATED `sdk_spec/version.ts` (source of truth: contracts
|
|
@@ -17,7 +17,7 @@ export declare const PRESET_VERSION: "1.6";
|
|
|
17
17
|
* extend the union.
|
|
18
18
|
*/
|
|
19
19
|
export interface ResolveCompressOptionsInput {
|
|
20
|
-
readonly media:
|
|
20
|
+
readonly media: DetectedMedia;
|
|
21
21
|
readonly op: PresetOp;
|
|
22
22
|
/** Defaults registered via `gisl.create({ presetDefaults: ... })`. */
|
|
23
23
|
readonly presetDefaults?: PresetDefaults;
|
|
@@ -37,7 +37,7 @@
|
|
|
37
37
|
import { sha256Hex } from '../sha256.js';
|
|
38
38
|
import { GislConfigError } from '../errors.js';
|
|
39
39
|
import { PRESET_VERSION as GENERATED_PRESET_VERSION } from '../generated/sdk_spec/version.js';
|
|
40
|
-
import { ImageCompressPresetOptions, AudioCompressPresetOptions, VideoCompressPresetOptions,
|
|
40
|
+
import { ImageCompressPresetOptions, AudioCompressPresetOptions, VideoCompressPresetOptions, DocumentOfficeCompressPresetOptions, DocumentOdfCompressPresetOptions, DocumentEpubCompressPresetOptions, definedFieldsOf, } from './presets/index.js';
|
|
41
41
|
/**
|
|
42
42
|
* The preset matrix version emitted on every resolve. Re-exported from the
|
|
43
43
|
* GENERATED `sdk_spec/version.ts` (source of truth: contracts
|
|
@@ -175,8 +175,6 @@ function sdkDefaultRecord(media, op, optimize) {
|
|
|
175
175
|
return { ...AudioCompressPresetOptions.shippedDefaultsFor(optimize) };
|
|
176
176
|
case 'video':
|
|
177
177
|
return { ...VideoCompressPresetOptions.shippedDefaultsFor(optimize) };
|
|
178
|
-
case 'document_pdf':
|
|
179
|
-
return { ...DocumentPdfCompressPresetOptions.shippedDefaultsFor(optimize) };
|
|
180
178
|
case 'document_office':
|
|
181
179
|
return { ...DocumentOfficeCompressPresetOptions.shippedDefaultsFor(optimize) };
|
|
182
180
|
case 'document_odf':
|
|
@@ -213,9 +211,6 @@ function presetDefaultsCellRecord(defaults, media, op, optimize) {
|
|
|
213
211
|
case 'video':
|
|
214
212
|
cell = defaults.cellFor('video', 'compress', optimize);
|
|
215
213
|
break;
|
|
216
|
-
case 'document_pdf':
|
|
217
|
-
cell = defaults.cellFor('document_pdf', 'compress', optimize);
|
|
218
|
-
break;
|
|
219
214
|
case 'document_office':
|
|
220
215
|
cell = defaults.cellFor('document_office', 'compress', optimize);
|
|
221
216
|
break;
|
|
@@ -252,7 +247,6 @@ const MEDIA_FIELDS = Object.freeze({
|
|
|
252
247
|
image: new Set(['quality', 'metadata', 'outputFormat']),
|
|
253
248
|
audio: new Set(['bitrate', 'channels', 'sampleRate', 'normalize']),
|
|
254
249
|
video: new Set(['codec', 'targetSize', 'crf', 'preset', 'width', 'height', 'fit', 'fps', 'faststart', 'audioCodec', 'audioBitrate']),
|
|
255
|
-
document_pdf: new Set(['profile', 'grayscale']),
|
|
256
250
|
document_office: new Set(['stripMacros', 'stripHiddenData', 'stripUnusedFonts']),
|
|
257
251
|
document_odf: new Set(['stripMetadata', 'stripUnusedStyles']),
|
|
258
252
|
document_epub: new Set(['fontSubsetting', 'stripUnusedCss']),
|
|
@@ -278,9 +272,9 @@ function detectMismatchedOverrides(media, overrides) {
|
|
|
278
272
|
const otherSet = MEDIA_FIELDS[otherMedia];
|
|
279
273
|
if (unknownFields.every((k) => otherSet.has(k))) {
|
|
280
274
|
// PascalCase every underscore-separated segment so multi-segment
|
|
281
|
-
// media (`
|
|
275
|
+
// media (`document_office` → `DocumentOffice…`) emit the actual exported
|
|
282
276
|
// class name (code-review MEDIUM: previously emitted
|
|
283
|
-
// `
|
|
277
|
+
// `Documentoffice…` which doesn't resolve in user code).
|
|
284
278
|
const className = otherMedia
|
|
285
279
|
.split('_')
|
|
286
280
|
.map((s) => s.charAt(0).toUpperCase() + s.slice(1))
|
|
@@ -321,7 +315,6 @@ export const KNOWN_WIRE_FIELDS = Object.freeze({
|
|
|
321
315
|
image: new Set(['quality', 'metadata', 'output_format']),
|
|
322
316
|
audio: new Set(['bitrate', 'channels', 'sample_rate', 'normalize', 'trim_start', 'trim_end']),
|
|
323
317
|
video: new Set(['codec', 'encoding_mode', 'crf', 'target_size_bytes', 'preset', 'width', 'height', 'fit', 'fps', 'faststart', 'audio_codec', 'audio_bitrate', 'trim_start', 'trim_end']),
|
|
324
|
-
document_pdf: new Set(['profile', 'grayscale']),
|
|
325
318
|
document_office: new Set(['strip_macros', 'strip_hidden_data', 'strip_unused_fonts']),
|
|
326
319
|
document_odf: new Set(['strip_metadata', 'strip_unused_styles']),
|
|
327
320
|
document_epub: new Set(['font_subsetting', 'strip_unused_css']),
|
|
@@ -444,6 +437,9 @@ function computePresetConfigHash(clientDefault, scopedDefault, callPresetOverrid
|
|
|
444
437
|
* `optimize` unset ⇒ layer 1 contributes nothing; `resolvedOptions.preset = null`.
|
|
445
438
|
*/
|
|
446
439
|
export function resolveCompressOptions(input) {
|
|
440
|
+
if (input.media === 'document_pdf') {
|
|
441
|
+
throw new GislConfigError('PDF compression was removed at contracts v2.166.0; convert() / transform() still accept PDF.', { reason: 'unsupported_media' });
|
|
442
|
+
}
|
|
447
443
|
const { media, op, presetDefaults, scopedPresetDefaults, presetOverrides, optimize, explicitOptions, audioLossless } = input;
|
|
448
444
|
if (op !== 'compress') {
|
|
449
445
|
throw new GislConfigError(`Preset resolution is only wired for compress operations today; got op='${op}'.`, { reason: 'unsupported_op' });
|
|
@@ -2,27 +2,31 @@ import { OptimizeFor } from '../../generated/sdk_spec/enums.js';
|
|
|
2
2
|
import { ImageCompressPresetOptions, type ImageCompressPresetOptionsInput } from './image_compress.js';
|
|
3
3
|
import { AudioCompressPresetOptions, type AudioCompressPresetOptionsInput } from './audio_compress.js';
|
|
4
4
|
import { VideoCompressPresetOptions, type VideoCompressPresetOptionsInput } from './video_compress.js';
|
|
5
|
-
import { DocumentPdfCompressPresetOptions, type DocumentPdfCompressPresetOptionsInput } from './document_pdf_compress.js';
|
|
6
5
|
import { DocumentOfficeCompressPresetOptions, type DocumentOfficeCompressPresetOptionsInput } from './document_office_compress.js';
|
|
7
6
|
import { DocumentOdfCompressPresetOptions, type DocumentOdfCompressPresetOptionsInput } from './document_odf_compress.js';
|
|
8
7
|
import { DocumentEpubCompressPresetOptions, type DocumentEpubCompressPresetOptionsInput } from './document_epub_compress.js';
|
|
9
8
|
export { ImageCompressPresetOptions, type ImageCompressPresetOptionsInput, } from './image_compress.js';
|
|
10
9
|
export { AudioCompressPresetOptions, type AudioCompressPresetOptionsInput, } from './audio_compress.js';
|
|
11
10
|
export { VideoCompressPresetOptions, type VideoCompressPresetOptionsInput, } from './video_compress.js';
|
|
12
|
-
export { DocumentPdfCompressPresetOptions, type DocumentPdfCompressPresetOptionsInput, } from './document_pdf_compress.js';
|
|
13
11
|
export { DocumentOfficeCompressPresetOptions, type DocumentOfficeCompressPresetOptionsInput, } from './document_office_compress.js';
|
|
14
12
|
export { DocumentOdfCompressPresetOptions, type DocumentOdfCompressPresetOptionsInput, } from './document_odf_compress.js';
|
|
15
13
|
export { DocumentEpubCompressPresetOptions, type DocumentEpubCompressPresetOptionsInput, } from './document_epub_compress.js';
|
|
16
|
-
export { OptimizeFor, ImageMetadataPolicy, ImageFormat, VideoCodec, VideoPreset, VideoFit, AudioBitrate, AudioCodec, AudioSampleRate,
|
|
14
|
+
export { OptimizeFor, ImageMetadataPolicy, ImageFormat, VideoCodec, VideoPreset, VideoFit, AudioBitrate, AudioCodec, AudioSampleRate, } from '../../generated/sdk_spec/enums.js';
|
|
17
15
|
/** Supported media×op pairs for preset cells in T4a. Compress-only. */
|
|
18
|
-
export type PresetMedia = 'image' | 'audio' | 'video' | '
|
|
16
|
+
export type PresetMedia = 'image' | 'audio' | 'video' | 'document_office' | 'document_odf' | 'document_epub';
|
|
17
|
+
/**
|
|
18
|
+
* Media the file-first detector can identify — the compressible
|
|
19
|
+
* `PresetMedia` set PLUS `document_pdf`, which is detectable (and a valid
|
|
20
|
+
* watermark-reject / convert / transform base) but NOT compressible.
|
|
21
|
+
*/
|
|
22
|
+
export type DetectedMedia = PresetMedia | 'document_pdf';
|
|
19
23
|
export type PresetOp = 'compress';
|
|
20
24
|
/**
|
|
21
25
|
* Union of leaf-DTO types the resolver will see from `cellFor()`.
|
|
22
26
|
* Discriminated by which `media` the caller passes — the type system
|
|
23
27
|
* narrows the return automatically via the overload set below.
|
|
24
28
|
*/
|
|
25
|
-
export type AnyPresetOptions = ImageCompressPresetOptions | AudioCompressPresetOptions | VideoCompressPresetOptions |
|
|
29
|
+
export type AnyPresetOptions = ImageCompressPresetOptions | AudioCompressPresetOptions | VideoCompressPresetOptions | DocumentOfficeCompressPresetOptions | DocumentOdfCompressPresetOptions | DocumentEpubCompressPresetOptions;
|
|
26
30
|
/**
|
|
27
31
|
* Per-cell field-merge: parent fields ⊕ child fields where defined.
|
|
28
32
|
* Re-construct the leaf DTO via the matching `<LeafClass>.from(merged)`
|
|
@@ -73,8 +77,6 @@ export declare class PresetDefaults {
|
|
|
73
77
|
audioCompress(level: OptimizeFor, input?: AudioCompressPresetOptionsInput): PresetDefaults;
|
|
74
78
|
/** Register a (level, delta) on the video-compress cell. Immutable. */
|
|
75
79
|
videoCompress(level: OptimizeFor, input?: VideoCompressPresetOptionsInput): PresetDefaults;
|
|
76
|
-
/** Register a (level, delta) on the document-pdf-compress cell. Immutable. */
|
|
77
|
-
pdfCompress(level: OptimizeFor, input?: DocumentPdfCompressPresetOptionsInput): PresetDefaults;
|
|
78
80
|
/** Register a (level, delta) on the document-office-compress cell. Immutable. */
|
|
79
81
|
officeCompress(level: OptimizeFor, input?: DocumentOfficeCompressPresetOptionsInput): PresetDefaults;
|
|
80
82
|
/** Register a (level, delta) on the document-odf-compress cell. Immutable. */
|
|
@@ -84,7 +86,6 @@ export declare class PresetDefaults {
|
|
|
84
86
|
/** @internal */ cellFor(media: 'image', op: 'compress', level: OptimizeFor): ImageCompressPresetOptions | undefined;
|
|
85
87
|
/** @internal */ cellFor(media: 'audio', op: 'compress', level: OptimizeFor): AudioCompressPresetOptions | undefined;
|
|
86
88
|
/** @internal */ cellFor(media: 'video', op: 'compress', level: OptimizeFor): VideoCompressPresetOptions | undefined;
|
|
87
|
-
/** @internal */ cellFor(media: 'document_pdf', op: 'compress', level: OptimizeFor): DocumentPdfCompressPresetOptions | undefined;
|
|
88
89
|
/** @internal */ cellFor(media: 'document_office', op: 'compress', level: OptimizeFor): DocumentOfficeCompressPresetOptions | undefined;
|
|
89
90
|
/** @internal */ cellFor(media: 'document_odf', op: 'compress', level: OptimizeFor): DocumentOdfCompressPresetOptions | undefined;
|
|
90
91
|
/** @internal */ cellFor(media: 'document_epub', op: 'compress', level: OptimizeFor): DocumentEpubCompressPresetOptions | undefined;
|
|
@@ -28,7 +28,6 @@
|
|
|
28
28
|
import { ImageCompressPresetOptions, } from './image_compress.js';
|
|
29
29
|
import { AudioCompressPresetOptions, } from './audio_compress.js';
|
|
30
30
|
import { VideoCompressPresetOptions, } from './video_compress.js';
|
|
31
|
-
import { DocumentPdfCompressPresetOptions, } from './document_pdf_compress.js';
|
|
32
31
|
import { DocumentOfficeCompressPresetOptions, } from './document_office_compress.js';
|
|
33
32
|
import { DocumentOdfCompressPresetOptions, } from './document_odf_compress.js';
|
|
34
33
|
import { DocumentEpubCompressPresetOptions, } from './document_epub_compress.js';
|
|
@@ -36,12 +35,11 @@ import { DocumentEpubCompressPresetOptions, } from './document_epub_compress.js'
|
|
|
36
35
|
export { ImageCompressPresetOptions, } from './image_compress.js';
|
|
37
36
|
export { AudioCompressPresetOptions, } from './audio_compress.js';
|
|
38
37
|
export { VideoCompressPresetOptions, } from './video_compress.js';
|
|
39
|
-
export { DocumentPdfCompressPresetOptions, } from './document_pdf_compress.js';
|
|
40
38
|
export { DocumentOfficeCompressPresetOptions, } from './document_office_compress.js';
|
|
41
39
|
export { DocumentOdfCompressPresetOptions, } from './document_odf_compress.js';
|
|
42
40
|
export { DocumentEpubCompressPresetOptions, } from './document_epub_compress.js';
|
|
43
41
|
// Re-export ergonomic enums for callers (single canonical path).
|
|
44
|
-
export { OptimizeFor, ImageMetadataPolicy, ImageFormat, VideoCodec, VideoPreset, VideoFit, AudioBitrate, AudioCodec, AudioSampleRate,
|
|
42
|
+
export { OptimizeFor, ImageMetadataPolicy, ImageFormat, VideoCodec, VideoPreset, VideoFit, AudioBitrate, AudioCodec, AudioSampleRate, } from '../../generated/sdk_spec/enums.js';
|
|
45
43
|
function cellKeyOf(media, op) {
|
|
46
44
|
return `${media}_${op}`;
|
|
47
45
|
}
|
|
@@ -89,8 +87,6 @@ function mergePresetOptions(cellKey, parentOpts, childOpts) {
|
|
|
89
87
|
return AudioCompressPresetOptions.from(mergedFields);
|
|
90
88
|
case 'video_compress':
|
|
91
89
|
return VideoCompressPresetOptions.from(mergedFields);
|
|
92
|
-
case 'document_pdf_compress':
|
|
93
|
-
return DocumentPdfCompressPresetOptions.from(mergedFields);
|
|
94
90
|
case 'document_office_compress':
|
|
95
91
|
return DocumentOfficeCompressPresetOptions.from(mergedFields);
|
|
96
92
|
case 'document_odf_compress':
|
|
@@ -178,10 +174,6 @@ export class PresetDefaults {
|
|
|
178
174
|
videoCompress(level, input = {}) {
|
|
179
175
|
return new PresetDefaults(withCellEntry(this.cells, 'video_compress', level, VideoCompressPresetOptions.from(input)));
|
|
180
176
|
}
|
|
181
|
-
/** Register a (level, delta) on the document-pdf-compress cell. Immutable. */
|
|
182
|
-
pdfCompress(level, input = {}) {
|
|
183
|
-
return new PresetDefaults(withCellEntry(this.cells, 'document_pdf_compress', level, DocumentPdfCompressPresetOptions.from(input)));
|
|
184
|
-
}
|
|
185
177
|
/** Register a (level, delta) on the document-office-compress cell. Immutable. */
|
|
186
178
|
officeCompress(level, input = {}) {
|
|
187
179
|
return new PresetDefaults(withCellEntry(this.cells, 'document_office_compress', level, DocumentOfficeCompressPresetOptions.from(input)));
|
package/dist/errors.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { AuthErrorResponse, AuthRejectionEnvelope, AuthRejectionEnvelopeErrorTypeEnum, BalanceExhaustedResponse, FeatureNotAvailableResponse, FeatureTierRestrictedResponse, ProbePendingResponse, TierRestrictionResponse, UploadDurationExceedsTierResponse, UploadSizeExceedsTierResponse, WorkflowExpiredResponse } from '@giveitsmaller/contracts/openapi';
|
|
1
|
+
import type { AuthErrorResponse, AuthRejectionEnvelope, AuthRejectionEnvelopeErrorTypeEnum, BalanceExhaustedResponse, FeatureNotAvailableResponse, FeatureTierRestrictedResponse, LongFormConcurrencyLimitResponse, ProbePendingResponse, TierRestrictionResponse, UploadDurationExceedsTierResponse, UploadSizeExceedsTierResponse, WorkflowExpiredResponse } from '@giveitsmaller/contracts/openapi';
|
|
2
2
|
import type { ErrorCategory } from './generated/sdk_spec/errors.js';
|
|
3
3
|
import type { RateLimitSnapshot } from './retry-metadata.js';
|
|
4
4
|
export declare class GislError extends Error {
|
|
@@ -134,6 +134,34 @@ export declare class GislBalanceExhaustedError extends GislApiError {
|
|
|
134
134
|
readonly payload: BalanceExhaustedResponse;
|
|
135
135
|
constructor(statusCode: number, errorMessage: string, payload: BalanceExhaustedResponse, path?: string, extra?: Omit<GislApiErrorOptions, 'payload'>);
|
|
136
136
|
}
|
|
137
|
+
/**
|
|
138
|
+
* `429` on `POST /api/workflows` when the caller already holds the maximum
|
|
139
|
+
* number of concurrent in-flight long-form (Fargate) workflows their tier
|
|
140
|
+
* permits (Pro 2 / Max 5; Enterprise uncapped). DISTINCT from an infrastructure
|
|
141
|
+
* rate-limit `429`: it carries the machine code `LONG_FORM_CONCURRENCY_LIMIT_EXCEEDED`
|
|
142
|
+
* and a `links.upgrade` deep link, and has **no `Retry-After`** — the limit clears
|
|
143
|
+
* when an in-flight long-form workflow finishes, not on a timer. A generic infra
|
|
144
|
+
* rate-limit `429` (no matching code) surfaces as the base {@link GislApiError}
|
|
145
|
+
* instead, where {@link GislApiError.retryAfterSeconds} applies.
|
|
146
|
+
*
|
|
147
|
+
* Dispatched on the `error` CODE, not `error_type` (the envelope carries none).
|
|
148
|
+
*
|
|
149
|
+
* @example
|
|
150
|
+
* try {
|
|
151
|
+
* await client.createWorkflow({ jobs });
|
|
152
|
+
* } catch (e) {
|
|
153
|
+
* if (e instanceof GislLongFormConcurrencyError) {
|
|
154
|
+
* showUpgradeCta(e.upgradeUrl); // wait on completion or upgrade — do NOT back off
|
|
155
|
+
* }
|
|
156
|
+
* throw e;
|
|
157
|
+
* }
|
|
158
|
+
*/
|
|
159
|
+
export declare class GislLongFormConcurrencyError extends GislApiError {
|
|
160
|
+
readonly payload: LongFormConcurrencyLimitResponse;
|
|
161
|
+
constructor(statusCode: number, errorMessage: string, payload: LongFormConcurrencyLimitResponse, path?: string, extra?: Omit<GislApiErrorOptions, 'payload'>);
|
|
162
|
+
/** The pricing / upgrade deep link (`links.upgrade`), or `undefined` when absent. */
|
|
163
|
+
get upgradeUrl(): string | undefined;
|
|
164
|
+
}
|
|
137
165
|
export declare class GislTierRestrictedError extends GislApiError {
|
|
138
166
|
readonly payload: TierRestrictionResponse;
|
|
139
167
|
constructor(statusCode: number, errorMessage: string, payload: TierRestrictionResponse, path?: string, extra?: Omit<GislApiErrorOptions, 'payload'>);
|
|
@@ -411,7 +439,25 @@ export declare class GislBundleAlreadyArchivedError extends GislConfigError {
|
|
|
411
439
|
constructor();
|
|
412
440
|
}
|
|
413
441
|
export declare class GislTimeoutError extends GislError {
|
|
414
|
-
|
|
442
|
+
/**
|
|
443
|
+
* The workflow this timeout is scoped to, when the SDK knows it. Set on a
|
|
444
|
+
* timed-out `run()` / `wait()` / poll / download once the workflow has been
|
|
445
|
+
* created: a timeout does NOT mean the work failed — the server keeps
|
|
446
|
+
* processing, so poll `client.getWorkflowStatus(workflowId)` /
|
|
447
|
+
* `getWorkflowDownloads(workflowId)` to recover a result that completed after
|
|
448
|
+
* the deadline, instead of re-running (a re-run re-uploads and, for
|
|
449
|
+
* authenticated callers, settles a SECOND charge for the same deliverable).
|
|
450
|
+
*
|
|
451
|
+
* `undefined` when the SDK has no id to offer. That is NOT a guarantee that
|
|
452
|
+
* nothing was created or charged: it covers both the safe case (an upload /
|
|
453
|
+
* probe timeout before any workflow existed) AND the AMBIGUOUS case (the
|
|
454
|
+
* `POST /api/workflows` request itself timed out — the server may have
|
|
455
|
+
* created and charged the workflow before its response was lost). Treat an
|
|
456
|
+
* absent id as "cannot auto-recover", not "clean slate": reconcile (e.g. list
|
|
457
|
+
* recent workflows) before re-running rather than assuming nothing happened.
|
|
458
|
+
*/
|
|
459
|
+
readonly workflowId?: string;
|
|
460
|
+
constructor(message: string, workflowId?: string);
|
|
415
461
|
}
|
|
416
462
|
/**
|
|
417
463
|
* Transport-level failure: the underlying `fetch` (or other transport) could
|