import { StorageClient } from '@dcd-sdk/storage-client';
const client = new StorageClient({
endpoint: 'https://cdn.mycompany.com',
apiKey: 'dcd_key_live_xxxx',
});
// 1. Upload
const asset = await client.upload('./photo.jpg', { folder: 'gallery' });
// 2. Generate optimized WebP URL
const webpUrl = client
.url(asset.publicId)
.resize({ width: 800, height: 600, crop: 'fill' })
.format('webp')
.quality(80)
.build();
console.log('Delivery URL:', webpUrl);The @dcd-sdk/storage-client package is written in 100% strict TypeScript, has zero external HTTP client dependencies (uses native fetch), and provides full typings for all methods, transformation parameters, and server responses.
1. Installation#
Install the package using your favorite package manager:
bashnpm install @dcd-sdk/storage-client # or pnpm add @dcd-sdk/storage-client # or yarn add @dcd-sdk/storage-client
2. Client Initialization#
Initialize the StorageClient (or alias BucketClient) with your storage server endpoint and master API key. If omitted, the client automatically falls back to DCD_STORAGE_ENDPOINT and DCD_STORAGE_API_KEY environment variables.
typescriptimport { StorageClient, BucketClient } from '@dcd-sdk/storage-client'; export const storage = new StorageClient({ endpoint: process.env.DCD_STORAGE_ENDPOINT || 'https://cdn.mycompany.com', apiKey: process.env.DCD_STORAGE_API_KEY || 'dcd_key_live_99a8b1c4e23', timeoutMs: 30000, // Optional: default 30s });
3. Dynamic URL Transformation Builder#
Generate optimized CDN URLs dynamically using the fluent builder. The server executes Sharp/libvips transforms on the first request and serves subsequent requests from the high-speed NVMe edge cache.
typescript// Fluent builder with method chaining const avatarUrl = storage .url('avatars/user-849.png') .width(400) .height(400) .crop('fill') .format('webp') .quality(85) .build(); // Output: https://cdn.mycompany.com/image/upload/w_400,h_400,c_fill,f_webp,q_85/avatars/user-849.webp // Direct options object (fast inline style) const bannerUrl = storage.url('banners/hero.jpg', { width: 1200, height: 630, crop: 'fill', format: 'auto', quality: 80, }); // Fast raw CDN link without transformations const rawUrl = storage.getPublicUrl('documents/manual.pdf');
4. Uploading Files & In-Memory Buffers#
The upload() method automatically determines whether the input is a file path string, a Node.js Buffer, an ArrayBuffer, or a browser Blob/File, and pipes it into an RFC-compliant multipart/form-data stream.
typescript// Upload from disk path (Node.js) const diskAsset = await storage.upload('./assets/product-shot.jpg', { folder: 'ecommerce/products', access: 'public', // 'public' | 'private' }); console.log('Uploaded asset public ID:', diskAsset.publicId); console.log('Public URL:', diskAsset.url); // Upload from Buffer (e.g. from Multer, sharp, or fetch) const buffer = Buffer.from('...'); const bufferAsset = await storage.upload(buffer, { folder: 'generated-reports', filename: 'report-q4.pdf', access: 'private', // 🔒 Access restricted to signed URLs });
5. Generating Signed Download URLs#
For confidential files (such as customer invoices, KYC verification passports, or private contracts), generate time-limited HMAC-signed URLs. Requests without a valid timestamp and signature return HTTP 403 Forbidden.
typescriptconst signedInvoiceUrl = await storage.getSignedUrl('invoices/inv-2026-001.pdf', { expiresInSeconds: 600, // Valid for 10 minutes resourceType: 'raw', // 'image' | 'raw' }); console.log('Temporary link:', signedInvoiceUrl); // https://cdn.mycompany.com/cdn/invoices/inv-2026-001.pdf?exp=1792600000&sig=a8f4c2...
6. Client-Side Upload Tokens & Direct Frontend Ingestion#
To protect your master secret key while allowing web and mobile users to upload files directly to your storage cluster, generate a short-lived signed upload token on your backend:
typescriptconst uploadToken = await storage.createSignedUploadToken({ folder: 'user-avatars', maxBytes: 10 * 1024 * 1024, // 10MB maximum file size expiresInSeconds: 300, // Token expires in 5 minutes }); // Pass uploadToken.token and uploadToken.uploadUrl to your React/Vue frontend
On the frontend, use createStorageClient and uploadWithToken() for zero-secret direct streaming:
typescriptimport { createStorageClient } from '@dcd-sdk/storage-client'; const client = createStorageClient({ endpoint: 'https://cdn.mycompany.com' }); const asset = await client.uploadWithToken(file, uploadToken.token, { folder: 'user-avatars', onProgress: (percent) => console.log(`Progress: ${percent}%`), });
7. Inspecting, Deleting & Purging Cache#
Manage your storage assets programmatically:
typescript// Inspect asset metadata const asset = await storage.getFile('ecommerce/products/hero'); console.log('Dimensions:', asset.width, 'x', asset.height, 'Bytes:', asset.bytes); // List files in a folder with pagination const fileList = await storage.listFiles({ folder: 'ecommerce/products', page: 1, limit: 25, }); console.log('Total files found:', fileList.pagination.total); // Delete an asset permanently (or alias: storage.destroy) await storage.delete('ecommerce/products/old-item.png'); // Invalidate & purge derived transformation cache const purgeRes = await storage.purgeCache(); console.log('Cache variants purged:', purgeRes.purgedCount);
8. Next.js & Express Production Recipes#
Next.js App Router (Route Handler app/api/upload/route.ts):
typescriptimport { NextResponse } from 'next/server'; import { StorageClient } from '@dcd-sdk/storage-client'; const storage = new StorageClient({ endpoint: process.env.DCD_STORAGE_ENDPOINT!, apiKey: process.env.DCD_STORAGE_API_KEY!, }); export async function POST(request: Request) { const formData = await request.formData(); const file = formData.get('file') as File; if (!file) { return NextResponse.json({ error: 'No file provided' }, { status: 400 }); } const bytes = await file.arrayBuffer(); const asset = await storage.upload(bytes, { folder: 'nextjs-uploads', filename: file.name, access: 'public', }); return NextResponse.json({ success: true, asset }); }
9. Error Classes & Troubleshooting#
All SDK errors inherit from StorageError, allowing specific catch blocks:
typescriptimport { StorageError, AuthenticationError, NotFoundError, PayloadTooLargeError, NetworkTimeoutError, } from '@dcd-sdk/storage-client'; try { await storage.upload(file, { folder: 'photos' }); } catch (error) { if (error instanceof AuthenticationError) { console.error('Invalid API Key or unauthorized upload token.'); } else if (error instanceof PayloadTooLargeError) { console.error('File exceeds bucket or token maxBytes limit.'); } else if (error instanceof NetworkTimeoutError) { console.error('Storage server timed out. Check network or VPS load.'); } else if (error instanceof StorageError) { console.error('Storage error code:', error.code, error.message); } }