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

Errors

The error catalog, building errors with s3Error, and handling APIError.

Every failed request returns an HTTP status and a JSON body:

{
  "code": "FILE_TOO_LARGE",
  "message": "File is 5242880 bytes; the limit is 2097152 bytes",
  "params": { "size": 5242880, "maxFileSize": 2097152 }
}

code is always set and stable. message is English, filled from params. Show users your own text: map code and params to it, as useFormatDimahError does.

Throwing errors

Throw s3Error with a catalog code to reject a call from a guard or hook. Codes with params require them:

import { s3Error } from "@dimah-s3/server";

throw new Error("Not allowed"); 
throw s3Error("UNAUTHORIZED"); 
throw s3Error("FORBIDDEN");
throw s3Error("OBJECT_NOT_FOUND");
throw s3Error("QUOTA_EXCEEDED", { limit: "bytes" });
throw s3Error("FILE_TOO_LARGE", { size: file.size, maxFileSize });

A plain Error, thrown anywhere, is logged, passed to onError, and returns 500 INTERNAL_ERROR. Its message never reaches the client.

For a code of your own, such as from a plugin, use APIError.from:

import { APIError } from "@dimah-s3/server";

throw APIError.from(402, {
  code: "PLAN_REQUIRED",
  message: "Upgrade your plan to upload videos",
});

Handling errors

createS3Client and the React hooks reject with an APIError. Narrow it by code with isS3ErrorCode:

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

try {
  await api.download({ route: "avatar", key });
} catch (error) {
  if (isS3ErrorCode(error, "OBJECT_NOT_FOUND")) {
    // Handle the missing object
  } else if (isAPIError(error)) {
    console.log(error.statusCode, error.code, error.message, error.params);
  }
}

The client turns every failure into an APIError:

  • A JSON error body keeps its code, params, and HTTP status. A params entry that is not a string or number is dropped; the rest of the error is kept.
  • Any other error response becomes INTERNAL_ERROR (status 500 or above) or UNKNOWN_ERROR, with the real HTTP status.
  • A 2xx response whose body is not a JSON object or array, such as an SPA fallback index.html, becomes UNKNOWN_ERROR with its status.
  • The browser upload transport raises CHECKSUM_MISMATCH when S3 rejects a checksummed PUT or part (BadDigest or XAmzContentChecksumMismatch).
  • A request that fails before a response becomes NETWORK_ERROR.
  • An aborted request rejects with the signal's abort reason, not an APIError.

deleteMany reports per-key failures as wire bodies in failed[].error. Rebuild one with APIError.fromShape(error); the status comes from the catalog.

Reporting errors

onError receives every failure that is not an APIError: plain errors, config errors, and S3 network errors. They are logged too, then answered with 500 INTERNAL_ERROR or 502 S3_NETWORK_ERROR. Rejections you throw with s3Error do not reach it. The context has the request and, when known, the operation. It may be async: a rejection from it is logged, never unhandled. An error from another library that is merely named APIError is an unexpected failure too, so it is logged and reaches onError.

lib/s3.ts
export const s3 = dimahS3({
  onError: async (error, { request, operation }) => {
    Sentry.captureException(error, { extra: { url: request.url, operation } }); 
  },
  // ...
});

A throw from your upload.onConfirmed, even an APIError, returns 500 INTERNAL_ERROR and keeps the object, so the client can retry the confirm. onError gets a wrapping Error with the original as cause. Reject content in confirmGuard instead; a rejection there deletes the object.


Server codes

Codes the server sends. The status is in S3_ERROR_CODES[code].status.

codeHTTPparamsWhen
VALIDATION_ERROR400issueThe body or query does not match the endpoint schema.
REQUEST_TOO_LARGE413maxSizeThe request body is larger than maxRequestBodySize.
UNSUPPORTED_MEDIA_TYPE415—A body endpoint got a body that is not JSON.
NOT_FOUND404—No endpoint serves the request path.
UNAUTHORIZED401—A guard rejects an anonymous caller.
FORBIDDEN403—A guard or authorize rejects the caller.
CONFLICT409—A concurrent change won, or db() purge is cleaning the key. A retry may succeed.
INTERNAL_ERROR500—Unhandled server error, or a non-JSON error response with status 500 or above.
S3_NETWORK_ERROR502codeThe server cannot reach the S3 endpoint.
UNKNOWN_ROUTE404routeThe route name is not registered on the server.
FEATURE_DISABLED404featureThe route does not enable the requested operation, or /file was asked for a route that is not in proxy mode.
KEY_INVALID400—The key is unsafe, or outside the route keyPrefix.
OBJECT_NOT_FOUND404—The object or multipart upload does not exist, or db() tracks it for another scope.
RANGE_NOT_SATISFIABLE416—A /file Range header is outside the object.
PRECONDITION_FAILED412—A /file If-Match or If-Unmodified-Since fails.
FILE_EMPTY400—The file has 0 bytes. validateFile checks it before upload.
FILE_TOO_LARGE413size, maxFileSizeThe file is larger than the route maxFileSize.
FILE_TYPE_NOT_ALLOWED400type, allowedThe file type is not in the route fileTypes.
FILE_CONTENT_MISMATCH400type, detectedWith verifyContent, the stored bytes do not match the declared type.
CHECKSUM_REQUIRED400—The route needs a SHA-256 checksum and none was sent.
QUOTA_EXCEEDED403limitThe upload would go over the db() byte or file quota.
UPLOAD_TICKET_INVALID403—The upload ticket is forged, for another call, or for another subject.
UPLOAD_TICKET_EXPIRED410—The upload ticket is past its expiry.
UPLOAD_SIZE_MISMATCH400expected, actualThe stored size differs from the declared size.
MULTIPART_PART_MISSING400partNumberComplete found a part that was never uploaded.
MULTIPART_PART_INVALID400partNumber, partCountThe part number is outside the server's layout.
OBJECT_EXISTS409—A db() route without replace: "overwrite" already holds an object at the key.
FOLDER_INVALID400—The db() folder path is not valid, or deeper than 32 levels.
FOLDER_EXISTS409—A db() folder with that path already exists.
FOLDER_NOT_EMPTY409—A db() folder still holds folders or objects.

Client codes

Codes only the browser raises (client: true in the catalog). They never come from the server.

codeHTTPparamsWhen
TOO_MANY_FILES400maxFilesMore files were added than maxFiles allows.
HOOK_FAILED500hookOne of your callbacks, such as onSuccess, threw.
NETWORK_ERROR502—The browser request failed before a response arrived.
UPLOAD_FAILED502statusS3 rejected the upload. status is S3's HTTP status.
DOWNLOAD_FAILED502statususeFetchDownload got a non-2xx response.
CHECKSUM_MISMATCH400—The uploaded bytes do not match their checksum.
PARTIAL_FAILURE207failed, totalSome, but not all, files or keys in one call failed.
UNKNOWN_ERROR500—Any other failure, or a non-JSON error response below status 500.

Frequently asked questions

On this page