or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

docs

access-control.mdauthentication.mdbucket-operations.mdfile-operations.mdindex.mdnotifications.mdstorage-client.mdtransfer-manager.mdutilities.md
tile.json

bucket-operations.mddocs/

Bucket Operations

The Bucket class represents a Cloud Storage bucket and provides comprehensive methods for file management, metadata operations, lifecycle rules, and access control.

Class Definition

class Bucket extends ServiceObject {
  constructor(storage: Storage, name: string, options?: BucketOptions);
  
  // Properties
  name: string;
  storage: Storage;
  acl: Acl;
  iam: Iam;
  userProject?: string;
  metadata: BucketMetadata;
  cloudStorageURI: URL;
  
  // File management methods
  file(name: string, options?: FileOptions): File;
  getFiles(options?: GetFilesOptions): Promise<GetFilesResponse>;
  getFilesStream(query?: GetFilesOptions): Readable;
  upload(pathString: string, options?: UploadOptions): Promise<UploadResponse>;
  deleteFiles(query?: DeleteFilesOptions): Promise<void>;
  combine(sources: (File | string)[], destination: File | string, options?: CombineOptions): Promise<CombineResponse>;
  
  // Bucket metadata and management
  exists(options?: BucketExistsOptions): Promise<BucketExistsResponse>;
  get(options?: GetBucketOptions): Promise<GetBucketResponse>;
  getMetadata(options?: GetBucketMetadataOptions): Promise<GetBucketMetadataResponse>;
  setMetadata(metadata: BucketMetadata, options?: SetBucketMetadataOptions): Promise<SetBucketMetadataResponse>;
  delete(options?: DeleteBucketOptions): Promise<DeleteBucketResponse>;
  restore(options?: RestoreBucketOptions): Promise<RestoreBucketResponse>;
  
  // Retention policy management
  setRetentionPeriod(period: number, options?: SetRetentionPeriodOptions): Promise<SetBucketMetadataResponse>;
  removeRetentionPeriod(options?: RemoveRetentionPeriodOptions): Promise<SetBucketMetadataResponse>;
  
  // Configuration helpers
  setCorsConfiguration(cors: Cors[], options?: SetCorsOptions): Promise<SetBucketMetadataResponse>;
  setUserProject(projectId: string): void;
  enableLogging(config: LoggingConfig, options?: EnableLoggingOptions): Promise<SetBucketMetadataResponse>;
  
  // Notifications
  notification(id: string): Notification;
  
  // Low-level access
  request(options: RequestOptions): Promise<RequestResponse>;
}

interface BucketOptions {
  crc32cGenerator?: CRC32CValidatorGenerator;
  kmsKeyName?: string;
  preconditionOpts?: PreconditionOptions;
  userProject?: string;
  generation?: number;
  softDeleted?: boolean;
}

File Management

Get File Reference

file(name: string, options?: FileOptions): File

// Get file reference
const file = bucket.file('path/to/file.txt');

// With options
const file = bucket.file('path/to/file.txt', {
  generation: 1234567890,
  encryptionKey: 'base64-encoded-key'
});

// With KMS encryption
const file = bucket.file('encrypted-file.txt', {
  kmsKeyName: 'projects/PROJECT_ID/locations/LOCATION/keyRings/RING_ID/cryptoKeys/KEY_ID'
});

Upload Files

upload(pathString: string, options?: UploadOptions): Promise<UploadResponse>

interface UploadOptions {
  destination?: string;
  encryptionKey?: string | Buffer;
  gzip?: boolean;
  metadata?: FileMetadata;
  offset?: number;
  predefinedAcl?: PredefinedAcl;
  private?: boolean;
  public?: boolean;
  resumable?: boolean;
  timeout?: number;
  uri?: string;
  userProject?: string;
  validation?: string | boolean;
  preconditionOpts?: PreconditionOptions;
  chunkSize?: number;
}

type UploadResponse = [File, unknown]; // [file, apiResponse]

// Simple upload
const [file] = await bucket.upload('/local/path/file.txt');

// Upload with custom destination
const [file] = await bucket.upload('/local/path/file.txt', {
  destination: 'remote/path/renamed-file.txt'
});

// Upload with metadata
const [file] = await bucket.upload('/local/path/image.jpg', {
  metadata: {
    contentType: 'image/jpeg',
    cacheControl: 'public, max-age=86400',
    metadata: {
      originalName: 'vacation-photo.jpg',
      uploadedBy: 'user-123'
    }
  }
});

// Upload with compression
const [file] = await bucket.upload('/local/path/large-file.txt', {
  gzip: true,
  metadata: {
    contentEncoding: 'gzip'
  }
});

// Upload with encryption
const [file] = await bucket.upload('/local/path/secret.txt', {
  encryptionKey: crypto.randomBytes(32).toString('base64')
});

// Upload with access control
const [file] = await bucket.upload('/local/path/public-file.txt', {
  public: true
});

// Resumable upload for large files
const [file] = await bucket.upload('/local/path/large-video.mp4', {
  resumable: true,
  chunkSize: 256 * 1024, // 256KB chunks
  metadata: {
    contentType: 'video/mp4'
  }
});

List Files

getFiles(options?: GetFilesOptions): Promise<GetFilesResponse>
getFilesStream(query?: GetFilesOptions): Readable

interface GetFilesOptions {
  delimiter?: string;
  directory?: string;
  endOffset?: string;
  includeFoldersAsPrefixes?: boolean;
  includeTrailingDelimiter?: boolean;
  maxResults?: number;
  pageToken?: string;
  prefix?: string;
  startOffset?: string;
  userProject?: string;
  versions?: boolean;
  autoPaginate?: boolean;
  softDeleted?: boolean;
}

type GetFilesResponse = [File[], {}, unknown]; // [files, nextQuery, apiResponse]

// List all files
const [files] = await bucket.getFiles();
files.forEach(file => {
  console.log(`File: ${file.name}`);
});

// List with prefix filter
const [files] = await bucket.getFiles({
  prefix: 'uploads/'
});

// List files in a "directory"
const [files] = await bucket.getFiles({
  prefix: 'images/',
  delimiter: '/'
});

// List with pagination
const [files, nextQuery] = await bucket.getFiles({
  maxResults: 100
});

if (nextQuery) {
  const [moreFiles] = await bucket.getFiles(nextQuery);
}

// List all versions
const [files] = await bucket.getFiles({
  versions: true
});

// Stream files for large lists
bucket.getFilesStream({ prefix: 'logs/' })
  .on('data', file => {
    console.log(`File: ${file.name}, Size: ${file.metadata.size}`);
  })
  .on('end', () => {
    console.log('Finished listing files');
  });

// List with date range
const [files] = await bucket.getFiles({
  startOffset: '2023-01-01',
  endOffset: '2023-12-31'
});

Delete Files

deleteFiles(query?: DeleteFilesOptions): Promise<void>

interface DeleteFilesOptions {
  prefix?: string;
  delimiter?: string;
  directory?: string;
  versions?: boolean;
  force?: boolean;
  userProject?: string;
}

// Delete all files with prefix
await bucket.deleteFiles({
  prefix: 'temp/'
});

// Delete all files in "directory"
await bucket.deleteFiles({
  prefix: 'old-data/',
  delimiter: '/'
});

// Delete all versions
await bucket.deleteFiles({
  prefix: 'archived/',
  versions: true
});

// Force delete (ignore errors)
await bucket.deleteFiles({
  prefix: 'cleanup/',
  force: true
});

Combine Files

combine(sources: (File | string)[], destination: File | string, options?: CombineOptions): Promise<CombineResponse>

interface CombineOptions {
  kmsKeyName?: string;
  userProject?: string;
}

type CombineResponse = [File, unknown]; // [combinedFile, apiResponse]

// Combine files by name
const [combinedFile] = await bucket.combine([
  'part1.txt',
  'part2.txt', 
  'part3.txt'
], 'combined.txt');

// Combine File objects
const part1 = bucket.file('logs/part1.log');
const part2 = bucket.file('logs/part2.log');
const destination = bucket.file('logs/combined.log');

const [combinedFile] = await bucket.combine([part1, part2], destination);

// Combine with encryption
const [combinedFile] = await bucket.combine(
  ['encrypted-part1.txt', 'encrypted-part2.txt'],
  'encrypted-combined.txt',
  {
    kmsKeyName: 'projects/PROJECT_ID/locations/us/keyRings/my-ring/cryptoKeys/my-key'
  }
);

Bucket Metadata and Management

Check Existence

exists(options?: BucketExistsOptions): Promise<BucketExistsResponse>

interface BucketExistsOptions {
  userProject?: string;
}

type BucketExistsResponse = [boolean]; // [exists]

// Check if bucket exists
const [exists] = await bucket.exists();
if (exists) {
  console.log('Bucket exists');
} else {
  console.log('Bucket does not exist');
}

Get Bucket (Create if Needed)

get(options?: GetBucketOptions): Promise<GetBucketResponse>

interface GetBucketOptions extends GetBucketMetadataOptions {
  autoCreate?: boolean;
  location?: string;
  storageClass?: string;
}

type GetBucketResponse = [Bucket, unknown]; // [bucket, apiResponse]

// Get bucket (create if doesn't exist)
const [bucket] = await bucket.get({
  autoCreate: true,
  location: 'us-central1'
});

Get and Set Metadata

getMetadata(options?: GetBucketMetadataOptions): Promise<GetBucketMetadataResponse>
setMetadata(metadata: BucketMetadata, options?: SetBucketMetadataOptions): Promise<SetBucketMetadataResponse>

interface GetBucketMetadataOptions {
  userProject?: string;
  ifMetagenerationMatch?: number;
  ifMetagenerationNotMatch?: number;
}

interface BucketMetadata {
  acl?: AclMetadata[];
  cors?: Cors[];
  defaultEventBasedHold?: boolean;
  defaultObjectAcl?: AclMetadata[];
  encryption?: {
    defaultKmsKeyName?: string;
  };
  iamConfiguration?: {
    bucketPolicyOnly?: {
      enabled?: boolean;
    };
    publicAccessPrevention?: 'inherited' | 'enforced';
    uniformBucketLevelAccess?: {
      enabled?: boolean;
    };
  };
  labels?: { [key: string]: string };
  lifecycle?: {
    rule?: LifecycleRule[];
  };
  location?: string;
  locationType?: string;
  logging?: {
    logBucket?: string;
    logObjectPrefix?: string;
  };
  name?: string;
  owner?: {
    entity?: string;
    entityId?: string;
  };
  projectNumber?: string;
  retentionPolicy?: {
    effectiveTime?: string;
    isLocked?: boolean;
    retentionPeriod?: number;
  };
  storageClass?: string;
  timeCreated?: string;
  updated?: string;
  versioning?: {
    enabled?: boolean;
  };
  website?: {
    mainPageSuffix?: string;
    notFoundPage?: string;
  };
  requesterPays?: boolean;
}

type GetBucketMetadataResponse = [BucketMetadata, unknown]; // [metadata, apiResponse]
type SetBucketMetadataResponse = [BucketMetadata, unknown]; // [metadata, apiResponse]

// Get bucket metadata
const [metadata] = await bucket.getMetadata();
console.log('Location:', metadata.location);
console.log('Storage Class:', metadata.storageClass);
console.log('Created:', metadata.timeCreated);

// Update bucket metadata
const [metadata] = await bucket.setMetadata({
  website: {
    mainPageSuffix: 'index.html',
    notFoundPage: '404.html'
  },
  labels: {
    environment: 'production',
    team: 'frontend'
  }
});

// Enable versioning
await bucket.setMetadata({
  versioning: {
    enabled: true
  }
});

// Set CORS configuration
await bucket.setMetadata({
  cors: [
    {
      origin: ['https://example.com', 'https://app.example.com'],
      method: ['GET', 'POST', 'PUT', 'DELETE'],
      responseHeader: ['Content-Type', 'x-custom-header'],
      maxAgeSeconds: 3600
    }
  ]
});

Lifecycle Management

Add Lifecycle Rules

addLifecycleRule(rule: LifecycleRule | LifecycleRule[], options?: AddLifecycleRuleOptions): Promise<SetBucketMetadataResponse>

interface LifecycleRule {
  action: LifecycleAction;
  condition: LifecycleCondition;
}

interface LifecycleAction {
  type: 'Delete' | 'SetStorageClass' | 'AbortIncompleteMultipartUpload';
  storageClass?: string;
}

interface LifecycleCondition {
  age?: number;
  createdBefore?: Date | string;
  customTimeBefore?: Date | string;
  daysSinceCustomTime?: number;
  daysSinceNoncurrentTime?: number;
  isLive?: boolean;
  matchesStorageClass?: string[];
  noncurrentTimeBefore?: Date | string;
  numNewerVersions?: number;
}

interface AddLifecycleRuleOptions {
  append?: boolean;
  userProject?: string;
}

// Delete files older than 365 days
await bucket.addLifecycleRule({
  action: { type: 'Delete' },
  condition: { age: 365 }
});

// Move to Coldline after 30 days
await bucket.addLifecycleRule({
  action: { 
    type: 'SetStorageClass',
    storageClass: 'COLDLINE'
  },
  condition: { age: 30 }
});

// Delete old versions (keep only 5 newest)
await bucket.addLifecycleRule({
  action: { type: 'Delete' },
  condition: { 
    numNewerVersions: 5,
    isLive: false
  }
});

// Multiple rules at once
await bucket.addLifecycleRule([
  {
    action: { type: 'SetStorageClass', storageClass: 'NEARLINE' },
    condition: { age: 7, matchesStorageClass: ['STANDARD'] }
  },
  {
    action: { type: 'SetStorageClass', storageClass: 'ARCHIVE' },
    condition: { age: 90, matchesStorageClass: ['NEARLINE'] }
  },
  {
    action: { type: 'Delete' },
    condition: { age: 2555 } // ~7 years
  }
]);

// Abort incomplete multipart uploads
await bucket.addLifecycleRule({
  action: { type: 'AbortIncompleteMultipartUpload' },
  condition: { age: 7 }
});

Set Storage Class

setStorageClass(storageClass: string, options?: SetBucketStorageClassOptions): Promise<SetBucketMetadataResponse>

interface SetBucketStorageClassOptions {
  userProject?: string;
}

// Available storage classes
const storageClasses = [
  'STANDARD',
  'NEARLINE',
  'COLDLINE', 
  'ARCHIVE',
  'DURABLE_REDUCED_AVAILABILITY'
];

// Set bucket storage class
await bucket.setStorageClass('COLDLINE');

// Regional storage
await bucket.setStorageClass('REGIONAL');

Retention Policy Management

Set Retention Period

setRetentionPeriod(period: number, options?: SetRetentionPeriodOptions): Promise<SetBucketMetadataResponse>

interface SetRetentionPeriodOptions {
  userProject?: string;
}

// Set retention period (in seconds)
await bucket.setRetentionPeriod(86400 * 30); // 30 days

// Set retention period with user project
await bucket.setRetentionPeriod(86400 * 365, {
  userProject: 'billing-project-id'
});

// Equivalent using setMetadata
await bucket.setMetadata({
  retentionPolicy: {
    retentionPeriod: 86400 * 30
  }
});

Remove Retention Period

removeRetentionPeriod(options?: RemoveRetentionPeriodOptions): Promise<SetBucketMetadataResponse>

interface RemoveRetentionPeriodOptions {
  userProject?: string;
}

// Remove retention period (only possible if policy is not locked)
await bucket.removeRetentionPeriod();

// With user project
await bucket.removeRetentionPeriod({
  userProject: 'billing-project-id'
});

Lock Retention Policy

lock(metageneration: number | string): Promise<BucketLockResponse>

type BucketLockResponse = [BucketMetadata, unknown]; // [metadata, apiResponse]

// Lock retention policy (irreversible!)
const [metadata] = await bucket.lock(bucket.metadata.metageneration);
console.log('Retention policy locked until:', metadata.retentionPolicy.effectiveTime);

Access Control and Security

Make Public/Private

makePublic(options?: MakeBucketPublicOptions): Promise<MakeBucketPublicResponse>
makePrivate(options?: MakeBucketPrivateOptions): Promise<MakeBucketPrivateResponse>

interface MakeBucketPublicOptions {
  includeFiles?: boolean;
  force?: boolean;
  userProject?: string;
}

interface MakeBucketPrivateOptions {
  includeFiles?: boolean;
  force?: boolean;
  userProject?: string;
}

type MakeBucketPublicResponse = [File[], unknown]; // [files, apiResponse]
type MakeBucketPrivateResponse = [File[], unknown]; // [files, apiResponse]

// Make bucket public (bucket and all files)
const [files] = await bucket.makePublic({
  includeFiles: true
});

// Make bucket private
await bucket.makePrivate({
  includeFiles: true
});

// Force operation (ignore errors)
await bucket.makePublic({
  includeFiles: true,
  force: true
});

Requester Pays

enableRequesterPays(options?: EnableRequesterPaysOptions): Promise<EnableRequesterPaysResponse>
disableRequesterPays(options?: DisableRequesterPaysOptions): Promise<DisableRequesterPaysResponse>

interface EnableRequesterPaysOptions {
  userProject?: string;
}

interface DisableRequesterPaysOptions {
  userProject?: string;
}

type EnableRequesterPaysResponse = [unknown]; // [apiResponse]
type DisableRequesterPaysResponse = [unknown]; // [apiResponse]

// Enable requester pays
await bucket.enableRequesterPays();

// Disable requester pays
await bucket.disableRequesterPays();

Configuration Helper Methods

Set CORS Configuration

setCorsConfiguration(cors: Cors[], options?: SetCorsOptions): Promise<SetBucketMetadataResponse>

interface SetCorsOptions {
  userProject?: string;
}

interface Cors {
  origin?: string[];
  method?: string[];
  responseHeader?: string[];
  maxAgeSeconds?: number;
}

// Set CORS configuration
await bucket.setCorsConfiguration([
  {
    origin: ['https://example.com', 'https://app.example.com'],
    method: ['GET', 'POST', 'PUT', 'DELETE'],
    responseHeader: ['Content-Type', 'x-custom-header'],
    maxAgeSeconds: 3600
  }
]);

// Allow all origins (use carefully)
await bucket.setCorsConfiguration([
  {
    origin: ['*'],
    method: ['GET', 'HEAD'],
    responseHeader: ['*'],
    maxAgeSeconds: 86400
  }
]);

Set User Project

setUserProject(projectId: string): void

// Set user project for billing
bucket.setUserProject('billing-project-id');

// All subsequent operations will use this project for billing
const [files] = await bucket.getFiles(); // Uses billing-project-id for billing

Enable Access Logging

enableLogging(config: LoggingConfig, options?: EnableLoggingOptions): Promise<SetBucketMetadataResponse>

interface LoggingConfig {
  logBucket: string;
  logObjectPrefix?: string;
}

interface EnableLoggingOptions {
  userProject?: string;
}

// Enable access logging
await bucket.enableLogging({
  logBucket: 'access-logs-bucket',
  logObjectPrefix: 'my-bucket-logs/'
});

// Basic logging (no prefix)
await bucket.enableLogging({
  logBucket: 'access-logs-bucket'
});

Labels Management

Get, Set, and Delete Labels

getLabels(options?: GetLabelsOptions): Promise<GetLabelsResponse>
setLabels(labels: Labels, options?: SetLabelsOptions): Promise<SetLabelsResponse>
deleteLabels(labels?: string[], options?: DeleteLabelsOptions): Promise<DeleteLabelsResponse>

interface Labels {
  [key: string]: string;
}

interface GetLabelsOptions {
  userProject?: string;
}

interface SetLabelsOptions {
  userProject?: string;
}

interface DeleteLabelsOptions {
  userProject?: string;
}

type GetLabelsResponse = [Labels, unknown]; // [labels, apiResponse]
type SetLabelsResponse = [Labels, unknown]; // [labels, apiResponse]
type DeleteLabelsResponse = [Labels, unknown]; // [labels, apiResponse]

// Get current labels
const [labels] = await bucket.getLabels();
console.log('Current labels:', labels);

// Set labels
await bucket.setLabels({
  environment: 'production',
  team: 'backend',
  cost_center: '12345'
});

// Delete specific labels
await bucket.deleteLabels(['cost_center']);

// Delete all labels
await bucket.deleteLabels();

Notifications and Channels

Create Notifications

createNotification(topic: string, options?: CreateNotificationOptions): Promise<CreateNotificationResponse>
getNotifications(options?: GetNotificationsOptions): Promise<GetNotificationsResponse>

interface CreateNotificationOptions {
  eventTypes?: string[];
  objectNamePrefix?: string;
  payloadFormat?: string;
  userProject?: string;
}

interface GetNotificationsOptions {
  userProject?: string;
}

type CreateNotificationResponse = [Notification, unknown]; // [notification, apiResponse]
type GetNotificationsResponse = [Notification[], unknown]; // [notifications, apiResponse]

// Create notification for all events
const [notification] = await bucket.createNotification('projects/my-project/topics/bucket-notifications');

// Create notification for specific events
const [notification] = await bucket.createNotification('projects/my-project/topics/bucket-uploads', {
  eventTypes: ['OBJECT_FINALIZE'],
  objectNamePrefix: 'uploads/',
  payloadFormat: 'JSON_API_V1'
});

// List notifications
const [notifications] = await bucket.getNotifications();
notifications.forEach(notification => {
  console.log(`Notification: ${notification.id}`);
});

Get Notification Reference

notification(id: string): Notification

// Get notification reference by ID
const notification = bucket.notification('notification-id-123');

// Use the reference to get metadata or delete
const [metadata] = await notification.getMetadata();
console.log('Topic:', metadata.topic);

// Delete the notification
await notification.delete();

Create Watch Channel

createChannel(id: string, config: CreateChannelConfig, options?: CreateChannelOptions): Promise<CreateChannelResponse>

interface CreateChannelConfig {
  address: string;
  type?: string;
  token?: string;
  params?: { [key: string]: string };
}

interface CreateChannelOptions {
  userProject?: string;
}

type CreateChannelResponse = [Channel, unknown]; // [channel, apiResponse]

// Create watch channel
const [channel] = await bucket.createChannel('my-channel-id', {
  address: 'https://example.com/webhook',
  type: 'web_hook'
});

// With authentication token
const [channel] = await bucket.createChannel('secure-channel', {
  address: 'https://api.example.com/storage-webhook',
  token: 'secret-token-123'
});

Signed URLs

Generate Signed URLs

getSignedUrl(config: GetBucketSignedUrlConfig): Promise<GetSignedUrlResponse>

interface GetBucketSignedUrlConfig {
  version: 'v2' | 'v4';
  action: 'list';
  expires: string | number | Date;
  extensionHeaders?: { [key: string]: string };
  queryParams?: { [key: string]: string };
  cname?: string;
  contentMd5?: string;
  contentType?: string;
  virtualHostedStyle?: boolean;
}

type GetSignedUrlResponse = [string]; // [signedUrl]

// Generate signed URL for listing bucket contents
const [url] = await bucket.getSignedUrl({
  version: 'v4',
  action: 'list',
  expires: Date.now() + 15 * 60 * 1000 // 15 minutes
});

console.log('Signed URL:', url);

// With query parameters
const [url] = await bucket.getSignedUrl({
  version: 'v4', 
  action: 'list',
  expires: Date.now() + 3600000, // 1 hour
  queryParams: {
    prefix: 'uploads/',
    delimiter: '/'
  }
});

Delete Bucket

delete(options?: DeleteBucketOptions): Promise<DeleteBucketResponse>

interface DeleteBucketOptions {
  ignoreNotFound?: boolean;
  userProject?: string;
  ifMetagenerationMatch?: number;
  ifMetagenerationNotMatch?: number;
}

type DeleteBucketResponse = [unknown]; // [apiResponse]

// Delete empty bucket
await bucket.delete();

// Delete bucket ignoring not found errors
await bucket.delete({
  ignoreNotFound: true
});

Bucket Restoration

Restore Soft-Deleted Bucket

restore(options?: RestoreBucketOptions): Promise<RestoreBucketResponse>

interface RestoreBucketOptions {
  userProject?: string;
  ifMetagenerationMatch?: number;
  ifMetagenerationNotMatch?: number;
}

type RestoreBucketResponse = [Bucket, unknown]; // [bucket, apiResponse]

// Restore soft-deleted bucket
const [restoredBucket] = await bucket.restore();
console.log('Bucket restored:', restoredBucket.name);

// Restore with preconditions
const [restoredBucket] = await bucket.restore({
  ifMetagenerationMatch: bucket.metadata.metageneration
});

// Check if bucket is soft-deleted before restoring
const [exists] = await bucket.exists();
if (!exists && bucket.metadata.softDeleted) {
  const [restoredBucket] = await bucket.restore();
  console.log('Soft-deleted bucket restored successfully');
}

Low-Level Request Access

Make Custom HTTP Request

request(options: RequestOptions): Promise<RequestResponse>

interface RequestOptions {
  uri: string;
  method?: string;
  headers?: { [key: string]: string };
  body?: any;
  json?: boolean;
  qs?: { [key: string]: string };
}

type RequestResponse = [any, unknown]; // [responseBody, rawResponse]

// Make custom authenticated request to Storage API
const [response] = await bucket.request({
  uri: `/b/${bucket.name}/o`,
  method: 'GET',
  qs: {
    prefix: 'logs/',
    delimiter: '/'
  }
});

// Custom POST request with body
const [response] = await bucket.request({
  uri: `/b/${bucket.name}/acl`,
  method: 'POST',
  json: true,
  body: {
    entity: 'user-example@gmail.com',
    role: 'READER'
  }
});

// Use for advanced operations not covered by high-level methods
const [response] = await bucket.request({
  uri: `/b/${bucket.name}`,
  method: 'PATCH',
  json: true,
  body: {
    labels: {
      'custom-label': 'custom-value'
    }
  }
});

Callback Support

All async methods support both Promise and callback patterns:

// Promise pattern (recommended)
const [files] = await bucket.getFiles();

// Callback pattern  
bucket.getFiles((err, files, nextQuery, apiResponse) => {
  if (err) {
    console.error('Error:', err);
    return;
  }
  console.log(`Found ${files.length} files`);
});