npm package: @synet/fs-linode
Description: **Linode Object Storage FileSystem Adapter** - High-performance cloud storage for applications requiring scalable, S3-compatible object storage with global CDN integration.
Author: Nodules Team
Latest version: 1.0.0

# @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

```bash
# npm
npm install @synet/fs-linode

# yarn
yarn add @synet/fs-linode

# pnpm
pnpm add @synet/fs-linode
```

---

## Quick Start

### Basic Configuration

```typescript
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

```bash
# .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
```

```typescript
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

```typescript
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

```typescript
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

```typescript
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

```typescript
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

```typescript
// 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

```typescript
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

```typescript
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

```bash
# 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:

```typescript
// 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

```typescript
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**
```typescript
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**
```typescript
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**
```typescript
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**
```typescript
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**
```typescript
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

```typescript
// ✅ 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

```typescript
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

```typescript
// 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

```typescript
// 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

```typescript
// 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

```typescript
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

```typescript
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

```bash
npm run build
```

### Linting

```bash
npm run lint
npm run lint:fix
```

### Formatting

```bash
npm run format
```

### Demo

```bash
npm run demo
```


---

## License

MIT License - see [LICENSE](LICENSE) file for details.

---

## Contributing

Contributions are welcome! Please read our [Contributing Guide](CONTRIBUTING.md) for details on our code of conduct and the process for submitting pull requests.

### Development Setup

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

---

## Related Packages

- **[@synet/fs](https://www.npmjs.com/package/@synet/fs)** - Core filesystem abstraction and Unit Architecture
- **[@synet/fs-azure](https://www.npmjs.com/package/@synet/fs-azure)** - Azure Blob Storage adapter
- **[@synet/fs-gcs](https://www.npmjs.com/package/@synet/fs-gcs)** - Google Cloud Storage adapter
- **[@synet/fs-s3](https://www.npmjs.com/package/@synet/fs-s3)** - AWS S3 storage adapter
- **[@synet/fs-memory](https://www.npmjs.com/package/@synet/fs-memory)** - In-memory storage adapter

---

**Built with ❤️ by the Synet Team**