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.
- package/README.md +19 -0
- 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 +3 -0
- 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 +37 -43
- package/dist/cli/index.js.map +1 -1
- package/dist/cli.js +967 -222
- package/dist/core/config-loader.d.ts.map +1 -1
- package/dist/core/config-loader.js +5 -2
- package/dist/core/config-loader.js.map +1 -1
- package/dist/core/server.d.ts +4 -0
- package/dist/core/server.d.ts.map +1 -1
- package/dist/core/server.js +240 -9
- package/dist/core/server.js.map +1 -1
- package/dist/core/vector.d.ts +4 -2
- package/dist/core/vector.d.ts.map +1 -1
- package/dist/core/vector.js +32 -2
- package/dist/core/vector.js.map +1 -1
- package/dist/errors/index.cjs +2 -0
- package/dist/index.cjs +1434 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +12 -1327
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +153 -46
- package/dist/openapi/docs-ui.d.ts +1 -1
- package/dist/openapi/docs-ui.d.ts.map +1 -1
- package/dist/openapi/docs-ui.js +147 -35
- package/dist/openapi/docs-ui.js.map +1 -1
- package/dist/openapi/generator.d.ts.map +1 -1
- package/dist/openapi/generator.js +318 -6
- package/dist/openapi/generator.js.map +1 -1
- 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 +25 -0
- package/dist/types/index.d.ts.map +1 -1
- package/dist/utils/logger.js +1 -1
- package/dist/utils/validation.d.ts.map +1 -1
- package/dist/utils/validation.js +2 -0
- package/dist/utils/validation.js.map +1 -1
- package/package.json +10 -14
- package/src/auth/protected.ts +4 -0
- package/src/cache/manager.ts +4 -0
- package/src/cli/graceful-shutdown.ts +60 -0
- package/src/cli/index.ts +42 -49
- package/src/core/config-loader.ts +5 -2
- package/src/core/server.ts +304 -9
- package/src/core/vector.ts +38 -4
- package/src/index.ts +4 -3
- 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/docs-ui.ts +153 -35
- package/src/openapi/generator.ts +341 -6
- package/src/start-vector.ts +50 -0
- package/src/types/index.ts +34 -0
- package/src/utils/logger.ts +1 -1
- package/src/utils/validation.ts +2 -0
package/src/openapi/generator.ts
CHANGED
|
@@ -131,18 +131,25 @@ function convertInputSchema(
|
|
|
131
131
|
warnings: string[]
|
|
132
132
|
): JsonSchema | null {
|
|
133
133
|
if (!isJSONSchemaCapable(inputSchema)) {
|
|
134
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
+
}
|
package/src/types/index.ts
CHANGED
|
@@ -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;
|
package/src/utils/logger.ts
CHANGED
|
@@ -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 ?? '[
|
|
20
|
+
prefix: config.prefix ?? '[vector]',
|
|
21
21
|
timestamp: config.timestamp ?? true,
|
|
22
22
|
};
|
|
23
23
|
}
|
package/src/utils/validation.ts
CHANGED
|
@@ -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;
|