hazo_images 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGE_LOG.md +52 -0
- package/README.md +166 -0
- package/SETUP_CHECKLIST.md +83 -0
- package/config/hazo_images_config.ini.sample +53 -0
- package/dist/index.d.ts +79 -0
- package/dist/index.js +53 -0
- package/dist/server/index.d.ts +93 -0
- package/dist/server/index.js +227 -0
- package/dist/ui/index.d.ts +2 -0
- package/dist/ui/index.js +0 -0
- package/package.json +73 -0
package/CHANGE_LOG.md
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
# hazo_images Change Log
|
|
2
|
+
|
|
3
|
+
## 1.1.0 (2026-05-29) — Wave 2 standardisation
|
|
4
|
+
|
|
5
|
+
### Changed
|
|
6
|
+
- Error classes now extend `HazoError` subclasses from `hazo_core`:
|
|
7
|
+
- `ImageProcessingError` → extends `HazoExternalError` (`HAZO_IMAGES_PROCESSING_FAILED`, HTTP 502, retryable)
|
|
8
|
+
- `UnsupportedFormatError` → extends `HazoValidationError` (`HAZO_IMAGES_UNSUPPORTED_FORMAT`, HTTP 400)
|
|
9
|
+
- Existing class names + public API preserved — `instanceof` checks and `errorType` discriminators keep working.
|
|
10
|
+
- `processImage` and `uploadProcessedImage` emit structured `log.debug` events
|
|
11
|
+
(`process_image.started`, `process_image.completed`,
|
|
12
|
+
`upload_processed_image.completed`) via `hazo_core.createLogger`.
|
|
13
|
+
Correlation ID is inherited from the caller's AsyncLocalStorage context.
|
|
14
|
+
- `package.json`: `hazo_core@^1.0.0` added as a required peer dependency.
|
|
15
|
+
Engines block removed (workspace standard). `dist/` files list now
|
|
16
|
+
includes `config/`.
|
|
17
|
+
|
|
18
|
+
### Added
|
|
19
|
+
- `SharpMissingError` extends `HazoConfigError` (`HAZO_IMAGES_CONFIG_SHARP_MISSING`)
|
|
20
|
+
thrown when the optional `sharp` peer dep is not installed at first
|
|
21
|
+
`processImage` call. Replaces the previous bare `Error` throw.
|
|
22
|
+
- `ImageUploadError` extends `HazoExternalError` (`HAZO_IMAGES_UPLOAD_FAILED`)
|
|
23
|
+
thrown when a FileManager rejects a buffer upload during
|
|
24
|
+
`uploadProcessedImage`. Replaces the previous bare `Error` throws and
|
|
25
|
+
carries `{ virtualPath, fileManagerError }` in `context`.
|
|
26
|
+
- Workspace-standard INI config under `config/hazo_images_config.ini.sample`
|
|
27
|
+
with `[general]`, `[log.overrides]`, `[processing]` sections. Loader at
|
|
28
|
+
`src/lib/config/hazo_images_config.ts::getImagesConfig()` wraps
|
|
29
|
+
`hazo_core.loadConfig` with a Zod schema. Env-var overrides:
|
|
30
|
+
`HAZO_IMAGES_<SECTION>_<KEY>`.
|
|
31
|
+
- `src/utils/logger.ts` — `get_logger()` / `set_logger()` defaulting to
|
|
32
|
+
`createLogger('hazo_images')` from `hazo_core`.
|
|
33
|
+
- `src/utils/uuid.ts` — `generateUUID()` wrapping
|
|
34
|
+
`generateRequestId().slice(4)` so all IDs trace through `hazo_core`.
|
|
35
|
+
- `AGENTS.md` at package root + `src/` + `test-app/` (S13).
|
|
36
|
+
- `design/feature_requests/INDEX.md` (S16).
|
|
37
|
+
|
|
38
|
+
### Notes
|
|
39
|
+
- No outbound HTTP calls — `fetchWithRequestId` wiring not required.
|
|
40
|
+
- No database tables owned — no `db_setup_*.sql` files.
|
|
41
|
+
- `hazo_images` is in the "Doesn't adopt hazo_api" column per the
|
|
42
|
+
ecosystem map; no envelope/route wiring in src/.
|
|
43
|
+
|
|
44
|
+
## 1.0.0 (2026-05-21)
|
|
45
|
+
|
|
46
|
+
### Added
|
|
47
|
+
- Initial release: server-side image pipeline
|
|
48
|
+
- `processImage(buffer, opts)` — Sharp wrapper with lazy load, EXIF strip, auto-rotate, resize, thumbnail generation
|
|
49
|
+
- `uploadProcessedImage(fm, buffer, path, opts)` — orchestration helper composing with hazo_files FileManager
|
|
50
|
+
- Subpath exports: `hazo_images/server` (pipeline), `hazo_images/ui` (reserved for @1.1+)
|
|
51
|
+
- `ImageProcessingError` and `UnsupportedFormatError` error types
|
|
52
|
+
- Sharp is an optional peer dep — importing the package without Sharp installed does not crash
|
package/README.md
ADDED
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
# hazo_images
|
|
2
|
+
|
|
3
|
+
Server-side image processing pipeline for the hazo ecosystem. Sharp wrapper with EXIF handling, auto-rotate, resize, and thumbnail generation. Composes with `hazo_files` via an integration helper.
|
|
4
|
+
|
|
5
|
+
Image **storage** is owned by `hazo_files`. This package handles **processing only**.
|
|
6
|
+
|
|
7
|
+
## Install
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npm install hazo_images
|
|
11
|
+
|
|
12
|
+
# Required peer dep — provides errors, correlation IDs, logger, config loader
|
|
13
|
+
npm install hazo_core
|
|
14
|
+
|
|
15
|
+
# Required peer deps for image processing
|
|
16
|
+
npm install sharp
|
|
17
|
+
|
|
18
|
+
# Optional — only needed if using uploadProcessedImage
|
|
19
|
+
npm install hazo_files@^2.1.1
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
## Usage
|
|
23
|
+
|
|
24
|
+
### processImage — standalone pipeline primitive
|
|
25
|
+
|
|
26
|
+
```ts
|
|
27
|
+
import { processImage } from 'hazo_images/server';
|
|
28
|
+
|
|
29
|
+
const fs = await import('node:fs/promises');
|
|
30
|
+
const buffer = await fs.readFile('./photo.jpg');
|
|
31
|
+
|
|
32
|
+
const result = await processImage(buffer, {
|
|
33
|
+
stripExif: true, // default: true — privacy-safe
|
|
34
|
+
autoRotate: true, // default: true — corrects EXIF orientation
|
|
35
|
+
maxDimension: 4096, // default: 4096 — resize longest side, keep AR
|
|
36
|
+
webp: false, // default: false — preserve original format
|
|
37
|
+
thumbnails: [256, 512, 1024],
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
console.log(result.metadata);
|
|
41
|
+
// { width: 2400, height: 1600, format: 'jpeg' }
|
|
42
|
+
|
|
43
|
+
console.log(result.thumbnails.length);
|
|
44
|
+
// 3 — one per requested size
|
|
45
|
+
|
|
46
|
+
// result.buffer — processed main image as Buffer
|
|
47
|
+
// result.thumbnails[0] — { size: 256, buffer: Buffer, format: 'jpeg' }
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
### uploadProcessedImage — compose with hazo_files
|
|
51
|
+
|
|
52
|
+
```ts
|
|
53
|
+
import { uploadProcessedImage } from 'hazo_images/server';
|
|
54
|
+
import { createInitializedFileManager } from 'hazo_files/server';
|
|
55
|
+
|
|
56
|
+
const fm = await createInitializedFileManager({ config: { provider: 'local', local: { basePath: './uploads' } } });
|
|
57
|
+
const buffer = await fs.readFile('./photo.jpg');
|
|
58
|
+
|
|
59
|
+
const result = await uploadProcessedImage(fm, buffer, '/photos/wedding.jpg', {
|
|
60
|
+
thumbnails: [256, 512, 1024],
|
|
61
|
+
stripExif: true,
|
|
62
|
+
autoRotate: true,
|
|
63
|
+
maxDimension: 4096,
|
|
64
|
+
webp: false,
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
console.log(result.main); // FileItem — the processed main image
|
|
68
|
+
console.log(result.thumbs); // { 256: FileItem, 512: FileItem, 1024: FileItem }
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
Thumbnails are named `{basename}__thumb_{size}.{ext}` and stored in the same directory as the main file:
|
|
72
|
+
```
|
|
73
|
+
/photos/wedding.jpg
|
|
74
|
+
/photos/wedding__thumb_256.jpg
|
|
75
|
+
/photos/wedding__thumb_512.jpg
|
|
76
|
+
/photos/wedding__thumb_1024.jpg
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
### WebP transcoding
|
|
80
|
+
|
|
81
|
+
```ts
|
|
82
|
+
const result = await processImage(buffer, {
|
|
83
|
+
webp: true,
|
|
84
|
+
thumbnails: [256, 512],
|
|
85
|
+
});
|
|
86
|
+
// result.metadata.format === 'webp'
|
|
87
|
+
// result.thumbnails[0].format === 'webp'
|
|
88
|
+
// thumbnail extension in uploadProcessedImage: __thumb_256.webp
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
## Error Types
|
|
92
|
+
|
|
93
|
+
All errors extend `HazoError` subclasses from `hazo_core` (Wave 2). The
|
|
94
|
+
legacy class names are preserved so existing `instanceof` checks and
|
|
95
|
+
`errorType` discriminators keep working.
|
|
96
|
+
|
|
97
|
+
```ts
|
|
98
|
+
import {
|
|
99
|
+
ImageProcessingError,
|
|
100
|
+
ImageUploadError,
|
|
101
|
+
SharpMissingError,
|
|
102
|
+
UnsupportedFormatError,
|
|
103
|
+
} from 'hazo_images';
|
|
104
|
+
import { HazoError } from 'hazo_core';
|
|
105
|
+
|
|
106
|
+
try {
|
|
107
|
+
const result = await processImage(badBuffer);
|
|
108
|
+
} catch (err) {
|
|
109
|
+
if (err instanceof UnsupportedFormatError) {
|
|
110
|
+
// Buffer was not a recognized image format (e.g. PDF, text)
|
|
111
|
+
console.error('Not an image:', err.message);
|
|
112
|
+
} else if (err instanceof ImageProcessingError) {
|
|
113
|
+
// Sharp failed during processing
|
|
114
|
+
console.error('Processing failed:', err.message);
|
|
115
|
+
console.error('Original error:', err.originalError);
|
|
116
|
+
} else if (HazoError.is(err)) {
|
|
117
|
+
// Any other hazo_images error — read `err.code` to discriminate
|
|
118
|
+
console.error(err.code, err.message);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
### Error reference
|
|
124
|
+
|
|
125
|
+
| Class | Extends | Code | HTTP | When |
|
|
126
|
+
|---|---|---|---|---|
|
|
127
|
+
| `UnsupportedFormatError` | `HazoValidationError` | `HAZO_IMAGES_UNSUPPORTED_FORMAT` | 400 | Buffer is not a recognized image format |
|
|
128
|
+
| `ImageProcessingError` | `HazoExternalError` | `HAZO_IMAGES_PROCESSING_FAILED` | 502 | Sharp failed during processing or thumbnail generation |
|
|
129
|
+
| `SharpMissingError` | `HazoConfigError` | `HAZO_IMAGES_CONFIG_SHARP_MISSING` | n/a | Optional `sharp` peer dep is not installed |
|
|
130
|
+
| `ImageUploadError` | `HazoExternalError` | `HAZO_IMAGES_UPLOAD_FAILED` | 502 | Underlying `FileManager.uploadFile` returned `success: false` |
|
|
131
|
+
|
|
132
|
+
## Subpath exports
|
|
133
|
+
|
|
134
|
+
| Import | Contents |
|
|
135
|
+
|---|---|
|
|
136
|
+
| `hazo_images` | Shared types + error classes (isomorphic, no Node deps) |
|
|
137
|
+
| `hazo_images/server` | `processImage`, `uploadProcessedImage` (Node.js only) |
|
|
138
|
+
| `hazo_images/ui` | Reserved for `@1.1+` UI components (empty in `@1.0`) |
|
|
139
|
+
|
|
140
|
+
## Supported formats
|
|
141
|
+
|
|
142
|
+
Passes through whatever Sharp supports: JPEG, PNG, WebP, AVIF, TIFF, GIF (static), SVG (rasterized). HEIC requires the optional `sharp-heic` package installed separately.
|
|
143
|
+
|
|
144
|
+
## Options reference
|
|
145
|
+
|
|
146
|
+
```ts
|
|
147
|
+
interface ProcessImageOptions {
|
|
148
|
+
stripExif?: boolean; // default: true — strip all EXIF metadata
|
|
149
|
+
autoRotate?: boolean; // default: true — apply EXIF orientation, then strip tag
|
|
150
|
+
maxDimension?: number; // default: 4096 — longest side; 0 = disable resize
|
|
151
|
+
webp?: boolean; // default: false — transcode to WebP
|
|
152
|
+
thumbnails?: number[]; // default: [] — pixel sizes for longest side
|
|
153
|
+
}
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
## Roadmap
|
|
157
|
+
|
|
158
|
+
| Version | Scope |
|
|
159
|
+
|---|---|
|
|
160
|
+
| `@1.0` | Server pipeline: `processImage` + `uploadProcessedImage` |
|
|
161
|
+
| `@1.1+` | UI: redesigned AI-edit component, crop/rotate/filter primitives |
|
|
162
|
+
| `@1.2+` | Face detection via `face-api-node` (speculative, non-committed) |
|
|
163
|
+
|
|
164
|
+
## License
|
|
165
|
+
|
|
166
|
+
MIT
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
# hazo_images Setup Checklist
|
|
2
|
+
|
|
3
|
+
## 1. Install the package
|
|
4
|
+
|
|
5
|
+
```bash
|
|
6
|
+
npm install hazo_images
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
## 1b. Install hazo_core (required peer dep)
|
|
10
|
+
|
|
11
|
+
`hazo_core` provides the `HazoError` base class, correlation-ID
|
|
12
|
+
propagation, the structured logger factory, and the INI config loader
|
|
13
|
+
used by `hazo_images`. It is a required peer dep — install it once at
|
|
14
|
+
the consuming app's root.
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
npm install hazo_core
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## 2. Install Sharp (required for image processing)
|
|
21
|
+
|
|
22
|
+
Sharp is an optional peer dependency. It is required whenever `processImage` or `uploadProcessedImage` is called. If Sharp is not installed, an error is thrown at call time (not at import time).
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
npm install sharp
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
Sharp installs platform-specific native bindings (~10 MB). On some CI systems you may need `npm install --ignore-scripts sharp` followed by a rebuild step.
|
|
29
|
+
|
|
30
|
+
## 3. Install hazo_files (required for uploadProcessedImage)
|
|
31
|
+
|
|
32
|
+
Only needed if using the `uploadProcessedImage` helper. `processImage` works standalone without `hazo_files`.
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
npm install hazo_files@^2.1.1
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
## 4. Server-only imports
|
|
39
|
+
|
|
40
|
+
Always import from `hazo_images/server` in server-side code only. Do NOT import `hazo_images/server` in client-bundled code (components, browser entry points). It has Node.js dependencies.
|
|
41
|
+
|
|
42
|
+
```ts
|
|
43
|
+
// In API routes, server components, or Node.js scripts:
|
|
44
|
+
import { processImage, uploadProcessedImage } from 'hazo_images/server';
|
|
45
|
+
|
|
46
|
+
// Safe anywhere (types + errors, no Node deps):
|
|
47
|
+
import { ImageProcessingError, UnsupportedFormatError } from 'hazo_images';
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
## 5. Next.js apps — transpilePackages
|
|
51
|
+
|
|
52
|
+
Add `hazo_images` to `transpilePackages` in `next.config.js`:
|
|
53
|
+
|
|
54
|
+
```js
|
|
55
|
+
const nextConfig = {
|
|
56
|
+
transpilePackages: ['hazo_images'],
|
|
57
|
+
};
|
|
58
|
+
module.exports = nextConfig;
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
## 6. (Optional) Drop in `hazo_images_config.ini`
|
|
62
|
+
|
|
63
|
+
The package ships `config/hazo_images_config.ini.sample` with sections
|
|
64
|
+
for `[general]`, `[log.overrides]`, and `[processing]` defaults. Copy
|
|
65
|
+
to `<consuming-app>/config/hazo_images_config.ini` (or the package root)
|
|
66
|
+
and override what you need. Any unset key falls back to the
|
|
67
|
+
`processImage` defaults. Values may also be overridden at runtime via
|
|
68
|
+
`HAZO_IMAGES_<SECTION>_<KEY>` env vars.
|
|
69
|
+
|
|
70
|
+
Per `D-020`, a per-environment overlay file at
|
|
71
|
+
`config/hazo_images_config.<HAZO_ENV>.ini` is layered on top of the
|
|
72
|
+
base file when `HAZO_ENV` is set.
|
|
73
|
+
|
|
74
|
+
## 7. Verify
|
|
75
|
+
|
|
76
|
+
```ts
|
|
77
|
+
import { processImage } from 'hazo_images/server';
|
|
78
|
+
|
|
79
|
+
const buffer = Buffer.from('...'); // a valid JPEG buffer
|
|
80
|
+
const result = await processImage(buffer, { thumbnails: [256] });
|
|
81
|
+
console.log(result.metadata); // { width, height, format }
|
|
82
|
+
console.log(result.thumbnails.length); // 1
|
|
83
|
+
```
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
; hazo_images configuration
|
|
2
|
+
;
|
|
3
|
+
; Copy to `hazo_images_config.ini` and customise. Loaded by getImagesConfig()
|
|
4
|
+
; via hazo_core's loadConfig() on first access.
|
|
5
|
+
;
|
|
6
|
+
; Per hazo workspace standard S7, values may also be overridden via env vars:
|
|
7
|
+
; HAZO_IMAGES_<SECTION>_<KEY>=value
|
|
8
|
+
; e.g. HAZO_IMAGES_PROCESSING_MAX_DIMENSION=2048
|
|
9
|
+
;
|
|
10
|
+
; Per D-020, a per-environment overlay file at
|
|
11
|
+
; config/hazo_images_config.<HAZO_ENV>.ini
|
|
12
|
+
; is layered on top of this base file when HAZO_ENV is set.
|
|
13
|
+
|
|
14
|
+
; ============================================================================
|
|
15
|
+
; [general] - Workspace-standard general configuration
|
|
16
|
+
; ============================================================================
|
|
17
|
+
[general]
|
|
18
|
+
|
|
19
|
+
; Deployment environment label. Falls back to NODE_ENV, then 'development'.
|
|
20
|
+
; env = production
|
|
21
|
+
|
|
22
|
+
; ============================================================================
|
|
23
|
+
; [log.overrides] - Per-namespace log level overrides (workspace standard)
|
|
24
|
+
; Format: <namespace> = trace|debug|info|warn|error
|
|
25
|
+
; ============================================================================
|
|
26
|
+
[log.overrides]
|
|
27
|
+
|
|
28
|
+
; hazo_images = info
|
|
29
|
+
; hazo_images/processing = warn
|
|
30
|
+
|
|
31
|
+
; ============================================================================
|
|
32
|
+
; [processing] - Default processImage option overrides
|
|
33
|
+
; Each key here becomes the implicit default if the caller does not pass
|
|
34
|
+
; an explicit value to processImage(buffer, opts).
|
|
35
|
+
; ============================================================================
|
|
36
|
+
[processing]
|
|
37
|
+
|
|
38
|
+
; Strip EXIF metadata from output buffers. true | false (default: true)
|
|
39
|
+
; strip_exif = true
|
|
40
|
+
|
|
41
|
+
; Auto-rotate using EXIF Orientation tag. true | false (default: true)
|
|
42
|
+
; auto_rotate = true
|
|
43
|
+
|
|
44
|
+
; Resize to fit within max_dimension × max_dimension. 0 disables resize.
|
|
45
|
+
; Default: 4096
|
|
46
|
+
; max_dimension = 4096
|
|
47
|
+
|
|
48
|
+
; Re-encode to WebP. true | false (default: false)
|
|
49
|
+
; webp = false
|
|
50
|
+
|
|
51
|
+
; Comma-separated thumbnail sizes in pixels. Empty = no thumbnails.
|
|
52
|
+
; Default: (none)
|
|
53
|
+
; thumbnails = 256, 512, 1024
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { HazoExternalError, HazoConfigError, HazoValidationError } from 'hazo_core';
|
|
2
|
+
|
|
3
|
+
interface ProcessImageOptions {
|
|
4
|
+
stripExif?: boolean;
|
|
5
|
+
autoRotate?: boolean;
|
|
6
|
+
maxDimension?: number;
|
|
7
|
+
webp?: boolean;
|
|
8
|
+
thumbnails?: number[];
|
|
9
|
+
}
|
|
10
|
+
interface ProcessImageResult {
|
|
11
|
+
buffer: Buffer;
|
|
12
|
+
thumbnails: {
|
|
13
|
+
size: number;
|
|
14
|
+
buffer: Buffer;
|
|
15
|
+
format: string;
|
|
16
|
+
}[];
|
|
17
|
+
metadata: {
|
|
18
|
+
width: number;
|
|
19
|
+
height: number;
|
|
20
|
+
format: string;
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
interface UploadProcessedImageResult {
|
|
24
|
+
main: any;
|
|
25
|
+
thumbs: Record<number, any>;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* hazo_images error classes.
|
|
30
|
+
*
|
|
31
|
+
* Per Wave 2 / D-008: all errors extend a HazoError subclass from hazo_core.
|
|
32
|
+
* Code namespace: HAZO_IMAGES_*.
|
|
33
|
+
*
|
|
34
|
+
* The legacy class names (`ImageProcessingError`, `UnsupportedFormatError`)
|
|
35
|
+
* are preserved for backward compatibility — existing test-app scenarios
|
|
36
|
+
* and the @1.0 public API still discriminate by `errorType`/`error.name`.
|
|
37
|
+
*/
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Sharp pipeline failure (encode/decode/resize error from the image library).
|
|
41
|
+
* Maps to HazoExternalError because the underlying failure originates outside
|
|
42
|
+
* our code path (Sharp / libvips). HTTP status 502, retryable=true.
|
|
43
|
+
*
|
|
44
|
+
* Code: HAZO_IMAGES_PROCESSING_FAILED
|
|
45
|
+
*/
|
|
46
|
+
declare class ImageProcessingError extends HazoExternalError {
|
|
47
|
+
readonly originalError: Error;
|
|
48
|
+
constructor(originalError: Error, message?: string);
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Caller passed a buffer Sharp couldn't decode (PDF, corrupt JPEG, etc).
|
|
52
|
+
* Maps to HazoValidationError because it's a bad-input problem on the
|
|
53
|
+
* caller's side. HTTP status 400.
|
|
54
|
+
*
|
|
55
|
+
* Code: HAZO_IMAGES_UNSUPPORTED_FORMAT
|
|
56
|
+
*/
|
|
57
|
+
declare class UnsupportedFormatError extends HazoValidationError {
|
|
58
|
+
constructor(message?: string);
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Optional peer `sharp` is not installed in the consuming app. Maps to
|
|
62
|
+
* HazoConfigError because the install/setup is misconfigured; there is no
|
|
63
|
+
* HTTP status — startup-time concern. Code: HAZO_IMAGES_CONFIG_SHARP_MISSING.
|
|
64
|
+
*/
|
|
65
|
+
declare class SharpMissingError extends HazoConfigError {
|
|
66
|
+
constructor();
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Underlying FileManager (hazo_files) rejected a buffer upload during
|
|
70
|
+
* uploadProcessedImage. HazoExternalError because the failure is in the
|
|
71
|
+
* downstream storage adapter, not in our code.
|
|
72
|
+
*
|
|
73
|
+
* Code: HAZO_IMAGES_UPLOAD_FAILED
|
|
74
|
+
*/
|
|
75
|
+
declare class ImageUploadError extends HazoExternalError {
|
|
76
|
+
constructor(message: string, context?: Record<string, unknown>);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export { ImageProcessingError, ImageUploadError, type ProcessImageOptions, type ProcessImageResult, SharpMissingError, UnsupportedFormatError, type UploadProcessedImageResult };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
// src/errors.ts
|
|
2
|
+
import {
|
|
3
|
+
HazoConfigError,
|
|
4
|
+
HazoExternalError,
|
|
5
|
+
HazoValidationError
|
|
6
|
+
} from "hazo_core";
|
|
7
|
+
var ImageProcessingError = class extends HazoExternalError {
|
|
8
|
+
originalError;
|
|
9
|
+
constructor(originalError, message) {
|
|
10
|
+
super({
|
|
11
|
+
code: "HAZO_IMAGES_PROCESSING_FAILED",
|
|
12
|
+
pkg: "hazo_images",
|
|
13
|
+
message: message ?? originalError.message,
|
|
14
|
+
cause: originalError,
|
|
15
|
+
context: { originalErrorName: originalError.name }
|
|
16
|
+
});
|
|
17
|
+
this.originalError = originalError;
|
|
18
|
+
}
|
|
19
|
+
};
|
|
20
|
+
var UnsupportedFormatError = class extends HazoValidationError {
|
|
21
|
+
constructor(message = "Buffer is not a recognized image format") {
|
|
22
|
+
super({
|
|
23
|
+
code: "HAZO_IMAGES_UNSUPPORTED_FORMAT",
|
|
24
|
+
pkg: "hazo_images",
|
|
25
|
+
message
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
};
|
|
29
|
+
var SharpMissingError = class extends HazoConfigError {
|
|
30
|
+
constructor() {
|
|
31
|
+
super({
|
|
32
|
+
code: "HAZO_IMAGES_CONFIG_SHARP_MISSING",
|
|
33
|
+
pkg: "hazo_images",
|
|
34
|
+
message: "[hazo_images] Sharp is not installed. Run: npm install sharp\nSharp is an optional peer dependency required for image processing."
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
};
|
|
38
|
+
var ImageUploadError = class extends HazoExternalError {
|
|
39
|
+
constructor(message, context) {
|
|
40
|
+
super({
|
|
41
|
+
code: "HAZO_IMAGES_UPLOAD_FAILED",
|
|
42
|
+
pkg: "hazo_images",
|
|
43
|
+
message,
|
|
44
|
+
context
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
};
|
|
48
|
+
export {
|
|
49
|
+
ImageProcessingError,
|
|
50
|
+
ImageUploadError,
|
|
51
|
+
SharpMissingError,
|
|
52
|
+
UnsupportedFormatError
|
|
53
|
+
};
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import { HazoExternalError, HazoConfigError, HazoValidationError } from 'hazo_core';
|
|
2
|
+
|
|
3
|
+
interface ProcessImageOptions {
|
|
4
|
+
stripExif?: boolean;
|
|
5
|
+
autoRotate?: boolean;
|
|
6
|
+
maxDimension?: number;
|
|
7
|
+
webp?: boolean;
|
|
8
|
+
thumbnails?: number[];
|
|
9
|
+
}
|
|
10
|
+
interface ProcessImageResult {
|
|
11
|
+
buffer: Buffer;
|
|
12
|
+
thumbnails: {
|
|
13
|
+
size: number;
|
|
14
|
+
buffer: Buffer;
|
|
15
|
+
format: string;
|
|
16
|
+
}[];
|
|
17
|
+
metadata: {
|
|
18
|
+
width: number;
|
|
19
|
+
height: number;
|
|
20
|
+
format: string;
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
interface UploadProcessedImageResult {
|
|
24
|
+
main: any;
|
|
25
|
+
thumbs: Record<number, any>;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
declare function processImage(buffer: Buffer, opts?: ProcessImageOptions): Promise<ProcessImageResult>;
|
|
29
|
+
|
|
30
|
+
interface FileManager {
|
|
31
|
+
uploadFile(source: Buffer | string, remotePath: string, options?: Record<string, unknown>): Promise<{
|
|
32
|
+
success: boolean;
|
|
33
|
+
data?: unknown;
|
|
34
|
+
error?: string;
|
|
35
|
+
}>;
|
|
36
|
+
}
|
|
37
|
+
interface UploadOptions {
|
|
38
|
+
actor_id?: string;
|
|
39
|
+
}
|
|
40
|
+
declare function uploadProcessedImage(fm: FileManager, buffer: Buffer, virtualPath: string, opts?: ProcessImageOptions & UploadOptions): Promise<UploadProcessedImageResult>;
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* hazo_images error classes.
|
|
44
|
+
*
|
|
45
|
+
* Per Wave 2 / D-008: all errors extend a HazoError subclass from hazo_core.
|
|
46
|
+
* Code namespace: HAZO_IMAGES_*.
|
|
47
|
+
*
|
|
48
|
+
* The legacy class names (`ImageProcessingError`, `UnsupportedFormatError`)
|
|
49
|
+
* are preserved for backward compatibility — existing test-app scenarios
|
|
50
|
+
* and the @1.0 public API still discriminate by `errorType`/`error.name`.
|
|
51
|
+
*/
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Sharp pipeline failure (encode/decode/resize error from the image library).
|
|
55
|
+
* Maps to HazoExternalError because the underlying failure originates outside
|
|
56
|
+
* our code path (Sharp / libvips). HTTP status 502, retryable=true.
|
|
57
|
+
*
|
|
58
|
+
* Code: HAZO_IMAGES_PROCESSING_FAILED
|
|
59
|
+
*/
|
|
60
|
+
declare class ImageProcessingError extends HazoExternalError {
|
|
61
|
+
readonly originalError: Error;
|
|
62
|
+
constructor(originalError: Error, message?: string);
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Caller passed a buffer Sharp couldn't decode (PDF, corrupt JPEG, etc).
|
|
66
|
+
* Maps to HazoValidationError because it's a bad-input problem on the
|
|
67
|
+
* caller's side. HTTP status 400.
|
|
68
|
+
*
|
|
69
|
+
* Code: HAZO_IMAGES_UNSUPPORTED_FORMAT
|
|
70
|
+
*/
|
|
71
|
+
declare class UnsupportedFormatError extends HazoValidationError {
|
|
72
|
+
constructor(message?: string);
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Optional peer `sharp` is not installed in the consuming app. Maps to
|
|
76
|
+
* HazoConfigError because the install/setup is misconfigured; there is no
|
|
77
|
+
* HTTP status — startup-time concern. Code: HAZO_IMAGES_CONFIG_SHARP_MISSING.
|
|
78
|
+
*/
|
|
79
|
+
declare class SharpMissingError extends HazoConfigError {
|
|
80
|
+
constructor();
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Underlying FileManager (hazo_files) rejected a buffer upload during
|
|
84
|
+
* uploadProcessedImage. HazoExternalError because the failure is in the
|
|
85
|
+
* downstream storage adapter, not in our code.
|
|
86
|
+
*
|
|
87
|
+
* Code: HAZO_IMAGES_UPLOAD_FAILED
|
|
88
|
+
*/
|
|
89
|
+
declare class ImageUploadError extends HazoExternalError {
|
|
90
|
+
constructor(message: string, context?: Record<string, unknown>);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
export { ImageProcessingError, ImageUploadError, type ProcessImageOptions, type ProcessImageResult, SharpMissingError, UnsupportedFormatError, type UploadProcessedImageResult, processImage, uploadProcessedImage };
|
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
// src/errors.ts
|
|
2
|
+
import {
|
|
3
|
+
HazoConfigError,
|
|
4
|
+
HazoExternalError,
|
|
5
|
+
HazoValidationError
|
|
6
|
+
} from "hazo_core";
|
|
7
|
+
var ImageProcessingError = class extends HazoExternalError {
|
|
8
|
+
originalError;
|
|
9
|
+
constructor(originalError, message) {
|
|
10
|
+
super({
|
|
11
|
+
code: "HAZO_IMAGES_PROCESSING_FAILED",
|
|
12
|
+
pkg: "hazo_images",
|
|
13
|
+
message: message ?? originalError.message,
|
|
14
|
+
cause: originalError,
|
|
15
|
+
context: { originalErrorName: originalError.name }
|
|
16
|
+
});
|
|
17
|
+
this.originalError = originalError;
|
|
18
|
+
}
|
|
19
|
+
};
|
|
20
|
+
var UnsupportedFormatError = class extends HazoValidationError {
|
|
21
|
+
constructor(message = "Buffer is not a recognized image format") {
|
|
22
|
+
super({
|
|
23
|
+
code: "HAZO_IMAGES_UNSUPPORTED_FORMAT",
|
|
24
|
+
pkg: "hazo_images",
|
|
25
|
+
message
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
};
|
|
29
|
+
var SharpMissingError = class extends HazoConfigError {
|
|
30
|
+
constructor() {
|
|
31
|
+
super({
|
|
32
|
+
code: "HAZO_IMAGES_CONFIG_SHARP_MISSING",
|
|
33
|
+
pkg: "hazo_images",
|
|
34
|
+
message: "[hazo_images] Sharp is not installed. Run: npm install sharp\nSharp is an optional peer dependency required for image processing."
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
};
|
|
38
|
+
var ImageUploadError = class extends HazoExternalError {
|
|
39
|
+
constructor(message, context) {
|
|
40
|
+
super({
|
|
41
|
+
code: "HAZO_IMAGES_UPLOAD_FAILED",
|
|
42
|
+
pkg: "hazo_images",
|
|
43
|
+
message,
|
|
44
|
+
context
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
// src/utils/logger.ts
|
|
50
|
+
import { createLogger } from "hazo_core";
|
|
51
|
+
var console_logger = {
|
|
52
|
+
info: (m, d) => d ? console.log(`[hazo_images] ${m}`, d) : console.log(`[hazo_images] ${m}`),
|
|
53
|
+
debug: (m, d) => d ? console.debug(`[hazo_images] ${m}`, d) : console.debug(`[hazo_images] ${m}`),
|
|
54
|
+
warn: (m, d) => d ? console.warn(`[hazo_images] ${m}`, d) : console.warn(`[hazo_images] ${m}`),
|
|
55
|
+
error: (m, d) => d ? console.error(`[hazo_images] ${m}`, d) : console.error(`[hazo_images] ${m}`)
|
|
56
|
+
};
|
|
57
|
+
function build_default_logger() {
|
|
58
|
+
try {
|
|
59
|
+
return createLogger("hazo_images");
|
|
60
|
+
} catch {
|
|
61
|
+
return console_logger;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
var current_logger = null;
|
|
65
|
+
function get_logger() {
|
|
66
|
+
if (!current_logger) {
|
|
67
|
+
current_logger = build_default_logger();
|
|
68
|
+
}
|
|
69
|
+
return current_logger;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// src/server/process-image.ts
|
|
73
|
+
var sharpPromise = null;
|
|
74
|
+
async function getSharp() {
|
|
75
|
+
if (!sharpPromise) {
|
|
76
|
+
sharpPromise = import("sharp").then((m) => m.default ?? m).catch(() => {
|
|
77
|
+
sharpPromise = null;
|
|
78
|
+
throw new SharpMissingError();
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
return sharpPromise;
|
|
82
|
+
}
|
|
83
|
+
async function processImage(buffer, opts = {}) {
|
|
84
|
+
const {
|
|
85
|
+
stripExif = true,
|
|
86
|
+
autoRotate = true,
|
|
87
|
+
maxDimension = 4096,
|
|
88
|
+
webp = false,
|
|
89
|
+
thumbnails = []
|
|
90
|
+
} = opts;
|
|
91
|
+
const log = get_logger();
|
|
92
|
+
const sharpFn = await getSharp();
|
|
93
|
+
log.debug("process_image.started", {
|
|
94
|
+
inputBytes: buffer.byteLength,
|
|
95
|
+
webp,
|
|
96
|
+
maxDimension,
|
|
97
|
+
thumbnailCount: thumbnails.length
|
|
98
|
+
});
|
|
99
|
+
let inputMetadata;
|
|
100
|
+
try {
|
|
101
|
+
inputMetadata = await sharpFn(buffer).metadata();
|
|
102
|
+
} catch (err) {
|
|
103
|
+
throw new UnsupportedFormatError();
|
|
104
|
+
}
|
|
105
|
+
if (!inputMetadata.format) {
|
|
106
|
+
throw new UnsupportedFormatError();
|
|
107
|
+
}
|
|
108
|
+
let pipeline = sharpFn(buffer);
|
|
109
|
+
if (autoRotate) {
|
|
110
|
+
pipeline = pipeline.autoOrient();
|
|
111
|
+
}
|
|
112
|
+
if (!stripExif) {
|
|
113
|
+
pipeline = pipeline.withMetadata();
|
|
114
|
+
}
|
|
115
|
+
if (maxDimension > 0) {
|
|
116
|
+
pipeline = pipeline.resize({
|
|
117
|
+
width: maxDimension,
|
|
118
|
+
height: maxDimension,
|
|
119
|
+
fit: "inside",
|
|
120
|
+
withoutEnlargement: true
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
let mainBuffer;
|
|
124
|
+
let outputMetadata;
|
|
125
|
+
try {
|
|
126
|
+
if (webp) {
|
|
127
|
+
const { data, info } = await pipeline.webp().toBuffer({ resolveWithObject: true });
|
|
128
|
+
mainBuffer = data;
|
|
129
|
+
outputMetadata = { width: info.width, height: info.height, format: "webp" };
|
|
130
|
+
} else {
|
|
131
|
+
const { data, info } = await pipeline.toBuffer({ resolveWithObject: true });
|
|
132
|
+
mainBuffer = data;
|
|
133
|
+
outputMetadata = { width: info.width, height: info.height, format: info.format };
|
|
134
|
+
}
|
|
135
|
+
} catch (err) {
|
|
136
|
+
throw new ImageProcessingError(err);
|
|
137
|
+
}
|
|
138
|
+
const thumbResults = [];
|
|
139
|
+
for (const size of thumbnails) {
|
|
140
|
+
try {
|
|
141
|
+
const thumbPipeline = sharpFn(mainBuffer).resize({
|
|
142
|
+
width: size,
|
|
143
|
+
height: size,
|
|
144
|
+
fit: "inside",
|
|
145
|
+
withoutEnlargement: true
|
|
146
|
+
});
|
|
147
|
+
let thumbBuffer;
|
|
148
|
+
let thumbFormat;
|
|
149
|
+
if (webp) {
|
|
150
|
+
const { data } = await thumbPipeline.webp().toBuffer({ resolveWithObject: true });
|
|
151
|
+
thumbBuffer = data;
|
|
152
|
+
thumbFormat = "webp";
|
|
153
|
+
} else {
|
|
154
|
+
const { data, info } = await thumbPipeline.toBuffer({ resolveWithObject: true });
|
|
155
|
+
thumbBuffer = data;
|
|
156
|
+
thumbFormat = info.format;
|
|
157
|
+
}
|
|
158
|
+
thumbResults.push({ size, buffer: thumbBuffer, format: thumbFormat });
|
|
159
|
+
} catch (err) {
|
|
160
|
+
throw new ImageProcessingError(
|
|
161
|
+
err,
|
|
162
|
+
`Thumbnail generation failed for size ${size}`
|
|
163
|
+
);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
log.debug("process_image.completed", {
|
|
167
|
+
outputBytes: mainBuffer.byteLength,
|
|
168
|
+
outputFormat: outputMetadata.format,
|
|
169
|
+
outputWidth: outputMetadata.width,
|
|
170
|
+
outputHeight: outputMetadata.height,
|
|
171
|
+
thumbnailsGenerated: thumbResults.length
|
|
172
|
+
});
|
|
173
|
+
return {
|
|
174
|
+
buffer: mainBuffer,
|
|
175
|
+
thumbnails: thumbResults,
|
|
176
|
+
metadata: outputMetadata
|
|
177
|
+
};
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// src/server/upload-processed-image.ts
|
|
181
|
+
import path from "path";
|
|
182
|
+
async function uploadProcessedImage(fm, buffer, virtualPath, opts) {
|
|
183
|
+
const { actor_id, ...processOpts } = opts ?? {};
|
|
184
|
+
const log = get_logger();
|
|
185
|
+
const result = await processImage(buffer, processOpts);
|
|
186
|
+
const uploadOpts = actor_id !== void 0 ? { actor_id } : void 0;
|
|
187
|
+
const mainResult = await fm.uploadFile(result.buffer, virtualPath, uploadOpts);
|
|
188
|
+
if (!mainResult.success) {
|
|
189
|
+
throw new ImageUploadError(
|
|
190
|
+
`Failed to upload main image: ${mainResult.error}`,
|
|
191
|
+
{ virtualPath, fileManagerError: mainResult.error }
|
|
192
|
+
);
|
|
193
|
+
}
|
|
194
|
+
const thumbs = {};
|
|
195
|
+
const ext = path.extname(virtualPath);
|
|
196
|
+
const basename = path.basename(virtualPath, ext);
|
|
197
|
+
const dir = path.dirname(virtualPath);
|
|
198
|
+
const prefix = dir === "/" ? "" : dir;
|
|
199
|
+
for (const thumb of result.thumbnails) {
|
|
200
|
+
const thumbExt = thumb.format === "webp" ? ".webp" : ext;
|
|
201
|
+
const thumbPath = `${prefix}/${basename}__thumb_${thumb.size}${thumbExt}`;
|
|
202
|
+
const thumbResult = await fm.uploadFile(thumb.buffer, thumbPath, uploadOpts);
|
|
203
|
+
if (!thumbResult.success) {
|
|
204
|
+
throw new ImageUploadError(
|
|
205
|
+
`Failed to upload thumbnail ${thumb.size}px: ${thumbResult.error}`,
|
|
206
|
+
{ virtualPath: thumbPath, thumbSize: thumb.size, fileManagerError: thumbResult.error }
|
|
207
|
+
);
|
|
208
|
+
}
|
|
209
|
+
thumbs[thumb.size] = thumbResult.data;
|
|
210
|
+
}
|
|
211
|
+
log.debug("upload_processed_image.completed", {
|
|
212
|
+
virtualPath,
|
|
213
|
+
thumbnailCount: result.thumbnails.length
|
|
214
|
+
});
|
|
215
|
+
return {
|
|
216
|
+
main: mainResult.data,
|
|
217
|
+
thumbs
|
|
218
|
+
};
|
|
219
|
+
}
|
|
220
|
+
export {
|
|
221
|
+
ImageProcessingError,
|
|
222
|
+
ImageUploadError,
|
|
223
|
+
SharpMissingError,
|
|
224
|
+
UnsupportedFormatError,
|
|
225
|
+
processImage,
|
|
226
|
+
uploadProcessedImage
|
|
227
|
+
};
|
package/dist/ui/index.js
ADDED
|
File without changes
|
package/package.json
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "hazo_images",
|
|
3
|
+
"version": "1.1.0",
|
|
4
|
+
"description": "Image processing pipeline for the hazo ecosystem — Sharp wrapper, thumbnail generation, EXIF handling, and hazo_files integration helper",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"main": "./dist/index.js",
|
|
8
|
+
"module": "./dist/index.js",
|
|
9
|
+
"types": "./dist/index.d.ts",
|
|
10
|
+
"exports": {
|
|
11
|
+
".": {
|
|
12
|
+
"types": "./dist/index.d.ts",
|
|
13
|
+
"import": "./dist/index.js",
|
|
14
|
+
"default": "./dist/index.js"
|
|
15
|
+
},
|
|
16
|
+
"./server": {
|
|
17
|
+
"types": "./dist/server/index.d.ts",
|
|
18
|
+
"import": "./dist/server/index.js",
|
|
19
|
+
"default": "./dist/server/index.js"
|
|
20
|
+
},
|
|
21
|
+
"./ui": {
|
|
22
|
+
"types": "./dist/ui/index.d.ts",
|
|
23
|
+
"import": "./dist/ui/index.js",
|
|
24
|
+
"default": "./dist/ui/index.js"
|
|
25
|
+
}
|
|
26
|
+
},
|
|
27
|
+
"files": [
|
|
28
|
+
"dist",
|
|
29
|
+
"config",
|
|
30
|
+
"README.md",
|
|
31
|
+
"SETUP_CHECKLIST.md",
|
|
32
|
+
"CHANGE_LOG.md"
|
|
33
|
+
],
|
|
34
|
+
"scripts": {
|
|
35
|
+
"build": "tsup",
|
|
36
|
+
"dev": "tsup --watch",
|
|
37
|
+
"typecheck": "tsc --noEmit",
|
|
38
|
+
"lint": "tsc --noEmit"
|
|
39
|
+
},
|
|
40
|
+
"dependencies": {
|
|
41
|
+
"ini": "^4.1.0",
|
|
42
|
+
"zod": "^3.23.8"
|
|
43
|
+
},
|
|
44
|
+
"peerDependencies": {
|
|
45
|
+
"hazo_core": "^1.0.0",
|
|
46
|
+
"hazo_files": "^3.0.0",
|
|
47
|
+
"react": "^18.0.0 || ^19.0.0",
|
|
48
|
+
"react-dom": "^18.0.0 || ^19.0.0",
|
|
49
|
+
"sharp": "^0.33.0"
|
|
50
|
+
},
|
|
51
|
+
"peerDependenciesMeta": {
|
|
52
|
+
"hazo_files": {
|
|
53
|
+
"optional": true
|
|
54
|
+
},
|
|
55
|
+
"react": {
|
|
56
|
+
"optional": true
|
|
57
|
+
},
|
|
58
|
+
"react-dom": {
|
|
59
|
+
"optional": true
|
|
60
|
+
},
|
|
61
|
+
"sharp": {
|
|
62
|
+
"optional": true
|
|
63
|
+
}
|
|
64
|
+
},
|
|
65
|
+
"devDependencies": {
|
|
66
|
+
"@types/node": "^22.10.0",
|
|
67
|
+
"@types/react": "^19.0.0",
|
|
68
|
+
"hazo_core": "^1.0.0",
|
|
69
|
+
"tsup": "^8.0.0",
|
|
70
|
+
"typescript": "^5.7.2"
|
|
71
|
+
},
|
|
72
|
+
"sideEffects": false
|
|
73
|
+
}
|