@vesk/adapter 0.2.94 → 0.2.95

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 +1 @@
1
- {"version":3,"file":"client-bundle.d.ts","sourceRoot":"","sources":["../src/client-bundle.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"client-bundle.d.ts","sourceRoot":"","sources":["../src/client-bundle.ts"],"names":[],"mappings":"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"}
@@ -59,12 +59,24 @@ function fileUnchanged(filePath, cached) {
59
59
  return false;
60
60
  }
61
61
  }
62
- function buildRouterOpts(options) {
62
+ function hasServerMiddleware(nodes) {
63
+ for (const node of nodes) {
64
+ if (node.hasMiddleware)
65
+ return true;
66
+ if (node.children?.length && hasServerMiddleware(node.children))
67
+ return true;
68
+ }
69
+ return false;
70
+ }
71
+ function buildRouterOpts(options, serverMiddleware) {
63
72
  const ttl = options?.routeDataCache;
73
+ const opts = [];
74
+ if (serverMiddleware)
75
+ opts.push('serverMiddleware: true');
64
76
  if (typeof ttl === 'number' && ttl > 0) {
65
- return `, { routeDataCache: ${ttl} }`;
77
+ opts.push(`routeDataCache: ${ttl}`);
66
78
  }
67
- return '';
79
+ return opts.length ? `, { ${opts.join(', ')} }` : '';
68
80
  }
69
81
  function findRuntimeSrc(appDir) {
70
82
  const monorepoRoot = resolve(__dirname, '..', '..', '..');
@@ -143,7 +155,7 @@ function isAnyImport(node) {
143
155
  * map (registrations would land on a detached object).
144
156
  */
145
157
  function isRuntimePreamble(node) {
146
- if (node.type === 'FunctionDeclaration' && (node.id?.name === '__cleanup' || node.id?.name === '__place')) {
158
+ if (node.type === 'FunctionDeclaration' && (node.id?.name === '__cleanup' || node.id?.name === '__place' || node.id?.name === '__reconcileRegion')) {
147
159
  return true;
148
160
  }
149
161
  if (node.type === 'VariableDeclaration') {
@@ -1920,6 +1932,31 @@ async function buildMainBundle(routeTree, runtimeDir, codeSplit, mono, hmr, impo
1920
1932
  '\tfallback.appendChild(end);\n' +
1921
1933
  '\tfor (let i = 0; i < nodes.length; i++) fallback.insertBefore(nodes[i], end);\n' +
1922
1934
  '}\n';
1935
+ const reconcileRegionFn = 'function __reconcileRegion(parent, id, branch, walker) {\n' +
1936
+ '\tif (!parent) return;\n' +
1937
+ '\tlet start = null;\n' +
1938
+ '\tlet end = null;\n' +
1939
+ '\tfor (let n = parent.firstChild; n; n = n.nextSibling) {\n' +
1940
+ '\t\tif (n.nodeType !== 8) continue;\n' +
1941
+ "\t\tconst text = n.textContent || '';\n" +
1942
+ "\t\tif (text === 'vesk-r:' + id + ':0' || text === 'vesk-r:' + id + ':1') start = n;\n" +
1943
+ "\t\telse if (text === 'vesk-r-end:' + id) end = n;\n" +
1944
+ '\t\tif (start && end) break;\n' +
1945
+ '\t}\n' +
1946
+ '\tif (!start || !end) return;\n' +
1947
+ "\tconst ssrBranch = (start.textContent || '').slice(-2) === ':1' ? 1 : 0;\n" +
1948
+ '\tif (ssrBranch !== branch) {\n' +
1949
+ '\t\tlet n = start.nextSibling;\n' +
1950
+ '\t\twhile (n && n !== end) {\n' +
1951
+ '\t\t\tconst next = n.nextSibling;\n' +
1952
+ '\t\t\tn.remove();\n' +
1953
+ '\t\t\tn = next;\n' +
1954
+ '\t\t}\n' +
1955
+ '\t\tif (walker && walker.retireDetached) walker.retireDetached();\n' +
1956
+ '\t}\n' +
1957
+ '\tstart.remove();\n' +
1958
+ '\tend.remove();\n' +
1959
+ '}\n';
1923
1960
  const updateComponentsFn = 'function __updateComponents(nodes) {\n' +
1924
1961
  ' for (const n of nodes) {\n' +
1925
1962
  " if (n._pageName && __components[n._pageName]) n.page = __components[n._pageName];\n" +
@@ -1956,7 +1993,7 @@ async function buildMainBundle(routeTree, runtimeDir, codeSplit, mono, hmr, impo
1956
1993
  ' }\n' +
1957
1994
  ' }\n' +
1958
1995
  '}\n';
1959
- const routerOpts = buildRouterOpts({ routeDataCache });
1996
+ const routerOpts = buildRouterOpts({ routeDataCache }, hasServerMiddleware(routeTree));
1960
1997
  const startRouterCode = 'const __startRouter = function() {\n' +
1961
1998
  ' __updateComponents(__routeTree);\n' +
1962
1999
  ` const __router = createFileRouter(__routeTree${routerOpts});\n` +
@@ -1989,7 +2026,8 @@ async function buildMainBundle(routeTree, runtimeDir, codeSplit, mono, hmr, impo
1989
2026
  .join('') +
1990
2027
  'globalThis.__runtime_comps = __runtime_comps;\n' +
1991
2028
  'globalThis.__cleanup = __cleanup;\n' +
1992
- 'globalThis.__place = __place;\n\n';
2029
+ 'globalThis.__place = __place;\n' +
2030
+ 'globalThis.__reconcileRegion = __reconcileRegion;\n\n';
1993
2031
  const extraGlobals = [...(runtimeImportNames || [])]
1994
2032
  .filter(n => n && n !== 'default')
1995
2033
  .map(n => `globalThis.${n} = ${n};\n`)
@@ -2006,6 +2044,7 @@ async function buildMainBundle(routeTree, runtimeDir, codeSplit, mono, hmr, impo
2006
2044
  (sharedModuleBundle ? sharedModuleBundle + '\n' : '') +
2007
2045
  cleanupFn +
2008
2046
  placeFn +
2047
+ reconcileRegionFn +
2009
2048
  'globalThis.__components = __components;\n' +
2010
2049
  resolveNamesFn +
2011
2050
  updateComponentsFn +
@@ -2021,7 +2060,7 @@ async function buildMainBundle(routeTree, runtimeDir, codeSplit, mono, hmr, impo
2021
2060
  const hydratorAliasLines = mono?.hydratorAliasLines || [];
2022
2061
  const aliasCode = aliasLines.length > 0 ? aliasLines.join('\n') + '\n' : '';
2023
2062
  const hydratorAliasCode = hydratorAliasLines.length > 0 ? hydratorAliasLines.join('\n') + '\n' : '';
2024
- const routerOpts = buildRouterOpts({ routeDataCache });
2063
+ const routerOpts = buildRouterOpts({ routeDataCache }, hasServerMiddleware(routeTree));
2025
2064
  const code = preamble +
2026
2065
  'const __components = {};\n' +
2027
2066
  'const __hydrators = {};\n' +
@@ -2032,10 +2071,12 @@ async function buildMainBundle(routeTree, runtimeDir, codeSplit, mono, hmr, impo
2032
2071
  hydratorAliasCode +
2033
2072
  cleanupFn +
2034
2073
  placeFn +
2074
+ reconcileRegionFn +
2035
2075
  'globalThis.__components = __components;\n' +
2036
2076
  'globalThis.__runtime_comps = __runtime_comps;\n' +
2037
2077
  'globalThis.__cleanup = __cleanup;\n' +
2038
2078
  'globalThis.__place = __place;\n' +
2079
+ 'globalThis.__reconcileRegion = __reconcileRegion;\n' +
2039
2080
  allRuntimeImports.map(n => `globalThis.${n} = ${n};\n`).join('') +
2040
2081
  'function __resolveNames(nodes) {\n' +
2041
2082
  ' for (const n of nodes) {\n' +
@@ -1 +1 @@
1
- {"version":3,"file":"dev-server.d.ts","sourceRoot":"","sources":["../src/dev-server.ts"],"names":[],"mappings":"AAEA,OAAO,EAAgB,KAAK,MAAM,EAA6C,MAAM,WAAW,CAAC;AASjG,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,OAAO,CAAC;AAO7C,OAAO,KAAK,EAAa,gBAAgB,EAAwB,MAAM,yBAAyB,CAAC;AAgBjG;;;;;GAKG;AACH,MAAM,WAAW,WAAW;IAC1B,MAAM,EAAE,IAAI,GAAG,WAAW,GAAG,OAAO,GAAG,MAAM,CAAC;IAC9C,aAAa,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,KAAK,EAAE,eAAe,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAC;IACxD,QAAQ,EAAE,OAAO,CAAC;IAClB,cAAc,EAAE,MAAM,CAAC;IACvB;+EAC2E;IAC3E,gBAAgB,CAAC,EAAE,OAAO,CAAC;CAC5B;AAED,mEAAmE;AACnE,wBAAgB,kBAAkB,IAAI,WAAW,CAEhD;AAwDD,MAAM,WAAW,gBAAgB;IAC/B,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAChC,IAAI,EAAE,MAAM,CAAC;IACb,mHAAmH;IACnH,QAAQ,CAAC,EAAE,MAAM,GAAG,QAAQ,CAAC;IAC7B;;8BAE0B;IAC1B,MAAM,CAAC,EAAE,aAAa,CAAC,MAAM,CAAC,CAAC;CAChC;AAED,MAAM,WAAW,uBAAuB;IACtC,IAAI,EAAE,UAAU,GAAG,YAAY,GAAG,SAAS,GAAG,WAAW,GAAG,QAAQ,CAAC;IACrE,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,wBAAwB;IACvC,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;IAChB,iBAAiB,EAAE,MAAM,EAAE,CAAC;IAC5B,WAAW,CAAC,EAAE,MAAM,WAAW,CAAC;IAChC,8EAA8E;IAC9E,cAAc,CAAC,EAAE,CAAC,KAAK,EAAE,uBAAuB,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CAC3E;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,uBAAuB,CAAC,IAAI,EAAE,wBAAwB,GAAG;IACvE,KAAK,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,OAAO,EAAE,MAAM,CAAC,EAAE,MAAM,KAAK,OAAO,CAAC,gBAAgB,GAAG,IAAI,CAAC,CAAC;CAChH,CAUA;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,kBAAkB,IAAI,MAAM,CAa3C;AAED;;;;GAIG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAQrD;AAUD,wBAAsB,cAAc,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,gBAAgB,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CA4sBvG"}
1
+ {"version":3,"file":"dev-server.d.ts","sourceRoot":"","sources":["../src/dev-server.ts"],"names":[],"mappings":"AAEA,OAAO,EAAgB,KAAK,MAAM,EAA6C,MAAM,WAAW,CAAC;AASjG,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,OAAO,CAAC;AAO7C,OAAO,KAAK,EAAa,gBAAgB,EAAwB,MAAM,yBAAyB,CAAC;AAgBjG;;;;;GAKG;AACH,MAAM,WAAW,WAAW;IAC1B,MAAM,EAAE,IAAI,GAAG,WAAW,GAAG,OAAO,GAAG,MAAM,CAAC;IAC9C,aAAa,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,KAAK,EAAE,eAAe,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAC;IACxD,QAAQ,EAAE,OAAO,CAAC;IAClB,cAAc,EAAE,MAAM,CAAC;IACvB;+EAC2E;IAC3E,gBAAgB,CAAC,EAAE,OAAO,CAAC;CAC5B;AAED,mEAAmE;AACnE,wBAAgB,kBAAkB,IAAI,WAAW,CAEhD;AAwDD,MAAM,WAAW,gBAAgB;IAC/B,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAChC,IAAI,EAAE,MAAM,CAAC;IACb,mHAAmH;IACnH,QAAQ,CAAC,EAAE,MAAM,GAAG,QAAQ,CAAC;IAC7B;;8BAE0B;IAC1B,MAAM,CAAC,EAAE,aAAa,CAAC,MAAM,CAAC,CAAC;CAChC;AAED,MAAM,WAAW,uBAAuB;IACtC,IAAI,EAAE,UAAU,GAAG,YAAY,GAAG,SAAS,GAAG,WAAW,GAAG,QAAQ,CAAC;IACrE,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,wBAAwB;IACvC,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;IAChB,iBAAiB,EAAE,MAAM,EAAE,CAAC;IAC5B,WAAW,CAAC,EAAE,MAAM,WAAW,CAAC;IAChC,8EAA8E;IAC9E,cAAc,CAAC,EAAE,CAAC,KAAK,EAAE,uBAAuB,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CAC3E;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,uBAAuB,CAAC,IAAI,EAAE,wBAAwB,GAAG;IACvE,KAAK,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,OAAO,EAAE,MAAM,CAAC,EAAE,MAAM,KAAK,OAAO,CAAC,gBAAgB,GAAG,IAAI,CAAC,CAAC;CAChH,CAUA;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,kBAAkB,IAAI,MAAM,CAa3C;AAED;;;;GAIG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAgBrD;AAUD,wBAAsB,cAAc,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,gBAAgB,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CA4sBvG"}
@@ -150,7 +150,14 @@ export function devBootstrapScript() {
150
150
  export function injectDevScripts(html) {
151
151
  if (!html.includes('</body>'))
152
152
  return html;
153
- const scripts = '\t<script type="module" src="/_vesk/client.js"></script>\n' +
153
+ // SSR already emits its own client script (hmr.ts bakes
154
+ // `clientScriptUrl: "/_vesk/static/client.js"`). Injecting a second copy at
155
+ // `/_vesk/client.js` loads the runtime module twice, giving the page two
156
+ // routers and two sets of event listeners — every SPA click then runs
157
+ // navigate() twice and pushes a duplicate history entry. Only add the
158
+ // fallback client script for HTML that has none.
159
+ const hasClientScript = html.includes('/_vesk/static/client.js') || html.includes('/_vesk/client.js');
160
+ const scripts = (hasClientScript ? '' : '\t<script type="module" src="/_vesk/client.js"></script>\n') +
154
161
  '\t<script type="module" src="/_vesk/hmr.js"></script>\n' +
155
162
  devBootstrapScript() +
156
163
  '\n';
@@ -1 +1 @@
1
- {"version":3,"file":"middleware.d.ts","sourceRoot":"","sources":["../src/middleware.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,mBAAmB,EAA2B,MAAM,yBAAyB,CAAC;AAS5F,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,mBAAmB,EAAE,EAAE,OAAO,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAmFhG;AAED,wBAAgB,qBAAqB,CAAC,aAAa,EAAE,MAAM,EAAE,GAAG,MAAM,GAAG,IAAI,CAiF5E"}
1
+ {"version":3,"file":"middleware.d.ts","sourceRoot":"","sources":["../src/middleware.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,mBAAmB,EAA2B,MAAM,yBAAyB,CAAC;AAS5F,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,mBAAmB,EAAE,EAAE,OAAO,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAuFhG;AAED,wBAAgB,qBAAqB,CAAC,aAAa,EAAE,MAAM,EAAE,GAAG,MAAM,GAAG,IAAI,CAqF5E"}
@@ -4,18 +4,22 @@ function extractMiddlewareBody(src) {
4
4
  const parts = extractMiddlewareParts(src);
5
5
  if (!parts)
6
6
  return null;
7
- return { params: parts.params, body: parts.body };
7
+ return { params: parts.params, body: parts.body, imports: parts.imports };
8
8
  }
9
9
  export function compileMiddleware(mwChain, _appDir) {
10
10
  if (mwChain.length === 0)
11
11
  return null;
12
12
  const parts = [];
13
+ const imports = [];
13
14
  for (let i = 0; i < mwChain.length; i++) {
14
15
  const { sourcePath } = mwChain[i];
15
16
  const src = readFileSync(sourcePath, 'utf-8');
16
17
  const extracted = extractMiddlewareBody(src);
17
18
  if (!extracted)
18
19
  continue;
20
+ for (const line of extracted.imports)
21
+ if (!imports.includes(line))
22
+ imports.push(line);
19
23
  parts.push(`async function mw_${i}(${extracted.params}) {\n${extracted.body}\n}`);
20
24
  }
21
25
  if (parts.length === 0)
@@ -23,6 +27,8 @@ export function compileMiddleware(mwChain, _appDir) {
23
27
  const code = [
24
28
  '// Auto-generated middleware chain — do not edit',
25
29
  '',
30
+ ...imports,
31
+ ...(imports.length ? [''] : []),
26
32
  parts.join('\n\n'),
27
33
  '',
28
34
  `const chain = [${parts.map((_, i) => `mw_${i}`).join(', ')}];`,
@@ -93,10 +99,14 @@ export function compileMiddlewareCode(mwSourceTexts) {
93
99
  if (!mwSourceTexts || mwSourceTexts.length === 0)
94
100
  return null;
95
101
  const parts = [];
102
+ const imports = [];
96
103
  for (let i = 0; i < mwSourceTexts.length; i++) {
97
104
  const extracted = extractMiddlewareBody(mwSourceTexts[i]);
98
105
  if (!extracted)
99
106
  continue;
107
+ for (const line of extracted.imports)
108
+ if (!imports.includes(line))
109
+ imports.push(line);
100
110
  parts.push(`async function mw_${i}(${extracted.params}) {\n${extracted.body}\n}`);
101
111
  }
102
112
  if (parts.length === 0)
@@ -104,6 +114,8 @@ export function compileMiddlewareCode(mwSourceTexts) {
104
114
  const code = [
105
115
  '// ── Middleware chain (inline) ──',
106
116
  '',
117
+ ...imports,
118
+ ...(imports.length ? [''] : []),
107
119
  parts.join('\n\n'),
108
120
  '',
109
121
  `const __mwChain = [${parts.map((_, i) => `mw_${i}`).join(', ')}];`,
@@ -1 +1 @@
1
- {"version":3,"file":"prod-server.d.ts","sourceRoot":"","sources":["../src/prod-server.ts"],"names":[],"mappings":"AAEA,OAAO,EAAgB,KAAK,eAAe,EAAuB,KAAK,MAAM,EAAE,MAAM,WAAW,CAAC;AAejG,OAAO,KAAK,EAAkB,iBAAiB,EAAE,iBAAiB,EAAE,MAAM,yBAAyB,CAAC;AAIpG,YAAY,EAAE,iBAAiB,EAAE,iBAAiB,EAAE,CAAC;AAuIrD,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,eAAe,EAAE,IAAI,EAAE,MAAM,EAAE,UAAU,EAAE,OAAO,GAAG,GAAG,CAY9F;AAED,wBAAsB,eAAe,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,iBAAiB,GAAG,OAAO,CAAC,MAAM,CAAC,CAmpBlG"}
1
+ {"version":3,"file":"prod-server.d.ts","sourceRoot":"","sources":["../src/prod-server.ts"],"names":[],"mappings":"AAEA,OAAO,EAAgB,KAAK,eAAe,EAAuB,KAAK,MAAM,EAAE,MAAM,WAAW,CAAC;AAejG,OAAO,KAAK,EAAkB,iBAAiB,EAAE,iBAAiB,EAAE,MAAM,yBAAyB,CAAC;AAIpG,YAAY,EAAE,iBAAiB,EAAE,iBAAiB,EAAE,CAAC;AAuIrD,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,eAAe,EAAE,IAAI,EAAE,MAAM,EAAE,UAAU,EAAE,OAAO,GAAG,GAAG,CAY9F;AAED,wBAAsB,eAAe,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,iBAAiB,GAAG,OAAO,CAAC,MAAM,CAAC,CA6rBlG"}
@@ -480,6 +480,37 @@ export async function startProdServer(outDir, options) {
480
480
  }
481
481
  }
482
482
  }
483
+ if (url.pathname === '/_vesk/middleware' && middlewareMod) {
484
+ const targetPath = url.searchParams.get('path') || '/';
485
+ const targetUrl = new URL(targetPath, url.origin);
486
+ const middlewareRequest = new Request(targetUrl.href, {
487
+ method: 'GET',
488
+ headers: req.headers,
489
+ });
490
+ const mwEndpointCtx = {
491
+ request: middlewareRequest,
492
+ params: {},
493
+ url: targetUrl,
494
+ locals: Object.assign({}, globalThis.__vesk_server_ctx || {}),
495
+ cookies: {},
496
+ set(key, value) { this.locals[key] = value; },
497
+ get(key) { return this.locals[key]; },
498
+ };
499
+ const mwResult = await middlewareMod.execute(mwEndpointCtx);
500
+ res.writeHead(200, {
501
+ 'Content-Type': 'application/json; charset=utf-8',
502
+ 'Cache-Control': 'no-store',
503
+ });
504
+ if (mwResult.response) {
505
+ const location = mwResult.response.headers.get('location') || mwResult.response.headers.get('Location');
506
+ if (location) {
507
+ res.end(JSON.stringify({ redirect: location }));
508
+ return;
509
+ }
510
+ }
511
+ res.end(JSON.stringify(mwResult.rewriteUrl ? { rewrite: mwResult.rewriteUrl } : { allow: true }));
512
+ return;
513
+ }
483
514
  let mwCtx;
484
515
  const serverStore = globalThis.__vesk_server_ctx;
485
516
  if (middlewareMod) {
@@ -526,221 +557,232 @@ export async function startProdServer(outDir, options) {
526
557
  }
527
558
  // Expose root middleware locals to API/SSR handlers (which seed their own
528
559
  // ctx.locals from this before running route-level middleware).
560
+ //
561
+ // Scoped to this request: an SSR render can `fetch()` its own API route,
562
+ // which re-enters this handler. Without restoring, that nested request's
563
+ // context (params: {}) stays installed and every later pass of the outer
564
+ // render reads the wrong params.
565
+ const __outerRequest = globalThis.__vesk_request;
529
566
  globalThis.__vesk_request = mwCtx;
530
- if (url.pathname.startsWith('/_vesk/action/')) {
531
- const actionId = url.pathname.replace('/_vesk/action/', '');
532
- const actionEntry = buildConfig.actions && buildConfig.actions.find(a => a.id === actionId);
533
- if (!actionEntry) {
534
- res.writeHead(404, { 'Content-Type': 'application/json' });
535
- res.end(JSON.stringify({ ok: false, error: 'Action not found' }));
536
- return;
537
- }
538
- const mod = await loadFunction(actionEntry.function);
539
- if (!mod || !mod.handleAction) {
540
- res.writeHead(404, { 'Content-Type': 'application/json' });
541
- res.end(JSON.stringify({ ok: false, error: 'Action not found' }));
567
+ try {
568
+ if (url.pathname.startsWith('/_vesk/action/')) {
569
+ const actionId = url.pathname.replace('/_vesk/action/', '');
570
+ const actionEntry = buildConfig.actions && buildConfig.actions.find(a => a.id === actionId);
571
+ if (!actionEntry) {
572
+ res.writeHead(404, { 'Content-Type': 'application/json' });
573
+ res.end(JSON.stringify({ ok: false, error: 'Action not found' }));
574
+ return;
575
+ }
576
+ const mod = await loadFunction(actionEntry.function);
577
+ if (!mod || !mod.handleAction) {
578
+ res.writeHead(404, { 'Content-Type': 'application/json' });
579
+ res.end(JSON.stringify({ ok: false, error: 'Action not found' }));
580
+ return;
581
+ }
582
+ try {
583
+ const webRequest = makeWebRequest(req, url.href, maxBodyBytes);
584
+ const handleAction = mod.handleAction.bind(mod);
585
+ const response = await withSecurityScope(() => handleAction(webRequest, actionId));
586
+ await deliverResponse(res, response);
587
+ }
588
+ catch (e) {
589
+ const status = errorStatus(e, 500);
590
+ const message = status === 500 && process.env.NODE_ENV === 'production'
591
+ ? 'Internal Server Error'
592
+ : e instanceof Error ? e.message : String(e);
593
+ res.writeHead(status, { 'Content-Type': 'application/json' });
594
+ res.end(JSON.stringify({ ok: false, error: message }));
595
+ }
542
596
  return;
543
597
  }
544
- try {
545
- const webRequest = makeWebRequest(req, url.href, maxBodyBytes);
546
- const handleAction = mod.handleAction.bind(mod);
547
- const response = await withSecurityScope(() => handleAction(webRequest, actionId));
548
- await deliverResponse(res, response);
549
- }
550
- catch (e) {
551
- const status = errorStatus(e, 500);
552
- const message = status === 500 && process.env.NODE_ENV === 'production'
553
- ? 'Internal Server Error'
554
- : e instanceof Error ? e.message : String(e);
555
- res.writeHead(status, { 'Content-Type': 'application/json' });
556
- res.end(JSON.stringify({ ok: false, error: message }));
557
- }
558
- return;
559
- }
560
- if (url.pathname.startsWith('/api')) {
561
- for (const route of buildConfig.routes) {
562
- if (route.type === 'api') {
563
- const params = matchPath(route.path, url.pathname);
564
- if (params) {
565
- const mod = await loadFunction(route.function);
566
- if (!mod) {
567
- // The route matched, so a 404 would be a lie: the handler is
568
- // there and could not be loaded.
569
- if (functionFailure(route.function)) {
570
- res.writeHead(500, { 'Content-Type': 'application/json' });
571
- res.end(JSON.stringify({ error: 'Internal Server Error' }));
598
+ if (url.pathname.startsWith('/api')) {
599
+ for (const route of buildConfig.routes) {
600
+ if (route.type === 'api') {
601
+ const params = matchPath(route.path, url.pathname);
602
+ if (params) {
603
+ const mod = await loadFunction(route.function);
604
+ if (!mod) {
605
+ // The route matched, so a 404 would be a lie: the handler is
606
+ // there and could not be loaded.
607
+ if (functionFailure(route.function)) {
608
+ res.writeHead(500, { 'Content-Type': 'application/json' });
609
+ res.end(JSON.stringify({ error: 'Internal Server Error' }));
610
+ return;
611
+ }
612
+ continue;
613
+ }
614
+ try {
615
+ const webRequest = makeWebRequest(req, url.href, maxBodyBytes);
616
+ const response = await withSecurityScope(() => mod.handle(webRequest));
617
+ await deliverResponse(res, response);
618
+ return;
619
+ }
620
+ catch (e) {
621
+ const status = errorStatus(e, 500);
622
+ res.writeHead(status, { 'Content-Type': 'application/json' });
623
+ res.end(JSON.stringify({ error: status === 500 ? 'Internal Server Error' : e instanceof Error ? e.message : String(e) }));
572
624
  return;
573
625
  }
574
- continue;
575
- }
576
- try {
577
- const webRequest = makeWebRequest(req, url.href, maxBodyBytes);
578
- const response = await withSecurityScope(() => mod.handle(webRequest));
579
- await deliverResponse(res, response);
580
- return;
581
- }
582
- catch (e) {
583
- const status = errorStatus(e, 500);
584
- res.writeHead(status, { 'Content-Type': 'application/json' });
585
- res.end(JSON.stringify({ error: status === 500 ? 'Internal Server Error' : e instanceof Error ? e.message : String(e) }));
586
- return;
587
626
  }
588
627
  }
589
628
  }
590
629
  }
591
- }
592
- const appDir = resolve(projectDir, 'app');
593
- const pageCssUrls = resolveCssUrls({
594
- enabled: hasBuiltGlobalCss(outDir),
595
- });
596
- /**
597
- * Render the boundary that owns `url.pathname`.
598
- *
599
- * Deepest-match over the URL's directories, not a hard read of the root
600
- * file: `app/admin/not-found.vsk` is the boundary for `/admin/*`, and the
601
- * root one is the fallback. The generated SSR function does the same walk at
602
- * build time (`resolveNotFoundFile`) and renders the boundary itself inside
603
- * the layout stack; this path is what remains for an unmatched URL, for a
604
- * route with no layout (which streams, so its status is already committed),
605
- * and for an error thrown outside a page's own try block.
606
- */
607
- const boundaryCache = new Map();
608
- async function renderBoundaryFile(file, pathname, props) {
609
- const boundaryPath = resolveBoundaryFileForUrl(pathname, appDir, file);
610
- if (!boundaryPath)
611
- return null;
612
- const key = `${file}:${boundaryPath}`;
613
- let html = boundaryCache.has(key) ? boundaryCache.get(key) : null;
614
- if (html === null) {
615
- try {
616
- const runtimePath = resolve(outDir, 'server', 'runtime.js');
617
- const { renderFullPage, storeDataScriptGlobal } = await import(runtimePath);
618
- const src = readFileSync(boundaryPath, 'utf-8');
619
- const compName = resolveComponentName(src) || (file === 'error.vsk' ? 'Error' : 'NotFound');
620
- html = await renderFullPage(src, compName, props, new Map(), { hydrate: true, cssUrls: pageCssUrls, clientScriptUrl: buildConfig?.assets?.client?.url || '/_vesk/static/client.js', clientScriptIntegrity: buildConfig?.assets?.client?.integrity, security: securityConfig?.security || {}, externalDataScript: storeDataScriptGlobal, sourcePath: boundaryPath, headExtra: bakedHeadExtra });
621
- }
622
- catch {
630
+ const appDir = resolve(projectDir, 'app');
631
+ const pageCssUrls = resolveCssUrls({
632
+ enabled: hasBuiltGlobalCss(outDir),
633
+ });
634
+ /**
635
+ * Render the boundary that owns `url.pathname`.
636
+ *
637
+ * Deepest-match over the URL's directories, not a hard read of the root
638
+ * file: `app/admin/not-found.vsk` is the boundary for `/admin/*`, and the
639
+ * root one is the fallback. The generated SSR function does the same walk at
640
+ * build time (`resolveNotFoundFile`) and renders the boundary itself inside
641
+ * the layout stack; this path is what remains for an unmatched URL, for a
642
+ * route with no layout (which streams, so its status is already committed),
643
+ * and for an error thrown outside a page's own try block.
644
+ */
645
+ const boundaryCache = new Map();
646
+ async function renderBoundaryFile(file, pathname, props) {
647
+ const boundaryPath = resolveBoundaryFileForUrl(pathname, appDir, file);
648
+ if (!boundaryPath)
623
649
  return null;
650
+ const key = `${file}:${boundaryPath}`;
651
+ let html = boundaryCache.has(key) ? boundaryCache.get(key) : null;
652
+ if (html === null) {
653
+ try {
654
+ const runtimePath = resolve(outDir, 'server', 'runtime.js');
655
+ const { renderFullPage, storeDataScriptGlobal } = await import(runtimePath);
656
+ const src = readFileSync(boundaryPath, 'utf-8');
657
+ const compName = resolveComponentName(src) || (file === 'error.vsk' ? 'Error' : 'NotFound');
658
+ html = await renderFullPage(src, compName, props, new Map(), { hydrate: true, cssUrls: pageCssUrls, clientScriptUrl: buildConfig?.assets?.client?.url || '/_vesk/static/client.js', clientScriptIntegrity: buildConfig?.assets?.client?.integrity, security: securityConfig?.security || {}, externalDataScript: storeDataScriptGlobal, sourcePath: boundaryPath, headExtra: bakedHeadExtra });
659
+ }
660
+ catch {
661
+ return null;
662
+ }
624
663
  }
664
+ return html;
625
665
  }
626
- return html;
627
- }
628
- let notFoundHtml = null;
629
- {
630
- const rendered = await renderBoundaryFile('not-found.vsk', url.pathname, { params: {}, url: url.pathname });
631
- notFoundHtml = rendered;
632
- }
633
- for (const route of buildConfig.routes) {
634
- if (route.type === 'ssr') {
635
- const params = matchPath(route.path, url.pathname);
636
- if (params) {
637
- const mod = await loadFunction(route.function);
638
- if (mod) {
639
- const webRequest = makeWebRequest(req, url.href, maxBodyBytes);
640
- // During SSR, fetch() with a relative URL (e.g. '/api/student') throws
641
- // because undici requires an absolute URL and there is no browser
642
- // origin to resolve against. Wrap globalThis.fetch for the duration
643
- // of the SSR render so relative API calls resolve against the
644
- // request's origin — matching what the client does automatically.
645
- const origin = new URL(url.href).origin;
646
- const origFetch = globalThis.fetch;
647
- globalThis.fetch = async (input, init) => {
648
- if (typeof input === 'string' && input.startsWith('/')) {
649
- input = origin + input;
650
- }
651
- return origFetch(input, init);
652
- };
653
- try {
654
- let cachedResult = null;
655
- if (route.revalidate && route.revalidate > 0) {
656
- const { pageIsr } = await import('@vesk/runtime/src/index-server');
657
- cachedResult = await pageIsr(url.pathname, async () => {
658
- const response = await withSecurityScope(() => mod.handle(webRequest));
659
- return { html: await response.text(), headers: Object.fromEntries(response.headers) };
660
- }, { revalidate: route.revalidate, tags: route.tags || [] });
661
- }
662
- if (cachedResult) {
663
- res.writeHead(200, cachedResult.headers || { 'Content-Type': 'text/html' });
664
- res.end(cachedResult.html);
665
- return;
666
- }
667
- const response = await withSecurityScope(() => mod.handle(webRequest));
668
- const headers = Object.fromEntries(response.headers);
669
- if (!headers['content-type'] && !headers['Content-Type'])
670
- headers['Content-Type'] = 'text/html';
671
- if (response.body && typeof response.body.getReader === 'function') {
672
- res.writeHead(response.status, headers);
673
- const reader = response.body.getReader();
674
- const pump = () => {
675
- reader.read().then(({ done, value }) => {
676
- if (done) {
677
- res.end();
678
- return;
679
- }
680
- res.write(value);
681
- pump();
682
- }).catch(() => res.end());
683
- };
684
- pump();
685
- }
686
- else {
687
- const body = await response.text();
688
- res.writeHead(response.status, headers);
689
- res.end(body);
690
- }
691
- return;
692
- }
693
- catch (e) {
694
- const err = e instanceof Error ? e : new Error(String(e));
695
- if (err.name === 'Redirect') {
696
- const redirect = err;
697
- res.writeHead(Number(redirect.status) || 302, { Location: typeof redirect.url === 'string' ? redirect.url : '/' });
698
- res.end();
666
+ let notFoundHtml = null;
667
+ {
668
+ const rendered = await renderBoundaryFile('not-found.vsk', url.pathname, { params: {}, url: url.pathname });
669
+ notFoundHtml = rendered;
670
+ }
671
+ for (const route of buildConfig.routes) {
672
+ if (route.type === 'ssr') {
673
+ const params = matchPath(route.path, url.pathname);
674
+ if (params) {
675
+ const mod = await loadFunction(route.function);
676
+ if (mod) {
677
+ const webRequest = makeWebRequest(req, url.href, maxBodyBytes);
678
+ // During SSR, fetch() with a relative URL (e.g. '/api/student') throws
679
+ // because undici requires an absolute URL and there is no browser
680
+ // origin to resolve against. Wrap globalThis.fetch for the duration
681
+ // of the SSR render so relative API calls resolve against the
682
+ // request's origin — matching what the client does automatically.
683
+ const origin = new URL(url.href).origin;
684
+ const origFetch = globalThis.fetch;
685
+ globalThis.fetch = async (input, init) => {
686
+ if (typeof input === 'string' && input.startsWith('/')) {
687
+ input = origin + input;
688
+ }
689
+ return origFetch(input, init);
690
+ };
691
+ try {
692
+ let cachedResult = null;
693
+ if (route.revalidate && route.revalidate > 0) {
694
+ const { pageIsr } = await import('@vesk/runtime/src/index-server');
695
+ cachedResult = await pageIsr(url.pathname, async () => {
696
+ const response = await withSecurityScope(() => mod.handle(webRequest));
697
+ return { html: await response.text(), headers: Object.fromEntries(response.headers) };
698
+ }, { revalidate: route.revalidate, tags: route.tags || [] });
699
+ }
700
+ if (cachedResult) {
701
+ res.writeHead(200, cachedResult.headers || { 'Content-Type': 'text/html' });
702
+ res.end(cachedResult.html);
703
+ return;
704
+ }
705
+ const response = await withSecurityScope(() => mod.handle(webRequest));
706
+ const headers = Object.fromEntries(response.headers);
707
+ if (!headers['content-type'] && !headers['Content-Type'])
708
+ headers['Content-Type'] = 'text/html';
709
+ if (response.body && typeof response.body.getReader === 'function') {
710
+ res.writeHead(response.status, headers);
711
+ const reader = response.body.getReader();
712
+ const pump = () => {
713
+ reader.read().then(({ done, value }) => {
714
+ if (done) {
715
+ res.end();
716
+ return;
717
+ }
718
+ res.write(value);
719
+ pump();
720
+ }).catch(() => res.end());
721
+ };
722
+ pump();
723
+ }
724
+ else {
725
+ const body = await response.text();
726
+ res.writeHead(response.status, headers);
727
+ res.end(body);
728
+ }
699
729
  return;
700
730
  }
701
- if (err.name === 'NotFoundError') {
702
- res.writeHead(404, { 'Content-Type': 'text/html' });
703
- res.end(notFoundHtml || renderDefaultNotFoundPage({ url: url.pathname, headExtra: bakedHeadExtra }));
731
+ catch (e) {
732
+ const err = e instanceof Error ? e : new Error(String(e));
733
+ if (err.name === 'Redirect') {
734
+ const redirect = err;
735
+ res.writeHead(Number(redirect.status) || 302, { Location: typeof redirect.url === 'string' ? redirect.url : '/' });
736
+ res.end();
737
+ return;
738
+ }
739
+ if (err.name === 'NotFoundError') {
740
+ res.writeHead(404, { 'Content-Type': 'text/html' });
741
+ res.end(notFoundHtml || renderDefaultNotFoundPage({ url: url.pathname, headExtra: bakedHeadExtra }));
742
+ return;
743
+ }
744
+ console.error('vesk ssr error:', err.message);
745
+ // Two seams, both best-effort: a plugin's `onError` (dev-style
746
+ // plugin objects don't exist here) and `_events.ts`'s, which IS
747
+ // baked into the production build.
748
+ await reportServerError(err, {
749
+ url: url.pathname + url.search,
750
+ routePath: route.path,
751
+ target: 'node',
752
+ status: 500,
753
+ buildConfig,
754
+ });
755
+ if (eventsMod) {
756
+ await executeError(err, { url: url.href, routePath: route.path, target: 'node', status: 500 }, eventsMod);
757
+ }
758
+ // Same nearest-boundary walk as not-found above. This is the last resort for a
759
+ // throw the generated function could not absorb — a layout render
760
+ // failure, or the error boundary itself failing — and it must pick
761
+ // the boundary that owns the URL, not the root one.
762
+ const expose = process.env.NODE_ENV !== 'production';
763
+ const errorHtml = await renderBoundaryFile('error.vsk', url.pathname, {
764
+ error: expose ? err.message : 'Internal Server Error',
765
+ stack: expose ? err.stack : '',
766
+ statusCode: 500,
767
+ url: url.pathname,
768
+ });
769
+ res.writeHead(500, { 'Content-Type': 'text/html' });
770
+ res.end(errorHtml || renderDefaultErrorPage({ message: process.env.NODE_ENV !== 'production' ? err.message : undefined, url: url.pathname, headExtra: bakedHeadExtra }));
704
771
  return;
705
772
  }
706
- console.error('vesk ssr error:', err.message);
707
- // Two seams, both best-effort: a plugin's `onError` (dev-style
708
- // plugin objects don't exist here) and `_events.ts`'s, which IS
709
- // baked into the production build.
710
- await reportServerError(err, {
711
- url: url.pathname + url.search,
712
- routePath: route.path,
713
- target: 'node',
714
- status: 500,
715
- buildConfig,
716
- });
717
- if (eventsMod) {
718
- await executeError(err, { url: url.href, routePath: route.path, target: 'node', status: 500 }, eventsMod);
773
+ finally {
774
+ globalThis.fetch = origFetch;
719
775
  }
720
- // Same nearest-boundary walk as not-found above. This is the last resort for a
721
- // throw the generated function could not absorb — a layout render
722
- // failure, or the error boundary itself failing — and it must pick
723
- // the boundary that owns the URL, not the root one.
724
- const expose = process.env.NODE_ENV !== 'production';
725
- const errorHtml = await renderBoundaryFile('error.vsk', url.pathname, {
726
- error: expose ? err.message : 'Internal Server Error',
727
- stack: expose ? err.stack : '',
728
- statusCode: 500,
729
- url: url.pathname,
730
- });
731
- res.writeHead(500, { 'Content-Type': 'text/html' });
732
- res.end(errorHtml || renderDefaultErrorPage({ message: process.env.NODE_ENV !== 'production' ? err.message : undefined, url: url.pathname, headExtra: bakedHeadExtra }));
733
- return;
734
- }
735
- finally {
736
- globalThis.fetch = origFetch;
737
776
  }
738
777
  }
739
778
  }
740
779
  }
780
+ res.writeHead(404, { 'Content-Type': 'text/html' });
781
+ res.end(notFoundHtml || renderDefaultNotFoundPage({ url: url.pathname, headExtra: bakedHeadExtra }));
782
+ }
783
+ finally {
784
+ globalThis.__vesk_request = __outerRequest;
741
785
  }
742
- res.writeHead(404, { 'Content-Type': 'text/html' });
743
- res.end(notFoundHtml || renderDefaultNotFoundPage({ url: url.pathname, headExtra: bakedHeadExtra }));
744
786
  });
745
787
  try {
746
788
  await eventsMod?.executeStart?.({ server, port, host });
@@ -1 +1 @@
1
- {"version":3,"file":"ssr-function.d.ts","sourceRoot":"","sources":["../src/ssr-function.ts"],"names":[],"mappings":"AAKA,OAAO,EAAE,cAAc,EAAE,MAAM,mCAAmC,CAAC;AACnE,OAAO,KAAK,EAAE,SAAS,EAAkB,kBAAkB,EAAE,MAAM,yBAAyB,CAAC;AAE7F;;;;;;;;GAQG;AACH,wBAAgB,mBAAmB,CAAC,SAAS,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAQlG;AAED,wBAAgB,gBAAgB,CAAC,SAAS,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAEjF;AAED,wBAAgB,mBAAmB,CAAC,SAAS,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAEpF;AAED;;;;;;;GAOG;AACH,wBAAgB,yBAAyB,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAQvG;AAgED;;;2EAG2E;AAC3E,wBAAgB,qBAAqB,IAAI,MAAM,CA4C9C;AAED,wBAAgB,mBAAmB,CACjC,SAAS,EAAE,SAAS,EACpB,MAAM,EAAE,MAAM,EACd,MAAM,EAAE,MAAM,EACd,YAAY,CAAC,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,EAClC,OAAO,CAAC,EAAE,kBAAkB,EAC5B,QAAQ,CAAC,EAAE,cAAc,GACxB;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAuftD"}
1
+ {"version":3,"file":"ssr-function.d.ts","sourceRoot":"","sources":["../src/ssr-function.ts"],"names":[],"mappings":"AAKA,OAAO,EAAE,cAAc,EAAE,MAAM,mCAAmC,CAAC;AACnE,OAAO,KAAK,EAAE,SAAS,EAAkB,kBAAkB,EAAE,MAAM,yBAAyB,CAAC;AAE7F;;;;;;;;GAQG;AACH,wBAAgB,mBAAmB,CAAC,SAAS,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAQlG;AAED,wBAAgB,gBAAgB,CAAC,SAAS,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAEjF;AAED,wBAAgB,mBAAmB,CAAC,SAAS,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAEpF;AAED;;;;;;;GAOG;AACH,wBAAgB,yBAAyB,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAQvG;AAgED;;;2EAG2E;AAC3E,wBAAgB,qBAAqB,IAAI,MAAM,CA4C9C;AAED,wBAAgB,mBAAmB,CACjC,SAAS,EAAE,SAAS,EACpB,MAAM,EAAE,MAAM,EACd,MAAM,EAAE,MAAM,EACd,YAAY,CAAC,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,EAClC,OAAO,CAAC,EAAE,kBAAkB,EAC5B,QAAQ,CAAC,EAAE,cAAc,GACxB;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAsgBtD"}
@@ -428,7 +428,10 @@ export function generateSsrFunction(routeNode, appDir, outDir, componentMap, opt
428
428
  // its own request scope — otherwise its renderPage mints a token that is
429
429
  // still live when the full-page render starts and the two share a data slot.
430
430
  const dataNavWrap = (body) => [
431
- ' return withSsrStore(async () => {',
431
+ // Awaited for the same reason as the main render below: the caller restores
432
+ // `__vesk_request` in a finally, and that must happen only after the async
433
+ // data render (which reads params through useParams) has fully resolved.
434
+ ' return await withSsrStore(async () => {',
432
435
  ...body.map((l) => (l ? ' ' + l : l)),
433
436
  ' });',
434
437
  ];
@@ -456,7 +459,13 @@ export function generateSsrFunction(routeNode, appDir, outDir, componentMap, opt
456
459
  ...dataNavCleanup,
457
460
  ]),
458
461
  ' }',
459
- ' return __renderHtml(params, url.href);',
462
+ // Awaited so the surrounding try/finally keeps `__vesk_request` installed
463
+ // for the whole async render. Without the await the finally restores the
464
+ // previous context synchronously, while the async page body is still
465
+ // suspended on its SSR fetches; every render pass after the first `await`
466
+ // then reads an empty params object and `useParams()`-derived keys/URLs
467
+ // collapse (e.g. a verify page rendering its not-found branch).
468
+ ' return await __renderHtml(params, url.href);',
460
469
  ].join('\n');
461
470
  }
462
471
  else {
@@ -478,7 +487,13 @@ export function generateSsrFunction(routeNode, appDir, outDir, componentMap, opt
478
487
  ...dataNavCleanup,
479
488
  ]),
480
489
  ' }',
481
- ' return __renderHtml(params, url.href);',
490
+ // Awaited so the surrounding try/finally keeps `__vesk_request` installed
491
+ // for the whole async render. Without the await the finally restores the
492
+ // previous context synchronously, while the async page body is still
493
+ // suspended on its SSR fetches; every render pass after the first `await`
494
+ // then reads an empty params object and `useParams()`-derived keys/URLs
495
+ // collapse (e.g. a verify page rendering its not-found branch).
496
+ ' return await __renderHtml(params, url.href);',
482
497
  ].join('\n');
483
498
  }
484
499
  const hasMiddleware = !!middlewareCode;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vesk/adapter",
3
- "version": "0.2.94",
3
+ "version": "0.2.95",
4
4
  "type": "module",
5
5
  "description": "Vesk adapter — builds deployable .vesk/ output for Deno and Node serverless",
6
6
  "main": "./dist/index.js",
@@ -23,9 +23,9 @@
23
23
  "sharp": "^0.35.3"
24
24
  },
25
25
  "dependencies": {
26
- "@vesk/compiler": "^0.2.94",
27
- "@vesk/runtime": "^0.2.94",
28
- "@vesk/types": "^0.2.94"
26
+ "@vesk/compiler": "^0.2.95",
27
+ "@vesk/runtime": "^0.2.95",
28
+ "@vesk/types": "^0.2.95"
29
29
  },
30
30
  "devDependencies": {
31
31
  "@types/ws": "^8.18.1",