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

Upload Hooks

Authorize uploads, track them from start to confirm, and persist verified metadata.

Enable upload operations on a named route with upload: true or an UploadConfig object.

lib/s3.ts
import { dimahS3, route, s3Error } from "@dimah-s3/server";
import { awsS3 } from "./aws-s3";

export const s3 = dimahS3({
  client: awsS3,
  bucket: process.env.S3_BUCKET!,
  routes: {
    avatar: route({
      upload: {
        fileTypes: ["image/*"],
        maxFileSize: 2 * 1024 * 1024,
        guard: async ({ request }) => {
          const session = await getSession(request);
          if (!session) throw s3Error("UNAUTHORIZED");
        },
        onConfirmed: async ({ key, object, request }) => {
          const session = await getSession(request); 
          await db.user.update({
            where: { id: session.userId }, 
            data: { avatarKey: key, avatarSize: object.size }, 
          }); 
        }, 
      },
    }),
  },
});

Upload lifecycle flow

Upload lifecycle
  1. route.guardhook
  2. objecthook
  3. upload.guardhook
  4. onStarthook
  5. Upload to S3client
  6. HeadObjectS3
  7. confirmGuardhook
  8. onConfirmedhook
  1. route.guard: Runs after the instance guard, before the key is chosen.
  2. object: Chooses the key folder or key, S3 metadata, ACL, storage class, cache control, and tags.
  3. upload.guard: Authorizes the upload once the server-owned key is known.
  4. onStart: Runs once the upload can begin, with its uploadToken and confirm deadline. The presign response, with its upload ticket, is returned after it.
  5. Confirm: HeadObject reads the stored object. The server checks the upload marker, the size against the declared size (UPLOAD_SIZE_MISMATCH), and the route limits. With verifyContent, it checks the first bytes too.
  6. confirmGuard: Authorizes the stored object. It sees the verified object from HeadObject next to the declared file, so check object. A rejection deletes it.
  7. onConfirmed: Receives the verified object from HeadObject, and the previousKey the server deletes next. Persist the record here.

upload.guard and onStart also run for multipart init and s3.put. kind tells them apart: "put" or "post" for a presigned upload, "multipart", or "server" for s3.put. s3.put skips confirmGuard.


Tracking an upload

onStart is the place to record a pending upload. It receives the absolute deadline after which the upload can never be confirmed:

upload: {
  onStart: async ({ uploadToken, key, file, expiresAt, locals }) => {
    await db.upload.create({
      data: {
        token: uploadToken,
        key,
        name: file.name,
        userId: locals.userId,
        expiresAt, 
      },
    });
  },
  onConfirmed: async ({ uploadToken, object }) => {
    await db.upload.updateMany({
      where: { token: uploadToken, status: "pending" },
      data: { status: "active", size: object.size },
    });
  },
},

expiresAt is the ticket expiry, or 15 minutes from now for s3.put. Presigned uploads also get url and urlExpiresAt, and multipart uploads get uploadId.


Failure rules

HookWhen it throws
upload.guardThe error is returned. Nothing exists in S3.
onStartThe error is returned. A multipart upload is aborted; s3.put skips PutObject.
confirmGuardA rejection (4xx) deletes the object, or aborts the multipart upload.
onConfirmed, pluginA rejection (4xx) deletes the object. Any other throw keeps it for a retry.
onConfirmed, yoursThe object is kept and the call returns 500 INTERNAL_ERROR.

A confirm that fails for an infrastructure reason, such as an S3 network error or a guard whose database is down, keeps the object so the client can retry.

onConfirmed runs at least once per upload. Confirm is stateless, so a retried confirm runs every check and hook again with the same uploadToken. Make the hook safe to replay, and reject content in confirmGuard, not in onConfirmed.


Type reference

import type {
  UploadGuardContext,
  UploadOnStartContext,
  UploadConfirmGuardContext,
  UploadOnConfirmedContext,
  UploadObjectContext,
  VerifiedObject,
} from "@dimah-s3/server";

UploadGuardContext

Runs after upload.object assigns the key (and after the instance and route guards).

Prop

Type


UploadOnStartContext

Prop

Type


UploadConfirmGuardContext

Runs on confirm and multipart complete, after HeadObject and the size checks.

Prop

Type


UploadOnConfirmedContext

Prop

Type


VerifiedObject

The object in onConfirmed, as HeadObject reports it.

Prop

Type


UploadObjectContext

Input passed to upload.object to generate custom destination keys and metadata.

Prop

Type

Frequently asked questions

On this page