@vertz/ui-server 0.2.12 → 0.2.13

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.
@@ -29,6 +29,8 @@ interface BunDevServerOptions {
29
29
  * @default 'vscode'
30
30
  */
31
31
  editor?: string;
32
+ /** Extra HTML tags to inject into the <head> (e.g., font preloads, meta tags). */
33
+ headTags?: string;
32
34
  }
33
35
  interface ErrorDetail {
34
36
  message: string;
@@ -93,11 +95,12 @@ interface SSRPageHtmlOptions {
93
95
  ssrData: unknown[];
94
96
  scriptTag: string;
95
97
  editor?: string;
98
+ headTags?: string;
96
99
  }
97
100
  /**
98
101
  * Generate a full SSR HTML page with the given content, CSS, SSR data, and script tag.
99
102
  */
100
- declare function generateSSRPageHtml({ title, css, bodyHtml, ssrData, scriptTag, editor }: SSRPageHtmlOptions): string;
103
+ declare function generateSSRPageHtml({ title, css, bodyHtml, ssrData, scriptTag, editor, headTags }: SSRPageHtmlOptions): string;
101
104
  interface FetchInterceptorOptions {
102
105
  apiHandler: (req: Request) => Promise<Response>;
103
106
  origin: string;
@@ -56,6 +56,9 @@ class DiagnosticsCollector {
56
56
  ssrFailedReloadCount = 0;
57
57
  hmrBundledScriptUrl = null;
58
58
  hmrBootstrapDiscovered = false;
59
+ manifestFileCount = 0;
60
+ manifestDurationMs = 0;
61
+ manifestWarnings = [];
59
62
  errorCurrent = null;
60
63
  errorLastCategory = null;
61
64
  errorLastMessage = null;
@@ -86,6 +89,11 @@ class DiagnosticsCollector {
86
89
  this.ssrFailedReloadCount++;
87
90
  }
88
91
  }
92
+ recordManifestPrepass(fileCount, durationMs, warnings) {
93
+ this.manifestFileCount = fileCount;
94
+ this.manifestDurationMs = durationMs;
95
+ this.manifestWarnings = warnings;
96
+ }
89
97
  recordHMRAssets(bundledScriptUrl, bootstrapDiscovered) {
90
98
  this.hmrBundledScriptUrl = bundledScriptUrl;
91
99
  this.hmrBootstrapDiscovered = bootstrapDiscovered;
@@ -141,6 +149,11 @@ class DiagnosticsCollector {
141
149
  bundledScriptUrl: this.hmrBundledScriptUrl,
142
150
  bootstrapDiscovered: this.hmrBootstrapDiscovered
143
151
  },
152
+ manifest: {
153
+ fileCount: this.manifestFileCount,
154
+ durationMs: this.manifestDurationMs,
155
+ warnings: [...this.manifestWarnings]
156
+ },
144
157
  errors: {
145
158
  current: this.errorCurrent,
146
159
  lastCategory: this.errorLastCategory,
@@ -158,6 +171,28 @@ class DiagnosticsCollector {
158
171
  }
159
172
  }
160
173
 
174
+ // src/fetch-scope.ts
175
+ import { AsyncLocalStorage } from "async_hooks";
176
+ var fetchScope = new AsyncLocalStorage;
177
+ var _originalFetch = null;
178
+ function installFetchProxy() {
179
+ if (_originalFetch !== null)
180
+ return;
181
+ const original = globalThis.fetch;
182
+ _originalFetch = original;
183
+ const proxy = (input, init) => {
184
+ const scoped = fetchScope.getStore();
185
+ if (scoped)
186
+ return scoped(input, init);
187
+ return original(input, init);
188
+ };
189
+ proxy.preconnect = original.preconnect;
190
+ globalThis.fetch = proxy;
191
+ }
192
+ function runWithScopedFetch(interceptor, fn) {
193
+ return fetchScope.run(interceptor, fn);
194
+ }
195
+
161
196
  // src/source-map-resolver.ts
162
197
  import { readFileSync } from "fs";
163
198
  import { resolve as resolvePath } from "path";
@@ -305,6 +340,7 @@ function createSourceMapResolver(projectRoot) {
305
340
 
306
341
  // src/ssr-render.ts
307
342
  import { compileTheme } from "@vertz/ui";
343
+ import { EntityStore, MemoryCache, QueryEnvelopeStore } from "@vertz/ui/internals";
308
344
 
309
345
  // src/dom-shim/index.ts
310
346
  import { setAdapter } from "@vertz/ui/internals";
@@ -628,6 +664,25 @@ function createSSRAdapter() {
628
664
  };
629
665
  }
630
666
 
667
+ // src/ssr-context.ts
668
+ import { AsyncLocalStorage as AsyncLocalStorage2 } from "async_hooks";
669
+ import { registerSSRResolver } from "@vertz/ui/internals";
670
+ var ssrStorage = new AsyncLocalStorage2;
671
+ registerSSRResolver(() => ssrStorage.getStore());
672
+ function getSSRQueries() {
673
+ return ssrStorage.getStore()?.queries ?? [];
674
+ }
675
+ function setGlobalSSRTimeout(timeout) {
676
+ const store = ssrStorage.getStore();
677
+ if (store)
678
+ store.globalSSRTimeout = timeout;
679
+ }
680
+ function clearGlobalSSRTimeout() {
681
+ const store = ssrStorage.getStore();
682
+ if (store)
683
+ store.globalSSRTimeout = undefined;
684
+ }
685
+
631
686
  // src/dom-shim/index.ts
632
687
  var SHIM_GLOBALS = [
633
688
  "document",
@@ -663,10 +718,6 @@ function installDomShim() {
663
718
  }
664
719
  installedGlobals = [];
665
720
  setAdapter(createSSRAdapter());
666
- const isSSRContext = typeof globalThis.__SSR_URL__ !== "undefined";
667
- if (typeof document !== "undefined" && !isSSRContext && !shimInstalled) {
668
- return;
669
- }
670
721
  if (!shimInstalled) {
671
722
  savedGlobals = new Map;
672
723
  for (const g of SHIM_GLOBALS) {
@@ -699,7 +750,7 @@ function installDomShim() {
699
750
  globalThis.document = fakeDocument;
700
751
  if (typeof window === "undefined") {
701
752
  globalThis.window = {
702
- location: { pathname: globalThis.__SSR_URL__ || "/", search: "", hash: "" },
753
+ location: { pathname: ssrStorage.getStore()?.url || "/", search: "", hash: "" },
703
754
  addEventListener: () => {},
704
755
  removeEventListener: () => {},
705
756
  history: {
@@ -710,7 +761,7 @@ function installDomShim() {
710
761
  } else {
711
762
  globalThis.window.location = {
712
763
  ...globalThis.window.location || {},
713
- pathname: globalThis.__SSR_URL__ || "/"
764
+ pathname: ssrStorage.getStore()?.url || "/"
714
765
  };
715
766
  }
716
767
  globalThis.Node = SSRNode;
@@ -772,31 +823,6 @@ function installDomShim() {
772
823
  }
773
824
  });
774
825
  }
775
- function removeDomShim() {
776
- setAdapter(null);
777
- if (!shimInstalled) {
778
- return;
779
- }
780
- shimInstalled = false;
781
- if (savedGlobals) {
782
- for (const g of SHIM_GLOBALS) {
783
- if (savedGlobals.has(g)) {
784
- globalThis[g] = savedGlobals.get(g);
785
- } else {
786
- delete globalThis[g];
787
- }
788
- }
789
- savedGlobals = null;
790
- } else {
791
- for (const g of SHIM_GLOBALS) {
792
- delete globalThis[g];
793
- }
794
- }
795
- for (const g of installedGlobals) {
796
- delete globalThis[g];
797
- }
798
- installedGlobals = [];
799
- }
800
826
  function toVNode(element) {
801
827
  if (element instanceof SSRElement) {
802
828
  return element.toVNode();
@@ -976,49 +1002,36 @@ function renderToStream(tree, options) {
976
1002
  });
977
1003
  }
978
1004
 
979
- // src/ssr-context.ts
980
- import { AsyncLocalStorage } from "async_hooks";
981
- var ssrStorage = new AsyncLocalStorage;
982
- function isInSSR() {
983
- return ssrStorage.getStore() !== undefined;
984
- }
985
- function registerSSRQuery(entry) {
986
- ssrStorage.getStore()?.queries.push(entry);
987
- }
988
- function getSSRQueries() {
989
- return ssrStorage.getStore()?.queries ?? [];
990
- }
991
- function setGlobalSSRTimeout(timeout) {
992
- const store = ssrStorage.getStore();
993
- if (store)
994
- store.globalSSRTimeout = timeout;
995
- }
996
- function clearGlobalSSRTimeout() {
997
- const store = ssrStorage.getStore();
998
- if (store)
999
- store.globalSSRTimeout = undefined;
1000
- }
1001
- function getGlobalSSRTimeout() {
1002
- return ssrStorage.getStore()?.globalSSRTimeout;
1003
- }
1004
- globalThis.__VERTZ_IS_SSR__ = isInSSR;
1005
- globalThis.__VERTZ_SSR_REGISTER_QUERY__ = registerSSRQuery;
1006
- globalThis.__VERTZ_GET_GLOBAL_SSR_TIMEOUT__ = getGlobalSSRTimeout;
1007
-
1008
1005
  // src/ssr-streaming-runtime.ts
1009
1006
  function safeSerialize(data) {
1010
1007
  return JSON.stringify(data).replace(/</g, "\\u003c");
1011
1008
  }
1012
1009
 
1013
1010
  // src/ssr-render.ts
1014
- var renderLock = Promise.resolve();
1015
- function withRenderLock(fn) {
1016
- const prev = renderLock;
1017
- let release;
1018
- renderLock = new Promise((r) => {
1019
- release = r;
1020
- });
1021
- return prev.then(fn).finally(() => release());
1011
+ function createRequestContext(url) {
1012
+ return {
1013
+ url,
1014
+ adapter: createSSRAdapter(),
1015
+ subscriber: null,
1016
+ readValueCb: null,
1017
+ cleanupStack: [],
1018
+ batchDepth: 0,
1019
+ pendingEffects: new Map,
1020
+ contextScope: null,
1021
+ entityStore: new EntityStore,
1022
+ envelopeStore: new QueryEnvelopeStore,
1023
+ queryCache: new MemoryCache,
1024
+ inflight: new Map,
1025
+ queries: [],
1026
+ errors: []
1027
+ };
1028
+ }
1029
+ var domShimInstalled = false;
1030
+ function ensureDomShim() {
1031
+ if (domShimInstalled && typeof document !== "undefined")
1032
+ return;
1033
+ domShimInstalled = true;
1034
+ installDomShim();
1022
1035
  }
1023
1036
  function resolveAppFactory(module) {
1024
1037
  const createApp = module.default || module.App;
@@ -1038,35 +1051,18 @@ function collectCSS(themeCss, module) {
1038
1051
  for (const s of module.styles)
1039
1052
  alreadyIncluded.add(s);
1040
1053
  }
1041
- let componentCss;
1042
- if (module.getInjectedCSS) {
1043
- componentCss = module.getInjectedCSS().filter((s) => !alreadyIncluded.has(s));
1044
- } else {
1045
- componentCss = [];
1046
- const head = globalThis.document?.head;
1047
- if (head instanceof SSRElement) {
1048
- for (const child of head.children) {
1049
- if (child instanceof SSRElement && child.tag === "style" && "data-vertz-css" in child.attrs && child.textContent && !alreadyIncluded.has(child.textContent)) {
1050
- componentCss.push(child.textContent);
1051
- }
1052
- }
1053
- }
1054
- }
1054
+ const componentCss = module.getInjectedCSS ? module.getInjectedCSS().filter((s) => !alreadyIncluded.has(s)) : [];
1055
1055
  const componentStyles = componentCss.map((s) => `<style data-vertz-css>${s}</style>`).join(`
1056
1056
  `);
1057
1057
  return [themeTag, globalTags, componentStyles].filter(Boolean).join(`
1058
1058
  `);
1059
1059
  }
1060
1060
  async function ssrRenderToString(module, url, options) {
1061
- return withRenderLock(() => ssrRenderToStringUnsafe(module, url, options));
1062
- }
1063
- async function ssrRenderToStringUnsafe(module, url, options) {
1064
1061
  const normalizedUrl = url.endsWith("/index.html") ? url.slice(0, -"/index.html".length) || "/" : url;
1065
1062
  const ssrTimeout = options?.ssrTimeout ?? 300;
1066
- return ssrStorage.run({ url: normalizedUrl, errors: [], queries: [] }, async () => {
1067
- globalThis.__SSR_URL__ = normalizedUrl;
1068
- installDomShim();
1069
- globalThis.__VERTZ_CLEAR_QUERY_CACHE__?.();
1063
+ ensureDomShim();
1064
+ const ctx = createRequestContext(normalizedUrl);
1065
+ return ssrStorage.run(ctx, async () => {
1070
1066
  try {
1071
1067
  setGlobalSSRTimeout(ssrTimeout);
1072
1068
  const createApp = resolveAppFactory(module);
@@ -1078,7 +1074,6 @@ async function ssrRenderToStringUnsafe(module, url, options) {
1078
1074
  console.error("[vertz] Failed to compile theme export. Ensure your theme is created with defineTheme().", e);
1079
1075
  }
1080
1076
  }
1081
- globalThis.__VERTZ_SSR_SYNC_ROUTER__?.(normalizedUrl);
1082
1077
  createApp();
1083
1078
  const queries = getSSRQueries();
1084
1079
  const resolvedQueries = [];
@@ -1107,8 +1102,6 @@ async function ssrRenderToStringUnsafe(module, url, options) {
1107
1102
  return { html, css, ssrData };
1108
1103
  } finally {
1109
1104
  clearGlobalSSRTimeout();
1110
- removeDomShim();
1111
- delete globalThis.__SSR_URL__;
1112
1105
  }
1113
1106
  });
1114
1107
  }
@@ -1116,14 +1109,12 @@ async function ssrStreamNavQueries(module, url, options) {
1116
1109
  const normalizedUrl = url.endsWith("/index.html") ? url.slice(0, -"/index.html".length) || "/" : url;
1117
1110
  const ssrTimeout = options?.ssrTimeout ?? 300;
1118
1111
  const navTimeout = options?.navSsrTimeout ?? 5000;
1119
- const queries = await withRenderLock(() => ssrStorage.run({ url: normalizedUrl, errors: [], queries: [] }, async () => {
1120
- globalThis.__SSR_URL__ = normalizedUrl;
1121
- installDomShim();
1122
- globalThis.__VERTZ_CLEAR_QUERY_CACHE__?.();
1112
+ ensureDomShim();
1113
+ const ctx = createRequestContext(normalizedUrl);
1114
+ const queries = await ssrStorage.run(ctx, async () => {
1123
1115
  try {
1124
1116
  setGlobalSSRTimeout(ssrTimeout);
1125
1117
  const createApp = resolveAppFactory(module);
1126
- globalThis.__VERTZ_SSR_SYNC_ROUTER__?.(normalizedUrl);
1127
1118
  createApp();
1128
1119
  const discovered = getSSRQueries();
1129
1120
  return discovered.map((q) => ({
@@ -1134,10 +1125,8 @@ async function ssrStreamNavQueries(module, url, options) {
1134
1125
  }));
1135
1126
  } finally {
1136
1127
  clearGlobalSSRTimeout();
1137
- removeDomShim();
1138
- delete globalThis.__SSR_URL__;
1139
1128
  }
1140
- }));
1129
+ });
1141
1130
  if (queries.length === 0) {
1142
1131
  const encoder3 = new TextEncoder;
1143
1132
  return new ReadableStream({
@@ -1441,7 +1430,8 @@ function generateSSRPageHtml({
1441
1430
  bodyHtml,
1442
1431
  ssrData,
1443
1432
  scriptTag,
1444
- editor = "vscode"
1433
+ editor = "vscode",
1434
+ headTags = ""
1445
1435
  }) {
1446
1436
  const ssrDataScript = ssrData.length > 0 ? `<script>window.__VERTZ_SSR_DATA__=${safeSerialize(ssrData)};</script>` : "";
1447
1437
  return `<!doctype html>
@@ -1450,6 +1440,7 @@ function generateSSRPageHtml({
1450
1440
  <meta charset="UTF-8" />
1451
1441
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
1452
1442
  <title>${title}</title>
1443
+ ${headTags}
1453
1444
  ${css}
1454
1445
  ${buildErrorChannelScript(editor)}
1455
1446
  ${RELOAD_GUARD_SCRIPT}
@@ -1530,9 +1521,13 @@ function createBunDevServer(options) {
1530
1521
  title = "Vertz App",
1531
1522
  projectRoot = process.cwd(),
1532
1523
  logRequests = true,
1533
- editor: editorOption
1524
+ editor: editorOption,
1525
+ headTags = ""
1534
1526
  } = options;
1535
1527
  const editor = detectEditor(editorOption);
1528
+ if (apiHandler) {
1529
+ installFetchProxy();
1530
+ }
1536
1531
  const devDir = resolve(projectRoot, ".vertz", "dev");
1537
1532
  mkdirSync(devDir, { recursive: true });
1538
1533
  const logger = createDebugLogger(devDir);
@@ -1936,16 +1931,13 @@ data: {}
1936
1931
  console.log(`[Server] SSR: ${pathname}`);
1937
1932
  }
1938
1933
  try {
1939
- const originalFetch = globalThis.fetch;
1940
- if (apiHandler) {
1941
- globalThis.fetch = createFetchInterceptor({
1942
- apiHandler,
1943
- origin: `http://${host}:${server?.port}`,
1944
- skipSSRPaths,
1945
- originalFetch
1946
- });
1947
- }
1948
- try {
1934
+ const interceptor = apiHandler ? createFetchInterceptor({
1935
+ apiHandler,
1936
+ origin: `http://${host}:${server?.port}`,
1937
+ skipSSRPaths,
1938
+ originalFetch: globalThis.fetch
1939
+ }) : null;
1940
+ const doRender = async () => {
1949
1941
  logger.log("ssr", "render-start", { url: pathname });
1950
1942
  const ssrStart = performance.now();
1951
1943
  const result = await ssrRenderToString(ssrMod, pathname, { ssrTimeout: 300 });
@@ -1961,7 +1953,8 @@ data: {}
1961
1953
  bodyHtml: result.html,
1962
1954
  ssrData: result.ssrData,
1963
1955
  scriptTag,
1964
- editor
1956
+ editor,
1957
+ headTags
1965
1958
  });
1966
1959
  return new Response(html, {
1967
1960
  status: 200,
@@ -1970,11 +1963,8 @@ data: {}
1970
1963
  "Cache-Control": "no-store"
1971
1964
  }
1972
1965
  });
1973
- } finally {
1974
- if (apiHandler) {
1975
- globalThis.fetch = originalFetch;
1976
- }
1977
- }
1966
+ };
1967
+ return interceptor ? await runWithScopedFetch(interceptor, doRender) : await doRender();
1978
1968
  } catch (err) {
1979
1969
  console.error("[Server] SSR error:", err);
1980
1970
  const errMsg = err instanceof Error ? err.message : String(err);
@@ -1988,7 +1978,8 @@ data: {}
1988
1978
  bodyHtml: "",
1989
1979
  ssrData: [],
1990
1980
  scriptTag,
1991
- editor
1981
+ editor,
1982
+ headTags
1992
1983
  });
1993
1984
  return new Response(fallbackHtml, {
1994
1985
  status: 200,
@@ -1,6 +1,6 @@
1
1
  type ErrorCategory = "build" | "resolve" | "runtime" | "ssr";
2
2
  import { CSSExtractionResult } from "@vertz/ui-compiler";
3
- type DebugCategory = "plugin" | "ssr" | "watcher" | "ws";
3
+ type DebugCategory = "manifest" | "plugin" | "ssr" | "watcher" | "ws";
4
4
  interface DebugLogger {
5
5
  log(category: DebugCategory, message: string, data?: Record<string, unknown>): void;
6
6
  isEnabled(category: DebugCategory): boolean;
@@ -32,6 +32,14 @@ interface DiagnosticsSnapshot {
32
32
  bundledScriptUrl: string | null;
33
33
  bootstrapDiscovered: boolean;
34
34
  };
35
+ manifest: {
36
+ fileCount: number;
37
+ durationMs: number;
38
+ warnings: {
39
+ type: string;
40
+ message: string;
41
+ }[];
42
+ };
35
43
  errors: {
36
44
  current: ErrorCategory | null;
37
45
  lastCategory: ErrorCategory | null;
@@ -61,6 +69,9 @@ declare class DiagnosticsCollector {
61
69
  private ssrFailedReloadCount;
62
70
  private hmrBundledScriptUrl;
63
71
  private hmrBootstrapDiscovered;
72
+ private manifestFileCount;
73
+ private manifestDurationMs;
74
+ private manifestWarnings;
64
75
  private errorCurrent;
65
76
  private errorLastCategory;
66
77
  private errorLastMessage;
@@ -72,6 +83,10 @@ declare class DiagnosticsCollector {
72
83
  recordPluginConfig(filter: string, hmr: boolean, fastRefresh: boolean): void;
73
84
  recordPluginProcess(file: string): void;
74
85
  recordSSRReload(success: boolean, durationMs: number, error?: string): void;
86
+ recordManifestPrepass(fileCount: number, durationMs: number, warnings: {
87
+ type: string;
88
+ message: string;
89
+ }[]): void;
75
90
  recordHMRAssets(bundledScriptUrl: string | null, bootstrapDiscovered: boolean): void;
76
91
  recordError(category: ErrorCategory, message: string): void;
77
92
  recordErrorClear(): void;
@@ -103,6 +118,8 @@ interface VertzBunPluginOptions {
103
118
  fastRefresh?: boolean;
104
119
  /** Project root for computing relative paths. */
105
120
  projectRoot?: string;
121
+ /** Source directory for manifest generation pre-pass. Defaults to `src/` relative to projectRoot. */
122
+ srcDir?: string;
106
123
  /** Debug logger for opt-in diagnostic logging. */
107
124
  logger?: DebugLogger;
108
125
  /** Diagnostics collector for the health check endpoint. */
@@ -1,11 +1,19 @@
1
1
  // @bun
2
- import"../shared/chunk-eb80r8e8.js";
2
+ import {
3
+ __require
4
+ } from "../shared/chunk-eb80r8e8.js";
3
5
 
4
6
  // src/bun-plugin/plugin.ts
5
7
  import { mkdirSync, writeFileSync } from "fs";
6
8
  import { dirname, relative, resolve } from "path";
7
9
  import remapping from "@ampproject/remapping";
8
- import { ComponentAnalyzer, CSSExtractor, compile, HydrationTransformer } from "@vertz/ui-compiler";
10
+ import {
11
+ ComponentAnalyzer,
12
+ CSSExtractor,
13
+ compile,
14
+ generateAllManifests,
15
+ HydrationTransformer
16
+ } from "@vertz/ui-compiler";
9
17
  import MagicString from "magic-string";
10
18
  import { Project, ts as ts2 } from "ts-morph";
11
19
 
@@ -109,6 +117,45 @@ function createVertzBunPlugin(options) {
109
117
  const diagnostics = options?.diagnostics;
110
118
  const fileExtractions = new Map;
111
119
  const cssSidecarMap = new Map;
120
+ const srcDir = options?.srcDir ?? resolve(projectRoot, "src");
121
+ const frameworkManifestJson = __require(__require.resolve("@vertz/ui/reactivity.json"));
122
+ const manifestResult = generateAllManifests({
123
+ srcDir,
124
+ packageManifests: { "@vertz/ui": frameworkManifestJson }
125
+ });
126
+ const manifests = manifestResult.manifests;
127
+ let manifestsRecord = null;
128
+ const getManifestsRecord = () => {
129
+ if (manifestsRecord)
130
+ return manifestsRecord;
131
+ const record = {};
132
+ for (const [key, value] of manifests) {
133
+ record[key] = value;
134
+ }
135
+ manifestsRecord = record;
136
+ return record;
137
+ };
138
+ if (logger?.isEnabled("manifest")) {
139
+ logger.log("manifest", "pre-pass", {
140
+ files: manifests.size,
141
+ durationMs: Math.round(manifestResult.durationMs),
142
+ warnings: manifestResult.warnings.length
143
+ });
144
+ for (const [filePath, manifest] of manifests) {
145
+ const exportShapes = {};
146
+ for (const [name, info] of Object.entries(manifest.exports)) {
147
+ exportShapes[name] = info.reactivity.type;
148
+ }
149
+ logger.log("manifest", "file", {
150
+ file: relative(projectRoot, filePath),
151
+ exports: exportShapes
152
+ });
153
+ }
154
+ for (const warning of manifestResult.warnings) {
155
+ logger.log("manifest", "warning", { type: warning.type, message: warning.message });
156
+ }
157
+ }
158
+ diagnostics?.recordManifestPrepass(manifests.size, Math.round(manifestResult.durationMs), manifestResult.warnings.map((w) => ({ type: w.type, message: w.message })));
112
159
  mkdirSync(cssOutDir, { recursive: true });
113
160
  const plugin = {
114
161
  name: "vertz-bun-plugin",
@@ -141,7 +188,8 @@ function createVertzBunPlugin(options) {
141
188
  });
142
189
  const compileResult = compile(hydratedCode, {
143
190
  filename: args.path,
144
- target: options?.target
191
+ target: options?.target,
192
+ manifests: getManifestsRecord()
145
193
  });
146
194
  const remapped = remapping([compileResult.map, hydrationMap], () => null);
147
195
  const extraction = cssExtractor.extract(source, args.path);
@@ -7,7 +7,7 @@ import {
7
7
  installDomShim,
8
8
  removeDomShim,
9
9
  toVNode
10
- } from "../shared/chunk-4t0ekdyv.js";
10
+ } from "../shared/chunk-n1arq9xq.js";
11
11
  export {
12
12
  toVNode,
13
13
  removeDomShim,
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Install a global fetch proxy that delegates to per-request interceptors.
3
+ *
4
+ * Call once at server startup. Subsequent calls are no-ops.
5
+ * Outside of runWithScopedFetch(), fetch() behaves normally.
6
+ */
7
+ declare function installFetchProxy(): void;
8
+ /**
9
+ * Run a function with a scoped fetch interceptor.
10
+ *
11
+ * Any fetch() call inside fn() routes through the interceptor.
12
+ * Calls outside fn() (or without installFetchProxy()) use the original fetch.
13
+ */
14
+ declare function runWithScopedFetch<T>(interceptor: typeof fetch, fn: () => T): T;
15
+ /** Reset fetch proxy state. Used in tests only. */
16
+ declare function _resetFetchProxy(): void;
17
+ export { runWithScopedFetch, installFetchProxy, _resetFetchProxy };
@@ -0,0 +1,32 @@
1
+ // src/fetch-scope.ts
2
+ import { AsyncLocalStorage } from "node:async_hooks";
3
+ var fetchScope = new AsyncLocalStorage;
4
+ var _originalFetch = null;
5
+ function installFetchProxy() {
6
+ if (_originalFetch !== null)
7
+ return;
8
+ const original = globalThis.fetch;
9
+ _originalFetch = original;
10
+ const proxy = (input, init) => {
11
+ const scoped = fetchScope.getStore();
12
+ if (scoped)
13
+ return scoped(input, init);
14
+ return original(input, init);
15
+ };
16
+ proxy.preconnect = original.preconnect;
17
+ globalThis.fetch = proxy;
18
+ }
19
+ function runWithScopedFetch(interceptor, fn) {
20
+ return fetchScope.run(interceptor, fn);
21
+ }
22
+ function _resetFetchProxy() {
23
+ if (_originalFetch !== null) {
24
+ globalThis.fetch = _originalFetch;
25
+ _originalFetch = null;
26
+ }
27
+ }
28
+ export {
29
+ runWithScopedFetch,
30
+ installFetchProxy,
31
+ _resetFetchProxy
32
+ };
package/dist/index.d.ts CHANGED
@@ -293,6 +293,26 @@ declare function resetSlotCounter(): void;
293
293
  declare function createSlotPlaceholder(fallback: VNode | string): VNode & {
294
294
  _slotId: number;
295
295
  };
296
+ import { AccessSet } from "@vertz/ui/auth";
297
+ /**
298
+ * Extract an AccessSet from a decoded JWT payload for SSR injection.
299
+ *
300
+ * - If the JWT has an inline `acl.set` (no overflow), returns the decoded AccessSet.
301
+ * - If `acl.overflow` is true, returns `null` — caller should re-compute from live stores.
302
+ * - If no `acl` claim exists, returns `null`.
303
+ *
304
+ * @param jwtPayload - The decoded JWT payload (from verifyJWT)
305
+ * @returns The AccessSet for SSR injection, or null
306
+ */
307
+ declare function getAccessSetForSSR(jwtPayload: Record<string, unknown> | null): AccessSet | null;
308
+ /**
309
+ * Serialize an AccessSet into a `<script>` tag that sets
310
+ * `window.__VERTZ_ACCESS_SET__`.
311
+ *
312
+ * @param accessSet - The access set to serialize
313
+ * @param nonce - Optional CSP nonce for the script tag
314
+ */
315
+ declare function createAccessSetScript(accessSet: AccessSet, nonce?: string): string;
296
316
  import { RenderAdapter } from "@vertz/ui/internals";
297
317
  /**
298
318
  * Create an SSR adapter that uses in-memory SSR node classes.
@@ -300,21 +320,9 @@ import { RenderAdapter } from "@vertz/ui/internals";
300
320
  */
301
321
  declare function createSSRAdapter(): RenderAdapter;
302
322
  import { AsyncLocalStorage } from "node:async_hooks";
303
- interface SSRQueryEntry {
304
- promise: Promise<unknown>;
305
- timeout: number;
306
- resolve: (data: unknown) => void;
307
- key: string;
308
- resolved?: boolean;
309
- }
310
- interface SSRContext {
311
- url: string;
312
- errors: unknown[];
313
- queries: SSRQueryEntry[];
314
- /** Global per-query timeout override (ms). Set by the dev server entry. */
315
- globalSSRTimeout?: number;
316
- }
317
- declare const ssrStorage: AsyncLocalStorage<SSRContext>;
323
+ import { SSRQueryEntry, SSRRenderContext } from "@vertz/ui/internals";
324
+ import { SSRQueryEntry as SSRQueryEntry2 } from "@vertz/ui/internals";
325
+ declare const ssrStorage: AsyncLocalStorage<SSRRenderContext>;
318
326
  declare function isInSSR(): boolean;
319
327
  declare function getSSRUrl(): string | undefined;
320
328
  /**
@@ -326,7 +334,7 @@ declare function registerSSRQuery(entry: SSRQueryEntry): void;
326
334
  * Get all registered SSR queries for the current render.
327
335
  * Returns an empty array when called outside an SSR context.
328
336
  */
329
- declare function getSSRQueries(): SSRQueryEntry[];
337
+ declare function getSSRQueries(): SSRRenderContext["queries"];
330
338
  /**
331
339
  * Set the global SSR timeout for queries that don't specify their own.
332
340
  * Called by the virtual SSR entry to propagate the plugin-level ssrTimeout.
@@ -491,4 +499,4 @@ declare function collectStreamChunks(stream: ReadableStream<Uint8Array>): Promis
491
499
  * @param nonce - Optional CSP nonce to add to the inline script tag.
492
500
  */
493
501
  declare function createTemplateChunk(slotId: number, resolvedHtml: string, nonce?: string): string;
494
- export { wrapWithHydrationMarkers, streamToString, ssrStorage, ssrRenderToString, ssrDiscoverQueries, setGlobalSSRTimeout, serializeToHtml, safeSerialize, resetSlotCounter, renderToStream, renderToHTMLStream, renderToHTML, renderPage, renderHeadToHtml, renderAssetTags, registerSSRQuery, rawHtml, isInSSR, inlineCriticalCss, getStreamingRuntimeScript, getSSRUrl, getSSRQueries, getGlobalSSRTimeout, generateSSRHtml, encodeChunk, createTemplateChunk, createSlotPlaceholder, createSSRHandler, createSSRDataChunk, createSSRAdapter, collectStreamChunks, clearGlobalSSRTimeout, VNode, SSRRenderResult, SSRQueryEntry, SSRModule, SSRHandlerOptions, SSRDiscoverResult, RenderToStreamOptions, RenderToHTMLStreamOptions, RenderToHTMLOptions, RawHtml, PageOptions, HydrationOptions, HeadEntry, HeadCollector, GenerateSSRHtmlOptions, AssetDescriptor };
502
+ export { wrapWithHydrationMarkers, streamToString, ssrStorage, ssrRenderToString, ssrDiscoverQueries, setGlobalSSRTimeout, serializeToHtml, safeSerialize, resetSlotCounter, renderToStream, renderToHTMLStream, renderToHTML, renderPage, renderHeadToHtml, renderAssetTags, registerSSRQuery, rawHtml, isInSSR, inlineCriticalCss, getStreamingRuntimeScript, getSSRUrl, getSSRQueries, getGlobalSSRTimeout, getAccessSetForSSR, generateSSRHtml, encodeChunk, createTemplateChunk, createSlotPlaceholder, createSSRHandler, createSSRDataChunk, createSSRAdapter, createAccessSetScript, collectStreamChunks, clearGlobalSSRTimeout, VNode, SSRRenderResult, SSRQueryEntry2 as SSRQueryEntry, SSRModule, SSRHandlerOptions, SSRDiscoverResult, RenderToStreamOptions, RenderToHTMLStreamOptions, RenderToHTMLOptions, RawHtml, PageOptions, HydrationOptions, HeadEntry, HeadCollector, GenerateSSRHtmlOptions, AssetDescriptor };
package/dist/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import {
2
- clearGlobalSSRTimeout,
3
2
  collectStreamChunks,
3
+ createRequestContext,
4
4
  createSSRDataChunk,
5
5
  createSSRHandler,
6
6
  createSlotPlaceholder,
@@ -8,29 +8,27 @@ import {
8
8
  encodeChunk,
9
9
  escapeAttr,
10
10
  escapeHtml,
11
- getGlobalSSRTimeout,
12
- getSSRQueries,
13
- getSSRUrl,
14
11
  getStreamingRuntimeScript,
15
- isInSSR,
16
- registerSSRQuery,
17
12
  renderToStream,
18
13
  resetSlotCounter,
19
14
  safeSerialize,
20
15
  serializeToHtml,
21
- setGlobalSSRTimeout,
22
16
  ssrDiscoverQueries,
23
17
  ssrRenderToString,
24
- ssrStorage,
25
18
  streamToString
26
- } from "./shared/chunk-32688jav.js";
19
+ } from "./shared/chunk-98972e43.js";
27
20
  import {
28
- SSRElement,
21
+ clearGlobalSSRTimeout,
29
22
  createSSRAdapter,
30
- installDomShim,
23
+ getGlobalSSRTimeout,
24
+ getSSRQueries,
25
+ getSSRUrl,
26
+ isInSSR,
31
27
  rawHtml,
32
- removeDomShim
33
- } from "./shared/chunk-4t0ekdyv.js";
28
+ registerSSRQuery,
29
+ setGlobalSSRTimeout,
30
+ ssrStorage
31
+ } from "./shared/chunk-n1arq9xq.js";
34
32
 
35
33
  // src/asset-pipeline.ts
36
34
  function renderAssetTags(assets) {
@@ -216,16 +214,7 @@ ${options.head}` : headHtml;
216
214
  });
217
215
  }
218
216
  // src/render-to-html.ts
219
- import { compileTheme } from "@vertz/ui";
220
- import { setAdapter } from "@vertz/ui/internals";
221
- function installSSR() {
222
- setAdapter(createSSRAdapter());
223
- installDomShim();
224
- }
225
- function removeSSR() {
226
- setAdapter(null);
227
- removeDomShim();
228
- }
217
+ import { compileTheme, getInjectedCSS } from "@vertz/ui";
229
218
  async function twoPassRender(options) {
230
219
  options.app();
231
220
  const queries = getSSRQueries();
@@ -243,17 +232,7 @@ async function twoPassRender(options) {
243
232
  }
244
233
  const pendingQueries = queries.filter((q) => !q.resolved);
245
234
  const vnode = options.app();
246
- const fakeDoc = globalThis.document;
247
- const collectedCSS = [];
248
- if (fakeDoc?.head?.children) {
249
- for (const child of fakeDoc.head.children) {
250
- if (child instanceof SSRElement && child.tag === "style") {
251
- const cssText = child.children?.join("") ?? "";
252
- if (cssText)
253
- collectedCSS.push(cssText);
254
- }
255
- }
256
- }
235
+ const collectedCSS = getInjectedCSS();
257
236
  const themeCss = options.theme ? compileTheme(options.theme).css : "";
258
237
  const allStyles = [themeCss, ...options.styles ?? [], ...collectedCSS].filter(Boolean);
259
238
  const styleTags = allStyles.map((css) => `<style>${css}</style>`).join(`
@@ -273,9 +252,8 @@ async function twoPassRender(options) {
273
252
  return { html, pendingQueries };
274
253
  }
275
254
  async function renderToHTMLStream(options) {
276
- installSSR();
277
255
  const streamTimeout = options.streamTimeout ?? 30000;
278
- return ssrStorage.run({ url: options.url, errors: [], queries: [] }, async () => {
256
+ return ssrStorage.run(createRequestContext(options.url), async () => {
279
257
  try {
280
258
  if (options.ssrTimeout !== undefined) {
281
259
  setGlobalSSRTimeout(options.ssrTimeout);
@@ -283,14 +261,12 @@ async function renderToHTMLStream(options) {
283
261
  const { html, pendingQueries } = await twoPassRender(options);
284
262
  if (pendingQueries.length === 0) {
285
263
  clearGlobalSSRTimeout();
286
- removeSSR();
287
264
  return new Response(html, {
288
265
  status: 200,
289
266
  headers: { "content-type": "text/html; charset=utf-8" }
290
267
  });
291
268
  }
292
269
  clearGlobalSSRTimeout();
293
- removeSSR();
294
270
  const TIMEOUT_SENTINEL = Symbol("stream-timeout");
295
271
  let closed = false;
296
272
  let hardTimeoutId;
@@ -322,24 +298,56 @@ async function renderToHTMLStream(options) {
322
298
  });
323
299
  } catch (err) {
324
300
  clearGlobalSSRTimeout();
325
- removeSSR();
326
301
  throw err;
327
302
  }
328
303
  });
329
304
  }
330
305
  async function renderToHTML(appOrOptions, maybeOptions) {
331
306
  const options = typeof appOrOptions === "function" ? { ...maybeOptions, app: appOrOptions } : appOrOptions;
332
- installSSR();
333
- return ssrStorage.run({ url: options.url, errors: [], queries: [] }, async () => {
307
+ return ssrStorage.run(createRequestContext(options.url), async () => {
334
308
  try {
335
309
  const { html } = await twoPassRender(options);
336
310
  return html;
337
311
  } finally {
338
312
  clearGlobalSSRTimeout();
339
- removeSSR();
340
313
  }
341
314
  });
342
315
  }
316
+ // src/ssr-access-set.ts
317
+ function getAccessSetForSSR(jwtPayload) {
318
+ if (!jwtPayload)
319
+ return null;
320
+ const acl = jwtPayload.acl;
321
+ if (!acl)
322
+ return null;
323
+ if (acl.overflow)
324
+ return null;
325
+ if (!acl.set)
326
+ return null;
327
+ return {
328
+ entitlements: Object.fromEntries(Object.entries(acl.set.entitlements).map(([name, check]) => [
329
+ name,
330
+ {
331
+ allowed: check.allowed,
332
+ reasons: check.reasons ?? [],
333
+ ...check.reason ? { reason: check.reason } : {},
334
+ ...check.meta ? { meta: check.meta } : {}
335
+ }
336
+ ])),
337
+ flags: acl.set.flags,
338
+ plan: acl.set.plan,
339
+ computedAt: acl.set.computedAt
340
+ };
341
+ }
342
+ function createAccessSetScript(accessSet, nonce) {
343
+ const json = JSON.stringify(accessSet);
344
+ const escaped = json.replace(/</g, "\\u003c").replace(/\u2028/g, "\\u2028").replace(/\u2029/g, "\\u2029");
345
+ const nonceAttr = nonce ? ` nonce="${escapeAttr2(nonce)}"` : "";
346
+ return `<script${nonceAttr}>window.__VERTZ_ACCESS_SET__=${escaped}</script>`;
347
+ }
348
+ function escapeAttr2(s) {
349
+ return s.replace(/[&"'<>]/g, (c) => `&#${c.charCodeAt(0)};`);
350
+ }
343
351
  // src/ssr-html.ts
344
352
  function generateSSRHtml(options) {
345
353
  const { appHtml, css, ssrData, clientEntry, title = "Vertz App" } = options;
@@ -383,6 +391,7 @@ export {
383
391
  getSSRUrl,
384
392
  getSSRQueries,
385
393
  getGlobalSSRTimeout,
394
+ getAccessSetForSSR,
386
395
  generateSSRHtml,
387
396
  encodeChunk,
388
397
  createTemplateChunk,
@@ -390,6 +399,7 @@ export {
390
399
  createSSRHandler,
391
400
  createSSRDataChunk,
392
401
  createSSRAdapter,
402
+ createAccessSetScript,
393
403
  collectStreamChunks,
394
404
  clearGlobalSSRTimeout,
395
405
  HeadCollector
@@ -1,9 +1,12 @@
1
1
  import {
2
- SSRElement,
2
+ clearGlobalSSRTimeout,
3
+ createSSRAdapter,
4
+ getSSRQueries,
3
5
  installDomShim,
4
- removeDomShim,
6
+ setGlobalSSRTimeout,
7
+ ssrStorage,
5
8
  toVNode
6
- } from "./chunk-4t0ekdyv.js";
9
+ } from "./chunk-n1arq9xq.js";
7
10
 
8
11
  // src/html-serializer.ts
9
12
  var VOID_ELEMENTS = new Set([
@@ -177,38 +180,6 @@ function renderToStream(tree, options) {
177
180
  });
178
181
  }
179
182
 
180
- // src/ssr-context.ts
181
- import { AsyncLocalStorage } from "node:async_hooks";
182
- var ssrStorage = new AsyncLocalStorage;
183
- function isInSSR() {
184
- return ssrStorage.getStore() !== undefined;
185
- }
186
- function getSSRUrl() {
187
- return ssrStorage.getStore()?.url;
188
- }
189
- function registerSSRQuery(entry) {
190
- ssrStorage.getStore()?.queries.push(entry);
191
- }
192
- function getSSRQueries() {
193
- return ssrStorage.getStore()?.queries ?? [];
194
- }
195
- function setGlobalSSRTimeout(timeout) {
196
- const store = ssrStorage.getStore();
197
- if (store)
198
- store.globalSSRTimeout = timeout;
199
- }
200
- function clearGlobalSSRTimeout() {
201
- const store = ssrStorage.getStore();
202
- if (store)
203
- store.globalSSRTimeout = undefined;
204
- }
205
- function getGlobalSSRTimeout() {
206
- return ssrStorage.getStore()?.globalSSRTimeout;
207
- }
208
- globalThis.__VERTZ_IS_SSR__ = isInSSR;
209
- globalThis.__VERTZ_SSR_REGISTER_QUERY__ = registerSSRQuery;
210
- globalThis.__VERTZ_GET_GLOBAL_SSR_TIMEOUT__ = getGlobalSSRTimeout;
211
-
212
183
  // src/ssr-streaming-runtime.ts
213
184
  function safeSerialize(data) {
214
185
  return JSON.stringify(data).replace(/</g, "\\u003c");
@@ -225,14 +196,31 @@ function createSSRDataChunk(key, data, nonce) {
225
196
 
226
197
  // src/ssr-render.ts
227
198
  import { compileTheme } from "@vertz/ui";
228
- var renderLock = Promise.resolve();
229
- function withRenderLock(fn) {
230
- const prev = renderLock;
231
- let release;
232
- renderLock = new Promise((r) => {
233
- release = r;
234
- });
235
- return prev.then(fn).finally(() => release());
199
+ import { EntityStore, MemoryCache, QueryEnvelopeStore } from "@vertz/ui/internals";
200
+ function createRequestContext(url) {
201
+ return {
202
+ url,
203
+ adapter: createSSRAdapter(),
204
+ subscriber: null,
205
+ readValueCb: null,
206
+ cleanupStack: [],
207
+ batchDepth: 0,
208
+ pendingEffects: new Map,
209
+ contextScope: null,
210
+ entityStore: new EntityStore,
211
+ envelopeStore: new QueryEnvelopeStore,
212
+ queryCache: new MemoryCache,
213
+ inflight: new Map,
214
+ queries: [],
215
+ errors: []
216
+ };
217
+ }
218
+ var domShimInstalled = false;
219
+ function ensureDomShim() {
220
+ if (domShimInstalled && typeof document !== "undefined")
221
+ return;
222
+ domShimInstalled = true;
223
+ installDomShim();
236
224
  }
237
225
  function resolveAppFactory(module) {
238
226
  const createApp = module.default || module.App;
@@ -252,35 +240,18 @@ function collectCSS(themeCss, module) {
252
240
  for (const s of module.styles)
253
241
  alreadyIncluded.add(s);
254
242
  }
255
- let componentCss;
256
- if (module.getInjectedCSS) {
257
- componentCss = module.getInjectedCSS().filter((s) => !alreadyIncluded.has(s));
258
- } else {
259
- componentCss = [];
260
- const head = globalThis.document?.head;
261
- if (head instanceof SSRElement) {
262
- for (const child of head.children) {
263
- if (child instanceof SSRElement && child.tag === "style" && "data-vertz-css" in child.attrs && child.textContent && !alreadyIncluded.has(child.textContent)) {
264
- componentCss.push(child.textContent);
265
- }
266
- }
267
- }
268
- }
243
+ const componentCss = module.getInjectedCSS ? module.getInjectedCSS().filter((s) => !alreadyIncluded.has(s)) : [];
269
244
  const componentStyles = componentCss.map((s) => `<style data-vertz-css>${s}</style>`).join(`
270
245
  `);
271
246
  return [themeTag, globalTags, componentStyles].filter(Boolean).join(`
272
247
  `);
273
248
  }
274
249
  async function ssrRenderToString(module, url, options) {
275
- return withRenderLock(() => ssrRenderToStringUnsafe(module, url, options));
276
- }
277
- async function ssrRenderToStringUnsafe(module, url, options) {
278
250
  const normalizedUrl = url.endsWith("/index.html") ? url.slice(0, -"/index.html".length) || "/" : url;
279
251
  const ssrTimeout = options?.ssrTimeout ?? 300;
280
- return ssrStorage.run({ url: normalizedUrl, errors: [], queries: [] }, async () => {
281
- globalThis.__SSR_URL__ = normalizedUrl;
282
- installDomShim();
283
- globalThis.__VERTZ_CLEAR_QUERY_CACHE__?.();
252
+ ensureDomShim();
253
+ const ctx = createRequestContext(normalizedUrl);
254
+ return ssrStorage.run(ctx, async () => {
284
255
  try {
285
256
  setGlobalSSRTimeout(ssrTimeout);
286
257
  const createApp = resolveAppFactory(module);
@@ -292,7 +263,6 @@ async function ssrRenderToStringUnsafe(module, url, options) {
292
263
  console.error("[vertz] Failed to compile theme export. Ensure your theme is created with defineTheme().", e);
293
264
  }
294
265
  }
295
- globalThis.__VERTZ_SSR_SYNC_ROUTER__?.(normalizedUrl);
296
266
  createApp();
297
267
  const queries = getSSRQueries();
298
268
  const resolvedQueries = [];
@@ -321,25 +291,18 @@ async function ssrRenderToStringUnsafe(module, url, options) {
321
291
  return { html, css, ssrData };
322
292
  } finally {
323
293
  clearGlobalSSRTimeout();
324
- removeDomShim();
325
- delete globalThis.__SSR_URL__;
326
294
  }
327
295
  });
328
296
  }
329
297
  async function ssrDiscoverQueries(module, url, options) {
330
- return withRenderLock(() => ssrDiscoverQueriesUnsafe(module, url, options));
331
- }
332
- async function ssrDiscoverQueriesUnsafe(module, url, options) {
333
298
  const normalizedUrl = url.endsWith("/index.html") ? url.slice(0, -"/index.html".length) || "/" : url;
334
299
  const ssrTimeout = options?.ssrTimeout ?? 300;
335
- return ssrStorage.run({ url: normalizedUrl, errors: [], queries: [] }, async () => {
336
- globalThis.__SSR_URL__ = normalizedUrl;
337
- installDomShim();
338
- globalThis.__VERTZ_CLEAR_QUERY_CACHE__?.();
300
+ ensureDomShim();
301
+ const ctx = createRequestContext(normalizedUrl);
302
+ return ssrStorage.run(ctx, async () => {
339
303
  try {
340
304
  setGlobalSSRTimeout(ssrTimeout);
341
305
  const createApp = resolveAppFactory(module);
342
- globalThis.__VERTZ_SSR_SYNC_ROUTER__?.(normalizedUrl);
343
306
  createApp();
344
307
  const queries = getSSRQueries();
345
308
  const resolvedQueries = [];
@@ -375,8 +338,6 @@ async function ssrDiscoverQueriesUnsafe(module, url, options) {
375
338
  };
376
339
  } finally {
377
340
  clearGlobalSSRTimeout();
378
- removeDomShim();
379
- delete globalThis.__SSR_URL__;
380
341
  }
381
342
  });
382
343
  }
@@ -384,14 +345,12 @@ async function ssrStreamNavQueries(module, url, options) {
384
345
  const normalizedUrl = url.endsWith("/index.html") ? url.slice(0, -"/index.html".length) || "/" : url;
385
346
  const ssrTimeout = options?.ssrTimeout ?? 300;
386
347
  const navTimeout = options?.navSsrTimeout ?? 5000;
387
- const queries = await withRenderLock(() => ssrStorage.run({ url: normalizedUrl, errors: [], queries: [] }, async () => {
388
- globalThis.__SSR_URL__ = normalizedUrl;
389
- installDomShim();
390
- globalThis.__VERTZ_CLEAR_QUERY_CACHE__?.();
348
+ ensureDomShim();
349
+ const ctx = createRequestContext(normalizedUrl);
350
+ const queries = await ssrStorage.run(ctx, async () => {
391
351
  try {
392
352
  setGlobalSSRTimeout(ssrTimeout);
393
353
  const createApp = resolveAppFactory(module);
394
- globalThis.__VERTZ_SSR_SYNC_ROUTER__?.(normalizedUrl);
395
354
  createApp();
396
355
  const discovered = getSSRQueries();
397
356
  return discovered.map((q) => ({
@@ -402,10 +361,8 @@ async function ssrStreamNavQueries(module, url, options) {
402
361
  }));
403
362
  } finally {
404
363
  clearGlobalSSRTimeout();
405
- removeDomShim();
406
- delete globalThis.__SSR_URL__;
407
364
  }
408
- }));
365
+ });
409
366
  if (queries.length === 0) {
410
367
  const encoder3 = new TextEncoder;
411
368
  return new ReadableStream({
@@ -561,4 +518,4 @@ async function handleHTMLRequest(module, template, url, ssrTimeout, nonce) {
561
518
  }
562
519
  }
563
520
 
564
- export { escapeHtml, escapeAttr, serializeToHtml, resetSlotCounter, createSlotPlaceholder, encodeChunk, streamToString, collectStreamChunks, createTemplateChunk, renderToStream, ssrStorage, isInSSR, getSSRUrl, registerSSRQuery, getSSRQueries, setGlobalSSRTimeout, clearGlobalSSRTimeout, getGlobalSSRTimeout, safeSerialize, getStreamingRuntimeScript, createSSRDataChunk, ssrRenderToString, ssrDiscoverQueries, createSSRHandler };
521
+ export { escapeHtml, escapeAttr, serializeToHtml, resetSlotCounter, createSlotPlaceholder, encodeChunk, streamToString, collectStreamChunks, createTemplateChunk, renderToStream, safeSerialize, getStreamingRuntimeScript, createSSRDataChunk, createRequestContext, ssrRenderToString, ssrDiscoverQueries, createSSRHandler };
@@ -320,6 +320,37 @@ function createSSRAdapter() {
320
320
  };
321
321
  }
322
322
 
323
+ // src/ssr-context.ts
324
+ import { AsyncLocalStorage } from "node:async_hooks";
325
+ import { registerSSRResolver } from "@vertz/ui/internals";
326
+ var ssrStorage = new AsyncLocalStorage;
327
+ registerSSRResolver(() => ssrStorage.getStore());
328
+ function isInSSR() {
329
+ return ssrStorage.getStore() !== undefined;
330
+ }
331
+ function getSSRUrl() {
332
+ return ssrStorage.getStore()?.url;
333
+ }
334
+ function registerSSRQuery(entry) {
335
+ ssrStorage.getStore()?.queries.push(entry);
336
+ }
337
+ function getSSRQueries() {
338
+ return ssrStorage.getStore()?.queries ?? [];
339
+ }
340
+ function setGlobalSSRTimeout(timeout) {
341
+ const store = ssrStorage.getStore();
342
+ if (store)
343
+ store.globalSSRTimeout = timeout;
344
+ }
345
+ function clearGlobalSSRTimeout() {
346
+ const store = ssrStorage.getStore();
347
+ if (store)
348
+ store.globalSSRTimeout = undefined;
349
+ }
350
+ function getGlobalSSRTimeout() {
351
+ return ssrStorage.getStore()?.globalSSRTimeout;
352
+ }
353
+
323
354
  // src/dom-shim/index.ts
324
355
  var SHIM_GLOBALS = [
325
356
  "document",
@@ -355,10 +386,6 @@ function installDomShim() {
355
386
  }
356
387
  installedGlobals = [];
357
388
  setAdapter(createSSRAdapter());
358
- const isSSRContext = typeof globalThis.__SSR_URL__ !== "undefined";
359
- if (typeof document !== "undefined" && !isSSRContext && !shimInstalled) {
360
- return;
361
- }
362
389
  if (!shimInstalled) {
363
390
  savedGlobals = new Map;
364
391
  for (const g of SHIM_GLOBALS) {
@@ -391,7 +418,7 @@ function installDomShim() {
391
418
  globalThis.document = fakeDocument;
392
419
  if (typeof window === "undefined") {
393
420
  globalThis.window = {
394
- location: { pathname: globalThis.__SSR_URL__ || "/", search: "", hash: "" },
421
+ location: { pathname: ssrStorage.getStore()?.url || "/", search: "", hash: "" },
395
422
  addEventListener: () => {},
396
423
  removeEventListener: () => {},
397
424
  history: {
@@ -402,7 +429,7 @@ function installDomShim() {
402
429
  } else {
403
430
  globalThis.window.location = {
404
431
  ...globalThis.window.location || {},
405
- pathname: globalThis.__SSR_URL__ || "/"
432
+ pathname: ssrStorage.getStore()?.url || "/"
406
433
  };
407
434
  }
408
435
  globalThis.Node = SSRNode;
@@ -510,4 +537,4 @@ function toVNode(element) {
510
537
  return { tag: "span", attrs: {}, children: [String(element)] };
511
538
  }
512
539
 
513
- export { SSRNode, SSRComment, rawHtml, SSRTextNode, SSRDocumentFragment, SSRElement, createSSRAdapter, installDomShim, removeDomShim, toVNode };
540
+ export { ssrStorage, isInSSR, getSSRUrl, registerSSRQuery, getSSRQueries, setGlobalSSRTimeout, clearGlobalSSRTimeout, getGlobalSSRTimeout, SSRNode, SSRComment, rawHtml, SSRTextNode, SSRDocumentFragment, SSRElement, createSSRAdapter, installDomShim, removeDomShim, toVNode };
package/dist/ssr/index.js CHANGED
@@ -2,8 +2,8 @@ import {
2
2
  createSSRHandler,
3
3
  ssrDiscoverQueries,
4
4
  ssrRenderToString
5
- } from "../shared/chunk-32688jav.js";
6
- import"../shared/chunk-4t0ekdyv.js";
5
+ } from "../shared/chunk-98972e43.js";
6
+ import"../shared/chunk-n1arq9xq.js";
7
7
  export {
8
8
  ssrRenderToString,
9
9
  ssrDiscoverQueries,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vertz/ui-server",
3
- "version": "0.2.12",
3
+ "version": "0.2.13",
4
4
  "type": "module",
5
5
  "license": "MIT",
6
6
  "description": "Vertz UI server-side rendering runtime",
@@ -39,6 +39,10 @@
39
39
  "./bun-dev-server": {
40
40
  "import": "./dist/bun-dev-server.js",
41
41
  "types": "./dist/bun-dev-server.d.ts"
42
+ },
43
+ "./fetch-scope": {
44
+ "import": "./dist/fetch-scope.js",
45
+ "types": "./dist/fetch-scope.d.ts"
42
46
  }
43
47
  },
44
48
  "files": [
@@ -53,9 +57,9 @@
53
57
  "dependencies": {
54
58
  "@ampproject/remapping": "^2.3.0",
55
59
  "@jridgewell/trace-mapping": "^0.3.31",
56
- "@vertz/core": "^0.2.11",
57
- "@vertz/ui": "^0.2.11",
58
- "@vertz/ui-compiler": "^0.2.11",
60
+ "@vertz/core": "^0.2.12",
61
+ "@vertz/ui": "^0.2.12",
62
+ "@vertz/ui-compiler": "^0.2.12",
59
63
  "magic-string": "^0.30.0",
60
64
  "ts-morph": "^27.0.2"
61
65
  },