Skip to content

Latest commit

 

History

History
409 lines (310 loc) · 10.7 KB

File metadata and controls

409 lines (310 loc) · 10.7 KB

Constructor

Warning

s3mini is a deprecated alias retained solely for backward compatibility. It is scheduled for removal in a future release. Please migrate to the new S3mini class.

Create a new instance of the s3mini client:

import { S3mini } from 's3mini';
// add S3Config types if needed for Typescript

const s3client = new S3mini({
  accessKeyId: 'YOUR_ACCESS_KEY_ID',
  secretAccessKey: 'YOUR_SECRET_ACCESS_KEY',
  endpoint: 'https://s3.amazonaws.com/...', // S3 endpoint (use your region or custom domain) including bucket name
  region: 'us-east-1',// AWS region (use 'auto' for Cloudflare R2)
  maxRequestSizeInBytes?: 8388608, // Optional, defaults to 8MB
  requestAbortTimeout?: 30000, // Optional, timeout in milliseconds
  logger?: console, // Optional, custom logger
});

Bucket Operations

Check if Bucket Exists

const bucketExists = await s3client.bucketExists();
console.log(`Bucket exists: ${bucketExists}`);

Create Bucket

const created = await s3client.createBucket();
console.log(`Bucket created: ${created}`);

Object Operations

List Operations

List Objects

const delimiter = '/';
const prefix = 'folder/';
const maxKeys = 1000;

const objects = await s3client.listObjects(delimiter, prefix, maxKeys);
console.log(`Objects: ${JSON.stringify(objects)}`);

List Multipart Uploads

const delimiter = '/';
const prefix = 'folder/';

const uploads = await s3client.listMultiPartUploads(delimiter, prefix);
console.log(`Multipart uploads: ${JSON.stringify(uploads)}`);

Upload a File

const fileContent = 'Hello, World!';
const key = 'example.txt';
const response = await s3client.putObject(key, fileContent);
console.log(`File uploaded successfully: ${response.status === 200}`);

Get a File

const key = 'example.txt';
const response = await s3client.getObject(key);
if (response) {
  const content = await response.text();
  console.log(`File content: ${content}`);
} else {
  console.log('File not found');
}

Get a File with ETag

const key = 'example.txt';
const { etag, data } = await s3client.getObjectWithETag(key);
if (data) {
  console.log(`File content: ${data}`);
  console.log(`ETag: ${etag}`);
}

Check if File Exists

const key = 'example.txt';
const exists = await s3client.objectExists(key);
console.log(`File exists: ${exists}`);

Get ETag of a File

const key = 'example.txt';
const etag = await s3client.getEtag(key);
console.log(`File ETag: ${etag}`);

Get Content Length

const key = 'example.txt';
const contentLength = await s3client.getContentLength(key);
console.log(`File size: ${contentLength} bytes`);

Get Raw Response

const key = 'example.txt';
const response = await s3client.getObjectRaw(key);
// Process the raw response

Delete a File

const key = 'example.txt';
const deleted = await s3client.deleteObject(key);
console.log(`File deleted: ${deleted}`);

Object Versioning

Requires a versioned bucket and a provider that implements the versioning S3 APIs (ListObjectVersions, GetObject with versionId, versioned Delete/Copy).

Provider notes:

Provider Notes
MinIO / AWS S3 / Ceph Full support; enable with setBucketVersioning('Enabled') (MinIO E2E does this in setup)
Backblaze B2 Object versions work; setBucketVersioning may 403 — enable “keep all versions” in the B2 bucket settings
Cloudflare R2 Not supported. R2 returns x-amz-version-id on Put for client compatibility, but ListObjectVersions / GetObject?versionId / PutBucketVersioning return 501 NotImplemented
Garage No S3 object versioning

Enable / read bucket versioning

await s3client.setBucketVersioning('Enabled'); // or 'Suspended'
const status = await s3client.getBucketVersioning(); // 'Enabled' | 'Suspended' | 'Off'

Upload response headers may include x-amz-version-id (on some providers this is cosmetic only — verify with get-by-versionId).

Get a specific version

// opts are forwarded as query parameters
const body = await s3client.getObject('file.jpg', { versionId: 'abc123' });

List all versions of one object

// Returns every version (+ delete markers) for the exact key.
// Each entry may include VersionId, IsLatest, IsDeleteMarker.
const versions = await s3client.listObjectVersions('file.jpg');
const latest = versions?.find(v => v.IsLatest && !v.IsDeleteMarker);
const older = versions?.filter(v => !v.IsLatest && !v.IsDeleteMarker);

List versions for a prefix (bucket-wide)

// Same listObjects API; pass { versions: true }
const all = await s3client.listObjects('/', 'photos/', undefined, { versions: true });
// also works with listObjectsPaged(..., { versions: true })

Restore an older version (copy onto same key)

await s3client.copyObject('file.jpg', 'file.jpg', { versionId: olderVersionId });

Delete a specific version

await s3client.deleteObject({ key: 'file.jpg', versionId: 'abc123' });

// Bulk: mix plain keys and versioned targets
await s3client.deleteObjects([
  'other.txt',
  { key: 'file.jpg', versionId: 'v1' },
  { key: 'file.jpg', versionId: 'v2' },
]);

Multipart Upload

Initiate Multipart Upload

const key = 'large-file.txt';
const fileType = 'text/plain';
const uploadId = await s3client.getMultipartUploadId(key, fileType);
console.log(`Multipart upload initiated with ID: ${uploadId}`);

Upload Part

const key = 'large-file.txt';
const partContent = Buffer.from('Part content...');
const uploadId = 'your-upload-id';
const partNumber = 1;

const partResult = await s3client.uploadPart(key, partContent, uploadId, partNumber);
console.log(`Part uploaded: ${JSON.stringify(partResult)}`);

Complete Multipart Upload

const key = 'large-file.txt';
const uploadId = 'your-upload-id';
const parts = [
  { partNumber: 1, ETag: 'etag1' },
  { partNumber: 2, ETag: 'etag2' },
];

const result = await s3client.completeMultipartUpload(key, uploadId, parts);
console.log(`Multipart upload completed: ${JSON.stringify(result)}`);

Abort Multipart Upload

const key = 'large-file.txt';
const uploadId = 'your-upload-id';

const result = await s3client.abortMultipartUpload(key, uploadId);
console.log(`Multipart upload aborted: ${JSON.stringify(result)}`);

Useful Helpers

Sanitize ETag

import { sanitizeETag } from 's3mini';
...
const rawETag = '\"abcdef1234567890\"';
const sanitizedETag = s3client.sanitizeETag(rawETag);
console.log(`Sanitized ETag: ${sanitizedETag}`); // Outputs: abcdef1234567890

Ratelimiting and batching (runInBatches)

Some operations can be rate-limited. Use the runInBatches method to process items in batches within a specified time interval:

import { runInBatches } from 's3mini';
const OP_CAP = 50; // Max operations per second
const INTERVAL = 1_000; // Interval in milliseconds
const generator = function* (n) {
  for (let i = 0; i < n; i++)
    yield async () => {
      await s3client.putObject(`${prefix}object${i}.txt`, 'hello world');
    };
};
// you can feed runInBatches with any async generator or array of promises/async functions
await runInBatches(generator(5000), OP_CAP, INTERVAL);

Pre-signed URLs

Generate time-limited URLs for direct client access without credentials.

Generate a Download URL

// Default expiration: 1 hour (3600 seconds)
const downloadUrl = await s3client.getPresignedUrl('GET', 'example.txt');
console.log(`Download URL: ${downloadUrl}`);

Generate an Upload URL

// Expires in 5 minutes
const uploadUrl = await s3client.getPresignedUrl('PUT', 'uploads/file.bin', 300);
console.log(`Upload URL: ${uploadUrl}`);

Client-Side Upload via Pre-signed URL

const uploadUrl = await s3client.getPresignedUrl('PUT', 'user-upload.jpg', 600);

// No credentials needed — use plain fetch
const response = await fetch(uploadUrl, {
  method: 'PUT',
  body: fileBuffer,
  headers: { 'Content-Type': 'image/jpeg' },
});
console.log(`Upload success: ${response.ok}`);

Client-Side Download via Pre-signed URL

const downloadUrl = await s3client.getPresignedUrl('GET', 'user-upload.jpg');

const response = await fetch(downloadUrl);
const data = await response.arrayBuffer();

Custom Response Headers

const url = await s3client.getPresignedUrl('GET', 'report.pdf', 3600, {
  'response-content-disposition': 'attachment; filename="report.pdf"',
  'response-content-type': 'application/pdf',
});

Signed Headers

Enforce that the client sends specific HTTP headers when using the pre-signed URL. The server will reject requests where signed headers don't match.

// Upload URL that requires Content-Type — client MUST send this exact header
const url = await s3client.getPresignedUrl('PUT', 'uploads/data.json', 300, {}, {
  'Content-Type': 'application/json',
});

await fetch(url, {
  method: 'PUT',
  body: JSON.stringify({ ok: true }),
  headers: { 'Content-Type': 'application/json' }, // must match signed value
});

Error Handling

The library throws descriptive error messages for invalid parameters and failed operations. Always use try-catch blocks when working with asynchronous operations:

try {
  const result = await s3client.getObject('non-existent-file.txt');
  // Process result
} catch (error) {
  console.error(`Error: ${error.message}`);
}

Advanced Usage

Custom Headers and Options

Many methods accept optional parameters for customization:

// Get with conditional headers
const result = await s3client.getObject('example.txt', {
  'if-match': 'etag-value',
  'if-modified-since': new Date().toUTCString(),
});

Custom Logger Integration

The library supports custom loggers for better integration with your application's logging system:

const customLogger = {
  info: message => {
    /* Custom info logging */
  },
  error: message => {
    /* Custom error logging */
  },
  warn: message => {
    /* Custom warning logging */
  },
};

const s3client = new S3mini({
  // Other parameters
  logger?: customLogger,
});