@vistagenic/vista 0.2.2 → 0.2.3

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.
Files changed (37) hide show
  1. package/LICENSE +21 -0
  2. package/bin/vista.js +30 -20
  3. package/dist/bin/generate.d.ts +7 -0
  4. package/dist/bin/generate.js +248 -0
  5. package/dist/config.d.ts +19 -0
  6. package/dist/config.js +62 -4
  7. package/dist/server/engine.js +23 -57
  8. package/dist/server/rsc-engine.js +21 -48
  9. package/dist/server/static-generator.js +98 -0
  10. package/dist/server/structure-validator.js +1 -1
  11. package/dist/server/typed-api-runtime.d.ts +16 -0
  12. package/dist/server/typed-api-runtime.js +336 -0
  13. package/dist/stack/client/create-client.d.ts +2 -0
  14. package/dist/stack/client/create-client.js +195 -0
  15. package/dist/stack/client/error.d.ts +18 -0
  16. package/dist/stack/client/error.js +22 -0
  17. package/dist/stack/client/index.d.ts +9 -0
  18. package/dist/stack/client/index.js +13 -0
  19. package/dist/stack/client/types.d.ts +39 -0
  20. package/dist/stack/client/types.js +2 -0
  21. package/dist/stack/index.d.ts +32 -0
  22. package/dist/stack/index.js +45 -0
  23. package/dist/stack/server/executor.d.ts +36 -0
  24. package/dist/stack/server/executor.js +174 -0
  25. package/dist/stack/server/index.d.ts +10 -0
  26. package/dist/stack/server/index.js +23 -0
  27. package/dist/stack/server/merge-routers.d.ts +2 -0
  28. package/dist/stack/server/merge-routers.js +80 -0
  29. package/dist/stack/server/procedure.d.ts +18 -0
  30. package/dist/stack/server/procedure.js +58 -0
  31. package/dist/stack/server/router.d.ts +9 -0
  32. package/dist/stack/server/router.js +80 -0
  33. package/dist/stack/server/serialization.d.ts +9 -0
  34. package/dist/stack/server/serialization.js +119 -0
  35. package/dist/stack/server/types.d.ts +100 -0
  36. package/dist/stack/server/types.js +2 -0
  37. package/package.json +11 -2
@@ -70,6 +70,7 @@ const structure_watch_1 = require("./structure-watch");
70
70
  const structure_log_1 = require("./structure-log");
71
71
  const error_boundary_1 = require("../components/error-boundary");
72
72
  const route_suspense_1 = require("../components/route-suspense");
73
+ const typed_api_runtime_1 = require("./typed-api-runtime");
73
74
  // Support CSS imports on server runtime
74
75
  // - Regular .css: ignored (handled by PostCSS)
75
76
  // - .module.css: return empty class mapping (webpack build handles real mappings)
@@ -447,58 +448,29 @@ function createHtmlDocument(appHtml, metadataHtml, chunkFiles, rootMode = 'legac
447
448
  </body>
448
449
  </html>`;
449
450
  }
450
- async function handleApiRoute(req, res, cwd, isDev) {
451
- const apiRoute = req.path.substring(5);
452
- const routeTsPath = path_1.default.resolve(cwd, 'app', 'api', apiRoute, 'route.ts');
453
- const routeTsxPath = path_1.default.resolve(cwd, 'app', 'api', apiRoute, 'route.tsx');
454
- const legacyPath = path_1.default.resolve(cwd, 'app', 'api', `${apiRoute}.ts`);
455
- let apiPath = null;
456
- if (fs_1.default.existsSync(routeTsPath))
457
- apiPath = routeTsPath;
458
- else if (fs_1.default.existsSync(routeTsxPath))
459
- apiPath = routeTsxPath;
460
- else if (fs_1.default.existsSync(legacyPath))
461
- apiPath = legacyPath;
462
- if (!apiPath) {
463
- res.status(404).json({ error: 'API Route Not Found' });
464
- return;
465
- }
451
+ async function handleApiRoute(req, res, cwd, isDev, typedApiConfig) {
466
452
  try {
467
- if (isDev)
468
- delete require.cache[require.resolve(apiPath)];
469
- const apiModule = require(apiPath);
470
- const method = req.method?.toUpperCase() || 'GET';
471
- const methodHandler = apiModule[method];
472
- if (typeof methodHandler === 'function') {
473
- const request = {
474
- url: req.protocol + '://' + req.get('host') + req.originalUrl,
475
- method: req.method,
476
- headers: new Map(Object.entries(req.headers)),
477
- json: async () => req.body,
478
- text: async () => JSON.stringify(req.body),
479
- nextUrl: {
480
- pathname: req.path,
481
- searchParams: new URLSearchParams(req.query),
482
- },
483
- };
484
- const result = await methodHandler(request, { params: {} });
485
- if (result && typeof result.json === 'function') {
486
- const json = await result.json();
487
- res.status(result.status || 200).json(json);
488
- }
489
- else if (result) {
490
- res.status(200).json(result);
491
- }
492
- else {
493
- res.status(204).end();
494
- }
453
+ const legacyApiPath = (0, typed_api_runtime_1.resolveLegacyApiRoutePath)(cwd, req.path);
454
+ if (legacyApiPath) {
455
+ await (0, typed_api_runtime_1.runLegacyApiRoute)({
456
+ req,
457
+ res,
458
+ apiPath: legacyApiPath,
459
+ isDev,
460
+ });
495
461
  return;
496
462
  }
497
- if (typeof apiModule.default === 'function') {
498
- apiModule.default(req, res);
463
+ const typedHandled = await (0, typed_api_runtime_1.runTypedApiRoute)({
464
+ req,
465
+ res,
466
+ cwd,
467
+ isDev,
468
+ config: typedApiConfig,
469
+ });
470
+ if (typedHandled) {
499
471
  return;
500
472
  }
501
- res.status(405).json({ error: `Method ${method} not allowed` });
473
+ res.status(404).json({ error: 'API Route Not Found' });
502
474
  }
503
475
  catch (error) {
504
476
  console.error('[vista:rsc] API route error:', error);
@@ -654,6 +626,7 @@ function startRSCServer(options = {}) {
654
626
  const cwd = process.cwd();
655
627
  const isDev = process.env.NODE_ENV !== 'production';
656
628
  const vistaConfig = (0, config_1.loadConfig)(cwd);
629
+ const typedApiConfig = (0, config_1.resolveTypedApiConfig)(vistaConfig);
657
630
  // Clean stale hot-update files from previous runs
658
631
  cleanHotUpdateFiles(cwd);
659
632
  // Request logger — logs GET/POST with timing
@@ -1067,7 +1040,7 @@ function startRSCServer(options = {}) {
1067
1040
  return;
1068
1041
  }
1069
1042
  if (req.path.startsWith('/api/')) {
1070
- await handleApiRoute(req, res, cwd, isDev);
1043
+ await handleApiRoute(req, res, cwd, isDev, typedApiConfig);
1071
1044
  return;
1072
1045
  }
1073
1046
  // ==================================================================
@@ -17,6 +17,102 @@ exports.revalidatePath = revalidatePath;
17
17
  const path_1 = __importDefault(require("path"));
18
18
  const fs_1 = __importDefault(require("fs"));
19
19
  const static_cache_1 = require("./static-cache");
20
+ const CjsModule = require('module');
21
+ let staticRuntimeReady = false;
22
+ let reactResolutionInstalled = false;
23
+ let originalResolveFilename = null;
24
+ function installSingleReactResolution(cwd) {
25
+ if (reactResolutionInstalled)
26
+ return;
27
+ let reactPath;
28
+ let reactDomPath;
29
+ try {
30
+ reactPath = require.resolve('react', { paths: [cwd] });
31
+ reactDomPath = require.resolve('react-dom', { paths: [cwd] });
32
+ }
33
+ catch {
34
+ try {
35
+ reactPath = require.resolve('react');
36
+ reactDomPath = require.resolve('react-dom');
37
+ }
38
+ catch {
39
+ return;
40
+ }
41
+ }
42
+ originalResolveFilename = CjsModule._resolveFilename;
43
+ CjsModule._resolveFilename = function (request, parent, isMain, options) {
44
+ if (request === 'react')
45
+ return reactPath;
46
+ if (request === 'react-dom')
47
+ return reactDomPath;
48
+ if (request.startsWith('react/')) {
49
+ const subPath = request.slice('react/'.length);
50
+ try {
51
+ return require.resolve(`react/${subPath}`, { paths: [path_1.default.dirname(reactPath)] });
52
+ }
53
+ catch {
54
+ // fall through
55
+ }
56
+ }
57
+ if (request.startsWith('react-dom/')) {
58
+ const subPath = request.slice('react-dom/'.length);
59
+ try {
60
+ return require.resolve(`react-dom/${subPath}`, { paths: [path_1.default.dirname(reactDomPath)] });
61
+ }
62
+ catch {
63
+ // fall through
64
+ }
65
+ }
66
+ return originalResolveFilename.call(this, request, parent, isMain, options);
67
+ };
68
+ reactResolutionInstalled = true;
69
+ }
70
+ function setupTypeScriptRuntime(cwd) {
71
+ try {
72
+ const swcPath = require.resolve('@swc-node/register', { paths: [cwd] });
73
+ require(swcPath);
74
+ return;
75
+ }
76
+ catch {
77
+ // fallback
78
+ }
79
+ try {
80
+ const tsNodePath = require.resolve('ts-node', { paths: [cwd] });
81
+ require(tsNodePath).register({
82
+ transpileOnly: true,
83
+ compilerOptions: {
84
+ module: 'commonjs',
85
+ jsx: 'react-jsx',
86
+ moduleResolution: 'node16',
87
+ esModuleInterop: true,
88
+ },
89
+ });
90
+ return;
91
+ }
92
+ catch {
93
+ // fallback
94
+ }
95
+ try {
96
+ require.resolve('tsx', { paths: [cwd] });
97
+ require('tsx/cjs');
98
+ }
99
+ catch {
100
+ // no transpiler available
101
+ }
102
+ }
103
+ function setupStaticGenerationRuntime(cwd) {
104
+ if (staticRuntimeReady)
105
+ return;
106
+ // Ignore CSS imports while requiring app modules for prerender.
107
+ require.extensions['.css'] = (m, filename) => {
108
+ if (filename.endsWith('.module.css')) {
109
+ m.exports = {};
110
+ }
111
+ };
112
+ installSingleReactResolution(cwd);
113
+ setupTypeScriptRuntime(cwd);
114
+ staticRuntimeReady = true;
115
+ }
20
116
  // ---------------------------------------------------------------------------
21
117
  // Static param expansion
22
118
  // ---------------------------------------------------------------------------
@@ -25,6 +121,7 @@ const static_cache_1 = require("./static-cache");
25
121
  * and return the list of param sets.
26
122
  */
27
123
  async function resolveStaticParams(route, cwd) {
124
+ setupStaticGenerationRuntime(cwd);
28
125
  if (!route.hasGenerateStaticParams) {
29
126
  return [];
30
127
  }
@@ -80,6 +177,7 @@ function expandPattern(pattern, params) {
80
177
  * handled by the upstream process.
81
178
  */
82
179
  async function prerenderPage(urlPath, route, params, cwd) {
180
+ setupStaticGenerationRuntime(cwd);
83
181
  try {
84
182
  const React = require('react');
85
183
  const { renderToString } = require('react-dom/server');
@@ -20,7 +20,7 @@ const path_1 = __importDefault(require("path"));
20
20
  // ============================================================================
21
21
  const FILE_EXTENSIONS = ['.tsx', '.ts', '.jsx', '.js'];
22
22
  const RESERVED_INTERNAL_SEGMENTS = new Set(['[not-found]']);
23
- const VALID_SEGMENT_PATTERN = /^[a-zA-Z0-9_\-]+$|^\[\[\.\.\.[\w\-]+\]\]$|^\[[\w\-]+\]$|^\[\.\.\.[\ w\-]+\]$|^\([\w\-]+\)$/;
23
+ const VALID_SEGMENT_PATTERN = /^[a-zA-Z0-9_\-]+$|^\[\[\.\.\.[\w\-]+\]\]$|^\[[\w\-]+\]$|^\[\.\.\.[\w\-]+\]$|^\([\w\-]+\)$/;
24
24
  const CONVENTION_FILES = new Set([
25
25
  'page',
26
26
  'layout',
@@ -0,0 +1,16 @@
1
+ import type express from 'express';
2
+ import type { ResolvedTypedApiConfig } from '../config';
3
+ export declare function resolveLegacyApiRoutePath(cwd: string, requestPath: string): string | null;
4
+ export declare function runLegacyApiRoute(options: {
5
+ req: express.Request;
6
+ res: express.Response;
7
+ apiPath: string;
8
+ isDev: boolean;
9
+ }): Promise<void>;
10
+ export declare function runTypedApiRoute(options: {
11
+ req: express.Request;
12
+ res: express.Response;
13
+ cwd: string;
14
+ isDev: boolean;
15
+ config: ResolvedTypedApiConfig;
16
+ }): Promise<boolean>;
@@ -0,0 +1,336 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.resolveLegacyApiRoutePath = resolveLegacyApiRoutePath;
7
+ exports.runLegacyApiRoute = runLegacyApiRoute;
8
+ exports.runTypedApiRoute = runTypedApiRoute;
9
+ const fs_1 = __importDefault(require("fs"));
10
+ const path_1 = __importDefault(require("path"));
11
+ const server_1 = require("../stack/server");
12
+ const TYPED_API_ENTRYPOINTS = [
13
+ path_1.default.join('app', 'api', 'typed.ts'),
14
+ path_1.default.join('app', 'api', 'typed.tsx'),
15
+ path_1.default.join('app', 'api', 'typed.js'),
16
+ path_1.default.join('app', 'api', 'typed.jsx'),
17
+ path_1.default.join('app', 'typed-api.ts'),
18
+ path_1.default.join('app', 'typed-api.tsx'),
19
+ path_1.default.join('app', 'typed-api.js'),
20
+ path_1.default.join('app', 'typed-api.jsx'),
21
+ ];
22
+ class BodyLimitError extends Error {
23
+ status = 413;
24
+ constructor(limitBytes) {
25
+ super(`Typed API body exceeds configured limit (${limitBytes} bytes)`);
26
+ this.name = 'BodyLimitError';
27
+ }
28
+ }
29
+ class BodyParseError extends Error {
30
+ status = 400;
31
+ constructor(message) {
32
+ super(message);
33
+ this.name = 'BodyParseError';
34
+ }
35
+ }
36
+ function isStackRouterLike(value) {
37
+ if (!value || typeof value !== 'object') {
38
+ return false;
39
+ }
40
+ const candidate = value;
41
+ return (!!candidate.procedures &&
42
+ !!candidate.routes &&
43
+ !!candidate.metadata &&
44
+ typeof candidate.resolve === 'function');
45
+ }
46
+ function resolveTypedRouterFromModule(mod) {
47
+ const candidates = [
48
+ mod?.default,
49
+ mod?.router,
50
+ mod?.typedRouter,
51
+ mod?.api,
52
+ typeof mod?.createRouter === 'function' ? mod.createRouter() : null,
53
+ typeof mod?.createTypedRouter === 'function' ? mod.createTypedRouter() : null,
54
+ ];
55
+ for (const candidate of candidates) {
56
+ if (isStackRouterLike(candidate)) {
57
+ return candidate;
58
+ }
59
+ }
60
+ return null;
61
+ }
62
+ function normalizeApiPath(pathname) {
63
+ if (!pathname.startsWith('/api')) {
64
+ return pathname || '/';
65
+ }
66
+ const stripped = pathname.slice('/api'.length);
67
+ return stripped ? stripped : '/';
68
+ }
69
+ function buildPathCandidates(pathname) {
70
+ const normalized = pathname || '/';
71
+ const apiNormalized = normalizeApiPath(normalized);
72
+ const dedup = new Set([normalized, apiNormalized]);
73
+ return Array.from(dedup);
74
+ }
75
+ function hasMethodMatch(router, pathname, method) {
76
+ const normalized = method.toLowerCase();
77
+ return router.resolve(pathname, normalized) !== null;
78
+ }
79
+ function hasRouteForAnyMethod(router, pathname) {
80
+ return hasMethodMatch(router, pathname, 'get') || hasMethodMatch(router, pathname, 'post');
81
+ }
82
+ async function parseRequestBody(req, bodySizeLimitBytes) {
83
+ if (req.method === 'GET' || req.method === 'HEAD') {
84
+ return undefined;
85
+ }
86
+ const chunks = [];
87
+ let size = 0;
88
+ for await (const chunk of req) {
89
+ const buffer = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
90
+ size += buffer.length;
91
+ if (size > bodySizeLimitBytes) {
92
+ throw new BodyLimitError(bodySizeLimitBytes);
93
+ }
94
+ chunks.push(buffer);
95
+ }
96
+ if (chunks.length === 0) {
97
+ return undefined;
98
+ }
99
+ const raw = Buffer.concat(chunks);
100
+ const contentType = String(req.headers['content-type'] || '')
101
+ .split(';')[0]
102
+ .trim()
103
+ .toLowerCase();
104
+ if (!contentType || contentType === 'application/json' || contentType.endsWith('+json')) {
105
+ try {
106
+ return JSON.parse(raw.toString('utf-8'));
107
+ }
108
+ catch {
109
+ throw new BodyParseError('Invalid JSON body for typed API request.');
110
+ }
111
+ }
112
+ if (contentType === 'application/x-www-form-urlencoded') {
113
+ return Object.fromEntries(new URLSearchParams(raw.toString('utf-8')).entries());
114
+ }
115
+ if (contentType.startsWith('text/')) {
116
+ return raw.toString('utf-8');
117
+ }
118
+ return raw;
119
+ }
120
+ async function sendFetchResponse(res, response) {
121
+ response.headers.forEach((value, key) => {
122
+ res.setHeader(key, value);
123
+ });
124
+ const arrayBuffer = await response.arrayBuffer();
125
+ const body = Buffer.from(arrayBuffer);
126
+ res.status(response.status).send(body);
127
+ }
128
+ function getTypedApiEntrypoint(cwd) {
129
+ for (const relativePath of TYPED_API_ENTRYPOINTS) {
130
+ const absolutePath = path_1.default.resolve(cwd, relativePath);
131
+ if (fs_1.default.existsSync(absolutePath)) {
132
+ return absolutePath;
133
+ }
134
+ }
135
+ return null;
136
+ }
137
+ async function executeTypedRoute(router, options) {
138
+ const pathCandidates = buildPathCandidates(options.req.path);
139
+ const method = options.method.toLowerCase();
140
+ let selectedPath = null;
141
+ let routeExistsForDifferentMethod = false;
142
+ for (const candidate of pathCandidates) {
143
+ if (hasMethodMatch(router, candidate, method)) {
144
+ selectedPath = candidate;
145
+ break;
146
+ }
147
+ if (hasRouteForAnyMethod(router, candidate)) {
148
+ routeExistsForDifferentMethod = true;
149
+ }
150
+ }
151
+ if (!selectedPath) {
152
+ if (routeExistsForDifferentMethod) {
153
+ return {
154
+ kind: 'method-not-allowed',
155
+ status: 405,
156
+ error: `Method ${method.toUpperCase()} not allowed`,
157
+ };
158
+ }
159
+ return { kind: 'not-found' };
160
+ }
161
+ const result = await (0, server_1.executeRoute)(router, {
162
+ path: selectedPath,
163
+ method,
164
+ req: {
165
+ method,
166
+ path: selectedPath,
167
+ query: options.query,
168
+ body: options.body,
169
+ headers: options.req.headers,
170
+ originalUrl: options.req.originalUrl,
171
+ url: options.req.url,
172
+ },
173
+ ctx: options.context,
174
+ env: options.env,
175
+ serialization: options.serialization,
176
+ });
177
+ return {
178
+ kind: 'handled',
179
+ status: 200,
180
+ payload: result.serializedData,
181
+ };
182
+ }
183
+ function resolveLegacyApiRoutePath(cwd, requestPath) {
184
+ if (!requestPath.startsWith('/api/')) {
185
+ return null;
186
+ }
187
+ const apiRoute = requestPath.substring('/api/'.length);
188
+ const routeCandidates = [
189
+ path_1.default.resolve(cwd, 'app', 'api', apiRoute, 'route.ts'),
190
+ path_1.default.resolve(cwd, 'app', 'api', apiRoute, 'route.tsx'),
191
+ path_1.default.resolve(cwd, 'app', 'api', apiRoute, 'route.js'),
192
+ path_1.default.resolve(cwd, 'app', 'api', apiRoute, 'route.jsx'),
193
+ path_1.default.resolve(cwd, 'app', 'api', `${apiRoute}.ts`),
194
+ path_1.default.resolve(cwd, 'app', 'api', `${apiRoute}.tsx`),
195
+ path_1.default.resolve(cwd, 'app', 'api', `${apiRoute}.js`),
196
+ path_1.default.resolve(cwd, 'app', 'api', `${apiRoute}.jsx`),
197
+ ];
198
+ for (const routePath of routeCandidates) {
199
+ if (fs_1.default.existsSync(routePath)) {
200
+ return routePath;
201
+ }
202
+ }
203
+ return null;
204
+ }
205
+ async function runLegacyApiRoute(options) {
206
+ const { req, res, apiPath, isDev } = options;
207
+ if (isDev) {
208
+ delete require.cache[require.resolve(apiPath)];
209
+ }
210
+ const apiModule = require(apiPath);
211
+ const method = req.method?.toUpperCase() || 'GET';
212
+ const methodHandler = apiModule[method];
213
+ if (typeof methodHandler === 'function') {
214
+ const request = {
215
+ url: req.protocol + '://' + req.get('host') + req.originalUrl,
216
+ method: req.method,
217
+ headers: new Map(Object.entries(req.headers)),
218
+ json: async () => req.body,
219
+ text: async () => JSON.stringify(req.body),
220
+ nextUrl: {
221
+ pathname: req.path,
222
+ searchParams: new URLSearchParams(req.query),
223
+ },
224
+ };
225
+ const result = await methodHandler(request, { params: {} });
226
+ if (result && typeof result.json === 'function') {
227
+ const json = await result.json();
228
+ res.status(result.status || 200).json(json);
229
+ return;
230
+ }
231
+ if (result !== undefined) {
232
+ res.status(200).json(result);
233
+ return;
234
+ }
235
+ res.status(204).end();
236
+ return;
237
+ }
238
+ if (typeof apiModule.default === 'function') {
239
+ apiModule.default(req, res);
240
+ return;
241
+ }
242
+ res.status(405).json({ error: `Method ${method} not allowed` });
243
+ }
244
+ async function runTypedApiRoute(options) {
245
+ const { req, res, cwd, isDev, config } = options;
246
+ if (!config.enabled) {
247
+ return false;
248
+ }
249
+ const entrypoint = getTypedApiEntrypoint(cwd);
250
+ if (!entrypoint) {
251
+ return false;
252
+ }
253
+ try {
254
+ if (isDev) {
255
+ delete require.cache[require.resolve(entrypoint)];
256
+ }
257
+ const typedModule = require(entrypoint);
258
+ const router = resolveTypedRouterFromModule(typedModule);
259
+ if (!router) {
260
+ res.status(500).json({
261
+ error: `Typed API entrypoint "${path_1.default.relative(cwd, entrypoint)}" does not export a valid stack router.`,
262
+ });
263
+ return true;
264
+ }
265
+ const method = (req.method || 'GET').toUpperCase();
266
+ const body = await parseRequestBody(req, config.bodySizeLimitBytes);
267
+ const query = (req.query ?? {});
268
+ const contextFactory = typeof typedModule.createContext === 'function' ? typedModule.createContext : null;
269
+ const envFactory = typeof typedModule.createEnv === 'function' ? typedModule.createEnv : null;
270
+ const context = contextFactory ? await contextFactory({ req, res }) : {};
271
+ const env = envFactory ? await envFactory({ req, res }) : {};
272
+ const routeResult = await executeTypedRoute(router, {
273
+ req,
274
+ method,
275
+ query,
276
+ body,
277
+ serialization: config.serialization,
278
+ context: context ?? {},
279
+ env,
280
+ });
281
+ if (routeResult.kind === 'not-found') {
282
+ return false;
283
+ }
284
+ if (routeResult.kind === 'method-not-allowed') {
285
+ res.status(routeResult.status).json({ error: routeResult.error });
286
+ return true;
287
+ }
288
+ res.status(routeResult.status).json(routeResult.payload);
289
+ return true;
290
+ }
291
+ catch (error) {
292
+ const typedError = error;
293
+ if (typedError instanceof BodyLimitError || typedError instanceof BodyParseError) {
294
+ res.status(typedError.status).json({ error: typedError.message });
295
+ return true;
296
+ }
297
+ if (typedError instanceof server_1.StackValidationError ||
298
+ typedError instanceof server_1.StackMethodNotAllowedError) {
299
+ const status = typeof typedError.status === 'number' ? typedError.status : 400;
300
+ res.status(status).json({ error: typedError.message });
301
+ return true;
302
+ }
303
+ if (typedError instanceof server_1.StackRouteNotFoundError) {
304
+ return false;
305
+ }
306
+ // Router-level error handler gets first chance.
307
+ try {
308
+ const entrypoint = getTypedApiEntrypoint(cwd);
309
+ if (entrypoint) {
310
+ if (isDev) {
311
+ delete require.cache[require.resolve(entrypoint)];
312
+ }
313
+ const typedModule = require(entrypoint);
314
+ const router = resolveTypedRouterFromModule(typedModule);
315
+ const errorHandler = router?.metadata?.errorHandler;
316
+ if (typeof errorHandler === 'function') {
317
+ const response = errorHandler(error, {
318
+ method: req.method,
319
+ path: req.path,
320
+ query: (req.query ?? {}),
321
+ headers: req.headers,
322
+ });
323
+ if (response instanceof Response) {
324
+ await sendFetchResponse(res, response);
325
+ return true;
326
+ }
327
+ }
328
+ }
329
+ }
330
+ catch {
331
+ // Ignore fallback handler errors and use generic 500 response below.
332
+ }
333
+ res.status(500).json({ error: 'Internal Server Error in Typed API' });
334
+ return true;
335
+ }
336
+ }
@@ -0,0 +1,2 @@
1
+ import type { AppRouterLike, ClientRoutes, CreateVistaClientOptions, VistaClient } from './types';
2
+ export declare function createVistaClient<TAppRouter extends AppRouterLike, TRoutes = ClientRoutes<TAppRouter>>(options?: CreateVistaClientOptions): VistaClient<TRoutes>;