vector-framework 1.0.0 → 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 -634
- package/dist/auth/protected.d.ts.map +1 -1
- package/dist/auth/protected.js.map +1 -1
- package/dist/cache/manager.d.ts +5 -2
- package/dist/cache/manager.d.ts.map +1 -1
- package/dist/cache/manager.js +21 -12
- package/dist/cache/manager.js.map +1 -1
- package/dist/cli/index.js +60 -126
- 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 +2774 -599
- 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 +2 -2
- package/dist/core/config-loader.d.ts.map +1 -1
- package/dist/core/config-loader.js +18 -18
- package/dist/core/config-loader.js.map +1 -1
- package/dist/core/router.d.ts +41 -15
- package/dist/core/router.d.ts.map +1 -1
- package/dist/core/router.js +465 -150
- package/dist/core/router.js.map +1 -1
- package/dist/core/server.d.ts +17 -3
- package/dist/core/server.d.ts.map +1 -1
- package/dist/core/server.js +274 -33
- package/dist/core/server.js.map +1 -1
- package/dist/core/vector.d.ts +9 -8
- package/dist/core/vector.d.ts.map +1 -1
- package/dist/core/vector.js +40 -32
- 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 +1 -1
- package/dist/dev/route-scanner.d.ts.map +1 -1
- package/dist/dev/route-scanner.js +37 -43
- 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 +84 -84
- package/dist/http.js.map +1 -1
- package/dist/index.d.ts +3 -3
- package/dist/index.js +1314 -8
- package/dist/index.mjs +1314 -8
- package/dist/middleware/manager.d.ts +1 -1
- 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 +7 -0
- package/dist/utils/path.d.ts.map +1 -1
- package/dist/utils/path.js +14 -3
- 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 +24 -19
- package/src/auth/protected.ts +3 -13
- package/src/cache/manager.ts +25 -30
- package/src/cli/index.ts +62 -141
- package/src/cli/option-resolution.ts +40 -0
- package/src/constants/index.ts +7 -0
- package/src/core/config-loader.ts +20 -22
- package/src/core/router.ts +535 -155
- package/src/core/server.ts +354 -45
- package/src/core/vector.ts +71 -61
- package/src/dev/route-generator.ts +1 -3
- package/src/dev/route-scanner.ts +38 -51
- package/src/http.ts +117 -187
- package/src/index.ts +3 -3
- package/src/middleware/manager.ts +8 -11
- 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 +19 -4
- package/src/utils/schema-validation.ts +123 -0
- package/src/utils/validation.ts +1 -0
|
@@ -36,9 +36,7 @@ export class RouteGenerator {
|
|
|
36
36
|
|
|
37
37
|
if (fileRoutes.some((r) => r.name === 'default')) {
|
|
38
38
|
if (namedImports.length > 0) {
|
|
39
|
-
imports.push(
|
|
40
|
-
`import ${importName}, { ${namedImports.join(', ')} } from '${relativePath}';`
|
|
41
|
-
);
|
|
39
|
+
imports.push(`import ${importName}, { ${namedImports.join(', ')} } from '${relativePath}';`);
|
|
42
40
|
} else {
|
|
43
41
|
imports.push(`import ${importName} from '${relativePath}';`);
|
|
44
42
|
}
|
package/src/dev/route-scanner.ts
CHANGED
|
@@ -1,32 +1,31 @@
|
|
|
1
|
-
import { existsSync, promises as fs } from
|
|
2
|
-
import { join, relative, resolve, sep } from
|
|
3
|
-
import type { GeneratedRoute } from
|
|
1
|
+
import { existsSync, promises as fs } from 'node:fs';
|
|
2
|
+
import { join, relative, resolve, sep } from 'node:path';
|
|
3
|
+
import type { GeneratedRoute } from '../types';
|
|
4
4
|
|
|
5
5
|
export class RouteScanner {
|
|
6
6
|
private routesDir: string;
|
|
7
7
|
private excludePatterns: string[];
|
|
8
8
|
private static readonly DEFAULT_EXCLUDE_PATTERNS = [
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
9
|
+
'*.test.ts',
|
|
10
|
+
'*.test.js',
|
|
11
|
+
'*.test.tsx',
|
|
12
|
+
'*.test.jsx',
|
|
13
|
+
'*.spec.ts',
|
|
14
|
+
'*.spec.js',
|
|
15
|
+
'*.spec.tsx',
|
|
16
|
+
'*.spec.jsx',
|
|
17
|
+
'*.tests.ts',
|
|
18
|
+
'*.tests.js',
|
|
19
|
+
'**/__tests__/**',
|
|
20
|
+
'*.interface.ts',
|
|
21
|
+
'*.type.ts',
|
|
22
|
+
'*.d.ts',
|
|
23
23
|
];
|
|
24
24
|
|
|
25
|
-
constructor(routesDir =
|
|
25
|
+
constructor(routesDir = './routes', excludePatterns?: string[]) {
|
|
26
26
|
// Always resolve from the current working directory (user's project)
|
|
27
27
|
this.routesDir = resolve(process.cwd(), routesDir);
|
|
28
|
-
this.excludePatterns =
|
|
29
|
-
excludePatterns || RouteScanner.DEFAULT_EXCLUDE_PATTERNS;
|
|
28
|
+
this.excludePatterns = excludePatterns || RouteScanner.DEFAULT_EXCLUDE_PATTERNS;
|
|
30
29
|
}
|
|
31
30
|
|
|
32
31
|
async scan(): Promise<GeneratedRoute[]> {
|
|
@@ -40,7 +39,7 @@ export class RouteScanner {
|
|
|
40
39
|
try {
|
|
41
40
|
await this.scanDirectory(this.routesDir, routes);
|
|
42
41
|
} catch (error) {
|
|
43
|
-
if ((error as any).code ===
|
|
42
|
+
if ((error as any).code === 'ENOENT') {
|
|
44
43
|
console.warn(` ✗ Routes directory not accessible: ${this.routesDir}`);
|
|
45
44
|
return [];
|
|
46
45
|
}
|
|
@@ -56,15 +55,16 @@ export class RouteScanner {
|
|
|
56
55
|
for (const pattern of this.excludePatterns) {
|
|
57
56
|
// Convert glob pattern to regex
|
|
58
57
|
const regexPattern = pattern
|
|
59
|
-
.replace(/\./g,
|
|
60
|
-
.replace(
|
|
61
|
-
.replace(
|
|
62
|
-
.replace(
|
|
58
|
+
.replace(/\./g, '\\.') // Escape dots
|
|
59
|
+
.replace(/\*\*/g, '__GLOBSTAR__') // protect ** before * replacement
|
|
60
|
+
.replace(/\*/g, '[^/]*') // * matches anything except /
|
|
61
|
+
.replace(/__GLOBSTAR__/g, '.*') // ** matches anything including /
|
|
62
|
+
.replace(/\?/g, '.'); // ? matches single character
|
|
63
63
|
|
|
64
64
|
const regex = new RegExp(`^${regexPattern}$`);
|
|
65
65
|
|
|
66
66
|
// Check both the full relative path and just the filename
|
|
67
|
-
const filename = relativePath.split(sep).pop() ||
|
|
67
|
+
const filename = relativePath.split(sep).pop() || '';
|
|
68
68
|
if (regex.test(relativePath) || regex.test(filename)) {
|
|
69
69
|
return true;
|
|
70
70
|
}
|
|
@@ -73,11 +73,7 @@ export class RouteScanner {
|
|
|
73
73
|
return false;
|
|
74
74
|
}
|
|
75
75
|
|
|
76
|
-
private async scanDirectory(
|
|
77
|
-
dir: string,
|
|
78
|
-
routes: GeneratedRoute[],
|
|
79
|
-
basePath = ""
|
|
80
|
-
): Promise<void> {
|
|
76
|
+
private async scanDirectory(dir: string, routes: GeneratedRoute[], basePath = ''): Promise<void> {
|
|
81
77
|
const entries = await fs.readdir(dir);
|
|
82
78
|
|
|
83
79
|
for (const entry of entries) {
|
|
@@ -87,32 +83,29 @@ export class RouteScanner {
|
|
|
87
83
|
if (stats.isDirectory()) {
|
|
88
84
|
const newBasePath = basePath ? `${basePath}/${entry}` : entry;
|
|
89
85
|
await this.scanDirectory(fullPath, routes, newBasePath);
|
|
90
|
-
} else if (entry.endsWith(
|
|
86
|
+
} else if (entry.endsWith('.ts') || entry.endsWith('.js')) {
|
|
91
87
|
// Skip excluded files (test files, etc.)
|
|
92
88
|
if (this.isExcluded(fullPath)) {
|
|
93
89
|
continue;
|
|
94
90
|
}
|
|
95
91
|
const routePath = relative(this.routesDir, fullPath)
|
|
96
|
-
.replace(/\.(ts|js)$/,
|
|
92
|
+
.replace(/\.(ts|js)$/, '')
|
|
97
93
|
.split(sep)
|
|
98
|
-
.join(
|
|
94
|
+
.join('/');
|
|
99
95
|
|
|
100
96
|
try {
|
|
101
97
|
// Convert Windows paths to URLs for import
|
|
102
|
-
const importPath =
|
|
103
|
-
process.platform === "win32"
|
|
104
|
-
? `file:///${fullPath.replace(/\\/g, "/")}`
|
|
105
|
-
: fullPath;
|
|
98
|
+
const importPath = process.platform === 'win32' ? `file:///${fullPath.replace(/\\/g, '/')}` : fullPath;
|
|
106
99
|
|
|
107
100
|
const module = await import(importPath);
|
|
108
101
|
|
|
109
|
-
if (module.default && typeof module.default ===
|
|
102
|
+
if (module.default && typeof module.default === 'function') {
|
|
110
103
|
routes.push({
|
|
111
|
-
name:
|
|
104
|
+
name: 'default',
|
|
112
105
|
path: fullPath,
|
|
113
|
-
method:
|
|
106
|
+
method: 'GET',
|
|
114
107
|
options: {
|
|
115
|
-
method:
|
|
108
|
+
method: 'GET',
|
|
116
109
|
path: `/${routePath}`,
|
|
117
110
|
expose: true,
|
|
118
111
|
},
|
|
@@ -120,16 +113,10 @@ export class RouteScanner {
|
|
|
120
113
|
}
|
|
121
114
|
|
|
122
115
|
for (const [name, value] of Object.entries(module)) {
|
|
123
|
-
if (name ===
|
|
116
|
+
if (name === 'default') continue;
|
|
124
117
|
|
|
125
118
|
// Check for new RouteDefinition format
|
|
126
|
-
if (
|
|
127
|
-
value &&
|
|
128
|
-
typeof value === "object" &&
|
|
129
|
-
"entry" in value &&
|
|
130
|
-
"options" in value &&
|
|
131
|
-
"handler" in value
|
|
132
|
-
) {
|
|
119
|
+
if (value && typeof value === 'object' && 'entry' in value && 'options' in value && 'handler' in value) {
|
|
133
120
|
const routeDef = value as any;
|
|
134
121
|
routes.push({
|
|
135
122
|
name,
|
|
@@ -161,7 +148,7 @@ export class RouteScanner {
|
|
|
161
148
|
}
|
|
162
149
|
|
|
163
150
|
enableWatch(callback: () => void) {
|
|
164
|
-
if (typeof Bun !==
|
|
151
|
+
if (typeof Bun !== 'undefined' && Bun.env.NODE_ENV === 'development') {
|
|
165
152
|
console.log(`Watching for route changes in ${this.routesDir}`);
|
|
166
153
|
|
|
167
154
|
setInterval(async () => {
|
package/src/http.ts
CHANGED
|
@@ -1,89 +1,66 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { CONTENT_TYPES, HTTP_STATUS } from "./constants";
|
|
1
|
+
import { CONTENT_TYPES, HTTP_STATUS } from './constants';
|
|
3
2
|
import type {
|
|
4
3
|
CacheOptions,
|
|
5
4
|
DefaultVectorTypes,
|
|
6
5
|
GetAuthType,
|
|
6
|
+
InferRouteInputFromSchemaDefinition,
|
|
7
|
+
RouteSchemaDefinition,
|
|
7
8
|
VectorRequest,
|
|
8
9
|
VectorTypes,
|
|
9
|
-
} from
|
|
10
|
-
import { getVectorInstance } from
|
|
10
|
+
} from './types';
|
|
11
|
+
import { getVectorInstance } from './core/vector';
|
|
11
12
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
> extends IRequest {
|
|
15
|
-
authUser?: GetAuthType<TTypes>;
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
export const { preflight, corsify } = cors({
|
|
19
|
-
origin: "*",
|
|
20
|
-
credentials: true,
|
|
21
|
-
allowHeaders: "Content-Type, Authorization",
|
|
22
|
-
allowMethods: "GET, POST, PUT, PATCH, DELETE, OPTIONS",
|
|
23
|
-
exposeHeaders: "Authorization",
|
|
24
|
-
maxAge: 86_400,
|
|
25
|
-
});
|
|
26
|
-
|
|
27
|
-
interface ExtendedApiOptions extends ApiOptions {
|
|
13
|
+
interface ExtendedApiOptions<TSchemaDef extends RouteSchemaDefinition | undefined = RouteSchemaDefinition | undefined>
|
|
14
|
+
extends ApiOptions<TSchemaDef> {
|
|
28
15
|
method: string;
|
|
29
16
|
path: string;
|
|
30
17
|
}
|
|
31
18
|
|
|
32
19
|
export interface RouteDefinition<
|
|
33
|
-
TTypes extends VectorTypes = DefaultVectorTypes
|
|
20
|
+
TTypes extends VectorTypes = DefaultVectorTypes,
|
|
21
|
+
TValidatedInput = undefined,
|
|
22
|
+
TSchemaDef extends RouteSchemaDefinition | undefined = RouteSchemaDefinition | undefined,
|
|
34
23
|
> {
|
|
35
|
-
entry:
|
|
36
|
-
options: ExtendedApiOptions
|
|
37
|
-
handler: (req: VectorRequest<TTypes>) => Promise<unknown
|
|
24
|
+
entry: { method: string; path: string };
|
|
25
|
+
options: ExtendedApiOptions<TSchemaDef>;
|
|
26
|
+
handler: (req: VectorRequest<TTypes, TValidatedInput>) => Promise<unknown> | unknown;
|
|
38
27
|
}
|
|
39
28
|
|
|
40
|
-
export function route<
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
options.method.toUpperCase(),
|
|
48
|
-
RegExp(
|
|
49
|
-
`^${
|
|
50
|
-
options.path
|
|
51
|
-
.replace(/\/+(\/|$)/g, "$1") // strip double & trailing splash
|
|
52
|
-
.replace(/(\/?\.?):(\w+)\+/g, "($1(?<$2>*))") // greedy params
|
|
53
|
-
.replace(/(\/?\.?):(\w+)/g, "($1(?<$2>[^$1/]+?))") // named params and image format
|
|
54
|
-
.replace(/\./g, "\\.") // dot in path
|
|
55
|
-
.replace(/(\/?)\*/g, "($1.*)?") // wildcard
|
|
56
|
-
}/*$`
|
|
57
|
-
),
|
|
58
|
-
[handler],
|
|
59
|
-
options.path,
|
|
60
|
-
];
|
|
61
|
-
|
|
29
|
+
export function route<
|
|
30
|
+
TTypes extends VectorTypes = DefaultVectorTypes,
|
|
31
|
+
TSchemaDef extends RouteSchemaDefinition | undefined = RouteSchemaDefinition | undefined,
|
|
32
|
+
>(
|
|
33
|
+
options: ExtendedApiOptions<TSchemaDef>,
|
|
34
|
+
fn: (req: VectorRequest<TTypes, InferRouteInputFromSchemaDefinition<TSchemaDef>>) => Promise<unknown> | unknown
|
|
35
|
+
): RouteDefinition<TTypes, InferRouteInputFromSchemaDefinition<TSchemaDef>, TSchemaDef> {
|
|
62
36
|
return {
|
|
63
|
-
entry
|
|
37
|
+
entry: {
|
|
38
|
+
method: options.method.toUpperCase(),
|
|
39
|
+
path: options.path,
|
|
40
|
+
},
|
|
64
41
|
options,
|
|
65
42
|
handler: fn,
|
|
66
43
|
};
|
|
67
44
|
}
|
|
68
45
|
|
|
69
46
|
function stringifyData(data: unknown): string {
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
47
|
+
const val = data ?? null;
|
|
48
|
+
try {
|
|
49
|
+
return JSON.stringify(val);
|
|
50
|
+
} catch (e) {
|
|
51
|
+
if (e instanceof TypeError && /\bbigint\b/i.test(e.message)) {
|
|
52
|
+
return JSON.stringify(val, (_key, value) => (typeof value === 'bigint' ? value.toString() : value));
|
|
53
|
+
}
|
|
54
|
+
throw e;
|
|
55
|
+
}
|
|
73
56
|
}
|
|
74
57
|
|
|
75
58
|
const ApiResponse = {
|
|
76
|
-
success: <T>(data: T, contentType?: string) =>
|
|
77
|
-
|
|
78
|
-
created: <T>(data: T, contentType?: string) =>
|
|
79
|
-
createResponse(HTTP_STATUS.CREATED, data, contentType),
|
|
59
|
+
success: <T>(data: T, contentType?: string) => createResponse(HTTP_STATUS.OK, data, contentType),
|
|
60
|
+
created: <T>(data: T, contentType?: string) => createResponse(HTTP_STATUS.CREATED, data, contentType),
|
|
80
61
|
};
|
|
81
62
|
|
|
82
|
-
function createErrorResponse(
|
|
83
|
-
code: number,
|
|
84
|
-
message: string,
|
|
85
|
-
contentType?: string
|
|
86
|
-
): Response {
|
|
63
|
+
function createErrorResponse(code: number, message: string, contentType?: string): Response {
|
|
87
64
|
const errorBody = {
|
|
88
65
|
error: true,
|
|
89
66
|
message,
|
|
@@ -96,204 +73,152 @@ function createErrorResponse(
|
|
|
96
73
|
|
|
97
74
|
export const APIError = {
|
|
98
75
|
// 4xx Client Errors
|
|
99
|
-
badRequest: (msg =
|
|
76
|
+
badRequest: (msg = 'Bad Request', contentType?: string) =>
|
|
100
77
|
createErrorResponse(HTTP_STATUS.BAD_REQUEST, msg, contentType),
|
|
101
78
|
|
|
102
|
-
unauthorized: (msg =
|
|
79
|
+
unauthorized: (msg = 'Unauthorized', contentType?: string) =>
|
|
103
80
|
createErrorResponse(HTTP_STATUS.UNAUTHORIZED, msg, contentType),
|
|
104
81
|
|
|
105
|
-
paymentRequired: (msg =
|
|
106
|
-
createErrorResponse(402, msg, contentType),
|
|
82
|
+
paymentRequired: (msg = 'Payment Required', contentType?: string) => createErrorResponse(402, msg, contentType),
|
|
107
83
|
|
|
108
|
-
forbidden: (msg =
|
|
109
|
-
createErrorResponse(HTTP_STATUS.FORBIDDEN, msg, contentType),
|
|
84
|
+
forbidden: (msg = 'Forbidden', contentType?: string) => createErrorResponse(HTTP_STATUS.FORBIDDEN, msg, contentType),
|
|
110
85
|
|
|
111
|
-
notFound: (msg =
|
|
112
|
-
createErrorResponse(HTTP_STATUS.NOT_FOUND, msg, contentType),
|
|
86
|
+
notFound: (msg = 'Not Found', contentType?: string) => createErrorResponse(HTTP_STATUS.NOT_FOUND, msg, contentType),
|
|
113
87
|
|
|
114
|
-
methodNotAllowed: (msg =
|
|
115
|
-
createErrorResponse(405, msg, contentType),
|
|
88
|
+
methodNotAllowed: (msg = 'Method Not Allowed', contentType?: string) => createErrorResponse(405, msg, contentType),
|
|
116
89
|
|
|
117
|
-
notAcceptable: (msg =
|
|
118
|
-
createErrorResponse(406, msg, contentType),
|
|
90
|
+
notAcceptable: (msg = 'Not Acceptable', contentType?: string) => createErrorResponse(406, msg, contentType),
|
|
119
91
|
|
|
120
|
-
requestTimeout: (msg =
|
|
121
|
-
createErrorResponse(408, msg, contentType),
|
|
92
|
+
requestTimeout: (msg = 'Request Timeout', contentType?: string) => createErrorResponse(408, msg, contentType),
|
|
122
93
|
|
|
123
|
-
conflict: (msg =
|
|
124
|
-
createErrorResponse(HTTP_STATUS.CONFLICT, msg, contentType),
|
|
94
|
+
conflict: (msg = 'Conflict', contentType?: string) => createErrorResponse(HTTP_STATUS.CONFLICT, msg, contentType),
|
|
125
95
|
|
|
126
|
-
gone: (msg =
|
|
127
|
-
createErrorResponse(410, msg, contentType),
|
|
96
|
+
gone: (msg = 'Gone', contentType?: string) => createErrorResponse(410, msg, contentType),
|
|
128
97
|
|
|
129
|
-
lengthRequired: (msg =
|
|
130
|
-
createErrorResponse(411, msg, contentType),
|
|
98
|
+
lengthRequired: (msg = 'Length Required', contentType?: string) => createErrorResponse(411, msg, contentType),
|
|
131
99
|
|
|
132
|
-
preconditionFailed: (msg =
|
|
133
|
-
createErrorResponse(412, msg, contentType),
|
|
100
|
+
preconditionFailed: (msg = 'Precondition Failed', contentType?: string) => createErrorResponse(412, msg, contentType),
|
|
134
101
|
|
|
135
|
-
payloadTooLarge: (msg =
|
|
136
|
-
createErrorResponse(413, msg, contentType),
|
|
102
|
+
payloadTooLarge: (msg = 'Payload Too Large', contentType?: string) => createErrorResponse(413, msg, contentType),
|
|
137
103
|
|
|
138
|
-
uriTooLong: (msg =
|
|
139
|
-
createErrorResponse(414, msg, contentType),
|
|
104
|
+
uriTooLong: (msg = 'URI Too Long', contentType?: string) => createErrorResponse(414, msg, contentType),
|
|
140
105
|
|
|
141
|
-
unsupportedMediaType: (
|
|
142
|
-
msg
|
|
143
|
-
contentType?: string
|
|
144
|
-
) => createErrorResponse(415, msg, contentType),
|
|
106
|
+
unsupportedMediaType: (msg = 'Unsupported Media Type', contentType?: string) =>
|
|
107
|
+
createErrorResponse(415, msg, contentType),
|
|
145
108
|
|
|
146
|
-
rangeNotSatisfiable: (msg =
|
|
109
|
+
rangeNotSatisfiable: (msg = 'Range Not Satisfiable', contentType?: string) =>
|
|
147
110
|
createErrorResponse(416, msg, contentType),
|
|
148
111
|
|
|
149
|
-
expectationFailed: (msg =
|
|
150
|
-
createErrorResponse(417, msg, contentType),
|
|
112
|
+
expectationFailed: (msg = 'Expectation Failed', contentType?: string) => createErrorResponse(417, msg, contentType),
|
|
151
113
|
|
|
152
|
-
imATeapot: (msg = "I'm a teapot", contentType?: string) =>
|
|
153
|
-
createErrorResponse(418, msg, contentType),
|
|
114
|
+
imATeapot: (msg = "I'm a teapot", contentType?: string) => createErrorResponse(418, msg, contentType),
|
|
154
115
|
|
|
155
|
-
misdirectedRequest: (msg =
|
|
156
|
-
createErrorResponse(421, msg, contentType),
|
|
116
|
+
misdirectedRequest: (msg = 'Misdirected Request', contentType?: string) => createErrorResponse(421, msg, contentType),
|
|
157
117
|
|
|
158
|
-
unprocessableEntity: (msg =
|
|
118
|
+
unprocessableEntity: (msg = 'Unprocessable Entity', contentType?: string) =>
|
|
159
119
|
createErrorResponse(HTTP_STATUS.UNPROCESSABLE_ENTITY, msg, contentType),
|
|
160
120
|
|
|
161
|
-
locked: (msg =
|
|
162
|
-
createErrorResponse(423, msg, contentType),
|
|
121
|
+
locked: (msg = 'Locked', contentType?: string) => createErrorResponse(423, msg, contentType),
|
|
163
122
|
|
|
164
|
-
failedDependency: (msg =
|
|
165
|
-
createErrorResponse(424, msg, contentType),
|
|
123
|
+
failedDependency: (msg = 'Failed Dependency', contentType?: string) => createErrorResponse(424, msg, contentType),
|
|
166
124
|
|
|
167
|
-
tooEarly: (msg =
|
|
168
|
-
createErrorResponse(425, msg, contentType),
|
|
125
|
+
tooEarly: (msg = 'Too Early', contentType?: string) => createErrorResponse(425, msg, contentType),
|
|
169
126
|
|
|
170
|
-
upgradeRequired: (msg =
|
|
171
|
-
createErrorResponse(426, msg, contentType),
|
|
127
|
+
upgradeRequired: (msg = 'Upgrade Required', contentType?: string) => createErrorResponse(426, msg, contentType),
|
|
172
128
|
|
|
173
|
-
preconditionRequired: (msg =
|
|
129
|
+
preconditionRequired: (msg = 'Precondition Required', contentType?: string) =>
|
|
174
130
|
createErrorResponse(428, msg, contentType),
|
|
175
131
|
|
|
176
|
-
tooManyRequests: (msg =
|
|
177
|
-
createErrorResponse(429, msg, contentType),
|
|
132
|
+
tooManyRequests: (msg = 'Too Many Requests', contentType?: string) => createErrorResponse(429, msg, contentType),
|
|
178
133
|
|
|
179
|
-
requestHeaderFieldsTooLarge: (
|
|
180
|
-
msg
|
|
181
|
-
contentType?: string
|
|
182
|
-
) => createErrorResponse(431, msg, contentType),
|
|
134
|
+
requestHeaderFieldsTooLarge: (msg = 'Request Header Fields Too Large', contentType?: string) =>
|
|
135
|
+
createErrorResponse(431, msg, contentType),
|
|
183
136
|
|
|
184
|
-
unavailableForLegalReasons: (
|
|
185
|
-
msg
|
|
186
|
-
contentType?: string
|
|
187
|
-
) => createErrorResponse(451, msg, contentType),
|
|
137
|
+
unavailableForLegalReasons: (msg = 'Unavailable For Legal Reasons', contentType?: string) =>
|
|
138
|
+
createErrorResponse(451, msg, contentType),
|
|
188
139
|
|
|
189
140
|
// 5xx Server Errors
|
|
190
|
-
internalServerError: (msg =
|
|
141
|
+
internalServerError: (msg = 'Internal Server Error', contentType?: string) =>
|
|
191
142
|
createErrorResponse(HTTP_STATUS.INTERNAL_SERVER_ERROR, msg, contentType),
|
|
192
143
|
|
|
193
|
-
notImplemented: (msg =
|
|
194
|
-
createErrorResponse(501, msg, contentType),
|
|
144
|
+
notImplemented: (msg = 'Not Implemented', contentType?: string) => createErrorResponse(501, msg, contentType),
|
|
195
145
|
|
|
196
|
-
badGateway: (msg =
|
|
197
|
-
createErrorResponse(502, msg, contentType),
|
|
146
|
+
badGateway: (msg = 'Bad Gateway', contentType?: string) => createErrorResponse(502, msg, contentType),
|
|
198
147
|
|
|
199
|
-
serviceUnavailable: (msg =
|
|
200
|
-
createErrorResponse(503, msg, contentType),
|
|
148
|
+
serviceUnavailable: (msg = 'Service Unavailable', contentType?: string) => createErrorResponse(503, msg, contentType),
|
|
201
149
|
|
|
202
|
-
gatewayTimeout: (msg =
|
|
203
|
-
createErrorResponse(504, msg, contentType),
|
|
150
|
+
gatewayTimeout: (msg = 'Gateway Timeout', contentType?: string) => createErrorResponse(504, msg, contentType),
|
|
204
151
|
|
|
205
|
-
httpVersionNotSupported: (
|
|
206
|
-
msg
|
|
207
|
-
contentType?: string
|
|
208
|
-
) => createErrorResponse(505, msg, contentType),
|
|
152
|
+
httpVersionNotSupported: (msg = 'HTTP Version Not Supported', contentType?: string) =>
|
|
153
|
+
createErrorResponse(505, msg, contentType),
|
|
209
154
|
|
|
210
|
-
variantAlsoNegotiates: (
|
|
211
|
-
msg
|
|
212
|
-
contentType?: string
|
|
213
|
-
) => createErrorResponse(506, msg, contentType),
|
|
155
|
+
variantAlsoNegotiates: (msg = 'Variant Also Negotiates', contentType?: string) =>
|
|
156
|
+
createErrorResponse(506, msg, contentType),
|
|
214
157
|
|
|
215
|
-
insufficientStorage: (msg =
|
|
158
|
+
insufficientStorage: (msg = 'Insufficient Storage', contentType?: string) =>
|
|
216
159
|
createErrorResponse(507, msg, contentType),
|
|
217
160
|
|
|
218
|
-
loopDetected: (msg =
|
|
219
|
-
createErrorResponse(508, msg, contentType),
|
|
161
|
+
loopDetected: (msg = 'Loop Detected', contentType?: string) => createErrorResponse(508, msg, contentType),
|
|
220
162
|
|
|
221
|
-
notExtended: (msg =
|
|
222
|
-
createErrorResponse(510, msg, contentType),
|
|
163
|
+
notExtended: (msg = 'Not Extended', contentType?: string) => createErrorResponse(510, msg, contentType),
|
|
223
164
|
|
|
224
|
-
networkAuthenticationRequired: (
|
|
225
|
-
msg
|
|
226
|
-
contentType?: string
|
|
227
|
-
) => createErrorResponse(511, msg, contentType),
|
|
165
|
+
networkAuthenticationRequired: (msg = 'Network Authentication Required', contentType?: string) =>
|
|
166
|
+
createErrorResponse(511, msg, contentType),
|
|
228
167
|
|
|
229
168
|
// Aliases for common use cases
|
|
230
|
-
invalidArgument: (msg =
|
|
169
|
+
invalidArgument: (msg = 'Invalid Argument', contentType?: string) =>
|
|
231
170
|
createErrorResponse(HTTP_STATUS.UNPROCESSABLE_ENTITY, msg, contentType),
|
|
232
171
|
|
|
233
|
-
rateLimitExceeded: (msg =
|
|
234
|
-
createErrorResponse(429, msg, contentType),
|
|
172
|
+
rateLimitExceeded: (msg = 'Rate Limit Exceeded', contentType?: string) => createErrorResponse(429, msg, contentType),
|
|
235
173
|
|
|
236
|
-
maintenance: (msg =
|
|
237
|
-
createErrorResponse(503, msg, contentType),
|
|
174
|
+
maintenance: (msg = 'Service Under Maintenance', contentType?: string) => createErrorResponse(503, msg, contentType),
|
|
238
175
|
|
|
239
176
|
// Helper to create custom error with any status code
|
|
240
|
-
custom: (statusCode: number, msg: string, contentType?: string) =>
|
|
241
|
-
createErrorResponse(statusCode, msg, contentType),
|
|
177
|
+
custom: (statusCode: number, msg: string, contentType?: string) => createErrorResponse(statusCode, msg, contentType),
|
|
242
178
|
};
|
|
243
179
|
|
|
244
|
-
export function createResponse(
|
|
245
|
-
statusCode: number,
|
|
246
|
-
data?: unknown,
|
|
247
|
-
contentType: string = CONTENT_TYPES.JSON
|
|
248
|
-
): Response {
|
|
180
|
+
export function createResponse(statusCode: number, data?: unknown, contentType: string = CONTENT_TYPES.JSON): Response {
|
|
249
181
|
const body = contentType === CONTENT_TYPES.JSON ? stringifyData(data) : data;
|
|
250
182
|
|
|
251
183
|
return new Response(body as string, {
|
|
252
184
|
status: statusCode,
|
|
253
|
-
headers: {
|
|
185
|
+
headers: { 'content-type': contentType },
|
|
254
186
|
});
|
|
255
187
|
}
|
|
256
188
|
|
|
257
|
-
export const protectedRoute = async <
|
|
258
|
-
TTypes extends VectorTypes = DefaultVectorTypes
|
|
259
|
-
>(
|
|
189
|
+
export const protectedRoute = async <TTypes extends VectorTypes = DefaultVectorTypes>(
|
|
260
190
|
request: VectorRequest<TTypes>,
|
|
261
191
|
responseContentType?: string
|
|
262
192
|
) => {
|
|
263
|
-
// Get the Vector instance to access the protected handler
|
|
264
193
|
const vector = getVectorInstance();
|
|
265
194
|
|
|
266
195
|
const protectedHandler = vector.getProtectedHandler();
|
|
267
196
|
if (!protectedHandler) {
|
|
268
|
-
throw APIError.unauthorized(
|
|
269
|
-
"Authentication not configured",
|
|
270
|
-
responseContentType
|
|
271
|
-
);
|
|
197
|
+
throw APIError.unauthorized('Authentication not configured', responseContentType);
|
|
272
198
|
}
|
|
273
199
|
|
|
274
200
|
try {
|
|
275
201
|
const authUser = await protectedHandler(request as any);
|
|
276
202
|
request.authUser = authUser as GetAuthType<TTypes>;
|
|
277
203
|
} catch (error) {
|
|
278
|
-
throw APIError.unauthorized(
|
|
279
|
-
error instanceof Error ? error.message : "Authentication failed",
|
|
280
|
-
responseContentType
|
|
281
|
-
);
|
|
204
|
+
throw APIError.unauthorized(error instanceof Error ? error.message : 'Authentication failed', responseContentType);
|
|
282
205
|
}
|
|
283
206
|
};
|
|
284
207
|
|
|
285
|
-
export interface ApiOptions {
|
|
208
|
+
export interface ApiOptions<TSchemaDef extends RouteSchemaDefinition | undefined = RouteSchemaDefinition | undefined> {
|
|
286
209
|
auth?: boolean;
|
|
287
210
|
expose?: boolean;
|
|
288
211
|
rawRequest?: boolean;
|
|
212
|
+
validate?: boolean;
|
|
289
213
|
rawResponse?: boolean;
|
|
290
214
|
cache?: CacheOptions | number | null;
|
|
291
215
|
responseContentType?: string;
|
|
216
|
+
schema?: TSchemaDef;
|
|
292
217
|
}
|
|
293
218
|
|
|
294
|
-
export function api<TTypes extends VectorTypes = DefaultVectorTypes>(
|
|
219
|
+
export function api<TTypes extends VectorTypes = DefaultVectorTypes, TValidatedInput = undefined>(
|
|
295
220
|
options: ApiOptions,
|
|
296
|
-
fn: (request: VectorRequest<TTypes>) => Promise<unknown>
|
|
221
|
+
fn: (request: VectorRequest<TTypes, TValidatedInput>) => Promise<unknown> | unknown
|
|
297
222
|
) {
|
|
298
223
|
const {
|
|
299
224
|
auth = false,
|
|
@@ -303,37 +228,42 @@ export function api<TTypes extends VectorTypes = DefaultVectorTypes>(
|
|
|
303
228
|
responseContentType = CONTENT_TYPES.JSON,
|
|
304
229
|
} = options;
|
|
305
230
|
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
231
|
+
return async (request: Request) => {
|
|
232
|
+
const req = request as unknown as VectorRequest<TTypes>;
|
|
233
|
+
|
|
309
234
|
if (!expose) {
|
|
310
|
-
return APIError.forbidden(
|
|
235
|
+
return APIError.forbidden('Forbidden');
|
|
311
236
|
}
|
|
312
237
|
|
|
313
238
|
try {
|
|
314
239
|
if (auth) {
|
|
315
|
-
await protectedRoute(
|
|
316
|
-
request as any as VectorRequest<TTypes>,
|
|
317
|
-
responseContentType
|
|
318
|
-
);
|
|
240
|
+
await protectedRoute(req, responseContentType);
|
|
319
241
|
}
|
|
320
242
|
|
|
321
|
-
if (!rawRequest) {
|
|
322
|
-
|
|
243
|
+
if (!rawRequest && req.method !== 'GET' && req.method !== 'HEAD') {
|
|
244
|
+
try {
|
|
245
|
+
const contentType = req.headers.get('content-type');
|
|
246
|
+
if (contentType?.startsWith('application/json')) {
|
|
247
|
+
req.content = await req.json();
|
|
248
|
+
} else if (contentType?.startsWith('application/x-www-form-urlencoded')) {
|
|
249
|
+
req.content = Object.fromEntries(await req.formData());
|
|
250
|
+
} else if (contentType?.startsWith('multipart/form-data')) {
|
|
251
|
+
req.content = await req.formData();
|
|
252
|
+
} else {
|
|
253
|
+
req.content = await req.text();
|
|
254
|
+
}
|
|
255
|
+
} catch {
|
|
256
|
+
req.content = null;
|
|
257
|
+
}
|
|
323
258
|
}
|
|
324
259
|
|
|
325
|
-
|
|
326
|
-
const result = await fn(request as any as VectorRequest<TTypes>);
|
|
260
|
+
const result = await fn(req as unknown as VectorRequest<TTypes, TValidatedInput>);
|
|
327
261
|
|
|
328
|
-
return rawResponse
|
|
329
|
-
? result
|
|
330
|
-
: ApiResponse.success(result, responseContentType);
|
|
262
|
+
return rawResponse ? result : ApiResponse.success(result, responseContentType);
|
|
331
263
|
} catch (err: unknown) {
|
|
332
|
-
// Ensure we return a Response object
|
|
333
264
|
if (err instanceof Response) {
|
|
334
265
|
return err;
|
|
335
266
|
}
|
|
336
|
-
// For non-Response errors, wrap them
|
|
337
267
|
return APIError.internalServerError(String(err), responseContentType);
|
|
338
268
|
}
|
|
339
269
|
};
|
package/src/index.ts
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
// Public exports for route definitions only
|
|
2
|
-
import { route } from
|
|
2
|
+
import { route } from './http';
|
|
3
3
|
|
|
4
4
|
// Export route function for defining routes
|
|
5
5
|
export { route };
|
|
6
6
|
|
|
7
7
|
// Export utilities for route handlers
|
|
8
|
-
export { APIError, createResponse } from
|
|
8
|
+
export { APIError, createResponse } from './http';
|
|
9
9
|
|
|
10
10
|
// Export types for TypeScript users
|
|
11
|
-
export * from
|
|
11
|
+
export * from './types';
|
|
12
12
|
|
|
13
13
|
// Note: Vector framework is now config-driven and runs via CLI
|
|
14
14
|
// Usage: Create vector.config.ts and run 'vector dev' or 'vector start'
|