@vesk/adapter 0.2.8 → 0.2.10

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":"dev-server.d.ts","sourceRoot":"","sources":["../src/dev-server.ts"],"names":[],"mappings":"AAEA,OAAO,EAAgB,KAAK,MAAM,EAA6C,MAAM,WAAW,CAAC;AAQjG,OAAO,KAAK,EAAa,gBAAgB,EAAY,MAAM,yBAAyB,CAAC;AAWrF,wBAAgB,YAAY,CAAC,QAAQ,EAAE,MAAM,GAAG,KAAK,GAAG;IAAE,MAAM,EAAE,MAAM,CAAA;CAAE,CAIzE;AA6DD,wBAAsB,cAAc,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,gBAAgB,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAoTvG"}
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;AAQjG,OAAO,KAAK,EAAa,gBAAgB,EAAY,MAAM,yBAAyB,CAAC;AAmCrF,wBAAgB,YAAY,CAAC,QAAQ,EAAE,MAAM,GAAG,KAAK,GAAG;IAAE,MAAM,EAAE,MAAM,CAAA;CAAE,CAIzE;AA6DD,wBAAsB,cAAc,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,gBAAgB,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAmTvG"}
@@ -7,8 +7,33 @@ import { DEFAULT_MAX_BODY_BYTES } from '@vesk/compiler/src/server-codegen';
7
7
  import { build } from '@vesk/adapter/src/index';
8
8
  import { createHmrServer } from '@vesk/adapter/src/hmr';
9
9
  import { buildRuntimeCode } from '@vesk/adapter/src/client-bundle';
10
- import { resolveWithin } from '@vesk/adapter/src/paths';
10
+ import { resolveWithin, installMdReadHook } from '@vesk/adapter/src/paths';
11
11
  const __dirname = dirname(fileURLToPath(import.meta.url));
12
+ /**
13
+ * Writes a handler Response to the socket, piping a streaming body
14
+ * chunk-by-chunk (SSE / text streams) instead of buffering everything.
15
+ */
16
+ async function deliverResponse(res, response) {
17
+ res.writeHead(response.status, Object.fromEntries(response.headers));
18
+ const body = response.body;
19
+ if (body && typeof body.getReader === 'function') {
20
+ const reader = body.getReader();
21
+ try {
22
+ for (;;) {
23
+ const { done, value } = await reader.read();
24
+ if (done)
25
+ break;
26
+ res.write(Buffer.from(value));
27
+ }
28
+ }
29
+ catch {
30
+ /* stream aborted by the client */
31
+ }
32
+ res.end();
33
+ return;
34
+ }
35
+ res.end(await response.text());
36
+ }
12
37
  export function bodyTooLarge(maxBytes) {
13
38
  const err = new Error(`Request body exceeds limit (${maxBytes} bytes)`);
14
39
  err.status = 413;
@@ -86,6 +111,7 @@ export async function startDevServer(appDir, options) {
86
111
  process.env.NODE_ENV = 'development';
87
112
  const devDir = resolve(appDir, '..', '.vesk', 'dev');
88
113
  const publicDir = options?.publicDir || resolve(appDir, '..', 'public');
114
+ installMdReadHook([publicDir, resolve(devDir, 'static', 'public')]);
89
115
  let componentMap = new Map();
90
116
  const monorepoRouter = resolve(__dirname, '..', '..', 'compiler', 'dist', 'router.js');
91
117
  const pkgRouter = resolve(appDir, '..', 'node_modules', '@vesk/compiler', 'router.js');
@@ -223,9 +249,7 @@ export async function startDevServer(appDir, options) {
223
249
  const mod = await import(`${handlerPath}?t=${ssrVersion}`);
224
250
  const webRequest = makeWebRequest(req, url.href, maxBodyBytes);
225
251
  const response = await mod.handle(webRequest);
226
- const body = await response.text();
227
- res.writeHead(response.status, Object.fromEntries(response.headers));
228
- res.end(body);
252
+ await deliverResponse(res, response);
229
253
  }
230
254
  catch (e) {
231
255
  const message = e instanceof Error ? e.message : String(e);
package/dist/paths.d.ts CHANGED
@@ -5,6 +5,14 @@
5
5
  * serving / prerender writes so traversal defenses cannot drift.
6
6
  */
7
7
  export declare function resolveWithin(baseDir: string, relPath: string): string | null;
8
+ /**
9
+ * Installs `globalThis.__vsk_md_read_file` — the server-side backing of <Md>
10
+ * runtime markdown-file loading. Reads ONLY `.md`/`.markdown` files strictly
11
+ * inside one of the given public dirs (never above them, never other file
12
+ * types) and returns null otherwise, so a client-supplied path can only ever
13
+ * surface a public markdown file, never arbitrary filesystem content.
14
+ */
15
+ export declare function installMdReadHook(publicDirs: string[]): void;
8
16
  /**
9
17
  * True when an incoming WebSocket upgrade may connect: same-origin when the
10
18
  * client sends an Origin header, always allowed for non-browser clients that
@@ -1 +1 @@
1
- {"version":3,"file":"paths.d.ts","sourceRoot":"","sources":["../src/paths.ts"],"names":[],"mappings":"AAEA;;;;;GAKG;AACH,wBAAgB,aAAa,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAM7E;AAED;;;;;;GAMG;AACH,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,OAAO,CAU5E"}
1
+ {"version":3,"file":"paths.d.ts","sourceRoot":"","sources":["../src/paths.ts"],"names":[],"mappings":"AAGA;;;;;GAKG;AACH,wBAAgB,aAAa,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAM7E;AAED;;;;;;GAMG;AACH,wBAAgB,iBAAiB,CAAC,UAAU,EAAE,MAAM,EAAE,GAAG,IAAI,CAkB5D;AAED;;;;;;GAMG;AACH,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,OAAO,CAU5E"}
package/dist/paths.js CHANGED
@@ -1,4 +1,5 @@
1
1
  import { resolve, sep } from 'node:path';
2
+ import { existsSync, statSync, readFileSync } from 'node:fs';
2
3
  /**
3
4
  * Resolves `relPath` against `baseDir` and returns the absolute path ONLY if
4
5
  * it stays strictly inside `baseDir` (never the directory itself, never a
@@ -13,6 +14,37 @@ export function resolveWithin(baseDir, relPath) {
13
14
  return null;
14
15
  return target;
15
16
  }
17
+ /**
18
+ * Installs `globalThis.__vsk_md_read_file` — the server-side backing of <Md>
19
+ * runtime markdown-file loading. Reads ONLY `.md`/`.markdown` files strictly
20
+ * inside one of the given public dirs (never above them, never other file
21
+ * types) and returns null otherwise, so a client-supplied path can only ever
22
+ * surface a public markdown file, never arbitrary filesystem content.
23
+ */
24
+ export function installMdReadHook(publicDirs) {
25
+ const dirs = publicDirs.map((d) => resolve(d));
26
+ globalThis.__vsk_md_read_file = (p) => {
27
+ for (const dir of dirs) {
28
+ try {
29
+ let rel = String(p);
30
+ while (rel.length > 0 && rel.charCodeAt(0) === 47)
31
+ rel = rel.slice(1); // strip leading '/'
32
+ const abs = resolveWithin(dir, rel);
33
+ if (!abs)
34
+ continue;
35
+ const lower = abs.toLowerCase();
36
+ if (!lower.endsWith('.md') && !lower.endsWith('.markdown'))
37
+ continue;
38
+ if (existsSync(abs) && statSync(abs).isFile())
39
+ return readFileSync(abs, 'utf8');
40
+ }
41
+ catch {
42
+ /* try the next public dir */
43
+ }
44
+ }
45
+ return null;
46
+ };
47
+ }
16
48
  /**
17
49
  * True when an incoming WebSocket upgrade may connect: same-origin when the
18
50
  * client sends an Origin header, always allowed for non-browser clients that
@@ -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;AAIvE,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;CACxB;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;AAQD;;;;;GAKG;AACH,wBAAgB,6BAA6B,CAAC,KAAK,EAAE,oBAAoB,GAAG,MAAM,CAgIjF;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,CAiFzF;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;CACxB"}
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;AAIvE,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;CACxB;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;AAQD;;;;;GAKG;AACH,wBAAgB,6BAA6B,CAAC,KAAK,EAAE,oBAAoB,GAAG,MAAM,CAiIjF;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,CAsFzF;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;CACxB"}
@@ -96,7 +96,7 @@ export async function handleRequest(request) {
96
96
  return new Response(null, { status: 308, headers: { Location: '/_vesk/static/public' + (pathname.endsWith('/') ? pathname + 'index.html' : pathname + '.html') } });
97
97
  }
98
98
 
99
- let mwCtx = { params: {}, url, locals: {}, cookies: {}, request, set() {}, get() { return undefined; } };
99
+ let mwCtx = { params: {}, url, locals: {}, cookies: {}, request, resolveUrl(u) { return new URL(u, request.url).href; }, set() {}, get() { return undefined; } };
100
100
  if (${hasMwLiteral}) {
101
101
  mwCtx = {
102
102
  request,
@@ -104,6 +104,7 @@ export async function handleRequest(request) {
104
104
  url,
105
105
  locals: {},
106
106
  cookies: typeof parseCookies !== 'undefined' ? parseCookies(request.headers.get('cookie') || '') : {},
107
+ resolveUrl(u) { return new URL(u, request.url).href; },
107
108
  set(key, value) { this.locals[key] = value; },
108
109
  get(key) { return this.locals[key]; },
109
110
  };
@@ -158,7 +159,7 @@ export async function bundlePlatformHandler(options) {
158
159
  plugins.push({
159
160
  name: 'empty-node-builtins',
160
161
  setup(build) {
161
- const builtins = /^(node:)?(fs|path|child_process|os|crypto|net|stream|buffer|events|util|url|querystring|http|https|zlib|tty|async_hooks)$/;
162
+ const builtins = /^(node:)?(fs|module|path|child_process|os|crypto|net|stream|buffer|events|util|url|querystring|http|https|zlib|tty|async_hooks)$/;
162
163
  build.onResolve({ filter: builtins }, (args) => {
163
164
  return { path: args.path, namespace: 'empty-node' };
164
165
  });
@@ -189,14 +190,19 @@ export const readFileSync = () => {};
189
190
  export const writeFileSync = () => {};
190
191
  export const existsSync = () => {};
191
192
  export const statSync = () => {};
193
+ export const realpathSync = () => '';
192
194
  export const readdirSync = () => {};
193
195
  export const mkdirSync = () => {};
194
196
  export const unlinkSync = () => {};
195
197
  export const rmSync = () => {};
196
198
  export const copyFileSync = () => {};
197
199
  export const accessSync = () => {};
200
+ // node:module — createRequire is server-only dead code in the browser bundle;
201
+ // the stub keeps esbuild from failing on the named import.
202
+ export const createRequire = () => () => undefined;
198
203
  export const join = (...a) => a.join('/');
199
204
  export const resolve = (...a) => a.join('/');
205
+ export const isAbsolute = () => false;
200
206
  export const dirname = () => '';
201
207
  export const basename = () => '';
202
208
  export const extname = () => '';
@@ -1 +1 @@
1
- {"version":3,"file":"prod-server.d.ts","sourceRoot":"","sources":["../src/prod-server.ts"],"names":[],"mappings":"AAEA,OAAO,EAA2D,KAAK,MAAM,EAAE,MAAM,WAAW,CAAC;AAsBjG,wBAAgB,YAAY,CAAC,QAAQ,EAAE,MAAM,GAAG,KAAK,GAAG;IAAE,MAAM,EAAE,MAAM,CAAA;CAAE,CAIzE;AA4FD,wBAAsB,eAAe,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE;IAAE,IAAI,CAAC,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IAAC,YAAY,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,OAAO,CAAC,MAAM,CAAC,CA0YxI"}
1
+ {"version":3,"file":"prod-server.d.ts","sourceRoot":"","sources":["../src/prod-server.ts"],"names":[],"mappings":"AAEA,OAAO,EAA2D,KAAK,MAAM,EAAE,MAAM,WAAW,CAAC;AA8CjG,wBAAgB,YAAY,CAAC,QAAQ,EAAE,MAAM,GAAG,KAAK,GAAG;IAAE,MAAM,EAAE,MAAM,CAAA;CAAE,CAIzE;AA4FD,wBAAsB,eAAe,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE;IAAE,IAAI,CAAC,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IAAC,YAAY,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,OAAO,CAAC,MAAM,CAAC,CAuYxI"}
@@ -5,7 +5,7 @@ import { createRequire } from 'node:module';
5
5
  import { fileURLToPath } from 'node:url';
6
6
  import { createRateLimiter, resolveComponentName, safeJsonForScript, getClientProtocol, DEFAULT_MAX_BODY_BYTES } from '@vesk/compiler/src/server-codegen';
7
7
  import { securityHeaders } from '@vesk/compiler/src/server-utils';
8
- import { resolveWithin } from '@vesk/adapter/src/paths';
8
+ import { resolveWithin, installMdReadHook } from '@vesk/adapter/src/paths';
9
9
  const _require = createRequire(import.meta.url);
10
10
  const __dirname = dirname(fileURLToPath(import.meta.url));
11
11
  async function readBody(req, maxBytes = DEFAULT_MAX_BODY_BYTES) {
@@ -19,6 +19,31 @@ async function readBody(req, maxBytes = DEFAULT_MAX_BODY_BYTES) {
19
19
  }
20
20
  return Buffer.concat(chunks);
21
21
  }
22
+ /**
23
+ * Writes a handler Response to the socket, piping a streaming body
24
+ * chunk-by-chunk (SSE / text streams) instead of buffering everything.
25
+ */
26
+ async function deliverResponse(res, response) {
27
+ res.writeHead(response.status, Object.fromEntries(response.headers));
28
+ const body = response.body;
29
+ if (body && typeof body.getReader === 'function') {
30
+ const reader = body.getReader();
31
+ try {
32
+ for (;;) {
33
+ const { done, value } = await reader.read();
34
+ if (done)
35
+ break;
36
+ res.write(Buffer.from(value));
37
+ }
38
+ }
39
+ catch {
40
+ /* stream aborted by the client */
41
+ }
42
+ res.end();
43
+ return;
44
+ }
45
+ res.end(await response.text());
46
+ }
22
47
  export function bodyTooLarge(maxBytes) {
23
48
  const err = new Error(`Request body exceeds limit (${maxBytes} bytes)`);
24
49
  err.status = 413;
@@ -97,6 +122,7 @@ export async function startProdServer(outDir, options) {
97
122
  process.env.NODE_ENV = 'production';
98
123
  const staticDir = resolve(outDir, 'static');
99
124
  const configPath = resolve(outDir, 'config.json');
125
+ installMdReadHook([resolve(staticDir, 'public')]);
100
126
  if (!existsSync(configPath)) {
101
127
  console.error(`vesk start: no build found at ${outDir}`);
102
128
  console.error('Run "vesk build" first');
@@ -345,9 +371,7 @@ export async function startProdServer(outDir, options) {
345
371
  try {
346
372
  const webRequest = makeWebRequest(req, url.href, maxBodyBytes);
347
373
  const response = await mod.handleAction(webRequest, actionId);
348
- const body = await response.text();
349
- res.writeHead(response.status, Object.fromEntries(response.headers));
350
- res.end(body);
374
+ await deliverResponse(res, response);
351
375
  }
352
376
  catch (e) {
353
377
  const status = errorStatus(e, 500);
@@ -369,9 +393,7 @@ export async function startProdServer(outDir, options) {
369
393
  try {
370
394
  const webRequest = makeWebRequest(req, url.href, maxBodyBytes);
371
395
  const response = await mod.handle(webRequest);
372
- const body = await response.text();
373
- res.writeHead(response.status, Object.fromEntries(response.headers));
374
- res.end(body);
396
+ await deliverResponse(res, response);
375
397
  return;
376
398
  }
377
399
  catch (e) {
@@ -123,7 +123,7 @@ export async function bundleRuntime(appDir, outDir) {
123
123
  format: 'esm',
124
124
  minify: true,
125
125
  outfile: resolve(outDir, 'server', 'runtime.js'),
126
- external: ['fs', 'node:fs', 'path', 'node:path', 'node:async_hooks'],
126
+ external: ['fs', 'node:fs', 'path', 'node:path', 'module', 'node:module', 'node:async_hooks'],
127
127
  target: ['es2022'],
128
128
  treeShaking: true,
129
129
  });
@@ -1 +1 @@
1
- {"version":3,"file":"ssr-function.d.ts","sourceRoot":"","sources":["../src/ssr-function.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,SAAS,EAAkB,kBAAkB,EAAE,MAAM,yBAAyB,CAAC;AAW7F,wBAAgB,gBAAgB,CAAC,SAAS,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAQjF;AAiDD,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,GAC3B;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAyWtD"}
1
+ {"version":3,"file":"ssr-function.d.ts","sourceRoot":"","sources":["../src/ssr-function.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,SAAS,EAAkB,kBAAkB,EAAE,MAAM,yBAAyB,CAAC;AAW7F,wBAAgB,gBAAgB,CAAC,SAAS,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAQjF;AAiDD,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,GAC3B;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAqXtD"}
@@ -278,6 +278,7 @@ export function generateSsrFunction(routeNode, appDir, outDir, componentMap, opt
278
278
  ' url,',
279
279
  " locals: Object.assign({}, __rootLocals),",
280
280
  " cookies: parseCookies(request.headers.get('cookie') || ''),",
281
+ ' resolveUrl(u) { return new URL(u, request.url).href; },',
281
282
  ' set(key, value) { this.locals[key] = value; },',
282
283
  ' get(key) { return this.locals[key]; },',
283
284
  ' };',
@@ -294,7 +295,17 @@ export function generateSsrFunction(routeNode, appDir, outDir, componentMap, opt
294
295
  ].join('\n');
295
296
  }
296
297
  else {
297
- bodyCode = dataCode;
298
+ const indentedRender = dataCode.split('\n').map(l => l ? ` ${l}` : '').join('\n');
299
+ bodyCode = [
300
+ ' // Request context for SSR helpers (useParams/useRequest, relative useFetch resolution).',
301
+ ' const prevReq = globalThis.__vesk_request;',
302
+ ' globalThis.__vesk_request = VeskRequest.from(request, { params });',
303
+ ' try {',
304
+ indentedRender,
305
+ ' } finally {',
306
+ ' globalThis.__vesk_request = prevReq;',
307
+ ' }',
308
+ ].join('\n');
298
309
  }
299
310
  let registerActionsCode;
300
311
  if (hasLayout || hasAncestorLayout) {
@@ -368,6 +379,7 @@ export function generateSsrFunction(routeNode, appDir, outDir, componentMap, opt
368
379
  ' url: pageUrl,',
369
380
  ' locals: {},',
370
381
  " cookies: parseCookies(request.headers.get('cookie') || ''),",
382
+ ' resolveUrl(u) { return new URL(u, pageUrl.href).href; },',
371
383
  ' };',
372
384
  ' try {',
373
385
  ' const result = await action.execute(input, {',
@@ -398,7 +410,7 @@ export function generateSsrFunction(routeNode, appDir, outDir, componentMap, opt
398
410
  '',
399
411
  ].join('\n');
400
412
  const funcCode = [
401
- "import { renderFullPage, renderPageStream, renderPage, compileFile, setVskHydrate, parseCookies, getAction, validateActionInput, issuesToFieldMap, storeDataScriptGlobal, withSsrStore, assertSameOrigin } from '../runtime.js';",
413
+ "import { renderFullPage, renderPageStream, renderPage, compileFile, setVskHydrate, parseCookies, getAction, validateActionInput, issuesToFieldMap, storeDataScriptGlobal, withSsrStore, assertSameOrigin, VeskRequest } from '../runtime.js';",
402
414
  '',
403
415
  middlewareCode || '',
404
416
  registryCode,
package/dist/types.d.ts CHANGED
@@ -1,2 +1,2 @@
1
- export type { RouteNode, ApiRouteNode, VeskPlugin, VeskSecurity, VeskCors, VeskRateLimit, VeskSecurityPreset, SecurityConfig, MiddlewareChainItem, MiddlewareExtractResult, AncestorLayout, BuildOptions, DevServerOptions, ProdServerOptions, SsrFunctionOptions, ApiFunctionOptions, ClientBundleOptions, ClientBundleFileEntry, ClientBundleCache, ChunkEntry, ClientBundleResult, MonolithicBundleParts, Manifest, ManifestRouteEntry, ManifestActionEntry, ManifestPrerenderedEntry, SsgRouteResult, BuildResult, ImageRef, ImageResult, SeoCheckIssue, SeoAuditResult, CombinedPageInfo, } from '@vesk/types';
1
+ export type { RouteNode, ApiRouteNode, VeskPlugin, VeskSecurity, VeskCors, VeskRateLimit, VeskSecurityPreset, SecurityConfig, MiddlewareChainItem, MiddlewareExtractResult, AncestorLayout, BuildOptions, DevServerOptions, ProdServerOptions, SsrFunctionOptions, ApiFunctionOptions, ClientBundleOptions, ClientBundleFileEntry, ClientBundleCache, ChunkEntry, ClientBundleResult, MonolithicBundleParts, Manifest, ManifestRouteEntry, ManifestActionEntry, ManifestPrerenderedEntry, SsgRouteResult, BuildResult, ImageRef, ImageResult, SeoCheckIssue, SeoAuditResult, CombinedPageInfo, Tracked, Resource, UseFetchOptions, UseFetchStreamOptions, VeskRequest, VeskResponse, SetCookieOptions, CookieStore, ServerRequest, SsrDataSink, MdProps, MdConfig, MdHtmlMode, MarkdownOptions, MdHtmlWarning, } from '@vesk/types';
2
2
  //# sourceMappingURL=types.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAGA,YAAY,EACV,SAAS,EACT,YAAY,EACZ,UAAU,EACV,YAAY,EACZ,QAAQ,EACR,aAAa,EACb,kBAAkB,EAClB,cAAc,EACd,mBAAmB,EACnB,uBAAuB,EACvB,cAAc,EACd,YAAY,EACZ,gBAAgB,EAChB,iBAAiB,EACjB,kBAAkB,EAClB,kBAAkB,EAClB,mBAAmB,EACnB,qBAAqB,EACrB,iBAAiB,EACjB,UAAU,EACV,kBAAkB,EAClB,qBAAqB,EACrB,QAAQ,EACR,kBAAkB,EAClB,mBAAmB,EACnB,wBAAwB,EACxB,cAAc,EACd,WAAW,EACX,QAAQ,EACR,WAAW,EACX,aAAa,EACb,cAAc,EACd,gBAAgB,GACjB,MAAM,aAAa,CAAC"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAGA,YAAY,EACV,SAAS,EACT,YAAY,EACZ,UAAU,EACV,YAAY,EACZ,QAAQ,EACR,aAAa,EACb,kBAAkB,EAClB,cAAc,EACd,mBAAmB,EACnB,uBAAuB,EACvB,cAAc,EACd,YAAY,EACZ,gBAAgB,EAChB,iBAAiB,EACjB,kBAAkB,EAClB,kBAAkB,EAClB,mBAAmB,EACnB,qBAAqB,EACrB,iBAAiB,EACjB,UAAU,EACV,kBAAkB,EAClB,qBAAqB,EACrB,QAAQ,EACR,kBAAkB,EAClB,mBAAmB,EACnB,wBAAwB,EACxB,cAAc,EACd,WAAW,EACX,QAAQ,EACR,WAAW,EACX,aAAa,EACb,cAAc,EACd,gBAAgB,EAChB,OAAO,EACP,QAAQ,EACR,eAAe,EACf,qBAAqB,EACrB,WAAW,EACX,YAAY,EACZ,gBAAgB,EAChB,WAAW,EACX,aAAa,EACb,WAAW,EACX,OAAO,EACP,QAAQ,EACR,UAAU,EACV,eAAe,EACf,aAAa,GACd,MAAM,aAAa,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vesk/adapter",
3
- "version": "0.2.8",
3
+ "version": "0.2.10",
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.8",
27
- "@vesk/runtime": "^0.2.8",
28
- "@vesk/types": "^0.2.8"
26
+ "@vesk/compiler": "^0.2.10",
27
+ "@vesk/runtime": "^0.2.10",
28
+ "@vesk/types": "^0.2.10"
29
29
  },
30
30
  "devDependencies": {
31
31
  "@types/ws": "^8.18.1",