release: WRNexusJS 0.8.0
Quality / quality (ubuntu-latest) (push) Failing after 21s
Quality / quality (windows-latest) (push) Canceled after 0s

This commit is contained in:
2026-08-02 23:18:51 +05:30
parent 87507edf59
commit 586a6db8ff
625 changed files with 243608 additions and 11210 deletions
+9
View File
@@ -0,0 +1,9 @@
node_modules
dist
**/.wrnexus
.git
*.log
*.db
*.db-shm
*.db-wal
.DS_Store
@@ -0,0 +1,7 @@
# Copy to .env.production and replace every required value.
NODE_ENV=production
PORT=3000
HOST=0.0.0.0
DATABASE_URL=postgres://USER:PASSWORD@HOST:5432/DB
SESSION_SECRET=REPLACE_WITH_AT_LEAST_32_RANDOM_BYTES
# OTEL_EXPORTER_OTLP_ENDPOINT=https://collector.example.com
+3
View File
@@ -1,5 +1,8 @@
node_modules/
dist/
.wrnexus/
.wirefw/
**/.wirefw/
**/*.gen.ts
**/*.generated.d.ts
*.log
+20
View File
@@ -0,0 +1,20 @@
# syntax=docker/dockerfile:1
# --- build stage: install deps + produce dist/server.js ---
FROM oven/bun:1 AS build
WORKDIR /app
COPY package.json bun.lock* bun.lockb* ./
RUN bun install
COPY . .
RUN bun run build
# --- runtime stage: slim image with only the built server + migrations ---
FROM oven/bun:1-slim AS runtime
WORKDIR /app
ENV NODE_ENV=production
ENV PORT=3000
COPY --from=build /app/dist ./dist
COPY --from=build /app/app/db/migrations ./app/db/migrations
EXPOSE 3000
HEALTHCHECK --interval=30s --timeout=3s --retries=3 \
CMD bun -e "fetch('http://localhost:'+(process.env.PORT||3000)+'/healthz').then(r=>process.exit(r.ok?0:1)).catch(()=>process.exit(1))"
CMD ["bun", "dist/server.js"]
+6 -1
View File
@@ -8,6 +8,11 @@ export const GET = async () => {
};
export const POST = async (ctx: Context) => {
const body = await ctx.req.json();
const body = await ctx.req.json().catch(() => undefined);
if (body === undefined)
return Response.json(
{ error: "Request body must be valid JSON." },
{ status: 400, headers: { "cache-control": "no-store" } },
);
return Response.json({ received: body });
};
@@ -0,0 +1,14 @@
import { createGraphqlHandler } from "@wrnexus/graphql";
const graphql = createGraphqlHandler({
maxDepth: 8,
maxAliases: 20,
allowIntrospection: false,
async execute(request) {
return { data: { example: request.operationName ?? "anonymous" } };
},
});
export function POST(ctx: { req: Request }): Promise<Response> {
return graphql(ctx.req);
}
+12
View File
@@ -0,0 +1,12 @@
import { defineEndpoint } from "@wrnexus/core";
import { CreateUserSchema } from "../schemas/create-user.ts";
/** Schema-derived request and response types are emitted into wrnexus.generated.d.ts. */
export const POST = defineEndpoint({
input: CreateUserSchema,
output: CreateUserSchema,
description: "Validate and echo a typed user payload.",
handler(input) {
return input;
},
});
@@ -0,0 +1,11 @@
export const webhook = {
event: "payment.completed",
summary: "Payment completed",
description: "Sent after a payment reaches its settled state.",
payloadSchema: "#/components/schemas/PaymentCompleted",
signatureHeader: "x-payment-signature",
};
export async function POST(): Promise<Response> {
return Response.json({ accepted: true });
}
+38
View File
@@ -73,4 +73,42 @@ describe("full app", () => {
expect(typeof body.message).toBe("string");
expect(body.lang).toBeTruthy();
});
test("language-server playground renders its real interactive output", async () => {
const res = await app.fetch("/language-tools");
expect(res.status).toBe(200);
const html = await res.text();
expect(html).toContain("Language server playground");
expect(html).toContain("computed double:");
expect(html).toContain('data-text="doubled">0</span>');
expect(html).toContain('aria-live="polite"');
});
test("login renders real accessible fields without an accidental Card component", async () => {
const res = await app.fetch("/login?next=/dashboard");
expect(res.status).toBe(200);
const html = await res.text();
expect(html).toContain('name="email"');
expect(html).toContain('name="password"');
expect(html).toContain('autocomplete="current-password"');
expect(html).not.toContain("Card title");
});
test("platform showcase uses the public layout and styled reactive primitives", async () => {
const res = await app.fetch("/platform-showcase");
expect(res.status).toBe(200);
const html = await res.text();
expect(html).toContain("WrNexus");
expect(html).toContain('data-wrn-dynamic-component="Admin"');
expect(html).toContain('data-wrn-portal="#showcase-modal"');
expect(html).toContain("platform-showcase");
});
test("async data page renders its server loader without a cache failure", async () => {
const res = await app.fetch("/async-data");
expect(res.status).toBe(200);
const html = await res.text();
expect(html).toContain("Rendered by the server loader");
expect(html).toContain("Loading the browser profile");
});
});
+20 -2
View File
@@ -3,9 +3,27 @@
component AuthLayout {
view {
<div class="auth-wrap">
<div data-component="card" class="auth-card">
<section class="auth-card" aria-label="Authentication">
<slot></slot>
</div>
</section>
</div>
}
style {
.auth-wrap {
display: grid;
min-height: 100vh;
place-items: center;
padding: 1.5rem;
}
.auth-card {
width: min(100%, 28rem);
padding: 2rem;
border: 1px solid var(--wire-color-border);
border-radius: var(--wire-radius-lg);
background: var(--wire-color-surface);
box-shadow: 0 1.25rem 3rem rgb(0 0 0 / 0.18);
}
}
}
+1 -1
View File
@@ -9,7 +9,7 @@ component DashboardLayout {
<a href="/ui">Components</a>
<a href="/">Home</a>
</nav>
<div data-component="theme-toggle" label="Toggle theme"></div>
<button type="button" data-wire-theme-toggle aria-label="Toggle color theme">Theme</button>
</aside>
<main class="dash-main">
+1 -1
View File
@@ -11,7 +11,7 @@ component PublicLayout {
<a href="/chat">{t:nav.chat}</a>
<a href="/dashboard">{t:nav.dashboard}</a>
<a href="/about">{t:nav.about}</a>
<div data-component="theme-toggle" label="Theme"></div>
<button type="button" data-wire-theme-toggle aria-label="Toggle color theme">Theme</button>
<button class="wire-btn wire-btn--ghost wire-btn--sm" data-wire-lang-set="en">EN</button>
<button class="wire-btn wire-btn--ghost wire-btn--sm" data-wire-lang-set="es">ES</button>
</nav>
@@ -0,0 +1,29 @@
page AsyncData {
layout = "public"
load server summary {
return { message: "Rendered by the server loader" }
}
load client profile {
return { name: "Ada", role: "Administrator" }
}
view {
<section class="stack">
<h1>Async data boundaries</h1>
<Async source="summary">
<Loading><p>Preparing the server summary…</p></Loading>
<Success data="summary"><p>{summary.message}</p></Success>
<Error error="error"><p>{error.message}</p></Error>
</Async>
<Async source="profile" retries="2">
<Loading><p>Loading the browser profile…</p></Loading>
<Success data="profile"><p>Welcome {profile.name} — {profile.role}</p></Success>
<Error error="error"><p>Profile failed: {error.message}</p></Error>
</Async>
</section>
}
}
@@ -0,0 +1,15 @@
page ClientOnly {
layout = "public"
render = "client"
hydrate = "load"
state count: number = 0
view {
<section class="wire-card stack">
<p class="wire-badge">Mounted in the browser</p>
<h1>Client-only page</h1>
<button type="button" class="wire-btn" @click="count++">Count: {count}</button>
</section>
}
}
+2 -1
View File
@@ -27,10 +27,11 @@ page Home {
restyles instantly when the theme changes. -->
<div class="themed-box">
This box uses theme tokens. Current theme controls its colors.
<button data-wire-theme-toggle class="theme-toggle">Toggle theme</button>
<button type="button" data-wire-theme-toggle class="theme-toggle">Toggle theme</button>
</div>
<p><a class="text-red-600 hover:underline" href="/about">About</a></p>
<p><a class="hover:underline" href="/language-tools">Language server playground</a></p>
}
style {
@@ -0,0 +1,104 @@
// Real page for checking compiler, formatter, LSP navigation, rename, and reactivity.
page LanguageTools {
layout = "public"
state count: number = 0
state message: string = "The shared language server is active."
computed doubled: number = count * 2
seo {
title = "WRN language tools"
description = "Interactive WRN page used to verify editor and compiler features."
canonical = "/language-tools"
}
functions {
function increment() {
count++
message = `Renaming count updates all ${count} references safely.`
}
function reset() {
count = 0
message = "State reset. Try hover, definition, references, and rename in your editor."
}
}
view {
<main class="language-tools">
<header>
<span class="eyebrow">Editor-neutral WRN tooling</span>
<h1>Language server playground</h1>
<p>{message}</p>
</header>
<section aria-labelledby="counter-heading" class="demo-card">
<h2 id="counter-heading">Reactive Data symbol test</h2>
<p aria-live="polite">Count: {count}; computed double: {doubled}</p>
<div class="actions">
<button type="button" @click="increment()">Increment</button>
<button type="button" class="secondary" @click="reset()">Reset</button>
</div>
</section>
<section aria-labelledby="features-heading" class="demo-card">
<h2 id="features-heading">Features exercised by this file</h2>
<ul>
<li>Compiler and accessibility diagnostics</li>
<li>Canonical formatting and completion</li>
<li>Hover, symbols, definition, references, and rename</li>
<li>Typed state, computed values, and browser events</li>
</ul>
</section>
</main>
}
style {
.language-tools {
display: grid;
max-width: 58rem;
gap: 1.25rem;
margin: 3rem auto;
}
.language-tools header,
.demo-card {
display: grid;
gap: 0.75rem;
}
.eyebrow {
color: var(--wire-color-primary);
font-weight: 700;
letter-spacing: 0.08em;
text-transform: uppercase;
}
.demo-card {
padding: 1.25rem;
border: 1px solid var(--wire-color-border);
border-radius: var(--wire-radius-lg);
background: var(--wire-color-surface);
}
.actions {
display: flex;
gap: 0.75rem;
}
.actions button {
padding: 0.65rem 1rem;
border: 1px solid var(--wire-color-primary);
border-radius: var(--wire-radius-md);
background: var(--wire-color-primary);
color: var(--wire-color-primary-contrast);
cursor: pointer;
}
.actions .secondary {
background: transparent;
color: var(--wire-color-primary);
}
}
}
+32 -7
View File
@@ -11,24 +11,49 @@ page Login {
view {
<form data-schema="login" method="post" action="/api/login" data-redirect="/dashboard">
<div data-component="stack" gap="4">
<div class="auth-stack">
<h1>Sign in</h1>
<div class="wire-alert wire-alert--success" data-success="Signed in! The form posted JSON and was validated server-side." hidden></div>
<div>
<div data-component="input" type="email" name="email" placeholder="you@example.com"></div>
<label class="auth-field">
<span>Email address</span>
<input type="email" name="email" autocomplete="email" placeholder="you@example.com" required />
<span class="wire-field-error" data-error="email"></span>
</div>
</label>
<div>
<div data-component="input" type="password" name="password" placeholder="Password"></div>
<label class="auth-field">
<span>Password</span>
<input type="password" name="password" autocomplete="current-password" placeholder="Password" required />
<span class="wire-field-error" data-error="password"></span>
</div>
</label>
<button type="submit" class="wire-btn wire-btn--primary wire-btn--lg">Sign in</button>
<p><a href="/">Back home</a></p>
</div>
</form>
}
style {
form,
.auth-stack,
.auth-field {
display: grid;
gap: 0.5rem;
}
form,
.auth-stack {
gap: 1.25rem;
}
.auth-field input {
min-height: 2.75rem;
padding: 0.7rem 0.8rem;
border: 1px solid var(--wire-color-border);
border-radius: var(--wire-radius-md);
background: var(--wire-color-surface);
color: inherit;
}
}
}
@@ -0,0 +1,27 @@
page PartialStatic {
layout = "public"
render = "partial-static"
seo {
title = "Partial-static streaming"
description = "A build-time WRNexus shell with request-streamed dynamic content."
}
view {
<Static>
<section class="wire-card stack">
<p class="wire-badge">Built once</p>
<h1>Build-time static shell</h1>
<p>This heading and page structure are emitted into dist/partial-shells.json.</p>
</section>
</Static>
<Dynamic>
<section class="wire-card stack" aria-live="polite">
<p class="wire-badge">Streamed per request</p>
<h2>Dynamic account region</h2>
<p>Personalized data belongs inside this boundary.</p>
</section>
</Dynamic>
}
}
@@ -0,0 +1,77 @@
page PlatformShowcase {
layout = "public"
render = "hybrid"
hydrate = "load"
state active = "Admin"
view {
<main class="platform-showcase">
<header>
<span class="eyebrow">Reactive primitives</span>
<h1>Reactive platform showcase</h1>
<p>Switch a dynamic component and verify transition and portal rendering.</p>
</header>
<section class="showcase-card">
<button type="button" @click='active = active === "Admin" ? "Guest" : "Admin"'>Switch component</button>
<Transition name="fade">
<Component is={active}>
<section class="component-case" data-component-case="Admin"><strong>Administrator tools</strong><p>Manage users, permissions, and platform settings.</p></section>
<section class="component-case" data-component-case="Guest"><strong>Guest dashboard</strong><p>Explore the public workspace and available resources.</p></section>
</Component>
</Transition>
</section>
<div id="showcase-modal" class="portal-target" aria-live="polite"></div>
<Portal to="#showcase-modal"><p>Portaled content rendered in its target.</p></Portal>
</main>
}
style {
.platform-showcase {
display: grid;
max-width: 58rem;
gap: 1.25rem;
margin: 3rem auto;
}
.platform-showcase header,
.showcase-card,
.component-case {
display: grid;
gap: 0.75rem;
}
.eyebrow {
color: var(--wire-color-primary);
font-weight: 700;
text-transform: uppercase;
}
.showcase-card,
.portal-target {
padding: 1.25rem;
border: 1px solid var(--wire-color-border);
border-radius: var(--wire-radius-lg);
background: var(--wire-color-surface);
}
.showcase-card button {
width: fit-content;
padding: 0.7rem 1rem;
border: 0;
border-radius: var(--wire-radius-md);
background: var(--wire-color-primary);
color: var(--wire-color-primary-contrast);
cursor: pointer;
}
.fade-enter-active {
animation: platform-fade 180ms ease-out;
}
@keyframes platform-fade {
from { opacity: 0; transform: translateY(0.25rem); }
to { opacity: 1; transform: translateY(0); }
}
}
}
@@ -0,0 +1,27 @@
import { CreateUserSchema } from "../schemas/create-user";
page ServerActions {
seo {
title = "Typed server actions"
description = "Schema validation, CSRF, invalidation and progressive enhancement"
}
action createUser using CreateUserSchema {
invalidate("users")
return { id: crypto.randomUUID(), name: input.name, email: input.email }
}
view {
<main>
<h1>Typed server actions</h1>
<p>The form works with or without JavaScript.</p>
<form @submit="createUser">
<label for="action-name">Name</label>
<input id="action-name" name="name" autocomplete="name" required />
<label for="action-email">Email</label>
<input id="action-email" name="email" type="email" autocomplete="email" required />
<button type="submit">Create user</button>
</form>
</main>
}
}
@@ -0,0 +1,13 @@
import { defineJob } from "@wrnexus/queue";
export interface WelcomeEmailPayload {
userId: number;
email: string;
}
export default defineJob<WelcomeEmailPayload>({
name: "welcome-email",
async run(job) {
console.log(`Welcome email queued for ${job.data.email}`);
},
});
+84 -10
View File
@@ -4,16 +4,50 @@
export interface Routes {
"/": Record<string, never>;
"/about": Record<string, never>;
"/async-data": Record<string, never>;
"/chat": Record<string, never>;
"/client-only": Record<string, never>;
"/dashboard": Record<string, never>;
"/hello": Record<string, never>;
"/language-tools": Record<string, never>;
"/login": Record<string, never>;
"/modal": Record<string, never>;
"/partial-static": Record<string, never>;
"/platform-showcase": Record<string, never>;
"/reactive": Record<string, never>;
"/server-actions": Record<string, never>;
"/test": Record<string, never>;
"/ui": Record<string, never>;
}
export interface RouteNames {
"index": "/";
"about": "/about";
"async.data": "/async-data";
"chat": "/chat";
"client.only": "/client-only";
"dashboard": "/dashboard";
"hello": "/hello";
"language.tools": "/language-tools";
"login": "/login";
"modal": "/modal";
"partial.static": "/partial-static";
"platform.showcase": "/platform-showcase";
"reactive": "/reactive";
"server.actions": "/server-actions";
"test": "/test";
"ui": "/ui";
}
export interface RouteQueries {
[path: string]: Record<string, string | number | boolean | null | undefined>;
}
export type RoutePath = keyof Routes;
export type RouteName = keyof RouteNames;
export type RouteQuery<P extends RoutePath> = P extends keyof RouteQueries
? RouteQueries[P]
: Record<string, string | number | boolean | null | undefined>;
type RouteValue = string | readonly string[] | undefined;
function encodeRouteValue(value: RouteValue, catchAll: boolean): string {
@@ -22,17 +56,9 @@ function encodeRouteValue(value: RouteValue, catchAll: boolean): string {
return values.map((part) => encodeURIComponent(part)).join("/");
}
export function href<P extends RoutePath>(
path: P,
...args: keyof Routes[P] extends never
? []
: Record<string, never> extends Routes[P]
? [params?: Routes[P]]
: [params: Routes[P]]
): string {
const params = (args[0] ?? {}) as Record<string, RouteValue>;
function buildHref(path: string, params: Record<string, RouteValue> = {}): string {
const output: string[] = [];
for (const segment of String(path).split("/").filter(Boolean)) {
for (const segment of path.split("/").filter(Boolean)) {
let name: string | undefined;
let optional = false;
let catchAll = false;
@@ -61,3 +87,51 @@ export function href<P extends RoutePath>(
}
return "/" + output.filter(Boolean).join("/");
}
export function href<P extends RoutePath>(
path: P,
...args: keyof Routes[P] extends never
? []
: Record<string, never> extends Routes[P]
? [params?: Routes[P]]
: [params: Routes[P]]
): string {
const params = (args[0] ?? {}) as Record<string, RouteValue>;
return buildHref(String(path), params);
}
export function route<N extends RouteName>(
name: N,
...args: keyof Routes[RouteNames[N]] extends never
? [params?: Routes[RouteNames[N]], query?: RouteQuery<RouteNames[N]>]
: Record<string, never> extends Routes[RouteNames[N]]
? [params?: Routes[RouteNames[N]], query?: RouteQuery<RouteNames[N]>]
: [params: Routes[RouteNames[N]], query?: RouteQuery<RouteNames[N]>]
): string {
const paths: Record<RouteName, RoutePath> = {
"index": "/",
"about": "/about",
"async.data": "/async-data",
"chat": "/chat",
"client.only": "/client-only",
"dashboard": "/dashboard",
"hello": "/hello",
"language.tools": "/language-tools",
"login": "/login",
"modal": "/modal",
"partial.static": "/partial-static",
"platform.showcase": "/platform-showcase",
"reactive": "/reactive",
"server.actions": "/server-actions",
"test": "/test",
"ui": "/ui"
} as Record<RouteName, RoutePath>;
const output = buildHref(paths[name], (args[0] ?? {}) as Record<string, RouteValue>);
const query = args[1];
if (!query) return output;
const search = new URLSearchParams();
for (const [key, value] of Object.entries(query))
if (value !== undefined && value !== null) search.set(key, String(value));
const text = search.toString();
return text ? `${output}?${text}` : output;
}
@@ -0,0 +1,6 @@
import { v } from "@wrnexus/validation";
export const CreateUserSchema = v.object({
name: v.string().trim().min(2),
email: v.string().trim().email(),
});
@@ -0,0 +1,86 @@
import {
afterAll,
beforeAll,
createHarness,
describe,
expect,
test,
type Harness,
} from "@wrnexus/test";
describe("real application security abuse checks", () => {
let app: Harness;
beforeAll(async () => {
app = await createHarness(import.meta.dir + "/..");
});
afterAll(() => app?.close());
test("sets browser hardening headers on rendered pages", async () => {
const response = await app.fetch("/");
expect(response.headers.get("content-security-policy")).toContain("default-src");
expect(response.headers.get("x-content-type-options")).toBe("nosniff");
expect(response.headers.get("x-frame-options")).toBeTruthy();
expect(response.headers.get("referrer-policy")).toBeTruthy();
});
test("does not reflect script payloads into HTML", async () => {
const payload = `<script>globalThis.__attacked=true</script>`;
const response = await app.fetch(`/?search=${encodeURIComponent(payload)}`);
expect(response.status).toBe(200);
expect(await response.text()).not.toContain(payload);
});
test("rejects traversal attempts without exposing source files", async () => {
for (const path of ["/../../package.json", "/%2e%2e/%2e%2e/package.json", "/..%5c..%5c.env"]) {
const response = await app.fetch(path);
expect([400, 404]).toContain(response.status);
const body = await response.text();
expect(body).not.toContain("DATABASE_URL");
expect(body).not.toContain('"workspaces"');
}
});
test("does not grant CORS credentials to an untrusted origin", async () => {
const response = await app.fetch("/api/hello", {
headers: { origin: "https://evil.example" },
});
expect(response.headers.get("access-control-allow-origin")).toBeNull();
expect(response.headers.get("access-control-allow-credentials")).toBeNull();
});
test("requires CSRF for login and avoids credential oracle details", async () => {
const response = await app.fetch("/api/login", {
method: "POST",
headers: { "content-type": "application/json" },
body: JSON.stringify({ email: "victim@example.com", password: "wrong-password" }),
});
expect(response.status).toBe(403);
expect(await response.text()).not.toContain("passwordHash");
});
test("handles malformed JSON without a stack trace", async () => {
const response = await app.fetch("/api/echo", {
method: "POST",
headers: { "content-type": "application/json" },
body: "{broken",
});
expect(response.status).toBe(400);
const body = await response.text();
expect(body).not.toContain(" at ");
expect(body).not.toContain("node_modules");
});
test("refuses actual request bodies over the configured limit", async () => {
try {
const response = await app.fetch("/api/echo", {
method: "POST",
headers: { "content-type": "application/json" },
body: `"${"x".repeat(10 * 1024 * 1024)}"`,
});
expect(response.status).toBe(413);
} catch (error) {
// Bun rejects the oversized socket before application dispatch on some versions.
expect(String(error)).toMatch(/ECONNRESET|socket connection was closed/i);
}
});
});
@@ -0,0 +1,30 @@
import type { Context } from "@wrnexus/core";
const apiInput: WRNexusGenerated.ApiContracts["/api/typed-user"]["POST"]["input"] = {
name: "Ada",
email: "ada@example.test",
};
const queuePayload: WRNexusGenerated.QueuePayloads["welcome-email"] = {
userId: 42,
email: "ada@example.test",
};
const queryArgs: WRNexusGenerated.DatabaseQueries["GetUserByEmail"]["args"] = {
email: "ada@example.test",
};
const realtimeMessage: WRNexusGenerated.RealtimeMessages["/realtime/chat"] = {
user: "ada",
text: "hello",
};
const middlewareContext: WRNexusGenerated.MiddlewareContexts["auth"] = {} as Context;
void [apiInput, queuePayload, queryArgs, realtimeMessage, middlewareContext];
// @ts-expect-error schema-derived input requires an email.
const invalidApiInput: WRNexusGenerated.ApiContracts["/api/typed-user"]["POST"]["input"] = {
name: "Missing email",
};
void invalidApiInput;
// @ts-expect-error generated SQL query arguments require an email string.
const invalidQuery: WRNexusGenerated.DatabaseQueries["GetUserByEmail"]["args"] = {};
void invalidQuery;
+59
View File
@@ -0,0 +1,59 @@
// AUTO-GENERATED by `wrnexus generate types` - do not edit.
declare namespace WRNexusGenerated {
type ApiContract<T> = T extends import("@wrnexus/core").DefinedEndpoint<infer I, infer O>
? { input: I; output: O }
: T extends (...args: infer A) => infer R
? { input: A extends [any, infer I, ...any[]] ? I : unknown; output: Awaited<R> }
: { input: unknown; output: unknown };
type MiddlewareContext<T> = T extends (ctx: infer C, ...args: any[]) => any ? C : never;
type QueryContract<T> = T extends (db: any, args: infer A, ...rest: any[]) => infer R
? { args: A; result: Awaited<R> }
: T extends (db: any, ...rest: any[]) => infer R
? { args: Record<string, never>; result: Awaited<R> }
: never;
type RealtimeMessage<T> = T extends import("@wrnexus/core").RoomDefinition<any, infer M> ? M : unknown;
type QueuePayload<T> = T extends import("@wrnexus/queue").JobDefinition<infer I> ? I : unknown;
type RouteName = "about" | "async.data" | "chat" | "client.only" | "dashboard" | "hello" | "index" | "language.tools" | "login" | "modal" | "partial.static" | "platform.showcase" | "reactive" | "server.actions" | "test" | "ui";
type ApiRoute = "/api/echo" | "/api/graphql-example" | "/api/hello" | "/api/login" | "/api/logout" | "/api/me" | "/api/typed-user" | "/api/users/csr" | "/api/users/ssr" | "/api/webhooks/payment";
type RealtimeRoute = "/realtime/chat" | "/realtime/hello";
type EnvironmentKey = "APP_LABEL" | "DATABASE_URL" | "DEMO_SHARED" | "HOST" | "NODE_ENV" | "PORT" | "SESSION_SECRET" | "UAT_ONLY";
type TranslationKey = "api.greeting" | "home.intro" | "home.title" | "nav.about" | "nav.chat" | "nav.dashboard" | "nav.home" | "nav.ui";
type QueueName = "welcome-email";
type CacheKey = "users";
interface Components {
"Modal": { props: Record<string, never>; outputs: Record<string, never> };
"Counter": { props: { "start"?: unknown; "label"?: unknown }; outputs: Record<string, never> };
}
interface ApiContracts {
"/api/webhooks/payment": { POST: ApiContract<typeof import("../api/webhooks/payment.ts")["POST"]> };
"/api/users/csr": { GET: ApiContract<typeof import("../api/users/csr.ts")["GET"]> };
"/api/users/ssr": { GET: ApiContract<typeof import("../api/users/ssr.ts")["GET"]> };
"/api/graphql-example": { POST: ApiContract<typeof import("../api/graphql-example.ts")["POST"]> };
"/api/typed-user": { POST: ApiContract<typeof import("../api/typed-user.ts")["POST"]> };
"/api/logout": { POST: ApiContract<typeof import("../api/logout.ts")["POST"]> };
"/api/hello": { GET: ApiContract<typeof import("../api/hello.ts")["GET"]> };
"/api/login": { POST: ApiContract<typeof import("../api/login.ts")["POST"]> };
"/api/echo": { GET: ApiContract<typeof import("../api/echo.ts")["GET"]>; POST: ApiContract<typeof import("../api/echo.ts")["POST"]> };
"/api/me": { GET: ApiContract<typeof import("../api/me.ts")["GET"]> };
}
interface MiddlewareContexts {
"auth": MiddlewareContext<(typeof import("../middleware/auth.ts"))["default"]>;
"logger": MiddlewareContext<(typeof import("../middleware/logger.ts"))["default"]>;
"ratelimit": MiddlewareContext<(typeof import("../middleware/ratelimit.ts"))["default"]>;
}
interface DatabaseQueries {
"GetUserByEmail": QueryContract<typeof import("../db/queries.gen.ts")["GetUserByEmail"]>;
"ListUsers": QueryContract<typeof import("../db/queries.gen.ts")["ListUsers"]>;
"CountActive": QueryContract<typeof import("../db/queries.gen.ts")["CountActive"]>;
"CreateUser": QueryContract<typeof import("../db/queries.gen.ts")["CreateUser"]>;
"DeactivateUser": QueryContract<typeof import("../db/queries.gen.ts")["DeactivateUser"]>;
}
interface RealtimeMessages {
"/realtime/hello": RealtimeMessage<(typeof import("../pages/hello.wrn"))["default"]>;
"/realtime/chat": RealtimeMessage<(typeof import("../realtime/chat.ts"))["default"]>;
}
interface QueuePayloads {
"welcome-email": QueuePayload<(typeof import("../queues/welcome-email.ts"))["default"]>;
}
type ApplicationConfig = (typeof import("../../wrnexus.config.ts"))["default"];
}
@@ -0,0 +1 @@
// AUTO-GENERATED plugin type aggregation - do not edit.
+10
View File
@@ -0,0 +1,10 @@
# WRNexus deployment operations
- Liveness: `GET /healthz`
- Readiness: `GET /readyz` (includes registered dependency checks)
- Migrations: run `bunx wrnexus db migrate --profile=production` once per release before scaling.
- Shutdown: the Bun production server drains on SIGTERM/SIGINT.
- Assets: `dist/public` files are content-addressed and may be cached immutably by a CDN.
- Secrets: provide `DATABASE_URL` and `SESSION_SECRET` through the platform secret store; never commit production env files.
- Logs: stdout/stderr are structured for platform collection. Configure OTLP for centralized telemetry.
- Scaling: start with 250m CPU/256Mi memory, use readiness probes, and scale horizontally from request latency and CPU.
+44
View File
@@ -0,0 +1,44 @@
apiVersion: v1
kind: Service
metadata:
name: wrnexus
spec:
selector: { app: wrnexus }
ports: [{ name: http, port: 80, targetPort: 3000 }]
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: wrnexus
spec:
replicas: 2
selector: { matchLabels: { app: wrnexus } }
template:
metadata: { labels: { app: wrnexus } }
spec:
containers:
- name: app
image: ghcr.io/OWNER/APP:latest
ports: [{ containerPort: 3000 }]
envFrom: [{ secretRef: { name: wrnexus-secrets } }]
livenessProbe: { httpGet: { path: /healthz, port: 3000 }, initialDelaySeconds: 5 }
readinessProbe: { httpGet: { path: /readyz, port: 3000 }, initialDelaySeconds: 5 }
resources:
requests: { cpu: 250m, memory: 256Mi }
limits: { cpu: "1", memory: 512Mi }
lifecycle: { preStop: { exec: { command: ["sh", "-c", "sleep 5"] } } }
terminationGracePeriodSeconds: 30
---
apiVersion: batch/v1
kind: Job
metadata:
name: wrnexus-migrate
spec:
template:
spec:
restartPolicy: Never
containers:
- name: migrate
image: ghcr.io/OWNER/APP:latest
command: ["bunx", "wrnexus", "db", "migrate", "--profile=production"]
envFrom: [{ secretRef: { name: wrnexus-secrets } }]
+6
View File
@@ -0,0 +1,6 @@
server {
listen 80;
server_name example.com;
location /assets/ { root /srv/wrnexus/dist/public; expires 1y; add_header Cache-Control "public, immutable"; }
location / { proxy_pass http://127.0.0.1:3000; proxy_http_version 1.1; proxy_set_header Host $host; proxy_set_header X-Forwarded-Proto $scheme; proxy_set_header X-Request-ID $request_id; }
}
+22
View File
@@ -0,0 +1,22 @@
[Unit]
Description=WRNexus application
After=network-online.target
[Service]
Type=simple
WorkingDirectory=/srv/wrnexus
EnvironmentFile=/etc/wrnexus/wrnexus.env
ExecStartPre=/usr/bin/bunx wrnexus db migrate --profile=production
ExecStart=/usr/bin/bun dist/server.js
Restart=on-failure
RestartSec=5
TimeoutStopSec=30
User=wrnexus
Group=wrnexus
NoNewPrivileges=true
PrivateTmp=true
ProtectSystem=strict
ReadWritePaths=/srv/wrnexus
[Install]
WantedBy=multi-user.target
+30
View File
@@ -0,0 +1,30 @@
services:
app:
build: .
ports:
- "3000:3000"
environment:
NODE_ENV: production
PORT: "3000"
DATABASE_URL: postgres://wire:wire@db:5432/app
depends_on:
db:
condition: service_healthy
restart: unless-stopped
db:
image: postgres:16-alpine
environment:
POSTGRES_USER: wire
POSTGRES_PASSWORD: wire
POSTGRES_DB: app
healthcheck:
test: ["CMD-SHELL", "pg_isready -U wire -d app"]
interval: 3s
timeout: 3s
retries: 20
volumes:
- pgdata:/var/lib/postgresql/data
volumes:
pgdata:
+19
View File
@@ -0,0 +1,19 @@
app = "wrnexus-app"
primary_region = "bom"
[build]
dockerfile = "Dockerfile"
[env]
PORT = "3000"
[http_service]
internal_port = 3000
force_https = true
auto_stop_machines = "stop"
auto_start_machines = true
min_machines_running = 1
[[http_service.checks]]
path = "/readyz"
interval = "15s"
timeout = "2s"
[deploy]
release_command = "bunx wrnexus db migrate --profile=production"
@@ -0,0 +1,67 @@
# API examples
## postWebhooksPayment
```bash
curl -X POST "http://localhost:3000/api/webhooks/payment"
```
## getUsersCsr
```bash
curl -X GET "http://localhost:3000/api/users/csr"
```
## getUsersSsr
```bash
curl -X GET "http://localhost:3000/api/users/ssr"
```
## postGraphqlExample
```bash
curl -X POST "http://localhost:3000/api/graphql-example"
```
## postTypedUser
```bash
curl -X POST "http://localhost:3000/api/typed-user"
```
## postLogout
```bash
curl -X POST "http://localhost:3000/api/logout"
```
## getHello
```bash
curl -X GET "http://localhost:3000/api/hello"
```
## postLogin
```bash
curl -X POST "http://localhost:3000/api/login"
```
## getEcho
```bash
curl -X GET "http://localhost:3000/api/echo"
```
## postEcho
```bash
curl -X POST "http://localhost:3000/api/echo"
```
## getMe
```bash
curl -X GET "http://localhost:3000/api/me"
```
@@ -0,0 +1,79 @@
<!doctype html><meta charset="utf-8" /><meta name="viewport" content="width=device-width" /><title>
basic-app API</title
><style>
body {
font: 16px system-ui;
max-width: 960px;
margin: auto;
padding: 2rem;
}
code,
pre {
background: #f4f4f5;
padding: 0.2rem 0.4rem;
}
article {
border-bottom: 1px solid #ddd;
padding: 1rem 0;
}
</style>
<h1>basic-app API</h1>
<p>OpenAPI 3.1 · 11 operations · <a href="openapi.json">specification</a></p>
<article>
<h2><code>POST</code> /api/webhooks/payment</h2>
<p>Payment completed</p>
<p>Sent after a payment reaches its settled state.</p>
<p>Webhook event: <code>payment.completed</code> · signature: <code>x-payment-signature</code></p>
<small>app/api/webhooks/payment.ts</small>
</article>
<article>
<h2><code>GET</code> /api/users/csr</h2>
<p>getUsersCsr</p>
<small>app/api/users/csr.ts</small>
</article>
<article>
<h2><code>GET</code> /api/users/ssr</h2>
<p>getUsersSsr</p>
<small>app/api/users/ssr.ts</small>
</article>
<article>
<h2><code>POST</code> /api/graphql-example</h2>
<p>postGraphqlExample</p>
<small>app/api/graphql-example.ts</small>
</article>
<article>
<h2><code>POST</code> /api/typed-user</h2>
<p>postTypedUser</p>
<p>Validate and echo a typed user payload.</p>
<small>app/api/typed-user.ts</small>
</article>
<article>
<h2><code>POST</code> /api/logout</h2>
<p>postLogout</p>
<small>app/api/logout.ts</small>
</article>
<article>
<h2><code>GET</code> /api/hello</h2>
<p>getHello</p>
<small>app/api/hello.ts</small>
</article>
<article>
<h2><code>POST</code> /api/login</h2>
<p>postLogin</p>
<small>app/api/login.ts</small>
</article>
<article>
<h2><code>GET</code> /api/echo</h2>
<p>getEcho</p>
<small>app/api/echo.ts</small>
</article>
<article>
<h2><code>POST</code> /api/echo</h2>
<p>postEcho</p>
<small>app/api/echo.ts</small>
</article>
<article>
<h2><code>GET</code> /api/me</h2>
<p>getMe</p>
<small>app/api/me.ts</small>
</article>
@@ -0,0 +1,318 @@
{
"openapi": "3.1.0",
"info": {
"title": "basic-app API",
"version": "0.8.0"
},
"paths": {
"/api/webhooks/payment": {
"post": {
"operationId": "postWebhooksPayment",
"summary": "Payment completed",
"description": "Sent after a payment reaches its settled state.",
"tags": ["API"],
"parameters": [],
"responses": {
"200": {
"description": "Successful response",
"content": {
"application/json": {
"schema": {}
}
}
},
"400": {
"description": "Invalid request"
},
"500": {
"description": "Internal error"
}
},
"x-wrnexus-source": "app/api/webhooks/payment.ts"
}
},
"/api/users/csr": {
"get": {
"operationId": "getUsersCsr",
"summary": "GET /api/users/csr",
"tags": ["API"],
"parameters": [],
"responses": {
"200": {
"description": "Successful response",
"content": {
"application/json": {
"schema": {}
}
}
},
"400": {
"description": "Invalid request"
},
"500": {
"description": "Internal error"
}
},
"x-wrnexus-source": "app/api/users/csr.ts"
}
},
"/api/users/ssr": {
"get": {
"operationId": "getUsersSsr",
"summary": "GET /api/users/ssr",
"tags": ["API"],
"parameters": [],
"responses": {
"200": {
"description": "Successful response",
"content": {
"application/json": {
"schema": {}
}
}
},
"400": {
"description": "Invalid request"
},
"500": {
"description": "Internal error"
}
},
"x-wrnexus-source": "app/api/users/ssr.ts"
}
},
"/api/graphql-example": {
"post": {
"operationId": "postGraphqlExample",
"summary": "POST /api/graphql-example",
"tags": ["API"],
"parameters": [],
"responses": {
"200": {
"description": "Successful response",
"content": {
"application/json": {
"schema": {}
}
}
},
"400": {
"description": "Invalid request"
},
"500": {
"description": "Internal error"
}
},
"x-wrnexus-source": "app/api/graphql-example.ts"
}
},
"/api/typed-user": {
"post": {
"operationId": "postTypedUser",
"summary": "POST /api/typed-user",
"description": "Validate and echo a typed user payload.",
"tags": ["API"],
"parameters": [],
"responses": {
"200": {
"description": "Successful response",
"content": {
"application/json": {
"schema": {}
}
}
},
"400": {
"description": "Invalid request"
},
"500": {
"description": "Internal error"
}
},
"x-wrnexus-source": "app/api/typed-user.ts"
}
},
"/api/logout": {
"post": {
"operationId": "postLogout",
"summary": "POST /api/logout",
"tags": ["API"],
"parameters": [],
"responses": {
"200": {
"description": "Successful response",
"content": {
"application/json": {
"schema": {}
}
}
},
"400": {
"description": "Invalid request"
},
"500": {
"description": "Internal error"
}
},
"x-wrnexus-source": "app/api/logout.ts"
}
},
"/api/hello": {
"get": {
"operationId": "getHello",
"summary": "GET /api/hello",
"tags": ["API"],
"parameters": [],
"responses": {
"200": {
"description": "Successful response",
"content": {
"application/json": {
"schema": {}
}
}
},
"400": {
"description": "Invalid request"
},
"500": {
"description": "Internal error"
}
},
"x-wrnexus-source": "app/api/hello.ts"
}
},
"/api/login": {
"post": {
"operationId": "postLogin",
"summary": "POST /api/login",
"tags": ["API"],
"parameters": [],
"responses": {
"200": {
"description": "Successful response",
"content": {
"application/json": {
"schema": {}
}
}
},
"400": {
"description": "Invalid request"
},
"500": {
"description": "Internal error"
}
},
"x-wrnexus-source": "app/api/login.ts"
}
},
"/api/echo": {
"get": {
"operationId": "getEcho",
"summary": "GET /api/echo",
"tags": ["API"],
"parameters": [],
"responses": {
"200": {
"description": "Successful response",
"content": {
"application/json": {
"schema": {}
}
}
},
"400": {
"description": "Invalid request"
},
"500": {
"description": "Internal error"
}
},
"x-wrnexus-source": "app/api/echo.ts"
},
"post": {
"operationId": "postEcho",
"summary": "POST /api/echo",
"tags": ["API"],
"parameters": [],
"responses": {
"200": {
"description": "Successful response",
"content": {
"application/json": {
"schema": {}
}
}
},
"400": {
"description": "Invalid request"
},
"500": {
"description": "Internal error"
}
},
"x-wrnexus-source": "app/api/echo.ts"
}
},
"/api/me": {
"get": {
"operationId": "getMe",
"summary": "GET /api/me",
"tags": ["API"],
"parameters": [],
"responses": {
"200": {
"description": "Successful response",
"content": {
"application/json": {
"schema": {}
}
}
},
"400": {
"description": "Invalid request"
},
"500": {
"description": "Internal error"
}
},
"x-wrnexus-source": "app/api/me.ts"
}
}
},
"webhooks": {
"payment.completed": {
"post": {
"summary": "Payment completed",
"description": "Sent after a payment reaches its settled state.",
"parameters": [
{
"name": "x-payment-signature",
"in": "header",
"required": true,
"schema": {
"type": "string"
}
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/PaymentCompleted"
}
}
}
},
"responses": {
"200": {
"description": "Webhook accepted"
}
},
"x-wrnexus-source": "app/api/webhooks/payment.ts"
}
}
}
}
@@ -0,0 +1,91 @@
{
"info": {
"name": "basic-app API",
"schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json"
},
"item": [
{
"name": "postWebhooksPayment",
"request": {
"method": "POST",
"url": "{{baseUrl}}/api/webhooks/payment"
}
},
{
"name": "getUsersCsr",
"request": {
"method": "GET",
"url": "{{baseUrl}}/api/users/csr"
}
},
{
"name": "getUsersSsr",
"request": {
"method": "GET",
"url": "{{baseUrl}}/api/users/ssr"
}
},
{
"name": "postGraphqlExample",
"request": {
"method": "POST",
"url": "{{baseUrl}}/api/graphql-example"
}
},
{
"name": "postTypedUser",
"request": {
"method": "POST",
"url": "{{baseUrl}}/api/typed-user"
}
},
{
"name": "postLogout",
"request": {
"method": "POST",
"url": "{{baseUrl}}/api/logout"
}
},
{
"name": "getHello",
"request": {
"method": "GET",
"url": "{{baseUrl}}/api/hello"
}
},
{
"name": "postLogin",
"request": {
"method": "POST",
"url": "{{baseUrl}}/api/login"
}
},
{
"name": "getEcho",
"request": {
"method": "GET",
"url": "{{baseUrl}}/api/echo"
}
},
{
"name": "postEcho",
"request": {
"method": "POST",
"url": "{{baseUrl}}/api/echo"
}
},
{
"name": "getMe",
"request": {
"method": "GET",
"url": "{{baseUrl}}/api/me"
}
}
],
"variable": [
{
"key": "baseUrl",
"value": "http://localhost:3000"
}
]
}
@@ -0,0 +1,5 @@
package wrnexussdk
import ("bytes"; "encoding/json"; "fmt"; "net/http")
type Client struct { BaseURL string; HTTP *http.Client }
func (c *Client) Request(method, path string, body any) (map[string]any, error) { data,_:=json.Marshal(body); req,_:=http.NewRequest(method,c.BaseURL+path,bytes.NewReader(data)); req.Header.Set("content-type","application/json"); client:=c.HTTP;if client==nil{client=http.DefaultClient};res,err:=client.Do(req);if err!=nil{return nil,err};defer res.Body.Close();if res.StatusCode>=400{return nil,fmt.Errorf("API status %d",res.StatusCode)};var out map[string]any;err=json.NewDecoder(res.Body).Decode(&out);return out,err }
@@ -0,0 +1,3 @@
package dev.wrnexus.sdk;
import java.net.URI; import java.net.http.*;
public final class WrnexusApi { private final String baseUrl; private final HttpClient http = HttpClient.newHttpClient(); public WrnexusApi(String baseUrl){this.baseUrl=baseUrl;} public String request(String method,String path,String json)throws Exception{var request=HttpRequest.newBuilder(URI.create(baseUrl+path)).header("content-type","application/json").method(method,HttpRequest.BodyPublishers.ofString(json==null?"":json)).build();var response=http.send(request,HttpResponse.BodyHandlers.ofString());if(response.statusCode()>=400)throw new IllegalStateException("API status "+response.statusCode());return response.body();} }
@@ -0,0 +1,58 @@
const request = async (method, path, body, options = {}) => {
const response = await globalThis.fetch((options.baseUrl || "") + path, {
method,
headers: { "content-type": "application/json", ...(options.headers || {}) },
body: body === undefined ? undefined : JSON.stringify(body),
});
const value = await response.json();
if (!response.ok)
throw Object.assign(new Error(value?.error?.message || "API request failed"), {
status: response.status,
body: value,
});
return value.data ?? value;
};
export const postWebhooksPayment = (params = {}, body, options = {}) => {
void params;
return request("POST", `/api/webhooks/payment`, body, options);
};
export const getUsersCsr = (params = {}, body, options = {}) => {
void params;
return request("GET", `/api/users/csr`, body, options);
};
export const getUsersSsr = (params = {}, body, options = {}) => {
void params;
return request("GET", `/api/users/ssr`, body, options);
};
export const postGraphqlExample = (params = {}, body, options = {}) => {
void params;
return request("POST", `/api/graphql-example`, body, options);
};
export const postTypedUser = (params = {}, body, options = {}) => {
void params;
return request("POST", `/api/typed-user`, body, options);
};
export const postLogout = (params = {}, body, options = {}) => {
void params;
return request("POST", `/api/logout`, body, options);
};
export const getHello = (params = {}, body, options = {}) => {
void params;
return request("GET", `/api/hello`, body, options);
};
export const postLogin = (params = {}, body, options = {}) => {
void params;
return request("POST", `/api/login`, body, options);
};
export const getEcho = (params = {}, body, options = {}) => {
void params;
return request("GET", `/api/echo`, body, options);
};
export const postEcho = (params = {}, body, options = {}) => {
void params;
return request("POST", `/api/echo`, body, options);
};
export const getMe = (params = {}, body, options = {}) => {
void params;
return request("GET", `/api/me`, body, options);
};
@@ -0,0 +1,19 @@
import json, urllib.request
class WrnexusApi:
def __init__(self, base_url): self.base_url = base_url.rstrip('/')
def request(self, method, path, body=None):
data = None if body is None else json.dumps(body).encode()
request = urllib.request.Request(self.base_url + path, data=data, method=method, headers={'content-type':'application/json'})
with urllib.request.urlopen(request) as response: return json.load(response)
def postWebhooksPayment(self, path, body=None): return self.request('POST', path, body)
def getUsersCsr(self, path, body=None): return self.request('GET', path, body)
def getUsersSsr(self, path, body=None): return self.request('GET', path, body)
def postGraphqlExample(self, path, body=None): return self.request('POST', path, body)
def postTypedUser(self, path, body=None): return self.request('POST', path, body)
def postLogout(self, path, body=None): return self.request('POST', path, body)
def getHello(self, path, body=None): return self.request('GET', path, body)
def postLogin(self, path, body=None): return self.request('POST', path, body)
def getEcho(self, path, body=None): return self.request('GET', path, body)
def postEcho(self, path, body=None): return self.request('POST', path, body)
def getMe(self, path, body=None): return self.request('GET', path, body)
@@ -0,0 +1,109 @@
export type RequestOptions = { baseUrl?: string; headers?: HeadersInit };
type ApiEnvelope = { data?: unknown; error?: { message?: string } };
const request = async (
method: string,
path: string,
body: unknown,
options: RequestOptions = {},
) => {
const response = await globalThis.fetch((options.baseUrl || "") + path, {
method,
headers: { "content-type": "application/json", ...(options.headers || {}) },
body: body === undefined ? undefined : JSON.stringify(body),
});
const value: ApiEnvelope = await response.json();
if (!response.ok)
throw Object.assign(new Error(value?.error?.message || "API request failed"), {
status: response.status,
body: value,
});
return value.data ?? value;
};
export const postWebhooksPayment = (
params: Record<string, string> = {},
body: unknown,
options: RequestOptions = {},
) => {
void params;
return request("POST", `/api/webhooks/payment`, body, options);
};
export const getUsersCsr = (
params: Record<string, string> = {},
body: unknown,
options: RequestOptions = {},
) => {
void params;
return request("GET", `/api/users/csr`, body, options);
};
export const getUsersSsr = (
params: Record<string, string> = {},
body: unknown,
options: RequestOptions = {},
) => {
void params;
return request("GET", `/api/users/ssr`, body, options);
};
export const postGraphqlExample = (
params: Record<string, string> = {},
body: unknown,
options: RequestOptions = {},
) => {
void params;
return request("POST", `/api/graphql-example`, body, options);
};
export const postTypedUser = (
params: Record<string, string> = {},
body: unknown,
options: RequestOptions = {},
) => {
void params;
return request("POST", `/api/typed-user`, body, options);
};
export const postLogout = (
params: Record<string, string> = {},
body: unknown,
options: RequestOptions = {},
) => {
void params;
return request("POST", `/api/logout`, body, options);
};
export const getHello = (
params: Record<string, string> = {},
body: unknown,
options: RequestOptions = {},
) => {
void params;
return request("GET", `/api/hello`, body, options);
};
export const postLogin = (
params: Record<string, string> = {},
body: unknown,
options: RequestOptions = {},
) => {
void params;
return request("POST", `/api/login`, body, options);
};
export const getEcho = (
params: Record<string, string> = {},
body: unknown,
options: RequestOptions = {},
) => {
void params;
return request("GET", `/api/echo`, body, options);
};
export const postEcho = (
params: Record<string, string> = {},
body: unknown,
options: RequestOptions = {},
) => {
void params;
return request("POST", `/api/echo`, body, options);
};
export const getMe = (
params: Record<string, string> = {},
body: unknown,
options: RequestOptions = {},
) => {
void params;
return request("GET", `/api/me`, body, options);
};
+4 -4
View File
@@ -1,6 +1,6 @@
{
"name": "basic-app",
"version": "0.1.0",
"version": "0.8.0",
"private": true,
"type": "module",
"scripts": {
@@ -22,13 +22,13 @@
},
"devDependencies": {
"@wrnexus/test": "workspace:*",
"@eslint/js": "latest",
"@eslint/js": "^10.0.1",
"@iconify-json/lucide": "^1.2.118",
"@iconify/tailwind4": "^1.2.3",
"@tailwindcss/cli": "^4.0.0",
"eslint": "latest",
"eslint": "^10.8.0",
"prettier": "^3.9.4",
"tailwindcss": "^4.0.0",
"typescript-eslint": "latest"
"typescript-eslint": "^8.65.0"
}
}
+8
View File
@@ -0,0 +1,8 @@
[build]
builder = "DOCKERFILE"
[deploy]
startCommand = "bun dist/server.js"
healthcheckPath = "/readyz"
restartPolicyType = "ON_FAILURE"
preDeployCommand = ["bunx wrnexus db migrate --profile=production"]
+11
View File
@@ -0,0 +1,11 @@
services:
- type: web
name: wrnexus
runtime: docker
healthCheckPath: /readyz
preDeployCommand: bunx wrnexus db migrate --profile=production
envVars:
- key: DATABASE_URL
sync: false
- key: SESSION_SECRET
sync: false
+2
View File
@@ -10,6 +10,8 @@ import type { AppConfig } from "@wrnexus/styles";
* framework-level headers and optional CORS.
*/
const config: AppConfig = {
frameworkBehaviour: 1,
compatibilityDate: "2026-08-02",
head: [
// --- Use a CSS framework via CDN (uncomment one) ---
// Bootstrap: