Files
WRNexusJS/packages/uploader
ClintchizandClaude Opus 5 7a2b58652a
Quality / quality (ubuntu-latest) (push) Failing after 11m2s
Quality / quality (windows-latest) (push) Canceled after 0s
chore(release): prepare 0.8.6
Bumps all 47 packages, the root manifest and the VS Code extension to 0.8.6,
and rebuilds the editor compiler, language server and extension bundles that
embed the version.

The release carries the output delivery fix: camelCase outputs now reach
parent bindings, and 18 components emit through output.* instead of
hand-built CustomEvents. See the 0.8.6 migration entry for what changes for
consumers.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-09 01:54:44 +05:30
..
2026-08-03 19:47:30 +05:30
2026-08-02 23:18:51 +05:30
2026-08-09 01:54:44 +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.