@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/gisl.d.ts CHANGED
@@ -19,12 +19,12 @@
19
19
  import { GislClient } from './client.js';
20
20
  import { GislConfigError, GislFeatureRequiresAuthError, GislMissingCredentialsError } from './errors.js';
21
21
  import { type ResolveCredentialsOptions, type ResolveEndpointOptions } from './credentials.js';
22
- import type { CreditsUsageOptions, GislClientConfig } from './types.js';
23
- import type { AccountLimits, CreditsBalanceResponse, CreditsUsageResponse } from '@giveitsmaller/contracts/openapi';
22
+ import type { CreditsUsageOptions, GislClientConfig, CapabilitiesSnapshot } from './types.js';
23
+ import type { AccountLimits, CreditsBalanceResponse, CreditsUsageResponse, OperationCapability, OperationType } from '@giveitsmaller/contracts/openapi';
24
24
  import { OperationBuilder } from './builder.js';
25
25
  import { MergeBuilder, type Asset, type MergeOptions } from './merge.js';
26
26
  import { PresetDefaults } from './ergonomic/presets/index.js';
27
- import { Recipe, FilesRecipe, type FileInput } from './file-first.js';
27
+ import { Recipe, FilesRecipe, BatchRecipe, type FileInput } from './file-first.js';
28
28
  import { Handle } from './handle.js';
29
29
  /**
30
30
  * Operations that may be invoked on a `gisl.anonymous()` client without
@@ -61,6 +61,21 @@ export interface GislCreateOptions extends ResolveCredentialsOptions, ResolveEnd
61
61
  * legitimately have no apiKey at construction time.
62
62
  */
63
63
  export declare function create(opts?: GislCreateOptions): Promise<ErgonomicClient>;
64
+ /**
65
+ * Operation types that need MORE THAN ONE input source, so they cannot be
66
+ * driven through the single-input {@link ErgonomicClient.operation} escape
67
+ * hatch — each has a dedicated multi-input builder (`merge(...)`,
68
+ * `files(...).archive(...)`, `file(a).watermark(b)`). Excluded from
69
+ * `operation()`'s op-type autocomplete.
70
+ */
71
+ export type MultiInputOperationType = 'merge' | 'archive' | 'image_watermark' | 'video_watermark' | 'audio_overlay' | 'audio_to_video';
72
+ /**
73
+ * Op types reachable via {@link ErgonomicClient.operation}: every
74
+ * {@link OperationType} except the {@link MultiInputOperationType} ones, widened
75
+ * with `(string & {})` so a genuinely-unknown (not-yet-in-contract) op type is
76
+ * still accepted while known single-input ops keep autocomplete.
77
+ */
78
+ export type SingleInputOperationType = Exclude<OperationType, MultiInputOperationType> | (string & {});
64
79
  /**
65
80
  * The ergonomic-client surface: `GislClient` (verbatim low-level API)
66
81
  * plus three ergonomic op-builder factories. Intersection type — at
@@ -91,6 +106,20 @@ export type ErgonomicClient = GislClient & {
91
106
  * returns a {@link Handle} whose `wait()`/`result()` partition per input.
92
107
  */
93
108
  files(inputs: ReadonlyArray<string | Blob | FileInput>): FilesRecipe;
109
+ /**
110
+ * Keyed multi-recipe batch entry point (FF7). Run N DISTINCT single-input
111
+ * keyed {@link Recipe}s as ONE workflow — build each via
112
+ * `client.file(input, key).<op>(...)` with a UNIQUE key, then
113
+ * `client.batch([r1, r2, …]).run()`. The partitioned {@link RunResult}
114
+ * addresses each entry's outputs by its caller key (`res.byKey('hero')`); one
115
+ * failed entry lands in `failed` without sinking the rest.
116
+ *
117
+ * v1 accepts ONLY single-input {@link Recipe} entries — the multi-input
118
+ * builders ({@link FilesRecipe} via `files(...)`, `merge(...)`, `archive(...)`,
119
+ * `watermark(...)`) are rejected pre-upload with a typed {@link GislConfigError}.
120
+ * `.run()`-only; `.submit()` / reattach are a follow-up.
121
+ */
122
+ batch(recipes: ReadonlyArray<Recipe>): BatchRecipe;
94
123
  /**
95
124
  * Reattach to a previously-created workflow (FF5a). Returns a client-bound
96
125
  * {@link Handle} you can `.status()` / `.wait()` / `.result()`. The handle
@@ -102,6 +131,8 @@ export type ErgonomicClient = GislClient & {
102
131
  compress(input: string | Blob, options?: Record<string, unknown>): OperationBuilder;
103
132
  convert(input: string | Blob, options?: Record<string, unknown>): OperationBuilder;
104
133
  thumbnail(input: string | Blob, options?: Record<string, unknown>): OperationBuilder;
134
+ /** Geometric transform (rotate/flip). Passthrough; the op is `planned` (server 422s until Lambdas ship). */
135
+ transform(input: string | Blob, options?: Record<string, unknown>): OperationBuilder;
105
136
  /**
106
137
  * Merge ordered-sequence factory (T3). Accepts a variadic list of assets
107
138
  * (strings/Blobs/`handle()`/`asset()`) optionally terminated by a
@@ -135,6 +166,44 @@ export type ErgonomicClient = GislClient & {
135
166
  creditsUsage(options?: CreditsUsageOptions): Promise<CreditsUsageResponse>;
136
167
  /** Effective account limits / tier-resolved caps (sugar for `getAccountLimits()`). */
137
168
  limits(): Promise<AccountLimits>;
169
+ /**
170
+ * Operation-capability read helper (qUhxfDA5). A typed projection over
171
+ * `getSchema()` that surfaces the tier-scoped operation-capability matrix,
172
+ * the output-property table, and the image-encode capability matrix —
173
+ * without dropping to the low-level `getSchema()` and its not-modified union.
174
+ *
175
+ * Called with no argument it returns the full {@link CapabilitiesSnapshot};
176
+ * called with an operation type it returns just that op's
177
+ * {@link OperationCapability}, or `undefined` when the op is absent from the
178
+ * server's capability matrix.
179
+ *
180
+ * Degraded fallback: if the client is configured to force conditional
181
+ * revalidation (a static `If-None-Match` in `config.headers`) the schema
182
+ * fetch may 304 with no body — in that case the snapshot is empty / the
183
+ * per-op lookup is `undefined`.
184
+ */
185
+ capabilities(): Promise<CapabilitiesSnapshot>;
186
+ capabilities(opType: OperationType | (string & {})): Promise<OperationCapability | undefined>;
187
+ /**
188
+ * Generic operation escape hatch (qUhxfDA5). Build + run a SINGLE-input,
189
+ * SINGLE-operation job for an op type with no first-class verb (e.g.
190
+ * `text_watermark`, `split`, or a not-yet-in-contract op). `options` reach the
191
+ * wire unchanged — there is NO pre-upload validation (the server validates) and
192
+ * NO preset resolution unless `opType` is `compress`. Prefer the typed verbs
193
+ * (`compress` / `convert` / `thumbnail`) when they exist — they add local
194
+ * validation.
195
+ *
196
+ * Multi-input operations (`merge`, `archive`, overlay watermarks — see
197
+ * {@link MultiInputOperationType}) are REJECTED at compile time: passing one
198
+ * of those literals is a type error (its dedicated builder is `merge(...)`,
199
+ * `files(...).archive(...)`, or `file(a).watermark(b)`). A genuinely-unknown
200
+ * (not-yet-in-contract) op string is still accepted.
201
+ *
202
+ * The generic parameter enforces the exclusion: a known single-input op or an
203
+ * unknown string maps to itself, while a {@link MultiInputOperationType}
204
+ * literal maps to `never` (so it cannot be passed).
205
+ */
206
+ operation<Op extends SingleInputOperationType>(opType: Op extends MultiInputOperationType ? never : Op, input: string | Blob, options?: Record<string, unknown>): OperationBuilder;
138
207
  };
139
208
  /**
140
209
  * The `gisl` namespace — primary ergonomic-layer entry point.
package/dist/gisl.js CHANGED
@@ -20,9 +20,10 @@ import { GislClient } from './client.js';
20
20
  import { GislConfigError, GislFeatureRequiresAuthError, GislMissingCredentialsError, } from './errors.js';
21
21
  import { resolveApiKey, resolveEndpoint, } from './credentials.js';
22
22
  import { OperationBuilder } from './builder.js';
23
+ import { validateVerbOptions, validateSingleOpConvertOptions, assertThumbnailDimensions, } from './ergonomic/option_validation.js';
23
24
  import { MergeBuilder, asset } from './merge.js';
24
25
  import { PresetDefaults } from './ergonomic/presets/index.js';
25
- import { Recipe, FilesRecipe, fileInput } from './file-first.js';
26
+ import { Recipe, FilesRecipe, BatchRecipe, fileInput } from './file-first.js';
26
27
  import { Handle } from './handle.js';
27
28
  // ---------------------------------------------------------------------------
28
29
  // Anonymous-capable operation allowlist (internal)
@@ -112,14 +113,42 @@ function wrapErgonomic(client, presetDefaults, scopedPresetDefaults) {
112
113
  return new FilesRecipe(resolved, [], presetDefaults, scopedPresetDefaults, target);
113
114
  };
114
115
  }
116
+ if (prop === 'batch') {
117
+ // Keyed multi-recipe batch entry point (FF7). Run N DISTINCT single-input
118
+ // keyed recipes as ONE workflow; run() partitions the RunResult per entry
119
+ // by the caller key given at `file(input, key)` time. Client-only ctor —
120
+ // each entry already captured its own preset defaults at `client.file(...)`
121
+ // time, so batch never re-plumbs presetDefaults/scopedPresetDefaults.
122
+ return (recipes) => new BatchRecipe(recipes, target);
123
+ }
115
124
  if (prop === 'workflow') {
116
125
  // Reattach to a previously-created workflow (FF5a). Returns a
117
126
  // client-bound Handle with no webhookSecret and no recipe key —
118
127
  // its RunResult is therefore keyless (succeeded[].key === null).
119
128
  return (id) => new Handle(id, undefined, target);
120
129
  }
121
- if (prop === 'compress' || prop === 'convert' || prop === 'thumbnail') {
130
+ if (prop === 'compress' || prop === 'convert' || prop === 'thumbnail' || prop === 'transform') {
122
131
  return (input, options = {}) => {
132
+ // ExVcchMz — validate the option bag pre-upload for the exported
133
+ // single-op builder so a bad bag (unknown key / missing thumbnail dims /
134
+ // missing convert target) fails locally instead of as a server 422.
135
+ // `compress` is EXCLUDED: it validates through the preset resolver
136
+ // (resolveCompressOptions / KNOWN_WIRE_FIELDS), not these guards.
137
+ // `convert` uses a SINGLE-OP-specific guard (NOT validateVerbOptions):
138
+ // the single-op builder has no positional format, so its target rides
139
+ // the bag as `output_format` — which the file-first convert guard would
140
+ // reject as positional-owned. `thumbnail` reuses the file-first guards
141
+ // (it has no positional-owned keys).
142
+ if (prop === 'convert')
143
+ validateSingleOpConvertOptions(options);
144
+ if (prop === 'thumbnail') {
145
+ validateVerbOptions('thumbnail', options);
146
+ assertThumbnailDimensions(options);
147
+ }
148
+ // `transform` is a passthrough (rotate/flip); no positional-owned keys
149
+ // and no required dims — just the generic key-validation.
150
+ if (prop === 'transform')
151
+ validateVerbOptions('transform', options);
123
152
  // T4b — pass client-scope presetDefaults into the builder so
124
153
  // .run()/.submit() consult the preset resolver. The Proxy's
125
154
  // closure carries the same reference for every per-call
@@ -178,6 +207,47 @@ function wrapErgonomic(client, presetDefaults, scopedPresetDefaults) {
178
207
  if (prop === 'limits') {
179
208
  return () => target.getAccountLimits();
180
209
  }
210
+ if (prop === 'capabilities') {
211
+ // qUhxfDA5 — READ/PROJECTION over getSchema() surfacing the three
212
+ // v2.124 capability fields (previously typed but with no ergonomic
213
+ // consumer). No arg → the full CapabilitiesSnapshot; an opType → that
214
+ // op's OperationCapability (or undefined when absent).
215
+ return async (opType) => {
216
+ const schema = await target.getSchema();
217
+ // capabilities() passes no conditional headers, so getSchema()
218
+ // normally returns the 200 hit with data. A 304 is only possible if
219
+ // the caller globally configured a conditional header (e.g. a static
220
+ // `If-None-Match` in `config.headers`) — an unusual, self-inflicted
221
+ // case. Rather than throw, degrade to an empty projection (documented
222
+ // on the method); a caller who forces revalidation gets no snapshot.
223
+ const data = schema.notModified ? undefined : schema.data;
224
+ const operations = data?.capabilities ?? {};
225
+ if (opType !== undefined) {
226
+ return operations[opType];
227
+ }
228
+ return {
229
+ operations,
230
+ outputProperties: data?.outputProperties ?? {},
231
+ ...(data?.imageEncodeCapabilities !== undefined
232
+ ? { imageEncode: data.imageEncodeCapabilities }
233
+ : {}),
234
+ };
235
+ };
236
+ }
237
+ if (prop === 'operation') {
238
+ // qUhxfDA5 — generic escape-hatch sibling of the single-op verbs
239
+ // (compress/convert/thumbnail). Builds a SINGLE-input, SINGLE-operation
240
+ // job for an op type with no typed verb (e.g. `text_watermark`, `split`,
241
+ // or a not-yet-in-contract op). Options ride through to the wire
242
+ // unchanged (no preset resolution unless opType is 'compress'); NO
243
+ // pre-upload validation — the server validates.
244
+ //
245
+ // Multi-input operations (merge, archive, image/video/audio overlay
246
+ // watermarks) canNOT be expressed here — they need multiple sources and
247
+ // have dedicated builders (`merge(...)`, `files(...).archive(...)`,
248
+ // `file(a).watermark(b)`). They are excluded from the op-type param.
249
+ return (opType, input, options = {}) => new OperationBuilder(target, opType, input, options, presetDefaults, scopedPresetDefaults);
250
+ }
181
251
  return Reflect.get(target, prop, receiver);
182
252
  },
183
253
  });
@@ -1,10 +1,11 @@
1
1
  export { GislClient, DEFAULT_MULTIPART_FIRST_CHUNK_SIZE } from './client.js';
2
2
  export { parseSseStream } from './sse.js';
3
- export type { CreditsUsageOptions, ListWorkflowsOptions, GetSchemaOptions, GetSchemaResult, PreflightClipError, PreflightClipsResult, ProbeWaitOptions, ProbeWaitResult, GislClientConfig, GislSseEvent, UploadOptions, WaitOptions, WorkflowCreatePayload, OperationDef, WorkflowSourcePayload, MultiInputSourcePayload, UploadSourcePayload, JobOutputSourcePayload, ExternalImportSourcePayload, ConnectionSourcePayload, JobInputV2Payload, JobDefinitionPayload, ExternalDestinationPayload, DeliveryPayload, DeliveryModePayload, DeliveryBundleFormatPayload, DeliverySelectionPayload, DeliverySelectionTypePayload, DeliveryOutputRefPayload, WorkflowProcessingPayload, ProcessingClassHintPayload, MultipartCheckpointState, _Sdk3HandCodedUploadedPart, _Sdk3HandCodedMultipartStatusResult, _Sdk3HandCodedPresignedPart, _Sdk3HandCodedPresignPartsResult, _Sdk3HandCodedKeepaliveResult, } from './types.js';
3
+ export type { CreditsUsageOptions, ListWorkflowsOptions, GetSchemaOptions, GetSchemaResult, CapabilitiesSnapshot, PreflightClipError, PreflightClipsResult, ProbeWaitOptions, ProbeWaitResult, GislClientConfig, GislSseEvent, GislSseParseFailure, UploadOptions, WaitOptions, WorkflowCreatePayload, OperationDef, WorkflowSourcePayload, MultiInputSourcePayload, UploadSourcePayload, JobOutputSourcePayload, ExternalImportSourcePayload, ConnectionSourcePayload, JobInputV2Payload, JobDefinitionPayload, ExternalDestinationPayload, DeliveryPayload, DeliveryModePayload, DeliveryBundleFormatPayload, DeliverySelectionPayload, DeliverySelectionTypePayload, DeliveryOutputRefPayload, WorkflowProcessingPayload, ProcessingClassHintPayload, MultipartCheckpointState, _Sdk3HandCodedUploadedPart, _Sdk3HandCodedMultipartStatusResult, _Sdk3HandCodedPresignedPart, _Sdk3HandCodedPresignPartsResult, _Sdk3HandCodedKeepaliveResult, } from './types.js';
4
4
  export { uploadSource, jobOutputSource, externalImportSource, connectionSource, } from './types.js';
5
5
  export type { GislConfigErrorMetadata } from './errors.js';
6
6
  export { GislError, GislApiError, GislValidationError, GislBalanceExhaustedError, GislTierRestrictedError, GislFeatureTierRestrictedError, GislFeatureNotAvailableError, GislWorkflowExpiredError, GislProbePendingError, GislAuthError, GislUploadCapExceededError, GislMultipartPartError, GislMultipartPartCountError, GislMultipartSessionNotFoundError, GislMultipartSessionOwnershipError, GislMultipartSessionAuthRequiredError, GislTimeoutError, GislAbortError, GislNetworkError, GislConfigError, GislMissingCredentialsError, GislFeatureRequiresAuthError, GislUndeclaredAssetError, GislUnusedAssetError, GislPerInputOptionsNotSupportedError, GislChainCardinalityMismatchError, GislBundleAlreadyArchivedError, GislNoSuchKeyError, GislSinkError, GislItemFailedError, GislResultNotReadyError, } from './errors.js';
7
7
  export type { GislApiErrorOptions, GislUploadCapKind } from './errors.js';
8
+ export type { ErrorCategory } from './generated/sdk_spec/errors.js';
8
9
  export { RunResult } from './file-first.js';
9
10
  export type { OutputFile, ItemResult, ItemFailure, Manifest, Downloader, } from './file-first.js';
10
11
  export { Recipe, fileInput } from './file-first.js';
@@ -15,9 +16,10 @@ export { ArchivedRecipe } from './file-first.js';
15
16
  export type { ArchiveRecipeOptions } from './file-first.js';
16
17
  export { WatermarkedRecipe } from './file-first.js';
17
18
  export type { WatermarkWireOp } from './file-first.js';
19
+ export { BatchRecipe } from './file-first.js';
18
20
  export { Handle, StatusSnapshot } from './handle.js';
19
21
  export { gisl, create } from './gisl.js';
20
- export type { GislCreateOptions, Environment, ErgonomicClient } from './gisl.js';
22
+ export type { GislCreateOptions, Environment, ErgonomicClient, SingleInputOperationType, MultiInputOperationType, } from './gisl.js';
21
23
  export { presetDefaults, PresetDefaults, type PresetMedia, type PresetOp, type AnyPresetOptions, ImageCompressPresetOptions, type ImageCompressPresetOptionsInput, AudioCompressPresetOptions, type AudioCompressPresetOptionsInput, VideoCompressPresetOptions, type VideoCompressPresetOptionsInput, DocumentPdfCompressPresetOptions, type DocumentPdfCompressPresetOptionsInput, DocumentOfficeCompressPresetOptions, type DocumentOfficeCompressPresetOptionsInput, DocumentOdfCompressPresetOptions, type DocumentOdfCompressPresetOptionsInput, DocumentEpubCompressPresetOptions, type DocumentEpubCompressPresetOptionsInput, OptimizeFor, ImageMetadataPolicy, ImageFormat, VideoCodec, VideoPreset, VideoFit, AudioBitrate, AudioCodec, AudioSampleRate, PdfProfile, PdfColorspace, } from './ergonomic/presets/index.js';
22
24
  export { OperationBuilder, MapEachBuilder } from './builder.js';
23
25
  export { MergeBuilder, asset, handle, clip } from './merge.js';
@@ -25,14 +27,14 @@ export type { Asset, ClipEntry, ClipOptions, MergeMediaKind, MergeOptions, Seque
25
27
  export type { Artifact, ArtifactRef, JobBreakdown, OperationBreakdown, ProcessingProgressEvent, ProgressEvent, ResolvedOptions, ResolvedOptionsSources, Result, RunOptions, SubmitOptions, UploadProgressEvent, } from './builder.js';
26
28
  export { PRESET_VERSION, resolveCompressOptions } from './ergonomic/preset_resolver.js';
27
29
  export type { ResolveCompressOptionsInput, ResolveCompressOptionsOutput, } from './ergonomic/preset_resolver.js';
28
- export type { AccountLimits, AccountLimitsLimits, AccountLimitEntry, AudioWatermarkDecodeRequest, AudioWatermarkDecodeResponse, ContactRequest, CreditsBalanceResponse, CreditsUsageResponse, CreditTransaction, ExternalImportCreatedResponse, ExternalImportRequest, LoginUserRequest, LoginUser200ResponseData, LoginUser200ResponseDataUser, WorkflowCancelResponse, WorkflowResumeResponse, WorkflowPausedDetail, WorkflowPausedDetailLinks, UploadResponse, UploadConstraintsApplied, UploadProbeResponse, UploadProbeMediaMetadata, MultipartInitiateRequestMetadataHint, WorkflowCreateResponse, WorkflowStatusResponse, WorkflowListResponse, WorkflowSummary, WorkflowDownloadResponse, MetadataResponse, MetadataResponseDimensions, MetadataResponseExif, MetadataResponseExifGps, OperationsSchemaResponse, OperationSchemaDefinition, MimeGroupSchema, OptionSchema, PerValueAvailabilityEntry, PerRoleCardinalityEntry, RetryResponse, JobDownload, OperationDownload, DownloadBundle, WebhookPayload, WebhookOperationContext, JobResponse, OperationResponse, OperationResult, OperationResultMetrics, ExternalDestination, Delivery, DeliveryPlan, DeliveryPlanOutput, WorkflowProcessing, ProcessingPlan, ProcessingPlanJob, WorkflowEdge, WorkflowWarning, JobInputV2, WorkflowSource, UploadSource, JobOutputSource, ConnectionSource, ExternalImportToken, BalanceExhaustedResponse, BalanceExhaustedResponseAllOfLinks, TierRestrictionResponse, FeatureTierRestrictedResponse, FeatureNotAvailableResponse, FeatureViolation, WorkflowExpiredResponse, ProbePendingResponse, AuthErrorResponse, } from '@giveitsmaller/contracts/openapi';
30
+ export type { AccountLimits, AccountLimitsLimits, AccountLimitEntry, AudioWatermarkDecodeRequest, AudioWatermarkDecodeResponse, ContactRequest, CreditsBalanceResponse, CreditsUsageResponse, CreditTransaction, ExternalImportCreatedResponse, ExternalImportRequest, LoginUserRequest, LoginUser200ResponseData, LoginUser200ResponseDataUser, WorkflowCancelResponse, WorkflowResumeResponse, WorkflowPausedDetail, WorkflowPausedDetailLinks, UploadResponse, UploadConstraintsApplied, UploadProbeResponse, UploadProbeMediaMetadata, MultipartInitiateRequestMetadataHint, WorkflowCreateResponse, WorkflowStatusResponse, WorkflowListResponse, WorkflowSummary, WorkflowDownloadResponse, MetadataResponse, MetadataResponseDimensions, MetadataResponseExif, MetadataResponseExifGps, OperationsSchemaResponse, OperationCapability, OutputProperties, ImageEncodeCapabilities, OperationSchemaDefinition, MimeGroupSchema, OptionSchema, PerValueAvailabilityEntry, PerRoleCardinalityEntry, RetryResponse, JobDownload, OperationDownload, DownloadBundle, WebhookPayload, WebhookOperationContext, JobResponse, OperationResponse, OperationResult, OperationResultMetrics, ExternalDestination, Delivery, DeliveryPlan, DeliveryPlanOutput, WorkflowProcessing, ProcessingPlan, ProcessingPlanJob, WorkflowEdge, WorkflowWarning, JobInputV2, WorkflowSource, UploadSource, JobOutputSource, ConnectionSource, ExternalImportToken, BalanceExhaustedResponse, BalanceExhaustedResponseAllOfLinks, TierRestrictionResponse, FeatureTierRestrictedResponse, FeatureNotAvailableResponse, FeatureViolation, WorkflowExpiredResponse, ProbePendingResponse, AuthErrorResponse, } from '@giveitsmaller/contracts/openapi';
29
31
  export { AudioWatermarkDecodeRequestMethodHintEnum, AudioWatermarkDecodeResponseMethodEnum, OperationInputModel, ExternalImportRequestProviderHintEnum, ContactSubject, CreditTransactionSourceBucket, UploadProbeStatus, UploadProbeProcessingClass, WorkflowCancelBillingEffect, WorkflowPauseRequiredAction, WorkflowStatus, WarningType, WorkflowWarningSeverity, OperationType, SseEventType, CallbackEventType, OperationStatus, JobStatus, JobInputV2RoleEnum, AuthErrorType, TierRestrictionKind, BalanceExhaustedResponseRequiredActionEnum, ProcessingClassReason, DeliveryPlanReason, UserTier, ProcessingClass, } from '@giveitsmaller/contracts/openapi';
30
32
  export type { SseOperationProgressData, SseOperationCompletedData, SseOperationFailedData, SseJobCompletedData, SseJobFailedData, SseWorkflowTerminalData, } from '@giveitsmaller/contracts/openapi';
31
33
  export type { MultiOutputCompletion, PageIndexed, PositionIndexed, Unindexed, } from '@giveitsmaller/contracts/asyncapi';
32
34
  import type { MultiOutputCompletion as _MultiOutputCompletion } from '@giveitsmaller/contracts/asyncapi';
33
35
  export type OperationResultOutputEntry = _MultiOutputCompletion['outputs'][number];
34
- export type { CompressImageOptions, CompressImageJpegOptions, CompressImagePngOptions, CompressImageAvifOptions, CompressVideoOptions, CompressAudioOptions, CompressDocumentPdfOptions, CompressDocumentOfficeOptions, CompressDocumentOdfOptions, CompressDocumentEpubOptions, ThumbnailImageOptions, ThumbnailVideoOptions, ThumbnailDocumentOptions, ConvertImageOptions, ConvertVideoOptions, ConvertAudioOptions, ConvertDocumentPdfOptions, MergeImageOptions, MergeVideoOptions, MergeVideoPerInputOptions, MergeAudioOptions, MergeAudioPerInputOptions, ArchiveOptions, ImageWatermarkImageOptions, ImageWatermarkImageGifOptions, TextWatermarkImageOptions, CustomLumaVideoOptions, AudioOverlayAudioOptions, AudioOverlayVideoOptions, AudioWatermarkAudioOptions, AudioWatermarkVideoOptions, AudioToVideoAudioOptions, VideoWatermarkVideoOptions, VideoTextWatermarkVideoOptions, SplitImageGifOptions, SplitDocumentPdfOptions, SplitAudioOptions, SplitVideoOptions, } from '@giveitsmaller/contracts/operations';
36
+ export type { CompressImageOptions, CompressImageJpegOptions, CompressImagePngOptions, CompressImageAvifOptions, CompressVideoOptions, CompressAudioOptions, CompressDocumentPdfOptions, CompressDocumentOfficeOptions, CompressDocumentOdfOptions, CompressDocumentEpubOptions, ThumbnailImageOptions, ThumbnailVideoOptions, ThumbnailDocumentOptions, TransformImageOptions, TransformImageGifOptions, TransformVideoOptions, TransformDocumentPdfOptions, ConvertImageOptions, ConvertVideoOptions, ConvertAudioOptions, ConvertDocumentPdfOptions, MergeImageOptions, MergeVideoOptions, MergeVideoPerInputOptions, MergeAudioOptions, MergeAudioPerInputOptions, ArchiveOptions, ImageWatermarkImageOptions, ImageWatermarkImageGifOptions, TextWatermarkImageOptions, CustomLumaVideoOptions, AudioOverlayAudioOptions, AudioOverlayVideoOptions, AudioWatermarkAudioOptions, AudioWatermarkVideoOptions, AudioToVideoAudioOptions, VideoWatermarkVideoOptions, VideoTextWatermarkVideoOptions, SplitImageGifOptions, SplitDocumentPdfOptions, SplitAudioOptions, SplitVideoOptions, } from '@giveitsmaller/contracts/operations';
35
37
  export { ImageWatermarkImageAnchor, ImageWatermarkImageGifAnchor, TextWatermarkImageAnchor, TextWatermarkImageFontFamily, TextWatermarkImageWatermarkMode, AudioOverlayAudioMode, AudioOverlayVideoMode, AudioOverlayVideoNoAudioTrackBehaviour, AudioWatermarkAudioMethod, AudioWatermarkAudioRobustness, AudioWatermarkAudioDensity, AudioWatermarkVideoMethod, AudioWatermarkVideoRobustness, AudioWatermarkVideoDensity, AudioToVideoAudioOutputResolution, AudioToVideoAudioImageFit, AudioToVideoAudioOutputFormat, VideoWatermarkVideoAnchor, VideoTextWatermarkVideoFontFamily, VideoTextWatermarkVideoWatermarkMode, VideoTextWatermarkVideoAnchor, SplitImageGifOutputFormat, SplitDocumentPdfMode, SplitAudioMode, SplitAudioPrecision, SplitVideoMode, SplitVideoPrecision, } from '@giveitsmaller/contracts/operations';
36
- export { archiveMetadata, audioOverlayMetadata, audioWatermarkMetadata, compressMetadata, convertMetadata, customLumaMetadata, imageWatermarkMetadata, mergeMetadata, textWatermarkMetadata, thumbnailMetadata, audioToVideoMetadata, videoWatermarkMetadata, videoTextWatermarkMetadata, splitMetadata, } from '@giveitsmaller/contracts/operations';
37
- export type { ConvertOptions, ThumbnailOptions, TextWatermarkOptions, WatermarkOptions, WatermarkAnchor, } from './ergonomic/option_types.js';
38
+ export { archiveMetadata, audioOverlayMetadata, audioWatermarkMetadata, compressMetadata, convertMetadata, customLumaMetadata, imageWatermarkMetadata, mergeMetadata, textWatermarkMetadata, thumbnailMetadata, audioToVideoMetadata, videoWatermarkMetadata, videoTextWatermarkMetadata, splitMetadata, transformMetadata, } from '@giveitsmaller/contracts/operations';
39
+ export type { ConvertOptions, ThumbnailOptions, TransformOptions, TextWatermarkOptions, WatermarkOptions, WatermarkOverlay, WatermarkAnchor, } from './ergonomic/option_types.js';
38
40
  export type { OperationMetadata, AvailabilityValue, AvailabilityEntry, FeatureEntry, MimeGroupMetadata, OptionMetadata, ProcessingClassConstraints, } from '@giveitsmaller/contracts/operations';
@@ -55,6 +55,11 @@ export { ArchivedRecipe } from './file-first.js';
55
55
  // post-watermark ops on, then run()/submit(). Routes image_watermark / video_watermark
56
56
  // by base media; gates planned/unsupported bases locally pre-upload.
57
57
  export { WatermarkedRecipe } from './file-first.js';
58
+ // File-first keyed multi-recipe batch (FF7 / MFaCjL8d) — `client.batch([r1, r2, …])`
59
+ // runs N DISTINCT single-input keyed recipes as ONE workflow; run() partitions the
60
+ // RunResult by each entry's caller key. v1 = single-input keyed, run()-only; the
61
+ // multi-input builders are rejected pre-upload.
62
+ export { BatchRecipe } from './file-first.js';
58
63
  // `HttpDownloader` (Node streaming downloader) is re-exported from the Node-only
59
64
  // entry `index.ts`, NOT here — it statically imports node:fs/node:stream.
60
65
  // `projectDownloadsToRunResult` is intentionally NOT re-exported here — it is an
@@ -109,4 +114,7 @@ AudioToVideoAudioOutputResolution, AudioToVideoAudioImageFit, AudioToVideoAudioO
109
114
  // surfaces as `GislFeatureNotAvailableError`).
110
115
  export { archiveMetadata, audioOverlayMetadata, audioWatermarkMetadata, compressMetadata, convertMetadata, customLumaMetadata, imageWatermarkMetadata, mergeMetadata, textWatermarkMetadata, thumbnailMetadata,
111
116
  // New planned operation metadata sidecars — contracts v2.15 (AJCLLGaG).
112
- audioToVideoMetadata, videoWatermarkMetadata, videoTextWatermarkMetadata, splitMetadata, } from '@giveitsmaller/contracts/operations';
117
+ audioToVideoMetadata, videoWatermarkMetadata, videoTextWatermarkMetadata, splitMetadata,
118
+ // transform is `availability: planned` — consumers can inspect this sidecar to
119
+ // gate UI before submitting (the API otherwise returns feature_not_available/422). T4.
120
+ transformMetadata, } from '@giveitsmaller/contracts/operations';
package/dist/merge.d.ts CHANGED
@@ -90,13 +90,25 @@ export interface MergeOptions {
90
90
  readonly crossfadeDuration?: number;
91
91
  readonly gapDuration?: number;
92
92
  readonly normalizeAudio?: boolean;
93
+ /**
94
+ * Video re-encode policy (`auto` | `always` | `never`). Passed through
95
+ * verbatim — `codec`/`crf`/`preset`/`targetResolution`/`targetSize` are only
96
+ * honoured by the worker when re-encoding (`auto`/`always`); the server owns
97
+ * that dependency validation (the SDK is a passthrough allowlist, same as the
98
+ * pre-existing codec/crf/preset fields). Video merge only.
99
+ */
100
+ readonly reEncodeMode?: string;
93
101
  readonly codec?: string;
94
102
  readonly crf?: number;
95
103
  readonly preset?: string;
104
+ /** Video output dimensions `WxH` (e.g. `"1920x1080"`); omit to inherit from inputs. Video merge only. */
105
+ readonly targetResolution?: string;
96
106
  readonly targetSize?: string | number;
97
107
  readonly transitionDuration?: number;
98
108
  readonly fps?: number;
99
109
  readonly durationPerImage?: number;
110
+ /** Milliseconds between frames for an animated-GIF image merge (`output_type: gif`). Image merge only. */
111
+ readonly delay?: number;
100
112
  readonly loopCount?: number;
101
113
  readonly output?: string;
102
114
  readonly videoFormat?: string;
package/dist/merge.js CHANGED
@@ -406,12 +406,16 @@ export class MergeBuilder {
406
406
  out.gapDuration = o.gapDuration;
407
407
  // Video only.
408
408
  if (mediaKind === 'video') {
409
+ if (o.reEncodeMode !== undefined)
410
+ out.reEncodeMode = o.reEncodeMode;
409
411
  if (o.codec !== undefined)
410
412
  out.codec = o.codec;
411
413
  if (o.crf !== undefined)
412
414
  out.crf = o.crf;
413
415
  if (o.preset !== undefined)
414
416
  out.preset = o.preset;
417
+ if (o.targetResolution !== undefined)
418
+ out.targetResolution = o.targetResolution;
415
419
  if (o.targetSize !== undefined)
416
420
  out.targetSize = o.targetSize;
417
421
  }
@@ -423,6 +427,8 @@ export class MergeBuilder {
423
427
  out.fps = o.fps;
424
428
  if (o.durationPerImage !== undefined)
425
429
  out.durationPerImage = o.durationPerImage;
430
+ if (o.delay !== undefined)
431
+ out.delay = o.delay;
426
432
  if (o.loopCount !== undefined)
427
433
  out.loopCount = o.loopCount;
428
434
  if (o.videoFormat !== undefined)
@@ -519,12 +525,16 @@ export function wireMergeOptions(opts, mediaKind) {
519
525
  out.gap_duration = opts.gapDuration;
520
526
  // Video only.
521
527
  if (mediaKind === 'video') {
528
+ if (opts.reEncodeMode !== undefined)
529
+ out.re_encode_mode = opts.reEncodeMode;
522
530
  if (opts.codec !== undefined)
523
531
  out.codec = opts.codec;
524
532
  if (opts.crf !== undefined)
525
533
  out.crf = opts.crf;
526
534
  if (opts.preset !== undefined)
527
535
  out.preset = opts.preset;
536
+ if (opts.targetResolution !== undefined)
537
+ out.target_resolution = opts.targetResolution;
528
538
  if (opts.targetSize !== undefined) {
529
539
  out.target_size_bytes = typeof opts.targetSize === 'number'
530
540
  ? opts.targetSize
@@ -540,6 +550,8 @@ export function wireMergeOptions(opts, mediaKind) {
540
550
  out.fps = opts.fps;
541
551
  if (opts.durationPerImage !== undefined)
542
552
  out.duration_per_image = opts.durationPerImage;
553
+ if (opts.delay !== undefined)
554
+ out.delay = opts.delay;
543
555
  if (opts.loopCount !== undefined)
544
556
  out.loop_count = opts.loopCount;
545
557
  if (opts.videoFormat !== undefined)
@@ -0,0 +1,37 @@
1
+ /**
2
+ * A rate-limit snapshot derived from the `x-ratelimit-*` response headers.
3
+ * `resetSeconds` is the server's seconds-to-reset value (`X-RateLimit-Reset`),
4
+ * NOT an absolute epoch — read {@link GislApiError.rateLimit} to obtain one.
5
+ */
6
+ export interface RateLimitSnapshot {
7
+ readonly limit: number;
8
+ readonly remaining: number;
9
+ readonly resetSeconds: number;
10
+ }
11
+ /**
12
+ * Whether an HTTP status is retryable per the API-error taxonomy: request
13
+ * timeout (408), rate-limit (429), or any 5xx (500–599). BOUNDED at 599 — a
14
+ * non-standard 6xx-and-up status is NOT classified retryable.
15
+ *
16
+ * Now value-identical to the S3-PUT retry predicate (`isRetryableStatus`) in
17
+ * `client.ts` (both are `408 || 429 || 500-599` after qz7MjNTy), but kept
18
+ * DELIBERATELY SEPARATE: they guard different retry paths (S3-PUT chunk uploads
19
+ * vs the API-error taxonomy) and may diverge again, so they must NOT be merged.
20
+ */
21
+ export declare function isApiRetryableStatus(status: number): boolean;
22
+ export declare function parseRetryAfterMs(headerValue: string | undefined): number | undefined;
23
+ /**
24
+ * The server-suggested back-off delay in WHOLE seconds, parsed from the
25
+ * `Retry-After` response header. Derived from {@link parseRetryAfterMs}
26
+ * (`Math.floor(ms / 1000)`) so the semantics mirror the retry-loop parser:
27
+ * absent / malformed / zero / past all collapse to `undefined`, as does a
28
+ * sub-second future HTTP-date (floors to zero → treated as absent).
29
+ */
30
+ export declare function retryAfterSecondsFromHeaders(headers: Record<string, string> | undefined): number | undefined;
31
+ /**
32
+ * A rate-limit snapshot parsed from the `x-ratelimit-*` response headers.
33
+ * Present ONLY when `x-ratelimit-limit`, `x-ratelimit-remaining`, and
34
+ * `x-ratelimit-reset` all parse as non-negative integers; otherwise
35
+ * `undefined` (a partial set is not a usable snapshot).
36
+ */
37
+ export declare function rateLimitFromHeaders(headers: Record<string, string> | undefined): RateLimitSnapshot | undefined;
@@ -0,0 +1,86 @@
1
+ // Shared HTTP retry-metadata helpers. Extracted here so `errors.ts` can consume
2
+ // them WITHOUT importing `client.ts`: `client.ts` already imports `errors.ts`, so
3
+ // pulling the module-private `parseRetryAfterMs` back out of `client.ts` would
4
+ // form a `client → errors → client` circular import. The millisecond parser is
5
+ // MOVED here verbatim; `client.ts` re-imports it so the retry-loop timing stays
6
+ // byte-identical.
7
+ /**
8
+ * Whether an HTTP status is retryable per the API-error taxonomy: request
9
+ * timeout (408), rate-limit (429), or any 5xx (500–599). BOUNDED at 599 — a
10
+ * non-standard 6xx-and-up status is NOT classified retryable.
11
+ *
12
+ * Now value-identical to the S3-PUT retry predicate (`isRetryableStatus`) in
13
+ * `client.ts` (both are `408 || 429 || 500-599` after qz7MjNTy), but kept
14
+ * DELIBERATELY SEPARATE: they guard different retry paths (S3-PUT chunk uploads
15
+ * vs the API-error taxonomy) and may diverge again, so they must NOT be merged.
16
+ */
17
+ export function isApiRetryableStatus(status) {
18
+ return status === 408 || status === 429 || (status >= 500 && status <= 599);
19
+ }
20
+ // Parse an HTTP `Retry-After` header into milliseconds. Accepts the two RFC
21
+ // 9110 forms: delta-seconds (e.g. "5") or an HTTP-date. Returns `undefined`
22
+ // for an absent / unparseable / negative value (caller falls back to its own
23
+ // backoff). A past HTTP-date clamps to 0.
24
+ export function parseRetryAfterMs(headerValue) {
25
+ if (headerValue === undefined)
26
+ return undefined;
27
+ const trimmed = headerValue.trim();
28
+ if (trimmed === '')
29
+ return undefined;
30
+ let ms;
31
+ if (/^\d+$/.test(trimmed)) {
32
+ ms = Number(trimmed) * 1000;
33
+ }
34
+ else {
35
+ const when = Date.parse(trimmed);
36
+ if (Number.isNaN(when))
37
+ return undefined;
38
+ ms = when - Date.now();
39
+ }
40
+ // A non-positive Retry-After (e.g. "0" or a past HTTP-date) must NOT short-
41
+ // circuit the backoff to zero — treat it as absent so the caller falls back
42
+ // to jitter and the loop can't busy-poll until timeout.
43
+ return ms > 0 ? ms : undefined;
44
+ }
45
+ /**
46
+ * The server-suggested back-off delay in WHOLE seconds, parsed from the
47
+ * `Retry-After` response header. Derived from {@link parseRetryAfterMs}
48
+ * (`Math.floor(ms / 1000)`) so the semantics mirror the retry-loop parser:
49
+ * absent / malformed / zero / past all collapse to `undefined`, as does a
50
+ * sub-second future HTTP-date (floors to zero → treated as absent).
51
+ */
52
+ export function retryAfterSecondsFromHeaders(headers) {
53
+ const ms = parseRetryAfterMs(headers?.['retry-after']);
54
+ if (ms === undefined)
55
+ return undefined;
56
+ const seconds = Math.floor(ms / 1000);
57
+ return seconds > 0 ? seconds : undefined;
58
+ }
59
+ // Parse a non-negative integer response header. Returns `undefined` for an
60
+ // absent value or anything that isn't a bare run of decimal digits (so a
61
+ // float, sign, or units suffix is rejected rather than silently truncated).
62
+ function parseIntHeader(headerValue) {
63
+ if (headerValue === undefined)
64
+ return undefined;
65
+ const trimmed = headerValue.trim();
66
+ if (!/^\d+$/.test(trimmed))
67
+ return undefined;
68
+ return Number(trimmed);
69
+ }
70
+ /**
71
+ * A rate-limit snapshot parsed from the `x-ratelimit-*` response headers.
72
+ * Present ONLY when `x-ratelimit-limit`, `x-ratelimit-remaining`, and
73
+ * `x-ratelimit-reset` all parse as non-negative integers; otherwise
74
+ * `undefined` (a partial set is not a usable snapshot).
75
+ */
76
+ export function rateLimitFromHeaders(headers) {
77
+ if (headers === undefined)
78
+ return undefined;
79
+ const limit = parseIntHeader(headers['x-ratelimit-limit']);
80
+ const remaining = parseIntHeader(headers['x-ratelimit-remaining']);
81
+ const resetSeconds = parseIntHeader(headers['x-ratelimit-reset']);
82
+ if (limit === undefined || remaining === undefined || resetSeconds === undefined) {
83
+ return undefined;
84
+ }
85
+ return { limit, remaining, resetSeconds };
86
+ }
package/dist/sse.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import type { GislSseEvent } from './types.js';
1
+ import type { GislSseEvent, GislSseParseFailure } from './types.js';
2
2
  /**
3
3
  * Parse an SSE stream from a fetch Response into an AsyncIterable of typed events.
4
4
  *
@@ -27,4 +27,5 @@ import type { GislSseEvent } from './types.js';
27
27
  */
28
28
  export declare function parseSseStream(response: Response, opts?: {
29
29
  signal?: AbortSignal;
30
+ onParseError?: (diagnostic: GislSseParseFailure) => void;
30
31
  }): AsyncGenerator<GislSseEvent>;
package/dist/sse.js CHANGED
@@ -76,15 +76,27 @@ export async function* parseSseStream(response, opts = {}) {
76
76
  // Empty line = end of event
77
77
  if (dataLines.length > 0) {
78
78
  const rawData = dataLines.join('\n');
79
+ const frameEvent = eventType || 'message';
79
80
  let parsed;
80
81
  try {
81
82
  parsed = JSON.parse(rawData);
82
83
  }
83
- catch {
84
- parsed = rawData;
84
+ catch (err) {
85
+ // TYNjcjpo — a malformed-JSON frame is SKIPPED (not yielded as a
86
+ // raw string) so the stream stays resilient, but the failure is
87
+ // surfaced via the optional onParseError diagnostic rather than
88
+ // silently lost. Identical to the PHP `flushSseFrame` drop-path.
89
+ opts.onParseError?.({
90
+ raw: rawData,
91
+ event: frameEvent,
92
+ error: err instanceof Error ? err.message : String(err),
93
+ });
94
+ eventType = '';
95
+ dataLines = [];
96
+ continue;
85
97
  }
86
98
  yield {
87
- event: eventType || 'message',
99
+ event: frameEvent,
88
100
  data: parsed,
89
101
  };
90
102
  }
@@ -124,15 +136,23 @@ export async function* parseSseStream(response, opts = {}) {
124
136
  // not be yielded once the consumer has abandoned the stream.
125
137
  if (!aborted && dataLines.length > 0) {
126
138
  const rawData = dataLines.join('\n');
139
+ const frameEvent = eventType || 'message';
127
140
  let parsed;
128
141
  try {
129
142
  parsed = JSON.parse(rawData);
130
143
  }
131
- catch {
132
- parsed = rawData;
144
+ catch (err) {
145
+ // TYNjcjpo — trailing-flush malformed frame: skip + diagnostic (same as
146
+ // the in-loop path above).
147
+ opts.onParseError?.({
148
+ raw: rawData,
149
+ event: frameEvent,
150
+ error: err instanceof Error ? err.message : String(err),
151
+ });
152
+ return;
133
153
  }
134
154
  yield {
135
- event: eventType || 'message',
155
+ event: frameEvent,
136
156
  data: parsed,
137
157
  };
138
158
  }
package/dist/types.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import type { OperationType, OperationsSchemaResponse, CallbackEventType, SseEventType, SseOperationProgressData, SseOperationCompletedData, SseOperationFailedData, SseJobCompletedData, SseJobFailedData, SseWorkflowTerminalData, MultipartInitiateRequestMetadataHint, UploadProbeResponse } from '@giveitsmaller/contracts/openapi';
1
+ import type { OperationType, OperationsSchemaResponse, OperationCapability, OutputProperties, ImageEncodeCapabilities, CallbackEventType, SseEventType, SseOperationProgressData, SseOperationCompletedData, SseOperationFailedData, SseJobCompletedData, SseJobFailedData, SseWorkflowTerminalData, MultipartInitiateRequestMetadataHint, UploadProbeResponse } from '@giveitsmaller/contracts/openapi';
2
2
  import type { JobInputV2RoleEnum } from '@giveitsmaller/contracts/openapi';
3
3
  export interface GislClientConfig {
4
4
  baseUrl: string;
@@ -227,6 +227,32 @@ export type GetSchemaResult = {
227
227
  etag?: string;
228
228
  lastModified?: string;
229
229
  };
230
+ /**
231
+ * Typed projection of the operation-capability surface returned by
232
+ * {@link ErgonomicClient.capabilities} (qUhxfDA5). Bundles the three v2.124
233
+ * capability fields of `OperationsSchemaResponse` — previously typed but with
234
+ * no ergonomic consumer — so a caller can read them without dropping to
235
+ * `getSchema()` and its not-modified union.
236
+ *
237
+ * Mirrors the PHP `Gisl\Sdk\Ergonomic\CapabilitiesSnapshot` value object.
238
+ */
239
+ export interface CapabilitiesSnapshot {
240
+ /**
241
+ * Tier-scoped operation-capability matrix, keyed by operation type
242
+ * (`compress`, `convert`, …). Empty when the server omits the field.
243
+ */
244
+ readonly operations: Record<string, OperationCapability>;
245
+ /**
246
+ * Output-format property table (`hasAudioTrack` / `isAnimated`), keyed by
247
+ * `output_format`. Tier-invariant. Empty when the server omits the field.
248
+ */
249
+ readonly outputProperties: Record<string, OutputProperties>;
250
+ /**
251
+ * Pre-flight image-encode capability matrix (`webpQualitySupported`,
252
+ * `backgroundFlatten`). Tier-invariant. `undefined` when the server omits it.
253
+ */
254
+ readonly imageEncode?: ImageEncodeCapabilities;
255
+ }
230
256
  export interface CreditsUsageOptions {
231
257
  /**
232
258
  * Page size. Server defaults to 20 and rejects values outside `[1, 100]`
@@ -373,6 +399,22 @@ export type GislSseEvent = {
373
399
  event: string;
374
400
  data: unknown;
375
401
  };
402
+ /**
403
+ * A typed, non-throwing diagnostic surfaced when an SSE frame's `data:` body fails
404
+ * to JSON-parse (TYNjcjpo). The malformed frame is SKIPPED from the event stream —
405
+ * a long-running consumer must not break on one garbled server frame — but the
406
+ * failure is observable via the `onParseError` callback on `streamEvents` /
407
+ * `parseSseStream` rather than silently lost. Mirrors the PHP `GislSseParseFailure`
408
+ * value object; the shape is identical across the two SDKs (cross-SDK parity).
409
+ */
410
+ export interface GislSseParseFailure {
411
+ /** The joined `data:` line(s) that failed to parse. */
412
+ readonly raw: string;
413
+ /** The frame's event type (or `'message'` when the frame had no `event:` field). */
414
+ readonly event: string;
415
+ /** The parse error message (e.g. the `JSON.parse` `SyntaxError` text). */
416
+ readonly error: string;
417
+ }
376
418
  export interface UploadOptions {
377
419
  /** Called with bytes uploaded so far (only for multipart) */
378
420
  onProgress?: (uploadedBytes: number, totalBytes: number) => void;