@vertz/ui-server 0.2.28 → 0.2.30

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.
@@ -1,764 +0,0 @@
1
- import {
2
- clearGlobalSSRTimeout,
3
- createSSRAdapter,
4
- getSSRQueries,
5
- installDomShim,
6
- setGlobalSSRTimeout,
7
- ssrStorage,
8
- toVNode
9
- } from "./chunk-gcwqkynf.js";
10
-
11
- // src/html-serializer.ts
12
- var VOID_ELEMENTS = new Set([
13
- "area",
14
- "base",
15
- "br",
16
- "col",
17
- "embed",
18
- "hr",
19
- "img",
20
- "input",
21
- "link",
22
- "meta",
23
- "param",
24
- "source",
25
- "track",
26
- "wbr"
27
- ]);
28
- var RAW_TEXT_ELEMENTS = new Set(["script", "style"]);
29
- function escapeHtml(text) {
30
- return text.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
31
- }
32
- function escapeAttr(value) {
33
- const str = typeof value === "string" ? value : String(value);
34
- return str.replace(/&/g, "&amp;").replace(/"/g, "&quot;");
35
- }
36
- function serializeAttrs(attrs) {
37
- const parts = [];
38
- for (const [key, value] of Object.entries(attrs)) {
39
- parts.push(` ${key}="${escapeAttr(value)}"`);
40
- }
41
- return parts.join("");
42
- }
43
- function isRawHtml(value) {
44
- return typeof value === "object" && "__raw" in value && value.__raw === true;
45
- }
46
- function serializeToHtml(node) {
47
- if (typeof node === "string") {
48
- return escapeHtml(node);
49
- }
50
- if (isRawHtml(node)) {
51
- return node.html;
52
- }
53
- const { tag, attrs, children } = node;
54
- if (tag === "fragment") {
55
- return children.map((child) => serializeToHtml(child)).join("");
56
- }
57
- const attrStr = serializeAttrs(attrs);
58
- if (VOID_ELEMENTS.has(tag)) {
59
- return `<${tag}${attrStr}>`;
60
- }
61
- const isRawText = RAW_TEXT_ELEMENTS.has(tag);
62
- const childrenHtml = children.map((child) => {
63
- if (typeof child === "string" && isRawText) {
64
- return child;
65
- }
66
- return serializeToHtml(child);
67
- }).join("");
68
- return `<${tag}${attrStr}>${childrenHtml}</${tag}>`;
69
- }
70
-
71
- // src/slot-placeholder.ts
72
- var slotCounter = 0;
73
- function resetSlotCounter() {
74
- slotCounter = 0;
75
- }
76
- function createSlotPlaceholder(fallback) {
77
- const id = slotCounter++;
78
- const placeholder = {
79
- tag: "div",
80
- attrs: { id: `v-slot-${id}` },
81
- children: typeof fallback === "string" ? [fallback] : [fallback],
82
- _slotId: id
83
- };
84
- return placeholder;
85
- }
86
-
87
- // src/streaming.ts
88
- var encoder = new TextEncoder;
89
- var decoder = new TextDecoder;
90
- function encodeChunk(html) {
91
- return encoder.encode(html);
92
- }
93
- async function streamToString(stream) {
94
- const reader = stream.getReader();
95
- const parts = [];
96
- for (;; ) {
97
- const { done, value } = await reader.read();
98
- if (done)
99
- break;
100
- parts.push(decoder.decode(value, { stream: true }));
101
- }
102
- parts.push(decoder.decode());
103
- return parts.join("");
104
- }
105
- async function collectStreamChunks(stream) {
106
- const reader = stream.getReader();
107
- const chunks = [];
108
- for (;; ) {
109
- const { done, value } = await reader.read();
110
- if (done)
111
- break;
112
- chunks.push(decoder.decode(value, { stream: true }));
113
- }
114
- return chunks;
115
- }
116
-
117
- // src/template-chunk.ts
118
- function createTemplateChunk(slotId, resolvedHtml, nonce) {
119
- const tmplId = `v-tmpl-${slotId}`;
120
- const slotRef = `v-slot-${slotId}`;
121
- const nonceAttr = nonce != null ? ` nonce="${escapeAttr(nonce)}"` : "";
122
- return `<template id="${tmplId}">${resolvedHtml}</template>` + `<script${nonceAttr}>` + `(function(){` + `var s=document.getElementById("${slotRef}");` + `var t=document.getElementById("${tmplId}");` + `if(s&&t){s.replaceWith(t.content.cloneNode(true));t.remove()}` + `})()` + "</script>";
123
- }
124
-
125
- // src/render-to-stream.ts
126
- function isSuspenseNode(node) {
127
- return typeof node === "object" && "tag" in node && node.tag === "__suspense" && "_resolve" in node;
128
- }
129
- function renderToStream(tree, options) {
130
- const pendingBoundaries = [];
131
- function walkAndSerialize(node) {
132
- if (typeof node === "string") {
133
- return escapeHtml(node);
134
- }
135
- if (isRawHtml(node)) {
136
- return node.html;
137
- }
138
- if (isSuspenseNode(node)) {
139
- const placeholder = createSlotPlaceholder(node._fallback);
140
- pendingBoundaries.push({
141
- slotId: placeholder._slotId,
142
- resolve: node._resolve
143
- });
144
- return serializeToHtml(placeholder);
145
- }
146
- const { tag, attrs, children } = node;
147
- if (tag === "fragment") {
148
- return children.map((child) => walkAndSerialize(child)).join("");
149
- }
150
- const isRawText = RAW_TEXT_ELEMENTS.has(tag);
151
- const attrStr = Object.entries(attrs).map(([k, v]) => ` ${k}="${escapeAttr(v)}"`).join("");
152
- if (VOID_ELEMENTS.has(tag)) {
153
- return `<${tag}${attrStr}>`;
154
- }
155
- const childrenHtml = children.map((child) => {
156
- if (typeof child === "string" && isRawText) {
157
- return child;
158
- }
159
- return walkAndSerialize(child);
160
- }).join("");
161
- return `<${tag}${attrStr}>${childrenHtml}</${tag}>`;
162
- }
163
- return new ReadableStream({
164
- async start(controller) {
165
- const mainHtml = walkAndSerialize(tree);
166
- controller.enqueue(encodeChunk(mainHtml));
167
- if (pendingBoundaries.length > 0) {
168
- const nonce = options?.nonce;
169
- const resolutions = pendingBoundaries.map(async (boundary) => {
170
- try {
171
- const resolved = await boundary.resolve;
172
- const resolvedHtml = serializeToHtml(resolved);
173
- return createTemplateChunk(boundary.slotId, resolvedHtml, nonce);
174
- } catch (_err) {
175
- const errorHtml = `<div data-v-ssr-error="true" id="v-ssr-error-${boundary.slotId}">` + "<!--SSR error--></div>";
176
- return createTemplateChunk(boundary.slotId, errorHtml, nonce);
177
- }
178
- });
179
- const chunks = await Promise.all(resolutions);
180
- for (const chunk of chunks) {
181
- controller.enqueue(encodeChunk(chunk));
182
- }
183
- }
184
- controller.close();
185
- }
186
- });
187
- }
188
-
189
- // src/ssr-streaming-runtime.ts
190
- function safeSerialize(data) {
191
- return JSON.stringify(data).replace(/</g, "\\u003c");
192
- }
193
- function getStreamingRuntimeScript(nonce) {
194
- const nonceAttr = nonce != null ? ` nonce="${escapeAttr(nonce)}"` : "";
195
- return `<script${nonceAttr}>` + "window.__VERTZ_SSR_DATA__=[];" + "window.__VERTZ_SSR_PUSH__=function(k,d){" + "window.__VERTZ_SSR_DATA__.push({key:k,data:d});" + 'document.dispatchEvent(new CustomEvent("vertz:ssr-data",{detail:{key:k,data:d}}))' + "};" + "</script>";
196
- }
197
- function createSSRDataChunk(key, data, nonce) {
198
- const nonceAttr = nonce != null ? ` nonce="${escapeAttr(nonce)}"` : "";
199
- const serialized = safeSerialize(data);
200
- return `<script${nonceAttr}>window.__VERTZ_SSR_PUSH__(${safeSerialize(key)},${serialized})</script>`;
201
- }
202
-
203
- // src/ssr-render.ts
204
- import { compileTheme } from "@vertz/ui";
205
- import { EntityStore, MemoryCache, QueryEnvelopeStore } from "@vertz/ui/internals";
206
- function createRequestContext(url) {
207
- return {
208
- url,
209
- adapter: createSSRAdapter(),
210
- subscriber: null,
211
- readValueCb: null,
212
- cleanupStack: [],
213
- batchDepth: 0,
214
- pendingEffects: new Map,
215
- contextScope: null,
216
- entityStore: new EntityStore,
217
- envelopeStore: new QueryEnvelopeStore,
218
- queryCache: new MemoryCache({ maxSize: Infinity }),
219
- inflight: new Map,
220
- queries: [],
221
- errors: []
222
- };
223
- }
224
- var domShimInstalled = false;
225
- function ensureDomShim() {
226
- if (domShimInstalled && typeof document !== "undefined")
227
- return;
228
- domShimInstalled = true;
229
- installDomShim();
230
- }
231
- function resolveAppFactory(module) {
232
- const createApp = module.default || module.App;
233
- if (typeof createApp !== "function") {
234
- throw new Error("App entry must export a default function or named App function");
235
- }
236
- return createApp;
237
- }
238
- function collectCSS(themeCss, module) {
239
- const alreadyIncluded = new Set;
240
- if (themeCss)
241
- alreadyIncluded.add(themeCss);
242
- if (module.styles) {
243
- for (const s of module.styles)
244
- alreadyIncluded.add(s);
245
- }
246
- const componentCss = module.getInjectedCSS ? module.getInjectedCSS().filter((s) => !alreadyIncluded.has(s)) : [];
247
- const themeTag = themeCss ? `<style data-vertz-css>${themeCss}</style>` : "";
248
- const globalTag = module.styles && module.styles.length > 0 ? `<style data-vertz-css>${module.styles.join(`
249
- `)}</style>` : "";
250
- const componentTag = componentCss.length > 0 ? `<style data-vertz-css>${componentCss.join(`
251
- `)}</style>` : "";
252
- return [themeTag, globalTag, componentTag].filter(Boolean).join(`
253
- `);
254
- }
255
- async function ssrRenderToString(module, url, options) {
256
- const normalizedUrl = url.endsWith("/index.html") ? url.slice(0, -"/index.html".length) || "/" : url;
257
- const ssrTimeout = options?.ssrTimeout ?? 300;
258
- ensureDomShim();
259
- const ctx = createRequestContext(normalizedUrl);
260
- if (options?.ssrAuth) {
261
- ctx.ssrAuth = options.ssrAuth;
262
- }
263
- return ssrStorage.run(ctx, async () => {
264
- try {
265
- setGlobalSSRTimeout(ssrTimeout);
266
- const createApp = resolveAppFactory(module);
267
- let themeCss = "";
268
- let themePreloadTags = "";
269
- if (module.theme) {
270
- try {
271
- const compiled = compileTheme(module.theme, {
272
- fallbackMetrics: options?.fallbackMetrics
273
- });
274
- themeCss = compiled.css;
275
- themePreloadTags = compiled.preloadTags;
276
- } catch (e) {
277
- console.error("[vertz] Failed to compile theme export. Ensure your theme is created with defineTheme().", e);
278
- }
279
- }
280
- createApp();
281
- if (ctx.ssrRedirect) {
282
- return {
283
- html: "",
284
- css: "",
285
- ssrData: [],
286
- headTags: "",
287
- redirect: ctx.ssrRedirect
288
- };
289
- }
290
- const store = ssrStorage.getStore();
291
- if (store) {
292
- if (store.pendingRouteComponents?.size) {
293
- const entries = Array.from(store.pendingRouteComponents.entries());
294
- const results = await Promise.allSettled(entries.map(([route, promise]) => Promise.race([
295
- promise.then((mod) => ({ route, factory: mod.default })),
296
- new Promise((_, reject) => setTimeout(() => reject(new Error("lazy route timeout")), ssrTimeout))
297
- ])));
298
- store.resolvedComponents = new Map;
299
- for (const result of results) {
300
- if (result.status === "fulfilled") {
301
- const { route, factory } = result.value;
302
- store.resolvedComponents.set(route, factory);
303
- }
304
- }
305
- store.pendingRouteComponents = undefined;
306
- }
307
- if (!store.resolvedComponents) {
308
- store.resolvedComponents = new Map;
309
- }
310
- }
311
- const queries = getSSRQueries();
312
- const resolvedQueries = [];
313
- if (queries.length > 0) {
314
- await Promise.allSettled(queries.map(({ promise, timeout, resolve, key }) => Promise.race([
315
- promise.then((data) => {
316
- resolve(data);
317
- resolvedQueries.push({ key, data });
318
- return "resolved";
319
- }),
320
- new Promise((r) => setTimeout(r, timeout || ssrTimeout)).then(() => "timeout")
321
- ])));
322
- if (store)
323
- store.queries = [];
324
- }
325
- const app = createApp();
326
- const vnode = toVNode(app);
327
- const stream = renderToStream(vnode);
328
- const html = await streamToString(stream);
329
- const css = collectCSS(themeCss, module);
330
- const ssrData = resolvedQueries.length > 0 ? resolvedQueries.map(({ key, data }) => ({
331
- key,
332
- data: JSON.parse(JSON.stringify(data))
333
- })) : [];
334
- return {
335
- html,
336
- css,
337
- ssrData,
338
- headTags: themePreloadTags,
339
- discoveredRoutes: ctx.discoveredRoutes,
340
- matchedRoutePatterns: ctx.matchedRoutePatterns
341
- };
342
- } finally {
343
- clearGlobalSSRTimeout();
344
- }
345
- });
346
- }
347
- async function ssrDiscoverQueries(module, url, options) {
348
- const normalizedUrl = url.endsWith("/index.html") ? url.slice(0, -"/index.html".length) || "/" : url;
349
- const ssrTimeout = options?.ssrTimeout ?? 300;
350
- ensureDomShim();
351
- const ctx = createRequestContext(normalizedUrl);
352
- return ssrStorage.run(ctx, async () => {
353
- try {
354
- setGlobalSSRTimeout(ssrTimeout);
355
- const createApp = resolveAppFactory(module);
356
- createApp();
357
- const queries = getSSRQueries();
358
- const resolvedQueries = [];
359
- const pendingKeys = [];
360
- if (queries.length > 0) {
361
- await Promise.allSettled(queries.map(({ promise, timeout, resolve, key }) => {
362
- let settled = false;
363
- return Promise.race([
364
- promise.then((data) => {
365
- if (settled)
366
- return "late";
367
- settled = true;
368
- resolve(data);
369
- resolvedQueries.push({ key, data });
370
- return "resolved";
371
- }),
372
- new Promise((r) => setTimeout(r, timeout || ssrTimeout)).then(() => {
373
- if (settled)
374
- return "already-resolved";
375
- settled = true;
376
- pendingKeys.push(key);
377
- return "timeout";
378
- })
379
- ]);
380
- }));
381
- }
382
- return {
383
- resolved: resolvedQueries.map(({ key, data }) => ({
384
- key,
385
- data: JSON.parse(JSON.stringify(data))
386
- })),
387
- pending: pendingKeys
388
- };
389
- } finally {
390
- clearGlobalSSRTimeout();
391
- }
392
- });
393
- }
394
- async function ssrStreamNavQueries(module, url, options) {
395
- const normalizedUrl = url.endsWith("/index.html") ? url.slice(0, -"/index.html".length) || "/" : url;
396
- const ssrTimeout = options?.ssrTimeout ?? 300;
397
- const navTimeout = options?.navSsrTimeout ?? 5000;
398
- ensureDomShim();
399
- const ctx = createRequestContext(normalizedUrl);
400
- const queries = await ssrStorage.run(ctx, async () => {
401
- try {
402
- setGlobalSSRTimeout(ssrTimeout);
403
- const createApp = resolveAppFactory(module);
404
- createApp();
405
- const discovered = getSSRQueries();
406
- return discovered.map((q) => ({
407
- promise: q.promise,
408
- timeout: q.timeout || ssrTimeout,
409
- resolve: q.resolve,
410
- key: q.key
411
- }));
412
- } finally {
413
- clearGlobalSSRTimeout();
414
- }
415
- });
416
- if (queries.length === 0) {
417
- const encoder3 = new TextEncoder;
418
- return new ReadableStream({
419
- start(controller) {
420
- controller.enqueue(encoder3.encode(`event: done
421
- data: {}
422
-
423
- `));
424
- controller.close();
425
- }
426
- });
427
- }
428
- const encoder2 = new TextEncoder;
429
- let remaining = queries.length;
430
- return new ReadableStream({
431
- start(controller) {
432
- let closed = false;
433
- function safeEnqueue(chunk) {
434
- if (closed)
435
- return;
436
- try {
437
- controller.enqueue(chunk);
438
- } catch {
439
- closed = true;
440
- }
441
- }
442
- function safeClose() {
443
- if (closed)
444
- return;
445
- closed = true;
446
- try {
447
- controller.close();
448
- } catch {}
449
- }
450
- function checkDone() {
451
- if (remaining === 0) {
452
- safeEnqueue(encoder2.encode(`event: done
453
- data: {}
454
-
455
- `));
456
- safeClose();
457
- }
458
- }
459
- for (const { promise, resolve, key } of queries) {
460
- let settled = false;
461
- promise.then((data) => {
462
- if (settled)
463
- return;
464
- settled = true;
465
- resolve(data);
466
- const entry = { key, data: JSON.parse(JSON.stringify(data)) };
467
- safeEnqueue(encoder2.encode(`event: data
468
- data: ${safeSerialize(entry)}
469
-
470
- `));
471
- remaining--;
472
- checkDone();
473
- }, () => {
474
- if (settled)
475
- return;
476
- settled = true;
477
- remaining--;
478
- checkDone();
479
- });
480
- setTimeout(() => {
481
- if (settled)
482
- return;
483
- settled = true;
484
- remaining--;
485
- checkDone();
486
- }, navTimeout);
487
- }
488
- }
489
- });
490
- }
491
-
492
- // src/ssr-access-set.ts
493
- function getAccessSetForSSR(jwtPayload) {
494
- if (!jwtPayload)
495
- return null;
496
- const acl = jwtPayload.acl;
497
- if (!acl)
498
- return null;
499
- if (acl.overflow)
500
- return null;
501
- if (!acl.set)
502
- return null;
503
- return {
504
- entitlements: Object.fromEntries(Object.entries(acl.set.entitlements).map(([name, check]) => [
505
- name,
506
- {
507
- allowed: check.allowed,
508
- reasons: check.reasons ?? [],
509
- ...check.reason ? { reason: check.reason } : {},
510
- ...check.meta ? { meta: check.meta } : {}
511
- }
512
- ])),
513
- flags: acl.set.flags,
514
- plan: acl.set.plan,
515
- computedAt: acl.set.computedAt
516
- };
517
- }
518
- function createAccessSetScript(accessSet, nonce) {
519
- const json = JSON.stringify(accessSet);
520
- const escaped = json.replace(/</g, "\\u003c").replace(/\u2028/g, "\\u2028").replace(/\u2029/g, "\\u2029");
521
- const nonceAttr = nonce ? ` nonce="${escapeAttr2(nonce)}"` : "";
522
- return `<script${nonceAttr}>window.__VERTZ_ACCESS_SET__=${escaped}</script>`;
523
- }
524
- function escapeAttr2(s) {
525
- return s.replace(/[&"'<>]/g, (c) => `&#${c.charCodeAt(0)};`);
526
- }
527
-
528
- // src/ssr-session.ts
529
- function createSessionScript(session, nonce) {
530
- const json = JSON.stringify(session);
531
- const escaped = json.replace(/</g, "\\u003c").replace(/\u2028/g, "\\u2028").replace(/\u2029/g, "\\u2029");
532
- const nonceAttr = nonce ? ` nonce="${escapeAttr3(nonce)}"` : "";
533
- return `<script${nonceAttr}>window.__VERTZ_SESSION__=${escaped}</script>`;
534
- }
535
- function escapeAttr3(s) {
536
- return s.replace(/[&"'<>]/g, (c) => `&#${c.charCodeAt(0)};`);
537
- }
538
-
539
- // src/template-inject.ts
540
- function injectIntoTemplate(options) {
541
- const { template, appHtml, appCss, ssrData, nonce, headTags, sessionScript } = options;
542
- let html;
543
- if (template.includes("<!--ssr-outlet-->")) {
544
- html = template.replace("<!--ssr-outlet-->", appHtml);
545
- } else {
546
- html = replaceAppDivContent(template, appHtml);
547
- }
548
- if (headTags) {
549
- html = html.replace("</head>", `${headTags}
550
- </head>`);
551
- }
552
- if (appCss) {
553
- html = html.replace(/<link\s+rel="stylesheet"\s+href="([^"]+)"[^>]*>/g, (match, href) => `<link rel="stylesheet" href="${href}" media="print" onload="this.media='all'">
554
- <noscript>${match}</noscript>`);
555
- html = html.replace("</head>", `${appCss}
556
- </head>`);
557
- }
558
- if (sessionScript) {
559
- html = html.replace("</body>", `${sessionScript}
560
- </body>`);
561
- }
562
- if (ssrData.length > 0) {
563
- const nonceAttr = nonce != null ? ` nonce="${nonce}"` : "";
564
- const ssrDataScript = `<script${nonceAttr}>window.__VERTZ_SSR_DATA__=${safeSerialize(ssrData)};</script>`;
565
- html = html.replace("</body>", `${ssrDataScript}
566
- </body>`);
567
- }
568
- return html;
569
- }
570
- function replaceAppDivContent(template, appHtml) {
571
- const openMatch = template.match(/<div[^>]*id="app"[^>]*>/);
572
- if (!openMatch || openMatch.index == null)
573
- return template;
574
- const openTag = openMatch[0];
575
- const contentStart = openMatch.index + openTag.length;
576
- let depth = 1;
577
- let i = contentStart;
578
- const len = template.length;
579
- while (i < len && depth > 0) {
580
- if (template[i] === "<") {
581
- if (template.startsWith("</div>", i)) {
582
- depth--;
583
- if (depth === 0)
584
- break;
585
- i += 6;
586
- } else if (template.startsWith("<div", i) && /^<div[\s>]/.test(template.slice(i, i + 5))) {
587
- depth++;
588
- i += 4;
589
- } else {
590
- i++;
591
- }
592
- } else {
593
- i++;
594
- }
595
- }
596
- if (depth !== 0) {
597
- return template;
598
- }
599
- return template.slice(0, contentStart) + appHtml + template.slice(i);
600
- }
601
-
602
- // src/ssr-handler.ts
603
- import { compileTheme as compileTheme2 } from "@vertz/ui";
604
- function sanitizeLinkHref(href) {
605
- return href.replace(/[<>,;\s"']/g, (ch) => `%${ch.charCodeAt(0).toString(16).toUpperCase()}`);
606
- }
607
- function sanitizeLinkParam(value) {
608
- return value.replace(/[^a-zA-Z0-9/_.-]/g, "");
609
- }
610
- function buildLinkHeader(items) {
611
- return items.map((item) => {
612
- const parts = [
613
- `<${sanitizeLinkHref(item.href)}>`,
614
- "rel=preload",
615
- `as=${sanitizeLinkParam(item.as)}`
616
- ];
617
- if (item.type)
618
- parts.push(`type=${sanitizeLinkParam(item.type)}`);
619
- if (item.crossorigin)
620
- parts.push("crossorigin");
621
- return parts.join("; ");
622
- }).join(", ");
623
- }
624
- function buildModulepreloadTags(paths) {
625
- return paths.map((p) => `<link rel="modulepreload" href="${escapeAttr(p)}">`).join(`
626
- `);
627
- }
628
- function createSSRHandler(options) {
629
- const {
630
- module,
631
- ssrTimeout,
632
- inlineCSS,
633
- nonce,
634
- fallbackMetrics,
635
- modulepreload,
636
- routeChunkManifest,
637
- cacheControl,
638
- sessionResolver
639
- } = options;
640
- let template = options.template;
641
- if (inlineCSS) {
642
- for (const [href, css] of Object.entries(inlineCSS)) {
643
- const escapedHref = href.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
644
- const linkPattern = new RegExp(`<link[^>]*href=["']${escapedHref}["'][^>]*>`);
645
- const safeCss = css.replace(/<\//g, "<\\/");
646
- template = template.replace(linkPattern, `<style data-vertz-css>${safeCss}</style>`);
647
- }
648
- }
649
- let linkHeader;
650
- if (module.theme) {
651
- const compiled = compileTheme2(module.theme, { fallbackMetrics });
652
- if (compiled.preloadItems.length > 0) {
653
- linkHeader = buildLinkHeader(compiled.preloadItems);
654
- }
655
- }
656
- const modulepreloadTags = modulepreload?.length ? buildModulepreloadTags(modulepreload) : undefined;
657
- return async (request) => {
658
- const url = new URL(request.url);
659
- const pathname = url.pathname;
660
- if (request.headers.get("x-vertz-nav") === "1") {
661
- return handleNavRequest(module, pathname, ssrTimeout);
662
- }
663
- let sessionScript = "";
664
- let ssrAuth;
665
- if (sessionResolver) {
666
- try {
667
- const sessionResult = await sessionResolver(request);
668
- if (sessionResult) {
669
- ssrAuth = {
670
- status: "authenticated",
671
- user: sessionResult.session.user,
672
- expiresAt: sessionResult.session.expiresAt
673
- };
674
- const scripts = [];
675
- scripts.push(createSessionScript(sessionResult.session, nonce));
676
- if (sessionResult.accessSet != null) {
677
- scripts.push(createAccessSetScript(sessionResult.accessSet, nonce));
678
- }
679
- sessionScript = scripts.join(`
680
- `);
681
- } else {
682
- ssrAuth = { status: "unauthenticated" };
683
- }
684
- } catch (resolverErr) {
685
- console.warn("[Server] Session resolver failed:", resolverErr instanceof Error ? resolverErr.message : resolverErr);
686
- }
687
- }
688
- return handleHTMLRequest(module, template, pathname + url.search, ssrTimeout, nonce, fallbackMetrics, linkHeader, modulepreloadTags, routeChunkManifest, cacheControl, sessionScript, ssrAuth);
689
- };
690
- }
691
- async function handleNavRequest(module, url, ssrTimeout) {
692
- try {
693
- const stream = await ssrStreamNavQueries(module, url, { ssrTimeout });
694
- return new Response(stream, {
695
- status: 200,
696
- headers: {
697
- "Content-Type": "text/event-stream",
698
- "Cache-Control": "no-cache"
699
- }
700
- });
701
- } catch {
702
- return new Response(`event: done
703
- data: {}
704
-
705
- `, {
706
- status: 200,
707
- headers: {
708
- "Content-Type": "text/event-stream",
709
- "Cache-Control": "no-cache"
710
- }
711
- });
712
- }
713
- }
714
- async function handleHTMLRequest(module, template, url, ssrTimeout, nonce, fallbackMetrics, linkHeader, staticModulepreloadTags, routeChunkManifest, cacheControl, sessionScript, ssrAuth) {
715
- try {
716
- const result = await ssrRenderToString(module, url, { ssrTimeout, fallbackMetrics, ssrAuth });
717
- if (result.redirect) {
718
- return new Response(null, {
719
- status: 302,
720
- headers: { Location: result.redirect.to }
721
- });
722
- }
723
- let modulepreloadTags = staticModulepreloadTags;
724
- if (routeChunkManifest && result.matchedRoutePatterns?.length) {
725
- const chunkPaths = new Set;
726
- for (const pattern of result.matchedRoutePatterns) {
727
- const chunks = routeChunkManifest.routes[pattern];
728
- if (chunks) {
729
- for (const chunk of chunks) {
730
- chunkPaths.add(chunk);
731
- }
732
- }
733
- }
734
- if (chunkPaths.size > 0) {
735
- modulepreloadTags = buildModulepreloadTags([...chunkPaths]);
736
- }
737
- }
738
- const allHeadTags = [result.headTags, modulepreloadTags].filter(Boolean).join(`
739
- `);
740
- const html = injectIntoTemplate({
741
- template,
742
- appHtml: result.html,
743
- appCss: result.css,
744
- ssrData: result.ssrData,
745
- nonce,
746
- headTags: allHeadTags || undefined,
747
- sessionScript
748
- });
749
- const headers = { "Content-Type": "text/html; charset=utf-8" };
750
- if (linkHeader)
751
- headers.Link = linkHeader;
752
- if (cacheControl)
753
- headers["Cache-Control"] = cacheControl;
754
- return new Response(html, { status: 200, headers });
755
- } catch (err) {
756
- console.error("[SSR] Render failed:", err instanceof Error ? err.message : err);
757
- return new Response("Internal Server Error", {
758
- status: 500,
759
- headers: { "Content-Type": "text/plain" }
760
- });
761
- }
762
- }
763
-
764
- export { escapeHtml, escapeAttr, serializeToHtml, resetSlotCounter, createSlotPlaceholder, encodeChunk, streamToString, collectStreamChunks, createTemplateChunk, renderToStream, safeSerialize, getStreamingRuntimeScript, createSSRDataChunk, createRequestContext, ssrRenderToString, ssrDiscoverQueries, getAccessSetForSSR, createAccessSetScript, createSessionScript, injectIntoTemplate, createSSRHandler };