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

Purge Stale Uploads

Clean up uploads that were never confirmed, and prune old soft-deleted rows.

An upload that starts but never confirms (a closed tab, a network error) leaves a pending row, and sometimes an object or a multipart upload in S3. Each pending row expires with its upload ticket. After that it no longer counts toward the quota, and s3.db.purgeStale cleans it up.

scripts/purge.ts
import { s3 } from "@/lib/s3";

const { purged, failed, more } = await s3.db.purgeStale();
console.log(`Purged ${purged} stale uploads, ${failed} failed.`);
if (more) console.log("More stale rows are waiting.");

What purge does

purgeStale({ limit }) handles at most limit rows per run (default 100), oldest first. It returns purged (uploads cleaned up), failed (rows that will be retried), skipped (rows another run claimed, or that were confirmed meanwhile), and more, which is true when the run hit limit. For each row past its deadline:

  1. It claims the row with a compare-and-set. A pending row becomes purging. A row that was confirmed, deleted, or taken by another run in the meantime is skipped.
  2. It aborts the multipart upload, if there is one.
  3. It runs HeadObject, and deletes the object only while it still carries this upload's marker. The delete sends If-Match with the ETag, so an object written after HeadObject survives. A provider that does not implement If-Match gets a plain delete.
  4. It removes the row.

An expired overwrite keeps its active row and the previous object, unless the overwrite's object had already replaced it. Then the row goes too.

A row that fails stays claimed, and a warning goes to the dimah-s3 logger. A row whose route has no S3 client (a route you removed from dimahS3()) fails the same way instead of leaving its object untracked: keep the route until its rows are purged. The claim lapses after 15 minutes, and a later run retries the row. An upload to a key that purge has claimed gets CONFLICT until then.

The deadline is the upload ticket's expiry: by default one hour after the presigned URL expires, and 24 hours for multipart uploads. Change it with upload.confirmWindow and upload.multipart.ticketExpiresIn (see upload tickets).

Prune deleted rows

With deleteMode: "soft", deleted rows stay for audit. pruneDeleted removes the rows deleted before olderThan:

scripts/purge.ts
const DAY = 24 * 60 * 60 * 1000;

const { pruned } = await s3.db.pruneDeleted({
  olderThan: new Date(Date.now() - 30 * DAY),
});

olderThan is a Date or epoch milliseconds, not a duration.

Prop

Type

Frequently asked questions

On this page