@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-digitaloceanQuick 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.comDigitalOcean Setup
Create a Space in DigitalOcean Control Panel
Generate API Keys:
- Go to API → Spaces Keys
- Generate new key pair
- Note the access key and secret key
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 filereadFile(path: string): Promise<string>- Download file contentdeleteFile(path: string): Promise<void>- Delete fileexists(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 prefixreadDir(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-checkTest 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 formatLicense
MIT License - see LICENSE file for details.
Related Packages
- @synet/fs - Core filesystem abstraction
- @synet/fs-azure - Azure Blob Storage adapter
- @synet/fs-gcs - Google Cloud Storage adapter
- @synet/fs-s3 - AWS S3 storage adapter
- @synet/fs-linode - Linode Object Storage adapter
Built with ❤️ by the SYNET Team
