vector-framework 1.1.1 → 1.2.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/README.md +99 -628
- package/dist/auth/protected.d.ts +1 -0
- package/dist/auth/protected.d.ts.map +1 -1
- package/dist/auth/protected.js +3 -0
- package/dist/auth/protected.js.map +1 -1
- package/dist/cache/manager.d.ts +1 -0
- package/dist/cache/manager.d.ts.map +1 -1
- package/dist/cache/manager.js +5 -7
- package/dist/cache/manager.js.map +1 -1
- package/dist/cli/graceful-shutdown.d.ts +15 -0
- package/dist/cli/graceful-shutdown.d.ts.map +1 -0
- package/dist/cli/graceful-shutdown.js +42 -0
- package/dist/cli/graceful-shutdown.js.map +1 -0
- package/dist/cli/index.js +46 -97
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/option-resolution.d.ts +4 -0
- package/dist/cli/option-resolution.d.ts.map +1 -0
- package/dist/cli/option-resolution.js +28 -0
- package/dist/cli/option-resolution.js.map +1 -0
- package/dist/cli.js +3423 -660
- package/dist/constants/index.d.ts +3 -0
- package/dist/constants/index.d.ts.map +1 -1
- package/dist/constants/index.js +6 -0
- package/dist/constants/index.js.map +1 -1
- package/dist/core/config-loader.d.ts.map +1 -1
- package/dist/core/config-loader.js +7 -2
- package/dist/core/config-loader.js.map +1 -1
- package/dist/core/router.d.ts +41 -17
- package/dist/core/router.d.ts.map +1 -1
- package/dist/core/router.js +432 -153
- package/dist/core/router.js.map +1 -1
- package/dist/core/server.d.ts +17 -1
- package/dist/core/server.d.ts.map +1 -1
- package/dist/core/server.js +471 -31
- package/dist/core/server.js.map +1 -1
- package/dist/core/vector.d.ts +8 -5
- package/dist/core/vector.d.ts.map +1 -1
- package/dist/core/vector.js +53 -14
- package/dist/core/vector.js.map +1 -1
- package/dist/dev/route-generator.d.ts.map +1 -1
- 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 +1 -5
- package/dist/dev/route-scanner.js.map +1 -1
- package/dist/http.d.ts +14 -14
- package/dist/http.d.ts.map +1 -1
- package/dist/http.js +34 -41
- package/dist/http.js.map +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1420 -8
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +1420 -8
- 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/openapi/docs-ui.d.ts +2 -0
- package/dist/openapi/docs-ui.d.ts.map +1 -0
- package/dist/openapi/docs-ui.js +1425 -0
- package/dist/openapi/docs-ui.js.map +1 -0
- package/dist/openapi/generator.d.ts +12 -0
- package/dist/openapi/generator.d.ts.map +1 -0
- package/dist/openapi/generator.js +502 -0
- package/dist/openapi/generator.js.map +1 -0
- package/dist/start-vector.d.ts +3 -0
- package/dist/start-vector.d.ts.map +1 -0
- package/dist/start-vector.js +38 -0
- package/dist/start-vector.js.map +1 -0
- package/dist/types/index.d.ts +95 -11
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/standard-schema.d.ts +118 -0
- package/dist/types/standard-schema.d.ts.map +1 -0
- package/dist/types/standard-schema.js +2 -0
- package/dist/types/standard-schema.js.map +1 -0
- package/dist/utils/cors.d.ts +13 -0
- package/dist/utils/cors.d.ts.map +1 -0
- package/dist/utils/cors.js +89 -0
- package/dist/utils/cors.js.map +1 -0
- package/dist/utils/logger.js +1 -1
- package/dist/utils/path.d.ts +6 -0
- package/dist/utils/path.d.ts.map +1 -1
- package/dist/utils/path.js +5 -0
- package/dist/utils/path.js.map +1 -1
- package/dist/utils/schema-validation.d.ts +31 -0
- package/dist/utils/schema-validation.d.ts.map +1 -0
- package/dist/utils/schema-validation.js +77 -0
- package/dist/utils/schema-validation.js.map +1 -0
- package/dist/utils/validation.d.ts.map +1 -1
- package/dist/utils/validation.js +3 -0
- package/dist/utils/validation.js.map +1 -1
- package/package.json +15 -12
- package/src/auth/protected.ts +7 -13
- package/src/cache/manager.ts +8 -18
- package/src/cli/graceful-shutdown.ts +60 -0
- package/src/cli/index.ts +52 -115
- package/src/cli/option-resolution.ts +40 -0
- package/src/constants/index.ts +7 -0
- package/src/core/config-loader.ts +7 -4
- package/src/core/router.ts +502 -156
- package/src/core/server.ts +610 -33
- package/src/core/vector.ts +87 -33
- package/src/dev/route-generator.ts +1 -3
- package/src/dev/route-scanner.ts +2 -9
- package/src/http.ts +85 -125
- package/src/index.ts +4 -3
- package/src/middleware/manager.ts +4 -0
- package/src/openapi/assets/favicon/android-chrome-192x192.png +0 -0
- package/src/openapi/assets/favicon/android-chrome-512x512.png +0 -0
- package/src/openapi/assets/favicon/apple-touch-icon.png +0 -0
- package/src/openapi/assets/favicon/favicon-16x16.png +0 -0
- package/src/openapi/assets/favicon/favicon-32x32.png +0 -0
- package/src/openapi/assets/favicon/favicon.ico +0 -0
- package/src/openapi/assets/favicon/site.webmanifest +11 -0
- package/src/openapi/assets/logo.svg +12 -0
- package/src/openapi/assets/logo_dark.svg +6 -0
- package/src/openapi/assets/logo_icon.png +0 -0
- package/src/openapi/assets/logo_white.svg +6 -0
- package/src/openapi/assets/tailwindcdn.js +83 -0
- package/src/openapi/docs-ui.ts +1435 -0
- package/src/openapi/generator.ts +586 -0
- package/src/start-vector.ts +50 -0
- package/src/types/index.ts +138 -17
- package/src/types/standard-schema.ts +147 -0
- package/src/utils/cors.ts +101 -0
- package/src/utils/logger.ts +1 -1
- package/src/utils/path.ts +6 -0
- package/src/utils/schema-validation.ts +123 -0
- package/src/utils/validation.ts +3 -0
package/dist/utils/validation.js
CHANGED
|
@@ -7,9 +7,12 @@ export function validateConfig(config) {
|
|
|
7
7
|
development: config.development || false,
|
|
8
8
|
routesDir: config.routesDir || DEFAULT_CONFIG.ROUTES_DIR,
|
|
9
9
|
autoDiscover: config.autoDiscover !== false,
|
|
10
|
+
defaults: config.defaults,
|
|
10
11
|
cors: config.cors ? validateCorsOptions(config.cors) : undefined,
|
|
11
12
|
before: config.before || [],
|
|
12
13
|
finally: config.finally || [],
|
|
14
|
+
startup: config.startup,
|
|
15
|
+
shutdown: config.shutdown,
|
|
13
16
|
};
|
|
14
17
|
return validatedConfig;
|
|
15
18
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"validation.js","sourceRoot":"","sources":["../../src/utils/validation.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAG9C,MAAM,UAAU,cAAc,CAAC,MAAoB;IACjD,MAAM,eAAe,GAAiB;QACpC,IAAI,EAAE,YAAY,CAAC,MAAM,CAAC,IAAI,CAAC;QAC/B,QAAQ,EAAE,MAAM,CAAC,QAAQ,IAAI,cAAc,CAAC,QAAQ;QACpD,SAAS,EAAE,MAAM,CAAC,SAAS,KAAK,KAAK;QACrC,WAAW,EAAE,MAAM,CAAC,WAAW,IAAI,KAAK;QACxC,SAAS,EAAE,MAAM,CAAC,SAAS,IAAI,cAAc,CAAC,UAAU;QACxD,YAAY,EAAE,MAAM,CAAC,YAAY,KAAK,KAAK;QAC3C,IAAI,EAAE,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,mBAAmB,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,SAAS;QAChE,MAAM,EAAE,MAAM,CAAC,MAAM,IAAI,EAAE;QAC3B,OAAO,EAAE,MAAM,CAAC,OAAO,IAAI,EAAE;
|
|
1
|
+
{"version":3,"file":"validation.js","sourceRoot":"","sources":["../../src/utils/validation.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAG9C,MAAM,UAAU,cAAc,CAAC,MAAoB;IACjD,MAAM,eAAe,GAAiB;QACpC,IAAI,EAAE,YAAY,CAAC,MAAM,CAAC,IAAI,CAAC;QAC/B,QAAQ,EAAE,MAAM,CAAC,QAAQ,IAAI,cAAc,CAAC,QAAQ;QACpD,SAAS,EAAE,MAAM,CAAC,SAAS,KAAK,KAAK;QACrC,WAAW,EAAE,MAAM,CAAC,WAAW,IAAI,KAAK;QACxC,SAAS,EAAE,MAAM,CAAC,SAAS,IAAI,cAAc,CAAC,UAAU;QACxD,YAAY,EAAE,MAAM,CAAC,YAAY,KAAK,KAAK;QAC3C,QAAQ,EAAE,MAAM,CAAC,QAAQ;QACzB,IAAI,EAAE,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,mBAAmB,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,SAAS;QAChE,MAAM,EAAE,MAAM,CAAC,MAAM,IAAI,EAAE;QAC3B,OAAO,EAAE,MAAM,CAAC,OAAO,IAAI,EAAE;QAC7B,OAAO,EAAE,MAAM,CAAC,OAAO;QACvB,QAAQ,EAAE,MAAM,CAAC,QAAQ;KAC1B,CAAC;IAEF,OAAO,eAAe,CAAC;AACzB,CAAC;AAED,SAAS,YAAY,CAAC,IAAa;IACjC,IAAI,IAAI,KAAK,SAAS,EAAE,CAAC;QACvB,OAAO,cAAc,CAAC,IAAI,CAAC;IAC7B,CAAC;IAED,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,IAAI,CAAC,IAAI,IAAI,GAAG,CAAC,IAAI,IAAI,GAAG,KAAK,EAAE,CAAC;QACxD,MAAM,IAAI,KAAK,CAAC,iBAAiB,IAAI,qCAAqC,CAAC,CAAC;IAC9E,CAAC;IAED,OAAO,IAAI,CAAC;AACd,CAAC;AAED,SAAS,mBAAmB,CAAC,IAA2B;IACtD,IAAI,IAAI,KAAK,KAAK,EAAE,CAAC;QACnB,OAAO,SAAS,CAAC;IACnB,CAAC;IAED,IAAI,IAAI,KAAK,IAAI,EAAE,CAAC;QAClB,OAAO;YACL,MAAM,EAAE,GAAG;YACX,WAAW,EAAE,IAAI;YACjB,YAAY,EAAE,CAAC,cAAc,EAAE,eAAe,CAAC;YAC/C,YAAY,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,EAAE,QAAQ,EAAE,SAAS,CAAC;YAClE,aAAa,EAAE,CAAC,eAAe,CAAC;YAChC,MAAM,EAAE,cAAc,CAAC,YAAY;SACpC,CAAC;IACJ,CAAC;IAED,OAAO,IAAI,CAAC;AACd,CAAC;AAED,MAAM,UAAU,iBAAiB,CAAC,MAAc;IAC9C,MAAM,YAAY,GAAG,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,EAAE,QAAQ,EAAE,SAAS,EAAE,MAAM,CAAC,CAAC;IAClF,OAAO,YAAY,CAAC,QAAQ,CAAC,MAAM,CAAC,WAAW,EAAE,CAAC,CAAC;AACrD,CAAC;AAED,MAAM,UAAU,WAAW,CAAC,IAAY;IACtC,OAAO,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC;AACrD,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "vector-framework",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.2.1",
|
|
4
4
|
"author": "webhie-com",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -8,15 +8,16 @@
|
|
|
8
8
|
},
|
|
9
9
|
"main": "./dist/index.js",
|
|
10
10
|
"module": "./dist/index.mjs",
|
|
11
|
-
"dependencies": {
|
|
12
|
-
"itty-router": "^5.0.0"
|
|
13
|
-
},
|
|
11
|
+
"dependencies": {},
|
|
14
12
|
"devDependencies": {
|
|
15
13
|
"@biomejs/biome": "^2.4.6",
|
|
16
14
|
"@biomejs/cli-darwin-arm64": "^2.4.6",
|
|
17
15
|
"@types/bun": "latest",
|
|
16
|
+
"arktype": "^2.2.0",
|
|
18
17
|
"oxlint": "^1.15.0",
|
|
19
|
-
"typescript": "^5.0.0"
|
|
18
|
+
"typescript": "^5.0.0",
|
|
19
|
+
"valibot": "^1.2.0",
|
|
20
|
+
"zod": "^4.3.6"
|
|
20
21
|
},
|
|
21
22
|
"peerDependencies": {
|
|
22
23
|
"bun": ">=1.0.0"
|
|
@@ -49,7 +50,7 @@
|
|
|
49
50
|
"bugs": {
|
|
50
51
|
"url": "https://github.com/webhie-com/vector/issues"
|
|
51
52
|
},
|
|
52
|
-
"description": "A modern TypeScript API framework built with Bun
|
|
53
|
+
"description": "A modern TypeScript API framework built with Bun",
|
|
53
54
|
"engines": {
|
|
54
55
|
"bun": ">=1.0.0"
|
|
55
56
|
},
|
|
@@ -65,7 +66,6 @@
|
|
|
65
66
|
"api",
|
|
66
67
|
"framework",
|
|
67
68
|
"bun",
|
|
68
|
-
"itty-router",
|
|
69
69
|
"typescript",
|
|
70
70
|
"rest",
|
|
71
71
|
"http",
|
|
@@ -77,22 +77,25 @@
|
|
|
77
77
|
"scripts": {
|
|
78
78
|
"dev": "bun run src/cli/index.ts dev",
|
|
79
79
|
"start": "bun run src/cli/index.ts start",
|
|
80
|
-
"build": "bun run
|
|
81
|
-
"build:app": "bun run src/cli/index.ts build",
|
|
80
|
+
"build": "bun run build:lib",
|
|
82
81
|
"build:lib": "bun run build:clean && bun run build:ts && bun run build:bundle && bun run build:cli",
|
|
83
82
|
"build:clean": "rm -rf dist",
|
|
84
83
|
"build:ts": "tsc",
|
|
85
84
|
"build:bundle": "bun build src/index.ts --format esm --minify --outfile dist/index.mjs && bun build src/index.ts --format cjs --minify --outfile dist/index.js",
|
|
86
85
|
"build:cli": "bun build src/cli/index.ts --target bun --outfile dist/cli.js",
|
|
87
|
-
"
|
|
86
|
+
"local:cli:build": "bun run build:lib",
|
|
87
|
+
"local:cli:dev": "bun run dist/cli.js dev --config ./vector.config.ts",
|
|
88
|
+
"local:cli:start": "bun run dist/cli.js start --config ./vector.config.ts",
|
|
89
|
+
"test": "bun test ./tests/*.test.ts",
|
|
88
90
|
"test:unit": "bun test tests/*.test.ts",
|
|
89
91
|
"test:watch": "bun test --watch tests/*.test.ts",
|
|
90
92
|
"test:coverage": "bun test --coverage tests/*.test.ts",
|
|
91
|
-
"test:e2e": "bun test tests/e2e/e2e.test.ts",
|
|
93
|
+
"test:e2e": "bun test --max-concurrency 1 tests/e2e/e2e.test.ts tests/e2e/zod-io.e2e.test.ts",
|
|
92
94
|
"test:load": "bun run tests/e2e/load.test.ts",
|
|
93
95
|
"test:soak": "bun run tests/e2e/soak.test.ts",
|
|
94
96
|
"test:benchmark": "bun run tests/e2e/benchmark.test.ts",
|
|
95
|
-
"test:
|
|
97
|
+
"test:benchmark:io-schema": "bun run tests/e2e/concurrency-io-schema-benchmark.ts",
|
|
98
|
+
"test:all": "bun test ./tests/**/*.test.ts",
|
|
96
99
|
"test:perf": "bun run test:load && bun run test:soak && bun run test:benchmark",
|
|
97
100
|
"docker:build": "docker build -t vector:latest .",
|
|
98
101
|
"docker:build:test": "docker build -t vector:test --target test .",
|
package/src/auth/protected.ts
CHANGED
|
@@ -1,10 +1,4 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
DefaultVectorTypes,
|
|
3
|
-
GetAuthType,
|
|
4
|
-
ProtectedHandler,
|
|
5
|
-
VectorRequest,
|
|
6
|
-
VectorTypes,
|
|
7
|
-
} from '../types';
|
|
1
|
+
import type { DefaultVectorTypes, GetAuthType, ProtectedHandler, VectorRequest, VectorTypes } from '../types';
|
|
8
2
|
|
|
9
3
|
export class AuthManager<TTypes extends VectorTypes = DefaultVectorTypes> {
|
|
10
4
|
private protectedHandler: ProtectedHandler<TTypes> | null = null;
|
|
@@ -13,11 +7,13 @@ export class AuthManager<TTypes extends VectorTypes = DefaultVectorTypes> {
|
|
|
13
7
|
this.protectedHandler = handler;
|
|
14
8
|
}
|
|
15
9
|
|
|
10
|
+
clearProtectedHandler() {
|
|
11
|
+
this.protectedHandler = null;
|
|
12
|
+
}
|
|
13
|
+
|
|
16
14
|
async authenticate(request: VectorRequest<TTypes>): Promise<GetAuthType<TTypes> | null> {
|
|
17
15
|
if (!this.protectedHandler) {
|
|
18
|
-
throw new Error(
|
|
19
|
-
'Protected handler not configured. Use vector.protected() to set authentication handler.'
|
|
20
|
-
);
|
|
16
|
+
throw new Error('Protected handler not configured. Use vector.protected() to set authentication handler.');
|
|
21
17
|
}
|
|
22
18
|
|
|
23
19
|
try {
|
|
@@ -25,9 +21,7 @@ export class AuthManager<TTypes extends VectorTypes = DefaultVectorTypes> {
|
|
|
25
21
|
request.authUser = authUser;
|
|
26
22
|
return authUser;
|
|
27
23
|
} catch (error) {
|
|
28
|
-
throw new Error(
|
|
29
|
-
`Authentication failed: ${error instanceof Error ? error.message : String(error)}`
|
|
30
|
-
);
|
|
24
|
+
throw new Error(`Authentication failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
31
25
|
}
|
|
32
26
|
}
|
|
33
27
|
|
package/src/cache/manager.ts
CHANGED
|
@@ -16,6 +16,10 @@ export class CacheManager<TTypes extends VectorTypes = DefaultVectorTypes> {
|
|
|
16
16
|
this.cacheHandler = handler;
|
|
17
17
|
}
|
|
18
18
|
|
|
19
|
+
clearCacheHandler() {
|
|
20
|
+
this.cacheHandler = null;
|
|
21
|
+
}
|
|
22
|
+
|
|
19
23
|
async get<T = GetCacheType<TTypes>>(
|
|
20
24
|
key: string,
|
|
21
25
|
factory: () => Promise<T>,
|
|
@@ -32,11 +36,7 @@ export class CacheManager<TTypes extends VectorTypes = DefaultVectorTypes> {
|
|
|
32
36
|
return this.getFromMemoryCache(key, factory, ttl);
|
|
33
37
|
}
|
|
34
38
|
|
|
35
|
-
private async getFromMemoryCache<T>(
|
|
36
|
-
key: string,
|
|
37
|
-
factory: () => Promise<T>,
|
|
38
|
-
ttl: number
|
|
39
|
-
): Promise<T> {
|
|
39
|
+
private async getFromMemoryCache<T>(key: string, factory: () => Promise<T>, ttl: number): Promise<T> {
|
|
40
40
|
const now = Date.now();
|
|
41
41
|
const cached = this.memoryCache.get(key);
|
|
42
42
|
|
|
@@ -104,11 +104,7 @@ export class CacheManager<TTypes extends VectorTypes = DefaultVectorTypes> {
|
|
|
104
104
|
}
|
|
105
105
|
}
|
|
106
106
|
|
|
107
|
-
async set<T = GetCacheType<TTypes>>(
|
|
108
|
-
key: string,
|
|
109
|
-
value: T,
|
|
110
|
-
ttl: number = DEFAULT_CONFIG.CACHE_TTL
|
|
111
|
-
): Promise<void> {
|
|
107
|
+
async set<T = GetCacheType<TTypes>>(key: string, value: T, ttl: number = DEFAULT_CONFIG.CACHE_TTL): Promise<void> {
|
|
112
108
|
if (ttl <= 0) {
|
|
113
109
|
return;
|
|
114
110
|
}
|
|
@@ -140,13 +136,7 @@ export class CacheManager<TTypes extends VectorTypes = DefaultVectorTypes> {
|
|
|
140
136
|
|
|
141
137
|
generateKey(request: Request & { _parsedUrl?: URL }, options?: { authUser?: any }): string {
|
|
142
138
|
const url = request._parsedUrl ?? new URL(request.url);
|
|
143
|
-
const
|
|
144
|
-
|
|
145
|
-
url.pathname,
|
|
146
|
-
url.search,
|
|
147
|
-
options?.authUser?.id != null ? String(options.authUser.id) : 'anonymous',
|
|
148
|
-
];
|
|
149
|
-
|
|
150
|
-
return parts.join(':');
|
|
139
|
+
const userId = options?.authUser?.id != null ? String(options.authUser.id) : 'anonymous';
|
|
140
|
+
return `${request.method}:${url.pathname}:${url.search}:${userId}`;
|
|
151
141
|
}
|
|
152
142
|
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
type SignalEvent = 'SIGINT' | 'SIGTERM';
|
|
2
|
+
|
|
3
|
+
interface ShutdownTarget {
|
|
4
|
+
shutdown?: () => Promise<void> | void;
|
|
5
|
+
stop?: () => void;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
interface GracefulShutdownOptions {
|
|
9
|
+
getTarget: () => ShutdownTarget | null | undefined;
|
|
10
|
+
on?: (event: SignalEvent, listener: () => void) => void;
|
|
11
|
+
off?: (event: SignalEvent, listener: () => void) => void;
|
|
12
|
+
exit?: (code: number) => void;
|
|
13
|
+
logError?: (message?: any, ...optionalParams: any[]) => void;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function installGracefulShutdownHandlers(options: GracefulShutdownOptions): () => void {
|
|
17
|
+
const on = options.on ?? ((event: SignalEvent, listener: () => void) => process.on(event, listener));
|
|
18
|
+
const off = options.off ?? ((event: SignalEvent, listener: () => void) => process.off(event, listener));
|
|
19
|
+
const exit = options.exit ?? ((code: number) => process.exit(code));
|
|
20
|
+
const logError = options.logError ?? console.error;
|
|
21
|
+
let shuttingDown = false;
|
|
22
|
+
|
|
23
|
+
const handleSignal = async (signal: SignalEvent): Promise<void> => {
|
|
24
|
+
if (shuttingDown) {
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
shuttingDown = true;
|
|
28
|
+
|
|
29
|
+
try {
|
|
30
|
+
const target = options.getTarget();
|
|
31
|
+
if (target) {
|
|
32
|
+
if (typeof target.shutdown === 'function') {
|
|
33
|
+
await target.shutdown();
|
|
34
|
+
} else if (typeof target.stop === 'function') {
|
|
35
|
+
target.stop();
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
exit(0);
|
|
39
|
+
} catch (error) {
|
|
40
|
+
logError(`[vector] Graceful shutdown failed after ${signal}:`, error);
|
|
41
|
+
exit(1);
|
|
42
|
+
}
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
const onSigint = () => {
|
|
46
|
+
void handleSignal('SIGINT');
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
const onSigterm = () => {
|
|
50
|
+
void handleSignal('SIGTERM');
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
on('SIGINT', onSigint);
|
|
54
|
+
on('SIGTERM', onSigterm);
|
|
55
|
+
|
|
56
|
+
return () => {
|
|
57
|
+
off('SIGINT', onSigint);
|
|
58
|
+
off('SIGTERM', onSigterm);
|
|
59
|
+
};
|
|
60
|
+
}
|
package/src/cli/index.ts
CHANGED
|
@@ -2,8 +2,10 @@
|
|
|
2
2
|
|
|
3
3
|
import { watch } from 'node:fs';
|
|
4
4
|
import { parseArgs } from 'node:util';
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
5
|
+
import { resolveHost, resolvePort, resolveRoutesDir } from './option-resolution';
|
|
6
|
+
import { installGracefulShutdownHandlers } from './graceful-shutdown';
|
|
7
|
+
import { startVector } from '../start-vector';
|
|
8
|
+
import type { StartedVectorApp } from '../types';
|
|
7
9
|
|
|
8
10
|
// Compatibility layer for both Node and Bun
|
|
9
11
|
const args = typeof Bun !== 'undefined' ? Bun.argv.slice(2) : process.argv.slice(2);
|
|
@@ -45,14 +47,18 @@ const { values, positionals } = parseArgs({
|
|
|
45
47
|
});
|
|
46
48
|
|
|
47
49
|
const command = positionals[0] || 'dev';
|
|
50
|
+
const hasRoutesOption = args.some((arg) => arg === '--routes' || arg === '-r' || arg.startsWith('--routes='));
|
|
51
|
+
const hasHostOption = args.some((arg) => arg === '--host' || arg === '-h' || arg.startsWith('--host='));
|
|
52
|
+
const hasPortOption = args.some((arg) => arg === '--port' || arg === '-p' || arg.startsWith('--port='));
|
|
48
53
|
|
|
49
54
|
async function runDev() {
|
|
50
55
|
const isDev = command === 'dev';
|
|
51
56
|
|
|
52
57
|
let server: any = null;
|
|
53
|
-
let
|
|
58
|
+
let app: StartedVectorApp<any> | null = null;
|
|
59
|
+
let removeShutdownHandlers: (() => void) | null = null;
|
|
54
60
|
|
|
55
|
-
async function startServer(): Promise<{ server: any;
|
|
61
|
+
async function startServer(): Promise<{ server: any; app: StartedVectorApp<any>; config: any }> {
|
|
56
62
|
// Create a timeout promise that rejects after 10 seconds
|
|
57
63
|
const timeoutPromise = new Promise<never>((_, reject) => {
|
|
58
64
|
setTimeout(() => {
|
|
@@ -61,49 +67,35 @@ async function runDev() {
|
|
|
61
67
|
});
|
|
62
68
|
|
|
63
69
|
// Create the actual server start promise
|
|
64
|
-
const serverStartPromise = (async (): Promise<{ server: any;
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
maxAge: 86400,
|
|
87
|
-
};
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
// Get Vector instance and configure handlers
|
|
91
|
-
vector = getVectorInstance();
|
|
92
|
-
|
|
93
|
-
// Load and set auth handler if configured
|
|
94
|
-
const authHandler = await configLoader.loadAuthHandler();
|
|
95
|
-
if (authHandler) {
|
|
96
|
-
vector.setProtectedHandler(authHandler);
|
|
97
|
-
}
|
|
70
|
+
const serverStartPromise = (async (): Promise<{ server: any; app: StartedVectorApp<any>; config: any }> => {
|
|
71
|
+
const explicitConfigPath = values.config as string | undefined;
|
|
72
|
+
app = await startVector({
|
|
73
|
+
configPath: explicitConfigPath,
|
|
74
|
+
mutateConfig: (loadedConfig) => {
|
|
75
|
+
const config = { ...loadedConfig } as Record<string, any>;
|
|
76
|
+
config.port = resolvePort(config.port, hasPortOption, values.port as string);
|
|
77
|
+
config.hostname = resolveHost(config.hostname, hasHostOption, values.host as string);
|
|
78
|
+
config.routesDir = resolveRoutesDir(config.routesDir, hasRoutesOption, values.routes as string);
|
|
79
|
+
config.development = config.development ?? isDev;
|
|
80
|
+
config.autoDiscover = true; // Always auto-discover routes
|
|
81
|
+
|
|
82
|
+
if (config.cors === undefined && values.cors) {
|
|
83
|
+
config.cors = {
|
|
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
|
+
}
|
|
98
92
|
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
vector.setCacheHandler(cacheHandler);
|
|
103
|
-
}
|
|
93
|
+
return config;
|
|
94
|
+
},
|
|
95
|
+
});
|
|
104
96
|
|
|
105
|
-
|
|
106
|
-
|
|
97
|
+
server = app.server;
|
|
98
|
+
const config = app.config as Record<string, any>;
|
|
107
99
|
|
|
108
100
|
// Verify the server is actually running
|
|
109
101
|
if (!server || !server.port) {
|
|
@@ -115,7 +107,7 @@ async function runDev() {
|
|
|
115
107
|
|
|
116
108
|
console.log(`\nListening on ${cyan}http://${config.hostname}:${config.port}${reset}\n`);
|
|
117
109
|
|
|
118
|
-
return { server,
|
|
110
|
+
return { server, app, config };
|
|
119
111
|
})();
|
|
120
112
|
|
|
121
113
|
// Race between server startup and timeout
|
|
@@ -127,6 +119,12 @@ async function runDev() {
|
|
|
127
119
|
const result = await startServer();
|
|
128
120
|
server = result.server;
|
|
129
121
|
|
|
122
|
+
if (!removeShutdownHandlers) {
|
|
123
|
+
removeShutdownHandlers = installGracefulShutdownHandlers({
|
|
124
|
+
getTarget: () => app,
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
|
|
130
128
|
// Setup file watching for hot reload
|
|
131
129
|
if (isDev && values.watch) {
|
|
132
130
|
try {
|
|
@@ -142,9 +140,7 @@ async function runDev() {
|
|
|
142
140
|
if (isReloading || now - lastReloadTime < 1000) return;
|
|
143
141
|
|
|
144
142
|
const segments = filename ? filename.split(/[/\\]/) : [];
|
|
145
|
-
const excluded = segments.some((s) =>
|
|
146
|
-
['node_modules', '.git', '.vector', 'dist'].includes(s)
|
|
147
|
-
);
|
|
143
|
+
const excluded = segments.some((s) => ['node_modules', '.git', '.vector', 'dist'].includes(s));
|
|
148
144
|
if (
|
|
149
145
|
filename &&
|
|
150
146
|
(filename.endsWith('.ts') || filename.endsWith('.js') || filename.endsWith('.json')) &&
|
|
@@ -170,8 +166,8 @@ async function runDev() {
|
|
|
170
166
|
changedFiles.clear();
|
|
171
167
|
|
|
172
168
|
// Stop the current server
|
|
173
|
-
if (
|
|
174
|
-
|
|
169
|
+
if (app) {
|
|
170
|
+
app.stop();
|
|
175
171
|
}
|
|
176
172
|
|
|
177
173
|
// Small delay to ensure file system operations complete
|
|
@@ -181,7 +177,7 @@ async function runDev() {
|
|
|
181
177
|
try {
|
|
182
178
|
const result = await startServer();
|
|
183
179
|
server = result.server;
|
|
184
|
-
|
|
180
|
+
app = result.app;
|
|
185
181
|
} catch (error: any) {
|
|
186
182
|
console.error('\n[Reload Error]', error.message || error);
|
|
187
183
|
// Don't exit the process on reload failures, just continue watching
|
|
@@ -213,73 +209,15 @@ async function runDev() {
|
|
|
213
209
|
}
|
|
214
210
|
}
|
|
215
211
|
|
|
216
|
-
async function runBuild() {
|
|
217
|
-
try {
|
|
218
|
-
const { RouteScanner } = await import('../dev/route-scanner');
|
|
219
|
-
const { RouteGenerator } = await import('../dev/route-generator');
|
|
220
|
-
|
|
221
|
-
// Step 1: Scan and generate routes
|
|
222
|
-
const scanner = new RouteScanner(values.routes as string);
|
|
223
|
-
const generator = new RouteGenerator();
|
|
224
|
-
|
|
225
|
-
const routes = await scanner.scan();
|
|
226
|
-
await generator.generate(routes);
|
|
227
|
-
|
|
228
|
-
// Step 2: Build the application with Bun
|
|
229
|
-
if (typeof Bun !== 'undefined') {
|
|
230
|
-
// Build the CLI as an executable
|
|
231
|
-
const buildProcess = Bun.spawn([
|
|
232
|
-
'bun',
|
|
233
|
-
'build',
|
|
234
|
-
'src/cli/index.ts',
|
|
235
|
-
'--target',
|
|
236
|
-
'bun',
|
|
237
|
-
'--outfile',
|
|
238
|
-
'dist/server.js',
|
|
239
|
-
'--minify',
|
|
240
|
-
]);
|
|
241
|
-
|
|
242
|
-
const exitCode = await buildProcess.exited;
|
|
243
|
-
if (exitCode !== 0) {
|
|
244
|
-
throw new Error(`Build failed with exit code ${exitCode}`);
|
|
245
|
-
}
|
|
246
|
-
} else {
|
|
247
|
-
// For Node.js, use child_process
|
|
248
|
-
const { spawnSync } = await import('child_process');
|
|
249
|
-
const result = spawnSync(
|
|
250
|
-
'bun',
|
|
251
|
-
['build', 'src/cli/index.ts', '--target', 'bun', '--outfile', 'dist/server.js', '--minify'],
|
|
252
|
-
{
|
|
253
|
-
stdio: 'inherit',
|
|
254
|
-
shell: true,
|
|
255
|
-
}
|
|
256
|
-
);
|
|
257
|
-
|
|
258
|
-
if (result.status !== 0) {
|
|
259
|
-
throw new Error(`Build failed with exit code ${result.status}`);
|
|
260
|
-
}
|
|
261
|
-
}
|
|
262
|
-
|
|
263
|
-
console.log('\nBuild complete: dist/server.js\n');
|
|
264
|
-
} catch (error: any) {
|
|
265
|
-
const red = '\x1b[31m';
|
|
266
|
-
const reset = '\x1b[0m';
|
|
267
|
-
console.error(`\n${red}Error: ${error.message || error}${reset}\n`);
|
|
268
|
-
process.exit(1);
|
|
269
|
-
}
|
|
270
|
-
}
|
|
271
|
-
|
|
272
212
|
switch (command) {
|
|
273
213
|
case 'dev':
|
|
274
214
|
await runDev();
|
|
275
215
|
break;
|
|
276
|
-
case '
|
|
277
|
-
await runBuild();
|
|
278
|
-
break;
|
|
279
|
-
case 'start':
|
|
216
|
+
case 'start': {
|
|
280
217
|
process.env.NODE_ENV = 'production';
|
|
281
218
|
await runDev();
|
|
282
219
|
break;
|
|
220
|
+
}
|
|
283
221
|
default:
|
|
284
222
|
console.error(`Unknown command: ${command}`);
|
|
285
223
|
console.log(`
|
|
@@ -287,15 +225,14 @@ Usage: vector [command] [options]
|
|
|
287
225
|
|
|
288
226
|
Commands:
|
|
289
227
|
dev Start development server (default)
|
|
290
|
-
build Build for production
|
|
291
228
|
start Start production server
|
|
292
229
|
|
|
293
230
|
Options:
|
|
294
231
|
-p, --port <port> Port to listen on (default: 3000)
|
|
295
232
|
-h, --host <host> Hostname to bind to (default: localhost)
|
|
296
|
-
-r, --routes <dir> Routes directory (
|
|
233
|
+
-r, --routes <dir> Routes directory (dev/start)
|
|
297
234
|
-w, --watch Watch for file changes (default: true)
|
|
298
|
-
-c, --config <path> Path to config file (
|
|
235
|
+
-c, --config <path> Path to config file (dev/start)
|
|
299
236
|
--cors Enable CORS (default: true)
|
|
300
237
|
`);
|
|
301
238
|
process.exit(1);
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
export function resolveRoutesDir(
|
|
2
|
+
configRoutesDir: string | null | undefined,
|
|
3
|
+
hasRoutesOption: boolean,
|
|
4
|
+
cliRoutes: string
|
|
5
|
+
): string {
|
|
6
|
+
if (hasRoutesOption) {
|
|
7
|
+
return cliRoutes;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
return configRoutesDir ?? cliRoutes;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function parseAndValidatePort(value: unknown): number {
|
|
14
|
+
const parsed = typeof value === 'number' ? value : Number.parseInt(String(value), 10);
|
|
15
|
+
|
|
16
|
+
if (!Number.isInteger(parsed) || parsed < 0 || parsed > 65535) {
|
|
17
|
+
throw new Error(`Invalid port value: ${String(value)}`);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
return parsed;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function resolvePort(configPort: number | null | undefined, hasPortOption: boolean, cliPort: string): number {
|
|
24
|
+
if (hasPortOption) {
|
|
25
|
+
return parseAndValidatePort(cliPort);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const resolved = configPort ?? cliPort;
|
|
29
|
+
return parseAndValidatePort(resolved);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function resolveHost(configHost: string | null | undefined, hasHostOption: boolean, cliHost: string): string {
|
|
33
|
+
const resolved = hasHostOption ? cliHost : (configHost ?? cliHost);
|
|
34
|
+
|
|
35
|
+
if (typeof resolved !== 'string' || resolved.length === 0) {
|
|
36
|
+
throw new Error(`Invalid host value: ${String(resolved)}`);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
return resolved;
|
|
40
|
+
}
|
package/src/constants/index.ts
CHANGED
|
@@ -91,3 +91,10 @@ export const HTTP_METHODS = {
|
|
|
91
91
|
OPTIONS: 'OPTIONS',
|
|
92
92
|
HEAD: 'HEAD',
|
|
93
93
|
} as const;
|
|
94
|
+
|
|
95
|
+
export const STATIC_RESPONSES: { NOT_FOUND: Response } = {
|
|
96
|
+
NOT_FOUND: new Response(JSON.stringify({ error: true, message: 'Not Found', statusCode: 404 }), {
|
|
97
|
+
status: 404,
|
|
98
|
+
headers: { 'content-type': 'application/json' },
|
|
99
|
+
}) as Response,
|
|
100
|
+
};
|
|
@@ -35,10 +35,8 @@ export class ConfigLoader<TTypes extends VectorTypes = DefaultVectorTypes> {
|
|
|
35
35
|
this.configSource = 'user';
|
|
36
36
|
} catch (error: any) {
|
|
37
37
|
const msg = error instanceof Error ? error.message : String(error);
|
|
38
|
-
console.error(`[
|
|
39
|
-
console.error(
|
|
40
|
-
'[Vector] Server is using default configuration. Fix your config file and restart.'
|
|
41
|
-
);
|
|
38
|
+
console.error(`[vector] Failed to load config from ${this.configPath}: ${msg}`);
|
|
39
|
+
console.error('[vector] Server is using default configuration. Fix your config file and restart.');
|
|
42
40
|
this.config = {};
|
|
43
41
|
}
|
|
44
42
|
} else {
|
|
@@ -64,7 +62,12 @@ export class ConfigLoader<TTypes extends VectorTypes = DefaultVectorTypes> {
|
|
|
64
62
|
config.reusePort = this.config.reusePort;
|
|
65
63
|
config.development = this.config.development;
|
|
66
64
|
config.routesDir = this.config.routesDir || './routes';
|
|
65
|
+
config.routeExcludePatterns = this.config.routeExcludePatterns;
|
|
67
66
|
config.idleTimeout = this.config.idleTimeout;
|
|
67
|
+
config.defaults = this.config.defaults;
|
|
68
|
+
config.openapi = this.config.openapi;
|
|
69
|
+
config.startup = this.config.startup;
|
|
70
|
+
config.shutdown = this.config.shutdown;
|
|
68
71
|
}
|
|
69
72
|
|
|
70
73
|
// Always auto-discover routes
|