@vertz/ui-server 0.2.30 → 0.2.32
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/bun-dev-server.js +47 -2
- package/dist/dom-shim/index.js +1 -1
- package/dist/index.d.ts +818 -747
- package/dist/index.js +86 -150
- package/dist/node-handler.d.ts +223 -0
- package/dist/node-handler.js +8 -0
- package/dist/shared/{chunk-bd0sgykf.js → chunk-34fexgex.js} +247 -431
- package/dist/shared/chunk-es0406qq.js +227 -0
- package/dist/shared/chunk-gbcsa7h1.js +471 -0
- package/dist/shared/{chunk-gcwqkynf.js → chunk-ybftdw1r.js} +1 -1
- package/dist/ssr/index.d.ts +119 -57
- package/dist/ssr/index.js +6 -3
- package/package.json +9 -5
package/dist/index.js
CHANGED
|
@@ -1,10 +1,19 @@
|
|
|
1
|
+
import {
|
|
2
|
+
createHoles,
|
|
3
|
+
createSSRHandler,
|
|
4
|
+
isAotDebugEnabled,
|
|
5
|
+
loadAotManifest,
|
|
6
|
+
ssrRenderAot
|
|
7
|
+
} from "./shared/chunk-gbcsa7h1.js";
|
|
8
|
+
import {
|
|
9
|
+
createNodeHandler
|
|
10
|
+
} from "./shared/chunk-es0406qq.js";
|
|
1
11
|
import {
|
|
2
12
|
collectStreamChunks,
|
|
3
13
|
compileThemeCached,
|
|
4
14
|
createAccessSetScript,
|
|
5
15
|
createRequestContext,
|
|
6
16
|
createSSRDataChunk,
|
|
7
|
-
createSSRHandler,
|
|
8
17
|
createSessionScript,
|
|
9
18
|
createSlotPlaceholder,
|
|
10
19
|
createTemplateChunk,
|
|
@@ -25,28 +34,30 @@ import {
|
|
|
25
34
|
ssrRenderToString,
|
|
26
35
|
streamToString,
|
|
27
36
|
toPrefetchSession
|
|
28
|
-
} from "./shared/chunk-
|
|
37
|
+
} from "./shared/chunk-34fexgex.js";
|
|
29
38
|
import {
|
|
30
39
|
clearGlobalSSRTimeout,
|
|
31
40
|
createSSRAdapter,
|
|
32
41
|
getGlobalSSRTimeout,
|
|
33
42
|
getSSRQueries,
|
|
34
43
|
getSSRUrl,
|
|
35
|
-
installDomShim,
|
|
36
44
|
isInSSR,
|
|
37
45
|
rawHtml,
|
|
38
46
|
registerSSRQuery,
|
|
39
47
|
setGlobalSSRTimeout,
|
|
40
|
-
ssrStorage
|
|
41
|
-
|
|
42
|
-
|
|
48
|
+
ssrStorage
|
|
49
|
+
} from "./shared/chunk-ybftdw1r.js";
|
|
50
|
+
|
|
51
|
+
// src/index.ts
|
|
52
|
+
import { extractRoutes } from "@vertz/ui-compiler";
|
|
43
53
|
|
|
44
54
|
// src/aot-manifest-build.ts
|
|
45
55
|
import { readdirSync, readFileSync } from "node:fs";
|
|
46
|
-
import { join } from "node:path";
|
|
56
|
+
import { basename, join } from "node:path";
|
|
47
57
|
import { compileForSSRAot } from "@vertz/ui-compiler";
|
|
48
58
|
function generateAotBuildManifest(srcDir) {
|
|
49
59
|
const components = {};
|
|
60
|
+
const compiledFiles = {};
|
|
50
61
|
const classificationLog = [];
|
|
51
62
|
const tsxFiles = collectTsxFiles(srcDir);
|
|
52
63
|
for (const filePath of tsxFiles) {
|
|
@@ -56,7 +67,14 @@ function generateAotBuildManifest(srcDir) {
|
|
|
56
67
|
for (const comp of result.components) {
|
|
57
68
|
components[comp.name] = {
|
|
58
69
|
tier: comp.tier,
|
|
59
|
-
holes: comp.holes
|
|
70
|
+
holes: comp.holes,
|
|
71
|
+
queryKeys: comp.queryKeys
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
if (result.components.length > 0) {
|
|
75
|
+
compiledFiles[filePath] = {
|
|
76
|
+
code: result.code,
|
|
77
|
+
components: result.components
|
|
60
78
|
};
|
|
61
79
|
}
|
|
62
80
|
} catch (e) {
|
|
@@ -83,7 +101,61 @@ function generateAotBuildManifest(srcDir) {
|
|
|
83
101
|
const pct = Math.round(aotCount / total * 100);
|
|
84
102
|
classificationLog.push(`Coverage: ${aotCount}/${total} components (${pct}%)`);
|
|
85
103
|
}
|
|
86
|
-
return { components, classificationLog };
|
|
104
|
+
return { components, compiledFiles, classificationLog };
|
|
105
|
+
}
|
|
106
|
+
function buildAotRouteMap(components, routes) {
|
|
107
|
+
const routeMap = {};
|
|
108
|
+
for (const route of routes) {
|
|
109
|
+
const comp = components[route.componentName];
|
|
110
|
+
if (!comp || comp.tier === "runtime-fallback")
|
|
111
|
+
continue;
|
|
112
|
+
routeMap[route.pattern] = {
|
|
113
|
+
renderFn: `__ssr_${route.componentName}`,
|
|
114
|
+
holes: comp.holes,
|
|
115
|
+
queryKeys: comp.queryKeys
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
return routeMap;
|
|
119
|
+
}
|
|
120
|
+
function generateAotBarrel(compiledFiles, routeMap) {
|
|
121
|
+
const neededFns = new Set;
|
|
122
|
+
for (const entry of Object.values(routeMap)) {
|
|
123
|
+
neededFns.add(entry.renderFn);
|
|
124
|
+
}
|
|
125
|
+
const fnToFile = new Map;
|
|
126
|
+
for (const [filePath, compiled] of Object.entries(compiledFiles)) {
|
|
127
|
+
for (const comp of compiled.components) {
|
|
128
|
+
const fnName = `__ssr_${comp.name}`;
|
|
129
|
+
if (neededFns.has(fnName)) {
|
|
130
|
+
fnToFile.set(fnName, filePath);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
const fileToFns = new Map;
|
|
135
|
+
for (const [fnName, filePath] of fnToFile) {
|
|
136
|
+
const existing = fileToFns.get(filePath) ?? [];
|
|
137
|
+
existing.push(fnName);
|
|
138
|
+
fileToFns.set(filePath, existing);
|
|
139
|
+
}
|
|
140
|
+
const lines = [];
|
|
141
|
+
const files = {};
|
|
142
|
+
let fileIndex = 0;
|
|
143
|
+
for (const [filePath, fns] of fileToFns) {
|
|
144
|
+
const moduleName = basename(filePath, ".tsx").replace(/[^a-zA-Z0-9_-]/g, "_");
|
|
145
|
+
const tempFileName = `__aot_${fileIndex}_${moduleName}`;
|
|
146
|
+
const moduleRef = `./${tempFileName}`;
|
|
147
|
+
lines.push(`export { ${fns.sort().join(", ")} } from '${moduleRef}';`);
|
|
148
|
+
const compiled = compiledFiles[filePath];
|
|
149
|
+
if (compiled) {
|
|
150
|
+
files[`${tempFileName}.tsx`] = compiled.code;
|
|
151
|
+
}
|
|
152
|
+
fileIndex++;
|
|
153
|
+
}
|
|
154
|
+
return {
|
|
155
|
+
barrelSource: lines.join(`
|
|
156
|
+
`),
|
|
157
|
+
files
|
|
158
|
+
};
|
|
87
159
|
}
|
|
88
160
|
function collectTsxFiles(dir) {
|
|
89
161
|
const files = [];
|
|
@@ -665,147 +737,6 @@ function createAotManifestManager(options) {
|
|
|
665
737
|
}
|
|
666
738
|
};
|
|
667
739
|
}
|
|
668
|
-
// src/ssr-aot-pipeline.ts
|
|
669
|
-
function createHoles(holeNames, module, url, queryCache, ssrAuth) {
|
|
670
|
-
if (holeNames.length === 0)
|
|
671
|
-
return {};
|
|
672
|
-
const holes = {};
|
|
673
|
-
for (const name of holeNames) {
|
|
674
|
-
holes[name] = () => {
|
|
675
|
-
const holeCtx = createRequestContext(url);
|
|
676
|
-
for (const [key, data] of queryCache) {
|
|
677
|
-
holeCtx.queryCache.set(key, data);
|
|
678
|
-
}
|
|
679
|
-
if (ssrAuth) {
|
|
680
|
-
holeCtx.ssrAuth = ssrAuth;
|
|
681
|
-
}
|
|
682
|
-
holeCtx.resolvedComponents = new Map;
|
|
683
|
-
return ssrStorage.run(holeCtx, () => {
|
|
684
|
-
ensureDomShim();
|
|
685
|
-
const factory = resolveHoleComponent(module, name);
|
|
686
|
-
if (!factory) {
|
|
687
|
-
return `<!-- AOT hole: ${name} not found -->`;
|
|
688
|
-
}
|
|
689
|
-
const node = factory();
|
|
690
|
-
const vnode = toVNode(node);
|
|
691
|
-
return serializeToHtml(vnode);
|
|
692
|
-
});
|
|
693
|
-
};
|
|
694
|
-
}
|
|
695
|
-
return holes;
|
|
696
|
-
}
|
|
697
|
-
function resolveHoleComponent(module, name) {
|
|
698
|
-
const moduleRecord = module;
|
|
699
|
-
const exported = moduleRecord[name];
|
|
700
|
-
if (typeof exported === "function") {
|
|
701
|
-
return exported;
|
|
702
|
-
}
|
|
703
|
-
return;
|
|
704
|
-
}
|
|
705
|
-
async function ssrRenderAot(module, url, options) {
|
|
706
|
-
const { aotManifest, manifest } = options;
|
|
707
|
-
const ssrTimeout = options.ssrTimeout ?? 300;
|
|
708
|
-
const normalizedUrl = url.endsWith("/index.html") ? url.slice(0, -"/index.html".length) || "/" : url;
|
|
709
|
-
const fallbackOptions = {
|
|
710
|
-
ssrTimeout,
|
|
711
|
-
fallbackMetrics: options.fallbackMetrics,
|
|
712
|
-
ssrAuth: options.ssrAuth,
|
|
713
|
-
manifest,
|
|
714
|
-
prefetchSession: options.prefetchSession
|
|
715
|
-
};
|
|
716
|
-
const aotPatterns = Object.keys(aotManifest.routes);
|
|
717
|
-
const matches = matchUrlToPatterns(normalizedUrl, aotPatterns);
|
|
718
|
-
if (matches.length === 0) {
|
|
719
|
-
return ssrRenderSinglePass(module, normalizedUrl, fallbackOptions);
|
|
720
|
-
}
|
|
721
|
-
const match = matches[matches.length - 1];
|
|
722
|
-
if (!match) {
|
|
723
|
-
return ssrRenderSinglePass(module, normalizedUrl, fallbackOptions);
|
|
724
|
-
}
|
|
725
|
-
const aotEntry = aotManifest.routes[match.pattern];
|
|
726
|
-
if (!aotEntry) {
|
|
727
|
-
return ssrRenderSinglePass(module, normalizedUrl, fallbackOptions);
|
|
728
|
-
}
|
|
729
|
-
const queryCache = new Map;
|
|
730
|
-
try {
|
|
731
|
-
setGlobalSSRTimeout(ssrTimeout);
|
|
732
|
-
const holes = createHoles(aotEntry.holes, module, normalizedUrl, queryCache, options.ssrAuth);
|
|
733
|
-
const ctx = {
|
|
734
|
-
holes,
|
|
735
|
-
getData: (key) => queryCache.get(key),
|
|
736
|
-
session: options.prefetchSession,
|
|
737
|
-
params: match.params
|
|
738
|
-
};
|
|
739
|
-
const data = {};
|
|
740
|
-
for (const [key, value] of queryCache) {
|
|
741
|
-
data[key] = value;
|
|
742
|
-
}
|
|
743
|
-
const html = aotEntry.render(data, ctx);
|
|
744
|
-
if (options.diagnostics && isAotDebugEnabled()) {
|
|
745
|
-
try {
|
|
746
|
-
const domResult = await ssrRenderSinglePass(module, normalizedUrl, fallbackOptions);
|
|
747
|
-
if (domResult.html !== html) {
|
|
748
|
-
options.diagnostics.recordDivergence(match.pattern, html, domResult.html);
|
|
749
|
-
}
|
|
750
|
-
} catch {}
|
|
751
|
-
}
|
|
752
|
-
const css = collectCSSFromModule(module, options.fallbackMetrics);
|
|
753
|
-
const ssrData = [];
|
|
754
|
-
for (const [key, data2] of queryCache) {
|
|
755
|
-
ssrData.push({ key, data: data2 });
|
|
756
|
-
}
|
|
757
|
-
return {
|
|
758
|
-
html,
|
|
759
|
-
css: css.cssString,
|
|
760
|
-
ssrData,
|
|
761
|
-
headTags: css.preloadTags
|
|
762
|
-
};
|
|
763
|
-
} finally {
|
|
764
|
-
clearGlobalSSRTimeout();
|
|
765
|
-
}
|
|
766
|
-
}
|
|
767
|
-
function isAotDebugEnabled() {
|
|
768
|
-
const env = process.env.VERTZ_DEBUG;
|
|
769
|
-
if (!env)
|
|
770
|
-
return false;
|
|
771
|
-
return env === "1" || env.split(",").includes("aot");
|
|
772
|
-
}
|
|
773
|
-
var domShimInstalled = false;
|
|
774
|
-
function ensureDomShim() {
|
|
775
|
-
if (domShimInstalled && typeof document !== "undefined")
|
|
776
|
-
return;
|
|
777
|
-
domShimInstalled = true;
|
|
778
|
-
installDomShim();
|
|
779
|
-
}
|
|
780
|
-
function collectCSSFromModule(module, fallbackMetrics) {
|
|
781
|
-
let themeCss = "";
|
|
782
|
-
let preloadTags = "";
|
|
783
|
-
if (module.theme) {
|
|
784
|
-
try {
|
|
785
|
-
const compiled = compileThemeCached(module.theme, fallbackMetrics);
|
|
786
|
-
themeCss = compiled.css;
|
|
787
|
-
preloadTags = compiled.preloadTags;
|
|
788
|
-
} catch (e) {
|
|
789
|
-
console.error("[vertz] Failed to compile theme export. Ensure your theme is created with defineTheme().", e);
|
|
790
|
-
}
|
|
791
|
-
}
|
|
792
|
-
const alreadyIncluded = new Set;
|
|
793
|
-
if (themeCss)
|
|
794
|
-
alreadyIncluded.add(themeCss);
|
|
795
|
-
if (module.styles) {
|
|
796
|
-
for (const s of module.styles)
|
|
797
|
-
alreadyIncluded.add(s);
|
|
798
|
-
}
|
|
799
|
-
const componentCss = module.getInjectedCSS ? module.getInjectedCSS().filter((s) => !alreadyIncluded.has(s)) : [];
|
|
800
|
-
const themeTag = themeCss ? `<style data-vertz-css>${themeCss}</style>` : "";
|
|
801
|
-
const globalTag = module.styles && module.styles.length > 0 ? `<style data-vertz-css>${module.styles.join(`
|
|
802
|
-
`)}</style>` : "";
|
|
803
|
-
const componentTag = componentCss.length > 0 ? `<style data-vertz-css>${componentCss.join(`
|
|
804
|
-
`)}</style>` : "";
|
|
805
|
-
const cssString = [themeTag, globalTag, componentTag].filter(Boolean).join(`
|
|
806
|
-
`);
|
|
807
|
-
return { cssString, preloadTags };
|
|
808
|
-
}
|
|
809
740
|
// src/ssr-aot-runtime.ts
|
|
810
741
|
var UNITLESS_PROPERTIES = new Set([
|
|
811
742
|
"animationIterationCount",
|
|
@@ -1089,6 +1020,7 @@ export {
|
|
|
1089
1020
|
reconstructDescriptors,
|
|
1090
1021
|
rawHtml,
|
|
1091
1022
|
matchUrlToPatterns,
|
|
1023
|
+
loadAotManifest,
|
|
1092
1024
|
isInSSR,
|
|
1093
1025
|
isAotDebugEnabled,
|
|
1094
1026
|
inlineCriticalCss,
|
|
@@ -1099,6 +1031,8 @@ export {
|
|
|
1099
1031
|
getAccessSetForSSR,
|
|
1100
1032
|
generateSSRHtml,
|
|
1101
1033
|
generateAotBuildManifest,
|
|
1034
|
+
generateAotBarrel,
|
|
1035
|
+
extractRoutes,
|
|
1102
1036
|
extractFontMetrics,
|
|
1103
1037
|
evaluateAccessRule,
|
|
1104
1038
|
encodeChunk,
|
|
@@ -1110,11 +1044,13 @@ export {
|
|
|
1110
1044
|
createSSRDataChunk,
|
|
1111
1045
|
createSSRAdapter,
|
|
1112
1046
|
createPrefetchManifestManager,
|
|
1047
|
+
createNodeHandler,
|
|
1113
1048
|
createHoles,
|
|
1114
1049
|
createAotManifestManager,
|
|
1115
1050
|
createAccessSetScript,
|
|
1116
1051
|
collectStreamChunks,
|
|
1117
1052
|
clearGlobalSSRTimeout,
|
|
1053
|
+
buildAotRouteMap,
|
|
1118
1054
|
__ssr_style_object,
|
|
1119
1055
|
__ssr_spread,
|
|
1120
1056
|
__esc_attr,
|
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
import { IncomingMessage, ServerResponse } from "node:http";
|
|
2
|
+
import { FontFallbackMetrics as FontFallbackMetrics4 } from "@vertz/ui";
|
|
3
|
+
/**
|
|
4
|
+
* SSR prefetch access rule evaluator.
|
|
5
|
+
*
|
|
6
|
+
* Evaluates serialized entity access rules against the current session
|
|
7
|
+
* to determine whether a query should be prefetched during SSR.
|
|
8
|
+
*
|
|
9
|
+
* The serialized rules come from the prefetch manifest (generated at build time).
|
|
10
|
+
* The session comes from the JWT decoded at request time.
|
|
11
|
+
*/
|
|
12
|
+
/**
|
|
13
|
+
* Serialized access rule — the JSON-friendly format stored in the manifest.
|
|
14
|
+
* Mirrors SerializedRule from @vertz/server/auth/rules but defined here
|
|
15
|
+
* to avoid importing the server package into the SSR pipeline.
|
|
16
|
+
*/
|
|
17
|
+
type SerializedAccessRule = {
|
|
18
|
+
type: "public";
|
|
19
|
+
} | {
|
|
20
|
+
type: "authenticated";
|
|
21
|
+
} | {
|
|
22
|
+
type: "role";
|
|
23
|
+
roles: string[];
|
|
24
|
+
} | {
|
|
25
|
+
type: "entitlement";
|
|
26
|
+
value: string;
|
|
27
|
+
} | {
|
|
28
|
+
type: "where";
|
|
29
|
+
conditions: Record<string, unknown>;
|
|
30
|
+
} | {
|
|
31
|
+
type: "all";
|
|
32
|
+
rules: SerializedAccessRule[];
|
|
33
|
+
} | {
|
|
34
|
+
type: "any";
|
|
35
|
+
rules: SerializedAccessRule[];
|
|
36
|
+
} | {
|
|
37
|
+
type: "fva";
|
|
38
|
+
maxAge: number;
|
|
39
|
+
} | {
|
|
40
|
+
type: "deny";
|
|
41
|
+
};
|
|
42
|
+
/**
|
|
43
|
+
* Minimal session shape needed for prefetch access evaluation.
|
|
44
|
+
* Extracted from the JWT at SSR request time.
|
|
45
|
+
*/
|
|
46
|
+
type PrefetchSession = {
|
|
47
|
+
status: "authenticated";
|
|
48
|
+
roles?: string[];
|
|
49
|
+
entitlements?: Record<string, boolean>;
|
|
50
|
+
tenantId?: string;
|
|
51
|
+
} | {
|
|
52
|
+
status: "unauthenticated";
|
|
53
|
+
};
|
|
54
|
+
import { CompiledRoute, Theme } from "@vertz/ui";
|
|
55
|
+
interface SSRModule {
|
|
56
|
+
default?: () => unknown;
|
|
57
|
+
App?: () => unknown;
|
|
58
|
+
theme?: Theme;
|
|
59
|
+
/** Global CSS strings to include in every SSR response (e.g. resets, body styles). */
|
|
60
|
+
styles?: string[];
|
|
61
|
+
/**
|
|
62
|
+
* Return all CSS tracked by the bundled @vertz/ui instance.
|
|
63
|
+
* The Vite SSR build inlines @vertz/ui into the server bundle, creating
|
|
64
|
+
* a separate module instance from @vertz/ui-server's dependency. Without
|
|
65
|
+
* this, component CSS from module-level css() calls is invisible to the
|
|
66
|
+
* SSR renderer. Export `getInjectedCSS` from @vertz/ui in the app entry.
|
|
67
|
+
*/
|
|
68
|
+
getInjectedCSS?: () => string[];
|
|
69
|
+
/** Compiled routes exported from the app for build-time SSG with generateParams. */
|
|
70
|
+
routes?: CompiledRoute[];
|
|
71
|
+
/** Code-generated API client for manifest-driven zero-discovery prefetching. */
|
|
72
|
+
api?: Record<string, Record<string, (...args: unknown[]) => unknown>>;
|
|
73
|
+
}
|
|
74
|
+
import { ExtractedQuery } from "@vertz/ui-compiler";
|
|
75
|
+
/** Serialized entity access rules from the prefetch manifest. */
|
|
76
|
+
type EntityAccessMap = Record<string, Partial<Record<string, SerializedAccessRule>>>;
|
|
77
|
+
interface SSRPrefetchManifest {
|
|
78
|
+
/** Route patterns present in the manifest. */
|
|
79
|
+
routePatterns: string[];
|
|
80
|
+
/** Entity access rules keyed by entity name → operation → serialized rule. */
|
|
81
|
+
entityAccess?: EntityAccessMap;
|
|
82
|
+
/** Route entries with query binding metadata for zero-discovery prefetch. */
|
|
83
|
+
routeEntries?: Record<string, {
|
|
84
|
+
queries: ExtractedQuery[];
|
|
85
|
+
}>;
|
|
86
|
+
}
|
|
87
|
+
/** Context passed to AOT render functions for accessing data and runtime holes. */
|
|
88
|
+
interface SSRAotContext {
|
|
89
|
+
/** Pre-generated closures for runtime-rendered components. */
|
|
90
|
+
holes: Record<string, () => string>;
|
|
91
|
+
/** Access query data by cache key. */
|
|
92
|
+
getData(key: string): unknown;
|
|
93
|
+
/** Auth session for conditional rendering. */
|
|
94
|
+
session: PrefetchSession | undefined;
|
|
95
|
+
/** Route params for the current request. */
|
|
96
|
+
params: Record<string, string>;
|
|
97
|
+
}
|
|
98
|
+
/** An AOT render function: takes props/data and context, returns HTML string. */
|
|
99
|
+
type AotRenderFn = (data: Record<string, unknown>, ctx: SSRAotContext) => string;
|
|
100
|
+
/** Per-route AOT entry in the manifest. */
|
|
101
|
+
interface AotRouteEntry {
|
|
102
|
+
/** The pre-compiled render function. */
|
|
103
|
+
render: AotRenderFn;
|
|
104
|
+
/** Component names that need runtime fallback (holes). */
|
|
105
|
+
holes: string[];
|
|
106
|
+
/** Query cache keys this route reads via ctx.getData(). */
|
|
107
|
+
queryKeys?: string[];
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* AOT manifest — maps route patterns to pre-compiled render functions.
|
|
111
|
+
*
|
|
112
|
+
* Generated at build time by the AOT compiler pipeline.
|
|
113
|
+
*/
|
|
114
|
+
interface AotManifest {
|
|
115
|
+
/** Route pattern → AOT entry. */
|
|
116
|
+
routes: Record<string, AotRouteEntry>;
|
|
117
|
+
}
|
|
118
|
+
import { AccessSet } from "@vertz/ui/auth";
|
|
119
|
+
interface SessionData {
|
|
120
|
+
user: {
|
|
121
|
+
id: string;
|
|
122
|
+
email: string;
|
|
123
|
+
role: string;
|
|
124
|
+
[key: string]: unknown;
|
|
125
|
+
};
|
|
126
|
+
/** Unix timestamp in milliseconds (JWT exp * 1000). */
|
|
127
|
+
expiresAt: number;
|
|
128
|
+
}
|
|
129
|
+
/** Resolved session data for SSR injection. */
|
|
130
|
+
interface SSRSessionInfo {
|
|
131
|
+
session: SessionData;
|
|
132
|
+
/**
|
|
133
|
+
* Access set from JWT acl claim.
|
|
134
|
+
* - Present (object): inline access set (no overflow)
|
|
135
|
+
* - null: access control is configured but the set overflowed the JWT
|
|
136
|
+
* - undefined: access control is not configured
|
|
137
|
+
*/
|
|
138
|
+
accessSet?: AccessSet | null;
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* Callback that extracts session data from a request.
|
|
142
|
+
* Returns null when no valid session exists (expired, missing, or invalid cookie).
|
|
143
|
+
*/
|
|
144
|
+
type SessionResolver = (request: Request) => Promise<SSRSessionInfo | null>;
|
|
145
|
+
interface SSRHandlerOptions {
|
|
146
|
+
/** The loaded SSR module (import('./dist/server/index.js')) */
|
|
147
|
+
module: SSRModule;
|
|
148
|
+
/** HTML template string (contents of dist/client/index.html) */
|
|
149
|
+
template: string;
|
|
150
|
+
/** SSR timeout for queries (default: 300ms) */
|
|
151
|
+
ssrTimeout?: number;
|
|
152
|
+
/**
|
|
153
|
+
* Map of CSS asset URLs to their content for inlining.
|
|
154
|
+
* Replaces `<link rel="stylesheet" href="...">` tags with inline `<style>` tags.
|
|
155
|
+
* Eliminates extra network requests, preventing FOUC on slow connections.
|
|
156
|
+
*
|
|
157
|
+
* @example
|
|
158
|
+
* ```ts
|
|
159
|
+
* inlineCSS: { '/assets/vertz.css': await Bun.file('./dist/client/assets/vertz.css').text() }
|
|
160
|
+
* ```
|
|
161
|
+
*/
|
|
162
|
+
inlineCSS?: Record<string, string>;
|
|
163
|
+
/**
|
|
164
|
+
* CSP nonce to inject on all inline `<script>` tags emitted during SSR.
|
|
165
|
+
*
|
|
166
|
+
* When set, the SSR data hydration script will include `nonce="<value>"`
|
|
167
|
+
* so that strict Content-Security-Policy headers do not block it.
|
|
168
|
+
*/
|
|
169
|
+
nonce?: string;
|
|
170
|
+
/** Pre-computed font fallback metrics (computed at server startup). */
|
|
171
|
+
fallbackMetrics?: Record<string, FontFallbackMetrics4>;
|
|
172
|
+
/** Paths to inject as `<link rel="modulepreload">` in `<head>`. */
|
|
173
|
+
modulepreload?: string[];
|
|
174
|
+
/**
|
|
175
|
+
* Route chunk manifest for per-route modulepreload injection.
|
|
176
|
+
* When provided, only chunks for the matched route are preloaded instead of all chunks.
|
|
177
|
+
*/
|
|
178
|
+
routeChunkManifest?: {
|
|
179
|
+
routes: Record<string, string[]>;
|
|
180
|
+
};
|
|
181
|
+
/** Cache-Control header for HTML responses. Omit or undefined = no header (safe default). */
|
|
182
|
+
cacheControl?: string;
|
|
183
|
+
/**
|
|
184
|
+
* Resolves session data from request cookies for SSR injection.
|
|
185
|
+
* When provided, SSR HTML includes `window.__VERTZ_SESSION__` and
|
|
186
|
+
* optionally `window.__VERTZ_ACCESS_SET__` for instant auth hydration.
|
|
187
|
+
*/
|
|
188
|
+
sessionResolver?: SessionResolver;
|
|
189
|
+
/**
|
|
190
|
+
* Prefetch manifest for single-pass SSR optimization.
|
|
191
|
+
*
|
|
192
|
+
* When provided with route entries and an API client export, enables
|
|
193
|
+
* zero-discovery rendering — queries are prefetched from the manifest
|
|
194
|
+
* without executing the component tree, then a single render pass
|
|
195
|
+
* produces the HTML. Without a manifest, SSR still uses the single-pass
|
|
196
|
+
* discovery-then-render approach (cheaper than two-pass).
|
|
197
|
+
*/
|
|
198
|
+
manifest?: SSRPrefetchManifest;
|
|
199
|
+
/**
|
|
200
|
+
* Enable progressive HTML streaming. Default: false.
|
|
201
|
+
*
|
|
202
|
+
* When true, the Response body is a ReadableStream that sends `<head>`
|
|
203
|
+
* content (CSS, preloads, fonts) before `<body>` rendering is complete.
|
|
204
|
+
* This improves TTFB and FCP.
|
|
205
|
+
*
|
|
206
|
+
* Has no effect on zero-discovery routes (manifest with routeEntries),
|
|
207
|
+
* which always use buffered rendering.
|
|
208
|
+
*/
|
|
209
|
+
progressiveHTML?: boolean;
|
|
210
|
+
/**
|
|
211
|
+
* AOT manifest with pre-compiled render functions.
|
|
212
|
+
*
|
|
213
|
+
* When provided, routes matching AOT entries are rendered via string-builder
|
|
214
|
+
* functions (no DOM shim), bypassing the reactive runtime for 4-6x speedup.
|
|
215
|
+
* Routes not in the manifest fall back to `ssrRenderSinglePass()`.
|
|
216
|
+
*
|
|
217
|
+
* Load via `loadAotManifest(serverDir)` at startup.
|
|
218
|
+
*/
|
|
219
|
+
aotManifest?: AotManifest;
|
|
220
|
+
}
|
|
221
|
+
type NodeHandlerOptions = SSRHandlerOptions;
|
|
222
|
+
declare function createNodeHandler(options: NodeHandlerOptions): (req: IncomingMessage, res: ServerResponse) => void;
|
|
223
|
+
export { createNodeHandler, NodeHandlerOptions };
|