@giveitsmaller/sdk 0.4.0 → 0.6.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 +7 -0
- package/dist/client.d.ts +86 -2
- package/dist/client.js +942 -33
- package/dist/errors.d.ts +105 -1
- package/dist/errors.js +107 -0
- package/dist/index.d.ts +3 -3
- package/dist/index.js +3 -1
- package/dist/sse.d.ts +20 -1
- package/dist/sse.js +62 -3
- package/dist/types.d.ts +132 -0
- package/package.json +2 -2
package/dist/errors.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { AuthErrorResponse, BalanceExhaustedResponse, FeatureNotAvailableResponse, FeatureTierRestrictedResponse, TierRestrictionResponse, WorkflowExpiredResponse } from '@giveitsmaller/contracts/openapi';
|
|
1
|
+
import type { AuthErrorResponse, BalanceExhaustedResponse, FeatureNotAvailableResponse, FeatureTierRestrictedResponse, TierRestrictionResponse, UploadDurationExceedsTierResponse, UploadSizeExceedsTierResponse, WorkflowExpiredResponse } from '@giveitsmaller/contracts/openapi';
|
|
2
2
|
export declare class GislError extends Error {
|
|
3
3
|
constructor(message: string);
|
|
4
4
|
}
|
|
@@ -70,9 +70,113 @@ export declare class GislAuthError extends GislApiError {
|
|
|
70
70
|
readonly payload: AuthErrorResponse;
|
|
71
71
|
constructor(statusCode: number, errorMessage: string, payload: AuthErrorResponse, path?: string, extra?: Omit<GislApiErrorOptions, 'payload'>);
|
|
72
72
|
}
|
|
73
|
+
/**
|
|
74
|
+
* Discriminates the four upload-too-big shapes the server can return:
|
|
75
|
+
* - `size_tier` — 422 `upload_size_exceeds_tier` (typed payload present)
|
|
76
|
+
* - `duration_tier` — 422 `upload_duration_exceeds_tier` (typed payload present)
|
|
77
|
+
* - `absolute_413` — 413, the absolute across-tier cap. The contract models
|
|
78
|
+
* 413 as a plain `ErrorEnvelope` with NO `error_type`
|
|
79
|
+
* discriminator, so there is NO typed payload for it.
|
|
80
|
+
* - `cap_v2_multipart` — 422 `FILE_TOO_LARGE_FOR_MULTIPART` (SDK-3 / Wb6ebOMM,
|
|
81
|
+
* pre-S3 capacity reject on the resume-support endpoints).
|
|
82
|
+
* The contract carries no structured payload for this
|
|
83
|
+
* code today — `payload` is undefined for this kind.
|
|
84
|
+
*
|
|
85
|
+
* **Caveat for exhaustive-narrowing consumers.** The `cap_v2_multipart` value
|
|
86
|
+
* was added in TS SDK 0.5.0 / PHP SDK 0.3.0. A consumer writing
|
|
87
|
+
* `switch (e.kind) { case 'size_tier': ... case 'duration_tier': ... default:
|
|
88
|
+
* absurd(e.kind); }` against the prior 3-value union now sees a non-exhaustive
|
|
89
|
+
* switch and must add the new arm. The bump is logged in CHANGELOG.md.
|
|
90
|
+
*/
|
|
91
|
+
export type GislUploadCapKind = 'size_tier' | 'duration_tier' | 'absolute_413' | 'cap_v2_multipart';
|
|
92
|
+
/**
|
|
93
|
+
* A single class covering all three "upload exceeds a size/duration cap"
|
|
94
|
+
* responses (422 size-tier, 422 duration-tier, 413 absolute).
|
|
95
|
+
*
|
|
96
|
+
* CONSCIOUS DEVIATION from the one-typed-payload-per-class invariant that the
|
|
97
|
+
* other structured subclasses follow (`GislBalanceExhaustedError`,
|
|
98
|
+
* `GislWorkflowExpiredError`, …). Justification: the card mandates this single
|
|
99
|
+
* `GislUploadCapExceededError` name and SDK-3 / E2E-1 are blocked-on it, so
|
|
100
|
+
* splitting into size/duration subclasses would break a cross-ticket naming
|
|
101
|
+
* contract; and 413 carries no typed envelope at all (plain `ErrorEnvelope`),
|
|
102
|
+
* so a one-payload-per-class split could not cover it uniformly anyway. The
|
|
103
|
+
* `kind` discriminant + a union-typed (possibly absent) `payload` is the
|
|
104
|
+
* deliberate trade-off. This is the only structured error in the tree that
|
|
105
|
+
* does not bind exactly one payload type — documented here in the same spirit
|
|
106
|
+
* as the inline PHP↔TS divergence notes.
|
|
107
|
+
*
|
|
108
|
+
* The two multipart-part errors below are deliberately NOT folded in with a
|
|
109
|
+
* `kind`: they carry different fields (`partNumber`/`uploadId` for an instance
|
|
110
|
+
* PUT failure vs `requiredParts`/`maxParts` for the count-ceiling guard) and
|
|
111
|
+
* are thrown from the multipart path, not the response handler.
|
|
112
|
+
*/
|
|
113
|
+
export declare class GislUploadCapExceededError extends GislApiError {
|
|
114
|
+
readonly kind: GislUploadCapKind;
|
|
115
|
+
readonly payload: UploadSizeExceedsTierResponse | UploadDurationExceedsTierResponse | undefined;
|
|
116
|
+
constructor(statusCode: number, errorMessage: string, kind: GislUploadCapKind, payload: UploadSizeExceedsTierResponse | UploadDurationExceedsTierResponse | undefined, path?: string, extra?: Omit<GislApiErrorOptions, 'payload'>);
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* 404 `MULTIPART_SESSION_NOT_FOUND` — the durable multipart session referenced
|
|
120
|
+
* by a resume / status / presign / keepalive call cannot be located (expired
|
|
121
|
+
* past its 48h manifest TTL, deleted, or never existed). Thrown by the SDK-3
|
|
122
|
+
* resume-support endpoints (`getUploadStatus`, `presignParts`,
|
|
123
|
+
* `keepaliveUpload`, and the resume branch of `uploadFile`).
|
|
124
|
+
*
|
|
125
|
+
* Carries no typed structured payload — the contract for the 3 resume-support
|
|
126
|
+
* endpoints models this code as a plain `ErrorEnvelope`. Consumers should
|
|
127
|
+
* detect via `instanceof` and abandon the resume; a fresh `uploadFile()` call
|
|
128
|
+
* (without `resumeUploadId`) will start a new session.
|
|
129
|
+
*/
|
|
130
|
+
export declare class GislMultipartSessionNotFoundError extends GislApiError {
|
|
131
|
+
constructor(statusCode: number, errorMessage: string, path?: string, options?: GislApiErrorOptions);
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* 403 `MULTIPART_SESSION_OWNERSHIP` — the caller is authenticated but the
|
|
135
|
+
* multipart session belongs to a different user. Thrown by the SDK-3
|
|
136
|
+
* resume-support endpoints. The session itself exists (otherwise the server
|
|
137
|
+
* would return 404 NOT_FOUND); the caller's identity simply doesn't match
|
|
138
|
+
* `manifest.userId`. Consumers should abandon the resume.
|
|
139
|
+
*/
|
|
140
|
+
export declare class GislMultipartSessionOwnershipError extends GislApiError {
|
|
141
|
+
constructor(statusCode: number, errorMessage: string, path?: string, options?: GislApiErrorOptions);
|
|
142
|
+
}
|
|
143
|
+
/**
|
|
144
|
+
* 403 `MULTIPART_SESSION_AUTH_REQUIRED` — the multipart session was initiated
|
|
145
|
+
* anonymously (no `manifest.userId`) and the SDK-3 resume-support endpoints
|
|
146
|
+
* refuse to serve it on an authed caller. There is no "claim" workflow today
|
|
147
|
+
* to bind an authed identity to an anonymously-started session; that is the
|
|
148
|
+
* future flip tracked at upstream ticket 8LABloaz. Consumers hitting this on
|
|
149
|
+
* resume should abandon and re-upload from scratch under the authed identity.
|
|
150
|
+
*/
|
|
151
|
+
export declare class GislMultipartSessionAuthRequiredError extends GislApiError {
|
|
152
|
+
constructor(statusCode: number, errorMessage: string, path?: string, options?: GislApiErrorOptions);
|
|
153
|
+
}
|
|
73
154
|
export declare class GislTimeoutError extends GislError {
|
|
74
155
|
constructor(message: string);
|
|
75
156
|
}
|
|
76
157
|
export declare class GislAbortError extends GislError {
|
|
77
158
|
constructor(message: string);
|
|
78
159
|
}
|
|
160
|
+
/**
|
|
161
|
+
* A single S3 multipart part PUT failed terminally (after the configured
|
|
162
|
+
* retry attempts) or could not be read. Subclasses `GislError` — NOT
|
|
163
|
+
* `GislApiError` — because it carries no contract error envelope and is
|
|
164
|
+
* thrown from the multipart upload path, never from the response handler.
|
|
165
|
+
* Mirrors the `GislAbortError` shape, plus the failing part's identifiers.
|
|
166
|
+
*/
|
|
167
|
+
export declare class GislMultipartPartError extends GislError {
|
|
168
|
+
readonly partNumber: number;
|
|
169
|
+
readonly uploadId: string;
|
|
170
|
+
constructor(message: string, partNumber: number, uploadId: string);
|
|
171
|
+
}
|
|
172
|
+
/**
|
|
173
|
+
* The upload would require more than the S3 hard limit of 10 000 multipart
|
|
174
|
+
* parts at the server-provided chunk size. Client-side guard (Model A: the
|
|
175
|
+
* server computes the part plan; the SDK asserts the ceiling). Subclasses
|
|
176
|
+
* `GislError` for the same reason as `GislMultipartPartError`.
|
|
177
|
+
*/
|
|
178
|
+
export declare class GislMultipartPartCountError extends GislError {
|
|
179
|
+
readonly requiredParts: number;
|
|
180
|
+
readonly maxParts: number;
|
|
181
|
+
constructor(message: string, requiredParts: number, maxParts: number);
|
|
182
|
+
}
|
package/dist/errors.js
CHANGED
|
@@ -79,6 +79,80 @@ export class GislAuthError extends GislApiError {
|
|
|
79
79
|
this.name = 'GislAuthError';
|
|
80
80
|
}
|
|
81
81
|
}
|
|
82
|
+
/**
|
|
83
|
+
* A single class covering all three "upload exceeds a size/duration cap"
|
|
84
|
+
* responses (422 size-tier, 422 duration-tier, 413 absolute).
|
|
85
|
+
*
|
|
86
|
+
* CONSCIOUS DEVIATION from the one-typed-payload-per-class invariant that the
|
|
87
|
+
* other structured subclasses follow (`GislBalanceExhaustedError`,
|
|
88
|
+
* `GislWorkflowExpiredError`, …). Justification: the card mandates this single
|
|
89
|
+
* `GislUploadCapExceededError` name and SDK-3 / E2E-1 are blocked-on it, so
|
|
90
|
+
* splitting into size/duration subclasses would break a cross-ticket naming
|
|
91
|
+
* contract; and 413 carries no typed envelope at all (plain `ErrorEnvelope`),
|
|
92
|
+
* so a one-payload-per-class split could not cover it uniformly anyway. The
|
|
93
|
+
* `kind` discriminant + a union-typed (possibly absent) `payload` is the
|
|
94
|
+
* deliberate trade-off. This is the only structured error in the tree that
|
|
95
|
+
* does not bind exactly one payload type — documented here in the same spirit
|
|
96
|
+
* as the inline PHP↔TS divergence notes.
|
|
97
|
+
*
|
|
98
|
+
* The two multipart-part errors below are deliberately NOT folded in with a
|
|
99
|
+
* `kind`: they carry different fields (`partNumber`/`uploadId` for an instance
|
|
100
|
+
* PUT failure vs `requiredParts`/`maxParts` for the count-ceiling guard) and
|
|
101
|
+
* are thrown from the multipart path, not the response handler.
|
|
102
|
+
*/
|
|
103
|
+
export class GislUploadCapExceededError extends GislApiError {
|
|
104
|
+
kind;
|
|
105
|
+
constructor(statusCode, errorMessage, kind, payload, path, extra) {
|
|
106
|
+
super(statusCode, errorMessage, path, undefined, buildOptionsWithPayload(payload, extra));
|
|
107
|
+
this.name = 'GislUploadCapExceededError';
|
|
108
|
+
this.kind = kind;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* 404 `MULTIPART_SESSION_NOT_FOUND` — the durable multipart session referenced
|
|
113
|
+
* by a resume / status / presign / keepalive call cannot be located (expired
|
|
114
|
+
* past its 48h manifest TTL, deleted, or never existed). Thrown by the SDK-3
|
|
115
|
+
* resume-support endpoints (`getUploadStatus`, `presignParts`,
|
|
116
|
+
* `keepaliveUpload`, and the resume branch of `uploadFile`).
|
|
117
|
+
*
|
|
118
|
+
* Carries no typed structured payload — the contract for the 3 resume-support
|
|
119
|
+
* endpoints models this code as a plain `ErrorEnvelope`. Consumers should
|
|
120
|
+
* detect via `instanceof` and abandon the resume; a fresh `uploadFile()` call
|
|
121
|
+
* (without `resumeUploadId`) will start a new session.
|
|
122
|
+
*/
|
|
123
|
+
export class GislMultipartSessionNotFoundError extends GislApiError {
|
|
124
|
+
constructor(statusCode, errorMessage, path, options) {
|
|
125
|
+
super(statusCode, errorMessage, path, undefined, options);
|
|
126
|
+
this.name = 'GislMultipartSessionNotFoundError';
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* 403 `MULTIPART_SESSION_OWNERSHIP` — the caller is authenticated but the
|
|
131
|
+
* multipart session belongs to a different user. Thrown by the SDK-3
|
|
132
|
+
* resume-support endpoints. The session itself exists (otherwise the server
|
|
133
|
+
* would return 404 NOT_FOUND); the caller's identity simply doesn't match
|
|
134
|
+
* `manifest.userId`. Consumers should abandon the resume.
|
|
135
|
+
*/
|
|
136
|
+
export class GislMultipartSessionOwnershipError extends GislApiError {
|
|
137
|
+
constructor(statusCode, errorMessage, path, options) {
|
|
138
|
+
super(statusCode, errorMessage, path, undefined, options);
|
|
139
|
+
this.name = 'GislMultipartSessionOwnershipError';
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
/**
|
|
143
|
+
* 403 `MULTIPART_SESSION_AUTH_REQUIRED` — the multipart session was initiated
|
|
144
|
+
* anonymously (no `manifest.userId`) and the SDK-3 resume-support endpoints
|
|
145
|
+
* refuse to serve it on an authed caller. There is no "claim" workflow today
|
|
146
|
+
* to bind an authed identity to an anonymously-started session; that is the
|
|
147
|
+
* future flip tracked at upstream ticket 8LABloaz. Consumers hitting this on
|
|
148
|
+
* resume should abandon and re-upload from scratch under the authed identity.
|
|
149
|
+
*/
|
|
150
|
+
export class GislMultipartSessionAuthRequiredError extends GislApiError {
|
|
151
|
+
constructor(statusCode, errorMessage, path, options) {
|
|
152
|
+
super(statusCode, errorMessage, path, undefined, options);
|
|
153
|
+
this.name = 'GislMultipartSessionAuthRequiredError';
|
|
154
|
+
}
|
|
155
|
+
}
|
|
82
156
|
export class GislTimeoutError extends GislError {
|
|
83
157
|
constructor(message) {
|
|
84
158
|
super(message);
|
|
@@ -91,3 +165,36 @@ export class GislAbortError extends GislError {
|
|
|
91
165
|
this.name = 'GislAbortError';
|
|
92
166
|
}
|
|
93
167
|
}
|
|
168
|
+
/**
|
|
169
|
+
* A single S3 multipart part PUT failed terminally (after the configured
|
|
170
|
+
* retry attempts) or could not be read. Subclasses `GislError` — NOT
|
|
171
|
+
* `GislApiError` — because it carries no contract error envelope and is
|
|
172
|
+
* thrown from the multipart upload path, never from the response handler.
|
|
173
|
+
* Mirrors the `GislAbortError` shape, plus the failing part's identifiers.
|
|
174
|
+
*/
|
|
175
|
+
export class GislMultipartPartError extends GislError {
|
|
176
|
+
partNumber;
|
|
177
|
+
uploadId;
|
|
178
|
+
constructor(message, partNumber, uploadId) {
|
|
179
|
+
super(message);
|
|
180
|
+
this.name = 'GislMultipartPartError';
|
|
181
|
+
this.partNumber = partNumber;
|
|
182
|
+
this.uploadId = uploadId;
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
/**
|
|
186
|
+
* The upload would require more than the S3 hard limit of 10 000 multipart
|
|
187
|
+
* parts at the server-provided chunk size. Client-side guard (Model A: the
|
|
188
|
+
* server computes the part plan; the SDK asserts the ceiling). Subclasses
|
|
189
|
+
* `GislError` for the same reason as `GislMultipartPartError`.
|
|
190
|
+
*/
|
|
191
|
+
export class GislMultipartPartCountError extends GislError {
|
|
192
|
+
requiredParts;
|
|
193
|
+
maxParts;
|
|
194
|
+
constructor(message, requiredParts, maxParts) {
|
|
195
|
+
super(message);
|
|
196
|
+
this.name = 'GislMultipartPartCountError';
|
|
197
|
+
this.requiredParts = requiredParts;
|
|
198
|
+
this.maxParts = maxParts;
|
|
199
|
+
}
|
|
200
|
+
}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
export { GislClient, DEFAULT_MULTIPART_FIRST_CHUNK_SIZE } from './client.js';
|
|
2
2
|
export { verifyWebhook } from './webhook.js';
|
|
3
3
|
export { parseSseStream } from './sse.js';
|
|
4
|
-
export type { CreditsUsageOptions, GetSchemaOptions, GetSchemaResult, PreflightClipError, PreflightClipsResult, GislClientConfig, GislSseEvent, UploadOptions, WaitOptions, WorkflowCreatePayload, OperationDef, WorkflowSourcePayload, UploadSourcePayload, JobOutputSourcePayload, ExternalImportSourcePayload, ConnectionSourcePayload, JobInputV2Payload, JobDefinitionPayload, ExternalDestinationPayload, DeliveryPayload, DeliveryModePayload, DeliveryBundleFormatPayload, DeliverySelectionPayload, DeliverySelectionTypePayload, DeliveryOutputRefPayload, WorkflowProcessingPayload, ProcessingClassHintPayload, } from './types.js';
|
|
4
|
+
export type { CreditsUsageOptions, GetSchemaOptions, GetSchemaResult, PreflightClipError, PreflightClipsResult, GislClientConfig, GislSseEvent, UploadOptions, WaitOptions, WorkflowCreatePayload, OperationDef, WorkflowSourcePayload, UploadSourcePayload, JobOutputSourcePayload, ExternalImportSourcePayload, ConnectionSourcePayload, JobInputV2Payload, JobDefinitionPayload, ExternalDestinationPayload, DeliveryPayload, DeliveryModePayload, DeliveryBundleFormatPayload, DeliverySelectionPayload, DeliverySelectionTypePayload, DeliveryOutputRefPayload, WorkflowProcessingPayload, ProcessingClassHintPayload, MultipartCheckpointState, _Sdk3HandCodedUploadedPart, _Sdk3HandCodedMultipartStatusResult, _Sdk3HandCodedPresignedPart, _Sdk3HandCodedPresignPartsResult, _Sdk3HandCodedKeepaliveResult, } from './types.js';
|
|
5
5
|
export { uploadSource, jobOutputSource, externalImportSource, connectionSource, } from './types.js';
|
|
6
|
-
export { GislError, GislApiError, GislValidationError, GislBalanceExhaustedError, GislTierRestrictedError, GislFeatureTierRestrictedError, GislFeatureNotAvailableError, GislWorkflowExpiredError, GislAuthError, GislTimeoutError, GislAbortError, } from './errors.js';
|
|
7
|
-
export type { GislApiErrorOptions } from './errors.js';
|
|
6
|
+
export { GislError, GislApiError, GislValidationError, GislBalanceExhaustedError, GislTierRestrictedError, GislFeatureTierRestrictedError, GislFeatureNotAvailableError, GislWorkflowExpiredError, GislAuthError, GislUploadCapExceededError, GislMultipartPartError, GislMultipartPartCountError, GislMultipartSessionNotFoundError, GislMultipartSessionOwnershipError, GislMultipartSessionAuthRequiredError, GislTimeoutError, GislAbortError, } from './errors.js';
|
|
7
|
+
export type { GislApiErrorOptions, GislUploadCapKind } from './errors.js';
|
|
8
8
|
export type { AudioWatermarkDecodeRequest, AudioWatermarkDecodeResponse, ContactRequest, CreditsBalanceResponse, CreditsUsageResponse, CreditTransaction, ExternalImportCreatedResponse, ExternalImportRequest, LoginUserRequest, LoginUser200ResponseData, LoginUser200ResponseDataUser, WorkflowCancelResponse, WorkflowResumeResponse, WorkflowPausedDetail, WorkflowPausedDetailLinks, UploadResponse, UploadConstraintsApplied, UploadProbeResponse, UploadProbeMediaMetadata, MultipartInitiateRequestMetadataHint, WorkflowCreateResponse, WorkflowStatusResponse, WorkflowDownloadResponse, MetadataResponse, MetadataResponseDimensions, MetadataResponseExif, MetadataResponseExifGps, OperationsSchemaResponse, OperationSchemaDefinition, MimeGroupSchema, OptionSchema, PerValueAvailabilityEntry, RetryResponse, JobDownload, OperationDownload, 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, AuthErrorResponse, } from '@giveitsmaller/contracts/openapi';
|
|
9
9
|
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';
|
|
10
10
|
export type { SseOperationProgressData, SseOperationCompletedData, SseOperationFailedData, SseJobCompletedData, SseJobFailedData, SseWorkflowTerminalData, } from '@giveitsmaller/contracts/openapi';
|
package/dist/index.js
CHANGED
|
@@ -4,7 +4,9 @@ export { verifyWebhook } from './webhook.js';
|
|
|
4
4
|
export { parseSseStream } from './sse.js';
|
|
5
5
|
export { uploadSource, jobOutputSource, externalImportSource, connectionSource, } from './types.js';
|
|
6
6
|
// Errors
|
|
7
|
-
export { GislError, GislApiError, GislValidationError, GislBalanceExhaustedError, GislTierRestrictedError, GislFeatureTierRestrictedError, GislFeatureNotAvailableError, GislWorkflowExpiredError, GislAuthError,
|
|
7
|
+
export { GislError, GislApiError, GislValidationError, GislBalanceExhaustedError, GislTierRestrictedError, GislFeatureTierRestrictedError, GislFeatureNotAvailableError, GislWorkflowExpiredError, GislAuthError, GislUploadCapExceededError, GislMultipartPartError, GislMultipartPartCountError,
|
|
8
|
+
// SDK-3 (Wb6ebOMM) — typed errors for the 3 resume-support endpoints.
|
|
9
|
+
GislMultipartSessionNotFoundError, GislMultipartSessionOwnershipError, GislMultipartSessionAuthRequiredError, GislTimeoutError, GislAbortError, } from './errors.js';
|
|
8
10
|
export { AudioWatermarkDecodeRequestMethodHintEnum, AudioWatermarkDecodeResponseMethodEnum,
|
|
9
11
|
// OperationInputModel — value-bearing enum (`single` | `multi`).
|
|
10
12
|
// Surfaced on OperationSchemaDefinition.inputModel so form-renderers
|
package/dist/sse.d.ts
CHANGED
|
@@ -7,5 +7,24 @@ import type { GislSseEvent } from './types.js';
|
|
|
7
7
|
* - Multi-line `data:` fields (concatenated with newlines)
|
|
8
8
|
* - Comment lines (`:` prefix) used as keep-alives
|
|
9
9
|
* - `retry:` field (ignored, SDK manages its own reconnection)
|
|
10
|
+
*
|
|
11
|
+
* `opts.signal` (optional): when it aborts, the underlying body reader is
|
|
12
|
+
* cancelled. This is the ONLY way to promptly stop a stream parked on a
|
|
13
|
+
* quiet socket: `reader.read()` is suspended, so the generator's `finally`
|
|
14
|
+
* cannot run until that read settles — calling `reader.cancel()` from the
|
|
15
|
+
* abort listener settles it (`{ done: true }`) and runs the stream's cancel
|
|
16
|
+
* algorithm, freeing the connection. (MDN/TC39: an async generator's
|
|
17
|
+
* `return()` is itself unreachable while suspended at `await`; cancellation
|
|
18
|
+
* must be driven externally via an AbortSignal.) `GislClient.streamEvents`
|
|
19
|
+
* owns the controller and wires `return()`/`throw()` → `abort()`.
|
|
20
|
+
*
|
|
21
|
+
* By-design limitation: when called WITHOUT `opts.signal`, there is no
|
|
22
|
+
* cancellation path while `reader.read()` is suspended — a consumer that
|
|
23
|
+
* `break`s / `gen.return()`s on a quiet socket stays stuck until the
|
|
24
|
+
* server sends data or closes (an inherent JS async-generator constraint,
|
|
25
|
+
* not a defect). Pass `opts.signal`, or prefer `GislClient.streamEvents`
|
|
26
|
+
* (which always wires one), whenever early termination must be prompt.
|
|
10
27
|
*/
|
|
11
|
-
export declare function parseSseStream(response: Response
|
|
28
|
+
export declare function parseSseStream(response: Response, opts?: {
|
|
29
|
+
signal?: AbortSignal;
|
|
30
|
+
}): AsyncGenerator<GislSseEvent>;
|
package/dist/sse.js
CHANGED
|
@@ -6,13 +6,51 @@
|
|
|
6
6
|
* - Multi-line `data:` fields (concatenated with newlines)
|
|
7
7
|
* - Comment lines (`:` prefix) used as keep-alives
|
|
8
8
|
* - `retry:` field (ignored, SDK manages its own reconnection)
|
|
9
|
+
*
|
|
10
|
+
* `opts.signal` (optional): when it aborts, the underlying body reader is
|
|
11
|
+
* cancelled. This is the ONLY way to promptly stop a stream parked on a
|
|
12
|
+
* quiet socket: `reader.read()` is suspended, so the generator's `finally`
|
|
13
|
+
* cannot run until that read settles — calling `reader.cancel()` from the
|
|
14
|
+
* abort listener settles it (`{ done: true }`) and runs the stream's cancel
|
|
15
|
+
* algorithm, freeing the connection. (MDN/TC39: an async generator's
|
|
16
|
+
* `return()` is itself unreachable while suspended at `await`; cancellation
|
|
17
|
+
* must be driven externally via an AbortSignal.) `GislClient.streamEvents`
|
|
18
|
+
* owns the controller and wires `return()`/`throw()` → `abort()`.
|
|
19
|
+
*
|
|
20
|
+
* By-design limitation: when called WITHOUT `opts.signal`, there is no
|
|
21
|
+
* cancellation path while `reader.read()` is suspended — a consumer that
|
|
22
|
+
* `break`s / `gen.return()`s on a quiet socket stays stuck until the
|
|
23
|
+
* server sends data or closes (an inherent JS async-generator constraint,
|
|
24
|
+
* not a defect). Pass `opts.signal`, or prefer `GislClient.streamEvents`
|
|
25
|
+
* (which always wires one), whenever early termination must be prompt.
|
|
9
26
|
*/
|
|
10
|
-
export async function* parseSseStream(response) {
|
|
27
|
+
export async function* parseSseStream(response, opts = {}) {
|
|
11
28
|
const body = response.body;
|
|
12
29
|
if (!body) {
|
|
13
30
|
return;
|
|
14
31
|
}
|
|
32
|
+
const signal = opts.signal;
|
|
33
|
+
if (signal?.aborted) {
|
|
34
|
+
// Pre-aborted: the body is still unlocked (no reader yet), so cancel it
|
|
35
|
+
// directly to free the connection, then yield nothing.
|
|
36
|
+
await body.cancel().catch(() => { });
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
15
39
|
const reader = body.getReader();
|
|
40
|
+
// `reader.cancel()` is valid while the reader holds the lock (unlike
|
|
41
|
+
// `body.cancel()`, which throws "Cannot cancel a locked stream"). It
|
|
42
|
+
// settles the in-flight `reader.read()` with `{ done: true }` and runs
|
|
43
|
+
// the stream's cancel algorithm. We track `aborted` so the post-loop
|
|
44
|
+
// trailing flush below does NOT emit a partial, never-terminated event
|
|
45
|
+
// once the consumer has abandoned the stream (the cancelled read looks
|
|
46
|
+
// exactly like a clean EOF — `{ done: true }` — but a buffered
|
|
47
|
+
// unterminated `data:` run after an abort is garbage, not an event).
|
|
48
|
+
let aborted = false;
|
|
49
|
+
const onAbort = () => {
|
|
50
|
+
aborted = true;
|
|
51
|
+
void reader.cancel().catch(() => { });
|
|
52
|
+
};
|
|
53
|
+
signal?.addEventListener('abort', onAbort, { once: true });
|
|
16
54
|
const decoder = new TextDecoder();
|
|
17
55
|
let buffer = '';
|
|
18
56
|
let eventType = '';
|
|
@@ -28,6 +66,12 @@ export async function* parseSseStream(response) {
|
|
|
28
66
|
// Keep the last (potentially incomplete) line in the buffer
|
|
29
67
|
buffer = lines.pop() ?? '';
|
|
30
68
|
for (const line of lines) {
|
|
69
|
+
// If a single read delivered multiple complete events and the
|
|
70
|
+
// consumer aborted after receiving an earlier one (we were
|
|
71
|
+
// suspended at `yield`), do NOT keep emitting the remaining
|
|
72
|
+
// buffered events on resume — stop processing this batch.
|
|
73
|
+
if (aborted)
|
|
74
|
+
break;
|
|
31
75
|
if (line === '') {
|
|
32
76
|
// Empty line = end of event
|
|
33
77
|
if (dataLines.length > 0) {
|
|
@@ -73,8 +117,12 @@ export async function* parseSseStream(response) {
|
|
|
73
117
|
}
|
|
74
118
|
}
|
|
75
119
|
}
|
|
76
|
-
// Flush any remaining buffered event
|
|
77
|
-
|
|
120
|
+
// Flush any remaining buffered event — but ONLY on a genuine
|
|
121
|
+
// end-of-stream (server closed without a final blank line). After an
|
|
122
|
+
// abort, the cancelled read also surfaces as `{ done: true }`, yet a
|
|
123
|
+
// partial unterminated `data:` buffer is not a real event and must
|
|
124
|
+
// not be yielded once the consumer has abandoned the stream.
|
|
125
|
+
if (!aborted && dataLines.length > 0) {
|
|
78
126
|
const rawData = dataLines.join('\n');
|
|
79
127
|
let parsed;
|
|
80
128
|
try {
|
|
@@ -90,6 +138,17 @@ export async function* parseSseStream(response) {
|
|
|
90
138
|
}
|
|
91
139
|
}
|
|
92
140
|
finally {
|
|
141
|
+
signal?.removeEventListener('abort', onAbort);
|
|
142
|
+
// Cancel the body so the underlying HTTP connection is released on
|
|
143
|
+
// EVERY exit path (early `return()`/abort AND normal completion) — a
|
|
144
|
+
// bare `releaseLock()` leaves the socket open until GC. Cancelling an
|
|
145
|
+
// already-closed/cancelled stream is a harmless no-op that resolves.
|
|
146
|
+
try {
|
|
147
|
+
await reader.cancel();
|
|
148
|
+
}
|
|
149
|
+
catch {
|
|
150
|
+
/* stream already errored/closed — nothing to release */
|
|
151
|
+
}
|
|
93
152
|
reader.releaseLock();
|
|
94
153
|
}
|
|
95
154
|
}
|
package/dist/types.d.ts
CHANGED
|
@@ -284,4 +284,136 @@ export interface UploadOptions {
|
|
|
284
284
|
* single FormData field on the multipart/initiate request.
|
|
285
285
|
*/
|
|
286
286
|
metadataHint?: MultipartInitiateRequestMetadataHint;
|
|
287
|
+
/**
|
|
288
|
+
* Resume an in-progress multipart upload (SDK-3 / Wb6ebOMM).
|
|
289
|
+
*
|
|
290
|
+
* When set, `uploadFile()` skips `/multipart/initiate` entirely. Instead it
|
|
291
|
+
* walks `GET /api/uploads/multipart/{uploadId}/status` (paginated via
|
|
292
|
+
* `next_part_number_marker` / `is_truncated`), computes which parts are
|
|
293
|
+
* still missing, re-presigns them in batches of <=100 via
|
|
294
|
+
* `POST /api/uploads/multipart/{uploadId}/presign`, PUTs only the missing
|
|
295
|
+
* parts, then `POST /api/uploads/multipart/complete`.
|
|
296
|
+
*
|
|
297
|
+
* The caller's `file` argument MUST be byte-identical to the file used in
|
|
298
|
+
* the original initiate call (same byte content at the same offsets).
|
|
299
|
+
* Mismatched bytes will produce S3 etags that don't match the server's
|
|
300
|
+
* recorded state, and `/multipart/complete` will reject.
|
|
301
|
+
*
|
|
302
|
+
* Anonymous-initiated sessions cannot be resumed by an authed caller
|
|
303
|
+
* (server returns 403 → `GislMultipartSessionAuthRequiredError`); a
|
|
304
|
+
* non-existent or expired session returns 404 →
|
|
305
|
+
* `GislMultipartSessionNotFoundError`.
|
|
306
|
+
*/
|
|
307
|
+
resumeUploadId?: string;
|
|
308
|
+
/**
|
|
309
|
+
* Called after every successful part PUT during fresh-upload AND resume
|
|
310
|
+
* paths. Receives a JSON-serialisable snapshot of the upload state — round-
|
|
311
|
+
* trippable via `JSON.stringify` for persistence across process restarts,
|
|
312
|
+
* so a future `uploadFile({ resumeUploadId: state.uploadId, ... })` can
|
|
313
|
+
* pick up where the prior process stopped.
|
|
314
|
+
*
|
|
315
|
+
* The callback is invoked OUTSIDE the per-part retry-scoped path: a throw
|
|
316
|
+
* here will fail the upload but NEVER trigger a duplicate PUT (mirrors the
|
|
317
|
+
* `onProgress` discipline at `client.ts:1195-1199`).
|
|
318
|
+
*/
|
|
319
|
+
onCheckpoint?: (state: MultipartCheckpointState) => void;
|
|
320
|
+
}
|
|
321
|
+
/**
|
|
322
|
+
* JSON-serialisable snapshot of an in-progress multipart upload, emitted
|
|
323
|
+
* after every successful part PUT via `UploadOptions.onCheckpoint`. Designed
|
|
324
|
+
* to round-trip through `JSON.stringify` / `JSON.parse` so consumers can
|
|
325
|
+
* persist it across process restarts and resume via
|
|
326
|
+
* `uploadFile({ resumeUploadId: state.uploadId, ... })`.
|
|
327
|
+
*
|
|
328
|
+
* All fields are primitive: no `Date` (use the ISO-8601 string on
|
|
329
|
+
* `manifestExpiresAt`), no `Buffer`, no functions.
|
|
330
|
+
*/
|
|
331
|
+
export interface MultipartCheckpointState {
|
|
332
|
+
/** The server-assigned `upload_id` (UUID) used as `resumeUploadId` later. */
|
|
333
|
+
readonly uploadId: string;
|
|
334
|
+
/** Total parts the server computed for this upload. <=10 000. */
|
|
335
|
+
readonly totalParts: number;
|
|
336
|
+
/**
|
|
337
|
+
* Part numbers (1-indexed) that have been successfully PUT to S3 so far.
|
|
338
|
+
* Includes part 1 (the initiate first chunk) once a part >= 2 lands. Sorted
|
|
339
|
+
* ascending. Excluded numbers in `[1, totalParts]` are the still-missing
|
|
340
|
+
* set a resume must re-PUT.
|
|
341
|
+
*/
|
|
342
|
+
readonly uploadedPartNumbers: readonly number[];
|
|
343
|
+
/**
|
|
344
|
+
* ISO-8601 wall-clock instant at which the server's durable manifest for
|
|
345
|
+
* this `uploadId` expires (currently a 48h TTL per the API-2 contract). On
|
|
346
|
+
* resume, clients SHOULD call `keepaliveUpload(uploadId)` every 12-24h to
|
|
347
|
+
* extend this, leaving >=24h of slack against the 48h ceiling even with
|
|
348
|
+
* worst-case clock skew between client and server.
|
|
349
|
+
*/
|
|
350
|
+
readonly manifestExpiresAt: string;
|
|
351
|
+
}
|
|
352
|
+
/**
|
|
353
|
+
* One entry in `_Sdk3HandCodedMultipartStatusResult.uploadedParts`. Aggregated
|
|
354
|
+
* across all pages of the underlying `GET /status` endpoint by the SDK's
|
|
355
|
+
* walk-pagination loop.
|
|
356
|
+
*
|
|
357
|
+
* TODO(HxUmVr3Y): replace hand-coded shape on regen.
|
|
358
|
+
*/
|
|
359
|
+
export interface _Sdk3HandCodedUploadedPart {
|
|
360
|
+
readonly partNumber: number;
|
|
361
|
+
readonly etag: string;
|
|
362
|
+
readonly sizeBytes: number;
|
|
363
|
+
/** ISO-8601 last-modified instant of the S3 part. */
|
|
364
|
+
readonly lastModified: string;
|
|
365
|
+
}
|
|
366
|
+
/**
|
|
367
|
+
* Aggregated result of `getUploadStatus()` after the SDK has walked every
|
|
368
|
+
* page of the underlying `GET /api/uploads/multipart/{uploadId}/status`
|
|
369
|
+
* endpoint (paginated via `next_part_number_marker` / `is_truncated`).
|
|
370
|
+
* `uploadedParts` is the merged-and-sorted list of every part the server
|
|
371
|
+
* has recorded across all pages — callers see the complete state without
|
|
372
|
+
* needing to drive the pagination cursor themselves.
|
|
373
|
+
*
|
|
374
|
+
* TODO(HxUmVr3Y): replace hand-coded shape on regen.
|
|
375
|
+
*/
|
|
376
|
+
export interface _Sdk3HandCodedMultipartStatusResult {
|
|
377
|
+
readonly uploadId: string;
|
|
378
|
+
readonly multipartUploadId: string;
|
|
379
|
+
readonly cloudKey: string;
|
|
380
|
+
readonly totalParts: number;
|
|
381
|
+
/** Sorted ascending by `partNumber`; complete across all server-side pages. */
|
|
382
|
+
readonly uploadedParts: readonly _Sdk3HandCodedUploadedPart[];
|
|
383
|
+
/** ISO-8601 wall-clock when the durable session manifest expires. */
|
|
384
|
+
readonly manifestExpiresAt: string;
|
|
385
|
+
/** Server-recommended chunk size in bytes (matches the initiate envelope). */
|
|
386
|
+
readonly recommendedChunkSize: number;
|
|
387
|
+
}
|
|
388
|
+
/**
|
|
389
|
+
* One entry in `_Sdk3HandCodedPresignPartsResult.presignedUrls`. Shape mirrors
|
|
390
|
+
* the contract-pinned `PresignedUrlPart` from the initiate envelope; kept
|
|
391
|
+
* hand-coded here so the resume path does not depend on the generator's name
|
|
392
|
+
* for that shape (decoupling for the HxUmVr3Y regen window).
|
|
393
|
+
*
|
|
394
|
+
* TODO(HxUmVr3Y): replace hand-coded shape on regen.
|
|
395
|
+
*/
|
|
396
|
+
export interface _Sdk3HandCodedPresignedPart {
|
|
397
|
+
readonly partNumber: number;
|
|
398
|
+
readonly url: string;
|
|
399
|
+
readonly expiresAt: string;
|
|
400
|
+
}
|
|
401
|
+
/**
|
|
402
|
+
* Result of `presignParts()` — the server-issued presigned PUT URLs for the
|
|
403
|
+
* requested part numbers.
|
|
404
|
+
*
|
|
405
|
+
* TODO(HxUmVr3Y): replace hand-coded shape on regen.
|
|
406
|
+
*/
|
|
407
|
+
export interface _Sdk3HandCodedPresignPartsResult {
|
|
408
|
+
readonly uploadId: string;
|
|
409
|
+
readonly presignedUrls: readonly _Sdk3HandCodedPresignedPart[];
|
|
410
|
+
}
|
|
411
|
+
/**
|
|
412
|
+
* Result of `keepaliveUpload()` — the refreshed manifest TTL expiry instant.
|
|
413
|
+
*
|
|
414
|
+
* TODO(HxUmVr3Y): replace hand-coded shape on regen.
|
|
415
|
+
*/
|
|
416
|
+
export interface _Sdk3HandCodedKeepaliveResult {
|
|
417
|
+
readonly uploadId: string;
|
|
418
|
+
readonly manifestExpiresAt: string;
|
|
287
419
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@giveitsmaller/sdk",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.0",
|
|
4
4
|
"description": "Node.js SDK for the GISL (Give It Smaller) file compression and processing API",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
"node": ">=18"
|
|
20
20
|
},
|
|
21
21
|
"dependencies": {
|
|
22
|
-
"@giveitsmaller/contracts": "^0.
|
|
22
|
+
"@giveitsmaller/contracts": "^0.4.0"
|
|
23
23
|
},
|
|
24
24
|
"devDependencies": {
|
|
25
25
|
"@types/node": "^22",
|