@vistagenic/vista 0.2.16 → 0.3.2

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 (153) hide show
  1. package/bin/vista.js +184 -177
  2. package/dist/ai/agent.d.ts +37 -0
  3. package/dist/ai/agent.js +385 -0
  4. package/dist/ai/embeddings.d.ts +11 -0
  5. package/dist/ai/embeddings.js +71 -0
  6. package/dist/ai/index.d.ts +10 -0
  7. package/dist/ai/index.js +26 -0
  8. package/dist/ai/memory.d.ts +16 -0
  9. package/dist/ai/memory.js +39 -0
  10. package/dist/ai/observability.d.ts +21 -0
  11. package/dist/ai/observability.js +69 -0
  12. package/dist/ai/providers/anthropic.d.ts +2 -0
  13. package/dist/ai/providers/anthropic.js +190 -0
  14. package/dist/ai/providers/base.d.ts +7 -0
  15. package/dist/ai/providers/base.js +85 -0
  16. package/dist/ai/providers/gemini.d.ts +2 -0
  17. package/dist/ai/providers/gemini.js +194 -0
  18. package/dist/ai/providers/index.d.ts +5 -0
  19. package/dist/ai/providers/index.js +21 -0
  20. package/dist/ai/providers/mock.d.ts +8 -0
  21. package/dist/ai/providers/mock.js +77 -0
  22. package/dist/ai/providers/openai.d.ts +2 -0
  23. package/dist/ai/providers/openai.js +211 -0
  24. package/dist/ai/rag.d.ts +35 -0
  25. package/dist/ai/rag.js +110 -0
  26. package/dist/ai/react/index.d.ts +1 -0
  27. package/dist/ai/react/index.js +17 -0
  28. package/dist/ai/react/react-server.d.ts +1 -0
  29. package/dist/ai/react/react-server.js +17 -0
  30. package/dist/ai/react/use-agent.d.ts +23 -0
  31. package/dist/ai/react/use-agent.js +138 -0
  32. package/dist/ai/stream.d.ts +24 -0
  33. package/dist/ai/stream.js +109 -0
  34. package/dist/ai/tool.d.ts +11 -0
  35. package/dist/ai/tool.js +47 -0
  36. package/dist/ai/types.d.ts +117 -0
  37. package/dist/ai/types.js +5 -0
  38. package/dist/auth/core.d.ts +140 -0
  39. package/dist/auth/core.js +165 -0
  40. package/dist/auth/index.d.ts +20 -8
  41. package/dist/auth/index.js +369 -16
  42. package/dist/auth/react-server.d.ts +1 -0
  43. package/dist/auth/react-server.js +10 -0
  44. package/dist/auth/react.d.ts +18 -0
  45. package/dist/auth/react.js +71 -0
  46. package/dist/bin/build-rsc.js +602 -604
  47. package/dist/bin/build.js +389 -388
  48. package/dist/bin/deploy-output.d.ts +2 -7
  49. package/dist/bin/deploy-output.js +3 -76
  50. package/dist/bin/deploy.d.ts +7 -0
  51. package/dist/bin/deploy.js +102 -0
  52. package/dist/bin/dev-error-overlay-snippet.js +778 -552
  53. package/dist/bin/devtools-indicator-snippet.js +387 -387
  54. package/dist/bin/generate.js +510 -248
  55. package/dist/build/manifest.d.ts +169 -139
  56. package/dist/build/manifest.js +423 -367
  57. package/dist/build/rsc/client-manifest.d.ts +62 -52
  58. package/dist/build/rsc/client-manifest.js +295 -193
  59. package/dist/build/rsc/client-reference-plugin.d.ts +37 -37
  60. package/dist/build/rsc/client-reference-plugin.js +160 -160
  61. package/dist/build/rsc/native-scanner.d.ts +135 -123
  62. package/dist/build/rsc/native-scanner.js +203 -170
  63. package/dist/build/rsc/react-client-reference-manifest.d.ts +28 -22
  64. package/dist/build/rsc/react-client-reference-manifest.js +240 -219
  65. package/dist/build/rsc/rsc-renderer.d.ts +99 -99
  66. package/dist/build/rsc/rsc-renderer.js +263 -263
  67. package/dist/build/rsc/server-component-loader.d.ts +19 -19
  68. package/dist/build/rsc/server-component-loader.js +91 -91
  69. package/dist/build/rsc/server-manifest.d.ts +17 -0
  70. package/dist/build/rsc/server-manifest.js +20 -0
  71. package/dist/build/standalone.js +337 -334
  72. package/dist/build/webpack/plugins/vista-flight-plugin.js +5 -5
  73. package/dist/client/dynamic.react-server.d.ts +2 -0
  74. package/dist/client/dynamic.react-server.js +23 -0
  75. package/dist/client/link.react-server.d.ts +2 -0
  76. package/dist/client/link.react-server.js +11 -0
  77. package/dist/client/navigation.react-server.d.ts +1 -0
  78. package/dist/client/navigation.react-server.js +17 -0
  79. package/dist/client/router.react-server.d.ts +1 -0
  80. package/dist/client/router.react-server.js +17 -0
  81. package/dist/client/rsc-router.react-server.d.ts +1 -0
  82. package/dist/client/rsc-router.react-server.js +17 -0
  83. package/dist/client/script.react-server.d.ts +2 -0
  84. package/dist/client/script.react-server.js +23 -0
  85. package/dist/components/client-island.d.ts +34 -34
  86. package/dist/components/client-island.js +75 -75
  87. package/dist/config.d.ts +17 -0
  88. package/dist/config.js +60 -1
  89. package/dist/deploy/adapters/cloudflare.d.ts +2 -0
  90. package/dist/deploy/adapters/cloudflare.js +124 -0
  91. package/dist/deploy/adapters/docker.d.ts +2 -0
  92. package/dist/deploy/adapters/docker.js +112 -0
  93. package/dist/deploy/adapters/index.d.ts +15 -0
  94. package/dist/deploy/adapters/index.js +24 -0
  95. package/dist/deploy/adapters/netlify.d.ts +2 -0
  96. package/dist/deploy/adapters/netlify.js +115 -0
  97. package/dist/deploy/adapters/render.d.ts +2 -0
  98. package/dist/deploy/adapters/render.js +95 -0
  99. package/dist/deploy/adapters/vercel.d.ts +7 -0
  100. package/dist/deploy/adapters/vercel.js +164 -0
  101. package/dist/deploy/cli-runner.d.ts +8 -0
  102. package/dist/deploy/cli-runner.js +58 -0
  103. package/dist/deploy/detect.d.ts +6 -0
  104. package/dist/deploy/detect.js +77 -0
  105. package/dist/deploy/index.d.ts +24 -0
  106. package/dist/deploy/index.js +112 -0
  107. package/dist/deploy/preflight.d.ts +8 -0
  108. package/dist/deploy/preflight.js +80 -0
  109. package/dist/deploy/types.d.ts +48 -0
  110. package/dist/deploy/types.js +2 -0
  111. package/dist/deploy/utils.d.ts +18 -0
  112. package/dist/deploy/utils.js +70 -0
  113. package/dist/dev-error.d.ts +14 -0
  114. package/dist/dev-error.js +1763 -705
  115. package/dist/index.d.ts +23 -21
  116. package/dist/index.js +61 -57
  117. package/dist/server/cookie-parse.d.ts +4 -0
  118. package/dist/server/cookie-parse.js +14 -0
  119. package/dist/server/engine.js +7 -26
  120. package/dist/server/index.d.ts +109 -102
  121. package/dist/server/index.js +315 -286
  122. package/dist/server/middleware-runner.d.ts +125 -57
  123. package/dist/server/middleware-runner.js +615 -218
  124. package/dist/server/middleware-security.d.ts +36 -0
  125. package/dist/server/middleware-security.js +183 -0
  126. package/dist/server/module-compile-hook.js +695 -662
  127. package/dist/server/route-handler-registry.d.ts +65 -0
  128. package/dist/server/route-handler-registry.js +195 -0
  129. package/dist/server/route-patterns.d.ts +67 -0
  130. package/dist/server/route-patterns.js +186 -0
  131. package/dist/server/rsc-engine.js +75 -92
  132. package/dist/server/rsc-module-system.d.ts +33 -33
  133. package/dist/server/rsc-module-system.js +87 -87
  134. package/dist/server/rsc-upstream.js +892 -888
  135. package/dist/server/static-generator.js +10 -10
  136. package/dist/server/typed-api-runtime.d.ts +18 -0
  137. package/dist/server/typed-api-runtime.js +635 -507
  138. package/dist/server/vista-import-map.js +134 -123
  139. package/dist/stack/index.d.ts +34 -32
  140. package/dist/stack/index.js +49 -45
  141. package/dist/stack/server/caller.d.ts +13 -0
  142. package/dist/stack/server/caller.js +42 -0
  143. package/dist/stack/server/executor.d.ts +40 -36
  144. package/dist/stack/server/executor.js +222 -174
  145. package/dist/stack/server/index.d.ts +11 -10
  146. package/dist/stack/server/index.js +24 -23
  147. package/dist/stack/server/procedure.d.ts +20 -18
  148. package/dist/stack/server/procedure.js +66 -58
  149. package/dist/stack/server/types.d.ts +113 -100
  150. package/dist/theme/react-server.d.ts +10 -0
  151. package/dist/theme/react-server.js +16 -0
  152. package/package.json +28 -3
  153. package/LICENSE +0 -21
@@ -1,218 +1,615 @@
1
- "use strict";
2
- /**
3
- * Vista Middleware Runner
4
- *
5
- * Shared middleware execution logic used by both the standard SSR engine
6
- * and the RSC engine. Discovers `middleware.ts` / `.tsx` / `.js` at the
7
- * project root, constructs a NextRequest-like object from the Express
8
- * request, invokes the user middleware, and returns a disposition that
9
- * the caller can act on.
10
- */
11
- var __importDefault = (this && this.__importDefault) || function (mod) {
12
- return (mod && mod.__esModule) ? mod : { "default": mod };
13
- };
14
- Object.defineProperty(exports, "__esModule", { value: true });
15
- exports.runMiddleware = runMiddleware;
16
- exports.applyMiddlewareResult = applyMiddlewareResult;
17
- const path_1 = __importDefault(require("path"));
18
- const fs_1 = __importDefault(require("fs"));
19
- // ---------------------------------------------------------------------------
20
- // Middleware discovery cache (per-cwd)
21
- // ---------------------------------------------------------------------------
22
- const discoveryCache = new Map();
23
- function discoverMiddleware(cwd, bustCache) {
24
- if (!bustCache && discoveryCache.has(cwd)) {
25
- return discoveryCache.get(cwd);
26
- }
27
- const candidates = [
28
- path_1.default.resolve(cwd, 'middleware.ts'),
29
- path_1.default.resolve(cwd, 'middleware.tsx'),
30
- path_1.default.resolve(cwd, 'middleware.js'),
31
- ];
32
- for (const p of candidates) {
33
- if (fs_1.default.existsSync(p)) {
34
- discoveryCache.set(cwd, p);
35
- return p;
36
- }
37
- }
38
- discoveryCache.set(cwd, null);
39
- return null;
40
- }
41
- // ---------------------------------------------------------------------------
42
- // Build NextRequest-like object
43
- // ---------------------------------------------------------------------------
44
- function buildNextRequest(req) {
45
- const protocol = req.protocol;
46
- const host = req.get('host') || 'localhost';
47
- const fullUrl = `${protocol}://${host}${req.originalUrl}`;
48
- return {
49
- url: fullUrl,
50
- method: req.method,
51
- headers: new Map(Object.entries(req.headers)),
52
- nextUrl: {
53
- pathname: req.path,
54
- searchParams: new URLSearchParams(req.query),
55
- href: fullUrl,
56
- origin: `${protocol}://${host}`,
57
- },
58
- cookies: {
59
- get: (name) => req.cookies?.[name] ? { name, value: req.cookies[name] } : undefined,
60
- getAll: () => Object.entries(req.cookies || {}).map(([n, v]) => ({
61
- name: n,
62
- value: v,
63
- })),
64
- has: (name) => !!req.cookies?.[name],
65
- },
66
- };
67
- }
68
- // ---------------------------------------------------------------------------
69
- // Matcher support
70
- // ---------------------------------------------------------------------------
71
- /**
72
- * Evaluate the optional `config.matcher` exported alongside the middleware.
73
- * Returns `true` if the request matches (or if no matcher is defined).
74
- */
75
- function shouldRunMiddleware(middlewareModule, pathname) {
76
- const config = middlewareModule.config;
77
- if (!config?.matcher)
78
- return true;
79
- const matchers = Array.isArray(config.matcher) ? config.matcher : [config.matcher];
80
- return matchers.some((pattern) => {
81
- // Simple path-prefix matching with basic wildcard support
82
- // e.g. '/dashboard/:path*' → matches /dashboard, /dashboard/settings …
83
- const re = patternToRegExp(pattern);
84
- return re.test(pathname);
85
- });
86
- }
87
- function patternToRegExp(pattern) {
88
- // Convert Next.js-style matcher patterns to RegExp:
89
- // /foo/:path* → /foo(/.*)?
90
- // /foo/:bar → /foo/[^/]+
91
- // /foo/* → /foo(/.*)?
92
- let re = pattern
93
- .replace(/:[^/]+\*/g, '(.*)') // :path*
94
- .replace(/:[^/]+/g, '[^/]+') // :param
95
- .replace(/\*/g, '(.*)'); // bare *
96
- return new RegExp(`^${re}(/)?$`);
97
- }
98
- // ---------------------------------------------------------------------------
99
- // Public API
100
- // ---------------------------------------------------------------------------
101
- /**
102
- * Run user-defined middleware for the given request.
103
- *
104
- * @param req Express request
105
- * @param cwd Project root (where middleware.ts lives)
106
- * @param isDev Whether we're in dev mode (busts require cache)
107
- */
108
- async function runMiddleware(req, cwd, isDev) {
109
- const middlewareFile = discoverMiddleware(cwd, isDev);
110
- if (!middlewareFile) {
111
- return { kind: 'skip' };
112
- }
113
- try {
114
- // Hot-reload: bust require cache in dev
115
- if (isDev) {
116
- try {
117
- delete require.cache[require.resolve(middlewareFile)];
118
- }
119
- catch {
120
- // resolve may throw if file was just deleted — treat as skip
121
- discoveryCache.delete(cwd);
122
- return { kind: 'skip' };
123
- }
124
- }
125
- const middlewareModule = require(middlewareFile);
126
- const middleware = middlewareModule.default || middlewareModule.middleware;
127
- if (typeof middleware !== 'function') {
128
- return { kind: 'skip' };
129
- }
130
- // Matcher check
131
- if (!shouldRunMiddleware(middlewareModule, req.path)) {
132
- return { kind: 'skip' };
133
- }
134
- const nextRequest = buildNextRequest(req);
135
- const response = await middleware(nextRequest);
136
- if (!response) {
137
- return { kind: 'next' };
138
- }
139
- // Collect response headers the middleware may have set
140
- const responseHeaders = new Map();
141
- if (response.headers && typeof response.headers.forEach === 'function') {
142
- response.headers.forEach((value, key) => {
143
- responseHeaders.set(key, value);
144
- });
145
- }
146
- // 1. Redirect
147
- const location = response.headers?.get?.('Location');
148
- if (location) {
149
- return {
150
- kind: 'redirect',
151
- status: response.status || 307,
152
- location,
153
- responseHeaders,
154
- };
155
- }
156
- // 2. Rewrite
157
- const rewrite = response.headers?.get?.('x-middleware-rewrite');
158
- if (rewrite) {
159
- return {
160
- kind: 'rewrite',
161
- location: rewrite,
162
- responseHeaders,
163
- };
164
- }
165
- // 3. Continue
166
- const shouldContinue = response.headers?.get?.('x-middleware-next');
167
- if (shouldContinue) {
168
- return { kind: 'next', responseHeaders };
169
- }
170
- // 4. Short-circuit (non-200 status with no continue/redirect/rewrite)
171
- if (response.status && response.status !== 200) {
172
- return {
173
- kind: 'short-circuit',
174
- status: response.status,
175
- responseHeaders,
176
- };
177
- }
178
- // Default — continue
179
- return { kind: 'next', responseHeaders };
180
- }
181
- catch (err) {
182
- console.error(`[vista] Middleware error: ${err?.message ?? String(err)}`);
183
- // On error, let the request continue rather than crashing
184
- return { kind: 'next' };
185
- }
186
- }
187
- /**
188
- * Apply a MiddlewareResult to the Express request/response.
189
- * Returns `true` if the response was finalized (caller should `return`),
190
- * `false` if the request should continue to the next handler.
191
- */
192
- function applyMiddlewareResult(result, req, res) {
193
- // Forward any response headers the middleware set
194
- if (result.responseHeaders) {
195
- result.responseHeaders.forEach((value, key) => {
196
- // Skip internal headers
197
- if (key === 'x-middleware-next' || key === 'x-middleware-rewrite' || key === 'Location') {
198
- return;
199
- }
200
- res.setHeader(key, value);
201
- });
202
- }
203
- switch (result.kind) {
204
- case 'redirect':
205
- res.redirect(result.status || 307, result.location);
206
- return true;
207
- case 'rewrite':
208
- req.url = result.location;
209
- return false; // continue with rewritten URL
210
- case 'short-circuit':
211
- res.status(result.status || 403).end();
212
- return true;
213
- case 'next':
214
- case 'skip':
215
- default:
216
- return false; // continue
217
- }
218
- }
1
+ "use strict";
2
+ /**
3
+ * Vista Middleware Runner
4
+ *
5
+ * Built-in middleware system for Vista.js.
6
+ * Supports:
7
+ * 1. Global middleware (top-level middleware.ts / middleware.js at cwd or src/)
8
+ * 2. Route-specific middleware (co-located in app/ directory segments, e.g. app/api/auth/middleware.ts)
9
+ * 3. Exported middleware from route files (e.g. export const middleware = ... in page.tsx or route.ts)
10
+ * 4. Middleware signature: middleware({ request, next }) matching Issue #7 Part 5
11
+ * 5. Middleware chaining and clearly defined execution order:
12
+ * Global -> Segment middlewares (shallowest to deepest) -> Route-level middleware -> Handler
13
+ * 6. Request modification (headers, URL rewrites) and Response modification / short-circuiting
14
+ */
15
+ var __importDefault = (this && this.__importDefault) || function (mod) {
16
+ return (mod && mod.__esModule) ? mod : { "default": mod };
17
+ };
18
+ Object.defineProperty(exports, "__esModule", { value: true });
19
+ exports.clearMiddlewareCaches = clearMiddlewareCaches;
20
+ exports.discoverGlobalMiddleware = discoverGlobalMiddleware;
21
+ exports.discoverRouteMiddlewares = discoverRouteMiddlewares;
22
+ exports.patternToRegExp = patternToRegExp;
23
+ exports.shouldRunMiddleware = shouldRunMiddleware;
24
+ exports.buildNextRequest = buildNextRequest;
25
+ exports.runMiddleware = runMiddleware;
26
+ exports.applyMiddlewareResult = applyMiddlewareResult;
27
+ const path_1 = __importDefault(require("path"));
28
+ const fs_1 = __importDefault(require("fs"));
29
+ const cookie_parse_1 = require("./cookie-parse");
30
+ const middleware_security_1 = require("./middleware-security");
31
+ // ---------------------------------------------------------------------------
32
+ // Discovery Caches
33
+ // ---------------------------------------------------------------------------
34
+ const globalDiscoveryCache = new Map();
35
+ const routeDiscoveryCache = new Map();
36
+ const MIDDLEWARE_FILE_EXTS = ['.ts', '.tsx', '.js', '.jsx'];
37
+ const ROUTE_FILE_STEMS = ['page', 'route', 'index', 'layout'];
38
+ function clearMiddlewareCaches() {
39
+ globalDiscoveryCache.clear();
40
+ routeDiscoveryCache.clear();
41
+ }
42
+ // ---------------------------------------------------------------------------
43
+ // Helpers
44
+ // ---------------------------------------------------------------------------
45
+ function getAppDir(cwd) {
46
+ const candidates = [path_1.default.join(cwd, 'app'), path_1.default.join(cwd, 'src', 'app')];
47
+ for (const candidate of candidates) {
48
+ if (fs_1.default.existsSync(candidate) && fs_1.default.statSync(candidate).isDirectory()) {
49
+ return candidate;
50
+ }
51
+ }
52
+ return null;
53
+ }
54
+ /**
55
+ * Discover top-level global middleware.
56
+ * Checks `<cwd>/middleware.*` then `<cwd>/src/middleware.*`.
57
+ */
58
+ function discoverGlobalMiddleware(cwd, bustCache) {
59
+ if (!bustCache && globalDiscoveryCache.has(cwd)) {
60
+ return globalDiscoveryCache.get(cwd);
61
+ }
62
+ const searchDirs = [cwd, path_1.default.join(cwd, 'src')];
63
+ for (const dir of searchDirs) {
64
+ for (const ext of MIDDLEWARE_FILE_EXTS) {
65
+ const candidate = path_1.default.join(dir, `middleware${ext}`);
66
+ if (fs_1.default.existsSync(candidate) && fs_1.default.statSync(candidate).isFile()) {
67
+ globalDiscoveryCache.set(cwd, candidate);
68
+ return candidate;
69
+ }
70
+ }
71
+ }
72
+ globalDiscoveryCache.set(cwd, null);
73
+ return null;
74
+ }
75
+ /**
76
+ * Discover route-specific middleware files along the path hierarchy.
77
+ * Returns file paths from shallowest segment to deepest segment.
78
+ */
79
+ function discoverRouteMiddlewares(cwd, pathname, bustCache) {
80
+ const cacheKey = `${cwd}:${pathname}`;
81
+ if (!bustCache && routeDiscoveryCache.has(cacheKey)) {
82
+ return routeDiscoveryCache.get(cacheKey);
83
+ }
84
+ const appDir = getAppDir(cwd);
85
+ if (!appDir) {
86
+ routeDiscoveryCache.set(cacheKey, []);
87
+ return [];
88
+ }
89
+ const globalMiddleware = discoverGlobalMiddleware(cwd, bustCache);
90
+ const results = [];
91
+ // 1. Check app/middleware.* (if different from global middleware)
92
+ for (const ext of MIDDLEWARE_FILE_EXTS) {
93
+ const appLevel = path_1.default.join(appDir, `middleware${ext}`);
94
+ if (fs_1.default.existsSync(appLevel) && fs_1.default.statSync(appLevel).isFile() && appLevel !== globalMiddleware) {
95
+ results.push(appLevel);
96
+ break;
97
+ }
98
+ }
99
+ // 2. Traverse route segments from root to leaf
100
+ const segments = pathname.split('/').filter(Boolean);
101
+ let currentDir = appDir;
102
+ for (let i = 0; i < segments.length; i++) {
103
+ const seg = segments[i];
104
+ let nextDir = null;
105
+ // Direct segment match (e.g. app/api)
106
+ const exactDir = path_1.default.join(currentDir, seg);
107
+ if (fs_1.default.existsSync(exactDir) && fs_1.default.statSync(exactDir).isDirectory()) {
108
+ nextDir = exactDir;
109
+ }
110
+ else {
111
+ // Dynamic route segment match (e.g. [id], [...slug])
112
+ try {
113
+ const entries = fs_1.default.readdirSync(currentDir, { withFileTypes: true });
114
+ const dynamicEntry = entries.find((e) => e.isDirectory() &&
115
+ e.name.startsWith('[') &&
116
+ e.name.endsWith(']') &&
117
+ e.name !== '[not-found]');
118
+ if (dynamicEntry) {
119
+ nextDir = path_1.default.join(currentDir, dynamicEntry.name);
120
+ }
121
+ }
122
+ catch {
123
+ // Ignore read errors
124
+ }
125
+ }
126
+ if (!nextDir) {
127
+ break;
128
+ }
129
+ currentDir = nextDir;
130
+ // Check for co-located middleware in this segment directory
131
+ for (const ext of MIDDLEWARE_FILE_EXTS) {
132
+ const segmentMiddleware = path_1.default.join(currentDir, `middleware${ext}`);
133
+ if (fs_1.default.existsSync(segmentMiddleware) &&
134
+ fs_1.default.statSync(segmentMiddleware).isFile() &&
135
+ segmentMiddleware !== globalMiddleware &&
136
+ !results.includes(segmentMiddleware)) {
137
+ results.push(segmentMiddleware);
138
+ break;
139
+ }
140
+ }
141
+ }
142
+ // 3. At leaf directory, check for exported middleware in route files (page.tsx, route.ts, etc.)
143
+ for (const stem of ROUTE_FILE_STEMS) {
144
+ for (const ext of MIDDLEWARE_FILE_EXTS) {
145
+ const candidateRouteFile = path_1.default.join(currentDir, `${stem}${ext}`);
146
+ if (fs_1.default.existsSync(candidateRouteFile) &&
147
+ fs_1.default.statSync(candidateRouteFile).isFile() &&
148
+ !results.includes(candidateRouteFile)) {
149
+ // Record candidate route file to inspect for exported middleware
150
+ results.push(candidateRouteFile);
151
+ }
152
+ }
153
+ }
154
+ routeDiscoveryCache.set(cacheKey, results);
155
+ return results;
156
+ }
157
+ // ---------------------------------------------------------------------------
158
+ // Matcher Support
159
+ // ---------------------------------------------------------------------------
160
+ function patternToRegExp(pattern) {
161
+ // Convert Next.js-style / path pattern to RegExp:
162
+ // /foo/:path* → /foo(?:/(.*))?
163
+ // /foo/:bar → /foo/[^/]+
164
+ // /foo/* → /foo(?:/(.*))?
165
+ let re = pattern
166
+ .replace(/\/:[^/]+\*/g, '(?:/(.*))?') // /:path* (0 or more sub-paths)
167
+ .replace(/:[^/]+\*/g, '(.*)') // bare :path*
168
+ .replace(/:[^/]+/g, '[^/]+') // :param (single segment)
169
+ .replace(/\/\*/g, '(?:/(.*))?') // /*
170
+ .replace(/\*/g, '(.*)'); // bare *
171
+ return new RegExp(`^${re}/?$`);
172
+ }
173
+ function shouldRunMiddleware(middlewareModule, pathname, request) {
174
+ const config = middlewareModule.config;
175
+ if (!config?.matcher)
176
+ return true;
177
+ const matchers = Array.isArray(config.matcher)
178
+ ? config.matcher
179
+ : [config.matcher];
180
+ return matchers.some((matcher) => {
181
+ try {
182
+ const source = typeof matcher === 'string' ? matcher : matcher.source;
183
+ const re = patternToRegExp(source);
184
+ if (!re.test(pathname)) {
185
+ return false;
186
+ }
187
+ if (typeof matcher === 'string' || !request) {
188
+ return true;
189
+ }
190
+ if (matcher.has && !matcher.has.every((rule) => matchRequestRule(request, rule))) {
191
+ return false;
192
+ }
193
+ if (matcher.missing && !matcher.missing.every((rule) => !matchRequestRule(request, rule))) {
194
+ return false;
195
+ }
196
+ return true;
197
+ }
198
+ catch {
199
+ // Fail closed: a broken matcher must not skip auth middleware.
200
+ return true;
201
+ }
202
+ });
203
+ }
204
+ function matchRequestRule(request, rule) {
205
+ let actual;
206
+ if (rule.type === 'header') {
207
+ actual = request.headers.get(rule.key) || undefined;
208
+ }
209
+ else if (rule.type === 'cookie') {
210
+ actual = request.cookies.get(rule.key)?.value;
211
+ }
212
+ else {
213
+ actual = request.nextUrl.searchParams.get(rule.key) || undefined;
214
+ }
215
+ if (rule.value === undefined) {
216
+ return Boolean(actual);
217
+ }
218
+ return actual === rule.value;
219
+ }
220
+ // ---------------------------------------------------------------------------
221
+ // Build NextRequest-like Object
222
+ // ---------------------------------------------------------------------------
223
+ function buildNextRequest(req) {
224
+ const protocol = req.protocol || 'http';
225
+ const host = (req.get && req.get('host')) || req.headers?.host || 'localhost';
226
+ const rawPath = req.path || (req.url ? req.url.split('?')[0] : '/');
227
+ const fullUrl = `${protocol}://${host}${req.originalUrl || req.url || rawPath}`;
228
+ const headers = new Headers();
229
+ if (req.headers) {
230
+ for (const [key, val] of Object.entries(req.headers)) {
231
+ if (val !== undefined) {
232
+ if (Array.isArray(val)) {
233
+ for (const item of val) {
234
+ headers.append(key, item);
235
+ }
236
+ }
237
+ else {
238
+ headers.set(key, String(val));
239
+ }
240
+ }
241
+ }
242
+ }
243
+ const queryParams = new URLSearchParams(typeof req.query === 'object' && req.query !== null
244
+ ? req.query
245
+ : {});
246
+ // Cookie helpers
247
+ const rawCookies = req.cookies || {};
248
+ if (!Object.keys(rawCookies).length && req.headers?.cookie) {
249
+ for (const pair of String(req.headers.cookie).split(';')) {
250
+ const [k, ...v] = pair.split('=');
251
+ const name = k?.trim();
252
+ if (name) {
253
+ rawCookies[name] = (0, cookie_parse_1.safeDecodeURIComponent)(v.join('=').trim());
254
+ }
255
+ }
256
+ }
257
+ return {
258
+ url: fullUrl,
259
+ method: req.method || 'GET',
260
+ path: rawPath,
261
+ headers,
262
+ nextUrl: {
263
+ pathname: rawPath,
264
+ searchParams: queryParams,
265
+ href: fullUrl,
266
+ origin: `${protocol}://${host}`,
267
+ },
268
+ cookies: {
269
+ get: (name) => rawCookies[name] !== undefined ? { name, value: String(rawCookies[name]) } : undefined,
270
+ getAll: () => Object.entries(rawCookies).map(([n, v]) => ({
271
+ name: n,
272
+ value: v,
273
+ })),
274
+ has: (name) => rawCookies[name] !== undefined,
275
+ },
276
+ };
277
+ }
278
+ function cloneRequestWithHeaders(req, newHeaders) {
279
+ const mergedHeaders = new Headers(req.headers);
280
+ if (newHeaders instanceof Headers) {
281
+ newHeaders.forEach((val, key) => mergedHeaders.set(key, val));
282
+ }
283
+ else {
284
+ for (const [k, v] of Object.entries(newHeaders)) {
285
+ mergedHeaders.set(k, String(v));
286
+ }
287
+ }
288
+ return {
289
+ ...req,
290
+ headers: mergedHeaders,
291
+ };
292
+ }
293
+ // ---------------------------------------------------------------------------
294
+ // Execution Pipeline
295
+ // ---------------------------------------------------------------------------
296
+ function loadModule(filePath, isDev) {
297
+ try {
298
+ if (isDev) {
299
+ try {
300
+ delete require.cache[require.resolve(filePath)];
301
+ }
302
+ catch { }
303
+ }
304
+ return require(filePath);
305
+ }
306
+ catch (err) {
307
+ console.warn(`[vista:middleware] Failed to load middleware module ${filePath}:`, err?.message);
308
+ return null;
309
+ }
310
+ }
311
+ /**
312
+ * Run user-defined middleware chain (global + route-specific) for the given request.
313
+ *
314
+ * Execution Order:
315
+ * 1. Global middleware (cwd/middleware.ts or cwd/src/middleware.ts)
316
+ * 2. Route segment middlewares (from shallowest to deepest in app/)
317
+ * 3. Route file middleware (exported `middleware` from page.tsx/route.ts)
318
+ * 4. Downstream handler (Page component or API route handler)
319
+ *
320
+ * Returns a `MiddlewareResult` indicating whether the request was redirected,
321
+ * short-circuited with a response, rewritten, or continued to the next handler.
322
+ */
323
+ async function runMiddleware(req, cwd, isDev = false) {
324
+ const pathname = req.path || (req.url ? req.url.split('?')[0] : '/');
325
+ // 1. Discover Global Middleware
326
+ const globalFile = discoverGlobalMiddleware(cwd, isDev);
327
+ const entries = [];
328
+ if (globalFile) {
329
+ entries.push({
330
+ source: 'global',
331
+ filePath: globalFile,
332
+ depth: 0,
333
+ });
334
+ }
335
+ // 2. Discover Route-Specific Middlewares
336
+ const routeFiles = discoverRouteMiddlewares(cwd, pathname, isDev);
337
+ for (let i = 0; i < routeFiles.length; i++) {
338
+ const file = routeFiles[i];
339
+ const isMiddlewareFile = path_1.default.basename(file).startsWith('middleware.');
340
+ entries.push({
341
+ source: isMiddlewareFile ? 'segment' : 'route',
342
+ filePath: file,
343
+ depth: i + 1,
344
+ });
345
+ }
346
+ // If no middleware discovered, skip directly
347
+ if (entries.length === 0) {
348
+ return { kind: 'skip' };
349
+ }
350
+ const activeChain = [];
351
+ for (const entry of entries) {
352
+ const mod = loadModule(entry.filePath, isDev);
353
+ if (!mod) {
354
+ // Dedicated middleware files must fail closed. Skipping them would
355
+ // silently bypass auth. Route files may fail for unrelated reasons.
356
+ if (entry.source !== 'route') {
357
+ return {
358
+ kind: 'short-circuit',
359
+ status: 500,
360
+ body: 'Middleware Error',
361
+ };
362
+ }
363
+ continue;
364
+ }
365
+ // In a route file (e.g. page.tsx, route.ts), we ONLY accept an explicit `export const middleware`
366
+ // or `export function middleware` (not `export default`, which is the page component)
367
+ let fn;
368
+ if (entry.source === 'route') {
369
+ if (typeof mod.middleware === 'function') {
370
+ fn = mod.middleware;
371
+ }
372
+ }
373
+ else {
374
+ // In a dedicated middleware.* file, accept either `export default` or `export function middleware`
375
+ if (typeof mod.middleware === 'function') {
376
+ fn = mod.middleware;
377
+ }
378
+ else if (typeof mod.default === 'function') {
379
+ fn = mod.default;
380
+ }
381
+ }
382
+ if (!fn) {
383
+ if (entry.source !== 'route') {
384
+ return {
385
+ kind: 'short-circuit',
386
+ status: 500,
387
+ body: 'Middleware Error',
388
+ };
389
+ }
390
+ continue;
391
+ }
392
+ // Check optional matcher config
393
+ if (!shouldRunMiddleware(mod, pathname, buildNextRequest(req))) {
394
+ continue;
395
+ }
396
+ activeChain.push({
397
+ filePath: entry.filePath,
398
+ source: entry.source,
399
+ fn,
400
+ allowedRedirectHosts: mod.config?.allowedRedirectHosts,
401
+ });
402
+ }
403
+ if (activeChain.length === 0) {
404
+ return { kind: 'skip' };
405
+ }
406
+ // 4. Execute the chain
407
+ let currentRequest = buildNextRequest(req);
408
+ const aggregatedResponseHeaders = new Map();
409
+ const modifiedRequestHeaders = new Map();
410
+ async function dispatch(index, requestObj) {
411
+ if (index >= activeChain.length) {
412
+ // Terminal node: reached end of middleware chain
413
+ return new Response(null, {
414
+ status: 200,
415
+ headers: {
416
+ 'x-middleware-next': '1',
417
+ },
418
+ });
419
+ }
420
+ const currentItem = activeChain[index];
421
+ let nextCalled = false;
422
+ let nextRequestObj = requestObj;
423
+ const nextFn = async (options) => {
424
+ nextCalled = true;
425
+ if (options?.request?.headers) {
426
+ const sanitized = (0, middleware_security_1.sanitizeRequestHeaderMap)(options.request.headers);
427
+ const headerRecord = {};
428
+ sanitized.forEach((value, key) => {
429
+ headerRecord[key] = value;
430
+ modifiedRequestHeaders.set(key, value);
431
+ });
432
+ nextRequestObj = cloneRequestWithHeaders(requestObj, headerRecord);
433
+ }
434
+ return dispatch(index + 1, nextRequestObj);
435
+ };
436
+ const context = {
437
+ request: requestObj,
438
+ next: nextFn,
439
+ url: requestObj.url,
440
+ method: requestObj.method,
441
+ headers: requestObj.headers,
442
+ nextUrl: requestObj.nextUrl,
443
+ cookies: requestObj.cookies,
444
+ };
445
+ try {
446
+ // Invoke middleware. Supports:
447
+ // - middleware({ request, next }) [Issue #7 Part 5 signature]
448
+ // - middleware(request, next)
449
+ // - middleware(request)
450
+ const output = await currentItem.fn(context, nextFn);
451
+ // Handle direct Response return
452
+ if (output instanceof Response) {
453
+ return output;
454
+ }
455
+ // If next() was explicitly called and returned nothing, propagate standard continuation
456
+ if (nextCalled) {
457
+ return new Response(null, {
458
+ status: 200,
459
+ headers: {
460
+ 'x-middleware-next': '1',
461
+ },
462
+ });
463
+ }
464
+ // If middleware returned void without calling next(), continue to next in chain
465
+ return dispatch(index + 1, requestObj);
466
+ }
467
+ catch (err) {
468
+ console.error(`[vista:middleware] Error in middleware at ${currentItem.filePath}:`, err?.message ?? String(err));
469
+ if (process.env.NODE_ENV === 'production') {
470
+ return new Response('Middleware Error', { status: 500 });
471
+ }
472
+ return dispatch(index + 1, requestObj);
473
+ }
474
+ }
475
+ const finalResponse = await dispatch(0, currentRequest);
476
+ // 5. Convert final Response to MiddlewareResult
477
+ // Collect all response headers
478
+ if (finalResponse.headers && typeof finalResponse.headers.forEach === 'function') {
479
+ finalResponse.headers.forEach((val, key) => {
480
+ const lower = key.toLowerCase();
481
+ if (lower.startsWith('x-middleware-request-')) {
482
+ const name = lower.slice('x-middleware-request-'.length);
483
+ if (!name || name === 'host' || name === 'cookie' || name.startsWith('x-forwarded-') || name.startsWith('x-middleware-')) {
484
+ return;
485
+ }
486
+ modifiedRequestHeaders.set(name, val);
487
+ return;
488
+ }
489
+ aggregatedResponseHeaders.set(key, val);
490
+ });
491
+ }
492
+ if (process.env.NODE_ENV === 'production') {
493
+ (0, middleware_security_1.securityHeaders)().forEach((value, key) => {
494
+ if (!aggregatedResponseHeaders.has(key) && !aggregatedResponseHeaders.has(key.toLowerCase())) {
495
+ aggregatedResponseHeaders.set(key, value);
496
+ }
497
+ });
498
+ }
499
+ const allowedRedirectHosts = activeChain.flatMap((item) => item.allowedRedirectHosts || []);
500
+ const requestUrl = currentRequest.url;
501
+ // 5a. Redirect
502
+ const location = finalResponse.headers?.get?.('Location') || finalResponse.headers?.get?.('location');
503
+ if (location) {
504
+ if (!(0, middleware_security_1.isSafeRedirectLocation)(location, requestUrl, allowedRedirectHosts)) {
505
+ return {
506
+ kind: 'short-circuit',
507
+ status: 400,
508
+ responseHeaders: aggregatedResponseHeaders,
509
+ requestHeaders: modifiedRequestHeaders,
510
+ body: 'Invalid redirect',
511
+ };
512
+ }
513
+ return {
514
+ kind: 'redirect',
515
+ status: finalResponse.status || 307,
516
+ location,
517
+ responseHeaders: aggregatedResponseHeaders,
518
+ requestHeaders: modifiedRequestHeaders,
519
+ };
520
+ }
521
+ // 5b. Rewrite
522
+ const rewrite = finalResponse.headers?.get?.('x-middleware-rewrite');
523
+ if (rewrite) {
524
+ if (!(0, middleware_security_1.isSafeRewriteLocation)(rewrite)) {
525
+ return {
526
+ kind: 'short-circuit',
527
+ status: 400,
528
+ responseHeaders: aggregatedResponseHeaders,
529
+ requestHeaders: modifiedRequestHeaders,
530
+ body: 'Invalid rewrite',
531
+ };
532
+ }
533
+ return {
534
+ kind: 'rewrite',
535
+ location: rewrite,
536
+ responseHeaders: aggregatedResponseHeaders,
537
+ requestHeaders: modifiedRequestHeaders,
538
+ };
539
+ }
540
+ // 5c. Continue to page/route handler
541
+ const shouldContinue = finalResponse.headers?.get?.('x-middleware-next');
542
+ if (shouldContinue) {
543
+ return {
544
+ kind: 'next',
545
+ responseHeaders: aggregatedResponseHeaders,
546
+ requestHeaders: modifiedRequestHeaders,
547
+ };
548
+ }
549
+ // 5d. Short-circuit with response body and custom status
550
+ let bodyContent = null;
551
+ try {
552
+ bodyContent = await finalResponse.text();
553
+ }
554
+ catch { }
555
+ return {
556
+ kind: 'short-circuit',
557
+ status: finalResponse.status || 200,
558
+ responseHeaders: aggregatedResponseHeaders,
559
+ requestHeaders: modifiedRequestHeaders,
560
+ body: bodyContent,
561
+ };
562
+ }
563
+ // ---------------------------------------------------------------------------
564
+ // Apply Middleware Result
565
+ // ---------------------------------------------------------------------------
566
+ /**
567
+ * Apply a MiddlewareResult to the Express request/response.
568
+ * Returns `true` if the response was finalized (caller should `return`),
569
+ * `false` if the request should continue to the next handler.
570
+ */
571
+ function applyMiddlewareResult(result, req, res) {
572
+ // 1. Forward any modified request headers to req.headers
573
+ if (result.requestHeaders && req.headers) {
574
+ const sanitized = (0, middleware_security_1.sanitizeRequestHeaderMap)(result.requestHeaders);
575
+ sanitized.forEach((value, key) => {
576
+ req.headers[key] = value;
577
+ });
578
+ }
579
+ // 2. Forward any response headers the middleware set
580
+ if (result.responseHeaders) {
581
+ result.responseHeaders.forEach((value, key) => {
582
+ const lower = key.toLowerCase();
583
+ // Skip internal transport headers
584
+ if (lower === 'x-middleware-next' ||
585
+ lower === 'x-middleware-rewrite' ||
586
+ lower === 'location' ||
587
+ lower.startsWith('x-middleware-request-')) {
588
+ return;
589
+ }
590
+ res.setHeader(key, value);
591
+ });
592
+ }
593
+ switch (result.kind) {
594
+ case 'redirect':
595
+ res.redirect(result.status || 307, result.location);
596
+ return true;
597
+ case 'rewrite':
598
+ req.url = result.location;
599
+ return false; // continue with rewritten URL
600
+ case 'short-circuit': {
601
+ const status = result.status || 403;
602
+ if (result.body !== undefined && result.body !== null && result.body !== '') {
603
+ res.status(status).send(result.body);
604
+ }
605
+ else {
606
+ res.status(status).end();
607
+ }
608
+ return true;
609
+ }
610
+ case 'next':
611
+ case 'skip':
612
+ default:
613
+ return false; // continue
614
+ }
615
+ }