@kumix/storage 0.1.1 → 0.1.2

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.
@@ -30,16 +30,16 @@ declare class CloudinaryService implements StorageInterface {
30
30
  folderExists(path: string): Promise<FolderExistsResult>;
31
31
  renameFolder(options: RenameFolderOptions): Promise<RenameFolderResult>;
32
32
  copyFolder(options: CopyFolderOptions): Promise<CopyFolderResult>;
33
- uploadFile(key: string, file: Buffer | Uint8Array | string, contentType?: string, metadata?: Record<string, string>): Promise<UploadResult>;
33
+ uploadFile(key: string, file: Buffer | Uint8Array | string, options?: Partial<UploadOptions>): Promise<UploadResult>;
34
34
  downloadFile(key: string): Promise<DownloadResult>;
35
35
  deleteFile(key: string): Promise<DeleteResult>;
36
36
  deleteFiles(keys: string[]): Promise<BatchDeleteResult>;
37
37
  listFiles(prefix?: string, maxKeys?: number): Promise<ListResult>;
38
38
  fileExists(key: string): Promise<boolean>;
39
- copyFile(sourceKey: string, destinationKey: string, metadata?: Record<string, string>): Promise<CopyResult>;
40
- moveFile(sourceKey: string, destinationKey: string, metadata?: Record<string, string>): Promise<MoveResult>;
41
- duplicateFile(sourceKey: string, destinationKey: string, metadata?: Record<string, string>): Promise<DuplicateResult>;
42
- renameFile(sourceKey: string, destinationKey: string, metadata?: Record<string, string>): Promise<MoveResult>;
39
+ copyFile(sourceKey: string, destinationKey: string, options?: Partial<CopyOptions>): Promise<CopyResult>;
40
+ moveFile(sourceKey: string, destinationKey: string, options?: Partial<MoveOptions>): Promise<MoveResult>;
41
+ duplicateFile(sourceKey: string, destinationKey: string, options?: Partial<DuplicateOptions>): Promise<DuplicateResult>;
42
+ renameFile(sourceKey: string, destinationKey: string, options?: Partial<MoveOptions>): Promise<MoveResult>;
43
43
  getDownloadUrl(key: string, expiresIn?: number): Promise<PresignedUrlResult>;
44
44
  getUploadUrl(key: string, contentType?: string, expiresIn?: number): Promise<PresignedUrlResult>;
45
45
  createFolderPath(path: string): Promise<CreateFolderResult>;
@@ -1 +1 @@
1
- {"version":3,"file":"cloudinary.d.ts","names":[],"sources":["../src/services/cloudinary.ts","../src/cloudinary.ts"],"mappings":"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"}
1
+ {"version":3,"file":"cloudinary.d.ts","names":[],"sources":["../src/services/cloudinary.ts","../src/cloudinary.ts"],"mappings":"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"}
@@ -1,36 +1,41 @@
1
1
  import { o as loadCloudinaryConfig } from "./config-By5ib98s.js";
2
- import { v2 } from "cloudinary";
3
2
  //#region src/providers/cloudinary.ts
4
- /**
5
- * Cloudinary storage provider implementation
6
- * Provides cloud storage operations using Cloudinary's API for media management
7
- */
8
- /**
9
- * Cloudinary storage provider
10
- * Implements StorageInterface for Cloudinary cloud storage
11
- * @internal
12
- */
13
3
  var CloudinaryProvider = class {
14
4
  config;
5
+ _client = null;
15
6
  constructor(config) {
16
7
  this.config = config;
17
- this.configure();
18
8
  }
19
9
  /**
20
- * Re-apply this instance's config to the global Cloudinary client. Call this
21
- * before any API operation to reduce (but not eliminate) the chance that a
22
- * different `CloudinaryProvider` instance has flipped the global config.
10
+ * Lazily load the Cloudinary SDK and re-apply this instance's config to the
11
+ * module-global v2 client. The SDK is dynamically imported so the optional
12
+ * `cloudinary` peer dep isn't required at module load time — consumers who
13
+ * only use the S3 provider can import the package without installing it.
14
+ *
15
+ * NOTE: the Cloudinary SDK keeps a single global config, so creating multiple
16
+ * `CloudinaryProvider` instances with different credentials in the same
17
+ * process is not supported — the most recently-configured instance wins.
18
+ * `getClient()` re-applies config before each API call to narrow the race
19
+ * window when instances are interleaved.
23
20
  */
24
- configure() {
25
- v2.config({
21
+ async getClient() {
22
+ if (!this._client) try {
23
+ const mod = await import("cloudinary");
24
+ this._client = mod.v2 ?? mod.default?.v2;
25
+ } catch {
26
+ throw new Error("cloudinary is not available in this runtime. Install the `cloudinary` package to use the Cloudinary storage provider.");
27
+ }
28
+ this._client.config({
26
29
  cloud_name: this.config.cloudName,
27
30
  api_key: this.config.apiKey,
28
31
  api_secret: this.config.apiSecret,
29
32
  secure: this.config.secure !== false
30
33
  });
34
+ return this._client;
31
35
  }
32
36
  async upload(options) {
33
37
  try {
38
+ const cloudinary = await this.getClient();
34
39
  let fileData;
35
40
  if (typeof options.file === "string") fileData = options.file;
36
41
  else fileData = `data:${options.contentType || "application/octet-stream"};base64,${Buffer.from(options.file).toString("base64")}`;
@@ -41,8 +46,10 @@ var CloudinaryProvider = class {
41
46
  let folder = "";
42
47
  let publicId = keyParts[keyParts.length - 1];
43
48
  if (keyParts.length > 1) folder = keyParts.slice(0, -1).join("/");
44
- const dotIndex = publicId.lastIndexOf(".");
45
- if (dotIndex > 0) publicId = publicId.slice(0, dotIndex);
49
+ if (resourceType !== "raw") {
50
+ const dotIndex = publicId.lastIndexOf(".");
51
+ if (dotIndex > 0) publicId = publicId.slice(0, dotIndex);
52
+ }
46
53
  const uploadOptions = {
47
54
  public_id: publicId,
48
55
  resource_type: resourceType,
@@ -54,7 +61,7 @@ var CloudinaryProvider = class {
54
61
  if (this.config.folder) folderParts.push(this.config.folder);
55
62
  if (folder) folderParts.push(folder);
56
63
  if (folderParts.length > 0) uploadOptions.folder = folderParts.join("/");
57
- const result = await v2.uploader.upload(fileData, uploadOptions);
64
+ const result = await cloudinary.uploader.upload(fileData, uploadOptions);
58
65
  const publicUrl = this.getPublicUrl(result.public_id, result.format, resourceType);
59
66
  return {
60
67
  success: true,
@@ -73,7 +80,11 @@ var CloudinaryProvider = class {
73
80
  }
74
81
  async download(options) {
75
82
  try {
76
- const publicUrl = this.getPublicUrl(options.key);
83
+ const dotIndex = options.key.lastIndexOf(".");
84
+ const hasExt = dotIndex > 0 && dotIndex < options.key.length - 1;
85
+ const baseKey = hasExt ? options.key.slice(0, dotIndex) : options.key;
86
+ const extension = hasExt ? options.key.slice(dotIndex + 1) : void 0;
87
+ const publicUrl = this.getPublicUrl(baseKey, extension);
77
88
  const response = await fetch(publicUrl);
78
89
  if (!response.ok) return {
79
90
  success: false,
@@ -98,23 +109,24 @@ var CloudinaryProvider = class {
98
109
  }
99
110
  async delete(options) {
100
111
  try {
112
+ const cloudinary = await this.getClient();
101
113
  let publicId = options.key;
102
114
  const dotIndex = publicId.lastIndexOf(".");
103
115
  if (dotIndex > 0) publicId = publicId.slice(0, dotIndex);
104
116
  if (this.config.folder) publicId = `${this.config.folder}/${publicId}`;
105
117
  let resourceType = "raw";
106
118
  try {
107
- await v2.api.resource(publicId, { resource_type: "image" });
119
+ await cloudinary.api.resource(publicId, { resource_type: "image" });
108
120
  resourceType = "image";
109
121
  } catch {
110
122
  try {
111
- await v2.api.resource(publicId, { resource_type: "video" });
123
+ await cloudinary.api.resource(publicId, { resource_type: "video" });
112
124
  resourceType = "video";
113
125
  } catch {
114
126
  resourceType = "raw";
115
127
  }
116
128
  }
117
- await v2.uploader.destroy(publicId, {
129
+ await cloudinary.uploader.destroy(publicId, {
118
130
  resource_type: resourceType,
119
131
  invalidate: true
120
132
  });
@@ -128,6 +140,7 @@ var CloudinaryProvider = class {
128
140
  }
129
141
  async batchDelete(options) {
130
142
  try {
143
+ const cloudinary = await this.getClient();
131
144
  const publicIds = options.keys.map((key) => {
132
145
  let publicId = key;
133
146
  const dotIndex = publicId.lastIndexOf(".");
@@ -135,7 +148,7 @@ var CloudinaryProvider = class {
135
148
  if (this.config.folder) publicId = `${this.config.folder}/${publicId}`;
136
149
  return publicId;
137
150
  });
138
- const result = await v2.api.delete_resources(publicIds);
151
+ const result = await cloudinary.api.delete_resources(publicIds);
139
152
  const deleted = [];
140
153
  const errors = [];
141
154
  for (const [publicId, info] of Object.entries(result)) {
@@ -164,7 +177,7 @@ var CloudinaryProvider = class {
164
177
  }
165
178
  async list(options = {}) {
166
179
  try {
167
- this.configure();
180
+ const cloudinary = await this.getClient();
168
181
  const prefix = options.prefix || "";
169
182
  const maxResults = options.maxKeys || 50;
170
183
  const TYPES = [
@@ -185,7 +198,7 @@ var CloudinaryProvider = class {
185
198
  }
186
199
  }
187
200
  }
188
- const fetchPage = (rt, c) => v2.api.resources({
201
+ const fetchPage = (rt, c) => cloudinary.api.resources({
189
202
  type: "upload",
190
203
  resource_type: rt,
191
204
  prefix,
@@ -211,7 +224,7 @@ var CloudinaryProvider = class {
211
224
  size: Number(resource.bytes) || 0,
212
225
  lastModified: new Date(String(resource.created_at || /* @__PURE__ */ new Date())),
213
226
  etag: String(resource.etag || ""),
214
- contentType: resource.format ? `${resource.resource_type}/${resource.format}` : "application/octet-stream"
227
+ contentType: this.formatToMime(String(resource.resource_type || "raw"), resource.format ? String(resource.format) : void 0)
215
228
  }));
216
229
  const nextContinuationToken = nextCursor ? `${type}:${nextCursor}` : void 0;
217
230
  return {
@@ -229,6 +242,7 @@ var CloudinaryProvider = class {
229
242
  }
230
243
  async exists(key) {
231
244
  try {
245
+ const cloudinary = await this.getClient();
232
246
  let publicId = key;
233
247
  const dotIndex = publicId.lastIndexOf(".");
234
248
  if (dotIndex > 0) publicId = publicId.slice(0, dotIndex);
@@ -238,7 +252,7 @@ var CloudinaryProvider = class {
238
252
  "video",
239
253
  "raw"
240
254
  ]) try {
241
- await v2.api.resource(publicId, { resource_type: resourceType });
255
+ await cloudinary.api.resource(publicId, { resource_type: resourceType });
242
256
  return { exists: true };
243
257
  } catch {}
244
258
  return { exists: false };
@@ -276,6 +290,27 @@ var CloudinaryProvider = class {
276
290
  if (this.config.folder && !publicId.startsWith(`${this.config.folder}/`)) publicId = `${this.config.folder}/${publicId}`;
277
291
  return `${baseUrl}/${resourceType || this.inferResourceType(extension || key)}/upload/${publicId}${extension ? `.${extension}` : ""}`;
278
292
  }
293
+ /**
294
+ * Build a valid MIME type from a Cloudinary `resource_type` + `format`.
295
+ * Naive `${resource_type}/${format}` produces invalid types like `raw/pdf`
296
+ * or `image/jpg`, so map known formats to their real MIME types and fall back
297
+ * to `application/octet-stream` for raw/unknown assets.
298
+ */
299
+ formatToMime(resourceType, format) {
300
+ if (!format) return "application/octet-stream";
301
+ const fmt = format.toLowerCase();
302
+ const overrides = {
303
+ jpg: "image/jpeg",
304
+ jpeg: "image/jpeg",
305
+ svg: "image/svg+xml",
306
+ pdf: "application/pdf",
307
+ mov: "video/quicktime",
308
+ mp3: "audio/mpeg"
309
+ };
310
+ if (overrides[fmt]) return overrides[fmt];
311
+ if (resourceType === "image" || resourceType === "video") return `${resourceType}/${fmt}`;
312
+ return "application/octet-stream";
313
+ }
279
314
  /** Infer Cloudinary resource type from a filename or extension */
280
315
  inferResourceType(nameOrExt) {
281
316
  const ext = nameOrExt.includes(".") ? nameOrExt.split(".").pop()?.toLowerCase() || "" : nameOrExt.toLowerCase();
@@ -314,10 +349,13 @@ var CloudinaryProvider = class {
314
349
  }
315
350
  async deleteFolder(options) {
316
351
  try {
317
- const folderPath = options.path.endsWith("/") ? options.path : `${options.path}/`;
318
- const folderApiPath = options.path.replace(/\/$/, "");
319
- if (options.recursive) await v2.api.delete_resources_by_prefix(folderPath);
320
- await v2.api.delete_folder(folderApiPath);
352
+ const cloudinary = await this.getClient();
353
+ const normalizedPath = options.path.replace(/\/$/, "");
354
+ const scopedPath = this.config.folder ? `${this.config.folder}/${normalizedPath}` : normalizedPath;
355
+ const folderPath = `${scopedPath}/`;
356
+ const folderApiPath = scopedPath;
357
+ if (options.recursive) await cloudinary.api.delete_resources_by_prefix(folderPath);
358
+ await cloudinary.api.delete_folder(folderApiPath);
321
359
  return { success: true };
322
360
  } catch (error) {
323
361
  return {
@@ -330,9 +368,9 @@ var CloudinaryProvider = class {
330
368
  try {
331
369
  return {
332
370
  success: true,
333
- folders: ((await v2.api.root_folders()).folders || []).map((folder) => ({
334
- name: folder.name,
335
- path: folder.path
371
+ folders: ((await (await this.getClient()).api.root_folders()).folders || []).map((folder) => ({
372
+ name: String(folder.name ?? ""),
373
+ path: String(folder.path ?? "")
336
374
  })),
337
375
  isTruncated: false,
338
376
  nextContinuationToken: void 0
@@ -438,12 +476,11 @@ var CloudinaryService = class {
438
476
  async copyFolder(options) {
439
477
  return this.provider.copyFolder(options);
440
478
  }
441
- async uploadFile(key, file, contentType, metadata) {
479
+ async uploadFile(key, file, options) {
442
480
  return this.upload({
443
481
  key,
444
482
  file,
445
- contentType,
446
- metadata
483
+ ...options
447
484
  });
448
485
  }
449
486
  async downloadFile(key) {
@@ -464,29 +501,29 @@ var CloudinaryService = class {
464
501
  async fileExists(key) {
465
502
  return (await this.exists(key)).exists;
466
503
  }
467
- async copyFile(sourceKey, destinationKey, metadata) {
504
+ async copyFile(sourceKey, destinationKey, options) {
468
505
  return this.copy({
469
506
  sourceKey,
470
507
  destinationKey,
471
- metadata
508
+ ...options
472
509
  });
473
510
  }
474
- async moveFile(sourceKey, destinationKey, metadata) {
511
+ async moveFile(sourceKey, destinationKey, options) {
475
512
  return this.move({
476
513
  sourceKey,
477
514
  destinationKey,
478
- metadata
515
+ ...options
479
516
  });
480
517
  }
481
- async duplicateFile(sourceKey, destinationKey, metadata) {
518
+ async duplicateFile(sourceKey, destinationKey, options) {
482
519
  return this.duplicate({
483
520
  sourceKey,
484
521
  destinationKey,
485
- metadata
522
+ ...options
486
523
  });
487
524
  }
488
- async renameFile(sourceKey, destinationKey, metadata) {
489
- return this.moveFile(sourceKey, destinationKey, metadata);
525
+ async renameFile(sourceKey, destinationKey, options) {
526
+ return this.moveFile(sourceKey, destinationKey, options);
490
527
  }
491
528
  async getDownloadUrl(key, expiresIn) {
492
529
  return this.getPresignedUrl({
@@ -1 +1 @@
1
- {"version":3,"file":"cloudinary.js","names":["cloudinary"],"sources":["../src/providers/cloudinary.ts","../src/services/cloudinary.ts","../src/cloudinary.ts"],"sourcesContent":["/**\n * Cloudinary storage provider implementation\n * Provides cloud storage operations using Cloudinary's API for media management\n */\n\nimport { v2 as cloudinary } from \"cloudinary\";\n\nimport type {\n BatchDeleteOptions,\n BatchDeleteResult,\n CloudinaryConfig,\n CopyFolderOptions,\n CopyFolderResult,\n CopyOptions,\n CopyResult,\n CreateFolderOptions,\n CreateFolderResult,\n DeleteFolderOptions,\n DeleteFolderResult,\n DeleteOptions,\n DeleteResult,\n DownloadOptions,\n DownloadResult,\n DuplicateOptions,\n DuplicateResult,\n ExistsResult,\n FolderExistsResult,\n ListFoldersOptions,\n ListFoldersResult,\n ListOptions,\n ListResult,\n MoveOptions,\n MoveResult,\n PresignedUrlOptions,\n PresignedUrlResult,\n RenameFolderOptions,\n RenameFolderResult,\n StorageInterface,\n UploadOptions,\n UploadResult,\n} from \"../types\";\n\n/**\n * Cloudinary storage provider\n * Implements StorageInterface for Cloudinary cloud storage\n * @internal\n */\nexport class CloudinaryProvider implements StorageInterface {\n private config: CloudinaryConfig;\n\n constructor(config: CloudinaryConfig) {\n this.config = config;\n // Apply this instance's config to the module-global Cloudinary v2 client.\n // NOTE: the Cloudinary SDK keeps a single global config, so creating\n // multiple `CloudinaryProvider` instances with different credentials in the\n // same process is not supported — the most recently-constructed instance\n // wins. `configure()` is called before each API call below to narrow the\n // race window when instances are interleaved.\n this.configure();\n }\n\n /**\n * Re-apply this instance's config to the global Cloudinary client. Call this\n * before any API operation to reduce (but not eliminate) the chance that a\n * different `CloudinaryProvider` instance has flipped the global config.\n */\n private configure(): void {\n cloudinary.config({\n cloud_name: this.config.cloudName,\n api_key: this.config.apiKey,\n api_secret: this.config.apiSecret,\n secure: this.config.secure !== false,\n });\n }\n\n // File operations\n async upload(options: UploadOptions): Promise<UploadResult> {\n try {\n // Convert Buffer/Uint8Array to base64 for Cloudinary\n let fileData: string;\n if (typeof options.file === \"string\") {\n fileData = options.file;\n } else {\n fileData = `data:${options.contentType || \"application/octet-stream\"};base64,${Buffer.from(options.file).toString(\"base64\")}`;\n }\n\n // Determine resource type based on content type\n let resourceType: \"image\" | \"video\" | \"raw\" = \"raw\";\n if (options.contentType?.startsWith(\"image/\")) {\n resourceType = \"image\";\n } else if (options.contentType?.startsWith(\"video/\")) {\n resourceType = \"video\";\n }\n\n // Extract folder and filename from key for Cloudinary folder support\n const keyParts = options.key.split(\"/\");\n let folder = \"\";\n let publicId = keyParts[keyParts.length - 1];\n\n if (keyParts.length > 1) {\n folder = keyParts.slice(0, -1).join(\"/\");\n }\n\n const dotIndex = publicId.lastIndexOf(\".\");\n if (dotIndex > 0) {\n publicId = publicId.slice(0, dotIndex);\n }\n\n const uploadOptions: Record<string, unknown> = {\n public_id: publicId,\n resource_type: resourceType,\n overwrite: true,\n invalidate: true,\n // Send user metadata under `context`/`metadata` instead of spreading it\n // at the top level. Spreading last let callers override security-\n // sensitive fields like `public_id`, `resource_type`, and `folder`.\n ...(options.metadata ? { context: options.metadata } : {}),\n };\n\n // Combine config folder + extracted folder without trailing slashes\n const folderParts: string[] = [];\n if (this.config.folder) folderParts.push(this.config.folder);\n if (folder) folderParts.push(folder);\n if (folderParts.length > 0) {\n uploadOptions.folder = folderParts.join(\"/\");\n }\n\n const result = await cloudinary.uploader.upload(fileData, uploadOptions);\n\n // Sample result structure\n // {\n // asset_id: 'b4033628c0897843e61e362167f338d6',\n // public_id: 'starter/workspaces/logo/ws_1KBQ2V0VRKK5EN936G8S2XZ30',\n // version: 1765965394,\n // version_id: 'abd49804c49061cef406f8d703466849',\n // signature: 'ff52169a483bada7ca4943ce322132137bec4d2f',\n // width: 256,\n // height: 256,\n // format: 'png',\n // resource_type: 'image',\n // created_at: '2025-12-17T09:52:12Z',\n // tags: [],\n // bytes: 76429,\n // type: 'upload',\n // etag: '85e34db0ea9ce8e771e25bd8eb33ba56',\n // placeholder: false,\n // url: 'http://res.cloudinary.com/droen5ejq/image/upload/v1765965394/starter/workspaces/logo/ws_1KBQ2V0VRKK5EN936G8S2XZ30.png',\n // secure_url: 'https://res.cloudinary.com/droen5ejq/image/upload/v1765965394/starter/workspaces/logo/ws_1KBQ2V0VRKK5EN936G8S2XZ30.png',\n // asset_folder: 'starter/workspaces/logo',\n // display_name: 'ws_1KBQ2V0VRKK5EN936G8S2XZ30',\n // overwritten: true,\n // api_key: '255239588173378'\n // }\n\n const publicUrl = this.getPublicUrl(result.public_id, result.format, resourceType);\n\n return {\n success: true,\n key: result.public_id,\n url: result.secure_url,\n publicUrl,\n etag: result.etag,\n size: result.bytes,\n };\n } catch (error) {\n return {\n success: false,\n error: error instanceof Error ? error.message : \"Upload failed\",\n };\n }\n }\n\n async download(options: DownloadOptions): Promise<DownloadResult> {\n try {\n // Cloudinary doesn't support direct download like S3\n // We need to fetch from the public URL\n const publicUrl = this.getPublicUrl(options.key);\n\n const response = await fetch(publicUrl);\n if (!response.ok) {\n return {\n success: false,\n error: `Failed to download: ${response.status} ${response.statusText}`,\n };\n }\n\n const arrayBuffer = await response.arrayBuffer();\n const content = Buffer.from(arrayBuffer);\n\n return {\n success: true,\n content, // Main content field\n data: content, // Alias for backward compatibility\n contentType: response.headers.get(\"content-type\") || undefined,\n contentLength: content.length,\n lastModified: response.headers.get(\"last-modified\")\n ? new Date(response.headers.get(\"last-modified\")!)\n : new Date(),\n };\n } catch (error) {\n return {\n success: false,\n error: error instanceof Error ? error.message : \"Download failed\",\n };\n }\n }\n\n async delete(options: DeleteOptions): Promise<DeleteResult> {\n try {\n // Strip extension to get public_id\n let publicId = options.key;\n const dotIndex = publicId.lastIndexOf(\".\");\n if (dotIndex > 0) {\n publicId = publicId.slice(0, dotIndex);\n }\n\n // Add folder if enabled in config\n if (this.config.folder) {\n publicId = `${this.config.folder}/${publicId}`;\n }\n\n // Determine resource type from key/path\n let resourceType: \"image\" | \"video\" | \"raw\" = \"raw\";\n\n // Try to get resource info first to determine type\n try {\n await cloudinary.api.resource(publicId, { resource_type: \"image\" });\n resourceType = \"image\";\n } catch {\n try {\n await cloudinary.api.resource(publicId, { resource_type: \"video\" });\n resourceType = \"video\";\n } catch {\n resourceType = \"raw\";\n }\n }\n\n await cloudinary.uploader.destroy(publicId, {\n resource_type: resourceType,\n invalidate: true,\n });\n\n return {\n success: true,\n };\n } catch (error) {\n return {\n success: false,\n error: error instanceof Error ? error.message : \"Delete failed\",\n };\n }\n }\n\n async batchDelete(options: BatchDeleteOptions): Promise<BatchDeleteResult> {\n try {\n // Convert keys to public_ids (strip extensions, add folder prefix)\n const publicIds = options.keys.map((key) => {\n let publicId = key;\n const dotIndex = publicId.lastIndexOf(\".\");\n if (dotIndex > 0) {\n publicId = publicId.slice(0, dotIndex);\n }\n if (this.config.folder) {\n publicId = `${this.config.folder}/${publicId}`;\n }\n return publicId;\n });\n\n const result = await cloudinary.api.delete_resources(publicIds);\n\n // delete_resources returns an object keyed by public_id with status info\n const deleted: string[] = [];\n const errors: { key: string; error: string }[] = [];\n\n for (const [publicId, info] of Object.entries(result)) {\n const originalKey = options.keys[publicIds.indexOf(publicId)] || publicId;\n const status = (info as Record<string, unknown>)?.status;\n if (status === \"deleted\" || status === \"not_found\") {\n deleted.push(originalKey);\n } else {\n errors.push({ key: originalKey, error: `Delete status: ${String(status)}` });\n }\n }\n\n return {\n success: errors.length === 0,\n deleted,\n errors,\n };\n } catch (error) {\n return {\n success: false,\n errors: options.keys.map((key) => ({\n key,\n error: error instanceof Error ? error.message : \"Batch delete failed\",\n })),\n };\n }\n }\n\n async list(options: ListOptions = {}): Promise<ListResult> {\n try {\n this.configure();\n const prefix = options.prefix || \"\";\n const maxResults = options.maxKeys || 50;\n\n // Each Cloudinary resource_type (image/video/raw) maintains its OWN\n // opaque cursor. The previous implementation queried all three in\n // parallel and returned `images.next_cursor || videos.next_cursor ||\n // raw.next_cursor` — so a page-2 token belonging to (say) videos was\n // fed to the images call on the next request, producing wrong/empty\n // results. We now query ONE type per page call and encode the owning\n // type into the continuation token so the next call resumes the right\n // type. Types are visited in stable order (image → video → raw); when a\n // type's page is empty we auto-advance to the next type within the same\n // call so the caller never receives an empty page.\n const TYPES = [\"image\", \"video\", \"raw\"] as const;\n type RType = (typeof TYPES)[number];\n\n let type: RType = \"image\";\n let cursor: string | undefined;\n if (options.continuationToken) {\n const sep = options.continuationToken.indexOf(\":\");\n if (sep > 0) {\n const t = options.continuationToken.slice(0, sep);\n const c = options.continuationToken.slice(sep + 1);\n if ((TYPES as readonly string[]).includes(t)) {\n type = t as RType;\n cursor = c || undefined;\n }\n }\n }\n\n const fetchPage = (rt: RType, c?: string) =>\n cloudinary.api\n .resources({\n type: \"upload\",\n resource_type: rt,\n prefix,\n max_results: maxResults,\n ...(c ? { next_cursor: c } : {}),\n })\n .catch(() => ({ resources: [], next_cursor: undefined }));\n\n let page = await fetchPage(type, cursor);\n let resources: Record<string, unknown>[] = (page.resources || []) as Record<\n string,\n unknown\n >[];\n let nextCursor: string | undefined = page.next_cursor;\n\n // Auto-advance: if this type returned nothing and has no next page, move\n // to the next resource type (still within maxResults) so callers don't\n // see empty intermediate pages while iterating through all types.\n let typeIdx = TYPES.indexOf(type);\n while (!nextCursor && resources.length === 0 && typeIdx < TYPES.length - 1) {\n typeIdx++;\n type = TYPES[typeIdx];\n page = await fetchPage(type);\n resources = (page.resources || []) as Record<string, unknown>[];\n nextCursor = page.next_cursor;\n }\n\n const files = resources.map((resource) => ({\n key: String(resource.public_id || \"\"),\n size: Number(resource.bytes) || 0,\n lastModified: new Date(String(resource.created_at || new Date())),\n etag: String(resource.etag || \"\"),\n contentType: resource.format\n ? `${resource.resource_type}/${resource.format}`\n : \"application/octet-stream\",\n }));\n\n const nextContinuationToken = nextCursor ? `${type}:${nextCursor}` : undefined;\n\n return {\n success: true,\n files,\n isTruncated: !!nextContinuationToken,\n nextContinuationToken,\n };\n } catch (error) {\n return {\n success: false,\n error: error instanceof Error ? error.message : \"List failed\",\n };\n }\n }\n\n async exists(key: string): Promise<ExistsResult> {\n try {\n // Strip extension and add config folder prefix for the public_id\n let publicId = key;\n const dotIndex = publicId.lastIndexOf(\".\");\n if (dotIndex > 0) {\n publicId = publicId.slice(0, dotIndex);\n }\n if (this.config.folder) {\n publicId = `${this.config.folder}/${publicId}`;\n }\n\n // Try each resource type until found\n for (const resourceType of [\"image\", \"video\", \"raw\"] as const) {\n try {\n await cloudinary.api.resource(publicId, { resource_type: resourceType });\n return { exists: true };\n } catch {\n // Try next resource type\n }\n }\n\n return { exists: false };\n } catch (error) {\n return {\n exists: false,\n error: error instanceof Error ? error.message : \"Check existence failed\",\n };\n }\n }\n\n async copy(_options: CopyOptions): Promise<CopyResult> {\n return {\n success: false,\n error: \"Cloudinary copy not implemented yet\",\n };\n }\n\n async move(_options: MoveOptions): Promise<MoveResult> {\n return {\n success: false,\n error: \"Cloudinary move not implemented yet\",\n };\n }\n\n async duplicate(options: DuplicateOptions): Promise<DuplicateResult> {\n // Duplicate is just an alias for copy\n return this.copy(options);\n }\n\n async getPresignedUrl(_options: PresignedUrlOptions): Promise<PresignedUrlResult> {\n return {\n success: false,\n error: \"Cloudinary presigned URL not implemented yet\",\n };\n }\n\n getPublicUrl(key: string, extension?: string, resourceType?: \"image\" | \"video\" | \"raw\"): string {\n // Generate Cloudinary URL\n const protocol = this.config.secure !== false ? \"https\" : \"http\";\n const baseUrl = `${protocol}://res.cloudinary.com/${this.config.cloudName}`;\n\n // `upload()` prepends `config.folder` to the stored public_id, so callers\n // holding a relative key need the folder re-prepended here to round-trip\n // correctly. If the key already starts with the folder (full public_id),\n // don't double-prefix.\n let publicId = key;\n if (this.config.folder && !publicId.startsWith(`${this.config.folder}/`)) {\n publicId = `${this.config.folder}/${publicId}`;\n }\n\n // Use explicit resource type if provided, otherwise infer from extension\n const type = resourceType || this.inferResourceType(extension || key);\n\n return `${baseUrl}/${type}/upload/${publicId}${extension ? `.${extension}` : \"\"}`;\n }\n\n /** Infer Cloudinary resource type from a filename or extension */\n private inferResourceType(nameOrExt: string): \"image\" | \"video\" | \"raw\" {\n const ext = nameOrExt.includes(\".\")\n ? nameOrExt.split(\".\").pop()?.toLowerCase() || \"\"\n : nameOrExt.toLowerCase();\n const imageExts = [\"jpg\", \"jpeg\", \"png\", \"gif\", \"webp\", \"svg\", \"bmp\", \"ico\", \"avif\", \"tiff\"];\n const videoExts = [\"mp4\", \"webm\", \"mov\", \"avi\", \"mkv\", \"flv\", \"wmv\", \"m4v\", \"ogv\"];\n if (imageExts.includes(ext)) return \"image\";\n if (videoExts.includes(ext)) return \"video\";\n return \"raw\";\n }\n\n // Folder operations\n async createFolder(options: CreateFolderOptions): Promise<CreateFolderResult> {\n // Cloudinary folders are created implicitly when uploading files\n return {\n success: true,\n path: options.path,\n };\n }\n\n async deleteFolder(options: DeleteFolderOptions): Promise<DeleteFolderResult> {\n try {\n // Cloudinary requires deleting all assets in folder first, then the\n // folder itself. Preserve the trailing slash for the prefix delete so we\n // only match `folder/*` and never a sibling like `folderXYZ/*`. Stripping\n // the slash (the previous behavior) made `deleteFolder(\"folder/\")` also\n // delete `folderXYZ/...` — a data-loss hazard.\n const folderPath = options.path.endsWith(\"/\") ? options.path : `${options.path}/`;\n const folderApiPath = options.path.replace(/\\/$/, \"\");\n\n if (options.recursive) {\n // Delete all resources in the folder\n await cloudinary.api.delete_resources_by_prefix(folderPath);\n }\n\n // Delete the folder\n await cloudinary.api.delete_folder(folderApiPath);\n\n return {\n success: true,\n };\n } catch (error) {\n return {\n success: false,\n error: error instanceof Error ? error.message : \"Delete folder failed\",\n };\n }\n }\n\n async listFolders(_options?: ListFoldersOptions): Promise<ListFoldersResult> {\n try {\n // Use Cloudinary Admin API to list folders\n const result = await cloudinary.api.root_folders();\n\n const folders = (result.folders || []).map((folder: Record<string, unknown>) => ({\n name: folder.name,\n path: folder.path,\n }));\n\n return {\n success: true,\n folders,\n isTruncated: false,\n nextContinuationToken: undefined,\n };\n } catch (error) {\n return {\n success: false,\n error: error instanceof Error ? error.message : \"List folders failed\",\n };\n }\n }\n\n async folderExists(_path: string): Promise<FolderExistsResult> {\n // Previously this returned `{ exists: false, error: \"...\" }`, which looks\n // like a definitive \"the folder does not exist\" to a caller doing\n // `if (!result.exists)`. Return an `error`-only result (no `exists` claim)\n // so the unimplemented state is distinguishable from a real \"not found\".\n return {\n exists: false,\n error:\n \"Cloudinary folder existence is not implemented yet — this result does NOT mean the folder is absent.\",\n };\n }\n\n async renameFolder(_options: RenameFolderOptions): Promise<RenameFolderResult> {\n return {\n success: false,\n error: \"Cloudinary rename folder not implemented yet\",\n };\n }\n\n async copyFolder(_options: CopyFolderOptions): Promise<CopyFolderResult> {\n return {\n success: false,\n error: \"Cloudinary copy folder not implemented yet\",\n };\n }\n}\n","/**\n * Cloudinary storage service implementation\n * High-level service wrapper for Cloudinary storage operations with convenience methods\n */\n\nimport { loadCloudinaryConfig } from \"../config\";\nimport { CloudinaryProvider } from \"../providers/cloudinary\";\nimport type {\n BatchDeleteOptions,\n BatchDeleteResult,\n CloudinaryConfig,\n CopyFolderOptions,\n CopyFolderResult,\n CopyOptions,\n CopyResult,\n CreateFolderOptions,\n CreateFolderResult,\n DeleteFolderOptions,\n DeleteFolderResult,\n DeleteOptions,\n DeleteResult,\n DownloadOptions,\n DownloadResult,\n DuplicateOptions,\n DuplicateResult,\n ExistsResult,\n FolderExistsResult,\n ListFoldersOptions,\n ListFoldersResult,\n ListOptions,\n ListResult,\n MoveOptions,\n MoveResult,\n PresignedUrlOptions,\n PresignedUrlResult,\n RenameFolderOptions,\n RenameFolderResult,\n StorageInterface,\n UploadOptions,\n UploadResult,\n} from \"../types\";\n\n/**\n * Cloudinary storage service\n * High-level service for Cloudinary storage operations with convenience methods\n * @public\n */\nexport class CloudinaryService implements StorageInterface {\n private provider: CloudinaryProvider;\n private config: CloudinaryConfig;\n\n constructor();\n constructor(config: CloudinaryConfig);\n constructor(config?: CloudinaryConfig) {\n this.config = config || loadCloudinaryConfig();\n this.provider = new CloudinaryProvider(this.config);\n }\n\n // Utility methods\n getConfig(): CloudinaryConfig {\n return { ...this.config };\n }\n\n getProvider(): string {\n return this.config.provider;\n }\n\n getPublicUrl(key: string): string {\n return this.provider.getPublicUrl(key);\n }\n\n // File operations\n async upload(options: UploadOptions): Promise<UploadResult> {\n return this.provider.upload(options);\n }\n\n async download(options: DownloadOptions): Promise<DownloadResult> {\n return this.provider.download(options);\n }\n\n async delete(options: DeleteOptions): Promise<DeleteResult> {\n return this.provider.delete(options);\n }\n\n async batchDelete(options: BatchDeleteOptions): Promise<BatchDeleteResult> {\n return this.provider.batchDelete(options);\n }\n\n async list(options?: ListOptions): Promise<ListResult> {\n return this.provider.list(options);\n }\n\n async exists(key: string): Promise<ExistsResult> {\n return this.provider.exists(key);\n }\n\n async copy(options: CopyOptions): Promise<CopyResult> {\n return this.provider.copy(options);\n }\n\n async move(options: MoveOptions): Promise<MoveResult> {\n return this.provider.move(options);\n }\n\n async duplicate(options: DuplicateOptions): Promise<DuplicateResult> {\n return this.provider.duplicate(options);\n }\n\n async getPresignedUrl(options: PresignedUrlOptions): Promise<PresignedUrlResult> {\n return this.provider.getPresignedUrl(options);\n }\n\n // Folder operations\n async createFolder(options: CreateFolderOptions): Promise<CreateFolderResult> {\n return this.provider.createFolder(options);\n }\n\n async deleteFolder(options: DeleteFolderOptions): Promise<DeleteFolderResult> {\n return this.provider.deleteFolder(options);\n }\n\n async listFolders(options?: ListFoldersOptions): Promise<ListFoldersResult> {\n return this.provider.listFolders(options);\n }\n\n async folderExists(path: string): Promise<FolderExistsResult> {\n return this.provider.folderExists(path);\n }\n\n async renameFolder(options: RenameFolderOptions): Promise<RenameFolderResult> {\n return this.provider.renameFolder(options);\n }\n\n async copyFolder(options: CopyFolderOptions): Promise<CopyFolderResult> {\n return this.provider.copyFolder(options);\n }\n\n // Convenience methods\n async uploadFile(\n key: string,\n file: Buffer | Uint8Array | string,\n contentType?: string,\n metadata?: Record<string, string>,\n ): Promise<UploadResult> {\n return this.upload({ key, file, contentType, metadata });\n }\n\n async downloadFile(key: string): Promise<DownloadResult> {\n return this.download({ key });\n }\n\n async deleteFile(key: string): Promise<DeleteResult> {\n return this.delete({ key });\n }\n\n async deleteFiles(keys: string[]): Promise<BatchDeleteResult> {\n return this.batchDelete({ keys });\n }\n\n async listFiles(prefix?: string, maxKeys?: number): Promise<ListResult> {\n return this.list({ prefix, maxKeys });\n }\n\n async fileExists(key: string): Promise<boolean> {\n const result = await this.exists(key);\n return result.exists;\n }\n\n async copyFile(\n sourceKey: string,\n destinationKey: string,\n metadata?: Record<string, string>,\n ): Promise<CopyResult> {\n return this.copy({ sourceKey, destinationKey, metadata });\n }\n\n async moveFile(\n sourceKey: string,\n destinationKey: string,\n metadata?: Record<string, string>,\n ): Promise<MoveResult> {\n return this.move({ sourceKey, destinationKey, metadata });\n }\n\n async duplicateFile(\n sourceKey: string,\n destinationKey: string,\n metadata?: Record<string, string>,\n ): Promise<DuplicateResult> {\n return this.duplicate({ sourceKey, destinationKey, metadata });\n }\n\n async renameFile(\n sourceKey: string,\n destinationKey: string,\n metadata?: Record<string, string>,\n ): Promise<MoveResult> {\n return this.moveFile(sourceKey, destinationKey, metadata);\n }\n\n async getDownloadUrl(key: string, expiresIn?: number): Promise<PresignedUrlResult> {\n return this.getPresignedUrl({ key, operation: \"get\", expiresIn });\n }\n\n async getUploadUrl(\n key: string,\n contentType?: string,\n expiresIn?: number,\n ): Promise<PresignedUrlResult> {\n return this.getPresignedUrl({\n key,\n operation: \"put\",\n contentType,\n expiresIn,\n });\n }\n\n // Folder convenience methods\n async createFolderPath(path: string): Promise<CreateFolderResult> {\n return this.createFolder({ path });\n }\n\n async deleteFolderPath(path: string, recursive = false): Promise<DeleteFolderResult> {\n return this.deleteFolder({ path, recursive });\n }\n\n async folderPathExists(path: string): Promise<boolean> {\n const result = await this.folderExists(path);\n return result.exists;\n }\n\n async renameFolderPath(oldPath: string, newPath: string): Promise<RenameFolderResult> {\n return this.renameFolder({ oldPath, newPath });\n }\n\n async copyFolderPath(\n sourcePath: string,\n destinationPath: string,\n recursive = true,\n ): Promise<CopyFolderResult> {\n return this.copyFolder({ sourcePath, destinationPath, recursive });\n }\n}\n","import type { EnvRecord } from \"./config\";\nimport { loadCloudinaryConfig } from \"./config\";\nimport { CloudinaryService } from \"./services/cloudinary\";\nimport type { CloudinaryConfig } from \"./types\";\n\n// === SERVICE CLASSES ===\nexport { CloudinaryService } from \"./services/cloudinary\";\n\n// === MAIN FACTORY FUNCTIONS (Primary API) ===\n/**\n * Create Cloudinary service using environment variables or manual configuration\n * @param config Optional Cloudinary configuration. If not provided, loads from environment variables\n * @param env - Optional environment record\n * @returns CloudinaryService instance\n * @throws Error if configuration is invalid or environment variables are missing\n * @public\n *\n * @example\n * ```typescript\n * import { createCloudinary } from '@kumix/storage/cloudinary';\n *\n * // Using environment variables\n * // Set: KUMIX_CLOUDINARY_CLOUD_NAME=my-cloud, KUMIX_CLOUDINARY_API_KEY=123..., etc.\n * const cloudinaryFromEnv = createCloudinary();\n * await cloudinaryFromEnv.uploadFile('photo.jpg', imageBuffer);\n *\n * // Using manual configuration\n * const cloudinary = createCloudinary({\n * provider: 'cloudinary',\n * cloudName: 'my-cloud-name',\n * apiKey: '123456789012345',\n * apiSecret: 'your-api-secret',\n * secure: true,\n * folder: 'uploads'\n * });\n *\n * // Upload with transformations\n * await cloudinary.uploadFile('profile-pic.jpg', imageBuffer);\n *\n * // Get optimized URL\n * const publicUrl = cloudinary.getPublicUrl('profile-pic.jpg');\n * console.log(publicUrl); // Cloudinary optimized URL\n * ```\n */\nexport function createCloudinary(): CloudinaryService;\nexport function createCloudinary(config: CloudinaryConfig): CloudinaryService;\nexport function createCloudinary(config: CloudinaryConfig, env: EnvRecord): CloudinaryService;\nexport function createCloudinary(config: undefined, env: EnvRecord): CloudinaryService;\nexport function createCloudinary(config?: CloudinaryConfig, env?: EnvRecord): CloudinaryService {\n if (config) {\n return new CloudinaryService(config);\n }\n const envConfig = loadCloudinaryConfig(env);\n return new CloudinaryService(envConfig);\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"cloudinary.js","names":[],"sources":["../src/providers/cloudinary.ts","../src/services/cloudinary.ts","../src/cloudinary.ts"],"sourcesContent":["/**\n * Cloudinary storage provider implementation\n * Provides cloud storage operations using Cloudinary's API for media management\n */\n\nimport type {\n BatchDeleteOptions,\n BatchDeleteResult,\n CloudinaryConfig,\n CopyFolderOptions,\n CopyFolderResult,\n CopyOptions,\n CopyResult,\n CreateFolderOptions,\n CreateFolderResult,\n DeleteFolderOptions,\n DeleteFolderResult,\n DeleteOptions,\n DeleteResult,\n DownloadOptions,\n DownloadResult,\n DuplicateOptions,\n DuplicateResult,\n ExistsResult,\n FolderExistsResult,\n ListFoldersOptions,\n ListFoldersResult,\n ListOptions,\n ListResult,\n MoveOptions,\n MoveResult,\n PresignedUrlOptions,\n PresignedUrlResult,\n RenameFolderOptions,\n RenameFolderResult,\n StorageInterface,\n UploadOptions,\n UploadResult,\n} from \"../types\";\n\n/**\n * Cloudinary storage provider\n * Implements StorageInterface for Cloudinary cloud storage\n * @internal\n */\n// biome-ignore lint/suspicious/noExplicitAny: Cloudinary SDK is dynamically imported and untyped here.\ntype CloudinaryClient = any;\n\nexport class CloudinaryProvider implements StorageInterface {\n private config: CloudinaryConfig;\n private _client: CloudinaryClient | null = null;\n\n constructor(config: CloudinaryConfig) {\n this.config = config;\n }\n\n /**\n * Lazily load the Cloudinary SDK and re-apply this instance's config to the\n * module-global v2 client. The SDK is dynamically imported so the optional\n * `cloudinary` peer dep isn't required at module load time — consumers who\n * only use the S3 provider can import the package without installing it.\n *\n * NOTE: the Cloudinary SDK keeps a single global config, so creating multiple\n * `CloudinaryProvider` instances with different credentials in the same\n * process is not supported — the most recently-configured instance wins.\n * `getClient()` re-applies config before each API call to narrow the race\n * window when instances are interleaved.\n */\n private async getClient(): Promise<CloudinaryClient> {\n if (!this._client) {\n try {\n const mod = await import(\"cloudinary\");\n this._client = mod.v2 ?? (mod as { default?: { v2?: CloudinaryClient } }).default?.v2;\n } catch {\n throw new Error(\n \"cloudinary is not available in this runtime. \" +\n \"Install the `cloudinary` package to use the Cloudinary storage provider.\",\n );\n }\n }\n this._client.config({\n cloud_name: this.config.cloudName,\n api_key: this.config.apiKey,\n api_secret: this.config.apiSecret,\n secure: this.config.secure !== false,\n });\n return this._client;\n }\n\n // File operations\n async upload(options: UploadOptions): Promise<UploadResult> {\n try {\n const cloudinary = await this.getClient();\n // Convert Buffer/Uint8Array to base64 for Cloudinary\n let fileData: string;\n if (typeof options.file === \"string\") {\n fileData = options.file;\n } else {\n fileData = `data:${options.contentType || \"application/octet-stream\"};base64,${Buffer.from(options.file).toString(\"base64\")}`;\n }\n\n // Determine resource type based on content type\n let resourceType: \"image\" | \"video\" | \"raw\" = \"raw\";\n if (options.contentType?.startsWith(\"image/\")) {\n resourceType = \"image\";\n } else if (options.contentType?.startsWith(\"video/\")) {\n resourceType = \"video\";\n }\n\n // Extract folder and filename from key for Cloudinary folder support\n const keyParts = options.key.split(\"/\");\n let folder = \"\";\n let publicId = keyParts[keyParts.length - 1];\n\n if (keyParts.length > 1) {\n folder = keyParts.slice(0, -1).join(\"/\");\n }\n\n // Only strip the extension for image/video assets. Cloudinary keeps the\n // extension as part of the public_id for `raw` resources, so stripping it\n // would break the stored-id / `getPublicUrl` round-trip for raw files.\n if (resourceType !== \"raw\") {\n const dotIndex = publicId.lastIndexOf(\".\");\n if (dotIndex > 0) {\n publicId = publicId.slice(0, dotIndex);\n }\n }\n\n const uploadOptions: Record<string, unknown> = {\n public_id: publicId,\n resource_type: resourceType,\n overwrite: true,\n invalidate: true,\n // Send user metadata under `context`/`metadata` instead of spreading it\n // at the top level. Spreading last let callers override security-\n // sensitive fields like `public_id`, `resource_type`, and `folder`.\n ...(options.metadata ? { context: options.metadata } : {}),\n };\n\n // Combine config folder + extracted folder without trailing slashes\n const folderParts: string[] = [];\n if (this.config.folder) folderParts.push(this.config.folder);\n if (folder) folderParts.push(folder);\n if (folderParts.length > 0) {\n uploadOptions.folder = folderParts.join(\"/\");\n }\n\n const result = await cloudinary.uploader.upload(fileData, uploadOptions);\n\n // Sample result structure\n // {\n // asset_id: 'b4033628c0897843e61e362167f338d6',\n // public_id: 'starter/workspaces/logo/ws_1KBQ2V0VRKK5EN936G8S2XZ30',\n // version: 1765965394,\n // version_id: 'abd49804c49061cef406f8d703466849',\n // signature: 'ff52169a483bada7ca4943ce322132137bec4d2f',\n // width: 256,\n // height: 256,\n // format: 'png',\n // resource_type: 'image',\n // created_at: '2025-12-17T09:52:12Z',\n // tags: [],\n // bytes: 76429,\n // type: 'upload',\n // etag: '85e34db0ea9ce8e771e25bd8eb33ba56',\n // placeholder: false,\n // url: 'http://res.cloudinary.com/droen5ejq/image/upload/v1765965394/starter/workspaces/logo/ws_1KBQ2V0VRKK5EN936G8S2XZ30.png',\n // secure_url: 'https://res.cloudinary.com/droen5ejq/image/upload/v1765965394/starter/workspaces/logo/ws_1KBQ2V0VRKK5EN936G8S2XZ30.png',\n // asset_folder: 'starter/workspaces/logo',\n // display_name: 'ws_1KBQ2V0VRKK5EN936G8S2XZ30',\n // overwritten: true,\n // api_key: '255239588173378'\n // }\n\n const publicUrl = this.getPublicUrl(result.public_id, result.format, resourceType);\n\n return {\n success: true,\n key: result.public_id,\n url: result.secure_url,\n publicUrl,\n etag: result.etag,\n size: result.bytes,\n };\n } catch (error) {\n return {\n success: false,\n error: error instanceof Error ? error.message : \"Upload failed\",\n };\n }\n }\n\n async download(options: DownloadOptions): Promise<DownloadResult> {\n try {\n // Cloudinary doesn't support direct download like S3, so we fetch from\n // the public URL. Split the key into a base + extension and pass the\n // extension through so `getPublicUrl` can infer the correct resource\n // type (image/video/raw). Passing the bare key would default to `raw`\n // and 404 for image/video assets.\n const dotIndex = options.key.lastIndexOf(\".\");\n const hasExt = dotIndex > 0 && dotIndex < options.key.length - 1;\n const baseKey = hasExt ? options.key.slice(0, dotIndex) : options.key;\n const extension = hasExt ? options.key.slice(dotIndex + 1) : undefined;\n const publicUrl = this.getPublicUrl(baseKey, extension);\n\n const response = await fetch(publicUrl);\n if (!response.ok) {\n return {\n success: false,\n error: `Failed to download: ${response.status} ${response.statusText}`,\n };\n }\n\n const arrayBuffer = await response.arrayBuffer();\n const content = Buffer.from(arrayBuffer);\n\n return {\n success: true,\n content, // Main content field\n data: content, // Alias for backward compatibility\n contentType: response.headers.get(\"content-type\") || undefined,\n contentLength: content.length,\n lastModified: response.headers.get(\"last-modified\")\n ? new Date(response.headers.get(\"last-modified\")!)\n : new Date(),\n };\n } catch (error) {\n return {\n success: false,\n error: error instanceof Error ? error.message : \"Download failed\",\n };\n }\n }\n\n async delete(options: DeleteOptions): Promise<DeleteResult> {\n try {\n const cloudinary = await this.getClient();\n // Strip extension to get public_id\n let publicId = options.key;\n const dotIndex = publicId.lastIndexOf(\".\");\n if (dotIndex > 0) {\n publicId = publicId.slice(0, dotIndex);\n }\n\n // Add folder if enabled in config\n if (this.config.folder) {\n publicId = `${this.config.folder}/${publicId}`;\n }\n\n // Determine resource type from key/path\n let resourceType: \"image\" | \"video\" | \"raw\" = \"raw\";\n\n // Try to get resource info first to determine type\n try {\n await cloudinary.api.resource(publicId, { resource_type: \"image\" });\n resourceType = \"image\";\n } catch {\n try {\n await cloudinary.api.resource(publicId, { resource_type: \"video\" });\n resourceType = \"video\";\n } catch {\n resourceType = \"raw\";\n }\n }\n\n await cloudinary.uploader.destroy(publicId, {\n resource_type: resourceType,\n invalidate: true,\n });\n\n return {\n success: true,\n };\n } catch (error) {\n return {\n success: false,\n error: error instanceof Error ? error.message : \"Delete failed\",\n };\n }\n }\n\n async batchDelete(options: BatchDeleteOptions): Promise<BatchDeleteResult> {\n try {\n const cloudinary = await this.getClient();\n // Convert keys to public_ids (strip extensions, add folder prefix)\n const publicIds = options.keys.map((key) => {\n let publicId = key;\n const dotIndex = publicId.lastIndexOf(\".\");\n if (dotIndex > 0) {\n publicId = publicId.slice(0, dotIndex);\n }\n if (this.config.folder) {\n publicId = `${this.config.folder}/${publicId}`;\n }\n return publicId;\n });\n\n const result = await cloudinary.api.delete_resources(publicIds);\n\n // delete_resources returns an object keyed by public_id with status info\n const deleted: string[] = [];\n const errors: { key: string; error: string }[] = [];\n\n for (const [publicId, info] of Object.entries(result)) {\n const originalKey = options.keys[publicIds.indexOf(publicId)] || publicId;\n const status = (info as Record<string, unknown>)?.status;\n if (status === \"deleted\" || status === \"not_found\") {\n deleted.push(originalKey);\n } else {\n errors.push({ key: originalKey, error: `Delete status: ${String(status)}` });\n }\n }\n\n return {\n success: errors.length === 0,\n deleted,\n errors,\n };\n } catch (error) {\n return {\n success: false,\n errors: options.keys.map((key) => ({\n key,\n error: error instanceof Error ? error.message : \"Batch delete failed\",\n })),\n };\n }\n }\n\n async list(options: ListOptions = {}): Promise<ListResult> {\n try {\n const cloudinary = await this.getClient();\n const prefix = options.prefix || \"\";\n const maxResults = options.maxKeys || 50;\n\n // Each Cloudinary resource_type (image/video/raw) maintains its OWN\n // opaque cursor. The previous implementation queried all three in\n // parallel and returned `images.next_cursor || videos.next_cursor ||\n // raw.next_cursor` — so a page-2 token belonging to (say) videos was\n // fed to the images call on the next request, producing wrong/empty\n // results. We now query ONE type per page call and encode the owning\n // type into the continuation token so the next call resumes the right\n // type. Types are visited in stable order (image → video → raw); when a\n // type's page is empty we auto-advance to the next type within the same\n // call so the caller never receives an empty page.\n const TYPES = [\"image\", \"video\", \"raw\"] as const;\n type RType = (typeof TYPES)[number];\n\n let type: RType = \"image\";\n let cursor: string | undefined;\n if (options.continuationToken) {\n const sep = options.continuationToken.indexOf(\":\");\n if (sep > 0) {\n const t = options.continuationToken.slice(0, sep);\n const c = options.continuationToken.slice(sep + 1);\n if ((TYPES as readonly string[]).includes(t)) {\n type = t as RType;\n cursor = c || undefined;\n }\n }\n }\n\n const fetchPage = (rt: RType, c?: string) =>\n cloudinary.api\n .resources({\n type: \"upload\",\n resource_type: rt,\n prefix,\n max_results: maxResults,\n ...(c ? { next_cursor: c } : {}),\n })\n .catch(() => ({ resources: [], next_cursor: undefined }));\n\n let page = await fetchPage(type, cursor);\n let resources: Record<string, unknown>[] = (page.resources || []) as Record<\n string,\n unknown\n >[];\n let nextCursor: string | undefined = page.next_cursor;\n\n // Auto-advance: if this type returned nothing and has no next page, move\n // to the next resource type (still within maxResults) so callers don't\n // see empty intermediate pages while iterating through all types.\n let typeIdx = TYPES.indexOf(type);\n while (!nextCursor && resources.length === 0 && typeIdx < TYPES.length - 1) {\n typeIdx++;\n type = TYPES[typeIdx];\n page = await fetchPage(type);\n resources = (page.resources || []) as Record<string, unknown>[];\n nextCursor = page.next_cursor;\n }\n\n const files = resources.map((resource) => ({\n key: String(resource.public_id || \"\"),\n size: Number(resource.bytes) || 0,\n lastModified: new Date(String(resource.created_at || new Date())),\n etag: String(resource.etag || \"\"),\n contentType: this.formatToMime(\n String(resource.resource_type || \"raw\"),\n resource.format ? String(resource.format) : undefined,\n ),\n }));\n\n const nextContinuationToken = nextCursor ? `${type}:${nextCursor}` : undefined;\n\n return {\n success: true,\n files,\n isTruncated: !!nextContinuationToken,\n nextContinuationToken,\n };\n } catch (error) {\n return {\n success: false,\n error: error instanceof Error ? error.message : \"List failed\",\n };\n }\n }\n\n async exists(key: string): Promise<ExistsResult> {\n try {\n const cloudinary = await this.getClient();\n // Strip extension and add config folder prefix for the public_id\n let publicId = key;\n const dotIndex = publicId.lastIndexOf(\".\");\n if (dotIndex > 0) {\n publicId = publicId.slice(0, dotIndex);\n }\n if (this.config.folder) {\n publicId = `${this.config.folder}/${publicId}`;\n }\n\n // Try each resource type until found\n for (const resourceType of [\"image\", \"video\", \"raw\"] as const) {\n try {\n await cloudinary.api.resource(publicId, { resource_type: resourceType });\n return { exists: true };\n } catch {\n // Try next resource type\n }\n }\n\n return { exists: false };\n } catch (error) {\n return {\n exists: false,\n error: error instanceof Error ? error.message : \"Check existence failed\",\n };\n }\n }\n\n async copy(_options: CopyOptions): Promise<CopyResult> {\n return {\n success: false,\n error: \"Cloudinary copy not implemented yet\",\n };\n }\n\n async move(_options: MoveOptions): Promise<MoveResult> {\n return {\n success: false,\n error: \"Cloudinary move not implemented yet\",\n };\n }\n\n async duplicate(options: DuplicateOptions): Promise<DuplicateResult> {\n // Duplicate is just an alias for copy\n return this.copy(options);\n }\n\n async getPresignedUrl(_options: PresignedUrlOptions): Promise<PresignedUrlResult> {\n return {\n success: false,\n error: \"Cloudinary presigned URL not implemented yet\",\n };\n }\n\n getPublicUrl(key: string, extension?: string, resourceType?: \"image\" | \"video\" | \"raw\"): string {\n // Generate Cloudinary URL\n const protocol = this.config.secure !== false ? \"https\" : \"http\";\n const baseUrl = `${protocol}://res.cloudinary.com/${this.config.cloudName}`;\n\n // `upload()` prepends `config.folder` to the stored public_id, so callers\n // holding a relative key need the folder re-prepended here to round-trip\n // correctly. If the key already starts with the folder (full public_id),\n // don't double-prefix.\n let publicId = key;\n if (this.config.folder && !publicId.startsWith(`${this.config.folder}/`)) {\n publicId = `${this.config.folder}/${publicId}`;\n }\n\n // Use explicit resource type if provided, otherwise infer from extension\n const type = resourceType || this.inferResourceType(extension || key);\n\n return `${baseUrl}/${type}/upload/${publicId}${extension ? `.${extension}` : \"\"}`;\n }\n\n /**\n * Build a valid MIME type from a Cloudinary `resource_type` + `format`.\n * Naive `${resource_type}/${format}` produces invalid types like `raw/pdf`\n * or `image/jpg`, so map known formats to their real MIME types and fall back\n * to `application/octet-stream` for raw/unknown assets.\n */\n private formatToMime(resourceType: string, format?: string): string {\n if (!format) return \"application/octet-stream\";\n const fmt = format.toLowerCase();\n const overrides: Record<string, string> = {\n jpg: \"image/jpeg\",\n jpeg: \"image/jpeg\",\n svg: \"image/svg+xml\",\n pdf: \"application/pdf\",\n mov: \"video/quicktime\",\n mp3: \"audio/mpeg\",\n };\n if (overrides[fmt]) return overrides[fmt];\n if (resourceType === \"image\" || resourceType === \"video\") {\n return `${resourceType}/${fmt}`;\n }\n return \"application/octet-stream\";\n }\n\n /** Infer Cloudinary resource type from a filename or extension */\n private inferResourceType(nameOrExt: string): \"image\" | \"video\" | \"raw\" {\n const ext = nameOrExt.includes(\".\")\n ? nameOrExt.split(\".\").pop()?.toLowerCase() || \"\"\n : nameOrExt.toLowerCase();\n const imageExts = [\"jpg\", \"jpeg\", \"png\", \"gif\", \"webp\", \"svg\", \"bmp\", \"ico\", \"avif\", \"tiff\"];\n const videoExts = [\"mp4\", \"webm\", \"mov\", \"avi\", \"mkv\", \"flv\", \"wmv\", \"m4v\", \"ogv\"];\n if (imageExts.includes(ext)) return \"image\";\n if (videoExts.includes(ext)) return \"video\";\n return \"raw\";\n }\n\n // Folder operations\n async createFolder(options: CreateFolderOptions): Promise<CreateFolderResult> {\n // Cloudinary folders are created implicitly when uploading files\n return {\n success: true,\n path: options.path,\n };\n }\n\n async deleteFolder(options: DeleteFolderOptions): Promise<DeleteFolderResult> {\n try {\n const cloudinary = await this.getClient();\n // Cloudinary requires deleting all assets in folder first, then the\n // folder itself. Prepend `config.folder` so this matches the prefix that\n // `upload`/`delete`/`exists` use — otherwise a recursive delete targets\n // the wrong (unprefixed) prefix and silently deletes nothing (or the\n // wrong assets). Preserve the trailing slash for the prefix delete so we\n // only match `folder/*` and never a sibling like `folderXYZ/*`. Stripping\n // the slash made `deleteFolder(\"folder/\")` also delete `folderXYZ/...` —\n // a data-loss hazard.\n const normalizedPath = options.path.replace(/\\/$/, \"\");\n const scopedPath = this.config.folder\n ? `${this.config.folder}/${normalizedPath}`\n : normalizedPath;\n const folderPath = `${scopedPath}/`;\n const folderApiPath = scopedPath;\n\n if (options.recursive) {\n // Delete all resources in the folder\n await cloudinary.api.delete_resources_by_prefix(folderPath);\n }\n\n // Delete the folder\n await cloudinary.api.delete_folder(folderApiPath);\n\n return {\n success: true,\n };\n } catch (error) {\n return {\n success: false,\n error: error instanceof Error ? error.message : \"Delete folder failed\",\n };\n }\n }\n\n async listFolders(_options?: ListFoldersOptions): Promise<ListFoldersResult> {\n try {\n const cloudinary = await this.getClient();\n // Use Cloudinary Admin API to list folders\n const result = await cloudinary.api.root_folders();\n\n const folders = (result.folders || []).map((folder: Record<string, unknown>) => ({\n name: String(folder.name ?? \"\"),\n path: String(folder.path ?? \"\"),\n }));\n\n return {\n success: true,\n folders,\n isTruncated: false,\n nextContinuationToken: undefined,\n };\n } catch (error) {\n return {\n success: false,\n error: error instanceof Error ? error.message : \"List folders failed\",\n };\n }\n }\n\n async folderExists(_path: string): Promise<FolderExistsResult> {\n // Previously this returned `{ exists: false, error: \"...\" }`, which looks\n // like a definitive \"the folder does not exist\" to a caller doing\n // `if (!result.exists)`. Return an `error`-only result (no `exists` claim)\n // so the unimplemented state is distinguishable from a real \"not found\".\n return {\n exists: false,\n error:\n \"Cloudinary folder existence is not implemented yet — this result does NOT mean the folder is absent.\",\n };\n }\n\n async renameFolder(_options: RenameFolderOptions): Promise<RenameFolderResult> {\n return {\n success: false,\n error: \"Cloudinary rename folder not implemented yet\",\n };\n }\n\n async copyFolder(_options: CopyFolderOptions): Promise<CopyFolderResult> {\n return {\n success: false,\n error: \"Cloudinary copy folder not implemented yet\",\n };\n }\n}\n","/**\n * Cloudinary storage service implementation\n * High-level service wrapper for Cloudinary storage operations with convenience methods\n */\n\nimport { loadCloudinaryConfig } from \"../config\";\nimport { CloudinaryProvider } from \"../providers/cloudinary\";\nimport type {\n BatchDeleteOptions,\n BatchDeleteResult,\n CloudinaryConfig,\n CopyFolderOptions,\n CopyFolderResult,\n CopyOptions,\n CopyResult,\n CreateFolderOptions,\n CreateFolderResult,\n DeleteFolderOptions,\n DeleteFolderResult,\n DeleteOptions,\n DeleteResult,\n DownloadOptions,\n DownloadResult,\n DuplicateOptions,\n DuplicateResult,\n ExistsResult,\n FolderExistsResult,\n ListFoldersOptions,\n ListFoldersResult,\n ListOptions,\n ListResult,\n MoveOptions,\n MoveResult,\n PresignedUrlOptions,\n PresignedUrlResult,\n RenameFolderOptions,\n RenameFolderResult,\n StorageInterface,\n UploadOptions,\n UploadResult,\n} from \"../types\";\n\n/**\n * Cloudinary storage service\n * High-level service for Cloudinary storage operations with convenience methods\n * @public\n */\nexport class CloudinaryService implements StorageInterface {\n private provider: CloudinaryProvider;\n private config: CloudinaryConfig;\n\n constructor();\n constructor(config: CloudinaryConfig);\n constructor(config?: CloudinaryConfig) {\n this.config = config || loadCloudinaryConfig();\n this.provider = new CloudinaryProvider(this.config);\n }\n\n // Utility methods\n getConfig(): CloudinaryConfig {\n return { ...this.config };\n }\n\n getProvider(): string {\n return this.config.provider;\n }\n\n getPublicUrl(key: string): string {\n return this.provider.getPublicUrl(key);\n }\n\n // File operations\n async upload(options: UploadOptions): Promise<UploadResult> {\n return this.provider.upload(options);\n }\n\n async download(options: DownloadOptions): Promise<DownloadResult> {\n return this.provider.download(options);\n }\n\n async delete(options: DeleteOptions): Promise<DeleteResult> {\n return this.provider.delete(options);\n }\n\n async batchDelete(options: BatchDeleteOptions): Promise<BatchDeleteResult> {\n return this.provider.batchDelete(options);\n }\n\n async list(options?: ListOptions): Promise<ListResult> {\n return this.provider.list(options);\n }\n\n async exists(key: string): Promise<ExistsResult> {\n return this.provider.exists(key);\n }\n\n async copy(options: CopyOptions): Promise<CopyResult> {\n return this.provider.copy(options);\n }\n\n async move(options: MoveOptions): Promise<MoveResult> {\n return this.provider.move(options);\n }\n\n async duplicate(options: DuplicateOptions): Promise<DuplicateResult> {\n return this.provider.duplicate(options);\n }\n\n async getPresignedUrl(options: PresignedUrlOptions): Promise<PresignedUrlResult> {\n return this.provider.getPresignedUrl(options);\n }\n\n // Folder operations\n async createFolder(options: CreateFolderOptions): Promise<CreateFolderResult> {\n return this.provider.createFolder(options);\n }\n\n async deleteFolder(options: DeleteFolderOptions): Promise<DeleteFolderResult> {\n return this.provider.deleteFolder(options);\n }\n\n async listFolders(options?: ListFoldersOptions): Promise<ListFoldersResult> {\n return this.provider.listFolders(options);\n }\n\n async folderExists(path: string): Promise<FolderExistsResult> {\n return this.provider.folderExists(path);\n }\n\n async renameFolder(options: RenameFolderOptions): Promise<RenameFolderResult> {\n return this.provider.renameFolder(options);\n }\n\n async copyFolder(options: CopyFolderOptions): Promise<CopyFolderResult> {\n return this.provider.copyFolder(options);\n }\n\n // Convenience methods\n async uploadFile(\n key: string,\n file: Buffer | Uint8Array | string,\n options?: Partial<UploadOptions>,\n ): Promise<UploadResult> {\n return this.upload({ key, file, ...options });\n }\n\n async downloadFile(key: string): Promise<DownloadResult> {\n return this.download({ key });\n }\n\n async deleteFile(key: string): Promise<DeleteResult> {\n return this.delete({ key });\n }\n\n async deleteFiles(keys: string[]): Promise<BatchDeleteResult> {\n return this.batchDelete({ keys });\n }\n\n async listFiles(prefix?: string, maxKeys?: number): Promise<ListResult> {\n return this.list({ prefix, maxKeys });\n }\n\n async fileExists(key: string): Promise<boolean> {\n const result = await this.exists(key);\n return result.exists;\n }\n\n async copyFile(\n sourceKey: string,\n destinationKey: string,\n options?: Partial<CopyOptions>,\n ): Promise<CopyResult> {\n return this.copy({ sourceKey, destinationKey, ...options });\n }\n\n async moveFile(\n sourceKey: string,\n destinationKey: string,\n options?: Partial<MoveOptions>,\n ): Promise<MoveResult> {\n return this.move({ sourceKey, destinationKey, ...options });\n }\n\n async duplicateFile(\n sourceKey: string,\n destinationKey: string,\n options?: Partial<DuplicateOptions>,\n ): Promise<DuplicateResult> {\n return this.duplicate({ sourceKey, destinationKey, ...options });\n }\n\n async renameFile(\n sourceKey: string,\n destinationKey: string,\n options?: Partial<MoveOptions>,\n ): Promise<MoveResult> {\n return this.moveFile(sourceKey, destinationKey, options);\n }\n\n async getDownloadUrl(key: string, expiresIn?: number): Promise<PresignedUrlResult> {\n return this.getPresignedUrl({ key, operation: \"get\", expiresIn });\n }\n\n async getUploadUrl(\n key: string,\n contentType?: string,\n expiresIn?: number,\n ): Promise<PresignedUrlResult> {\n return this.getPresignedUrl({\n key,\n operation: \"put\",\n contentType,\n expiresIn,\n });\n }\n\n // Folder convenience methods\n async createFolderPath(path: string): Promise<CreateFolderResult> {\n return this.createFolder({ path });\n }\n\n async deleteFolderPath(path: string, recursive = false): Promise<DeleteFolderResult> {\n return this.deleteFolder({ path, recursive });\n }\n\n async folderPathExists(path: string): Promise<boolean> {\n const result = await this.folderExists(path);\n return result.exists;\n }\n\n async renameFolderPath(oldPath: string, newPath: string): Promise<RenameFolderResult> {\n return this.renameFolder({ oldPath, newPath });\n }\n\n async copyFolderPath(\n sourcePath: string,\n destinationPath: string,\n recursive = true,\n ): Promise<CopyFolderResult> {\n return this.copyFolder({ sourcePath, destinationPath, recursive });\n }\n}\n","import type { EnvRecord } from \"./config\";\nimport { loadCloudinaryConfig } from \"./config\";\nimport { CloudinaryService } from \"./services/cloudinary\";\nimport type { CloudinaryConfig } from \"./types\";\n\n// === SERVICE CLASSES ===\nexport { CloudinaryService } from \"./services/cloudinary\";\n\n// === MAIN FACTORY FUNCTIONS (Primary API) ===\n/**\n * Create Cloudinary service using environment variables or manual configuration\n * @param config Optional Cloudinary configuration. If not provided, loads from environment variables\n * @param env - Optional environment record\n * @returns CloudinaryService instance\n * @throws Error if configuration is invalid or environment variables are missing\n * @public\n *\n * @example\n * ```typescript\n * import { createCloudinary } from '@kumix/storage/cloudinary';\n *\n * // Using environment variables\n * // Set: KUMIX_CLOUDINARY_CLOUD_NAME=my-cloud, KUMIX_CLOUDINARY_API_KEY=123..., etc.\n * const cloudinaryFromEnv = createCloudinary();\n * await cloudinaryFromEnv.uploadFile('photo.jpg', imageBuffer);\n *\n * // Using manual configuration\n * const cloudinary = createCloudinary({\n * provider: 'cloudinary',\n * cloudName: 'my-cloud-name',\n * apiKey: '123456789012345',\n * apiSecret: 'your-api-secret',\n * secure: true,\n * folder: 'uploads'\n * });\n *\n * // Upload with transformations\n * await cloudinary.uploadFile('profile-pic.jpg', imageBuffer);\n *\n * // Get optimized URL\n * const publicUrl = cloudinary.getPublicUrl('profile-pic.jpg');\n * console.log(publicUrl); // Cloudinary optimized URL\n * ```\n */\nexport function createCloudinary(): CloudinaryService;\nexport function createCloudinary(config: CloudinaryConfig): CloudinaryService;\nexport function createCloudinary(config: CloudinaryConfig, env: EnvRecord): CloudinaryService;\nexport function createCloudinary(config: undefined, env: EnvRecord): CloudinaryService;\nexport function createCloudinary(config?: CloudinaryConfig, env?: EnvRecord): CloudinaryService {\n if (config) {\n return new CloudinaryService(config);\n }\n const envConfig = loadCloudinaryConfig(env);\n return new CloudinaryService(envConfig);\n}\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"helpers.d.ts","names":[],"sources":["../src/helpers.ts"],"mappings":"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"}
1
+ {"version":3,"file":"helpers.d.ts","names":[],"sources":["../src/helpers.ts"],"mappings":";;AA4GA;;;;AAAqE;AAgCrE;;;;AAA4C;AAuB5C;;;;;;;;;AAG0B;AAkC1B;iBA5FgB,eAAA,CAAgB,YAAA,UAAsB,MAAe;;;;;;;;AA+F3C;AA4C1B;;;;AAA4C;AA4B5C;;;;AAAiD;AAkCjD;;iBAzKgB,WAAA,CAAY,QAAgB;;AAyKK;AA6BjD;;;;AAE0B;AA6B1B;;;;;;;;AAA+D;AAoD/D;;iBAlQgB,gBAAA,CACd,QAAA,UACA,OAAA;EACG,KAAA;EAAgB,KAAA;AAAA;;;;AAmQD;AAuCpB;;;;;;;;AAAmE;AA8CnE;;;;;;;;;;;iBAtTgB,gBAAA,CACd,QAAA,UACA,YAAA;EACG,KAAA;EAAgB,KAAA;AAAA;;;;;;;;;;;;AAsVD;AA6BpB;;;;;;;;iBAvUgB,cAAA,CAAe,KAAa;AAsX5C;;;;;;;;;AAGQ;AA+DR;;;;;;;AAlEA,iBA1VgB,gBAAA,CAAiB,QAAgB;;AA8ZzC;AA0DR;;;;;;;;AAEO;AAiEP;;;;AAEqD;AAuBrD;;;;iBAlhBgB,gBAAA,CAAiB,QAAgB;AAwiBjD;;;;AAA4C;AAsB5C;;;;AAA4C;AAyB5C;;;;AAA+C;AAoC/C;;;;AAA0C;AA2B1C;AA9GA,iBA3gBgB,oBAAA,CACd,MAAA;AACA,QAAwB;;AAunBU;AA4BpC;;;;AAAyC;AA0BzC;;;;AAAuC;AA2BvC;;;;AAA0D;AAwC1D;;;;;;iBAntBgB,UAAA,CAAW,GAAA;EAAgB,MAAA;EAAiB,GAAA;AAAA;AA6wB5D;;;;;;;;;AAGU;AAqDV;;;;AAAsE;AAiCtE;;;;AAzFA,iBAztBgB,cAAA,CACd,OAAA,UACA,MAAA,UACA,GAAA,UACA,QAAA;;;;;;;;;;;AAqzBiD;AA2EnD;;;;AAA4D;;iBAz1B5C,aAAA,CAAc,GAAA;EAAgB,KAAA;EAAgB,KAAA;AAAA;;;;;;;;;;;;;;;;;;;;;;iBA8CxC,YAAA,CAAa,IAAA,EAAM,IAAA,GAAO,OAAA,CAAQ,UAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;iBA+BxC,WAAA,CAAY,IAAA,EAAM,IAAA;EAChC,IAAA;EACA,IAAA;EACA,IAAA;EACA,YAAA,EAAc,IAAI;AAAA;;;;;;;;;;;;;;;;;;;;iBA6BJ,iBAAA,CACd,QAAA,UACA,MAAA,WACA,gBAAA;;;;;;;;;;;;;;;;;;;;;;;;iBA4Cc,aAAA,CAAc,GAAA;EAC5B,MAAA;EACA,GAAA;EACA,MAAA;AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;iBA+Dc,gBAAA,CAAiB,MAAA,EAAQ,MAAM;EAC7C,KAAA;EACA,MAAA;AAAA;;;;;;;;;;;;;;;;;;;;;iBA0Dc,kBAAA,CAAmB,UAAA;EACjC,KAAA;EACA,KAAA;AAAA;;;;;;;;;;;;;;;;;;;;;;iBAiEc,qBAAA,CACd,QAAA,UACA,WAAmD;;;;;;;;;;;;;;;;;;iBAuBrC,WAAA,CAAY,QAAgB;;;;;;;;;;;;;;;;;;iBAsB5B,WAAA,CAAY,QAAgB;;;;;;;;;;;;;;;;;;iBAsB5B,WAAA,CAAY,QAAgB;;;;;;;;;;;;;;;;;;;;;iBAyB5B,cAAA,CAAe,QAAgB;;;;;;;;;;;;;;;;;;;;;iBAoC/B,aAAA,CAAc,IAAY;;;;;;;;;;;;;;;;;;;;;iBA2B1B,QAAA,IAAY,QAAQ;;;;;;;;;;;;;;;;;;;;;iBA4BpB,aAAA,CAAc,GAAW;;;;;;;;;;;;;;;;;;;;;iBA0BzB,WAAA,CAAY,GAAW;;;;;;;;;;;;;;;;;;;;;;iBA2BvB,cAAA,CAAe,MAAA,WAAiB,UAAU;;;;;;;;;;;;;;;;;;;;;;;;;;iBAwC1C,cAAA,CACd,MAAA,EAAQ,UAAU,EAClB,cAAA,YACA,QAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAuDoB,gBAAA,CACpB,OAAA,EAAS,UAAA,WACT,SAAA,+BACC,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAqDM,iBAAA,CAAkB,SAAA,YAAqB,MAAe;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAiCtD,kBAAA,CACd,KAAA,EAAO,IAAA,IACP,OAAA;EACE,OAAA;EACA,YAAA;EACA,QAAA;AAAA,IAED,KAAK;EAAG,KAAA;EAAgB,QAAA;EAAkB,MAAA;AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBA2E7B,yBAAA,CAA0B,MAAkB,EAAV,UAAU"}
package/dist/helpers.js CHANGED
@@ -265,7 +265,7 @@ function sanitizeFileName(fileName) {
265
265
  */
266
266
  function getFileExtension(fileName) {
267
267
  const lastDot = fileName.lastIndexOf(".");
268
- if (lastDot <= 0) return "";
268
+ if (lastDot <= 0 || lastDot === fileName.length - 1) return "";
269
269
  return `.${fileName.slice(lastDot + 1).toLowerCase()}`;
270
270
  }
271
271
  /**
@@ -483,7 +483,7 @@ function getFileInfo(file) {
483
483
  function generateUniqueKey(fileName, prefix, includeTimestamp = true) {
484
484
  const sanitized = sanitizeFileName(fileName);
485
485
  const extension = getFileExtension(sanitized);
486
- const baseName = sanitized.replace(extension, "");
486
+ const baseName = extension ? sanitized.slice(0, sanitized.length - extension.length) : sanitized;
487
487
  let uniquePart = "";
488
488
  if (includeTimestamp) uniquePart = `-${Date.now()}-${Math.random().toString(36).substring(2, 8)}`;
489
489
  const finalName = `${baseName}${uniquePart}${extension}`;