first commit

This commit is contained in:
2026-05-29 08:20:56 +01:00
commit 949299f58d
6368 changed files with 527176 additions and 0 deletions

View File

@@ -0,0 +1,102 @@
# sharp-adapter
<!-- begin:auto-docs -->
_(no submodules)_
<!-- end:auto-docs -->
## Purpose
Provides a production-grade `ImageAdapter` implementation backed by the Sharp image processing library. Sharp delivers native-speed JPEG, PNG, WebP, and AVIF encoding through libvips, making it the recommended adapter for server-side workloads where pixel-accurate compression and real resizing are required.
Sharp is deliberately **not** a direct dependency of this package. The caller supplies a Sharp factory function at construction time, which eliminates the native-addon from the `@suaveplan/compress-image` dependency tree and allows the package to be used in environments where Sharp cannot be installed (e.g., edge runtimes, bundled frontends, or CI test runs).
## Features
- Full `ImageAdapter` implementation using the real Sharp pipeline
- JPEG progressive encoding, PNG compression levels, WebP lossless, AVIF effort tuning
- Metadata stripping by default; opt-in preservation via `stripMetadata: false`
- `ResizeMode` mapped to Sharp's `fit` option (cover / contain / fill / inside / outside)
- Caller-supplied Sharp factory for dependency injection
- Wraps all Sharp errors in `CompressImageError` with cause-chain forwarding
## Usage: Basic Example
```ts
import sharp from 'sharp';
import { SharpAdapter } from '@suaveplan/compress-image/adapters/sharp';
import { createCompressor } from '@suaveplan/compress-image/compressor';
const compressor = createCompressor(new SharpAdapter(sharp));
const result = await compressor.compress(imageBuffer, {
format: 'webp',
quality: 75,
width: 800,
height: 600,
});
```
## Usage: Advanced Example
```ts
import sharp from 'sharp';
import { SharpAdapter } from '@suaveplan/compress-image/adapters/sharp';
const adapter = new SharpAdapter(sharp);
// Get metadata
const info = await adapter.getInfo(jpegBuffer);
console.log(info.width, info.height, info.format, info.hasAlpha);
// Convert JPEG to AVIF with high compression effort
const result = await adapter.compress(jpegBuffer, {
format: 'avif',
quality: 60,
effort: 6,
stripMetadata: true,
});
// Resize to thumbnail keeping aspect ratio (contain mode)
const thumb = await adapter.resize(jpegBuffer, 128, 128, 'contain');
```
## API Reference
### `interface SharpLike`
A minimal factory interface matching the Sharp module export signature. Implement this to inject a mock in tests.
```ts
interface SharpLike {
(input: Buffer | Uint8Array): SharpInstance;
}
```
### `interface SharpInstance`
The fluent pipeline instance returned by `SharpLike`. Covers the methods used by `SharpAdapter`.
### `class SharpAdapter implements ImageAdapter`
#### `constructor(sharp: SharpLike)`
Stores the provided Sharp factory. No I/O occurs at construction time.
#### `compress(input: Uint8Array, options: CompressOptions): Promise<CompressResult>`
Runs the Sharp pipeline with format conversion, quality settings, and optional resize. Returns a `CompressResult` with real output bytes and actual dimensions. Throws `CompressImageError(UNSUPPORTED_FORMAT)` for undetectable inputs; throws `CompressImageError(COMPRESS_FAILED)` for all other Sharp errors.
#### `getInfo(input: Uint8Array): Promise<ImageInfo>`
Calls `sharp(input).metadata()` to extract width, height, format, and alpha channel information. Throws `CompressImageError(UNSUPPORTED_FORMAT)` if Sharp returns a format not in the known set.
#### `resize(input: Uint8Array, width: number, height: number, mode?: ResizeMode): Promise<Uint8Array>`
Resizes using `sharp.resize()` with the mapped `fit` option. Returns a `Uint8Array` wrapping the Sharp output `Buffer`. Default mode is `'cover'`.
## Implementation Notes
Format encoding options are mapped per-format: JPEG receives `quality` and `progressive`; WebP receives `quality`, `lossless`, and `effort`; AVIF receives `quality` and `effort`; PNG receives `compressionLevel` (mapped from `effort`) but no quality parameter since PNG is lossless.
A second `.metadata()` pass on the output buffer is used to derive the final `width` and `height` in `CompressResult`. This adds a small overhead but guarantees accuracy after resize operations where the actual output dimensions may differ from the requested values (e.g., when using `inside` or `outside` fit modes).
Sharp's `Buffer` is wrapped in `new Uint8Array(buffer)` to satisfy the `Uint8Array` contract in `CompressResult.data`, ensuring interoperability with environments that do not use Node.js `Buffer`.