@vertz/ui-server 0.2.24 → 0.2.26

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.
package/dist/index.js CHANGED
@@ -19,20 +19,78 @@ import {
19
19
  ssrDiscoverQueries,
20
20
  ssrRenderToString,
21
21
  streamToString
22
- } from "./shared/chunk-g0zqrb60.js";
22
+ } from "./shared/chunk-yr65qdge.js";
23
23
  import {
24
24
  clearGlobalSSRTimeout,
25
25
  createSSRAdapter,
26
26
  getGlobalSSRTimeout,
27
27
  getSSRQueries,
28
28
  getSSRUrl,
29
+ installDomShim,
29
30
  isInSSR,
30
31
  rawHtml,
31
32
  registerSSRQuery,
32
33
  setGlobalSSRTimeout,
33
- ssrStorage
34
- } from "./shared/chunk-zs75v8qj.js";
34
+ ssrStorage,
35
+ toVNode
36
+ } from "./shared/chunk-gcwqkynf.js";
35
37
 
38
+ // src/aot-manifest-build.ts
39
+ import { readdirSync, readFileSync } from "node:fs";
40
+ import { join } from "node:path";
41
+ import { compileForSSRAot } from "@vertz/ui-compiler";
42
+ function generateAotBuildManifest(srcDir) {
43
+ const components = {};
44
+ const classificationLog = [];
45
+ const tsxFiles = collectTsxFiles(srcDir);
46
+ for (const filePath of tsxFiles) {
47
+ try {
48
+ const source = readFileSync(filePath, "utf-8");
49
+ const result = compileForSSRAot(source, { filename: filePath });
50
+ for (const comp of result.components) {
51
+ components[comp.name] = {
52
+ tier: comp.tier,
53
+ holes: comp.holes
54
+ };
55
+ }
56
+ } catch (e) {
57
+ classificationLog.push(`⚠ ${filePath}: ${e instanceof Error ? e.message : "compilation failed"}`);
58
+ }
59
+ }
60
+ let aotCount = 0;
61
+ let runtimeCount = 0;
62
+ for (const [name, entry] of Object.entries(components)) {
63
+ let line = `${name}: ${entry.tier}`;
64
+ if (entry.holes.length > 0) {
65
+ const holeLabel = entry.holes.length === 1 ? "hole" : "holes";
66
+ line += `, ${entry.holes.length} ${holeLabel} (${entry.holes.join(", ")})`;
67
+ }
68
+ classificationLog.push(line);
69
+ if (entry.tier === "runtime-fallback") {
70
+ runtimeCount++;
71
+ } else {
72
+ aotCount++;
73
+ }
74
+ }
75
+ const total = aotCount + runtimeCount;
76
+ if (total > 0) {
77
+ const pct = Math.round(aotCount / total * 100);
78
+ classificationLog.push(`Coverage: ${aotCount}/${total} components (${pct}%)`);
79
+ }
80
+ return { components, classificationLog };
81
+ }
82
+ function collectTsxFiles(dir) {
83
+ const files = [];
84
+ for (const entry of readdirSync(dir, { withFileTypes: true })) {
85
+ const fullPath = join(dir, entry.name);
86
+ if (entry.isDirectory()) {
87
+ files.push(...collectTsxFiles(fullPath));
88
+ } else if (entry.name.endsWith(".tsx")) {
89
+ files.push(fullPath);
90
+ }
91
+ }
92
+ return files;
93
+ }
36
94
  // src/asset-pipeline.ts
37
95
  function renderAssetTags(assets) {
38
96
  if (assets.length === 0)
@@ -61,7 +119,7 @@ function inlineCriticalCss(css) {
61
119
  // src/font-metrics.ts
62
120
  import { access as fsAccess } from "node:fs/promises";
63
121
  import { readFile } from "node:fs/promises";
64
- import { join } from "node:path";
122
+ import { join as join2 } from "node:path";
65
123
  import { fromBuffer } from "@capsizecss/unpack";
66
124
  var SYSTEM_FONT_METRICS = {
67
125
  Arial: {
@@ -130,12 +188,12 @@ function getPrimarySrcPath(descriptor) {
130
188
  }
131
189
  async function resolveFilePath(urlPath, rootDir) {
132
190
  const cleaned = urlPath.startsWith("/") ? urlPath.slice(1) : urlPath;
133
- const direct = join(rootDir, cleaned);
191
+ const direct = join2(rootDir, cleaned);
134
192
  try {
135
193
  await fsAccess(direct);
136
194
  return direct;
137
195
  } catch {
138
- return join(rootDir, "public", cleaned);
196
+ return join2(rootDir, "public", cleaned);
139
197
  }
140
198
  }
141
199
  async function extractFontMetrics(fonts, rootDir) {
@@ -425,6 +483,897 @@ async function renderToHTML(appOrOptions, maybeOptions) {
425
483
  }
426
484
  });
427
485
  }
486
+ // src/ssr-access-evaluator.ts
487
+ function toPrefetchSession(ssrAuth) {
488
+ if (!ssrAuth || ssrAuth.status !== "authenticated" || !ssrAuth.user) {
489
+ return { status: "unauthenticated" };
490
+ }
491
+ const roles = ssrAuth.user.role ? [ssrAuth.user.role] : undefined;
492
+ return {
493
+ status: "authenticated",
494
+ roles,
495
+ tenantId: ssrAuth.user.tenantId
496
+ };
497
+ }
498
+ function evaluateAccessRule(rule, session) {
499
+ switch (rule.type) {
500
+ case "public":
501
+ return true;
502
+ case "authenticated":
503
+ return session.status === "authenticated";
504
+ case "role":
505
+ if (session.status !== "authenticated")
506
+ return false;
507
+ return session.roles?.some((r) => rule.roles.includes(r)) === true;
508
+ case "entitlement":
509
+ if (session.status !== "authenticated")
510
+ return false;
511
+ return session.entitlements?.[rule.value] === true;
512
+ case "where":
513
+ return true;
514
+ case "fva":
515
+ return session.status === "authenticated";
516
+ case "deny":
517
+ return false;
518
+ case "all":
519
+ return rule.rules.every((r) => evaluateAccessRule(r, session));
520
+ case "any":
521
+ return rule.rules.some((r) => evaluateAccessRule(r, session));
522
+ default:
523
+ return false;
524
+ }
525
+ }
526
+ // src/ssr-aot-diagnostics.ts
527
+ var MAX_DIVERGENCES = 20;
528
+
529
+ class AotDiagnostics {
530
+ _components = new Map;
531
+ _divergences = [];
532
+ recordCompilation(components) {
533
+ for (const comp of components) {
534
+ this._components.set(comp.name, {
535
+ tier: comp.tier,
536
+ holes: comp.holes
537
+ });
538
+ }
539
+ }
540
+ recordDivergence(component, aotHtml, domHtml) {
541
+ this._divergences.push({
542
+ component,
543
+ aotHtml,
544
+ domHtml,
545
+ timestamp: new Date().toISOString()
546
+ });
547
+ if (this._divergences.length > MAX_DIVERGENCES) {
548
+ this._divergences = this._divergences.slice(this._divergences.length - MAX_DIVERGENCES);
549
+ }
550
+ }
551
+ clear() {
552
+ this._components.clear();
553
+ this._divergences = [];
554
+ }
555
+ clearComponents() {
556
+ this._components.clear();
557
+ }
558
+ getClassificationLog() {
559
+ const lines = [];
560
+ for (const [name, comp] of this._components) {
561
+ let line = `${name}: ${comp.tier}`;
562
+ if (comp.holes.length > 0) {
563
+ line += `, ${comp.holes.length} hole${comp.holes.length > 1 ? "s" : ""} (${comp.holes.join(", ")})`;
564
+ }
565
+ lines.push(line);
566
+ }
567
+ const snapshot = this.getSnapshot();
568
+ const { total, aot, percentage } = snapshot.coverage;
569
+ if (total > 0) {
570
+ lines.push(`Coverage: ${aot}/${total} components (${percentage}%)`);
571
+ }
572
+ return lines;
573
+ }
574
+ getSnapshot() {
575
+ let aot = 0;
576
+ let runtime = 0;
577
+ for (const comp of this._components.values()) {
578
+ if (comp.tier === "runtime-fallback") {
579
+ runtime++;
580
+ } else {
581
+ aot++;
582
+ }
583
+ }
584
+ const total = aot + runtime;
585
+ return {
586
+ components: Object.fromEntries(this._components),
587
+ coverage: {
588
+ total,
589
+ aot,
590
+ runtime,
591
+ percentage: total === 0 ? 0 : Math.round(aot / total * 100)
592
+ },
593
+ divergences: [...this._divergences]
594
+ };
595
+ }
596
+ }
597
+ // src/ssr-aot-manifest-dev.ts
598
+ import { compileForSSRAot as compileForSSRAot2 } from "@vertz/ui-compiler";
599
+ function createAotManifestManager(options) {
600
+ const { readFile: readFile2, listFiles } = options;
601
+ let currentManifest = null;
602
+ const diagnostics = new AotDiagnostics;
603
+ let rebuildCount = 0;
604
+ let lastRebuildMs = null;
605
+ let lastRebuildAt = null;
606
+ function compileFile(filePath, source) {
607
+ try {
608
+ const result = compileForSSRAot2(source, { filename: filePath });
609
+ return result.components.map((comp) => ({
610
+ name: comp.name,
611
+ entry: {
612
+ tier: comp.tier,
613
+ holes: comp.holes,
614
+ file: filePath
615
+ }
616
+ }));
617
+ } catch {
618
+ return [];
619
+ }
620
+ }
621
+ function updateDiagnostics(manifest, isFullBuild) {
622
+ if (isFullBuild) {
623
+ diagnostics.clear();
624
+ } else {
625
+ diagnostics.clearComponents();
626
+ }
627
+ const entries = Object.entries(manifest.components).map(([name, entry]) => ({
628
+ name,
629
+ tier: entry.tier,
630
+ holes: entry.holes
631
+ }));
632
+ diagnostics.recordCompilation(entries);
633
+ }
634
+ function fullBuild() {
635
+ const start = performance.now();
636
+ const files = listFiles();
637
+ const components = {};
638
+ for (const filePath of files) {
639
+ if (!filePath.endsWith(".tsx"))
640
+ continue;
641
+ const source = readFile2(filePath);
642
+ if (!source)
643
+ continue;
644
+ const entries = compileFile(filePath, source);
645
+ for (const { name, entry } of entries) {
646
+ components[name] = entry;
647
+ }
648
+ }
649
+ currentManifest = { components };
650
+ updateDiagnostics(currentManifest, true);
651
+ rebuildCount++;
652
+ lastRebuildMs = Math.round(performance.now() - start);
653
+ lastRebuildAt = new Date().toISOString();
654
+ }
655
+ function incrementalUpdate(filePath, sourceText) {
656
+ if (!currentManifest)
657
+ return;
658
+ const start = performance.now();
659
+ const newComponents = { ...currentManifest.components };
660
+ for (const [name, entry] of Object.entries(newComponents)) {
661
+ if (entry.file === filePath) {
662
+ delete newComponents[name];
663
+ }
664
+ }
665
+ if (sourceText.trim()) {
666
+ const entries = compileFile(filePath, sourceText);
667
+ for (const { name, entry } of entries) {
668
+ newComponents[name] = entry;
669
+ }
670
+ }
671
+ currentManifest = { components: newComponents };
672
+ updateDiagnostics(currentManifest, false);
673
+ rebuildCount++;
674
+ lastRebuildMs = Math.round(performance.now() - start);
675
+ lastRebuildAt = new Date().toISOString();
676
+ }
677
+ return {
678
+ build() {
679
+ fullBuild();
680
+ },
681
+ onFileChange(filePath, sourceText) {
682
+ if (!filePath.endsWith(".tsx"))
683
+ return;
684
+ incrementalUpdate(filePath, sourceText);
685
+ },
686
+ getManifest() {
687
+ return currentManifest;
688
+ },
689
+ getSnapshot() {
690
+ return {
691
+ manifest: currentManifest,
692
+ rebuildCount,
693
+ lastRebuildMs,
694
+ lastRebuildAt
695
+ };
696
+ },
697
+ getDiagnostics() {
698
+ return diagnostics;
699
+ }
700
+ };
701
+ }
702
+ // src/ssr-aot-pipeline.ts
703
+ import { compileTheme as compileTheme3 } from "@vertz/ui";
704
+
705
+ // src/ssr-route-matcher.ts
706
+ function matchUrlToPatterns(url, patterns) {
707
+ const path = (url.split("?")[0] ?? "").split("#")[0] ?? "";
708
+ const matches = [];
709
+ for (const pattern of patterns) {
710
+ const result = matchPattern(path, pattern);
711
+ if (result) {
712
+ matches.push(result);
713
+ }
714
+ }
715
+ matches.sort((a, b) => {
716
+ const aSegments = a.pattern.split("/").length;
717
+ const bSegments = b.pattern.split("/").length;
718
+ return aSegments - bSegments;
719
+ });
720
+ return matches;
721
+ }
722
+ function matchPattern(path, pattern) {
723
+ const pathSegments = path.split("/").filter(Boolean);
724
+ const patternSegments = pattern.split("/").filter(Boolean);
725
+ if (patternSegments.length > pathSegments.length)
726
+ return;
727
+ const params = {};
728
+ for (let i = 0;i < patternSegments.length; i++) {
729
+ const seg = patternSegments[i];
730
+ const val = pathSegments[i];
731
+ if (seg.startsWith(":")) {
732
+ params[seg.slice(1)] = val;
733
+ } else if (seg !== val) {
734
+ return;
735
+ }
736
+ }
737
+ return { pattern, params };
738
+ }
739
+
740
+ // src/ssr-single-pass.ts
741
+ import { compileTheme as compileTheme2 } from "@vertz/ui";
742
+
743
+ // src/ssr-manifest-prefetch.ts
744
+ function reconstructDescriptors(queries, routeParams, apiClient) {
745
+ if (!apiClient)
746
+ return [];
747
+ const result = [];
748
+ for (const query of queries) {
749
+ const descriptor = reconstructSingle(query, routeParams, apiClient);
750
+ if (descriptor) {
751
+ result.push(descriptor);
752
+ }
753
+ }
754
+ return result;
755
+ }
756
+ function reconstructSingle(query, routeParams, apiClient) {
757
+ const { entity, operation } = query;
758
+ if (!entity || !operation)
759
+ return;
760
+ const entitySdk = apiClient[entity];
761
+ if (!entitySdk)
762
+ return;
763
+ const method = entitySdk[operation];
764
+ if (typeof method !== "function")
765
+ return;
766
+ const args = buildFactoryArgs(query, routeParams);
767
+ if (args === undefined)
768
+ return;
769
+ try {
770
+ const descriptor = method(...args);
771
+ if (!descriptor || typeof descriptor._key !== "string" || typeof descriptor._fetch !== "function") {
772
+ return;
773
+ }
774
+ return { key: descriptor._key, fetch: descriptor._fetch };
775
+ } catch {
776
+ return;
777
+ }
778
+ }
779
+ function buildFactoryArgs(query, routeParams) {
780
+ const { operation, idParam, queryBindings } = query;
781
+ if (operation === "get") {
782
+ if (idParam) {
783
+ const id = routeParams[idParam];
784
+ if (!id)
785
+ return;
786
+ const options = resolveQueryBindings(queryBindings, routeParams);
787
+ if (options === undefined && queryBindings)
788
+ return;
789
+ return options ? [id, options] : [id];
790
+ }
791
+ return;
792
+ }
793
+ if (!queryBindings)
794
+ return [];
795
+ const resolved = resolveQueryBindings(queryBindings, routeParams);
796
+ if (resolved === undefined)
797
+ return;
798
+ return [resolved];
799
+ }
800
+ function resolveQueryBindings(bindings, routeParams) {
801
+ if (!bindings)
802
+ return;
803
+ const resolved = {};
804
+ if (bindings.where) {
805
+ const where = {};
806
+ for (const [key, value] of Object.entries(bindings.where)) {
807
+ if (value === null)
808
+ return;
809
+ if (typeof value === "string" && value.startsWith("$")) {
810
+ const paramName = value.slice(1);
811
+ const paramValue = routeParams[paramName];
812
+ if (!paramValue)
813
+ return;
814
+ where[key] = paramValue;
815
+ } else {
816
+ where[key] = value;
817
+ }
818
+ }
819
+ resolved.where = where;
820
+ }
821
+ if (bindings.select)
822
+ resolved.select = bindings.select;
823
+ if (bindings.include)
824
+ resolved.include = bindings.include;
825
+ if (bindings.orderBy)
826
+ resolved.orderBy = bindings.orderBy;
827
+ if (bindings.limit !== undefined)
828
+ resolved.limit = bindings.limit;
829
+ return resolved;
830
+ }
831
+
832
+ // src/ssr-single-pass.ts
833
+ async function ssrRenderSinglePass(module, url, options) {
834
+ if (options?.prefetch === false) {
835
+ return ssrRenderToString(module, url, options);
836
+ }
837
+ const normalizedUrl = url.endsWith("/index.html") ? url.slice(0, -"/index.html".length) || "/" : url;
838
+ const ssrTimeout = options?.ssrTimeout ?? 300;
839
+ ensureDomShim();
840
+ const zeroDiscoveryData = attemptZeroDiscovery(normalizedUrl, module, options, ssrTimeout);
841
+ if (zeroDiscoveryData) {
842
+ return renderWithPrefetchedData(module, normalizedUrl, zeroDiscoveryData, options);
843
+ }
844
+ const discoveryCtx = createRequestContext(normalizedUrl);
845
+ if (options?.ssrAuth) {
846
+ discoveryCtx.ssrAuth = options.ssrAuth;
847
+ }
848
+ const discoveredData = await ssrStorage.run(discoveryCtx, async () => {
849
+ try {
850
+ setGlobalSSRTimeout(ssrTimeout);
851
+ const createApp = resolveAppFactory(module);
852
+ createApp();
853
+ if (discoveryCtx.ssrRedirect) {
854
+ return { redirect: discoveryCtx.ssrRedirect };
855
+ }
856
+ if (discoveryCtx.pendingRouteComponents?.size) {
857
+ const entries = Array.from(discoveryCtx.pendingRouteComponents.entries());
858
+ const results = await Promise.allSettled(entries.map(([route, promise]) => Promise.race([
859
+ promise.then((mod) => ({ route, factory: mod.default })),
860
+ new Promise((_, reject) => setTimeout(() => reject(new Error("lazy route timeout")), ssrTimeout))
861
+ ])));
862
+ discoveryCtx.resolvedComponents = new Map;
863
+ for (const result of results) {
864
+ if (result.status === "fulfilled") {
865
+ const { route, factory } = result.value;
866
+ discoveryCtx.resolvedComponents.set(route, factory);
867
+ }
868
+ }
869
+ discoveryCtx.pendingRouteComponents = undefined;
870
+ }
871
+ const queries = getSSRQueries();
872
+ const eligibleQueries = filterByEntityAccess(queries, options?.manifest?.entityAccess, options?.prefetchSession);
873
+ const resolvedQueries = [];
874
+ if (eligibleQueries.length > 0) {
875
+ await Promise.allSettled(eligibleQueries.map(({ promise, timeout, resolve, key }) => Promise.race([
876
+ promise.then((data) => {
877
+ resolve(data);
878
+ resolvedQueries.push({ key, data });
879
+ return "resolved";
880
+ }),
881
+ new Promise((r) => setTimeout(r, timeout || ssrTimeout)).then(() => "timeout")
882
+ ])));
883
+ }
884
+ return {
885
+ resolvedQueries,
886
+ resolvedComponents: discoveryCtx.resolvedComponents
887
+ };
888
+ } finally {
889
+ clearGlobalSSRTimeout();
890
+ }
891
+ });
892
+ if ("redirect" in discoveredData) {
893
+ return {
894
+ html: "",
895
+ css: "",
896
+ ssrData: [],
897
+ headTags: "",
898
+ redirect: discoveredData.redirect
899
+ };
900
+ }
901
+ const renderCtx = createRequestContext(normalizedUrl);
902
+ if (options?.ssrAuth) {
903
+ renderCtx.ssrAuth = options.ssrAuth;
904
+ }
905
+ for (const { key, data } of discoveredData.resolvedQueries) {
906
+ renderCtx.queryCache.set(key, data);
907
+ }
908
+ renderCtx.resolvedComponents = discoveredData.resolvedComponents ?? new Map;
909
+ return ssrStorage.run(renderCtx, async () => {
910
+ try {
911
+ setGlobalSSRTimeout(ssrTimeout);
912
+ const createApp = resolveAppFactory(module);
913
+ let themeCss = "";
914
+ let themePreloadTags = "";
915
+ if (module.theme) {
916
+ try {
917
+ const compiled = compileTheme2(module.theme, {
918
+ fallbackMetrics: options?.fallbackMetrics
919
+ });
920
+ themeCss = compiled.css;
921
+ themePreloadTags = compiled.preloadTags;
922
+ } catch (e) {
923
+ console.error("[vertz] Failed to compile theme export. Ensure your theme is created with defineTheme().", e);
924
+ }
925
+ }
926
+ const app = createApp();
927
+ const vnode = toVNode(app);
928
+ const stream = renderToStream(vnode);
929
+ const html = await streamToString(stream);
930
+ const css = collectCSS(themeCss, module);
931
+ const ssrData = discoveredData.resolvedQueries.map(({ key, data }) => ({
932
+ key,
933
+ data: JSON.parse(JSON.stringify(data))
934
+ }));
935
+ return {
936
+ html,
937
+ css,
938
+ ssrData,
939
+ headTags: themePreloadTags,
940
+ discoveredRoutes: renderCtx.discoveredRoutes,
941
+ matchedRoutePatterns: renderCtx.matchedRoutePatterns
942
+ };
943
+ } finally {
944
+ clearGlobalSSRTimeout();
945
+ }
946
+ });
947
+ }
948
+ function attemptZeroDiscovery(url, module, options, ssrTimeout) {
949
+ const manifest = options?.manifest;
950
+ if (!manifest?.routeEntries || !module.api)
951
+ return null;
952
+ const matches = matchUrlToPatterns(url, manifest.routePatterns);
953
+ if (matches.length === 0)
954
+ return null;
955
+ const allQueries = [];
956
+ let mergedParams = {};
957
+ for (const match of matches) {
958
+ const entry = manifest.routeEntries[match.pattern];
959
+ if (entry) {
960
+ allQueries.push(...entry.queries);
961
+ }
962
+ mergedParams = { ...mergedParams, ...match.params };
963
+ }
964
+ if (allQueries.length === 0)
965
+ return null;
966
+ const descriptors = reconstructDescriptors(allQueries, mergedParams, module.api);
967
+ if (descriptors.length === 0)
968
+ return null;
969
+ return prefetchFromDescriptors(descriptors, ssrTimeout);
970
+ }
971
+ async function prefetchFromDescriptors(descriptors, ssrTimeout) {
972
+ const resolvedQueries = [];
973
+ await Promise.allSettled(descriptors.map(({ key, fetch: fetchFn }) => Promise.race([
974
+ fetchFn().then((result) => {
975
+ const data = unwrapResult(result);
976
+ resolvedQueries.push({ key, data });
977
+ return "resolved";
978
+ }),
979
+ new Promise((r) => setTimeout(r, ssrTimeout)).then(() => "timeout")
980
+ ])));
981
+ return { resolvedQueries };
982
+ }
983
+ function unwrapResult(result) {
984
+ if (result && typeof result === "object" && "ok" in result && "data" in result) {
985
+ const r = result;
986
+ if (r.ok)
987
+ return r.data;
988
+ }
989
+ return result;
990
+ }
991
+ async function renderWithPrefetchedData(module, normalizedUrl, prefetchedData, options) {
992
+ const data = await prefetchedData;
993
+ const ssrTimeout = options?.ssrTimeout ?? 300;
994
+ const renderCtx = createRequestContext(normalizedUrl);
995
+ if (options?.ssrAuth) {
996
+ renderCtx.ssrAuth = options.ssrAuth;
997
+ }
998
+ for (const { key, data: queryData } of data.resolvedQueries) {
999
+ renderCtx.queryCache.set(key, queryData);
1000
+ }
1001
+ renderCtx.resolvedComponents = new Map;
1002
+ return ssrStorage.run(renderCtx, async () => {
1003
+ try {
1004
+ setGlobalSSRTimeout(ssrTimeout);
1005
+ const createApp = resolveAppFactory(module);
1006
+ let themeCss = "";
1007
+ let themePreloadTags = "";
1008
+ if (module.theme) {
1009
+ try {
1010
+ const compiled = compileTheme2(module.theme, {
1011
+ fallbackMetrics: options?.fallbackMetrics
1012
+ });
1013
+ themeCss = compiled.css;
1014
+ themePreloadTags = compiled.preloadTags;
1015
+ } catch (e) {
1016
+ console.error("[vertz] Failed to compile theme export. Ensure your theme is created with defineTheme().", e);
1017
+ }
1018
+ }
1019
+ const app = createApp();
1020
+ const vnode = toVNode(app);
1021
+ const stream = renderToStream(vnode);
1022
+ const html = await streamToString(stream);
1023
+ if (renderCtx.ssrRedirect) {
1024
+ return {
1025
+ html: "",
1026
+ css: "",
1027
+ ssrData: [],
1028
+ headTags: "",
1029
+ redirect: renderCtx.ssrRedirect,
1030
+ discoveredRoutes: renderCtx.discoveredRoutes,
1031
+ matchedRoutePatterns: renderCtx.matchedRoutePatterns
1032
+ };
1033
+ }
1034
+ const css = collectCSS(themeCss, module);
1035
+ const ssrData = data.resolvedQueries.map(({ key, data: d }) => ({
1036
+ key,
1037
+ data: JSON.parse(JSON.stringify(d))
1038
+ }));
1039
+ return {
1040
+ html,
1041
+ css,
1042
+ ssrData,
1043
+ headTags: themePreloadTags,
1044
+ discoveredRoutes: renderCtx.discoveredRoutes,
1045
+ matchedRoutePatterns: renderCtx.matchedRoutePatterns
1046
+ };
1047
+ } finally {
1048
+ clearGlobalSSRTimeout();
1049
+ }
1050
+ });
1051
+ }
1052
+ var domShimInstalled = false;
1053
+ function ensureDomShim() {
1054
+ if (domShimInstalled && typeof document !== "undefined")
1055
+ return;
1056
+ domShimInstalled = true;
1057
+ installDomShim();
1058
+ }
1059
+ function resolveAppFactory(module) {
1060
+ const createApp = module.default || module.App;
1061
+ if (typeof createApp !== "function") {
1062
+ throw new Error("App entry must export a default function or named App function");
1063
+ }
1064
+ return createApp;
1065
+ }
1066
+ function filterByEntityAccess(queries, entityAccess, session) {
1067
+ if (!entityAccess || !session)
1068
+ return queries;
1069
+ return queries.filter(({ key }) => {
1070
+ const entity = extractEntityFromKey(key);
1071
+ const method = extractMethodFromKey(key);
1072
+ if (!entity)
1073
+ return true;
1074
+ const entityRules = entityAccess[entity];
1075
+ if (!entityRules)
1076
+ return true;
1077
+ const rule = entityRules[method];
1078
+ if (!rule)
1079
+ return true;
1080
+ return evaluateAccessRule(rule, session);
1081
+ });
1082
+ }
1083
+ function extractEntityFromKey(key) {
1084
+ const pathStart = key.indexOf(":/");
1085
+ if (pathStart === -1)
1086
+ return;
1087
+ const path = key.slice(pathStart + 2);
1088
+ const firstSlash = path.indexOf("/");
1089
+ const questionMark = path.indexOf("?");
1090
+ if (firstSlash === -1 && questionMark === -1)
1091
+ return path;
1092
+ if (firstSlash === -1)
1093
+ return path.slice(0, questionMark);
1094
+ if (questionMark === -1)
1095
+ return path.slice(0, firstSlash);
1096
+ return path.slice(0, Math.min(firstSlash, questionMark));
1097
+ }
1098
+ function extractMethodFromKey(key) {
1099
+ const pathStart = key.indexOf(":/");
1100
+ if (pathStart === -1)
1101
+ return "list";
1102
+ const path = key.slice(pathStart + 2);
1103
+ const cleanPath = path.split("?")[0] ?? "";
1104
+ const segments = cleanPath.split("/").filter(Boolean);
1105
+ return segments.length > 1 ? "get" : "list";
1106
+ }
1107
+ function collectCSS(themeCss, module) {
1108
+ const alreadyIncluded = new Set;
1109
+ if (themeCss)
1110
+ alreadyIncluded.add(themeCss);
1111
+ if (module.styles) {
1112
+ for (const s of module.styles)
1113
+ alreadyIncluded.add(s);
1114
+ }
1115
+ const componentCss = module.getInjectedCSS ? module.getInjectedCSS().filter((s) => !alreadyIncluded.has(s)) : [];
1116
+ const themeTag = themeCss ? `<style data-vertz-css>${themeCss}</style>` : "";
1117
+ const globalTag = module.styles && module.styles.length > 0 ? `<style data-vertz-css>${module.styles.join(`
1118
+ `)}</style>` : "";
1119
+ const componentTag = componentCss.length > 0 ? `<style data-vertz-css>${componentCss.join(`
1120
+ `)}</style>` : "";
1121
+ return [themeTag, globalTag, componentTag].filter(Boolean).join(`
1122
+ `);
1123
+ }
1124
+
1125
+ // src/ssr-aot-pipeline.ts
1126
+ function createHoles(holeNames, module, url, queryCache, ssrAuth) {
1127
+ if (holeNames.length === 0)
1128
+ return {};
1129
+ const holes = {};
1130
+ for (const name of holeNames) {
1131
+ holes[name] = () => {
1132
+ const holeCtx = createRequestContext(url);
1133
+ for (const [key, data] of queryCache) {
1134
+ holeCtx.queryCache.set(key, data);
1135
+ }
1136
+ if (ssrAuth) {
1137
+ holeCtx.ssrAuth = ssrAuth;
1138
+ }
1139
+ holeCtx.resolvedComponents = new Map;
1140
+ return ssrStorage.run(holeCtx, () => {
1141
+ ensureDomShim2();
1142
+ const factory = resolveHoleComponent(module, name);
1143
+ if (!factory) {
1144
+ return `<!-- AOT hole: ${name} not found -->`;
1145
+ }
1146
+ const node = factory();
1147
+ const vnode = toVNode(node);
1148
+ return serializeToHtml(vnode);
1149
+ });
1150
+ };
1151
+ }
1152
+ return holes;
1153
+ }
1154
+ function resolveHoleComponent(module, name) {
1155
+ const moduleRecord = module;
1156
+ const exported = moduleRecord[name];
1157
+ if (typeof exported === "function") {
1158
+ return exported;
1159
+ }
1160
+ return;
1161
+ }
1162
+ async function ssrRenderAot(module, url, options) {
1163
+ const { aotManifest, manifest } = options;
1164
+ const ssrTimeout = options.ssrTimeout ?? 300;
1165
+ const normalizedUrl = url.endsWith("/index.html") ? url.slice(0, -"/index.html".length) || "/" : url;
1166
+ const fallbackOptions = {
1167
+ ssrTimeout,
1168
+ fallbackMetrics: options.fallbackMetrics,
1169
+ ssrAuth: options.ssrAuth,
1170
+ manifest,
1171
+ prefetchSession: options.prefetchSession
1172
+ };
1173
+ const aotPatterns = Object.keys(aotManifest.routes);
1174
+ const matches = matchUrlToPatterns(normalizedUrl, aotPatterns);
1175
+ if (matches.length === 0) {
1176
+ return ssrRenderSinglePass(module, normalizedUrl, fallbackOptions);
1177
+ }
1178
+ const match = matches[matches.length - 1];
1179
+ if (!match) {
1180
+ return ssrRenderSinglePass(module, normalizedUrl, fallbackOptions);
1181
+ }
1182
+ const aotEntry = aotManifest.routes[match.pattern];
1183
+ if (!aotEntry) {
1184
+ return ssrRenderSinglePass(module, normalizedUrl, fallbackOptions);
1185
+ }
1186
+ const queryCache = new Map;
1187
+ try {
1188
+ setGlobalSSRTimeout(ssrTimeout);
1189
+ const holes = createHoles(aotEntry.holes, module, normalizedUrl, queryCache, options.ssrAuth);
1190
+ const ctx = {
1191
+ holes,
1192
+ getData: (key) => queryCache.get(key),
1193
+ session: options.prefetchSession,
1194
+ params: match.params
1195
+ };
1196
+ const data = {};
1197
+ for (const [key, value] of queryCache) {
1198
+ data[key] = value;
1199
+ }
1200
+ const html = aotEntry.render(data, ctx);
1201
+ if (options.diagnostics && isAotDebugEnabled()) {
1202
+ try {
1203
+ const domResult = await ssrRenderSinglePass(module, normalizedUrl, fallbackOptions);
1204
+ if (domResult.html !== html) {
1205
+ options.diagnostics.recordDivergence(match.pattern, html, domResult.html);
1206
+ }
1207
+ } catch {}
1208
+ }
1209
+ const css = collectCSSFromModule(module, options.fallbackMetrics);
1210
+ const ssrData = [];
1211
+ for (const [key, data2] of queryCache) {
1212
+ ssrData.push({ key, data: JSON.parse(JSON.stringify(data2)) });
1213
+ }
1214
+ return {
1215
+ html,
1216
+ css: css.cssString,
1217
+ ssrData,
1218
+ headTags: css.preloadTags
1219
+ };
1220
+ } finally {
1221
+ clearGlobalSSRTimeout();
1222
+ }
1223
+ }
1224
+ function isAotDebugEnabled() {
1225
+ const env = process.env.VERTZ_DEBUG;
1226
+ if (!env)
1227
+ return false;
1228
+ return env === "1" || env.split(",").includes("aot");
1229
+ }
1230
+ var domShimInstalled2 = false;
1231
+ function ensureDomShim2() {
1232
+ if (domShimInstalled2 && typeof document !== "undefined")
1233
+ return;
1234
+ domShimInstalled2 = true;
1235
+ installDomShim();
1236
+ }
1237
+ function collectCSSFromModule(module, fallbackMetrics) {
1238
+ let themeCss = "";
1239
+ let preloadTags = "";
1240
+ if (module.theme) {
1241
+ try {
1242
+ const compiled = compileTheme3(module.theme, { fallbackMetrics });
1243
+ themeCss = compiled.css;
1244
+ preloadTags = compiled.preloadTags;
1245
+ } catch (e) {
1246
+ console.error("[vertz] Failed to compile theme export. Ensure your theme is created with defineTheme().", e);
1247
+ }
1248
+ }
1249
+ const alreadyIncluded = new Set;
1250
+ if (themeCss)
1251
+ alreadyIncluded.add(themeCss);
1252
+ if (module.styles) {
1253
+ for (const s of module.styles)
1254
+ alreadyIncluded.add(s);
1255
+ }
1256
+ const componentCss = module.getInjectedCSS ? module.getInjectedCSS().filter((s) => !alreadyIncluded.has(s)) : [];
1257
+ const themeTag = themeCss ? `<style data-vertz-css>${themeCss}</style>` : "";
1258
+ const globalTag = module.styles && module.styles.length > 0 ? `<style data-vertz-css>${module.styles.join(`
1259
+ `)}</style>` : "";
1260
+ const componentTag = componentCss.length > 0 ? `<style data-vertz-css>${componentCss.join(`
1261
+ `)}</style>` : "";
1262
+ const cssString = [themeTag, globalTag, componentTag].filter(Boolean).join(`
1263
+ `);
1264
+ return { cssString, preloadTags };
1265
+ }
1266
+ // src/ssr-aot-runtime.ts
1267
+ var UNITLESS_PROPERTIES = new Set([
1268
+ "animationIterationCount",
1269
+ "aspectRatio",
1270
+ "borderImageOutset",
1271
+ "borderImageSlice",
1272
+ "borderImageWidth",
1273
+ "boxFlex",
1274
+ "boxFlexGroup",
1275
+ "boxOrdinalGroup",
1276
+ "columnCount",
1277
+ "columns",
1278
+ "flex",
1279
+ "flexGrow",
1280
+ "flexPositive",
1281
+ "flexShrink",
1282
+ "flexNegative",
1283
+ "flexOrder",
1284
+ "fontWeight",
1285
+ "gridArea",
1286
+ "gridColumn",
1287
+ "gridColumnEnd",
1288
+ "gridColumnSpan",
1289
+ "gridColumnStart",
1290
+ "gridRow",
1291
+ "gridRowEnd",
1292
+ "gridRowSpan",
1293
+ "gridRowStart",
1294
+ "lineClamp",
1295
+ "lineHeight",
1296
+ "opacity",
1297
+ "order",
1298
+ "orphans",
1299
+ "scale",
1300
+ "tabSize",
1301
+ "widows",
1302
+ "zIndex",
1303
+ "zoom"
1304
+ ]);
1305
+ function __esc(value) {
1306
+ if (value == null || value === false)
1307
+ return "";
1308
+ if (value === true)
1309
+ return "true";
1310
+ if (typeof value === "number")
1311
+ return String(value);
1312
+ if (Array.isArray(value))
1313
+ return value.map(__esc).join("");
1314
+ return String(value).replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
1315
+ }
1316
+ function __esc_attr(value) {
1317
+ const str = typeof value === "string" ? value : String(value);
1318
+ return str.replace(/&/g, "&amp;").replace(/"/g, "&quot;");
1319
+ }
1320
+ function __ssr_spread(props) {
1321
+ const parts = [];
1322
+ for (const key in props) {
1323
+ const value = props[key];
1324
+ if (value == null || value === false)
1325
+ continue;
1326
+ const third = key.charAt(2);
1327
+ if (key.length > 2 && key.charAt(0) === "o" && key.charAt(1) === "n" && third >= "A" && third <= "Z") {
1328
+ continue;
1329
+ }
1330
+ if (typeof value === "function")
1331
+ continue;
1332
+ if (key === "key" || key === "ref" || key === "children")
1333
+ continue;
1334
+ const attrName = key === "className" ? "class" : key === "htmlFor" ? "for" : key;
1335
+ if (key === "style" && typeof value === "object") {
1336
+ const css = __ssr_style_object(value);
1337
+ if (css) {
1338
+ parts.push(` style="${__esc_attr(css)}"`);
1339
+ }
1340
+ continue;
1341
+ }
1342
+ if (value === true) {
1343
+ parts.push(` ${attrName}`);
1344
+ continue;
1345
+ }
1346
+ parts.push(` ${attrName}="${__esc_attr(value)}"`);
1347
+ }
1348
+ return parts.join("");
1349
+ }
1350
+ function camelToKebab(prop) {
1351
+ if (prop.startsWith("--"))
1352
+ return prop;
1353
+ if (prop.startsWith("ms")) {
1354
+ return `-${prop.replace(/[A-Z]/g, (m) => `-${m.toLowerCase()}`)}`;
1355
+ }
1356
+ const first = prop.charAt(0);
1357
+ if (first >= "A" && first <= "Z") {
1358
+ return `-${first.toLowerCase()}${prop.slice(1).replace(/[A-Z]/g, (m) => `-${m.toLowerCase()}`)}`;
1359
+ }
1360
+ return prop.replace(/[A-Z]/g, (m) => `-${m.toLowerCase()}`);
1361
+ }
1362
+ function __ssr_style_object(style) {
1363
+ const parts = [];
1364
+ for (const prop in style) {
1365
+ const value = style[prop];
1366
+ if (value == null || value === "")
1367
+ continue;
1368
+ const cssProp = camelToKebab(prop);
1369
+ if (typeof value === "number" && value !== 0 && !UNITLESS_PROPERTIES.has(prop)) {
1370
+ parts.push(`${cssProp}: ${value}px`);
1371
+ } else {
1372
+ parts.push(`${cssProp}: ${value}`);
1373
+ }
1374
+ }
1375
+ return parts.join("; ");
1376
+ }
428
1377
  // src/ssr-html.ts
429
1378
  function generateSSRHtml(options) {
430
1379
  const {
@@ -457,11 +1406,131 @@ function generateSSRHtml(options) {
457
1406
  </body>
458
1407
  </html>`;
459
1408
  }
1409
+ // src/ssr-prefetch-dev.ts
1410
+ import {
1411
+ analyzeComponentQueries,
1412
+ generatePrefetchManifest
1413
+ } from "@vertz/ui-compiler";
1414
+ function createPrefetchManifestManager(options) {
1415
+ const { routerPath, readFile: readFile2, resolveImport } = options;
1416
+ let currentManifest = null;
1417
+ let currentSSRManifest;
1418
+ let rebuildCount = 0;
1419
+ let lastRebuildMs = null;
1420
+ let lastRebuildAt = null;
1421
+ let fileToRouteIndices = new Map;
1422
+ function buildFileIndex(routes) {
1423
+ const index = new Map;
1424
+ for (let i = 0;i < routes.length; i++) {
1425
+ const file = routes[i]?.file;
1426
+ if (file) {
1427
+ const existing = index.get(file) ?? [];
1428
+ existing.push(i);
1429
+ index.set(file, existing);
1430
+ }
1431
+ }
1432
+ return index;
1433
+ }
1434
+ function toSSRManifest(manifest) {
1435
+ const routeEntries = {};
1436
+ for (const route of manifest.routes) {
1437
+ const existing = routeEntries[route.pattern];
1438
+ if (existing) {
1439
+ existing.queries.push(...route.queries);
1440
+ } else {
1441
+ routeEntries[route.pattern] = { queries: [...route.queries] };
1442
+ }
1443
+ }
1444
+ return {
1445
+ routePatterns: [...new Set(manifest.routes.map((r) => r.pattern))],
1446
+ routeEntries
1447
+ };
1448
+ }
1449
+ function fullBuild(routerSourceOverride) {
1450
+ const start = performance.now();
1451
+ const routerSource = routerSourceOverride ?? readFile2(routerPath);
1452
+ if (!routerSource) {
1453
+ return;
1454
+ }
1455
+ try {
1456
+ const manifest = generatePrefetchManifest({
1457
+ routerSource,
1458
+ routerPath,
1459
+ readFile: readFile2,
1460
+ resolveImport
1461
+ });
1462
+ currentManifest = manifest;
1463
+ currentSSRManifest = toSSRManifest(manifest);
1464
+ fileToRouteIndices = buildFileIndex(manifest.routes);
1465
+ rebuildCount++;
1466
+ lastRebuildMs = Math.round(performance.now() - start);
1467
+ lastRebuildAt = new Date().toISOString();
1468
+ } catch {}
1469
+ }
1470
+ function incrementalUpdate(filePath, sourceText) {
1471
+ if (!currentManifest)
1472
+ return;
1473
+ const indices = fileToRouteIndices.get(filePath);
1474
+ if (!indices || indices.length === 0)
1475
+ return;
1476
+ const start = performance.now();
1477
+ try {
1478
+ const analysis = analyzeComponentQueries(sourceText, filePath);
1479
+ const newRoutes = [...currentManifest.routes];
1480
+ for (const idx of indices) {
1481
+ const existing = newRoutes[idx];
1482
+ if (existing) {
1483
+ newRoutes[idx] = {
1484
+ ...existing,
1485
+ queries: analysis.queries,
1486
+ params: analysis.params
1487
+ };
1488
+ }
1489
+ }
1490
+ const newManifest = {
1491
+ ...currentManifest,
1492
+ routes: newRoutes,
1493
+ generatedAt: new Date().toISOString()
1494
+ };
1495
+ currentManifest = newManifest;
1496
+ currentSSRManifest = toSSRManifest(newManifest);
1497
+ rebuildCount++;
1498
+ lastRebuildMs = Math.round(performance.now() - start);
1499
+ lastRebuildAt = new Date().toISOString();
1500
+ } catch {}
1501
+ }
1502
+ return {
1503
+ build() {
1504
+ fullBuild();
1505
+ },
1506
+ onFileChange(filePath, sourceText) {
1507
+ if (filePath === routerPath) {
1508
+ fullBuild(sourceText);
1509
+ } else {
1510
+ incrementalUpdate(filePath, sourceText);
1511
+ }
1512
+ },
1513
+ getSSRManifest() {
1514
+ return currentSSRManifest;
1515
+ },
1516
+ getSnapshot() {
1517
+ return {
1518
+ manifest: currentManifest,
1519
+ rebuildCount,
1520
+ lastRebuildMs,
1521
+ lastRebuildAt
1522
+ };
1523
+ }
1524
+ };
1525
+ }
460
1526
  export {
461
1527
  wrapWithHydrationMarkers,
1528
+ toPrefetchSession,
462
1529
  streamToString,
463
1530
  ssrStorage,
464
1531
  ssrRenderToString,
1532
+ ssrRenderSinglePass,
1533
+ ssrRenderAot,
465
1534
  ssrDiscoverQueries,
466
1535
  setGlobalSSRTimeout,
467
1536
  serializeToHtml,
@@ -474,8 +1543,11 @@ export {
474
1543
  renderHeadToHtml,
475
1544
  renderAssetTags,
476
1545
  registerSSRQuery,
1546
+ reconstructDescriptors,
477
1547
  rawHtml,
1548
+ matchUrlToPatterns,
478
1549
  isInSSR,
1550
+ isAotDebugEnabled,
479
1551
  inlineCriticalCss,
480
1552
  getStreamingRuntimeScript,
481
1553
  getSSRUrl,
@@ -483,7 +1555,9 @@ export {
483
1555
  getGlobalSSRTimeout,
484
1556
  getAccessSetForSSR,
485
1557
  generateSSRHtml,
1558
+ generateAotBuildManifest,
486
1559
  extractFontMetrics,
1560
+ evaluateAccessRule,
487
1561
  encodeChunk,
488
1562
  detectFallbackFont,
489
1563
  createTemplateChunk,
@@ -492,8 +1566,16 @@ export {
492
1566
  createSSRHandler,
493
1567
  createSSRDataChunk,
494
1568
  createSSRAdapter,
1569
+ createPrefetchManifestManager,
1570
+ createHoles,
1571
+ createAotManifestManager,
495
1572
  createAccessSetScript,
496
1573
  collectStreamChunks,
497
1574
  clearGlobalSSRTimeout,
498
- HeadCollector
1575
+ __ssr_style_object,
1576
+ __ssr_spread,
1577
+ __esc_attr,
1578
+ __esc,
1579
+ HeadCollector,
1580
+ AotDiagnostics
499
1581
  };