vector-framework 0.8.1 → 0.9.0

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