@sproux/media-sdk 0.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/README.md ADDED
@@ -0,0 +1,208 @@
1
+ # @sproux/media-sdk
2
+
3
+ Media URL builder library for Sproux — build image variant URLs, video HLS playlist URLs, and thumbnails from original media URLs.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ # npm
9
+ npm install @sproux/media-sdk
10
+
11
+ # pnpm
12
+ pnpm add @sproux/media-sdk
13
+
14
+ # yarn
15
+ yarn add @sproux/media-sdk
16
+ ```
17
+
18
+ ## Quick Start
19
+
20
+ ```typescript
21
+ import {
22
+ buildImageVariantUrl,
23
+ buildVideoHlsUrl,
24
+ buildVideoThumbnailUrl,
25
+ parseMediaUrl,
26
+ } from '@sproux/media-sdk';
27
+
28
+ // Build an image variant URL
29
+ const imageUrl = buildImageVariantUrl(
30
+ 'https://cdn.example.com/avatar/image/usr-1/profile.jpg',
31
+ { width: 200, height: 200, resizeType: 'fit', quality: 80 }
32
+ );
33
+ // → "https://cdn.example.com/avatar/image/usr-1/profile-200x200-fit-q80.webp"
34
+
35
+ // Build a video HLS playlist URL
36
+ const hlsUrl = buildVideoHlsUrl(
37
+ 'https://cdn.example.com/gallery/video/usr-1/intro.mp4'
38
+ );
39
+ // → "https://cdn.example.com/gallery/video/usr-1/intro/playlist.m3u8"
40
+
41
+ // Build a video thumbnail URL
42
+ const thumbnailUrl = buildVideoThumbnailUrl(
43
+ 'https://cdn.example.com/gallery/video/usr-1/intro.mp4'
44
+ );
45
+ // → "https://cdn.example.com/gallery/video/usr-1/intro/thumbnail.webp"
46
+ ```
47
+
48
+ ## API Reference
49
+
50
+ ### `parseMediaUrl(url: string): ParsedMediaUrl`
51
+
52
+ Parse a media URL into its structured components.
53
+
54
+ **Expected URL pattern:** `{cdnBase}/{purpose}/{type}/{userId}/{name}.{extension}`
55
+
56
+ ```typescript
57
+ import { parseMediaUrl } from '@sproux/media-sdk';
58
+
59
+ const parsed = parseMediaUrl('https://cdn.example.com/avatar/image/usr-1/abc.jpg');
60
+ // {
61
+ // originalUrl: "https://cdn.example.com/avatar/image/usr-1/abc.jpg",
62
+ // cdnBase: "https://cdn.example.com",
63
+ // objectKey: "avatar/image/usr-1/abc.jpg",
64
+ // purpose: "avatar",
65
+ // type: "image",
66
+ // userId: "usr-1",
67
+ // name: "abc",
68
+ // extension: "jpg"
69
+ // }
70
+ ```
71
+
72
+ ### `buildImageVariantUrl(originalUrl: string, options: ImageVariantOptions): string`
73
+
74
+ Build a CDN URL for an image variant with specific dimensions, resize type, and quality.
75
+
76
+ ```typescript
77
+ import { buildImageVariantUrl } from '@sproux/media-sdk';
78
+
79
+ const url = buildImageVariantUrl(
80
+ 'https://cdn.example.com/avatar/image/usr-1/photo.jpg',
81
+ {
82
+ width: 400,
83
+ height: 300,
84
+ resizeType: 'fill',
85
+ format: 'webp', // optional, defaults to 'webp'
86
+ quality: 85, // optional
87
+ }
88
+ );
89
+ // → "https://cdn.example.com/avatar/image/usr-1/photo-400x300-fill-q85.webp"
90
+ ```
91
+
92
+ #### ImageVariantOptions
93
+
94
+ | Property | Type | Required | Description |
95
+ | ------------ | ----------------- | -------- | ------------------------------------- |
96
+ | `width` | `number` | Yes | Target width in pixels (1-4096) |
97
+ | `height` | `number` | Yes | Target height in pixels (1-4096) |
98
+ | `resizeType` | `ImageResizeType` | Yes | Resize strategy: `fit`, `fill`, `auto` |
99
+ | `format` | `ImageFormat` | No | Output format (default: `webp`) |
100
+ | `quality` | `number` | No | Quality 1-100 |
101
+
102
+ ### `buildVideoHlsUrl(originalUrl: string): string`
103
+
104
+ Build an HLS playlist URL from an original video URL.
105
+
106
+ ```typescript
107
+ import { buildVideoHlsUrl } from '@sproux/media-sdk';
108
+
109
+ const hlsUrl = buildVideoHlsUrl(
110
+ 'https://cdn.example.com/gallery/video/usr-1/video.mp4'
111
+ );
112
+ // → "https://cdn.example.com/gallery/video/usr-1/video/playlist.m3u8"
113
+ ```
114
+
115
+ ### `buildVideoThumbnailUrl(originalUrl: string): string`
116
+
117
+ Build a thumbnail URL from an original video URL.
118
+
119
+ ```typescript
120
+ import { buildVideoThumbnailUrl } from '@sproux/media-sdk';
121
+
122
+ const thumbnailUrl = buildVideoThumbnailUrl(
123
+ 'https://cdn.example.com/gallery/video/usr-1/video.mp4'
124
+ );
125
+ // → "https://cdn.example.com/gallery/video/usr-1/video/thumbnail.webp"
126
+ ```
127
+
128
+ ### `buildVariantString(options: ImageVariantOptions): string`
129
+
130
+ Build a deterministic variant string from image variant options. This is used internally by `buildImageVariantUrl`.
131
+
132
+ ```typescript
133
+ import { buildVariantString } from '@sproux/media-sdk';
134
+
135
+ buildVariantString({ width: 200, height: 200, resizeType: 'fit' });
136
+ // → "200x200-fit"
137
+
138
+ buildVariantString({ width: 200, height: 200, resizeType: 'fit', quality: 80 });
139
+ // → "200x200-fit-q80"
140
+ ```
141
+
142
+ ## Constants
143
+
144
+ The SDK exports constants for type-safe usage:
145
+
146
+ ```typescript
147
+ import {
148
+ MEDIA_TYPE, // { IMAGE: 'image', VIDEO: 'video' }
149
+ MEDIA_PURPOSE, // { AVATAR: 'avatar', HERO: 'hero', GALLERY: 'gallery' }
150
+ MEDIA_STATUS, // { PENDING, UPLOADED, PROCESSING, READY, ERROR }
151
+ IMAGE_FORMAT, // { WEBP, AVIF, JPEG, PNG, JPG }
152
+ IMAGE_RESIZE_TYPE // { FIT, FILL, AUTO }
153
+ } from '@sproux/media-sdk';
154
+ ```
155
+
156
+ ## Types
157
+
158
+ ```typescript
159
+ import type {
160
+ MediaType, // 'image' | 'video'
161
+ MediaPurpose, // 'avatar' | 'hero' | 'gallery'
162
+ MediaStatus, // 'pending' | 'uploaded' | 'processing' | 'ready' | 'error'
163
+ ImageFormat, // 'webp' | 'avif' | 'jpeg' | 'png' | 'jpg'
164
+ ImageResizeType, // 'fit' | 'fill' | 'auto'
165
+ ParsedMediaUrl,
166
+ ImageVariantOptions,
167
+ } from '@sproux/media-sdk';
168
+ ```
169
+
170
+ ## URL Structure
171
+
172
+ This SDK expects media URLs to follow this structure:
173
+
174
+ ```
175
+ {cdnBase}/{purpose}/{type}/{userId}/{filename}
176
+ ```
177
+
178
+ | Segment | Description | Valid Values |
179
+ | ---------- | ----------------------------------------- | --------------------------------- |
180
+ | `cdnBase` | CDN origin including protocol | Any valid URL origin |
181
+ | `purpose` | Media use case | `avatar`, `hero`, `gallery` |
182
+ | `type` | Media type | `image`, `video` |
183
+ | `userId` | User identifier | Any string |
184
+ | `filename` | File name with extension | `{name}.{extension}` |
185
+
186
+ ### Image Variant URL Structure
187
+
188
+ ```
189
+ {cdnBase}/{purpose}/image/{userId}/{name}-{width}x{height}-{resizeType}[-q{quality}].{format}
190
+ ```
191
+
192
+ Example: `https://cdn.example.com/avatar/image/usr-1/photo-200x200-fit-q80.webp`
193
+
194
+ ### Video HLS URL Structure
195
+
196
+ ```
197
+ {cdnBase}/{purpose}/video/{userId}/{name}/playlist.m3u8
198
+ ```
199
+
200
+ ### Video Thumbnail URL Structure
201
+
202
+ ```
203
+ {cdnBase}/{purpose}/video/{userId}/{name}/thumbnail.webp
204
+ ```
205
+
206
+ ## License
207
+
208
+ MIT
package/dist/index.cjs ADDED
@@ -0,0 +1,200 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/index.ts
21
+ var index_exports = {};
22
+ __export(index_exports, {
23
+ IMAGE_FORMAT: () => IMAGE_FORMAT,
24
+ IMAGE_RESIZE_TYPE: () => IMAGE_RESIZE_TYPE,
25
+ MEDIA_DEFAULTS: () => MEDIA_DEFAULTS,
26
+ MEDIA_PURPOSE: () => MEDIA_PURPOSE,
27
+ MEDIA_STATUS: () => MEDIA_STATUS,
28
+ MEDIA_TYPE: () => MEDIA_TYPE,
29
+ R2_PATH: () => R2_PATH,
30
+ buildImageVariantUrl: () => buildImageVariantUrl,
31
+ buildVariantString: () => buildVariantString,
32
+ buildVideoHlsUrl: () => buildVideoHlsUrl,
33
+ buildVideoThumbnailUrl: () => buildVideoThumbnailUrl,
34
+ parseMediaUrl: () => parseMediaUrl
35
+ });
36
+ module.exports = __toCommonJS(index_exports);
37
+
38
+ // src/constants.ts
39
+ var MEDIA_TYPE = {
40
+ IMAGE: "image",
41
+ VIDEO: "video"
42
+ };
43
+ var MEDIA_PURPOSE = {
44
+ AVATAR: "avatar",
45
+ HERO: "hero",
46
+ GALLERY: "gallery"
47
+ };
48
+ var MEDIA_STATUS = {
49
+ PENDING: "pending",
50
+ UPLOADED: "uploaded",
51
+ PROCESSING: "processing",
52
+ READY: "ready",
53
+ ERROR: "error"
54
+ };
55
+ var IMAGE_FORMAT = {
56
+ WEBP: "webp",
57
+ AVIF: "avif",
58
+ JPEG: "jpeg",
59
+ PNG: "png",
60
+ JPG: "jpg"
61
+ };
62
+ var IMAGE_RESIZE_TYPE = {
63
+ FIT: "fit",
64
+ FILL: "fill",
65
+ AUTO: "auto"
66
+ };
67
+ var R2_PATH = {
68
+ HLS_PLAYLIST: "playlist.m3u8",
69
+ THUMBNAIL: "thumbnail.webp"
70
+ };
71
+ var MEDIA_DEFAULTS = {
72
+ IMAGE_FORMAT: IMAGE_FORMAT.WEBP
73
+ };
74
+
75
+ // src/parse-media-url.ts
76
+ var VALID_PURPOSES = new Set(Object.values(MEDIA_PURPOSE));
77
+ var VALID_TYPES = new Set(Object.values(MEDIA_TYPE));
78
+ function parseMediaUrl(originalUrl) {
79
+ let url;
80
+ try {
81
+ url = new URL(originalUrl);
82
+ } catch {
83
+ throw new Error(`Invalid media URL: "${originalUrl}"`);
84
+ }
85
+ const pathname = url.pathname.startsWith("/") ? url.pathname.slice(1) : url.pathname;
86
+ const segments = pathname.split("/");
87
+ if (segments.length < 4) {
88
+ throw new Error(
89
+ `Invalid media URL path: expected at least 4 segments (purpose/type/userId/filename), got ${segments.length} in "${pathname}"`
90
+ );
91
+ }
92
+ const [purpose, type, userId, filename] = segments;
93
+ if (!VALID_PURPOSES.has(purpose)) {
94
+ throw new Error(
95
+ `Invalid media purpose: "${purpose}". Expected one of: ${[...VALID_PURPOSES].join(", ")}`
96
+ );
97
+ }
98
+ if (!VALID_TYPES.has(type)) {
99
+ throw new Error(
100
+ `Invalid media type: "${type}". Expected one of: ${[...VALID_TYPES].join(", ")}`
101
+ );
102
+ }
103
+ if (!userId) {
104
+ throw new Error("Missing userId segment in media URL");
105
+ }
106
+ if (!filename) {
107
+ throw new Error("Missing filename segment in media URL");
108
+ }
109
+ const dotIndex = filename.lastIndexOf(".");
110
+ if (dotIndex <= 0) {
111
+ throw new Error(`Invalid filename: "${filename}". Expected format: "name.extension"`);
112
+ }
113
+ const name = filename.slice(0, dotIndex);
114
+ const extension = filename.slice(dotIndex + 1);
115
+ if (!extension) {
116
+ throw new Error(`Missing file extension in filename: "${filename}"`);
117
+ }
118
+ const cdnBase = `${url.protocol}//${url.host}`;
119
+ const objectKey = pathname;
120
+ return {
121
+ originalUrl,
122
+ cdnBase,
123
+ objectKey,
124
+ purpose,
125
+ type,
126
+ userId,
127
+ name,
128
+ extension
129
+ };
130
+ }
131
+
132
+ // src/build-variant-string.ts
133
+ function buildVariantString(options) {
134
+ validateVariantOptions(options);
135
+ const parts = [`${options.width}x${options.height}`, options.resizeType];
136
+ if (options.quality != null) {
137
+ parts.push(`q${options.quality}`);
138
+ }
139
+ return parts.join("-");
140
+ }
141
+ function validateVariantOptions(options) {
142
+ if (!Number.isInteger(options.width) || options.width < 1 || options.width > 4096) {
143
+ throw new Error(`Invalid width: ${options.width}. Must be an integer between 1 and 4096.`);
144
+ }
145
+ if (!Number.isInteger(options.height) || options.height < 1 || options.height > 4096) {
146
+ throw new Error(`Invalid height: ${options.height}. Must be an integer between 1 and 4096.`);
147
+ }
148
+ if (options.quality != null) {
149
+ if (!Number.isInteger(options.quality) || options.quality < 1 || options.quality > 100) {
150
+ throw new Error(
151
+ `Invalid quality: ${options.quality}. Must be an integer between 1 and 100.`
152
+ );
153
+ }
154
+ }
155
+ }
156
+
157
+ // src/build-image-variant-url.ts
158
+ function buildImageVariantUrl(originalUrl, options) {
159
+ const parsed = parseMediaUrl(originalUrl);
160
+ if (parsed.type !== "image") {
161
+ throw new Error(`Expected image URL, got type "${parsed.type}" in "${originalUrl}"`);
162
+ }
163
+ const variant = buildVariantString(options);
164
+ const format = options.format ?? MEDIA_DEFAULTS.IMAGE_FORMAT;
165
+ return `${parsed.cdnBase}/${parsed.purpose}/${parsed.type}/${parsed.userId}/${parsed.name}-${variant}.${format}`;
166
+ }
167
+
168
+ // src/build-video-hls-url.ts
169
+ function buildVideoHlsUrl(originalUrl) {
170
+ const parsed = parseMediaUrl(originalUrl);
171
+ if (parsed.type !== "video") {
172
+ throw new Error(`Expected video URL, got type "${parsed.type}" in "${originalUrl}"`);
173
+ }
174
+ return `${parsed.cdnBase}/${parsed.purpose}/video/${parsed.userId}/${parsed.name}/${R2_PATH.HLS_PLAYLIST}`;
175
+ }
176
+
177
+ // src/build-video-thumbnail-url.ts
178
+ function buildVideoThumbnailUrl(originalUrl) {
179
+ const parsed = parseMediaUrl(originalUrl);
180
+ if (parsed.type !== "video") {
181
+ throw new Error(`Expected video URL, got type "${parsed.type}" in "${originalUrl}"`);
182
+ }
183
+ return `${parsed.cdnBase}/${parsed.purpose}/video/${parsed.userId}/${parsed.name}/${R2_PATH.THUMBNAIL}`;
184
+ }
185
+ // Annotate the CommonJS export names for ESM import in node:
186
+ 0 && (module.exports = {
187
+ IMAGE_FORMAT,
188
+ IMAGE_RESIZE_TYPE,
189
+ MEDIA_DEFAULTS,
190
+ MEDIA_PURPOSE,
191
+ MEDIA_STATUS,
192
+ MEDIA_TYPE,
193
+ R2_PATH,
194
+ buildImageVariantUrl,
195
+ buildVariantString,
196
+ buildVideoHlsUrl,
197
+ buildVideoThumbnailUrl,
198
+ parseMediaUrl
199
+ });
200
+ //# sourceMappingURL=index.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/index.ts","../src/constants.ts","../src/parse-media-url.ts","../src/build-variant-string.ts","../src/build-image-variant-url.ts","../src/build-video-hls-url.ts","../src/build-video-thumbnail-url.ts"],"sourcesContent":["// Constants\r\nexport {\r\n MEDIA_TYPE,\r\n MEDIA_PURPOSE,\r\n MEDIA_STATUS,\r\n IMAGE_FORMAT,\r\n IMAGE_RESIZE_TYPE,\r\n R2_PATH,\r\n MEDIA_DEFAULTS,\r\n} from './constants';\r\n\r\n// Types\r\nexport type {\r\n MediaType,\r\n MediaPurpose,\r\n MediaStatus,\r\n ImageFormat,\r\n ImageResizeType,\r\n ParsedMediaUrl,\r\n ImageVariantOptions,\r\n} from './types';\r\n\r\n// Functions\r\nexport { parseMediaUrl } from './parse-media-url';\r\nexport { buildVariantString } from './build-variant-string';\r\nexport { buildImageVariantUrl } from './build-image-variant-url';\r\nexport { buildVideoHlsUrl } from './build-video-hls-url';\r\nexport { buildVideoThumbnailUrl } from './build-video-thumbnail-url';\r\n","// ============================================================================\r\n// MEDIA TYPE\r\n// ============================================================================\r\n\r\nexport const MEDIA_TYPE = {\r\n IMAGE: 'image',\r\n VIDEO: 'video',\r\n} as const;\r\n\r\n// ============================================================================\r\n// MEDIA PURPOSE\r\n// ============================================================================\r\n\r\nexport const MEDIA_PURPOSE = {\r\n AVATAR: 'avatar',\r\n HERO: 'hero',\r\n GALLERY: 'gallery',\r\n} as const;\r\n\r\n// ============================================================================\r\n// MEDIA STATUS\r\n// ============================================================================\r\n\r\nexport const MEDIA_STATUS = {\r\n PENDING: 'pending',\r\n UPLOADED: 'uploaded',\r\n PROCESSING: 'processing',\r\n READY: 'ready',\r\n ERROR: 'error',\r\n} as const;\r\n\r\n// ============================================================================\r\n// IMAGE FORMAT\r\n// ============================================================================\r\n\r\nexport const IMAGE_FORMAT = {\r\n WEBP: 'webp',\r\n AVIF: 'avif',\r\n JPEG: 'jpeg',\r\n PNG: 'png',\r\n JPG: 'jpg',\r\n} as const;\r\n\r\n// ============================================================================\r\n// IMAGE RESIZE TYPE\r\n// ============================================================================\r\n\r\nexport const IMAGE_RESIZE_TYPE = {\r\n FIT: 'fit',\r\n FILL: 'fill',\r\n AUTO: 'auto',\r\n} as const;\r\n\r\n// ============================================================================\r\n// R2/CDN PATH CONSTANTS\r\n// ============================================================================\r\n\r\nexport const R2_PATH = {\r\n HLS_PLAYLIST: 'playlist.m3u8',\r\n THUMBNAIL: 'thumbnail.webp',\r\n} as const;\r\n\r\n// ============================================================================\r\n// DEFAULTS\r\n// ============================================================================\r\n\r\nexport const MEDIA_DEFAULTS = {\r\n IMAGE_FORMAT: IMAGE_FORMAT.WEBP,\r\n} as const;\r\n","import { MEDIA_PURPOSE, MEDIA_TYPE } from './constants';\r\nimport type { ParsedMediaUrl, MediaPurpose, MediaType } from './types';\r\n\r\nconst VALID_PURPOSES = new Set<string>(Object.values(MEDIA_PURPOSE));\r\nconst VALID_TYPES = new Set<string>(Object.values(MEDIA_TYPE));\r\n\r\n/**\r\n * Parse a full media URL into structured components.\r\n *\r\n * Expected URL pattern:\r\n * {cdnBase}/{purpose}/{type}/{userId}/{name}.{extension}\r\n *\r\n * Example:\r\n * https://cdn.example.com/avatar/image/usr-1/abc.jpg\r\n * → { cdnBase: \"https://cdn.example.com\", purpose: \"avatar\", type: \"image\", userId: \"usr-1\", name: \"abc\", extension: \"jpg\" }\r\n */\r\nexport function parseMediaUrl(originalUrl: string): ParsedMediaUrl {\r\n let url: URL;\r\n try {\r\n url = new URL(originalUrl);\r\n } catch {\r\n throw new Error(`Invalid media URL: \"${originalUrl}\"`);\r\n }\r\n\r\n // Remove leading slash and split into segments\r\n const pathname = url.pathname.startsWith('/') ? url.pathname.slice(1) : url.pathname;\r\n const segments = pathname.split('/');\r\n\r\n if (segments.length < 4) {\r\n throw new Error(\r\n `Invalid media URL path: expected at least 4 segments (purpose/type/userId/filename), got ${segments.length} in \"${pathname}\"`,\r\n );\r\n }\r\n\r\n const [purpose, type, userId, filename] = segments as [string, string, string, string];\r\n\r\n if (!VALID_PURPOSES.has(purpose)) {\r\n throw new Error(\r\n `Invalid media purpose: \"${purpose}\". Expected one of: ${[...VALID_PURPOSES].join(', ')}`,\r\n );\r\n }\r\n\r\n if (!VALID_TYPES.has(type)) {\r\n throw new Error(\r\n `Invalid media type: \"${type}\". Expected one of: ${[...VALID_TYPES].join(', ')}`,\r\n );\r\n }\r\n\r\n if (!userId) {\r\n throw new Error('Missing userId segment in media URL');\r\n }\r\n\r\n if (!filename) {\r\n throw new Error('Missing filename segment in media URL');\r\n }\r\n\r\n const dotIndex = filename.lastIndexOf('.');\r\n if (dotIndex <= 0) {\r\n throw new Error(`Invalid filename: \"${filename}\". Expected format: \"name.extension\"`);\r\n }\r\n\r\n const name = filename.slice(0, dotIndex);\r\n const extension = filename.slice(dotIndex + 1);\r\n\r\n if (!extension) {\r\n throw new Error(`Missing file extension in filename: \"${filename}\"`);\r\n }\r\n\r\n const cdnBase = `${url.protocol}//${url.host}`;\r\n const objectKey = pathname;\r\n\r\n return {\r\n originalUrl,\r\n cdnBase,\r\n objectKey,\r\n purpose: purpose as MediaPurpose,\r\n type: type as MediaType,\r\n userId,\r\n name,\r\n extension,\r\n };\r\n}\r\n","import type { ImageVariantOptions } from './types';\r\n\r\n/**\r\n * Build a deterministic variant string from image variant options.\r\n *\r\n * Format: {width}x{height}-{resizeType}[-q{quality}]\r\n *\r\n * Examples:\r\n * { width: 200, height: 200, resizeType: 'fit' } → \"200x200-fit\"\r\n * { width: 200, height: 200, resizeType: 'fit', quality: 80 } → \"200x200-fit-q80\"\r\n */\r\nexport function buildVariantString(options: ImageVariantOptions): string {\r\n validateVariantOptions(options);\r\n\r\n const parts: string[] = [`${options.width}x${options.height}`, options.resizeType];\r\n\r\n if (options.quality != null) {\r\n parts.push(`q${options.quality}`);\r\n }\r\n\r\n return parts.join('-');\r\n}\r\n\r\nfunction validateVariantOptions(options: ImageVariantOptions): void {\r\n if (!Number.isInteger(options.width) || options.width < 1 || options.width > 4096) {\r\n throw new Error(`Invalid width: ${options.width}. Must be an integer between 1 and 4096.`);\r\n }\r\n\r\n if (!Number.isInteger(options.height) || options.height < 1 || options.height > 4096) {\r\n throw new Error(`Invalid height: ${options.height}. Must be an integer between 1 and 4096.`);\r\n }\r\n\r\n if (options.quality != null) {\r\n if (!Number.isInteger(options.quality) || options.quality < 1 || options.quality > 100) {\r\n throw new Error(\r\n `Invalid quality: ${options.quality}. Must be an integer between 1 and 100.`,\r\n );\r\n }\r\n }\r\n}\r\n","import { MEDIA_DEFAULTS } from './constants';\r\nimport { parseMediaUrl } from './parse-media-url';\r\nimport { buildVariantString } from './build-variant-string';\r\nimport type { ImageVariantOptions } from './types';\r\n\r\n/**\r\n * Build a full CDN URL for an image variant.\r\n *\r\n * Takes an original image URL and variant options, returns the CDN URL\r\n * for that specific variant.\r\n *\r\n * Example:\r\n * buildImageVariantUrl(\"https://cdn.example.com/avatar/image/usr-1/abc.jpg\", {\r\n * width: 200, height: 200, resizeType: 'fit', quality: 80\r\n * })\r\n * → \"https://cdn.example.com/avatar/image/usr-1/abc-200x200-fit-q80.webp\"\r\n */\r\nexport function buildImageVariantUrl(originalUrl: string, options: ImageVariantOptions): string {\r\n const parsed = parseMediaUrl(originalUrl);\r\n\r\n if (parsed.type !== 'image') {\r\n throw new Error(`Expected image URL, got type \"${parsed.type}\" in \"${originalUrl}\"`);\r\n }\r\n\r\n const variant = buildVariantString(options);\r\n const format = options.format ?? MEDIA_DEFAULTS.IMAGE_FORMAT;\r\n\r\n return `${parsed.cdnBase}/${parsed.purpose}/${parsed.type}/${parsed.userId}/${parsed.name}-${variant}.${format}`;\r\n}\r\n","import { R2_PATH } from './constants';\r\nimport { parseMediaUrl } from './parse-media-url';\r\n\r\n/**\r\n * Build HLS playlist URL from an original video URL.\r\n *\r\n * Example:\r\n * buildVideoHlsUrl(\"https://cdn.example.com/gallery/video/usr-1/xyz.mp4\")\r\n * → \"https://cdn.example.com/gallery/video/usr-1/xyz/playlist.m3u8\"\r\n */\r\nexport function buildVideoHlsUrl(originalUrl: string): string {\r\n const parsed = parseMediaUrl(originalUrl);\r\n\r\n if (parsed.type !== 'video') {\r\n throw new Error(`Expected video URL, got type \"${parsed.type}\" in \"${originalUrl}\"`);\r\n }\r\n\r\n return `${parsed.cdnBase}/${parsed.purpose}/video/${parsed.userId}/${parsed.name}/${R2_PATH.HLS_PLAYLIST}`;\r\n}\r\n","import { R2_PATH } from './constants';\r\nimport { parseMediaUrl } from './parse-media-url';\r\n\r\n/**\r\n * Build thumbnail URL from an original video URL.\r\n *\r\n * Example:\r\n * buildVideoThumbnailUrl(\"https://cdn.example.com/gallery/video/usr-1/xyz.mp4\")\r\n * → \"https://cdn.example.com/gallery/video/usr-1/xyz/thumbnail.webp\"\r\n */\r\nexport function buildVideoThumbnailUrl(originalUrl: string): string {\r\n const parsed = parseMediaUrl(originalUrl);\r\n\r\n if (parsed.type !== 'video') {\r\n throw new Error(`Expected video URL, got type \"${parsed.type}\" in \"${originalUrl}\"`);\r\n }\r\n\r\n return `${parsed.cdnBase}/${parsed.purpose}/video/${parsed.userId}/${parsed.name}/${R2_PATH.THUMBNAIL}`;\r\n}\r\n"],"mappings":"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","names":[]}
@@ -0,0 +1,128 @@
1
+ declare const MEDIA_TYPE: {
2
+ readonly IMAGE: "image";
3
+ readonly VIDEO: "video";
4
+ };
5
+ declare const MEDIA_PURPOSE: {
6
+ readonly AVATAR: "avatar";
7
+ readonly HERO: "hero";
8
+ readonly GALLERY: "gallery";
9
+ };
10
+ declare const MEDIA_STATUS: {
11
+ readonly PENDING: "pending";
12
+ readonly UPLOADED: "uploaded";
13
+ readonly PROCESSING: "processing";
14
+ readonly READY: "ready";
15
+ readonly ERROR: "error";
16
+ };
17
+ declare const IMAGE_FORMAT: {
18
+ readonly WEBP: "webp";
19
+ readonly AVIF: "avif";
20
+ readonly JPEG: "jpeg";
21
+ readonly PNG: "png";
22
+ readonly JPG: "jpg";
23
+ };
24
+ declare const IMAGE_RESIZE_TYPE: {
25
+ readonly FIT: "fit";
26
+ readonly FILL: "fill";
27
+ readonly AUTO: "auto";
28
+ };
29
+ declare const R2_PATH: {
30
+ readonly HLS_PLAYLIST: "playlist.m3u8";
31
+ readonly THUMBNAIL: "thumbnail.webp";
32
+ };
33
+ declare const MEDIA_DEFAULTS: {
34
+ readonly IMAGE_FORMAT: "webp";
35
+ };
36
+
37
+ type MediaType = (typeof MEDIA_TYPE)[keyof typeof MEDIA_TYPE];
38
+ type MediaPurpose = (typeof MEDIA_PURPOSE)[keyof typeof MEDIA_PURPOSE];
39
+ type MediaStatus = (typeof MEDIA_STATUS)[keyof typeof MEDIA_STATUS];
40
+ type ImageFormat = (typeof IMAGE_FORMAT)[keyof typeof IMAGE_FORMAT];
41
+ type ImageResizeType = (typeof IMAGE_RESIZE_TYPE)[keyof typeof IMAGE_RESIZE_TYPE];
42
+ interface ParsedMediaUrl {
43
+ /** Full original URL */
44
+ originalUrl: string;
45
+ /** CDN base including protocol and host */
46
+ cdnBase: string;
47
+ /** Object key without CDN base */
48
+ objectKey: string;
49
+ /** Purpose segment: avatar, hero, gallery */
50
+ purpose: MediaPurpose;
51
+ /** Type segment: image, video */
52
+ type: MediaType;
53
+ /** User ID segment */
54
+ userId: string;
55
+ /** File name without extension */
56
+ name: string;
57
+ /** File extension without dot */
58
+ extension: string;
59
+ }
60
+ interface ImageVariantOptions {
61
+ /** Target width in pixels (1-4096) */
62
+ width: number;
63
+ /** Target height in pixels (1-4096) */
64
+ height: number;
65
+ /** Resize type: fit, fill, auto */
66
+ resizeType: ImageResizeType;
67
+ /** Output format (default: webp) */
68
+ format?: ImageFormat;
69
+ /** Quality 1-100 */
70
+ quality?: number;
71
+ }
72
+
73
+ /**
74
+ * Parse a full media URL into structured components.
75
+ *
76
+ * Expected URL pattern:
77
+ * {cdnBase}/{purpose}/{type}/{userId}/{name}.{extension}
78
+ *
79
+ * Example:
80
+ * https://cdn.example.com/avatar/image/usr-1/abc.jpg
81
+ * → { cdnBase: "https://cdn.example.com", purpose: "avatar", type: "image", userId: "usr-1", name: "abc", extension: "jpg" }
82
+ */
83
+ declare function parseMediaUrl(originalUrl: string): ParsedMediaUrl;
84
+
85
+ /**
86
+ * Build a deterministic variant string from image variant options.
87
+ *
88
+ * Format: {width}x{height}-{resizeType}[-q{quality}]
89
+ *
90
+ * Examples:
91
+ * { width: 200, height: 200, resizeType: 'fit' } → "200x200-fit"
92
+ * { width: 200, height: 200, resizeType: 'fit', quality: 80 } → "200x200-fit-q80"
93
+ */
94
+ declare function buildVariantString(options: ImageVariantOptions): string;
95
+
96
+ /**
97
+ * Build a full CDN URL for an image variant.
98
+ *
99
+ * Takes an original image URL and variant options, returns the CDN URL
100
+ * for that specific variant.
101
+ *
102
+ * Example:
103
+ * buildImageVariantUrl("https://cdn.example.com/avatar/image/usr-1/abc.jpg", {
104
+ * width: 200, height: 200, resizeType: 'fit', quality: 80
105
+ * })
106
+ * → "https://cdn.example.com/avatar/image/usr-1/abc-200x200-fit-q80.webp"
107
+ */
108
+ declare function buildImageVariantUrl(originalUrl: string, options: ImageVariantOptions): string;
109
+
110
+ /**
111
+ * Build HLS playlist URL from an original video URL.
112
+ *
113
+ * Example:
114
+ * buildVideoHlsUrl("https://cdn.example.com/gallery/video/usr-1/xyz.mp4")
115
+ * → "https://cdn.example.com/gallery/video/usr-1/xyz/playlist.m3u8"
116
+ */
117
+ declare function buildVideoHlsUrl(originalUrl: string): string;
118
+
119
+ /**
120
+ * Build thumbnail URL from an original video URL.
121
+ *
122
+ * Example:
123
+ * buildVideoThumbnailUrl("https://cdn.example.com/gallery/video/usr-1/xyz.mp4")
124
+ * → "https://cdn.example.com/gallery/video/usr-1/xyz/thumbnail.webp"
125
+ */
126
+ declare function buildVideoThumbnailUrl(originalUrl: string): string;
127
+
128
+ export { IMAGE_FORMAT, IMAGE_RESIZE_TYPE, type ImageFormat, type ImageResizeType, type ImageVariantOptions, MEDIA_DEFAULTS, MEDIA_PURPOSE, MEDIA_STATUS, MEDIA_TYPE, type MediaPurpose, type MediaStatus, type MediaType, type ParsedMediaUrl, R2_PATH, buildImageVariantUrl, buildVariantString, buildVideoHlsUrl, buildVideoThumbnailUrl, parseMediaUrl };
@@ -0,0 +1,128 @@
1
+ declare const MEDIA_TYPE: {
2
+ readonly IMAGE: "image";
3
+ readonly VIDEO: "video";
4
+ };
5
+ declare const MEDIA_PURPOSE: {
6
+ readonly AVATAR: "avatar";
7
+ readonly HERO: "hero";
8
+ readonly GALLERY: "gallery";
9
+ };
10
+ declare const MEDIA_STATUS: {
11
+ readonly PENDING: "pending";
12
+ readonly UPLOADED: "uploaded";
13
+ readonly PROCESSING: "processing";
14
+ readonly READY: "ready";
15
+ readonly ERROR: "error";
16
+ };
17
+ declare const IMAGE_FORMAT: {
18
+ readonly WEBP: "webp";
19
+ readonly AVIF: "avif";
20
+ readonly JPEG: "jpeg";
21
+ readonly PNG: "png";
22
+ readonly JPG: "jpg";
23
+ };
24
+ declare const IMAGE_RESIZE_TYPE: {
25
+ readonly FIT: "fit";
26
+ readonly FILL: "fill";
27
+ readonly AUTO: "auto";
28
+ };
29
+ declare const R2_PATH: {
30
+ readonly HLS_PLAYLIST: "playlist.m3u8";
31
+ readonly THUMBNAIL: "thumbnail.webp";
32
+ };
33
+ declare const MEDIA_DEFAULTS: {
34
+ readonly IMAGE_FORMAT: "webp";
35
+ };
36
+
37
+ type MediaType = (typeof MEDIA_TYPE)[keyof typeof MEDIA_TYPE];
38
+ type MediaPurpose = (typeof MEDIA_PURPOSE)[keyof typeof MEDIA_PURPOSE];
39
+ type MediaStatus = (typeof MEDIA_STATUS)[keyof typeof MEDIA_STATUS];
40
+ type ImageFormat = (typeof IMAGE_FORMAT)[keyof typeof IMAGE_FORMAT];
41
+ type ImageResizeType = (typeof IMAGE_RESIZE_TYPE)[keyof typeof IMAGE_RESIZE_TYPE];
42
+ interface ParsedMediaUrl {
43
+ /** Full original URL */
44
+ originalUrl: string;
45
+ /** CDN base including protocol and host */
46
+ cdnBase: string;
47
+ /** Object key without CDN base */
48
+ objectKey: string;
49
+ /** Purpose segment: avatar, hero, gallery */
50
+ purpose: MediaPurpose;
51
+ /** Type segment: image, video */
52
+ type: MediaType;
53
+ /** User ID segment */
54
+ userId: string;
55
+ /** File name without extension */
56
+ name: string;
57
+ /** File extension without dot */
58
+ extension: string;
59
+ }
60
+ interface ImageVariantOptions {
61
+ /** Target width in pixels (1-4096) */
62
+ width: number;
63
+ /** Target height in pixels (1-4096) */
64
+ height: number;
65
+ /** Resize type: fit, fill, auto */
66
+ resizeType: ImageResizeType;
67
+ /** Output format (default: webp) */
68
+ format?: ImageFormat;
69
+ /** Quality 1-100 */
70
+ quality?: number;
71
+ }
72
+
73
+ /**
74
+ * Parse a full media URL into structured components.
75
+ *
76
+ * Expected URL pattern:
77
+ * {cdnBase}/{purpose}/{type}/{userId}/{name}.{extension}
78
+ *
79
+ * Example:
80
+ * https://cdn.example.com/avatar/image/usr-1/abc.jpg
81
+ * → { cdnBase: "https://cdn.example.com", purpose: "avatar", type: "image", userId: "usr-1", name: "abc", extension: "jpg" }
82
+ */
83
+ declare function parseMediaUrl(originalUrl: string): ParsedMediaUrl;
84
+
85
+ /**
86
+ * Build a deterministic variant string from image variant options.
87
+ *
88
+ * Format: {width}x{height}-{resizeType}[-q{quality}]
89
+ *
90
+ * Examples:
91
+ * { width: 200, height: 200, resizeType: 'fit' } → "200x200-fit"
92
+ * { width: 200, height: 200, resizeType: 'fit', quality: 80 } → "200x200-fit-q80"
93
+ */
94
+ declare function buildVariantString(options: ImageVariantOptions): string;
95
+
96
+ /**
97
+ * Build a full CDN URL for an image variant.
98
+ *
99
+ * Takes an original image URL and variant options, returns the CDN URL
100
+ * for that specific variant.
101
+ *
102
+ * Example:
103
+ * buildImageVariantUrl("https://cdn.example.com/avatar/image/usr-1/abc.jpg", {
104
+ * width: 200, height: 200, resizeType: 'fit', quality: 80
105
+ * })
106
+ * → "https://cdn.example.com/avatar/image/usr-1/abc-200x200-fit-q80.webp"
107
+ */
108
+ declare function buildImageVariantUrl(originalUrl: string, options: ImageVariantOptions): string;
109
+
110
+ /**
111
+ * Build HLS playlist URL from an original video URL.
112
+ *
113
+ * Example:
114
+ * buildVideoHlsUrl("https://cdn.example.com/gallery/video/usr-1/xyz.mp4")
115
+ * → "https://cdn.example.com/gallery/video/usr-1/xyz/playlist.m3u8"
116
+ */
117
+ declare function buildVideoHlsUrl(originalUrl: string): string;
118
+
119
+ /**
120
+ * Build thumbnail URL from an original video URL.
121
+ *
122
+ * Example:
123
+ * buildVideoThumbnailUrl("https://cdn.example.com/gallery/video/usr-1/xyz.mp4")
124
+ * → "https://cdn.example.com/gallery/video/usr-1/xyz/thumbnail.webp"
125
+ */
126
+ declare function buildVideoThumbnailUrl(originalUrl: string): string;
127
+
128
+ export { IMAGE_FORMAT, IMAGE_RESIZE_TYPE, type ImageFormat, type ImageResizeType, type ImageVariantOptions, MEDIA_DEFAULTS, MEDIA_PURPOSE, MEDIA_STATUS, MEDIA_TYPE, type MediaPurpose, type MediaStatus, type MediaType, type ParsedMediaUrl, R2_PATH, buildImageVariantUrl, buildVariantString, buildVideoHlsUrl, buildVideoThumbnailUrl, parseMediaUrl };
package/dist/index.js ADDED
@@ -0,0 +1,162 @@
1
+ // src/constants.ts
2
+ var MEDIA_TYPE = {
3
+ IMAGE: "image",
4
+ VIDEO: "video"
5
+ };
6
+ var MEDIA_PURPOSE = {
7
+ AVATAR: "avatar",
8
+ HERO: "hero",
9
+ GALLERY: "gallery"
10
+ };
11
+ var MEDIA_STATUS = {
12
+ PENDING: "pending",
13
+ UPLOADED: "uploaded",
14
+ PROCESSING: "processing",
15
+ READY: "ready",
16
+ ERROR: "error"
17
+ };
18
+ var IMAGE_FORMAT = {
19
+ WEBP: "webp",
20
+ AVIF: "avif",
21
+ JPEG: "jpeg",
22
+ PNG: "png",
23
+ JPG: "jpg"
24
+ };
25
+ var IMAGE_RESIZE_TYPE = {
26
+ FIT: "fit",
27
+ FILL: "fill",
28
+ AUTO: "auto"
29
+ };
30
+ var R2_PATH = {
31
+ HLS_PLAYLIST: "playlist.m3u8",
32
+ THUMBNAIL: "thumbnail.webp"
33
+ };
34
+ var MEDIA_DEFAULTS = {
35
+ IMAGE_FORMAT: IMAGE_FORMAT.WEBP
36
+ };
37
+
38
+ // src/parse-media-url.ts
39
+ var VALID_PURPOSES = new Set(Object.values(MEDIA_PURPOSE));
40
+ var VALID_TYPES = new Set(Object.values(MEDIA_TYPE));
41
+ function parseMediaUrl(originalUrl) {
42
+ let url;
43
+ try {
44
+ url = new URL(originalUrl);
45
+ } catch {
46
+ throw new Error(`Invalid media URL: "${originalUrl}"`);
47
+ }
48
+ const pathname = url.pathname.startsWith("/") ? url.pathname.slice(1) : url.pathname;
49
+ const segments = pathname.split("/");
50
+ if (segments.length < 4) {
51
+ throw new Error(
52
+ `Invalid media URL path: expected at least 4 segments (purpose/type/userId/filename), got ${segments.length} in "${pathname}"`
53
+ );
54
+ }
55
+ const [purpose, type, userId, filename] = segments;
56
+ if (!VALID_PURPOSES.has(purpose)) {
57
+ throw new Error(
58
+ `Invalid media purpose: "${purpose}". Expected one of: ${[...VALID_PURPOSES].join(", ")}`
59
+ );
60
+ }
61
+ if (!VALID_TYPES.has(type)) {
62
+ throw new Error(
63
+ `Invalid media type: "${type}". Expected one of: ${[...VALID_TYPES].join(", ")}`
64
+ );
65
+ }
66
+ if (!userId) {
67
+ throw new Error("Missing userId segment in media URL");
68
+ }
69
+ if (!filename) {
70
+ throw new Error("Missing filename segment in media URL");
71
+ }
72
+ const dotIndex = filename.lastIndexOf(".");
73
+ if (dotIndex <= 0) {
74
+ throw new Error(`Invalid filename: "${filename}". Expected format: "name.extension"`);
75
+ }
76
+ const name = filename.slice(0, dotIndex);
77
+ const extension = filename.slice(dotIndex + 1);
78
+ if (!extension) {
79
+ throw new Error(`Missing file extension in filename: "${filename}"`);
80
+ }
81
+ const cdnBase = `${url.protocol}//${url.host}`;
82
+ const objectKey = pathname;
83
+ return {
84
+ originalUrl,
85
+ cdnBase,
86
+ objectKey,
87
+ purpose,
88
+ type,
89
+ userId,
90
+ name,
91
+ extension
92
+ };
93
+ }
94
+
95
+ // src/build-variant-string.ts
96
+ function buildVariantString(options) {
97
+ validateVariantOptions(options);
98
+ const parts = [`${options.width}x${options.height}`, options.resizeType];
99
+ if (options.quality != null) {
100
+ parts.push(`q${options.quality}`);
101
+ }
102
+ return parts.join("-");
103
+ }
104
+ function validateVariantOptions(options) {
105
+ if (!Number.isInteger(options.width) || options.width < 1 || options.width > 4096) {
106
+ throw new Error(`Invalid width: ${options.width}. Must be an integer between 1 and 4096.`);
107
+ }
108
+ if (!Number.isInteger(options.height) || options.height < 1 || options.height > 4096) {
109
+ throw new Error(`Invalid height: ${options.height}. Must be an integer between 1 and 4096.`);
110
+ }
111
+ if (options.quality != null) {
112
+ if (!Number.isInteger(options.quality) || options.quality < 1 || options.quality > 100) {
113
+ throw new Error(
114
+ `Invalid quality: ${options.quality}. Must be an integer between 1 and 100.`
115
+ );
116
+ }
117
+ }
118
+ }
119
+
120
+ // src/build-image-variant-url.ts
121
+ function buildImageVariantUrl(originalUrl, options) {
122
+ const parsed = parseMediaUrl(originalUrl);
123
+ if (parsed.type !== "image") {
124
+ throw new Error(`Expected image URL, got type "${parsed.type}" in "${originalUrl}"`);
125
+ }
126
+ const variant = buildVariantString(options);
127
+ const format = options.format ?? MEDIA_DEFAULTS.IMAGE_FORMAT;
128
+ return `${parsed.cdnBase}/${parsed.purpose}/${parsed.type}/${parsed.userId}/${parsed.name}-${variant}.${format}`;
129
+ }
130
+
131
+ // src/build-video-hls-url.ts
132
+ function buildVideoHlsUrl(originalUrl) {
133
+ const parsed = parseMediaUrl(originalUrl);
134
+ if (parsed.type !== "video") {
135
+ throw new Error(`Expected video URL, got type "${parsed.type}" in "${originalUrl}"`);
136
+ }
137
+ return `${parsed.cdnBase}/${parsed.purpose}/video/${parsed.userId}/${parsed.name}/${R2_PATH.HLS_PLAYLIST}`;
138
+ }
139
+
140
+ // src/build-video-thumbnail-url.ts
141
+ function buildVideoThumbnailUrl(originalUrl) {
142
+ const parsed = parseMediaUrl(originalUrl);
143
+ if (parsed.type !== "video") {
144
+ throw new Error(`Expected video URL, got type "${parsed.type}" in "${originalUrl}"`);
145
+ }
146
+ return `${parsed.cdnBase}/${parsed.purpose}/video/${parsed.userId}/${parsed.name}/${R2_PATH.THUMBNAIL}`;
147
+ }
148
+ export {
149
+ IMAGE_FORMAT,
150
+ IMAGE_RESIZE_TYPE,
151
+ MEDIA_DEFAULTS,
152
+ MEDIA_PURPOSE,
153
+ MEDIA_STATUS,
154
+ MEDIA_TYPE,
155
+ R2_PATH,
156
+ buildImageVariantUrl,
157
+ buildVariantString,
158
+ buildVideoHlsUrl,
159
+ buildVideoThumbnailUrl,
160
+ parseMediaUrl
161
+ };
162
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/constants.ts","../src/parse-media-url.ts","../src/build-variant-string.ts","../src/build-image-variant-url.ts","../src/build-video-hls-url.ts","../src/build-video-thumbnail-url.ts"],"sourcesContent":["// ============================================================================\r\n// MEDIA TYPE\r\n// ============================================================================\r\n\r\nexport const MEDIA_TYPE = {\r\n IMAGE: 'image',\r\n VIDEO: 'video',\r\n} as const;\r\n\r\n// ============================================================================\r\n// MEDIA PURPOSE\r\n// ============================================================================\r\n\r\nexport const MEDIA_PURPOSE = {\r\n AVATAR: 'avatar',\r\n HERO: 'hero',\r\n GALLERY: 'gallery',\r\n} as const;\r\n\r\n// ============================================================================\r\n// MEDIA STATUS\r\n// ============================================================================\r\n\r\nexport const MEDIA_STATUS = {\r\n PENDING: 'pending',\r\n UPLOADED: 'uploaded',\r\n PROCESSING: 'processing',\r\n READY: 'ready',\r\n ERROR: 'error',\r\n} as const;\r\n\r\n// ============================================================================\r\n// IMAGE FORMAT\r\n// ============================================================================\r\n\r\nexport const IMAGE_FORMAT = {\r\n WEBP: 'webp',\r\n AVIF: 'avif',\r\n JPEG: 'jpeg',\r\n PNG: 'png',\r\n JPG: 'jpg',\r\n} as const;\r\n\r\n// ============================================================================\r\n// IMAGE RESIZE TYPE\r\n// ============================================================================\r\n\r\nexport const IMAGE_RESIZE_TYPE = {\r\n FIT: 'fit',\r\n FILL: 'fill',\r\n AUTO: 'auto',\r\n} as const;\r\n\r\n// ============================================================================\r\n// R2/CDN PATH CONSTANTS\r\n// ============================================================================\r\n\r\nexport const R2_PATH = {\r\n HLS_PLAYLIST: 'playlist.m3u8',\r\n THUMBNAIL: 'thumbnail.webp',\r\n} as const;\r\n\r\n// ============================================================================\r\n// DEFAULTS\r\n// ============================================================================\r\n\r\nexport const MEDIA_DEFAULTS = {\r\n IMAGE_FORMAT: IMAGE_FORMAT.WEBP,\r\n} as const;\r\n","import { MEDIA_PURPOSE, MEDIA_TYPE } from './constants';\r\nimport type { ParsedMediaUrl, MediaPurpose, MediaType } from './types';\r\n\r\nconst VALID_PURPOSES = new Set<string>(Object.values(MEDIA_PURPOSE));\r\nconst VALID_TYPES = new Set<string>(Object.values(MEDIA_TYPE));\r\n\r\n/**\r\n * Parse a full media URL into structured components.\r\n *\r\n * Expected URL pattern:\r\n * {cdnBase}/{purpose}/{type}/{userId}/{name}.{extension}\r\n *\r\n * Example:\r\n * https://cdn.example.com/avatar/image/usr-1/abc.jpg\r\n * → { cdnBase: \"https://cdn.example.com\", purpose: \"avatar\", type: \"image\", userId: \"usr-1\", name: \"abc\", extension: \"jpg\" }\r\n */\r\nexport function parseMediaUrl(originalUrl: string): ParsedMediaUrl {\r\n let url: URL;\r\n try {\r\n url = new URL(originalUrl);\r\n } catch {\r\n throw new Error(`Invalid media URL: \"${originalUrl}\"`);\r\n }\r\n\r\n // Remove leading slash and split into segments\r\n const pathname = url.pathname.startsWith('/') ? url.pathname.slice(1) : url.pathname;\r\n const segments = pathname.split('/');\r\n\r\n if (segments.length < 4) {\r\n throw new Error(\r\n `Invalid media URL path: expected at least 4 segments (purpose/type/userId/filename), got ${segments.length} in \"${pathname}\"`,\r\n );\r\n }\r\n\r\n const [purpose, type, userId, filename] = segments as [string, string, string, string];\r\n\r\n if (!VALID_PURPOSES.has(purpose)) {\r\n throw new Error(\r\n `Invalid media purpose: \"${purpose}\". Expected one of: ${[...VALID_PURPOSES].join(', ')}`,\r\n );\r\n }\r\n\r\n if (!VALID_TYPES.has(type)) {\r\n throw new Error(\r\n `Invalid media type: \"${type}\". Expected one of: ${[...VALID_TYPES].join(', ')}`,\r\n );\r\n }\r\n\r\n if (!userId) {\r\n throw new Error('Missing userId segment in media URL');\r\n }\r\n\r\n if (!filename) {\r\n throw new Error('Missing filename segment in media URL');\r\n }\r\n\r\n const dotIndex = filename.lastIndexOf('.');\r\n if (dotIndex <= 0) {\r\n throw new Error(`Invalid filename: \"${filename}\". Expected format: \"name.extension\"`);\r\n }\r\n\r\n const name = filename.slice(0, dotIndex);\r\n const extension = filename.slice(dotIndex + 1);\r\n\r\n if (!extension) {\r\n throw new Error(`Missing file extension in filename: \"${filename}\"`);\r\n }\r\n\r\n const cdnBase = `${url.protocol}//${url.host}`;\r\n const objectKey = pathname;\r\n\r\n return {\r\n originalUrl,\r\n cdnBase,\r\n objectKey,\r\n purpose: purpose as MediaPurpose,\r\n type: type as MediaType,\r\n userId,\r\n name,\r\n extension,\r\n };\r\n}\r\n","import type { ImageVariantOptions } from './types';\r\n\r\n/**\r\n * Build a deterministic variant string from image variant options.\r\n *\r\n * Format: {width}x{height}-{resizeType}[-q{quality}]\r\n *\r\n * Examples:\r\n * { width: 200, height: 200, resizeType: 'fit' } → \"200x200-fit\"\r\n * { width: 200, height: 200, resizeType: 'fit', quality: 80 } → \"200x200-fit-q80\"\r\n */\r\nexport function buildVariantString(options: ImageVariantOptions): string {\r\n validateVariantOptions(options);\r\n\r\n const parts: string[] = [`${options.width}x${options.height}`, options.resizeType];\r\n\r\n if (options.quality != null) {\r\n parts.push(`q${options.quality}`);\r\n }\r\n\r\n return parts.join('-');\r\n}\r\n\r\nfunction validateVariantOptions(options: ImageVariantOptions): void {\r\n if (!Number.isInteger(options.width) || options.width < 1 || options.width > 4096) {\r\n throw new Error(`Invalid width: ${options.width}. Must be an integer between 1 and 4096.`);\r\n }\r\n\r\n if (!Number.isInteger(options.height) || options.height < 1 || options.height > 4096) {\r\n throw new Error(`Invalid height: ${options.height}. Must be an integer between 1 and 4096.`);\r\n }\r\n\r\n if (options.quality != null) {\r\n if (!Number.isInteger(options.quality) || options.quality < 1 || options.quality > 100) {\r\n throw new Error(\r\n `Invalid quality: ${options.quality}. Must be an integer between 1 and 100.`,\r\n );\r\n }\r\n }\r\n}\r\n","import { MEDIA_DEFAULTS } from './constants';\r\nimport { parseMediaUrl } from './parse-media-url';\r\nimport { buildVariantString } from './build-variant-string';\r\nimport type { ImageVariantOptions } from './types';\r\n\r\n/**\r\n * Build a full CDN URL for an image variant.\r\n *\r\n * Takes an original image URL and variant options, returns the CDN URL\r\n * for that specific variant.\r\n *\r\n * Example:\r\n * buildImageVariantUrl(\"https://cdn.example.com/avatar/image/usr-1/abc.jpg\", {\r\n * width: 200, height: 200, resizeType: 'fit', quality: 80\r\n * })\r\n * → \"https://cdn.example.com/avatar/image/usr-1/abc-200x200-fit-q80.webp\"\r\n */\r\nexport function buildImageVariantUrl(originalUrl: string, options: ImageVariantOptions): string {\r\n const parsed = parseMediaUrl(originalUrl);\r\n\r\n if (parsed.type !== 'image') {\r\n throw new Error(`Expected image URL, got type \"${parsed.type}\" in \"${originalUrl}\"`);\r\n }\r\n\r\n const variant = buildVariantString(options);\r\n const format = options.format ?? MEDIA_DEFAULTS.IMAGE_FORMAT;\r\n\r\n return `${parsed.cdnBase}/${parsed.purpose}/${parsed.type}/${parsed.userId}/${parsed.name}-${variant}.${format}`;\r\n}\r\n","import { R2_PATH } from './constants';\r\nimport { parseMediaUrl } from './parse-media-url';\r\n\r\n/**\r\n * Build HLS playlist URL from an original video URL.\r\n *\r\n * Example:\r\n * buildVideoHlsUrl(\"https://cdn.example.com/gallery/video/usr-1/xyz.mp4\")\r\n * → \"https://cdn.example.com/gallery/video/usr-1/xyz/playlist.m3u8\"\r\n */\r\nexport function buildVideoHlsUrl(originalUrl: string): string {\r\n const parsed = parseMediaUrl(originalUrl);\r\n\r\n if (parsed.type !== 'video') {\r\n throw new Error(`Expected video URL, got type \"${parsed.type}\" in \"${originalUrl}\"`);\r\n }\r\n\r\n return `${parsed.cdnBase}/${parsed.purpose}/video/${parsed.userId}/${parsed.name}/${R2_PATH.HLS_PLAYLIST}`;\r\n}\r\n","import { R2_PATH } from './constants';\r\nimport { parseMediaUrl } from './parse-media-url';\r\n\r\n/**\r\n * Build thumbnail URL from an original video URL.\r\n *\r\n * Example:\r\n * buildVideoThumbnailUrl(\"https://cdn.example.com/gallery/video/usr-1/xyz.mp4\")\r\n * → \"https://cdn.example.com/gallery/video/usr-1/xyz/thumbnail.webp\"\r\n */\r\nexport function buildVideoThumbnailUrl(originalUrl: string): string {\r\n const parsed = parseMediaUrl(originalUrl);\r\n\r\n if (parsed.type !== 'video') {\r\n throw new Error(`Expected video URL, got type \"${parsed.type}\" in \"${originalUrl}\"`);\r\n }\r\n\r\n return `${parsed.cdnBase}/${parsed.purpose}/video/${parsed.userId}/${parsed.name}/${R2_PATH.THUMBNAIL}`;\r\n}\r\n"],"mappings":"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","names":[]}
package/package.json ADDED
@@ -0,0 +1,57 @@
1
+ {
2
+ "name": "@sproux/media-sdk",
3
+ "version": "0.1.0",
4
+ "description": "Media URL builder library for Sproux — build image variant URLs, video HLS playlist URLs, and thumbnails from original media URLs.",
5
+ "author": "Sproux",
6
+ "license": "MIT",
7
+ "type": "module",
8
+ "main": "./dist/index.cjs",
9
+ "module": "./dist/index.js",
10
+ "types": "./dist/index.d.ts",
11
+ "exports": {
12
+ ".": {
13
+ "import": {
14
+ "types": "./dist/index.d.ts",
15
+ "default": "./dist/index.js"
16
+ },
17
+ "require": {
18
+ "types": "./dist/index.d.cts",
19
+ "default": "./dist/index.cjs"
20
+ }
21
+ }
22
+ },
23
+ "files": [
24
+ "dist"
25
+ ],
26
+ "scripts": {
27
+ "build": "tsup",
28
+ "test": "vitest run",
29
+ "test:watch": "vitest",
30
+ "test:cov": "vitest run --coverage",
31
+ "lint": "tsc --noEmit",
32
+ "prepublishOnly": "pnpm build"
33
+ },
34
+ "devDependencies": {
35
+ "@vitest/coverage-v8": "^3.0.0",
36
+ "tsup": "^8.0.0",
37
+ "typescript": "^5.7.0",
38
+ "vitest": "^3.0.0"
39
+ },
40
+ "keywords": [
41
+ "media",
42
+ "cdn",
43
+ "image",
44
+ "video",
45
+ "url-builder",
46
+ "hls",
47
+ "sproux"
48
+ ],
49
+ "repository": {
50
+ "type": "git",
51
+ "url": "git+https://github.com/beincom/image-proxy-sdk.git"
52
+ },
53
+ "bugs": {
54
+ "url": "https://github.com/beincom/image-proxy-sdk/issues"
55
+ },
56
+ "homepage": "https://github.com/beincom/image-proxy-sdk#readme"
57
+ }