vector-framework 1.1.1 → 1.2.0
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 +87 -635
- package/dist/auth/protected.d.ts.map +1 -1
- package/dist/auth/protected.js.map +1 -1
- package/dist/cache/manager.d.ts.map +1 -1
- package/dist/cache/manager.js +2 -7
- package/dist/cache/manager.js.map +1 -1
- package/dist/cli/index.js +17 -62
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/option-resolution.d.ts +4 -0
- package/dist/cli/option-resolution.d.ts.map +1 -0
- package/dist/cli/option-resolution.js +28 -0
- package/dist/cli/option-resolution.js.map +1 -0
- package/dist/cli.js +2721 -617
- package/dist/constants/index.d.ts +3 -0
- package/dist/constants/index.d.ts.map +1 -1
- package/dist/constants/index.js +6 -0
- package/dist/constants/index.js.map +1 -1
- package/dist/core/config-loader.d.ts.map +1 -1
- package/dist/core/config-loader.js +2 -0
- package/dist/core/config-loader.js.map +1 -1
- package/dist/core/router.d.ts +41 -17
- package/dist/core/router.d.ts.map +1 -1
- package/dist/core/router.js +432 -153
- package/dist/core/router.js.map +1 -1
- package/dist/core/server.d.ts +14 -1
- package/dist/core/server.d.ts.map +1 -1
- package/dist/core/server.js +250 -30
- package/dist/core/server.js.map +1 -1
- package/dist/core/vector.d.ts +4 -3
- package/dist/core/vector.d.ts.map +1 -1
- package/dist/core/vector.js +21 -12
- package/dist/core/vector.js.map +1 -1
- package/dist/dev/route-generator.d.ts.map +1 -1
- package/dist/dev/route-generator.js.map +1 -1
- package/dist/dev/route-scanner.d.ts.map +1 -1
- package/dist/dev/route-scanner.js +1 -5
- package/dist/dev/route-scanner.js.map +1 -1
- package/dist/http.d.ts +14 -14
- package/dist/http.d.ts.map +1 -1
- package/dist/http.js +34 -41
- package/dist/http.js.map +1 -1
- package/dist/index.js +1314 -8
- package/dist/index.mjs +1314 -8
- package/dist/middleware/manager.d.ts.map +1 -1
- package/dist/middleware/manager.js +4 -0
- package/dist/middleware/manager.js.map +1 -1
- package/dist/openapi/docs-ui.d.ts +2 -0
- package/dist/openapi/docs-ui.d.ts.map +1 -0
- package/dist/openapi/docs-ui.js +1313 -0
- package/dist/openapi/docs-ui.js.map +1 -0
- package/dist/openapi/generator.d.ts +12 -0
- package/dist/openapi/generator.d.ts.map +1 -0
- package/dist/openapi/generator.js +273 -0
- package/dist/openapi/generator.js.map +1 -0
- package/dist/types/index.d.ts +70 -11
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/standard-schema.d.ts +118 -0
- package/dist/types/standard-schema.d.ts.map +1 -0
- package/dist/types/standard-schema.js +2 -0
- package/dist/types/standard-schema.js.map +1 -0
- package/dist/utils/cors.d.ts +13 -0
- package/dist/utils/cors.d.ts.map +1 -0
- package/dist/utils/cors.js +89 -0
- package/dist/utils/cors.js.map +1 -0
- package/dist/utils/path.d.ts +6 -0
- package/dist/utils/path.d.ts.map +1 -1
- package/dist/utils/path.js +5 -0
- package/dist/utils/path.js.map +1 -1
- package/dist/utils/schema-validation.d.ts +31 -0
- package/dist/utils/schema-validation.d.ts.map +1 -0
- package/dist/utils/schema-validation.js +77 -0
- package/dist/utils/schema-validation.js.map +1 -0
- package/dist/utils/validation.d.ts.map +1 -1
- package/dist/utils/validation.js +1 -0
- package/dist/utils/validation.js.map +1 -1
- package/package.json +13 -12
- package/src/auth/protected.ts +3 -13
- package/src/cache/manager.ts +4 -18
- package/src/cli/index.ts +19 -75
- package/src/cli/option-resolution.ts +40 -0
- package/src/constants/index.ts +7 -0
- package/src/core/config-loader.ts +3 -3
- package/src/core/router.ts +502 -156
- package/src/core/server.ts +327 -32
- package/src/core/vector.ts +49 -29
- package/src/dev/route-generator.ts +1 -3
- package/src/dev/route-scanner.ts +2 -9
- package/src/http.ts +85 -125
- package/src/middleware/manager.ts +4 -0
- package/src/openapi/assets/tailwindcdn.js +83 -0
- package/src/openapi/docs-ui.ts +1317 -0
- package/src/openapi/generator.ts +359 -0
- package/src/types/index.ts +104 -17
- package/src/types/standard-schema.ts +147 -0
- package/src/utils/cors.ts +101 -0
- package/src/utils/path.ts +6 -0
- package/src/utils/schema-validation.ts +123 -0
- package/src/utils/validation.ts +1 -0
|
@@ -0,0 +1,359 @@
|
|
|
1
|
+
import type { RegisteredRouteDefinition } from '../core/router';
|
|
2
|
+
import type { OpenAPIInfoOptions, RouteSchemaDefinition, StandardJSONSchemaCapable } from '../types';
|
|
3
|
+
|
|
4
|
+
type JsonSchema = Record<string, unknown>;
|
|
5
|
+
|
|
6
|
+
export interface OpenAPIGenerationOptions {
|
|
7
|
+
target: string;
|
|
8
|
+
info?: OpenAPIInfoOptions;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export interface OpenAPIGenerationResult {
|
|
12
|
+
document: Record<string, unknown>;
|
|
13
|
+
warnings: string[];
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function isJSONSchemaCapable(schema: unknown): schema is StandardJSONSchemaCapable {
|
|
17
|
+
const standard = (schema as any)?.['~standard'];
|
|
18
|
+
const converter = standard?.jsonSchema;
|
|
19
|
+
return (
|
|
20
|
+
!!standard &&
|
|
21
|
+
typeof standard === 'object' &&
|
|
22
|
+
standard.version === 1 &&
|
|
23
|
+
!!converter &&
|
|
24
|
+
typeof converter.input === 'function' &&
|
|
25
|
+
typeof converter.output === 'function'
|
|
26
|
+
);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function normalizeRoutePathForOpenAPI(path: string): { openapiPath: string; pathParamNames: string[] } {
|
|
30
|
+
let wildcardCount = 0;
|
|
31
|
+
const pathParamNames: string[] = [];
|
|
32
|
+
|
|
33
|
+
const segments = path.split('/').map((segment) => {
|
|
34
|
+
const greedyParamMatch = /^:([A-Za-z0-9_]+)\+$/.exec(segment);
|
|
35
|
+
if (greedyParamMatch?.[1]) {
|
|
36
|
+
pathParamNames.push(greedyParamMatch[1]);
|
|
37
|
+
return `{${greedyParamMatch[1]}}`;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const paramMatch = /^:([A-Za-z0-9_]+)$/.exec(segment);
|
|
41
|
+
if (paramMatch?.[1]) {
|
|
42
|
+
pathParamNames.push(paramMatch[1]);
|
|
43
|
+
return `{${paramMatch[1]}}`;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
if (segment === '*') {
|
|
47
|
+
wildcardCount += 1;
|
|
48
|
+
const wildcardParamName = wildcardCount === 1 ? 'wildcard' : `wildcard${wildcardCount}`;
|
|
49
|
+
pathParamNames.push(wildcardParamName);
|
|
50
|
+
return `{${wildcardParamName}}`;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
return segment;
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
return {
|
|
57
|
+
openapiPath: segments.join('/'),
|
|
58
|
+
pathParamNames,
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function toOpenAPIPath(path: string): string {
|
|
63
|
+
return normalizeRoutePathForOpenAPI(path).openapiPath;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function createOperationId(method: string, path: string): string {
|
|
67
|
+
const normalized = `${method.toLowerCase()}_${path}`
|
|
68
|
+
.replace(/[:{}]/g, '')
|
|
69
|
+
.replace(/[^A-Za-z0-9_]+/g, '_')
|
|
70
|
+
.replace(/^_+|_+$/g, '');
|
|
71
|
+
return normalized || `${method.toLowerCase()}_operation`;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function inferTagFromPath(path: string): string {
|
|
75
|
+
const segments = path.split('/').filter(Boolean);
|
|
76
|
+
for (const segment of segments) {
|
|
77
|
+
if (!segment.startsWith(':') && segment !== '*') {
|
|
78
|
+
return segment.toLowerCase();
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
return 'default';
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function extractPathParamNames(path: string): string[] {
|
|
85
|
+
return normalizeRoutePathForOpenAPI(path).pathParamNames;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function addMissingPathParameters(operation: Record<string, any>, routePath: string): void {
|
|
89
|
+
const existingPathNames = new Set(
|
|
90
|
+
(operation.parameters || []).filter((p: any) => p.in === 'path').map((p: any) => String(p.name))
|
|
91
|
+
);
|
|
92
|
+
|
|
93
|
+
for (const pathName of extractPathParamNames(routePath)) {
|
|
94
|
+
if (existingPathNames.has(pathName)) continue;
|
|
95
|
+
|
|
96
|
+
(operation.parameters ||= []).push({
|
|
97
|
+
name: pathName,
|
|
98
|
+
in: 'path',
|
|
99
|
+
required: true,
|
|
100
|
+
schema: { type: 'string' },
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function isNoBodyResponseStatus(status: string): boolean {
|
|
106
|
+
const numericStatus = Number(status);
|
|
107
|
+
if (!Number.isInteger(numericStatus)) return false;
|
|
108
|
+
return (
|
|
109
|
+
(numericStatus >= 100 && numericStatus < 200) ||
|
|
110
|
+
numericStatus === 204 ||
|
|
111
|
+
numericStatus === 205 ||
|
|
112
|
+
numericStatus === 304
|
|
113
|
+
);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
function getResponseDescription(status: string): string {
|
|
117
|
+
if (status === '204') return 'No Content';
|
|
118
|
+
if (status === '205') return 'Reset Content';
|
|
119
|
+
if (status === '304') return 'Not Modified';
|
|
120
|
+
const numericStatus = Number(status);
|
|
121
|
+
if (Number.isInteger(numericStatus) && numericStatus >= 100 && numericStatus < 200) {
|
|
122
|
+
return 'Informational';
|
|
123
|
+
}
|
|
124
|
+
return 'OK';
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
function convertInputSchema(
|
|
128
|
+
routePath: string,
|
|
129
|
+
inputSchema: unknown,
|
|
130
|
+
target: string,
|
|
131
|
+
warnings: string[]
|
|
132
|
+
): JsonSchema | null {
|
|
133
|
+
if (!isJSONSchemaCapable(inputSchema)) {
|
|
134
|
+
return null;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
try {
|
|
138
|
+
return inputSchema['~standard'].jsonSchema.input({ target });
|
|
139
|
+
} catch (error) {
|
|
140
|
+
warnings.push(
|
|
141
|
+
`[OpenAPI] Failed input schema conversion for ${routePath}: ${
|
|
142
|
+
error instanceof Error ? error.message : String(error)
|
|
143
|
+
}`
|
|
144
|
+
);
|
|
145
|
+
return null;
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
function convertOutputSchema(
|
|
150
|
+
routePath: string,
|
|
151
|
+
statusCode: string,
|
|
152
|
+
outputSchema: unknown,
|
|
153
|
+
target: string,
|
|
154
|
+
warnings: string[]
|
|
155
|
+
): JsonSchema | null {
|
|
156
|
+
if (!isJSONSchemaCapable(outputSchema)) {
|
|
157
|
+
return null;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
try {
|
|
161
|
+
return outputSchema['~standard'].jsonSchema.output({ target });
|
|
162
|
+
} catch (error) {
|
|
163
|
+
warnings.push(
|
|
164
|
+
`[OpenAPI] Failed output schema conversion for ${routePath} (${statusCode}): ${
|
|
165
|
+
error instanceof Error ? error.message : String(error)
|
|
166
|
+
}`
|
|
167
|
+
);
|
|
168
|
+
return null;
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
173
|
+
return !!value && typeof value === 'object' && !Array.isArray(value);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
function addStructuredInputToOperation(operation: Record<string, any>, inputJSONSchema: JsonSchema): void {
|
|
177
|
+
if (!isRecord(inputJSONSchema)) return;
|
|
178
|
+
if (inputJSONSchema.type !== 'object' || !isRecord(inputJSONSchema.properties)) {
|
|
179
|
+
operation.requestBody = {
|
|
180
|
+
required: true,
|
|
181
|
+
content: {
|
|
182
|
+
'application/json': {
|
|
183
|
+
schema: inputJSONSchema,
|
|
184
|
+
},
|
|
185
|
+
},
|
|
186
|
+
};
|
|
187
|
+
return;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
const rootRequired = new Set<string>(
|
|
191
|
+
Array.isArray(inputJSONSchema.required) ? (inputJSONSchema.required as string[]) : []
|
|
192
|
+
);
|
|
193
|
+
const properties = inputJSONSchema.properties as Record<string, unknown>;
|
|
194
|
+
const parameters: any[] = Array.isArray(operation.parameters) ? operation.parameters : [];
|
|
195
|
+
|
|
196
|
+
const parameterSections: Array<{
|
|
197
|
+
key: string;
|
|
198
|
+
in: 'path' | 'query' | 'header' | 'cookie';
|
|
199
|
+
}> = [
|
|
200
|
+
{ key: 'params', in: 'path' },
|
|
201
|
+
{ key: 'query', in: 'query' },
|
|
202
|
+
{ key: 'headers', in: 'header' },
|
|
203
|
+
{ key: 'cookies', in: 'cookie' },
|
|
204
|
+
];
|
|
205
|
+
|
|
206
|
+
for (const section of parameterSections) {
|
|
207
|
+
const sectionSchema = properties[section.key];
|
|
208
|
+
if (!isRecord(sectionSchema)) continue;
|
|
209
|
+
if (sectionSchema.type !== 'object' || !isRecord(sectionSchema.properties)) continue;
|
|
210
|
+
|
|
211
|
+
const sectionRequired = new Set<string>(
|
|
212
|
+
Array.isArray(sectionSchema.required) ? (sectionSchema.required as string[]) : []
|
|
213
|
+
);
|
|
214
|
+
|
|
215
|
+
for (const [name, schema] of Object.entries(sectionSchema.properties)) {
|
|
216
|
+
parameters.push({
|
|
217
|
+
name,
|
|
218
|
+
in: section.in,
|
|
219
|
+
required: section.in === 'path' ? true : sectionRequired.has(name),
|
|
220
|
+
schema: isRecord(schema) ? schema : {},
|
|
221
|
+
});
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
if (parameters.length > 0) {
|
|
226
|
+
const deduped = new Map<string, any>();
|
|
227
|
+
for (const parameter of parameters) {
|
|
228
|
+
deduped.set(`${parameter.in}:${parameter.name}`, parameter);
|
|
229
|
+
}
|
|
230
|
+
operation.parameters = [...deduped.values()];
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
const bodySchema = properties.body;
|
|
234
|
+
if (bodySchema) {
|
|
235
|
+
operation.requestBody = {
|
|
236
|
+
required: rootRequired.has('body'),
|
|
237
|
+
content: {
|
|
238
|
+
'application/json': {
|
|
239
|
+
schema: isRecord(bodySchema) ? bodySchema : {},
|
|
240
|
+
},
|
|
241
|
+
},
|
|
242
|
+
};
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
function addOutputSchemasToOperation(
|
|
247
|
+
operation: Record<string, any>,
|
|
248
|
+
routePath: string,
|
|
249
|
+
routeSchema: RouteSchemaDefinition,
|
|
250
|
+
target: string,
|
|
251
|
+
warnings: string[]
|
|
252
|
+
): void {
|
|
253
|
+
const output = routeSchema.output;
|
|
254
|
+
|
|
255
|
+
if (!output) {
|
|
256
|
+
operation.responses = {
|
|
257
|
+
200: { description: 'OK' },
|
|
258
|
+
};
|
|
259
|
+
return;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
const responses: Record<string, any> = {};
|
|
263
|
+
|
|
264
|
+
// Single output schema shorthand: schema.output = SomeSchema (defaults to 200)
|
|
265
|
+
if (typeof output === 'object' && output !== null && '~standard' in output) {
|
|
266
|
+
const outputSchema = convertOutputSchema(routePath, '200', output, target, warnings);
|
|
267
|
+
|
|
268
|
+
if (outputSchema) {
|
|
269
|
+
responses['200'] = {
|
|
270
|
+
description: 'OK',
|
|
271
|
+
content: {
|
|
272
|
+
'application/json': {
|
|
273
|
+
schema: outputSchema,
|
|
274
|
+
},
|
|
275
|
+
},
|
|
276
|
+
};
|
|
277
|
+
} else {
|
|
278
|
+
responses['200'] = { description: 'OK' };
|
|
279
|
+
}
|
|
280
|
+
} else {
|
|
281
|
+
for (const [statusCode, schema] of Object.entries(output as Record<string, unknown>)) {
|
|
282
|
+
const status = String(statusCode);
|
|
283
|
+
const outputSchema = convertOutputSchema(routePath, status, schema, target, warnings);
|
|
284
|
+
const description = getResponseDescription(status);
|
|
285
|
+
|
|
286
|
+
if (outputSchema && !isNoBodyResponseStatus(status)) {
|
|
287
|
+
responses[status] = {
|
|
288
|
+
description,
|
|
289
|
+
content: {
|
|
290
|
+
'application/json': {
|
|
291
|
+
schema: outputSchema,
|
|
292
|
+
},
|
|
293
|
+
},
|
|
294
|
+
};
|
|
295
|
+
} else {
|
|
296
|
+
responses[status] = {
|
|
297
|
+
description,
|
|
298
|
+
};
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
if (Object.keys(responses).length === 0) {
|
|
304
|
+
responses['200'] = { description: 'OK' };
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
operation.responses = responses;
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
export function generateOpenAPIDocument(
|
|
311
|
+
routes: RegisteredRouteDefinition[],
|
|
312
|
+
options: OpenAPIGenerationOptions
|
|
313
|
+
): OpenAPIGenerationResult {
|
|
314
|
+
const warnings: string[] = [];
|
|
315
|
+
const paths: Record<string, Record<string, unknown>> = {};
|
|
316
|
+
|
|
317
|
+
for (const route of routes) {
|
|
318
|
+
if (route.options.expose === false) continue;
|
|
319
|
+
if (!route.method || !route.path) continue;
|
|
320
|
+
|
|
321
|
+
const method = route.method.toLowerCase();
|
|
322
|
+
if (method === 'options') continue;
|
|
323
|
+
|
|
324
|
+
const openapiPath = toOpenAPIPath(route.path);
|
|
325
|
+
const operation: Record<string, any> = {
|
|
326
|
+
operationId: createOperationId(method, openapiPath),
|
|
327
|
+
tags: [route.options.schema?.tag || inferTagFromPath(route.path)],
|
|
328
|
+
};
|
|
329
|
+
|
|
330
|
+
const inputJSONSchema = convertInputSchema(route.path, route.options.schema?.input, options.target, warnings);
|
|
331
|
+
|
|
332
|
+
if (inputJSONSchema) {
|
|
333
|
+
addStructuredInputToOperation(operation, inputJSONSchema);
|
|
334
|
+
}
|
|
335
|
+
addMissingPathParameters(operation, route.path);
|
|
336
|
+
|
|
337
|
+
addOutputSchemasToOperation(operation, route.path, route.options.schema || {}, options.target, warnings);
|
|
338
|
+
|
|
339
|
+
paths[openapiPath] ||= {};
|
|
340
|
+
paths[openapiPath][method] = operation;
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
const openapiVersion = options.target === 'openapi-3.0' ? '3.0.3' : '3.1.0';
|
|
344
|
+
|
|
345
|
+
const document = {
|
|
346
|
+
openapi: openapiVersion,
|
|
347
|
+
info: {
|
|
348
|
+
title: options.info?.title || 'Vector API',
|
|
349
|
+
version: options.info?.version || '1.0.0',
|
|
350
|
+
...(options.info?.description ? { description: options.info.description } : {}),
|
|
351
|
+
},
|
|
352
|
+
paths,
|
|
353
|
+
};
|
|
354
|
+
|
|
355
|
+
return {
|
|
356
|
+
document,
|
|
357
|
+
warnings,
|
|
358
|
+
};
|
|
359
|
+
}
|
package/src/types/index.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { StandardJSONSchemaV1, StandardSchemaV1, StandardTypedV1 } from './standard-schema';
|
|
2
2
|
|
|
3
3
|
// Default AuthUser type - users can override this with their own type
|
|
4
4
|
export interface DefaultAuthUser {
|
|
@@ -27,13 +27,9 @@ export interface DefaultVectorTypes extends VectorTypes {
|
|
|
27
27
|
}
|
|
28
28
|
|
|
29
29
|
// Type helpers
|
|
30
|
-
export type GetAuthType<T extends VectorTypes> = T['auth'] extends undefined
|
|
31
|
-
? DefaultAuthUser
|
|
32
|
-
: T['auth'];
|
|
30
|
+
export type GetAuthType<T extends VectorTypes> = T['auth'] extends undefined ? DefaultAuthUser : T['auth'];
|
|
33
31
|
|
|
34
|
-
export type GetContextType<T extends VectorTypes> = T['context'] extends undefined
|
|
35
|
-
? Record<string, any>
|
|
36
|
-
: T['context'];
|
|
32
|
+
export type GetContextType<T extends VectorTypes> = T['context'] extends undefined ? Record<string, any> : T['context'];
|
|
37
33
|
|
|
38
34
|
export type GetCacheType<T extends VectorTypes> = T['cache'] extends undefined ? any : T['cache'];
|
|
39
35
|
|
|
@@ -44,16 +40,39 @@ export type GetMetadataType<T extends VectorTypes> = T['metadata'] extends undef
|
|
|
44
40
|
// Legacy support - keep AuthUser for backward compatibility
|
|
45
41
|
export type AuthUser = DefaultAuthUser;
|
|
46
42
|
|
|
47
|
-
|
|
48
|
-
|
|
43
|
+
type DefaultQueryShape = { [key: string]: string | string[] | undefined };
|
|
44
|
+
type DefaultParamsShape = Record<string, string>;
|
|
45
|
+
type DefaultCookiesShape = Record<string, string>;
|
|
46
|
+
|
|
47
|
+
type BaseVectorRequest = Omit<Request, 'body' | 'json' | 'text' | 'formData' | 'arrayBuffer' | 'blob'>;
|
|
48
|
+
|
|
49
|
+
type InferValidatedSection<TValidatedInput, TKey extends string, TFallback> = [TValidatedInput] extends [undefined]
|
|
50
|
+
? TFallback
|
|
51
|
+
: TValidatedInput extends Record<string, unknown>
|
|
52
|
+
? TKey extends keyof TValidatedInput
|
|
53
|
+
? TValidatedInput[TKey]
|
|
54
|
+
: TFallback
|
|
55
|
+
: TFallback;
|
|
56
|
+
|
|
57
|
+
type InferValidatedInputValue<TValidatedInput> = [TValidatedInput] extends [undefined] ? unknown : TValidatedInput;
|
|
58
|
+
|
|
59
|
+
export type BunRouteHandler = (req: Request) => Response | Promise<Response>;
|
|
60
|
+
export type BunMethodMap = Record<string, BunRouteHandler>;
|
|
61
|
+
export type BunRouteTable = Record<string, BunMethodMap | Response>;
|
|
62
|
+
export type LegacyRouteEntry = [string, RegExp, [BunRouteHandler, ...BunRouteHandler[]], string?];
|
|
63
|
+
|
|
64
|
+
export interface VectorRequest<TTypes extends VectorTypes = DefaultVectorTypes, TValidatedInput = undefined>
|
|
65
|
+
extends BaseVectorRequest {
|
|
49
66
|
authUser?: GetAuthType<TTypes>;
|
|
50
67
|
context: GetContextType<TTypes>;
|
|
51
68
|
metadata?: GetMetadataType<TTypes>;
|
|
52
|
-
content?: any
|
|
53
|
-
|
|
54
|
-
|
|
69
|
+
content?: InferValidatedSection<TValidatedInput, 'body', any>;
|
|
70
|
+
body?: InferValidatedSection<TValidatedInput, 'body', any>;
|
|
71
|
+
params?: InferValidatedSection<TValidatedInput, 'params', DefaultParamsShape>;
|
|
72
|
+
query: InferValidatedSection<TValidatedInput, 'query', DefaultQueryShape>;
|
|
55
73
|
headers: Headers;
|
|
56
|
-
cookies?:
|
|
74
|
+
cookies?: InferValidatedSection<TValidatedInput, 'cookies', DefaultCookiesShape>;
|
|
75
|
+
validatedInput?: InferValidatedInputValue<TValidatedInput>;
|
|
57
76
|
startTime?: number;
|
|
58
77
|
[key: string]: any;
|
|
59
78
|
}
|
|
@@ -63,6 +82,33 @@ export interface CacheOptions {
|
|
|
63
82
|
ttl?: number;
|
|
64
83
|
}
|
|
65
84
|
|
|
85
|
+
export type StandardRouteSchema = StandardSchemaV1<any, any>;
|
|
86
|
+
export type RouteSchemaStatusCode = number | `${number}` | 'default';
|
|
87
|
+
export type RouteSchemaOutputMap = Partial<Record<RouteSchemaStatusCode, StandardRouteSchema>>;
|
|
88
|
+
|
|
89
|
+
export interface RouteSchemaDefinition<
|
|
90
|
+
TInput extends StandardRouteSchema | undefined = StandardRouteSchema | undefined,
|
|
91
|
+
TOutput extends RouteSchemaOutputMap | StandardRouteSchema | undefined =
|
|
92
|
+
| RouteSchemaOutputMap
|
|
93
|
+
| StandardRouteSchema
|
|
94
|
+
| undefined,
|
|
95
|
+
> {
|
|
96
|
+
input?: TInput;
|
|
97
|
+
output?: TOutput;
|
|
98
|
+
tag?: string;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
export type InferStandardSchemaInput<TSchema extends StandardRouteSchema> = StandardSchemaV1.InferInput<TSchema>;
|
|
102
|
+
export type InferStandardSchemaOutput<TSchema extends StandardRouteSchema> = StandardSchemaV1.InferOutput<TSchema>;
|
|
103
|
+
export type StandardJSONSchemaCapable = StandardJSONSchemaV1<any, any>;
|
|
104
|
+
|
|
105
|
+
export type InferRouteInputFromSchemaDefinition<TSchemaDef extends RouteSchemaDefinition | undefined> =
|
|
106
|
+
TSchemaDef extends { input: infer TInputSchema }
|
|
107
|
+
? TInputSchema extends StandardRouteSchema
|
|
108
|
+
? InferStandardSchemaOutput<TInputSchema>
|
|
109
|
+
: undefined
|
|
110
|
+
: undefined;
|
|
111
|
+
|
|
66
112
|
export interface RouteOptions<TTypes extends VectorTypes = DefaultVectorTypes> {
|
|
67
113
|
method: string;
|
|
68
114
|
path: string;
|
|
@@ -70,9 +116,23 @@ export interface RouteOptions<TTypes extends VectorTypes = DefaultVectorTypes> {
|
|
|
70
116
|
expose?: boolean; // defaults to true
|
|
71
117
|
cache?: CacheOptions | number;
|
|
72
118
|
rawRequest?: boolean;
|
|
119
|
+
validate?: boolean; // defaults to validating schema.input unless false
|
|
73
120
|
rawResponse?: boolean;
|
|
74
121
|
responseContentType?: string;
|
|
75
122
|
metadata?: GetMetadataType<TTypes>;
|
|
123
|
+
schema?: RouteSchemaDefinition;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
export interface RouteBooleanDefaults {
|
|
127
|
+
auth?: boolean;
|
|
128
|
+
expose?: boolean;
|
|
129
|
+
rawRequest?: boolean;
|
|
130
|
+
validate?: boolean;
|
|
131
|
+
rawResponse?: boolean;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
export interface VectorDefaults {
|
|
135
|
+
route?: RouteBooleanDefaults;
|
|
76
136
|
}
|
|
77
137
|
|
|
78
138
|
// Legacy config interface - will be deprecated
|
|
@@ -88,6 +148,8 @@ export interface VectorConfig<TTypes extends VectorTypes = DefaultVectorTypes> {
|
|
|
88
148
|
routeExcludePatterns?: string[];
|
|
89
149
|
autoDiscover?: boolean;
|
|
90
150
|
idleTimeout?: number;
|
|
151
|
+
defaults?: VectorDefaults;
|
|
152
|
+
openapi?: OpenAPIOptions | boolean;
|
|
91
153
|
}
|
|
92
154
|
|
|
93
155
|
// New config-driven schema - flat structure
|
|
@@ -100,6 +162,7 @@ export interface VectorConfigSchema<TTypes extends VectorTypes = DefaultVectorTy
|
|
|
100
162
|
routesDir?: string;
|
|
101
163
|
routeExcludePatterns?: string[];
|
|
102
164
|
idleTimeout?: number;
|
|
165
|
+
defaults?: VectorDefaults;
|
|
103
166
|
|
|
104
167
|
// Middleware functions
|
|
105
168
|
before?: BeforeMiddlewareHandler<TTypes>[];
|
|
@@ -112,6 +175,9 @@ export interface VectorConfigSchema<TTypes extends VectorTypes = DefaultVectorTy
|
|
|
112
175
|
// CORS configuration
|
|
113
176
|
cors?: CorsOptions | boolean;
|
|
114
177
|
|
|
178
|
+
// OpenAPI/docs configuration
|
|
179
|
+
openapi?: OpenAPIOptions | boolean;
|
|
180
|
+
|
|
115
181
|
// Custom types for TypeScript
|
|
116
182
|
types?: VectorTypes;
|
|
117
183
|
}
|
|
@@ -125,6 +191,25 @@ export interface CorsOptions {
|
|
|
125
191
|
maxAge?: number;
|
|
126
192
|
}
|
|
127
193
|
|
|
194
|
+
export interface OpenAPIDocsOptions {
|
|
195
|
+
enabled?: boolean;
|
|
196
|
+
path?: string;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
export interface OpenAPIInfoOptions {
|
|
200
|
+
title?: string;
|
|
201
|
+
version?: string;
|
|
202
|
+
description?: string;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
export interface OpenAPIOptions {
|
|
206
|
+
enabled?: boolean;
|
|
207
|
+
path?: string;
|
|
208
|
+
target?: 'openapi-3.0' | 'draft-2020-12' | 'draft-07' | ({} & string);
|
|
209
|
+
docs?: boolean | OpenAPIDocsOptions;
|
|
210
|
+
info?: OpenAPIInfoOptions;
|
|
211
|
+
}
|
|
212
|
+
|
|
128
213
|
export type BeforeMiddlewareHandler<TTypes extends VectorTypes = DefaultVectorTypes> = (
|
|
129
214
|
request: VectorRequest<TTypes>
|
|
130
215
|
) => Promise<VectorRequest<TTypes> | Response> | VectorRequest<TTypes> | Response;
|
|
@@ -135,8 +220,8 @@ export type AfterMiddlewareHandler<TTypes extends VectorTypes = DefaultVectorTyp
|
|
|
135
220
|
) => Promise<Response> | Response;
|
|
136
221
|
export type MiddlewareHandler = BeforeMiddlewareHandler | AfterMiddlewareHandler;
|
|
137
222
|
|
|
138
|
-
export type RouteHandler<TTypes extends VectorTypes = DefaultVectorTypes> = (
|
|
139
|
-
request: VectorRequest<TTypes>
|
|
223
|
+
export type RouteHandler<TTypes extends VectorTypes = DefaultVectorTypes, TValidatedInput = undefined> = (
|
|
224
|
+
request: VectorRequest<TTypes, TValidatedInput>
|
|
140
225
|
) => Promise<any> | any;
|
|
141
226
|
|
|
142
227
|
export type ProtectedHandler<TTypes extends VectorTypes = DefaultVectorTypes> = (
|
|
@@ -145,9 +230,9 @@ export type ProtectedHandler<TTypes extends VectorTypes = DefaultVectorTypes> =
|
|
|
145
230
|
|
|
146
231
|
export type CacheHandler = (key: string, factory: () => Promise<any>, ttl: number) => Promise<any>;
|
|
147
232
|
|
|
148
|
-
export interface RouteDefinition<TTypes extends VectorTypes = DefaultVectorTypes> {
|
|
233
|
+
export interface RouteDefinition<TTypes extends VectorTypes = DefaultVectorTypes, TValidatedInput = undefined> {
|
|
149
234
|
options: RouteOptions<TTypes>;
|
|
150
|
-
handler: RouteHandler<TTypes>;
|
|
235
|
+
handler: RouteHandler<TTypes, TValidatedInput>;
|
|
151
236
|
}
|
|
152
237
|
|
|
153
238
|
export interface GeneratedRoute<TTypes extends VectorTypes = DefaultVectorTypes> {
|
|
@@ -156,3 +241,5 @@ export interface GeneratedRoute<TTypes extends VectorTypes = DefaultVectorTypes>
|
|
|
156
241
|
method: string;
|
|
157
242
|
options: RouteOptions<TTypes>;
|
|
158
243
|
}
|
|
244
|
+
|
|
245
|
+
export type { StandardJSONSchemaV1, StandardSchemaV1, StandardTypedV1 };
|