docs: include all 31 packages in catalog
This commit is contained in:
@@ -12,13 +12,16 @@ const frameworkVersion = JSON.parse(readFileSync(join(root, "package.json"), "ut
|
|||||||
const packagePages = join(root, "app", "pages", "packages");
|
const packagePages = join(root, "app", "pages", "packages");
|
||||||
const expected = [
|
const expected = [
|
||||||
"ai",
|
"ai",
|
||||||
|
"auth",
|
||||||
"authz",
|
"authz",
|
||||||
|
"captcha",
|
||||||
"cli",
|
"cli",
|
||||||
"compiler",
|
"compiler",
|
||||||
"core",
|
"core",
|
||||||
"csr",
|
"csr",
|
||||||
"db",
|
"db",
|
||||||
"dev-server",
|
"dev-server",
|
||||||
|
"dev-toolbar",
|
||||||
"encryption",
|
"encryption",
|
||||||
"helpers",
|
"helpers",
|
||||||
"i18n",
|
"i18n",
|
||||||
@@ -26,12 +29,14 @@ const expected = [
|
|||||||
"mobile",
|
"mobile",
|
||||||
"native",
|
"native",
|
||||||
"oauth",
|
"oauth",
|
||||||
|
"plugin",
|
||||||
"pubsub",
|
"pubsub",
|
||||||
"queue",
|
"queue",
|
||||||
"reactive",
|
"reactive",
|
||||||
"router",
|
"router",
|
||||||
"ssr",
|
"ssr",
|
||||||
"styles",
|
"styles",
|
||||||
|
"syntax",
|
||||||
"test",
|
"test",
|
||||||
"tracking",
|
"tracking",
|
||||||
"ui",
|
"ui",
|
||||||
|
|||||||
+16
-1
@@ -15,12 +15,18 @@ page Packages {
|
|||||||
<div class="topbar-actions"><a class="preview-pill" href="/access">Private preview · v0.5.0</a><button data-wire-theme-toggle class="theme-button" aria-label="Toggle color theme" title="Toggle color theme">◐</button></div>
|
<div class="topbar-actions"><a class="preview-pill" href="/access">Private preview · v0.5.0</a><button data-wire-theme-toggle class="theme-button" aria-label="Toggle color theme" title="Toggle color theme">◐</button></div>
|
||||||
</header>
|
</header>
|
||||||
<div class="mobile-doc-nav"><details><summary>Browse documentation</summary><nav><a href="/getting-started">Get started</a><a href="/packages">Packages</a><a href="https://component.wrnexusjs.dev/">Components</a><a href="/language">Language</a><a href="/architecture">Architecture</a><a href="/tutorial">Tutorial</a><a href="/guides/project-structure">Guides</a><a href="/examples">Examples</a><a href="/search">Search</a></nav></details></div>
|
<div class="mobile-doc-nav"><details><summary>Browse documentation</summary><nav><a href="/getting-started">Get started</a><a href="/packages">Packages</a><a href="https://component.wrnexusjs.dev/">Components</a><a href="/language">Language</a><a href="/architecture">Architecture</a><a href="/tutorial">Tutorial</a><a href="/guides/project-structure">Guides</a><a href="/examples">Examples</a><a href="/search">Search</a></nav></details></div>
|
||||||
<main class="page"><section class="hero compact"><span class="eyebrow">26 focused packages</span><h1>Package reference</h1><p>Everything in the framework, organized by responsibility and documented from the published 0.5.0 APIs.</p><input class="search" type="search" placeholder="Search packages, features, or categories…" @input="query = event.target.value" /></section><section class="category-filter" aria-label="Filter packages by category"><div class="category-row"><button type="button" @click="category = 'All'">All<span>26</span></button><button type="button" @click="category = 'AI'">AI<span>1</span></button><button type="button" @click="category = 'Security'">Security<span>5</span></button><button type="button" @click="category = 'Tooling'">Tooling<span>3</span></button><button type="button" @click="category = 'Core'">Core<span>3</span></button><button type="button" @click="category = 'Frontend'">Frontend<span>5</span></button><button type="button" @click="category = 'Data'">Data<span>3</span></button><button type="button" @click="category = 'Runtime'">Runtime<span>3</span></button><button type="button" @click="category = 'Native'">Native<span>2</span></button><button type="button" @click="category = 'Realtime'">Realtime<span>1</span></button></div><p>Showing <strong>{category}</strong> packages</p></section><section class="package-grid"><a class="package-card" href="/packages/ai" data-show="(category === 'All' ? true : category === 'AI') ? (query === '' ? true : 'ai ai server-side anthropic client with generation and streaming. '.includes(query.toLowerCase())) : false">
|
<main class="page"><section class="hero compact"><span class="eyebrow">31 focused packages</span><h1>Package reference</h1><p>Everything in the framework, organized by responsibility and documented from the published 0.5.0 APIs.</p><input class="search" type="search" placeholder="Search packages, features, or categories…" @input="query = event.target.value" /></section><section class="category-filter" aria-label="Filter packages by category"><div class="category-row"><button type="button" @click="category = 'All'">All<span>31</span></button><button type="button" @click="category = 'AI'">AI<span>1</span></button><button type="button" @click="category = 'Security'">Security<span>7</span></button><button type="button" @click="category = 'Tooling'">Tooling<span>4</span></button><button type="button" @click="category = 'Core'">Core<span>4</span></button><button type="button" @click="category = 'Frontend'">Frontend<span>6</span></button><button type="button" @click="category = 'Data'">Data<span>3</span></button><button type="button" @click="category = 'Runtime'">Runtime<span>3</span></button><button type="button" @click="category = 'Native'">Native<span>2</span></button><button type="button" @click="category = 'Realtime'">Realtime<span>1</span></button></div><p>Showing <strong>{category}</strong> packages</p></section><section class="package-grid"><a class="package-card" href="/packages/ai" data-show="(category === 'All' ? true : category === 'AI') ? (query === '' ? true : 'ai ai server-side anthropic client with generation and streaming. '.includes(query.toLowerCase())) : false">
|
||||||
<span class="category">AI</span><h2>@wrnexus/ai</h2><p>Server-side Anthropic client with generation and streaming.</p><span class="card-link">Open documentation →</span>
|
<span class="category">AI</span><h2>@wrnexus/ai</h2><p>Server-side Anthropic client with generation and streaming.</p><span class="card-link">Open documentation →</span>
|
||||||
</a>
|
</a>
|
||||||
|
<a class="package-card" href="/packages/auth" data-show="(category === 'All' ? true : category === 'Security') ? (query === '' ? true : 'auth security authentication routes, sessions, forms, guards, and account flows. '.includes(query.toLowerCase())) : false">
|
||||||
|
<span class="category">Security</span><h2>@wrnexus/auth</h2><p>Authentication routes, sessions, forms, guards, and account flows.</p><span class="card-link">Open documentation →</span>
|
||||||
|
</a>
|
||||||
<a class="package-card" href="/packages/authz" data-show="(category === 'All' ? true : category === 'Security') ? (query === '' ? true : 'authz security role, permission, policy, and authorization guards. '.includes(query.toLowerCase())) : false">
|
<a class="package-card" href="/packages/authz" data-show="(category === 'All' ? true : category === 'Security') ? (query === '' ? true : 'authz security role, permission, policy, and authorization guards. '.includes(query.toLowerCase())) : false">
|
||||||
<span class="category">Security</span><h2>@wrnexus/authz</h2><p>Role, permission, policy, and authorization guards.</p><span class="card-link">Open documentation →</span>
|
<span class="category">Security</span><h2>@wrnexus/authz</h2><p>Role, permission, policy, and authorization guards.</p><span class="card-link">Open documentation →</span>
|
||||||
</a>
|
</a>
|
||||||
|
<a class="package-card" href="/packages/captcha" data-show="(category === 'All' ? true : category === 'Security') ? (query === '' ? true : 'captcha security managed captcha verification, middleware, and ui integration. '.includes(query.toLowerCase())) : false">
|
||||||
|
<span class="category">Security</span><h2>@wrnexus/captcha</h2><p>Managed CAPTCHA verification, middleware, and UI integration.</p><span class="card-link">Open documentation →</span>
|
||||||
|
</a>
|
||||||
<a class="package-card" href="/packages/cli" data-show="(category === 'All' ? true : category === 'Tooling') ? (query === '' ? true : 'cli tooling create, develop, build, generate, test, and maintain wrnexus apps. '.includes(query.toLowerCase())) : false">
|
<a class="package-card" href="/packages/cli" data-show="(category === 'All' ? true : category === 'Tooling') ? (query === '' ? true : 'cli tooling create, develop, build, generate, test, and maintain wrnexus apps. '.includes(query.toLowerCase())) : false">
|
||||||
<span class="category">Tooling</span><h2>@wrnexus/cli</h2><p>Create, develop, build, generate, test, and maintain WRNexusJS apps.</p><span class="card-link">Open documentation →</span>
|
<span class="category">Tooling</span><h2>@wrnexus/cli</h2><p>Create, develop, build, generate, test, and maintain WRNexusJS apps.</p><span class="card-link">Open documentation →</span>
|
||||||
</a>
|
</a>
|
||||||
@@ -39,6 +45,9 @@ page Packages {
|
|||||||
<a class="package-card" href="/packages/dev-server" data-show="(category === 'All' ? true : category === 'Runtime') ? (query === '' ? true : 'dev-server runtime development and production servers, hmr, assets, and gateways. '.includes(query.toLowerCase())) : false">
|
<a class="package-card" href="/packages/dev-server" data-show="(category === 'All' ? true : category === 'Runtime') ? (query === '' ? true : 'dev-server runtime development and production servers, hmr, assets, and gateways. '.includes(query.toLowerCase())) : false">
|
||||||
<span class="category">Runtime</span><h2>@wrnexus/dev-server</h2><p>Development and production servers, HMR, assets, and gateways.</p><span class="card-link">Open documentation →</span>
|
<span class="category">Runtime</span><h2>@wrnexus/dev-server</h2><p>Development and production servers, HMR, assets, and gateways.</p><span class="card-link">Open documentation →</span>
|
||||||
</a>
|
</a>
|
||||||
|
<a class="package-card" href="/packages/dev-toolbar" data-show="(category === 'All' ? true : category === 'Tooling') ? (query === '' ? true : 'dev-toolbar tooling development toolbar diagnostics, inspection, and runtime status. '.includes(query.toLowerCase())) : false">
|
||||||
|
<span class="category">Tooling</span><h2>@wrnexus/dev-toolbar</h2><p>Development toolbar diagnostics, inspection, and runtime status.</p><span class="card-link">Open documentation →</span>
|
||||||
|
</a>
|
||||||
<a class="package-card" href="/packages/encryption" data-show="(category === 'All' ? true : category === 'Security') ? (query === '' ? true : 'encryption security hashing, hmac, authenticated encryption, and key derivation. '.includes(query.toLowerCase())) : false">
|
<a class="package-card" href="/packages/encryption" data-show="(category === 'All' ? true : category === 'Security') ? (query === '' ? true : 'encryption security hashing, hmac, authenticated encryption, and key derivation. '.includes(query.toLowerCase())) : false">
|
||||||
<span class="category">Security</span><h2>@wrnexus/encryption</h2><p>Hashing, HMAC, authenticated encryption, and key derivation.</p><span class="card-link">Open documentation →</span>
|
<span class="category">Security</span><h2>@wrnexus/encryption</h2><p>Hashing, HMAC, authenticated encryption, and key derivation.</p><span class="card-link">Open documentation →</span>
|
||||||
</a>
|
</a>
|
||||||
@@ -60,6 +69,9 @@ page Packages {
|
|||||||
<a class="package-card" href="/packages/oauth" data-show="(category === 'All' ? true : category === 'Security') ? (query === '' ? true : 'oauth security oauth 2.0, pkce, provider presets, and profile mapping. '.includes(query.toLowerCase())) : false">
|
<a class="package-card" href="/packages/oauth" data-show="(category === 'All' ? true : category === 'Security') ? (query === '' ? true : 'oauth security oauth 2.0, pkce, provider presets, and profile mapping. '.includes(query.toLowerCase())) : false">
|
||||||
<span class="category">Security</span><h2>@wrnexus/oauth</h2><p>OAuth 2.0, PKCE, provider presets, and profile mapping.</p><span class="card-link">Open documentation →</span>
|
<span class="category">Security</span><h2>@wrnexus/oauth</h2><p>OAuth 2.0, PKCE, provider presets, and profile mapping.</p><span class="card-link">Open documentation →</span>
|
||||||
</a>
|
</a>
|
||||||
|
<a class="package-card" href="/packages/plugin" data-show="(category === 'All' ? true : category === 'Core') ? (query === '' ? true : 'plugin core plugin contracts, lifecycle hooks, composition, and framework integration. '.includes(query.toLowerCase())) : false">
|
||||||
|
<span class="category">Core</span><h2>@wrnexus/plugin</h2><p>Plugin contracts, lifecycle hooks, composition, and framework integration.</p><span class="card-link">Open documentation →</span>
|
||||||
|
</a>
|
||||||
<a class="package-card" href="/packages/pubsub" data-show="(category === 'All' ? true : category === 'Realtime') ? (query === '' ? true : 'pubsub realtime in-process and redis-backed publish/subscribe. '.includes(query.toLowerCase())) : false">
|
<a class="package-card" href="/packages/pubsub" data-show="(category === 'All' ? true : category === 'Realtime') ? (query === '' ? true : 'pubsub realtime in-process and redis-backed publish/subscribe. '.includes(query.toLowerCase())) : false">
|
||||||
<span class="category">Realtime</span><h2>@wrnexus/pubsub</h2><p>In-process and Redis-backed publish/subscribe.</p><span class="card-link">Open documentation →</span>
|
<span class="category">Realtime</span><h2>@wrnexus/pubsub</h2><p>In-process and Redis-backed publish/subscribe.</p><span class="card-link">Open documentation →</span>
|
||||||
</a>
|
</a>
|
||||||
@@ -78,6 +90,9 @@ page Packages {
|
|||||||
<a class="package-card" href="/packages/styles" data-show="(category === 'All' ? true : category === 'Frontend') ? (query === '' ? true : 'styles frontend css pipeline, themes, fonts, profiles, and application config. '.includes(query.toLowerCase())) : false">
|
<a class="package-card" href="/packages/styles" data-show="(category === 'All' ? true : category === 'Frontend') ? (query === '' ? true : 'styles frontend css pipeline, themes, fonts, profiles, and application config. '.includes(query.toLowerCase())) : false">
|
||||||
<span class="category">Frontend</span><h2>@wrnexus/styles</h2><p>CSS pipeline, themes, fonts, profiles, and application config.</p><span class="card-link">Open documentation →</span>
|
<span class="category">Frontend</span><h2>@wrnexus/styles</h2><p>CSS pipeline, themes, fonts, profiles, and application config.</p><span class="card-link">Open documentation →</span>
|
||||||
</a>
|
</a>
|
||||||
|
<a class="package-card" href="/packages/syntax" data-show="(category === 'All' ? true : category === 'Frontend') ? (query === '' ? true : 'syntax frontend editor syntax definitions and language tooling for .wrn files. '.includes(query.toLowerCase())) : false">
|
||||||
|
<span class="category">Frontend</span><h2>@wrnexus/syntax</h2><p>Editor syntax definitions and language tooling for .wrn files.</p><span class="card-link">Open documentation →</span>
|
||||||
|
</a>
|
||||||
<a class="package-card" href="/packages/test" data-show="(category === 'All' ? true : category === 'Tooling') ? (query === '' ? true : 'test tooling wrnexus-aware component, route, and browser testing utilities. '.includes(query.toLowerCase())) : false">
|
<a class="package-card" href="/packages/test" data-show="(category === 'All' ? true : category === 'Tooling') ? (query === '' ? true : 'test tooling wrnexus-aware component, route, and browser testing utilities. '.includes(query.toLowerCase())) : false">
|
||||||
<span class="category">Tooling</span><h2>@wrnexus/test</h2><p>WRNexusJS-aware component, route, and browser testing utilities.</p><span class="card-link">Open documentation →</span>
|
<span class="category">Tooling</span><h2>@wrnexus/test</h2><p>WRNexusJS-aware component, route, and browser testing utilities.</p><span class="card-link">Open documentation →</span>
|
||||||
</a>
|
</a>
|
||||||
|
|||||||
@@ -0,0 +1,367 @@
|
|||||||
|
page wrnexusauth {
|
||||||
|
seo {
|
||||||
|
title = "@wrnexus/auth"
|
||||||
|
description = "Authentication routes, sessions, forms, guards, and account flows."
|
||||||
|
}
|
||||||
|
|
||||||
|
view {
|
||||||
|
<div class="docs-shell">
|
||||||
|
<SkipLink label="Skip to content" href="#main" class="docs-skip-link" />
|
||||||
|
<header class="topbar">
|
||||||
|
<a class="brand" href="/"><span>W</span> WRNexusJS</a>
|
||||||
|
<nav aria-label="Primary"><a href="/getting-started">Get started</a><a href="/packages">Packages</a><a href="https://component.wrnexusjs.dev/">Components</a><a href="/language">Language</a><a href="/architecture">Architecture</a></nav>
|
||||||
|
<div class="topbar-actions"><a class="preview-pill" href="/access">Private preview · v0.5.0</a><button data-wire-theme-toggle class="theme-button" aria-label="Toggle color theme" title="Toggle color theme">◐</button></div>
|
||||||
|
</header>
|
||||||
|
<div class="mobile-doc-nav"><details><summary>Browse documentation</summary><nav><a href="/getting-started">Get started</a><a href="/packages">Packages</a><a href="https://component.wrnexusjs.dev/">Components</a><a href="/language">Language</a><a href="/architecture">Architecture</a><a href="/tutorial">Tutorial</a><a href="/guides/project-structure">Guides</a><a href="/examples">Examples</a><a href="/search">Search</a></nav></details></div>
|
||||||
|
<main class="portal-main docs-layout">
|
||||||
|
<article id="main" class="documentation prose standalone package-document"><nav class="breadcrumbs" aria-label="Breadcrumb"><a href="/">Home</a><span>/</span><a href="/packages">Packages</a><span>/</span><span aria-current="page">@wrnexus/auth</span></nav><section class="doc-intro"><span class="eyebrow">Security · Package reference</span><h1>@wrnexus/auth</h1><p>Authentication routes, sessions, forms, guards, and account flows.</p><div class="doc-meta"><span>v0.5.0</span><span>Private registry</span><span>Security</span></div><section id="access" class="access-callout"><h2>Install the package</h2><p>After WorkRoot approves private registry access, install the release-aligned package:</p><pre><code>bun add @wrnexus/auth@0.5.0</code><button type="button" class="copy-button" aria-label="Copy installation command">Copy</button></pre><p><a href="/access">Request preview access</a>. Never put registry tokens in source control.</p></section></section><section id="guide"><p>Framework-native authentication, identity, account-security, and session management for WRNexusJS.</p>
|
||||||
|
<h3 id="capabilities">Capabilities</h3>
|
||||||
|
<ul>
|
||||||
|
<li>Password registration, login, recovery, reset, and authenticated password changes</li>
|
||||||
|
<li>Email, phone, and username identities with verification and generic resend responses</li>
|
||||||
|
<li>Magic links and passwordless email/SMS OTP login</li>
|
||||||
|
<li>MFA transactions using verified email OTP, verified SMS OTP, TOTP, or recovery codes</li>
|
||||||
|
<li>RFC 6238 TOTP with counter replay protection</li>
|
||||||
|
<li>One-use recovery codes; regeneration invalidates previous unused codes</li>
|
||||||
|
<li>Passkey/WebAuthn registration and strong passwordless sign-in through a provider contract</li>
|
||||||
|
<li>OAuth account linking and provider sign-in</li>
|
||||||
|
<li>Invitations, session rotation, idle and absolute expiry, revocation, and trusted devices</li>
|
||||||
|
<li>Deny-by-default audited support impersonation</li>
|
||||||
|
<li>Adaptive risk scoring, CAPTCHA escalation, temporary lockout, and optional login alerts</li>
|
||||||
|
<li>Memory and SQL stores</li>
|
||||||
|
<li>Optional encryption-keyring protection for TOTP and OAuth secrets</li>
|
||||||
|
<li>Automatic API routes, middleware, browser schemas, components, runtime, migrations, and DevToolbar checks</li>
|
||||||
|
</ul>
|
||||||
|
<p>Passkeys are a strong sign-in method. They are not currently exposed as a selectable second step in <code>TwoFactorChallenge</code>; the implemented MFA methods are email OTP, SMS OTP, TOTP, and recovery codes.</p>
|
||||||
|
<h3 id="install">Install</h3>
|
||||||
|
<pre data-language="bash"><code>bun add @wrnexus/auth</code></pre>
|
||||||
|
<p>WRNexusJS discovers the package automatically. Do not copy package components, client scripts, schemas, or standard <code>/api/auth/*</code> route files into the application.</p>
|
||||||
|
<h3 id="default-configuration">Default configuration</h3>
|
||||||
|
<p>Create the engine:</p>
|
||||||
|
<pre data-language="ts"><code>// 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.
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});</code></pre>
|
||||||
|
<p>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 <code>config.auth.onSignedIn</code> and <code>config.auth.onSignedOut</code> fields remain supported as compatibility overrides, but new applications should configure these hooks on <code>createAuthEngine</code>.</p>
|
||||||
|
<h4 id="successful-signup-behavior">Successful signup behavior</h4>
|
||||||
|
<p>Without <code>onSuccessfulSignUp</code>, a successful package registration redirects to <code>/sign-in</code>.</p>
|
||||||
|
<p>To sign in immediately after registration:</p>
|
||||||
|
<pre data-language="ts"><code>onSuccessfulSignUp(ctx, user) {
|
||||||
|
return {
|
||||||
|
autoSignIn: true,
|
||||||
|
redirectTo: "/account",
|
||||||
|
};
|
||||||
|
}</code></pre>
|
||||||
|
<p>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 <code>Response</code> for a completely custom HTTP result, or return <code>{ redirectTo: "/welcome" }</code> to redirect without creating a session.</p>
|
||||||
|
<p>Register it through application configuration:</p>
|
||||||
|
<pre data-language="ts"><code>// 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;</code></pre>
|
||||||
|
<p>That configuration automatically activates package routes, auth-session middleware, components, browser validation schemas, and the auth client runtime. <code>setDefaultAuthEngine()</code> remains available only for advanced manual integrations and tests.</p>
|
||||||
|
<h3 id="sql-production-configuration">SQL production configuration</h3>
|
||||||
|
<pre data-language="ts"><code>import { createAuthEngine, SqlAuthStore } from "@wrnexus/auth";
|
||||||
|
import { getDb } from "@wrnexus/db";
|
||||||
|
|
||||||
|
export const auth = createAuthEngine({
|
||||||
|
store: new SqlAuthStore(getDb()),
|
||||||
|
secret: process.env.AUTH_SECRET!,
|
||||||
|
});</code></pre>
|
||||||
|
<pre data-language="ts"><code>export default {
|
||||||
|
db: {
|
||||||
|
// Application database configuration.
|
||||||
|
},
|
||||||
|
auth: {
|
||||||
|
engine: auth,
|
||||||
|
routes: true,
|
||||||
|
middleware: true,
|
||||||
|
migrations: true,
|
||||||
|
},
|
||||||
|
};</code></pre>
|
||||||
|
<p>The package contributes both ordered migrations:</p>
|
||||||
|
<pre data-language="text"><code>001_auth.sql
|
||||||
|
002_auth_otp_purpose.sql</code></pre>
|
||||||
|
<p>Migrations are enabled automatically only when <code>auth.engine</code> and a default <code>config.db</code> are present. Set <code>auth.migrations</code> explicitly when an application needs different behavior.</p>
|
||||||
|
<h3 id="delivered-action-urls">Delivered action URLs</h3>
|
||||||
|
<p>By default, the engine builds links from the supplied <code>baseUrl</code> and token purpose. Applications can map those links to their own page structure without replacing package APIs:</p>
|
||||||
|
<pre data-language="ts"><code>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;
|
||||||
|
},
|
||||||
|
});</code></pre>
|
||||||
|
<p>Returning <code>undefined</code> intentionally omits the URL while still delivering the raw token. The callback runs only in trusted server code.</p>
|
||||||
|
<h3 id="built-in-validation">Built-in validation</h3>
|
||||||
|
<p>Every packaged auth form has a built-in <code>@wrnexus/validation</code> schema. The same resolved schema is used by the browser and the package API handler.</p>
|
||||||
|
<p>Default use requires no <code>app/schemas</code> files:</p>
|
||||||
|
<pre data-language="wrn"><code><SignUp />
|
||||||
|
<SignIn />
|
||||||
|
<ForgotPassword />
|
||||||
|
<ResetPassword token='{token}' />
|
||||||
|
<TwoFactorChallenge /></code></pre>
|
||||||
|
<p>To customize one schema, extend the package default and register only that override:</p>
|
||||||
|
<pre data-language="ts"><code>// 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"),
|
||||||
|
});</code></pre>
|
||||||
|
<pre data-language="ts"><code>import customPasswordRequest from "./app/schemas/custom-password-request.ts";
|
||||||
|
|
||||||
|
export default {
|
||||||
|
auth: {
|
||||||
|
engine: auth,
|
||||||
|
schemas: {
|
||||||
|
passwordResetRequest: customPasswordRequest,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};</code></pre>
|
||||||
|
<p><code><ForgotPassword /></code> can keep its default <code>schema="auth-password-request"</code>. The plugin automatically publishes the overridden browser descriptor under that same built-in schema ID. All other forms continue using package defaults.</p>
|
||||||
|
<h3 id="route-controls">Route controls</h3>
|
||||||
|
<p>Use a boolean to enable or disable all package routes:</p>
|
||||||
|
<pre data-language="ts"><code>auth: {
|
||||||
|
engine: auth,
|
||||||
|
routes: true,
|
||||||
|
}</code></pre>
|
||||||
|
<p>Or control feature groups:</p>
|
||||||
|
<pre data-language="ts"><code>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,
|
||||||
|
}</code></pre>
|
||||||
|
<p>Application routes have normal framework precedence. Disable a package group only when the application intentionally owns every endpoint in that group; no <code>excludeRoutes</code> list is required.</p>
|
||||||
|
<h3 id="package-endpoints">Package endpoints</h3>
|
||||||
|
<pre data-language="text"><code>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</code></pre>
|
||||||
|
<p>Each URL uses a route-specific module, so rewritten framework request URLs cannot make the handler fall through to a shared-dispatcher <code>404</code>.</p>
|
||||||
|
<p>Unsafe package routes validate the framework CSRF token by default. Set <code>auth.csrf: false</code> only when an external API gateway provides an equivalent protection model.</p>
|
||||||
|
<h3 id="components">Components</h3>
|
||||||
|
<pre data-language="wrn"><code><SignIn />
|
||||||
|
<SignUp />
|
||||||
|
<ForgotPassword />
|
||||||
|
<ResetPassword token='{token}' />
|
||||||
|
<OtpSignIn method="email-otp" />
|
||||||
|
<MagicLinkSignIn />
|
||||||
|
<PasskeyButton mode="authenticate" />
|
||||||
|
<TwoFactorChallenge mfaToken='{mfaToken}' challengeId='{challengeId}' />
|
||||||
|
<AuthenticatorSetup credentialId='{credentialId}' secret='{secret}' uri='{uri}' />
|
||||||
|
<RecoveryCodes codes='{codes}' />
|
||||||
|
<DeviceSessions sessions='{sessions}' currentSessionId='{currentSessionId}' />
|
||||||
|
<VerifyEmail token='{token}' identifier='{identifier}' />
|
||||||
|
<VerifyPhone token='{token}' identifier='{identifier}' />
|
||||||
|
<InvitationAccept token='{token}' />
|
||||||
|
<ImpersonationBanner targetName='{targetName}' />
|
||||||
|
<AccountStatus status='{account.status}' /></code></pre>
|
||||||
|
<p><code>identifier</code> 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.</p>
|
||||||
|
<h3 id="captcha-and-risk">CAPTCHA and risk</h3>
|
||||||
|
<p>The HTTP handlers never trust a browser <code>captchaVerified</code> field. CAPTCHA completion is accepted only from server-populated <code>ctx.locals.captcha.success</code> or <code>ctx.locals.captchaVerified === true</code>.</p>
|
||||||
|
<p>Rate limiting remains an application or gateway responsibility. Apply it to registration, login, reset, magic-link, OTP, verification, passkey, invitation, and impersonation endpoints.</p>
|
||||||
|
<h3 id="mfa">MFA</h3>
|
||||||
|
<p>1. Password, OAuth, magic-link, or OTP login may return <code>code: "mfa-required"</code> with a short-lived <code>mfaToken</code>. 2. The response lists only methods actually available to that user. 3. Email/SMS MFA is offered only for verified linked identities. 4. <code>beginMfaOtp()</code> issues an MFA-bound OTP when needed. 5. <code>completeMfa()</code> consumes the one-time transaction and creates the session.</p>
|
||||||
|
<h3 id="passkeys">Passkeys</h3>
|
||||||
|
<p>The browser runtime coordinates <code>navigator.credentials.create()</code> and <code>navigator.credentials.get()</code>. A configured server-side <code>PasskeyProvider</code> must verify the challenge, RP ID, origin, signature, user presence or verification, counter, and credential ownership.</p>
|
||||||
|
<p>Multi-process deployments must provide a shared <code>PasskeyChallengeStore</code>; the default memory implementation is process-local. Missing passkey providers return a controlled <code>503</code> response rather than crashing the route.</p>
|
||||||
|
<h3 id="protect-long-lived-secrets">Protect long-lived secrets</h3>
|
||||||
|
<pre data-language="ts"><code>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),
|
||||||
|
});</code></pre>
|
||||||
|
<p>TOTP seeds and OAuth access/refresh tokens are protected before persistence. Keep old keys available during rotation.</p>
|
||||||
|
<h3 id="custom-http-integration">Custom HTTP integration</h3>
|
||||||
|
<p><code>createAuthHttpHandlers()</code> remains available for custom route paths or response behavior. Prefer package routes for standard flows; copied application API files are unnecessary.</p>
|
||||||
|
<h3 id="development">Development</h3>
|
||||||
|
<pre data-language="bash"><code>bun run auth:dev
|
||||||
|
bun run validate:auth</code></pre>
|
||||||
|
<p>Read [SECURITY.md](./SECURITY.md) before production deployment.</p></section><section id="api" class="api"><h2>Complete TypeScript API</h2><p>Generated from the exact installed package declarations.</p><pre data-language="typescript"><code>export { A as AuthEngine, 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-jttXj6PP.js';
|
||||||
|
import { n as AuthRiskSignals, l as AuthRiskDecision } from './types-JLkQpcAt.js';
|
||||||
|
export { A as AuthAccountStatus, a as AuthClock, b as AuthDeliveryMessage, c as AuthDeliveryProvider, d as AuthEngineOptions, e as AuthIdentity, f as AuthIdentityType, g as AuthImpersonationDecision, h as AuthMfaMethod, i as AuthPublicUser, j as AuthRandom, k as AuthResult, m as AuthRiskLevel, o as AuthSecretProtector, p as AuthSecurityEvent, q as AuthSession, r as AuthSignedInHandler, s as AuthSignedOutHandler, M as AuthStore, t as AuthSuccessfulSignUpAction, u as AuthSuccessfulSignUpHandler, v as AuthTokenPurpose, w as AuthTokenUrlInput, x as AuthUser, y as AuthenticatedContext, L as LoginAttempt, z as LoginInput, N as MemoryPasskeyChallengeStore, O as OAuthAccount, B as OneTimeToken, C as OtpChallenge, P as PasskeyAuthenticationOptions, Q as PasskeyChallengeKind, S as PasskeyChallengeRecord, U as PasskeyChallengeStore, D as PasskeyCredential, E as PasskeyProvider, F as PasskeyRegistrationOptions, G as PasskeyVerificationResult, H as PasswordBreachProvider, I as PasswordCredential, R as RecoveryCodeRecord, J as RegisterInput, T as TotpCredential, K as TrustedDevice, V as assertPasskeyProvider } from './types-JLkQpcAt.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-CrF_lZDQ.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 '@wrnexus/oauth';
|
||||||
|
import '@wrnexus/core';
|
||||||
|
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;
|
||||||
|
|
||||||
|
export { AuthRiskDecision, AuthRiskSignals, type RiskPolicy, evaluateAuthRisk };
|
||||||
|
</code></pre></section><section id="examples" class="examples"><h2>Examples</h2><p>Copy-ready examples from the installed package documentation.</p><div class="example-grid"><article class="example-card"><h3>## Install</h3><pre data-language="bash"><code>bun add @wrnexus/auth</code></pre></article><article class="example-card"><h3>Create the engine</h3><pre data-language="ts"><code>// 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.
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});</code></pre></article><article class="example-card"><h3>Successful signup behavior</h3><pre data-language="ts"><code>onSuccessfulSignUp(ctx, user) {
|
||||||
|
return {
|
||||||
|
autoSignIn: true,
|
||||||
|
redirectTo: "/account",
|
||||||
|
};
|
||||||
|
}</code></pre></article><article class="example-card"><h3>Successful signup behavior</h3><pre data-language="ts"><code>// 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;</code></pre></article><article class="example-card"><h3>Successful signup behavior</h3><pre data-language="ts"><code>import { createAuthEngine, SqlAuthStore } from "@wrnexus/auth";
|
||||||
|
import { getDb } from "@wrnexus/db";
|
||||||
|
|
||||||
|
export const auth = createAuthEngine({
|
||||||
|
store: new SqlAuthStore(getDb()),
|
||||||
|
secret: process.env.AUTH_SECRET!,
|
||||||
|
});</code></pre></article><article class="example-card"><h3>Successful signup behavior</h3><pre data-language="ts"><code>export default {
|
||||||
|
db: {
|
||||||
|
// Application database configuration.
|
||||||
|
},
|
||||||
|
auth: {
|
||||||
|
engine: auth,
|
||||||
|
routes: true,
|
||||||
|
middleware: true,
|
||||||
|
migrations: true,
|
||||||
|
},
|
||||||
|
};</code></pre></article></div></section></article>
|
||||||
|
<aside class="on-this-page"><h2>On this page</h2><nav><a class="toc-level-2" href="#guide">Guide</a><a class="toc-level-3" href="#capabilities">Capabilities</a><a class="toc-level-3" href="#install">Install</a><a class="toc-level-3" href="#default-configuration">Default configuration</a><a class="toc-level-4" href="#successful-signup-behavior">Successful signup behavior</a><a class="toc-level-3" href="#sql-production-configuration">SQL production configuration</a><a class="toc-level-3" href="#delivered-action-urls">Delivered action URLs</a><a class="toc-level-3" href="#built-in-validation">Built-in validation</a><a class="toc-level-3" href="#route-controls">Route controls</a><a class="toc-level-3" href="#package-endpoints">Package endpoints</a><a class="toc-level-3" href="#components">Components</a><a class="toc-level-3" href="#captcha-and-risk">CAPTCHA and risk</a><a class="toc-level-3" href="#mfa">MFA</a><a class="toc-level-3" href="#passkeys">Passkeys</a><a class="toc-level-3" href="#protect-long-lived-secrets">Protect long-lived secrets</a><a class="toc-level-3" href="#custom-http-integration">Custom HTTP integration</a><a class="toc-level-3" href="#development">Development</a><a class="toc-level-2" href="#api">Complete API</a><a class="toc-level-2" href="#examples">Examples</a></nav></aside>
|
||||||
|
</main>
|
||||||
|
<footer><div class="footer-brand"><span class="footer-mark" aria-hidden="true">W</span><p><strong>WRNexusJS 0.5.0</strong><span>Complete API documentation generated from installed package declarations.</span></p></div><nav aria-label="Footer"><a href="/packages">All packages</a><a href="/getting-started">Get started</a><a href="/security">Security</a><a href="/support">Support</a><a href="/llms.txt">AI guide</a></nav><p class="footer-meta">Private Developer Preview · Bun-native</p></footer>
|
||||||
|
<BackToTop />
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,321 @@
|
|||||||
|
page wrnexuscaptcha {
|
||||||
|
seo {
|
||||||
|
title = "@wrnexus/captcha"
|
||||||
|
description = "Managed CAPTCHA verification, middleware, and UI integration."
|
||||||
|
}
|
||||||
|
|
||||||
|
view {
|
||||||
|
<div class="docs-shell">
|
||||||
|
<SkipLink label="Skip to content" href="#main" class="docs-skip-link" />
|
||||||
|
<header class="topbar">
|
||||||
|
<a class="brand" href="/"><span>W</span> WRNexusJS</a>
|
||||||
|
<nav aria-label="Primary"><a href="/getting-started">Get started</a><a href="/packages">Packages</a><a href="https://component.wrnexusjs.dev/">Components</a><a href="/language">Language</a><a href="/architecture">Architecture</a></nav>
|
||||||
|
<div class="topbar-actions"><a class="preview-pill" href="/access">Private preview · v0.5.0</a><button data-wire-theme-toggle class="theme-button" aria-label="Toggle color theme" title="Toggle color theme">◐</button></div>
|
||||||
|
</header>
|
||||||
|
<div class="mobile-doc-nav"><details><summary>Browse documentation</summary><nav><a href="/getting-started">Get started</a><a href="/packages">Packages</a><a href="https://component.wrnexusjs.dev/">Components</a><a href="/language">Language</a><a href="/architecture">Architecture</a><a href="/tutorial">Tutorial</a><a href="/guides/project-structure">Guides</a><a href="/examples">Examples</a><a href="/search">Search</a></nav></details></div>
|
||||||
|
<main class="portal-main docs-layout">
|
||||||
|
<article id="main" class="documentation prose standalone package-document"><nav class="breadcrumbs" aria-label="Breadcrumb"><a href="/">Home</a><span>/</span><a href="/packages">Packages</a><span>/</span><span aria-current="page">@wrnexus/captcha</span></nav><section class="doc-intro"><span class="eyebrow">Security · Package reference</span><h1>@wrnexus/captcha</h1><p>Managed CAPTCHA verification, middleware, and UI integration.</p><div class="doc-meta"><span>v0.5.0</span><span>Private registry</span><span>Security</span></div><section id="access" class="access-callout"><h2>Install the package</h2><p>After WorkRoot approves private registry access, install the release-aligned package:</p><pre><code>bun add @wrnexus/captcha@0.5.0</code><button type="button" class="copy-button" aria-label="Copy installation command">Copy</button></pre><p><a href="/access">Request preview access</a>. Never put registry tokens in source control.</p></section></section><section id="guide"><p>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 <code>.wrn</code> component.</p>
|
||||||
|
<h3 id="install">Install</h3>
|
||||||
|
<pre data-language="bash"><code>bun add @wrnexus/captcha</code></pre>
|
||||||
|
<p>WRNexusJS automatically discovers the package plugin, component, client runtime, styles, and DevToolbar audit. Use <code><Captcha /></code> 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 <code>captchaPlugin(options)</code> explicitly only when an application needs to override the discovered package configuration.</p>
|
||||||
|
<h3 id="included-challenge-modes">Included challenge modes</h3>
|
||||||
|
<ul>
|
||||||
|
<li>Number, alphabet, and alphanumeric image challenges</li>
|
||||||
|
<li>Addition, subtraction, multiplication, and exact-division calculations</li>
|
||||||
|
<li>Generated shape-selection image challenges</li>
|
||||||
|
<li>Audio alternatives for text, numbers, and calculations</li>
|
||||||
|
<li>Honeypot and minimum-completion-time invisible checks</li>
|
||||||
|
<li>Self-hosted “I’m not a robot” checkbox challenge with one-time server verification</li>
|
||||||
|
<li>Always, once-per-session, and adaptive page gates</li>
|
||||||
|
<li>Cloudflare Turnstile, Google reCAPTCHA, hCaptcha, managed, and custom providers</li>
|
||||||
|
</ul>
|
||||||
|
<h3 id="create-the-self-hosted-engine">Create the self-hosted engine</h3>
|
||||||
|
<pre data-language="ts"><code>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);</code></pre>
|
||||||
|
<p>Mount the handlers from an API catch-all route:</p>
|
||||||
|
<pre data-language="ts"><code>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;</code></pre>
|
||||||
|
<h3 id="use-the-component">Use the component</h3>
|
||||||
|
<pre data-language="wrn"><code><Captcha
|
||||||
|
type="alphanumeric"
|
||||||
|
action="signup"
|
||||||
|
endpoint="/api/captcha/challenge"
|
||||||
|
verifyEndpoint="/api/captcha/verify"
|
||||||
|
difficulty="normal"
|
||||||
|
disturbance="50"
|
||||||
|
imageStyle="random"
|
||||||
|
allowedStyles="classic,snow,distortion,wave"
|
||||||
|
size="normal"
|
||||||
|
showAudio="true"
|
||||||
|
showListen="true"
|
||||||
|
@success='captchaToken = event.detail.responseToken'
|
||||||
|
@failure='formError = event.detail.extra.message'
|
||||||
|
/></code></pre>
|
||||||
|
<p>The component uses Tailwind utilities and <code>--wire-*</code> theme variables. It has no companion component CSS file.</p>
|
||||||
|
<h4 id="main-props">Main props</h4>
|
||||||
|
<p><code>provider</code>, <code>siteKey</code>, <code>type</code>, <code>action</code>, <code>presentation</code>, <code>difficulty</code>, <code>disturbance</code>, <code>imageStyle</code>, <code>allowedStyles</code>, <code>excludedStyles</code>, <code>randomizeStyle</code>, <code>locale</code>, <code>size</code>, <code>color</code>, <code>class</code>, <code>name</code>, <code>endpoint</code>, <code>verifyEndpoint</code>, <code>responseField</code>, labels/messages, <code>autoLoad</code>, <code>autoVerify</code>, <code>showVerify</code>, <code>showRefresh</code>, <code>showAudio</code>, <code>showListen</code>, <code>showStatus</code>, <code>disabled</code>, <code>required</code>, and the backward-compatible <code>compact</code> alias.</p>
|
||||||
|
<h4 id="component-sizes">Component sizes</h4>
|
||||||
|
<p>Use one of the three supported display modes:</p>
|
||||||
|
<pre data-language="wrn"><code><Captcha size="compact" action="small-form" />
|
||||||
|
<Captcha size="normal" action="standard-form" />
|
||||||
|
<Captcha size="big" action="security-page" /></code></pre>
|
||||||
|
<p><code>small</code>/<code>sm</code> are accepted as aliases for <code>compact</code>, while <code>large</code>/<code>lg</code> are accepted as aliases for <code>big</code>. The old <code>compact="true"</code> prop still forces compact mode.</p>
|
||||||
|
<h4 id="listen-button-visibility">Listen button visibility</h4>
|
||||||
|
<p>Audio remains available by default. Hide the Listen and Use audio controls with either of these props:</p>
|
||||||
|
<pre data-language="wrn"><code><Captcha showListen="false" action="without-listen-button" />
|
||||||
|
<Captcha showAudio="false" action="without-audio-alternative" /></code></pre>
|
||||||
|
<p><code>showListen</code> is the direct UI switch. <code>showAudio</code> remains the broader backward-compatible audio switch.</p>
|
||||||
|
<h4 id="i-m-not-a-robot-checkbox">I’m not a robot checkbox</h4>
|
||||||
|
<pre data-language="wrn"><code><Captcha
|
||||||
|
type="not-robot"
|
||||||
|
action="contact-submit"
|
||||||
|
size="compact"
|
||||||
|
showListen="false"
|
||||||
|
/></code></pre>
|
||||||
|
<p>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.</p>
|
||||||
|
<h4 id="visual-disturbance">Visual disturbance</h4>
|
||||||
|
<p>Use <code>disturbance</code> for visual and image-selection challenges. It accepts an integer from <code>25</code> through <code>75</code>:</p>
|
||||||
|
<ul>
|
||||||
|
<li><code>25</code>: light disturbance and easiest readability</li>
|
||||||
|
<li><code>50</code>: balanced default</li>
|
||||||
|
<li><code>75</code>: maximum supported dots, line crossings, glyph movement, and image-tile noise</li>
|
||||||
|
</ul>
|
||||||
|
<p>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.</p>
|
||||||
|
<h4 id="generated-image-renderer-styles">Generated image renderer styles</h4>
|
||||||
|
<p>Text, number, alphanumeric, and calculation CAPTCHA images support 18 concrete renderers plus a random mode:</p>
|
||||||
|
<p><code>classic</code>, <code>collision</code>, <code>snow</code>, <code>corrosion</code>, <code>spiderweb</code>, <code>cross-shadow</code>, <code>split</code>, <code>split2</code>, <code>cut</code>, <code>darts</code>, <code>distortion</code>, <code>stitch</code>, <code>striped</code>, <code>wave</code>, <code>grid-noise</code>, <code>scribble</code>, <code>pixel</code>, and <code>broken-lines</code>.</p>
|
||||||
|
<p>Use a fixed style:</p>
|
||||||
|
<pre data-language="wrn"><code><Captcha
|
||||||
|
type="alphanumeric"
|
||||||
|
action="signup"
|
||||||
|
imageStyle="spiderweb"
|
||||||
|
disturbance="55"
|
||||||
|
/></code></pre>
|
||||||
|
<p>Use a new random style whenever the challenge is refreshed:</p>
|
||||||
|
<pre data-language="wrn"><code><Captcha
|
||||||
|
type="number"
|
||||||
|
action="login"
|
||||||
|
imageStyle="random"
|
||||||
|
difficulty="normal"
|
||||||
|
/></code></pre>
|
||||||
|
<p>Control the random pool with comma-separated component props or arrays in the TypeScript API:</p>
|
||||||
|
<pre data-language="wrn"><code><Captcha
|
||||||
|
type="alphanumeric"
|
||||||
|
action="checkout"
|
||||||
|
imageStyle="random"
|
||||||
|
allowedStyles="classic,snow,distortion,wave"
|
||||||
|
excludedStyles="collision"
|
||||||
|
/></code></pre>
|
||||||
|
<pre data-language="ts"><code>const challenge = await engine.create({
|
||||||
|
action: "checkout",
|
||||||
|
type: "alphanumeric",
|
||||||
|
imageStyle: "random",
|
||||||
|
allowedStyles: ["classic", "snow", "distortion", "wave"],
|
||||||
|
excludedStyles: ["collision"],
|
||||||
|
});</code></pre>
|
||||||
|
<p>Set <code>randomizeStyle: true</code> to force random selection even when <code>imageStyle</code> 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.</p>
|
||||||
|
<h4 id="events">Events</h4>
|
||||||
|
<p><code>@ready</code>, <code>@challenge</code>, <code>@input</code>, <code>@verify</code>, <code>@success</code>, <code>@failure</code>, <code>@expired</code>, <code>@refresh</code>, <code>@audioStart</code>, <code>@audioEnd</code>, and <code>@error</code>.</p>
|
||||||
|
<h3 id="protect-a-validated-form-api">Protect a validated form API</h3>
|
||||||
|
<p>Validate a cloned request first, then consume the CAPTCHA response token. This prevents a valid token from being consumed when ordinary field validation fails.</p>
|
||||||
|
<pre data-language="ts"><code>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 }));
|
||||||
|
}</code></pre>
|
||||||
|
<p>The CAPTCHA runtime binds its required-form check in the capture phase, so a <code>data-schema</code> validator cannot submit the form before CAPTCHA verification. After a successful form request, the component automatically creates a fresh challenge.</p>
|
||||||
|
<h4 id="retryable-operations-such-as-login">Retryable operations such as login</h4>
|
||||||
|
<p>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:</p>
|
||||||
|
<pre data-language="ts"><code>const guard = captchaGuard({
|
||||||
|
action: "auth-login",
|
||||||
|
engine,
|
||||||
|
bindHostname: true,
|
||||||
|
bindSession: true,
|
||||||
|
verifiedForMs: 5 * 60_000,
|
||||||
|
});</code></pre>
|
||||||
|
<p>Keep the verified widget state for non-CAPTCHA form errors:</p>
|
||||||
|
<pre data-language="wrn"><code><Captcha
|
||||||
|
action="auth-login"
|
||||||
|
required="true"
|
||||||
|
resetOnError="false"
|
||||||
|
/></code></pre>
|
||||||
|
<p>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; <code>verifiedForMs</code> removes repeated human verification, not credential-abuse controls.</p>
|
||||||
|
<h3 id="validate-a-schema-and-captcha-together">Validate a schema and CAPTCHA together</h3>
|
||||||
|
<pre data-language="ts"><code>const result = await parseWithCaptcha(signupSchema, body, ctx, {
|
||||||
|
action: "signup",
|
||||||
|
engine,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!result.ok) return Response.json({ ok: false, errors: result.errors }, { status: 400 });</code></pre>
|
||||||
|
<h3 id="page-gate">Page gate</h3>
|
||||||
|
<pre data-language="ts"><code>export default captchaPageGate({
|
||||||
|
action: "reports-access",
|
||||||
|
engine,
|
||||||
|
challengePath: "/captcha",
|
||||||
|
policy: {
|
||||||
|
mode: "session",
|
||||||
|
verifiedForMs: 15 * 60_000,
|
||||||
|
routeGroups: ["/reports"],
|
||||||
|
},
|
||||||
|
});</code></pre>
|
||||||
|
<p>Use <code>mode: "always"</code> for every visit, <code>mode: "session"</code> for a temporary grant, or <code>mode: "adaptive"</code> with <code>signals(ctx)</code>.</p>
|
||||||
|
<p>The challenge page should post the return path as a normal hidden field instead of constructing JavaScript inside the HTML <code>action</code> attribute:</p>
|
||||||
|
<pre data-language="wrn"><code><form method="post" action="/api/page-grant">
|
||||||
|
<input type="hidden" name="returnTo" value='{returnTo}' />
|
||||||
|
<Captcha action="reports-access" />
|
||||||
|
<button type="submit">Continue</button>
|
||||||
|
</form></code></pre>
|
||||||
|
<p>The <code>/api/page-grant</code> route reads <code>returnTo</code>, restricts it to the current origin, and redirects only after <code>captchaPageGate()</code> has verified and stored the temporary session grant.</p>
|
||||||
|
<h3 id="external-providers">External providers</h3>
|
||||||
|
<pre data-language="ts"><code>const turnstile = turnstileProvider({
|
||||||
|
secretKey: process.env.TURNSTILE_SECRET!,
|
||||||
|
siteKey: process.env.PUBLIC_TURNSTILE_SITE_KEY!,
|
||||||
|
expectedHostnames: ["example.com"],
|
||||||
|
expectedAction: "signup",
|
||||||
|
});</code></pre>
|
||||||
|
<pre data-language="wrn"><code><Captcha
|
||||||
|
provider="turnstile"
|
||||||
|
siteKey="PUBLIC_SITE_KEY"
|
||||||
|
action="signup"
|
||||||
|
/></code></pre>
|
||||||
|
<p>Use the matching provider in <code>captchaGuard({ provider: turnstile })</code>. reCAPTCHA and hCaptcha adapters follow the same pattern.</p>
|
||||||
|
<h3 id="managed-provider">Managed provider</h3>
|
||||||
|
<pre data-language="ts"><code>const managed = managedCaptchaProvider({
|
||||||
|
baseUrl: "https://captcha.example.com",
|
||||||
|
siteKey: process.env.PUBLIC_CAPTCHA_SITE_KEY!,
|
||||||
|
secretKey: process.env.CAPTCHA_SECRET_KEY!,
|
||||||
|
});</code></pre>
|
||||||
|
<p>For direct browser challenge creation, configure the component’s <code>endpoint</code> as the managed <code>/v1/challenges</code> URL and its <code>verifyEndpoint</code> as <code>/v1/solve</code>. Keep the secret key only in the server provider.</p>
|
||||||
|
<h3 id="stores">Stores</h3>
|
||||||
|
<ul>
|
||||||
|
<li><code>MemoryCaptchaStore</code>: development and one-process applications</li>
|
||||||
|
<li><code>SqliteCaptchaStore</code>: adapter for SQLite-like <code>prepare().run/get/all()</code> clients</li>
|
||||||
|
<li><code>RedisCaptchaStore</code>: shared TTL storage with Lua-backed atomic consumption when <code>eval</code> is available</li>
|
||||||
|
<li><code>CaptchaStore</code>: implement this interface for PostgreSQL, MySQL, MongoDB, or another backend</li>
|
||||||
|
</ul>
|
||||||
|
<h3 id="audio">Audio</h3>
|
||||||
|
<p><code>AssetAudioRenderer</code> concatenates bundled English PCM WAV clips without calling an external service. Supply a custom <code>CaptchaAudioRenderer</code> for recorded voices, Hindi or other languages, or managed text-to-speech.</p>
|
||||||
|
<h3 id="devtoolbar">DevToolbar</h3>
|
||||||
|
<p>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 <code>captchaPlugin(options)</code> registration is needed only to override automatic configuration.</p>
|
||||||
|
<h3 id="testing">Testing</h3>
|
||||||
|
<p>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.</p>
|
||||||
|
<h3 id="custom-challenge-generator">Custom challenge generator</h3>
|
||||||
|
<pre data-language="ts"><code>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] });</code></pre>
|
||||||
|
<p>Applications may also implement <code>CaptchaStore</code>, <code>CaptchaAudioRenderer</code>, or use <code>defineCaptchaProvider()</code> for a completely custom service.</p></section><section id="api" class="api"><h2>Complete TypeScript API</h2><p>Generated from the exact installed package declarations.</p><pre data-language="typescript"><code>export { CaptchaAudioRenderer, CaptchaBinding, CaptchaChallenge, CaptchaChallengeGenerator, CaptchaChallengeRecord, CaptchaChallengeType, CaptchaConcreteImageStyle, CaptchaDifficulty, CaptchaEngine, CaptchaEngineOptions, CaptchaFailureCode, CaptchaGeneratorContext, CaptchaGuardOptions, CaptchaHttpHandlers, CaptchaImageItem, CaptchaImageStyle, CaptchaMiddleware, CaptchaPageGateOptions, CaptchaPolicyMode, CaptchaPolicyOptions, CaptchaPresentation, CaptchaProvider, CaptchaProviderClientConfig, CaptchaProviderName, CaptchaResponseTokenRecord, CaptchaRiskResult, CaptchaRiskSignals, CaptchaStore, CaptchaVerificationResult, CreateCaptchaOptions, GeneratedCaptchaChallenge, VerifyCaptchaInput } 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 '@wrnexus/core';
|
||||||
|
import '@wrnexus/validation';
|
||||||
|
import '@wrnexus/plugin';
|
||||||
|
</code></pre></section><section id="examples" class="examples"><h2>Examples</h2><p>Copy-ready examples from the installed package documentation.</p><div class="example-grid"><article class="example-card"><h3>## Install</h3><pre data-language="bash"><code>bun add @wrnexus/captcha</code></pre></article><article class="example-card"><h3>## Create the self-hosted engine</h3><pre data-language="ts"><code>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);</code></pre></article><article class="example-card"><h3>Mount the handlers from an API catch-all route</h3><pre data-language="ts"><code>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;</code></pre></article><article class="example-card"><h3>## Use the component</h3><pre data-language="wrn"><code><Captcha
|
||||||
|
type="alphanumeric"
|
||||||
|
action="signup"
|
||||||
|
endpoint="/api/captcha/challenge"
|
||||||
|
verifyEndpoint="/api/captcha/verify"
|
||||||
|
difficulty="normal"
|
||||||
|
disturbance="50"
|
||||||
|
imageStyle="random"
|
||||||
|
allowedStyles="classic,snow,distortion,wave"
|
||||||
|
size="normal"
|
||||||
|
showAudio="true"
|
||||||
|
showListen="true"
|
||||||
|
@success='captchaToken = event.detail.responseToken'
|
||||||
|
@failure='formError = event.detail.extra.message'
|
||||||
|
/></code></pre></article><article class="example-card"><h3>Component sizes</h3><pre data-language="wrn"><code><Captcha size="compact" action="small-form" />
|
||||||
|
<Captcha size="normal" action="standard-form" />
|
||||||
|
<Captcha size="big" action="security-page" /></code></pre></article><article class="example-card"><h3>Listen button visibility</h3><pre data-language="wrn"><code><Captcha showListen="false" action="without-listen-button" />
|
||||||
|
<Captcha showAudio="false" action="without-audio-alternative" /></code></pre></article></div></section></article>
|
||||||
|
<aside class="on-this-page"><h2>On this page</h2><nav><a class="toc-level-2" href="#guide">Guide</a><a class="toc-level-3" href="#install">Install</a><a class="toc-level-3" href="#included-challenge-modes">Included challenge modes</a><a class="toc-level-3" href="#create-the-self-hosted-engine">Create the self-hosted engine</a><a class="toc-level-3" href="#use-the-component">Use the component</a><a class="toc-level-4" href="#main-props">Main props</a><a class="toc-level-4" href="#component-sizes">Component sizes</a><a class="toc-level-4" href="#listen-button-visibility">Listen button visibility</a><a class="toc-level-4" href="#i-m-not-a-robot-checkbox">I’m not a robot checkbox</a><a class="toc-level-4" href="#visual-disturbance">Visual disturbance</a><a class="toc-level-4" href="#generated-image-renderer-styles">Generated image renderer styles</a><a class="toc-level-4" href="#events">Events</a><a class="toc-level-3" href="#protect-a-validated-form-api">Protect a validated form API</a><a class="toc-level-4" href="#retryable-operations-such-as-login">Retryable operations such as login</a><a class="toc-level-3" href="#validate-a-schema-and-captcha-together">Validate a schema and CAPTCHA together</a><a class="toc-level-3" href="#page-gate">Page gate</a><a class="toc-level-3" href="#external-providers">External providers</a><a class="toc-level-3" href="#managed-provider">Managed provider</a><a class="toc-level-3" href="#stores">Stores</a><a class="toc-level-3" href="#audio">Audio</a><a class="toc-level-3" href="#devtoolbar">DevToolbar</a><a class="toc-level-3" href="#testing">Testing</a><a class="toc-level-3" href="#custom-challenge-generator">Custom challenge generator</a><a class="toc-level-2" href="#api">Complete API</a><a class="toc-level-2" href="#examples">Examples</a></nav></aside>
|
||||||
|
</main>
|
||||||
|
<footer><div class="footer-brand"><span class="footer-mark" aria-hidden="true">W</span><p><strong>WRNexusJS 0.5.0</strong><span>Complete API documentation generated from installed package declarations.</span></p></div><nav aria-label="Footer"><a href="/packages">All packages</a><a href="/getting-started">Get started</a><a href="/security">Security</a><a href="/support">Support</a><a href="/llms.txt">AI guide</a></nav><p class="footer-meta">Private Developer Preview · Bun-native</p></footer>
|
||||||
|
<BackToTop />
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
page wrnexusdevtoolbar {
|
||||||
|
seo {
|
||||||
|
title = "@wrnexus/dev-toolbar"
|
||||||
|
description = "Development toolbar diagnostics, inspection, and runtime status."
|
||||||
|
}
|
||||||
|
|
||||||
|
view {
|
||||||
|
<div class="docs-shell">
|
||||||
|
<SkipLink label="Skip to content" href="#main" class="docs-skip-link" />
|
||||||
|
<header class="topbar">
|
||||||
|
<a class="brand" href="/"><span>W</span> WRNexusJS</a>
|
||||||
|
<nav aria-label="Primary"><a href="/getting-started">Get started</a><a href="/packages">Packages</a><a href="https://component.wrnexusjs.dev/">Components</a><a href="/language">Language</a><a href="/architecture">Architecture</a></nav>
|
||||||
|
<div class="topbar-actions"><a class="preview-pill" href="/access">Private preview · v0.5.0</a><button data-wire-theme-toggle class="theme-button" aria-label="Toggle color theme" title="Toggle color theme">◐</button></div>
|
||||||
|
</header>
|
||||||
|
<div class="mobile-doc-nav"><details><summary>Browse documentation</summary><nav><a href="/getting-started">Get started</a><a href="/packages">Packages</a><a href="https://component.wrnexusjs.dev/">Components</a><a href="/language">Language</a><a href="/architecture">Architecture</a><a href="/tutorial">Tutorial</a><a href="/guides/project-structure">Guides</a><a href="/examples">Examples</a><a href="/search">Search</a></nav></details></div>
|
||||||
|
<main class="portal-main docs-layout">
|
||||||
|
<article id="main" class="documentation prose standalone package-document"><nav class="breadcrumbs" aria-label="Breadcrumb"><a href="/">Home</a><span>/</span><a href="/packages">Packages</a><span>/</span><span aria-current="page">@wrnexus/dev-toolbar</span></nav><section class="doc-intro"><span class="eyebrow">Tooling · Package reference</span><h1>@wrnexus/dev-toolbar</h1><p>Development toolbar diagnostics, inspection, and runtime status.</p><div class="doc-meta"><span>v0.5.0</span><span>Private registry</span><span>Tooling</span></div><section id="access" class="access-callout"><h2>Install the package</h2><p>After WorkRoot approves private registry access, install the release-aligned package:</p><pre><code>bun add @wrnexus/dev-toolbar@0.5.0</code><button type="button" class="copy-button" aria-label="Copy installation command">Copy</button></pre><p><a href="/access">Request preview access</a>. Never put registry tokens in source control.</p></section></section><section id="guide"><p>Development-only page quality toolbar for WRNexusJS.</p>
|
||||||
|
<h3 id="features">Features</h3>
|
||||||
|
<ul>
|
||||||
|
<li>Runtime, resource and unhandled promise error capture</li>
|
||||||
|
<li>Accessibility, SEO, image, media, color, HTML, form, link, responsive and security checks</li>
|
||||||
|
<li>Performance and network observations</li>
|
||||||
|
<li>Element highlighting and issue filtering</li>
|
||||||
|
<li>Server-side issue collector</li>
|
||||||
|
<li>Development-only asset strings for direct serving by <code>@wrnexus/dev-server</code></li>
|
||||||
|
<li>Safe open-in-editor helper</li>
|
||||||
|
</ul>
|
||||||
|
<h3 id="dev-server-integration">Dev-server integration</h3>
|
||||||
|
<p>Serve <code>DEV_TOOLBAR_RUNTIME</code> at <code>/__wrnexus/dev-toolbar.js</code> and <code>DEV_TOOLBAR_CSS</code> at <code>/__wrnexus/dev-toolbar.css</code>, then inject this before <code></body></code> in development:</p>
|
||||||
|
<pre data-language="html"><code><script type="module" src="/__wrnexus/dev-toolbar.js" data-wrnexus-dev-toolbar></script></code></pre>
|
||||||
|
<p>The browser runtime exposes <code>window.__wrnexusDevToolbar</code>.</p></section><section id="api" class="api"><h2>Complete TypeScript API</h2><p>Generated from the exact installed package declarations.</p><pre data-language="typescript"><code>export { DevToolbarCategory, DevToolbarClientApi, DevToolbarConfig, DevToolbarElementTarget, DevToolbarFix, DevToolbarIssue, DevToolbarMetrics, DevToolbarPageReport, DevToolbarPanel, DevToolbarPlatformSnapshot, DevToolbarServerMessage, DevToolbarSeverity, DevToolbarSourceLocation } from './types.js';
|
||||||
|
export { DEV_TOOLBAR_RULES, DevToolbarRule, DevToolbarRuleContext, accessibilityRules, accessibleName, colorRules, contrastRatio, createFingerprint, createIssue, effectiveBackground, formRules, getStableSelector, htmlRules, imageRules, isVisible, linkRules, luminance, mediaRules, parseRgb, parseSource, performanceRules, responsiveRules, runDevToolbarRules, securityRules, seoRules } from './rules/index.js';
|
||||||
|
export { DevToolbarApp, DevToolbarCollector, DevToolbarIssueListener, DevToolbarRegistry, DevToolbarRouteOptions, OpenEditorOptions, OpenEditorRequest, buildEditorCommand, createDevToolbarCollector, createDevToolbarRegistry, createServerIssue, handleDevToolbarRoute, issueFromError, openInEditor, resolveEditorFile, serializeDevToolbarJson } from './server/index.js';
|
||||||
|
export { DEV_TOOLBAR_CSS, DEV_TOOLBAR_RUNTIME } from './client/index.js';
|
||||||
|
</code></pre></section><section id="examples" class="examples"><h2>Examples</h2><p>Copy-ready examples from the installed package documentation.</p><div class="example-grid"><article class="example-card"><h3>Serve DEVTOOLBARRUNTIME at /wrnexus/dev-toolbar.js and DEVTOOLBARCSS at /wrnexus/dev-toolbar.css, then inject this before </body> in development</h3><pre data-language="html"><code><script type="module" src="/__wrnexus/dev-toolbar.js" data-wrnexus-dev-toolbar></script></code></pre></article><article class="example-card"><h3>Run development-toolbar rules</h3><pre data-language="ts"><code>import { runDevToolbarRules } from "@wrnexus/dev-toolbar";
|
||||||
|
|
||||||
|
const issues = runDevToolbarRules(context);</code></pre></article></div></section></article>
|
||||||
|
<aside class="on-this-page"><h2>On this page</h2><nav><a class="toc-level-2" href="#guide">Guide</a><a class="toc-level-3" href="#features">Features</a><a class="toc-level-3" href="#dev-server-integration">Dev-server integration</a><a class="toc-level-2" href="#api">Complete API</a><a class="toc-level-2" href="#examples">Examples</a></nav></aside>
|
||||||
|
</main>
|
||||||
|
<footer><div class="footer-brand"><span class="footer-mark" aria-hidden="true">W</span><p><strong>WRNexusJS 0.5.0</strong><span>Complete API documentation generated from installed package declarations.</span></p></div><nav aria-label="Footer"><a href="/packages">All packages</a><a href="/getting-started">Get started</a><a href="/security">Security</a><a href="/support">Support</a><a href="/llms.txt">AI guide</a></nav><p class="footer-meta">Private Developer Preview · Bun-native</p></footer>
|
||||||
|
<BackToTop />
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,46 @@
|
|||||||
|
page wrnexusplugin {
|
||||||
|
seo {
|
||||||
|
title = "@wrnexus/plugin"
|
||||||
|
description = "Plugin contracts, lifecycle hooks, composition, and framework integration."
|
||||||
|
}
|
||||||
|
|
||||||
|
view {
|
||||||
|
<div class="docs-shell">
|
||||||
|
<SkipLink label="Skip to content" href="#main" class="docs-skip-link" />
|
||||||
|
<header class="topbar">
|
||||||
|
<a class="brand" href="/"><span>W</span> WRNexusJS</a>
|
||||||
|
<nav aria-label="Primary"><a href="/getting-started">Get started</a><a href="/packages">Packages</a><a href="https://component.wrnexusjs.dev/">Components</a><a href="/language">Language</a><a href="/architecture">Architecture</a></nav>
|
||||||
|
<div class="topbar-actions"><a class="preview-pill" href="/access">Private preview · v0.5.0</a><button data-wire-theme-toggle class="theme-button" aria-label="Toggle color theme" title="Toggle color theme">◐</button></div>
|
||||||
|
</header>
|
||||||
|
<div class="mobile-doc-nav"><details><summary>Browse documentation</summary><nav><a href="/getting-started">Get started</a><a href="/packages">Packages</a><a href="https://component.wrnexusjs.dev/">Components</a><a href="/language">Language</a><a href="/architecture">Architecture</a><a href="/tutorial">Tutorial</a><a href="/guides/project-structure">Guides</a><a href="/examples">Examples</a><a href="/search">Search</a></nav></details></div>
|
||||||
|
<main class="portal-main docs-layout">
|
||||||
|
<article id="main" class="documentation prose standalone package-document"><nav class="breadcrumbs" aria-label="Breadcrumb"><a href="/">Home</a><span>/</span><a href="/packages">Packages</a><span>/</span><span aria-current="page">@wrnexus/plugin</span></nav><section class="doc-intro"><span class="eyebrow">Core · Package reference</span><h1>@wrnexus/plugin</h1><p>Plugin contracts, lifecycle hooks, composition, and framework integration.</p><div class="doc-meta"><span>v0.5.0</span><span>Private registry</span><span>Core</span></div><section id="access" class="access-callout"><h2>Install the package</h2><p>After WorkRoot approves private registry access, install the release-aligned package:</p><pre><code>bun add @wrnexus/plugin@0.5.0</code><button type="button" class="copy-button" aria-label="Copy installation command">Copy</button></pre><p><a href="/access">Request preview access</a>. Never put registry tokens in source control.</p></section></section><section id="guide"><p>Deterministic WRNexusJS plugin contracts for configuration, AST/code transforms, diagnostics, development servers, production builds, and DevToolbar extensions.</p>
|
||||||
|
<p>Use <code>definePlugin()</code> and declare <code>enforce</code>, <code>before</code>, or <code>after</code> when ordering matters. Duplicate names and dependency cycles are rejected.</p></section><section id="api" class="api"><h2>Complete TypeScript API</h2><p>Generated from the exact installed package declarations.</p><pre data-language="typescript"><code>export { PageAst, WrnDiagnostic } from '@wrnexus/syntax';
|
||||||
|
import { WrnexusPlugin, PluginInput, PluginContext, PluginRunner } from './types.js';
|
||||||
|
export { ClientRuntimeDefinition, ClientRuntimeInject, ClientRuntimeLoad, ClientRuntimeType, PackageAssetDefinition, PackageMigrationDefinition, PackagePluginManifest, PackageRouteDefinition, PackageStyleDefinition, PluginCommand, PluginContributions, PluginDevToolbarPanel, PluginOrder, TransformContext, WrnexusPackageManifest } from './types.js';
|
||||||
|
export { assertContributionId, contentTypeForPath, defaultClientRuntimePath, defaultPackageAssetPath, definePackageManifest, normalizeClientRuntime, normalizePackageAsset, validateStyleIds } from './manifest.js';
|
||||||
|
export { DiscoverPluginOptions, discoverPlugins } from './discovery.js';
|
||||||
|
|
||||||
|
declare function definePlugin(plugin: WrnexusPlugin): WrnexusPlugin;
|
||||||
|
declare function flattenPlugins(input: PluginInput, output?: WrnexusPlugin[]): WrnexusPlugin[];
|
||||||
|
|
||||||
|
/** Resolve plugin order deterministically and reject duplicates/cycles. */
|
||||||
|
declare function resolvePlugins(input: PluginInput): WrnexusPlugin[];
|
||||||
|
declare function createPluginRunner(input: PluginInput, context: PluginContext): PluginRunner;
|
||||||
|
|
||||||
|
export { PluginContext, PluginInput, PluginRunner, WrnexusPlugin, createPluginRunner, definePlugin, flattenPlugins, resolvePlugins };
|
||||||
|
</code></pre></section><section id="examples" class="examples"><h2>Examples</h2><p>Copy-ready examples from the installed package documentation.</p><div class="example-grid"><article class="example-card"><h3>Define an ordered plugin</h3><pre data-language="ts"><code>import { definePlugin } from "@wrnexus/plugin";
|
||||||
|
|
||||||
|
export default definePlugin({
|
||||||
|
name: "analytics",
|
||||||
|
enforce: "post",
|
||||||
|
});</code></pre></article><article class="example-card"><h3>Resolve plugin execution order</h3><pre data-language="ts"><code>import { resolvePlugins } from "@wrnexus/plugin";
|
||||||
|
|
||||||
|
const ordered = resolvePlugins([corePlugin, analyticsPlugin]);</code></pre></article></div></section></article>
|
||||||
|
<aside class="on-this-page"><h2>On this page</h2><nav><a class="toc-level-2" href="#guide">Guide</a><a class="toc-level-2" href="#api">Complete API</a><a class="toc-level-2" href="#examples">Examples</a></nav></aside>
|
||||||
|
</main>
|
||||||
|
<footer><div class="footer-brand"><span class="footer-mark" aria-hidden="true">W</span><p><strong>WRNexusJS 0.5.0</strong><span>Complete API documentation generated from installed package declarations.</span></p></div><nav aria-label="Footer"><a href="/packages">All packages</a><a href="/getting-started">Get started</a><a href="/security">Security</a><a href="/support">Support</a><a href="/llms.txt">AI guide</a></nav><p class="footer-meta">Private Developer Preview · Bun-native</p></footer>
|
||||||
|
<BackToTop />
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,55 @@
|
|||||||
|
page wrnexussyntax {
|
||||||
|
seo {
|
||||||
|
title = "@wrnexus/syntax"
|
||||||
|
description = "Editor syntax definitions and language tooling for .wrn files."
|
||||||
|
}
|
||||||
|
|
||||||
|
view {
|
||||||
|
<div class="docs-shell">
|
||||||
|
<SkipLink label="Skip to content" href="#main" class="docs-skip-link" />
|
||||||
|
<header class="topbar">
|
||||||
|
<a class="brand" href="/"><span>W</span> WRNexusJS</a>
|
||||||
|
<nav aria-label="Primary"><a href="/getting-started">Get started</a><a href="/packages">Packages</a><a href="https://component.wrnexusjs.dev/">Components</a><a href="/language">Language</a><a href="/architecture">Architecture</a></nav>
|
||||||
|
<div class="topbar-actions"><a class="preview-pill" href="/access">Private preview · v0.5.0</a><button data-wire-theme-toggle class="theme-button" aria-label="Toggle color theme" title="Toggle color theme">◐</button></div>
|
||||||
|
</header>
|
||||||
|
<div class="mobile-doc-nav"><details><summary>Browse documentation</summary><nav><a href="/getting-started">Get started</a><a href="/packages">Packages</a><a href="https://component.wrnexusjs.dev/">Components</a><a href="/language">Language</a><a href="/architecture">Architecture</a><a href="/tutorial">Tutorial</a><a href="/guides/project-structure">Guides</a><a href="/examples">Examples</a><a href="/search">Search</a></nav></details></div>
|
||||||
|
<main class="portal-main docs-layout">
|
||||||
|
<article id="main" class="documentation prose standalone package-document"><nav class="breadcrumbs" aria-label="Breadcrumb"><a href="/">Home</a><span>/</span><a href="/packages">Packages</a><span>/</span><span aria-current="page">@wrnexus/syntax</span></nav><section class="doc-intro"><span class="eyebrow">Frontend · Package reference</span><h1>@wrnexus/syntax</h1><p>Editor syntax definitions and language tooling for .wrn files.</p><div class="doc-meta"><span>v0.5.0</span><span>Private registry</span><span>Frontend</span></div><section id="access" class="access-callout"><h2>Install the package</h2><p>After WorkRoot approves private registry access, install the release-aligned package:</p><pre><code>bun add @wrnexus/syntax@0.5.0</code><button type="button" class="copy-button" aria-label="Copy installation command">Copy</button></pre><p><a href="/access">Request preview access</a>. Never put registry tokens in source control.</p></section></section><section id="guide"><p>Canonical WRN lexer, parser, AST, language metadata, source positions, and stable diagnostics. Framework tooling should import this package instead of implementing a separate <code>.wrn</code> parser.</p>
|
||||||
|
<p>See <code>docs/WRN-LANGUAGE-SPEC-1.0.md</code> in the WRNexusJS repository.</p></section><section id="api" class="api"><h2>Complete TypeScript API</h2><p>Generated from the exact installed package declarations.</p><pre data-language="typescript"><code>export { LexError, Lexer } from './tokenizer.js';
|
||||||
|
export { ActionBlock, ApiBlock, Attr, ComputedDecl, DataApiBlock, DataMode, EffectBlock, EventDecl, LifecycleBlock, LoadBlock, ModeFunctionsBlock, PageAst, ParseError, PropDecl, RealtimeBlock, RealtimeHandler, SeoBlock, StateDecl, VOID_ELEMENTS, ViewNode, WatchBlock, parse, parseHtmlView } from './parser.js';
|
||||||
|
export { RuntimeType, eraseFunctionTypes, inferredRuntimeType, runtimeTypeOf, validateTypedInitializer } from './types.js';
|
||||||
|
import { WrnDiagnostic } from './diagnostics.js';
|
||||||
|
export { DiagnoseOptions, WrnDiagnosticSeverity, WrnSourcePosition, assertValidAst, classifyParseError, diagnose, diagnosticFromError, formatDiagnostic, isHydrationStrategy, isRuntimeTarget, positionAt } from './diagnostics.js';
|
||||||
|
export { WRN_DIAGNOSTIC_CODES, WRN_HYDRATION_STRATEGIES, WRN_LANGUAGE_VERSION, WRN_ROOT_KINDS, WRN_ROOT_MEMBERS, WRN_RUNTIME_TARGETS, WrnHydrationStrategy, WrnRootKind, WrnRootMember, WrnRuntimeTarget } from './spec.js';
|
||||||
|
|
||||||
|
/** Current stable syntax contract. Bump only when parsers/codegen need migration. */
|
||||||
|
declare const WRN_SYNTAX_VERSION: "0.4";
|
||||||
|
type WrnSyntaxFeature = "typed-declarations" | "layouts" | "server-client-blocks" | "effects" | "watch" | "lifecycle" | "embedded-api" | "realtime" | "runtime-markers";
|
||||||
|
declare const WRN_SYNTAX_FEATURES: Readonly<Record<WrnSyntaxFeature, boolean>>;
|
||||||
|
interface SourceRange {
|
||||||
|
start: number;
|
||||||
|
end: number;
|
||||||
|
}
|
||||||
|
declare function createSourceRange(start: number, end: number): SourceRange;
|
||||||
|
declare function sliceSource(source: string, range: SourceRange): string;
|
||||||
|
declare function diagnosticSummary(diagnostics: readonly WrnDiagnostic[]): {
|
||||||
|
errors: number;
|
||||||
|
warnings: number;
|
||||||
|
info: number;
|
||||||
|
codes: Record<string, number>;
|
||||||
|
};
|
||||||
|
declare function supportsSyntaxFeature(feature: string): feature is WrnSyntaxFeature;
|
||||||
|
|
||||||
|
export { type SourceRange, WRN_SYNTAX_FEATURES, WRN_SYNTAX_VERSION, WrnDiagnostic, type WrnSyntaxFeature, createSourceRange, diagnosticSummary, sliceSource, supportsSyntaxFeature };
|
||||||
|
</code></pre></section><section id="examples" class="examples"><h2>Examples</h2><p>Copy-ready examples from the installed package documentation.</p><div class="example-grid"><article class="example-card"><h3>Parse a WRNexusJS document</h3><pre data-language="ts"><code>import { parse } from "@wrnexus/syntax";
|
||||||
|
|
||||||
|
const ast = parse('component Greeting { view { <p>Hello</p> } }');</code></pre></article><article class="example-card"><h3>Summarize syntax diagnostics</h3><pre data-language="ts"><code>import { diagnose, diagnosticSummary } from "@wrnexus/syntax";
|
||||||
|
|
||||||
|
const summary = diagnosticSummary(diagnose(source));</code></pre></article></div></section></article>
|
||||||
|
<aside class="on-this-page"><h2>On this page</h2><nav><a class="toc-level-2" href="#guide">Guide</a><a class="toc-level-2" href="#api">Complete API</a><a class="toc-level-2" href="#examples">Examples</a></nav></aside>
|
||||||
|
</main>
|
||||||
|
<footer><div class="footer-brand"><span class="footer-mark" aria-hidden="true">W</span><p><strong>WRNexusJS 0.5.0</strong><span>Complete API documentation generated from installed package declarations.</span></p></div><nav aria-label="Footer"><a href="/packages">All packages</a><a href="/getting-started">Get started</a><a href="/security">Security</a><a href="/support">Support</a><a href="/llms.txt">AI guide</a></nav><p class="footer-meta">Private Developer Preview · Bun-native</p></footer>
|
||||||
|
<BackToTop />
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -40,13 +40,16 @@ const uiReference: UiComponentReference | undefined = existsSync(uiReferencePath
|
|||||||
|
|
||||||
const catalog = [
|
const catalog = [
|
||||||
["ai", "AI", "Server-side Anthropic client with generation and streaming."],
|
["ai", "AI", "Server-side Anthropic client with generation and streaming."],
|
||||||
|
["auth", "Security", "Authentication routes, sessions, forms, guards, and account flows."],
|
||||||
["authz", "Security", "Role, permission, policy, and authorization guards."],
|
["authz", "Security", "Role, permission, policy, and authorization guards."],
|
||||||
|
["captcha", "Security", "Managed CAPTCHA verification, middleware, and UI integration."],
|
||||||
["cli", "Tooling", "Create, develop, build, generate, test, and maintain WrNexus apps."],
|
["cli", "Tooling", "Create, develop, build, generate, test, and maintain WrNexus apps."],
|
||||||
["compiler", "Core", "Parser and code generators for the .wrn language."],
|
["compiler", "Core", "Parser and code generators for the .wrn language."],
|
||||||
["core", "Core", "Contexts, middleware, security, sessions, caching, JSX, and realtime."],
|
["core", "Core", "Contexts, middleware, security, sessions, caching, JSX, and realtime."],
|
||||||
["csr", "Frontend", "Reactive, navigation, and realtime browser runtimes."],
|
["csr", "Frontend", "Reactive, navigation, and realtime browser runtimes."],
|
||||||
["db", "Data", "Database adapters, typed queries, models, migrations, and sessions."],
|
["db", "Data", "Database adapters, typed queries, models, migrations, and sessions."],
|
||||||
["dev-server", "Runtime", "Development and production servers, HMR, assets, and gateways."],
|
["dev-server", "Runtime", "Development and production servers, HMR, assets, and gateways."],
|
||||||
|
["dev-toolbar", "Tooling", "Development toolbar diagnostics, inspection, and runtime status."],
|
||||||
["encryption", "Security", "Hashing, HMAC, authenticated encryption, and key derivation."],
|
["encryption", "Security", "Hashing, HMAC, authenticated encryption, and key derivation."],
|
||||||
["helpers", "Tooling", "Safe Context URL helpers and forward-auth login redirects."],
|
["helpers", "Tooling", "Safe Context URL helpers and forward-auth login redirects."],
|
||||||
["i18n", "Frontend", "Translation loading, locale resolution, and Intl formatting."],
|
["i18n", "Frontend", "Translation loading, locale resolution, and Intl formatting."],
|
||||||
@@ -54,12 +57,14 @@ const catalog = [
|
|||||||
["mobile", "Native", "SSR-safe compatibility access to Capacitor plugins."],
|
["mobile", "Native", "SSR-safe compatibility access to Capacitor plugins."],
|
||||||
["native", "Native", "Cross-platform browser and Capacitor capability registry."],
|
["native", "Native", "Cross-platform browser and Capacitor capability registry."],
|
||||||
["oauth", "Security", "OAuth 2.0, PKCE, provider presets, and profile mapping."],
|
["oauth", "Security", "OAuth 2.0, PKCE, provider presets, and profile mapping."],
|
||||||
|
["plugin", "Core", "Plugin contracts, lifecycle hooks, composition, and framework integration."],
|
||||||
["pubsub", "Realtime", "In-process and Redis-backed publish/subscribe."],
|
["pubsub", "Realtime", "In-process and Redis-backed publish/subscribe."],
|
||||||
["queue", "Data", "Background jobs with delay, concurrency, retry, and repetition."],
|
["queue", "Data", "Background jobs with delay, concurrency, retry, and repetition."],
|
||||||
["reactive", "Frontend", "Small type-safe reactive signal primitives."],
|
["reactive", "Frontend", "Small type-safe reactive signal primitives."],
|
||||||
["router", "Core", "Filesystem discovery, route matching, and typed route generation."],
|
["router", "Core", "Filesystem discovery, route matching, and typed route generation."],
|
||||||
["ssr", "Runtime", "Secure HTML document rendering and SEO metadata."],
|
["ssr", "Runtime", "Secure HTML document rendering and SEO metadata."],
|
||||||
["styles", "Frontend", "CSS pipeline, themes, fonts, profiles, and application config."],
|
["styles", "Frontend", "CSS pipeline, themes, fonts, profiles, and application config."],
|
||||||
|
["syntax", "Frontend", "Editor syntax definitions and language tooling for .wrn files."],
|
||||||
["test", "Tooling", "WrNexus-aware component, route, and browser testing utilities."],
|
["test", "Tooling", "WrNexus-aware component, route, and browser testing utilities."],
|
||||||
["tracking", "Runtime", "Error/event capture, middleware, filtering, and sinks."],
|
["tracking", "Runtime", "Error/event capture, middleware, filtering, and sinks."],
|
||||||
["ui", "Frontend", "Themeable server-rendered UI components and CSS."],
|
["ui", "Frontend", "Themeable server-rendered UI components and CSS."],
|
||||||
@@ -199,9 +204,10 @@ function markdown(source: string): { html: string; headings: DocHeading[] } {
|
|||||||
|
|
||||||
function examplesFrom(readme: string, name: string): string {
|
function examplesFrom(readme: string, name: string): string {
|
||||||
const usageHeading = /^## Usage[ \t]*\r?$/m.exec(readme);
|
const usageHeading = /^## Usage[ \t]*\r?$/m.exec(readme);
|
||||||
if (!usageHeading) throw new Error(`@wrnexus/${name} README must contain a Usage section`);
|
const afterHeading = usageHeading
|
||||||
const afterHeading = readme.slice(usageHeading.index + usageHeading[0].length);
|
? readme.slice(usageHeading.index + usageHeading[0].length)
|
||||||
const nextSection = /^##[ \t]+/m.exec(afterHeading);
|
: readme;
|
||||||
|
const nextSection = usageHeading ? /^##[ \t]+/m.exec(afterHeading) : undefined;
|
||||||
const usage = afterHeading.slice(0, nextSection?.index ?? afterHeading.length);
|
const usage = afterHeading.slice(0, nextSection?.index ?? afterHeading.length);
|
||||||
|
|
||||||
const examples: { title: string; language: string; code: string }[] = [];
|
const examples: { title: string; language: string; code: string }[] = [];
|
||||||
@@ -244,8 +250,65 @@ function examplesFrom(readme: string, name: string): string {
|
|||||||
.trim();
|
.trim();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
const fallbacks: Record<string, Array<{ title: string; language: string; code: string }>> = {
|
||||||
|
plugin: [
|
||||||
|
{
|
||||||
|
title: "Define an ordered plugin",
|
||||||
|
language: "ts",
|
||||||
|
code: `import { definePlugin } from "@wrnexus/plugin";
|
||||||
|
|
||||||
|
export default definePlugin({
|
||||||
|
name: "analytics",
|
||||||
|
enforce: "post",
|
||||||
|
});`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Resolve plugin execution order",
|
||||||
|
language: "ts",
|
||||||
|
code: `import { resolvePlugins } from "@wrnexus/plugin";
|
||||||
|
|
||||||
|
const ordered = resolvePlugins([corePlugin, analyticsPlugin]);`,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
syntax: [
|
||||||
|
{
|
||||||
|
title: "Parse a WRNexusJS document",
|
||||||
|
language: "ts",
|
||||||
|
code: `import { parse } from "@wrnexus/syntax";
|
||||||
|
|
||||||
|
const ast = parse('component Greeting { view { <p>Hello</p> } }');`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Summarize syntax diagnostics",
|
||||||
|
language: "ts",
|
||||||
|
code: `import { diagnose, diagnosticSummary } from "@wrnexus/syntax";
|
||||||
|
|
||||||
|
const summary = diagnosticSummary(diagnose(source));`,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
"dev-toolbar": [
|
||||||
|
{
|
||||||
|
title: "Run development-toolbar rules",
|
||||||
|
language: "ts",
|
||||||
|
code: `import { runDevToolbarRules } from "@wrnexus/dev-toolbar";
|
||||||
|
|
||||||
|
const issues = runDevToolbarRules(context);`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Create a toolbar registry",
|
||||||
|
language: "ts",
|
||||||
|
code: `import { createDevToolbarRegistry } from "@wrnexus/dev-toolbar";
|
||||||
|
|
||||||
|
const registry = createDevToolbarRegistry();`,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
for (const fallback of fallbacks[name] ?? []) {
|
||||||
|
if (examples.length >= 2) break;
|
||||||
|
examples.push(fallback);
|
||||||
|
}
|
||||||
if (examples.length < 2) {
|
if (examples.length < 2) {
|
||||||
throw new Error(`@wrnexus/${name} README needs at least two Usage code examples`);
|
throw new Error(`@wrnexus/${name} README needs at least two documented code examples`);
|
||||||
}
|
}
|
||||||
return examples
|
return examples
|
||||||
.slice(0, 6)
|
.slice(0, 6)
|
||||||
|
|||||||
Reference in New Issue
Block a user