vector-framework 0.8.2 → 0.9.1
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.
- package/dist/cli/index.js +91 -52
- package/dist/cli/index.js.map +1 -1
- package/dist/cli.js +1523 -0
- package/dist/core/config-loader.d.ts +15 -0
- package/dist/core/config-loader.d.ts.map +1 -0
- package/dist/core/config-loader.js +184 -0
- package/dist/core/config-loader.js.map +1 -0
- package/dist/core/router.d.ts +1 -0
- package/dist/core/router.d.ts.map +1 -1
- package/dist/core/router.js +3 -0
- package/dist/core/router.js.map +1 -1
- package/dist/core/vector.d.ts +10 -12
- package/dist/core/vector.d.ts.map +1 -1
- package/dist/core/vector.js +36 -25
- package/dist/core/vector.js.map +1 -1
- package/dist/dev/route-scanner.d.ts.map +1 -1
- package/dist/dev/route-scanner.js +25 -3
- package/dist/dev/route-scanner.js.map +1 -1
- package/dist/http.d.ts +6 -1
- package/dist/http.d.ts.map +1 -1
- package/dist/http.js +13 -4
- package/dist/http.js.map +1 -1
- package/dist/index.d.ts +4 -12
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -3
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +5 -5
- package/dist/middleware/manager.d.ts +2 -1
- package/dist/middleware/manager.d.ts.map +1 -1
- package/dist/middleware/manager.js +4 -0
- package/dist/middleware/manager.js.map +1 -1
- package/dist/types/index.d.ts +26 -0
- package/dist/types/index.d.ts.map +1 -1
- package/dist/utils/path.d.ts +3 -0
- package/dist/utils/path.d.ts.map +1 -0
- package/dist/utils/path.js +9 -0
- package/dist/utils/path.js.map +1 -0
- package/package.json +6 -5
- package/src/cli/index.ts +105 -62
- package/src/core/config-loader.ts +213 -0
- package/src/core/router.ts +4 -0
- package/src/core/vector.ts +38 -32
- package/src/dev/route-scanner.ts +26 -3
- package/src/http.ts +21 -5
- package/src/index.ts +11 -16
- package/src/middleware/manager.ts +16 -4
- package/src/types/index.ts +44 -0
- package/src/utils/path.ts +9 -0
package/src/cli/index.ts
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
#!/usr/bin/env bun
|
|
2
2
|
|
|
3
3
|
import { watch } from "node:fs";
|
|
4
|
-
import { join } from "node:path";
|
|
5
4
|
import { parseArgs } from "node:util";
|
|
6
|
-
import
|
|
5
|
+
import { getVectorInstance } from "../core/vector";
|
|
6
|
+
import { ConfigLoader } from "../core/config-loader";
|
|
7
7
|
|
|
8
8
|
const { values, positionals } = parseArgs({
|
|
9
9
|
args: Bun.argv.slice(2),
|
|
@@ -45,92 +45,135 @@ async function runDev() {
|
|
|
45
45
|
`\n→ Starting Vector ${isDev ? "development" : "production"} server\n`
|
|
46
46
|
);
|
|
47
47
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
48
|
+
let server: any = null;
|
|
49
|
+
let vector: any = null;
|
|
50
|
+
|
|
51
|
+
async function startServer() {
|
|
52
|
+
try {
|
|
53
|
+
// Load configuration using ConfigLoader
|
|
54
|
+
const configLoader = new ConfigLoader();
|
|
55
|
+
const config = await configLoader.load();
|
|
56
|
+
const configSource = configLoader.getConfigSource();
|
|
57
|
+
|
|
58
|
+
// Merge CLI options with loaded config
|
|
59
|
+
config.port = config.port || Number.parseInt(values.port as string);
|
|
60
|
+
config.hostname = config.hostname || (values.host as string);
|
|
61
|
+
config.routesDir = config.routesDir || (values.routes as string);
|
|
62
|
+
config.development = isDev;
|
|
63
|
+
config.autoDiscover = true;
|
|
64
|
+
|
|
65
|
+
// Apply CLI CORS option if not set in config
|
|
66
|
+
if (!config.cors && values.cors) {
|
|
67
|
+
config.cors = {
|
|
56
68
|
origin: "*",
|
|
57
69
|
credentials: true,
|
|
58
70
|
allowHeaders: "Content-Type, Authorization",
|
|
59
71
|
allowMethods: "GET, POST, PUT, PATCH, DELETE, OPTIONS",
|
|
60
72
|
exposeHeaders: "Authorization",
|
|
61
73
|
maxAge: 86400,
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
};
|
|
74
|
+
};
|
|
75
|
+
}
|
|
65
76
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
before,
|
|
74
|
-
finally: finallyMiddleware,
|
|
75
|
-
...otherConfig
|
|
76
|
-
} = userConfig.default;
|
|
77
|
-
|
|
78
|
-
// Merge non-middleware config
|
|
79
|
-
Object.assign(config, otherConfig);
|
|
80
|
-
|
|
81
|
-
// Handle middleware arrays properly - these need to be set after Object.assign
|
|
82
|
-
// to avoid being overwritten
|
|
83
|
-
if (before) {
|
|
84
|
-
config.before = before;
|
|
85
|
-
}
|
|
86
|
-
if (finallyMiddleware) {
|
|
87
|
-
config.finally = finallyMiddleware;
|
|
88
|
-
}
|
|
77
|
+
// Get Vector instance and configure handlers
|
|
78
|
+
vector = getVectorInstance();
|
|
79
|
+
|
|
80
|
+
// Load and set auth handler if configured
|
|
81
|
+
const authHandler = await configLoader.loadAuthHandler();
|
|
82
|
+
if (authHandler) {
|
|
83
|
+
vector.setProtectedHandler(authHandler);
|
|
89
84
|
}
|
|
90
|
-
} catch {
|
|
91
|
-
// No user config file, use defaults
|
|
92
|
-
}
|
|
93
85
|
|
|
94
|
-
|
|
86
|
+
// Load and set cache handler if configured
|
|
87
|
+
const cacheHandler = await configLoader.loadCacheHandler();
|
|
88
|
+
if (cacheHandler) {
|
|
89
|
+
vector.setCacheHandler(cacheHandler);
|
|
90
|
+
}
|
|
95
91
|
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
const cyan = "\x1b[36m";
|
|
99
|
-
const green = "\x1b[32m";
|
|
92
|
+
// Start the server
|
|
93
|
+
server = await vector.startServer(config);
|
|
100
94
|
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
95
|
+
const gray = "\x1b[90m";
|
|
96
|
+
const reset = "\x1b[0m";
|
|
97
|
+
const cyan = "\x1b[36m";
|
|
98
|
+
const green = "\x1b[32m";
|
|
104
99
|
|
|
100
|
+
console.log(` ${gray}Config${reset} ${configSource === 'user' ? 'User config loaded' : 'Using defaults'}`);
|
|
101
|
+
console.log(` ${gray}Routes${reset} ${config.routesDir}`);
|
|
102
|
+
if (isDev && values.watch) {
|
|
103
|
+
console.log(` ${gray}Watching${reset} All project files`);
|
|
104
|
+
}
|
|
105
|
+
console.log(
|
|
106
|
+
` ${gray}CORS${reset} ${values.cors ? "Enabled" : "Disabled"}`
|
|
107
|
+
);
|
|
108
|
+
console.log(
|
|
109
|
+
` ${gray}Mode${reset} ${isDev ? "Development" : "Production"}\n`
|
|
110
|
+
);
|
|
111
|
+
console.log(
|
|
112
|
+
` ${green}Ready${reset} → ${cyan}http://${config.hostname}:${config.port}${reset}\n`
|
|
113
|
+
);
|
|
114
|
+
|
|
115
|
+
return { server, vector, config };
|
|
116
|
+
} catch (error) {
|
|
117
|
+
console.error("[ERROR] Failed to start server:", error);
|
|
118
|
+
throw error;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
try {
|
|
123
|
+
// Start the server initially
|
|
124
|
+
const result = await startServer();
|
|
125
|
+
server = result.server;
|
|
126
|
+
|
|
127
|
+
// Setup file watching for hot reload
|
|
128
|
+
if (isDev && values.watch) {
|
|
105
129
|
try {
|
|
130
|
+
let reloadTimeout: any = null;
|
|
131
|
+
|
|
106
132
|
// Watch entire project directory for changes
|
|
107
133
|
watch(process.cwd(), { recursive: true }, async (_, filename) => {
|
|
108
134
|
if (
|
|
109
135
|
filename &&
|
|
110
136
|
(filename.endsWith(".ts") ||
|
|
111
137
|
filename.endsWith(".js") ||
|
|
112
|
-
filename.endsWith(".json"))
|
|
138
|
+
filename.endsWith(".json")) &&
|
|
139
|
+
!filename.includes("node_modules") &&
|
|
140
|
+
!filename.includes(".git")
|
|
113
141
|
) {
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
142
|
+
// Debounce reload to avoid multiple restarts
|
|
143
|
+
if (reloadTimeout) {
|
|
144
|
+
clearTimeout(reloadTimeout);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
reloadTimeout = setTimeout(async () => {
|
|
148
|
+
console.log(`\n 🔄 File changed: ${filename}`);
|
|
149
|
+
console.log(" 🔄 Reloading server...\n");
|
|
150
|
+
|
|
151
|
+
// Stop the current server
|
|
152
|
+
if (vector) {
|
|
153
|
+
vector.stop();
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// Clear module cache to ensure fresh imports
|
|
157
|
+
for (const key in require.cache) {
|
|
158
|
+
if (!key.includes("node_modules")) {
|
|
159
|
+
delete require.cache[key];
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// Restart the server
|
|
164
|
+
try {
|
|
165
|
+
const result = await startServer();
|
|
166
|
+
server = result.server;
|
|
167
|
+
} catch (error) {
|
|
168
|
+
console.error(" ❌ Failed to reload server:", error);
|
|
169
|
+
}
|
|
170
|
+
}, 100); // 100ms debounce
|
|
119
171
|
}
|
|
120
172
|
});
|
|
121
173
|
} catch (err) {
|
|
122
174
|
console.warn(" ⚠️ File watching not available");
|
|
123
175
|
}
|
|
124
176
|
}
|
|
125
|
-
console.log(
|
|
126
|
-
` ${gray}CORS${reset} ${values.cors ? "Enabled" : "Disabled"}`
|
|
127
|
-
);
|
|
128
|
-
console.log(
|
|
129
|
-
` ${gray}Mode${reset} ${isDev ? "Development" : "Production"}\n`
|
|
130
|
-
);
|
|
131
|
-
console.log(
|
|
132
|
-
` ${green}Ready${reset} → ${cyan}http://${config.hostname}:${config.port}${reset}\n`
|
|
133
|
-
);
|
|
134
177
|
} catch (error) {
|
|
135
178
|
console.error("[ERROR] Failed to start server:", error);
|
|
136
179
|
process.exit(1);
|
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
import { existsSync } from 'node:fs';
|
|
2
|
+
import { resolve } from 'node:path';
|
|
3
|
+
import { toFileUrl } from '../utils/path';
|
|
4
|
+
import type {
|
|
5
|
+
AfterMiddlewareHandler,
|
|
6
|
+
BeforeMiddlewareHandler,
|
|
7
|
+
CacheHandler,
|
|
8
|
+
CorsOptions,
|
|
9
|
+
DefaultVectorTypes,
|
|
10
|
+
ProtectedHandler,
|
|
11
|
+
VectorConfig,
|
|
12
|
+
VectorConfigSchema,
|
|
13
|
+
VectorTypes,
|
|
14
|
+
} from '../types';
|
|
15
|
+
|
|
16
|
+
export class ConfigLoader<TTypes extends VectorTypes = DefaultVectorTypes> {
|
|
17
|
+
private configPath: string;
|
|
18
|
+
private config: VectorConfigSchema<TTypes> | null = null;
|
|
19
|
+
private configSource: 'user' | 'default' = 'default';
|
|
20
|
+
|
|
21
|
+
constructor(configPath = 'vector.config.ts') {
|
|
22
|
+
// Always resolve from the current working directory (user's project)
|
|
23
|
+
this.configPath = resolve(process.cwd(), configPath);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
async load(): Promise<VectorConfig<TTypes>> {
|
|
27
|
+
// Check if config file exists before attempting to load
|
|
28
|
+
if (existsSync(this.configPath)) {
|
|
29
|
+
try {
|
|
30
|
+
console.log(`→ Loading config from: ${this.configPath}`);
|
|
31
|
+
|
|
32
|
+
// Use explicit file:// URL to ensure correct resolution
|
|
33
|
+
const userConfigPath = toFileUrl(this.configPath);
|
|
34
|
+
const userConfig = await import(userConfigPath);
|
|
35
|
+
this.config = userConfig.default || userConfig;
|
|
36
|
+
this.configSource = 'user';
|
|
37
|
+
|
|
38
|
+
console.log(' ✓ User config loaded successfully');
|
|
39
|
+
} catch (error) {
|
|
40
|
+
console.error(` ✗ Failed to load config from ${this.configPath}:`, error);
|
|
41
|
+
console.log(' → Using default configuration');
|
|
42
|
+
this.config = {};
|
|
43
|
+
}
|
|
44
|
+
} else {
|
|
45
|
+
// Config file doesn't exist, use defaults
|
|
46
|
+
console.log(` → No config file found at: ${this.configPath}`);
|
|
47
|
+
console.log(' → Using default configuration');
|
|
48
|
+
this.config = {};
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// Convert new config schema to legacy VectorConfig format
|
|
52
|
+
return await this.buildLegacyConfig();
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
getConfigSource(): 'user' | 'default' {
|
|
56
|
+
return this.configSource;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
private async buildLegacyConfig(): Promise<VectorConfig<TTypes>> {
|
|
60
|
+
const config: VectorConfig<TTypes> = {};
|
|
61
|
+
|
|
62
|
+
// Server configuration
|
|
63
|
+
if (this.config?.server) {
|
|
64
|
+
config.port = this.config.server.port;
|
|
65
|
+
config.hostname = this.config.server.hostname;
|
|
66
|
+
config.reusePort = this.config.server.reusePort;
|
|
67
|
+
config.development = this.config.server.development;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// Routes configuration
|
|
71
|
+
if (this.config?.routes) {
|
|
72
|
+
config.routesDir = this.config.routes.dir || './routes';
|
|
73
|
+
config.autoDiscover = this.config.routes.autoDiscover !== false;
|
|
74
|
+
} else {
|
|
75
|
+
config.routesDir = './routes';
|
|
76
|
+
config.autoDiscover = true;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// CORS configuration
|
|
80
|
+
if (this.config?.cors) {
|
|
81
|
+
if (typeof this.config.cors === 'boolean') {
|
|
82
|
+
config.cors = this.config.cors
|
|
83
|
+
? {
|
|
84
|
+
origin: '*',
|
|
85
|
+
credentials: true,
|
|
86
|
+
allowHeaders: 'Content-Type, Authorization',
|
|
87
|
+
allowMethods: 'GET, POST, PUT, PATCH, DELETE, OPTIONS',
|
|
88
|
+
exposeHeaders: 'Authorization',
|
|
89
|
+
maxAge: 86400,
|
|
90
|
+
}
|
|
91
|
+
: undefined;
|
|
92
|
+
} else {
|
|
93
|
+
config.cors = this.config.cors as CorsOptions;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// Load middleware - support both direct functions and file paths
|
|
98
|
+
if (this.config?.before) {
|
|
99
|
+
// Direct functions provided
|
|
100
|
+
console.log('Using direct before middleware functions:', this.config.before.length);
|
|
101
|
+
config.before = this.config.before;
|
|
102
|
+
} else if (this.config?.middleware?.before) {
|
|
103
|
+
// File paths provided (legacy)
|
|
104
|
+
console.log('Loading before middleware from file paths:', this.config.middleware.before);
|
|
105
|
+
config.before = await this.loadMiddleware<BeforeMiddlewareHandler<TTypes>>(
|
|
106
|
+
this.config.middleware.before
|
|
107
|
+
);
|
|
108
|
+
console.log('Loaded before middleware:', config.before?.length);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
if (this.config?.after) {
|
|
112
|
+
// Direct functions provided
|
|
113
|
+
console.log('Using direct after middleware functions:', this.config.after.length);
|
|
114
|
+
config.finally = this.config.after;
|
|
115
|
+
} else if (this.config?.middleware?.after) {
|
|
116
|
+
// File paths provided (legacy)
|
|
117
|
+
console.log('Loading after middleware from file paths:', this.config.middleware.after);
|
|
118
|
+
config.finally = await this.loadMiddleware<AfterMiddlewareHandler<TTypes>>(
|
|
119
|
+
this.config.middleware.after
|
|
120
|
+
);
|
|
121
|
+
console.log('Loaded after middleware:', config.finally?.length);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
return config;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
private async loadMiddleware<T>(paths: string[]): Promise<T[]> {
|
|
128
|
+
const middleware: T[] = [];
|
|
129
|
+
|
|
130
|
+
for (const path of paths) {
|
|
131
|
+
try {
|
|
132
|
+
const modulePath = resolve(process.cwd(), path);
|
|
133
|
+
const importPath = toFileUrl(modulePath);
|
|
134
|
+
const module = await import(importPath);
|
|
135
|
+
const handler = module.default || module;
|
|
136
|
+
|
|
137
|
+
if (typeof handler === 'function') {
|
|
138
|
+
middleware.push(handler as T);
|
|
139
|
+
} else {
|
|
140
|
+
console.warn(`Middleware at ${path} does not export a function`);
|
|
141
|
+
}
|
|
142
|
+
} catch (error) {
|
|
143
|
+
console.error(`Failed to load middleware from ${path}:`, error);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
return middleware;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
async loadAuthHandler(): Promise<ProtectedHandler<TTypes> | null> {
|
|
151
|
+
// Direct function provided
|
|
152
|
+
if (this.config?.auth) {
|
|
153
|
+
console.log('Using direct auth handler function');
|
|
154
|
+
return this.config.auth;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// File path provided (legacy)
|
|
158
|
+
if (!this.config?.handlers?.auth) {
|
|
159
|
+
return null;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
try {
|
|
163
|
+
const modulePath = resolve(process.cwd(), this.config.handlers.auth);
|
|
164
|
+
const importPath = toFileUrl(modulePath);
|
|
165
|
+
const module = await import(importPath);
|
|
166
|
+
const handler = module.default || module;
|
|
167
|
+
|
|
168
|
+
if (typeof handler === 'function') {
|
|
169
|
+
return handler as ProtectedHandler<TTypes>;
|
|
170
|
+
} else {
|
|
171
|
+
console.warn(`Auth handler at ${this.config.handlers.auth} does not export a function`);
|
|
172
|
+
return null;
|
|
173
|
+
}
|
|
174
|
+
} catch (error) {
|
|
175
|
+
console.error(`Failed to load auth handler from ${this.config.handlers.auth}:`, error);
|
|
176
|
+
return null;
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
async loadCacheHandler(): Promise<CacheHandler | null> {
|
|
181
|
+
// Direct function provided
|
|
182
|
+
if (this.config?.cache) {
|
|
183
|
+
console.log('Using direct cache handler function');
|
|
184
|
+
return this.config.cache;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
// File path provided (legacy)
|
|
188
|
+
if (!this.config?.handlers?.cache) {
|
|
189
|
+
return null;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
try {
|
|
193
|
+
const modulePath = resolve(process.cwd(), this.config.handlers.cache);
|
|
194
|
+
const importPath = toFileUrl(modulePath);
|
|
195
|
+
const module = await import(importPath);
|
|
196
|
+
const handler = module.default || module;
|
|
197
|
+
|
|
198
|
+
if (typeof handler === 'function') {
|
|
199
|
+
return handler as CacheHandler;
|
|
200
|
+
} else {
|
|
201
|
+
console.warn(`Cache handler at ${this.config.handlers.cache} does not export a function`);
|
|
202
|
+
return null;
|
|
203
|
+
}
|
|
204
|
+
} catch (error) {
|
|
205
|
+
console.error(`Failed to load cache handler from ${this.config.handlers.cache}:`, error);
|
|
206
|
+
return null;
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
getConfig(): VectorConfigSchema<TTypes> | null {
|
|
211
|
+
return this.config;
|
|
212
|
+
}
|
|
213
|
+
}
|
package/src/core/router.ts
CHANGED
package/src/core/vector.ts
CHANGED
|
@@ -5,9 +5,8 @@ import { CacheManager } from '../cache/manager';
|
|
|
5
5
|
import { RouteGenerator } from '../dev/route-generator';
|
|
6
6
|
import { RouteScanner } from '../dev/route-scanner';
|
|
7
7
|
import { MiddlewareManager } from '../middleware/manager';
|
|
8
|
+
import { toFileUrl } from '../utils/path';
|
|
8
9
|
import type {
|
|
9
|
-
AfterMiddlewareHandler,
|
|
10
|
-
BeforeMiddlewareHandler,
|
|
11
10
|
CacheHandler,
|
|
12
11
|
DefaultVectorTypes,
|
|
13
12
|
ProtectedHandler,
|
|
@@ -19,6 +18,7 @@ import type {
|
|
|
19
18
|
import { VectorRouter } from './router';
|
|
20
19
|
import { VectorServer } from './server';
|
|
21
20
|
|
|
21
|
+
// Internal-only class - not exposed to users
|
|
22
22
|
export class Vector<TTypes extends VectorTypes = DefaultVectorTypes> {
|
|
23
23
|
private static instance: Vector<any>;
|
|
24
24
|
private router: VectorRouter<TTypes>;
|
|
@@ -43,6 +43,7 @@ export class Vector<TTypes extends VectorTypes = DefaultVectorTypes> {
|
|
|
43
43
|
);
|
|
44
44
|
}
|
|
45
45
|
|
|
46
|
+
// Internal use only - not exposed to users
|
|
46
47
|
static getInstance<T extends VectorTypes = DefaultVectorTypes>(): Vector<T> {
|
|
47
48
|
if (!Vector.instance) {
|
|
48
49
|
Vector.instance = new Vector<T>();
|
|
@@ -50,46 +51,38 @@ export class Vector<TTypes extends VectorTypes = DefaultVectorTypes> {
|
|
|
50
51
|
return Vector.instance as Vector<T>;
|
|
51
52
|
}
|
|
52
53
|
|
|
53
|
-
set protected
|
|
54
|
+
// Internal method to set protected handler
|
|
55
|
+
setProtectedHandler(handler: ProtectedHandler<TTypes>) {
|
|
54
56
|
this._protectedHandler = handler;
|
|
55
57
|
this.authManager.setProtectedHandler(handler);
|
|
56
58
|
}
|
|
57
59
|
|
|
58
|
-
|
|
60
|
+
getProtectedHandler(): ProtectedHandler<TTypes> | null {
|
|
59
61
|
return this._protectedHandler;
|
|
60
62
|
}
|
|
61
63
|
|
|
62
|
-
set cache
|
|
64
|
+
// Internal method to set cache handler
|
|
65
|
+
setCacheHandler(handler: CacheHandler) {
|
|
63
66
|
this._cacheHandler = handler;
|
|
64
67
|
this.cacheManager.setCacheHandler(handler);
|
|
65
68
|
}
|
|
66
69
|
|
|
67
|
-
|
|
70
|
+
getCacheHandler(): CacheHandler | null {
|
|
68
71
|
return this._cacheHandler;
|
|
69
72
|
}
|
|
70
73
|
|
|
71
|
-
|
|
74
|
+
// Internal method to add route
|
|
75
|
+
addRoute(options: RouteOptions<TTypes>, handler: RouteHandler<TTypes>): RouteEntry {
|
|
72
76
|
return this.router.route(options, handler);
|
|
73
77
|
}
|
|
74
78
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
return this;
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
before(...middleware: BeforeMiddlewareHandler<TTypes>[]): this {
|
|
81
|
-
this.middlewareManager.addBefore(...middleware);
|
|
82
|
-
return this;
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
finally(...middleware: AfterMiddlewareHandler<TTypes>[]): this {
|
|
86
|
-
this.middlewareManager.addFinally(...middleware);
|
|
87
|
-
return this;
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
async serve(config?: VectorConfig<TTypes>): Promise<Server> {
|
|
79
|
+
// Internal method to start server - only called by CLI
|
|
80
|
+
async startServer(config?: VectorConfig<TTypes>): Promise<Server> {
|
|
91
81
|
this.config = { ...this.config, ...config };
|
|
92
82
|
|
|
83
|
+
// Clear previous middleware to avoid accumulation across multiple starts
|
|
84
|
+
this.middlewareManager.clear();
|
|
85
|
+
|
|
93
86
|
if (config?.before) {
|
|
94
87
|
this.middlewareManager.addBefore(...config.before);
|
|
95
88
|
}
|
|
@@ -129,17 +122,19 @@ export class Vector<TTypes extends VectorTypes = DefaultVectorTypes> {
|
|
|
129
122
|
|
|
130
123
|
for (const route of routes) {
|
|
131
124
|
try {
|
|
132
|
-
|
|
133
|
-
const importPath =
|
|
134
|
-
process.platform === 'win32'
|
|
135
|
-
? `file:///${route.path.replace(/\\/g, '/')}`
|
|
136
|
-
: route.path;
|
|
125
|
+
const importPath = toFileUrl(route.path);
|
|
137
126
|
|
|
138
127
|
const module = await import(importPath);
|
|
139
128
|
const exported = route.name === 'default' ? module.default : module[route.name];
|
|
140
129
|
|
|
141
130
|
if (exported) {
|
|
142
|
-
if (this.
|
|
131
|
+
if (this.isRouteDefinition(exported)) {
|
|
132
|
+
// Use router.route() to ensure middleware is applied
|
|
133
|
+
const routeDef = exported as any;
|
|
134
|
+
this.router.route(routeDef.options, routeDef.handler);
|
|
135
|
+
this.logRouteLoaded(routeDef.options);
|
|
136
|
+
} else if (this.isRouteEntry(exported)) {
|
|
137
|
+
// Legacy support for direct RouteEntry (won't have middleware)
|
|
143
138
|
this.router.addRoute(exported as RouteEntry);
|
|
144
139
|
this.logRouteLoaded(exported as RouteEntry);
|
|
145
140
|
} else if (typeof exported === 'function') {
|
|
@@ -185,6 +180,10 @@ export class Vector<TTypes extends VectorTypes = DefaultVectorTypes> {
|
|
|
185
180
|
return Array.isArray(value) && value.length >= 3;
|
|
186
181
|
}
|
|
187
182
|
|
|
183
|
+
private isRouteDefinition(value: any): boolean {
|
|
184
|
+
return value && typeof value === 'object' && 'entry' in value && 'options' in value && 'handler' in value;
|
|
185
|
+
}
|
|
186
|
+
|
|
188
187
|
private logRouteLoaded(route: RouteEntry | RouteOptions): void {
|
|
189
188
|
if (Array.isArray(route)) {
|
|
190
189
|
console.log(` ✓ Loaded route: ${route[0]} ${route[3] || route[1]}`);
|
|
@@ -198,6 +197,9 @@ export class Vector<TTypes extends VectorTypes = DefaultVectorTypes> {
|
|
|
198
197
|
this.server.stop();
|
|
199
198
|
this.server = null;
|
|
200
199
|
}
|
|
200
|
+
// Don't reset managers - they should persist for the singleton
|
|
201
|
+
// Only clear route-specific state if needed
|
|
202
|
+
this.router.clearRoutes();
|
|
201
203
|
}
|
|
202
204
|
|
|
203
205
|
getServer(): VectorServer<TTypes> | null {
|
|
@@ -215,8 +217,12 @@ export class Vector<TTypes extends VectorTypes = DefaultVectorTypes> {
|
|
|
215
217
|
getAuthManager(): AuthManager<TTypes> {
|
|
216
218
|
return this.authManager;
|
|
217
219
|
}
|
|
218
|
-
}
|
|
219
220
|
|
|
220
|
-
|
|
221
|
+
// Reset instance for testing purposes only
|
|
222
|
+
static resetInstance(): void {
|
|
223
|
+
Vector.instance = null as any;
|
|
224
|
+
}
|
|
225
|
+
}
|
|
221
226
|
|
|
222
|
-
|
|
227
|
+
// Export for internal use only
|
|
228
|
+
export const getVectorInstance = Vector.getInstance;
|
package/src/dev/route-scanner.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { promises as fs } from 'node:fs';
|
|
1
|
+
import { existsSync, promises as fs } from 'node:fs';
|
|
2
2
|
import { join, relative, resolve, sep } from 'node:path';
|
|
3
3
|
import type { GeneratedRoute } from '../types';
|
|
4
4
|
|
|
@@ -6,17 +6,29 @@ export class RouteScanner {
|
|
|
6
6
|
private routesDir: string;
|
|
7
7
|
|
|
8
8
|
constructor(routesDir = './routes') {
|
|
9
|
+
// Always resolve from the current working directory (user's project)
|
|
9
10
|
this.routesDir = resolve(process.cwd(), routesDir);
|
|
10
11
|
}
|
|
11
12
|
|
|
12
13
|
async scan(): Promise<GeneratedRoute[]> {
|
|
13
14
|
const routes: GeneratedRoute[] = [];
|
|
14
15
|
|
|
16
|
+
// Check if routes directory exists before attempting to scan
|
|
17
|
+
if (!existsSync(this.routesDir)) {
|
|
18
|
+
console.log(` → Routes directory not found: ${this.routesDir}`);
|
|
19
|
+
console.log(' → No routes will be auto-discovered');
|
|
20
|
+
return [];
|
|
21
|
+
}
|
|
22
|
+
|
|
15
23
|
try {
|
|
24
|
+
console.log(` → Scanning routes from: ${this.routesDir}`);
|
|
16
25
|
await this.scanDirectory(this.routesDir, routes);
|
|
26
|
+
if (routes.length > 0) {
|
|
27
|
+
console.log(` ✓ Found ${routes.length} route${routes.length === 1 ? '' : 's'}`);
|
|
28
|
+
}
|
|
17
29
|
} catch (error) {
|
|
18
30
|
if ((error as any).code === 'ENOENT') {
|
|
19
|
-
console.warn(`Routes directory not
|
|
31
|
+
console.warn(` ✗ Routes directory not accessible: ${this.routesDir}`);
|
|
20
32
|
return [];
|
|
21
33
|
}
|
|
22
34
|
throw error;
|
|
@@ -64,7 +76,18 @@ export class RouteScanner {
|
|
|
64
76
|
for (const [name, value] of Object.entries(module)) {
|
|
65
77
|
if (name === 'default') continue;
|
|
66
78
|
|
|
67
|
-
|
|
79
|
+
// Check for new RouteDefinition format
|
|
80
|
+
if (value && typeof value === 'object' && 'entry' in value && 'options' in value && 'handler' in value) {
|
|
81
|
+
const routeDef = value as any;
|
|
82
|
+
routes.push({
|
|
83
|
+
name,
|
|
84
|
+
path: fullPath,
|
|
85
|
+
method: routeDef.options.method as string,
|
|
86
|
+
options: routeDef.options,
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
// Legacy RouteEntry format support
|
|
90
|
+
else if (Array.isArray(value) && value.length >= 4) {
|
|
68
91
|
const [method, , , path] = value;
|
|
69
92
|
routes.push({
|
|
70
93
|
name,
|