@printrail/media
v0.2.0
Published
Printrail Media SDK — upload, transform, and manage media assets
Downloads
23
Maintainers
Readme
@printrail/media
TypeScript SDK for Printrail Media — a self-hosted media storage and processing service built on AWS (S3, Lambda, CloudFront).
Upload files, auto-generate image variants, serve via CDN, and apply on-demand transforms. Drop-in replacement for Cloudinary.
Install
pnpm add @printrail/media
# or
npm install @printrail/mediaQuick Start
import { PrintrailMedia } from "@printrail/media";
const media = new PrintrailMedia({
apiKey: "pm_live_...",
});
// Upload a file (browser, all-in-one)
const asset = await media.upload(file, {
folder: "/photos",
onProgress: (pct) => console.log(`${pct}%`),
});
console.log(asset.cdnUrl); // Full-size CDN URL
console.log(asset.variants.thumb); // 200x200 thumbnailServer / Client Split
The SDK enforces a security boundary — PrintrailMedia only works server-side (throws in the browser). This prevents API key leakage.
For frameworks like Next.js where uploads happen client-side but auth stays server-side:
// --- Server action ---
import { PrintrailMedia } from "@printrail/media";
const media = new PrintrailMedia({ apiKey: process.env.PM_API_KEY! });
export async function getUploadUrl(fileName: string, contentType: string) {
return media.presign({ fileName, contentType });
}
export async function confirmUpload(assetId: string) {
return media.confirm(assetId);
}// --- Client component ---
import { uploadToPresignedUrl } from "@printrail/media/client";
const { uploadUrl, assetId } = await getUploadUrl(file.name, file.type);
await uploadToPresignedUrl(file, uploadUrl, file.type, {
onProgress: (pct) => setProgress(pct),
});
const asset = await confirmUpload(assetId);Upload Methods
upload(file, options?) — Browser, all-in-one
Handles presign, S3 PUT, and confirm in one call. Uses XHR for progress tracking.
const asset = await media.upload(file, {
folder: "/wedding/photos",
tags: ["ceremony"],
onProgress: (pct) => console.log(`${pct}%`),
});uploadBlob(blob, fileName, contentType, options?) — Universal
Works in Node.js and browsers. Uses fetch (no progress tracking).
const blob = new Blob([buffer], { type: "image/png" });
const asset = await media.uploadBlob(blob, "screenshot.png", "image/png");uploadDataUrl(dataUrl, fileName, options?) — Base64
Upload from a data URL (e.g., canvas, QR codes).
const asset = await media.uploadDataUrl(
canvas.toDataURL("image/png"),
"qr-code.png",
{ folder: "/qr-codes" },
);presign(input) / confirm(assetId) — Split flow
For custom upload flows where you control each step.
const { uploadUrl, assetId } = await media.presign({
fileName: "photo.jpg",
contentType: "image/jpeg",
fileSize: file.size,
folder: "/uploads",
});
// Upload to S3 yourself (e.g., via XHR, fetch, or any HTTP client)
await fetch(uploadUrl, { method: "PUT", body: file, headers: { "Content-Type": "image/jpeg" } });
const asset = await media.confirm(assetId);Image Processing
Images are automatically processed after upload into three WebP variants:
| Variant | Size | Quality |
|----------|-----------|---------|
| thumb | 200x200 | 70 |
| medium | 800x800 | 80 |
| full | 1920x1920 | 85 |
Processing is async. Use waitForReady() if you need variants immediately:
const asset = await media.confirm(assetId);
const ready = await media.waitForReady(asset.id, {
timeout: 15_000,
onPoll: (a) => console.log(a.status), // "processing" → "ready"
});
console.log(ready.variants.thumb); // https://cdn.../slug/uuid/thumb.webp
console.log(ready.variants.medium); // https://cdn.../slug/uuid/medium.webp
console.log(ready.variants.full); // https://cdn.../slug/uuid/full.webpCDN URLs
Processed variants
media.variantUrl("my-project/abc-123", "thumb");
// → https://d1eksnlsktdkcl.cloudfront.net/my-project/abc-123/thumb.webp
media.variantUrl("my-project/abc-123", "medium");
media.variantUrl("my-project/abc-123", "full");On-demand transforms
Generate any size/crop on the fly — results are cached at the CDN edge.
media.transformUrl("my-project/abc-123", {
width: 400,
height: 300,
crop: "fill", // fill, fit, thumb, pad
format: "webp", // webp, jpeg, png, auto
quality: 80,
gravity: "center", // center, north, south, east, west
blur: 10,
});
// → https://d1eksnlsktdkcl.cloudfront.net/t/blur_10,c_fill,f_webp,h_300,q_80,w_400/my-project/abc-123Shorthands
media.thumbnail("my-project/abc-123"); // 200x200 thumb
media.thumbnail("my-project/abc-123", 100); // 100x100 thumb
media.ogImage("my-project/abc-123"); // 1200x630 social imageAssets CRUD
// List with filtering + cursor pagination
const { assets, pagination } = await media.list({
folder: "/wedding",
mediaType: "image",
status: "ready",
tags: ["ceremony"],
search: "cake",
limit: 20,
});
// Get next page
if (pagination.hasMore) {
const page2 = await media.list({ cursor: pagination.nextCursor });
}
// Get single asset
const asset = await media.get("asset-id");
// Update metadata
await media.update("asset-id", {
tags: ["featured"],
altText: "Wedding cake with flowers",
});
// Delete
await media.delete("asset-id");Client Exports
@printrail/media/client exports only browser-safe code (no API key):
import { uploadToPresignedUrl, buildTransformUrl } from "@printrail/media/client";
// Upload with progress (XHR)
await uploadToPresignedUrl(file, presignedUrl, file.type, {
onProgress: (pct) => setProgress(pct),
signal: abortController.signal, // optional cancel
});
// Build transform URL (pure function, no network call)
const url = buildTransformUrl(
"https://d1eksnlsktdkcl.cloudfront.net",
"my-project/abc-123",
{ width: 400, crop: "fill" },
);Supported File Types
| Type | Extensions | Max Size | |-----------|-----------------------------------------|----------| | Images | jpg, jpeg, png, gif, webp, svg, avif, heic | 10 MB | | Videos | mp4, mov, webm, avi, mkv | 100 MB | | Documents | pdf, doc, docx, xls, xlsx, txt, csv | 25 MB | | Audio | mp3, wav, ogg, aac | 50 MB |
Configuration
const media = new PrintrailMedia({
apiKey: "pm_live_...", // Required — your project API key
apiUrl: "https://...", // Optional — defaults to production
cdnUrl: "https://...", // Optional — defaults to production CDN
});License
MIT
