npm package: @pixengine/adapter-storage-local
Description: Local filesystem storage adapter for PixEngine
Author: PixEngine Team
Latest version: 0.3.0

# @pixengine/adapter-storage-local

**English** | [한국어](README.ko.md)

Local filesystem storage adapter for PixEngine.

## Installation

```bash
npm install @pixengine/adapter-storage-local
# or
pnpm add @pixengine/adapter-storage-local
# or
yarn add @pixengine/adapter-storage-local
```

## Usage

```typescript
import { optimize } from '@pixengine/core';
import { SharpEngine } from '@pixengine/adapter-engine-sharp';
import { LocalStorage } from '@pixengine/adapter-storage-local';

const manifest = await optimize({
  input: {
    filename: 'photo.jpg',
    bytes: imageBuffer,
    contentType: 'image/jpeg',
  },
  policy: (ctx) => ({
    variants: [
      { width: 400, format: 'webp', quality: 80 },
      { width: 800, format: 'webp', quality: 85 },
    ],
  }),
  engine: new SharpEngine(),
  storage: new LocalStorage({ // ✨ Save to local disk
    baseDir: './public/uploads',
    baseUrl: 'https://example.com/uploads',
  }),
});

console.log(manifest.variants[0].url);
// 'https://example.com/uploads/variants/photo_400w.webp'
```

## Features

- 💾 **Local Filesystem**: Save images directly to disk
- 📁 **Automatic Directory Creation**: Creates nested directories as needed
- 🔗 **URL Generation**: Generates public URLs for stored images
- ⚡ **Simple & Fast**: No external dependencies or services required

## API

### `LocalStorage`

Implements the `StorageAdapter` interface from `@pixengine/core`.

#### Constructor

```typescript
new LocalStorage(config: {
  baseDir: string;
  baseUrl: string;
})
```

**Parameters:**

- `baseDir: string` - Root directory for file storage
  - Example: `'./public/uploads'`
  - Example: `'/var/www/static/images'`
- `baseUrl: string` - Base URL for accessing stored files
  - Example: `'https://example.com/uploads'`
  - Example: `'http://localhost:3000/static/images'`

#### Methods

##### `put(args)`

Save an image to the local filesystem.

```typescript
const result = await storage.put({
  key: 'variants/photo_800w.webp',
  bytes: imageBytes,
  contentType: 'image/webp',
  meta: {
    width: 800,
    height: 600,
    format: 'webp',
  },
});

console.log(result);
// { url: 'https://example.com/uploads/variants/photo_800w.webp' }
```

**Parameters:**

- `key: string` - File path relative to `baseDir`
- `bytes: Uint8Array` - Image data
- `contentType: string` - MIME type
- `meta` - Image metadata (for future use)

**Returns:** `Promise<{ url: string }>`

## File Organization

LocalStorage organizes files automatically:

```
baseDir/
├── original/
│   └── photo.jpg          # Original images
└── variants/
    ├── photo_400w.webp    # Generated variants
    └── photo_800w.webp
```

## Examples

### Express.js Integration

```typescript
import express from 'express';
import { optimize } from '@pixengine/core';
import { SharpEngine } from '@pixengine/adapter-engine-sharp';
import { LocalStorage } from '@pixengine/adapter-storage-local';
import multer from 'multer';

const app = express();
const upload = multer();

app.post('/upload', upload.single('image'), async (req, res) => {
  const manifest = await optimize({
    input: {
      filename: req.file.originalname,
      bytes: new Uint8Array(req.file.buffer),
      contentType: req.file.mimetype,
    },
    policy: (ctx) => ({
      variants: [
        { width: 400, format: 'webp', quality: 80 },
        { width: 800, format: 'webp', quality: 85 },
      ],
    }),
    engine: new SharpEngine(),
    storage: new LocalStorage({
      baseDir: './public/uploads',
      baseUrl: `${req.protocol}://${req.get('host')}/uploads`,
    }),
  });

  res.json(manifest);
});

// Serve static files
app.use('/uploads', express.static('./public/uploads'));

app.listen(3000);
```

### Next.js Integration

```typescript
// app/api/upload/route.ts
import { optimize } from '@pixengine/core';
import { SharpEngine } from '@pixengine/adapter-engine-sharp';
import { LocalStorage } from '@pixengine/adapter-storage-local';

export async function POST(request: Request) {
  const formData = await request.formData();
  const file = formData.get('image') as File;
  const bytes = new Uint8Array(await file.arrayBuffer());

  const manifest = await optimize({
    input: {
      filename: file.name,
      bytes,
      contentType: file.type,
    },
    policy: (ctx) => ({
      variants: [
        { width: 400, format: 'webp', quality: 80 },
        { width: 800, format: 'webp', quality: 85 },
      ],
    }),
    engine: new SharpEngine(),
    storage: new LocalStorage({
      baseDir: './public/uploads',
      baseUrl: '/uploads',
    }),
  });

  return Response.json(manifest);
}
```

Then configure `next.config.js` to serve static files:

```javascript
// next.config.js
module.exports = {
  // ... other config
};
```

## Production Considerations

### Security

- **Validate file paths**: Ensure `baseDir` is properly sandboxed
- **Limit file sizes**: Use upload size limits
- **Sanitize filenames**: Remove special characters

### Performance

- **Use CDN**: Serve files through a CDN for better performance
- **Set up caching**: Configure proper cache headers
- **Consider object storage**: For high-scale applications, consider S3-compatible storage

### File System

- **Disk space**: Monitor available disk space
- **Backup**: Regular backups of the storage directory
- **Permissions**: Ensure proper file/directory permissions

## When to Use

LocalStorage is ideal for:

- ✅ Development and testing
- ✅ Small to medium applications
- ✅ Single-server deployments
- ✅ Applications with predictable storage needs

Consider cloud storage (S3, etc.) for:

- ❌ High-scale applications
- ❌ Multi-server deployments
- ❌ Applications requiring CDN integration
- ❌ Distributed systems

## Requirements

- Node.js >= 18.0.0
- Write permissions for `baseDir`

## License

MIT © PixEngine Team

## Links

- [PixEngine Core](https://www.npmjs.com/package/@pixengine/core)
- [GitHub Repository](https://github.com/pixengine/pixengine)
- [Issue Tracker](https://github.com/pixengine/pixengine/issues)