@vesk/adapter 0.2.93 → 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') {
@@ -1617,16 +1629,22 @@ export async function generateClientBundle(routeTree, appDir, componentMap, opti
1617
1629
  loader: { '.js': 'tsx' },
1618
1630
  legalComments: 'none',
1619
1631
  ...(appTsconfig ? { tsconfig: appTsconfig } : {}),
1620
- // Stub optional CJS deps (qrcode-generator via vesk-qr, canvas,
1621
- // @zxing/*) so esbuild never bundles their CJS source into the client
1622
- // bundle — that would emit a `__require` shim that throws in the
1623
- // browser. vesk-qr now lazy-loads them via dynamic import; stubbing
1624
- // keeps the bundle valid ESM without shipping dead code.
1632
+ // Stub only the OPTIONAL CJS deps (canvas for raster export, @zxing/*
1633
+ // for scan validation). They degrade gracefully and their absence must
1634
+ // not break the bundle.
1635
+ //
1636
+ // `qrcode-generator` must NOT be stubbed: it is the pure-JS encoder
1637
+ // every client-side QR (vesk-qr's dynamic `import("qrcode-generator")`)
1638
+ // runs on. Stubbing it to `export {}` makes `getQrcode()` resolve to an
1639
+ // empty object, so `CofoundrMatrix.generate()` throws
1640
+ // "QREncodingError: generator is not a function" during hydration and
1641
+ // the page falls back to the client error boundary. esbuild bundles it
1642
+ // as CJS fine; only genuinely Node-only/optional deps belong here.
1625
1643
  plugins: [
1626
1644
  {
1627
1645
  name: 'stub-browser-packages',
1628
1646
  setup(b) {
1629
- b.onResolve({ filter: /^(qrcode-generator|canvas|@zxing\/(browser|node))(\/|$)/ }, (args) => ({ path: args.path, namespace: 'browser-stub' }));
1647
+ b.onResolve({ filter: /^(canvas|@zxing\/(browser|node))(\/|$)/ }, (args) => ({ path: args.path, namespace: 'browser-stub' }));
1630
1648
  b.onLoad({ filter: /.*/, namespace: 'browser-stub' }, () => ({
1631
1649
  contents: 'export {}\n',
1632
1650
  loader: 'js',
@@ -1914,6 +1932,31 @@ async function buildMainBundle(routeTree, runtimeDir, codeSplit, mono, hmr, impo
1914
1932
  '\tfallback.appendChild(end);\n' +
1915
1933
  '\tfor (let i = 0; i < nodes.length; i++) fallback.insertBefore(nodes[i], end);\n' +
1916
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';
1917
1960
  const updateComponentsFn = 'function __updateComponents(nodes) {\n' +
1918
1961
  ' for (const n of nodes) {\n' +
1919
1962
  " if (n._pageName && __components[n._pageName]) n.page = __components[n._pageName];\n" +
@@ -1950,7 +1993,7 @@ async function buildMainBundle(routeTree, runtimeDir, codeSplit, mono, hmr, impo
1950
1993
  ' }\n' +
1951
1994
  ' }\n' +
1952
1995
  '}\n';
1953
- const routerOpts = buildRouterOpts({ routeDataCache });
1996
+ const routerOpts = buildRouterOpts({ routeDataCache }, hasServerMiddleware(routeTree));
1954
1997
  const startRouterCode = 'const __startRouter = function() {\n' +
1955
1998
  ' __updateComponents(__routeTree);\n' +
1956
1999
  ` const __router = createFileRouter(__routeTree${routerOpts});\n` +
@@ -1983,7 +2026,8 @@ async function buildMainBundle(routeTree, runtimeDir, codeSplit, mono, hmr, impo
1983
2026
  .join('') +
1984
2027
  'globalThis.__runtime_comps = __runtime_comps;\n' +
1985
2028
  'globalThis.__cleanup = __cleanup;\n' +
1986
- 'globalThis.__place = __place;\n\n';
2029
+ 'globalThis.__place = __place;\n' +
2030
+ 'globalThis.__reconcileRegion = __reconcileRegion;\n\n';
1987
2031
  const extraGlobals = [...(runtimeImportNames || [])]
1988
2032
  .filter(n => n && n !== 'default')
1989
2033
  .map(n => `globalThis.${n} = ${n};\n`)
@@ -2000,6 +2044,7 @@ async function buildMainBundle(routeTree, runtimeDir, codeSplit, mono, hmr, impo
2000
2044
  (sharedModuleBundle ? sharedModuleBundle + '\n' : '') +
2001
2045
  cleanupFn +
2002
2046
  placeFn +
2047
+ reconcileRegionFn +
2003
2048
  'globalThis.__components = __components;\n' +
2004
2049
  resolveNamesFn +
2005
2050
  updateComponentsFn +
@@ -2015,7 +2060,7 @@ async function buildMainBundle(routeTree, runtimeDir, codeSplit, mono, hmr, impo
2015
2060
  const hydratorAliasLines = mono?.hydratorAliasLines || [];
2016
2061
  const aliasCode = aliasLines.length > 0 ? aliasLines.join('\n') + '\n' : '';
2017
2062
  const hydratorAliasCode = hydratorAliasLines.length > 0 ? hydratorAliasLines.join('\n') + '\n' : '';
2018
- const routerOpts = buildRouterOpts({ routeDataCache });
2063
+ const routerOpts = buildRouterOpts({ routeDataCache }, hasServerMiddleware(routeTree));
2019
2064
  const code = preamble +
2020
2065
  'const __components = {};\n' +
2021
2066
  'const __hydrators = {};\n' +
@@ -2026,10 +2071,12 @@ async function buildMainBundle(routeTree, runtimeDir, codeSplit, mono, hmr, impo
2026
2071
  hydratorAliasCode +
2027
2072
  cleanupFn +
2028
2073
  placeFn +
2074
+ reconcileRegionFn +
2029
2075
  'globalThis.__components = __components;\n' +
2030
2076
  'globalThis.__runtime_comps = __runtime_comps;\n' +
2031
2077
  'globalThis.__cleanup = __cleanup;\n' +
2032
2078
  'globalThis.__place = __place;\n' +
2079
+ 'globalThis.__reconcileRegion = __reconcileRegion;\n' +
2033
2080
  allRuntimeImports.map(n => `globalThis.${n} = ${n};\n`).join('') +
2034
2081
  'function __resolveNames(nodes) {\n' +
2035
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(', ')}];`,
@@ -4,6 +4,22 @@ import type { Platform } from '@vesk/adapter/src/platform';
4
4
  export interface DeployContext extends PlatformBuildContext {
5
5
  prerenderedRoutes: SsgRouteResult[];
6
6
  }
7
+ /**
8
+ * Ship the installed TCP driver packages (and their transitive dependency
9
+ * closures) into a Node function's `node_modules`.
10
+ *
11
+ * A serverless function built by Vesk is otherwise self-contained — esbuild
12
+ * inlines everything. But a driver loaded through a variable `import(spec)`
13
+ * (e.g. `slintorm`'s `importDriver("pg")`) is invisible to esbuild *and* to the
14
+ * host's dependency tracer, and Build Output API functions do not install
15
+ * package.json dependencies. Without this the Postgres driver fails at runtime
16
+ * with `Cannot find module 'pg'` even though it is installed at build time.
17
+ *
18
+ * Only `dependencies`/`optionalDependencies` are followed (never dev deps), and
19
+ * nested `node_modules` are skipped because every dependency is flattened into
20
+ * the function's `node_modules` by name.
21
+ */
22
+ export declare function shipNodeDriverClosures(projectRoot: string, funcDir: string): void;
7
23
  /**
8
24
  * Universal deployment emit. The handler, the bundle and the static layout are
9
25
  * shared across every platform — only the ~10-line shell each runtime mandates
@@ -16,4 +32,16 @@ export interface DeployContext extends PlatformBuildContext {
16
32
  * not a symlink).
17
33
  */
18
34
  export declare function emitPlatformOutput(platform: Platform, ctx: DeployContext): Promise<string | null>;
35
+ /**
36
+ * Vercel Node entry source.
37
+ *
38
+ * The request URL is the base that SSR helpers resolve relative
39
+ * `useFetch('/api/…')` calls against. On Vercel, `req.headers.host` can be the
40
+ * deployment-specific host, which sits behind deployment protection, while
41
+ * `x-forwarded-host` carries the public alias the browser used. Preferring the
42
+ * forwarded host/proto keeps internal SSR fetches on the public origin; hitting
43
+ * the protected host follows a redirect to Vercel's auth HTML, which then fails
44
+ * `res.json()` with `Unexpected token '<'`.
45
+ */
46
+ export declare function vercelBootstrapSource(): string;
19
47
  //# sourceMappingURL=platform-deploy.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"platform-deploy.d.ts","sourceRoot":"","sources":["../src/platform-deploy.ts"],"names":[],"mappings":"AAEA,OAAO,EAAwD,KAAK,oBAAoB,EAAE,MAAM,oCAAoC,CAAC;AAErI,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,yBAAyB,CAAC;AAC9D,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,4BAA4B,CAAC;AAE3D,MAAM,WAAW,aAAc,SAAQ,oBAAoB;IACzD,iBAAiB,EAAE,cAAc,EAAE,CAAC;CACrC;AA0BD;;;;;;;;;;GAUG;AACH,wBAAsB,kBAAkB,CAAC,QAAQ,EAAE,QAAQ,EAAE,GAAG,EAAE,aAAa,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAmFvG"}
1
+ {"version":3,"file":"platform-deploy.d.ts","sourceRoot":"","sources":["../src/platform-deploy.ts"],"names":[],"mappings":"AAEA,OAAO,EAAwD,KAAK,oBAAoB,EAAE,MAAM,oCAAoC,CAAC;AAGrI,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,yBAAyB,CAAC;AAC9D,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,4BAA4B,CAAC;AAE3D,MAAM,WAAW,aAAc,SAAQ,oBAAoB;IACzD,iBAAiB,EAAE,cAAc,EAAE,CAAC;CACrC;AAkBD;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,sBAAsB,CAAC,WAAW,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,IAAI,CA0BjF;AA0BD;;;;;;;;;;GAUG;AACH,wBAAsB,kBAAkB,CAAC,QAAQ,EAAE,QAAQ,EAAE,GAAG,EAAE,aAAa,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAoFvG;AAqGD;;;;;;;;;;GAUG;AACH,wBAAgB,qBAAqB,IAAI,MAAM,CA2B9C"}
@@ -1,7 +1,71 @@
1
- import { writeFileSync, mkdirSync, rmSync, cpSync } from 'node:fs';
2
- import { resolve } from 'node:path';
1
+ import { writeFileSync, mkdirSync, rmSync, cpSync, readFileSync, existsSync } from 'node:fs';
2
+ import { resolve, dirname, relative } from 'node:path';
3
3
  import { generatePlatformHandlerSource, bundlePlatformHandler } from '@vesk/adapter/src/platform-handler';
4
4
  import { ensureCleanDir, writePlatformStatic, writePrerenderedStatic, listStaticDir, mimeFor } from '@vesk/adapter/src/platform-output';
5
+ import { tcpDriverPackages } from '@vesk/adapter/src/server-only';
6
+ /**
7
+ * Resolve an installed package directory the way Node does: walk up from the
8
+ * project root looking for `node_modules/<name>` (handles npm hoisting and
9
+ * monorepo workspaces). Returns null when the package is not installed.
10
+ */
11
+ function resolveInstalledPackage(projectRoot, name) {
12
+ let dir = projectRoot;
13
+ while (true) {
14
+ const candidate = resolve(dir, 'node_modules', name);
15
+ if (existsSync(resolve(candidate, 'package.json')))
16
+ return candidate;
17
+ const parent = dirname(dir);
18
+ if (parent === dir)
19
+ return null;
20
+ dir = parent;
21
+ }
22
+ }
23
+ /**
24
+ * Ship the installed TCP driver packages (and their transitive dependency
25
+ * closures) into a Node function's `node_modules`.
26
+ *
27
+ * A serverless function built by Vesk is otherwise self-contained — esbuild
28
+ * inlines everything. But a driver loaded through a variable `import(spec)`
29
+ * (e.g. `slintorm`'s `importDriver("pg")`) is invisible to esbuild *and* to the
30
+ * host's dependency tracer, and Build Output API functions do not install
31
+ * package.json dependencies. Without this the Postgres driver fails at runtime
32
+ * with `Cannot find module 'pg'` even though it is installed at build time.
33
+ *
34
+ * Only `dependencies`/`optionalDependencies` are followed (never dev deps), and
35
+ * nested `node_modules` are skipped because every dependency is flattened into
36
+ * the function's `node_modules` by name.
37
+ */
38
+ export function shipNodeDriverClosures(projectRoot, funcDir) {
39
+ const destModules = resolve(funcDir, 'node_modules');
40
+ const seen = new Set();
41
+ const queue = tcpDriverPackages();
42
+ while (queue.length > 0) {
43
+ const name = queue.shift();
44
+ if (seen.has(name))
45
+ continue;
46
+ seen.add(name);
47
+ const pkgDir = resolveInstalledPackage(projectRoot, name);
48
+ if (!pkgDir)
49
+ continue;
50
+ const dest = resolve(destModules, name);
51
+ mkdirSync(dirname(dest), { recursive: true });
52
+ cpSync(pkgDir, dest, {
53
+ recursive: true,
54
+ filter: (src) => !relative(pkgDir, src).split(/[\\/]/).includes('node_modules'),
55
+ });
56
+ let pkg = {};
57
+ try {
58
+ pkg = JSON.parse(readFileSync(resolve(pkgDir, 'package.json'), 'utf-8'));
59
+ }
60
+ catch { /* unreadable package.json: copy the package as-is */ }
61
+ const deps = {
62
+ ...(pkg.dependencies || {}),
63
+ ...(pkg.optionalDependencies || {}),
64
+ };
65
+ for (const dep of Object.keys(deps))
66
+ queue.push(dep);
67
+ }
68
+ }
5
69
  /**
6
70
  * Universal deployment emit. The handler, the bundle and the static layout are
7
71
  * shared across every platform — only the ~10-line shell each runtime mandates
@@ -60,6 +124,8 @@ export async function emitPlatformOutput(platform, ctx) {
60
124
  }
61
125
  handlerRel = `${shell.functionFile.dir}/${shell.functionFile.file}`;
62
126
  await bundlePlatformHandler({ entry, outfile: resolve(funcDir, shell.functionFile.file), nodeBuiltins: shell.nodeBuiltins });
127
+ if (shell.nodeBuiltins)
128
+ shipNodeDriverClosures(projectRoot, funcDir);
63
129
  }
64
130
  else {
65
131
  handlerRel = shell.outfile || 'index.js';
@@ -186,6 +252,45 @@ function serveEmbeddedStatic(request) {
186
252
  return null;
187
253
  }`;
188
254
  }
255
+ /**
256
+ * Vercel Node entry source.
257
+ *
258
+ * The request URL is the base that SSR helpers resolve relative
259
+ * `useFetch('/api/…')` calls against. On Vercel, `req.headers.host` can be the
260
+ * deployment-specific host, which sits behind deployment protection, while
261
+ * `x-forwarded-host` carries the public alias the browser used. Preferring the
262
+ * forwarded host/proto keeps internal SSR fetches on the public origin; hitting
263
+ * the protected host follows a redirect to Vercel's auth HTML, which then fails
264
+ * `res.json()` with `Unexpected token '<'`.
265
+ */
266
+ export function vercelBootstrapSource() {
267
+ return [
268
+ 'export default async function veskNodeEntry(req, res) {',
269
+ " const __fwdHost = String(req.headers['x-forwarded-host'] || '').split(',')[0].trim();",
270
+ " const __host = __fwdHost || req.headers.host || 'localhost';",
271
+ " const __fwdProto = String(req.headers['x-forwarded-proto'] || '').split(',')[0].trim();",
272
+ " const __local = __host === 'localhost' || __host.startsWith('127.') || __host === '[::1]';",
273
+ " const __proto = __fwdProto || (__local ? 'http' : 'https');",
274
+ ' const url = new URL(req.url, `${__proto}://${__host}`);',
275
+ " const method = req.method || 'GET';",
276
+ ' let body;',
277
+ " if (method !== 'GET' && method !== 'HEAD') {",
278
+ ' body = await new Promise((resolveBody, reject) => {',
279
+ ' const chunks = [];',
280
+ " req.on('data', (c) => chunks.push(c));",
281
+ ' req.on("error", reject);',
282
+ ' req.on("end", () => resolveBody(Buffer.concat(chunks)));',
283
+ ' });',
284
+ ' }',
285
+ ' const headers = {};',
286
+ ' for (const key in req.headers) headers[key] = req.headers[key];',
287
+ ' const request = new Request(url, { method, headers, body });',
288
+ ' const response = await handleRequest(request);',
289
+ ' res.writeHead(response.status, Object.fromEntries(response.headers));',
290
+ ' res.end(await response.text());',
291
+ '}',
292
+ ].join('\n');
293
+ }
189
294
  function shellFor(platform) {
190
295
  switch (platform) {
191
296
  case 'vercel':
@@ -206,27 +311,7 @@ function shellFor(platform) {
206
311
  shouldAddSourcemapSupport: false,
207
312
  supportsResponseStreaming: true,
208
313
  },
209
- bootstrap: [
210
- 'export default async function veskNodeEntry(req, res) {',
211
- " const url = new URL(req.url, 'http://' + (req.headers.host || 'localhost'));",
212
- " const method = req.method || 'GET';",
213
- ' let body;',
214
- " if (method !== 'GET' && method !== 'HEAD') {",
215
- ' body = await new Promise((resolveBody, reject) => {',
216
- ' const chunks = [];',
217
- " req.on('data', (c) => chunks.push(c));",
218
- ' req.on("error", reject);',
219
- ' req.on("end", () => resolveBody(Buffer.concat(chunks)));',
220
- ' });',
221
- ' }',
222
- ' const headers = {};',
223
- ' for (const key in req.headers) headers[key] = req.headers[key];',
224
- ' const request = new Request(url, { method, headers, body });',
225
- ' const response = await handleRequest(request);',
226
- ' res.writeHead(response.status, Object.fromEntries(response.headers));',
227
- ' res.end(await response.text());',
228
- '}',
229
- ].join('\n'),
314
+ bootstrap: vercelBootstrapSource(),
230
315
  };
231
316
  case 'netlify':
232
317
  return {
@@ -1 +1 @@
1
- {"version":3,"file":"platform-handler.d.ts","sourceRoot":"","sources":["../src/platform-handler.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,SAAS,EAAE,YAAY,EAAE,MAAM,yBAAyB,CAAC;AASvE,MAAM,WAAW,oBAAoB;IACnC,SAAS,EAAE,SAAS,EAAE,CAAC;IACvB,SAAS,EAAE,YAAY,EAAE,CAAC;IAC1B,gBAAgB,EAAE,MAAM,EAAE,CAAC;IAC3B,aAAa,EAAE,OAAO,CAAC;IACvB,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,iFAAiF;IACjF,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,wBAAgB,SAAS,CAAC,QAAQ,EAAE,MAAM,EAAE,GAAG,MAAM,CAMpD;AAED,wBAAgB,YAAY,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAGrD;AAED,wBAAgB,IAAI,CAAC,CAAC,EAAE,MAAM,GAAG,MAAM,CAEtC;AAgBD;;;;;GAKG;AACH,wBAAgB,6BAA6B,CAAC,KAAK,EAAE,oBAAoB,GAAG,MAAM,CA4PjF;AAED,MAAM,WAAW,qBAAqB;IACpC,kEAAkE;IAClE,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,MAAM,CAAC;CACf;AAED,wBAAsB,qBAAqB,CAAC,OAAO,EAAE,qBAAqB,GAAG,OAAO,CAAC,IAAI,CAAC,CAmJzF;AAED,MAAM,WAAW,oBAAoB;IACnC,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,SAAS,EAAE,CAAC;IACvB,SAAS,EAAE,YAAY,EAAE,CAAC;IAC1B,gBAAgB,EAAE,MAAM,EAAE,CAAC;IAC3B,aAAa,EAAE,OAAO,CAAC;IACvB,SAAS,CAAC,EAAE,OAAO,CAAC;CACrB"}
1
+ {"version":3,"file":"platform-handler.d.ts","sourceRoot":"","sources":["../src/platform-handler.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,SAAS,EAAE,YAAY,EAAE,MAAM,yBAAyB,CAAC;AASvE,MAAM,WAAW,oBAAoB;IACnC,SAAS,EAAE,SAAS,EAAE,CAAC;IACvB,SAAS,EAAE,YAAY,EAAE,CAAC;IAC1B,gBAAgB,EAAE,MAAM,EAAE,CAAC;IAC3B,aAAa,EAAE,OAAO,CAAC;IACvB,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,iFAAiF;IACjF,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,wBAAgB,SAAS,CAAC,QAAQ,EAAE,MAAM,EAAE,GAAG,MAAM,CAMpD;AAED,wBAAgB,YAAY,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAGrD;AAED,wBAAgB,IAAI,CAAC,CAAC,EAAE,MAAM,GAAG,MAAM,CAEtC;AAgBD;;;;;GAKG;AACH,wBAAgB,6BAA6B,CAAC,KAAK,EAAE,oBAAoB,GAAG,MAAM,CA4PjF;AAED,MAAM,WAAW,qBAAqB;IACpC,kEAAkE;IAClE,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,MAAM,CAAC;CACf;AAED,wBAAsB,qBAAqB,CAAC,OAAO,EAAE,qBAAqB,GAAG,OAAO,CAAC,IAAI,CAAC,CA6JzF;AAED,MAAM,WAAW,oBAAoB;IACnC,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,SAAS,EAAE,CAAC;IACvB,SAAS,EAAE,YAAY,EAAE,CAAC;IAC1B,gBAAgB,EAAE,MAAM,EAAE,CAAC;IAC3B,aAAa,EAAE,OAAO,CAAC;IACvB,SAAS,CAAC,EAAE,OAAO,CAAC;CACrB"}
@@ -348,15 +348,25 @@ export const execSync = () => '';
348
348
  // their require() calls into `__require` shims that throw in ESM output.
349
349
  // esbuild 0.28's string `external` option does not externalize resolvable
350
350
  // bare specifiers (it emits `__require` shims instead), so resolve these
351
- // packages to empty stub modules. Nothing from them is bundled, so no
352
- // `__require` definition or call leaks into the edge bundle. vesk-qr and its
353
- // optional CJS deps (qrcode-generator, canvas, @zxing/*) are optional at
354
- // runtime on edge (they fail gracefully), so stubbing them at build time is
355
- // safe and keeps the bundle valid ESM.
351
+ // packages to empty stub modules. vesk-qr and its optional CJS deps
352
+ // (qrcode-generator, canvas, @zxing/*) are optional at runtime on edge (they
353
+ // fail gracefully), so stubbing them at build time is safe and keeps the
354
+ // bundle valid ESM.
355
+ //
356
+ // Node targets are different: the `createRequire` banner makes the
357
+ // `__require` shim resolve for real, so vesk-qr and its encoder (the CJS
358
+ // `qrcode-generator`) MUST be bundled. A Build Output function ships no
359
+ // package.json deps, and stubbing the encoder makes `buildQrSvg()` throw
360
+ // during SSR (500 on any page that awaits a QR). canvas/@zxing stay
361
+ // stubbed because they are optional and only back raster/scan validation,
362
+ // which degrades gracefully when the decoder is unavailable.
363
+ const qrStubPattern = nodeBuiltins
364
+ ? /^(canvas|@zxing\/(browser|node))(\/|$)/
365
+ : /^(vesk-qr|qrcode-generator|canvas|@zxing\/(browser|node))(\/|$)/;
356
366
  plugins.push({
357
- name: 'stub-edge-packages',
367
+ name: 'stub-optional-qr-packages',
358
368
  setup(build) {
359
- build.onResolve({ filter: /^(vesk-qr|qrcode-generator|canvas|@zxing\/(browser|node))(\/|$)/ }, (args) => ({ path: args.path, namespace: 'vesk-edge-stub' }));
369
+ build.onResolve({ filter: qrStubPattern }, (args) => ({ path: args.path, namespace: 'vesk-edge-stub' }));
360
370
  build.onLoad({ filter: /.*/, namespace: 'vesk-edge-stub' }, () => ({
361
371
  contents: [
362
372
  'export const CofoundrMatrix = undefined;\n',
@@ -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 });
@@ -52,6 +52,15 @@
52
52
  * `packages/adapter/AGENTS.md` for the three outcomes the plugin must
53
53
  * distinguish.
54
54
  */
55
+ /**
56
+ * The TCP drivers that are real npm packages (not `node:` builtins). A Node
57
+ * serverless function bundles statically-imported code, but a driver loaded
58
+ * through a variable `import(spec)` (e.g. `slintorm`'s `importDriver("pg")`)
59
+ * cannot be traced by the bundler or the platform's dependency tracer. Node
60
+ * targets ship these package closures into the function's `node_modules` so
61
+ * that dynamic import resolves at runtime.
62
+ */
63
+ export declare function tcpDriverPackages(): string[];
55
64
  export type ServerOnlyKind = 'server-only-package' | 'tcp-driver';
56
65
  /**
57
66
  * Classify a bare import specifier. Returns null for anything that is fine on
@@ -1 +1 @@
1
- {"version":3,"file":"server-only.d.ts","sourceRoot":"","sources":["../src/server-only.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAqDG;AAsCH,MAAM,MAAM,cAAc,GAAG,qBAAqB,GAAG,YAAY,CAAC;AAElE;;;;GAIG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,MAAM,GAAG,cAAc,GAAG,IAAI,CAMrE;AAED,wBAAgB,qBAAqB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAE3D;AAED,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAE1D;AAED;;;;;;;;;GASG;AACH,wBAAgB,eAAe,CAAC,KAAK,EAAE,KAAK,CAAC;IAAE,SAAS,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAAC,GAAG,MAAM,CAkB5F;AAED;;;GAGG;AACH,wBAAgB,qBAAqB,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,GAAE,CAAC,IAAI,EAAE,MAAM,KAAK,cAAc,GAAG,IAAwB,GAAG,MAAM,CAoBjI;AAED,MAAM,WAAW,mBAAmB;IAClC,IAAI,EAAE,cAAc,CAAC;IACrB,SAAS,EAAE,MAAM,CAAC;IAClB,2DAA2D;IAC3D,IAAI,EAAE,MAAM,CAAC;IACb,uDAAuD;IACvD,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACpB,2BAA2B;IAC3B,MAAM,EAAE,MAAM,CAAC;CAChB;AAWD;;;;;;;GAOG;AACH,wBAAgB,qBAAqB,CACnC,KAAK,EAAE,KAAK,CAAC;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAAC,GAC3C,mBAAmB,EAAE,CA8BvB;AAYD;;;;;;GAMG;AACH,wBAAgB,oBAAoB,CAAC,UAAU,EAAE,mBAAmB,EAAE,GAAG,KAAK,CAU7E;AAYD,wBAAgB,kBAAkB,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,EAAE,MAAM,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAAG,OAAO,CAGlH;AAMD,sDAAsD;AACtD,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,GAAE,MAAM,EAAO,GAAG,MAAM,EAAE,CAwB3E;AAED;;;;;;;GAOG;AACH,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,MAAM,GAAG,mBAAmB,EAAE,CAUzE"}
1
+ {"version":3,"file":"server-only.d.ts","sourceRoot":"","sources":["../src/server-only.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAqDG;AAsCH;;;;;;;GAOG;AACH,wBAAgB,iBAAiB,IAAI,MAAM,EAAE,CAE5C;AAED,MAAM,MAAM,cAAc,GAAG,qBAAqB,GAAG,YAAY,CAAC;AAElE;;;;GAIG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,MAAM,GAAG,cAAc,GAAG,IAAI,CAMrE;AAED,wBAAgB,qBAAqB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAE3D;AAED,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAE1D;AAED;;;;;;;;;GASG;AACH,wBAAgB,eAAe,CAAC,KAAK,EAAE,KAAK,CAAC;IAAE,SAAS,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAAC,GAAG,MAAM,CAkB5F;AAED;;;GAGG;AACH,wBAAgB,qBAAqB,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,GAAE,CAAC,IAAI,EAAE,MAAM,KAAK,cAAc,GAAG,IAAwB,GAAG,MAAM,CAoBjI;AAED,MAAM,WAAW,mBAAmB;IAClC,IAAI,EAAE,cAAc,CAAC;IACrB,SAAS,EAAE,MAAM,CAAC;IAClB,2DAA2D;IAC3D,IAAI,EAAE,MAAM,CAAC;IACb,uDAAuD;IACvD,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACpB,2BAA2B;IAC3B,MAAM,EAAE,MAAM,CAAC;CAChB;AAWD;;;;;;;GAOG;AACH,wBAAgB,qBAAqB,CACnC,KAAK,EAAE,KAAK,CAAC;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAAC,GAC3C,mBAAmB,EAAE,CA8BvB;AAYD;;;;;;GAMG;AACH,wBAAgB,oBAAoB,CAAC,UAAU,EAAE,mBAAmB,EAAE,GAAG,KAAK,CAU7E;AAYD,wBAAgB,kBAAkB,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,EAAE,MAAM,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAAG,OAAO,CAGlH;AAMD,sDAAsD;AACtD,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,GAAE,MAAM,EAAO,GAAG,MAAM,EAAE,CAwB3E;AAED;;;;;;;GAOG;AACH,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,MAAM,GAAG,mBAAmB,EAAE,CAUzE"}
@@ -85,6 +85,17 @@ const TCP_DRIVERS = new Set([
85
85
  'node:net',
86
86
  'node:tls',
87
87
  ]);
88
+ /**
89
+ * The TCP drivers that are real npm packages (not `node:` builtins). A Node
90
+ * serverless function bundles statically-imported code, but a driver loaded
91
+ * through a variable `import(spec)` (e.g. `slintorm`'s `importDriver("pg")`)
92
+ * cannot be traced by the bundler or the platform's dependency tracer. Node
93
+ * targets ship these package closures into the function's `node_modules` so
94
+ * that dynamic import resolves at runtime.
95
+ */
96
+ export function tcpDriverPackages() {
97
+ return [...TCP_DRIVERS].filter((spec) => !spec.startsWith('node:'));
98
+ }
88
99
  /**
89
100
  * Classify a bare import specifier. Returns null for anything that is fine on
90
101
  * the client — including a subpath of a listed package that is itself a
@@ -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.93",
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.93",
27
- "@vesk/runtime": "^0.2.93",
28
- "@vesk/types": "^0.2.93"
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",