vector-framework 0.9.5 → 0.9.7
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 +2 -0
- package/dist/cli/index.js +90 -55
- package/dist/cli/index.js.map +1 -1
- package/dist/cli.js +112 -62
- package/dist/core/config-loader.d.ts.map +1 -1
- package/dist/core/config-loader.js +9 -4
- package/dist/core/config-loader.js.map +1 -1
- package/dist/core/server.d.ts +3 -3
- package/dist/core/server.d.ts.map +1 -1
- package/dist/core/server.js +50 -27
- package/dist/core/server.js.map +1 -1
- package/dist/index.js +7 -7
- package/dist/index.mjs +8 -8
- package/dist/types/index.d.ts +2 -0
- package/dist/types/index.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/cli/index.ts +111 -70
- package/src/core/config-loader.ts +10 -4
- package/src/core/server.ts +60 -31
- package/src/types/index.ts +6 -4
package/src/cli/index.ts
CHANGED
|
@@ -36,6 +36,10 @@ const { values, positionals } = parseArgs({
|
|
|
36
36
|
type: "boolean",
|
|
37
37
|
default: true,
|
|
38
38
|
},
|
|
39
|
+
config: {
|
|
40
|
+
type: "string",
|
|
41
|
+
short: "c",
|
|
42
|
+
},
|
|
39
43
|
},
|
|
40
44
|
strict: true,
|
|
41
45
|
allowPositionals: true,
|
|
@@ -52,80 +56,97 @@ async function runDev() {
|
|
|
52
56
|
let server: any = null;
|
|
53
57
|
let vector: any = null;
|
|
54
58
|
|
|
55
|
-
async function startServer() {
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
// Merge CLI options with loaded config
|
|
63
|
-
// Only use CLI values if config doesn't have them
|
|
64
|
-
config.port = config.port ?? Number.parseInt(values.port as string);
|
|
65
|
-
config.hostname = config.hostname ?? (values.host as string);
|
|
66
|
-
config.routesDir = config.routesDir ?? (values.routes as string);
|
|
67
|
-
config.development = config.development ?? isDev;
|
|
68
|
-
config.autoDiscover = true; // Always auto-discover routes
|
|
69
|
-
|
|
70
|
-
// Apply CLI CORS option if not set in config
|
|
71
|
-
if (!config.cors && values.cors) {
|
|
72
|
-
config.cors = {
|
|
73
|
-
origin: "*",
|
|
74
|
-
credentials: true,
|
|
75
|
-
allowHeaders: "Content-Type, Authorization",
|
|
76
|
-
allowMethods: "GET, POST, PUT, PATCH, DELETE, OPTIONS",
|
|
77
|
-
exposeHeaders: "Authorization",
|
|
78
|
-
maxAge: 86400,
|
|
79
|
-
};
|
|
80
|
-
}
|
|
59
|
+
async function startServer(): Promise<{ server: any; vector: any; config: any }> {
|
|
60
|
+
// Create a timeout promise that rejects after 10 seconds
|
|
61
|
+
const timeoutPromise = new Promise<never>((_, reject) => {
|
|
62
|
+
setTimeout(() => {
|
|
63
|
+
reject(new Error("Server startup timed out (10s)"));
|
|
64
|
+
}, 10000);
|
|
65
|
+
});
|
|
81
66
|
|
|
82
|
-
|
|
83
|
-
|
|
67
|
+
// Create the actual server start promise
|
|
68
|
+
const serverStartPromise = (async (): Promise<{ server: any; vector: any; config: any }> => {
|
|
69
|
+
try {
|
|
70
|
+
// Load configuration using ConfigLoader
|
|
71
|
+
const configLoader = new ConfigLoader(values.config as string | undefined);
|
|
72
|
+
const config = await configLoader.load();
|
|
73
|
+
const configSource = configLoader.getConfigSource();
|
|
74
|
+
|
|
75
|
+
// Merge CLI options with loaded config
|
|
76
|
+
// Only use CLI values if config doesn't have them
|
|
77
|
+
config.port = config.port ?? Number.parseInt(values.port as string);
|
|
78
|
+
config.hostname = config.hostname ?? (values.host as string);
|
|
79
|
+
config.routesDir = config.routesDir ?? (values.routes as string);
|
|
80
|
+
config.development = config.development ?? isDev;
|
|
81
|
+
config.autoDiscover = true; // Always auto-discover routes
|
|
82
|
+
|
|
83
|
+
// Apply CLI CORS option if not set in config
|
|
84
|
+
if (!config.cors && values.cors) {
|
|
85
|
+
config.cors = {
|
|
86
|
+
origin: "*",
|
|
87
|
+
credentials: true,
|
|
88
|
+
allowHeaders: "Content-Type, Authorization",
|
|
89
|
+
allowMethods: "GET, POST, PUT, PATCH, DELETE, OPTIONS",
|
|
90
|
+
exposeHeaders: "Authorization",
|
|
91
|
+
maxAge: 86400,
|
|
92
|
+
};
|
|
93
|
+
}
|
|
84
94
|
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
if (authHandler) {
|
|
88
|
-
vector.setProtectedHandler(authHandler);
|
|
89
|
-
}
|
|
95
|
+
// Get Vector instance and configure handlers
|
|
96
|
+
vector = getVectorInstance();
|
|
90
97
|
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
98
|
+
// Load and set auth handler if configured
|
|
99
|
+
const authHandler = await configLoader.loadAuthHandler();
|
|
100
|
+
if (authHandler) {
|
|
101
|
+
vector.setProtectedHandler(authHandler);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// Load and set cache handler if configured
|
|
105
|
+
const cacheHandler = await configLoader.loadCacheHandler();
|
|
106
|
+
if (cacheHandler) {
|
|
107
|
+
vector.setCacheHandler(cacheHandler);
|
|
108
|
+
}
|
|
96
109
|
|
|
97
|
-
|
|
98
|
-
|
|
110
|
+
// Start the server
|
|
111
|
+
server = await vector.startServer(config);
|
|
99
112
|
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
113
|
+
// Verify the server is actually running
|
|
114
|
+
if (!server || !server.port) {
|
|
115
|
+
throw new Error("Server started but is not responding correctly");
|
|
116
|
+
}
|
|
104
117
|
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
118
|
+
const gray = "\x1b[90m";
|
|
119
|
+
const reset = "\x1b[0m";
|
|
120
|
+
const cyan = "\x1b[36m";
|
|
121
|
+
const green = "\x1b[32m";
|
|
122
|
+
|
|
123
|
+
console.log(
|
|
124
|
+
` ${gray}Config${reset} ${
|
|
125
|
+
configSource === "user" ? "User config loaded" : "Using defaults"
|
|
126
|
+
}`
|
|
127
|
+
);
|
|
128
|
+
console.log(` ${gray}Routes${reset} ${config.routesDir}`);
|
|
129
|
+
if (isDev && values.watch) {
|
|
130
|
+
console.log(` ${gray}Watching${reset} All project files`);
|
|
131
|
+
}
|
|
132
|
+
console.log(
|
|
133
|
+
` ${gray}CORS${reset} ${config.cors ? "Enabled" : "Disabled"}`
|
|
134
|
+
);
|
|
135
|
+
console.log(
|
|
136
|
+
` ${gray}Mode${reset} ${config.development ? "Development" : "Production"}\n`
|
|
137
|
+
);
|
|
138
|
+
console.log(
|
|
139
|
+
` ${green}Ready${reset} → ${cyan}http://${config.hostname}:${config.port}${reset}\n`
|
|
140
|
+
);
|
|
141
|
+
|
|
142
|
+
return { server, vector, config };
|
|
143
|
+
} catch (error) {
|
|
144
|
+
throw error;
|
|
113
145
|
}
|
|
114
|
-
|
|
115
|
-
` ${gray}CORS${reset} ${config.cors ? "Enabled" : "Disabled"}`
|
|
116
|
-
);
|
|
117
|
-
console.log(
|
|
118
|
-
` ${gray}Mode${reset} ${config.development ? "Development" : "Production"}\n`
|
|
119
|
-
);
|
|
120
|
-
console.log(
|
|
121
|
-
` ${green}Ready${reset} → ${cyan}http://${config.hostname}:${config.port}${reset}\n`
|
|
122
|
-
);
|
|
146
|
+
})();
|
|
123
147
|
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
console.error("[ERROR] Failed to start server:", error);
|
|
127
|
-
throw error;
|
|
128
|
-
}
|
|
148
|
+
// Race between server startup and timeout
|
|
149
|
+
return await Promise.race([serverStartPromise, timeoutPromise]);
|
|
129
150
|
}
|
|
130
151
|
|
|
131
152
|
try {
|
|
@@ -196,8 +217,9 @@ async function runDev() {
|
|
|
196
217
|
const result = await startServer();
|
|
197
218
|
server = result.server;
|
|
198
219
|
vector = result.vector;
|
|
199
|
-
} catch (error) {
|
|
200
|
-
console.error("
|
|
220
|
+
} catch (error: any) {
|
|
221
|
+
console.error("\n[Reload Error]", error.message || error);
|
|
222
|
+
// Don't exit the process on reload failures, just continue watching
|
|
201
223
|
} finally {
|
|
202
224
|
// Reset flag after a delay
|
|
203
225
|
setTimeout(() => {
|
|
@@ -211,8 +233,26 @@ async function runDev() {
|
|
|
211
233
|
console.warn(" ⚠️ File watching not available");
|
|
212
234
|
}
|
|
213
235
|
}
|
|
214
|
-
} catch (error) {
|
|
215
|
-
|
|
236
|
+
} catch (error: any) {
|
|
237
|
+
const red = "\x1b[31m";
|
|
238
|
+
const reset = "\x1b[0m";
|
|
239
|
+
|
|
240
|
+
console.error(`\n${red}[ERROR] Failed to start server${reset}\n`);
|
|
241
|
+
|
|
242
|
+
// Always show the error message and stack trace
|
|
243
|
+
if (error.message) {
|
|
244
|
+
console.error(`Message: ${error.message}`);
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
if (error.stack) {
|
|
248
|
+
console.error(`\nStack trace:`);
|
|
249
|
+
console.error(error.stack);
|
|
250
|
+
} else if (!error.message) {
|
|
251
|
+
// If no message or stack, show the raw error
|
|
252
|
+
console.error(`Raw error:`, error);
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
// Ensure we exit with error code
|
|
216
256
|
process.exit(1);
|
|
217
257
|
}
|
|
218
258
|
}
|
|
@@ -289,6 +329,7 @@ Options:
|
|
|
289
329
|
-h, --host <host> Hostname to bind to (default: localhost)
|
|
290
330
|
-r, --routes <dir> Routes directory (default: ./routes)
|
|
291
331
|
-w, --watch Watch for file changes (default: true)
|
|
332
|
+
-c, --config <path> Path to config file (default: vector.config.ts)
|
|
292
333
|
--cors Enable CORS (default: true)
|
|
293
334
|
`);
|
|
294
335
|
process.exit(1);
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { existsSync } from "node:fs";
|
|
2
|
-
import { resolve } from "node:path";
|
|
2
|
+
import { resolve, isAbsolute } from "node:path";
|
|
3
3
|
import { toFileUrl } from "../utils/path";
|
|
4
4
|
import type {
|
|
5
5
|
CacheHandler,
|
|
@@ -16,9 +16,14 @@ export class ConfigLoader<TTypes extends VectorTypes = DefaultVectorTypes> {
|
|
|
16
16
|
private config: VectorConfigSchema<TTypes> | null = null;
|
|
17
17
|
private configSource: "user" | "default" = "default";
|
|
18
18
|
|
|
19
|
-
constructor(configPath
|
|
20
|
-
//
|
|
21
|
-
|
|
19
|
+
constructor(configPath?: string) {
|
|
20
|
+
// Use provided config path or default to vector.config.ts
|
|
21
|
+
const path = configPath || "vector.config.ts";
|
|
22
|
+
|
|
23
|
+
// Handle absolute vs relative paths
|
|
24
|
+
this.configPath = isAbsolute(path)
|
|
25
|
+
? path
|
|
26
|
+
: resolve(process.cwd(), path);
|
|
22
27
|
}
|
|
23
28
|
|
|
24
29
|
async load(): Promise<VectorConfig<TTypes>> {
|
|
@@ -67,6 +72,7 @@ export class ConfigLoader<TTypes extends VectorTypes = DefaultVectorTypes> {
|
|
|
67
72
|
config.reusePort = this.config.reusePort;
|
|
68
73
|
config.development = this.config.development;
|
|
69
74
|
config.routesDir = this.config.routesDir || "./routes";
|
|
75
|
+
config.idleTimeout = this.config.idleTimeout;
|
|
70
76
|
}
|
|
71
77
|
|
|
72
78
|
// Always auto-discover routes
|
package/src/core/server.ts
CHANGED
|
@@ -1,7 +1,12 @@
|
|
|
1
|
-
import type { Server } from
|
|
2
|
-
import { cors } from
|
|
3
|
-
import type {
|
|
4
|
-
|
|
1
|
+
import type { Server } from "bun";
|
|
2
|
+
import { cors } from "itty-router";
|
|
3
|
+
import type {
|
|
4
|
+
CorsOptions,
|
|
5
|
+
DefaultVectorTypes,
|
|
6
|
+
VectorConfig,
|
|
7
|
+
VectorTypes,
|
|
8
|
+
} from "../types";
|
|
9
|
+
import type { VectorRouter } from "./router";
|
|
5
10
|
|
|
6
11
|
export class VectorServer<TTypes extends VectorTypes = DefaultVectorTypes> {
|
|
7
12
|
private server: Server | null = null;
|
|
@@ -14,36 +19,38 @@ export class VectorServer<TTypes extends VectorTypes = DefaultVectorTypes> {
|
|
|
14
19
|
this.config = config;
|
|
15
20
|
|
|
16
21
|
if (config.cors) {
|
|
17
|
-
const { preflight, corsify } = cors(
|
|
22
|
+
const { preflight, corsify } = cors(
|
|
23
|
+
this.normalizeCorsOptions(config.cors)
|
|
24
|
+
);
|
|
18
25
|
this.corsHandler = { preflight, corsify };
|
|
19
26
|
}
|
|
20
27
|
}
|
|
21
28
|
|
|
22
29
|
private normalizeCorsOptions(options: CorsOptions): any {
|
|
23
30
|
return {
|
|
24
|
-
origin: options.origin ||
|
|
31
|
+
origin: options.origin || "*",
|
|
25
32
|
credentials: options.credentials !== false,
|
|
26
33
|
allowHeaders: Array.isArray(options.allowHeaders)
|
|
27
|
-
? options.allowHeaders.join(
|
|
28
|
-
: options.allowHeaders ||
|
|
34
|
+
? options.allowHeaders.join(", ")
|
|
35
|
+
: options.allowHeaders || "Content-Type, Authorization",
|
|
29
36
|
allowMethods: Array.isArray(options.allowMethods)
|
|
30
|
-
? options.allowMethods.join(
|
|
31
|
-
: options.allowMethods ||
|
|
37
|
+
? options.allowMethods.join(", ")
|
|
38
|
+
: options.allowMethods || "GET, POST, PUT, PATCH, DELETE, OPTIONS",
|
|
32
39
|
exposeHeaders: Array.isArray(options.exposeHeaders)
|
|
33
|
-
? options.exposeHeaders.join(
|
|
34
|
-
: options.exposeHeaders ||
|
|
40
|
+
? options.exposeHeaders.join(", ")
|
|
41
|
+
: options.exposeHeaders || "Authorization",
|
|
35
42
|
maxAge: options.maxAge || 86400,
|
|
36
43
|
};
|
|
37
44
|
}
|
|
38
45
|
|
|
39
46
|
async start(): Promise<Server> {
|
|
40
47
|
const port = this.config.port || 3000;
|
|
41
|
-
const hostname = this.config.hostname ||
|
|
48
|
+
const hostname = this.config.hostname || "localhost";
|
|
42
49
|
|
|
43
50
|
const fetch = async (request: Request): Promise<Response> => {
|
|
44
51
|
try {
|
|
45
52
|
// Handle CORS preflight
|
|
46
|
-
if (this.corsHandler && request.method ===
|
|
53
|
+
if (this.corsHandler && request.method === "OPTIONS") {
|
|
47
54
|
return this.corsHandler.preflight(request);
|
|
48
55
|
}
|
|
49
56
|
|
|
@@ -57,33 +64,55 @@ export class VectorServer<TTypes extends VectorTypes = DefaultVectorTypes> {
|
|
|
57
64
|
|
|
58
65
|
return response;
|
|
59
66
|
} catch (error) {
|
|
60
|
-
console.error(
|
|
61
|
-
return new Response(
|
|
67
|
+
console.error("Server error:", error);
|
|
68
|
+
return new Response("Internal Server Error", { status: 500 });
|
|
62
69
|
}
|
|
63
70
|
};
|
|
64
71
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
72
|
+
try {
|
|
73
|
+
this.server = Bun.serve({
|
|
74
|
+
port,
|
|
75
|
+
hostname,
|
|
76
|
+
reusePort: this.config.reusePort !== false,
|
|
77
|
+
fetch,
|
|
78
|
+
idleTimeout: this.config.idleTimeout || 60,
|
|
79
|
+
error: (error) => {
|
|
80
|
+
console.error("[ERROR] Server error:", error);
|
|
81
|
+
return new Response("Internal Server Error", { status: 500 });
|
|
82
|
+
},
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
// Validate that the server actually started
|
|
86
|
+
if (!this.server || !this.server.port) {
|
|
87
|
+
throw new Error(`Failed to start server on ${hostname}:${port} - server object is invalid`);
|
|
88
|
+
}
|
|
75
89
|
|
|
76
|
-
|
|
77
|
-
|
|
90
|
+
// Server logs are handled by CLI, keep this minimal
|
|
91
|
+
console.log(`→ Vector server running at http://${hostname}:${port}`);
|
|
92
|
+
|
|
93
|
+
return this.server;
|
|
94
|
+
} catch (error: any) {
|
|
95
|
+
// Enhance error message with context for common issues
|
|
96
|
+
if (error.code === 'EADDRINUSE' || error.message?.includes('address already in use')) {
|
|
97
|
+
error.message = `Port ${port} is already in use`;
|
|
98
|
+
error.port = port;
|
|
99
|
+
} else if (error.code === 'EACCES' || error.message?.includes('permission denied')) {
|
|
100
|
+
error.message = `Permission denied to bind to port ${port}`;
|
|
101
|
+
error.port = port;
|
|
102
|
+
} else if (error.message?.includes('EADDRNOTAVAIL')) {
|
|
103
|
+
error.message = `Cannot bind to hostname ${hostname}`;
|
|
104
|
+
error.hostname = hostname;
|
|
105
|
+
}
|
|
78
106
|
|
|
79
|
-
|
|
107
|
+
throw error;
|
|
108
|
+
}
|
|
80
109
|
}
|
|
81
110
|
|
|
82
111
|
stop() {
|
|
83
112
|
if (this.server) {
|
|
84
113
|
this.server.stop();
|
|
85
114
|
this.server = null;
|
|
86
|
-
console.log(
|
|
115
|
+
console.log("Server stopped");
|
|
87
116
|
}
|
|
88
117
|
}
|
|
89
118
|
|
|
@@ -96,7 +125,7 @@ export class VectorServer<TTypes extends VectorTypes = DefaultVectorTypes> {
|
|
|
96
125
|
}
|
|
97
126
|
|
|
98
127
|
getHostname(): string {
|
|
99
|
-
return this.server?.hostname || this.config.hostname ||
|
|
128
|
+
return this.server?.hostname || this.config.hostname || "localhost";
|
|
100
129
|
}
|
|
101
130
|
|
|
102
131
|
getUrl(): string {
|
package/src/types/index.ts
CHANGED
|
@@ -83,6 +83,7 @@ export interface VectorConfig<TTypes extends VectorTypes = DefaultVectorTypes> {
|
|
|
83
83
|
finally?: AfterMiddlewareHandler<TTypes>[];
|
|
84
84
|
routesDir?: string;
|
|
85
85
|
autoDiscover?: boolean;
|
|
86
|
+
idleTimeout?: number;
|
|
86
87
|
}
|
|
87
88
|
|
|
88
89
|
// New config-driven schema - flat structure
|
|
@@ -93,18 +94,19 @@ export interface VectorConfigSchema<TTypes extends VectorTypes = DefaultVectorTy
|
|
|
93
94
|
reusePort?: boolean;
|
|
94
95
|
development?: boolean;
|
|
95
96
|
routesDir?: string;
|
|
96
|
-
|
|
97
|
+
idleTimeout?: number;
|
|
98
|
+
|
|
97
99
|
// Middleware functions
|
|
98
100
|
before?: BeforeMiddlewareHandler<TTypes>[];
|
|
99
101
|
after?: AfterMiddlewareHandler<TTypes>[];
|
|
100
|
-
|
|
102
|
+
|
|
101
103
|
// Handler functions
|
|
102
104
|
auth?: ProtectedHandler<TTypes>;
|
|
103
105
|
cache?: CacheHandler;
|
|
104
|
-
|
|
106
|
+
|
|
105
107
|
// CORS configuration
|
|
106
108
|
cors?: CorsOptions | boolean;
|
|
107
|
-
|
|
109
|
+
|
|
108
110
|
// Custom types for TypeScript
|
|
109
111
|
types?: VectorTypes;
|
|
110
112
|
}
|