@giveitsmaller/sdk 0.2.3 → 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/README.md ADDED
@@ -0,0 +1,58 @@
1
+ # @giveitsmaller/sdk
2
+
3
+ TypeScript / Node.js SDK for the [GISL](https://giveitsmaller.com) (Give It Smaller) file compression and processing API.
4
+
5
+ ## Install
6
+
7
+ ```bash
8
+ npm install @giveitsmaller/sdk
9
+ ```
10
+
11
+ Node.js 18+ required.
12
+
13
+ ## Quickstart
14
+
15
+ ```ts
16
+ import { GislClient, uploadSource, OperationType } from '@giveitsmaller/sdk';
17
+
18
+ const client = new GislClient({
19
+ baseUrl: 'https://api.giveitsmaller.com',
20
+ apiKey: 'REPLACE_ME_API_KEY',
21
+ });
22
+
23
+ const upload = await client.uploadFile('./photo.jpg');
24
+
25
+ const workflow = await client.createWorkflow({
26
+ jobs: [
27
+ {
28
+ id: 'compressed',
29
+ source: uploadSource(upload.fileId),
30
+ operations: [
31
+ { type: OperationType.compress, options: { mode: 'lossy', quality: 80 } },
32
+ ],
33
+ },
34
+ ],
35
+ });
36
+
37
+ await client.waitForWorkflow(workflow.workflowId);
38
+
39
+ const dls = await client.getWorkflowDownloads(workflow.workflowId);
40
+ console.log('Compressed:', dls.downloads[0].files[0].downloadUrl);
41
+ ```
42
+
43
+ ## Full documentation
44
+
45
+ Docs are published in the [giveitsmaller-sdks](https://github.com/AntonioCS/giveitsmaller-sdks) repository — they are **not** shipped in the npm tarball (only `dist/` is published).
46
+
47
+ - **Getting started & concepts** — [`docs/typescript/index.md`](https://github.com/AntonioCS/giveitsmaller-sdks/blob/main/docs/typescript/index.md)
48
+ - **Client reference** (all `GislClient` methods + operation option list) — [`docs/typescript/client.md`](https://github.com/AntonioCS/giveitsmaller-sdks/blob/main/docs/typescript/client.md)
49
+ - **Types & authoring primitives** (config, payloads, job factories) — [`docs/typescript/types.md`](https://github.com/AntonioCS/giveitsmaller-sdks/blob/main/docs/typescript/types.md)
50
+ - **SSE / live progress** — [`docs/typescript/sse.md`](https://github.com/AntonioCS/giveitsmaller-sdks/blob/main/docs/typescript/sse.md)
51
+ - **Webhooks** — [`docs/typescript/webhook.md`](https://github.com/AntonioCS/giveitsmaller-sdks/blob/main/docs/typescript/webhook.md)
52
+ - **Errors & retry guidance** — [`docs/typescript/errors.md`](https://github.com/AntonioCS/giveitsmaller-sdks/blob/main/docs/typescript/errors.md)
53
+ - **Troubleshooting** — [`docs/typescript/troubleshooting.md`](https://github.com/AntonioCS/giveitsmaller-sdks/blob/main/docs/typescript/troubleshooting.md)
54
+ - **Examples** — compress, thumbnail, convert, merge, archive — [`docs/typescript/examples/`](https://github.com/AntonioCS/giveitsmaller-sdks/tree/main/docs/typescript/examples)
55
+
56
+ ## License
57
+
58
+ MIT — see the [LICENSE](https://github.com/AntonioCS/giveitsmaller-sdks/blob/main/LICENSE) file.
@@ -0,0 +1 @@
1
+ export declare function _runAudit(): void;
package/dist/_audit.js ADDED
@@ -0,0 +1,64 @@
1
+ // Per ticket J65ZERTi (T20) — public-API completeness audit gate.
2
+ //
3
+ // This file sits inside `src/` so it is type-checked by `tsc --noEmit`
4
+ // (the test directory is excluded from `tsconfig.json` so a test-side
5
+ // audit is dead — earlier follow-up review caught this). The list of
6
+ // type-only imports here IS the gate: if a regen drops or renames any
7
+ // symbol, `tsc` fails on the import line.
8
+ //
9
+ // Each `accept<T>()` call resolves the type parameter; the function
10
+ // itself is a no-op. The body is never invoked at runtime — only the
11
+ // import resolution matters.
12
+ function accept(_value) {
13
+ // intentionally empty — type-presence is the assertion
14
+ }
15
+ // One call per imported type. tsc resolves the type parameter against
16
+ // the import; if the import is broken, the call site fails to compile.
17
+ export function _runAudit() {
18
+ accept();
19
+ accept();
20
+ accept();
21
+ accept();
22
+ accept();
23
+ accept();
24
+ accept();
25
+ accept();
26
+ accept();
27
+ accept();
28
+ accept();
29
+ accept();
30
+ accept();
31
+ accept();
32
+ accept();
33
+ accept();
34
+ accept();
35
+ accept();
36
+ accept();
37
+ accept();
38
+ accept();
39
+ accept();
40
+ accept();
41
+ accept();
42
+ accept();
43
+ accept();
44
+ accept();
45
+ accept();
46
+ accept();
47
+ accept();
48
+ accept();
49
+ accept();
50
+ accept();
51
+ accept();
52
+ accept();
53
+ accept();
54
+ accept();
55
+ accept();
56
+ accept();
57
+ // SDK-3 (Wb6ebOMM) public-API surface for the 3 resume-support endpoints.
58
+ accept();
59
+ accept();
60
+ accept();
61
+ accept();
62
+ accept();
63
+ accept();
64
+ }
package/dist/client.d.ts CHANGED
@@ -1,15 +1,29 @@
1
- import type { UploadResponse, WorkflowCreateResponse, WorkflowStatusResponse, WorkflowDownloadResponse, MetadataResponse, OperationsSchemaResponse, RetryResponse } from '@giveitsmaller/contracts/openapi';
2
- import type { GislClientConfig, GislSseEvent, UploadOptions, WaitOptions, WorkflowCreatePayload } from './types.js';
1
+ import type { AudioWatermarkDecodeRequest, AudioWatermarkDecodeResponse, ExternalImportCreatedResponse, ExternalImportRequest, LoginUserRequest, LoginUser200ResponseData, ContactRequest, CreditsBalanceResponse, CreditsUsageResponse, UploadResponse, UploadProbeResponse, WorkflowCancelResponse, WorkflowCreateResponse, WorkflowResumeResponse, WorkflowStatusResponse, WorkflowDownloadResponse, MetadataResponse, RetryResponse } from '@giveitsmaller/contracts/openapi';
2
+ import type { CreditsUsageOptions, GetSchemaOptions, GetSchemaResult, GislClientConfig, GislSseEvent, PreflightClipsResult, UploadOptions, WaitOptions, WorkflowCreatePayload, _Sdk3HandCodedKeepaliveResult, _Sdk3HandCodedMultipartStatusResult, _Sdk3HandCodedPresignPartsResult } from './types.js';
3
+ export declare const MULTIPART_CONCURRENCY_DEFAULT: 4;
3
4
  export declare const DEFAULT_MULTIPART_FIRST_CHUNK_SIZE: number;
5
+ export interface ValidationDetail {
6
+ message: string;
7
+ field?: string;
8
+ operation?: string;
9
+ option?: string;
10
+ messageKey?: string;
11
+ locale?: string;
12
+ messageParams?: Record<string, unknown>;
13
+ }
4
14
  export declare class GislClient {
5
15
  private readonly baseUrl;
6
16
  private readonly headers;
7
17
  private readonly timeoutMs;
8
18
  private readonly multipartThreshold;
9
19
  private readonly multipartConcurrency;
20
+ private readonly multipartMaxAttempts;
21
+ private readonly multipartRetryBaseMs;
22
+ private readonly useSessionCookie;
10
23
  constructor(config: GislClientConfig);
11
24
  private request;
12
25
  private handleResponse;
26
+ private isAuthErrorType;
13
27
  /**
14
28
  * Upload a file. Automatically uses multipart upload for files exceeding
15
29
  * the configured threshold (default 10 MB).
@@ -31,6 +45,88 @@ export declare class GislClient {
31
45
  * post-upload metadata callers should use getMetadata(fileId).
32
46
  */
33
47
  private multipartUpload;
48
+ /**
49
+ * SDK-3 (Wb6ebOMM): resume an in-progress multipart upload.
50
+ *
51
+ * Skips `/multipart/initiate` entirely (the original initiate happened in a
52
+ * prior process). Walks `/status` for the authoritative list of recorded
53
+ * parts, re-presigns the missing ones in batches of <=100, PUTs only those,
54
+ * and finalises with `/complete`. Caller's `source` MUST be byte-identical
55
+ * to the originally-uploaded file at the same offsets (parts whose etags
56
+ * don't match server state will fail `/complete`).
57
+ *
58
+ * Re-runs the same `uploadId` / `chunkSize` / `totalParts` / plan-consistency
59
+ * guards as the fresh-upload path (`multipartUpload`), using the /status
60
+ * envelope as the equivalent of the initiate envelope. Reuses the same
61
+ * `failureController` sibling-wake + `drainResponseBody` cleanup discipline
62
+ * as the fresh-upload PUT loop. `onProgress` fires on entry seeded from
63
+ * (uploadedPartNumbers.length * chunkSize) and again after every successful
64
+ * PUT. `onCheckpoint` fires OUTSIDE the retry-scoped path after every
65
+ * successful PUT — a callback-throw must not trigger a duplicate PUT.
66
+ *
67
+ * TODO(HxUmVr3Y): replace inline hand-coded request body marshalling on regen.
68
+ */
69
+ private multipartResume;
70
+ /**
71
+ * Fetch the durable status of an in-progress multipart upload session.
72
+ *
73
+ * Walks every page of `GET /api/uploads/multipart/{uploadId}/status`
74
+ * (paginated via `next_part_number_marker` + `is_truncated`) and returns
75
+ * the aggregated state. Callers see the complete set of recorded parts
76
+ * across pages without driving the cursor themselves.
77
+ *
78
+ * Anonymous-initiated sessions return 403 → `GislMultipartSessionAuthRequiredError`.
79
+ * Non-existent / expired sessions return 404 → `GislMultipartSessionNotFoundError`.
80
+ * Authed-but-non-owning callers return 403 → `GislMultipartSessionOwnershipError`.
81
+ *
82
+ * TODO(HxUmVr3Y): replace hand-coded response shape on regen.
83
+ */
84
+ getUploadStatus(uploadId: string, opts?: {
85
+ signal?: AbortSignal;
86
+ }): Promise<_Sdk3HandCodedMultipartStatusResult>;
87
+ /**
88
+ * Re-presign a batch of missing part numbers on an in-progress multipart
89
+ * session.
90
+ *
91
+ * Validates client-side BEFORE the HTTP round-trip:
92
+ * - `partNumbers` non-empty
93
+ * - length <=100 (server raw-body cap is 8 KiB before json_decode)
94
+ * - every entry an integer in `[2, totalParts]` — part 1 is sealed at
95
+ * initiate (re-presigning it would break the etag recorded server-side
96
+ * for /complete)
97
+ * - entries unique
98
+ * - `totalParts` <=10 000 (S3 hard limit; mirrors the SDK-1 ceiling guard)
99
+ *
100
+ * TODO(HxUmVr3Y): replace hand-coded request/response shapes on regen.
101
+ */
102
+ presignParts(uploadId: string, partNumbers: readonly number[], totalParts: number, opts?: {
103
+ signal?: AbortSignal;
104
+ }): Promise<_Sdk3HandCodedPresignPartsResult>;
105
+ /**
106
+ * Extend the manifest TTL of an in-progress multipart upload session.
107
+ *
108
+ * The durable session manifest defaults to a 48 h TTL (decoupled from the
109
+ * shorter presigned-URL TTL). For a long-running resume that spans days
110
+ * (e.g. an upload paused overnight on flaky Wi-Fi), callers SHOULD invoke
111
+ * `keepaliveUpload` every **12-24 h** while resuming — the 12-24 h band
112
+ * leaves >=24 h of slack against the 48 h ceiling even with worst-case
113
+ * clock skew between client and server. The server atomically refreshes
114
+ * the Redis EXPIRE for the manifest key; the call is idempotent.
115
+ *
116
+ * TODO(HxUmVr3Y): replace hand-coded response shape on regen.
117
+ */
118
+ keepaliveUpload(uploadId: string, opts?: {
119
+ signal?: AbortSignal;
120
+ }): Promise<_Sdk3HandCodedKeepaliveResult>;
121
+ /**
122
+ * Private walk-pagination helper for /status. Aggregates every page into
123
+ * a single `_Sdk3HandCodedMultipartStatusResult`. AbortSignal short-circuits
124
+ * the loop between page fetches AND propagates into each fetch.
125
+ *
126
+ * Limit pinned to 1000 (max per page) so we make the minimum number of
127
+ * round-trips even for the worst-case ~10 pages on a 10 000-part upload.
128
+ */
129
+ private walkUploadStatus;
34
130
  /**
35
131
  * Create a new workflow.
36
132
  */
@@ -43,6 +139,41 @@ export declare class GislClient {
43
139
  * Poll until the workflow reaches a terminal status.
44
140
  */
45
141
  waitForWorkflow(workflowId: string, options?: WaitOptions): Promise<WorkflowStatusResponse>;
142
+ /**
143
+ * Cancel a workflow. Idempotent — cancelling an already-cancelled
144
+ * workflow returns 200 with the same shape (and the original
145
+ * `cancelledAt`). Cancelling a `completed` / `failed` /
146
+ * `partially_failed` / `expired` workflow returns 409.
147
+ *
148
+ * The response's `billingEffect` field tells the caller what
149
+ * happened to outstanding reservations:
150
+ * - `unspent_reservation_released` — workflow was active or paused
151
+ * and the unspent portion of the reservation has been refunded.
152
+ * The refund appears as a separate `CreditTransaction` with
153
+ * `type: refund`.
154
+ * - `none` — no refund (all reserved credits were already consumed
155
+ * by completed jobs, or this is an idempotent re-cancel).
156
+ *
157
+ * In-flight operations may continue running briefly after the
158
+ * cancel response while their Lambda processes terminate; the
159
+ * response is the binding "no further reservations will be made"
160
+ * signal.
161
+ */
162
+ cancelWorkflow(workflowId: string): Promise<WorkflowCancelResponse>;
163
+ /**
164
+ * Resume a workflow that is in `paused_insufficient_credits`.
165
+ *
166
+ * Resume succeeds only when `availableCredits` covers the next
167
+ * reservation. If the balance is still insufficient, throws
168
+ * `GislBalanceExhaustedError` (402, same envelope as the workflow-
169
+ * create 402 path) and the workflow stays paused. If the workflow
170
+ * is past its `expiresAt` (default 7-day TTL from `pausedAt`),
171
+ * throws `GislWorkflowExpiredError` (422) and the workflow has
172
+ * transitioned to `expired` — callers cannot un-expire a workflow.
173
+ * Resuming a workflow that is not in `paused_insufficient_credits`
174
+ * is a 409 (no-op).
175
+ */
176
+ resumeWorkflow(workflowId: string): Promise<WorkflowResumeResponse>;
46
177
  /**
47
178
  * Get download URLs for a completed workflow.
48
179
  */
@@ -50,18 +181,137 @@ export declare class GislClient {
50
181
  /**
51
182
  * Stream SSE events for a workflow. Returns an async iterable.
52
183
  */
53
- streamEvents(workflowId: string): Promise<AsyncGenerator<GislSseEvent>>;
184
+ streamEvents(workflowId: string, opts?: {
185
+ signal?: AbortSignal;
186
+ }): Promise<AsyncGenerator<GislSseEvent>>;
54
187
  /**
55
188
  * Get metadata for an uploaded file.
56
189
  */
57
190
  getMetadata(fileId: string): Promise<MetadataResponse>;
58
191
  /**
59
192
  * Get the operations schema (available types, options, constraints).
60
- * This endpoint returns raw JSON (no envelope) and is CDN-cacheable.
193
+ *
194
+ * Returns raw JSON (no envelope). The response is **per-tier private**
195
+ * (cache key includes the caller's `user_tier`); CDN-style public
196
+ * caching is not used. Pass `ifNoneMatch` / `ifModifiedSince` from a
197
+ * previous response to revalidate — a 304 surfaces as
198
+ * `{ notModified: true, etag, lastModified }` so callers can keep
199
+ * using their cached copy.
61
200
  */
62
- getSchema(): Promise<OperationsSchemaResponse>;
201
+ getSchema(options?: GetSchemaOptions): Promise<GetSchemaResult>;
63
202
  /**
64
203
  * Retry a failed operation.
65
204
  */
66
205
  retryOperation(operationId: string): Promise<RetryResponse>;
206
+ /**
207
+ * Submit a contact-form message. The endpoint returns 204 No Content on
208
+ * success, so this method resolves to `void`.
209
+ *
210
+ * Validation errors (e.g. missing `email`, non-empty honeypot `website`)
211
+ * surface as `GislValidationError` from the standard error envelope.
212
+ */
213
+ submitContact(payload: ContactRequest): Promise<void>;
214
+ /**
215
+ * Get a snapshot of the caller's current credit position. The canonical
216
+ * billing-state surface — `BalanceExhaustedResponse` (402) on workflow
217
+ * creation includes pre-error counters for context, but UIs should drive
218
+ * spend-now affordances and tier-upgrade prompts off this endpoint, not
219
+ * off the error envelope.
220
+ */
221
+ getCreditsBalance(): Promise<CreditsBalanceResponse>;
222
+ /**
223
+ * Authenticate with email/password. On success the server issues a
224
+ * session cookie via `Set-Cookie`; subsequent requests authenticate
225
+ * via that cookie when the client is configured with
226
+ * `useSessionCookie: true`.
227
+ *
228
+ * Failure modes per ticket FX6mbTJD:
229
+ * - **401** `invalid_credentials` (collapsed with unverified
230
+ * accounts for anti-enumeration) → `GislAuthError`.
231
+ * - **403** account-state failures (`account_locked`,
232
+ * `account_disabled`, `account_deleted`,
233
+ * `account_deletion_expired`) → `GislAuthError`.
234
+ * - **429** infrastructure rate-limit → `GislApiError` with
235
+ * the `Retry-After` header echoed on the response.
236
+ *
237
+ * Node session persistence (cookie-jar across processes) is out of
238
+ * scope — this method only touches the request side.
239
+ */
240
+ login(credentials: LoginUserRequest): Promise<LoginUser200ResponseData>;
241
+ /**
242
+ * Invalidate the current session.
243
+ *
244
+ * Idempotent: calling logout without an active session returns 401,
245
+ * but the SDK collapses both 200 and 401 into a single "logged out"
246
+ * outcome — `logout()` resolves to `void` in either case so caller
247
+ * cleanup code does not need to special-case the not-currently-
248
+ * authenticated path. Other errors (e.g. 500, network failures)
249
+ * still throw.
250
+ */
251
+ logout(): Promise<void>;
252
+ /**
253
+ * Register a one-shot bearer URL (S3 presigned, GCS signed, Azure
254
+ * SAS, Dropbox shared link, public HTTPS) and receive an opaque
255
+ * `externalSourceId` handle. Subsequent workflows reference the
256
+ * handle via `WorkflowSource` of `type: external_import` —
257
+ * compose with the [`externalImportSource()`](./types.ts) factory.
258
+ *
259
+ * Per ADR-0005 §"SSRF posture": the server validates 8 rules at
260
+ * registration time AND again at fetch time. HTTPS-only;
261
+ * private/loopback/cloud-metadata IPs are rejected (403). The
262
+ * original URL + password are encrypted at rest and never
263
+ * returned in any response.
264
+ *
265
+ * Currently `availability: planned` — the runtime endpoint returns
266
+ * 422 `feature_not_available` (or 404, per the cross-repo rollout)
267
+ * until the external-import infrastructure ships. The method
268
+ * exists today so consumers can write the integration ahead of
269
+ * time.
270
+ */
271
+ createExternalImport(payload: ExternalImportRequest): Promise<ExternalImportCreatedResponse>;
272
+ /**
273
+ * Decode a previously-embedded steganographic audio watermark
274
+ * (per ticket I20). Pairs with the `audio_watermark` operation —
275
+ * the operation embeds; this endpoint decodes.
276
+ *
277
+ * **Enterprise tier only.** Free / pro callers receive
278
+ * `GislFeatureTierRestrictedError` (403).
279
+ *
280
+ * **Own watermarks only.** The decoder will refuse to extract from
281
+ * media the caller did not mark themselves — mismatches return 404
282
+ * (rather than leaking that *some* watermark was detected).
283
+ *
284
+ * Currently `availability: planned` — calls return
285
+ * `GislFeatureNotAvailableError` (422) until the cross-repo Lambda
286
+ * support ships. Decode requests are rate-limited independently
287
+ * from workflow-create.
288
+ */
289
+ decodeAudioWatermark(payload: AudioWatermarkDecodeRequest): Promise<AudioWatermarkDecodeResponse>;
290
+ /**
291
+ * Probe an uploaded file for workflow-readiness — detects corruption,
292
+ * unsupported codecs, and pre-assigns the processing class the server
293
+ * would route the file to. Designed for the long-form merge edge case
294
+ * where a single bad input would fail the whole workflow.
295
+ *
296
+ * Currently `availability: planned` — calls return
297
+ * `GislFeatureNotAvailableError` (422) until the cross-repo Lambda
298
+ * support ships. Idempotent: probing the same `fileId` twice returns
299
+ * the cached result.
300
+ */
301
+ probeUpload(fileId: string): Promise<UploadProbeResponse>;
302
+ /**
303
+ * Probe N uploaded files in parallel and partition the results by
304
+ * outcome. Returns `{ ok, rejected, errors }` so the caller can
305
+ * cleanly drop bad clips before submitting a long-form merge
306
+ * workflow. Probe-call failures (including the
307
+ * `feature_not_available` 422 returned while the endpoint is
308
+ * `availability: planned`) land in `errors` rather than throwing,
309
+ * so a partially-successful batch still yields useful aggregation.
310
+ */
311
+ preflightClips(fileIds: string[]): Promise<PreflightClipsResult>;
312
+ /**
313
+ * Get a paginated page of credit transaction history for the caller.
314
+ * Server defaults: `limit=20`, `offset=0`. Most-recent-first.
315
+ */
316
+ getCreditsUsage(options?: CreditsUsageOptions): Promise<CreditsUsageResponse>;
67
317
  }