release: WRNexusJS 0.3.0
This commit is contained in:
@@ -23,6 +23,8 @@ import {
|
||||
withContextHeaders,
|
||||
withSecurityHeaders,
|
||||
resolveRequestUrl,
|
||||
tenantMiddleware,
|
||||
tracingMiddleware,
|
||||
type Context,
|
||||
type Middleware,
|
||||
type Mode,
|
||||
@@ -43,6 +45,8 @@ import {
|
||||
type ResolvedTheme,
|
||||
type MobileConfig,
|
||||
type PwaConfig,
|
||||
type ObservabilityConfig,
|
||||
type TenancyConfig,
|
||||
} from "@wrnexus/styles";
|
||||
import {
|
||||
LANG_COOKIE,
|
||||
@@ -129,6 +133,10 @@ export interface RuntimeDeps {
|
||||
pwa?: PwaConfig | false;
|
||||
/** Framework security headers and CORS policy. */
|
||||
security?: SecurityConfig;
|
||||
/** Built-in request tracing and Server-Timing policy. */
|
||||
observability?: ObservabilityConfig;
|
||||
/** Built-in tenant identity resolution. */
|
||||
tenancy?: TenancyConfig;
|
||||
/** Max request body size in bytes (413 above this). Default 10 MB. */
|
||||
maxBodyBytes?: number;
|
||||
/** HMR hub for browser live-update sockets (dev only). */
|
||||
@@ -158,6 +166,65 @@ function shouldEnableDevToolbar(mode: string, deps: RuntimeDeps): boolean {
|
||||
);
|
||||
}
|
||||
|
||||
function tenantIdentityFromConfig(
|
||||
config: TenancyConfig,
|
||||
): (ctx: Context) => Promise<{ id: string; slug?: string } | null> {
|
||||
return async (ctx) => {
|
||||
const host = ctx.url.hostname.toLowerCase();
|
||||
|
||||
if (config.mode === "domain") return host ? { id: host, slug: host } : null;
|
||||
|
||||
if (config.mode === "path") {
|
||||
const segments = ctx.url.pathname.split("/").filter(Boolean);
|
||||
const prefix = config.pathPrefix?.replace(/^\/+|\/+$/g, "");
|
||||
const slug = prefix ? (segments[0] === prefix ? segments[1] : undefined) : segments[0];
|
||||
return slug ? { id: slug, slug } : null;
|
||||
}
|
||||
|
||||
if (config.mode === "subdomain" || config.mode === undefined) {
|
||||
const roots = config.rootDomains?.map((domain) => domain.toLowerCase()) ?? [];
|
||||
const root = roots.find((domain) => host === domain || host.endsWith(`.${domain}`));
|
||||
const slug = root ? host.slice(0, -(root.length + 1)) : host.split(".")[0];
|
||||
if (!slug || slug === host || slug === "www" || slug === "localhost") return null;
|
||||
return { id: slug, slug };
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
}
|
||||
|
||||
function frameworkMiddleware(deps: RuntimeDeps): Middleware[] {
|
||||
const middleware: Middleware[] = [];
|
||||
|
||||
if (deps.observability && deps.observability.enabled !== false) {
|
||||
middleware.push(
|
||||
tracingMiddleware(undefined, {
|
||||
sampleRate: deps.observability.sampleRate,
|
||||
serverTiming: deps.observability.serverTiming,
|
||||
onComplete:
|
||||
deps.observability.exporter === "console"
|
||||
? (ctx, records) => {
|
||||
const total = records.find((record) => record.name === "http.request")?.durationMs;
|
||||
console.log(
|
||||
`[wrnexus:trace] ${ctx.req.method} ${ctx.url.pathname} ${total?.toFixed(2) ?? "0.00"}ms`,
|
||||
);
|
||||
}
|
||||
: undefined,
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
if (deps.tenancy && deps.tenancy.mode !== "custom") {
|
||||
middleware.push(
|
||||
tenantMiddleware(tenantIdentityFromConfig(deps.tenancy), {
|
||||
required: deps.tenancy.required,
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
return middleware;
|
||||
}
|
||||
|
||||
function shouldReportNotFound(pathname: string): boolean {
|
||||
return !(
|
||||
pathname.startsWith("/__wrnexus/") ||
|
||||
@@ -597,6 +664,11 @@ export interface Handlers {
|
||||
/** Build the fetch + websocket handlers from a set of dependencies. */
|
||||
export function createHandlers(deps: RuntimeDeps): Handlers {
|
||||
const { mode, hmr, router, loadModule, getMiddleware, assets } = deps;
|
||||
const builtInMiddleware = frameworkMiddleware(deps);
|
||||
const resolveMiddleware = async (): Promise<Middleware[]> => [
|
||||
...builtInMiddleware,
|
||||
...(await getMiddleware()),
|
||||
];
|
||||
const maxBodyBytes = deps.maxBodyBytes ?? 10 * 1024 * 1024; // 10 MB default
|
||||
|
||||
// Server-side realtime room manager (shared by every `defineRoom` connection).
|
||||
@@ -832,7 +904,7 @@ export function createHandlers(deps: RuntimeDeps): Handlers {
|
||||
);
|
||||
ctx.t = makeT(deps.i18n, ctx.lang);
|
||||
}
|
||||
const mws = await getMiddleware();
|
||||
const mws = await resolveMiddleware();
|
||||
const res = secure(
|
||||
withContextHeaders(ctx, await runMiddleware(mws, ctx, () => dispatch(ctx))),
|
||||
);
|
||||
@@ -1271,7 +1343,7 @@ export function createHandlers(deps: RuntimeDeps): Handlers {
|
||||
}
|
||||
const res = withContextHeaders(
|
||||
ctx,
|
||||
await runMiddleware(await getMiddleware(), ctx, () => dispatch(ctx)),
|
||||
await runMiddleware(await resolveMiddleware(), ctx, () => dispatch(ctx)),
|
||||
);
|
||||
const html = await res.text();
|
||||
ws.send(JSON.stringify({ type: "html", html }));
|
||||
|
||||
Reference in New Issue
Block a user