@vertz/ui-server 0.2.31 → 0.2.33
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/README.md +54 -50
- package/dist/bun-dev-server.d.ts +14 -1
- package/dist/bun-dev-server.js +56 -4
- package/dist/index.d.ts +301 -233
- package/dist/index.js +81 -151
- package/dist/node-handler.d.ts +76 -23
- package/dist/node-handler.js +2 -2
- package/dist/shared/{chunk-wb5fv233.js → chunk-2kx402c1.js} +62 -8
- package/dist/shared/{chunk-34fexgex.js → chunk-hx7drzm3.js} +599 -409
- package/dist/shared/{chunk-es0406qq.js → chunk-xdb8qn68.js} +17 -5
- package/dist/ssr/index.d.ts +119 -57
- package/dist/ssr/index.js +5 -4
- package/package.json +18 -16
|
@@ -68,41 +68,136 @@ function serializeToHtml(node) {
|
|
|
68
68
|
return `<${tag}${attrStr}>${childrenHtml}</${tag}>`;
|
|
69
69
|
}
|
|
70
70
|
|
|
71
|
-
// src/ssr-access-
|
|
72
|
-
function
|
|
73
|
-
if (!
|
|
74
|
-
return
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
if (acl.overflow)
|
|
79
|
-
return null;
|
|
80
|
-
if (!acl.set)
|
|
81
|
-
return null;
|
|
71
|
+
// src/ssr-access-evaluator.ts
|
|
72
|
+
function toPrefetchSession(ssrAuth, accessSet) {
|
|
73
|
+
if (!ssrAuth || ssrAuth.status !== "authenticated" || !ssrAuth.user) {
|
|
74
|
+
return { status: "unauthenticated" };
|
|
75
|
+
}
|
|
76
|
+
const roles = ssrAuth.user.role ? [ssrAuth.user.role] : undefined;
|
|
77
|
+
const entitlements = accessSet != null ? Object.fromEntries(Object.entries(accessSet.entitlements).map(([name, check]) => [name, check.allowed])) : undefined;
|
|
82
78
|
return {
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
reasons: check.reasons ?? [],
|
|
88
|
-
...check.reason ? { reason: check.reason } : {},
|
|
89
|
-
...check.meta ? { meta: check.meta } : {}
|
|
90
|
-
}
|
|
91
|
-
])),
|
|
92
|
-
flags: acl.set.flags,
|
|
93
|
-
plan: acl.set.plan,
|
|
94
|
-
plans: acl.set.plans ?? {},
|
|
95
|
-
computedAt: acl.set.computedAt
|
|
79
|
+
status: "authenticated",
|
|
80
|
+
roles,
|
|
81
|
+
entitlements,
|
|
82
|
+
tenantId: ssrAuth.user.tenantId
|
|
96
83
|
};
|
|
97
84
|
}
|
|
98
|
-
function
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
85
|
+
function evaluateAccessRule(rule, session) {
|
|
86
|
+
switch (rule.type) {
|
|
87
|
+
case "public":
|
|
88
|
+
return true;
|
|
89
|
+
case "authenticated":
|
|
90
|
+
return session.status === "authenticated";
|
|
91
|
+
case "role":
|
|
92
|
+
if (session.status !== "authenticated")
|
|
93
|
+
return false;
|
|
94
|
+
return session.roles?.some((r) => rule.roles.includes(r)) === true;
|
|
95
|
+
case "entitlement":
|
|
96
|
+
if (session.status !== "authenticated")
|
|
97
|
+
return false;
|
|
98
|
+
return session.entitlements?.[rule.value] === true;
|
|
99
|
+
case "where":
|
|
100
|
+
return true;
|
|
101
|
+
case "fva":
|
|
102
|
+
return session.status === "authenticated";
|
|
103
|
+
case "deny":
|
|
104
|
+
return false;
|
|
105
|
+
case "all":
|
|
106
|
+
return rule.rules.every((r) => evaluateAccessRule(r, session));
|
|
107
|
+
case "any":
|
|
108
|
+
return rule.rules.some((r) => evaluateAccessRule(r, session));
|
|
109
|
+
default:
|
|
110
|
+
return false;
|
|
111
|
+
}
|
|
103
112
|
}
|
|
104
|
-
|
|
105
|
-
|
|
113
|
+
|
|
114
|
+
// src/ssr-manifest-prefetch.ts
|
|
115
|
+
function reconstructDescriptors(queries, routeParams, apiClient) {
|
|
116
|
+
if (!apiClient)
|
|
117
|
+
return [];
|
|
118
|
+
const result = [];
|
|
119
|
+
for (const query of queries) {
|
|
120
|
+
const descriptor = reconstructSingle(query, routeParams, apiClient);
|
|
121
|
+
if (descriptor) {
|
|
122
|
+
result.push(descriptor);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
return result;
|
|
126
|
+
}
|
|
127
|
+
function reconstructSingle(query, routeParams, apiClient) {
|
|
128
|
+
const { entity, operation } = query;
|
|
129
|
+
if (!entity || !operation)
|
|
130
|
+
return;
|
|
131
|
+
const entitySdk = apiClient[entity];
|
|
132
|
+
if (!entitySdk)
|
|
133
|
+
return;
|
|
134
|
+
const method = entitySdk[operation];
|
|
135
|
+
if (typeof method !== "function")
|
|
136
|
+
return;
|
|
137
|
+
const args = buildFactoryArgs(query, routeParams);
|
|
138
|
+
if (args === undefined)
|
|
139
|
+
return;
|
|
140
|
+
try {
|
|
141
|
+
const descriptor = method(...args);
|
|
142
|
+
if (!descriptor || typeof descriptor._key !== "string" || typeof descriptor._fetch !== "function") {
|
|
143
|
+
return;
|
|
144
|
+
}
|
|
145
|
+
return { key: descriptor._key, fetch: descriptor._fetch };
|
|
146
|
+
} catch {
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
function buildFactoryArgs(query, routeParams) {
|
|
151
|
+
const { operation, idParam, queryBindings } = query;
|
|
152
|
+
if (operation === "get") {
|
|
153
|
+
if (idParam) {
|
|
154
|
+
const id = routeParams[idParam];
|
|
155
|
+
if (!id)
|
|
156
|
+
return;
|
|
157
|
+
const options = resolveQueryBindings(queryBindings, routeParams);
|
|
158
|
+
if (options === undefined && queryBindings)
|
|
159
|
+
return;
|
|
160
|
+
return options ? [id, options] : [id];
|
|
161
|
+
}
|
|
162
|
+
return;
|
|
163
|
+
}
|
|
164
|
+
if (!queryBindings)
|
|
165
|
+
return [];
|
|
166
|
+
const resolved = resolveQueryBindings(queryBindings, routeParams);
|
|
167
|
+
if (resolved === undefined)
|
|
168
|
+
return;
|
|
169
|
+
return [resolved];
|
|
170
|
+
}
|
|
171
|
+
function resolveQueryBindings(bindings, routeParams) {
|
|
172
|
+
if (!bindings)
|
|
173
|
+
return;
|
|
174
|
+
const resolved = {};
|
|
175
|
+
if (bindings.where) {
|
|
176
|
+
const where = {};
|
|
177
|
+
for (const [key, value] of Object.entries(bindings.where)) {
|
|
178
|
+
if (value === null)
|
|
179
|
+
return;
|
|
180
|
+
if (typeof value === "string" && value.startsWith("$")) {
|
|
181
|
+
const paramName = value.slice(1);
|
|
182
|
+
const paramValue = routeParams[paramName];
|
|
183
|
+
if (!paramValue)
|
|
184
|
+
return;
|
|
185
|
+
where[key] = paramValue;
|
|
186
|
+
} else {
|
|
187
|
+
where[key] = value;
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
resolved.where = where;
|
|
191
|
+
}
|
|
192
|
+
if (bindings.select)
|
|
193
|
+
resolved.select = bindings.select;
|
|
194
|
+
if (bindings.include)
|
|
195
|
+
resolved.include = bindings.include;
|
|
196
|
+
if (bindings.orderBy)
|
|
197
|
+
resolved.orderBy = bindings.orderBy;
|
|
198
|
+
if (bindings.limit !== undefined)
|
|
199
|
+
resolved.limit = bindings.limit;
|
|
200
|
+
return resolved;
|
|
106
201
|
}
|
|
107
202
|
|
|
108
203
|
// src/slot-placeholder.ts
|
|
@@ -530,366 +625,61 @@ data: ${safeSerialize(entry)}
|
|
|
530
625
|
});
|
|
531
626
|
}
|
|
532
627
|
|
|
533
|
-
// src/ssr-
|
|
534
|
-
function
|
|
535
|
-
const
|
|
536
|
-
const
|
|
537
|
-
const
|
|
538
|
-
|
|
628
|
+
// src/ssr-route-matcher.ts
|
|
629
|
+
function matchUrlToPatterns(url, patterns) {
|
|
630
|
+
const path = (url.split("?")[0] ?? "").split("#")[0] ?? "";
|
|
631
|
+
const matches = [];
|
|
632
|
+
for (const pattern of patterns) {
|
|
633
|
+
const result = matchPattern(path, pattern);
|
|
634
|
+
if (result) {
|
|
635
|
+
matches.push(result);
|
|
636
|
+
}
|
|
637
|
+
}
|
|
638
|
+
matches.sort((a, b) => {
|
|
639
|
+
const aSegments = a.pattern.split("/").length;
|
|
640
|
+
const bSegments = b.pattern.split("/").length;
|
|
641
|
+
return aSegments - bSegments;
|
|
642
|
+
});
|
|
643
|
+
return matches;
|
|
539
644
|
}
|
|
540
|
-
function
|
|
541
|
-
|
|
645
|
+
function matchPattern(path, pattern) {
|
|
646
|
+
const pathSegments = path.split("/").filter(Boolean);
|
|
647
|
+
const patternSegments = pattern.split("/").filter(Boolean);
|
|
648
|
+
if (patternSegments.length > pathSegments.length)
|
|
649
|
+
return;
|
|
650
|
+
const params = {};
|
|
651
|
+
for (let i = 0;i < patternSegments.length; i++) {
|
|
652
|
+
const seg = patternSegments[i];
|
|
653
|
+
const val = pathSegments[i];
|
|
654
|
+
if (seg.startsWith(":")) {
|
|
655
|
+
params[seg.slice(1)] = val;
|
|
656
|
+
} else if (seg !== val) {
|
|
657
|
+
return;
|
|
658
|
+
}
|
|
659
|
+
}
|
|
660
|
+
return { pattern, params };
|
|
542
661
|
}
|
|
543
662
|
|
|
544
|
-
// src/
|
|
545
|
-
function
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
processed = inlineCSSAssets(processed, options.inlineCSS);
|
|
663
|
+
// src/ssr-single-pass.ts
|
|
664
|
+
async function ssrRenderSinglePass(module, url, options) {
|
|
665
|
+
if (options?.prefetch === false) {
|
|
666
|
+
return ssrRenderToString(module, url, options);
|
|
549
667
|
}
|
|
550
|
-
const
|
|
551
|
-
const
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
};
|
|
668
|
+
const normalizedUrl = url.endsWith("/index.html") ? url.slice(0, -"/index.html".length) || "/" : url;
|
|
669
|
+
const ssrTimeout = options?.ssrTimeout ?? 300;
|
|
670
|
+
ensureDomShim2();
|
|
671
|
+
const zeroDiscoveryData = attemptZeroDiscovery(normalizedUrl, module, options, ssrTimeout);
|
|
672
|
+
if (zeroDiscoveryData) {
|
|
673
|
+
return renderWithPrefetchedData(module, normalizedUrl, zeroDiscoveryData, options);
|
|
557
674
|
}
|
|
558
|
-
const
|
|
559
|
-
if (
|
|
560
|
-
const openTag = appDivMatch[0];
|
|
561
|
-
const contentStart = appDivMatch.index + openTag.length;
|
|
562
|
-
const closingIndex = findMatchingDivClose(processed, contentStart);
|
|
675
|
+
const discoveredData = await runDiscoveryPhase(normalizedUrl, ssrTimeout, module, options);
|
|
676
|
+
if ("redirect" in discoveredData) {
|
|
563
677
|
return {
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
}
|
|
570
|
-
function findMatchingDivClose(html, startAfterOpen) {
|
|
571
|
-
let depth = 1;
|
|
572
|
-
let i = startAfterOpen;
|
|
573
|
-
const len = html.length;
|
|
574
|
-
while (i < len && depth > 0) {
|
|
575
|
-
if (html[i] === "<") {
|
|
576
|
-
if (html.startsWith("</div>", i)) {
|
|
577
|
-
depth--;
|
|
578
|
-
if (depth === 0)
|
|
579
|
-
return i;
|
|
580
|
-
i += 6;
|
|
581
|
-
} else if (html.startsWith("<div", i) && /^<div[\s>]/.test(html.slice(i, i + 5))) {
|
|
582
|
-
depth++;
|
|
583
|
-
i += 4;
|
|
584
|
-
} else {
|
|
585
|
-
i++;
|
|
586
|
-
}
|
|
587
|
-
} else {
|
|
588
|
-
i++;
|
|
589
|
-
}
|
|
590
|
-
}
|
|
591
|
-
return len;
|
|
592
|
-
}
|
|
593
|
-
function inlineCSSAssets(html, inlineCSS) {
|
|
594
|
-
let result = html;
|
|
595
|
-
for (const [href, css] of Object.entries(inlineCSS)) {
|
|
596
|
-
const escapedHref = href.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
597
|
-
const linkPattern = new RegExp(`<link[^>]*href=["']${escapedHref}["'][^>]*>`);
|
|
598
|
-
const safeCss = css.replace(/<\//g, "<\\/");
|
|
599
|
-
result = result.replace(linkPattern, `<style data-vertz-css>${safeCss}</style>`);
|
|
600
|
-
}
|
|
601
|
-
result = result.replace(/<link\s+rel="stylesheet"\s+href="([^"]+)"[^>]*>/g, (match, href) => `<link rel="stylesheet" href="${href}" media="print" onload="this.media='all'">
|
|
602
|
-
<noscript>${match}</noscript>`);
|
|
603
|
-
return result;
|
|
604
|
-
}
|
|
605
|
-
|
|
606
|
-
// src/ssr-handler-shared.ts
|
|
607
|
-
function sanitizeLinkHref(href) {
|
|
608
|
-
return href.replace(/[<>,;\s"']/g, (ch) => `%${ch.charCodeAt(0).toString(16).toUpperCase()}`);
|
|
609
|
-
}
|
|
610
|
-
function sanitizeLinkParam(value) {
|
|
611
|
-
return value.replace(/[^a-zA-Z0-9/_.-]/g, "");
|
|
612
|
-
}
|
|
613
|
-
function buildLinkHeader(items) {
|
|
614
|
-
return items.map((item) => {
|
|
615
|
-
const parts = [
|
|
616
|
-
`<${sanitizeLinkHref(item.href)}>`,
|
|
617
|
-
"rel=preload",
|
|
618
|
-
`as=${sanitizeLinkParam(item.as)}`
|
|
619
|
-
];
|
|
620
|
-
if (item.type)
|
|
621
|
-
parts.push(`type=${sanitizeLinkParam(item.type)}`);
|
|
622
|
-
if (item.crossorigin)
|
|
623
|
-
parts.push("crossorigin");
|
|
624
|
-
return parts.join("; ");
|
|
625
|
-
}).join(", ");
|
|
626
|
-
}
|
|
627
|
-
function buildModulepreloadTags(paths) {
|
|
628
|
-
return paths.map((p) => `<link rel="modulepreload" href="${escapeAttr(p)}">`).join(`
|
|
629
|
-
`);
|
|
630
|
-
}
|
|
631
|
-
function resolveRouteModulepreload(routeChunkManifest, matchedPatterns, fallback) {
|
|
632
|
-
if (routeChunkManifest && matchedPatterns?.length) {
|
|
633
|
-
const chunkPaths = new Set;
|
|
634
|
-
for (const pattern of matchedPatterns) {
|
|
635
|
-
const chunks = routeChunkManifest.routes[pattern];
|
|
636
|
-
if (chunks) {
|
|
637
|
-
for (const chunk of chunks) {
|
|
638
|
-
chunkPaths.add(chunk);
|
|
639
|
-
}
|
|
640
|
-
}
|
|
641
|
-
}
|
|
642
|
-
if (chunkPaths.size > 0) {
|
|
643
|
-
return buildModulepreloadTags([...chunkPaths]);
|
|
644
|
-
}
|
|
645
|
-
}
|
|
646
|
-
return fallback;
|
|
647
|
-
}
|
|
648
|
-
function preprocessInlineCSS(template, inlineCSS) {
|
|
649
|
-
let result = template;
|
|
650
|
-
for (const [href, css] of Object.entries(inlineCSS)) {
|
|
651
|
-
const escapedHref = href.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
652
|
-
const linkPattern = new RegExp(`<link[^>]*href=["']${escapedHref}["'][^>]*>`);
|
|
653
|
-
const safeCss = css.replace(/<\//g, "<\\/");
|
|
654
|
-
result = result.replace(linkPattern, `<style data-vertz-css>${safeCss}</style>`);
|
|
655
|
-
}
|
|
656
|
-
return result;
|
|
657
|
-
}
|
|
658
|
-
function precomputeHandlerState(options) {
|
|
659
|
-
const { module, inlineCSS, fallbackMetrics, modulepreload, progressiveHTML } = options;
|
|
660
|
-
let template = options.template;
|
|
661
|
-
if (inlineCSS) {
|
|
662
|
-
template = preprocessInlineCSS(template, inlineCSS);
|
|
663
|
-
}
|
|
664
|
-
let linkHeader;
|
|
665
|
-
if (module.theme) {
|
|
666
|
-
const compiled = compileThemeCached(module.theme, fallbackMetrics);
|
|
667
|
-
if (compiled.preloadItems.length > 0) {
|
|
668
|
-
linkHeader = buildLinkHeader(compiled.preloadItems);
|
|
669
|
-
}
|
|
670
|
-
}
|
|
671
|
-
const modulepreloadTags = modulepreload?.length ? buildModulepreloadTags(modulepreload) : undefined;
|
|
672
|
-
const splitResult = progressiveHTML ? splitTemplate(template) : undefined;
|
|
673
|
-
if (splitResult && module.theme) {
|
|
674
|
-
splitResult.headTemplate = splitResult.headTemplate.replace(/<link\s+rel="stylesheet"\s+href="([^"]+)"[^>]*>/g, (match, href) => `<link rel="stylesheet" href="${href}" media="print" onload="this.media='all'">
|
|
675
|
-
<noscript>${match}</noscript>`);
|
|
676
|
-
}
|
|
677
|
-
return { template, linkHeader, modulepreloadTags, splitResult };
|
|
678
|
-
}
|
|
679
|
-
async function resolveSession(request, sessionResolver, nonce) {
|
|
680
|
-
let sessionScript = "";
|
|
681
|
-
let ssrAuth;
|
|
682
|
-
try {
|
|
683
|
-
const sessionResult = await sessionResolver(request);
|
|
684
|
-
if (sessionResult) {
|
|
685
|
-
ssrAuth = {
|
|
686
|
-
status: "authenticated",
|
|
687
|
-
user: sessionResult.session.user,
|
|
688
|
-
expiresAt: sessionResult.session.expiresAt
|
|
689
|
-
};
|
|
690
|
-
const scripts = [];
|
|
691
|
-
scripts.push(createSessionScript(sessionResult.session, nonce));
|
|
692
|
-
if (sessionResult.accessSet != null) {
|
|
693
|
-
scripts.push(createAccessSetScript(sessionResult.accessSet, nonce));
|
|
694
|
-
}
|
|
695
|
-
sessionScript = scripts.join(`
|
|
696
|
-
`);
|
|
697
|
-
} else {
|
|
698
|
-
ssrAuth = { status: "unauthenticated" };
|
|
699
|
-
}
|
|
700
|
-
} catch (resolverErr) {
|
|
701
|
-
console.warn("[Server] Session resolver failed:", resolverErr instanceof Error ? resolverErr.message : resolverErr);
|
|
702
|
-
}
|
|
703
|
-
return { sessionScript, ssrAuth };
|
|
704
|
-
}
|
|
705
|
-
|
|
706
|
-
// src/ssr-access-evaluator.ts
|
|
707
|
-
function toPrefetchSession(ssrAuth, accessSet) {
|
|
708
|
-
if (!ssrAuth || ssrAuth.status !== "authenticated" || !ssrAuth.user) {
|
|
709
|
-
return { status: "unauthenticated" };
|
|
710
|
-
}
|
|
711
|
-
const roles = ssrAuth.user.role ? [ssrAuth.user.role] : undefined;
|
|
712
|
-
const entitlements = accessSet != null ? Object.fromEntries(Object.entries(accessSet.entitlements).map(([name, check]) => [name, check.allowed])) : undefined;
|
|
713
|
-
return {
|
|
714
|
-
status: "authenticated",
|
|
715
|
-
roles,
|
|
716
|
-
entitlements,
|
|
717
|
-
tenantId: ssrAuth.user.tenantId
|
|
718
|
-
};
|
|
719
|
-
}
|
|
720
|
-
function evaluateAccessRule(rule, session) {
|
|
721
|
-
switch (rule.type) {
|
|
722
|
-
case "public":
|
|
723
|
-
return true;
|
|
724
|
-
case "authenticated":
|
|
725
|
-
return session.status === "authenticated";
|
|
726
|
-
case "role":
|
|
727
|
-
if (session.status !== "authenticated")
|
|
728
|
-
return false;
|
|
729
|
-
return session.roles?.some((r) => rule.roles.includes(r)) === true;
|
|
730
|
-
case "entitlement":
|
|
731
|
-
if (session.status !== "authenticated")
|
|
732
|
-
return false;
|
|
733
|
-
return session.entitlements?.[rule.value] === true;
|
|
734
|
-
case "where":
|
|
735
|
-
return true;
|
|
736
|
-
case "fva":
|
|
737
|
-
return session.status === "authenticated";
|
|
738
|
-
case "deny":
|
|
739
|
-
return false;
|
|
740
|
-
case "all":
|
|
741
|
-
return rule.rules.every((r) => evaluateAccessRule(r, session));
|
|
742
|
-
case "any":
|
|
743
|
-
return rule.rules.some((r) => evaluateAccessRule(r, session));
|
|
744
|
-
default:
|
|
745
|
-
return false;
|
|
746
|
-
}
|
|
747
|
-
}
|
|
748
|
-
|
|
749
|
-
// src/ssr-manifest-prefetch.ts
|
|
750
|
-
function reconstructDescriptors(queries, routeParams, apiClient) {
|
|
751
|
-
if (!apiClient)
|
|
752
|
-
return [];
|
|
753
|
-
const result = [];
|
|
754
|
-
for (const query of queries) {
|
|
755
|
-
const descriptor = reconstructSingle(query, routeParams, apiClient);
|
|
756
|
-
if (descriptor) {
|
|
757
|
-
result.push(descriptor);
|
|
758
|
-
}
|
|
759
|
-
}
|
|
760
|
-
return result;
|
|
761
|
-
}
|
|
762
|
-
function reconstructSingle(query, routeParams, apiClient) {
|
|
763
|
-
const { entity, operation } = query;
|
|
764
|
-
if (!entity || !operation)
|
|
765
|
-
return;
|
|
766
|
-
const entitySdk = apiClient[entity];
|
|
767
|
-
if (!entitySdk)
|
|
768
|
-
return;
|
|
769
|
-
const method = entitySdk[operation];
|
|
770
|
-
if (typeof method !== "function")
|
|
771
|
-
return;
|
|
772
|
-
const args = buildFactoryArgs(query, routeParams);
|
|
773
|
-
if (args === undefined)
|
|
774
|
-
return;
|
|
775
|
-
try {
|
|
776
|
-
const descriptor = method(...args);
|
|
777
|
-
if (!descriptor || typeof descriptor._key !== "string" || typeof descriptor._fetch !== "function") {
|
|
778
|
-
return;
|
|
779
|
-
}
|
|
780
|
-
return { key: descriptor._key, fetch: descriptor._fetch };
|
|
781
|
-
} catch {
|
|
782
|
-
return;
|
|
783
|
-
}
|
|
784
|
-
}
|
|
785
|
-
function buildFactoryArgs(query, routeParams) {
|
|
786
|
-
const { operation, idParam, queryBindings } = query;
|
|
787
|
-
if (operation === "get") {
|
|
788
|
-
if (idParam) {
|
|
789
|
-
const id = routeParams[idParam];
|
|
790
|
-
if (!id)
|
|
791
|
-
return;
|
|
792
|
-
const options = resolveQueryBindings(queryBindings, routeParams);
|
|
793
|
-
if (options === undefined && queryBindings)
|
|
794
|
-
return;
|
|
795
|
-
return options ? [id, options] : [id];
|
|
796
|
-
}
|
|
797
|
-
return;
|
|
798
|
-
}
|
|
799
|
-
if (!queryBindings)
|
|
800
|
-
return [];
|
|
801
|
-
const resolved = resolveQueryBindings(queryBindings, routeParams);
|
|
802
|
-
if (resolved === undefined)
|
|
803
|
-
return;
|
|
804
|
-
return [resolved];
|
|
805
|
-
}
|
|
806
|
-
function resolveQueryBindings(bindings, routeParams) {
|
|
807
|
-
if (!bindings)
|
|
808
|
-
return;
|
|
809
|
-
const resolved = {};
|
|
810
|
-
if (bindings.where) {
|
|
811
|
-
const where = {};
|
|
812
|
-
for (const [key, value] of Object.entries(bindings.where)) {
|
|
813
|
-
if (value === null)
|
|
814
|
-
return;
|
|
815
|
-
if (typeof value === "string" && value.startsWith("$")) {
|
|
816
|
-
const paramName = value.slice(1);
|
|
817
|
-
const paramValue = routeParams[paramName];
|
|
818
|
-
if (!paramValue)
|
|
819
|
-
return;
|
|
820
|
-
where[key] = paramValue;
|
|
821
|
-
} else {
|
|
822
|
-
where[key] = value;
|
|
823
|
-
}
|
|
824
|
-
}
|
|
825
|
-
resolved.where = where;
|
|
826
|
-
}
|
|
827
|
-
if (bindings.select)
|
|
828
|
-
resolved.select = bindings.select;
|
|
829
|
-
if (bindings.include)
|
|
830
|
-
resolved.include = bindings.include;
|
|
831
|
-
if (bindings.orderBy)
|
|
832
|
-
resolved.orderBy = bindings.orderBy;
|
|
833
|
-
if (bindings.limit !== undefined)
|
|
834
|
-
resolved.limit = bindings.limit;
|
|
835
|
-
return resolved;
|
|
836
|
-
}
|
|
837
|
-
|
|
838
|
-
// src/ssr-route-matcher.ts
|
|
839
|
-
function matchUrlToPatterns(url, patterns) {
|
|
840
|
-
const path = (url.split("?")[0] ?? "").split("#")[0] ?? "";
|
|
841
|
-
const matches = [];
|
|
842
|
-
for (const pattern of patterns) {
|
|
843
|
-
const result = matchPattern(path, pattern);
|
|
844
|
-
if (result) {
|
|
845
|
-
matches.push(result);
|
|
846
|
-
}
|
|
847
|
-
}
|
|
848
|
-
matches.sort((a, b) => {
|
|
849
|
-
const aSegments = a.pattern.split("/").length;
|
|
850
|
-
const bSegments = b.pattern.split("/").length;
|
|
851
|
-
return aSegments - bSegments;
|
|
852
|
-
});
|
|
853
|
-
return matches;
|
|
854
|
-
}
|
|
855
|
-
function matchPattern(path, pattern) {
|
|
856
|
-
const pathSegments = path.split("/").filter(Boolean);
|
|
857
|
-
const patternSegments = pattern.split("/").filter(Boolean);
|
|
858
|
-
if (patternSegments.length > pathSegments.length)
|
|
859
|
-
return;
|
|
860
|
-
const params = {};
|
|
861
|
-
for (let i = 0;i < patternSegments.length; i++) {
|
|
862
|
-
const seg = patternSegments[i];
|
|
863
|
-
const val = pathSegments[i];
|
|
864
|
-
if (seg.startsWith(":")) {
|
|
865
|
-
params[seg.slice(1)] = val;
|
|
866
|
-
} else if (seg !== val) {
|
|
867
|
-
return;
|
|
868
|
-
}
|
|
869
|
-
}
|
|
870
|
-
return { pattern, params };
|
|
871
|
-
}
|
|
872
|
-
|
|
873
|
-
// src/ssr-single-pass.ts
|
|
874
|
-
async function ssrRenderSinglePass(module, url, options) {
|
|
875
|
-
if (options?.prefetch === false) {
|
|
876
|
-
return ssrRenderToString(module, url, options);
|
|
877
|
-
}
|
|
878
|
-
const normalizedUrl = url.endsWith("/index.html") ? url.slice(0, -"/index.html".length) || "/" : url;
|
|
879
|
-
const ssrTimeout = options?.ssrTimeout ?? 300;
|
|
880
|
-
ensureDomShim2();
|
|
881
|
-
const zeroDiscoveryData = attemptZeroDiscovery(normalizedUrl, module, options, ssrTimeout);
|
|
882
|
-
if (zeroDiscoveryData) {
|
|
883
|
-
return renderWithPrefetchedData(module, normalizedUrl, zeroDiscoveryData, options);
|
|
884
|
-
}
|
|
885
|
-
const discoveredData = await runDiscoveryPhase(normalizedUrl, ssrTimeout, module, options);
|
|
886
|
-
if ("redirect" in discoveredData) {
|
|
887
|
-
return {
|
|
888
|
-
html: "",
|
|
889
|
-
css: "",
|
|
890
|
-
ssrData: [],
|
|
891
|
-
headTags: "",
|
|
892
|
-
redirect: discoveredData.redirect
|
|
678
|
+
html: "",
|
|
679
|
+
css: "",
|
|
680
|
+
ssrData: [],
|
|
681
|
+
headTags: "",
|
|
682
|
+
redirect: discoveredData.redirect
|
|
893
683
|
};
|
|
894
684
|
}
|
|
895
685
|
const renderCtx = createRequestContext(normalizedUrl);
|
|
@@ -1176,31 +966,220 @@ function filterByEntityAccess(queries, entityAccess, session) {
|
|
|
1176
966
|
return evaluateAccessRule(rule, session);
|
|
1177
967
|
});
|
|
1178
968
|
}
|
|
1179
|
-
function extractEntityFromKey(key) {
|
|
1180
|
-
const pathStart = key.indexOf(":/");
|
|
1181
|
-
if (pathStart === -1)
|
|
969
|
+
function extractEntityFromKey(key) {
|
|
970
|
+
const pathStart = key.indexOf(":/");
|
|
971
|
+
if (pathStart === -1)
|
|
972
|
+
return;
|
|
973
|
+
const path = key.slice(pathStart + 2);
|
|
974
|
+
const firstSlash = path.indexOf("/");
|
|
975
|
+
const questionMark = path.indexOf("?");
|
|
976
|
+
if (firstSlash === -1 && questionMark === -1)
|
|
977
|
+
return path;
|
|
978
|
+
if (firstSlash === -1)
|
|
979
|
+
return path.slice(0, questionMark);
|
|
980
|
+
if (questionMark === -1)
|
|
981
|
+
return path.slice(0, firstSlash);
|
|
982
|
+
return path.slice(0, Math.min(firstSlash, questionMark));
|
|
983
|
+
}
|
|
984
|
+
function extractMethodFromKey(key) {
|
|
985
|
+
const pathStart = key.indexOf(":/");
|
|
986
|
+
if (pathStart === -1)
|
|
987
|
+
return "list";
|
|
988
|
+
const path = key.slice(pathStart + 2);
|
|
989
|
+
const cleanPath = path.split("?")[0] ?? "";
|
|
990
|
+
const segments = cleanPath.split("/").filter(Boolean);
|
|
991
|
+
return segments.length > 1 ? "get" : "list";
|
|
992
|
+
}
|
|
993
|
+
function collectCSS2(themeCss, module) {
|
|
994
|
+
const alreadyIncluded = new Set;
|
|
995
|
+
if (themeCss)
|
|
996
|
+
alreadyIncluded.add(themeCss);
|
|
997
|
+
if (module.styles) {
|
|
998
|
+
for (const s of module.styles)
|
|
999
|
+
alreadyIncluded.add(s);
|
|
1000
|
+
}
|
|
1001
|
+
const componentCss = module.getInjectedCSS ? module.getInjectedCSS().filter((s) => !alreadyIncluded.has(s)) : [];
|
|
1002
|
+
const themeTag = themeCss ? `<style data-vertz-css>${themeCss}</style>` : "";
|
|
1003
|
+
const globalTag = module.styles && module.styles.length > 0 ? `<style data-vertz-css>${module.styles.join(`
|
|
1004
|
+
`)}</style>` : "";
|
|
1005
|
+
const componentTag = componentCss.length > 0 ? `<style data-vertz-css>${componentCss.join(`
|
|
1006
|
+
`)}</style>` : "";
|
|
1007
|
+
return [themeTag, globalTag, componentTag].filter(Boolean).join(`
|
|
1008
|
+
`);
|
|
1009
|
+
}
|
|
1010
|
+
|
|
1011
|
+
// src/ssr-aot-pipeline.ts
|
|
1012
|
+
function createHoles(holeNames, module, url, queryCache, ssrAuth) {
|
|
1013
|
+
if (holeNames.length === 0)
|
|
1014
|
+
return {};
|
|
1015
|
+
const holes = {};
|
|
1016
|
+
for (const name of holeNames) {
|
|
1017
|
+
holes[name] = () => {
|
|
1018
|
+
const holeCtx = createRequestContext(url);
|
|
1019
|
+
for (const [key, data] of queryCache) {
|
|
1020
|
+
holeCtx.queryCache.set(key, data);
|
|
1021
|
+
}
|
|
1022
|
+
if (ssrAuth) {
|
|
1023
|
+
holeCtx.ssrAuth = ssrAuth;
|
|
1024
|
+
}
|
|
1025
|
+
holeCtx.resolvedComponents = new Map;
|
|
1026
|
+
return ssrStorage.run(holeCtx, () => {
|
|
1027
|
+
ensureDomShim3();
|
|
1028
|
+
const factory = resolveHoleComponent(module, name);
|
|
1029
|
+
if (!factory) {
|
|
1030
|
+
return `<!-- AOT hole: ${name} not found -->`;
|
|
1031
|
+
}
|
|
1032
|
+
const node = factory();
|
|
1033
|
+
const vnode = toVNode(node);
|
|
1034
|
+
return serializeToHtml(vnode);
|
|
1035
|
+
});
|
|
1036
|
+
};
|
|
1037
|
+
}
|
|
1038
|
+
return holes;
|
|
1039
|
+
}
|
|
1040
|
+
function resolveHoleComponent(module, name) {
|
|
1041
|
+
const moduleRecord = module;
|
|
1042
|
+
const exported = moduleRecord[name];
|
|
1043
|
+
if (typeof exported === "function") {
|
|
1044
|
+
return exported;
|
|
1045
|
+
}
|
|
1046
|
+
return;
|
|
1047
|
+
}
|
|
1048
|
+
async function ssrRenderAot(module, url, options) {
|
|
1049
|
+
const { aotManifest, manifest } = options;
|
|
1050
|
+
const ssrTimeout = options.ssrTimeout ?? 300;
|
|
1051
|
+
const normalizedUrl = url.endsWith("/index.html") ? url.slice(0, -"/index.html".length) || "/" : url;
|
|
1052
|
+
const fallbackOptions = {
|
|
1053
|
+
ssrTimeout,
|
|
1054
|
+
fallbackMetrics: options.fallbackMetrics,
|
|
1055
|
+
ssrAuth: options.ssrAuth,
|
|
1056
|
+
manifest,
|
|
1057
|
+
prefetchSession: options.prefetchSession
|
|
1058
|
+
};
|
|
1059
|
+
const aotPatterns = Object.keys(aotManifest.routes);
|
|
1060
|
+
const matches = matchUrlToPatterns(normalizedUrl, aotPatterns);
|
|
1061
|
+
if (matches.length === 0) {
|
|
1062
|
+
return ssrRenderSinglePass(module, normalizedUrl, fallbackOptions);
|
|
1063
|
+
}
|
|
1064
|
+
const match = matches[matches.length - 1];
|
|
1065
|
+
if (!match) {
|
|
1066
|
+
return ssrRenderSinglePass(module, normalizedUrl, fallbackOptions);
|
|
1067
|
+
}
|
|
1068
|
+
const aotEntry = aotManifest.routes[match.pattern];
|
|
1069
|
+
if (!aotEntry) {
|
|
1070
|
+
return ssrRenderSinglePass(module, normalizedUrl, fallbackOptions);
|
|
1071
|
+
}
|
|
1072
|
+
const queryCache = new Map;
|
|
1073
|
+
if (aotEntry.queryKeys && aotEntry.queryKeys.length > 0 && manifest?.routeEntries) {
|
|
1074
|
+
const apiClient = module.api;
|
|
1075
|
+
if (apiClient) {
|
|
1076
|
+
await prefetchForAot(aotEntry.queryKeys, manifest.routeEntries, match, apiClient, ssrTimeout, queryCache);
|
|
1077
|
+
}
|
|
1078
|
+
}
|
|
1079
|
+
try {
|
|
1080
|
+
setGlobalSSRTimeout(ssrTimeout);
|
|
1081
|
+
const holes = createHoles(aotEntry.holes, module, normalizedUrl, queryCache, options.ssrAuth);
|
|
1082
|
+
const ctx = {
|
|
1083
|
+
holes,
|
|
1084
|
+
getData: (key) => queryCache.get(key),
|
|
1085
|
+
session: options.prefetchSession,
|
|
1086
|
+
params: match.params
|
|
1087
|
+
};
|
|
1088
|
+
const data = {};
|
|
1089
|
+
for (const [key, value] of queryCache) {
|
|
1090
|
+
data[key] = value;
|
|
1091
|
+
}
|
|
1092
|
+
const html = aotEntry.render(data, ctx);
|
|
1093
|
+
if (options.diagnostics && isAotDebugEnabled()) {
|
|
1094
|
+
try {
|
|
1095
|
+
const domResult = await ssrRenderSinglePass(module, normalizedUrl, fallbackOptions);
|
|
1096
|
+
if (domResult.html !== html) {
|
|
1097
|
+
options.diagnostics.recordDivergence(match.pattern, html, domResult.html);
|
|
1098
|
+
}
|
|
1099
|
+
} catch {}
|
|
1100
|
+
}
|
|
1101
|
+
const css = collectCSSFromModule(module, options.fallbackMetrics);
|
|
1102
|
+
const ssrData = [];
|
|
1103
|
+
for (const [key, data2] of queryCache) {
|
|
1104
|
+
ssrData.push({ key, data: data2 });
|
|
1105
|
+
}
|
|
1106
|
+
return {
|
|
1107
|
+
html,
|
|
1108
|
+
css: css.cssString,
|
|
1109
|
+
ssrData,
|
|
1110
|
+
headTags: css.preloadTags,
|
|
1111
|
+
matchedRoutePatterns: [match.pattern]
|
|
1112
|
+
};
|
|
1113
|
+
} finally {
|
|
1114
|
+
clearGlobalSSRTimeout();
|
|
1115
|
+
}
|
|
1116
|
+
}
|
|
1117
|
+
async function prefetchForAot(queryKeys, routeEntries, match, apiClient, ssrTimeout, queryCache) {
|
|
1118
|
+
const entry = routeEntries[match.pattern];
|
|
1119
|
+
if (!entry)
|
|
1182
1120
|
return;
|
|
1183
|
-
const
|
|
1184
|
-
const
|
|
1185
|
-
const
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1121
|
+
const queryKeySet = new Set(queryKeys);
|
|
1122
|
+
const fetchJobs = [];
|
|
1123
|
+
for (const query of entry.queries) {
|
|
1124
|
+
if (!query.entity || !query.operation)
|
|
1125
|
+
continue;
|
|
1126
|
+
const aotKey = `${query.entity}-${query.operation}`;
|
|
1127
|
+
if (!queryKeySet.has(aotKey))
|
|
1128
|
+
continue;
|
|
1129
|
+
const descriptors = reconstructDescriptors([query], match.params, apiClient);
|
|
1130
|
+
if (descriptors.length > 0 && descriptors[0]) {
|
|
1131
|
+
fetchJobs.push({ aotKey, fetchFn: descriptors[0].fetch });
|
|
1132
|
+
}
|
|
1133
|
+
}
|
|
1134
|
+
if (fetchJobs.length === 0)
|
|
1135
|
+
return;
|
|
1136
|
+
await Promise.allSettled(fetchJobs.map(({ aotKey, fetchFn }) => {
|
|
1137
|
+
let timer;
|
|
1138
|
+
return Promise.race([
|
|
1139
|
+
fetchFn().then((result) => {
|
|
1140
|
+
clearTimeout(timer);
|
|
1141
|
+
const data = unwrapResult2(result);
|
|
1142
|
+
queryCache.set(aotKey, data);
|
|
1143
|
+
}),
|
|
1144
|
+
new Promise((r) => {
|
|
1145
|
+
timer = setTimeout(r, ssrTimeout);
|
|
1146
|
+
})
|
|
1147
|
+
]);
|
|
1148
|
+
}));
|
|
1193
1149
|
}
|
|
1194
|
-
function
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
return segments.length > 1 ? "get" : "list";
|
|
1150
|
+
function unwrapResult2(result) {
|
|
1151
|
+
if (result && typeof result === "object" && "ok" in result && "data" in result) {
|
|
1152
|
+
const r = result;
|
|
1153
|
+
if (r.ok)
|
|
1154
|
+
return r.data;
|
|
1155
|
+
}
|
|
1156
|
+
return result;
|
|
1202
1157
|
}
|
|
1203
|
-
function
|
|
1158
|
+
function isAotDebugEnabled() {
|
|
1159
|
+
const env = process.env.VERTZ_DEBUG;
|
|
1160
|
+
if (!env)
|
|
1161
|
+
return false;
|
|
1162
|
+
return env === "1" || env.split(",").includes("aot");
|
|
1163
|
+
}
|
|
1164
|
+
var domShimInstalled3 = false;
|
|
1165
|
+
function ensureDomShim3() {
|
|
1166
|
+
if (domShimInstalled3 && typeof document !== "undefined")
|
|
1167
|
+
return;
|
|
1168
|
+
domShimInstalled3 = true;
|
|
1169
|
+
installDomShim();
|
|
1170
|
+
}
|
|
1171
|
+
function collectCSSFromModule(module, fallbackMetrics) {
|
|
1172
|
+
let themeCss = "";
|
|
1173
|
+
let preloadTags = "";
|
|
1174
|
+
if (module.theme) {
|
|
1175
|
+
try {
|
|
1176
|
+
const compiled = compileThemeCached(module.theme, fallbackMetrics);
|
|
1177
|
+
themeCss = compiled.css;
|
|
1178
|
+
preloadTags = compiled.preloadTags;
|
|
1179
|
+
} catch (e) {
|
|
1180
|
+
console.error("[vertz] Failed to compile theme export. Ensure your theme is created with defineTheme().", e);
|
|
1181
|
+
}
|
|
1182
|
+
}
|
|
1204
1183
|
const alreadyIncluded = new Set;
|
|
1205
1184
|
if (themeCss)
|
|
1206
1185
|
alreadyIncluded.add(themeCss);
|
|
@@ -1214,8 +1193,219 @@ function collectCSS2(themeCss, module) {
|
|
|
1214
1193
|
`)}</style>` : "";
|
|
1215
1194
|
const componentTag = componentCss.length > 0 ? `<style data-vertz-css>${componentCss.join(`
|
|
1216
1195
|
`)}</style>` : "";
|
|
1217
|
-
|
|
1196
|
+
const cssString = [themeTag, globalTag, componentTag].filter(Boolean).join(`
|
|
1197
|
+
`);
|
|
1198
|
+
return { cssString, preloadTags };
|
|
1199
|
+
}
|
|
1200
|
+
|
|
1201
|
+
// src/ssr-access-set.ts
|
|
1202
|
+
function getAccessSetForSSR(jwtPayload) {
|
|
1203
|
+
if (!jwtPayload)
|
|
1204
|
+
return null;
|
|
1205
|
+
const acl = jwtPayload.acl;
|
|
1206
|
+
if (!acl)
|
|
1207
|
+
return null;
|
|
1208
|
+
if (acl.overflow)
|
|
1209
|
+
return null;
|
|
1210
|
+
if (!acl.set)
|
|
1211
|
+
return null;
|
|
1212
|
+
return {
|
|
1213
|
+
entitlements: Object.fromEntries(Object.entries(acl.set.entitlements).map(([name, check]) => [
|
|
1214
|
+
name,
|
|
1215
|
+
{
|
|
1216
|
+
allowed: check.allowed,
|
|
1217
|
+
reasons: check.reasons ?? [],
|
|
1218
|
+
...check.reason ? { reason: check.reason } : {},
|
|
1219
|
+
...check.meta ? { meta: check.meta } : {}
|
|
1220
|
+
}
|
|
1221
|
+
])),
|
|
1222
|
+
flags: acl.set.flags,
|
|
1223
|
+
plan: acl.set.plan,
|
|
1224
|
+
plans: acl.set.plans ?? {},
|
|
1225
|
+
computedAt: acl.set.computedAt
|
|
1226
|
+
};
|
|
1227
|
+
}
|
|
1228
|
+
function createAccessSetScript(accessSet, nonce) {
|
|
1229
|
+
const json = JSON.stringify(accessSet);
|
|
1230
|
+
const escaped = json.replace(/</g, "\\u003c").replace(/\u2028/g, "\\u2028").replace(/\u2029/g, "\\u2029");
|
|
1231
|
+
const nonceAttr = nonce ? ` nonce="${escapeAttr2(nonce)}"` : "";
|
|
1232
|
+
return `<script${nonceAttr}>window.__VERTZ_ACCESS_SET__=${escaped}</script>`;
|
|
1233
|
+
}
|
|
1234
|
+
function escapeAttr2(s) {
|
|
1235
|
+
return s.replace(/[&"'<>]/g, (c) => `&#${c.charCodeAt(0)};`);
|
|
1236
|
+
}
|
|
1237
|
+
|
|
1238
|
+
// src/ssr-session.ts
|
|
1239
|
+
function createSessionScript(session, nonce) {
|
|
1240
|
+
const json = JSON.stringify(session);
|
|
1241
|
+
const escaped = json.replace(/</g, "\\u003c").replace(/\u2028/g, "\\u2028").replace(/\u2029/g, "\\u2029");
|
|
1242
|
+
const nonceAttr = nonce ? ` nonce="${escapeAttr3(nonce)}"` : "";
|
|
1243
|
+
return `<script${nonceAttr}>window.__VERTZ_SESSION__=${escaped}</script>`;
|
|
1244
|
+
}
|
|
1245
|
+
function escapeAttr3(s) {
|
|
1246
|
+
return s.replace(/[&"'<>]/g, (c) => `&#${c.charCodeAt(0)};`);
|
|
1247
|
+
}
|
|
1248
|
+
|
|
1249
|
+
// src/template-split.ts
|
|
1250
|
+
function splitTemplate(template, options) {
|
|
1251
|
+
let processed = template;
|
|
1252
|
+
if (options?.inlineCSS) {
|
|
1253
|
+
processed = inlineCSSAssets(processed, options.inlineCSS);
|
|
1254
|
+
}
|
|
1255
|
+
const outletMarker = "<!--ssr-outlet-->";
|
|
1256
|
+
const outletIndex = processed.indexOf(outletMarker);
|
|
1257
|
+
if (outletIndex !== -1) {
|
|
1258
|
+
return {
|
|
1259
|
+
headTemplate: processed.slice(0, outletIndex),
|
|
1260
|
+
tailTemplate: processed.slice(outletIndex + outletMarker.length)
|
|
1261
|
+
};
|
|
1262
|
+
}
|
|
1263
|
+
const appDivMatch = processed.match(/<div[^>]*id="app"[^>]*>/);
|
|
1264
|
+
if (appDivMatch && appDivMatch.index != null) {
|
|
1265
|
+
const openTag = appDivMatch[0];
|
|
1266
|
+
const contentStart = appDivMatch.index + openTag.length;
|
|
1267
|
+
const closingIndex = findMatchingDivClose(processed, contentStart);
|
|
1268
|
+
return {
|
|
1269
|
+
headTemplate: processed.slice(0, contentStart),
|
|
1270
|
+
tailTemplate: processed.slice(closingIndex)
|
|
1271
|
+
};
|
|
1272
|
+
}
|
|
1273
|
+
throw new Error('Could not find <!--ssr-outlet--> or <div id="app"> in the HTML template. ' + "The template must contain one of these markers for SSR content injection.");
|
|
1274
|
+
}
|
|
1275
|
+
function findMatchingDivClose(html, startAfterOpen) {
|
|
1276
|
+
let depth = 1;
|
|
1277
|
+
let i = startAfterOpen;
|
|
1278
|
+
const len = html.length;
|
|
1279
|
+
while (i < len && depth > 0) {
|
|
1280
|
+
if (html[i] === "<") {
|
|
1281
|
+
if (html.startsWith("</div>", i)) {
|
|
1282
|
+
depth--;
|
|
1283
|
+
if (depth === 0)
|
|
1284
|
+
return i;
|
|
1285
|
+
i += 6;
|
|
1286
|
+
} else if (html.startsWith("<div", i) && /^<div[\s>]/.test(html.slice(i, i + 5))) {
|
|
1287
|
+
depth++;
|
|
1288
|
+
i += 4;
|
|
1289
|
+
} else {
|
|
1290
|
+
i++;
|
|
1291
|
+
}
|
|
1292
|
+
} else {
|
|
1293
|
+
i++;
|
|
1294
|
+
}
|
|
1295
|
+
}
|
|
1296
|
+
return len;
|
|
1297
|
+
}
|
|
1298
|
+
function inlineCSSAssets(html, inlineCSS) {
|
|
1299
|
+
let result = html;
|
|
1300
|
+
for (const [href, css] of Object.entries(inlineCSS)) {
|
|
1301
|
+
const escapedHref = href.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
1302
|
+
const linkPattern = new RegExp(`<link[^>]*href=["']${escapedHref}["'][^>]*>`);
|
|
1303
|
+
const safeCss = css.replace(/<\//g, "<\\/");
|
|
1304
|
+
result = result.replace(linkPattern, `<style data-vertz-css>${safeCss}</style>`);
|
|
1305
|
+
}
|
|
1306
|
+
result = result.replace(/<link\s+rel="stylesheet"\s+href="([^"]+)"[^>]*>/g, (match, href) => `<link rel="stylesheet" href="${href}" media="print" onload="this.media='all'">
|
|
1307
|
+
<noscript>${match}</noscript>`);
|
|
1308
|
+
return result;
|
|
1309
|
+
}
|
|
1310
|
+
|
|
1311
|
+
// src/ssr-handler-shared.ts
|
|
1312
|
+
function sanitizeLinkHref(href) {
|
|
1313
|
+
return href.replace(/[<>,;\s"']/g, (ch) => `%${ch.charCodeAt(0).toString(16).toUpperCase()}`);
|
|
1314
|
+
}
|
|
1315
|
+
function sanitizeLinkParam(value) {
|
|
1316
|
+
return value.replace(/[^a-zA-Z0-9/_.-]/g, "");
|
|
1317
|
+
}
|
|
1318
|
+
function buildLinkHeader(items) {
|
|
1319
|
+
return items.map((item) => {
|
|
1320
|
+
const parts = [
|
|
1321
|
+
`<${sanitizeLinkHref(item.href)}>`,
|
|
1322
|
+
"rel=preload",
|
|
1323
|
+
`as=${sanitizeLinkParam(item.as)}`
|
|
1324
|
+
];
|
|
1325
|
+
if (item.type)
|
|
1326
|
+
parts.push(`type=${sanitizeLinkParam(item.type)}`);
|
|
1327
|
+
if (item.crossorigin)
|
|
1328
|
+
parts.push("crossorigin");
|
|
1329
|
+
return parts.join("; ");
|
|
1330
|
+
}).join(", ");
|
|
1331
|
+
}
|
|
1332
|
+
function buildModulepreloadTags(paths) {
|
|
1333
|
+
return paths.map((p) => `<link rel="modulepreload" href="${escapeAttr(p)}">`).join(`
|
|
1334
|
+
`);
|
|
1335
|
+
}
|
|
1336
|
+
function resolveRouteModulepreload(routeChunkManifest, matchedPatterns, fallback) {
|
|
1337
|
+
if (routeChunkManifest && matchedPatterns?.length) {
|
|
1338
|
+
const chunkPaths = new Set;
|
|
1339
|
+
for (const pattern of matchedPatterns) {
|
|
1340
|
+
const chunks = routeChunkManifest.routes[pattern];
|
|
1341
|
+
if (chunks) {
|
|
1342
|
+
for (const chunk of chunks) {
|
|
1343
|
+
chunkPaths.add(chunk);
|
|
1344
|
+
}
|
|
1345
|
+
}
|
|
1346
|
+
}
|
|
1347
|
+
if (chunkPaths.size > 0) {
|
|
1348
|
+
return buildModulepreloadTags([...chunkPaths]);
|
|
1349
|
+
}
|
|
1350
|
+
}
|
|
1351
|
+
return fallback;
|
|
1352
|
+
}
|
|
1353
|
+
function preprocessInlineCSS(template, inlineCSS) {
|
|
1354
|
+
let result = template;
|
|
1355
|
+
for (const [href, css] of Object.entries(inlineCSS)) {
|
|
1356
|
+
const escapedHref = href.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
1357
|
+
const linkPattern = new RegExp(`<link[^>]*href=["']${escapedHref}["'][^>]*>`);
|
|
1358
|
+
const safeCss = css.replace(/<\//g, "<\\/");
|
|
1359
|
+
result = result.replace(linkPattern, `<style data-vertz-css>${safeCss}</style>`);
|
|
1360
|
+
}
|
|
1361
|
+
return result;
|
|
1362
|
+
}
|
|
1363
|
+
function precomputeHandlerState(options) {
|
|
1364
|
+
const { module, inlineCSS, fallbackMetrics, modulepreload, progressiveHTML } = options;
|
|
1365
|
+
let template = options.template;
|
|
1366
|
+
if (inlineCSS) {
|
|
1367
|
+
template = preprocessInlineCSS(template, inlineCSS);
|
|
1368
|
+
}
|
|
1369
|
+
let linkHeader;
|
|
1370
|
+
if (module.theme) {
|
|
1371
|
+
const compiled = compileThemeCached(module.theme, fallbackMetrics);
|
|
1372
|
+
if (compiled.preloadItems.length > 0) {
|
|
1373
|
+
linkHeader = buildLinkHeader(compiled.preloadItems);
|
|
1374
|
+
}
|
|
1375
|
+
}
|
|
1376
|
+
const modulepreloadTags = modulepreload?.length ? buildModulepreloadTags(modulepreload) : undefined;
|
|
1377
|
+
const splitResult = progressiveHTML ? splitTemplate(template) : undefined;
|
|
1378
|
+
if (splitResult && module.theme) {
|
|
1379
|
+
splitResult.headTemplate = splitResult.headTemplate.replace(/<link\s+rel="stylesheet"\s+href="([^"]+)"[^>]*>/g, (match, href) => `<link rel="stylesheet" href="${href}" media="print" onload="this.media='all'">
|
|
1380
|
+
<noscript>${match}</noscript>`);
|
|
1381
|
+
}
|
|
1382
|
+
return { template, linkHeader, modulepreloadTags, splitResult };
|
|
1383
|
+
}
|
|
1384
|
+
async function resolveSession(request, sessionResolver, nonce) {
|
|
1385
|
+
let sessionScript = "";
|
|
1386
|
+
let ssrAuth;
|
|
1387
|
+
try {
|
|
1388
|
+
const sessionResult = await sessionResolver(request);
|
|
1389
|
+
if (sessionResult) {
|
|
1390
|
+
ssrAuth = {
|
|
1391
|
+
status: "authenticated",
|
|
1392
|
+
user: sessionResult.session.user,
|
|
1393
|
+
expiresAt: sessionResult.session.expiresAt
|
|
1394
|
+
};
|
|
1395
|
+
const scripts = [];
|
|
1396
|
+
scripts.push(createSessionScript(sessionResult.session, nonce));
|
|
1397
|
+
if (sessionResult.accessSet != null) {
|
|
1398
|
+
scripts.push(createAccessSetScript(sessionResult.accessSet, nonce));
|
|
1399
|
+
}
|
|
1400
|
+
sessionScript = scripts.join(`
|
|
1218
1401
|
`);
|
|
1402
|
+
} else {
|
|
1403
|
+
ssrAuth = { status: "unauthenticated" };
|
|
1404
|
+
}
|
|
1405
|
+
} catch (resolverErr) {
|
|
1406
|
+
console.warn("[Server] Session resolver failed:", resolverErr instanceof Error ? resolverErr.message : resolverErr);
|
|
1407
|
+
}
|
|
1408
|
+
return { sessionScript, ssrAuth };
|
|
1219
1409
|
}
|
|
1220
1410
|
|
|
1221
1411
|
// src/template-inject.ts
|
|
@@ -1281,4 +1471,4 @@ function replaceAppDivContent(template, appHtml) {
|
|
|
1281
1471
|
return template.slice(0, contentStart) + appHtml + template.slice(i);
|
|
1282
1472
|
}
|
|
1283
1473
|
|
|
1284
|
-
export { escapeHtml, escapeAttr, serializeToHtml,
|
|
1474
|
+
export { escapeHtml, escapeAttr, serializeToHtml, toPrefetchSession, evaluateAccessRule, reconstructDescriptors, resetSlotCounter, createSlotPlaceholder, encodeChunk, streamToString, collectStreamChunks, createTemplateChunk, renderToStream, safeSerialize, getStreamingRuntimeScript, createSSRDataChunk, compileThemeCached, createRequestContext, ssrRenderToString, ssrDiscoverQueries, ssrStreamNavQueries, matchUrlToPatterns, ssrRenderSinglePass, ssrRenderProgressive, createHoles, ssrRenderAot, isAotDebugEnabled, getAccessSetForSSR, createAccessSetScript, createSessionScript, resolveRouteModulepreload, precomputeHandlerState, resolveSession, injectIntoTemplate };
|