@vistagenic/vista 0.2.2 → 0.2.4

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 (42) hide show
  1. package/bin/vista.js +30 -20
  2. package/dist/bin/build-rsc.js +81 -5
  3. package/dist/bin/build.js +25 -5
  4. package/dist/bin/generate.d.ts +7 -0
  5. package/dist/bin/generate.js +248 -0
  6. package/dist/build/manifest.js +23 -5
  7. package/dist/client/link.d.ts +1 -1
  8. package/dist/client/link.js +30 -11
  9. package/dist/config.d.ts +19 -0
  10. package/dist/config.js +62 -4
  11. package/dist/server/engine.js +23 -57
  12. package/dist/server/rsc-engine.js +179 -119
  13. package/dist/server/rsc-upstream.js +24 -19
  14. package/dist/server/static-generator.js +98 -0
  15. package/dist/server/structure-validator.js +1 -1
  16. package/dist/server/typed-api-runtime.d.ts +16 -0
  17. package/dist/server/typed-api-runtime.js +336 -0
  18. package/dist/stack/client/create-client.d.ts +2 -0
  19. package/dist/stack/client/create-client.js +195 -0
  20. package/dist/stack/client/error.d.ts +18 -0
  21. package/dist/stack/client/error.js +22 -0
  22. package/dist/stack/client/index.d.ts +9 -0
  23. package/dist/stack/client/index.js +13 -0
  24. package/dist/stack/client/types.d.ts +39 -0
  25. package/dist/stack/client/types.js +2 -0
  26. package/dist/stack/index.d.ts +32 -0
  27. package/dist/stack/index.js +45 -0
  28. package/dist/stack/server/executor.d.ts +36 -0
  29. package/dist/stack/server/executor.js +174 -0
  30. package/dist/stack/server/index.d.ts +10 -0
  31. package/dist/stack/server/index.js +23 -0
  32. package/dist/stack/server/merge-routers.d.ts +2 -0
  33. package/dist/stack/server/merge-routers.js +80 -0
  34. package/dist/stack/server/procedure.d.ts +18 -0
  35. package/dist/stack/server/procedure.js +58 -0
  36. package/dist/stack/server/router.d.ts +9 -0
  37. package/dist/stack/server/router.js +80 -0
  38. package/dist/stack/server/serialization.d.ts +9 -0
  39. package/dist/stack/server/serialization.js +119 -0
  40. package/dist/stack/server/types.d.ts +100 -0
  41. package/dist/stack/server/types.js +2 -0
  42. package/package.json +11 -2
@@ -77,6 +77,25 @@ function resolveFromWorkspace(specifier, cwd) {
77
77
  function normalizeModulePath(filePath) {
78
78
  return filePath.replace(/\\/g, '/').toLowerCase();
79
79
  }
80
+ function shouldInvalidateDevModule(modulePath, cwd) {
81
+ const normalized = normalizeModulePath(modulePath);
82
+ const rootPrefix = normalizeModulePath(`${cwd}${path_1.default.sep}`);
83
+ if (!normalized.startsWith(rootPrefix))
84
+ return false;
85
+ if (normalized.includes('/node_modules/'))
86
+ return false;
87
+ if (normalized.includes(`/${constants_1.BUILD_DIR.toLowerCase()}/`))
88
+ return false;
89
+ return /\.(?:[cm]?[jt]sx?|json)$/i.test(normalized);
90
+ }
91
+ function clearProjectRequireCache(cwd) {
92
+ for (const key of Object.keys(require.cache)) {
93
+ if (!shouldInvalidateDevModule(key, cwd))
94
+ continue;
95
+ delete require.cache[key];
96
+ clientDirectiveCache.delete(key);
97
+ }
98
+ }
80
99
  function setupTypeScriptRuntime(cwd) {
81
100
  try {
82
101
  const swcPath = require.resolve('@swc-node/register', { paths: [cwd] });
@@ -174,18 +193,10 @@ function installSingleReactResolution() {
174
193
  function installClientLoadHook(cwd, createClientModuleProxy) {
175
194
  if (installedClientLoadHook)
176
195
  return;
177
- const appDir = path_1.default.join(cwd, 'app');
178
- const componentsDir = path_1.default.join(cwd, 'components');
179
- const normalizedAppDir = normalizeModulePath(appDir);
180
- const normalizedComponentsDir = normalizeModulePath(componentsDir);
181
196
  originalCompile = CjsModule.prototype._compile;
182
197
  CjsModule.prototype._compile = function (content, filename) {
183
- const normalized = normalizeModulePath(filename);
184
- const isInAppTree = normalized.startsWith(normalizedAppDir);
185
- const isInComponentsTree = normalized.startsWith(normalizedComponentsDir);
186
- if ((isInAppTree || isInComponentsTree) &&
187
- /\.[jt]sx?$/.test(filename) &&
188
- isClientBoundaryFile(filename, content)) {
198
+ const isJavaScriptModule = /\.[jt]sx?$/.test(filename);
199
+ if (isJavaScriptModule && isClientBoundaryFile(filename, content)) {
189
200
  const moduleId = (0, url_1.pathToFileURL)(filename).href;
190
201
  this.exports = createClientModuleProxy(moduleId);
191
202
  return;
@@ -254,16 +265,10 @@ function extractParams(pathname, route) {
254
265
  }
255
266
  return params;
256
267
  }
257
- async function createRouteElement(route, context, isDev) {
268
+ async function createRouteElement(route, context, isDev, cwd) {
258
269
  const { params, searchParams, req } = context;
259
270
  if (isDev) {
260
- for (const key of Object.keys(require.cache)) {
261
- if (key.includes(`${path_1.default.sep}app${path_1.default.sep}`) ||
262
- key.includes(`${path_1.default.sep}components${path_1.default.sep}`)) {
263
- delete require.cache[key];
264
- clientDirectiveCache.delete(key);
265
- }
266
- }
271
+ clearProjectRequireCache(cwd);
267
272
  }
268
273
  const PageModule = require(route.pagePath);
269
274
  const PageComponent = PageModule.default;
@@ -429,7 +434,7 @@ function startUpstream() {
429
434
  }
430
435
  const params = extractParams(pathname, route);
431
436
  const searchParams = Object.fromEntries(new URLSearchParams(req.query).entries());
432
- const element = await createRouteElement(route, { params, searchParams, req }, isDev);
437
+ const element = await createRouteElement(route, { params, searchParams, req }, isDev, cwd);
433
438
  res.setHeader('Content-Type', 'text/x-component');
434
439
  res.setHeader('Vary', 'Accept');
435
440
  const stream = flightServer.renderToPipeableStream(element, flightManifest, {
@@ -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>;