vector-framework 0.8.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (84) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +508 -0
  3. package/dist/auth/protected.d.ts +9 -0
  4. package/dist/auth/protected.d.ts.map +1 -0
  5. package/dist/auth/protected.js +26 -0
  6. package/dist/auth/protected.js.map +1 -0
  7. package/dist/cache/manager.d.ts +21 -0
  8. package/dist/cache/manager.d.ts.map +1 -0
  9. package/dist/cache/manager.js +92 -0
  10. package/dist/cache/manager.js.map +1 -0
  11. package/dist/cli/index.d.ts +3 -0
  12. package/dist/cli/index.d.ts.map +1 -0
  13. package/dist/cli/index.js +142 -0
  14. package/dist/cli/index.js.map +1 -0
  15. package/dist/constants/index.d.ts +84 -0
  16. package/dist/constants/index.d.ts.map +1 -0
  17. package/dist/constants/index.js +88 -0
  18. package/dist/constants/index.js.map +1 -0
  19. package/dist/core/router.d.ts +26 -0
  20. package/dist/core/router.d.ts.map +1 -0
  21. package/dist/core/router.js +208 -0
  22. package/dist/core/router.js.map +1 -0
  23. package/dist/core/server.d.ts +18 -0
  24. package/dist/core/server.d.ts.map +1 -0
  25. package/dist/core/server.js +89 -0
  26. package/dist/core/server.js.map +1 -0
  27. package/dist/core/vector.d.ts +43 -0
  28. package/dist/core/vector.d.ts.map +1 -0
  29. package/dist/core/vector.js +179 -0
  30. package/dist/core/vector.js.map +1 -0
  31. package/dist/dev/route-generator.d.ts +8 -0
  32. package/dist/dev/route-generator.d.ts.map +1 -0
  33. package/dist/dev/route-generator.js +77 -0
  34. package/dist/dev/route-generator.js.map +1 -0
  35. package/dist/dev/route-scanner.d.ts +9 -0
  36. package/dist/dev/route-scanner.d.ts.map +1 -0
  37. package/dist/dev/route-scanner.js +85 -0
  38. package/dist/dev/route-scanner.js.map +1 -0
  39. package/dist/errors/index.d.ts +24 -0
  40. package/dist/errors/index.d.ts.map +1 -0
  41. package/dist/errors/index.js +73 -0
  42. package/dist/errors/index.js.map +1 -0
  43. package/dist/http.d.ts +73 -0
  44. package/dist/http.d.ts.map +1 -0
  45. package/dist/http.js +143 -0
  46. package/dist/http.js.map +1 -0
  47. package/dist/index.d.ts +13 -0
  48. package/dist/index.d.ts.map +1 -0
  49. package/dist/index.js +21 -0
  50. package/dist/index.js.map +1 -0
  51. package/dist/index.mjs +21 -0
  52. package/dist/middleware/manager.d.ts +11 -0
  53. package/dist/middleware/manager.d.ts.map +1 -0
  54. package/dist/middleware/manager.js +35 -0
  55. package/dist/middleware/manager.js.map +1 -0
  56. package/dist/types/index.d.ts +85 -0
  57. package/dist/types/index.d.ts.map +1 -0
  58. package/dist/types/index.js +2 -0
  59. package/dist/types/index.js.map +1 -0
  60. package/dist/utils/logger.d.ts +25 -0
  61. package/dist/utils/logger.d.ts.map +1 -0
  62. package/dist/utils/logger.js +68 -0
  63. package/dist/utils/logger.js.map +1 -0
  64. package/dist/utils/validation.d.ts +5 -0
  65. package/dist/utils/validation.d.ts.map +1 -0
  66. package/dist/utils/validation.js +48 -0
  67. package/dist/utils/validation.js.map +1 -0
  68. package/package.json +110 -0
  69. package/src/auth/protected.ts +41 -0
  70. package/src/cache/manager.ts +133 -0
  71. package/src/cli/index.ts +157 -0
  72. package/src/constants/index.ts +93 -0
  73. package/src/core/router.ts +258 -0
  74. package/src/core/server.ts +107 -0
  75. package/src/core/vector.ts +228 -0
  76. package/src/dev/route-generator.ts +93 -0
  77. package/src/dev/route-scanner.ts +97 -0
  78. package/src/errors/index.ts +91 -0
  79. package/src/http.ts +331 -0
  80. package/src/index.ts +19 -0
  81. package/src/middleware/manager.ts +53 -0
  82. package/src/types/index.ts +126 -0
  83. package/src/utils/logger.ts +87 -0
  84. package/src/utils/validation.ts +58 -0
@@ -0,0 +1,97 @@
1
+ import { readdir, stat } from 'node:fs/promises';
2
+ import { join, relative, resolve, sep } from 'node:path';
3
+ import type { GeneratedRoute } from '../types';
4
+
5
+ export class RouteScanner {
6
+ private routesDir: string;
7
+
8
+ constructor(routesDir = './routes') {
9
+ this.routesDir = resolve(process.cwd(), routesDir);
10
+ }
11
+
12
+ async scan(): Promise<GeneratedRoute[]> {
13
+ const routes: GeneratedRoute[] = [];
14
+
15
+ try {
16
+ await this.scanDirectory(this.routesDir, routes);
17
+ } catch (error) {
18
+ if ((error as any).code === 'ENOENT') {
19
+ console.warn(`Routes directory not found: ${this.routesDir}`);
20
+ return [];
21
+ }
22
+ throw error;
23
+ }
24
+
25
+ return routes;
26
+ }
27
+
28
+ private async scanDirectory(dir: string, routes: GeneratedRoute[], basePath = ''): Promise<void> {
29
+ const entries = await readdir(dir);
30
+
31
+ for (const entry of entries) {
32
+ const fullPath = join(dir, entry);
33
+ const stats = await stat(fullPath);
34
+
35
+ if (stats.isDirectory()) {
36
+ const newBasePath = basePath ? `${basePath}/${entry}` : entry;
37
+ await this.scanDirectory(fullPath, routes, newBasePath);
38
+ } else if (entry.endsWith('.ts') || entry.endsWith('.js')) {
39
+ const routePath = relative(this.routesDir, fullPath)
40
+ .replace(/\.(ts|js)$/, '')
41
+ .split(sep)
42
+ .join('/');
43
+
44
+ try {
45
+ // Convert Windows paths to URLs for import
46
+ const importPath =
47
+ process.platform === 'win32' ? `file:///${fullPath.replace(/\\/g, '/')}` : fullPath;
48
+
49
+ const module = await import(importPath);
50
+
51
+ if (module.default && typeof module.default === 'function') {
52
+ routes.push({
53
+ name: 'default',
54
+ path: fullPath,
55
+ method: 'GET',
56
+ options: {
57
+ method: 'GET',
58
+ path: `/${routePath}`,
59
+ expose: true,
60
+ },
61
+ });
62
+ }
63
+
64
+ for (const [name, value] of Object.entries(module)) {
65
+ if (name === 'default') continue;
66
+
67
+ if (Array.isArray(value) && value.length >= 4) {
68
+ const [method, , , path] = value;
69
+ routes.push({
70
+ name,
71
+ path: fullPath,
72
+ method: method as string,
73
+ options: {
74
+ method: method as string,
75
+ path: path as string,
76
+ expose: true,
77
+ },
78
+ });
79
+ }
80
+ }
81
+ } catch (error) {
82
+ console.error(`Failed to load route from ${fullPath}:`, error);
83
+ }
84
+ }
85
+ }
86
+ }
87
+
88
+ enableWatch(callback: () => void) {
89
+ if (typeof Bun !== 'undefined' && Bun.env.NODE_ENV === 'development') {
90
+ console.log(`Watching for route changes in ${this.routesDir}`);
91
+
92
+ setInterval(async () => {
93
+ await callback();
94
+ }, 1000);
95
+ }
96
+ }
97
+ }
@@ -0,0 +1,91 @@
1
+ export class VectorError extends Error {
2
+ constructor(
3
+ message: string,
4
+ public readonly code: string,
5
+ public readonly statusCode?: number
6
+ ) {
7
+ super(message);
8
+ this.name = 'VectorError';
9
+ }
10
+ }
11
+
12
+ export class AuthenticationError extends VectorError {
13
+ constructor(message = 'Authentication failed') {
14
+ super(message, 'AUTH_ERROR', 401);
15
+ this.name = 'AuthenticationError';
16
+ }
17
+ }
18
+
19
+ export class ValidationError extends VectorError {
20
+ constructor(
21
+ message: string,
22
+ public readonly field?: string
23
+ ) {
24
+ super(message, 'VALIDATION_ERROR', 400);
25
+ this.name = 'ValidationError';
26
+ }
27
+ }
28
+
29
+ export class RouteNotFoundError extends VectorError {
30
+ constructor(path: string) {
31
+ super(`Route not found: ${path}`, 'ROUTE_NOT_FOUND', 404);
32
+ this.name = 'RouteNotFoundError';
33
+ }
34
+ }
35
+
36
+ export class ConfigurationError extends VectorError {
37
+ constructor(message: string) {
38
+ super(message, 'CONFIG_ERROR');
39
+ this.name = 'ConfigurationError';
40
+ }
41
+ }
42
+
43
+ export class ServerError extends VectorError {
44
+ constructor(message = 'Internal server error') {
45
+ super(message, 'SERVER_ERROR', 500);
46
+ this.name = 'ServerError';
47
+ }
48
+ }
49
+
50
+ export function isVectorError(error: unknown): error is VectorError {
51
+ return error instanceof VectorError;
52
+ }
53
+
54
+ export function handleError(error: unknown): Response {
55
+ if (isVectorError(error)) {
56
+ return new Response(
57
+ JSON.stringify({
58
+ error: error.message,
59
+ code: error.code,
60
+ }),
61
+ {
62
+ status: error.statusCode || 500,
63
+ headers: { 'content-type': 'application/json' },
64
+ }
65
+ );
66
+ }
67
+
68
+ if (error instanceof Error) {
69
+ return new Response(
70
+ JSON.stringify({
71
+ error: error.message,
72
+ code: 'UNKNOWN_ERROR',
73
+ }),
74
+ {
75
+ status: 500,
76
+ headers: { 'content-type': 'application/json' },
77
+ }
78
+ );
79
+ }
80
+
81
+ return new Response(
82
+ JSON.stringify({
83
+ error: 'An unknown error occurred',
84
+ code: 'UNKNOWN_ERROR',
85
+ }),
86
+ {
87
+ status: 500,
88
+ headers: { 'content-type': 'application/json' },
89
+ }
90
+ );
91
+ }
package/src/http.ts ADDED
@@ -0,0 +1,331 @@
1
+ import {
2
+ cors,
3
+ type IRequest,
4
+ type RouteEntry,
5
+ withContent,
6
+ withCookies,
7
+ } from "itty-router";
8
+ import { CONTENT_TYPES, HTTP_STATUS } from "./constants";
9
+ import type {
10
+ DefaultVectorTypes,
11
+ GetAuthType,
12
+ VectorRequest,
13
+ VectorTypes,
14
+ } from "./types";
15
+
16
+ export interface ProtectedRequest<
17
+ TTypes extends VectorTypes = DefaultVectorTypes
18
+ > extends IRequest {
19
+ authUser?: GetAuthType<TTypes>;
20
+ }
21
+
22
+ export const { preflight, corsify } = cors({
23
+ origin: "*",
24
+ credentials: true,
25
+ allowHeaders: "Content-Type, Authorization",
26
+ allowMethods: "GET, POST, PUT, PATCH, DELETE, OPTIONS",
27
+ exposeHeaders: "Authorization",
28
+ maxAge: 86_400,
29
+ });
30
+
31
+ interface ExtendedApiOptions extends ApiOptions {
32
+ method: string;
33
+ path: string;
34
+ }
35
+
36
+ export function route<TTypes extends VectorTypes = DefaultVectorTypes>(
37
+ options: ExtendedApiOptions,
38
+ fn: (req: VectorRequest<TTypes>) => Promise<unknown>
39
+ ): RouteEntry {
40
+ const handler = api(options, fn);
41
+
42
+ return [
43
+ options.method.toUpperCase(),
44
+ RegExp(
45
+ `^${
46
+ options.path
47
+ .replace(/\/+(\/|$)/g, "$1") // strip double & trailing splash
48
+ .replace(/(\/?\.?):(\w+)\+/g, "($1(?<$2>*))") // greedy params
49
+ .replace(/(\/?\.?):(\w+)/g, "($1(?<$2>[^$1/]+?))") // named params and image format
50
+ .replace(/\./g, "\\.") // dot in path
51
+ .replace(/(\/?)\*/g, "($1.*)?") // wildcard
52
+ }/*$`
53
+ ),
54
+ [handler],
55
+ options.path,
56
+ ];
57
+ }
58
+
59
+ function stringifyData(data: unknown): string {
60
+ return JSON.stringify(data ?? null, (_key, value) =>
61
+ typeof value === "bigint" ? value.toString() : value
62
+ );
63
+ }
64
+
65
+ const ApiResponse = {
66
+ success: <T>(data: T, contentType?: string) =>
67
+ createResponse(HTTP_STATUS.OK, data, contentType),
68
+ created: <T>(data: T, contentType?: string) =>
69
+ createResponse(HTTP_STATUS.CREATED, data, contentType),
70
+ };
71
+
72
+ function createErrorResponse(
73
+ code: number,
74
+ message: string,
75
+ contentType?: string
76
+ ): Response {
77
+ const errorBody = {
78
+ error: true,
79
+ message,
80
+ statusCode: code,
81
+ timestamp: new Date().toISOString(),
82
+ };
83
+
84
+ return createResponse(code, errorBody, contentType);
85
+ }
86
+
87
+ export const APIError = {
88
+ // 4xx Client Errors
89
+ badRequest: (msg = "Bad Request", contentType?: string) =>
90
+ createErrorResponse(HTTP_STATUS.BAD_REQUEST, msg, contentType),
91
+
92
+ unauthorized: (msg = "Unauthorized", contentType?: string) =>
93
+ createErrorResponse(HTTP_STATUS.UNAUTHORIZED, msg, contentType),
94
+
95
+ paymentRequired: (msg = "Payment Required", contentType?: string) =>
96
+ createErrorResponse(402, msg, contentType),
97
+
98
+ forbidden: (msg = "Forbidden", contentType?: string) =>
99
+ createErrorResponse(HTTP_STATUS.FORBIDDEN, msg, contentType),
100
+
101
+ notFound: (msg = "Not Found", contentType?: string) =>
102
+ createErrorResponse(HTTP_STATUS.NOT_FOUND, msg, contentType),
103
+
104
+ methodNotAllowed: (msg = "Method Not Allowed", contentType?: string) =>
105
+ createErrorResponse(405, msg, contentType),
106
+
107
+ notAcceptable: (msg = "Not Acceptable", contentType?: string) =>
108
+ createErrorResponse(406, msg, contentType),
109
+
110
+ requestTimeout: (msg = "Request Timeout", contentType?: string) =>
111
+ createErrorResponse(408, msg, contentType),
112
+
113
+ conflict: (msg = "Conflict", contentType?: string) =>
114
+ createErrorResponse(HTTP_STATUS.CONFLICT, msg, contentType),
115
+
116
+ gone: (msg = "Gone", contentType?: string) =>
117
+ createErrorResponse(410, msg, contentType),
118
+
119
+ lengthRequired: (msg = "Length Required", contentType?: string) =>
120
+ createErrorResponse(411, msg, contentType),
121
+
122
+ preconditionFailed: (msg = "Precondition Failed", contentType?: string) =>
123
+ createErrorResponse(412, msg, contentType),
124
+
125
+ payloadTooLarge: (msg = "Payload Too Large", contentType?: string) =>
126
+ createErrorResponse(413, msg, contentType),
127
+
128
+ uriTooLong: (msg = "URI Too Long", contentType?: string) =>
129
+ createErrorResponse(414, msg, contentType),
130
+
131
+ unsupportedMediaType: (
132
+ msg = "Unsupported Media Type",
133
+ contentType?: string
134
+ ) => createErrorResponse(415, msg, contentType),
135
+
136
+ rangeNotSatisfiable: (msg = "Range Not Satisfiable", contentType?: string) =>
137
+ createErrorResponse(416, msg, contentType),
138
+
139
+ expectationFailed: (msg = "Expectation Failed", contentType?: string) =>
140
+ createErrorResponse(417, msg, contentType),
141
+
142
+ imATeapot: (msg = "I'm a teapot", contentType?: string) =>
143
+ createErrorResponse(418, msg, contentType),
144
+
145
+ misdirectedRequest: (msg = "Misdirected Request", contentType?: string) =>
146
+ createErrorResponse(421, msg, contentType),
147
+
148
+ unprocessableEntity: (msg = "Unprocessable Entity", contentType?: string) =>
149
+ createErrorResponse(HTTP_STATUS.UNPROCESSABLE_ENTITY, msg, contentType),
150
+
151
+ locked: (msg = "Locked", contentType?: string) =>
152
+ createErrorResponse(423, msg, contentType),
153
+
154
+ failedDependency: (msg = "Failed Dependency", contentType?: string) =>
155
+ createErrorResponse(424, msg, contentType),
156
+
157
+ tooEarly: (msg = "Too Early", contentType?: string) =>
158
+ createErrorResponse(425, msg, contentType),
159
+
160
+ upgradeRequired: (msg = "Upgrade Required", contentType?: string) =>
161
+ createErrorResponse(426, msg, contentType),
162
+
163
+ preconditionRequired: (msg = "Precondition Required", contentType?: string) =>
164
+ createErrorResponse(428, msg, contentType),
165
+
166
+ tooManyRequests: (msg = "Too Many Requests", contentType?: string) =>
167
+ createErrorResponse(429, msg, contentType),
168
+
169
+ requestHeaderFieldsTooLarge: (
170
+ msg = "Request Header Fields Too Large",
171
+ contentType?: string
172
+ ) => createErrorResponse(431, msg, contentType),
173
+
174
+ unavailableForLegalReasons: (
175
+ msg = "Unavailable For Legal Reasons",
176
+ contentType?: string
177
+ ) => createErrorResponse(451, msg, contentType),
178
+
179
+ // 5xx Server Errors
180
+ internalServerError: (msg = "Internal Server Error", contentType?: string) =>
181
+ createErrorResponse(HTTP_STATUS.INTERNAL_SERVER_ERROR, msg, contentType),
182
+
183
+ notImplemented: (msg = "Not Implemented", contentType?: string) =>
184
+ createErrorResponse(501, msg, contentType),
185
+
186
+ badGateway: (msg = "Bad Gateway", contentType?: string) =>
187
+ createErrorResponse(502, msg, contentType),
188
+
189
+ serviceUnavailable: (msg = "Service Unavailable", contentType?: string) =>
190
+ createErrorResponse(503, msg, contentType),
191
+
192
+ gatewayTimeout: (msg = "Gateway Timeout", contentType?: string) =>
193
+ createErrorResponse(504, msg, contentType),
194
+
195
+ httpVersionNotSupported: (
196
+ msg = "HTTP Version Not Supported",
197
+ contentType?: string
198
+ ) => createErrorResponse(505, msg, contentType),
199
+
200
+ variantAlsoNegotiates: (
201
+ msg = "Variant Also Negotiates",
202
+ contentType?: string
203
+ ) => createErrorResponse(506, msg, contentType),
204
+
205
+ insufficientStorage: (msg = "Insufficient Storage", contentType?: string) =>
206
+ createErrorResponse(507, msg, contentType),
207
+
208
+ loopDetected: (msg = "Loop Detected", contentType?: string) =>
209
+ createErrorResponse(508, msg, contentType),
210
+
211
+ notExtended: (msg = "Not Extended", contentType?: string) =>
212
+ createErrorResponse(510, msg, contentType),
213
+
214
+ networkAuthenticationRequired: (
215
+ msg = "Network Authentication Required",
216
+ contentType?: string
217
+ ) => createErrorResponse(511, msg, contentType),
218
+
219
+ // Aliases for common use cases
220
+ invalidArgument: (msg = "Invalid Argument", contentType?: string) =>
221
+ createErrorResponse(HTTP_STATUS.UNPROCESSABLE_ENTITY, msg, contentType),
222
+
223
+ rateLimitExceeded: (msg = "Rate Limit Exceeded", contentType?: string) =>
224
+ createErrorResponse(429, msg, contentType),
225
+
226
+ maintenance: (msg = "Service Under Maintenance", contentType?: string) =>
227
+ createErrorResponse(503, msg, contentType),
228
+
229
+ // Helper to create custom error with any status code
230
+ custom: (statusCode: number, msg: string, contentType?: string) =>
231
+ createErrorResponse(statusCode, msg, contentType),
232
+ };
233
+
234
+ export function createResponse(
235
+ statusCode: number,
236
+ data?: unknown,
237
+ contentType: string = CONTENT_TYPES.JSON
238
+ ): Response {
239
+ const body = contentType === CONTENT_TYPES.JSON ? stringifyData(data) : data;
240
+
241
+ return new Response(body as string, {
242
+ status: statusCode,
243
+ headers: { "content-type": contentType },
244
+ });
245
+ }
246
+
247
+ export const protectedRoute = async <
248
+ TTypes extends VectorTypes = DefaultVectorTypes
249
+ >(
250
+ request: VectorRequest<TTypes>,
251
+ responseContentType?: string
252
+ ) => {
253
+ // Get the Vector instance to access the protected handler
254
+ const vector = (await import("./core/vector")).default;
255
+
256
+ if (!vector.protected) {
257
+ throw APIError.unauthorized(
258
+ "Authentication not configured",
259
+ responseContentType
260
+ );
261
+ }
262
+
263
+ try {
264
+ const authUser = await vector.protected(request as any);
265
+ request.authUser = authUser as GetAuthType<TTypes>;
266
+ } catch (error) {
267
+ throw APIError.unauthorized(
268
+ error instanceof Error ? error.message : "Authentication failed",
269
+ responseContentType
270
+ );
271
+ }
272
+ };
273
+
274
+ export interface ApiOptions {
275
+ auth?: boolean;
276
+ expose?: boolean;
277
+ rawRequest?: boolean;
278
+ rawResponse?: boolean;
279
+ cache?: number | null;
280
+ responseContentType?: string;
281
+ }
282
+
283
+ export function api<TTypes extends VectorTypes = DefaultVectorTypes>(
284
+ options: ApiOptions,
285
+ fn: (request: VectorRequest<TTypes>) => Promise<unknown>
286
+ ) {
287
+ const {
288
+ auth = false,
289
+ expose = false,
290
+ rawRequest = false,
291
+ rawResponse = false,
292
+ responseContentType = CONTENT_TYPES.JSON,
293
+ } = options;
294
+
295
+ return async (request: IRequest) => {
296
+ if (!expose) {
297
+ return APIError.forbidden("Forbidden");
298
+ }
299
+
300
+ try {
301
+ if (auth) {
302
+ await protectedRoute(
303
+ request as any as VectorRequest<TTypes>,
304
+ responseContentType
305
+ );
306
+ }
307
+
308
+ if (!rawRequest) {
309
+ await withContent(request);
310
+ }
311
+
312
+ withCookies(request);
313
+
314
+ // Cache handling is now done in the router
315
+ const result = await fn(request as any as VectorRequest<TTypes>);
316
+
317
+ return rawResponse
318
+ ? result
319
+ : ApiResponse.success(result, responseContentType);
320
+ } catch (err: unknown) {
321
+ // Ensure we return a Response object
322
+ if (err instanceof Response) {
323
+ return err;
324
+ }
325
+ // For non-Response errors, wrap them
326
+ return APIError.internalServerError(String(err), responseContentType);
327
+ }
328
+ };
329
+ }
330
+
331
+ export default ApiResponse;
package/src/index.ts ADDED
@@ -0,0 +1,19 @@
1
+ import { Vector } from './core/vector';
2
+ import { route } from './http';
3
+ import type { DefaultVectorTypes, VectorTypes } from './types';
4
+
5
+ export { route, Vector };
6
+ export { AuthManager } from './auth/protected';
7
+ export { CacheManager } from './cache/manager';
8
+ export { APIError, createResponse } from './http';
9
+ export { MiddlewareManager } from './middleware/manager';
10
+ export * from './types';
11
+
12
+ // Create a typed Vector instance with custom types
13
+ export function createVector<TTypes extends VectorTypes = DefaultVectorTypes>(): Vector<TTypes> {
14
+ return Vector.getInstance<TTypes>();
15
+ }
16
+
17
+ // Default vector instance with default AuthUser type
18
+ const vector = Vector.getInstance();
19
+ export default vector;
@@ -0,0 +1,53 @@
1
+ import type {
2
+ AfterMiddlewareHandler,
3
+ BeforeMiddlewareHandler,
4
+ DefaultVectorTypes,
5
+ VectorRequest,
6
+ VectorTypes,
7
+ } from '../types';
8
+
9
+ export class MiddlewareManager<TTypes extends VectorTypes = DefaultVectorTypes> {
10
+ private beforeHandlers: BeforeMiddlewareHandler<TTypes>[] = [];
11
+ private finallyHandlers: AfterMiddlewareHandler<TTypes>[] = [];
12
+
13
+ addBefore(...handlers: BeforeMiddlewareHandler<TTypes>[]): void {
14
+ this.beforeHandlers.push(...handlers);
15
+ }
16
+
17
+ addFinally(...handlers: AfterMiddlewareHandler<TTypes>[]): void {
18
+ this.finallyHandlers.push(...handlers);
19
+ }
20
+
21
+ async executeBefore(request: VectorRequest<TTypes>): Promise<VectorRequest<TTypes> | Response> {
22
+ let currentRequest = request;
23
+
24
+ for (const handler of this.beforeHandlers) {
25
+ const result = await handler(currentRequest);
26
+
27
+ if (result instanceof Response) {
28
+ return result;
29
+ }
30
+
31
+ currentRequest = result;
32
+ }
33
+
34
+ return currentRequest;
35
+ }
36
+
37
+ async executeFinally(response: Response, request: VectorRequest<TTypes>): Promise<Response> {
38
+ let currentResponse = response;
39
+
40
+ for (const handler of this.finallyHandlers) {
41
+ currentResponse = await handler(currentResponse, request);
42
+ }
43
+
44
+ return currentResponse;
45
+ }
46
+
47
+ clone(): MiddlewareManager<TTypes> {
48
+ const manager = new MiddlewareManager<TTypes>();
49
+ manager.beforeHandlers = [...this.beforeHandlers];
50
+ manager.finallyHandlers = [...this.finallyHandlers];
51
+ return manager;
52
+ }
53
+ }