first commit
This commit is contained in:
@@ -0,0 +1,119 @@
|
||||
/**
|
||||
* Core request context and middleware contracts.
|
||||
*
|
||||
* The `Context` object is the single value that flows through middleware,
|
||||
* pages and API routes. It is intentionally small and framework-agnostic so
|
||||
* it can later be reused by the `.wrn` compiler output.
|
||||
*/
|
||||
|
||||
import {
|
||||
applyCookieHeaders,
|
||||
createCookieStore,
|
||||
createLocalStorageSnapshot,
|
||||
createSessionStore,
|
||||
type CookieStore,
|
||||
type LocalStorageSnapshot,
|
||||
type SessionStore,
|
||||
} from "./storage.ts";
|
||||
|
||||
/** Translate a key for the active language, interpolating `{param}` placeholders. */
|
||||
export type TFunction = (key: string, params?: Record<string, string | number>) => string;
|
||||
|
||||
export type Context = {
|
||||
/** The raw incoming web-standard Request. */
|
||||
req: Request;
|
||||
/** Parsed URL of the request (pathname, query, etc.). */
|
||||
url: URL;
|
||||
/** Active language for this request (resolved by the runtime); "" if i18n is unused. */
|
||||
lang: string;
|
||||
/** Translate a key for the active language (identity until the runtime sets it). */
|
||||
t: TFunction;
|
||||
/** Dynamic route params, e.g. `/users/[id]` -> `{ id: "42" }`. */
|
||||
params: Record<string, string>;
|
||||
/**
|
||||
* Per-request scratch space. Middleware can attach values here
|
||||
* (e.g. the authenticated user) and downstream handlers can read them.
|
||||
*/
|
||||
locals: Record<string, unknown>;
|
||||
/**
|
||||
* The authenticated user for this request, or null when anonymous. Populated
|
||||
* by the `sessionAuth` middleware (or `logIn`); read via `getUser(ctx)`.
|
||||
*/
|
||||
user?: unknown;
|
||||
/**
|
||||
* The direct socket peer IP, set by the server from `server.requestIP`. This
|
||||
* is NOT spoofable by request headers — prefer it over `x-forwarded-for` for
|
||||
* rate limiting unless you run behind a trusted proxy.
|
||||
*/
|
||||
ip?: string;
|
||||
/** Read/write HTTP cookies for the current response. */
|
||||
cookies: CookieStore;
|
||||
/** In-memory cookie-backed session store. */
|
||||
session: SessionStore;
|
||||
/** Read-only localStorage snapshot sent by the browser for CSR data bindings. */
|
||||
localStorage: LocalStorageSnapshot;
|
||||
};
|
||||
|
||||
/** Calls the next middleware in the chain (or the final route handler). */
|
||||
export type Next = () => Promise<Response> | Response;
|
||||
|
||||
/**
|
||||
* Middleware runs before pages and API routes. It can:
|
||||
* - inspect/modify `ctx`
|
||||
* - short-circuit by returning a `Response` without calling `next()`
|
||||
* - continue by returning `await next()`
|
||||
*/
|
||||
export type Middleware = (ctx: Context, next: Next) => Promise<Response> | Response;
|
||||
|
||||
/** SEO metadata rendered into the document `<head>`. */
|
||||
export type SeoConfig = {
|
||||
title?: string;
|
||||
titleTemplate?: string;
|
||||
description?: string;
|
||||
canonical?: string;
|
||||
canonicalBase?: string;
|
||||
robots?: string;
|
||||
keywords?: string | string[];
|
||||
image?: string;
|
||||
siteName?: string;
|
||||
type?: string;
|
||||
locale?: string;
|
||||
twitterCard?: string;
|
||||
twitterSite?: string;
|
||||
themeColor?: string;
|
||||
};
|
||||
|
||||
/** Page metadata rendered into the document `<head>`. */
|
||||
export type PageMeta = SeoConfig;
|
||||
|
||||
/** A page module's default export. Returns an HTML string for the body. */
|
||||
export type PageComponent = (ctx: Context) => string | Promise<string>;
|
||||
|
||||
/** Create a fresh context for an incoming request. */
|
||||
export function createContext(req: Request, url: URL): Context {
|
||||
const cookies = createCookieStore(req);
|
||||
return {
|
||||
req,
|
||||
url,
|
||||
params: {},
|
||||
locals: {},
|
||||
lang: "",
|
||||
t: (key) => key,
|
||||
cookies,
|
||||
// `url` already reflects X-Forwarded-Proto when trustProxy is on, so session
|
||||
// cookies get `Secure` behind a TLS-terminating proxy (matches CSRF cookies).
|
||||
session: createSessionStore(cookies, req, undefined, url.protocol === "https:"),
|
||||
localStorage: createLocalStorageSnapshot(req),
|
||||
};
|
||||
}
|
||||
|
||||
/** Apply headers accumulated on the context, such as Set-Cookie. */
|
||||
export function withContextHeaders(ctx: Context, res: Response): Response {
|
||||
const headers = new Headers(res.headers);
|
||||
applyCookieHeaders(ctx, headers);
|
||||
return new Response(res.body, {
|
||||
status: res.status,
|
||||
statusText: res.statusText,
|
||||
headers,
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user