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
@@ -131,18 +131,25 @@ function convertInputSchema(
131
131
  warnings: string[]
132
132
  ): JsonSchema | null {
133
133
  if (!isJSONSchemaCapable(inputSchema)) {
134
- return null;
134
+ const fallback = buildFallbackJSONSchema(inputSchema);
135
+ return isEmptyObjectSchema(fallback) ? null : fallback;
135
136
  }
136
137
 
137
138
  try {
138
139
  return inputSchema['~standard'].jsonSchema.input({ target });
139
140
  } catch (error) {
141
+ const alternate = tryAlternateTargetConversion(inputSchema, 'input', target, error, routePath, undefined, warnings);
142
+ if (alternate) {
143
+ return alternate;
144
+ }
145
+
140
146
  warnings.push(
141
147
  `[OpenAPI] Failed input schema conversion for ${routePath}: ${
142
148
  error instanceof Error ? error.message : String(error)
143
- }`
149
+ }. Falling back to a permissive JSON Schema.`
144
150
  );
145
- return null;
151
+ const fallback = buildFallbackJSONSchema(inputSchema);
152
+ return isEmptyObjectSchema(fallback) ? null : fallback;
146
153
  }
147
154
  }
148
155
 
@@ -154,18 +161,32 @@ function convertOutputSchema(
154
161
  warnings: string[]
155
162
  ): JsonSchema | null {
156
163
  if (!isJSONSchemaCapable(outputSchema)) {
157
- return null;
164
+ const fallback = buildFallbackJSONSchema(outputSchema);
165
+ return isEmptyObjectSchema(fallback) ? null : fallback;
158
166
  }
159
167
 
160
168
  try {
161
169
  return outputSchema['~standard'].jsonSchema.output({ target });
162
170
  } catch (error) {
171
+ const alternate = tryAlternateTargetConversion(
172
+ outputSchema,
173
+ 'output',
174
+ target,
175
+ error,
176
+ routePath,
177
+ statusCode,
178
+ warnings
179
+ );
180
+ if (alternate) {
181
+ return alternate;
182
+ }
183
+
163
184
  warnings.push(
164
185
  `[OpenAPI] Failed output schema conversion for ${routePath} (${statusCode}): ${
165
186
  error instanceof Error ? error.message : String(error)
166
- }`
187
+ }. Falling back to a permissive JSON Schema.`
167
188
  );
168
- return null;
189
+ return buildFallbackJSONSchema(outputSchema);
169
190
  }
170
191
  }
171
192
 
@@ -173,6 +194,320 @@ function isRecord(value: unknown): value is Record<string, unknown> {
173
194
  return !!value && typeof value === 'object' && !Array.isArray(value);
174
195
  }
175
196
 
197
+ function isEmptyObjectSchema(value: unknown): value is Record<string, never> {
198
+ return isRecord(value) && Object.keys(value).length === 0;
199
+ }
200
+
201
+ function tryAlternateTargetConversion(
202
+ schema: unknown,
203
+ kind: 'input' | 'output',
204
+ target: string,
205
+ originalError: unknown,
206
+ routePath: string,
207
+ statusCode: string | undefined,
208
+ warnings: string[]
209
+ ): JsonSchema | null {
210
+ if (!isJSONSchemaCapable(schema)) {
211
+ return null;
212
+ }
213
+
214
+ const message = originalError instanceof Error ? originalError.message : String(originalError);
215
+ const unsupportedOpenAPITarget =
216
+ target === 'openapi-3.0' &&
217
+ message.includes("target 'openapi-3.0' is not supported") &&
218
+ message.includes('draft-2020-12') &&
219
+ message.includes('draft-07');
220
+
221
+ if (!unsupportedOpenAPITarget) {
222
+ return null;
223
+ }
224
+
225
+ try {
226
+ const converted = schema['~standard'].jsonSchema[kind]({ target: 'draft-07' });
227
+ warnings.push(
228
+ kind === 'input'
229
+ ? `[OpenAPI] ${routePath} converter does not support openapi-3.0 target; using draft-07 conversion output.`
230
+ : `[OpenAPI] ${routePath} (${statusCode}) converter does not support openapi-3.0 target; using draft-07 conversion output.`
231
+ );
232
+ return converted;
233
+ } catch {
234
+ return null;
235
+ }
236
+ }
237
+
238
+ // Best-effort extraction of internal schema definition metadata from common
239
+ // standards-compatible validators. If unavailable, callers should fall back to {}.
240
+ function getValidatorSchemaDef(schema: unknown): Record<string, unknown> | null {
241
+ if (!schema || typeof schema !== 'object') return null;
242
+ const value = schema as Record<string, any>;
243
+ if (isRecord(value._def)) return value._def as Record<string, unknown>;
244
+ if (isRecord(value._zod) && isRecord((value._zod as Record<string, any>).def)) {
245
+ return (value._zod as Record<string, any>).def as Record<string, unknown>;
246
+ }
247
+ if (value.kind === 'schema' && typeof value.type === 'string') {
248
+ return value as Record<string, unknown>;
249
+ }
250
+ return null;
251
+ }
252
+
253
+ function getSchemaKind(def: Record<string, unknown> | null): string | null {
254
+ if (!def) return null;
255
+ const typeName = def.typeName;
256
+ if (typeof typeName === 'string') return typeName;
257
+ const type = def.type;
258
+ if (typeof type === 'string') return type;
259
+ return null;
260
+ }
261
+
262
+ function pickSchemaChild(def: Record<string, unknown>): unknown {
263
+ const candidates = ['innerType', 'schema', 'type', 'out', 'in', 'left', 'right', 'wrapped', 'element'];
264
+ for (const key of candidates) {
265
+ if (key in def) return (def as Record<string, unknown>)[key];
266
+ }
267
+ return undefined;
268
+ }
269
+
270
+ function pickSchemaObjectCandidate(def: Record<string, unknown>, keys: string[]): unknown {
271
+ for (const key of keys) {
272
+ const value = (def as Record<string, unknown>)[key];
273
+ if (value && typeof value === 'object' && !Array.isArray(value)) {
274
+ return value;
275
+ }
276
+ }
277
+ return undefined;
278
+ }
279
+
280
+ function isOptionalWrapperKind(kind: string | null): boolean {
281
+ if (!kind) return false;
282
+ const lower = kind.toLowerCase();
283
+ return lower.includes('optional') || lower.includes('default') || lower.includes('catch');
284
+ }
285
+
286
+ function unwrapOptionalForRequired(schema: unknown): { schema: unknown; optional: boolean } {
287
+ let current = schema;
288
+ let optional = false;
289
+ let guard = 0;
290
+ while (guard < 8) {
291
+ guard += 1;
292
+ const def = getValidatorSchemaDef(current);
293
+ const kind = getSchemaKind(def);
294
+ if (!def || !isOptionalWrapperKind(kind)) break;
295
+ optional = true;
296
+ const inner = pickSchemaChild(def);
297
+ if (!inner) break;
298
+ current = inner;
299
+ }
300
+ return { schema: current, optional };
301
+ }
302
+
303
+ function getObjectShape(def: Record<string, unknown>): Record<string, unknown> {
304
+ const entries = (def as Record<string, any>).entries;
305
+ if (isRecord(entries)) {
306
+ return entries as Record<string, unknown>;
307
+ }
308
+
309
+ const rawShape = (def as Record<string, any>).shape;
310
+ if (typeof rawShape === 'function') {
311
+ try {
312
+ const resolved = rawShape();
313
+ return isRecord(resolved) ? (resolved as Record<string, unknown>) : {};
314
+ } catch {
315
+ return {};
316
+ }
317
+ }
318
+ return isRecord(rawShape) ? (rawShape as Record<string, unknown>) : {};
319
+ }
320
+
321
+ function extractEnumValues(def: Record<string, unknown>): unknown[] {
322
+ const values = (def as Record<string, any>).values;
323
+ if (Array.isArray(values)) return values;
324
+ if (values && typeof values === 'object') return Object.values(values as Record<string, unknown>);
325
+
326
+ const entries = (def as Record<string, any>).entries;
327
+ if (entries && typeof entries === 'object') return Object.values(entries as Record<string, unknown>);
328
+
329
+ const enumObject = (def as Record<string, any>).enum;
330
+ if (enumObject && typeof enumObject === 'object') return Object.values(enumObject as Record<string, unknown>);
331
+
332
+ const options = (def as Record<string, any>).options;
333
+ if (Array.isArray(options)) {
334
+ return options
335
+ .map((item) => {
336
+ if (item && typeof item === 'object' && 'unit' in (item as Record<string, unknown>)) {
337
+ return (item as Record<string, unknown>).unit;
338
+ }
339
+ return item;
340
+ })
341
+ .filter((item) => item !== undefined);
342
+ }
343
+
344
+ return [];
345
+ }
346
+
347
+ function mapPrimitiveKind(kind: string): JsonSchema | null {
348
+ const lower = kind.toLowerCase();
349
+ if (lower.includes('string')) return { type: 'string' };
350
+ if (lower.includes('number')) return { type: 'number' };
351
+ if (lower.includes('boolean')) return { type: 'boolean' };
352
+ if (lower.includes('bigint')) return { type: 'string' };
353
+ if (lower === 'null' || lower.includes('zodnull')) return { type: 'null' };
354
+ if (lower.includes('any') || lower.includes('unknown') || lower.includes('never')) return {};
355
+ if (lower.includes('date')) return { type: 'string', format: 'date-time' };
356
+ if (lower.includes('custom')) return { type: 'object', additionalProperties: true };
357
+ return null;
358
+ }
359
+
360
+ // Universal fallback schema builder used when converter functions throw.
361
+ // This keeps docs generation resilient and preserves routes in OpenAPI output.
362
+ function buildIntrospectedFallbackJSONSchema(schema: unknown, seen: WeakSet<object> = new WeakSet()): JsonSchema {
363
+ if (!schema || typeof schema !== 'object') return {};
364
+ if (seen.has(schema as object)) return {};
365
+ seen.add(schema as object);
366
+
367
+ const def = getValidatorSchemaDef(schema);
368
+ const kind = getSchemaKind(def);
369
+ if (!def || !kind) return {};
370
+
371
+ const primitive = mapPrimitiveKind(kind);
372
+ if (primitive) return primitive;
373
+
374
+ const lower = kind.toLowerCase();
375
+
376
+ if (lower.includes('object')) {
377
+ const shape = getObjectShape(def);
378
+ const properties: Record<string, unknown> = {};
379
+ const required: string[] = [];
380
+
381
+ for (const [key, child] of Object.entries(shape)) {
382
+ const unwrapped = unwrapOptionalForRequired(child);
383
+ properties[key] = buildIntrospectedFallbackJSONSchema(unwrapped.schema, seen);
384
+ if (!unwrapped.optional) required.push(key);
385
+ }
386
+
387
+ const out: JsonSchema = {
388
+ type: 'object',
389
+ properties,
390
+ additionalProperties: true,
391
+ };
392
+
393
+ if (required.length > 0) {
394
+ out.required = required;
395
+ }
396
+
397
+ return out;
398
+ }
399
+
400
+ if (lower.includes('array')) {
401
+ const itemSchema = pickSchemaObjectCandidate(def, ['element', 'items', 'innerType', 'type']) ?? {};
402
+ return {
403
+ type: 'array',
404
+ items: buildIntrospectedFallbackJSONSchema(itemSchema, seen),
405
+ };
406
+ }
407
+
408
+ if (lower.includes('record')) {
409
+ const valueType = (def as Record<string, any>).valueType ?? (def as Record<string, any>).valueSchema;
410
+ return {
411
+ type: 'object',
412
+ additionalProperties: valueType ? buildIntrospectedFallbackJSONSchema(valueType, seen) : true,
413
+ };
414
+ }
415
+
416
+ if (lower.includes('tuple')) {
417
+ const items = Array.isArray((def as Record<string, any>).items)
418
+ ? ((def as Record<string, any>).items as unknown[])
419
+ : [];
420
+ const prefixItems = items.map((item) => buildIntrospectedFallbackJSONSchema(item, seen));
421
+ return {
422
+ type: 'array',
423
+ prefixItems,
424
+ minItems: prefixItems.length,
425
+ maxItems: prefixItems.length,
426
+ };
427
+ }
428
+
429
+ if (lower.includes('union')) {
430
+ const options =
431
+ ((def as Record<string, any>).options as unknown[]) ?? ((def as Record<string, any>).schemas as unknown[]) ?? [];
432
+ if (!Array.isArray(options) || options.length === 0) return {};
433
+ return {
434
+ anyOf: options.map((option) => buildIntrospectedFallbackJSONSchema(option, seen)),
435
+ };
436
+ }
437
+
438
+ if (lower.includes('intersection')) {
439
+ const left = (def as Record<string, any>).left;
440
+ const right = (def as Record<string, any>).right;
441
+ if (!left || !right) return {};
442
+ return {
443
+ allOf: [buildIntrospectedFallbackJSONSchema(left, seen), buildIntrospectedFallbackJSONSchema(right, seen)],
444
+ };
445
+ }
446
+
447
+ if (lower.includes('enum')) {
448
+ const enumValues = extractEnumValues(def);
449
+ if (enumValues.length > 0) {
450
+ const allString = enumValues.every((v) => typeof v === 'string');
451
+ const allNumber = enumValues.every((v) => typeof v === 'number');
452
+ const allBoolean = enumValues.every((v) => typeof v === 'boolean');
453
+ if (allString) return { type: 'string', enum: enumValues };
454
+ if (allNumber) return { type: 'number', enum: enumValues };
455
+ if (allBoolean) return { type: 'boolean', enum: enumValues };
456
+ return { enum: enumValues };
457
+ }
458
+ return {};
459
+ }
460
+
461
+ if (lower.includes('picklist')) {
462
+ const enumValues = extractEnumValues(def);
463
+ if (enumValues.length > 0) {
464
+ const allString = enumValues.every((v) => typeof v === 'string');
465
+ if (allString) return { type: 'string', enum: enumValues };
466
+ return { enum: enumValues };
467
+ }
468
+ return {};
469
+ }
470
+
471
+ if (lower.includes('literal')) {
472
+ const value = (def as Record<string, any>).value;
473
+ if (value === undefined) return {};
474
+ const valueType = value === null ? 'null' : typeof value;
475
+ if (valueType === 'string' || valueType === 'number' || valueType === 'boolean' || valueType === 'null') {
476
+ return { type: valueType, const: value };
477
+ }
478
+ return { const: value };
479
+ }
480
+
481
+ if (lower.includes('nullable')) {
482
+ const inner = pickSchemaChild(def);
483
+ if (!inner) return {};
484
+ return {
485
+ anyOf: [buildIntrospectedFallbackJSONSchema(inner, seen), { type: 'null' }],
486
+ };
487
+ }
488
+
489
+ if (lower.includes('lazy')) {
490
+ const getter = (def as Record<string, any>).getter;
491
+ if (typeof getter !== 'function') return {};
492
+ try {
493
+ return buildIntrospectedFallbackJSONSchema(getter(), seen);
494
+ } catch {
495
+ return {};
496
+ }
497
+ }
498
+
499
+ const child = pickSchemaChild(def);
500
+ if (child) return buildIntrospectedFallbackJSONSchema(child, seen);
501
+
502
+ return {};
503
+ }
504
+
505
+ function buildFallbackJSONSchema(schema: unknown): JsonSchema {
506
+ const def = getValidatorSchemaDef(schema);
507
+ if (!def) return {};
508
+ return buildIntrospectedFallbackJSONSchema(schema);
509
+ }
510
+
176
511
  function addStructuredInputToOperation(operation: Record<string, any>, inputJSONSchema: JsonSchema): void {
177
512
  if (!isRecord(inputJSONSchema)) return;
178
513
  if (inputJSONSchema.type !== 'object' || !isRecord(inputJSONSchema.properties)) {
@@ -0,0 +1,50 @@
1
+ import { ConfigLoader } from './core/config-loader';
2
+ import { DEFAULT_CONFIG } from './constants';
3
+ import { getVectorInstance } from './core/vector';
4
+ import type { DefaultVectorTypes, StartVectorOptions, StartedVectorApp, VectorTypes } from './types';
5
+
6
+ export async function startVector<TTypes extends VectorTypes = DefaultVectorTypes>(
7
+ options: StartVectorOptions<TTypes> = {}
8
+ ): Promise<StartedVectorApp<TTypes>> {
9
+ const configLoader = new ConfigLoader<TTypes>(options.configPath);
10
+ const loadedConfig = await configLoader.load();
11
+ const configSource = configLoader.getConfigSource();
12
+
13
+ let config = { ...loadedConfig };
14
+ if (options.mutateConfig) {
15
+ config = await options.mutateConfig(config, { configSource });
16
+ }
17
+
18
+ if (options.config) {
19
+ config = { ...config, ...options.config };
20
+ }
21
+
22
+ if (options.autoDiscover !== undefined) {
23
+ config.autoDiscover = options.autoDiscover;
24
+ }
25
+
26
+ const vector = getVectorInstance<TTypes>();
27
+ const resolvedProtectedHandler =
28
+ options.protectedHandler !== undefined ? options.protectedHandler : await configLoader.loadAuthHandler();
29
+ const resolvedCacheHandler =
30
+ options.cacheHandler !== undefined ? options.cacheHandler : await configLoader.loadCacheHandler();
31
+
32
+ vector.setProtectedHandler(resolvedProtectedHandler ?? null);
33
+ vector.setCacheHandler(resolvedCacheHandler ?? null);
34
+
35
+ const server = await vector.startServer(config);
36
+ const effectiveConfig = {
37
+ ...config,
38
+ port: server.port ?? config.port ?? DEFAULT_CONFIG.PORT,
39
+ hostname: server.hostname || config.hostname || DEFAULT_CONFIG.HOSTNAME,
40
+ reusePort: config.reusePort !== false,
41
+ idleTimeout: config.idleTimeout ?? 60,
42
+ };
43
+
44
+ return {
45
+ server,
46
+ config: effectiveConfig,
47
+ stop: () => vector.stop(),
48
+ shutdown: () => vector.shutdown(),
49
+ };
50
+ }
@@ -1,4 +1,5 @@
1
1
  import type { StandardJSONSchemaV1, StandardSchemaV1, StandardTypedV1 } from './standard-schema';
2
+ import type { Server } from 'bun';
2
3
 
3
4
  // Default AuthUser type - users can override this with their own type
4
5
  export interface DefaultAuthUser {
@@ -150,6 +151,31 @@ export interface VectorConfig<TTypes extends VectorTypes = DefaultVectorTypes> {
150
151
  idleTimeout?: number;
151
152
  defaults?: VectorDefaults;
152
153
  openapi?: OpenAPIOptions | boolean;
154
+ startup?: StartupHandler;
155
+ shutdown?: ShutdownHandler;
156
+ }
157
+
158
+ export interface StartVectorContext {
159
+ configSource: 'user' | 'default';
160
+ }
161
+
162
+ export interface StartVectorOptions<TTypes extends VectorTypes = DefaultVectorTypes> {
163
+ configPath?: string;
164
+ config?: Partial<VectorConfig<TTypes>>;
165
+ autoDiscover?: boolean;
166
+ protectedHandler?: ProtectedHandler<TTypes> | null;
167
+ cacheHandler?: CacheHandler | null;
168
+ mutateConfig?: (
169
+ config: VectorConfig<TTypes>,
170
+ context: StartVectorContext
171
+ ) => VectorConfig<TTypes> | Promise<VectorConfig<TTypes>>;
172
+ }
173
+
174
+ export interface StartedVectorApp<TTypes extends VectorTypes = DefaultVectorTypes> {
175
+ server: Server;
176
+ config: VectorConfig<TTypes>;
177
+ stop: () => void;
178
+ shutdown: () => Promise<void>;
153
179
  }
154
180
 
155
181
  // New config-driven schema - flat structure
@@ -178,6 +204,10 @@ export interface VectorConfigSchema<TTypes extends VectorTypes = DefaultVectorTy
178
204
  // OpenAPI/docs configuration
179
205
  openapi?: OpenAPIOptions | boolean;
180
206
 
207
+ // Startup lifecycle
208
+ startup?: StartupHandler;
209
+ shutdown?: ShutdownHandler;
210
+
181
211
  // Custom types for TypeScript
182
212
  types?: VectorTypes;
183
213
  }
@@ -194,6 +224,7 @@ export interface CorsOptions {
194
224
  export interface OpenAPIDocsOptions {
195
225
  enabled?: boolean;
196
226
  path?: string;
227
+ exposePaths?: string[];
197
228
  }
198
229
 
199
230
  export interface OpenAPIInfoOptions {
@@ -220,6 +251,9 @@ export type AfterMiddlewareHandler<TTypes extends VectorTypes = DefaultVectorTyp
220
251
  ) => Promise<Response> | Response;
221
252
  export type MiddlewareHandler = BeforeMiddlewareHandler | AfterMiddlewareHandler;
222
253
 
254
+ export type StartupHandler = () => Promise<void> | void;
255
+ export type ShutdownHandler = () => Promise<void> | void;
256
+
223
257
  export type RouteHandler<TTypes extends VectorTypes = DefaultVectorTypes, TValidatedInput = undefined> = (
224
258
  request: VectorRequest<TTypes, TValidatedInput>
225
259
  ) => Promise<any> | any;
@@ -17,7 +17,7 @@ export class Logger {
17
17
  constructor(config: Partial<LoggerConfig> = {}) {
18
18
  this.config = {
19
19
  level: config.level ?? LogLevel.INFO,
20
- prefix: config.prefix ?? '[Vector]',
20
+ prefix: config.prefix ?? '[vector]',
21
21
  timestamp: config.timestamp ?? true,
22
22
  };
23
23
  }
@@ -13,6 +13,8 @@ export function validateConfig(config: VectorConfig): VectorConfig {
13
13
  cors: config.cors ? validateCorsOptions(config.cors) : undefined,
14
14
  before: config.before || [],
15
15
  finally: config.finally || [],
16
+ startup: config.startup,
17
+ shutdown: config.shutdown,
16
18
  };
17
19
 
18
20
  return validatedConfig;