or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

docs

access-control-security.mdbucket-configuration.mdbucket-operations.mdclient-configuration.mdindex.mdlisting-pagination.mdmultipart-upload.mdobject-operations.mdwaiters-utilities.md
tile.json

waiters-utilities.mddocs/

Waiters & Utilities

Built-in waiters for polling operations and utility functions for common S3 tasks, providing convenient abstractions for asynchronous operations.

Capabilities

Bucket Waiters

Wait for bucket state changes with automatic polling and timeout handling.

/**
 * Wait until a bucket exists and is accessible
 */
function waitUntilBucketExists(
  params: WaiterConfiguration<S3Client>,
  input: HeadBucketCommandInput
): Promise<WaiterResult>;

/**
 * Wait until a bucket is deleted or inaccessible
 */
function waitUntilBucketNotExists(
  params: WaiterConfiguration<S3Client>,
  input: HeadBucketCommandInput
): Promise<WaiterResult>;

interface WaiterConfiguration<Client> {
  /** S3 client instance */
  client: Client;
  
  /** Maximum wait time in seconds (default: 120) */
  maxWaitTime?: number;
  
  /** Minimum delay between checks in seconds (default: 2) */
  minDelay?: number;
  
  /** Maximum delay between checks in seconds (default: 120) */
  maxDelay?: number;
}

interface WaiterResult {
  /** Final state */
  state: WaiterState;
  
  /** Reason for the final state */
  reason?: any;
}

type WaiterState = "SUCCESS" | "FAILURE" | "RETRY" | "TIMEOUT" | "ABORTED";

Usage Examples:

import { 
  S3Client, 
  CreateBucketCommand,
  DeleteBucketCommand,
  waitUntilBucketExists,
  waitUntilBucketNotExists 
} from "@aws-sdk/client-s3";

const client = new S3Client({ region: "us-east-1" });

// Wait for bucket creation to complete
async function createAndWaitForBucket(bucketName: string) {
  // Create the bucket
  const createBucket = new CreateBucketCommand({
    Bucket: bucketName
  });
  await client.send(createBucket);
  
  // Wait for it to be accessible
  const waiterResult = await waitUntilBucketExists(
    {
      client,
      maxWaitTime: 60, // Wait up to 60 seconds
      minDelay: 2,     // Check every 2 seconds
      maxDelay: 10     // Max 10 seconds between checks
    },
    { Bucket: bucketName }
  );
  
  if (waiterResult.state === "SUCCESS") {
    console.log(`Bucket ${bucketName} is ready`);
  } else {
    throw new Error(`Bucket creation failed: ${waiterResult.reason}`);
  }
}

// Wait for bucket deletion to complete
async function deleteAndWaitForBucket(bucketName: string) {
  // Delete the bucket
  const deleteBucket = new DeleteBucketCommand({
    Bucket: bucketName
  });
  await client.send(deleteBucket);
  
  // Wait for it to be gone
  const waiterResult = await waitUntilBucketNotExists(
    { client, maxWaitTime: 30 },
    { Bucket: bucketName }
  );
  
  if (waiterResult.state === "SUCCESS") {
    console.log(`Bucket ${bucketName} has been deleted`);
  } else {
    throw new Error(`Bucket deletion incomplete: ${waiterResult.reason}`);
  }
}

// Usage
await createAndWaitForBucket("my-new-bucket");
await deleteAndWaitForBucket("my-old-bucket");

Object Waiters

Wait for object state changes with automatic polling and timeout handling.

/**
 * Wait until an object exists in S3
 */
function waitUntilObjectExists(
  params: WaiterConfiguration<S3Client>,
  input: HeadObjectCommandInput
): Promise<WaiterResult>;

/**
 * Wait until an object is deleted from S3
 */
function waitUntilObjectNotExists(
  params: WaiterConfiguration<S3Client>,
  input: HeadObjectCommandInput
): Promise<WaiterResult>;

Usage Examples:

import { 
  S3Client, 
  PutObjectCommand,
  DeleteObjectCommand,
  waitUntilObjectExists,
  waitUntilObjectNotExists 
} from "@aws-sdk/client-s3";

const client = new S3Client({ region: "us-east-1" });

// Wait for object upload to be complete and accessible
async function uploadAndWaitForObject(bucket: string, key: string, content: string) {
  // Upload the object
  const putObject = new PutObjectCommand({
    Bucket: bucket,
    Key: key,
    Body: content
  });
  await client.send(putObject);
  
  // Wait for it to be accessible
  const waiterResult = await waitUntilObjectExists(
    {
      client,
      maxWaitTime: 30,
      minDelay: 1,
      maxDelay: 5
    },
    { 
      Bucket: bucket, 
      Key: key 
    }
  );
  
  if (waiterResult.state === "SUCCESS") {
    console.log(`Object ${key} is ready`);
  } else {
    throw new Error(`Object upload failed: ${waiterResult.reason}`);
  }
}

// Wait for object deletion to complete
async function deleteAndWaitForObject(bucket: string, key: string) {
  // Delete the object
  const deleteObject = new DeleteObjectCommand({
    Bucket: bucket,
    Key: key
  });
  await client.send(deleteObject);
  
  // Wait for it to be gone
  const waiterResult = await waitUntilObjectNotExists(
    { client, maxWaitTime: 15 },
    { 
      Bucket: bucket, 
      Key: key 
    }
  );
  
  if (waiterResult.state === "SUCCESS") {
    console.log(`Object ${key} has been deleted`);
  } else {
    throw new Error(`Object deletion incomplete: ${waiterResult.reason}`);
  }
}

// Usage
await uploadAndWaitForObject("my-bucket", "test-file.txt", "Hello, World!");
await deleteAndWaitForObject("my-bucket", "old-file.txt");

Waiter Error Handling

Handle different waiter outcomes and implement custom retry logic.

// Advanced waiter configuration with custom behavior
interface AdvancedWaiterConfiguration<Client> extends WaiterConfiguration<Client> {
  /** Custom acceptor functions */
  acceptors?: WaiterAcceptor[];
  
  /** Abort signal for cancellation */
  abortSignal?: AbortSignal;
}

interface WaiterAcceptor {
  /** State to return when this acceptor matches */
  state: WaiterState;
  
  /** Matcher function */
  matcher: (result: any) => boolean;
  
  /** Expected value to match against */
  expected?: any;
}

Advanced Waiter Usage:

import { 
  S3Client, 
  waitUntilObjectExists,
  WaiterState 
} from "@aws-sdk/client-s3";

const client = new S3Client({ region: "us-east-1" });

// Wait with timeout and error handling
async function waitForObjectWithRetry(
  bucket: string, 
  key: string, 
  maxRetries: number = 3
): Promise<boolean> {
  let attempt = 0;
  
  while (attempt < maxRetries) {
    try {
      const result = await waitUntilObjectExists(
        {
          client,
          maxWaitTime: 30,
          minDelay: 2,
          maxDelay: 8
        },
        { Bucket: bucket, Key: key }
      );
      
      switch (result.state) {
        case "SUCCESS":
          return true;
          
        case "TIMEOUT":
          console.warn(`Attempt ${attempt + 1}: Timeout waiting for object`);
          break;
          
        case "FAILURE":
          console.error(`Attempt ${attempt + 1}: Failed - ${result.reason}`);
          break;
          
        default:
          console.warn(`Attempt ${attempt + 1}: Unexpected state - ${result.state}`);
      }
      
    } catch (error) {
      console.error(`Attempt ${attempt + 1}: Error - ${error.message}`);
    }
    
    attempt++;
    
    if (attempt < maxRetries) {
      // Exponential backoff
      const delay = Math.min(1000 * Math.pow(2, attempt), 10000);
      await new Promise(resolve => setTimeout(resolve, delay));
    }
  }
  
  return false;
}

// Wait with cancellation support
async function waitForObjectWithCancellation(
  bucket: string, 
  key: string,
  timeoutMs: number = 30000
): Promise<boolean> {
  const abortController = new AbortController();
  
  // Set up timeout
  const timeoutId = setTimeout(() => {
    abortController.abort();
  }, timeoutMs);
  
  try {
    const result = await waitForObjectExists(
      {
        client,
        maxWaitTime: Math.ceil(timeoutMs / 1000),
        abortSignal: abortController.signal
      },
      { Bucket: bucket, Key: key }
    );
    
    clearTimeout(timeoutId);
    return result.state === "SUCCESS";
    
  } catch (error) {
    clearTimeout(timeoutId);
    
    if (error.name === "AbortError") {
      console.log("Wait operation was cancelled");
      return false;
    }
    
    throw error;
  }
}

// Usage examples
const exists = await waitForObjectWithRetry("my-bucket", "important-file.txt", 5);
console.log(`Object exists after retries: ${exists}`);

const existsWithTimeout = await waitForObjectWithCancellation(
  "my-bucket", 
  "slow-upload.dat", 
  15000
);
console.log(`Object exists within timeout: ${existsWithTimeout}`);

Utility Functions

Common utility patterns for S3 operations.

// Utility types for common patterns
interface S3ObjectReference {
  Bucket: string;
  Key: string;
  VersionId?: string;
}

interface S3OperationOptions {
  /** Maximum retry attempts */
  maxRetries?: number;
  
  /** Delay between retries (ms) */
  retryDelay?: number;
  
  /** Exponential backoff multiplier */
  backoffMultiplier?: number;
  
  /** Request timeout (ms) */
  timeout?: number;
}

Utility Function Examples:

import { 
  S3Client, 
  HeadObjectCommand,
  GetObjectCommand,
  PutObjectCommand,
  CopyObjectCommand,
  waitForObjectExists 
} from "@aws-sdk/client-s3";

const client = new S3Client({ region: "us-east-1" });

// Utility: Check if object exists
async function objectExists(bucket: string, key: string): Promise<boolean> {
  try {
    await client.send(new HeadObjectCommand({ Bucket: bucket, Key: key }));
    return true;
  } catch (error) {
    if (error.name === "NotFound") {
      return false;
    }
    throw error;
  }
}

// Utility: Safe object upload with existence check
async function safeUploadObject(
  bucket: string, 
  key: string, 
  content: string | Buffer,
  options: { overwrite?: boolean } = {}
): Promise<string> {
  // Check if object already exists
  const exists = await objectExists(bucket, key);
  
  if (exists && !options.overwrite) {
    throw new Error(`Object ${key} already exists. Set overwrite=true to replace.`);
  }
  
  // Upload the object
  const putCommand = new PutObjectCommand({
    Bucket: bucket,
    Key: key,
    Body: content,
    ContentType: typeof content === "string" ? "text/plain" : "application/octet-stream"
  });
  
  const response = await client.send(putCommand);
  
  // Wait for it to be accessible
  await waitForObjectExists(
    { client, maxWaitTime: 30 },
    { Bucket: bucket, Key: key }
  );
  
  return response.ETag || "";
}

// Utility: Download object as string
async function downloadObjectAsString(
  bucket: string, 
  key: string
): Promise<string> {
  const getCommand = new GetObjectCommand({
    Bucket: bucket,
    Key: key
  });
  
  const response = await client.send(getCommand);
  
  if (!response.Body) {
    throw new Error("Object has no content");
  }
  
  return await response.Body.transformToString();
}

// Utility: Copy object with wait
async function copyObjectWithWait(
  sourceBucket: string,
  sourceKey: string,
  destBucket: string,
  destKey: string
): Promise<void> {
  const copyCommand = new CopyObjectCommand({
    Bucket: destBucket,
    Key: destKey,
    CopySource: `${sourceBucket}/${sourceKey}`
  });
  
  await client.send(copyCommand);
  
  // Wait for the copy to be accessible
  await waitForObjectExists(
    { client, maxWaitTime: 60 },
    { Bucket: destBucket, Key: destKey }
  );
}

// Utility: Batch operation with concurrency control
async function batchOperation<T, R>(
  items: T[],
  operation: (item: T) => Promise<R>,
  concurrency: number = 5
): Promise<R[]> {
  const results: R[] = [];
  const executing: Promise<void>[] = [];
  
  for (const item of items) {
    const promise = operation(item).then(result => {
      results.push(result);
    });
    
    executing.push(promise);
    
    if (executing.length >= concurrency) {
      await Promise.race(executing);
      executing.splice(executing.findIndex(p => p === promise), 1);
    }
  }
  
  await Promise.all(executing);
  return results;
}

// Usage examples
const exists = await objectExists("my-bucket", "test-file.txt");
console.log(`Object exists: ${exists}`);

const etag = await safeUploadObject(
  "my-bucket", 
  "new-file.txt", 
  "Hello, World!", 
  { overwrite: false }
);
console.log(`Uploaded with ETag: ${etag}`);

const content = await downloadObjectAsString("my-bucket", "text-file.txt");
console.log(`Downloaded content: ${content}`);

await copyObjectWithWait(
  "source-bucket", "original.txt",
  "dest-bucket", "copy.txt"
);
console.log("Copy completed and verified");

// Batch upload multiple files
const uploadTasks = [
  { key: "file1.txt", content: "Content 1" },
  { key: "file2.txt", content: "Content 2" },
  { key: "file3.txt", content: "Content 3" }
];

const uploadResults = await batchOperation(
  uploadTasks,
  async (task) => {
    return await safeUploadObject("my-bucket", task.key, task.content);
  },
  3 // Max 3 concurrent uploads
);

console.log(`Completed ${uploadResults.length} uploads`);

Polling Utilities

Custom polling utilities for complex scenarios.

// Generic polling utility
async function pollUntil<T>(
  operation: () => Promise<T>,
  condition: (result: T) => boolean,
  options: {
    maxAttempts?: number;
    interval?: number;
    backoff?: boolean;
    timeout?: number;
  } = {}
): Promise<T> {
  const {
    maxAttempts = 30,
    interval = 1000,
    backoff = false,
    timeout = 0
  } = options;
  
  const startTime = Date.now();
  let attempt = 0;
  let currentInterval = interval;
  
  while (attempt < maxAttempts) {
    if (timeout > 0 && Date.now() - startTime > timeout) {
      throw new Error("Polling timeout exceeded");
    }
    
    try {
      const result = await operation();
      
      if (condition(result)) {
        return result;
      }
      
    } catch (error) {
      console.warn(`Polling attempt ${attempt + 1} failed:`, error.message);
    }
    
    attempt++;
    
    if (attempt < maxAttempts) {
      await new Promise(resolve => setTimeout(resolve, currentInterval));
      
      if (backoff) {
        currentInterval = Math.min(currentInterval * 1.5, 10000);
      }
    }
  }
  
  throw new Error(`Polling failed after ${maxAttempts} attempts`);
}

// Usage: Wait for object to have specific size
async function waitForObjectSize(
  bucket: string, 
  key: string, 
  expectedSize: number
): Promise<void> {
  await pollUntil(
    async () => {
      const headResponse = await client.send(new HeadObjectCommand({
        Bucket: bucket,
        Key: key
      }));
      return headResponse.ContentLength || 0;
    },
    (size) => size === expectedSize,
    {
      maxAttempts: 20,
      interval: 2000,
      backoff: true,
      timeout: 60000
    }
  );
}

// Usage: Wait for multipart upload completion
async function waitForMultipartUploadCompletion(
  bucket: string,
  key: string,
  uploadId: string
): Promise<boolean> {
  try {
    await pollUntil(
      async () => {
        return await objectExists(bucket, key);
      },
      (exists) => exists,
      {
        maxAttempts: 30,
        interval: 3000,
        timeout: 300000 // 5 minutes
      }
    );
    return true;
  } catch (error) {
    console.error("Multipart upload did not complete:", error.message);
    return false;
  }
}