@pracht/core 0.6.1 → 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/app-BWriseLj.d.mts +298 -0
- package/dist/app-BvC1uQG5.mjs +308 -0
- package/dist/browser.d.mts +7 -0
- package/dist/browser.mjs +8 -0
- package/dist/client.d.mts +4 -0
- package/dist/client.mjs +4 -0
- package/dist/forwardRef-grZ6t4GS.mjs +27 -0
- package/dist/hydration-M1QzbQ1O.d.mts +23 -0
- package/dist/index.d.mts +7 -438
- package/dist/index.mjs +7 -1965
- package/dist/manifest.d.mts +2 -0
- package/dist/manifest.mjs +2 -0
- package/dist/prefetch-B50kX9RL.mjs +118 -0
- package/dist/prefetch-cache-DzP2Bj9H.mjs +129 -0
- package/dist/prerender-B8_CqYwd.d.mts +51 -0
- package/dist/prerender-D3E2H96p.mjs +648 -0
- package/dist/router-B7J4YYwg.mjs +418 -0
- package/dist/router-BcUmg1kB.d.mts +27 -0
- package/dist/runtime-context-B5pREhcM.mjs +164 -0
- package/dist/runtime-context-ytVd7GmZ.d.mts +72 -0
- package/dist/runtime-middleware-aeBdgjYr.d.mts +77 -0
- package/dist/server.d.mts +5 -0
- package/dist/server.mjs +5 -0
- package/dist/types-idmK5omD.mjs +355 -0
- package/package.json +19 -2
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { A as HrefRouteDefinition, F as LoaderLike, N as LoaderData, O as HrefFn, dt as RouteTarget, nt as RouteId, st as RouteParams } from "./app-BWriseLj.mjs";
|
|
2
|
+
import { c as SerializedRouteError } from "./runtime-context-ytVd7GmZ.mjs";
|
|
3
|
+
import * as _$preact from "preact";
|
|
4
|
+
import { JSX, h } from "preact";
|
|
5
|
+
|
|
6
|
+
//#region src/href.d.ts
|
|
7
|
+
declare function createHref(routes: readonly HrefRouteDefinition[]): HrefFn;
|
|
8
|
+
//#endregion
|
|
9
|
+
//#region src/runtime-hooks.d.ts
|
|
10
|
+
interface FormProps extends Omit<JSX.HTMLAttributes<HTMLFormElement>, "action" | "method"> {
|
|
11
|
+
action?: string;
|
|
12
|
+
method?: string;
|
|
13
|
+
}
|
|
14
|
+
type LinkProps<TRoute extends RouteId = RouteId> = Omit<JSX.HTMLAttributes<HTMLAnchorElement>, "href"> & RouteTarget<TRoute>;
|
|
15
|
+
interface Location {
|
|
16
|
+
pathname: string;
|
|
17
|
+
search: string;
|
|
18
|
+
}
|
|
19
|
+
declare function useRouteData<TLoader extends LoaderLike>(): LoaderData<TLoader>;
|
|
20
|
+
declare function useRouteData<TData = unknown>(): TData;
|
|
21
|
+
declare function useLocation(): Location;
|
|
22
|
+
declare function useParams(): RouteParams;
|
|
23
|
+
declare function useRevalidate(): () => Promise<unknown>;
|
|
24
|
+
declare function Link<TRoute extends RouteId>(props: LinkProps<TRoute>): _$preact.VNode<_$preact.ClassAttributes<HTMLAnchorElement> & h.JSX.HTMLAttributes<HTMLAnchorElement>>;
|
|
25
|
+
declare function Form(props: FormProps): _$preact.VNode<_$preact.ClassAttributes<HTMLFormElement> & h.JSX.HTMLAttributes<HTMLFormElement>>;
|
|
26
|
+
//#endregion
|
|
27
|
+
//#region src/runtime-client-fetch.d.ts
|
|
28
|
+
type RouteStateResult = {
|
|
29
|
+
type: "data";
|
|
30
|
+
data: unknown;
|
|
31
|
+
} | {
|
|
32
|
+
type: "redirect";
|
|
33
|
+
location: string;
|
|
34
|
+
} | {
|
|
35
|
+
type: "error";
|
|
36
|
+
error: SerializedRouteError;
|
|
37
|
+
};
|
|
38
|
+
/**
|
|
39
|
+
* Parse a possibly-server-supplied redirect target against a base URL and
|
|
40
|
+
* return it only if it uses a safe navigation scheme (`http:` or `https:`).
|
|
41
|
+
*
|
|
42
|
+
* `javascript:`, `data:`, `vbscript:`, `blob:`, `file:` and similar schemes
|
|
43
|
+
* can execute script or bypass same-origin assumptions when assigned to
|
|
44
|
+
* `window.location.href` — a server-controlled redirect (from a loader,
|
|
45
|
+
* middleware, form action response, or API route) must never be able to
|
|
46
|
+
* trigger them. Returns `null` for unsafe or unparseable inputs.
|
|
47
|
+
*/
|
|
48
|
+
declare function parseSafeNavigationUrl(location: string, base: string | URL): URL | null;
|
|
49
|
+
declare function fetchPrachtRouteState(url: string, options?: {
|
|
50
|
+
signal?: AbortSignal;
|
|
51
|
+
useDataParam?: boolean;
|
|
52
|
+
}): Promise<RouteStateResult>;
|
|
53
|
+
//#endregion
|
|
54
|
+
//#region src/runtime-middleware.d.ts
|
|
55
|
+
type RedirectOptions = number | {
|
|
56
|
+
baseUrl?: string | URL;
|
|
57
|
+
method?: string;
|
|
58
|
+
request?: Request;
|
|
59
|
+
status?: number;
|
|
60
|
+
};
|
|
61
|
+
/**
|
|
62
|
+
* Convenience helper for middleware (and loaders/handlers) to short-circuit
|
|
63
|
+
* with a redirect Response. Validates the target's scheme and rejects
|
|
64
|
+
* CR/LF injection. Pass the current request (or method) when the default
|
|
65
|
+
* status should follow HTTP method safety: safe methods default to 302,
|
|
66
|
+
* unsafe methods default to 303.
|
|
67
|
+
*
|
|
68
|
+
* ```ts
|
|
69
|
+
* export const middleware: MiddlewareFn = async ({ request }, next) => {
|
|
70
|
+
* if (!hasSession(request)) return redirect("/login", { request });
|
|
71
|
+
* return next();
|
|
72
|
+
* };
|
|
73
|
+
* ```
|
|
74
|
+
*/
|
|
75
|
+
declare function redirect(target: string, options?: RedirectOptions): Response;
|
|
76
|
+
//#endregion
|
|
77
|
+
export { parseSafeNavigationUrl as a, Link as c, useLocation as d, useParams as f, createHref as h, fetchPrachtRouteState as i, LinkProps as l, useRouteData as m, redirect as n, Form as o, useRevalidate as p, RouteStateResult as r, FormProps as s, RedirectOptions as t, Location as u };
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import { $ as RouteComponentProps, A as HrefRouteDefinition, B as ModuleImporter, C as HeadAttributes, D as HrefArgs, E as HeadersArgs, G as PrachtAppConfig, H as ModuleRegistry, I as MiddlewareArgs, J as Register, K as PrachtHttpError, L as MiddlewareFn, M as LoaderArgs, N as LoaderData, O as HrefFn, P as LoaderFn, Q as ResolvedRoute, R as MiddlewareModule, S as HeadArgs, T as HeadScriptDescriptor, U as NavigateOptions, V as ModuleRef, W as PrachtApp, X as ResolvedApiRoute, Y as RenderMode, Z as ResolvedPrachtApp, _ as BuildHrefOptions, _t as ShellProps, a as matchApiRoute, at as RouteModule, b as GroupDefinition, c as resolveApp, ct as RouteParamsFor, d as ApiConfig, dt as RouteTarget, et as RouteConfig, f as ApiRouteArgs, ft as RouteTreeNode, g as BaseRouteArgs, gt as ShellModule, h as ApiRouteModule, ht as SearchParamsInput, i as group, it as RouteMeta, j as HttpMethod, k as HrefOptions, l as route, lt as RouteRevalidate, m as ApiRouteMatch, mt as SearchParamValue, n as buildPathFromSegments, nt as RouteId, o as matchAppRoute, ot as RouteParamInput, p as ApiRouteHandler, pt as SearchParamPrimitive, q as PrefetchStrategy, r as defineApp, rt as RouteMatch, s as resolveApiRoutes, st as RouteParams, t as buildHref, tt as RouteDefinition, u as timeRevalidate, ut as RouteSearchFor, v as DataModule, vt as TimeRevalidatePolicy, w as HeadMetadata, x as GroupMeta, y as ErrorBoundaryProps, z as MiddlewareNext } from "./app-BWriseLj.mjs";
|
|
2
|
+
import { h as createHref, n as redirect, t as RedirectOptions } from "./runtime-middleware-aeBdgjYr.mjs";
|
|
3
|
+
import { c as SerializedRouteError, n as PrachtRuntimeProvider, o as PrachtRuntimeDiagnosticPhase, s as PrachtRuntimeDiagnostics } from "./runtime-context-ytVd7GmZ.mjs";
|
|
4
|
+
import { a as prerenderApp, c as applyDefaultSecurityHeaders, i as PrerenderResult, n as PrerenderAppOptions, o as HandlePrachtRequestOptions, r as PrerenderAppResult, s as handlePrachtRequest, t as ISGManifestEntry } from "./prerender-B8_CqYwd.mjs";
|
|
5
|
+
export { type ApiConfig, type ApiRouteArgs, type ApiRouteHandler, type ApiRouteMatch, type ApiRouteModule, type BaseRouteArgs, type BuildHrefOptions, type DataModule, type ErrorBoundaryProps, type GroupDefinition, type GroupMeta, type HandlePrachtRequestOptions, type HeadArgs, type HeadAttributes, type HeadMetadata, type HeadScriptDescriptor, type HeadersArgs, type HrefArgs, type HrefFn, type HrefOptions, type HrefRouteDefinition, type HttpMethod, type ISGManifestEntry, type LoaderArgs, type LoaderData, type LoaderFn, type MiddlewareArgs, type MiddlewareFn, type MiddlewareModule, type MiddlewareNext, type ModuleImporter, type ModuleRef, type ModuleRegistry, type NavigateOptions, type PrachtApp, type PrachtAppConfig, PrachtHttpError, type PrachtRuntimeDiagnosticPhase, type PrachtRuntimeDiagnostics, PrachtRuntimeProvider, type PrefetchStrategy, type PrerenderAppOptions, type PrerenderAppResult, type PrerenderResult, type RedirectOptions, type Register, type RenderMode, type ResolvedApiRoute, type ResolvedPrachtApp, type ResolvedRoute, type RouteComponentProps, type RouteConfig, type RouteDefinition, type RouteId, type RouteMatch, type RouteMeta, type RouteModule, type RouteParamInput, type RouteParams, type RouteParamsFor, type RouteRevalidate, type RouteSearchFor, type RouteTarget, type RouteTreeNode, type SearchParamPrimitive, type SearchParamValue, type SearchParamsInput, type SerializedRouteError, type ShellModule, type ShellProps, type TimeRevalidatePolicy, applyDefaultSecurityHeaders, buildHref, buildPathFromSegments, createHref, defineApp, group, handlePrachtRequest, matchApiRoute, matchAppRoute, prerenderApp, redirect, resolveApiRoutes, resolveApp, route, timeRevalidate };
|
package/dist/server.mjs
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import { a as matchApiRoute, c as resolveApp, i as group, l as route, n as buildPathFromSegments, o as matchAppRoute, r as defineApp, s as resolveApiRoutes, t as buildHref, u as timeRevalidate } from "./app-BvC1uQG5.mjs";
|
|
2
|
+
import { S as createHref, _ as applyDefaultSecurityHeaders, t as PrachtHttpError, u as redirect } from "./types-idmK5omD.mjs";
|
|
3
|
+
import { t as PrachtRuntimeProvider } from "./runtime-context-B5pREhcM.mjs";
|
|
4
|
+
import { n as handlePrachtRequest, t as prerenderApp } from "./prerender-D3E2H96p.mjs";
|
|
5
|
+
export { PrachtHttpError, PrachtRuntimeProvider, applyDefaultSecurityHeaders, buildHref, buildPathFromSegments, createHref, defineApp, group, handlePrachtRequest, matchApiRoute, matchAppRoute, prerenderApp, redirect, resolveApiRoutes, resolveApp, route, timeRevalidate };
|
|
@@ -0,0 +1,355 @@
|
|
|
1
|
+
import { t as buildHref } from "./app-BvC1uQG5.mjs";
|
|
2
|
+
import { c as navigateToClientLocation, h as SAFE_METHODS, l as parseSafeNavigationUrl, m as ROUTE_STATE_REQUEST_HEADER, p as ROUTE_STATE_CACHE_CONTROL, r as clearPrefetchCache, s as fetchPrachtRouteState } from "./prefetch-cache-DzP2Bj9H.mjs";
|
|
3
|
+
import { n as RouteDataContext, s as deserializeRouteError } from "./runtime-context-B5pREhcM.mjs";
|
|
4
|
+
import { h } from "preact";
|
|
5
|
+
import { useContext } from "preact/hooks";
|
|
6
|
+
//#region src/href.ts
|
|
7
|
+
function createHref(routes) {
|
|
8
|
+
return ((routeId, options) => buildHref(routes, routeId, options));
|
|
9
|
+
}
|
|
10
|
+
//#endregion
|
|
11
|
+
//#region src/runtime-headers.ts
|
|
12
|
+
const HEADER_CRLF_RE = /[\r\n]/;
|
|
13
|
+
/**
|
|
14
|
+
* Reject header values containing CR/LF. Some runtimes (Node `undici`
|
|
15
|
+
* Headers) throw on their own, but Web-runtime fetch implementations
|
|
16
|
+
* vary, and a user-supplied `headers()` value is never trusted input.
|
|
17
|
+
* Keeping the check here means response-splitting can't slip through on
|
|
18
|
+
* any adapter.
|
|
19
|
+
*/
|
|
20
|
+
function assertSafeHeaderValue(name, value) {
|
|
21
|
+
if (HEADER_CRLF_RE.test(value)) throw new Error(`Refused to set header "${name}": value contains CR or LF`);
|
|
22
|
+
}
|
|
23
|
+
function applyHeaders(headers, init) {
|
|
24
|
+
for (const [key, value] of iterateHeaderInit(init)) assertSafeHeaderValue(key, value);
|
|
25
|
+
new Headers(init).forEach((value, key) => {
|
|
26
|
+
headers.set(key, value);
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
function* iterateHeaderInit(init) {
|
|
30
|
+
if (init instanceof Headers) {
|
|
31
|
+
for (const entry of init.entries()) yield entry;
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
if (Array.isArray(init)) {
|
|
35
|
+
for (const entry of init) if (entry && entry.length >= 2) yield [entry[0], entry[1]];
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
for (const [key, value] of Object.entries(init)) yield [key, value];
|
|
39
|
+
}
|
|
40
|
+
function applyDefaultSecurityHeaders(headers) {
|
|
41
|
+
if (!headers.has("permissions-policy")) headers.set("permissions-policy", "accelerometer=(), camera=(), geolocation=(), gyroscope=(), magnetometer=(), microphone=(), payment=(), usb=()");
|
|
42
|
+
if (!headers.has("referrer-policy")) headers.set("referrer-policy", "strict-origin-when-cross-origin");
|
|
43
|
+
if (!headers.has("x-content-type-options")) headers.set("x-content-type-options", "nosniff");
|
|
44
|
+
if (!headers.has("x-frame-options")) headers.set("x-frame-options", "SAMEORIGIN");
|
|
45
|
+
return headers;
|
|
46
|
+
}
|
|
47
|
+
function applySecurityAndRouteHeaders(headers, options) {
|
|
48
|
+
applyDefaultSecurityHeaders(headers);
|
|
49
|
+
if (options) {
|
|
50
|
+
appendVaryHeader(headers, ROUTE_STATE_REQUEST_HEADER);
|
|
51
|
+
if (options.isRouteStateRequest && !headers.has("cache-control")) headers.set("cache-control", ROUTE_STATE_CACHE_CONTROL);
|
|
52
|
+
}
|
|
53
|
+
return headers;
|
|
54
|
+
}
|
|
55
|
+
function withDefaultSecurityHeaders(response) {
|
|
56
|
+
const headers = new Headers(response.headers);
|
|
57
|
+
applySecurityAndRouteHeaders(headers);
|
|
58
|
+
return new Response(response.body, {
|
|
59
|
+
status: response.status,
|
|
60
|
+
statusText: response.statusText,
|
|
61
|
+
headers
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
function withRouteResponseHeaders(response, options) {
|
|
65
|
+
const headers = new Headers(response.headers);
|
|
66
|
+
applySecurityAndRouteHeaders(headers, options);
|
|
67
|
+
return new Response(response.body, {
|
|
68
|
+
status: response.status,
|
|
69
|
+
statusText: response.statusText,
|
|
70
|
+
headers
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
function appendVaryHeader(headers, value) {
|
|
74
|
+
const current = headers.get("vary");
|
|
75
|
+
if (!current) {
|
|
76
|
+
headers.set("vary", value);
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
const values = current.split(",").map((part) => part.trim().toLowerCase()).filter(Boolean);
|
|
80
|
+
if (values.includes("*") || values.includes(value.toLowerCase())) return;
|
|
81
|
+
headers.set("vary", `${current}, ${value}`);
|
|
82
|
+
}
|
|
83
|
+
//#endregion
|
|
84
|
+
//#region src/runtime-manifest.ts
|
|
85
|
+
/** Strip leading `./` and `/` so all module paths share one canonical form. */
|
|
86
|
+
function normalizeModulePath(path) {
|
|
87
|
+
return path.replace(/^\.?\//, "");
|
|
88
|
+
}
|
|
89
|
+
function buildSuffixIndex(manifest) {
|
|
90
|
+
const index = /* @__PURE__ */ new Map();
|
|
91
|
+
for (const key of Object.keys(manifest)) {
|
|
92
|
+
const normalized = normalizeModulePath(key);
|
|
93
|
+
if (!normalized) continue;
|
|
94
|
+
if (!index.has(normalized)) index.set(normalized, key);
|
|
95
|
+
for (let i = normalized.indexOf("/"); i !== -1; i = normalized.indexOf("/", i + 1)) {
|
|
96
|
+
const suffix = normalized.slice(i + 1);
|
|
97
|
+
if (suffix && !index.has(suffix)) index.set(suffix, key);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
return index;
|
|
101
|
+
}
|
|
102
|
+
const suffixIndexCache = /* @__PURE__ */ new WeakMap();
|
|
103
|
+
function getSuffixIndex(manifest) {
|
|
104
|
+
let index = suffixIndexCache.get(manifest);
|
|
105
|
+
if (index) return index;
|
|
106
|
+
index = buildSuffixIndex(manifest);
|
|
107
|
+
suffixIndexCache.set(manifest, index);
|
|
108
|
+
return index;
|
|
109
|
+
}
|
|
110
|
+
function resolveManifestEntries(manifest, file) {
|
|
111
|
+
if (file in manifest) return manifest[file];
|
|
112
|
+
const resolved = getSuffixIndex(manifest).get(normalizeModulePath(file));
|
|
113
|
+
if (resolved) return manifest[resolved];
|
|
114
|
+
}
|
|
115
|
+
function resolvePageUrlsFromManifest(manifest, shellFile, routeFile) {
|
|
116
|
+
const urls = /* @__PURE__ */ new Set();
|
|
117
|
+
const add = (file) => {
|
|
118
|
+
const entries = resolveManifestEntries(manifest, file);
|
|
119
|
+
if (entries) for (const url of entries) urls.add(url);
|
|
120
|
+
};
|
|
121
|
+
if (shellFile) add(shellFile);
|
|
122
|
+
add(routeFile);
|
|
123
|
+
return [...urls];
|
|
124
|
+
}
|
|
125
|
+
function resolvePageCssUrls(cssManifest, shellFile, routeFile) {
|
|
126
|
+
if (!cssManifest) return [];
|
|
127
|
+
return resolvePageUrlsFromManifest(cssManifest, shellFile, routeFile);
|
|
128
|
+
}
|
|
129
|
+
function resolvePageJsUrls(jsManifest, shellFile, routeFile) {
|
|
130
|
+
if (!jsManifest) return [];
|
|
131
|
+
return resolvePageUrlsFromManifest(jsManifest, shellFile, routeFile);
|
|
132
|
+
}
|
|
133
|
+
async function resolveRegistryModule(modules, file) {
|
|
134
|
+
if (!modules) return void 0;
|
|
135
|
+
if (file in modules) return modules[file]();
|
|
136
|
+
const resolved = getSuffixIndex(modules).get(normalizeModulePath(file));
|
|
137
|
+
if (resolved) return modules[resolved]();
|
|
138
|
+
}
|
|
139
|
+
async function resolveDataFunctions(route, routeModule, registry) {
|
|
140
|
+
let loader = routeModule?.loader;
|
|
141
|
+
let loaderFile = routeModule?.loader ? route.file : void 0;
|
|
142
|
+
if (route.loaderFile) {
|
|
143
|
+
const dataModule = await resolveRegistryModule(registry.dataModules, route.loaderFile);
|
|
144
|
+
if (dataModule?.loader) {
|
|
145
|
+
loader = dataModule.loader;
|
|
146
|
+
loaderFile = route.loaderFile;
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
return {
|
|
150
|
+
loader,
|
|
151
|
+
loaderFile
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
//#endregion
|
|
155
|
+
//#region src/runtime-middleware.ts
|
|
156
|
+
const DEFAULT_REDIRECT_STATUS_SAFE = 302;
|
|
157
|
+
const DEFAULT_REDIRECT_STATUS_UNSAFE = 303;
|
|
158
|
+
const REDIRECT_VALIDATION_BASE = "https://invalid.pracht.local/";
|
|
159
|
+
/**
|
|
160
|
+
* Build a safe redirect response from middleware/loader output. Rejects
|
|
161
|
+
* non-http(s) schemes (no `javascript:`/`data:`/etc.) and CR/LF injection
|
|
162
|
+
* against the `Location` header. When status is omitted, non-GET/HEAD
|
|
163
|
+
* requests default to 303 so the browser does not resend the body to the
|
|
164
|
+
* redirect target; safe methods default to 302.
|
|
165
|
+
*
|
|
166
|
+
* The original `target` string is preserved on success (relative paths
|
|
167
|
+
* stay relative) — we only parse it to validate scheme, not to rewrite
|
|
168
|
+
* it. Both the original input and its resolved URL must be CR/LF-free.
|
|
169
|
+
*/
|
|
170
|
+
function buildRedirectResponse(target, options) {
|
|
171
|
+
if (/[\r\n]/.test(target)) throw new Error("Refused redirect target containing CR/LF");
|
|
172
|
+
if (!parseSafeNavigationUrl(target, options.baseUrl)) throw new Error("Refused unsafe redirect target");
|
|
173
|
+
const method = (options.method ?? "GET").toUpperCase();
|
|
174
|
+
const defaultStatus = SAFE_METHODS.has(method) ? DEFAULT_REDIRECT_STATUS_SAFE : DEFAULT_REDIRECT_STATUS_UNSAFE;
|
|
175
|
+
const status = options.status ?? defaultStatus;
|
|
176
|
+
return new Response(null, {
|
|
177
|
+
status,
|
|
178
|
+
headers: { location: target }
|
|
179
|
+
});
|
|
180
|
+
}
|
|
181
|
+
/**
|
|
182
|
+
* Convenience helper for middleware (and loaders/handlers) to short-circuit
|
|
183
|
+
* with a redirect Response. Validates the target's scheme and rejects
|
|
184
|
+
* CR/LF injection. Pass the current request (or method) when the default
|
|
185
|
+
* status should follow HTTP method safety: safe methods default to 302,
|
|
186
|
+
* unsafe methods default to 303.
|
|
187
|
+
*
|
|
188
|
+
* ```ts
|
|
189
|
+
* export const middleware: MiddlewareFn = async ({ request }, next) => {
|
|
190
|
+
* if (!hasSession(request)) return redirect("/login", { request });
|
|
191
|
+
* return next();
|
|
192
|
+
* };
|
|
193
|
+
* ```
|
|
194
|
+
*/
|
|
195
|
+
function redirect(target, options = {}) {
|
|
196
|
+
if (typeof options === "number") return buildRedirectResponse(target, {
|
|
197
|
+
baseUrl: REDIRECT_VALIDATION_BASE,
|
|
198
|
+
status: options
|
|
199
|
+
});
|
|
200
|
+
return buildRedirectResponse(target, {
|
|
201
|
+
baseUrl: options.baseUrl ?? options.request?.url ?? REDIRECT_VALIDATION_BASE,
|
|
202
|
+
method: options.method ?? options.request?.method,
|
|
203
|
+
status: options.status
|
|
204
|
+
});
|
|
205
|
+
}
|
|
206
|
+
/**
|
|
207
|
+
* Run the middleware chain wrap-around-style. Each middleware receives
|
|
208
|
+
* `next` and may call it at most once. Calling `next()` invokes the rest
|
|
209
|
+
* of the chain (downstream middleware then `terminal`) and resolves to
|
|
210
|
+
* the final `Response`. A middleware that returns without calling `next()`
|
|
211
|
+
* short-circuits with whatever Response it returned.
|
|
212
|
+
*
|
|
213
|
+
* Module imports are kicked off concurrently up front; execution stays
|
|
214
|
+
* sequential because middleware may mutate `args.context` and ordering
|
|
215
|
+
* is part of the public contract.
|
|
216
|
+
*/
|
|
217
|
+
async function runMiddlewareChain(options) {
|
|
218
|
+
const { middlewareFiles, terminal } = options;
|
|
219
|
+
if (middlewareFiles.length === 0) return terminal();
|
|
220
|
+
const modulePromises = middlewareFiles.map((mwFile) => resolveRegistryModule(options.registry.middlewareModules, mwFile));
|
|
221
|
+
for (const p of modulePromises) p.catch(() => {});
|
|
222
|
+
const dispatch = async (i) => {
|
|
223
|
+
if (i >= middlewareFiles.length) return terminal();
|
|
224
|
+
const mwModule = await modulePromises[i];
|
|
225
|
+
if (!mwModule?.middleware) return dispatch(i + 1);
|
|
226
|
+
let calledNext = false;
|
|
227
|
+
const next = () => {
|
|
228
|
+
if (calledNext) throw new Error(`Middleware "${middlewareFiles[i]}" called next() multiple times`);
|
|
229
|
+
calledNext = true;
|
|
230
|
+
return dispatch(i + 1);
|
|
231
|
+
};
|
|
232
|
+
const args = {
|
|
233
|
+
request: options.request,
|
|
234
|
+
params: options.params,
|
|
235
|
+
context: options.context,
|
|
236
|
+
signal: options.signal,
|
|
237
|
+
url: options.url,
|
|
238
|
+
route: options.route
|
|
239
|
+
};
|
|
240
|
+
const response = await mwModule.middleware(args, next);
|
|
241
|
+
if (!(response instanceof Response)) throw new Error(`Middleware "${middlewareFiles[i]}" did not return a Response. Middleware must return the result of next() or a short-circuit Response.`);
|
|
242
|
+
return response;
|
|
243
|
+
};
|
|
244
|
+
return dispatch(0);
|
|
245
|
+
}
|
|
246
|
+
async function mergeHeadMetadata(shellModule, routeModule, routeArgs, data) {
|
|
247
|
+
const [shellHead, routeHead] = await Promise.all([shellModule?.head ? shellModule.head(routeArgs) : Promise.resolve({}), routeModule?.head ? routeModule.head({
|
|
248
|
+
...routeArgs,
|
|
249
|
+
data
|
|
250
|
+
}) : Promise.resolve({})]);
|
|
251
|
+
return {
|
|
252
|
+
title: routeHead.title ?? shellHead.title,
|
|
253
|
+
lang: routeHead.lang ?? shellHead.lang,
|
|
254
|
+
meta: [...shellHead.meta ?? [], ...routeHead.meta ?? []],
|
|
255
|
+
link: [...shellHead.link ?? [], ...routeHead.link ?? []],
|
|
256
|
+
script: [...shellHead.script ?? [], ...routeHead.script ?? []]
|
|
257
|
+
};
|
|
258
|
+
}
|
|
259
|
+
async function mergeDocumentHeaders(shellModule, routeModule, routeArgs, data) {
|
|
260
|
+
const headers = new Headers();
|
|
261
|
+
const [shellHeaders, routeHeaders] = await Promise.all([shellModule?.headers ? shellModule.headers(routeArgs) : Promise.resolve(void 0), routeModule?.headers ? routeModule.headers({
|
|
262
|
+
...routeArgs,
|
|
263
|
+
data
|
|
264
|
+
}) : Promise.resolve(void 0)]);
|
|
265
|
+
if (shellHeaders) applyHeaders(headers, shellHeaders);
|
|
266
|
+
if (routeHeaders) applyHeaders(headers, routeHeaders);
|
|
267
|
+
return headers;
|
|
268
|
+
}
|
|
269
|
+
//#endregion
|
|
270
|
+
//#region src/runtime-hooks.ts
|
|
271
|
+
function useRouteData() {
|
|
272
|
+
return useContext(RouteDataContext)?.data;
|
|
273
|
+
}
|
|
274
|
+
function useLocation() {
|
|
275
|
+
return parseLocation(useContext(RouteDataContext)?.url ?? (typeof window !== "undefined" ? window.location.pathname + window.location.search : "/"));
|
|
276
|
+
}
|
|
277
|
+
function useParams() {
|
|
278
|
+
return useContext(RouteDataContext)?.params ?? {};
|
|
279
|
+
}
|
|
280
|
+
function useRevalidate() {
|
|
281
|
+
const runtime = useContext(RouteDataContext);
|
|
282
|
+
return async () => {
|
|
283
|
+
if (typeof window === "undefined") return;
|
|
284
|
+
const result = await fetchPrachtRouteState(runtime?.url || window.location.pathname + window.location.search);
|
|
285
|
+
if (result.type === "redirect") {
|
|
286
|
+
await navigateToClientLocation(result.location);
|
|
287
|
+
return;
|
|
288
|
+
}
|
|
289
|
+
if (result.type === "error") throw deserializeRouteError(result.error);
|
|
290
|
+
runtime?.setData(result.data);
|
|
291
|
+
return result.data;
|
|
292
|
+
};
|
|
293
|
+
}
|
|
294
|
+
function Link(props) {
|
|
295
|
+
const routes = useContext(RouteDataContext)?.routes ?? globalThis.__PRACHT_ROUTE_DEFINITIONS__;
|
|
296
|
+
if (!routes) throw new Error("<Link route=...> must render inside a pracht route tree.");
|
|
297
|
+
const { route, params, search, hash, ...anchorProps } = props;
|
|
298
|
+
return h("a", {
|
|
299
|
+
...anchorProps,
|
|
300
|
+
href: buildHref(routes, route, {
|
|
301
|
+
params,
|
|
302
|
+
search,
|
|
303
|
+
hash
|
|
304
|
+
})
|
|
305
|
+
});
|
|
306
|
+
}
|
|
307
|
+
function Form(props) {
|
|
308
|
+
const { onSubmit, method, ...rest } = props;
|
|
309
|
+
return h("form", {
|
|
310
|
+
...rest,
|
|
311
|
+
method,
|
|
312
|
+
onSubmit: async (event) => {
|
|
313
|
+
onSubmit?.(event);
|
|
314
|
+
if (event.defaultPrevented) return;
|
|
315
|
+
const form = event.currentTarget;
|
|
316
|
+
if (!(form instanceof HTMLFormElement)) return;
|
|
317
|
+
const formMethod = (method ?? form.method ?? "post").toUpperCase();
|
|
318
|
+
if (SAFE_METHODS.has(formMethod)) return;
|
|
319
|
+
event.preventDefault();
|
|
320
|
+
clearPrefetchCache();
|
|
321
|
+
const response = await fetch(props.action ?? form.action, {
|
|
322
|
+
method: formMethod,
|
|
323
|
+
body: new FormData(form),
|
|
324
|
+
redirect: "manual"
|
|
325
|
+
});
|
|
326
|
+
if (response.type === "opaqueredirect" || response.status >= 300 && response.status < 400) {
|
|
327
|
+
const location = response.headers.get("location");
|
|
328
|
+
if (location) {
|
|
329
|
+
await navigateToClientLocation(location);
|
|
330
|
+
return;
|
|
331
|
+
}
|
|
332
|
+
window.location.href = props.action ?? form.action;
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
});
|
|
336
|
+
}
|
|
337
|
+
function parseLocation(value) {
|
|
338
|
+
const url = new URL(value, "http://pracht.local");
|
|
339
|
+
return {
|
|
340
|
+
pathname: url.pathname,
|
|
341
|
+
search: url.search
|
|
342
|
+
};
|
|
343
|
+
}
|
|
344
|
+
//#endregion
|
|
345
|
+
//#region src/types.ts
|
|
346
|
+
var PrachtHttpError = class extends Error {
|
|
347
|
+
status;
|
|
348
|
+
constructor(status, message) {
|
|
349
|
+
super(message);
|
|
350
|
+
this.name = "PrachtHttpError";
|
|
351
|
+
this.status = status;
|
|
352
|
+
}
|
|
353
|
+
};
|
|
354
|
+
//#endregion
|
|
355
|
+
export { createHref as S, applyDefaultSecurityHeaders as _, useParams as a, withDefaultSecurityHeaders as b, mergeDocumentHeaders as c, runMiddlewareChain as d, resolveDataFunctions as f, appendVaryHeader as g, resolveRegistryModule as h, useLocation as i, mergeHeadMetadata as l, resolvePageJsUrls as m, Form as n, useRevalidate as o, resolvePageCssUrls as p, Link as r, useRouteData as s, PrachtHttpError as t, redirect as u, applyHeaders as v, withRouteResponseHeaders as x, applySecurityAndRouteHeaders as y };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pracht/core",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.8.0",
|
|
4
4
|
"description": "Core runtime for Pracht, a full-stack Preact framework with per-route SSR, SSG, ISG, SPA, loaders, and API routes.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"pracht",
|
|
@@ -31,8 +31,25 @@
|
|
|
31
31
|
"exports": {
|
|
32
32
|
".": {
|
|
33
33
|
"types": "./dist/index.d.mts",
|
|
34
|
+
"browser": "./dist/browser.mjs",
|
|
34
35
|
"default": "./dist/index.mjs"
|
|
35
36
|
},
|
|
37
|
+
"./browser": {
|
|
38
|
+
"types": "./dist/browser.d.mts",
|
|
39
|
+
"default": "./dist/browser.mjs"
|
|
40
|
+
},
|
|
41
|
+
"./client": {
|
|
42
|
+
"types": "./dist/client.d.mts",
|
|
43
|
+
"default": "./dist/client.mjs"
|
|
44
|
+
},
|
|
45
|
+
"./manifest": {
|
|
46
|
+
"types": "./dist/manifest.d.mts",
|
|
47
|
+
"default": "./dist/manifest.mjs"
|
|
48
|
+
},
|
|
49
|
+
"./server": {
|
|
50
|
+
"types": "./dist/server.d.mts",
|
|
51
|
+
"default": "./dist/server.mjs"
|
|
52
|
+
},
|
|
36
53
|
"./error-overlay": {
|
|
37
54
|
"types": "./dist/error-overlay.d.mts",
|
|
38
55
|
"default": "./dist/error-overlay.mjs"
|
|
@@ -42,7 +59,7 @@
|
|
|
42
59
|
"provenance": true
|
|
43
60
|
},
|
|
44
61
|
"dependencies": {
|
|
45
|
-
"preact-suspense": "^0.
|
|
62
|
+
"preact-suspense": "^0.3.0"
|
|
46
63
|
},
|
|
47
64
|
"peerDependencies": {
|
|
48
65
|
"preact": "^10.0.0",
|