@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 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 using enums
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
- const url = media.getImageUrl('avatar/image/usr-1/photo', {
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: ImageUrlOptions): string`
87
+ ### `media.getImageUrl(objectKey: string, options?: ImageUrlOptions): string`
80
88
 
81
- Build a CDN URL for an image variant with specific dimensions, resize type, and quality.
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 **without** extension (e.g. `"avatar/image/usr-1/abc"`)
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
- media.getImageUrl('avatar/image/usr-1/photo', {
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
- | Property | Type | Required | Description |
101
- | ------------ | ----------------- | -------- | -------------------------------------- |
102
- | `extension` | `ImageFormat` | Yes | Output format (`webp`, `avif`, `jpeg`, `png`, `gif`, `ico`, `svg`, `jpg`) |
103
- | `width` | `number` | Yes | Target width in pixels (1–4096) |
104
- | `height` | `number` | Yes | Target height in pixels (1–4096) |
105
- | `resizeType` | `ImageResizeType` | Yes | Resize strategy: `fit`, `fill`, `force`, `fill-down`, `auto` |
106
- | `quality` | `number` | No | Quality 1–100 |
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, width, height, resizeType, quality? }
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 without extension (e.g. "avatar/image/usr-1/abc")
109
- * @param options - Image variant options including extension, dimensions, resize type, and optional quality
110
- * @returns Full CDN URL for the image variant
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
- * media.getImageUrl('avatar/image/usr-1/abc', {
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 variant = this.buildVariantString(options);
121
- return `${this.cdnUrl}/${objectKey}-${variant}.${options.extension}`;
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
- * Build a deterministic variant string from image options.
143
- *
144
- * Format: {width}x{height}-{resizeType}[-q{quality}]
169
+ * Parse an object key into name (path without extension) and extension.
145
170
  */
146
- buildVariantString(options) {
147
- const parts = [`${options.width}x${options.height}`, options.resizeType];
148
- if (options.quality != null) {
149
- parts.push(`q${options.quality}`);
171
+ parseObjectKey(objectKey) {
172
+ const lastDot = objectKey.lastIndexOf(".");
173
+ if (lastDot === -1) {
174
+ return { name: objectKey, extension: "" };
150
175
  }
151
- return parts.join("-");
176
+ return {
177
+ name: objectKey.substring(0, lastDot),
178
+ extension: objectKey.substring(lastDot + 1)
179
+ };
152
180
  }
153
181
  validateImageOptions(options) {
154
- if (!Number.isInteger(options.width) || options.width < 1 || options.width > 4096) {
155
- throw new Error(`Invalid width: ${options.width}. Must be an integer between 1 and 4096.`);
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 (!Number.isInteger(options.height) || options.height < 1 || options.height > 4096) {
158
- throw new Error(
159
- `Invalid height: ${options.height}. Must be an integer between 1 and 4096.`
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
@@ -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: ImageFormat;
50
+ extension?: ImageFormat;
51
51
  /** Target width in pixels (1-4096) */
52
- width: number;
52
+ width?: number;
53
53
  /** Target height in pixels (1-4096) */
54
- height: number;
54
+ height?: number;
55
55
  /** Resize type: fit, fill, auto */
56
- resizeType: ImageResizeType;
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 without extension (e.g. "avatar/image/usr-1/abc")
79
- * @param options - Image variant options including extension, dimensions, resize type, and optional quality
80
- * @returns Full CDN URL for the image variant
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
- * media.getImageUrl('avatar/image/usr-1/abc', {
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: ImageUrlOptions): string;
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
- * Build a deterministic variant string from image options.
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 buildVariantString;
112
+ private parseObjectKey;
109
113
  private validateImageOptions;
110
114
  }
111
115
 
112
- 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 };
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: ImageFormat;
50
+ extension?: ImageFormat;
51
51
  /** Target width in pixels (1-4096) */
52
- width: number;
52
+ width?: number;
53
53
  /** Target height in pixels (1-4096) */
54
- height: number;
54
+ height?: number;
55
55
  /** Resize type: fit, fill, auto */
56
- resizeType: ImageResizeType;
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 without extension (e.g. "avatar/image/usr-1/abc")
79
- * @param options - Image variant options including extension, dimensions, resize type, and optional quality
80
- * @returns Full CDN URL for the image variant
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
- * media.getImageUrl('avatar/image/usr-1/abc', {
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: ImageUrlOptions): string;
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
- * Build a deterministic variant string from image options.
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 buildVariantString;
112
+ private parseObjectKey;
109
113
  private validateImageOptions;
110
114
  }
111
115
 
112
- 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 };
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 without extension (e.g. "avatar/image/usr-1/abc")
76
- * @param options - Image variant options including extension, dimensions, resize type, and optional quality
77
- * @returns Full CDN URL for the image variant
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
- * media.getImageUrl('avatar/image/usr-1/abc', {
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 variant = this.buildVariantString(options);
88
- return `${this.cdnUrl}/${objectKey}-${variant}.${options.extension}`;
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
- * Build a deterministic variant string from image options.
110
- *
111
- * Format: {width}x{height}-{resizeType}[-q{quality}]
135
+ * Parse an object key into name (path without extension) and extension.
112
136
  */
113
- buildVariantString(options) {
114
- const parts = [`${options.width}x${options.height}`, options.resizeType];
115
- if (options.quality != null) {
116
- parts.push(`q${options.quality}`);
137
+ parseObjectKey(objectKey) {
138
+ const lastDot = objectKey.lastIndexOf(".");
139
+ if (lastDot === -1) {
140
+ return { name: objectKey, extension: "" };
117
141
  }
118
- return parts.join("-");
142
+ return {
143
+ name: objectKey.substring(0, lastDot),
144
+ extension: objectKey.substring(lastDot + 1)
145
+ };
119
146
  }
120
147
  validateImageOptions(options) {
121
- if (!Number.isInteger(options.width) || options.width < 1 || options.width > 4096) {
122
- throw new Error(`Invalid width: ${options.width}. Must be an integer between 1 and 4096.`);
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 (!Number.isInteger(options.height) || options.height < 1 || options.height > 4096) {
125
- throw new Error(
126
- `Invalid height: ${options.height}. Must be an integer between 1 and 4096.`
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":"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","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.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",