@nomideusz/svelte-media 0.2.0 → 0.3.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/CHANGELOG.md +15 -0
- package/README.md +26 -0
- package/dist/core/media.d.ts +2 -2
- package/dist/core/process.d.ts +1 -1
- package/dist/core/process.js +14 -1
- package/dist/core/types.d.ts +35 -2
- package/dist/index.d.ts +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,20 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.3.0 — 2026-08-03
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
- **`derive` / `onDeriveError` on `MediaConfig`, and `derived` on `StoredMedia`.**
|
|
7
|
+
A hook over the decoded bytes during processing, so anything computed from the
|
|
8
|
+
image — placeholder, blurhash, dominant colour, EXIF — happens while the bytes
|
|
9
|
+
are in memory rather than fetching them back later. `TDerived` is inferred from
|
|
10
|
+
the callback, so the package takes no dependency on what you derive.
|
|
11
|
+
|
|
12
|
+
The hook runs after every size is stored, and failures deliberately do not
|
|
13
|
+
propagate: by then the upload has succeeded, and rejecting would make the
|
|
14
|
+
caller retry a completed upload and orphan the objects already written.
|
|
15
|
+
`derived` is left undefined and `onDeriveError` is called.
|
|
16
|
+
|
|
17
|
+
|
|
3
18
|
## 0.2.0 — 2026-08-03
|
|
4
19
|
|
|
5
20
|
### Changed
|
package/README.md
CHANGED
|
@@ -58,6 +58,32 @@ touches a database.
|
|
|
58
58
|
**Output is always WebP**, whatever was uploaded; `generateMediaKey` names every
|
|
59
59
|
file `<cuid2>.webp` and ignores the original extension.
|
|
60
60
|
|
|
61
|
+
## Deriving extras at upload time
|
|
62
|
+
|
|
63
|
+
`processAndStore` already has the decoded bytes in memory. A `derive` hook lets
|
|
64
|
+
you compute something from them in the same pass — a placeholder, a blurhash, a
|
|
65
|
+
dominant colour, EXIF — instead of fetching the image back later:
|
|
66
|
+
|
|
67
|
+
```ts
|
|
68
|
+
import { generatePlaceholder } from '@nomideusz/svelte-geometrize/node';
|
|
69
|
+
|
|
70
|
+
const stored = await processAndStore(storage, file, 'tours', tourId, {
|
|
71
|
+
derive: ({ buffer }) => generatePlaceholder(buffer),
|
|
72
|
+
onDeriveError: (err) => log.warn('placeholder failed', err),
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
stored.derived; // GeometrizePlaceholder | undefined — inferred from the hook
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
The hook runs **after** every size is stored, and **failures do not propagate**:
|
|
79
|
+
by that point the upload has succeeded, and rejecting would make the caller
|
|
80
|
+
retry a completed upload — orphaning the objects already written. A failing
|
|
81
|
+
hook leaves `derived` undefined and calls `onDeriveError`. Wire that to your
|
|
82
|
+
logger, or the failure is silent.
|
|
83
|
+
|
|
84
|
+
The package takes no dependency on whatever you derive; `TDerived` is inferred
|
|
85
|
+
from the callback's return type.
|
|
86
|
+
|
|
61
87
|
## Sizes
|
|
62
88
|
|
|
63
89
|
`processAndStore` always writes four variants:
|
package/dist/core/media.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { ImageSize,
|
|
1
|
+
import type { ImageSize, ValidationConfig, ValidationResult } from './types.js';
|
|
2
2
|
export declare const DEFAULT_MAX_SIZE: number;
|
|
3
3
|
export declare const DEFAULT_ALLOWED_TYPES: string[];
|
|
4
4
|
export declare const IMAGE_SIZES: {
|
|
@@ -20,6 +20,6 @@ export declare const IMAGE_SIZES: {
|
|
|
20
20
|
};
|
|
21
21
|
export declare const SIZE_PREFIXES: Record<ImageSize, string>;
|
|
22
22
|
export declare const SIZE_QUALITY: Record<ImageSize, number>;
|
|
23
|
-
export declare function validateImageFile(file: File, config?:
|
|
23
|
+
export declare function validateImageFile(file: File, config?: ValidationConfig): ValidationResult;
|
|
24
24
|
export declare function generateMediaKey(_originalName?: string): string;
|
|
25
25
|
export declare function getStorageKey(prefix: string, entityId: string, filename: string, size: ImageSize): string;
|
package/dist/core/process.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
import type { StorageAdapter, StoredMedia, ImageSize, MediaConfig } from './types.js';
|
|
2
|
-
export declare function processAndStore(adapter: StorageAdapter, file: File, prefix: string, entityId: string, config?: MediaConfig): Promise<StoredMedia
|
|
2
|
+
export declare function processAndStore<TDerived = never>(adapter: StorageAdapter, file: File, prefix: string, entityId: string, config?: MediaConfig<TDerived>): Promise<StoredMedia<TDerived>>;
|
|
3
3
|
export declare function deleteMedia(adapter: StorageAdapter, prefix: string, entityId: string, filename: string): Promise<void>;
|
|
4
4
|
export declare function getMediaUrl(adapter: StorageAdapter, prefix: string, entityId: string, filename: string, size?: ImageSize): string;
|
package/dist/core/process.js
CHANGED
|
@@ -29,7 +29,20 @@ export async function processAndStore(adapter, file, prefix, entityId, config) {
|
|
|
29
29
|
}
|
|
30
30
|
// ponytail: existing objects stay JPEG under their old keys — URLs derive
|
|
31
31
|
// from the stored filename, so old rows keep working without migration.
|
|
32
|
-
|
|
32
|
+
// Runs last, so a throwing hook cannot orphan a half-written upload — and it
|
|
33
|
+
// is caught, because by this point the image is stored and rejecting would
|
|
34
|
+
// make the caller retry a completed upload.
|
|
35
|
+
let derived;
|
|
36
|
+
if (config?.derive) {
|
|
37
|
+
const source = { buffer, filename, mimeType: file.type };
|
|
38
|
+
try {
|
|
39
|
+
derived = await config.derive(source);
|
|
40
|
+
}
|
|
41
|
+
catch (error) {
|
|
42
|
+
config.onDeriveError?.(error, source);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
return { filename, originalName: file.name, prefix, entityId, sizes, derived };
|
|
33
46
|
}
|
|
34
47
|
export async function deleteMedia(adapter, prefix, entityId, filename) {
|
|
35
48
|
const keys = ['original', 'thumbnail', 'medium', 'large'].map((size) => getStorageKey(prefix, entityId, filename, size));
|
package/dist/core/types.d.ts
CHANGED
|
@@ -17,7 +17,7 @@ export interface S3Config {
|
|
|
17
17
|
/** Use path-style access (default: true for MinIO/R2, set false for Railway/AWS) */
|
|
18
18
|
forcePathStyle?: boolean;
|
|
19
19
|
}
|
|
20
|
-
export interface StoredMedia {
|
|
20
|
+
export interface StoredMedia<TDerived = never> {
|
|
21
21
|
filename: string;
|
|
22
22
|
originalName: string;
|
|
23
23
|
/** Storage prefix, e.g. 'tours' or 'avatars' */
|
|
@@ -26,6 +26,11 @@ export interface StoredMedia {
|
|
|
26
26
|
entityId: string;
|
|
27
27
|
/** Storage keys for each size */
|
|
28
28
|
sizes: Record<ImageSize, string>;
|
|
29
|
+
/**
|
|
30
|
+
* Result of `config.derive`, when supplied. Undefined if no hook was given,
|
|
31
|
+
* or if it threw — see `MediaConfig.onDeriveError`.
|
|
32
|
+
*/
|
|
33
|
+
derived?: TDerived;
|
|
29
34
|
}
|
|
30
35
|
export interface MediaValidationError {
|
|
31
36
|
isValid: false;
|
|
@@ -41,7 +46,35 @@ export interface ValidationResult {
|
|
|
41
46
|
valid: boolean;
|
|
42
47
|
error?: string;
|
|
43
48
|
}
|
|
44
|
-
|
|
49
|
+
/** What a `derive` hook receives — the image as uploaded, before any resizing. */
|
|
50
|
+
export interface DeriveSource {
|
|
51
|
+
/** Original bytes, pre-resize, EXIF intact. */
|
|
52
|
+
buffer: Buffer;
|
|
53
|
+
/** The generated storage filename, e.g. `k7x2m9.webp`. */
|
|
54
|
+
filename: string;
|
|
55
|
+
/** The uploaded file's MIME type. */
|
|
56
|
+
mimeType: string;
|
|
57
|
+
}
|
|
58
|
+
/** The validation half of MediaConfig — all `validateImageFile` needs. */
|
|
59
|
+
export interface ValidationConfig {
|
|
45
60
|
maxFileSize?: number;
|
|
46
61
|
allowedTypes?: string[];
|
|
47
62
|
}
|
|
63
|
+
export interface MediaConfig<TDerived = never> extends ValidationConfig {
|
|
64
|
+
/**
|
|
65
|
+
* Optional side-computation over the image, run once during processing while
|
|
66
|
+
* the bytes are already in hand — a placeholder, a blurhash, a dominant
|
|
67
|
+
* colour, EXIF. Whatever it returns is attached to the result as `derived`.
|
|
68
|
+
*
|
|
69
|
+
* Runs after every size has been stored, so a failure cannot orphan a
|
|
70
|
+
* half-written upload. Failures do not propagate: the upload has already
|
|
71
|
+
* succeeded and a derived extra must never invalidate it. `derived` is left
|
|
72
|
+
* undefined and `onDeriveError` is called.
|
|
73
|
+
*/
|
|
74
|
+
derive?(source: DeriveSource): Promise<TDerived>;
|
|
75
|
+
/**
|
|
76
|
+
* Called when `derive` throws. The upload still succeeds. Without this the
|
|
77
|
+
* failure is silent, so wire it to your logger.
|
|
78
|
+
*/
|
|
79
|
+
onDeriveError?(error: unknown, source: DeriveSource): void;
|
|
80
|
+
}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export type { StorageAdapter, S3Config, StoredMedia, ImageSize, MediaConfig, ValidationResult, } from './core/types.js';
|
|
1
|
+
export type { StorageAdapter, S3Config, StoredMedia, ImageSize, MediaConfig, ValidationResult, ValidationConfig, DeriveSource, } from './core/types.js';
|
|
2
2
|
export { validateImageFile, generateMediaKey, getStorageKey, IMAGE_SIZES, DEFAULT_MAX_SIZE, DEFAULT_ALLOWED_TYPES, } from './core/media.js';
|
|
3
3
|
export { default as ImageUpload } from './components/ImageUpload.svelte';
|
|
4
4
|
export { default as ImageGallery } from './components/ImageGallery.svelte';
|