@sproux/media-sdk 0.1.2 → 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
@@ -113,13 +113,14 @@ media.getImageUrl('avatar/image/usr-1/photo.jpg');
113
113
 
114
114
  All fields are optional.
115
115
 
116
- | Property | Type | Required | Description |
117
- | ------------ | ----------------- | -------- | -------------------------------------- |
116
+
117
+ | Property | Type | Required | Description |
118
+ | -------------- | ------------------- | ---------- | --------------------------------------------------------------------------- |
118
119
  | `extension` | `ImageFormat` | No | Output format (`webp`, `avif`, `jpeg`, `png`, `gif`, `ico`, `svg`, `jpg`) |
119
- | `width` | `number` | No | Target width in pixels (1–4096) |
120
- | `height` | `number` | No | Target height in pixels (1–4096) |
121
- | `resizeType` | `ImageResizeType` | No | Resize strategy: `fit`, `fill`, `force`, `fill-down`, `auto` |
122
- | `quality` | `number` | No | Quality 1–100 |
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 |
123
124
 
124
125
  ### `media.getVideoHlsUrl(objectKey: string): string`
125
126
 
@@ -189,6 +190,19 @@ import type {
189
190
 
190
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'`).
191
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
+
192
206
  ## URL Structure
193
207
 
194
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;
@@ -128,7 +144,7 @@ var SprouxMedia = class _SprouxMedia {
128
144
  this.validateImageOptions(options);
129
145
  const { name, extension: originalExt } = this.parseObjectKey(objectKey);
130
146
  const ext = options.extension ?? originalExt;
131
- const variant = this.buildVariantString(options);
147
+ const variant = buildVariantString(options);
132
148
  return variant ? `${this.cdnUrl}/${name}-${variant}.${ext}` : `${this.cdnUrl}/${objectKey}`;
133
149
  }
134
150
  /**
@@ -162,24 +178,6 @@ var SprouxMedia = class _SprouxMedia {
162
178
  extension: objectKey.substring(lastDot + 1)
163
179
  };
164
180
  }
165
- /**
166
- * Build a deterministic variant string from image options.
167
- *
168
- * Format: {width}x{height}-{resizeType}[-q{quality}]
169
- */
170
- buildVariantString(options) {
171
- const parts = [];
172
- if (options.width != null && options.height != null) {
173
- parts.push(`${options.width}x${options.height}`);
174
- }
175
- if (options.resizeType != null) {
176
- parts.push(options.resizeType);
177
- }
178
- if (options.quality != null) {
179
- parts.push(`q${options.quality}`);
180
- }
181
- return parts.join("-");
182
- }
183
181
  validateImageOptions(options) {
184
182
  if (options.width != null) {
185
183
  if (!Number.isInteger(options.width) || options.width < 1 || options.width > 4096) {
@@ -211,6 +209,7 @@ var SprouxMedia = class _SprouxMedia {
211
209
  MEDIA_STATUS,
212
210
  MEDIA_TYPE,
213
211
  R2_PATH,
214
- SprouxMedia
212
+ SprouxMedia,
213
+ buildVariantString
215
214
  });
216
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, 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 = this.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 * 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[] = [];\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 }\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"]}
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":"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","names":["IMAGE_FORMAT","IMAGE_RESIZE_TYPE"]}
package/dist/index.d.cts CHANGED
@@ -110,13 +110,14 @@ declare class SprouxMedia {
110
110
  * Parse an object key into name (path without extension) and extension.
111
111
  */
112
112
  private parseObjectKey;
113
- /**
114
- * Build a deterministic variant string from image options.
115
- *
116
- * Format: {width}x{height}-{resizeType}[-q{quality}]
117
- */
118
- private buildVariantString;
119
113
  private validateImageOptions;
120
114
  }
121
115
 
122
- 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
@@ -110,13 +110,14 @@ declare class SprouxMedia {
110
110
  * Parse an object key into name (path without extension) and extension.
111
111
  */
112
112
  private parseObjectKey;
113
- /**
114
- * Build a deterministic variant string from image options.
115
- *
116
- * Format: {width}x{height}-{resizeType}[-q{quality}]
117
- */
118
- private buildVariantString;
119
113
  private validateImageOptions;
120
114
  }
121
115
 
122
- 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;
@@ -95,7 +110,7 @@ var SprouxMedia = class _SprouxMedia {
95
110
  this.validateImageOptions(options);
96
111
  const { name, extension: originalExt } = this.parseObjectKey(objectKey);
97
112
  const ext = options.extension ?? originalExt;
98
- const variant = this.buildVariantString(options);
113
+ const variant = buildVariantString(options);
99
114
  return variant ? `${this.cdnUrl}/${name}-${variant}.${ext}` : `${this.cdnUrl}/${objectKey}`;
100
115
  }
101
116
  /**
@@ -129,24 +144,6 @@ var SprouxMedia = class _SprouxMedia {
129
144
  extension: objectKey.substring(lastDot + 1)
130
145
  };
131
146
  }
132
- /**
133
- * Build a deterministic variant string from image options.
134
- *
135
- * Format: {width}x{height}-{resizeType}[-q{quality}]
136
- */
137
- buildVariantString(options) {
138
- const parts = [];
139
- if (options.width != null && options.height != null) {
140
- parts.push(`${options.width}x${options.height}`);
141
- }
142
- if (options.resizeType != null) {
143
- parts.push(options.resizeType);
144
- }
145
- if (options.quality != null) {
146
- parts.push(`q${options.quality}`);
147
- }
148
- return parts.join("-");
149
- }
150
147
  validateImageOptions(options) {
151
148
  if (options.width != null) {
152
149
  if (!Number.isInteger(options.width) || options.width < 1 || options.width > 4096) {
@@ -177,6 +174,7 @@ export {
177
174
  MEDIA_STATUS,
178
175
  MEDIA_TYPE,
179
176
  R2_PATH,
180
- SprouxMedia
177
+ SprouxMedia,
178
+ buildVariantString
181
179
  };
182
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, 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 = this.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 * 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[] = [];\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 }\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"]}
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.2",
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",