@nomideusz/svelte-media 0.1.0 → 0.2.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 +32 -2
- package/README.md +21 -12
- package/dist/core/media.d.ts +25 -0
- package/dist/core/media.js +43 -0
- package/dist/core/process.d.ts +1 -21
- package/dist/core/process.js +4 -40
- package/dist/index.d.ts +1 -4
- package/dist/index.js +4 -3
- package/dist/server/index.d.ts +6 -0
- package/dist/server/index.js +6 -0
- package/package.json +7 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,12 +1,42 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.2.0 — 2026-08-03
|
|
4
|
+
|
|
5
|
+
### Changed
|
|
6
|
+
- **Breaking: the package is split into two entry points.** Storage adapters,
|
|
7
|
+
`processAndStore`, `deleteMedia` and `getMediaUrl` move to
|
|
8
|
+
`@nomideusz/svelte-media/server`; components, `validateImageFile`, the key
|
|
9
|
+
helpers, `IMAGE_SIZES` and the types stay on the root import.
|
|
10
|
+
|
|
11
|
+
Everything was previously behind one entry, and `core/process.ts` imported
|
|
12
|
+
`sharp` while `core/local-adapter.ts` imported `node:fs` and `node:path` at
|
|
13
|
+
module level. Any browser bundle that touched the package pulled those in and
|
|
14
|
+
failed to build — which means `ImageUpload` and `ImageGallery`, the two
|
|
15
|
+
exports that exist to be used from a component, could not be. The bug went
|
|
16
|
+
unnoticed because both apps in the source monorepo import only from server
|
|
17
|
+
files.
|
|
18
|
+
|
|
19
|
+
Migration: append `/server` to imports of the adapters or the pipeline. Key
|
|
20
|
+
helpers are re-exported from `/server` too, so a server file still needs one
|
|
21
|
+
import.
|
|
22
|
+
|
|
23
|
+
### Added
|
|
24
|
+
- A real demo at https://svelte-media-gamma.vercel.app/ — the components running
|
|
25
|
+
live, `validateImageFile` against files you choose, and the storage-key layout
|
|
26
|
+
for a prefix and entity you type. The server pipeline is shown as code, since
|
|
27
|
+
it cannot run in a browser.
|
|
28
|
+
|
|
29
|
+
|
|
3
30
|
Backfilled 2026-08-02 from git history. Entries before that date are
|
|
4
31
|
reconstructed from commits, so they record what changed rather than a release
|
|
5
|
-
that was tagged at the time.
|
|
32
|
+
that was tagged at the time. 0.1.0 was published manually — trusted publishing
|
|
33
|
+
cannot bootstrap a package that does not exist yet — and releases from 0.1.1 on
|
|
34
|
+
go through the repo's Release & Publish workflow.
|
|
6
35
|
|
|
7
|
-
##
|
|
36
|
+
## 0.1.1 — 2026-08-03
|
|
8
37
|
|
|
9
38
|
### Added
|
|
39
|
+
- Demo site at https://svelte-media-gamma.vercel.app/; `homepage` points at it.
|
|
10
40
|
- README — the package's first, documenting the adapter seam, the four sizes,
|
|
11
41
|
the `(prefix, entityId, filename)` key shape the helpers take, and the fact
|
|
12
42
|
that all output is WebP regardless of what was uploaded.
|
package/README.md
CHANGED
|
@@ -10,9 +10,18 @@ pluggable storage adapter — S3-compatible or local disk.
|
|
|
10
10
|
pnpm add @nomideusz/svelte-media
|
|
11
11
|
```
|
|
12
12
|
|
|
13
|
-
> Requires Svelte 5 (`^5.0.0`).
|
|
14
|
-
|
|
15
|
-
|
|
13
|
+
> Requires Svelte 5 (`^5.0.0`).
|
|
14
|
+
|
|
15
|
+
The package has two entry points, because half of it cannot run in a browser:
|
|
16
|
+
|
|
17
|
+
| Import | Contains | Where |
|
|
18
|
+
|---|---|---|
|
|
19
|
+
| `@nomideusz/svelte-media` | components, `validateImageFile`, key helpers, `IMAGE_SIZES`, types | anywhere |
|
|
20
|
+
| `@nomideusz/svelte-media/server` | storage adapters, `processAndStore`, `deleteMedia`, `getMediaUrl` | server only |
|
|
21
|
+
|
|
22
|
+
The pipeline needs `sharp`, `node:fs` and `Buffer`. Keeping it behind `/server`
|
|
23
|
+
is what lets a component import `ImageUpload` without a bundler dragging those
|
|
24
|
+
into the browser build.
|
|
16
25
|
|
|
17
26
|
## Why
|
|
18
27
|
|
|
@@ -24,7 +33,7 @@ the seam.
|
|
|
24
33
|
## Quick Start
|
|
25
34
|
|
|
26
35
|
```ts
|
|
27
|
-
import { createS3Adapter, processAndStore, getMediaUrl } from '@nomideusz/svelte-media';
|
|
36
|
+
import { createS3Adapter, processAndStore, getMediaUrl } from '@nomideusz/svelte-media/server';
|
|
28
37
|
|
|
29
38
|
const storage = createS3Adapter({
|
|
30
39
|
endpoint: process.env.S3_ENDPOINT!, // https://xxx.r2.cloudflarestorage.com
|
|
@@ -79,7 +88,7 @@ interface StorageAdapter {
|
|
|
79
88
|
Two ship with the package:
|
|
80
89
|
|
|
81
90
|
```ts
|
|
82
|
-
import { createS3Adapter, createLocalAdapter } from '@nomideusz/svelte-media';
|
|
91
|
+
import { createS3Adapter, createLocalAdapter } from '@nomideusz/svelte-media/server';
|
|
83
92
|
|
|
84
93
|
createS3Adapter({ /* S3Config, above */ }); // R2, MinIO, Tigris, AWS
|
|
85
94
|
createLocalAdapter({ root: '/data/images' }); // writes under root, mkdir -p
|
|
@@ -109,7 +118,7 @@ is only needed to reject early and report a friendlier message.
|
|
|
109
118
|
## Deleting
|
|
110
119
|
|
|
111
120
|
```ts
|
|
112
|
-
import { deleteMedia } from '@nomideusz/svelte-media';
|
|
121
|
+
import { deleteMedia } from '@nomideusz/svelte-media/server';
|
|
113
122
|
|
|
114
123
|
// Removes all four sizes; individual failures are swallowed
|
|
115
124
|
await deleteMedia(storage, stored.prefix, stored.entityId, stored.filename);
|
|
@@ -152,23 +161,23 @@ the same reason: it renders on the client, where the adapter cannot go.
|
|
|
152
161
|
## API
|
|
153
162
|
|
|
154
163
|
```ts
|
|
155
|
-
//
|
|
164
|
+
// ── @nomideusz/svelte-media/server ──
|
|
156
165
|
createS3Adapter(config: S3Config): StorageAdapter
|
|
157
166
|
createLocalAdapter(config: LocalConfig): StorageAdapter
|
|
158
|
-
|
|
159
|
-
// Pipeline
|
|
160
167
|
processAndStore(adapter, file, prefix, entityId, config?): Promise<StoredMedia>
|
|
161
168
|
deleteMedia(adapter, prefix, entityId, filename): Promise<void>
|
|
162
169
|
getMediaUrl(adapter, prefix, entityId, filename, size = 'medium'): string
|
|
163
|
-
|
|
170
|
+
|
|
171
|
+
// ── @nomideusz/svelte-media (client-safe) ──
|
|
164
172
|
validateImageFile(file, config?): ValidationResult
|
|
165
173
|
generateMediaKey(): string // `<cuid2>.webp`
|
|
174
|
+
getStorageKey(prefix, entityId, filename, size): string // `${prefix}/${entityId}/${sizePrefix}${filename}`
|
|
166
175
|
IMAGE_SIZES
|
|
167
|
-
|
|
168
|
-
// Components
|
|
169
176
|
ImageUpload, ImageGallery
|
|
170
177
|
```
|
|
171
178
|
|
|
179
|
+
Key helpers are exported from both entries, so server code needs only one import.
|
|
180
|
+
|
|
172
181
|
Types: `StorageAdapter`, `S3Config`, `LocalConfig`, `StoredMedia`, `ImageSize`,
|
|
173
182
|
`MediaConfig`, `ValidationResult`.
|
|
174
183
|
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { ImageSize, MediaConfig, ValidationResult } from './types.js';
|
|
2
|
+
export declare const DEFAULT_MAX_SIZE: number;
|
|
3
|
+
export declare const DEFAULT_ALLOWED_TYPES: string[];
|
|
4
|
+
export declare const IMAGE_SIZES: {
|
|
5
|
+
thumbnail: {
|
|
6
|
+
width: number;
|
|
7
|
+
height: number;
|
|
8
|
+
fit: "cover";
|
|
9
|
+
};
|
|
10
|
+
medium: {
|
|
11
|
+
width: number;
|
|
12
|
+
height: number;
|
|
13
|
+
fit: "inside";
|
|
14
|
+
};
|
|
15
|
+
large: {
|
|
16
|
+
width: number;
|
|
17
|
+
height: number;
|
|
18
|
+
fit: "inside";
|
|
19
|
+
};
|
|
20
|
+
};
|
|
21
|
+
export declare const SIZE_PREFIXES: Record<ImageSize, string>;
|
|
22
|
+
export declare const SIZE_QUALITY: Record<ImageSize, number>;
|
|
23
|
+
export declare function validateImageFile(file: File, config?: MediaConfig): ValidationResult;
|
|
24
|
+
export declare function generateMediaKey(_originalName?: string): string;
|
|
25
|
+
export declare function getStorageKey(prefix: string, entityId: string, filename: string, size: ImageSize): string;
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
// Client-safe half of the package: validation, naming and key layout.
|
|
2
|
+
// Deliberately free of sharp, node:fs and Buffer so components and browser code
|
|
3
|
+
// can import it — see core/process.ts for the server pipeline.
|
|
4
|
+
import { createId } from '@paralleldrive/cuid2';
|
|
5
|
+
export const DEFAULT_MAX_SIZE = 5 * 1024 * 1024;
|
|
6
|
+
export const DEFAULT_ALLOWED_TYPES = ['image/jpeg', 'image/jpg', 'image/png', 'image/webp'];
|
|
7
|
+
export const IMAGE_SIZES = {
|
|
8
|
+
thumbnail: { width: 300, height: 300, fit: 'cover' },
|
|
9
|
+
medium: { width: 800, height: 600, fit: 'inside' },
|
|
10
|
+
large: { width: 1200, height: 900, fit: 'inside' },
|
|
11
|
+
};
|
|
12
|
+
export const SIZE_PREFIXES = {
|
|
13
|
+
original: '',
|
|
14
|
+
thumbnail: 'thumb_',
|
|
15
|
+
medium: 'med_',
|
|
16
|
+
large: 'large_',
|
|
17
|
+
};
|
|
18
|
+
export const SIZE_QUALITY = {
|
|
19
|
+
original: 95,
|
|
20
|
+
thumbnail: 80,
|
|
21
|
+
medium: 85,
|
|
22
|
+
large: 90,
|
|
23
|
+
};
|
|
24
|
+
export function validateImageFile(file, config) {
|
|
25
|
+
const maxSize = config?.maxFileSize ?? DEFAULT_MAX_SIZE;
|
|
26
|
+
const allowed = config?.allowedTypes ?? DEFAULT_ALLOWED_TYPES;
|
|
27
|
+
if (file.size > maxSize) {
|
|
28
|
+
const mb = Math.round(maxSize / 1024 / 1024);
|
|
29
|
+
return { valid: false, error: `File too large (max ${mb}MB)` };
|
|
30
|
+
}
|
|
31
|
+
if (!allowed.includes(file.type)) {
|
|
32
|
+
return { valid: false, error: `Invalid file type. Allowed: JPEG, PNG, WebP` };
|
|
33
|
+
}
|
|
34
|
+
return { valid: true };
|
|
35
|
+
}
|
|
36
|
+
export function generateMediaKey(_originalName) {
|
|
37
|
+
// All processed output is WebP regardless of the uploaded format.
|
|
38
|
+
return `${createId()}.webp`;
|
|
39
|
+
}
|
|
40
|
+
export function getStorageKey(prefix, entityId, filename, size) {
|
|
41
|
+
const sizePrefix = SIZE_PREFIXES[size];
|
|
42
|
+
return `${prefix}/${entityId}/${sizePrefix}${filename}`;
|
|
43
|
+
}
|
package/dist/core/process.d.ts
CHANGED
|
@@ -1,24 +1,4 @@
|
|
|
1
|
-
import type { StorageAdapter, StoredMedia, ImageSize, MediaConfig
|
|
2
|
-
export declare const IMAGE_SIZES: {
|
|
3
|
-
thumbnail: {
|
|
4
|
-
width: number;
|
|
5
|
-
height: number;
|
|
6
|
-
fit: "cover";
|
|
7
|
-
};
|
|
8
|
-
medium: {
|
|
9
|
-
width: number;
|
|
10
|
-
height: number;
|
|
11
|
-
fit: "inside";
|
|
12
|
-
};
|
|
13
|
-
large: {
|
|
14
|
-
width: number;
|
|
15
|
-
height: number;
|
|
16
|
-
fit: "inside";
|
|
17
|
-
};
|
|
18
|
-
};
|
|
19
|
-
export declare function validateImageFile(file: File, config?: MediaConfig): ValidationResult;
|
|
20
|
-
export declare function generateMediaKey(_originalName?: string): string;
|
|
21
|
-
export declare function getStorageKey(prefix: string, entityId: string, filename: string, size: ImageSize): string;
|
|
1
|
+
import type { StorageAdapter, StoredMedia, ImageSize, MediaConfig } from './types.js';
|
|
22
2
|
export declare function processAndStore(adapter: StorageAdapter, file: File, prefix: string, entityId: string, config?: MediaConfig): Promise<StoredMedia>;
|
|
23
3
|
export declare function deleteMedia(adapter: StorageAdapter, prefix: string, entityId: string, filename: string): Promise<void>;
|
|
24
4
|
export declare function getMediaUrl(adapter: StorageAdapter, prefix: string, entityId: string, filename: string, size?: ImageSize): string;
|
package/dist/core/process.js
CHANGED
|
@@ -1,44 +1,8 @@
|
|
|
1
|
+
// Server-only pipeline: needs sharp and Node's Buffer. Importing this from a
|
|
2
|
+
// browser bundle will fail — the client-safe helpers live in core/media.ts and
|
|
3
|
+
// are re-exported from the package root.
|
|
1
4
|
import sharp from 'sharp';
|
|
2
|
-
import {
|
|
3
|
-
const DEFAULT_MAX_SIZE = 5 * 1024 * 1024;
|
|
4
|
-
const DEFAULT_ALLOWED_TYPES = ['image/jpeg', 'image/jpg', 'image/png', 'image/webp'];
|
|
5
|
-
export const IMAGE_SIZES = {
|
|
6
|
-
thumbnail: { width: 300, height: 300, fit: 'cover' },
|
|
7
|
-
medium: { width: 800, height: 600, fit: 'inside' },
|
|
8
|
-
large: { width: 1200, height: 900, fit: 'inside' },
|
|
9
|
-
};
|
|
10
|
-
const SIZE_PREFIXES = {
|
|
11
|
-
original: '',
|
|
12
|
-
thumbnail: 'thumb_',
|
|
13
|
-
medium: 'med_',
|
|
14
|
-
large: 'large_',
|
|
15
|
-
};
|
|
16
|
-
const SIZE_QUALITY = {
|
|
17
|
-
original: 95,
|
|
18
|
-
thumbnail: 80,
|
|
19
|
-
medium: 85,
|
|
20
|
-
large: 90,
|
|
21
|
-
};
|
|
22
|
-
export function validateImageFile(file, config) {
|
|
23
|
-
const maxSize = config?.maxFileSize ?? DEFAULT_MAX_SIZE;
|
|
24
|
-
const allowed = config?.allowedTypes ?? DEFAULT_ALLOWED_TYPES;
|
|
25
|
-
if (file.size > maxSize) {
|
|
26
|
-
const mb = Math.round(maxSize / 1024 / 1024);
|
|
27
|
-
return { valid: false, error: `File too large (max ${mb}MB)` };
|
|
28
|
-
}
|
|
29
|
-
if (!allowed.includes(file.type)) {
|
|
30
|
-
return { valid: false, error: `Invalid file type. Allowed: JPEG, PNG, WebP` };
|
|
31
|
-
}
|
|
32
|
-
return { valid: true };
|
|
33
|
-
}
|
|
34
|
-
export function generateMediaKey(_originalName) {
|
|
35
|
-
// All processed output is WebP regardless of the uploaded format.
|
|
36
|
-
return `${createId()}.webp`;
|
|
37
|
-
}
|
|
38
|
-
export function getStorageKey(prefix, entityId, filename, size) {
|
|
39
|
-
const sizePrefix = SIZE_PREFIXES[size];
|
|
40
|
-
return `${prefix}/${entityId}/${sizePrefix}${filename}`;
|
|
41
|
-
}
|
|
5
|
+
import { IMAGE_SIZES, SIZE_QUALITY, generateMediaKey, getStorageKey, validateImageFile, } from './media.js';
|
|
42
6
|
export async function processAndStore(adapter, file, prefix, entityId, config) {
|
|
43
7
|
const validation = validateImageFile(file, config);
|
|
44
8
|
if (!validation.valid)
|
package/dist/index.d.ts
CHANGED
|
@@ -1,7 +1,4 @@
|
|
|
1
1
|
export type { StorageAdapter, S3Config, StoredMedia, ImageSize, MediaConfig, ValidationResult, } from './core/types.js';
|
|
2
|
-
export
|
|
3
|
-
export { createS3Adapter } from './core/adapter.js';
|
|
4
|
-
export { createLocalAdapter } from './core/local-adapter.js';
|
|
5
|
-
export { validateImageFile, generateMediaKey, getStorageKey, processAndStore, deleteMedia, getMediaUrl, IMAGE_SIZES, } from './core/process.js';
|
|
2
|
+
export { validateImageFile, generateMediaKey, getStorageKey, IMAGE_SIZES, DEFAULT_MAX_SIZE, DEFAULT_ALLOWED_TYPES, } from './core/media.js';
|
|
6
3
|
export { default as ImageUpload } from './components/ImageUpload.svelte';
|
|
7
4
|
export { default as ImageGallery } from './components/ImageGallery.svelte';
|
package/dist/index.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
// Client-safe entry. The image pipeline and storage adapters need sharp,
|
|
2
|
+
// node:fs and Buffer, so they live behind '@nomideusz/svelte-media/server' —
|
|
3
|
+
// importing them here would break any browser bundle that touches a component.
|
|
4
|
+
export { validateImageFile, generateMediaKey, getStorageKey, IMAGE_SIZES, DEFAULT_MAX_SIZE, DEFAULT_ALLOWED_TYPES, } from './core/media.js';
|
|
4
5
|
export { default as ImageUpload } from './components/ImageUpload.svelte';
|
|
5
6
|
export { default as ImageGallery } from './components/ImageGallery.svelte';
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export type { LocalConfig } from '../core/local-adapter.js';
|
|
2
|
+
export { createS3Adapter } from '../core/adapter.js';
|
|
3
|
+
export { createLocalAdapter } from '../core/local-adapter.js';
|
|
4
|
+
export { processAndStore, deleteMedia, getMediaUrl } from '../core/process.js';
|
|
5
|
+
export { validateImageFile, generateMediaKey, getStorageKey, IMAGE_SIZES, } from '../core/media.js';
|
|
6
|
+
export type * from '../core/types.js';
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
// Server-only entry: sharp, node:fs and Buffer live behind here.
|
|
2
|
+
export { createS3Adapter } from '../core/adapter.js';
|
|
3
|
+
export { createLocalAdapter } from '../core/local-adapter.js';
|
|
4
|
+
export { processAndStore, deleteMedia, getMediaUrl } from '../core/process.js';
|
|
5
|
+
// Re-exported so server code needs only one import.
|
|
6
|
+
export { validateImageFile, generateMediaKey, getStorageKey, IMAGE_SIZES, } from '../core/media.js';
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nomideusz/svelte-media",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "Image upload, processing, and S3-compatible storage for Svelte 5 apps.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"sideEffects": false,
|
|
@@ -12,6 +12,10 @@
|
|
|
12
12
|
"types": "./dist/index.d.ts",
|
|
13
13
|
"svelte": "./dist/index.js",
|
|
14
14
|
"default": "./dist/index.js"
|
|
15
|
+
},
|
|
16
|
+
"./server": {
|
|
17
|
+
"types": "./dist/server/index.d.ts",
|
|
18
|
+
"default": "./dist/server/index.js"
|
|
15
19
|
}
|
|
16
20
|
},
|
|
17
21
|
"files": [
|
|
@@ -64,5 +68,6 @@
|
|
|
64
68
|
"repository": {
|
|
65
69
|
"type": "git",
|
|
66
70
|
"url": "https://github.com/nomideusz/svelte-media"
|
|
67
|
-
}
|
|
71
|
+
},
|
|
72
|
+
"homepage": "https://svelte-media-gamma.vercel.app/"
|
|
68
73
|
}
|