Skip to main content

Stream uploads

For large files or dynamic content generation, use the streaming upload API with uploadWritable(). This gives you direct access to a WritableStream.
import { FTPClient } from "workerd-ftp";

const ftp = new FTPClient('ftp.example.com', {
  user: 'username',
  pass: 'password'
});

await ftp.connect();

// Get a writable stream
const writable = await ftp.uploadWritable('large-file.bin', 1024000);
const writer = writable.getWriter();

try {
  // Write data in chunks
  const chunk1 = new TextEncoder().encode('First chunk of data\n');
  await writer.write(chunk1);
  
  const chunk2 = new TextEncoder().encode('Second chunk of data\n');
  await writer.write(chunk2);
  
  // Close the writer when done
  await writer.close();
  
  // Finalize the transfer
  await ftp.finalizeStream();
  
  console.log('Streaming upload complete');
} catch (error) {
  await writer.abort();
  throw error;
}
The second parameter to uploadWritable() is optional but some FTP servers require you to allocate space before uploading.
You must call finalizeStream() after finishing with the stream to release the internal lock and close the data connection.

Stream downloads

Download large files efficiently using the streaming API with downloadReadable(). This returns a ReadableStream that you can process incrementally.
import { FTPClient } from "workerd-ftp";

const ftp = new FTPClient('ftp.example.com', {
  user: 'username',
  pass: 'password'
});

await ftp.connect();

// Get a readable stream
const readable = await ftp.downloadReadable('large-file.bin');
const reader = readable.getReader();

try {
  while (true) {
    const { done, value } = await reader.read();
    
    if (done) break;
    
    // Process chunk
    console.log(`Received ${value.byteLength} bytes`);
    // Process value (Uint8Array) as needed
  }
  
  console.log('Download complete');
} finally {
  reader.releaseLock();
  await ftp.finalizeStream();
}

Upload multiple files

Upload multiple files in sequence. Each operation waits for the previous one to complete.
import { FTPClient } from "workerd-ftp";

interface FileToUpload {
  name: string;
  content: string;
}

async function uploadMultipleFiles(files: FileToUpload[]) {
  const ftp = new FTPClient('ftp.example.com', {
    user: 'username',
    pass: 'password'
  });

  try {
    await ftp.connect();
    console.log('Connected to FTP server');

    for (const file of files) {
      try {
        const data = new TextEncoder().encode(file.content);
        await ftp.upload(file.name, data);
        console.log(`✓ Uploaded: ${file.name}`);
      } catch (error) {
        console.error(`✗ Failed to upload ${file.name}:`, error);
        // Continue with next file
      }
    }

    console.log('All uploads completed');
  } finally {
    await ftp.close();
  }
}

// Usage
const files = [
  { name: 'file1.txt', content: 'Content of file 1' },
  { name: 'file2.txt', content: 'Content of file 2' },
  { name: 'file3.txt', content: 'Content of file 3' }
];

await uploadMultipleFiles(files);

Download multiple files

Download multiple files and process them as needed.
import { FTPClient } from "workerd-ftp";

interface DownloadedFile {
  name: string;
  content: string;
  size: number;
}

async function downloadMultipleFiles(fileNames: string[]): Promise<DownloadedFile[]> {
  const ftp = new FTPClient('ftp.example.com', {
    user: 'username',
    pass: 'password'
  });

  const results: DownloadedFile[] = [];

  try {
    await ftp.connect();

    for (const fileName of fileNames) {
      try {
        const data = await ftp.download(fileName);
        const content = new TextDecoder().decode(data);
        
        results.push({
          name: fileName,
          content: content,
          size: data.byteLength
        });
        
        console.log(`✓ Downloaded: ${fileName} (${data.byteLength} bytes)`);
      } catch (error) {
        console.error(`✗ Failed to download ${fileName}:`, error);
        // Continue with next file
      }
    }

    return results;
  } finally {
    await ftp.close();
  }
}

// Usage
const files = await downloadMultipleFiles(['file1.txt', 'file2.txt', 'file3.txt']);
console.log(`Downloaded ${files.length} files`);

Error handling

Implement comprehensive error handling for robust FTP operations.
import { FTPClient } from "workerd-ftp";

async function robustFileTransfer(fileName: string, content: string) {
  const ftp = new FTPClient('ftp.example.com', {
    user: 'username',
    pass: 'password'
  });

  try {
    // Connect with timeout handling
    await ftp.connect();
    
  } catch (error: any) {
    if (error.code) {
      console.error(`FTP error code ${error.code}: ${error.message}`);
    } else {
      console.error('Connection failed:', error);
    }
    throw error;
  }

  try {
    // Try to upload the file
    const data = new TextEncoder().encode(content);
    await ftp.upload(fileName, data);
    console.log('Upload successful');
    
    // Verify the upload
    const fileSize = await ftp.size(fileName);
    console.log(`File size on server: ${fileSize} bytes`);
    
    if (fileSize !== data.byteLength) {
      throw new Error('File size mismatch - upload may be corrupted');
    }
    
  } catch (error: any) {
    if (error.code === 550) {
      console.error('File not found or permission denied');
    } else if (error.code === 426) {
      console.error('Connection closed; transfer aborted');
    } else if (error.code === 552) {
      console.error('Storage allocation exceeded');
    } else {
      console.error('Upload error:', error);
    }
    throw error;
    
  } finally {
    // Always clean up
    await ftp.close();
  }
}
FTP errors include a code property with the standard FTP status code. Common codes include 550 (file unavailable), 426 (connection closed), and 552 (storage exceeded).

Track upload progress

Monitor upload progress using the streaming API.
import { FTPClient } from "workerd-ftp";

async function uploadWithProgress(fileName: string, data: Uint8Array) {
  const ftp = new FTPClient('ftp.example.com', {
    user: 'username',
    pass: 'password'
  });

  await ftp.connect();

  const writable = await ftp.uploadWritable(fileName, data.byteLength);
  const writer = writable.getWriter();

  try {
    const chunkSize = 64 * 1024; // 64 KB chunks
    let offset = 0;
    let bytesUploaded = 0;

    while (offset < data.byteLength) {
      const end = Math.min(offset + chunkSize, data.byteLength);
      const chunk = data.slice(offset, end);
      
      await writer.write(chunk);
      
      bytesUploaded += chunk.byteLength;
      const progress = (bytesUploaded / data.byteLength) * 100;
      
      console.log(`Progress: ${progress.toFixed(1)}% (${bytesUploaded}/${data.byteLength} bytes)`);
      
      offset = end;
    }

    await writer.close();
    await ftp.finalizeStream();
    
    console.log('Upload complete!');
  } catch (error) {
    await writer.abort();
    throw error;
  } finally {
    await ftp.close();
  }
}

// Usage
const largeContent = new TextEncoder().encode('Large file content...');
await uploadWithProgress('large-file.txt', largeContent);

Binary file handling

The library automatically uses binary mode for all transfers. Here’s how to handle different file types:
import { FTPClient } from "workerd-ftp";

const ftp = new FTPClient('ftp.example.com', {
  user: 'username',
  pass: 'password'
});

await ftp.connect();

// Upload text
const text = 'Hello, World!';
await ftp.upload('text.txt', new TextEncoder().encode(text));

// Download text
const data = await ftp.download('text.txt');
const downloadedText = new TextDecoder().decode(data);

Build docs developers (and LLMs) love