first commit
This commit is contained in:
commit
1dbdb4f4d5
49 files changed
+12172
No files matched your search
@@ -0,0 +1,419 @@
|
||||
page wrnexusuploader {
|
||||
seo {
|
||||
title = "@wrnexus/uploader"
|
||||
description = "Validated local/S3 uploads and secure file serving."
|
||||
}
|
||||
|
||||
view {
|
||||
<div class="docs-shell">
|
||||
<header class="topbar">
|
||||
<a class="brand" href="/"><span>W</span> WRNexusJS</a>
|
||||
<nav><a href="/getting-started">Get started</a><a href="/packages">Packages</a><a href="/language">Language</a><a href="/architecture">Architecture</a></nav>
|
||||
<button data-wire-theme-toggle class="theme-button" aria-label="Toggle theme">Theme</button>
|
||||
</header>
|
||||
<main class="page package-page">
|
||||
<aside class="sidebar"><a href="/packages">← All packages</a><span class="category">Data</span><h1>@wrnexus/uploader</h1><p>Validated local/S3 uploads and secure file serving.</p><code>bun add @wrnexus/uploader@0.2.12</code><nav><a href="#guide">Guide</a><a href="#api">Complete API</a></nav></aside>
|
||||
<article class="documentation"><section class="doc-intro"><span class="eyebrow">Data</span><h1>@wrnexus/uploader</h1><p>Validated local/S3 uploads and secure file serving.</p><pre><code>bun add @wrnexus/uploader@0.2.12</code></pre></section><section id="guide" class="prose"><p>Config-driven file uploads + serving for <a href="https://www.npmjs.com/org/wrnexus" rel="noreferrer">WRNexusJS</a>. Declare named <strong>storage stores</strong> (local disk or any S3-compatible backend) in <code>wrnexus.config.ts</code>, 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).</p>
|
||||
<h3 id="configure">Configure</h3>
|
||||
<pre data-language="ts"><code>// 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;</code></pre>
|
||||
<h3 id="upload-server">Upload (server)</h3>
|
||||
<pre data-language="ts"><code>// 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 }] }</code></pre>
|
||||
<pre data-language="ts"><code>// 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);</code></pre>
|
||||
<p>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 <code>url</code>.</p>
|
||||
<h3 id="widget-client">Widget (client)</h3>
|
||||
<p>Drop the element anywhere; the runtime (drag-and-drop, per-file progress, success/failed states) is auto-injected on pages that contain <code>data-uploader</code>:</p>
|
||||
<pre data-language="html"><code><div
|
||||
data-uploader="public"
|
||||
data-endpoint="/api/upload"
|
||||
data-accept="image/*"
|
||||
data-max="10000000"
|
||||
data-multiple
|
||||
></div></code></pre>
|
||||
<p>Or via the first-party UI component:</p>
|
||||
<pre data-language="html"><code><div
|
||||
data-component="file-upload"
|
||||
store="public"
|
||||
endpoint="/api/upload"
|
||||
accept="image/*"
|
||||
multiple="true"
|
||||
></div></code></pre>
|
||||
<p>It dispatches bubbling events you can listen for:</p>
|
||||
<ul>
|
||||
<li><code>wrnexus:upload</code> — <code>detail: { file, result: { key, url, name, size, type } }</code></li>
|
||||
<li><code>wrnexus:upload-error</code> — <code>detail: { file, error }</code></li>
|
||||
</ul>
|
||||
<h3 id="serve-files">Serve files</h3>
|
||||
<ul>
|
||||
<li><strong>Public + local</strong> → served automatically at <code>/__wrnexus/uploads/<store>/<key></code> (immutable cache).</li>
|
||||
<li><strong>Public + S3</strong> → <code>url</code> is the bucket/CDN URL directly.</li>
|
||||
<li><strong>Private</strong> (any driver) → mount a route and gate it with your auth middleware:</li>
|
||||
</ul>
|
||||
<pre data-language="ts"><code>// app/api/files/[key].ts
|
||||
import { serveFromStore } from "@wrnexus/uploader";
|
||||
export const GET = serveFromStore("docs"); // your middleware decides who gets in</code></pre>
|
||||
<h3 id="api">API</h3>
|
||||
<div class="table-wrap"><table>
|
||||
<thead><tr><th>Export</th><th>What</th></tr></thead>
|
||||
<tbody><tr><td><code>handleUpload(opts)</code></td><td>POST route handler → JSON <code>{ ok, files }</code></td></tr><tr><td><code>upload(store, req, opts)</code></td><td>Parse + validate + store; returns <code>{ files }</code></td></tr><tr><td><code>serveFromStore(store)</code></td><td>Route handler that streams an object back (gate it for private)</td></tr><tr><td><code>getStore(name?)</code> / <code>hasStorage(name?)</code></td><td>Reach a store's <code>driver</code> (<code>put</code>/<code>get</code>/<code>delete</code>/<code>publicUrl</code>)</td></tr><tr><td><code>configureStorage(config, root)</code></td><td>Build the registry (the framework calls this at startup)</td></tr><tr><td><code>s3Driver</code> / <code>localDriver</code> / <code>signS3</code></td><td>Lower-level building blocks</td></tr></tbody></table></div>
|
||||
<h3 id="notes">Notes</h3>
|
||||
<ul>
|
||||
<li>Uploads count against the server's <code>maxBodyBytes</code>; per-file limits use each store's <code>maxBytes</code>.</li>
|
||||
<li>SigV4 signing is implemented from scratch (no <code>@aws-sdk</code>); tested against local S3 semantics.</li>
|
||||
<p>Live AWS/R2 connectivity depends on your credentials + bucket policy.</p>
|
||||
<li>v1 buffers each file in memory up to its size cap (fine for images/docs up to tens of MB).</li>
|
||||
</ul></section><section id="api" class="prose api"><h2>Complete TypeScript API</h2><p>This declaration is generated from the exact published package and lists its exported functions, classes, interfaces, and types.</p><pre data-language="typescript"><code>import { Context } from '@wrnexus/core';
|
||||
|
||||
/**
|
||||
* Storage driver contract + config types.
|
||||
*
|
||||
* A `StorageDriver` is the low-level object store (local disk, S3, …). It knows
|
||||
* how to put/get/delete raw bytes under a key — nothing about HTTP, multipart
|
||||
* parsing, validation, or URLs. The registry (`client.ts`) builds one driver per
|
||||
* configured store and the upload layer (`upload.ts`) drives them. This mirrors
|
||||
* `@wrnexus/db`'s driver/adapter split.
|
||||
*/
|
||||
/** Whether a store's objects are world-readable or served behind app auth. */
|
||||
type StoreAccess = "public" | "private";
|
||||
/** An object read back from a store. */
|
||||
interface StoredObject {
|
||||
/** Object bytes as a web stream (preferred) or a buffer. */
|
||||
body: ReadableStream<Uint8Array> | Uint8Array;
|
||||
/** MIME type to serve with. */
|
||||
contentType: string;
|
||||
/** Size in bytes, when known. */
|
||||
size?: number;
|
||||
}
|
||||
/** Metadata passed alongside the bytes on `put`. */
|
||||
interface PutMeta {
|
||||
contentType: string;
|
||||
/** Original client filename (informational only — NEVER used as a path). */
|
||||
filename?: string;
|
||||
}
|
||||
/** The low-level object store. Implementations: `adapters/local.ts`, `adapters/s3.ts`. */
|
||||
interface StorageDriver {
|
||||
/** Persist `data` under `key` (overwrites). */
|
||||
put(key: string, data: Uint8Array, meta: PutMeta): Promise<void>;
|
||||
/** Fetch an object, or `null` if it doesn't exist. */
|
||||
get(key: string): Promise<StoredObject | null>;
|
||||
/** Remove an object. No error if it's already gone. */
|
||||
delete(key: string): Promise<void>;
|
||||
/**
|
||||
* A directly-servable absolute URL for a PUBLIC object (e.g. an S3/CDN URL), or
|
||||
* `null` when the framework should serve it (local public stores). Private
|
||||
* stores always return `null`.
|
||||
*/
|
||||
publicUrl(key: string): string | null;
|
||||
}
|
||||
/** Local-disk store. `dir` is resolved against the app root when relative. */
|
||||
interface LocalStoreConfig {
|
||||
driver: "local";
|
||||
access: StoreAccess;
|
||||
/** Directory the files live under (e.g. "uploads/public"). */
|
||||
dir: string;
|
||||
/** Reject files larger than this many bytes (per file). */
|
||||
maxBytes?: number;
|
||||
/** Allowed types: MIME (`"image/*"`, `"application/pdf"`) and/or extensions (`".pdf"`). */
|
||||
accept?: string[];
|
||||
}
|
||||
/** S3 / S3-compatible store (AWS, Cloudflare R2, Backblaze B2, MinIO, DO Spaces). */
|
||||
interface S3StoreConfig {
|
||||
driver: "s3";
|
||||
access: StoreAccess;
|
||||
bucket: string;
|
||||
region: string;
|
||||
accessKeyId: string;
|
||||
secretAccessKey: string;
|
||||
/**
|
||||
* Custom endpoint for non-AWS services, e.g.
|
||||
* `https://<acct>.r2.cloudflarestorage.com`. Omit for AWS S3.
|
||||
*/
|
||||
endpoint?: string;
|
||||
/** Force path-style URLs (`/bucket/key`). Defaults on for custom endpoints. */
|
||||
forcePathStyle?: boolean;
|
||||
/** Public base URL for `publicUrl()` (a CDN or public bucket domain). */
|
||||
publicBaseUrl?: string;
|
||||
maxBytes?: number;
|
||||
accept?: string[];
|
||||
}
|
||||
type StoreConfig = LocalStoreConfig | S3StoreConfig;
|
||||
/** The `storage` block in `wrnexus.config.ts`. */
|
||||
interface StorageConfig {
|
||||
/** Name of the store used when a call omits one. Defaults to the first store. */
|
||||
default?: string;
|
||||
/** Named stores, reached with `getStore("<name>")` / `upload("<name>", …)`. */
|
||||
stores: Record<string, StoreConfig>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Process-wide store registry, configured once at server startup from the
|
||||
* `storage` block in `wrnexus.config.ts` (mirrors `@wrnexus/db`'s registry).
|
||||
* Handlers then call `getStore("<name>")` — or omit the name for the default.
|
||||
*/
|
||||
|
||||
interface Store {
|
||||
name: string;
|
||||
access: StoreAccess;
|
||||
driver: StorageDriver;
|
||||
config: StoreConfig;
|
||||
}
|
||||
/** Build a driver per configured store. Safe to call again (fully replaces). */
|
||||
declare function configureStorage(config: StorageConfig | undefined, appRoot: string): void;
|
||||
/** Whether the default (or a named) store is configured. */
|
||||
declare function hasStorage(name?: string): boolean;
|
||||
/** The default store, or a named one. Throws if it isn't configured. */
|
||||
declare function getStore(name?: string): Store;
|
||||
/** Names of all configured stores. */
|
||||
declare function storeNames(): string[];
|
||||
|
||||
/**
|
||||
* The HTTP-facing upload + serve layer: parse multipart requests, validate,
|
||||
* store, and serve files back. Built on the store registry (`client.ts`).
|
||||
*/
|
||||
|
||||
/** Reserved prefix the framework serves PUBLIC local objects from. */
|
||||
declare const UPLOADS_PREFIX = "/__wrnexus/uploads/";
|
||||
interface UploadedFile {
|
||||
/** Storage key — pass to `getStore().driver.get/delete` or a serve route. */
|
||||
key: string;
|
||||
/** A servable URL for public objects, or `null` for private stores. */
|
||||
url: string | null;
|
||||
/** Original (sanitized) client filename, for display. */
|
||||
name: string;
|
||||
type: string;
|
||||
size: number;
|
||||
}
|
||||
interface UploadOptions {
|
||||
/** Only read files from this form field (default: every file field). */
|
||||
field?: string;
|
||||
/** Override the store's `maxBytes`. */
|
||||
maxBytes?: number;
|
||||
/** Override the store's `accept` list. */
|
||||
accept?: string[];
|
||||
/** Key prefix, e.g. `"avatars"` → keys become `avatars/<yyyy>/<mm>/<rand>.<ext>`. */
|
||||
prefix?: string;
|
||||
}
|
||||
/** A 4xx-carrying error so `handleUpload` can map it to a status. */
|
||||
declare class UploadError extends Error {
|
||||
readonly status: number;
|
||||
constructor(message: string, status?: number);
|
||||
}
|
||||
/** The servable URL for a stored object (public → URL, private → null). */
|
||||
declare function storedUrl(store: Store, key: string): string | null;
|
||||
/**
|
||||
* Read multipart file(s) from a request and store them. Throws `UploadError`
|
||||
* (4xx) on validation failures. Call it directly, or use `handleUpload`.
|
||||
*/
|
||||
declare function upload(storeName: string | undefined, req: Request, opts?: UploadOptions): Promise<{
|
||||
files: UploadedFile[];
|
||||
}>;
|
||||
/**
|
||||
* Ready-made POST handler:
|
||||
*
|
||||
* // app/api/upload.ts
|
||||
* export const POST = handleUpload({ store: "public" });
|
||||
*
|
||||
* Returns `{ ok:true, files:[…] }` on success, or `{ ok:false, error }` with a
|
||||
* 4xx/5xx status.
|
||||
*/
|
||||
declare function handleUpload(opts?: UploadOptions & {
|
||||
store?: string;
|
||||
}): (ctx: Context) => Promise<Response>;
|
||||
/**
|
||||
* Serve an object from a store as a route handler — mount it behind your auth
|
||||
* middleware to gate PRIVATE files:
|
||||
*
|
||||
* // app/api/files/[key].ts
|
||||
* export const GET = serveFromStore("docs");
|
||||
*
|
||||
* Reads the key from `ctx.params.key` (or `ctx.params.path`); it may contain `/`.
|
||||
*/
|
||||
declare function serveFromStore(storeName?: string, opts?: {
|
||||
param?: string;
|
||||
}): (ctx: Context) => Promise<Response>;
|
||||
/**
|
||||
* Framework asset hook: serve PUBLIC local objects at
|
||||
* `/__wrnexus/uploads/<store>/<key>`. Returns `null` for anything it doesn't
|
||||
* own (unknown/private/S3-backed store) so the caller falls through. Wired into
|
||||
* the dev + prod asset servers.
|
||||
*/
|
||||
declare function serveStoredFile(pathname: string): Promise<Response | null>;
|
||||
|
||||
/**
|
||||
* Client runtime for `<div data-uploader>` elements — drag-and-drop + file
|
||||
* input, per-file progress bars, and success/failed states. Injected by
|
||||
* `collectScripts` only on pages that contain `data-uploader` (same mechanism as
|
||||
* `validate.js`). Self-contained: it injects its own themed stylesheet (using
|
||||
* `--wire-*` tokens) and posts each file via XHR so upload progress is live.
|
||||
*
|
||||
* Markup it enhances (also a valid no-JS `<form>` fallback if you wrap it):
|
||||
* <div data-uploader="public" data-endpoint="/api/upload"
|
||||
* data-accept="image/*" data-max="10000000" data-multiple></div>
|
||||
*
|
||||
* Events dispatched on the element (bubble):
|
||||
* wrnexus:upload detail: { file, result: { key, url, name, size, type } }
|
||||
* wrnexus:upload-error detail: { file, error }
|
||||
*
|
||||
* NOTE: written with single/double quotes + string concatenation only — no
|
||||
* backticks and no ${...}, so it embeds safely in the exported template string.
|
||||
*/
|
||||
declare const UPLOAD_JS_HREF = "/__wrnexus/uploader.js";
|
||||
declare const UPLOAD_RUNTIME = "\n(function () {\n if (typeof document === \"undefined\") return;\n var CSRF_COOKIE = \"wire-csrf\";\n\n var CSS =\n \".wire-uploader{display:block}\" +\n \".wire-uploader-zone{display:flex;align-items:center;justify-content:center;text-align:center;\" +\n \"min-height:8rem;padding:1.25rem;border:2px dashed var(--wire-border,#cbd5e1);border-radius:12px;\" +\n \"background:var(--wire-surface,transparent);color:var(--wire-muted,#64748b);cursor:pointer;\" +\n \"transition:border-color .15s ease,background-color .15s ease;position:relative}\" +\n \".wire-uploader-zone:hover,.wire-uploader-zone:focus-visible{border-color:var(--wire-brand,#3f7dff);outline:none}\" +\n \".wire-uploader-zone.is-drag{border-color:var(--wire-brand,#3f7dff);background:color-mix(in oklab,var(--wire-brand,#3f7dff) 8%,transparent)}\" +\n \".wire-uploader-prompt{font-size:.9rem;pointer-events:none}\" +\n \".wire-uploader-input{position:absolute;inset:0;width:100%;height:100%;opacity:0;cursor:pointer}\" +\n \".wire-uploader-list{list-style:none;margin:.75rem 0 0;padding:0;display:flex;flex-direction:column;gap:.5rem}\" +\n \".wire-uploader-item{display:grid;grid-template-columns:1fr auto;gap:.15rem .75rem;align-items:center;\" +\n \"font-size:.82rem;padding:.5rem .7rem;border:1px solid var(--wire-border,#e2e8f0);border-radius:8px}\" +\n \".wire-uploader-name{font-weight:500;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;color:var(--wire-text,#0f172a)}\" +\n \".wire-uploader-meta{color:var(--wire-muted,#94a3b8);font-variant-numeric:tabular-nums}\" +\n \".wire-uploader-bar{grid-column:1/-1;height:5px;border-radius:999px;background:var(--wire-border,#e2e8f0);overflow:hidden}\" +\n \".wire-uploader-fill{height:100%;width:0;border-radius:999px;background:var(--wire-brand,#3f7dff);transition:width .15s ease}\" +\n \".wire-uploader-status{grid-column:1/-1;font-size:.75rem;color:var(--wire-muted,#94a3b8);font-variant-numeric:tabular-nums}\" +\n \".wire-uploader-item.is-done .wire-uploader-fill{background:var(--wire-success,#16a34a)}\" +\n \".wire-uploader-item.is-done .wire-uploader-status{color:var(--wire-success,#16a34a)}\" +\n \".wire-uploader-item.is-error .wire-uploader-fill{background:var(--wire-danger,#dc2626)}\" +\n \".wire-uploader-item.is-error .wire-uploader-status{color:var(--wire-danger,#dc2626)}\";\n\n function injectCss() {\n if (document.getElementById(\"wire-uploader-css\")) return;\n var s = document.createElement(\"style\");\n s.id = \"wire-uploader-css\";\n s.textContent = CSS;\n document.head.appendChild(s);\n }\n\n function cookie(name) {\n var m = document.cookie.match(new RegExp(\"(?:^|; )\" + name + \"=([^;]*)\"));\n return m ? decodeURIComponent(m[1]) : \"\";\n }\n function el(tag, cls, text) {\n var e = document.createElement(tag);\n if (cls) e.className = cls;\n if (text != null) e.textContent = text;\n return e;\n }\n function fmt(n) {\n if (n < 1024) return n + \" B\";\n if (n < 1048576) return (n / 1024).toFixed(1) + \" KB\";\n return (n / 1048576).toFixed(1) + \" MB\";\n }\n function accepts(accept, file) {\n var list = (accept || \"\").split(\",\").map(function (s) { return s.trim().toLowerCase(); }).filter(Boolean);\n if (!list.length) return true;\n var type = (file.type || \"\").toLowerCase();\n var name = (file.name || \"\").toLowerCase();\n var ext = name.indexOf(\".\") >= 0 ? name.slice(name.lastIndexOf(\".\")) : \"\";\n return list.some(function (rule) {\n if (rule.charAt(0) === \".\") return rule === ext;\n if (rule.slice(-2) === \"/*\") return type.indexOf(rule.slice(0, -1)) === 0;\n return rule === type;\n });\n }\n\n function setup(root) {\n if (root.__wrnexusUploader) return;\n root.__wrnexusUploader = true;\n\n var endpoint = root.getAttribute(\"data-endpoint\") || \"/api/upload\";\n var multipleAttr = root.getAttribute(\"data-multiple\");\n var multiple = root.hasAttribute(\"data-multiple\") && multipleAttr !== \"false\";\n var accept = root.getAttribute(\"data-accept\") || \"\";\n var maxBytes = parseInt(root.getAttribute(\"data-max\&qLine truncated
|
||||
|
||||
/**
|
||||
* Local-disk storage driver. Files live under a configured directory; keys map
|
||||
* to relative paths inside it. Path traversal is rejected — a key can never
|
||||
* escape the base dir.
|
||||
*/
|
||||
|
||||
declare function localDriver(config: LocalStoreConfig, appRoot: string): StorageDriver;
|
||||
|
||||
/**
|
||||
* S3 (and S3-compatible) storage driver — zero deps, SigV4-signed `fetch`.
|
||||
* Works with AWS S3, Cloudflare R2, Backblaze B2, MinIO, DigitalOcean Spaces.
|
||||
*
|
||||
* Path-style vs virtual-hosted: AWS defaults to virtual-hosted
|
||||
* (`bucket.s3.region.amazonaws.com`); custom endpoints (R2/MinIO) default to
|
||||
* path-style (`endpoint/bucket/key`). Override with `forcePathStyle`.
|
||||
*/
|
||||
|
||||
declare function s3Driver(config: S3StoreConfig): StorageDriver;
|
||||
|
||||
/**
|
||||
* AWS Signature Version 4 for S3 requests — zero external deps, built on
|
||||
* `node:crypto` + `fetch`. Matches the framework's zero-dep ethos (like
|
||||
* `@wrnexus/ai`) and works with any S3-compatible service (AWS, Cloudflare R2,
|
||||
* Backblaze B2, MinIO, DigitalOcean Spaces).
|
||||
*
|
||||
* Reference: docs.aws.amazon.com/general/latest/gr/sigv4_signing.html
|
||||
*/
|
||||
/** Hex-encoded SHA-256 of a payload. */
|
||||
declare function sha256Hex(data: Uint8Array | string): string;
|
||||
/**
|
||||
* Percent-encode an S3 object key for the request path. Every character except
|
||||
* the RFC 3986 unreserved set is encoded; `/` between segments is preserved.
|
||||
*/
|
||||
declare function encodeKey(key: string): string;
|
||||
interface SignInput {
|
||||
method: string;
|
||||
host: string;
|
||||
/** Canonical URI — already `%`-encoded, begins with `/`. */
|
||||
path: string;
|
||||
region: string;
|
||||
accessKeyId: string;
|
||||
secretAccessKey: string;
|
||||
/** Hex SHA-256 of the body, or `"UNSIGNED-PAYLOAD"`. */
|
||||
payloadHash: string;
|
||||
/** Extra headers to sign (e.g. `content-type`). `host`/`x-amz-*` are added here. */
|
||||
headers?: Record<string, string>;
|
||||
date: Date;
|
||||
service?: string;
|
||||
}
|
||||
/**
|
||||
* Compute the signed header set for an S3 request. Returns the headers to send
|
||||
* (lowercased names, including `authorization`, `host`, `x-amz-date`,
|
||||
* `x-amz-content-sha256`).
|
||||
*/
|
||||
declare function signS3(input: SignInput): Record<string, string>;
|
||||
|
||||
/**
|
||||
* Minimal extension ↔ MIME mapping + `accept` matching. Zero-dep: just a table
|
||||
* big enough for the common upload types (images, docs, media, archives).
|
||||
*/
|
||||
/** Lowercased extension WITHOUT the dot (e.g. "png"), or "" if none. */
|
||||
declare function extOf(name: string): string;
|
||||
/** MIME type for a filename/key by its extension, or a safe default. */
|
||||
declare function contentTypeOf(name: string, fallback?: string): string;
|
||||
/** The conventional extension for a MIME type, or "" (used to name S3 keys). */
|
||||
declare function extForType(type: string): string;
|
||||
/**
|
||||
* Does `file` (its MIME `type` + `name`) satisfy an `accept` list? Each accept
|
||||
* entry is a MIME type (`"image/png"`), a wildcard MIME (`"image/*"`), or a
|
||||
* dotted extension (`".pdf"`). An empty/omitted list accepts everything.
|
||||
*/
|
||||
declare function accepts(accept: string[] | undefined, file: {
|
||||
type: string;
|
||||
name: string;
|
||||
}): boolean;
|
||||
|
||||
export { type LocalStoreConfig, type PutMeta, type S3StoreConfig, type StorageConfig, type StorageDriver, type Store, type StoreAccess, type StoreConfig, type StoredObject, UPLOADS_PREFIX, UPLOAD_JS_HREF, UPLOAD_RUNTIME, UploadError, type UploadOptions, type UploadedFile, accepts, configureStorage, contentTypeOf, encodeKey, extForType, extOf, getStore, handleUpload, hasStorage, localDriver, s3Driver, serveFromStore, serveStoredFile, sha256Hex, signS3, storeNames, storedUrl, upload };
|
||||
</code></pre></section><section id="examples" class="prose examples"><h2>Examples</h2><p>Copy-ready examples taken from this package's published documentation.</p><div class="example-grid"><article class="example-card"><h3>Example 1</h3><pre data-language="ts"><code>// 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;</code></pre></article><article class="example-card"><h3>Example 2</h3><pre data-language="ts"><code>// 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 }] }</code></pre></article><article class="example-card"><h3>Example 3</h3><pre data-language="ts"><code>// 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);</code></pre></article><article class="example-card"><h3>Example 4</h3><pre data-language="html"><code><div
|
||||
data-uploader="public"
|
||||
data-endpoint="/api/upload"
|
||||
data-accept="image/*"
|
||||
data-max="10000000"
|
||||
data-multiple
|
||||
></div></code></pre></article></div></section></article>
|
||||
<aside class="on-this-page"><h2>On this page</h2><nav><a class="toc-level-2" href="#guide">Guide</a><a class="toc-level-3" href="#configure">Configure</a><a class="toc-level-3" href="#upload-server">Upload (server)</a><a class="toc-level-3" href="#widget-client">Widget (client)</a><a class="toc-level-3" href="#serve-files">Serve files</a><a class="toc-level-3" href="#api">API</a><a class="toc-level-3" href="#notes">Notes</a><a class="toc-level-2" href="#api">Complete API</a><a class="toc-level-2" href="#examples">Examples</a></nav></aside>
|
||||
</main>
|
||||
<footer>WRNexusJS 0.2.12 · SSR-first · Bun-native · Documentation generated from published package APIs.</footer>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user