@pracht/core 0.8.0 → 0.8.1

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.
@@ -26,7 +26,7 @@ function route(path, fileOrConfig, meta = {}) {
26
26
  function resolveModuleRef(ref) {
27
27
  if (ref === void 0) return void 0;
28
28
  if (typeof ref === "string") return ref;
29
- return "";
29
+ 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
30
  }
31
31
  function group(meta, routes) {
32
32
  return {
package/dist/browser.mjs CHANGED
@@ -1,7 +1,7 @@
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";
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-w-P1wf5T.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-DQv2poC5.mjs";
3
3
  import { t as forwardRef } from "./forwardRef-grZ6t4GS.mjs";
4
- import { n as useNavigate, r as useIsHydrated, t as initClientRouter } from "./router-B7J4YYwg.mjs";
4
+ import { n as useNavigate, r as useIsHydrated, t as initClientRouter } from "./router-BkkyNjqB.mjs";
5
5
  import { l as parseSafeNavigationUrl, s as fetchPrachtRouteState } from "./prefetch-cache-DzP2Bj9H.mjs";
6
6
  import { i as startApp, r as readHydrationState, t as PrachtRuntimeProvider } from "./runtime-context-B5pREhcM.mjs";
7
7
  import { Suspense, lazy } from "preact-suspense";
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";
1
+ import { c as resolveApp } from "./app-w-P1wf5T.mjs";
2
+ import { t as initClientRouter } from "./router-BkkyNjqB.mjs";
3
3
  import { r as readHydrationState } from "./runtime-context-B5pREhcM.mjs";
4
4
  export { initClientRouter, readHydrationState, resolveApp };
package/dist/index.mjs CHANGED
@@ -1,8 +1,8 @@
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, 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";
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-w-P1wf5T.mjs";
2
+ import { S as createHref, _ as applyDefaultSecurityHeaders, 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-DQv2poC5.mjs";
3
3
  import { t as forwardRef } from "./forwardRef-grZ6t4GS.mjs";
4
- import { n as useNavigate, r as useIsHydrated, t as initClientRouter } from "./router-B7J4YYwg.mjs";
4
+ import { n as useNavigate, r as useIsHydrated, t as initClientRouter } from "./router-BkkyNjqB.mjs";
5
5
  import { i as startApp, r as readHydrationState, t as PrachtRuntimeProvider } from "./runtime-context-B5pREhcM.mjs";
6
- import { n as handlePrachtRequest, t as prerenderApp } from "./prerender-D3E2H96p.mjs";
6
+ import { n as handlePrachtRequest, t as prerenderApp } from "./prerender-CoMyuJKm.mjs";
7
7
  import { Suspense, lazy } from "preact-suspense";
8
8
  export { Form, Link, PrachtHttpError, PrachtRuntimeProvider, Suspense, applyDefaultSecurityHeaders, buildHref, buildPathFromSegments, createHref, defineApp, forwardRef, group, handlePrachtRequest, initClientRouter, lazy, matchApiRoute, matchAppRoute, prerenderApp, readHydrationState, redirect, resolveApiRoutes, resolveApp, route, startApp, timeRevalidate, useIsHydrated, useLocation, useNavigate, useParams, useRevalidate, useRouteData };
package/dist/manifest.mjs CHANGED
@@ -1,2 +1,2 @@
1
- import { i as group, l as route, r as defineApp, u as timeRevalidate } from "./app-BvC1uQG5.mjs";
1
+ import { i as group, l as route, r as defineApp, u as timeRevalidate } from "./app-w-P1wf5T.mjs";
2
2
  export { defineApp, group, route, timeRevalidate };
@@ -1,4 +1,4 @@
1
- import { o as matchAppRoute } from "./app-BvC1uQG5.mjs";
1
+ import { o as matchAppRoute } from "./app-w-P1wf5T.mjs";
2
2
  import { a as trimMapToSize, i as getCachedRouteState, n as cacheRouteState, s as fetchPrachtRouteState, t as EMPTY_ROUTE_STATE_PROMISE, u as routeNeedsServerFetch } from "./prefetch-cache-DzP2Bj9H.mjs";
3
3
  //#region src/prefetch.ts
4
4
  const MAX_MATCH_CACHE_ENTRIES = 250;
@@ -1,5 +1,5 @@
1
- import { a as matchApiRoute, c as resolveApp, n as buildPathFromSegments, o as matchAppRoute } from "./app-BvC1uQG5.mjs";
2
- import { _ as applyDefaultSecurityHeaders, b as withDefaultSecurityHeaders, c as mergeDocumentHeaders, d as runMiddlewareChain, f as resolveDataFunctions, g as appendVaryHeader, h as resolveRegistryModule, l as mergeHeadMetadata, m as resolvePageJsUrls, p as resolvePageCssUrls, v as applyHeaders, x as withRouteResponseHeaders, y as applySecurityAndRouteHeaders } from "./types-idmK5omD.mjs";
1
+ import { a as matchApiRoute, c as resolveApp, n as buildPathFromSegments, o as matchAppRoute } from "./app-w-P1wf5T.mjs";
2
+ import { _ as applyDefaultSecurityHeaders, b as withDefaultSecurityHeaders, c as mergeDocumentHeaders, d as runMiddlewareChain, f as resolveDataFunctions, g as appendVaryHeader, h as resolveRegistryModule, l as mergeHeadMetadata, m as resolvePageJsUrls, p as resolvePageCssUrls, v as applyHeaders, x as withRouteResponseHeaders, y as applySecurityAndRouteHeaders } from "./types-DQv2poC5.mjs";
3
3
  import { f as HYDRATION_STATE_ELEMENT_ID, h as SAFE_METHODS, m as ROUTE_STATE_REQUEST_HEADER, o as buildRouteStateUrl } from "./prefetch-cache-DzP2Bj9H.mjs";
4
4
  import { a as buildRuntimeDiagnostics, c as normalizeRouteError, l as shouldExposeServerErrors, o as createSerializedRouteError, s as deserializeRouteError, t as PrachtRuntimeProvider } from "./runtime-context-B5pREhcM.mjs";
5
5
  import { h } from "preact";
@@ -249,11 +249,9 @@ function prefersMarkdown(accept) {
249
249
  if (!html) return true;
250
250
  return md.quality >= html.quality;
251
251
  }
252
- function markdownResponse(source) {
253
- const headers = new Headers({
254
- "content-type": "text/markdown; charset=utf-8",
255
- "cache-control": "public, max-age=0, must-revalidate"
256
- });
252
+ function markdownResponse(source, initHeaders) {
253
+ const headers = new Headers(initHeaders);
254
+ headers.set("content-type", "text/markdown; charset=utf-8");
257
255
  appendVaryHeader(headers, "Accept");
258
256
  applyDefaultSecurityHeaders(headers);
259
257
  return new Response(source, {
@@ -444,7 +442,6 @@ async function handlePrachtRequest(options) {
444
442
  currentPhase = "render";
445
443
  routeModule = await routeModulePromise;
446
444
  if (!routeModule) throw new Error("Route module not found");
447
- if (!isRouteStateRequest && typeof routeModule.markdown === "string" && prefersMarkdown(options.request.headers.get("accept"))) return markdownResponse(routeModule.markdown);
448
445
  currentPhase = "loader";
449
446
  const { loader, loaderFile: resolvedLoaderFile } = await dataFunctionsPromise;
450
447
  loaderFile = resolvedLoaderFile;
@@ -455,6 +452,7 @@ async function handlePrachtRequest(options) {
455
452
  currentPhase = "render";
456
453
  shellModule = await shellModulePromise;
457
454
  const [head, documentHeaders] = await Promise.all([mergeHeadMetadata(shellModule, routeModule, routeArgs, data), mergeDocumentHeaders(shellModule, routeModule, routeArgs, data)]);
455
+ if (!isRouteStateRequest && typeof routeModule.markdown === "string" && prefersMarkdown(options.request.headers.get("accept"))) return markdownResponse(routeModule.markdown, documentHeaders);
458
456
  const cssUrls = resolvePageCssUrls(options.cssManifest, match.route.shellFile, match.route.file);
459
457
  const modulePreloadUrls = resolvePageJsUrls(options.jsManifest, match.route.shellFile, match.route.file);
460
458
  if (match.route.render === "spa") {
@@ -1,11 +1,11 @@
1
- import { o as matchAppRoute, t as buildHref } from "./app-BvC1uQG5.mjs";
1
+ import { o as matchAppRoute, t as buildHref } from "./app-w-P1wf5T.mjs";
2
2
  import { i as getCachedRouteState, l as parseSafeNavigationUrl, s as fetchPrachtRouteState, u as routeNeedsServerFetch } from "./prefetch-cache-DzP2Bj9H.mjs";
3
3
  import { s as deserializeRouteError, t as PrachtRuntimeProvider } from "./runtime-context-B5pREhcM.mjs";
4
4
  import { createContext, h, hydrate, options, render } from "preact";
5
5
  import { useContext, useEffect, useMemo, useState } from "preact/hooks";
6
6
  import { Suspense } from "preact-suspense";
7
7
  //#region src/hydration.ts
8
- const MODE_HYDRATE = 32;
8
+ const MODE_HYDRATE$1 = 32;
9
9
  let _hydrating = false;
10
10
  let _suspensionCount = 0;
11
11
  let _hydrated = false;
@@ -13,7 +13,7 @@ new Suspense({});
13
13
  const oldCatchError = options.__e;
14
14
  options.__e = (err, newVNode, oldVNode, errorInfo) => {
15
15
  if (_hydrating && !_hydrated && err && err.then) {
16
- if (!!(newVNode && newVNode.__u && newVNode.__u & MODE_HYDRATE) || !!(newVNode && newVNode.__h)) {
16
+ if (!!(newVNode && newVNode.__u && newVNode.__u & MODE_HYDRATE$1) || !!(newVNode && newVNode.__h)) {
17
17
  _suspensionCount++;
18
18
  let settled = false;
19
19
  const onSettled = () => {
@@ -55,21 +55,102 @@ function useIsHydrated() {
55
55
  //#endregion
56
56
  //#region src/hydration-mismatch.ts
57
57
  const HYDRATION_BANNER_ID = "__pracht_hydration_mismatch__";
58
+ const MODE_HYDRATE = 32;
58
59
  let installed = false;
60
+ let prevMismatch;
61
+ let prevCatchError;
62
+ let prevCommit;
63
+ const pendingSuspenseChecks = /* @__PURE__ */ new Set();
64
+ let flushScheduled = false;
59
65
  function installHydrationMismatchWarning() {
60
66
  if (installed) return;
61
67
  installed = true;
62
- const prev = options.__m;
63
- options.__m = function(vnode) {
68
+ const opts = options;
69
+ prevMismatch = opts.__m;
70
+ prevCatchError = opts.__e;
71
+ prevCommit = opts.__c;
72
+ opts.__m = function(vnode) {
64
73
  appendHydrationWarning(vnode);
65
- if (prev) prev(vnode);
74
+ if (prevMismatch) prevMismatch(vnode);
75
+ };
76
+ opts.__e = function(err, newVNode, oldVNode, errorInfo) {
77
+ trackSuspendingVNode(err, newVNode);
78
+ if (prevCatchError) prevCatchError(err, newVNode, oldVNode, errorInfo);
79
+ };
80
+ opts.__c = function(vnode, commitQueue) {
81
+ if (prevCommit) prevCommit(vnode, commitQueue);
82
+ scheduleSuspenseCheckFlush();
83
+ };
84
+ }
85
+ function trackSuspendingVNode(err, vnode) {
86
+ if (!vnode) return;
87
+ if (!err || typeof err.then !== "function") return;
88
+ if (!!!(vnode.__u && vnode.__u & MODE_HYDRATE || vnode.__h)) return;
89
+ const promise = err;
90
+ const onSettle = () => {
91
+ pendingSuspenseChecks.add(vnode);
66
92
  };
93
+ promise.then(onSettle, onSettle);
94
+ }
95
+ function scheduleSuspenseCheckFlush() {
96
+ if (flushScheduled) return;
97
+ if (pendingSuspenseChecks.size === 0) return;
98
+ flushScheduled = true;
99
+ queueMicrotask(flushSuspenseChecks);
100
+ }
101
+ function flushSuspenseChecks() {
102
+ flushScheduled = false;
103
+ if (pendingSuspenseChecks.size === 0) return;
104
+ const checks = Array.from(pendingSuspenseChecks);
105
+ pendingSuspenseChecks.clear();
106
+ for (const vnode of checks) {
107
+ const rendered = currentVNode(vnode);
108
+ const count = countTopLevelDomNodes(rendered);
109
+ if (count !== 1) appendSuspenseOffsetWarning(pickReportableVNode(rendered), count);
110
+ }
111
+ }
112
+ function currentVNode(vnode) {
113
+ return vnode.__c?.__v ?? vnode;
114
+ }
115
+ function pickReportableVNode(vnode) {
116
+ let current = vnode;
117
+ for (let depth = 0; depth < 4; depth++) {
118
+ if (!isLazyWrapperVNode(current)) break;
119
+ const children = current.__k;
120
+ if (!Array.isArray(children) || children.length !== 1) break;
121
+ const child = children[0];
122
+ if (!child || typeof child.type !== "function") break;
123
+ current = currentVNode(child);
124
+ }
125
+ return current;
126
+ }
127
+ function isLazyWrapperVNode(vnode) {
128
+ const type = vnode.type;
129
+ if (typeof type !== "function") return false;
130
+ const fn = type;
131
+ return fn.displayName === "Lazy" || fn.name === "Lazy";
132
+ }
133
+ function countTopLevelDomNodes(vnode) {
134
+ if (!vnode || typeof vnode !== "object") return 0;
135
+ const type = vnode.type;
136
+ if (type === null) return 1;
137
+ if (typeof type === "string") return 1;
138
+ const children = vnode.__k;
139
+ if (!Array.isArray(children)) return 0;
140
+ let total = 0;
141
+ for (const child of children) total += countTopLevelDomNodes(child);
142
+ return total;
143
+ }
144
+ function appendSuspenseOffsetWarning(vnode, count) {
145
+ if (typeof document === "undefined") return;
146
+ appendBannerMessage(`Suspense boundary resolved during hydration: <${getVNodeName(vnode)}> ${count === 0 ? "rendered 0 DOM nodes" : `rendered ${count} DOM nodes`}. Components that unsuspend during hydration must render exactly one DOM node — otherwise sibling offsets can drift and later updates may bind to the wrong nodes.`);
67
147
  }
68
148
  function appendHydrationWarning(vnode) {
149
+ appendBannerMessage(`Hydration mismatch detected on <${getVNodeName(vnode)}>. The server-rendered HTML did not match the client.`);
150
+ }
151
+ function appendBannerMessage(message) {
69
152
  if (typeof document === "undefined") return;
70
- const componentName = getVNodeName(vnode);
71
153
  let banner = document.getElementById(HYDRATION_BANNER_ID);
72
- const message = `Hydration mismatch detected on <${componentName}>. The server-rendered HTML did not match the client.`;
73
154
  if (banner) {
74
155
  const list = banner.querySelector(`[data-pracht-mismatch-list]`);
75
156
  if (list) {
@@ -246,7 +327,9 @@ async function initClientRouter(options) {
246
327
  const navigationTarget = typeof to === "string" ? to : buildHref(app.routes, to.route, to);
247
328
  const target = resolveBrowserRouteTarget(navigationTarget);
248
329
  if (!target) {
249
- window.location.href = navigationTarget;
330
+ const safeUrl = parseSafeNavigationUrl(navigationTarget, window.location.href);
331
+ if (safeUrl) window.location.href = safeUrl.toString();
332
+ else if (navigationTarget) console.error(`[pracht] refused to navigate to unsafe URL: ${navigationTarget}`);
250
333
  return;
251
334
  }
252
335
  const match = matchAppRoute(app, target.pathname);
@@ -396,7 +479,7 @@ async function initClientRouter(options) {
396
479
  startRouteImport(match);
397
480
  startShellImport(match);
398
481
  };
399
- import("./prefetch-B50kX9RL.mjs").then(({ setupPrefetching }) => {
482
+ import("./prefetch-D9amIQtw.mjs").then(({ setupPrefetching }) => {
400
483
  setupPrefetching(app, warmModules);
401
484
  });
402
485
  }
package/dist/server.mjs CHANGED
@@ -1,5 +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";
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-w-P1wf5T.mjs";
2
+ import { S as createHref, _ as applyDefaultSecurityHeaders, t as PrachtHttpError, u as redirect } from "./types-DQv2poC5.mjs";
3
3
  import { t as PrachtRuntimeProvider } from "./runtime-context-B5pREhcM.mjs";
4
- import { n as handlePrachtRequest, t as prerenderApp } from "./prerender-D3E2H96p.mjs";
4
+ import { n as handlePrachtRequest, t as prerenderApp } from "./prerender-CoMyuJKm.mjs";
5
5
  export { PrachtHttpError, PrachtRuntimeProvider, applyDefaultSecurityHeaders, buildHref, buildPathFromSegments, createHref, defineApp, group, handlePrachtRequest, matchApiRoute, matchAppRoute, prerenderApp, redirect, resolveApiRoutes, resolveApp, route, timeRevalidate };
@@ -1,4 +1,4 @@
1
- import { t as buildHref } from "./app-BvC1uQG5.mjs";
1
+ import { t as buildHref } from "./app-w-P1wf5T.mjs";
2
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
3
  import { n as RouteDataContext, s as deserializeRouteError } from "./runtime-context-B5pREhcM.mjs";
4
4
  import { h } from "preact";
@@ -323,14 +323,7 @@ function Form(props) {
323
323
  body: new FormData(form),
324
324
  redirect: "manual"
325
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
- }
326
+ if (response.type === "opaqueredirect" || response.status >= 300 && response.status < 400) await navigateToClientLocation(response.headers.get("location") ?? props.action ?? form.action);
334
327
  }
335
328
  });
336
329
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pracht/core",
3
- "version": "0.8.0",
3
+ "version": "0.8.1",
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",