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

Helpers

Formatter hooks, plain formatters, and the core helpers for validation, errors, and checksums.

Helpers come from three places:

  • @dimah-s3/react — formatter hooks that follow the S3Provider locale and translations.
  • @dimah-s3/react/engine — plain formatters with no React import, such as formatFileSize.
  • @dimah-s3/core — protocol helpers: validation, checksums, and errors.

Formatter hooks

Each hook returns a formatter. Digits and unit labels follow the S3Provider locale and your translations; see Internationalization. The built-in UI uses the same hooks.

"use client";

import {
  useFormatDate,
  useFormatEta,
  useFormatFileSize,
  useFormatNumber,
  useFormatSpeed,
  type UploadFileState,
} from "@dimah-s3/react";

export function FileLine({ file }: { file: UploadFileState }) {
  const formatFileSize = useFormatFileSize();
  const formatSpeed = useFormatSpeed();
  const formatEta = useFormatEta();
  const formatNumber = useFormatNumber();
  const formatDate = useFormatDate();
  const { speed } = file.progress;

  return (
    <p>
      {file.fileName} · {formatFileSize(file.fileSize)} ·{" "}
      {formatNumber(file.progress.percent)}%
      {speed ? ` · ${formatSpeed(speed)}` : null}
      {formatEta(file.progress) ? ` · ${formatEta(file.progress)}` : null}
      {file.result?.lastModified
        ? ` · ${formatDate(file.result.lastModified)}`
        : null}
    </p>
  );
}
  • useFormatFileSize() — 12.4 MB.
  • useFormatNumber() — counts in the locale's digits.
  • useFormatSpeed() — 1.2 MB/s.
  • useFormatEta() — 9s left, 4m left, 1h 30m left. It takes a TransferProgress and returns null until the speed is known.
  • useFormatDate() — a Date, an ISO string, or epoch milliseconds, with optional Intl.DateTimeFormat options. Invalid input returns "".
  • useFormatAcceptLabels() — short labels for accept tokens, such as ["Images", "PDF"] for upload.policy.accept. Group labels such as Images are translated ("Images(file type group)").

Errors

Every error a hook reports is an APIError from @dimah-s3/core with a stable code. useFormatDimahError() turns it into a translated message, with sizes and counts in the provider's locale:

"use client";

import { useFormatDimahError, useUpload } from "@dimah-s3/react";
import { toast } from "@dimah-s3/ui";

export function AttachmentUploader() {
  const formatError = useFormatDimahError();
  const upload = useUpload({
    route: "attachments",
    onError: (error) => {
      toast.add({ type: "error", title: formatError(error) });
    },
  });

  return (
    <>
      <input {...upload.getInputProps()} />
      <button type="button" onClick={() => upload.open()}>
        Attach files
      </button>
    </>
  );
}

Codes it does not know, and plain Errors, return their message. To branch on a code, use isS3ErrorCode from core:

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

if (isS3ErrorCode(error, "FILE_TOO_LARGE")) {
  console.log(error.params); // { size, maxFileSize }
}

The full list of codes is in Errors. See also Outcomes and errors.


Plain formatters

These ship from the engine entry, so they work outside React and on the server. The size formatters take a locale and unit labels; the hooks above pass the provider's for you.

import {
  formatAcceptLabels,
  formatFileSize,
  formatSpeed,
  formatUploadProgress,
  toTimestamp,
  truncateFileName,
} from "@dimah-s3/react/engine";

formatFileSize(1_500_000); // "1.4 MB"
formatFileSize(1_500_000, { locale: "fa", units: { MB: "مگابایت" } }); // "۱٫۴ مگابایت"
formatUploadProgress(1_200_000, 2_000_000, 60); // "1.1 MB / 1.9 MB (60%)"
formatSpeed(1_200_000); // "1.1 MB/s"
formatAcceptLabels(["image/*", ".pdf", "application/zip"]); // ["Images", "PDF", "ZIP"]
formatAcceptLabels(["image/*"], { groups: { image: "Bilder" } }); // ["Bilder"]
truncateFileName("my-avatar-profile-picture-long-name.png", 20); // "my-avatar-profi….png"
toTimestamp("2026-10-31T12:00:00Z"); // 1793448000000, or null when invalid

truncateFileName is for plain text, such as a document title. In markup, the UI's FileName shortens a name to the width it has and keeps the extension visible.


Core helpers

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

// null when valid, otherwise an APIError:
// FILE_TYPE_NOT_ALLOWED, FILE_EMPTY, or FILE_TOO_LARGE
const error = validateFile(file, {
  accept: ["image/*"],
  maxFileSize: 2 * 1024 * 1024,
});

Checksums and public URLs

import { buildPublicObjectUrl, sha256Blob } from "@dimah-s3/core";

// SHA-256 as unpadded base64, read in 1 MiB chunks
const hash = await sha256Blob(file);

// Public CDN URL. It does not sign; use it for public-read objects only.
const url = buildPublicObjectUrl({
  baseUrl: "https://cdn.example.com",
  key: "avatar/uuid/avatar.png",
});

useUpload already validates files and sends checksums when the route asks for them. Use these helpers in your own pickers or server code.

On this page