@vertz/ui-server 0.2.30 → 0.2.31

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.
@@ -0,0 +1,227 @@
1
+ import {
2
+ escapeAttr,
3
+ injectIntoTemplate,
4
+ precomputeHandlerState,
5
+ resolveRouteModulepreload,
6
+ resolveSession,
7
+ safeSerialize,
8
+ ssrRenderProgressive,
9
+ ssrRenderSinglePass,
10
+ ssrStreamNavQueries
11
+ } from "./chunk-34fexgex.js";
12
+
13
+ // src/node-handler.ts
14
+ function createNodeHandler(options) {
15
+ const {
16
+ module,
17
+ ssrTimeout,
18
+ nonce,
19
+ fallbackMetrics,
20
+ routeChunkManifest,
21
+ cacheControl,
22
+ sessionResolver,
23
+ manifest,
24
+ progressiveHTML
25
+ } = options;
26
+ const { template, linkHeader, modulepreloadTags, splitResult } = precomputeHandlerState(options);
27
+ const useProgressive = progressiveHTML && splitResult && !(manifest?.routeEntries && Object.keys(manifest.routeEntries).length > 0);
28
+ return (req, res) => {
29
+ (async () => {
30
+ try {
31
+ const url = req.url ?? "/";
32
+ if (req.headers["x-vertz-nav"] === "1") {
33
+ const pathname = url.split("?")[0];
34
+ await handleNavRequest(req, res, module, pathname, ssrTimeout);
35
+ return;
36
+ }
37
+ let sessionScript = "";
38
+ let ssrAuth;
39
+ if (sessionResolver) {
40
+ const fullUrl = `http://${req.headers.host ?? "localhost"}${url}`;
41
+ const webRequest = new Request(fullUrl, {
42
+ method: req.method ?? "GET",
43
+ headers: req.headers
44
+ });
45
+ const result2 = await resolveSession(webRequest, sessionResolver, nonce);
46
+ sessionScript = result2.sessionScript;
47
+ ssrAuth = result2.ssrAuth;
48
+ }
49
+ if (useProgressive) {
50
+ await handleProgressiveRequest(req, res, module, splitResult, url, ssrTimeout, nonce, fallbackMetrics, linkHeader, modulepreloadTags, routeChunkManifest, cacheControl, sessionScript, ssrAuth, manifest);
51
+ return;
52
+ }
53
+ const result = await ssrRenderSinglePass(module, url, {
54
+ ssrTimeout,
55
+ fallbackMetrics,
56
+ ssrAuth,
57
+ manifest
58
+ });
59
+ if (result.redirect) {
60
+ res.writeHead(302, { Location: result.redirect.to });
61
+ res.end();
62
+ return;
63
+ }
64
+ const resolvedModulepreloadTags = resolveRouteModulepreload(routeChunkManifest, result.matchedRoutePatterns, modulepreloadTags);
65
+ const allHeadTags = [result.headTags, resolvedModulepreloadTags].filter(Boolean).join(`
66
+ `);
67
+ const html = injectIntoTemplate({
68
+ template,
69
+ appHtml: result.html,
70
+ appCss: result.css,
71
+ ssrData: result.ssrData,
72
+ nonce,
73
+ headTags: allHeadTags || undefined,
74
+ sessionScript
75
+ });
76
+ const headers = {
77
+ "Content-Type": "text/html; charset=utf-8"
78
+ };
79
+ if (linkHeader)
80
+ headers.Link = linkHeader;
81
+ if (cacheControl)
82
+ headers["Cache-Control"] = cacheControl;
83
+ res.writeHead(200, headers);
84
+ res.end(html);
85
+ } catch (err) {
86
+ console.error("[SSR] Render failed:", err instanceof Error ? err.message : err);
87
+ if (!res.headersSent) {
88
+ res.writeHead(500, { "Content-Type": "text/plain" });
89
+ }
90
+ res.end("Internal Server Error");
91
+ }
92
+ })();
93
+ };
94
+ }
95
+ async function handleProgressiveRequest(req, res, module, split, url, ssrTimeout, nonce, fallbackMetrics, linkHeader, staticModulepreloadTags, routeChunkManifest, cacheControl, sessionScript, ssrAuth, manifest) {
96
+ const result = await ssrRenderProgressive(module, url, {
97
+ ssrTimeout,
98
+ fallbackMetrics,
99
+ ssrAuth,
100
+ manifest
101
+ });
102
+ if (result.redirect) {
103
+ res.writeHead(302, { Location: result.redirect.to });
104
+ res.end();
105
+ return;
106
+ }
107
+ const resolvedModulepreloadTags = resolveRouteModulepreload(routeChunkManifest, result.matchedRoutePatterns, staticModulepreloadTags);
108
+ let headChunk = split.headTemplate;
109
+ const headCloseIdx = headChunk.lastIndexOf("</head>");
110
+ if (headCloseIdx !== -1) {
111
+ const injections = [];
112
+ if (result.css)
113
+ injections.push(result.css);
114
+ if (result.headTags)
115
+ injections.push(result.headTags);
116
+ if (resolvedModulepreloadTags)
117
+ injections.push(resolvedModulepreloadTags);
118
+ if (sessionScript)
119
+ injections.push(sessionScript);
120
+ if (injections.length > 0) {
121
+ headChunk = headChunk.slice(0, headCloseIdx) + injections.join(`
122
+ `) + `
123
+ ` + headChunk.slice(headCloseIdx);
124
+ }
125
+ } else {
126
+ if (result.css)
127
+ headChunk += result.css;
128
+ if (result.headTags)
129
+ headChunk += result.headTags;
130
+ if (resolvedModulepreloadTags)
131
+ headChunk += resolvedModulepreloadTags;
132
+ if (sessionScript)
133
+ headChunk += sessionScript;
134
+ }
135
+ const headers = {
136
+ "Content-Type": "text/html; charset=utf-8"
137
+ };
138
+ if (linkHeader)
139
+ headers.Link = linkHeader;
140
+ if (cacheControl)
141
+ headers["Cache-Control"] = cacheControl;
142
+ res.writeHead(200, headers);
143
+ res.write(headChunk);
144
+ let clientDisconnected = false;
145
+ req.on("close", () => {
146
+ clientDisconnected = true;
147
+ });
148
+ if (result.renderStream) {
149
+ const reader = result.renderStream.getReader();
150
+ let renderError;
151
+ try {
152
+ for (;; ) {
153
+ if (clientDisconnected) {
154
+ reader.cancel();
155
+ break;
156
+ }
157
+ const { done, value } = await reader.read();
158
+ if (done)
159
+ break;
160
+ const canContinue = res.write(value);
161
+ if (!canContinue && !clientDisconnected) {
162
+ await new Promise((resolve) => res.once("drain", resolve));
163
+ }
164
+ }
165
+ } catch (err) {
166
+ renderError = err instanceof Error ? err : new Error(String(err));
167
+ } finally {
168
+ reader.releaseLock();
169
+ }
170
+ if (renderError && !clientDisconnected) {
171
+ console.error("[SSR] Render error after head sent:", renderError.message);
172
+ const nonceAttr = nonce != null ? ` nonce="${escapeAttr(nonce)}"` : "";
173
+ const errorScript = `<script${nonceAttr}>document.dispatchEvent(new CustomEvent('vertz:ssr-error',` + `{detail:{message:${safeSerialize(renderError.message)}}}))</script>`;
174
+ res.write(errorScript);
175
+ }
176
+ }
177
+ if (clientDisconnected)
178
+ return;
179
+ let tail = "";
180
+ if (result.ssrData.length > 0) {
181
+ const nonceAttr = nonce != null ? ` nonce="${escapeAttr(nonce)}"` : "";
182
+ tail += `<script${nonceAttr}>window.__VERTZ_SSR_DATA__=${safeSerialize(result.ssrData)};</script>`;
183
+ }
184
+ tail += split.tailTemplate;
185
+ res.end(tail);
186
+ }
187
+ async function handleNavRequest(req, res, module, url, ssrTimeout) {
188
+ res.writeHead(200, {
189
+ "Content-Type": "text/event-stream",
190
+ "Cache-Control": "no-cache"
191
+ });
192
+ try {
193
+ const stream = await ssrStreamNavQueries(module, url, { ssrTimeout });
194
+ let clientDisconnected = false;
195
+ req.on("close", () => {
196
+ clientDisconnected = true;
197
+ });
198
+ const reader = stream.getReader();
199
+ try {
200
+ for (;; ) {
201
+ if (clientDisconnected) {
202
+ reader.cancel();
203
+ break;
204
+ }
205
+ const { done, value } = await reader.read();
206
+ if (done)
207
+ break;
208
+ const canContinue = res.write(value);
209
+ if (!canContinue && !clientDisconnected) {
210
+ await new Promise((resolve) => res.once("drain", resolve));
211
+ }
212
+ }
213
+ } finally {
214
+ reader.releaseLock();
215
+ }
216
+ } catch {
217
+ res.write(`event: done
218
+ data: {}
219
+
220
+ `);
221
+ }
222
+ if (!res.writableEnded) {
223
+ res.end();
224
+ }
225
+ }
226
+
227
+ export { createNodeHandler };
@@ -0,0 +1,216 @@
1
+ import {
2
+ encodeChunk,
3
+ escapeAttr,
4
+ injectIntoTemplate,
5
+ precomputeHandlerState,
6
+ resolveRouteModulepreload,
7
+ resolveSession,
8
+ safeSerialize,
9
+ ssrRenderProgressive,
10
+ ssrRenderSinglePass,
11
+ ssrStreamNavQueries
12
+ } from "./chunk-34fexgex.js";
13
+
14
+ // src/ssr-progressive-response.ts
15
+ function buildProgressiveResponse(options) {
16
+ const { headChunk, renderStream, tailChunk, ssrData, nonce, headers } = options;
17
+ const stream = new ReadableStream({
18
+ async start(controller) {
19
+ controller.enqueue(encodeChunk(headChunk));
20
+ const reader = renderStream.getReader();
21
+ let renderError;
22
+ try {
23
+ for (;; ) {
24
+ const { done, value } = await reader.read();
25
+ if (done)
26
+ break;
27
+ controller.enqueue(value);
28
+ }
29
+ } catch (err) {
30
+ renderError = err instanceof Error ? err : new Error(String(err));
31
+ }
32
+ if (renderError) {
33
+ console.error("[SSR] Render error after head sent:", renderError.message);
34
+ const nonceAttr = nonce != null ? ` nonce="${escapeAttr(nonce)}"` : "";
35
+ const errorScript = `<script${nonceAttr}>document.dispatchEvent(new CustomEvent('vertz:ssr-error',` + `{detail:{message:${safeSerialize(renderError.message)}}}))</script>`;
36
+ controller.enqueue(encodeChunk(errorScript));
37
+ }
38
+ let tail = "";
39
+ if (ssrData.length > 0) {
40
+ const nonceAttr = nonce != null ? ` nonce="${escapeAttr(nonce)}"` : "";
41
+ tail += `<script${nonceAttr}>window.__VERTZ_SSR_DATA__=${safeSerialize(ssrData)};</script>`;
42
+ }
43
+ tail += tailChunk;
44
+ controller.enqueue(encodeChunk(tail));
45
+ controller.close();
46
+ }
47
+ });
48
+ const responseHeaders = {
49
+ "Content-Type": "text/html; charset=utf-8",
50
+ ...headers
51
+ };
52
+ return new Response(stream, { status: 200, headers: responseHeaders });
53
+ }
54
+
55
+ // src/ssr-handler.ts
56
+ function createSSRHandler(options) {
57
+ const {
58
+ module,
59
+ ssrTimeout,
60
+ nonce,
61
+ fallbackMetrics,
62
+ routeChunkManifest,
63
+ cacheControl,
64
+ sessionResolver,
65
+ manifest,
66
+ progressiveHTML
67
+ } = options;
68
+ const { template, linkHeader, modulepreloadTags, splitResult } = precomputeHandlerState(options);
69
+ return async (request) => {
70
+ const url = new URL(request.url);
71
+ const pathname = url.pathname;
72
+ if (request.headers.get("x-vertz-nav") === "1") {
73
+ return handleNavRequest(module, pathname, ssrTimeout);
74
+ }
75
+ let sessionScript = "";
76
+ let ssrAuth;
77
+ if (sessionResolver) {
78
+ const result = await resolveSession(request, sessionResolver, nonce);
79
+ sessionScript = result.sessionScript;
80
+ ssrAuth = result.ssrAuth;
81
+ }
82
+ const useProgressive = progressiveHTML && splitResult && !(manifest?.routeEntries && Object.keys(manifest.routeEntries).length > 0);
83
+ if (useProgressive) {
84
+ return handleProgressiveHTMLRequest(module, splitResult, pathname + url.search, ssrTimeout, nonce, fallbackMetrics, linkHeader, modulepreloadTags, routeChunkManifest, cacheControl, sessionScript, ssrAuth, manifest);
85
+ }
86
+ return handleHTMLRequest(module, template, pathname + url.search, ssrTimeout, nonce, fallbackMetrics, linkHeader, modulepreloadTags, routeChunkManifest, cacheControl, sessionScript, ssrAuth, manifest);
87
+ };
88
+ }
89
+ async function handleNavRequest(module, url, ssrTimeout) {
90
+ try {
91
+ const stream = await ssrStreamNavQueries(module, url, { ssrTimeout });
92
+ return new Response(stream, {
93
+ status: 200,
94
+ headers: {
95
+ "Content-Type": "text/event-stream",
96
+ "Cache-Control": "no-cache"
97
+ }
98
+ });
99
+ } catch {
100
+ return new Response(`event: done
101
+ data: {}
102
+
103
+ `, {
104
+ status: 200,
105
+ headers: {
106
+ "Content-Type": "text/event-stream",
107
+ "Cache-Control": "no-cache"
108
+ }
109
+ });
110
+ }
111
+ }
112
+ async function handleProgressiveHTMLRequest(module, split, url, ssrTimeout, nonce, fallbackMetrics, linkHeader, staticModulepreloadTags, routeChunkManifest, cacheControl, sessionScript, ssrAuth, manifest) {
113
+ try {
114
+ const result = await ssrRenderProgressive(module, url, {
115
+ ssrTimeout,
116
+ fallbackMetrics,
117
+ ssrAuth,
118
+ manifest
119
+ });
120
+ if (result.redirect) {
121
+ return new Response(null, {
122
+ status: 302,
123
+ headers: { Location: result.redirect.to }
124
+ });
125
+ }
126
+ const modulepreloadTags = resolveRouteModulepreload(routeChunkManifest, result.matchedRoutePatterns, staticModulepreloadTags);
127
+ let headChunk = split.headTemplate;
128
+ const headCloseIdx = headChunk.lastIndexOf("</head>");
129
+ if (headCloseIdx !== -1) {
130
+ const injections = [];
131
+ if (result.css)
132
+ injections.push(result.css);
133
+ if (result.headTags)
134
+ injections.push(result.headTags);
135
+ if (modulepreloadTags)
136
+ injections.push(modulepreloadTags);
137
+ if (sessionScript)
138
+ injections.push(sessionScript);
139
+ if (injections.length > 0) {
140
+ headChunk = headChunk.slice(0, headCloseIdx) + injections.join(`
141
+ `) + `
142
+ ` + headChunk.slice(headCloseIdx);
143
+ }
144
+ } else {
145
+ if (result.css)
146
+ headChunk += result.css;
147
+ if (result.headTags)
148
+ headChunk += result.headTags;
149
+ if (modulepreloadTags)
150
+ headChunk += modulepreloadTags;
151
+ if (sessionScript)
152
+ headChunk += sessionScript;
153
+ }
154
+ const headers = {};
155
+ if (linkHeader)
156
+ headers.Link = linkHeader;
157
+ if (cacheControl)
158
+ headers["Cache-Control"] = cacheControl;
159
+ return buildProgressiveResponse({
160
+ headChunk,
161
+ renderStream: result.renderStream,
162
+ tailChunk: split.tailTemplate,
163
+ ssrData: result.ssrData,
164
+ nonce,
165
+ headers
166
+ });
167
+ } catch (err) {
168
+ console.error("[SSR] Render failed:", err instanceof Error ? err.message : err);
169
+ return new Response("Internal Server Error", {
170
+ status: 500,
171
+ headers: { "Content-Type": "text/plain" }
172
+ });
173
+ }
174
+ }
175
+ async function handleHTMLRequest(module, template, url, ssrTimeout, nonce, fallbackMetrics, linkHeader, staticModulepreloadTags, routeChunkManifest, cacheControl, sessionScript, ssrAuth, manifest) {
176
+ try {
177
+ const result = await ssrRenderSinglePass(module, url, {
178
+ ssrTimeout,
179
+ fallbackMetrics,
180
+ ssrAuth,
181
+ manifest
182
+ });
183
+ if (result.redirect) {
184
+ return new Response(null, {
185
+ status: 302,
186
+ headers: { Location: result.redirect.to }
187
+ });
188
+ }
189
+ const modulepreloadTags = resolveRouteModulepreload(routeChunkManifest, result.matchedRoutePatterns, staticModulepreloadTags);
190
+ const allHeadTags = [result.headTags, modulepreloadTags].filter(Boolean).join(`
191
+ `);
192
+ const html = injectIntoTemplate({
193
+ template,
194
+ appHtml: result.html,
195
+ appCss: result.css,
196
+ ssrData: result.ssrData,
197
+ nonce,
198
+ headTags: allHeadTags || undefined,
199
+ sessionScript
200
+ });
201
+ const headers = { "Content-Type": "text/html; charset=utf-8" };
202
+ if (linkHeader)
203
+ headers.Link = linkHeader;
204
+ if (cacheControl)
205
+ headers["Cache-Control"] = cacheControl;
206
+ return new Response(html, { status: 200, headers });
207
+ } catch (err) {
208
+ console.error("[SSR] Render failed:", err instanceof Error ? err.message : err);
209
+ return new Response("Internal Server Error", {
210
+ status: 500,
211
+ headers: { "Content-Type": "text/plain" }
212
+ });
213
+ }
214
+ }
215
+
216
+ export { createSSRHandler };
@@ -707,4 +707,4 @@ function toVNode(element) {
707
707
  return { tag: "span", attrs: {}, children: [String(element)] };
708
708
  }
709
709
 
710
- export { ssrStorage, isInSSR, getSSRUrl, registerSSRQuery, getSSRQueries, setGlobalSSRTimeout, clearGlobalSSRTimeout, getGlobalSSRTimeout, SSRNode, SSRComment, rawHtml, SSRTextNode, SSRDocumentFragment, SSRElement, createSSRAdapter, installDomShim, removeDomShim, toVNode };
710
+ export { SSRNode, SSRComment, rawHtml, SSRTextNode, SSRDocumentFragment, SSRElement, createSSRAdapter, ssrStorage, isInSSR, getSSRUrl, registerSSRQuery, getSSRQueries, setGlobalSSRTimeout, clearGlobalSSRTimeout, getGlobalSSRTimeout, installDomShim, removeDomShim, toVNode };
@@ -129,33 +129,6 @@ declare function stripScriptsFromStaticHTML(html: string): string;
129
129
  */
130
130
  declare function collectPrerenderPaths(routes: CompiledRoute2[], prefix?: string): Promise<string[]>;
131
131
  import { FontFallbackMetrics as FontFallbackMetrics4 } from "@vertz/ui";
132
- import { AccessSet } from "@vertz/ui/auth";
133
- interface SessionData {
134
- user: {
135
- id: string;
136
- email: string;
137
- role: string;
138
- [key: string]: unknown;
139
- };
140
- /** Unix timestamp in milliseconds (JWT exp * 1000). */
141
- expiresAt: number;
142
- }
143
- /** Resolved session data for SSR injection. */
144
- interface SSRSessionInfo {
145
- session: SessionData;
146
- /**
147
- * Access set from JWT acl claim.
148
- * - Present (object): inline access set (no overflow)
149
- * - null: access control is configured but the set overflowed the JWT
150
- * - undefined: access control is not configured
151
- */
152
- accessSet?: AccessSet | null;
153
- }
154
- /**
155
- * Callback that extracts session data from a request.
156
- * Returns null when no valid session exists (expired, missing, or invalid cookie).
157
- */
158
- type SessionResolver = (request: Request) => Promise<SSRSessionInfo | null>;
159
132
  import { ExtractedQuery } from "@vertz/ui-compiler";
160
133
  /**
161
134
  * SSR prefetch access rule evaluator.
@@ -208,6 +181,33 @@ interface SSRPrefetchManifest {
208
181
  queries: ExtractedQuery[];
209
182
  }>;
210
183
  }
184
+ import { AccessSet } from "@vertz/ui/auth";
185
+ interface SessionData {
186
+ user: {
187
+ id: string;
188
+ email: string;
189
+ role: string;
190
+ [key: string]: unknown;
191
+ };
192
+ /** Unix timestamp in milliseconds (JWT exp * 1000). */
193
+ expiresAt: number;
194
+ }
195
+ /** Resolved session data for SSR injection. */
196
+ interface SSRSessionInfo {
197
+ session: SessionData;
198
+ /**
199
+ * Access set from JWT acl claim.
200
+ * - Present (object): inline access set (no overflow)
201
+ * - null: access control is configured but the set overflowed the JWT
202
+ * - undefined: access control is not configured
203
+ */
204
+ accessSet?: AccessSet | null;
205
+ }
206
+ /**
207
+ * Callback that extracts session data from a request.
208
+ * Returns null when no valid session exists (expired, missing, or invalid cookie).
209
+ */
210
+ type SessionResolver = (request: Request) => Promise<SSRSessionInfo | null>;
211
211
  interface SSRHandlerOptions {
212
212
  /** The loaded SSR module (import('./dist/server/index.js')) */
213
213
  module: SSRModule;
package/dist/ssr/index.js CHANGED
@@ -1,10 +1,12 @@
1
1
  import {
2
- createSSRHandler,
2
+ createSSRHandler
3
+ } from "../shared/chunk-wb5fv233.js";
4
+ import {
3
5
  injectIntoTemplate,
4
6
  ssrDiscoverQueries,
5
7
  ssrRenderToString
6
- } from "../shared/chunk-bd0sgykf.js";
7
- import"../shared/chunk-gcwqkynf.js";
8
+ } from "../shared/chunk-34fexgex.js";
9
+ import"../shared/chunk-ybftdw1r.js";
8
10
 
9
11
  // src/prerender.ts
10
12
  async function discoverRoutes(module) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vertz/ui-server",
3
- "version": "0.2.30",
3
+ "version": "0.2.31",
4
4
  "type": "module",
5
5
  "license": "MIT",
6
6
  "description": "Vertz UI server-side rendering runtime",
@@ -43,6 +43,10 @@
43
43
  "./fetch-scope": {
44
44
  "import": "./dist/fetch-scope.js",
45
45
  "types": "./dist/fetch-scope.d.ts"
46
+ },
47
+ "./node": {
48
+ "import": "./dist/node-handler.js",
49
+ "types": "./dist/node-handler.d.ts"
46
50
  }
47
51
  },
48
52
  "files": [
@@ -58,15 +62,15 @@
58
62
  "@ampproject/remapping": "^2.3.0",
59
63
  "@capsizecss/unpack": "^4.0.0",
60
64
  "@jridgewell/trace-mapping": "^0.3.31",
61
- "@vertz/core": "^0.2.29",
62
- "@vertz/ui": "^0.2.29",
63
- "@vertz/ui-compiler": "^0.2.29",
65
+ "@vertz/core": "^0.2.30",
66
+ "@vertz/ui": "^0.2.30",
67
+ "@vertz/ui-compiler": "^0.2.30",
64
68
  "magic-string": "^0.30.0",
65
69
  "sharp": "^0.34.5",
66
70
  "ts-morph": "^27.0.2"
67
71
  },
68
72
  "devDependencies": {
69
- "@vertz/codegen": "^0.2.29",
73
+ "@vertz/codegen": "^0.2.30",
70
74
  "@vertz/ui-auth": "^0.2.19",
71
75
  "bun-types": "^1.3.10",
72
76
  "bunup": "^0.16.31",