Features

Downloads

rezo.download() saves a remote resource to a local file with real-time progress events. It returns a RezoDownloadResponse event emitter immediately, letting you track percentage, speed, ETA, and more while the download runs in the background.

Quick Start

import { Rezo } from 'rezo';

const rezo = new Rezo();

const download = rezo.download(
  'https://releases.example.com/app-v2.0.zip',
  './downloads/app-v2.0.zip'
);

download.on('progress', (p) => {
  console.log(`${p.percentage.toFixed(1)}% | ${(p.speed / 1024).toFixed(0)} KB/s | ETA: ${(p.estimatedTime / 1000).toFixed(0)}s`);
});

download.on('finish', (info) => {
  console.log(`Saved to ${info.fileName} (${info.fileSize} bytes)`);
});

download.on('error', (err) => {
  console.error('Download failed:', err.message);
});

Method Signature

rezo.download(url: string | URL, saveTo: string, options?: RezoHttpRequest): RezoDownloadResponse
  • url — the resource to download.
  • saveTo — local file path where the file will be saved.
  • options — optional request configuration (headers, auth, proxy, timeout, etc.).

The method returns immediately. The download runs asynchronously and emits events as it progresses.

Events Reference

initiated

Emitted when the request object has been created:

download.on('initiated', () => {
  console.log('Download request created');
});

start

Emitted when the request is sent to the server:

download.on('start', (info) => {
  console.log('Downloading from:', info.url);
  console.log('Method:', info.method);
});

headers

Emitted when response headers arrive (first byte):

download.on('headers', (info) => {
  console.log('Status:', info.status, info.statusText);
  console.log('Content-Type:', info.contentType);
  console.log('File size:', info.contentLength, 'bytes');
  console.log('TTFB:', info.timing.firstByte, 'ms');
});

status

Emitted with the HTTP status code:

download.on('status', (status, statusText) => {
  if (status !== 200) {
    console.warn(`Unexpected status: ${status} ${statusText}`);
  }
});

cookies

Emitted with cookies from the response:

download.on('cookies', (cookies) => {
  cookies.forEach(c => console.log(`${c.key}=${c.value}`));
});

redirect

Emitted when a redirect is followed:

download.on('redirect', (info) => {
  console.log(`Redirect: ${info.sourceUrl} -> ${info.destinationUrl}`);
});

progress

Emitted periodically as data is received:

download.on('progress', (progress) => {
  console.log(`Downloaded: ${progress.loaded} / ${progress.total} bytes`);
  console.log(`Percentage: ${progress.percentage.toFixed(1)}%`);
  console.log(`Speed: ${(progress.speed / 1024 / 1024).toFixed(2)} MB/s`);
  console.log(`Average speed: ${(progress.averageSpeed / 1024 / 1024).toFixed(2)} MB/s`);
  console.log(`ETA: ${(progress.estimatedTime / 1000).toFixed(1)} seconds`);
});

The progress object contains:

PropertyTypeDescription
loadednumberBytes received so far
totalnumberTotal bytes (from Content-Length)
percentagenumber0—100
speednumberCurrent speed in bytes/second
averageSpeednumberAverage speed in bytes/second
estimatedTimenumberEstimated time remaining in milliseconds
timestampnumberTimestamp of this progress event

finish / done

Emitted when the download completes. Both events carry the same payload:

download.on('finish', (info) => {
  console.log('File saved:', info.fileName);
  console.log('File size:', info.fileSize, 'bytes');
  console.log('Status:', info.status, info.statusText);
  console.log('Final URL:', info.finalUrl);
  console.log('Total time:', info.timing.total, 'ms');
  console.log('Download time:', info.timing.download, 'ms');
  console.log('Average speed:', (info.averageSpeed / 1024 / 1024).toFixed(2), 'MB/s');
  console.log('Cookies:', info.cookies.string);
  console.log('URLs traversed:', info.urls);
});

done is an alias for finish.

error

Emitted if the download fails:

download.on('error', (err) => {
  console.error('Download error:', err.code, err.message);
});

Progress Tracking Patterns

Console Progress Bar

const download = rezo.download('https://releases.example.com/large-file.tar.gz', './large-file.tar.gz');

download.on('headers', (info) => {
  const sizeMB = ((info.contentLength || 0) / 1024 / 1024).toFixed(1);
  console.log(`Starting download: ${sizeMB} MB`);
});

download.on('progress', (p) => {
  const bar = '#'.repeat(Math.floor(p.percentage / 2)).padEnd(50, '-');
  const speedMB = (p.speed / 1024 / 1024).toFixed(1);
  const etaSec = (p.estimatedTime / 1000).toFixed(0);
  process.stdout.write(`
[${bar}] ${p.percentage.toFixed(0)}% | ${speedMB} MB/s | ETA: ${etaSec}s`);
});

download.on('finish', (info) => {
  const totalMB = (info.fileSize / 1024 / 1024).toFixed(1);
  const totalSec = (info.timing.total / 1000).toFixed(1);
  console.log(`
Complete: ${totalMB} MB in ${totalSec}s`);
});

Collecting Timing Metadata

download.on('finish', (info) => {
  const report = {
    url: info.finalUrl,
    file: info.fileName,
    size: info.fileSize,
    dns: info.timing.dns,
    tcp: info.timing.tcp,
    tls: info.timing.tls,
    ttfb: info.timing.firstByte,
    download: info.timing.download,
    total: info.timing.total,
    avgSpeed: info.averageSpeed
  };
  saveMetrics(report);
});

Filename Tracking

The DownloadResponse exposes the file name and URL:

const download = rezo.download('https://cdn.example.com/assets/report.pdf', '/tmp/report.pdf');

console.log(download.fileName); // '/tmp/report.pdf'
console.log(download.url);      // 'https://cdn.example.com/assets/report.pdf'

After the download finishes, info.fileName in the finish event contains the final path.

With Request Options

Pass any standard request option as the third argument:

const download = rezo.download(
  'https://private-cdn.example.com/build-artifact.tar.gz',
  './artifact.tar.gz',
  {
    headers: {
      'Authorization': 'Bearer token123',
      'Accept-Encoding': 'gzip'
    },
    timeout: 120_000,
    maxRedirects: 5,
    proxy: 'http://proxy.example.com:8080'
  }
);

Checking State

download.isFinished(); // true after finish event

Error Handling

When a download fails mid-transfer, the adapter cleans up the partial file. Listen for the error event to handle failures:

download.on('error', (err) => {
  console.error(`Download of ${download.url} failed: ${err.message}`);
  // The partial file is cleaned up automatically
});

For retries, create a new download call:

async function downloadWithRetry(url, saveTo, maxRetries = 3) {
  for (let attempt = 1; attempt <= maxRetries; attempt++) {
    try {
      const dl = rezo.download(url, saveTo);
      await new Promise((resolve, reject) => {
        dl.on('finish', resolve);
        dl.on('error', reject);
      });
      return; // Success
    } catch (err) {
      if (attempt === maxRetries) throw err;
      console.log(`Retry ${attempt}/${maxRetries}...`);
    }
  }
}