vector-framework 1.2.0 → 1.2.2

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 (77) hide show
  1. package/README.md +19 -0
  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 +3 -0
  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 +37 -43
  15. package/dist/cli/index.js.map +1 -1
  16. package/dist/cli.js +967 -222
  17. package/dist/core/config-loader.d.ts.map +1 -1
  18. package/dist/core/config-loader.js +5 -2
  19. package/dist/core/config-loader.js.map +1 -1
  20. package/dist/core/server.d.ts +4 -0
  21. package/dist/core/server.d.ts.map +1 -1
  22. package/dist/core/server.js +240 -9
  23. package/dist/core/server.js.map +1 -1
  24. package/dist/core/vector.d.ts +4 -2
  25. package/dist/core/vector.d.ts.map +1 -1
  26. package/dist/core/vector.js +32 -2
  27. package/dist/core/vector.js.map +1 -1
  28. package/dist/errors/index.cjs +2 -0
  29. package/dist/index.cjs +1434 -0
  30. package/dist/index.d.ts +2 -0
  31. package/dist/index.d.ts.map +1 -1
  32. package/dist/index.js +12 -1327
  33. package/dist/index.js.map +1 -1
  34. package/dist/index.mjs +153 -46
  35. package/dist/openapi/docs-ui.d.ts +1 -1
  36. package/dist/openapi/docs-ui.d.ts.map +1 -1
  37. package/dist/openapi/docs-ui.js +147 -35
  38. package/dist/openapi/docs-ui.js.map +1 -1
  39. package/dist/openapi/generator.d.ts.map +1 -1
  40. package/dist/openapi/generator.js +318 -6
  41. package/dist/openapi/generator.js.map +1 -1
  42. package/dist/start-vector.d.ts +3 -0
  43. package/dist/start-vector.d.ts.map +1 -0
  44. package/dist/start-vector.js +38 -0
  45. package/dist/start-vector.js.map +1 -0
  46. package/dist/types/index.d.ts +25 -0
  47. package/dist/types/index.d.ts.map +1 -1
  48. package/dist/utils/logger.js +1 -1
  49. package/dist/utils/validation.d.ts.map +1 -1
  50. package/dist/utils/validation.js +2 -0
  51. package/dist/utils/validation.js.map +1 -1
  52. package/package.json +10 -14
  53. package/src/auth/protected.ts +4 -0
  54. package/src/cache/manager.ts +4 -0
  55. package/src/cli/graceful-shutdown.ts +60 -0
  56. package/src/cli/index.ts +42 -49
  57. package/src/core/config-loader.ts +5 -2
  58. package/src/core/server.ts +304 -9
  59. package/src/core/vector.ts +38 -4
  60. package/src/index.ts +4 -3
  61. package/src/openapi/assets/favicon/android-chrome-192x192.png +0 -0
  62. package/src/openapi/assets/favicon/android-chrome-512x512.png +0 -0
  63. package/src/openapi/assets/favicon/apple-touch-icon.png +0 -0
  64. package/src/openapi/assets/favicon/favicon-16x16.png +0 -0
  65. package/src/openapi/assets/favicon/favicon-32x32.png +0 -0
  66. package/src/openapi/assets/favicon/favicon.ico +0 -0
  67. package/src/openapi/assets/favicon/site.webmanifest +11 -0
  68. package/src/openapi/assets/logo.svg +12 -0
  69. package/src/openapi/assets/logo_dark.svg +6 -0
  70. package/src/openapi/assets/logo_icon.png +0 -0
  71. package/src/openapi/assets/logo_white.svg +6 -0
  72. package/src/openapi/docs-ui.ts +153 -35
  73. package/src/openapi/generator.ts +341 -6
  74. package/src/start-vector.ts +50 -0
  75. package/src/types/index.ts +34 -0
  76. package/src/utils/logger.ts +1 -1
  77. package/src/utils/validation.ts +2 -0
@@ -15,6 +15,7 @@ interface NormalizedOpenAPIConfig {
15
15
  docs: {
16
16
  enabled: boolean;
17
17
  path: string;
18
+ exposePaths?: string[];
18
19
  };
19
20
  info?: {
20
21
  title?: string;
@@ -24,6 +25,15 @@ interface NormalizedOpenAPIConfig {
24
25
  }
25
26
 
26
27
  const OPENAPI_TAILWIND_ASSET_PATH = '/_vector/openapi/tailwindcdn.js';
28
+ const OPENAPI_LOGO_DARK_ASSET_PATH = '/_vector/openapi/logo_dark.svg';
29
+ const OPENAPI_LOGO_WHITE_ASSET_PATH = '/_vector/openapi/logo_white.svg';
30
+ const OPENAPI_APPLE_TOUCH_ICON_ASSET_PATH = '/_vector/openapi/favicon/apple-touch-icon.png';
31
+ const OPENAPI_FAVICON_32_ASSET_PATH = '/_vector/openapi/favicon/favicon-32x32.png';
32
+ const OPENAPI_FAVICON_16_ASSET_PATH = '/_vector/openapi/favicon/favicon-16x16.png';
33
+ const OPENAPI_FAVICON_ICO_ASSET_PATH = '/_vector/openapi/favicon/favicon.ico';
34
+ const OPENAPI_WEBMANIFEST_ASSET_PATH = '/_vector/openapi/favicon/site.webmanifest';
35
+ const OPENAPI_ANDROID_192_ASSET_PATH = '/_vector/openapi/favicon/android-chrome-192x192.png';
36
+ const OPENAPI_ANDROID_512_ASSET_PATH = '/_vector/openapi/favicon/android-chrome-512x512.png';
27
37
  const OPENAPI_TAILWIND_ASSET_RELATIVE_CANDIDATES = [
28
38
  // Source execution (src/core/server.ts -> src/openapi/assets/tailwindcdn.js)
29
39
  '../openapi/assets/tailwindcdn.js',
@@ -32,15 +42,58 @@ const OPENAPI_TAILWIND_ASSET_RELATIVE_CANDIDATES = [
32
42
  // Unbundled dist/core/server.js execution (dist/core -> src/openapi/assets/tailwindcdn.js)
33
43
  '../../src/openapi/assets/tailwindcdn.js',
34
44
  ] as const;
45
+ const OPENAPI_LOGO_DARK_ASSET_RELATIVE_CANDIDATES = [
46
+ // Source execution (src/core/server.ts -> src/openapi/assets/logo_dark.svg)
47
+ '../openapi/assets/logo_dark.svg',
48
+ // Bundled dist entrypoints (dist/index.mjs|dist/cli.js -> src/openapi/assets/logo_dark.svg)
49
+ '../src/openapi/assets/logo_dark.svg',
50
+ // Unbundled dist/core/server.js execution (dist/core -> src/openapi/assets/logo_dark.svg)
51
+ '../../src/openapi/assets/logo_dark.svg',
52
+ ] as const;
53
+ const OPENAPI_LOGO_WHITE_ASSET_RELATIVE_CANDIDATES = [
54
+ // Source execution (src/core/server.ts -> src/openapi/assets/logo_white.svg)
55
+ '../openapi/assets/logo_white.svg',
56
+ // Bundled dist entrypoints (dist/index.mjs|dist/cli.js -> src/openapi/assets/logo_white.svg)
57
+ '../src/openapi/assets/logo_white.svg',
58
+ // Unbundled dist/core/server.js execution (dist/core -> src/openapi/assets/logo_white.svg)
59
+ '../../src/openapi/assets/logo_white.svg',
60
+ ] as const;
35
61
  const OPENAPI_TAILWIND_ASSET_CWD_CANDIDATES = [
36
62
  'src/openapi/assets/tailwindcdn.js',
37
63
  'openapi/assets/tailwindcdn.js',
38
64
  'dist/openapi/assets/tailwindcdn.js',
39
65
  ] as const;
66
+ const OPENAPI_LOGO_DARK_ASSET_CWD_CANDIDATES = [
67
+ 'src/openapi/assets/logo_dark.svg',
68
+ 'openapi/assets/logo_dark.svg',
69
+ 'dist/openapi/assets/logo_dark.svg',
70
+ ] as const;
71
+ const OPENAPI_LOGO_WHITE_ASSET_CWD_CANDIDATES = [
72
+ 'src/openapi/assets/logo_white.svg',
73
+ 'openapi/assets/logo_white.svg',
74
+ 'dist/openapi/assets/logo_white.svg',
75
+ ] as const;
76
+ const OPENAPI_FAVICON_ASSET_RELATIVE_BASE_CANDIDATES = [
77
+ '../openapi/assets/favicon',
78
+ '../src/openapi/assets/favicon',
79
+ '../../src/openapi/assets/favicon',
80
+ ] as const;
81
+ const OPENAPI_FAVICON_ASSET_CWD_BASE_CANDIDATES = [
82
+ 'src/openapi/assets/favicon',
83
+ 'openapi/assets/favicon',
84
+ 'dist/openapi/assets/favicon',
85
+ ] as const;
40
86
  const OPENAPI_TAILWIND_ASSET_INLINE_FALLBACK = '/* OpenAPI docs runtime asset missing: tailwind disabled */';
41
87
 
42
- function resolveOpenAPITailwindAssetFile(): ReturnType<typeof Bun.file> | null {
43
- for (const relativePath of OPENAPI_TAILWIND_ASSET_RELATIVE_CANDIDATES) {
88
+ function buildOpenAPIAssetCandidatePaths(bases: readonly string[], filename: string): string[] {
89
+ return bases.map((base) => `${base}/${filename}`);
90
+ }
91
+
92
+ function resolveOpenAPIAssetFile(
93
+ relativeCandidates: readonly string[],
94
+ cwdCandidates: readonly string[]
95
+ ): ReturnType<typeof Bun.file> | null {
96
+ for (const relativePath of relativeCandidates) {
44
97
  try {
45
98
  const fileUrl = new URL(relativePath, import.meta.url);
46
99
  if (existsSync(fileUrl)) {
@@ -52,7 +105,7 @@ function resolveOpenAPITailwindAssetFile(): ReturnType<typeof Bun.file> | null {
52
105
  }
53
106
 
54
107
  const cwd = process.cwd();
55
- for (const relativePath of OPENAPI_TAILWIND_ASSET_CWD_CANDIDATES) {
108
+ for (const relativePath of cwdCandidates) {
56
109
  const absolutePath = join(cwd, relativePath);
57
110
  if (existsSync(absolutePath)) {
58
111
  return Bun.file(absolutePath);
@@ -62,9 +115,93 @@ function resolveOpenAPITailwindAssetFile(): ReturnType<typeof Bun.file> | null {
62
115
  return null;
63
116
  }
64
117
 
65
- const OPENAPI_TAILWIND_ASSET_FILE = resolveOpenAPITailwindAssetFile();
118
+ const OPENAPI_TAILWIND_ASSET_FILE = resolveOpenAPIAssetFile(
119
+ OPENAPI_TAILWIND_ASSET_RELATIVE_CANDIDATES,
120
+ OPENAPI_TAILWIND_ASSET_CWD_CANDIDATES
121
+ );
122
+ const OPENAPI_LOGO_DARK_ASSET_FILE = resolveOpenAPIAssetFile(
123
+ OPENAPI_LOGO_DARK_ASSET_RELATIVE_CANDIDATES,
124
+ OPENAPI_LOGO_DARK_ASSET_CWD_CANDIDATES
125
+ );
126
+ const OPENAPI_LOGO_WHITE_ASSET_FILE = resolveOpenAPIAssetFile(
127
+ OPENAPI_LOGO_WHITE_ASSET_RELATIVE_CANDIDATES,
128
+ OPENAPI_LOGO_WHITE_ASSET_CWD_CANDIDATES
129
+ );
130
+ const OPENAPI_APPLE_TOUCH_ICON_ASSET_FILE = resolveOpenAPIAssetFile(
131
+ buildOpenAPIAssetCandidatePaths(OPENAPI_FAVICON_ASSET_RELATIVE_BASE_CANDIDATES, 'apple-touch-icon.png'),
132
+ buildOpenAPIAssetCandidatePaths(OPENAPI_FAVICON_ASSET_CWD_BASE_CANDIDATES, 'apple-touch-icon.png')
133
+ );
134
+ const OPENAPI_FAVICON_32_ASSET_FILE = resolveOpenAPIAssetFile(
135
+ buildOpenAPIAssetCandidatePaths(OPENAPI_FAVICON_ASSET_RELATIVE_BASE_CANDIDATES, 'favicon-32x32.png'),
136
+ buildOpenAPIAssetCandidatePaths(OPENAPI_FAVICON_ASSET_CWD_BASE_CANDIDATES, 'favicon-32x32.png')
137
+ );
138
+ const OPENAPI_FAVICON_16_ASSET_FILE = resolveOpenAPIAssetFile(
139
+ buildOpenAPIAssetCandidatePaths(OPENAPI_FAVICON_ASSET_RELATIVE_BASE_CANDIDATES, 'favicon-16x16.png'),
140
+ buildOpenAPIAssetCandidatePaths(OPENAPI_FAVICON_ASSET_CWD_BASE_CANDIDATES, 'favicon-16x16.png')
141
+ );
142
+ const OPENAPI_FAVICON_ICO_ASSET_FILE = resolveOpenAPIAssetFile(
143
+ buildOpenAPIAssetCandidatePaths(OPENAPI_FAVICON_ASSET_RELATIVE_BASE_CANDIDATES, 'favicon.ico'),
144
+ buildOpenAPIAssetCandidatePaths(OPENAPI_FAVICON_ASSET_CWD_BASE_CANDIDATES, 'favicon.ico')
145
+ );
146
+ const OPENAPI_WEBMANIFEST_ASSET_FILE = resolveOpenAPIAssetFile(
147
+ buildOpenAPIAssetCandidatePaths(OPENAPI_FAVICON_ASSET_RELATIVE_BASE_CANDIDATES, 'site.webmanifest'),
148
+ buildOpenAPIAssetCandidatePaths(OPENAPI_FAVICON_ASSET_CWD_BASE_CANDIDATES, 'site.webmanifest')
149
+ );
150
+ const OPENAPI_ANDROID_192_ASSET_FILE = resolveOpenAPIAssetFile(
151
+ buildOpenAPIAssetCandidatePaths(OPENAPI_FAVICON_ASSET_RELATIVE_BASE_CANDIDATES, 'android-chrome-192x192.png'),
152
+ buildOpenAPIAssetCandidatePaths(OPENAPI_FAVICON_ASSET_CWD_BASE_CANDIDATES, 'android-chrome-192x192.png')
153
+ );
154
+ const OPENAPI_ANDROID_512_ASSET_FILE = resolveOpenAPIAssetFile(
155
+ buildOpenAPIAssetCandidatePaths(OPENAPI_FAVICON_ASSET_RELATIVE_BASE_CANDIDATES, 'android-chrome-512x512.png'),
156
+ buildOpenAPIAssetCandidatePaths(OPENAPI_FAVICON_ASSET_CWD_BASE_CANDIDATES, 'android-chrome-512x512.png')
157
+ );
158
+ const OPENAPI_FAVICON_ASSETS = [
159
+ {
160
+ path: OPENAPI_APPLE_TOUCH_ICON_ASSET_PATH,
161
+ file: OPENAPI_APPLE_TOUCH_ICON_ASSET_FILE,
162
+ contentType: 'image/png',
163
+ filename: 'apple-touch-icon.png',
164
+ },
165
+ {
166
+ path: OPENAPI_FAVICON_32_ASSET_PATH,
167
+ file: OPENAPI_FAVICON_32_ASSET_FILE,
168
+ contentType: 'image/png',
169
+ filename: 'favicon-32x32.png',
170
+ },
171
+ {
172
+ path: OPENAPI_FAVICON_16_ASSET_PATH,
173
+ file: OPENAPI_FAVICON_16_ASSET_FILE,
174
+ contentType: 'image/png',
175
+ filename: 'favicon-16x16.png',
176
+ },
177
+ {
178
+ path: OPENAPI_FAVICON_ICO_ASSET_PATH,
179
+ file: OPENAPI_FAVICON_ICO_ASSET_FILE,
180
+ contentType: 'image/x-icon',
181
+ filename: 'favicon.ico',
182
+ },
183
+ {
184
+ path: OPENAPI_WEBMANIFEST_ASSET_PATH,
185
+ file: OPENAPI_WEBMANIFEST_ASSET_FILE,
186
+ contentType: 'application/manifest+json; charset=utf-8',
187
+ filename: 'site.webmanifest',
188
+ },
189
+ {
190
+ path: OPENAPI_ANDROID_192_ASSET_PATH,
191
+ file: OPENAPI_ANDROID_192_ASSET_FILE,
192
+ contentType: 'image/png',
193
+ filename: 'android-chrome-192x192.png',
194
+ },
195
+ {
196
+ path: OPENAPI_ANDROID_512_ASSET_PATH,
197
+ file: OPENAPI_ANDROID_512_ASSET_FILE,
198
+ contentType: 'image/png',
199
+ filename: 'android-chrome-512x512.png',
200
+ },
201
+ ] as const;
66
202
  const DOCS_HTML_CACHE_CONTROL = 'public, max-age=0, must-revalidate';
67
203
  const DOCS_ASSET_CACHE_CONTROL = 'public, max-age=31536000, immutable';
204
+ const DOCS_ASSET_ERROR_CACHE_CONTROL = 'no-store';
68
205
 
69
206
  interface OpenAPIDocsHtmlCacheEntry {
70
207
  html: string;
@@ -72,6 +209,30 @@ interface OpenAPIDocsHtmlCacheEntry {
72
209
  etag: string;
73
210
  }
74
211
 
212
+ function escapeRegex(value: string): string {
213
+ return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
214
+ }
215
+
216
+ function wildcardPatternToRegex(pattern: string): RegExp {
217
+ let regexSource = '^';
218
+ for (const char of pattern) {
219
+ if (char === '*') {
220
+ regexSource += '.*';
221
+ continue;
222
+ }
223
+ regexSource += escapeRegex(char);
224
+ }
225
+ regexSource += '$';
226
+ return new RegExp(regexSource);
227
+ }
228
+
229
+ function matchesExposePath(path: string, exposePathPattern: string): boolean {
230
+ if (!exposePathPattern.includes('*')) {
231
+ return path === exposePathPattern;
232
+ }
233
+ return wildcardPatternToRegex(exposePathPattern).test(path);
234
+ }
235
+
75
236
  export class VectorServer<TTypes extends VectorTypes = DefaultVectorTypes> {
76
237
  private server: Server | null = null;
77
238
  private router: VectorRouter<TTypes>;
@@ -81,6 +242,8 @@ export class VectorServer<TTypes extends VectorTypes = DefaultVectorTypes> {
81
242
  private openapiDocsHtmlCache: OpenAPIDocsHtmlCacheEntry | null = null;
82
243
  private openapiWarningsLogged = false;
83
244
  private openapiTailwindMissingLogged = false;
245
+ private openapiLogoDarkMissingLogged = false;
246
+ private openapiLogoWhiteMissingLogged = false;
84
247
  private corsHandler: {
85
248
  preflight: (request: Request) => Response;
86
249
  corsify: (response: Response, request: Request) => Response;
@@ -149,10 +312,15 @@ export class VectorServer<TTypes extends VectorTypes = DefaultVectorTypes> {
149
312
  const docsValue = openapiObject.docs;
150
313
  const docs =
151
314
  typeof docsValue === 'boolean'
152
- ? { enabled: docsValue, path: '/docs' }
315
+ ? { enabled: docsValue, path: '/docs', exposePaths: undefined }
153
316
  : {
154
317
  enabled: docsValue?.enabled === true,
155
318
  path: docsValue?.path || '/docs',
319
+ exposePaths: Array.isArray(docsValue?.exposePaths)
320
+ ? docsValue.exposePaths
321
+ .map((path) => (typeof path === 'string' ? path.trim() : ''))
322
+ .filter((path) => path.length > 0)
323
+ : undefined,
156
324
  };
157
325
 
158
326
  return {
@@ -170,6 +338,17 @@ export class VectorServer<TTypes extends VectorTypes = DefaultVectorTypes> {
170
338
  );
171
339
  }
172
340
 
341
+ private shouldLogOpenAPIConversionWarnings(): boolean {
342
+ const nodeEnv = process.env.NODE_ENV;
343
+ const isDevelopment = this.config.development !== false && nodeEnv !== 'production';
344
+ if (!isDevelopment) {
345
+ return false;
346
+ }
347
+
348
+ const logLevel = process.env.LOG_LEVEL;
349
+ return typeof logLevel === 'string' && logLevel.toLowerCase() === 'debug';
350
+ }
351
+
173
352
  private getOpenAPIDocument(): Record<string, unknown> {
174
353
  if (this.openapiDocCache) {
175
354
  return this.openapiDocCache;
@@ -183,8 +362,10 @@ export class VectorServer<TTypes extends VectorTypes = DefaultVectorTypes> {
183
362
  });
184
363
 
185
364
  if (!this.openapiWarningsLogged && result.warnings.length > 0) {
186
- for (const warning of result.warnings) {
187
- console.warn(warning);
365
+ if (this.shouldLogOpenAPIConversionWarnings()) {
366
+ for (const warning of result.warnings) {
367
+ console.warn(warning);
368
+ }
188
369
  }
189
370
  this.openapiWarningsLogged = true;
190
371
  }
@@ -193,12 +374,48 @@ export class VectorServer<TTypes extends VectorTypes = DefaultVectorTypes> {
193
374
  return this.openapiDocCache;
194
375
  }
195
376
 
377
+ private getOpenAPIDocumentForDocs(): Record<string, unknown> {
378
+ const exposePaths = this.openapiConfig.docs.exposePaths;
379
+ const document = this.getOpenAPIDocument();
380
+
381
+ if (!Array.isArray(exposePaths) || exposePaths.length === 0) {
382
+ return document;
383
+ }
384
+
385
+ const existingPaths =
386
+ document.paths && typeof document.paths === 'object' && !Array.isArray(document.paths)
387
+ ? (document.paths as Record<string, unknown>)
388
+ : {};
389
+
390
+ const filteredPaths: Record<string, unknown> = {};
391
+ for (const [path, value] of Object.entries(existingPaths)) {
392
+ if (exposePaths.some((pattern) => matchesExposePath(path, pattern))) {
393
+ filteredPaths[path] = value;
394
+ }
395
+ }
396
+
397
+ return {
398
+ ...document,
399
+ paths: filteredPaths,
400
+ };
401
+ }
402
+
196
403
  private getOpenAPIDocsHtmlCacheEntry(): OpenAPIDocsHtmlCacheEntry {
197
404
  if (this.openapiDocsHtmlCache) {
198
405
  return this.openapiDocsHtmlCache;
199
406
  }
200
407
 
201
- const html = renderOpenAPIDocsHtml(this.getOpenAPIDocument(), this.openapiConfig.path, OPENAPI_TAILWIND_ASSET_PATH);
408
+ const html = renderOpenAPIDocsHtml(
409
+ this.getOpenAPIDocumentForDocs(),
410
+ this.openapiConfig.path,
411
+ OPENAPI_TAILWIND_ASSET_PATH,
412
+ OPENAPI_LOGO_DARK_ASSET_PATH,
413
+ OPENAPI_LOGO_WHITE_ASSET_PATH,
414
+ OPENAPI_APPLE_TOUCH_ICON_ASSET_PATH,
415
+ OPENAPI_FAVICON_32_ASSET_PATH,
416
+ OPENAPI_FAVICON_16_ASSET_PATH,
417
+ OPENAPI_WEBMANIFEST_ASSET_PATH
418
+ );
202
419
  const gzip = Bun.gzipSync(html);
203
420
  const etag = `"${Bun.hash(html).toString(16)}"`;
204
421
 
@@ -220,6 +437,11 @@ export class VectorServer<TTypes extends VectorTypes = DefaultVectorTypes> {
220
437
  if (this.openapiConfig.docs.enabled) {
221
438
  reserved.add(this.openapiConfig.docs.path);
222
439
  reserved.add(OPENAPI_TAILWIND_ASSET_PATH);
440
+ reserved.add(OPENAPI_LOGO_DARK_ASSET_PATH);
441
+ reserved.add(OPENAPI_LOGO_WHITE_ASSET_PATH);
442
+ for (const asset of OPENAPI_FAVICON_ASSETS) {
443
+ reserved.add(asset.path);
444
+ }
223
445
  }
224
446
 
225
447
  const methodConflicts = this.router
@@ -316,6 +538,79 @@ export class VectorServer<TTypes extends VectorTypes = DefaultVectorTypes> {
316
538
  });
317
539
  }
318
540
 
541
+ if (this.openapiConfig.docs.enabled && pathname === OPENAPI_LOGO_DARK_ASSET_PATH) {
542
+ if (!OPENAPI_LOGO_DARK_ASSET_FILE) {
543
+ if (!this.openapiLogoDarkMissingLogged) {
544
+ this.openapiLogoDarkMissingLogged = true;
545
+ console.warn('[OpenAPI] Missing docs runtime asset "logo_dark.svg".');
546
+ }
547
+
548
+ return new Response('OpenAPI docs runtime asset missing: logo_dark.svg', {
549
+ status: 404,
550
+ headers: {
551
+ 'content-type': 'text/plain; charset=utf-8',
552
+ 'cache-control': DOCS_ASSET_ERROR_CACHE_CONTROL,
553
+ },
554
+ });
555
+ }
556
+
557
+ return new Response(OPENAPI_LOGO_DARK_ASSET_FILE, {
558
+ status: 200,
559
+ headers: {
560
+ 'content-type': 'image/svg+xml; charset=utf-8',
561
+ 'cache-control': DOCS_ASSET_CACHE_CONTROL,
562
+ },
563
+ });
564
+ }
565
+
566
+ if (this.openapiConfig.docs.enabled && pathname === OPENAPI_LOGO_WHITE_ASSET_PATH) {
567
+ if (!OPENAPI_LOGO_WHITE_ASSET_FILE) {
568
+ if (!this.openapiLogoWhiteMissingLogged) {
569
+ this.openapiLogoWhiteMissingLogged = true;
570
+ console.warn('[OpenAPI] Missing docs runtime asset "logo_white.svg".');
571
+ }
572
+
573
+ return new Response('OpenAPI docs runtime asset missing: logo_white.svg', {
574
+ status: 404,
575
+ headers: {
576
+ 'content-type': 'text/plain; charset=utf-8',
577
+ 'cache-control': DOCS_ASSET_ERROR_CACHE_CONTROL,
578
+ },
579
+ });
580
+ }
581
+
582
+ return new Response(OPENAPI_LOGO_WHITE_ASSET_FILE, {
583
+ status: 200,
584
+ headers: {
585
+ 'content-type': 'image/svg+xml; charset=utf-8',
586
+ 'cache-control': DOCS_ASSET_CACHE_CONTROL,
587
+ },
588
+ });
589
+ }
590
+
591
+ if (this.openapiConfig.docs.enabled) {
592
+ const faviconAsset = OPENAPI_FAVICON_ASSETS.find((asset) => asset.path === pathname);
593
+ if (faviconAsset) {
594
+ if (!faviconAsset.file) {
595
+ return new Response(`OpenAPI docs runtime asset missing: ${faviconAsset.filename}`, {
596
+ status: 404,
597
+ headers: {
598
+ 'content-type': 'text/plain; charset=utf-8',
599
+ 'cache-control': DOCS_ASSET_ERROR_CACHE_CONTROL,
600
+ },
601
+ });
602
+ }
603
+
604
+ return new Response(faviconAsset.file, {
605
+ status: 200,
606
+ headers: {
607
+ 'content-type': faviconAsset.contentType,
608
+ 'cache-control': DOCS_ASSET_CACHE_CONTROL,
609
+ },
610
+ });
611
+ }
612
+ }
613
+
319
614
  return null;
320
615
  }
321
616
 
@@ -385,7 +680,7 @@ export class VectorServer<TTypes extends VectorTypes = DefaultVectorTypes> {
385
680
  reusePort: this.config.reusePort !== false,
386
681
  routes: this.router.getRouteTable(),
387
682
  fetch: fallbackFetch,
388
- idleTimeout: this.config.idleTimeout || 60,
683
+ idleTimeout: this.config.idleTimeout ?? 60,
389
684
  error: (error, request?: Request) => {
390
685
  console.error('[ERROR] Server error:', error);
391
686
  return this.applyCors(new Response('Internal Server Error', { status: 500 }), request);
@@ -39,6 +39,7 @@ export class Vector<TTypes extends VectorTypes = DefaultVectorTypes> {
39
39
  private routeGenerator: RouteGenerator | null = null;
40
40
  private _protectedHandler: ProtectedHandler<TTypes> | null = null;
41
41
  private _cacheHandler: CacheHandler | null = null;
42
+ private shutdownPromise: Promise<void> | null = null;
42
43
 
43
44
  private constructor() {
44
45
  this.middlewareManager = new MiddlewareManager<TTypes>();
@@ -56,9 +57,13 @@ export class Vector<TTypes extends VectorTypes = DefaultVectorTypes> {
56
57
  }
57
58
 
58
59
  // Internal method to set protected handler
59
- setProtectedHandler(handler: ProtectedHandler<TTypes>) {
60
+ setProtectedHandler(handler: ProtectedHandler<TTypes> | null) {
60
61
  this._protectedHandler = handler;
61
- this.authManager.setProtectedHandler(handler);
62
+ if (handler) {
63
+ this.authManager.setProtectedHandler(handler);
64
+ return;
65
+ }
66
+ this.authManager.clearProtectedHandler();
62
67
  }
63
68
 
64
69
  getProtectedHandler(): ProtectedHandler<TTypes> | null {
@@ -66,9 +71,13 @@ export class Vector<TTypes extends VectorTypes = DefaultVectorTypes> {
66
71
  }
67
72
 
68
73
  // Internal method to set cache handler
69
- setCacheHandler(handler: CacheHandler) {
74
+ setCacheHandler(handler: CacheHandler | null) {
70
75
  this._cacheHandler = handler;
71
- this.cacheManager.setCacheHandler(handler);
76
+ if (handler) {
77
+ this.cacheManager.setCacheHandler(handler);
78
+ return;
79
+ }
80
+ this.cacheManager.clearCacheHandler();
72
81
  }
73
82
 
74
83
  getCacheHandler(): CacheHandler | null {
@@ -107,6 +116,10 @@ export class Vector<TTypes extends VectorTypes = DefaultVectorTypes> {
107
116
  this.middlewareManager.addFinally(...config.finally);
108
117
  }
109
118
 
119
+ if (typeof this.config.startup === 'function') {
120
+ await this.config.startup();
121
+ }
122
+
110
123
  if (this.config.autoDiscover !== false) {
111
124
  await this.discoverRoutes();
112
125
  }
@@ -228,6 +241,27 @@ export class Vector<TTypes extends VectorTypes = DefaultVectorTypes> {
228
241
  // Routes will be cleared on next startServer() call
229
242
  }
230
243
 
244
+ async shutdown(): Promise<void> {
245
+ if (this.shutdownPromise) {
246
+ return this.shutdownPromise;
247
+ }
248
+
249
+ this.shutdownPromise = (async () => {
250
+ // Stop accepting new traffic first.
251
+ this.stop();
252
+
253
+ if (typeof this.config.shutdown === 'function') {
254
+ await this.config.shutdown();
255
+ }
256
+ })();
257
+
258
+ try {
259
+ await this.shutdownPromise;
260
+ } finally {
261
+ this.shutdownPromise = null;
262
+ }
263
+ }
264
+
231
265
  getServer(): VectorServer<TTypes> | null {
232
266
  return this.server;
233
267
  }
package/src/index.ts CHANGED
@@ -1,8 +1,10 @@
1
- // Public exports for route definitions only
1
+ // Public exports for route definitions and Vector startup API
2
2
  import { route } from './http';
3
+ import { startVector } from './start-vector';
3
4
 
4
5
  // Export route function for defining routes
5
6
  export { route };
7
+ export { startVector };
6
8
 
7
9
  // Export utilities for route handlers
8
10
  export { APIError, createResponse } from './http';
@@ -10,5 +12,4 @@ export { APIError, createResponse } from './http';
10
12
  // Export types for TypeScript users
11
13
  export * from './types';
12
14
 
13
- // Note: Vector framework is now config-driven and runs via CLI
14
- // Usage: Create vector.config.ts and run 'vector dev' or 'vector start'
15
+ // Note: Vector is config-driven and can run via CLI or programmatic startup API
@@ -0,0 +1,11 @@
1
+ {
2
+ "name": "",
3
+ "short_name": "",
4
+ "icons": [
5
+ { "src": "/_vector/openapi/favicon/android-chrome-192x192.png", "sizes": "192x192", "type": "image/png" },
6
+ { "src": "/_vector/openapi/favicon/android-chrome-512x512.png", "sizes": "512x512", "type": "image/png" }
7
+ ],
8
+ "theme_color": "#00a1ff",
9
+ "background_color": "#ffffff",
10
+ "display": "standalone"
11
+ }
@@ -0,0 +1,12 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 15.000000000000002 2.8668130259787783" preserveAspectRatio="xMidYMid meet">
3
+ <defs>
4
+ <linearGradient id="SvgjsLinearGradient2002">
5
+ <stop stop-color="#00ff8f" offset="0"/>
6
+ <stop stop-color="#00a1ff" offset="1"/>
7
+ </linearGradient>
8
+ </defs>
9
+ <g transform="matrix(0.09366995975118918,0,0,0.09366995975118918,-0.3622393421406193,-0.9696304427369192)" fill="url(#SvgjsLinearGradient2002)">
10
+ <path d="M25.664063 26.71875 l-7.5 13.359375 l-9.433594 0 l-1.386719 -13.359375 l-1.796875 0 l-1.679687 -15.957031 l10.214844 0 l1.191406 14.082031 c2.636719 -4.628906 5.019531 -9.472656 7.636719 -14.082031 l9.960938 0 l-8.964844 15.957031 l1.757813 0 z M31.503906 27.34375 l-1.992187 0 l3.828125 -16.484375 l20.917969 0 l-1.679687 7.402344 l-11.152344 0 l-0.585937 2.5 l10.253906 0 l-1.503906 6.582031 l1.992188 0 l-0.175781 0.78125 l-10.273437 0 l-1.035156 4.570313 l11.113281 0 l-1.679687 7.382813 l-20.957031 0 z M62.363281 27.265625 l2.226563 0 c-0.351562 4.707031 2.851563 8.242188 11.875 2.441406 l2.929688 0 l-1.835937 7.96875 c-9.902344 5.703125 -24.589844 3.984375 -22.441406 -10.410156 l-2.207031 0 c0.117188 -0.527344 0.234375 -1.152344 0.390625 -1.835937 c1.992188 -8.730469 8.652344 -15.078125 17.929688 -15.078125 c5.117188 0 8.066406 1.464844 9.824219 2.675781 l-1.894531 8.164063 l-3.144531 0 c-7.070312 -7.617187 -13.183594 0.15625 -13.652344 6.074219 z M99.101563 26.71875 l-2.96875 13.359375 l-9.902344 0 l3.046875 -13.359375 l-1.777344 0 l1.972656 -8.554687 l-7.8125 0 l1.71875 -7.402344 l25.351563 0 l-1.660156 7.402344 l-7.792969 0 l-1.914062 8.554688 l1.738281 0 z M134.003907 25.683594 c-0.15625 0.625 -0.332031 1.25 -0.566406 1.894531 l2.128906 0 c-2.480469 7.753906 -8.789062 13.378906 -17.441406 13.378906 c-8.925781 0 -12.402344 -5.625 -11.269531 -13.378906 l-2.070312 0 c0.078125 -0.644531 0.175781 -1.269531 0.3125 -1.894531 c2.070313 -8.925781 8.535156 -15.3125 17.929688 -15.3125 c9.199219 0 12.988281 6.445313 10.976563 15.3125 z M125.429688 27.578125 l-2.109375 0 c0.195313 -0.644531 0.390625 -1.269531 0.527344 -1.933594 c2.480469 -10.722656 -6.171875 -10.410156 -8.59375 0.039063 c-0.136719 0.683594 -0.234375 1.308594 -0.332031 1.894531 l2.109375 0 c-1.015625 8.164063 5.644531 7.988281 8.398438 0 z M138.457031 26.71875 l-1.777344 0 l3.632813 -15.957031 c3.398438 0 9.53125 -0.332031 14.375 0.3125 c4.277344 0.625 8.417969 2.207031 7.695313 7.34375 c-0.410156 3.105469 -2.792969 6.289063 -5.371094 8.300781 l1.757813 0 c-0.46875 0.351563 -0.9375 0.722656 -1.445312 0.976563 l6.679688 12.382813 l-11.71875 0 l-4.882812 -9.550781 l-2.167969 9.550781 l-9.84375 0 z M148.535156 17.988281 l-1.191406 5.039063 c4.707031 0 7.34375 -5.039062 1.191406 -5.039062 z"/>
11
+ </g>
12
+ </svg>
@@ -0,0 +1,6 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 15.000000000000002 2.8668130259787783" preserveAspectRatio="xMidYMid meet">
3
+ <g transform="matrix(0.09366995975118918,0,0,0.09366995975118918,-0.3622393421406193,-0.9696304427369192)" fill="#111">
4
+ <path d="M25.664063 26.71875 l-7.5 13.359375 l-9.433594 0 l-1.386719 -13.359375 l-1.796875 0 l-1.679687 -15.957031 l10.214844 0 l1.191406 14.082031 c2.636719 -4.628906 5.019531 -9.472656 7.636719 -14.082031 l9.960938 0 l-8.964844 15.957031 l1.757813 0 z M31.503906 27.34375 l-1.992187 0 l3.828125 -16.484375 l20.917969 0 l-1.679687 7.402344 l-11.152344 0 l-0.585937 2.5 l10.253906 0 l-1.503906 6.582031 l1.992188 0 l-0.175781 0.78125 l-10.273437 0 l-1.035156 4.570313 l11.113281 0 l-1.679687 7.382813 l-20.957031 0 z M62.363281 27.265625 l2.226563 0 c-0.351562 4.707031 2.851563 8.242188 11.875 2.441406 l2.929688 0 l-1.835937 7.96875 c-9.902344 5.703125 -24.589844 3.984375 -22.441406 -10.410156 l-2.207031 0 c0.117188 -0.527344 0.234375 -1.152344 0.390625 -1.835937 c1.992188 -8.730469 8.652344 -15.078125 17.929688 -15.078125 c5.117188 0 8.066406 1.464844 9.824219 2.675781 l-1.894531 8.164063 l-3.144531 0 c-7.070312 -7.617187 -13.183594 0.15625 -13.652344 6.074219 z M99.101563 26.71875 l-2.96875 13.359375 l-9.902344 0 l3.046875 -13.359375 l-1.777344 0 l1.972656 -8.554687 l-7.8125 0 l1.71875 -7.402344 l25.351563 0 l-1.660156 7.402344 l-7.792969 0 l-1.914062 8.554688 l1.738281 0 z M134.003907 25.683594 c-0.15625 0.625 -0.332031 1.25 -0.566406 1.894531 l2.128906 0 c-2.480469 7.753906 -8.789062 13.378906 -17.441406 13.378906 c-8.925781 0 -12.402344 -5.625 -11.269531 -13.378906 l-2.070312 0 c0.078125 -0.644531 0.175781 -1.269531 0.3125 -1.894531 c2.070313 -8.925781 8.535156 -15.3125 17.929688 -15.3125 c9.199219 0 12.988281 6.445313 10.976563 15.3125 z M125.429688 27.578125 l-2.109375 0 c0.195313 -0.644531 0.390625 -1.269531 0.527344 -1.933594 c2.480469 -10.722656 -6.171875 -10.410156 -8.59375 0.039063 c-0.136719 0.683594 -0.234375 1.308594 -0.332031 1.894531 l2.109375 0 c-1.015625 8.164063 5.644531 7.988281 8.398438 0 z M138.457031 26.71875 l-1.777344 0 l3.632813 -15.957031 c3.398438 0 9.53125 -0.332031 14.375 0.3125 c4.277344 0.625 8.417969 2.207031 7.695313 7.34375 c-0.410156 3.105469 -2.792969 6.289063 -5.371094 8.300781 l1.757813 0 c-0.46875 0.351563 -0.9375 0.722656 -1.445312 0.976563 l6.679688 12.382813 l-11.71875 0 l-4.882812 -9.550781 l-2.167969 9.550781 l-9.84375 0 z M148.535156 17.988281 l-1.191406 5.039063 c4.707031 0 7.34375 -5.039062 1.191406 -5.039062 z"/>
5
+ </g>
6
+ </svg>
Binary file
@@ -0,0 +1,6 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 15.000000000000002 2.8668130259787783" preserveAspectRatio="xMidYMid meet">
3
+ <g transform="matrix(0.09366995975118918,0,0,0.09366995975118918,-0.3622393421406193,-0.9696304427369192)" fill="#fff">
4
+ <path d="M25.664063 26.71875 l-7.5 13.359375 l-9.433594 0 l-1.386719 -13.359375 l-1.796875 0 l-1.679687 -15.957031 l10.214844 0 l1.191406 14.082031 c2.636719 -4.628906 5.019531 -9.472656 7.636719 -14.082031 l9.960938 0 l-8.964844 15.957031 l1.757813 0 z M31.503906 27.34375 l-1.992187 0 l3.828125 -16.484375 l20.917969 0 l-1.679687 7.402344 l-11.152344 0 l-0.585937 2.5 l10.253906 0 l-1.503906 6.582031 l1.992188 0 l-0.175781 0.78125 l-10.273437 0 l-1.035156 4.570313 l11.113281 0 l-1.679687 7.382813 l-20.957031 0 z M62.363281 27.265625 l2.226563 0 c-0.351562 4.707031 2.851563 8.242188 11.875 2.441406 l2.929688 0 l-1.835937 7.96875 c-9.902344 5.703125 -24.589844 3.984375 -22.441406 -10.410156 l-2.207031 0 c0.117188 -0.527344 0.234375 -1.152344 0.390625 -1.835937 c1.992188 -8.730469 8.652344 -15.078125 17.929688 -15.078125 c5.117188 0 8.066406 1.464844 9.824219 2.675781 l-1.894531 8.164063 l-3.144531 0 c-7.070312 -7.617187 -13.183594 0.15625 -13.652344 6.074219 z M99.101563 26.71875 l-2.96875 13.359375 l-9.902344 0 l3.046875 -13.359375 l-1.777344 0 l1.972656 -8.554687 l-7.8125 0 l1.71875 -7.402344 l25.351563 0 l-1.660156 7.402344 l-7.792969 0 l-1.914062 8.554688 l1.738281 0 z M134.003907 25.683594 c-0.15625 0.625 -0.332031 1.25 -0.566406 1.894531 l2.128906 0 c-2.480469 7.753906 -8.789062 13.378906 -17.441406 13.378906 c-8.925781 0 -12.402344 -5.625 -11.269531 -13.378906 l-2.070312 0 c0.078125 -0.644531 0.175781 -1.269531 0.3125 -1.894531 c2.070313 -8.925781 8.535156 -15.3125 17.929688 -15.3125 c9.199219 0 12.988281 6.445313 10.976563 15.3125 z M125.429688 27.578125 l-2.109375 0 c0.195313 -0.644531 0.390625 -1.269531 0.527344 -1.933594 c2.480469 -10.722656 -6.171875 -10.410156 -8.59375 0.039063 c-0.136719 0.683594 -0.234375 1.308594 -0.332031 1.894531 l2.109375 0 c-1.015625 8.164063 5.644531 7.988281 8.398438 0 z M138.457031 26.71875 l-1.777344 0 l3.632813 -15.957031 c3.398438 0 9.53125 -0.332031 14.375 0.3125 c4.277344 0.625 8.417969 2.207031 7.695313 7.34375 c-0.410156 3.105469 -2.792969 6.289063 -5.371094 8.300781 l1.757813 0 c-0.46875 0.351563 -0.9375 0.722656 -1.445312 0.976563 l6.679688 12.382813 l-11.71875 0 l-4.882812 -9.550781 l-2.167969 9.550781 l-9.84375 0 z M148.535156 17.988281 l-1.191406 5.039063 c4.707031 0 7.34375 -5.039062 1.191406 -5.039062 z"/>
5
+ </g>
6
+ </svg>