# @wrnexus/core > The framework core: the request `Context`, middleware contract, and the security, session, caching, streaming, realtime, and JSX primitives every other WrNexus package builds on. Part of the **WrNexus** framework — an SSR-first, Bun-native full-stack web framework. ## Overview `@wrnexus/core` is the shared foundation of WrNexus. It defines the `Context` object that flows through every middleware, page, and API route, plus the `Middleware`/`Next` contract they implement. On top of that it ships the building blocks a real app needs: cookie-backed sessions, password auth, CSRF protection, rate limiting, request logging, HTTP + in-memory caching, file uploads, streaming/SSE responses, WebSocket "rooms", security headers/CORS, and a server-side JSX runtime that renders to HTML strings. Everything here is **server-side** and Bun-native (it uses `Bun.password`, `Bun.write`, the web-standard `Request`/`Response`, and `crypto`). You depend on it directly and transitively through the rest of the framework. ## Installation ```bash bun add @wrnexus/core ``` > Private package — the machine must be authenticated to the `wrnexus` npm org > (a read token in `~/.npmrc`). Requires **Bun** (Node is not supported). ## API ### Context & middleware — `@wrnexus/core` The `Context` (`ctx`) is the single value passed to middleware and handlers. | Export | Kind | Description | | ------------------------------ | ---- | ------------------------------------------------------------------------------------------------------------------------ | | `Context` | type | Per-request object: `req`, `url`, `lang`, `t`, `params`, `locals`, `user?`, `ip?`, `cookies`, `session`, `localStorage`. | | `Next` | type | `() => Promise \| Response` — invokes the next middleware/handler. | | `Middleware` | type | `(ctx, next) => Promise \| Response`. Return `next()` to continue, or a `Response` to short-circuit. | | `createContext(req, url)` | fn | Build a fresh `Context` for an incoming request (wires up cookies, session, localStorage snapshot). | | `withContextHeaders(ctx, res)` | fn | Apply accumulated headers (e.g. `Set-Cookie`) from the context onto a response. | | `PageComponent` | type | `(ctx) => string \| Promise` — a page module's default export. | | `PageMeta` / `SeoConfig` | type | `` metadata: `title`, `description`, `canonical`, `robots`, `image`, `twitterCard`, `themeColor`, … | | `TFunction` | type | `(key, params?) => string` — translate a key for `ctx.lang`, interpolating `{param}` placeholders. | Key `Context` fields: - `ctx.locals` — per-request scratch space for passing values between middleware. - `ctx.user` — the authenticated user (populated by `sessionAuth`/`logIn`), or `null`. - `ctx.ip` — the direct socket peer IP (not spoofable via headers). - `ctx.cookies` / `ctx.session` / `ctx.localStorage` — see **Storage** below. ### Authentication — `@wrnexus/core` Passwords are hashed with argon2id via `Bun.password`; sessions ride the cookie-backed `SessionStore`. | Export | Signature | Notes | | -------------------------------- | -------------------------------------- | --------------------------------------------------------------------------------------------------------------------- | | `hashPassword(password)` | `(string) => Promise` | argon2id hash to store. | | `verifyPassword(password, hash)` | `(string, string) => Promise` | Constant-safe; returns `false` on bad/empty hash. | | `logIn(ctx, user)` | `(Context, U) => void` | Regenerates the session id (fixation defense), stores the user, sets `ctx.user`. | | `logOut(ctx)` | `(Context) => void` | Clears the session and `ctx.user`. | | `getUser(ctx)` | `(Context) => U \| null` | Current user from `ctx.user`, falling back to the session. | | `sessionAuth()` | `() => Middleware` | Hydrates `ctx.user` from the session each request. Register early. | | `requireAuth(options?)` | `(RequireAuthOptions?) => Middleware` | Guard: API/fetch requests get `401 JSON`, page navigations get `302` to `loginPath` (default `/login`) with `?next=`. | | `SESSION_USER_KEY` | `"user"` | Session key holding the user. | `RequireAuthOptions`: `{ loginPath?: string }`. ### CSRF — `@wrnexus/core` Double-submit cookie pattern: a readable `wire-csrf` cookie is echoed in an `x-csrf-token` header on unsafe requests. | Export | Signature | Notes | | ----------------------------- | -------------------------------- | ---------------------------------------------------------------------------------------------------------------- | | `csrfToken(ctx)` | `(Context) => string` | Ensures the CSRF cookie exists and returns its token. | | `verifyCsrf(ctx)` | `(Context) => boolean` | Safe methods (GET/HEAD/OPTIONS) pass; otherwise header/`ctx.locals._csrf` must match the cookie (constant-time). | | `csrfProtection()` | `() => Middleware` | 403s unsafe requests with a missing/mismatched token. | | `CSRF_COOKIE` / `CSRF_HEADER` | `"wire-csrf"` / `"x-csrf-token"` | Cookie & header names. | ### Rate limiting — `@wrnexus/core` Fixed-window limiter that returns `429` with `Retry-After` and emits `RateLimit-Limit`/`-Remaining`/`-Reset` headers. | Export | Signature | Notes | | --------------------- | ----------------------------------- | ---------------------------------------------------------------------- | | `rateLimit(options?)` | `(RateLimitOptions?) => Middleware` | Main middleware. | | `peerKey(ctx)` | `(Context) => string` | Non-spoofable key from `ctx.ip` (default). | | `proxyKey(ctx)` | `(Context) => string` | Trusts `x-forwarded-for`/`x-real-ip`. Use only behind a trusted proxy. | | `defaultKey` | — | **Deprecated** alias of `proxyKey`. | `RateLimitOptions`: `windowMs` (default `60_000`), `max` (default `60`), `key`, `trustProxy` (default `false` → keys on `peerKey`; `true` → `proxyKey`), `message`, `headers` (default `true`), `store`. `RateLimitStore` is pluggable — implement `hit(key, windowMs, now) => Bucket | Promise` (a `Bucket` is `{ count, resetAt }`) to back limits with Redis/SQL across instances. The default store is process-local memory. ### Request logging — `@wrnexus/core` | Export | Signature | Notes | | ------------------------- | --------------------------------------- | -------------------------------------------------------------------------------- | | `requestLogger(options?)` | `(RequestLoggerOptions?) => Middleware` | One record per request with a request id (stored on `ctx.locals[requestIdKey]`). | `RequestLoggerOptions`: `format` (`"pretty"` default \| `"json"`), `sink(line, record)` (default `console.log`), `requestIdKey` (default `"requestId"`), `now`. `RequestRecord` = `{ time, id, method, path, status, durationMs }`. ### Resilience — `@wrnexus/core` `resilientCall` standardizes cancellation-aware timeouts, controlled retries, fixed or exponential backoff, fallback responses, circuit breaking, and bounded concurrency. Reuse a declarative circuit/bulkhead options object, or an explicit `CircuitBreaker`/`Bulkhead` instance, wherever calls must share health and capacity state. ```ts import { resilientCall } from "@wrnexus/core"; const paymentCircuit = { failures: 5, resetAfter: "30s" } as const; const status = await resilientCall({ timeout: "5s", retries: 3, retryDelay: "100ms", backoff: "exponential", circuitBreaker: paymentCircuit, bulkhead: { concurrency: 20, queue: 100 }, run: (signal) => paymentProvider.checkStatus({ signal }), fallback: () => ({ state: "unavailable" }), }); ``` `CircuitBreaker.snapshot()` reports `closed`, `open`, or `half-open`, failure and success counts, and the remaining retry delay for health endpoints and development tooling. Fail-fast conditions use stable `WRN-RESILIENCE-*` codes. Core's existing `HealthRegistry`, `withIdempotency`, and pluggable stores/locks cover health reporting, idempotent requests, and distributed coordination. ### Caching — `@wrnexus/core` | Export | Kind | Notes | | -------------------------------- | ----- | ------------------------------------------------------------------------------------------------------------------------------------------ | | `TTLCache` | class | In-memory TTL cache: `get`, `set`, `getOrLoad(key, loader, ttlMs?)`, `delete`, `clear`, `size`. Constructor takes a default `ttlMs` (60s). | | `cacheControl(options)` | fn | Build a `Cache-Control` value from `CacheControlOptions`. | | `withCacheControl(res, options)` | fn | Apply `Cache-Control` to a response. | | `etag(body, weak?)` | fn | Stable quoted FNV-1a ETag (weak by default). | | `notModified(req, tag)` | fn | `true` when `If-None-Match` matches — send a `304`. | `CacheControlOptions`: `maxAge`, `sMaxAge`, `private`, `noStore`, `noCache`, `staleWhileRevalidate`, `immutable`. ### File uploads — `@wrnexus/core` Bun parses `multipart/form-data` via `Request.formData()`; these helpers validate and persist the resulting `File`s. | Export | Signature | Notes | | --------------------------- | --------------------------------------------------- | -------------------------------------------------------------------------------------- | | `collectUploads(form)` | `(FormData) => { field, file }[]` | Every non-empty `File` in a parsed form. | | `saveUpload(file, options)` | `(File, SaveUploadOptions) => Promise` | Validates size/type, sanitizes the name, writes via `Bun.write`. Throws `UploadError`. | | `sanitizeFilename(name)` | `(string) => string` | Strips separators, traversal, control/illegal chars; caps at 255. | | `UploadError` | class | Thrown on rejected uploads. | `SaveUploadOptions`: `dir` (required), `maxBytes`, `allowedTypes` (MIME types like `"image/png"` and/or extensions like `".png"`), `filename(file)`. `SavedUpload` = `{ path, filename, size, type }`. ### Streaming & SSE — `@wrnexus/core` | Export | Signature | Notes | | ------------------------------- | -------------------------------------------------------------------------------- | ------------------------------------------------------------------- | | `streamResponse(source, init?)` | `(Iterable\|AsyncIterable, StreamResponseInit?) => Response` | Streaming `Response` from a chunk source (basis for streaming SSR). | | `sse(source)` | `(Iterable\|AsyncIterable) => Response` | `text/event-stream` response. | `StreamResponseInit`: `status`, `headers`, `contentType` (default `"text/html; charset=utf-8"`). `ServerSentEvent`: `{ data, event?, id?, retry? }`. ### Realtime rooms — `@wrnexus/core` WebSocket rooms. A file in `app/realtime/` exports `default defineRoom({ ... })` and is served at `ws://host/realtime/`. | Export | Signature | Notes | | --------------------------------------- | -------------------------------------------------------- | -------------------------------------------------------------------- | | `defineRoom(handlers)` | `(RoomHandlers) => RoomDefinition` | Define a room. Export the result as `default`. | | `isRoomDefinition(value)` | `(unknown) => boolean` | Type guard for a room definition. | | `createRealtimeRegistry()` | `() => RealtimeRegistry` | Server-side connection manager mapping sockets ↔ rooms. | | `bridgeRealtime(registry, bus, topic?)` | `(RealtimeRegistry, RealtimeBus, string?) => () => void` | Bridge broadcasts/`toUser` sends across processes via a pub/sub bus. | `RoomHandlers`: `authorize(info) => boolean` (gate before accept — return `false` to reject with 403), `onConnect(client)`, `onMessage(client, message)` (JSON auto-parsed), `onLeave(client)`. A handler receives a `RoomClient` with `id`, `user`, `query`, `data`, `room`, and `send` / `broadcast` / `to(id)` / `toUser(user)` / `close`. The `Room` API adds `state`, `clients()`, `count()`, and `broadcast`. `RealtimeBus` is structurally satisfied by `@wrnexus/pubsub`. Legacy `RealtimeHandler`/`RealtimeSocket` raw handlers are still exported. Connection-targeted sends (`send`, `to(id)`) stay local; room broadcasts and `toUser` cross the bridge. ### Error pages — `@wrnexus/core` | Export | Signature | Notes | | ------------------------------ | -------------------------------- | ----------------------------------------------------- | | `renderError(err, mode)` | `(unknown, Mode) => Response` | Dev page (with stack) or generic prod page by `mode`. | | `renderDevError(err, status?)` | `(unknown, number?) => Response` | Readable HTML error page including the stack trace. | | `renderProdError(status?)` | `(number?) => Response` | Generic page that never leaks file paths. | | `renderNotFound()` | `() => Response` | Simple 404 page. | `Mode` = `"development" | "production"`. ### Security headers & CORS — `@wrnexus/core` | Export | Signature | Notes | | -------------------------------------------------------- | -------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------- | | `withSecurityHeaders(req, res, mode, security?, nonce?)` | → `Response` | Applies CORS + CSP, HSTS, `X-Frame-Options`, `X-Content-Type-Options`, `Referrer-Policy`, `Permissions-Policy`, COOP, Trusted Types, and `extraHeaders`. | | `createCorsPreflightResponse(req, security?)` | → `Response \| null` | Builds a `204`/`403` preflight response for CORS `OPTIONS` requests. | | `isWebSocketOriginAllowed(req, security?)` | → `boolean` | Guards WS upgrades against cross-site hijacking (allows same-origin, configured CORS origins, and non-browser clients). | Config types: `SecurityConfig` (top-level), `CorsConfig`/`CorsOrigin`, `ContentSecurityPolicyConfig`/`CspDirectiveValue`, `HstsConfig`, `TrustedTypesConfig`, `PermissionsPolicyConfig`. WrNexus applies sensible defaults (self-only CSP, `frame-ancestors 'none'`, restrictive Permissions-Policy, HSTS in production, Trusted Types in production); each is individually overridable or disable-able via `false`. ### Storage: cookies, sessions, localStorage — `@wrnexus/core` These back the `ctx.cookies`, `ctx.session`, and `ctx.localStorage` fields. | Export | Kind | Notes | | --------------------------------------------------------- | ----------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `setSessionBackend(backend)` | fn | Swap the **sync** session persistence backend (`SessionBackend`) — e.g. `bun:sqlite`. Default is process-local memory. Call once at startup. | | `loadSession(backend, options?)` | fn → `Middleware` | Back `ctx.session` with an **async** store (`AsyncSessionBackend`: `load`/`save`/`destroy`) — loads before the request, saves after. `options.ttlMs` default 24h. | | `CookieStore` | type | `get`/`getAll`/`has`/`set(name, value, opts?)`/`delete`/`headers`. | | `SessionStore` | type | `id`/`get`/`getAll`/`set`/`delete`/`regenerate`/`clear`. | | `LocalStorageSnapshot` | type | Read-only view of the browser's localStorage sent via header for CSR bindings. | | `CookieOptions` | type | `path`, `domain`, `maxAge`, `expires`, `httpOnly`, `secure`, `sameSite`. | | `SessionEntry` / `SessionBackend` / `AsyncSessionBackend` | types | Session persistence contracts. | ### Low-level security helpers — `@wrnexus/core` | Export | Signature | Notes | | ----------------------------- | --------------------- | --------------------------------------------------- | | `escapeHtml(value)` | `(string) => string` | Escape for HTML text/attributes. | | `isSafeIslandName(name)` | `(string) => boolean` | Allow only a conservative `[A-Za-z0-9_-]+` charset. | | `isSafeRequestPath(pathname)` | `(string) => boolean` | Reject NULs, `..` traversal, and backslashes. | ### JSX runtime — `@wrnexus/core`, `@wrnexus/core/jsx-runtime`, `@wrnexus/core/jsx-dev-runtime` A server-side JSX runtime that renders to HTML **strings** (no virtual DOM). Point `tsconfig`'s `jsxImportSource` at `@wrnexus/core`. | Export | Kind | Notes | | ------------------------------------------ | ------ | --------------------------------------------------------------------------------------- | | `jsx` / `jsxs` | fn | The runtime factory (TypeScript calls these automatically). Returns an `Html` instance. | | `Fragment` | symbol | JSX fragment marker. | | `Html` | class | Wraps a raw, already-safe HTML string (`toString()` returns it). | | `mustache(expr)` | fn | Emit a `{{expr}}` placeholder (tagged-template or string form) for the client binder. | | `JSXComponent` / `JSXProps` / `Renderable` | types | Component signature and renderable value types. | Values interpolated as children are HTML-escaped unless they are an `Html` instance; use `dangerouslySetInnerHTML={{ __html }}` for trusted markup. Void elements render without a closing tag; `className`→`class`, `htmlFor`→`for`, and `style` objects are serialized to CSS text. The subpath exports map to the runtime TypeScript's JSX transform expects: ```jsonc // tsconfig.json { "compilerOptions": { "jsx": "react-jsx", "jsxImportSource": "@wrnexus/core", }, } ``` ## Usage ### A minimal middleware chain ```ts import { createContext, withContextHeaders, sessionAuth, requireAuth, requestLogger, rateLimit, csrfProtection, type Middleware, } from "@wrnexus/core"; const chain: Middleware[] = [ requestLogger({ format: "json" }), rateLimit({ max: 100, windowMs: 60_000 }), csrfProtection(), sessionAuth(), requireAuth({ loginPath: "/login" }), ]; ``` ### Password auth ```ts import { hashPassword, verifyPassword, logIn, getUser } from "@wrnexus/core"; // Registration const passwordHash = await hashPassword(form.password); // Login if (await verifyPassword(form.password, user.passwordHash)) { logIn(ctx, { id: user.id, email: user.email }); } const current = getUser<{ id: string }>(ctx); // or null ``` ### HTTP caching with ETags ```ts import { etag, notModified, withCacheControl } from "@wrnexus/core"; const body = JSON.stringify(data); const tag = etag(body); if (notModified(ctx.req, tag)) { return new Response(null, { status: 304, headers: { ETag: tag } }); } const res = new Response(body, { headers: { ETag: tag, "content-type": "application/json" } }); return withCacheControl(res, { maxAge: 60, staleWhileRevalidate: 300 }); ``` ### Streaming SSE ```ts import { sse } from "@wrnexus/core"; async function* ticks() { for (let n = 0; ; n++) { yield { event: "tick", data: String(n) }; await Bun.sleep(1000); } } export default (ctx) => sse(ticks()); ``` ### A realtime room ```ts // app/realtime/chat.ts import { defineRoom } from "@wrnexus/core"; export default defineRoom({ authorize: (info) => !!info.user, // require auth onConnect(client) { client.user = client.query.user; client.room.broadcast({ type: "join", id: client.id }); }, onMessage(client, msg) { client.broadcast({ type: "say", from: client.id, text: msg.text }); }, }); ``` Scale it across processes: ```ts import { createRealtimeRegistry, bridgeRealtime } from "@wrnexus/core"; import { createPubSub } from "@wrnexus/pubsub"; import { redisDriver } from "@wrnexus/pubsub/redis"; const registry = createRealtimeRegistry(); bridgeRealtime(registry, createPubSub(redisDriver(process.env.REDIS_URL))); ``` ### JSX rendering ```tsx import { Html } from "@wrnexus/core"; function Card({ title, body }: { title: string; body: string }) { return (

{title}

{body}

); } const html: Html = ; return new Response(html.toString(), { headers: { "content-type": "text/html" } }); ``` ## Requirements / Notes - **Bun-only.** Uses `Bun.password` (argon2id), `Bun.write`, web-standard `Request`/`Response`/`FormData`/`ReadableStream`, and the global `crypto`. Node is not supported. - Session and rate-limit backends default to **process-local memory**. For multi-instance deployments, swap in a shared backend: `setSessionBackend` (sync, e.g. `bun:sqlite`) or `loadSession` (async, e.g. Redis) for sessions, a custom `RateLimitStore` for limits, and `bridgeRealtime` for realtime. - Works with the rest of the framework: realtime bridging is structurally compatible with [`@wrnexus/pubsub`](../pubsub); the security, auth, and JSX primitives here are consumed by the WrNexus server/router packages. - Subpath exports: `@wrnexus/core/jsx-runtime` and `@wrnexus/core/jsx-dev-runtime` for TypeScript's automatic JSX transform.