# WRNexusJS documentation 0.2.22 Status: Private Developer Preview. This site documents 26 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 ``` wrnexus dev . # dev server + HMR wrnexus build . # production build → dist/server.js bun dist/server.js # run the production server (or npm start) wrnexus create # scaffold a new app wrnexus update --latest # deps + syntax/config migrations + verification wrnexus generate page # scaffold a page (aliases: g p) wrnexus generate component | api | schema wrnexus db migrate | rollback | status | new [--from-models] | generate | seed wrnexus eject # copy a Wire UI component's .wrn into app/components to customize ``` ## 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. # Installed package documentation The following README files and declarations come from the installed private 0.2.22 release. ## @wrnexus/ai Documentation URL: https://wrnexusjs.dev/packages/ai # @wrnexus/ai > 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 */ } } ``` ## Usage ```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 }); }; ``` ## 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 /** * @wrnexus/ai — a tiny, zero-dependency Claude (Anthropic) client for WrNexus apps. * * Use it in API routes, jobs, or anywhere server-side to generate text with Claude. * It talks to the Anthropic Messages API over `fetch` (no SDK dependency, Bun-native), * and defaults to the most capable model, `claude-opus-4-8`. * * import { createAI } from "@wrnexus/ai"; * const ai = createAI(); // reads ANTHROPIC_API_KEY * const text = await ai.generate("Write a haiku about Bun."); * * Streaming (great for API routes): * export const POST = async (ctx) => ai.streamResponse(await ctx.req.text()); */ type Role = "user" | "assistant"; interface Message { role: Role; content: string; } /** Reasoning effort — higher means deeper thinking + more tokens. */ type Effort = "low" | "medium" | "high" | "xhigh" | "max"; interface AIConfig { /** Anthropic API key. Default: `ANTHROPIC_API_KEY` from the environment. */ apiKey?: string; /** Model id. Default: `claude-opus-4-8` (the most capable Claude model). */ model?: string; /** Default max output tokens. Default: 4096. */ maxTokens?: number; /** API base URL. Default: `https://api.anthropic.com`. */ baseURL?: string; /** `anthropic-version` header. Default: `2023-06-01`. */ version?: string; } interface GenerateOptions { /** System prompt — sets the assistant's role/behavior. */ system?: string; /** Override the model for this call. */ model?: string; /** Override max output tokens for this call. */ maxTokens?: number; /** Enable adaptive extended thinking (slower, deeper reasoning). */ thinking?: boolean; /** Reasoning effort / token spend (`output_config.effort`). */ effort?: Effort; /** Full message history — supersedes the `prompt` argument when provided. */ messages?: Message[]; /** Abort the request. */ signal?: AbortSignal; } /** Thrown when the API returns a non-2xx response or refuses the request. */ declare class AIError extends Error { readonly status: number; readonly type: string; constructor(message: string, status?: number, type?: string); } interface AI { /** Generate a full text response (non-streaming). */ generate(prompt: string | Message[], opts?: GenerateOptions): Promise; /** Stream the response as text deltas, as they arrive. */ stream(prompt: string | Message[], opts?: GenerateOptions): AsyncGenerator; /** Stream straight to a `Response` (text/plain) — drop-in for an API route return. */ streamResponse(prompt: string | Message[], opts?: GenerateOptions): Response; } /** Create a Claude client. Reads `ANTHROPIC_API_KEY` from the environment by default. */ declare function createAI(config?: AIConfig): AI; export { type AI, type AIConfig, AIError, type Effort, type GenerateOptions, type Message, type Role, createAI }; ``` --- ## @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). ### Exported TypeScript declarations ```ts import { Context, Middleware } from '@wrnexus/core'; /** * @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; export { type Policy, type Rbac, type Subject, all, any, attr, authorize, defineRbac, hasRole, requirePermission, requireRole }; ``` --- ## @wrnexus/cli Documentation URL: https://wrnexusjs.dev/packages/cli # @wrnexus/cli > 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 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 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 help` | Print usage. | `wrnexus g` is an alias for `wrnexus generate`. ### `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 new app from an inline (dependency-free) template — `package.json`, `.gitignore`, config, and starter `app/` files. Use `npm run dev` during development, `npm run build && npm start` for production, or `npm run production` to build and start in one command. The generated production server currently requires Bun even when npm is used to manage packages and scripts. ### `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: several WrNexus apps under `apps/*` and shared libraries under `packages/*`, plus 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). ```bash wrnexus workspace acme wrnexus gateway --port=3000 ``` 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 ``` ## 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 > 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. ## 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. ## 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: `kind` (`"page" \| "component"`), `name`, optional `layout`, `props`, `states`, `seo`, `view`, `styles`, `functions`, `dataApis`, `modeFunctions`, `apis`, `realtimes`. | | `ViewNode` | `{ type: "text"; value }` or `{ type: "element"; tag; attrs; children }`. | | `Attr` | `{ name; value; event; boolean? }` — `event` marks `@event` bindings. | | `StateDecl` | `{ name; expr }` — a `state x = ` 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). - `props { name = ... }` — component props; each default's type drives coercion. - `state = ` — reactive state seeded from a raw JS expression. - `view { }` — plain HTML with `{expr}` interpolation, hyphenated attributes, boolean attributes, `@event="..."` client bindings, and ``. - `seo { key = "value" ... }` — metadata merged into the generated `meta`. - `style { }` — inlined page/component stylesheet (repeatable). - `functions { }` — shared server-side helpers (repeatable). - `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 /** * Recursive-descent parser for `.wrn`, producing a small AST. * * Grammar (subset of the vision, but real): * * page { * state = // zero or more * view { } // plain HTML (see parseHtmlView) * seo { title = "Home" description = "..." } * ssr { api { } functions { } } * client { api { } functions { } } * style { } // zero or more, inlined with the page * functions { } // zero or more, shared helpers * api { } // zero or more * realtime { on () { } * } // zero or more * } * * The `view` block is written as ordinary HTML — nothing new to learn. Text may * contain `{expr}` interpolation, attributes may be hyphenated (`data-*`), and * `@event="..."` declares a client event binding. See `parseHtmlView`. */ interface StateDecl { name: string; /** Raw JS initializer expression, e.g. `0` or `'x'`. */ expr: string; } interface Attr { name: string; value: string; /** True for `@event` bindings (vs. plain HTML attributes). */ event: boolean; /** True for a valueless boolean attribute, e.g. `