@giveitsmaller/sdk 0.19.0 → 0.20.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/dist/_audit.js +12 -0
- package/dist/builder.d.ts +1 -0
- package/dist/client.d.ts +8 -1
- package/dist/client.js +27 -32
- package/dist/ergonomic/option_types.d.ts +34 -1
- package/dist/ergonomic/option_types.js +5 -1
- package/dist/ergonomic/option_validation.d.ts +19 -1
- package/dist/ergonomic/option_validation.js +34 -1
- package/dist/errors.d.ts +57 -1
- package/dist/errors.js +82 -1
- package/dist/file-first.d.ts +153 -3
- package/dist/file-first.js +417 -115
- package/dist/gisl.d.ts +72 -3
- package/dist/gisl.js +72 -2
- package/dist/index.core.d.ts +8 -6
- package/dist/index.core.js +9 -1
- package/dist/merge.d.ts +12 -0
- package/dist/merge.js +12 -0
- package/dist/retry-metadata.d.ts +37 -0
- package/dist/retry-metadata.js +86 -0
- package/dist/sse.d.ts +2 -1
- package/dist/sse.js +26 -6
- package/dist/types.d.ts +43 -1
- package/package.json +2 -2
package/dist/_audit.js
CHANGED
|
@@ -139,6 +139,8 @@ export function _runAudit() {
|
|
|
139
139
|
accept();
|
|
140
140
|
// FF3a / u0hBt6fl — homogeneous fan-out builder surface.
|
|
141
141
|
accept();
|
|
142
|
+
// FF7 / MFaCjL8d — keyed multi-recipe batch builder surface.
|
|
143
|
+
accept();
|
|
142
144
|
// FF4a / Z7zTr789 — multi-input watermark recipe surface.
|
|
143
145
|
accept();
|
|
144
146
|
accept();
|
|
@@ -149,4 +151,14 @@ export function _runAudit() {
|
|
|
149
151
|
// FF5a / Ao8RPVxD — file-first Handle reattach surface.
|
|
150
152
|
accept();
|
|
151
153
|
accept();
|
|
154
|
+
// TYNjcjpo — SSE parse-failure diagnostic surface.
|
|
155
|
+
accept();
|
|
156
|
+
// qUhxfDA5 — capabilities() projection surface + the three contract
|
|
157
|
+
// capability types it exposes.
|
|
158
|
+
accept();
|
|
159
|
+
accept();
|
|
160
|
+
accept();
|
|
161
|
+
accept();
|
|
162
|
+
// W8v4jWzx — error-taxonomy category union surfaced by GislApiError.category.
|
|
163
|
+
accept();
|
|
152
164
|
}
|
package/dist/builder.d.ts
CHANGED
|
@@ -217,6 +217,7 @@ export interface UploadProgressEvent {
|
|
|
217
217
|
export interface ProcessingProgressEvent {
|
|
218
218
|
readonly phase: 'processing';
|
|
219
219
|
readonly status?: SseOperationProgressDataStatusEnum;
|
|
220
|
+
/** Percent complete, `0-100` (the wire integer) — NOT a 0..1 fraction. */
|
|
220
221
|
readonly progress: number;
|
|
221
222
|
readonly jobRef: string;
|
|
222
223
|
readonly operationId: string;
|
package/dist/client.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { AudioWatermarkDecodeRequest, AudioWatermarkDecodeResponse, ExternalImportCreatedResponse, ExternalImportRequest, LoginUserRequest, LoginUser200ResponseData, ContactRequest, AccountLimits, 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, ProbeWaitOptions, ProbeWaitResult, ReadCapabilityOptions, UploadOptions, WaitOptions, WorkflowCreatePayload, _Sdk3HandCodedKeepaliveResult, _Sdk3HandCodedMultipartStatusResult, _Sdk3HandCodedPresignPartsResult } from './types.js';
|
|
2
|
+
import type { CreditsUsageOptions, ListWorkflowsOptions, GetSchemaOptions, GetSchemaResult, GislClientConfig, GislSseEvent, GislSseParseFailure, PreflightClipsResult, ProbeWaitOptions, ProbeWaitResult, ReadCapabilityOptions, 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 {
|
|
@@ -199,6 +199,13 @@ export declare class GislClient {
|
|
|
199
199
|
streamEvents(workflowId: string, opts?: {
|
|
200
200
|
signal?: AbortSignal;
|
|
201
201
|
capability?: string;
|
|
202
|
+
/**
|
|
203
|
+
* Observe malformed-JSON SSE frames (TYNjcjpo). A frame whose `data:` body
|
|
204
|
+
* fails to parse is SKIPPED from the stream (kept resilient) and reported
|
|
205
|
+
* here as a typed {@link GislSseParseFailure} instead of being silently lost.
|
|
206
|
+
* Omit to drop malformed frames silently (the default; PHP parity).
|
|
207
|
+
*/
|
|
208
|
+
onParseError?: (diagnostic: GislSseParseFailure) => void;
|
|
202
209
|
}): Promise<AsyncGenerator<GislSseEvent>>;
|
|
203
210
|
/**
|
|
204
211
|
* Get metadata for an uploaded file.
|
package/dist/client.js
CHANGED
|
@@ -6,6 +6,10 @@
|
|
|
6
6
|
import { open, stat, basename } from './node-fs.js';
|
|
7
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
8
|
import { GislAbortError, GislApiError, GislAuthError, GislAuthRejectionError, GislBalanceExhaustedError, GislError, GislFeatureNotAvailableError, GislFeatureTierRestrictedError, GislMultipartPartCountError, GislMultipartPartError, GislMultipartSessionNotFoundError, GislMultipartSessionOwnershipError, GislMultipartSessionAuthRequiredError, GislTierRestrictedError, GislTimeoutError, GislProbePendingError, GislUploadCapExceededError, GislValidationError, GislWorkflowExpiredError, } from './errors.js';
|
|
9
|
+
// The `Retry-After` millisecond parser lives in the shared retry-metadata
|
|
10
|
+
// module (extracted to break the client ↔ errors circular import); re-imported
|
|
11
|
+
// here so the retry-loop timing stays byte-identical.
|
|
12
|
+
import { parseRetryAfterMs } from './retry-metadata.js';
|
|
9
13
|
import { parseSseStream } from './sse.js';
|
|
10
14
|
const DEFAULT_TIMEOUT_MS = 30_000;
|
|
11
15
|
// SDK-internal aliases derived from the contract-pinned UploadThresholds enums
|
|
@@ -109,12 +113,14 @@ function isAbortError(err) {
|
|
|
109
113
|
typeof err === 'object' &&
|
|
110
114
|
err.name === 'AbortError');
|
|
111
115
|
}
|
|
112
|
-
// Retryable S3 PUT response statuses: 429 throttling, 503
|
|
113
|
-
// other 5xx (502/504 are common transients behind
|
|
114
|
-
//
|
|
115
|
-
//
|
|
116
|
+
// Retryable S3 PUT response statuses: 408 request timeout, 429 throttling, 503
|
|
117
|
+
// slow-down, and any other 5xx (502/504 are common transients behind
|
|
118
|
+
// CloudFront/S3). 408 is a transient timeout on the PUT itself, so it is retried
|
|
119
|
+
// (matching the PHP SDK's S3-PUT predicate — qz7MjNTy cross-SDK alignment). Other
|
|
120
|
+
// 4xx (403 signed-URL expiry, 400 SignatureDoesNotMatch, etc.) are configuration
|
|
121
|
+
// / authority issues — retrying just delays the real failure.
|
|
116
122
|
function isRetryableStatus(status) {
|
|
117
|
-
return status === 429 || (status >= 500 && status <= 599);
|
|
123
|
+
return status === 408 || status === 429 || (status >= 500 && status <= 599);
|
|
118
124
|
}
|
|
119
125
|
// fetch surfaces network failures (DNS, TLS, TCP reset, mid-body disconnect)
|
|
120
126
|
// as TypeError. Abort surfaces as a DOMException with name='AbortError', not
|
|
@@ -123,31 +129,6 @@ function isRetryableStatus(status) {
|
|
|
123
129
|
function isRetryableNetworkError(err) {
|
|
124
130
|
return err instanceof TypeError;
|
|
125
131
|
}
|
|
126
|
-
// Parse an HTTP `Retry-After` header into milliseconds. Accepts the two RFC
|
|
127
|
-
// 9110 forms: delta-seconds (e.g. "5") or an HTTP-date. Returns `undefined`
|
|
128
|
-
// for an absent / unparseable / negative value (caller falls back to its own
|
|
129
|
-
// backoff). A past HTTP-date clamps to 0.
|
|
130
|
-
function parseRetryAfterMs(headerValue) {
|
|
131
|
-
if (headerValue === undefined)
|
|
132
|
-
return undefined;
|
|
133
|
-
const trimmed = headerValue.trim();
|
|
134
|
-
if (trimmed === '')
|
|
135
|
-
return undefined;
|
|
136
|
-
let ms;
|
|
137
|
-
if (/^\d+$/.test(trimmed)) {
|
|
138
|
-
ms = Number(trimmed) * 1000;
|
|
139
|
-
}
|
|
140
|
-
else {
|
|
141
|
-
const when = Date.parse(trimmed);
|
|
142
|
-
if (Number.isNaN(when))
|
|
143
|
-
return undefined;
|
|
144
|
-
ms = when - Date.now();
|
|
145
|
-
}
|
|
146
|
-
// A non-positive Retry-After (e.g. "0" or a past HTTP-date) must NOT short-
|
|
147
|
-
// circuit the backoff to zero — treat it as absent so the caller falls back
|
|
148
|
-
// to jitter and the loop can't busy-poll until timeout.
|
|
149
|
-
return ms > 0 ? ms : undefined;
|
|
150
|
-
}
|
|
151
132
|
// Cancellable sleep for poll loops. Resolves after `ms`, or rejects with
|
|
152
133
|
// `GislAbortError` if `signal` aborts. Resolves immediately for ms <= 0.
|
|
153
134
|
function cancellableSleep(ms, signal) {
|
|
@@ -493,6 +474,11 @@ export class GislClient {
|
|
|
493
474
|
messageKey: json.message_key,
|
|
494
475
|
locale: json.locale,
|
|
495
476
|
messageParams: json.message_params,
|
|
477
|
+
// The wire-stable machine code (SCREAMING_SNAKE `error`), surfaced as
|
|
478
|
+
// `error.errorCode` on every dispatched error (PHP parity). Threaded via
|
|
479
|
+
// this single options object → base GislApiError, GislValidationError,
|
|
480
|
+
// and every structured subclass (passed through as `extra`).
|
|
481
|
+
errorCode: typeof json.error === 'string' ? json.error : undefined,
|
|
496
482
|
responseHeaders,
|
|
497
483
|
contentLanguage,
|
|
498
484
|
};
|
|
@@ -1747,7 +1733,10 @@ export class GislClient {
|
|
|
1747
1733
|
releaseConsumerSignal();
|
|
1748
1734
|
}
|
|
1749
1735
|
}
|
|
1750
|
-
const inner = parseSseStream(response, {
|
|
1736
|
+
const inner = parseSseStream(response, {
|
|
1737
|
+
signal: controller.signal,
|
|
1738
|
+
...(opts.onParseError !== undefined ? { onParseError: opts.onParseError } : {}),
|
|
1739
|
+
});
|
|
1751
1740
|
let started = false;
|
|
1752
1741
|
let settled = false;
|
|
1753
1742
|
// Idempotent teardown. `abort` only on consumer-driven early
|
|
@@ -1874,6 +1863,7 @@ export class GislClient {
|
|
|
1874
1863
|
}
|
|
1875
1864
|
if (!response.ok) {
|
|
1876
1865
|
let errorMessage = 'Unknown error';
|
|
1866
|
+
let errorCode;
|
|
1877
1867
|
try {
|
|
1878
1868
|
const errJson = (await response.json());
|
|
1879
1869
|
// Prefer the human `message`; `error` is the machine code (x9Lbf6uy).
|
|
@@ -1881,13 +1871,18 @@ export class GislClient {
|
|
|
1881
1871
|
errorMessage = errJson.message;
|
|
1882
1872
|
else if (errJson.error)
|
|
1883
1873
|
errorMessage = errJson.error;
|
|
1874
|
+
// Surface the machine code as errorCode too (parity with handleResponse
|
|
1875
|
+
// + PHP), even when `message` supplied the human text.
|
|
1876
|
+
if (typeof errJson.error === 'string')
|
|
1877
|
+
errorCode = errJson.error;
|
|
1884
1878
|
}
|
|
1885
1879
|
catch {
|
|
1886
|
-
// Non-JSON body — keep generic message.
|
|
1880
|
+
// Non-JSON body — keep generic message, no machine code.
|
|
1887
1881
|
}
|
|
1888
1882
|
// This throw is OUTSIDE handleResponse (rawResponse:true / 304 path), so
|
|
1889
1883
|
// build the response-header surface from the in-scope `response` here.
|
|
1890
1884
|
throw new GislApiError(response.status, errorMessage, path, undefined, {
|
|
1885
|
+
errorCode,
|
|
1891
1886
|
responseHeaders: headersToRecord(response.headers),
|
|
1892
1887
|
contentLanguage: response.headers.get('content-language') ?? undefined,
|
|
1893
1888
|
});
|
|
@@ -74,6 +74,12 @@ export interface ThumbnailOptions {
|
|
|
74
74
|
/** 1-based page index for document input. */
|
|
75
75
|
page?: number;
|
|
76
76
|
}
|
|
77
|
+
export interface TransformOptions {
|
|
78
|
+
/** Clockwise rotation in degrees. document_pdf honors `rotate` only. */
|
|
79
|
+
rotate?: 0 | 90 | 180 | 270;
|
|
80
|
+
/** Mirror axis (applied after `rotate`). Not honored on document_pdf input. */
|
|
81
|
+
flip?: 'none' | 'horizontal' | 'vertical' | 'both';
|
|
82
|
+
}
|
|
77
83
|
export interface TextWatermarkOptions {
|
|
78
84
|
/** Font size in pixels (8-512). */
|
|
79
85
|
font_size?: number;
|
|
@@ -96,6 +102,24 @@ export interface TextWatermarkOptions {
|
|
|
96
102
|
/** Overlay opacity (0-1). */
|
|
97
103
|
opacity?: number;
|
|
98
104
|
}
|
|
105
|
+
/**
|
|
106
|
+
* One entry in the multi-overlay stack (contract `overlays[]` items, v2.152.0).
|
|
107
|
+
* Index-aligned to the overlay-role sources — `overlays[i]` places overlay
|
|
108
|
+
* source `i` — and mirrors the flat single-overlay option shape. Matches the
|
|
109
|
+
* generated `ImageWatermarkImageOverlaysItem`.
|
|
110
|
+
*/
|
|
111
|
+
export interface WatermarkOverlay {
|
|
112
|
+
/** 9-grid anchor position for this overlay. */
|
|
113
|
+
anchor?: WatermarkAnchor;
|
|
114
|
+
/** Horizontal offset from the anchor (e.g. '40px' or '5%'). */
|
|
115
|
+
margin_x?: string;
|
|
116
|
+
/** Vertical offset from the anchor. */
|
|
117
|
+
margin_y?: string;
|
|
118
|
+
/** Overlay opacity (0-1). */
|
|
119
|
+
opacity?: number;
|
|
120
|
+
/** Overlay width (e.g. '120px' or '20%'). */
|
|
121
|
+
overlay_width?: string;
|
|
122
|
+
}
|
|
99
123
|
export interface WatermarkOptions {
|
|
100
124
|
/** 9-grid anchor position. */
|
|
101
125
|
anchor?: WatermarkAnchor;
|
|
@@ -107,6 +131,14 @@ export interface WatermarkOptions {
|
|
|
107
131
|
opacity?: number;
|
|
108
132
|
/** Overlay width (e.g. '120px' or '20%'). */
|
|
109
133
|
overlay_width?: string;
|
|
134
|
+
/**
|
|
135
|
+
* Per-overlay placement for the multi-overlay stack (contract `overlays[]`,
|
|
136
|
+
* v2.152.0) — one entry per overlay source, index-aligned; stacks up to 8
|
|
137
|
+
* overlays on one base image (z-order = array index). MUTUALLY EXCLUSIVE with
|
|
138
|
+
* the flat single-overlay options above; the server rejects mixing the two as
|
|
139
|
+
* `invalid_options`. image_watermark jpeg/png/webp bases only.
|
|
140
|
+
*/
|
|
141
|
+
overlays?: WatermarkOverlay[];
|
|
110
142
|
}
|
|
111
143
|
/** Resize mode (contract `fit` enum, v2.97.0). */
|
|
112
144
|
export type OutputFit = 'max' | 'crop' | 'scale';
|
|
@@ -182,7 +214,8 @@ export interface OutputOptions {
|
|
|
182
214
|
export declare const VERB_OPTION_KEYS: {
|
|
183
215
|
readonly convert: readonly ["quality", "background", "crf", "trim_start", "trim_end", "fps", "width", "height", "fit", "metadata", "color_profile", "auto_orient", "max_colors", "loop", "dither", "bitrate", "pages", "dpi"];
|
|
184
216
|
readonly thumbnail: readonly ["width", "height", "fit", "format", "quality", "background", "timestamp", "source", "page"];
|
|
217
|
+
readonly transform: readonly ["rotate", "flip"];
|
|
185
218
|
readonly textWatermark: readonly ["font_size", "color", "font_family", "rotation", "watermark_mode", "tile_spacing", "anchor", "margin_x", "margin_y", "opacity"];
|
|
186
|
-
readonly watermark: readonly ["anchor", "margin_x", "margin_y", "opacity", "overlay_width"];
|
|
219
|
+
readonly watermark: readonly ["anchor", "margin_x", "margin_y", "opacity", "overlay_width", "overlays"];
|
|
187
220
|
readonly output: readonly ["quality", "quality_preset", "encoding_mode", "target_size_bytes", "chroma_subsampling", "width", "height", "fit", "background", "progressive", "optimization_level", "avif_speed", "metadata", "color_profile", "auto_orient", "lossless"];
|
|
188
221
|
};
|
|
@@ -21,12 +21,13 @@ const CONVERT_OPTION_KEYS = [
|
|
|
21
21
|
const THUMBNAIL_OPTION_KEYS = [
|
|
22
22
|
'width', 'height', 'fit', 'format', 'quality', 'background', 'timestamp', 'source', 'page',
|
|
23
23
|
];
|
|
24
|
+
const TRANSFORM_OPTION_KEYS = ['rotate', 'flip'];
|
|
24
25
|
const TEXT_WATERMARK_OPTION_KEYS = [
|
|
25
26
|
'font_size', 'color', 'font_family', 'rotation', 'watermark_mode',
|
|
26
27
|
'tile_spacing', 'anchor', 'margin_x', 'margin_y', 'opacity',
|
|
27
28
|
];
|
|
28
29
|
const WATERMARK_OPTION_KEYS = [
|
|
29
|
-
'anchor', 'margin_x', 'margin_y', 'opacity', 'overlay_width',
|
|
30
|
+
'anchor', 'margin_x', 'margin_y', 'opacity', 'overlay_width', 'overlays',
|
|
30
31
|
];
|
|
31
32
|
const OUTPUT_OPTION_KEYS = [
|
|
32
33
|
'quality', 'quality_preset', 'encoding_mode', 'target_size_bytes', 'chroma_subsampling', 'width', 'height', 'fit',
|
|
@@ -35,12 +36,14 @@ const OUTPUT_OPTION_KEYS = [
|
|
|
35
36
|
];
|
|
36
37
|
const _convertKeysMatch = true;
|
|
37
38
|
const _thumbnailKeysMatch = true;
|
|
39
|
+
const _transformKeysMatch = true;
|
|
38
40
|
const _textWatermarkKeysMatch = true;
|
|
39
41
|
const _watermarkKeysMatch = true;
|
|
40
42
|
const _outputKeysMatch = true;
|
|
41
43
|
// Reference the assertions so `noUnusedLocals` doesn't strip them.
|
|
42
44
|
void _convertKeysMatch;
|
|
43
45
|
void _thumbnailKeysMatch;
|
|
46
|
+
void _transformKeysMatch;
|
|
44
47
|
void _textWatermarkKeysMatch;
|
|
45
48
|
void _watermarkKeysMatch;
|
|
46
49
|
void _outputKeysMatch;
|
|
@@ -52,6 +55,7 @@ void _outputKeysMatch;
|
|
|
52
55
|
export const VERB_OPTION_KEYS = {
|
|
53
56
|
convert: CONVERT_OPTION_KEYS,
|
|
54
57
|
thumbnail: THUMBNAIL_OPTION_KEYS,
|
|
58
|
+
transform: TRANSFORM_OPTION_KEYS,
|
|
55
59
|
textWatermark: TEXT_WATERMARK_OPTION_KEYS,
|
|
56
60
|
watermark: WATERMARK_OPTION_KEYS,
|
|
57
61
|
output: OUTPUT_OPTION_KEYS,
|
|
@@ -25,7 +25,7 @@ import { type OperationMetadata } from '@giveitsmaller/contracts/operations';
|
|
|
25
25
|
*/
|
|
26
26
|
export declare function operationOptionKeys(metadata: OperationMetadata): ReadonlySet<string>;
|
|
27
27
|
/** The ergonomic verbs whose option bags this module key-validates. */
|
|
28
|
-
export type ValidatedVerb = 'convert' | 'thumbnail' | 'textWatermark' | 'watermark' | 'output';
|
|
28
|
+
export type ValidatedVerb = 'convert' | 'thumbnail' | 'transform' | 'textWatermark' | 'watermark' | 'output';
|
|
29
29
|
/** Accessor for the conformance guard (pins these sets to the contract metadata). */
|
|
30
30
|
export declare function allowedKeysFor(verb: ValidatedVerb): ReadonlySet<string>;
|
|
31
31
|
/**
|
|
@@ -39,6 +39,24 @@ export declare function allowedKeysFor(verb: ValidatedVerb): ReadonlySet<string>
|
|
|
39
39
|
* option set.
|
|
40
40
|
*/
|
|
41
41
|
export declare function validateVerbOptions(verb: ValidatedVerb, options: object | null | undefined): void;
|
|
42
|
+
/**
|
|
43
|
+
* Validate the option bag for the SINGLE-OP builder `gisl().convert(input, options)`
|
|
44
|
+
* (ExVcchMz). DISTINCT from `validateVerbOptions('convert', ...)`, which is for the
|
|
45
|
+
* file-first `Recipe.convert(format, options)` where `output_format` is set by the
|
|
46
|
+
* positional `format` arg and is therefore positional-owned (rejected in the bag).
|
|
47
|
+
* The single-op builder has NO positional format — its target is carried in the bag
|
|
48
|
+
* as the wire key `output_format` — so this guard ALLOWS `output_format` (and only
|
|
49
|
+
* that; the SDK alias `format` is NOT accepted, the single-op bag lowers verbatim to
|
|
50
|
+
* the wire) while still rejecting any other unknown key, AND requires `output_format`
|
|
51
|
+
* to be present (a convert with no target is a guaranteed server 422). `format`, the
|
|
52
|
+
* SDK alias, is intentionally excluded so a caller using it gets a clear unknown-key
|
|
53
|
+
* error rather than a silent wire `format` the server 422s.
|
|
54
|
+
*
|
|
55
|
+
* @throws {GislConfigError} reason `unknown_field` for a key outside the convert
|
|
56
|
+
* contract set ∪ {output_format}; reason `missing_required_field` when
|
|
57
|
+
* `output_format` is absent/nullish.
|
|
58
|
+
*/
|
|
59
|
+
export declare function validateSingleOpConvertOptions(options: object | null | undefined): void;
|
|
42
60
|
/**
|
|
43
61
|
* Assert thumbnail `width` AND `height` are both present and non-nullish (the
|
|
44
62
|
* contract marks both `required` for image/video/document). The typed signature
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { convertMetadata, thumbnailMetadata, textWatermarkMetadata, imageWatermarkMetadata, videoWatermarkMetadata, } from '@giveitsmaller/contracts/operations';
|
|
1
|
+
import { convertMetadata, thumbnailMetadata, transformMetadata, textWatermarkMetadata, imageWatermarkMetadata, videoWatermarkMetadata, } from '@giveitsmaller/contracts/operations';
|
|
2
2
|
import { GislConfigError } from '../errors.js';
|
|
3
3
|
import { VERB_OPTION_KEYS } from './option_types.js';
|
|
4
4
|
/**
|
|
@@ -51,6 +51,9 @@ function union(...sets) {
|
|
|
51
51
|
const ALLOWED_KEYS = {
|
|
52
52
|
convert: operationOptionKeys(convertMetadata),
|
|
53
53
|
thumbnail: operationOptionKeys(thumbnailMetadata),
|
|
54
|
+
// transform is a passthrough verb (rotate/flip). The generic allowed set is
|
|
55
|
+
// the op-wide union {rotate, flip}; `flip`-on-PDF is narrowed server-side.
|
|
56
|
+
transform: operationOptionKeys(transformMetadata),
|
|
54
57
|
textWatermark: operationOptionKeys(textWatermarkMetadata),
|
|
55
58
|
watermark: union(operationOptionKeys(imageWatermarkMetadata), operationOptionKeys(videoWatermarkMetadata)),
|
|
56
59
|
// `output` is the image Output facade — its allowed keys are the UNION of every
|
|
@@ -115,6 +118,36 @@ export function validateVerbOptions(verb, options) {
|
|
|
115
118
|
}
|
|
116
119
|
}
|
|
117
120
|
}
|
|
121
|
+
/**
|
|
122
|
+
* Validate the option bag for the SINGLE-OP builder `gisl().convert(input, options)`
|
|
123
|
+
* (ExVcchMz). DISTINCT from `validateVerbOptions('convert', ...)`, which is for the
|
|
124
|
+
* file-first `Recipe.convert(format, options)` where `output_format` is set by the
|
|
125
|
+
* positional `format` arg and is therefore positional-owned (rejected in the bag).
|
|
126
|
+
* The single-op builder has NO positional format — its target is carried in the bag
|
|
127
|
+
* as the wire key `output_format` — so this guard ALLOWS `output_format` (and only
|
|
128
|
+
* that; the SDK alias `format` is NOT accepted, the single-op bag lowers verbatim to
|
|
129
|
+
* the wire) while still rejecting any other unknown key, AND requires `output_format`
|
|
130
|
+
* to be present (a convert with no target is a guaranteed server 422). `format`, the
|
|
131
|
+
* SDK alias, is intentionally excluded so a caller using it gets a clear unknown-key
|
|
132
|
+
* error rather than a silent wire `format` the server 422s.
|
|
133
|
+
*
|
|
134
|
+
* @throws {GislConfigError} reason `unknown_field` for a key outside the convert
|
|
135
|
+
* contract set ∪ {output_format}; reason `missing_required_field` when
|
|
136
|
+
* `output_format` is absent/nullish.
|
|
137
|
+
*/
|
|
138
|
+
export function validateSingleOpConvertOptions(options) {
|
|
139
|
+
const o = (options ?? {});
|
|
140
|
+
const allowed = new Set([...ALLOWED_KEYS.convert, 'output_format']);
|
|
141
|
+
for (const key of Object.keys(o)) {
|
|
142
|
+
if (!allowed.has(key)) {
|
|
143
|
+
throw new GislConfigError(`convert: unknown option '${key}'. Valid options: ${[...allowed].sort().join(', ')}.`, { reason: 'unknown_field', conflictingFields: [key] });
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
if (o.output_format === undefined || o.output_format === null) {
|
|
147
|
+
throw new GislConfigError(`convert requires 'output_format' (the target format) in the options bag; ` +
|
|
148
|
+
`e.g. gisl().convert(input, { output_format: 'webp' }).`, { reason: 'missing_required_field', conflictingFields: ['output_format'] });
|
|
149
|
+
}
|
|
150
|
+
}
|
|
118
151
|
/**
|
|
119
152
|
* Assert thumbnail `width` AND `height` are both present and non-nullish (the
|
|
120
153
|
* contract marks both `required` for image/video/document). The typed signature
|
package/dist/errors.d.ts
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import type { AuthErrorResponse, AuthRejectionEnvelope, AuthRejectionEnvelopeErrorTypeEnum, BalanceExhaustedResponse, FeatureNotAvailableResponse, FeatureTierRestrictedResponse, ProbePendingResponse, TierRestrictionResponse, UploadDurationExceedsTierResponse, UploadSizeExceedsTierResponse, WorkflowExpiredResponse } from '@giveitsmaller/contracts/openapi';
|
|
2
|
+
import type { ErrorCategory } from './generated/sdk_spec/errors.js';
|
|
3
|
+
import type { RateLimitSnapshot } from './retry-metadata.js';
|
|
2
4
|
export declare class GislError extends Error {
|
|
3
5
|
constructor(message: string);
|
|
4
6
|
}
|
|
@@ -14,6 +16,11 @@ export interface GislApiErrorOptions {
|
|
|
14
16
|
readonly locale?: string;
|
|
15
17
|
readonly messageParams?: Record<string, unknown>;
|
|
16
18
|
readonly payload?: unknown;
|
|
19
|
+
/**
|
|
20
|
+
* The wire-stable machine error code (the response envelope's `error` field,
|
|
21
|
+
* SCREAMING_SNAKE, never localised). See {@link GislApiError.errorCode}.
|
|
22
|
+
*/
|
|
23
|
+
readonly errorCode?: string;
|
|
17
24
|
/**
|
|
18
25
|
* The response headers from the HTTP response that produced this error.
|
|
19
26
|
* Keys are LOWERCASED (HTTP header names are case-insensitive per RFC 9110,
|
|
@@ -33,6 +40,19 @@ export interface GislApiErrorOptions {
|
|
|
33
40
|
export declare class GislApiError extends GislError {
|
|
34
41
|
readonly statusCode: number;
|
|
35
42
|
readonly errorMessage: string;
|
|
43
|
+
/**
|
|
44
|
+
* The wire-stable machine error code — the response envelope's `error` field
|
|
45
|
+
* (SCREAMING_SNAKE, never localised). DISTINCT from {@link errorMessage},
|
|
46
|
+
* which is the human `message`. Mirrors the PHP `GislApiError.errorCode`.
|
|
47
|
+
*
|
|
48
|
+
* Optional here (PHP's is a required field defaulting to `'unknown_error'`):
|
|
49
|
+
* a DELIBERATE optional-vs-sentinel divergence — `undefined` when the wire
|
|
50
|
+
* envelope carries no `error` (e.g. a non-JSON / invalid-JSON response). When
|
|
51
|
+
* the wire DOES carry `error`, both SDKs surface the same value. Machine
|
|
52
|
+
* dispatch still keys off the typed subclasses (`payload.errorType`); this is
|
|
53
|
+
* the flat machine code for a base `GislApiError` (e.g. a plain 404).
|
|
54
|
+
*/
|
|
55
|
+
readonly errorCode?: string;
|
|
36
56
|
readonly path?: string;
|
|
37
57
|
readonly details?: unknown;
|
|
38
58
|
readonly messageKey?: string;
|
|
@@ -53,6 +73,42 @@ export declare class GislApiError extends GislError {
|
|
|
53
73
|
*/
|
|
54
74
|
readonly contentLanguage?: string;
|
|
55
75
|
constructor(statusCode: number, errorMessage: string, path?: string, details?: unknown, options?: GislApiErrorOptions);
|
|
76
|
+
/**
|
|
77
|
+
* Resolve the generated `ERROR_CODES` entry for this error, SOURCE-AWARE
|
|
78
|
+
* (plan D1). ~9 registry codes are keyed by the `error_type` discriminator
|
|
79
|
+
* rather than the envelope `error` field, so try the typed discriminator
|
|
80
|
+
* FIRST (camel `errorType`, raw-snake `error_type` fallback), then fall back
|
|
81
|
+
* to the flat machine {@link errorCode}. Returns `undefined` when neither
|
|
82
|
+
* resolves — e.g. a bare base error whose payload carries no discriminator.
|
|
83
|
+
* NEVER throws on a missing payload / discriminator.
|
|
84
|
+
*/
|
|
85
|
+
private resolveErrorEntry;
|
|
86
|
+
/**
|
|
87
|
+
* Whether retrying this request could plausibly succeed. `true` when the HTTP
|
|
88
|
+
* status is inherently retryable (408 / 429 / 5xx) OR the resolved taxonomy
|
|
89
|
+
* entry marks the code retryable (e.g. `probe_pending`). Note: logical OR
|
|
90
|
+
* (not `??`) — a 429 is retryable regardless of the taxonomy, and a
|
|
91
|
+
* registry-retryable code is retryable regardless of status.
|
|
92
|
+
*/
|
|
93
|
+
get retryable(): boolean;
|
|
94
|
+
/**
|
|
95
|
+
* The taxonomy category for this error's machine code, from the generated
|
|
96
|
+
* `ERROR_CODES` registry, or `undefined` when the code isn't in the registry
|
|
97
|
+
* (e.g. a bare base error whose payload carries no discriminator).
|
|
98
|
+
*/
|
|
99
|
+
get category(): ErrorCategory | undefined;
|
|
100
|
+
/**
|
|
101
|
+
* The rate-limit snapshot parsed from the `x-ratelimit-*` response headers,
|
|
102
|
+
* or `undefined` when they aren't all present as non-negative integers. Read
|
|
103
|
+
* this after a 429 to schedule a back-off.
|
|
104
|
+
*/
|
|
105
|
+
get rateLimit(): RateLimitSnapshot | undefined;
|
|
106
|
+
/**
|
|
107
|
+
* The server-suggested back-off delay in whole seconds, parsed from the
|
|
108
|
+
* `Retry-After` response header, or `undefined` when absent / zero / past /
|
|
109
|
+
* malformed. Mirrors the retry-loop parser's semantics.
|
|
110
|
+
*/
|
|
111
|
+
get retryAfterSeconds(): number | undefined;
|
|
56
112
|
}
|
|
57
113
|
/**
|
|
58
114
|
* Shape of a single validation detail entry. Mirrors the v2
|
|
@@ -439,7 +495,7 @@ export type GislSinkErrorReason = 'not_single_output' | 'downloader_unavailable'
|
|
|
439
495
|
/**
|
|
440
496
|
* Thrown by the file-first `RunResult` sinks (`toFile()` / `downloadTo()`,
|
|
441
497
|
* FF1) when they cannot deliver. The machine-readable `reason` discriminates
|
|
442
|
-
* the
|
|
498
|
+
* the six cases below, mirroring the `reason`-bag convention on
|
|
443
499
|
* {@link GislConfigError}:
|
|
444
500
|
*
|
|
445
501
|
* - `not_single_output` — `toFile()` requires exactly one output but the
|
package/dist/errors.js
CHANGED
|
@@ -1,3 +1,12 @@
|
|
|
1
|
+
// W8v4jWzx — the generated error-taxonomy registry stays INTERNAL to this
|
|
2
|
+
// module (only the `ErrorCategory` TYPE is re-exported from the public barrel).
|
|
3
|
+
import { ERROR_CODES } from './generated/sdk_spec/errors.js';
|
|
4
|
+
import { isApiRetryableStatus, rateLimitFromHeaders, retryAfterSecondsFromHeaders, } from './retry-metadata.js';
|
|
5
|
+
// Registry keys are lowercase_snake; normalise the wire code / discriminator
|
|
6
|
+
// (trim + lowercase) before looking it up in ERROR_CODES.
|
|
7
|
+
function normalizeErrorCode(rawCode) {
|
|
8
|
+
return rawCode.trim().toLowerCase();
|
|
9
|
+
}
|
|
1
10
|
export class GislError extends Error {
|
|
2
11
|
constructor(message) {
|
|
3
12
|
super(message);
|
|
@@ -7,6 +16,19 @@ export class GislError extends Error {
|
|
|
7
16
|
export class GislApiError extends GislError {
|
|
8
17
|
statusCode;
|
|
9
18
|
errorMessage;
|
|
19
|
+
/**
|
|
20
|
+
* The wire-stable machine error code — the response envelope's `error` field
|
|
21
|
+
* (SCREAMING_SNAKE, never localised). DISTINCT from {@link errorMessage},
|
|
22
|
+
* which is the human `message`. Mirrors the PHP `GislApiError.errorCode`.
|
|
23
|
+
*
|
|
24
|
+
* Optional here (PHP's is a required field defaulting to `'unknown_error'`):
|
|
25
|
+
* a DELIBERATE optional-vs-sentinel divergence — `undefined` when the wire
|
|
26
|
+
* envelope carries no `error` (e.g. a non-JSON / invalid-JSON response). When
|
|
27
|
+
* the wire DOES carry `error`, both SDKs surface the same value. Machine
|
|
28
|
+
* dispatch still keys off the typed subclasses (`payload.errorType`); this is
|
|
29
|
+
* the flat machine code for a base `GislApiError` (e.g. a plain 404).
|
|
30
|
+
*/
|
|
31
|
+
errorCode;
|
|
10
32
|
path;
|
|
11
33
|
details;
|
|
12
34
|
messageKey;
|
|
@@ -41,10 +63,69 @@ export class GislApiError extends GislError {
|
|
|
41
63
|
this.locale = options.locale;
|
|
42
64
|
this.messageParams = options.messageParams;
|
|
43
65
|
this.payload = options.payload;
|
|
66
|
+
this.errorCode = options.errorCode;
|
|
44
67
|
this.responseHeaders = options.responseHeaders;
|
|
45
68
|
this.contentLanguage = options.contentLanguage;
|
|
46
69
|
}
|
|
47
70
|
}
|
|
71
|
+
/**
|
|
72
|
+
* Resolve the generated `ERROR_CODES` entry for this error, SOURCE-AWARE
|
|
73
|
+
* (plan D1). ~9 registry codes are keyed by the `error_type` discriminator
|
|
74
|
+
* rather than the envelope `error` field, so try the typed discriminator
|
|
75
|
+
* FIRST (camel `errorType`, raw-snake `error_type` fallback), then fall back
|
|
76
|
+
* to the flat machine {@link errorCode}. Returns `undefined` when neither
|
|
77
|
+
* resolves — e.g. a bare base error whose payload carries no discriminator.
|
|
78
|
+
* NEVER throws on a missing payload / discriminator.
|
|
79
|
+
*/
|
|
80
|
+
resolveErrorEntry() {
|
|
81
|
+
const payload = this.payload;
|
|
82
|
+
const rawErrorType = payload?.errorType ?? payload?.error_type;
|
|
83
|
+
if (typeof rawErrorType === 'string') {
|
|
84
|
+
const byType = ERROR_CODES[normalizeErrorCode(rawErrorType)];
|
|
85
|
+
if (byType !== undefined)
|
|
86
|
+
return byType;
|
|
87
|
+
}
|
|
88
|
+
if (this.errorCode !== undefined) {
|
|
89
|
+
const byCode = ERROR_CODES[normalizeErrorCode(this.errorCode)];
|
|
90
|
+
if (byCode !== undefined)
|
|
91
|
+
return byCode;
|
|
92
|
+
}
|
|
93
|
+
return undefined;
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Whether retrying this request could plausibly succeed. `true` when the HTTP
|
|
97
|
+
* status is inherently retryable (408 / 429 / 5xx) OR the resolved taxonomy
|
|
98
|
+
* entry marks the code retryable (e.g. `probe_pending`). Note: logical OR
|
|
99
|
+
* (not `??`) — a 429 is retryable regardless of the taxonomy, and a
|
|
100
|
+
* registry-retryable code is retryable regardless of status.
|
|
101
|
+
*/
|
|
102
|
+
get retryable() {
|
|
103
|
+
return (isApiRetryableStatus(this.statusCode) || (this.resolveErrorEntry()?.retryable ?? false));
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* The taxonomy category for this error's machine code, from the generated
|
|
107
|
+
* `ERROR_CODES` registry, or `undefined` when the code isn't in the registry
|
|
108
|
+
* (e.g. a bare base error whose payload carries no discriminator).
|
|
109
|
+
*/
|
|
110
|
+
get category() {
|
|
111
|
+
return this.resolveErrorEntry()?.category;
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* The rate-limit snapshot parsed from the `x-ratelimit-*` response headers,
|
|
115
|
+
* or `undefined` when they aren't all present as non-negative integers. Read
|
|
116
|
+
* this after a 429 to schedule a back-off.
|
|
117
|
+
*/
|
|
118
|
+
get rateLimit() {
|
|
119
|
+
return rateLimitFromHeaders(this.responseHeaders);
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* The server-suggested back-off delay in whole seconds, parsed from the
|
|
123
|
+
* `Retry-After` response header, or `undefined` when absent / zero / past /
|
|
124
|
+
* malformed. Mirrors the retry-loop parser's semantics.
|
|
125
|
+
*/
|
|
126
|
+
get retryAfterSeconds() {
|
|
127
|
+
return retryAfterSecondsFromHeaders(this.responseHeaders);
|
|
128
|
+
}
|
|
48
129
|
}
|
|
49
130
|
export class GislValidationError extends GislApiError {
|
|
50
131
|
constructor(statusCode, errorMessage, details, path, options) {
|
|
@@ -479,7 +560,7 @@ export class GislResultNotReadyError extends GislError {
|
|
|
479
560
|
/**
|
|
480
561
|
* Thrown by the file-first `RunResult` sinks (`toFile()` / `downloadTo()`,
|
|
481
562
|
* FF1) when they cannot deliver. The machine-readable `reason` discriminates
|
|
482
|
-
* the
|
|
563
|
+
* the six cases below, mirroring the `reason`-bag convention on
|
|
483
564
|
* {@link GislConfigError}:
|
|
484
565
|
*
|
|
485
566
|
* - `not_single_output` — `toFile()` requires exactly one output but the
|