@vertz/ui-server 0.2.29 → 0.2.31

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.
@@ -25,6 +25,7 @@ interface SSRSessionInfo {
25
25
  * Returns null when no valid session exists (expired, missing, or invalid cookie).
26
26
  */
27
27
  type SessionResolver = (request: Request) => Promise<SSRSessionInfo | null>;
28
+ import { BunPlugin as BunPlugin_seob6 } from "bun";
28
29
  /**
29
30
  * Detect `public/favicon.svg` and return a `<link>` tag for it.
30
31
  * Returns empty string when the file does not exist.
@@ -98,6 +99,21 @@ interface BunDevServerOptions {
98
99
  * script restarts the process.
99
100
  */
100
101
  onRestartNeeded?: () => void;
102
+ /**
103
+ * Enable progressive HTML streaming in the dev server. Default: false.
104
+ *
105
+ * When true, SSR responses stream `<head>` content before `<body>`
106
+ * rendering completes. Currently reserved for future use — the dev server
107
+ * always uses buffered rendering. The option is accepted to match the
108
+ * production handler's `progressiveHTML` option.
109
+ */
110
+ progressiveHTML?: boolean;
111
+ /**
112
+ * Additional Bun plugins to register alongside the Vertz compiler plugin.
113
+ * Use this to add MDX compilation or other custom file type handling.
114
+ * Plugins are registered once (process-global) on the first `start()` call.
115
+ */
116
+ plugins?: BunPlugin_seob6[];
101
117
  }
102
118
  interface ErrorDetail {
103
119
  message: string;
@@ -110,6 +126,13 @@ interface ErrorDetail {
110
126
  }
111
127
  type ErrorCategory = "build" | "resolve" | "runtime" | "ssr";
112
128
  declare function isStaleGraphError(message: string): boolean;
129
+ /**
130
+ * Detect Bun's reload stub — the response served when the dev bundler
131
+ * fails to compile a client module. The stub is literally:
132
+ * try{location.reload()}catch(_){}
133
+ * addEventListener("DOMContentLoaded",function(event){location.reload()})
134
+ */
135
+ declare function isReloadStub(text: string): boolean;
113
136
  /** A resolved stack frame for terminal logging. */
114
137
  interface TerminalStackFrame {
115
138
  functionName: string | null;
@@ -221,4 +244,4 @@ declare function clearSSRRequireCache(): number;
221
244
  * SSR is always on. HMR always works. No mode toggle needed.
222
245
  */
223
246
  declare function createBunDevServer(options: BunDevServerOptions): BunDevServer;
224
- export { parseHMRAssets, isStaleGraphError, generateSSRPageHtml, formatTerminalRuntimeError, detectFaviconTag, createRuntimeErrorDeduplicator, createFetchInterceptor, createBunDevServer, clearSSRRequireCache, buildScriptTag, SSRPageHtmlOptions, HMRAssets, FetchInterceptorOptions, ErrorDetail, ErrorCategory, BunDevServerOptions, BunDevServer };
247
+ export { parseHMRAssets, isStaleGraphError, isReloadStub, generateSSRPageHtml, formatTerminalRuntimeError, detectFaviconTag, createRuntimeErrorDeduplicator, createFetchInterceptor, createBunDevServer, clearSSRRequireCache, buildScriptTag, SSRPageHtmlOptions, HMRAssets, FetchInterceptorOptions, ErrorDetail, ErrorCategory, BunDevServerOptions, BunDevServer };
@@ -1790,6 +1790,15 @@ function safeSerialize(data) {
1790
1790
  }
1791
1791
 
1792
1792
  // src/ssr-render.ts
1793
+ var compiledThemeCache = new WeakMap;
1794
+ function compileThemeCached(theme, fallbackMetrics) {
1795
+ const cached = compiledThemeCache.get(theme);
1796
+ if (cached)
1797
+ return cached;
1798
+ const compiled = compileTheme(theme, { fallbackMetrics });
1799
+ compiledThemeCache.set(theme, compiled);
1800
+ return compiled;
1801
+ }
1793
1802
  function createRequestContext(url) {
1794
1803
  return {
1795
1804
  url,
@@ -1855,9 +1864,7 @@ async function ssrRenderToString(module, url, options) {
1855
1864
  let themePreloadTags = "";
1856
1865
  if (module.theme) {
1857
1866
  try {
1858
- const compiled = compileTheme(module.theme, {
1859
- fallbackMetrics: options?.fallbackMetrics
1860
- });
1867
+ const compiled = compileThemeCached(module.theme, options?.fallbackMetrics);
1861
1868
  themeCss = compiled.css;
1862
1869
  themePreloadTags = compiled.preloadTags;
1863
1870
  } catch (e) {
@@ -1914,10 +1921,7 @@ async function ssrRenderToString(module, url, options) {
1914
1921
  const stream = renderToStream(vnode);
1915
1922
  const html = await streamToString(stream);
1916
1923
  const css = collectCSS(themeCss, module);
1917
- const ssrData = resolvedQueries.length > 0 ? resolvedQueries.map(({ key, data }) => ({
1918
- key,
1919
- data: JSON.parse(JSON.stringify(data))
1920
- })) : [];
1924
+ const ssrData = resolvedQueries.length > 0 ? resolvedQueries.map(({ key, data }) => ({ key, data })) : [];
1921
1925
  return {
1922
1926
  html,
1923
1927
  css,
@@ -2003,7 +2007,7 @@ data: {}
2003
2007
  return;
2004
2008
  settled = true;
2005
2009
  resolve(data);
2006
- const entry = { key, data: JSON.parse(JSON.stringify(data)) };
2010
+ const entry = { key, data };
2007
2011
  safeEnqueue(encoder2.encode(`event: data
2008
2012
  data: ${safeSerialize(entry)}
2009
2013
 
@@ -2040,9 +2044,6 @@ function escapeAttr3(s) {
2040
2044
  return s.replace(/[&"'<>]/g, (c) => `&#${c.charCodeAt(0)};`);
2041
2045
  }
2042
2046
 
2043
- // src/ssr-single-pass.ts
2044
- import { compileTheme as compileTheme2 } from "@vertz/ui";
2045
-
2046
2047
  // src/ssr-manifest-prefetch.ts
2047
2048
  function reconstructDescriptors(queries, routeParams, apiClient) {
2048
2049
  if (!apiClient)
@@ -2179,54 +2180,7 @@ async function ssrRenderSinglePass(module, url, options) {
2179
2180
  if (zeroDiscoveryData) {
2180
2181
  return renderWithPrefetchedData(module, normalizedUrl, zeroDiscoveryData, options);
2181
2182
  }
2182
- const discoveryCtx = createRequestContext(normalizedUrl);
2183
- if (options?.ssrAuth) {
2184
- discoveryCtx.ssrAuth = options.ssrAuth;
2185
- }
2186
- const discoveredData = await ssrStorage.run(discoveryCtx, async () => {
2187
- try {
2188
- setGlobalSSRTimeout(ssrTimeout);
2189
- const createApp = resolveAppFactory2(module);
2190
- createApp();
2191
- if (discoveryCtx.ssrRedirect) {
2192
- return { redirect: discoveryCtx.ssrRedirect };
2193
- }
2194
- if (discoveryCtx.pendingRouteComponents?.size) {
2195
- const entries = Array.from(discoveryCtx.pendingRouteComponents.entries());
2196
- const results = await Promise.allSettled(entries.map(([route, promise]) => Promise.race([
2197
- promise.then((mod) => ({ route, factory: mod.default })),
2198
- new Promise((_, reject) => setTimeout(() => reject(new Error("lazy route timeout")), ssrTimeout))
2199
- ])));
2200
- discoveryCtx.resolvedComponents = new Map;
2201
- for (const result of results) {
2202
- if (result.status === "fulfilled") {
2203
- const { route, factory } = result.value;
2204
- discoveryCtx.resolvedComponents.set(route, factory);
2205
- }
2206
- }
2207
- discoveryCtx.pendingRouteComponents = undefined;
2208
- }
2209
- const queries = getSSRQueries();
2210
- const eligibleQueries = filterByEntityAccess(queries, options?.manifest?.entityAccess, options?.prefetchSession);
2211
- const resolvedQueries = [];
2212
- if (eligibleQueries.length > 0) {
2213
- await Promise.allSettled(eligibleQueries.map(({ promise, timeout, resolve, key }) => Promise.race([
2214
- promise.then((data) => {
2215
- resolve(data);
2216
- resolvedQueries.push({ key, data });
2217
- return "resolved";
2218
- }),
2219
- new Promise((r) => setTimeout(r, timeout || ssrTimeout)).then(() => "timeout")
2220
- ])));
2221
- }
2222
- return {
2223
- resolvedQueries,
2224
- resolvedComponents: discoveryCtx.resolvedComponents
2225
- };
2226
- } finally {
2227
- clearGlobalSSRTimeout();
2228
- }
2229
- });
2183
+ const discoveredData = await runDiscoveryPhase(normalizedUrl, ssrTimeout, module, options);
2230
2184
  if ("redirect" in discoveredData) {
2231
2185
  return {
2232
2186
  html: "",
@@ -2252,9 +2206,7 @@ async function ssrRenderSinglePass(module, url, options) {
2252
2206
  let themePreloadTags = "";
2253
2207
  if (module.theme) {
2254
2208
  try {
2255
- const compiled = compileTheme2(module.theme, {
2256
- fallbackMetrics: options?.fallbackMetrics
2257
- });
2209
+ const compiled = compileThemeCached(module.theme, options?.fallbackMetrics);
2258
2210
  themeCss = compiled.css;
2259
2211
  themePreloadTags = compiled.preloadTags;
2260
2212
  } catch (e) {
@@ -2268,7 +2220,7 @@ async function ssrRenderSinglePass(module, url, options) {
2268
2220
  const css = collectCSS2(themeCss, module);
2269
2221
  const ssrData = discoveredData.resolvedQueries.map(({ key, data }) => ({
2270
2222
  key,
2271
- data: JSON.parse(JSON.stringify(data))
2223
+ data
2272
2224
  }));
2273
2225
  return {
2274
2226
  html,
@@ -2283,6 +2235,56 @@ async function ssrRenderSinglePass(module, url, options) {
2283
2235
  }
2284
2236
  });
2285
2237
  }
2238
+ async function runDiscoveryPhase(normalizedUrl, ssrTimeout, module, options) {
2239
+ const discoveryCtx = createRequestContext(normalizedUrl);
2240
+ if (options?.ssrAuth) {
2241
+ discoveryCtx.ssrAuth = options.ssrAuth;
2242
+ }
2243
+ return ssrStorage.run(discoveryCtx, async () => {
2244
+ try {
2245
+ setGlobalSSRTimeout(ssrTimeout);
2246
+ const createApp = resolveAppFactory2(module);
2247
+ createApp();
2248
+ if (discoveryCtx.ssrRedirect) {
2249
+ return { redirect: discoveryCtx.ssrRedirect };
2250
+ }
2251
+ if (discoveryCtx.pendingRouteComponents?.size) {
2252
+ const entries = Array.from(discoveryCtx.pendingRouteComponents.entries());
2253
+ const results = await Promise.allSettled(entries.map(([route, promise]) => Promise.race([
2254
+ promise.then((mod) => ({ route, factory: mod.default })),
2255
+ new Promise((_, reject) => setTimeout(() => reject(new Error("lazy route timeout")), ssrTimeout))
2256
+ ])));
2257
+ discoveryCtx.resolvedComponents = new Map;
2258
+ for (const result of results) {
2259
+ if (result.status === "fulfilled") {
2260
+ const { route, factory } = result.value;
2261
+ discoveryCtx.resolvedComponents.set(route, factory);
2262
+ }
2263
+ }
2264
+ discoveryCtx.pendingRouteComponents = undefined;
2265
+ }
2266
+ const queries = getSSRQueries();
2267
+ const eligibleQueries = filterByEntityAccess(queries, options?.manifest?.entityAccess, options?.prefetchSession);
2268
+ const resolvedQueries = [];
2269
+ if (eligibleQueries.length > 0) {
2270
+ await Promise.allSettled(eligibleQueries.map(({ promise, timeout, resolve, key }) => Promise.race([
2271
+ promise.then((data) => {
2272
+ resolve(data);
2273
+ resolvedQueries.push({ key, data });
2274
+ return "resolved";
2275
+ }),
2276
+ new Promise((r) => setTimeout(r, timeout || ssrTimeout)).then(() => "timeout")
2277
+ ])));
2278
+ }
2279
+ return {
2280
+ resolvedQueries,
2281
+ resolvedComponents: discoveryCtx.resolvedComponents
2282
+ };
2283
+ } finally {
2284
+ clearGlobalSSRTimeout();
2285
+ }
2286
+ });
2287
+ }
2286
2288
  function attemptZeroDiscovery(url, module, options, ssrTimeout) {
2287
2289
  const manifest = options?.manifest;
2288
2290
  if (!manifest?.routeEntries || !module.api)
@@ -2345,9 +2347,7 @@ async function renderWithPrefetchedData(module, normalizedUrl, prefetchedData, o
2345
2347
  let themePreloadTags = "";
2346
2348
  if (module.theme) {
2347
2349
  try {
2348
- const compiled = compileTheme2(module.theme, {
2349
- fallbackMetrics: options?.fallbackMetrics
2350
- });
2350
+ const compiled = compileThemeCached(module.theme, options?.fallbackMetrics);
2351
2351
  themeCss = compiled.css;
2352
2352
  themePreloadTags = compiled.preloadTags;
2353
2353
  } catch (e) {
@@ -2372,7 +2372,7 @@ async function renderWithPrefetchedData(module, normalizedUrl, prefetchedData, o
2372
2372
  const css = collectCSS2(themeCss, module);
2373
2373
  const ssrData = data.resolvedQueries.map(({ key, data: d }) => ({
2374
2374
  key,
2375
- data: JSON.parse(JSON.stringify(d))
2375
+ data: d
2376
2376
  }));
2377
2377
  return {
2378
2378
  html,
@@ -2553,6 +2553,9 @@ var STALE_GRAPH_PATTERNS = [
2553
2553
  function isStaleGraphError(message) {
2554
2554
  return STALE_GRAPH_PATTERNS.some((pattern) => pattern.test(message));
2555
2555
  }
2556
+ function isReloadStub(text) {
2557
+ return text.trimStart().startsWith("try{location.reload()}");
2558
+ }
2556
2559
  var MAX_TERMINAL_STACK_FRAMES = 5;
2557
2560
  function formatTerminalRuntimeError(errors, parsedStack) {
2558
2561
  const primary = errors[0];
@@ -2881,9 +2884,13 @@ var BUILD_ERROR_LOADER = [
2881
2884
  "if(t.trimStart().startsWith('try{location.reload()}')){",
2882
2885
  "fetch('/__vertz_build_check').then(function(r){return r.json()}).then(function(j){",
2883
2886
  "if(j.errors&&j.errors.length>0){showOverlay('Build failed',formatErrors(j.errors),j)}",
2887
+ "else{var V2=window.__vertz_overlay;",
2888
+ "if(V2&&V2._autoRestart&&V2._canAutoRestart&&V2._canAutoRestart()){",
2889
+ "V2._autoRestart();sessionStorage.removeItem('__vertz_stub_retry');",
2890
+ `showOverlay('Restarting dev server','<p style="margin:0;color:#666;font-size:12px">Dev bundler appears stale after adding new files. Restarting...</p>')}`,
2884
2891
  "else{var rk='__vertz_stub_retry',rc=+(sessionStorage.getItem(rk)||0);",
2885
2892
  "if(rc<3){sessionStorage.setItem(rk,String(rc+1));setTimeout(function(){location.reload()},2000)}",
2886
- `else{sessionStorage.removeItem(rk);showOverlay('Build failed','<p style="margin:0;color:#666;font-size:12px">Could not load client bundle. Try reloading manually.</p>')}}`,
2893
+ `else{sessionStorage.removeItem(rk);showOverlay('Build failed','<p style="margin:0;color:#666;font-size:12px">Could not load client bundle. Try restarting the dev server.</p>',null,null,true)}}}`,
2887
2894
  "}).catch(function(){",
2888
2895
  `showOverlay('Build failed','<p style="margin:0;color:#666;font-size:12px">Check your terminal for details.</p>')})}`,
2889
2896
  "else{sessionStorage.removeItem('__vertz_stub_retry');var s=document.createElement('script');s.type='module';s.crossOrigin='';s.src=src;document.body.appendChild(s)}",
@@ -3277,6 +3284,11 @@ function createBunDevServer(options) {
3277
3284
  plugin(serverPlugin);
3278
3285
  stableUpdateManifest = updateManifest;
3279
3286
  }
3287
+ if (!pluginsRegistered && options.plugins) {
3288
+ for (const userPlugin of options.plugins) {
3289
+ plugin(userPlugin);
3290
+ }
3291
+ }
3280
3292
  pluginsRegistered = true;
3281
3293
  const updateServerManifest = stableUpdateManifest;
3282
3294
  let ssrMod;
@@ -3897,6 +3909,24 @@ data: {}
3897
3909
  if (bundledScriptUrl !== prevUrl)
3898
3910
  break;
3899
3911
  }
3912
+ if (bundledScriptUrl && server && !isRestarting) {
3913
+ try {
3914
+ const bundleRes = await fetch(`http://${host}:${server.port}${bundledScriptUrl}`);
3915
+ const bundleText = await bundleRes.text();
3916
+ if (isReloadStub(bundleText)) {
3917
+ if (canAutoRestart()) {
3918
+ autoRestartTimestamps.push(Date.now());
3919
+ if (logRequests) {
3920
+ console.log("[Server] Dev bundler serving reload stub after successful build \u2014 restarting");
3921
+ }
3922
+ devServer.restart();
3923
+ return;
3924
+ } else if (logRequests) {
3925
+ console.log("[Server] Dev bundler stale but auto-restart cap reached");
3926
+ }
3927
+ }
3928
+ } catch {}
3929
+ }
3900
3930
  clearErrorForFileChange();
3901
3931
  }
3902
3932
  } catch {}
@@ -4110,6 +4140,7 @@ data: {}
4110
4140
  export {
4111
4141
  parseHMRAssets,
4112
4142
  isStaleGraphError,
4143
+ isReloadStub,
4113
4144
  generateSSRPageHtml,
4114
4145
  formatTerminalRuntimeError,
4115
4146
  detectFaviconTag,
@@ -133,7 +133,7 @@ declare class DiagnosticsCollector {
133
133
  recordFieldMiss(type: string, id: string, field: string, querySource: string): void;
134
134
  getSnapshot(): DiagnosticsSnapshot;
135
135
  }
136
- import { BunPlugin as BunPlugin_seob6 } from "bun";
136
+ import { BunPlugin as BunPlugin_seob62 } from "bun";
137
137
  interface VertzBunPluginOptions {
138
138
  /** Regex filter for files to transform. Defaults to .tsx files. */
139
139
  filter?: RegExp;
@@ -187,7 +187,7 @@ interface ManifestUpdateResult {
187
187
  }
188
188
  interface VertzBunPluginResult {
189
189
  /** The Bun plugin to pass to Bun.build or bunfig.toml. */
190
- plugin: BunPlugin_seob6;
190
+ plugin: BunPlugin_seob62;
191
191
  /** CSS extractions for all transformed files (for production dead CSS elimination). */
192
192
  fileExtractions: FileExtractionsMap;
193
193
  /** Map of source file to CSS sidecar file path (for debugging). */
@@ -7,7 +7,7 @@ import {
7
7
  installDomShim,
8
8
  removeDomShim,
9
9
  toVNode
10
- } from "../shared/chunk-gcwqkynf.js";
10
+ } from "../shared/chunk-ybftdw1r.js";
11
11
  export {
12
12
  toVNode,
13
13
  removeDomShim,