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 🙏

© 2025 – Pkg Stats / Ryan Hefner

@synet/fs-linode

v1.0.0

Published

**Linode Object Storage FileSystem Adapter** - High-performance cloud storage for applications requiring scalable, S3-compatible object storage with global CDN integration.

Downloads

4

Readme

@synet/fs-linode

Linode Object Storage FileSystem Adapter - High-performance cloud storage for applications requiring scalable, S3-compatible object storage with global CDN integration.

Built on Linode's robust infrastructure with 11 global regions, automatic backups, and enterprise-grade security. Perfect for applications requiring reliable cloud storage with competitive pricing.


Linode features:

  • S3-Compatible API: Full compatibility with AWS S3 SDKs and tools
  • Global Regions: 11+ worldwide regions for optimal performance
  • Cost-Effective: Competitive pricing with predictable costs
  • High Performance: Built-in CDN and edge locations

Installation

# npm
npm install @synet/fs-linode

# yarn
yarn add @synet/fs-linode

# pnpm
pnpm add @synet/fs-linode

Quick Start

Basic Configuration

import { LinodeObjectStorageFileSystem } from '@synet/fs-linode';

const linodeFs = new LinodeObjectStorageFileSystem({
  region: 'us-east-1',           // Newark, NJ (or your preferred region)
  bucket: 'my-app-storage',
  accessKey: process.env.LINODE_ACCESS_KEY,
  secretKey: process.env.LINODE_SECRET_KEY,
  prefix: 'app-data/',           // Optional: acts as root directory
});

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

// Directory operations
await linodeFs.ensureDir('/uploads/images');
await linodeFs.writeFile('/uploads/images/logo.svg', svgContent);

// List files
const files = await linodeFs.readDir('/uploads');
console.log('Uploaded files:', files);

Environment Variables

# .env file
LINODE_ACCESS_KEY=your_access_key_here
LINODE_SECRET_KEY=your_secret_key_here
LINODE_REGION=us-east-1
LINODE_BUCKET=my-app-storage
import { LinodeObjectStorageFileSystem } from '@synet/fs-linode';

const linodeFs = new LinodeObjectStorageFileSystem({
  region: process.env.LINODE_REGION!,
  bucket: process.env.LINODE_BUCKET!,
  accessKey: process.env.LINODE_ACCESS_KEY!,
  secretKey: process.env.LINODE_SECRET_KEY!,
  prefix: 'production/'
});

Advanced Configuration

Multi-Region Setup

import { LinodeObjectStorageFileSystem } from '@synet/fs-linode';

// Primary region (US East)
const primaryFs = new LinodeObjectStorageFileSystem({
  region: 'us-east-1',
  bucket: 'app-primary',
  accessKey: process.env.LINODE_ACCESS_KEY!,
  secretKey: process.env.LINODE_SECRET_KEY!,
  prefix: 'primary/'
});

// EU region for GDPR compliance
const euFs = new LinodeObjectStorageFileSystem({
  region: 'eu-west-1',
  bucket: 'app-eu',
  accessKey: process.env.LINODE_ACCESS_KEY!,
  secretKey: process.env.LINODE_SECRET_KEY!,
  prefix: 'eu-data/'
});

// Route based on user location
const getFileSystem = (userRegion: string) => {
  return userRegion.startsWith('eu') ? euFs : primaryFs;
};

Custom Endpoint Configuration

const customFs = new LinodeObjectStorageFileSystem({
  region: 'us-east-1',
  bucket: 'custom-setup',
  accessKey: process.env.LINODE_ACCESS_KEY!,
  secretKey: process.env.LINODE_SECRET_KEY!,
  endpoint: 'https://us-east-1.linodeobjects.com', // Custom endpoint
  prefix: 'custom/'
});

Application Integration

import { LinodeObjectStorageFileSystem } from '@synet/fs-linode';

class DocumentService {
  private storage = new LinodeObjectStorageFileSystem({
    region: 'us-east-1',
    bucket: 'documents-bucket',
    accessKey: process.env.LINODE_ACCESS_KEY!,
    secretKey: process.env.LINODE_SECRET_KEY!,
    prefix: 'documents/'
  });

  async saveDocument(userId: string, filename: string, content: string): Promise<void> {
    const path = `/${userId}/${filename}`;
    await this.storage.writeFile(path, content);
  }

  async getDocument(userId: string, filename: string): Promise<string> {
    const path = `/${userId}/${filename}`;
    return await this.storage.readFile(path);
  }

  async listUserDocuments(userId: string): Promise<string[]> {
    return await this.storage.readDir(`/${userId}`);
  }

  async deleteDocument(userId: string, filename: string): Promise<void> {
    const path = `/${userId}/${filename}`;
    await this.storage.deleteFile(path);
  }
}

// Usage
const docService = new DocumentService();
await docService.saveDocument('user-123', 'report.pdf', pdfContent);
const documents = await docService.listUserDocuments('user-123');

Backup and Sync Operations

class BackupService {
  private primaryStorage = new LinodeObjectStorageFileSystem({
    region: 'us-east-1',
    bucket: 'primary-backup',
    accessKey: process.env.LINODE_ACCESS_KEY!,
    secretKey: process.env.LINODE_SECRET_KEY!,
    prefix: 'primary/'
  });

  private backupStorage = new LinodeObjectStorageFileSystem({
    region: 'us-west-1',
    bucket: 'secondary-backup',
    accessKey: process.env.LINODE_ACCESS_KEY!,
    secretKey: process.env.LINODE_SECRET_KEY!,
    prefix: 'backup/'
  });

  async syncToBackup(sourcePath: string): Promise<void> {
    if (await this.primaryStorage.exists(sourcePath)) {
      const content = await this.primaryStorage.readFile(sourcePath);
      await this.backupStorage.writeFile(sourcePath, content);
    }
  }

  async syncDirectory(dirPath: string): Promise<void> {
    const files = await this.primaryStorage.readDir(dirPath);
    
    const syncPromises = files.map(async (file) => {
      const fullPath = `${dirPath}/${file}`;
      if (await this.primaryStorage.exists(fullPath)) {
        await this.syncToBackup(fullPath);
      }
    });

    await Promise.all(syncPromises);
  }
}

Available Regions

Linode Object Storage is available in these regions:

| Region Code | Location | Description | |-------------|----------|-------------| | us-east-1 | Newark, NJ | US East Coast | | us-central-1 | Dallas, TX | US Central | | us-west-1 | Fremont, CA | US West Coast | | us-southeast-1 | Atlanta, GA | US Southeast | | ca-central-1 | Toronto, ON | Canada Central | | eu-west-1 | London, UK | Europe West | | eu-central-1 | Frankfurt, DE | Europe Central | | ap-south-1 | Singapore | Asia Pacific | | ap-northeast-1 | Tokyo, JP | Asia Pacific | | ap-west-1 | Mumbai, IN | Asia Pacific | | ap-southeast-1 | Sydney, AU | Asia Pacific |

Region Selection Best Practices

// Choose region closest to your users
const getOptimalRegion = (userLocation: string) => {
  const regionMap = {
    'north-america': 'us-east-1',
    'europe': 'eu-west-1',
    'asia': 'ap-south-1',
    'australia': 'ap-southeast-1',
  };
  
  return regionMap[userLocation] || 'us-east-1';
};

const createRegionalStorage = (region: string) => {
  return new LinodeObjectStorageFileSystem({
    region,
    bucket: 'global-app-storage',
    accessKey: process.env.LINODE_ACCESS_KEY!,
    secretKey: process.env.LINODE_SECRET_KEY!,
    prefix: `${region}/`
  });
};

Authentication & Security

Access Key Management

  1. Create Access Keys in Linode Cloud Manager:

    • Navigate to Object Storage → Access Keys
    • Generate new access key pair
    • Store securely in environment variables
  2. Scope Access Keys to specific buckets:

    • Create bucket-specific keys for enhanced security
    • Use separate keys for different environments
  3. Rotate Keys Regularly:

    • Implement key rotation strategy
    • Monitor access patterns and usage

API Reference

Constructor Options

interface LinodeObjectStorageOptions {
  region: string;          // Linode Object Storage region
  bucket: string;          // Bucket name
  accessKey: string;       // Access key ID
  secretKey: string;       // Secret access key
  prefix?: string;         // Base prefix (acts as root directory)
  endpoint?: string;       // Custom endpoint URL
}

File Operations

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

Directory Operations

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

Metadata Operations

  • stat(path: string): Promise<FileStats> - Get object metadata and stats

FileStats Interface

interface FileStats {
  isFile(): boolean;        // Always true for objects
  isDirectory(): boolean;   // Always false for objects
  isSymbolicLink(): boolean; // Always false
  size: number;            // Object size in bytes
  mtime: Date;             // Last modified time
  ctime: Date;             // Creation time
  atime: Date;             // Access time (same as mtime)
  mode: number;            // File permissions (644)
}

Testing

# Run all tests
npm test

# Run tests in watch mode
npm run dev:test

# Run tests with coverage
npm run coverage

# Run demo
npm run demo

Test Configuration

Create test configuration file:

// test-config.ts
export const testConfig = {
  region: 'us-east-1',
  bucket: 'test-bucket',
  accessKey: process.env.LINODE_TEST_ACCESS_KEY!,
  secretKey: process.env.LINODE_TEST_SECRET_KEY!,
  prefix: 'test-data/'
};

Integration Tests

import { LinodeObjectStorageFileSystem } from '@synet/fs-linode';
import { testConfig } from './test-config';

describe('Linode Object Storage Integration', () => {
  let storage: LinodeObjectStorageFileSystem;

  beforeEach(() => {
    storage = new LinodeObjectStorageFileSystem(testConfig);
  });

  afterEach(async () => {
    // Clean up test data
    try {
      await storage.deleteDir('/test');
    } catch {
      // Ignore cleanup errors
    }
  });

  it('should upload and download files', async () => {
    const path = '/test/integration.txt';
    const content = 'Integration test content';

    await storage.writeFile(path, content);
    const retrieved = await storage.readFile(path);

    expect(retrieved).toBe(content);
  });
});

Use Cases

1. Static Website Hosting

const websiteStorage = new LinodeObjectStorageFileSystem({
  region: 'us-east-1',
  bucket: 'my-website',
  accessKey: process.env.LINODE_ACCESS_KEY!,
  secretKey: process.env.LINODE_SECRET_KEY!,
  prefix: 'public/'
});

// Deploy static assets
await websiteStorage.writeFile('/index.html', htmlContent);
await websiteStorage.writeFile('/styles.css', cssContent);
await websiteStorage.writeFile('/app.js', jsContent);

2. Media Storage and CDN

const mediaStorage = new LinodeObjectStorageFileSystem({
  region: 'us-east-1',
  bucket: 'media-assets',
  accessKey: process.env.LINODE_ACCESS_KEY!,
  secretKey: process.env.LINODE_SECRET_KEY!,
  prefix: 'media/'
});

// Upload user content
await mediaStorage.writeFile('/images/user-123/avatar.jpg', imageBuffer);
await mediaStorage.writeFile('/videos/user-123/intro.mp4', videoBuffer);

// CDN URLs are automatically available
const avatarUrl = `https://media-assets.us-east-1.linodeobjects.com/media/images/user-123/avatar.jpg`;

3. Application Data Backup

const backupStorage = new LinodeObjectStorageFileSystem({
  region: 'us-west-1', // Different region for redundancy
  bucket: 'app-backups',
  accessKey: process.env.LINODE_ACCESS_KEY!,
  secretKey: process.env.LINODE_SECRET_KEY!,
  prefix: 'daily-backups/'
});

// Automated backup process
const backupData = async () => {
  const timestamp = new Date().toISOString().split('T')[0];
  const databaseDump = await exportDatabase();
  
  await backupStorage.writeFile(`/db-${timestamp}.sql`, databaseDump);
};

4. Log Aggregation

const logStorage = new LinodeObjectStorageFileSystem({
  region: 'us-east-1',
  bucket: 'application-logs',
  accessKey: process.env.LINODE_ACCESS_KEY!,
  secretKey: process.env.LINODE_SECRET_KEY!,
  prefix: 'logs/'
});

// Store application logs
const storeLog = async (service: string, level: string, message: string) => {
  const date = new Date().toISOString().split('T')[0];
  const logPath = `/${service}/${date}/${level}.log`;
  
  const logEntry = `${new Date().toISOString()} - ${message}\n`;
  
  // Append to existing log file
  let existingContent = '';
  if (await logStorage.exists(logPath)) {
    existingContent = await logStorage.readFile(logPath);
  }
  
  await logStorage.writeFile(logPath, existingContent + logEntry);
};

5. Multi-Tenant SaaS Storage

class TenantStorageService {
  private createTenantStorage(tenantId: string) {
    return new LinodeObjectStorageFileSystem({
      region: 'us-east-1',
      bucket: 'saas-tenant-data',
      accessKey: process.env.LINODE_ACCESS_KEY!,
      secretKey: process.env.LINODE_SECRET_KEY!,
      prefix: `tenant-${tenantId}/`
    });
  }

  async uploadFile(tenantId: string, fileName: string, content: string): Promise<void> {
    const storage = this.createTenantStorage(tenantId);
    await storage.writeFile(`/uploads/${fileName}`, content);
  }

  async getFile(tenantId: string, fileName: string): Promise<string> {
    const storage = this.createTenantStorage(tenantId);
    return await storage.readFile(`/uploads/${fileName}`);
  }
}

Performance Optimization

Concurrent Operations

// ✅ Good: Parallel uploads
const uploadFiles = async (files: Array<{ path: string; content: string }>) => {
  const uploadPromises = files.map(file => 
    storage.writeFile(file.path, file.content)
  );
  
  await Promise.all(uploadPromises);
};

// ✅ Good: Batch directory operations
const syncDirectories = async (directories: string[]) => {
  const syncPromises = directories.map(dir => 
    storage.readDir(dir).then(files => 
      console.log(`${dir}: ${files.length} files`)
    )
  );
  
  await Promise.all(syncPromises);
};

Caching Strategy

class CachedLinodeStorage {
  private storage: LinodeObjectStorageFileSystem;
  private cache = new Map<string, { content: string; timestamp: number }>();
  private cacheTTL = 5 * 60 * 1000; // 5 minutes

  constructor(options: LinodeObjectStorageOptions) {
    this.storage = new LinodeObjectStorageFileSystem(options);
  }

  async readFile(path: string): Promise<string> {
    const cached = this.cache.get(path);
    const now = Date.now();

    if (cached && (now - cached.timestamp) < this.cacheTTL) {
      return cached.content;
    }

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

  async writeFile(path: string, content: string): Promise<void> {
    await this.storage.writeFile(path, content);
    
    // Update cache
    this.cache.set(path, { content, timestamp: Date.now() });
  }
}

Connection Pooling

// Configure S3 client for optimal performance
const optimizedStorage = new LinodeObjectStorageFileSystem({
  region: 'us-east-1',
  bucket: 'high-performance',
  accessKey: process.env.LINODE_ACCESS_KEY!,
  secretKey: process.env.LINODE_SECRET_KEY!,
  // Additional S3 client options can be configured
});

Cost Optimization

Storage Classes and Lifecycle

// Organize data by access patterns
const createTieredStorage = () => {
  // Hot data (frequently accessed)
  const hotStorage = new LinodeObjectStorageFileSystem({
    region: 'us-east-1',
    bucket: 'hot-data',
    accessKey: process.env.LINODE_ACCESS_KEY!,
    secretKey: process.env.LINODE_SECRET_KEY!,
    prefix: 'hot/'
  });

  // Cold data (infrequently accessed)
  const coldStorage = new LinodeObjectStorageFileSystem({
    region: 'us-west-1', // Different region for cost optimization
    bucket: 'cold-data',
    accessKey: process.env.LINODE_ACCESS_KEY!,
    secretKey: process.env.LINODE_SECRET_KEY!,
    prefix: 'archived/'
  });

  return { hotStorage, coldStorage };
};

Transfer Optimization

// Minimize data transfer costs
const optimizeTransfers = {
  // Use compression for text content
  compressContent: (content: string): string => {
    // Implement compression logic
    return compressed;
  },

  // Batch operations to reduce API calls
  batchUploads: async (files: Array<{ path: string; content: string }>) => {
    const batches = chunk(files, 10); // Process in batches of 10
    
    for (const batch of batches) {
      await Promise.all(
        batch.map(file => storage.writeFile(file.path, file.content))
      );
    }
  }
};

Error Handling

Retry Logic

class ResilientLinodeStorage {
  private storage: LinodeObjectStorageFileSystem;
  private maxRetries = 3;
  private retryDelay = 1000;

  constructor(options: LinodeObjectStorageOptions) {
    this.storage = new LinodeObjectStorageFileSystem(options);
  }

  async writeFileWithRetry(path: string, content: string): Promise<void> {
    let lastError: Error;

    for (let attempt = 1; attempt <= this.maxRetries; attempt++) {
      try {
        await this.storage.writeFile(path, content);
        return; // Success
      } catch (error) {
        lastError = error as Error;
        
        if (attempt < this.maxRetries) {
          await this.delay(this.retryDelay * attempt);
        }
      }
    }

    throw new Error(`Failed to write file after ${this.maxRetries} attempts: ${lastError.message}`);
  }

  private delay(ms: number): Promise<void> {
    return new Promise(resolve => setTimeout(resolve, ms));
  }
}

Error Classification

const handleLinodeError = (error: any) => {
  if (error.name === 'NoSuchKey') {
    return { type: 'NOT_FOUND', retryable: false };
  }
  
  if (error.name === 'ServiceUnavailable') {
    return { type: 'SERVICE_UNAVAILABLE', retryable: true };
  }
  
  if (error.statusCode === 403) {
    return { type: 'ACCESS_DENIED', retryable: false };
  }
  
  if (error.statusCode >= 500) {
    return { type: 'SERVER_ERROR', retryable: true };
  }
  
  return { type: 'UNKNOWN', retryable: false };
};

Development

Building

npm run build

Linting

npm run lint
npm run lint:fix

Formatting

npm run format

Demo

npm run demo

License

MIT License - see LICENSE file for details.


Contributing

Contributions are welcome! Please read our Contributing Guide for details on our code of conduct and the process for submitting pull requests.

Development Setup

git clone https://github.com/synthetism/fs-linode.git
cd fs-linode
npm install
npm test

Related Packages


Built with ❤️ by the Synet Team