@vesk/adapter 0.2.38 → 0.2.40

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":"api-function.d.ts","sourceRoot":"","sources":["../src/api-function.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,YAAY,EAAE,kBAAkB,EAAE,MAAM,yBAAyB,CAAC;AAOhF,wBAAgB,mBAAmB,CACjC,OAAO,EAAE,YAAY,EACrB,OAAO,EAAE,MAAM,EACf,MAAM,EAAE,MAAM,EACd,OAAO,CAAC,EAAE,kBAAkB,GAC3B;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAyLtD"}
1
+ {"version":3,"file":"api-function.d.ts","sourceRoot":"","sources":["../src/api-function.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,YAAY,EAAE,kBAAkB,EAAE,MAAM,yBAAyB,CAAC;AAOhF,wBAAgB,mBAAmB,CACjC,OAAO,EAAE,YAAY,EACrB,OAAO,EAAE,MAAM,EACf,MAAM,EAAE,MAAM,EACd,OAAO,CAAC,EAAE,kBAAkB,GAC3B;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAqOtD"}
@@ -44,21 +44,126 @@ export function generateApiFunction(apiNode, _apiDir, outDir, options) {
44
44
  const paramsCode = extracts.length > 0
45
45
  ? ` const params = { ${extracts.join(', ')} };\n`
46
46
  : ' const params = {};\n';
47
- const handlerMethods = ['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'HEAD', 'OPTIONS']
48
- .filter(m => routeSrc.includes(`async function ${m}(`) || routeSrc.includes(`async function ${m} (`) || routeSrc.includes(`export async function ${m}`))
49
- .map(m => `${m}`).join(', ');
47
+ // Shared wrapper body. The route source is inlined into this module, so the
48
+ // handlers are ordinary bindings here and `typeof` resolves them at call
49
+ // time (function declarations hoist; const arrow handlers are initialized by
50
+ // the time a request arrives). This replaces a textual pre-scan, which used
51
+ // to miss `export const GET = …` / non-async functions and made the `Allow`
52
+ // header wrong.
53
+ const sharedBody = [
54
+ " const url = new URL(request.url);",
55
+ " const urlParts = url.pathname.replace(/^\\/api\\/?/, '/').split('/').filter(Boolean);",
56
+ " const method = request.method || 'GET';",
57
+ `${paramsCode}`,
58
+ " Object.defineProperty(request, 'query', {",
59
+ ' get: () => Object.fromEntries(url.searchParams.entries()),',
60
+ ' enumerable: true,',
61
+ ' });',
62
+ " const __rootLocals = (globalThis.__vesk_request && globalThis.__vesk_request.locals) || {};",
63
+ ];
64
+ const dispatchBody = [
65
+ ' try {',
66
+ " const __mod = {",
67
+ ...['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'HEAD', 'OPTIONS'].map((m) => ` ${m}: typeof ${m} === 'function' ? ${m} : undefined,`),
68
+ ' };',
69
+ " const __allowed = ['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'HEAD', 'OPTIONS'].filter((m) => __mod[m]);",
70
+ ' const handler = __mod[method];',
71
+ ' if (!handler) {',
72
+ " if (method === 'OPTIONS') {",
73
+ ' return new Response(null, { status: 204, headers: { Allow: __allowed.join(\', \') } });',
74
+ ' }',
75
+ " return new Response(JSON.stringify({ error: `Method ${method} not allowed` }), {",
76
+ " status: 405,",
77
+ " headers: { 'Content-Type': 'application/json', Allow: __allowed.join(', ') },",
78
+ ' });',
79
+ ' }',
80
+ ' const __routeConfig = (typeof config !== \'undefined\' && config) || {};',
81
+ ' // Default CSRF defense for mutating API calls: same-origin check unless',
82
+ ' // the route opts out via `config.csrf = false`.',
83
+ " if (__routeConfig.csrf !== false && method !== 'GET' && method !== 'HEAD' && method !== 'OPTIONS') {",
84
+ ' try {',
85
+ ' assertSameOrigin({',
86
+ ' method,',
87
+ ' headers: Object.fromEntries([...request.headers.entries()].map(([k, v]) => [k, Array.isArray(v) ? v.join(\', \') : v])),',
88
+ ' });',
89
+ ' } catch {',
90
+ " return new Response(JSON.stringify({ error: 'Cross-origin request blocked' }), {",
91
+ " status: 403, headers: { 'Content-Type': 'application/json' },",
92
+ ' });',
93
+ ' }',
94
+ ' }',
95
+ ' // Body cap. The handler receives the raw platform Request, so a route that',
96
+ ' // calls `req.json()` directly would bypass a VeskRequest-level guard —',
97
+ ' // enforce it here, before any handler runs, on every target (dev, node,',
98
+ ' // serverless, edge).',
99
+ " const __declared = Number(request.headers.get('content-length') || '0');",
100
+ ' if (Number.isFinite(__declared) && __declared > DEFAULT_MAX_BODY_BYTES) {',
101
+ " return new Response(JSON.stringify({ error: `Request body exceeds limit (${DEFAULT_MAX_BODY_BYTES} bytes)` }), {",
102
+ " status: 413, headers: { 'Content-Type': 'application/json' },",
103
+ ' });',
104
+ ' }',
105
+ ' let __signal = request.signal;',
106
+ ' let __timeout;',
107
+ ' if (__routeConfig.maxDuration) {',
108
+ ' const __controller = new AbortController();',
109
+ ' __timeout = setTimeout(',
110
+ ' () => __controller.abort(new Error(`Request timed out after ${__routeConfig.maxDuration}s`)),',
111
+ ' Number(__routeConfig.maxDuration) * 1000',
112
+ ' );',
113
+ ' __signal = __controller.signal;',
114
+ " Object.defineProperty(request, 'signal', { value: __signal, writable: false });",
115
+ ' }',
116
+ ' // Route hooks may be a single function or an array of them.',
117
+ ' const __before = typeof beforeRequest === \'undefined\' ? [] : (Array.isArray(beforeRequest) ? beforeRequest : [beforeRequest]);',
118
+ ' for (const __hook of __before) {',
119
+ ' const __hookResult = await __hook(request, { params: Promise.resolve(params), locals: ctx.locals });',
120
+ ' if (__hookResult instanceof Response) return __hookResult;',
121
+ ' }',
122
+ " let __global = await runHooks('beforeRequest', request, { params, locals: ctx.locals });",
123
+ ' if (__global instanceof Response) return __global;',
124
+ ' let __response;',
125
+ ' try {',
126
+ ' __response = await handler(request, { params: Promise.resolve(params) });',
127
+ ' } catch (__e) {',
128
+ " const __handled = await runHooks('onError', __e, request);",
129
+ ' if (__handled instanceof Response) return __handled;',
130
+ ' throw __e;',
131
+ ' } finally {',
132
+ ' if (__timeout) clearTimeout(__timeout);',
133
+ ' }',
134
+ ' const __after = typeof afterRequest === \'undefined\' ? [] : (Array.isArray(afterRequest) ? afterRequest : [afterRequest]);',
135
+ ' for (const __hook of __after) {',
136
+ ' const __hookResult = await __hook(request, __response);',
137
+ ' if (__hookResult instanceof Response) __response = __hookResult;',
138
+ ' }',
139
+ " __global = await runHooks('afterRequest', request, __response);",
140
+ ' if (__global instanceof Response) __response = __global;',
141
+ ' if (__response && typeof __response.build === \'function\') __response.build();',
142
+ ' if (__response instanceof Response) return __response;',
143
+ ' return new Response(JSON.stringify(__response), {',
144
+ ' status: 200,',
145
+ " headers: { 'Content-Type': 'application/json' },",
146
+ ' });',
147
+ ' } catch (e) {',
148
+ ' const err = /** @type {Error & Record<string, unknown>} */(e);',
149
+ " if (err.name === 'Redirect') {",
150
+ " return new Response(null, { status: Number(err.status) || 302, headers: { Location: String(err.url || '/') } });",
151
+ ' }',
152
+ " if (err.name === 'NotFoundError') {",
153
+ " return new Response(JSON.stringify({ error: 'Not Found' }), { status: 404, headers: { 'Content-Type': 'application/json' } });",
154
+ ' }',
155
+ ' if (err.name === \'PayloadTooLargeError\') {',
156
+ " return new Response(JSON.stringify({ error: 'Request body too large' }), { status: 413, headers: { 'Content-Type': 'application/json' } });",
157
+ ' }',
158
+ " return new Response(JSON.stringify({ error: process.env.NODE_ENV === 'production' ? 'Internal Server Error' : err.message }), { status: 500, headers: { 'Content-Type': 'application/json' } });",
159
+ ' } finally {',
160
+ ' globalThis.__vesk_request = prev;',
161
+ ' }',
162
+ ];
50
163
  let handleBody;
51
164
  if (middlewareCode) {
52
165
  handleBody = [
53
- " const url = new URL(request.url);",
54
- " const urlParts = url.pathname.replace(/^\\/api\\/?/, '/').split('/').filter(Boolean);",
55
- " const method = request.method || 'GET';",
56
- `${paramsCode}`,
57
- " Object.defineProperty(request, 'query', {",
58
- ' get: () => Object.fromEntries(url.searchParams.entries()),',
59
- ' enumerable: true,',
60
- ' });',
61
- " const __rootLocals = (globalThis.__vesk_request && globalThis.__vesk_request.locals) || {};",
166
+ ...sharedBody,
62
167
  ' // ── Middleware context ──',
63
168
  ' const __ctx = {',
64
169
  ' request,',
@@ -85,48 +190,12 @@ export function generateApiFunction(apiNode, _apiDir, outDir, options) {
85
190
  ' });',
86
191
  ' const prev = globalThis.__vesk_request;',
87
192
  ' globalThis.__vesk_request = ctx;',
88
- ' try {',
89
- ` const handler = { ${handlerMethods} }[method];`,
90
- ' if (!handler) {',
91
- " return new Response(JSON.stringify({ error: 'Method not allowed' }), {",
92
- ' status: 405,',
93
- " headers: { 'Content-Type': 'application/json' },",
94
- ' });',
95
- ' }',
96
- ' const response = await handler(request, { params: Promise.resolve(params) });',
97
- ' if (response instanceof Response) {',
98
- " if (typeof response.build === 'function') response.build();",
99
- ' return response;',
100
- ' }',
101
- ' return new Response(JSON.stringify(response), {',
102
- ' status: 200,',
103
- " headers: { 'Content-Type': 'application/json' },",
104
- ' });',
105
- ' } catch (e) {',
106
- ' const err = /** @type {Error & Record<string, unknown>} */(e);',
107
- " if (err.name === 'Redirect') {",
108
- ' return new Response(null, { status: Number(err.status) || 302, headers: { Location: String(err.url || "") } });',
109
- ' }',
110
- " if (err.name === 'NotFoundError') {",
111
- " return new Response(JSON.stringify({ error: 'Not Found' }), { status: 404, headers: { 'Content-Type': 'application/json' } });",
112
- ' }',
113
- ' return new Response(JSON.stringify({ error: err.message }), { status: 500, headers: { \'Content-Type\': \'application/json\' } });',
114
- ' } finally {',
115
- ' globalThis.__vesk_request = prev;',
116
- ' }',
193
+ ...dispatchBody,
117
194
  ].join('\n');
118
195
  }
119
196
  else {
120
197
  handleBody = [
121
- " const url = new URL(request.url);",
122
- " const urlParts = url.pathname.replace(/^\\/api\\/?/, '/').split('/').filter(Boolean);",
123
- " const method = request.method || 'GET';",
124
- `${paramsCode}`,
125
- " Object.defineProperty(request, 'query', {",
126
- ' get: () => Object.fromEntries(url.searchParams.entries()),',
127
- ' enumerable: true,',
128
- ' });',
129
- " const __rootLocals = (globalThis.__vesk_request && globalThis.__vesk_request.locals) || {};",
198
+ ...sharedBody,
130
199
  ' const ctx = {',
131
200
  " headers: Object.fromEntries(request.headers.entries()),",
132
201
  ' url: request.url,',
@@ -140,35 +209,7 @@ export function generateApiFunction(apiNode, _apiDir, outDir, options) {
140
209
  ' });',
141
210
  ' const prev = globalThis.__vesk_request;',
142
211
  ' globalThis.__vesk_request = ctx;',
143
- ' try {',
144
- ` const handler = { ${handlerMethods} }[method];`,
145
- ' if (!handler) {',
146
- " return new Response(JSON.stringify({ error: 'Method not allowed' }), {",
147
- ' status: 405,',
148
- " headers: { 'Content-Type': 'application/json' },",
149
- ' });',
150
- ' }',
151
- ' const response = await handler(request, { params: Promise.resolve(params) });',
152
- ' if (response instanceof Response) {',
153
- " if (typeof response.build === 'function') response.build();",
154
- ' return response;',
155
- ' }',
156
- ' return new Response(JSON.stringify(response), {',
157
- ' status: 200,',
158
- " headers: { 'Content-Type': 'application/json' },",
159
- ' });',
160
- ' } catch (e) {',
161
- ' const err = /** @type {Error & Record<string, unknown>} */(e);',
162
- " if (err.name === 'Redirect') {",
163
- ' return new Response(null, { status: Number(err.status) || 302, headers: { Location: String(err.url || "") } });',
164
- ' }',
165
- " if (err.name === 'NotFoundError') {",
166
- " return new Response(JSON.stringify({ error: 'Not Found' }), { status: 404, headers: { 'Content-Type': 'application/json' } });",
167
- ' }',
168
- ' return new Response(JSON.stringify({ error: err.message }), { status: 500, headers: { \'Content-Type\': \'application/json\' } });',
169
- ' } finally {',
170
- ' globalThis.__vesk_request = prev;',
171
- ' }',
212
+ ...dispatchBody,
172
213
  ].join('\n');
173
214
  }
174
215
  const funcCode = [
@@ -177,7 +218,7 @@ export function generateApiFunction(apiNode, _apiDir, outDir, options) {
177
218
  routeSrc.trim(),
178
219
  '',
179
220
  '// ── Request handler wrapper ──',
180
- "import { parseCookies } from '../runtime.js';",
221
+ "import { parseCookies, assertSameOrigin, runHooks, DEFAULT_MAX_BODY_BYTES } from '../runtime.js';",
181
222
  middlewareCode ? "import { parseCookies as __parseCookies } from '../runtime.js';" : '',
182
223
  '',
183
224
  middlewareCode || '',
@@ -1 +1 @@
1
- {"version":3,"file":"runtime-bundle.d.ts","sourceRoot":"","sources":["../src/runtime-bundle.ts"],"names":[],"mappings":"AAuCA,wBAAsB,aAAa,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAqHnF"}
1
+ {"version":3,"file":"runtime-bundle.d.ts","sourceRoot":"","sources":["../src/runtime-bundle.ts"],"names":[],"mappings":"AAuCA,wBAAsB,aAAa,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAwJnF"}
@@ -105,6 +105,8 @@ export async function bundleRuntime(appDir, outDir) {
105
105
  '',
106
106
  '// Re-export runtime classes used by API routes',
107
107
  'export const VeskRequest = __veskRuntime.VeskRequest;',
108
+ 'export const PayloadTooLargeError = __veskRuntime.PayloadTooLargeError;',
109
+ 'export const DEFAULT_MAX_BODY_BYTES = __veskRuntime.DEFAULT_MAX_BODY_BYTES;',
108
110
  'export const VeskResponse = __veskRuntime.VeskResponse;',
109
111
  '',
110
112
  '// Server actions',
@@ -114,6 +116,39 @@ export async function bundleRuntime(appDir, outDir) {
114
116
  'export const validateActionInput = __veskRuntime.validateActionInput;',
115
117
  'export const issuesToFieldMap = __veskRuntime.issuesToFieldMap;',
116
118
  '',
119
+ '// API-route surface. `@vesk/runtime/server` imports in app/api/**/route.ts',
120
+ '// are rewritten to this bundle, so anything a route is documented to',
121
+ '// import must be re-exported here — otherwise it resolves in dev (which',
122
+ '// imports the real barrel) and fails only in a production build.',
123
+ 'export const redirect = __veskRuntime.redirect;',
124
+ 'export const permanentRedirect = __veskRuntime.permanentRedirect;',
125
+ 'export const notFound = __veskRuntime.notFound;',
126
+ 'export const NotFoundError = __veskRuntime.NotFoundError;',
127
+ 'export const useParams = __veskRuntime.useParams;',
128
+ 'export const useBody = __veskRuntime.useBody;',
129
+ 'export const useRequest = __veskRuntime.useRequest;',
130
+ 'export const withValidation = __veskRuntime.withValidation;',
131
+ 'export const cors = __veskRuntime.cors;',
132
+ 'export const webhook = __veskRuntime.webhook;',
133
+ 'export const signCookie = __veskRuntime.signCookie;',
134
+ 'export const unsignCookie = __veskRuntime.unsignCookie;',
135
+ 'export const setSignedCookie = __veskRuntime.setSignedCookie;',
136
+ 'export const readSignedCookie = __veskRuntime.readSignedCookie;',
137
+ 'export const HttpError = __veskRuntime.HttpError;',
138
+ 'export const TimeoutError = __veskRuntime.TimeoutError;',
139
+ 'export const mutate = __veskRuntime.mutate;',
140
+ 'export const runHooks = __veskRuntime.runHooks;',
141
+ '',
142
+ '// ISR (server-only; revalidate*/clear* are no-ops outside a request).',
143
+ 'export const isr = __veskRuntime.isr;',
144
+ 'export const pageIsr = __veskRuntime.pageIsr;',
145
+ 'export const componentIsr = __veskRuntime.componentIsr;',
146
+ 'export const revalidatePath = __veskRuntime.revalidatePath;',
147
+ 'export const revalidateTag = __veskRuntime.revalidateTag;',
148
+ 'export const revalidateComponent = __veskRuntime.revalidateComponent;',
149
+ 'export const clearIsrCache = __veskRuntime.clearIsrCache;',
150
+ 'export const isrConfigToRevalidate = __veskRuntime.isrConfigToRevalidate;',
151
+ '',
117
152
  '// Server-wide context store (server events)',
118
153
  'export const serverLocals = __veskRuntime.serverLocals;',
119
154
  'export const getServerContext = __veskRuntime.getServerContext;',
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vesk/adapter",
3
- "version": "0.2.38",
3
+ "version": "0.2.40",
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.38",
27
- "@vesk/runtime": "^0.2.38",
28
- "@vesk/types": "^0.2.38"
26
+ "@vesk/compiler": "^0.2.40",
27
+ "@vesk/runtime": "^0.2.40",
28
+ "@vesk/types": "^0.2.40"
29
29
  },
30
30
  "devDependencies": {
31
31
  "@types/ws": "^8.18.1",