Guide — Handling large files

APULODI splits large files into parts and uploads them in parallel-ish sequence against a single session. This guide covers the failure modes you'll hit in the real world.

Do I need multipart?

  • Files ≤ 8 MiB: the simple flow (one presigned PUT) is all you need — apulodi.files.upload() uses it automatically.
  • Files > 8 MiB: the SDK switches to multipart automatically. You can tune the threshold with multipartThreshold.
ts
await apulodi.files.upload({
  file: bigVideo,
  fileName: "gopro.mp4",
  contentType: "video/mp4",
  multipartThreshold: 16 * 1024 * 1024, // switch over sooner
});

Presigned URLs expire

Both the single-PUT URL and every part URL expire (default TTL is short for security). If a PUT returns a 403 (signature expired), fetch a fresh URL:

ts
const part = await apulodi.uploads.uploadPart(session, 7, chunk, {
  contentType: "video/mp4",
});
// uploadPart regenerates the URL automatically when the old one expired

For the simple flow, call files.upload() again with the same idempotency key to get a fresh URL without creating a second file.

Part count & ordering

The server validates on complete that:

  • the number of parts equals ceil(size / partSize),
  • part numbers are present and contiguous,
  • the assembled object's size matches the declared size.

Uploading the same part twice or skipping a number results in 409 UPLOAD_PARTS_INCOMPLETE / UPLOAD_SIZE_MISMATCH. List what storage actually holds to reconcile:

ts
const held = await apulodi.uploads.listStoredParts(session.id);
for (const p of held) console.log(p.partNumber, p.etag, p.size);

Aborting a failed upload

The SDK aborts automatically when a part PUT fails:

ts
try {
  await apulodi.files.upload({ file, fileName: "x.bin", contentType: "application/octet-stream" });
} catch (error) {
  // session was aborted for you; file is marked "failed"
}

To abandon an explicit session:

ts
await apulodi.uploads.abort(session.id);

Abort is idempotent. In production, sessions that expire are cleaned up by a background job; orphaned storage parts are reaped automatically.

Retrying complete

If the app crashes after the parts are uploaded but before complete(), just continue the session later — parts persist in storage:

ts
// Re-create the session object from the stored session id:
const resumed = {
  id: "upload_5c1a…",
  partSize: 8388608,
  parts: [], // URLs are regenerated on demand by uploadPart()
  expiresAt: "",
};

const parts = await apulodi.uploads.listStoredParts(resumed.id);
const completed = parts.map((p) => ({ partNumber: p.partNumber, etag: p.etag }));

const file = await apulodi.uploads.complete(resumed.id, completed);

Next: return to the Introduction or browse the API reference.