Files
WRNexusJS/packages/uploader
Clintchiz feff30a2b5
Quality / quality (ubuntu-latest) (push) Failing after 9m49s
Quality / quality (windows-latest) (push) Canceled after 0s
release: prepare WRNexusJS 0.8.8
2026-08-11 13:40:03 +05:30
..
2026-08-03 19:47:30 +05:30
2026-08-11 13:40:03 +05:30
2026-08-02 23:18:51 +05:30

@wrnexus/uploader

Config-driven file uploads + serving for 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).

Usage

Configure local and S3 stores

// 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://<acct>.r2.cloudflarestorage.com",
        accessKeyId: process.env.S3_KEY!,
        secretAccessKey: process.env.S3_SECRET!,
      },
    },
  },
};
export default config;

Upload from an API route or server function

// 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 }] }
// 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.

Add a client upload widget

Drop the element anywhere; the runtime (drag-and-drop, per-file progress, success/failed states) is auto-injected on pages that contain data-uploader:

<div
  data-uploader="public"
  data-endpoint="/api/upload"
  data-accept="image/*"
  data-max="10000000"
  data-multiple
></div>

Or via the first-party UI component:

<div
  data-component="file-upload"
  store="public"
  endpoint="/api/upload"
  accept="image/*"
  multiple="true"
></div>

It dispatches bubbling events you can listen for:

  • wrnexus:uploaddetail: { file, result: { key, url, name, size, type } }
  • wrnexus:upload-errordetail: { file, error }

Serve private files behind application authentication

  • Public + local → served automatically at /__wrnexus/uploads/<store>/<key> (immutable cache).
  • Public + S3url is the bucket/CDN URL directly.
  • Private (any driver) → mount a route and gate it with your auth middleware:
// app/api/files/[key].ts
import { serveFromStore } from "@wrnexus/uploader";
export const GET = serveFromStore("docs"); // your middleware decides who gets in

API

Uploads can participate in security and media pipelines without changing storage drivers. Pass a scan hook to reject malware/DLP findings before storage, and afterStore to enqueue image/video processing or indexing. If post-processing throws, WRNexus deletes the newly written object so a partially accepted upload is never left behind.

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).

Helper and component kit

Use formatFileSize, uploadAccept, uploadedFileMap, uploaderAttributes, and assertUploadedFiles to keep upload forms and server validation consistent.

Enable uploaderPlugin() for:

  • <UploadDropzone />
  • <UploadStatus />

The complete blocks compose Card, Alert, and Badge from @wrnexus/ui; the specialized upload runtime remains responsible for the native file input and secure transport behavior. Large files can use createResumableUploadManager. Sessions are bounded and expiring; chunks may arrive out of order, carry SHA-256 checksums, and are idempotent when retried. Conflicting retries reject, and the object is assembled only after every exact-sized chunk is present.

const uploads = createResumableUploadManager({
  driver: getStore("documents").driver,
  sessions: redisUploadSessionStore,
  chunkSize: 5 * 1024 * 1024,
  maxBytes: 500 * 1024 * 1024,
  accept: ["application/pdf"],
});

const session = await uploads.create({ name: "report.pdf", size, type });
await uploads.uploadChunk(session.id, index, bytes, sha256);

The included memory session store is intended for one-process apps and tests. Multi-instance production deployments should implement ResumableSessionStore with shared durable storage and atomic session updates, and periodically call prune() for abandoned uploads.