CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/npm-cfkit--r2

High-level Cloudflare R2 storage API wrapper for generating pre-signed URLs and performing object operations

Overview
Eval results
Files

bucket-operations.mddocs/reference/

Bucket Operations

Bucket operations provide functionality for managing buckets and checking the existence of buckets and objects.

Key Information for Agents

Core Capabilities:

  • Create bucket instances from R2Client for bucket-scoped operations
  • List all buckets in the account
  • Check bucket existence without throwing errors
  • Get detailed bucket information (name, creation date, location)
  • Check object existence in a bucket
  • Access internal methods for advanced use cases

Key Methods:

  • R2Client.bucket(name: string): R2Bucket - Factory method to create bucket instances
  • R2Client.listBuckets(): Promise<BucketInfo[]> - List all account buckets
  • R2Bucket.getName(): string - Get bucket name (synchronous)
  • R2Bucket.exists(): Promise<boolean> - Check if bucket exists (non-throwing)
  • R2Bucket.getInfo(): Promise<BucketDetails> - Get bucket details (throws if not exists)
  • R2Bucket.objectExists(key: string): Promise<boolean> - Check if object exists (non-throwing)
  • R2Client.getBaseUrl(): string - Internal: Get base URL for R2 API
  • R2Client.getAwsClient(): AwsClient - Internal: Get AWS signing client

Default Behaviors:

  • listBuckets() returns empty array [] if account has no buckets (not an error)
  • exists() returns false for non-existent buckets (does not throw)
  • getInfo() throws error if bucket doesn't exist (use exists() first to check)
  • objectExists() returns false for non-existent objects (does not throw)
  • getName() is synchronous and always returns the bucket name
  • All R2 buckets have location: "auto" (automatic location constraint)
  • creationDate may be undefined for some buckets (depends on R2 API response)

Threading Model:

  • All methods are stateless and thread-safe
  • Multiple concurrent exists() or objectExists() calls are safe
  • getName() is synchronous and thread-safe
  • No internal locking or state management

Lifecycle:

  • Bucket instances created via R2Client.bucket() are reusable
  • Bucket instances are lightweight (no connection pooling)
  • Buckets must exist in R2 before operations (except listBuckets())
  • Bucket existence doesn't change during instance lifetime (cache if needed)

Common Patterns:

  • Check before operation: if (await bucket.exists()) { /* operate */ }
  • Check object before download: if (await bucket.objectExists(key)) { /* download */ }
  • List and filter: const buckets = await r2.listBuckets(); const names = buckets.map(b => b.name);
  • Multiple bucket instances: Create separate instances for different buckets
  • Logging: Use bucket.getName() for consistent logging across operations

Integration Points:

  • Uses S3-compatible ListBuckets API (GET /)
  • Uses S3-compatible GetBucketLocation API for getInfo()
  • Uses S3-compatible HeadObject API for objectExists()
  • Base URL format: https://{accountId}.r2.cloudflarestorage.com

Critical Edge Cases:

  • Non-existent bucket: getInfo() throws error, exists() returns false
  • Non-existent object: objectExists() returns false (not an error)
  • Empty account: listBuckets() returns empty array (not an error)
  • Invalid credentials: All operations throw authentication error
  • Network failures: Operations throw generic fetch errors
  • Missing creationDate: Some buckets may not have creationDate (undefined)
  • Location always "auto": All R2 buckets return location: "auto" (not region-specific)
  • Concurrent existence checks: Safe but may have race conditions with actual operations
  • Bucket deletion: If bucket deleted between exists() and operation, operation will fail
  • Race conditions: Bucket/object may be deleted between existence check and operation

Exception Handling:

  • getInfo() throws error if bucket doesn't exist
  • listBuckets() throws error on authentication failure or network error
  • exists() and objectExists() never throw (return false on errors)
  • Always wrap getInfo() and listBuckets() in try-catch
  • exists() and objectExists() are safe to call without try-catch

Capabilities

Create Bucket Instance

Get a bucket instance from the R2Client for performing operations on a specific bucket.

/**
 * Get a bucket instance for performing operations on a specific bucket
 * @param name - The name of the bucket
 * @returns An R2Bucket instance for the specified bucket
 */
bucket(name: string): R2Bucket;

Usage Examples:

import { R2Client } from '@cfkit/r2';

const r2 = new R2Client({
  accountId: process.env.CLOUDFLARE_ACCOUNT_ID!,
  accessKeyId: process.env.R2_ACCESS_KEY_ID!,
  secretAccessKey: process.env.R2_SECRET_ACCESS_KEY!
});

// Create bucket instance
const bucket = r2.bucket('gallery');

// Multiple bucket instances
const galleryBucket = r2.bucket('gallery');
const uploadsBucket = r2.bucket('uploads');
const backupsBucket = r2.bucket('backups');

List Buckets

List all buckets in the Cloudflare account.

/**
 * List all buckets in the account
 * Uses the S3-compatible ListBuckets API (GET /)
 * @returns Array of bucket information with names, creation dates, and location
 */
listBuckets(): Promise<BucketInfo[]>;

interface BucketInfo {
  /** Bucket name */
  name: string;
  /** Creation date */
  creationDate?: Date;
  /** Bucket location constraint (typically "auto" for R2) */
  location: string;
}

Usage Examples:

import { R2Client } from '@cfkit/r2';

const r2 = new R2Client({
  accountId: process.env.CLOUDFLARE_ACCOUNT_ID!,
  accessKeyId: process.env.R2_ACCESS_KEY_ID!,
  secretAccessKey: process.env.R2_SECRET_ACCESS_KEY!
});

// List all buckets
const buckets = await r2.listBuckets();
console.log('Available buckets:', buckets.map(b => b.name));
// => ['gallery', 'uploads', 'backups']

// Access bucket details
buckets.forEach(bucket => {
  console.log(`Bucket: ${bucket.name}`);
  console.log(`  Created: ${bucket.creationDate}`);
  console.log(`  Location: ${bucket.location}`);
});

// Check if a specific bucket exists in the list
const bucketNames = buckets.map(b => b.name);
const hasGallery = bucketNames.includes('gallery');
console.log('Gallery bucket exists:', hasGallery);

Check Bucket Existence

Check if a bucket exists.

/**
 * Check if the bucket exists
 * @returns true if bucket exists, false otherwise
 */
exists(): Promise<boolean>;

Usage Examples:

import { R2Client } from '@cfkit/r2';

const r2 = new R2Client({
  accountId: process.env.CLOUDFLARE_ACCOUNT_ID!,
  accessKeyId: process.env.R2_ACCESS_KEY_ID!,
  secretAccessKey: process.env.R2_SECRET_ACCESS_KEY!
});

const bucket = r2.bucket('gallery');

// Check if bucket exists
const exists = await bucket.exists();
if (exists) {
  console.log('Bucket exists');
} else {
  console.log('Bucket does not exist');
}

// Conditional operations based on existence
if (await bucket.exists()) {
  await bucket.uploadFile('photo.jpg', file, {
    contentType: 'image/jpeg'
  });
}

Get Bucket Information

Get detailed information about a bucket including its location.

/**
 * Get bucket information and metadata
 * Uses the S3-compatible GetBucketLocation API
 * @returns Bucket details including name and location
 */
getInfo(): Promise<BucketDetails>;

interface BucketDetails {
  /** Bucket name */
  name: string;
  /** Creation date */
  creationDate?: Date;
  /** Bucket location constraint (typically "auto" for R2) */
  location: string;
}

Usage Examples:

import { R2Client } from '@cfkit/r2';

const r2 = new R2Client({
  accountId: process.env.CLOUDFLARE_ACCOUNT_ID!,
  accessKeyId: process.env.R2_ACCESS_KEY_ID!,
  secretAccessKey: process.env.R2_SECRET_ACCESS_KEY!
});

const bucket = r2.bucket('gallery');

// Get bucket information
const info = await bucket.getInfo();
console.log(`Bucket: ${info.name}`);
console.log(`Location: ${info.location}`);
// => Bucket: gallery
// => Location: auto

// All R2 buckets use the "auto" location
if (info.location === 'auto') {
  console.log('This is an R2 bucket with automatic location');
}

Get Bucket Name

Get the name of the bucket instance.

/**
 * Get the bucket name
 * @returns The bucket name
 */
getName(): string;

Usage Examples:

import { R2Client } from '@cfkit/r2';

const r2 = new R2Client({
  accountId: process.env.CLOUDFLARE_ACCOUNT_ID!,
  accessKeyId: process.env.R2_ACCESS_KEY_ID!,
  secretAccessKey: process.env.R2_SECRET_ACCESS_KEY!
});

const bucket = r2.bucket('gallery');

// Get bucket name
const name = bucket.getName();
console.log('Bucket name:', name);
// => Bucket name: gallery

// Useful for logging or validation
function logOperation(bucket: R2Bucket, operation: string) {
  console.log(`Performing ${operation} on bucket: ${bucket.getName()}`);
}

logOperation(bucket, 'upload');

Check Object Existence

Check if an object exists in the bucket.

/**
 * Check if an object exists in the bucket
 * @param key - Object key (filename)
 * @returns true if object exists, false otherwise
 */
objectExists(key: string): Promise<boolean>;

Usage Examples:

import { R2Client } from '@cfkit/r2';

const r2 = new R2Client({
  accountId: process.env.CLOUDFLARE_ACCOUNT_ID!,
  accessKeyId: process.env.R2_ACCESS_KEY_ID!,
  secretAccessKey: process.env.R2_SECRET_ACCESS_KEY!
});

const bucket = r2.bucket('gallery');

// Check if object exists
const exists = await bucket.objectExists('photo.jpg');
if (exists) {
  console.log('Object exists');
} else {
  console.log('Object does not exist');
}

// Conditional download based on existence
if (await bucket.objectExists('photo.jpg')) {
  const downloadUrl = await bucket.presignedDownloadUrl('photo.jpg');
  console.log('Download URL:', downloadUrl.url);
}

// Avoid overwriting existing files
const key = 'important-file.jpg';
if (await bucket.objectExists(key)) {
  console.log('File already exists, skipping upload');
} else {
  await bucket.uploadFile(key, file, {
    contentType: 'image/jpeg'
  });
}

// Check multiple objects
const keys = ['photo1.jpg', 'photo2.jpg', 'photo3.jpg'];
const existenceChecks = await Promise.all(
  keys.map(key => bucket.objectExists(key))
);
const existingFiles = keys.filter((_, index) => existenceChecks[index]);
console.log('Existing files:', existingFiles);

Advanced Usage

R2Client Internal Methods

The R2Client exposes some internal methods for advanced use cases. These are typically not needed for normal operations.

/**
 * Get the base URL for R2 operations (internal use)
 * @returns The base URL used for all R2 API requests
 */
getBaseUrl(): string;

/**
 * Get the AWS client instance (internal use)
 * @returns The underlying AwsClient instance from aws4fetch
 */
getAwsClient(): AwsClient;

AwsClient type from aws4fetch:

// Reference to external type from aws4fetch package
interface AwsClient {
  // Internal AWS signing client - see aws4fetch documentation
  // Used for AWS Signature V4 request signing
}

Usage Examples:

import { R2Client } from '@cfkit/r2';

const r2 = new R2Client({
  accountId: process.env.CLOUDFLARE_ACCOUNT_ID!,
  accessKeyId: process.env.R2_ACCESS_KEY_ID!,
  secretAccessKey: process.env.R2_SECRET_ACCESS_KEY!
});

// Get base URL (for debugging or custom requests)
const baseUrl = r2.getBaseUrl();
console.log('R2 Base URL:', baseUrl);
// => https://your-account-id.r2.cloudflarestorage.com

// Get AWS client (for advanced custom signing)
const awsClient = r2.getAwsClient();
// Use awsClient for custom S3-compatible API requests

When to use these methods:

  • Custom S3-compatible API operations not provided by the library
  • Debugging and logging
  • Testing and mocking
  • Advanced integrations

R2Bucket Constructor

R2Bucket instances should always be created using R2Client.bucket(). The constructor is documented here for completeness but should not be called directly.

/**
 * R2Bucket constructor (internal - use R2Client.bucket() instead)
 * @param name - Bucket name
 * @param awsClient - AWS client instance for signing
 * @param baseUrl - Base URL for R2 operations
 */
constructor(name: string, awsClient: AwsClient, baseUrl: string);

Correct usage:

// ✓ Correct - use R2Client.bucket()
const bucket = r2.bucket('gallery');

// ✗ Incorrect - don't instantiate directly
// const bucket = new R2Bucket('gallery', awsClient, baseUrl);

Error Handling

Bucket operations may throw errors when:

  • Bucket does not exist (for getInfo)
  • Invalid credentials
  • Network failures
  • Insufficient permissions

Always wrap operations in try-catch blocks:

try {
  const buckets = await r2.listBuckets();
  console.log('Buckets:', buckets);
} catch (error) {
  if (error instanceof Error) {
    console.error('Failed to list buckets:', error.message);
  }
}

Error Handling Patterns:

// Safe existence check (never throws)
const exists = await bucket.exists();
if (!exists) {
  console.log('Bucket does not exist');
  return;
}

// getInfo() throws if bucket doesn't exist
try {
  const info = await bucket.getInfo();
  console.log('Bucket info:', info);
} catch (error) {
  if (error instanceof Error) {
    console.error('Bucket not found or error:', error.message);
  }
}

// objectExists() never throws (returns false)
const objectExists = await bucket.objectExists('photo.jpg');
if (!objectExists) {
  console.log('Object does not exist');
}

Install with Tessl CLI

npx tessl i tessl/npm-cfkit--r2

docs

index.md

tile.json