@vertz/ui-server 0.2.34 → 0.2.36

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 };
@@ -2134,11 +2134,12 @@ function resolveQueryBindings(bindings, routeParams) {
2134
2134
  }
2135
2135
 
2136
2136
  // src/ssr-route-matcher.ts
2137
- function matchUrlToPatterns(url, patterns) {
2137
+ function matchUrlToPatterns(url, patterns, options) {
2138
2138
  const path = (url.split("?")[0] ?? "").split("#")[0] ?? "";
2139
+ const exact = options?.exact ?? false;
2139
2140
  const matches = [];
2140
2141
  for (const pattern of patterns) {
2141
- const result = matchPattern(path, pattern);
2142
+ const result = matchPattern(path, pattern, exact);
2142
2143
  if (result) {
2143
2144
  matches.push(result);
2144
2145
  }
@@ -2150,11 +2151,16 @@ function matchUrlToPatterns(url, patterns) {
2150
2151
  });
2151
2152
  return matches;
2152
2153
  }
2153
- function matchPattern(path, pattern) {
2154
+ function matchPattern(path, pattern, exact) {
2154
2155
  const pathSegments = path.split("/").filter(Boolean);
2155
2156
  const patternSegments = pattern.split("/").filter(Boolean);
2156
- if (patternSegments.length > pathSegments.length)
2157
- return;
2157
+ if (exact) {
2158
+ if (patternSegments.length !== pathSegments.length)
2159
+ return;
2160
+ } else {
2161
+ if (patternSegments.length > pathSegments.length)
2162
+ return;
2163
+ }
2158
2164
  const params = {};
2159
2165
  for (let i = 0;i < patternSegments.length; i++) {
2160
2166
  const seg = patternSegments[i];
@@ -2553,6 +2559,19 @@ var STALE_GRAPH_PATTERNS = [
2553
2559
  function isStaleGraphError(message) {
2554
2560
  return STALE_GRAPH_PATTERNS.some((pattern) => pattern.test(message));
2555
2561
  }
2562
+ var PLUGIN_ERROR_PATTERN = /\[vertz-bun-plugin\] Failed to process (.+?): ([\s\S]*)/;
2563
+ var LINE_COL_PATTERN = /\((\d+):(\d+)\)/;
2564
+ function parsePluginError(text) {
2565
+ const match = text.match(PLUGIN_ERROR_PATTERN);
2566
+ if (!match)
2567
+ return null;
2568
+ const file = match[1];
2569
+ const message = match[2]?.trim() || "Compilation failed";
2570
+ const locMatch = message.match(LINE_COL_PATTERN);
2571
+ const line = locMatch ? Number(locMatch[1]) : undefined;
2572
+ const column = locMatch ? Number(locMatch[2]) : undefined;
2573
+ return { message, file, line, column };
2574
+ }
2556
2575
  function isReloadStub(text) {
2557
2576
  return text.trimStart().startsWith("try{location.reload()}");
2558
2577
  }
@@ -3119,7 +3138,13 @@ function createBunDevServer(options) {
3119
3138
  const text = args.map((a) => typeof a === "string" ? a : String(a)).join(" ");
3120
3139
  if (!text.startsWith("[Server]")) {
3121
3140
  lastBuildError = text;
3122
- if (resolvePatterns.some((p) => text.includes(p)) && text !== lastBroadcastedError) {
3141
+ const pluginErr = parsePluginError(text);
3142
+ if (pluginErr && text !== lastBroadcastedError) {
3143
+ lastBroadcastedError = text;
3144
+ const absFile = pluginErr.file ? resolve(projectRoot, pluginErr.file) : undefined;
3145
+ const lineText = pluginErr.line && absFile ? readLineText(absFile, pluginErr.line) : undefined;
3146
+ broadcastError("build", [{ ...pluginErr, absFile, lineText }]);
3147
+ } else if (resolvePatterns.some((p) => text.includes(p)) && text !== lastBroadcastedError) {
3123
3148
  lastBroadcastedError = text;
3124
3149
  broadcastError("resolve", [{ message: text }]);
3125
3150
  } else {
@@ -4190,6 +4215,7 @@ data: {}
4190
4215
  }
4191
4216
  export {
4192
4217
  shouldCheckStaleBundler,
4218
+ parsePluginError,
4193
4219
  parseHMRAssets,
4194
4220
  isStaleGraphError,
4195
4221
  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;
@@ -1069,13 +1091,21 @@ interface MatchedRoute {
1069
1091
  /** Extracted route parameter values (e.g., { projectId: 'abc123' }) */
1070
1092
  params: Record<string, string>;
1071
1093
  }
1094
+ interface MatchOptions {
1095
+ /**
1096
+ * When true, require exact segment count match (no prefix matching).
1097
+ * Use for AOT routes (page-level). When false (default), prefix matching
1098
+ * is used for layout matching where `/` matches all nested routes.
1099
+ */
1100
+ exact?: boolean;
1101
+ }
1072
1102
  /**
1073
1103
  * Match a URL path against a list of route patterns.
1074
1104
  * Returns all matching patterns (layouts + page) ordered from most general to most specific.
1075
1105
  *
1076
1106
  * Patterns use Express-style `:param` syntax.
1077
1107
  */
1078
- declare function matchUrlToPatterns(url: string, patterns: string[]): MatchedRoute[];
1108
+ declare function matchUrlToPatterns(url: string, patterns: string[], options?: MatchOptions): MatchedRoute[];
1079
1109
  /**
1080
1110
  * Serialize data to JSON with `<` escaped as `\u003c`.
1081
1111
  * Prevents `<\/script>` breakout and `<!--` injection in inline scripts.
@@ -1126,4 +1156,4 @@ declare function collectStreamChunks(stream: ReadableStream<Uint8Array>): Promis
1126
1156
  * @param nonce - Optional CSP nonce to add to the inline script tag.
1127
1157
  */
1128
1158
  declare function createTemplateChunk(slotId: number, resolvedHtml: string, nonce?: string): string;
1129
- 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, 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 };
1159
+ 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-2kx402c1.js";
4
+ } from "./shared/chunk-hjsbx25c.js";
5
5
  import {
6
6
  createNodeHandler
7
- } from "./shared/chunk-xdb8qn68.js";
7
+ } from "./shared/chunk-9s2dppdh.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-hx7drzm3.js";
37
+ } from "./shared/chunk-wq2ke61r.js";
38
38
  import {
39
39
  clearGlobalSSRTimeout,
40
40
  createSSRAdapter,
@@ -137,7 +137,9 @@ function generateAotBarrel(compiledFiles, routeMap) {
137
137
  existing.push(fnName);
138
138
  fileToFns.set(filePath, existing);
139
139
  }
140
- const lines = [];
140
+ const lines = [
141
+ "import { __esc, __esc_attr, __ssr_spread, __ssr_style_object } from '@vertz/ui-server';"
142
+ ];
141
143
  const files = {};
142
144
  let fileIndex = 0;
143
145
  for (const [filePath, fns] of fileToFns) {
@@ -147,7 +149,9 @@ function generateAotBarrel(compiledFiles, routeMap) {
147
149
  lines.push(`export { ${fns.sort().join(", ")} } from '${moduleRef}';`);
148
150
  const compiled = compiledFiles[filePath];
149
151
  if (compiled) {
150
- files[`${tempFileName}.tsx`] = compiled.code;
152
+ const helperImport = `import { __esc, __esc_attr, __ssr_spread, __ssr_style_object } from '@vertz/ui-server';
153
+ `;
154
+ files[`${tempFileName}.tsx`] = helperImport + compiled.code;
151
155
  }
152
156
  fileIndex++;
153
157
  }
@@ -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-xdb8qn68.js";
4
- import"./shared/chunk-hx7drzm3.js";
3
+ } from "./shared/chunk-9s2dppdh.js";
4
+ import"./shared/chunk-wq2ke61r.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-hx7drzm3.js";
13
+ } from "./chunk-wq2ke61r.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-hx7drzm3.js";
14
+ } from "./chunk-wq2ke61r.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,
@@ -626,11 +626,12 @@ data: ${safeSerialize(entry)}
626
626
  }
627
627
 
628
628
  // src/ssr-route-matcher.ts
629
- function matchUrlToPatterns(url, patterns) {
629
+ function matchUrlToPatterns(url, patterns, options) {
630
630
  const path = (url.split("?")[0] ?? "").split("#")[0] ?? "";
631
+ const exact = options?.exact ?? false;
631
632
  const matches = [];
632
633
  for (const pattern of patterns) {
633
- const result = matchPattern(path, pattern);
634
+ const result = matchPattern(path, pattern, exact);
634
635
  if (result) {
635
636
  matches.push(result);
636
637
  }
@@ -642,11 +643,16 @@ function matchUrlToPatterns(url, patterns) {
642
643
  });
643
644
  return matches;
644
645
  }
645
- function matchPattern(path, pattern) {
646
+ function matchPattern(path, pattern, exact) {
646
647
  const pathSegments = path.split("/").filter(Boolean);
647
648
  const patternSegments = pattern.split("/").filter(Boolean);
648
- if (patternSegments.length > pathSegments.length)
649
- return;
649
+ if (exact) {
650
+ if (patternSegments.length !== pathSegments.length)
651
+ return;
652
+ } else {
653
+ if (patternSegments.length > pathSegments.length)
654
+ return;
655
+ }
650
656
  const params = {};
651
657
  for (let i = 0;i < patternSegments.length; i++) {
652
658
  const seg = patternSegments[i];
@@ -1057,7 +1063,7 @@ async function ssrRenderAot(module, url, options) {
1057
1063
  prefetchSession: options.prefetchSession
1058
1064
  };
1059
1065
  const aotPatterns = Object.keys(aotManifest.routes);
1060
- const matches = matchUrlToPatterns(normalizedUrl, aotPatterns);
1066
+ const matches = matchUrlToPatterns(normalizedUrl, aotPatterns, { exact: true });
1061
1067
  if (matches.length === 0) {
1062
1068
  return ssrRenderSinglePass(module, normalizedUrl, fallbackOptions);
1063
1069
  }
@@ -1076,6 +1082,25 @@ async function ssrRenderAot(module, url, options) {
1076
1082
  await prefetchForAot(aotEntry.queryKeys, manifest.routeEntries, match, apiClient, ssrTimeout, queryCache);
1077
1083
  }
1078
1084
  }
1085
+ if (aotEntry.queryKeys && aotEntry.queryKeys.length > 0 && options.aotDataResolver) {
1086
+ const unresolvedKeys = aotEntry.queryKeys.filter((k) => !queryCache.has(k));
1087
+ if (unresolvedKeys.length > 0) {
1088
+ try {
1089
+ const resolved = await options.aotDataResolver(match.pattern, match.params, unresolvedKeys);
1090
+ for (const [key, value] of resolved) {
1091
+ queryCache.set(key, value);
1092
+ }
1093
+ } catch {
1094
+ return ssrRenderSinglePass(module, normalizedUrl, fallbackOptions);
1095
+ }
1096
+ }
1097
+ }
1098
+ if (aotEntry.queryKeys && aotEntry.queryKeys.length > 0) {
1099
+ const allKeysResolved = aotEntry.queryKeys.every((k) => queryCache.has(k));
1100
+ if (!allKeysResolved) {
1101
+ return ssrRenderSinglePass(module, normalizedUrl, fallbackOptions);
1102
+ }
1103
+ }
1079
1104
  try {
1080
1105
  setGlobalSSRTimeout(ssrTimeout);
1081
1106
  const holes = createHoles(aotEntry.holes, module, normalizedUrl, queryCache, options.ssrAuth);
@@ -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-2kx402c1.js";
4
+ } from "../shared/chunk-hjsbx25c.js";
5
5
  import {
6
6
  injectIntoTemplate,
7
7
  ssrDiscoverQueries,
8
8
  ssrRenderToString
9
- } from "../shared/chunk-hx7drzm3.js";
9
+ } from "../shared/chunk-wq2ke61r.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.34",
3
+ "version": "0.2.36",
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",