@temps-sdk/blob
v0.0.3
Published
Blob storage SDK for the Temps platform
Readme
# npm
npm install @temps-sdk/blob
# bun
bun add @temps-sdk/blob
# pnpm
pnpm add @temps-sdk/blob
# yarn
yarn add @temps-sdk/blobQuick Start
import { blob } from '@temps-sdk/blob';
// Upload a file
const { url } = await blob.put('avatars/user-123.png', fileBuffer);
// Download it back
const response = await blob.download(url);
const data = await response.arrayBuffer();
// List files
const { blobs } = await blob.list({ prefix: 'avatars/' });
// Delete it
await blob.del(url);That's it. The blob singleton reads TEMPS_API_URL and TEMPS_TOKEN from your environment automatically.
Configuration
Environment Variables
TEMPS_API_URL=https://your-instance.temps.dev # Your Temps API URL
TEMPS_TOKEN=your-token # API key or deployment token
TEMPS_PROJECT_ID=42 # Required for API keys, optional for deployment tokensExplicit Configuration
import { createClient } from '@temps-sdk/blob';
const storage = createClient({
apiUrl: 'https://your-instance.temps.dev',
token: 'your-token',
projectId: 42,
});Deployment tokens embed the project ID, so
projectIdis optional. API keys requireprojectIdto be set explicitly.
API Reference
put(pathname, body, options?): Promise<BlobInfo>
Upload a file. Content type is auto-detected from the file extension or can be set explicitly.
// Upload a string
await blob.put('notes/readme.txt', 'Hello, world!');
// Upload a Buffer (Node.js)
import { readFileSync } from 'fs';
const file = readFileSync('./photo.jpg');
await blob.put('photos/vacation.jpg', file);
// Upload a Uint8Array
const bytes = new Uint8Array([0x89, 0x50, 0x4e, 0x47]);
await blob.put('data/header.bin', bytes);
// Upload a Blob (browser)
const formBlob = new Blob(['content'], { type: 'text/plain' });
await blob.put('uploads/file.txt', formBlob);
// Upload a ReadableStream
const stream = file.stream();
await blob.put('videos/clip.mp4', stream);Accepted body types: string | ArrayBuffer | Uint8Array | Blob | ReadableStream<Uint8Array> | Buffer
Options:
| Option | Type | Default | Description |
|--------|------|---------|-------------|
| contentType | string | Auto-detected | MIME type of the file |
| addRandomSuffix | boolean | true | Append a random suffix to prevent name collisions |
| cacheControl | string | - | Cache-Control header value |
| contentEncoding | string | - | Content encoding (e.g., 'gzip') |
| contentDisposition | string | - | Content disposition (e.g., 'attachment; filename="file.txt"') |
Returns BlobInfo:
{
url: string; // Full URL to access the file
pathname: string; // Path/name of the file
contentType: string; // MIME type
size: number; // Size in bytes
uploadedAt: string; // ISO 8601 timestamp
}del(urls): Promise<void>
Delete one or more files by URL or pathname.
// Delete a single file
await blob.del(fileUrl);
// Delete multiple files
await blob.del([urlA, urlB, urlC]);head(url): Promise<BlobInfo>
Get metadata about a file without downloading it.
const info = await blob.head(fileUrl);
console.log(info.size); // 1048576
console.log(info.contentType); // 'image/png'
console.log(info.uploadedAt); // '2025-01-15T10:30:00.000Z'list(options?): Promise<ListResult>
List files with optional prefix filtering and cursor-based pagination.
// List all files
const { blobs, hasMore, cursor } = await blob.list();
// List files under a prefix
const images = await blob.list({ prefix: 'images/', limit: 50 });
// Paginate through results
let page = await blob.list({ limit: 100 });
while (page.hasMore) {
for (const file of page.blobs) {
console.log(file.pathname, file.size);
}
page = await blob.list({ limit: 100, cursor: page.cursor });
}Options:
| Option | Type | Default | Description |
|--------|------|---------|-------------|
| limit | number | 1000 | Maximum number of results |
| prefix | string | - | Filter by path prefix (e.g., 'images/') |
| cursor | string | - | Pagination cursor from a previous response |
download(url): Promise<Response>
Download a file. Returns a standard Response object.
const response = await blob.download(fileUrl);
// Read as text
const text = await response.text();
// Read as binary
const buffer = await response.arrayBuffer();
// Stream to file (Node.js)
import { writeFile } from 'fs/promises';
const data = Buffer.from(await response.arrayBuffer());
await writeFile('./downloaded.png', data);copy(fromUrl, toPathname): Promise<BlobInfo>
Copy a file to a new location server-side (no re-upload needed).
const copy = await blob.copy(originalUrl, 'backups/photo-backup.jpg');
console.log(copy.url); // URL of the new copyUsage Patterns
User Avatar Upload (Next.js API Route)
import { blob } from '@temps-sdk/blob';
export async function POST(request: Request) {
const form = await request.formData();
const file = form.get('avatar') as File;
const { url } = await blob.put(
`avatars/${userId}/${file.name}`,
await file.arrayBuffer(),
{
contentType: file.type,
addRandomSuffix: false,
cacheControl: 'public, max-age=31536000, immutable',
}
);
return Response.json({ url });
}Backup and Restore
// Backup
const data = JSON.stringify(await db.export());
await blob.put(`backups/${new Date().toISOString()}.json`, data, {
contentType: 'application/json',
});
// List recent backups
const { blobs } = await blob.list({ prefix: 'backups/', limit: 10 });
// Restore latest
const latest = blobs[blobs.length - 1];
const response = await blob.download(latest.url);
const backup = await response.json();Static Asset Pipeline
import { readFileSync } from 'fs';
import { createHash } from 'crypto';
async function uploadAsset(filePath: string) {
const content = readFileSync(filePath);
const hash = createHash('md5').update(content).digest('hex').slice(0, 8);
const ext = filePath.split('.').pop();
const { url } = await blob.put(`assets/${hash}.${ext}`, content, {
addRandomSuffix: false,
cacheControl: 'public, max-age=31536000, immutable',
});
return url;
}Cleanup Old Files
async function cleanupOlderThan(prefix: string, maxAgeMs: number) {
const { blobs } = await blob.list({ prefix });
const cutoff = Date.now() - maxAgeMs;
const stale = blobs.filter(b => new Date(b.uploadedAt).getTime() < cutoff);
if (stale.length > 0) {
await blob.del(stale.map(b => b.url));
console.log(`Deleted ${stale.length} stale files`);
}
}
await cleanupOlderThan('temp/', 7 * 24 * 60 * 60 * 1000); // 7 daysMultiple Clients
Create separate instances for different use cases:
import { BlobClient } from '@temps-sdk/blob';
const publicAssets = new BlobClient({ apiUrl: '...', token: '...' });
const privateData = new BlobClient({ apiUrl: '...', token: '...' });Error Handling
All errors are instances of BlobError with structured details:
import { blob, BlobError } from '@temps-sdk/blob';
try {
await blob.head('nonexistent.txt');
} catch (error) {
if (error instanceof BlobError) {
console.error(error.message); // 'Blob not found: nonexistent.txt'
console.error(error.code); // 'NOT_FOUND'
console.error(error.status); // 404
console.error(error.title); // RFC 7807 problem title
console.error(error.detail); // RFC 7807 problem detail
}
}Error codes:
| Code | Description |
|------|-------------|
| MISSING_CONFIG | Required environment variable or config option not set |
| NETWORK_ERROR | Failed to reach the Temps API |
| NOT_FOUND | File does not exist |
| INVALID_INPUT | Invalid argument (e.g., empty pathname) |
Auto-Detected Content Types
The SDK detects MIME types from file extensions automatically:
| Extensions | Content Type |
|---|---|
| .jpg, .jpeg | image/jpeg |
| .png | image/png |
| .gif | image/gif |
| .webp | image/webp |
| .svg | image/svg+xml |
| .pdf | application/pdf |
| .json | application/json |
| .html | text/html |
| .css | text/css |
| .js | application/javascript |
| .mp4 | video/mp4 |
| .mp3 | audio/mpeg |
| .zip | application/zip |
| Others | application/octet-stream |
Override with the contentType option when auto-detection isn't sufficient.
TypeScript
Fully typed. All methods return typed responses:
import type { BlobInfo, ListResult, PutOptions } from '@temps-sdk/blob';
const info: BlobInfo = await blob.put('file.txt', 'content');
const result: ListResult = await blob.list();Requirements
- Node.js 18+ or Bun
- A running Temps instance
Related
@temps-sdk/kv-- Key-value store@temps-sdk/react-analytics-- React analytics, session replay, error tracking@temps-sdk/node-sdk-- Full platform API client and server-side error tracking
License
MIT
