npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@synet/fs-digitalocean

v1.0.0

Published

DigitalOcean Spaces filesystem abstraction following FileSystem pattern

Readme

@synet/fs-digitalocean

DigitalOcean Spaces FileSystem Adapter - High-performance object storage with S3-compatible API, built-in CDN, and developer-friendly pricing.

Perfect for applications requiring reliable cloud storage with predictable costs and global edge locations.


Features

  • S3-Compatible API: Full compatibility with AWS S3 SDKs and tools
  • Built-in CDN: Global content delivery with 300+ edge locations
  • Developer Pricing: Simple, predictable pricing starting at $5/month
  • High Performance: SSD-backed storage with low latency
  • Global Regions: 8+ regions worldwide for optimal performance
  • TypeScript First: Complete type safety with full interface definitions

Installation

npm install @synet/fs-digitalocean

Quick Start

import { DigitalOceanSpacesFileSystem } from '@synet/fs-digitalocean';

const doFs = new DigitalOceanSpacesFileSystem({
  endpoint: 'https://nyc3.digitaloceanspaces.com',
  accessKeyId: process.env.DO_ACCESS_KEY!,
  secretAccessKey: process.env.DO_SECRET_KEY!,
  bucket: 'my-app-storage',
  region: 'nyc3',
  prefix: 'app-data/' // Optional: acts as root directory
});

// File operations
await doFs.writeFile('/config.json', JSON.stringify({ app: 'myapp' }));
const config = await doFs.readFile('/config.json');

// Directory operations
await doFs.ensureDir('/uploads');
const files = await doFs.readDir('/uploads');

Configuration

Environment Variables

# .env
DO_ACCESS_KEY=your_access_key
DO_SECRET_KEY=your_secret_key
DO_REGION=nyc3
DO_BUCKET=my-storage
DO_ENDPOINT=https://nyc3.digitaloceanspaces.com

DigitalOcean Setup

  1. Create a Space in DigitalOcean Control Panel

  2. Generate API Keys:

    • Go to API → Spaces Keys
    • Generate new key pair
    • Note the access key and secret key
  3. Configure CDN (Optional):

    • Enable CDN for global distribution
    • Set custom domain if needed

Regional Endpoints

| Region | Endpoint | Location | |--------|----------|----------| | nyc3 | https://nyc3.digitaloceanspaces.com | New York | | ams3 | https://ams3.digitaloceanspaces.com | Amsterdam | | sgp1 | https://sgp1.digitaloceanspaces.com | Singapore | | sfo3 | https://sfo3.digitaloceanspaces.com | San Francisco | | fra1 | https://fra1.digitaloceanspaces.com | Frankfurt |


Advanced Usage

Multi-Environment Setup

const createSpacesFS = (environment: string) => {
  return new DigitalOceanSpacesFileSystem({
    endpoint: `https://${process.env.DO_REGION}.digitaloceanspaces.com`,
    accessKeyId: process.env.DO_ACCESS_KEY!,
    secretAccessKey: process.env.DO_SECRET_KEY!,
    bucket: process.env.DO_BUCKET!,
    region: process.env.DO_REGION!,
    prefix: `${environment}/`
  });
};

const prodFs = createSpacesFS('production');
const stagingFs = createSpacesFS('staging');

CDN Integration

const spacesFs = new DigitalOceanSpacesFileSystem({
  endpoint: 'https://nyc3.digitaloceanspaces.com',
  accessKeyId: process.env.DO_ACCESS_KEY!,
  secretAccessKey: process.env.DO_SECRET_KEY!,
  bucket: 'my-cdn-space',
  region: 'nyc3'
});

// Upload assets
await spacesFs.writeFile('/assets/logo.png', imageBuffer);

// CDN URL (if CDN is enabled)
const cdnUrl = `https://my-cdn-space.nyc3.cdn.digitaloceanspaces.com/assets/logo.png`;

Backup and Sync

class SpacesBackup {
  private spaces: DigitalOceanSpacesFileSystem;

  constructor() {
    this.spaces = new DigitalOceanSpacesFileSystem({
      endpoint: 'https://nyc3.digitaloceanspaces.com',
      accessKeyId: process.env.DO_ACCESS_KEY!,
      secretAccessKey: process.env.DO_SECRET_KEY!,
      bucket: 'backups',
      region: 'nyc3',
      prefix: 'daily/'
    });
  }

  async backupData(data: any): Promise<void> {
    const timestamp = new Date().toISOString().split('T')[0];
    await this.spaces.writeFile(`/backup-${timestamp}.json`, JSON.stringify(data));
  }
}

API Reference

Constructor Options

interface DigitalOceanSpacesOptions {
  endpoint: string;          // DigitalOcean Spaces endpoint
  accessKeyId: string;       // DO Spaces access key
  secretAccessKey: string;   // DO Spaces secret key
  bucket: string;            // Space name
  region: string;            // DO region
  prefix?: string;           // Optional prefix for all operations
}

File Operations

  • writeFile(path: string, data: string): Promise<void> - Upload file
  • readFile(path: string): Promise<string> - Download file content
  • deleteFile(path: string): Promise<void> - Delete file
  • exists(path: string): Promise<boolean> - Check if file exists

Directory Operations

  • ensureDir(path: string): Promise<void> - Ensure directory exists (no-op)
  • deleteDir(path: string): Promise<void> - Delete all files with prefix
  • readDir(path: string): Promise<string[]> - List files with prefix

Metadata Operations

  • stat(path: string): Promise<FileStats> - Get file metadata

Testing

# Run tests
npm test

# Run with coverage
npm run coverage

# Type checking
npm run type-check

Test Configuration

import { DigitalOceanSpacesFileSystem } from '@synet/fs-digitalocean';

const testSpaces = new DigitalOceanSpacesFileSystem({
  endpoint: 'https://nyc3.digitaloceanspaces.com',
  accessKeyId: process.env.DO_TEST_ACCESS_KEY!,
  secretAccessKey: process.env.DO_TEST_SECRET_KEY!,
  bucket: 'test-space',
  region: 'nyc3',
  prefix: 'test-data/'
});

describe('DigitalOcean Spaces Integration', () => {
  it('should handle file operations', async () => {
    await testSpaces.writeFile('/test.json', '{"test": true}');
    const content = await testSpaces.readFile('/test.json');
    expect(JSON.parse(content)).toEqual({ test: true });
  });
});

Performance

Concurrent Operations

// Parallel uploads
const uploadPromises = files.map(file => 
  spacesFs.writeFile(file.path, file.content)
);
await Promise.all(uploadPromises);

// Batch operations
const contents = await Promise.all(
  paths.map(path => spacesFs.readFile(path))
);

Caching

class CachedSpacesFS {
  private spaces: DigitalOceanSpacesFileSystem;
  private cache = new Map<string, { content: string; timestamp: number }>();
  private ttl = 5 * 60 * 1000; // 5 minutes

  async readFile(path: string): Promise<string> {
    const cached = this.cache.get(path);
    if (cached && Date.now() - cached.timestamp < this.ttl) {
      return cached.content;
    }

    const content = await this.spaces.readFile(path);
    this.cache.set(path, { content, timestamp: Date.now() });
    return content;
  }
}

Error Handling

try {
  await spacesFs.writeFile('/config.json', JSON.stringify(config));
} catch (error: any) {
  if (error.name === 'NoSuchBucket') {
    console.error('Space does not exist');
  } else if (error.name === 'AccessDenied') {
    console.error('Invalid credentials or permissions');
  } else if (error.statusCode === 403) {
    console.error('Access denied - check API keys');
  } else {
    console.error('Upload failed:', error.message);
  }
}

Retry Logic

async function writeWithRetry(path: string, content: string, maxRetries = 3): Promise<void> {
  for (let attempt = 1; attempt <= maxRetries; attempt++) {
    try {
      await spacesFs.writeFile(path, content);
      return;
    } catch (error: any) {
      if (attempt === maxRetries || error.statusCode === 403) {
        throw error;
      }
      await new Promise(resolve => setTimeout(resolve, 1000 * attempt));
    }
  }
}

Cost Optimization

Storage Classes

  • Standard: For frequently accessed data
  • Archive: For long-term backup (coming soon)

Transfer Optimization

// Minimize API calls with batch operations
const batchSize = 10;
for (let i = 0; i < files.length; i += batchSize) {
  const batch = files.slice(i, i + batchSize);
  await Promise.all(batch.map(file => spacesFs.writeFile(file.path, file.content)));
}

Development

# Install dependencies
npm install

# Build package
npm run build

# Run linting
npm run lint

# Format code
npm run format

License

MIT License - see LICENSE file for details.


Related Packages


Built with ❤️ by the SYNET Team