npm package: @sproux/media-sdk
Description: Media URL builder library for Sproux — build image variant URLs, video HLS playlist URLs, and thumbnails from original media URLs.
Author: Sproux
Latest version: 0.1.14

# @sproux/media-sdk

Media URL builder library for Sproux — build image variant URLs, video HLS playlist URLs, thumbnails, and document URLs from object keys using a singleton pattern.

## Installation

```bash
# npm
npm install @sproux/media-sdk

# pnpm
pnpm add @sproux/media-sdk

# yarn
yarn add @sproux/media-sdk
```

## Object Key Format

All object keys follow the format:

```
{purpose}/{asset-type}/{user-id}/{asset-id}/{sanitized-fileName}.{extension}
```

Variant keys follow the format:

```
{purpose}/{asset-type}/{user-id}/{asset-id}/{sanitized-fileName}-{variant-key}.{extension}
```

Examples:

- `avatar/image/usr-1/abc/my-photo.jpg`
- `gallery/video/usr-1/abc/intro.mp4`
- `contract/document/usr-1/abc/contract-v2.pdf`
- `avatar/image/usr-1/abc/my-photo-w200-h200-fit-q80.webp` (variant)

## Quick Start

```typescript
import { SprouxMedia, IMAGE_FORMAT, IMAGE_RESIZE_TYPE } from '@sproux/media-sdk';

// Initialize once (e.g. at app startup)
const media = SprouxMedia.init({ cdnUrl: 'https://cdn.example.com' });

// Build an image variant URL with all options
const imageUrl = media.getImageUrl('avatar/image/usr-1/abc/my-photo.jpg', {
  format: IMAGE_FORMAT.WEBP,
  width: 200,
  height: 200,
  resizeType: IMAGE_RESIZE_TYPE.FIT,
  quality: 80,
});
// → "https://cdn.example.com/avatar/image/usr-1/abc/my-photo-w200-h200-fit-q80.webp"

// Width-only (height auto-scaled)
const widthOnly = media.getImageUrl('avatar/image/usr-1/abc/my-photo.jpg', {
  format: IMAGE_FORMAT.WEBP,
  width: 200,
  resizeType: IMAGE_RESIZE_TYPE.FIT,
});
// → "https://cdn.example.com/avatar/image/usr-1/abc/my-photo-w200-fit.webp"

// Height-only (width auto-scaled)
const heightOnly = media.getImageUrl('avatar/image/usr-1/abc/my-photo.jpg', {
  format: IMAGE_FORMAT.WEBP,
  height: 300,
  resizeType: IMAGE_RESIZE_TYPE.FIT,
});
// → "https://cdn.example.com/avatar/image/usr-1/abc/my-photo-h300-fit.webp"

// Or with just the object key (no variant transformation)
const originalUrl = media.getImageUrl('avatar/image/usr-1/abc/my-photo.jpg');
// → "https://cdn.example.com/avatar/image/usr-1/abc/my-photo.jpg"

// Determine the asset type from an object key
const imageType = SprouxMedia.determineObjectType('avatar/image/usr-1/abc/my-photo.jpg');
// → "image"

const videoType = SprouxMedia.determineObjectType('gallery/video/usr-1/abc/intro.mp4');
// → "video"

const documentType = SprouxMedia.determineObjectType('contract/document/usr-1/abc/contract-v2.pdf');
// → "document"

// Extract the asset ID
const assetId = SprouxMedia.getAssetId('avatar/image/usr-1/abc/my-photo.jpg');
// → "abc"

// Extract the file name
const fileName = SprouxMedia.getFileName('avatar/image/usr-1/abc/my-photo.jpg');
// → "my-photo.jpg"

// Build a document URL
const docUrl = media.getDocumentUrl('contract/document/usr-1/abc/contract-v2.pdf');
// → "https://cdn.example.com/contract/document/usr-1/abc/contract-v2.pdf"

// Build a video original URL
const videoUrl = media.getVideoOriginalUrl('gallery/video/usr-1/abc/intro.mp4');
// → "https://cdn.example.com/gallery/video/usr-1/abc/intro.mp4"

// Build a video HLS playlist URL
const hlsUrl = media.getVideoHlsUrl('gallery/video/usr-1/abc/intro.mp4');
// → "https://cdn.example.com/gallery/video/usr-1/abc/intro/playlist.m3u8"

// Build a video thumbnail URL
const thumbUrl = media.getVideoThumbnailUrl('gallery/video/usr-1/abc/intro.mp4');
// → "https://cdn.example.com/gallery/video/usr-1/abc/intro/thumbnail_5_0.jpg"
```

## Singleton Usage

```typescript
import { SprouxMedia, IMAGE_FORMAT, IMAGE_RESIZE_TYPE } from '@sproux/media-sdk';

// Initialize once at startup
SprouxMedia.init({ cdnUrl: 'https://cdn.example.com' });

// Access from anywhere via getInstance()
const media = SprouxMedia.getInstance();
// With variant options (both dimensions)
const url = media.getImageUrl('avatar/image/usr-1/abc/photo.jpg', {
  format: IMAGE_FORMAT.WEBP,
  width: 400,
  height: 300,
  resizeType: IMAGE_RESIZE_TYPE.FILL,
});

// Without options — returns the original URL
const original = media.getImageUrl('avatar/image/usr-1/abc/photo.jpg');
```

> Calling `getInstance()` before `init()` will throw an error.

## API Reference

### `SprouxMedia.init(config: SprouxMediaConfig): SprouxMedia`

Initialize the singleton with CDN configuration. Returns the singleton instance.

```typescript
const media = SprouxMedia.init({ cdnUrl: 'https://cdn.example.com' });
```

### `SprouxMedia.getInstance(): SprouxMedia`

Returns the existing singleton instance. Throws if `init()` has not been called.

### `media.getImageUrl(objectKey: string, options?: ImageUrlOptions): string`

Build a CDN URL for an image variant. When `options` is omitted, returns the original CDN URL for the object key as-is.

- `objectKey` — Object key **with** format (e.g. `"avatar/image/usr-1/abc/my-photo.jpg"`)
- `options` — Optional variant transformation options

```typescript
import { IMAGE_FORMAT, IMAGE_RESIZE_TYPE } from '@sproux/media-sdk';

// With variant options (both dimensions)
media.getImageUrl('avatar/image/usr-1/abc/photo.jpg', {
  format: IMAGE_FORMAT.WEBP,
  width: 400,
  height: 300,
  resizeType: IMAGE_RESIZE_TYPE.FILL,
  quality: 85,
});
// → "https://cdn.example.com/avatar/image/usr-1/abc/photo-w400-h300-fill-q85.webp"

// Width-only (height auto-scaled)
media.getImageUrl('avatar/image/usr-1/abc/photo.jpg', {
  format: IMAGE_FORMAT.WEBP,
  width: 400,
  resizeType: IMAGE_RESIZE_TYPE.FIT,
});
// → "https://cdn.example.com/avatar/image/usr-1/abc/photo-w400-fit.webp"

// Height-only (width auto-scaled)
media.getImageUrl('avatar/image/usr-1/abc/photo.jpg', {
  format: IMAGE_FORMAT.WEBP,
  height: 300,
  resizeType: IMAGE_RESIZE_TYPE.FIT,
});
// → "https://cdn.example.com/avatar/image/usr-1/abc/photo-h300-fit.webp"

// Without options — returns the original URL
media.getImageUrl('avatar/image/usr-1/abc/photo.jpg');
// → "https://cdn.example.com/avatar/image/usr-1/abc/photo.jpg"
```

#### ImageUrlOptions

All fields are optional.


| Property     | Type              | Required | Description                                                               |
| -------------- | ------------------- | ---------- | --------------------------------------------------------------------------- |
| `format`     | `ImageFormat`     | No       | Output format (`webp`, `avif`, `jpeg`, `png`, `gif`, `ico`, `svg`, `jpg`) |
| `width`      | `number`          | No       | Target width in pixels (1–4096)                                          |
| `height`     | `number`          | No       | Target height in pixels (1–4096)                                         |
| `resizeType` | `ImageResizeType` | No       | Resize strategy:`fit`, `fill`, `force`, `fill-down`, `auto`               |
| `quality`    | `number`          | No       | Quality 1–100                                                            |

### `media.getDocumentUrl(objectKey: string): string`

Build a CDN URL for a document file from an object key.

```typescript
media.getDocumentUrl('contract/document/usr-1/abc/contract-v2.pdf');
// → "https://cdn.example.com/contract/document/usr-1/abc/contract-v2.pdf"
```

### `media.getVideoOriginalUrl(objectKey: string): string`

Build the original CDN URL for a video from an object key.

```typescript
media.getVideoOriginalUrl('gallery/video/usr-1/abc/intro.mp4');
// → "https://cdn.example.com/gallery/video/usr-1/abc/intro.mp4"
```

### `media.getVideoHlsUrl(objectKey: string): string`

Build an HLS playlist URL from an object key.

```typescript
media.getVideoHlsUrl('gallery/video/usr-1/abc/intro.mp4');
// → "https://cdn.example.com/gallery/video/usr-1/abc/intro/playlist.m3u8"
```

### `media.getVideoThumbnailUrl(objectKey: string): string`

Build a thumbnail URL from an object key.

```typescript
media.getVideoThumbnailUrl('gallery/video/usr-1/abc/intro.mp4');
// → "https://cdn.example.com/gallery/video/usr-1/abc/intro/thumbnail_5_0.jpg"
```

### `SprouxMedia.determineObjectType(objectKey: string): AssetType | null`

Determine the asset type (`'image'`, `'video'`, or `'document'`) from an object key. Returns `null` if the asset-type segment is missing or does not match a known type.

Object key format: `{purpose}/{asset-type}/{user-id}/{asset-id}/{sanitized-fileName}.{extension}`

```typescript
SprouxMedia.determineObjectType('avatar/image/usr-1/abc/my-photo.jpg');
// → "image"

SprouxMedia.determineObjectType('gallery/video/usr-1/abc/intro.mp4');
// → "video"

SprouxMedia.determineObjectType('invalid');
// → null
```

### `SprouxMedia.getAssetId(objectKey: string): string | null`

Extract the asset ID from an object key. Returns `null` if the object key has fewer than 5 segments.

Object key format: `{purpose}/{asset-type}/{user-id}/{asset-id}/{sanitized-fileName}.{extension}`

```typescript
SprouxMedia.getAssetId('avatar/image/usr-1/abc/my-photo.jpg');
// → "abc"

SprouxMedia.getAssetId('gallery/video/019b7297-f2da-702f-aaa3-612498436e02/019ca305-abe5-7b31-929b-4a1999aeaca1/intro.mp4');
// → "019ca305-abe5-7b31-929b-4a1999aeaca1"

SprouxMedia.getAssetId('invalid/key');
// → null
```

### `SprouxMedia.getFileName(objectKey: string): string | null`

Extract the sanitized file name (with extension) from an object key. Returns `null` if the object key has fewer than 5 segments.

Object key format: `{purpose}/{asset-type}/{user-id}/{asset-id}/{sanitized-fileName}.{extension}`

```typescript
SprouxMedia.getFileName('avatar/image/usr-1/abc/my-photo.jpg');
// → "my-photo.jpg"

SprouxMedia.getFileName('invalid/key');
// → null
```

## Enums

Use the provided enums for type-safe image options:

```typescript
import { ASSET_TYPE, IMAGE_FORMAT, IMAGE_RESIZE_TYPE, MEDIA_PURPOSE } from '@sproux/media-sdk';

// MEDIA_PURPOSE
MEDIA_PURPOSE.AVATAR                     // 'avatar'
MEDIA_PURPOSE.HERO                       // 'hero'
MEDIA_PURPOSE.GALLERY                    // 'gallery'
MEDIA_PURPOSE.DELIVERY_PROOF             // 'delivery_proof'
MEDIA_PURPOSE.RESPONSE_DISPUTE_EVIDENCE  // 'response_dispute_evidence'
MEDIA_PURPOSE.REPORT_EVIDENCE            // 'report_evidence'
MEDIA_PURPOSE.DELIERY_UPDATE             // 'delivery_update'

// ASSET_TYPE
ASSET_TYPE.IMAGE    // 'image'
ASSET_TYPE.VIDEO    // 'video'
ASSET_TYPE.DOCUMENT // 'document'

// IMAGE_FORMAT
IMAGE_FORMAT.WEBP   // 'webp'
IMAGE_FORMAT.AVIF   // 'avif'
IMAGE_FORMAT.JPEG   // 'jpeg'
IMAGE_FORMAT.PNG    // 'png'
IMAGE_FORMAT.GIF    // 'gif'
IMAGE_FORMAT.ICO    // 'ico'
IMAGE_FORMAT.SVG    // 'svg'
IMAGE_FORMAT.JPG    // 'jpg'

// IMAGE_RESIZE_TYPE
IMAGE_RESIZE_TYPE.FIT       // 'fit'
IMAGE_RESIZE_TYPE.FILL      // 'fill'
IMAGE_RESIZE_TYPE.FORCE     // 'force'
IMAGE_RESIZE_TYPE.FILL_DOWN // 'fill-down'
IMAGE_RESIZE_TYPE.AUTO      // 'auto'
```

## Types

```typescript
import type {
  AssetType,         // 'image' | 'video' | 'document'
  ImageFormat,       // 'webp' | 'avif' | 'jpeg' | 'png' | 'gif' | 'ico' | 'svg' | 'jpg'
  ImageResizeType,   // 'fit' | 'fill' | 'force' | 'fill-down' | 'auto'
  SprouxMediaConfig, // { cdnUrl: string }
  ImageUrlOptions,   // { format?, width?, height?, resizeType?, quality? }
} from '@sproux/media-sdk';
```

> `ImageFormat` and `ImageResizeType` are string union types derived from their respective enums. You can use either the enum values (`IMAGE_FORMAT.WEBP`) or raw strings (`'webp'`).

## Helper

### `buildVariantString(options: ImageUrlOptions): string | null`

Build a deterministic variant string from image options. Returns `null` if no options are provided.

```typescript
import { buildVariantString } from '@sproux/media-sdk';

const variant = buildVariantString({
  width: 200,
  height: 200,
  resizeType: IMAGE_RESIZE_TYPE.FIT,
  quality: 80,
}); // "w200-h200-fit-q80"

// Width-only
buildVariantString({ width: 200, resizeType: IMAGE_RESIZE_TYPE.FIT }); // "w200-fit"

// Height-only
buildVariantString({ height: 300, resizeType: IMAGE_RESIZE_TYPE.FIT }); // "h300-fit"

// Empty options
buildVariantString({}); // null
```

## URL Structure

### Object Key

```
{purpose}/{asset-type}/{user-id}/{asset-id}/{sanitized-fileName}.{extension}
```

### Image Variant URL

```
{cdnUrl}/{purpose}/{asset-type}/{user-id}/{asset-id}/{sanitized-fileName}-{variant-key}.{format}
```

Examples:
- Both dimensions: `https://cdn.example.com/avatar/image/usr-1/abc/photo-w200-h200-fit-q80.webp`
- Width-only: `https://cdn.example.com/avatar/image/usr-1/abc/photo-w200-fit.webp`
- Height-only: `https://cdn.example.com/avatar/image/usr-1/abc/photo-h300-fit.webp`

### Document URL

```
{cdnUrl}/{objectKey}
```

### Video Original URL

```
{cdnUrl}/{objectKey}
```

### Video HLS URL

```
{cdnUrl}/{purpose}/{asset-type}/{user-id}/{asset-id}/{sanitized-fileName}/playlist.m3u8
```

### Video Thumbnail URL

```
{cdnUrl}/{purpose}/{asset-type}/{user-id}/{asset-id}/{sanitized-fileName}/thumbnail_5_0.jpg
```

## License

MIT