@vistagenic/vista 0.2.1 → 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 (38) hide show
  1. package/LICENSE +21 -0
  2. package/bin/vista.js +30 -20
  3. package/dist/bin/build-rsc.js +6 -4
  4. package/dist/bin/generate.d.ts +7 -0
  5. package/dist/bin/generate.js +248 -0
  6. package/dist/config.d.ts +19 -0
  7. package/dist/config.js +62 -4
  8. package/dist/server/engine.js +23 -57
  9. package/dist/server/rsc-engine.js +21 -48
  10. package/dist/server/static-generator.js +98 -0
  11. package/dist/server/structure-validator.js +1 -1
  12. package/dist/server/typed-api-runtime.d.ts +16 -0
  13. package/dist/server/typed-api-runtime.js +336 -0
  14. package/dist/stack/client/create-client.d.ts +2 -0
  15. package/dist/stack/client/create-client.js +195 -0
  16. package/dist/stack/client/error.d.ts +18 -0
  17. package/dist/stack/client/error.js +22 -0
  18. package/dist/stack/client/index.d.ts +9 -0
  19. package/dist/stack/client/index.js +13 -0
  20. package/dist/stack/client/types.d.ts +39 -0
  21. package/dist/stack/client/types.js +2 -0
  22. package/dist/stack/index.d.ts +32 -0
  23. package/dist/stack/index.js +45 -0
  24. package/dist/stack/server/executor.d.ts +36 -0
  25. package/dist/stack/server/executor.js +174 -0
  26. package/dist/stack/server/index.d.ts +10 -0
  27. package/dist/stack/server/index.js +23 -0
  28. package/dist/stack/server/merge-routers.d.ts +2 -0
  29. package/dist/stack/server/merge-routers.js +80 -0
  30. package/dist/stack/server/procedure.d.ts +18 -0
  31. package/dist/stack/server/procedure.js +58 -0
  32. package/dist/stack/server/router.d.ts +9 -0
  33. package/dist/stack/server/router.js +80 -0
  34. package/dist/stack/server/serialization.d.ts +9 -0
  35. package/dist/stack/server/serialization.js +119 -0
  36. package/dist/stack/server/types.d.ts +100 -0
  37. package/dist/stack/server/types.js +2 -0
  38. package/package.json +11 -2
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2026 Vista.js contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/bin/vista.js CHANGED
@@ -1,12 +1,20 @@
1
1
  #!/usr/bin/env node
2
2
 
3
- const command = process.argv[2];
4
- const flags = process.argv.slice(3);
5
-
6
- // RSC is the default mode (like Next.js App Router)
7
- // Use --legacy to fall back to traditional SSR mode
8
- const useLegacy = flags.includes('--legacy') || process.env.VISTA_LEGACY === 'true';
9
- const useRSC = !useLegacy;
3
+ const command = process.argv[2];
4
+ const flags = process.argv.slice(3);
5
+
6
+ if (command === 'g' || command === 'generate') {
7
+ const { runGenerateCommand } = require('../dist/bin/generate');
8
+ runGenerateCommand(flags).then((code) => {
9
+ if (code !== 0) process.exit(code);
10
+ });
11
+ return;
12
+ }
13
+
14
+ // RSC is the default mode (like Next.js App Router)
15
+ // Use --legacy to fall back to traditional SSR mode
16
+ const useLegacy = flags.includes('--legacy') || process.env.VISTA_LEGACY === 'true';
17
+ const useRSC = !useLegacy;
10
18
 
11
19
  // Mark startup time for "Ready in Xms" display
12
20
  const { markStartTime } = require('../dist/server/logger');
@@ -85,16 +93,18 @@ if (command === 'dev') {
85
93
  console.log('Usage: vista <command> [options]');
86
94
  console.log('');
87
95
  console.log('Commands:');
88
- console.log(' dev Start development server with HMR');
89
- console.log(' build Create production build');
90
- console.log(' start Start production server');
91
- console.log('');
92
- console.log('Options:');
93
- console.log(' --legacy Use traditional SSR mode (instead of RSC)');
94
- console.log('');
95
- console.log('Examples:');
96
- console.log(' vista dev # Start dev server (RSC mode)');
97
- console.log(' vista dev --legacy # Start dev server with legacy SSR');
98
- console.log(' vista build # Production build with RSC');
99
- console.log('');
100
- }
96
+ console.log(' dev Start development server with HMR');
97
+ console.log(' build Create production build');
98
+ console.log(' start Start production server');
99
+ console.log(' g Generate typed API scaffolds (api-init, router, procedure)');
100
+ console.log('');
101
+ console.log('Options:');
102
+ console.log(' --legacy Use traditional SSR mode (instead of RSC)');
103
+ console.log('');
104
+ console.log('Examples:');
105
+ console.log(' vista dev # Start dev server (RSC mode)');
106
+ console.log(' vista dev --legacy # Start dev server with legacy SSR');
107
+ console.log(' vista build # Production build with RSC');
108
+ console.log(' vista g api-init # Generate typed API starter files');
109
+ console.log('');
110
+ }
@@ -53,7 +53,7 @@ function runPostCSS(cwd, vistaDir) {
53
53
  /**
54
54
  * Generate the RSC-aware client entry file
55
55
  */
56
- function generateRSCClientEntry(cwd, vistaDir) {
56
+ function generateRSCClientEntry(cwd, vistaDir, isDev) {
57
57
  const clientEntryContent = `/**
58
58
  * Vista RSC Client Entry
59
59
  *
@@ -89,7 +89,8 @@ hydrateRoot(
89
89
  })
90
90
  );
91
91
 
92
- // Vista live-reload: listen for server component changes via SSE
92
+ ${isDev
93
+ ? `// Vista live-reload: listen for server component changes via SSE
93
94
  (function connectReload() {
94
95
  const es = new EventSource('${constants_1.SSE_ENDPOINT}');
95
96
  es.onmessage = (e) => {
@@ -108,7 +109,8 @@ hydrateRoot(
108
109
  es.close();
109
110
  setTimeout(connectReload, 3000);
110
111
  };
111
- })();
112
+ })();`
113
+ : '// SSE live-reload disabled in production'}
112
114
  `;
113
115
  fs_1.default.writeFileSync(path_1.default.join(vistaDir, 'rsc-client.tsx'), clientEntryContent);
114
116
  if (_debug)
@@ -257,7 +259,7 @@ async function buildRSC(watch = false) {
257
259
  type: route.type,
258
260
  })));
259
261
  // Generate client entry
260
- generateRSCClientEntry(cwd, vistaDirs.root);
262
+ generateRSCClientEntry(cwd, vistaDirs.root, watch);
261
263
  // Create webpack configs
262
264
  const options = {
263
265
  cwd,
@@ -0,0 +1,7 @@
1
+ interface RunGenerateOptions {
2
+ cwd?: string;
3
+ log?: (message: string) => void;
4
+ error?: (message: string) => void;
5
+ }
6
+ export declare function runGenerateCommand(args: string[], options?: RunGenerateOptions): Promise<number>;
7
+ export {};
@@ -0,0 +1,248 @@
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.runGenerateCommand = runGenerateCommand;
7
+ const fs_1 = __importDefault(require("fs"));
8
+ const path_1 = __importDefault(require("path"));
9
+ function toKebabCase(value) {
10
+ return value
11
+ .trim()
12
+ .replace(/([a-z0-9])([A-Z])/g, '$1-$2')
13
+ .replace(/[^a-zA-Z0-9]+/g, '-')
14
+ .replace(/^-+|-+$/g, '')
15
+ .toLowerCase();
16
+ }
17
+ function toPascalCase(value) {
18
+ return toKebabCase(value)
19
+ .split('-')
20
+ .filter(Boolean)
21
+ .map((segment) => segment[0].toUpperCase() + segment.slice(1))
22
+ .join('');
23
+ }
24
+ function toCamelCase(value) {
25
+ const pascal = toPascalCase(value);
26
+ return pascal ? pascal[0].toLowerCase() + pascal.slice(1) : '';
27
+ }
28
+ function ensureDirectory(targetDir) {
29
+ fs_1.default.mkdirSync(targetDir, { recursive: true });
30
+ }
31
+ function writeFileIfMissing(baseDir, relativePath, content) {
32
+ const absolutePath = path_1.default.join(baseDir, relativePath);
33
+ if (fs_1.default.existsSync(absolutePath)) {
34
+ return { path: absolutePath, created: false };
35
+ }
36
+ ensureDirectory(path_1.default.dirname(absolutePath));
37
+ fs_1.default.writeFileSync(absolutePath, content, 'utf8');
38
+ return { path: absolutePath, created: true };
39
+ }
40
+ function findMatchingBrace(source, openBraceIndex) {
41
+ let depth = 0;
42
+ for (let i = openBraceIndex; i < source.length; i++) {
43
+ const char = source[i];
44
+ if (char === '{')
45
+ depth++;
46
+ if (char === '}')
47
+ depth--;
48
+ if (depth === 0)
49
+ return i;
50
+ }
51
+ return -1;
52
+ }
53
+ function insertTypedApiConfigIntoObject(source, objectStartIndex) {
54
+ const openBraceIndex = source.indexOf('{', objectStartIndex);
55
+ if (openBraceIndex < 0) {
56
+ return null;
57
+ }
58
+ const closeBraceIndex = findMatchingBrace(source, openBraceIndex);
59
+ if (closeBraceIndex < 0) {
60
+ return null;
61
+ }
62
+ const before = source.slice(0, closeBraceIndex);
63
+ const after = source.slice(closeBraceIndex);
64
+ const insertion = `\n experimental: {\n typedApi: {\n enabled: true,\n },\n },`;
65
+ return `${before}${insertion}${after}`;
66
+ }
67
+ function ensureTypedApiEnabledInConfig(cwd) {
68
+ const tsPath = path_1.default.join(cwd, 'vista.config.ts');
69
+ const jsPath = path_1.default.join(cwd, 'vista.config.js');
70
+ if (!fs_1.default.existsSync(tsPath) && !fs_1.default.existsSync(jsPath)) {
71
+ const configSource = [
72
+ 'const config = {',
73
+ ' experimental: {',
74
+ ' typedApi: {',
75
+ ' enabled: true,',
76
+ ' },',
77
+ ' },',
78
+ '};',
79
+ '',
80
+ 'export default config;',
81
+ '',
82
+ ].join('\n');
83
+ fs_1.default.writeFileSync(tsPath, configSource, 'utf8');
84
+ return 'created';
85
+ }
86
+ const targetPath = fs_1.default.existsSync(tsPath) ? tsPath : jsPath;
87
+ const source = fs_1.default.readFileSync(targetPath, 'utf8');
88
+ if (/\btypedApi\b/.test(source) && /\benabled\s*:\s*true\b/.test(source)) {
89
+ return 'unchanged';
90
+ }
91
+ const constConfigIndex = source.indexOf('const config');
92
+ if (constConfigIndex >= 0) {
93
+ const updated = insertTypedApiConfigIntoObject(source, constConfigIndex);
94
+ if (updated) {
95
+ fs_1.default.writeFileSync(targetPath, updated, 'utf8');
96
+ return 'updated';
97
+ }
98
+ }
99
+ const exportDefaultIndex = source.indexOf('export default');
100
+ if (exportDefaultIndex >= 0) {
101
+ const updated = insertTypedApiConfigIntoObject(source, exportDefaultIndex);
102
+ if (updated) {
103
+ fs_1.default.writeFileSync(targetPath, updated, 'utf8');
104
+ return 'updated';
105
+ }
106
+ }
107
+ return 'manual';
108
+ }
109
+ function renderApiInitEntrypoint() {
110
+ return [
111
+ "import { vstack } from 'vista/stack';",
112
+ "import { createRootRouter } from './routers';",
113
+ '',
114
+ 'const v = vstack.init();',
115
+ '',
116
+ 'export const router = createRootRouter(v);',
117
+ '',
118
+ ].join('\n');
119
+ }
120
+ function renderRootRouter() {
121
+ return [
122
+ "import type { VStackInstance } from 'vista/stack';",
123
+ "import { healthProcedure } from '../procedures/health';",
124
+ '',
125
+ 'export function createRootRouter(v: VStackInstance<any, any>) {',
126
+ ' return v.router({',
127
+ ' health: healthProcedure(v),',
128
+ ' });',
129
+ '}',
130
+ '',
131
+ ].join('\n');
132
+ }
133
+ function renderProcedure(name, method) {
134
+ const safeName = toCamelCase(name);
135
+ const functionName = `${safeName}Procedure`;
136
+ const procedureMethod = method === 'post' ? 'mutation' : 'query';
137
+ const sampleResult = method === 'post'
138
+ ? " ok: true,\n message: 'Mutation executed',"
139
+ : " ok: true,\n message: 'Query executed',";
140
+ return [
141
+ "import type { VStackInstance } from 'vista/stack';",
142
+ '',
143
+ `export function ${functionName}(v: VStackInstance<any, any>) {`,
144
+ ` return v.procedure.${procedureMethod}(() => ({`,
145
+ sampleResult,
146
+ ' }));',
147
+ '}',
148
+ '',
149
+ ].join('\n');
150
+ }
151
+ function renderRouter(name) {
152
+ const pascal = toPascalCase(name);
153
+ const camel = toCamelCase(name);
154
+ return [
155
+ "import type { VStackInstance } from 'vista/stack';",
156
+ '',
157
+ `export function create${pascal}Router(v: VStackInstance<any, any>) {`,
158
+ ' return v.router({',
159
+ ` ${camel}: v.procedure.query(() => ({`,
160
+ ` route: '${toKebabCase(name)}',`,
161
+ " ok: true,",
162
+ ' })),',
163
+ ' });',
164
+ '}',
165
+ '',
166
+ ].join('\n');
167
+ }
168
+ function printGenerateUsage(log) {
169
+ log('Vista generator usage:');
170
+ log(' vista g api-init');
171
+ log(' vista g router <name>');
172
+ log(' vista g procedure <name> [get|post]');
173
+ }
174
+ async function runGenerateCommand(args, options = {}) {
175
+ const cwd = options.cwd ?? process.cwd();
176
+ const log = options.log ?? console.log;
177
+ const error = options.error ?? console.error;
178
+ const command = (args[0] || '').toLowerCase();
179
+ if (!command || !['api-init', 'router', 'procedure'].includes(command)) {
180
+ printGenerateUsage(log);
181
+ return 1;
182
+ }
183
+ if (command === 'api-init') {
184
+ const writes = [
185
+ writeFileIfMissing(cwd, path_1.default.join('app', 'api', 'typed.ts'), renderApiInitEntrypoint()),
186
+ writeFileIfMissing(cwd, path_1.default.join('app', 'api', 'routers', 'index.ts'), renderRootRouter()),
187
+ writeFileIfMissing(cwd, path_1.default.join('app', 'api', 'procedures', 'health.ts'), renderProcedure('health', 'get')),
188
+ ];
189
+ const configState = ensureTypedApiEnabledInConfig(cwd);
190
+ writes.forEach((result) => {
191
+ const relativePath = path_1.default.relative(cwd, result.path).replace(/\\/g, '/');
192
+ log(`${result.created ? 'created' : 'skipped'} ${relativePath}`);
193
+ });
194
+ if (configState === 'created') {
195
+ log('created vista.config.ts with experimental.typedApi.enabled = true');
196
+ }
197
+ else if (configState === 'updated') {
198
+ log('updated vista.config.* to enable experimental typed API');
199
+ }
200
+ else if (configState === 'unchanged') {
201
+ log('typed API config already enabled');
202
+ }
203
+ else {
204
+ error('Could not update vista.config automatically. Please enable experimental.typedApi.enabled manually.');
205
+ return 1;
206
+ }
207
+ return 0;
208
+ }
209
+ if (command === 'router') {
210
+ const rawName = args[1];
211
+ if (!rawName) {
212
+ error('Missing router name. Example: vista g router users');
213
+ return 1;
214
+ }
215
+ const safeName = toKebabCase(rawName);
216
+ if (!safeName) {
217
+ error(`Invalid router name "${rawName}".`);
218
+ return 1;
219
+ }
220
+ const result = writeFileIfMissing(cwd, path_1.default.join('app', 'api', 'routers', `${safeName}.ts`), renderRouter(safeName));
221
+ const relativePath = path_1.default.relative(cwd, result.path).replace(/\\/g, '/');
222
+ log(`${result.created ? 'created' : 'skipped'} ${relativePath}`);
223
+ return 0;
224
+ }
225
+ if (command === 'procedure') {
226
+ const rawName = args[1];
227
+ if (!rawName) {
228
+ error('Missing procedure name. Example: vista g procedure list-users get');
229
+ return 1;
230
+ }
231
+ const methodArg = (args[2] || 'get').toLowerCase();
232
+ if (methodArg !== 'get' && methodArg !== 'post') {
233
+ error(`Invalid procedure method "${methodArg}". Use "get" or "post".`);
234
+ return 1;
235
+ }
236
+ const safeName = toKebabCase(rawName);
237
+ if (!safeName) {
238
+ error(`Invalid procedure name "${rawName}".`);
239
+ return 1;
240
+ }
241
+ const result = writeFileIfMissing(cwd, path_1.default.join('app', 'api', 'procedures', `${safeName}.ts`), renderProcedure(safeName, methodArg));
242
+ const relativePath = path_1.default.relative(cwd, result.path).replace(/\\/g, '/');
243
+ log(`${result.created ? 'created' : 'skipped'} ${relativePath}`);
244
+ return 0;
245
+ }
246
+ printGenerateUsage(log);
247
+ return 1;
248
+ }
package/dist/config.d.ts CHANGED
@@ -1,6 +1,7 @@
1
1
  import { ImageConfig } from './image/image-config';
2
2
  export type ValidationMode = 'strict' | 'warn';
3
3
  export type ValidationLogLevel = 'compact' | 'verbose';
4
+ export type TypedApiSerialization = 'json' | 'superjson';
4
5
  export interface StructureValidationConfig {
5
6
  /** Enable structure validation. Default: true */
6
7
  enabled?: boolean;
@@ -13,6 +14,17 @@ export interface StructureValidationConfig {
13
14
  /** Debounce interval for watch events in ms. Default: 120 */
14
15
  watchDebounceMs?: number;
15
16
  }
17
+ export interface TypedApiExperimentalConfig {
18
+ /** Enable typed API runtime. Default: false */
19
+ enabled?: boolean;
20
+ /** Request/response serialization mode. Default: 'json' */
21
+ serialization?: TypedApiSerialization;
22
+ /** Maximum request body size for typed API endpoints in bytes. Default: 1MB */
23
+ bodySizeLimitBytes?: number;
24
+ }
25
+ export interface ExperimentalConfig {
26
+ typedApi?: TypedApiExperimentalConfig;
27
+ }
16
28
  export interface VistaConfig {
17
29
  images?: ImageConfig;
18
30
  react?: any;
@@ -22,11 +34,18 @@ export interface VistaConfig {
22
34
  validation?: {
23
35
  structure?: StructureValidationConfig;
24
36
  };
37
+ experimental?: ExperimentalConfig;
25
38
  }
26
39
  export declare const defaultStructureValidationConfig: Required<StructureValidationConfig>;
40
+ export declare const defaultTypedApiConfig: Required<TypedApiExperimentalConfig>;
27
41
  export declare const defaultConfig: VistaConfig;
28
42
  /**
29
43
  * Resolve the effective structure validation config merging user overrides.
30
44
  */
31
45
  export declare function resolveStructureValidationConfig(config: VistaConfig): Required<StructureValidationConfig>;
46
+ export type ResolvedTypedApiConfig = Required<TypedApiExperimentalConfig>;
47
+ /**
48
+ * Resolve and sanitize experimental typed API config.
49
+ */
50
+ export declare function resolveTypedApiConfig(config: VistaConfig): ResolvedTypedApiConfig;
32
51
  export declare function loadConfig(cwd?: string): VistaConfig;
package/dist/config.js CHANGED
@@ -3,8 +3,9 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
3
3
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.defaultConfig = exports.defaultStructureValidationConfig = void 0;
6
+ exports.defaultConfig = exports.defaultTypedApiConfig = exports.defaultStructureValidationConfig = void 0;
7
7
  exports.resolveStructureValidationConfig = resolveStructureValidationConfig;
8
+ exports.resolveTypedApiConfig = resolveTypedApiConfig;
8
9
  exports.loadConfig = loadConfig;
9
10
  const path_1 = __importDefault(require("path"));
10
11
  const fs_1 = __importDefault(require("fs"));
@@ -15,11 +16,19 @@ exports.defaultStructureValidationConfig = {
15
16
  logLevel: 'compact',
16
17
  watchDebounceMs: 120,
17
18
  };
19
+ exports.defaultTypedApiConfig = {
20
+ enabled: false,
21
+ serialization: 'json',
22
+ bodySizeLimitBytes: 1024 * 1024,
23
+ };
18
24
  exports.defaultConfig = {
19
25
  images: {},
20
26
  validation: {
21
27
  structure: { ...exports.defaultStructureValidationConfig },
22
28
  },
29
+ experimental: {
30
+ typedApi: { ...exports.defaultTypedApiConfig },
31
+ },
23
32
  };
24
33
  /**
25
34
  * Resolve the effective structure validation config merging user overrides.
@@ -30,6 +39,55 @@ function resolveStructureValidationConfig(config) {
30
39
  ...(config.validation?.structure ?? {}),
31
40
  };
32
41
  }
42
+ /**
43
+ * Resolve and sanitize experimental typed API config.
44
+ */
45
+ function resolveTypedApiConfig(config) {
46
+ const merged = {
47
+ ...exports.defaultTypedApiConfig,
48
+ ...(config.experimental?.typedApi ?? {}),
49
+ };
50
+ const serialization = merged.serialization === 'superjson' ? 'superjson' : 'json';
51
+ const parsedLimit = Number(merged.bodySizeLimitBytes);
52
+ const bodySizeLimitBytes = Number.isFinite(parsedLimit) && parsedLimit > 0
53
+ ? Math.floor(parsedLimit)
54
+ : exports.defaultTypedApiConfig.bodySizeLimitBytes;
55
+ return {
56
+ enabled: Boolean(merged.enabled),
57
+ serialization,
58
+ bodySizeLimitBytes,
59
+ };
60
+ }
61
+ function mergeConfig(userConfig) {
62
+ return {
63
+ ...exports.defaultConfig,
64
+ ...userConfig,
65
+ images: {
66
+ ...(exports.defaultConfig.images ?? {}),
67
+ ...(userConfig.images ?? {}),
68
+ },
69
+ server: {
70
+ ...(exports.defaultConfig.server ?? {}),
71
+ ...(userConfig.server ?? {}),
72
+ },
73
+ validation: {
74
+ ...(exports.defaultConfig.validation ?? {}),
75
+ ...(userConfig.validation ?? {}),
76
+ structure: {
77
+ ...exports.defaultStructureValidationConfig,
78
+ ...(userConfig.validation?.structure ?? {}),
79
+ },
80
+ },
81
+ experimental: {
82
+ ...(exports.defaultConfig.experimental ?? {}),
83
+ ...(userConfig.experimental ?? {}),
84
+ typedApi: {
85
+ ...exports.defaultTypedApiConfig,
86
+ ...(userConfig.experimental?.typedApi ?? {}),
87
+ },
88
+ },
89
+ };
90
+ }
33
91
  function loadConfig(cwd = process.cwd()) {
34
92
  const tsPath = path_1.default.join(cwd, 'vista.config.ts');
35
93
  const jsPath = path_1.default.join(cwd, 'vista.config.js');
@@ -37,15 +95,15 @@ function loadConfig(cwd = process.cwd()) {
37
95
  if (fs_1.default.existsSync(tsPath)) {
38
96
  // We assume ts-node is registered by engine or bin
39
97
  const mod = require(tsPath);
40
- return { ...exports.defaultConfig, ...(mod.default || mod) };
98
+ return mergeConfig(mod.default || mod);
41
99
  }
42
100
  else if (fs_1.default.existsSync(jsPath)) {
43
101
  const mod = require(jsPath);
44
- return { ...exports.defaultConfig, ...(mod.default || mod) };
102
+ return mergeConfig(mod.default || mod);
45
103
  }
46
104
  }
47
105
  catch (error) {
48
106
  console.error('Error loading vista.config:', error);
49
107
  }
50
- return exports.defaultConfig;
108
+ return mergeConfig({});
51
109
  }
@@ -25,6 +25,7 @@ const image_optimizer_1 = require("./image-optimizer");
25
25
  const static_cache_1 = require("./static-cache");
26
26
  const registry_1 = require("../font/registry");
27
27
  const not_found_page_1 = require("./not-found-page");
28
+ const typed_api_runtime_1 = require("./typed-api-runtime");
28
29
  const logger_1 = require("./logger");
29
30
  const structure_log_1 = require("./structure-log");
30
31
  // Support CSS imports on server runtime
@@ -188,6 +189,7 @@ function startServer(port = 3003, compiler) {
188
189
  const app = (0, express_1.default)();
189
190
  const cwd = process.cwd();
190
191
  const vistaConfig = (0, config_1.loadConfig)(cwd);
192
+ const typedApiConfig = (0, config_1.resolveTypedApiConfig)(vistaConfig);
191
193
  const isDev = process.env.NODE_ENV !== 'production';
192
194
  const appDir = path_1.default.join(cwd, 'app');
193
195
  // Allow port override from config
@@ -408,69 +410,33 @@ function startServer(port = 3003, compiler) {
408
410
  return;
409
411
  // API ROUTES SUPPORT - Next.js App Router Style
410
412
  if (req.path.startsWith('/api/')) {
411
- // Remove /api/ prefix and check for route.ts file
412
- const apiRoute = req.path.substring(5); // Remove '/api/'
413
- // Try route.ts pattern first (Next.js App Router style)
414
- const routeTsPath = path_1.default.resolve(cwd, 'app', 'api', apiRoute, 'route.ts');
415
- const routeTsxPath = path_1.default.resolve(cwd, 'app', 'api', apiRoute, 'route.tsx');
416
- // Fallback to old pattern (api/path.ts)
417
- const legacyPath = path_1.default.resolve(cwd, 'app', 'api', apiRoute + '.ts');
418
- let apiPath = null;
419
- if (fs_1.default.existsSync(routeTsPath)) {
420
- apiPath = routeTsPath;
421
- }
422
- else if (fs_1.default.existsSync(routeTsxPath)) {
423
- apiPath = routeTsxPath;
424
- }
425
- else if (fs_1.default.existsSync(legacyPath)) {
426
- apiPath = legacyPath;
427
- }
428
- if (apiPath) {
413
+ const legacyApiPath = (0, typed_api_runtime_1.resolveLegacyApiRoutePath)(cwd, req.path);
414
+ if (legacyApiPath) {
429
415
  try {
430
- delete require.cache[require.resolve(apiPath)];
431
- const apiModule = require(apiPath);
432
- // Next.js App Router style: named exports for HTTP methods
433
- const method = req.method?.toUpperCase() || 'GET';
434
- const methodHandler = apiModule[method];
435
- if (typeof methodHandler === 'function') {
436
- // Create Request-like object for App Router compatibility
437
- const request = {
438
- url: req.protocol + '://' + req.get('host') + req.originalUrl,
439
- method: req.method,
440
- headers: new Map(Object.entries(req.headers)),
441
- json: async () => req.body,
442
- text: async () => JSON.stringify(req.body),
443
- nextUrl: {
444
- pathname: req.path,
445
- searchParams: new URLSearchParams(req.query),
446
- },
447
- };
448
- const result = await methodHandler(request, { params: {} });
449
- // Handle Response object
450
- if (result && typeof result.json === 'function') {
451
- const json = await result.json();
452
- return res.status(result.status || 200).json(json);
453
- }
454
- else if (result) {
455
- return res.status(200).json(result);
456
- }
457
- return res.status(204).end();
458
- }
459
- // Fallback to default export (Pages Router style)
460
- const handler = apiModule.default;
461
- if (typeof handler === 'function') {
462
- return handler(req, res);
463
- }
464
- return res.status(405).json({ error: `Method ${method} not allowed` });
416
+ await (0, typed_api_runtime_1.runLegacyApiRoute)({
417
+ req,
418
+ res,
419
+ apiPath: legacyApiPath,
420
+ isDev,
421
+ });
422
+ return;
465
423
  }
466
- catch (err) {
467
- console.error(`[vista:ssr] API route error: ${err?.message ?? String(err)}`);
424
+ catch (error) {
425
+ console.error(`[vista:ssr] API route error: ${error?.message ?? String(error)}`);
468
426
  return res.status(500).json({ error: 'Internal Server Error in API' });
469
427
  }
470
428
  }
471
- else {
472
- return res.status(404).json({ error: 'API Route Not Found' });
429
+ const typedHandled = await (0, typed_api_runtime_1.runTypedApiRoute)({
430
+ req,
431
+ res,
432
+ cwd,
433
+ isDev,
434
+ config: typedApiConfig,
435
+ });
436
+ if (typedHandled) {
437
+ return;
473
438
  }
439
+ return res.status(404).json({ error: 'API Route Not Found' });
474
440
  }
475
441
  // ==================================================================
476
442
  // Static / ISR Cache Check