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.
Files changed (128) hide show
  1. package/README.md +99 -628
  2. package/dist/auth/protected.d.ts +1 -0
  3. package/dist/auth/protected.d.ts.map +1 -1
  4. package/dist/auth/protected.js +3 -0
  5. package/dist/auth/protected.js.map +1 -1
  6. package/dist/cache/manager.d.ts +1 -0
  7. package/dist/cache/manager.d.ts.map +1 -1
  8. package/dist/cache/manager.js +5 -7
  9. package/dist/cache/manager.js.map +1 -1
  10. package/dist/cli/graceful-shutdown.d.ts +15 -0
  11. package/dist/cli/graceful-shutdown.d.ts.map +1 -0
  12. package/dist/cli/graceful-shutdown.js +42 -0
  13. package/dist/cli/graceful-shutdown.js.map +1 -0
  14. package/dist/cli/index.js +46 -97
  15. package/dist/cli/index.js.map +1 -1
  16. package/dist/cli/option-resolution.d.ts +4 -0
  17. package/dist/cli/option-resolution.d.ts.map +1 -0
  18. package/dist/cli/option-resolution.js +28 -0
  19. package/dist/cli/option-resolution.js.map +1 -0
  20. package/dist/cli.js +3423 -660
  21. package/dist/constants/index.d.ts +3 -0
  22. package/dist/constants/index.d.ts.map +1 -1
  23. package/dist/constants/index.js +6 -0
  24. package/dist/constants/index.js.map +1 -1
  25. package/dist/core/config-loader.d.ts.map +1 -1
  26. package/dist/core/config-loader.js +7 -2
  27. package/dist/core/config-loader.js.map +1 -1
  28. package/dist/core/router.d.ts +41 -17
  29. package/dist/core/router.d.ts.map +1 -1
  30. package/dist/core/router.js +432 -153
  31. package/dist/core/router.js.map +1 -1
  32. package/dist/core/server.d.ts +17 -1
  33. package/dist/core/server.d.ts.map +1 -1
  34. package/dist/core/server.js +471 -31
  35. package/dist/core/server.js.map +1 -1
  36. package/dist/core/vector.d.ts +8 -5
  37. package/dist/core/vector.d.ts.map +1 -1
  38. package/dist/core/vector.js +53 -14
  39. package/dist/core/vector.js.map +1 -1
  40. package/dist/dev/route-generator.d.ts.map +1 -1
  41. package/dist/dev/route-generator.js.map +1 -1
  42. package/dist/dev/route-scanner.d.ts.map +1 -1
  43. package/dist/dev/route-scanner.js +1 -5
  44. package/dist/dev/route-scanner.js.map +1 -1
  45. package/dist/http.d.ts +14 -14
  46. package/dist/http.d.ts.map +1 -1
  47. package/dist/http.js +34 -41
  48. package/dist/http.js.map +1 -1
  49. package/dist/index.d.ts +2 -0
  50. package/dist/index.d.ts.map +1 -1
  51. package/dist/index.js +1420 -8
  52. package/dist/index.js.map +1 -1
  53. package/dist/index.mjs +1420 -8
  54. package/dist/middleware/manager.d.ts.map +1 -1
  55. package/dist/middleware/manager.js +4 -0
  56. package/dist/middleware/manager.js.map +1 -1
  57. package/dist/openapi/docs-ui.d.ts +2 -0
  58. package/dist/openapi/docs-ui.d.ts.map +1 -0
  59. package/dist/openapi/docs-ui.js +1425 -0
  60. package/dist/openapi/docs-ui.js.map +1 -0
  61. package/dist/openapi/generator.d.ts +12 -0
  62. package/dist/openapi/generator.d.ts.map +1 -0
  63. package/dist/openapi/generator.js +502 -0
  64. package/dist/openapi/generator.js.map +1 -0
  65. package/dist/start-vector.d.ts +3 -0
  66. package/dist/start-vector.d.ts.map +1 -0
  67. package/dist/start-vector.js +38 -0
  68. package/dist/start-vector.js.map +1 -0
  69. package/dist/types/index.d.ts +95 -11
  70. package/dist/types/index.d.ts.map +1 -1
  71. package/dist/types/standard-schema.d.ts +118 -0
  72. package/dist/types/standard-schema.d.ts.map +1 -0
  73. package/dist/types/standard-schema.js +2 -0
  74. package/dist/types/standard-schema.js.map +1 -0
  75. package/dist/utils/cors.d.ts +13 -0
  76. package/dist/utils/cors.d.ts.map +1 -0
  77. package/dist/utils/cors.js +89 -0
  78. package/dist/utils/cors.js.map +1 -0
  79. package/dist/utils/logger.js +1 -1
  80. package/dist/utils/path.d.ts +6 -0
  81. package/dist/utils/path.d.ts.map +1 -1
  82. package/dist/utils/path.js +5 -0
  83. package/dist/utils/path.js.map +1 -1
  84. package/dist/utils/schema-validation.d.ts +31 -0
  85. package/dist/utils/schema-validation.d.ts.map +1 -0
  86. package/dist/utils/schema-validation.js +77 -0
  87. package/dist/utils/schema-validation.js.map +1 -0
  88. package/dist/utils/validation.d.ts.map +1 -1
  89. package/dist/utils/validation.js +3 -0
  90. package/dist/utils/validation.js.map +1 -1
  91. package/package.json +15 -12
  92. package/src/auth/protected.ts +7 -13
  93. package/src/cache/manager.ts +8 -18
  94. package/src/cli/graceful-shutdown.ts +60 -0
  95. package/src/cli/index.ts +52 -115
  96. package/src/cli/option-resolution.ts +40 -0
  97. package/src/constants/index.ts +7 -0
  98. package/src/core/config-loader.ts +7 -4
  99. package/src/core/router.ts +502 -156
  100. package/src/core/server.ts +610 -33
  101. package/src/core/vector.ts +87 -33
  102. package/src/dev/route-generator.ts +1 -3
  103. package/src/dev/route-scanner.ts +2 -9
  104. package/src/http.ts +85 -125
  105. package/src/index.ts +4 -3
  106. package/src/middleware/manager.ts +4 -0
  107. package/src/openapi/assets/favicon/android-chrome-192x192.png +0 -0
  108. package/src/openapi/assets/favicon/android-chrome-512x512.png +0 -0
  109. package/src/openapi/assets/favicon/apple-touch-icon.png +0 -0
  110. package/src/openapi/assets/favicon/favicon-16x16.png +0 -0
  111. package/src/openapi/assets/favicon/favicon-32x32.png +0 -0
  112. package/src/openapi/assets/favicon/favicon.ico +0 -0
  113. package/src/openapi/assets/favicon/site.webmanifest +11 -0
  114. package/src/openapi/assets/logo.svg +12 -0
  115. package/src/openapi/assets/logo_dark.svg +6 -0
  116. package/src/openapi/assets/logo_icon.png +0 -0
  117. package/src/openapi/assets/logo_white.svg +6 -0
  118. package/src/openapi/assets/tailwindcdn.js +83 -0
  119. package/src/openapi/docs-ui.ts +1435 -0
  120. package/src/openapi/generator.ts +586 -0
  121. package/src/start-vector.ts +50 -0
  122. package/src/types/index.ts +138 -17
  123. package/src/types/standard-schema.ts +147 -0
  124. package/src/utils/cors.ts +101 -0
  125. package/src/utils/logger.ts +1 -1
  126. package/src/utils/path.ts +6 -0
  127. package/src/utils/schema-validation.ts +123 -0
  128. package/src/utils/validation.ts +3 -0
@@ -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;KAC9B,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"}
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.1.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 and itty-router",
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 src/cli/index.ts build",
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
- "test": "bun test tests/api-error.test.ts tests/cache.test.ts tests/middleware.test.ts tests/middleware-integration.test.ts tests/router.test.ts",
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:all": "bun test 'tests/**/*.test.ts'",
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 .",
@@ -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
 
@@ -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 parts = [
144
- request.method,
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 { getVectorInstance } from '../core/vector';
6
- import { ConfigLoader } from '../core/config-loader';
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 vector: any = null;
58
+ let app: StartedVectorApp<any> | null = null;
59
+ let removeShutdownHandlers: (() => void) | null = null;
54
60
 
55
- async function startServer(): Promise<{ server: any; vector: any; config: 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; vector: any; config: any }> => {
65
- // Load configuration using ConfigLoader
66
- const configLoader = new ConfigLoader(values.config as string | undefined);
67
- const config = await configLoader.load();
68
-
69
- // Merge CLI options with loaded config
70
- // Only use CLI values if config doesn't have them
71
- config.port = config.port ?? Number.parseInt(values.port as string);
72
- config.hostname = config.hostname ?? (values.host as string);
73
- config.routesDir = config.routesDir ?? (values.routes as string);
74
- config.development = config.development ?? isDev;
75
- config.autoDiscover = true; // Always auto-discover routes
76
-
77
- // Apply CLI CORS option if not explicitly set in config
78
- // Only apply default CORS if config.cors is undefined (not set)
79
- if (config.cors === undefined && values.cors) {
80
- config.cors = {
81
- origin: '*',
82
- credentials: true,
83
- allowHeaders: 'Content-Type, Authorization',
84
- allowMethods: 'GET, POST, PUT, PATCH, DELETE, OPTIONS',
85
- exposeHeaders: 'Authorization',
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
- // Load and set cache handler if configured
100
- const cacheHandler = await configLoader.loadCacheHandler();
101
- if (cacheHandler) {
102
- vector.setCacheHandler(cacheHandler);
103
- }
93
+ return config;
94
+ },
95
+ });
104
96
 
105
- // Start the server
106
- server = await vector.startServer(config);
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, vector, config };
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 (vector) {
174
- vector.stop();
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
- vector = result.vector;
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 'build':
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 (default: ./routes)
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 (default: vector.config.ts)
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
+ }
@@ -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(`[Vector] Failed to load config from ${this.configPath}: ${msg}`);
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