# WRNexusJS documentation 0.8.8 Status: Private Developer Preview. This site documents 47 release-aligned packages. # WrNexus > WrNexus is an SSR-first, **Bun-native** full-stack web framework. UI is written in > `.wrn` files (its own component language — NOT React/JSX/Vue). Routing is file-based. > This document teaches an AI how to write correct WrNexus code. It is private and > post-dates model training data, so rely on THIS document, not prior web-framework > assumptions. ## Golden rules - **Pages, components, and layouts are `.wrn` files.** Do NOT write `.tsx`/`.jsx`/React for UI. Do NOT use `useState`, hooks, JSX, or a client bundler. - **Routing is file-based** under `app/`. The filename is the route. No router config. - **Interactivity** lives in `state` + `{expr}` + `@event` inside `.wrn`. Components render on the server and hydrate automatically — you never write client-side JS islands. - **Runtime is Bun only** (uses `Bun.serve`, `bun:sqlite`, `Bun.password`, …). Node is not supported. - To add files, prefer the CLI: `wrnexus generate page ` / `component ` / `api ` / `schema `. ## Project layout ``` app/ pages/ *.wrn → routes: index.wrn = "/", about.wrn = "/about", blog/[slug].wrn = "/blog/:slug" components/ *.wrn → reusable UI, mounted in a page/component via
layouts/ *.wrn → named layouts; a page opts in with layout = "name" api/ *.ts → HTTP handlers: export const GET/POST/PUT/PATCH/DELETE = async (ctx) => Response middleware/ *.ts → export default async (ctx, next) => next() realtime/ *.ts → export default defineRoom({ ... }) from "@wrnexus/core" (ws://host/realtime/) schemas/ *.ts → validation schemas (the `v` builder), used by forms + parseBody locales/ *.json → i18n messages per language db/ schema.ts, queries/*.sql, migrations/*.sql styles/ global.css → Tailwind (default) or plain CSS wrnexus.config.ts → app config (AppConfig from "@wrnexus/styles") public/ → static assets served at / ``` ## `.wrn` page ```wrn page Home { layout = "public" // optional: a component in app/layouts/.wrn ("none" to skip) state count = 0 // optional: seeds client-reactive state (omit for pure SSR) seo { title = "Home" description = "..." canonical = "/" } view {

Hello

Count is {count}, doubled is {count * 2}.

} style { h1 { color: var(--wire-color-text); } } } ``` ## `.wrn` component ```wrn component Counter { props { // props come from mount attributes; each is coerced to the start = 0 // TYPE of its default (so start="5" arrives as the number 5) label = "Count" } state count = start // state may reference props view { } } ``` Mount it from any page/component: `
`. Components render on the server with their props, then hydrate — no per-component JS. ## The `view { }` block (plain HTML + a few directives) - `{expr}` — interpolate a JS expression. Reactive if it references `state`: `{count}`, `{count * 2}`, `{user.name}`. - `@event="expr"` — bind a DOM event; the expression runs in the reactive scope: `@click="count++"`, `@input="name = event.target.value"`. - `
` — mount a component (attrs become string props, coerced). - `` / `` — component/layout slots; fill with `
`. - **Server loop (DB/list/table):** `{#each as [, ]} …rows… {:empty} …fallback… {/each}` — iterates SSR data on the server and renders markup per item. `{item.field}` interpolates (HTML-escaped, XSS-safe). `` is a JS expression, usually an `ssr` data binding (see "Data-driven tables" below). This is how you render a database table in `.wrn`. - **Server conditional:** `{#if } … {:else if } … {:else} … {/if}` — renders the first truthy branch on the server. `` can reference `ssr` data, or the `item`/`index` of an enclosing `{#each}`. Works at page level and inside loops (e.g. `{#if r.active}{:else}{/if}` per row). For client-side show/hide based on reactive `state`, use `data-show="expr"` instead. - i18n: `{t:home.title}` in text, `t:placeholder="form.name"` on attributes — resolved per request from `app/locales/`. - Theme: any element with `data-wire-theme-toggle` toggles light/dark; `data-wire-theme-set="dark"` sets it. - Void/self-closing tags are fine: `
`, ``. - Only `{` and `}` are special (interpolation). Don't use a bare `}` in view text. ## Data-driven tables / lists (server-rendered `.wrn`) Use an `ssr` data binding to fetch rows on the server, then `{#each}` to render them. This renders on the **server** (SSR-first) and is HTML-escaped by default. ```wrn page Admin { layout = "dashboard" // Fetch on the server. The api handler at /api/contacts returns { contacts: [...] }; // this block's `return contacts` exposes that array (via `$data`) as the binding `rows`. ssr { api rows GET /api/contacts { return contacts } } view { {#each rows as r, i} {:empty} {/each}
#{i} {r.name} {r.email}
No submissions yet.
} } ``` The matching API returns the array under a key the `ssr` block reads: ```ts // app/api/contacts.ts → GET /api/contacts import { getDb } from "@wrnexus/db"; export const GET = async () => { const contacts = await getDb().all("SELECT id, name, email FROM contacts ORDER BY id DESC"); return Response.json({ contacts }); // ssr block does `return contacts` }; ``` **Prefer this `.wrn` + `{#each}` approach for DB-backed tables and lists.** (`.ts`/`.tsx` pages returning an HTML string are also supported for fully-custom programmatic rendering, but a `.wrn` page with `ssr` data + `{#each}` is the idiomatic, SSR-first way.) ## API routes (`app/api/*.ts`) ```ts // app/api/users/list.ts → GET /api/users/list import { getDb } from "@wrnexus/db"; export const GET = async (ctx) => { return Response.json({ users: await ListUsers(getDb()) }); }; export const POST = async (ctx) => { const body = await ctx.req.json(); return Response.json({ ok: true, body }, { status: 201 }); }; ``` `ctx` (the `Context` from `@wrnexus/core`) has: `req: Request`, `url: URL`, `params: Record` (dynamic route params, e.g. `/users/[id]` → `ctx.params.id`), `lang: string`, `t(key, params?)` (i18n), `cookies` (get/set), `session` (get/set). Auth: `getUser(ctx)` after `sessionAuth`/`logIn`. When an SSO forward-auth verifier needs the URL that originally reached the gateway, use `@wrnexus/helpers` instead of constructing it from untrusted headers: ```ts import { redirectToLogin } from "@wrnexus/helpers"; return redirectToLogin(ctx, "/login", { allowedHosts: ["admin.localhost:3000", "reports.localhost:3000"], }); ``` The package also exports `getOriginalRequestUrl`, `getOriginalRequestOrigin`, `getOriginalRequestPath`, and `getOriginalRequestMethod`. Always pass `allowedHosts` when using forwarded gateway URLs; the helper rejects untrusted redirect destinations. ## Middleware & realtime ```ts // app/middleware/logger.ts export default async function logger(ctx, next) { console.log(ctx.req.method, ctx.url.pathname); return next(); // return a Response WITHOUT calling next() to short-circuit } ``` ```ts // app/realtime/chat.ts → ws://host/realtime/chat import { defineRoom } from "@wrnexus/core"; export default defineRoom({ onConnect(client) { client.send({ type: "system", text: "connected" }); }, onMessage(client, msg) { client.room.broadcast({ type: "message", data: msg }); }, }); ``` Client side: a page opts in with `data-room="chat"` (handled by the realtime runtime). ## Config (`wrnexus.config.ts`) ```ts import type { AppConfig } from "@wrnexus/styles"; const config: AppConfig = { seo: { title: "App", titleTemplate: "%s | App", description: "..." }, styles: { entry: "app/styles/global.css", process: async ({ entryPath, mode }) => /* Tailwind */ "" }, fonts: { sans: '"Inter", system-ui, sans-serif', google: [{ family: "Inter", weights: [400, 600] }] }, theme: { default: "dark", themes: { light: { "color-primary": "#2563eb" } } }, i18n: { default: "en", locales: ["en", "es"] }, db: { driver: "sqlite", url: "file:./dev.db" }, security: { cors: { enabled: true, origin: ["http://localhost:5173"] } }, // profiles: { production: { db: { driver: "postgres", url: process.env.DATABASE_URL } } }, }; export default config; ``` ## Database (`@wrnexus/db`) ```ts // app/db/schema.ts import { v, table } from "@wrnexus/db"; export const users = table("users", { id: v.id(), name: v.string(), email: v.string().unique(), createdAt: v.timestamp(), }); ``` - Queries: write `app/db/queries/*.sql` with `-- name: ListUsers :many` blocks; `wrnexus db generate` emits typed functions. - Access at runtime: `import { getDb } from "@wrnexus/db"; const rows = await ListUsers(getDb());` - Migrations in `app/db/migrations/`; run `wrnexus db migrate` (dev auto-migrates sqlite). ## Validation (`@wrnexus/validation`) ```ts // app/schemas/login.ts import { v } from "@wrnexus/validation"; export default v.object({ email: v.string().email(), password: v.string().min(8), }); ``` In an API route: `import s from "../schemas/login"; import { parseBody } from "@wrnexus/validation"; const r = await parseBody(s, ctx.req);` → `r.ok ? r.value : r.response`. In a form: `
` + `` (client + server validation wired automatically). ## AI / LLM (`@wrnexus/ai`) ```ts // app/api/ai.ts import { createAI } from "@wrnexus/ai"; const ai = createAI(); // reads ANTHROPIC_API_KEY; default model claude-opus-4-8 export const POST = async (ctx) => { const { prompt } = await ctx.req.json(); return ai.streamResponse(prompt); // or: return Response.json({ text: await ai.generate(prompt) }) }; ``` ## CLI See the generated **Complete CLI command reference** below. It is sourced from the installed 0.8.8 executable so command names and options cannot drift. ## When asked to "create a page/component/feature" 1. Create the `.wrn` file under `app/pages/` (or `app/components/`) with a `page`/`component` block — or run `wrnexus generate page `. 2. Put markup in `view { }`, interactive bits in `state` + `{expr}` + `@event`, reusable UI as components mounted via `data-component`. 3. For data, add an `app/api/*.ts` route and `getDb()`; for forms, add an `app/schemas/*.ts` and `data-schema`. 4. Style with Tailwind utility classes in the view, or theme tokens (`var(--wire-*)`), or `style { }`. 5. Never emit React/JSX, a manual router, or client-side island JS — the framework handles hydration. # Complete CLI command reference This section is generated from the installed `@wrnexus/cli@0.8.7` executable. It is the canonical command inventory for this release. ```text wrnexus — WrNexus CLI Usage: wrnexus dev [app-dir] [--port=3000] [--host=::] Start the development server (live reload) wrnexus dev [app-dir] --services Start local production-service simulators with the app wrnexus dev [app-dir] --production-runtime wrnexus dev [app-dir] --services [--services-port=3099] [--services-http] Rebuild and reload the exact production artifact wrnexus build [app-dir] Build a production server bundle + assets wrnexus preview [app-dir] Serve the existing exact production output wrnexus create Scaffold a new app wrnexus workspace Scaffold a monorepo (apps/* + shared packages/*) wrnexus workspace add [--domain=name.localhost] Add an app to the current workspace wrnexus gateway [--port=3000] Serve every workspace app behind one port, routed by domain wrnexus production [workspace-dir] Build, migrate, and serve the complete production workspace wrnexus [workspace-dir] Run a configured workspace environment (development/staging/custom) wrnexus generate Scaffold a page | component | api | schema wrnexus generate routes | docker | mobile Generate routes or scaffold deployment targets wrnexus generate types [app-dir] Generate application-wide route/component/key types wrnexus routes [app-dir] Generate typed named routes wrnexus typecheck [app-dir] Generate types and check TypeScript plus every .wrn file wrnexus mobile add Install Capacitor or Expo native packages wrnexus mobile compile Compile .wrn pages into native Expo routes wrnexus native list List cross-platform native capabilities wrnexus native add Install capability packages for the configured mobile mode wrnexus eject Copy a Wire UI component into app/components wrnexus update [dir] [--latest] Upgrade deps, migrate project files, and verify the app wrnexus db Migrations: migrate | rollback | status | seed | generate | new wrnexus authz Authorization: list | generate | init [--dialect=sqlite|postgres|mysql] wrnexus test [level] [app-dir] [--watch] Run unit | component | api | browser | visual | accessibility | performance wrnexus profiles [app-dir] List config profiles (dev/prod/uat/…) and their env files wrnexus doctor [app-dir] [--fix] Check project health; optionally apply safe repairs wrnexus compatibility [app-dir] Inspect or explicitly upgrade behavior defaults wrnexus contracts [app-dir] Detect breaking boundary contract changes wrnexus security [app-dir] Audit ASVS controls, inspect headers, or run abuse tests wrnexus api [app-dir] Generate OpenAPI, docs, Postman, examples and SDKs wrnexus sdk generate [app-dir] Generate TypeScript, JavaScript, Java, Go, or Python SDK wrnexus deploy [app-dir] Generate docker | kubernetes | systemd | railway | render | fly wrnexus mcp [app-dir] Start the WRNexus MCP server over stdio wrnexus i18n [app-dir] Extract and audit translation keys wrnexus report [app-dir] [--file=app/pages/page.wrn] Create a sanitized reproduction bundle wrnexus playground [--port=4173] Start the shareable WRN compiler playground wrnexus config [app-dir] --explain Print the fully resolved profile configuration wrnexus analyze [app-dir] Inspect dist/build-report.json and performance budgets wrnexus explain [subject] [app-dir] Explain compiler and production build decisions wrnexus explain [app-dir] Explain route caching or permission enforcement wrnexus inspect [app-dir] Inspect packages | plugins | routes | assets | runtimes | styles | migrations | bundle wrnexus inspect component [app-dir] Inspect a component's typed public contract wrnexus generate system Scaffold a complete framework-native package Profiles: pass --profile= to dev/build/db (or set WRNEXUS_PROFILE) to load that profile's .env cascade and config overrides. e.g. wrnexus dev --profile=uat Workspace environments: configure protocol, rootDomain, port, hostname, runtime (development|production), hmr, build, migrate, and profile in wrnexus.workspace.ts. CLI overrides: --port, --host, --build/--no-build, --migrate/--no-migrate, --profile, and --prepare-only. Update options: --dry-run previews changes; --no-verify skips post-update check/build. ``` ## CLI workflows with expected output Commands below are copy-ready. Paths, ports, counts, and timings vary by project; the shown output identifies the success condition to check. ### Create, check, build, and preview an application ```bash bunx @wrnexus/cli@0.8.7 create my-app cd my-app bun install bunx wrnexus typecheck . bunx wrnexus build . bunx wrnexus preview . --port=3000 ``` Expected output: ```text ✓ Application types are valid ✓ Runtime: .../dist/reactive.js ✓ Styles: .../dist/styles.css ✓ Server: .../dist/server.js Run it: bun .../dist/server.js ``` ### Generate framework files and committed application types ```bash bunx wrnexus generate page Dashboard bunx wrnexus generate component status-card bunx wrnexus generate api health bunx wrnexus generate schema account bunx wrnexus generate routes bunx wrnexus generate types . ``` Expected output includes created source paths followed by: ```text ✓ Generated app/routes.gen.ts (... routes) ✓ Generated .../app/types/wrnexus.generated.d.ts (... routes, ... components) ``` ### Run development and production-runtime modes ```bash bunx wrnexus dev . --port=3000 bunx wrnexus dev . --services --services-port=3099 bunx wrnexus dev . --production-runtime --port=3000 ``` Expected output reports the active profile, discovered plugins/routes, generated cache path, and listening URL. Production-runtime mode rebuilds and reloads the exact production artifact. ### Database lifecycle ```bash bunx wrnexus db status bunx wrnexus db new create_accounts --from-models bunx wrnexus db migrate bunx wrnexus db generate bunx wrnexus db seed ``` Expected output identifies the selected profile/dialect, migration state, applied migration names, and generated typed query files. Run `db rollback` only when intentionally reverting the latest migration. ### Diagnose, inspect, and enforce contracts ```bash bunx wrnexus doctor . bunx wrnexus typecheck . bunx wrnexus compatibility check . bunx wrnexus contracts check . bunx wrnexus security audit . bunx wrnexus inspect packages . bunx wrnexus inspect routes . bunx wrnexus inspect component Navbar . bunx wrnexus analyze . ``` Successful checks exit with status 0. Inspect commands print the resolved package, route, component, asset, runtime, style, migration, or bundle contract without changing the application. ### Tests, API artifacts, SDKs, and deployment manifests ```bash bunx wrnexus test unit . bunx wrnexus test component . bunx wrnexus test api . bunx wrnexus test browser . bunx wrnexus api generate . bunx wrnexus api docs . bunx wrnexus sdk generate typescript . bunx wrnexus deploy docker . ``` Test commands return the underlying suite summary and a nonzero status on failure. API/SDK/deploy commands report each generated artifact; review and commit generated files after validation. ### Workspaces and environments ```bash bunx wrnexus workspace company-platform cd company-platform bunx wrnexus workspace add reports --domain=reports.localhost bunx wrnexus gateway --port=3000 bunx wrnexus production . --prepare-only bunx wrnexus staging . ``` Expected output lists created apps, domain mappings, selected environment/profile, build and migration decisions, and the gateway address. ### Internationalization, native targets, MCP, and support bundles ```bash bunx wrnexus i18n extract . bunx wrnexus i18n validate . bunx wrnexus generate mobile bunx wrnexus mobile compile bunx wrnexus native list bunx wrnexus mcp . bunx wrnexus report . --file=app/pages/index.wrn ``` Expected output identifies extracted/validated translation keys, generated native routes, available capabilities, MCP stdio startup, or the sanitized reproduction archive. MCP uses stdio, so protocol messages—not a web URL—are its normal runtime output. ### Safe upgrades ```bash bunx wrnexus update . --latest --dry-run bunx wrnexus update . --latest ``` The dry run previews dependency and migration changes. A real update prints the source and target versions, backup directory, each migration action, installation result, and verification result. Commit or back up the application before upgrading. # Complete CLI command reference This section is generated from the installed `@wrnexus/cli@0.8.7` executable. It is the canonical command inventory for this release. ```text wrnexus — WrNexus CLI Usage: wrnexus dev [app-dir] [--port=3000] [--host=::] Start the development server (live reload) wrnexus dev [app-dir] --services Start local production-service simulators with the app wrnexus dev [app-dir] --production-runtime wrnexus dev [app-dir] --services [--services-port=3099] [--services-http] Rebuild and reload the exact production artifact wrnexus build [app-dir] Build a production server bundle + assets wrnexus preview [app-dir] Serve the existing exact production output wrnexus create Scaffold a new app wrnexus workspace Scaffold a monorepo (apps/* + shared packages/*) wrnexus workspace add [--domain=name.localhost] Add an app to the current workspace wrnexus gateway [--port=3000] Serve every workspace app behind one port, routed by domain wrnexus production [workspace-dir] Build, migrate, and serve the complete production workspace wrnexus [workspace-dir] Run a configured workspace environment (development/staging/custom) wrnexus generate Scaffold a page | component | api | schema wrnexus generate routes | docker | mobile Generate routes or scaffold deployment targets wrnexus generate types [app-dir] Generate application-wide route/component/key types wrnexus routes [app-dir] Generate typed named routes wrnexus typecheck [app-dir] Generate types and check TypeScript plus every .wrn file wrnexus mobile add Install Capacitor or Expo native packages wrnexus mobile compile Compile .wrn pages into native Expo routes wrnexus native list List cross-platform native capabilities wrnexus native add Install capability packages for the configured mobile mode wrnexus eject Copy a Wire UI component into app/components wrnexus update [dir] [--latest] Upgrade deps, migrate project files, and verify the app wrnexus db Migrations: migrate | rollback | status | seed | generate | new wrnexus authz Authorization: list | generate | init [--dialect=sqlite|postgres|mysql] wrnexus test [level] [app-dir] [--watch] Run unit | component | api | browser | visual | accessibility | performance wrnexus profiles [app-dir] List config profiles (dev/prod/uat/…) and their env files wrnexus doctor [app-dir] [--fix] Check project health; optionally apply safe repairs wrnexus compatibility [app-dir] Inspect or explicitly upgrade behavior defaults wrnexus contracts [app-dir] Detect breaking boundary contract changes wrnexus security [app-dir] Audit ASVS controls, inspect headers, or run abuse tests wrnexus api [app-dir] Generate OpenAPI, docs, Postman, examples and SDKs wrnexus sdk generate [app-dir] Generate TypeScript, JavaScript, Java, Go, or Python SDK wrnexus deploy [app-dir] Generate docker | kubernetes | systemd | railway | render | fly wrnexus mcp [app-dir] Start the WRNexus MCP server over stdio wrnexus i18n [app-dir] Extract and audit translation keys wrnexus report [app-dir] [--file=app/pages/page.wrn] Create a sanitized reproduction bundle wrnexus playground [--port=4173] Start the shareable WRN compiler playground wrnexus config [app-dir] --explain Print the fully resolved profile configuration wrnexus analyze [app-dir] Inspect dist/build-report.json and performance budgets wrnexus explain [subject] [app-dir] Explain compiler and production build decisions wrnexus explain [app-dir] Explain route caching or permission enforcement wrnexus inspect [app-dir] Inspect packages | plugins | routes | assets | runtimes | styles | migrations | bundle wrnexus inspect component [app-dir] Inspect a component's typed public contract wrnexus generate system Scaffold a complete framework-native package Profiles: pass --profile= to dev/build/db (or set WRNEXUS_PROFILE) to load that profile's .env cascade and config overrides. e.g. wrnexus dev --profile=uat Workspace environments: configure protocol, rootDomain, port, hostname, runtime (development|production), hmr, build, migrate, and profile in wrnexus.workspace.ts. CLI overrides: --port, --host, --build/--no-build, --migrate/--no-migrate, --profile, and --prepare-only. Update options: --dry-run previews changes; --no-verify skips post-update check/build. ``` ## CLI workflows with expected output Commands below are copy-ready. Paths, ports, counts, and timings vary by project; the shown output identifies the success condition to check. ### Create, check, build, and preview an application ```bash bunx @wrnexus/cli@0.8.7 create my-app cd my-app bun install bunx wrnexus typecheck . bunx wrnexus build . bunx wrnexus preview . --port=3000 ``` Expected output: ```text ✓ Application types are valid ✓ Runtime: .../dist/reactive.js ✓ Styles: .../dist/styles.css ✓ Server: .../dist/server.js Run it: bun .../dist/server.js ``` ### Generate framework files and committed application types ```bash bunx wrnexus generate page Dashboard bunx wrnexus generate component status-card bunx wrnexus generate api health bunx wrnexus generate schema account bunx wrnexus generate routes bunx wrnexus generate types . ``` Expected output includes created source paths followed by: ```text ✓ Generated app/routes.gen.ts (... routes) ✓ Generated .../app/types/wrnexus.generated.d.ts (... routes, ... components) ``` ### Run development and production-runtime modes ```bash bunx wrnexus dev . --port=3000 bunx wrnexus dev . --services --services-port=3099 bunx wrnexus dev . --production-runtime --port=3000 ``` Expected output reports the active profile, discovered plugins/routes, generated cache path, and listening URL. Production-runtime mode rebuilds and reloads the exact production artifact. ### Database lifecycle ```bash bunx wrnexus db status bunx wrnexus db new create_accounts --from-models bunx wrnexus db migrate bunx wrnexus db generate bunx wrnexus db seed ``` Expected output identifies the selected profile/dialect, migration state, applied migration names, and generated typed query files. Run `db rollback` only when intentionally reverting the latest migration. ### Diagnose, inspect, and enforce contracts ```bash bunx wrnexus doctor . bunx wrnexus typecheck . bunx wrnexus compatibility check . bunx wrnexus contracts check . bunx wrnexus security audit . bunx wrnexus inspect packages . bunx wrnexus inspect routes . bunx wrnexus inspect component Navbar . bunx wrnexus analyze . ``` Successful checks exit with status 0. Inspect commands print the resolved package, route, component, asset, runtime, style, migration, or bundle contract without changing the application. ### Tests, API artifacts, SDKs, and deployment manifests ```bash bunx wrnexus test unit . bunx wrnexus test component . bunx wrnexus test api . bunx wrnexus test browser . bunx wrnexus api generate . bunx wrnexus api docs . bunx wrnexus sdk generate typescript . bunx wrnexus deploy docker . ``` Test commands return the underlying suite summary and a nonzero status on failure. API/SDK/deploy commands report each generated artifact; review and commit generated files after validation. ### Workspaces and environments ```bash bunx wrnexus workspace company-platform cd company-platform bunx wrnexus workspace add reports --domain=reports.localhost bunx wrnexus gateway --port=3000 bunx wrnexus production . --prepare-only bunx wrnexus staging . ``` Expected output lists created apps, domain mappings, selected environment/profile, build and migration decisions, and the gateway address. ### Internationalization, native targets, MCP, and support bundles ```bash bunx wrnexus i18n extract . bunx wrnexus i18n validate . bunx wrnexus generate mobile bunx wrnexus mobile compile bunx wrnexus native list bunx wrnexus mcp . bunx wrnexus report . --file=app/pages/index.wrn ``` Expected output identifies extracted/validated translation keys, generated native routes, available capabilities, MCP stdio startup, or the sanitized reproduction archive. MCP uses stdio, so protocol messages—not a web URL—are its normal runtime output. ### Safe upgrades ```bash bunx wrnexus update . --latest --dry-run bunx wrnexus update . --latest ``` The dry run previews dependency and migration changes. A real update prints the source and target versions, backup directory, each migration action, installation result, and verification result. Commit or back up the application before upgrading. # Complete CLI command reference This section is generated from the installed `@wrnexus/cli@0.8.7` executable. It is the canonical command inventory for this release. ```text wrnexus — WrNexus CLI Usage: wrnexus dev [app-dir] [--port=3000] [--host=::] Start the development server (live reload) wrnexus dev [app-dir] --services Start local production-service simulators with the app wrnexus dev [app-dir] --production-runtime wrnexus dev [app-dir] --services [--services-port=3099] [--services-http] Rebuild and reload the exact production artifact wrnexus build [app-dir] Build a production server bundle + assets wrnexus preview [app-dir] Serve the existing exact production output wrnexus create Scaffold a new app wrnexus workspace Scaffold a monorepo (apps/* + shared packages/*) wrnexus workspace add [--domain=name.localhost] Add an app to the current workspace wrnexus gateway [--port=3000] Serve every workspace app behind one port, routed by domain wrnexus production [workspace-dir] Build, migrate, and serve the complete production workspace wrnexus [workspace-dir] Run a configured workspace environment (development/staging/custom) wrnexus generate Scaffold a page | component | api | schema wrnexus generate routes | docker | mobile Generate routes or scaffold deployment targets wrnexus generate types [app-dir] Generate application-wide route/component/key types wrnexus routes [app-dir] Generate typed named routes wrnexus typecheck [app-dir] Generate types and check TypeScript plus every .wrn file wrnexus mobile add Install Capacitor or Expo native packages wrnexus mobile compile Compile .wrn pages into native Expo routes wrnexus native list List cross-platform native capabilities wrnexus native add Install capability packages for the configured mobile mode wrnexus eject Copy a Wire UI component into app/components wrnexus update [dir] [--latest] Upgrade deps, migrate project files, and verify the app wrnexus db Migrations: migrate | rollback | status | seed | generate | new wrnexus authz Authorization: list | generate | init [--dialect=sqlite|postgres|mysql] wrnexus test [level] [app-dir] [--watch] Run unit | component | api | browser | visual | accessibility | performance wrnexus profiles [app-dir] List config profiles (dev/prod/uat/…) and their env files wrnexus doctor [app-dir] [--fix] Check project health; optionally apply safe repairs wrnexus compatibility [app-dir] Inspect or explicitly upgrade behavior defaults wrnexus contracts [app-dir] Detect breaking boundary contract changes wrnexus security [app-dir] Audit ASVS controls, inspect headers, or run abuse tests wrnexus api [app-dir] Generate OpenAPI, docs, Postman, examples and SDKs wrnexus sdk generate [app-dir] Generate TypeScript, JavaScript, Java, Go, or Python SDK wrnexus deploy [app-dir] Generate docker | kubernetes | systemd | railway | render | fly wrnexus mcp [app-dir] Start the WRNexus MCP server over stdio wrnexus i18n [app-dir] Extract and audit translation keys wrnexus report [app-dir] [--file=app/pages/page.wrn] Create a sanitized reproduction bundle wrnexus playground [--port=4173] Start the shareable WRN compiler playground wrnexus config [app-dir] --explain Print the fully resolved profile configuration wrnexus analyze [app-dir] Inspect dist/build-report.json and performance budgets wrnexus explain [subject] [app-dir] Explain compiler and production build decisions wrnexus explain [app-dir] Explain route caching or permission enforcement wrnexus inspect [app-dir] Inspect packages | plugins | routes | assets | runtimes | styles | migrations | bundle wrnexus inspect component [app-dir] Inspect a component's typed public contract wrnexus generate system Scaffold a complete framework-native package Profiles: pass --profile= to dev/build/db (or set WRNEXUS_PROFILE) to load that profile's .env cascade and config overrides. e.g. wrnexus dev --profile=uat Workspace environments: configure protocol, rootDomain, port, hostname, runtime (development|production), hmr, build, migrate, and profile in wrnexus.workspace.ts. CLI overrides: --port, --host, --build/--no-build, --migrate/--no-migrate, --profile, and --prepare-only. Update options: --dry-run previews changes; --no-verify skips post-update check/build. ``` ## CLI workflows with expected output Commands below are copy-ready. Paths, ports, counts, and timings vary by project; the shown output identifies the success condition to check. ### Create, check, build, and preview an application ```bash bunx @wrnexus/cli@0.8.7 create my-app cd my-app bun install bunx wrnexus typecheck . bunx wrnexus build . bunx wrnexus preview . --port=3000 ``` Expected output: ```text ✓ Application types are valid ✓ Runtime: .../dist/reactive.js ✓ Styles: .../dist/styles.css ✓ Server: .../dist/server.js Run it: bun .../dist/server.js ``` ### Generate framework files and committed application types ```bash bunx wrnexus generate page Dashboard bunx wrnexus generate component status-card bunx wrnexus generate api health bunx wrnexus generate schema account bunx wrnexus generate routes bunx wrnexus generate types . ``` Expected output includes created source paths followed by: ```text ✓ Generated app/routes.gen.ts (... routes) ✓ Generated .../app/types/wrnexus.generated.d.ts (... routes, ... components) ``` ### Run development and production-runtime modes ```bash bunx wrnexus dev . --port=3000 bunx wrnexus dev . --services --services-port=3099 bunx wrnexus dev . --production-runtime --port=3000 ``` Expected output reports the active profile, discovered plugins/routes, generated cache path, and listening URL. Production-runtime mode rebuilds and reloads the exact production artifact. ### Database lifecycle ```bash bunx wrnexus db status bunx wrnexus db new create_accounts --from-models bunx wrnexus db migrate bunx wrnexus db generate bunx wrnexus db seed ``` Expected output identifies the selected profile/dialect, migration state, applied migration names, and generated typed query files. Run `db rollback` only when intentionally reverting the latest migration. ### Diagnose, inspect, and enforce contracts ```bash bunx wrnexus doctor . bunx wrnexus typecheck . bunx wrnexus compatibility check . bunx wrnexus contracts check . bunx wrnexus security audit . bunx wrnexus inspect packages . bunx wrnexus inspect routes . bunx wrnexus inspect component Navbar . bunx wrnexus analyze . ``` Successful checks exit with status 0. Inspect commands print the resolved package, route, component, asset, runtime, style, migration, or bundle contract without changing the application. ### Tests, API artifacts, SDKs, and deployment manifests ```bash bunx wrnexus test unit . bunx wrnexus test component . bunx wrnexus test api . bunx wrnexus test browser . bunx wrnexus api generate . bunx wrnexus api docs . bunx wrnexus sdk generate typescript . bunx wrnexus deploy docker . ``` Test commands return the underlying suite summary and a nonzero status on failure. API/SDK/deploy commands report each generated artifact; review and commit generated files after validation. ### Workspaces and environments ```bash bunx wrnexus workspace company-platform cd company-platform bunx wrnexus workspace add reports --domain=reports.localhost bunx wrnexus gateway --port=3000 bunx wrnexus production . --prepare-only bunx wrnexus staging . ``` Expected output lists created apps, domain mappings, selected environment/profile, build and migration decisions, and the gateway address. ### Internationalization, native targets, MCP, and support bundles ```bash bunx wrnexus i18n extract . bunx wrnexus i18n validate . bunx wrnexus generate mobile bunx wrnexus mobile compile bunx wrnexus native list bunx wrnexus mcp . bunx wrnexus report . --file=app/pages/index.wrn ``` Expected output identifies extracted/validated translation keys, generated native routes, available capabilities, MCP stdio startup, or the sanitized reproduction archive. MCP uses stdio, so protocol messages—not a web URL—are its normal runtime output. ### Safe upgrades ```bash bunx wrnexus update . --latest --dry-run bunx wrnexus update . --latest ``` The dry run previews dependency and migration changes. A real update prints the source and target versions, backup directory, each migration action, installation result, and verification result. Commit or back up the application before upgrading. # Complete CLI command reference This section is generated from the installed `@wrnexus/cli@0.8.7` executable. It is the canonical command inventory for this release. ```text wrnexus — WrNexus CLI Usage: wrnexus dev [app-dir] [--port=3000] [--host=::] Start the development server (live reload) wrnexus dev [app-dir] --services Start local production-service simulators with the app wrnexus dev [app-dir] --production-runtime wrnexus dev [app-dir] --services [--services-port=3099] [--services-http] Rebuild and reload the exact production artifact wrnexus build [app-dir] Build a production server bundle + assets wrnexus preview [app-dir] Serve the existing exact production output wrnexus create Scaffold a new app wrnexus workspace Scaffold a monorepo (apps/* + shared packages/*) wrnexus workspace add [--domain=name.localhost] Add an app to the current workspace wrnexus gateway [--port=3000] Serve every workspace app behind one port, routed by domain wrnexus production [workspace-dir] Build, migrate, and serve the complete production workspace wrnexus [workspace-dir] Run a configured workspace environment (development/staging/custom) wrnexus generate Scaffold a page | component | api | schema wrnexus generate routes | docker | mobile Generate routes or scaffold deployment targets wrnexus generate types [app-dir] Generate application-wide route/component/key types wrnexus routes [app-dir] Generate typed named routes wrnexus typecheck [app-dir] Generate types and check TypeScript plus every .wrn file wrnexus mobile add Install Capacitor or Expo native packages wrnexus mobile compile Compile .wrn pages into native Expo routes wrnexus native list List cross-platform native capabilities wrnexus native add Install capability packages for the configured mobile mode wrnexus eject Copy a Wire UI component into app/components wrnexus update [dir] [--latest] Upgrade deps, migrate project files, and verify the app wrnexus db Migrations: migrate | rollback | status | seed | generate | new wrnexus authz Authorization: list | generate | init [--dialect=sqlite|postgres|mysql] wrnexus test [level] [app-dir] [--watch] Run unit | component | api | browser | visual | accessibility | performance wrnexus profiles [app-dir] List config profiles (dev/prod/uat/…) and their env files wrnexus doctor [app-dir] [--fix] Check project health; optionally apply safe repairs wrnexus compatibility [app-dir] Inspect or explicitly upgrade behavior defaults wrnexus contracts [app-dir] Detect breaking boundary contract changes wrnexus security [app-dir] Audit ASVS controls, inspect headers, or run abuse tests wrnexus api [app-dir] Generate OpenAPI, docs, Postman, examples and SDKs wrnexus sdk generate [app-dir] Generate TypeScript, JavaScript, Java, Go, or Python SDK wrnexus deploy [app-dir] Generate docker | kubernetes | systemd | railway | render | fly wrnexus mcp [app-dir] Start the WRNexus MCP server over stdio wrnexus i18n [app-dir] Extract and audit translation keys wrnexus report [app-dir] [--file=app/pages/page.wrn] Create a sanitized reproduction bundle wrnexus playground [--port=4173] Start the shareable WRN compiler playground wrnexus config [app-dir] --explain Print the fully resolved profile configuration wrnexus analyze [app-dir] Inspect dist/build-report.json and performance budgets wrnexus explain [subject] [app-dir] Explain compiler and production build decisions wrnexus explain [app-dir] Explain route caching or permission enforcement wrnexus inspect [app-dir] Inspect packages | plugins | routes | assets | runtimes | styles | migrations | bundle wrnexus inspect component [app-dir] Inspect a component's typed public contract wrnexus generate system Scaffold a complete framework-native package Profiles: pass --profile= to dev/build/db (or set WRNEXUS_PROFILE) to load that profile's .env cascade and config overrides. e.g. wrnexus dev --profile=uat Workspace environments: configure protocol, rootDomain, port, hostname, runtime (development|production), hmr, build, migrate, and profile in wrnexus.workspace.ts. CLI overrides: --port, --host, --build/--no-build, --migrate/--no-migrate, --profile, and --prepare-only. Update options: --dry-run previews changes; --no-verify skips post-update check/build. ``` ## CLI workflows with expected output Commands below are copy-ready. Paths, ports, counts, and timings vary by project; the shown output identifies the success condition to check. ### Create, check, build, and preview an application ```bash bunx @wrnexus/cli@0.8.7 create my-app cd my-app bun install bunx wrnexus typecheck . bunx wrnexus build . bunx wrnexus preview . --port=3000 ``` Expected output: ```text ✓ Application types are valid ✓ Runtime: .../dist/reactive.js ✓ Styles: .../dist/styles.css ✓ Server: .../dist/server.js Run it: bun .../dist/server.js ``` ### Generate framework files and committed application types ```bash bunx wrnexus generate page Dashboard bunx wrnexus generate component status-card bunx wrnexus generate api health bunx wrnexus generate schema account bunx wrnexus generate routes bunx wrnexus generate types . ``` Expected output includes created source paths followed by: ```text ✓ Generated app/routes.gen.ts (... routes) ✓ Generated .../app/types/wrnexus.generated.d.ts (... routes, ... components) ``` ### Run development and production-runtime modes ```bash bunx wrnexus dev . --port=3000 bunx wrnexus dev . --services --services-port=3099 bunx wrnexus dev . --production-runtime --port=3000 ``` Expected output reports the active profile, discovered plugins/routes, generated cache path, and listening URL. Production-runtime mode rebuilds and reloads the exact production artifact. ### Database lifecycle ```bash bunx wrnexus db status bunx wrnexus db new create_accounts --from-models bunx wrnexus db migrate bunx wrnexus db generate bunx wrnexus db seed ``` Expected output identifies the selected profile/dialect, migration state, applied migration names, and generated typed query files. Run `db rollback` only when intentionally reverting the latest migration. ### Diagnose, inspect, and enforce contracts ```bash bunx wrnexus doctor . bunx wrnexus typecheck . bunx wrnexus compatibility check . bunx wrnexus contracts check . bunx wrnexus security audit . bunx wrnexus inspect packages . bunx wrnexus inspect routes . bunx wrnexus inspect component Navbar . bunx wrnexus analyze . ``` Successful checks exit with status 0. Inspect commands print the resolved package, route, component, asset, runtime, style, migration, or bundle contract without changing the application. ### Tests, API artifacts, SDKs, and deployment manifests ```bash bunx wrnexus test unit . bunx wrnexus test component . bunx wrnexus test api . bunx wrnexus test browser . bunx wrnexus api generate . bunx wrnexus api docs . bunx wrnexus sdk generate typescript . bunx wrnexus deploy docker . ``` Test commands return the underlying suite summary and a nonzero status on failure. API/SDK/deploy commands report each generated artifact; review and commit generated files after validation. ### Workspaces and environments ```bash bunx wrnexus workspace company-platform cd company-platform bunx wrnexus workspace add reports --domain=reports.localhost bunx wrnexus gateway --port=3000 bunx wrnexus production . --prepare-only bunx wrnexus staging . ``` Expected output lists created apps, domain mappings, selected environment/profile, build and migration decisions, and the gateway address. ### Internationalization, native targets, MCP, and support bundles ```bash bunx wrnexus i18n extract . bunx wrnexus i18n validate . bunx wrnexus generate mobile bunx wrnexus mobile compile bunx wrnexus native list bunx wrnexus mcp . bunx wrnexus report . --file=app/pages/index.wrn ``` Expected output identifies extracted/validated translation keys, generated native routes, available capabilities, MCP stdio startup, or the sanitized reproduction archive. MCP uses stdio, so protocol messages—not a web URL—are its normal runtime output. ### Safe upgrades ```bash bunx wrnexus update . --latest --dry-run bunx wrnexus update . --latest ``` The dry run previews dependency and migration changes. A real update prints the source and target versions, backup directory, each migration action, installation result, and verification result. Commit or back up the application before upgrading. # Complete CLI command reference This section is generated from the installed `@wrnexus/cli@0.8.7` executable. It is the canonical command inventory for this release. ```text wrnexus — WrNexus CLI Usage: wrnexus dev [app-dir] [--port=3000] [--host=::] Start the development server (live reload) wrnexus dev [app-dir] --services Start local production-service simulators with the app wrnexus dev [app-dir] --production-runtime wrnexus dev [app-dir] --services [--services-port=3099] [--services-http] Rebuild and reload the exact production artifact wrnexus build [app-dir] Build a production server bundle + assets wrnexus preview [app-dir] Serve the existing exact production output wrnexus create Scaffold a new app wrnexus workspace Scaffold a monorepo (apps/* + shared packages/*) wrnexus workspace add [--domain=name.localhost] Add an app to the current workspace wrnexus gateway [--port=3000] Serve every workspace app behind one port, routed by domain wrnexus production [workspace-dir] Build, migrate, and serve the complete production workspace wrnexus [workspace-dir] Run a configured workspace environment (development/staging/custom) wrnexus generate Scaffold a page | component | api | schema wrnexus generate routes | docker | mobile Generate routes or scaffold deployment targets wrnexus generate types [app-dir] Generate application-wide route/component/key types wrnexus routes [app-dir] Generate typed named routes wrnexus typecheck [app-dir] Generate types and check TypeScript plus every .wrn file wrnexus mobile add Install Capacitor or Expo native packages wrnexus mobile compile Compile .wrn pages into native Expo routes wrnexus native list List cross-platform native capabilities wrnexus native add Install capability packages for the configured mobile mode wrnexus eject Copy a Wire UI component into app/components wrnexus update [dir] [--latest] Upgrade deps, migrate project files, and verify the app wrnexus db Migrations: migrate | rollback | status | seed | generate | new wrnexus authz Authorization: list | generate | init [--dialect=sqlite|postgres|mysql] wrnexus test [level] [app-dir] [--watch] Run unit | component | api | browser | visual | accessibility | performance wrnexus profiles [app-dir] List config profiles (dev/prod/uat/…) and their env files wrnexus doctor [app-dir] [--fix] Check project health; optionally apply safe repairs wrnexus compatibility [app-dir] Inspect or explicitly upgrade behavior defaults wrnexus contracts [app-dir] Detect breaking boundary contract changes wrnexus security [app-dir] Audit ASVS controls, inspect headers, or run abuse tests wrnexus api [app-dir] Generate OpenAPI, docs, Postman, examples and SDKs wrnexus sdk generate [app-dir] Generate TypeScript, JavaScript, Java, Go, or Python SDK wrnexus deploy [app-dir] Generate docker | kubernetes | systemd | railway | render | fly wrnexus mcp [app-dir] Start the WRNexus MCP server over stdio wrnexus i18n [app-dir] Extract and audit translation keys wrnexus report [app-dir] [--file=app/pages/page.wrn] Create a sanitized reproduction bundle wrnexus playground [--port=4173] Start the shareable WRN compiler playground wrnexus config [app-dir] --explain Print the fully resolved profile configuration wrnexus analyze [app-dir] Inspect dist/build-report.json and performance budgets wrnexus explain [subject] [app-dir] Explain compiler and production build decisions wrnexus explain [app-dir] Explain route caching or permission enforcement wrnexus inspect [app-dir] Inspect packages | plugins | routes | assets | runtimes | styles | migrations | bundle wrnexus inspect component [app-dir] Inspect a component's typed public contract wrnexus generate system Scaffold a complete framework-native package Profiles: pass --profile= to dev/build/db (or set WRNEXUS_PROFILE) to load that profile's .env cascade and config overrides. e.g. wrnexus dev --profile=uat Workspace environments: configure protocol, rootDomain, port, hostname, runtime (development|production), hmr, build, migrate, and profile in wrnexus.workspace.ts. CLI overrides: --port, --host, --build/--no-build, --migrate/--no-migrate, --profile, and --prepare-only. Update options: --dry-run previews changes; --no-verify skips post-update check/build. ``` ## CLI workflows with expected output Commands below are copy-ready. Paths, ports, counts, and timings vary by project; the shown output identifies the success condition to check. ### Create, check, build, and preview an application ```bash bunx @wrnexus/cli@0.8.7 create my-app cd my-app bun install bunx wrnexus typecheck . bunx wrnexus build . bunx wrnexus preview . --port=3000 ``` Expected output: ```text ✓ Application types are valid ✓ Runtime: .../dist/reactive.js ✓ Styles: .../dist/styles.css ✓ Server: .../dist/server.js Run it: bun .../dist/server.js ``` ### Generate framework files and committed application types ```bash bunx wrnexus generate page Dashboard bunx wrnexus generate component status-card bunx wrnexus generate api health bunx wrnexus generate schema account bunx wrnexus generate routes bunx wrnexus generate types . ``` Expected output includes created source paths followed by: ```text ✓ Generated app/routes.gen.ts (... routes) ✓ Generated .../app/types/wrnexus.generated.d.ts (... routes, ... components) ``` ### Run development and production-runtime modes ```bash bunx wrnexus dev . --port=3000 bunx wrnexus dev . --services --services-port=3099 bunx wrnexus dev . --production-runtime --port=3000 ``` Expected output reports the active profile, discovered plugins/routes, generated cache path, and listening URL. Production-runtime mode rebuilds and reloads the exact production artifact. ### Database lifecycle ```bash bunx wrnexus db status bunx wrnexus db new create_accounts --from-models bunx wrnexus db migrate bunx wrnexus db generate bunx wrnexus db seed ``` Expected output identifies the selected profile/dialect, migration state, applied migration names, and generated typed query files. Run `db rollback` only when intentionally reverting the latest migration. ### Diagnose, inspect, and enforce contracts ```bash bunx wrnexus doctor . bunx wrnexus typecheck . bunx wrnexus compatibility check . bunx wrnexus contracts check . bunx wrnexus security audit . bunx wrnexus inspect packages . bunx wrnexus inspect routes . bunx wrnexus inspect component Navbar . bunx wrnexus analyze . ``` Successful checks exit with status 0. Inspect commands print the resolved package, route, component, asset, runtime, style, migration, or bundle contract without changing the application. ### Tests, API artifacts, SDKs, and deployment manifests ```bash bunx wrnexus test unit . bunx wrnexus test component . bunx wrnexus test api . bunx wrnexus test browser . bunx wrnexus api generate . bunx wrnexus api docs . bunx wrnexus sdk generate typescript . bunx wrnexus deploy docker . ``` Test commands return the underlying suite summary and a nonzero status on failure. API/SDK/deploy commands report each generated artifact; review and commit generated files after validation. ### Workspaces and environments ```bash bunx wrnexus workspace company-platform cd company-platform bunx wrnexus workspace add reports --domain=reports.localhost bunx wrnexus gateway --port=3000 bunx wrnexus production . --prepare-only bunx wrnexus staging . ``` Expected output lists created apps, domain mappings, selected environment/profile, build and migration decisions, and the gateway address. ### Internationalization, native targets, MCP, and support bundles ```bash bunx wrnexus i18n extract . bunx wrnexus i18n validate . bunx wrnexus generate mobile bunx wrnexus mobile compile bunx wrnexus native list bunx wrnexus mcp . bunx wrnexus report . --file=app/pages/index.wrn ``` Expected output identifies extracted/validated translation keys, generated native routes, available capabilities, MCP stdio startup, or the sanitized reproduction archive. MCP uses stdio, so protocol messages—not a web URL—are its normal runtime output. ### Safe upgrades ```bash bunx wrnexus update . --latest --dry-run bunx wrnexus update . --latest ``` The dry run previews dependency and migration changes. A real update prints the source and target versions, backup directory, each migration action, installation result, and verification result. Commit or back up the application before upgrading. # Complete CLI command reference This section is generated from the installed `@wrnexus/cli@0.8.7` executable. It is the canonical command inventory for this release. ```text wrnexus — WrNexus CLI Usage: wrnexus dev [app-dir] [--port=3000] [--host=::] Start the development server (live reload) wrnexus dev [app-dir] --services Start local production-service simulators with the app wrnexus dev [app-dir] --production-runtime wrnexus dev [app-dir] --services [--services-port=3099] [--services-http] Rebuild and reload the exact production artifact wrnexus build [app-dir] Build a production server bundle + assets wrnexus preview [app-dir] Serve the existing exact production output wrnexus create Scaffold a new app wrnexus workspace Scaffold a monorepo (apps/* + shared packages/*) wrnexus workspace add [--domain=name.localhost] Add an app to the current workspace wrnexus gateway [--port=3000] Serve every workspace app behind one port, routed by domain wrnexus production [workspace-dir] Build, migrate, and serve the complete production workspace wrnexus [workspace-dir] Run a configured workspace environment (development/staging/custom) wrnexus generate Scaffold a page | component | api | schema wrnexus generate routes | docker | mobile Generate routes or scaffold deployment targets wrnexus generate types [app-dir] Generate application-wide route/component/key types wrnexus routes [app-dir] Generate typed named routes wrnexus typecheck [app-dir] Generate types and check TypeScript plus every .wrn file wrnexus mobile add Install Capacitor or Expo native packages wrnexus mobile compile Compile .wrn pages into native Expo routes wrnexus native list List cross-platform native capabilities wrnexus native add Install capability packages for the configured mobile mode wrnexus eject Copy a Wire UI component into app/components wrnexus update [dir] [--latest] Upgrade deps, migrate project files, and verify the app wrnexus db Migrations: migrate | rollback | status | seed | generate | new wrnexus authz Authorization: list | generate | init [--dialect=sqlite|postgres|mysql] wrnexus test [level] [app-dir] [--watch] Run unit | component | api | browser | visual | accessibility | performance wrnexus profiles [app-dir] List config profiles (dev/prod/uat/…) and their env files wrnexus doctor [app-dir] [--fix] Check project health; optionally apply safe repairs wrnexus compatibility [app-dir] Inspect or explicitly upgrade behavior defaults wrnexus contracts [app-dir] Detect breaking boundary contract changes wrnexus security [app-dir] Audit ASVS controls, inspect headers, or run abuse tests wrnexus api [app-dir] Generate OpenAPI, docs, Postman, examples and SDKs wrnexus sdk generate [app-dir] Generate TypeScript, JavaScript, Java, Go, or Python SDK wrnexus deploy [app-dir] Generate docker | kubernetes | systemd | railway | render | fly wrnexus mcp [app-dir] Start the WRNexus MCP server over stdio wrnexus i18n [app-dir] Extract and audit translation keys wrnexus report [app-dir] [--file=app/pages/page.wrn] Create a sanitized reproduction bundle wrnexus playground [--port=4173] Start the shareable WRN compiler playground wrnexus config [app-dir] --explain Print the fully resolved profile configuration wrnexus analyze [app-dir] Inspect dist/build-report.json and performance budgets wrnexus explain [subject] [app-dir] Explain compiler and production build decisions wrnexus explain [app-dir] Explain route caching or permission enforcement wrnexus inspect [app-dir] Inspect packages | plugins | routes | assets | runtimes | styles | migrations | bundle wrnexus inspect component [app-dir] Inspect a component's typed public contract wrnexus generate system Scaffold a complete framework-native package Profiles: pass --profile= to dev/build/db (or set WRNEXUS_PROFILE) to load that profile's .env cascade and config overrides. e.g. wrnexus dev --profile=uat Workspace environments: configure protocol, rootDomain, port, hostname, runtime (development|production), hmr, build, migrate, and profile in wrnexus.workspace.ts. CLI overrides: --port, --host, --build/--no-build, --migrate/--no-migrate, --profile, and --prepare-only. Update options: --dry-run previews changes; --no-verify skips post-update check/build. ``` ## CLI workflows with expected output Commands below are copy-ready. Paths, ports, counts, and timings vary by project; the shown output identifies the success condition to check. ### Create, check, build, and preview an application ```bash bunx @wrnexus/cli@0.8.7 create my-app cd my-app bun install bunx wrnexus typecheck . bunx wrnexus build . bunx wrnexus preview . --port=3000 ``` Expected output: ```text ✓ Application types are valid ✓ Runtime: .../dist/reactive.js ✓ Styles: .../dist/styles.css ✓ Server: .../dist/server.js Run it: bun .../dist/server.js ``` ### Generate framework files and committed application types ```bash bunx wrnexus generate page Dashboard bunx wrnexus generate component status-card bunx wrnexus generate api health bunx wrnexus generate schema account bunx wrnexus generate routes bunx wrnexus generate types . ``` Expected output includes created source paths followed by: ```text ✓ Generated app/routes.gen.ts (... routes) ✓ Generated .../app/types/wrnexus.generated.d.ts (... routes, ... components) ``` ### Run development and production-runtime modes ```bash bunx wrnexus dev . --port=3000 bunx wrnexus dev . --services --services-port=3099 bunx wrnexus dev . --production-runtime --port=3000 ``` Expected output reports the active profile, discovered plugins/routes, generated cache path, and listening URL. Production-runtime mode rebuilds and reloads the exact production artifact. ### Database lifecycle ```bash bunx wrnexus db status bunx wrnexus db new create_accounts --from-models bunx wrnexus db migrate bunx wrnexus db generate bunx wrnexus db seed ``` Expected output identifies the selected profile/dialect, migration state, applied migration names, and generated typed query files. Run `db rollback` only when intentionally reverting the latest migration. ### Diagnose, inspect, and enforce contracts ```bash bunx wrnexus doctor . bunx wrnexus typecheck . bunx wrnexus compatibility check . bunx wrnexus contracts check . bunx wrnexus security audit . bunx wrnexus inspect packages . bunx wrnexus inspect routes . bunx wrnexus inspect component Navbar . bunx wrnexus analyze . ``` Successful checks exit with status 0. Inspect commands print the resolved package, route, component, asset, runtime, style, migration, or bundle contract without changing the application. ### Tests, API artifacts, SDKs, and deployment manifests ```bash bunx wrnexus test unit . bunx wrnexus test component . bunx wrnexus test api . bunx wrnexus test browser . bunx wrnexus api generate . bunx wrnexus api docs . bunx wrnexus sdk generate typescript . bunx wrnexus deploy docker . ``` Test commands return the underlying suite summary and a nonzero status on failure. API/SDK/deploy commands report each generated artifact; review and commit generated files after validation. ### Workspaces and environments ```bash bunx wrnexus workspace company-platform cd company-platform bunx wrnexus workspace add reports --domain=reports.localhost bunx wrnexus gateway --port=3000 bunx wrnexus production . --prepare-only bunx wrnexus staging . ``` Expected output lists created apps, domain mappings, selected environment/profile, build and migration decisions, and the gateway address. ### Internationalization, native targets, MCP, and support bundles ```bash bunx wrnexus i18n extract . bunx wrnexus i18n validate . bunx wrnexus generate mobile bunx wrnexus mobile compile bunx wrnexus native list bunx wrnexus mcp . bunx wrnexus report . --file=app/pages/index.wrn ``` Expected output identifies extracted/validated translation keys, generated native routes, available capabilities, MCP stdio startup, or the sanitized reproduction archive. MCP uses stdio, so protocol messages—not a web URL—are its normal runtime output. ### Safe upgrades ```bash bunx wrnexus update . --latest --dry-run bunx wrnexus update . --latest ``` The dry run previews dependency and migration changes. A real update prints the source and target versions, backup directory, each migration action, installation result, and verification result. Commit or back up the application before upgrading. # Complete CLI command reference This section is generated from the installed `@wrnexus/cli@0.8.8` executable. It is the canonical command inventory for this release. ```text wrnexus — WrNexus CLI Usage: wrnexus dev [app-dir] [--port=3000] [--host=::] Start the development server (live reload) wrnexus dev [app-dir] --services Start local production-service simulators with the app wrnexus dev [app-dir] --production-runtime wrnexus dev [app-dir] --services [--services-port=3099] [--services-http] Rebuild and reload the exact production artifact wrnexus build [app-dir] Build a production server bundle + assets wrnexus preview [app-dir] Serve the existing exact production output wrnexus create Scaffold a new app wrnexus workspace Scaffold a monorepo (apps/* + shared packages/*) wrnexus workspace add [--domain=name.localhost] Add an app to the current workspace wrnexus gateway [--port=3000] Serve every workspace app behind one port, routed by domain wrnexus production [workspace-dir] Build, migrate, and serve the complete production workspace wrnexus [workspace-dir] Run a configured workspace environment (development/staging/custom) wrnexus generate Scaffold a page | component | api | schema wrnexus generate routes | docker | mobile Generate routes or scaffold deployment targets wrnexus generate types [app-dir] Generate application-wide route/component/key types wrnexus routes [app-dir] Generate typed named routes wrnexus typecheck [app-dir] Generate types and check TypeScript plus every .wrn file wrnexus mobile add Install Capacitor or Expo native packages wrnexus mobile compile Compile .wrn pages into native Expo routes wrnexus native list List cross-platform native capabilities wrnexus native add Install capability packages for the configured mobile mode wrnexus eject Copy a Wire UI component into app/components wrnexus update [dir] [--latest] Upgrade deps, migrate project files, and verify the app wrnexus db Migrations: migrate | rollback | status | seed | generate | new wrnexus authz Authorization: list | generate | init [--dialect=sqlite|postgres|mysql] wrnexus test [level] [app-dir] [--watch] Run unit | component | api | browser | visual | accessibility | performance wrnexus profiles [app-dir] List config profiles (dev/prod/uat/…) and their env files wrnexus doctor [app-dir] [--fix] Check project health; optionally apply safe repairs wrnexus compatibility [app-dir] Inspect or explicitly upgrade behavior defaults wrnexus contracts [app-dir] Detect breaking boundary contract changes wrnexus security [app-dir] Audit ASVS controls, inspect headers, or run abuse tests wrnexus api [app-dir] Generate OpenAPI, docs, Postman, examples and SDKs wrnexus sdk generate [app-dir] Generate TypeScript, JavaScript, Java, Go, or Python SDK wrnexus deploy [app-dir] Generate docker | kubernetes | systemd | railway | render | fly wrnexus mcp [app-dir] Start the WRNexus MCP server over stdio wrnexus i18n [app-dir] Extract and audit translation keys wrnexus report [app-dir] [--file=app/pages/page.wrn] Create a sanitized reproduction bundle wrnexus playground [--port=4173] Start the shareable WRN compiler playground wrnexus config [app-dir] --explain Print the fully resolved profile configuration wrnexus analyze [app-dir] Inspect dist/build-report.json and performance budgets wrnexus explain [subject] [app-dir] Explain compiler and production build decisions wrnexus explain [app-dir] Explain route caching or permission enforcement wrnexus inspect [app-dir] Inspect packages | plugins | routes | assets | runtimes | styles | migrations | bundle wrnexus inspect component [app-dir] Inspect a component's typed public contract wrnexus generate system Scaffold a complete framework-native package Profiles: pass --profile= to dev/build/db (or set WRNEXUS_PROFILE) to load that profile's .env cascade and config overrides. e.g. wrnexus dev --profile=uat Workspace environments: configure protocol, rootDomain, port, hostname, runtime (development|production), hmr, build, migrate, and profile in wrnexus.workspace.ts. CLI overrides: --port, --host, --build/--no-build, --migrate/--no-migrate, --profile, and --prepare-only. Update options: --dry-run previews changes; --no-verify skips post-update check/build. ``` ## CLI workflows with expected output Commands below are copy-ready. Paths, ports, counts, and timings vary by project; the shown output identifies the success condition to check. ### Create, check, build, and preview an application ```bash bunx @wrnexus/cli@0.8.8 create my-app cd my-app bun install bunx wrnexus typecheck . bunx wrnexus build . bunx wrnexus preview . --port=3000 ``` Expected output: ```text ✓ Application types are valid ✓ Runtime: .../dist/reactive.js ✓ Styles: .../dist/styles.css ✓ Server: .../dist/server.js Run it: bun .../dist/server.js ``` ### Generate framework files and committed application types ```bash bunx wrnexus generate page Dashboard bunx wrnexus generate component status-card bunx wrnexus generate api health bunx wrnexus generate schema account bunx wrnexus generate routes bunx wrnexus generate types . ``` Expected output includes created source paths followed by: ```text ✓ Generated app/routes.gen.ts (... routes) ✓ Generated .../app/types/wrnexus.generated.d.ts (... routes, ... components) ``` ### Run development and production-runtime modes ```bash bunx wrnexus dev . --port=3000 bunx wrnexus dev . --services --services-port=3099 bunx wrnexus dev . --production-runtime --port=3000 ``` Expected output reports the active profile, discovered plugins/routes, generated cache path, and listening URL. Production-runtime mode rebuilds and reloads the exact production artifact. ### Database lifecycle ```bash bunx wrnexus db status bunx wrnexus db new create_accounts --from-models bunx wrnexus db migrate bunx wrnexus db generate bunx wrnexus db seed ``` Expected output identifies the selected profile/dialect, migration state, applied migration names, and generated typed query files. Run `db rollback` only when intentionally reverting the latest migration. ### Diagnose, inspect, and enforce contracts ```bash bunx wrnexus doctor . bunx wrnexus typecheck . bunx wrnexus compatibility check . bunx wrnexus contracts check . bunx wrnexus security audit . bunx wrnexus inspect packages . bunx wrnexus inspect routes . bunx wrnexus inspect component Navbar . bunx wrnexus analyze . ``` Successful checks exit with status 0. Inspect commands print the resolved package, route, component, asset, runtime, style, migration, or bundle contract without changing the application. ### Tests, API artifacts, SDKs, and deployment manifests ```bash bunx wrnexus test unit . bunx wrnexus test component . bunx wrnexus test api . bunx wrnexus test browser . bunx wrnexus api generate . bunx wrnexus api docs . bunx wrnexus sdk generate typescript . bunx wrnexus deploy docker . ``` Test commands return the underlying suite summary and a nonzero status on failure. API/SDK/deploy commands report each generated artifact; review and commit generated files after validation. ### Workspaces and environments ```bash bunx wrnexus workspace company-platform cd company-platform bunx wrnexus workspace add reports --domain=reports.localhost bunx wrnexus gateway --port=3000 bunx wrnexus production . --prepare-only bunx wrnexus staging . ``` Expected output lists created apps, domain mappings, selected environment/profile, build and migration decisions, and the gateway address. ### Internationalization, native targets, MCP, and support bundles ```bash bunx wrnexus i18n extract . bunx wrnexus i18n validate . bunx wrnexus generate mobile bunx wrnexus mobile compile bunx wrnexus native list bunx wrnexus mcp . bunx wrnexus report . --file=app/pages/index.wrn ``` Expected output identifies extracted/validated translation keys, generated native routes, available capabilities, MCP stdio startup, or the sanitized reproduction archive. MCP uses stdio, so protocol messages—not a web URL—are its normal runtime output. ### Safe upgrades ```bash bunx wrnexus update . --latest --dry-run bunx wrnexus update . --latest ``` The dry run previews dependency and migration changes. A real update prints the source and target versions, backup directory, each migration action, installation result, and verification result. Commit or back up the application before upgrading. Release: WRNexusJS 0.8.8 # Complete CLI command reference This section is generated from the installed `@wrnexus/cli@0.8.8` executable. It is the canonical command inventory for this release. ```text wrnexus — WrNexus CLI Usage: wrnexus dev [app-dir] [--port=3000] [--host=::] Start the development server (live reload) wrnexus dev [app-dir] --services Start local production-service simulators with the app wrnexus dev [app-dir] --production-runtime wrnexus dev [app-dir] --services [--services-port=3099] [--services-http] Rebuild and reload the exact production artifact wrnexus build [app-dir] Build a production server bundle + assets wrnexus preview [app-dir] Serve the existing exact production output wrnexus create Scaffold a new app wrnexus workspace Scaffold a monorepo (apps/* + shared packages/*) wrnexus workspace add [--domain=name.localhost] Add an app to the current workspace wrnexus gateway [--port=3000] Serve every workspace app behind one port, routed by domain wrnexus production [workspace-dir] Build, migrate, and serve the complete production workspace wrnexus [workspace-dir] Run a configured workspace environment (development/staging/custom) wrnexus generate Scaffold a page | component | api | schema wrnexus generate routes | docker | mobile Generate routes or scaffold deployment targets wrnexus generate types [app-dir] Generate application-wide route/component/key types wrnexus routes [app-dir] Generate typed named routes wrnexus typecheck [app-dir] Generate types and check TypeScript plus every .wrn file wrnexus mobile add Install Capacitor or Expo native packages wrnexus mobile compile Compile .wrn pages into native Expo routes wrnexus native list List cross-platform native capabilities wrnexus native add Install capability packages for the configured mobile mode wrnexus eject Copy a Wire UI component into app/components wrnexus update [dir] [--latest] Upgrade deps, migrate project files, and verify the app wrnexus db Migrations: migrate | rollback | status | seed | generate | new wrnexus authz Authorization: list | generate | init [--dialect=sqlite|postgres|mysql] wrnexus test [level] [app-dir] [--watch] Run unit | component | api | browser | visual | accessibility | performance wrnexus profiles [app-dir] List config profiles (dev/prod/uat/…) and their env files wrnexus doctor [app-dir] [--fix] Check project health; optionally apply safe repairs wrnexus compatibility [app-dir] Inspect or explicitly upgrade behavior defaults wrnexus contracts [app-dir] Detect breaking boundary contract changes wrnexus security [app-dir] Audit ASVS controls, inspect headers, or run abuse tests wrnexus api [app-dir] Generate OpenAPI, docs, Postman, examples and SDKs wrnexus sdk generate [app-dir] Generate TypeScript, JavaScript, Java, Go, or Python SDK wrnexus deploy [app-dir] Generate docker | kubernetes | systemd | railway | render | fly wrnexus mcp [app-dir] Start the WRNexus MCP server over stdio wrnexus i18n [app-dir] Extract and audit translation keys wrnexus report [app-dir] [--file=app/pages/page.wrn] Create a sanitized reproduction bundle wrnexus playground [--port=4173] Start the shareable WRN compiler playground wrnexus config [app-dir] --explain Print the fully resolved profile configuration wrnexus analyze [app-dir] Inspect dist/build-report.json and performance budgets wrnexus explain [subject] [app-dir] Explain compiler and production build decisions wrnexus explain [app-dir] Explain route caching or permission enforcement wrnexus inspect [app-dir] Inspect packages | plugins | routes | assets | runtimes | styles | migrations | bundle wrnexus inspect component [app-dir] Inspect a component's typed public contract wrnexus generate system Scaffold a complete framework-native package Profiles: pass --profile= to dev/build/db (or set WRNEXUS_PROFILE) to load that profile's .env cascade and config overrides. e.g. wrnexus dev --profile=uat Workspace environments: configure protocol, rootDomain, port, hostname, runtime (development|production), hmr, build, migrate, and profile in wrnexus.workspace.ts. CLI overrides: --port, --host, --build/--no-build, --migrate/--no-migrate, --profile, and --prepare-only. Update options: --dry-run previews changes; --no-verify skips post-update check/build. ``` ## CLI workflows with expected output Commands below are copy-ready. Paths, ports, counts, and timings vary by project; the shown output identifies the success condition to check. ### Create, check, build, and preview an application ```bash bunx @wrnexus/cli@0.8.8 create my-app cd my-app bun install bunx wrnexus typecheck . bunx wrnexus build . bunx wrnexus preview . --port=3000 ``` Expected output: ```text ✓ Application types are valid ✓ Runtime: .../dist/reactive.js ✓ Styles: .../dist/styles.css ✓ Server: .../dist/server.js Run it: bun .../dist/server.js ``` ### Generate framework files and committed application types ```bash bunx wrnexus generate page Dashboard bunx wrnexus generate component status-card bunx wrnexus generate api health bunx wrnexus generate schema account bunx wrnexus generate routes bunx wrnexus generate types . ``` Expected output includes created source paths followed by: ```text ✓ Generated app/routes.gen.ts (... routes) ✓ Generated .../app/types/wrnexus.generated.d.ts (... routes, ... components) ``` ### Run development and production-runtime modes ```bash bunx wrnexus dev . --port=3000 bunx wrnexus dev . --services --services-port=3099 bunx wrnexus dev . --production-runtime --port=3000 ``` Expected output reports the active profile, discovered plugins/routes, generated cache path, and listening URL. Production-runtime mode rebuilds and reloads the exact production artifact. ### Database lifecycle ```bash bunx wrnexus db status bunx wrnexus db new create_accounts --from-models bunx wrnexus db migrate bunx wrnexus db generate bunx wrnexus db seed ``` Expected output identifies the selected profile/dialect, migration state, applied migration names, and generated typed query files. Run `db rollback` only when intentionally reverting the latest migration. ### Diagnose, inspect, and enforce contracts ```bash bunx wrnexus doctor . bunx wrnexus typecheck . bunx wrnexus compatibility check . bunx wrnexus contracts check . bunx wrnexus security audit . bunx wrnexus inspect packages . bunx wrnexus inspect routes . bunx wrnexus inspect component Navbar . bunx wrnexus analyze . ``` Successful checks exit with status 0. Inspect commands print the resolved package, route, component, asset, runtime, style, migration, or bundle contract without changing the application. ### Tests, API artifacts, SDKs, and deployment manifests ```bash bunx wrnexus test unit . bunx wrnexus test component . bunx wrnexus test api . bunx wrnexus test browser . bunx wrnexus api generate . bunx wrnexus api docs . bunx wrnexus sdk generate typescript . bunx wrnexus deploy docker . ``` Test commands return the underlying suite summary and a nonzero status on failure. API/SDK/deploy commands report each generated artifact; review and commit generated files after validation. ### Workspaces and environments ```bash bunx wrnexus workspace company-platform cd company-platform bunx wrnexus workspace add reports --domain=reports.localhost bunx wrnexus gateway --port=3000 bunx wrnexus production . --prepare-only bunx wrnexus staging . ``` Expected output lists created apps, domain mappings, selected environment/profile, build and migration decisions, and the gateway address. ### Internationalization, native targets, MCP, and support bundles ```bash bunx wrnexus i18n extract . bunx wrnexus i18n validate . bunx wrnexus generate mobile bunx wrnexus mobile compile bunx wrnexus native list bunx wrnexus mcp . bunx wrnexus report . --file=app/pages/index.wrn ``` Expected output identifies extracted/validated translation keys, generated native routes, available capabilities, MCP stdio startup, or the sanitized reproduction archive. MCP uses stdio, so protocol messages—not a web URL—are its normal runtime output. ### Safe upgrades ```bash bunx wrnexus update . --latest --dry-run bunx wrnexus update . --latest ``` The dry run previews dependency and migration changes. A real update prints the source and target versions, backup directory, each migration action, installation result, and verification result. Commit or back up the application before upgrading. # Canonical documentation locations - Framework and package documentation: https://wrnexusjs.dev/ - Interactive UI component showcase and examples: https://component.wrnexusjs.dev/ # Installed package documentation The following README files and declarations come from the installed private 0.8.8 release. ## @wrnexus/ai Documentation URL: https://wrnexusjs.dev/packages/ai # @wrnexus/ai Provider-neutral AI orchestration for OpenAI, Anthropic, Google and local OpenAI-compatible models, with streaming, structured output, tools, embeddings, vector search/RAG, conversation persistence, templates, guardrails, usage events, fallback, rate limits and evaluation reports. > A tiny, zero-dependency Claude (Anthropic) client for WrNexus apps — generate and stream text with Claude from any server-side code. Part of the **WrNexus** framework — an SSR-first, Bun-native full-stack web framework. ## Overview `@wrnexus/ai` is a thin, dependency-free wrapper over the Anthropic **Messages API**, built on `fetch` (Bun-native, no SDK). Use it in API routes, jobs, or middleware to call Claude. It defaults to the most capable model, **`claude-opus-4-8`**, reads your key from `ANTHROPIC_API_KEY`, and supports both one-shot generation and streaming. ## Installation ```bash bun add @wrnexus/ai ``` > Private package — the machine must be authenticated to the `wrnexus` npm org > (a read token in `~/.npmrc`). Requires **Bun** (Node is not supported). Set your key in the environment (e.g. `.env`): ``` ANTHROPIC_API_KEY=sk-ant-... ``` ## API ### `createAI(config?)` Creates a client. The key is read at call time, so it's safe to create at import. ```ts import { createAI } from "@wrnexus/ai"; const ai = createAI(); // or createAI({ apiKey, model, maxTokens, baseURL, version }) ``` `AIConfig` fields (all optional): | Field | Default | Description | | ----------- | --------------------------- | -------------------------- | | `apiKey` | `ANTHROPIC_API_KEY` | Anthropic API key | | `model` | `"claude-opus-4-8"` | Model id | | `maxTokens` | `4096` | Default max output tokens | | `baseURL` | `https://api.anthropic.com` | API base URL | | `version` | `"2023-06-01"` | `anthropic-version` header | ### `ai.generate(prompt, opts?): Promise` One-shot text generation. `prompt` is a string or a `Message[]` history. ```ts const text = await ai.generate("Write a haiku about Bun."); const reply = await ai.generate( [ { role: "user", content: "My name is Ada." }, { role: "assistant", content: "Hi Ada!" }, { role: "user", content: "What's my name?" }, ], { system: "You are concise." }, ); ``` ### `ai.stream(prompt, opts?): AsyncGenerator` Yields text deltas as they arrive. ```ts for await (const chunk of ai.stream("Tell me a story.")) { process.stdout.write(chunk); } ``` ### `ai.streamResponse(prompt, opts?): Response` Returns a streaming `text/plain` `Response` — drop it straight into an API route. ```ts // app/api/chat.ts import { createAI } from "@wrnexus/ai"; const ai = createAI(); export const POST = async (ctx) => { const { prompt } = await ctx.req.json(); return ai.streamResponse(prompt); }; ``` ### `GenerateOptions` | Option | Type | Description | | ----------- | ------------------------------------------------- | ---------------------------------------------------- | | `system` | `string` | System prompt | | `model` | `string` | Override the model for this call | | `maxTokens` | `number` | Override max output tokens | | `thinking` | `boolean` | Enable adaptive extended thinking (deeper reasoning) | | `effort` | `"low" \| "medium" \| "high" \| "xhigh" \| "max"` | Reasoning effort / token spend | | `messages` | `Message[]` | Full history — supersedes `prompt` | | `signal` | `AbortSignal` | Cancel the request | > `temperature` / `top_p` are intentionally **not** exposed — the current Claude > models reject them (400). Steer output with prompting instead. ### `AIError` Thrown on non-2xx responses or a model refusal. Carries `.status` and `.type` (e.g. `"authentication_error"`, `"rate_limit_error"`, `"refusal"`). ```ts import { AIError } from "@wrnexus/ai"; try { await ai.generate("..."); } catch (e) { if (e instanceof AIError && e.type === "rate_limit_error") { /* back off */ } } ``` ### Multi-provider client `createAIClient` adds named-provider selection and fallback, capability discovery, validated JSON output, validated tool execution, abort-aware exponential retries, and per-provider circuit breakers. Attempt events intentionally contain metadata only: prompts, credentials, and raw model responses are never passed to telemetry. ```ts import { anthropicProvider, createAIClient } from "@wrnexus/ai"; const ai = createAIClient({ providers: [anthropicProvider()], retry: { attempts: 3, baseDelayMs: 100, maxDelayMs: 2_000 }, circuitBreaker: { failureThreshold: 5, resetAfterMs: 30_000 }, }); const result = await ai.generateObject<{ title: string }>("Return a JSON title", { validate: (value): value is { title: string } => typeof value === "object" && value !== null && "title" in value, }); ``` Providers can return normalized `usage` (`inputTokens`, `outputTokens`, `totalTokens`, and `costUsd`) and `toolCalls`. Use `executeTools` with a named, validated tool registry; unknown tools and invalid arguments are rejected before application code runs. `deterministicAIProvider` supplies ordered or computed offline responses for tests and examples without API keys or network calls. ## Usage ### Return generated JSON from an API route ```ts // app/api/summarize.ts — summarize posted text import { createAI } from "@wrnexus/ai"; const ai = createAI(); export const POST = async (ctx) => { const { text } = await ctx.req.json().catch(() => ({})); if (!text) return Response.json({ error: "Provide 'text'." }, { status: 400 }); const summary = await ai.generate(`Summarize in one sentence:\n\n${text}`, { system: "You are a precise summarizer.", }); return Response.json({ summary }); }; ``` ### Stream a chat response to the browser ```ts // app/api/chat.ts import { createAI } from "@wrnexus/ai"; const ai = createAI({ model: "claude-sonnet-5" }); export const POST = async (ctx) => { const { messages } = await ctx.req.json(); return ai.streamResponse(messages, { system: "Answer using concise Markdown.", maxTokens: 1_500, }); }; ``` ## Requirements / Notes - **Bun-only.** Uses `fetch`, `ReadableStream`, `TextDecoder`/`TextEncoder`, and reads `ANTHROPIC_API_KEY` from `Bun.env` (falls back to `process.env`). - **Zero dependencies** — no `@anthropic-ai/sdk`; talks to the Messages API directly. - Defaults to `claude-opus-4-8`. Pass `{ model }` for a different model (e.g. `"claude-sonnet-5"` for speed/cost, `"claude-haiku-4-5"` for the fastest). ### Exported TypeScript declarations ```ts export { A as AI, a as AIAttemptEvent, b as AICircuitBreakerOptions, c as AIClient, d as AIClientOptions, e as AIConfig, f as AIError, AIGuardrail, g as AIProvider, h as AIProviderCapabilities, i as AIResult, j as AIRetryOptions, k as AITool, l as AIToolCall, m as AIUsage, ConversationStore, D as DeterministicAIProviderOptions, E as Effort, EmbeddingProvider, G as GenerateOptions, HttpAIProviderOptions, M as Message, R as Role, VectorMatch, VectorRecord, VectorStore, n as aiProvider, aiRateLimiter, o as anthropicProvider, p as createAI, q as createAIClient, createRagPipeline, r as deterministicAIProvider, evaluateAI, googleAIProvider, guardedProvider, localAIProvider, maxPromptLength, memoryConversationStore, memoryVectorStore, openAIEmbeddings, openAIProvider, promptTemplate } from './platform.js'; ``` --- ## @wrnexus/auth Documentation URL: https://wrnexusjs.dev/packages/auth # @wrnexus/auth Framework-native authentication, identity, account-security, and session management for WRNexusJS. ## Capabilities - Password registration, login, recovery, reset, and authenticated password changes - Email, phone, and username identities with verification and generic resend responses - Magic links and passwordless email/SMS OTP login - MFA transactions using verified email OTP, verified SMS OTP, TOTP, or recovery codes - RFC 6238 TOTP with counter replay protection - One-use recovery codes; regeneration invalidates previous unused codes - Passkey/WebAuthn registration and strong passwordless sign-in through a provider contract - OAuth account linking and provider sign-in - Invitations, session rotation, idle and absolute expiry, revocation, and trusted devices - Deny-by-default audited support impersonation - Adaptive risk scoring, CAPTCHA escalation, temporary lockout, and optional login alerts - Memory and SQL stores - Optional encryption-keyring protection for TOTP and OAuth secrets - Automatic API routes, middleware, browser schemas, components, runtime, migrations, and DevToolbar checks Passkeys are a strong sign-in method. They are not currently exposed as a selectable second step in `TwoFactorChallenge`; the implemented MFA methods are email OTP, SMS OTP, TOTP, and recovery codes. ## Install ```bash bun add @wrnexus/auth ``` WRNexusJS discovers the package automatically. Do not copy package components, client scripts, schemas, or standard `/api/auth/*` route files into the application. ## Default configuration Create the engine: ```ts // app/lib/auth.ts import { createAuthEngine, MemoryAuthStore } from "@wrnexus/auth"; export const auth = createAuthEngine({ store: new MemoryAuthStore(), secret: process.env.AUTH_SECRET!, issuer: "My application", onSignedIn(ctx, returnTo) { const safe = returnTo?.startsWith("/") && !returnTo.startsWith("//") ? returnTo : "/account"; return Response.redirect(new URL(safe, ctx.url), 303); }, onSignedOut(ctx) { return Response.redirect(new URL("/sign-in", ctx.url), 303); }, onSuccessfulSignUp() { return { autoSignIn: true, redirectTo: "/account", }; }, delivery: { async send(message) { // Queue email/SMS through your provider. Never log message.code or message.token. }, }, }); ``` Authentication behavior belongs in this engine definition: delivery, token URL mapping, successful sign-in/sign-out responses, password policy, risk thresholds, MFA, passkeys, and auditing can all be configured in one server-only location. The older `config.auth.onSignedIn` and `config.auth.onSignedOut` fields remain supported as compatibility overrides, but new applications should configure these hooks on `createAuthEngine`. ### Successful signup behavior Without `onSuccessfulSignUp`, a successful package registration redirects to `/sign-in`. To sign in immediately after registration: ```ts onSuccessfulSignUp(ctx, user) { return { autoSignIn: true, redirectTo: "/account", }; } ``` Automatic sign-in runs the normal login policy. It does not bypass required email or phone verification, CAPTCHA, MFA, account status, or risk checks. The hook may also return a `Response` for a completely custom HTTP result, or return `{ redirectTo: "/welcome" }` to redirect without creating a session. Register it through application configuration: ```ts // wrnexus.config.ts import type { AuthConfig } from "@wrnexus/auth"; import type { AppConfig } from "@wrnexus/styles"; import { auth } from "./app/lib/auth.ts"; const config = { auth: { engine: auth, routes: true, middleware: true, migrations: false, }, } satisfies AppConfig & { auth: AuthConfig }; export default config; ``` That configuration automatically activates package routes, auth-session middleware, components, browser validation schemas, and the auth client runtime. `setDefaultAuthEngine()` remains available only for advanced manual integrations and tests. ## SQL production configuration ```ts import { createAuthEngine, SqlAuthStore } from "@wrnexus/auth"; import { getDb } from "@wrnexus/db"; export const auth = createAuthEngine({ store: new SqlAuthStore(getDb()), secret: process.env.AUTH_SECRET!, }); ``` ```ts export default { db: { // Application database configuration. }, auth: { engine: auth, routes: true, middleware: true, migrations: true, }, }; ``` The package contributes both ordered migrations: ```text 001_auth.sql 002_auth_otp_purpose.sql ``` Migrations are enabled automatically only when `auth.engine` and a default `config.db` are present. Set `auth.migrations` explicitly when an application needs different behavior. ## Delivered action URLs By default, the engine builds links from the supplied `baseUrl` and token purpose. Applications can map those links to their own page structure without replacing package APIs: ```ts const auth = createAuthEngine({ store: new SqlAuthStore(getDb()), secret: process.env.AUTH_SECRET!, tokenUrl({ purpose, token, baseUrl }) { if (!baseUrl) return undefined; const paths = { "verify-email": `/verify-email?token=${encodeURIComponent(token)}`, "verify-phone": `/verify-phone?token=${encodeURIComponent(token)}`, "password-reset": `/recover/reset?token=${encodeURIComponent(token)}`, "magic-link": `/magic-link?token=${encodeURIComponent(token)}`, invite: `/invitation?token=${encodeURIComponent(token)}`, }; const path = paths[purpose as keyof typeof paths]; return path ? new URL(path, baseUrl).toString() : undefined; }, }); ``` Returning `undefined` intentionally omits the URL while still delivering the raw token. The callback runs only in trusted server code. ## Built-in validation Every packaged auth form has a built-in `@wrnexus/validation` schema. The same resolved schema is used by the browser and the package API handler. Default use requires no `app/schemas` files: ```wrn ``` To customize one schema, extend the package default and register only that override: ```ts // app/schemas/custom-password-request.ts import { authSchemas } from "@wrnexus/auth"; import { v } from "@wrnexus/validation"; export default authSchemas.passwordResetRequest.extend({ identifier: v .string() .trim() .required("Enter your registered email address") .email("Enter a valid registered email address"), }); ``` ```ts import customPasswordRequest from "./app/schemas/custom-password-request.ts"; export default { auth: { engine: auth, schemas: { passwordResetRequest: customPasswordRequest, }, }, }; ``` `` can keep its default `schema="auth-password-request"`. The plugin automatically publishes the overridden browser descriptor under that same built-in schema ID. All other forms continue using package defaults. ## Route controls Use a boolean to enable or disable all package routes: ```ts auth: { engine: auth, routes: true, } ``` Or control feature groups: ```ts routes: { enabled: true, registration: true, login: true, verification: true, password: true, invitations: true, magicLink: true, otp: true, mfa: true, sessions: true, impersonation: false, passkeys: true, } ``` Application routes have normal framework precedence. Disable a package group only when the application intentionally owns every endpoint in that group; no `excludeRoutes` list is required. ## Package endpoints ```text POST /api/auth/register POST /api/auth/login POST /api/auth/logout POST /api/auth/verification/request GET|POST /api/auth/verify/email POST /api/auth/verify/phone POST /api/auth/password/request POST /api/auth/password/reset POST /api/auth/password/change POST /api/auth/invitations/accept POST /api/auth/magic-link/request GET|POST /api/auth/magic-link POST /api/auth/otp/login/request POST /api/auth/otp/login/complete POST /api/auth/otp POST /api/auth/otp/verify POST /api/auth/totp/setup POST /api/auth/totp/confirm POST /api/auth/totp/disable POST /api/auth/recovery-codes POST /api/auth/mfa/otp POST /api/auth/mfa/complete GET /api/auth/sessions POST /api/auth/sessions/revoke POST /api/auth/impersonation/start POST /api/auth/impersonation/stop POST /api/auth/passkeys/register/options POST /api/auth/passkeys/register/verify POST /api/auth/passkeys/login/options POST /api/auth/passkeys/login/verify ``` Each URL uses a route-specific module, so rewritten framework request URLs cannot make the handler fall through to a shared-dispatcher `404`. Unsafe package routes validate the framework CSRF token by default. Set `auth.csrf: false` only when an external API gateway provides an equivalent protection model. ## Components ```wrn ``` `identifier` is optional on verification components. Supply it when an unauthenticated verification page should support resending a token. The response remains generic whether the account exists or not. ## CAPTCHA and risk The HTTP handlers never trust a browser `captchaVerified` field. CAPTCHA completion is accepted only from server-populated `ctx.locals.captcha.success` or `ctx.locals.captchaVerified === true`. Rate limiting remains an application or gateway responsibility. Apply it to registration, login, reset, magic-link, OTP, verification, passkey, invitation, and impersonation endpoints. ## MFA 1. Password, OAuth, magic-link, or OTP login may return `code: "mfa-required"` with a short-lived `mfaToken`. 2. The response lists only methods actually available to that user. 3. Email/SMS MFA is offered only for verified linked identities. 4. `beginMfaOtp()` issues an MFA-bound OTP when needed. 5. `completeMfa()` consumes the one-time transaction and creates the session. ## Passkeys The browser runtime coordinates `navigator.credentials.create()` and `navigator.credentials.get()`. A configured server-side `PasskeyProvider` must verify the challenge, RP ID, origin, signature, user presence or verification, counter, and credential ownership. Multi-process deployments must provide a shared `PasskeyChallengeStore`; the default memory implementation is process-local. Missing passkey providers return a controlled `503` response rather than crashing the route. ## Protect long-lived secrets ```ts import { createAuthSecretProtector } from "@wrnexus/auth"; import { createKeyring } from "@wrnexus/encryption"; const keyring = createKeyring([ { id: "auth-2026-01", secret: process.env.AUTH_ENCRYPTION_KEY!, active: true, }, ]); const auth = createAuthEngine({ store: new SqlAuthStore(getDb()), secret: process.env.AUTH_SECRET!, secretProtector: createAuthSecretProtector(keyring), }); ``` TOTP seeds and OAuth access/refresh tokens are protected before persistence. Keep old keys available during rotation. ## Custom HTTP integration `createAuthHttpHandlers()` remains available for custom route paths or response behavior. Prefer package routes for standard flows; copied application API files are unnecessary. ## Development ```bash bun run auth:dev bun run validate:auth ``` Read [SECURITY.md](./SECURITY.md) before production deployment. ## Package-owned UI blocks and route helpers Authentication forms continue to compose `@wrnexus/ui` inputs, buttons, cards, alerts, badges, avatars, and PIN controls. The package also provides: - `` - `` - `` - complete sign-in, sign-up, MFA, passkey, recovery, account-status, session, and impersonation blocks Server helpers include `authRoute`, `authSuccess`, `authFailure`, `requireAuthUser`, `optionalAuthUser`, `currentAuthSession`, and `authComponentProps`. ### Exported TypeScript declarations ```ts import { A as AuthEngine } from './engine-BDu0-aZp.js'; export { c as createAuthEngine, i as inferIdentityType, n as normalizeEmail, a as normalizeIdentity, b as normalizePhone, d as normalizeUsername, p as publicUser, s as safeAuthReturnTo } from './engine-BDu0-aZp.js'; import { o as AuthRiskSignals, m as AuthRiskDecision, s as AuthSession, B as AuthUser } from './types-BhwPi3qr.js'; export { A as AUTH_SECURITY_EVENT_TYPES, a as AuthAccountStatus, b as AuthClock, c as AuthDeliveryMessage, d as AuthDeliveryProvider, e as AuthEngineOptions, f as AuthIdentity, g as AuthIdentityType, h as AuthImpersonationDecision, i as AuthMfaMethod, j as AuthPublicUser, k as AuthRandom, l as AuthResult, n as AuthRiskLevel, p as AuthSecretProtector, q as AuthSecurityEvent, r as AuthSecurityEventType, t as AuthSessionVerificationHandler, u as AuthSignedInHandler, v as AuthSignedOutHandler, U as AuthStore, w as AuthSuccessfulSignUpAction, x as AuthSuccessfulSignUpHandler, y as AuthTokenPurpose, z as AuthTokenUrlInput, C as AuthenticatedContext, K as KnownAuthSecurityEventType, L as LoginAttempt, D as LoginInput, V as MemoryPasskeyChallengeStore, O as OAuthAccount, E as OneTimeToken, F as OtpChallenge, P as PasskeyAuthenticationOptions, W as PasskeyChallengeKind, X as PasskeyChallengeRecord, Y as PasskeyChallengeStore, G as PasskeyCredential, H as PasskeyProvider, I as PasskeyRegistrationOptions, J as PasskeyVerificationResult, M as PasswordBreachProvider, N as PasswordCredential, R as RecoveryCodeRecord, Q as RegisterInput, T as TotpCredential, S as TrustedDevice, Z as assertPasskeyProvider } from './types-BhwPi3qr.js'; export { MemoryAuthStore } from './stores/memory.js'; export { SqlAuthStore } from './stores/sql.js'; export { AUTH_SESSION_KEY, authSession, clearAuthSession, establishAuthSession, getAuthSession, getAuthUser, isAuthenticatedContext, requireAuth } from './middleware.js'; export { A as AuthHttpOptions, a as AuthPasskeyHttpOptions, b as AuthSchemaOverrides, c as AuthSchemaSet, d as authBrowserSchemaDescriptors, e as authBrowserSchemaMap, f as authSchemas, g as authenticatorConfirmSchema, h as authenticatorDisableSchema, i as authenticatorSetupSchema, j as changePasswordSchema, k as createAuthHttpHandlers, l as emptyActionSchema, m as impersonationStartSchema, n as invitationAcceptSchema, o as loginSchema, p as magicLinkConsumeSchema, q as magicLinkRequestSchema, r as mfaOtpRequestSchema, s as mfaSchema, t as otpIssueSchema, u as otpLoginCompleteSchema, v as otpLoginRequestSchema, w as otpSchema, x as passkeyAuthenticationOptionsSchema, y as passkeyAuthenticationVerifySchema, z as passkeyRegistrationOptionsSchema, B as passkeyRegistrationVerifySchema, C as passwordResetRequestSchema, D as passwordResetSchema, E as recoveryCodesSchema, F as registerSchema, G as resolveAuthSchemas, H as sessionRevokeSchema, I as signUpSchema, J as verificationRequestSchema, K as verificationTokenSchema } from './index-B4uaD0Z3.js'; export { AuthAuditIssue, AuthConfig, AuthPluginOptions, AuthRoutesConfig, authComponentsDir, authPlugin } from './plugin.js'; export { DefaultAuthRouteOptions, clearDefaultAuthEngine, getDefaultAuthEngine, getDefaultAuthRouteOptions, getDefaultAuthSchemas, hasDefaultAuthEngine, setDefaultAuthEngine, setDefaultAuthRouteOptions, setDefaultAuthSchemas, tryGetDefaultAuthEngine } from './runtime.js'; export { createAuthSecretProtector } from './protector.js'; export { decodeBase32, encodeBase32, generateTotp, generateTotpSecret, totpUri, verifyTotp } from './totp/index.js'; import { Context } from '@wrnexus/core'; import '@wrnexus/oauth'; import '@wrnexus/db'; import '@wrnexus/validation'; import '@wrnexus/plugin'; import '@wrnexus/encryption'; interface RiskPolicy { captchaThreshold: number; mfaThreshold: number; blockThreshold: number; } declare function evaluateAuthRisk(signals?: AuthRiskSignals, policy?: RiskPolicy): AuthRiskDecision; type AuthRouteName = "signIn" | "signUp" | "signOut" | "forgotPassword" | "resetPassword" | "verifyEmail" | "verifyPhone" | "twoFactor" | "sessions" | "passkeys"; declare function authRoute(name: AuthRouteName, options?: { basePath?: string; overrides?: Partial>; }): string; declare function authSuccess>(data: T, init?: ResponseInit): Response; declare function authFailure(code: string, message: string, status?: number, details?: Record): Response; declare function requireAuthUser(ctx: Context): AuthUser; declare function optionalAuthUser(ctx: Context): AuthUser | null; declare function currentAuthSession(engine: AuthEngine, sessionId: string | undefined): Promise; declare function authComponentProps(input: Record, defaults?: { color?: string; size?: string; class?: string; }): Record; export { AuthEngine, AuthRiskDecision, AuthRiskSignals, type AuthRouteName, AuthSession, AuthUser, type RiskPolicy, authComponentProps, authFailure, authRoute, authSuccess, currentAuthSession, evaluateAuthRisk, optionalAuthUser, requireAuthUser }; ``` --- ## @wrnexus/authz Documentation URL: https://wrnexusjs.dev/packages/authz # @wrnexus/authz > Composable authorization for WrNexus — role-based (RBAC), policy-based (PBAC), and attribute-based (ABAC) access control that reduces to a boolean check plus an `authorize()` guard. Part of the **WrNexus** framework — an SSR-first, Bun-native full-stack web framework. ## Overview `@wrnexus/authz` is a small, server-side authorization toolkit. It gives you three interchangeable models — RBAC (roles → permissions), PBAC (policy predicates), and ABAC (attribute matchers) — that all collapse to a `boolean | Promise` decision. Wrap any decision in a `Middleware` guard (`authorize`, `requireRole`, `requirePermission`) to protect WrNexus routes. Reach for it whenever a route or action needs to be gated on who the user is, what roles they hold, or attributes of the user and the resource. It plugs into `@wrnexus/core` by reading `ctx.user` as the authorization subject. ## Installation ```bash bun add @wrnexus/authz ``` > Private package — the machine must be authenticated to the `wrnexus` npm org > (a read token in `~/.npmrc`). Requires **Bun** (Node is not supported). ## API The package has a single entry point (`@wrnexus/authz`) exporting the following. ### Types | Symbol | Description | | ---------------------------------- | --------------------------------------------------------------------------------------------- | | `Subject` | The authorized principal: `{ id?: string; roles?: string[]; [attribute: string]: unknown }`. | | `Rbac` | An RBAC checker: `{ can(subject, permission): boolean; permissionsFor(roles): Set }`. | | `Policy` | A predicate `(subject: S, resource?: R) => boolean \| Promise`. | ### RBAC #### `defineRbac(roles: Record): Rbac` Builds an RBAC checker from a role → permissions map. Supported permission forms: - `"*"` — grants every permission. - `"ns:*"` — namespace wildcard (e.g. `"post:*"` grants `"post:write"`). - `"role:"` — inherits all permissions of another role (resolved recursively, cycle-safe). The returned `Rbac` provides: - `can(subject, permission)` — `true` if any of `subject.roles` grants `permission` (honouring `*` and namespace wildcards). Returns `false` when the subject has no roles. - `permissionsFor(roles)` — the resolved `Set` of all permissions granted to a set of roles. #### `hasRole(subject: Subject | undefined, ...required: string[]): boolean` `true` if the subject holds **all** of the given roles. ### PBAC / ABAC combinators - `any(...policies: Policy[]): Policy` — allow if **any** policy passes (OR); awaits async policies. - `all(...policies: Policy[]): Policy` — allow only if **all** policies pass (AND); awaits async policies. - `attr(name: string, match: unknown | ((value: unknown) => boolean)): Policy` — ABAC helper that allows when `subject[name]` equals `match`, or when `match` is a function, when `match(value)` is truthy. ### Guards (middleware) Each guard returns a `@wrnexus/core` `Middleware`. A denied request short-circuits with `Response.json({ ok: false, error: "Forbidden" }, { status: 403 })`. - `authorize(policy: (ctx: Context) => boolean | Promise): Middleware` — runs `policy` against the request `Context`; calls `next()` when it resolves truthy, otherwise returns 403. - `requireRole(...roles: string[]): Middleware` — allows when `ctx.user` holds **any** of the listed roles. - `requirePermission(rbac: Rbac, permission: string): Middleware` — allows when `rbac.can(ctx.user, permission)` is `true`. ## Usage ### RBAC ```ts import { defineRbac, hasRole } from "@wrnexus/authz"; const rbac = defineRbac({ admin: ["*"], editor: ["post:read", "post:write"], viewer: ["post:read"], // role inheritance: lead gets everything an editor has, plus post:publish lead: ["role:editor", "post:publish"], }); const user = { id: "u1", roles: ["editor"] }; rbac.can(user, "post:write"); // true rbac.can(user, "post:delete"); // false rbac.permissionsFor(["lead"]); // Set { "post:read", "post:write", "post:publish" } hasRole(user, "editor"); // true ``` ### Guarding routes ```ts import { authorize, requireRole, requirePermission, defineRbac } from "@wrnexus/authz"; const rbac = defineRbac({ admin: ["*"], editor: ["post:read", "post:write"] }); // Only admins or editors app.get("/dashboard", requireRole("admin", "editor"), handler); // Requires a specific permission app.post("/posts", requirePermission(rbac, "post:write"), handler); // Arbitrary policy over the request context app.delete( "/posts/:id", authorize((ctx) => hasRole(ctx.user, "admin")), handler, ); ``` ### PBAC / ABAC policies ```ts import { any, all, attr, authorize, type Policy } from "@wrnexus/authz"; interface User { id: string; department?: string; roles?: string[]; } interface Post { authorId: string; } // Ownership policy (subject + resource) const ownsPost: Policy = (u, post) => u.id === post?.authorId; // ABAC: attribute equality, or a predicate const inEngineering = attr("department", "engineering"); const isVerified = attr("verified", (v) => v === true); // Compose: allow if the user owns the post OR is in engineering AND verified const canEdit = any(ownsPost, all(inEngineering, isVerified)); app.put( "/posts/:id", authorize((ctx) => canEdit(ctx.user as User, loadPost(ctx))), handler, ); ``` ## Requirements / Notes - **Bun-only** — like the rest of WrNexus, this package targets the Bun runtime; Node is not supported. - Works with [`@wrnexus/core`](../core) — the guards return `Middleware` and read the subject from `ctx.user` on the request `Context`. Both types are imported from `@wrnexus/core`. - Policy combinators (`any`, `all`) and `authorize` are async-aware, so policies may return a `Promise` (e.g. for a database ownership check). ## Declaring permissions The RBAC/PBAC/ABAC surface above is the low-level toolkit. On top of it sits a declarative **registry + catalog + store + engine**: permissions, roles, and policies are declared once in code, merged into a frozen catalog at boot, and resolved per-request against a pluggable `PermissionStore` that holds who has what. Put declarations in `app/authz/.ts`; they are discovered automatically and merged (conflicting declarations of the same permission/role/policy across files fail the boot loudly, naming both source files). ```ts import { defineAuthz, owner } from "@wrnexus/authz"; export default defineAuthz({ permissions: { "post:read": { title: "View posts", public: true }, "post:delete": { title: "Delete posts", risk: "high" }, }, // "post:*" is a namespace wildcard grant, valid inside a role's list — it is // not itself a registered permission, so it can only ever grant permissions // that ARE declared above (e.g. "post:read", "post:delete"). roles: { editor: ["post:*"], admin: ["role:editor"] }, policies: { ownsPost: owner("id", "authorId") }, bindings: { "post:delete": ["ownsPost"] }, }); ``` `public: true` means anonymous callers may hold the permission — but any policy bound to it still runs, and can still veto the anonymous caller (e.g. a `notBanned` policy on a public `post:preview` permission). ## Checking permissions Register `authzMiddleware` once, in `app/middleware/`, with the merged catalog and a `PermissionStore`. Like every other `app/middleware/*.ts` file, the registration is an eager, module-scope call — the same shape as `authzMiddleware({ catalog, store })` requires — so it must run after the catalog has been populated. Both the dev server and `wrnexus build`'s generated production entry guarantee `getAuthzCatalog()` is populated before any app middleware module evaluates. Name the file so it sorts after whatever middleware sets `ctx.user` (middleware runs in alphabetical filename order — `authz.ts` after `auth.ts`, for instance). ```ts // app/middleware/authz.ts import { authzMiddleware, getAuthzCatalog } from "@wrnexus/authz"; import { dbPermissionStore } from "@wrnexus/authz/db"; import { getDb } from "@wrnexus/db"; export default authzMiddleware({ catalog: getAuthzCatalog(), store: dbPermissionStore(getDb()) }); ``` > **`subject.id` must be a non-empty string.** The engine denies (and logs to > stderr) whenever `ctx.user.id` is present but not a non-empty string — this > includes the common case of an integer primary key. Coerce it before it > reaches `ctx.user`, e.g. `user.id = String(row.id)`, or every request for > that user denies with "Invalid subject" instead of resolving normally. > `owner()` (the built-in ownership policy) compares subject and resource ids > with `Object.is`, so both sides must be the same type too — `owner()` on a > numeric `resource.authorId` against a stringified `subject.id` never > matches even when they represent "the same" id. There is no per-route `middleware` export — `app/middleware/*.ts` is the only place middleware is registered. To gate part of the app, branch on the request the same way any other conditional middleware does (compare `app/middleware/captcha-login.ts` in the auth showcase, which branches on method + path the same way): ```ts // app/middleware/protect-posts.ts import type { Context, Next } from "@wrnexus/core"; import { guardPermission } from "@wrnexus/authz"; const guardPostWrite = guardPermission("post:write"); export default function protectPosts(ctx: Context, next: Next) { return ctx.url.pathname.startsWith("/api/posts") && ctx.req.method !== "GET" ? guardPostWrite(ctx, next) : next(); } ``` Or check inline inside a route handler with the free function `can()`: ```ts // app/api/posts/[id].ts import type { Context } from "@wrnexus/core"; import { can } from "@wrnexus/authz"; export const DELETE = async (ctx: Context) => { const post = { id: "1", authorId: "alice" }; // load your own resource here if (!(await can(ctx, "post:delete", post))) { return Response.json({ ok: false, error: "Forbidden" }, { status: 403 }); } return Response.json({ ok: true }); }; ``` `can()` is a free function taking `ctx`, not `ctx.can` — `@wrnexus/core` must not depend on `@wrnexus/authz`, so the per-request resolver lives in `ctx.locals` instead, reached through `can()` / `decideFor()` / `guardPermission()` / `filterCan()`. Calling any of them before `authzMiddleware` has run for that request throws a `WRN-AUTHZ-SETUP` error naming the missing registration, rather than silently denying. See `examples/auth-showcase/app/authz/showcase.ts` and `examples/auth-showcase/app/middleware/authz.ts` for a complete, runnable version of this wiring. ## Precedence 1. An explicit deny wins over everything, including `*` — and honours the same namespace-wildcard matching as grants (denying `post:*` blocks `post:comment:delete`, not just `post:*` itself). 2. A bound policy can veto a permission a role grants, and runs even for a `public: true` permission — including for an anonymous caller. 3. Otherwise the permission must be held via a role or an explicit grant. 4. Default deny. Every failure — an unknown permission (outside strict/dev mode), a store outage, a thrown policy — denies rather than throwing through to the caller. `permissionsFor()` (on the resolver returned by `createAuthzResolver`) is a coarse hint for hiding UI (e.g. a menu section), **never authoritative**. A `Set` cannot represent "granted `post:*` except `post:delete`", so a narrow deny beneath a broad grant is invisible to it — the set still contains `post:*` while `can()` / `decide()` correctly refuse `post:delete`. Gate real actions with `can()`, `decideFor()`, or `filterCan()`; never by matching against `permissionsFor()`'s result. ## CLI ```bash wrnexus authz list # every registered permission, role, and policy wrnexus authz generate # app/authz/permissions.gen.ts type unions wrnexus authz init # scaffold the assignment-table migration ``` `wrnexus authz generate`'s output is a plain `Permission | Role` string-literal union — `can()`, `guardPermission()`, and `decideFor()` all take a bare `string` and nothing reads this file automatically, so import it to type your own helpers/constants against the registered catalog, e.g.: ```ts import type { Permission } from "app/authz/permissions.gen.ts"; function guard(permission: Permission) { return guardPermission(permission); } ``` ### Exported TypeScript declarations ```ts import { Context, Middleware } from '@wrnexus/core'; /** * Validate and freeze one authorization declaration. Called from * `app/authz/.ts` as the module's default export. */ declare function defineAuthz(module: AuthzModule): AuthzModule; interface CatalogSource { /** File or package that declared this module, used in conflict messages. */ source: string; module: AuthzModule; } declare function emptyCatalog(): AuthzCatalog; declare function mergeCatalogs(sources: CatalogSource[]): AuthzCatalog; /** * A process-wide authorization catalog registry, mirroring `@wrnexus/db`'s * `client.ts` (`setDb`/`getDb`/`hasDb`). It exists for the same reason: app * middleware runs at module-eval time — `app/middleware/*.ts` registers * `authzMiddleware({ catalog, store, ... })` itself, an EAGER call (the same * shape as `logger.ts`'s `export default requestLogger({...})`), and it needs * the merged catalog *then*, before its own module body finishes running. * Passing it through `ctx` does not work at that point, so the framework * loads and merges every `app/authz/*.ts` declaration and stashes it here * before any other module can observe it: * * - dev: `startServer` calls `loadAppAuthzCatalog` + `setAuthzCatalog` * before middleware is resolved. * - prod (the normal `wrnexus build` output): the generated entry statically * imports a small `.authz-setup.ts` module FIRST — before any page, API, * or middleware import — which calls `setAuthzCatalog` at ITS OWN module * scope. ES modules evaluate every static import before the importing * module's body runs, and evaluate sibling imports in declaration order, * so import position is evaluation order: this guarantees the catalog * exists before app middleware's own module body (which may read it * eagerly) ever evaluates. `createProductionHandlers` (`prod.ts`) then * repeats the merge as an idempotent second pass, mainly so a caller who * bypasses the generated entry and invokes it directly still gets a * catalog — for THAT path specifically, an eager module-scope read in * middleware is only safe if the caller sets the catalog before importing * the middleware itself, since no generated `.authz-setup.ts` runs first. * * The framework never installs `authzMiddleware` itself — the app always * chooses its own store and registers the middleware; this registry only * makes the merged catalog reachable when it does. */ /** Set the process-wide authorization catalog (called by the framework at boot). */ declare function setAuthzCatalog(next: AuthzCatalog): AuthzCatalog; /** The process-wide authorization catalog. Throws if it hasn't been set. */ declare function getAuthzCatalog(): AuthzCatalog; /** Whether the process-wide authorization catalog has been set. */ declare function hasAuthzCatalog(): boolean; interface AuthzAuditEvent { subjectId?: string; scope?: AuthzScope; permission: string; allowed: boolean; reason?: string; policy?: string; /** Epoch milliseconds. */ at: number; } interface AuthzAuditSink { record(event: AuthzAuditEvent): void | Promise; } interface MemoryAuditSink extends AuthzAuditSink { events: AuthzAuditEvent[]; clear(): void; } declare function memoryAuditSink(): MemoryAuditSink; declare function consoleAuditSink(): AuthzAuditSink; /** Record without ever letting a sink failure escape into the request path. */ declare function safeRecord(sink: AuthzAuditSink | undefined, event: AuthzAuditEvent): void; interface AuthzResolverOptions { catalog: AuthzCatalog; store: PermissionStore; audit?: AuthzAuditSink; /** * Throw on an unregistered permission instead of denying. Defaults to true * outside production, so typos surface during development. */ strict?: boolean; /** Record allows as well as denies. Off by default to bound write volume. */ auditAllows?: boolean; } interface DecideInput { subject: { id?: string; [key: string]: unknown; } | null | undefined; permission: string; resource?: unknown; scope?: AuthzScope; } interface AuthzResolver { /** * Effective permissions with denied entries removed — for coarse gating such * as hiding a menu section. * * NOT authoritative. A set of strings cannot express "everything under * `post:*` except `post:delete`", so a narrow deny beneath a broad grant is * not representable here: the set still contains `post:*` while `decide()` * correctly refuses `post:delete`. Gate individual actions with `decide()` * (or `can()` / `filterCan()`), never by matching against this set. */ permissionsFor(subjectId: string, scope?: AuthzScope): Promise>; decide(input: DecideInput): Promise; } /** Expand roles into their granted entries, following `role:` and stopping on cycles. */ declare function expandRoles(catalog: AuthzCatalog, roles: readonly string[]): Set; /** * Exact match, root wildcard, or a namespace wildcard at any depth. * * Do NOT gate access by matching against `permissionsFor()`'s result — that set * cannot represent a narrow deny beneath a broad grant, so the composition * returns true where `decide()` refuses. Use `decide()` / `can()` instead. */ declare function permissionMatches(granted: Set, permission: string): boolean; /** * True if any entry in the deny list covers `permission`. Denies honour the * same depth-aware wildcards as grants, so denying "post:*" blocks * post:comment:delete rather than being accepted and silently doing nothing. */ declare function deniedBy(denies: readonly string[], permission: string): boolean; declare function createAuthzResolver(options: AuthzResolverOptions): AuthzResolver; /** * `can` is deliberately not a Context member: @wrnexus/core must not depend on * @wrnexus/authz. The per-request resolver lives here instead. */ declare const AUTHZ_LOCALS_KEY = "_authz"; /** Install the per-request resolver. Register early, after sessionAuth. */ declare function authzMiddleware(options: AuthzResolverOptions): Middleware; /** * Object resources are memoised by identity (`byRef`), never by serialising * their contents — serialisation is what let unrelated resources collide * (same `id` shape, circular references, BigInt fields, throwing getters all * funnelled into one bucket). Symbols are memoised by identity too (`bySymbol`) * since `String(symbol)` collapses distinct symbols with the same description. * Primitive/absent resources are memoised by a * `[scope, permission, typeof, String(value)]` tuple, with `-0` rendered * distinctly from `0` since `String(-0) === "0"` would otherwise merge them. * * Subject and scope are both part of the key. A request that reassigns * ctx.user (impersonation, step-up auth, session revocation) or ctx.tenant * must not be served the previous principal's verdict from the memo. */ declare function decideFor(ctx: Context, permission: string, resource?: unknown): Promise; declare function can(ctx: Context, permission: string, resource?: unknown): Promise; interface GuardOptions { /** Load the resource a bound policy needs. */ getResource?: (ctx: Context) => unknown; /** Include reason and policy name in the 403 body. Off by default. */ exposeReason?: boolean; /** Redirect page requests here instead of returning 403. Ignored for JSON/API requests and for any non-local target. */ redirectTo?: string; } /** * Guard a route on a registered permission. Named `guardPermission` because * `requirePermission(rbac, permission)` already exists with a different shape. */ declare function guardPermission(permission: string, options?: GuardOptions): Middleware; /** Keep only the items the current subject may act on. */ declare function filterCan(ctx: Context, permission: string, items: readonly T[]): Promise; /** * Emit `Permission`/`Role` string-literal unions from the registered catalog. * * This does NOT make `can(ctx, "post:wrtie")` a type error — `can()`, * `guardPermission()`, and `decideFor()` all take a bare `string`, and * nothing in the framework consumes this generated file automatically. * Import the unions yourself to type your OWN helpers/constants, e.g. * `const PERM: Permission = "post:write"` or a typed wrapper around `can()`. */ declare function generatePermissionTypes(catalog: AuthzCatalog): string; /** * @wrnexus/authz — authorization: role-based (RBAC), policy-based (PBAC), and * attribute-based (ABAC). Compose freely; all three reduce to a boolean check * plus an `authorize()` guard middleware. * * const rbac = defineRbac({ admin: ["*"], editor: ["post:read", "post:write"] }); * rbac.can(user, "post:write"); * * // PBAC/ABAC: a policy is a predicate over subject + resource + attributes * const ownsPost: Policy = (u, post) => u.id === post.authorId; * authorize((ctx) => ownsPost(ctx.user, resource)) // middleware */ interface Subject { id?: string; roles?: string[]; [attribute: string]: unknown; } interface Rbac { /** True if any of the subject's roles grants `permission` (supports "*" and "ns:*"). */ can(subject: Subject | undefined, permission: string): boolean; /** All permissions granted to a set of roles. */ permissionsFor(roles: string[]): Set; } /** Build an RBAC checker from a role → permissions map. */ declare function defineRbac(roles: Record): Rbac; /** True if the subject has ALL of the given roles. */ declare function hasRole(subject: Subject | undefined, ...required: string[]): boolean; /** A policy predicate: subject (+ optional resource/attributes) → allowed. */ type Policy = (subject: S, resource?: R) => boolean | Promise; /** Combine policies: allow if ANY passes (OR). */ declare function any(...policies: Policy[]): Policy; /** Combine policies: allow only if ALL pass (AND). */ declare function all(...policies: Policy[]): Policy; /** ABAC helper: allow when an attribute matches (equality or predicate). */ declare function attr(name: string, match: unknown | ((value: unknown) => boolean)): Policy; /** Guard a route with a policy over `ctx` (reads `ctx.user` as the subject). */ declare function authorize(policy: (ctx: Context) => boolean | Promise): Middleware; /** Guard requiring one of the given roles. */ declare function requireRole(...roles: string[]): Middleware; /** Guard requiring an RBAC permission. */ declare function requirePermission(rbac: Rbac, permission: string): Middleware; interface AuthorizationDecision { allowed: boolean; reason?: string; policy?: string; metadata?: Record; } type DecisionPolicy = (subject: S, resource?: R) => AuthorizationDecision | Promise; declare function allow(reason?: string, metadata?: Record): AuthorizationDecision; declare function deny(reason?: string, metadata?: Record): AuthorizationDecision; declare function decision(name: string, policy: Policy, denial?: string): DecisionPolicy; declare function owner>(subjectKey?: keyof SubjectType, resourceKey?: keyof Resource | string): DecisionPolicy; declare function anyDecision(...policies: DecisionPolicy[]): DecisionPolicy; declare function allDecisions(...policies: DecisionPolicy[]): DecisionPolicy; interface AuthorizeDecisionOptions { /** * Include `reason` and `policy` in the 403 body. Off by default: policy * names describe internal authorization structure and should not reach an * unauthenticated caller. */ exposeReason?: boolean; } declare function authorizeDecision(evaluate: (ctx: Context) => AuthorizationDecision | Promise, options?: AuthorizeDecisionOptions): Middleware; declare function filterAuthorized(subject: S, values: readonly R[], policy: Policy): Promise; /** Narrows an assignment to a tenant. Absent means a global assignment. */ interface AuthzScope { tenantId?: string; } interface PermissionMeta { title?: string; description?: string; risk?: "low" | "medium" | "high"; /** Granted to anonymous subjects. Every other permission denies without a user. */ public?: boolean; } interface AttributeMeta { description?: string; } /** One `app/authz/.ts` declaration. */ interface AuthzModule { permissions?: Record; roles?: Record; policies?: Record>; attributes?: Record; /** permission id -> policy names that must pass for it. */ bindings?: Record; } /** The merged, frozen view of every declaration in the app. */ interface AuthzCatalog { permissions: ReadonlyMap; roles: ReadonlyMap; policies: ReadonlyMap>; attributes: ReadonlyMap; bindings: ReadonlyMap; } interface SubjectAssignments { roles: string[]; /** Explicit allows, bypassing roles. */ grants: string[]; /** Explicit denies. Win over everything, including "*". */ denies: string[]; } type GrantEffect = "allow" | "deny"; interface PermissionStore { assignmentsFor(subjectId: string, scope?: AuthzScope): Promise; assignRole(subjectId: string, role: string, scope?: AuthzScope): Promise; revokeRole(subjectId: string, role: string, scope?: AuthzScope): Promise; grant(subjectId: string, permission: string, effect: GrantEffect, scope?: AuthzScope): Promise; revokeGrant(subjectId: string, permission: string, scope?: AuthzScope): Promise; listSubjects(scope?: AuthzScope): Promise; } /** * Global assignments are stored under the empty-string scope key. An OMITTED * scope means global; an explicitly EMPTY or non-string tenantId is refused, * because an empty string is indistinguishable from global (and would let a * caller who controls the tenant id read and write global assignments), and a * non-string value (e.g. `null` from a JSON body or a nullable column) would * otherwise flow through un-normalised and leave the adapters disagreeing * about what happened. */ declare function scopeKey(scope?: AuthzScope): string; declare function memoryPermissionStore(): PermissionStore; interface CachedPermissionStore extends PermissionStore { /** Drop one subject. Call after changing roles out of band. */ invalidate(subjectId: string, scope?: AuthzScope): void; invalidateAll(): void; /** Cached entry count, for tests and diagnostics. */ size(): number; } interface CacheOptions { ttlMs?: number; max?: number; } /** * Caches assignment reads. Writes through this decorator invalidate the * affected subject immediately; changes made directly against the inner store * need an explicit `invalidate()` call rather than waiting out the TTL. */ declare function cachedPermissionStore(inner: PermissionStore, options?: CacheOptions): CachedPermissionStore; export { AUTHZ_LOCALS_KEY, type AttributeMeta, type AuthorizationDecision, type AuthorizeDecisionOptions, type AuthzAuditEvent, type AuthzAuditSink, type AuthzCatalog, type AuthzModule, type AuthzResolver, type AuthzResolverOptions, type AuthzScope, type CacheOptions, type CachedPermissionStore, type CatalogSource, type DecideInput, type DecisionPolicy, type GrantEffect, type GuardOptions, type MemoryAuditSink, type PermissionMeta, type PermissionStore, type Policy, type Rbac, type Subject, type SubjectAssignments, all, allDecisions, allow, any, anyDecision, attr, authorize, authorizeDecision, authzMiddleware, cachedPermissionStore, can, consoleAuditSink, createAuthzResolver, decideFor, decision, defineAuthz, defineRbac, deniedBy, deny, emptyCatalog, expandRoles, filterAuthorized, filterCan, generatePermissionTypes, getAuthzCatalog, guardPermission, hasAuthzCatalog, hasRole, memoryAuditSink, memoryPermissionStore, mergeCatalogs, owner, permissionMatches, requirePermission, requireRole, safeRecord, scopeKey, setAuthzCatalog }; ``` --- ## @wrnexus/benchmark Documentation URL: https://wrnexusjs.dev/packages/benchmark # @wrnexus/benchmark Deterministic benchmark execution, percentiles, baseline comparisons, and regression budgets for builds, SSR, hydration, stores, and application hot paths. ```ts import { runBenchmark, assertBenchmarkBudget } from "@wrnexus/benchmark"; const result = await runBenchmark("render", render, { iterations: 100 }); assertBenchmarkBudget(result, baseline, { p95Percent: 5 }); ``` ### Exported TypeScript declarations ```ts interface BenchmarkOptions { iterations?: number; warmup?: number; clock?: () => number; setup?: () => void | Promise; teardown?: () => void | Promise; } interface BenchmarkResult { name: string; iterations: number; totalMs: number; meanMs: number; minMs: number; maxMs: number; p50Ms: number; p95Ms: number; p99Ms: number; operationsPerSecond: number; samples: number[]; } interface RegressionBudget { meanPercent?: number; p95Percent?: number; maxAbsoluteMs?: number; minOperationsPerSecond?: number; } interface RegressionViolation { metric: "meanMs" | "p95Ms" | "maxMs" | "operationsPerSecond"; baseline?: number; current: number; limit: number; message: string; } declare function percentile(values: readonly number[], quantile: number): number; declare function runBenchmark(name: string, operation: () => void | Promise, options?: BenchmarkOptions): Promise; declare function compareBenchmark(current: BenchmarkResult, baseline: BenchmarkResult | undefined, budget?: RegressionBudget): RegressionViolation[]; declare function assertBenchmarkBudget(current: BenchmarkResult, baseline: BenchmarkResult | undefined, budget: RegressionBudget): void; export { type BenchmarkOptions, type BenchmarkResult, type RegressionBudget, type RegressionViolation, assertBenchmarkBudget, compareBenchmark, percentile, runBenchmark }; ``` --- ## @wrnexus/cache Documentation URL: https://wrnexusjs.dev/packages/cache # @wrnexus/cache Bounded in-memory/tag caching and HTTP response caching for WRNexusJS. Supports request deduplication, tag invalidation, ETags, fresh/stale states, and optional detached stale revalidation. ```ts import { connectCacheInvalidation, TagCache, responseCache } from "@wrnexus/cache"; const cache = new TagCache({ ttlMs: 60_000, staleWhileRevalidateMs: 300_000 }); export default responseCache({ cache, tags: ["products"] }); ``` `TagCache` bounds entries with LRU-style eviction, deduplicates concurrent loaders, and prevents an invalidated in-flight loader from repopulating stale data. Use `lookup()` when fresh/stale state matters, or `getOrLoad()` for stampede-safe loading. For multi-instance applications, connect the cache to any compatible pub/sub bus (including `@wrnexus/pubsub`). Namespaces isolate applications sharing the same broker. Local invalidation happens first and the returned promise confirms cross-instance publication; failures remain visible to the caller. ```ts import { connectCacheInvalidation, TagCache } from "@wrnexus/cache"; import { createPubSub } from "@wrnexus/pubsub"; import { redisDriver } from "@wrnexus/pubsub/redis"; const cache = new TagCache({ maxEntries: 10_000 }); const bus = createPubSub(redisDriver(process.env.REDIS_URL)); const invalidation = connectCacheInvalidation(cache, bus, { namespace: "storefront-production", onError: (error) => logger.error("cache invalidation failed", { error }), }); await invalidation.invalidateTag("products"); await invalidation.delete("product:42"); // Unsubscribes this cache only; the shared bus remains owned by the app. invalidation.close(); await bus.close(); ``` ## Framework cache layers `CacheCoordinator` keeps the four cache lifetimes explicit: - `coordinator.request()` creates request-only deduplication. - `coordinator.data` caches loader/query results. - `coordinator.component` caches reusable rendered fragments. - `coordinator.page` caches complete safe documents. All cross-request layers are bounded, tag-aware, stale-while-revalidate capable, stampede-safe, and expose `withLock()` for exclusive per-key work. `inspect()` returns metadata without cached values. Development applications expose that inspection through the Cache panel and `GET /__wrnexus/cache`. Pages and components can opt in declaratively: ```wrn cache { scope = "page" strategy = "stale-while-revalidate" ttl = "5m" stale = "10m" tags = ["catalog", "marketing"] vary = ["tenant", "language"] } ``` Omit `scope` to cache named loader data. Use `scope = "page"` for full-page caching. Component policies cache their rendered fragment. Authenticated user and tenant identities are always included automatically; page caches also vary by language, theme, and accent. Add header names or `cookie:name` entries for other application-specific variation. Pages containing CSRF forms are never stored in the full-page cache. ### Exported TypeScript declarations ```ts import { Context, Middleware } from '@wrnexus/core'; interface CacheEntry { value: V; createdAt: number; expiresAt: number; staleUntil: number; tags: string[]; } type CacheLookup = { state: "miss"; } | { state: "fresh" | "stale"; entry: CacheEntry; }; interface CacheSetOptions { ttlMs?: number; staleWhileRevalidateMs?: number; tags?: string[]; } interface TagCacheOptions { ttlMs?: number; staleWhileRevalidateMs?: number; maxEntries?: number; clock?: () => number; onEvent?: (event: CacheEvent) => void; } interface CacheEvent { operation: "hit" | "stale" | "miss" | "set" | "delete" | "invalidate" | "clear" | "load"; key?: string; tags?: string[]; at: number; } interface CacheSnapshotEntry { key: string; state: "fresh" | "stale"; createdAt: number; expiresAt: number; staleUntil: number; tags: string[]; } declare class TagCache { private entries; private tagIndex; private pending; private locks; private revisions; private readonly ttlMs; private readonly staleMs; private readonly maxEntries; private readonly clock; private readonly onEvent?; constructor(options?: TagCacheOptions); private emit; lookup(key: string): CacheLookup; get(key: string): V | undefined; set(key: string, value: V, options?: CacheSetOptions): void; private store; getOrLoad(key: string, loader: () => V | Promise, options?: CacheSetOptions): Promise; /** Serialize arbitrary cache-adjacent work for a key without storing its result. */ withLock(key: string, task: () => T | Promise): Promise; delete(key: string): boolean; private removeEntry; invalidateTag(tag: string): number; invalidateTags(tags: Iterable): number; clear(): void; get size(): number; snapshot(): CacheSnapshotEntry[]; private revision; private bump; } type CacheLayerName = "data" | "component" | "page"; interface CacheInspection { layers: Record["snapshot"]>>; recentEvents: Array; } interface CacheCoordinatorOptions extends Omit { eventLimit?: number; onEvent?: (event: CacheEvent & { layer: CacheLayerName; }) => void; } /** A request-lifetime cache: deduplicates work without leaking values between requests. */ declare class RequestCache { private pending; getOrLoad(key: string, loader: () => V | Promise): Promise; clear(): void; } /** Owns the three cross-request cache layers and creates isolated request caches. */ declare class CacheCoordinator { readonly data: TagCache; readonly component: TagCache; readonly page: TagCache; private readonly events; private readonly eventLimit; constructor(options?: CacheCoordinatorOptions); request(): RequestCache; layer(name: CacheLayerName): TagCache; getOrLoad(layer: CacheLayerName, key: string, loader: () => V | Promise, options?: CacheSetOptions): Promise; invalidateTags(tags: Iterable): number; inspect(): CacheInspection; clear(): void; } interface CachedResponse { status: number; statusText: string; headers: [string, string][]; body: Uint8Array; etag: string; } interface ResponseCacheOptions extends CacheSetOptions { cache?: TagCache; key?: (ctx: Context) => string; vary?: string[]; shouldCache?: (ctx: Context, response: Response) => boolean; /** * Optional detached revalidator used for stale-while-revalidate. Middleware * `next()` is deliberately never called after a response has been returned, * because many middleware pipelines are single-use. */ revalidate?: (ctx: Context) => Promise; onRevalidateError?: (error: unknown, ctx: Context) => void; } declare function responseCache(options?: ResponseCacheOptions): Middleware; interface CacheInvalidationBus { publish(topic: string, message: unknown): void | Promise; subscribe(pattern: string, handler: (message: unknown) => void | Promise): () => void; } interface DistributedInvalidationOptions { namespace?: string; instanceId?: string; onError?: (error: unknown) => void; } interface DistributedInvalidation { invalidateTag(tag: string): Promise; invalidateTags(tags: Iterable): Promise; delete(key: string): Promise; clear(): Promise; close(): void; } /** * Propagate cache invalidations over any structurally compatible pub/sub bus. * The bus is intentionally not closed because applications commonly share it. */ declare function connectCacheInvalidation(cache: TagCache, bus: CacheInvalidationBus, options?: DistributedInvalidationOptions): DistributedInvalidation; export { CacheCoordinator, type CacheCoordinatorOptions, type CacheEntry, type CacheEvent, type CacheInspection, type CacheInvalidationBus, type CacheLayerName, type CacheLookup, type CacheSetOptions, type CacheSnapshotEntry, type CachedResponse, type DistributedInvalidation, type DistributedInvalidationOptions, RequestCache, type ResponseCacheOptions, TagCache, type TagCacheOptions, connectCacheInvalidation, responseCache }; ``` --- ## @wrnexus/captcha Documentation URL: https://wrnexusjs.dev/packages/captcha # @wrnexus/captcha A first-class CAPTCHA and anti-automation package for WRNexusJS. It supports self-hosted challenges, a managed WRNexus service, external providers, form submission guards, page gates, accessible audio, adaptive risk checks, and a Tailwind-only `.wrn` component. ## Install ```bash bun add @wrnexus/captcha ``` WRNexusJS automatically discovers the package plugin, component, client runtime, styles, and DevToolbar audit. Use `` directly after installation. The browser runtime is injected once only on responses that render a CAPTCHA; no script tag, public-file copy, or manual plugin registration is required. Call `captchaPlugin(options)` explicitly only when an application needs to override the discovered package configuration. ## Included challenge modes - Number, alphabet, and alphanumeric image challenges - Addition, subtraction, multiplication, and exact-division calculations - Generated shape-selection image challenges - Audio alternatives for text, numbers, and calculations - Honeypot and minimum-completion-time invisible checks - Self-hosted “I’m not a robot” checkbox challenge with one-time server verification - Always, once-per-session, and adaptive page gates - Cloudflare Turnstile, Google reCAPTCHA, hCaptcha, managed, and custom providers ## Create the self-hosted engine ```ts import { createCaptchaEngine, createCaptchaHttpHandlers, RedisCaptchaStore, } from "@wrnexus/captcha/server"; const engine = createCaptchaEngine({ secret: process.env.CAPTCHA_SECRET!, store: new RedisCaptchaStore(redis), basePath: "/api/captcha", challengeTtlMs: 2 * 60_000, responseTokenTtlMs: 5 * 60_000, maxAttempts: 3, minCompletionMs: 800, }); export const handlers = createCaptchaHttpHandlers(engine); ``` Mount the handlers from an API catch-all route: ```ts import type { Context } from "@wrnexus/core"; import { handlers } from "../../lib/captcha.ts"; export async function POST(ctx: Context) { return (await handlers.handle(ctx.req, ctx)) ?? new Response("Not Found", { status: 404 }); } export const GET = POST; export const HEAD = POST; ``` ## Use the component ```wrn ``` The component uses Tailwind utilities and `--wire-*` theme variables. It has no companion component CSS file. ### Main props `provider`, `siteKey`, `type`, `action`, `presentation`, `difficulty`, `disturbance`, `imageStyle`, `allowedStyles`, `excludedStyles`, `randomizeStyle`, `locale`, `size`, `color`, `class`, `name`, `endpoint`, `verifyEndpoint`, `responseField`, labels/messages, `autoLoad`, `autoVerify`, `showVerify`, `showRefresh`, `showAudio`, `showListen`, `showStatus`, `disabled`, `required`, and the backward-compatible `compact` alias. ### Component sizes Use one of the three supported display modes: ```wrn ``` `small`/`sm` are accepted as aliases for `compact`, while `large`/`lg` are accepted as aliases for `big`. The old `compact="true"` prop still forces compact mode. ### Listen button visibility Audio remains available by default. Hide the Listen and Use audio controls with either of these props: ```wrn ``` `showListen` is the direct UI switch. `showAudio` remains the broader backward-compatible audio switch. ### I’m not a robot checkbox ```wrn ``` The checkbox is not a client-only boolean. Clicking it completes a self-hosted invisible challenge that is time-limited, attempt-limited, one-time-use, action-bound, optionally session/hostname/IP-bound, and verified on the server. It is a low-friction anti-automation layer; use adaptive escalation to a visual or external provider for high-risk traffic. ### Visual disturbance Use `disturbance` for visual and image-selection challenges. It accepts an integer from `25` through `75`: - `25`: light disturbance and easiest readability - `50`: balanced default - `75`: maximum supported dots, line crossings, glyph movement, and image-tile noise The browser sends this value to the challenge API, and the server validates the range before generating the challenge. It is also returned in challenge metadata. ### Generated image renderer styles Text, number, alphanumeric, and calculation CAPTCHA images support 18 concrete renderers plus a random mode: `classic`, `collision`, `snow`, `corrosion`, `spiderweb`, `cross-shadow`, `split`, `split2`, `cut`, `darts`, `distortion`, `stitch`, `striped`, `wave`, `grid-noise`, `scribble`, `pixel`, and `broken-lines`. Use a fixed style: ```wrn ``` Use a new random style whenever the challenge is refreshed: ```wrn ``` Control the random pool with comma-separated component props or arrays in the TypeScript API: ```wrn ``` ```ts const challenge = await engine.create({ action: "checkout", type: "alphanumeric", imageStyle: "random", allowedStyles: ["classic", "snow", "distortion", "wave"], excludedStyles: ["collision"], }); ``` Set `randomizeStyle: true` to force random selection even when `imageStyle` names a concrete renderer. The resolved style, requested style, and active pool are returned in challenge metadata. The answer is never embedded in metadata or browser JavaScript. ### Events `@ready`, `@challenge`, `@input`, `@verify`, `@success`, `@failure`, `@expired`, `@refresh`, `@audioStart`, `@audioEnd`, and `@error`. ## Protect a validated form API Validate a cloned request first, then consume the CAPTCHA response token. This prevents a valid token from being consumed when ordinary field validation fails. ```ts import { captchaGuard } from "@wrnexus/captcha/server"; import { parseBody } from "@wrnexus/validation"; import contactSchema from "../schemas/contact.ts"; import { engine } from "../lib/captcha.ts"; const guard = captchaGuard({ action: "contact-submit", engine, bindHostname: true, bindSession: true, }); export async function POST(ctx) { const validation = await parseBody(contactSchema, ctx.req.clone()); if (!validation.ok) return validation.response; return guard(ctx, async () => Response.json({ ok: true, submission: validation.value })); } ``` The CAPTCHA runtime binds its required-form check in the capture phase, so a `data-schema` validator cannot submit the form before CAPTCHA verification. After a successful form request, the component automatically creates a fresh challenge. ### Retryable operations such as login A login may consume a valid CAPTCHA and then fail because the password is incorrect. Configure a short action-bound session grant so the user can correct their credentials without solving CAPTCHA again: ```ts const guard = captchaGuard({ action: "auth-login", engine, bindHostname: true, bindSession: true, verifiedForMs: 5 * 60_000, }); ``` Keep the verified widget state for non-CAPTCHA form errors: ```wrn ``` The grant is stored in the current session and bound to the configured action. Expired grants and CAPTCHA-specific errors still require and load a fresh challenge. Keep login rate limits and authentication lockout enabled; `verifiedForMs` removes repeated human verification, not credential-abuse controls. ## Validate a schema and CAPTCHA together ```ts const result = await parseWithCaptcha(signupSchema, body, ctx, { action: "signup", engine, }); if (!result.ok) return Response.json({ ok: false, errors: result.errors }, { status: 400 }); ``` ## Page gate ```ts export default captchaPageGate({ action: "reports-access", engine, challengePath: "/captcha", policy: { mode: "session", verifiedForMs: 15 * 60_000, routeGroups: ["/reports"], }, }); ``` Use `mode: "always"` for every visit, `mode: "session"` for a temporary grant, or `mode: "adaptive"` with `signals(ctx)`. The challenge page should post the return path as a normal hidden field instead of constructing JavaScript inside the HTML `action` attribute: ```wrn ``` The `/api/page-grant` route reads `returnTo`, restricts it to the current origin, and redirects only after `captchaPageGate()` has verified and stored the temporary session grant. ## External providers ```ts const turnstile = turnstileProvider({ secretKey: process.env.TURNSTILE_SECRET!, siteKey: process.env.PUBLIC_TURNSTILE_SITE_KEY!, expectedHostnames: ["example.com"], expectedAction: "signup", }); ``` ```wrn ``` Use the matching provider in `captchaGuard({ provider: turnstile })`. reCAPTCHA and hCaptcha adapters follow the same pattern. ## Managed provider ```ts const managed = managedCaptchaProvider({ baseUrl: "https://captcha.example.com", siteKey: process.env.PUBLIC_CAPTCHA_SITE_KEY!, secretKey: process.env.CAPTCHA_SECRET_KEY!, }); ``` For direct browser challenge creation, configure the component’s `endpoint` as the managed `/v1/challenges` URL and its `verifyEndpoint` as `/v1/solve`. Keep the secret key only in the server provider. ## Stores - `MemoryCaptchaStore`: development and one-process applications - `SqliteCaptchaStore`: adapter for SQLite-like `prepare().run/get/all()` clients - `RedisCaptchaStore`: shared TTL storage with Lua-backed atomic consumption when `eval` is available - `CaptchaStore`: implement this interface for PostgreSQL, MySQL, MongoDB, or another backend ## Audio `AssetAudioRenderer` concatenates bundled English PCM WAV clips without calling an external service. Supply a custom `CaptchaAudioRenderer` for recorded voices, Hindi or other languages, or managed text-to-speech. ## DevToolbar The automatically discovered CAPTCHA plugin registers its DevToolbar audit panel. It checks for likely client-side secrets, missing action bindings, missing provider site keys, optional CAPTCHA fields, accessible alternatives, and server-verification reminders. Explicit `captchaPlugin(options)` registration is needed only to override automatic configuration. ## Testing Use deterministic custom generators in unit tests. Never require users or CI to solve random CAPTCHA images. The package includes engine, provider, policy, HTTP, storage, replay, expiry, binding, and audio authorization tests. ## Custom challenge generator ```ts import { defineCaptchaGenerator, createCaptchaEngine } from "@wrnexus/captcha"; const wordChallenge = defineCaptchaGenerator({ type: "word" as const, generate(context) { const answer = "NEXUS"; return { type: "word", presentation: "visual", prompt: "Enter the displayed word", answer, answerKind: "text", image: renderYourImage(answer), inputMode: "text", }; }, }); const engine = createCaptchaEngine({ secret, generators: [wordChallenge] }); ``` Applications may also implement `CaptchaStore`, `CaptchaAudioRenderer`, or use `defineCaptchaProvider()` for a completely custom service. ## Helper and block kit The package exports `captchaTokenFrom`, `captchaHeaders`, `captchaFields`, `verifyCaptcha`, `verifyCaptchaOrThrow`, `captchaResultResponse`, and `captchaContext` for consistent server and client integration. Enable the CAPTCHA plugin to use the low-level `` challenge plus complete UI-composed blocks: - `` - `` `CaptchaField` composes `Card` from `@wrnexus/ui` and keeps the CAPTCHA-specific size separate from the surrounding UI size. ### Exported TypeScript declarations ```ts import { CaptchaVerificationResult, CaptchaProvider, CaptchaEngine, VerifyCaptchaInput } from './types.js'; export { CaptchaAudioRenderer, CaptchaBinding, CaptchaChallenge, CaptchaChallengeGenerator, CaptchaChallengeRecord, CaptchaChallengeType, CaptchaConcreteImageStyle, CaptchaDifficulty, CaptchaEngineOptions, CaptchaFailureCode, CaptchaGeneratorContext, CaptchaGuardOptions, CaptchaHttpHandlers, CaptchaImageItem, CaptchaImageStyle, CaptchaMiddleware, CaptchaPageGateOptions, CaptchaPolicyMode, CaptchaPolicyOptions, CaptchaPresentation, CaptchaProviderClientConfig, CaptchaProviderName, CaptchaResponseTokenRecord, CaptchaRiskResult, CaptchaRiskSignals, CaptchaStore, CreateCaptchaOptions, GeneratedCaptchaChallenge } from './types.js'; export { CaptchaHttpOptions, CaptchaParseResult, CaptchaSessionGrant, DefaultCaptchaEngine, ParseWithCaptchaOptions, bindingHash, bytesToBase64Url, captchaGuard, captchaPageGate, clearCaptchaGrants, constantTimeEqual, createCaptchaEngine, createCaptchaHttpHandlers, defaultRandomBytes, evaluateCaptchaRisk, hmacSha256, parseWithCaptcha, randomId, sha256, shouldRequireCaptcha, validCaptchaGrant } from './server/index.js'; export { CaptchaAuditIssue, CaptchaPluginOptions, captchaComponentsDir, captchaPlugin } from './plugin.js'; export { MemoryCaptchaStore, MemoryCaptchaStoreOptions, createMemoryCaptchaStore } from './stores/memory.js'; export { SqliteCaptchaStore, SqliteCaptchaStoreOptions, SqliteDatabaseLike, SqliteStatementLike, createSqliteCaptchaStore } from './stores/sqlite.js'; export { RedisCaptchaClient, RedisCaptchaStore, RedisCaptchaStoreOptions, createRedisCaptchaStore } from './stores/redis.js'; export { SelfHostedCaptchaProvider, selfHostedProvider } from './providers/self-hosted.js'; export { S as SiteverifyCaptchaProvider, a as SiteverifyPreset, b as SiteverifyProviderOptions } from './siteverify-Cg3TTAp4.js'; export { TurnstileCaptchaProvider, turnstileProvider } from './providers/turnstile.js'; export { RecaptchaProvider, recaptchaProvider } from './providers/recaptcha.js'; export { HcaptchaProvider, hcaptchaProvider } from './providers/hcaptcha.js'; export { ManagedCaptchaProvider, ManagedCaptchaProviderOptions, managedCaptchaProvider } from './providers/managed.js'; export { defineCaptchaProvider } from './providers/custom.js'; export { CAPTCHA_CONCRETE_IMAGE_STYLES, CAPTCHA_IMAGE_STYLES, CalculationCaptchaGenerator, ImageCaptchaGenerator, InvisibleCaptchaGenerator, ResolveCaptchaImageStyleOptions, ResolvedCaptchaImageStyle, Rgba, RgbaImage, TextCaptchaGenerator, alphaCaptchaGenerator, alphanumericCaptchaGenerator, bytesToBase64, calculationCaptchaGenerator, createImage, defaultCaptchaGenerators, defineCaptchaGenerator, drawGlyph, drawLine, drawText, encodePng, fillCircle, fillPolygon, fillRect, honeypotCaptchaGenerator, imageCaptchaGenerator, isCaptchaImageStyle, normalizeCaptchaImageStyle, normalizeCaptchaImageStyleList, notRobotCaptchaGenerator, numberCaptchaGenerator, pngDataUri, resolveCaptchaImageStyle, setPixel, timingCaptchaGenerator } from './challenges/index.js'; export { AssetAudioRenderer, AssetAudioRendererOptions, createAssetAudioRenderer, resolveCaptchaAudioAssetsDir } from './audio/index.js'; import { Context } from '@wrnexus/core'; import '@wrnexus/validation'; import '@wrnexus/plugin'; declare function captchaTokenFrom(value: Request | Headers | FormData | URLSearchParams | Record, field?: string): Promise | string | undefined; declare function captchaHeaders(token: string): HeadersInit; declare function captchaFields(token: string, field?: string): Record; declare function verifyCaptcha(providerOrEngine: CaptchaProvider | CaptchaEngine, input: VerifyCaptchaInput): Promise; declare function verifyCaptchaOrThrow(providerOrEngine: CaptchaProvider | CaptchaEngine, input: VerifyCaptchaInput): Promise; declare function captchaResultResponse(result: CaptchaVerificationResult): Response; declare function captchaContext(ctx: Context): CaptchaVerificationResult | null; export { CaptchaEngine, CaptchaProvider, CaptchaVerificationResult, VerifyCaptchaInput, captchaContext, captchaFields, captchaHeaders, captchaResultResponse, captchaTokenFrom, verifyCaptcha, verifyCaptchaOrThrow }; ``` --- ## @wrnexus/cli Documentation URL: https://wrnexusjs.dev/packages/cli # @wrnexus/cli Production parity commands: ```bash wrnexus build . wrnexus preview . --port=3000 wrnexus dev . --production-runtime ``` `preview` refuses to start without `dist/server.js` and executes that exact artifact with the production profile. Production-runtime development rebuilds the same minified artifact after app, public, or configuration changes and keeps the last good server running when a rebuild fails. > The `wrnexus` command-line tool that scaffolds, runs, builds, tests, and manages WrNexus apps. Part of the **WrNexus** framework — an SSR-first, Bun-native full-stack web framework. ## Overview `@wrnexus/cli` provides the `wrnexus` executable — the single entry point for developing a WrNexus app. It runs the HMR dev server, produces a self-contained production build, scaffolds apps/pages/components, drives database migrations, regenerates typed routes and queries, runs tests, and manages configuration profiles. It also scaffolds multi-app monorepos and serves them behind a domain-routing gateway. This is a CLI/build-time package (it shells out to the Bun binary for the dev child and tests) and it also exports the workspace config types via a subpath. ## Installation ```bash bun add @wrnexus/cli ``` > Private package — the machine must be authenticated to the `wrnexus` npm org > (a read token in `~/.npmrc`). Requires **Bun** (Node is not supported). Once installed, invoke it from an app directory: ```bash bunx wrnexus dev # or add scripts: "dev": "wrnexus dev .", "build": "wrnexus build ." ``` ## Commands ### Local production services `wrnexus dev . --services` starts the application and the bounded local database, cache, mail, SMS, webhook, storage, queue, cron, authentication and metrics simulator. It generates a localhost/`*.localhost` development certificate under `.wrnexus/certificates/` and serves both the application and service console over HTTPS. Trust that certificate locally to remove the browser warning. Use `--services-http` only when an external development proxy already terminates TLS. ### Exact production runtime with live updates `wrnexus dev . --production-runtime` rebuilds and executes `dist/server.js` with production resolution, serialization, caching, headers and assets. The supervisor keeps the last good process when a build fails. On a successful rebuild the opt-in production HMR socket reconnects, requests the new document and morphs it into the browser; ordinary `wrnexus preview` and deployed production servers never include that client. ### API platform `wrnexus api generate [app-dir]` (or `api docs`) derives operations from file routes and emits `generated/api/openapi.json`, safe static documentation, Postman collection, curl examples, and TypeScript, JavaScript, Java, Go and Python SDKs. Generate one client with `wrnexus sdk generate [app-dir]`. Every command accepts an optional `[app-dir]` (defaults to `.`). Commands that read config or `.env` also accept `--profile=` (see [Profiles](#profiles)). | Command | Purpose | | ------------------------------------- | ---------------------------------------------------------------------- | | `wrnexus dev [app-dir] [--port=3000]` | Start the development server with live reload / HMR. | | `wrnexus build [app-dir]` | Build a self-contained production server bundle + assets into `dist/`. | | `wrnexus create ` | Scaffold a new single app from an inline template. | | `wrnexus workspace ` | Scaffold a monorepo (`apps/*` + shared `packages/*`). | | `wrnexus workspace add ` | Add and register an app in the current workspace. | | `wrnexus gateway [--port=3000]` | Serve every workspace app behind one port, routed by domain. | | `wrnexus production [workspace-dir]` | Build, migrate, and serve every workspace app in production. | | `wrnexus generate ` | Scaffold a `page` \| `component` \| `api` \| `schema`. | | `wrnexus generate routes` | Regenerate the typed routes file (`app/routes.gen.ts`). | | `wrnexus generate docker` | Scaffold `Dockerfile`, `.dockerignore`, and `docker-compose.yml`. | | `wrnexus generate mobile` | Scaffold a Capacitor shell for iOS and Android. | | `wrnexus mobile add ` | Install Capacitor plugins and sync native projects. | | `wrnexus eject ` | Copy Wire UI component `.wrn` sources into `app/components/`. | | `wrnexus db ` | Database migrations and tooling (see [db](#wrnexus-db)). | | `wrnexus test [app-dir] [--watch]` | Run the app's tests via `bun test` (defaults to the `test` profile). | | `wrnexus profiles [app-dir]` | List config profiles and their `.env` files, marking the active one. | | `wrnexus compatibility check` | Check whether behavior defaults are explicitly pinned and current. | | `wrnexus compatibility explain` | Explain configured, effective, and current compatibility behavior. | | `wrnexus compatibility upgrade` | Back up config and explicitly opt into reviewed current behavior. | | `wrnexus help` | Print usage. | `wrnexus g` is an alias for `wrnexus generate`. Compatibility upgrades never happen implicitly. New applications pin `compatibilityDate` and `frameworkBehaviour`; existing applications use `wrnexus compatibility explain` before the backed-up, idempotent upgrade command. ### `wrnexus dev` Supervises a child dev-server process (from `@wrnexus/dev-server`). The child owns file watching and HMR: CSS and client-island edits update the live page over a WebSocket with no restart; when a server module changes, the child exits with a restart code and the supervisor respawns it (the browser reconnects and morphs in the new HTML). On startup it regenerates typed DB queries and typed routes (best effort). Use `--port=` to change the port (default `3000`). ```bash wrnexus dev . --port=8080 ``` ### `wrnexus build` Emits into `/dist/`: - `server.js` — a single, minified, self-contained Bun server with a **static** manifest of every page / api / realtime / middleware / component / layout module (no runtime filesystem scan or on-the-fly bundling). - `reactive.js`, `theme.css`, `theme.js`, `ui.css`, and (if present) `styles.css` — hashed, minified browser assets. - `public/` — copied verbatim. Before bundling, it regenerates typed queries for the default and every named database. Run the output with: ```bash bun dist/server.js # PORT env var optional # Generated apps also provide: npm start # Build and start together: npm run production ``` ### `wrnexus create` Scaffolds a complete v0.8 app from an inline template. The generated project includes strict TypeScript, ESLint and Prettier, editor recommendations, environment templates, database migrations, locales, schemas, tests, API/middleware/realtime examples, Tailwind and Iconify, PWA/mobile defaults, and the framework package kits. Its `wrnexus.config.ts` documents the current imports, types, stores, performance, observability, tenancy, build, navigation, theme, i18n, database, storage, realtime, security, and profile configuration. Use `bun run dev` during development, `bun run check` for the complete typecheck/lint/test/format gate, `bun run build && bun run start` for production, or `bun run production` to build and start in one command. ### `wrnexus update` `wrnexus update --latest` performs a complete project upgrade. It hands control to the exact target CLI, backs up important project files under `.wrnexus/update-backups/`, updates every `@wrnexus/*` dependency, refreshes framework-owned references, and applies every versioned syntax/config/file migration between the project version and target version. After installation it runs the project's `check` and `build` scripts; the new version is recorded only after verification succeeds. Use `--dry-run` to preview an upgrade or `--no-verify` when verification is intentionally handled elsewhere. Migrations never overwrite user-owned configuration wholesale: each release must provide a focused, idempotent transformation for any changed syntax or config contract. ```bash wrnexus create my-app ``` ### `wrnexus generate` Scaffolds a single file from a template, refusing to overwrite an existing file. Types (with aliases): `page`/`p`, `component`/`c`, `api`/`a`, `schema`/`s`. Nested names create nested paths. ```bash wrnexus generate page about # app/pages/about.wrn wrnexus generate component user-card # app/components/user-card.wrn wrnexus generate api users/list # app/api/users/list.ts wrnexus generate schema signup # app/schemas/signup.ts wrnexus generate routes # regenerate app/routes.gen.ts wrnexus generate docker # Dockerfile + compose + .dockerignore wrnexus generate mobile --mode=webview --app-id=com.example.app --app-name="Example" --url=https://app.example.com wrnexus generate mobile --mode=native ``` The mobile generator creates a separate `mobile/` package and reads `config.mobile.mode`. `webview` creates a Capacitor shell that renders the hosted WrNexus application. `native` creates a WebView-free Expo/React Native app whose screens call the shared backend through `mobile/src/wrnexus.ts`. Native screens do not render `.wrn` HTML. In either mode, run `bun install` in `mobile/`; iOS device builds require macOS and Xcode. Install official or community Capacitor plugins through the root CLI: ```bash wrnexus mobile add @capacitor/camera @capacitor/haptics wrnexus mobile sync wrnexus mobile assets # generate native icons from config.mobile.icon ``` In native mode, `mobile add` runs `expo install` and `mobile sync` runs Expo prebuild. In WebView mode they retain the Capacitor install/sync behavior. `wrnexus mobile compile` maps portable `app/pages/**/*.wrn` pages to Expo Router TSX routes. Native `bun run start` invokes this compilation automatically. Browser code can access installed plugins through the SSR-safe `@wrnexus/mobile` bridge. The command adds each plugin to both the WrNexus app (JavaScript proxy) and `mobile/` (native synchronization). `wrnexus mobile sync` also configures Android so only true network failures use the local connection-error screen. HTTP errors such as 404 and 500 keep their WrNexus response pages. ### `wrnexus eject` Copies a Wire UI component's `.wrn` source out of `@wrnexus/ui` into `app/components/`, so the app owns and can edit it (the app copy shadows the library one by name). Run with no names to list available components. It skips components that already exist in the app. ```bash wrnexus eject button card modal ``` ### `wrnexus db` Database migrations and tooling. Without a flag, commands target the **default** database (`db` in `wrnexus.config.ts`, files under `app/db/`). Pass `--db=` to target a named database (`databases.`, files under `app/db//`). | Subcommand | Purpose | | ------------------------------- | ----------------------------------------------------------------------------------- | | `db new [--from-models]` | Scaffold a migration; `--from-models` derives it from the TS models in `schema.ts`. | | `db migrate` | Apply all pending migrations. | | `db rollback` | Revert the last applied migration. | | `db status` | List applied / pending migrations. | | `db generate` | Regenerate typed queries (`queries/*.sql` → `queries.gen.ts`). | | `db seed` | Run the database's `seed.ts` (default export / `seed` function). | | `db studio [table]` | Inspect tables — list row counts, or dump the first 50 rows of one table. | ```bash wrnexus db new create_users --from-models wrnexus db migrate wrnexus db studio users wrnexus db status --db=analytics ``` ### `wrnexus workspace` and `wrnexus gateway` `workspace ` scaffolds a monorepo: complete v0.8 apps under `apps/*`, shared libraries under `packages/*`, root TypeScript/lint/format/editor/environment tooling, and a `wrnexus.workspace.ts` that maps each app to the domains it serves. `gateway` runs every app behind one port and routes by `Host` header, with optional per-app auth and gateway-wide security (trusted hosts, rate limit, security headers, access log). Newly added workspace apps use the same current scaffold. ```bash wrnexus workspace acme wrnexus gateway --port=3000 ``` For a complete production start, use the first-class workspace orchestrator: ```bash wrnexus production --host=0.0.0.0 --port=3000 ``` It builds every registered app, applies default and named-database SQL migrations when present, and starts the production gateway only after preparation succeeds. Use `--prepare-only`, `--no-build`, or `--no-migrate` when deployment stages are managed separately; `--environment=` selects another workspace environment. From a workspace root, add and register another app in one command: ```bash wrnexus workspace add reports --domain=reports.localhost bun install ``` Development gateways bind to `127.0.0.1` by default for reliable access on Windows, macOS, and Linux. Open the configured app domain on the gateway port (for example `http://localhost:3000` or `http://admin.localhost:3000`), not the internal child ports printed while apps start. Pass `--host=0.0.0.0` to accept connections from other devices. ### `wrnexus test` Runs the app's tests with `bun test`. Defaults to the `test` profile (config + `.env.test`). Pass `--watch` to re-run on change; extra flags pass straight through to `bun test`. ```bash wrnexus test . --watch ``` ## Usage ### Create and run a single application ```bash bunx @wrnexus/cli create customer-portal cd customer-portal bun install bun run dev ``` ### Add routes and shared UI to an existing app ```bash wrnexus generate page reports/monthly wrnexus generate api reports/export wrnexus generate component report-filter wrnexus generate routes ``` ### Create a multi-app workspace and add another app ```bash wrnexus workspace company-suite cd company-suite wrnexus workspace add reports --domain=reports.localhost bun install wrnexus gateway --port=3000 ``` Open `http://reports.localhost:3000`; the gateway selects `apps/reports` from the request host. ### Upgrade with migrations and verification ```bash wrnexus update --latest --dry-run wrnexus update --latest wrnexus doctor ``` Use `wrnexus doctor --fix [app-dir]` to apply conservative repairs before the health check: create missing `app/pages` and a default config, align skewed `@wrnexus/*` dependency ranges, record the current migration marker, and format only syntax-valid `.wrn` files. Invalid JSON or WRN sources are reported/skipped instead of overwritten; repeat runs are idempotent. ## Profiles Pass `--profile=` to `dev`, `build`, `db` (or set `WRNEXUS_PROFILE`) to select a config profile. The CLI publishes `WRNEXUS_PROFILE` so config loaders and the dev child pick it up, and loads that profile's `.env` cascade (`.env`, `.env.local`, `.env.`, `.env..local`) into `process.env`. ```bash wrnexus dev --profile=uat wrnexus profiles # ● development (config, .env.development) # ○ production # ○ uat (config, .env.uat) ``` ## Subpath exports `@wrnexus/cli/workspace` exposes the workspace configuration types used by `wrnexus.workspace.ts`: ```ts import type { WorkspaceConfig, WorkspaceApp } from "@wrnexus/cli/workspace"; const config: WorkspaceConfig = { security: { trustedHostsOnly: true, headers: true, accessLog: true }, apps: [{ name: "web", dir: "apps/web", domains: ["localhost", "web.localhost"] }], }; export default config; ``` ## Requirements / Notes - **Bun-only.** The CLI runs on Bun, spawns the Bun binary for the dev child and `bun test`, and the production build uses `Bun.build`. Node is not supported. - Orchestrates the rest of the framework: `@wrnexus/dev-server` (dev/prod server + gateway), `@wrnexus/router` (route + typed-routes codegen), `@wrnexus/compiler` (`.wrn` → `.ts`), `@wrnexus/db` (migrations, typed queries), `@wrnexus/styles` (config, profiles, `.env`, themes, styles), `@wrnexus/ui` (ejectable Wire UI components), `@wrnexus/validation`, `@wrnexus/csr`, and `@wrnexus/i18n`. - Reads `wrnexus.config.ts` for `db` / `databases`, `theme`, `styles`, `seo`, `security`, `i18n`, and `profiles`, and `wrnexus.workspace.ts` for the gateway. ### Exported TypeScript declarations ```ts #!/usr/bin/env bun ``` --- ## @wrnexus/compiler Documentation URL: https://wrnexusjs.dev/packages/compiler # @wrnexus/compiler ## Partial-static rendering Pages can select `render = "partial-static"` and divide their view with `` and `` boundaries. The compiler emits a build-only shell renderer that never evaluates dynamic-boundary children. `wrnexus build` expands static component mounts into `dist/partial-shells.json`, records byte/region evidence in `build-report.json`, and embeds the shell in the production route manifest. At request time the production runtime retains request-aware layouts, locale/theme metadata and security nonces while streaming dynamic regions into stable placeholders. > Compiler for the `.wrn` language — tokenizes, parses, and lowers `.wrn` page and component files to TypeScript. Part of the **WrNexus** framework — an SSR-first, Bun-native full-stack web framework. Production adapters use `analyzeRuntimeImports` before bundling. Edge, worker, service-worker, and browser targets reject Node filesystem, TCP, and process modules with `WRN-RUNTIME-CAPABILITY`. Package manifests can declare supported `wrnexus.runtimes` and required `wrnexus.requires` capabilities; discovery fails when the selected deployment cannot satisfy them. ## Server actions ```wrn action createUser using CreateUserSchema { const user = await users.create(input) invalidate("users") return user } view {
...
} ``` The compiler produces a schema-aware server registry, a fully inferred action client, and progressively enhanced form metadata. The shared runtime performs validation, authentication/permission checks, CSRF verification, serialization, invalidation reporting, and browser lifecycle events. ## Overview `@wrnexus/compiler` turns `.wrn` source into TypeScript that targets the framework's runtime primitives. A `.wrn` file declares either a `page` (a route) or a `component` (a reusable, prop-driven fragment) with blocks for `state`, `view` (plain HTML), `seo`, `style`, `functions`, `api`, `ssr`/`client` data bindings, and `realtime` websocket handlers. The pipeline is `source → Lexer → parse() → PageAst → generate() → TypeScript`. It is a build/server-side library — the WrNexus dev loader calls it to compile `.wrn` files on the fly, surfacing `ParseError` as a readable error page. Static ES module imports may appear before the root declaration. Imported values are available to server-rendered expressions, including component props: ```wrn import { appUrl } from "@wrnexus/helpers"; layout PublicLayout { view { } } ``` ## Installation ```bash bun add @wrnexus/compiler ``` > Private package — the machine must be authenticated to the `wrnexus` npm org > (a read token in `~/.npmrc`). Requires **Bun** (Node is not supported). ## API All exports come from the package root (`@wrnexus/compiler`). ### `compileWireFile(source: string): string` Compile `.wrn` source to a TypeScript module string. Throws `ParseError` on invalid input. The output is prefixed with a `// compiled from .wrn` comment. ### `compile(source: string): CompileResult` Richer entry point that returns the generated code, the AST, and any diagnostics. ```ts interface CompileResult { code: string; ast: PageAst; diagnostics: string[]; } ``` On a `ParseError` it pushes the message into `diagnostics` and re-throws. ### `parse(source: string): PageAst` Run the lexer + recursive-descent parser and return the AST. Throws `ParseError` (lexer `LexError`s are caught and rethrown as `ParseError`). ### `generate(ast: PageAst): string` Lower a `PageAst` to TypeScript. `page` ASTs become a default-export page component (plus `meta`, optional `layout`, `__wrnexusApi`/method handlers, `websocket`, and SSR/CSR data bindings); `component` ASTs become a module exporting `render(props)` and `__wrnexusComponent`. ### `Lexer` On-demand lexer for `.wrn`. Yields structural tokens and exposes raw-span readers for the parser. ```ts class Lexer { pos: number; constructor(src: string); next(): Token; // consume next structural token peek(): Token; // look ahead without consuming readPath(): string; // route path, e.g. /users/[id] readToLineEnd(): string; // rest of line (state/prop initializers) readBalancedBraces(): string; // inner text of a { ... } block, string-aware } ``` `Token` is `{ type: TokenType; value: string; pos: number }`, where `TokenType` is one of `ident`, `string`, `lbrace`, `rbrace`, `lparen`, `rparen`, `at`, `eq`, `comma`, `eof`. ### Errors | Class | Thrown by | Meaning | | ------------ | ------------------------------------------------- | --------------------------------------------------------------- | | `ParseError` | `parse`, `compile`, `compileWireFile`, `generate` | Invalid `.wrn` grammar or (rewrapped) lex failure. | | `LexError` | `Lexer` | Unexpected character / unterminated string / unbalanced braces. | ### AST types Exported type-only symbols describing the parsed tree: | Type | Description | | -------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `PageAst` | Root node including top-level `imports`, `kind`, `name`, `types`, typed `props`, typed `states`, `view`, styles, functions, data APIs, lifecycle, and routes. | | `ViewNode` | `{ type: "text"; value }` or `{ type: "element"; tag; attrs; children }`. | | `Attr` | `{ name; value; event; boolean? }` — `event` marks `@event` bindings. | | `StateDecl` | `{ name; valueType?; expr }` — a typed `state x: Type = ` declaration. | | `PropDecl` | `{ name; valueType?; required; default }` — a typed prop declaration. | | `SeoBlock` | `Record` from the `seo { ... }` block. | | `ApiBlock` | `{ method; path; body }` — a top-level `api METHOD /path { ... }`. | | `DataApiBlock` | `{ mode; name; method; path; body }` — an `api` inside an `ssr`/`client` block. | | `DataMode` | `"ssr" \| "client"`. | | `ModeFunctionsBlock` | `{ mode; body }` — a `functions { ... }` inside an `ssr`/`client` block. | | `RealtimeBlock` | `{ name; handlers }` — a `realtime { on evt(args) { ... } }` block. | ## Usage Compile a page: ```ts import { compileWireFile } from "@wrnexus/compiler"; const ts = compileWireFile(` page Home { state count = 0 seo { title = "Home" description = "Welcome" } view { } } `); // ts is a TypeScript module: exports `meta`, and a default page component // returning an HTML string, wrapped in a data-scope for the reactive runtime. ``` Inspect the AST and diagnostics: ```ts import { compile, ParseError } from "@wrnexus/compiler"; try { const { code, ast, diagnostics } = compile(source); console.log(ast.kind, ast.name, ast.states.length); } catch (err) { if (err instanceof ParseError) console.error(err.message); } ``` Drive the parse/codegen stages directly: ```ts import { parse, generate } from "@wrnexus/compiler"; const ast = parse(componentSource); // ast.kind === "component" const module = generate(ast); // exports render(props) + __wrnexusComponent ``` Use the lexer standalone: ```ts import { Lexer } from "@wrnexus/compiler"; const lx = new Lexer("page Home {"); lx.next(); // { type: "ident", value: "page", pos: 0 } lx.next(); // { type: "ident", value: "Home", pos: 5 } lx.next(); // { type: "lbrace", value: "{", pos: 10 } ``` ## The `.wrn` language (as parsed) A file opens with `page ` or `component ` followed by a `{ ... }` body containing zero or more members: - `layout = ""` — selects `app/layouts/.wrn` (pages only). - `types { }` — reusable interfaces and aliases for the current file. - `props { name: Type = ... }` — typed component props. Omit `= ` to make a prop required. Legacy inferred props remain supported. - `@event name = function` inside `props` — declares a public component event. Emit it from component behavior with `name(detail)` or `$emit("name", detail)`, and consume it with ``. - `state : Type = ` — typed reactive state seeded from a raw JS expression, including native array and object literals. The annotation is optional for backward compatibility. - `view { }` — plain HTML with `{expr}` interpolation in text and attributes, JSX-style component props such as `items={items}`, `items={[...]}`, and `options={{...}}`, hyphenated attributes, boolean attributes, `@event="..."` client bindings, and ``. Structured component props are serialized safely for SSR; expressions that reference `state` retain their initial value and update reactively in the browser. - Client functions automatically commit state changed by `setTimeout` callbacks. For other deferred callbacks (observers, third-party APIs, or detached promise callbacks), call the injected `commit()` function after changing local state; returning/awaiting a promise also commits through the normal function boundary. - `seo { key = "value" ... }` — metadata merged into the generated `meta`. - `style { }` — inlined page/component stylesheet (repeatable). - `functions { }` — helpers with typed parameters and return values. Types remain in server output and are safely erased from browser behavior code. - `api { }` — route handler, lowered to a `METHOD` export (repeatable). - `ssr { ... }` / `client { ... }` — data blocks holding `api { ... }` bindings and their own `functions { ... }`. - `realtime { on () { } ... }` — websocket handlers, lowered to a `websocket` export. `view` markup is parsed by a lenient dedicated HTML parser (`parseHtmlView`); HTML void elements (`
`, ``, …) take no closing tag. Line comments (`//`) are skipped by the lexer. ## Requirements / Notes - Pure TypeScript with no runtime dependencies; runs under **Bun** as part of the WrNexus toolchain (Node is not supported). - Generated modules target WrNexus runtime primitives (`data-scope`, `data-text`, `data-on-*`, `data-for`, `data-component`, `__wrnexus*`/`__wire*` helpers) — consume the output within a WrNexus app, e.g. via `@wrnexus/core`'s dev loader. ### Exported TypeScript declarations ```ts import { PageAst as PageAst$1, StructuredImportDecl, WrnDiagnostic } from '@wrnexus/syntax'; export { ActionBlock, ApiBlock, Attr, ComputedDecl, DataApiBlock, DataMode, EffectBlock, EventDecl, FormatWrnOptions, LexError, Lexer, LoadBlock, ModeFunctionsBlock, OutputDecl, PageAst, ParseError, PropDecl, RealtimeBlock, RuntimeFunctionDecl, SeoBlock, StateDecl, StateRuntime, StoreKind, StructuredImportDecl, ViewNode, WrnDiagnostic, assertValidAst, diagnose, diagnosticFromError, eraseFunctionTypes, formatDiagnostic, formatWrn, inferredRuntimeType, parse, runtimeTypeOf } from '@wrnexus/syntax'; import { PageAst } from '@wrnexus/syntax/parser'; /** * Code generation: lower a `.wrn` AST to TypeScript that targets the framework's * existing primitives. * * state -> a `data-scope` declaration consumed by the runtime * view -> an HTML string returned by a page component * @event="..." -> data-on-="..." * "...{expr}..." -> text kept verbatim ({expr} is mustache for runtime) * api="" -> SSR/client data binding declared in a mode block * ssrGet/ssrText -> legacy server-side API fetch + render * csrGet/csrText -> legacy browser-side API fetch + render * style -> tagged local stylesheet metadata promoted by SSR * functions -> server-only helpers for API/realtime code * api M /p {b} -> export const M = async (ctx) => { b } * realtime {..} -> export const websocket = { evt(ws, ...args) { b } } */ declare function generate(ast: PageAst): string; interface ComponentContractMetadata { name: string; kind: PageAst$1["kind"]; props: Array<{ name: string; type: string; required: boolean; default?: string; options?: string[]; }>; outputs: Array<{ name: string; payloadName?: string; payloadType?: string; }>; functions: Array<{ name: string; runtime: string; async: boolean; parameters: Array<{ name: string; type: string; optional: boolean; }>; returnType: string; }>; states: Array<{ name: string; runtime: string; type: string; initializer: string; }>; computed: Array<{ name: string; type: string; expression: string; }>; imports: Array<{ source: string; typeOnly: boolean; defaultImport?: string; namedImports: string[]; }>; } declare function createComponentContract(ast: PageAst$1): ComponentContractMetadata; interface RpcManifestEntry { id: string; component: string; function: string; parameters: Array<{ name: string; type: string; optional: boolean; }>; returnType: string; } declare function rpcManifest(ast: PageAst$1): RpcManifestEntry[]; declare function generateServerFunctionsModule(ast: PageAst$1): string; interface CompileTargets { server: string; browser: string; declarations: string; contract: ReturnType; rpc: ReturnType; } declare function generateTargets(ast: PageAst$1): CompileTargets; declare function generateBrowserModule(ast: PageAst$1): string; declare function generateDeclarations(ast: PageAst$1): string; declare function generateStoreModule(ast: PageAst$1): string; /** Standalone browser artifact for an imported `.wrn` store. */ declare function generateStoreBrowserModule(ast: PageAst$1): string; type ImportMode = "legacy" | "compatible" | "explicit"; interface ImportResolverOptions { appRoot: string; mode?: ImportMode; aliases?: Record; } interface ResolvedImport { declaration: StructuredImportDecl; resolved?: string; diagnostic?: { code: string; message: string; severity: "error" | "warning"; }; } declare function resolveWrnImport(declaration: StructuredImportDecl, importer: string, options: ImportResolverOptions): ResolvedImport; declare function resolveWrnImports(declarations: StructuredImportDecl[], importer: string, options: ImportResolverOptions): ResolvedImport[]; interface WrnSourceMapEntry { generatedLine: number; sourceLine: number; sourceColumn: number; kind: string; } interface WrnSourceMap { version: 1; source: string; generated: string; mappings: WrnSourceMapEntry[]; } declare function createWrnSourceMap(source: string, generated: string): WrnSourceMap; type RouteExecutionKind = "static" | "static-interactive" | "request-ssr" | "authenticated-ssr" | "streaming-ssr" | "dynamic"; interface RuntimeRequirements { kind: RouteExecutionKind; canPrerender: boolean; needsClientRuntime: boolean; needsServerRuntime: boolean; hydrationStrategy: string | null; reasons: string[]; optimization: OptimizationReport; cachePolicy: Record; requiredPermission: string | null; } interface OptimizationReport { staticNodes: number; reactiveRegions: number; eliminatedBranches: number; unusedState: string[]; unusedHandlers: string[]; constantProps: string[]; unusedLocalCssClasses: string[]; batchableStateUpdates: number; memoizableComponents: string[]; preloadDependencies: string[]; serverOnlyModules: string[]; } /** Safe compile-time folding for literal conditional branches. */ declare function optimizeAst(ast: PageAst$1): { ast: PageAst$1; eliminatedBranches: number; }; declare function analyzeOptimizations(ast: PageAst$1): OptimizationReport; declare function analyzeRuntimeRequirements(ast: PageAst$1): RuntimeRequirements; type DeploymentRuntime = "bun" | "node" | "edge" | "worker" | "service-worker" | "browser"; type RuntimeCapability = "filesystem" | "tcp" | "process" | "websocket" | "crypto" | "streams" | "background-tasks"; interface RuntimeCapabilityDiagnostic { code: "WRN-RUNTIME-CAPABILITY"; runtime: DeploymentRuntime; module: string; capability: RuntimeCapability; message: string; } declare function runtimeCapabilities(runtime: DeploymentRuntime): ReadonlySet; declare function analyzeRuntimeImports(source: string, runtime: DeploymentRuntime): RuntimeCapabilityDiagnostic[]; declare class NativeCompileError extends Error { constructor(message: string); } /** Compile a parsed `.wrn` page to an Expo Router React Native screen. */ declare function generateNative(ast: PageAst): string; interface CompilationCacheEntry extends CompileResult { key: string; file: string; sourceHash: string; createdAt: number; } interface CompilationCacheOptions { maxEntries?: number; now?: () => number; } interface CompilationCache { compile(source: string, file?: string, salt?: string): CompilationCacheEntry; get(key: string): CompilationCacheEntry | undefined; invalidate(file?: string): number; clear(): void; size(): number; stats(): { hits: number; misses: number; entries: number; }; } declare function compilationKey(source: string, file?: string, salt?: string): string; declare function createCompilationCache(options?: CompilationCacheOptions): CompilationCache; declare class DependencyGraph { #private; set(file: string, dependencies: Iterable): void; remove(file: string): void; dependencies(file: string): string[]; dependents(file: string): string[]; affected(file: string): string[]; } /** * @wrnexus/compiler — the `.wrn` language compiler. * * Parsing and language diagnostics are provided by the canonical * `@wrnexus/syntax` package. This package owns platform-specific codegen. */ interface CompileResult { code: string; ast: PageAst$1; /** Backward-compatible plain diagnostic messages. */ diagnostics: string[]; /** Structured diagnostics for editors, CI, and the DevToolbar. */ richDiagnostics: WrnDiagnostic[]; } /** Compile `.wrn` source into an Expo Router React Native screen. */ declare function compileNativeWireFile(source: string): string; /** * Compile `.wrn` source into TypeScript source. Errors include a stable code, * source location, code frame, and actionable hint whenever available. */ declare function compileWireFile(source: string, filePath?: string): string; /** Richer entry point returning the AST and structured diagnostics. */ declare function compile(source: string, filePath?: string): CompileResult; export { type CompilationCache, type CompilationCacheEntry, type CompilationCacheOptions, type CompileResult, DependencyGraph, type DeploymentRuntime, NativeCompileError, type OptimizationReport, type RouteExecutionKind, type RuntimeCapability, type RuntimeCapabilityDiagnostic, type RuntimeRequirements, analyzeOptimizations, analyzeRuntimeImports, analyzeRuntimeRequirements, compilationKey, compile, compileNativeWireFile, compileWireFile, createCompilationCache, createComponentContract, createWrnSourceMap, generate, generateBrowserModule, generateDeclarations, generateNative, generateServerFunctionsModule, generateStoreBrowserModule, generateStoreModule, generateTargets, optimizeAst, resolveWrnImport, resolveWrnImports, rpcManifest, runtimeCapabilities }; ``` --- ## @wrnexus/content Documentation URL: https://wrnexusjs.dev/packages/content # @wrnexus/content Typed content collections for Markdown/MDX-like documents and remote CMS records. Collections validate frontmatter through any `{ parse(input) }` schema, render escaped HTML, and expose draft preview, versions, references, headings, search indexes, pagination, RSS and sitemaps. ```ts const posts = defineCollection({ name: "posts", schema: PostSchema, loader: localContentLoader("content/posts"), previewToken: process.env.CONTENT_PREVIEW_TOKEN, }); const published = await posts.load(); const preview = await posts.load({ previewToken: request.headers.get("x-preview-token") ?? "" }); ``` Remote systems implement `CmsAdapter`, use `cmsContentLoader`, or return JSON records through `remoteContentLoader`. Markdown HTML is escaped by default; raw executable HTML is never trusted. ### Exported TypeScript declarations ```ts type MdxComponent = (props: Record, children: string) => string; /** Execute explicitly registered MDX components without evaluating arbitrary JavaScript. */ declare function renderMdxComponents(source: string, components: Record): string; interface SyntaxLanguageBundle { highlight(source: string): string; } declare function createIncrementalHighlighter(loaders: Record SyntaxLanguageBundle | Promise>): { languages: () => string[]; highlight(language: string, source: string): Promise; render(html: string): Promise; }; interface VendorAdapterOptions { fetch?: (input: string | URL | Request, init?: RequestInit) => Promise; token?: string; map?: (entry: any) => { id: string; content: string; source?: string; }; } declare function contentfulAdapter(space: string, environment?: string, options?: VendorAdapterOptions): { list(collection: string): Promise; }; declare function sanityAdapter(project: string, dataset: string, options?: VendorAdapterOptions & { apiVersion?: string; }): { list(collection: string): Promise; }; declare function strapiAdapter(baseUrl: string, options?: VendorAdapterOptions): { list(collection: string): Promise; }; interface ContentSchema { parse(input: unknown): T; } interface ContentEntry> { id: string; slug: string; collection: string; data: T; body: string; html: string; excerpt: string; headings: ContentHeading[]; draft: boolean; version?: string; source: string; } interface ContentHeading { depth: number; text: string; slug: string; } interface ContentLoaderResult { id: string; source: string; content: string; } interface ContentLoader { load(): ContentLoaderResult[] | Promise; } interface ContentCollectionOptions { name: string; schema: ContentSchema; loader: ContentLoader; includeDrafts?: boolean; previewToken?: string; references?: Record>; } interface ContentCollection { name: string; load(options?: { drafts?: boolean; previewToken?: string; version?: string; }): Promise[]>; get(id: string, options?: { drafts?: boolean; previewToken?: string; version?: string; }): Promise | null>; } declare function parseFrontmatter(source: string): { data: Record; body: string; }; declare function renderMarkdown(source: string): { html: string; headings: ContentHeading[]; excerpt: string; }; declare function localContentLoader(directory: string): ContentLoader; declare function remoteContentLoader(url: string, options?: { fetch?: (input: string | URL | Request, init?: RequestInit) => Promise; headers?: HeadersInit; }): ContentLoader; declare function defineCollection(options: ContentCollectionOptions): ContentCollection; declare function resolveContentReference(collections: Record>, reference: string): Promise | null>; declare function paginateContent(entries: T[], page?: number, pageSize?: number): { items: T[]; page: number; pageSize: number; total: number; totalPages: number; hasNext: boolean; hasPrevious: boolean; }; declare function createSearchIndex(entries: ContentEntry[]): { id: string; slug: string; title: string; text: string; }[]; declare function searchContent(index: ReturnType, query: string): { id: string; slug: string; title: string; text: string; }[]; declare function contentSitemap(entries: ContentEntry[], baseUrl: string): string; declare function contentRss(entries: ContentEntry[], options: { title: string; baseUrl: string; description?: string; }): string; interface CmsAdapter { list(collection: string): Promise>; } declare function cmsContentLoader(adapter: CmsAdapter, collection: string): ContentLoader; export { type CmsAdapter, type ContentCollection, type ContentCollectionOptions, type ContentEntry, type ContentHeading, type ContentLoader, type ContentLoaderResult, type ContentSchema, type MdxComponent, type SyntaxLanguageBundle, type VendorAdapterOptions, cmsContentLoader, contentRss, contentSitemap, contentfulAdapter, createIncrementalHighlighter, createSearchIndex, defineCollection, localContentLoader, paginateContent, parseFrontmatter, remoteContentLoader, renderMarkdown, renderMdxComponents, resolveContentReference, sanityAdapter, searchContent, strapiAdapter }; ``` --- ## @wrnexus/core Documentation URL: https://wrnexusjs.dev/packages/core # @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. ### Exported TypeScript declarations ```ts export { Fragment, Html, Component as JSXComponent, Props as JSXProps, Renderable, jsx, jsxs, mustache } from './jsx-runtime.js'; interface Tenant { id: string; slug?: string; name?: string; metadata?: Record; } interface TenantResource { tenantId: string; } interface TenantMembership { tenantId: string; userId: string; roles?: string[]; workspaceIds?: string[]; } interface TenantAuditEvent { tenantId: string; action: string; actorId?: string; resource?: string; metadata?: Record; createdAt: number; } interface TenantQuota { tenantId: string; resource: string; limit: number; usage: number; } interface TenantDirectoryStore { putMembership(value: TenantMembership): Promise; getMembership(tenantId: string, userId: string): Promise; listMemberships(tenantId: string): Promise; putQuota(value: TenantQuota): Promise; getQuota(tenantId: string, resource: string): Promise; } type TenantResolver = (ctx: Context) => Tenant | null | Promise; interface TenantMiddlewareOptions { required?: boolean; status?: number; } declare function tenantMiddleware(resolveTenant: TenantResolver, options?: TenantMiddlewareOptions): Middleware; declare function tenantFromSubdomain(lookup: (slug: string, ctx: Context) => Tenant | null | Promise, rootDomains?: string[]): TenantResolver; declare function tenantFromDomain(lookup: (domain: string, ctx: Context) => Tenant | null | Promise): TenantResolver; declare function tenantFromPath(lookup: (slug: string, ctx: Context) => Tenant | null | Promise, prefix?: string): TenantResolver; /** Header resolution is intentionally opt-in and must only be used behind a trusted proxy. */ declare function tenantFromHeader(lookup: (id: string, ctx: Context) => Tenant | null | Promise, header?: string): TenantResolver; declare function tenantFromSession(resolveId: (ctx: Context) => string | null | Promise, lookup: (id: string, ctx: Context) => Tenant | null | Promise): TenantResolver; declare function composeTenantResolvers(...resolvers: TenantResolver[]): TenantResolver; declare function requireTenant(ctx: Context): Tenant; /** Wrap a repository so every operation receives the current tenant id. */ declare function tenantScope(tenant: Tenant, repository: T): T & { tenantId: string; }; declare function assertTenantAccess(tenant: Tenant, resource: TenantResource): void; declare function tenantKey(tenant: Tenant | string, ...parts: Array): string; declare function createTenantDirectory(options?: { audit?: (event: TenantAuditEvent) => void | Promise; now?: () => number; }): { addMembership(membership: TenantMembership, actorId?: string): Promise; membership(tenantId: string, userId: string): TenantMembership | null; switchWorkspace(tenantId: string, userId: string, workspaceId: string): Promise<{ tenantId: string; workspaceId: string; }>; setQuota(tenantId: string, resource: string, limit: number): void; enforceQuota(tenantId: string, resource: string, usage: number, requested?: number): { usage: number; requested: number; limit: number | undefined; }; }; declare function memoryTenantDirectoryStore(): TenantDirectoryStore; declare function createPersistentTenantDirectory(store: TenantDirectoryStore, options?: { audit?: (event: TenantAuditEvent) => void | Promise; now?: () => number; }): { addMembership(membership: TenantMembership, actorId?: string): Promise; membership: (tenantId: string, userId: string) => Promise; memberships: (tenantId: string) => Promise; switchWorkspace(tenantId: string, userId: string, workspaceId: string): Promise<{ tenantId: string; workspaceId: string; }>; setQuota(tenantId: string, resource: string, limit: number, usage?: number): Promise; consumeQuota(tenantId: string, resource: string, requested: number): Promise; }; interface TenantSqlClient { query>(sql: string, parameters?: unknown[]): Promise<{ rows: T[]; }>; } declare function postgresTenantDirectoryStore(db: TenantSqlClient): TenantDirectoryStore; declare const POSTGRES_TENANT_DIRECTORY_SCHEMA = "CREATE TABLE IF NOT EXISTS wrnexus_tenant_memberships (tenant_id text NOT NULL,user_id text NOT NULL,roles jsonb NOT NULL DEFAULT '[]',workspace_ids jsonb NOT NULL DEFAULT '[]',PRIMARY KEY (tenant_id,user_id)); CREATE TABLE IF NOT EXISTS wrnexus_tenant_quotas (tenant_id text NOT NULL,resource text NOT NULL,quota_limit bigint NOT NULL,usage bigint NOT NULL DEFAULT 0,PRIMARY KEY (tenant_id,resource));"; declare function migrateTenants(tenants: T[], migrate: (tenant: T) => void | Promise, options?: { concurrency?: number; continueOnError?: boolean; }): Promise<{ migrated: string[]; failed: { tenantId: string; error: string; }[]; }>; interface SpanRecord { name: string; startTime: number; endTime?: number; durationMs?: number; status?: "ok" | "error"; attributes: Record; error?: unknown; } interface Tracer { startSpan(name: string, attributes?: SpanRecord["attributes"]): Span; records(): readonly SpanRecord[]; } interface Span { setAttribute(name: string, value: string | number | boolean): void; end(status?: "ok" | "error", error?: unknown): SpanRecord; } declare function createTracer(clock?: () => number): Tracer; declare function withSpan(tracer: Tracer, name: string, run: (span: Span) => T | Promise, attributes?: SpanRecord["attributes"]): Promise; interface TracingMiddlewareOptions { /** Include W3C Server-Timing response headers. Defaults to true. */ serverTiming?: boolean; /** Fraction of requests to trace, from 0 to 1. Defaults to 1. */ sampleRate?: number; /** Called after a traced response completes. */ onComplete?: (ctx: Context, records: readonly SpanRecord[]) => void | Promise; } declare function tracingMiddleware(tracerFactory?: (ctx: Context) => Tracer, options?: TracingMiddlewareOptions): Middleware; interface CookieOptions { path?: string; domain?: string; maxAge?: number; expires?: Date | string; httpOnly?: boolean; secure?: boolean; sameSite?: "Strict" | "Lax" | "None" | "strict" | "lax" | "none"; } interface CookieStore { get(name: string): string | undefined; getAll(): Record; has(name: string): boolean; set(name: string, value: string, options?: CookieOptions): void; delete(name: string, options?: CookieOptions): void; headers(): string[]; } interface SessionStore { id(): string; get(key: string): T | undefined; getAll(): Record; set(key: string, value: unknown): void; delete(key: string): void; /** Issue a fresh session id, keeping the data — defends against fixation. */ regenerate(): void; clear(): void; } interface LocalStorageSnapshot { get(key: string): string | undefined; getAll(): Record; has(key: string): boolean; } interface SessionPolicy { cookieName?: string; idleTimeoutMs?: number; absoluteTimeoutMs?: number; sameSite?: NonNullable; secure?: boolean; } declare function setSessionPolicy(policy: SessionPolicy): void; /** A stored session: its data plus an absolute expiry timestamp (ms). */ interface SessionEntry { data: Record; expiresAt: number; /** Creation time used for the absolute session lifetime. Optional for old backends. */ createdAt?: number; lastAccessAt?: number; } /** * Pluggable session persistence. The default is process-local memory; swap in a * shared backend (Redis, SQL, etc.) via `setSessionBackend` so sessions survive * restarts and work across multiple instances. Methods are synchronous, so a * backend must be sync (e.g. `bun:sqlite`); async stores need a load/save * wrapper around the request (future work). */ interface SessionBackend { get(id: string): SessionEntry | undefined; set(id: string, entry: SessionEntry): void; delete(id: string): void; /** Optional: drop expired entries. Called periodically by the store. */ gc?(now: number): void; } /** Replace the session persistence backend (call once at startup). */ declare function setSessionBackend(backend: SessionBackend): void; /** * An ASYNC session store (Redis, a remote DB). Use it via the `loadSession` * middleware, which loads the session before the request and saves it after — * keeping the `ctx.session` API synchronous while persistence is shared across * instances. */ interface AsyncSessionBackend { load(id: string): Promise; save(id: string, entry: SessionEntry): Promise; destroy(id: string): Promise; } /** * Back `ctx.session` with an async store. Register early (before anything reads * `ctx.session`). Loads once at the start of the request and saves once at the * end; regenerate/clear destroy the old id. */ declare function loadSession(backend: AsyncSessionBackend, options?: { ttlMs?: number; absoluteTtlMs?: number; cookieName?: string; sameSite?: NonNullable; secure?: boolean; }): Middleware; /** * Core request context and middleware contracts. * * The `Context` object is the single value that flows through middleware, * pages and API routes. It is intentionally small and framework-agnostic so * it can later be reused by the `.wrn` compiler output. */ /** Translate a key for the active language, interpolating `{param}` placeholders. */ type TFunction = (key: string, params?: Record) => string; type Context = { /** The raw incoming web-standard Request. */ req: Request; /** Parsed URL of the request (pathname, query, etc.). */ url: URL; /** Active language for this request (resolved by the runtime); "" if i18n is unused. */ lang: string; /** Translate a key for the active language (identity until the runtime sets it). */ t: TFunction; /** Dynamic route params, e.g. `/users/[id]` -> `{ id: "42" }`. */ params: Record; /** * Per-request scratch space. Middleware can attach values here * (e.g. the authenticated user) and downstream handlers can read them. */ locals: Record; /** * The authenticated user for this request, or null when anonymous. Populated * by the `sessionAuth` middleware (or `logIn`); read via `getUser(ctx)`. */ user?: unknown; /** Active tenant/workspace resolved by tenant middleware. */ tenant?: Tenant; /** Request tracer installed by observability middleware. */ tracer?: Tracer; /** * The direct socket peer IP, set by the server from `server.requestIP`. This * is NOT spoofable by request headers — prefer it over `x-forwarded-for` for * rate limiting unless you run behind a trusted proxy. */ ip?: string; /** Read/write HTTP cookies for the current response. */ cookies: CookieStore; /** In-memory cookie-backed session store. */ session: SessionStore; /** Read-only localStorage snapshot sent by the browser for CSR data bindings. */ localStorage: LocalStorageSnapshot; }; /** Calls the next middleware in the chain (or the final route handler). */ type Next = () => Promise | Response; /** * Middleware runs before pages and API routes. It can: * - inspect/modify `ctx` * - short-circuit by returning a `Response` without calling `next()` * - continue by returning `await next()` */ type Middleware = (ctx: Context, next: Next) => Promise | Response; /** SEO metadata rendered into the document ``. */ type SeoConfig = { /** BCP 47 document language used on `` (default: `en`). */ lang?: string; title?: string; titleTemplate?: string; description?: string; canonical?: string; canonicalBase?: string; robots?: string; keywords?: string | string[]; image?: string; siteName?: string; type?: string; locale?: string; twitterCard?: string; twitterSite?: string; themeColor?: string; }; /** Page metadata rendered into the document ``. */ type PageMeta = SeoConfig; /** A page module's default export. Returns an HTML string for the body. */ type PageComponent = (ctx: Context) => string | Promise; /** Create a fresh context for an incoming request. */ declare function createContext(req: Request, url: URL): Context; /** Apply headers accumulated on the context, such as Set-Cookie. */ declare function withContextHeaders(ctx: Context, res: Response): Response; type ExecutionKind = "http" | "api" | "action" | "loader" | "middleware" | "realtime" | "queue" | "cron" | "webhook"; interface ResponseContext { status: number; headers: Headers; setStatus(status: number): void; } interface ExecutionContext { kind: ExecutionKind; id: string; request: Request; response: ResponseContext; user: unknown | null; session: unknown | null; tenant: Tenant | null; locale: string; timezone: string; db?: unknown; cache?: unknown; logger?: unknown; trace?: Tracer; signal: AbortSignal; deadline: Date | null; metadata: Record; authorize(permission: string): void | Promise; } interface ExecutionContextInput extends Partial> { kind: ExecutionKind; id?: string; request?: Request; response?: Partial> & { headers?: HeadersInit; }; signal?: AbortSignal; deadline?: Date | number | null; timeoutMs?: number; metadata?: Record; authorize?: (permission: string) => void | Promise; } declare function createExecutionContext(input: ExecutionContextInput): ExecutionContext; declare function executionContextFromHttp(context: Context, kind?: Extract, input?: Omit): ExecutionContext; /** * Small, dependency-free security helpers shared across packages. */ /** * Escape a string for safe interpolation into HTML text or attributes. * Used for page metadata (title/description) so untrusted values can't * break out of an attribute or inject markup. */ declare function escapeHtml(value: string): string; declare function isSafeIslandName(name: string): boolean; /** * Reject obvious path-traversal in a request path before it is ever used to * resolve a file. The router never builds file paths from request input * (routes are resolved against a pre-scanned table), but this is a cheap * defense-in-depth guard. */ declare function isSafeRequestPath(pathname: string): boolean; /** * CSRF protection via the double-submit cookie pattern plus origin/fetch * metadata validation for unsafe requests. */ declare const CSRF_COOKIE = "wire-csrf"; declare const CSRF_HEADER = "x-csrf-token"; interface CsrfProtectionOptions { /** Validate Origin when present. Defaults to true. */ verifyOrigin?: boolean; /** Additional exact origins permitted for trusted cross-origin clients. */ trustedOrigins?: string[]; /** Reject Sec-Fetch-Site: cross-site on unsafe requests. Defaults to true. */ verifyFetchMetadata?: boolean; } /** Ensure the CSRF cookie exists (readable by JS) and return its token. */ declare function csrfToken(ctx: Context): string; /** Verify an unsafe request's token, origin, and browser fetch metadata. */ declare function verifyCsrf(ctx: Context, options?: CsrfProtectionOptions): boolean; /** Middleware that 403s unsafe requests with a missing/mismatched token. */ declare function csrfProtection(options?: CsrfProtectionOptions): Middleware; /** * Authentication primitives. * * Passwords are hashed with argon2id via `Bun.password`. Sessions ride on the * existing cookie-backed `SessionStore`: logging a user in stores a serializable * user object under the "user" key, and `sessionAuth` hydrates `ctx.user` from * it on every request. `requireAuth` is a guard middleware for protected routes. */ /** Session key under which the authenticated user is stored. */ declare const SESSION_USER_KEY = "user"; /** Hash a plaintext password (argon2id). Store the returned string. */ declare function hashPassword(password: string): Promise; /** Verify a plaintext password against a stored hash. Safe against bad hashes. */ declare function verifyPassword(password: string, hash: string): Promise; /** Persist the authenticated user in the session and on the context. */ declare function logIn(ctx: Context, user: U): void; /** Clear the session and forget the current user. */ declare function logOut(ctx: Context): void; /** * The currently-authenticated user, or null. Reads `ctx.user` first (set by * `sessionAuth`/`logIn`), falling back to the session store. */ declare function getUser(ctx: Context): U | null; /** * Hydrate `ctx.user` from the session for every request. Register this early in * the middleware chain so downstream pages and API routes can read `ctx.user`. */ declare function sessionAuth(): Middleware; interface RequireAuthOptions { /** Where to redirect unauthenticated page requests. Default "/login". */ loginPath?: string; } /** * Guard that requires an authenticated user. Unauthenticated requests that look * like an API/fetch call get a 401 JSON response; page navigations get a 302 * redirect to the login page with the original target preserved as `?next=`. */ declare function requireAuth(options?: RequireAuthOptions): Middleware; /** * Fixed-window rate limiting middleware. Keeps an in-memory counter per key * (client IP by default, read from `x-forwarded-for` / `x-real-ip`) and rejects * requests over the limit with a 429 and a `Retry-After` header. Sets the * `RateLimit-Limit` / `RateLimit-Remaining` / `RateLimit-Reset` headers. * * The store is process-local; behind multiple instances use a shared store * (out of scope here). Suitable as-is for single-process apps and dev. */ interface RateLimitOptions { /** Window length in milliseconds. Default 60_000 (1 minute). */ windowMs?: number; /** Max requests allowed per key per window. Default 60. */ max?: number; /** Derive the bucket key from the request. Default: client IP. */ key?: (ctx: Context) => string; /** * Trust `x-forwarded-for` / `x-real-ip` for the client IP. Default false — * those headers are attacker-spoofable, so by default we key on the direct * socket peer (`ctx.ip`). Enable ONLY when behind a proxy that overwrites * these headers (nginx, a load balancer, Cloudflare). */ trustProxy?: boolean; /** Body returned on 429. Default "Too Many Requests". */ message?: string; /** Emit RateLimit-* headers. Default true. */ headers?: boolean; /** Persistence for the counters. Default: process-local memory. */ store?: RateLimitStore; /** Maximum in-memory keys before oldest buckets are evicted. Ignored for custom stores. */ maxKeys?: number; } interface Bucket { count: number; resetAt: number; } /** * Pluggable rate-limit counter store. The default is process-local memory; swap * in a shared store (Redis/SQL) so limits hold across instances. `hit` records * one request for `key` in the current window and returns the running bucket. * It may be async (e.g. a Redis INCR + PEXPIRE) — the middleware awaits it. */ interface RateLimitStore { hit(key: string, windowMs: number, now: number): Bucket | Promise; } declare function rateLimit(options?: RateLimitOptions): Middleware; /** Non-spoofable key: the direct socket peer IP (set by the server). */ declare function peerKey(ctx: Context): string; /** Proxy-aware key: trusts `x-forwarded-for` / `x-real-ip`, else the peer IP. */ declare function proxyKey(ctx: Context): string; /** @deprecated Use `peerKey` (default) or `proxyKey`. Kept for compatibility. */ declare const defaultKey: typeof proxyKey; /** * Structured request logging middleware. Emits one record per request with a * request id, method, path, status, and duration — as pretty text (dev) or JSON * (production/log aggregation). The request id is stored on `ctx.locals` so * downstream handlers can correlate their own logs. */ interface RequestRecord { time: string; id: string; method: string; path: string; status: number; durationMs: number; } interface RequestLoggerOptions { /** "pretty" (default) for humans, "json" for machines. */ format?: "pretty" | "json"; /** Where each finished record goes. Default console.log. */ sink?: (line: string, record: RequestRecord) => void; /** ctx.locals key for the request id. Default "requestId". */ requestIdKey?: string; /** Clock injection for tests. Default Date.now. */ now?: () => number; } declare function requestLogger(options?: RequestLoggerOptions): Middleware; /** * Caching primitives: * - `TTLCache` — a small in-memory time-to-live cache with `getOrLoad`, for * memoising expensive data (query results, computed pages). * - HTTP helpers — `cacheControl` to build a directive, `withCacheControl` to * apply it, and `etag` / `notModified` for conditional requests (304s). */ declare class TTLCache { private readonly ttlMs; private store; private loading; private revisions; private generation; constructor(ttlMs?: number); get(key: string): V | undefined; set(key: string, value: V, ttlMs?: number): void; /** Return the cached value or compute, cache, and return it. */ getOrLoad(key: string, loader: () => Promise | V, ttlMs?: number): Promise; delete(key: string): void; clear(): void; get size(): number; } interface CacheControlOptions { /** max-age in seconds. */ maxAge?: number; /** s-maxage (shared/CDN cache) in seconds. */ sMaxAge?: number; /** Mark private (per-user) rather than public. */ private?: boolean; /** no-store: never cache. Overrides other directives. */ noStore?: boolean; /** no-cache: revalidate before use. */ noCache?: boolean; /** stale-while-revalidate window in seconds. */ staleWhileRevalidate?: number; /** stale-if-error window in seconds. */ staleIfError?: number; immutable?: boolean; } /** Build a Cache-Control header value from options. */ declare function cacheControl(options: CacheControlOptions): string; /** Apply a Cache-Control header to a response (returns the same response). */ declare function withCacheControl(res: Response, options: CacheControlOptions): Response; /** A stable, quoted ETag for a string/bytes body (FNV-1a, weak by default). */ declare function etag(body: string | ArrayBuffer | Uint8Array, weak?: boolean): string; /** True when the request's If-None-Match matches the given ETag (send a 304). */ declare function notModified(req: Request, tag: string): boolean; /** * File upload helpers. The legacy `saveUpload` keeps the original sanitized * filename for compatibility. New applications should use `saveUploadSecure`, * which stores a random name and supports content inspection/scanning hooks. */ declare class UploadError extends Error { readonly code: string; constructor(message: string, code?: string); } interface UploadInspectionResult { allowed: boolean; detectedType?: string; reason?: string; } type UploadInspector = (input: { file: File; bytes: Uint8Array; filename: string; }) => UploadInspectionResult | Promise; type UploadScanner = (input: { file: File; bytes: Uint8Array; filename: string; }) => boolean | { clean: boolean; reason?: string; } | Promise; interface SaveUploadOptions { /** Destination directory. Keep this outside the public web root. */ dir: string; /** Reject files larger than this many bytes. */ maxBytes?: number; /** Allowed MIME types (e.g. "image/png") and/or extensions (e.g. ".png"). */ allowedTypes?: string[]; /** Choose the stored filename. Default: the sanitised original name. */ filename?: (file: File) => string; /** Content/magic-byte inspection hook. */ inspect?: UploadInspector; /** Malware scanning hook. */ scan?: UploadScanner; /** Called after validation but before persistence. */ beforeSave?: (input: { file: File; bytes: Uint8Array; filename: string; }) => void | Promise; } interface SecureUploadOptions extends Omit { /** Preserve the original sanitized name instead of a random server name. */ preserveOriginalName?: boolean; /** Optional custom secure filename generator. */ filename?: (file: File) => string; /** Preserve a conservative extension on random filenames. Defaults to true. */ preserveExtension?: boolean; } interface SavedUpload { path: string; filename: string; size: number; type: string; detectedType?: string; } /** All `File` values in a parsed form, with their field names. */ declare function collectUploads(form: FormData, options?: { maxFiles?: number; maxTotalBytes?: number; }): { field: string; file: File; }[]; /** Validate and write one uploaded file using a compatibility filename policy. */ declare function saveUpload(file: File, options: SaveUploadOptions): Promise; /** Store an upload under a random server-generated name by default. */ declare function saveUploadSecure(file: File, options: SecureUploadOptions): Promise; /** Strip directory separators, traversal, and control chars from a filename. */ declare function sanitizeFilename(name: string): string; declare function randomUploadFilename(originalName?: string, preserveExtension?: boolean): string; declare function secureDownloadHeaders(filename: string, type?: string): Headers; /** * Streaming response primitives. * * `streamResponse` turns a (sync or async) iterable of strings/bytes into a * streaming `Response` — the basis for streaming SSR (send the shell, then flush * page chunks as they render) and any progressively-generated output. `sse` * builds a Server-Sent Events stream from an async iterable of events. * * API routes and pages can already return a `Response` with a `ReadableStream` * body and the framework streams it unbuffered; these helpers just make the * common cases ergonomic. */ interface StreamResponseInit { status?: number; headers?: HeadersInit; /** Content-Type; default "text/html; charset=utf-8". */ contentType?: string; } type Chunk = string | Uint8Array; type ChunkSource = Iterable | AsyncIterable; /** Build a streaming Response from an (async) iterable of chunks. */ declare function streamResponse(source: ChunkSource, init?: StreamResponseInit): Response; interface ServerSentEvent { data: string; event?: string; id?: string; /** Client reconnection hint in milliseconds. */ retry?: number; } /** Build a Server-Sent Events (text/event-stream) Response from events. */ declare function sse(source: Iterable | AsyncIterable): Response; /** * Realtime rooms. * * A file in `app/realtime/` exports `default defineRoom({ onConnect, onMessage, * onLeave })` and is served at `ws://host/realtime/`. The framework's * client runtime (`/__wrnexus/realtime.js`) handles the browser side, so pages * ship NO hand-written WebSocket code. * * Handlers get a `RoomClient` with everything you need: * client.send(msg) → this connection * client.broadcast(msg) → everyone else in the room * client.room.broadcast(msg) → everyone (incl. sender) * client.to(id | ids).send(msg) → specific connection(s) * client.toUser(u | users).send() → a user / selected users (all their tabs) * client.user = "u1" → identify a connection for targeting * client.data / client.room.state → per-connection / shared room state * * The dynamic route `app/realtime/[room].ts` gives one handler many independent * rooms — `/realtime/lobby` and `/realtime/game-7` are separate room instances. */ interface RawSocket { send(data: string): unknown; close(code?: number, reason?: string): void; } interface RealtimeSocket { readonly data: Data; send(data: string | Uint8Array): number; subscribe(topic: string): void; unsubscribe(topic: string): void; publish(topic: string, data: string | Uint8Array): number; isSubscribed(topic: string): boolean; close(code?: number, reason?: string): void; } interface RealtimeHandler { open?(ws: RealtimeSocket): void | Promise; message?(ws: RealtimeSocket, message: string | Uint8Array): void | Promise; close?(ws: RealtimeSocket, code?: number, reason?: string): void | Promise; drain?(ws: RealtimeSocket): void | Promise; } interface Target { /** Send a message (objects are JSON-serialized). */ send(message: unknown): void; } interface Room> { readonly name: string; /** Shared, in-memory room state (lives while ≥1 client is connected). */ readonly state: Record; /** All connected clients. */ clients(): RoomClient[]; /** Number of connected clients. */ count(): number; /** Send to everyone in the room, including the sender. */ broadcast(message: unknown): void; /** Target specific connection id(s). */ to(id: string | string[]): Target; /** Target a user / users by identity (reaches all their connections). */ toUser(user: string | string[]): Target; } interface RoomClient> { /** Unique per connection (a tab). */ readonly id: string; /** App identity for targeting; assign it in `onConnect`. */ user: string | undefined; /** Query params from the connection URL. */ readonly query: Record; /** Per-connection scratch state. */ readonly data: TData; readonly room: Room; /** Send to THIS connection. */ send(message: unknown): void; /** Send to everyone else in the room. */ broadcast(message: unknown): void; /** Target specific connection id(s). */ to(id: string | string[]): Target; /** Target a user / users by identity. */ toUser(user: string | string[]): Target; /** Close this connection. */ close(code?: number, reason?: string): void; } /** Info available when authorizing a connection, before it is accepted. */ interface RoomAuthInfo { /** Authenticated session user id, or `?user=` — undefined when anonymous. */ user?: string; /** Connection URL query params. */ query: Record; /** The upgrade request's headers (cookies, etc.). */ headers: Headers; } interface RealtimeSecurityOptions { /** Maximum inbound or outbound serialized message size. Defaults to 64 KiB. */ maxMessageBytes?: number; /** Maximum messages accepted per connection per rolling second. Defaults to 30. */ maxMessagesPerSecond?: number; /** Maximum live connections in one room. Defaults to 1,000. */ maxConnectionsPerRoom?: number; /** Maximum connections for one authenticated user in a room. Defaults to 10. */ maxConnectionsPerUser?: number; /** Reject anonymous connections before onConnect. */ requireUser?: boolean; /** Maximum nested JSON depth. Defaults to 32. */ maxJsonDepth?: number; /** Optional message schema/authorization predicate. */ validateMessage?(message: unknown, client: RoomClient): boolean | Promise; /** Called when a connection is rejected or closed for a policy violation. */ onViolation?(reason: string, client?: RoomClient): void; } interface RoomHandlers, TMessage = any> { /** Per-room abuse and payload controls. */ security?: RealtimeSecurityOptions; /** * Gate the connection BEFORE it is accepted. Return false to reject the * upgrade with 403 (e.g. `authorize: (info) => !!info.user` to require auth). */ authorize?(info: RoomAuthInfo): boolean | Promise; /** A client connected (a new tab joined the room). */ onConnect?(client: RoomClient): void | Promise; /** A message arrived (JSON is parsed; non-JSON arrives as a string). */ onMessage?(client: RoomClient, message: TMessage): void | Promise; /** A client disconnected. */ onLeave?(client: RoomClient): void | Promise; } interface RoomDefinition, TMessage = any> { readonly __wrnexusRoom: true; readonly handlers: RoomHandlers; } /** Define a realtime room. Export the result as the `default` of a realtime file. */ declare function defineRoom, TMessage = any>(handlers: RoomHandlers): RoomDefinition; declare function isRoomDefinition(value: unknown): value is RoomDefinition; interface RealtimeConnectMeta { room: string; def: RoomDefinition; query?: Record; user?: string; } /** One cross-instance message: a room broadcast, or a targeted user send. */ interface RealtimeEnvelope { room: string; /** If set, deliver only to these user identities; otherwise the whole room. */ users?: string[]; message: unknown; } /** * A pub/sub bridge for horizontal scaling. Wire the registry to a shared bus * (Redis pub/sub, NATS, …): local broadcasts/`toUser` sends are published to * peers, and messages received from peers are delivered via `registry.deliver`. * Connection-targeted sends (`send`, `to(id)`) stay local (ids are per-process). */ interface RealtimeBridge { publish(envelope: RealtimeEnvelope): void; } interface RealtimeRegistryOptions extends RealtimeSecurityOptions { now?: () => number; } interface RealtimeRegistry { open(socket: RawSocket, meta: RealtimeConnectMeta): void | Promise; message(socket: RawSocket, raw: string | Uint8Array): void | Promise; close(socket: RawSocket): void | Promise; /** Attach a cross-instance bridge (call once at startup). */ setBridge(bridge: RealtimeBridge): void; /** Deliver an envelope received from a peer to LOCAL connections only. */ deliver(envelope: RealtimeEnvelope): void; /** Number of live connections (across all rooms) — for tests/metrics. */ size(): number; } /** Create the registry that maps sockets ↔ rooms and drives room handlers. */ declare function createRealtimeRegistry(options?: RealtimeRegistryOptions): RealtimeRegistry; /** * A minimal pub/sub bus (structurally satisfied by `@wrnexus/pubsub`). Used to * bridge realtime broadcasts across processes without a hard dependency. */ interface RealtimeBus { publish(topic: string, message: unknown): void | Promise; subscribe(topic: string, handler: (message: unknown, topic: string) => void): () => void; } /** * Bridge a realtime registry across processes/instances via a pub/sub bus (use * the Redis driver so it crosses machines). After this, `client.room.broadcast` * and `client.toUser(...)` reach connected clients on **every** app process/ * instance subscribed to the same bus — the foundation for realtime that works * with multiple running apps behind the gateway. Connection-targeted sends * (`send`, `to(id)`) stay local. Returns an unsubscribe function. * * import { createRealtimeRegistry, bridgeRealtime } from "@wrnexus/core"; * import { createPubSub } from "@wrnexus/pubsub"; * import { redisDriver } from "@wrnexus/pubsub/redis"; * bridgeRealtime(registry, createPubSub(redisDriver(process.env.REDIS_URL))); */ declare function bridgeRealtime(registry: RealtimeRegistry, bus: RealtimeBus, topic?: string): () => void; /** * Error + status pages. Every page here is a self-contained HTML document — * inline CSS only, no external stylesheet, no JavaScript (so it renders under the * strict CSP, even when the app's assets are what failed). Theme-aware via * `prefers-color-scheme`, styled in the WrNexus design language (ink-navy, * azure, a faint blueprint grid + glow). Development shows the stack trace; * production never leaks internal paths. */ type Mode = "development" | "production"; /** A beautiful, self-contained HTML page for any 4xx/5xx status. */ declare function renderStatusPage(status: number): Response; /** Readable, styled development error page — includes the stack trace. */ declare function renderDevError(err: unknown, status?: number): Response; /** Generic production error page — no stack, no file paths. */ declare function renderProdError(status?: number): Response; /** Pick the right error page for the current mode. */ declare function renderError(err: unknown, mode: Mode): Response; /** Beautiful 404 page. */ declare function renderNotFound(): Response; type CorsOrigin = "*" | string | string[]; interface CorsConfig { /** Enable CORS headers and preflight handling. Defaults to false. */ enabled?: boolean; /** Allowed origins. Use "*" for public APIs. Defaults to "*". */ origin?: CorsOrigin; /** Allowed methods for preflight responses. */ methods?: string[]; /** Allowed request headers. Defaults to the browser's requested headers. */ allowedHeaders?: string[]; /** Response headers exposed to browser JavaScript. */ exposedHeaders?: string[]; /** Whether to send Access-Control-Allow-Credentials. */ credentials?: boolean; /** Access-Control-Max-Age, in seconds. */ maxAge?: number; } type CspDirectiveValue = string | string[] | false | null | undefined; interface ContentSecurityPolicyConfig { /** Defaults to true. */ enabled?: boolean; /** Use Content-Security-Policy-Report-Only instead of enforcing. */ reportOnly?: boolean; /** Merge or remove directives. Set a directive to false/null to remove it. */ directives?: Record; /** Set false to start from an empty policy instead of WrNexus defaults. */ useDefaults?: boolean; } interface HstsConfig { /** Defaults to true in production, false in development. */ enabled?: boolean; /** Defaults to 31536000 seconds (1 year). */ maxAge?: number; /** Defaults to true. */ includeSubDomains?: boolean; /** Defaults to true. */ preload?: boolean; } interface TrustedTypesConfig { /** Defaults to true in production, false in development. */ enabled?: boolean; /** * Defaults to ["*"] in production so browser extensions and dev tooling can * create their own policies without noisy console errors. Set this to a * concrete list, e.g. ["wrnexus", "default"], for stricter deployments. */ policyNames?: string[]; /** Defaults to true. */ requireForScript?: boolean; /** Adds "allow-duplicates" to the trusted-types directive. */ allowDuplicates?: boolean; } type PermissionsPolicyConfig = Record; interface RequestLimitsConfig { maxUrlLength?: number; maxHeaderCount?: number; maxHeaderBytes?: number; maxQueryParameters?: number; maxBodyBytes?: number; timeoutMs?: number; maxConcurrent?: number; trustedHosts?: string[]; fetchMetadata?: boolean; } interface SecurityConfig { /** Set false to skip all framework security headers except explicitly enabled CORS. */ headers?: boolean; /** Built-in request size, timeout, concurrency, host, and Fetch Metadata limits. */ requestLimits?: RequestLimitsConfig; /** * Trust `X-Forwarded-Proto` / `X-Forwarded-Host` when building `ctx.url` — set * this when the app runs behind a TLS-terminating reverse proxy (nginx, the * WrNexus gateway, a load balancer). Without it, a proxied app sees the internal * `http://` request and marks cookies (e.g. CSRF/session) non-`Secure`. Default * false; enable ONLY when a trusted proxy actually sets these headers. */ trustProxy?: boolean; cors?: boolean | CorsConfig; contentSecurityPolicy?: false | ContentSecurityPolicyConfig; hsts?: false | HstsConfig; trustedTypes?: false | TrustedTypesConfig; /** Defaults to "same-origin". */ crossOriginOpenerPolicy?: false | "same-origin" | "same-origin-allow-popups" | "unsafe-none"; /** Defaults to "DENY". */ frameOptions?: false | "DENY" | "SAMEORIGIN"; /** Defaults to "strict-origin-when-cross-origin". */ referrerPolicy?: false | string; /** Defaults to "same-origin". */ crossOriginResourcePolicy?: false | "same-origin" | "same-site" | "cross-origin"; /** Isolate the origin in its own agent cluster. Defaults to true. */ originAgentCluster?: boolean; /** Disable speculative DNS prefetching. Defaults to true. */ disableDnsPrefetch?: boolean; /** Defaults to a restrictive browser capability policy. */ permissionsPolicy?: false | PermissionsPolicyConfig; /** Extra static headers applied last. */ extraHeaders?: Record; } /** * Guard a WebSocket upgrade against Cross-Site WebSocket Hijacking: browsers * always send an `Origin` header on a WS handshake, and — unlike fetch — WS is * NOT subject to CORS, so cookies would otherwise flow cross-site. We allow * same-origin (Origin host === Host header), configured CORS origins, and * non-browser clients (no Origin, which also carry no ambient cookies). */ declare function isWebSocketOriginAllowed(req: Request, security?: SecurityConfig): boolean; declare function createCorsPreflightResponse(req: Request, security?: SecurityConfig): Response | null; /** * Build the request URL, honoring `X-Forwarded-Proto` / `X-Forwarded-Host` when * `trustProxy` is set (app behind a TLS-terminating reverse proxy). This makes * `ctx.url.protocol` reflect the EXTERNAL scheme, so protocol-dependent logic — * `Secure` cookies, canonical URLs — is correct behind nginx / the gateway. * Security checks that compare the raw `Host`/`Origin` headers don't use this URL, * so they are unaffected. An invalid forwarded value is ignored by the URL setter. */ declare function resolveRequestUrl(req: Request, trustProxy?: boolean): URL; declare function withSecurityHeaders(req: Request, res: Response, mode: Mode, security?: SecurityConfig, nonce?: string): Response; interface SchemaLike { parse(input: unknown): T; } interface OutputSchemaLike { readonly __output: T; parse(input: unknown): unknown; } type InferEndpointSchema = TSchema extends OutputSchemaLike ? TValue : never; interface EndpointErrorBody { code: string; message: string; details?: unknown; } declare class EndpointError extends Error { readonly status: number; readonly code: string; readonly details?: unknown | undefined; constructor(status: number, code: string, message: string, details?: unknown | undefined); } interface EndpointDefinition { input?: SchemaLike | OutputSchemaLike; output?: SchemaLike | OutputSchemaLike; auth?: "optional" | "required"; description?: string; tags?: string[]; handler(input: I, ctx: Context): O | Promise; } interface DefinedEndpoint { readonly definition: EndpointDefinition; (ctx: Context, input?: unknown): Promise; } /** Define a validated, typed endpoint that can also drive SDK/OpenAPI generation. */ declare function defineEndpoint, OutputSchema extends OutputSchemaLike>(definition: Omit, InferEndpointSchema>, "input" | "output"> & { input: InputSchema; output: OutputSchema; }): DefinedEndpoint, InferEndpointSchema>; declare function defineEndpoint(definition: EndpointDefinition): DefinedEndpoint; interface RpcClientOptions { baseUrl?: string; fetch?: typeof globalThis.fetch; headers?: HeadersInit | (() => HeadersInit | Promise); } /** Create a tiny typed RPC caller for endpoints exposed by a WrNexus app. */ declare function createRpcClient(options?: RpcClientOptions): (path: string, input: I) => Promise; interface CachePolicy { ttlMs?: number; staleWhileRevalidateMs?: number; tags?: string[] | ((ctx: Context) => string[]); } interface LoaderDefinition { cache?: CachePolicy; load(ctx: Context): T | Promise; } interface ActionDefinition { csrf?: boolean; run(input: I, ctx: Context): O | Promise; invalidate?: string[] | ((output: O, ctx: Context) => string[]); } interface DefinedLoader { readonly definition: LoaderDefinition; (ctx: Context): Promise; } interface DefinedAction { readonly definition: ActionDefinition; (input: I, ctx: Context): Promise; } declare function defineLoader(definition: LoaderDefinition): DefinedLoader; declare function defineAction(definition: ActionDefinition): DefinedAction; /** Request-local fetch deduplication keyed by a stable string. */ declare function dedupe(ctx: Context, key: string, load: () => T | Promise): Promise; type FeatureValue = boolean | string | number; type FeatureRule = FeatureValue | ((ctx: Context) => FeatureValue | Promise); interface FeatureFlags { get(name: string, ctx: Context): Promise; enabled(name: string, ctx: Context): Promise; } declare function defineFeatureFlags(rules: Record): FeatureFlags; interface PerformanceBudgets { routeJsBytes?: number; routeCssBytes?: number; htmlBytes?: number; imageBytes?: number; hydrationMs?: number; serverRenderMs?: number; /** Largest Contentful Paint in milliseconds. Recommended <= 2500. */ lcpMs?: number; /** Interaction to Next Paint in milliseconds. Recommended <= 200. */ inpMs?: number; /** Cumulative Layout Shift score. Recommended <= 0.1. */ cls?: number; /** Time to First Byte in milliseconds. */ ttfbMs?: number; /** Longest main-thread task in milliseconds. Recommended <= 50. */ longTaskMs?: number; /** Number of client hydration boundaries on the route. */ hydratedComponents?: number; /** Total request count for the initial navigation. */ requests?: number; } interface PerformanceMeasurement { routeJsBytes?: number; routeCssBytes?: number; htmlBytes?: number; imageBytes?: number; hydrationMs?: number; serverRenderMs?: number; lcpMs?: number; inpMs?: number; cls?: number; ttfbMs?: number; longTaskMs?: number; hydratedComponents?: number; requests?: number; } interface BudgetViolation { metric: keyof PerformanceBudgets; budget: number; actual: number; overBy: number; } declare const recommendedWebBudgets: Readonly; declare function checkPerformanceBudgets(budgets: PerformanceBudgets, measurement: PerformanceMeasurement): BudgetViolation[]; type Duration = number | `${number}${"ms" | "s" | "m" | "h"}`; type BackoffStrategy = "fixed" | "exponential" | ((attempt: number) => Duration); interface CircuitBreakerOptions { failures: number; resetAfter: Duration; successesToClose?: number; } interface CircuitBreakerSnapshot { state: "closed" | "open" | "half-open"; failures: number; successes: number; retryAfterMs: number; } declare class ResilienceError extends Error { readonly code: "WRN-RESILIENCE-TIMEOUT" | "WRN-RESILIENCE-ABORTED" | "WRN-RESILIENCE-CIRCUIT-OPEN" | "WRN-RESILIENCE-BULKHEAD-FULL"; constructor(code: "WRN-RESILIENCE-TIMEOUT" | "WRN-RESILIENCE-ABORTED" | "WRN-RESILIENCE-CIRCUIT-OPEN" | "WRN-RESILIENCE-BULKHEAD-FULL", message: string, options?: ErrorOptions); } declare function durationMs(value: Duration): number; declare class CircuitBreaker { private readonly options; private failures; private successes; private openedAt; private probing; constructor(options: CircuitBreakerOptions); snapshot(now?: number): CircuitBreakerSnapshot; execute(operation: () => Promise): Promise; } interface BulkheadOptions { concurrency: number; queue?: number; } declare class Bulkhead { private readonly options; private active; private readonly waiting; constructor(options: BulkheadOptions); get snapshot(): Readonly<{ active: number; queued: number; capacity: number; }>; execute(operation: () => Promise): Promise; } interface ResilientCallOptions { run: (signal: AbortSignal, attempt: number) => Promise; timeout?: Duration; retries?: number; retryDelay?: Duration; backoff?: BackoffStrategy; circuitBreaker?: CircuitBreaker | CircuitBreakerOptions; bulkhead?: Bulkhead | BulkheadOptions; signal?: AbortSignal; retryWhen?: (error: unknown, attempt: number) => boolean | Promise; fallback?: (error: unknown, signal: AbortSignal) => T | Promise; onRetry?: (error: unknown, attempt: number, delayMs: number) => void; } declare function resilientCall(options: ResilientCallOptions): Promise; interface ProblemDetails { type: string; title: string; status: number; detail?: string; instance?: string; code?: string; [key: string]: unknown; } interface ProblemDetailsInput { type?: string; title: string; status: number; detail?: string; instance?: string; code?: string; [key: string]: unknown; } declare function problem(details: ProblemDetailsInput, headers?: HeadersInit): Response; type ServiceToken = string | symbol | { readonly key: symbol; readonly __type?: T; }; declare function serviceToken(description: string): ServiceToken; declare class ServiceContainer { #private; private readonly parent?; constructor(parent?: ServiceContainer | undefined); set(token: ServiceToken, value: T): this; has(token: ServiceToken): boolean; get(token: ServiceToken): T; tryGet(token: ServiceToken): T | undefined; scope(): ServiceContainer; } type LifecyclePhase = "starting" | "started" | "stopping" | "stopped"; type LifecycleHandler = (signal: AbortSignal) => void | Promise; declare class ApplicationLifecycle { #private; on(phase: LifecyclePhase, handler: LifecycleHandler): () => void; run(phase: LifecyclePhase): Promise; get signal(): AbortSignal; } interface HealthCheckResult { status: "up" | "down" | "degraded"; message?: string; details?: unknown; durationMs?: number; } type HealthCheck = () => HealthCheckResult | Promise; declare class HealthRegistry { #private; register(name: string, check: HealthCheck): () => void; check(): Promise<{ status: "up" | "down" | "degraded"; checks: Record; }>; } declare function requestId(headers: Headers, preferred?: string): string; interface IdempotencyRecord { key: string; value: T; expiresAt: number; } interface IdempotencyStore { get(key: string): Promise | null>; set(record: IdempotencyRecord): Promise; delete(key: string): Promise; } declare function memoryIdempotencyStore(now?: () => number): IdempotencyStore; declare function withIdempotency(store: IdempotencyStore, key: string, execute: () => Promise, ttlMs?: number): Promise<{ value: T; replayed: boolean; }>; export { type ActionDefinition, ApplicationLifecycle, type AsyncSessionBackend, type BackoffStrategy, type Bucket, type BudgetViolation, Bulkhead, type BulkheadOptions, CSRF_COOKIE, CSRF_HEADER, type CacheControlOptions, type CachePolicy, CircuitBreaker, type CircuitBreakerOptions, type CircuitBreakerSnapshot, type ContentSecurityPolicyConfig, type Context, type CookieOptions, type CookieStore, type CorsConfig, type CorsOrigin, type CspDirectiveValue, type CsrfProtectionOptions, type DefinedAction, type DefinedEndpoint, type DefinedLoader, type Duration, type EndpointDefinition, EndpointError, type EndpointErrorBody, type ExecutionContext, type ExecutionContextInput, type ExecutionKind, type FeatureFlags, type FeatureRule, type FeatureValue, type HealthCheck, type HealthCheckResult, HealthRegistry, type HstsConfig, type IdempotencyRecord, type IdempotencyStore, type InferEndpointSchema, type LifecycleHandler, type LifecyclePhase, type LoaderDefinition, type LocalStorageSnapshot, type Middleware, type Mode, type Next, type OutputSchemaLike, POSTGRES_TENANT_DIRECTORY_SCHEMA, type PageComponent, type PageMeta, type PerformanceBudgets, type PerformanceMeasurement, type PermissionsPolicyConfig, type ProblemDetails, type ProblemDetailsInput, type RateLimitOptions, type RateLimitStore, type RawSocket, type RealtimeBridge, type RealtimeBus, type RealtimeConnectMeta, type RealtimeEnvelope, type RealtimeHandler, type RealtimeRegistry, type RealtimeRegistryOptions, type RealtimeSecurityOptions, type RealtimeSocket, type RequestLimitsConfig, type RequestLoggerOptions, type RequestRecord, type RequireAuthOptions, ResilienceError, type ResilientCallOptions, type ResponseContext, type Room, type RoomAuthInfo, type RoomClient, type RoomDefinition, type RoomHandlers, type RpcClientOptions, SESSION_USER_KEY, type SaveUploadOptions, type SavedUpload, type SchemaLike, type SecureUploadOptions, type SecurityConfig, type SeoConfig, type ServerSentEvent, ServiceContainer, type ServiceToken, type SessionBackend, type SessionEntry, type SessionPolicy, type SessionStore, type Span, type SpanRecord, type StreamResponseInit, type TFunction, TTLCache, type Target, type Tenant, type TenantAuditEvent, type TenantDirectoryStore, type TenantMembership, type TenantMiddlewareOptions, type TenantQuota, type TenantResolver, type TenantResource, type TenantSqlClient, type Tracer, type TrustedTypesConfig, UploadError, type UploadInspectionResult, type UploadInspector, type UploadScanner, assertTenantAccess, bridgeRealtime, cacheControl, checkPerformanceBudgets, collectUploads, composeTenantResolvers, createContext, createCorsPreflightResponse, createExecutionContext, createPersistentTenantDirectory, createRealtimeRegistry, createRpcClient, createTenantDirectory, createTracer, csrfProtection, csrfToken, dedupe, defaultKey, defineAction, defineEndpoint, defineFeatureFlags, defineLoader, defineRoom, durationMs, escapeHtml, etag, executionContextFromHttp, getUser, hashPassword, isRoomDefinition, isSafeIslandName, isSafeRequestPath, isWebSocketOriginAllowed, loadSession, logIn, logOut, memoryIdempotencyStore, memoryTenantDirectoryStore, migrateTenants, notModified, peerKey, postgresTenantDirectoryStore, problem, proxyKey, randomUploadFilename, rateLimit, recommendedWebBudgets, renderDevError, renderError, renderNotFound, renderProdError, renderStatusPage, requestId, requestLogger, requireAuth, requireTenant, resilientCall, resolveRequestUrl, sanitizeFilename, saveUpload, saveUploadSecure, secureDownloadHeaders, serviceToken, sessionAuth, setSessionBackend, setSessionPolicy, sse, streamResponse, tenantFromDomain, tenantFromHeader, tenantFromPath, tenantFromSession, tenantFromSubdomain, tenantKey, tenantMiddleware, tenantScope, tracingMiddleware, verifyCsrf, verifyPassword, withCacheControl, withContextHeaders, withIdempotency, withSecurityHeaders, withSpan }; ``` --- ## @wrnexus/csr Documentation URL: https://wrnexusjs.dev/packages/csr # @wrnexus/csr ## Navigation state preservation Pages can opt into restoration across client navigation: ```wrn page Users { navigation { preserve = ["filters", "pagination", "scroll", "tabs", "expanded"] } } ``` Form-like categories restore named inputs, selects, and textareas. Password, file, hidden, CSRF/token/secret/credential fields, and elements marked `data-no-preserve` are never saved. For tab, expanded, or component UI state, mark stable elements with `data-wrn-preserve="key"`; their value and ARIA selected/expanded state are restored. State is scoped to pathname plus query. ## Typed server actions `createActionClient(route, name)` supports programmatic calls. Schema-backed WRN actions also export `__wrnexusActionClients`, whose input and output are inferred automatically. Enhanced forms expose `data-wrn-action-state="pending|success|error"` and dispatch bubbling `wrnexus:action:optimistic`, `:pending`, `:success`, and `:error` events. Success details contain returned data and invalidated cache tags; error details contain field errors. Without JavaScript, the same form posts to its page and receives a 303 redirect or accessible validation response. > The browser-side client runtime for WrNexus — generic, self-contained JS that hydrates server-rendered pages with reactivity, client-side navigation, and realtime rooms. Part of the **WrNexus** framework — an SSR-first, Bun-native full-stack web framework. ## Overview `@wrnexus/csr` holds the three client runtimes that WrNexus serves to the browser. Components are authored as `.wrn` files and rendered on the **server**; this package provides the single, generic runtime that **hydrates** that HTML in the browser — there are no per-component browser bundles. Each runtime is exported as a plain-JS string (no build step, no imports) intended to be served verbatim from a well-known URL: - **reactive** at `/__wrnexus/reactive.js` — reactive directives (`data-scope`, `data-text`, `data-for`, …) - **nav** at `/__wrnexus/nav.js` — SPA-style client navigation with graceful fallback - **realtime** at `/__wrnexus/realtime.js` — WebSocket "rooms", declarative or programmatic The package itself runs on the server (it just returns strings); the strings it returns run in the browser. A dev/prod server (see `@wrnexus/core`) is responsible for actually serving them. ## Installation ```bash bun add @wrnexus/csr ``` > Private package — the machine must be authenticated to the `wrnexus` npm org > (a read token in `~/.npmrc`). Requires **Bun** (Node is not supported). ## API All exports come from the package root (`@wrnexus/csr`). The runtime source is delivered as strings, so the "API" on the server side is small; the real surface is the browser directives/globals each string installs. ### Runtime strings | Export | Type | Served at | Contents | | ------------------ | -------- | ------------------------ | ------------------------------ | | `REACTIVE_RUNTIME` | `string` | `/__wrnexus/reactive.js` | Reactive directive runtime | | `NAV_RUNTIME` | `string` | `/__wrnexus/nav.js` | Client-side navigation runtime | | `REALTIME_RUNTIME` | `string` | `/__wrnexus/realtime.js` | Realtime rooms runtime | ### Accessor functions Convenience getters that return the same strings. ```ts getReactiveRuntime(): string // → REACTIVE_RUNTIME getNavRuntime(): string // → NAV_RUNTIME getRealtimeRuntime(): string // → REALTIME_RUNTIME ``` ### Browser: reactive directives Applied to any subtree containing `data-scope`. Expressions are parsed by a tiny eval-free evaluator, so a strict CSP with no `unsafe-eval` works. | Directive | Purpose | | ---------------------------------- | ---------------------------------------------------- | | `data-scope="count: 0, name: 'x'"` | Declare reactive state on a subtree | | `data-on-="count++"` | Run a statement in scope on a DOM event | | `data-text="expr"` | Bind an element's `textContent` to an expression | | `data-show="expr"` | Toggle visibility while preserving interactive state | Compiled conditional rendering and dynamic component cases omit inactive elements from the live DOM. `data-show` is a visibility directive for stateful controls and keeps its element mounted. Neither mechanism is authorization: never place secrets in client-rendered branches. Authorize on the server and return only data the current request may access. | `data-for="item in list"` (opt. index and `key item.id`) | Per-item rendering; stable keys preserve DOM identity during reorder | | `data-key="item.id"` | Alternative key declaration for `data-for` templates | | `{{expr}}` or `{expr}` | Interpolation inside text nodes and attribute values | | `data-wrnexus-csr="id"` | Target for a generated CSR fetch binding (fetches `/__wrnexus/csr?...`) | Supported expression features: literals, identifiers, member access (`a.b`, `a[b]`), function/method calls, arrays, objects, arithmetic, comparison, equality, logical (`&& ||`), unary (`! - +`), and ternary. Statements support `++`/`--`, assignment operators (`= += -= *= /= %=`), and bare expression/method calls. Rendering is dependency-tracked: a signal change only re-runs the renderers that actually read it. Browser globals installed: `window.__wrnexusHydrateScopes(root)` and `window.__wrnexusHydrateCsrFetches(root)` — both idempotent, so re-running after a DOM swap or HMR morph is safe. Both run automatically on `DOMContentLoaded`. ### Browser: navigation Intercepts same-origin `` clicks, fetches the target page, and swaps the `#app` container in place (via `importNode` — not `innerHTML` — so it works under a Trusted-Types CSP), updating history, title, and scroll, then re-hydrates. Cross-origin links, modified clicks, `download`/`data-no-nav`/`rel="external"`/`target` links, non-HTML responses, or a missing `#app` fall back to a full browser navigation. - Programmatic navigation: `window.__wrnexusNavigate(url)` - Emits a `wrnexus:navigated` `CustomEvent` (`detail.url`) after each swap - Sends `x-wrnexus-nav: 1` on fetches so the server can return the page fragment - Appends any `/__wrnexus/*` runtime scripts the incoming page needs but the current document lacks ### Browser: realtime rooms Connects to `/realtime/` over WebSocket (`ws`/`wss` chosen from `location.protocol`). Two usage modes. Programmatic API via `window.wire`: ```ts wire.room(name): Room // open (or reuse) a room connection wire.bindRooms(root?) // (re)bind declarative [data-room] containers interface Room { name: string; send(obj: object | string): Room; // JSON-stringifies objects; queues until open on(type: string, cb): Room; // filter by msg.type; "*" or a fn = all messages on(cb): Room; close(): Room; } ``` Internal lifecycle messages are emitted to listeners as `{ type }`: `__open`, `__close`, `__error`, and `__raw` (non-JSON frames, with `data`). Reconnect uses exponential backoff capped at 5s; queued sends flush on reconnect. Declarative binding (zero JS) on a `data-room=""` container: | Attribute | On | Purpose | | ------------------------------------ | --------------- | ------------------------------------------------------------------------ | | `data-room=""` | container | Connect to room `` | | `data-room-user=""` | container | Identify the connection (`?user=`) | | `data-room-log` | element | Where incoming messages are appended | | `