@giveitsmaller/sdk 0.18.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.
Files changed (40) hide show
  1. package/README.md +1 -1
  2. package/dist/_audit.js +12 -0
  3. package/dist/builder.d.ts +1 -0
  4. package/dist/client.d.ts +8 -1
  5. package/dist/client.js +27 -32
  6. package/dist/ergonomic/image_output_routes.d.ts +6 -5
  7. package/dist/ergonomic/image_output_routes.js +29 -21
  8. package/dist/ergonomic/option_types.d.ts +85 -11
  9. package/dist/ergonomic/option_types.js +11 -6
  10. package/dist/ergonomic/option_validation.d.ts +21 -3
  11. package/dist/ergonomic/option_validation.js +36 -3
  12. package/dist/ergonomic/preset_resolver.d.ts +1 -1
  13. package/dist/ergonomic/preset_resolver.js +6 -7
  14. package/dist/ergonomic/presets/document_epub_compress.d.ts +0 -2
  15. package/dist/ergonomic/presets/document_epub_compress.js +2 -7
  16. package/dist/ergonomic/presets/document_odf_compress.d.ts +0 -2
  17. package/dist/ergonomic/presets/document_odf_compress.js +2 -7
  18. package/dist/ergonomic/presets/document_office_compress.d.ts +0 -2
  19. package/dist/ergonomic/presets/document_office_compress.js +2 -7
  20. package/dist/errors.d.ts +57 -1
  21. package/dist/errors.js +82 -1
  22. package/dist/file-first.d.ts +195 -8
  23. package/dist/file-first.js +462 -124
  24. package/dist/generated/sdk_spec/enums.d.ts +4 -2
  25. package/dist/generated/sdk_spec/enums.js +11 -5
  26. package/dist/generated/sdk_spec/presets.js +3 -12
  27. package/dist/generated/sdk_spec/version.d.ts +3 -3
  28. package/dist/generated/sdk_spec/version.js +3 -3
  29. package/dist/gisl.d.ts +72 -3
  30. package/dist/gisl.js +72 -2
  31. package/dist/index.core.d.ts +8 -6
  32. package/dist/index.core.js +9 -1
  33. package/dist/merge.d.ts +12 -0
  34. package/dist/merge.js +12 -0
  35. package/dist/retry-metadata.d.ts +37 -0
  36. package/dist/retry-metadata.js +86 -0
  37. package/dist/sse.d.ts +2 -1
  38. package/dist/sse.js +26 -6
  39. package/dist/types.d.ts +43 -1
  40. package/package.json +2 -2
@@ -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;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@giveitsmaller/sdk",
3
- "version": "0.18.0",
3
+ "version": "0.20.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",
@@ -31,7 +31,7 @@
31
31
  "node": ">=18"
32
32
  },
33
33
  "dependencies": {
34
- "@giveitsmaller/contracts": "^0.35.0"
34
+ "@giveitsmaller/contracts": "^0.53.0"
35
35
  },
36
36
  "devDependencies": {
37
37
  "@types/node": "^22",