@vertz/ui-server 0.2.35 → 0.2.37

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.
@@ -126,6 +126,7 @@ interface ErrorDetail {
126
126
  }
127
127
  type ErrorCategory = "build" | "resolve" | "runtime" | "ssr";
128
128
  declare function isStaleGraphError(message: string): boolean;
129
+ declare function parsePluginError(text: string): ErrorDetail | null;
129
130
  /**
130
131
  * Detect Bun's reload stub — the response served when the dev bundler
131
132
  * fails to compile a client module. The stub is literally:
@@ -257,4 +258,4 @@ declare function clearSSRRequireCache(): number;
257
258
  * SSR is always on. HMR always works. No mode toggle needed.
258
259
  */
259
260
  declare function createBunDevServer(options: BunDevServerOptions): BunDevServer;
260
- export { shouldCheckStaleBundler, parseHMRAssets, isStaleGraphError, isReloadStub, generateSSRPageHtml, formatTerminalRuntimeError, detectFaviconTag, createRuntimeErrorDeduplicator, createFetchInterceptor, createBunDevServer, clearSSRRequireCache, buildScriptTag, SSRPageHtmlOptions, HMRAssets, FetchInterceptorOptions, ErrorDetail, ErrorCategory, BunDevServerOptions, BunDevServer };
261
+ export { shouldCheckStaleBundler, parsePluginError, parseHMRAssets, isStaleGraphError, isReloadStub, generateSSRPageHtml, formatTerminalRuntimeError, detectFaviconTag, createRuntimeErrorDeduplicator, createFetchInterceptor, createBunDevServer, clearSSRRequireCache, buildScriptTag, SSRPageHtmlOptions, HMRAssets, FetchInterceptorOptions, ErrorDetail, ErrorCategory, BunDevServerOptions, BunDevServer };
@@ -1814,7 +1814,8 @@ function createRequestContext(url) {
1814
1814
  queryCache: new MemoryCache({ maxSize: Infinity }),
1815
1815
  inflight: new Map,
1816
1816
  queries: [],
1817
- errors: []
1817
+ errors: [],
1818
+ cssTracker: new Set
1818
1819
  };
1819
1820
  }
1820
1821
  var domShimInstalled = false;
@@ -1839,7 +1840,9 @@ function collectCSS(themeCss, module) {
1839
1840
  for (const s of module.styles)
1840
1841
  alreadyIncluded.add(s);
1841
1842
  }
1842
- const componentCss = module.getInjectedCSS ? module.getInjectedCSS().filter((s) => !alreadyIncluded.has(s)) : [];
1843
+ const ssrCtx = ssrStorage.getStore();
1844
+ const rawComponentCss = ssrCtx?.cssTracker?.size ? Array.from(ssrCtx.cssTracker) : module.getInjectedCSS?.() ?? [];
1845
+ const componentCss = rawComponentCss.filter((s) => !alreadyIncluded.has(s));
1843
1846
  const themeTag = themeCss ? `<style data-vertz-css>${themeCss}</style>` : "";
1844
1847
  const globalTag = module.styles && module.styles.length > 0 ? `<style data-vertz-css>${module.styles.join(`
1845
1848
  `)}</style>` : "";
@@ -2456,7 +2459,9 @@ function collectCSS2(themeCss, module) {
2456
2459
  for (const s of module.styles)
2457
2460
  alreadyIncluded.add(s);
2458
2461
  }
2459
- const componentCss = module.getInjectedCSS ? module.getInjectedCSS().filter((s) => !alreadyIncluded.has(s)) : [];
2462
+ const ssrCtx = ssrStorage.getStore();
2463
+ const rawComponentCss = ssrCtx?.cssTracker?.size ? Array.from(ssrCtx.cssTracker) : module.getInjectedCSS?.() ?? [];
2464
+ const componentCss = rawComponentCss.filter((s) => !alreadyIncluded.has(s));
2460
2465
  const themeTag = themeCss ? `<style data-vertz-css>${themeCss}</style>` : "";
2461
2466
  const globalTag = module.styles && module.styles.length > 0 ? `<style data-vertz-css>${module.styles.join(`
2462
2467
  `)}</style>` : "";
@@ -2559,6 +2564,19 @@ var STALE_GRAPH_PATTERNS = [
2559
2564
  function isStaleGraphError(message) {
2560
2565
  return STALE_GRAPH_PATTERNS.some((pattern) => pattern.test(message));
2561
2566
  }
2567
+ var PLUGIN_ERROR_PATTERN = /\[vertz-bun-plugin\] Failed to process (.+?): ([\s\S]*)/;
2568
+ var LINE_COL_PATTERN = /\((\d+):(\d+)\)/;
2569
+ function parsePluginError(text) {
2570
+ const match = text.match(PLUGIN_ERROR_PATTERN);
2571
+ if (!match)
2572
+ return null;
2573
+ const file = match[1];
2574
+ const message = match[2]?.trim() || "Compilation failed";
2575
+ const locMatch = message.match(LINE_COL_PATTERN);
2576
+ const line = locMatch ? Number(locMatch[1]) : undefined;
2577
+ const column = locMatch ? Number(locMatch[2]) : undefined;
2578
+ return { message, file, line, column };
2579
+ }
2562
2580
  function isReloadStub(text) {
2563
2581
  return text.trimStart().startsWith("try{location.reload()}");
2564
2582
  }
@@ -3125,7 +3143,13 @@ function createBunDevServer(options) {
3125
3143
  const text = args.map((a) => typeof a === "string" ? a : String(a)).join(" ");
3126
3144
  if (!text.startsWith("[Server]")) {
3127
3145
  lastBuildError = text;
3128
- if (resolvePatterns.some((p) => text.includes(p)) && text !== lastBroadcastedError) {
3146
+ const pluginErr = parsePluginError(text);
3147
+ if (pluginErr && text !== lastBroadcastedError) {
3148
+ lastBroadcastedError = text;
3149
+ const absFile = pluginErr.file ? resolve(projectRoot, pluginErr.file) : undefined;
3150
+ const lineText = pluginErr.line && absFile ? readLineText(absFile, pluginErr.line) : undefined;
3151
+ broadcastError("build", [{ ...pluginErr, absFile, lineText }]);
3152
+ } else if (resolvePatterns.some((p) => text.includes(p)) && text !== lastBroadcastedError) {
3129
3153
  lastBroadcastedError = text;
3130
3154
  broadcastError("resolve", [{ message: text }]);
3131
3155
  } else {
@@ -4196,6 +4220,7 @@ data: {}
4196
4220
  }
4197
4221
  export {
4198
4222
  shouldCheckStaleBundler,
4223
+ parsePluginError,
4199
4224
  parseHMRAssets,
4200
4225
  isStaleGraphError,
4201
4226
  isReloadStub,
@@ -9,7 +9,7 @@ import {
9
9
 
10
10
  // src/bun-plugin/plugin.ts
11
11
  import { mkdirSync as mkdirSync2, readFileSync as readFileSync3, writeFileSync as writeFileSync2 } from "fs";
12
- import { dirname, relative, resolve as resolve2 } from "path";
12
+ import { dirname, relative, resolve as resolve3 } from "path";
13
13
  import remapping from "@ampproject/remapping";
14
14
  import {
15
15
  ComponentAnalyzer,
@@ -55,6 +55,39 @@ function injectContextStableIds(source, sourceFile, relFilePath) {
55
55
  }
56
56
  }
57
57
 
58
+ // src/bun-plugin/native-compiler-loader.ts
59
+ import { existsSync } from "fs";
60
+ import { join, resolve } from "path";
61
+ var __dirname = "/home/runner/work/vertz/vertz/packages/ui-server/src/bun-plugin";
62
+ function tryLoadNativeCompiler() {
63
+ if (process.env.VERTZ_NATIVE_COMPILER !== "1") {
64
+ return null;
65
+ }
66
+ const platform = process.platform === "darwin" ? "darwin" : "linux";
67
+ const arch = process.arch === "arm64" ? "arm64" : "x64";
68
+ const binaryName = `vertz-compiler.${platform}-${arch}.node`;
69
+ try {
70
+ const modulePath = __require.resolve(`@vertz/native-compiler/${binaryName}`);
71
+ return __require(modulePath);
72
+ } catch {}
73
+ let dir = resolve(__dirname);
74
+ for (let i = 0;i < 10; i++) {
75
+ const candidate = join(dir, "native", "vertz-compiler", binaryName);
76
+ if (existsSync(candidate)) {
77
+ try {
78
+ return __require(candidate);
79
+ } catch {
80
+ return null;
81
+ }
82
+ }
83
+ const parent = resolve(dir, "..");
84
+ if (parent === dir)
85
+ break;
86
+ dir = parent;
87
+ }
88
+ return null;
89
+ }
90
+
58
91
  // src/bun-plugin/entity-schema-loader.ts
59
92
  import { readFileSync } from "fs";
60
93
  function loadEntitySchema(schemaPath) {
@@ -491,8 +524,8 @@ function filePathHash(filePath) {
491
524
 
492
525
  // src/bun-plugin/image-processor.ts
493
526
  import { createHash } from "crypto";
494
- import { existsSync, mkdirSync, readFileSync as readFileSync2, writeFileSync } from "fs";
495
- import { basename, extname, resolve } from "path";
527
+ import { existsSync as existsSync2, mkdirSync, readFileSync as readFileSync2, writeFileSync } from "fs";
528
+ import { basename, extname, resolve as resolve2 } from "path";
496
529
  import sharp from "sharp";
497
530
  var FORMAT_MAP = {
498
531
  jpeg: { ext: ".jpg", mime: "image/jpeg" },
@@ -505,7 +538,7 @@ var FORMAT_MAP = {
505
538
  };
506
539
  async function processImage(opts) {
507
540
  const { sourcePath, width, height, quality, fit, outputDir } = opts;
508
- if (!existsSync(sourcePath)) {
541
+ if (!existsSync2(sourcePath)) {
509
542
  return { ok: false, error: `Image not found: ${sourcePath}` };
510
543
  }
511
544
  const sourceBuffer = readFileSync2(sourcePath);
@@ -518,10 +551,10 @@ async function processImage(opts) {
518
551
  const webp1xName = `${name}-${hash}-${width}w.webp`;
519
552
  const webp2xName = `${name}-${hash}-${width * 2}w.webp`;
520
553
  const fallbackName = `${name}-${hash}-${width * 2}w${formatInfo.ext}`;
521
- const webp1xPath = resolve(outputDir, webp1xName);
522
- const webp2xPath = resolve(outputDir, webp2xName);
523
- const fallbackPath = resolve(outputDir, fallbackName);
524
- if (existsSync(webp1xPath) && existsSync(webp2xPath) && existsSync(fallbackPath)) {
554
+ const webp1xPath = resolve2(outputDir, webp1xName);
555
+ const webp2xPath = resolve2(outputDir, webp2xName);
556
+ const fallbackPath = resolve2(outputDir, fallbackName);
557
+ if (existsSync2(webp1xPath) && existsSync2(webp2xPath) && existsSync2(fallbackPath)) {
525
558
  return {
526
559
  ok: true,
527
560
  webp1x: { path: webp1xPath, url: `/__vertz_img/${webp1xName}` },
@@ -935,14 +968,14 @@ function createVertzBunPlugin(options) {
935
968
  const fastRefresh = options?.fastRefresh ?? hmr;
936
969
  const routeSplitting = options?.routeSplitting ?? false;
937
970
  const projectRoot = options?.projectRoot ?? process.cwd();
938
- const cssOutDir = options?.cssOutDir ?? resolve2(projectRoot, ".vertz", "css");
971
+ const cssOutDir = options?.cssOutDir ?? resolve3(projectRoot, ".vertz", "css");
939
972
  const cssExtractor = new CSSExtractor;
940
973
  const componentAnalyzer = new ComponentAnalyzer;
941
974
  const logger = options?.logger;
942
975
  const diagnostics = options?.diagnostics;
943
976
  const fileExtractions = new Map;
944
977
  const cssSidecarMap = new Map;
945
- const srcDir = options?.srcDir ?? resolve2(projectRoot, "src");
978
+ const srcDir = options?.srcDir ?? resolve3(projectRoot, "src");
946
979
  const frameworkManifestJson = __require(__require.resolve("@vertz/ui/reactivity.json"));
947
980
  const manifestResult = generateAllManifests({
948
981
  srcDir,
@@ -997,7 +1030,7 @@ function createVertzBunPlugin(options) {
997
1030
  }
998
1031
  }
999
1032
  diagnostics?.recordFieldSelectionManifest(fieldSelectionFileCount);
1000
- const entitySchemaPath = options?.entitySchemaPath ?? resolve2(projectRoot, ".vertz", "generated", "entity-schema.json");
1033
+ const entitySchemaPath = options?.entitySchemaPath ?? resolve3(projectRoot, ".vertz", "generated", "entity-schema.json");
1001
1034
  let entitySchema = loadEntitySchema(entitySchemaPath);
1002
1035
  if (logger?.isEnabled("fields") && entitySchema) {
1003
1036
  logger.log("fields", "entity-schema-loaded", {
@@ -1005,6 +1038,8 @@ function createVertzBunPlugin(options) {
1005
1038
  entities: Object.keys(entitySchema).length
1006
1039
  });
1007
1040
  }
1041
+ const nativeCompiler = tryLoadNativeCompiler();
1042
+ let nativeManifestWarningLogged = false;
1008
1043
  mkdirSync2(cssOutDir, { recursive: true });
1009
1044
  const plugin = {
1010
1045
  name: "vertz-bun-plugin",
@@ -1094,7 +1129,7 @@ function createVertzBunPlugin(options) {
1094
1129
  }))
1095
1130
  });
1096
1131
  }
1097
- const imageOutputDir = resolve2(projectRoot, ".vertz", "images");
1132
+ const imageOutputDir = resolve3(projectRoot, ".vertz", "images");
1098
1133
  const imageQueue = [];
1099
1134
  const imageResult = transformImages(codeForCompile, args.path, {
1100
1135
  projectRoot,
@@ -1124,7 +1159,31 @@ function createVertzBunPlugin(options) {
1124
1159
  if (imageQueue.length > 0) {
1125
1160
  await Promise.all(imageQueue.map((opts) => processImage({ ...opts, fit: opts.fit })));
1126
1161
  }
1127
- const compileResult = compile(codeAfterImageTransform, {
1162
+ const compileResult = nativeCompiler ? (() => {
1163
+ if (!nativeManifestWarningLogged && manifests.size > 0) {
1164
+ nativeManifestWarningLogged = true;
1165
+ const userManifestCount = [...manifests.keys()].filter((k) => !k.includes("node_modules")).length;
1166
+ if (userManifestCount > 0) {
1167
+ console.warn(`[vertz-bun-plugin] Native compiler does not support cross-file reactivity manifests. ` + `${userManifestCount} user module(s) with exported signal APIs will not have ` + `.value insertion for their signal properties. ` + `Set VERTZ_NATIVE_COMPILER=0 if cross-file reactivity is needed.`);
1168
+ }
1169
+ }
1170
+ const nativeResult = nativeCompiler.compile(codeAfterImageTransform, {
1171
+ filename: args.path,
1172
+ target: options?.target,
1173
+ fastRefresh: false
1174
+ });
1175
+ return {
1176
+ code: nativeResult.code,
1177
+ map: nativeResult.map ? JSON.parse(nativeResult.map) : { version: 3, sources: [], mappings: "", names: [] },
1178
+ diagnostics: (nativeResult.diagnostics ?? []).map((d) => ({
1179
+ code: "native-diagnostic",
1180
+ message: d.message,
1181
+ severity: "warning",
1182
+ line: d.line ?? 1,
1183
+ column: d.column ?? 0
1184
+ }))
1185
+ };
1186
+ })() : compile(codeAfterImageTransform, {
1128
1187
  filename: args.path,
1129
1188
  target: options?.target,
1130
1189
  manifests: getManifestsRecord()
@@ -1145,7 +1204,7 @@ function createVertzBunPlugin(options) {
1145
1204
  if (hmr) {
1146
1205
  const hash = filePathHash(args.path);
1147
1206
  const cssFileName = `${hash}.css`;
1148
- const cssFilePath = resolve2(cssOutDir, cssFileName);
1207
+ const cssFilePath = resolve3(cssOutDir, cssFileName);
1149
1208
  writeFileSync2(cssFilePath, extraction.css);
1150
1209
  cssSidecarMap.set(args.path, cssFilePath);
1151
1210
  const relPath2 = relative(dirname(args.path), cssFilePath);
package/dist/index.d.ts CHANGED
@@ -302,6 +302,18 @@ interface AotManifest {
302
302
  /** Route pattern → AOT entry. */
303
303
  routes: Record<string, AotRouteEntry>;
304
304
  }
305
+ /**
306
+ * Resolves custom data for AOT-rendered routes.
307
+ *
308
+ * Called after the entity prefetch pipeline, before the `allKeysResolved` bail check.
309
+ * Only called when there are unresolved query keys remaining.
310
+ *
311
+ * @param pattern - Matched route pattern (e.g., '/products/:id')
312
+ * @param params - Route params (e.g., \{ id: 'abc-123' \})
313
+ * @param unresolvedKeys - Query keys not yet populated by entity prefetch
314
+ * @returns Map of cache key → data, or empty Map to skip
315
+ */
316
+ type AotDataResolver = (pattern: string, params: Record<string, string>, unresolvedKeys: string[]) => Promise<Map<string, unknown>> | Map<string, unknown>;
305
317
  /** Options for `ssrRenderAot()`. */
306
318
  interface SSRRenderAotOptions {
307
319
  /** AOT manifest with pre-compiled render functions. */
@@ -318,6 +330,8 @@ interface SSRRenderAotOptions {
318
330
  prefetchSession?: PrefetchSession;
319
331
  /** AOT diagnostics collector (dev mode). When provided with VERTZ_DEBUG=aot, enables dual rendering and divergence detection. */
320
332
  diagnostics?: AotDiagnostics;
333
+ /** Custom data resolver for non-entity AOT routes. */
334
+ aotDataResolver?: AotDataResolver;
321
335
  }
322
336
  /**
323
337
  * Create closure-based runtime fallback renderers for components
@@ -465,6 +479,14 @@ interface SSRHandlerOptions {
465
479
  * Load via `loadAotManifest(serverDir)` at startup.
466
480
  */
467
481
  aotManifest?: AotManifest;
482
+ /**
483
+ * Custom data resolver for AOT routes with non-entity data sources.
484
+ *
485
+ * Called after entity prefetch, with only the unresolved query keys.
486
+ * Enables AOT rendering for routes that use custom data layers
487
+ * (JSON files, third-party APIs, custom DB clients).
488
+ */
489
+ aotDataResolver?: AotDataResolver;
468
490
  }
469
491
  declare function createSSRHandler(options: SSRHandlerOptions): (request: Request) => Promise<Response>;
470
492
  type NodeHandlerOptions = SSRHandlerOptions;
@@ -491,8 +513,10 @@ interface AotRouteMapEntry {
491
513
  renderFn: string;
492
514
  /** Component names that need runtime fallback rendering. */
493
515
  holes: string[];
494
- /** Query cache keys this route reads via ctx.getData(). */
516
+ /** Query cache keys this route reads via ctx.getData(). May contain ${paramName} placeholders. */
495
517
  queryKeys: string[];
518
+ /** Route param names referenced in queryKeys. Present only when queryKeys have ${...} placeholders. */
519
+ paramBindings?: string[];
496
520
  }
497
521
  interface AotBuildManifest {
498
522
  components: Record<string, AotBuildComponentEntry>;
@@ -1134,4 +1158,4 @@ declare function collectStreamChunks(stream: ReadableStream<Uint8Array>): Promis
1134
1158
  * @param nonce - Optional CSP nonce to add to the inline script tag.
1135
1159
  */
1136
1160
  declare function createTemplateChunk(slotId: number, resolvedHtml: string, nonce?: string): string;
1137
- export { wrapWithHydrationMarkers, toPrefetchSession, streamToString, ssrStorage, ssrRenderToString, ssrRenderSinglePass, ssrRenderAot, ssrDiscoverQueries, setGlobalSSRTimeout, serializeToHtml, safeSerialize, resetSlotCounter, renderToStream, renderToHTMLStream, renderToHTML, renderPage, renderHeadToHtml, renderAssetTags, registerSSRQuery, reconstructDescriptors, rawHtml, matchUrlToPatterns, loadAotManifest, isInSSR, isAotDebugEnabled, inlineCriticalCss, getStreamingRuntimeScript, getSSRUrl, getSSRQueries, getGlobalSSRTimeout, getAccessSetForSSR, generateSSRHtml, generateAotBuildManifest, generateAotBarrel, extractRoutes, extractFontMetrics, evaluateAccessRule, encodeChunk, detectFallbackFont, createTemplateChunk, createSlotPlaceholder, createSessionScript, createSSRHandler, createSSRDataChunk, createSSRAdapter, createPrefetchManifestManager, createNodeHandler, createHoles, createAotManifestManager, createAccessSetScript, collectStreamChunks, clearGlobalSSRTimeout, buildAotRouteMap, __ssr_style_object, __ssr_spread, __esc_attr, __esc, VNode, SessionResolver, SessionData, SerializedAccessRule, SSRSinglePassOptions, SSRSessionInfo, SSRRenderResult, SSRRenderAotOptions, SSRQueryEntry2 as SSRQueryEntry, SSRPrefetchManifest, SSRModule, SSRHandlerOptions, SSRDiscoverResult, SSRAotContext, RenderToStreamOptions, RenderToHTMLStreamOptions, RenderToHTMLOptions, ReconstructedDescriptor, RawHtml, PrefetchSession, PrefetchManifestSnapshot, PrefetchManifestManagerOptions, PrefetchManifestManager, PageOptions, NodeHandlerOptions, MatchedRoute, MatchOptions, HydrationOptions, HeadEntry, HeadCollector, GenerateSSRHtmlOptions, FontFallbackMetrics7 as FontFallbackMetrics, FallbackFontName2 as FallbackFontName, ExtractedRoute, EntityAccessMap, AssetDescriptor, AotTier, AotRouteMapEntry, AotRouteEntry, AotRenderFn, AotManifestSnapshot, AotManifestManagerOptions, AotManifestManager, AotManifest, AotDivergenceEntry, AotDiagnosticsSnapshot, AotDiagnostics, AotDevManifest, AotDevComponentEntry, AotComponentDiagnostic, AotCompiledFile, AotBuildManifest, AotBuildComponentEntry, AotBarrelResult };
1161
+ export { wrapWithHydrationMarkers, toPrefetchSession, streamToString, ssrStorage, ssrRenderToString, ssrRenderSinglePass, ssrRenderAot, ssrDiscoverQueries, setGlobalSSRTimeout, serializeToHtml, safeSerialize, resetSlotCounter, renderToStream, renderToHTMLStream, renderToHTML, renderPage, renderHeadToHtml, renderAssetTags, registerSSRQuery, reconstructDescriptors, rawHtml, matchUrlToPatterns, loadAotManifest, isInSSR, isAotDebugEnabled, inlineCriticalCss, getStreamingRuntimeScript, getSSRUrl, getSSRQueries, getGlobalSSRTimeout, getAccessSetForSSR, generateSSRHtml, generateAotBuildManifest, generateAotBarrel, extractRoutes, extractFontMetrics, evaluateAccessRule, encodeChunk, detectFallbackFont, createTemplateChunk, createSlotPlaceholder, createSessionScript, createSSRHandler, createSSRDataChunk, createSSRAdapter, createPrefetchManifestManager, createNodeHandler, createHoles, createAotManifestManager, createAccessSetScript, collectStreamChunks, clearGlobalSSRTimeout, buildAotRouteMap, __ssr_style_object, __ssr_spread, __esc_attr, __esc, VNode, SessionResolver, SessionData, SerializedAccessRule, SSRSinglePassOptions, SSRSessionInfo, SSRRenderResult, SSRRenderAotOptions, SSRQueryEntry2 as SSRQueryEntry, SSRPrefetchManifest, SSRModule, SSRHandlerOptions, SSRDiscoverResult, SSRAotContext, RenderToStreamOptions, RenderToHTMLStreamOptions, RenderToHTMLOptions, ReconstructedDescriptor, RawHtml, PrefetchSession, PrefetchManifestSnapshot, PrefetchManifestManagerOptions, PrefetchManifestManager, PageOptions, NodeHandlerOptions, MatchedRoute, MatchOptions, HydrationOptions, HeadEntry, HeadCollector, GenerateSSRHtmlOptions, FontFallbackMetrics7 as FontFallbackMetrics, FallbackFontName2 as FallbackFontName, ExtractedRoute, EntityAccessMap, AssetDescriptor, AotTier, AotRouteMapEntry, AotRouteEntry, AotRenderFn, AotManifestSnapshot, AotManifestManagerOptions, AotManifestManager, AotManifest, AotDivergenceEntry, AotDiagnosticsSnapshot, AotDiagnostics, AotDevManifest, AotDevComponentEntry, AotDataResolver, AotComponentDiagnostic, AotCompiledFile, AotBuildManifest, AotBuildComponentEntry, AotBarrelResult };
package/dist/index.js CHANGED
@@ -1,10 +1,10 @@
1
1
  import {
2
2
  createSSRHandler,
3
3
  loadAotManifest
4
- } from "./shared/chunk-tqa618t8.js";
4
+ } from "./shared/chunk-rwa95knv.js";
5
5
  import {
6
6
  createNodeHandler
7
- } from "./shared/chunk-fv51ctcf.js";
7
+ } from "./shared/chunk-4qb2xcyb.js";
8
8
  import {
9
9
  collectStreamChunks,
10
10
  compileThemeCached,
@@ -34,7 +34,7 @@ import {
34
34
  ssrRenderToString,
35
35
  streamToString,
36
36
  toPrefetchSession
37
- } from "./shared/chunk-bggdy77b.js";
37
+ } from "./shared/chunk-vcpk8fs6.js";
38
38
  import {
39
39
  clearGlobalSSRTimeout,
40
40
  createSSRAdapter,
@@ -109,11 +109,22 @@ function buildAotRouteMap(components, routes) {
109
109
  const comp = components[route.componentName];
110
110
  if (!comp || comp.tier === "runtime-fallback")
111
111
  continue;
112
- routeMap[route.pattern] = {
112
+ const paramNames = new Set;
113
+ for (const key of comp.queryKeys) {
114
+ const matches = key.matchAll(/\$\{(\w+)\}/g);
115
+ for (const m of matches) {
116
+ paramNames.add(m[1]);
117
+ }
118
+ }
119
+ const entry = {
113
120
  renderFn: `__ssr_${route.componentName}`,
114
121
  holes: comp.holes,
115
122
  queryKeys: comp.queryKeys
116
123
  };
124
+ if (paramNames.size > 0) {
125
+ entry.paramBindings = [...paramNames];
126
+ }
127
+ routeMap[route.pattern] = entry;
117
128
  }
118
129
  return routeMap;
119
130
  }
@@ -484,7 +495,8 @@ async function twoPassRender(options) {
484
495
  }
485
496
  const pendingQueries = queries.filter((q) => !q.resolved);
486
497
  const vnode = options.app();
487
- const collectedCSS = getInjectedCSS();
498
+ const ssrCtx = ssrStorage.getStore();
499
+ const collectedCSS = ssrCtx?.cssTracker?.size ? Array.from(ssrCtx.cssTracker) : getInjectedCSS();
488
500
  const themeCss = options.theme ? compileThemeCached(options.theme, options.fallbackMetrics).css : "";
489
501
  const allStyles = [themeCss, ...options.styles ?? [], ...collectedCSS].filter(Boolean);
490
502
  const styleTags = allStyles.length > 0 ? `<style>${allStyles.join(`
@@ -115,6 +115,18 @@ interface AotManifest {
115
115
  /** Route pattern → AOT entry. */
116
116
  routes: Record<string, AotRouteEntry>;
117
117
  }
118
+ /**
119
+ * Resolves custom data for AOT-rendered routes.
120
+ *
121
+ * Called after the entity prefetch pipeline, before the `allKeysResolved` bail check.
122
+ * Only called when there are unresolved query keys remaining.
123
+ *
124
+ * @param pattern - Matched route pattern (e.g., '/products/:id')
125
+ * @param params - Route params (e.g., \{ id: 'abc-123' \})
126
+ * @param unresolvedKeys - Query keys not yet populated by entity prefetch
127
+ * @returns Map of cache key → data, or empty Map to skip
128
+ */
129
+ type AotDataResolver = (pattern: string, params: Record<string, string>, unresolvedKeys: string[]) => Promise<Map<string, unknown>> | Map<string, unknown>;
118
130
  import { AccessSet } from "@vertz/ui/auth";
119
131
  interface SessionData {
120
132
  user: {
@@ -217,6 +229,14 @@ interface SSRHandlerOptions {
217
229
  * Load via `loadAotManifest(serverDir)` at startup.
218
230
  */
219
231
  aotManifest?: AotManifest;
232
+ /**
233
+ * Custom data resolver for AOT routes with non-entity data sources.
234
+ *
235
+ * Called after entity prefetch, with only the unresolved query keys.
236
+ * Enables AOT rendering for routes that use custom data layers
237
+ * (JSON files, third-party APIs, custom DB clients).
238
+ */
239
+ aotDataResolver?: AotDataResolver;
220
240
  }
221
241
  type NodeHandlerOptions = SSRHandlerOptions;
222
242
  declare function createNodeHandler(options: NodeHandlerOptions): (req: IncomingMessage, res: ServerResponse) => void;
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  createNodeHandler
3
- } from "./shared/chunk-fv51ctcf.js";
4
- import"./shared/chunk-bggdy77b.js";
3
+ } from "./shared/chunk-4qb2xcyb.js";
4
+ import"./shared/chunk-vcpk8fs6.js";
5
5
  import"./shared/chunk-ybftdw1r.js";
6
6
  export {
7
7
  createNodeHandler
@@ -10,7 +10,7 @@ import {
10
10
  ssrRenderSinglePass,
11
11
  ssrStreamNavQueries,
12
12
  toPrefetchSession
13
- } from "./chunk-bggdy77b.js";
13
+ } from "./chunk-vcpk8fs6.js";
14
14
 
15
15
  // src/node-handler.ts
16
16
  function createNodeHandler(options) {
@@ -11,7 +11,7 @@ import {
11
11
  ssrRenderSinglePass,
12
12
  ssrStreamNavQueries,
13
13
  toPrefetchSession
14
- } from "./chunk-bggdy77b.js";
14
+ } from "./chunk-vcpk8fs6.js";
15
15
 
16
16
  // src/aot-manifest-loader.ts
17
17
  import { existsSync, readFileSync } from "node:fs";
@@ -108,7 +108,8 @@ function createSSRHandler(options) {
108
108
  sessionResolver,
109
109
  manifest,
110
110
  progressiveHTML,
111
- aotManifest
111
+ aotManifest,
112
+ aotDataResolver
112
113
  } = options;
113
114
  const { template, linkHeader, modulepreloadTags, splitResult } = precomputeHandlerState(options);
114
115
  return async (request) => {
@@ -128,7 +129,7 @@ function createSSRHandler(options) {
128
129
  if (useProgressive) {
129
130
  return handleProgressiveHTMLRequest(module, splitResult, pathname + url.search, ssrTimeout, nonce, fallbackMetrics, linkHeader, modulepreloadTags, routeChunkManifest, cacheControl, sessionScript, ssrAuth, manifest);
130
131
  }
131
- return handleHTMLRequest(module, template, pathname + url.search, ssrTimeout, nonce, fallbackMetrics, linkHeader, modulepreloadTags, routeChunkManifest, cacheControl, sessionScript, ssrAuth, manifest, aotManifest);
132
+ return handleHTMLRequest(module, template, pathname + url.search, ssrTimeout, nonce, fallbackMetrics, linkHeader, modulepreloadTags, routeChunkManifest, cacheControl, sessionScript, ssrAuth, manifest, aotManifest, aotDataResolver);
132
133
  };
133
134
  }
134
135
  async function handleNavRequest(module, url, ssrTimeout) {
@@ -217,7 +218,7 @@ async function handleProgressiveHTMLRequest(module, split, url, ssrTimeout, nonc
217
218
  });
218
219
  }
219
220
  }
220
- async function handleHTMLRequest(module, template, url, ssrTimeout, nonce, fallbackMetrics, linkHeader, staticModulepreloadTags, routeChunkManifest, cacheControl, sessionScript, ssrAuth, manifest, aotManifest) {
221
+ async function handleHTMLRequest(module, template, url, ssrTimeout, nonce, fallbackMetrics, linkHeader, staticModulepreloadTags, routeChunkManifest, cacheControl, sessionScript, ssrAuth, manifest, aotManifest, aotDataResolver) {
221
222
  try {
222
223
  const prefetchSession = ssrAuth ? toPrefetchSession(ssrAuth) : undefined;
223
224
  const result = aotManifest ? await ssrRenderAot(module, url, {
@@ -226,7 +227,8 @@ async function handleHTMLRequest(module, template, url, ssrTimeout, nonce, fallb
226
227
  ssrTimeout,
227
228
  fallbackMetrics,
228
229
  ssrAuth,
229
- prefetchSession
230
+ prefetchSession,
231
+ aotDataResolver
230
232
  }) : await ssrRenderSinglePass(module, url, {
231
233
  ssrTimeout,
232
234
  fallbackMetrics,
@@ -359,7 +359,8 @@ function createRequestContext(url) {
359
359
  queryCache: new MemoryCache({ maxSize: Infinity }),
360
360
  inflight: new Map,
361
361
  queries: [],
362
- errors: []
362
+ errors: [],
363
+ cssTracker: new Set
363
364
  };
364
365
  }
365
366
  var domShimInstalled = false;
@@ -384,7 +385,9 @@ function collectCSS(themeCss, module) {
384
385
  for (const s of module.styles)
385
386
  alreadyIncluded.add(s);
386
387
  }
387
- const componentCss = module.getInjectedCSS ? module.getInjectedCSS().filter((s) => !alreadyIncluded.has(s)) : [];
388
+ const ssrCtx = ssrStorage.getStore();
389
+ const rawComponentCss = ssrCtx?.cssTracker?.size ? Array.from(ssrCtx.cssTracker) : module.getInjectedCSS?.() ?? [];
390
+ const componentCss = rawComponentCss.filter((s) => !alreadyIncluded.has(s));
388
391
  const themeTag = themeCss ? `<style data-vertz-css>${themeCss}</style>` : "";
389
392
  const globalTag = module.styles && module.styles.length > 0 ? `<style data-vertz-css>${module.styles.join(`
390
393
  `)}</style>` : "";
@@ -1004,7 +1007,9 @@ function collectCSS2(themeCss, module) {
1004
1007
  for (const s of module.styles)
1005
1008
  alreadyIncluded.add(s);
1006
1009
  }
1007
- const componentCss = module.getInjectedCSS ? module.getInjectedCSS().filter((s) => !alreadyIncluded.has(s)) : [];
1010
+ const ssrCtx = ssrStorage.getStore();
1011
+ const rawComponentCss = ssrCtx?.cssTracker?.size ? Array.from(ssrCtx.cssTracker) : module.getInjectedCSS?.() ?? [];
1012
+ const componentCss = rawComponentCss.filter((s) => !alreadyIncluded.has(s));
1008
1013
  const themeTag = themeCss ? `<style data-vertz-css>${themeCss}</style>` : "";
1009
1014
  const globalTag = module.styles && module.styles.length > 0 ? `<style data-vertz-css>${module.styles.join(`
1010
1015
  `)}</style>` : "";
@@ -1076,14 +1081,28 @@ async function ssrRenderAot(module, url, options) {
1076
1081
  return ssrRenderSinglePass(module, normalizedUrl, fallbackOptions);
1077
1082
  }
1078
1083
  const queryCache = new Map;
1079
- if (aotEntry.queryKeys && aotEntry.queryKeys.length > 0 && manifest?.routeEntries) {
1084
+ const resolvedQueryKeys = aotEntry.queryKeys ? resolveParamQueryKeys(aotEntry.queryKeys, match.params) : undefined;
1085
+ if (resolvedQueryKeys && resolvedQueryKeys.length > 0 && manifest?.routeEntries) {
1080
1086
  const apiClient = module.api;
1081
1087
  if (apiClient) {
1082
- await prefetchForAot(aotEntry.queryKeys, manifest.routeEntries, match, apiClient, ssrTimeout, queryCache);
1088
+ await prefetchForAot(resolvedQueryKeys, manifest.routeEntries, match, apiClient, ssrTimeout, queryCache);
1083
1089
  }
1084
1090
  }
1085
- if (aotEntry.queryKeys && aotEntry.queryKeys.length > 0) {
1086
- const allKeysResolved = aotEntry.queryKeys.every((k) => queryCache.has(k));
1091
+ if (resolvedQueryKeys && resolvedQueryKeys.length > 0 && options.aotDataResolver) {
1092
+ const unresolvedKeys = resolvedQueryKeys.filter((k) => !queryCache.has(k));
1093
+ if (unresolvedKeys.length > 0) {
1094
+ try {
1095
+ const resolved = await options.aotDataResolver(match.pattern, match.params, unresolvedKeys);
1096
+ for (const [key, value] of resolved) {
1097
+ queryCache.set(key, value);
1098
+ }
1099
+ } catch {
1100
+ return ssrRenderSinglePass(module, normalizedUrl, fallbackOptions);
1101
+ }
1102
+ }
1103
+ }
1104
+ if (resolvedQueryKeys && resolvedQueryKeys.length > 0) {
1105
+ const allKeysResolved = resolvedQueryKeys.every((k) => queryCache.has(k));
1087
1106
  if (!allKeysResolved) {
1088
1107
  return ssrRenderSinglePass(module, normalizedUrl, fallbackOptions);
1089
1108
  }
@@ -1167,6 +1186,9 @@ function unwrapResult2(result) {
1167
1186
  }
1168
1187
  return result;
1169
1188
  }
1189
+ function resolveParamQueryKeys(queryKeys, params) {
1190
+ return queryKeys.map((key) => key.replace(/\$\{(\w+)\}/g, (_, paramName) => params[paramName] ?? ""));
1191
+ }
1170
1192
  function isAotDebugEnabled() {
1171
1193
  const env = process.env.VERTZ_DEBUG;
1172
1194
  if (!env)
@@ -1199,7 +1221,9 @@ function collectCSSFromModule(module, fallbackMetrics) {
1199
1221
  for (const s of module.styles)
1200
1222
  alreadyIncluded.add(s);
1201
1223
  }
1202
- const componentCss = module.getInjectedCSS ? module.getInjectedCSS().filter((s) => !alreadyIncluded.has(s)) : [];
1224
+ const ssrCtx = ssrStorage.getStore();
1225
+ const rawComponentCss = ssrCtx?.cssTracker?.size ? Array.from(ssrCtx.cssTracker) : module.getInjectedCSS?.() ?? [];
1226
+ const componentCss = rawComponentCss.filter((s) => !alreadyIncluded.has(s));
1203
1227
  const themeTag = themeCss ? `<style data-vertz-css>${themeCss}</style>` : "";
1204
1228
  const globalTag = module.styles && module.styles.length > 0 ? `<style data-vertz-css>${module.styles.join(`
1205
1229
  `)}</style>` : "";
@@ -162,6 +162,18 @@ interface AotManifest {
162
162
  routes: Record<string, AotRouteEntry>;
163
163
  }
164
164
  /**
165
+ * Resolves custom data for AOT-rendered routes.
166
+ *
167
+ * Called after the entity prefetch pipeline, before the `allKeysResolved` bail check.
168
+ * Only called when there are unresolved query keys remaining.
169
+ *
170
+ * @param pattern - Matched route pattern (e.g., '/products/:id')
171
+ * @param params - Route params (e.g., \{ id: 'abc-123' \})
172
+ * @param unresolvedKeys - Query keys not yet populated by entity prefetch
173
+ * @returns Map of cache key → data, or empty Map to skip
174
+ */
175
+ type AotDataResolver = (pattern: string, params: Record<string, string>, unresolvedKeys: string[]) => Promise<Map<string, unknown>> | Map<string, unknown>;
176
+ /**
165
177
  * Load AOT manifest and routes module from a server build directory.
166
178
  *
167
179
  * Returns `null` if either `aot-manifest.json` or `aot-routes.js` is missing,
@@ -335,6 +347,14 @@ interface SSRHandlerOptions {
335
347
  * Load via `loadAotManifest(serverDir)` at startup.
336
348
  */
337
349
  aotManifest?: AotManifest;
350
+ /**
351
+ * Custom data resolver for AOT routes with non-entity data sources.
352
+ *
353
+ * Called after entity prefetch, with only the unresolved query keys.
354
+ * Enables AOT rendering for routes that use custom data layers
355
+ * (JSON files, third-party APIs, custom DB clients).
356
+ */
357
+ aotDataResolver?: AotDataResolver;
338
358
  }
339
359
  declare function createSSRHandler(options: SSRHandlerOptions): (request: Request) => Promise<Response>;
340
360
  interface InjectIntoTemplateOptions {
@@ -357,4 +377,4 @@ interface InjectIntoTemplateOptions {
357
377
  * injects CSS before </head>, and ssrData before </body>.
358
378
  */
359
379
  declare function injectIntoTemplate(options: InjectIntoTemplateOptions): string;
360
- export { stripScriptsFromStaticHTML, ssrRenderToString, ssrDiscoverQueries, prerenderRoutes, loadAotManifest, injectIntoTemplate, filterPrerenderableRoutes, discoverRoutes, createSSRHandler, collectPrerenderPaths, SSRRenderResult, SSRModule, SSRHandlerOptions, SSRDiscoverResult, PrerenderResult, PrerenderOptions, AotManifest };
380
+ export { stripScriptsFromStaticHTML, ssrRenderToString, ssrDiscoverQueries, prerenderRoutes, loadAotManifest, injectIntoTemplate, filterPrerenderableRoutes, discoverRoutes, createSSRHandler, collectPrerenderPaths, SSRRenderResult, SSRModule, SSRHandlerOptions, SSRDiscoverResult, PrerenderResult, PrerenderOptions, AotManifest, AotDataResolver };
package/dist/ssr/index.js CHANGED
@@ -1,12 +1,12 @@
1
1
  import {
2
2
  createSSRHandler,
3
3
  loadAotManifest
4
- } from "../shared/chunk-tqa618t8.js";
4
+ } from "../shared/chunk-rwa95knv.js";
5
5
  import {
6
6
  injectIntoTemplate,
7
7
  ssrDiscoverQueries,
8
8
  ssrRenderToString
9
- } from "../shared/chunk-bggdy77b.js";
9
+ } from "../shared/chunk-vcpk8fs6.js";
10
10
  import"../shared/chunk-ybftdw1r.js";
11
11
  // src/prerender.ts
12
12
  async function discoverRoutes(module) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vertz/ui-server",
3
- "version": "0.2.35",
3
+ "version": "0.2.37",
4
4
  "description": "Vertz UI server-side rendering runtime",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -67,9 +67,9 @@
67
67
  "@ampproject/remapping": "^2.3.0",
68
68
  "@capsizecss/unpack": "^4.0.0",
69
69
  "@jridgewell/trace-mapping": "^0.3.31",
70
- "@vertz/core": "^0.2.31",
71
- "@vertz/ui": "^0.2.31",
72
- "@vertz/ui-compiler": "^0.2.31",
70
+ "@vertz/core": "^0.2.35",
71
+ "@vertz/ui": "^0.2.35",
72
+ "@vertz/ui-compiler": "^0.2.35",
73
73
  "magic-string": "^0.30.0",
74
74
  "sharp": "^0.34.5",
75
75
  "ts-morph": "^27.0.2"
@@ -77,7 +77,7 @@
77
77
  "devDependencies": {
78
78
  "@happy-dom/global-registrator": "^20.8.3",
79
79
  "@playwright/test": "^1.58.2",
80
- "@vertz/codegen": "^0.2.31",
80
+ "@vertz/codegen": "^0.2.35",
81
81
  "@vertz/ui-auth": "^0.2.19",
82
82
  "bun-types": "^1.3.10",
83
83
  "bunup": "^0.16.31",