Files
WRNexusJSDoc/app/pages/packages/helpers.wrn
T
ClintchizandClaude Opus 5.5 b536b57447 docs: update portal for WRNexusJS 0.8.72
- Bump every @wrnexus/* dependency to its latest release (ui 0.8.88,
  styles 0.8.26, cli 0.8.72, core 0.8.16, i18n 0.8.12, ...) and refresh
  bun.lock and package-lock.json. graphql/native 0.8.8 were republished,
  so their stale lockfile integrity entries were re-resolved.
- wrnexus.config.ts: drop `functions` and `compatibility`, both removed
  from the framework.
- Regenerate package, portal and discovery docs from the installed
  packages, including the new 0.8.72 release page.
- Package pages now install each package's own released version
  (ui 0.8.88, core 0.8.16) instead of stamping the CLI version on all.
- llms.txt / llms-full.txt: the generator kept every earlier run's CLI
  reference and appended another; it now replaces it, so the guide holds
  one current reference and no longer advertises the removed
  `wrnexus compatibility` command.
- docs.test.ts: compileWireFile was renamed compileWrnFile; tests follow
  the per-package install versions and the removed command.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 16:46:32 +05:30

223 lines
18 KiB
Plaintext

page wrnexushelpers {
seo {
title = "@wrnexus/helpers"
description = "Safe Context URL helpers and forward-auth login redirects."
}
view {
<div class="docs-shell">
<a href="#main" class="skip-link">Skip to content</a>
<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.8.72</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 id="main" class="portal-main docs-layout docs-layout--navigation"><aside class="docs-section-menu" aria-label="Documentation sections"><nav>
<section><strong>Get started</strong><a href="/getting-started">Quick start</a><a href="/tutorial">Tutorial</a><a href="/guides/full-stack-auth-demo">Auth dashboard demo</a><a href="/guides/configuration-and-profiles">Configuration</a></section>
<section><strong>Build</strong><a href="/guides/pages-and-components">Pages and components</a><a href="/guides/routing">Routing</a><a href="/guides/server-data">Server data</a><a href="/guides/forms-and-validation">Forms and validation</a><a href="/guides/database">Database</a></section>
<section><strong>Auth and security</strong><a href="/guides/authentication">Authentication</a><a href="/guides/authorization">Authorization</a><a href="/guides/security">Application security</a><a href="/security">Security policy</a></section>
<section><strong>Reference</strong><a href="/packages">Packages overview</a><a href="/packages/cli">CLI</a><a href="/language">.wrn language</a><a href="https://component.wrnexusjs.dev/">Components</a><a href="/examples">Examples</a></section>
<section class="package-menu"><strong>Packages</strong><a href="/packages/ai">@wrnexus/ai</a><a href="/packages/auth">@wrnexus/auth</a><a href="/packages/authz">@wrnexus/authz</a><a href="/packages/benchmark">@wrnexus/benchmark</a><a href="/packages/cache">@wrnexus/cache</a><a href="/packages/captcha">@wrnexus/captcha</a><a href="/packages/cli">@wrnexus/cli</a><a href="/packages/compiler">@wrnexus/compiler</a><a href="/packages/content">@wrnexus/content</a><a href="/packages/core">@wrnexus/core</a><a href="/packages/csr">@wrnexus/csr</a><a href="/packages/db">@wrnexus/db</a><a href="/packages/dev-server">@wrnexus/dev-server</a><a href="/packages/dev-toolbar">@wrnexus/dev-toolbar</a><a href="/packages/encryption">@wrnexus/encryption</a><a href="/packages/graphql">@wrnexus/graphql</a><a href="/packages/helpers">@wrnexus/helpers</a><a href="/packages/i18n">@wrnexus/i18n</a><a href="/packages/identity">@wrnexus/identity</a><a href="/packages/image">@wrnexus/image</a><a href="/packages/jwt">@wrnexus/jwt</a><a href="/packages/language-server">@wrnexus/language-server</a><a href="/packages/mcp">@wrnexus/mcp</a><a href="/packages/mobile">@wrnexus/mobile</a><a href="/packages/native">@wrnexus/native</a><a href="/packages/oauth">@wrnexus/oauth</a><a href="/packages/observability">@wrnexus/observability</a><a href="/packages/playground">@wrnexus/playground</a><a href="/packages/plugin">@wrnexus/plugin</a><a href="/packages/pubsub">@wrnexus/pubsub</a><a href="/packages/pwa">@wrnexus/pwa</a><a href="/packages/queue">@wrnexus/queue</a><a href="/packages/reactive">@wrnexus/reactive</a><a href="/packages/realtime">@wrnexus/realtime</a><a href="/packages/router">@wrnexus/router</a><a href="/packages/security">@wrnexus/security</a><a href="/packages/ssr">@wrnexus/ssr</a><a href="/packages/store">@wrnexus/store</a><a href="/packages/styles">@wrnexus/styles</a><a href="/packages/syntax">@wrnexus/syntax</a><a href="/packages/test">@wrnexus/test</a><a href="/packages/tracking">@wrnexus/tracking</a><a href="/packages/typecheck">@wrnexus/typecheck</a><a href="/packages/ui">@wrnexus/ui</a><a href="/packages/uploader">@wrnexus/uploader</a><a href="/packages/validation">@wrnexus/validation</a></section>
</nav></aside><div class="docs-reading-column">
<article 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/helpers</span></nav><section class="doc-intro"><span class="eyebrow">Tooling · Package reference</span><h1>@wrnexus/helpers</h1><p>Safe Context URL helpers and forward-auth login redirects.</p><div class="doc-meta"><span>v0.8.9</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 package:</p><pre><code>bun add @wrnexus/helpers@0.8.9</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>Safe convenience helpers for common WRNexusJS application flows. The package uses standard <code>Context</code>, <code>URL</code>, and <code>Response</code> values and has no runtime dependency beyond <code>@wrnexus/core</code>.</p>
<pre data-language="bash"><code>bun add @wrnexus/helpers</code></pre>
<p>The package is private, so the machine must be authenticated to the <code>wrnexus</code> npm organization.</p>
<h3 id="usage">Usage</h3>
<h4 id="redirect-an-unauthenticated-forward-auth-request">Redirect an unauthenticated forward-auth request</h4>
<p>The gateway calls an SSO verifier on a different URL from the original application. These helpers reconstruct the original URL from the gateway headers and safely place it in the login redirect:</p>
<pre data-language="ts"><code>import type &#123; Context &#125; from &quot;@wrnexus/core&quot;;
import &#123; redirectToLogin &#125; from &quot;@wrnexus/helpers&quot;;
export const GET = async (ctx: Context) =&gt; &#123;
if (await hasValidSession(ctx)) &#123;
return new Response(null, &#123; status: 204 &#125;);
&#125;
return redirectToLogin(ctx, &quot;/login&quot;, &#123;
allowedHosts: [&quot;admin.localhost:3000&quot;, &quot;reports.localhost:3000&quot;],
&#125;);
&#125;;</code></pre>
<p>This creates a response such as:</p>
<pre data-language="text"><code>Location: http://sso.localhost:3000/login?returnTo=http%3A%2F%2Fadmin.localhost%3A3000%2F</code></pre>
<p>Always list the application hosts that are valid redirect destinations. Forwarded host headers are rejected when <code>allowedHosts</code> is absent or does not match, preventing an open redirect.</p>
<p>The SSO hostname is the login destination, not an <code>allowedHosts</code> entry. For example, when protecting <code>admin.localhost:3000</code>, keep <code>admin.localhost:3000</code> in the allowlist even though the verifier runs at <code>sso.localhost:3000</code>. WRNexus preserves both hosts across a nested gateway request.</p>
<h4 id="support-dynamic-tenant-domains">Support dynamic tenant domains</h4>
<pre data-language="ts"><code>import type &#123; Context &#125; from &quot;@wrnexus/core&quot;;
import &#123; getOriginalRequestOrigin, redirectToLogin &#125; from &quot;@wrnexus/helpers&quot;;
export const GET = async (ctx: Context) =&gt; &#123;
const allowedHosts = (host: string) =&gt; host === &quot;example.test&quot; || host.endsWith(&quot;.example.test&quot;);
console.info(&quot;Authentication requested by&quot;, getOriginalRequestOrigin(ctx, &#123; allowedHosts &#125;));
return redirectToLogin(ctx, &quot;https://auth.example.test/login&quot;, &#123;
allowedHosts,
returnToParam: &quot;continue&quot;,
status: 303,
&#125;);
&#125;;</code></pre>
<h3 id="api">API</h3>
<ul>
<li><code>getOriginalRequestUrl(ctx, options): URL</code> — reconstruct the gateway URL.</li>
<li><code>getOriginalRequestOrigin(ctx, options): string</code> — return only its origin.</li>
<li><code>getOriginalRequestPath(ctx): string</code> — return its path and query string.</li>
<li><code>getOriginalRequestMethod(ctx): string</code> — return its HTTP method.</li>
<li><code>redirectToLogin(ctx, loginUrl, options): Response</code> — create a login redirect with an</li>
<p>encoded <code>returnTo</code> parameter.</p>
</ul>
<p>For direct requests without gateway headers, URL helpers use <code>ctx.url</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>import &#123; Context &#125; from '@wrnexus/core';
import &#123; Db &#125; from '@wrnexus/db';
declare function appOrigin(appName: string): string;
declare function appUrl(appName: string, path?: string): string;
declare function currentAppName(): string | undefined;
declare function currentAppOrigin(): string | undefined;
declare function workspaceAppOrigins(): Readonly&lt;Record&lt;string, string&gt;&gt;;
/** Shared DNS suffix for configured workspace apps (for example `staging.example.com`). */
declare function workspaceRootDomain(): string;
interface RetryOptions &#123;
attempts?: number;
minDelayMs?: number;
maxDelayMs?: number;
factor?: number;
jitter?: number;
signal?: AbortSignal;
retryIf?: (error: unknown, attempt: number) =&gt; boolean | Promise&lt;boolean&gt;;
onRetry?: (error: unknown, attempt: number, delayMs: number) =&gt; void | Promise&lt;void&gt;;
&#125;
declare function backoffDelay(attempt: number, options?: Pick&lt;RetryOptions, &quot;minDelayMs&quot; | &quot;maxDelayMs&quot; | &quot;factor&quot; | &quot;jitter&quot;&gt;): number;
declare function sleep(ms: number, signal?: AbortSignal): Promise&lt;void&gt;;
declare function retry&lt;T&gt;(operation: (attempt: number, signal?: AbortSignal) =&gt; Promise&lt;T&gt;, options?: RetryOptions): Promise&lt;T&gt;;
declare function withTimeout&lt;T&gt;(promise: Promise&lt;T&gt;, timeoutMs: number, message?: string, signal?: AbortSignal): Promise&lt;T&gt;;
declare function stableStringify(value: unknown): string;
declare function safeJsonParse&lt;T&gt;(value: string, fallback: T): T;
declare function clamp(value: number, min: number, max: number): number;
declare function once&lt;T extends (...args: any[]) =&gt; any&gt;(fn: T): T;
declare class HttpError extends Error &#123;
readonly status: number;
readonly details?: unknown | undefined;
constructor(status: number, message: string, details?: unknown | undefined);
&#125;
declare function subjectId(ctx: Pick&lt;Context, &quot;user&quot;&gt;): string;
declare function requireUser(ctx: Pick&lt;Context, &quot;user&quot;&gt;): &#123;
id: string;
user: unknown;
&#125;;
declare function requireParam(ctx: Pick&lt;Context, &quot;params&quot;&gt;, name: string, options?: &#123;
integer?: boolean;
positive?: boolean;
&#125;): string | number;
declare function requireJson&lt;T = Record&lt;string, unknown&gt;&gt;(ctx: Pick&lt;Context, &quot;req&quot;&gt;): Promise&lt;T&gt;;
declare function requirePermission(ctx: Context, permission: string, resource?: unknown): Promise&lt;void&gt;;
declare const json: &#123;
ok: &lt;T&gt;(value: T) =&gt; Response;
created: &lt;T&gt;(value: T) =&gt; Response;
noContent: () =&gt; Response;
error: (status: number, message: string, details?: unknown) =&gt; Response;
&#125;;
type ApiHandler = (ctx: Context) =&gt; Response | Promise&lt;Response&gt;;
/** Convert thrown HttpError values into the framework's standard JSON error shape. */
declare function defineApiRoute(handler: ApiHandler): ApiHandler;
declare function authorized(permission: string, handler: ApiHandler, resource?: (ctx: Context) =&gt; unknown | Promise&lt;unknown&gt;): ApiHandler;
interface ResourceDefinition&lt;T extends Record&lt;string, unknown&gt;&gt; &#123;
name: string;
db: () =&gt; Db;
table: string;
id?: string;
owner?: string;
fields: readonly (keyof T &amp; string)[];
permissions?: &#123;
read?: string;
readAny?: string;
write?: string;
writeAny?: string;
&#125;;
validate?: (input: unknown, mode: &quot;create&quot; | &quot;update&quot;) =&gt; T | Promise&lt;T&gt;;
serialize?: (row: T) =&gt; unknown;
&#125;
interface ResourceHandlers &#123;
list(ctx: Context): Response | Promise&lt;Response&gt;;
create(ctx: Context): Response | Promise&lt;Response&gt;;
get(ctx: Context): Response | Promise&lt;Response&gt;;
update(ctx: Context): Response | Promise&lt;Response&gt;;
remove(ctx: Context): Response | Promise&lt;Response&gt;;
&#125;
/** Build conventional owned CRUD handlers from one checked resource declaration. */
declare function defineResource&lt;T extends Record&lt;string, unknown&gt;&gt;(definition: ResourceDefinition&lt;T&gt;): ResourceHandlers;
/**
* @wrnexus/helpers — safe conveniences for common WRNexusJS application flows.
*
* Helpers stay small and composable. They accept the standard WRNexusJS Context
* and return web-platform values such as URL and Response.
*/
type RequestContext = Pick&lt;Context, &quot;req&quot; | &quot;url&quot;&gt;;
type AllowedHosts = readonly string[] | ReadonlySet&lt;string&gt; | ((host: string, ctx: RequestContext) =&gt; boolean);
interface OriginalRequestOptions &#123;
/**
* Hosts that the application permits as redirect destinations. This is
* required when a proxy supplied X-Forwarded-Host is present.
*/
allowedHosts?: AllowedHosts;
&#125;
interface LoginRedirectOptions extends OriginalRequestOptions &#123;
/** Query parameter that receives the original absolute URL. */
returnToParam?: string;
/** Browser redirect status. Defaults to 302. */
status?: 301 | 302 | 303 | 307 | 308;
&#125;
/** Get the original path and query string seen by the gateway. */
declare function getOriginalRequestPath(ctx: RequestContext): string;
/** Get the original HTTP method seen by the gateway. */
declare function getOriginalRequestMethod(ctx: RequestContext): string;
/**
* Reconstruct the absolute URL that reached the gateway.
*
* Forwarded hosts are never trusted implicitly: pass allowedHosts when this is
* used behind the WRNexusJS gateway. Direct requests fall back to ctx.url.
*/
declare function getOriginalRequestUrl(ctx: RequestContext, options?: OriginalRequestOptions): URL;
/** Get the original request origin, for example http://admin.localhost:3000. */
declare function getOriginalRequestOrigin(ctx: RequestContext, options?: OriginalRequestOptions): string;
/**
* Redirect to a login page with the original absolute URL encoded as returnTo.
* Relative login URLs resolve against the current app (normally the SSO app).
*/
declare function redirectToLogin(ctx: RequestContext, loginUrl: string | URL, options?: LoginRedirectOptions): Response;
export &#123; type AllowedHosts, type ApiHandler, HttpError, type LoginRedirectOptions, type OriginalRequestOptions, type RequestContext, type ResourceDefinition, type ResourceHandlers, type RetryOptions, appOrigin, appUrl, authorized, backoffDelay, clamp, currentAppName, currentAppOrigin, defineApiRoute, defineResource, getOriginalRequestMethod, getOriginalRequestOrigin, getOriginalRequestPath, getOriginalRequestUrl, json, once, redirectToLogin, requireJson, requireParam, requirePermission, requireUser, retry, safeJsonParse, sleep, stableStringify, subjectId, withTimeout, workspaceAppOrigins, workspaceRootDomain &#125;;
</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>Redirect an unauthenticated forward-auth request</h3><pre data-language="ts"><code>import type &#123; Context &#125; from &quot;@wrnexus/core&quot;;
import &#123; redirectToLogin &#125; from &quot;@wrnexus/helpers&quot;;
export const GET = async (ctx: Context) =&gt; &#123;
if (await hasValidSession(ctx)) &#123;
return new Response(null, &#123; status: 204 &#125;);
&#125;
return redirectToLogin(ctx, &quot;/login&quot;, &#123;
allowedHosts: [&quot;admin.localhost:3000&quot;, &quot;reports.localhost:3000&quot;],
&#125;);
&#125;;</code></pre></article><article class="example-card"><h3>Support dynamic tenant domains</h3><pre data-language="ts"><code>import type &#123; Context &#125; from &quot;@wrnexus/core&quot;;
import &#123; getOriginalRequestOrigin, redirectToLogin &#125; from &quot;@wrnexus/helpers&quot;;
export const GET = async (ctx: Context) =&gt; &#123;
const allowedHosts = (host: string) =&gt; host === &quot;example.test&quot; || host.endsWith(&quot;.example.test&quot;);
console.info(&quot;Authentication requested by&quot;, getOriginalRequestOrigin(ctx, &#123; allowedHosts &#125;));
return redirectToLogin(ctx, &quot;https://auth.example.test/login&quot;, &#123;
allowedHosts,
returnToParam: &quot;continue&quot;,
status: 303,
&#125;);
&#125;;</code></pre></article></div></section></article></div>
<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="#usage">Usage</a><a class="toc-level-4" href="#redirect-an-unauthenticated-forward-auth-request">Redirect an unauthenticated forward-auth request</a><a class="toc-level-4" href="#support-dynamic-tenant-domains">Support dynamic tenant domains</a><a class="toc-level-3" href="#api">API</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.8.72</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>
</div>
}
}