npm package: @posif/sdk
Description: TypeScript SDK for Posif applications with automatic query parameter parsing
Author: Posif
Latest version: 1.0.7

# Posif SDK

A TypeScript SDK for Posif applications with automatic query parameter parsing, designed for Next.js apps launched within Flutter WebViews.

## Features

- 🔄 Automatic URL query parameter parsing on initialization
- 🏷️ TypeScript support with full type definitions
- 🌐 Browser and server-side rendering (SSR) compatible
- 📦 ESM and CommonJS module formats
- 🎯 Singleton pattern for easy use across your application
- 🛠️ Utility functions for URL manipulation
- 🔒 Authentication token handling
- 🔌 Complete API integration for users, groups, and networks
- 🗄️ Token-based MongoDB database connection caching
- 🔐 Isolated database access per JWT token
- ⚡ Advanced cache management and configuration
- 🛠️ Development mode with local MongoDB URI fallback
- 🚀 Optimized for Next.js 15+ App Router
- 📦 **NEW:** S3-compatible Object Storage with presigned URLs
- 🔑 **NEW:** Automatic S3 key generation with user/group isolation

## Installation

```bash
npm install @posif/sdk
# or
yarn add @posif/sdk
# or
pnpm add @posif/sdk
```

## Package Exports

The SDK provides **three entry points** for optimal bundle size and tree-shaking:

### Main Export (Full SDK)

```typescript
// Import everything (backward compatible)
import { getDb, uploadFile, generateUploadUrl } from '@posif/sdk';
```

**Use when:** You need both database and storage functionality.

### Storage Export (Storage Functions for API Routes)

```typescript
// Import storage functions for API routes (no MongoDB)
import { uploadFile, generateUploadUrl, generateFileUrls } from '@posif/sdk/storage';
```

**Use when:** You need S3 storage functionality in API routes. This avoids pulling in MongoDB dependencies, reducing bundle size.

**Includes:**
- `uploadFile()` - Complete file upload with progress
- `generateUploadUrl()` - Get presigned upload URL
- `generateFileUrls()` - Get view/download URLs
- `generateBatchUploadUrls()` - Batch upload URLs
- `generateDeleteUrl()` - Get delete URL
- `generatePublicUrl()` - Get public URL for public buckets
- `getStorage()` - Advanced storage API
- Storage cache management functions

**⚠️ Note:** These functions are SERVER-ONLY and must be used in API routes, not client components.

### Server Export (Database Only)

```typescript
// Import only server-side database functions (no S3)
import { getDb, getMongoDbConfig } from '@posif/sdk/server';
```

**Use when:** You only need MongoDB functionality in server-side code.

**Includes:**
- `getDb()` - MongoDB database access
- `getMongoDbConfig()` - Get database configuration
- Database cache management functions
- `posifSDK` - SDK configuration

### Benefits

✅ **Smaller Bundles**: Import only what you need  
✅ **Better Tree-Shaking**: Optimized for modern bundlers  
✅ **Backward Compatible**: Main export still works  
✅ **Zero Breaking Changes**: Existing code continues to work

### Migration Guide

**Existing code works without changes!** But you can optimize by updating imports:

```typescript
// Before (still works)
import { uploadFile, getDb } from '@posif/sdk';

// After (optimized for storage-only API routes)
import { uploadFile } from '@posif/sdk/storage';

// After (optimized for database-only API routes)
import { getDb } from '@posif/sdk/server';
```

**When to migrate:**
- **API Routes (Storage Only)**: Use `/storage` to avoid MongoDB in storage API route bundles
- **API Routes (Database Only)**: Use `/server` if you only need database access
- **Mixed Usage**: Keep using main export or use both `/storage` and `/server`

## Quick Start

### Basic Usage

```typescript
import sdk from '@posif/sdk';

// The SDK automatically parses URL parameters on load
const params = sdk.getParams();
console.log(params);
// Output: { token: "abc123", user: "john", group: "dev", ... }

// Get specific parameters
const token = sdk.getParam('token');
const user = sdk.getParam('user');

// Check authentication status
if (sdk.isAuthenticated()) {
  console.log('User is authenticated');
}
```

### API Methods

```typescript
import sdk from '@posif/sdk';

// Fetch user information
const userInfo = await sdk.getUserInfo();
console.log(userInfo);

// Fetch group information
const groupInfo = await sdk.getGroupInfo();
console.log(groupInfo);

// Fetch group members
const members = await sdk.getMembers(); // Default: limit=20, offset=0
console.log(members);

// Fetch group members with pagination
const paginatedMembers = await sdk.getMembers(10, 5); // limit=10, offset=5
console.log(paginatedMembers);

// Fetch groups that user is part of
const groups = await sdk.getGroups(); // Default: limit=20, offset=0
console.log(groups);

// Fetch groups with role filter
const ownerGroups = await sdk.getGroups(20, 0, 'owner'); // Only groups where user is owner
console.log(ownerGroups);

// Fetch networks that user is part of
const networks = await sdk.getNetworks(); // Default: limit=20, offset=0
console.log(networks);

// Fetch networks with role filter
const ownerNetworks = await sdk.getNetworks(20, 0, 'owner'); // Only networks where user is owner
console.log(ownerNetworks);
```

### Database Access (Next.js 15+ App Router Optimized)

The SDK now includes a powerful `getDb()` function optimized for Next.js 15+ App Router with secure MongoDB access:

```typescript
import { NextRequest, NextResponse } from 'next/server';
import { getDb } from '@posif/sdk';

// Next.js 15+ App Router - Automatic token extraction from URL
// Example: /api/users?token=your_jwt_token
export async function GET(req: NextRequest) {
  try {
    // Get database instance (token automatically extracted from URL parameters)
    const db = await getDb(req);
    
    // Use MongoDB native driver
    const users = await db.collection('users').find({}).toArray();
    
    return NextResponse.json({ users });
  } catch (error) {
    return NextResponse.json({ error: error.message }, { status: 500 });
  }
}
```

**Token Extraction Order:**
1. **Explicit token parameter**: `getDb(req, 'your_token')`
2. **URL parameters (server-side)**: `/api/users?token=xxx` (automatically extracted)
3. **Pages Router query**: `req.query.token` (Next.js Pages Router)
4. **SDK params (browser-only)**: Fallback for client-side rendering

**With explicit JWT token from headers:**
```typescript
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
  try {
    // Extract token from Authorization header
    const token = req.headers.authorization?.replace('Bearer ', '');
    
    // Pass token explicitly (overrides URL parameter token)
    const db = await getDb(req, token);
    
    const users = await db.collection('users').find({}).toArray();
    res.status(200).json({ users });
  } catch (error) {
    res.status(500).json({ error: error.message });
  }
}
```

### Development Mode

For local development, the SDK automatically detects development mode and uses your local MongoDB URI:

```typescript
// .env.local
MONGODB_URI=mongodb://localhost:27017/your-database-name

// In your Next.js API route
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
  try {
    // SDK automatically detects development mode
    // Uses MONGODB_URI from .env.local instead of API calls
    const db = await getDb(req);
    
    const data = await db.collection('users').find({}).toArray();
    res.json({ data });
  } catch (error) {
    res.status(500).json({ error: error.message });
  }
}
```

**Development Mode Features:**
- ✅ Automatically detects `NODE_ENV=development`
- ✅ Uses `MONGODB_URI` from environment variables
- ✅ Skips API calls for MongoDB URI
- ✅ Perfect for local development and testing
- ✅ No need for JWT tokens in development

**Check Development Mode:**
```typescript
import { getDevelopmentInfo } from '@posif/sdk';

const devInfo = getDevelopmentInfo();
console.log('Development mode:', devInfo.isDevelopment);
console.log('Using local MongoDB:', devInfo.usingLocalMongoDb);
```

**Key Features:**
- ✅ **NEW:** Token-based database connection caching
- ✅ **NEW:** Isolated database access per JWT token
- ✅ **NEW:** Advanced cache management and configuration
- ✅ JWT token authentication with automatic extraction from URL parameters
- ✅ Supports explicit token override from Authorization headers
- ✅ Fetches MongoDB URI from Posif API with authentication
- ✅ Connection pooling and reuse (no reconnection overhead)
- ✅ Server-only (throws error if used in client code)
- ✅ Full TypeScript support with native MongoDB driver types

## Supported Query Parameters

The SDK automatically parses the following query parameters from the URL:

| Parameter | Type | Description |
|-----------|------|-------------|
| `token` | `string` | Authentication token |
| `user` | `string` | User identifier |
| `group` | `string` | Group identifier |
| `network` | `string` | Network identifier |
| `mode` | `string` | Application mode |
| `color` | `string` | Theme color |
| `lang` | `string` | Language code |

## API Reference

### PosifSDK Instance Methods

#### `getParams(): QueryParams`
Returns all parsed query parameters as an object.

#### `getParam<K>(key: K): QueryParams[K]`
Returns the value of a specific parameter.

#### `setParams(params: Partial<QueryParams>): void`
Updates the stored parameters (useful for testing or manual override).

#### `getUserInfo(): Promise<UserInfo>`
Fetches user information from the API endpoint `/api/v1/users/{id}`.
- **Endpoint**: `GET {apiBaseUrl}/api/v1/users/{user_id}`
- **Headers**: `Accept: application/json`, `Authorization: Bearer <token>` (if token provided)
- **Returns**: User information object

#### `getGroupInfo(): Promise<GroupInfo>`
Fetches group information from the API endpoint `/api/v1/groups/{id}`.
- **Endpoint**: `GET {apiBaseUrl}/api/v1/groups/{group_id}`
- **Headers**: `Accept: application/json`, `Authorization: Bearer <token>` (optional for some groups)
- **Returns**: Group information object

#### `getMembers(limit?: number, offset?: number): Promise<GroupMembersResponse>`
Fetches group members from the API endpoint `/api/v1/groups/{id}/members`.
- **Endpoint**: `GET {apiBaseUrl}/api/v1/groups/{group_id}/members`
- **Parameters**: `limit` (default: 20), `offset` (default: 0)
- **Headers**: `Accept: application/json`, `Authorization: Bearer <token>` (required)
- **Returns**: Group members with pagination information

#### `getGroups(limit?: number, offset?: number, roleType?: 'owner' | 'staff' | 'admin' | 'member'): Promise<GroupsResponse>`
Fetches groups that the user is part of from the API endpoint `/api/v1/groups`.
- **Endpoint**: `GET {apiBaseUrl}/api/v1/groups`
- **Parameters**: `limit` (default: 20), `offset` (default: 0), `roleType` (optional role filter)
- **Headers**: `Accept: application/json`, `Authorization: Bearer <token>` (required)
- **Returns**: User groups with pagination information and platform filtering status

#### `getNetworks(limit?: number, offset?: number, roleType?: 'owner' | 'staff' | 'admin' | 'member'): Promise<NetworksResponse>`
Fetches networks that the user is part of from the API endpoint `/api/v1/networks`.
- **Endpoint**: `GET {apiBaseUrl}/api/v1/networks`
- **Parameters**: `limit` (default: 20), `offset` (default: 0), `roleType` (optional role filter)
- **Headers**: `Accept: application/json`, `Authorization: Bearer <token>` (required)
- **Returns**: User networks with pagination information and platform filtering status

#### `isAuthenticated(): boolean`
Checks if a valid authentication token is present.

#### `getToken(): string | undefined`
Returns the authentication token if present.

#### `isBrowser(): boolean`
Checks if the SDK is running in a browser environment.

#### `refresh(): void`
Re-parses parameters from the current URL.

#### `clear(): void`
Clears all stored parameters.

### Utility Functions

#### `parseQueryParams(url?: string): QueryParams`
Parses query parameters from a URL string or current browser location.

#### `buildQueryString(params: QueryParams): string`
Builds a query string from a parameters object.

#### `mergeParams(base: QueryParams, override: Partial<QueryParams>): QueryParams`
Merges two parameter objects.

#### `validateRequiredParams(params: QueryParams, required: (keyof QueryParams)[]): boolean`
Validates that required parameters are present.

## Type Definitions

### QueryParams
```typescript
interface QueryParams {
  token?: string;
  user?: string;
  group?: string;
  network?: string;
  mode?: string;
  color?: string;
  lang?: string;
}
```

### UserInfo
```typescript
interface UserInfo {
  id: string;
  name: string;
  email: string;
  avatar?: string;
  role?: string;
  permissions?: string[];
}
```

### GroupInfo
```typescript
interface GroupInfo {
  id: string;
  name: string;
  userName: string;
  description: string;
  memberCount: number;
  type: 'public' | 'private';
  roleType: string;
  roleName: string;
  avatar: string;
}
```

### Group
```typescript
interface Group {
  id: string;
  user_name: string;
  group_name: string;
  member_count: string;
  avatar: string;
  cover_photo: string;
  role_id: string;
  role_name: string;
  role_type: 'owner' | 'staff' | 'admin' | 'member';
}
```

### GroupsResponse
```typescript
interface GroupsResponse {
  groups: Group[];
  pagination: {
    limit: number;
    offset: number;
    count: number;
  };
  platform_filtered: boolean;
}
```

### Network
```typescript
interface Network {
  id: string;
  user_name: string;
  network_name: string;
  member_count: string;
  avatar: string;
  cover_photo: string | null;
  role_id: string;
  role_name: string;
  role_type: 'owner' | 'staff' | 'admin' | 'member';
}
```

### NetworksResponse
```typescript
interface NetworksResponse {
  networks: Network[];
  pagination: {
    limit: number;
    offset: number;
    count: number;
  };
  platform_filtered: boolean;
}
```

### NetworkInfo
```typescript
interface NetworkInfo {
  id: string;
  name: string;
  domain: string;
  region: string;
  status: 'active' | 'inactive' | 'maintenance';
  endpoints?: {
    api: string;
    websocket?: string;
    cdn?: string;
  };
}
```

## Next.js Integration

### App Router (app directory)

```typescript
// app/layout.tsx
'use client';

import { useEffect } from 'react';
import sdk from '@posif/sdk';

export default function RootLayout({
  children,
}: {
  children: React.ReactNode;
}) {
  useEffect(() => {
    // SDK automatically parses URL parameters
    const params = sdk.getParams();
    
    if (params.token) {
      // Handle authentication
      console.log('User authenticated with token:', params.token);
    }
    
    if (params.color) {
      // Apply theme color
      document.documentElement.style.setProperty('--theme-color', params.color);
    }
    
    if (params.lang) {
      // Set language
      document.documentElement.lang = params.lang;
    }
  }, []);

  return (
    <html>
      <body>{children}</body>
    </html>
  );
}
```

### Pages Router (pages directory)

```typescript
// pages/_app.tsx
import { useEffect } from 'react';
import type { AppProps } from 'next/app';
import sdk from '@posif/sdk';

export default function App({ Component, pageProps }: AppProps) {
  useEffect(() => {
    const params = sdk.getParams();
    
    // Handle URL parameters
    if (params.token) {
      // Set up authentication
    }
    
    if (params.mode) {
      // Configure app mode
    }
  }, []);

  return <Component {...pageProps} />;
}
```

## S3 Object Storage

The SDK provides server-side storage functions for S3-compatible object storage with presigned URLs. **All storage functions are SERVER-ONLY** and must be used in API routes, as they require server-side execution to securely fetch S3 credentials.

### ⚠️ Important: Server-Side Only

**Storage functions cannot be called directly from client components.** They use `getStorage()` internally, which has `assertServerOnly()` protection.

**Required Pattern:**
1. ✅ Create API routes that use storage functions (server-side)
2. ✅ Call those API routes from client components
3. ✅ Client receives presigned URLs and uploads directly to S3

**This will NOT work:**
```typescript
'use client';

// ❌ ERROR: This will throw "Function can only be used on the server"
import { uploadFile } from '@posif/sdk/storage';

const key = await uploadFile(file); // ❌ Throws error in browser
```

**This is the correct way:**
```typescript
// ✅ Step 1: API Route (server-side)
// app/api/upload/route.ts
import { generateUploadUrl } from '@posif/sdk';

export async function POST(req: NextRequest) {
  const { filename } = await req.json();
  const { key, uploadUrl } = await generateUploadUrl(filename, req);
  return NextResponse.json({ key, uploadUrl });
}

// ✅ Step 2: Client Component
'use client';

const response = await fetch('/api/upload', {
  method: 'POST',
  body: JSON.stringify({ filename: file.name })
});
const { key, uploadUrl } = await response.json();

// ✅ Step 3: Upload to S3
await fetch(uploadUrl, {
  method: 'PUT',
  body: file,
  headers: { 'Content-Type': file.type }
});
```

### ✨ Key Features

- 🔒 **Server-Side Security**: S3 credentials never exposed to browser
- 🔑 **Automatic Authentication**: JWT token extraction from requests
- 👥 **User/Group Isolation**: Files automatically organized by user and group
- ⚡ **Connection Caching**: Optimized credential fetching and reuse
- 📝 **Presigned URLs**: Secure, temporary URLs for S3 operations
- 💪 **Type Safe**: Full TypeScript support

### Architecture Pattern

```
Client Component (Browser)
    ↓ fetch('/api/upload')
API Route (Server)
    ↓ generateUploadUrl()
SDK → getStorage() → Fetch S3 credentials from Posif API
    ↓
Generate Presigned URL
    ↓
Return to Client
    ↓
Client uploads directly to S3 using presigned URL
```

### Quick Start - File Upload

**Step 1: Create Upload URL API Route** (`app/api/upload/route.ts`)

```typescript
import { NextRequest, NextResponse } from 'next/server';
import { generateUploadUrl } from '@posif/sdk';

export async function POST(req: NextRequest) {
  try {
    const { filename, contentType, isPrivate } = await req.json();
    
    // Token automatically extracted from request (?token=xxx)
    const { key, uploadUrl } = await generateUploadUrl(filename, req, undefined, {
      contentType,
      isPrivate: isPrivate !== false, // Default to private
    });
    
    return NextResponse.json({ key, uploadUrl });
  } catch (error: any) {
    return NextResponse.json(
      { error: error.message },
      { status: 500 }
    );
  }
}
```

**Step 2: Client Component**

```typescript
'use client';

import { useState } from 'react';

export default function FileUploader() {
  const [file, setFile] = useState<File | null>(null);
  const [uploading, setUploading] = useState(false);
  const [progress, setProgress] = useState(0);

  const handleUpload = async () => {
    if (!file) return;
    setUploading(true);

    try {
      // Step 1: Get upload URL from API route
      const response = await fetch('/api/upload', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({
          filename: file.name,
          contentType: file.type,
          isPrivate: true,
        }),
      });

      const { key, uploadUrl } = await response.json();

      // Step 2: Upload directly to S3 with progress tracking
      const xhr = new XMLHttpRequest();
      
      xhr.upload.addEventListener('progress', (e) => {
        if (e.lengthComputable) {
          setProgress(Math.round((e.loaded / e.total) * 100));
        }
      });

      await new Promise<void>((resolve, reject) => {
        xhr.addEventListener('load', () => {
          if (xhr.status >= 200 && xhr.status < 300) {
            resolve();
          } else {
            reject(new Error(`Upload failed: ${xhr.status}`));
          }
        });
        xhr.addEventListener('error', () => reject(new Error('Upload failed')));
        
        xhr.open('PUT', uploadUrl);
        xhr.setRequestHeader('Content-Type', file.type);
        xhr.send(file);
      });

      alert('Upload successful! Key: ' + key);
    } catch (error: any) {
      alert('Upload failed: ' + error.message);
    } finally {
      setUploading(false);
      setProgress(0);
    }
  };

  return (
    <div>
      <input
        type="file"
        onChange={(e) => setFile(e.target.files?.[0] || null)}
        disabled={uploading}
      />
      <button onClick={handleUpload} disabled={!file || uploading}>
        {uploading ? `Uploading ${progress}%...` : 'Upload'}
      </button>
    </div>
  );
}
```

### Storage API Functions (All Server-Side Only)

All these functions **must be used in API routes**:

#### `uploadFile(file, req?, token?, options?)`

Complete server-side upload (use in API routes that accept FormData).

```typescript
// app/api/upload-direct/route.ts
import { NextRequest, NextResponse } from 'next/server';
import { uploadFile } from '@posif/sdk';

export async function POST(req: NextRequest) {
  const formData = await req.formData();
  const file = formData.get('file') as File;
  
  if (!file) {
    return NextResponse.json({ error: 'No file provided' }, { status: 400 });
  }
  
  try {
    // Upload file to S3 (all handled server-side)
    const key = await uploadFile(file, req, undefined, {
      isPrivate: true,
      onProgress: (percent) => {
        console.log(`Upload progress: ${percent}%`);
      },
    });
    
    return NextResponse.json({ key });
  } catch (error: any) {
    return NextResponse.json({ error: error.message }, { status: 500 });
  }
}
```

**Client-side usage:**

```typescript
// Upload file via FormData
const formData = new FormData();
formData.append('file', file);

const response = await fetch('/api/upload-direct', {
  method: 'POST',
  body: formData,
});

const { key } = await response.json();
```

#### `generateUploadUrl(filename, req?, token?, options?)`

Generate presigned upload URL (use in API routes).

```typescript
// app/api/upload/route.ts
import { NextRequest, NextResponse } from 'next/server';
import { generateUploadUrl } from '@posif/sdk';

export async function POST(req: NextRequest) {
  const { filename } = await req.json();
  
  // Token auto-extracted from request URL (?token=xxx)
  const { key, uploadUrl } = await generateUploadUrl(filename, req, undefined, {
    isPrivate: true,
    expiresIn: 3600, // 1 hour
  });
  
  return NextResponse.json({ key, uploadUrl });
}
```

#### `generateFileUrls(key, req?, token?, isPrivate?)`

Generate view and download URLs (use in API routes).

```typescript
// app/api/files/[key]/urls/route.ts
import { NextRequest, NextResponse } from 'next/server';
import { generateFileUrls } from '@posif/sdk';

export async function GET(
  req: NextRequest,
  { params }: { params: { key: string } }
) {
  const { viewUrl, downloadUrl } = await generateFileUrls(
    params.key,
    req,
    undefined,
    true // isPrivate
  );
  
  return NextResponse.json({ viewUrl, downloadUrl });
}
```

**Client-side usage:**

```typescript
const response = await fetch(`/api/files/${key}/urls`);
const { viewUrl, downloadUrl } = await response.json();

// Display image
<img src={viewUrl} alt="File" />
```

#### `generateDeleteUrl(key, req?, token?, isPrivate?)`

Generate delete URL (use in API routes).

```typescript
// app/api/files/[key]/delete-url/route.ts
import { NextRequest, NextResponse } from 'next/server';
import { generateDeleteUrl } from '@posif/sdk';

export async function GET(
  req: NextRequest,
  { params }: { params: { key: string } }
) {
  const deleteUrl = await generateDeleteUrl(params.key, req);
  return NextResponse.json({ deleteUrl });
}
```

**Client-side usage:**

```typescript
const response = await fetch(`/api/files/${key}/delete-url`);
const { deleteUrl } = await response.json();
await fetch(deleteUrl, { method: 'DELETE' });
```

#### `generateBatchUploadUrls(files, req?, token?, options?)`

Generate multiple upload URLs at once (use in API routes).

```typescript
// app/api/upload/batch/route.ts
import { NextRequest, NextResponse } from 'next/server';
import { generateBatchUploadUrls } from '@posif/sdk';

export async function POST(req: NextRequest) {
  const { files } = await req.json();
  
  const results = await generateBatchUploadUrls(files, req, undefined, {
    isPrivate: true,
  });
  
  return NextResponse.json({ results });
}
```

**Client-side usage:**

```typescript
const files = [
  { filename: 'photo1.jpg', contentType: 'image/jpeg' },
  { filename: 'photo2.png', contentType: 'image/png' }
];

const response = await fetch('/api/upload/batch', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({ files })
});

const { results } = await response.json();
// results: [{ key, uploadUrl, id }, ...]
```

#### `generatePublicUrl(key, req?, token?)`

Get direct public URL for files in **public buckets** (use in API routes).

```typescript
// app/api/files/[key]/public-url/route.ts
import { NextRequest, NextResponse } from 'next/server';
import { generatePublicUrl } from '@posif/sdk';

export async function GET(
  req: NextRequest,
  { params }: { params: { key: string } }
) {
  const publicUrl = await generatePublicUrl(params.key, req);
  return NextResponse.json({ publicUrl });
}
```

**Benefits of Public URLs:**
- ✅ **No Expiration**: URL never expires (unlike presigned URLs)
- ✅ **No Token Needed**: After storing, use URL directly without authentication
- ✅ **Database Friendly**: Perfect for storing in databases
- ✅ **CDN Compatible**: Can be cached by CDNs

**Use for:** Profile pictures, product images, public content  
**Don't use for:** Private documents, sensitive data

### Complete Example - File Manager with API Routes

**API Routes:**

```typescript
// app/api/files/upload-url/route.ts
import { NextRequest, NextResponse } from 'next/server';
import { generateUploadUrl } from '@posif/sdk';

export async function POST(req: NextRequest) {
  const { filename, isPrivate } = await req.json();
  const { key, uploadUrl } = await generateUploadUrl(filename, req, undefined, {
    isPrivate: isPrivate !== false,
  });
  return NextResponse.json({ key, uploadUrl });
}
```

```typescript
// app/api/files/[key]/urls/route.ts
import { NextRequest, NextResponse } from 'next/server';
import { generateFileUrls } from '@posif/sdk';

export async function GET(
  req: NextRequest,
  { params }: { params: { key: string } }
) {
  const { viewUrl, downloadUrl } = await generateFileUrls(params.key, req);
  return NextResponse.json({ viewUrl, downloadUrl });
}
```

```typescript
// app/api/files/[key]/delete-url/route.ts
import { NextRequest, NextResponse } from 'next/server';
import { generateDeleteUrl } from '@posif/sdk';

export async function GET(
  req: NextRequest,
  { params }: { params: { key: string } }
) {
  const deleteUrl = await generateDeleteUrl(params.key, req);
  return NextResponse.json({ deleteUrl });
}
```

**Client Component:**

```typescript
'use client';

import { useState } from 'react';

interface FileItem {
  key: string;
  filename: string;
  viewUrl?: string;
}

export default function FileManager() {
  const [files, setFiles] = useState<FileItem[]>([]);

  const handleUpload = async (e: React.ChangeEvent<HTMLInputElement>) => {
    const file = e.target.files?.[0];
    if (!file) return;

    try {
      // Step 1: Get upload URL from API
      const uploadResponse = await fetch('/api/files/upload-url', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ filename: file.name, isPrivate: true }),
      });
      const { key, uploadUrl } = await uploadResponse.json();

      // Step 2: Upload to S3
      await fetch(uploadUrl, {
        method: 'PUT',
        body: file,
        headers: { 'Content-Type': file.type },
      });

      // Step 3: Get view URL
      const urlsResponse = await fetch(`/api/files/${key}/urls`);
      const { viewUrl } = await urlsResponse.json();

      // Step 4: Add to list
      setFiles((prev) => [...prev, { key, filename: file.name, viewUrl }]);
    } catch (error: any) {
      alert('Upload failed: ' + error.message);
    }
  };

  const handleDelete = async (key: string) => {
    try {
      // Get delete URL
      const response = await fetch(`/api/files/${key}/delete-url`);
      const { deleteUrl } = await response.json();

      // Delete from S3
      await fetch(deleteUrl, { method: 'DELETE' });

      // Remove from list
      setFiles((prev) => prev.filter((f) => f.key !== key));
    } catch (error: any) {
      alert('Delete failed: ' + error.message);
    }
  };

  return (
    <div>
      <input type="file" onChange={handleUpload} />

      <div>
        {files.map((file) => (
          <div key={file.key}>
            {file.viewUrl && (
              <img src={file.viewUrl} alt={file.filename} width="100" />
            )}
            <span>{file.filename}</span>
            <button onClick={() => handleDelete(file.key)}>Delete</button>
          </div>
        ))}
      </div>
    </div>
  );
}
```

### Advanced: Using getStorage() Directly

For maximum control in API routes:

```typescript
import { NextRequest, NextResponse } from 'next/server';
import { getStorage } from '@posif/sdk';

export async function POST(req: NextRequest) {
  // Get storage instance (server-side only)
  const storage = await getStorage(req);
  
  // Generate key
  const key = await storage.generateKey('photo.jpg', true);
  
  // Generate presigned URL
  const uploadUrl = await storage.getPresignedUrl(key, 'upload', true);
  
  // Batch operations
  const files = [
    { filename: 'photo1.jpg', contentType: 'image/jpeg' },
    { filename: 'photo2.png', contentType: 'image/png' },
  ];
  const batchResults = await storage.getBatchUploadUrls(files, true);
  
  return NextResponse.json({ key, uploadUrl, batchResults });
}
```

### Why API Routes are Required

**Security & Architecture:**

1. 🔒 **Credential Protection**: S3 credentials must never be exposed to the browser
2. 🔑 **Token Validation**: Server validates JWT tokens before fetching credentials
3. 👥 **Access Control**: Server enforces user/group file isolation
4. 🛡️ **Rate Limiting**: Server can implement upload limits
5. ⚡ **Performance**: Credentials are cached server-side

**The storage functions use `assertServerOnly()` which throws an error if called from client-side code.**

### Package Exports Clarification

⚠️ **Important Note about `/storage` export:**

The `/storage` export (`@posif/sdk/storage`) is for **bundle optimization** in API routes. It excludes MongoDB dependencies to reduce bundle size, but the storage functions **still require server-side execution**.

```typescript
// ✅ Correct: Use in API routes to avoid MongoDB in bundle
// app/api/upload/route.ts
import { generateUploadUrl } from '@posif/sdk/storage';

export async function POST(req: NextRequest) {
  const { key, uploadUrl } = await generateUploadUrl(filename, req);
  return NextResponse.json({ key, uploadUrl });
}
```

```typescript
// ❌ Incorrect: This will throw error
'use client';
import { uploadFile } from '@posif/sdk/storage';

// ❌ ERROR: "Function can only be used on the server"
const key = await uploadFile(file);
```

### Benefits

✅ **Secure**: S3 credentials never exposed to browser  
✅ **Simple API Routes**: Minimal boilerplate required  
✅ **Direct S3 Upload**: Files go straight to S3 (no proxy through your server)  
✅ **Progress Tracking**: Built-in progress callbacks (via XMLHttpRequest)  
✅ **Type Safe**: Full TypeScript support  
✅ **User/Group Isolation**: Automatic file organization  
✅ **Flexible**: Use high-level functions or `getStorage()` for control  
✅ **Optimized Bundles**: Use `/storage` export to exclude MongoDB from API routes

### What the SDK Handles

The SDK completely manages (server-side):
- 🔐 S3 credential fetching and caching
- 🔑 JWT token extraction and validation
- 📝 Presigned URL generation
- 👥 User/group file isolation
- ♻️ Connection pooling and reuse
- ⚠️ Error handling and retries

### What You Build

You need to create:
- 🛜️ **API Routes**: Server endpoints that call SDK functions
- 🎨 **UI Components**: Upload buttons, galleries, progress bars
- 💾 **Database Logic**: Store S3 keys and metadata (optional)
- 🎯 **Business Logic**: Validation, permissions, etc.

For more examples including batch uploads and custom progress bars, see `examples/storage-examples.ts`




## License

MIT

## Contributing

Contributions are welcome! Please feel free to submit a Pull Request.