@everystack/images
v0.2.0
Published
Image processing via Sharp — thumbnails, format conversion, EXIF, variant generation
Readme
@everystack/images
Image processing via Sharp — resize, format conversion, EXIF stripping, and variant generation. Designed for server-side use in Lambda or Node.js environments.
Install
pnpm add @everystack/images sharpEntry Points
| Import | Description |
|--------|-------------|
| @everystack/images | Processor, variants, presets |
| @everystack/images/jobs | Job queue payload types |
| @everystack/images/schema | Drizzle image metadata table |
Quick Start
import { createImageProcessor, generateVariants, RESPONSIVE_IMAGES } from '@everystack/images';
const processor = createImageProcessor();
// Process a single image
const result = await processor.process(imageBuffer, {
width: 800,
format: 'webp',
quality: 80,
});
// → { data: Buffer, width: 800, height: 600, format: 'webp', size: 45230 }
// Generate all variants
const variants = await generateVariants(imageBuffer, processor, RESPONSIVE_IMAGES);
// → Map<string, ProcessingResult> with keys: 'thumb', 'small', 'medium', 'large', 'thumb-jpeg'Image Processor
Create a processor instance and process images:
import { createImageProcessor } from '@everystack/images';
const processor = createImageProcessor();
// Resize
const resized = await processor.process(buffer, {
width: 400,
height: 300,
fit: 'cover', // 'cover' | 'contain' | 'fill' | 'inside' | 'outside'
});
// Format conversion
const webp = await processor.process(buffer, {
format: 'webp', // 'webp' | 'avif' | 'jpeg' | 'png'
quality: 85,
});
// Extract metadata
const metadata = await processor.extractMetadata(buffer);
// → { width: 3024, height: 4032, format: 'jpeg', hasAlpha: false, orientation: 6 }Processing Options
interface ProcessingOptions {
width?: number;
height?: number;
format?: 'webp' | 'avif' | 'jpeg' | 'png';
quality?: number; // 1-100, default: 80
fit?: 'cover' | 'contain' | 'fill' | 'inside' | 'outside';
stripExif?: boolean; // Default: true (auto-rotates then strips)
}Variant Presets
Built-in variant sets for common use cases:
RESPONSIVE_IMAGES
Generates thumbnails through large sizes in WebP (+ JPEG thumbnail fallback):
| Variant | Width | Format | Quality |
|---------|-------|--------|---------|
| thumb | 200 | webp | 80 |
| small | 400 | webp | 80 |
| medium | 800 | webp | 80 |
| large | 1200 | webp | 85 |
| thumb-jpeg | 200 | jpeg | 80 |
AVATAR_IMAGES
Square crops for profile pictures:
| Variant | Size | Format | Quality |
|---------|------|--------|---------|
| avatar-sm | 48x48 | webp | 80 |
| avatar-md | 96x96 | webp | 80 |
| avatar-lg | 256x256 | webp | 85 |
Custom Variants
import { generateVariants } from '@everystack/images';
import type { VariantSet } from '@everystack/images';
const PRODUCT_IMAGES: VariantSet = {
definitions: [
{ name: 'card', width: 300, height: 300, format: 'webp', quality: 80, fit: 'cover' },
{ name: 'detail', width: 1200, format: 'webp', quality: 90 },
{ name: 'zoom', width: 2400, format: 'jpeg', quality: 95 },
],
};
const variants = await generateVariants(imageBuffer, processor, PRODUCT_IMAGES);Variant Keys
Generate storage keys for variants:
import { variantKey } from '@everystack/images';
const key = variantKey('upload-123', { name: 'thumb', format: 'webp', width: 200 });
// → 'images/upload-123/thumb.webp'Job Queue Integration
Use with @everystack/jobs for background image processing:
import { createJobClient, PostgresJobAdapter } from '@everystack/jobs';
import { createImageProcessor, generateVariants, RESPONSIVE_IMAGES, variantKey } from '@everystack/images';
import { S3ObjectStorage } from '@everystack/storage';
const processor = createImageProcessor();
const storage = new S3ObjectStorage('my-bucket', 'us-east-1');
// Register the handler
const worker = createJobWorker(adapter, {
handlers: {
'process-image': async (payload: { key: string; uploadId: string }) => {
const file = await storage.get(payload.key);
if (!file) throw new Error('File not found');
const variants = await generateVariants(file.data, processor, RESPONSIVE_IMAGES);
for (const [name, result] of variants) {
const def = RESPONSIVE_IMAGES.definitions.find(d => d.name === name)!;
const key = variantKey(payload.uploadId, def);
await storage.put(key, result.data, `image/${result.format}`);
}
},
},
});Schema
Add the image metadata table to your Drizzle migrations:
import { imageMetadata } from '@everystack/images/schema';
// Include in your Drizzle schemaPeer Dependencies
| Package | Version | Required |
|---------|---------|----------|
| sharp | >=0.33.0 | Yes |
| @everystack/storage | >=0.1.0 | For storage integration |
| drizzle-orm | >=0.30.0 | For schema |
Part of everystack — a self-hosted application stack for Expo apps on AWS.
License
MIT
