CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/npm-google-cloud--storage

Cloud Storage Client Library for Node.js that provides comprehensive API for managing buckets, files, and metadata with authentication, streaming, and access control.

Pending
Quality

Pending

Does it follow best practices?

Impact

Pending

No eval scenarios have been run

SecuritybySnyk

Pending

The risk profile of this skill

Overview
Eval results
Files

utilities.mddocs/

Utilities

Google Cloud Storage library provides various utility classes and functions for checksums, signed URLs, stream validation, and other supporting functionality.

CRC32C Checksums

CRC32C Class

class CRC32C {
  constructor(initial?: number);
  
  // Methods
  update(data: Buffer | Uint8Array | string): CRC32C;
  digest(): Buffer;
  toString(encoding?: string): string;
  
  // Static methods
  static from(data: Buffer | Uint8Array | string): CRC32C;
  static calculate(data: Buffer | Uint8Array | string): Buffer;
}

// Basic usage
import { CRC32C } from '@google-cloud/storage';

// Calculate checksum for data
const data = Buffer.from('Hello, World!');
const checksum = CRC32C.from(data);
console.log('CRC32C:', checksum.toString('base64'));

// Incremental calculation
const crc = new CRC32C();
crc.update('Hello, ');
crc.update('World!');
const result = crc.digest();
console.log('CRC32C:', result.toString('base64'));

// One-shot calculation
const quickChecksum = CRC32C.calculate(data);
console.log('CRC32C:', quickChecksum.toString('base64'));

CRC32C Validator Generator

type CRC32CValidatorGenerator = () => CRC32C;

// Default validator generator
import { CRC32C_DEFAULT_VALIDATOR_GENERATOR } from '@google-cloud/storage';

const storage = new Storage({
  crc32cGenerator: CRC32C_DEFAULT_VALIDATOR_GENERATOR
});

// Custom validator generator
function customCRC32CGenerator(): CRC32C {
  return new CRC32C();
}

const storage = new Storage({
  crc32cGenerator: customCRC32CGenerator
});

File Integrity Validation

// Validate file integrity during upload
const file = bucket.file('important-data.zip');

// Calculate local file checksum
const fs = require('fs');
const localData = fs.readFileSync('/local/important-data.zip');
const expectedChecksum = CRC32C.from(localData).toString('base64');

// Upload with validation
await file.save(localData, {
  validation: 'crc32c',
  metadata: {
    crc32c: expectedChecksum
  }
});

// Download with validation
const [downloadedData] = await file.download({
  validation: 'crc32c'
});

// Verify checksums match
const actualChecksum = CRC32C.from(downloadedData).toString('base64');
if (actualChecksum === expectedChecksum) {
  console.log('File integrity verified');
} else {
  console.error('File integrity check failed');
}

Hash Stream Validator

HashStreamValidator Class

class HashStreamValidator extends Transform {
  constructor(options: HashStreamValidatorOptions);
  
  // Properties
  crc32c?: string;
  md5?: string;
  
  // Methods
  test(hash: 'crc32c' | 'md5', sum: string): boolean;
}

interface HashStreamValidatorOptions {
  crc32c?: boolean | string;
  md5?: boolean | string;
}

import { HashStreamValidator } from '@google-cloud/storage';

// Validate stream with expected checksums
const validator = new HashStreamValidator({
  crc32c: true,
  md5: true
});

// Stream file through validator
fs.createReadStream('/local/file.txt')
  .pipe(validator)
  .pipe(fs.createWriteStream('/local/validated-file.txt'))
  .on('finish', () => {
    console.log('CRC32C:', validator.crc32c);
    console.log('MD5:', validator.md5);
    
    // Test against expected values
    const crcValid = validator.test('crc32c', 'expected-crc32c-value');
    const md5Valid = validator.test('md5', 'expected-md5-value');
    
    console.log('CRC32C valid:', crcValid);
    console.log('MD5 valid:', md5Valid);
  });

Stream Validation Patterns

// Upload with stream validation
function uploadWithValidation(localPath: string, file: File) {
  return new Promise<void>((resolve, reject) => {
    const validator = new HashStreamValidator({
      crc32c: true,
      md5: true
    });
    
    const readStream = fs.createReadStream(localPath);
    const writeStream = file.createWriteStream();
    
    readStream
      .pipe(validator)
      .pipe(writeStream)
      .on('error', reject)
      .on('finish', async () => {
        try {
          // Set metadata with calculated checksums
          await file.setMetadata({
            crc32c: validator.crc32c,
            md5Hash: validator.md5
          });
          
          console.log('Upload completed with validation');
          resolve();
        } catch (error) {
          reject(error);
        }
      });
  });
}

// Download with validation
function downloadWithValidation(file: File, localPath: string) {
  return new Promise<void>((resolve, reject) => {
    file.getMetadata()
      .then(([metadata]) => {
        const validator = new HashStreamValidator({
          crc32c: metadata.crc32c,
          md5: metadata.md5Hash
        });
        
        file.createReadStream()
          .pipe(validator)
          .pipe(fs.createWriteStream(localPath))
          .on('error', reject)
          .on('finish', () => {
            console.log('Download completed with validation');
            resolve();
          });
      })
      .catch(reject);
  });
}

Signed URL Generation

URL Signer

class URLSigner {
  constructor(authClient: AuthClient, bucket: Bucket, file?: File);
  
  // Methods
  getSignedUrl(config: GetSignedUrlConfig): Promise<string>;
}

interface GetSignedUrlConfig {
  version: 'v2' | 'v4';
  action: 'read' | 'write' | 'delete' | 'resumable' | 'list';
  expires: string | number | Date;
  accessibleAt?: string | number | Date;
  virtualHostedStyle?: boolean;
  cname?: string;
  contentMd5?: string;
  contentType?: string;
  extensionHeaders?: { [key: string]: string };
  queryParams?: { [key: string]: string };
  responseDisposition?: string;
  responseType?: string;
  promptSaveAs?: string;
}

type GetSignedUrlResponse = [string]; // [signedUrl]
type GetSignedUrlCallback = (err: Error | null, url?: string) => void;

Advanced Signed URL Patterns

// Batch signed URL generation
async function generateBatchSignedUrls(files: File[], action: string, expires: Date) {
  const urlPromises = files.map(file => 
    file.getSignedUrl({
      version: 'v4',
      action: action as any,
      expires: expires
    })
  );
  
  const urls = await Promise.all(urlPromises);
  
  const result = files.map((file, index) => ({
    fileName: file.name,
    signedUrl: urls[index][0]
  }));
  
  return result;
}

// Time-limited upload URLs
async function generateTimeLimitedUploadUrls(fileNames: string[], bucket: Bucket) {
  const expires = Date.now() + 60 * 60 * 1000; // 1 hour
  
  const urlPromises = fileNames.map(fileName => {
    const file = bucket.file(fileName);
    return file.getSignedUrl({
      version: 'v4',
      action: 'write',
      expires: expires,
      contentType: 'application/octet-stream'
    });
  });
  
  const urls = await Promise.all(urlPromises);
  
  return fileNames.map((fileName, index) => ({
    fileName,
    uploadUrl: urls[index][0],
    expiresAt: new Date(expires)
  }));
}

// Conditional access URLs
async function generateConditionalUrls(file: File) {
  // URL valid only for specific IP ranges
  const restrictedUrl = await file.getSignedUrl({
    version: 'v4',
    action: 'read',
    expires: Date.now() + 3600000, // 1 hour
    extensionHeaders: {
      'x-goog-ip-range': '192.168.1.0/24'
    }
  });
  
  // URL with custom response headers
  const customHeaderUrl = await file.getSignedUrl({
    version: 'v4',
    action: 'read',
    expires: Date.now() + 3600000,
    responseDisposition: 'attachment; filename="download.pdf"',
    responseType: 'application/pdf'
  });
  
  return {
    restricted: restrictedUrl[0],
    customHeaders: customHeaderUrl[0]
  };
}

Channel Management

Channel Class

class Channel {
  constructor(storage: Storage, id: string, resourceId: string);
  
  // Properties
  id: string;
  resourceId: string;
  
  // Methods
  stop(): Promise<void>;
}

type StopCallback = (err: Error | null) => void;

// Create and manage channels
const channel = storage.channel('my-channel', 'resource-123');

// Stop channel (Promise)
await channel.stop();

// Stop channel (callback)
channel.stop((err) => {
  if (err) {
    console.error('Error stopping channel:', err);
  } else {
    console.log('Channel stopped successfully');
  }
});

Channel Lifecycle Management

class ChannelManager {
  private channels = new Map<string, Channel>();
  private storage: Storage;
  
  constructor(storage: Storage) {
    this.storage = storage;
  }
  
  async createChannel(bucket: Bucket, channelId: string, webhookUrl: string) {
    // Create watch channel
    const [channel] = await bucket.createChannel(channelId, {
      address: webhookUrl,
      type: 'web_hook'
    });
    
    // Track channel
    this.channels.set(channelId, channel);
    
    // Auto-stop after timeout
    setTimeout(async () => {
      await this.stopChannel(channelId);
    }, 24 * 60 * 60 * 1000); // 24 hours
    
    return channel;
  }
  
  async stopChannel(channelId: string) {
    const channel = this.channels.get(channelId);
    if (channel) {
      await channel.stop();
      this.channels.delete(channelId);
      console.log(`Channel ${channelId} stopped`);
    }
  }
  
  async stopAllChannels() {
    const stopPromises = Array.from(this.channels.values()).map(channel => channel.stop());
    await Promise.all(stopPromises);
    this.channels.clear();
    console.log('All channels stopped');
  }
  
  getActiveChannels() {
    return Array.from(this.channels.keys());
  }
}

Error Classes and Handling

ApiError

// ApiError is exported from the common library
import { ApiError } from '@google-cloud/storage';

interface ApiErrorOptions {
  code?: number;
  message?: string;
  errors?: any[];
}

class ApiError extends Error {
  code?: number;
  errors?: any[];
  
  constructor(options?: ApiErrorOptions | string);
}

// Error handling utilities
class StorageErrorHandler {
  static isRetryable(error: Error): boolean {
    if (error instanceof ApiError) {
      // Retry on server errors and rate limiting
      return error.code >= 500 || error.code === 429 || error.code === 408;
    }
    return false;
  }
  
  static getErrorCategory(error: Error): string {
    if (error instanceof ApiError) {
      if (error.code === 400) return 'client-error';
      if (error.code === 401 || error.code === 403) return 'auth-error';
      if (error.code === 404) return 'not-found';
      if (error.code === 409) return 'conflict';
      if (error.code === 412) return 'precondition-failed';
      if (error.code === 429) return 'rate-limited';
      if (error.code >= 500) return 'server-error';
    }
    return 'unknown';
  }
  
  static async retryOperation<T>(
    operation: () => Promise<T>,
    maxRetries = 3,
    baseDelay = 1000
  ): Promise<T> {
    let lastError: Error;
    
    for (let attempt = 1; attempt <= maxRetries; attempt++) {
      try {
        return await operation();
      } catch (error) {
        lastError = error as Error;
        
        if (!this.isRetryable(lastError) || attempt === maxRetries) {
          throw lastError;
        }
        
        // Exponential backoff with jitter
        const delay = baseDelay * Math.pow(2, attempt - 1) + Math.random() * 1000;
        await new Promise(resolve => setTimeout(resolve, delay));
        
        console.warn(`Attempt ${attempt} failed, retrying in ${delay}ms:`, lastError.message);
      }
    }
    
    throw lastError!;
  }
}

Utility Functions

Path and Name Utilities

// Utility functions for common operations
class StorageUtils {
  // Sanitize file names for Cloud Storage
  static sanitizeFileName(fileName: string): string {
    // Remove invalid characters
    return fileName
      .replace(/[<>:"/\\|?*]/g, '_')
      .replace(/\s+/g, '_')
      .toLowerCase();
  }
  
  // Generate unique file names
  static generateUniqueFileName(baseName: string, extension?: string): string {
    const timestamp = Date.now();
    const random = Math.random().toString(36).substring(2, 8);
    const ext = extension || '';
    return `${baseName}_${timestamp}_${random}${ext}`;
  }
  
  // Parse Cloud Storage URLs
  static parseStorageUrl(url: string): { bucket: string; object: string } | null {
    // Match gs://bucket/object or https://storage.googleapis.com/bucket/object
    const gsMatch = url.match(/^gs:\/\/([^\/]+)\/(.+)$/);
    if (gsMatch) {
      return { bucket: gsMatch[1], object: gsMatch[2] };
    }
    
    const httpsMatch = url.match(/^https:\/\/storage\.googleapis\.com\/([^\/]+)\/(.+)$/);
    if (httpsMatch) {
      return { bucket: httpsMatch[1], object: decodeURIComponent(httpsMatch[2]) };
    }
    
    return null;
  }
  
  // Build Cloud Storage URLs
  static buildStorageUrl(bucket: string, object: string, protocol: 'gs' | 'https' = 'gs'): string {
    if (protocol === 'gs') {
      return `gs://${bucket}/${object}`;
    } else {
      return `https://storage.googleapis.com/${bucket}/${encodeURIComponent(object)}`;
    }
  }
  
  // Extract metadata from file path
  static getFileInfo(filePath: string) {
    const path = require('path');
    const fs = require('fs');
    
    const stat = fs.statSync(filePath);
    const parsed = path.parse(filePath);
    
    return {
      name: parsed.name,
      extension: parsed.ext,
      size: stat.size,
      modified: stat.mtime,
      directory: parsed.dir,
      fullPath: filePath
    };
  }
}

Batch Operations

// Batch utility for processing large numbers of operations
class BatchProcessor<T, R> {
  private batchSize: number;
  private concurrency: number;
  
  constructor(batchSize = 100, concurrency = 10) {
    this.batchSize = batchSize;
    this.concurrency = concurrency;
  }
  
  async processBatch<T, R>(
    items: T[],
    processor: (item: T) => Promise<R>
  ): Promise<Array<{ item: T; result?: R; error?: Error }>> {
    const results: Array<{ item: T; result?: R; error?: Error }> = [];
    
    // Split into batches
    for (let i = 0; i < items.length; i += this.batchSize) {
      const batch = items.slice(i, i + this.batchSize);
      
      // Process batch with concurrency limit
      const batchPromises = batch.map(async (item) => {
        try {
          const result = await processor(item);
          return { item, result };
        } catch (error) {
          return { item, error: error as Error };
        }
      });
      
      const batchResults = await this.limitConcurrency(batchPromises, this.concurrency);
      results.push(...batchResults);
      
      console.log(`Processed batch ${Math.floor(i / this.batchSize) + 1}/${Math.ceil(items.length / this.batchSize)}`);
    }
    
    return results;
  }
  
  private async limitConcurrency<T>(promises: Promise<T>[], limit: number): Promise<T[]> {
    const results: T[] = [];
    
    for (let i = 0; i < promises.length; i += limit) {
      const batch = promises.slice(i, i + limit);
      const batchResults = await Promise.all(batch);
      results.push(...batchResults);
    }
    
    return results;
  }
}

// Usage example
const processor = new BatchProcessor<string, File>(50, 5);

const filePaths = [/* array of file paths */];
const results = await processor.processBatch(filePaths, async (filePath) => {
  const [uploadResult] = await bucket.upload(filePath);
  return uploadResult;
});

// Process results
const successful = results.filter(r => r.result && !r.error);
const failed = results.filter(r => r.error);

console.log(`Success: ${successful.length}, Failed: ${failed.length}`);

Storage Metrics and Monitoring

// Metrics collection utilities
class StorageMetrics {
  private metrics = {
    uploads: 0,
    downloads: 0,
    deletes: 0,
    errors: 0,
    bytesUploaded: 0,
    bytesDownloaded: 0
  };
  
  recordUpload(bytes: number) {
    this.metrics.uploads++;
    this.metrics.bytesUploaded += bytes;
  }
  
  recordDownload(bytes: number) {
    this.metrics.downloads++;
    this.metrics.bytesDownloaded += bytes;
  }
  
  recordDelete() {
    this.metrics.deletes++;
  }
  
  recordError() {
    this.metrics.errors++;
  }
  
  getMetrics() {
    return { ...this.metrics };
  }
  
  reset() {
    Object.keys(this.metrics).forEach(key => {
      (this.metrics as any)[key] = 0;
    });
  }
  
  // Format bytes for display
  formatBytes(bytes: number): string {
    const units = ['B', 'KB', 'MB', 'GB', 'TB'];
    let size = bytes;
    let unitIndex = 0;
    
    while (size >= 1024 && unitIndex < units.length - 1) {
      size /= 1024;
      unitIndex++;
    }
    
    return `${size.toFixed(2)} ${units[unitIndex]}`;
  }
  
  getSummary() {
    return {
      operations: {
        uploads: this.metrics.uploads,
        downloads: this.metrics.downloads,
        deletes: this.metrics.deletes,
        errors: this.metrics.errors
      },
      bandwidth: {
        uploaded: this.formatBytes(this.metrics.bytesUploaded),
        downloaded: this.formatBytes(this.metrics.bytesDownloaded),
        total: this.formatBytes(this.metrics.bytesUploaded + this.metrics.bytesDownloaded)
      },
      errorRate: this.metrics.uploads + this.metrics.downloads > 0 
        ? (this.metrics.errors / (this.metrics.uploads + this.metrics.downloads) * 100).toFixed(2) + '%'
        : '0%'
    };
  }
}

// Global metrics instance
const storageMetrics = new StorageMetrics();

// Usage in operations
async function monitoredUpload(file: File, data: Buffer) {
  try {
    await file.save(data);
    storageMetrics.recordUpload(data.length);
  } catch (error) {
    storageMetrics.recordError();
    throw error;
  }
}

// Periodic reporting
setInterval(() => {
  console.log('Storage Metrics:', storageMetrics.getSummary());
}, 60000); // Every minute

Type Guards and Validation

// Type guards for runtime validation
class StorageTypeGuards {
  static isFile(obj: any): obj is File {
    return obj && typeof obj.save === 'function' && typeof obj.download === 'function';
  }
  
  static isBucket(obj: any): obj is Bucket {
    return obj && typeof obj.file === 'function' && typeof obj.upload === 'function';
  }
  
  static isStorage(obj: any): obj is Storage {
    return obj && typeof obj.bucket === 'function' && typeof obj.createBucket === 'function';
  }
  
  static isApiError(error: any): error is ApiError {
    return error instanceof ApiError || (error && typeof error.code === 'number');
  }
  
  // Validate storage configuration
  static validateStorageOptions(options: any): options is StorageOptions {
    if (!options || typeof options !== 'object') return false;
    
    if (options.projectId && typeof options.projectId !== 'string') return false;
    if (options.keyFilename && typeof options.keyFilename !== 'string') return false;
    if (options.apiEndpoint && typeof options.apiEndpoint !== 'string') return false;
    
    return true;
  }
  
  // Validate file metadata
  static validateFileMetadata(metadata: any): metadata is FileMetadata {
    if (!metadata || typeof metadata !== 'object') return false;
    
    if (metadata.contentType && typeof metadata.contentType !== 'string') return false;
    if (metadata.size && typeof metadata.size !== 'string') return false;
    if (metadata.generation && typeof metadata.generation !== 'string') return false;
    
    return true;
  }
}

Constants and Enums

// Storage class constants
export const STORAGE_CLASSES = {
  STANDARD: 'STANDARD',
  NEARLINE: 'NEARLINE', 
  COLDLINE: 'COLDLINE',
  ARCHIVE: 'ARCHIVE',
  DURABLE_REDUCED_AVAILABILITY: 'DURABLE_REDUCED_AVAILABILITY'
} as const;

// ACL constants
export const ACL_ROLES = {
  OWNER: 'OWNER',
  READER: 'READER', 
  WRITER: 'WRITER',
  FULL_CONTROL: 'FULL_CONTROL'
} as const;

export const ACL_ENTITIES = {
  ALL_USERS: 'allUsers',
  ALL_AUTHENTICATED_USERS: 'allAuthenticatedUsers'
} as const;

// HTTP methods for signed URLs
export const SIGNED_URL_ACTIONS = {
  READ: 'read',
  WRITE: 'write',
  DELETE: 'delete',
  RESUMABLE: 'resumable'
} as const;

// Event types for notifications
export const NOTIFICATION_EVENTS = {
  OBJECT_FINALIZE: 'OBJECT_FINALIZE',
  OBJECT_DELETE: 'OBJECT_DELETE', 
  OBJECT_METADATA_UPDATE: 'OBJECT_METADATA_UPDATE',
  OBJECT_ACL_UPDATE: 'OBJECT_ACL_UPDATE'
} as const;

// Usage
const bucket = storage.bucket('my-bucket');
await bucket.setStorageClass(STORAGE_CLASSES.COLDLINE);

await file.acl.add({
  entity: ACL_ENTITIES.ALL_USERS,
  role: ACL_ROLES.READER
});

docs

access-control.md

authentication.md

bucket-operations.md

file-operations.md

index.md

notifications.md

storage-client.md

transfer-manager.md

utilities.md

tile.json