@giveitsmaller/sdk 0.7.0 → 0.9.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 CHANGED
@@ -121,4 +121,20 @@ export function _runAudit() {
121
121
  accept();
122
122
  accept();
123
123
  accept();
124
+ // FF1 / 3BIxEnfR — file-first result surface + sink errors.
125
+ accept();
126
+ accept();
127
+ accept();
128
+ accept();
129
+ accept();
130
+ accept();
131
+ accept();
132
+ // FF3a / u0hBt6fl — homogeneous fan-out builder surface.
133
+ accept();
134
+ accept();
135
+ accept();
136
+ accept();
137
+ // FF5a / Ao8RPVxD — file-first Handle reattach surface.
138
+ accept();
139
+ accept();
124
140
  }
package/dist/builder.d.ts CHANGED
@@ -26,6 +26,7 @@
26
26
  */
27
27
  import type { GislClient } from './client.js';
28
28
  import type { OperationDownload, WorkflowStatusResponse, SseOperationProgressDataStatusEnum } from '@giveitsmaller/contracts/openapi';
29
+ import { Handle } from './handle.js';
29
30
  import type { PresetDefaults, PresetMedia } from './ergonomic/presets/index.js';
30
31
  /**
31
32
  * Best-effort detection of the compress-operation media from the
@@ -183,14 +184,6 @@ export interface Result {
183
184
  */
184
185
  readonly resolvedOptions: ResolvedOptions;
185
186
  }
186
- /**
187
- * Lighter return value from `.submit({webhook})` — no SSE/poll wait,
188
- * caller reconciles completion via the webhook.
189
- */
190
- export interface Handle {
191
- readonly workflowId: string;
192
- readonly webhookSecret?: string;
193
- }
194
187
  /**
195
188
  * Upload-phase progress event. The byte counter comes from
196
189
  * `UploadOptions.onProgress` — there is no `phase` field on the wire.
package/dist/builder.js CHANGED
@@ -27,6 +27,10 @@
27
27
  import { SseEventType, SseOperationProgressDataFromJSON, } from '@giveitsmaller/contracts/openapi';
28
28
  import { uploadSource } from './types.js';
29
29
  import { GislTimeoutError } from './errors.js';
30
+ // Deferred-usage-only import: `Handle` is constructed inside submit() at call
31
+ // time, not at module load, so the builder.ts <-> handle.ts cycle is safe
32
+ // under ESM (handle.ts imports the await-primitives from this module).
33
+ import { Handle } from './handle.js';
30
34
  import { resolveCompressOptions, } from './ergonomic/preset_resolver.js';
31
35
  /**
32
36
  * Best-effort detection of the compress-operation media from the
@@ -264,11 +268,9 @@ export class OperationBuilder {
264
268
  callback_url: options.webhook,
265
269
  };
266
270
  const created = await this.client.createWorkflow(payload);
267
- const handle = {
268
- workflowId: created.workflowId,
269
- ...(created.webhookSecret != null ? { webhookSecret: created.webhookSecret } : {}),
270
- };
271
- return handle;
271
+ // No client passed → the returned Handle's status()/wait()/result()
272
+ // throw `no_client`; the operation-first submit reconciles via webhook.
273
+ return new Handle(created.workflowId, created.webhookSecret != null ? created.webhookSecret : undefined);
272
274
  }
273
275
  /**
274
276
  * Fan-out chain: run this builder to completion, then for each artifact
package/dist/client.js CHANGED
@@ -399,10 +399,19 @@ export class GislClient {
399
399
  locale: json.locale,
400
400
  messageParams: json.message_params,
401
401
  };
402
+ // Human-readable text comes from `message` (the I26 localised field).
403
+ // `error` is the stable, never-localised SCREAMING_SNAKE machine code —
404
+ // NOT display text. Surfacing `error` as the thrown error's `.message`
405
+ // regressed consumers that render the human string (x9Lbf6uy). Fall back
406
+ // to `error` when `message` is absent (deployed contract guarantees
407
+ // `message` on conforming error envelopes). Machine dispatch keys off
408
+ // `error_type` (below), unchanged.
409
+ const status = response.status;
410
+ const errorMessage = json.message ?? json.error ?? 'Unknown error';
402
411
  // Validation-details branch first — preserve existing shape so callers
403
412
  // matching on `instanceof GislValidationError` keep working.
404
413
  if (isValidationDetails(json.details)) {
405
- throw new GislValidationError(response.status, json.error ?? 'Validation error', json.details, path, i18n);
414
+ throw new GislValidationError(response.status, errorMessage, json.details, path, i18n);
406
415
  }
407
416
  // Dispatch by (status, error_type) onto the structured envelope shapes
408
417
  // emitted by the v2 contracts. Each branch builds the typed payload via
@@ -416,8 +425,6 @@ export class GislClient {
416
425
  // fall through to the base `GislApiError` rather than handing the
417
426
  // caller silently-corrupted typed metadata.
418
427
  const errorType = json.error_type;
419
- const status = response.status;
420
- const errorMessage = json.error ?? 'Unknown error';
421
428
  // Build the typed payload via FromJSON, then validate that all
422
429
  // required typed fields are well-formed. FromJSON does not throw on
423
430
  // missing required fields — for example `workflow_expired` without
@@ -1740,7 +1747,10 @@ export class GislClient {
1740
1747
  let errorMessage = 'Unknown error';
1741
1748
  try {
1742
1749
  const errJson = (await response.json());
1743
- if (errJson.error)
1750
+ // Prefer the human `message`; `error` is the machine code (x9Lbf6uy).
1751
+ if (errJson.message)
1752
+ errorMessage = errJson.message;
1753
+ else if (errJson.error)
1744
1754
  errorMessage = errJson.error;
1745
1755
  }
1746
1756
  catch {
package/dist/errors.d.ts CHANGED
@@ -300,6 +300,18 @@ export declare class GislChainCardinalityMismatchError extends GislConfigError {
300
300
  export declare class GislTimeoutError extends GislError {
301
301
  constructor(message: string);
302
302
  }
303
+ /**
304
+ * Transport-level failure: the underlying `fetch` (or other transport) could
305
+ * not produce a usable response — DNS, TCP, TLS, a mid-stream disconnect, or a
306
+ * non-ok status / empty body when fetching a result download. Mirrors the PHP
307
+ * `Gisl\Sdk\Errors\GislNetworkError`. Subclasses `GislError` (not
308
+ * `GislApiError`) because it carries no contract error envelope. The concrete
309
+ * file-first {@link Downloader} raises this when the output URL cannot be read
310
+ * (a destination-WRITE failure is `GislSinkError` reason `write_failed`).
311
+ */
312
+ export declare class GislNetworkError extends GislError {
313
+ constructor(message: string);
314
+ }
303
315
  export declare class GislAbortError extends GislError {
304
316
  constructor(message: string);
305
317
  }
@@ -326,3 +338,56 @@ export declare class GislMultipartPartCountError extends GislError {
326
338
  readonly maxParts: number;
327
339
  constructor(message: string, requiredParts: number, maxParts: number);
328
340
  }
341
+ /**
342
+ * Thrown by the file-first `RunResult.byKey()` (FF1) when no result entry
343
+ * matches the requested key. A keyless run (no `key:` supplied to `file()`)
344
+ * is addressable positionally only — `byKey()` always throws.
345
+ *
346
+ * Mirrors the PHP `Gisl\Sdk\Errors\GislNoSuchKeyError`.
347
+ */
348
+ export declare class GislNoSuchKeyError extends GislError {
349
+ constructor(message: string);
350
+ }
351
+ /**
352
+ * Thrown by the file-first `Handle.result()` (FF5a) when the workflow has not
353
+ * yet reached a terminal state. `result()` is the NON-blocking accessor: it
354
+ * fetches the current status once and, if the workflow is still
355
+ * `pending`/`in_progress`, throws this rather than waiting. Use `Handle.wait()`
356
+ * to block until terminal instead.
357
+ *
358
+ * Carries the `workflowId` and the current (non-terminal) `state`.
359
+ *
360
+ * Mirrors the PHP `Gisl\Sdk\Errors\GislResultNotReadyError`.
361
+ */
362
+ export declare class GislResultNotReadyError extends GislError {
363
+ readonly workflowId: string;
364
+ readonly state: string;
365
+ constructor(workflowId: string, state: string);
366
+ }
367
+ /** Machine-readable cause carried by {@link GislSinkError}. */
368
+ export type GislSinkErrorReason = 'not_single_output' | 'downloader_unavailable' | 'partial_failure' | 'duplicate_filename' | 'invalid_directory' | 'write_failed';
369
+ /**
370
+ * Thrown by the file-first `RunResult` sinks (`toFile()` / `downloadTo()`,
371
+ * FF1) when they cannot deliver. The machine-readable `reason` discriminates
372
+ * the three cases, mirroring the `reason`-bag convention on
373
+ * {@link GislConfigError}:
374
+ *
375
+ * - `not_single_output` — `toFile()` requires exactly one output but the
376
+ * run produced zero or more than one.
377
+ * - `downloader_unavailable` — the `RunResult` has no downloader bound (e.g. a
378
+ * browser / no-I/O context).
379
+ * - `partial_failure` — `downloadTo({ failOnPartial: true })` and the
380
+ * run had at least one failed input.
381
+ * - `duplicate_filename` — two outputs share a destination filename in one
382
+ * `downloadTo(dir)`, which would silently overwrite.
383
+ * - `write_failed` — a concrete {@link Downloader} could not open or
384
+ * stream to the destination path.
385
+ *
386
+ * Mirrors the PHP `Gisl\Sdk\Errors\GislSinkError`.
387
+ */
388
+ export declare class GislSinkError extends GislError {
389
+ readonly reason: GislSinkErrorReason;
390
+ constructor(message: string, options: {
391
+ readonly reason: GislSinkErrorReason;
392
+ });
393
+ }
package/dist/errors.js CHANGED
@@ -320,6 +320,21 @@ export class GislTimeoutError extends GislError {
320
320
  this.name = 'GislTimeoutError';
321
321
  }
322
322
  }
323
+ /**
324
+ * Transport-level failure: the underlying `fetch` (or other transport) could
325
+ * not produce a usable response — DNS, TCP, TLS, a mid-stream disconnect, or a
326
+ * non-ok status / empty body when fetching a result download. Mirrors the PHP
327
+ * `Gisl\Sdk\Errors\GislNetworkError`. Subclasses `GislError` (not
328
+ * `GislApiError`) because it carries no contract error envelope. The concrete
329
+ * file-first {@link Downloader} raises this when the output URL cannot be read
330
+ * (a destination-WRITE failure is `GislSinkError` reason `write_failed`).
331
+ */
332
+ export class GislNetworkError extends GislError {
333
+ constructor(message) {
334
+ super(message);
335
+ this.name = 'GislNetworkError';
336
+ }
337
+ }
323
338
  export class GislAbortError extends GislError {
324
339
  constructor(message) {
325
340
  super(message);
@@ -359,3 +374,65 @@ export class GislMultipartPartCountError extends GislError {
359
374
  this.maxParts = maxParts;
360
375
  }
361
376
  }
377
+ /**
378
+ * Thrown by the file-first `RunResult.byKey()` (FF1) when no result entry
379
+ * matches the requested key. A keyless run (no `key:` supplied to `file()`)
380
+ * is addressable positionally only — `byKey()` always throws.
381
+ *
382
+ * Mirrors the PHP `Gisl\Sdk\Errors\GislNoSuchKeyError`.
383
+ */
384
+ export class GislNoSuchKeyError extends GislError {
385
+ constructor(message) {
386
+ super(message);
387
+ this.name = 'GislNoSuchKeyError';
388
+ }
389
+ }
390
+ /**
391
+ * Thrown by the file-first `Handle.result()` (FF5a) when the workflow has not
392
+ * yet reached a terminal state. `result()` is the NON-blocking accessor: it
393
+ * fetches the current status once and, if the workflow is still
394
+ * `pending`/`in_progress`, throws this rather than waiting. Use `Handle.wait()`
395
+ * to block until terminal instead.
396
+ *
397
+ * Carries the `workflowId` and the current (non-terminal) `state`.
398
+ *
399
+ * Mirrors the PHP `Gisl\Sdk\Errors\GislResultNotReadyError`.
400
+ */
401
+ export class GislResultNotReadyError extends GislError {
402
+ workflowId;
403
+ state;
404
+ constructor(workflowId, state) {
405
+ super(`Workflow ${workflowId} is not ready (state '${state}'); its result is not available yet. ` +
406
+ 'Call wait() to block until it reaches a terminal state, or poll result() again later.');
407
+ this.name = 'GislResultNotReadyError';
408
+ this.workflowId = workflowId;
409
+ this.state = state;
410
+ }
411
+ }
412
+ /**
413
+ * Thrown by the file-first `RunResult` sinks (`toFile()` / `downloadTo()`,
414
+ * FF1) when they cannot deliver. The machine-readable `reason` discriminates
415
+ * the three cases, mirroring the `reason`-bag convention on
416
+ * {@link GislConfigError}:
417
+ *
418
+ * - `not_single_output` — `toFile()` requires exactly one output but the
419
+ * run produced zero or more than one.
420
+ * - `downloader_unavailable` — the `RunResult` has no downloader bound (e.g. a
421
+ * browser / no-I/O context).
422
+ * - `partial_failure` — `downloadTo({ failOnPartial: true })` and the
423
+ * run had at least one failed input.
424
+ * - `duplicate_filename` — two outputs share a destination filename in one
425
+ * `downloadTo(dir)`, which would silently overwrite.
426
+ * - `write_failed` — a concrete {@link Downloader} could not open or
427
+ * stream to the destination path.
428
+ *
429
+ * Mirrors the PHP `Gisl\Sdk\Errors\GislSinkError`.
430
+ */
431
+ export class GislSinkError extends GislError {
432
+ reason;
433
+ constructor(message, options) {
434
+ super(message);
435
+ this.name = 'GislSinkError';
436
+ this.reason = options.reason;
437
+ }
438
+ }