@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.
- package/dist/cloudinary.d.ts +5 -5
- package/dist/cloudinary.d.ts.map +1 -1
- package/dist/cloudinary.js +83 -46
- package/dist/cloudinary.js.map +1 -1
- package/dist/helpers.d.ts.map +1 -1
- package/dist/helpers.js +2 -2
- package/dist/helpers.js.map +1 -1
- package/dist/s3.js +101 -47
- package/dist/s3.js.map +1 -1
- package/package.json +3 -3
package/dist/cloudinary.d.ts
CHANGED
|
@@ -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,
|
|
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,
|
|
40
|
-
moveFile(sourceKey: string, destinationKey: string,
|
|
41
|
-
duplicateFile(sourceKey: string, destinationKey: string,
|
|
42
|
-
renameFile(sourceKey: string, destinationKey: string,
|
|
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>;
|
package/dist/cloudinary.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cloudinary.d.ts","names":[],"sources":["../src/services/cloudinary.ts","../src/cloudinary.ts"],"mappings":";;;;;;;;cA+Ca,iBAAA,YAA6B,gBAAA;EAAA,QAChC,QAAA;EAAA,QACA,MAAA;;cAGI,MAAA,EAAQ,gBAAA;EAOpB,SAAA,IAAa,gBAAA;EAIb,WAAA;EAIA,YAAA,CAAa,GAAA;EAKP,MAAA,CAAO,OAAA,EAAS,aAAA,GAAgB,OAAA,CAAQ,YAAA;EAIxC,QAAA,CAAS,OAAA,EAAS,eAAA,GAAkB,OAAA,CAAQ,cAAA;EAI5C,MAAA,CAAO,OAAA,EAAS,aAAA,GAAgB,OAAA,CAAQ,YAAA;EAIxC,WAAA,CAAY,OAAA,EAAS,kBAAA,GAAqB,OAAA,CAAQ,iBAAA;EAIlD,IAAA,CAAK,OAAA,GAAU,WAAA,GAAc,OAAA,CAAQ,UAAA;EAIrC,MAAA,CAAO,GAAA,WAAc,OAAA,CAAQ,YAAA;EAI7B,IAAA,CAAK,OAAA,EAAS,WAAA,GAAc,OAAA,CAAQ,UAAA;EAIpC,IAAA,CAAK,OAAA,EAAS,WAAA,GAAc,OAAA,CAAQ,UAAA;EAIpC,SAAA,CAAU,OAAA,EAAS,gBAAA,GAAmB,OAAA,CAAQ,eAAA;EAI9C,eAAA,CAAgB,OAAA,EAAS,mBAAA,GAAsB,OAAA,CAAQ,kBAAA;EAKvD,YAAA,CAAa,OAAA,EAAS,mBAAA,GAAsB,OAAA,CAAQ,kBAAA;EAIpD,YAAA,CAAa,OAAA,EAAS,mBAAA,GAAsB,OAAA,CAAQ,kBAAA;EAIpD,WAAA,CAAY,OAAA,GAAU,kBAAA,GAAqB,OAAA,CAAQ,iBAAA;EAInD,YAAA,CAAa,IAAA,WAAe,OAAA,CAAQ,kBAAA;EAIpC,YAAA,CAAa,OAAA,EAAS,mBAAA,GAAsB,OAAA,CAAQ,kBAAA;EAIpD,UAAA,CAAW,OAAA,EAAS,iBAAA,GAAoB,OAAA,CAAQ,gBAAA;EAKhD,UAAA,CACJ,GAAA,UACA,IAAA,EAAM,MAAA,GAAS,UAAA,WACf,
|
|
1
|
+
{"version":3,"file":"cloudinary.d.ts","names":[],"sources":["../src/services/cloudinary.ts","../src/cloudinary.ts"],"mappings":"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"}
|
package/dist/cloudinary.js
CHANGED
|
@@ -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
|
-
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
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
|
-
|
|
25
|
-
|
|
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
|
-
|
|
45
|
-
|
|
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
|
|
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
|
|
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
|
|
119
|
+
await cloudinary.api.resource(publicId, { resource_type: "image" });
|
|
108
120
|
resourceType = "image";
|
|
109
121
|
} catch {
|
|
110
122
|
try {
|
|
111
|
-
await
|
|
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
|
|
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
|
|
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.
|
|
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) =>
|
|
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 ?
|
|
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
|
|
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
|
|
318
|
-
const
|
|
319
|
-
|
|
320
|
-
|
|
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
|
|
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,
|
|
479
|
+
async uploadFile(key, file, options) {
|
|
442
480
|
return this.upload({
|
|
443
481
|
key,
|
|
444
482
|
file,
|
|
445
|
-
|
|
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,
|
|
504
|
+
async copyFile(sourceKey, destinationKey, options) {
|
|
468
505
|
return this.copy({
|
|
469
506
|
sourceKey,
|
|
470
507
|
destinationKey,
|
|
471
|
-
|
|
508
|
+
...options
|
|
472
509
|
});
|
|
473
510
|
}
|
|
474
|
-
async moveFile(sourceKey, destinationKey,
|
|
511
|
+
async moveFile(sourceKey, destinationKey, options) {
|
|
475
512
|
return this.move({
|
|
476
513
|
sourceKey,
|
|
477
514
|
destinationKey,
|
|
478
|
-
|
|
515
|
+
...options
|
|
479
516
|
});
|
|
480
517
|
}
|
|
481
|
-
async duplicateFile(sourceKey, destinationKey,
|
|
518
|
+
async duplicateFile(sourceKey, destinationKey, options) {
|
|
482
519
|
return this.duplicate({
|
|
483
520
|
sourceKey,
|
|
484
521
|
destinationKey,
|
|
485
|
-
|
|
522
|
+
...options
|
|
486
523
|
});
|
|
487
524
|
}
|
|
488
|
-
async renameFile(sourceKey, destinationKey,
|
|
489
|
-
return this.moveFile(sourceKey, destinationKey,
|
|
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({
|
package/dist/cloudinary.js.map
CHANGED
|
@@ -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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"}
|
package/dist/helpers.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
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;
|
|
1
|
+
{"version":3,"file":"helpers.d.ts","names":[],"sources":["../src/helpers.ts"],"mappings":"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"}
|
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.
|
|
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}`;
|