# @wrnexus/uploader Config-driven file uploads + serving for [WrNexus](https://www.npmjs.com/org/wrnexus). Declare named **storage stores** (local disk or any S3-compatible backend) in `wrnexus.config.ts`, upload with one function call, drop a drag-and-drop widget on a page, and serve files back — public or private. Zero external dependencies (S3 is signed with a built-in AWS SigV4 implementation, like the rest of the framework). ## Configure ```ts // wrnexus.config.ts import type { AppConfig } from "@wrnexus/styles"; const config: AppConfig = { storage: { default: "public", stores: { // Local disk, world-readable — served by the framework with a 1-year cache. public: { driver: "local", dir: "uploads/public", // relative to the app root (dev) / cwd (prod) access: "public", maxBytes: 10_000_000, accept: ["image/*", ".pdf"], // MIME, "type/*" wildcards, or ".ext" }, // Private S3 (works with AWS, Cloudflare R2, Backblaze B2, MinIO, DO Spaces). docs: { driver: "s3", access: "private", bucket: "my-bucket", region: "auto", endpoint: "https://.r2.cloudflarestorage.com", accessKeyId: process.env.S3_KEY!, secretAccessKey: process.env.S3_SECRET!, }, }, }, }; export default config; ``` ## Upload (server) ```ts // app/api/upload.ts — one-liner import { handleUpload } from "@wrnexus/uploader"; export const POST = handleUpload({ store: "public" }); // → { ok: true, files: [{ key, url, name, type, size }] } ``` ```ts // or drive it yourself, anywhere you have the request import { upload, getStore } from "@wrnexus/uploader"; const { files } = await upload("docs", ctx.req, { prefix: "invoices" }); await getStore("docs").driver.delete(files[0].key); ``` Uploads are validated (size + type), stored under a random, collision-proof, path-safe key (the client filename is never used as a path), and — for public stores — returned with a servable `url`. ## Widget (client) Drop the element anywhere; the runtime (drag-and-drop, per-file progress, success/failed states) is auto-injected on pages that contain `data-uploader`: ```html
``` Or via the first-party UI component: ```html
``` It dispatches bubbling events you can listen for: - `wrnexus:upload` — `detail: { file, result: { key, url, name, size, type } }` - `wrnexus:upload-error` — `detail: { file, error }` ## Serve files - **Public + local** → served automatically at `/__wrnexus/uploads//` (immutable cache). - **Public + S3** → `url` is the bucket/CDN URL directly. - **Private** (any driver) → mount a route and gate it with your auth middleware: ```ts // app/api/files/[key].ts import { serveFromStore } from "@wrnexus/uploader"; export const GET = serveFromStore("docs"); // your middleware decides who gets in ``` ## API | Export | What | | --------------------------------------- | --------------------------------------------------------------- | | `handleUpload(opts)` | POST route handler → JSON `{ ok, files }` | | `upload(store, req, opts)` | Parse + validate + store; returns `{ files }` | | `serveFromStore(store)` | Route handler that streams an object back (gate it for private) | | `getStore(name?)` / `hasStorage(name?)` | Reach a store's `driver` (`put`/`get`/`delete`/`publicUrl`) | | `configureStorage(config, root)` | Build the registry (the framework calls this at startup) | | `s3Driver` / `localDriver` / `signS3` | Lower-level building blocks | ## Notes - Uploads count against the server's `maxBodyBytes`; per-file limits use each store's `maxBytes`. - SigV4 signing is implemented from scratch (no `@aws-sdk`); tested against local S3 semantics. Live AWS/R2 connectivity depends on your credentials + bucket policy. - v1 buffers each file in memory up to its size cap (fine for images/docs up to tens of MB).