2.0 beta — new releases can include breaking changes. Stable 1.x docs
dimah-s3v2.0.0-beta.0

Outcomes and errors

What hook actions return, how callbacks run, and how to read errors and aborts.

Hook actions do not throw for transfer errors. Each one resolves with an outcome for one key, or a result for a batch. Read the status, then the data or the error.

Outcomes

remove, download, and presign resolve with an outcome:

type OperationOutcome<TData> =
  | { status: "success"; key: string; data: TData }
  | { status: "error"; key: string; error: APIError }
  | { status: "blocked"; key: string }
  | { status: "aborted"; key: string };
  • success — data holds the response.
  • error — error is an APIError.
  • blocked — a before* callback returned false.
  • aborted — the call was cancelled. A delete has no cancel, so it never ends this way.
const outcome = await download(objectKey);

if (outcome.status === "error") {
  console.log(outcome.key, outcome.error.code);
}

Results

Batch actions resolve with one result that sorts every unit:

ActionResult
useUpload().handleFiles{ uploaded, failed, blocked, aborted } by file
useDelete().removeMany{ deleted, failed, blocked } by key
useDownload().presignMany{ signed, failed, blocked } by key

failed entries pair the unit with its APIError, such as { key, error }.

Joined calls

The download and delete hooks track each key on its own. A second call for a key that is already running does not start a new request. It joins the running call and resolves with the same outcome, and the callbacks do not run a second time.

Callback order

Every hook runs its callbacks the same way:

  1. before* (beforeUpload, beforeDownload, beforeDelete) runs once per call. Returning false blocks the call. A throw fails it with HOOK_FAILED.
  2. on*Start (onUploadStart, onDeleteStart, onDownloadStart) runs once per call. A throw fails it with HOOK_FAILED.
  3. onError runs once per failed unit, then onSuccess once per successful unit.
  4. onSettled runs once per call, last.

onSuccess, onSettled, and the other after-hooks are awaited. If one throws, the error goes to the global reportError and the outcome does not change.

Errors

Every error is an APIError from @dimah-s3/core:

  • code — always set. Branch on it with isS3ErrorCode(error, code).
  • params — values for the message, such as { size, maxFileSize }.
  • statusCode — the HTTP status.
  • message — English. Show users useFormatDimahError() instead; see Helpers.

Server codes come from the route and your hooks. Some codes are raised in the browser only:

CodeWhen
TOO_MANY_FILESA call has more valid files than maxFiles.
HOOK_FAILEDA before* or on*Start callback threw.
NETWORK_ERRORA request got no response.
UPLOAD_FAILEDS3 rejected an upload. params.status is S3's status.
DOWNLOAD_FAILEDA fetch download got a non-2xx response.
PARTIAL_FAILUREupload.error when more than one file failed.
UNKNOWN_ERRORAnything that is not an APIError. The original is cause.

The full table is in Errors.

import { isS3ErrorCode } from "@dimah-s3/core";

const { failed } = await upload.handleFiles(files);
for (const { file, error } of failed) {
  if (isS3ErrorCode(error, "FILE_TOO_LARGE")) {
    console.log(`${file.name} is too large`);
  }
}

Aborts

A cancel is not an error. Hooks report it as an aborted outcome, or as the aborted list of an upload result.

Engine functions such as uploadFile reject with the signal's reason instead. The upload abort reasons are DOMExceptions named AbortError. Read which kind with getUploadAbortKind:

import { getUploadAbortKind } from "@dimah-s3/react/engine";

try {
  await uploadFile(api, file, options);
} catch (error) {
  const kind = getUploadAbortKind(error); // "cancel", "detach", or null
  if (kind === null) throw error;
}

See Engine.

Type reference

Prop

Type

Prop

Type

On this page