// 1. BEFORE: Cloudinary SDK
// import { v2 as cloudinary } from 'cloudinary';
// cloudinary.config({ cloud_name: 'xxx', api_key: 'yyy', api_secret: 'zzz' });
// const res = await cloudinary.uploader.upload('./avatar.png', { folder: 'users' });
// 2. AFTER: DevCloudDesk SDK (Zero syntax change!)
import { StorageClient } from '@dcd-sdk/storage-client';
const storage = new StorageClient({
endpoint: 'https://cdn.mycompany.com',
apiKey: process.env.DCD_STORAGE_API_KEY!,
});
// Same method name, same options, same response shapes
const res = await storage.uploader.upload('./avatar.png', {
folder: 'users',
});
console.log('Uploaded asset:', res.publicId, res.url);Cloudinary is popular for rapid prototyping, but SaaS bandwidth markups (often $0.08 to $0.15 per GB) quickly become unaffordable as your userbase scales.
To eliminate migration friction, the DevCloudDesk SDK implements Cloudinary-compatible .uploader and .api namespaces. You can replace the SDK import and point to your self-hosted cluster without rewriting your application business logic.
1. Migration Overview: What Changes?#
Only your client initialization changes. Your existing helper functions, file upload workflows, and cleanup scripts remain untouched:
| Step | Cloudinary | DevCloudDesk |
|---|---|---|
| Package | npm install cloudinary | npm install @dcd-sdk/storage-client |
| Python | pip install cloudinary | pip install dcd-storage-client |
| Config | cloud_name, api_key, api_secret | endpoint, apiKey |
| Upload Call | client.uploader.upload() | client.uploader.upload() |
| Delete Call | client.uploader.destroy() | client.uploader.destroy() |
| List Call | client.api.resources() | client.api.resources() |
| Asset Details | client.api.resource() | client.api.resource() |
2. The .uploader Namespace#
The uploader namespace handles write operations: uploading raw files, streams, or buffers, and deleting assets.
Uploading an Asset#
import { StorageClient } from '@dcd-sdk/storage-client';
const client = new StorageClient();
const upload = await client.uploader.upload('./product-hero.png', {
folder: 'ecommerce/products',
publicId: 'custom-sku-1049',
overwrite: true,
});
console.log(upload.publicId); // "ecommerce/products/custom-sku-1049"
console.log(upload.url); // "https://cdn.mycompany.com/cdn/ecommerce/products/custom-sku-1049.png"
console.log(upload.bytes); // 418290
console.log(upload.format); // "png"Deleting an Asset (destroy)#
const result = await client.uploader.destroy('ecommerce/products/custom-sku-1049');
if (result.result === 'ok') {
console.log('Asset deleted from disk and edge cache purged.');
}3. The .api Administrative Namespace#
The api namespace provides asset querying, metadata inspection, and cluster utilization metrics.
Listing Resources (api.resources)#
Retrieve files with folder-based filtering and pagination:
const response = await client.api.resources({
folder: 'ecommerce/products',
maxResults: 20,
nextCursor: undefined, // pass cursor for next page
});
for (const file of response.resources) {
console.log(`${file.publicId} (${file.format}) - ${file.bytes} bytes`);
}Fetching Single Resource Details (api.resource)#
Inspect dimensions, MIME type, and access visibility of a stored asset:
const details = await client.api.resource('ecommerce/products/custom-sku-1049');
console.log('Dimensions:', details.width, 'x', details.height);
console.log('Access:', details.access); // 'public' | 'private'Cluster Resource Utilization (api.usage)#
Monitor your cluster's NVMe storage usage, total file count, and cache performance:
const usage = await client.api.usage();
console.log('Originals Stored:', usage.totalOriginals);
console.log('Disk Used (MB):', (usage.totalBytes / (1024 * 1024)).toFixed(2));
console.log('Derived Cache Size (MB):', (usage.cacheBytes / (1024 * 1024)).toFixed(2));4. Parameter Mapping Reference#
| Cloudinary Option | DevCloudDesk Option | Notes |
|---|---|---|
folder | folder | Subdirectory path in storage cluster |
public_id | publicId (JS) / public_id (PY) | Asset identifier without extension |
overwrite | overwrite | Overwrites existing file if true |
resource_type | resourceType / resource_type | 'image' (processed) or 'raw' (exact bytes) |
type: "private" | access: "private" | Restricts file to signed download URLs |
type: "upload" | access: "public" | Default mode, publicly accessible on CDN |