@kumix/storage 0.1.0 → 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.
@@ -1,4 +1,4 @@
1
- import { A as DownloadOptions, B as PresignedUrlResult, C as RenameFolderResult, D as CopyResult, E as CopyOptions, F as ListOptions, H as UploadResult, I as ListResult, L as MoveOptions, M as DuplicateOptions, N as DuplicateResult, O as DeleteOptions, P as ExistsResult, R as MoveResult, S as RenameFolderOptions, T as BatchDeleteResult, U as CloudinaryConfig, V as UploadOptions, _ as DeleteFolderOptions, b as ListFoldersOptions, f as StorageInterface, g as CreateFolderResult, h as CreateFolderOptions, j as DownloadResult, k as DeleteResult, m as CopyFolderResult, n as EnvRecord, p as CopyFolderOptions, v as DeleteFolderResult, w as BatchDeleteOptions, x as ListFoldersResult, y as FolderExistsResult, z as PresignedUrlOptions } from "./config-J6AdDh_c.js";
1
+ import { A as DownloadOptions, B as PresignedUrlResult, C as RenameFolderResult, D as CopyResult, E as CopyOptions, F as ListOptions, H as UploadResult, I as ListResult, L as MoveOptions, M as DuplicateOptions, N as DuplicateResult, O as DeleteOptions, P as ExistsResult, R as MoveResult, S as RenameFolderOptions, T as BatchDeleteResult, U as CloudinaryConfig, V as UploadOptions, _ as DeleteFolderOptions, b as ListFoldersOptions, f as StorageInterface, g as CreateFolderResult, h as CreateFolderOptions, j as DownloadResult, k as DeleteResult, m as CopyFolderResult, n as EnvRecord, p as CopyFolderOptions, v as DeleteFolderResult, w as BatchDeleteOptions, x as ListFoldersResult, y as FolderExistsResult, z as PresignedUrlOptions } from "./config-DGzEH-Zd.js";
2
2
 
3
3
  //#region src/services/cloudinary.d.ts
4
4
  /**
@@ -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>;
@@ -88,6 +88,7 @@ declare class CloudinaryService implements StorageInterface {
88
88
  declare function createCloudinary(): CloudinaryService;
89
89
  declare function createCloudinary(config: CloudinaryConfig): CloudinaryService;
90
90
  declare function createCloudinary(config: CloudinaryConfig, env: EnvRecord): CloudinaryService;
91
+ declare function createCloudinary(config: undefined, env: EnvRecord): CloudinaryService;
91
92
  //#endregion
92
93
  export { CloudinaryService, createCloudinary };
93
94
  //# sourceMappingURL=cloudinary.d.ts.map
@@ -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":";;;;;;;;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,OAAA,GAAU,OAAA,CAAQ,aAAA,IACjB,OAAA,CAAQ,YAAA;EAIL,YAAA,CAAa,GAAA,WAAc,OAAA,CAAQ,cAAA;EAInC,UAAA,CAAW,GAAA,WAAc,OAAA,CAAQ,YAAA;EAIjC,WAAA,CAAY,IAAA,aAAiB,OAAA,CAAQ,iBAAA;EAIrC,SAAA,CAAU,MAAA,WAAiB,OAAA,YAAmB,OAAA,CAAQ,UAAA;EAItD,UAAA,CAAW,GAAA,WAAc,OAAA;EAKzB,QAAA,CACJ,SAAA,UACA,cAAA,UACA,OAAA,GAAU,OAAA,CAAQ,WAAA,IACjB,OAAA,CAAQ,UAAA;EAIL,QAAA,CACJ,SAAA,UACA,cAAA,UACA,OAAA,GAAU,OAAA,CAAQ,WAAA,IACjB,OAAA,CAAQ,UAAA;EAIL,aAAA,CACJ,SAAA,UACA,cAAA,UACA,OAAA,GAAU,OAAA,CAAQ,gBAAA,IACjB,OAAA,CAAQ,eAAA;EAIL,UAAA,CACJ,SAAA,UACA,cAAA,UACA,OAAA,GAAU,OAAA,CAAQ,WAAA,IACjB,OAAA,CAAQ,UAAA;EAIL,cAAA,CAAe,GAAA,UAAa,SAAA,YAAqB,OAAA,CAAQ,kBAAA;EAIzD,YAAA,CACJ,GAAA,UACA,WAAA,WACA,SAAA,YACC,OAAA,CAAQ,kBAAA;EAUL,gBAAA,CAAiB,IAAA,WAAe,OAAA,CAAQ,kBAAA;EAIxC,gBAAA,CAAiB,IAAA,UAAc,SAAA,aAAoB,OAAA,CAAQ,kBAAA;EAI3D,gBAAA,CAAiB,IAAA,WAAe,OAAA;EAKhC,gBAAA,CAAiB,OAAA,UAAiB,OAAA,WAAkB,OAAA,CAAQ,kBAAA;EAI5D,cAAA,CACJ,UAAA,UACA,eAAA,UACA,SAAA,aACC,OAAA,CAAQ,gBAAA;AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBClMG,gBAAA,IAAoB,iBAAiB;AAAA,iBACrC,gBAAA,CAAiB,MAAA,EAAQ,gBAAA,GAAmB,iBAAiB;AAAA,iBAC7D,gBAAA,CAAiB,MAAA,EAAQ,gBAAA,EAAkB,GAAA,EAAK,SAAA,GAAY,iBAAA;AAAA,iBAC5D,gBAAA,CAAiB,MAAA,aAAmB,GAAA,EAAK,SAAA,GAAY,iBAAiB"}
@@ -1,28 +1,41 @@
1
- import { o as loadCloudinaryConfig } from "./config-t-NVJICl.js";
2
- import { v2 } from "cloudinary";
1
+ import { o as loadCloudinaryConfig } from "./config-By5ib98s.js";
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
- v2.config({
18
- cloud_name: config.cloudName,
19
- api_key: config.apiKey,
20
- api_secret: config.apiSecret,
21
- secure: config.secure !== false
8
+ }
9
+ /**
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.
20
+ */
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({
29
+ cloud_name: this.config.cloudName,
30
+ api_key: this.config.apiKey,
31
+ api_secret: this.config.apiSecret,
32
+ secure: this.config.secure !== false
22
33
  });
34
+ return this._client;
23
35
  }
24
36
  async upload(options) {
25
37
  try {
38
+ const cloudinary = await this.getClient();
26
39
  let fileData;
27
40
  if (typeof options.file === "string") fileData = options.file;
28
41
  else fileData = `data:${options.contentType || "application/octet-stream"};base64,${Buffer.from(options.file).toString("base64")}`;
@@ -33,19 +46,23 @@ var CloudinaryProvider = class {
33
46
  let folder = "";
34
47
  let publicId = keyParts[keyParts.length - 1];
35
48
  if (keyParts.length > 1) folder = keyParts.slice(0, -1).join("/");
36
- const dotIndex = publicId.lastIndexOf(".");
37
- 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
+ }
38
53
  const uploadOptions = {
39
54
  public_id: publicId,
40
55
  resource_type: resourceType,
41
56
  overwrite: true,
42
57
  invalidate: true,
43
- ...options.metadata
58
+ ...options.metadata ? { context: options.metadata } : {}
44
59
  };
45
- if (folder) uploadOptions.folder = folder;
46
- if (this.config.folder) uploadOptions.folder = `${this.config.folder}/${folder}`;
47
- const result = await v2.uploader.upload(fileData, uploadOptions);
48
- const publicUrl = this.getPublicUrl(result.public_id, result.format);
60
+ const folderParts = [];
61
+ if (this.config.folder) folderParts.push(this.config.folder);
62
+ if (folder) folderParts.push(folder);
63
+ if (folderParts.length > 0) uploadOptions.folder = folderParts.join("/");
64
+ const result = await cloudinary.uploader.upload(fileData, uploadOptions);
65
+ const publicUrl = this.getPublicUrl(result.public_id, result.format, resourceType);
49
66
  return {
50
67
  success: true,
51
68
  key: result.public_id,
@@ -63,7 +80,11 @@ var CloudinaryProvider = class {
63
80
  }
64
81
  async download(options) {
65
82
  try {
66
- 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);
67
88
  const response = await fetch(publicUrl);
68
89
  if (!response.ok) return {
69
90
  success: false,
@@ -88,25 +109,24 @@ var CloudinaryProvider = class {
88
109
  }
89
110
  async delete(options) {
90
111
  try {
91
- const keyParts = options.key.split("/");
112
+ const cloudinary = await this.getClient();
92
113
  let publicId = options.key;
93
- if (keyParts.length > 1) publicId = `${keyParts.slice(0, -1).join("/")}/${keyParts[keyParts.length - 1]}`;
94
114
  const dotIndex = publicId.lastIndexOf(".");
95
115
  if (dotIndex > 0) publicId = publicId.slice(0, dotIndex);
96
116
  if (this.config.folder) publicId = `${this.config.folder}/${publicId}`;
97
117
  let resourceType = "raw";
98
118
  try {
99
- await v2.api.resource(publicId, { resource_type: "image" });
119
+ await cloudinary.api.resource(publicId, { resource_type: "image" });
100
120
  resourceType = "image";
101
121
  } catch {
102
122
  try {
103
- await v2.api.resource(publicId, { resource_type: "video" });
123
+ await cloudinary.api.resource(publicId, { resource_type: "video" });
104
124
  resourceType = "video";
105
125
  } catch {
106
126
  resourceType = "raw";
107
127
  }
108
128
  }
109
- await v2.uploader.destroy(publicId, {
129
+ await cloudinary.uploader.destroy(publicId, {
110
130
  resource_type: resourceType,
111
131
  invalidate: true
112
132
  });
@@ -120,12 +140,30 @@ var CloudinaryProvider = class {
120
140
  }
121
141
  async batchDelete(options) {
122
142
  try {
143
+ const cloudinary = await this.getClient();
144
+ const publicIds = options.keys.map((key) => {
145
+ let publicId = key;
146
+ const dotIndex = publicId.lastIndexOf(".");
147
+ if (dotIndex > 0) publicId = publicId.slice(0, dotIndex);
148
+ if (this.config.folder) publicId = `${this.config.folder}/${publicId}`;
149
+ return publicId;
150
+ });
151
+ const result = await cloudinary.api.delete_resources(publicIds);
152
+ const deleted = [];
153
+ const errors = [];
154
+ for (const [publicId, info] of Object.entries(result)) {
155
+ const originalKey = options.keys[publicIds.indexOf(publicId)] || publicId;
156
+ const status = info?.status;
157
+ if (status === "deleted" || status === "not_found") deleted.push(originalKey);
158
+ else errors.push({
159
+ key: originalKey,
160
+ error: `Delete status: ${String(status)}`
161
+ });
162
+ }
123
163
  return {
124
- success: false,
125
- errors: options.keys.map((key) => ({
126
- key,
127
- error: "Cloudinary batch delete not implemented yet"
128
- }))
164
+ success: errors.length === 0,
165
+ deleted,
166
+ errors
129
167
  };
130
168
  } catch (error) {
131
169
  return {
@@ -139,55 +177,61 @@ var CloudinaryProvider = class {
139
177
  }
140
178
  async list(options = {}) {
141
179
  try {
180
+ const cloudinary = await this.getClient();
142
181
  const prefix = options.prefix || "";
143
182
  const maxResults = options.maxKeys || 50;
144
- const [images, videos, rawFiles] = await Promise.all([
145
- v2.api.resources({
146
- type: "upload",
147
- resource_type: "image",
148
- prefix,
149
- max_results: maxResults,
150
- next_cursor: options.continuationToken
151
- }).catch(() => ({
152
- resources: [],
153
- next_cursor: void 0
154
- })),
155
- v2.api.resources({
156
- type: "upload",
157
- resource_type: "video",
158
- prefix,
159
- max_results: maxResults,
160
- next_cursor: options.continuationToken
161
- }).catch(() => ({
162
- resources: [],
163
- next_cursor: void 0
164
- })),
165
- v2.api.resources({
166
- type: "upload",
167
- resource_type: "raw",
168
- prefix,
169
- max_results: maxResults,
170
- next_cursor: options.continuationToken
171
- }).catch(() => ({
172
- resources: [],
173
- next_cursor: void 0
174
- }))
175
- ]);
183
+ const TYPES = [
184
+ "image",
185
+ "video",
186
+ "raw"
187
+ ];
188
+ let type = "image";
189
+ let cursor;
190
+ if (options.continuationToken) {
191
+ const sep = options.continuationToken.indexOf(":");
192
+ if (sep > 0) {
193
+ const t = options.continuationToken.slice(0, sep);
194
+ const c = options.continuationToken.slice(sep + 1);
195
+ if (TYPES.includes(t)) {
196
+ type = t;
197
+ cursor = c || void 0;
198
+ }
199
+ }
200
+ }
201
+ const fetchPage = (rt, c) => cloudinary.api.resources({
202
+ type: "upload",
203
+ resource_type: rt,
204
+ prefix,
205
+ max_results: maxResults,
206
+ ...c ? { next_cursor: c } : {}
207
+ }).catch(() => ({
208
+ resources: [],
209
+ next_cursor: void 0
210
+ }));
211
+ let page = await fetchPage(type, cursor);
212
+ let resources = page.resources || [];
213
+ let nextCursor = page.next_cursor;
214
+ let typeIdx = TYPES.indexOf(type);
215
+ while (!nextCursor && resources.length === 0 && typeIdx < TYPES.length - 1) {
216
+ typeIdx++;
217
+ type = TYPES[typeIdx];
218
+ page = await fetchPage(type);
219
+ resources = page.resources || [];
220
+ nextCursor = page.next_cursor;
221
+ }
222
+ const files = resources.map((resource) => ({
223
+ key: String(resource.public_id || ""),
224
+ size: Number(resource.bytes) || 0,
225
+ lastModified: new Date(String(resource.created_at || /* @__PURE__ */ new Date())),
226
+ etag: String(resource.etag || ""),
227
+ contentType: this.formatToMime(String(resource.resource_type || "raw"), resource.format ? String(resource.format) : void 0)
228
+ }));
229
+ const nextContinuationToken = nextCursor ? `${type}:${nextCursor}` : void 0;
176
230
  return {
177
231
  success: true,
178
- files: [
179
- ...images.resources || [],
180
- ...videos.resources || [],
181
- ...rawFiles.resources || []
182
- ].map((resource) => ({
183
- key: String(resource.public_id || ""),
184
- size: Number(resource.bytes) || 0,
185
- lastModified: new Date(String(resource.created_at || /* @__PURE__ */ new Date())),
186
- etag: String(resource.etag || ""),
187
- contentType: resource.format ? `${resource.resource_type}/${resource.format}` : "application/octet-stream"
188
- })),
189
- isTruncated: !!(images.next_cursor || videos.next_cursor || rawFiles.next_cursor),
190
- nextContinuationToken: images.next_cursor || videos.next_cursor || rawFiles.next_cursor
232
+ files,
233
+ isTruncated: !!nextContinuationToken,
234
+ nextContinuationToken
191
235
  };
192
236
  } catch (error) {
193
237
  return {
@@ -196,12 +240,22 @@ var CloudinaryProvider = class {
196
240
  };
197
241
  }
198
242
  }
199
- async exists(_key) {
243
+ async exists(key) {
200
244
  try {
201
- return {
202
- exists: false,
203
- error: "Cloudinary exists check not implemented yet"
204
- };
245
+ const cloudinary = await this.getClient();
246
+ let publicId = key;
247
+ const dotIndex = publicId.lastIndexOf(".");
248
+ if (dotIndex > 0) publicId = publicId.slice(0, dotIndex);
249
+ if (this.config.folder) publicId = `${this.config.folder}/${publicId}`;
250
+ for (const resourceType of [
251
+ "image",
252
+ "video",
253
+ "raw"
254
+ ]) try {
255
+ await cloudinary.api.resource(publicId, { resource_type: resourceType });
256
+ return { exists: true };
257
+ } catch {}
258
+ return { exists: false };
205
259
  } catch (error) {
206
260
  return {
207
261
  exists: false,
@@ -210,68 +264,98 @@ var CloudinaryProvider = class {
210
264
  }
211
265
  }
212
266
  async copy(_options) {
213
- try {
214
- return {
215
- success: false,
216
- error: "Cloudinary copy not implemented yet"
217
- };
218
- } catch (error) {
219
- return {
220
- success: false,
221
- error: error instanceof Error ? error.message : "Copy failed"
222
- };
223
- }
267
+ return {
268
+ success: false,
269
+ error: "Cloudinary copy not implemented yet"
270
+ };
224
271
  }
225
272
  async move(_options) {
226
- try {
227
- return {
228
- success: false,
229
- error: "Cloudinary move not implemented yet"
230
- };
231
- } catch (error) {
232
- return {
233
- success: false,
234
- error: error instanceof Error ? error.message : "Move failed"
235
- };
236
- }
273
+ return {
274
+ success: false,
275
+ error: "Cloudinary move not implemented yet"
276
+ };
237
277
  }
238
278
  async duplicate(options) {
239
279
  return this.copy(options);
240
280
  }
241
281
  async getPresignedUrl(_options) {
242
- try {
243
- return {
244
- success: false,
245
- error: "Cloudinary presigned URL not implemented yet"
246
- };
247
- } catch (error) {
248
- return {
249
- success: false,
250
- error: error instanceof Error ? error.message : "Presigned URL generation failed"
251
- };
252
- }
253
- }
254
- getPublicUrl(key, extension) {
255
- return `${`${this.config.secure !== false ? "https" : "http"}://res.cloudinary.com/${this.config.cloudName}`}/image/upload/${key}${extension ? `.${extension}` : ""}`;
282
+ return {
283
+ success: false,
284
+ error: "Cloudinary presigned URL not implemented yet"
285
+ };
286
+ }
287
+ getPublicUrl(key, extension, resourceType) {
288
+ const baseUrl = `${this.config.secure !== false ? "https" : "http"}://res.cloudinary.com/${this.config.cloudName}`;
289
+ let publicId = key;
290
+ if (this.config.folder && !publicId.startsWith(`${this.config.folder}/`)) publicId = `${this.config.folder}/${publicId}`;
291
+ return `${baseUrl}/${resourceType || this.inferResourceType(extension || key)}/upload/${publicId}${extension ? `.${extension}` : ""}`;
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
+ }
314
+ /** Infer Cloudinary resource type from a filename or extension */
315
+ inferResourceType(nameOrExt) {
316
+ const ext = nameOrExt.includes(".") ? nameOrExt.split(".").pop()?.toLowerCase() || "" : nameOrExt.toLowerCase();
317
+ const imageExts = [
318
+ "jpg",
319
+ "jpeg",
320
+ "png",
321
+ "gif",
322
+ "webp",
323
+ "svg",
324
+ "bmp",
325
+ "ico",
326
+ "avif",
327
+ "tiff"
328
+ ];
329
+ const videoExts = [
330
+ "mp4",
331
+ "webm",
332
+ "mov",
333
+ "avi",
334
+ "mkv",
335
+ "flv",
336
+ "wmv",
337
+ "m4v",
338
+ "ogv"
339
+ ];
340
+ if (imageExts.includes(ext)) return "image";
341
+ if (videoExts.includes(ext)) return "video";
342
+ return "raw";
256
343
  }
257
344
  async createFolder(options) {
258
- try {
259
- return {
260
- success: true,
261
- path: options.path
262
- };
263
- } catch (error) {
264
- return {
265
- success: false,
266
- error: error instanceof Error ? error.message : "Create folder failed"
267
- };
268
- }
345
+ return {
346
+ success: true,
347
+ path: options.path
348
+ };
269
349
  }
270
350
  async deleteFolder(options) {
271
351
  try {
272
- const folderPath = options.path.endsWith("/") ? options.path.slice(0, -1) : options.path;
273
- if (options.recursive) await v2.api.delete_resources_by_prefix(folderPath);
274
- await v2.api.delete_folder(folderPath);
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);
275
359
  return { success: true };
276
360
  } catch (error) {
277
361
  return {
@@ -284,9 +368,9 @@ var CloudinaryProvider = class {
284
368
  try {
285
369
  return {
286
370
  success: true,
287
- folders: ((await v2.api.root_folders()).folders || []).map((folder) => ({
288
- name: folder.name,
289
- 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 ?? "")
290
374
  })),
291
375
  isTruncated: false,
292
376
  nextContinuationToken: void 0
@@ -299,43 +383,22 @@ var CloudinaryProvider = class {
299
383
  }
300
384
  }
301
385
  async folderExists(_path) {
302
- try {
303
- return {
304
- exists: false,
305
- error: "Cloudinary folder exists check not implemented yet"
306
- };
307
- } catch (error) {
308
- return {
309
- exists: false,
310
- error: error instanceof Error ? error.message : "Check folder existence failed"
311
- };
312
- }
386
+ return {
387
+ exists: false,
388
+ error: "Cloudinary folder existence is not implemented yet — this result does NOT mean the folder is absent."
389
+ };
313
390
  }
314
391
  async renameFolder(_options) {
315
- try {
316
- return {
317
- success: false,
318
- error: "Cloudinary rename folder not implemented yet"
319
- };
320
- } catch (error) {
321
- return {
322
- success: false,
323
- error: error instanceof Error ? error.message : "Rename folder failed"
324
- };
325
- }
392
+ return {
393
+ success: false,
394
+ error: "Cloudinary rename folder not implemented yet"
395
+ };
326
396
  }
327
397
  async copyFolder(_options) {
328
- try {
329
- return {
330
- success: false,
331
- error: "Cloudinary copy folder not implemented yet"
332
- };
333
- } catch (error) {
334
- return {
335
- success: false,
336
- error: error instanceof Error ? error.message : "Copy folder failed"
337
- };
338
- }
398
+ return {
399
+ success: false,
400
+ error: "Cloudinary copy folder not implemented yet"
401
+ };
339
402
  }
340
403
  };
341
404
  //#endregion
@@ -413,12 +476,11 @@ var CloudinaryService = class {
413
476
  async copyFolder(options) {
414
477
  return this.provider.copyFolder(options);
415
478
  }
416
- async uploadFile(key, file, contentType, metadata) {
479
+ async uploadFile(key, file, options) {
417
480
  return this.upload({
418
481
  key,
419
482
  file,
420
- contentType,
421
- metadata
483
+ ...options
422
484
  });
423
485
  }
424
486
  async downloadFile(key) {
@@ -439,29 +501,29 @@ var CloudinaryService = class {
439
501
  async fileExists(key) {
440
502
  return (await this.exists(key)).exists;
441
503
  }
442
- async copyFile(sourceKey, destinationKey, metadata) {
504
+ async copyFile(sourceKey, destinationKey, options) {
443
505
  return this.copy({
444
506
  sourceKey,
445
507
  destinationKey,
446
- metadata
508
+ ...options
447
509
  });
448
510
  }
449
- async moveFile(sourceKey, destinationKey, metadata) {
511
+ async moveFile(sourceKey, destinationKey, options) {
450
512
  return this.move({
451
513
  sourceKey,
452
514
  destinationKey,
453
- metadata
515
+ ...options
454
516
  });
455
517
  }
456
- async duplicateFile(sourceKey, destinationKey, metadata) {
518
+ async duplicateFile(sourceKey, destinationKey, options) {
457
519
  return this.duplicate({
458
520
  sourceKey,
459
521
  destinationKey,
460
- metadata
522
+ ...options
461
523
  });
462
524
  }
463
- async renameFile(sourceKey, destinationKey, metadata) {
464
- return this.moveFile(sourceKey, destinationKey, metadata);
525
+ async renameFile(sourceKey, destinationKey, options) {
526
+ return this.moveFile(sourceKey, destinationKey, options);
465
527
  }
466
528
  async getDownloadUrl(key, expiresIn) {
467
529
  return this.getPresignedUrl({