@sproux/media-sdk 0.1.1 → 0.1.3
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 +45 -15
- package/dist/index.cjs +56 -23
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +26 -15
- package/dist/index.d.ts +26 -15
- package/dist/index.js +54 -22
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -23,8 +23,8 @@ import { SprouxMedia, IMAGE_FORMAT, IMAGE_RESIZE_TYPE } from '@sproux/media-sdk'
|
|
|
23
23
|
// Initialize once (e.g. at app startup)
|
|
24
24
|
const media = SprouxMedia.init({ cdnUrl: 'https://cdn.example.com' });
|
|
25
25
|
|
|
26
|
-
// Build an image variant URL
|
|
27
|
-
const imageUrl = media.getImageUrl('avatar/image/usr-1/abc', {
|
|
26
|
+
// Build an image variant URL with all options
|
|
27
|
+
const imageUrl = media.getImageUrl('avatar/image/usr-1/abc.jpg', {
|
|
28
28
|
extension: IMAGE_FORMAT.WEBP,
|
|
29
29
|
width: 200,
|
|
30
30
|
height: 200,
|
|
@@ -33,6 +33,10 @@ const imageUrl = media.getImageUrl('avatar/image/usr-1/abc', {
|
|
|
33
33
|
});
|
|
34
34
|
// → "https://cdn.example.com/avatar/image/usr-1/abc-200x200-fit-q80.webp"
|
|
35
35
|
|
|
36
|
+
// Or with just the object key (no variant transformation)
|
|
37
|
+
const originalUrl = media.getImageUrl('avatar/image/usr-1/abc.jpg');
|
|
38
|
+
// → "https://cdn.example.com/avatar/image/usr-1/abc.jpg"
|
|
39
|
+
|
|
36
40
|
// Build a video HLS playlist URL
|
|
37
41
|
const hlsUrl = media.getVideoHlsUrl('gallery/video/usr-1/intro');
|
|
38
42
|
// → "https://cdn.example.com/gallery/video/usr-1/intro/playlist.m3u8"
|
|
@@ -52,12 +56,16 @@ SprouxMedia.init({ cdnUrl: 'https://cdn.example.com' });
|
|
|
52
56
|
|
|
53
57
|
// Access from anywhere via getInstance()
|
|
54
58
|
const media = SprouxMedia.getInstance();
|
|
55
|
-
|
|
59
|
+
// With variant options
|
|
60
|
+
const url = media.getImageUrl('avatar/image/usr-1/photo.jpg', {
|
|
56
61
|
extension: IMAGE_FORMAT.WEBP,
|
|
57
62
|
width: 400,
|
|
58
63
|
height: 300,
|
|
59
64
|
resizeType: IMAGE_RESIZE_TYPE.FILL,
|
|
60
65
|
});
|
|
66
|
+
|
|
67
|
+
// Without options — returns the original URL
|
|
68
|
+
const original = media.getImageUrl('avatar/image/usr-1/photo.jpg');
|
|
61
69
|
```
|
|
62
70
|
|
|
63
71
|
> Calling `getInstance()` before `init()` will throw an error.
|
|
@@ -76,16 +84,18 @@ const media = SprouxMedia.init({ cdnUrl: 'https://cdn.example.com' });
|
|
|
76
84
|
|
|
77
85
|
Returns the existing singleton instance. Throws if `init()` has not been called.
|
|
78
86
|
|
|
79
|
-
### `media.getImageUrl(objectKey: string, options
|
|
87
|
+
### `media.getImageUrl(objectKey: string, options?: ImageUrlOptions): string`
|
|
80
88
|
|
|
81
|
-
Build a CDN URL for an image variant
|
|
89
|
+
Build a CDN URL for an image variant. When `options` is omitted, returns the original CDN URL for the object key as-is.
|
|
82
90
|
|
|
83
|
-
- `objectKey` — Object key **
|
|
91
|
+
- `objectKey` — Object key **with** extension (e.g. `"avatar/image/usr-1/abc.jpg"`)
|
|
92
|
+
- `options` — Optional variant transformation options
|
|
84
93
|
|
|
85
94
|
```typescript
|
|
86
95
|
import { IMAGE_FORMAT, IMAGE_RESIZE_TYPE } from '@sproux/media-sdk';
|
|
87
96
|
|
|
88
|
-
|
|
97
|
+
// With variant options
|
|
98
|
+
media.getImageUrl('avatar/image/usr-1/photo.jpg', {
|
|
89
99
|
extension: IMAGE_FORMAT.WEBP,
|
|
90
100
|
width: 400,
|
|
91
101
|
height: 300,
|
|
@@ -93,17 +103,24 @@ media.getImageUrl('avatar/image/usr-1/photo', {
|
|
|
93
103
|
quality: 85,
|
|
94
104
|
});
|
|
95
105
|
// → "https://cdn.example.com/avatar/image/usr-1/photo-400x300-fill-q85.webp"
|
|
106
|
+
|
|
107
|
+
// Without options — returns the original URL
|
|
108
|
+
media.getImageUrl('avatar/image/usr-1/photo.jpg');
|
|
109
|
+
// → "https://cdn.example.com/avatar/image/usr-1/photo.jpg"
|
|
96
110
|
```
|
|
97
111
|
|
|
98
112
|
#### ImageUrlOptions
|
|
99
113
|
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
|
104
|
-
|
|
|
105
|
-
| `
|
|
106
|
-
| `
|
|
114
|
+
All fields are optional.
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
| Property | Type | Required | Description |
|
|
118
|
+
| -------------- | ------------------- | ---------- | --------------------------------------------------------------------------- |
|
|
119
|
+
| `extension` | `ImageFormat` | No | Output format (`webp`, `avif`, `jpeg`, `png`, `gif`, `ico`, `svg`, `jpg`) |
|
|
120
|
+
| `width` | `number` | No | Target width in pixels (1–4096) |
|
|
121
|
+
| `height` | `number` | No | Target height in pixels (1–4096) |
|
|
122
|
+
| `resizeType` | `ImageResizeType` | No | Resize strategy:`fit`, `fill`, `force`, `fill-down`, `auto` |
|
|
123
|
+
| `quality` | `number` | No | Quality 1–100 |
|
|
107
124
|
|
|
108
125
|
### `media.getVideoHlsUrl(objectKey: string): string`
|
|
109
126
|
|
|
@@ -167,12 +184,25 @@ import type {
|
|
|
167
184
|
ImageFormat, // 'webp' | 'avif' | 'jpeg' | 'png' | 'gif' | 'ico' | 'svg' | 'jpg'
|
|
168
185
|
ImageResizeType, // 'fit' | 'fill' | 'force' | 'fill-down' | 'auto'
|
|
169
186
|
SprouxMediaConfig, // { cdnUrl: string }
|
|
170
|
-
ImageUrlOptions, // { extension
|
|
187
|
+
ImageUrlOptions, // { extension?, width?, height?, resizeType?, quality? }
|
|
171
188
|
} from '@sproux/media-sdk';
|
|
172
189
|
```
|
|
173
190
|
|
|
174
191
|
> `ImageFormat` and `ImageResizeType` are string union types derived from their respective enums. You can use either the enum values (`IMAGE_FORMAT.WEBP`) or raw strings (`'webp'`).
|
|
175
192
|
|
|
193
|
+
## Helper
|
|
194
|
+
|
|
195
|
+
```typescript
|
|
196
|
+
import { buildVariantString } from '@sproux/media-sdk';
|
|
197
|
+
|
|
198
|
+
const variant = buildVariantString({
|
|
199
|
+
width: 200,
|
|
200
|
+
height: 200,
|
|
201
|
+
resizeType: IMAGE_RESIZE_TYPE.FIT,
|
|
202
|
+
quality: 80,
|
|
203
|
+
}); // 200x200-fit-q80
|
|
204
|
+
```
|
|
205
|
+
|
|
176
206
|
## URL Structure
|
|
177
207
|
|
|
178
208
|
### Image Variant URL
|
package/dist/index.cjs
CHANGED
|
@@ -27,7 +27,8 @@ __export(index_exports, {
|
|
|
27
27
|
MEDIA_STATUS: () => MEDIA_STATUS,
|
|
28
28
|
MEDIA_TYPE: () => MEDIA_TYPE,
|
|
29
29
|
R2_PATH: () => R2_PATH,
|
|
30
|
-
SprouxMedia: () => SprouxMedia
|
|
30
|
+
SprouxMedia: () => SprouxMedia,
|
|
31
|
+
buildVariantString: () => buildVariantString
|
|
31
32
|
});
|
|
32
33
|
module.exports = __toCommonJS(index_exports);
|
|
33
34
|
|
|
@@ -75,6 +76,21 @@ var MEDIA_DEFAULTS = {
|
|
|
75
76
|
IMAGE_FORMAT: "webp" /* WEBP */
|
|
76
77
|
};
|
|
77
78
|
|
|
79
|
+
// src/sproux-media.helper.ts
|
|
80
|
+
function buildVariantString(options) {
|
|
81
|
+
const parts = [];
|
|
82
|
+
if (options.width != null && options.height != null) {
|
|
83
|
+
parts.push(`${options.width}x${options.height}`);
|
|
84
|
+
}
|
|
85
|
+
if (options.resizeType != null) {
|
|
86
|
+
parts.push(options.resizeType);
|
|
87
|
+
}
|
|
88
|
+
if (options.quality != null) {
|
|
89
|
+
parts.push(`q${options.quality}`);
|
|
90
|
+
}
|
|
91
|
+
return parts.join("-");
|
|
92
|
+
}
|
|
93
|
+
|
|
78
94
|
// src/sproux-media.ts
|
|
79
95
|
var SprouxMedia = class _SprouxMedia {
|
|
80
96
|
static instance = null;
|
|
@@ -103,22 +119,33 @@ var SprouxMedia = class _SprouxMedia {
|
|
|
103
119
|
return _SprouxMedia.instance;
|
|
104
120
|
}
|
|
105
121
|
/**
|
|
106
|
-
* Build a CDN URL for an image variant.
|
|
122
|
+
* Build a CDN URL for an image, optionally with variant transformation.
|
|
107
123
|
*
|
|
108
|
-
* @param objectKey - Object key
|
|
109
|
-
* @param options -
|
|
110
|
-
* @returns Full CDN URL
|
|
124
|
+
* @param objectKey - Object key with extension (e.g. "avatar/image/usr-1/abc.jpg")
|
|
125
|
+
* @param options - Optional image variant options (extension, dimensions, resize type, quality)
|
|
126
|
+
* @returns Full CDN URL — variant URL if options are provided, original URL otherwise
|
|
111
127
|
*
|
|
112
128
|
* @example
|
|
113
|
-
*
|
|
129
|
+
* // With variant options
|
|
130
|
+
* media.getImageUrl('avatar/image/usr-1/abc.jpg', {
|
|
114
131
|
* extension: 'webp', width: 200, height: 200, resizeType: 'fit', quality: 80,
|
|
115
132
|
* });
|
|
116
133
|
* // → "https://cdn.example.com/avatar/image/usr-1/abc-200x200-fit-q80.webp"
|
|
134
|
+
*
|
|
135
|
+
* @example
|
|
136
|
+
* // Without options — returns the original URL
|
|
137
|
+
* media.getImageUrl('avatar/image/usr-1/abc.jpg');
|
|
138
|
+
* // → "https://cdn.example.com/avatar/image/usr-1/abc.jpg"
|
|
117
139
|
*/
|
|
118
140
|
getImageUrl(objectKey, options) {
|
|
141
|
+
if (!options || Object.keys(options).length === 0) {
|
|
142
|
+
return `${this.cdnUrl}/${objectKey}`;
|
|
143
|
+
}
|
|
119
144
|
this.validateImageOptions(options);
|
|
120
|
-
const
|
|
121
|
-
|
|
145
|
+
const { name, extension: originalExt } = this.parseObjectKey(objectKey);
|
|
146
|
+
const ext = options.extension ?? originalExt;
|
|
147
|
+
const variant = buildVariantString(options);
|
|
148
|
+
return variant ? `${this.cdnUrl}/${name}-${variant}.${ext}` : `${this.cdnUrl}/${objectKey}`;
|
|
122
149
|
}
|
|
123
150
|
/**
|
|
124
151
|
* Build a CDN URL for a video HLS playlist.
|
|
@@ -139,25 +166,30 @@ var SprouxMedia = class _SprouxMedia {
|
|
|
139
166
|
return `${this.cdnUrl}/${objectKey}/${R2_PATH.THUMBNAIL}`;
|
|
140
167
|
}
|
|
141
168
|
/**
|
|
142
|
-
*
|
|
143
|
-
*
|
|
144
|
-
* Format: {width}x{height}-{resizeType}[-q{quality}]
|
|
169
|
+
* Parse an object key into name (path without extension) and extension.
|
|
145
170
|
*/
|
|
146
|
-
|
|
147
|
-
const
|
|
148
|
-
if (
|
|
149
|
-
|
|
171
|
+
parseObjectKey(objectKey) {
|
|
172
|
+
const lastDot = objectKey.lastIndexOf(".");
|
|
173
|
+
if (lastDot === -1) {
|
|
174
|
+
return { name: objectKey, extension: "" };
|
|
150
175
|
}
|
|
151
|
-
return
|
|
176
|
+
return {
|
|
177
|
+
name: objectKey.substring(0, lastDot),
|
|
178
|
+
extension: objectKey.substring(lastDot + 1)
|
|
179
|
+
};
|
|
152
180
|
}
|
|
153
181
|
validateImageOptions(options) {
|
|
154
|
-
if (
|
|
155
|
-
|
|
182
|
+
if (options.width != null) {
|
|
183
|
+
if (!Number.isInteger(options.width) || options.width < 1 || options.width > 4096) {
|
|
184
|
+
throw new Error(`Invalid width: ${options.width}. Must be an integer between 1 and 4096.`);
|
|
185
|
+
}
|
|
156
186
|
}
|
|
157
|
-
if (
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
187
|
+
if (options.height != null) {
|
|
188
|
+
if (!Number.isInteger(options.height) || options.height < 1 || options.height > 4096) {
|
|
189
|
+
throw new Error(
|
|
190
|
+
`Invalid height: ${options.height}. Must be an integer between 1 and 4096.`
|
|
191
|
+
);
|
|
192
|
+
}
|
|
161
193
|
}
|
|
162
194
|
if (options.quality != null) {
|
|
163
195
|
if (!Number.isInteger(options.quality) || options.quality < 1 || options.quality > 100) {
|
|
@@ -177,6 +209,7 @@ var SprouxMedia = class _SprouxMedia {
|
|
|
177
209
|
MEDIA_STATUS,
|
|
178
210
|
MEDIA_TYPE,
|
|
179
211
|
R2_PATH,
|
|
180
|
-
SprouxMedia
|
|
212
|
+
SprouxMedia,
|
|
213
|
+
buildVariantString
|
|
181
214
|
});
|
|
182
215
|
//# sourceMappingURL=index.cjs.map
|
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts","../src/constants.ts","../src/sproux-media.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 { ImageFormat, ImageResizeType, SprouxMediaConfig, ImageUrlOptions } from './types';\r\n\r\n// Singleton class\r\nexport { SprouxMedia } from './sproux-media';\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 enum IMAGE_FORMAT {\r\n WEBP = 'webp',\r\n AVIF = 'avif',\r\n JPEG = 'jpeg',\r\n PNG = 'png',\r\n GIF = 'gif',\r\n ICO = 'ico',\r\n SVG = 'svg',\r\n JPG = 'jpg',\r\n}\r\n\r\n// ============================================================================\r\n// IMAGE RESIZE TYPE\r\n// ============================================================================\r\n\r\nexport enum IMAGE_RESIZE_TYPE {\r\n FIT = 'fit',\r\n FILL = 'fill',\r\n FORCE = 'force',\r\n FILL_DOWN = 'fill-down',\r\n AUTO = 'auto',\r\n}\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 { R2_PATH } from './constants';\r\nimport type { SprouxMediaConfig, ImageUrlOptions } from './types';\r\n\r\nexport class SprouxMedia {\r\n private static instance: SprouxMedia | null = null;\r\n\r\n private readonly cdnUrl: string;\r\n\r\n private constructor(config: SprouxMediaConfig) {\r\n this.cdnUrl = config.cdnUrl.replace(/\\/+$/, '');\r\n }\r\n\r\n /**\r\n * Initialize the singleton with CDN configuration.\r\n * Returns the singleton instance.\r\n */\r\n static init(config: SprouxMediaConfig): SprouxMedia {\r\n if (SprouxMedia.instance) {\r\n return SprouxMedia.instance;\r\n }\r\n \r\n return (SprouxMedia.instance = new SprouxMedia(config));\r\n }\r\n\r\n /**\r\n * Returns the existing singleton instance.\r\n * Throws if `init()` has not been called.\r\n */\r\n static getInstance(): SprouxMedia {\r\n if (!SprouxMedia.instance) {\r\n throw new Error('SprouxMedia has not been initialized. Call SprouxMedia.init() first.');\r\n }\r\n return SprouxMedia.instance;\r\n }\r\n\r\n /**\r\n * Build a CDN URL for an image variant.\r\n *\r\n * @param objectKey - Object key without extension (e.g. \"avatar/image/usr-1/abc\")\r\n * @param options - Image variant options including extension, dimensions, resize type, and optional quality\r\n * @returns Full CDN URL for the image variant\r\n *\r\n * @example\r\n * media.getImageUrl('avatar/image/usr-1/abc', {\r\n * extension: 'webp', 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\n getImageUrl(objectKey: string, options: ImageUrlOptions): string {\r\n this.validateImageOptions(options);\r\n const variant = this.buildVariantString(options);\r\n return `${this.cdnUrl}/${objectKey}-${variant}.${options.extension}`;\r\n }\r\n\r\n /**\r\n * Build a CDN URL for a video HLS playlist.\r\n *\r\n * @param objectKey - Object key without extension (e.g. \"gallery/video/usr-1/xyz\")\r\n * @returns Full CDN URL for the HLS playlist\r\n */\r\n getVideoHlsUrl(objectKey: string): string {\r\n return `${this.cdnUrl}/${objectKey}/${R2_PATH.HLS_PLAYLIST}`;\r\n }\r\n\r\n /**\r\n * Build a CDN URL for a video thumbnail.\r\n *\r\n * @param objectKey - Object key without extension (e.g. \"gallery/video/usr-1/xyz\")\r\n * @returns Full CDN URL for the thumbnail\r\n */\r\n getVideoThumbnailUrl(objectKey: string): string {\r\n return `${this.cdnUrl}/${objectKey}/${R2_PATH.THUMBNAIL}`;\r\n }\r\n\r\n /**\r\n * Build a deterministic variant string from image options.\r\n *\r\n * Format: {width}x{height}-{resizeType}[-q{quality}]\r\n */\r\n private buildVariantString(options: ImageUrlOptions): string {\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\n private validateImageOptions(options: ImageUrlOptions): 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(\r\n `Invalid height: ${options.height}. Must be an integer between 1 and 4096.`,\r\n );\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}\r\n"],"mappings":"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","names":["IMAGE_FORMAT","IMAGE_RESIZE_TYPE"]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/constants.ts","../src/sproux-media.helper.ts","../src/sproux-media.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 { ImageFormat, ImageResizeType, SprouxMediaConfig, ImageUrlOptions } from './types';\r\n\r\n// Singleton class\r\nexport { SprouxMedia } from './sproux-media';\r\n\r\n// Helper functions\r\nexport { buildVariantString } from './sproux-media.helper';\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 enum IMAGE_FORMAT {\r\n WEBP = 'webp',\r\n AVIF = 'avif',\r\n JPEG = 'jpeg',\r\n PNG = 'png',\r\n GIF = 'gif',\r\n ICO = 'ico',\r\n SVG = 'svg',\r\n JPG = 'jpg',\r\n}\r\n\r\n// ============================================================================\r\n// IMAGE RESIZE TYPE\r\n// ============================================================================\r\n\r\nexport enum IMAGE_RESIZE_TYPE {\r\n FIT = 'fit',\r\n FILL = 'fill',\r\n FORCE = 'force',\r\n FILL_DOWN = 'fill-down',\r\n AUTO = 'auto',\r\n}\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 { ImageUrlOptions } from \"./types\";\r\n\r\n /**\r\n * Build a deterministic variant string from image options.\r\n *\r\n * Format: {width}x{height}-{resizeType}[-q{quality}]\r\n */\r\n export function buildVariantString(options: ImageUrlOptions): string {\r\n const parts: string[] = [];\r\n\r\n if (options.width != null && options.height != null) {\r\n parts.push(`${options.width}x${options.height}`);\r\n }\r\n\r\n if (options.resizeType != null) {\r\n parts.push(options.resizeType);\r\n }\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 }","import { R2_PATH } from './constants';\r\nimport { buildVariantString } from './sproux-media.helper';\r\nimport type { SprouxMediaConfig, ImageUrlOptions } from './types';\r\n\r\nexport class SprouxMedia {\r\n private static instance: SprouxMedia | null = null;\r\n\r\n private readonly cdnUrl: string;\r\n\r\n private constructor(config: SprouxMediaConfig) {\r\n this.cdnUrl = config.cdnUrl.replace(/\\/+$/, '');\r\n }\r\n\r\n /**\r\n * Initialize the singleton with CDN configuration.\r\n * Returns the singleton instance.\r\n */\r\n static init(config: SprouxMediaConfig): SprouxMedia {\r\n if (SprouxMedia.instance) {\r\n return SprouxMedia.instance;\r\n }\r\n \r\n return (SprouxMedia.instance = new SprouxMedia(config));\r\n }\r\n\r\n /**\r\n * Returns the existing singleton instance.\r\n * Throws if `init()` has not been called.\r\n */\r\n static getInstance(): SprouxMedia {\r\n if (!SprouxMedia.instance) {\r\n throw new Error('SprouxMedia has not been initialized. Call SprouxMedia.init() first.');\r\n }\r\n return SprouxMedia.instance;\r\n }\r\n\r\n /**\r\n * Build a CDN URL for an image, optionally with variant transformation.\r\n *\r\n * @param objectKey - Object key with extension (e.g. \"avatar/image/usr-1/abc.jpg\")\r\n * @param options - Optional image variant options (extension, dimensions, resize type, quality)\r\n * @returns Full CDN URL — variant URL if options are provided, original URL otherwise\r\n *\r\n * @example\r\n * // With variant options\r\n * media.getImageUrl('avatar/image/usr-1/abc.jpg', {\r\n * extension: 'webp', 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\n * @example\r\n * // Without options — returns the original URL\r\n * media.getImageUrl('avatar/image/usr-1/abc.jpg');\r\n * // → \"https://cdn.example.com/avatar/image/usr-1/abc.jpg\"\r\n */\r\n getImageUrl(objectKey: string, options?: ImageUrlOptions): string {\r\n if (!options || Object.keys(options).length === 0) {\r\n return `${this.cdnUrl}/${objectKey}`;\r\n }\r\n\r\n this.validateImageOptions(options);\r\n\r\n const { name, extension: originalExt } = this.parseObjectKey(objectKey);\r\n const ext = options.extension ?? originalExt;\r\n const variant = buildVariantString(options);\r\n\r\n return variant\r\n ? `${this.cdnUrl}/${name}-${variant}.${ext}`\r\n : `${this.cdnUrl}/${objectKey}`;\r\n }\r\n\r\n /**\r\n * Build a CDN URL for a video HLS playlist.\r\n *\r\n * @param objectKey - Object key without extension (e.g. \"gallery/video/usr-1/xyz\")\r\n * @returns Full CDN URL for the HLS playlist\r\n */\r\n getVideoHlsUrl(objectKey: string): string {\r\n return `${this.cdnUrl}/${objectKey}/${R2_PATH.HLS_PLAYLIST}`;\r\n }\r\n\r\n /**\r\n * Build a CDN URL for a video thumbnail.\r\n *\r\n * @param objectKey - Object key without extension (e.g. \"gallery/video/usr-1/xyz\")\r\n * @returns Full CDN URL for the thumbnail\r\n */\r\n getVideoThumbnailUrl(objectKey: string): string {\r\n return `${this.cdnUrl}/${objectKey}/${R2_PATH.THUMBNAIL}`;\r\n }\r\n\r\n /**\r\n * Parse an object key into name (path without extension) and extension.\r\n */\r\n private parseObjectKey(objectKey: string): { name: string; extension: string } {\r\n const lastDot = objectKey.lastIndexOf('.');\r\n if (lastDot === -1) {\r\n return { name: objectKey, extension: '' };\r\n }\r\n return {\r\n name: objectKey.substring(0, lastDot),\r\n extension: objectKey.substring(lastDot + 1),\r\n };\r\n }\r\n\r\n\r\n\r\n private validateImageOptions(options: ImageUrlOptions): void {\r\n if (options.width != null) {\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\r\n if (options.height != null) {\r\n if (!Number.isInteger(options.height) || options.height < 1 || options.height > 4096) {\r\n throw new Error(\r\n `Invalid height: ${options.height}. Must be an integer between 1 and 4096.`,\r\n );\r\n }\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}\r\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACIO,IAAM,aAAa;AAAA,EACxB,OAAO;AAAA,EACP,OAAO;AACT;AAMO,IAAM,gBAAgB;AAAA,EAC3B,QAAQ;AAAA,EACR,MAAM;AAAA,EACN,SAAS;AACX;AAMO,IAAM,eAAe;AAAA,EAC1B,SAAS;AAAA,EACT,UAAU;AAAA,EACV,YAAY;AAAA,EACZ,OAAO;AAAA,EACP,OAAO;AACT;AAMO,IAAK,eAAL,kBAAKA,kBAAL;AACL,EAAAA,cAAA,UAAO;AACP,EAAAA,cAAA,UAAO;AACP,EAAAA,cAAA,UAAO;AACP,EAAAA,cAAA,SAAM;AACN,EAAAA,cAAA,SAAM;AACN,EAAAA,cAAA,SAAM;AACN,EAAAA,cAAA,SAAM;AACN,EAAAA,cAAA,SAAM;AARI,SAAAA;AAAA,GAAA;AAeL,IAAK,oBAAL,kBAAKC,uBAAL;AACL,EAAAA,mBAAA,SAAM;AACN,EAAAA,mBAAA,UAAO;AACP,EAAAA,mBAAA,WAAQ;AACR,EAAAA,mBAAA,eAAY;AACZ,EAAAA,mBAAA,UAAO;AALG,SAAAA;AAAA,GAAA;AAYL,IAAM,UAAU;AAAA,EACrB,cAAc;AAAA,EACd,WAAW;AACb;AAMO,IAAM,iBAAiB;AAAA,EAC5B,cAAc;AAChB;;;AClES,SAAS,mBAAmB,SAAkC;AACnE,QAAM,QAAkB,CAAC;AAEzB,MAAI,QAAQ,SAAS,QAAQ,QAAQ,UAAU,MAAM;AACnD,UAAM,KAAK,GAAG,QAAQ,KAAK,IAAI,QAAQ,MAAM,EAAE;AAAA,EACjD;AAEA,MAAI,QAAQ,cAAc,MAAM;AAC9B,UAAM,KAAK,QAAQ,UAAU;AAAA,EAC/B;AAEA,MAAI,QAAQ,WAAW,MAAM;AAC3B,UAAM,KAAK,IAAI,QAAQ,OAAO,EAAE;AAAA,EAClC;AAEA,SAAO,MAAM,KAAK,GAAG;AACvB;;;ACnBK,IAAM,cAAN,MAAM,aAAY;AAAA,EACvB,OAAe,WAA+B;AAAA,EAE7B;AAAA,EAET,YAAY,QAA2B;AAC7C,SAAK,SAAS,OAAO,OAAO,QAAQ,QAAQ,EAAE;AAAA,EAChD;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,OAAO,KAAK,QAAwC;AAClD,QAAI,aAAY,UAAU;AACxB,aAAO,aAAY;AAAA,IACrB;AAEA,WAAQ,aAAY,WAAW,IAAI,aAAY,MAAM;AAAA,EACvD;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,OAAO,cAA2B;AAChC,QAAI,CAAC,aAAY,UAAU;AACzB,YAAM,IAAI,MAAM,sEAAsE;AAAA,IACxF;AACA,WAAO,aAAY;AAAA,EACrB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAqBA,YAAY,WAAmB,SAAmC;AAChE,QAAI,CAAC,WAAW,OAAO,KAAK,OAAO,EAAE,WAAW,GAAG;AACjD,aAAO,GAAG,KAAK,MAAM,IAAI,SAAS;AAAA,IACpC;AAEA,SAAK,qBAAqB,OAAO;AAEjC,UAAM,EAAE,MAAM,WAAW,YAAY,IAAI,KAAK,eAAe,SAAS;AACtE,UAAM,MAAM,QAAQ,aAAa;AACjC,UAAM,UAAU,mBAAmB,OAAO;AAE1C,WAAO,UACH,GAAG,KAAK,MAAM,IAAI,IAAI,IAAI,OAAO,IAAI,GAAG,KACxC,GAAG,KAAK,MAAM,IAAI,SAAS;AAAA,EACjC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,eAAe,WAA2B;AACxC,WAAO,GAAG,KAAK,MAAM,IAAI,SAAS,IAAI,QAAQ,YAAY;AAAA,EAC5D;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,qBAAqB,WAA2B;AAC9C,WAAO,GAAG,KAAK,MAAM,IAAI,SAAS,IAAI,QAAQ,SAAS;AAAA,EACzD;AAAA;AAAA;AAAA;AAAA,EAKQ,eAAe,WAAwD;AAC7E,UAAM,UAAU,UAAU,YAAY,GAAG;AACzC,QAAI,YAAY,IAAI;AAClB,aAAO,EAAE,MAAM,WAAW,WAAW,GAAG;AAAA,IAC1C;AACA,WAAO;AAAA,MACL,MAAM,UAAU,UAAU,GAAG,OAAO;AAAA,MACpC,WAAW,UAAU,UAAU,UAAU,CAAC;AAAA,IAC5C;AAAA,EACF;AAAA,EAIQ,qBAAqB,SAAgC;AAC3D,QAAI,QAAQ,SAAS,MAAM;AACzB,UAAI,CAAC,OAAO,UAAU,QAAQ,KAAK,KAAK,QAAQ,QAAQ,KAAK,QAAQ,QAAQ,MAAM;AACjF,cAAM,IAAI,MAAM,kBAAkB,QAAQ,KAAK,0CAA0C;AAAA,MAC3F;AAAA,IACF;AAEA,QAAI,QAAQ,UAAU,MAAM;AAC1B,UAAI,CAAC,OAAO,UAAU,QAAQ,MAAM,KAAK,QAAQ,SAAS,KAAK,QAAQ,SAAS,MAAM;AACpF,cAAM,IAAI;AAAA,UACR,mBAAmB,QAAQ,MAAM;AAAA,QACnC;AAAA,MACF;AAAA,IACF;AAEA,QAAI,QAAQ,WAAW,MAAM;AAC3B,UAAI,CAAC,OAAO,UAAU,QAAQ,OAAO,KAAK,QAAQ,UAAU,KAAK,QAAQ,UAAU,KAAK;AACtF,cAAM,IAAI;AAAA,UACR,oBAAoB,QAAQ,OAAO;AAAA,QACrC;AAAA,MACF;AAAA,IACF;AAAA,EACF;AACF;","names":["IMAGE_FORMAT","IMAGE_RESIZE_TYPE"]}
|
package/dist/index.d.cts
CHANGED
|
@@ -47,13 +47,13 @@ interface SprouxMediaConfig {
|
|
|
47
47
|
}
|
|
48
48
|
interface ImageUrlOptions {
|
|
49
49
|
/** Output format extension (e.g. "webp", "avif") */
|
|
50
|
-
extension
|
|
50
|
+
extension?: ImageFormat;
|
|
51
51
|
/** Target width in pixels (1-4096) */
|
|
52
|
-
width
|
|
52
|
+
width?: number;
|
|
53
53
|
/** Target height in pixels (1-4096) */
|
|
54
|
-
height
|
|
54
|
+
height?: number;
|
|
55
55
|
/** Resize type: fit, fill, auto */
|
|
56
|
-
resizeType
|
|
56
|
+
resizeType?: ImageResizeType;
|
|
57
57
|
/** Quality 1-100 (optional) */
|
|
58
58
|
quality?: number;
|
|
59
59
|
}
|
|
@@ -73,19 +73,25 @@ declare class SprouxMedia {
|
|
|
73
73
|
*/
|
|
74
74
|
static getInstance(): SprouxMedia;
|
|
75
75
|
/**
|
|
76
|
-
* Build a CDN URL for an image variant.
|
|
76
|
+
* Build a CDN URL for an image, optionally with variant transformation.
|
|
77
77
|
*
|
|
78
|
-
* @param objectKey - Object key
|
|
79
|
-
* @param options -
|
|
80
|
-
* @returns Full CDN URL
|
|
78
|
+
* @param objectKey - Object key with extension (e.g. "avatar/image/usr-1/abc.jpg")
|
|
79
|
+
* @param options - Optional image variant options (extension, dimensions, resize type, quality)
|
|
80
|
+
* @returns Full CDN URL — variant URL if options are provided, original URL otherwise
|
|
81
81
|
*
|
|
82
82
|
* @example
|
|
83
|
-
*
|
|
83
|
+
* // With variant options
|
|
84
|
+
* media.getImageUrl('avatar/image/usr-1/abc.jpg', {
|
|
84
85
|
* extension: 'webp', width: 200, height: 200, resizeType: 'fit', quality: 80,
|
|
85
86
|
* });
|
|
86
87
|
* // → "https://cdn.example.com/avatar/image/usr-1/abc-200x200-fit-q80.webp"
|
|
88
|
+
*
|
|
89
|
+
* @example
|
|
90
|
+
* // Without options — returns the original URL
|
|
91
|
+
* media.getImageUrl('avatar/image/usr-1/abc.jpg');
|
|
92
|
+
* // → "https://cdn.example.com/avatar/image/usr-1/abc.jpg"
|
|
87
93
|
*/
|
|
88
|
-
getImageUrl(objectKey: string, options
|
|
94
|
+
getImageUrl(objectKey: string, options?: ImageUrlOptions): string;
|
|
89
95
|
/**
|
|
90
96
|
* Build a CDN URL for a video HLS playlist.
|
|
91
97
|
*
|
|
@@ -101,12 +107,17 @@ declare class SprouxMedia {
|
|
|
101
107
|
*/
|
|
102
108
|
getVideoThumbnailUrl(objectKey: string): string;
|
|
103
109
|
/**
|
|
104
|
-
*
|
|
105
|
-
*
|
|
106
|
-
* Format: {width}x{height}-{resizeType}[-q{quality}]
|
|
110
|
+
* Parse an object key into name (path without extension) and extension.
|
|
107
111
|
*/
|
|
108
|
-
private
|
|
112
|
+
private parseObjectKey;
|
|
109
113
|
private validateImageOptions;
|
|
110
114
|
}
|
|
111
115
|
|
|
112
|
-
|
|
116
|
+
/**
|
|
117
|
+
* Build a deterministic variant string from image options.
|
|
118
|
+
*
|
|
119
|
+
* Format: {width}x{height}-{resizeType}[-q{quality}]
|
|
120
|
+
*/
|
|
121
|
+
declare function buildVariantString(options: ImageUrlOptions): string;
|
|
122
|
+
|
|
123
|
+
export { IMAGE_FORMAT, IMAGE_RESIZE_TYPE, type ImageFormat, type ImageResizeType, type ImageUrlOptions, MEDIA_DEFAULTS, MEDIA_PURPOSE, MEDIA_STATUS, MEDIA_TYPE, R2_PATH, SprouxMedia, type SprouxMediaConfig, buildVariantString };
|
package/dist/index.d.ts
CHANGED
|
@@ -47,13 +47,13 @@ interface SprouxMediaConfig {
|
|
|
47
47
|
}
|
|
48
48
|
interface ImageUrlOptions {
|
|
49
49
|
/** Output format extension (e.g. "webp", "avif") */
|
|
50
|
-
extension
|
|
50
|
+
extension?: ImageFormat;
|
|
51
51
|
/** Target width in pixels (1-4096) */
|
|
52
|
-
width
|
|
52
|
+
width?: number;
|
|
53
53
|
/** Target height in pixels (1-4096) */
|
|
54
|
-
height
|
|
54
|
+
height?: number;
|
|
55
55
|
/** Resize type: fit, fill, auto */
|
|
56
|
-
resizeType
|
|
56
|
+
resizeType?: ImageResizeType;
|
|
57
57
|
/** Quality 1-100 (optional) */
|
|
58
58
|
quality?: number;
|
|
59
59
|
}
|
|
@@ -73,19 +73,25 @@ declare class SprouxMedia {
|
|
|
73
73
|
*/
|
|
74
74
|
static getInstance(): SprouxMedia;
|
|
75
75
|
/**
|
|
76
|
-
* Build a CDN URL for an image variant.
|
|
76
|
+
* Build a CDN URL for an image, optionally with variant transformation.
|
|
77
77
|
*
|
|
78
|
-
* @param objectKey - Object key
|
|
79
|
-
* @param options -
|
|
80
|
-
* @returns Full CDN URL
|
|
78
|
+
* @param objectKey - Object key with extension (e.g. "avatar/image/usr-1/abc.jpg")
|
|
79
|
+
* @param options - Optional image variant options (extension, dimensions, resize type, quality)
|
|
80
|
+
* @returns Full CDN URL — variant URL if options are provided, original URL otherwise
|
|
81
81
|
*
|
|
82
82
|
* @example
|
|
83
|
-
*
|
|
83
|
+
* // With variant options
|
|
84
|
+
* media.getImageUrl('avatar/image/usr-1/abc.jpg', {
|
|
84
85
|
* extension: 'webp', width: 200, height: 200, resizeType: 'fit', quality: 80,
|
|
85
86
|
* });
|
|
86
87
|
* // → "https://cdn.example.com/avatar/image/usr-1/abc-200x200-fit-q80.webp"
|
|
88
|
+
*
|
|
89
|
+
* @example
|
|
90
|
+
* // Without options — returns the original URL
|
|
91
|
+
* media.getImageUrl('avatar/image/usr-1/abc.jpg');
|
|
92
|
+
* // → "https://cdn.example.com/avatar/image/usr-1/abc.jpg"
|
|
87
93
|
*/
|
|
88
|
-
getImageUrl(objectKey: string, options
|
|
94
|
+
getImageUrl(objectKey: string, options?: ImageUrlOptions): string;
|
|
89
95
|
/**
|
|
90
96
|
* Build a CDN URL for a video HLS playlist.
|
|
91
97
|
*
|
|
@@ -101,12 +107,17 @@ declare class SprouxMedia {
|
|
|
101
107
|
*/
|
|
102
108
|
getVideoThumbnailUrl(objectKey: string): string;
|
|
103
109
|
/**
|
|
104
|
-
*
|
|
105
|
-
*
|
|
106
|
-
* Format: {width}x{height}-{resizeType}[-q{quality}]
|
|
110
|
+
* Parse an object key into name (path without extension) and extension.
|
|
107
111
|
*/
|
|
108
|
-
private
|
|
112
|
+
private parseObjectKey;
|
|
109
113
|
private validateImageOptions;
|
|
110
114
|
}
|
|
111
115
|
|
|
112
|
-
|
|
116
|
+
/**
|
|
117
|
+
* Build a deterministic variant string from image options.
|
|
118
|
+
*
|
|
119
|
+
* Format: {width}x{height}-{resizeType}[-q{quality}]
|
|
120
|
+
*/
|
|
121
|
+
declare function buildVariantString(options: ImageUrlOptions): string;
|
|
122
|
+
|
|
123
|
+
export { IMAGE_FORMAT, IMAGE_RESIZE_TYPE, type ImageFormat, type ImageResizeType, type ImageUrlOptions, MEDIA_DEFAULTS, MEDIA_PURPOSE, MEDIA_STATUS, MEDIA_TYPE, R2_PATH, SprouxMedia, type SprouxMediaConfig, buildVariantString };
|
package/dist/index.js
CHANGED
|
@@ -42,6 +42,21 @@ var MEDIA_DEFAULTS = {
|
|
|
42
42
|
IMAGE_FORMAT: "webp" /* WEBP */
|
|
43
43
|
};
|
|
44
44
|
|
|
45
|
+
// src/sproux-media.helper.ts
|
|
46
|
+
function buildVariantString(options) {
|
|
47
|
+
const parts = [];
|
|
48
|
+
if (options.width != null && options.height != null) {
|
|
49
|
+
parts.push(`${options.width}x${options.height}`);
|
|
50
|
+
}
|
|
51
|
+
if (options.resizeType != null) {
|
|
52
|
+
parts.push(options.resizeType);
|
|
53
|
+
}
|
|
54
|
+
if (options.quality != null) {
|
|
55
|
+
parts.push(`q${options.quality}`);
|
|
56
|
+
}
|
|
57
|
+
return parts.join("-");
|
|
58
|
+
}
|
|
59
|
+
|
|
45
60
|
// src/sproux-media.ts
|
|
46
61
|
var SprouxMedia = class _SprouxMedia {
|
|
47
62
|
static instance = null;
|
|
@@ -70,22 +85,33 @@ var SprouxMedia = class _SprouxMedia {
|
|
|
70
85
|
return _SprouxMedia.instance;
|
|
71
86
|
}
|
|
72
87
|
/**
|
|
73
|
-
* Build a CDN URL for an image variant.
|
|
88
|
+
* Build a CDN URL for an image, optionally with variant transformation.
|
|
74
89
|
*
|
|
75
|
-
* @param objectKey - Object key
|
|
76
|
-
* @param options -
|
|
77
|
-
* @returns Full CDN URL
|
|
90
|
+
* @param objectKey - Object key with extension (e.g. "avatar/image/usr-1/abc.jpg")
|
|
91
|
+
* @param options - Optional image variant options (extension, dimensions, resize type, quality)
|
|
92
|
+
* @returns Full CDN URL — variant URL if options are provided, original URL otherwise
|
|
78
93
|
*
|
|
79
94
|
* @example
|
|
80
|
-
*
|
|
95
|
+
* // With variant options
|
|
96
|
+
* media.getImageUrl('avatar/image/usr-1/abc.jpg', {
|
|
81
97
|
* extension: 'webp', width: 200, height: 200, resizeType: 'fit', quality: 80,
|
|
82
98
|
* });
|
|
83
99
|
* // → "https://cdn.example.com/avatar/image/usr-1/abc-200x200-fit-q80.webp"
|
|
100
|
+
*
|
|
101
|
+
* @example
|
|
102
|
+
* // Without options — returns the original URL
|
|
103
|
+
* media.getImageUrl('avatar/image/usr-1/abc.jpg');
|
|
104
|
+
* // → "https://cdn.example.com/avatar/image/usr-1/abc.jpg"
|
|
84
105
|
*/
|
|
85
106
|
getImageUrl(objectKey, options) {
|
|
107
|
+
if (!options || Object.keys(options).length === 0) {
|
|
108
|
+
return `${this.cdnUrl}/${objectKey}`;
|
|
109
|
+
}
|
|
86
110
|
this.validateImageOptions(options);
|
|
87
|
-
const
|
|
88
|
-
|
|
111
|
+
const { name, extension: originalExt } = this.parseObjectKey(objectKey);
|
|
112
|
+
const ext = options.extension ?? originalExt;
|
|
113
|
+
const variant = buildVariantString(options);
|
|
114
|
+
return variant ? `${this.cdnUrl}/${name}-${variant}.${ext}` : `${this.cdnUrl}/${objectKey}`;
|
|
89
115
|
}
|
|
90
116
|
/**
|
|
91
117
|
* Build a CDN URL for a video HLS playlist.
|
|
@@ -106,25 +132,30 @@ var SprouxMedia = class _SprouxMedia {
|
|
|
106
132
|
return `${this.cdnUrl}/${objectKey}/${R2_PATH.THUMBNAIL}`;
|
|
107
133
|
}
|
|
108
134
|
/**
|
|
109
|
-
*
|
|
110
|
-
*
|
|
111
|
-
* Format: {width}x{height}-{resizeType}[-q{quality}]
|
|
135
|
+
* Parse an object key into name (path without extension) and extension.
|
|
112
136
|
*/
|
|
113
|
-
|
|
114
|
-
const
|
|
115
|
-
if (
|
|
116
|
-
|
|
137
|
+
parseObjectKey(objectKey) {
|
|
138
|
+
const lastDot = objectKey.lastIndexOf(".");
|
|
139
|
+
if (lastDot === -1) {
|
|
140
|
+
return { name: objectKey, extension: "" };
|
|
117
141
|
}
|
|
118
|
-
return
|
|
142
|
+
return {
|
|
143
|
+
name: objectKey.substring(0, lastDot),
|
|
144
|
+
extension: objectKey.substring(lastDot + 1)
|
|
145
|
+
};
|
|
119
146
|
}
|
|
120
147
|
validateImageOptions(options) {
|
|
121
|
-
if (
|
|
122
|
-
|
|
148
|
+
if (options.width != null) {
|
|
149
|
+
if (!Number.isInteger(options.width) || options.width < 1 || options.width > 4096) {
|
|
150
|
+
throw new Error(`Invalid width: ${options.width}. Must be an integer between 1 and 4096.`);
|
|
151
|
+
}
|
|
123
152
|
}
|
|
124
|
-
if (
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
153
|
+
if (options.height != null) {
|
|
154
|
+
if (!Number.isInteger(options.height) || options.height < 1 || options.height > 4096) {
|
|
155
|
+
throw new Error(
|
|
156
|
+
`Invalid height: ${options.height}. Must be an integer between 1 and 4096.`
|
|
157
|
+
);
|
|
158
|
+
}
|
|
128
159
|
}
|
|
129
160
|
if (options.quality != null) {
|
|
130
161
|
if (!Number.isInteger(options.quality) || options.quality < 1 || options.quality > 100) {
|
|
@@ -143,6 +174,7 @@ export {
|
|
|
143
174
|
MEDIA_STATUS,
|
|
144
175
|
MEDIA_TYPE,
|
|
145
176
|
R2_PATH,
|
|
146
|
-
SprouxMedia
|
|
177
|
+
SprouxMedia,
|
|
178
|
+
buildVariantString
|
|
147
179
|
};
|
|
148
180
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/constants.ts","../src/sproux-media.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 enum IMAGE_FORMAT {\r\n WEBP = 'webp',\r\n AVIF = 'avif',\r\n JPEG = 'jpeg',\r\n PNG = 'png',\r\n GIF = 'gif',\r\n ICO = 'ico',\r\n SVG = 'svg',\r\n JPG = 'jpg',\r\n}\r\n\r\n// ============================================================================\r\n// IMAGE RESIZE TYPE\r\n// ============================================================================\r\n\r\nexport enum IMAGE_RESIZE_TYPE {\r\n FIT = 'fit',\r\n FILL = 'fill',\r\n FORCE = 'force',\r\n FILL_DOWN = 'fill-down',\r\n AUTO = 'auto',\r\n}\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 { R2_PATH } from './constants';\r\nimport type { SprouxMediaConfig, ImageUrlOptions } from './types';\r\n\r\nexport class SprouxMedia {\r\n private static instance: SprouxMedia | null = null;\r\n\r\n private readonly cdnUrl: string;\r\n\r\n private constructor(config: SprouxMediaConfig) {\r\n this.cdnUrl = config.cdnUrl.replace(/\\/+$/, '');\r\n }\r\n\r\n /**\r\n * Initialize the singleton with CDN configuration.\r\n * Returns the singleton instance.\r\n */\r\n static init(config: SprouxMediaConfig): SprouxMedia {\r\n if (SprouxMedia.instance) {\r\n return SprouxMedia.instance;\r\n }\r\n \r\n return (SprouxMedia.instance = new SprouxMedia(config));\r\n }\r\n\r\n /**\r\n * Returns the existing singleton instance.\r\n * Throws if `init()` has not been called.\r\n */\r\n static getInstance(): SprouxMedia {\r\n if (!SprouxMedia.instance) {\r\n throw new Error('SprouxMedia has not been initialized. Call SprouxMedia.init() first.');\r\n }\r\n return SprouxMedia.instance;\r\n }\r\n\r\n /**\r\n * Build a CDN URL for an image variant.\r\n *\r\n * @param objectKey - Object key without extension (e.g. \"avatar/image/usr-1/abc\")\r\n * @param options - Image variant options including extension, dimensions, resize type, and optional quality\r\n * @returns Full CDN URL for the image variant\r\n *\r\n * @example\r\n * media.getImageUrl('avatar/image/usr-1/abc', {\r\n * extension: 'webp', 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\n getImageUrl(objectKey: string, options: ImageUrlOptions): string {\r\n this.validateImageOptions(options);\r\n const variant = this.buildVariantString(options);\r\n return `${this.cdnUrl}/${objectKey}-${variant}.${options.extension}`;\r\n }\r\n\r\n /**\r\n * Build a CDN URL for a video HLS playlist.\r\n *\r\n * @param objectKey - Object key without extension (e.g. \"gallery/video/usr-1/xyz\")\r\n * @returns Full CDN URL for the HLS playlist\r\n */\r\n getVideoHlsUrl(objectKey: string): string {\r\n return `${this.cdnUrl}/${objectKey}/${R2_PATH.HLS_PLAYLIST}`;\r\n }\r\n\r\n /**\r\n * Build a CDN URL for a video thumbnail.\r\n *\r\n * @param objectKey - Object key without extension (e.g. \"gallery/video/usr-1/xyz\")\r\n * @returns Full CDN URL for the thumbnail\r\n */\r\n getVideoThumbnailUrl(objectKey: string): string {\r\n return `${this.cdnUrl}/${objectKey}/${R2_PATH.THUMBNAIL}`;\r\n }\r\n\r\n /**\r\n * Build a deterministic variant string from image options.\r\n *\r\n * Format: {width}x{height}-{resizeType}[-q{quality}]\r\n */\r\n private buildVariantString(options: ImageUrlOptions): string {\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\n private validateImageOptions(options: ImageUrlOptions): 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(\r\n `Invalid height: ${options.height}. Must be an integer between 1 and 4096.`,\r\n );\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}\r\n"],"mappings":";AAIO,IAAM,aAAa;AAAA,EACxB,OAAO;AAAA,EACP,OAAO;AACT;AAMO,IAAM,gBAAgB;AAAA,EAC3B,QAAQ;AAAA,EACR,MAAM;AAAA,EACN,SAAS;AACX;AAMO,IAAM,eAAe;AAAA,EAC1B,SAAS;AAAA,EACT,UAAU;AAAA,EACV,YAAY;AAAA,EACZ,OAAO;AAAA,EACP,OAAO;AACT;AAMO,IAAK,eAAL,kBAAKA,kBAAL;AACL,EAAAA,cAAA,UAAO;AACP,EAAAA,cAAA,UAAO;AACP,EAAAA,cAAA,UAAO;AACP,EAAAA,cAAA,SAAM;AACN,EAAAA,cAAA,SAAM;AACN,EAAAA,cAAA,SAAM;AACN,EAAAA,cAAA,SAAM;AACN,EAAAA,cAAA,SAAM;AARI,SAAAA;AAAA,GAAA;AAeL,IAAK,oBAAL,kBAAKC,uBAAL;AACL,EAAAA,mBAAA,SAAM;AACN,EAAAA,mBAAA,UAAO;AACP,EAAAA,mBAAA,WAAQ;AACR,EAAAA,mBAAA,eAAY;AACZ,EAAAA,mBAAA,UAAO;AALG,SAAAA;AAAA,GAAA;AAYL,IAAM,UAAU;AAAA,EACrB,cAAc;AAAA,EACd,WAAW;AACb;AAMO,IAAM,iBAAiB;AAAA,EAC5B,cAAc;AAChB;;;ACtEO,IAAM,cAAN,MAAM,aAAY;AAAA,EACvB,OAAe,WAA+B;AAAA,EAE7B;AAAA,EAET,YAAY,QAA2B;AAC7C,SAAK,SAAS,OAAO,OAAO,QAAQ,QAAQ,EAAE;AAAA,EAChD;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,OAAO,KAAK,QAAwC;AAClD,QAAI,aAAY,UAAU;AACxB,aAAO,aAAY;AAAA,IACrB;AAEA,WAAQ,aAAY,WAAW,IAAI,aAAY,MAAM;AAAA,EACvD;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,OAAO,cAA2B;AAChC,QAAI,CAAC,aAAY,UAAU;AACzB,YAAM,IAAI,MAAM,sEAAsE;AAAA,IACxF;AACA,WAAO,aAAY;AAAA,EACrB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAeA,YAAY,WAAmB,SAAkC;AAC/D,SAAK,qBAAqB,OAAO;AACjC,UAAM,UAAU,KAAK,mBAAmB,OAAO;AAC/C,WAAO,GAAG,KAAK,MAAM,IAAI,SAAS,IAAI,OAAO,IAAI,QAAQ,SAAS;AAAA,EACpE;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,eAAe,WAA2B;AACxC,WAAO,GAAG,KAAK,MAAM,IAAI,SAAS,IAAI,QAAQ,YAAY;AAAA,EAC5D;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,qBAAqB,WAA2B;AAC9C,WAAO,GAAG,KAAK,MAAM,IAAI,SAAS,IAAI,QAAQ,SAAS;AAAA,EACzD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOQ,mBAAmB,SAAkC;AAC3D,UAAM,QAAkB,CAAC,GAAG,QAAQ,KAAK,IAAI,QAAQ,MAAM,IAAI,QAAQ,UAAU;AAEjF,QAAI,QAAQ,WAAW,MAAM;AAC3B,YAAM,KAAK,IAAI,QAAQ,OAAO,EAAE;AAAA,IAClC;AAEA,WAAO,MAAM,KAAK,GAAG;AAAA,EACvB;AAAA,EAEQ,qBAAqB,SAAgC;AAC3D,QAAI,CAAC,OAAO,UAAU,QAAQ,KAAK,KAAK,QAAQ,QAAQ,KAAK,QAAQ,QAAQ,MAAM;AACjF,YAAM,IAAI,MAAM,kBAAkB,QAAQ,KAAK,0CAA0C;AAAA,IAC3F;AAEA,QAAI,CAAC,OAAO,UAAU,QAAQ,MAAM,KAAK,QAAQ,SAAS,KAAK,QAAQ,SAAS,MAAM;AACpF,YAAM,IAAI;AAAA,QACR,mBAAmB,QAAQ,MAAM;AAAA,MACnC;AAAA,IACF;AAEA,QAAI,QAAQ,WAAW,MAAM;AAC3B,UAAI,CAAC,OAAO,UAAU,QAAQ,OAAO,KAAK,QAAQ,UAAU,KAAK,QAAQ,UAAU,KAAK;AACtF,cAAM,IAAI;AAAA,UACR,oBAAoB,QAAQ,OAAO;AAAA,QACrC;AAAA,MACF;AAAA,IACF;AAAA,EACF;AACF;","names":["IMAGE_FORMAT","IMAGE_RESIZE_TYPE"]}
|
|
1
|
+
{"version":3,"sources":["../src/constants.ts","../src/sproux-media.helper.ts","../src/sproux-media.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 enum IMAGE_FORMAT {\r\n WEBP = 'webp',\r\n AVIF = 'avif',\r\n JPEG = 'jpeg',\r\n PNG = 'png',\r\n GIF = 'gif',\r\n ICO = 'ico',\r\n SVG = 'svg',\r\n JPG = 'jpg',\r\n}\r\n\r\n// ============================================================================\r\n// IMAGE RESIZE TYPE\r\n// ============================================================================\r\n\r\nexport enum IMAGE_RESIZE_TYPE {\r\n FIT = 'fit',\r\n FILL = 'fill',\r\n FORCE = 'force',\r\n FILL_DOWN = 'fill-down',\r\n AUTO = 'auto',\r\n}\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 { ImageUrlOptions } from \"./types\";\r\n\r\n /**\r\n * Build a deterministic variant string from image options.\r\n *\r\n * Format: {width}x{height}-{resizeType}[-q{quality}]\r\n */\r\n export function buildVariantString(options: ImageUrlOptions): string {\r\n const parts: string[] = [];\r\n\r\n if (options.width != null && options.height != null) {\r\n parts.push(`${options.width}x${options.height}`);\r\n }\r\n\r\n if (options.resizeType != null) {\r\n parts.push(options.resizeType);\r\n }\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 }","import { R2_PATH } from './constants';\r\nimport { buildVariantString } from './sproux-media.helper';\r\nimport type { SprouxMediaConfig, ImageUrlOptions } from './types';\r\n\r\nexport class SprouxMedia {\r\n private static instance: SprouxMedia | null = null;\r\n\r\n private readonly cdnUrl: string;\r\n\r\n private constructor(config: SprouxMediaConfig) {\r\n this.cdnUrl = config.cdnUrl.replace(/\\/+$/, '');\r\n }\r\n\r\n /**\r\n * Initialize the singleton with CDN configuration.\r\n * Returns the singleton instance.\r\n */\r\n static init(config: SprouxMediaConfig): SprouxMedia {\r\n if (SprouxMedia.instance) {\r\n return SprouxMedia.instance;\r\n }\r\n \r\n return (SprouxMedia.instance = new SprouxMedia(config));\r\n }\r\n\r\n /**\r\n * Returns the existing singleton instance.\r\n * Throws if `init()` has not been called.\r\n */\r\n static getInstance(): SprouxMedia {\r\n if (!SprouxMedia.instance) {\r\n throw new Error('SprouxMedia has not been initialized. Call SprouxMedia.init() first.');\r\n }\r\n return SprouxMedia.instance;\r\n }\r\n\r\n /**\r\n * Build a CDN URL for an image, optionally with variant transformation.\r\n *\r\n * @param objectKey - Object key with extension (e.g. \"avatar/image/usr-1/abc.jpg\")\r\n * @param options - Optional image variant options (extension, dimensions, resize type, quality)\r\n * @returns Full CDN URL — variant URL if options are provided, original URL otherwise\r\n *\r\n * @example\r\n * // With variant options\r\n * media.getImageUrl('avatar/image/usr-1/abc.jpg', {\r\n * extension: 'webp', 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\n * @example\r\n * // Without options — returns the original URL\r\n * media.getImageUrl('avatar/image/usr-1/abc.jpg');\r\n * // → \"https://cdn.example.com/avatar/image/usr-1/abc.jpg\"\r\n */\r\n getImageUrl(objectKey: string, options?: ImageUrlOptions): string {\r\n if (!options || Object.keys(options).length === 0) {\r\n return `${this.cdnUrl}/${objectKey}`;\r\n }\r\n\r\n this.validateImageOptions(options);\r\n\r\n const { name, extension: originalExt } = this.parseObjectKey(objectKey);\r\n const ext = options.extension ?? originalExt;\r\n const variant = buildVariantString(options);\r\n\r\n return variant\r\n ? `${this.cdnUrl}/${name}-${variant}.${ext}`\r\n : `${this.cdnUrl}/${objectKey}`;\r\n }\r\n\r\n /**\r\n * Build a CDN URL for a video HLS playlist.\r\n *\r\n * @param objectKey - Object key without extension (e.g. \"gallery/video/usr-1/xyz\")\r\n * @returns Full CDN URL for the HLS playlist\r\n */\r\n getVideoHlsUrl(objectKey: string): string {\r\n return `${this.cdnUrl}/${objectKey}/${R2_PATH.HLS_PLAYLIST}`;\r\n }\r\n\r\n /**\r\n * Build a CDN URL for a video thumbnail.\r\n *\r\n * @param objectKey - Object key without extension (e.g. \"gallery/video/usr-1/xyz\")\r\n * @returns Full CDN URL for the thumbnail\r\n */\r\n getVideoThumbnailUrl(objectKey: string): string {\r\n return `${this.cdnUrl}/${objectKey}/${R2_PATH.THUMBNAIL}`;\r\n }\r\n\r\n /**\r\n * Parse an object key into name (path without extension) and extension.\r\n */\r\n private parseObjectKey(objectKey: string): { name: string; extension: string } {\r\n const lastDot = objectKey.lastIndexOf('.');\r\n if (lastDot === -1) {\r\n return { name: objectKey, extension: '' };\r\n }\r\n return {\r\n name: objectKey.substring(0, lastDot),\r\n extension: objectKey.substring(lastDot + 1),\r\n };\r\n }\r\n\r\n\r\n\r\n private validateImageOptions(options: ImageUrlOptions): void {\r\n if (options.width != null) {\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\r\n if (options.height != null) {\r\n if (!Number.isInteger(options.height) || options.height < 1 || options.height > 4096) {\r\n throw new Error(\r\n `Invalid height: ${options.height}. Must be an integer between 1 and 4096.`,\r\n );\r\n }\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}\r\n"],"mappings":"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","names":["IMAGE_FORMAT","IMAGE_RESIZE_TYPE"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sproux/media-sdk",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.3",
|
|
4
4
|
"description": "Media URL builder library for Sproux — build image variant URLs, video HLS playlist URLs, and thumbnails from original media URLs.",
|
|
5
5
|
"author": "Sproux",
|
|
6
6
|
"license": "MIT",
|