@pracht/core 0.8.0 → 0.9.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.
Files changed (38) hide show
  1. package/README.md +5 -1
  2. package/dist/app-BN3pjxtw.d.mts +24 -0
  3. package/dist/{app-BvC1uQG5.mjs → app-CGRKjZ6Z.mjs} +97 -8
  4. package/dist/app-graph-BlaCcGUZ.mjs +52 -0
  5. package/dist/app-graph-DBoDsHJ5.d.mts +38 -0
  6. package/dist/browser.d.mts +7 -6
  7. package/dist/browser.mjs +8 -7
  8. package/dist/client.d.mts +3 -3
  9. package/dist/client.mjs +3 -3
  10. package/dist/dev-404.d.mts +26 -0
  11. package/dist/dev-404.mjs +166 -0
  12. package/dist/devtools.d.mts +8 -0
  13. package/dist/devtools.mjs +156 -0
  14. package/dist/error-overlay.d.mts +38 -1
  15. package/dist/error-overlay.mjs +134 -5
  16. package/dist/index.d.mts +9 -7
  17. package/dist/index.mjs +9 -7
  18. package/dist/manifest.d.mts +2 -1
  19. package/dist/manifest.mjs +1 -1
  20. package/dist/{runtime-context-B5pREhcM.mjs → navigation-state-BjwXTeVz.mjs} +74 -2
  21. package/dist/{prefetch-B50kX9RL.mjs → prefetch-COYeuvQK.mjs} +44 -27
  22. package/dist/prefetch-api-Bf-P7XFo.d.mts +39 -0
  23. package/dist/prefetch-api-ChSCTbEB.mjs +69 -0
  24. package/dist/{prefetch-cache-DzP2Bj9H.mjs → prefetch-cache-BNLEp9H5.mjs} +12 -1
  25. package/dist/{prerender-B8_CqYwd.d.mts → prerender-BV42B9jK.d.mts} +31 -2
  26. package/dist/{prerender-D3E2H96p.mjs → prerender-C8BpANz3.mjs} +57 -15
  27. package/dist/{router-BcUmg1kB.d.mts → router-B_Aj5TtY.d.mts} +2 -2
  28. package/dist/router-E5Wh-dOR.mjs +694 -0
  29. package/dist/{runtime-context-ytVd7GmZ.d.mts → runtime-context-BYBwVA0M.d.mts} +1 -1
  30. package/dist/{runtime-middleware-aeBdgjYr.d.mts → runtime-middleware-pYubngnL.d.mts} +57 -4
  31. package/dist/server.d.mts +7 -5
  32. package/dist/server.mjs +6 -5
  33. package/dist/{app-BWriseLj.d.mts → types-B-sfVjaH.d.mts} +42 -22
  34. package/dist/{types-idmK5omD.mjs → types-DEPXdbyp.mjs} +42 -21
  35. package/package.json +9 -1
  36. package/dist/hydration-M1QzbQ1O.d.mts +0 -23
  37. package/dist/router-B7J4YYwg.mjs +0 -418
  38. /package/dist/{forwardRef-grZ6t4GS.mjs → forwardRef-B0cSkHwH.mjs} +0 -0
package/README.md CHANGED
@@ -36,13 +36,17 @@ metadata for the failure phase and matched framework files when available.
36
36
 
37
37
  - `startApp()` — client-side hydration and runtime
38
38
  - `useLocation()` — access the current pathname and search string separately
39
- - `useRouteData()` — access loader data inside a route component
39
+ - `useRouteData()` — access loader data inside a route component; pass a route
40
+ id for fully typed data after `pracht typegen`, or a `typeof loader` generic
41
+ otherwise
40
42
  - `useRevalidate()` — trigger a revalidation of the current route's data
41
43
  - `<Form>` — progressive enhancement form component
42
44
 
43
45
  ### Types
44
46
 
45
47
  - `LoaderData<T>` — infer the return type of a loader
48
+ - `RouteLoaderData<TModule, TFallbackModule?>` — infer loader data from a route
49
+ module type; used by `pracht typegen` to key loader data by route id
46
50
  - `RouteComponentProps<T>` — props type for route components
47
51
  - `LoaderArgs` — argument type passed to loaders
48
52
 
@@ -0,0 +1,24 @@
1
+ import { F as PrachtApp, G as RouteConfig, H as ResolvedPrachtApp, I as PrachtAppConfig, J as RouteId, M as ModuleRef, V as ResolvedApiRoute, X as RouteMatch, Z as RouteMeta, _ as HrefArgs, b as HrefRouteDefinition, d as GroupMeta, et as RouteParams, ft as TimeRevalidatePolicy, i as ApiRouteMatch, it as RouteSegment, ot as RouteTreeNode, q as RouteDefinition, u as GroupDefinition } from "./types-B-sfVjaH.mjs";
2
+
3
+ //#region src/app.d.ts
4
+ declare function timeRevalidate(seconds: number): TimeRevalidatePolicy;
5
+ declare function route(path: string, file: ModuleRef, meta?: RouteMeta): RouteDefinition;
6
+ declare function route(path: string, config: RouteConfig): RouteDefinition;
7
+ declare function group(meta: GroupMeta, routes: RouteTreeNode[]): GroupDefinition;
8
+ declare function defineApp(config: PrachtAppConfig): PrachtApp;
9
+ declare function resolveApp(app: PrachtApp): ResolvedPrachtApp;
10
+ declare function matchAppRoute(app: PrachtApp | ResolvedPrachtApp, pathname: string): RouteMatch | undefined;
11
+ declare function buildPathFromSegments(segments: readonly RouteSegment[], params: RouteParams): string;
12
+ declare function buildHref<TRoute extends RouteId>(routes: readonly HrefRouteDefinition[], routeId: TRoute, ...args: HrefArgs<TRoute>): string;
13
+ /**
14
+ * Convert a list of file paths from `import.meta.glob` into resolved API routes.
15
+ *
16
+ * Example: `"/src/api/health.ts"` → path `/api/health`
17
+ * `"/src/api/users/[id].ts"` → path `/api/users/:id`
18
+ * `"/src/api/files/[...path].ts"` → path `/api/files/*`
19
+ * `"/src/api/index.ts"` → path `/api`
20
+ */
21
+ declare function resolveApiRoutes(files: string[], apiDir?: string): ResolvedApiRoute[];
22
+ declare function matchApiRoute(apiRoutes: ResolvedApiRoute[], pathname: string): ApiRouteMatch | undefined;
23
+ //#endregion
24
+ export { matchApiRoute as a, resolveApp as c, group as i, route as l, buildPathFromSegments as n, matchAppRoute as o, defineApp as r, resolveApiRoutes as s, buildHref as t, timeRevalidate as u };
@@ -1,3 +1,61 @@
1
+ //#region src/name-suggestions.ts
2
+ /**
3
+ * "Did you mean" helpers for manifest wiring errors.
4
+ *
5
+ * When a route references an unknown shell, middleware, or route id, the
6
+ * error message includes the closest registered name plus the full list of
7
+ * registered names. A tiny internal edit-distance implementation keeps this
8
+ * dependency-free.
9
+ */
10
+ /** Classic Levenshtein edit distance using two rolling rows. */
11
+ function levenshteinDistance(a, b) {
12
+ if (a === b) return 0;
13
+ if (a.length === 0) return b.length;
14
+ if (b.length === 0) return a.length;
15
+ let previous = Array.from({ length: b.length + 1 }, (_, index) => index);
16
+ let current = Array.from({ length: b.length + 1 });
17
+ for (let i = 1; i <= a.length; i += 1) {
18
+ current[0] = i;
19
+ for (let j = 1; j <= b.length; j += 1) {
20
+ const substitution = previous[j - 1] + (a[i - 1] === b[j - 1] ? 0 : 1);
21
+ current[j] = Math.min(previous[j] + 1, current[j - 1] + 1, substitution);
22
+ }
23
+ [previous, current] = [current, previous];
24
+ }
25
+ return previous[b.length];
26
+ }
27
+ /**
28
+ * Return the candidate closest to `input`, or `undefined` when nothing is
29
+ * close enough to be a plausible typo. The threshold scales with the input
30
+ * length (roughly a third of it, minimum 2 edits).
31
+ */
32
+ function closestName(input, candidates) {
33
+ let best;
34
+ let bestDistance = Infinity;
35
+ for (const candidate of candidates) {
36
+ const distance = levenshteinDistance(input.toLowerCase(), candidate.toLowerCase());
37
+ if (distance < bestDistance) {
38
+ bestDistance = distance;
39
+ best = candidate;
40
+ }
41
+ }
42
+ const threshold = Math.max(2, Math.floor(input.length / 3));
43
+ return bestDistance <= threshold ? best : void 0;
44
+ }
45
+ /**
46
+ * Format an enriched wiring error, e.g.
47
+ * `Unknown shell "pubic" for route "/". Did you mean "public"? Registered shells: public, app.`
48
+ */
49
+ function formatUnknownNameError(options) {
50
+ const { kind, name, registered, context } = options;
51
+ const kindPlural = options.kindPlural ?? `${kind}s`;
52
+ let message = `Unknown ${kind} "${name}"${context ? ` for ${context}` : ""}.`;
53
+ const suggestion = closestName(name, registered);
54
+ if (suggestion) message += ` Did you mean "${suggestion}"?`;
55
+ message += registered.length > 0 ? ` Registered ${kindPlural}: ${registered.join(", ")}.` : ` No ${kindPlural} are registered in defineApp().`;
56
+ return message;
57
+ }
58
+ //#endregion
1
59
  //#region src/app.ts
2
60
  function timeRevalidate(seconds) {
3
61
  if (!Number.isInteger(seconds) || seconds <= 0) throw new Error("timeRevalidate expects a positive integer number of seconds.");
@@ -26,7 +84,7 @@ function route(path, fileOrConfig, meta = {}) {
26
84
  function resolveModuleRef(ref) {
27
85
  if (ref === void 0) return void 0;
28
86
  if (typeof ref === "string") return ref;
29
- return "";
87
+ throw new Error("Invalid ModuleRef: expected a string path, but received a function at runtime. Use a plain string path (e.g. \"./routes/home.tsx\"), or ensure the Vite plugin rewrites inline `() => import(\"./file\")` refs in the app manifest.");
30
88
  }
31
89
  function group(meta, routes) {
32
90
  return {
@@ -40,7 +98,8 @@ function defineApp(config) {
40
98
  shells: resolveModuleRefRecord(config.shells ?? {}),
41
99
  middleware: resolveModuleRefRecord(config.middleware ?? {}),
42
100
  api: config.api ?? {},
43
- routes: config.routes
101
+ routes: config.routes,
102
+ viewTransitions: config.viewTransitions
44
103
  };
45
104
  }
46
105
  function resolveModuleRefRecord(record) {
@@ -54,13 +113,21 @@ function resolveApp(app) {
54
113
  pathPrefix: "/",
55
114
  middleware: []
56
115
  };
116
+ for (const name of app.api?.middleware ?? []) if (!hasOwnEntry(app.middleware, name)) throw new Error(formatUnknownNameError({
117
+ kind: "middleware",
118
+ kindPlural: "middleware",
119
+ name,
120
+ registered: Object.keys(app.middleware),
121
+ context: "api routes"
122
+ }));
57
123
  for (const node of app.routes) flattenRouteNode(app, node, inherited, routes);
58
124
  return {
59
125
  shells: app.shells,
60
126
  middleware: app.middleware,
61
127
  api: app.api,
62
128
  routes,
63
- apiRoutes: []
129
+ apiRoutes: [],
130
+ viewTransitions: app.viewTransitions
64
131
  };
65
132
  }
66
133
  function matchAppRoute(app, pathname) {
@@ -90,6 +157,12 @@ function flattenRouteNode(app, node, inherited, routes) {
90
157
  const fullPath = mergeRoutePaths(inherited.pathPrefix, node.path);
91
158
  const shell = node.shell ?? inherited.shell;
92
159
  const middleware = [...inherited.middleware, ...node.middleware ?? []];
160
+ if (shell !== void 0 && !hasOwnEntry(app.shells, shell)) throw new Error(formatUnknownNameError({
161
+ kind: "shell",
162
+ name: shell,
163
+ registered: Object.keys(app.shells),
164
+ context: `route "${fullPath}"`
165
+ }));
93
166
  routes.push({
94
167
  id: node.id ?? createRouteId(fullPath),
95
168
  path: fullPath,
@@ -97,17 +170,28 @@ function flattenRouteNode(app, node, inherited, routes) {
97
170
  loaderFile: node.loaderFile,
98
171
  hasLoader: node.loaderFile ? true : node.hasLoader,
99
172
  shell,
100
- shellFile: shell ? app.shells[shell] : void 0,
173
+ shellFile: shell !== void 0 ? app.shells[shell] : void 0,
101
174
  render: node.render ?? inherited.render,
102
175
  middleware,
103
- middlewareFiles: middleware.flatMap((name) => {
104
- const middlewareFile = app.middleware[name];
105
- return middlewareFile ? [middlewareFile] : [];
176
+ middlewareFiles: middleware.map((name) => {
177
+ if (!hasOwnEntry(app.middleware, name)) throw new Error(formatUnknownNameError({
178
+ kind: "middleware",
179
+ kindPlural: "middleware",
180
+ name,
181
+ registered: Object.keys(app.middleware),
182
+ context: `route "${fullPath}"`
183
+ }));
184
+ return app.middleware[name];
106
185
  }),
186
+ prefetch: node.prefetch,
107
187
  revalidate: node.revalidate,
108
188
  segments: parseRouteSegments(fullPath)
109
189
  });
110
190
  }
191
+ /** `in` would also match `Object.prototype` keys such as `constructor`. */
192
+ function hasOwnEntry(record, name) {
193
+ return Object.prototype.hasOwnProperty.call(record, name);
194
+ }
111
195
  function isResolvedApp(app) {
112
196
  return app.routes.length === 0 || "segments" in app.routes[0];
113
197
  }
@@ -192,7 +276,12 @@ function buildHref(routes, routeId, ...args) {
192
276
  }
193
277
  function buildHrefUntyped(routes, routeId, options = {}) {
194
278
  const route = routes.find((candidate) => candidate.id === routeId);
195
- if (!route) throw new Error(`Unknown pracht route id: ${routeId}.`);
279
+ if (!route) throw new Error(formatUnknownNameError({
280
+ kind: "pracht route id",
281
+ kindPlural: "route ids",
282
+ name: routeId,
283
+ registered: routes.flatMap((candidate) => candidate.id ? [candidate.id] : [])
284
+ }));
196
285
  const segments = route.segments ?? parseRouteSegments(route.path);
197
286
  return `${buildPathFromSegments(segments, normalizeHrefParams(segments, options.params ?? {}))}${serializeSearch(options.search)}${serializeHash(options.hash)}`;
198
287
  }
@@ -0,0 +1,52 @@
1
+ //#region src/app-graph.ts
2
+ const API_METHOD_ORDER = [
3
+ "GET",
4
+ "POST",
5
+ "PUT",
6
+ "PATCH",
7
+ "DELETE",
8
+ "HEAD",
9
+ "OPTIONS"
10
+ ];
11
+ function serializeAppRoutes(routes) {
12
+ return routes.map((route) => ({
13
+ file: route.file,
14
+ id: route.id ?? "",
15
+ loaderFile: route.loaderFile ?? null,
16
+ middleware: route.middleware,
17
+ path: route.path,
18
+ render: route.render ?? null,
19
+ revalidate: route.revalidate ?? null,
20
+ shell: route.shell ?? null,
21
+ shellFile: route.shellFile ?? null
22
+ }));
23
+ }
24
+ function serializeApiRoutes(apiRoutes, access) {
25
+ return Promise.all(apiRoutes.map(async (route) => ({
26
+ file: route.file,
27
+ methods: await detectApiMethods(route.file, access),
28
+ path: route.path
29
+ })));
30
+ }
31
+ async function buildAppGraph(options) {
32
+ return {
33
+ api: await serializeApiRoutes(options.apiRoutes ?? [], options),
34
+ routes: serializeAppRoutes(options.app.routes)
35
+ };
36
+ }
37
+ async function detectApiMethods(file, access) {
38
+ try {
39
+ const module = await access.loadModule(file);
40
+ return API_METHOD_ORDER.filter((method) => typeof module[method] === "function");
41
+ } catch {
42
+ let source;
43
+ try {
44
+ source = access.readSource(file);
45
+ } catch {
46
+ return [];
47
+ }
48
+ return API_METHOD_ORDER.filter((method) => new RegExp(`export\\s+(?:async\\s+)?(?:function|const|let|var)\\s+${method}\\b`).test(source));
49
+ }
50
+ }
51
+ //#endregion
52
+ export { serializeAppRoutes as i, detectApiMethods as n, serializeApiRoutes as r, buildAppGraph as t };
@@ -0,0 +1,38 @@
1
+ import { H as ResolvedPrachtApp, U as ResolvedRoute, V as ResolvedApiRoute, x as HttpMethod } from "./types-B-sfVjaH.mjs";
2
+
3
+ //#region src/app-graph.d.ts
4
+ interface AppGraphRoute {
5
+ file: string;
6
+ id: string;
7
+ loaderFile: string | null;
8
+ middleware: string[];
9
+ path: string;
10
+ render: string | null;
11
+ revalidate: unknown;
12
+ shell: string | null;
13
+ shellFile: string | null;
14
+ }
15
+ interface AppGraphApiRoute {
16
+ file: string;
17
+ methods: string[];
18
+ path: string;
19
+ }
20
+ interface AppGraph {
21
+ api: AppGraphApiRoute[];
22
+ routes: AppGraphRoute[];
23
+ }
24
+ interface AppGraphModuleAccess {
25
+ /** Import an app module by its app-relative file path (e.g. Vite's `ssrLoadModule`). */
26
+ loadModule: (file: string) => Promise<Record<string, unknown>>;
27
+ /** Read an app module's source text — fallback method detection when importing fails. */
28
+ readSource: (file: string) => string;
29
+ }
30
+ declare function serializeAppRoutes(routes: readonly ResolvedRoute[]): AppGraphRoute[];
31
+ declare function serializeApiRoutes(apiRoutes: readonly ResolvedApiRoute[], access: AppGraphModuleAccess): Promise<AppGraphApiRoute[]>;
32
+ declare function buildAppGraph(options: {
33
+ apiRoutes?: readonly ResolvedApiRoute[];
34
+ app: ResolvedPrachtApp;
35
+ } & AppGraphModuleAccess): Promise<AppGraph>;
36
+ declare function detectApiMethods(file: string, access: AppGraphModuleAccess): Promise<HttpMethod[]>;
37
+ //#endregion
38
+ export { buildAppGraph as a, serializeAppRoutes as c, AppGraphRoute as i, AppGraphApiRoute as n, detectApiMethods as o, AppGraphModuleAccess as r, serializeApiRoutes as s, AppGraph as t };
@@ -1,7 +1,8 @@
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, 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, 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 { a as parseSafeNavigationUrl, c as Link, d as useLocation, f as useParams, h as createHref, i as fetchPrachtRouteState, l as LinkProps, m as useRouteData, n as redirect, o as Form, p as useRevalidate, r as RouteStateResult, s as FormProps, t as RedirectOptions, u as Location } from "./runtime-middleware-aeBdgjYr.mjs";
3
- import { n as forwardRef, t as useIsHydrated } from "./hydration-M1QzbQ1O.mjs";
4
- import { a as startApp, c as SerializedRouteError, i as readHydrationState, n as PrachtRuntimeProvider, r as StartAppOptions, t as PrachtHydrationState } from "./runtime-context-ytVd7GmZ.mjs";
5
- import { i as useNavigate, n as NavigateFn, r as initClientRouter, t as InitClientRouterOptions } from "./router-BcUmg1kB.mjs";
1
+ import { $ as RouteParamInput, A as MiddlewareNext, B as RenderMode, C as LoaderArgs, D as MiddlewareArgs, F as PrachtApp, G as RouteConfig, H as ResolvedPrachtApp, I as PrachtAppConfig, J as RouteId, K as RouteDataFor, L as PrachtHttpError, M as ModuleRef, N as ModuleRegistry, O as MiddlewareFn, P as NavigateOptions, Q as RouteModule, R as PrefetchStrategy, S as LinkPrefetchStrategy, T as LoaderFn, U as ResolvedRoute, V as ResolvedApiRoute, W as RouteComponentProps, X as RouteMatch, Y as RouteLoaderData, Z as RouteMeta, _ as HrefArgs, a as ApiRouteModule, at as RouteTarget, b as HrefRouteDefinition, c as DataModule, ct as SearchParamValue, d as GroupMeta, dt as ShellProps, et as RouteParams, f as HeadArgs, ft as TimeRevalidatePolicy, g as HeadersArgs, h as HeadScriptDescriptor, i as ApiRouteMatch, j as ModuleImporter, k as MiddlewareModule, l as ErrorBoundaryProps, lt as SearchParamsInput, m as HeadMetadata, n as ApiRouteArgs, nt as RouteRevalidate, o as BaseRouteArgs, ot as RouteTreeNode, p as HeadAttributes, q as RouteDefinition, r as ApiRouteHandler, rt as RouteSearchFor, s as BuildHrefOptions, st as SearchParamPrimitive, t as ApiConfig, tt as RouteParamsFor, u as GroupDefinition, ut as ShellModule, v as HrefFn, w as LoaderData, x as HttpMethod, y as HrefOptions, z as Register } from "./types-B-sfVjaH.mjs";
2
+ import { c as resolveApp, i as group, l as route, n as buildPathFromSegments, o as matchAppRoute, r as defineApp, t as buildHref, u as timeRevalidate } from "./app-BN3pjxtw.mjs";
3
+ import { _ as NavigationLocation, a as parseSafeNavigationUrl, c as Link, d as useLocation, f as useNavigation, g as Navigation, h as useRouteData, i as fetchPrachtRouteState, l as LinkProps, m as useRevalidate, n as redirect, o as Form, p as useParams, r as RouteStateResult, s as FormProps, t as RedirectOptions, u as Location, v as createHref } from "./runtime-middleware-pYubngnL.mjs";
4
+ import { i as forwardRef, n as prefetch, r as useIsHydrated, t as PrefetchFn } from "./prefetch-api-Bf-P7XFo.mjs";
5
+ import { a as startApp, c as SerializedRouteError, i as readHydrationState, n as PrachtRuntimeProvider, r as StartAppOptions, t as PrachtHydrationState } from "./runtime-context-BYBwVA0M.mjs";
6
+ import { i as useNavigate, n as NavigateFn, r as initClientRouter, t as InitClientRouterOptions } from "./router-B_Aj5TtY.mjs";
6
7
  import { Suspense, lazy } from "preact-suspense";
7
- export { type ApiConfig, type ApiRouteArgs, type ApiRouteHandler, type ApiRouteMatch, type ApiRouteModule, type BaseRouteArgs, type BuildHrefOptions, type DataModule, type ErrorBoundaryProps, Form, type FormProps, type GroupDefinition, type GroupMeta, type HeadArgs, type HeadAttributes, type HeadMetadata, type HeadScriptDescriptor, type HeadersArgs, type HrefArgs, type HrefFn, type HrefOptions, type HrefRouteDefinition, type HttpMethod, type InitClientRouterOptions, Link, type LinkProps, type LoaderArgs, type LoaderData, type LoaderFn, type Location, type MiddlewareArgs, type MiddlewareFn, type MiddlewareModule, type MiddlewareNext, type ModuleImporter, type ModuleRef, type ModuleRegistry, type NavigateFn, type NavigateOptions, type PrachtApp, type PrachtAppConfig, PrachtHttpError, type PrachtHydrationState, PrachtRuntimeProvider, type PrefetchStrategy, 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 RouteStateResult, type RouteTarget, type RouteTreeNode, type SearchParamPrimitive, type SearchParamValue, type SearchParamsInput, type SerializedRouteError, type ShellModule, type ShellProps, type StartAppOptions, Suspense, type TimeRevalidatePolicy, buildHref, buildPathFromSegments, createHref, defineApp, fetchPrachtRouteState, forwardRef, group, initClientRouter, lazy, matchAppRoute, parseSafeNavigationUrl, readHydrationState, redirect, resolveApp, route, startApp, timeRevalidate, useIsHydrated, useLocation, useNavigate, useParams, useRevalidate, useRouteData };
8
+ export { type ApiConfig, type ApiRouteArgs, type ApiRouteHandler, type ApiRouteMatch, type ApiRouteModule, type BaseRouteArgs, type BuildHrefOptions, type DataModule, type ErrorBoundaryProps, Form, type FormProps, type GroupDefinition, type GroupMeta, type HeadArgs, type HeadAttributes, type HeadMetadata, type HeadScriptDescriptor, type HeadersArgs, type HrefArgs, type HrefFn, type HrefOptions, type HrefRouteDefinition, type HttpMethod, type InitClientRouterOptions, Link, type LinkPrefetchStrategy, type LinkProps, type LoaderArgs, type LoaderData, type LoaderFn, type Location, type MiddlewareArgs, type MiddlewareFn, type MiddlewareModule, type MiddlewareNext, type ModuleImporter, type ModuleRef, type ModuleRegistry, type NavigateFn, type NavigateOptions, type Navigation, type NavigationLocation, type PrachtApp, type PrachtAppConfig, PrachtHttpError, type PrachtHydrationState, PrachtRuntimeProvider, type PrefetchFn, type PrefetchStrategy, type RedirectOptions, type Register, type RenderMode, type ResolvedApiRoute, type ResolvedPrachtApp, type ResolvedRoute, type RouteComponentProps, type RouteConfig, type RouteDataFor, type RouteDefinition, type RouteId, type RouteLoaderData, type RouteMatch, type RouteMeta, type RouteModule, type RouteParamInput, type RouteParams, type RouteParamsFor, type RouteRevalidate, type RouteSearchFor, type RouteStateResult, type RouteTarget, type RouteTreeNode, type SearchParamPrimitive, type SearchParamValue, type SearchParamsInput, type SerializedRouteError, type ShellModule, type ShellProps, type StartAppOptions, Suspense, type TimeRevalidatePolicy, buildHref, buildPathFromSegments, createHref, defineApp, fetchPrachtRouteState, forwardRef, group, initClientRouter, lazy, matchAppRoute, parseSafeNavigationUrl, prefetch, readHydrationState, redirect, resolveApp, route, startApp, timeRevalidate, useIsHydrated, useLocation, useNavigate, useNavigation, useParams, useRevalidate, useRouteData };
package/dist/browser.mjs CHANGED
@@ -1,8 +1,9 @@
1
- import { c as resolveApp, i as group, l as route, n as buildPathFromSegments, o as matchAppRoute, r as defineApp, t as buildHref, u as timeRevalidate } from "./app-BvC1uQG5.mjs";
2
- import { S as createHref, a as useParams, i as useLocation, n as Form, o as useRevalidate, r as Link, s as useRouteData, t as PrachtHttpError, u as redirect } from "./types-idmK5omD.mjs";
3
- import { t as forwardRef } from "./forwardRef-grZ6t4GS.mjs";
4
- import { n as useNavigate, r as useIsHydrated, t as initClientRouter } from "./router-B7J4YYwg.mjs";
5
- import { l as parseSafeNavigationUrl, s as fetchPrachtRouteState } from "./prefetch-cache-DzP2Bj9H.mjs";
6
- import { i as startApp, r as readHydrationState, t as PrachtRuntimeProvider } from "./runtime-context-B5pREhcM.mjs";
1
+ import { c as resolveApp, i as group, l as route, n as buildPathFromSegments, o as matchAppRoute, r as defineApp, t as buildHref, u as timeRevalidate } from "./app-CGRKjZ6Z.mjs";
2
+ import { C as createHref, a as useNavigation, c as useRouteData, d as redirect, i as useLocation, n as Form, o as useParams, r as Link, s as useRevalidate, t as PrachtHttpError } from "./types-DEPXdbyp.mjs";
3
+ import { t as forwardRef } from "./forwardRef-B0cSkHwH.mjs";
4
+ import { n as useNavigate, r as useIsHydrated, t as initClientRouter } from "./router-E5Wh-dOR.mjs";
5
+ import { c as fetchPrachtRouteState, u as parseSafeNavigationUrl } from "./prefetch-cache-BNLEp9H5.mjs";
6
+ import { l as readHydrationState, s as PrachtRuntimeProvider, u as startApp } from "./navigation-state-BjwXTeVz.mjs";
7
+ import { t as prefetch } from "./prefetch-api-ChSCTbEB.mjs";
7
8
  import { Suspense, lazy } from "preact-suspense";
8
- export { Form, Link, PrachtHttpError, PrachtRuntimeProvider, Suspense, buildHref, buildPathFromSegments, createHref, defineApp, fetchPrachtRouteState, forwardRef, group, initClientRouter, lazy, matchAppRoute, parseSafeNavigationUrl, readHydrationState, redirect, resolveApp, route, startApp, timeRevalidate, useIsHydrated, useLocation, useNavigate, useParams, useRevalidate, useRouteData };
9
+ export { Form, Link, PrachtHttpError, PrachtRuntimeProvider, Suspense, buildHref, buildPathFromSegments, createHref, defineApp, fetchPrachtRouteState, forwardRef, group, initClientRouter, lazy, matchAppRoute, parseSafeNavigationUrl, prefetch, readHydrationState, redirect, resolveApp, route, startApp, timeRevalidate, useIsHydrated, useLocation, useNavigate, useNavigation, useParams, useRevalidate, useRouteData };
package/dist/client.d.mts CHANGED
@@ -1,4 +1,4 @@
1
- import { c as resolveApp } from "./app-BWriseLj.mjs";
2
- import { i as readHydrationState, t as PrachtHydrationState } from "./runtime-context-ytVd7GmZ.mjs";
3
- import { n as NavigateFn, r as initClientRouter, t as InitClientRouterOptions } from "./router-BcUmg1kB.mjs";
1
+ import { c as resolveApp } from "./app-BN3pjxtw.mjs";
2
+ import { i as readHydrationState, t as PrachtHydrationState } from "./runtime-context-BYBwVA0M.mjs";
3
+ import { n as NavigateFn, r as initClientRouter, t as InitClientRouterOptions } from "./router-B_Aj5TtY.mjs";
4
4
  export { type InitClientRouterOptions, type NavigateFn, type PrachtHydrationState, initClientRouter, readHydrationState, resolveApp };
package/dist/client.mjs CHANGED
@@ -1,4 +1,4 @@
1
- import { c as resolveApp } from "./app-BvC1uQG5.mjs";
2
- import { t as initClientRouter } from "./router-B7J4YYwg.mjs";
3
- import { r as readHydrationState } from "./runtime-context-B5pREhcM.mjs";
1
+ import { c as resolveApp } from "./app-CGRKjZ6Z.mjs";
2
+ import { t as initClientRouter } from "./router-E5Wh-dOR.mjs";
3
+ import { l as readHydrationState } from "./navigation-state-BjwXTeVz.mjs";
4
4
  export { initClientRouter, readHydrationState, resolveApp };
@@ -0,0 +1,26 @@
1
+ //#region src/dev-404.d.ts
2
+ /**
3
+ * Self-contained 404 page for pracht dev mode.
4
+ *
5
+ * Returns a standalone HTML document with inline styles and scripts.
6
+ * Not a Preact component — rendered by the dev middleware when a request
7
+ * matches no page route and no API route. Loaded exclusively through the
8
+ * dev server (`@pracht/core/dev-404`) and never reachable from production
9
+ * code paths.
10
+ */
11
+ interface DevNotFoundRoute {
12
+ path: string;
13
+ render?: string | null;
14
+ }
15
+ interface DevNotFoundApiRoute {
16
+ path: string;
17
+ methods?: string[];
18
+ }
19
+ interface DevNotFoundOptions {
20
+ requestedPath: string;
21
+ routes: DevNotFoundRoute[];
22
+ apiRoutes?: DevNotFoundApiRoute[];
23
+ }
24
+ declare function buildDevNotFoundHtml(options: DevNotFoundOptions): string;
25
+ //#endregion
26
+ export { DevNotFoundApiRoute, DevNotFoundOptions, DevNotFoundRoute, buildDevNotFoundHtml };
@@ -0,0 +1,166 @@
1
+ //#region src/dev-404.ts
2
+ const DYNAMIC_SEGMENT = /[:*]/;
3
+ function buildDevNotFoundHtml(options) {
4
+ const { requestedPath, routes, apiRoutes = [] } = options;
5
+ const routeRows = routes.map((route) => {
6
+ const mode = route.render ?? "ssr";
7
+ return `<tr><td>${!DYNAMIC_SEGMENT.test(route.path) ? `<a class="path" href="${escapeHtml(route.path)}">${escapeHtml(route.path)}</a>` : `<span class="path dynamic">${escapeHtml(route.path)}</span>`}</td><td><span class="mode mode-${escapeHtml(mode)}">${escapeHtml(mode)}</span></td></tr>`;
8
+ }).join("\n ");
9
+ const routesHtml = routes.length > 0 ? `<table class="routes">\n ${routeRows}\n </table>` : `<p class="empty">No page routes are registered.</p>`;
10
+ const apiRows = apiRoutes.map((route) => {
11
+ const methods = route.methods?.length ? route.methods.join(", ") : "";
12
+ return `<tr><td><span class="path">${escapeHtml(route.path)}</span></td><td><span class="methods">${escapeHtml(methods)}</span></td></tr>`;
13
+ }).join("\n ");
14
+ const apiHtml = apiRoutes.length > 0 ? `<h2>API routes</h2>\n <table class="routes">\n ${apiRows}\n </table>` : "";
15
+ return `<!DOCTYPE html>
16
+ <html>
17
+ <head>
18
+ <meta charset="utf-8">
19
+ <title>404 — pracht dev</title>
20
+ <style>
21
+ * { margin: 0; padding: 0; box-sizing: border-box; }
22
+ body {
23
+ font-family: ui-monospace, "Cascadia Code", "Source Code Pro", Menlo, Consolas, monospace;
24
+ background: #1a1a2e;
25
+ color: #e0e0e0;
26
+ padding: 32px;
27
+ line-height: 1.5;
28
+ }
29
+ .overlay {
30
+ max-width: 900px;
31
+ margin: 0 auto;
32
+ }
33
+ .header {
34
+ display: flex;
35
+ align-items: center;
36
+ gap: 12px;
37
+ margin-bottom: 24px;
38
+ padding-bottom: 16px;
39
+ border-bottom: 1px solid #333;
40
+ }
41
+ .badge {
42
+ background: #f39c12;
43
+ color: #1a1a2e;
44
+ font-size: 11px;
45
+ font-weight: 700;
46
+ text-transform: uppercase;
47
+ letter-spacing: 0.05em;
48
+ padding: 4px 10px;
49
+ border-radius: 4px;
50
+ }
51
+ .title {
52
+ font-size: 14px;
53
+ color: #888;
54
+ }
55
+ .message {
56
+ font-size: 18px;
57
+ font-weight: 600;
58
+ color: #ffd166;
59
+ margin-bottom: 8px;
60
+ word-break: break-word;
61
+ }
62
+ .requested {
63
+ color: #a0c4ff;
64
+ }
65
+ .sub {
66
+ font-size: 13px;
67
+ color: #888;
68
+ margin-bottom: 28px;
69
+ }
70
+ h2 {
71
+ font-size: 13px;
72
+ font-weight: 600;
73
+ text-transform: uppercase;
74
+ letter-spacing: 0.08em;
75
+ color: #888;
76
+ margin: 28px 0 10px;
77
+ }
78
+ .routes {
79
+ width: 100%;
80
+ border-collapse: collapse;
81
+ background: #16162a;
82
+ border: 1px solid #333;
83
+ border-radius: 8px;
84
+ overflow: hidden;
85
+ font-size: 13px;
86
+ }
87
+ .routes td {
88
+ padding: 8px 16px;
89
+ border-bottom: 1px solid #262640;
90
+ }
91
+ .routes tr:last-child td {
92
+ border-bottom: none;
93
+ }
94
+ .routes td:last-child {
95
+ text-align: right;
96
+ width: 1%;
97
+ white-space: nowrap;
98
+ }
99
+ a.path {
100
+ color: #a0c4ff;
101
+ text-decoration: none;
102
+ }
103
+ a.path:hover {
104
+ text-decoration: underline;
105
+ }
106
+ .path.dynamic {
107
+ color: #ccc;
108
+ }
109
+ .mode {
110
+ font-size: 11px;
111
+ font-weight: 700;
112
+ text-transform: uppercase;
113
+ letter-spacing: 0.05em;
114
+ padding: 2px 8px;
115
+ border-radius: 4px;
116
+ background: #262640;
117
+ color: #a0c4ff;
118
+ }
119
+ .mode-ssg { color: #7bd88f; }
120
+ .mode-isg { color: #5fd7d7; }
121
+ .mode-ssr { color: #ffd166; }
122
+ .mode-spa { color: #d7a0ff; }
123
+ .methods {
124
+ font-size: 12px;
125
+ color: #7bd88f;
126
+ }
127
+ .empty {
128
+ font-size: 13px;
129
+ color: #888;
130
+ }
131
+ .hint {
132
+ margin-top: 24px;
133
+ font-size: 12px;
134
+ color: #666;
135
+ }
136
+ </style>
137
+ </head>
138
+ <body>
139
+ <div class="overlay">
140
+ <div class="header">
141
+ <span class="badge">404</span>
142
+ <span class="title">pracht dev</span>
143
+ </div>
144
+ <div class="message">No route matches <span class="requested">${escapeHtml(requestedPath)}</span></div>
145
+ <div class="sub">This page is only shown in development — production serves your app's own 404 response.</div>
146
+ <h2>Page routes</h2>
147
+ ${routesHtml}
148
+ ${apiHtml}
149
+ <div class="hint">Add the route to your app manifest and save — the page will reload automatically.</div>
150
+ </div>
151
+ <script>
152
+ // Auto-reload when Vite triggers a full reload (e.g. a route was added)
153
+ if (import.meta.hot) {
154
+ import.meta.hot.on("vite:beforeFullReload", function () {
155
+ window.location.reload();
156
+ });
157
+ }
158
+ <\/script>
159
+ </body>
160
+ </html>`;
161
+ }
162
+ function escapeHtml(str) {
163
+ return str.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
164
+ }
165
+ //#endregion
166
+ export { buildDevNotFoundHtml };
@@ -0,0 +1,8 @@
1
+ import { a as buildAppGraph, c as serializeAppRoutes, i as AppGraphRoute, n as AppGraphApiRoute, o as detectApiMethods, r as AppGraphModuleAccess, s as serializeApiRoutes, t as AppGraph } from "./app-graph-DBoDsHJ5.mjs";
2
+
3
+ //#region src/devtools.d.ts
4
+ declare const DEVTOOLS_PATH = "/_pracht";
5
+ declare const DEVTOOLS_JSON_PATH = "/_pracht.json";
6
+ declare function buildDevtoolsHtml(graph: AppGraph): string;
7
+ //#endregion
8
+ export { type AppGraph, type AppGraphApiRoute, type AppGraphModuleAccess, type AppGraphRoute, DEVTOOLS_JSON_PATH, DEVTOOLS_PATH, buildAppGraph, buildDevtoolsHtml, detectApiMethods, serializeApiRoutes, serializeAppRoutes };