@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 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. The package has not been published to npm yet.
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
- ## Unreleased
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`). The processing path is server-only: it uses
14
- > `sharp` and Node `Buffer`. Import `processAndStore` from server code
15
- > (`+page.server.ts`, a route handler, a script) never from a component.
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
- // Adapters
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
- getStorageKey(prefix, entityId, filename, size): string // `${prefix}/${entityId}/${sizePrefix}${filename}`
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
+ }
@@ -1,24 +1,4 @@
1
- import type { StorageAdapter, StoredMedia, ImageSize, MediaConfig, ValidationResult } from './types.js';
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;
@@ -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 { createId } from '@paralleldrive/cuid2';
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 type { LocalConfig } from './core/local-adapter.js';
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
- export { createS3Adapter } from './core/adapter.js';
2
- export { createLocalAdapter } from './core/local-adapter.js';
3
- export { validateImageFile, generateMediaKey, getStorageKey, processAndStore, deleteMedia, getMediaUrl, IMAGE_SIZES, } from './core/process.js';
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.1.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
  }