sonuslab-storage
v0.1.2
Published
Typed client SDK for SonusLab Storage — server SDK, browser uploads, Vue composable, webhook verification, and client-side encryption.
Maintainers
Readme
sonuslab-storage
Typed client SDK for SonusLab Storage. One package, multiple entry points: server SDK, browser uploads, framework adapters for Vue / Svelte / React, webhook verification, and client-side encryption.
API keys stay on the server. Always. The browser must never see your storage API key. Use the server SDK in your backend to mint upload URLs; the browser helper PUTs to those URLs.
Full docs: storage.sonuslab.dev/docs
Install
bun add sonuslab-storagevue, svelte, and react are all optional peer dependencies — install only what you import.
Server
import { StorageClient } from "sonuslab-storage/server";
const storage = new StorageClient({
apiKey: process.env.SONUSLAB_STORAGE_API_KEY!,
});
const file = await storage.upload({
name: "report.pdf",
contentType: "application/pdf",
data: pdfBuffer,
});baseUrl defaults to https://storage-api.sonuslab.dev — override only when self-hosting.
Browser
The browser hits your server, which proxies to the storage API. Two endpoints — presign + complete.
import { uploadFile } from "sonuslab-storage/client";
await uploadFile({
file,
presignEndpoint: "/api/upload/presign",
completeEndpoint: "/api/upload/complete",
onProgress: (p) => console.log(`${p.percent}%`),
});Framework adapters
Vue
import { useUpload } from "sonuslab-storage/vue";
const { upload, status, progress, error, reset } = useUpload({
presignEndpoint: "/api/upload/presign",
completeEndpoint: "/api/upload/complete",
onComplete: (r) => console.log(r.fileId),
});A headless <StorageUpload> component is also exported. See docs/vue.
Svelte 5
import { Upload } from "sonuslab-storage/svelte";
const uploader = new Upload({
presignEndpoint: "/api/upload/presign",
completeEndpoint: "/api/upload/complete",
});
// uploader.status, uploader.progress, uploader.error — all runesSee docs/svelte.
React / Next.js App Router
// app/api/upload/route.ts
import { StorageClient } from "sonuslab-storage/server";
import { createUploadRoute } from "sonuslab-storage/react";
const storage = new StorageClient({ apiKey: process.env.SONUSLAB_STORAGE_API_KEY! });
export const { POST } = createUploadRoute({ storage });"use client";
import { useUpload } from "sonuslab-storage/react";
const { upload, status, progress } = useUpload({
presignEndpoint: "/api/upload",
completeEndpoint: "/api/upload",
});See docs/nextjs.
Lifecycle callbacks
Every framework adapter accepts onStart(file), onComplete(result), and onError(err) — fire-and-forget side-effects without watching status.
Client-side encryption
Opt-in AES-256-GCM via WebCrypto. Bytes are encrypted before they leave your process — the storage backend only ever sees ciphertext.
import { generateEncryptionKey } from "sonuslab-storage/crypto";
const key = generateEncryptionKey();
await storage.upload({ name, contentType, data, encrypt: { key } });No key escrow. Lose the key, lose the data — there is no recovery path.
Passphrase mode (PBKDF2-SHA256, 100k iterations) also works:
encrypt: { key: "correct horse battery staple" }See docs/encryption.
Webhooks
Verify against the raw request body — JSON parsing changes whitespace and breaks the signature.
import { verifyWebhook } from "sonuslab-storage/webhook";
const payload = verifyWebhook({ body, signature, secret });See docs/webhooks.
Multipart
For files larger than ~5 MB, use multipart on the server SDK: initMultipart, presignMultipartPart, completeMultipart. See docs/multipart.
Errors
| Class | Where | Notable fields |
| -------------------------- | --------------- | ------------------------- |
| StorageApiError | server SDK | .status, .body |
| UploadError | browser client | .cause |
| WebhookVerificationError | webhook helper | — |
See docs/errors.
License
MIT
