# @seaverse/utils-sdk

SeaVerse Utils SDK - A utility toolkit including file upload, image compression, and more.

[![npm version](https://img.shields.io/npm/v/@seaverse/utils-sdk.svg)](https://www.npmjs.com/package/@seaverse/utils-sdk)
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)

## Features

- 📤 **File Upload** - Two-step upload with presigned URLs, secure and efficient
- 🖼️ **Image Compression** - Auto compress to 1080p, configurable quality and dimensions
- 📊 **Upload Progress** - Real-time progress callback with speed and remaining time
- ✅ **File Validation** - Size and type validation
- ⚡ **TypeScript** - Complete type definitions
- 🌍 **Environment Config** - Support for multiple environment switching

## Installation

```bash
npm install @seaverse/utils-sdk
# or
pnpm add @seaverse/utils-sdk
```

## Quick Start

### Basic Usage

```typescript
import { UtilsClient } from '@seaverse/utils-sdk';

// Initialize client
const client = new UtilsClient({
  token: 'your-bearer-token',
});

// Upload file
const file = document.querySelector('input[type="file"]').files[0];
const result = await client.uploadFile(file);

console.log('CDN URL:', result.cdnUrl);
console.log('File size:', result.fileSize);
console.log('Compressed:', result.compressed);
```

### With Progress Callback

```typescript
const result = await client.uploadFile(file, {
  onProgress: (progress) => {
    console.log(`Progress: ${progress.percent}%`);
    console.log(`Stage: ${progress.stage}`);
    console.log(`Speed: ${progress.speed} bytes/sec`);
    console.log(`Remaining time: ${progress.remainingTime} sec`);
  },
});
```

### Disable Image Compression

```typescript
const result = await client.uploadFile(file, {
  compress: false, // Disable compression
});
```

### Custom Compression Config

```typescript
const client = new UtilsClient({
  token: 'your-bearer-token',
  compress: {
    enabled: true,
    maxWidth: 1920,    // Custom max width
    maxHeight: 1080,   // Custom max height
    quality: 0.9,      // Custom compression quality
  },
});
```

## API Reference

### UtilsClient

#### Constructor

```typescript
new UtilsClient(options: UtilsClientOptions)
```

**Parameters:**

- `token` (string, required): Bearer Token
- `baseURL` (string, optional): API base URL, default `https://resource.seaverse.ai`
- `timeout` (number, optional): Request timeout (ms), default 60000
- `compress` (CompressionConfig, optional): Image compression config

**CompressionConfig:**

- `enabled` (boolean): Enable compression, default `true`
- `maxWidth` (number): Max width, default `1080`
- `maxHeight` (number): Max height, default `1080`
- `quality` (number): Compression quality (0-1), default `0.8`

#### uploadFile()

Upload file with automatic compression, validation, and upload flow.

```typescript
async uploadFile(file: File, options?: UploadOptions): Promise<UploadResult>
```

**Parameters:**

- `file`: File object to upload
- `options` (optional):
  - `onProgress`: Progress callback function
  - `compress`: Whether to compress (overrides global config)
  - `metadata`: Custom metadata

**Returns:**

```typescript
{
  fileName: string          // File name
  originalFileName: string  // Original file name
  cdnUrl: string           // CDN access URL
  objectPath: string       // Storage path
  fileSize: number         // File size (bytes)
  originalSize: number     // Original size (bytes)
  compressed: boolean      // Whether compressed
  contentType: string      // MIME type
  duration: number         // Upload duration (ms)
  expiresAt: string        // Expiration time
}
```

**Errors:**

- Throws `UtilsAPIError` on failure

**Example:**

```typescript
try {
  const result = await client.uploadFile(file, {
    onProgress: (progress) => {
      updateProgressBar(progress.percent);
    },
  });
  console.log('Upload successful:', result.cdnUrl);
} catch (error) {
  if (error instanceof UtilsAPIError) {
    console.error(`Upload failed [${error.code}]: ${error.message}`);
  }
}
```

#### setToken()

Update Bearer Token.

```typescript
setToken(token: string): void
```

#### setBaseURL()

Update base URL.

```typescript
setBaseURL(baseURL: string): void
```

#### updateCompressionConfig()

Update compression config.

```typescript
updateCompressionConfig(config: Partial<CompressionConfig>): void
```

## Utility Functions

### validateFile()

Validate file size and type.

```typescript
import { validateFile } from '@seaverse/utils-sdk';

const result = validateFile(file, {
  maxSize: 10 * 1024 * 1024,  // 10MB
  allowedTypes: ['image/jpeg', 'image/png'],
});

if (!result.valid) {
  console.error('Validation failed:', result.errors);
}
```

### compressImage()

Manually compress image.

```typescript
import { compressImage } from '@seaverse/utils-sdk';

const result = await compressImage(file, {
  maxWidth: 1920,
  maxHeight: 1080,
  quality: 0.8,
});

console.log('Compression ratio:', result.compressionRatio);
console.log('Compressed size:', result.compressedSize);
```

### formatBytes()

Format bytes to human-readable string.

```typescript
import { formatBytes } from '@seaverse/utils-sdk';

console.log(formatBytes(1024));       // "1 KB"
console.log(formatBytes(1048576));    // "1 MB"
console.log(formatBytes(1073741824)); // "1 GB"
```

## Error Handling

```typescript
import { UtilsAPIError, ErrorCode } from '@seaverse/utils-sdk';

try {
  const result = await client.uploadFile(file);
} catch (error) {
  if (error instanceof UtilsAPIError) {
    switch (error.code) {
      case ErrorCode.FILE_TOO_LARGE:
        alert('File too large, please select a file smaller than 100MB');
        break;
      case ErrorCode.INVALID_FILE_TYPE:
        alert('Unsupported file type');
        break;
      case ErrorCode.NETWORK_ERROR:
        alert('Network error, please check your connection');
        break;
      default:
        alert(`Upload failed: ${error.message}`);
    }
  }
}
```

## Error Codes

| Error Code | Description |
|------------|-------------|
| `FILE_TOO_LARGE` | File size exceeds limit |
| `INVALID_FILE_TYPE` | Unsupported file type |
| `PRESIGNED_URL_FAILED` | Failed to get presigned URL |
| `UPLOAD_FAILED` | Upload failed |
| `COMPRESSION_FAILED` | Image compression failed |
| `NETWORK_ERROR` | Network error |
| `TIMEOUT` | Request timeout |
| `VALIDATION_FAILED` | File validation failed |

## Environment Configuration

The SDK provides predefined environment URLs:

```typescript
import { UtilsClient, ENVIRONMENT_URLS } from '@seaverse/utils-sdk';

// Production environment (default)
const client = new UtilsClient({
  token: prodToken,
  // baseURL defaults to 'https://resource.seaverse.ai'
});

// Staging/Test environment
const stagingClient = new UtilsClient({
  baseURL: ENVIRONMENT_URLS.STAGING, // 'https://resource.sg.seaverse.dev'
  token: stagingToken,
});

// Local development
const localClient = new UtilsClient({
  baseURL: ENVIRONMENT_URLS.LOCAL, // 'http://localhost:8003'
  token: localToken,
});
```

### Available Environment URLs

| Environment | URL | Constant |
|-------------|-----|----------|
| Production | `https://resource.seaverse.ai` | `ENVIRONMENT_URLS.PRODUCTION` |
| Staging | `https://resource.sg.seaverse.dev` | `ENVIRONMENT_URLS.STAGING` |
| Local | `http://localhost:8003` | `ENVIRONMENT_URLS.LOCAL` |

## Integration with Auth SDK

```typescript
import { SeaVerseBackendAPIClient } from '@seaverse/auth-sdk';
import { UtilsClient } from '@seaverse/utils-sdk';

// 1. Login with Auth SDK
const authClient = new SeaVerseBackendAPIClient({
  appId: 'your-app-id',
});

const loginResult = await authClient.login({
  email: 'user@example.com',
  password: 'password',
});

// 2. Initialize Utils SDK with the same token
const utilsClient = new UtilsClient({
  token: loginResult.token,
});

// 3. Upload file
const result = await utilsClient.uploadFile(file);
```

## Upload Flow

The SDK uses presigned URL two-step upload mode:

```
1. Get presigned URL
   POST /api/v1/resources/presign-upload
   → Returns upload_url and cdn_url

2. Upload to GCS
   PUT {upload_url}
   → Direct upload to Google Cloud Storage

3. Return CDN URL
   Use cdn_url to access the file
```

**Advantages:**

- ✅ Secure: Token not exposed to third-party storage
- ✅ Efficient: Direct upload to GCS, no backend proxy
- ✅ Reliable: GCS provides high availability guarantee

## Image Compression

### Compression Strategy

- Max size: 1080×1080 pixels (default config)
- Maintains aspect ratio with auto scaling
- Configurable compression quality (0-1)
- Supports custom max width/height (maxWidth, maxHeight)

### Compression Trigger Conditions

Compression only occurs when all conditions are met:

1. File is an image type (MIME type starts with `image/`)
2. Compression is enabled (`compress.enabled === true`)
3. Not explicitly disabled in upload options (`options.compress !== false`)

### Skip Compression

```typescript
// Method 1: Disable globally
const client = new UtilsClient({
  token: 'xxx',
  compress: { enabled: false },
});

// Method 2: Disable for single upload
const result = await client.uploadFile(file, {
  compress: false,
});
```

## FAQ

### Q1: How to get token?

**A**: Get Bearer Token by logging in with `@seaverse/auth-sdk`

### Q2: What file types are supported?

**A**:
- **Images**: JPEG, PNG, GIF, WebP
- **Documents**: PDF, TXT, MD
- **Others**: Depends on backend configuration

### Q3: What's the file size limit?

**A**:
- Default limit: 100MB
- Can be customized via validation rules
- Images are auto-compressed to reduce transfer size

### Q4: How to handle upload failures?

**A**:
```typescript
try {
  await client.uploadFile(file);
} catch (error) {
  if (error instanceof UtilsAPIError) {
    // Handle based on error code
    if (error.code === ErrorCode.NETWORK_ERROR) {
      // Retry logic
    }
  }
}
```

### Q5: Does compression affect image quality?

**A**:
- Default quality is 0.8, balancing file size and image quality
- Adjustable via `quality` parameter (0-1)
- Recommended range: 0.7-0.9

## Development

```bash
# Install dependencies
pnpm install

# Build
pnpm build

# Development mode
pnpm dev

# Type check
pnpm typecheck
```

## License

MIT © [SeaVerse Team](mailto:support@seaverse.com)

## Changelog

### v0.1.0 (2026-01-07)

- 🎉 Initial release
- ✨ Support file upload (presigned URL mode)
- ✨ Support auto image compression (1080p)
- ✨ Support upload progress callback
- ✨ Support file validation
- ✨ Complete TypeScript type definitions
