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.
- package/README.md +78 -56
- package/dist/cache/manager.d.ts +1 -1
- package/dist/cache/manager.d.ts.map +1 -1
- package/dist/cache/manager.js +8 -3
- package/dist/cache/manager.js.map +1 -1
- package/dist/cli/index.js +134 -68
- package/dist/cli/index.js.map +1 -1
- package/dist/core/config-loader.d.ts +13 -0
- package/dist/core/config-loader.d.ts.map +1 -0
- package/dist/core/config-loader.js +166 -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 +5 -1
- 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 +37 -31
- package/dist/core/vector.js.map +1 -1
- package/dist/dev/route-generator.js +3 -3
- package/dist/dev/route-generator.js.map +1 -1
- package/dist/dev/route-scanner.d.ts.map +1 -1
- package/dist/dev/route-scanner.js +15 -4
- 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 +2 -2
- package/src/cache/manager.ts +15 -5
- package/src/cli/index.ts +156 -71
- package/src/core/config-loader.ts +193 -0
- package/src/core/router.ts +6 -1
- package/src/core/vector.ts +39 -39
- package/src/dev/route-generator.ts +3 -3
- package/src/dev/route-scanner.ts +15 -4
- package/src/http.ts +21 -5
- package/src/index.ts +11 -16
- package/src/middleware/manager.ts +16 -4
- package/src/types/index.ts +45 -1
- 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 {
|
|
4
|
-
import { parseArgs } from
|
|
5
|
-
import
|
|
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:
|
|
12
|
-
short:
|
|
13
|
-
default:
|
|
12
|
+
type: "string",
|
|
13
|
+
short: "p",
|
|
14
|
+
default: "3000",
|
|
14
15
|
},
|
|
15
16
|
host: {
|
|
16
|
-
type:
|
|
17
|
-
short:
|
|
18
|
-
default:
|
|
17
|
+
type: "string",
|
|
18
|
+
short: "h",
|
|
19
|
+
default: "localhost",
|
|
19
20
|
},
|
|
20
21
|
routes: {
|
|
21
|
-
type:
|
|
22
|
-
short:
|
|
23
|
-
default:
|
|
22
|
+
type: "string",
|
|
23
|
+
short: "r",
|
|
24
|
+
default: "./routes",
|
|
24
25
|
},
|
|
25
26
|
watch: {
|
|
26
|
-
type:
|
|
27
|
-
short:
|
|
27
|
+
type: "boolean",
|
|
28
|
+
short: "w",
|
|
28
29
|
default: true,
|
|
29
30
|
},
|
|
30
31
|
cors: {
|
|
31
|
-
type:
|
|
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] ||
|
|
40
|
+
const command = positionals[0] || "dev";
|
|
40
41
|
|
|
41
42
|
async function runDev() {
|
|
42
|
-
const isDev = command ===
|
|
43
|
-
console.log(
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
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:
|
|
56
|
-
allowMethods:
|
|
57
|
-
exposeHeaders:
|
|
69
|
+
allowHeaders: "Content-Type, Authorization",
|
|
70
|
+
allowMethods: "GET, POST, PUT, PATCH, DELETE, OPTIONS",
|
|
71
|
+
exposeHeaders: "Authorization",
|
|
58
72
|
maxAge: 86400,
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
};
|
|
73
|
+
};
|
|
74
|
+
}
|
|
62
75
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
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
|
-
|
|
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
|
-
|
|
77
|
-
|
|
78
|
-
const cyan = '\x1b[36m';
|
|
79
|
-
const green = '\x1b[32m';
|
|
91
|
+
// Start the server
|
|
92
|
+
server = await vector.startServer(config);
|
|
80
93
|
|
|
81
|
-
|
|
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
|
-
|
|
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(
|
|
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(
|
|
182
|
+
console.log("\n→ Building Vector application\n");
|
|
98
183
|
|
|
99
184
|
try {
|
|
100
|
-
const { RouteScanner } = await import(
|
|
101
|
-
const { RouteGenerator } = await import(
|
|
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
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
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(
|
|
206
|
+
console.log("\n ✓ Build complete\n");
|
|
122
207
|
} catch (error) {
|
|
123
|
-
console.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
|
|
214
|
+
case "dev":
|
|
130
215
|
await runDev();
|
|
131
216
|
break;
|
|
132
|
-
case
|
|
217
|
+
case "build":
|
|
133
218
|
await runBuild();
|
|
134
219
|
break;
|
|
135
|
-
case
|
|
136
|
-
process.env.NODE_ENV =
|
|
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
|
+
}
|
package/src/core/router.ts
CHANGED
|
@@ -140,7 +140,8 @@ export class VectorRouter<TTypes extends VectorTypes = DefaultVectorTypes> {
|
|
|
140
140
|
|
|
141
141
|
request = vectorRequest;
|
|
142
142
|
try {
|
|
143
|
-
|
|
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
|
}
|
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
|
}
|
|
@@ -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
|
-
|
|
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.
|
|
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
|
-
|
|
221
|
+
// Reset instance for testing purposes only
|
|
222
|
+
static resetInstance(): void {
|
|
223
|
+
Vector.instance = null as any;
|
|
224
|
+
}
|
|
225
|
+
}
|
|
227
226
|
|
|
228
|
-
|
|
227
|
+
// Export for internal use only
|
|
228
|
+
export const getVectorInstance = Vector.getInstance;
|