Files
WRNexusJS/packages/core
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-03 19:47:30 +05:30
2026-08-09 01:54:44 +05:30
2026-08-02 23:18:51 +05:30

@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

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> | Response — invokes the next middleware/handler.
Middleware type (ctx, next) => Promise<Response> | 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<string> — a page module's default export.
PageMeta / SeoConfig type <head> 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<string> argon2id hash to store.
verifyPassword(password, hash) (string, string) => Promise<boolean> 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; trueproxyKey), message, headers (default true), store.

RateLimitStore is pluggable — implement hit(key, windowMs, now) => Bucket | Promise<Bucket> (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.

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<V> 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 Files.

Export Signature Notes
collectUploads(form) (FormData) => { field, file }[] Every non-empty File in a parsed form.
saveUpload(file, options) (File, SaveUploadOptions) => Promise<SavedUpload> 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<string|Uint8Array>, StreamResponseInit?) => Response Streaming Response from a chunk source (basis for streaming SSR).
sse(source) (Iterable|AsyncIterable<ServerSentEvent>) => 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/<name>.

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; classNameclass, htmlForfor, and style objects are serialized to CSS text.

The subpath exports map to the runtime TypeScript's JSX transform expects:

// tsconfig.json
{
  "compilerOptions": {
    "jsx": "react-jsx",
    "jsxImportSource": "@wrnexus/core",
  },
}

Usage

A minimal middleware chain

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

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

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

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

// 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:

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

import { Html } from "@wrnexus/core";

function Card({ title, body }: { title: string; body: string }) {
  return (
    <article class="card">
      <h2>{title}</h2>
      <p>{body}</p>
    </article>
  );
}

const html: Html = <Card title="Hi" body="<b>escaped</b> automatically" />;
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; 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.