@vertz/ui-server 0.2.24 → 0.2.25
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 +650 -3
- package/dist/bun-plugin/index.d.ts +1 -1
- package/dist/dom-shim/index.d.ts +4 -0
- package/dist/dom-shim/index.js +1 -1
- package/dist/index.d.ts +186 -2
- package/dist/index.js +583 -3
- package/dist/shared/{chunk-zs75v8qj.js → chunk-gcwqkynf.js} +4 -0
- package/dist/shared/{chunk-g0zqrb60.js → chunk-yr65qdge.js} +1 -1
- package/dist/ssr/index.d.ts +2 -0
- package/dist/ssr/index.js +2 -2
- package/package.json +5 -5
package/dist/index.js
CHANGED
|
@@ -19,19 +19,21 @@ import {
|
|
|
19
19
|
ssrDiscoverQueries,
|
|
20
20
|
ssrRenderToString,
|
|
21
21
|
streamToString
|
|
22
|
-
} from "./shared/chunk-
|
|
22
|
+
} from "./shared/chunk-yr65qdge.js";
|
|
23
23
|
import {
|
|
24
24
|
clearGlobalSSRTimeout,
|
|
25
25
|
createSSRAdapter,
|
|
26
26
|
getGlobalSSRTimeout,
|
|
27
27
|
getSSRQueries,
|
|
28
28
|
getSSRUrl,
|
|
29
|
+
installDomShim,
|
|
29
30
|
isInSSR,
|
|
30
31
|
rawHtml,
|
|
31
32
|
registerSSRQuery,
|
|
32
33
|
setGlobalSSRTimeout,
|
|
33
|
-
ssrStorage
|
|
34
|
-
|
|
34
|
+
ssrStorage,
|
|
35
|
+
toVNode
|
|
36
|
+
} from "./shared/chunk-gcwqkynf.js";
|
|
35
37
|
|
|
36
38
|
// src/asset-pipeline.ts
|
|
37
39
|
function renderAssetTags(assets) {
|
|
@@ -425,6 +427,46 @@ async function renderToHTML(appOrOptions, maybeOptions) {
|
|
|
425
427
|
}
|
|
426
428
|
});
|
|
427
429
|
}
|
|
430
|
+
// src/ssr-access-evaluator.ts
|
|
431
|
+
function toPrefetchSession(ssrAuth) {
|
|
432
|
+
if (!ssrAuth || ssrAuth.status !== "authenticated" || !ssrAuth.user) {
|
|
433
|
+
return { status: "unauthenticated" };
|
|
434
|
+
}
|
|
435
|
+
const roles = ssrAuth.user.role ? [ssrAuth.user.role] : undefined;
|
|
436
|
+
return {
|
|
437
|
+
status: "authenticated",
|
|
438
|
+
roles,
|
|
439
|
+
tenantId: ssrAuth.user.tenantId
|
|
440
|
+
};
|
|
441
|
+
}
|
|
442
|
+
function evaluateAccessRule(rule, session) {
|
|
443
|
+
switch (rule.type) {
|
|
444
|
+
case "public":
|
|
445
|
+
return true;
|
|
446
|
+
case "authenticated":
|
|
447
|
+
return session.status === "authenticated";
|
|
448
|
+
case "role":
|
|
449
|
+
if (session.status !== "authenticated")
|
|
450
|
+
return false;
|
|
451
|
+
return session.roles?.some((r) => rule.roles.includes(r)) === true;
|
|
452
|
+
case "entitlement":
|
|
453
|
+
if (session.status !== "authenticated")
|
|
454
|
+
return false;
|
|
455
|
+
return session.entitlements?.[rule.value] === true;
|
|
456
|
+
case "where":
|
|
457
|
+
return true;
|
|
458
|
+
case "fva":
|
|
459
|
+
return session.status === "authenticated";
|
|
460
|
+
case "deny":
|
|
461
|
+
return false;
|
|
462
|
+
case "all":
|
|
463
|
+
return rule.rules.every((r) => evaluateAccessRule(r, session));
|
|
464
|
+
case "any":
|
|
465
|
+
return rule.rules.some((r) => evaluateAccessRule(r, session));
|
|
466
|
+
default:
|
|
467
|
+
return false;
|
|
468
|
+
}
|
|
469
|
+
}
|
|
428
470
|
// src/ssr-html.ts
|
|
429
471
|
function generateSSRHtml(options) {
|
|
430
472
|
const {
|
|
@@ -457,11 +499,545 @@ function generateSSRHtml(options) {
|
|
|
457
499
|
</body>
|
|
458
500
|
</html>`;
|
|
459
501
|
}
|
|
502
|
+
// src/ssr-prefetch-dev.ts
|
|
503
|
+
import {
|
|
504
|
+
analyzeComponentQueries,
|
|
505
|
+
generatePrefetchManifest
|
|
506
|
+
} from "@vertz/ui-compiler";
|
|
507
|
+
function createPrefetchManifestManager(options) {
|
|
508
|
+
const { routerPath, readFile: readFile2, resolveImport } = options;
|
|
509
|
+
let currentManifest = null;
|
|
510
|
+
let currentSSRManifest;
|
|
511
|
+
let rebuildCount = 0;
|
|
512
|
+
let lastRebuildMs = null;
|
|
513
|
+
let lastRebuildAt = null;
|
|
514
|
+
let fileToRouteIndices = new Map;
|
|
515
|
+
function buildFileIndex(routes) {
|
|
516
|
+
const index = new Map;
|
|
517
|
+
for (let i = 0;i < routes.length; i++) {
|
|
518
|
+
const file = routes[i]?.file;
|
|
519
|
+
if (file) {
|
|
520
|
+
const existing = index.get(file) ?? [];
|
|
521
|
+
existing.push(i);
|
|
522
|
+
index.set(file, existing);
|
|
523
|
+
}
|
|
524
|
+
}
|
|
525
|
+
return index;
|
|
526
|
+
}
|
|
527
|
+
function toSSRManifest(manifest) {
|
|
528
|
+
const routeEntries = {};
|
|
529
|
+
for (const route of manifest.routes) {
|
|
530
|
+
const existing = routeEntries[route.pattern];
|
|
531
|
+
if (existing) {
|
|
532
|
+
existing.queries.push(...route.queries);
|
|
533
|
+
} else {
|
|
534
|
+
routeEntries[route.pattern] = { queries: [...route.queries] };
|
|
535
|
+
}
|
|
536
|
+
}
|
|
537
|
+
return {
|
|
538
|
+
routePatterns: [...new Set(manifest.routes.map((r) => r.pattern))],
|
|
539
|
+
routeEntries
|
|
540
|
+
};
|
|
541
|
+
}
|
|
542
|
+
function fullBuild(routerSourceOverride) {
|
|
543
|
+
const start = performance.now();
|
|
544
|
+
const routerSource = routerSourceOverride ?? readFile2(routerPath);
|
|
545
|
+
if (!routerSource) {
|
|
546
|
+
return;
|
|
547
|
+
}
|
|
548
|
+
try {
|
|
549
|
+
const manifest = generatePrefetchManifest({
|
|
550
|
+
routerSource,
|
|
551
|
+
routerPath,
|
|
552
|
+
readFile: readFile2,
|
|
553
|
+
resolveImport
|
|
554
|
+
});
|
|
555
|
+
currentManifest = manifest;
|
|
556
|
+
currentSSRManifest = toSSRManifest(manifest);
|
|
557
|
+
fileToRouteIndices = buildFileIndex(manifest.routes);
|
|
558
|
+
rebuildCount++;
|
|
559
|
+
lastRebuildMs = Math.round(performance.now() - start);
|
|
560
|
+
lastRebuildAt = new Date().toISOString();
|
|
561
|
+
} catch {}
|
|
562
|
+
}
|
|
563
|
+
function incrementalUpdate(filePath, sourceText) {
|
|
564
|
+
if (!currentManifest)
|
|
565
|
+
return;
|
|
566
|
+
const indices = fileToRouteIndices.get(filePath);
|
|
567
|
+
if (!indices || indices.length === 0)
|
|
568
|
+
return;
|
|
569
|
+
const start = performance.now();
|
|
570
|
+
try {
|
|
571
|
+
const analysis = analyzeComponentQueries(sourceText, filePath);
|
|
572
|
+
const newRoutes = [...currentManifest.routes];
|
|
573
|
+
for (const idx of indices) {
|
|
574
|
+
const existing = newRoutes[idx];
|
|
575
|
+
if (existing) {
|
|
576
|
+
newRoutes[idx] = {
|
|
577
|
+
...existing,
|
|
578
|
+
queries: analysis.queries,
|
|
579
|
+
params: analysis.params
|
|
580
|
+
};
|
|
581
|
+
}
|
|
582
|
+
}
|
|
583
|
+
const newManifest = {
|
|
584
|
+
...currentManifest,
|
|
585
|
+
routes: newRoutes,
|
|
586
|
+
generatedAt: new Date().toISOString()
|
|
587
|
+
};
|
|
588
|
+
currentManifest = newManifest;
|
|
589
|
+
currentSSRManifest = toSSRManifest(newManifest);
|
|
590
|
+
rebuildCount++;
|
|
591
|
+
lastRebuildMs = Math.round(performance.now() - start);
|
|
592
|
+
lastRebuildAt = new Date().toISOString();
|
|
593
|
+
} catch {}
|
|
594
|
+
}
|
|
595
|
+
return {
|
|
596
|
+
build() {
|
|
597
|
+
fullBuild();
|
|
598
|
+
},
|
|
599
|
+
onFileChange(filePath, sourceText) {
|
|
600
|
+
if (filePath === routerPath) {
|
|
601
|
+
fullBuild(sourceText);
|
|
602
|
+
} else {
|
|
603
|
+
incrementalUpdate(filePath, sourceText);
|
|
604
|
+
}
|
|
605
|
+
},
|
|
606
|
+
getSSRManifest() {
|
|
607
|
+
return currentSSRManifest;
|
|
608
|
+
},
|
|
609
|
+
getSnapshot() {
|
|
610
|
+
return {
|
|
611
|
+
manifest: currentManifest,
|
|
612
|
+
rebuildCount,
|
|
613
|
+
lastRebuildMs,
|
|
614
|
+
lastRebuildAt
|
|
615
|
+
};
|
|
616
|
+
}
|
|
617
|
+
};
|
|
618
|
+
}
|
|
619
|
+
// src/ssr-manifest-prefetch.ts
|
|
620
|
+
function reconstructDescriptors(queries, routeParams, apiClient) {
|
|
621
|
+
if (!apiClient)
|
|
622
|
+
return [];
|
|
623
|
+
const result = [];
|
|
624
|
+
for (const query of queries) {
|
|
625
|
+
const descriptor = reconstructSingle(query, routeParams, apiClient);
|
|
626
|
+
if (descriptor) {
|
|
627
|
+
result.push(descriptor);
|
|
628
|
+
}
|
|
629
|
+
}
|
|
630
|
+
return result;
|
|
631
|
+
}
|
|
632
|
+
function reconstructSingle(query, routeParams, apiClient) {
|
|
633
|
+
const { entity, operation } = query;
|
|
634
|
+
if (!entity || !operation)
|
|
635
|
+
return;
|
|
636
|
+
const entitySdk = apiClient[entity];
|
|
637
|
+
if (!entitySdk)
|
|
638
|
+
return;
|
|
639
|
+
const method = entitySdk[operation];
|
|
640
|
+
if (typeof method !== "function")
|
|
641
|
+
return;
|
|
642
|
+
const args = buildFactoryArgs(query, routeParams);
|
|
643
|
+
if (args === undefined)
|
|
644
|
+
return;
|
|
645
|
+
try {
|
|
646
|
+
const descriptor = method(...args);
|
|
647
|
+
if (!descriptor || typeof descriptor._key !== "string" || typeof descriptor._fetch !== "function") {
|
|
648
|
+
return;
|
|
649
|
+
}
|
|
650
|
+
return { key: descriptor._key, fetch: descriptor._fetch };
|
|
651
|
+
} catch {
|
|
652
|
+
return;
|
|
653
|
+
}
|
|
654
|
+
}
|
|
655
|
+
function buildFactoryArgs(query, routeParams) {
|
|
656
|
+
const { operation, idParam, queryBindings } = query;
|
|
657
|
+
if (operation === "get") {
|
|
658
|
+
if (idParam) {
|
|
659
|
+
const id = routeParams[idParam];
|
|
660
|
+
if (!id)
|
|
661
|
+
return;
|
|
662
|
+
const options = resolveQueryBindings(queryBindings, routeParams);
|
|
663
|
+
if (options === undefined && queryBindings)
|
|
664
|
+
return;
|
|
665
|
+
return options ? [id, options] : [id];
|
|
666
|
+
}
|
|
667
|
+
return;
|
|
668
|
+
}
|
|
669
|
+
if (!queryBindings)
|
|
670
|
+
return [];
|
|
671
|
+
const resolved = resolveQueryBindings(queryBindings, routeParams);
|
|
672
|
+
if (resolved === undefined)
|
|
673
|
+
return;
|
|
674
|
+
return [resolved];
|
|
675
|
+
}
|
|
676
|
+
function resolveQueryBindings(bindings, routeParams) {
|
|
677
|
+
if (!bindings)
|
|
678
|
+
return;
|
|
679
|
+
const resolved = {};
|
|
680
|
+
if (bindings.where) {
|
|
681
|
+
const where = {};
|
|
682
|
+
for (const [key, value] of Object.entries(bindings.where)) {
|
|
683
|
+
if (value === null)
|
|
684
|
+
return;
|
|
685
|
+
if (typeof value === "string" && value.startsWith("$")) {
|
|
686
|
+
const paramName = value.slice(1);
|
|
687
|
+
const paramValue = routeParams[paramName];
|
|
688
|
+
if (!paramValue)
|
|
689
|
+
return;
|
|
690
|
+
where[key] = paramValue;
|
|
691
|
+
} else {
|
|
692
|
+
where[key] = value;
|
|
693
|
+
}
|
|
694
|
+
}
|
|
695
|
+
resolved.where = where;
|
|
696
|
+
}
|
|
697
|
+
if (bindings.select)
|
|
698
|
+
resolved.select = bindings.select;
|
|
699
|
+
if (bindings.include)
|
|
700
|
+
resolved.include = bindings.include;
|
|
701
|
+
if (bindings.orderBy)
|
|
702
|
+
resolved.orderBy = bindings.orderBy;
|
|
703
|
+
if (bindings.limit !== undefined)
|
|
704
|
+
resolved.limit = bindings.limit;
|
|
705
|
+
return resolved;
|
|
706
|
+
}
|
|
707
|
+
// src/ssr-route-matcher.ts
|
|
708
|
+
function matchUrlToPatterns(url, patterns) {
|
|
709
|
+
const path = (url.split("?")[0] ?? "").split("#")[0] ?? "";
|
|
710
|
+
const matches = [];
|
|
711
|
+
for (const pattern of patterns) {
|
|
712
|
+
const result = matchPattern(path, pattern);
|
|
713
|
+
if (result) {
|
|
714
|
+
matches.push(result);
|
|
715
|
+
}
|
|
716
|
+
}
|
|
717
|
+
matches.sort((a, b) => {
|
|
718
|
+
const aSegments = a.pattern.split("/").length;
|
|
719
|
+
const bSegments = b.pattern.split("/").length;
|
|
720
|
+
return aSegments - bSegments;
|
|
721
|
+
});
|
|
722
|
+
return matches;
|
|
723
|
+
}
|
|
724
|
+
function matchPattern(path, pattern) {
|
|
725
|
+
const pathSegments = path.split("/").filter(Boolean);
|
|
726
|
+
const patternSegments = pattern.split("/").filter(Boolean);
|
|
727
|
+
if (patternSegments.length > pathSegments.length)
|
|
728
|
+
return;
|
|
729
|
+
const params = {};
|
|
730
|
+
for (let i = 0;i < patternSegments.length; i++) {
|
|
731
|
+
const seg = patternSegments[i];
|
|
732
|
+
const val = pathSegments[i];
|
|
733
|
+
if (seg.startsWith(":")) {
|
|
734
|
+
params[seg.slice(1)] = val;
|
|
735
|
+
} else if (seg !== val) {
|
|
736
|
+
return;
|
|
737
|
+
}
|
|
738
|
+
}
|
|
739
|
+
return { pattern, params };
|
|
740
|
+
}
|
|
741
|
+
// src/ssr-single-pass.ts
|
|
742
|
+
import { compileTheme as compileTheme2 } from "@vertz/ui";
|
|
743
|
+
async function ssrRenderSinglePass(module, url, options) {
|
|
744
|
+
if (options?.prefetch === false) {
|
|
745
|
+
return ssrRenderToString(module, url, options);
|
|
746
|
+
}
|
|
747
|
+
const normalizedUrl = url.endsWith("/index.html") ? url.slice(0, -"/index.html".length) || "/" : url;
|
|
748
|
+
const ssrTimeout = options?.ssrTimeout ?? 300;
|
|
749
|
+
ensureDomShim();
|
|
750
|
+
const zeroDiscoveryData = attemptZeroDiscovery(normalizedUrl, module, options, ssrTimeout);
|
|
751
|
+
if (zeroDiscoveryData) {
|
|
752
|
+
return renderWithPrefetchedData(module, normalizedUrl, zeroDiscoveryData, options);
|
|
753
|
+
}
|
|
754
|
+
const discoveryCtx = createRequestContext(normalizedUrl);
|
|
755
|
+
if (options?.ssrAuth) {
|
|
756
|
+
discoveryCtx.ssrAuth = options.ssrAuth;
|
|
757
|
+
}
|
|
758
|
+
const discoveredData = await ssrStorage.run(discoveryCtx, async () => {
|
|
759
|
+
try {
|
|
760
|
+
setGlobalSSRTimeout(ssrTimeout);
|
|
761
|
+
const createApp = resolveAppFactory(module);
|
|
762
|
+
createApp();
|
|
763
|
+
if (discoveryCtx.ssrRedirect) {
|
|
764
|
+
return { redirect: discoveryCtx.ssrRedirect };
|
|
765
|
+
}
|
|
766
|
+
if (discoveryCtx.pendingRouteComponents?.size) {
|
|
767
|
+
const entries = Array.from(discoveryCtx.pendingRouteComponents.entries());
|
|
768
|
+
const results = await Promise.allSettled(entries.map(([route, promise]) => Promise.race([
|
|
769
|
+
promise.then((mod) => ({ route, factory: mod.default })),
|
|
770
|
+
new Promise((_, reject) => setTimeout(() => reject(new Error("lazy route timeout")), ssrTimeout))
|
|
771
|
+
])));
|
|
772
|
+
discoveryCtx.resolvedComponents = new Map;
|
|
773
|
+
for (const result of results) {
|
|
774
|
+
if (result.status === "fulfilled") {
|
|
775
|
+
const { route, factory } = result.value;
|
|
776
|
+
discoveryCtx.resolvedComponents.set(route, factory);
|
|
777
|
+
}
|
|
778
|
+
}
|
|
779
|
+
discoveryCtx.pendingRouteComponents = undefined;
|
|
780
|
+
}
|
|
781
|
+
const queries = getSSRQueries();
|
|
782
|
+
const eligibleQueries = filterByEntityAccess(queries, options?.manifest?.entityAccess, options?.prefetchSession);
|
|
783
|
+
const resolvedQueries = [];
|
|
784
|
+
if (eligibleQueries.length > 0) {
|
|
785
|
+
await Promise.allSettled(eligibleQueries.map(({ promise, timeout, resolve, key }) => Promise.race([
|
|
786
|
+
promise.then((data) => {
|
|
787
|
+
resolve(data);
|
|
788
|
+
resolvedQueries.push({ key, data });
|
|
789
|
+
return "resolved";
|
|
790
|
+
}),
|
|
791
|
+
new Promise((r) => setTimeout(r, timeout || ssrTimeout)).then(() => "timeout")
|
|
792
|
+
])));
|
|
793
|
+
}
|
|
794
|
+
return {
|
|
795
|
+
resolvedQueries,
|
|
796
|
+
resolvedComponents: discoveryCtx.resolvedComponents
|
|
797
|
+
};
|
|
798
|
+
} finally {
|
|
799
|
+
clearGlobalSSRTimeout();
|
|
800
|
+
}
|
|
801
|
+
});
|
|
802
|
+
if ("redirect" in discoveredData) {
|
|
803
|
+
return {
|
|
804
|
+
html: "",
|
|
805
|
+
css: "",
|
|
806
|
+
ssrData: [],
|
|
807
|
+
headTags: "",
|
|
808
|
+
redirect: discoveredData.redirect
|
|
809
|
+
};
|
|
810
|
+
}
|
|
811
|
+
const renderCtx = createRequestContext(normalizedUrl);
|
|
812
|
+
if (options?.ssrAuth) {
|
|
813
|
+
renderCtx.ssrAuth = options.ssrAuth;
|
|
814
|
+
}
|
|
815
|
+
for (const { key, data } of discoveredData.resolvedQueries) {
|
|
816
|
+
renderCtx.queryCache.set(key, data);
|
|
817
|
+
}
|
|
818
|
+
renderCtx.resolvedComponents = discoveredData.resolvedComponents ?? new Map;
|
|
819
|
+
return ssrStorage.run(renderCtx, async () => {
|
|
820
|
+
try {
|
|
821
|
+
setGlobalSSRTimeout(ssrTimeout);
|
|
822
|
+
const createApp = resolveAppFactory(module);
|
|
823
|
+
let themeCss = "";
|
|
824
|
+
let themePreloadTags = "";
|
|
825
|
+
if (module.theme) {
|
|
826
|
+
try {
|
|
827
|
+
const compiled = compileTheme2(module.theme, {
|
|
828
|
+
fallbackMetrics: options?.fallbackMetrics
|
|
829
|
+
});
|
|
830
|
+
themeCss = compiled.css;
|
|
831
|
+
themePreloadTags = compiled.preloadTags;
|
|
832
|
+
} catch (e) {
|
|
833
|
+
console.error("[vertz] Failed to compile theme export. Ensure your theme is created with defineTheme().", e);
|
|
834
|
+
}
|
|
835
|
+
}
|
|
836
|
+
const app = createApp();
|
|
837
|
+
const vnode = toVNode(app);
|
|
838
|
+
const stream = renderToStream(vnode);
|
|
839
|
+
const html = await streamToString(stream);
|
|
840
|
+
const css = collectCSS(themeCss, module);
|
|
841
|
+
const ssrData = discoveredData.resolvedQueries.map(({ key, data }) => ({
|
|
842
|
+
key,
|
|
843
|
+
data: JSON.parse(JSON.stringify(data))
|
|
844
|
+
}));
|
|
845
|
+
return {
|
|
846
|
+
html,
|
|
847
|
+
css,
|
|
848
|
+
ssrData,
|
|
849
|
+
headTags: themePreloadTags,
|
|
850
|
+
discoveredRoutes: renderCtx.discoveredRoutes,
|
|
851
|
+
matchedRoutePatterns: renderCtx.matchedRoutePatterns
|
|
852
|
+
};
|
|
853
|
+
} finally {
|
|
854
|
+
clearGlobalSSRTimeout();
|
|
855
|
+
}
|
|
856
|
+
});
|
|
857
|
+
}
|
|
858
|
+
function attemptZeroDiscovery(url, module, options, ssrTimeout) {
|
|
859
|
+
const manifest = options?.manifest;
|
|
860
|
+
if (!manifest?.routeEntries || !module.api)
|
|
861
|
+
return null;
|
|
862
|
+
const matches = matchUrlToPatterns(url, manifest.routePatterns);
|
|
863
|
+
if (matches.length === 0)
|
|
864
|
+
return null;
|
|
865
|
+
const allQueries = [];
|
|
866
|
+
let mergedParams = {};
|
|
867
|
+
for (const match of matches) {
|
|
868
|
+
const entry = manifest.routeEntries[match.pattern];
|
|
869
|
+
if (entry) {
|
|
870
|
+
allQueries.push(...entry.queries);
|
|
871
|
+
}
|
|
872
|
+
mergedParams = { ...mergedParams, ...match.params };
|
|
873
|
+
}
|
|
874
|
+
if (allQueries.length === 0)
|
|
875
|
+
return null;
|
|
876
|
+
const descriptors = reconstructDescriptors(allQueries, mergedParams, module.api);
|
|
877
|
+
if (descriptors.length === 0)
|
|
878
|
+
return null;
|
|
879
|
+
return prefetchFromDescriptors(descriptors, ssrTimeout);
|
|
880
|
+
}
|
|
881
|
+
async function prefetchFromDescriptors(descriptors, ssrTimeout) {
|
|
882
|
+
const resolvedQueries = [];
|
|
883
|
+
await Promise.allSettled(descriptors.map(({ key, fetch: fetchFn }) => Promise.race([
|
|
884
|
+
fetchFn().then((result) => {
|
|
885
|
+
const data = unwrapResult(result);
|
|
886
|
+
resolvedQueries.push({ key, data });
|
|
887
|
+
return "resolved";
|
|
888
|
+
}),
|
|
889
|
+
new Promise((r) => setTimeout(r, ssrTimeout)).then(() => "timeout")
|
|
890
|
+
])));
|
|
891
|
+
return { resolvedQueries };
|
|
892
|
+
}
|
|
893
|
+
function unwrapResult(result) {
|
|
894
|
+
if (result && typeof result === "object" && "ok" in result && "data" in result) {
|
|
895
|
+
const r = result;
|
|
896
|
+
if (r.ok)
|
|
897
|
+
return r.data;
|
|
898
|
+
}
|
|
899
|
+
return result;
|
|
900
|
+
}
|
|
901
|
+
async function renderWithPrefetchedData(module, normalizedUrl, prefetchedData, options) {
|
|
902
|
+
const data = await prefetchedData;
|
|
903
|
+
const ssrTimeout = options?.ssrTimeout ?? 300;
|
|
904
|
+
const renderCtx = createRequestContext(normalizedUrl);
|
|
905
|
+
if (options?.ssrAuth) {
|
|
906
|
+
renderCtx.ssrAuth = options.ssrAuth;
|
|
907
|
+
}
|
|
908
|
+
for (const { key, data: queryData } of data.resolvedQueries) {
|
|
909
|
+
renderCtx.queryCache.set(key, queryData);
|
|
910
|
+
}
|
|
911
|
+
renderCtx.resolvedComponents = new Map;
|
|
912
|
+
return ssrStorage.run(renderCtx, async () => {
|
|
913
|
+
try {
|
|
914
|
+
setGlobalSSRTimeout(ssrTimeout);
|
|
915
|
+
const createApp = resolveAppFactory(module);
|
|
916
|
+
let themeCss = "";
|
|
917
|
+
let themePreloadTags = "";
|
|
918
|
+
if (module.theme) {
|
|
919
|
+
try {
|
|
920
|
+
const compiled = compileTheme2(module.theme, {
|
|
921
|
+
fallbackMetrics: options?.fallbackMetrics
|
|
922
|
+
});
|
|
923
|
+
themeCss = compiled.css;
|
|
924
|
+
themePreloadTags = compiled.preloadTags;
|
|
925
|
+
} catch (e) {
|
|
926
|
+
console.error("[vertz] Failed to compile theme export. Ensure your theme is created with defineTheme().", e);
|
|
927
|
+
}
|
|
928
|
+
}
|
|
929
|
+
const app = createApp();
|
|
930
|
+
const vnode = toVNode(app);
|
|
931
|
+
const stream = renderToStream(vnode);
|
|
932
|
+
const html = await streamToString(stream);
|
|
933
|
+
if (renderCtx.ssrRedirect) {
|
|
934
|
+
return {
|
|
935
|
+
html: "",
|
|
936
|
+
css: "",
|
|
937
|
+
ssrData: [],
|
|
938
|
+
headTags: "",
|
|
939
|
+
redirect: renderCtx.ssrRedirect,
|
|
940
|
+
discoveredRoutes: renderCtx.discoveredRoutes,
|
|
941
|
+
matchedRoutePatterns: renderCtx.matchedRoutePatterns
|
|
942
|
+
};
|
|
943
|
+
}
|
|
944
|
+
const css = collectCSS(themeCss, module);
|
|
945
|
+
const ssrData = data.resolvedQueries.map(({ key, data: d }) => ({
|
|
946
|
+
key,
|
|
947
|
+
data: JSON.parse(JSON.stringify(d))
|
|
948
|
+
}));
|
|
949
|
+
return {
|
|
950
|
+
html,
|
|
951
|
+
css,
|
|
952
|
+
ssrData,
|
|
953
|
+
headTags: themePreloadTags,
|
|
954
|
+
discoveredRoutes: renderCtx.discoveredRoutes,
|
|
955
|
+
matchedRoutePatterns: renderCtx.matchedRoutePatterns
|
|
956
|
+
};
|
|
957
|
+
} finally {
|
|
958
|
+
clearGlobalSSRTimeout();
|
|
959
|
+
}
|
|
960
|
+
});
|
|
961
|
+
}
|
|
962
|
+
var domShimInstalled = false;
|
|
963
|
+
function ensureDomShim() {
|
|
964
|
+
if (domShimInstalled && typeof document !== "undefined")
|
|
965
|
+
return;
|
|
966
|
+
domShimInstalled = true;
|
|
967
|
+
installDomShim();
|
|
968
|
+
}
|
|
969
|
+
function resolveAppFactory(module) {
|
|
970
|
+
const createApp = module.default || module.App;
|
|
971
|
+
if (typeof createApp !== "function") {
|
|
972
|
+
throw new Error("App entry must export a default function or named App function");
|
|
973
|
+
}
|
|
974
|
+
return createApp;
|
|
975
|
+
}
|
|
976
|
+
function filterByEntityAccess(queries, entityAccess, session) {
|
|
977
|
+
if (!entityAccess || !session)
|
|
978
|
+
return queries;
|
|
979
|
+
return queries.filter(({ key }) => {
|
|
980
|
+
const entity = extractEntityFromKey(key);
|
|
981
|
+
const method = extractMethodFromKey(key);
|
|
982
|
+
if (!entity)
|
|
983
|
+
return true;
|
|
984
|
+
const entityRules = entityAccess[entity];
|
|
985
|
+
if (!entityRules)
|
|
986
|
+
return true;
|
|
987
|
+
const rule = entityRules[method];
|
|
988
|
+
if (!rule)
|
|
989
|
+
return true;
|
|
990
|
+
return evaluateAccessRule(rule, session);
|
|
991
|
+
});
|
|
992
|
+
}
|
|
993
|
+
function extractEntityFromKey(key) {
|
|
994
|
+
const pathStart = key.indexOf(":/");
|
|
995
|
+
if (pathStart === -1)
|
|
996
|
+
return;
|
|
997
|
+
const path = key.slice(pathStart + 2);
|
|
998
|
+
const firstSlash = path.indexOf("/");
|
|
999
|
+
const questionMark = path.indexOf("?");
|
|
1000
|
+
if (firstSlash === -1 && questionMark === -1)
|
|
1001
|
+
return path;
|
|
1002
|
+
if (firstSlash === -1)
|
|
1003
|
+
return path.slice(0, questionMark);
|
|
1004
|
+
if (questionMark === -1)
|
|
1005
|
+
return path.slice(0, firstSlash);
|
|
1006
|
+
return path.slice(0, Math.min(firstSlash, questionMark));
|
|
1007
|
+
}
|
|
1008
|
+
function extractMethodFromKey(key) {
|
|
1009
|
+
const pathStart = key.indexOf(":/");
|
|
1010
|
+
if (pathStart === -1)
|
|
1011
|
+
return "list";
|
|
1012
|
+
const path = key.slice(pathStart + 2);
|
|
1013
|
+
const cleanPath = path.split("?")[0] ?? "";
|
|
1014
|
+
const segments = cleanPath.split("/").filter(Boolean);
|
|
1015
|
+
return segments.length > 1 ? "get" : "list";
|
|
1016
|
+
}
|
|
1017
|
+
function collectCSS(themeCss, module) {
|
|
1018
|
+
const alreadyIncluded = new Set;
|
|
1019
|
+
if (themeCss)
|
|
1020
|
+
alreadyIncluded.add(themeCss);
|
|
1021
|
+
if (module.styles) {
|
|
1022
|
+
for (const s of module.styles)
|
|
1023
|
+
alreadyIncluded.add(s);
|
|
1024
|
+
}
|
|
1025
|
+
const componentCss = module.getInjectedCSS ? module.getInjectedCSS().filter((s) => !alreadyIncluded.has(s)) : [];
|
|
1026
|
+
const themeTag = themeCss ? `<style data-vertz-css>${themeCss}</style>` : "";
|
|
1027
|
+
const globalTag = module.styles && module.styles.length > 0 ? `<style data-vertz-css>${module.styles.join(`
|
|
1028
|
+
`)}</style>` : "";
|
|
1029
|
+
const componentTag = componentCss.length > 0 ? `<style data-vertz-css>${componentCss.join(`
|
|
1030
|
+
`)}</style>` : "";
|
|
1031
|
+
return [themeTag, globalTag, componentTag].filter(Boolean).join(`
|
|
1032
|
+
`);
|
|
1033
|
+
}
|
|
460
1034
|
export {
|
|
461
1035
|
wrapWithHydrationMarkers,
|
|
1036
|
+
toPrefetchSession,
|
|
462
1037
|
streamToString,
|
|
463
1038
|
ssrStorage,
|
|
464
1039
|
ssrRenderToString,
|
|
1040
|
+
ssrRenderSinglePass,
|
|
465
1041
|
ssrDiscoverQueries,
|
|
466
1042
|
setGlobalSSRTimeout,
|
|
467
1043
|
serializeToHtml,
|
|
@@ -474,7 +1050,9 @@ export {
|
|
|
474
1050
|
renderHeadToHtml,
|
|
475
1051
|
renderAssetTags,
|
|
476
1052
|
registerSSRQuery,
|
|
1053
|
+
reconstructDescriptors,
|
|
477
1054
|
rawHtml,
|
|
1055
|
+
matchUrlToPatterns,
|
|
478
1056
|
isInSSR,
|
|
479
1057
|
inlineCriticalCss,
|
|
480
1058
|
getStreamingRuntimeScript,
|
|
@@ -484,6 +1062,7 @@ export {
|
|
|
484
1062
|
getAccessSetForSSR,
|
|
485
1063
|
generateSSRHtml,
|
|
486
1064
|
extractFontMetrics,
|
|
1065
|
+
evaluateAccessRule,
|
|
487
1066
|
encodeChunk,
|
|
488
1067
|
detectFallbackFont,
|
|
489
1068
|
createTemplateChunk,
|
|
@@ -492,6 +1071,7 @@ export {
|
|
|
492
1071
|
createSSRHandler,
|
|
493
1072
|
createSSRDataChunk,
|
|
494
1073
|
createSSRAdapter,
|
|
1074
|
+
createPrefetchManifestManager,
|
|
495
1075
|
createAccessSetScript,
|
|
496
1076
|
collectStreamChunks,
|
|
497
1077
|
clearGlobalSSRTimeout,
|
|
@@ -3,6 +3,7 @@ import { setAdapter } from "@vertz/ui/internals";
|
|
|
3
3
|
|
|
4
4
|
// src/dom-shim/ssr-node.ts
|
|
5
5
|
class SSRNode {
|
|
6
|
+
nodeType = 1;
|
|
6
7
|
childNodes = [];
|
|
7
8
|
parentNode = null;
|
|
8
9
|
get firstChild() {
|
|
@@ -48,6 +49,7 @@ class SSRNode {
|
|
|
48
49
|
|
|
49
50
|
// src/dom-shim/ssr-comment.ts
|
|
50
51
|
class SSRComment extends SSRNode {
|
|
52
|
+
nodeType = 8;
|
|
51
53
|
text;
|
|
52
54
|
constructor(text) {
|
|
53
55
|
super();
|
|
@@ -71,6 +73,7 @@ function rawHtml(html) {
|
|
|
71
73
|
|
|
72
74
|
// src/dom-shim/ssr-text-node.ts
|
|
73
75
|
class SSRTextNode extends SSRNode {
|
|
76
|
+
nodeType = 3;
|
|
74
77
|
text;
|
|
75
78
|
constructor(text) {
|
|
76
79
|
super();
|
|
@@ -86,6 +89,7 @@ class SSRTextNode extends SSRNode {
|
|
|
86
89
|
|
|
87
90
|
// src/dom-shim/ssr-fragment.ts
|
|
88
91
|
class SSRDocumentFragment extends SSRNode {
|
|
92
|
+
nodeType = 11;
|
|
89
93
|
children = [];
|
|
90
94
|
appendChild(child) {
|
|
91
95
|
if (child instanceof SSRTextNode) {
|
package/dist/ssr/index.d.ts
CHANGED
|
@@ -17,6 +17,8 @@ interface SSRModule {
|
|
|
17
17
|
getInjectedCSS?: () => string[];
|
|
18
18
|
/** Compiled routes exported from the app for build-time SSG with generateParams. */
|
|
19
19
|
routes?: CompiledRoute[];
|
|
20
|
+
/** Code-generated API client for manifest-driven zero-discovery prefetching. */
|
|
21
|
+
api?: Record<string, Record<string, (...args: unknown[]) => unknown>>;
|
|
20
22
|
}
|
|
21
23
|
interface SSRRenderResult {
|
|
22
24
|
html: string;
|
package/dist/ssr/index.js
CHANGED
|
@@ -3,8 +3,8 @@ import {
|
|
|
3
3
|
injectIntoTemplate,
|
|
4
4
|
ssrDiscoverQueries,
|
|
5
5
|
ssrRenderToString
|
|
6
|
-
} from "../shared/chunk-
|
|
7
|
-
import"../shared/chunk-
|
|
6
|
+
} from "../shared/chunk-yr65qdge.js";
|
|
7
|
+
import"../shared/chunk-gcwqkynf.js";
|
|
8
8
|
|
|
9
9
|
// src/prerender.ts
|
|
10
10
|
async function discoverRoutes(module) {
|