@vertz/ui-server 0.2.47 → 0.2.52

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,3 +1,32 @@
1
+ interface QuerySnapshot {
2
+ data: SerializedValue;
3
+ loading: boolean;
4
+ revalidating: boolean;
5
+ error: SerializedValue;
6
+ idle: boolean;
7
+ key?: string;
8
+ }
9
+ interface InstanceSnapshot {
10
+ index: number;
11
+ key?: string;
12
+ signals: Record<string, SerializedValue>;
13
+ queries: Record<string, QuerySnapshot>;
14
+ }
15
+ interface ComponentSnapshot {
16
+ name: string;
17
+ moduleId: string;
18
+ instanceCount: number;
19
+ instances: InstanceSnapshot[];
20
+ }
21
+ interface StateSnapshot {
22
+ components: ComponentSnapshot[];
23
+ totalInstances: number;
24
+ connectedClients: number;
25
+ timestamp: string;
26
+ message?: string;
27
+ truncated?: boolean;
28
+ }
29
+ type SerializedValue = string | number | boolean | null | object;
1
30
  import { AccessSet } from "@vertz/ui/auth";
2
31
  interface SessionData {
3
32
  user: {
@@ -198,6 +227,12 @@ interface BunDevServer {
198
227
  clearErrorForFileChange(): void;
199
228
  /** Set the last changed file path (for testing). */
200
229
  setLastChangedFile(file: string): void;
230
+ /**
231
+ * Request a state inspection from connected browser clients.
232
+ * Broadcasts an `inspect-state` command and waits for the first
233
+ * `state-snapshot` response. Returns the snapshot JSON or an error.
234
+ */
235
+ inspectState(filter?: string): Promise<StateSnapshot>;
201
236
  }
202
237
  interface HMRAssets {
203
238
  /** Discovered `/_bun/client/<hash>.js` URL, or null if not found */
@@ -2060,11 +2060,50 @@ class SSRElement extends SSRNode {
2060
2060
  set name(value) {
2061
2061
  this.attrs.name = value;
2062
2062
  }
2063
+ _selectValue;
2063
2064
  get value() {
2065
+ if (this.tag === "select") {
2066
+ return this._selectValue ?? "";
2067
+ }
2064
2068
  return this.attrs.value ?? "";
2065
2069
  }
2066
2070
  set value(value) {
2067
- this.attrs.value = value;
2071
+ if (this.tag === "select") {
2072
+ this._selectValue = value;
2073
+ this._applySelectValue();
2074
+ } else {
2075
+ this.attrs.value = value;
2076
+ }
2077
+ }
2078
+ _applySelectValue() {
2079
+ if (this._selectValue === undefined)
2080
+ return;
2081
+ this._applySelectValueToChildren(this.children);
2082
+ }
2083
+ _applySelectValueToChildren(children) {
2084
+ for (const child of children) {
2085
+ if (child instanceof SSRElement) {
2086
+ if (child.tag === "option") {
2087
+ const optValue = child.attrs.value ?? this._getChildTextContent(child);
2088
+ if (optValue === this._selectValue) {
2089
+ child.attrs.selected = "";
2090
+ } else {
2091
+ delete child.attrs.selected;
2092
+ }
2093
+ } else if (child.tag === "optgroup") {
2094
+ this._applySelectValueToChildren(child.children);
2095
+ }
2096
+ }
2097
+ }
2098
+ }
2099
+ _getChildTextContent(el) {
2100
+ return el.children.map((c) => {
2101
+ if (typeof c === "string")
2102
+ return c;
2103
+ if (c instanceof SSRElement)
2104
+ return this._getChildTextContent(c);
2105
+ return "";
2106
+ }).join("");
2068
2107
  }
2069
2108
  get src() {
2070
2109
  return this.attrs.src ?? "";
@@ -2135,9 +2174,14 @@ class SSRElement extends SSRNode {
2135
2174
  addEventListener(_event, _handler) {}
2136
2175
  removeEventListener(_event, _handler) {}
2137
2176
  toVNode() {
2177
+ let attrs = { ...this.attrs };
2178
+ if (this.tag === "select") {
2179
+ this._applySelectValue();
2180
+ delete attrs.value;
2181
+ }
2138
2182
  return {
2139
2183
  tag: this.tag,
2140
- attrs: { ...this.attrs },
2184
+ attrs,
2141
2185
  children: this.children.map((child) => {
2142
2186
  if (typeof child === "string") {
2143
2187
  return this._innerHTML != null ? rawHtml(child) : child;
@@ -3632,6 +3676,7 @@ function createBunDevServer(options) {
3632
3676
  let ssrFallback = false;
3633
3677
  const wsClients = new Set;
3634
3678
  let currentError = null;
3679
+ const pendingInspections = new Map;
3635
3680
  const sourceMapResolver = createSourceMapResolver(projectRoot);
3636
3681
  let clearGraceUntil = 0;
3637
3682
  let runtimeDebounceTimer = null;
@@ -4098,6 +4143,10 @@ function createBunDevServer(options) {
4098
4143
  const frInitPath = resolve(devDir, "fast-refresh-init.ts");
4099
4144
  writeFileSync3(frInitPath, `import '@vertz/ui-server/fast-refresh-runtime';
4100
4145
  if (import.meta.hot) import.meta.hot.accept();
4146
+ `);
4147
+ const siInitPath = resolve(devDir, "state-inspector-init.ts");
4148
+ writeFileSync3(siInitPath, `import '@vertz/ui-server/state-inspector';
4149
+ if (import.meta.hot) import.meta.hot.accept();
4101
4150
  `);
4102
4151
  const hmrShellHtml = `<!doctype html>
4103
4152
  <html lang="en"><head>
@@ -4105,6 +4154,7 @@ if (import.meta.hot) import.meta.hot.accept();
4105
4154
  <title>HMR Shell</title>
4106
4155
  </head><body>
4107
4156
  <script type="module" src="./fast-refresh-init.ts"></script>
4157
+ <script type="module" src="./state-inspector-init.ts"></script>
4108
4158
  <script type="module" src="${clientSrc}"></script>
4109
4159
  </body></html>`;
4110
4160
  const hmrShellPath = resolve(devDir, "hmr-shell.html");
@@ -4431,6 +4481,13 @@ data: {}
4431
4481
  devServer.restart();
4432
4482
  } else if (data.type === "ping") {
4433
4483
  ws.sendText(JSON.stringify({ type: "pong" }));
4484
+ } else if (data.type === "state-snapshot" && data.requestId) {
4485
+ const pending = pendingInspections.get(data.requestId);
4486
+ if (pending) {
4487
+ clearTimeout(pending.timer);
4488
+ pendingInspections.delete(data.requestId);
4489
+ pending.resolve(data.snapshot);
4490
+ }
4434
4491
  } else if (data.type === "resolve-stack" && data.stack) {
4435
4492
  const selfFetch = async (url) => {
4436
4493
  const absUrl = url.startsWith("http") ? url : `http://${host}:${server?.port}${url}`;
@@ -4803,8 +4860,53 @@ data: {}
4803
4860
  setLastChangedFile(file) {
4804
4861
  lastChangedFile = file;
4805
4862
  },
4863
+ async inspectState(filter) {
4864
+ if (wsClients.size === 0) {
4865
+ return {
4866
+ components: [],
4867
+ totalInstances: 0,
4868
+ connectedClients: 0,
4869
+ timestamp: new Date().toISOString(),
4870
+ message: "No browser clients connected. Open the app in a browser first."
4871
+ };
4872
+ }
4873
+ const requestId = crypto.randomUUID();
4874
+ const TIMEOUT_MS = 5000;
4875
+ return new Promise((resolve2) => {
4876
+ const timer = setTimeout(() => {
4877
+ pendingInspections.delete(requestId);
4878
+ resolve2({
4879
+ components: [],
4880
+ totalInstances: 0,
4881
+ connectedClients: wsClients.size,
4882
+ timestamp: new Date().toISOString(),
4883
+ message: "State inspection timed out after 5 seconds."
4884
+ });
4885
+ }, TIMEOUT_MS);
4886
+ pendingInspections.set(requestId, { resolve: resolve2, timer });
4887
+ const cmd = JSON.stringify({
4888
+ type: "inspect-state",
4889
+ requestId,
4890
+ ...filter ? { filter } : {}
4891
+ });
4892
+ for (const client of wsClients) {
4893
+ client.sendText(cmd);
4894
+ }
4895
+ });
4896
+ },
4806
4897
  async stop() {
4807
4898
  stopped = true;
4899
+ for (const [, { resolve: res, timer }] of pendingInspections) {
4900
+ clearTimeout(timer);
4901
+ res({
4902
+ components: [],
4903
+ totalInstances: 0,
4904
+ connectedClients: 0,
4905
+ timestamp: new Date().toISOString(),
4906
+ message: "Server stopped."
4907
+ });
4908
+ }
4909
+ pendingInspections.clear();
4808
4910
  if (refreshTimeout) {
4809
4911
  clearTimeout(refreshTimeout);
4810
4912
  refreshTimeout = null;
@@ -0,0 +1,59 @@
1
+ interface QuerySnapshot {
2
+ data: SerializedValue;
3
+ loading: boolean;
4
+ revalidating: boolean;
5
+ error: SerializedValue;
6
+ idle: boolean;
7
+ key?: string;
8
+ }
9
+ interface InstanceSnapshot {
10
+ index: number;
11
+ key?: string;
12
+ signals: Record<string, SerializedValue>;
13
+ queries: Record<string, QuerySnapshot>;
14
+ }
15
+ interface ComponentSnapshot {
16
+ name: string;
17
+ moduleId: string;
18
+ instanceCount: number;
19
+ instances: InstanceSnapshot[];
20
+ }
21
+ interface StateSnapshot {
22
+ components: ComponentSnapshot[];
23
+ totalInstances: number;
24
+ connectedClients: number;
25
+ timestamp: string;
26
+ message?: string;
27
+ truncated?: boolean;
28
+ }
29
+ type SerializedValue = string | number | boolean | null | object;
30
+ /**
31
+ * Serialize any JavaScript value to a JSON-safe representation.
32
+ * Handles functions, DOM nodes, circular references, Date, Map, Set,
33
+ * Error, Promise, Symbol, WeakRef, ArrayBuffer, and depth limiting.
34
+ */
35
+ declare function safeSerialize(value: unknown, maxDepth?: number, seen?: WeakSet<object>): SerializedValue;
36
+ /**
37
+ * Walk the Fast Refresh registry and collect a state snapshot.
38
+ * Optionally filter by component function name (case-sensitive).
39
+ */
40
+ declare function collectStateSnapshot(filter?: string): StateSnapshot;
41
+ /**
42
+ * Handle incoming `inspect-state` WebSocket messages.
43
+ * Collects a state snapshot and sends it back with the matching requestId.
44
+ */
45
+ declare function handleInspectMessage(event: MessageEvent, ws: WebSocket): void;
46
+ /**
47
+ * Set up the state inspector's WebSocket listener.
48
+ *
49
+ * Uses `addEventListener` instead of replacing `onmessage` to coexist with
50
+ * the error overlay handler. Polls for `__vertz_overlay._ws` reference changes
51
+ * so that reconnections (which create a new WebSocket instance) are re-hooked.
52
+ *
53
+ * NOTE: When multiple browser tabs are connected, the server broadcasts
54
+ * inspect-state to all. The first response wins — other tabs' responses are
55
+ * dropped. This is acceptable for v0.1.x; a future improvement could merge
56
+ * responses from multiple tabs.
57
+ */
58
+ declare function setupStateInspector(): void;
59
+ export { setupStateInspector, safeSerialize, handleInspectMessage, collectStateSnapshot, StateSnapshot, QuerySnapshot, InstanceSnapshot, ComponentSnapshot };
@@ -0,0 +1,270 @@
1
+ // @bun
2
+ import"../shared/chunk-pshsm7ck.js";
3
+
4
+ // src/bun-plugin/state-inspector.ts
5
+ var REGISTRY_KEY = Symbol.for("vertz:fast-refresh:registry");
6
+ var MAX_RESPONSE_SIZE = 2 * 1024 * 1024;
7
+ var DEFAULT_MAX_DEPTH = 4;
8
+ var QUERY_SIGNAL_NAMES = ["data", "loading", "revalidating", "error", "idle"];
9
+ function safeSerialize(value, maxDepth = DEFAULT_MAX_DEPTH, seen = new WeakSet) {
10
+ if (value === null)
11
+ return null;
12
+ if (value === undefined)
13
+ return null;
14
+ if (typeof value === "boolean")
15
+ return value;
16
+ if (typeof value === "number") {
17
+ if (Number.isNaN(value))
18
+ return "[NaN]";
19
+ if (!Number.isFinite(value))
20
+ return value > 0 ? "[Infinity]" : "[-Infinity]";
21
+ return value;
22
+ }
23
+ if (typeof value === "string")
24
+ return value;
25
+ if (typeof value === "bigint")
26
+ return value.toString();
27
+ if (typeof value === "symbol") {
28
+ const desc = value.description;
29
+ return desc ? `[Symbol: ${desc}]` : "[Symbol]";
30
+ }
31
+ if (typeof value === "function") {
32
+ const name = value.name;
33
+ return name && name !== "anonymous" ? `[Function: ${name}]` : "[Function]";
34
+ }
35
+ const obj = value;
36
+ if (seen.has(obj))
37
+ return "[Circular]";
38
+ if (obj instanceof Date)
39
+ return obj.toISOString();
40
+ if (obj instanceof Error) {
41
+ return { name: obj.name, message: obj.message };
42
+ }
43
+ if (obj instanceof Promise)
44
+ return "[Promise]";
45
+ if (obj instanceof Map)
46
+ return `[Map: ${obj.size} entries]`;
47
+ if (obj instanceof Set)
48
+ return `[Set: ${obj.size} items]`;
49
+ if (obj instanceof WeakMap)
50
+ return "[WeakMap]";
51
+ if (obj instanceof WeakSet)
52
+ return "[WeakSet]";
53
+ if (typeof WeakRef !== "undefined" && obj instanceof WeakRef)
54
+ return "[WeakRef]";
55
+ if (obj instanceof ArrayBuffer)
56
+ return `[ArrayBuffer: ${obj.byteLength} bytes]`;
57
+ if (ArrayBuffer.isView(obj) && "byteLength" in obj) {
58
+ return `[ArrayBuffer: ${obj.byteLength} bytes]`;
59
+ }
60
+ if (typeof HTMLElement !== "undefined" && obj instanceof HTMLElement) {
61
+ return `[HTMLElement: ${obj.tagName}]`;
62
+ }
63
+ if (typeof Node !== "undefined" && obj instanceof Node) {
64
+ return `[Node: ${obj.nodeName}]`;
65
+ }
66
+ if (maxDepth <= 0) {
67
+ if (Array.isArray(obj))
68
+ return `[Array: ${obj.length} items]`;
69
+ return `[Object: ${Object.keys(obj).length} keys]`;
70
+ }
71
+ seen.add(obj);
72
+ if (Array.isArray(obj)) {
73
+ const result2 = obj.map((item) => safeSerialize(item, maxDepth - 1, seen));
74
+ seen.delete(obj);
75
+ return result2;
76
+ }
77
+ const result = {};
78
+ for (const key of Object.keys(obj)) {
79
+ result[key] = safeSerialize(obj[key], maxDepth - 1, seen);
80
+ }
81
+ seen.delete(obj);
82
+ return result;
83
+ }
84
+ function peekSafe(sig) {
85
+ try {
86
+ return sig.peek();
87
+ } catch (e) {
88
+ const msg = e instanceof Error ? e.message : String(e);
89
+ return `[Error: ${msg}]`;
90
+ }
91
+ }
92
+ function collectStateSnapshot(filter) {
93
+ const registry = globalThis[REGISTRY_KEY];
94
+ if (!registry || registry.size === 0) {
95
+ return emptySnapshot(filter ? `${filter} is not in the component registry. Check the name spelling or ensure the file has been loaded.` : undefined);
96
+ }
97
+ const components = [];
98
+ let totalInstances = 0;
99
+ let foundInRegistry = false;
100
+ for (const [moduleId, moduleMap] of registry) {
101
+ for (const [name, record] of moduleMap) {
102
+ if (filter && name !== filter)
103
+ continue;
104
+ if (filter)
105
+ foundInRegistry = true;
106
+ const instances = [];
107
+ for (let i = 0;i < record.instances.length; i++) {
108
+ const inst = record.instances[i];
109
+ if (!inst.element?.isConnected)
110
+ continue;
111
+ const signals = {};
112
+ const queries = {};
113
+ const queryGroups = new Map;
114
+ const standaloneSignals = [];
115
+ for (const sig of inst.signals) {
116
+ const group = sig._queryGroup;
117
+ if (group) {
118
+ if (!queryGroups.has(group))
119
+ queryGroups.set(group, []);
120
+ queryGroups.get(group).push(sig);
121
+ } else {
122
+ standaloneSignals.push(sig);
123
+ }
124
+ }
125
+ let positionalIdx = 0;
126
+ for (const sig of standaloneSignals) {
127
+ const key = sig._hmrKey ?? `signal_${positionalIdx++}`;
128
+ signals[key] = safeSerialize(peekSafe(sig));
129
+ }
130
+ for (const [groupKey, groupSignals] of queryGroups) {
131
+ queries[groupKey] = buildQuerySnapshot(groupSignals, groupKey);
132
+ }
133
+ instances.push({ index: i, signals, queries });
134
+ totalInstances++;
135
+ }
136
+ if (instances.length > 0) {
137
+ components.push({ name, moduleId, instanceCount: instances.length, instances });
138
+ } else if (filter && foundInRegistry) {}
139
+ }
140
+ }
141
+ let message;
142
+ if (filter && components.length === 0) {
143
+ if (foundInRegistry) {
144
+ let moduleId = "";
145
+ for (const [mid, moduleMap] of registry) {
146
+ if (moduleMap.has(filter)) {
147
+ moduleId = mid;
148
+ break;
149
+ }
150
+ }
151
+ message = `${filter} is registered (in ${moduleId}) but has 0 mounted instances on the current page. Navigate to a page that renders it.`;
152
+ } else {
153
+ message = `${filter} is not in the component registry. Check the name spelling or ensure the file has been loaded.`;
154
+ }
155
+ }
156
+ const snapshot = {
157
+ components,
158
+ totalInstances,
159
+ connectedClients: 0,
160
+ timestamp: new Date().toISOString(),
161
+ ...message ? { message } : {}
162
+ };
163
+ const jsonStr = JSON.stringify(snapshot);
164
+ if (jsonStr.length > MAX_RESPONSE_SIZE) {
165
+ return truncateSnapshot(snapshot);
166
+ }
167
+ return snapshot;
168
+ }
169
+ function emptySnapshot(message) {
170
+ return {
171
+ components: [],
172
+ totalInstances: 0,
173
+ connectedClients: 0,
174
+ timestamp: new Date().toISOString(),
175
+ ...message ? { message } : {}
176
+ };
177
+ }
178
+ function buildQuerySnapshot(signals, groupKey) {
179
+ const named = new Map;
180
+ const unnamed = [];
181
+ for (const sig of signals) {
182
+ const val = peekSafe(sig);
183
+ if (sig._hmrKey && QUERY_SIGNAL_NAMES.includes(sig._hmrKey)) {
184
+ named.set(sig._hmrKey, val);
185
+ } else {
186
+ unnamed.push(val);
187
+ }
188
+ }
189
+ return {
190
+ data: safeSerialize(named.get("data") ?? unnamed[0] ?? null),
191
+ loading: Boolean(named.get("loading") ?? unnamed[1] ?? false),
192
+ revalidating: Boolean(named.get("revalidating") ?? unnamed[2] ?? false),
193
+ error: safeSerialize(named.get("error") ?? unnamed[3] ?? null),
194
+ idle: Boolean(named.get("idle") ?? unnamed[4] ?? false),
195
+ key: groupKey
196
+ };
197
+ }
198
+ function handleInspectMessage(event, ws) {
199
+ if (typeof event.data !== "string")
200
+ return;
201
+ try {
202
+ const msg = JSON.parse(event.data);
203
+ if (msg.type === "inspect-state") {
204
+ const snapshot = collectStateSnapshot(msg.filter ?? undefined);
205
+ ws.send(JSON.stringify({
206
+ type: "state-snapshot",
207
+ requestId: msg.requestId,
208
+ snapshot
209
+ }));
210
+ }
211
+ } catch {}
212
+ }
213
+ function setupStateInspector() {
214
+ if (typeof window === "undefined")
215
+ return;
216
+ let currentWs = null;
217
+ const MAX_INIT_RETRIES = 10;
218
+ let initRetries = 0;
219
+ function hookWs(ws) {
220
+ if (ws === currentWs)
221
+ return;
222
+ currentWs = ws;
223
+ ws.addEventListener("message", (event) => {
224
+ handleInspectMessage(event, ws);
225
+ });
226
+ }
227
+ function poll() {
228
+ const overlay = window.__vertz_overlay;
229
+ if (!overlay) {
230
+ if (initRetries++ < MAX_INIT_RETRIES) {
231
+ setTimeout(poll, 500);
232
+ }
233
+ return;
234
+ }
235
+ const checkWs = () => {
236
+ if (overlay._ws && overlay._ws !== currentWs) {
237
+ hookWs(overlay._ws);
238
+ }
239
+ };
240
+ checkWs();
241
+ setInterval(checkWs, 2000);
242
+ }
243
+ poll();
244
+ }
245
+ if (typeof document !== "undefined") {
246
+ if (document.readyState === "loading") {
247
+ document.addEventListener("DOMContentLoaded", setupStateInspector);
248
+ } else {
249
+ setupStateInspector();
250
+ }
251
+ }
252
+ function truncateSnapshot(snapshot) {
253
+ const truncated = {
254
+ ...snapshot,
255
+ truncated: true,
256
+ components: snapshot.components.map((comp) => ({
257
+ ...comp,
258
+ instances: comp.instances.slice(0, 3),
259
+ instanceCount: comp.instanceCount
260
+ }))
261
+ };
262
+ truncated.totalInstances = truncated.components.reduce((sum, c) => sum + c.instances.length, 0);
263
+ return truncated;
264
+ }
265
+ export {
266
+ setupStateInspector,
267
+ safeSerialize,
268
+ handleInspectMessage,
269
+ collectStateSnapshot
270
+ };
@@ -113,8 +113,15 @@ declare class SSRElement extends SSRNode {
113
113
  set type(value: string);
114
114
  get name(): string;
115
115
  set name(value: string);
116
+ /** Internal storage for select.value (not serialized as an attribute). */
117
+ private _selectValue;
116
118
  get value(): string;
117
119
  set value(value: string);
120
+ /** Walk <option> children and set/remove "selected" to match _selectValue. */
121
+ private _applySelectValue;
122
+ private _applySelectValueToChildren;
123
+ /** Get concatenated text content of an element's children. */
124
+ private _getChildTextContent;
118
125
  get src(): string;
119
126
  set src(value: string);
120
127
  get alt(): string;
@@ -7,7 +7,7 @@ import {
7
7
  installDomShim,
8
8
  removeDomShim,
9
9
  toVNode
10
- } from "../shared/chunk-szvdd1qq.js";
10
+ } from "../shared/chunk-bp1ez7v4.js";
11
11
  import"../shared/chunk-bt1px3c4.js";
12
12
  export {
13
13
  toVNode,
package/dist/index.d.ts CHANGED
@@ -536,6 +536,28 @@ interface SSRHandlerOptions {
536
536
  * (JSON files, third-party APIs, custom DB clients).
537
537
  */
538
538
  aotDataResolver?: AotDataResolver;
539
+ /**
540
+ * Derive attributes to set on the `<html>` tag from the incoming request.
541
+ *
542
+ * Useful for setting `data-theme`, `dir`, `lang`, or other attributes that
543
+ * must be on `<html>` to avoid FOUC. The callback runs before SSR rendering
544
+ * so the attributes are available in the first byte of the response.
545
+ *
546
+ * If the template already has an attribute with the same name, the callback's
547
+ * value overrides it. Values are HTML-escaped automatically. Keys must be
548
+ * valid HTML attribute names (`/^[a-zA-Z][a-zA-Z0-9-]*$/`).
549
+ *
550
+ * Return `undefined`, `null`, or `{}` to skip injection.
551
+ *
552
+ * @example
553
+ * ```ts
554
+ * htmlAttributes: (request) => ({
555
+ * 'data-theme': getThemeCookie(request) ?? 'dark',
556
+ * dir: getDirection(request),
557
+ * })
558
+ * ```
559
+ */
560
+ htmlAttributes?: (request: Request) => Record<string, string> | null | undefined;
539
561
  }
540
562
  declare function createSSRHandler(options: SSRHandlerOptions): (request: Request) => Promise<Response>;
541
563
  type NodeHandlerOptions = SSRHandlerOptions;
@@ -741,6 +763,8 @@ interface AotBarrelResult {
741
763
  * Write each entry as `<tempDir>/<filename>.ts` alongside the barrel.
742
764
  */
743
765
  files: Record<string, string>;
766
+ /** Function names skipped due to residual JSX in compiled output. */
767
+ skippedFns: string[];
744
768
  }
745
769
  /**
746
770
  * Generate a barrel module that re-exports __ssr_* functions from compiled files.
package/dist/index.js CHANGED
@@ -1,10 +1,10 @@
1
1
  import {
2
2
  createSSRHandler,
3
3
  loadAotManifest
4
- } from "./shared/chunk-ck3n699k.js";
4
+ } from "./shared/chunk-nstzxw4e.js";
5
5
  import {
6
6
  createNodeHandler
7
- } from "./shared/chunk-kzycr5v0.js";
7
+ } from "./shared/chunk-x8r5mh0q.js";
8
8
  import {
9
9
  clearRouteCssCache,
10
10
  collectStreamChunks,
@@ -34,7 +34,7 @@ import {
34
34
  ssrStreamNavQueries,
35
35
  streamToString,
36
36
  toPrefetchSession
37
- } from "./shared/chunk-tm5aeq94.js";
37
+ } from "./shared/chunk-c22cgv96.js";
38
38
  import {
39
39
  clearGlobalSSRTimeout,
40
40
  createSSRAdapter,
@@ -46,7 +46,7 @@ import {
46
46
  registerSSRQuery,
47
47
  setGlobalSSRTimeout,
48
48
  ssrStorage
49
- } from "./shared/chunk-szvdd1qq.js";
49
+ } from "./shared/chunk-bp1ez7v4.js";
50
50
  import {
51
51
  __commonJS,
52
52
  __require,
@@ -171578,26 +171578,39 @@ function generateAotBarrel(compiledFiles, routeMap, appEntry) {
171578
171578
  "import { __esc, __esc_attr, __ssr_spread, __ssr_style_object } from '@vertz/ui-server';"
171579
171579
  ];
171580
171580
  const files = {};
171581
+ const skippedFns = [];
171581
171582
  let fileIndex = 0;
171582
171583
  for (const [filePath, fns] of fileToFns) {
171583
171584
  const moduleName = basename(filePath, ".tsx").replace(/[^a-zA-Z0-9_-]/g, "_");
171584
171585
  const tempFileName = `__aot_${fileIndex}_${moduleName}`;
171585
171586
  const moduleRef = `./${tempFileName}.ts`;
171586
- lines.push(`export { ${fns.sort().join(", ")} } from '${moduleRef}';`);
171587
171587
  const compiled = compiledFiles[filePath];
171588
171588
  if (compiled) {
171589
171589
  const helperImport = `import { __esc, __esc_attr, __ssr_spread, __ssr_style_object } from '@vertz/ui-server';
171590
171590
  ` + `import type { SSRAotContext } from '@vertz/ui-server';
171591
171591
  `;
171592
- const extracted = extractSsrFunctions(compiled.code, fns);
171593
- files[`${tempFileName}.ts`] = helperImport + extracted;
171592
+ const cleanFns = [];
171593
+ for (const fn of fns) {
171594
+ const fnCode = extractSsrFunctions(compiled.code, [fn]);
171595
+ if (hasResidualJsx(fnCode)) {
171596
+ skippedFns.push(fn);
171597
+ } else {
171598
+ cleanFns.push(fn);
171599
+ }
171600
+ }
171601
+ if (cleanFns.length > 0) {
171602
+ const extracted = extractSsrFunctions(compiled.code, cleanFns);
171603
+ lines.push(`export { ${cleanFns.sort().join(", ")} } from '${moduleRef}';`);
171604
+ files[`${tempFileName}.ts`] = helperImport + extracted;
171605
+ }
171594
171606
  }
171595
171607
  fileIndex++;
171596
171608
  }
171597
171609
  return {
171598
171610
  barrelSource: lines.join(`
171599
171611
  `),
171600
- files
171612
+ files,
171613
+ skippedFns
171601
171614
  };
171602
171615
  }
171603
171616
  function extractSsrFunctions(code, fnNames) {
@@ -171624,6 +171637,9 @@ function extractSsrFunctions(code, fnNames) {
171624
171637
  return extracted.join(`
171625
171638
  `);
171626
171639
  }
171640
+ function hasResidualJsx(code) {
171641
+ return /<[A-Za-z]\w*\s+[\w-]+=\{/.test(code);
171642
+ }
171627
171643
  function findAppComponent(components) {
171628
171644
  for (const [name, comp] of Object.entries(components)) {
171629
171645
  if (comp.tier === "runtime-fallback")