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
package/src/http.ts
CHANGED
|
@@ -1,81 +1,63 @@
|
|
|
1
|
-
import { cors, type IRequest, type RouteEntry, withContent } from 'itty-router';
|
|
2
|
-
import { buildRouteRegex } from './utils/path';
|
|
3
1
|
import { CONTENT_TYPES, HTTP_STATUS } from './constants';
|
|
4
2
|
import type {
|
|
5
3
|
CacheOptions,
|
|
6
4
|
DefaultVectorTypes,
|
|
7
5
|
GetAuthType,
|
|
6
|
+
InferRouteInputFromSchemaDefinition,
|
|
7
|
+
RouteSchemaDefinition,
|
|
8
8
|
VectorRequest,
|
|
9
9
|
VectorTypes,
|
|
10
10
|
} from './types';
|
|
11
11
|
import { getVectorInstance } from './core/vector';
|
|
12
12
|
|
|
13
|
-
|
|
14
|
-
extends
|
|
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
|
-
export interface RouteDefinition<
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
19
|
+
export interface RouteDefinition<
|
|
20
|
+
TTypes extends VectorTypes = DefaultVectorTypes,
|
|
21
|
+
TValidatedInput = undefined,
|
|
22
|
+
TSchemaDef extends RouteSchemaDefinition | undefined = RouteSchemaDefinition | undefined,
|
|
23
|
+
> {
|
|
24
|
+
entry: { method: string; path: string };
|
|
25
|
+
options: ExtendedApiOptions<TSchemaDef>;
|
|
26
|
+
handler: (req: VectorRequest<TTypes, TValidatedInput>) => Promise<unknown> | unknown;
|
|
36
27
|
}
|
|
37
28
|
|
|
38
|
-
export function route<
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
options.method.toUpperCase(),
|
|
46
|
-
buildRouteRegex(options.path),
|
|
47
|
-
[handler],
|
|
48
|
-
options.path,
|
|
49
|
-
];
|
|
50
|
-
|
|
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> {
|
|
51
36
|
return {
|
|
52
|
-
entry
|
|
37
|
+
entry: {
|
|
38
|
+
method: options.method.toUpperCase(),
|
|
39
|
+
path: options.path,
|
|
40
|
+
},
|
|
53
41
|
options,
|
|
54
42
|
handler: fn,
|
|
55
43
|
};
|
|
56
44
|
}
|
|
57
45
|
|
|
58
|
-
function hasBigInt(value: unknown, depth = 0): boolean {
|
|
59
|
-
if (typeof value === 'bigint') return true;
|
|
60
|
-
if (depth > 4 || value === null || typeof value !== 'object') return false;
|
|
61
|
-
for (const v of Object.values(value as object)) {
|
|
62
|
-
if (hasBigInt(v, depth + 1)) return true;
|
|
63
|
-
}
|
|
64
|
-
return false;
|
|
65
|
-
}
|
|
66
|
-
|
|
67
46
|
function stringifyData(data: unknown): string {
|
|
68
47
|
const val = data ?? null;
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
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
59
|
success: <T>(data: T, contentType?: string) => createResponse(HTTP_STATUS.OK, data, contentType),
|
|
77
|
-
created: <T>(data: T, contentType?: string) =>
|
|
78
|
-
createResponse(HTTP_STATUS.CREATED, data, contentType),
|
|
60
|
+
created: <T>(data: T, contentType?: string) => createResponse(HTTP_STATUS.CREATED, data, contentType),
|
|
79
61
|
};
|
|
80
62
|
|
|
81
63
|
function createErrorResponse(code: number, message: string, contentType?: string): Response {
|
|
@@ -97,40 +79,29 @@ export const APIError = {
|
|
|
97
79
|
unauthorized: (msg = 'Unauthorized', contentType?: string) =>
|
|
98
80
|
createErrorResponse(HTTP_STATUS.UNAUTHORIZED, msg, contentType),
|
|
99
81
|
|
|
100
|
-
paymentRequired: (msg = 'Payment Required', contentType?: string) =>
|
|
101
|
-
createErrorResponse(402, msg, contentType),
|
|
82
|
+
paymentRequired: (msg = 'Payment Required', contentType?: string) => createErrorResponse(402, msg, contentType),
|
|
102
83
|
|
|
103
|
-
forbidden: (msg = 'Forbidden', contentType?: string) =>
|
|
104
|
-
createErrorResponse(HTTP_STATUS.FORBIDDEN, msg, contentType),
|
|
84
|
+
forbidden: (msg = 'Forbidden', contentType?: string) => createErrorResponse(HTTP_STATUS.FORBIDDEN, msg, contentType),
|
|
105
85
|
|
|
106
|
-
notFound: (msg = 'Not Found', contentType?: string) =>
|
|
107
|
-
createErrorResponse(HTTP_STATUS.NOT_FOUND, msg, contentType),
|
|
86
|
+
notFound: (msg = 'Not Found', contentType?: string) => createErrorResponse(HTTP_STATUS.NOT_FOUND, msg, contentType),
|
|
108
87
|
|
|
109
|
-
methodNotAllowed: (msg = 'Method Not Allowed', contentType?: string) =>
|
|
110
|
-
createErrorResponse(405, msg, contentType),
|
|
88
|
+
methodNotAllowed: (msg = 'Method Not Allowed', contentType?: string) => createErrorResponse(405, msg, contentType),
|
|
111
89
|
|
|
112
|
-
notAcceptable: (msg = 'Not Acceptable', contentType?: string) =>
|
|
113
|
-
createErrorResponse(406, msg, contentType),
|
|
90
|
+
notAcceptable: (msg = 'Not Acceptable', contentType?: string) => createErrorResponse(406, msg, contentType),
|
|
114
91
|
|
|
115
|
-
requestTimeout: (msg = 'Request Timeout', contentType?: string) =>
|
|
116
|
-
createErrorResponse(408, msg, contentType),
|
|
92
|
+
requestTimeout: (msg = 'Request Timeout', contentType?: string) => createErrorResponse(408, msg, contentType),
|
|
117
93
|
|
|
118
|
-
conflict: (msg = 'Conflict', contentType?: string) =>
|
|
119
|
-
createErrorResponse(HTTP_STATUS.CONFLICT, msg, contentType),
|
|
94
|
+
conflict: (msg = 'Conflict', contentType?: string) => createErrorResponse(HTTP_STATUS.CONFLICT, msg, contentType),
|
|
120
95
|
|
|
121
96
|
gone: (msg = 'Gone', contentType?: string) => createErrorResponse(410, msg, contentType),
|
|
122
97
|
|
|
123
|
-
lengthRequired: (msg = 'Length Required', contentType?: string) =>
|
|
124
|
-
createErrorResponse(411, msg, contentType),
|
|
98
|
+
lengthRequired: (msg = 'Length Required', contentType?: string) => createErrorResponse(411, msg, contentType),
|
|
125
99
|
|
|
126
|
-
preconditionFailed: (msg = 'Precondition Failed', contentType?: string) =>
|
|
127
|
-
createErrorResponse(412, msg, contentType),
|
|
100
|
+
preconditionFailed: (msg = 'Precondition Failed', contentType?: string) => createErrorResponse(412, msg, contentType),
|
|
128
101
|
|
|
129
|
-
payloadTooLarge: (msg = 'Payload Too Large', contentType?: string) =>
|
|
130
|
-
createErrorResponse(413, msg, contentType),
|
|
102
|
+
payloadTooLarge: (msg = 'Payload Too Large', contentType?: string) => createErrorResponse(413, msg, contentType),
|
|
131
103
|
|
|
132
|
-
uriTooLong: (msg = 'URI Too Long', contentType?: string) =>
|
|
133
|
-
createErrorResponse(414, msg, contentType),
|
|
104
|
+
uriTooLong: (msg = 'URI Too Long', contentType?: string) => createErrorResponse(414, msg, contentType),
|
|
134
105
|
|
|
135
106
|
unsupportedMediaType: (msg = 'Unsupported Media Type', contentType?: string) =>
|
|
136
107
|
createErrorResponse(415, msg, contentType),
|
|
@@ -138,33 +109,27 @@ export const APIError = {
|
|
|
138
109
|
rangeNotSatisfiable: (msg = 'Range Not Satisfiable', contentType?: string) =>
|
|
139
110
|
createErrorResponse(416, msg, contentType),
|
|
140
111
|
|
|
141
|
-
expectationFailed: (msg = 'Expectation Failed', contentType?: string) =>
|
|
142
|
-
createErrorResponse(417, msg, contentType),
|
|
112
|
+
expectationFailed: (msg = 'Expectation Failed', contentType?: string) => createErrorResponse(417, msg, contentType),
|
|
143
113
|
|
|
144
|
-
imATeapot: (msg = "I'm a teapot", contentType?: string) =>
|
|
145
|
-
createErrorResponse(418, msg, contentType),
|
|
114
|
+
imATeapot: (msg = "I'm a teapot", contentType?: string) => createErrorResponse(418, msg, contentType),
|
|
146
115
|
|
|
147
|
-
misdirectedRequest: (msg = 'Misdirected Request', contentType?: string) =>
|
|
148
|
-
createErrorResponse(421, msg, contentType),
|
|
116
|
+
misdirectedRequest: (msg = 'Misdirected Request', contentType?: string) => createErrorResponse(421, msg, contentType),
|
|
149
117
|
|
|
150
118
|
unprocessableEntity: (msg = 'Unprocessable Entity', contentType?: string) =>
|
|
151
119
|
createErrorResponse(HTTP_STATUS.UNPROCESSABLE_ENTITY, msg, contentType),
|
|
152
120
|
|
|
153
121
|
locked: (msg = 'Locked', contentType?: string) => createErrorResponse(423, msg, contentType),
|
|
154
122
|
|
|
155
|
-
failedDependency: (msg = 'Failed Dependency', contentType?: string) =>
|
|
156
|
-
createErrorResponse(424, msg, contentType),
|
|
123
|
+
failedDependency: (msg = 'Failed Dependency', contentType?: string) => createErrorResponse(424, msg, contentType),
|
|
157
124
|
|
|
158
125
|
tooEarly: (msg = 'Too Early', contentType?: string) => createErrorResponse(425, msg, contentType),
|
|
159
126
|
|
|
160
|
-
upgradeRequired: (msg = 'Upgrade Required', contentType?: string) =>
|
|
161
|
-
createErrorResponse(426, msg, contentType),
|
|
127
|
+
upgradeRequired: (msg = 'Upgrade Required', contentType?: string) => createErrorResponse(426, msg, contentType),
|
|
162
128
|
|
|
163
129
|
preconditionRequired: (msg = 'Precondition Required', contentType?: string) =>
|
|
164
130
|
createErrorResponse(428, msg, contentType),
|
|
165
131
|
|
|
166
|
-
tooManyRequests: (msg = 'Too Many Requests', contentType?: string) =>
|
|
167
|
-
createErrorResponse(429, msg, contentType),
|
|
132
|
+
tooManyRequests: (msg = 'Too Many Requests', contentType?: string) => createErrorResponse(429, msg, contentType),
|
|
168
133
|
|
|
169
134
|
requestHeaderFieldsTooLarge: (msg = 'Request Header Fields Too Large', contentType?: string) =>
|
|
170
135
|
createErrorResponse(431, msg, contentType),
|
|
@@ -176,17 +141,13 @@ export const APIError = {
|
|
|
176
141
|
internalServerError: (msg = 'Internal Server Error', contentType?: string) =>
|
|
177
142
|
createErrorResponse(HTTP_STATUS.INTERNAL_SERVER_ERROR, msg, contentType),
|
|
178
143
|
|
|
179
|
-
notImplemented: (msg = 'Not Implemented', contentType?: string) =>
|
|
180
|
-
createErrorResponse(501, msg, contentType),
|
|
144
|
+
notImplemented: (msg = 'Not Implemented', contentType?: string) => createErrorResponse(501, msg, contentType),
|
|
181
145
|
|
|
182
|
-
badGateway: (msg = 'Bad Gateway', contentType?: string) =>
|
|
183
|
-
createErrorResponse(502, msg, contentType),
|
|
146
|
+
badGateway: (msg = 'Bad Gateway', contentType?: string) => createErrorResponse(502, msg, contentType),
|
|
184
147
|
|
|
185
|
-
serviceUnavailable: (msg = 'Service Unavailable', contentType?: string) =>
|
|
186
|
-
createErrorResponse(503, msg, contentType),
|
|
148
|
+
serviceUnavailable: (msg = 'Service Unavailable', contentType?: string) => createErrorResponse(503, msg, contentType),
|
|
187
149
|
|
|
188
|
-
gatewayTimeout: (msg = 'Gateway Timeout', contentType?: string) =>
|
|
189
|
-
createErrorResponse(504, msg, contentType),
|
|
150
|
+
gatewayTimeout: (msg = 'Gateway Timeout', contentType?: string) => createErrorResponse(504, msg, contentType),
|
|
190
151
|
|
|
191
152
|
httpVersionNotSupported: (msg = 'HTTP Version Not Supported', contentType?: string) =>
|
|
192
153
|
createErrorResponse(505, msg, contentType),
|
|
@@ -197,11 +158,9 @@ export const APIError = {
|
|
|
197
158
|
insufficientStorage: (msg = 'Insufficient Storage', contentType?: string) =>
|
|
198
159
|
createErrorResponse(507, msg, contentType),
|
|
199
160
|
|
|
200
|
-
loopDetected: (msg = 'Loop Detected', contentType?: string) =>
|
|
201
|
-
createErrorResponse(508, msg, contentType),
|
|
161
|
+
loopDetected: (msg = 'Loop Detected', contentType?: string) => createErrorResponse(508, msg, contentType),
|
|
202
162
|
|
|
203
|
-
notExtended: (msg = 'Not Extended', contentType?: string) =>
|
|
204
|
-
createErrorResponse(510, msg, contentType),
|
|
163
|
+
notExtended: (msg = 'Not Extended', contentType?: string) => createErrorResponse(510, msg, contentType),
|
|
205
164
|
|
|
206
165
|
networkAuthenticationRequired: (msg = 'Network Authentication Required', contentType?: string) =>
|
|
207
166
|
createErrorResponse(511, msg, contentType),
|
|
@@ -210,22 +169,15 @@ export const APIError = {
|
|
|
210
169
|
invalidArgument: (msg = 'Invalid Argument', contentType?: string) =>
|
|
211
170
|
createErrorResponse(HTTP_STATUS.UNPROCESSABLE_ENTITY, msg, contentType),
|
|
212
171
|
|
|
213
|
-
rateLimitExceeded: (msg = 'Rate Limit Exceeded', contentType?: string) =>
|
|
214
|
-
createErrorResponse(429, msg, contentType),
|
|
172
|
+
rateLimitExceeded: (msg = 'Rate Limit Exceeded', contentType?: string) => createErrorResponse(429, msg, contentType),
|
|
215
173
|
|
|
216
|
-
maintenance: (msg = 'Service Under Maintenance', contentType?: string) =>
|
|
217
|
-
createErrorResponse(503, msg, contentType),
|
|
174
|
+
maintenance: (msg = 'Service Under Maintenance', contentType?: string) => createErrorResponse(503, msg, contentType),
|
|
218
175
|
|
|
219
176
|
// Helper to create custom error with any status code
|
|
220
|
-
custom: (statusCode: number, msg: string, contentType?: string) =>
|
|
221
|
-
createErrorResponse(statusCode, msg, contentType),
|
|
177
|
+
custom: (statusCode: number, msg: string, contentType?: string) => createErrorResponse(statusCode, msg, contentType),
|
|
222
178
|
};
|
|
223
179
|
|
|
224
|
-
export function createResponse(
|
|
225
|
-
statusCode: number,
|
|
226
|
-
data?: unknown,
|
|
227
|
-
contentType: string = CONTENT_TYPES.JSON
|
|
228
|
-
): Response {
|
|
180
|
+
export function createResponse(statusCode: number, data?: unknown, contentType: string = CONTENT_TYPES.JSON): Response {
|
|
229
181
|
const body = contentType === CONTENT_TYPES.JSON ? stringifyData(data) : data;
|
|
230
182
|
|
|
231
183
|
return new Response(body as string, {
|
|
@@ -238,7 +190,6 @@ export const protectedRoute = async <TTypes extends VectorTypes = DefaultVectorT
|
|
|
238
190
|
request: VectorRequest<TTypes>,
|
|
239
191
|
responseContentType?: string
|
|
240
192
|
) => {
|
|
241
|
-
// Get the Vector instance to access the protected handler
|
|
242
193
|
const vector = getVectorInstance();
|
|
243
194
|
|
|
244
195
|
const protectedHandler = vector.getProtectedHandler();
|
|
@@ -250,25 +201,24 @@ export const protectedRoute = async <TTypes extends VectorTypes = DefaultVectorT
|
|
|
250
201
|
const authUser = await protectedHandler(request as any);
|
|
251
202
|
request.authUser = authUser as GetAuthType<TTypes>;
|
|
252
203
|
} catch (error) {
|
|
253
|
-
throw APIError.unauthorized(
|
|
254
|
-
error instanceof Error ? error.message : 'Authentication failed',
|
|
255
|
-
responseContentType
|
|
256
|
-
);
|
|
204
|
+
throw APIError.unauthorized(error instanceof Error ? error.message : 'Authentication failed', responseContentType);
|
|
257
205
|
}
|
|
258
206
|
};
|
|
259
207
|
|
|
260
|
-
export interface ApiOptions {
|
|
208
|
+
export interface ApiOptions<TSchemaDef extends RouteSchemaDefinition | undefined = RouteSchemaDefinition | undefined> {
|
|
261
209
|
auth?: boolean;
|
|
262
210
|
expose?: boolean;
|
|
263
211
|
rawRequest?: boolean;
|
|
212
|
+
validate?: boolean;
|
|
264
213
|
rawResponse?: boolean;
|
|
265
214
|
cache?: CacheOptions | number | null;
|
|
266
215
|
responseContentType?: string;
|
|
216
|
+
schema?: TSchemaDef;
|
|
267
217
|
}
|
|
268
218
|
|
|
269
|
-
export function api<TTypes extends VectorTypes = DefaultVectorTypes>(
|
|
219
|
+
export function api<TTypes extends VectorTypes = DefaultVectorTypes, TValidatedInput = undefined>(
|
|
270
220
|
options: ApiOptions,
|
|
271
|
-
fn: (request: VectorRequest<TTypes>) => Promise<unknown>
|
|
221
|
+
fn: (request: VectorRequest<TTypes, TValidatedInput>) => Promise<unknown> | unknown
|
|
272
222
|
) {
|
|
273
223
|
const {
|
|
274
224
|
auth = false,
|
|
@@ -278,32 +228,42 @@ export function api<TTypes extends VectorTypes = DefaultVectorTypes>(
|
|
|
278
228
|
responseContentType = CONTENT_TYPES.JSON,
|
|
279
229
|
} = options;
|
|
280
230
|
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
231
|
+
return async (request: Request) => {
|
|
232
|
+
const req = request as unknown as VectorRequest<TTypes>;
|
|
233
|
+
|
|
284
234
|
if (!expose) {
|
|
285
235
|
return APIError.forbidden('Forbidden');
|
|
286
236
|
}
|
|
287
237
|
|
|
288
238
|
try {
|
|
289
239
|
if (auth) {
|
|
290
|
-
await protectedRoute(
|
|
240
|
+
await protectedRoute(req, responseContentType);
|
|
291
241
|
}
|
|
292
242
|
|
|
293
|
-
if (!rawRequest) {
|
|
294
|
-
|
|
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
|
+
}
|
|
295
258
|
}
|
|
296
259
|
|
|
297
|
-
|
|
298
|
-
const result = await fn(request as any as VectorRequest<TTypes>);
|
|
260
|
+
const result = await fn(req as unknown as VectorRequest<TTypes, TValidatedInput>);
|
|
299
261
|
|
|
300
262
|
return rawResponse ? result : ApiResponse.success(result, responseContentType);
|
|
301
263
|
} catch (err: unknown) {
|
|
302
|
-
// Ensure we return a Response object
|
|
303
264
|
if (err instanceof Response) {
|
|
304
265
|
return err;
|
|
305
266
|
}
|
|
306
|
-
// For non-Response errors, wrap them
|
|
307
267
|
return APIError.internalServerError(String(err), responseContentType);
|
|
308
268
|
}
|
|
309
269
|
};
|
|
@@ -19,6 +19,8 @@ export class MiddlewareManager<TTypes extends VectorTypes = DefaultVectorTypes>
|
|
|
19
19
|
}
|
|
20
20
|
|
|
21
21
|
async executeBefore(request: VectorRequest<TTypes>): Promise<VectorRequest<TTypes> | Response> {
|
|
22
|
+
if (this.beforeHandlers.length === 0) return request;
|
|
23
|
+
|
|
22
24
|
let currentRequest = request;
|
|
23
25
|
|
|
24
26
|
for (const handler of this.beforeHandlers) {
|
|
@@ -35,6 +37,8 @@ export class MiddlewareManager<TTypes extends VectorTypes = DefaultVectorTypes>
|
|
|
35
37
|
}
|
|
36
38
|
|
|
37
39
|
async executeFinally(response: Response, request: VectorRequest<TTypes>): Promise<Response> {
|
|
40
|
+
if (this.finallyHandlers.length === 0) return response;
|
|
41
|
+
|
|
38
42
|
let currentResponse = response;
|
|
39
43
|
|
|
40
44
|
for (const handler of this.finallyHandlers) {
|